1、概念介绍node
下面这副图是咱们单链表运煤车队。git
每节运煤车就是单链表里的元素,每节车箱里的煤炭就是元素中保存的数据。先后车经过锁链相连,做为单链表运煤车,从1号车箱开始,每节车箱都知道后面拉着哪一节车箱,殊不知道前面是哪节车箱拉的本身。第一节车箱没有任何车箱拉它,咱们就叫它车头,第五节车箱后面拉其余车箱,咱们称为车尾。github
做为单链表它最大的特色就是能随意增长车队的长度,也能随意减小车队的长度。这是比数组公交车最大的优势。数组
2、Go语言实现讲解框架
一、节点
每节车箱都由车体、绳索和煤炭构成。在Go语言中表示这种自定义组合体的类型就是结构,固然为了通用性,咱们这里要把车箱转换成节点也就是元素,煤炭转换成数据,绳索转换成指针。ui
type Node struct { data Object next *Node }
用张图来描述这种对应关系。
这里结构体Node表示车箱,data表示煤炭用Object类型,next是牵着下节车箱的绳索,用指针表示。spa
对于车箱来讲,除了放煤炭外,还能放瓜果、衣物、饭菜等等,因此这里data的类型必须通用,固然Go里是没有Java里的Object类型的,因此咱们就本身定义了一个。指针
type Object interface{}
二、链表
光有车箱还不够,咱们还要描述车箱组成的车队。每一个单链表车队都有车头、车尾和车箱数量,咱们一样以结构来表现。code
type List struct { size uint64 // 车辆数量 head *Node // 车头 tail *Node // 车尾 }
三、方法索引
(1)初始化
第一步就是组装单链表车队,这就是初始化。不过开始的时候车队是个空壳,一个车箱没有。
func (list *List) Init() { (*list).size = 0 // 此时链表是空的 (*list).head = nil // 没有车头 (*list).tail = nil // 没有车尾 }
(2)添加元素
当前的单链表是空的,因此咱们要向里面添加元素。
func (list *List) Append(node *Node) { (*list).head = node // 这是单链表的第一个元素,也是链表的头部 (*list).tail = node // 同时是单链表的尾部 (*list).size = 1 // 单链表有了第一个元素 }
如今单链表有了第一个元素,我还想再添加一个元素,固然是添加到单链表尾部。
func (list *List) Append(node *Node) { if (*list).size == 0 { // 无元素的时候添加 (*list).head = node // 这是单链表的第一个元素,也是链表的头部 (*list).tail = node // 同时是单链表的尾部 (*list).size = 1 // 单链表有了第一个元素 } else { // 有元素了再添加 oldTail := (*list).tail (*oldTail).next = node // node放到尾部元素后面 (*list).tail = node // node成为新的尾部 (*list).size++ // 元素数量增长 } }
分析上面的代码存在3处疑点,元芳你怎么看?属下认为
第一,node若是为空,则添加无任何意义;
第二,代码中存在重复的地方;
这第三么,卑职如何才能知道新增结果?
下面大卫哥顺着元芳的思路改进下代码。
func (list *List) Append(node *Node) bool { if node == nil { return false } (*node).next = nil // 将新元素放入单链表中 if (*list).size == 0 { (*list).head = node } else { oldTail := (*list).tail (*oldTail).next = node } // 调整尾部位置,及链表元素数量 (*list).tail = node // node成为新的尾部 (*list).size++ // 元素数量增长 return true }
(3)插入元素
一天领导让大卫哥把他小舅子安排到第一个,因而大卫哥为了拍好领导马屁。绞尽脑汁弄了一个方法。
*func (list List) Insert(node *Node) bool {
if node == nil { return false } (*node).next = (*list).head // 领导小舅子排到以前第一名前面 (*list).head = node // 领导小舅子成为第一名 (*list).size++ return true
}**
来托关系插队的人愈来愈多,领导的关系户不能动,只能插后面人的队了。因而大卫哥又修改了代码,增长了位置参数。
func (list *List) Insert(i uint,node *Node) bool { // 空的节点、索引超出范围和空链表都没法作插入操做 if node == nil || i > (*list).size || (*list).size == 0 { return false } if i == 0 { // 直接排第一,也就领导小舅子才能够 (*node).next = (*list).head (*list).head = node } else { // 找到前一个元素 preItem := (*list).head for j := 1 ; j < i; j++ { // 数前面i个元素 preItem = (*preItem).next } // 原来元素放到新元素后面,新元素放到前一个元素后面 (*node).next = (*preItem).next (*preItem).next = preItem } (*list).size++ return true }
(4)删除元素
插队的关系户太多,影响了正常排队的人,被人投诉,大卫哥只好想办法删除一些。
func (list *List) Remove(i uint, node *Node) bool { if i >= (*list).size { return false } if i == 0 { // 删除头部 node = (*list).head (*list).head = (*node).next if (*list).size == 1 { // 若是只有一个元素,那尾部也要调整 (*list).tail = nil } } else { preItem := (*list).head for j := 1; j < i; j++ { preItem = (*preItem).next } node = (*preItem).next (*preItem).next = (*node).next if i == ((*list).size - 1) { // 若删除的尾部,尾部指针须要调整 (*list).tail = preItem } } (*list).size-- return true }
(5)获取
为了获取某个位置的元素,咱们须要一个方法。
func (list *List) Get(i uint) *Node { if i >= (*list).size { return nil } item := (*list).head for j := 0; j < i ; j++ { // 从head数i个 item = (*item).next } return item }
到这里基本框架已经出来了,不过还有几个接口还没实现,做为课后做业。
3、小结
单链表就和列车相似,一个接着一个,因此本节从列车类比介绍了单链表的Go语言实现。在接口实现部分大卫哥以序号做为链表中每一个节点的操做关键字。在实际应用中,咱们每每以data中的某一个字段做为操做关键字。因此这也衍生出链表的不一样接口,你们能够参考大卫哥留的连接中的代码实现做为理解。同时有些实现将表头独立出来并不存放数据,这在必定程度上简化了代码的实现。
4、习题
(1)补全GetSize,RemoveAll,GetHead和GetTail的定义和实现。
(2)以data做为参数,考虑单链表的实现。
(3)将单链表的head独立出来,此时的head是独立的,不存放data,以下图,考虑单链表的实现,并比较这种实现。
(4)若是将head和tail都独立出来,都不存放data,此时的单链表如何实现?这样实现的代码在插入删除操做时候是否是更容易点?