使用 container/heap

在这一小节中,你将了解 container/heap 包中提供的功能。首先,你应该知道的是 container/heap 实现的堆是一个树结构,树上的每个节点都是其所在子树上最小的元素。注意,我这里说的是最小的元素而不是最小的值是为了突出堆不止支持数值。

不过你可能已经猜到了,为了使用 Go 语言实现堆积树,你需要自己开发一种用来比较两个元素大小的方法。这种情况下,使用 Go 语言中的接口来定义比较合适。

这意味着 container/heap 包比 container 包中的其他两个包更加先进,而且你需要先完成一些定义之后才能使用 container/heap 包的功能。更准确地说,container/heap 包要求你实现 container/heap.Interface,其定义如下:

  1. type Interface struct {
  2. sort.Interface
  3. Push(x interface{}) // 将 x 添加为第 Len() 个元素
  4. Pop() interface{} // 删除并返回第 Len() - 1 个元素。
  5. }

第 7 章“反射和接口”中会更详细地介绍接口。目前,你只用记住实现 Go 语言的接口只需要实现接口中的函数和其中组合的其他接口,比如上面的例子中的 sort.InterfacePush() 函数和 Pop() 函数。sort.Interface 中需要实现的函数包括 Len()Less()Swap(),实现这些函数很有必要,因为实现排序的功能必须要先实现交换两个元素、计算需要排序的对象的值以及根据前面计算的值判断两元素大小这些功能。尽管你可能认为这工作量很大,但大多数情况下这些函数的实现要么很琐碎,要么很简单。

由于这一节的目的是为了阐明 container/heap 的用法而不是为了让你头疼,例子中元素的数据类型都将使用 float32

conHeap.go 中的 Go 语言代码将分为五个部分介绍。第一部分如下:

  1. package main
  2. import (
  3. "container/heap"
  4. "fmt"
  5. )
  6. type heapFloat32 []float32

conHeap.go 的第二部分是如下的 Go 代码:

  1. func (n *heapFloat32) Pop() interface{} {
  2. old := *n
  3. x := old[len(old)-1]
  4. new := old[0 : len(old)-1]
  5. *n = new
  6. return x
  7. }
  8. func (n *heapFloat32) Push(x interface{}) {
  9. *n = append(*n, x.(float32))
  10. }

尽管这里定义了 Pop()Push() 这两个函数,但这只是为了遵循接口的要求。当你需要在堆中增删元素的时候还是应该分别调用 heap.Push()heap.Pop()

conHeap.go 的第三个代码段包含如下的 Go 代码:

  1. func (n heapFloat32) Len() int {
  2. return len(n)
  3. }
  4. func (n heapFloat32) Less(a, b int) bool {
  5. return n[a] < n[b]
  6. }
  7. func (n heapFloat32) Swap(a, b int) {
  8. n[a], n[b] = n[b], n[a]
  9. }

这一部分实现了 sort.Interface 接口所需要的三个函数。

conHeap.go 的第四部分如下:

  1. func main() {
  2. myHeap := &heapFloat32{1.2, 2.1, 3.1, -100.1}
  3. heap.Init(myHeap)
  4. size := len(*myHeap)
  5. fmt.Printf("Heap size: %d\n", size)
  6. fmt.Printf("%v\n", myHeap)

conHeap.go 的最后一个代码段如下:

  1. myHeap.Push(float32(-100.2))
  2. myHeap.Push(float32(0.2))
  3. fmt.Printf("Heap size: %d\n", len(*myHeap))
  4. fmt.Printf("%v\n", myHeap)
  5. heap.Init(myHeap)
  6. fmt.Printf("%v\n", myHeap)
  7. }

conHeap.go 的最后一部分中使用 heap.Push()myHeap 中添加了两个新元素。然而,为了让堆重新有序排列,你需要再次调用 heap.Init()

执行 conHeap.go 将生成如下输出:

  1. $ go run conHeap.go
  2. Heap size: 4
  3. &[-100.1 1.2 3.1 2.1]
  4. Heap size: 6
  5. &[-100.1 1.2 3.1 2.1 -100.2 0.2]
  6. &[-100.2 -100.1 0.2 2.1 1.2 3.1]

输出的最后一行中的 2.1 1.2 3.1 这三个数的顺序看上去有些不对,这是因为堆不是按照线性逻辑进行排序的。请记住,堆是一个树状结构,而不是像数组或切片这样的线性结构。