完全二叉树与堆底层原理深度剖析 | 手写C++大顶堆实现
- 前言💫
- [🌳 一、前置基石:完全二叉树深度解析](#🌳 一、前置基石:完全二叉树深度解析)
- [⛰️ 二、堆的定义与核心性质拆解](#⛰️ 二、堆的定义与核心性质拆解)
-
- [2.1 堆的本质定义](#2.1 堆的本质定义)
- [2.2 堆中极值分布规律](#2.2 堆中极值分布规律)
- [🔧 三、堆元素插入 & 向上(上浮)调整](#🔧 三、堆元素插入 & 向上(上浮)调整)
-
- [3.1 插入规则](#3.1 插入规则)
- [3.2 上浮调整原理(大顶堆)](#3.2 上浮调整原理(大顶堆))
- [3.3 C++ 大顶堆插入 + 上浮核心代码](#3.3 C++ 大顶堆插入 + 上浮核心代码)
- [📉 四、堆顶弹出 & 向下(下沉)调整](#📉 四、堆顶弹出 & 向下(下沉)调整)
-
- [4.1 堆弹出规则](#4.1 堆弹出规则)
- [4.2 下沉调整原理(大顶堆)](#4.2 下沉调整原理(大顶堆))
- [4.3 C++ 堆弹出 + 下沉完整代码](#4.3 C++ 堆弹出 + 下沉完整代码)
- [💡 五、数据结构终极核心心法](#💡 五、数据结构终极核心心法)
- [📝 六、知识点总结](#📝 六、知识点总结)
前言💫
在编程算法的江湖里,堆绝对是不可或缺的核心数据结构,无论是堆排序、优先队列、TopK 问题求解,背后都离不开堆的支撑。
而堆并非凭空诞生,它深度依附于完全二叉树,二者有着密不可分的羁绊。很多同学学堆只记操作、不懂底层逻辑,上手写代码一头雾水。
今天带你由浅入深,从完全二叉树底层逻辑出发,层层拆解堆的定义、性质、元素插入、堆顶弹出、上浮 / 下沉调整原理,搭配字符图解 + 可直接运行的 C++ 源码,彻底吃透堆的核心精髓,打通数据结构思维壁垒!
🌳 一、前置基石:完全二叉树深度解析
1.1 完全二叉树数组存储的底层逻辑
完全二叉树最大的特性:可以用一段连续的一维数组完成存储,无需像普通二叉树那样存储指针、浪费空间。
究其根本:完全二叉树的父节点与子节点编号存在固定数学映射关系,节点之间排布紧凑、无空缺空位,天然适配连续数组存储。
我们要建立一种顶级编程思维:
逻辑层面是二维树形结构 ,存储层面是一维数组结构
高手看数组,眼中自动映射出树形结构;新手看数组,只看到一串数字。
1.2 节点编号映射公式📐
设定父节点编号为 i i i,分两种编号规则:
规则① 编号从 1 开始
-
左孩子编号: b o l d s y m b o l 2 i boldsymbol{2i} boldsymbol2i
-
右孩子编号: b o l d s y m b o l 2 i + 1 boldsymbol{2i+1} boldsymbol2i+1
-
父节点编号: b o l d s y m b o l i / 2 boldsymbol{i/2} boldsymboli/2(整数除法)
规则② 编号从 0 开始(编程数组常用)
-
左孩子编号: b o l d s y m b o l 2 i + 1 boldsymbol{2i+1} boldsymbol2i+1
-
右孩子编号: b o l d s y m b o l 2 i + 2 boldsymbol{2i+2} boldsymbol2i+2
-
父节点编号: b o l d s y m b o l ( i − 1 ) / 2 boldsymbol{(i-1)/2} boldsymbol(i−1)/2(整数除法)
1.3 字符图解:数组与完全二叉树映射
示例数组:[5,6,3,2,1,9,8,12,11]
树形逻辑结构
Plain
5
/
6 3
/ /
2 1 9 8
/
12 11
对应一维数组存储
Plain
下标:0 1 2 3 4 5 6 7 8
数值:5 6 3 2 1 9 8 12 11
二者信息完全等价 ,只是逻辑视图 和存储视图的区别,这是学习堆必须建立的直觉思维。
⛰️ 二、堆的定义与核心性质拆解
2.1 堆的本质定义
堆是特殊的完全二叉树 ,在完全二叉树结构基础上,额外增加了父子节点大小约束规则,分为两大类:
-
大顶堆(最大堆) :树中任意父节点值 > 左右子节点值
-
小顶堆(最小堆) :树中任意父节点值 < 左右子节点值
核心规律:只约束父子节点 大小关系,兄弟节点之间无任何大小约束,可随意交换位置且不破坏堆结构。
2.2 堆中极值分布规律
-
大顶堆 :全局最大值一定在堆顶根节点
-
小顶堆 :全局最小值一定在堆顶根节点
进阶极值位置分析
以大顶堆为例:
-
第二大值:只会出现在根节点的左孩子 或 右孩子
-
第三大值:可能出现在第二层 或 第三层所有节点
-
第四大值:可能分布在第二层、第三层、第四层
原理很简单:只有父子有大小层级,横向兄弟无约束,层级越靠下,节点越有可能成为次大值。
字符简易示意:
Plain
根(第1层) 最大值
/
左子(2层) 右子(2层) 第二大候选
/ /
3层节点 3层节点 第三大候选
🔧 三、堆元素插入 & 向上(上浮)调整
3.1 插入规则
堆是完全二叉树,必须遵守完全二叉树的排布规则:
新元素默认添加在完全二叉树最后一层最左侧
映射到数组:直接追加到数组末尾
插入后大概率会破坏大顶堆 / 小顶堆的性质 ,因此需要向上上浮调整,重新维护堆的规则。
3.2 上浮调整原理(大顶堆)
-
将新元素放在数组末尾
-
循环将当前节点与父节点比较
-
若当前节点 > 父节点,交换二者位置
-
持续向上比对,直到满足堆性质 或 到达堆顶
3.3 C++ 大顶堆插入 + 上浮核心代码
cpp
#include <iostream>
#include <vector>
using namespace std;
class MaxHeap {
private:
vector<int> heap;
// 向上上浮调整
void upAdjust(int idx) {
// 找到父节点下标
while (idx > 0) {
int parent = (idx - 1) / 2;
// 子节点小于等于父节点,无需调整
if (heap[idx] <= heap[parent]) break;
// 交换父子节点
swap(heap[idx], heap[parent]);
// 向上继续比对
idx = parent;
}
}
public:
// 堆中插入元素
void push(int val) {
heap.push_back(val);
// 从最后一个元素开始上浮调整
upAdjust(heap.size() - 1);
}
// 获取堆顶元素
int top() {
return heap.empty() ? -1 : heap[0];
}
// 判断堆是否为空
bool empty() {
return heap.empty();
}
};
int main() {
MaxHeap hp;
// 批量插入元素
hp.push(5);
hp.push(6);
hp.push(3);
hp.push(12);
cout << "大顶堆堆顶最大值:" << hp.top() << endl;
return 0;
}
📉 四、堆顶弹出 & 向下(下沉)调整
4.1 堆弹出规则
堆的弹出操作只弹出堆顶元素(大顶堆弹最大值,小顶堆弹最小值)。
弹出后规则:
-
不能直接删除堆顶,会破坏完全二叉树连续存储特性
-
用数组最后一个元素覆盖堆顶空位
-
数组长度减一,舍弃末尾无效元素
-
从堆顶开始向下下沉调整,重新维护堆性质
4.2 下沉调整原理(大顶堆)
-
以当前根节点为基准,找到左右子节点中的最大值
-
若当前节点 < 最大子节点,交换位置
-
往下一层继续比对,直到满足堆性质 或 到达叶子节点
4.3 C++ 堆弹出 + 下沉完整代码
在上面MaxHeap类中追加以下方法:
cpp
// 向下下沉调整
void downAdjust(int idx) {
int n = heap.size();
while (true) {
int left = 2 * idx + 1; // 左孩子
int right = 2 * idx + 2; // 右孩子
int maxPos = idx;
// 找三者最大值下标
if (left < n && heap[left] > heap[maxPos]) maxPos = left;
if (right < n && heap[right] > heap[maxPos]) maxPos = right;
// 当前节点已是最大,无需调整
if (maxPos == idx) break;
swap(heap[idx], heap[maxPos]);
// 下沉到最大值位置继续调整
idx = maxPos;
}
}
// 弹出堆顶元素
void pop() {
if (heap.empty()) return;
// 末尾元素覆盖堆顶
heap[0] = heap.back();
// 删除末尾元素
heap.pop_back();
// 从堆顶开始下沉调整
downAdjust(0);
}
💡 五、数据结构终极核心心法
学完堆的所有操作,我们可以领悟所有数据结构的通用本质:
数据结构 = 定义结构性质 + 代码操作维护性质
-
结构定义:规定数据结构长什么样、具备什么约束规则
-
堆:是完全二叉树 + 父子节点大小规则
-
队列:先进先出、队尾入队队头出队
-
-
结构操作 :插入、删除、查找等操作,最终目的都是维护既定性质
-
堆插入上浮、弹出下沉,都是为了不破坏大 / 小顶堆规则
-
队列不允许头部入队,否则就失去队列本身的性质
-
学习数据结构不要死记代码、死背步骤,先理解性质,再理解如何维护性质,一通百通,所有数据结构都能快速上手。
📝 六、知识点总结
-
完全二叉树是堆的底层载体,可通过固定公式实现数组与树形映射;
-
堆分为大顶堆、小顶堆,仅约束父子大小,兄弟无大小关系;
-
插入走末尾追加 + 向上上浮 ,弹出走末尾补位 + 向下下沉;
-
上浮从下往上比对父节点,下沉从上往下比对子节点最大值;
-
数据结构核心思想:定义规则,用操作维护规则。
