用数组实现优先级队列的核心是将完全二叉树按层序映射到数组,通过上浮(插入时)和下潜(删除时)维护堆序性,父子索引关系为:左孩子=2×parent+1,右孩子=2×parent+2,父节点=(child−1)/2(0起始)。

用数组实现优先级队列,核心是把堆(大根堆或小根堆)的逻辑结构映射到一维数组上,并通过“上浮”和“下潜”两个关键操作维护堆序性。变量浮动逻辑不是指随意移动,而是指在插入或删除时,元素在数组中按父子索引关系动态调整位置的过程——它由索引计算驱动,不依赖额外指针或链表。
数组如何对应完全二叉树结构
堆本质是完全二叉树,而数组天然适合层序存储:根节点放索引 0,后续节点按从上到下、从左到右顺序填入。此时父子与子节点索引满足固定关系:
- 已知父节点索引 parent → 左孩子索引 = 2 × parent + 1,右孩子索引 = 2 × parent + 2
- 已知孩子索引 child(不分左右)→ 父节点索引 = (child − 1) / 2(整除,向下取整)
这个关系是所有浮动操作的数学基础。注意:所有推导默认根在索引 0;若从索引 1 开始,公式变为 left = 2×i,right = 2×i+1,parent = i/2,但主流语言标准库(如 Java 的 PriorityQueue)和多数教材均采用 0 起始,应统一使用。
插入元素时的“上浮”逻辑
新任务加到数组末尾(即 usedSize 位置),然后不断与父节点比较优先级,若更高(大根堆)就交换,再向上继续——这就是上浮。关键在于:每次交换后,当前元素的新索引就是原父索引,下一次父索引自动更新为 (newIndex − 1) / 2。
- 初始 child = usedSize(插入后 size 已自增)
- 循环条件:child > 0 且 heap[child] > heap[(child−1)/2](大根堆)
- 每次执行:swap(heap[child], heap[(child−1)/2]),然后令 child = (child−1)/2
例如插入值 15 到数组 [10, 8, 5, 3](索引 0~3),新位置是索引 4 → 父为 (4−1)/2 = 1 → 比较 heap[4]=15 与 heap[1]=8 → 交换 → 新 child=1 → 父为 (1−1)/2=0 → 比较 15 与 heap[0]=10 → 再交换 → child=0 → 停止。最终数组变为 [15, 10, 5, 3, 8]。
删除堆顶时的“下潜”逻辑
优先级队列每次取最高优先级任务,即删掉索引 0 元素。实际做法是:将末尾元素移到堆顶,再从根开始下潜——与左右孩子中较大者(大根堆)比较,若比它小就交换,重复直到满足堆序。
- 设当前父索引为 parent = 0,目标长度为 len = usedSize − 1(删后大小)
- 循环中先算左孩子 left = 2×parent + 1;若 left ≥ len,说明无子节点,停止
- 否则找较大孩子:maxChild = (right heap[left]) ? right : left
- 若 heap[parent] ,则交换并令 parent = maxChild;否则终止
这个过程里,“浮动”的是那个被挪上来的末尾元素——它一路下沉,每步只换一次位置,路径长度最多为树高 O(log n),所以高效。
建堆与排序中的批量浮动策略
初始数组无序,不能逐个插入(O(n log n))。更优做法是从最后一个非叶子节点开始,对每个节点执行一次下潜(siftDown)——因为叶子节点无需调整,而任一非叶节点的子树已是局部有序(递归前提)。
- 最后一个非叶子节点索引 = (usedSize − 2) / 2(因最大索引为 usedSize−1,其父为 (usedSize−2)/2)
- 倒序遍历:for (int i = (usedSize−2)/2; i ≥ 0; i−−) siftDown(i, usedSize)
- 堆排序第二阶段:每次把堆顶(最大)与末尾交换,然后对剩余部分(长度减 1)从根下潜 —— 此时“浮动范围”逐步缩小,但下潜逻辑不变
这种批量下潜使建堆时间复杂度仅为 O(n),远优于 n 次插入。

















