c语言-数据结构-堆

       

目录

一、二叉树

1、二叉树的概念

 2、完全二叉树和满二叉树

3、完全二叉树的顺序存储

二、堆

2、堆的概念与结构

3、堆的创建及初始化

4、堆的插入(小堆)

 5、堆的删除

6、显示堆顶元素

7、显示堆里的元素个数

8、测试堆的各个功能

9、 实现堆排序

三、TOP-K的实现

结语:


前言:

        堆是数据结构中是很重要的一个知识点,通常用于实现堆排序, 比如在生活中我们要算出考试成绩的前十名,或者游戏排名的前一百名,这种场景下就用到堆排序了。堆其实是相同类型元素的集合,通常用数组的形式表示一个堆,数组内的所有元素都按照完全二叉树的形式进行存储,这时候又引出了二叉树的概念,只要明白了完全二叉树的概念就明白了什么是堆,因此下面先介绍二叉树的基本概念。

一、二叉树

1、二叉树的概念

        二叉树的根节点由一个左子树和一个右子树构成,也就是每一个结点可以少于两个孩子结点但是不能多于两个孩子结点,示意图如下:

        如上图,D可以是A的孩子结点,同时D也可以是H的父母节点,D同时也是E的兄弟节点,一个结点可以拥有多重身份。如果一棵树中的任意一个结点有超过2个孩子结点那么该树就不是一个二叉树,二叉树的其他情况如下:

 2、完全二叉树和满二叉树

        一颗二叉树的每一层的结点都是满的,将该二叉树称为满二叉树,完全二叉树则是在满二叉树的概念上引申而来的,即满二叉树的最后一层的结点可以不是满的,但是二叉树中的所有结点从左到右必须是按照顺序排序的,示意图如下:

         因此可以看出完全二叉树的一个特点:即每个节点必须是按照顺序进行存储的,即完全二叉树具有顺序性。

3、完全二叉树的顺序存储

        我们知道数组是一块连续的空间,而且数组里的元素都是“紧挨着”的,即不存在第一个元素和第三个元素中间空了位置不存储任何数据,这么做不符合逻辑也很浪费空间。由此看来,数组和完全二叉树的特点非常相似。

        因此一个完全二叉树可以用数组的形式表示出来,只要是一个数组就是一个完全二叉树。并且有了父母结点的下标就可以找到他的孩子结点,有了孩子的结点就能找到其父母结点的下标。 

        比如有了结点B的下标,可以找到他的两个孩子结点的下标:左孩子D=B的坐标*2,有孩子E=B坐标*2+1。

        B的父母结点A坐标=(B坐标-1)/2。

二、堆

2、堆的概念与结构

       堆是一个完全二叉树,但是他在完全二叉树的基本要求上增加了以下条件:树里任意一个父母结点都大于等于(或小于等于)他们的孩子结点,因此堆分为两种情况:大堆和小堆。

        大堆:从根节点开始,从上至下每个父亲结点必须大于他的两个孩子结点。

        小堆:从根节点开始,从上至下每个父亲结点必须小于他的两个孩子结点。

        示意图如下:

        因此从以上的分析来看,可以得出以下结论:数组其实就可以看成是一个完全二叉树,但是不一定是堆,但是是堆就一定是完全二叉树。而且即使是小堆也不一定是升序,大堆也不一定是降序(这里只是偶然)。当然,有序的数组一定是堆。 

        接下来用代码建立一个小堆。

3、堆的创建及初始化

        从以上得知,堆的本质是数组,因此堆的创建代码如下:

typedef int HeapDataType;//int类型重定义
typedef struct Heap
{
	HeapDataType* arr;//指针arr,即数组名表示数组的首地址
	int size;//数组的元素个数
	int capacity;//数组的容量
}Heap;

         堆初始化代码如下:

