堆的概念及结构
****堆是基于完全二叉树衍生的特殊数据结构,也是二叉树体系中最具实用价值的结构之一(比如堆排序、优先队列都依赖堆)。如果有一个关键码的集合 K={k0,k1,k2,...,kn−1},把它的所有元素按完全二叉树的顺序存储方式存储在一个一维数组中,并满足:Ki<=K2∗i+1(Ki>=K2∗i+1 且 Ki<=K2∗i+2),i=0、1、2......则称为小堆(或大堆),将根结点最大的堆叫做最大堆或大根堆,根结点最小的堆叫做最小堆或小根堆

如果是建立的小堆,那父节点就一定小于子节点

如果是建立的大堆,那父节点一定大于子节点
那我们用代码来实现一下建立大堆和小堆
首先说结论:对需要插入的数据进行排序使用向上调整算法,对初始化的值排序则是用向下调整算法
堆的代码结构
#include<stdio.h>
#include<assert.h>
#include<stdlib.h>
#include<stdbool.h>
typedef int HPDataType;
typedef struct Heap
{
HPDataType* data;
int size; //堆的数据个数
int capacity; //堆的总空间大小
}HP;
这里堆的结构很想顺序表,这是因为二叉树虽然在逻辑上是二叉树,但是物理上是数组构成,而顺序表就是用数组实现的,所以堆的底层逻辑还是数组
堆的初始化
void HPInit(HP* php)
{
assert(php);
php->data = NULL;
php->capacity = php->size = 0;
}
堆的销毁
void HPDestroy(HP* php)
{
assert(php);
php->data = NULL;
php->capacity = php->size = 0;
}
堆的初始化个销毁我们已经耳熟能详了,这里就不多讲解
插入数据
向上调整算法
在插入数据之前我们要先介绍一下向上调整算法

向上调整算法插入的值如果是要建立小堆,那就对新插入的值向上调整,看是否小于上面的值,如果小于,就双方调整位置,直到该值已经是比上一面的父节点要大就结束,若建立大堆区就相反
//向上调整算法
void Adjustup(HPDataType* data, int child)
{
int parent = (child - 1) / 2;
while (child>0)
{
if (data[child] < data[parent])
{
Swap(&data[child], &data[parent]);
child = parent;
parent = (child - 1) / 2;
}
else
{
break;
}
}
}
通过传递孩子节点计算父节点,"child>0"表示孩子节点要大于0,如果不大于0,就无法计算父节点,孩子节点小于父节点,交换位置,重新计算父节点
void Swap(HPDataType* p1, HPDataType* p2)
{
HPDataType tmp = *p1;
*p1 = *p2;
*p2 = tmp;
}
那现在有了向上调整算法,那就可以插入值了
void HPPush(HP* php, HPDataType x)
{
assert(php);
if (php->capacity == php->size)
{
int newCapacity = php->capacity == 0 ? 4 : 2 * php->capacity;
HPDataType* tmp = (HPDataType*)realloc(php->data, newCapacity * sizeof(HPDataType));//申请多大内存
if (tmp == NULL)
{
perror("realloc");
}
php->data = tmp;
php->capacity = newCapacity;
}
php->data[php->size] = x;
php->size++;
Adjustup(php->data, php->size - 1);
}
依旧是我们的开辟空间,然后把值传给向上调整算法,现在我们来测试一下代码的可行性
void TestHeap1()
{
int data[] = { 4,2,8,1,5,6,9,3 };
HP hp;
HPInit(&hp);
for (int i = 0; i < sizeof(data) / sizeof(data[0]); i++)
{
HPPush(&hp, data[i]);
}
int i = 0;
while (!HPEmpty(&hp))
{
printf("%d ", HPTop(&hp));
//data[i++] = HPTop(&hp);
HPPop(&hp);
}
HPDestroy(&hp);
}



这就是使用向上调整算法建立的小堆
向下调整算法
使用向下调整算法可以把初始化的值建成堆,也可以删除节点
//向下调整算法
void AdjustDown(HPDataType* data,int n,int parent)
{//假设左孩子小
int child = parent * 2 + 1;
while (child<n)//child>n,说明孩子不存在,调整到叶子了
{
if (child+1<n && data[child + 1] < data[child])
{
++child;
}
if (data[child] < data[parent])
{
Swap(&data[child], &data[parent]);
parent = child;
child = parent * 2 + 1;
}
else
{
break;
}
}
}
向下调整算法则是通过把父节点传过来,然后通过父节点计算出子节点,如果"child>n"就表示此时的父节点已经到了最后一行的某一个节点,就不存在子节点,循环结束,第一个if分支是判断找到的这个child节点的右边的节点,也就是右孩子在总的节点个数里面并且右孩子的值要小于左孩子,可以判断出这两个孩子哪一个更小,if分支结果为真就说明右孩子更小,就让原来指向左孩子的指针指向右孩子,第二个if分支是判断这个右孩子的值是否小于父节点,若为真,就让该右孩子和父节点替换位置,最后重新计算孩子节点,说明建立的是小堆
void HeapSort(int* a, int n)
{
//建堆
for (int i = (n - 1 - 1) / 2; i >= 0; i--)
{
AdjustDown(a, n, i);
}
for (int j = 0; j < n; j++)
{
printf("%d ", a[j]);
}
int end = n - 1;
while(end>0)
{
Swap(&a[0], &a[end]);
AdjustDown(a, end, 0);
printf("第 %d 轮调整后:", n - end);
for (int i = 0; i < n; i++)
{
printf("%d ", a[i]);
}
printf("\n");
--end;
}
}
在测试向下调整算法的时候也就是在创建大/小堆 ,第二个for循环则是在打印建立好的小堆

而while循环里面则是在输出倒序的数据

删除数据
首先删除数据是删除头结点 ,但是我们不能直接删除头结点,因为这样会破坏堆的父子节点关系
正确做法应该是先把头结点 和尾节点 交换位置,然后删除尾节点 ,这样实现起来比较简单,然后再使用向下调整算法 调整堆的父子节点关系

void HPPop(HP* php)
{
assert(php);
assert(php->size>0);
Swap(&php->data[0], &php->data[php->size-1]);
php->size--;
AdjustDown(php->data,php->size,0);
}
现在来测试一下删除功能
HP hp;
HPInit(&hp);
int nums[] = { 4,2,7,1,3 };
for (int i = 0; i < 5; i++)
{
HPPush(&hp, nums[i]);
}
HPPop(&hp);
for (int j = 0; j < hp.size ; j++)
{
printf("%d ", hp.data[j]);
}
printf("\n");


取出堆顶数据
HPDataType HPTop(HP* php)
{
assert(php);
assert(php->size > 0);
return php->data[0];
}
//在删除数据下面
int tmp=HPTop(&hp);
printf("%d", tmp);
