堆 堆排序 TopK问题

news2024/10/6 14:26:00

  • 一,堆的相关函数接口实现
    • 1,堆的初始化
    • 2,堆的销毁
    • 3,插入
    • 4,向上调整
    • 5,删除
    • 6,向下调整
    • 7,建堆
    • 8,取堆顶
    • 9,判空
    • 10,堆的大小
  • 二,向上建堆与向下建堆的时间复杂度
  • 三,堆排序
  • 四,TopK问题

一,堆的相关函数接口实现

在这里插入图片描述

堆是一颗完全二叉树,分为大堆和小堆两种结构
大堆:任何一个父节点都大于等于子节点,上图就是一个大堆
小堆:任何一个父节点都小于等于子节点
在顺序表中父节点与子节点的下标存在哪些关系呢?
leftchild=parent * 2+1
rightchild=parent * 2+2
那么,同样可以得到
父亲结点的下表就是(儿子结点下标-1)/2
主要采用顺序表来存储堆这个数据结构
主要完成以下的函数接口

#pragma once

#include<stdio.h>
#include<stdlib.h>
#include<assert.h>
#include<stdbool.h>

typedef int HPDataType;
typedef struct Heap
{
	HPDataType* a;
	int capcity;
	int size;
}Heap;

void HeapInit(Heap* php);
void HeapDestroy(Heap* php);
void HeapPush(Heap* php, HPDataType x);
void HeapPop(Heap* php);
HPDataType HeapTop(Heap* php);
bool HeapEmpty(Heap* php);
int HeapSize(Heap* php);
void  HeapCreate(Heap* php,HPDataType* a, int size);
void AdjustDown(HPDataType* a, int size, int parent);
void AdjustUp(HPDataType* a, int child);

1,堆的初始化

初始化很简单就是把用来存储堆的顺序表进行初始化

void HeapInit(Heap* php)
{
	assert(php);
	php->a = NULL;
	php->capcity = 0;
	php->size = 0;
}

2,堆的销毁

void HeapDestroy(Heap* php)
{
	assert(php);
	free(php->a);
	php->a = NULL;
	php->capcity = 0;
	php->size = 0;
}

3,插入

在这里插入图片描述
现在要插入一个结点,那么肯定是会链接到6的左子树的位置。
如果,插入结点的值是小于6的值,那么不会影响堆的结构,仍然是一个大堆。
如果,插入节点的值是大于6的值,那么此时就会影响堆的结构,使起其不是一个大堆,所以我们就到对这个结点进行向上调整。并且调整过程中只会改变此要调整的结点与其父节点的相对位置,直至符合大堆的结构为止。
在这里插入图片描述
我们先用图示来展现以下调整的过程:依次向上调整
在这里插入图片描述
插入之前还要判断是否需要扩容。

void HeapPush(Heap* php, HPDataType x)
{
	assert(php);
	if (php->capcity == php->size)
	{
		int newcapcity = (php->capcity == 0 ? 4 : php->capcity * 2);
		HPDataType* tmp = (HPDataType*)realloc(php->a, sizeof(HPDataType) * newcapcity);
		if (!tmp)
		{
			perror("realloc fail");
			exit(-1);
		}
		php->a = tmp;
		php->capcity = newcapcity;
	}
	php->a[php->size] = x;
	php->size++;
	AdjustUp(php->a, php->size - 1);
}

4,向上调整

void AdjustUp(HPDataType* a, int child)
{
	int parent = (child - 1) / 2;
	while (child > 0)
	{
		if (a[child] > a[parent])
		{
			swep(&a[child], &a[parent]);
			child = parent;
			parent = (child - 1) / 2;
		}
		else
		{
			break;
		}
	}
}

5,删除

删除结点时,通常删除的都是堆顶结点,因为如果时大堆那么堆顶数据就是最大的,反之就是最小的,删除其他结点没有什么意义。
删除时也有技巧,如果我们直接把后面的数据向前移动覆盖掉第一个数据,那么整个堆将变成无序的,所以我们采用这样的方式:
先将堆顶元素与最后一个元素交换,然后size–
此时,堆顶的左子树和右子树一定还是一个大堆没有被破坏,所以,我们就需要向下调整,与向上调整类似,直至成为大堆结构为止。

向下调整时要特别注意,如果父节点比子节点小的话,那么与父节点交换的一定是左右子节点中较大的那一个

在这里插入图片描述

