【数据结构】堆及堆排序的实现(C语言)

news2025/7/19 16:51:53

目录

前言

初始化

增删 

由一个数组构建堆

堆排序

TOPK问题


前言

我们都知道二叉树是度为 的树,如果在一个完全二叉树里,所有的子结点都小于他的父结点,那么它就是堆。这样的堆被称之为大堆,反之则称为小堆。

 虽然我们画出它的模型是完全二叉树的样子,但实际上堆的数据是存放在一个一维数组里的,不用惊慌,如下三个公式便可以解决我们于堆访问的问题。归根结底还是数学问题。

初始化

前面讲过,堆的数据的存放在数组里面的,因此构建的是一个顺序表的结构。并给予初始数值,因为将开辟空间一并放到 checkcapacity 里,所以这里就只是赋值成0而已。

typedef int heaptype;
typedef struct heap
{
	heaptype* a;
	int size;
	int capacity;
}heap;
//堆的初始化
void Heapinit(heap* hp)
{
	hp->a = NULL;
	hp->size = hp->capacity = 0;
}

销毁 

 堆的销毁十分简单,直接把申请的空间全部释放就可以了。

// 堆的销毁
void HeapDestory(heap* hp)
{
	assert(hp);
	assert(hp->a);

	free(hp->a);
	hp->a = NULL;                  //回归初始状态
	hp->size = hp->capacity = 0;

}

增删 

插入数据

作为一个数组,插入数据最佳的地方应该是数组的尾部。插入前还得检查一下数组的大小是否够用,否则扩容数组。

void checkcapacity(heap* hp)
{
	if (hp->size == hp->capacity)
	{
		int newcapacity = hp->capacity == 0 ? 4 : hp->capacity * 2;    //初始化为4,否则大小翻倍
		heaptype* narr = (heaptype*)realloc(hp->a, sizeof(heaptype) * newcapacity);
		if (narr == NULL)
		{
			perror(realloc);
			exit(-1);
		}
		hp->a = narr;         //更新数据
		hp->capacity = newcapacity;
	}
}

但仅仅插入是不行的,为了保证堆依然成立,我们还需要对数据的位置进行调整。(这里构建的是小堆) 

 我们应该注意到的是,小堆的定义是每一个子结点都要大于它的父结点,这里我们只需要让新插入的这个数据逐步地于它的父节点比较,小于则交换,大于就不再进行移动。

void adjustup(heap* hp)
{
	int child = hp->size;            //找到子结点的下标
	int parent = (child-1)/2;        //找到与该子结点对应的父结点

	while (child > 0)                //堆顶的下标为0位于堆顶无需再调整
	{
		if (hp->a[child] < hp->a[parent])   //子结点小于父结点则交换
		{
			swap(&hp->a[child], &hp->a[parent]);
			child = parent;
			parent = (child - 1) / 2;        //找当前位置的父结点
		}
		else
		{
			break;                   //大于则无需调整
		}
	}
}

 

把这些统合起来就完成了往堆里插入一个新值。

// 堆的插入(小堆)
void HeapPush(heap* hp, heaptype x)
{
	assert(hp);
	checkcapacity(hp);

	hp->a[hp->size] = x;
	adjustup(hp);
	hp->size++;

}

删除堆顶数据

仔细思考,我们会发现若直接删除堆顶是十分困难的,这时候我们不禁想:若堆顶的那个数据也在数组的尾部就好了。这无疑是为这个步骤提供了一个绝佳的思路!!!我们可以把堆顶与堆底的数值交换,把最后面的值删除之后,对堆顶的数据进行向下调整,由于原本堆底的值就是最大的值,因此调整结束后其仍会回归堆底。

void adjustdown(heaptype* a, int n, int root)   //n是数组的大小
{
	int parent = root;                          //找到向下调整的初始值
	int child = parent * 2 + 1;                 //往下找其左孩子
	while (parent < n)
	{
		if (child + 1 < n && a[child] > a[child + 1])  //找孩子里最小的那个
		{
			child++;
		}
		if (child < n && a[parent] > a[child])      //父结点大于子结点就交换
		{
			swap(&a[child], &a[parent]);
			parent = child;
			child = parent * 2 + 1;                 //找该结点对应的子结点
		}
		else
		{
			break;                                  //小于则停止调整
		}
	}
}

删除数组最后一个只需要调整size的值,并不需要对其进行其他调整。

