Golang 源码阅读系列---container

0 阅读26分钟

0 简介

container 包提供了 golang 存储数据相关的容器,比如 heap、list 和 ring。

1 Heap

包 heap 为任何实现 heap.Interface 的类型提供堆操作。堆是一棵树,其属性是每个节点都是其子树中最小值的节点。树中的最小元素是根,位于索引 0 处。

堆是实现优先级队列的常用方法。要构建优先级队列,请实现 Heap 接口,并将(负)优先级作为 Less 方法的排序,这样 Push 会添加项目,而 Pop 会从队列中删除优先级最高的项目。示例包括这样的实现;文件 example_pq_test.go 有完整的源代码。

接口类型描述了使用此包中的例程的类型的要求。任何实现它的类型都可以用作具有以下不变量的最小堆(在调用 [Init] 后或数据为空或已排序时建立):

!h.Less(j, i) for 0 <= i < h.Len() and 2*i+1 <= j <= 2*i+2 and j < h.Len()

注意,此接口中的 [Push] 和 [Pop] 供包 heap 的实现调用。要从堆中添加和删除内容,请使用 [heap.Push] 和 [heap.Pop]。

heap.Interface

type Interface interface {
    sort.Interface
    Push(x any)
    Pop() any
}

在这个接口中继承了 sort 包的 Interface 接口,这意味着它继承了所有 sort.Interface 声明的方法。因此不仅要实现 heap.Interface 直接声明的方法还要实现 sort.Interface 声明的所有方法才算实现了 heap.Interface,具体看下 sort.Interface:

// src/sort/sort.go
type Interface interface {
    Len() int
    Less(i, j int) bool
    Swap(i, j int)
}
  • Len 是集合中元素的数量。
  • Less 报告索引为 i 的元素是否必须排在索引为 j 的元素之前。如果 Less(i,j)和 Less(j,i)都为假,那么索引 i 和索引 j 处的元素被视为相等。Sort 可能会将相等元素以任意顺序放置在最终结果中,而 Stable 则保留相等元素的原始输入顺序。Less 必须描述传递顺序:
    • 如果 Less(i, j) 和 Less(j, k) 都为真,则 Less(i, k) 也必须为真。
    • 如果 Less(i, j) 和 Less(j, k) 都为假,则 Less(i, k) 也必定为假。

请注意,浮点比较(float32 或 float64 值上的 < 操作符)在涉及非整数 (NaN) 值时不是传递排序。有关浮点数值的正确实现,请参见 Float64Slice.Less。

  • Swap 交换索引为 i 和 j 的元素。

heap 的实现示例

type myHeap []int

func (h *myHeap) Len() int {
    return len(h)
} 

func (h *myHeap) Less(i, j int) bool {
    return (*h)[i] < (*h)[j]
}

func (h *myHeap) Swap(i, j int) {
    (*h)[i], (*h)[j] = (*h)[j], (*h)[i]
}

// 到此已经实现了 sort.Interface 直接声明的方法

func (h *myHeap) Push(v any) {
    *h = append(*h, v.(int))
}

func (h *myHeap) Pop() (v any) {
    *h, v = (*h)[:h.Len()-1], (*h)[h.Len()-1]
    return
}
// 到此实现了 heap.Interface

堆的建立(堆化)与调整

  1. 初始建堆,将原始底层数组以二叉树形式组织进行堆化,使所有父节点的值小于子节点。
  2. 此时堆顶元素是整个排列中的最小值,将其与最后一个节点进行交换后移出队列,此时堆顶元素就相当于确定了最终的排序位置。
  3. 由于将末尾元素移动到了堆顶,此时可能已经不再满足堆的性质,所以需要对堆进行调整。即对根节点进行向下调整,使其重新满足堆性质。
  4. 重复 2、3 步骤,直至所有排列中的元素都确定最终位置。

在源码库中,初始建堆对应 Init 函数,调整分为向下调整和向上调整,分别对应 down 和 up 函数。出堆入堆则分别对应 Push 和 Pop 函数,下面分别来看:

Init

Init 建立此包中其他例程所需的堆不变量。Init 对于堆不变量是幂等的,并且可以在堆不变量可能失效时调用。复杂度为 O(n),其中 n = h.Len()。

func Init(h Interface) {
    // heapify
    n := h.Len()
    for i := n/2 - 1; i >= 0; i-- {
        down(h, i, n)
    }
}

从第 n/2 - 1 个节点开始进行处理,到根节点时整个树就完成了堆化(即所有的子节点的值大于父节点值),看下向下调整 down 函数是如何实现的:

down

func down(h Interface, i0, n int) bool {
    i := i0
    for {
        j1 := 2*i + 1
        if j1 >= n || j1 < 0 { // 在 int 溢出后 j1 < 0
            break
        }
        j := j1
        if j2 := j1 + 1; j2 < n && h.Less(j2, j1) {
            j = j2 
        }
        if !h.Less(j, i) {
            break
        }
        h.Swap(j, i)
        i = j
    }
    return i > i0
}