void HeapPop(Heap* php)
{
	assert(php);
	assert(!HeapEmpty(php));
	//首尾交换
	swep(&php->a[0], &php->a[php->size - 1]);
	php->size--;
	//向下调整
	AdjustDown(php->a, php->size, 0);
}

6,向下调整

void AdjustDown(HPDataType* a, int size, int parent)
{
	int child = parent * 2 + 1;
	while (child < size)
	{
		//如果右子树大于左子树那么孩子结点就选右子树
		if (child + 1 < size && a[child + 1] > a[child])
		{
			child++;
		}
		if (a[child] > a[parent])
		{
			swep(&a[child], &a[parent]);
			parent = child;
			child = parent * 2 + 1;
		}
		else
		{
			break;
		}
	}
}

7,建堆

上面的建堆模式是逐个插入建堆的,效率比较低
下面我来介绍两种建堆方式,是基于向上调整与向下调整的。

第一种是向上调整建堆
第二种是向下调整建堆

向上调整建堆,基于的是在插入这个数据前原本就是个大堆
给定一个数组,从下标为1开始向上调整(因为只有一个元素时,即是大堆也是小堆),直至最后一个元素。

向下调整建堆,基于的条件是左右字数都是大堆
在这里插入图片描述
如上图这种结构,对于6来说左右子树都不是大堆,所以不能从6开始调整,但是我们可以从下往上走,5的左右字数都是大堆,那么先调整5,依次调整9,6。

void  HeapCreate(Heap* php, HPDataType* a, int size)
{
	assert(php);
	php->a = (HPDataType*)malloc(sizeof(HPDataType) * size);
	if (!php->a)
	{
		perror("malloc fail");
		exit(-1);
	}
	php->capcity = size;
	php->size = size;
	memcpy(php->a, a, sizeof(HPDataType) * size);
	//向上调整建堆
	/*for (int i = 1; i < size; i++)
	{
		AdjustUp(php->a, i);
	}*/
	//向下调整建堆
	for (int i = (size - 1 - 1) / 2; i >= 0; i--)
	{
		AdjustDown(php->a, size, i);
	}
}

8,取堆顶

HPDataType HeapTop(Heap* php)
{
	assert(php);
	assert(!HeapEmpty(php));
	return php->a[0];
}

9,判空

bool HeapEmpty(Heap* php)
{
	assert(php);
	return php->size == 0;
}

10,堆的大小

int HeapSize(Heap* php)
{
	assert(php);
	return php->size;
}

二,向上建堆与向下建堆的时间复杂度

上面介绍了两种建堆方式,那我们在使用中采用哪种方式呢?
肯定是选择效率高的那种,所以我们现在来计算以下两种方式的时间复杂度

向下调整
在这里插入图片描述

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

可见向下调整建堆要优于向上调整建堆。

三,堆排序

堆排序的思路是根据所给的数组进行向下调整建堆(升序建大堆,降序建小堆)
以升序为例,我们在建堆成功后堆顶元素是数组中最大的数据,将其与堆的最后一个数据交换,这样最大的数据就被放到了最后面,然后size–使堆的数据减少一个,保证后面操作不会影响到前一步选出的最大的数据,然后向下调整建堆再依次操作。
在这里插入图片描述

void HeapSort(int* a, int size)
{
	//升序建大堆
	for (int i = (size - 2) / 2; i >= 0; i--)
	{
		AdjustDown(a, size, i);
	}
	int end = size - 1;
	while (end > 0)
	{
		swep(&a[0], &a[end]);
		AdjustDown(a, end, 0);
		end--;
	}
}

堆排序的时间复杂度的计算式类似于向上调整的式子,
时间复杂度:O(N*logN) 空间复杂度O(1)

四,TopK问题

TopK问题的思路有两种:
我们以选出最大的K个数为例
1,将所有数据建立一个大堆,每次取堆顶,再删除堆顶,向下调整。
但是,这种思路的缺点是当所给的数据量巨大,内存放不下的时候,将会无法操作。
2,建立一个K个结点的小堆,由于小堆的堆顶是最小的数据,接下来遍历整个数据只要比堆顶大就进堆,再向下调整,知道遍历完,最大的K个数就全在堆中了。
这种思路就不用担心内存的问题,数据量大可以从文件中读取数据。
所以我来讲解一下这种思路