void HeapInit(Heap* php)//初始化
{
	assert(php);

	php->arr = NULL;//置为空
	php->size = 0;//刚开始没有元素因此为0
	php->capacity = 0;
}

4、堆的插入(小堆)

        原理就是把数据插入到数组中,所以在数组的末尾处添加数据即可,但是单单的插入数据并不能满足大堆和小堆的条件,因此每插入一个数据都要对数组进行调整,以便然数组满足小堆的条件。

        然而从数组的末尾处插入元素,从完全二叉树的角度来看,就是从树的最后一个结点插入元素,因此对数组进行调整其实就是调整插入元素与其父母结点的一个大小关系。插入最后一个结点的时候进行对树的调整的,该调整法称为向上调整法。向上调整示意图如下:

        建小堆和向上调整法代码如下:

void Swap(HeapDataType* p1, HeapDataType* p2)//交换函数
{
	int temp = *p1;
	*p1 = *p2;
	*p2 = temp;
}


void AdjustUp(HeapDataType* arr, int child)//向上调整函数
{
	assert(arr);

	int parent = (child - 1) / 2;//求出父母结点下标
	while (child > 0)//等于0才结束循环说明是最坏情况与根交换
	{
		if (arr[child] < arr[parent])//如果孩子小则与父母交换
		{
			Swap(&arr[child], &arr[parent]);//让孩子结点和父母结点交换

			child = parent;//让原先的父母结点变成孩子结点继续调整
			parent = (child - 1) / 2;//找到原先父母结点的父母结点
		}
		else//表示没有达到最坏情况
		{
			break;
		}
	}
}


void HeapPush(Heap* php, HeapDataType x)//入堆
{
	assert(php);

	if (php->size == php->capacity)
	{
		int newcapacity = php->capacity == 0 ? 4 : php->capacity * 2;//三目法更新容量
		HeapDataType* temp = (HeapDataType*)realloc
		(php->arr, sizeof(HeapDataType) * newcapacity);//开辟以及扩容空间

		if (temp == NULL)
		{
			perror("HeadFush");
			return;
		}
		php->arr = temp;//让arr指向新的空间
		php->capacity = newcapacity;//更新容量
	}
	php->arr[php->size] = x;//入堆
	php->size++;//更新size

	AdjustUp(php->arr, php->size - 1);//向上调整堆
}

 5、堆的删除

        堆的删除一般是将堆顶的元素删除,堆顶的元素即是下标为0的元素,也就是数组的第一个元素,如果将堆顶的元素直接删除,堆肯定会发生变化,会导致该数组不再是堆了,因此进行堆的删除的同时要调整堆。因为是堆顶的元素发生了改变,所以从堆顶的位置往下调整,进行向下调整。向下调整堆的具体逻辑如下图:

        向下调整法思路:把堆顶元素和堆尾元素进行交换,然后从堆顶处开始向下调整,一直调整到元素30的前一个元素。让堆顶元素跟他的两个孩子中最小的孩子进行比较,如果他的孩子比他小则他们俩进行交换,如此循环往下比较,直到树的最后一层。注意:此时的元素30是不参与调整的。

        堆的删除及向下调整法代码如下: 

bool Empty(Heap* php)//判空函数
{
	assert(php);

	return php->size == 0;//为空即返回真
}


void AdjustDown(HeapDataType* arr, int size, int parent)//向下调整堆
{
	assert(arr);

	int child = parent * 2 + 1;//找出其左孩子
	while (child < size)//若孩子的下标已经超出了数组元素个数,说明数组已经是堆
	{
		if ((child + 1 < size) && arr[child + 1] < arr[child])//此判断可以找出最小的孩子
		{
			child++;
		}
		if (arr[child] < arr[parent])//若孩子小于父母则交换
		{
			Swap(&arr[child], &arr[parent]);

			parent = child;//进行往下遍历
			child = parent * 2 + 1;
		}
		else
		{
			break;
		}
	}
}

