2024年9月2日更新:深入解读什么是堆及堆的储存方法

C语言堆实现建堆算法和堆排序

2024年9月2日更新:深入解读什么是堆及堆的储存方法

更新时间:二〇二四年九月二日 上午九点二十八分零九秒 作者:敲上瘾

本文主要介绍C语言堆实现建堆算法与堆排序。文中借助示例代码介绍得极为详细。其对大家的学习或工作具备一定参考学习价值。有需要的朋友们,跟随小编一同学习吧

目录

一.什么是堆?1.堆

堆是完全二叉树,是特殊的完全二叉树。它要满足条件,若每个父节点都大于子节点,这叫大堆;若每个父节点都小于子节点,这叫小堆。对于兄弟节点间大小关系没要求(所以它不是有序的)。如下:

2.堆的储存

对于完全二叉树而言 存在一种更好的储存方式 那便是采用顺序表进行储存 相较于链式储存 使用顺序表储存有个很大的优点 即当知晓某一结点时 能够轻易算出它父结点的下标 也能轻易算出它子结点的下标 并且还能够随机访问

父子结点下标计算公式:

二.堆结构的创建1.头文件的声明:

Heap.h

#pragma
#include
#include
#include
#define HpDataType int
typedef int HPDataType;
typedef struct Heap
{
	HPDataType* arr;
	int size;
	int cap;
}Heap;
该函数参数为指向Heap结构体的指针php
堆的销毁,声明函数HeapDestory,参数为指向Heap结构体的指针hp
参数二为堆的数据类型 HPDataType 的值 x
void HeapPop(Heap* hp);//堆的删除
HPDataType 取堆顶的数据函数 HeapTop,参数为 Heap* hp
整型 堆大小函数(堆指针)//获取堆中数据的个数
整型函数HeapEmpty,用于判断堆是否为空,参数为指向堆的指针hp
其功能是向上调整
定义一个名为AdjustDOWN的函数,该函数接收三个参数,分别是HpDataType类型指针arr、整型size和整型parent,其功能是向下调整
参数为HpDataType* a和HpDataType* b

其中堆的初始化、销毁、数据个数、判空,以及取堆顶数据的操作,和顺序表相同。这里重点学习堆的插入与删除。

2.向上调整

插入元素时直接往数组最后插入就行。不过插入后不一定是堆结构,所以需要调整。比如一个大堆:

向大堆中插入53

调整后:

代码示例:

第二个参数是整型变量child
{
计算父节点下标。通过(child - 1) / 2来得出,结果赋值给parent
当孩子大于0时//注意此处不能是父母大于0
	{
		if (arr[child] > arr[parent])
		{
封装一个函数。该函数用于交换。交换的是数组中两个元素。这两个元素分别是arr[child]和arr[parent]
			child = parent;//更新子节点
父节点等于(子节点减1)除以2
		}
		else
			break;
	}
}

如果是小堆只需要把if条件的大于号改为小于号

3.向下调整

要注意删除元素。我们删除的并非尾元素,那样做毫无意义。我们删除的是下标为0位置的元素。它是整个堆中最小或最大的元素。怎么删除呢?是直接将它删除然后让后面的元素覆盖上来吗?若如此操作 它便不再是堆 元素间关系会全然混乱 需从0开始创建堆 效率极低 我们能把首元素与尾元素互换 接着删除尾元素 虽说此操作后它可能不再是堆 但可将首元素向下调整 使其成为堆 比刚才方案效率高很多

比如我们删除大堆中的一个元素

调整过程:

调整后的结果:

代码示例:

第三个参数是整数parent
{
	int child = parent * 2 + 1;
	while (child < size)
	{
		if ((child+1) arr[parent])
		{
交换数组中父节点位置的元素和子节点位置的元素
			parent = child;
			child = parent * 2 + 1;
		}
		else
			break;
	}
}

还需要改变父子节点的大小关系

4.源码:

