【数据结构】——堆 堆的实现、堆排序、TopK问题

news2024/10/7 15:27:42

目录

    • 什么是堆?
    • 堆的分类
    • 堆的实现
    • 堆排序——时间复杂度(N*logN)
    • TopK问题

什么是堆?

什么是堆?
堆是一种叫做完全二叉树的数据结构,分为大根堆和小堆,堆排序也是基于这种结构产生的。
堆是父亲节点和孩子节点之间的关系。

堆的分类

**大根堆:**树任何一个父亲节点的值都大于或等于孩子。
**小根堆:**树任何一个父亲节点的值都小于或者等于孩子。
在这里插入图片描述

堆的逻辑结构是一棵二叉树,物理结构是一维数组,只要是数组就可以看成是一棵完全二叉树。
堆不一定有序

堆的实现

1、堆的结构 堆的初始化和堆的销毁(动态)

我们前面提到堆的存储结构其实是一个数组,所以在堆的结构中,应该定义数组、元素个数和数组容量。

typedef int HPDataType;
typedef struct Heap
{
	HPDataType* a;
	int size;
	int capacity;
}HP;
void HeapInit(HP* php)
{
	assert(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;
}

2、向堆中插入数据

向堆中插入数据,物理上是插入到数组的尾部,空间不够则需要扩容
逻辑上该数据是插入到完全二叉树中。

在这里插入图片描述

插入该节点后,要继续保持该堆是大根堆或者是小根堆,要对插入节点后的堆进行一些检查和调整。
主要检查的方面在孩子和双亲之间,以保证父亲节点大于孩子节点(大根堆)或者父亲节点小于孩子节点(小根堆)。
这里采用向上调整算法

向上调整算法:
前提:添加一个数据之前,该堆是大根堆或者是小根堆
在这里插入图片描述
主要调整孩子和双亲。堆的物理结构是数组,所以很容易可以得到双亲和孩子的下标:
parent = (child-1) / 2
左孩子:child = 2* parent+1
右孩子:child = 2* parent+2

插入节点形成小根堆
1、在调整孩子和双亲时,如果孩子节点值小于双亲,则调整孩子和双亲节点
2、孩子和父亲节点进行交换
3、继续调整孩子和父亲的下标,继续比较孩子节点是否小于双亲,如果小于则继续上述步骤,如果不小于则证明已经是小根堆,跳出循环。
4、循环结束条件:当孩子节点的下标到根节点时,循环结束。

void Swap(HPDataType* p1, HPDataType* p2)
{
	HPDataType tmp = *p1;
	*p1 = *p2;
	*p2 = tmp;
}
void AdjustUp(int* a, int child)
{
	int parent = (child - 1) / 2;
	while (child>0)//孩子节点下标大于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, newcapacity * sizeof(HPDataType));
		if (tmp == NULL)
		{
			perror("realloc fail");
			return;
		}
		php->a = tmp;
		php->capacity = newcapacity;
	}
	//插入数据
	php->a[php->size] = x;
	php->size++;

	AdjustUp(php->a, php->size - 1);//向上调整,从孩子的位置向上调整
}

3、删除堆顶数据

注意:虽然说堆的物理结构是一个数组,但是不能使用挪动删除的方法。
挪动删除不能保证挪动后形成的堆还是有序的(大根堆或者小根堆),挪动后父子关系全乱了
在这里插入图片描述
向下调整算法:
1、假设调小根堆,则从根节点开始调整,调整父节点和其孩子节点
2、如果父亲节点大于孩子节点,则找孩子节点当中最小节点值的孩子节点与父亲节点交换,之后调整父亲节点和孩子节点的下标继续向下调整
3、如果父亲节点小于孩子节点,则满足小根堆的条件,不进行调整。
***前提:***左右子树是大根堆或者小根堆
假设我们要删除堆顶数据:
1、先将堆顶元素和数组最后一个元素进行交换(也就是堆的最后一个元素),删除堆顶元素10
在这里插入图片描述
2、使用向下调整算法调小根堆(大根堆)
在这里插入图片描述
3、每次调整后都要继续向下调整(改变孩子和父亲的下标)
注意:
1、循环结束的条件:每次向下调整都要保证孩子的坐标在数组的范围之内。
2、左孩子存在但是右孩子不一定存在,所以一定要在右孩子存在的情况下,再进行右孩子和左孩子的大小比较

