前端进阶之旅前端进阶之旅
  • 基础篇HTML/CSS/JS 打底
  • 进阶篇原理与工程化
  • 高频篇面试最常问的那批
  • 精选篇按模块收敛的总结
  • 手写篇常考代码手写实现
  • 面经篇真实面试问题复盘
  • AI 篇NEWAI 时代的前端考点
  • 历年面经NEW按年份追踪真实考点
  • 每日一题每天一道,攒手感
  • 专项自测100 题快速查漏
  • 小程序题库小程序专项刷题
  • 算法题库NEW在线编码即时判题
  • 知识卡片NEW碎片时间过考点
  • 面试题大全常见问题解析
  • AI 答疑NEW随时提问,即时解析
  • AI 模拟面试NEW模拟真实面试 + 报告
  • AI 定制路线NEW按你的简历现排
  • AI 知识地图NEW串起全站知识点
  • 原理篇React / Vue 源码拆解
  • HTTP从报文一路讲到 HTTPS
  • 浏览器渲染、事件循环、进程
  • 计算机基础Linux、网络、操作系统
  • 设计模式23 种模式怎么用
  • Node学习指南从环境搭建到服务端
  • NPM工作流script、依赖与发布
  • Docker容器化部署上手
  • Canvas图形与动画实战
  • 前端系统进阶学习大型项目工程化
  • 前端综合文章长期沉淀的实践文
  • 思维导图知识点全景图
  • 学习路线按图索骥不跑偏
  • AI 热点NEWAI 每日动态
  • 公众号动态公众号历史文章
  • 博客动态站长的技术博客
  • 开发者导航常用工具与文档站
  • 基础篇HTML/CSS/JS 打底
  • 进阶篇原理与工程化
  • 高频篇面试最常问的那批
  • 精选篇按模块收敛的总结
  • 手写篇常考代码手写实现
  • 面经篇真实面试问题复盘
  • AI 篇NEWAI 时代的前端考点
  • 历年面经NEW按年份追踪真实考点
  • 每日一题每天一道,攒手感
  • 专项自测100 题快速查漏
  • 小程序题库小程序专项刷题
  • 算法题库NEW在线编码即时判题
  • 知识卡片NEW碎片时间过考点
  • 面试题大全常见问题解析
  • AI 答疑NEW随时提问,即时解析
  • AI 模拟面试NEW模拟真实面试 + 报告
  • AI 定制路线NEW按你的简历现排
  • AI 知识地图NEW串起全站知识点
  • 原理篇React / Vue 源码拆解
  • HTTP从报文一路讲到 HTTPS
  • 浏览器渲染、事件循环、进程
  • 计算机基础Linux、网络、操作系统
  • 设计模式23 种模式怎么用
  • Node学习指南从环境搭建到服务端
  • NPM工作流script、依赖与发布
  • Docker容器化部署上手
  • Canvas图形与动画实战
  • 前端系统进阶学习大型项目工程化
  • 前端综合文章长期沉淀的实践文
  • 思维导图知识点全景图
  • 学习路线按图索骥不跑偏
  • AI 热点NEWAI 每日动态
  • 公众号动态公众号历史文章
  • 博客动态站长的技术博客
  • 开发者导航常用工具与文档站
首页程序员面试题库二叉堆 数组表示 父节点 子节点 下标
数据数据结构索引与高级结构

二叉堆用数组存储时下标从 0 和从 1 开始,父子下标公式与边界检查差在哪里?

两种下标基址把父子关系换算成不同算术式:0 基用 (i-1)/2 与 2i+1,2i+2,1 基用 i/2 与 2i,2i+1。边界检查随公式偏移,但堆性质与操作复杂度不变。

前端进阶之旅 · 一题精讲更新于 2026.09.05
数据结构#索引与高级结构#Agent 记忆
先看核心答案读代码示例
理解线索

下标基址决定偏移量

  1. 0基根在a[0],子节点公式带+1偏移。
  2. 1基根在a[1],a[0]不用或存大小。
  3. 位运算1基可写i>>1和i<<1,0基需要加或减。

