Go 语言提供了 3 种容器双向链表 list,双向循环链表 ring 和堆 heap。
list 和 ring 的基本结构都是双向循环链表,但它们有一些区别,详见后文。

list

Go 语言中 list 是双向链表,其数据结构定义如下:

  1. // 节点
  2. type Element struct {
  3. next, prev *Element // 后指针和前指针
  4. list *List // 归属于哪个链表
  5. Value interface{} // 值
  6. }
  7. // 双向链表
  8. type List struct {
  9. root Element // 根节点
  10. len int // 链表长度
  11. }

其图示结构如下:
image.png
可以看到,其结构非常巧妙:

  • 链表的总体类型为 List,节点类型为 Element。
  • 通过一个 *List 类型的变量指明每一个 Element 归属于那一条链表。
  • 节点的值 Value 的类型为空接口类型 interface{},这表明它可以存储任何类型的值,int, string 等都可以,因此一条链表的元素类型是可以不相同的。

此外,虽然从结构上看 list 是一个双向循环链表,但如下遍历过程却不会循环输出:

  1. for e := L.front(); e != nil; e = e.Next() { // L 是一条 list 链表
  2. fmt.Println(e.Value)
  3. }

原因是 Next() 的实现:

  1. func (e *Element) Next() *Element {
  2. if p := e.next; e.list != nil && p != &e.list.root {
  3. return p
  4. }
  5. return nil
  6. }

可以看到如果 e.next == &e.list.root 的话,返回一个 nil,就避免了最后一个元素重新访问 root 节点。

提供的 API 如下:

  1. // 属于 Element 的方法
  2. func (e *Element) Next() *Element // 返回当前节点的下一个节点的地址
  3. func (e *Element) Prev() *Element // 返回当前节点的前一个节点的地址
  4. // 属于 List 的方法
  5. func (l *List) Init() *List // 初始化一个链表,返回指向该链表的指针
  6. func (l *List) Len() int // 返回节点个数
  7. func (l *List) Front() *Element // 取链表的第一个节点
  8. func (l *List) Back() *Element // 去链表的最后一个节点
  9. func (l *List) PushFront(v interface{}) *Element // 往表头插入一个节点,返回该节点的地址
  10. func (l *List) PushBack(v interface{}) *Element // 往表尾插入一个节点,返回该节点的地址
  11. func (l *List) Remove(e *Element) interface{} // 删除 e 所指向的节点
  12. func (l *List) MoveToFront(e *Element) // 把节点 e 移到表头,e 必须属于该链表
  13. func (l *List) MoveToBack(e *Element) // 把节点 e 移到表尾,e 必须属于该链表
  14. func (l *List) PushBackList(other *List) // 把另一条链表 other 拼接到当前链表末尾
  15. func (l *List) PushFrontList(other *List) // 把另一条链表 other 拼接到当前链表表头
  16. func (l *List) MoveBefore(e, mark *Element) // 往 mark 节点前面插入一个节点 e,e 必须属于该链表
  17. func (l *List) MoveAfter(e, mark *Element) // 往 mark 节点后面插入一个节点 e,e 必须属于该链表
  18. func (l *List) InsertBefore(v interface{}, mark *Element) *Element // 往 mark 节点之前插入一个节点
  19. func (l *List) InsertAfter(v interface{}, mark *Element) *Element // 往 mark 节点之后插入一个节点
  20. // 包外访问方法
  21. func New() *List // 初始化一个链表,返回指向该链表的指针,对 Init() 的封装

栈和队列

在 Go 语言中没有专门封装栈和队列这两种数据结构,但是两者在实际应用中频繁且广泛,那么 Go 语言是如何解决这个问题的呢?
答案就是:使用 list 来实现。

栈的实现

使用 list 实现栈

  1. stack := list.New() // 声明
  2. stack.PushBack(1) // 入栈
  3. e := stack.Back() // 取栈顶
  4. stack.Remove(e) // 出栈
  5. size := stack.Len() // 栈大小

使用切片实现栈

  1. stack := make([]int, 0, 10) // 声明
  2. stack = append(stack, 1) // 入栈
  3. top := stack[0] // 取栈顶
  4. stack = stack[:len(stack)-1] // 出栈
  5. size := len(stack) // 获取栈的元素个数

这种方法有一个致命缺点:内存泄漏!
内存泄漏:向操作系统申请的内存空间,使用后却不归还,进行某些操作后,连自己都无法在访问它们,操作系统也不能对它们进行回收再利用。
比如像上述出栈操作 stack = stack[:len(stack)-1] 如果之前没有保存栈顶的地址(或其他访问它的方式),以后都无法再访问它,而它又属于切片 stack 的底层数组,因为还要用到这个底层数组的一部分,操作系统又不能回收掉这个底层数组。

队列的实现

使用 list 实现队列

  1. // var queue list.List
  2. // queue.Init()
  3. queue := list.New() // 声明
  4. queue.PushBack(2) // 入队
  5. e := queue.Front() // 取队头
  6. queue.Remove(e) // 出队
  7. e = queue.Back() // 取队尾
  8. size := queue.Len() // 获取队列元素个数

