数据结构-----堆(完全二叉树)

news2024/11/24 3:19:31

 目录

前言

一.堆

1.堆的概念

2.堆的存储方式

二.堆的操作方法

1.堆的结构体表示

2.数字交换接口函数

3.向上调整(难点)

4.向下调整(难点)

5.创建堆

 6.堆的插入

 7.判断空

8.堆的删除

 9.获取堆的根(顶)元素

10.堆的遍历

 11.销毁堆

完整代码

三.堆的应用(堆排序)

1.算法介绍

2.基本思想

3.代码实现

4.算法分析


前言

         今天我们开始学习一种二叉树,没错,那就是完全二叉树,完全二叉树又叫做堆,在此之前我们简单介绍过了完全二叉树的概念(链接:数据结构-----树和二叉树的定义与性质_灰勒塔德的博客-CSDN博客),这种类型的二叉树又有什么特点呢?代码怎么去实现呢?应用有那些呢?下面就一起来看看吧!

一.堆

1.堆的概念

堆(heap)是计算机科学中一类特殊的数据结构的统称。堆通常是一个可以被看做一棵树的数组对象,物理层面上是一个数组,逻辑上是一个完全二叉树。堆总是满足下列性质:

  • 堆中某个结点的值总是不大于或不小于其父结点的值;

  • 堆总是一棵完全二叉树。

  • 满足任意父节点都大于子节点的称作为大堆

  • 满足任意子节点都大于父节点的称作为小堆

  • tip:(下文会以大堆的创建为示例)

如图所示:

 

2.堆的存储方式

堆的储存原则是从上到下,从左到右,也就是说先有上面的父节点才会有子节点,先有左子节点,才会有右子节点 ,所以堆可以去通过一个数组完整的表示出来,如下图所示:

二.堆的操作方法

以下是一个堆要实现的基本功能,下面我会一一去详细解释说明

void swap(DataType* a, DataType* b);//交换数据

void Adjust_Up(DataType* data, int child, int n);//向上调整

void Adjust_Down(DataType* data, int parent, int n);//向下调整

void Heap_Create(Heap* hp, DataType* data, int n);//创建堆

bool isEmpty(Heap* hp);//判断空

void Heap_Insert(Heap* hp, DataType x);//堆的插入

void Heap_Del(Heap* hp);//堆的删除操作

DataType Heap_Root(Heap* hp);//获取根元素

void Heap_show(Heap* hp);//堆的遍历

void Heap_Destory(Heap* hp);//堆的销毁

1.堆的结构体表示

#include<stdio.h>
#include<stdlib.h>
#include<assert.h>
#define Maxsize 50

//顺序结构
//堆(完全二叉树)
typedef int DataType;	//定义数据的类型
typedef struct Heap
{
	int size;	//当前节点数量
	int capacity;	//最大容量
	DataType* data;	//数据储存地址
}Heap;

2.数字交换接口函数

//数据交换接口
void swap(DataType* a, DataType* b) {
	DataType temp = *a;
	*a = *b;
	*b = temp;
}

3.向上调整(难点)

        创建大堆时,向上调整的目的是,在有子节点位置的情况下,进行与父节点的大小比较,如果子节点大于父节点,那么就进行交换,然后新的子节点就是上一个的父节点,依次这样比较下去,最后到根节点为止,如图所示:

 代码实现:

//向上调整
void Adjust_Up(DataType* data, int child, int n) {
	int parent = (child - 1) / 2;
	while (child > 0) {
		//如果子节点大于父节点就进行数值交换,然后此时的子节点就是前一个父节点,再找到
		//新的父节点,继续进行同样的操作,直到根节点为止
		if (data[child] > data[parent])
		{
			swap(&data[child], &data[parent]);
			child = parent;
			parent = (child - 1) / 2;
		}
		else
		{
			break;
		}
	}
}

4.向下调整(难点)

        同样的还有向下调整,如果有了当前的父节点位置,那么就要跟子节点进行比较,但是子节点有左和右子节点,所以左右子节点也要去比较,取到其中比较大的子节点与父节点比较,如果这个字节点大于父节点的话,那就进行数字交换,然后新的父节点就是上一个的子节点,依次往下遍历进行同样的操作。

代码实现: 

//向下调整
void Adjust_Down(DataType* data, int parent, int n) {
	int child = parent * 2 + 1;
	while (child <n ) {
		if (child+1 < n && data[child] < data[child+1])
		{
			//如果右子节点大于左子节点,那就child+1,选中到右子节点
			child++;
		}
		if (data[child] > data[parent]) {
			//同样的,有了当前父节点,然后找到子节点,进行向下遍历调整操作
			swap(&data[child], &data[parent]);
			parent = child;
			child = parent * 2 + 1;
		}
		else
		{
			break;
		}
	}
}

