数据结构——堆

目录

引言

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

输出结果如下:

结束语

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

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

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

相关推荐
CV工程师小林35 分钟前
【算法】BFS 系列之边权为 1 的最短路问题
数据结构·c++·算法·leetcode·宽度优先
Navigator_Z1 小时前
数据结构C //线性表(链表)ADT结构及相关函数
c语言·数据结构·算法·链表
还听珊瑚海吗1 小时前
数据结构—栈和队列
数据结构
Aic山鱼1 小时前
【如何高效学习数据结构:构建编程的坚实基石】
数据结构·学习·算法
菜菜想进步2 小时前
内存管理(C++版)
c语言·开发语言·c++
sjsjs112 小时前
【数据结构-一维差分】力扣1893. 检查是否区域内所有整数都被覆盖
数据结构·算法·leetcode
Lzc7742 小时前
堆+堆排序+topK问题
数据结构·
知星小度S2 小时前
C语言——自定义类型
c语言·开发语言
cleveryuoyuo2 小时前
二叉树的链式结构和递归程序的递归流程图
c语言·数据结构·流程图
科研小白_d.s2 小时前
vscode配置c/c++环境
c语言·c++·vscode