数据结构——堆

目录

引言

1.堆的概念

2.堆的实现方式

3.堆的功能

4.堆的声明

5.堆的功能实现

5.1堆的初始化

(1)代码实现

(2)复杂度分析

5.2.堆的插入

(1)代码实现

(2)复杂度分析

5.3.堆的删除

(1)代码实现

(2)复杂度分析

5.4.获取堆顶的元素

(1)代码实现

(2)复杂度分析

5.5.堆的元素个数

(1)代码实现

(2)复杂度分析

5.6.堆的判空

(1)代码实现

(2)复杂度分析

5.7.销毁堆

(1)代码实现

(2)复杂度分析

完整代码

结束语


引言

在学习完 数据结构------二叉树 后,我们接下来学习一下数据结构------堆。

1.堆的概念

堆是一种特殊的完全二叉树结构,在计算机科学和数据结构中广泛应用,特别是在堆排序算法和优先队列的实现中。堆可以通过数组来表示,这种表示方式利用了完全二叉树的性质,即除了最后一层外,每一层都被完全填满,并且所有节点都尽可能地向左对齐。

堆通常是一个可以被看作一棵完全二树的数组对象,若满足:

(1)任意节点的值>=其子节点的值。则称为大根堆

(2)任意节点的值<=其子节点的值。则称为小根堆

小根堆:

大根堆:

2.堆的实现方式

虽然堆是一种特殊的二叉树,它既可以用数组存储也可以用链式存储。但是考虑到其完全二叉树的特性,我们在这里采用数组存储的方式,因为这样既方便访问,也并不会浪费格外的空间。

如下所示:

数组与堆的映射关系:

1.若某节点在数组中的下标为i(i从0开始),则其左子节点(若存在)的下标为2i+1,右子节点(若存在)的下标为2i+2,其父节点(若存在)的下标为(i-1)/2。

2.堆的根节点在数组的下标为0。

3.堆的功能

我们要实现的堆的基本功能:

1.堆的初始化

2.堆的插入

3.堆的删除

4.获取堆顶的元素

5.堆的元素个数

6.堆的判空

7.销毁堆

4.堆的声明

由于我们使用数组来实现堆,因此堆的声明与顺序表类似。

代码如下所示:

复制代码
typedef int HPDataType;

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

5.堆的功能实现

在这里我们实现的是小根堆。

5.1堆的初始化
(1)代码实现
复制代码
// 堆的初始化
void HPInit(HP* php)
{
	assert(php);
	php->a = NULL;
	php->size = php->capacity = 0;
}
(2)复杂度分析

时间复杂度:由于没有额外的时间消耗,因此时间复杂度为O(1)。

空间复杂度:由于没有额外的空间消耗,因此空间复杂度为O(1)。

5.2.堆的插入
(1)代码实现

当我们试图对堆进行插入数据时,很有可能会破坏堆的原有结构,因此我们需要对其进行向上调整。

代码如下:

复制代码
// 交换
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 = (child - 1) / 2;
		}
		else
		{
			break;
		}
	}
}
// 堆插入数据
void HPPush(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, newcapacity * sizeof(HPDataType));
		if (tmp == NULL)
		{
			perror("realloc fail");
			return;
		}
		php->a = tmp;
		php->capacity = newcapacity;
	}
	php->a[php->size] = x;
	php->size++;
	AdjustUp(php->a, php->size - 1);
}
(2)复杂度分析

时间复杂度:n 是堆中元素的数量。在最坏的情况下,新插入的元素可能需要一直移动到堆的根节点(即堆的顶部)。由于堆的高度是 log_2(n+1),因此最多需要执行 log_2(n+1) 次比较和可能的交换操作。所以时间复杂度为O(log n)。

空间复杂度:由于没有额外的空间消耗,因此空间复杂度为O(1)。

5.3.堆的删除

堆的删除是指删除堆顶的数据。

如果我们直接删除堆顶元素并往前覆盖就可能打乱原有的亲缘关系。所以我们可以先将堆顶的元素与末尾元素交换 ,然后再进行向下调整·。

(1)代码实现
复制代码
// 堆向下调整
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 HPPop(HP* php)
{
	assert(php);
	assert(php->size > 0);
    // 将堆顶元素(即php->a[0])与堆的最后一个元素交换
	Swap(&php->a[0], &php->a[php->size - 1]);
	php->size--;
	AdjustDown(php->a, php->size, 0);
}
(2)复杂度分析

时间复杂度:最多需要执行 log_2(n+1) 次比较和可能的交换操作。所以时间复杂度为O(log n)。

空间复杂度:由于没有额外的空间消耗,因此空间复杂度为O(1)。

5.4.获取堆顶的元素
(1)代码实现
复制代码
// 读取堆顶数据
HPDataType HPTop(HP* php)
{
	assert(php);
	assert(php->size > 0);
	return php->a[0];
}
(2)复杂度分析

时间复杂度:由于没有额外的时间消耗,因此时间复杂度为O(1)。

空间复杂度:由于没有额外的空间消耗,因此空间复杂度为O(1)。

