【数据结构】堆,堆的实现,堆排序,TOP-K问题

news2024/12/23 14:29:51

大家好!今天我们来学习数据结构中的堆及其应用

目录

1. 堆的概念及结构

2. 堆的实现

2.1 初始化堆

2.2 销毁堆

2.3 打印堆

2.4 交换函数

2.5 堆的向上调整

2.6 堆的向下调整

2.7 堆的插入

2.8 堆的删除

2.9 取堆顶的数据

2.10 堆的数据个数

2.11 堆的判空

3. 堆的实现的完整代码

3.1 Heap.h

3.2 Heap.c

3.3 Test.c

4. 建堆的时间复杂度

4.1 向上调整建堆

4.2 向下调整建堆

5. 堆的应用

5.1 堆排序

5.2 TOP-K问题

6. 总结


1. 堆的概念及结构

堆(Heap)是计算机科学中中一类特殊的数据结构,是最高效的优先级队列,堆通常是一个可以被看作一棵完全二叉树数组对象

堆分为最小堆(Min Heap)和 最大堆(Max Heap)和最小堆(Min Heap)。对于最小堆父结点的值小于等于它的子结点的值。对于最大堆父结点的值大于等于它的子结点的值;

堆的性质:

1. 堆中某个结点的值总是不大于或不小于其父结点的值。

2. 堆总是一棵完全二叉树。

3. 小堆的根是整棵树的最小值,大堆的根是整棵树的最大值。

问题:

1. 小堆的底层数组是否升序?

2. 大堆的底层数组是否降序?

2. 堆的实现

我们这里以小根堆为例(大根堆可以在小根堆的基础上稍作修改),下面是要实现堆的接口函数:

//初始化堆
void HeapInit(HP* php);
//销毁堆
void HeapDestroy(HP* php);
//打印堆
void HeapPrint(HP* php);
//交换函数
void Swap(HPDataType* p1, HPDataType* p2);
//堆向上调整算法
void AdjustUp(HPDataType* a, int child);
//堆向下调整算法
void AdjustDown(HPDataType* a, int n, int parent);
//堆的插入
void HeapPush(HP* php, HPDataType x);
//堆的删除
void HeapPop(HP* php);
//取堆顶的数据
HPDataType HeapTop(HP* php);
//堆的数据个数
int HeapSize(HP* php);
//堆的判空
bool HeapEmpty(HP* php);

堆的定义:

typedef int HPDataType;
typedef struct Heap
{
	HPDataType* a;
	int size;
	int capacity;
}HP;

一些简单的接口函数,和我们之前实现的顺序表,链表,栈等数据结构类似。我们在这里就不详细介绍了,这里我们主要介绍堆向上调整算法堆向下调整算法。这两个函数分别会在堆的插入和堆的删除中调用。

2.1 初始化堆

//初始化堆
void HeapInit(HP* php)
{
	assert(php);
	php->a = NULL;
	php->size = 0;
	php->capacity = 0;
}

2.2 销毁堆

//销毁堆
void HeapDestroy(HP* php)
{
	assert(php);
	free(php->a);
	php->a = NULL;
	php->size = php->capacity = 0;
}

2.3 打印堆

//打印堆
void HeapPrint(HP* php)
{
	assert(php);
	for (int i = 0; i < php->size; i++)
	{
		printf("%d ", php->a[i]);
	}
	printf("\n");
}

2.4 交换函数

//交换函数
void Swap(HPDataType* p1, HPDataType* p2)
{
	HPDataType tmp = *p1;
	*p1 = *p2;
	*p2 = tmp;
}

2.5 堆的向上调整

向上调整前提前面的数据是堆

//堆向上调整算法
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 = (parent - 1) / 2;
		}
		else 
		{
			break;
		}	
	}
}

2.6 堆的向下调整

向下调整前提左右子树都是小堆或者都是大堆

//堆向下调整算法
void AdjustDown(HPDataType* a, int n, int parent)
{
	int child = parent * 2 + 1;
	while (child < n)
	{
		if (child + 1 < n && a[child + 1] < a[child]) //找出小的那个孩子
		{
			++child;
		}
		if (a[child] < a[parent])
		{
			Swap(&a[child], &a[parent]);
			parent = child; 
			child = parent * 2 + 1;
		}
		else
		{
			break;
		}
	}
}

