go源码阅读 - container/heap

container/heap

container/heap包对通用堆进行了定义并实现了标准堆操作函数,以此为基础可以很容易对各类堆和优先队列进行实现。

堆通常是一个可以被看做一棵树的数组对象。在队列中,调度程序反复提取队列中第一个作业并运行,因为实际情况中某些时间较短的任务将等待很长时间才能结束,或者某些不短小,但具有重要性的作业,同样应当具有优先权。堆即为解决此类问题设计的一种数据结构。

堆具有以下特性:

1. 任意节点大于或者小于他的所有后裔,最小元或者最大元在堆的根节点上

2. 堆总是一颗完全树。即除了最底层,其他层总是被占满

由于堆是完全二叉树,因此可以使用顺序数组来表示,如下图

堆的基础存储是一个树形结构,可以用数组或是链表实现:

type Interface interface {
sort.Interface
Push(x interface{}) // add x as element Len()
Pop() interface{} // remove and return element Len() - 1.
}
可以看出,这个堆结构继承自 sort.Interface, sort.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)

再加上堆定义了两个方法
Push(x interface{}) // add x as element Len()
Pop() interface{} // remove and return element Len() - 1.

也就是说,如果要定义一个堆,需要实现这五个方法


1. heap.Init()
func Init(h Interface) {
   // heapify
n := h.Len()
for i := n/2 - 1; i >= 0; i-- {
down(h, i, n)
}
}
// 任选一个元素 i, 将其与他的子节点 2i + 1 和 2i + 2 比较,如果元素 i比他的子节点大,则将元素i与子节点的最小值进行交换,从而满足最小树的要求。
// 为什么元素i比他的子节点都小,就可以跳出循环?
// 因为在 Init函数中,第一个开始 down的元素是 n/2-1个,总是可以保证从最后一颗子树开始down。因此可以保证Init->down时,如果元素 i 比它的两个子节点都小,那么该元素对应的子树,就是最小堆。
func down(h Interface, i0, n int) bool {
i := i0
for {
j1 := 2*i + 1
// 如果 2i + 1 在数组或者链表中的位置超出数组或者链表的长度,则 break
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
}
// 如果 i < 左右子节点中的最小值,则 break
if !h.Less(j, i) {
break
}
// 不然,交换 i, j的值; 即交换 i 和 左右子节点中的最小值
h.Swap(i, j)
i = j
}
return i > i0
}

2. head.Push()
// 首先将元素 x 将元素写入定义的 Interface中,而后通过 Up()来保证最小堆的顺序。
func Push(h Interface, x interface{}) {
h.Push(x)
up(h, h.Len()-1)
}
func up(h Interface, j int) {
for {
// 查找元素 j 的父节点,如果元素 j的父节点等于 j或者比j小,break;否则交换 i 和 j的值
i := (j - 1) / 2 // parent
if i == j || !h.Less(j, i) {
break
}
h.Swap(i, j)
j = i
}
}

3. heap.Pop()
func Pop(h Interface) interface{} {
//将根节点与末尾节点交换,而后使用 down()将根节点下沉到合适的位置,二后使用 h.Pop() 获取最后一个元素,即获取堆的根节点
n := h.Len() - 1
h.Swap(0, n)
down(h, 0, n)
return h.Pop()
}

4. heap.Remove()
func Remove(h Interface, i int) interface{} {
// 先将要删除的节点 i 与最后一个节点做交换,然后将新的节点i上浮或者下沉到合适的位置。
n := h.Len() - 1
if n != i {
h.Swap(i, n)
if !down(h, i, n) {
up(h, i)
}
}
return h.Pop()
}

5. heap.Fix()
func Fix(h Interface, i int) {
// 如果能下沉,则下沉,否则上浮。
if !down(h, i, h.Len()) {
up(h, i)
}
}
posted @ 2021-04-19 15:43  卷毛狒狒  阅读(122)  评论(0编辑  收藏  举报