Binary Heap 和 Binary Search Tree 很類似,但規則上有些不同。
有兩種 Binary Heap :
像上圖就是屬於 Max Binary Heap
而 Max Binary Heap 的規則就只管父節點比子節點大而已,就算某個子節點比父節點的相鄰節點大也沒關係。
還有另個與 Binary Search Tree 不同的規則是,加新節點時,一律往左邊先加,左邊有了加右邊,同層都滿了就到下一層的最左邊開始加。
Binary Heap 可用來實作 Priority Queues,也會在之後會寫到的 Graph Traversal 用到。
推薦觀看此網站的動畫演示
而也因為必須先加左再加右,同層滿了才可以往下加的規則,所以我們可以為每個節點標記 Index 順序,而存成陣列的形式。
延續上張圖的 Binary Heap
這邊可以觀察出一些規則:
由於上述的規則,我們可以將節點們存成陣列形式即可。
class MaxBinaryHeap {
  constructor() {
    this.values = []
  }
}
新增節點時,我們會將其加在最後面。
沿用上圖例子來說,新增一個 95 :
[93, 89, 80, 81, 88, 63, 21, 75, 11, 13, 50, 95]
95 的父節點來比大小,這邊可以用上面有提到的 (N-1) / 2 取整數 (floor) 方法來找到95 比其父節點 63 大,故兩個需要交換位置
95 比新父節點 80 大,故兩個需要交換位置95 比新父節點 93 大,故兩個需要交換位置
若是比父節點小,就可以直接結束。
insert(element) {
  this.values.push(element)
  this.bubbleUp()
  return this.values
}
bubbleUp() {
  let currentIndex = this.values.length - 1
  const newElement = this.values[currentIndex]
  while (currentIndex > 0) {
    let parentIndex = Math.floor((currentIndex - 1) / 2)
    let parentElement = this.values[parentIndex]
    if (newElement <= parentElement) break
    this.values[parentIndex] = newElement
    this.values[currentIndex] = parentElement
    currentIndex = parentIndex
  }
}
接著來實作移除根節點 (最大值) 的方法。
步驟如下:
若移除根節點後就沒有其他節點了,就直接回傳被移除的節點,不用再做比對交換。
extractMax() {
  const maxElement = this.values[0]
  const lastElement = this.values.pop()
  if (this.values.length > 0) {
    this.values[0] = lastElement
    this.sinkDown()
  }
  return maxElement
}
sinkDown() {
  let currentIndex = 0
  const element = this.values[currentIndex]
  let leftIndex = 2 * currentIndex + 1
  let rightIndex = 2 * currentIndex + 2
  while (leftIndex < this.values.length || rightIndex < this.values.length) {
    let leftElement = this.values[leftIndex]
    let rightElement = this.values[rightIndex]
    let swapLeft = false
    let swapRight = false
    if (leftElement && element < leftElement) swapLeft = true
    if (rightElement && element < rightElement) swapRight = true
    if (swapLeft && swapRight) {
      if (leftElement > rightElement) {
        swapRight = false
      } else {
        swapLeft = false
      }
    }
    if (swapLeft) {
      this.values[leftIndex] = element
      this.values[currentIndex] = leftElement
      currentIndex = leftIndex
    } else if (swapRight) {
      this.values[rightIndex] = element
      this.values[currentIndex] = rightElement
      currentIndex = rightIndex
    } else {
      break
    }
    leftIndex = 2 * currentIndex + 1
    rightIndex = 2 * currentIndex + 2
  }
}
| Search | Insertion | Removal | 
|---|---|---|
| O(n) | O(log n) | O(log n) | 
沿用此張圖示例
搜尋的部分, Binary Heap 只保證上下節點的大小規則,所以假設要找 13 ,從根節點開始遍歷在第二層遇到 89、93 時這兩個值是都有可能在 13 上面的,所以還是需要遍歷整個節點們。
Insertion 與 Removal 都是類似的,從最後開始往上比對跟從頭開始往下比對,每次比對都是走另半邊,不用全部都比對一次。
至於為何沒有 Worst Case 與 Best Case 呢?
是因為 Binary Heap 一定都是同層加滿後才會往下一層加,不會像下圖 Binary Search Tree 這樣集中分佈在某一邊。
