·堆的定义
如果有n个元素的序列{k0,k1,k2,…,kn-1,kn}当且仅当满足关系ki≤k2i且ki≤k2i+1 或(ki≥k2i且ki≥k2i+1) i=1,2,… ,n/2
将满足ki≤k2i且ki≤k2i+1关系的堆称为小堆,将满足ki≥k2i且ki≥k2i+1关系的堆称为大堆。
·堆的性质
1.
如果将此序列以一维数组的储存,将一维数组看成是一个完全二叉树,则由堆的定义可知,完全二叉树中所有非叶子结点的值均不大于(不小于)其左、右孩子结点的值。
2.
若一个序列是堆,则堆顶元素(完全二叉树的根)比是序列中n个元素的最小值(最大值)。小堆的堆顶元素为最小值,大堆的堆顶元素为最大值。
![](https://i-blog.csdnimg.cn/blog_migrate/07b3408d07a17ed8a93936137af378cb.png)
![](https://i-blog.csdnimg.cn/blog_migrate/237f2db98bd2728528a0c2aeba8fa71c.png)
小堆 大堆
3.将堆储存在以为数组中,则在堆中下标为i的结点有以下关系:
如果i=0,i所指结点为根节点,没有双亲结点,否则i所指结点的双亲结点下标为(i-1)/2。
如果2i+1≤ n-1,则i所指向的结点的左孩子的下标为2i+1,否则i所指向的结点没有左孩子。
如果2i+2
≤
n-1
,则i所指向的结点的右孩子的下标为2i+2,否贼i所指向的结点没有右孩子。
·堆的创建
首先给出堆的结构体
typedef int DataType;//将int类型重命名为DataType,DataType表示堆中所存储的数据类型
typedef int(*Compare)(DataType left, DataType right);//函数指针,指向Less和Greater函数,在创建堆时,指明创建的是小堆还是大堆
typedef struct Heap
{
DataType* _array;//数组指针,指向一个一维数组,数组中存储的数据类型是DataType
int _capacity;//堆的容量大小
int _size;//堆中的元素个数
Compare _com;//函数指针,指明是小堆还是大堆
}Heap;
Less函数和Greater函数
//小堆
int Less(DataType left, DataType right)
{
return left < right;
}
//大堆
int Greater(DataType left, DataType right)
{
return left > right;
}
然后对堆进行初始化,此时堆中没有任何元素
void InitHeap(Heap* hp,Compare com)
{
hp->_array = NULL;
hp->_capacity = 0;
hp->_size = 0;
hp->_com = com;
}
此时因为堆中没有任何元素,所以将数组指针置空,堆的容量和元素均为零,但小堆和大堆已经根据参数com改变,当com是Less时,创建的是小堆,com是Greater时创建的是大堆。
接下来就是根据给出的数组中的元素创建一个堆,因为堆本身可以看成是储存在一维数组中的完全二叉树,则我们可以将给出的数组也看成一个完全二叉树,只是这个完全二叉树不满足堆的条件。那么将这个完全二叉树调整为堆的方法就是从最好一个非叶子结点开始,逐步向根结点调整,一直到根结点为止,将每个结点及其子树都调整到满足堆的性质。由完全二叉树的性质可知最后一个非叶子结点的下标为(n-2)/2。
void CreateHeap(Heap* hp, DataType* array, int size)//hp为结构体指针,指向堆的结构体,array为给出的数组,size为数组中的元素个数
{
int i = 0;
int root = (size - 2) >> 1;//从最后一个非叶子结点开始,将root复制成最后一个非叶子结点的下标值
if (NULL == hp)
{
return;
}
hp->_array = (DataType*)malloc(sizeof(DataType)*size);//使用malloc函数在堆上开辟出一个相当于数组大小的空间
if (NULL == hp->_array)//检测malloc开辟空间是否失败
{
assert(0);//如果失败则终止程序,并指出错误所在
return;
}
hp->_capacity = size;//因为开辟的空间大小与给出的数组大小一致,所以堆的容量要数组中元素个数相同
for (i = 0; i < size; i++)//利用for循环将数组中的元素赋到堆中
{
hp->_array[i] = array[i];
}
hp->_size = size;//此时堆中元素个数和数组元素个数相同
for (; root >= 0; root--)//从最后一个非叶子结点开始,root逐渐向根结点靠近,直到根结点为止
{
_AdjustDown(hp, root);//从root位置开始向下调整
}
}
当上述函数运行完成后,一个堆就创建完毕了。下面是创建一个堆最重要的函数_AdjustDown(向下调整)的具体代码:
void _AdjustDown(Heap* hp, int parent)
{
int child = parent * 2 + 1;//找到双亲结点的左孩子
int size = hp->_size;
while (child < size)//当左孩子存在时进入循环
{
if ((child + 1)<size && hp->_com(hp->_array[child+1], hp->_array[child]))
//判断右孩子是否存在,判断左右孩子是否满足函数指针所指函数的关系
{
child++;//如果满足关系则让child指向右孩子
}
if (hp->_com(hp->_array[child] , hp->_array[parent]))
//判断child指向的孩子结点是否与双亲结点满足函数指针所指向函数的关系
{
Swap(&hp->_array[parent], &hp->_array[child]);//满足关系则交换孩子结点和双亲结点的值
}
else
{
return;
}
parent = child;//让双亲结点指向之前的孩子结点,继续选好,调整双亲结点的左右子树
child = parent * 2 + 1;//重新计算孩子结点,继续循环
}
}
Swap
函数
void Swap(DataType* pLeft, DataType* pRight)
{
DataType temp = 0;
assert(pLeft);
assert(pRight);
temp = *pLeft;
*pLeft = *pRight;
*pRight = temp;
}
·堆的基本操作
1. 在堆中插入新的元素,如果堆未满,则直接在堆后插入新的元素,插入新元素之后新组成的完全二叉树可能不满足堆的性质,所以要对堆进行重新调整。如果堆已满,则先为堆增容,再插入新元素。
void InsertHeap(Heap* hp, DataType data)
{
int child = 0;
assert(hp);
_CheckCapacity(hp);//增容函数,检查是否需要对堆进行增容,如果需要增容则进行增容
hp->_array[hp->_size++] = data;//将新元素插入堆的最后
child = hp->_size - 1;//将child指向新插入的元素
_AdjustUp(hp, child);//从新插入元素的位置向上调整
}
因为除插入的新元素之外的其他元素满足堆的性质的所以只需要对新插入的元素进行调整即可,当新插入元素满足堆的性质时,则整个新的完全二叉树满足堆的性质。称这种调整为向上调整。由完全二叉树的性质可知下标为i的结点的双亲结点的下标为(i-1)/2。
void _AdjustUp(Heap* hp, int child)
{
int parent = (child - 1) >> 1;//求出新插入结点的双亲结点
while (child)
{
if (hp->_com(hp->_array[child], hp->_array[parent]))//判断孩子结点和双亲结点是否满足函数指针指向函数的关系
{
Swap(&hp->_array[parent], &hp->_array[child]);//如果满足则交换孩子结点和双亲结点的值
child = parent;//因为双亲结点与孩子结点已经交换,将child指向之前的双亲结点,child指向的依然是新结点
parent = (child - 1) >> 1;//重新计算交换后的新插入结点的双亲结点
}
else
{
return;
}
}
}
_CheckCapacity
函数
void _CheckCapacity(Heap* hp)
{
assert(hp);
if (hp->_size == hp->_capacity)//如果堆中元素个数与堆的容量相等则说明堆已满,需要增容
{
hp->_array = (DataType*)realloc(hp->_array, sizeof(DataType)*(hp->_capacity * 2 + 1));//利用realloc函数将容量增加到原来的2倍再加1
hp->_capacity = hp->_capacity * 2 + 1;//更新堆的容量
}
if (NULL == hp->_array)
{
assert(0);
return;
}
}
2
.
在堆中删除一个元素,
堆的删除每次删除的都是堆顶的元素,但是在数组中我们无法直接删除下标为0的元素,这时我们需要将堆中的最后一个元素的值赋给堆顶元素,再将最后一个元素删除即可。因为将堆中最后一个元素的值赋给堆顶元素会破坏堆的结构,所以我们需要对堆进行调整,因为不满足结构的只有堆顶元素一个,所以只需要对堆顶元素进行一次向下调整即可。
void DeleteHeap(Heap* hp)
{
if (EmptyHeap(hp))//先判断堆是否为空,如果为空则不用进行删除
{
return;
}
hp->_array[0] = hp->_array[hp->_size - 1];//将堆中最后一个元素的值赋给堆顶元素
hp->_size--;//删除堆中最后一个元素
_AdjustDown(hp, 0);//从根结点位置(堆顶元素)向下调整
}
判空函数EmptyHeap
int EmptyHeap(Heap* hp)
{
if (!hp->_size)
{
return 1;
}
return 0;
}
以上所有代码均在VS2013环境下调试运行无误。堆是计算机存储的一个重要结构。利用堆我们可以进行堆排序,创建优先级队列以及解决Top K问题等