基于自底向上 (Sift-Down) 策略的建堆时间复杂度推导

基于自底向上 (Sift-Down) 策略的建堆时间复杂度推导

目标: 运用代数推导与错位相减法,证明将 n n n 个无序节点构建为二叉堆的最坏情况时间复杂度为 O ( n ) O(n) O(n)

一、确定单层节点数、单层最大下沉步数

此处我们用满二叉树推导:

  • n n n:代表这颗满二叉树的总节点个数
  • h h h:代表这棵树的总高度(最大层数)
  • i i i:代表我们当前正在观察的第 i i i 层(取值范围是 1 到 h h h
    在这里插入图片描述

1. 第 i i i 层的节点总数

(说明:数学来源解析)

  • 逻辑推导: 在满二叉树中,每层节点数是上一层的 2 倍(等比关系)
  • 通用结论: 位于第 i i i 层的节点总数为 2 i − 1 2^{i-1} 2i1 个。
  • 规律列举:
    • 第 1 层节点数: 2 0 = 1 2^0 = 1 20=1
    • 第 2 层节点数: 2 1 = 2 2^1 = 2 21=2
    • h − 1 h-1 h1 层节点数: 2 h − 2 2^{h-2} 2h2 个(这是建堆真正的起始层)
    • h h h 层节点数: 2 h − 1 2^{h-1} 2h1 个(叶子节点(第 h h h 层)无子节点,天然满足堆性质。)

2. 第 i i i 层节点的最坏下沉步数

  • 推导逻辑: 节点最坏情况需要一直向下交换,直到叶子节点(第 h h h 层)为止
  • 通用结论: 位于第 i i i 层的节点,最多向下调整 h − i h-i hi
  • 规律列举:
    • 第 1 层节点:下方有 h − 1 h-1 h1 层,最多向下移动 h − 1 h-1 h1
    • 第 2 层节点:下方有 h − 2 h-2 h2 层,最多向下移动 h − 2 h-2 h2
    • h − 2 h-2 h2 层节点:下方有 2 层,最多向下移动 2 次
    • h − 1 h-1 h1 层节点:下方有 1 层,最多向下移动 1 次
    • h h h 层节点:下方有 0 层,最多向下移动 0 次

二、构建总步数函数 S ( h ) S(h) S(h),将每层节点数、最坏下沉步数进行乘积求和

1. 明确定义

我们推导的是基于树高 h h h 的总步数,必须严格记为 S ( h ) S(h) S(h)

2. 组装求和项

因为:

  • 总步数 = ∑ \sum (单层节点数 × \times × 单层节点最坏调整步数)
  • 第 1 层总移动步数: 2 0 × ( h − 1 ) 2^0 \times (h - 1) 20×(h1)
  • 第 2 层总移动步数: 2 1 × ( h − 2 ) 2^1 \times (h - 2) 21×(h2)
  • h − 2 h-2 h2 层总移动步数: 2 h − 3 × 2 2^{h-3} \times 2 2h3×2
  • h − 1 h-1 h1 层总移动步数: 2 h − 2 × 1 2^{h-2} \times 1 2h2×1

3. 得出等式 A

所以:

S ( h ) = 2 0 ( h − 1 ) + 2 1 ( h − 2 ) + 2 2 ( h − 3 ) + ⋯ + 2 h − 3 ⋅ 2 + 2 h − 2 ⋅ 1 —— (等式 A) S(h) = 2^0(h-1) + 2^1(h-2) + 2^2(h-3) + \dots + 2^{h-3} \cdot 2 + 2^{h-2} \cdot 1 \quad \text{—— (等式 A)} S(h)=20(h1)+21(h2)+22(h3)++2h32+2h21—— (等式 A)

4. 解释

观察等式 A 的每一项:

左侧因式 ( 2 0 , 2 1 , 2 2 , … 2^0, 2^1, 2^2, \dots 20,21,22,) 构成等比数列;右侧因式 ( h − 1 , h − 2 , h − 3 , … h-1, h-2, h-3, \dots h1,h2,h3,) 构成等差数列。

结论: S ( h ) S(h) S(h) 是一个差比数列求和模型。

三、运用错位相减法求解 S ( h ) S(h) S(h)

在这里插入图片描述

1. 等式 A

S ( h ) = 2 0 ( h − 1 ) + 2 1 ( h − 2 ) + 2 2 ( h − 3 ) + ⋯ + 2 h − 3 ⋅ 2 + 2 h − 2 ⋅ 1 —— (等式 A) S(h) = 2^0(h-1) + 2^1(h-2) + 2^2(h-3) + \dots + 2^{h-3} \cdot 2 + 2^{h-2} \cdot 1 \quad \text{—— (等式 A)} S(h)=20(h1)+21(h2)+22(h3)++2h32+2h21—— (等式 A)

2. 等式 B

为了消除中间的复杂项,把等式两边同乘等比数列的公比(公比 q = 2 q=2 q=2),计算出等式 B:
2 S ( h ) = 2 1 ( h − 1 ) + 2 2 ( h − 2 ) + 2 3 ( h − 3 ) + ⋯ + 2 h − 2 ⋅ 2 + 2 h − 1 ⋅ 1 —— (等式 B) 2S(h) = 2^1(h-1) + 2^2(h-2) + 2^3(h-3) + \dots + 2^{h-2} \cdot 2 + 2^{h-1} \cdot 1 \quad \text{—— (等式 B)} 2S(h)=21(h1)+22(h2)+23(h3)++2h22+2h11—— (等式 B)

3. 两个式子错位相减

为了化简等式,将 (等式 B - 等式 A),把对齐的中间项相减去除。
即:
S ( h ) = 2 1 + 2 2 + 2 3 + ⋯ + 2 h − 2 + 2 h − 1 − ( h − 1 ) S(h) = 2^1 + 2^2 + 2^3 + \dots + 2^{h-2} + 2^{h-1} - (h-1) S(h)=21+22+23++2h2+2h1(h1)

4. 化简

将等式尾部的 − ( h − 1 ) -(h-1) (h1) 拆解为 − h + 1 -h + 1 h+1
S ( h ) = 2 1 + 2 2 + 2 3 + ⋯ + 2 h − 1 − h + 1 S(h) = 2^1 + 2^2 + 2^3 + \dots + 2^{h-1} - h + 1 S(h)=21+22+23++2h1h+1

为了凑齐完整的等比数列,将常数 1 1 1 转化为 2 0 2^0 20,并将其移至数列首部:
S ( h ) = [ 2 0 + 2 1 + 2 2 + 2 3 + ⋯ + 2 h − 1 ] − h S(h) = [2^0 + 2^1 + 2^2 + 2^3 + \dots + 2^{h-1}] - h S(h)=[20+21+22+23++2h1]h

在这里插入图片描述

此时,上面等式中括号内的部分构成了标准的等比数列。
根据等比数列求和公式计算:

  • 首项 a 1 = 1 a_1 = 1 a1=1
  • 公比 q = 2 q = 2 q=2
  • 项数 k = h k = h k=h (从 2 0 2^0 20 2 h − 1 2^{h-1} 2h1,共有 h h h 项)

代入公式 S k = a 1 ( q k − 1 ) q − 1 S_k = \frac{a_1(q^k - 1)}{q - 1} Sk=q1a1(qk1) 计算得出:
S 等比 = 1 ⋅ ( 2 h − 1 ) 2 − 1 = 2 h − 1 S_{等比} = \frac{1 \cdot (2^h - 1)}{2 - 1} = 2^h - 1 S等比=211(2h1)=2h1

将该结果代回原式,得出最终的解:等比数列求和公式

在这里插入图片描述

S ( h ) = 2 h − 1 − h S(h) = 2^h - 1 - h S(h)=2h1h

四、变量代换与渐进复杂度分析

目标: 消去中间变量 h h h,将基于高度的函数 S ( h ) S(h) S(h) 转换为基于数据规模的函数 T ( n ) T(n) T(n)

1. 建立高度 h h h与节点数 n n n的映射关系

(前置知识验证:基于满二叉树等比求和性质及对数定义,映射关系成立)

2. 代换求解 T ( n ) T(n) T(n)

将上述映射关系直接代入第三阶段求出的 S ( h ) = 2 h − 1 − h S(h) = 2^h - 1 - h S(h)=2h1h 中:
T ( n ) = ( n + 1 ) − 1 − log ⁡ 2 ( n + 1 ) T(n) = (n + 1) - 1 - \log_2(n+1) T(n)=(n+1)1log2(n+1)
T ( n ) = n − log ⁡ 2 ( n + 1 ) T(n) = n - \log_2(n+1) T(n)=nlog2(n+1)

3. 大 O 渐进分析 (Asymptotic Analysis)

在大 O 表示法中,当数据规模 n n n 极大时,需忽略低阶项:

  • 量级对比: 线性项 n n n 的增长速率远大于对数项。例如当 n = 1 , 000 , 000 , 000 n = 1,000,000,000 n=1,000,000,000(十亿)时, log ⁡ 2 n \log_2 n log2n 仅约为 30。

在这里插入图片描述

  • 结论提取: 相比于 n n n,对数项 log ⁡ 2 ( n + 1 ) \log_2(n+1) log2(n+1) 微不足道,直接作为低阶项舍去。

最终结论: 整体调整步数收敛于线性级别,自底向上(Sift-Down)建堆的最坏情况时间复杂度严格证明为 O ( n ) O(n) O(n)


补充

补充推导 1:满二叉树的总节点数计算

返回

假设满二叉树高度为 h h h,总节点数为 n n n

各层节点数依次为:

  • 第一层: 2 0 2^0 20
  • 第二层: 2 1 2^1 21
  • 第三层: 2 2 2^2 22
  • h h h 层: 2 h − 1 2^{h-1} 2h1

总节点数 n n n 即为上述各项之和。根据等比数列求和公式 S k = a 1 ( q k − 1 ) q − 1 S_k = \frac{a_1(q^k - 1)}{q - 1} Sk=q1a1(qk1)

  • 首项 a 1 = 1 a_1 = 1 a1=1
  • 公比 q = 2 q = 2 q=2
  • 项数 k = h k = h k=h (共有 h h h 层)

代入公式,得出高度为 h h h 的满二叉树总节点数为:
n = S h = 1 ⋅ ( 2 h − 1 ) 2 − 1 = 2 h − 1 n = S_h = \frac{1 \cdot (2^h - 1)}{2 - 1} = 2^h - 1 n=Sh=211(2h1)=2h1

补充推导 2:通过对数公式求树高h

返回

已知满二叉树总节点数 n n n 与高度 h h h 的关系为: n + 1 = 2 h n + 1 = 2^h n+1=2h

对数公式复习:

  • 表达式: log ⁡ a b = x    ⟺    a x = b \log_a b = x \iff a^x = b logab=xax=b
  • 含义: a a a 的几次幂等于 b b b
  • 术语: a a a 为底数, b b b 为真数

代入转换:
既然 a x = b a^x = b ax=b 可以转换为 x = log ⁡ a b x = \log_a b x=logab
那么 2 h = n + 1 2^h = n + 1 2h=n+1 即可转换为:
h = log ⁡ 2 ( n + 1 ) h = \log_2(n+1) h=log2(n+1)

补充推导 3:等比数列求和公式推导(错位相减法)

返回

1. 明确定义与变量:

  • a 1 a_1 a1(或简写为 a a a):代表首项(数列里的第一个数字)。
  • q q q:代表公比(后一个数字是前一个数字的多少倍)。
  • n n n:代表项数(这串数字里一共有多少个数)。
  • S n S_n Sn:代表前 n n n 项的和(Sum)。

例子对应:

对于数列 1, 2, 4, 8, 16:

  • a 1 = 1 a_1 = 1 a1=1
  • q = 2 q = 2 q=2
  • n = 5 n = 5 n=5(一共有 5 个数)
  • 我们要求的目标就是 S 5 = 1 + 2 + 4 + 8 + 16 S_5 = 1 + 2 + 4 + 8 + 16 S5=1+2+4+8+16

2. 数列的通用表达形式

利用乘方概念,这串求和式子可以写成字母形式:

S n = a 1 + a 1 q + a 1 q 2 + a 1 q 3 + ⋯ + a 1 q n − 1 S_n = a_1 + a_1q + a_1q^2 + a_1q^3 + \dots + a_1q^{n-1} Sn=a1+a1q+a1q2+a1q3++a1qn1

(注意:最后一项是 q n − 1 q^{n-1} qn1,是因为第一项是从 q 0 q^0 q0 开始算的。)

3. 核心魔术:错位相减法

这是最关键的一步。我们要利用等式的性质和分配律,把中间那些复杂的加法全部消掉。

  • 第一步:列出原式

    S n = a 1 + a 1 q + a 1 q 2 + ⋯ + a 1 q n − 1 —— (式子 A) S_n = a_1 + a_1q + a_1q^2 + \dots + a_1q^{n-1} \quad \text{—— (式子 A)} Sn=a1+a1q+a1q2++a1qn1—— (式子 A)

  • 第二步:整体放大 q q q

    根据等式性质,两边同时乘 q q q

    q ⋅ S n = a 1 q + a 1 q 2 + a 1 q 3 + ⋯ + a 1 q n —— (式子 B) q \cdot S_n = a_1q + a_1q^2 + a_1q^3 + \dots + a_1q^n \quad \text{—— (式子 B)} qSn=a1q+a1q2+a1q3++a1qn—— (式子 B)

  • 第三步:相减(见证奇迹)

    用 (式子 B) 减去 (式子 A)。你会发现,中间长得一模一样的项都被“减没”了:

    ( q − 1 ) ⋅ S n = a 1 q n − a 1 (q - 1) \cdot S_n = a_1q^n - a_1 (q1)Sn=a1qna1

  • 第四步:提取公因式并变形

    根据分配律的反向提取:

    ( q − 1 ) ⋅ S n = a 1 ( q n − 1 ) (q - 1) \cdot S_n = a_1(q^n - 1) (q1)Sn=a1(qn1)

    最后,为了求出 S n S_n Sn,在前提条件 q ≠ 1 q \neq 1 q=1 的情况下,我们把 ( q − 1 ) (q - 1) (q1) 移到等号右边(变成除法):

    S n = a 1 ( q n − 1 ) q − 1 S_n = \frac{a_1(q^n - 1)}{q - 1} Sn=q1a1(qn1)

4. 最终公式与代入验证

这就是我们要找的等比数列求和公式。我们拿你笔记里的例子验证一下: 1 + 2 + 4 + 8 1 + 2 + 4 + 8 1+2+4+8(此时 a 1 = 1 , q = 2 , n = 4 a_1 = 1, q = 2, n = 4 a1=1,q=2,n=4)。

  • 方法一(硬算):

    1 + 2 + 4 + 8 = 15 1 + 2 + 4 + 8 = 15 1+2+4+8=15

  • 方法二(套公式):

    S 4 = 1 ⋅ ( 2 4 − 1 ) 2 − 1 = 16 − 1 1 = 15 S_4 = \frac{1 \cdot (2^4 - 1)}{2 - 1} = \frac{16 - 1}{1} = 15 S4=211(241)=1161=15

结论: 结果完全一致,推导逻辑闭环成立!

`heapq.heapify()` 实现的是 **线性时间(O(n))**,而非 naïve 方法(对每个元素调用 `heappush`)的 O(n log n),其核心在于: ✅ **自底向上、逐层“下沉”(sift-down)已知子树,利用“大部分节点靠近叶子、需下沉距离短”的树结构特性,摊还分析后总操作数为 O(n)**。 下面从**原理 → 数学推导 → 图解过程 → 代码对比 → 常见误区**五部分彻底讲清,并附可运行的可视化示例。 --- ### ✅ 一、为什么直觉上觉得是 O(n log n)?—— 错误模型 常见误解: > “`heapify` 要把 n 个元素都‘放到里’,每次 `heappush` 是 O(log n),所以 O(n log n)。” ❌ 错!`heapify` **不是逐个 `heappush`**,而是**一次性重构整个列表的序关系**,且**只对非叶子节点执行 `sift-down`**,而这些节点中绝大多数只需下沉 1~2 层。 --- ### ✅ 二、关键观察:完全二叉树的节点分布(数学基础) 设有 `n` 个节点,对应一棵**完全二叉树**(数组索引 `0` 到 `n-1`): - 叶子节点索引范围:`⌊n/2⌋` 到 `n−1`(共 ≈ n/2 个)→ **无需任何操作** - 非叶子节点(即需检查/调整的节点)索引范围:`0` 到 `⌊n/2⌋−1`(共 ≈ n/2 个) 更重要的是:**深度为 `h` 的节点最多有 `⌈n / 2^{h+1}⌉` 个,且每个最多下沉 `h` 层**。 于是总比较/交换次数上限为: \[ T(n) \le \sum_{h=0}^{\lfloor \log_2 n \rfloor} \left( \text{深度 }h\text{ 的节点数} \right) \times h \le \sum_{h=0}^{\infty} \frac{n}{2^{h+1}} \cdot h = n \sum_{h=0}^\infty \frac{h}{2^{h+1}} \] 而级数 \(\sum_{h=0}^\infty \frac{h}{2^{h+1}} = 1\)(经典收敛级数,可求导验证),故: \[ T(n) \le n \times 1 = O(n) \] ✅ 所以 `heapify` 是 **严格 O(n) 时间复杂度**(最坏、平均都是)。 > 💡 直观理解:树中一半节点是叶子(下沉 0 步),1/4 是父节点(最多下沉 1 步),1/8 是祖父(最多下沉 2 步)…… 指数衰减的“工作量权重”让总和恒定比例于 `n`。 --- ### ✅ 三、图解:自底向上 `sift-down` 过程(以 n=10 为例) 我们用数组 `[3, 9, 2, 7, 1, 8, 4, 6, 5, 0]` 最小。 索引 `0~9`,完全二叉树结构如下(`i` 的左孩子=`2i+1`,右=`2i+2`): ``` 层0: [3] ← 索引0(根) / \ 层1: [9] [2] ← 索引1,2 / \ / \ 层2: [7] [1] [8] [4] ← 索引3,4,5,6 / \ / 层3: [6] [5] [0] ← 索引7,8,9(叶子层) ``` #### ✅ 步骤 1:找出所有「非叶子节点」起始位置 - 最后一个非叶子节点索引 = `len(arr)//2 - 1 = 10//2 - 1 = 4` - 所以需处理索引:`4, 3, 2, 1, 0`(**从 4 开始,倒序到 0**) > 🔑 关键:必须**自底向上**(从最后一个非叶节点开始),否则子树未有序时,父节点 `sift-down` 会失败。 #### ✅ 步骤 2:逐个 `sift-down`(带图示) | 步骤 | 当前索引 i | 子树根值 | 操作说明 | 图示(变化部分) | |------|------------|-----------|-----------|------------------| | **① i=4** | `arr[4] = 1` | 左=无,右=无 → 已是叶子?❌<br>但 `i=4` 是非叶?→ 查:`2*4+1 = 9 < 10` → 有左孩子 `arr[9]=0` → **需比大小** | `1 > 0` → 交换 `arr[4]↔arr[9]` → `[..., 0, ..., 1]` | `[3,9,2,7,0,8,4,6,5,1]` | | **② i=3** | `arr[3] = 7` | 左=`arr[7]=6`, 右=`arr[8]=5` → min=5 → `7>5` → 交换 `arr[3]↔arr[8]` | `[..., 5, ..., 7]` → `[3,9,2,5,0,8,4,6,7,1]` | | **③ i=2** | `arr[2] = 2` | 左=`arr[5]=8`, 右=`arr[6]=4` → min=4 → `2<4` → **无需动** | 不变 | | **④ i=1** | `arr[1] = 9` | 左=`arr[3]=5`, 右=`arr[4]=0` → min=0 → `9>0` → 交换 `arr[1]↔arr[4]` → `[3,0,2,5,9,8,4,6,7,1]`<br>→ 新 `arr[4]=9`,继续下沉:<br>左=`arr[9]=1`, 右=无 → `9>1` → 交换 `arr[4]↔arr[9]` → `[3,0,2,5,1,8,4,6,7,9]` | ✅ 完成下沉(2 步) | | **⑤ i=0** | `arr[0] = 3` | 左=`arr[1]=0`, 右=`arr[2]=2` → min=0 → `3>0` → 交换 `arr[0]↔arr[1]` → `[0,3,2,5,1,8,4,6,7,9]`<br>→ 新 `arr[1]=3`,继续:<br>左=`arr[3]=5`, 右=`arr[4]=1` → min=1 → `3>1` → 交换 `arr[1]↔arr[4]` → `[0,1,2,5,3,8,4,6,7,9]`<br>→ 新 `arr[4]=3`,继续:<br>左=`arr[9]=9`, 右=无 → `3<9` → 停止 | ✅ 完成(2 步) | 最终结果(最小): ``` [0, 1, 2, 5, 3, 8, 4, 6, 7, 9] ↓ 结构验证(每节点 ≤ 孩子): 0 / \ 1 2 / \ / \ 5 3 8 4 / \ / 6 7 9 ← 全部满足 heap property ✅ ``` > 📌 注意:虽然单次 `sift-down` 最坏 O(log n),但**越靠近底部的节点,子树越小、下沉越浅**;而 `i=4,3` 这些底层节点占了非叶节点的大多数,它们只下沉 1 步 —— 这正是 O(n) 的根源。 --- ### ✅ 四、代码对比:O(n) vs O(n log n) ```python import heapq import time import random def heapify_naive(arr): """O(n log n):逐个 heappush""" h = [] for x in arr: heapq.heappush(h, x) return h def heapify_optimal(arr): """O(n):原地 heapify""" h = arr.copy() heapq.heapify(h) return h # 测试 n = 100_000 data = [random.randint(1, 1e6) for _ in range(n)] # 时间对比 start = time.perf_counter() _ = heapify_naive(data) t_naive = time.perf_counter() - start start = time.perf_counter() _ = heapify_optimal(data) t_opt = time.perf_counter() - start print(f"Naive (n log n): {t_naive*1000:.1f} ms") print(f"Optimal (n): {t_opt*1000:.1f} ms") print(f"Speedup: {t_naive/t_opt:.1f}x") ``` 📌 典型输出(n=100k): ``` Naive (n log n): 42.3 ms Optimal (n): 1.9 ms Speedup: 22.3x ``` > ✅ 随着 `n` 增大,加速比趋近 `log₂n / 2` 量级(理论极限约 16–20x @ n=1e6)。 --- ### ✅ 五、常见误区澄清 | 误区 | 正解 | |------|------| | ❌ “`heapify` 就是调用 `n` 次 `sift-down`,所以 O(n log n)” | ✅ 只对 `n//2` 个非叶节点调用,且平均下沉深度 < 2,总和 O(n) | | ❌ “自顶向下(从根开始)也能 O(n)” | ✅ 不行!若先调 `sift-down(0)`,此时子树无序,无法保证正确性;必须先保证所有子树是,再修父节点(分治思想) | | ❌ “Python 的 `heapq.heapify` 不是 O(n),因为 C 实现有额外开销” | ✅ C 实现只是让常数更小,**渐进复杂度仍是严格 O(n)**,且实测吻合 | | ❌ “用 `sorted()` 更快?” | ✅ `sorted()` 是 O(n log n) 且生成新 list,空间 O(n);`heapify` 是 O(n) 时间 + O(1) 额外空间 | --- ### ✅ 六、Bonus:可视化过程(可运行) 以下代码用 `rich` + `time.sleep` 演示每一步 `sift-down`(需 `pip install rich`): ```python from rich.console import Console from rich.table import Table import time console = Console() def heapify_with_trace(arr): console.print(f"[bold green]Initial:[/bold green] {arr}") h = arr.copy() n = len(h) # 从最后一个非叶节点开始,倒序 for i in range(n // 2 - 1, -1, -1): console.print(f"\n[bold yellow]→ sift-down at index {i} (value={h[i]}):[/bold yellow]") pos = i while True: left = 2 * pos + 1 right = 2 * pos + 2 smallest = pos if left < n and h[left] < h[smallest]: smallest = left if right < n and h[right] < h[smallest]: smallest = right if smallest == pos: console.print(f" ✓ Already heap at {pos}") break h[pos], h[smallest] = h[smallest], h[pos] console.print(f" ✏️ swap {pos}<->{smallest}: {h}") pos = smallest time.sleep(0.3) console.print(f"\n[bold green]Final heap:[/bold green] {h}") return h # 示例运行 heapify_with_trace([3, 9, 2, 7, 1, 8, 4, 6, 5, 0]) ``` 运行后你会看到每一步交换,直观感受“自底向上”的力量。 ---
### 构最小时间复杂度分析 构一个包含 \( N \) 个元素的最小可以通过两种主要方法完成:一种是从上到下的逐步插入法,另一种是自底向上的整体调整法。 #### 方法一:逐步插入法 如果通过逐一插入的方式构最小,则每插入一个新的元素都需要将其向上调整至正确位置(即 sift-up 操作)。对于第 \( k \) 个插入的元素,其可能的最大调整路径长度为树的高度 \( \log_2(k) \)[^3]。因此,总的调整开销可以表示为: \[ T_{insert} = \sum_{k=1}^{N} O(\log_2(k)) \] 由于对数函数的增长速度较慢,上述求和的结果近似等于 \( O(N \log_2(N)) \)[^4]。这意味着逐个插入的方法在最坏情况下的时间复杂度为 \( O(N \log_2(N)) \)--- #### 方法二:自底向上调整法 相比之下,更高效的构方式是先将所有 \( N \) 个元素按顺序存入数组中,随后从最后一个非叶子节点开始依次向下调整(即 sift-down 操作),直到整个结构满足最小性质为止[^1]。 具体而言,假设的高度为 \( h = \lfloor \log_2(N) \rfloor \),则高度为 \( i \) 的层上有最多 \( 2^i \) 个节点,而这些节点各自需要执行不超过 \( h - i \) 次比较操作来完成调整[^2]。总的操作次数可由下式估算: \[ T_{buildheap} = \sum_{i=0}^{h} (h - i) \cdot 2^i \] 经过数学推导可知,此表达式的渐进上限为线性级别,即 \( T_{buildheap} = O(N) \)[^1]。这表明自底向上调整法能够在常系数意义上显著优于逐步插入法。 --- ### 结论 综上所述,在实际应用中最推荐使用基于自底向上调整策略的方式来快速创大小为 \( N \) 的最小,其理论最优时间复杂度为 **\( O(N) \)** [^1]^。 ```python def build_min_heap(array): n = len(array) for i in range((n // 2) - 1, -1, -1): # Start from the last non-leaf node heapify_down(array, i, n) def heapify_down(heap, index, size): smallest = index left_child = 2 * index + 1 right_child = 2 * index + 2 if left_child < size and heap[left_child] < heap[smallest]: smallest = left_child if right_child < size and heap[right_child] < heap[smallest]: smallest = right_child if smallest != index: heap[index], heap[smallest] = heap[smallest], heap[index] heapify_down(heap, smallest, size) ``` 问题
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值