二叉堆主要包含2个应用::
1、排序方法「堆排序」
2、数据结构 优先级队列
二叉堆是一个存储在数组里的完全二叉树
主要操作是插入和删除。
插入是先插到最后,然后上浮到正确位置;
删除是和堆底元素调换位置后再删除,然后下沉到正确位置。
const parent = (root) => {
return root/2
}
const left = (root) => {
return root/2
}
const rigth = (root) => {
return root/2+1
}
二叉堆还分为最大堆和最小堆。
最大堆的性质是:每个节点都大于等于它的两个子节点。类似的,
最小堆的性质是:每个节点都小于等于它的子节点。
优先级队列
插入insert或者删除delMax元素的时候,元素会自动排序,这底层的原理就是二叉堆的操作。
class primaryQueue {
constructor() {
this.heap = []
this.heap[0] = 0
}
swap(i, j) {
let temp = this.heap[i]
this.heap[i] = this.heap[j]
this.heap[j] = temp
}
getParentIdx(root) {
return Math.floor(root/2)
}
getLeftIdx(root) {
return root*2
}
getRightIdx(root) {
return root*2+1
}
swim(x) {
while(x > 1) { // x=1是根顶
let parent = this.getParentIdx(x)
if(this.heap[parent].val > this.heap[x].val) { // 小根堆
this.swap(parent, x)
x = this.getParentIdx(x)
}else {
break
}
}
}
sink(x) { // 下沉
while(this.heap[this.getLeftIdx(x)]) { // 叶子节点
let left = this.getLeftIdx(x) // 索引
let right = this.getRightIdx(x) // 索引
if(this.heap[right] && this.heap[left].val > this.heap[right].val) {
left = right
}
if(this.heap[x].val < this.heap[left].val) { // <max(a, b)
break
}
this.swap(left, x)
x= left
}
}
insert(val) {
this.heap.push(val)
this.swim(this.heap.length-1) // 插入堆底,堆底元素需要上浮
}
delMin(x) { // 删除最小节点-“最小堆”,交换和堆顶的元素,sink
const top = this.heap[1]
const len = this.heap.length-1
this.swap(1, len)
this.heap.length--
this.sink(1)
return top
}
getMin() {
return this.heap[1]
}
getSize() {
return this.heap.length
}
}
对于最大堆,会破坏堆性质的有两种情况:
1、节点 A 小于其中一个子节点,那么 A 就不配做父节点,应该下去,下面那个更大的节点上来做父节点,这就是对 A 进行下沉。
2、如果某个节点 A 比它的父节点大,那么 A 不应该做子节点,应该把父节点换下来,自己去做父节点,这就是对 A 的上浮。
最终操作只会在堆底和堆顶进行(等会讲原因),显然堆底的「错位」元素需要上浮,堆顶的「错位」元素需要下沉。