堆(Heap)的实现:基于完全二叉树的顺序存储与调整算法(含完整代码)

堆(Heap)的实现:基于完全二叉树的顺序存储与调整算法(含完整代码)

目录

🧠 一、核心认知

⚙️ 二、核心操作本质

🔼 三、向上调整(AdjustUp)

🔽 四、向下调整(AdjustDown)

🌟 设计巧妙点(重点)

📌 三种情况全覆盖

⚠️ 五、关键易错点总结

🚀 六、堆的核心接口实现

🧩 七、内存管理(非常重要)

🧠 八、我的关键问题总结

🧠 九、进阶理解(非常关键)

🚀 十、拓展 & 面试考点


 

🧠 一、核心认知

堆本质不是“树”,而是:

数组 + 完全二叉树的映射关系

---

📌 完全二叉树的数组映射

父节点: (i - 1) / 2
左孩子: 2*i + 1
右孩子: 2*i + 2

---

🌲 结构示意图

数组: [10, 8, 7, 3, 2]

        10
      /    \
     8      7
    / \
   3   2

---

⚙️ 二、核心操作本质

堆只做两件事:

操作| 场景| 本质
向上调整| 插入| 小往下,大往上
向下调整| 删除| 小往下沉,大往上顶

---

🔼 三、向上调整(AdjustUp)

📌 使用场景

插入新元素

---

🧠 核心思想

新节点不断和父节点比较
如果更大 → 上浮

---

✅ 代码实现

void AdjustUp(int* a, int child) {     int parent = (child - 1) / 2;     while (child > 0)     {         if (a[child] > a[parent])         {             Swap(&a[child], &a[parent]);             child = parent;             parent = (child - 1) / 2;         }         else         {             break;         }     } }

---

💡 关键点

- 只影响一条路径(O(log n))
- parent 写在外面是“初始化 + 更新”结构,更清晰

---

🔽 四、向下调整(AdjustDown)

📌 使用场景

删除堆顶 / 堆排序

---

🧠 核心思想

每次选“更大的孩子”,和父节点比较

---

✅ 标准代码(推荐写法)

void AdjustDown(int* a, int n, int parent) {     int child = parent * 2 + 1;     while (child < n)     {         if (child + 1 < n && a[child + 1] > a[child])         {             child++;         }         if (a[child] > a[parent])         {             Swap(&a[child], &a[parent]);             parent = child;             child = parent * 2 + 1;         }         else         {             break;         }     } }

---

🌟 设计巧妙点(重点)

1️⃣ 用左孩子控制循环(覆盖所有情况)
2️⃣ 用右孩子判断做分支(避免越界)
3️⃣ 把“左右比较”压缩成“选最优孩子”

---

📌 三种情况全覆盖

情况| 行为
只有左孩子| 直接用
有左右孩子| 选大的
没孩子| 退出

---

⚠️ 五、关键易错点总结

---

❌ 1. 顺序写反导致越界

a[child + 1] > a[child] && child + 1 < n ❌

✅ 正确:

child + 1 < n && a[child + 1] > a[child]

👉 利用短路避免越界

---

❌ 2. 忘记 break

👉 会死循环

---

❌ 3. 用右孩子做循环条件

while (child + 1 < n) ❌

👉 会漏掉“只有左孩子”的情况

---

🚀 六、堆的核心接口实现

---

✅ 插入

void HPPush(HP* php, int x) {     if (php->size == php->capacity)     {         int newcapacity = php->capacity == 0 ? 4 : php->capacity * 2;         int* tmp = (int*)realloc(php->a, sizeof(int) * newcapacity);         if (tmp == NULL)         {             perror("realloc fail");             exit(-1);         }         php->a = tmp;         php->capacity = newcapacity;     }     php->a[php->size] = x;     php->size++;     AdjustUp(php->a, php->size - 1); }

---

✅ 删除

void HPPop(HP* php) {     Swap(&php->a[0], &php->a[php->size - 1]);     php->size--;     AdjustDown(php->a, php->size, 0); }

---

🧩 七、内存管理(非常重要)

---

🎯 原则

谁 malloc,谁 free

---

⚠️ 易错点

free(php); ❌(如果是栈变量)

---

✅ 推荐写法

void HPDestroy(HP* php)
{
    free(php->a);
    php->a = NULL;
    php->size = php->capacity = 0;
}

---

🧠 八、我的关键问题总结

---

❓1:为什么 AdjustDown 要传 n?

n 用来限制“堆的有效范围”
防止访问已被移除的元素

---

❓2:为什么用左孩子做循环条件?

因为:
有右孩子 ⇒ 一定有左孩子

👉 左孩子能覆盖所有情况

---

❓3:为什么不会越界?

