目录
引言
在学习完 数据结构------二叉树 后,我们接下来学习一下数据结构------堆。
堆
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);
}
输出结果如下:
结束语
本文介绍了数据结构中的堆。
求点赞收藏评论关注!!!
感谢各位大佬的支持!!!