小根堆构建的两种方式:上浮法与下沉法
在构建小根堆(Min-Heap)时,通常有两种常见的构建方式:
- 上浮建堆(逐个插入,上浮调整)
- 下沉建堆(Heapify 自底向上,下沉调整)
这两种方法在时间复杂度上有显著差异。
一、上浮建小根堆:逐个插入 + 上浮调整
1. 核心原理:
-
对于给定的无序数组,逐个插入到堆中。
-
每次插入新元素时,将其放在堆的最后一个位置(末尾),然后执行"上浮"调整:
- 将新元素与其父节点比较,如果新元素更小,则与父节点交换。
- 重复此过程,直到新元素达到正确的位置或成为堆顶。
2. 上浮过程示例:
- 对于每个新插入的元素,最多需要调整至堆顶。
- 在完全二叉树中,高度为 ⌊ log n ⌋ \lfloor \log n \rfloor ⌊logn⌋。
- 因此,单个元素上浮的时间复杂度为 O ( log n ) O(\log n) O(logn)。
3. 总体时间复杂度分析:
-
对于 n n n 个元素,逐个插入,每次上浮调整:
O ( 1 × log 1 ) + O ( 1 × log 2 ) + ⋯ + O ( 1 × log n ) O(1 \times \log 1) + O(1 \times \log 2) + \cdots + O(1 \times \log n) O(1×log1)+O(1×log2)+⋯+O(1×logn)
-
这相当于一个对数级数求和:
∑ i = 1 n log i ≈ O ( n log n ) \sum_{i=1}^{n} \log i \approx O(n \log n) i=1∑nlogi≈O(nlogn)
-
结论:上浮建堆的时间复杂度为 O ( n log n ) O(n \log n) O(nlogn)。
二、下沉建小根堆:Heapify 自底向上
1. 核心原理:
-
直接将无序数组视为一个完全二叉树。
-
从最后一个非叶子节点开始,逐个向前进行"下沉"调整:
-
对当前节点执行下沉:
- 与左右子节点中较小的一个交换,确保当前节点保持小根堆性质。
-
重复下沉,直到当前节点满足堆性质或成为叶节点。
-
2. 下沉过程示例:
-
对于每个非叶子节点进行下沉操作。
-
下沉操作的时间取决于树的高度:
-
对于完全二叉树,树高为 ⌊ log n ⌋ \lfloor \log n \rfloor ⌊logn⌋。
-
但下沉的层次是逐级减少的:
- n / 2 n/2 n/2 个节点(叶节点)不需要下沉。
- n / 4 n/4 n/4 个节点下沉 1 次。
- n / 8 n/8 n/8 个节点下沉 2 次。
- ......
-
3. 总体时间复杂度分析:
-
这是一种渐进减半的过程:
∑ i = 1 log n n 2 i × i \sum_{i=1}^{\log n} \frac{n}{2^i} \times i i=1∑logn2in×i
-
该求和公式的复杂度为 O ( n ) O(n) O(n),这是因为下沉过程中的递减效果:
T ( n ) = 2 × n 2 × 1 + 2 × n 4 × 2 + 2 × n 8 × 3 + ⋯ ≈ O ( n ) T(n) = 2 \times \frac{n}{2} \times 1 + 2 \times \frac{n}{4} \times 2 + 2 \times \frac{n}{8} \times 3 + \cdots \approx O(n) T(n)=2×2n×1+2×4n×2+2×8n×3+⋯≈O(n)
- 结论:下沉建堆的时间复杂度为 O ( n ) O(n) O(n)。
三、两种建堆方式的时间复杂度对比
建堆方式 | 上浮建堆(逐个插入) | 下沉建堆(Heapify) |
---|---|---|
原理 | 逐个插入,上浮调整 | 自底向上,下沉调整 |
调整过程 | 每次上浮至正确位置 | 从最后一个非叶节点下沉 |
复杂度 | O ( n log n ) O(n \log n) O(nlogn) | O ( n ) O(n) O(n) |
四、为什么下沉建堆更高效?
- 下沉建堆(Heapify)直接从中间节点开始调整,避免了对叶子节点的重复操作。
- 叶子节点天生满足堆的性质,因此可以忽略。
- 而上浮建堆在每次插入时都进行上浮,无法利用已经部分有序的特性,导致较高的复杂度。
五、实际应用中的选择:
- 上浮建堆: 常用于动态增量插入堆的情况(如优先队列逐个入队)。
- 下沉建堆: 常用于一次性构建堆(如堆排序)。