【数据结构篇C++实现】- 堆
文章目录
- 🚀一、堆的原理精讲
- ⛳(一)堆的概念
- ⛳(二)看图识最大堆
- ⛳(三)详解堆是用数组表示的树
- 🚀二、堆的向下调整算法
- 🚀三、堆的向上调整算法
- 🚀四、将任意一棵树建成堆
- 🚀五、堆的算法实现
- 1.堆的结构体定义
- 2.初始化堆
- 3.判断堆是否为空
- 4.插入新元素
- 5.堆顶元素出列(删除元素),同时获取堆顶数据
- 6.遍历打印堆
- 7.销毁堆
- 🚀六、拓展
- ⛳(一)用最大堆或最小堆来实现优先队列
- ⛳(二)堆排序算法
- ⛳(三)top - k问题
🚀一、堆的原理精讲
⛳(一)堆的概念

堆(Heap):一种有特殊用途的数据结构——用来在一组变化频繁(发生增删查改的频率较高)的数据集中查找最值
- 堆中某个节点的值总是不大于或不小于其父节点的值;
- 堆总是一棵完全二叉树。
- 每个节点最多可以有两个节点
- 根结点的键值是所有堆结点键值中最大(小)者,且每个结点的值都比其孩子的值大(小),这样的堆叫最大(小)堆
- 除了根节点没有兄弟节点,最后一个左子节点可以没有兄弟节点,其他节点必须有兄弟节点
- 这里需要注意的是:在多个子树中,并不是说其中一个子树的父结点一定大于另一个子树的儿子结点。
实际上,先弄懂树这种数据结构再来看堆就会简单很多了,堆是你见过的最有个性的树!它是用数组表示的树。所以逻辑结构上其实是一棵树,物理结构上是一维数组,属于非线性结构
本篇博客默认你没有事先学过树,通俗易懂,讲解知识详细,即使不知道完全二叉树,也能辨认出堆,即使没学过树也能搞懂堆,本篇以最大堆进行讲解
⛳(二)看图识最大堆

图1:因为93 > 87,而且82无左子节点却有右子节点,不满足除了根节点和最后一个左子节点可以没有兄弟节点,其他节点必须要有兄弟节点的规定,所以图1不是堆
图2:82是最后一个左子节点,但92不是,而且没有兄弟节点,所以图2也不是堆
图3:满足条件,为最大堆
⛳(三)详解堆是用数组表示的树

i为下标,由上图我们能看出规律:
- i的左子节点:2i+1
- i的右子节点:2i+2
- i的父节点:(i-1)/2
这也就是我们在代码实现的时候需要注意的地方
🚀二、堆的向下调整算法
向下调整:是让调整的结点与其孩子节点进行比较,若想将其调整为小堆,那么根结点的左右子树必须都为小堆,
若想将其调整为大堆,那么根结点的左右子树必须都为大堆,有些文章说单纯满足为堆就行这种说法是不对的