从指定下标的节点开始,计算它左孩子的下标。如果左孩子下标没有超出底层数组的范围,那么找出左右孩子中较小值的下标(如果存在右孩子)。如果父节点的值已经比较小的子节点值还小,那么无需继续调整。否则将较小的子节点与父节点进行交换,继续判断调整后的父节点是否满足最小堆条件。

up

func up(h Interface, j int) {
    for {
        i := (j-1) / 2 // parent
        if i == j || !h.Less(j, i) {
            break
        }
        h.Swap(i, j)
        j = i
    }
}

从指定下标处开始向上调整堆,首先找到这个节点的父节点,如果当前节点已经是根节点或者父节点的值已经比当前节点的值小,则直接返回,停止调整。否则,将该节点与父节点交换位置,并从父节点开始继续向上调整,直至根节点。

Push

Push 将元素 x 推到堆上。复杂度为 O(log n),其中 n = h.Len()。

func Push(h Interface, x any) {
    h.Push(x)
    up(h, h.Len()-1)
}

实际上是之前 myHeap 实现的 Push 方法的封装,在将新元素推到堆上后(树的底层),需要重新保证调整堆以维护最小堆性质,于是进行向上调整。

Pop

Pop 从堆中移除并返回最小元素(根据 Less)。复杂度为 O(log n),其中 n = h.Len()。Pop 等同于 [Remove](h, 0)。

func Pop(h Interface) any {
    n := h.Len() - 1
    h.Swap(0, n)
    down(h, 0, n)
    return h.Pop()
}

先获取当前二叉堆对末尾节点的索引,然后将第一个堆顶元素和末尾元素交换,因为堆顶元素就是整颗树的最小元素,因此该元素就相当于确定了最终位置(可以移出)。将末尾元素交换到了堆顶可能导致堆的性质被破坏,所以使用 down 方法向下调整重新建堆(此时使用的 n 已经将末尾元素排除在外了)。最后调用 myHeap 实现的 Pop 方法将末尾的元素(交换过来的堆顶元素)从堆中移除并返回。

Remove

Remove 从堆中删除并返回索引 i 处的元素。复杂度为 O(log n),其中 n = h.Len()。所以之前的 Pop 可以直接通过调用 Remove(h, 0) 来完成。

func Remove(h Interface, i int) any {
    n := h.Len() - 1
    if n != i {
        h.Swap(i, n)
        if !down(h, i, n) {
            up(h, i)
        }
    }
    return h.Pop()
}

相比于 Pop 只能弹出堆顶元素,Remove 可以弹出堆中任意元素(给定索引)。如果待弹出的元素正好就是最后一个元素(只有一个元素的堆),那么直接弹出即可。否则将给定索引处的元素与末尾元素交换后开始向下调整,如果向下调整过程中发生了实际的交换,那么交换过来的元素还可能比它父的节点小,因此还需要继续向上调整,调整结束后再次满足最小堆的性质。

Fix

Fix 在索引 i 处的元素更改其值后重新建立堆排序。更改索引 i 处元素的值然后调用 Fix 相当于调用 [Remove](h, i) 然后 Push 新值,但成本更低。复杂度为 O(log n),其中 n = h.Len()。

func Fix(h Interface, i int) {
    if !down(h, i, h.Len()) {
        up(h, i)
    }
}

在修改了索引 i 处节点的值之后,先进行向下调整,向下调整完成后如果实际发生了节点值交换,那么继续向上调整以恢复堆性质。

测试

验证一下正确性,先写一个辅助验证函数 verify 以备复用:

func (h myHeap) verify(t *testing.T, i int) {
    // Helper 将调用函数标记为测试辅助函数。打印文件和行信息时,将跳过该函数。Helper 可以从多个 goroutine 同时调用。
    t.Helper()
    n := h.Len()
    // 对于小根堆,它的两个孩子都必须大于"根"的值
    j1 := 2*i + 1
    j2 := 2*i + 2
    if j1 < n {
        if h.Less(j1, i) {
            if h.Less(j1, i) {
                t.Errorf("heap invariant invalidated [%d] = %d > [%d] = %d", i, h[i], j1, h[j1])
                return
            }
            h.verify(t, j1) // 递归向下验证
        }
    }
    if j2 < n {
        if h.Less(j2, i) {
            t.Errorf("heap invariant invalidated [%d] = %d > [%d] = %d", i, h[i], j1, h[j2])
            return
        }
        h.verify(t, j2)
    }
}

初始化建堆验证(压入相同元素)

func TestInit0(t *testing.T) {
    h := new(myHeap)
    for i := 20; i > 0; i-- {
        h.Push(0)
    }
    Init(h)
    h.verify(t, 0)
    
    for i := 1; h.Len() > 0; i++ {
        x := Pop(h).(int)
        h.verify(t, 0)
        if x != 0 {
            t.Errorf("%d.th pop got %d; want %d", i, x, 0)
        }
    }
}

初始化建堆验证(压入不同元素)

func TestInit1(t *testing.T) {
    h := new(myHeap)
    for i := 20; i > 0; i-- {
        h.Push(i)
    }
    Init(h)
    h.verify(t, 0)

    // 1 2 3 ... 20 依次验证
    for i := 1; h.Len() > 0; i++ {
        x := Pop(h).(int)
        h.verify(t, 0)
        if x != i {
            t.Errorf("%d.th pop got %d; want %d", i, x, i)
        }
    }
}

