news 2026/5/8 22:26:41

手动插入和删除堆元素(不使用优先队列)

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
手动插入和删除堆元素(不使用优先队列)

1. 堆的基本原理

堆是一棵完全二叉树

  • 大根堆:任意节点的值 $\ge$ 其子节点的值(堆顶最大)。

  • 小根堆:任意节点的值 $\le$ 其子节点的值(堆顶最小)。

存储方式

我们使用一个数组heap[]来存储堆,索引从1开始,这样对于任意节点now

  • 父节点now / 2(或now >> 1)

  • 左子节点now * 2(或now << 1)

  • 右子节点now * 2 + 1(或now << 1 | 1)


2. 核心操作详解

2.1 入堆 (Push) —— 上浮操作

思路

  1. 将新元素插入到堆的末尾(heapsize++)。

  2. 将该元素与其父节点比较。

  3. 如果新元素 > 父节点,则交换两者(swap),并继续向上比较。

  4. 直到无法交换(满足堆性质)或到达堆顶。

void h_push(int k){ heap[++heapsize]=k;//插入末尾 int now=heapsize; while(now>1){ int parents=now>>1;//寻找父节点 if(heap[now]>heap[parents]){//子大则交换 swap(heap[now],heap[parents]); now=parents;//继续向上 }else break;//满足性质,退出 } }

2.2 出堆 (Pop) —— 下沉操作

思路

  1. 取出堆顶元素(即最大值)。

  2. 为了保持完全二叉树的形态,将堆底元素移动到堆顶,并将堆长度减 1。

  3. 从新的堆顶开始,与其左右子节点中较大的那个进行比较。

  4. 如果当前节点 < 较大的子节点,则交换,并继续向下比较。

  5. 直到无法交换或沉到底部。

注意:在判断左右儿子大小时,要确保逻辑独立,先选出最大的儿子,再和父节点比较。

int h_pop(){ int ans=heap[1];//记录最大值 swap(heap[1],heap[heapsize]);//堆尾移至堆顶 heapsize--;//长度减1 int now=1; while(now*2<=heapsize){//只要有左儿子 int nxt=now*2;//默认较大的儿子是左儿子 //如果右儿子存在,且右儿子>左儿子,则较大的儿子是右儿子 if(nxt+1<=heapsize&&heap[nxt+1]>heap[nxt]) nxt++; //比较:如果当前节点<较大的儿子,则下沉 if(heap[now]<heap[nxt]){ swap(heap[now],heap[nxt]); now=nxt;//继续向下 }else break;//满足堆性质,停止 } return ans; }

3. 完整代码实现

#include<iostream> #include<algorithm> using namespace std; const int MAXN=100;//适当扩大数组防止越界 int heap[MAXN]; int heapsize=0; //打印堆(调试用) void print(){ for(int i=1;i<=heapsize;i++)cout<<heap[i]<<" "; cout<<endl; } //入堆:上浮 void h_push(int k){ heap[++heapsize]=k; int now=heapsize; while(now>1){ int parents=now>>1; if(heap[now]>heap[parents]){ swap(heap[now],heap[parents]); now=parents; }else break; } } //出堆:下沉 int h_pop(){ if(heapsize==0)return -1;//空堆保护 int ans=heap[1]; swap(heap[1],heap[heapsize]); heapsize--; int now=1; while(now*2<=heapsize){ int nxt=now*2;//左儿子 //判断是否存在右儿子且右儿子更大 if(nxt+1<=heapsize&&heap[nxt+1]>heap[nxt]) nxt++; //与较大的儿子比较 if(heap[nxt]>heap[now]){ swap(heap[nxt],heap[now]); now=nxt; }else break; } return ans; } int main(){ //模拟输入9个数字 cout<<"Please enter 9 integers:"<<endl; for(int i=1;i<=9;i++){ int tmp; cin>>tmp; h_push(tmp); } cout<<"Current Heap: "; print(); cout<<"Popped Max Element: "<<h_pop()<<endl; cout<<"Heap after Pop: "; print(); return 0; }

4. 复杂度分析

  • 时间复杂度

    • h_push: 最坏情况是从叶子浮到根,高度为 log N,所以是 O(log N)。

    • h_pop: 最坏情况是从根沉到叶子,高度为 log N,所以是 O(log N)。

  • 空间复杂度:O(N),用于存储数组。

5. 总结

手动实现堆的关键在于理解“完全二叉树”的数组映射关系。

  • 上浮 (Up):用于插入,确保新来的“大将”能坐到正确的高位。

  • 下沉 (Down):用于删除,旧王退位,选出的新王可能能力不足,需要退居到合适的位置。

掌握这套逻辑后,不仅能手写优先队列,还能轻松解决“Top K 问题”或手写“堆排序”。

附原始代码:

/* //手动实现堆操作 堆的增加 大根堆 #include <iostream> using namespace std; int heap[10];//堆 int heapsize;//堆长度 void print(){ for(int i=1;i<=9;i++) cout<<heap[i]<<" "; } void h_push(int k){ heap[++heapsize]=k; int now=heapsize;//当前存入元素在堆的末尾 int parents; while(now>1){//当now=1时,已经没有父节点 parents=now/2; if(heap[now]>heap[parents])//当now节点大于父节点就交换彼此位置 { swap(heap[now],heap[parents]); now=parents; } else break;//当不大于父节点时就直接退出循环了 } } int main() { for(int i=1;i<=9;i++){ int tmp; cin>>tmp; h_push(tmp);//每次给堆增加一个元素 } print(); return 0; } */ //手动实现堆操作 堆的删除 大根堆 先创建堆,然后再删除堆顶元素,删除堆顶元素需要先把堆顶和堆底交换然后heapsize--,就删除了原始堆顶,接下来需要把堆下沉,即把堆顶元素和子节点比较,如果子节点大于当前元素就交换,递归下去,如果大于等于就结束,每次输出被弹出的元素 #include <iostream> using namespace std; int heap[10]; int heapsize; int h_pop(){//删除堆顶元素 int ans=heap[1]; swap(heap[1],heap[heapsize]);//先把堆顶和堆底交换 heapsize--;//删除堆底元素 //接下来从堆顶now节点开始与儿子节点进行比较,因为是大根堆,所以与儿子中较大值进行交换(如果now节点小于now儿子较大值) int now=1;//堆顶 while(now*2<=heapsize){//左儿子小于堆长 int nxt=now*2;//now节点左儿子 if(nxt+1<=heapsize && heap[nxt+1]>heap[nxt]){//如果右儿子也小于等于堆长,且右儿子大于左儿子 nxt++;//那么较大值为右儿子 否则较大值就还是左儿子 } if(heap[nxt]>heap[now]){//如果儿子大于now节点 swap(heap[nxt],heap[now]); now=nxt; } else break;//now节点大于儿子中较大值结束循环 } return ans; } void print(){ for(int i=1;i<=heapsize;i++) cout<<heap[i]<<" "; } void h_push(int k){ heap[++heapsize]=k; int now=heapsize; while(now>1){ int parents=now>>1; if(heap[now]>heap[parents]){ swap(heap[now],heap[parents]); now=parents; } else break; } } int main(){ for(int i=1;i<=9;i++){ int tmp; cin>>tmp; h_push(tmp); } print(); cout<<endl; cout<<h_pop()<<endl;//删除堆顶元素 print(); }
版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/4/28 6:29:28

Linly-Talker支持动态批处理,提高GPU吞吐量

Linly-Talker支持动态批处理&#xff0c;提高GPU吞吐量 在虚拟主播直播间里&#xff0c;成百上千名观众同时发问&#xff1a;“今天推荐什么股票&#xff1f;”“你能唱首歌吗&#xff1f;”“用四川话说一遍祝福语。”如果每个问题都要等系统逐个处理、逐个生成视频回应&#…

作者头像 李华
网站建设 2026/5/4 23:12:43

Linly-Talker数字人系统:一键生成口型同步讲解视频

Linly-Talker数字人系统&#xff1a;一键生成口型同步讲解视频 在教育机构忙着录制网课、电商主播通宵直播、客服团队疲于应对重复咨询的今天&#xff0c;一个共通的痛点浮现出来&#xff1a;优质内容生产太慢&#xff0c;人力成本太高。有没有可能让“另一个我”替我讲话&…

作者头像 李华
网站建设 2026/5/2 20:22:08

Linly-Talker支持多线程推理,高并发场景从容应对

Linly-Talker&#xff1a;高并发数字人对话系统的多线程推理实践 在虚拟主播直播间里&#xff0c;成百上千的观众同时提问&#xff1b;企业客服系统中&#xff0c;数十名员工正通过AI助手处理客户咨询&#xff1b;在线教育平台上&#xff0c;数百个学生正在与个性化AI讲师互动……

作者头像 李华
网站建设 2026/5/7 10:47:32

Win xp激活

链接&#xff1a;https://pan.quark.cn/s/15877e4b435a器。

作者头像 李华
网站建设 2026/4/30 14:49:52

AI客服升级方案:传统IVR向Linly-Talker智能交互演进

AI客服升级方案&#xff1a;传统IVR向Linly-Talker智能交互演进 在银行热线中反复按键、听机械女声播报“请按1查询余额”&#xff0c;这种体验对今天的用户来说早已过时。当人们习惯了与Siri、小爱同学自然对话&#xff0c;再回到层层菜单的语音系统&#xff0c;就像从智能手机…

作者头像 李华
网站建设 2026/5/4 9:50:49

编程世界时间对象的最小公倍数(闲话Float-Time)

五花八门赖算力&#xff0c;数值直传操现代。 笔记模板由python脚本于2025-12-20 23:48:53创建&#xff0c;本篇笔记适合喜欢日期时间玩味的coder翻阅。 学习的细节是欢悦的历程 博客的核心价值&#xff1a;在于输出思考与经验&#xff0c;而不仅仅是知识的简单复述。 Python官…

作者头像 李华