2.7 堆的插入

向堆中插入一个元素,就是这个元素插入到堆的尾部,因为堆的实际存储结构是一个数组,我们可以将元素放到数组末尾。

但是如果仅仅是将元素插入到数组末尾的话,会破坏堆的结构,我们还需要调用一个向上调整函数,保持堆的结构。

注意:在插入之前,我们需要判断堆的容量是否足够,如果堆的容量已满,需要扩容,扩容时我们在原来的基础上扩2倍

如下图:

先插入一个10到数组的尾上,再进行向上调整算法,直到满足堆。

//堆的插入
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, sizeof(HPDataType) * newCapacity);
		if (tmp == NULL)
		{
			perror("realloc failed");
			exit(-1);
		}
		php->a = tmp;
		php->capacity = newCapacity;
	}
	php->a[php->size] = x;
	php->size++;
	AdjustUp(php->a, php->size - 1);
}

2.8 堆的删除

删除堆删除堆顶的数据将堆顶的数据根最后一个数据一换然后删除数组最后一个数据,再进行向下调整算法。

//堆的删除
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);
}

2.9 取堆顶的数据

//取堆顶的数据
HPDataType HeapTop(HP* php)
{
	assert(php);
	assert(php->size > 0);
	return php->a[0];
}

2.10 堆的数据个数

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

2.11 堆的判空

//堆的判空
bool HeapEmpty(HP* php)
{
	assert(php);
	return php->size == 0;
}

3. 堆的实现的完整代码

3.1 Heap.h

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

typedef int HPDataType;
typedef struct Heap
{
	HPDataType* a;
	int size;
	int capacity;
}HP;


//初始化堆
void HeapInit(HP* php);
//销毁堆
void HeapDestroy(HP* php);
//打印堆
void HeapPrint(HP* php);
//交换函数
void Swap(HPDataType* p1, HPDataType* p2);
//堆向上调整算法
void AdjustUp(HPDataType* a, int child);
//堆向下调整算法
void AdjustDown(HPDataType* a, int n, int parent);
//堆的插入
void HeapPush(HP* php, HPDataType x);
//堆的删除
void HeapPop(HP* php);
//取堆顶的数据
HPDataType HeapTop(HP* php);
//堆的数据个数
int HeapSize(HP* php);
//堆的判空
bool HeapEmpty(HP* php);

3.2 Heap.c

#define _CRT_SECURE_NO_WARNINGS 1
#include"Heap.h"

//初始化堆
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 = php->capacity = 0;
}

//打印堆
void HeapPrint(HP* php)
{
	assert(php);
	for (int i = 0; i < php->size; i++)
	{
		printf("%d ", php->a[i]);
	}
	printf("\n");
}

//交换函数
void Swap(HPDataType* p1, HPDataType* p2)
{
	HPDataType tmp = *p1;
	*p1 = *p2;
	*p2 = tmp;
}

//堆向上调整算法
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 = (parent - 1) / 2;
		}
		else 
		{
			break;
		}	
	}
}

//堆向下调整算法
void AdjustDown(HPDataType* a, int n, int parent)
{
	int child = parent * 2 + 1;
	while (child < n)
	{
		if (child + 1 < n && 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 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, sizeof(HPDataType) * newCapacity);
		if (tmp == NULL)
		{
			perror("realloc failed");
			exit(-1);
		}
		php->a = tmp;
		php->capacity = newCapacity;
	}
	php->a[php->size] = x;
	php->size++;
	AdjustUp(php->a, php->size - 1);
}

//堆的删除
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);
}

//取堆顶的数据
HPDataType HeapTop(HP* php)
{
	assert(php);
	assert(php->size > 0);
	return php->a[0];
}

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

//堆的判空
bool HeapEmpty(HP* php)
{
	assert(php);
	return php->size == 0;
}

3.3 Test.c

#define _CRT_SECURE_NO_WARNINGS 1
#include"Heap.h"

