什么是堆?
堆满足完全二叉树
小堆(大堆)中:任一结点的关键码均小于(大于)等于它的左右孩子的关键码,位于堆顶结点的关键码最小(最大),从根结点到每个结点的路径上数组元素组成的序列都是递增(递减)的。
我们来看下图:
现在,我们对堆进行如下操作:
头文件声明:
//动态扩容,最大尺寸为1024
#define HeapMaxSize 1024
typedef char HeapType;
typedef int(*Compare)(HeapType a,HeapType b);
typedef struct Heap
{
HeapType data[HeapMaxSize];
size_t size;
Compare cmp;
}Heap;
初始化
我们可以选择创建大堆还是小堆,通过结构体成员cmp
如果 a 和 b 满足比较规则,返回1,否则返回0
所谓的比较规则,对于小堆来说,就是 a < b;对于大堆来说,就是a > b。
因此,我们初始化的时候就要决定好是创建大堆还是小堆。
//为小堆建立比较函数
int Less(HeapType a,HeapType b)
{
return a < b ? 1 : 0;
}
//为大堆建立比较函数
int Greate(HeapType a,HeapType b)
{
return a > b ? 1 : 0;
}
void HeapInit(Heap* heap,Compare cmp)
{
if(heap == NULL)
{
return;
//非法输入
}
heap->size = 0;
heap->cmp = cmp;
return;
}
这里我选择创建了一个大堆。
堆的销毁
void HeapDestroy(Heap* heap)
{
if(heap == NULL)
{
return;
//非法输入
}
heap->size = 0;
heap->cmp = NULL;
return;
}
堆的插入
思路:插入一个元素时,可能会改变我们原有的堆的结构,因此需要对其进行调整。先将元素插入到最后,然后对其进行上浮式调整。如果当前插入的元素和它的父节点不满足比较关系,即该节点值大于其父节点(因为我们建的是大堆),就交换两个节点的值,否则,直接退出,停止上浮。
具体代码如下:
void Swap(HeapType* a,HeapType* b)
{
HeapType tmp = *a;
*a = *b;
*b = tmp;
return;
}
void AdjustUp(Heap* heap,size_t index)
{
size_t child = index;
size_t parent = (child-1)/2;
while(child > 0)
{
//如果child和parent不满足比较规则,即child > parent(大堆),就交换child和parent
if(!heap->cmp(heap->data[parent],heap->data[child]))
{
Swap(&heap->data[parent],&heap->data[child]);
}
else
{
//如果某一个位置下,child和parent已经满足比较规则
//直接退出,停止上浮
//因为更上面的节点一定是满足堆的要求的
break;
}
child = parent;
parent = (child-1)/2;
}
}
void HeapInsert(Heap* heap,HeapType value)
{
if(heap == NULL)
{
return;//非法输入
}
if(heap->size >= HeapMaxSize)
{
return;//堆满了
}
heap->data[heap->size++] = value;//将元素插入到最后
AdjustUp(heap,heap->size-1);//将最后一个元素进行上浮式调整
return;
}
取堆顶元素
直接将堆顶元素通过输出型参数输出
int HeapRoot(Heap* heap,HeapType* value)
{
if(heap == NULL)
{
return 0;//非法输入
}
*value = heap->data[0];
return 1;
}
删除堆顶元素
删除堆顶元素时,堆的结构肯定会发生改变。所以,这里我们的思路是:
先将堆顶元素和最后一个元素进行交换,然后进行尾删,此时原来的堆顶元素已经删除,但此时堆的结构已经破坏,现在的堆顶元素已经不是整个堆中最大的,所以要对其进行下沉式调整。先判断当前节点左右子树谁更大,然后拿最大的和父节点进行交换,直到父节点和其子树满足比较规则,就停止下沉。
具体代码如下:
void AdjustDown(Heap* heap,size_t index)
{
if(heap == NULL)
{
return;//非法输入
}
size_t parent = index;
size_t child = 2 * index + 1;
while(child < heap->size)
{
//如果右子树存在并且比左子树更符合堆的要求
//大堆,即右子树大于左子树,就让child指向右子树
if(child+1 < heap->size && heap->cmp(heap->data[child+1],heap->data[child]))
{
child = child + 1;
}
if(heap->cmp(heap->data[child],heap->data[parent]))
{
Swap(&heap->data[child],&heap->data[parent]);
}
else
{
break;
}
parent = child;
child = parent * 2 + 1;
}
}
void HeapErase(Heap* heap)
{
if(heap == NULL)
{
return;//非法输入
}
if(heap->size == 0)
{
return;//空堆
}
//交换堆顶元素和最后一个元素
Swap(&heap->data[0],&heap->data[heap->size-1]);
//进行尾删;
--heap->size;
//从根结点开始,进行下沉调整
AdjustDown(heap,0);
return;
}
堆的创建
我们用数组来创建一个堆,遍历整个数组,然后依次的将数组元素插入到这个堆中即可。插入的时候已经对堆的结构进行了调整。
void HeapCreate(Heap* heap,HeapType array[],size_t size)
{
if(heap == NULL)
{
return;//非法输入
}
//遍历array数组,将每个元素依次插入到堆中
size_t i = 0;
for( ;i < size;i++ )
{
HeapInsert(heap,array[i]);
}
return;
}
堆排序
创建堆的时候:
升序 —> 大堆
降序 —> 小堆
//假设我们要升序排序,就要一个大堆
void HeapSort(HeapType array[],size_t size)
{
//把这个数组构建成一个堆
Heap heap;
HeapInit(&heap,Greate);
HeapCreate(&heap,array,size);
//循环的删除堆顶元素
while(heap.size > 0)
{
HeapErase(&heap);
}
//循环结束后,堆排序就完成
memcpy(array,heap.data,size * sizeof(HeapType));
return;
}
堆排序是一种不稳定的排序算法。