堆是一种特殊的树形数据结构,其每个节点的值都大于或等于(大顶堆)或小于或等于(小顶堆)其子节点的值。在计算机科学中,堆常用于实现优先级队列、堆排序等算法。本文将探讨如何使用数组实现堆,并分析其原理、实现细节以及应用场景。
注意:我们只是把数组在逻辑上想象成了抽象的堆,其实它本质上就是数组
堆的结构定义与顺序表基本是一致的,这也更说明了堆的概念更多的是在逻辑上更加抽象
包括
typedef int HPDataType;//数据类型重定义
typedef struct Heap//堆的结构定义
{
HPDataType* a;
int size;
int capacity;
}Heap;
void HeapInit(Heap* php)//初始化
{
assert(php);
php->a = NULL;
php->size = php->capacity = 0;
}
void HeapDestory(Heap* hp)//销毁
{
assert(hp);
free(hp->a);
hp->a = NULL;
hp->size = hp->capacity = 0;
}
void Adjustup(HPDataType* a, int child)//向上调整算法
{
assert(a);//数组必须存在,否则解引用就会报错
int parent = (child - 1) / 2;
while (child > 0 && a[parent] > a[child])//这里以小堆调整为例
{
Swap(&a[parent], &a[child]);//交换数据必须传地址
child = parent;
parent= (child - 1) / 2;
}
}
这里额外封装了一个交换函数,方便后面多次使用,并且想要通过形参改变实参的值,需要传址调用
void Swap(HPDataType* a, HPDataType* b)//交换函数
{
HPDataType tmp = *a;
*a = *b;
*b = tmp;
}
void HeapPush(Heap* hp, HPDataType x)//入堆
{
assert(hp);//接收的堆地址必须是有效的
if (hp->size == hp->capacity)//判断是否需要扩容
{
int newcapacity = hp->capacity == 0 ? 4 : (hp->capacity) * 2;
HPDataType* tmp = (HPDataType*)realloc(hp->a,sizeof(HPDataType) * newcapacity);
if (tmp == NULL)
{
perror("Push perror\n");
exit(1);
}
hp->a = tmp;
hp->capacity = newcapacity;
}
hp->a[hp->size++] = x;//插入到尾部
Adjustup(hp->a, hp->size - 1);//进行向上调整
}
void Adjustdown(HPDataType* a, int parent, int n)//向下调整算法
{
assert(a);
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 HeapPop(Heap* hp)//出堆
{
assert(hp);
assert(hp->size);//判断堆不为空
Swap(&(hp->a[0]), &(hp->a[hp->size - 1]));
hp->size--;//第一个数据与最后一个数据交换,然后删除最后一个
Adjustdown(hp->a, 0, hp->size);
}
HPDataType HeapTop(Heap* hp)// 取堆顶的数据
{
assert(hp);
assert(hp->size);
return hp->a[0];
}
int HeapEmpty(Heap* hp)//堆的判空
{
assert(hp);
return hp->size == 0;
}
int HeapSize(Heap* hp)//堆的数据个数
{
assert(hp);
return hp->size;
}
#include<stdio.h>
#include<assert.h>
#include<stdlib.h>
typedef int HPDataType;//数据类型重定义
typedef struct Heap//堆的结构定义
{
HPDataType* a;
int size;
int capacity;
}Heap;
//堆的初始化
void HeapInit(Heap* php);
// 堆的销毁
void HeapDestory(Heap* hp);
//向上调整算法
void Adjustup(HPDataType* a, int child);
//交换函数
void Swap(HPDataType* a, HPDataType* b);
// 堆的插入
void HeapPush(Heap* hp, HPDataType x);
// 向下调整算法
void Adjustdown(HPDataType* a, int parent, int n);
// 堆的删除
void HeapPop(Heap* hp);
// 取堆顶的数据
HPDataType HeapTop(Heap* hp);
// 堆的数据个数
int HeapSize(Heap* hp);
// 堆的判空
int HeapEmpty(Heap* hp);
#include"Heap.h"
void HeapInit(Heap* php)//初始化
{
assert(php);
php->a = NULL;
php->size = php->capacity = 0;
}
// 堆的销毁
void HeapDestory(Heap* hp)//销毁
{
assert(hp);
free(hp->a);
hp->a = NULL;
hp->size = hp->capacity = 0;
}
void Swap(HPDataType* a, HPDataType* b)//交换函数
{
HPDataType tmp = *a;
*a = *b;
*b = tmp;
}
void Adjustup(HPDataType* a, int child)//向上调整算法
{
assert(a);//数组必须存在,否则解引用就会报错
int parent = (child - 1) / 2;
while (child > 0 && a[parent] > a[child])//这里以小堆调整为例
{
Swap(&a[parent], &a[child]);//交换数据必须传地址
child = parent;
parent= (child - 1) / 2;
}
}
// 堆的插入
void HeapPush(Heap* hp, HPDataType x)//入堆
{
assert(hp);//接收的堆地址必须是有效的
if (hp->size == hp->capacity)//判断是否需要扩容
{
int newcapacity = hp->capacity == 0 ? 4 : (hp->capacity) * 2;
HPDataType* tmp = (HPDataType*)realloc(hp->a,sizeof(HPDataType) * newcapacity);
if (tmp == NULL)
{
perror("Push perror\n");
exit(1);
}
hp->a = tmp;
hp->capacity = newcapacity;
}
hp->a[hp->size++] = x;//插入到尾部
Adjustup(hp->a, hp->size - 1);//进行向上调整
}
void Adjustdown(HPDataType* a, int parent, int n)//向下调整算法
{
assert(a);
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 HeapPop(Heap* hp)//出堆
{
assert(hp);
assert(hp->size);//判断堆不为空
Swap(&(hp->a[0]), &(hp->a[hp->size - 1]));
hp->size--;//第一个数据与最后一个数据交换,然后删除最后一个
Adjustdown(hp->a, 0, hp->size);
}
HPDataType HeapTop(Heap* hp)// 取堆顶的数据
{
assert(hp);
assert(hp->size);
return hp->a[0];
}
int HeapSize(Heap* hp)//堆的数据个数
{
assert(hp);
return hp->size;
}
int HeapEmpty(Heap* hp)//堆的判空
{
assert(hp);
return hp->size == 0;
}
#include"Heap.h"
void test1()
{
Heap hp;
HeapInit(&hp);//初始化
if (HeapEmpty(&hp))
printf("堆空\n");
else
printf("堆非空\n");
int arr[] = { 5,7,8,11,2,9,15,13,21};
for (int i = 0; i < sizeof(arr) / sizeof(arr[0]); i++)//数据入堆
{
HeapPush(&hp, arr[i]);
}
if (HeapEmpty(&hp))
printf("堆空\n");
else
printf("堆非空\n");
printf("堆的数据个数:%d\n", HeapSize(&hp));
while (hp.size)//每次打印堆顶元素,并出堆
{
printf("%d ", HeapTop(&hp));
HeapPop(&hp);
}
printf("堆的数据个数:%d\n", HeapSize(&hp));
HeapDestory(&hp);
}
int main()
{
test1();
return 0;
}
优先队列: 堆可以高效地实现优先队列,支持按照元素的优先级进行插入和删除操作。
堆排序: 堆排序是一种基于堆的排序算法,具有O(nlogn)的时间复杂度。 参考文章: 【数据结构与算法】堆排序算法原理与实现:基于堆实现的高效排序算法-CSDN博客
数据流中的TopK问题: 在处理数据流时,可以使用堆来快速找到前K大或前K小的元素。 参考文章: 【数据结构与算法】利用堆结构高效解决TopK问题-CSDN博客
本文详细介绍了数组在堆数据结构中的妙用,并通过具体的代码示例和性能分析展示了其高效性和灵活性。通过深入学习堆的概念和实现方法,我们可以更好地理解其原理和应用场景,并在实际编程中灵活运用堆数据结构来解决各种问题。 如果看完本篇文章对您有所帮助,麻烦三连支持一下