增加随机性,先压入 10 个然后再压入 10 个,接着一边弹出堆顶元素一边压入更大元素。

func Test(t *testing.T) {
    h := new(myHeap)
    h.verify(t, 0)
    
    for i := 20; i > 10; i-- {
        h.Push(i)
    }
    Init(h)
    h.verify(t, 0)
    
    for i := 10; i > 0; i-- {
        Push(h, i)
        h.verify(t, 0)
    }
    
    for i := 1; h.Len() > 0; i++ {
        x := Pop(h).(int)
        if i < 20 {
            Push(h, 20+i)
        }
        h.verify(t, 0)
        if x != i {
            t.Errorf("%d.th pop got %d; want %d", i, x, i)
        }
    }
}

测试每次移除末尾元素后是否仍然满足最小堆性质。

func TestRemove0(t *testing.T) {
    h := new(myHeap)
    for i := 0; i < 10; i++ {
        h.Push(i) 
    } 
    h.verify(t, 0) // 递归验证是否满足最小堆性质
    
    for h.Len() > 0 { 
        i := h.Len() - 1 // 9 8 ...
        x := Remove(h, i).(int) // 堆末尾元素应该是 9 8 ...
        if x != i {      
            t.Errorf("Remove(0) got %d; want %d, i, x, i)
        }
        h.verify(t, 0) 
    }
}

Push 的顺序为 0 1 2 ... 9,末尾元素为 9;

使用 verify 辅助函数验证所有压入的元素是否按照最小堆排列;

取 i 为末尾元素下标,刚好也是末尾元素的值(因为值从 0 开始压入的);

调用 Remove 移除末尾元素并与 i 比较,两者必须相同;

移除末尾元素后再次验证堆性质。

测试每次都移除堆顶元素后是否仍然满足最小堆性质。

func TestRemove1(t *testing.T) {
    h := new(myHeap)
    for i := 0; i < 10; i++ {
        h.Push(i)
    }
    h.verify(t, 0)
    
    for i := 0; h.Len() > 0; i++ {
        x := Remove(h, 0).(int)
        if x != i {
            t.Errorf("Remove(0) got %d; want %d", x, i)
        }
        h.verify(t, 0)
    }
}

测试每次从中间移除堆元素后是否仍然满足最小堆性质。

func TestRemove2(t *testing.T) {
    N := 10

    h := new(myHeap)
    for i := 0; i < N; i++ {
        h.Push(i)
    }
    h.verify(t, 0)

    m := make(map[int]bool)
    for h.Len() > 0 {
        m[Remove(h, (h.Len()-1)/2).(int)] = true
        h.verify(t, 0)
    }

    if len(m) != N {
        t.Errorf("len(m) = %d; want %d", len(m), N)
    }
    for i := 0; i < len(m); i++ {
        if !m[i] {
                t.Errorf("m[%d] doesn't exist", i)
        }
    }
}

测试堆 Push 和 Pop 性能

func BenchmarkDup(b *testing.B) {
    const n = 10000
    // 24B * 10000 ~= 240KB ~= 0.24MB
    h := make(myHeap, 0, n)
    for i := 0; i < b.N; i++ {
        for j := 0; j < n; j++ {
            Push(&h, 0) // 所有元素都相同
        }
        for h.Len() > 0 {
            Pop(&h)
        }
    }
}

测试修复堆方法是否正常

func TestFix(t *testing.T) {
    h := new(myHeap)
    h.verify(t, 0)
    
    for i := 200; i > 0; i -= 10 {
        Push(h, i)
    }
    h.verify(t, 0)
    
    if (*h)[0] = != 10 {
        t.Fatalf("Expected head to be 10, was %d", (*h)[0])
    }
    (*h)[0] = 210
    Fix(h, 0)
    h.verify(t, 0)
    
    for i := 100; i > 0; i-- {
        elem := rang.Intn(h.Len())
        if i&1 == 0 {
            (*h)[elem] *= 2
        } else {
            (*h)[elem] /= 2
        }
        Fix(h, elem)
        h.verify(t, 0)
    }
}

Int Heap 实现示例

IntHeap 是一个 int 的最小堆。

type IntHeap []int

func (h IntHeap) Len() int { return len(h) }
func (h IntHeap) Less(i, j int) bool { return h[i] < h[j] }
func (h IntHeap) Swap(i, j int) { h[i], h[j] = h[j], h[i] }

// Push 和 Pop 使用指针接收器,因为它们修改切片的长度,而不仅仅是其内容。
func (h *IntHeap) Push(x any) {
    *h = append(*h, x.(int))
}

func (h IntHeap) Pop() any {
    old := *h 
    n := len(old)
    x := old[n-1]
    *h = old[:n-1]
    return x
}

此示例向 IntHeap 中插入多个输入值,检查最小值,然后按优先级顺序将其移除。

func Example_intHeap() {
    h := &IntHeap{2, 1, 5}
    heap.Init(h)
    heap.Push(h, 3)
    fmt.Printf("minimum: %d\n", (*h)[0])
    for h.Len() > 0 {
        fmt.Printf("%d ", heap.Pop(h))
    }
}

// Output:
// minimum: 1
// 1 2 3 5

优先队列

Item 是我们在优先级队列中管理的内容。

type Item struct {
    value string // 物品的价值;任意的。
    priority int // 在优先队列中的优先级
    index int // 更新需要索引,并由 heap.Interface 方法维护。堆中项的索引。
}

// 优先级队列实现 heap.Interface 并保存项目。
type PriorityQueue []*Item

func (pq PriorityQueue) Len() int {  return len(pq) }
// 按优先级大小进行排序,优先值越大优先级越高
func (pq PriorityQueue) Less(i, j int) bool { return pq[i].priority > pq[j].priority }
func (pq PriorityQueue) Swap(i, j int) { 
    // pq[i].index = pq[j].index
    // pq[j].index = pq[i].index
    pq[i], pq[j] = pq[j], pq[i] 
    pq[i].index = i
    pq[j].index = j
}

func (pq *PriorityQueue) Push(x any) {
    n := len(*pq)
    item := x.(Item)
    item.index = n  // 记得为新的 Item 设置索引
    *pq = append(*pq, item)
}

func (pq *PriorityQueue) Pop() any {
    old := *pq
    n := len(old)
    item := old[n-1]
    old[n-1] = nil // 不要阻止 GC 最终回收该项目,帮助 GC
    item.index = -1 // for safety
    *pq = old[:n-1]
    return item
}

// 修改 Item 的优先级并重新调整优先级队列
funcx (pq *PriorityQueuePriorityQueue) update(item *Item, value string, priority int) {
    item.value = value
    item.priority = priority
    heap.Fix(pq, item.index)
}

// 此示例创建了一个包含一些项目的优先级队列,添加并操作了一个项目,然后按优先级顺序删除了这些项目。
func Example_priorityQueue() {
    // 一些 Item 及其优先次序。
    items := map[string]int{
        "banana": 3, 
        "apple": 2,
        "pear": 4,
    }
    // 创建一个优先级队列,将项目放入其中,并建立优先级队列(堆)不变量。
    pq := make(PriorityQueue, len(items))
    i := 0
    for value, priority := range items {
        pq[i] = &Item{
            value: value,
            priority: priority,
            index: i,
        }
        i++
    }
    heap.Init(pq)
    
    // 插入一个新项目然后修改其优先级。
    item := &Item{value: "orange", priority: 1}
    heap.Push(&pq, item)
    pq.update(item, item.value, 5)
    
    // 取出物品;它们按照优先等级递减的顺序到达。
    for pq.Len() > 0 {
        item := heap.Pop(&pq).(*Item)
        fmt.Printf("%.2d:%s", item.priority, item.value)
    }
    // Output:
    // 05:orange 04:pear 03:banana 02:apple
}

2 list

包 list 实现了双向链表。要迭代一个列表(其中 l 是一个列表):

for e := l.Front(); e != nil; e = e.Next() {
    do something with e.Value 
}

Element

Element 是链表中的一个元素。

type Element struct {
    next, prev *Element
    // 该元素所属的列表。
    list *List
    // 和此元素一起存储的值。
    Value any
}

双向链表中元素的下一个指针和上一个指针。为了简化实现,列表 l 在内部实现为一个环,这样 &l.root 既是最后一个列表元素的下一个元素(l.Back()),又是第一个列表元素的前一个元素(l.Front())。

*Element.Next

Next 返回下一个列表元素或 nil。

func (e *Element) Next() *Element {
    if p := e.next; e.list != nil && p != &e.list.root {
        return p
    }
    return nil
}

*Element.Prev

Prev 返回前一个列表元素或 nil。

func (e *Element) Prev() *Element {
    if p := e.prev; e.list != nil && p != e.list.root {
        return p
    }
    return nil
}

如果一个元素的前一个或者后一个元素是 root 节点即哨兵节点,那么直接返回 nil,哨兵节点无实际意义。

List

List 表示双向链表。List 的零值是一个可立即使用的空列表。

type List struct {
    root Element // 哨兵列表元素,只使用 &root、root.prev 和 root.next
    len int       // 当前列表长度,不包括(此)哨兵元素
}

List.Init

Init 初始化或清除列表 l。

func (l *List) Init() *List {
    l.root.next = &l.root
    l.root.prev = &l.root
    l.len = 0
    return l
}

List.New

New 返回一个初始化的列表。

func New() *List {
    return new(List).Init()
}

List.Len

Len 返回列表 l 的元素数量,复杂度恒为 O(1),意味着对列表长度的计算不会成为瓶颈。

func (l *List) Len() int { return l.len }

List.Front

Front 返回列表 l 的第一个元素,如果列表为空,则返回 nil。

func (l *List) Front() *Element {
    if l.len == 0 {
        return nil
    }
    return l.root.next
}

List.Back

Back 返回列表 l 的最后一个元素,如果列表为空,则返回 nil。

func (l *List) Back() *Element {
    if l.len == 0 {
        return nil
    }
    return l.root.prev
}

List.lazyInit

lazyInit 会以惰性的方式初始化一个零 List 值。

func (l *List) lazyInit() {
    if l.root.next == nil {
        l.Init()
    }
}

List.insert

在 at 之后插入 e,递增 l.len,并返回 e。

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
}

