数据结构——堆(C语言)

基本概念:

1、完全二叉树:若二叉树的深度为h,则除第h层外,其他层的结点全部达到最大值,且第h层的所有结点都集中在左子树。

2、二叉树:满二叉树是一种特殊的的完全二叉树,所有层的结点都是最大值。

什么是堆

堆(英语:heap)是计算机科学中一类特殊的数据结构的统称。堆通常是一个可以被看做一棵树的数组对象。堆总是满足下列性质:

堆中某个节点的值总是不大于或不小于其父节点的值;

堆总是一棵完全二叉树。

根节点最大的堆叫做最大堆或大根堆根节点最小的堆叫做最小堆或小根堆。常见的堆有二叉堆、斐波那契堆等。

堆中父子节点结构的性质:在二叉树中,若当前节点的下标为 i, 则其父节点的下标为 i/2,其左子节点的下标为 i*2,其右子节点的下标为i*2+1;

堆的插入:

每次插入都是将先将新数据放在数组最后,由于从这个新数据的父结点到根结点必然为一个有序的序列,现在的任务是将这个新数据插入到这个有序序列中------这就类似于直接插入排序中将一个数据并入到有序区间中。

我们通过一个插入例子来看看插入操作的细节。我们将数字 16 插入到这个堆中:

如图所示,将16插入堆中

堆的数组是: [ 10, 7, 2, 5, 1 ]

第一步是将新的元素插入到数组的尾部,数组变成:[ 10, 7, 2, 5, 1, 16 ];

插入后相应的树就变成了:

16 被添加最后一行的第一个空位。

不行的是,现在堆属性不满足,因为 216 的上面,我们需要将大的数字在上面(这是一个最大堆)

为了恢复堆属性,我们需要交换 162

现在还没有完成,因为 10 也比 16 小。

我们继续交换我们的插入元素和它的父节点,直到它的父节点比它大或者我们到达树的顶部。这就是所谓的 shift-up,每一次插入操作后都需要进行。它将一个太大或者太小的数字"浮起"到树的顶部。

最后我们得到的堆:

现在每一个父节点都比它的子节点大。

最大堆:

构造最大堆

原始数据为a[] = {4, 1, 3, 2, 16, 9, 10, 14, 8, 7},采用顺序存储方式,对应的完全二叉树如下图所示:

基本思想:

首先将每个叶子节点视为一个堆,再将每个叶子节点与其父节点一起构造成一个包含更多节点的对。

所以,在构造堆的时候,首先需要找到最后一个节点的父节点 ,从这个节点开始构造最大堆;直到该节点前面所有分支节点都处理完毕,这样最大堆就构造完毕了。

假设树的节点个数为n,以1为下标开始编号,直到n结束。对于节点i,其父节点为i/2;左孩子节点为i*2,右孩子节点为i*2+1。最后一个节点的下标为n,其父节点的下标为n/2。

我们边针对上边数组操作如下图所示,最后一个节点为7,其父节点为16,从16这个节点开始构造最大堆;构造完毕之后,转移到下一个父节点2,直到所有父节点都构造完毕。

堆的构造:

数组,count表示内容大小,maxSize表示最大容量。

堆的判空、返回大小、初始化都很简单,直接返回性质(具体看最后代码)。

入堆:入堆需要判断他的大小,方法是:先将他放在最后面的位置(如图),然后依次和他的父亲比较,只要比父亲大,就交换。

出堆:直接取走顶端元素(arr[1]),然后把最后的元素挪到最前面,然后把它进行下移的操作。最后把数组最后一个元素删掉,并且count - 1就完成了。

具体代码

头文件:

复制代码
#define _CRT_SECURE_NO_WARNINGS 1

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

typedef int HeapDataType;

typedef struct MaxHeap {
	HeapDataType* data;
	int count;
	int MaxSize;
}MH;

//-----------堆的构建等等方法
int size(MH* mh);//返回堆大小
int isEmpty(MH* mh);//判空
void initMaxHeap(MH* mh, int size);//初始化堆
void initMaxHeap2(MH* mh, int size, HeapDataType* arr);//第二种初始化堆,heapify算法
void AdjustUp(MH* mh, int k);//上移元素
void AdjustDown(MH* mh, int k);//下移操作
void insertMaxHeap(MH* mh, HeapDataType value);//插入元素
HeapDataType TopK(MH* mh);//弹出元素
void TestMaxHeap();//测试函数

堆:

复制代码
#include"heap.h"

//返回堆大小
int size(MH* mh) {
	return mh->count;
}

//判空
int isEmpty(MH* mh) {
	if (mh->count == 0) {
		return 0;
	}
	else {
		return mh->count;
	}
}