void HeapPop(Heap* php)//删除堆顶
{
	assert(php);
	assert(!Empty(php));//堆为空则Empty函数返回真,对其取非,则堆为空assert生效

	Swap(&php->arr[0], &php->arr[php->size - 1]);//交换函数
	php->size--;szie需要--因为此时的size是最后一个元素的后一个位置

	AdjustDown(php->arr, php->size, 0);//向下调整堆,从堆顶开始到堆尾结束
}

6、显示堆顶元素

        显示堆顶元素就很简单了,堆顶表示的是数组的首元素,首元素下标为0,显示堆顶代码如下:

HeapDataType HeapTop(Heap* php)//展示堆顶
{
	assert(php);

	return php->arr[0];
}

7、显示堆里的元素个数

        堆的大小就是size的值,因为每次赋值后size都会++,所以刚好可以用size表示元素个数,代码如下:

int HeapSize(Heap* php)//堆的元素的总数
{
	assert(php);

	return php->size;
}

8、测试堆的各个功能

       以上介绍了那么多的功能,现在对这些功能进行测试,代码如下:

void test1()
{
	Heap hp;//创建堆的结构体变量
	HeapInit(&hp);//初始化

	int a[] = { 7,8,3,5,1,9,4,5 };
	for (int i = 0; i < sizeof(a) / sizeof(int); i++)
	{
		HeapPush(&hp, a[i]);//把数组a里的元素入进堆里
	}
	printf("size:%d\n", HeapSize(&hp));//打印堆里元素总数
	int i = 0;
	while (!Empty(&hp))//循环打印出堆里的所有元素
	{
		printf("%d ",HeapTop(&hp));//打印堆顶的元素
		HeapPop(&hp);//删除堆顶元素
	}
	HeapDestroy(&hp);//释放堆
}

int main()
{
	test1();
	return 0;

}

        运行结果:

        可以看到打印出来的结果是一个有序的数组,原因是每次打印堆顶的元素然后就删除该堆顶元素并且进行了向下调整,调整完之后此时堆顶元素就是第二小的元素,所有才能打印出有序的序列。 

9、 实现堆排序

        以上的测试可以发现是在数组arr中进行的,但是原数组a并没有受到改变,若想实现排序,则必须在原数组内对原数组里的顺序进行排序。

        堆排序代码如下:

void test2()//堆排序
{
	Heap hp;
	HeapInit(&hp);//初始化

	int arr[] = { 7,8,3,5,1,9,4,5 };//创建一个数组
	
	int k = sizeof(arr) / sizeof(int);

	//对原数组进行建堆
	for (int i = (k - 1 - 1) / 2; i >= 0; i--)
	{
		AdjustDown(arr, k, i);//
	}

	//建完堆之后不一定是有序的数组,因此还需要对其进行调整
	int end = sizeof(arr) / sizeof(int) - 1;
	while (end > 0)
	{
		Swap(&arr[0], &arr[end]);//堆顶元素与堆尾元素进行交换

		AdjustDown(arr, end, 0);//向下调整到堆尾的前一个元素
		end--;//堆尾下标--,准备下一次的交换
	}

	//打印
	for (int i = 0; i < k; i++)
	{
		printf("%d ", arr[i]);
	}
}

int main()
{
	test1();
	return 0;

}

        首先在原数组a中进行建堆,建完堆之后不代表数组a就是有序的,因此还需要对其进行调整,我们可以保证的是数组在建完堆之后堆顶的元素是最小的,因此把堆顶元素跟堆里的最后一个元素进行交换(用end表示堆尾的下标),然后从堆顶开始向下调整到end的前一个位置结束调整,思路与堆的删除相似。

        从上图分析来看,最后会得到一组降序的数组,因此得出结论建立小堆得到的是降序,而建立大堆得到的是升序。

