完全二叉堆:插入与上滤
看完你能逐步画出新元素上滤的每一步交换并说清何时停止
按 空格/→ 演示下一步
全部页面点击任意一页,跳回舞台从这页播放
完全二叉堆:插入与上滤
看完你能逐步画出新元素上滤的每一步交换并说清何时停止
堆序性:堆的秩序
上一页认识了完全二叉堆的形状,但光有形状还不够——节点之间得讲「规矩」。想象一家公司:上级的能力必须不弱于直接下属,否则组织就乱套了。堆的这条规矩,就叫堆序性。
上级不弱于直接下属对应父亲不小于孩子,平级同事互不管辖
完全二叉树的向量存储
上层是树形结构,实线箭头 2i+1、2i+2 指向孩子;虚线箭头标出父亲下标的换算,物理上它们连续存进一个向量。
插入三步走
插入一个新元素,靠三步既保住完全二叉的形,又保住堆序。
上滤(percolate up)
上一步我们把新元素直接追加到向量末尾——这是完全二叉树唯一能放的位置。但这个新来的可能比父节点还大,堆序被破坏了。怎么办?先看看它在树里和父亲的关系。
气泡比水轻就上升一层,比周围重(父亲更大)就停住
与父亲交换 vs 整体上滤
两版上滤结果相同,但赋值开销差三倍——混淆点在于以为比较次数也变了。
- 新元素一路与父亲swap换位
- 每次交换需3次赋值
- 比较与赋值都为O(logn)常数较大
- 先备份新元素,父亲逐一整体下移
- 每步只需1次赋值,最后归位1次
- 比较次数不变,赋值约降为1/3
实例:完整走一遍插入
把 5 插入堆 [2,4,6,7],逐帧跟踪上滤的每一步。
实现:代码逐行读
insert 与 percolateUp 的真实实现,逐行对照前面的三步走与整体上滤。
L1/L2 对应三步走的前两步;L4 是向量存储下的父结点下标公式;L5 是循环不变式出口——父亲更大即堆序恢复;L7 完成「整体上移」。
终止条件与边界
上一页我们逐行走完了上滤代码,循环里其实藏着一个问题:交换到什么时候才算完?理解了终止条件,边界情况——比如新元素一路顶到根——也就迎刃而解了。
要么到了目标层(不大于父亲),要么到了顶层(是根)
效率:为什么是 O(logn)
前面我们走完了一遍插入:新元素从叶一路向上滤,直到找到位置。那这条路最长能有多长?答案藏在这棵树的高度里——而完全二叉树的高度,恰好是对数级的。
员工逐层找上级,层级数对应树高,层内人数指数增长
自测:上滤考考你
向含 15 个元素的完全二叉堆插入一个新元素,上滤最坏需要与父亲交换几次?
要点收束
- ✓堆序性只需父子成立,兄弟无序——这是插入能局部修复的前提
- ✓向量按层序存完全二叉树,下标 i 的父亲是 ⌊(i−1)/2⌋,无需指针
- ✓插入三步:接到末尾、与父亲比较、必要时上滤
- ✓整体上滤优于逐次交换:比较次数不变,赋值从 3t 降到 t+1
- ✓树高 O(logn) 封顶,单次插入最坏 O(logn)
课后思考
先自己想一会儿,再对照参考答案——想不通的地方,值得回去翻对应那页。
参考答案核心在完全二叉树的高度是 ⌊log₂n⌋。最好情况:新元素不比父亲小,一次比较就停,O(1);最坏情况:一路顶到根,恰好做 ⌊log₂n⌋ 次比较。
参考答案优先级变小意味着要往上冒,正是上滤——定位到该元素后从那个位置向上滤即可。反过来优先级变大则要下沉,那叫下滤(percolate down),是 delete-max 的核心。
参考答案逐个插入时第 k 个元素最多上滤 log k 步,求和得 O(nlogn)。而 Floyd 自底向上建堆:从最后一个内部节点倒着逐个下滤,多数元素高度低,求和后恰好 O(n)——思路可迁移到本题。