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
堆的建立(堆化)与调整
- 初始建堆,将原始底层数组以二叉树形式组织进行堆化,使所有父节点的值小于子节点。
- 此时堆顶元素是整个排列中的最小值,将其与最后一个节点进行交换后移出队列,此时堆顶元素就相当于确定了最终的排序位置。
- 由于将末尾元素移动到了堆顶,此时可能已经不再满足堆的性质,所以需要对堆进行调整。即对根节点进行向下调整,使其重新满足堆性质。
- 重复 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
}
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 函数的功能,具体来说,它做了以下几件事情:
- 测试 New 函数
- 循环 10 次,每次调用 New(i) 创建一个长度为 i 的环
- 使用 verify 函数验证创建的环长度是否正确并且不关心环中元素的和(用 -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 方法用于将两个环链接在一起,具体来说,测试了以下情况:
- 将一个元素的环与一个空环链接
- 创建一个包含一个元素的环 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
。
如上图所示,r10 代表了长度为 10 的 Ring(值从 1~10),r10.Unlink(1) 的作用就是删除 r10 起始节点之后的一个节点,返回被删除的那部分 Ring 的起始节点(图中的节点 2),以 r10 作为起始节点的 Ring 就不包含节点 2 了。
再看一下 r10.Unlink(9) 的效果:
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/ring
、container/list
和container/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
包提供了三种常用的数据结构:环形链表、双向链表和堆。每种数据结构都有其特定的用途和操作方法,适用于不同的场景。通过使用这些数据结构,可以更方便地实现复杂的数据操作和算法。