5.5.堆的元素个数
(1)代码实现
复制代码
// 获取堆的个数
int HeapSize(HP* php)
{
	assert(php);
	return php->size;
}
(2)复杂度分析

时间复杂度:由于没有额外的时间消耗,因此时间复杂度为O(1)。

空间复杂度:由于没有额外的空间消耗,因此空间复杂度为O(1)。

5.6.堆的判空
(1)代码实现
复制代码
// 判断堆是否为空
bool HPEmpty(HP* php)
{
	assert(php);
	return php->size == 0;
}
(2)复杂度分析

时间复杂度:由于没有额外的时间消耗,因此时间复杂度为O(1)。

空间复杂度:由于没有额外的空间消耗,因此空间复杂度为O(1)。

5.7.销毁堆
(1)代码实现
复制代码
// 堆的销毁
void HPDestroy(HP* php)
{
	assert(php);
	free(php->a);
	php->a = NULL;
	php->size = php->capacity = 0;
}
(2)复杂度分析

时间复杂度:由于没有额外的时间消耗,因此时间复杂度为O(1)。

空间复杂度:由于没有额外的空间消耗,因此空间复杂度为O(1)。

完整代码

Heap.h

复制代码
#define _CRT_SECURE_NO_WARNINGS 1
#include<stdio.h>
#include<assert.h>
#include<stdlib.h>
#include<stdbool.h>

typedef int HPDataType;

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

void Swap(HPDataType* p1, HPDataType* p2);
void AdjustUp(HPDataType* a, int child);
void AdjustDown(HPDataType* a, int n, int parent);

void HPInit(HP* php);
void HPDestroy(HP* php);
void HPPush(HP* php, HPDataType x);
void HPPop(HP* php);
HPDataType HPTop(HP* php);
bool HPEmpty(HP* php);
int HeapSize(HP* php);

Heap.c

复制代码
#define _CRT_SECURE_NO_WARNINGS 1
#include"Heap.h"

// 交换
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 = (child - 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 HPInit(HP* php)
{
	assert(php);
	php->a = NULL;
	php->size = php->capacity = 0;
}

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


void HPPush(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, newcapacity * sizeof(HPDataType));
		if (tmp == NULL)
		{
			perror("realloc fail");
			return;
		}
		php->a = tmp;
		php->capacity = newcapacity;
	}
	php->a[php->size] = x;
	php->size++;
	AdjustUp(php->a, php->size - 1);
}

// 删除堆顶数据
void HPPop(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 HPTop(HP* php)
{
	assert(php);
	assert(php->size > 0);
	return php->a[0];
}

//判断堆是否为空
bool HPEmpty(HP* php)
{
	assert(php);
	return php->size == 0;
}


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

测试用例

复制代码
void TestHeap()
{
	HP hp;
	HPInit(&hp);
	if (HPEmpty(&hp))
	{
		printf("堆空\n");
	}
	else
	{
		printf("堆非空\n");
	}
	int a[] = { 4,2,8,1,5,6,9,7,3,2,23,
		55,232,66,222,33,7,1,66,3333,999 };

	for (size_t i = 0; i < sizeof(a) / sizeof(int); i++)
	{
		HPPush(&hp, a[i]);
	}
	if (HPEmpty(&hp))
	{
		printf("堆空\n");
	}
	else
	{
		printf("堆非空\n");
	}

	printf("堆的数据个数:%d\n", HeapSize(&hp));

	while (!HPEmpty(&hp))
	{
		printf("%d ", HPTop(&hp));
		HPPop(&hp);
	}
	printf("\n");
	printf("堆的数据个数:%d\n", HeapSize(&hp));
	HPDestroy(&hp);
}

输出结果如下:

结束语

本文介绍了数据结构中的堆。

求点赞收藏评论关注!!!

感谢各位大佬的支持!!!

相关推荐
草莓熊Lotso1 小时前
【洛谷题单】--分支结构(二)
c语言·c++·经验分享·其他·刷题
snowfoootball2 小时前
2025 蓝桥杯C/C++国B 部分题解
c语言·c++·笔记·学习·贪心算法·蓝桥杯
Hx__4 小时前
Redis中String数据结构为什么以长度44为embstr和raw实现的分界线?
数据结构·数据库·redis
小立爱学习4 小时前
Linux 内存管理之page folios
linux·c语言
Skylar_.5 小时前
嵌入式 - 数据结构:哈希表和排序与查找算法
数据结构·算法·嵌入式·哈希算法·散列表
科大饭桶7 小时前
Linux系统编程Day9 -- gdb (linux)和lldb(macOS)调试工具
linux·服务器·c语言·c++
黑色的山岗在沉睡7 小时前
【无标题】
数据结构·c++·算法·图论
2301_785038188 小时前
c++初学day1(类比C语言进行举例,具体原理等到学到更深层的东西再进行解析)
c语言·c++·算法
ZLRRLZ9 小时前
【数据结构】哈希扩展学习
数据结构·学习·哈希算法
啊阿狸不会拉杆10 小时前
《算法导论》第 12 章 - 二叉搜索树
数据结构·c++·算法·排序算法