(b3)完全二叉堆:删除与下滤

官官方信息技术老师·15 页·深入(追求细节与边界)·0 次浏览·38 天前
堆堆序性下滤

完全二叉堆:删除与下滤

看完你能亲手画出一个元素出堆后的下滤全过程

按 空格/→ 演示下一步

问一句
1 / 15 页

全部页面点击任意一页,跳回舞台从这页播放

堆堆序性下滤

完全二叉堆:删除与下滤

看完你能亲手画出一个元素出堆后的下滤全过程

1第 1 页 · 完全二叉堆:删除与下滤

删除的真正难题

上页说过:堆顶是最值,取走它天经地义。但取走后堆顶空出一个洞,谁来补?随手挑一个放上去,堆序和完全二叉树的形状可能同时崩掉。

摘顶留洞
取走堆顶后,根位置空缺,堆的骨架出现缺口
上移补洞不行
让最后的叶子直接上移,会破坏父子堆序关系
右下补也不行
把最后的叶子挪到根,形状对,但值往往不配当最值
真正的约束
补洞必须同时保住:堆序性质 + 完全二叉树形状
公司总经理离任对应 →删除堆顶

让最基层的新人直接当总经理(上移)会乱套,得选够格者接位再逐级调整

2第 2 页 · 删除的真正难题

下滤算法四步法

删除堆顶后,用固定四步把新顶沉到正确位置,恢复堆序。

1
末元替顶
取出堆顶后,把最后一个元素搬到顶上补位
2
找孩子比
新顶与左右孩子比较,检查是否已满足堆序
3
与大孩换
与较大的孩子交换,把更大的顶上去
4
重复下移
交换后以新位置继续比较,直到成为叶子或堆序成立
3第 3 页 · 下滤算法四步法

为什么选「较大的孩子」交换

上一页的下滤四步法里藏着一个关键选择:换哪个孩子?选错了,堆性质照样被破坏。这一页把所有选项摆上台面,看看为什么只有一个答案是安全的。

目标是恢复堆序
父须不小于两个孩子,换完后左右子树本就合法,无需操心
选较小的孩子:错
另一侧更大的孩子仍在父之下,违反父不小于子
选任意一个:不稳
等于赌运气,一半情况直接破坏堆序
选较大孩子:必对
新父成为三者最大,两条父子关系同时满足
拔河队选最强者对应 →下滤选较大孩子

新队长必须压住两边所有人,所以只能选最强者上场

a[i]≥a[2i+1] 且 a[i]≥a[2i+2]⇒换 max⁡(a[2i+1], a[2i+2])a[i] \ge a[2i{+}1] \ \text{且}\ a[i] \ge a[2i{+}2] \Rightarrow \text{换}\ \max(a[2i{+}1],\,a[2i{+}2])
4第 4 页 · 为什么选「较大的孩子」交换

删除前的堆全貌

这是一棵已建好的小节堆:树形与数组两种视角并存,先认清堆顶与末元,后面删除操作全围绕它们展开。

图解渲染中…
T:下标 0,即向量首元素,优先级最高R3:最后一个孩子,对应数组末位下标 5A0:双横线连接表示数组下标递增的线性关系tree -.- arr:同一批数据的两种等价视角,一一对应
5第 5 页 · 删除前的堆全貌

下滤执行全程图解

以实际堆为例:删除最大元 50 后,末元 10 逐层下滤的全过程。

1
末元补位
取出 50 后,末元 10 暂放堆顶,先满足结构性质
2
比孩子
10 与 30、40 比较,40 较大,准备交换
3
第一层换
10 与 40 交换,40 上位,10 沉到下一层
4
第二层比
10 与独子 10 比较,相等即稳定,下滤结束
6第 6 页 · 下滤执行全程图解

提前终止的边界

这张流程图展示下滤走到叶子边界的判断路径:每层先确认孩子是否存在,只比较真实存在的孩子。

图解渲染中…
a3:孩子越界即停,当前节点已是合法堆位a5:只有左孩子时,左孩子就是唯一候选,无需比较
7第 7 页 · 提前终止的边界

percolateDown 实现

java

循环节三步:找较大的孩子、比较决定是否交换、下移一层继续。

代码高亮加载中…

循环节完整对应「四步法」:算孩子下标→选较大孩子→不满足则交换→游标下移进入下一轮;两个 break 分别覆盖越界与提前终止两类边界。