//下移(构建最大堆)
void AdjustDown(MH* mh, int k)/*k为当前节点的索引*/{
	while (k * 2 <= mh->count)//只要当前节点有左孩子
	{
		int j = k * 2;//记录左孩子节点索引
		if (j + 1 <= mh->count && mh->data[j] < mh->data[j + 1])//如果右孩子存在且右孩子比左孩子大
		{
			j = j + 1;//记录右孩子节点索引
		}
		if (mh->data[k] > mh->data[j])//如果节点比孩子大
		{
			break;//不交换,已经是一个最大堆
		}
		//否则交换k和j
		int tmp = mh->data[k];
		mh->data[k] = mh->data[j];
		mh->data[j] = tmp;

		k = j;//移动记录节点到交换后的子孩子节点
	}
}


//初始化堆
void initMaxHeap(MH* mh, int size) {
	mh->MaxSize = size;//设定堆的最大容量
	mh->data = (HeapDataType*)malloc((mh->MaxSize + 1) * sizeof(HeapDataType));//从1开始存储
	mh->count = 0;
}

// 上移元素,调整堆中元素的顺序,确保堆的性质(最大堆)
void AdjustUp(MH* mh, int k) {
	// 当k不是堆的根节点且当前节点比父节点大时
	while (1 < k && mh->data[k / 2] < mh->data[k]) {
		// 交换当前节点和其父节点的值
		int tmp = mh->data[k / 2];   // 保存父节点的值
		mh->data[k / 2] = mh->data[k]; // 将当前节点的值赋给父节点
		mh->data[k] = tmp;             // 将父节点的值赋给当前节点

		// 更新k,移动到父节点的位置,继续检查堆的性质
		k /= 2; // 父节点的索引是当前节点索引的一半
	}
}


//插入元素
void insertMaxHeap(MH* mh, HeapDataType value) {
	//看看有没有满
	assert(mh->count + 1 <= mh->MaxSize);

	//count为最后一个元素
	mh->data[mh->count + 1] = value;
	mh->count++;

	AdjustUp(mh, mh->count);//上移到合适位置
}

// 弹出堆顶元素,并调整堆,使堆的性质得以保持
HeapDataType TopK(MH* mh) {
	// 确保堆中至少有一个元素,防止操作空堆
	assert(mh->count > 0);

	// 获取堆顶元素(最大值)
	HeapDataType res = mh->data[1];

	// 将堆中最后一个元素移到堆顶
	mh->data[1] = mh->data[mh->count];

	// 减少堆中元素的数量,并将最后一个元素置为0
	mh->count--;
	mh->data[mh->count + 1] = 0;

	// 将堆顶元素下移到正确的位置,恢复堆的性质
	AdjustDown(mh, 1);

	// 返回被弹出的堆顶元素
	return res;
}

测试函数:

复制代码
int main() {
	// 创建一个最大堆
	MH mh;
	initMaxHeap(&mh, 10); // 初始化最大堆,最大容量为10

	// 测试插入元素
	insertMaxHeap(&mh, 10);
	insertMaxHeap(&mh, 20);
	insertMaxHeap(&mh, 15);
	insertMaxHeap(&mh, 30);
	insertMaxHeap(&mh, 5);

	printf("堆的大小: %d\n", size(&mh));  // 输出堆的大小
	printf("堆是否为空: %d\n", isEmpty(&mh)); // 输出堆是否为空

	// 测试弹出堆顶元素
	printf("堆顶元素: %d\n", TopK(&mh));  // 弹出堆顶元素并输出
	printf("堆的大小: %d\n", size(&mh));  // 输出堆的大小

	// 弹出剩余元素
	while (isEmpty(&mh)) {
		printf("弹出的堆顶元素: %d\n", TopK(&mh));
	}
	return 0;
}

测试结果:

最小堆的整体操作和最大堆类似,这里不做赘述。

相关推荐
码流之上12 分钟前
【一看就会一写就废 指间算法】设计电子表格 —— 哈希表、字符串处理
javascript·算法
用户6120414922131 小时前
C语言做的文本词频数量统计功能
c语言·后端·敏捷开发
快手技术2 小时前
快手提出端到端生成式搜索框架 OneSearch,让搜索“一步到位”!
算法
CoovallyAIHub1 天前
中科大DSAI Lab团队多篇论文入选ICCV 2025,推动三维视觉与泛化感知技术突破
深度学习·算法·计算机视觉
NAGNIP1 天前
Serverless 架构下的大模型框架落地实践
算法·架构
moonlifesudo1 天前
半开区间和开区间的两个二分模版
算法
moonlifesudo1 天前
300:最长递增子序列
算法
CoovallyAIHub1 天前
港大&字节重磅发布DanceGRPO:突破视觉生成RLHF瓶颈,多项任务性能提升超180%!
深度学习·算法·计算机视觉
CoovallyAIHub1 天前
英伟达ViPE重磅发布!解决3D感知难题,SLAM+深度学习完美融合(附带数据集下载地址)
深度学习·算法·计算机视觉
聚客AI2 天前
🙋‍♀️Transformer训练与推理全流程:从输入处理到输出生成
人工智能·算法·llm