向下调整算法的基本思想(大部分搜到的都是以最小堆为例,我就继续以最大堆为例了):
- 从根结点处开始,选出左右孩子中值较大的孩子。
- 让大的孩子与其父亲进行比较。
- 若大的孩子比父亲还大,则该孩子与其父亲的位置进行交换。并将原来大的孩子的位置当成父亲继续向下进行调整,直到调整到叶子结点为止。
- 若大的孩子比父亲小,则不需处理了,调整完成,整个树已经是大堆了。
代码实现:
/*向下调整将当前的节点和子节点调整成最大堆*/
void adjustDown(Heap &heap, int index) { int cur=heap.arr[index];//当前待调整的节点int parent,child;
/*判断否存在大于当前节点子节点,如果不存在 ,则堆本身是平衡的,不需要调整;如果存在,则将最大的子节点与之交换,交换后,如果这个子节点还有子节点,则要继续按照同样的步骤对这个子节点进行调整*/for(parent=index; (parent*2+1)<heap.size; parent=child) {child=parent*2+1;//取两个子节点中的最大的节点if(((child+1)<heap.size)&&(heap.arr[child]<heap.arr[child+1])) {child++;}//判断最大的节点是否大于当前的父节点if(cur>=heap.arr[child]) {//不大于,则不需要调整,跳出循环break;}else {//大于当前的父节点,进行交换,然后从子节点位置继续向下调整heap.arr[parent]=heap.arr[child];heap.arr[child]=cur;}}
}
🚀三、堆的向上调整算法
向上调整:是让调整的结点与其父亲结点进行比较,当我们已经有一个堆,我们需要在堆的末尾插入数据,再对其进行调整,使其任然保持堆的结构,这里我们就需要用到堆的向上调整算法

向上调整算法的基本思想:
- 将目标结点与其父结点比较
- 若目标结点的值比其父结点的值大,则交换目标结点与其父结点的位置
- 将原目标结点的父结点当作新的目标结点继续进行向上调整。若目标结点的值比其父结点的值大,则停止向上调整,此时该树已经是大堆了。
代码实现:
/*将当前的节点和父节点调整成最大堆*/
void adjustUp(Heap &heap, int index) {if(index<0 || index >= heap.size) {//大于堆的最大值直接 returnreturn;}while(index>0){ int temp = heap.arr[index];int parent = (index - 1) / 2;if(parent >= 0){//如果索引没有出界就执行想要的操作if(temp > heap.arr[parent]){heap.arr[index] = heap.arr[parent];heap.arr[parent] = temp;index = parent;} else {//如果已经比父亲小 直接结束循环break;}} else {//越界结束循环break;}}
}
🚀四、将任意一棵树建成堆
前面我们已经知道,堆的向下调整算法是在基于根节点左右子树已经为最大堆或最小堆的前提下才能操作的;而堆的向上调整算法是在我们已经建好了一个最大堆或最小堆,用于插入元素后的调整
那么如何将任意一棵树建成最大(小)堆呢,这里我就改为:如何在数组中快速创建堆:
我们把向下调整算法倒着来,我们知道,满足堆,必须左右子树都是大堆或者小堆,我们可以利用这个思想,从下往上倒着走,从第一个非叶子节点开始,通过数组下标的控制,把它当做根去向下调整,依次往上,直到把当前路径调整成符合条件的大堆或者小堆即可
还是以最大堆为例讲解,可以看到,根节点右子树不是一个最大堆,所以不能直接用向下调整算法

- 首先我们需要找到最后一个结点的父结点如图(a),我们找到的结点是 87,然后找出该结点的最大子节点与自己比较,若该子节点比自身大,则将两个结点交换.。图(a)中,87 比左子节点 95 小,则交换之.如图(b)所示

- 我们移动到前一个父结点 93,如图©所示。同理做第一步的比较操作,结果不需要交换

- 继续移动结点到前一个父结点 82,82 小于右子节点 95,则 82 与 95 交换,如图(d)所示,82 交换后,其值小于左子节点,不符合最大堆的特点,故需要继续向下调整,如图(e)所示

- 所有节点交换完毕,最大堆构建完成
🚀五、堆的算法实现
1.堆的结构体定义
#define DEFAULT_CAPCITY 128typedef struct _Heap{int *arr; //存储堆元素的数组int size; //当前已存储的元素个数int capacity; //当前存储的容量
}Heap;
2.初始化堆
/*初始化堆*/
bool initHeap(Heap &heap, int *orginal, int size){//orginal:原数据 size:数据个数 heap:堆int capacity = DEFAULT_CAPCITY>size? DEFAULT_CAPCITY:size;heap.arr = new int[capacity]; //分配堆中数组空间if(!heap.arr) return false;heap.capacity = capacity;heap.size = 0;//如果存在原始数据则构建堆if(size>0){memcpy(heap.arr, orginal, size*sizeof(int));heap.size = size;buildHeap(heap);} else {heap.size = 0;}
return true;
}/* 从最后一个父节点(size/2-1 的位置)逐个往前调整所有父节点(直到根节点),确保每一个父节点都是一个最大堆,最后整体上形成一个最大堆 */
void buildHeap(Heap &heap){int i;for(i=heap.size/2-1; i>=0; i--){adjustDown(heap, i);}
}
解释:
- 初始化堆操作即为之前讲的将任意一棵树建成堆
- orginal为函数外传入的原数据,然后通过初始化将其建成堆
- buildHeap即为以最后一个非叶子节点开始的向下调整算法
3.判断堆是否为空
/*判断堆是否为空*/
bool isEmpty(Heap &heap){if(heap.size<1) return true;return false;
}
4.插入新元素
/*最大堆尾部插入节点,同时保证最大堆的特性*/
bool insert(Heap &heap, int value) {if (heap.size == heap.capacity) {fprintf(stderr, "栈空间耗尽!\n");return false;}int index = heap.size;heap.arr[heap.size++] = value;adjustUp(heap, index);return true;
}
5.堆顶元素出列(删除元素),同时获取堆顶数据
如果我们将堆顶的元素删除,那么顶部有一个空的节点,怎么处理?
我们先将堆顶的数据与最后一个数据交换位置,删除最后一个结点,然后再修复堆属性。
原因:我们若是直接删除堆顶的数据,那么原堆后面数据的父子关系就全部打乱了,需要全体重新建堆,时间复杂度为O(N)。若是用上述方法,那么只需要对堆进行一次向下调整即可,因为此时根结点的左右子树都是大堆,我们只需要在根结点处进行一次向下调整即可,时间复杂度为O ( log ( N ) )

代码实现:
/* 删除堆顶元素,获取堆顶的数据*/
bool pop(PriorityQueue &pq, DataType &value) {if (isEmpty(pq)) return false;value = pq.arr[0];pq.arr[0] = pq.arr[--pq.size];//heap.arr[0] = heap.arr[heap.size-1];//heap.size--;adjustDown(pq, 0);// 向下执行堆调整return true;
}
6.遍历打印堆
打印堆中的数据,这里用了两种打印格式。第一种打印格式是按照堆的物理结构进行打印,即打印为一排连续的数字。第二种打印格式是按照堆的逻辑结构进行打印,即打印成树形结构。
//求结点数为n的二叉树的深度
int depth(int n) {assert(n >= 0);if (n>0) {int m = 2;int hight = 1;while (m < n + 1) {m *= 2;hight++;}return hight;} else {return 0;}
}//打印堆
void HeapPrint(Heap* php) {assert(php);//按照物理结构进行打印int i = 0;for (i = 0; i < php->size; i++){printf("%d ", php->a[i]);}printf("\n");//按照树形结构进行打印int h = depth(php->size);int N = (int)pow(2, h) - 1;//与该二叉树深度相同的满二叉树的结点总数int space = N - 1;//记录每一行前面的空格数int row = 1;//当前打印的行数int pos = 0;//待打印数据的下标while (1) {//打印前面的空格int i = 0;for (i = 0; i < space; i++) {printf(" ");}//打印数据和间距int count = (int)pow(2, row - 1);//每一行的数字个数while (count--)//打印一行{printf("%02d", php->a[pos++]);//打印数据if (pos >= php->size)//数据打印完毕{printf("\n");return;}int distance = (space + 1) * 2;//两个数之间的空格数while (distance--)//打印两个数之间的空格{printf(" ");}}printf("\n");row++;space = space / 2 - 1;}
}
7.销毁堆
/*销毁堆*/
void destroy(Heap &heap){if(heap.arr) delete[] heap.arr;heap->arr = NULL;//及时置空heap->size = 0;//元素个数置0heap->capacity = 0;//容量置0
}
🚀六、拓展
⛳(一)用最大堆或最小堆来实现优先队列
操作系统内核作业调度是优先队列的一个应用实例,它根据优先级的高低而不是先到先服务的方式来进行调度;

如果最小键值元素拥有最高的优先级,那么这种优先队列叫作升序优先队列(即总是先删除最小的元素),类似的,如果最大键值元素拥有最高的优先级,那么这种优先队列叫作降序优先队列(即总是先删除最大的元素);由于这两种类型是完全对称的,所以只需要关注其中一种,如升序优先队列
⛳(二)堆排序算法
堆排序(Heapsort)是指利用堆这种数据结构所设计的一种排序算法,它是选择排序的一种。可以利用数组的特点快速定位指定索引的元素.
(选择排序工作原理 - 第一次从待排序的数据元素中选出最小(或最大)的一个元素,存放在序列的起始位置,然后再从剩余的未排序元素中寻找到最小(大)元素,然后放到已排序的序列的末尾。以此类推,直到全部待排序的数据元素的个数为零)
/* 实现堆排序 */
void heapSort(Heap &heap){if (heap.size<1) return ;while(heap.size>0){int tmp = heap.arr[0];heap.arr[0] = heap.arr[heap.size-1];heap.arr[heap.size-1] = tmp;heap.size--;adjustDown(heap, 0);// 向下执行堆调整}
}
⛳(三)top - k问题
若要从N个数字中取得最小的K个数字,则需要创建大小为K的大堆来获取。若要从N个数字中取得最大的K个数字,则需要创建大小为K的小堆来获取。
拜托,面试别再问我TopK了!!!_架构师之路-CSDN博客
相关文章:
【数据结构篇C++实现】- 堆
文章目录🚀一、堆的原理精讲⛳(一)堆的概念⛳(二)看图识最大堆⛳(三)详解堆是用数组表示的树🚀二、堆的向下调整算法🚀三、堆的向上调整算法🚀四、将任意一棵…...
C++笔试题
作用域运算符(::)的作用:1.存在具有相同名称的局部变量时,访问全局变量。2.在类之外定义类相关函数。3.访问类的静态变量。4.在多重继承的情况下,如果两个基类中存在相同的变量名,可以使用作用域运算符来进行区分。5.限定成员函数…...
【Python】基本语法
数据类型 通过 print(type(x)) 可以输出 x 的数据类型,type() 函数可以获取数据类型 整数 a 10 print(type(a)) 浮点数 a 0.5 print(type(a)) 字符串 a hello print(type(a)) 获取字符串长度 a hello print(len(a))字符串拼接 a hello b world prin…...
用栈实现队列(图示超详解哦)
全文目录引言用栈实现队列题目介绍思路简述实现栈的部分队列的部分创建队列判断队列是否为空在队列尾入在队列头出访问队头元素释放队列总结引言 在上一篇文章中,我们了解了用两个队列实现栈。在这篇问章中将继续介绍用两个栈实现队列的OJ练习: 用栈实现…...
Spring - Spring 注解相关面试题总结
文章目录01. Spring 配置方式有几种?02. Spring 如何实现基于xml的配置方式?03. Spring 如何实现基于注解的配置?04. Spring 如何基于注解配置bean的作用范围?05. Spring Component, Controller, Repository, Service 注解有何区别…...
【数据结构】实现二叉树的基本操作
目录 1. 二叉树的基本操作 2. 具体实现 2.1 创建BinaryTree类以及简单创建一棵树 2.2 前序遍历 2.3 中序遍历 2.4 后序遍历 2.5 层序遍历 2.6 获取树中节点的个数 2.7 获取叶子节点的个数 2.8 获取第K层节点的个数 2.9 获取二叉树的高度 2.10 检测值为val的元素是否…...
代码随想录算法训练营第五十二天| ● 300.最长递增子序列 ● 674. 最长连续递增序列 ● 718. 最长重复子数组
300.最长递增子序列 看完题后的思路 dp[i] [0,i]子数组中,以nums[i]结尾的子序列的长度 dp[i]dp[j]1 j从i-1向0遍历,在所有nums[j]<nums[i]中dp[j]最大 初始化 dp[0]1 代码 class Solution {public int lengthOfLIS(int[] nums) {if (nums.length0){return 0;}int[] dpne…...
手机验证发送及其验证(基于springboot+redis)保姆级
在Java开发中,发送手机验证码时需要考虑以下几个问题: 验证码的有效期:验证码应该有一定的有效期,一般设置为几分钟或者十几分钟。过期的验证码应该被认为是无效的,不能用于验证用户身份。手机号码格式的校验…...
【JavaScript 逆向】数美滑块逆向分析
声明本文章中所有内容仅供学习交流,相关链接做了脱敏处理,若有侵权,请联系我立即删除!案例目标验证码:aHR0cHM6Ly93d3cuaXNodW1laS5jb20vbmV3L3Byb2R1Y3QvdHcvY29kZQ以上均做了脱敏处理,Base64 编码及解码方…...
多任务之线程
文章目录一、多任务是什么?二、多任务-线程四、通过继承Tread类完成创建线程五、资源竞争六、同步与互斥锁七、对峙与避免死锁一、多任务是什么? 多个函数同时执行一件事情就是多任务,没有多任务的时候任务执行都是按照顺序的,而…...
(数字图像处理MATLAB+Python)第二章数字图像处理基础-第二节:色度学基础与颜色模型
文章目录一:颜色匹配二:CIE 1931-RGB系统三:CIE 1931标准色度系统四:CIE 1976Lab均匀颜色空间五:孟塞尔表色系统(1)孟塞尔明度(Value,记为V)(2)孟塞尔彩度(Ch…...
【华为OD机试 2023最新 】 网上商城优惠活动(C++)
文章目录 题目描述输入描述输出描述备注用例题目解析C++题目描述 某网上商场举办优惠活动,发布了满减、打折、无门槛3种优惠券,分别为: 每满100元优惠10元,无使用数限制,如100199元可以使用1张减10元,200299可使用2张减20元,以此类推;92折券,1次限使用1张,如100元,…...
记一次CentOS 8 部署packstack部署OpenStack失败案例,请直接看最后
首先你需要一台安装好CentOS8 的虚拟机,相关参数如图。两块网卡,网卡1 NAT IP 192.168.100.100 GW192.168.100.2 网卡2 可不做配置。能ping通百度。创建完成虚拟机记得打好快照。 开机编辑基本配置环境变量 [rootlocalhost ~]# nmcli connection show NA…...
【2023春招】美团技术岗笔试10min+AK
随手投递了前端&移动端,笔试2道算法+选择+行测题(为什么笔试会有行测题?) 目录 T1-火车栈结构 题意 输入描述 输出描述 样例 AC_Code T2-春游...
Echarts实现图表自适应屏幕分辨率
一:简介 之前做项目的时候要实现echarts图表随浏览器窗口大小变化而改变,echarts本身提供了一个resize()方法,然后我们需要用一个函数实现浏览器窗口监听,最初我选用的是window.onresize方法,当页面只有一个图表时可以…...
【2023年第十一届泰迪杯数据挖掘挑战赛】B题:产品订单的数据分析与需求预测 建模及python代码详解 问题一
相关链接 【2023年第十一届泰迪杯数据挖掘挑战赛】B题:产品订单的数据分析与需求预测 建模及python代码详解 问题一 【2023年第十一届泰迪杯数据挖掘挑战赛】B题:产品订单的数据分析与需求预测 建模及python代码详解 问题二 1 题目 一.问题…...
【蓝桥杯嵌入式】第十三届蓝桥杯嵌入式国赛客观题以及详细题解
题1 概念题。 USRAT:异步串口通信,常用于数据传输;SW-DP:SWD 的全称应该是 The Serial Wire Debug Port (SW-DP),也就是串行调试端口,是 >ARM 目前支持的两种调试端口之一;JTAG-DP:另一个调试…...
java中Map遍历的4种方式
目录 1、map.entrySet()方式 2、map.keySet()方式 3、map.values()方式 4、forEach方式 本文以如下map案例: Map<String, String> map new HashMap<>(); map.put("student1", "张三"); map.put("student2", "…...
GCC 编译器的主要组件和编译过程
主要组件: 分析器:分析器将源语言程序代码转换为汇编语言。因为要从一种格式转换为另一种格式(C到汇编),所以分析器需要知道目标机器的汇编语言。 汇编器:汇编器将汇编语言代码转换为CPU可以执行字节码。 …...
蓝桥杯冲刺 - week2
文章目录💬前言🌲day1最大和 (DP质因数分解)901. 滑雪 - 记忆化搜索🌲day21227. 分巧克力 - 二分🌲day31221. 四平方和 - 空间换时间1230. K倍区间🌲day41076. 迷宫问题 - 路径2017-迷宫-填空🌲day5848. 有…...
Python|GIF 解析与构建(5):手搓截屏和帧率控制
目录 Python|GIF 解析与构建(5):手搓截屏和帧率控制 一、引言 二、技术实现:手搓截屏模块 2.1 核心原理 2.2 代码解析:ScreenshotData类 2.2.1 截图函数:capture_screen 三、技术实现&…...
手游刚开服就被攻击怎么办?如何防御DDoS?
开服初期是手游最脆弱的阶段,极易成为DDoS攻击的目标。一旦遭遇攻击,可能导致服务器瘫痪、玩家流失,甚至造成巨大经济损失。本文为开发者提供一套简洁有效的应急与防御方案,帮助快速应对并构建长期防护体系。 一、遭遇攻击的紧急应…...
Nginx server_name 配置说明
Nginx 是一个高性能的反向代理和负载均衡服务器,其核心配置之一是 server 块中的 server_name 指令。server_name 决定了 Nginx 如何根据客户端请求的 Host 头匹配对应的虚拟主机(Virtual Host)。 1. 简介 Nginx 使用 server_name 指令来确定…...
Java 加密常用的各种算法及其选择
在数字化时代,数据安全至关重要,Java 作为广泛应用的编程语言,提供了丰富的加密算法来保障数据的保密性、完整性和真实性。了解这些常用加密算法及其适用场景,有助于开发者在不同的业务需求中做出正确的选择。 一、对称加密算法…...
GC1808高性能24位立体声音频ADC芯片解析
1. 芯片概述 GC1808是一款24位立体声音频模数转换器(ADC),支持8kHz~96kHz采样率,集成Δ-Σ调制器、数字抗混叠滤波器和高通滤波器,适用于高保真音频采集场景。 2. 核心特性 高精度:24位分辨率,…...
Linux C语言网络编程详细入门教程:如何一步步实现TCP服务端与客户端通信
文章目录 Linux C语言网络编程详细入门教程:如何一步步实现TCP服务端与客户端通信前言一、网络通信基础概念二、服务端与客户端的完整流程图解三、每一步的详细讲解和代码示例1. 创建Socket(服务端和客户端都要)2. 绑定本地地址和端口&#x…...
pikachu靶场通关笔记19 SQL注入02-字符型注入(GET)
目录 一、SQL注入 二、字符型SQL注入 三、字符型注入与数字型注入 四、源码分析 五、渗透实战 1、渗透准备 2、SQL注入探测 (1)输入单引号 (2)万能注入语句 3、获取回显列orderby 4、获取数据库名database 5、获取表名…...
Linux部署私有文件管理系统MinIO
最近需要用到一个文件管理服务,但是又不想花钱,所以就想着自己搭建一个,刚好我们用的一个开源框架已经集成了MinIO,所以就选了这个 我这边对文件服务性能要求不是太高,单机版就可以 安装非常简单,几个命令就…...
leetcode73-矩阵置零
leetcode 73 思路 记录 0 元素的位置:遍历整个矩阵,找出所有值为 0 的元素,并将它们的坐标记录在数组zeroPosition中置零操作:遍历记录的所有 0 元素位置,将每个位置对应的行和列的所有元素置为 0 具体步骤 初始化…...
从实验室到产业:IndexTTS 在六大核心场景的落地实践
一、内容创作:重构数字内容生产范式 在短视频创作领域,IndexTTS 的语音克隆技术彻底改变了配音流程。B 站 UP 主通过 5 秒参考音频即可克隆出郭老师音色,生成的 “各位吴彦祖们大家好” 语音相似度达 97%,单条视频播放量突破百万…...
