堆的基本概念,原理分析,和它的 js 代码的实现……
一 基本概念
堆(Heap),可以理解为一个实现二叉树的数组,本质上是一种二叉树,但存储的形式是数组,与普通数组的区别在于,它有特定的排序方式。
堆可以分为两种:
- 最大堆:每个根节点的值都大于(含等于)子节点。
- 最小堆:每个根节点的值都小于(含等于)子节点。
堆的根节点中存放的是最大或者最小元素,但是其他节点的排序顺序是未知的。
堆化, 维护最大/小堆性质的过程,和子节点不断比较,将最大/小值和父节点交换。
最大堆的例子:
如上图,存储为数组:
[10, 7, 2, 5, 1]
数组元素的顺序,按照二叉树的每一排依次进行排序。
二 索引的计算
如果单看数组,如何知道哪一个节点是父节点,哪一个节点是它的子节点?
对于索引为 n 的节点,它的:
- 父节点索引为:(n - 1) / 2。
- 子左节点索引为:2n + 1。
- 右子节点索引为:2n + 2。
如果子节点的索引超过了数组的大小,则说明子节点不存在。
其他计算公式
树的高度,是指从树的根节点到最低叶节点所需要的步数,即一个高度为 h 的堆有 h + 1 层。
如果一个堆有 n 个节点,那么它的高为 h = floor(log2(n))。(floor 舍小数取整方法)
已知树高 h ,则整个堆的节点数目为:2^(h + 1) - 1。
叶节点总是位于数组的 floor(n/2) 与 n - 1 之间。
三 堆的常用操作及增删原理
| 操作 | 描述 |
|---|---|
| shiftUp() | 一个节点需要将它同父节点交换位置以实现‘有效的堆’,这个节点在数组中的位置上升 |
| shiftDown() | 一个节点需要将它同子节点交换位置以实现‘有效的堆’,这个节点在数组中的位置后移。这个操作也叫堆化 |
| insert(value) | 在堆尾添加一个元素,然后使用 shiftUp 修复 |
| removeRoot() | 移除最大/小值,移除后要将最后一个元素补过来,然后用 shiftDown 修复 |
| removeAtIndex(index) | 移除 index 位置的节点,移除后要将最后一个元素补过来,然后用 shiftUp/shiftDown 修复 |
| replace(index, value) | 替换节点,然后用 shiftUp 修复 |
| search(value) | 寻找 index |
| buildHeap(array) | 反复调用 insert() 方法将一个(无序)数组转换成一个堆 |
| peek() | 返回最大/小值 |
insert 示例(最大堆)
将数字 16 插入到上图中的堆中,即在堆尾添加 16。数组为 [10, 7, 2, 5, 1, 16]。
相应的树变成了:
此时,子节点大于根节点,不满足堆属性,为了满足堆属性,需要将数字 2 和数字 16 交换位置:
这时,父节点 10 仍小于 子节点 16,仍不满足堆属性,需要继续交换子父节点。
以上操作就是 shiftUp。
removeRoot 示例
删除 root 元素,即删掉图中的数字 10。
删除一个元素,要将数组最后一个元素和被删的元素互换位置。
[ 10, 7, 2, 5, 1 ] ==> [ 1, 7, 2, 5, 10 ]
这时最后一个元素就是要返回的值,之后要将该元素从数组中彻底移除。
然后,shiftDown堆修复:
四 代码实现
// 最小堆的实现
class MinHeap {
constructor(initArr = []) {
this.heap = [];
this.buildHeap(initArr);
}
/**
* 交换父子节点位置
*/
swap(i1, i2) {
[this.heap[i1], this.heap[i2]] = [this.heap[i2], this.heap[i1]];
}
/**
* 父节点索引
*/
getParentIndex(i) {
return Math.floor((i - 1) / 2);
}
/*
* 左子节点索引
*/
getLeftIndex(i) {
return i * 2 + 1
}
/*
* 右子节点索引
*/
getRightIndex(i) {
return i * 2 + 2
}
/*
* 向上修复堆
*/
shiftUp(index) {
if (index === 0) {
return
}
const parentIndex = this.getParentIndex(index)
if (this.heap[parentIndex] > this.heap[index]) {
this.swap(parentIndex, index);
this.shiftUp(parentIndex)
}
}
/*
* 向下修复堆
*/
shiftDown(index) {
const leftIndex = this.getLeftIndex(index)
const rightIndex = this.getRightIndex(index)
if (this.heap[leftIndex] < this.heap[index]) {
this.swap(leftIndex, index)
this.shiftDown(leftIndex)
}
if (this.heap[rightIndex] < this.heap[index]) {
this.swap(rightIndex, index)
this.shiftDown(rightIndex)
}
}
/*
* 添加元素
*/
insert(value) {
this.heap.push(value)
this.shiftUp(this.heap.length - 1)
}
/*
* 删除 root 元素
*/
removeRoot() {
this.heap[0] = this.heap.pop() // 将最后一位pop并且赋值给堆顶
this.shiftDown(0)
}
buildHeap(arr) {
arr.forEach((item) => this.insert(item));
}
/*
* root 元素
*/
peek() {
return this.heap[0]
}
size() {
return this.heap.length
}
}
参考:raywenderlich 、 DaftJayee