8第 8 页 · percolateDown 实现

delMax 主函数实现

java

delMax 三步组合:摘顶、末元补位、缩减规模后调下滤,看主流程如何串起前面的算法。

代码高亮加载中…

四步各占一行:摘顶保返回值、末元补位破坏堆序、缩减规模、下滤修复——正是本节标题的组合逻辑。

9第 9 页 · delMax 主函数实现

实现中的陷阱辨析

前面已经跑通了 delMax 和 percolateDown 的完整代码,但「能跑对」和「写得稳」是两回事。这页把实现中最常踩的三个坑摆在一起,逐一辨析,帮你把代码的每个分支都变得有据可依。

只有左孩子的判断
j+1 会越界,必须先判 j+1 < n 再取右孩子
rank 越界
下滤到底层时孩子下标超出 n,循环条件要兜住
勿交换两次
找到目标孩子后交换一次即可,重复交换会破坏结构
深夜下楼梯对应 →下滤循环边界

楼梯只剩一级时不能再跨两步,对应右孩子不存在时的判断

10第 10 页 · 实现中的陷阱辨析

为什么是 O(logn)

前面图解里,下滤每一步只沿着一条路径往下走。直觉上走得不远,但深入分析要给出证明:这条路最长能有多长?答案藏在完全二叉树的形状里。

路径即树高
下滤从根出发,每次下降一层,路径长不超过树高
树高 O(logn)
完全二叉树每层节点数翻倍,n 个节点时高约 log₂n
每层 O(1)
每层只做两次比较加一次交换,代价为常数
相乘得界
路径长 × 每层代价 = O(logn)×O(1) = O(logn)
公司逐级审批对应 →下滤的层序比较

报告每下一级只和一个主管交涉,层级数就是审批时间

h=⌊log⁡2n⌋  ⇒  T(n)=O(h)=O(log⁡n)h = \lfloor\log_2 n\rfloor \;\Rightarrow\; T(n) = O(h) = O(\log n)
11第 11 页 · 为什么是 O(logn)

插入上滤 vs 删除下滤

上滤与下滤方向相反、比较对象不同,初学最易混为一谈,值得逐条对齐辨析。

插入·上滤
  • 方向:自底向上,向堆顶移动
  • 比较对象:只和父亲比
  • 场景:新元素入堆后的修复
删除·下滤
  • 方向:自顶向下,向叶层移动
  • 比较对象:和较大的孩子比
  • 场景:摘走堆顶后的修复
正确的理解是:两者都是把「违规元素」送往它该在的方向——只需一路移动,另一个必与兄弟参与比较。
12第 12 页 · 插入上滤 vs 删除下滤

自测:下滤走几步

点击作答

在含 1000 个元素的完全二叉堆中删除最大值,最坏情况下下滤需要交换多少次?

13第 13 页 · 自测:下滤走几步

要点回顾

  • ✓删除=末元素补顶+下滤复位
  • ✓每层与较大孩子交换,直到不再下沉
  • ✓到达叶子或无更大孩子即可提前终止
  • ✓树高 O(logn),故删除为 O(logn)
  • ✓上滤比父、下滤比子,方向成对
延伸主题:批量建堆:自下而上的 O(n) 方法堆排序完整流程多叉堆与左式堆
14第 14 页 · 要点回顾

课后思考

先合上讲义自己想,再对照参考答案,看思路是否一致。

1自底向上建堆时,为什么从最后一个内部节点倒着做下滤,而不是从根正着做?

参考答案倒序保证做下滤时左右子树已是合法堆,四步法的前提才成立;若从根开始,子树还未成堆,下滤无从比较。小堆先立,大堆再合。

2如果删除时允许与较小的孩子交换,堆序会怎么被破坏?构造一个具体例子说明。

参考答案与较小孩子交换后,较小的孩子升上来,可能仍大于另一侧被换下去的孩子的孩子,堆序在跨层处断裂。可试 9、8、7 的堆删 9,换 7 后 8 在 7 之下。

3堆能高效取最值,但整个堆不是有序序列——这个「部分有序」换来了什么、牺牲了什么?

参考答案换来插入与删除的 O(logn),却牺牲了全局次序:找中位数、第 k 小、范围查询都得扫全堆。只要最值,堆最省;要更多次序,得换数据结构。

15第 15 页 · 课后思考