【数据结构】堆的实现(向上、下调整比较,复杂度,堆排序,Top-K问题)

news2024/12/25 12:39:10

文章目录

  • 一、堆的实现
      • 1、堆的概念
      • 2、堆的性质
      • 3、堆的实现
  • 堆的创建(向上、下调整比较)
  • 堆排序
  • Top-K(读取文件当中的数据)

一、堆的实现

1、堆的概念

如果有一个关键码的集合K = {k0,k1, k2,…,kn-1},把它的所有元素按完全二叉树的顺序存储方式存储
在一个一维数组中,并满足:Ki <= K2i+1 且 Ki<=K2i+2 ,则称为小堆(或大堆)。
将根节点最大的堆叫做最大堆或大根堆,根节点最小的堆叫做最小堆或小根堆。
在这里插入图片描述

2、堆的性质

1️⃣堆中某个节点的值总是不大于或不小于其父节点的值;
2️⃣堆总是一棵完全二叉树。

3、堆的实现

在这里主要是将代码展现在这里,算法实现将会在后面详细实现。
代码中包括初始化打印插入数据删除数据判断空销毁堆输出最大值最小值

//初始化
void HpInit(Hp* php)
{
	assert(php);
	php->a = NULL;
	php->capacity = php->size = 0;
}
//插入数据
void HpPush(Hp* php, HPDataType x)
{
	assert(php);
	//判断是否需要扩容
	if (php->capacity == php->size)
	{
		int newcapacity = php->capacity == 0 ? 4 : php->capacity * 2;
		//这里使用三目操作符更便捷,
		HPDataType* tmp = (HPDataType*)realloc(php->a, sizeof(HPDataType) * newcapacity);
		//动态开辟空间
		if (tmp == NULL)
		{
			perror("malloc");
			exit(-1);
		}
		php->capacity = newcapacity;
		php->a = tmp;
	}
	php->a[php->size] = x;
	php->size++;

	AdjustUp(php->a, php->size - 1);
	//向上调整
}
//向上调整
void AdjustUp(HPDataType* a, int child)
{
	//int child = n;
	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 swap(HPDataType* a, HPDataType* b)
{
	HPDataType temp = *a;
	*a = * b;
	*b = temp;
}
//删除数据
void HpPop(Hp* php)
{
	assert(php);
	assert(!HpEmpty(php));
	swap(&php->a[0], &php->a[php->size-1]);
	//将最后一个元素和第一个元素进行交换
	php->size--;
	AdjustDown(php->a,php->size,0);
	//向下调整
}
void AdjustDown(HPDataType* a, int n, int parent)
{
	int child = parent * 2 + 1;
	while (child<n)
	{
		if (child + 1 < n &&a[child] > a[child + 1])//建大小堆这里也是需要改的,现在是建小堆
		{
			child++;
		}
		if (a[parent] > a[child])
		{
			swap(&a[parent], &a[child]);
			parent = child;
			child = parent * 2 + 1;
		}
		else
		{
			break;
		}
	}	
}
//销毁堆
void HpDestroy(Hp* php)
{
	assert(php);
	free(php->a);
	php->a = NULL;
	php->size = php->capacity = 0;
}
//打印数据
void HpPrint(Hp* php)
{
	assert(php);
	for (int i = 0; i < php->size; ++i)
	{
		printf("%d ",php->a[i]);
	}
	printf("\n");
}
//判断是否为空
bool HpEmpty(Hp* php)
{
	assert(php);
	return php->size == 0;
}
//返回数据个数
int HpSize(Hp* php)
{
	assert(php);
	return php->size;
}
//返回堆顶数据
HPDataType HpTop(Hp* php)
{
	assert(php);
	assert(php->size > 0);
	return php->a[0];
}
//将数组进行建堆                                                 
void HpCreat(Hp* php, HPDataType* a, int n)
{
	assert(php);
	HPDataType* tmp = (HPDataType*)malloc(sizeof(HPDataType) * n);
	if (tmp == NULL)
	{
		perror("malloc");
		exit(-1);
	}
	php->a = tmp;
	memcpy( php->a,a, sizeof(HPDataType) * n);
	php->size = php->capacity = n;
	for (int i = (n - 1 - 1) / 2; i >= 0; --i)
	{
		AdjustDown(php->a, n, i);
	}
}

在这里就要讨论一个问题了,将数组变成堆为什么用向下调整,为什么不用向上调整?
下面就将具体的介绍一下为什么用向下调整,并且说明堆是如何创建的

堆的创建(向上、下调整比较)

这个标题主要是讲堆的创建包括原理,向上、下调整比较,复杂度等问题。
我们知道堆在内存当中是以数组的形式来储存的,逻辑结构是完全二叉树。
实现原理:
在这里插入图片描述
代码实现如下:
下面所调用的函数在上面的有实现的代码。

//向下调整建堆
	for (int i = (n - 1 - 1) / 2; i >= 0; --i)
	{
		AdjustDown(php->a, n, i);
	}
	//向上调整建堆
	for (int i = 1; i <n; i++)
	{
		AdjustUp(php->a,i);
	}

接下来证明一下为什么向下调整比向上调整要好!!!

向下调整证明:时间复杂度为O(N)
这里是引用
向上调整证明:时间复杂度为O(NlogN)
在这里插入图片描述
它们的区别:因为树的结构是逐层递增的,向下调整开始的节点少调整的次数多,到后面节点多调整少
而向上调整开始的时候节点少调整的也少,到后面节点多调整的也多,它们之间的差距就在这里。
当节点数N=1000时,向上调整要10000次,而向下调整要1000次他们之间的差距还是很大的,这也就是选择向下调整的原因。

堆排序

在之前建完堆之后我们可以看到并不是有序数组,所以就需要堆排序了。
堆排序原理:例如实现升序数组,在建完大堆之后,将堆顶的数据和最后一个数据进行交换,然后在堆顶进行向下调整(并不包括下面进行交换位置的数据)所以每次堆顶向下调整都会少一个数据。就这样最大的数据,次大的数据就一个一个的交换在下面了。
并不需要开辟新的空间空间复杂度:O(1)
时间复杂度:O(N*logN)
N(建堆) + N* logN(排序) = N*logN 将N省略了
请添加图片描述

//堆排序
void HpSort(HPDataType *a,int n)
{    //建堆
	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;
	}
}