int main()
{
	int a[] = { 2,3,5,7,4,6,8};
	HP hp;
	HeapInit(&hp);
	for (int i = 0; i < sizeof(a) / sizeof(int); i++)
	{
		HeapPush(&hp, a[i]);
	}
	HeapPrint(&hp);
	int sz = HeapSize(&hp);
	printf("堆中元素个数为%d个\n", sz);
	
	while (!HeapEmpty(&hp))
	{
		printf("%d ", HeapTop(&hp));
		HeapPop(&hp);
	}
	HeapDestroy(&hp);
	return 0;
}

4. 建堆的时间复杂度

因为堆是完全二叉树,而满二叉树也是完全二叉树,此处为了简化使用满二叉树来证明(时间复杂度本来看的就是近似值,多几个结点不影响最终结果):

4.1 向上调整建堆

因此向上调整建堆的时间复杂度是O(N*logN)

4.2 向下调整建堆

因此,向下调整建堆的时间复杂度为O(N)

因为向下调整建堆的时间复杂度是O(N),小于向上调整建堆的时间复杂度O(N*logN),所以一般情况下,我们都采用向下调整建堆。

5. 堆的应用

5.1 堆排序

堆排序即利用堆的思想来进行排序,总共分为两个步骤:

1. 建堆

升序:建大堆

降序:建小堆

2. 利用堆删除思想来进行排序

建堆和堆删除中都用到了向下调整,因此掌握了向下调整,就可以完成堆排序。

这里我们以大堆为例,通过堆排序进行升序

我们也可以在堆的底层数组中进一步理解堆排序的过程

因为是大堆,所以我们要稍微修改向下调整部分的代码(我们在上面堆的实现中是以小堆为例)

void Swap(HPDataType* p1, HPDataType* p2)
{
	HPDataType tmp = *p1;
	*p1 = *p2;
	*p2 = tmp;
}

