二叉树中堆的实现
1 堆的声明和定义
typedef int HPDateType;
typedef struct Heap {
HPDateType* arr;
int size;
int capcity;
}HP;
与顺序表相似,我们需要一个数组,有效空间大小,有效元素个数
2 堆的初始化
void HPInit(HP*php)
{
assert(php);
php->arr = NULL;
php->size = php->capcity = 0;
}
把数组置空,有效元素个数和有效空间大小置为0
3 堆的销毁
void HPDestroy(HP* php)
{
assert(php);
if (php->arr)
free(php->arr);
php->arr = NULL;
php->capcity = php->size = 0;
}
传递的参数当然不能为空,用assert断言,接着把arr的空间释放掉,让size,capcity为0
4 入堆
这里我们需要先讲解思路
首先,堆是用数组储存起来的,如果直接插到数组最后一位是不合理的
如图所示,可能空间是满的,这里就需要们重新开辟空间
同时,为了保证堆为一个有效堆(保证为大堆或者小堆),我们需要重新调整堆的排序
空间开辟
if (php->size == php->capcity)
{
int newcapcity = php->capcity == 0 ? 4 : 2 * php->capcity;
HPDateType* tmp = (HPDateType*)realloc(php->arr, newcapcity * sizeof(HPDateType));
if (tmp == NULL)
{
perror("realloc fail");
exit(1);
}
php->capcity = newcapcity;
php->arr = tmp;
}
利用realloc开辟,最后给capcity和arr赋值
堆调整--向上调整--小堆
void AdjustUp(HPDateType* arr, int child)//向上调整
{
while (child > 0)
{
int parent = (child - 1) / 2;
// <: 小堆
// >: 大堆
if (arr[child] < arr[parent])
{
swap(&arr[child], &arr[parent]);//swap函数的形参是两个指针,需要传地址
child = parent;
parent = (child - 1) / 2;
}
else {
break;
}
}
}
调整原理:将该入堆元素(child)插到末尾,顺着其父母结点(parent)往上调整,在堆为小堆的条件下,如果该元素比他的父母结点小就交换二者,再让其父母结点成为新的孩子结点,循环往复,直到新的孩子结点跳出,或者直白点说就是下标<0就跳出循环
注意,因为当child走到根结点时也需要比较之后判断是否要交换,所以不能只是parent走到空,必须要child走到空
完整入堆操作的实现
void HPPush(HP* php, HPDateType x)
{
assert(php);
//判断空间是否足够
if (php->size == php->capcity)
{
int newcapcity = php->capcity == 0 ? 4 : 2 * php->capcity;
HPDateType* tmp = (HPDateType*)realloc(php->arr, newcapcity * sizeof(HPDateType));
if (tmp == NULL)
{
perror("realloc fail");
exit(1);
}
php->capcity = newcapcity;
php->arr = tmp;
}
php->arr[php->size] = x;
AdjustUp(php->arr, php->size);//向上调整
++php->size;
}
如果是大堆的调整就把向上调整的" < "改为" > "
最后还需要让最大元素个数+1
5 出堆
思路讲解:这里出堆出的通常是堆顶元素,如果要出堆尾元素只需要让size--即可,意义不大,如果是出堆顶元素,那么这里我们一定不能用顺序表的向前覆盖来写,这样会让整个堆的结构混乱,我们不妨另辟蹊径,继续沿用交换操作,让根结点和最后一个结点交换,再出堆尾让size--,这时候我们关注交换到堆顶的元素,利用向下调整算法,让其成为有效堆
堆调整--向下调整--大堆
void AdjustDown(HPDateType*arr,int parent,int n)
{
int child = parent * 2 + 1;
while (child<n)
{
// 大堆:<
// 小堆:>
if (child+1<n&&arr[child] < arr[child + 1])
{
child++;
}
if (arr[child] > arr[parent])
{
swap(&arr[child], &arr[parent]);
parent = child;
child=parent * 2 + 1;
}
else {
break;
}
}
}
调整原理:从堆顶开始,此时的堆顶设为初始parent,我们取child结点中较大的一个作为比较对象,如过child>parent就交换,再让child成为新的parent,直到调整完成跳出循环,或者child走到最后一个结点
这里我们还需要注意child+1在调整的时候是否越界,以防出现非法访问
完整出堆操作的实现
void HPPop(HP* php)
{
assert(!HPEmpty(php));
swap(&php->arr[0], &php->arr[php->size - 1]);
--php->size;
AdjustDown(php->arr, 0, php->size);
}
其中判空函数
bool HPEmpty(HP* php)
{
assert(php);
return php->size == 0;
}
6 借助数据结构堆来实现的堆排序
先来写取堆顶元素的实现
HPDateType HPTop(HP* php)
{
assert(!HPEmpty(php));
return php->arr[0];
}
void HeapSort01(int* arr, int n)
{
HP hp;
HPInit(&hp);
for (int i = 0; i < n; i++)
{
HPPush(&hp,arr[i]);
}
int i = 0;
while (!HPEmpty(&hp))
{
int top = HPTop(&hp);
arr[i++] = top;
HPPop(&hp);//删除之后会重新排列
}
HPDestroy(&hp);
}
这种堆排序借助了入堆和出堆时的堆调整,因为每次出堆我们都获取了堆中最小或者最大的元素,所以最终得到了一个有序序列
7 常规堆排序
思路讲解:首先利用向下堆调整让待排序的数组建堆,接着将堆顶元素和最后一个元素交换,接着再进行堆调整直到end<0,实质上还是堆顶元素一定为最大(小)的出堆操作的运用
有如下示例帮助理解
这里我们实现了把最大元素一个个放到末尾 ,最终实现堆排序
void HeapSort(int* arr,int n)
{
//向下调整建堆
for (int i = (n - 2) / 2; i>=0;i--)
{
AdjustDown(arr, i, n);//i是最后一个节点的parent节点
}
int end = n - 1;
while (end>0)
{
swap(&arr[0], &arr[end]);
AdjustDown(arr, 0, end);
end--;
}
}
完