heap 套件


如果在收集元素的過程中,想要一併排序,方式之一是使用堆積排序,對於這個需求,Go 提供了 heap 套件作為實現上的輔助。

heap 套件提供的是最小堆積樹演算,底層的資料結構必須實現 heap.Interface

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

也就是說,除了實現 sort.InterfaceLenLessSwap 方法之外,還要實現 PushPop 的行為,在 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 套件中的 InitPushPop 等函式來進行操作:

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))
}

PushPop 過程中有關堆積樹的調整,就都由 heap.Pushheap.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 的操作給封裝起來。