数据结构-堆和堆排序-TopK问题

news2024/11/29 8:33:36

在这里插入图片描述

内容总览

  • 1.堆的定义
  • 2.堆的实现接口(大堆)
    • 2.1 堆结构体定义
    • 2.2 堆的初始化与销毁
    • 2.3 堆的向上调整算法和插入
    • 2.4 堆的向下调整算法和删除堆顶元素
    • 2.5 堆的其他接口(调整堆递归版本)
  • 3.建堆效率问题分析
    • 3.1 向上建堆
    • 3.2 向下建堆
  • 4. 堆排序(升序)
  • 5.TopK问题分析

1.堆的定义

堆是以二叉树的结构方式,所存储的一维数组。
逻辑结构:二叉树
物理结构:一维数组

堆的特性:

  • 堆中某个节点的值总是不大于或不小于它的父亲节点的值。根结点值总是大于或等于其左右孩子结点的值,叫大根堆。根节点总是小于或等于其左右孩子结点的值,叫小根堆。
  • 堆总是一棵完全二叉树。

如下图堆的示例:
在这里插入图片描述
在这里插入图片描述

2.堆的实现接口(大堆)

2.1 堆结构体定义

使用一维数组来存储堆

typedef int HPDataType;
typedef struct Heap
{
	HPDataType* data;
	int size;
	int capacity;
}Heap;

2.2 堆的初始化与销毁

//堆初始化
void HeapInit(Heap* pa)
{
	assert(pa);
	pa->data = NULL;
	pa->capacity = pa->size = 0;
}

//销毁堆
void HeapDestroy(Heap* pa)
{
	assert(pa);
	free(pa->data);
	pa->data = NULL;
	pa->capacity = pa->size = 0;
}

2.3 堆的向上调整算法和插入

堆进行插入的时候,接口是进行尾插的,这样能保证不会破坏之前的堆的结构。插入过程大概如下步骤:假设输入数据为:

在这里插入图片描述
算法思想:由需要调整的结点开始,把它当作孩子结点child。计算出父亲节点parent,将孩子和父亲的值作比较,如果父亲小于孩子,择交换两结点的值。并且令child = parent.一直重复,直到调整到根结点为止。

