堆的实现

news/2024/5/19 5:10:41 标签: 数据结构, 堆排序, c语言

思维导图

 

堆的概念

普通的二叉树是不适合用数组来存储的,因为可能会存在大量的空间浪费。而完全二叉树更适合使用顺序结构存储。现实中我们通常把堆(一种二叉树)使用顺序结构的数组来存储,需要注意的是这里的堆和操作系统虚拟进程地址空间中的堆是两回事,一个是数据结构,一个是操作系统中管理内存的一块区域分段。

堆的性质

如果有一个关键码的集合K = { , , ,…, },把它的所有元素按完全二叉树的顺序存储方式存储
在一个一维数组中,并满足: <= 且 <= ( >= 且 >= ) i = 0,1,2…,则称为小堆(或大堆)。将根节点最大的堆叫做最大堆或大根堆,根节点最小的堆叫做最小堆或小根堆。 

堆的性质:
堆中某个节点的值总是不大于或不小于其父节点的值;
堆总是一棵完全二叉树。 

堆结构体的定义及基本函数的声明

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

void HeapPrint(HP* php);

void HeapInit(HP* php);

void HeapDestroy(HP* php);

void AdjustUp(HPDataType* a, int size);
void HeapPush(HP* php, HPDataType x);

void AdjustDown(HPDataType* a, int size, int parent);
void HeapPop(HP* php);

HPDataType HeapTop(HP* php);

int HeapSize(HP* php);

bool HeapEmpty(HP* php);

void Swap(HPDataType* x, HPDataType* y);

 堆的插入

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(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)
		{
			printf("realloc fail");
			exit(-1);
		}
		php->a = tmp;
		php->capacity = newCapacity;
	}

	// 插入数据
	php->a[php->size] = x;
	php->size++;
	// 向上调整
	AdjustUp(php->a, php->size - 1);
}

堆的删除

void AdjustDown(HPDataType* a, int size, int parent)
{
	int child = parent * 2 + 1;
	while (child < size)
	{
		// 选出两个child小的那个
		if (child + 1 < size && 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 HeapPop(HP* php)
{
	assert(php);
	assert(!HeapEmpty(php));

	// 根节点和最后一个节点交换,向下调整
	Swap(&php->a[0], &php->a[php->size - 1]);
	php->size--;
	AdjustDown(php->a, php->size, 0);
}

其他函数的实现

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

void HeapInit(HP* php)
{
	assert(php);

	php->a = NULL;
	php->capacity = php->size = 0;
}

void HeapDestroy(HP* php)
{
	assert(php);

	free(php->a);
	php->a = NULL;
	php->capacity = php->size = 0;
}

void Swap(HPDataType* x, HPDataType* y)
{
	HPDataType tmp = *x;
	*x = *y;
	*y = tmp;
}


HPDataType HeapTop(HP* php)
{
	assert(php);
	assert(!HeapEmpty);

	return php->a[0];
}

int HeapSize(HP* php)
{
	assert(php);

	return php->size;
}

bool HeapEmpty(HP* php)
{
	assert(php);

	return php->size == 0;
}

堆排序

堆排序就是利用堆的思想来排序,将一个数组建造成堆有向上调整算法和向下调整算法。

我们分别来算一下两种算法的时间复杂度

向上调整算法建堆的时间复杂度

向下调整算法建堆的时间复杂度

向下调整算法的时间复杂度更优,按照堆删除的思想,每次取出堆中根节点的数据与最后一个叶子节点交换,然后再对最后一个节点前面的数据重新建堆的思路来排序。

如果是升序建小堆的话,每一次选出最小的数后,后面的数就要重新进行建堆,此时时间复杂度就是O(n)了,但是建大堆的话,每一次选出最大的数并将其与最后一个数交换,只需要根节点开始向下调整一次就再次建好一个大堆了,此时建好一次大堆的时间复杂度为log(n),建n次,最终时间复杂度就是O(n*log(n))了。

所以是升序建大堆,降序建小堆

堆排序的函数如下

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);
		end--;
	}
}

void HeapSortTest()
{
	int a[] = { 27,15,19,18,28,34,65,49,25,37 };
	HeapSort(a, sizeof(a) / sizeof(a[0]));
}

我这里写的是降序,但其实只要把向下调整算法中的交换逻辑反过来,变成建大堆,那么这个函数就变成升序函数了

Top-K问题

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

比如:专业前10名、世界500强、富豪榜、游戏中前100的活跃玩家等。
对于Top-K问题,能想到的最简单直接的方式就是排序,但是:如果数据量非常大,排序就不太可取了(可能数据都不能一下子全部加载到内存中)。最佳的方式就是用堆来解决,基本思路如下:
1. 用数据集合中前K个元素来建堆
前k个最大的元素,则建小堆
前k个最小的元素,则建大堆
2. 用剩余的N-K个元素依次与堆顶元素来比较,不满足则替换堆顶元素
将剩余N-K个元素依次与堆顶元素比完之后,堆中剩余的K个元素就是所求的前K个最小或者最大的元素。 

