前言
嗨,各位程序猿和程序媛们!今天我要跟大家聊一聊一个有趣的话题——堆(heap)。别误会,我可不是在讲垃圾堆,虽然有时候我们的代码也可能像一堆垃圾一样令人头疼。堆其实是一种非常有用的数据结构,它可以帮助我们高效地解决很多问题,比如找出最大或最小的元素,构建优先队列等等。如果你还不了解堆,那么赶紧跟我来一起探索这个神奇的数据结构吧!
由于堆是特殊的二叉树,如果你还未曾了了解树、二叉树的概念。建议你先去阅读我的这一篇博客【数据结构】树和二叉树的介绍
一、堆的介绍
堆的概念:如果有一个关键码的集合K ={ko,ki1,k2,.,kn-1},把它的所有元素按完全二叉树的顺序存储方式存储在一个一维数组中,并满足:K;<= K2wi+1且K;<= K2i+2 (K>= K2wi+1且K;>= K2wi+2)i=O,1,.⒉…,则称为小堆(或大堆)。将根节点最大的堆叫做最大堆或大根堆,根节点最小的堆叫做最小堆或小根堆。
堆的性质:
- 堆中某个节点的值总是不大于或不小于其父节点的值;
- 堆总是一棵完全二叉树,符号完全二叉树的性质。
看文字或许难以理解,下面我们用图形来介绍
二、堆的实现
由上我们可以得出结论。要想实现一个堆,有以下三个条件。
(1)采用一维数组存储。
(2)父节点(father)对应的值 >= (或<=)子节点(child)对应的值。
(3)father,child的下标满足完全二叉树的性质。
文件的划分:
由于采用的是一维数组来存储,大部分和顺序表一致。
而与顺序表不同的则是,堆(Heap)的插入/删除需要调整数据的顺序,而这正好就是堆的难点。
- HeapPush()
- HeapPop()
更准确的说是它们内部包含的函数 AdjustUp()和AdjustDown()
本次示例采用的是大根堆:父节点 >= 子节点
- 插入操作详解:
2. 删除操作详解:删除操作指的是删除堆顶的元素。
其它函数就比较简单了,下面给出完整代码
Heap.h
#pragma once
#include <stdio.h>
#include <stdlib.h>
#include <stdbool.h>
#include <assert.h>
//大根堆
typedef int HPDateType;
typedef struct Heap {
HPDateType* a;
int size;
int capacity;
}Heap;
//堆的初始化
void HeapInit(Heap* php);
//堆增加元素
void HeapPush(Heap* php, HPDateType x);
//删除堆顶元素(即根元素)
void HeapPop(Heap* php);
//堆的大小
int HeapSize(Heap* php);
//返回堆顶元素
HPDateType HeapTop(Heap* php);
//堆销毁
void HeapDestroy(Heap* php);
//判断堆是否为空
bool HeapEmpty(Heap* php);
//打印
void HeapPrint(Heap* php);
//上述函数内部要用到的函数
//交换数据
void Swap(HPDateType* p1, HPDateType* p2);
//向上调整函数
void AdjustUp(HPDateType* a, int child);
//向下调整函数
void AdjustDown(HPDateType* a, int size, int father);
Heap.c
#define _CRT_SECURE_NO_WARNINGS 1
#include "Heap.h"
//大根堆 - father >= child
void Swap(HPDateType* p1, HPDateType* p2) {
HPDateType tmp = *p1;
*p1 = *p2;
*p2 = tmp;
}
void HeapInit(Heap* php) {
assert(php);
php->a = (HPDateType*)malloc(sizeof(HPDateType) * 10);
assert(php->a);
php->size = 0;
php->capacity = 10;
}
//向上调整 - HeapPush中要用
void AdjustUp(HPDateType* a, int child) {
int father = (child - 1) / 2;
while (a[father] < a[child]) {
Swap(&a[father], &a[child]);
child = father;
father = (child - 1) / 2;
}
}
void HeapPush(Heap* php, HPDateType x) {
assert(php);
if (php->size == php->capacity) {
HPDateType* tmp = (HPDateType*)realloc(php->a, sizeof(HPDateType) * php->capacity * 2);
assert(tmp);
php->a = tmp;
php->capacity = php->capacity * 2;
}
php->a[php->size++] = x;
AdjustUp(php->a, php->size-1);
}
//向下调整 - HeapPop中要用
void AdjustDown(HPDateType* a, int size, int father) {
int child = 2 * father + 1;
while (child < size) {
int MaxChild = child;
//判断是否具有右孩子
if (child + 1 < size) {
MaxChild = (a[child] >= a[child + 1] ? child : child + 1);
}
//判断最大的孩子是否大于父亲
if (a[MaxChild] > a[father]) {
Swap(&a[MaxChild], &a[father]);
father = MaxChild;
child = father * 2 + 1;
}
else {
return;
}
}
}
void HeapPop(Heap* php) {
assert(php);
assert(!HeapEmpty(php));
//根元素与尾元素交换
Swap(&php->a[0], &php->a[php->size - 1]);
//删除交换后的根元素
php->size--;
AdjustDown(php->a, php->size, 0);
}
void HeapPrint(Heap* php) {
assert(php);
int i = 0;
for (i = 0; i < php->size; i++) {
printf("%d ", php->a[i]);
}
printf("\n");
}
int HeapSize(Heap* php) {
assert(php);
return php->size;
}
HPDateType HeapTop(Heap* php) {
assert(php);
assert(!HeapEmpty(php));
return php->a[0];
}
void HeapDestroy(Heap* php) {
assert(php);
free(php->a);
php->size = 0;
php->capacity = 0;
}
bool HeapEmpty(Heap* php) {
return php->size == 0;
}
堆的实际运用
堆的概念与实现介绍完了,但这并不代表我们已经掌握了堆。
在堆排序这片博客中,我们将介绍堆的实际运用,掌握堆排序这一关键技能。
最后:
我希望你通过阅读这篇文章,能够对数据结构,堆有了更深入的理解和掌握。如果你觉得这篇文章对你有帮助,或者你想了解更多关于数据结构和算法的知识,请给我点赞👍,并关注我的博客。我会不定期地分享更多有趣和实用的内容。谢谢你的阅读和支持!😊