//向上调整
void AdjustUp(HPDataType* a, int child)
{
	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 HeapPush(Heap* pa, HPDataType x)
{
	assert(pa);

	//检查容量
	if (pa->size == pa->capacity)
	{
		int newcapacity = (pa->capacity == 0 ? 4 : pa->capacity * 2);
		HPDataType* tem = (HPDataType*)realloc(pa->data, sizeof(HPDataType) * newcapacity);
		//申请失败,终止程序
		if (tem == NULL)
		{
			perror("realloc failed");
			exit(-1);
		}
		pa->data = tem;
		pa->capacity = newcapacity;
	}

	//插入数据
	pa->data[pa->size] = x;
	pa->size++;

	//向上调整
	AdjustUp(pa->data,pa->size-1);
}

2.4 堆的向下调整算法和删除堆顶元素

堆进行删除时候,不能直接删除堆顶元素,这样会把数据整体前移,破坏了堆的结构。规定:把堆顶元素和最后一个元素交换。然后将size-1,就可完成删除任务。只需利用向下调整算法,就能在O(logN)的时间内完成恢复堆的任务。

在这里插入图片描述
算法思想:从堆顶元素开始向下调整,首先比较两个孩子结点,找出较大的结点。与父亲节点比较,若孩子结点的值小于父亲结点的值,则说明符合堆的结构,调整完成。否则将父亲结点和孩子结点的值交换,并且令parent = child(继续向下调整),直到符合堆结构或者没有叶子结点,调整完成。

//向下调整
void AdjustDown(HPDataType* a, int size, int parent)
{
	int child = 2 * parent + 1; //左孩子下标
	
	//如果有孩子,必须小于size
	while (child < size)
	{
		//选出较大的孩子(右孩子不能超出范围)
		if (child + 1 < size && a[child + 1] < a[child])
		{
			child++;
		}

		//孩子大于父亲就交换,不大于就退出
		if (a[child] < a[parent])
		{
			Swap(&a[child], &a[parent]);
			parent = child;
			child = parent * 2 + 1;
		}
		else
		{
			break;
		}
	}
}


//堆删除 
void HeapPop(Heap* pa)
{
	assert(pa);
	assert(pa->size > 0);
	
	//换到最低层
	Swap(&(pa->data[0]), &(pa->data[pa->size-1]));
	pa->size--;

	//向下调整
	AdjustDown(pa->data,pa->size,0);
}

2.5 堆的其他接口(调整堆递归版本)

//获取堆顶元素
HPDataType HeapTop(Heap* pa)
{
	assert(pa);
	assert(pa->size > 0);
	return pa->data[0];
}

//堆的数据个数
int HeapSize(Heap* pa)
{
	assert(pa);
	return pa->size;
}

//堆的判空
bool HeapEmpty(Heap* pa)
{
	assert(pa);
	if (pa->size > 0)
	{
		return false;
	}
	return true;
}


//向上调整(递归版本)
void AdjustUp(HPDataType* a, int child)
{
	
	int parent = (child - 1) / 2;
	if(child > 0)
	{
		//不符合堆定义
		if (a[child] > a[parent])
		{
			Swap(&a[child], &a[parent]);
			AdjustUp(a, parent); //把父亲当作儿子传进去
		}
	}
}

//向下调整(递归版本)
void AdjustDown(HPDataType* a, int size, int parent)
{
	int child = 2 * parent + 1; //左孩子下标
	//如果有孩子,必须小于size
	if (child < size)
	{
		//选出较大的孩子(右孩子不能超出范围)
		if (child + 1 < size && a[child + 1] > a[child])
		{
			child++;
		}

		//孩子大于父亲就交换,不大于就退出
		if (a[child] > a[parent])
		{
			Swap(&a[child], &a[parent]);
			AdjustDown(a, size, child); //把孩子当成父亲传进去
		}
	}
}

3.建堆效率问题分析

3.1 向上建堆

在这里插入图片描述
因此向上建堆的时间复杂度为O(NlogN)。

3.2 向下建堆

在这里插入图片描述
因此向下建堆的时间复杂度为O(N)。

4. 堆排序(升序)

  1. 利用堆的Top和Pop接口来实现排序,但是需要申请空间来保存Pop之后的数据。并且每次都需要实现堆的接口比较麻烦。
  2. 利用Pop的思想,将最大的元素和最后一个元素互换。每次交换size–,不断循环这个动作,直到size大小为0。

本文采用第二种方法:

//升序
void HeapSort(HPDataType* a, int size)
{
	//先建堆
	for (int i = (size - 1 - 1) / 2; i >= 0; i--)
	{
		//向下调整(从后往前)
		AdjustDown(a, size, i);
	}

	//再一个一个调整
	int end = size - 1; //记录堆的最后一个位置
	while (end > 0)
	{
		//第一个和最后一个位置交换
		Swap(&a[0], &a[end]);
		//向下调整
		AdjustDown(a, end, 0);
		end--;
	}
}

分析:第一部分向下建堆时间复杂度为0(N),堆排序的时间复杂度为O(NlogN)。
综上:堆排序的时间复杂度为O(NlogN)。

5.TopK问题分析

题目背景:要求找出100万个数字里最大的前10个数。

方法1:
建立N个数的大根堆,每次找出最大的数字,重复10次即可。但是空间效率极高。
时间复杂度O(N+KlogN)。

方法2:
建立K个数字的小根堆,依次遍历剩下数据,如果数值大于堆顶数据,则进堆,如果数值小于堆顶数据,则跳过。遍历完,堆中的数据就是最大的前10个。
时间复杂度O(K+(N-K)logK)。

方法2的效率更高一点,且占用空间小:

//找前K个最大的数字
void PrintTopK(int* a, int size, int K)
{
	int* tem = (int*)malloc(sizeof(int) * K);
	assert(tem);
	//建立前K个数字的小堆
	for (int i = 0; i < K; i++)
	{
		tem[i] = a[i];
		if (tem == NULL)
		{
			exit(-1);
		}
		AdjustUp(tem, i);
	}

	//剩下的数字依次和堆顶元素比较
	int j = K;
	while (j < size)
	{
		if (a[j] >= tem[0])
		{
			tem[0] = a[j];
			AdjustDown(tem, K, 0);
		}
		j++;
	}

	for (int i = 0; i < K; i++)
	{
		printf("%d ", tem[i]);
	}
}

总结:以上就是堆相关的问题介绍,后续还会继续更新数据结构相关的知识。敬请期待。💞

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

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

相关文章

Java中的TCP (Android通用)

TCP服务端&#xff0c;创建了一个线程的接口 public class TCPServer implements Runnable {private static final String TAG "TCPServer";private String chaSet "UTF-8";private int port;private boolean isListen true;public TCPServer(int port)…

TypeScript 学习笔记 (学习中)

学习视频1&#xff1a;coderwhy 学习视频2&#xff1a;尚硅谷 文章目录 TypeScript 学习笔记概述TypeScript 开发环境搭建 类型注解类型推断 数据类型JS的7个原始类型Array数组object、Object 和 {}1.可选属性 ? 2.type 类型别名 和 接口interface函数TS类型: any类型 | unkno…

分享Python采集66个css3代码,总有一款适合您

分享Python采集66个css3代码&#xff0c;总有一款适合您 Python采集的66个css3代码下链接&#xff1a; 百度网盘 请输入提取码 提取码&#xff1a;mads css3svg炫酷水滴Loading特效 css剪裁GIF背景图片动画特效 纯CSS制作辛普森一家卡通人物动画特效 CSS3图片遮罩层变形…

1688商品详情数据采集技术,支持整站数据高并发采集

一、如何通过手动方式查看1688商品详情页面的数据 1.1688商品详情 API 接口&#xff08;item_get - 获得1688商品详情接口&#xff09;&#xff0c;1688API 接口代码对接可以获取到宝贝 ID&#xff0c;宝贝标题&#xff0c;价格&#xff0c;优惠价&#xff0c;掌柜名称&a…

ArcSWAT报错:-2147217385;创建栅格数据集失败

文章目录 1 报错内容2 报错分析3 解决方案3.1 数据集路径错误3.2 数据格式不受支持3.3 文件访问权限问题 1 报错内容 此报错通常发生在建立了一个SWAT数据库后&#xff0c;执行Watershed Delineator中的Automatic Watershed Delineation操作中&#xff0c;在选择了DEM数据后弹出…

亚马逊云科技Amazon Compute Optimizer基础设施

亚马逊云科技Amazon Compute Optimizer如今推出了一项新功能&#xff0c;可以利用多个CPU架构&#xff08;包括基于x86的实例和基于Amazon Graviton的实例&#xff09;更轻松地优化EC2实例。Compute Optimizer是一项可选服务基础设施&#xff0c;可为工作负载推荐最佳Amazon资源…

Kali-linux使用OpenVAS

OpenVAS&#xff08;开放式漏洞评估系统&#xff09;是一个客户端/服务器架构&#xff0c;它常用来评估目标主机上的漏洞。OpenVAS是Nessus项目的一个分支&#xff0c;它提供的产品是完全地免费。OpenVAS默认安装在标准的Kali Linux上&#xff0c;本节将介绍配置及启动OpenVAS。…

Flink基础介绍-3 Time与Window

Flink基础介绍-3 Time与Window 三、流处理中的Time与Window3.1 Time3.2 window3.3 Window API3.4 Watermark 三、流处理中的Time与Window 3.1 Time Event Time&#xff1a;是事件创建的时间。它通常由事件中的时间戳描述&#xff0c;例如采集的日志数据中&#xff0c;每一条日…

SpringSecurity原理和实际应用

前提知识 认证&#xff1a;系统提供的用于识别用户身份的功能&#xff0c;通常提供用户名和密码进行登录其实就是在进行认证&#xff0c;认证的目的是让系统知道你是谁。 授权&#xff1a;用户认证成功后&#xff0c;需要为用户授权&#xff0c;其实就是指定当前用户可以操作哪…

Spring Resource接口 学习

Resource 接口是 Spring 资源访问策略的抽象&#xff0c;它本身并不提供任何资源访问实现&#xff0c;具体的资源访问由该接口的实现类完成——每个实现类代表一种资源访问策略。Resource一般包括这些实现类&#xff1a;UrlResource、ClassPathResource、FileSystemResource、S…

MySQL基础(三十二)事务基础知识

1 数据库事务概述 1.1 存储引擎支持情况 SHOW ENGINES 命令来查看当前 MySQL 支持的存储引擎都有哪些&#xff0c;以及这些存储引擎是否支持事务。 能看出在 MySQL 中&#xff0c;只有InnoDB 是支持事务的。 1.2 基本概念 事务&#xff1a;一组逻辑操作单元&#xff0c;使数…

由浅入深理解java集合(三)——集合 List

一、List集合 List集合判断元素相等的标准 List判断两个对象相等只要通过equals()方法比较返回true即可&#xff08;关于equals()方法的详解可以参考第二篇文章中的内容&#xff09;。 下面以用代码具体展示。 创建一个Book类&#xff0c;并重写equals()方法&#xff0c;如果两…

ctf.show MiSC入门 图片篇 (信息附加)

目录 图片篇 信息附加 misc5 misc6 misc7 misc8 misc9 misc10 misc11 misc12 misc13 misc14 misc15 misc16 misc17 misc18 misc19 misc20 misc 21 misc22 misc23 misc41 图片篇 信息附加 misc5 打开后啥也没有 使用16进制编辑器打开&#xff0c;在最下面…

就业内推 | 上市公司招网工运维,有华为、思科、华三认证均可

01 软通动力 &#x1f537;招聘岗位&#xff1a;网络工程师 &#x1f537;职责描述&#xff1a; 1、负责大型数据中心网络运维及变更&#xff0c;包括架构优化、性能调优、服务上线 2、负责网络故障类问题定位及排查&#xff0c;对于复杂故障类问题能够定位、跟进和解决实施等…

数据分析案例-BI工程师招聘岗位信息可视化分析(文末送书)

&#x1f935;‍♂️ 个人主页&#xff1a;艾派森的个人主页 ✍&#x1f3fb;作者简介&#xff1a;Python学习者 &#x1f40b; 希望大家多多支持&#xff0c;我们一起进步&#xff01;&#x1f604; 如果文章对你有帮助的话&#xff0c; 欢迎评论 &#x1f4ac;点赞&#x1f4…

25.在vue中使用axios

目录 1 项目中安装 axios 2 做测试用的后端 3 全局引入axios 4 定义全局根路径 1 项目中安装 axios 2 做测试用的后端 我们用python的flask做后端 简单来讲就是&#xff0c;发两个数字&#xff0c;如果是get就两个数相加&#xff0c;如果是post就两个数相乘 3 全局引…

Kali-linux使用Nessus

Nessus号称是世界上最流行的漏洞扫描程序&#xff0c;全世界有超过75000个组织在使用它。该工具提供完整的电脑漏洞扫描服务&#xff0c;并随时更新其漏洞数据库。Nessus不同于传统的漏洞扫描软件&#xff0c;Nessus可同时在本机或远端上遥控&#xff0c;进行系统的漏洞分析扫描…

【数据结构初阶】——第八节.优先级队列(小根堆的模拟实现)

作者简介&#xff1a;大家好&#xff0c;我是未央&#xff1b; 博客首页&#xff1a;未央.303 系列专栏&#xff1a;Java初阶数据结构 每日一句&#xff1a;人的一生&#xff0c;可以有所作为的时机只有一次&#xff0c;那就是现在&#xff01;&#xff01;&#xff01; 目录 …

API接口三问

一、API数据接口可以给我们带来哪些便利 API数据接口可以给我们带来以下便利&#xff1a; 数据共享&#xff1a;API允许数据在不同的应用程序之间共享。这使得数据转移更容易&#xff0c;因为不需要手动复制和粘贴数据内容。 程序集成&#xff1a;API作为中间件&#xff0c;可…

20年+资深审稿人:什么情况下建议文章大小修、拒稿或接收?

文章进入外审后&#xff0c;作者最终可能会得到大小修、接收或拒稿的意见。那么&#xff0c;审稿人是怎么给出这些不同意见的呢&#xff1f;有哪些方面需要作者提前了解呢&#xff1f; Surgery 发布过一篇文章&#xff0c;里面调查了一些具有20年审稿经验、平均年龄69岁的编委会…