因为:
child + 1 < n && ...

👉 利用短路机制避免访问非法内存

---

❓4:三目运算符怎么用?

child = (child + 1 < n && a[child + 1] > a[child]) ? child + 1 : child;

---

❓5:parent 为什么写在外面?

初始化 + 循环更新
结构更清晰

---

🧠 九、进阶理解(非常关键)

---

🎯 本质

堆的调整不是“改整棵树”
而是:
👉 修复一条路径

---

⏱ 时间复杂度

O(log n)

---

🚀 十、拓展 & 面试考点

---

⭐ 1. 堆排序(必考)

for (int i = n - 1; i > 0; i--)
{
    Swap(&a[0], &a[i]);
    AdjustDown(a, i, 0);
}

---

⭐ 2. TopK问题

👉 用小堆维护前K大元素

---

⭐ 3. 优先级队列

👉 堆的直接应用

---

⭐ 4. 建堆(Heapify)

for (int i = (n - 2) / 2; i >= 0; i--)
{
    AdjustDown(a, n, i);
}

---

⭐ 面试高频问法

- 为什么从 "(n-2)/2" 开始?
- AdjustUp 和 AdjustDown 区别?
- 堆和二叉搜索树区别?
- 堆排序稳定吗?(❌ 不稳定)

---

📌 最终总结

堆 = 数组 + 完全二叉树映射

核心:
插入 → 向上调整
删除 → 向下调整

本质:
局部破坏 → 路径修复

---

🧠 个人反思

总结原理,注意细节传参是否正确

---

Heap.h

#pragma once #define _CRT_SECURE_NO_WARNINGS #pragma once #include<stdio.h> #include<assert.h> #include<stdlib.h> #include<stdbool.h> typedef HPDateType int; typedef struct Heap { HPDateType* a; int size; int capacity; }HP; void Swap(HPDateType* p1, HPDateType* p2); void AdjustUp(HPDateType* a, int child); void AdjustDown(HPDateType* a, int n, int parent); void HPInit(HP* php); void HPDestroy(HP* php); void HPPush(HP* php, HPDateType x); void HPPop(HP* php); HPDateType HPTop(HP* php); bool HPEmpty(HP* php); void CreatHeap(int* a, int n); void HeapSort(int* a, int n); 

Heap.c

#include"Heap.h" void Swap(HPDateType* p1, HPDateType* p2) { assert(p1 && p2); int a = *p1; *p1 = *p2; *p2 = a; } void AdjustUp(HPDateType* a, int child) { int parent = (child - 1) / 2;//这个写在外面为什么好 while (child > 0) { if (a[child] > a[parent]) { Swap(&a[child], &a[parent]); child = parent; parent = (child - 1) / 2; } else { break; } } } void AdjustDown(HPDateType* a, int n, int parent) { int child = parent * 2 + 1; while (child < n) { if (child + 1 < n && a[child] < a[child + 1])//这个顺序不能写反 { child++; } //此时找到最大的孩子 //并且保证了左右都不越界; if (a[parent] < a[child]) { Swap(&a[parent], &a[child]); parent = child; child = parent * 2 + 1; } else { break; } } } void HPInit(HP* php) { php->a = NULL; php->capacity = php->size = 0; } void HPDestroy(HP* php) { free(php->a); php->a = NULL; php->size = php->capacity = 0; } void HPPush(HP* php, HPDateType x) { //先判断满了没有 if (php->size == php->capacity) { int newcapacity = php->capacity == 0 ? 4 : (php->capacity) * 2; HPDateType* temp = (HPDateType*)realloc(php->a, sizeof(HPDateType) * newcapacity); if (temp == NULL) { perror("realloc fail"); exit(-1); } php->a = temp; php->capacity = newcapacity; } php->a[php->size] = x; php->size++; //向上调整 AdjustUp(php->a, php->size-1); } void HPPop(HP* php) { assert(php); assert(php->size != 0); Swap(&(php->a[php->size - 1]), &php->a[0]); php->size--; AdjustDown(php->a, php->size, 0); } HPDateType HPTop(HP* php) { assert(php); assert(php->size != 0); return php->a[0]; } bool HPEmpty(HP* php) { assert(php); return php->size == 0; } //void CreatHeap(int* a; int k) //{ // int i = 1; // for (i; i < k; i++) // { // AdjustUp(a, i); // } //} void CreatHeap(int* a, int ) { // 1. 建堆 int i = (n-2)/2; for (i; i >= 0; i--) { AdjustDown(a, n, i); } } //堆排序 void HeapSort(int* a, int n) { // 1. 建堆 int i = (n - 2) / 2; for (i; i >= 0; i--) { AdjustDown(a, n, i); } // 2. 排序 int end = n - 1; while (end) { //Swap(&a[0], &a[end]); int tmp = a[0]; a[0] = a[end]; a[end] = tmp; AdjustDown(a, end, 0); end--; } } 

 

