一、堆的概念和性质
概念:
关键码的集合 K = {k0 , k1 , k2 , … , kn-1} ,把它的所有元素 按完全二叉树的顺序存储方式存储 在一 个一维数组中 ,并满足: Ki <= K2i+1 且 Ki<= K2i+2 (Ki >= K2i+1 且 Ki >=K2i+2) i = 0 , 1, 2… ,则 称为小堆 ( 或大堆) (即双亲比孩子的数值小(大)——小(大)堆)。将根节点最大的堆叫做最大堆或大根堆,根节点最小的堆叫做最小堆或小根堆。
性质:
- 堆中某个节点的值总是不大于或不小于其父节点的值。
- 堆总是一颗完全二叉树。
二、堆的实现:
堆的存储在一维数组中,其物理结构和顺序表差不多,但其逻辑结构是完全二叉树。
以下头文件中声明了堆的一些基本操作:
#pragma once
#include<stdio.h>
#include<stdlib.h>
#include<assert.h>
#include<stdbool.h>
typedef int HPDataType;
//小堆
typedef struct Heap
{
HPDataType* a;
int size;
int capacity;
}Heap;
// 堆的构建
void HeapCreate(Heap* hp);
// 堆的销毁
void HeapDestory(Heap* hp);
// 堆的插入
void HeapPush(Heap* hp, HPDataType x);
// 堆的删除
void HeapPop(Heap* hp);
// 取堆顶的数据
HPDataType HeapTop(Heap* hp);
// 堆的数据个数
size_t HeapSize(Heap* hp);
// 堆的判空
bool HeapEmpty(Heap* hp);
//向下调整
void AdjustDown(HPDataType* a, int size, int parent);
//向上调整
void AdjustUp(HPDataType* a, int child);
三、堆功能实现
1、堆的初识化和销毁
//堆的创建
void HeapCreate(Heap* hp)
{
assert(hp);
hp->a = NULL;
hp->size = 0;
hp->capacity = 0;
}
// 堆的销毁
void HeapDestory(Heap* hp)
{
assert(hp);
free(hp->a);
hp->a = NULL;
hp->capacity = 0;
hp->size = 0;
}
2、堆的插入
堆的插入虽然是在顺序表中实现,但是在插入的时候要时刻保持堆的特性。
需要我们在每插入一个数据后都要进行调整:向上调整
思想:
1、将刚插入的结点和他的父亲结点进行比较
2、若小于父节点则交换
3、交换后在和其新位置的父结点比较,重复步骤1和步骤2
2.1)堆的插入代码实现:
// 堆的插入----建堆
void HeapPush(Heap* php, HPDataType x)
{
assert(php);
//检查并扩容
if (php->size == php->capacity)
{
int NewCapacity = php->capacity == 0 ? 4 : php->capacity * 2;
HPDataType* temp = (HPDataType*)realloc(php->a, sizeof(HPDataType) * NewCapacity);
if (!temp)
{
printf("realloc fail\n");
exit(-1);
}
//将新空间赋给a
php->a = temp;
//capacity的更新
php->capacity = NewCapacity;
}
php->a[php->size] = x;
//插入后会影响祖先 ,为保证堆的特性,所以要向上调整
AdjustUp(php->a, php->size);
php->size++;
}
2.2)向上调整代码实现:
//交换函数----交换值
void Swap(HPDataType* p1, HPDataType* p2)
{
HPDataType tmp = *p1;
*p1 = *p2;
*p2 = tmp;
}
//向上调整(O(logn))---实现小堆
void AdjustUp(HPDataType* a, int child)
{
int parent = (child - 1) / 2;
while (child > 0)
{
//需要满足父亲结点要小于子结点
if (a[child] < a[parent])
{
Swap(&a[child], &a[parent]);
//更新孩子结点和父节点
child = parent;
parent = (parent - 1) / 2;
}
else
{
break;
}
}
}
3、堆的删除
堆的删除: 规定删除的是堆顶的元素(根结点)
将堆顶的元素删除后 要保证堆的特性 不能一味的将后续的结点向前覆盖,这样会打乱其中的父子关系,应该使用一种类似于向上调整的 方法,进行父子间的调整:向下调整
步骤:
1、将根部结点和堆的最后一个结点进行交换
2、将堆的size--即可完成对堆根结点的删除
3、将换到根结点的元素向下调整,以保持堆的特性
3.1)删除堆的代码实现
// 堆的删除----规定删除堆顶的结点(根结点)
void HeapPop(Heap* hp)
{
assert(hp);
assert(hp->size > 0);
Swap(&hp->a[0], &hp->a[hp->size - 1]);//头尾交换
hp->size--;//删除交换后的最小值
AdjustDown(hp->a,hp->size,0);//将换上去的头部值向下调整----保证成小堆
}
3.2)向下调整的代码实现
//向下调整(O(logn))
void AdjustDown(HPDataType* a, int size, int parent)
{
//假设法
int child = parent * 2 + 1;//假设左子小
while (child < size)
{
if (child + 1 < size && a[child] > a[child + 1])//假设错误
{
child++;//更新为右子
}
if (a[child] < a[parent])
{
Swap(&a[child], &a[parent]);
//更新父节点和子节点
parent = child;
child = parent * 2 + 1;
}
else
{
break;
}
}
}
4、取堆顶元素
// 取堆顶的数据
HPDataType HeapTop(Heap* hp)
{
assert(hp);
assert(hp->size > 0);
return hp->a[0];
}
5、堆的数据个数
// 堆的数据个数
size_t HeapSize(Heap* hp)
{
assert(hp);
return hp->size;
}
6、堆的判空
// 堆的判空
bool HeapEmpty(Heap* hp)
{
assert(hp);
return hp->size == 0;
}