📋 个人简介
-
💖 作者简介:大家好,我是菀枯😜
-
🎉 支持我:点赞👍+收藏⭐️+留言📝
-
💬格言:不要在低谷沉沦自己,不要在高峰上放弃努力!☀️
前言
上一次我们学习了一些树的基础概念,树的性质等。今天我们试着用C语言来实现一种数据结构:堆
一. 什么是堆
堆的概念
n个元素的序列 k 1 , k 2 . . . , k n {k_1,k_2..., k_n} k1,k2...,kn当且仅当满足下关系时,称之为堆。
( k i ≤ k 2 i 且 k i ≤ k 2 i + 1 ) 或 者 ( k i ≥ k 2 i 且 k i > k 2 i + 1 ) , i = [ 1 , 2 n ] (k_i\leq k_{2i} 且 k_i \leq k_{2i + 1}) 或者(k_i \geq k_{2i} 且 k_i > k_{2i+1}),i=[1,\frac{2}{n}] (ki≤k2i且ki≤k2i+1)或者(ki≥k2i且ki>k2i+1),i=[1,n2]
若将和此次序列对应的一维数组(即以一维数组作此序列的存储结构)看成是一个完全二叉树,则堆的含义表明,完全二叉树中所有非终端结点的值均不大于(或不小于)其左、右子结点的值。由此,若序列 k 1 , k 2 . . . k n {k_1, k_2...k_n} k1,k2...kn是堆,则堆顶元素(或完全二叉树的根)必为序列中n个元素的最小值(或最大值).
堆的性质
- 堆中某个节点的值总是不大于或不小于其父节点的值;
- 堆总是一棵完全二叉树。
堆的分类
根据堆中根元素的大小我们可将堆分为两类
-
根元素若为堆中最大元素,我们将此堆称为大根堆
-
若根中元素为堆中最小元素, 我们就称此堆为小根堆
二. 堆的实现
2.1 堆的头文件
1. 包含的标准库
#include <stdio.h>
#include <stdlib.h>
#include <stdbool.h>
#include <assert.h>
2. 定义结构体
typedef int HeapDType; (1)
typedef struct Heap (2)
{
HeapDType* val;
int count;
int capacity;
}Heap;
- 堆中存放的数据类型。
- Heap中有一个指向存放元素的数组的指针,记录元素个数的count, 记录数组容量的capacity。
3.函数声明
void HeapInti(Heap* pheap);
// 堆的初始化
void HeapDestory(Heap* pheap);
// 堆的销毁
void HeapPop(Heap* pheap);
// 删除根的元素
void HeapPush(Heap* pheap, HeapDType x);
// 将x的加入进堆中
bool HeapEmpty(Heap* pheap);
// 判断堆是否为空
void HeapPrint(Heap* pheap);
// 显示堆中元素
HeapDType HeapTop(Heap* pheap);
// 取根元素
int HeapSize(Heap* pheap);
// 堆的大小
2.2 函数实现
1. 堆的初始化
void HeapInti(Heap* pheap)
{
assert(pheap); //(1)
pheap->val = NULL; (2)
pheap->capacity = pheap->count = 0;
}
- 防止pheap为空指针
- 将val置为空指针, 将count 和 capacity清0
2. 堆的销毁
void HeapDestory(Heap* pheap)
{
assert(pheap);
free(pheap->val); //(1)
pheap->val = NULL; //(2)
pheap->capacity = pheap->count = 0;
}
- 释放掉数组的空间
- 将val置为空指针, 将count 和 capacity清0
3. 将新元素放入堆中
与之前顺序表的添加,链表的添加不同,堆的添加会需要改变堆的结构。我们先来看一个动画,看看堆插入元素的思路:
- 将新元素放到堆的最下面。
- 判读新元素是否比父亲节点里的元素小
- 如果小,就交换子节点和父亲节点,否则循环结束。
代码实现:
void HeapPush(Heap* pheap, HeapDType x)
{
assert(pheap);
if (pheap->count == pheap->capacity) //(1)
{
int newCapacity = pheap->capacity == 0 ? 4 : pheap->capacity * 2;
HeapDType* tmp = (HeapDType*)realloc(pheap->val, sizeof(HeapDType) * newCapacity);
if (NULL == tmp) //(2)
{
printf("realloc error\n");
exit(-1);
}
pheap->capacity = newCapacity;
pheap->val = tmp;
}
pheap->val[pheap->count++] = x; //(3)
AdjustTop(pheap->val, pheap->count - 1);
}
- 判断是否需要对原数组进行扩容
- 如果开辟失败,退出程序
- 将新元素放到堆尾,然后开始向上进行调整
void AdjustTop(HeapDType* val, int child)
{
int parent = (child - 1) / 2; //(1)
while (child > 0)
{
if (val[child] < val[parent]) //(2)
{
Swap(&(val[child]), &(val[parent]));
child = parent;
parent = (child - 1) / 2;
}
else
{
break;
}
}
}
void Swap(HeapDType* px, HeapDType* py) //(3)
{
HeapDType tmp = *px;
*px = *py;
*py = tmp;
}
- 找子节点的父亲节点。
- 判断是否需要交换子节点和父亲节点
- 交换子节点和父亲节点
4. 删除根元素
堆的删除,我们无法用以前顺序表的那种方式直接用后面的数据覆盖前面的数据,原因如下:
- 会破坏堆原有的数据结构
- 时间复杂度为 O(n), 时间复杂度太高
所以我们需要一种新的方法,来删除根处元素,大家可以仔细思考一下,用什么样的方法比较好。
思路如下(看动图):
- 将根节点与最后一个叶子节点交换。
- 删除最后一个叶节点。
- 将父亲节点和左右子节点中较小的一个进行比较。
- 若父亲节点大于子节点,则进行交换,直至子节点都大于父亲节点。
void HeapPop(Heap* pheap)
{
assert(pheap);
if (!HeapEmpty(pheap)) //(1)
{
Swap(&(pheap->val[0]), &(pheap->val[pheap->count - 1])); //(2)
pheap->count--;
AdjustDown(pheap->val, pheap->count, 0); //(3)
}
}
- 判断堆是否为空,不为空则进行删除
- 将根节点与最后一个叶节点交换
- 向下进行调整
void AdjustDown(HeapDType* val, int size, int parent)
{
int child = 2 * parent + 1; //(1)
while (child < size)
{
if (val[child] > val[child + 1]) //(2)
{
child++;
}
if (val[parent] > val[child] && child <size) //(3)
{
Swap(&val[parent], &val[child]);
parent = child;
child = 2 * parent + 1;
}
else
{
break;
}
}
}
- 找父亲节点的左子节点。
- 若右子节点小于左子节点,则让child为右子节点。
- 交换子节点和父亲节点。
5. 判断堆是否为空
bool HeapEmpty(Heap* pheap)
{
assert(pheap);
return pheap->count == 0;
}
判断堆中元素是否为空即可
6. 求堆的大小
int HeapSize(Heap* pheap)
{
assert(pheap);
return pheap->count;
}
返回堆中元素个数
7. 取根元素
HeapDType HeapTop(Heap* pheap)
{
assert(pheap);
assert(pheap->count > 0); //(1)
return pheap->val[0];
}
- 防止堆为空。
8. 打印堆中元素
void HeapPrint(Heap* pheap)
{
assert(pheap);
for (int i = 0; i < pheap->count; i++)
{
printf("%d ", pheap->val[i]);
}
}
遍历堆中元素,打印即可。
三. 完整代码
#include <stdio.h>
#include <stdlib.h>
#include <stdbool.h>
#include <assert.h>
typedef int HeapDType;
typedef struct Heap
{
HeapDType* val;
int count;
int capacity;
}Heap;
void HeapInti(Heap* pheap)
{
assert(pheap);
pheap->val = NULL;
pheap->capacity = pheap->count = 0;
}
bool HeapEmpty(Heap* pheap)
{
assert(pheap);
return pheap->count == 0;
}
void HeapDestory(Heap* pheap)
{
assert(pheap);
free(pheap->val);
pheap->val = NULL;
pheap->capacity = pheap->count = 0;
}
void Swap(HeapDType* px, HeapDType* py)
{
HeapDType tmp = *px;
*px = *py;
*py = tmp;
}
void AdjustTop(HeapDType* val, int child)
{
int parent = (child - 1) / 2;
while (child > 0)
{
if (val[child] < val[parent])
{
Swap(&(val[child]), &(val[parent]));
child = parent;
parent = (child - 1) / 2;
}
else
{
break;
}
}
}
void HeapPush(Heap* pheap, HeapDType x)
{
assert(pheap);
if (pheap->count == pheap->capacity)
{
int newCapacity = pheap->capacity == 0 ? 4 : pheap->capacity * 2;
HeapDType* tmp = (HeapDType*)realloc(pheap->val, sizeof(HeapDType) * newCapacity);
if (NULL == tmp)
{
printf("realloc error\n");
exit(-1);
}
pheap->capacity = newCapacity;
pheap->val = tmp;
}
pheap->val[pheap->count++] = x;
AdjustTop(pheap->val, pheap->count - 1);
}
void HeapPrint(Heap* pheap)
{
assert(pheap);
for (int i = 0; i < pheap->count; i++)
{
printf("%d ", pheap->val[i]);
}
}
void AdjustDown(HeapDType* val, int size, int parent)
{
int child = 2 * parent + 1;
while (child < size)
{
if (child + 1 < size && val[child] > val[child + 1])
{
child++;
}
if (val[parent] > val[child] && child <size)
{
Swap(&val[parent], &val[child]);
parent = child;
child = 2 * parent + 1;
}
else
{
break;
}
}
}
void HeapPop(Heap* pheap)
{
assert(pheap);
if (!HeapEmpty(pheap))
{
Swap(&(pheap->val[0]), &(pheap->val[pheap->count - 1]));
pheap->count--;
AdjustDown(pheap->val, pheap->count, 0);
}
}
HeapDType HeapTop(Heap* pheap)
{
assert(pheap);
assert(pheap->count > 0);
return pheap->val[0];
}
int HeapSize(Heap* pheap)
{
assert(pheap);
return pheap->count;
}
结语
欢迎各位参考与指导!!!博主最近在冲击C/C++领域新人,拜托大家帮忙点赞收藏一下❤️