5.创建堆

已有一个数组{ 5,1,2,3,6,4,8 },怎么把这个数组放入堆里面呢?同样的,空间申请去申请到一块连续的空间,然后依次把数据存入到这个数组里面去,最后进行向下调整,以达到堆的形式。

放入堆之后如下图所示: 

代码实现:

//创建堆
void Heap_Create(Heap* hp, DataType* data, int n) {
	assert(hp);
	hp->data = (DataType*)malloc(sizeof(DataType) * n);
	if (!hp->data) {
		printf("ERROR\n");
		exit(-1);
	}
	for (int i = 0; i < n; i++) {
		hp->data[i] = data[i];//赋值
	}
	hp->size = n;
	hp->capacity = Maxsize;
	for (int j = (n - 1) / 2; j >= 0; j--) {
		//创建完成了之后,就要进行向下调整
		Adjust_Down(hp->data, j ,hp->size);
	}
}

 6.堆的插入

堆的插入,就是在堆的最后面去添加一个元素,添加完成之后,就要去进行向上调整操作,如下图所示:

代码实现: 

//堆的插入
void Heap_Insert(Heap* hp, DataType x) {
	assert(hp);
	//如果此时的堆空间满了,那么就要去扩容空间
	if (hp->size == hp->capacity) {
		DataType* temp = (DataType*)realloc(hp->data,sizeof(DataType)  * (hp->capacity+1));//追加1个空间
		if (!temp) {
			printf("ERROR\n");
			exit(-1);
		}
		hp->data = temp;
		hp->data[hp->size] = x;
		hp->size++;
		hp->capacity++;
	}
	else
	{
		hp->data[hp->size] = x;
		hp->size++;
	}
	Adjust_Up(hp->data, hp->size - 1, hp->size);//插入后进行向上调整
}

 7.判断空

//判断空
bool isEmpty(Heap* hp) {
	assert(hp);
	return hp->size == 0;
}

8.堆的删除

