音乐播放器的链表实现


image
本篇文章将会使用链表实现一个简易音乐播放器。其中,涉及到的知识有三种链表的构造,链表的操作。

三种链表:

  1. 单链表
  2. 双链表
  3. 循环链表

链表的操作:

  • 元素的查找
  • 元素的插入和删除

1. 播放器的单链表实现

image
image
首先实现一个单链表的音乐播放器。它有两个函数 create_playlist 和 show_playlist 组成。其中,create_playlist 用于构建音乐播放器,它实际实现的是链表的插入操作。show_playlist 用于单向查找音乐在将其显示出来,它实际实现的是链表的查找操作。

package main

import "fmt"

var init_head head

type head struct {
	next *song
}

type song struct {
	name   string
	artist string
	next   *song
}

func create_playlist(head_pointer *head, current_song *song) {
	if head_pointer.next != nil {
		tmp := head_pointer.next
		for tmp.next != nil {
			tmp = tmp.next
		}
		tmp.next = current_song
	} else {
		head_pointer.next = current_song
	}
}

func show_playlist(playlist *head) {
	if playlist.next != nil {
		for tmp := playlist.next; tmp != nil; tmp = tmp.next {
			fmt.Printf("playing %s by %s
", tmp.name, tmp.artist)
		}
	} else {
		fmt.Printf("It is an empty playlist")
	}
}

func main() {
	create_playlist(&init_head, &song{name: "理想三旬", artist: "陈鸿宇"})
	create_playlist(&init_head, &song{name: "蓝莲花", artist: "许巍"})
	create_playlist(&init_head, &song{name: "乱舞春秋", artist: "周杰伦"})
	create_playlist(&init_head, &song{name: "稻香", artist: "周杰伦"})
	create_playlist(&init_head, &song{name: "和你在一起", artist: "李志"})
	show_playlist(&init_head)
}

这里使用了 head 结构体作为结点的头指针,使用全局变量 init_head 保存头指针的地址,在创建和 show playlist 时需要将头指针作为参数传递给函数。
从上述示例中可以看出:

  • 链表的尾插入复杂度为 O(n)。每次都要循环迭代找到尾节点(复杂度 O(n)),然后将当前节点和尾节点关联(复杂度o(1))。
  • 头指针是非常重要的,丢失头指针将无法进行链表的查找,插入等操作。

2. 播放器的双链表实现

image
单链表播放器只能单向查找,单向插入,实现音乐的下一首功能而无法实现上一首功能。基于此,这里使用双链表进行重新改造,并且使用方法而不是函数来实现音乐播放器的添加和显示功能。

package main

import (
	"fmt"
	"strings"
)

var current_song string

type playlist struct {
	next *song
	tail *song
}

type song struct {
	name     string
	artist   string
	next     *song
	previous *song
}

func player_song() {
	fmt.Printf("play song: ")
	fmt.Scanln(&current_song)
}

func (player *playlist) add_song(name string, artist string) {
	current_song := &song{name: name, artist: artist}
	if player.next != nil {
		player.tail.next = current_song
		player.tail.next.previous = player.tail
		player.tail = current_song
	} else {
		player.next = current_song
		player.tail = current_song
	}
}

func (player *playlist) next_song() {
	if current_song == "" {
		fmt.Printf("The current song is empty, please playing a song first.
")
		player_song()
	}
	if player.next != nil {
		tmp := player.next
		for tmp != nil {
			if strings.EqualFold(tmp.name, current_song) {
				if tmp.next != nil {
					fmt.Printf("the current song is %s by %s
", tmp.name, tmp.artist)
					fmt.Printf("the next song is %s by %s
", tmp.next.name, tmp.next.artist)
					current_song = tmp.next.name
					break
				} else {
					fmt.Printf("this is the last song.")
					break
				}
			}
			tmp = tmp.next
		}
		if tmp == nil {
			fmt.Printf("the song %s is not in the playlist
", current_song)
		}
	} else {
		fmt.Printf("It is a empty playlist
")
	}
}

func (player *playlist) previous_song() {
	if current_song == "" {
		fmt.Printf("The current song is empty, please playing a song first.
")
		player_song()
	}
	if player.tail != nil {
		tmp := player.tail
		for tmp != nil {
			if strings.EqualFold(tmp.name, current_song) {
				if tmp.previous != nil {
					fmt.Printf("the current song is %s by %s
", tmp.name, tmp.artist)
					fmt.Printf("the previous song is %s by %s
", tmp.previous.name, tmp.previous.artist)
					current_song = tmp.previous.name
					break
				} else {
					fmt.Printf("this is the first song.
")
					break
				}
			}
			tmp = tmp.previous
		}
		if tmp == nil {
			fmt.Printf("the song %s is not in the playlist
", current_song)
		}
	} else {
		fmt.Printf("It is a empty playlist
")
	}
}

func (player *playlist) show_playlist_reverse() {
	if player.tail != nil {
		tmp := player.tail
		for tmp != nil {
			fmt.Printf("playing %s by %s
", tmp.name, tmp.artist)
			tmp = tmp.previous
		}
		fmt.Printf("
")
	} else {
		fmt.Printf("It is a empty playlist
")
	}
}

func (player *playlist) show_playlist() {
	if player.next != nil {
		for tmp := player.next; tmp != nil; tmp = tmp.next {
			fmt.Printf("Playing %s by %s
", tmp.name, tmp.artist)
		}
		fmt.Printf("
")
	} else {
		fmt.Printf("It is a empty playlist
")
	}
}

func main() {
	album := playlist{}
	album.add_song("理想三旬", "陈鸿宇")
	album.add_song("蓝莲花", "许巍")
	album.add_song("乱舞春秋", "周杰伦")
	album.add_song("稻香", "周杰伦")
	album.add_song("和你在一起", "李志")
	album.show_playlist_reverse()
	album.show_playlist()

	album.next_song()
	album.previous_song()
	album.previous_song()
	album.next_song()
	album.next_song()
}

这里使用结构体 playlist 存储指向节点 song 的头指针和尾指针,通过 playlist 即可实现节点的插入,查找操作。并且,使用一个全局变量 current_song 来存储当前播放的音乐,用户可以手动输入当前音乐然后实现上一首,下一首播放效果。执行结果:

playing 和你在一起 by 李志
playing 稻香 by 周杰伦
playing 乱舞春秋 by 周杰伦
playing 蓝莲花 by 许巍
playing 理想三旬 by 陈鸿宇

Playing 理想三旬 by 陈鸿宇
Playing 蓝莲花 by 许巍
Playing 乱舞春秋 by 周杰伦
Playing 稻香 by 周杰伦
Playing 和你在一起 by 李志

The current song is empty, please playing a song first.
play song: 理想三旬
the current song is 理想三旬 by 陈鸿宇
the next song is 蓝莲花 by 许巍
the current song is 蓝莲花 by 许巍
the previous song is 理想三旬 by 陈鸿宇
this is the first song.
the current song is 理想三旬 by 陈鸿宇
the next song is 蓝莲花 by 许巍
the current song is 蓝莲花 by 许巍
the next song is 乱舞春秋 by 周杰伦

从上述示例可以看出:

  • 双链表的尾插入操作复杂度为 O(1),只需将当前尾指针指向的节点作为目前节点的上一节点即可。
  • 双链表的头节点和尾节点存储的头地址和尾地址均是 nil ,因此这里要小心判断链表的边界。
  • 双链表是在用空间换时间。相比于单链表,它占用了较多内存地址,但是可以更快的对数据进行插入,查找。

3. 播放器的循环链表实现

image
双向链表可以实现上一首,下一首效果。不过无法实现循环播放效果,为实现这种播放效果,这里将双链表改造成循环链表。

func (player *playlist) add_song_circular(name string, artist string) {
	current_song := &song{name: name, artist: artist}
	if player.next != nil {
		player.tail.next = current_song
		player.tail.next.previous = player.tail
		player.tail.next.next = player.next
		player.tail = current_song
		player.next.previous = player.tail
	} else {
		player.next = current_song
		player.next.next = player.next
		player.tail = current_song
		player.next.previous = player.tail
	}
}

只需要对 add_song 方法进行改造即可实现双链表的循环,改造的依据即是将头节点和尾节点的 nil 地址分别赋地址为尾节点和头节点的地址。

4. 数组和链表的比较

  • 内存占用: 数组占据的是内存中的连续地址,对数组元素的查找复杂度为 O(1),知道首地址和元素索引即可直接计算出元素所在内存地址。链表占据的是内存中的非连续地址,对链表读写需要通过指针逐次迭代,其查找复杂度为 O(n)。也正因内存非连续,链表在内存中的存储相比于数组要更灵活。
  • 查询速度: 数组是连续地址,CPU 在读取数据时会将数组的全部或者部分数据加载到 CPU 缓存中,而链表是非连续地址,数据将存储在内存中不会读写到 CPU 缓存,因此,同样查询复杂度下对数组的查询速度要比链表快很多。
  • 数据大小: 数组存储的是元素本身,链表存储的是元素加上指针,因此相比于数组,链表会多消耗内存资源。
  • 插入,删除元素复杂度: 对数组元素进行插入或者删除操作其复杂为 O(n),即插入或删除一个元素其它元素都要移动位置。对链表元素进行插入或者删除其复杂度为 O(1),只需要一次指针指向变换即可(不过需要 O(n) 的复杂度去找到要插入或删除的那个元素)。
芝兰生于空谷,不以无人而不芳。
原文地址:https://www.cnblogs.com/xingzheanan/p/14624775.html