如果在收集元素的过程中,想要一并排序,方式之一是使用堆排序,对于这个需求,Go 提供了heap
包作为实现上的辅助。
heap
包提供的是最小堆树演算,底层的数据结构必须实现heap.Interface
:
type Interface interface {
sort.Interface
Push(x interface{})
Pop() interface{}
}
也就是说,除了实现sort.Interface
的Len
、Less
、Swap
方法之外,还要实现Push
与Pop
的行为,在heap
的 Go 官方文件说明有个简单范例:
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] }
func (h *IntHeap) Push(x interface{}) {
*h = append(*h, x.(int))
}
func (h *IntHeap) Pop() interface{} {
old := *h
n := len(old)
x := old[n-1]
*h = old[0 : n-1]
return x
}
实现了heap.Interface
的数据结构,就可以透过heap
包中的Init
、Push
、Pop
等函数来进行操作:
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))
}
在Push
、Pop
过程中有关堆树的调整,就都由heap.Push
、heap.Pop
等函数来处理了。
官方文件提供的范例是可以简单示范heap
包的使用,不过,一下子使用heap.Xxx
,一下子又是使用h.Xxx
的混合风格,看来蛮怪的,可以来改变一下:
package main
import (
"container/heap"
"fmt"
)
// IntSlice 实现了 heap.Interface
type IntSlice []int
func (s IntSlice) Len() int { return len(s) }
func (s IntSlice) Less(i, j int) bool { return s[i] < s[j] }
func (s IntSlice) Swap(i, j int) { s[i], s[j] = s[j], s[i] }
func (s *IntSlice) Push(x interface{}) {
*s = append(*s, x.(int))
}
func (s *IntSlice) Pop() interface{} {
old := *s
n := len(old)
x := old[n-1]
*s = old[0 : n-1]
return x
}
// IntHeap 封装了 IntSlice
type IntHeap struct {
elems IntSlice
}
// 实现相关函数或方法时,透过 heap 提供的函数
func NewIntHeap(numbers ...int) *IntHeap {
h := &IntHeap{IntSlice(numbers)}
heap.Init(&(h.elems))
return h
}
func (h *IntHeap) Push(n int) {
heap.Push(&(h.elems), n)
}
func (h *IntHeap) Pop() int {
return heap.Pop(&(h.elems)).(int)
}
func (h *IntHeap) Len() int {
return len(h.elems)
}
// 一律透过 h 来操作
func main() {
h := NewIntHeap(2, 1, 5)
h.Push(3)
for h.Len() > 0 {
fmt.Printf("%d ", h.Pop())
}
}
官方文件提供的范例中,还有个PriorityQueue
的实现,类似地,该范例是简单示范,混合了两种操作风格,你也可以试着自行把heap.Xxx
的操作给封装起来。