Go基础:list的介绍与使用

326 阅读5分钟

Go基础:list的介绍与使用

介绍

Go语言内置容器list是一个双向链表(实际是一个)。位于包list当中。

结构体定义

list的核心结构体一共包含两个ListElement

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

ListElement的结构体如下:

type Element struct {
	list *List
	Value interface{}
   	   next, prev *Element
}

从节点的结构体可以看出,list中的一个节点包含四个元素:

  • list:指向List的指针,用于标识当前节点属于哪一个LIST
  • Value:用于存储元素的值。
  • 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().Values.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