跳至主要内容

[DS] Binary (Max) Heap

Binary Max Heap property: The parent of each vertex - except the root - contains value greater than the value of that vertex. This is an easier-to-verify definition than the following alternative definition: The value of a vertex - except the leaf/leaves - must be greater than the value of its one (or two) child(ren).

Binary Heap (Priority Queue) - VisuAlgo

1-based Compact Array

we can implement basic binary tree traversal operations with simple index manipulations (with help of bit shift manipulation):

  1. parent(i) = i>>1, index i divided by 2 (integer division),
  2. left(i) = i<<1, index i multiplied by 2,
  3. right(i) = (i<<1)+1, index i multiplied by 2 and added by 1.

Basic Operation

siftUp

siftUp swaps a node that is too large with its parent (thereby moving it up) until it is no larger than the node above it.

func (h *Heap) siftUp(idx int) {
parent := idx >> 1
for idx > 1 && (*h)[idx] > (*h)[parent] {
(*h)[idx], (*h)[parent] = (*h)[parent], (*h)[idx]
idx = parent
parent = idx >> 1
}
}

siftDown

siftDown swaps a node that is too small with its largest child (thereby moving it down) until it is at least as large as both nodes below it.

func (h *Heap) siftDown(idx int) {
isLChildLarger := false
isRChildLarger := false

left := idx << 1
if left < len(*h) {
if (*h)[left] > (*h)[idx] {
isLChildLarger = true
}
}

right := left + 1
if right < len(*h) {
if (*h)[right] > (*h)[idx] {
isRChildLarger = true
}
}

if isLChildLarger && isRChildLarger {
if (*h)[right] > (*h)[left] {
(*h)[right], (*h)[idx] = (*h)[idx], (*h)[right]
h.siftDown(right)
} else {
(*h)[left], (*h)[idx] = (*h)[idx], (*h)[left]
h.siftDown(left)
}
} else if isRChildLarger {
(*h)[right], (*h)[idx] = (*h)[idx], (*h)[right]
h.siftDown(right)
} else if isLChildLarger {
(*h)[left], (*h)[idx] = (*h)[idx], (*h)[left]
h.siftDown(left)
}
}

Method

All Binary Max Heap method could be finish by combination of basic operation.

Create

O(N log N)

Start from an empty Binary Max Heap

for (i = 0; i < A.length; ++i)
Insert(A[i])

O(N)

The input array A as it is

for (i = A.length/2; i >= 1; --i)
siftDown(i)

Insert

func (h *Heap) Insert(num int) {
*h = append(*h, num)
h.siftUp(len(*h) - 1)
}

ExtractMax

Because we promote a leaf vertex to the root vertex of a Binary Max Heap, it will very likely violates the Max Heap property. ExtractMax() operation then fixes Binary Max Heap property from the root downwards by comparing the current value with the its child/the larger of its two children (if necessary).

func (h *Heap) ExtractMax() (int, error) {
if len(*h) < 1 {
return 0, fmt.Errorf("Empty Heap")
}

result := (*h)[1]
(*h)[1] = (*h)[len(*h)-1]
*h = (*h)[:len(*h)-1]
h.siftDown(1)
return result, nil
}

UpdateKey(i, newv)

If the index i of the value is known, we can do the following simple strategy:

  1. Simply update A[i] = newv
  2. call both shiftUp(i) and shiftDown(i) only at most one operation will be triggered.
A[i] = newv; // let oldv = A[i]
shiftup(i); // if newv > oldv
shiftdown(i); // if newv < oldv

Delete(i)

Let A[i] become the new max one and fix the Heap, then ExtractMax().

A[i] = A[1]+1;
siftUp(i); // new max/root
ExtractMax(); // now easy to delete

Heap Sort

HeapSort() operation (assuming the Binary Max Heap has been created in O(N)) is very easy. Simply call the O(log N) ExtractMax() operation N times.

Simple Analysis: HeapSort() clearly runs in O(N log N) — an optimal comparison-based sorting algorithm.

func Sort(nums []int) []int {
h := NewHeap()
h.Create(nums)
result := make([]int, len(nums))
for i := len(nums) - 1; i >= 0; i-- {
result[i], _ = h.ExtractMax()
}
return result
}

小結

這次特別使用類別來實作 Binary Heap. Heap 中 siftUp / siftDown 的操作性質較偏向 class 內 private method. Insert / ExtractMax / UpdateKey / Delete 是偏向 public method 的操作. 而 HeapSort 則非常適合用 class methos 來實作.

趁這機會練習 Go / JS / Python 幾種語言中的類別寫法ㄡ. OOP 的觀念和能力在專案開發很實用, 熟悉如何實作或模擬 OOP 的操作以及相關限制, 是重要的基礎.

See Also

Heap

Class 類別