//从父亲(根节点)开始向下调整
void AdjustDown(int* a, int n, int parent)
{
	int child = 2 * parent + 1;//假设左孩子最小
	while (child<n)
	{
		if (a[child + 1] < a[child])//如果右孩子比左孩子更小
		{
			child++;//则最小的孩子+1变成右孩子
		}
		if (child+1<n && a[child] < a[parent])
		{
			Swap(&a[child], &a[parent]);
			parent = child;
			child = 2 * parent + 1;
		}
		else
		{
			break;
		}
	}
}
void HeapPop(HP* php)
{
	assert(php);
	assert(php->size > 0);
	//交换并删除堆顶元素
	Swap(&php->a[0], &php->a[php->size - 1]);
	php->size--;
	//向下调整成小根堆(大根堆)
	AdjustDown(php->a, php->size, 0);
}

在这里插入图片描述
**注意:**我们可以看到屏幕的结果是有序的,但是这并不是排序,只是有序打印。
4、取堆顶元素,堆中元素个数,堆的判空

HPDataType HeapTop(HP* php)
{
	assert(php);
	assert(!HeapEmpty(php));
	return php->a[0];
}
int HeapSize(HP* php)
{
	assert(php);
	return php->size;
}
bool HeapEmpty(HP* php)
{
	assert(php);
	return php->size == 0;
}

堆排序——时间复杂度(N*logN)

1、可以依次取堆顶元素放回数组

void HeapSort(int* a, int n)
{
	HP hp;
	HeapInit(&hp);
	for (int i = 0; i < n; i++)
	{
		HeapPush(&hp, a[i]);
	}
	int i = 0;
	while (!HeapEmpty(&hp))
	{
		int top = HeapTop(&hp);
		a[i++] = top;//
		HeapPop(&hp);
	}
}
int main()
{
	int a[] = { 7,8,3,5,1,9,5,4 };
	HeapSort(a, sizeof(a) / sizeof(int));
	return 0;
}

在这里插入图片描述

可以排序,但是这不是最佳方法。
此方法的弊端:
1、要先有一个堆——建堆N*logN
2、空间复杂度大
3、要来回拷贝数据很麻烦

2、最佳堆排序方法

1、先建堆——向上调整建堆,模拟插入的过程,每次插入都进行一次调整

在这里插入图片描述
2、升序:建大堆
降序:建小堆

降序:建小堆
1、建小堆选出最小的,首尾交换,最小的放到最后的位置
2、把最后一个数据,不看做堆里面的, 向下调整(时间复杂度logN) 选出次小的,再进行交换

在这里插入图片描述
向上调整建堆:
在这里插入图片描述

注意顺序:先交换堆顶元素和end位置的元素,再进行向下调整,最后end–。

代码:

void HeapSort(int* a, int n)
	//向上调整建堆
{	
	for (int i = 1; i < n; i++)
	{
		AdjustUp(a, i);
	}
	int end = n - 1;
	while (end>0)
	{
		Swap(&a[0], &a[end]);
		//再调整,选出次小的数
		AdjustDown(a, end, 0);
		end--;
	}
}
int main()
{
	int a[] = { 7,8,3,5,1,9,5,4 };
	HeapSort(a, sizeof(a) / sizeof(int));
	return 0;
}

在这里插入图片描述
从叶子节点的父亲节点开始向下调整建堆:
在这里插入图片描述