记住:0基时左孩子为2i+1,右2i+2;1基时左2i,右2i+1,差别来自根的位置。

核心回答

先记住这个答案

0 基数组里父节点索引为 (i-1)>>1,左孩子 2i+1,右孩子 2i+2;1 基数组(索引 1 起)父节点是 i>>1,左孩子 2i,右孩子 2i+1。检查孩子是否存在时,0 基须判断 2i+1 < n,1 基判断 2i <= n。两者只差一个偏移常数,但写错会越界或漏判。堆操作依赖这些公式从上往下或从下往上移动,基址变化不改变时间复杂度。

  • 0基父节点公式是(i-1)/2,非i/2
  • 1基数组通常索引0闲置以简化运算
  • 边界检查必须与下标基址配套

公式推导与越界判断的精确差异

对于从 0 开始的数组,若根节点放在 a[0],第 i 个节点的左孩子应为 2i+1,右孩子 2i+2,因为从 a[0] 出发每往下层要跳过当前层全部节点。反过来,父节点是 i==0 时无父,否则 floor((i-1)/2),对应右移 (i-1)>>1。判断左孩子是否存在要用 2i+1 < n,右孩子则是 2i+2 < n。

换成从 1 开始的数组,通常令 a[1] 为根并让 a[0] 闲置或充作哨兵。此时左孩子为 2i,右孩子为 2i+1,父节点为 i>>1(因为整数除法自动向下取整)。左孩子存在条件简化为 2i <= n,右孩子则需 2i+1 <= n。可见 1 基省去 -1 和 +1 修正,边界检查也直接与数组长度 n 比较,代码更对称,但代价是浪费一个元素的空间。

两种下标基址的父子换算函数JavaScript
function parent0(i) { return (i - 1) >> 1; }
function left0(i) { return 2 * i + 1; }
function right0(i) { return 2 * i + 2; }

function parent1(i) { return i >> 1; }
function left1(i) { return 2 * i; }
function right1(i) { return 2 * i + 1; }

// 给定数组长度 n 和孩子索引判断边界
function hasLeft0(i, n) { return left0(i) < n; }
function hasLeft1(i, n) { return left1(i) <= n && left1(i) >= 1; }

let n = 6; // 1基数组实际使用长度6,索引1~6
console.log(hasLeft0(2, 6)); // 左0:2*2+1=5 <6 -> true
console.log(hasLeft1(2, 6)); // 左1:4 <=6 -> true
console.log(hasLeft0(3, 6)); // 2*3+1=7 <6 false
console.log(hasLeft1(3, 6)); // 6 <=6 true
查看输出与解释
true
true
false
true

演示0基与1基在相同逻辑位置的孩子索引不同,边界判断结果因为基地址不同在同一数值下可能不同。

用数组画堆时位运算加速的场景

假设在内存受限的嵌入式环境中用 C 语言实现优先队列,堆容量固定为 1024,希望用位运算代替乘法除法。若采用 1 基下标,左孩子就是 i<<1,父节点就是 i>>1,代码无分支且快;但数组需声明为 int heap[1025],多一个元素。若用 0 基,则左孩子要 (i<<1)+1,父节点要 (i-1)>>1,多一次加减。测试中两种写法逻辑等价,但 1 基的移位更整洁且不易把 (i-1)>>1 错写成 i>>1。

实际选择取决于是否在意那一个元素的存储。当堆大小本身就是 2 的幂且需要频繁访问父节点时,1 基配合右移能减少一处减法,在紧凑循环中可能省下几个时钟周期。但多数高级语言里数组从 0 开始是自然习惯,强行加偏移反而会让代码读者困惑。工程中应固定一种约定并在封装函数中统一使用,避免混用导致越界。

下标公式容易失效的边界条件

