This is a creation in Article, where the information may have evolved or changed.
The go language's official package contains "Container/heap", which defines the use interface of the heap (heap) data structure within the package. As long as the custom data types implement standard interfaces, it is convenient to sort the custom data types in the heap.
The interface for the heap structure is:
type Interface interface {sort.InterfacePush(x interface{}) // add x as element Len()Pop() interface{} // remove and return element Len() - 1.}
At the same time sort. The interface interface is:
type Interface interface {// Len is the number of elements in the collection.Len() int// Less reports whether the element with// index i should sort before the element with index j.Less(i, j int) bool// Swap swaps the elements with indexes i and j.Swap(i, j int)}
Therefore, in order to use a custom heap structure, you need to define 5 interface functions.
In practice, it is desirable to construct a timer queue that uses heap sequencing to quickly get the most recent task trigger time according to the elements on top of the heap:
type element struct {TaskId stringExpire time.Time...}type eleHeap []*element
To construct a custom heap structure interface:
func (h eleHeap) Len() int { return len(h) }func (h eleHeap) Less(i, j int) bool { return h[i].Expire.Before(h[j].Expire) }func (h eleHeap) Swap(i, j int) { h[i], h[j] = h[j], h[i] }func (h *eleHeap) Push(x interface{}) {// Push and Pop use pointer receivers because they modify the slice's length,// not just its contents.*h = append(*h, x.(*element))}func (h *eleHeap) Pop() interface{} {old := *hn := len(old)x := old[n-1]// 为什么Pop是取slice最末元素?*h = old[0 : n-1] // 难道Golang的堆不是最小堆吗?return x}
After that, you can use the common functions of the heap to perform pop and push operations on the custom heap structure.
to := time.NewTimer(WaitInterval)hp := &eleHeap{} // 定义变量heap.Init(hp) // 堆结构初始化for {select {case ele := <-TaskChan:heap.Push(hp, ele) // 入堆to.Reset(0)case <-to.C:for hp.Len() != 0 {ele, ok := heap.Pop(hp).(*element) // 出堆if ok {if time.Now().Before(ele.Expire) {heap.Push(hp, ele) // 时辰未到,再次入堆to.Reset(ele.Expire.Sub(now))break}// time expired, do task...}}}}}
In the process of using the heap, it is somewhat confusing to understand why the last element in the queue was taken out during the custom pop process. The custom push process also puts the element at the bottom, so the pop process should take the smallest (large) element of the heap from the head of the queue after the float and sink operation. After viewing the "Container/heap" source code, finally opened the puzzle.
First look at the heap floating, sinking operation:
func up(h Interface, j int) {for {i := (j - 1) / 2 // parentif i == j || !h.Less(j, i) {break}// 在上浮过程中,从较大的节点j开始,与其父节点进行比较// 若不小于其父节点,则与其父节点进行交换h.Swap(i, j)j = i}}func down(h Interface, i0, n int) bool {i := i0for {j1 := 2*i + 1if j1 >= n || j1 < 0 { // j1 < 0 after int overflowbreak}j := j1 // left childif j2 := j1 + 1; j2 < n && !h.Less(j1, j2) {j = j2 // = 2*i + 2 // right child}if !h.Less(j, i) {break}// 在下沉过程中,从较小的i0节点开始,与其两个子节点进行比较// 若小于其中一个子节点,则与较小的子节点进行交换h.Swap(i, j)i = j}return i > i0}
The float and sink processes are still very standard, and look at the push and pop processes:
func Push(h Interface, x interface{}) {h.Push(x) // 使用自定义的Push接口,将元素放入队列尾部up(h, h.Len()-1) // 将新放入的元素进行上浮操作}func Pop(h Interface) interface{} {n := h.Len() - 1h.Swap(0, n) // 交换队列中头和尾元素down(h, 0, n) // 将原队列的尾元素在h.Len() - 1的新队列中进行下沉return h.Pop() // 弹出交换后的尾元素}
This shows that the error of understanding the custom pop function is due to the thought of the heap structure itself. If the Heap.pop process obtains the value of the head element and then puts the tail element into the queue header to sink, the custom pop is the return queue header element.
The truth is only one ~