void HeapSort(int* a, int n)
	//向上调整建堆
{	
	/*for (int i = 1; i < n; i++)
	{
		AdjustUp(a, i);
	}*/
	for (int i = (n - 1 - 1) / 2; i >= 0; i--)
	{
		AdjustDown(a, n, i);
	}
	int end = n - 1;
	while (end>0)
	{
		Swap(&a[0], &a[end]);
		//再调整,选出次小的数
		AdjustDown(a, end, 0);
		end--;
	}
}
int main()
{
	int a[] = { 7,8,3,5,1,9,5,4 };
	HeapSort(a, sizeof(a) / sizeof(int));
	return 0;
}

综合比较使用向下调整建堆会比使用向上调整建堆的方法更快

TopK问题

TopK问题实际应用:
1、饿了么、美团美食门店排行榜
2、优质筛选问题
3、专业前10名
4、世界500强

TopK问题方法1:
将给定的N个数建成大堆,再Pop K次,就可以找出最大的前K个
(但是如果N非常大,这种方法就解决不了)

TopK问题最优思路:
1、建立K个数的小堆
2、后面N-K个数,依次比较,如果比堆顶的数据大,就替换他进堆(覆盖堆顶元素进行向下调整)
3、最后这个小堆的值就是TopK

Step1:造数据
打开文件,向文件中写入1000000个数据

void CreateData()
{
	int n = 1000;//数据个数
	srand(time(0));
	const char* file = "data.txt";
	FILE* fin = fopen(file, "w");
	if (fin == NULL)
	{
		perror("fopen errror");
		return;
	}
	for (size_t i = 0; i < n; i++)
	{
		int x = rand() % 1000000;
		fprintf(fin, "%d ", x);
	}
	fclose(fin);
}
int main()
{
	CreateData();
	return 0;
}

在这里插入图片描述

在这里插入图片描述

void CreateData()
{
	int n = 10000;//数据个数
	srand(time(0));
	const char* file = "data.txt";
	FILE* fin = fopen(file, "w");
	if (fin == NULL)
	{
		perror("fopen error");
		return;
	}
	for (size_t i = 0; i < n; i++)
	{
		int x = rand() % 1000000;
		fprintf(fin, "%d\n ", x);
	}
	fclose(fin);
}
void PrintTopK(int k)
{
	const char* file = "data.txt";
	FILE* fout = fopen(file, "r");
	if (fout == NULL)
	{
		perror("fopen error");
		return;
	}
	int* kminheap = (int*)malloc(sizeof(int) * k);//K个数的小堆
	if (kminheap == NULL)
	{
		perror("malloc error");
		return;
	}
	
	for (int i = 0; i < k; i++)
	{
		fscanf(fout, "%d", &kminheap[i]);//读前k个
	}
	//向下调整建小堆
	for (int i = (k - 1 - 1) / 2; i >= 0; i--)
	{
		AdjustDown(kminheap, k, i);
	}
	int val = 0;
	while (!feof(fout))
	{
		fscanf(fout, "%d", &val);//从k+1开始读
		if (val > kminheap[0])
		{
			kminheap[0] = val;//覆盖
			AdjustDown(kminheap, k, 0);
		}
	}
	for (int i = 0; i < k; i++)
	{
		printf("%d ", kminheap[i]);
	}
}
int main()
{
	//CreateData();
	PrintTopK(5);
	return 0;
}

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

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

相关文章

爬虫-Webpack逆向实战 有习题

爬虫-Webpack逆向实战 我只要喝点果粒橙关注IP属地: 青海 0.1052022.04.30 19:21:46字数 4,875阅读 5,142 全文目录 webpack打包是前端js模块化压缩打包常用的手段&#xff0c;特征明显&#xff0c;比如下方的形式的代码就是webpack分发器 // 分发器 !function(x){function …

R语言混合效应(多水平/层次/嵌套)模型及贝叶斯实现技术

回归分析是科学研究中十分重要的数据分析工具。随着现代统计技术发展&#xff0c;回归分析方法得到了极大改进。混合效应模型&#xff08;Mixed effect model&#xff09;&#xff0c;即多水平模型&#xff08;Multilevel model&#xff09;/分层模型(Hierarchical Model)/嵌套…