定义 _CRT_SECURE_NO_WARNINGS 为 1
#include"Heap.h"
void HeapInit(Heap* ps)//初始化
{
	assert(ps);
	ps->arr = NULL;
	ps->cap = ps->size = 0;
}
void HeapDestory(Heap* hp)  //进行堆的销毁操作
{
	assert(hp);
	free(hp->arr);
	hp->cap = hp->size = 0;
}
函数功能是交换元素
{
	HpDataType c = *a;
	*a = *b;
	*b = c;
}
使向上调整无效(Hp数据类型指针*数组,整数子节点)//向下调整
{
	int parent = (child - 1) / 2;
	while (child>0)
	{
		if (arr[child] < arr[parent])
		{
			Swap(&arr[child], &arr[parent]);
			child = parent;
			parent = (child - 1) / 2;
		}
		else
			break;
	}
}
void AdjustDOWN(HpDataType* arr, int size, int parent)//向上调整
{
	int child = parent * 2 + 1;
	while (child < size)
	{
		if (arr[child] > arr[child + 1])
			child++;
		if ((child+1)size == ps->cap)
	{
如果ps的cap值等于0 那么pnc赋值为4 否则pnc赋值为ps的cap值乘以2
HpDataType指针pnew指向通过realloc函数重新分配内存后的地址,该内存大小为HPDataType类型大小乘以pnc,原内存地址由ps->arr指向
		assert(pnew);
		ps->arr = pnew;
		ps->cap = pnc;
	}
	ps->arr[ps->size] = x;
	ps->size++;
调整向上(ps指向的数组,ps的大小减1)
}
void HeapPop(Heap* hp)//删除元素
{
	assert(hp);
	assert(hp->size);
	if (hp->size == 1)
	{
		hp->size--;
		return;
	}
把hp->arr[0]与hp->arr[hp->size - 1]进行交换处理
	hp->size--;
对hp指向的数组进行向下调整,调整范围是hp的大小,起始位置为0
}
HPDataType HeapTop函数用于取堆顶元素,该函数的参数为Heap* hp类型的指针,函数名为HeapTop
{
	assert(hp);
	assert(hp->size);
	return hp->arr[0];
}
函数返回值为整型
{
	assert(hp);
	return hp->size;
}
函数返回值为整型
{
	assert(hp);
	return hp->size == 0;
}

三.建堆算法

学习建堆算法时以对数组建堆为例。就是将数组数据间的关系做成堆结构。一般有两种方法。分别是向上调整建堆和向下调整建堆。具体做法如下。

1.向上建堆法

向上建堆法是通过向上调整来建堆。拿到一个数组后,将数组首元素当作堆。把第二个元素当作新元素插入堆中。接着通过向上调整构成新的堆。依此类推,遍历完数组后一个堆就建成了。其时间复杂度为O(N*logN)

代码示例:

#include
#include"Heap.h"
int main()
{
其值为数组arr的大小除以整型的大小
	for (int i = 0; i < size; i++)
		AdjustUP(arr, i);//该函数在上文已给出,这里不再展示
	printf("建大堆后:\n");
	for (int i = 0; i < size; i++)
		printf("%d ", arr[i]);
	return 0;
}

不过该方法与向下调整建堆相比,效率较低。我们来看看向下调整建堆法。

2.向下建堆法

向下建堆法是通过向下调整来建堆。要注意不能从首元素开始调整。因为刚开始时它不满足左右子树都是堆结构。所以不能直接从第一个元素开始向下调整。既然要使左右子树都成为堆,那我们可以考虑从最后一个元素着手调整。不过最后一层下面已无元素,它已然是堆,无需调整。如此,我们从倒数第二层开始调整。所以,我们先来算一算倒数第二层最后一个父节点的下标:

(size-1-1)/2

第一个size - 1获取二叉树最后一个元素的下标。将该下标减一。用减一之后的结果除以二。得到其父节点的下标。

代码示例:

#include
#include"Heap.h"
int main()
{
	int arr[] = { 1,9,3,7,6,4,2,10,8,5 };
	int size = sizeof(arr) / sizeof(int);
每次循环 i 自减 1
调用AdjustDOWN函数,参数为arr、size、i  该函数在上文已给出  这里不再展示
	printf("建大堆后:\n");
	for (int i = 0; i < size; i++)
		printf("%d ", arr[i]);
	return 0;
}

它的时间复杂度为O(N)证明如下:

其中Sn为总的调整次数.

四.堆排序

将一个数组建堆。利用堆的性质对数组排序,让效率更高。这便是堆排序。例如要对一个数组做堆排序。首要问题是建大堆还是小堆。以及如何利用堆为数组排序。

要进行升序,就得建大堆。要是建的是小堆,堆顶即首元素就是最小元素,无需变动。处理第二个元素时会发现,它不一定是第二小的元素,只能从第二个元素开始重新建一个小堆。如此,每排一个元素都要重新建一个小堆,效率就会变得很低。

若按升序构建大堆,那么第一个元素即为最大元素。我们能够让它与最后一个元素进行互换。接着把堆的元素个数减一,这意味着将最后一个元素视为在堆外。最后对堆顶元素进行向下调整,不断重复此操作,直至堆的元素个数变为零。如此一来,一个数组便按升序排列好了。

降序需要建小堆,原理和排升序相同这里就不在赘述。

代码示例:

#include
#include"Heap.h"
int main()
{
	int arr[] = { 1,9,3,7,6,4,2,10,8,5 };
	int size = sizeof(arr) / sizeof(int);
	for (int i = (size - 1 - 1) / 2; i >= 0; i--)
		AdjustDOWN(arr, size,i);
	printf("建大堆后:\n");
	for (int i = 0; i < size; i++)
		printf("%d ", arr[i]);
	while (size)
	{
		Swap(&arr[0], &arr[size - 1]);//交换元素
		size--;
		AdjustDOWN(arr, size, 0);
	}
	printf("\n排序后;\n");
	for (int i = 0; i < sizeof(arr) / sizeof(int); i++)
		printf("%d ", arr[i]);
	return 0;
}

五.在文件中Top出最小的K个数

使用堆结构有个好处。无需排序就能高效找出最小的前n个元素或最大的前n个元素。现在我们利用堆尝试找出文件中最小的K个数。一种比较低效的方法是。取出文件中涉及的所有数据。将其建成一个小堆。然后Pop出前k次。得到最小的k个数。但是倘若这个数据极大呢。比如说存在上亿个数据。如此便会耗费极大的内存空间。

有一种非常好的方法。就是只取出文件的前K个数来建成一个大堆。这个堆只存储K个元素。堆顶就是这个堆的最大元素。接着继续遍历文件。每遍历一个元素。都与堆顶元素作比较。如果比堆顶元素小。就更新堆顶元素。把小的那个变为堆顶元素。然后进行向下调整。直到遍历完整个文件。此时堆中的元素就是文件中最小的K个元素。这种方法在时间复杂度方面和上一种方法相近。然而它极大地节省了空间。

代码示例:

#include
#include"Heap.h"
void CreateNDate()
{
	//造数据,写入文件中
	int n = 10000;
使用当前时间的无符号整型值作为种子来初始化随机数生成器
该指针指向的内容为"data.txt"
	FILE* fin = fopen(file, "w");
	if (fin == NULL)
	{
		perror("fopen error");
		return;
	}
	for (int i = 0; i < n; ++i)
	{
		int x = rand() % 1000000;
		fprintf(fin, "%d\n", x);
	}
	fclose(fin);
}
void PrintTopK(int k)
{
	int* arr = (int*)malloc(sizeof(int) * k);
	assert(arr);
	FILE* fop = fopen("data.txt", "r");
	if (!fop)
	{
		perror("fopen error");
		return;
	}
	for (int i = 0; i < k; i++)//先取出k个建大堆
		fscanf(fop, "%d", &arr[i]);
	for (int i = (k - 1 - 1) / 2; i >= 0; i--)
		AdjustDOWN(arr, k, i);
	int x = 0;
当使用fscanf函数从fop中读取一个整数到x,且读取结果不等于文件结束标志时
	{
		if (arr[0] > x)
		{
			arr[0] = x;
			AdjustDOWN(arr, k, 0);
		}
	}
	for (int i = 0; i < k; i++)//输出堆中元素
		printf("%d ", arr[i]);
}
int main()
{
	CreateNDate();
	int k = 0;
	scanf("%d", &k);
	PrintTopK(k);
	return 0;
}

关于C语言堆实现建堆算法和堆排序的文章,到此介绍完毕。更多相关C语言堆内容,可搜索脚本之家以前的文章,或继续浏览下面的相关文章。希望大家以后多多支持脚本之家!

你可能想看:
« 上一篇
下一篇 »

相关推荐

FreeBasic:强大免费开源的64位basic编译器及应用优势

2025年05月10日

82阅读

脊柱侧弯矫正方法大盘点:运动康复、支具矫正等多种方式

2025年05月09日

88阅读

如何在winForm标题栏去掉标题文字且任务栏图标正常显示

2025年05月09日

86阅读

十一黄金周促销广告词大全,国庆购物狂欢等你来

2025年05月09日

78阅读

关闭快速编辑模式及插入模式,设置控制台指定文字颜色举例

2025年05月09日

63阅读

黄金搭档低糖高钙饮品包装及成分功效全解析,适合人群要知晓

2025年05月09日

91阅读