堆的删除操作是删除掉根节点,过程是,先把最后一个节点与根节点进行交换,然后重新进行向下调整。(堆的删除操作,删除掉的是根节点!

代码实现: 

//堆的删除,删除根节点
void Heap_Del(Heap* hp) {
	assert(hp);
	if (!isEmpty(hp)) {
		swap(&hp->data[hp->size - 1], &hp->data[0]);//根节点和尾节点进行交换
		hp->size--;
		Adjust_Down(hp->data, 0, hp->size);//向下调整
	}
}

 9.获取堆的根(顶)元素

//获取堆顶元素
DataType Heap_Root(Heap* hp) {
	assert(hp);
	if (!isEmpty(hp))
		return hp->data[0];
	else
		exit(0);
}

10.堆的遍历

堆的遍历就直接按照数组的顺序去遍历就行了,完全二叉树的逻辑上是从上到下,从左到右去遍历的,代码如下:

//输出堆元素(按照顺序)
void Heap_show(Heap* hp) {
	assert(hp);
	if (isEmpty(hp)) {
		printf("The Heap is etmpy\n");
		return;
	}
	for (int i = 0; i < hp->size; i++)
		printf("%d ", hp->data[i]);
}

 11.销毁堆

//堆的销毁
void Heap_Destory(Heap* hp) {
	assert(hp);
	hp->size = hp->capacity = 0;
	free(hp);//释放空间
}

完整代码

#include<stdio.h>
#include<stdlib.h>
#include<assert.h>
#define Maxsize 50

//顺序结构
//堆(完全二叉树)
typedef int DataType;	//定义数据的类型
typedef struct Heap
{
	int size;	//当前节点数量
	int capacity;	//最大容量
	DataType* data;	//数据储存地址
}Heap;


//数据交换接口
void swap(DataType* a, DataType* b) {
	DataType temp = *a;
	*a = *b;
	*b = temp;
}

//向上调整
void Adjust_Up(DataType* data, int child, int n) {
	int parent = (child - 1) / 2;
	while (child > 0) {
		//如果子节点大于父节点就进行数值交换,然后此时的子节点就是前一个父节点,再找到
		//新的父节点,继续进行同样的操作,直到根节点为止
		if (data[child] > data[parent])
		{
			swap(&data[child], &data[parent]);
			child = parent;
			parent = (child - 1) / 2;
		}
		else
		{
			break;
		}
	}
}

//向下调整
void Adjust_Down(DataType* data, int parent, int n) {
	int child = parent * 2 + 1;
	while (child <n ) {
		if (child+1 < n && data[child] < data[child+1])
		{
			//如果右子节点大于左子节点,那就child+1,选中到右子节点
			child++;
		}
		if (data[child] > data[parent]) {
			//同样的,有了当前父节点,然后找到子节点,进行向下遍历调整操作
			swap(&data[child], &data[parent]);
			parent = child;
			child = parent * 2 + 1;
		}
		else
		{
			break;
		}
	}
}

//创建堆
void Heap_Create(Heap* hp, DataType* data, int n) {
	assert(hp);
	hp->data = (DataType*)malloc(sizeof(DataType) * n);
	if (!hp->data) {
		printf("ERROR\n");
		exit(-1);
	}
	for (int i = 0; i < n; i++) {
		hp->data[i] = data[i];//赋值
	}
	hp->size = n;
	hp->capacity = Maxsize;
	for (int j = (n - 1) / 2; j >= 0; j--) {
		//创建完成了之后,就要进行向下调整
		Adjust_Down(hp->data, j ,hp->size);
	}
}

//判断空
bool isEmpty(Heap* hp) {
	assert(hp);
	return hp->size == 0;
}

//堆的插入
void Heap_Insert(Heap* hp, DataType x) {
	assert(hp);
	//如果此时的堆空间满了,那么就要去扩容空间
	if (hp->size == hp->capacity) {
		DataType* temp = (DataType*)realloc(hp->data,sizeof(DataType)  * (hp->capacity+1));//追加1个空间
		if (!temp) {
			printf("ERROR\n");
			exit(-1);
		}
		hp->data = temp;
		hp->data[hp->size] = x;
		hp->size++;
		hp->capacity++;
	}
	else
	{
		hp->data[hp->size] = x;
		hp->size++;
	}
	Adjust_Up(hp->data, hp->size - 1, hp->size);//插入后进行向上调整
}

//堆的删除,取出根节点
void Heap_Del(Heap* hp) {
	assert(hp);
	if (!isEmpty(hp)) {
		swap(&hp->data[hp->size - 1], &hp->data[0]);//根节点和尾节点进行交换
		hp->size--;
		Adjust_Down(hp->data, 0, hp->size);//向下调整
	}
}


//获取堆顶元素
DataType Heap_Root(Heap* hp) {
	assert(hp);
	if (!isEmpty(hp))
		return hp->data[0];
	else
		exit(0);
}

//输出堆元素(按照顺序)
void Heap_show(Heap* hp) {
	assert(hp);
	if (isEmpty(hp)) {
		printf("The Heap is etmpy\n");
		return;
	}
	for (int i = 0; i < hp->size; i++)
		printf("%d ", hp->data[i]);
}

//堆的销毁
void Heap_Destory(Heap* hp) {
	assert(hp);
	hp->size = hp->capacity = 0;
	free(hp);//释放空间
}

三.堆的应用(堆排序)

1.算法介绍

        堆排序(Heapsort)是指利用堆这种数据结构所设计的一种排序算法。堆积是一个近似完全二叉树的结构,并同时满足堆积的性质:即子结点的键值或索引总是小于(或者大于)它的父节点。

2.基本思想

利用大顶堆(小顶堆)堆顶记录的是最大关键字(最小关键字)这一特性,使得每次从无序中选择最大记录(最小记录)变得简单。

① 将待排序的序列构造成一个最大堆,此时序列的最大值为根节点
② 依次将根节点与待排序序列的最后一个元素交换
③ 再维护从根节点到该元素的前一个节点为最大堆,如此往复,最终得到一个递增序列

3.代码实现

#include<stdio.h>
#include<assert.h>
//数据交换接口
void swap(int *a, int *b) {
	int temp = *a;
	*a = *b;
	*b = temp;
}

//向下调整
void Adjust_Down(int* data, int parent, int n) {
	int child = parent * 2 + 1;
	while (child < n) {
		if (child + 1 < n && data[child] < data[child + 1])
		{
			//如果右子节点大于左子节点,那就child+1,选中到右子节点
			child++;
		}
		if (data[child] > data[parent]) {
			//同样的,有了当前父节点,然后找到子节点,进行向下遍历调整操作
			swap(&data[child], &data[parent]);
			parent = child;
			child = parent * 2 + 1;
		}
		else
		{
			break;
		}
	}
}

//堆排序算法
void Heap_sort(int* arr, int n) {
	assert(arr);
	for (int i = (n - 2) / 2; i >= 0; i--) {
		Adjust_Down(arr, i, n);
	}//先形成最大堆

	int end = n - 1;
	//从小到大排序
	while (end > 0) {
		swap(&arr[0], &arr[end]);
		Adjust_Down(arr, 0, end);
		end--;	//此时最后一个也就是当前的最大值已经排序好了
	}
}

int main() {
	int a[9] = { 5,1,2,3,6,4,8,2,10 };
	Heap_sort(a, sizeof(a) / sizeof(int));
	for (int i = 0; i < sizeof(a) / sizeof(int); i++) {
		printf("%d ", a[i]);
	}
}
//输出
//1 2 2 3 4 5 6 8 10

4.算法分析

  • 平均时间复杂度:O(nlogn)
  • 最佳时间复杂度:O(nlogn)
  • 最差时间复杂度:O(nlogn)
  • 稳定性:不稳定

 以上就是本期的内容,我们下次见!

 分享一张壁纸:

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

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

相关文章

Linux中sudo命令的添加和操作

使用 sudo分配权限 &#xff08;1&#xff09;修改/etc/sudoers 文件分配文件 # chmod 740 /etc/sudoers # vi /etc/sudoers 找到这行&#xff1a;root ALL(ALL) ALL, 在这行下面添加 xxx ALL(ALL) ALL (这里的xxx就是你的普通用户&#xff0c;而ruice就是我的普通用户 ) 编…

nginx部署多个项目

前言 实现在一台服务器上使用nginx部署多个项目的方法 查看并修改nginx安装的默认配置文件 在 Linux 操作系统中&#xff0c;Nginx 在编译安装时默认的配置文件路径是 /usr/local/nginx/conf/nginx.conf。 如果是通过发行版的包管理器安装&#xff0c;则默认的配置文件路径可能…

Cesium加载海量点数据

目录 项目地址实现效果实现方法 项目地址 https://github.com/zhengjie9510/webgis-demo 实现效果 实现方法 const pointCollection viewer.scene.primitives.add(new Cesium.PointPrimitiveCollection({ blendOption: Cesium.BlendOption.OPAQUE })); for (let longitude …

dToF 和iToF傻傻分不清楚? pmd flexx2 见你所不能见

在现下数字化越来越成熟的时代,「3D感知」无疑在生活中成为了一种基础、甚至必须的需求:从人手至少一台的智能手机,到居家生活常见的扫地机器人,再到高科技或医疗产业会使用的无人机或工业机器人,如今高度科技化和便捷的世界,处处都需要比肉眼更加强大的立体视角来进行可…

如何搭建Linux环境

W...Y的主页 &#x1f60a; 代码仓库分享 &#x1f495; 当我们想要搭建一个Linux系统&#xff0c;我们应该怎么使用呢&#xff1f; 今天我就带领大家搭建Linux系统&#xff01;&#xff01;&#xff01; 目录 Linux环境安装 双系统&#xff08;不推荐&#xff09; poww…

计算机毕设 图像识别-人脸识别与疲劳检测 - python opencv

文章目录 0 前言1 课题背景2 Dlib人脸识别2.1 简介2.2 Dlib优点2.3 相关代码2.4 人脸数据库2.5 人脸录入加识别效果 3 疲劳检测算法3.1 眼睛检测算法3.2 打哈欠检测算法3.3 点头检测算法 4 PyQt54.1 简介4.2相关界面代码 5 最后 0 前言 &#x1f525; 这两年开始毕业设计和毕业…

14:00面试,14:06就出来了,问的问题过于变态了。。。

从小厂出来&#xff0c;没想到在另一家公司又寄了。 到这家公司开始上班&#xff0c;加班是每天必不可少的&#xff0c;看在钱给的比较多的份上&#xff0c;就不太计较了。没想到5月一纸通知&#xff0c;所有人不准加班&#xff0c;加班费不仅没有了&#xff0c;薪资还要降40%…

【NAD NADPH; FMN FAD ; NMN -化学】

NAD Nicotinamide adenine dinucleotide nicotinamide 烟酰胺 NAD NADPH 烟酰胺腺嘌呤二核苷酸 nucleosidase Nicotinamide adenine dinucleotide NMN&#xff08;烟酰胺单核苷酸&#xff09;简介 NMN全名 nicotinamide mononucleotide&#xff0c;即 烟酰胺单…

【全国科普日,共筑眼健康】景远眼科科普志愿者服务队在行动

今年是第21个全国科普日&#xff0c;为深入贯彻落实新时代中国特色社会主义思想&#xff0c;针对不同群体的需求&#xff0c;践行科普为民&#xff0c;共创美好生活&#xff0c;9月14日至9月24日期间&#xff0c;景远眼科科普志愿者服务队把丰富多样的内容、常有常新的活动带到…

IP归属地应用的几个主要特点

作为一款优秀的IP地址定位工具&#xff0c;主题IP归属地的应用无疑是最好的选择之一。该应用可以将您需要查询的IP地址快速定位到所在的具体物理位置&#xff0c;并提供详细的地址和地图信息。接下来&#xff0c;让我们一起来看一看IP归属地应用的几个主要特点&#xff1a; 1. …

Python:求求按规范写我

B站|公众号&#xff1a;啥都会一点的研究生 写在前面 代码被阅读的次数远多于编写的次数 我们可能花费很多时间来编写一段代码&#xff0c;一旦完成后大概率就再不会重新写它。当这段代码不仅是自己用时&#xff0c;就得注意了&#xff0c;每次自己或其他人浏览&#xff0c;需…

2023.9.23(对这一年过去几个月的总结)

这个时间点杭州正在开亚运会&#xff0c;周六&#xff0c;大周&#xff0c;难得的大周&#xff0c;早上在公司健身房跑完步&#xff0c;就来工位看书了。 反思一下&#xff1a; 技术&#xff1a; 今年在技术学习上的目标&#xff0c;达成率是在太低&#xff0c;但看文章输出来…

tcp/ip协议2实现的插图,数据结构

&#xff08;1&#xff09;以上是插图第2章和3章 的 mbuf 与 ifnet 与 ifaddr 与 le_softc 与 sockaddr_dl结构体 谢谢

生信技巧 | GNU 并行操作

简介 有些分析需要很长时间&#xff0c;因为它在单个处理器上运行并且有大量数据需要处理。如果数据可以分成块并单独处理&#xff0c;那么问题就被认为是可并行化的。 数据并行情况 当文件的每一行都可以单独处理时 基因组的每条染色体都可以单独处理 组件的每个脚手架都可以单…

安全学习_开发相关_JavaEE过滤器监听器简单了解

文章目录 Web应用运行流程图 JavaEE-过滤器-Filter过滤器概述&作用过滤器相关安全测试场景 JavaEE-监听器-Listener监听器作用&#xff1a;监听器相关安全测试场景 过滤器和监听器&#xff0c;主要对安全测试有影响的是过滤器&#xff0c;监听器只是在对代码进行逻辑分析时…

【数学计算】使用mathematica计算圆周率π

【数学计算】使用mathematica计算圆周率 计算π的意义如何通过函数表示圆函数几何图形 计算圆周率 计算π的意义 在我们计算圆面积时&#xff0c;通常需要知道面积和已知变量之间的共性关系&#xff0c;以便能够将圆面积计算进行理论研究&#xff0c;以推广到所有的圆面积、周…

Golang代码漏洞扫描工具介绍——trivy

Golang代码漏洞扫描工具介绍——trivy Golang作为一款近年来最火热的服务端语言之一&#xff0c;深受广大程序员的喜爱&#xff0c;笔者最近也在用&#xff0c;特别是高并发的场景下&#xff0c;golang易用性的优势十分明显&#xff0c;但笔者这次想要介绍的并不是golang本身&a…

JMeter之脚本录制

【软件测试面试突击班】如何逼自己一周刷完软件测试八股文教程&#xff0c;刷完面试就稳了&#xff0c;你也可以当高薪软件测试工程师&#xff08;自动化测试&#xff09; 前言&#xff1a; 对于一些JMeter初学者来说&#xff0c;录制脚本可能是最容易掌握的技能之一。…

C/C++开发,opencv阀值操作

目录 一、OpenCV-阀值操作 1.1阀值操作函数threshold 1.2threshold的操作类型 1.3Otsu算法 二、样例开发 2.1 Makefile 2.2 main.cpp 2.3 运行效果 三、OpenCV-自适应阀值操作 3.1 自适应阀值操作函数-adaptiveThreshold 3.2 样例开发 一、OpenCV-阀值操作 1.1阀值操…

渗透测试中的前端调试(上)

一、前言 前端调试是安全测试的重要组成部分。它能够帮助我们掌握网页的运行原理&#xff0c;包括js脚本的逻辑、加解密的方法、网络请求的参数等。利用这些信息&#xff0c;我们就可以更准确地发现网站的漏洞&#xff0c;制定出有效的攻击策略。前端知识对于安全来说&#xff…