Read more

[Git] 初识 Git 与安装入门

[Git] 初识 Git 与安装入门

告别文件噩梦:初识 Git 与安装入门 嘿,朋友!不知道你是不是也遇到过这样的情况:你在写一份重要的文档、报告,或者更常见的,一段代码时,为了安全起见,怕改错了回不去,或者想保留不同阶段的版本,于是不得不像这样保存文件: * “报告-v1.doc” * “报告-v2.doc” * “报告-最终版.doc” * “报告-最终版-最终的最终版.doc” * “报告-领导说再改改版.doc” * … 是不是看着就头大?电脑里塞满了各种名字相似的文件,时间一长,你根本分不清“最终版”和“最终的最终版”到底差在哪里,想要找回某个阶段的内容更是难上加难。 文档如此,我们辛辛苦苦写出来的代码更是这样!如果代码没有版本管理,那简直是一场灾难,特别是当需要和别人协作的时候。 别怕!解决这个问题的“神器”来了——它就是版本控制系统。 什么是版本控制系统? 想象一下,版本控制系统就像一个超级详细的“文件时光机”或者“改动日志本”

By Ne0inhk
copilot学生认证2026-github copilot学生认证(手把手教会)

copilot学生认证2026-github copilot学生认证(手把手教会)

1.前言 博主在24年的时候发过一篇copilot认证成功的帖子,当时也是领到了一年的pro 文章链接:github copilot学生认证(手把手一小时成功)-ZEEKLOG博客 如今26年了,copilot的申请增加了一年的时间,博主也进入了研究生生涯,前段时间也是再次进行了申请,现在已经用上了,Pro 版直接解锁无限制基础功能 + 海量高级模型,我的感受是:真香!:   既然官方的申请有变化,咱们教程也得与时俱进,下面就开始手把手教大家如何进行申请copilot学生会员。 2.完善 GitHub 账号基础配置 在Emails里面加入你对应学校的教育邮箱(以edu.cn结尾),打开教育邮箱点击GitHub发送的验证邮件链接,即可完成邮箱认证 3.Github学生认证 完成上述步骤后,打开学生认证申请链接,依旧还是在设置里面,这里也可以用手机操作,因为上传证明材料用手机拍照更方便: 选择身份为学生,下滑填写学校信息,输入学校的英文,最后选择自己的学校教育邮箱,点击continue(还得分享位置) 接下来就是上传证明材料: * 可以使用手机摄像头拍摄,证件

By Ne0inhk

git如何修改密码

1. HTTPS 方式(用户名+密码) 如果您之前用 HTTPS 地址克隆仓库(如 https://github.com/用户名/仓库名.git),密码通常保存在系统凭据中。修改密码需更新凭据: Windows(凭据管理器) 1. 打开 控制面板 → 凭据管理器 → Windows 凭据。 2. 找到对应的 Git 凭据(如 git:https://github.com),编辑或删除后重新输入密码。 macOS(钥匙串访问) 1. 打开 钥匙串访问,搜索 github.com或相关地址。 2. 修改或删除原有凭据,下次 Git 操作会提示输入新密码。 命令行清除缓存(所有系统)

By Ne0inhk

WuliArt Qwen-Image Turbo极速文生图:5分钟搞定高清AI绘画

WuliArt Qwen-Image Turbo极速文生图:5分钟搞定高清AI绘画 1. 为什么你需要一个“真能用”的本地文生图工具? 你是不是也经历过这些时刻: * 在线AI绘画平台排队半小时,生成一张图要等两分钟,还经常卡在“渲染中”; * 下载了几个开源模型,结果发现显存爆了、黑图频出、提示词不认中文、生成效果像蒙了一层雾; * 想试试赛博朋克风格,输入“霓虹雨夜街道”,出来的却是模糊的色块和扭曲的建筑轮廓; * 明明有RTX 4090,却因为模型没优化,只能开半精度跑得比笔记本还慢。 这些问题,不是你的错——而是大多数开源文生图方案,压根没为真实个人GPU环境做过工程打磨。 而今天要聊的这个镜像: WuliArt Qwen-Image Turbo,就是专治这些“水土不服”的本地化解药。它不堆参数、不讲概念,只做一件事:让你的4090真正跑起来,5分钟内从零开始,稳定输出1024×1024高清图。 它不是另一个“又一个SDXL复刻”,而是基于阿里通义千问最新多模态底座Qwen-Image-2512,再叠上Wuli-Art团队实测验证过的Turbo LoRA微

By Ne0inhk