我这里求的是前k个最大的元素 

先获取n个数据,并设置好k个最大数

void TestTopk()
{
	int n = 10000;
	int* a = (int*)malloc(sizeof(int) * n);
	srand(time(0));
	for (int i = 0; i < n; ++i)
	{
		a[i] = rand() % 1000000;
	}
	a[5] = 1000000 + 1;
	a[1231] = 1000000 + 2;
	a[531] = 1000000 + 3;
	a[5121] = 1000000 + 4;
	a[115] = 1000000 + 5;
	a[2335] = 1000000 + 6;
	a[9999] = 1000000 + 7;
	a[76] = 1000000 + 8;
	a[423] = 1000000 + 9;
	a[3144] = 1000000 + 10;
	PrintTopK(a, n, 10);
}

再调用TopK函数 

void PrintTopK(HPDataType* a, int n, int k)
{
	// 将前k个数据建堆
	HPDataType* tmp = (HPDataType*)malloc(sizeof(HPDataType) * k);
	if (tmp == NULL)
	{
		printf("malloc fail");
		exit(-1);
	}
	for (int i = 0; i < k; i++)
	{
		tmp[i] = a[i];
	}
	for (int end = (k - 1 - 1) / 2; end >= 0; end--)
	{
		AdjustDown(tmp, k, end);
	}

	// 将剩余的n-k个元素与堆顶的数进行比较,满足条件则替换
	for (int i = k; i < n; i++)
	{
		if (a[i] > tmp[0])
		{
			tmp[0] = a[i];
			AdjustDown(tmp, k, 0);
		}
	}
	for (int i = 0; i < k; i++)
	{
		printf("%d ", tmp[i]);
	}
}

至于这里为什么另开一个空间来建堆,则是因为有时候不一定是从数组中读取数据,可能是从文件中读取。


http://www.niftyadmin.cn/n/213272.html

相关文章

普通人是否能从ChatGPT中分一杯羹?

ChatGPT3.0刚刚推出&#xff0c;最开始的时候&#xff0c;人们只是将ChatGPT看作一个很会聊天的机器人&#xff0c;无论问题多么天马行空&#xff0c;它的答案看上去都有理有据。后来&#xff0c;像打开潘多拉魔盒一样&#xff0c;很多人开始拿它编大纲、撰写文案、编代码、创作…

对于数量庞大的粮仓来说,如何全面监控粮仓环境?

对于数量庞大的粮仓来说&#xff0c;保好全面的粮仓环境监控&#xff0c;将大大减少因环境原因造成的粮食变质与浪费&#xff0c;积少成多&#xff0c;那将节省数字惊人的粮食。 因此&#xff0c;及时掌握粮堆的温度变化&#xff0c;对保护粮食安全具有重要的作用。 物联网云盒…

护眼灯真的可以保护眼睛吗?推荐五款达到护眼级别的灯

护眼灯是可以起到一定的保护视力的作用。 普通的台灯的出现是为了照明&#xff0c;它的功能只要照明。像眩光、频闪、蓝光等是普通台灯所存在的问题&#xff0c;而这些问题会造成我们的眼睛近视&#xff0c;所以在我国近年来青少年近视率越来越高的重要原因之一。 护眼灯就优化…

上海开放大学大学英语计分作业二答案

一、听力选择 Listen to a telephone conversation and choose the correct answer.记分作业二音频 &#xff08;请点击播放音频&#xff09; 1、&#xff08;2.0分&#xff09;Are the two people friends? A) Yes.B) No.2、&#xff08;2.0分&#xff09;Where is the wom…

【Android开发】App Bundle技术之动态功能模块

前言 自 2021 年 8 月起&#xff0c;Google Play 将开始要求新应用使用 Android App Bundle 进行发布。该格式将取代 APK 作为标准发布格式。虽然这个政策目前还无法影响到国内应用&#xff0c;但是作为Android开发者&#xff0c;对于新的动态还是要有一定的认识。 Android A…

《C Primer Plus》第16章复习题与编程练习

《C Primer Plus》第16章复习题与编程练习复习题1. 下面的几组代码由一个或多个宏组成&#xff0c;其后是使用宏的源代码。在每种情况下代码的结果是什么&#xff1f;这些代码是否是有效代码&#xff1f;&#xff08;假设其中的变量已声明&#xff09;复习题 1. 下面的几组代码…

浮点数比较

目录 使用java的BigDecimal 比较&#xff1a; 实战 使用java的BigDecimal 比较&#xff1a; import java.math.BigDecimal;public class DoubleLean {public static void main(String[] args) {BigDecimal aanew BigDecimal("1.23");BigDecimal bbnew BigDecimal(…

基于unapp的自定义picker组件

基于unapp的自定义picker组件 <template><view class"pop-picker-container"><uni-popup ref"popupRef" type"bottom" style"z-index: 999;"><view class"pop-content"><view class"pop-…