Skip to content

最小堆

在 Scheduler 中,使用最小堆的数据结构在对任务进行排序。

// 两个任务队列
var taskQueue: Array<Task> = [];
var timerQueue: Array<Task> = [];
push(timerQueue, newTask); // 像数组中推入一个任务
pop(timerQueue); // 从数组中弹出一个任务
timer = peek(timerQueue); // 从数组中获取第一个任务

二叉堆基本知识#

二叉树#

所谓二叉树,指的是一个父节点只能有1个或者2个子节点,例如下图:

image-20221230135103093

总之就是不能多余两个节点。

完全树#

所谓完全树,指的是一棵树再进行填写的时候,遵循的是“从左往右,从上往下”

例如下面的这些树,就都是完全树:

image-20221230135524942

再例如,下面的这些树,就不是完全树:

image-20221230135856627

完全树中的数值#

可以分为两大类:

最大堆示例:

image-20221230140218584

最小堆示例:

image-20221230140339328 image-20221230140935130

堆的实现#

堆一般来讲,可以使用数组来实现

image-20221230141555180

通过数组,我们可以非常方便的找到一个节点的所有亲属

子节点父节点
10
31
41
52
父节点左分支节点
01
13
25
父节点右分支节点
02
14
26

react 中对最小堆的应用#

在 react 中,最小堆对应的源码在 SchedulerMinHeap.js 文件中,总共有 6 个方法,其中向外暴露了 3 个方法

没有暴露的是:

所谓向上调整,就是指将一个元素和它的父节点做比较,如果比父节点小,那么就应该和父节点做交换,交换完了之后继续和上一层的父节点做比较,依此类推,直到该元素放置到了正确的位置

image-20221230142926067

向下调整,就刚好相反,元素往下走,先和左分支进行比较,如果比左分支小,那就交换。

peek#

取出堆顶的任务,堆顶一定是最小的

这个方法极其的简单,如下:

peek(timerQueue)
export function peek(heap) {
// 返回这个数组的第一个元素
return heap.length === 0 ? null : heap[0]
}

push#

向最小堆推入一个新任务,因为使用的是数组,所以在推入任务的时候,首先该任务是被推入到数组的最后一项,但是这个时候,涉及到一个调整,我们需要向上调整,把这个任务调整到合适的位置

push(timerQueue, newTask)
export function push(heap, node) {
const index = heap.length
// 推入到数组的最后一位
heap.push(node)
// 向上调整,调整到合适的位置
siftUp(heap, node, index)
}

pop#

pop 是从任务堆里面弹出第一个任务,也就是意味着该任务已经没有在队列里面了

pop(taskQueue)
export function pop(heap) {
if (heap.length === 0) {
return null
}
// 获取数组的第一个任务(一定是最小的)
const first = heap[0]
// 拿到数组的最后一个
const last = heap.pop()
if (last !== first) {
// 将最后一个任务放到第一个
heap[0] = last
// 接下来向下调整
siftDown(heap, last, 0)
}
return first
}

具体的调整示意图如下:

image-20221230144713347