container包中的链表容器
Go 语言的链表实现在标准库的container/list代码包中。这个代码包中有两个公开的程序实体——List和Element,List 实现了一个双向链表(以下简称链表),而 Element则代表了链表中元素的结构。
List的插入方法
在以下方法中,“给定的元素”都是*Element类型的,*Element类型是Element类型的指针类型,*Element的值就是元素的指针。
- MoveBefore方法和MoveAfter方法: 它们分别用于把给定的元素移动到另一个元素的前面和后面。
- MoveToFront方法和MoveToBack方法: 分别用于把给定的元素移动到链表的最前端和最后端。
func (l *List) MoveBefore(e, mark *Element)
func (l *List) MoveAfter(e, mark *Element)
func (l *List) MoveToFront(e *Element)
func (l *List) MoveToBack(e *Element)
链表的方法实现
在List包含的方法中,用于插入新元素的那些方法都只接受interface{}类型的值。这些方法在内部会使用Element值,包装接收到的新元素。这样做正是为了避免直接使用我们自己生成的元素,主要原因是避免链表的内部关联,遭到外界破坏,这对于链表本身以及我们这些使用者来说都是有益的。
了解了链表插入方法实现之后,我们可以看一下下列问题:
可以把自己生成的Element类型值传给链表吗?
不会接受,这些方法将不会对链表做出任何改动。因为我们自己生成的Element值并不在链表中,所以也就谈不上“在链表中移动元素”。更何况链表不允许我们把自己生成的Element值插入其中。
List的几种获取Element指针的方法
- Front和Back方法分别用于获取链表中最前端和最后端的元素
- InsertBefore和InsertAfter方法分别用于在指定的元素之前和之后插入新元素,
- PushFront和PushBack方法则分别用于在链表的最前端和最后端插入新元素。
这些方法都会把一个Element值的指针作为结果返回,它们就是链表留给我们的安全“接口”。拿到这些内部元素的指针,我们就可以去调用前面提到的用于移动元素的方法了。
func (l *List) Front() *Element
func (l *List) Back() *Element
func (l *List) InsertBefore(v interface{}, mark *Element) *Element
func (l *List) InsertAfter(v interface{}, mark *Element) *Element
func (l *List) PushFront(v interface{}) *Element
func (l *List) PushBack(v interface{}) *Element
链表的开箱即用
List利用了自身以及Element在结构上的特点,巧妙地平衡了延迟初始化的优缺点,使得链表可以开箱即用,并且在性能上可以达到最优。
结构体类型
List和Element都是结构体类型。结构体类型有一个特点,那就是它们的零值都会是拥有特定结构,但是没有任何定制化内容的值,相当于一个空壳。值中的字段也都会被分别赋予各自类型的零值。
延迟加载
经过语句var l list.List声明的变量l的值会是一个长度为0的链表。这个链表持有的根元素也将会是一个空壳,其中只会包含缺省的内容。这个长度为0的链表我们可以开箱即用,因为其采用了延迟加载的策略,
链表的PushFront方法、PushBack方法、PushBackList方法以及PushFrontList方法总会先判断链表的状态,并在必要时进行初始化。
比如Front方法和Back方法,一旦发现链表的长度为0, 直接返回nil就好。
又比如,在用于删除元素、移动元素,以及一些用于插入元素的方法中,只要判断一下传入的元素中指向所属链表的指针,是否与当前链表的指针相等就可以了。如果不相等,就一定说明传入的元素不是这个链表中的,后续的操作就不用做了。反之,就一定说明这个链表已经被初始化了。
Ring与List的区别
container/ring包中的Ring类型实现的是一个循环链表,也就是我们俗称的环。其实List在内部就是一个循环链表。它的根元素永远不会持有任何实际的元素值,而该元素的存在就是为了连接这个循环链表的首尾两端。
两者的区别
- 表示方式上和结构复杂度上的不同: Ring类型的数据结构仅由它自身即可代表,而List类型则需要由它以及Element类型联合表示。这是。
- 表示维度上的不同: 一个Ring类型的值严格来讲,只代表了其所属的循环链表中的一个元素,而一个List类型的值则代表了一个完整的链表。这是。
- 两个代码包中的New函数在功能和两个类型在初始化值方面的第一个不同: 在创建并初始化一个Ring值的时候,我们可以指定它包含的元素的数量,但是对于一个List值来说却不能这样做(也没有必要这样做)。循环链表一旦被创建,其长度是不可变的。
- 初始化值方面的第二个不同:仅通过var r ring.Ring语句声明的r将会是一个长度为1的循环链表,而List类型的零值则是一个长度为0的链表。别忘了List中的根元素不会持有实际元素值,因此计算长度时不会包含它。
- 性能上的不同: Ring值的Len方法的算法复杂度是 O(N) 的,而List值的Len方法的算法复杂度则是O(1) 的。