void AdjustDown(HPDataType* a, int n, int parent)
{
	int child = parent * 2 + 1;
	while (child < n)
	{
		if (child + 1 < n && 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 HeapSort(int* 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);  //向下调整[0,end]的元素
		--end;
	}
}

int main()
{
	int a[] = { 20,17,4,16,5,3 };
	HeapSort(a, sizeof(a) / sizeof(int));
	for (int i=0 ; i < sizeof(a) / sizeof(int); i++)
	{
		printf("%d ", a[i]);
	}
	printf("\n");
	return 0;
}

5.2 TOP-K问题

TOP- K问题:即求数据结合中前K个最大的元素或者最小的元素一般情况下数据量都比较大

比如:专业前10名、世界500强、富豪榜、游戏中前100的活跃玩家等。

对于Top-K问题,能想到的最简单直接的方式就是排序,但是:如果数据量非常大,排序就不太可取了(可能数据都不能一下子全部加载到内存中)。最佳的方式就是用堆来解决,基本思路如下:

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

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

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

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

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

实际应用:在1000000个随机数中找出前十个最大的数字

void PrintTopK(int* a, int n, int k)
{
	int* KMaxHeap = (int*)malloc(sizeof(int) * k);
	assert(KMaxHeap);
	for (int i = 0; i < k; i++)
	{
		KMaxHeap[i] = a[i];
	}
	//建小根堆
	for (int i = (k - 1 - 1) / 2; i >= 0; i--)
	{
		AdjustDown(KMaxHeap, k, i);
	}
	//依次比较a数组中剩余的元素
	for (int i = k; i < n; i++)
	{
		if (a[i] > KMaxHeap[0])
		{
			KMaxHeap[0] = a[i];
		}
		AdjustDown(KMaxHeap, k, 0);
	}
	//打印
	for (int i = 0; i < k; i++)
	{
		printf("%d ", KMaxHeap[i]);
	}
	printf("\n");
}

void TestTopK()
{
	int n = 1000000;
	int* a = (int*)malloc(sizeof(int) * n);
	srand(time(0));
	for (int i = 0; i < n; i++)
	{
		a[i] = rand() % n;
	}
	//手动设定10个最大的数
	a[5] = n + 1;
	a[1231] = n + 2;
	a[531] = n + 3;
	a[5121] = n + 4;
	a[115] = n + 5;
	a[2335] = n + 6;
	a[9999] = n + 7;
	a[76] = n + 8;
	a[423] = n + 9;
	a[3144] = n + 10;
	PrintTopK(a, n, 10);
}

int main()
{
	TestTopK();
	return 0;
}

6. 总结

到这里,我们就用学习完了数据结构中堆的相关知识点。有什么问题欢迎在评论区讨论。如果觉得文章有什么不足之处,可以在评论区留言。如果喜欢我的文章,可以点赞收藏哦!

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

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

相关文章

【VUE复习·10】v-for 高级::key 作用和原理;尽量不要使用 index 来遍历

总览 1.:key 作用和原理 2.尽量不要使用 index 来遍历 一、:key 作用和原理 1.数据产生串位的原因 在我们使用 index 进行遍历的时候&#xff0c;会出现虚拟 DOM 和 真实 DOM 的渲染问题。 二、尽量不要使用 index 来遍历 详情见视频 1/3 处&#xff1a; https://www.bili…

复杂的连接如何破坏智能家居体验

智能家居网络复杂性的增加可能会导致客户体验不佳、回报增加以及品牌声誉挑战。如果不加以解决&#xff0c;这一趋势可能会影响智能家居市场的未来增长。 智能家居网络复杂性的增加可能会导致客户体验不佳、回报增加以及品牌声誉挑战。如果不加以解决&#xff0c;这一趋势可能会…

【数据库——MySQL】(12)过程式对象程序设计——存储过程

目录 1. 存储过程2. 局部变量3. 条件分支3.1 IF 语句3.2 CASE 语句 4. 循环语句4.1 WHILE 语句4.2 REPEAT 语句4.3 LOOP和LEAVE语句4.4 LOOP和ITERATE语句 5. 存储过程应用示例参考书籍 1. 存储过程 要创建存储过程&#xff0c;需要用到 CREATE 语句&#xff1a; CREATE PROCED…

卸载无用Mac电脑软件应用程序方法教程

如何在Mac电脑卸载应用程序&#xff1f;Mac OS系统的用户卸载软件时&#xff0c;大部分会选择直接将软件图标拖进废纸篓清倒。这种操作会留下大量程序残余文件占据磁盘空间&#xff0c;手动清理又怕误删文件&#xff0c;有时还会遇到无法移除的恶意/流氓软件。小编今天分享3种可…

端口被占用怎么解决

第一步&#xff1a;WinR 打开命令提示符&#xff0c;输入netstat -ano|findstr 端口号 找到占用端口的进程 第二步&#xff1a; 杀死使用该端口的进程&#xff0c;输入taskkill /t /f /im 进程号&#xff08; &#xff01;&#xff01;&#xff01;注意是进程号&#xff0c;不…

分布式文件系统FastDFS实战

1. 分布式文件系统应用场景 互联网海量非结构化数据的存储需求&#xff1a; 电商网站&#xff1a;海量商品图片视频网站&#xff1a;海量视频文件网盘&#xff1a;海量文件社交网站&#xff1a;海量图片 2.FastDFS介绍 https://github.com/happyfish100/fastdfs 2.1简介 …

Spring Boot中配置文件介绍及其使用教程

目录 一、配置文件介绍 二、配置简单数据 三、配置对象数据 四、配置集合数据 五、读取配置文件数据 六、占位符的使用 一、配置文件介绍 SpringBoot项目中&#xff0c;大部分配置都有默认值&#xff0c;但如果想替换默认配置的话&#xff0c;就可以使用application.prop…

Unity如何实现TreeView

前言 最近有一个需求,需要实现一个TreeView的试图显示,开始我一直觉得这么通用的结构,肯定有现成的UI组件或者插件可以使用,结果,找了好久,都没有找到合适的插件,有两个效果差强人意。 最后在回家的路上突然灵光一闪,想到了一种简单的实现方式,什么插件都不用,仅使用…

react create-react-app v5配置 px2rem (暴露 eject方式)

环境信息&#xff1a; create-react-app v5 “react”: “^18.2.0” “postcss-plugin-px2rem”: “^0.8.1” 配置步骤&#xff1a; 我这个方式是 npm run eject 暴露 webpack配置的方法 1.安装 postcss-plugin-px2rem 和 lib-flexible cnpm install postcss-plugin-px2rem…

RV1126笔记四十一:RV1126移植LIVE555

若该文为原创文章,转载请注明原文出处。 RV1126的SDK有提供了一个librtsp.a封装好的RTSP推流库,但不开源,还有个确定延时长,所以想自己写一个RTSP的推流,但不想太麻烦,所以使用Live555。 记录下移植过程和测试结果。 live555需要用到的包有 openssl 和live555 一、 编…

基于SpringBoot的服装生产管理系统的设计与实现

目录 前言 一、技术栈 二、系统功能介绍 登录界面的实现 系统主界面的实现 用户管理模块的实现 人事安排管理模块的实现 工资管理模块的实现 考勤管理模块的实现 样板管理模块的实现 三、核心代码 1、登录模块 2、文件上传模块 3、代码封装 前言 本协力服装厂服装生…

队列的使用以及模拟实现(C++版本)

&#x1f388;个人主页:&#x1f388; :✨✨✨初阶牛✨✨✨ &#x1f43b;强烈推荐优质专栏: &#x1f354;&#x1f35f;&#x1f32f;C的世界(持续更新中) &#x1f43b;推荐专栏1: &#x1f354;&#x1f35f;&#x1f32f;C语言初阶 &#x1f43b;推荐专栏2: &#x1f354;…

AR智能眼镜:提升现场服务技能、效率与盈利能力的利器(一)

随着技术的不断进步&#xff0c;现场服务组织正朝着远程支持转变&#xff0c;用以解决技能差距和生产力问题&#xff0c;提高员工培训和操作效率&#xff0c;同时为企业提高利润率&#xff0c;创造竞争优势。 本文将探讨增强现实&#xff08;AR&#xff09;、辅助现实&#xf…

后台管理系统: 商品管理

商品管理之三级联动静态组件 先做俩个卡片组件&#xff0c;分开距离 三级联动很多地方都用到了它&#xff0c;我们可以封装成一个组件 注册为一个全局组件 <div><el-form :inline"true" class"demo-form-inline"><el-form-item label&qu…

数据集笔记:纽约花旗共享单车od数据

花旗共享单车公布的其共享单车轨迹数据&#xff0c;包括2013年-2021年曼哈顿、布鲁克林、皇后区和泽西城大约14500辆自行车和950个站点的共享单车轨迹数据 数据地址&#xff1a;Citi Bike System Data | Citi Bike NYC | Citi Bike NYC 性别&#xff08;0未知&#xff1b;1男&…

Idea引入thymeleaf失败解决方法

报错 Whitelabel Error Page This application has no explicit mapping for /error, so you are seeing this as a fallback.Fri Sep 29 09:42:00 CST 2023 There was an unexpected error (typeNot Found, status404). 原因&#xff1a;html没有使用thymeleaf 首先要引入…

pmp考试有包过班吗?靠谱吗?

PMP考试是全球范围内最为知名和认可的项目管理专业认证考试之一。对于想要在项目管理领域取得突破和进步的人来说&#xff0c;PMP认证是非常重要的一项资格。然而&#xff0c;对于很多考生来说&#xff0c;他们可能会关心一个问题&#xff1a;PMP考试有包过班吗&#xff1f;靠谱…

四川玖璨电子商务有限公司抖音培训引领电商新潮

近年来&#xff0c;随着电子商务的迅猛发展&#xff0c;抖音这个社交媒体平台也逐渐成为了商家必争之地。四川玖璨电子商务有限公司抖音培训&#xff0c;为你解锁电商流量密码&#xff0c;助你一飞冲天&#xff01; 一、抖音电商&#xff1a;下一个电商蓝海 作为拥有海量用户的…

操作系统相关杂项

系列文章目录 文章目录 系列文章目录前言一、dlopen, dlerror, dlclose直接执行动态库中的某个函数/某段代码 Linux共享库的组织共享库的构造和析构函数 动态链接堆栈初始化C全局构造与析构模拟实现库函数 freadsyscallsyscall 原理基于int的Linux的经典系统调用实现 前言 一、…

idea Springboot 图书管理系统VS开发mysql数据库web结构java编程计算机网页源码maven项目

一、源码特点 springboot 图书管理系统是一套完善的信息系统&#xff0c;结合springboot框架和bootstrap完成本系统&#xff0c;对理解JSP java编程开发语言有帮助系统采用springboot框架&#xff08;MVC模式开发&#xff09;&#xff0c;系统具有完整的源代码和数据库&#…