linuxOPS基础_linux安装配置

Linux系统下载 Linux系统版本选择&#xff1a;CentOS7.6 x64&#xff0c;【镜像一般都是CentOS*.iso文件】 问题&#xff1a;为什么不选择最新版的8 版本&#xff1f; 7.x 目前依然是主流 7.x 的各种系统操作模式是基础 官网&#xff1a;https://www.centos.org/ &#xff0c;…

mysql8.0 修改密码

我使用的是 docker&#xff0c;但是这一期主要是讲解 mysql8 版本修改密码&#xff0c;我相信 linux、windows 和不使用 docker 应该都是可以的。 先说一下我的情况&#xff1a; 我在本地 windows 玩 docker 部署 mysql8.0.25 版本&#xff0c;无问题啦~ 然后我在 linux 里面玩…

模拟strcpy函数,assert,const修饰指针与凉皮男孩的故事

那么好了好了&#xff0c;宝子们&#xff0c;今天给大家介绍一下strcpy函数及其模拟&#xff0c;还有assert&#xff0c;const与凉皮男孩间的爱恨情仇&#xff0c;来吧&#xff0c;开始整活&#xff01;⛳️&#xff08;今天的内容和故事非常的有趣&#xff0c;希望大家一键三连…

黑马学生入职B站1年,晒出21K月薪:我想跳槽华为

现在的Z时代&#xff0c;嘴上说着不要&#xff0c;身体却很诚实。 前两天&#xff0c;黑马发布了《2022年度互联网平均薪资出炉&#xff01;高到离谱&#xff01;》&#xff0c;信息传输、软件和信息技术服务业薪资遥遥领先&#xff01;Z时代举头望着天花板&#xff0c;故作潇…

jsvmp逆向实战x-s、x-t算法还原

jsvmp逆向实战x-s、x-t算法还原 什么是jsvmp定位关键点log插桩日志分析代码还原 什么是jsvmp jsvmp就是将js源代码首先编译为自定义的字节码&#xff0c;只有对应的解释器才能执行这种字节码&#xff0c;这是一种前端代码虚拟化保护技术。 整体架构流程是服务器端通过对JavaS…

Sui基金会联合Tencent Cloud和Numen在香港举办的生态交流会圆满结束

5月24日&#xff0c;由Sui基金会、Tencent Cloud和Numen Cyber联合举办的Sui生态交流会在香港圆满结束。感谢Tencent Cloud为本次活动提供了场地支持。本次活动共吸引了60余名行业同仁线上和线下的参与。 本次活动旨在提升Web3产业对Sui生态的认识&#xff0c;并为生态中的开发…

调用华为API实现图像搜索

调用华为API实现图像搜索 1、作者介绍2、华为API介绍2.1 华为云图像搜索2.2 图像搜索应用场景2.2.1商品图片搜索2.2.2版权图片搜索 2.3 调用华为API实现图像标签 3、实验过程3.1完整代码3.2运行结果3.3常见错误 1、作者介绍 张勇进&#xff0c;男&#xff0c;西安工程大学电子…

通过Python的pdfplumber库将pdf转为图片

文章目录 前言一、pdfplumber库是什么&#xff1f;二、安装pdfplumber库三、查看pdfplumber库版本四、pdf素材五、将pdf转为图片1.引入库2.定义pdf路径3.打开PDF文件4.遍历每一页5.将PDF页面转换为Image对象6.将Image对象保存为图片文件7.效果 总结 前言 大家好&#xff0c;我是…

ChatGLM-6B之SSE通信(Server-sent Events)

写这篇博客还是很激动开心的&#xff0c;因为是我经过两周的时间&#xff0c;查阅各个地方的资料&#xff0c;经过不断的代码修改&#xff0c;不断的上传到有显卡的服务器运行才得出的可行的接口调用解决方案&#xff0c;在这里记录并分享一下。 研究历程&#xff08;只是感受&…

更适合iPhone的手柄,按键手感真不赖,LEADJOY M1B上手