最容易出错的是 0 基堆中把父节点写成 i/2。当 i 为奇数时,i/2 与 (i-1)/2 恰好相同;当 i 为偶数时,i/2 比 (i-1)/2 大 1,例如 i=2 实际父节点是 0,但 2/2=1,访问错位。另一个常见错误是用 2i < n 判断左孩子是否越界,但左孩子索引应为 2i+1;当 2i+1 == n 时左孩子不存在但条件仍成立,导致越界访问。

对于 1 基堆,若把 left1(i) <= n 误写成 left1(i) < n,当左孩子恰为最后一个元素时(2i == n)会被误判为不存在,导致 sift-down 提前终止。同样,右孩子存在条件是 2i+1 <= n,写成 < n 也会漏判最后两个元素之间的边界。因此建议在封装类中通过 hasLeft 和 hasRight 方法统一判断,并配合单元测试覆盖 n=1,2,3 的极端情况。

回答前,多想一步

容易答错的地方

认为下标基址不影响复杂度或正确性
复杂度确实相同,但公式必须配套。把 0 基的父节点误作 i/2 会破坏堆序;把 1 基的左孩子写成 2i+1 会跳过节点。正确性依赖准确的换算,尤其边界检查不能张冠李戴。
误以为 1 基数组必须浪费第一位
1 基只是一种逻辑视图,可以分配 n+1 长度并把 0 号元素设为哨兵或干脆不用,而用索引 1 到 n。有的语言支持自定义起始下标,但多数从 0 开始,此时只需在访问时减 1,不额外浪费。关键是保持实现的内部一致性,而不是纠结存储位置。
试着用自己的话回答

面试官还会怎么问?

堆的数组下标公式如何扩展到 d 叉堆?

0 基 d 叉堆中,第 i 个节点的第 k 个孩子索引为 d*i + k(k 从 1 到 d),父节点为 floor((i-1)/d)。1 基(根在 1)时,节点 i 的孩子索引为 d*i - d + 2 到 d*i + 1(第 k 个孩子索引为 d*(i-1)+k+1),父节点为 floor((i+d-2)/d)。当 d=2 时退化为 2i、2i+1 和 i/2,可验证。

数组下标从 0 和从 1 对堆的插入和删除代码具体影响哪几行?

插入时 sift-up 循环中,0 基比较当前节点与父节点 (i-1)/2,1 基为 i/2;删除堆顶后 sift-down 中,0 基找左右孩子 2i+2 与 2i+1,1 基为 2i+1 与 2i。边界条件也随基址改变,但交换与循环框架相同。

如果数组长度是 0(空堆),两种基址的边界检查会有何不同?

空堆无有效节点,任何访问都应避免。0 基中根索引 -1 非法,1 基中根索引 1 也非法但数组长度可为 0?若 1 基数组长度 n=0,无法有索引1。因此两者都必须先检查 n>0 再访问根。边界检查公式中 hasLeft0(0,0) 为假,hasLeft1(0,0) 因 0 不在合法范围内也为假,但直接调用可能出错。实际应增加空判断。

从一道题,走向一组知识

把知识连起来

索引与高级结构

堆和二叉堆是什么关系,为什么面试里常把优先级队列默认成二叉堆?

同属「索引与高级结构」专题,接着看 堆 二叉堆 优先级队列 区别 在具体场景中的处理方式。

链表栈队列

如何每 k 个节点一组反转链表,并处理末尾不足 k 个的情况?

继续了解 k个一组反转链表,补充本题涉及的 数据结构 相关知识。

参考资料

  • Algorithms for Competitive Programming¶

示例用于理解所注明的运行环境与边界;延伸学习可结合原文中的更多案例。

本题目录
  1. 先记住这个答案
  2. 公式推导与越界判断的精确差异
  3. 用数组画堆时位运算加速的场景
  4. 下标公式容易失效的边界条件
  5. 容易答错的地方
  6. 面试官还会怎么问
  7. 把知识连起来
读懂,再试着讲出来

先看核心答案,再读代码。最后展开追问,检查自己有没有遗漏边界。

试着回答追问
浏览全部面试题理解原理,也关注真实的使用场景。回到顶部 ↑