Go标准库提供了二叉堆的接口实现,源码在heap.go中。以下是其中几个核心函数。up和down分别表示自顶向下和自底向上调整二叉堆的过程。
(1)对于down方法来说,如果发生了交换,即将节点i与其左右子树中最小的j发生了交换,则满足`i > i0`返true,若没有发生交换则返回false
(2)对于up方法来说,则是不断重复地将当前节点i与其父结点j比较,若节点i的值小于节点j的值则交换(默认为小顶堆),直到遇到了根节点为止
```go
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
}
```
对于Remove方法,若待删除的节点i刚好是最末尾的元素则啥也不做直接调用用户提供的Pop方法将末尾元素移除即可。否则需要将节点i与末尾节点n交换,随后从i节点处自顶向下地调整堆结构使其符合二叉堆的性质。
那么问题来了,**为什么在down方法返false,即没有发生交换的情况下,需要从节点i开始调用up进行自底向上地调整二叉堆**?**被交换到i处的末尾元素n与原先的i节点是子节点的关系,肯定不会小于节点i的值,那必定也是不比节点i的父节点的值小,即小顶堆的性质是满足的,那为何还要调用up方法呢**?
```go
// Remove removes and returns the element at index i from the heap.
// The complexity is O(log n) where n = h.Len().
func Remove(h Interface, i int) any {
n := h.Len() - 1
if n != i {
h.Swap(i, n)
if !down(h, i, n) {
up(h, i)
}
}
return h.Pop()
}
```
Fix也是一样的情况,为何需要调用up?
```go
// Fix re-establishes the heap ordering after the element at index i has changed its value.
// Changing the value of the element at index i and then calling Fix is equivalent to,
// but less expensive than, calling Remove(h, i) followed by a Push of the new value.
// The complexity is O(log n) where n = h.Len().
func Fix(h Interface, i int) {
if !down(h, i, h.Len()) {
up(h, i)
}
}
```
更多评论
以一個最小堆為例,即使交換的元素已經大於它的子節點,但我們仍然需要確保該元素小於或等於它的父節點,這是因為交換的元素可能大於原來位置的元素,因此需要通過呼叫 up() 來進行向上的調整。這就確保了堆的性質仍然保持,即每個節點都小於或等於它的子節點,並且大於或等於它的父節點。
#1
假設有以下的最小堆
1
/ \
3 2
/ \ / \
5 6 7 8
\
9
移除元素 3,它將與最後一個元素 9 進行交換:
1
/ \
9 2
/ \ / \
5 6 7 8
元素 9 的位置違反了最小堆的性質,因為它大於它的兩個子節點 5 和 6。嘗試進行下行調整,可以將 9 與它的兩個子節點中的最小者(也就是 5)進行交換:
1
/ \
5 2
/ \ / \
9 6 7 8
下行調整可以成功地將 9 移到它應該在的位置。現在假設要移除元素 2,將它與最後一個元素 9 進行交換:
1
/ \
5 9
/ \ / \
6 7 3 8
這次,元素 9 在它的位置違反了最小堆的性質,因為它大於它的子節點 3。所以進行下行調整,可以將 9 與它的子節點 3 進行交換:
1
/ \
5 3
/ \ / \
6 7 9 8
此時,堆已經符合最小堆的性質。在這個例子中,儘管初始的下行調整不能將 9 移到它應該在的位置
大蓋4J個樣子
#3