堆-神奇的优先队列

来源:互联网 发布:电子图书数据库 编辑:程序博客网 时间:2024/06/03 19:52

在C语言中如果除号“/”两边都是整数,那么商也只有整数部分(即自动向下取整)。

如果一颗完全二叉树有N个结点,那么这个完全二叉树的高度是logN,即最多有logN层结点。

完全二叉树的最典型应用就是-堆、


堆是什么?是一种特殊的完全二叉树。

所有父节点都比子节点要小,符合这样特点的完全二叉树叫最小堆。

所有父节点都比子节点大,符合这样特点的完全二叉树叫最大堆。


如何建立堆?
1)从空的堆开始,然后依次往堆中插入一个元素,直到所有的数都被插入(转移到堆中)为止。
因为插入第i个元素所用的时间是O(logi),所以插入所有元素的整体时间复杂度是O(NlogN),代码如下:
n=0;
for(i=1;i<=m;i++) {
n++;
h[n]=a[i];//或者scanf("%d",&h[i]);
siftup(n);//向上调整



2)把n个元素建立一个堆,首先将这n个节点以自顶向下,从左到右方式从1到n编码,这样可以将n个结点转变成
一颗完全二叉树,紧接着从最后一个非叶结点(N/2)开始到根结点(1),逐个扫描所有结点。根据需要将当前结点向下调整,直到以当前结点为根节点的子树符合堆的特性。代码如下:
for(i=n/2;i>=1;i--)
siftdown(i);//向下调整


//向下调整代码:void siftdown(int i) {//传入一个需要向下调整的结点编号i,这里传入1,即从堆的顶点开始向下调整 int t,flag=0;//flag用来标记是否需要继续向下调整 while(i*2<=n && flag==0) {//当i结点有儿子(其实至少有左儿子情况下),且需要继续调整的时候,执行循环 if(h[i]>h[2*i]) //判断它与左二子的关系,并用t记录较小结点的编号 t=i*2;elset=i;if(i*2+1<=n) {//如果它有右儿子,再对右儿子进行讨论 if(h[t]>h[i*2+1])//如果右儿子的值更小,更新较小的结点编号 t=i*2+1;}if(t!=i) {//如果发现最小的结点编号不是自己,说明子节点中有比父节点更小的 swap(t,i);//交换它们,注意swap函数需要自己来写 i=t;//更新i为刚才与它交换的儿子结点的编号,便于接下来继续向下调整 }elseflag=1;//否则说明当前的父节点已经比两个子节点都要小了,不需要再进行调整了 } return;} 
//向上调整代码:void siftup(int i) {//传入一个需要向上调整的结点编号i int flag=0;//用来标记是否需要继续向上调整 if(i==1)return;//如果是堆顶,就返回,不需要调整 while(i!=1 && flag==0){if(h[i]< h[i/2])//判断是否比父节点的小 swap(i,i/2);//交换它和它爸爸的位置 else flag=1;//表示已经不需要调整了,当前结点的值比父节点的值更大 i=i/2;//这句很重要,更新编号i为它父节点的编号,从而便于下一次继续向上调整 }return;} 

######################################################
堆排序(如果我们现在要进行从小到大排序,可以建立最小堆,然后每次删除顶部元素并将顶部元素输出或者放入一个新的数组中,直到堆为空为止。最后输出的或者存放在新数组中的数已经是排序好的了)