// 堆顶的删除
void HeapPop(heap* hp)
{
	assert(hp);
	assert(hp->a);

	swap(&hp->a[hp->size - 1], &hp->a[0]);  
	hp->size--;
	adjustdown(hp->a, hp->size, 0);
}

堆顶数据 数据个数 判空

对基本数值判定就可以完成。

// 取堆顶的数据
heaptype HeapTop(heap* hp)
{
	assert(hp);
	assert(hp->a);

	return hp->a[0];
}

// 堆的数据个数
int HeapSize(heap* hp)
{
	assert(hp);

	return hp->size;
}

// 堆的判空
bool HeapEmpty(heap* hp)
{
	assert(hp);

	if (hp->size)
	{
		return true;
	}

	return false;
}

由一个数组构建堆

需要清楚的一件事是,我们能够使用向下调整的前提是下面两个堆都是小堆,因此若要由一个数组构建堆并不只是一个劲地向下调整就可以解决的。

                                                  

 仔细一想,若我们从尾部向下调整上去,似乎结果就有所不同,我们只需要找到数组最后一个数的父结点,这时候向下调整就只会在这(2~3)个数直接寻找最小值放在该父结点上。之后找到最靠近这个父结点的另一父结点再次进行调整,直到到达堆顶完成堆的构建。

void HeapCreate(heap* hp, heaptype* a, int n)
{
	heaptype* narr = (heaptype*)malloc(sizeof(heaptype)*n);   //开辟堆的空间
	if (narr == NULL)
	{
		perror(malloc);
		exit(-1);
	}
	hp->a = narr;
	hp->size = 0;
	hp->capacity = n;

	for (int i = 0; i < n; i++)            //导入原数组
	{
		checkcapacity(hp);
		hp->a[i] = a[i];
		hp->size++;
	}

	for (int i = (n - 1 - 1) / 2; i >= 0; i--)    //从下往上逐步构建小堆
	{
		adjustdown(hp, hp->size, i);
	}
}

堆排序

我们都知道,在小堆内堆顶的数据就是整个堆最小的,因此可以利用这个思想进行对数组的排序。把最小的数放在数组最后,其他的数继续排序,直到全部完成。因此构建大堆便排升序,构建小堆则排降序。这样子使得排序的实践复杂度大大减小,达到提高运行效率的结果。

void HeapSort(heaptype* a, int n)
{
	assert(a);

	for (int i = (n - 1 - 1) / 2; i >= 0; --i)
	{
		adjustdown(a, n, i);                        //构建小堆排降序
	}

	int end = n-1;                                  //找到数组尾端
	while (end)
	{
		swap(&a[0], &a[end]);                       //最小值与最尾值交换
		adjustdown(a, end, 0);                      //向下调整
		end--;                                      //把已调整完的值剔除于排序内
	}
}

TOPK问题

要求出最大的几个数,主要的思想便是先用前 个值创建一个小堆,若有值大于这个堆里最小的数(堆顶)则插入到堆里而剔除原堆顶的元素。遍历完整个数组后,堆里剩下的就是最大的 个数了。

void HeapSort(heaptype* a, int n)
{
	assert(a);

	for (int i = (n - 1 - 1) / 2; i >= 0; --i)
	{
		adjustdown(a, n, i);                        //构建小堆排降序
	}

	int end = n-1;                                  //找到数组尾端
	while (end)
	{
		swap(&a[0], &a[end]);                       //最小值与最尾值交换
		adjustdown(a, end, 0);                      //向下调整
		end--;                                      //把已调整完的值剔除于排序内
	}
}

void PrintTopK(int* a, int n, int k)
{
	int* minheap = (int*)malloc(sizeof(int) * k);      
	if (minheap == NULL)
	{
		perror(malloc);
		exit(-1);
	}

	for (int i = 0; i < k; i++)                        //先取前k个数放到这个堆里面
	{
		minheap[i] = a[i];
	}
	for (int i = (k - 1 - 1) / 2; i >= 0; i--)
	{
		adjustdown(minheap, k, i);                     //调整成一个小堆
	}
	for (int i = k; i < n; i++)
	{
		if (a[i] > minheap[0])
		{
			swap(&a[i], &minheap[0]);                  //大于堆顶就交换插进来
			adjustdown(minheap, k, 0);                 //向下调整找到定位
		}
	}
	for (int i = 0; i < k; i++)
	{
		printf("%d ", minheap[i]);
	}
}

