关于对的算法思想,随处都可以查到,下面总结下堆的算法。
堆排序和直接选择排序都是选择排序,即从未序区间中选择最大或者最小的元素追加到已序区间的尾部,直到剩下一个元素。
直接选择排序中,为了从R[1..n]中选出关键字最小的记录,必须进行n-1次比较,然后在R[2..n]中选出关键字最小的记录,又需要做n-2次比较。事实上,后面的n-2次比较中,有许多比较可能在前面的n-1次比较中已经做过,但由于前一趟排序时未保留这些比较结果,所以后一趟排序时又重复执行了这些比较操作。
堆排序可通过树形结构保存部分比较结果,可减少比较次数。
堆排序算法的解析:
1、建初始堆(以大顶堆为例),这一步骤把初始序列建成了一个满足大顶堆性质的序列,且每棵子树都满足。这个时候堆顶是本序列中最大的元素,因此将最后一个元素和堆顶元素调换,把最大值放到最终的位置上。建好了初始堆,就保留了排序时候的比较结果,后面的调整都可以再此基础上进行,加快排序效率。
2、由于每次讲堆顶元素和最后一个对调,破坏了堆的性质,因此要从新向下调整,建立大顶堆(这里在初始堆的基础上,只要将堆顶元素调到合适位置即可)。
3、调整完了之后,又将堆顶元素与未序区间的最后一个元素对调。
4、重复2,3直到堆中剩余一个元素。
下面是对算法的代码:
1 void HeapSort(ElemType A[],int len) 2 { 3 BuilMaxHeap(A,len);//建立初始堆 4 for(int i = len;i>1;i--) 5 { 6 A[0]=A[1];A[1]=A[i];A[i]=A[0];//堆顶和未序区间尾元素对调。 7 AdjustDown(A,1,i-1);//调整未序区间,继续下一次对调。从1 —— i-1是未序的。 8 } 9 }
建立大顶堆的算法如下:
1 void BuildMaxHeap(ElemType A[],int len) 2 { 3 for(int i=len/2;i>0;i--)//从最后一个父节点开始向前建堆 4 AdjustDown(A,i,len);//向下调整 5 }
向下调整算法如下:
1 void AdjustDown(Elemtype A[],int k,int len) 2 { 3 A[0]=A[k];//保存子堆的父节点 4 for(int i=2*k;i<=len;i*=2) 5 { 6 if(i<len&&A[i]<A[i+1])//寻找较大的孩子 7 i++; 8 if(A[0]>A[i])//如果孩子节点没有比父节点大,跳出循环 9 break; 10 else 11 { 12 A[k]=A[i];//孩子上调 13 k=i;//孩子当下一个父亲 14 } 15 } 16 A[k]=A[0];//把子堆顶元素放在最终的位置 17 }
注意:堆的最重要的操作就是调整函数,即向下调整AjustDown()月AdjustUp(),其余操作都建立在这两个操作之上。
删除堆顶元素时,需要堆顶元素和堆的最后一个元素对换,然后将堆的长度减一。
对堆查入操作时,先将堆的新节点放在堆的末尾,在对这个新节点指向向上调整AdjustUp()操作。
下面是向上调整的堆算法:
1 void AdjustUp(Elentype A[],int k)//k为向上调整的节点,也为新堆的元素个数 2 { 3 A[0]=A[k]; 4 int i = k/2;//i是k的双亲 5 while(i>0&&A[i]<A[0])//如果双亲存在,且双亲小于孩子节点,执行循环体中的语句 6 { 7 A[k]=A[i]; 8 k=i; 9 i=k/2;//寻找双亲的双亲,继续向上比较 10 }//如果不满足while中的条件跳出,k的位置就是接下来新节点的位置。 11 A[k]=A[0];//将新节点复制到最终位置上 12 }
堆的应用:
堆可以用来解决TOPK问题;优先级队列的底层也是用堆来实现。