List.insertValue

insertValue 是 insert(&Element{Value: v}, at) 的便捷包装器。

func (l *List) insertValue(v any, at *Element) *Element {
    return l.insert(&Element{Value: v}, at)
}

List.remove

remove 从其列表中移除 e,递减 l.len。

func (l *List) remove(e *Element) {
    e.prev.next = e.next
    e.next.prev = e.prev
    e.next = nil // 避免内存泄漏
    e.prev = nil // 避免内存泄漏
    e.list = nil
    l.len--
}

List.move

移动 e 到 at 的下一个位置。

func (l *List) move(e, at *Element) {
    if e == at {
        return
    }
    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
}

List.Remove

如果 e 是列表 l 的一个元素,则 Remove 会从 l 中删除 e。它返回元素值 e.Value。该元素不能为 nil。

func (l *List) Remove(e *Element) any {
    if e.list == l { 
        // 如果 e.list == l,则在 e 插入 l 时,l 必须已经初始化,否则 l == nil(e 为零元素),l.remove 将崩溃
        l.remove(e)
    }
    return e.Value
}

List.PushFront

PushFront 在 list l 的前面插入一个值为 v 的新元素 e,并返回 e。

func (l *List) PushFront(v any) *Element {
    l.lazyInit()
    return l.insertValue(v, l.root)
}