Top-K(读取文件当中的数据)

TOP-K问题:即求数据结合中前K个最大的元素或者最小的元素,一般情况下数据量都比较大。
对于Top-K问题,能想到的最简单直接的方式就是排序,但是:如果数据量非常大,排序就不太可取了(可能
数据都不能一下子全部加载到内存中)。最佳的方式就是用堆来解决,基本思路如下:

1. 用数据集合中前K个元素来建堆

  • 前k个最大的元素,则建小堆
  • 前k个最小的元素,则建大堆

2. 用剩余的N-K个元素依次与堆顶元素来比较,不满足则替换堆顶元素

  • 将剩余N-K个元素依次与堆顶元素比完之后,堆中剩余的K个元素就是所求的前K个最小或者最大的元素。

下面我们来对比一下上面的方法和在之前堆中实现的一个一个的Pop哪更快
K为需要找出K个数据,N为数据总个数
上面方法:K(建堆) + (N-k)logK(剩下的数据进行的比较和调整,复杂度取最坏结果)
时间复杂度:O(NlogK)
空间复杂度:O(k)
使用Pop方法:N(建堆) + K
logN
时间复杂度:O(N+K*logN)
空间复杂度:O(1)
实现代码如下:
使用rand函数创造一些随机数据存放在文件中,在文件中找TopK。

void HeapTest4()
{
	int k, n;
	printf("请输入取TOP的数量和总数据的大小:");
	scanf("%d%d",&k,&n);
	srand(time(0));
	FILE* pf = fopen("test.txt","w");
	if (pf == NULL)
	{
		perror("fopen");
		exit(-1);
	}
	for (int i = 0; i < n; i++)
	{
		int sat = rand() % 10000;
		//创造随机数
		fprintf(pf,"%d\n",sat);
	}
	fclose(pf);
	pf = NULL;
	FILE* ppf = fopen("test.txt", "r");
	if (ppf == NULL)
	{
		perror("fopen");
		exit(-1);
	}
	int* tmp = (int*)malloc(sizeof(int)*k);
	if (tmp == NULL)
	{
		perror("malloc");
		exit(-1);
	}
	int j = 0;
	for (; j < k;++j)
	{
		fscanf(ppf,"%d",&tmp[j]);
	}
	for (int i = (k - 1 - 1) / 2; i >= 0; --i)
	{
		AdjustDown(tmp, k, i);
		//建堆(向下调整)
	}

	int val = 0;
	while (fscanf(ppf, "%d", &val) != EOF)
	{
		if (val > tmp[0])
		{
			tmp[0] = val;
			AdjustDown(tmp, k, 0);
			//将剩下的数据进行比较,以及调整。
		}
	}
	for (int i = 0; i < k; i++)
	{
		printf("%d ",tmp[i]);
	}

	fclose(ppf);
	ppf = NULL;
}

