一,堆的概念
如果有一个关键码的集合K = {k(0) ,k(1) ,k(2),…,k(n-1) },把它的所有元素按完全二叉树的顺序存储方式存储 在一个一维数组中,并满足: = 且 >= ) i = 0,1, 2…,则称为小堆(或大堆)。将根节点最大的堆叫做最大堆或大根堆,根节点最小的堆叫做最小堆或小根堆。
大堆和小堆的简单区分:
大堆:数据从上到下逐渐减小
小堆:数据从上到下逐渐增大
ps:堆总是一颗完全二叉树。
二,C语言实现的思路
1.堆的初始化
使用结构体变量进行堆的构建
typedef int(*Func)(int left, int right);
typedef struct Heap
{
DataType* array; // 1
int capacity; // 2
int size; // 3
Func Compare; // 4
}Heap;
结构体变量中应当有如下成员:
(1).定义一个指针变量,用来指向一处空间的地址。
(2).定义变量capacity,记录空间的容量。
(3).定义变量size,记录空间中有效元素的个数。
(4).定义一个函数指针,由于堆的种类有大堆和小堆之分,所以在后面选择堆的种类时,使用函数指针,方便调用两个不同的函数。
void HeapCreate(Heap* hp, DataType* a, int n, PFUNC Compare)
{
//申请空间用来保存堆中的元素
hp->array = (DataType*)malloc(sizeof(DataType)*n);
if (NULL == hp->array) //判断空间是否申请成功
{
assert(0);
return;
}
hp->capacity = n;//对容量使用函数中所传递的参数n进行初始化
memcpy(hp->array, a, sizeof(DataType)*n);//将数组a中的元素拷贝到堆(hp)中
hp->size = n;//有效元素个数的记录
hp->Compare = Compare;//函数指针的调用
//从第一个非叶子节点向上进行元素的调整
for (int root = (n - 2) / 2; root >= 0; root--)
{
AdjustDown(hp, root);
}
}
(5).使用函数完成对堆的初始化
2.堆的插入
void HeapPush(Heap* hp, DataType x)
{
CheckHeapCapacity(hp);
hp->array[hp->size] = x;
hp->size++;
AdjustUp(hp, hp->size - 1);
}
(1).在进行插入之前,首先要对堆中的容量进行检测,如果容量不足,则需要扩容。
(2).将要插入的元素插入到堆中的最末尾。在用代码进行表示时,使用数组的方式,且数组的下标是size。(其中size表示堆中元素的有效个数,新插入的元素的下标就是size)最终使size++即可。
(3).将新插入的元素进行调整。(选择使用大堆还是小堆的方式进行调整)
3.堆的删除
ps:默认该堆中的元素删除只能删除“节点的祖先”。
void HeapPop(Heap* hp)
{
if (HeapEmpty(hp))//使用函数判断堆是否有元素
{
return;
}
Swap(&hp->array[0], &hp->array[hp->size - 1]);//将堆顶元素与堆中最后一个元素进行交换
hp->size--;//将堆中有效元素个数减少1个
AdjustDown(hp, 0);// 将堆顶元素往下调整
}
(1).进行堆的判空。
(2).记录堆中的元素个数。
(3).使用函数调整插入元素后的堆的结构,使其满足大小堆的规则。
4.获取堆顶元素的值
DataType HeapTop(Heap* hp)
{
assert(hp);//判空
return hp->array[0];、//返回堆顶元素的值
}
(1).使用断言函数assert(头文件#include<assert.h>)判断是堆是否为空 .
(2) .使用数组的形式,直接返回数组下标为0的元素就是堆顶元素的值。
5.获取堆中有限元素的个数
int HeapSize(Heap* hp)
{
assert(hp);//判空
return hp->size;//返回元素的个数
}
(1).使用断言函数assert(头文件#include<assert.h>)判断是堆是否为空 。
(2).返回结构体变量中的成员变量size的值即代表了堆中有限元素的个数。
6.堆的判空
int HeapEmpty(Heap* hp)
{
assert(hp);//判空
return 0 == hp->size;//返回有效元素的个数
}
(1).判空。
(2).使用有效元素的个数来表示堆中是否为空。
7.堆的销毁
void HeapDestory(Heap* hp)
{
assert(hp);//判空
free(hp->array);//释放空间
hp->array = NULL;//使指向空间的指针指向NULL;
hp->capacity = 0;//将容量置为0
hp->size = 0;//将有效元素的个数置为0
}
(1).判空。
(2).malloc函数是在堆中申请的空间,在将空间使用完毕时,需要使用函数free手动释放。
(3).将空间容量和有效元素的个数置为0。
三.使用到的其他辅助函数
1.函数指针需要调用的替他函数(用来先择创建大堆还是小堆)
int Less(int left, int right)//小堆
{
return left < right;
}
int Greater(int left, int right)//大堆
{
return left > right;
}
//只是用来比较大小,所以不需要传递指针作为参数
2.数据交换函数
void Swap(DataType* left, DataType* right)
{
int temp = *left;//交换数据
*left = *right;
*right = temp;
}
3.向下调整函数(需要使堆在插入或者删除元素之后仍旧保持大(小)堆的性质)
void AdjustDown(Heap* hp, int parent)//从最后一个有叶子的节点逐个向上进行判断
{
int size = hp->size;
int* array = hp->array;
// child默认标记parent的左孩子
// 因为:parent如果有孩子,一定是先有左孩子再有右孩子
int child = parent * 2 + 1;
while (child < size)//孩子的下标一定小于有效元素的个数
{
//判断右孩子是否存在,并找出较小的一个
if (child + 1 < size && hp->Compare(array[child + 1], array[child]))
child += 1;
// 检测parent是否满足堆的特性
if (hp->Compare(array[child], array[parent]))
{
Swap(&array[child], &array[parent]);
// 需要继续往下检测
parent = child;
child = parent * 2 + 1;
}
else
return;
}
}
4.向上调整函数
void AdjustUp(Heap* hp, int child)
{
int* array = hp->array;
//变换双亲和孩子之间的位置关系
int parent = (child - 1) / 2;//逐个向上进行调整
while (child)//不为0
{
if (hp->Compare(array[child], array[parent]))
{
Swap(&array[child], &array[parent]);
child = parent;
parent = (child - 1) / 2;//性质公式
}
else
return;
}
}