List.PushBack

PushBack 在 list l 的后面插入一个值为 v 的新元素 e,并返回 e。

func (l *List) PushBack(v any) *Element {
    l.lazyInit()
    l.insertValue(v, l.root.prev)
}

List.InsertBefore

InsertBefore 在 mark 之前立即插入一个值为 v 的新元素 e 并返回 e。如果 mark 不是 l 的元素,则列表不会被修改。mark 不能为 nil。

func (l *List) InsertBefore(v any, mark *Element) *Element {
    if mark.list != l {
        return nil
    }
    return l.insertValue(v, mark.prev)
}

List.InsertAfter

InsertAfter 在 mark 后立即插入一个值为 v 的新元素 e 并返回 e。如果 mark 不是 l 的元素,则不会修改列表。mark 不能为 nil。

func (l *List) InsertAfter(v any, mark *Elemnt) *Element {
    if mark.list != l {
        return
    }
    return l.insertValue(v, mark)
}

List.MoveToFront

MoveToFront 将元素 e 移到 list l 的前面。如果 e 不是 list l 的元素,则不修改 list。元素不能为 nil。

func (l *List) MoveToFront(e *Element) {
    // 不是 list 的元素或者已经位于 list 的前面
    if e.list != l || l.root.next == e {
        return
    }
    return l.move(e, l.root)
} 

List.MoveToBack

MoveToBack 将元素 e 移至列表 l 的后面。如果 e 不是 l 的元素,则列表不会被修改。元素不能为零。

func (l *List) MoveToBack(e *Element) {
    if e.list != l || l.root.prev == e {
        return
    }
    return l.move(e, l.root.prev)
}

List.MoveBefore

MoveBefore 将元素 e 移动到新位置 mark 之前(插入到 mark.prev 之后)。如果 e 或 mark 不是 l 的元素,或者 e == mark,列表不会被修改。元素和 mark 不能为空。

func (l *List) MoveBefore(e, mark *Element) {
    if e.list != l || mark.list != l || e == mark {
        return
    }
    l.move(e, mark.prev)
}

List.MoveAfter

MoveAfter 将元素 e 移动到 mark 之后的新位置。如果 e 或 mark 不是 l 的元素,或者 e == mark,则不会修改列表。元素和 mark 不能为空。

func (l *List) MoveAfter(e, mark *Element) {
    if e.list != l || mark.list != l || e == mark {
        return
    }
    return l.move(e, mark)
}

List.PushBackList

PushBackList 在列表 l 的后面插入另一个列表的副本。它们不能为零。

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) // l.root.prev type is *Element
    }
}

List.PushFrontList

PushFrontList 在列表 l 的前面插入另一个列表的副本。它们不能为空。

func (l *List) PushFrontList(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) // root type is Element
    }
}

List 测试

checkListLen(helper)

func checkListLen(t *tesing.T, l *List, len int) bool {
    if n := l.Len(); n != len {
        t.Errorf("l.Len() = %d, want %d", n, len)
        return false
    }
    return true
}

checkListPointers(helper)

