数组实现最大堆的核心是通过索引映射完全二叉树结构,父节点索引为(int)(($i-1)/2),左子为$i2+1,右子为$i2+2;插入时追加后上浮,删除最大值时用末尾元素覆盖堆顶再下沉。

直接用数组实现最大堆,核心是把堆的完全二叉树结构映射到一维数组上,再通过索引关系维护“父 ≥ 子”的堆序性。它不是为了一次性找最大值而设计的,而是为动态维护一组数、支持高频插入和快速取最大值服务的。如果你只需要静态找一个数组里的最大值,用 max() 就够了;但若后续还要不断加新数、删最大值、再取新最大值,那数组堆才是正解。
数组下标与父子节点的对应关系
这是整个逻辑的地基。假设数组索引从 0 开始(PHP 默认),那么对任意位置 i:
- 父节点索引 = (int)(($i - 1) / 2)
- 左子节点索引 = $i * 2 + 1
- 右子节点索引 = $i * 2 + 2
比如索引 0 是根,它的左子是 1、右子是 2;索引 3 的父就是 (3−1)/2 = 1;索引 4 的父是 (4−1)/2 = 1(整除后为 1)。这个关系必须熟记,所有上浮(siftUp)和下沉(siftDown)操作都靠它驱动。
插入新元素:先追加,再上浮调整
新值总放在数组末尾,但它可能比父节点小——不满足最大堆性质,就得“往上冒”直到位置正确:
- 把新值
array_push($heap, $value)加入数组尾部 - 设当前索引
$i = count($heap) - 1 - 循环判断:
if ($heap[$i] > $heap[(int)(($i-1)/2)]),成立就交换,并令$i = (int)(($i-1)/2) - 重复直到 $i 回到 0 或不再大于父节点
例如插入 15 到堆 [50,30,40,10,20,35],追加后为 [50,30,40,10,20,35,15],15 在索引 6,父在索引 2(值为 40),15
提取并删除最大值:取堆顶、补末尾、再下沉
最大值永远在索引 0。删它不能真“删”,而是用最后一个元素覆盖它,再让这个新根往下沉到底层合适位置:
- 保存
$max = $heap[0] - 把末尾元素移到索引 0:
$heap[0] = array_pop($heap) - 从索引 0 开始下沉:
siftDown(0) - 下沉逻辑:比较当前节点与左右子中较大者,若当前更小,就交换,并跳到那个子节点继续;否则停止
下沉时注意边界:左子索引 $left = $i * 2 + 1 必须 ,右子同理。没有右子时只比左子。
实战提取最大值:三步闭环操作
假设你有一组实时进来的温度数据,需要随时知道当前最高温,并支持新增测量值:
- 初始化空堆:
$tempHeap = []; - 逐个插入:
insert($tempHeap, 28); insert($tempHeap, 32); insert($tempHeap, 29); … - 任何时候要取当前最高温:
$highest = $tempHeap[0] ?? null;—— O(1),无需遍历 - 如果某次测量失效需移除最高值:
$removed = deleteMax($tempHeap);,堆自动重排,下次$tempHeap[0]仍是新最大值
这比每次 max($array) 都全量扫描快得多,尤其当数组频繁增删时,堆把“取最大”稳定在常数时间,“删最大”控制在对数时间。










