要对堆进行操作首先应该清楚什么是堆?
堆:是计算机科学中一类特殊的数据结构的统称,堆通常是一个可以被看做一棵树的数组对象。
堆的性质:
- 堆的某个节点的值总是不大于或不小于其父节点的值;
- 堆总是一棵完全二叉树
堆有大堆小堆之分,根节点最大的堆叫做最大堆或大根堆,根节点最小的堆叫做最小堆或小根堆。常见的堆有二叉堆、斐波那契堆等。
堆的定义如下:
n个元素的序列{k1,k2,...,kn}当且仅当满足以下关系时,称之为堆。
(ki <= k2i,ki <= k2i+1)或者(ki >= k2i,ki >= k2i+1),(i = 1,2,3,...n/2)
heap.c文件
#pragma once
#include<stddef.h>
#define HeapMaxSize 1024
typedef char HeapType;
//如果a和b满足比较关系,返回1
//如果a和b不满足比较关系,返回0
//所谓的比较关系,对于小堆来说就是a<b
//对于大堆来说,就是a>b
typedef int (*Compare)(HeapType a,HeapType b);
typedef struct Heap
{
HeapType data[HeapMaxSize];
size_t size;
Compare cmp;
}Heap;
1.堆的初始化
(1)实现函数:
int Less(HeapType a,HeapType b)//为小堆打造的比较函数
{
return a < b ? 1 : 0;
}
int Greater(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;
}
(2)测试代码:
void TestInit()
{
TEST_HEADER;
Heap heap;
HeapInit(&heap,Greater);
printf("size expected 0,actual %lu\n",heap.size);
printf("cmp expected %p,actual %p\n",Greater,heap.cmp);
}
(3)运行结果:
2.销毁堆
(1)实现代码:
void HeapDestroy(Heap* heap)
{
if(heap == NULL)
{
//非法输入
return;
}
heap->size = 0;
heap->cmp = NULL;
return;
}
(2)测试代码:
void TestDestroy()
{
TEST_HEADER;
Heap heap;
HeapDestroy(&heap);
printf("size expected 0,actual %lu\n",heap.size);
printf("cmp expected NULL,actual %p\n",heap.cmp);
}
(3)运行结果:
3.向堆中插入元素
思想:先将要插入的元素插入到堆的最后一个位置上然后进行上浮式调整
(1)实现代码:
void Swap(HeapType* a,HeapType* b)
{
HeapType tmp = *a;
*a = *b;
*b = tmp;
return;
}
void AdjustUp(Heap* heap,size_t index)
{
if(heap == NULL)
{
return;
}
//一个变量只做一件事
size_t child = index;
size_t parent = (child -1)/2;
while(child > 0)
{
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;
}
return;
}
void HeapInsert(Heap* heap,HeapType value)
{
if(heap == NULL)
{
//非法输入
return;
}
if(heap->size >= HeapMaxSize)
{
//堆已经满了,无法再插入
return;
}
heap->data[heap->size++] = value;
//对这个堆进行上浮调整
//调整的起始位置是size-1
AdjustUp(heap,heap->size - 1);
return;
}
(2)测试代码:
void HeapPrintChar(Heap* heap,const char* msg)
{
printf("[%s]\n",msg);
size_t i = 0;
for(;i < heap->size; ++i)
{
printf("[%c | %lu] ",heap->data[i],i);
}
printf("\n");
}
void TestInsert()
{
TEST_HEADER;
Heap heap;
HeapInit(&heap,Greater);
HeapInsert(&heap,'c');
HeapInsert(&heap,'b');
HeapInsert(&heap,'a');
HeapInsert(&heap,'e');
HeapInsert(&heap,'f');
HeapInsert(&heap,'d');
HeapPrintChar(&heap,"给堆中插入6个元素");
}
(3)运行结果:
4.取堆顶元素
(1)实现代码:
int HeapRoot(Heap* heap,HeapType* value)
{
if(heap == NULL || value == NULL)
{
//非法输入
return 0;
}
if(heap->size == 0)
{
//空堆
return 0;
}
*value = heap->data[0];
return 1;
}
(2)测试代码:
void TestRoot()
{
TEST_HEADER;
Heap heap;
HeapInit(&heap,Greater);
HeapInsert(&heap,'c');
HeapInsert(&heap,'b');
HeapInsert(&heap,'a');
HeapInsert(&heap,'e');
HeapInsert(&heap,'f');
HeapInsert(&heap,'d');
HeapType value;
int ret = HeapRoot(&heap,&value);
printf("ret expected 1,actual %d\n",ret);
printf("value expected f,actual %c\n",value);
}
(3)运行结果:
5.删除堆顶的元素
思想:现将堆顶元素和堆的最后一个元素进行交换,然后删掉最后一个元素(即此时已将堆顶元素删除,但是可能并不满足堆的要求),所以对堆进行下沉式调整
(1)实现代码:
void AdjustDown(Heap* heap,size_t index)
{
if(heap == NULL)
{
return;
}
if(heap->size == 0)
{
return;
}
size_t parent = index;
size_t child = 2 * index + 1;
while(child < heap->size)
{
if(child+1 < heap->size && heap->cmp(heap->data[child+1],heap->data[child]))
{
//如果右子树存在,并且右子树比左子树更符合堆的要求
//假设我们这是个小堆,就要求说
//如果右子树比左子树小,那么就让child指向右子树
child = child + 1;
}
//child 就指向了左右子树中更小的那个元素
if(heap->cmp(heap->data[child],heap->data[parent]))
{
Swap(&heap->data[parent],&heap->data[child]);
}
parent = child;
child = 2 * parent + 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;
}
(2)测试代码:
void TestErase()
{
TEST_HEADER;
Heap heap;
HeapInit(&heap,Greater);
HeapInsert(&heap,'c');
HeapInsert(&heap,'b');
HeapInsert(&heap,'a');
HeapInsert(&heap,'e');
HeapInsert(&heap,'f');
HeapInsert(&heap,'d');
HeapErase(&heap);
HeapPrintChar(&heap,"删除堆顶元素");
}
(3)运行结果:
6.给定一个数组创建一个堆
(1)实现代码:
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;
}
(2)测试代码:
void TestCreate()
{
TEST_HEADER;
Heap heap;
HeapInit(&heap,Greater);
HeapType array[] = {'d','e','c','a','b'};
HeapCreate(&heap,array,sizeof(array)/sizeof(array[0]));
HeapPrintChar(&heap,"创建一个堆");
}
(3)运行结果:
7.运用堆来给一个数组进行排序
思想:现将所给的数组建造成一个堆,然后再对堆进行堆顶元素的删除
如果需要升序排序就要建一个大堆:
因为大堆的堆顶元素为数组中最大的值,每次删除堆顶元素就是将当前数组中的最大值放在了数组的末尾,从而达到了升序排序
如果要进行降序排序,就要建一个小堆:
因为小堆的堆顶元素为数组中最小的值,每次删除堆顶元素就是将当前数组中的最小值放在了数组的末尾,从而达到了降序排序
(1)实现代码:
//如果要进行升序排序,那就要构建一个大堆
//如果要进行降序排序,那就要构建一个小堆
void HeapSort(HeapType array[],size_t size)
{
//把这个数组构建成一个堆
Heap heap;
HeapInit(&heap,Greater);
HeapCreate(&heap,array,size);
//循环的堆进行删除操作
while(heap.size > 0)
{
HeapErase(&heap);
}
//循环结束后,堆排序就完成了
memcpy(array,heap.data,size * sizeof(HeapType));
return;
}
(2)测试代码:
void TestSort()
{
TEST_HEADER;
HeapType array[] = {'d','e','c','a','b'};
HeapSort(array,sizeof(array)/sizeof(array[0]));
size_t i = 0;
for(;i < sizeof(array);++i)
{
printf("[%c] ",array[i]);
}
printf("\n");
}
(3)运行结果: