堆的概念:如果有一个关键码的集合k={k0,k1,k2,……kn-1},把所有的元素按照完全二叉树的顺序存储方式存储在一个一维数组中。如果满足:ki<=k2i+1且ki<=k2i+2; i=0,1,2……则称之为小堆;如果满足:ki>=k2i+1,ki>=k2i+2; i=0,1,2……则称之为大堆;将根节点最大的根叫做最大堆根或大根堆,将根节点最小的堆叫做最小堆或小根堆。
什么是堆?
通俗的来说就是二叉树的一种,按照特定规律摆放的一种完全二叉树。
对于任意的节点如果都比其孩子节点小是小堆;对于任意节点如果都比其孩子节点大为大堆。
堆的存储方式是属于二叉树的一种顺序结构。
普通的二叉树是不适合用数组来存储的。因为可能存在大量的空间浪费,而堆(完全二叉树)更适合使用顺序结构存储。
现实中我们通常把堆使用顺序结构的数组来存储。
需要注意的是这里堆和操作系统虚拟进程地址空间的堆是两回事, 一个是数据结构,一个是操作系统中管理内存的一块区域分段。
向下调整法是先找到倒数第一个非叶子节点,按照大堆或者小堆,从下往上
依次进行向下调整,给入一个根节点,child默认为左孩子,判断左孩子存在的情况下,如果右孩子存在,先将左右孩子进行对比找出较大的那一个,再将较大的与根节点进行比较,如果较大的孩子大于根,则进行交换,然后将孩子节点当作根节点,找它的孩子的下标进行下一轮比较。直到左孩子节点小于size,退出循环,调整完毕。
核心代码
void AdjustDown(Heap* hp, int parent)
{
//child为左孩子,因为parent可能不存在右孩子
DataType* arr = hp->arr;
int size = hp->size;
int child = 2 * parent + 1;
while (child < size)
{
//右孩子存在的情况下
//找到左右孩子中最小的孩子
if (child + 1<size && hp->pcom(arr[child+1],arr[child ]))
{
child += 1;
}
//检测双亲是否满足堆的特性
if (hp->pcom(arr[child] ,arr[parent]))
{
Swap(&arr[child], &arr[parent]);
parent = child;
child = 2 * parent + 1;
}
else
{
break;
}
}
}
向上调整法:只需要将最后一个叶子节点与其根节点进行比较交换,从下往上进行调整。一般用在堆的插入。
void AdjustUp(Heap* hp)
{
int child =hp-> size - 1;
int parent = ((child - 1) >> 1);
while (child>=0 )
{
if (hp->pcom(hp->arr[child] , hp->arr[parent]))
{
Swap(&hp->arr[child], &hp->arr[parent]);
child = parent;
parent = ((child - 1) >> 1);
}
else
{
return;
}
}
}
堆的初始化就是对结构体里面的成员,进行开辟空间,初始化,将数组中的元素放到创建的空间中,现在这个数组并不是堆,需要从倒数第一个非叶子节点进行调整一直调整到根节点,就可以调整成堆。这里的调整运用的是向下调整法。
//堆的初始化
void HeapInit(Heap* hp, DataType* arr, int size ,PCOM pcom)
{
int lastnotLeap;
assert(hp);
hp->arr = (DataType*)malloc((sizeof(DataType))*size);
if (NULL == hp->arr)
{
assert(0);
return;
}
hp->capacity = size;
memcpy(hp->arr, arr, sizeof(DataType)*size);
//将数组里面的元素放到hp中
//for (int i = 0; i < size; ++i)
//{
// hp->arr[i] = arr[i];
//}
hp->size=size;
hp->pcom = pcom;
//1.找倒数第一个非叶子节点
lastnotLeap = ((size-1)-1) / 2;
//2.从该节点位置开始逐个往前直到根节点,每遇到一个节点向下调整
for (int root = lastnotLeap; root >= 0; root--)
{
AdjustDown(hp, root);
}
}
堆的扩容实现了自动扩容,不用担心arr的空间不够。
判断如果有效个数size与容量capacity相等,则利用realloc创建空间大小为原来的2倍。然后将hp->capacity置为原来的2倍。
//扩容
void CheckCapacity(Heap* hp)
{
assert(hp);
if (hp->size == hp->capacity)
{
hp->arr = (DataType*)realloc(hp->arr, sizeof(DataType)*hp->size*2);
if (NULL == hp->arr)
{
assert(hp);
return;
}
hp->capacity *= 2;
}
}
核心代码
void HeapDestroy(Heap* hp)
{
assert(hp);
if (hp->arr)
{
free(hp->arr);
hp->arr = NULL;
hp->capacity = 0;
hp->size = 0;
}
插入 的时候,这里调用了扩容函数,会进行自动扩容,所以不用担心空间的问题;
将元素插入到数组的尾,在用向上调正,直到满足堆。
核心代码
void HeapInsert(Heap* hp, DataType data)
{
CheckCapacity(hp);
hp->arr[hp->size++] = data;
AdjustUp(hp);
}
检测非空,然后直接返回h->arr[0];
核心代码
DataType HeapTop(Heap* hp)
{
assert(!HeapEmpty(hp));
return hp->arr[0];
}
检测hp的合法性,然后直接返回, hp->size。
核心代码
int HeapSize(Heap* hp)
{
assert(hp);
return hp->size;
}
检测hp的合法性,然后直接返回hp->size==0。
核心代码
int HeapEmpty(Heap* hp)
{
assert(hp);
return 0 == hp->size;
}
交换函数
核心代码
void Swap(DataType* left, DataType* right)
{
int temp = *left;
*left = *right;
*right = temp;
}
比较函数:
核心代码
int less(DataType left, DataType right)
{
return left < right;
}
int Greater(DataType left, DataType right)
{
return left > right;
}
在进行堆实现的时候一般如果我们把代码写死的话,每次就只能写成大堆或者小堆。
在这里我们可以将有比较的写成比较函数,然后定义一个函数指针,指向比较函数,将函数指针当作参数,如果我们想要变为大堆或者小堆,只需要改变主函数里面的实参即可。
核心代码
typedef int(*PCOM)(DataType left, DataType right);
typedef struct Heap
{
DataType* arr;
int size;
int capacity;
PCOM pcom;//函数指针变量----指向所有的比较函数
}Heap;
堆排序:1.首先要进行建堆,如果排升序要建大堆,如果排降序要建小堆。
2.利用堆删除的思想进行排序----将根节点以最后一个叶子节点进行交换,然后删除最后一个节点,进行向下调整,调整好了,下标减去一 ,继续循环,直到节点被删完,退出循环,数组中输出的元素会从小到大排列好。
核心代码
// 大堆,向下调整法
void HeapAdjust(int arr[], int size, int parent)
{
int child = 2 * parent + 1;
while (child <size)
{
if (child+1<size &&arr[child + 1]>arr[child])
child += 1;
if (arr[child]>arr[parent])
{
int temp = arr[child];
arr[child] = arr[parent];
arr[parent] = temp;
parent = child;
child = 2 * parent + 1;
}
else
{
return;
}
}
}
//堆的排序
void HeapSort(int arr[], int size)
{
int end = size - 1;//最后一个元素的下标
//1建堆
for (int root = (size - 2) / 2; root >= 0; root--)
{
HeapAdjust(arr, size, root);
}
//2.利用堆删除的思想来进行排序
while (end > 0)
{
Swap(&arr[end], &arr[0]);
HeapAdjust(arr, end, 0);
end--;
}
}
将所学的知识进行总结,要走的路还有很长,继续加油吧!!!