container 包實現了三個複雜的資料結構:堆,鏈表,環(heap、list 和 ring)。當我們需要使用這些資料結構時可以直接使用而不必自己去實現一遍相關演算法。
1. 堆
堆使用的資料結構是最小二叉樹,即根節點比左邊子樹和右邊子樹的所有值都小。 go的堆包只是實現了一個介面,看下它的定義:
type Interface interface { sort.Interface Push(x interface{}) // add x as element Len() Pop() interface{} // remove and return element Len() - 1.}
可以看出,這個堆組合了 sort 包的 sort.Interface
, 回顧下 sort.Interface
,它需要實現三個方法
Len() intLess(i, j int) boolSwap(i, j int)
加上堆介面定義的兩個方法
Push(x interface{})Pop() interface{}
就是說你定義了一個堆,就要實現五個方法,直接拿 package doc 中的example 做例子:
type IntHeap []intfunc (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}
那麼 IntHeap 就實現了這個堆結構,我們就可以使用堆的方法來對它進行操作:
h := &IntHeap{2, 1, 5}heap.Init(h)heap.Push(h, 3)heap.Pop(h)
具體說下內部實現,是使用最小堆,索引排序從根節點開始,然後左子樹,右子樹的順序方式。 內部實現了down
和up
分別表示對堆中的某個元素向上保證最小堆和向上保證最小堆。
當往堆中插入一個元素的時候,這個元素插入到最右子樹的最後一個節點中,然後調用up向上保證最小堆。
當要從堆中推出一個元素的時候,先吧這個元素和右子樹最後一個節點兌換,然後彈出最後一個節點,然後對root調用down,向下保證最小堆。
鏈表
鏈表就是一個有 prev
和 next
指標的數組了。它維護兩個type
,(注意,這裡不是interface)
type Element struct { next, prev *Element // 上一個元素和下一個元素 list *List // 元素所在鏈表 Value interface{} // 元素}type List struct { root Element // 鏈表的根項目 len int // 鏈表的長度}
基本使用是先建立list,然後往list中插入值,list就內部建立一個Element,並內部設定好Element的next,prev等。具體可以看下例子:
// This example demonstrates an integer heap built using the heap interface.package mainimport ( "container/list" "fmt")func main() { list := list.New() list.PushBack(1) list.PushBack(2) fmt.Printf("len: %v\n", list.Len()); fmt.Printf("first: %#v\n", list.Front()); fmt.Printf("second: %#v\n", list.Front().Next());}
output:len: 2first: &list.Element{next:(*list.Element)(0x2081be1b0), prev:(*list.Element)(0x2081be150), list:(*list.List)(0x2081be150), Value:1}second: &list.Element{next:(*list.Element)(0x2081be150), prev:(*list.Element)(0x2081be180), list:(*list.List)(0x2081be150), Value:2}
list對應的方法有:
type Element func (e *Element) Next() *Element func (e *Element) Prev() *Elementtype List func New() *List func (l *List) Back() *Element // 最後一個元素 func (l *List) Front() *Element // 第一個元素 func (l *List) Init() *List // 鏈表初始化 func (l *List) InsertAfter(v interface{}, mark *Element) *Element // 在某個元素後插入 func (l *List) InsertBefore(v interface{}, mark *Element) *Element // 在某個元素前插入 func (l *List) Len() int // 在鏈表長度 func (l *List) MoveAfter(e, mark *Element) // 把e元素移動到mark之後 func (l *List) MoveBefore(e, mark *Element) // 把e元素移動到mark之前 func (l *List) MoveToBack(e *Element) // 把e元素移動到隊列最後 func (l *List) MoveToFront(e *Element) // 把e元素移動到隊列最頭部 func (l *List) PushBack(v interface{}) *Element // 在隊列最後插入元素 func (l *List) PushBackList(other *List) // 在隊列最後插入接上新隊列 func (l *List) PushFront(v interface{}) *Element // 在隊列頭部插入元素 func (l *List) PushFrontList(other *List) // 在隊列頭部插入接上新隊列 func (l *List) Remove(e *Element) interface{} // 刪除某個元素
環
環的結構有點特殊,環的尾部就是頭部,所以每個元素實際上就可以代表自身的這個環。 它不需要像 list 一樣保持 list 和 element 兩個結構,只需要保持一個結構就行。
type Ring struct { next, prev *Ring Value interface{}}
我們初始化環的時候,需要定義好環的大小,然後對環的每個元素進行賦值。環還提供一個 Do
方法,能便利一遍環,對每個元素執行一個function。 看下面的例子:
// This example demonstrates an integer heap built using the heap interface.package mainimport ( "container/ring" "fmt")func main() { ring := ring.New(3) for i := 1; i <= 3; i++ { ring.Value = i ring = ring.Next() } // 計算1+2+3 s := 0 ring.Do(func(p interface{}){ s += p.(int) }) fmt.Println("sum is", s)}
output:sum is 6
ring提供的方法有
type Ring func New(n int) *Ring // 初始化環 func (r *Ring) Do(f func(interface{})) // 迴圈環進行操作 func (r *Ring) Len() int // 環長度 func (r *Ring) Link(s *Ring) *Ring // 串連兩個環 func (r *Ring) Move(n int) *Ring // 指標從當前元素開始向後移動或者向前(n可以為負數) func (r *Ring) Next() *Ring // 當前元素的下個元素 func (r *Ring) Prev() *Ring // 當前元素的上個元素 func (r *Ring) Unlink(n int) *Ring // 從當前元素開始,刪除n個元素