← 返回题目列表

d 叉堆是什么?它和二叉堆相比有什么取舍?

中等 第 24 / 28 题 更新于 2026/07/30
d叉堆优先队列

简化版

d 叉堆就是每个节点最多有 d 个孩子的堆。

二叉堆是 d = 2 的特例。d 叉堆的高度更低,所以插入上浮路径更短;但下沉时要在最多 d 个孩子里找最优孩子,单层比较次数更多。

因此 d 叉堆适合插入和 decrease-key 多、删除堆顶相对少的场景。很多图算法或调度系统会根据读写比例选择合适的 d

详细版

二叉堆中,节点 i 的孩子下标通常是 2i + 12i + 2

d 叉堆把这个关系推广成:

children(i): d*i + 1 ... d*i + d
parent(i): (i - 1) / d

d 增大时,堆的高度大约从 log2 n 变成 logd n。这会减少上浮和下沉经过的层数。

但删除堆顶时,每下降一层都要从 d 个孩子中选出最小或最大者,所以单层比较成本变高。

操作二叉堆d 叉堆
插入O(log2 n)O(logd n)
删除堆顶每层比较少层数少但每层比较多
空间数组数组

面试回答重点不是说 d 越大越好,而是说它在高度和单层比较之间做权衡。

完整版教学

1. 从二叉堆自然推广到 d 叉堆

二叉堆之所以常见,是因为实现简单、常数稳定。

但堆的核心要求并不是「必须两个孩子」,而是:

  • 结构上接近完全树;
  • 顺序上满足父节点优先级不低于或不高于子节点;
  • 可以用数组紧凑存储。

d 叉堆只是把每个节点的孩子数从 2 扩展到 d

d 叉堆本质上仍然是数组堆,只是父子下标公式变了。

2. 数组下标公式怎么推

如果数组从 0 开始,节点 i 的第 k 个孩子是:

child(i, k) = d * i + k
其中 k = 1, 2, ..., d

父节点是:

parent(i) = floor((i - 1) / d)

比如 d = 4 时:

节点 i孩子范围父节点
01,2,3,4
15,6,7,80
29,10,11,120

这说明 d 叉堆依然不需要指针,空间局部性很好。

3. 高度为什么会降低

二叉堆每一层最多容纳 2^level 个节点,而 d 叉堆每一层最多容纳 d^level 个节点。

所以容纳 n 个元素时,高度大约是:

height = O(log_d n)

例如 n = 1,000,000

  • 二叉堆高度约 20
  • 四叉堆高度约 10
  • 八叉堆高度约 7

高度降低意味着上浮路径更短,这对大量插入或降低优先级的场景很有吸引力。

4. 为什么删除堆顶不一定更快

删除堆顶需要把最后一个元素放到根,再一路下沉。

二叉堆每层只要比较两个孩子,选一个更优的孩子交换。

d 叉堆每层要在最多 d 个孩子里找最优者:

best = firstChild
for each child in children:
  if child better than best:
    best = child

因此 d 增大后:

  • 层数减少;
  • 每层比较次数增加。

这就是典型的常数权衡,而不是单纯的大 O 结论。

5. decrease-key 场景为什么常提 d 叉堆

在一些图算法里,可能会频繁发生 decrease-key。

decrease-key 通常是让某个节点优先级变高,然后上浮。上浮每层只需要和父节点比较一次,所以 d 叉堆高度越低,上浮越快。

如果工作负载是:

操作次数特点
插入
decrease-key
删除堆顶相对少

那么 d 叉堆可能比二叉堆更合适。

6. d 应该取多大

没有一个永远最优的 d

实际选择要看:

  1. 元素规模;
  2. 插入、更新、删除堆顶的比例;
  3. CPU 缓存和分支预测;
  4. 比较函数是否昂贵;
  5. 语言运行时和标准库实现。

工程里常见的是 4 叉堆,因为它在高度和单层比较之间比较平衡,也比较适合缓存局部性。

7. 和二叉堆的面试对比话术

可以这样回答:

维度二叉堆d 叉堆
孩子数量2d
高度较高较低
上浮层数更多层数更少
下沉每层比较少每层比较多
实现复杂度最简单稍复杂
适合场景通用更新多、插入多的场景

这类题的关键是体现取舍意识:d 叉堆不是淘汰二叉堆,而是在特定负载下优化常数。

8. 常见误区与追问

  • 误区:d 叉堆的复杂度一定比二叉堆好。 大 O 仍然是对数级,真正变化的是高度和比较次数的常数。
  • 误区:d 越大越好。 d 太大会导致每次下沉扫描太多孩子,删除堆顶可能变慢。
  • 误区:d 叉堆不能用数组存储。 它和二叉堆一样可以用数组,只是下标公式不同。
  • 追问:为什么四叉堆比较常见? 因为高度明显下降,同时每层比较成本还可控,是比较折中的选择。
  • 追问:d 叉堆适合什么业务? 适合任务优先级频繁变化、插入多、需要较好缓存局部性的优先队列场景。