【C语言】数据结构——小堆实例探究
💗个人主页💗
⭐个人专栏——数据结构学习⭐
💫点击关注🤩一起学习C语言💯💫
导读:
我们在前面学习了单链表和顺序表,以及栈和队列。
今天我们来学习小堆。
关注博主或是订阅专栏,掌握第一消息。
1. 堆的概念及结构
现实中我们通常把堆(一种二叉树)使用顺序结构的数组来存储,需要注意的是这里的堆和操作系统虚拟进程地址空间中的堆是两回事,一个是数据结构,一个是操作系统中管理内存的一块区域分段。
1.1 什么是堆
堆是一种特殊的数据结构,它可以看做是一个完全二叉树(或者近似二叉树),其中每个节点的值都大于等于(或小于等于)其子节点的值。在一个最大堆中,根节点的值是最大的;在一个最小堆中,根节点的值是最小的。

1.2 堆的特点
堆的主要特点是:每个节点的值都大于等于(或小于等于)其子节点的值。这种特点使得堆可以快速找到最大(或最小)的元素。另外,堆还可以用于排序和优先队列等应用。
堆中兄弟节点的值之间没有关联。在堆中,节点之间的关系仅由其在树中的位置决定。
1.3 堆的结构
堆通常使用数组来实现,数组的下标代表节点在堆中的位置。根据节点在数组中的位置,可以通过简单的计算得到其父节点、左子节点和右子节点的位置。这样,在堆中插入一个新元素、删除堆顶的元素或者调整堆的结构时,只需要对数组进行简单的操作,而不需要改变整个堆的结构。
2. 堆的实现
我们需要创建两个 C文件: study.c 和 Heap.c,以及一个 头文件: Heap.h。
头文件来声明函数,一个C文件来定义函数,另外一个C文件来用于主函数main()进行测试。
堆的常见操作包括插入元素、删除堆顶元素、堆化(调整堆的结构使其满足堆的特点)等。其中,插入元素和删除堆顶元素的时间复杂度为O(logn),堆化的时间复杂度为O(nlogn)。
3. 代码实现
3.1 定义结构体
Heap.h:
typedef int HPDataType;
typedef struct Heap
{HPDataType* a;int size; //记录数组内的有效数据int capacity; //记录数组空间大小
}HP;
3.2 堆的初始化
Heap.h:
//堆的初始化
void HeapInit(HP* php);
Heap.c:
//堆的初始化
void HeapInit(HP* php)
{//各值初始化为0assert(php);php->a = NULL;php->size = 0;php->capacity = 0;
}
3.3 堆的销毁
我们的数组空间是用malloc函数开辟的,使用完之后需要进行释放。
Heap.h:
// 堆的销毁
void HeapDestroy(HP* php);
Heap.c:
// 堆的销毁
void HeapDestroy(HP* php)
{assert(php);free(php->a);php->a = NULL;php->size = 0;php->capacity = 0;
}
3.4 向上调整父节点与子节点
在出入数组后,我们需要对数组进行调整,以实现堆的结构特点。
在数组中,下标*2+1就是他的子节点,同样的下标-1/2就是他的父节点。
Heap.h:
//向上调整父节点与子节点
void AdjustUp(HPDataType* a, int child);
Heap.c:
//交换父节点和子节点的值
void Swap(int* x, int* y)
{int tmp = *x;*x = *y;*y = tmp;
}
//向上调整父节点与子节点
void AdjustUp(HPDataType* a, int child)
{assert(a);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;}}
}

3.5 堆的插入
Heap.h:
// 堆的插入
void HeapPush(HP* php, HPDataType x);
Heap.c:
// 堆的插入
// 堆的插入
void HeapPush(HP* php, HPDataType x)
{assert(php);//首先检查数组容量是否足够if (php->size == php->capacity){int newcapacity = php->capacity == 0 ? 4 : php->capacity * 2;HPDataType* tmp = (HPDataType*)realloc(php->a, sizeof(HPDataType) * newcapacity);if (tmp == NULL){perror("realloc fail");return;}php->a = tmp;php->capacity = newcapacity;}php->a[php->size] = x;//在插入数值后需要检查是否要进行调整AdjustUp(php->a, php->size);php->size++;
}


