目录
一、堆的性质:
堆就是以二叉树的顺序存储方式来存储元素,一般是数组数据看做一棵完全二叉树,也就是说堆总是一棵完全二叉树。
而堆又分为大堆和小堆:
-
大堆:父节点 >= 其任意子节点
-
小堆:父节点 <= 其任意子节点
概念图如下:
二、简易实现堆及要点解析:
头文件定义及主要实现的功能:
typedef int HPDateType; // 设置堆中元素的类型,方便进行更改
typedef struct Heap
{
HPDateType* a;
int Size; // 堆中的元素个数
int Capacity; // 堆容量大小
}HP;
// 堆的初始化与销毁
void HeapInit(HP* php);
void HeapDesTroy(HP* php);
// 堆的插入与删除
void HeapPush(HP* php, HPDateType x);
void HeapPop(HP* php);
// 返回堆顶元素、堆中元素个数、以及判空
HPDateType HeapTop(HP* php);
size_t HeapSize(HP* php);
bool HeapEmpty(HP* php);
// 向上调整与向下调整以及交换
void AdjustUp(HPDateType* a, int child);
void AdjustDown(HPDateType* a, int parent);
void Swap(HPDateType *p1, HPDateType* p2);
1、堆的初始化与销毁:
void HeapInit(HP* php)
{
assert(php);
php->a = NULL;
php->Capacity = php->Size = 0;
}
void HeapDesTroy(HP* php)
{
assert(php);
free(php->a);
php->a = NULL;
php->Capacity = php->Size = 0;
}
2、堆的插入与删除(向上与向下调整):
堆的插入:
在进行堆的插入时我们可以在堆尾进行插入, 然后再将该元素不断的向上进行调整,也就是与它的父节点进行比较,如果(以小堆为例)子节点小于父节点,则进行交换,不断重复该过程直到符合堆的结构为止。(大堆更改交换条件即可)
堆的删除:
在进行堆的删除时实际上是删除堆顶的元素,如果直接删除堆顶元素再将整个堆进行重构的话那效率将非常低下,再不然也会打乱堆的结构,所以我们可以先将堆顶的元素与堆尾的元素进行交换,然后删除堆尾的元素,再将堆顶元素进行向下调整,也就是与它的子节点进行比较,如果(以小堆为例)父节点大于子节点,则进行交换,不断重复该过程直到符合堆的结构为止。(大堆更改交换条件即可)
void Swap(HPDateType* p1, HPDateType* p2)
{
HPDateType tmp = *p1;
*p1 = *p2;
*p2 = tmp;
}
// 向上调整
void AdjustUp(HPDateType* a, int child)// 以小堆为例
{
int parent = (child - 1) / 2; // 父节点 = (其子节点 -1) / 2
while (child > 0)
{
if (a[child] < a[parent])// 若子节点小于父节点则交换它们的值
{
Swap(&a[child], &a[parent]);
child = parent;// 更新父子节点
parent = (parent - 1) / 2;
}
else
{
break;
}
}
}
// 堆的插入
void HeapPush(HP* php, HPDateType x)
{
assert(php);
if (php->Size == php->Capacity)
{
int NewCapacity = php->Capacity == 0 ? 4 : php->Capacity * 2;
HPDateType* tmp = (HPDateType*)realloc(php->a, NewCapacity * sizeof(HPDateType));
if (tmp == NULL)
{
perror("realloc error");
exit(-1);
}
php->a = tmp;
php->Capacity = NewCapacity;
}
php->a[php->Size] = x;
php->Size++;
AdjustUp(php->a, php->Size - 1);
}
// 向下调整
void AdjustDown(HPDateType* a, int size, int parent)// 以小堆为例
{
int child = parent / 2 + 1; // 子节点 = 其父节点 * 2 + 1
while (child < size)
{
if (child + 1 < size && a[child + 1] < a[child])// 找两个子节点中小的节点
{
child++;
}
if (a[child] < a[parent])// 若子节点小于父节点则交换它们的值
{
Swap(&a[child], &a[parent]);
parent = child;// 更新父子节点
child = child * 2 + 1;
}
else
{
break;
}
}
}
// 堆的删除
void HeapPop(HP* php)
{
assert(php);
assert(php->Size > 0);
// 交换首尾并删除尾后向下调整
Swap(&php->a[0], &php->a[php->Size - 1]);
php->Size--;
AdjustDown(php->a, php->Size, 0);
}
3、返回堆顶元素、堆中元素个数、以及判空:
HPDateType HeapTop(HP* php)
{
assert(php);
assert(php->Size > 0);
return php->a[0];
}
size_t HeapSize(HP* php)
{
assert(php);
return php->Size;
}
bool HeapEmpty(HP* php)
{
assert(php);
return php->Size == 0;
}
4、测试用例:
给定一个无序数组,以小堆存储结构呈现
void test1()
{
int a[] = { 3,6,8,1,3,2,7,9,5 };
HP hp;
HeapInit(&hp);
for (int i = 0; i < sizeof(a) / sizeof(a[0]); i++)
{
HeapPush(&hp, a[i]);
}
while (!HeapEmpty(&hp))
{
printf("%d ", HeapTop(&hp));
HeapPop(&hp);
}
HeapDesTroy(&hp);
}