JavaScript系列-堆
堆数据结构,是树中的一种,我们常用的堆数据是二叉堆;
二叉堆能够实现优先级队列
Linux内核中对各个进程的调度分配,定时器的实现原理,React filber的任务调度分配,都利用优先级队列的思维来解决问题。
二叉堆中,每一个结点都必须是可比较的,否则就无法判断优先级
二叉堆是一颗完全二叉树<即在树结构中,除了最后一层,其他结点都是完整的[每一个结点都拥有左右两个子节点]>:
分为两种类型:
- 最大堆,即任何父节点的键值,都大于等于任何一个子节点,最大堆的根节点为堆顶,堆顶元素是整个堆中的最大值。
- 最小堆,即任何父节点的键值,都小于等于任何一个子节点,最笑对的根节点为堆顶,堆顶元素是整个堆中的最小值。
对于二叉堆,只有几个常规操作:插入节点,删除结点,以及构建二叉堆
这几种操作都是基于堆的自我调整
-
插入节点
- 二叉堆的节点插入,只能是二叉堆结构中的最后一个位置,然后遵循最小堆原则,需要进行
上浮操作
- 二叉堆的节点插入,只能是二叉堆结构中的最后一个位置,然后遵循最小堆原则,需要进行
-
删除节点
- 二叉堆的删除节点,与插入节点正好相反,我们只会删除处于堆顶的元素,但是删除之后,二叉堆的结构就出现了混乱,为了维持完全二叉树的结构,我们把堆的最后一个节点补到原本堆顶的位置。
- 补充后我们发现,树结构不符合最小堆的特性,因此需要将新的堆顶元素与子元素比较,找到最小的子元素与其交换位置,这个行为我们称为
下沉。 - 直到完全符合最小堆的规则为止。
-
构建二叉堆
- 二叉堆虽然是一颗完全二叉树,但它的存储方式不是链式存储,而是顺序存储,二叉堆的所有节点都存储在数组中。
实现一个构建二叉堆的算法
class BinaryHead {
/**
*
* @param {*} compare 比较函数,构建最大堆还是最小堆
* @param {*} array 初始数组
*/
constructor(compare, array) {
this.compare = compare;
if (array) {
this.heap = array;
this.size = this.heap.length;
this.buildHead();
} else {
this.heap = [];
this.size = 0;
}
}
isEmpty() {
return this.size === 0;
}
// 通过子节点下标,找到父节点的下标
parentIndex(i) {
return Math.floor((i - 1) / 2);
}
leftIndex(i) {
return 2 * i + 1;
}
rightIndex(i) {
return 2 * i + 2;
}
swap(i, j) {
const temp = this.heap[i];
this.heap[i] = this.heap[j];
this.heap[j] = temp;
}
parent(i) {
return this.heap[this.parentIndexp(i)];
}
// 插入节点
push(node) {
if (this.size === 0) {
this.size++;
this.heap[0] = node;
return;
}
this.size++;
let i = this.size - 1;
this.heap[i] = node;
while (i !== 0 && this.compare(this.heap[i], this.parent[i])) {
this.swap(i, this.parentIndex(i));
i = this.parentIndex(i);
}
}
// 删除节点
pop() {
if (this.size <= 0) {
return null;
}
if (this.size === 1) {
let node = this.heap[this.size - 1];
this.size--;
this.heap.length = this.size;
return node;
}
// 将堆顶元素拿出来
const root = this.heap[0];
// 最下方的节点拿出来,放在堆顶
this.heap[0] = this.heap[this.size - 1];
this.size--;
this.heap.length = this.size;
this.heapify(0);
return root;
}
// 删除或者构建二叉树,都需要重新排序
heapify(i) {
const l = this.leftIndex(i);
const r = this.rightIndex(i);
const [pv, lv, rv] = [this.heap[i], this.heap[l], this.heap[r]];
let small = i;
if (l < this.size && this.compare(lv, pv)) {
small = l;
}
if (r < this.size && this.compare(rv, this.heap[small])) {
console.log(2);
small = r;
}
if (small !== i) {
this.swap(i, small);
this.heapify(small);
}
}
// 构建堆,从最后一个非叶子节点开始遍历构建
// 非叶子节点: 拥有子树,度不为0的节点
buildHead() {
for (let i = this.parentIndex(this.size - 1); i >= 0; i--) {
// 传递父节点下标
this.heapify(i);
}
}
}
const compare = (a, b) => {
return a < b;
};
let h1 = new BinaryHead(compare);
h1.push(1);
h1.push(2);
h1.push(3);
h1.push(4);
h1.push(5);
console.log(h1.heap);
h1.pop();
console.log(h1.heap);
var array = [150, 80, 40, 30, 10, 70, 110, 100, 20, 90, 60, 50, 120, 140, 130];
const h2 = new BinaryHead(compare, array);
console.log(h2.heap);
\