数据结构——堆

目录

引言

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

输出结果如下:

结束语

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

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

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

相关推荐
daiyang123...5 分钟前
测试岗位应该学什么
数据结构
kitesxian18 分钟前
Leetcode448. 找到所有数组中消失的数字(HOT100)+Leetcode139. 单词拆分(HOT100)
数据结构·算法·leetcode
qystca2 小时前
洛谷 B3637 最长上升子序列 C语言 记忆化搜索->‘正序‘dp
c语言·开发语言·算法
薯条不要番茄酱2 小时前
数据结构-8.Java. 七大排序算法(中篇)
java·开发语言·数据结构·后端·算法·排序算法·intellij-idea
盼海4 小时前
排序算法(五)--归并排序
数据结构·算法·排序算法
网易独家音乐人Mike Zhou8 小时前
【卡尔曼滤波】数据预测Prediction观测器的理论推导及应用 C语言、Python实现(Kalman Filter)
c语言·python·单片机·物联网·算法·嵌入式·iot
搬砖的小码农_Sky10 小时前
C语言:数组
c语言·数据结构
先鱼鲨生12 小时前
数据结构——栈、队列
数据结构
一念之坤12 小时前
零基础学Python之数据结构 -- 01篇
数据结构·python
IT 青年12 小时前
数据结构 (1)基本概念和术语
数据结构·算法