使用切片实现队列

  1. queue := make([]int, 0, 10) // 声明
  2. queue = append(queue, 1) // 入队
  3. front := queue[0] // 取队首
  4. back := queue[len(queue)-1] // 取队尾
  5. queue = queue[1:] // 出队
  6. size := len(queue) // 获取队列元素个数

同样存在内存泄漏的问题。

ring

heap

heap 包里定义了一组关于堆的接口,而且有关堆操作的方法的参数都必须是该接口类型的。

  1. package heap
  2. // heap.Interface
  3. type Interface interface {
  4. sort.Interface
  5. Push(x interface{}) // add x as element Len()
  6. Pop() interface{} // remove and return element Len() - 1.
  7. }
  8. func Init(h Interface)
  9. func Push(h Interface, x interface{})
  10. func Pop(h Interface) interface{}
  11. func Remove(h Interface, i int) interface{}
  12. func Fix(h Interface, i int)
  1. package sort
  2. type Interface interface {
  3. Len() int
  4. Less(i, j int) bool
  5. Swap(i, j int)
  6. }

因此根据“接口即约定”,若一个具体类型想使用接口中的方法,则必须实现该接口中的所有方法,让该具体类型具有该接口类型。

因此,我们先实现 heap.Interface,下面直接贴官方例子,打些注释

  1. package main
  2. import (
  3. "container/heap"
  4. "fmt"
  5. )
  6. // 整型最小堆
  7. type IntHeap []int
  8. // 定义如何获取堆中元素的个数
  9. func (h IntHeap) Len() int { return len(h) }
  10. // 定义堆中的元素的大小顺序
  11. func (h IntHeap) Less(i, j int) bool { return h[i] < h[j] } // 最小堆,换成 > 则为最大堆
  12. // 定义如何交换堆中的两个元素
  13. func (h IntHeap) Swap(i, j int) { h[i], h[j] = h[j], h[i] }
  14. // 直接往尾部添加
  15. func (h *IntHeap) Push(x interface{}) {
  16. *h = append(*h, x.(int))
  17. }
  18. // 取最后一个元素
  19. func (h *IntHeap) Pop() interface{} {
  20. old := *h
  21. n := len(old)
  22. x := old[n-1]
  23. *h = old[0 : n-1]
  24. return x
  25. }
  26. func main() {
  27. h := &IntHeap{2, 1, 5}
  28. heap.Init(h)
  29. heap.Push(h, 3)
  30. fmt.Printf("minimum: %d\n", (*h)[0])
  31. for h.Len() > 0 {
  32. fmt.Printf("%d ", heap.Pop(h))
  33. }
  34. }

再看官方的一个经典例子:

  1. package main
  2. import (
  3. "container/heap"
  4. "fmt"
  5. )
  6. // 优先队列中的元素
  7. type Item struct {
  8. value string
  9. priority int
  10. // The index is needed by update and is maintained by the heap.Interface methods.
  11. // 下标在 update 操作时需要,由 heap.Interface 中的方法维护
  12. index int // The index of the item in the heap.
  13. }
  14. // 实现 heap.Interface 并保存元素
  15. type PriorityQueue []*Item
  16. func (pq PriorityQueue) Len() int { return len(pq) }
  17. func (pq PriorityQueue) Less(i, j int) bool {
  18. return pq[i].priority > pq[j].priority // 根据优先级建立最大堆
  19. }
  20. func (pq PriorityQueue) Swap(i, j int) {
  21. pq[i], pq[j] = pq[j], pq[i]
  22. // 仔细思考一下为什么下标要换回来
  23. pq[i].index = i
  24. pq[j].index = j
  25. }
  26. func (pq *PriorityQueue) Push(x interface{}) {
  27. n := len(*pq)
  28. item := x.(*Item)
  29. item.index = n
  30. *pq = append(*pq, item)
  31. }
  32. func (pq *PriorityQueue) Pop() interface{} {
  33. old := *pq
  34. n := len(old)
  35. item := old[n-1]
  36. old[n-1] = nil // 避免内存泄漏
  37. item.index = -1 // 确保安全
  38. *pq = old[0 : n-1]
  39. return item
  40. }
  41. // 修改了 priority 或 value 后会破坏堆的性质,该方法用于调整堆以恢复堆性质
  42. func (pq *PriorityQueue) update(item *Item, value string, priority int) {
  43. item.value = value
  44. item.priority = priority
  45. heap.Fix(pq, item.index)
  46. }
  47. func main() {
  48. items := map[string]int{
  49. "banana": 3, "apple": 2, "pear": 4,
  50. }
  51. pq := make(PriorityQueue, len(items))
  52. i := 0
  53. for value, priority := range items {
  54. pq[i] = &Item{
  55. value: value,
  56. priority: priority,
  57. index: i,
  58. }
  59. i++
  60. }
  61. heap.Init(&pq) // 建堆
  62. item := &Item{
  63. value: "orange",
  64. priority: 1,
  65. }
  66. heap.Push(&pq, item) // 添加一个元素
  67. pq.update(item, item.value, 5) // 修改一下优先级
  68. // 依次出堆
  69. for pq.Len() > 0 {
  70. item := heap.Pop(&pq).(*Item)
  71. fmt.Printf("%.2d:%s ", item.priority, item.value)
  72. }
  73. }