堆的定义,它是一个完全二叉树,存储结构为数组。给出一个数组之后,可以认为这个初始堆是按照结点序号从小到大存放的。
满足对的定义要求,所有父结点的键值都要不小于(或者不大于)子结点的键值,称为大顶堆(小顶堆)。
对堆的操作有插入、删除,这些操作都要对堆进行一定的调整以保证堆的定义。
堆的插入直接放在堆最后一个位置,同时从此位置开始向上调整,向上调整的代码:
void MinHeapFixup(int a[],int i)
{
int j=(i-1)/2;
while(j>=0)//结束条件是还存在父结点,或子结点大于父结点
{
if(a[j]>a[i])//子结点跟父结点比较
{
swap(a[j],a[i]);//子结点较小则交换
i=j;
j=(i-1)/2;
}
else break;
}
}
i为结点序号(从0开始的)。
堆的删除都是从堆顶删除的,再对剩下的数进行调整,使依然为一个小顶堆。实际操作的时候将堆顶元素与最后一个元素交换,然后对新换的堆顶进行向下调整。
在小顶堆中对第i个位置的数进行向下调整的代码:
void MinHeapFixdown(int a[],int j,int n)
{
//堆中删除第i位置处的元素,需要在以这个点为根节点树中向下调整
int temp=a[j];//记录a[j]的值
// int j=i;
while(2*j+1<=n)//有子节点
{
if(2*j+2>n)//仅有左孩子没有右孩子
{
if(a[2*j+1]<a[j])
{
swap(a[2*j+1],a[j]);
j=2*j+1;//指向左孩子
}
else break;
}
else
if(a[2*j+1]<=a[2*j+2])//如果有两个孩子且左孩子小于右孩子
{
if(a[2*j+1]<a[j])
{
swap(a[2*j+1],a[j]);
j=2*j+1;
}
else break;
}
else//有两个孩子,右孩子小于左孩子
{
if(a[2*j+2]<a[j])
{
swap(a[2*j+2],a[j]);
j=2*j+2;
}
else break;
}
}
a[j]=temp;
}
这个里面只用了参数里给出的一个变量j和一个temp用于存a[j]的值,后面的判断语句比较多,代码较长。
下面这个多用了一个变量,使判断句少了很多:
void MinHeapFixdown(int a[],int i,int n)
{
int temp=a[i];
int j=2*i+1;//比上一个多用了一个变量,后面的比较语句减少
while(j<n)
{
if(j+1<n&&a[j+1]<a[j])//well down!
j=j+1;
if(a[j]>a[i])//if和else的顺序,good
break;
swap(a[j],a[i]);
i=j;
j=2*i+1;
}
a[i]=temp;
}
怎么通过堆的基本操作构建出一个小顶堆呢?因为小顶堆只要求父结点的键值小于子结点的键值,所以需要对所有的父结点进行一次向下调整。而又因为堆可以看做是一个完全二叉树,由完全二叉树的定义可知,其中度为2和1的结点为父结点,且度为0的点(叶子结点)个数是度为2的结点数+1,即n0=n2+1,而n=n0+n1+n2;所以可以得出最后一个度不为0的结点序号为n/2,因此调整只需从n/2处开始。
构建小顶堆的代码:
void MinHeapDelete(int a[],int n)//删除a[0]处的值,放在最后的位置
{
swap(a[0],a[n]);
MinHeapFixdown(a,0,n);
}
void MakeMinHeap(int a[],int n)//构造小顶堆
{
for(int i=(n-1)/2;i>=0;i--)
MinHeapFixdown(a,i,n);
}
堆排序就是根据大小顶堆得特性来按序输出的,首先根据输出构造出一个这里是小顶堆,然后对堆顶元素进行删除,放在最后,删完之后数组里的数就已经是按序排列的了,由代码可以看出,小顶堆对应的输出是按降序排列的:
int main()
{
int a[7];
for(int i=0;i<7;i++)
scanf("%d",&a[i]);
MinHeapFixup(a,6);
/*MakeMinHeap(a,7);//构建小顶堆
for(i=6;i>0;i--)
MinHeapDelete(a,i);//依次删除堆顶元素放在最后,注意数组长度i是递减的*/
for(i=0;i<7;i++)
printf("%d",a[i]);
return 0;
}
大根堆排序结束后,按序读数组应该是一个升序序列,小根堆对应降序序列。
在解决TOP K(max)问题时,可以通过大根堆实现,每次调整大根堆得到一个当前最大元素,进行K次得到TOP K,空间复杂度为0(N),时间复杂度为0(K*logN)。但这样在N较大,或者数据为流式数据时,总是要先将数据缓存完才能开始计算,而如果使用小根堆,可以只维护一个K大小的堆,作为当前的TOP K,然后依次读后面的数据,与当前的最小值(堆顶)进行比较,如果大于堆顶,则替代堆顶,并调整小根堆,得到新的最小值,再与后面的数据相比较,直到结束。空间复杂度0(K),时间复杂度0((N-K)*logK)。
小根堆实现TOP K(max):
int heap_TOPK()
{
//小顶堆找 TOP K大
int a[max+1],i=0;
int K=3;
for(i=1;i<=max;i++)
scanf("%d",&a[i]);
MinHeapCreate(a,K);//堆大小为K
for(i=4;i<=max;i++)
{
if(a[i]>a[1])
a[1]=a[i];
MinHeapFixDown(a,1,K);//依次比较和调整堆
}
for(i=1;i<=K;i++)
printf("%d ",a[i]);
system("pause");
return 0;
}