文章目录
一、堆的概念及性质结构
堆是一种非线性数据结构,本质上堆其实是一棵完全二叉树,通常存储在一维数组当中。将根节点最大的堆叫做最大堆或大根堆,根节点最小的堆叫做最小堆或小根堆。
堆具有的性质:
1.堆总是一棵完全二叉树;
2.堆中某个节点的值总是不大于或不小于其父亲节点的值;
对第二点的理解是:当堆为大根堆时,堆的根节点的值一定大于或等于其两个孩子节点的值;当堆为小根堆时,堆的根节点的值一定小于或等于其两个孩子节点的值
二、堆的实现
堆的存储结构是顺序表,堆的实现紧紧围绕着两个核心算法,即向上调整算法与向下调整算法。下面将分别从向上调整算法与向下调整算法展开,借助对堆的功能实现示例来分析这两种算法。首先展示堆的结构定义:
//定义Heap结构体
typedef struct Heap
{
int* a;
int size;//顺序表的长度
int capacity;//顺序表的容量
}HP;
1.向上调整算法
向上调整顾名思义即从下往上调整,从叶子节点开始向根节点的方向进行调整。
1)向上调整算法代码实现
基本的思想: (默认调整小堆)AdjustUp函数中形参child用来接收传入的孩子节点的下标,定义 parent变量来表示当前孩子的父亲节点的下标(由完全二叉树在数组中存储的性质得,parent=(child-1)/2 ,便可得到父亲节点的下标),通过比较 a[parent] 的值与 a[child] 的值,如果调整的是大堆则当
a[parent]>a[child] 时交换两值,如果调整的是小堆则当 a[parent] <a[child] 时交换两值。利用循环不断重复这一步骤,如果符合交换条件的就更新当前的child为parent,再将parent的下标更新一下,直到child走到根节点时停止,或者是当交换条件不符合时停止循环。
代码示例如下:
//交换函数
void Swap(int* a, int* b)
{
int tmp = *a;
*a = *b;
*b = tmp;
}
//a是存储堆的数组,child接收的是传入的孩子节点的下标
void AdjustUp(int* a, int child)
{
//完全二叉树的性质
int parent = (child - 1) / 2;
while (child > 0)
{
//调整小堆
if (a[child] < a[parent])
{
//交换两值
Swap(&a[child],&a[parent]);
//继续向上调整
child = parent;
parent = (child - 1) / 2;
}
else
{
break;
}
}
}
2)堆的插入
堆的插入运用到的就是向上调整算法。因为堆的插入是从堆最后一个元素后插入,插入元素容易,但同时也要确保堆的结构不被破坏,因此要在插入以后再向上调整以保持堆的结构。
基本思想: 由于堆的存储结构是动态的顺序表,所以当进行插入操作时第一步是需要判断顺序表是否有容量能够插入,如果容量满了,则需要realloc出一个新的空间,进行扩容操作。进行插入操作只需要在顺序表尾部插入元素并且将元素个数size加一即可,需要注意的是,插入的数据可能不符合堆的结构,所以我们需要采用向上调整算法将插入后的结构重新调整为堆。
//以小堆为示例
void HeapPush(HP* php, int x)