#include<stdio.h>int h[101];//用来存放堆的数组int n;//堆的大小,用来存放堆中元素的个数void swap(int x,int y) {//交换函数,用来交换堆中的两个元素的值 int t;t=h[x];h[x]=h[y];h[y]=t;return ;}  //向下调整代码:void siftdown(int i) {//传入一个需要向下调整的结点编号i,这里传入1,即从堆的顶点开始向下调整 int t,flag=0;//flag用来标记是否需要继续向下调整 while(i*2<=n && flag==0) {//当i结点有儿子(其实至少有左儿子情况下),且需要继续调整的时候,执行循环 if(h[i]>h[2*i]) //判断它与左二子的关系,并用t记录较小结点的编号 t=i*2;elset=i;if(i*2+1<=n) {//如果它有右儿子,再对右儿子进行讨论 if(h[t]>h[i*2+1])//如果右儿子的值更小,更新较小的结点编号 t=i*2+1;}if(t!=i) {//如果发现最小的结点编号不是自己,说明子节点中有比父节点更小的 swap(t,i);//交换它们,注意swap函数需要自己来写 i=t;//更新i为刚才与它交换的儿子结点的编号,便于接下来继续向下调整 }elseflag=1;//否则说明当前的父节点已经比两个子节点都要小了,不需要再进行调整了 } return;}  //建立堆的函数void creat() {int i;for(i=n/2;i>=1;i--)siftdown(i);}  //删除最大元素,返回最小值 int deletemax() { int t; t=h[1];//用一个临时变量记录堆顶点的值  h[1]=h[n];//将堆的最后一个点赋值到堆顶  n--;//堆的元素减1  siftdown(1);//向下调整  return t;//返回之前记录的堆的顶点的最小值 }int main() {int i,num;scanf("%d",&num);for(i=1;i<=num;i++)scanf("%d",&h[i]);n=num;creat();//建堆 for(i=1;i<=num;i++)//删除顶部元素,连续删除n次,其实也就是从大到小把数输出来 printf("%d ",deletemax());getchar();getchar();return 0; }


从小到大排序的时候不建立最小堆而建立最大堆,最大堆建立好后,最大的元素在h[1],因为我们的需求是从小到大排序,希望最大的放在最后。因此我们将h[1]和h[n]交换,此时h[n]就是数组中最大的元素。注意:交换后还需将h[1]向下调整以保持堆的特性。最大的元素已经归位后,需要将堆的大小减一即n--,并将交换后的新h[1]向下调整以保持堆的特性。如此反复,直至堆的大小变为1为止。此时数组h中的数就已经是排序好的了。

//建堆以及堆排序的完整代码#include<stdio.h>int h[10];//用来存放堆的数组int n;//堆的大小,用来存放堆中元素的个数void swap(int x,int y) {//交换函数,用来交换堆中的两个元素的值 int t;t=h[x];h[x]=h[y];h[y]=t;return ;}  //向下调整代码:void siftdown(int i) {//传入一个需要向下调整的结点编号i,这里传入1,即从堆的顶点开始向下调整 int t,flag=0;//flag用来标记是否需要继续向下调整 while(i*2<=n && flag==0) {//当i结点有儿子(其实至少有左儿子情况下),且需要继续调整的时候,执行循环 if(h[i]<h[2*i]) //判断它与左二子的关系,并用t记录较大结点的编号 t=i*2;elset=i;if(i*2+1<=n) {//如果它有右儿子,再对右儿子进行讨论 if(h[t]<h[i*2+1])//如果右儿子的值更小,更新较大的结点编号 t=i*2+1;}if(t!=i) {//如果发现最小的结点编号不是自己,说明子节点中有比父节点更小的 swap(t,i);//交换它们,注意swap函数需要自己来写 i=t;//更新i为刚才与它交换的儿子结点的编号,便于接下来继续向下调整 }elseflag=1;//否则说明当前的父节点已经比两个子节点都要小了,不需要再进行调整了 } return;}  //建立堆的函数void creat() {int i;for(i=n/2;i>=1;i--)siftdown(i);}  //堆排序 void heapsort() {while(n>1) {swap(1,n);n--;siftdown(1); }return;}int main() {int i,num;scanf("%d",&num);for(i=1;i<=num;i++)scanf("%d",&h[i]);n=num;creat();//建堆 heapsort();for(i=1;i<=num;i++)//删除顶部元素,连续删除n次,其实也就是从大到小把数输出来 printf("%d ",h[i]);getchar();getchar();return 0; }



0 0
原创粉丝点击