很多朋友平时玩手游的时候&#xff0c;操作体验往往不是很好&#xff0c;特别是到了夏天&#xff0c;手机玩久了总是热气腾腾的&#xff0c;对此&#xff0c;只需要配上一副手游手柄&#xff0c;就可以获得媲美掌机的游戏体验。最近我就在用一款LEADJOY M1B游戏手柄&#xff0c…

如何选择语音芯片?主流语音方案如何选,九芯电子来推荐

市场分析 近年来&#xff0c;随着我国半导体的不断发展和技术领域的不断突破&#xff0c;语音芯片实现了越来越多的国产化。其中涌现出的像NVD系列、NRK330X系列等不乏国产优秀产品。凭借其优秀的性能、设计&#xff0c;赢得了市场上的好评如潮。 对比分析 OTP语音芯片&#…

webAPI学习笔记3——BOM浏览器对象模型

目录 1、BOM概述 1.1 什么是 BOM 1.2 BOM 的构成 2. window 对象的常见事件 2.1 窗口加载事件 2.2 调整窗口大小事件 3. 定时器 3.1 两种定时器 3.2 setTimeout() 定时器 案例&#xff1a; 5秒后自动关闭的广告 3.3 停止 setTimeout() 定时器 3.4 setInterval() 定时…

解决不联网环境pip安装librosa、numba、llvmlite报错和版本兼容问题

项目场景&#xff1a; 项目是需要在内网不联网环境部署GitHub上一个有关音频、视频处理的深度学习Python工程&#xff0c;因此许多包需要下载好wheel包或tar包后在内网环境安装。 这个过程遇到了许多兼容性问题引起的报错。Python版本与librosa、numba、llvmlite版本兼容问题…

小狗避障-第14届蓝桥杯省赛Scratch中级组真题第4题

[导读]&#xff1a;超平老师的《Scratch蓝桥杯真题解析100讲》已经全部完成&#xff0c;后续会不定期解读蓝桥杯真题&#xff0c;这是Scratch蓝桥杯真题解析第139讲。 小狗避障&#xff0c;本题是2023年5月7日举行的第14届蓝桥杯省赛Scratch图形化编程中级组编程第4题&#xf…

从代码审计的角度分析 Ruoyi v4.7.6 的任意文件下载漏洞

前言 Ruoyi 的 v4.7.6 是 2022 年 12 月 16 日发布的一个版本&#xff0c;而任意文件下载漏洞实际上 12 月底的时候就已经爆出了&#xff0c;也陆续有一些文章在写这个漏洞&#xff0c;但是 Ruoyi 一直没有更新修复。 上月中旬&#xff08;2023 年 5 月&#xff09;&#xff0c…

内网渗透(八十四)之ADCS配置启用基于SSL的LDAP(LDAPS)

ADCS配置启用基于SSL的LDAP(LDAPS) 打开AD CS,选择证书颁发机构 选择证书模板,右键管理 选择Kerberos身份验证,右键 复制模板 然后会有一个Kerberos身份验证的副本,右键更改名称,更改为LDAPS 选择LDAPS,右键属性 设置模板属性,请求处理——>允许导出私钥(O) 创建证书…

最快实现一个自己的扫地机

​ 作者&#xff1a;良知犹存 转载授权以及围观&#xff1a;欢迎关注微信公众号&#xff1a;羽林君 或者添加作者个人微信&#xff1a;become_me 扫地机介绍 扫地机器人行业本质是技术驱动型行业&#xff0c;产品围绕导航系统的升级成为行业发展的主旋律。按功能划分&a…

【武汉万象奥科】瑞芯微RK3568芯片

▎产品展示 RK3568核心板是基于Rockchip的RK3568设计的一款高性能核心板。该处理器集成了最新的高性能CPU、GPU&#xff0c;并拥有丰富的接口&#xff0c;非常适用于工业自动化控制、人机界面、中小型医疗分析器、电力等多种行业应用。 ▎RK3568产品特点 ▎高性能处理器 ○ 采用…