这样今天的堆与堆排序的讲解就到这里结束了,如果有帮助到你还希望能给我一键三连,我们下次再见。

                                                   

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/36117.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

光学测量精度极限—光谱共焦位移传感器的六大行业应用

科技的不断发展&#xff0c;在半导体&#xff0c;高精密制造领域中都是采用微米及以上的加工工艺&#xff0c;并与之匹配高精度测量技术进行品质控制。光谱共焦的测量原理是一束白光经过镜头将不同的波长聚焦到光轴上&#xff0c;色散地形成一条彩虹状分布带&#xff0c;照射到…

第五章:LockSupport与线程中断

阿里蚂蚁金服面试题什么是线程中断机制&#xff1f;中断相关的 API 方法面试题1、如果停止正在运行的一个线程&#xff1f;2 当前线程的中断标识为true&#xff0c;是不是线程就立刻停止&#xff1f;案例一案例二3、静态方法Thread.interrupted()&#xff0c;谈谈你的理解总结L…

国外服务器采取数据备份和灾难恢复的重要性

在国外服务器的使用中&#xff0c;数据的安全性和完整性是任何组织都不能忽视的问题。丢失数据可能会对业务造成毁灭性的影响&#xff0c;因此必须有一个完善的数据备份和灾难恢复计划。 什么是备份? 备份将数据复制到辅助形式&#xff0c;如存档文件&#xff0c;在灾难发生时…

网络安全:SQL盲注概述

目录 盲SQL注入 什么是盲SQL注入&#xff1f; 通过触发条件响应来利用盲SQL注入 通过触发 SQL 错误来诱导条件响应 通过触发时间延迟来利用盲 SQL 注入 使用带外 &#xff08;OAST&#xff09; 技术利用盲 SQL 注入 如何防止SQL注入 二阶 SQL 注入 特定于数据库的因素…

4.每天不同时间段通过微信发消息提醒女友

目录 sentence_good_dinner.txt README.MD say_to_lady.py sentence_good_dream.txt sentence_good_lunch.txt sentence_good_dinner.txt 公主&#xff01;记得吃晚饭。 下班了&#xff0c;记得吃晚饭哦。 记得多吃点饭 你吃晚饭吧&#xff1f; 宝宝吃晚饭了吗&#xff0…

《从零开始:机器学习的数学原理和算法实践》chap1

《从零开始&#xff1a;机器学习的数学原理和算法实践》chap1 学习笔记 文章目录《从零开始&#xff1a;机器学习的数学原理和算法实践》chap1 学习笔记chap1 补基础&#xff1a;不怕学不懂微积分1.1 深入理解导数的本质直观理解复合函数求导1.2 理解多元函数偏导1.3 理解微积分…

超纯水系统中硼离子去除技术原理

硼在元素周期表里面是五号元素&#xff0c;是IIIA族中唯一 一个非金属元素。它是制造P型半导体的主要 掺杂剂&#xff0c;基材中硼的含量直接影响半导体的极限电压&#xff0c;因此要严格控制基材中硼的含量。在半导体 制造的过程中&#xff0c;水、气、化直接跟产品接触&…

b站黑马JavaScript的Ajax案例代码——图书管理案例

目录 目标效果&#xff1a; 重点原理&#xff1a; 1.js数组操作中push方法 2.jquery中append方法 3.js数组操作中join方法 4.jQuery中attr方法 5.jQuery中trim方法 代码部分&#xff1a; 1. 图书管理案例.html(js部分全是重点&#xff0c;html部分用于看结构) 2.jquery.js…

软考-系统架构师-计算机与网络基础知识-计算机网络基础知识

文章目录1.网络概述1.1开放系统互连参考模型1.2OSI协议集2.计算机网络2.1广域网局域网和城域网2.2网络互联2.3Internet3.网络管理与网络安全3.1网络管理3.2计算机网络安全3.3VPN4.网络工程5.存储及负载均衡技术5.1RAID技术5.2网络存储技术注1.网络概述 计算机网络通信按距离&a…

论文阅读笔记 | 三维目标检测——PointPillars算法

如有错误&#xff0c;恳请指出。 文章目录1. 背景2. 网络结构3. 实验结果paper&#xff1a;《PointPillars: Fast Encoders for Object Detection from Point Clouds》 1. 背景 PointPillars的出发点同样与SECOND一样&#xff0c;希望改进VoxelNet所使用3d卷积计算量太大推理…