func checkListPointers(t *testing.T, l *List, es []*Element) {
    root := &l.root
    
    if !checkListLen(t, l, len(es) { return }
    
    // 零长度列表必须是零值或正确初始化(哨兵圈)
    if len(es) == 0 {
        if l.root.next != nil && l.root.next != root || l.root.prev != nil && l.root.prev != root {
            t.Errorf("l.root.next = %p, l.root.prev = %p; both should both be nil or %p", l.root.next, l.root.prev, root)
        }
        return
    }
    
    // 检查内部和外部前/后连接
    for i, e := range es {
        prev := root
        Prev := (*Element)(nil)
        if i > 0 {
            prev = es[i-1]
            Prev = prev
        }
        if p := e.prev; p != prev {
            t.Errorf("elt[%d](%p).prev = %p, want %p", i, e, p, prev)
        }
        if p := e.Prev(); p != Prev {
            t.Errorf("elt[%d](%p).Prev() = %p, want %p", i, e, p, Prev)
        }
        
        next := root
        Next := (*Element)(nil)
        if i < len(es) - 1 {
            next = es[i+1]
            Next = next
        }
        if n := e.next; n != next {
            t.Errorf("elt[%d](%p).next = %p, want %p", i, e, n, next)
        }
        if n := e.Next(); n != Next {
            t.Errorf("elt[%d](%p).Next() = %p, want %p", i, e, n, Next)
        }
    }
    
}

checkList(helper)

func checkList(t *testing.T, l *List, es []any) {
    if !checkListLen(t, l, len(es)) {
        return 
    }
    
    i := 0
    for e := l.Front(); e != nil; e = e.Next() {
        le := e.Value.(int)
        if le != es[i] {
            t.Errorf("elt[%d].Value = %v, want %v", i, le, es[i])
        }
        i++
    }
}

TestExtending

func TestExtending(t *testing.T) {
    l1 := New()
    l2 := New()
    
    l1.PushBack(1)
    l1.PushBack(2)
    l1.PushBack(3)
    
    l2.PushBack(4)
    l2.PushBack(5)
    
    l3 := New()
    l3.PushBackList(l1)
    checkList(t, l3, []any{1, 2, 3})
    l3.PushBackList(l2)
    checkList(t, l3, []any{1, 2, 3, 4, 5})
    
    checkList(t, l1, []any{1, 2, 3})
    checkList(t, l2, []any{4, 5})
    
    l3 = New()
    l3.PushBackList(l1)
    checkList(t, l3, []any{1, 2, 3})
    l3.PushBackList(l3)
    checkList(t, l3, []any{1, 2, 3, 1, 2, 3})
}

TestRemove

func TestRemove(t *testing.T) {
    l := New()
    e1 := l.PushBack(1)
    e2 := l.PushBack(2)
    checkListPointers(t, l, []*Element{e1, e2})
    e := l.Front()
    l.Remove(e)
    checkListPointers(t, l, []*Element{e2})
    l.Remove(e)
    checkListPointers(t, l, []*Element{e2})
}

TestIssule4103

func TestIssue4103(t *testing.T) {
    l1 := New()
    l1.PushBack(1)
    l1.PushBack(2)
    
    l2 := New()
    l2.PushBack(3)
    l2.PushBack(4)
    
    e := l1.Front()
    l2.Remove(e) // L2 不应该改变,因为 e 不是 l2 的元素
    if n := l2.Len(); n != 2 {
        t.Errorf("l2.Len() = %d, want 2", n)
    }
    
    l1.InsertBefore(8, e)
    if n := l1.Len(); n != 3 {
        t.Errorf("l1.Len() = %d, want 3", n)
    }
}

TestInsertAfterUnknown

在调用InsertAfter时,测试列表l是否没有被修改,该标记不是列表l的元素。

func TestInsertAfterUnknownMark(t *testing.T) {
    var l List
    l.PushBack(1)
    l.PushBack(2)
    l.PushBack(3)
    l.InsertAfter(1, new(Element))
    checkList(t, &l, []any{1, 2, 3})
}

List Example

func Example() {
    // 创建一个新列表并在其中输入一些数字。
    l := list.New()
    e4 := l.PushBack(4)
    e1 := l.PushBack(1)
    l.InsertBefore(3, e4)
    l.InsertAfter(2, e1)

    // 遍历列表并打印其内容。
    for e := l.Front(); e != nil; e = e.Next() {
        fmt.Println(e.Value)
    }


// Output:
// 1
// 2
// 3
// 4
}

3 ring

ring 包实现了循环列表的操作。

环是循环列表或环的一个元素。环没有开始或结束;指向任何环元素的指针都可作为整个环的引用。空环表示为 nil 环指针。环的零值是具有 nil 值的单元素环。

Ring 定义

环形链表的结构体定义。

type Ring struct {
    next, prev *Ring
    Value any // 供客户端使用;本库未触及
}

Ring 初始化

对环进行初始化,使 Ring 的初始节点的 prev 和 next 都指向它自己,以满足双向循环链表的性质。

func (r *Ring) init() *Ring {
    r.next = r
    r.prev = r
    return r
}

Ring Next

Next 返回下一个环元素。如果 r 为 nil,那么对其进行初始化,无论如何 Ring 不能为空。

func (r *Ring) Next() *Ring {
    if r.next == nil {
        return r.init()
    }
    return r.next
}

Ring Prev

Prev 返回前一个环元素,和 Next 方法一样保证 r 不为 nil。

func (r *Ring) Prev() *Ring {
    if r.prev == nil {
        return r.init()
    }
    return r.prev
}

Ring Move

如果 n < 0 那么相当于将当前环节点向前移动 n 个位置,并返回那个位置的环节点。

如果 n > 0 那么相当于将当前环节点向后移动 n 个位置,并返回那个位置的环节点。

func (r *Ring) Move(n int) *Ring {
    if r.next == nil {
        return r.init()
    }
    switch {
    case n < 0:
        for ; n < 0; n++ {
            r = r.prev
        }
    case n > 0:
        for ; n > 0; n-- {
            r = r.next
        }
    }
    return r
}

Ring New

New 创建一个由 n 个元素组成的环。

func New(n int) *Ring {
    if n <= 0 {
        return nil
    }
    r := new(Ring)
    p := r
    for i := 1; i < n; i++ {
        p.next = &Ring{prev: p}
        p = p.next
    }
    p.next = r
    r.prev = p
    return r
}

Ring Link

Link 将环 r 与环 s 连接起来,使得 r.Next() 变成 s,并返回 r.Next() 的原始值。r 不能为空。

func (r *Ring) Link(s *Ring) *Ring {
    n := r.Next() // 若 r 为 nil,会进行惰性初始化
    if s != nil {
        p := s.Prev() // s prev
        // 注:不能使用多重赋值,因为没有指定 LHS 的评估顺序。
        // 将 r 的尾和 s 的头 Link
        r.next = s
        s.prev = r
        // 将 s 的尾和 r 的头 Link
        // n 即 r 的头,p 即 s 的尾
        n.prev = p
        p.next = n
    }
    return n
}

image.png

image.png

Ring Unlink

Unlink方法用于从环形链表中移除指定数量的元素,并返回一个新的环形链表,这个新链表包含被移除的元素。

func (r *Ring) Unlink(n int) *Ring {
    if n <= 0 {
        return nil
    }

    return r.Link(r.Move(n + 1))
}

Ring Len

Len 计算环 r 中元素的数量。其执行时间与元素数量成正比。

func (r *Ring) Len() int {
    n := 0
    if r != nil {
        n = 1
        for p := r.Next(); p != r; p = p.next {
            n++
        }
    }
    return n
}

Ring Do

Do 按正序对环中的每个元素调用函数 f。如果 f 更改 r,则 Do 的行为未定义。

func (r *Ring) Do(f func(any)) {
    if r != nil {
        f(r.Value)
        for p := r.Next(); p != r; p = p.next {
            f(p.Value)
        }
    }
}

Ring Test

Ring Test dump (debugger)

如果 Ring 不为空,则从头开始打印每个节点以及这个节点的 Prev 和 Next 节点。

func dump(r *Ring) {
    if r == nil {
        fmt.Println("empty")
        return
    }
    i, n := 0, r.Len()
    for p := r; i < n; p = p.next {
        fmt.Printf("%4d: %p = {<- %p | %p ->}\n", i, p, p.prev, p.next)
        i++
    }
    fmt.Println()
}

Test New

func TestNew(t *testing.T) {
    for i := 0; i < 10; i++ {
        r := New(i)
        verify(t, r, i, -1)
    }
    for i := 0; i < 10; i++ {
        r := makeN(i)
        verify(t, r, i, sumN(i))
    }
}

func makeN(n int) *Ring {
    r := New(n)
    for i := 0; i <= n; i++ {
        r.Value = i
        r = r.Next
    }
}

上述代码主要测试了 New 函数和 makeN 函数的功能,具体来说,它做了以下几件事情:

  1. 测试 New 函数
  • 循环 10 次,每次调用 New(i) 创建一个长度为 i 的环
  • 使用 verify 函数验证创建的环长度是否正确并且不关心环中元素的和(用 -1 表示)
  1. 测试 makeN 函数
  • 循环 10 次,每次调用 makeN(i) 创建一个长度为 i 的环,并且环中的元素值从 0 到 i-1
  • 使用 verify 函数验证创建的环的长度和元素的和是否正确

Test Link1

func TestLink1(t *testing.T) {
    r1a := makeN(1)
    var r1b Ring
    r2a := r1a.Link(&r1b)
    verify(t, r2a, 2, 1)
    if r2a != r1a {
        t.Errorf("a) 2-element link failed")
    }
    
    r2b := r2a.Link(r2a.Next())
    verify(t, r2b, 2, 1)
    if r2b != r2a.Next() {
            t.Errorf("b) 2-element link failed")
    }
    
    r1c := r2b.Link(r2b)
    verify(t, r1c, 1, 1)
    verify(t, r2b, 1, 0)
}

这段代码对 Link 方法进行测试,Link 方法用于将两个环链接在一起,具体来说,测试了以下情况:

  1. 将一个元素的环与一个空环链接
  • 创建一个包含一个元素的环 r1a
  • 创建一个空环 r1b
  • 将 r1b 链接到 r1a,形成一个包含 2 个元素的环形链表
  • 验证 r2a 是否包含 2 个元素且元素和为 1
  • 检查 r2a 是否等于 r1a
  • 将 r2a 的下一个节点连接到 r2a,形成一个新的环形链表
  • 验证 r2b 是否包含 2 个元素且元素和为 1
  • 检查 r2b 是否等于 r2a 的下一个节点
  • 将 r2b 链接到自身形成一个包含 1 个元素的环形链表
  • 验证 r1c 是否包含一个元素且元素和为 1
  • 验证 r2b 是否包含 1 个元素且元素和为 0

这段代码通过一系列的链接操作和验证,测试了环形链表的链接功能是否正常工作。每一步操作后都通过verify函数进行验证,确保环形链表的结构和元素值符合预期。

Test Unlink

func TestUnlink(t *testing.T) {
    r10 := makeN(10)
    s10 := r10.Move(6)

    sum10 := sumN(10)

    verify(t, r10, 10, sum10)
    verify(t, s10, 10, sum10)

    r0 := r10.Unlink(0)
    verify(t, r0, 0, 0)

    r1 := r10.Unlink(1)
    verify(t, r1, 1, 2)
    verify(t, r10, 9, sum10-2)

    r9 := r10.Unlink(9)
    verify(t, r9, 9, sum10-2)
    verify(t, r10, 9, sum10-2)
}

这段代码是用于测试环形链表(Ring)的Unlink方法的单元测试,具体来说:

  • 创建一个包含 10 个元素的环形链表
  • 将环形链表移动 6 个位置,得到新的起始节点 s10
  • 计算前 10 个自然数的和
  • 验证 r10 和 s10 是否都包含 10 个元素,且元素和均为 sum10
  • 从 r10 中移除 0 个元素,得到空的环形链表
  • 从 r10 中移除 1 个元素,得到环形链表 r1
  • 验证 r1 是否包含一个元素,且元素和为 2
  • 验证 r10 是否包含 9 个元素,且元素为 sum10 - 2
  • 从 r10 中移除 9 个元素,得到环形链表 r9
  • 验证 r9 是否包含 9 个元素,且元素和为 sum10 - 2
  • 验证 r10 是否包含 9 个元素,且元素和也为 sum10 - 2

这段代码通过一系列的Unlink操作和验证,测试了环形链表的Unlink方法是否正常工作。每一步操作后都通过verify函数进行验证,确保环形链表的结构和元素值符合预期。

Unlink方法的参数是要移除的元素数量,当参数为0时,实际上并不会移除任何元素。因此 r10.Unlink(0) 就意味着从r10中移除0个元素,得到一个空的环形链表r0

image.png

如上图所示,r10 代表了长度为 10 的 Ring(值从 1~10),r10.Unlink(1) 的作用就是删除 r10 起始节点之后的一个节点,返回被删除的那部分 Ring 的起始节点(图中的节点 2),以 r10 作为起始节点的 Ring 就不包含节点 2 了。

再看一下 r10.Unlink(9) 的效果:

image.png

Unlink 底层调用的是 Move 方法,r10.Move(10) 以 r10 为起始节点移动 10 个位置后又指向了 r10 自身,然后 r10.Link(r10) 就相当于形成了一个单节点的 Ring(Value = 1),Unlink 的返回值则是被删除的那部分 Ring(长度为 9),以第二个节点作为起始节点。

4 总结

Go语言中的container包提供了三种常用的数据结构:环形链表(Ring)、双向链表(List)和堆(Heap)。这些数据结构在container子包中定义,分别是container/ringcontainer/listcontainer/heap

1. container/ring

环形链表是一种特殊的链表,其中最后一个元素指向第一个元素,形成一个环。

  • 主要类型

    • type Ring struct
  • 主要方法

    • func (r *Ring) Next() *Ring:返回下一个环元素。
    • func (r *Ring) Prev() *Ring:返回上一个环元素。
    • func (r *Ring) Move(n int) *Ring:移动n个位置,返回新的环元素。
    • func (r *Ring) Link(s *Ring) *Ring:链接两个环。
    • func (r *Ring) Unlink(n int) *Ring:移除n个元素,返回被移除的环。

2. container/list

双向链表是一种链表,其中每个节点都包含指向前一个和后一个节点的指针。

  • 主要类型

    • type List struct
    • type Element struct
  • 主要方法

    • func (l *List) PushFront(v interface{}) *Element:在链表前端插入元素。
    • func (l *List) PushBack(v interface{}) *Element:在链表后端插入元素。
    • func (l *List) Remove(e *Element) interface{}:移除指定元素。
    • func (l *List) Front() *Element:返回链表的第一个元素。
    • func (l *List) Back() *Element:返回链表的最后一个元素。

3. container/heap

堆是一种特殊的树形数据结构,满足堆属性:对于最大堆,父节点的值总是大于或等于子节点的值;对于最小堆,父节点的值总是小于或等于子节点的值。

  • 主要类型

    • type Interface interface
  • 主要方法

    • func Init(h Interface):初始化堆。
    • func Push(h Interface, x interface{}):向堆中插入元素。
    • func Pop(h Interface) interface{}:移除并返回堆中的最小元素(对于最小堆)。
    • func Remove(h Interface, i int) interface{}:移除并返回堆中索引为i的元素。
    • func Fix(h Interface, i int):重新调整堆中索引为i的元素。

Go语言的container包提供了三种常用的数据结构:环形链表、双向链表和堆。每种数据结构都有其特定的用途和操作方法,适用于不同的场景。通过使用这些数据结构,可以更方便地实现复杂的数据操作和算法。