Golang標準庫學習—container/heap

Lazyboy_Chen7發表於2019-03-06

container包中有三個資料結構:heap(堆)、list(連結串列)、ring(環)

Package heap 

import "container/heap"

heap包提供了對任意實現了heap.Interface介面的型別的堆操作。最小堆是具有“每個節點都是以其為根的子樹中最小值”屬性的樹。最大堆相反。

樹的最小元素為其根元素,索引0的位置。最大堆相反。

heap是常用的實現優先佇列的方法。要建立一個優先佇列,實現一個具有使用(負的)優先順序作為比較的依據的Less方法的Heap介面,如此一來可用Push新增專案而用Pop取出佇列最高優先順序的專案。

type Interface 

這裡不像list宣告型別,而是直接作為介面令外部使用

type Interface interface {
	sort.Interface
	Push(x interface{}) // add x as element Len()
	Pop() interface{}   // remove and return element Len() - 1.
}

任何實現了本介面的型別都可以用於構建最小堆。最小堆可以通過heap.Init建立,資料是遞增順序或者空的話也是最小堆。

其中,這個堆結構組合了sort.Interface,所以需要實現Len(), Less(), Swap()三個方法。

注意介面的Push和Pop方法是供heap包呼叫的,使用heap.Push和heap.Pop來向一個堆新增或者刪除元素。

 heap對外提供五個方法

首先是Init方法。一個堆在使用任何堆操作之前應先初始化。Init函式對於堆的約束性是冪等的(多次執行無意義),並可能在任何時候堆的約束性被破壞時被呼叫。函式複雜度為O(n),其中n等於h.Len()。其中存在down內部方法,後面說。

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

接著是向堆中新增元素的Push方法。向堆h中插入元素x,並保持堆的約束性。複雜度O(log(n)),其中n等於h.Len()。

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

還有從堆中刪除根元素的Pop方法。刪除並返回堆h中的最小元素(不影響約束性)。複雜度O(log(n))。

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

然後是從堆中刪除第i各元素的Remov方法。刪除堆中的第i個元素,並保持堆的約束性。複雜度O(log(n))。

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

 最後是調整堆的Fix方法。在修改第i個元素後,呼叫本函式修復堆,比刪除第i個元素後插入新元素更有效率。複雜度O(log(n))。

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

heap內部存在兩個方法來實現對外的五個方法

兩個方法分別實現堆的上下調整,單獨呼叫只能保證呼叫節點保持約束性,不能保證其它節點。

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

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

這裡不再做例項了,只要實現相應方法就可以使用堆了。好像golang裡也沒有像C++那樣做了priority_queue優先佇列的庫實現,用的時候自己做一個8。(標準庫裡好像有例子)

堆的用處包括使用頻率都還是很大的,具體的實現原理不再贅述,但還是要牢牢把握啊。

記錄每天解決的一點小問題,積累起來就能解決大問題。

相關文章