三、TOP-K的实现

        一般我们要查询某个专业的前十名,或者前世界500强的公司有哪些的时候都会用到TOP-K来解决,当要搜索的范围很大的时候,用正常的排序是难以解决的,比如从10亿个数中求出前五个最大的数,这时候如果把10亿个数排好序则要耗费大量的内存,很明显排序的办法不行。

        这时候就需要堆来解决了,可以先建立一个5个数字的小堆,然后从这10个数中不断的取出数据跟堆顶元素进行比较,如果比堆顶元素大则替换堆顶元素然后再进行向下调整堆,最后堆里的5个元素就10亿中最大的5个元素。用堆解决的优势在于这10亿个数不需要全部拿到内存中,而是存在磁盘里的文件中就可以进行对比了,需要文件操作函数来实现。

        这里我就用1000个数作为测试用例,TOP-K代码如下:

void CreatData()//创造数据
{
	int n = 1000;//这里我们只创建1000个数用来测试
	srand(time(0));//生成随机数
	FILE* fin = fopen("data.txt", "w");//以写的方式打开文件
	if (fin == NULL)
	{
		perror("fopen error");
		return;
	}

	for (int i = 0; i < n; i++)
	{
		int x = rand() % 10000;//生成10000以内的随机值
		fprintf(fin, "%d\n", x);//把这些值写进文件中
	}
	fclose(fin);//关闭文件
	fin = NULL;
}

void TOP_K(int k)//选出5个最大的数
{
	FILE* fin = fopen("data.txt", "r");//以读的方式打开文件
	if (fin == NULL)
	{
		perror("fopen error");
		return;
	}
	//创建数组
	int* arr = (int*)malloc(sizeof(int)*k);
	if (arr == NULL)
	{
		perror("malloc error");
		return;
	}

	//倒数据
	for (int i = 0; i < k; i++)
	{
		fscanf(fin, "%d", arr + i);//把文件里的前五个数给到数组arr,准备建堆
	}

	//建小堆
	for (int i = (k - 1 - 1) / 2; i >= 0; i--)
	{
		AdjustDown(arr, k, i);//向下调整建立小堆
	}

	//TOP-K
	int val = 0;//创建val变量
	while (!feof(fin))//遍历文件内容
	{
		fscanf(fin, "%d", &val);//把文件里的值以%d的形式给到val变量
		if (val > arr[0])//如果val的值大于堆顶元素
		{
			arr[0] = val;//将val的值赋给堆顶
			AdjustDown(arr, k, 0);//并且重新向下调整堆,保证数组是一个堆
		}
	}
	//打印数组
	for (int i = 0; i < k; i++)
	{
		printf("%d ", arr[i]);
	}
	fclose(fin);//关闭文件
	fin = NULL;
}

int main()
{
	CreatData();//创建数据
	TOP_K(5);//TOP-K实现
	return 0;

}

        创建文件数据的结果:

        运行结果:

         从结果可以看到TOP-K确实帮助我们从1000个数中找出了最大的五个数。