void TopK(int* a, int size, int k)
{
	int* minHeap = (int*)malloc(sizeof(int) * k);
	if (!minHeap)
	{
		perror("malloc fail");
		exit(-1);
	}
	int j = 0;
	for (j = 0; j < k; j++)
	{
		minHeap[j] = a[j];
	}
	//建k个结点的堆
	for (int i = (k - 2) / 2; i >= 0; i--)
	{
		AdjustDown(minHeap, k, i);
	}
	for (; j < size; j++)
	{
		if (a[j] > minHeap[0])
		{
			minHeap[0] = a[j];
			AdjustDown(minHeap, k, 0);
		}
	}
	for (int i = 0; i < k; i++)
	{
		printf("%d ", minHeap[i]);
	}
	
}

思路2的时间复杂度:O (N*logK)
思路1的时间复杂度: O (N)

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

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

相关文章

用DIV+CSS技术设计的鲜花网站(web前端网页制作课作业)

&#x1f389;精彩专栏推荐 &#x1f4ad;文末获取联系 ✍️ 作者简介: 一个热爱把逻辑思维转变为代码的技术博主 &#x1f482; 作者主页: 【主页——&#x1f680;获取更多优质源码】 &#x1f393; web前端期末大作业&#xff1a; 【&#x1f4da;毕设项目精品实战案例 (10…

(人工智能的数学基础)第一章特征向量与矩阵分析——第一节:向量、向量空间和线性相关性

文章目录一&#xff1a;标量和向量&#xff08;1&#xff09;基本概念&#xff08;2&#xff09;坐标系中的向量表示二&#xff1a;向量运算&#xff08;1&#xff09;加减与数乘&#xff08;2&#xff09;向量内积A&#xff1a;为什么需要向量内积B&#xff1a;向量内积C&…

Linux之分区【详细总结】

