数据结构——堆

目录

引言

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

输出结果如下:

结束语

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

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

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

相关推荐
XiaoLeisj18 分钟前
【递归,搜索与回溯算法 & 综合练习】深入理解暴搜决策树:递归,搜索与回溯算法综合小专题(二)
数据结构·算法·leetcode·决策树·深度优先·剪枝
小林熬夜学编程1 小时前
【Linux网络编程】第十四弹---构建功能丰富的HTTP服务器:从状态码处理到服务函数扩展
linux·运维·服务器·c语言·网络·c++·http
Jackey_Song_Odd1 小时前
C语言 单向链表反转问题
c语言·数据结构·算法·链表
乐之者v2 小时前
leetCode43.字符串相乘
java·数据结构·算法
A懿轩A3 小时前
C/C++ 数据结构与算法【数组】 数组详细解析【日常学习,考研必备】带图+详细代码
c语言·数据结构·c++·学习·考研·算法·数组
半盏茶香3 小时前
在21世纪的我用C语言探寻世界本质 ——编译和链接(编译环境和运行环境)
c语言·开发语言·c++·算法
️南城丶北离4 小时前
[数据结构]图——C++描述
数据结构··最小生成树·最短路径·aov网络·aoe网络
✿ ༺ ོIT技术༻4 小时前
C++11:新特性&右值引用&移动语义
linux·数据结构·c++
字节高级特工4 小时前
【C++】深入剖析默认成员函数3:拷贝构造函数
c语言·c++
计算机学长大白5 小时前
C中设计不允许继承的类的实现方法是什么?
c语言·开发语言