3.6 向下调整父节点与子节点
删除堆是删除堆顶的数据,但是我们无法直接删除第一个元素,这有极大的可能会使我们的堆崩溃,不再具有堆的特点,而在删除之后把其它数值都往前移动,再进行调整是一项很大的工作量。
所以我们可以将堆顶的数据根最后一个数据一换,然后删除数组最后一个数据,再进行向下调整算法。将当前的根数值调整到符合堆特点的位置去。
Heap.h:
//向下调整父节点与子节点
void AdjustDown(int* a, int size, int parent);
Heap.c:
//向下调整父节点与子节点
void AdjustDown(int* a, int size, int parent)
{assert(a);int child = parent * 2 + 1;//找到孩子节点while (child < size){//找孩子中较小的一个if ((a[child] > a[child + 1]) && (child + 1) < size){child += 1;}//判断两个大小进行交换if (a[parent] > a[child]){Swap(&a[child], &a[parent]);parent = child;child = parent * 2 + 1;}else{break;}}
}
3.7 堆的删除
Heap.h:
// 堆的删除
void HeapPop(HP* php);
Heap.c:
// 堆的删除
void HeapPop(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);}


3.8 获取堆顶元素
返回数组首元素即可
Heap.h:
// 取堆顶的数据
HPDataType HeapTop(HP* php);
Heap.c:
// 取堆顶的数据
HPDataType HeapTop(HP* php)
{assert(php);return php->a[0];
}
3.9 获取堆的个数
直接返回size的值即可
Heap.h:
// 堆的数据个数
size_t HeapSize(HP* php);
Heap.c:
// 堆的数据个数
size_t HeapSize(HP* php)
{assert(php);return php->size;
}
3.10 堆的判空
只需判断size的值是否为0,如果是,返回true,反之返回false。
Heap.h:
// 堆的判空
bool HeapEmpty(HP* php);
Heap.c:
// 堆的判空
bool HeapEmpty(HP* php)
{assert(php);return php->size == 0;
}
4. 代码整理
4.1 Heap.h
#define _CRT_SECURE_NO_WARNINGS
#pragma once
#include <stdio.h>
#include <stdlib.h>
#include <assert.h>
#include <stdbool.h>typedef int HPDataType;typedef struct Heap
{HPDataType* a;int size; //记录数组内的有效数据int capacity; //记录数组空间大小
}HP;//堆的初始化
void HeapInit(HP* php);// 堆的销毁
void HeapDestroy(HP* php);// 堆的插入
void HeapPush(HP* php, HPDataType x);// 堆的删除
void HeapPop(HP* php);// 取堆顶的数据
HPDataType HeapTop(HP* php);// 堆的数据个数
size_t HeapSize(HP* php);// 堆的判空
bool HeapEmpty(HP* php);//向上调整父节点与子节点
void AdjustUp(HPDataType* a, int child);//向下调整父节点与子节点
void AdjustDown(int* a, int size, int parent);//交换父节点和子节点的值
void Swap(int* child, int* parent);
4.2 Heap.c
#include "Heap.h"//堆的初始化
void HeapInit(HP* php)
{//各值初始化为0assert(php);php->a = NULL;php->size = 0;php->capacity = 0;
}// 堆的销毁
void HeapDestroy(HP* php)
{assert(php);free(php->a);php->a = NULL;php->size = 0;php->capacity = 0;
}//交换父节点和子节点的值
void Swap(int* x, int* y)
{int tmp = *x;*x = *y;*y = tmp;
}
//向上调整父节点与子节点
void AdjustUp(HPDataType* a, int child)
{assert(a);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 HeapPush(HP* php, HPDataType x)
{assert(php);//首先检查数组容量是否足够if (php->size == php->capacity){int newcapacity = php->capacity == 0 ? 4 : php->capacity * 2;HPDataType* tmp = (HPDataType*)realloc(php->a, sizeof(HPDataType) * newcapacity);if (tmp == NULL){perror("realloc fail");return;}php->a = tmp;php->capacity = newcapacity;}php->a[php->size] = x;//在插入数值后需要检查是否要进行调整AdjustUp(php->a, php->size);php->size++;
}//向下调整父节点与子节点
void AdjustDown(int* a, int size, int parent)
{assert(a);int child = parent * 2 + 1;//找到孩子节点while (child < size){//找孩子中较小的一个if ((a[child] > a[child + 1]) && (child + 1) < size){child += 1;}//判断两个大小进行交换if (a[parent] > a[child]){Swap(&a[child], &a[parent]);parent = child;child = parent * 2 + 1;}else{break;}}
}// 堆的删除
void HeapPop(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);}// 取堆顶的数据
HPDataType HeapTop(HP* php)
{assert(php);return php->a[0];
}// 堆的数据个数
size_t HeapSize(HP* php)
{assert(php);return php->size;
}// 堆的判空
bool HeapEmpty(HP* php)
{assert(php);return php->size == 0;
}
4.3 study.c
void Test1()
{int array[] = { 27,15,19,18,28,34,65,49,25,37 };HP hp;HeapInit(&hp);for (int i = 0; i < sizeof(array) / sizeof(int); i++){HeapPush(&hp, array[i]);//插入数据}int k = HeapSize(&hp);while (k--){printf("%d ", HeapTop(&hp));HeapPop(&hp);}HeapDestroy(&hp);
}int main()
{;Test1();return 0;
}

相关文章:
【C语言】数据结构——小堆实例探究
💗个人主页💗 ⭐个人专栏——数据结构学习⭐ 💫点击关注🤩一起学习C语言💯💫 导读: 我们在前面学习了单链表和顺序表,以及栈和队列。 今天我们来学习小堆。 关注博主或是订阅专栏&a…...
Vue中比较两个JSON对象的差异
要在Vue.js中实现JSON数据的对比差异功能,你可以使用一些库来简化任务,比如diff-match-patch。以下是一个简单的例子,演示如何使用deep-diff库在Vue.js中比较两个JSON对象的差异: 首先,确保你的项目中已经安装了diff-m…...
前端知识库Html5和CSS3
1、常见的水平垂直居中实现方案 最简单的方案是flex布局 .container{display: flex;align-items: center;justify-content: center; }绝对定位配合margin:auto(一定要给.son宽高) .father {position: relative;height: 300px; } .son {position: absolute;top: 0;right: 0;b…...
智能优化算法应用:基于鸡群算法3D无线传感器网络(WSN)覆盖优化 - 附代码
智能优化算法应用:基于鸡群算法3D无线传感器网络(WSN)覆盖优化 - 附代码 文章目录 智能优化算法应用:基于鸡群算法3D无线传感器网络(WSN)覆盖优化 - 附代码1.无线传感网络节点模型2.覆盖数学模型及分析3.鸡群算法4.实验参数设定5.算法结果6.参考文献7.MA…...
Apollo配置发布原理解析
📫作者简介:小明java问道之路,2022年度博客之星全国TOP3,专注于后端、中间件、计算机底层、架构设计演进与稳定性建设优化,文章内容兼具广度、深度、大厂技术方案,对待技术喜欢推理加验证,就职于…...
TrustGeo论文问题理解
1、网络空间测绘中,如何理解地标? 在网络空间测绘中,地标可以理解为在互联网空间中具有明显特征和稳定性的实体,它们可以作为网络空间的基准点,用于定位和标识其他网络实体。地标通常是在网络空间中具有较高价值和影响…...
子查询在SQL中的应用和实践
作者:CSDN-川川菜鸟 在SQL中,子查询是一种强大的工具,用于解决复杂的数据查询问题。本文将深入探讨子查询的概念、类型、规则,并通过具体案例展示其在实际应用中的用途。 文章目录 子查询概念子查询的类型子查询的规则实际案例分析…...
C# Socket通信从入门到精通(14)——多个异步UDP客户端C#代码实现
前言: 在之前的文章C# Socket通信从入门到精通(13)——单个异步UDP客户端C#代码实现我介绍了单个异步Udp客户端的c#代码实现,但是有的时候,我们需要连接多个服务器,并且对于每个服务器,我们都有一些比如异步发送、异步接收的操作,那么这时候我们使用之前单个异步Udp客…...
【教3妹学编程-算法题】需要添加的硬币的最小数量
3妹:2哥2哥,你有没有看到新闻, 有人中了2.2亿彩票大奖! 2哥 : 看到了,2.2亿啊, 一生一世也花不完。 3妹:为啥我就中不了呢,不开心呀不开心。 2哥 : 得了吧,你又不买彩票&…...
【异常解决】SpringBoot + Maven 在 idea 下启动报错 Unable to start embedded Tomcat(已解决)
Unable to start embedded Tomcat(已解决) 一、背景介绍二、原因分析2.1 网络上整理2.2 其他原因 三、解决方案 一、背景介绍 spring boot(v2.5.14) maven idea 启动项目 之前项目一直启动的好好的,都能正常运行。重启的时候突然就不能启…...
做题总结 707. 设计链表
做题总结 707. 设计链表 leetcode中单链表节点的默认定义我的尝试正确运行的代码(java) leetcode中单链表节点的默认定义 class ListNode {int val;ListNode next;//无参public ListNode() {}//有参:1public ListNode(int val) {this.val val;}//有参:…...
django实现--视图的使用
在 Django 中,视图是处理 Web 请求并返回 Web 响应的组件。Django 提供了两种主要类型的视图:基于函数的视图和基于类的视图。下面详细解释基于类的视图的实现方法、使用以及与基于函数的视图的异同。 基于类的视图的实现方法 继承 Django 的类视图基类…...
【dirty cred】fileManager [XXX]
前言 这应该不是个题,应该是佬为了测试 dirty cred 利用写的。但是环境有问题,测试最多只能向文件中写入 1024MB 的数据。所以竞争窗口太短了,但是似乎替换 credential obj 又是成功的了。感觉是环境的问题。 漏洞分析与利用 一次任意释放…...
线程按顺序循环执行
不瞒大家说,这是之前参加阿里一面的手写编程题,平时不刷题,这个当时花的时间比较多,虽然最后用了很喽比方法写出来了,自己还是很不满意。下面实话也是看了其他大佬的思路,今天重新练了下。 假设有3个线程,依次打印A、B、C,按顺序循环打印100次。 这个其实是线程通信,…...
C# 使用异步委托获取线程返回值
写在前面 异步委托主要用于解决 ThreadPool.QueueUserWorkItem 没有提供获取线程执行完成后的返回值问题。异步委托只能在.Net Framework 框架下使用,.Net Core中会报平台错误,而且使用Task.Result来获取返回值,可以达成同样的目的ÿ…...
生鲜蔬果展示预约小程序作用是什么
线下生鲜蔬果店非常多,对商家来说主要以同城生意为主,而在互联网电商的发展下,更多的商家会选择搭建私域商城进行多渠道的销售卖货和拓展,当然除了直接卖货外,还有产品纯展示或预约订购等需求。 但无论哪种模式&#…...
【C++】类与对象(下)
本文目录 1. 再谈构造函数1.1 构造函数体赋值1.2 初始化列表1.3 explicit关键字 2. static成员2.1 概念2.2 特性 3. 友元3.1 友元函数3.2 友元类 4. 内部类5. 匿名对象6. 拷贝对象时的一些编译器优化7. 再次理解类和对象 1. 再谈构造函数 1.1 构造函数体赋值 在创建对象时&am…...
一文了解 Go 方法
前言 在前面的 一文熟悉 Go 函数 文章中,介绍了 Go 函数的声明,函数的几种形式如匿名函数、闭包、基于函数的自定义类型和函数参数详解等,而本文将对方法进行介绍,方法的本质就是函数,介绍方法的同时也会顺带对比其与函…...
【Docker】vxlan的原理与实验
VXLAN(Virtual eXtensible Local Area Network,虚拟可扩展局域网),是一种虚拟化隧道通信技术。它是一种Overlay(覆盖网络)技术,通过三层的网络来搭建虚拟的二层网络。 VXLAN介绍 VXLAN是在底层…...
广度(宽度)优先搜素——层层递进
分析算法及题目 完整代码实现 广度优先搜索(Breadth-First Search,BFS)是一种图和树的遍历算法,与深度优先搜索相对应。BFS从起始节点开始,首先访问起始节点,然后逐层地访问其邻居节点,直到达到…...
Java 语言特性(面试系列1)
一、面向对象编程 1. 封装(Encapsulation) 定义:将数据(属性)和操作数据的方法绑定在一起,通过访问控制符(private、protected、public)隐藏内部实现细节。示例: public …...
java 实现excel文件转pdf | 无水印 | 无限制
文章目录 目录 文章目录 前言 1.项目远程仓库配置 2.pom文件引入相关依赖 3.代码破解 二、Excel转PDF 1.代码实现 2.Aspose.License.xml 授权文件 总结 前言 java处理excel转pdf一直没找到什么好用的免费jar包工具,自己手写的难度,恐怕高级程序员花费一年的事件,也…...
三体问题详解
从物理学角度,三体问题之所以不稳定,是因为三个天体在万有引力作用下相互作用,形成一个非线性耦合系统。我们可以从牛顿经典力学出发,列出具体的运动方程,并说明为何这个系统本质上是混沌的,无法得到一般解…...
在WSL2的Ubuntu镜像中安装Docker
Docker官网链接: https://docs.docker.com/engine/install/ubuntu/ 1、运行以下命令卸载所有冲突的软件包: for pkg in docker.io docker-doc docker-compose docker-compose-v2 podman-docker containerd runc; do sudo apt-get remove $pkg; done2、设置Docker…...
Docker 本地安装 mysql 数据库
Docker: Accelerated Container Application Development 下载对应操作系统版本的 docker ;并安装。 基础操作不再赘述。 打开 macOS 终端,开始 docker 安装mysql之旅 第一步 docker search mysql 》〉docker search mysql NAME DE…...
排序算法总结(C++)
目录 一、稳定性二、排序算法选择、冒泡、插入排序归并排序随机快速排序堆排序基数排序计数排序 三、总结 一、稳定性 排序算法的稳定性是指:同样大小的样本 **(同样大小的数据)**在排序之后不会改变原始的相对次序。 稳定性对基础类型对象…...
代码规范和架构【立芯理论一】(2025.06.08)
1、代码规范的目标 代码简洁精炼、美观,可持续性好高效率高复用,可移植性好高内聚,低耦合没有冗余规范性,代码有规可循,可以看出自己当时的思考过程特殊排版,特殊语法,特殊指令,必须…...
tomcat指定使用的jdk版本
说明 有时候需要对tomcat配置指定的jdk版本号,此时,我们可以通过以下方式进行配置 设置方式 找到tomcat的bin目录中的setclasspath.bat。如果是linux系统则是setclasspath.sh set JAVA_HOMEC:\Program Files\Java\jdk8 set JRE_HOMEC:\Program Files…...
前端中slice和splic的区别
1. slice slice 用于从数组中提取一部分元素,返回一个新的数组。 特点: 不修改原数组:slice 不会改变原数组,而是返回一个新的数组。提取数组的部分:slice 会根据指定的开始索引和结束索引提取数组的一部分。不包含…...
深度学习之模型压缩三驾马车:模型剪枝、模型量化、知识蒸馏
一、引言 在深度学习中,我们训练出的神经网络往往非常庞大(比如像 ResNet、YOLOv8、Vision Transformer),虽然精度很高,但“太重”了,运行起来很慢,占用内存大,不适合部署到手机、摄…...
