一 定义
堆是一棵完全二叉树,树中每个节点的值都不小于(不大于)其左右孩子节点的值。
如果父亲节点的值大于或等于孩子节点的值,这样的堆称为大顶堆。这时每个节点的值都是以它为根节点的子树的最大值;反之则称为小顶堆。
堆一般用于优先队列的实现,优先队列默认情况下是大顶堆。
二 基本操作
针对一组无序数组,建堆的过程如下:
(1)首先把数组元素按照树的层序从上往下,从左往右依次排放。
(2)然后开始把节点从上往下的调整。调整节点的时候总是将当前节点V与它的左右孩子比较(如果有的话),假如孩子中存在比节点V的值还大的,就将其中权值最大的那个孩子与节点V交换;交换完毕后继续让节点V(新的节点V)与孩子节点比较,直到节点V的孩子权值都比V小或是V不存在孩子节点。
向下调整
二叉树的序号都是从1开始的,而数组中的元素起始下标为0,为了对应,可以让数组的heap[0]存放一个无意义的数字例如-1,真正的堆元素是a[1]-a[n]
//对heap数组在[low,high]范围进行向下调整
//low是欲调整节点的数组下标,high为堆中最后一个元素的数组下标
//时间复杂度为O(logn)
void downadjust(int low,int high)
{
int i=low; //欲调整节点
int j=2*i; //左孩子
while(j<=hign) //存在孩子节点
{
//如果右孩子存在,且右孩子的值大于左孩子
if(j+1<=high&&heap[j+1]>heap[j])
j=j+1; //j存储右孩子下标
if(heap[j]>heap[i])
{
swap_element(heap[j],heap[i]); //交换值
i=j; //i变为欲调整节点
j=i*2;
}
else
break;
}
}
建堆
知道了向下调整的流程,那么建堆的过程就很容易了。
假设元素个数为n。有完全二叉树的性质可知,其中叶子节点个数为n/2(向上取整),因此数组下标为【1,n/2(向下取整)】范围内的节点都是非叶子节点,于是从n/2号开始倒着枚举节点,对每个节点i进行范围[i,n]的调整。
//时间复杂度为O(n)
void createheap()
{
for(int i=n/2;i>=1;i--)
{
downadjust(i,n);
}
}
删除堆顶元素
如果要删除堆顶元素,即堆中最大的元素,并让其保持堆的结构,可以把最后一个元素覆盖堆顶元素,然后对根节点进行调整即可。时间复杂度为O(logn)
void deleteheaptop()
{
heap[1]=heap[n--];
downadjust(1,n);
}
向上调整
若往堆中添加一个元素(记住前提是已经有了一个堆了),可以把要添加的元素放到数组后面,然后进行向上调整操作。向上调整总是把欲调整节点与其父节点比较,如果权值比父节点大,就交换其与父节点。知道到达堆顶或是父亲节点的权值较大为止。时间复杂度为O(logn)
//对heap数组在[low,hign]范围进行向上调整
//low一般为1,hign表示欲调整节点的下标
void upadjust(int low,int high)
{
int i=high; //i为欲调整节点
int j=i/2; //j为父亲节点
while(j>=low)
{
if(heap[j]<heap[i])
{
swap_element(heap[j],heap[i]);
i=j; //i为欲调整节点
j=i/2; //j为父亲
}
else
break;
}
}
向堆中添加元素
void insert(int x)
{
heap[++n]=x;
upadjust(1,n);
}
三 堆排序
堆排序指使用堆结构对一个序列进行排序。这里讨论递增排序。
考虑一个堆来说,堆顶元素是最大的,因此在建堆完成之后,取出堆顶元素,让堆的最后一个元素替换至堆顶,再进行一次针对堆顶元素的向下调整,如此重复,直到堆中只有1个元素为止。
具体实现时,为了节省空间,可以倒着遍历数组,假设,当前访问到i位,将堆顶元素与该位元素交换,接着再[1,i-1]范围内对堆顶元素进行一次向下调整即可。
void heapsort()
{
createheap();
for(int i=n;i>1;i--)
{
swap_element(heap[i],heap[1]);
downadjust(1,i-1);
}
}
四 实例
对数组a[9]={-1,6,8,3,1,5,4,7,2}中的后8个元素排序
#include <iostream>
using namespace std;
void swap_element(int *a,int *b)
{
int tmp=*a;
*a=*b;
*b=tmp;
}
//对heap数组在[low,high]范围进行向下调整
//low是欲调整节点的数组下标,high为堆中最后一个元素的数组下标
void downadjust(int heap[],int low,int high)
{
int i=low; //欲调整节点
int j=2*i; //左孩子
while(j<=high) //存在孩子节点
{
//如果右孩子存在,且右孩子的值大于左孩子
if(j+1<=high&&heap[j+1]>heap[j])
j=j+1; //j存储右孩子下标
if(heap[j]>heap[i])
{
swap_element(&heap[j],&heap[i]); //交换值
i=j; //i变为欲调整节点
j=i*2;
}
else
break;
}
}
void createheap(int heap[],int n)
{
for(int i=n/2;i>=1;i--)
downadjust(heap,i,n);
}
//对heap数组在[low,hign]范围进行向上调整
//low一般为1,hign表示欲调整节点的下标
void upadjust(int heap[],int low,int high)
{
int i=high; //i为欲调整节点
int j=i/2; //j为父亲节点
while(j>=low)
{
if(heap[j]<heap[i])
{
swap_element(&heap[j],&heap[i]);
i=j; //i为欲调整节点
j=i/2; //j为父亲
}
else
break;
}
}
void tranverse_heap(int heap[],int n)
{
for(int i=1;i<=n;i++)
cout<<heap[i]<<" ";
}
int main()
{
int a[9]={-1,6,8,3,1,5,4,7,2};
createheap(a,8);
for(int i=8;i>1;i--)
{
swap_element(&a[i],&a[1]);
downadjust(a,1,i-1);
}
tranverse_heap(a,8);
return 0;
}