输出结果如下

最后:文章有什么不对的地方或者有什么更好的写法欢迎大家在评论区指出

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

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

相关文章

餐饮行业的成本率与毛利率

1、成本率与毛利率 成本率是指成本量与营业额&#xff08;销售额&#xff09;之间的比率&#xff0c;表示实现一定量的销售额需要多少比例的成本资源消耗。 成本率越低&#xff0c;表示企业实现单位业绩付出的资源代价越小&#xff0c;释放出的收益空间越大&#xff1b;相反&a…

docker安装nginx以及(nginxWebUI和nginx-gui图形化界面的使用)

一、docker安装nginx 1、拉取镜像 docker pull nginx2、创建挂载目录 mkdir -vp /usr/local/docker/nginx cd /usr/local/docker/nginx #创建用户挂在的目录 mkdir -vp logs html conf3、启动镜像 1、方式一(推荐) 1、启动 docker run -d --name nginx -p 80:80 nginx2、…

vue实现企业微信扫码登录后台管理系统

大致流程 在登录页面构建内嵌式登录二维码&#xff08;这种方式好处&#xff1a;无需跳转到企业微信域下登录后再返回&#xff0c;提升企业微信登录的流畅性与成功率&#xff09;扫二维码之后&#xff0c;微信那边会跳转到redirect_uri你重定向的地址&#xff0c;后面会拼接co…

医疗器械网络安全漏洞自评报告模板

提示&#xff1a;编制医疗器械网络安全漏洞自评报告要点解析 文章目录1. 目的2. 引用文件3. CVSS漏洞等级3.1 概述3.1.1 适用范围说明3.1.2 CNNVD-ID定义3.1.3 编码原则3.1.4 CNNVD-ID语法介绍3.2 指标分析3.2.1 基本指标3.2.1.1可用性指标1)攻击向量2)攻击复杂性3)所需权限4)用…

二、基础平滑、面积折线图与折线堆叠、面积堆叠《手把手教你 ECharts 数据可视化详解》

注&#xff1a;本系列教程需要对应 JavaScript 、html、css 基础&#xff0c;否则将会导致阅读时困难&#xff0c;本教程将会从 ECharts 的官方示例出发&#xff0c;详解每一个示例实现&#xff0c;从中学习 ECharts 。 ECharts 官方示例&#xff1a;https://echarts.apache.o…

蚁剑v4.0流量分析

​ 目录 0x01声明&#xff1a; 0x02简介&#xff1a; 0x03环境搭建&#xff1a; 下载&#xff1a; 初始化项目&#xff1a; 0x04流量分析&#xff1a; 解密&#xff1a; 0x05总结&#xff1a; 0x01声明&#xff1a; 仅供学习参考使用&#xff0c;请勿用作违法用途&…

2023年天津天狮学院专升本专业课如何线上考试考前准备的要求

天津天狮学院2023年高职升本科专业课线上考试要求根据目前疫情防控形势&#xff0c;为保障广大考生身体健康及安全&#xff0c;我校 2023 年高职升本科专业课考试拟采取在线考试形式。为保证此次考试顺利进行&#xff0c;特对报考我校专业课考生提出以下参加考试要求&#xff0…

Ansible自动化运维工具之playbook剧本编写

一.playbook的相关知识 1.1 playbook 的简介 playbook是 一个不同于使用Ansible命令行执行方式的模式&#xff0c;其功能更强大灵活。简单来说&#xff0c;playbook是一个非常简单的配置管理和多主机部署系统&#xff0c;不同于任何已经存在的模式&#xff0c;可作为一个适合…

【MAUI】为 Label、Image 等控件添加点击事件

一、前言 已经习惯了 WPF、WinForm 中“万物皆可点击”的方式。 但是在 MAUI 中却不行了。 在 MAUI 中&#xff0c;点击、双击的效果&#xff0c;是需要通过“手势识别器”来实现。 本篇文章&#xff0c;我们就通过“手势识别器”来为 Label、Image等控件实现点击事件。 相信…

若依微服务版登录流程源码分析1

