Number the H1 and H2 headings.

This commit is contained in:
Yudong Jin
2023-01-31 03:37:50 +08:00
parent dbbc1adc4f
commit eb0afc98ec
44 changed files with 263 additions and 257 deletions

View File

@ -2,22 +2,24 @@
comments: true
---
# 堆
# 8.1. # 1.1
「堆 Heap」是一颗限定条件下的「完全二叉树」。根据成立条件堆主要分为两种类型
- 「大顶堆 Max Heap」任意结点的值 $\geq$ 其子结点的值;
- 「小顶堆 Min Heap」任意结点的值 $\leq$ 其子结点的值;
![min_heap_and_max_heap](heap.assets/min_heap_and_max_heap.png)
<figure markdown>
![min_heap_and_max_heap](heap.assets/min_heap_and_max_heap.png){ width="600" }
</figure>
## 堆术语与性质
## 8.1.1. 堆术语与性质
- 由于堆是完全二叉树,因此最底层结点靠左填充,其它层结点皆被填满。
- 二叉树中的根结点对应「堆顶」,底层最靠右结点对应「堆底」。
- 对于大顶堆 / 小顶堆,其堆顶元素(即根结点)的值最大 / 最小。
## 堆常用操作
## 8.1.2. 堆常用操作
值得说明的是,多数编程语言提供的是「优先队列 Priority Queue」其是一种抽象数据结构**定义为具有出队优先级的队列**。
@ -203,7 +205,7 @@ comments: true
// Swift 未提供内置 heap 类
```
## 堆的实现
## 8.1.3. 堆的实现
下文实现的是「大顶堆」,若想转换为「小顶堆」,将所有大小逻辑判断取逆(例如将 $\geq$ 替换为 $\leq$ )即可,有兴趣的同学可自行实现。
@ -215,7 +217,9 @@ comments: true
具体地,给定索引 $i$ ,那么其左子结点索引为 $2i + 1$ 、右子结点索引为 $2i + 2$ 、父结点索引为 $(i - 1) / 2$ (向下整除)。当索引越界时,代表空结点或结点不存在。
![representation_of_heap](heap.assets/representation_of_heap.png)
<figure markdown>
![representation_of_heap](heap.assets/representation_of_heap.png){ width="600" }
</figure>
我们将索引映射公式封装成函数,以便后续使用。
@ -414,22 +418,24 @@ comments: true
考虑从入堆结点开始,**从底至顶执行堆化**。具体地,比较插入结点与其父结点的值,若插入结点更大则将它们交换;并循环以上操作,从底至顶地修复堆中的各个结点;直至越过根结点时结束,或当遇到无需交换的结点时提前结束。
=== "Step 1"
![heap_push_step1](heap.assets/heap_push_step1.png)
<figure markdown>
![heap_push_step1](heap.assets/heap_push_step1.png){ width="600" }
</figure>
=== "Step 2"
![heap_push_step2](heap.assets/heap_push_step2.png)
![heap_push_step2](heap.assets/heap_push_step2.png){ width="600" }
=== "Step 3"
![heap_push_step3](heap.assets/heap_push_step3.png)
![heap_push_step3](heap.assets/heap_push_step3.png){ width="600" }
=== "Step 4"
![heap_push_step4](heap.assets/heap_push_step4.png)
![heap_push_step4](heap.assets/heap_push_step4.png){ width="600" }
=== "Step 5"
![heap_push_step5](heap.assets/heap_push_step5.png)
![heap_push_step5](heap.assets/heap_push_step5.png){ width="600" }
=== "Step 6"
![heap_push_step6](heap.assets/heap_push_step6.png)
![heap_push_step6](heap.assets/heap_push_step6.png){ width="600" }
设结点总数为 $n$ ,则树的高度为 $O(\log n)$ ,易得堆化操作的循环轮数最多为 $O(\log n)$ **因而元素入堆操作的时间复杂度为 $O(\log n)$** 。
@ -564,34 +570,34 @@ comments: true
顾名思义,**从顶至底堆化的操作方向与从底至顶堆化相反**,我们比较根结点的值与其两个子结点的值,将最大的子结点与根结点执行交换,并循环以上操作,直到越过叶结点时结束,或当遇到无需交换的结点时提前结束。
=== "Step 1"
![heap_poll_step1](heap.assets/heap_poll_step1.png)
![heap_poll_step1](heap.assets/heap_poll_step1.png){ width="600" }
=== "Step 2"
![heap_poll_step2](heap.assets/heap_poll_step2.png)
![heap_poll_step2](heap.assets/heap_poll_step2.png){ width="600" }
=== "Step 3"
![heap_poll_step3](heap.assets/heap_poll_step3.png)
![heap_poll_step3](heap.assets/heap_poll_step3.png){ width="600" }
=== "Step 4"
![heap_poll_step4](heap.assets/heap_poll_step4.png)
![heap_poll_step4](heap.assets/heap_poll_step4.png){ width="600" }
=== "Step 5"
![heap_poll_step5](heap.assets/heap_poll_step5.png)
![heap_poll_step5](heap.assets/heap_poll_step5.png){ width="600" }
=== "Step 6"
![heap_poll_step6](heap.assets/heap_poll_step6.png)
![heap_poll_step6](heap.assets/heap_poll_step6.png){ width="600" }
=== "Step 7"
![heap_poll_step7](heap.assets/heap_poll_step7.png)
![heap_poll_step7](heap.assets/heap_poll_step7.png){ width="600" }
=== "Step 8"
![heap_poll_step8](heap.assets/heap_poll_step8.png)
![heap_poll_step8](heap.assets/heap_poll_step8.png){ width="600" }
=== "Step 9"
![heap_poll_step9](heap.assets/heap_poll_step9.png)
![heap_poll_step9](heap.assets/heap_poll_step9.png){ width="600" }
=== "Step 10"
![heap_poll_step10](heap.assets/heap_poll_step10.png)
![heap_poll_step10](heap.assets/heap_poll_step10.png){ width="600" }
与元素入堆操作类似,**堆顶元素出堆操作的时间复杂度为 $O(\log n)$** 。
@ -856,7 +862,7 @@ $$
T(h) = 2^0h + 2^1(h-1) + 2^2(h-2) + \cdots + 2^{(h-1)}\times1
$$
![heapify_count](heap.assets/heapify_count.png)
![heapify_count](heap.assets/heapify_count.png){ width="600" }
化简上式需要借助中学的数列知识,先对 $T(h)$ 乘以 $2$ ,易得
@ -885,7 +891,7 @@ $$
进一步地,高度为 $h$ 的完美二叉树的结点数量为 $n = 2^{h+1} - 1$ ,易得复杂度为 $O(2^h) = O(n)$。以上推算表明,**输入列表并建堆的时间复杂度为 $O(n)$ ,非常高效**。
## 堆常见应用
## 8.1.4. 堆常见应用
- **优先队列**。堆常作为实现优先队列的首选数据结构,入队和出队操作时间复杂度为 $O(\log n)$ ,建队操作为 $O(n)$ ,皆非常高效。
- **堆排序**。给定一组数据,我们使用其建堆,并依次全部弹出,则可以得到有序的序列。当然,堆排序一般无需弹出元素,仅需每轮将堆顶元素交换至数组尾部并减小堆的长度即可。