React 任务调度器使用的数据结构是最小堆,本文主要介绍最小堆的三种操作。
- peek-获取堆顶元素
- push-为堆添加元素
- pop-删除堆顶元素
获取堆顶元素
这个比较简单,不解释了。
export function peek(heap) {
return heap.length === 0 ? null : heap[0];
}
为堆添加元素
添加元素到最后,从下往上比较,根节点比子节点大就交换,一直比较到堆顶。
// 给堆添加元素
export function push(heap, node) {
// 1. 把node放到堆的最后
const index = heap.length;
heap.push(node);
// 2. 调整最小堆,从下往上堆化
siftUp(heap, node, index);
}
// 从下往上堆化
function siftUp(heap, node, i) {
let index = i;
while (index > 0) {
const parentIndex = (index - 1) >>> 1; // 除以 2
const parent = heap[parentIndex];
if (compare(parent, node) > 0) {
// node子节点更小,和根节点交换
heap[parentIndex] = node;
heap[index] = parent;
index = parentIndex;
} else {
return;
}
}
}
function compare(a, b) {
const diff = a.sortIndex - b.sortIndex;
return diff !== 0 ? diff : a.id - b.id;
}
删除堆顶元素
将最后一个元素放置堆顶,从上往下比较根节点和两个子节点,保证根节点最小,一直比较到最后一个非叶子节点。
// 删除堆顶元素
export function pop(heap) {
if (heap.length === 0) {
return null;
}
const first = heap[0];
const last = heap.pop()!;
if (first !== last) {
// 证明heap中有2个或者更多个元素
heap[0] = last;
siftDown(heap, last, 0);
}
return first;
}
function siftDown(heap, node, i) {
let index = i;
const length = heap.length;
const halfLength = length >>> 1; // 除以 2
// 最后一个非叶子节点的索引是 halfLength - 1,因为叶子节点没有子节点,所以不需要进行下沉操作
while (index < halfLength) {
const leftIndex = (index + 1) * 2 - 1;
const left = heap[leftIndex];
const rightIndex = leftIndex + 1;
const right = heap[rightIndex]; // right不一定存在,等下还要判断是否存在
if (compare(left, node) < 0) {
// left<node
if (rightIndex < length && compare(right, left) < 0) {
// right存在,且right<left
heap[index] = right;
heap[rightIndex] = node;
index = rightIndex;
} else {
// left更小或者right不存在
heap[index] = left;
heap[leftIndex] = node;
index = leftIndex;
}
} else if (rightIndex < length && compare(right, node) < 0) {
// left>=node && right<node
heap[index] = right;
heap[rightIndex] = node;
index = rightIndex;
} else {
// 根节点最小,不需要调整
return;
}
}
}
代码中都有详细的注释,有疑问评论区留言。