若依微服务版登录流程涉及到很多模块&#xff0c;本章先从网关讲起 验证码 验证码配置 先来看配置中心的网关配置文件ruoyi-gateway-dev.yml&#xff0c;其中有这么一段 # 安全配置 security:# 验证码captcha:enabled: truetype: math这段配置什么作用呢&#xff0c;就是将…

牛客网刷题【BC7、BC8、BC9、 BC10、 BC11、 BC12】

目录 一 、BC12 字符圣诞树 二、BC7 缩短二进制 三、BC8 十六进制转十进制 四、BC9 printf的返回值 五、BC10 成绩输入输出 六、BC11 学生基本信息输入输出 一 、BC12 字符圣诞树 #include <stdio.h>int main() {char val0;//读入字符scanf("%c",&…

Git入门与进阶 - 总览

Git入门与进阶教程 欢迎加好友一起讨论问题 知识地图&#xff1a;入门Git简介https://blog.csdn.net/lili40342/article/details/128241047Git安装与配置https://blog.csdn.net/lili40342/article/details/128241144Git工作流程https://blog.csdn.net/lili40342/article/detail…

[附源码]计算机毕业设计的个人理财系统Springboot程序

项目运行 环境配置&#xff1a; Jdk1.8 Tomcat7.0 Mysql HBuilderX&#xff08;Webstorm也行&#xff09; Eclispe&#xff08;IntelliJ IDEA,Eclispe,MyEclispe,Sts都支持&#xff09;。 项目技术&#xff1a; SSM mybatis Maven Vue 等等组成&#xff0c;B/S模式 M…

成功解决TypeError: cli.init is not a function for react native

用苹果电脑&#xff08;MacBook air 或者 M1&#xff09;运行 npx react-native init appName时候报错&#xff0c;如下图所示&#xff1a; TypeError: cli.init is not a function at run (/opt/homebrew/lib/node_modules/react-native-cli/index.js:302:7) at createProjec…

有了这款工具,自动化识别验证码再也不是问题

01 环境准备 1、windows 环境下载 exe http://digi.bib.uni-mannheim.de/tesseract/tesseract-ocr-setup-4.00.00dev.exe 双击 exe&#xff0c;一路 next 完成 Tesseract-OCR 安装 2、配置环境变量 PATH 增加 D:\ProgramFiles\Tesseract-OCR 新建环境变量 TESSDATA_PREFIX…

[GO] GORM入门使用

1. GORM 1.1 什么是ORM ORM是object relational mapping就是对象映射关系程序简单的说类似python这种面向对象的程序来说,一切都是对象.为了保证一致性的使用习惯,通过orm将编程语言的对象模型和数据库的关系型模型建立映射关系这样我们直接使用编程语言的对象模型进行数据库…

【cer转jks】

需要两个文件.key,和.cer https://www.myssl.cn/tools/merge-jks-cert.html 选择JKS在线生成 请参照示例&#xff0c;将密钥文件(KEY文件)复制到输入框 请参照示例&#xff0c;将证书文件(CRT/CER文件)复制到输入框 输入别名和密码即可生成 JAVA的application.properties中填写…

主流编程语言的底层实现是什么以及gcc,clang,llvm等编译器的区别

文章目录一、前言二、c和c和c#的区别1、高级语言和低级语言2、c 和 c 和 c#的区别&#xff08;1&#xff09;C语言&#xff08;2&#xff09;C三、各主流语言的底层实现1、python的底层实现2、 java的底层实现3、php的底层实现4、js的底层实现5、node是用什么语言写的6、golang…

[激光原理与应用-45]:《焊接质量检测》-2- 常见焊接缺陷与检验方法

目录 一、概述 二、焊接缺陷的分类 2.1 按产生原因 2.2 按性质分有&#xff1a; 2.3 按在焊缝中的位置分有&#xff1a; 三、焊接缺陷检验的常用方法 一、概述 对于一个金属结构来说&#xff0c;焊接检验就是对所有焊缝或焊接接头而言的&#xff0c;也就是对焊接缺陷的检…

2022年高压快充行业研究报告

第一章 行业概况 高压快充即为快速充电&#xff0c;衡量单位可用充电倍率&#xff08;C&#xff09;表示。充电倍率越大&#xff0c;充电时间越短。依据公式&#xff0c;电池充电的倍率&#xff08;C&#xff09;充电电流&#xff08;mA&#xff09;/电池额定容量&#xff08;…