结语:

        以上就是关于堆的一系列的解析与延申,堆的重点在于对向上调整和向下调整的理解,这两个调整法才是创建堆的核心。希望本文可以带给你更多的收获,如果本文对你起到了帮助,希望可以动动小指头帮忙点赞👍+关注😎+收藏👌!如果有遗漏或者有误的地方欢迎大家在评论区补充~!!谢谢大家!!(❁´◡`❁)

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

零代码编程:用ChatGPT批量转换多个视频文件夹到音频并自动移动文件夹

有很多个视频文件夹&#xff1a; 要全部转成音频&#xff0c;然后复制到另一个文件夹。 在ChatGPT中输入如下提示词&#xff1a; 你是一个Python编程专家&#xff0c;要完成一个批量将Mp4视频转为Mp3音频的任务&#xff0c;具体步骤如下&#xff1a; 打开文件夹&#xff1a;…

机器学习 天气识别

>- **&#x1f368; 本文为[&#x1f517;365天深度学习训练营](https://mp.weixin.qq.com/s/Nb93582M_5usednAKp_Jtw) 中的学习记录博客** >- **&#x1f356; 原作者&#xff1a;[K同学啊 | 接辅导、项目定制](https://mtyjkh.blog.csdn.net/)** >- **&#x1f680;…

matlab层次分析法模型及相关语言基础

发现更多计算机知识&#xff0c;欢迎访问Cr不是铬的个人网站 代码放在最后面! 这篇文章是学习层次分析法模型的笔记。 1.什么时候用层次分析法 层次分析法是建模比赛中最基础的模型之一&#xff0c;其主要用于解决评价类问题&#xff08;例如&#xff1a;选择哪种方案最好、…

Mysql数据库 16.SQL语言 数据库事务

一、数据库事务 数据库事务介绍——要么全部成功要么全部失败 我们把完成特定的业务的多个数据库DML操作步骤称之为一个事务 事务——就是完成同一个业务的多个DML操作 例&#xff1a; 数据库事务四大特性 原子性&#xff08;A&#xff09;&#xff1a;一个事务中的多个D…

ZYNQ7000---FLASH读写

提示&#xff1a;写完文章后&#xff0c;目录可以自动生成&#xff0c;如何生成可参考右边的帮助文档 文章目录 前言一、Flash是什么&#xff1f;二、Flash的分类1、内部结构&#xff08;接口&#xff09;区分&#xff1a;2、外部接口区分&#xff1a;SPIQPSI Flash: QSPI 控制…

如何做好性能压测 —— 压测环境设计和搭建!

简介&#xff1a;一般来说&#xff0c;保证执行性能压测的环境和生产环境高度一致是执行一次有效性能压测的首要原则。有时候&#xff0c;即便是压测环境和生产环境有很细微的差别&#xff0c;都有可能导致整个压测活动评测出来的结果不准确。 1. 性能环境要考虑的要素 1.1 系…

SMART PLC星三角延时启动功能块(梯形图FC)

这里我们介绍SMART PLC星三角延时启动功能块,SMART PLC的周期定时器功能块请参考下面文章链接: 周期定时器FB_Cycle_time(SCL+梯形图代码)-CSDN博客文章浏览阅读80次。博途PLC定时器指令使用详细介绍请参考下面文章链接:博途PLC IEC定时器编程应用(SCL语言)_scl定时器-CS…

python环境安装教程

1.python解释器安装 python解释器&#xff1a;将书写的代码转换为二进制。 1.打开官网&#xff1a;Welcome to Python.org&#xff0c;点击下载&#xff0c;选择对应的系统和想要下载的python版本进行下载&#xff1a; 2.双击打开下载好的python解释器进行安装&#xff0c;可…

链表(一)----关于单链表的一切细节这里都有

一.链表 1 链表的概念及结构 概念&#xff1a;链表是一种物理存储结构上非连续、非顺序的存储结构&#xff0c;数据元素的逻辑顺序是通过链表中的指针链接次序实现的 。 现实中的链表结构 数据结构中的链表结构 1.链式结构在逻辑上是连续的&#xff0c;但在物理上不一定是…

开启数据库审计 db,extended级别或os级别)并将审计文件存放到/opt/oracle/audit/下

文章目录 1、登录到数据库2、查看审计状态3、创建审计目录4、启用审计5、设置审计文件路径5、再次查看结果 1、登录到数据库 使用SQL*Plus或者其他Oracle数据库客户端登录到数据库。 sqlplus / as sysdba;2、查看审计状态 show parameter audit;目前是DB状态&#xff0c;并且…

matplotlib 绘制双纵坐标轴图像

效果图&#xff1a; 代码&#xff1a; 由于使用了两组y axis&#xff0c;如果直接使用ax.legend绘制图例&#xff0c;会得到两个图例。而下面的代码将两个图例合并显示。 import matplotlib.pyplot as plt import numpy as npdata np.random.randint(low0,high5,size(3,4)) …

2023年最新十大地推拉新接单平台,都是一手单 官签渠道

2023年做拉新推广的地推人员&#xff0c;一定不要错过这十个接单平台&#xff0c;助你轻松找到一手单&#xff0c;这10个平台分别是 1. 聚量推客&#xff1a; “聚量推客”汇聚了众多市场上有的和没有的地推网推拉新接单项目&#xff0c;目前比较火热&#xff0c;我们做地推和…

【leaflet】学习笔记5 自定义控制层、多图层及其控制 重构

▒ 目录 ▒ &#x1f6eb; 导读开发环境 1️⃣ 重构data.js 数据抽取MyMap 面向对象编程继承MyMap类 2️⃣ d5. 自定义控制层、多图层及其控制示例效果自定义控制层多图层及其控制 &#x1f6ec; 文章小结&#x1f4d6; 参考资料 &#x1f6eb; 导读 开发环境 版本号描述文章…

电子病历编辑器源码(Springboot+原生HTML)

一、系统简介 本系统主要面向医院医生、护士&#xff0c;提供对住院病人的电子病历书写、保存、修改、打印等功能。本系统基于云端SaaS服务方式&#xff0c;通过浏览器方式访问和使用系统功能&#xff0c;提供电子病历在线制作、管理和使用的一体化电子病历解决方案&#xff0c…

CTFhub-RCE-过滤cat

查看当前目录&#xff1a;输入:127.0.0.1|ls 127.0.0.1|cat flag_42211411527984.php 无输出内容 使用单引号绕过 127.0.0.1|cat flag_42211411527984.php|base 64 使用双引号绕过 127.0.0.1|c""at flag_42211411527984.php|base64 使用特殊变量绕过 127.0.0.…

计算机毕业设计基于java+springboot+vue的实验室管理系统

项目介绍 系统中的功能模块主要是实现管理员&#xff1b;首页、个人中心、实验室管理、用户管理、实验室申请管理、设备管理、设备报备管理、设备申请管理、消耗品管理、消耗品领取管理、论坛管理、系统管理&#xff0c;用户前台&#xff1b;首页、实验室、设备、消耗品、论坛…

无需公众号实现微信JSSDK分享卡片!Safari浏览器分享到微信自动成卡片!

摘要 要在微信分享卡片&#xff0c;需要接入微信自家的JSSDK&#xff0c;比较麻烦&#xff0c;还需要认证公众号&#xff0c;但是如果你没有这样的条件&#xff0c;那么你也可以试试使用iOS的Safari浏览器轻松实现&#xff0c;只需要在html中加入3个meta即可。 代码 <!DO…

Linux(2):初探

Linux 是什么 Linux 就是一套操作系统。Linux 就是核心与系统呼叫接口那两层。 应用程序不算 Linux。 Linux 提供了一个完整的操作系统当中最底层的硬件控制与资源管理的完整架构&#xff0c; 这个架构是沿袭Unix 良好的传统来的&#xff0c;相当的稳定而功能强大。 在 Lin…

jQuery UI简单的讲解

我们先进入一下问答时间&#xff0c;你都知道多少呢&#xff1f; &#xff08;1&#xff09;什么是jQuery UI 呢&#xff1f; 解答&#xff1a;jQuery UI 是以 jQuery 为基础的开源 JavaScript 网页用户界面代码库。包含底层用户交互、动画、特效和可更换主题的可视控件。我们…

混合云运维解决方案,支持公有云、私有云、信创云等环境

数字时代&#xff0c;政企业务上云已成为大势所趋。虽然上云可为政企用户带来业务应用部署调度更加灵活、资源利用率更高的优点&#xff0c;但因云平台建设处于不同的阶段&#xff0c;且运转过程中包含大量的、不同类型的业务系统和应用场景&#xff0c;在整体云平台的建设中往…
最新文章