如何利用数组实现基于数组的最大堆逻辑并实战提取变量最大值
数组实现的最大堆通过索引映射模拟完全二叉树,维持父节点大于等于子节点的秩序。其核心价值在于动态高效维护数据集,支持快速获取最大值。插入时追加元素并上浮调整,删除最大值时取堆顶、补末尾并下沉重组。该结构在实时排行榜、优先队列等场景中优势显著,能以常数时间获取
用数组实现最大堆,本质上是在一维空间里模拟一棵完全二叉树,并通过一套简单的索引计算规则,维持“父节点永远大于等于子节点”的核心秩序。这种结构的设计初衷,并非为了在静态数据里找一次最大值——那种情况用 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(整除结果)。这套关系必须烂熟于心,后续所有的“上浮”和“下沉”调整,都靠它来驱动。
插入新元素:先追加,再上浮调整
当有新成员要加入时,策略很直接:先把它放到数组末尾,再视情况把它“托举”到合适的高度。因为新元素可能比它的父节点大,这就破坏了堆序性,需要一次“上浮”操作来修复。
- 第一步,执行
array_push($heap, $value),将新值放到堆尾。 - 第二步,记下它的位置:
$i = count($heap) - 1。 - 第三步,开始循环“攀爬”:只要满足
$heap[$i] > $heap[(int)(($i-1)/2)](即比父节点大),就与父节点交换位置,并将当前位置$i更新为父节点的索引。 - 循环直到它到达根节点(索引0),或者不再大于其父节点时停止。
来看个实例:向最大堆 [50, 30, 40, 10, 20, 35] 中插入15。追加后数组变为 [50, 30, 40, 10, 20, 35, 15]。新元素15位于索引6,其父节点是索引2(值为40)。由于15小于40,不满足交换条件,插入过程就此完成,堆序性依然完好。
提取并删除最大值:取堆顶、补末尾、再下沉
最大值永远稳坐堆顶(索引0)。删除它时有个小技巧:不能简单地将它从数组中移除,那样会破坏完全二叉树的结构。正确的做法是“李代桃僵”。
- 首先,取出最大值:
$max = $heap[0]。 - 接着,将数组的最后一个元素移到堆顶:
$heap[0] = array_pop($heap)。 - 最后,关键的一步来了:这个被推到顶端的“末位元素”很可能德不配位,需要执行“下沉”操作(
siftDown(0)),让它找到自己真正该待的位置。
下沉的逻辑是:比较当前节点与其左右子节点中较大的那一个。如果当前节点比那个子节点小,就交换它们的位置,然后继续从新的子节点位置向下比较。这个过程一直持续到当前节点大于等于它的所有子节点,或者已经沉到底成为叶子节点为止。
实现时需注意边界:左子索引 $left = $i * 2 + 1 必须小于当前堆的大小,右子索引同理。如果只有左子节点,那就只和左子比较。
实战:动态获取数据流中的最大值
让我们设想一个实际场景:你正在监控一系列实时传入的温度读数,需要随时知道当前最高的温度值,并且这个数据集会不断新增,偶尔还需要移除无效的峰值。
- 初始化:
$tempHeap = [];创建一个空堆。 - 持续插入:
insert($tempHeap, 28); insert($tempHeap, 32); insert($tempHeap, 29); …每个新读数都通过插入操作入堆。 - 瞬时取最大值:任何时候,当前最高温就是
$tempHeap[0] ?? null;。这是O(1)时间复杂度,无需遍历整个数组。 - 移除最大值后:如果某个最高值被判定为无效需要剔除,调用
$removed = deleteMax($tempHeap);。堆会自动内部重组,之后$tempHeap[0]给出的就是新的最大值。
与每次调用 max($array) 都需要全量扫描O(n)相比,堆结构在频繁增删的场景下优势巨大。它将“获取最大值”的成本锁定在常数时间,而“删除并重组”的成本也仅是对数级别,堪称管理动态极值问题的利器。


