Docker 安装 mysql5.7

docker拉取镜像命令 docker pull mysql:5.7 docker安装MySQL命令 docker run -p 3306:3306 --name mysql -v /mydata/mysql/log:/var/log/mysql -v /mydata/mysql/data:/var/lib/mysql -v /mydata/mysql/conf:/etc/mysql -e MYSQL_ROOT_PASSWORDroot -d mysql:5.7 修改MySQL的…

SpringBoot2.7.4整合Redis

目录 一、添加maven依赖 二、添加配置项 三、新增配置类 四、编辑实体类 五、编写接口 六、编写业务层 1.编写service层 2.编写service实现层 七、测试接口 一、添加maven依赖 <dependency><groupId>org.springframework.boot</groupId><artif…

@PostConstruct详解

1、 Servlet中增加了两个影响Servlet生命周期的注解&#xff0c;PostConstruct和PreDestroy&#xff0c;这两个注解被用来修饰一个非静态的void&#xff08;&#xff09;方法。写法有如下两种方式&#xff1a; PostConstruct public void someMethod(){} 或者 public PostConst…

第六章:Java内存模型之JMM

有关JMM的面试题&#xff1f;计算机硬件存储体系Java内存模型Java Memory Model概念原则能干什么JMM三大特性原子性可见性有序性JMM规范下&#xff0c;多线程对变量的读写过程JMM规范下&#xff0c;多线程先行发生原则之happens-beforehappens-before 先行发生原则的概念Happen…

CentOS中yum install命令如何找到安装包的下载地址

我们通常用的yum install命令是怎么找到我们想要的下载地址的&#xff0c;这里简单做一下分析 首先&#xff0c;搜索所有/etc/yum.repos.d下所有repo 在每个repo中都有一个baseurl&#xff0c;这里以docker-ce.repo下载containerd.io 的rpm包为例 docker-ce的部分repo为 [do…

怎么去除视频上的文字?一篇教你:视频上的文字水印怎么去除

原创视频更能够吸引人&#xff0c;但是毕竟热点有限&#xff0c;想要随时保持活跃度和吸引力就必须借助更多的视频素材来留住粉丝。但是很多视频素材是有水印、文字、LOGO或者一些图像的&#xff0c;那怎么去除视频上的文字呢&#xff1f;小编一篇简单文章教你怎么去除视频上的…

GEO振弦式钢筋计的组装

&#xff08;1&#xff09;按钢筋直径选配相应的钢筋计&#xff0c;如果规格不符合&#xff0c;应选择尽量接近于结构钢筋直径 的钢筋计&#xff0c;例如&#xff1a;钢筋直径为 35mm&#xff0c;可使用 NZR-36 或 NZR-32 的钢筋计&#xff0c;此时仪器的最小 读数应进行修…

Quarkus 集成 mailer 使用 easyexcel 发送表格邮件

前言 在quarkus 项目开发中,需要实现一个把用户数据写入到excel 表格中,然后发送邮件给到对应的用户邮箱上,在查找了Quarkus 官方文档后发现,Quarkus 对于发送邮箱的服务是天然支持的. 官方文档: https://cn.quarkus.io/guides/mailer 环境配置 首先发送邮箱服务,那么就需要有…

CAS:89485-61-0,mPEG-N3,mPEG-Azide,甲氧基-peg-叠氮试剂供应

mPEG-N3&#xff08;mPEG-Azide&#xff09;中文名为甲氧基-聚乙二醇-叠氮&#xff0c;它所属分类为Azide PEG Methoxy PEG。CAS编号为89485-61-0。 peg试剂的分子量均可定制&#xff0c;有&#xff1a;甲氧基-聚乙二醇-叠氮 5k、甲氧基-PEG-叠氮 10k、mPEG-Azide 2k、甲氧基-…

error20221125--ssm项目用maven打包报错“找不到程序包 java.lang”或者“程序包javax.crypto不存在”,以及解决方案

从svn拉的ssm项目&#xff0c;写完代码准备发测试&#xff1b;结果用maven打war包的时候报错了 这个时候在控制台找到报错信息如下图 说“在类路径或引导类路径中找不到程序包 java.lang”&#xff1b; 我寻思着&#xff0c;java.lang不是jdk中基础的包吗&#xff1f;不应该有…