目录分区介绍分区查看指令lsblk ![请添加图片描述](https://img-blog.csdnimg.cn/d7ea5468d719433ea6ee4ab0eb145770.png)lsblk -f挂载案例分五部分组成 虚拟机添加硬盘 分区 格式化 挂载 设置自动挂载虚拟机增加硬盘查看整个系统磁盘情况查询查看整个目录磁盘占用情况磁盘情况…

初识 MySQL HeatWave

MySQL 作为全球最欢迎的数据库&#xff0c;已在交易场景叱咤风云多年。在 2020 年底&#xff0c;OCI&#xff08;Oracle Cloud Infrastructure&#xff09;推出了一个黑科技插件&#xff0c;它弥补了 MySQL 在分析场景的短板&#xff0c;Oracle 官方称它比 Aurora 快 1400 倍&a…

GIS 分析常用的 7 个地理处理工具

以下这7 个地理处理工具总是在 GIS 大师的热门列表中名列前茅&#xff0c;似乎如我们的精神食粮&#xff0c;像面包和黄油一样。从裁剪到缓冲&#xff0c;您将学习处理GIS 数据的基础知识&#xff0c;以便更好地了解如何将这些 GIS 工具用于实际应用程序。在ArcGIS 和 QGIS等 G…

Gradle学习笔记之第一个Gradle项目

文章目录前言创建gradle项目gradle目录结构gradle常用命令修改maven仓库地址启用init.gradle的方法关于gradle仓库gradle包装器前言 Gradle是Android构建的基本工具&#xff0c;因此作为Android研发&#xff0c;有必要系统地学一学Gradle&#xff0c;环境windows就可以。 创建…

学生个人网页模板 学生个人网页设计作品 简单个人主页成品 个人网页制作 HTML学生个人网站作业设计代做

&#x1f389;精彩专栏推荐&#x1f447;&#x1f3fb;&#x1f447;&#x1f3fb;&#x1f447;&#x1f3fb; ✍️ 作者简介: 一个热爱把逻辑思维转变为代码的技术博主 &#x1f482; 作者主页: 【主页——&#x1f680;获取更多优质源码】 &#x1f393; web前端期末大作业…

git原理和命令以及工具

原理 工作区、暂存区和版本库 分支结构 origin 对象模型 命令 配置 $ git config --global user.name “John Doe” $ git config --global user.email johndoeexample.com 针对特定项目使用不同的用户名称与邮件地址时&#xff0c;可以在那个项目目录下运行没有 --globa…

52、训练paddleSeg模型,部署自己的模型到OAK相机上

基本思想&#xff1a;简单记录一下训练过程&#xff0c;数据集在coco基础上进行&#xff0c;进行筛选出杯子的数据集&#xff0c;然后进行训练&#xff0c;比较简单&#xff0c; 从coco数据集中筛选出杯子的数据集&#xff0c;然后在labelme数据集的基础上&#xff0c;转成padd…

学生个人网页设计作品:基于HTML+CSS+JavaScript实现摄影艺术网站 DIV布局简单的摄影主题网站

&#x1f389;精彩专栏推荐 &#x1f4ad;文末获取联系 ✍️ 作者简介: 一个热爱把逻辑思维转变为代码的技术博主 &#x1f482; 作者主页: 【主页——&#x1f680;获取更多优质源码】 &#x1f393; web前端期末大作业&#xff1a; 【&#x1f4da;毕设项目精品实战案例 (10…

NATAPP 访问vue-cli启动的项目

由于疫情原因&#xff0c;最近又静默居家办公了&#xff0c;由于项目需要&#xff0c;不得不再使用一下natapp了&#xff0c;这个东西两年前就用过了&#xff0c;不过以前访问的不是vue-cli项目&#xff0c;特此记录一下&#xff0c;因为这里有个坑&#xff0c;上午搞了半天没搞…

APP逆向案例之(二)对加固APP进行分析和破解

说明&#xff1a;对加固APP进行分析和破解&#xff0c;对发现新版本提示关掉 1、先对APP窗口类行进HOOK&#xff0c;确定窗口提示用的是那个类。 android hooking watch class android.app.AlertDialog 2、发现一个非常明显的函数 setCancelable objection -g com.hello.qq…

50个html+css+js项目小练习(二:动画的倒计时效果)

2.animated-navigation 实现效果&#xff1a; 倒计时321后显示go数字依次从x轴负方向&#xff0c;顺时针倒下去 xy第一个数&#xff08;0,y&#xff09;—>&#xff08;-x,0&#xff09; 第一个数字倒下去的同时&#xff0c;第二个数从x轴正方向升起 第二个数&#xff08;x…

1. 开篇:既简单又复杂的基础框架

同样的基础但不简单 之前在写 《从 0 开始深入学习 Spring》 小册时&#xff0c;阿熊提到过一件事&#xff1a;学习 JavaEE 开发的第一个框架&#xff0c;大多数是推荐 MyBatis 的&#xff0c;因为它相对简单&#xff0c;学习起来也相对轻松。不过不要因为 MyBatis 入门简单&a…

【电商】管理后台篇之安全、菜单、通知管理

系统管理第一篇我们介绍了账号管理相关的业务&#xff0c;这一篇我们介绍下其他几个常见的业务&#xff1a;安全管理、菜单管理和通知管理。 本篇介绍的几个常见业务如下&#xff1a; 安全管理&#xff1a;监控每个账号的登录情况和操作情况通知管理&#xff1a;给使用后台系统…

[附源码]计算机毕业设计JAVA企业信息网站

[附源码]计算机毕业设计JAVA企业信息网站 项目运行 环境配置&#xff1a; Jdk1.8 Tomcat7.0 Mysql HBuilderX&#xff08;Webstorm也行&#xff09; Eclispe&#xff08;IntelliJ IDEA,Eclispe,MyEclispe,Sts都支持&#xff09;。 项目技术&#xff1a; SSM mybatis M…

自定义qtquick 插件模块,支持qmldesigner

自定义qtquick 插件模块&#xff0c;支持qmldesigner wmxModule.pro #------------------------------------------------- # # Project created by wmx # #-------------------------------------------------QT core qml quickTARGET wmxModule TEMPLATE lib CONF…

图像处理QPixmap、Picture、QBitmap

QPixmap 类是一种可用作绘制设备的屏幕外图像表示形式。 QPixmap可以使用QLabel或QAbstractButton的子类之一可以轻松显示在屏幕上 QPixmap 对象可以按值传递&#xff0c;因为 QPixmap 类使用隐式数据共享&#xff0c;也可以流式传输。 QPixmap可以和QImage之间进行转换&…

【关于lombok框架一文秒懂】

目录 1. Lombok框架 2. 安装lombok插件 1. Lombok框架 Lombok框架是用于在编译期自动生成相关代码的&#xff0c;例如&#xff1a;Setters & Getters、hashCode()、equals()、toString()、无参数构造方法、全参数构造方法等。 相关的注解有&#xff1a; Data&#xff1…

python中的编码格式

1- 字符集和编码 1&#xff09; 初代编码 – ASCII 1- 物理机与电脑的交互&#xff1a;物理机中有个组件叫二极管&#xff0c; 可以通过电流通过二极管显示的高电平和低电平来记录信号。 2- 二极管的高电平和低电平则被计算机解读为0 和 1 3- 多个0和1拼接起来成为了二进制数据…