什么是堆?
堆使用数组表示的完全二叉树,并且其结点大于或小于其子节点。
大于是大堆,小于是小堆。
堆的操作有哪些
堆的初始化
我建立了一个堆结点hp,这样在后面调用堆操作函数中方便调用,这个结点中包括了堆(就是数组)的指针a
,堆(就是数组)的大小capacity
,以及堆(就是数组)下标size
。
#include<stdio.h>
#include<assert.h>
#include<malloc.h>
typedef int HPDataType;
typedef struct Heap
{
HPDataType* a;
int size;
int capacity;
}Heap;
void HeapInit(Heap* hp, HPDataType* a, int n)//初始化,n是要构建的堆大小,即数组a的大小
{
assert(hp);
hp->a = a;//将构建堆的数组传递到堆指针中
hp->capacity = n;
hp->size = 0;
}
堆的建立
这里建一个小堆为例子。
小堆的特点就是整棵树中最小的结点在堆顶,并且父节点总大于等于子结点。
1、从最后一个父亲结点开始,自底向上交换结点,那么最小的结点自然就会交换到堆顶。可以看下面动图更方便的理解。
因此这里写了一个大结点向下调整的函数
void AdjustDown(HPDataType* a, int size, int n)//size是堆大小,n是下标
{
assert(a);
int parent = n;//传进来的结点下标一定是父节点
int child = 2 * parent + 1;//左孩子
while (child < size)//当判断到叶子结点为止
{
if (parent * 2 + 2 == size)//判断有无右节点
child = 2 * parent + 1;
else
child = (a[2 * parent + 1] < a[2 * parent + 2] ?
2 * parent + 1 : 2 * parent + 2);//取较小的孩子和父结点交换
if (a[parent] > a[child])//构建小堆
{
Swap(&a[parent], &a[child]);//交换较小孩子和夫结点
parent = child;
child = parent * 2 + 1;
}
else
break;//左右孩子都比夫结点大就停止
}
}
建堆函数,从最后一个夫结点开始向下调整,将大结点都调整到下面
void CreatLiHeap(HPDataType* a, int size)//建小堆
{
for (int i = size / 2 - 1; i >= 0; i--)
{
AdjustDown_L(a, size, i);//向下调整,n是下标
}
}
堆插入结点
对于堆后面插入一个结点
- 要判断空间是否足够,不够则扩容。
- 对于插入的结点进行向上调整向上调整就是,如果插入的结点比他的父结点要小,就要把他调整到上面。
向上调整函数
void AdjustUp_L(HPDataType* a, int n)//向上调整,用来建小堆
{
assert(a);
int child = n;
int parent = (n - 1) / 2;//得到其父结点下标
while (child > 0)
{
if (a[parent] <= a[child])
break;//知道比其父节点小则停止
else
{
Swap(&a[parent], &a[child]);
child = parent;
parent = (child - 1) / 2;
}
}
}
插入函数
void HeapPush(Heap* hp, HPDataType x)//后插
{
assert(hp);
if (HeapFull(hp))//判满函数,满的话重新开辟空间
{
int newspace = hp->capacity * 2;
hp->a = (HPDataType*)realloc(hp->a, sizeof(HPDataType)*newspace);
hp->capacity = newspace;
}
hp->a[hp->size] = x;
hp->size++;
AdjustUp_L(hp->a, hp->size - 1);//向上调整
}
堆排序
如果我们了解堆的特性,就知道他的堆顶总是最小(大)值,并且将一个数组构建乘一个堆的时间复杂度为o(logN)。比遍历一个数组找到一个最小(大)时间复杂度o(n)效率高得多,因此就有了堆排序。
void HeapSort(int* a, int size)//堆排序//升序
{
//1、先建一个大堆(升序建立大堆)
for (int i = size / 2 - 1; i >= 0; i--)
{
AdjustDown(a, size, i);//向下调整,size是数组大小i是下标
}
int end = size - 1;//end是最后一个结点的数组下标
//2、每次令堆顶和堆尾元素交换
while (end>0)
{
Swap(&a[0], &a[end]);
//3、向下调整,改变数组范围
AdjustDown(a, end, 0);
end--;
}
}