堆的定义
堆的概念解释
堆(Heap)是一种非常有用的数据结构,特别在需要频繁地进行插入和删除最大或最小元素的操作中表现出色。但请注意,这里的“堆”与编程语言中用于动态内存分配的“堆”是两个完全不同的概念。
简单地说,堆是一种特殊的树形数据结构,它满足堆属性:即任意节点都小于或等于(在最大堆中)或大于或等于(在最小堆中)其子节点。最常见的堆是二叉堆,它是一种完全二叉树,这意味着除了最后一层之外,每一层都是完全填满的,而且最后一层的元素都是靠左对齐的,是连续的。
最大堆和最小堆
最大堆:在最大堆中,父节点的值总是大于或等于其子节点的值。因此,根节点(也就是堆顶元素)总是包含堆中的最大值。
最小堆:与最大堆相反,在最小堆中,父节点的值总是小于或等于其子节点的值。因此,根节点包含堆中的最小值。
堆的应用举例
优先队列:堆是实现优先队列的理想数据结构,因为它能高效地插入新元素并删除最大或最小元素。
堆排序:堆排序算法使用堆数据结构来对数组进行排序,其时间复杂度为O(n log n)。
C语言实现堆
需要包含的头文件
#include<stdio.h>
#include<stdlib.h>//包含realloc()函数,作用是向栈申请一块空间
#include<assert.h>//包含assert()函数,作用是判断()内的内容是否为空
#include<stdbool.h>//包含bool函数,作用是判断返回值是否为0,若为0,则循环继续,若不为零,则跳出循环
#include<string.h>
堆的定义
typedef int HPDataType;//这里将int重命名为HPDataType是为了以后如果要用非int类型的时候方便改动,只需要改这一条代码就行
typedef struct Heap
{
HPDataType* a;
int size;//以下两行代码不用HPDataType声明是因为下标(size)和容量(capacity)一般都是用整数表示
int capacity;
}HP;//typedef使struct Heap重命名为HP,以便简化后续使用该结构体
堆的初始化
该函数通过断言确保传入的指针不是空的,并将结构体的三个成员分别初始化为 NULL、0 和 0,以确保在使用该结构体之前,它处于一个安全、已知的状态。
// 函数定义:初始化一个名为HP的结构体
// 参数:一个指向HP结构体的指针 php
void HPInit(HP* php)
{
// 断言:确保传入的指针 php 不是空指针
// 如果 php 是空指针,程序会在这里终止,并打印错误信息
assert(php);
// 将HP结构体的 a 成员初始化为 NULL
// 这通常是为了确保在使用前该指针是安全的,避免野指针问题
php->a = NULL;
// 将HP结构体的 size 成员初始化为 0
// size 表示当前HP结构体中存储的元素数量
php->size = 0;
// 将HP结构体的 capacity 成员初始化为 0
// capacity 表示HP结构体可以存储的元素数量的最大值
php->capacity = 0;
}
堆的销毁
以下代码定义了一个函数 HPDestroy,其作用是销毁一个HP结构体实例。具体来说,它首先检查传入的指针是否为空,然后释放该结构体中a成员指向的内存空间(这通常是一个动态分配的数组),并将a成员置为NULL以防止野指针。接着,它将capacity和size成员都设置为0,以表明该结构体当前没有分配任何内存,也没有存储任何元素。这是一个重要的安全实践同时也是良好的编程习惯,用于确保在销毁堆数据结构后不会留下任何可能导致错误或未定义行为的引用。
// 函数定义:销毁一个名为HP的结构体
// 参数:一个指向HP结构体的指针 php
void HPDestroy(HP* php)
{
// 断言:确保传入的指针 php 不是空指针
//只要是参数是指针都需要判断一下该指针是不是为空,这是一个良好的习惯
// 如果 php 是空指针,程序会在这里终止,并打印错误信息
assert(php);
// 释放HP结构体中a成员所指向的内存空间
// a成员可能是一个动态分配的数组或其他数据结构
// 注意:释放内存后,为了避免野指针,需要将指针置为NULL
free(php->a);
// 将a成员设置为NULL,以防止野指针的产生
// 这是一个重要的安全步骤,确保之后不会错误地访问已经释放的内存
php->a = NULL;
// 将capacity成员设置为0,表示当前没有分配任何容量
// 这是一个状态标记,用于表明内存已经被释放
php->capacity = 0;
// 将size成员设置为0,表示当前没有存储任何元素
// 与capacity一起,这两个值提供了关于HP结构体当前状态的清晰指示
php->size = 0;
}
交换函数
void swap(int* px, int* py) {//如果不传指针则交换不成功
int a = *px;
*px = *py;
*py = a;
}
堆的向上调整
这里使用的是左右孩子表示法,即左结点为左孩子,右结点为右孩子。
父子储存的下标规律:(左孩子)leftchild=parent x 2+1;(右孩子)rightchild=parent x 2+2;
parent=(child-1)/2,这里的child可以是leftchild也可以是rightchild。
void AdjustUp(HPDataType*a, int child) {//注意没有HP*php
int parent = (child - 1) / 2;//这里是已知parent找child
//while(parent>=0)
while (child > 0) {
if (a[child] > a[parent]) {
swap(&a[child], &a[parent]);
child = parent;//第八行
parent = (child - 1) / 2;//第九行
}
else {
break;
}
}
}
第八行:这里是已知child找parent,因为是向上调整,找到parent并且交换值后这个parent就是上面那个结点的child,这里的parent和child可以代表身份和下标,身份可以通过child = parent;赋值,这里是将parent的身份变成上面那个结点的child,然后再通过第九行用这个新的child去找到下一个新的parent,但是下标不会因为child = parent;而变化,然后如此循环。
堆的创建
以下代码定义了一个函数 HPPush,用于向一个最大堆(或最小堆,取决于 AdjustUp 函数的实现)中插入一个新元素。首先,它检查堆是否已满,如果已满则进行扩容操作。然后,在堆的末尾添加新元素,并通过调用 AdjustUp 函数来确保堆的性质得到维护。
// 函数定义:向堆(HP结构体)中插入一个新元素
// 参数:一个指向HP结构体的指针 php,以及要插入的数据 x
void HPPush(HP* php, HPDataType x) {
// 断言:确保传入的指针 php 不是空指针,记得养成习惯
// 如果 php 是空指针,程序会在这里终止,并打印错误信息
assert(php);
// 判断堆是否已满,即当前元素数量 size 是否等于容量 capacity
if (php->capacity == php->size) {
// 如果堆已满,则需要进行扩容
// 如果是第一次扩容(容量为0),则新容量为4;否则,容量翻倍
int newCapacity = php->capacity == 0 ? 4 : 2 * php->capacity;
// 使用realloc函数尝试重新分配内存,以扩大数组的容量
HPDataType* tmp = realloc(php->a, sizeof(HPDataType) * newCapacity);
// 检查realloc是否成功分配了内存
if (tmp == NULL) { //这个步骤也要养成习惯
// 如果realloc失败,则打印错误信息并返回,不继续执行后续操作
perror("realloc fail");
return;
}
// realloc成功后,更新HP结构体中的数组指针和容量信息
php->a = tmp;
php->capacity = newCapacity;
}
// 在堆的末尾插入新元素x
php->a[php->size] = x;
// 更新堆的大小(元素数量)
php->size++;
// 调用AdjustUp函数,对新插入的元素进行上浮调整,以保持堆的性质
// 传入的是堆数组、新插入元素的索引(即最后一个元素的索引,为size-1)
AdjustUp(php->a, php->size - 1);
}
取堆顶元素
这个函数 HPTop 的作用是返回堆顶元素。在堆这种数据结构中,堆顶元素通常是最优先的元素,对于最大堆来说它是最大的元素,对于最小堆来说它是最小的元素。这个函数通过直接访问堆数组的第一个元素(即索引为0的元素)来获取堆顶元素。注意这个函数只是把堆顶元素展示出来,并没有移动堆顶元素
//取堆顶元素
// 函数定义:获取堆顶元素
// 参数:一个指向HP结构体的指针 php
// 返回值:堆顶元素的数据类型 HPDataType
HPDataType HPTop(HP* php) {
// 断言:确保传入的指针 php 不是空指针
// 如果 php 是空指针,程序会在这里终止,并打印错误信息
assert(php);
// 返回堆顶元素,即数组中的第一个元素
// 在堆数据结构中,堆顶元素通常存储在数组的首位(索引为0)
return php->a[0];
}
向下调整算法
这个函数是堆数据结构中的一个关键部分,用于在移除堆顶元素或插入新元素后维护堆的性质。具体地,它是一个向下调整算法,通过比较和交换节点来确保每个父节点的值都大于或等于其子节点的值(在最大堆中)。这样,堆顶元素始终是最大值。函数通过循环和条件判断来不断地向下调整节点,直到整个堆重新满足堆的性质为止。
// 函数定义:向下调整算法,用于堆的维护
// 参数:
// a: 指向堆数据数组的指针
// size: 堆中元素的数量
// parent: 需要向下调整的父节点的索引
void AdjustDown(HPDataType* a, int size, int parent) {
// 计算父节点对应的左孩子的索引
int child = parent * 2 + 1;
// 当左孩子的索引小于堆的大小时,表示还有子节点需要比较
while (child < size) {
// 如果存在右孩子,并且右孩子的值大于左孩子,则选择右孩子进行比较
if (child + 1 < size && a[child + 1] > a[child]) {
child++;
}
// 如果子节点的值大于父节点,则交换它们的位置
// 这样做是为了保证父节点的值总是大于或等于其子节点的值,从而维护堆的性质
if (a[child] > a[parent]) {
swap(&a[child], &a[parent]); // 交换父节点和子节点的值
parent = child; // 更新父节点索引为当前的子节点索引
child = parent * 2 + 1; // 重新计算新的父节点对应的左孩子索引
}
else {
// 如果子节点的值不大于父节点,则堆的性质已经得到满足,退出循环
break;
}
}
}
堆的头删
// 函数定义:堆的头删操作,即移除堆顶元素并重新调整堆以保持其性质
// 参数:一个指向HP结构体的指针 php
void HPPop(HP* php) {
// 断言:确保传入的指针 php 不是空指针
// 如果 php 是空指针,程序会在这里终止,并打印错误信息
assert(php);
// 断言:确保堆中有元素可供删除
// 如果堆的大小为0,即没有元素可供删除,程序会在这里终止,并打印错误信息
assert(php->size > 0);
// 交换堆顶元素(数组中的第一个元素)和堆的最后一个元素的位置
// 这样做是为了将堆顶元素“移除”,同时不破坏数组中其他元素的相对顺序
swap(&php->a[0], &php->a[php->size - 1]);
// 减少堆的大小,以反映堆顶元素已被“移除”
php->size--;
// 调用AdjustDown函数,从堆顶开始向下调整堆
// 传入的是堆数组、新的堆大小和堆顶的索引(0)
// AdjustDown函数会确保在移除堆顶元素后,堆仍然保持其性质(最大堆或最小堆)
AdjustDown(php->a, php->size, 0);
}
堆的判空
// 函数定义:判断堆是否为空
// 参数:一个指向HP结构体的指针 php
// 返回值:如果堆为空,则返回true;否则返回false
//如果是在条件语句里true表示可以执行,false表示退出该语句
bool HPEmpty(HP* php)
{
// 断言:确保传入的指针 php 不是空指针
// 如果 php 是空指针,程序会在这里终止,并打印错误信息
assert(php);
// 判断堆的大小是否为0,如果是,则表示堆为空
// 返回判断结果,如果堆的大小为0,则返回true,表示堆为空
// 否则返回false,表示堆不为空
return php->size == 0;
}
测试
int main() {
HP hp;
int a[11] = { 34,74,14,20,30,50,82,68,30,72,55 };
HPInit(&hp);//记得初始化
for (int i = 0; i < 11; i++) {
HPPush(&hp, a[i]);
}
for (int i = 0; i < 11; i++) {
printf("%d ", hp.a[i]);
}
printf("\n");
HPPop(&hp);
printf("%d", HPTop(&hp));
HPPop(&hp);
printf("\n");
printf("%d", HPTop(&hp));
printf("\n");
for (int i = 0; i < 9; i++) {
printf("%d ", hp.a[i]);
}
return 0;
}
完整代码
#include<stdio.h>
#include<stdlib.h>
#include<assert.h>
#include<stdbool.h>
typedef int HPDataType;
typedef struct Heap {
HPDataType* a;
int size;
int capacity;
}HP;
//堆的初始化
void HPInit(HP* php) {
php->a = NULL;
php->size = php->capacity = 0;
}
//堆的销毁
void HPDestory(HP* php) {
assert(php);
free(php->a);
php->size = php->capacity = 0;
}
//交换函数
void swap(int* px, int* py) {
int a = *px;
*px = *py;
*py = a;
}
//堆的向上调整
void AdjustUp(HPDataType*a, int child) {//注意没有HP*php
int parent = (child - 1) / 2;
//while(parent>=0)
while (child > 0) {
if (a[child] > a[parent]) {
swap(&a[child], &a[parent]);
child = parent;
parent = (parent - 1) / 2;
}
else {
break;
}
}
}
//堆的创建
void HPPush(HP* php, HPDataType x) {
assert(php);
if (php->capacity == php->size) {
int newCapacity = php->capacity == 0 ? 4 : 2 * php->capacity;
HPDataType* tmp = realloc(php->a, sizeof(HPDataType) * newCapacity);
if (tmp == NULL) {//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);//注意
}
//取堆顶元素
HPDataType HPTop(HP* php) {
assert(php);
return php->a[0];
}
//向下调整算法
void AdjustDown(HPDataType* a,int size, int parent) {
int child = parent * 2 + 1;
while (child < size) {
if (child + 1 < size && 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);//注意
swap(&php->a[0], &php->a[php->size - 1]);
php->size--;//头删 ↓此处不能为php->a[0],是parent的下标
AdjustDown(php->a, php->size,0);
}
//堆的判空
bool HPEmpty(HP* php)
{
assert(php);
return php->size == 0;
}
int main() {
HP hp;
int a[11] = { 34,74,14,20,30,50,82,68,30,72,55 };
HPInit(&hp);//记得初始化
for (int i = 0; i < 11; i++) {
HPPush(&hp, a[i]);
}
for (int i = 0; i < 11; i++) {
printf("%d ", hp.a[i]);
}
printf("\n");
HPPop(&hp);
printf("%d", HPTop(&hp));
HPPop(&hp);
printf("\n");
printf("%d", HPTop(&hp));
printf("\n");
for (int i = 0; i < 9; i++) {
printf("%d ", hp.a[i]);
}
return 0;
}
若有错误或问题,欢迎在评论区提出