refactor: Replace 结点 with 节点 (#452)

* Replace 结点 with 节点
Update the footnotes in the figures

* Update mindmap

* Reduce the size of the mindmap.png
This commit is contained in:
Yudong Jin
2023-04-09 04:32:17 +08:00
committed by GitHub
parent 3f4e32b2b0
commit 1c8b7ef559
395 changed files with 2056 additions and 2056 deletions
Binary file not shown.

Before

Width:  |  Height:  |  Size: 58 KiB

After

Width:  |  Height:  |  Size: 57 KiB

+14 -14
View File
@@ -1,18 +1,18 @@
# 建堆操作 *
如果我们想要根据输入列表生成一个堆,这样的操作被称为「建堆」。
如果我们想要根据输入列表生成一个堆,这个过程被称为「建堆」。
## 两种建堆方法
### 借助入堆方法实现
最直接地,考虑借助元素入堆」方法,先建立一个空堆,**再将列表元素依次入堆即可**
最直接的方法是借助元素入堆操作”实现,首先创建一个空堆,然后将列表元素依次添加到堆中
设元素数量为 $n$ ,则最后一个元素入堆的时间复杂度为 $O(\log n)$ 在依次入堆时,堆的平均长度为 $\frac{n}{2}$ ,因此该方法的总体时间复杂度为 $O(n \log n)$ 。
设元素数量为 $n$ ,则最后一个元素入堆的时间复杂度为 $O(\log n)$ 在依次添加元素时,堆的平均长度为 $\frac{n}{2}$ ,因此该方法的总体时间复杂度为 $O(n \log n)$ 。
### 基于堆化操作实现
有趣的是,存在一种更高效的建堆方法,时间复杂度可以达到 $O(n)$ 。我们先将列表所有元素原封不动添加进堆**然后迭代地对各个点执行从顶至底堆化**。当然,**无需对叶点执行堆化**,因为没有子点。
有趣的是,存在一种更高效的建堆方法,时间复杂度仅为 $O(n)$ 。我们先将列表所有元素原封不动添加到堆中**然后迭代地对各个点执行从顶至底堆化**。当然,**我们不需要对叶点执行堆化操作**,因为它们没有子点。
=== "Java"
@@ -76,24 +76,24 @@
## 复杂度分析
第二种建堆方法的时间复杂度为什么是 $O(n)$ ?我们来展开推算一下。
为什么第二种建堆方法的时间复杂度是 $O(n)$ ?我们来展开推算一下。
- 完全二叉树中,设点总数为 $n$ ,则叶点数量为 $(n + 1) / 2$ ,其中 $/$ 为向下整除。因此在排除叶点后,需要堆化点数量为 $(n - 1)/2$ 为 $O(n)$
- 从顶至底堆化中,每个点最多堆化至叶结点,因此最大迭代次数为二叉树高度 $O(\log n)$
- 完全二叉树中,设点总数为 $n$ ,则叶点数量为 $(n + 1) / 2$ ,其中 $/$ 为向下整除。因此在排除叶点后,需要堆化的节点数量为 $(n - 1)/2$ 复杂度为 $O(n)$
- 从顶至底堆化的过程中,每个点最多堆化到叶节点,因此最大迭代次数为二叉树高度 $O(\log n)$
将上述两者相乘,可得时间复杂度为 $O(n \log n)$ 。这个估算结果不准确,因为我们没有考虑到 **二叉树底层结点远多于顶层结点** 的性质
将上述两者相乘,可得到建堆过程的时间复杂度为 $O(n \log n)$。**然而,这个估算结果不准确,因为我们没有考虑到二叉树底层节点数量远多于顶层节点的特性**
下面我们来展开计算。为了减小计算难度,我们假设树是一个完美二叉树,该假设不会影响计算结果的正确性。设二叉树(即堆)点数量为 $n$ ,树高度为 $h$ 。上文提到,**点堆化最大迭代次数等于该点到叶点的距离,而正是“点高度”**。
接下来我们来进行更为详细的计算。为了减小计算难度,我们假设树是一个完美二叉树,该假设不会影响计算结果的正确性。设二叉树(即堆)点数量为 $n$ ,树高度为 $h$ 。上文提到,**点堆化最大迭代次数等于该点到叶点的距离,而该距离正是“点高度”**。
![完美二叉树的各层点数量](build_heap.assets/heapify_operations_count.png)
![完美二叉树的各层点数量](build_heap.assets/heapify_operations_count.png)
因此,我们将各层的“点数量 $\times$ 点高度”求和,即可得到 **所有点的堆化迭代次数总和**
因此,我们可以将各层的“点数量 $\times$ 点高度”求和,**从而得到所有点的堆化迭代次数总和**。
$$
T(h) = 2^0h + 2^1(h-1) + 2^2(h-2) + \cdots + 2^{(h-1)}\times1
$$
化简上式需要借助中学的数列知识,先对 $T(h)$ 乘以 $2$
化简上式需要借助中学的数列知识,先对 $T(h)$ 乘以 $2$ ,得
$$
\begin{aligned}
@@ -108,7 +108,7 @@ $$
2T(h) - T(h) = T(h) = -2^0h + 2^1 + 2^2 + \cdots + 2^{h-1} + 2^h
$$
观察上式,$T(h)$ 是一个等比数列,可直接使用求和公式,得到时间复杂度为
观察上式,发现 $T(h)$ 是一个等比数列,可直接使用求和公式,得到时间复杂度为
$$
\begin{aligned}
@@ -118,4 +118,4 @@ T(h) & = 2 \frac{1 - 2^h}{1 - 2} - h \newline
\end{aligned}
$$
进一步地,高度为 $h$ 的完美二叉树的点数量为 $n = 2^{h+1} - 1$ ,易得复杂度为 $O(2^h) = O(n)$ 。以上推算表明,**输入列表并建堆的时间复杂度为 $O(n)$ ,非常高效**。
进一步地,高度为 $h$ 的完美二叉树的点数量为 $n = 2^{h+1} - 1$ ,易得复杂度为 $O(2^h) = O(n)$ 。以上推算表明,**输入列表并建堆的时间复杂度为 $O(n)$ ,非常高效**。
Binary file not shown.

Before

Width:  |  Height:  |  Size: 68 KiB

After

Width:  |  Height:  |  Size: 68 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 89 KiB

After

Width:  |  Height:  |  Size: 88 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 66 KiB

After

Width:  |  Height:  |  Size: 65 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 69 KiB

After

Width:  |  Height:  |  Size: 68 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 81 KiB

After

Width:  |  Height:  |  Size: 80 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 89 KiB

After

Width:  |  Height:  |  Size: 88 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 84 KiB

After

Width:  |  Height:  |  Size: 84 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 93 KiB

After

Width:  |  Height:  |  Size: 92 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 86 KiB

After

Width:  |  Height:  |  Size: 86 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 95 KiB

After

Width:  |  Height:  |  Size: 94 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 68 KiB

After

Width:  |  Height:  |  Size: 67 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 64 KiB

After

Width:  |  Height:  |  Size: 64 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 80 KiB

After

Width:  |  Height:  |  Size: 79 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 82 KiB

After

Width:  |  Height:  |  Size: 80 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 83 KiB

After

Width:  |  Height:  |  Size: 81 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 84 KiB

After

Width:  |  Height:  |  Size: 83 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 77 KiB

After

Width:  |  Height:  |  Size: 76 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 109 KiB

After

Width:  |  Height:  |  Size: 108 KiB

+15 -15
View File
@@ -2,16 +2,16 @@
「堆 Heap」是一棵限定条件下的「完全二叉树」。根据成立条件,堆主要分为两种类型:
- 「大顶堆 Max Heap」,任意点的值 $\geq$ 其子点的值;
- 「小顶堆 Min Heap」,任意点的值 $\leq$ 其子点的值;
- 「大顶堆 Max Heap」,任意点的值 $\geq$ 其子点的值;
- 「小顶堆 Min Heap」,任意点的值 $\leq$ 其子点的值;
![小顶堆与大顶堆](heap.assets/min_heap_and_max_heap.png)
## 堆术语与性质
- 由于堆是完全二叉树,因此最底层点靠左填充,其它层点皆被填满。
- 二叉树中的根点对应「堆顶」,底层最靠右点对应「堆底」。
- 对于大顶堆 / 小顶堆,其堆顶元素(即根点)的值最大 / 最小。
- 由于堆是完全二叉树,因此最底层点靠左填充,其它层点皆被填满。
- 二叉树中的根点对应「堆顶」,底层最靠右点对应「堆底」。
- 对于大顶堆 / 小顶堆,其堆顶元素(即根点)的值最大 / 最小。
## 堆常用操作
@@ -308,9 +308,9 @@
在二叉树章节我们学过,「完全二叉树」非常适合使用「数组」来表示,而堆恰好是一棵完全二叉树,**因而我们采用「数组」来存储「堆」**。
**二叉树指针**。使用数组表示二叉树时,元素代表点值,索引代表点在二叉树中的位置,**而点指针通过索引映射公式来实现**。
**二叉树指针**。使用数组表示二叉树时,元素代表点值,索引代表点在二叉树中的位置,**而点指针通过索引映射公式来实现**。
具体地,给定索引 $i$ ,那么其左子点索引为 $2i + 1$ 、右子点索引为 $2i + 2$ 、父点索引为 $(i - 1) / 2$ (向下整除)。当索引越界时,代表空点或点不存在。
具体地,给定索引 $i$ ,那么其左子点索引为 $2i + 1$ 、右子点索引为 $2i + 2$ 、父点索引为 $(i - 1) / 2$ (向下整除)。当索引越界时,代表空点或点不存在。
![堆的表示与存储](heap.assets/representation_of_heap.png)
@@ -418,7 +418,7 @@
### 访问堆顶元素
堆顶元素是二叉树的根点,即列表首元素。
堆顶元素是二叉树的根点,即列表首元素。
=== "Java"
@@ -482,9 +482,9 @@
### 元素入堆
给定元素 `val` ,我们先将其添加到堆底。添加后,由于 `val` 可能大于堆中其它元素,此时堆的成立条件可能已经被破坏,**因此需要修复从插入点到根点这条路径上的各个点**,该操作被称为「堆化 Heapify」。
给定元素 `val` ,我们先将其添加到堆底。添加后,由于 `val` 可能大于堆中其它元素,此时堆的成立条件可能已经被破坏,**因此需要修复从插入点到根点这条路径上的各个点**,该操作被称为「堆化 Heapify」。
考虑从入堆点开始,**从底至顶执行堆化**。具体地,比较插入点与其父点的值,若插入点更大则将它们交换;并循环以上操作,从底至顶地修复堆中的各个点;直至越过根点时结束,或当遇到无需交换的点时提前结束。
考虑从入堆点开始,**从底至顶执行堆化**。具体地,比较插入点与其父点的值,若插入点更大则将它们交换;并循环以上操作,从底至顶地修复堆中的各个点;直至越过根点时结束,或当遇到无需交换的点时提前结束。
=== "<1>"
![元素入堆步骤](heap.assets/heap_push_step1.png)
@@ -504,7 +504,7 @@
=== "<6>"
![heap_push_step6](heap.assets/heap_push_step6.png)
点总数为 $n$ ,则树的高度为 $O(\log n)$ ,易得堆化操作的循环轮数最多为 $O(\log n)$ ,**因而元素入堆操作的时间复杂度为 $O(\log n)$** 。
点总数为 $n$ ,则树的高度为 $O(\log n)$ ,易得堆化操作的循环轮数最多为 $O(\log n)$ ,**因而元素入堆操作的时间复杂度为 $O(\log n)$** 。
=== "Java"
@@ -588,13 +588,13 @@
### 堆顶元素出堆
堆顶元素是二叉树根点,即列表首元素,如果我们直接将首元素从列表中删除,则二叉树中所有点都会随之发生移位(索引发生变化),这样后续使用堆化修复就很麻烦了。为了尽量减少元素索引变动,采取以下操作步骤:
堆顶元素是二叉树根点,即列表首元素,如果我们直接将首元素从列表中删除,则二叉树中所有点都会随之发生移位(索引发生变化),这样后续使用堆化修复就很麻烦了。为了尽量减少元素索引变动,采取以下操作步骤:
1. 交换堆顶元素与堆底元素(即交换根点与最右叶点);
1. 交换堆顶元素与堆底元素(即交换根点与最右叶点);
2. 交换完成后,将堆底从列表中删除(注意,因为已经交换,实际上删除的是原来的堆顶元素);
3. 从根点开始,**从顶至底执行堆化**
3. 从根点开始,**从顶至底执行堆化**
顾名思义,**从顶至底堆化的操作方向与从底至顶堆化相反**,我们比较根点的值与其两个子点的值,将最大的子点与根点执行交换,并循环以上操作,直到越过叶点时结束,或当遇到无需交换的点时提前结束。
顾名思义,**从顶至底堆化的操作方向与从底至顶堆化相反**,我们比较根点的值与其两个子点的值,将最大的子点与根点执行交换,并循环以上操作,直到越过叶点时结束,或当遇到无需交换的点时提前结束。
=== "<1>"
![堆顶元素出堆步骤](heap.assets/heap_pop_step1.png)