跳到主要内容
约 3 分钟2 张卡

堆

二叉堆是满足堆序的完全二叉树。本文负责上浮、下沉、建堆与 Top K,范围是数据结构。

本文用完全二叉树分析数组布局与操作成本。尚不熟悉这种树形时,先读二叉树。

1. 面试核心​

先记住这几条,再读正文
  • 基本操作。 二叉堆查看极值为 O(1),插入与删除堆顶为 O(log n)。
  • 建堆成本。 自底向上对非叶节点执行下沉,建堆总时间为 O(n)。靠近叶子的节点数量多,但能下沉的距离短。卡 · 自底向上建堆
  • Top-K。 大小为 k 的最小堆可维护最大的 k 个元素。堆顶是候选中最小的一项,遇到更大的新元素时替换它即可。卡 · Top K 候选集合

2. 数组位置与堆序​

下标从 0 开始时,i 的孩子位于 2*i+1、2*i+2。非根节点的父下标为 (i-1)/2,取整数商。最小堆要求父值不大于两个孩子。

插入先写到末尾,再沿父节点上浮。删除堆顶先用末项替换根,再与较小孩子比较并下沉。每次调整最多经过 O(log n) 层。

3. Top K 候选集合​

static int kthLargest(int[] values, int k) {
if (k < 1 || k > values.length) throw new IllegalArgumentException();
java.util.PriorityQueue<Integer> heap = new java.util.PriorityQueue<>();
for (int value : values) {
if (heap.size() < k) heap.offer(value);
else if (value > heap.peek()) {
heap.poll();
heap.offer(value);
}
}
return heap.peek();
}

输入 [3,1,5,5,2]、k=2,返回 5。重复元素各占一个排名。读入不足 k 项时,堆保存全部元素。之后堆始终保存已读部分中最大的 k 项。

新值大于堆顶时,替换最小候选。时间为 O(n log(k+1)),空间为 O(k)。这个写法也覆盖 k=1 的线性成本。

4. 自底向上建堆​

叶子本身已满足堆序。从末个非叶节点 n/2-1 向前下沉即可建堆。处理一个父节点时,它的两个子堆已完成。

约一半节点下沉 0 层,四分之一至多下沉 1 层。更高节点越来越少。总工作受 n·Σ(h/2^(h+1)) 约束,级数收敛,因此为 O(n)。逐个插入的最坏总成本则为 O(n log n)。

5. 使用边界​

堆适合反复取极值,任意值查找最坏为 O(n)。Java 21 PriorityQueue 的迭代顺序未排序。需要有序输出时,应连续移除堆顶。

队内对象的优先级改变后,堆需重新调整。可先移除,再改值并插入。频繁定点修改可使用带位置索引的堆。比较器使用 Integer.compare,避免减法溢出。

6 · 面试题卡片​

先想答案,再翻面先在心里答一遍,再点开卡片对照参考回答。卡片之间可以左右切换,看完会回到这里。
逐张翻看 2 张 →
未标记的卡读完后明天进入复习;已标记的按状态安排
本页题目与参考回答

6.1 求最大的 k 项为何维护最小堆?​

考察重点:候选不变量与重复值排名。

相关内容:Top K 候选集合。

参考回答

最小堆能立即取到候选中最小的值。

新值更大时替换它,就保留已读部分最大的 k 项。满堆时的堆顶就是第 k 大值,重复项分别计数。

6.2 自底向上建堆为何是 O(n)?​

考察重点:节点高度分布与累计下沉次数。

相关内容:自底向上建堆。

参考回答

高节点少,低节点多,累计下沉次数为 O(n)。

叶子无需下沉,一半左右的节点直接完成。按各高度的节点数乘下沉上界求和,得到线性总量。

7. 参考资料​