2024-08-27 10:10:05 +08:00

184 lines
5.2 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# 最小堆
在 Scheduler 中,使用最小堆的数据结构在对任务进行排序。
```js
// 两个任务队列
var taskQueue: Array<Task> = [];
var timerQueue: Array<Task> = [];
push(timerQueue, newTask); // 像数组中推入一个任务
pop(timerQueue); // 从数组中弹出一个任务
timer = peek(timerQueue); // 从数组中获取第一个任务
```
## 二叉堆基本知识
### 二叉树
所谓二叉树指的是一个父节点只能有1个或者2个子节点例如下图
<img src="https://xiejie-typora.oss-cn-chengdu.aliyuncs.com/2022-12-30-055103.png" alt="image-20221230135103093" style="zoom:50%;" />
总之就是不能多余两个节点。
### 完全树
所谓完全树,指的是一棵树再进行填写的时候,遵循的是“从左往右,从上往下”
例如下面的这些树,就都是完全树:
![image-20221230135524942](https://xiejie-typora.oss-cn-chengdu.aliyuncs.com/2022-12-30-055525.png)
再例如,下面的这些树,就不是完全树:
<img src="https://xiejie-typora.oss-cn-chengdu.aliyuncs.com/2022-12-30-055856.png" alt="image-20221230135856627" style="zoom:50%;" />
### 完全树中的数值
可以分为两大类:
- 最大堆:父节点的数值大于或者等于所有的子节点
- 最小堆:刚好相反,父节点的数值小于或者等于所有的子节点
最大堆示例:
<img src="https://xiejie-typora.oss-cn-chengdu.aliyuncs.com/2022-12-30-060219.png" alt="image-20221230140218584" style="zoom:50%;" />
最小堆示例:
<img src="https://xiejie-typora.oss-cn-chengdu.aliyuncs.com/2022-12-30-060339.png" alt="image-20221230140339328" style="zoom:50%;" />
- 无论是最大堆还是最小堆,第一个节点一定是这个堆中最大的或者最小的
- 每一层并非是按照一定顺序来排列的比如下面的例子6可以在左分支3可以在右分支
<img src="https://xiejie-typora.oss-cn-chengdu.aliyuncs.com/2022-12-30-060935.png" alt="image-20221230140935130" style="zoom:50%;" />
- 每一层的所有元素并非一定比下一层(非自己的子节点)大或者小
### 堆的实现
堆一般来讲,可以使用数组来实现
<img src="https://xiejie-typora.oss-cn-chengdu.aliyuncs.com/2022-12-30-061555.png" alt="image-20221230141555180" style="zoom:50%;" />
通过数组,我们可以非常方便的找到一个节点的所有亲属
- 父节点Math.floor((当前节点的下标 - 1) / 2)
| 子节点 | 父节点 |
| ------ | ------ |
| 1 | 0 |
| 3 | 1 |
| 4 | 1 |
| 5 | 2 |
- 左分支节点:当前节点下标 * 2 + 1
| 父节点 | 左分支节点 |
| ------ | ---------- |
| 0 | 1 |
| 1 | 3 |
| 2 | 5 |
- 右分支节点:当前节点下标 * 2 + 2
| 父节点 | 右分支节点 |
| ------ | ---------- |
| 0 | 2 |
| 1 | 4 |
| 2 | 6 |
## react 中对最小堆的应用
在 react 中,最小堆对应的源码在 *SchedulerMinHeap.js* 文件中,总共有 6 个方法,其中向外暴露了 3 个方法
- push向最小堆推入一个元素
- pop弹出一个
- peek取出第一个
没有暴露的是:
- siftUp向上调整
- siftDown向下调整
- compare这是一个辅助方法就是两个元素做比较的
所谓向上调整,就是指将一个元素和它的父节点做比较,如果比父节点小,那么就应该和父节点做交换,交换完了之后继续和上一层的父节点做比较,依此类推,直到该元素放置到了正确的位置
<img src="https://xiejie-typora.oss-cn-chengdu.aliyuncs.com/2022-12-30-062926.png" alt="image-20221230142926067" style="zoom:50%;" />
向下调整,就刚好相反,元素往下走,先和左分支进行比较,如果比左分支小,那就交换。
### peek
取出堆顶的任务,堆顶一定是最小的
这个方法极其的简单,如下:
```js
peek(timerQueue);
export function peek(heap) {
// 返回这个数组的第一个元素
return heap.length === 0 ? null : heap[0];
}
```
### push
向最小堆推入一个新任务,因为使用的是数组,所以在推入任务的时候,首先该任务是被推入到数组的最后一项,但是这个时候,涉及到一个调整,我们需要向上调整,把这个任务调整到合适的位置
```js
push(timerQueue, newTask);
export function push(heap, node) {
const index = heap.length;
// 推入到数组的最后一位
heap.push(node);
// 向上调整,调整到合适的位置
siftUp(heap, node, index);
}
```
### pop
pop 是从任务堆里面弹出第一个任务,也就是意味着该任务已经没有在队列里面了
```js
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;
}
```
具体的调整示意图如下:
<img src="https://xiejie-typora.oss-cn-chengdu.aliyuncs.com/2022-12-30-064713.png" alt="image-20221230144713347" style="zoom:50%;" />