Go基础:list的介绍与使用
介绍
Go语言内置容器list
是一个双向链表(实际是一个环)。位于包list
当中。
结构体定义
list
的核心结构体一共包含两个List
和Element
。
List
List
的结构体如下:
type List struct {
root Element // sentinel list element, only &root, root.prev, and root.next are used
len int // current list length excluding (this) sentinel element
}
List
的结构体包含两个部分:
root
:类型为Element
的结构体。len
:用于记录List
的长度(除去哨兵节点)。
List
的第一个节点为哨兵节点,不存储任何信息。List
的头从哨兵节点的下一个节点开始。
Element
List
中Element
的结构体如下:
type Element struct {
list *List
Value interface{}
next, prev *Element
}
从节点的结构体可以看出,list中的一个节点包含四个元素:
list
:指向List
的指针,用于标识当前节点属于哪一个LISTValue
:用于存储元素的值。next
,prev
: 指向Element
的指针,用于定位后一个节点与前一个节点。
为了简化实现,List的内部实现为一个环,这样l.root
既是最后一个list元素(l.Back()
)的下一个元素,也是第一个list元素(l.Front()
)的上一个元素。
方法一览
下面将方法分为六个大类分别讲解:
- 初始化方法
- 基本方法
- 插入元素
- 删除元素
- 移动元素
- 合并链表
初始化
初始化的方法有两个:Init()
和lazyInit()
。
Init()
方法可以用来初始化一个链表,或者是清空链表。
lazyInit()
方法懒初始化一个零值的链表,从实现上可以看出,只有当哨兵节点的next
节点为空时,才会初始化链表。
func (l *List) Init() *List {
l.root.next = &l.root
l.root.prev = &l.root
l.len = 0
return l
}
func (l *List) lazyInit() {
if l.root.next == nil {
l.Init()
}
}
基础方法
Next()
:返回当前节点的下一个节点或者nil。从实现可以看出,当list
为空或者下一个节点为哨兵节点时,返回nil
。Prev()
:返回当前节点的上一个节点或者nil,跟上面类似。New()
:返回一个初始化完成的链表。Len()
:回链表中的元素个数,时间复杂度为O(1)。Front()
:返回链表的第一个元素。当链表为空时,返回nil。Back()
:返回链表的最后一个元素。当链表为空时,返回nil。
func (e *Element) Next() *Element {
if p := e.next; e.list != nil && p != &e.list.root {
return p
}
return nil
}
func (e *Element) Prev() *Element {
if p := e.prev; e.list != nil && p != &e.list.root {
return p
}
return nil
}
func New() *List { return new(List).Init() }
func (l *List) Len() int { return l.len }
func (l *List) Front() *Element {
if l.len == 0 {
return nil
}
return l.root.next
}
func (l *List) Back() *Element {
if l.len == 0 {
return nil
}
return l.root.prev
}
插入元素
insert()
:未导出函数。在元素at
的位置后面插入一个元素,链表长度加一,返回插入的元素。insertValue()
:未导出函数。将Value
包装成Element
然后进行insert()
。InsertBefore()
:调用insertValue()
在元素mark
前插入。要求元素mark
属于调用的链表对象且不为nil
。InsertAfter()
:与InsertBefore()
类似,在元素mark
后插入。PushFront()
:调用insertValue()
方法在表头插入。返回插入的元素。PushBack()
:调用insertValue()
方法在表尾插入。返回插入的元素。
func (l *List) insert(e, at *Element) *Element {
e.prev = at
e.next = at.next
e.prev.next = e
e.next.prev = e
e.list = l
l.len++
return e
}
func (l *List) insertValue(v interface{}, at *Element) *Element {
return l.insert(&Element{Value: v}, at)
}
func (l *List) InsertBefore(v interface{}, mark *Element) *Element {
if mark.list != l {
return nil
}
return l.insertValue(v, mark.prev)
}
func (l *List) InsertAfter(v interface{}, mark *Element) *Element {
if mark.list != l {
return nil
}
return l.insertValue(v, mark)
}
func (l *List) PushFront(v interface{}) *Element {
l.lazyInit()
return l.insertValue(v, &l.root)
}
func (l *List) PushBack(v interface{}) *Element {
l.lazyInit()
return l.insertValue(v, l.root.prev)
}
删除元素
remove()
:未导出函数。从链表中删除元素e
,链表长度减一,返回被删除的元素。Remove()
:当元素e
属于被调用链表的元素时,调用remove()
函数,返回e.Value
。
func (l *List) remove(e *Element) *Element {
e.prev.next = e.next
e.next.prev = e.prev
e.next = nil // 避免内存泄漏
e.prev = nil // 避免内存泄漏
e.list = nil
l.len--
return e
}
func (l *List) Remove(e *Element) interface{} {
if e.list == l {
l.remove(e)
}
return e.Value
}
移动元素
move()
:未导出函数。将元素e
移至元素at
的后面,返回元素e
。MoveToFront()
:将元素e
移至表头。要求元素e
属于被调用链表的元素。MoveToBack()
:将元素e
移至表尾。要求元素e
属于被调用链表的元素。MoveBefore()
:将元素e
移至元素mark
的前面。要求元素e
属于被调用链表的元素且不为元素mark
。MoveAfter()
:与MoveBefore()
类似,移至其后面。
func (l *List) move(e, at *Element) *Element {
if e == at {
return e
}
e.prev.next = e.next
e.next.prev = e.prev
e.prev = at
e.next = at.next
e.prev.next = e
e.next.prev = e
return e
}
func (l *List) MoveToFront(e *Element) {
if e.list != l || l.root.next == e {
return
}
l.move(e, &l.root)
}
func (l *List) MoveToBack(e *Element) {
if e.list != l || l.root.prev == e {
return
}
l.move(e, l.root.prev)
}
func (l *List) MoveBefore(e, mark *Element) {
if e.list != l || e == mark || mark.list != l {
return
}
l.move(e, mark.prev)
}
func (l *List) MoveAfter(e, mark *Element) {
if e.list != l || e == mark || mark.list != l {
return
}
l.move(e, mark)
}
合并链表
PushBackList()
:将另一个链表浅拷贝到当前链表的末尾。要求两个链表均不为nil
。PushFronList()
:将另一个链表浅拷贝到当前链表的开头。要求两个链表均不为nil
。
func (l *List) PushBackList(other *List) {
l.lazyInit()
for i, e := other.Len(), other.Front(); i > 0; i, e = i-1, e.Next() {
l.insertValue(e.Value, l.root.prev)
}
}
func (l *List) PushFrontList(other *List) {
l.lazyInit()
for i, e := other.Len(), other.Back(); i > 0; i, e = i-1, e.Prev() {
l.insertValue(e.Value, &l.root)
}
}
使用
下面从三个方面来讲解list
的使用:
- 基本操作
- 模拟栈
- 模拟队列
基本操作
下面演示一下链表的基本使用:
l := list.New()
e4 := l.PushBack(4)
e1 := l.PushFront(1)
l.InsertBefore(3, e4)
l.InsertAfter(2, e1)
// 遍历输出链表内容
for e := l.Front(); e != nil; e = e.Next() {
fmt.Println(e.Value)
}
输出结果如下:
1
2
3
4
栈
我们知道栈有三个基本操作:Push()
、Pop()
和Top()
,分别是向栈顶压入,弹出,和查看栈顶操作。我们可以用list
的方法来模拟这三个基本操作:s.PushBack()
、s.Back().Value
和s.Remove(s.Back())
。
stack := list.New()
stack.PushBack(1)
stack.PushBack(2)
stack.PushBack(3)
stack.PushBack(4)
for stack.Len() > 0 {
fmt.Println(stack.Remove(stack.Back()))
}
输出如下:
4
3
2
1
队列
同样,我们还可以用q.PushBack()
与q.Remove(q.Front())
来模拟队列的进度与出队操作。
queue := list.New()
queue.PushBack(1)
queue.PushBack(2)
queue.PushBack(3)
queue.PushBack(4)
for l.Len() > 0 {
fmt.Println(l.Remove(l.Front()))
}
输出如下:
4
3
2
1