【数据结构】排序算法:希尔、归并、快速、堆排序

作者和出处:http://blog.csdn.net/xiaowei_cqu,这女孩写得文章不错,大家可以去看一下,受益匪浅。

排序算法


排序是非常常用,非常基本的算法。排序的方法有很多,比如插入排序、选择排序、希尔排序、归并排序、快速排序、堆排序。
本次试验重点实现:希尔排序、归并排序、快速排序、堆排序

插入排序

简单说就是每次选未排序的队列中最小的条目插入到已排序队列的最后:

选择排序
选择排序和插入有点像,是每次从拿未排序中的第一个条目插入到已排序中应该插入的位置:
希尔排序
希尔排序是插入排序的一种,是针对直接插入排序算法的改进。
希尔排序的基本思想是:先取一个小于count的增量increment,把表中Record分为increment组,所有距离为increment的Record为同一组,现在各组中进行直接插入排序(insert_sort),然后减小increment重复分组和排序,知道increment=1,即所有Record放在同一组中进行直接插入排序为止。

【相关实验】

首先从类表List中派生子表Sortable_list。为了方便定义,我们可以重载这样的构造函数
  1. template<classRecord>
  2. Sortable_list<Record>::Sortable_list(constRecordA[],intsize)
  3. {
  4. for(inti=0;i<size;i++)
  5. insert(i,A[i]);
  6. }
1.编写函数shell_sort。函数中我们从首先定义increment=0,观察题目要求,可以得到在循环中有这样的关系increment=increment/2;使用循环将每次分组后的每组排列,排列我们再增加函数sort_interval();在每组的排序中使用的直接插入排序,所以可以这样实现sort_interval:每次定义一个临时的Sortable_list对象tmp记录每次分组的小组,对tmp使用insertion_sort,进而我们编写函数insertion_sort();
2.实现表的排序一个重要的步骤是将Record换到相应位置,即交换,所以编写函数swap;
3.为了输出每趟排序结果,我们再编写一个全局函数print_out,由List的成员函数traverse()调用调用的过程在置于swap之中,即每次有交换(或看做移动)则看做一趟排序。
相应算法函数如下:
copy
    voidSortable_list<Record>::shell_sort()
  1. /*Post:TheentriesoftheSortable_listhavebeenrearrangedsothatthekeysin
  2. alltheentriesaresortedintonondecreasingorder.
  3. Uses:sort_interval.
  4. */
  5. intincrement,//spacingofentriesinsublist
  6. start;//startingpointofsublist
  7. increment=count;
  8. do{
  9. increment=increment/2;
  10. for(start=0;start<increment;start++){
  11. sort_interval(start,increment);//modifiedinsertionsort
  12. traverse(print_out);
  13. cout<<endl;
  14. }
  15. }while(increment>1);
  16. voidSortable_list<Record>::sort_interval(intstart,87); background-color:inherit; font-weight:bold">intincrement)
  17. Sortable_list<Record>temp;
  18. intj=0;
  19. inti=start;i<size();i=i+increment){
  20. Recordtemp_record;
  21. retrieve(i,temp_record);
  22. temp.insert(j,temp_record);
  23. j++;
  24. temp.insertion_sort();
  25. j=0;
  26. intk=start;k<size();k+=increment){
  27. temp.retrieve(j,108); list-style:decimal-leading-zero outside; color:inherit; line-height:18px"> replace(k,248); line-height:18px"> }
  28. voidSortable_list<Record>::insertion_sort()
  29. Uses:MethodsfortheclassRecord;thecontiguousList
  30. intfirst_unsorted;//positionoffirstunsortedentry
  31. intposition;//searchessortedpartoflist
  32. Recordcurrent;//holdstheentryemporarilyremovedfromlist
  33. for(first_unsorted=1;first_unsorted<count;first_unsorted++)
  34. if(entry[first_unsorted]<entry[first_unsorted-1]){
  35. position=first_unsorted;
  36. current=entry[first_unsorted];//Pullunsortedentryoutofthelist.
  37. do{//Shiftallentriesuntiltheproperpositionisfound
  38. entry[position]=entry[position-1];
  39. position--;//positionifempty.
  40. while(position>0&&entry[position-1]>current);
  41. entry[position]=current;
  42. //其他辅助函数见源文件

【实验结果】




归并排序

归并排序是采用分治的思想将两个已排序的表合并成一个
归并排序算法的基本思想是:先将一个表分成两个表(当个数是奇数时,使左表的元素比右表多一)。对两个表分别进行归并排序,然后将两个已排序好的表合并。合并的思路就像将两罗纸牌混成一摞,每次取顶上最小的纸牌。


【相关实验】


1.仍旧使用上述的Sortable_list
2.根据归并排序的思想,每次子表仍旧使用归并排序,可以通过递归实现。所以编写递归函数recursive_merge_sort(),要把已排序好的子表合并,所以编写合并子表的辅助函数merge()
3.为了输出每趟排序结果,在归并时merge中加入traverse(print_out) //但因为递归调用的问题,很多次我们还是看不到过程
  1. classRecord>
  2. voidSortable_list<Record>::merge(intlow,87); background-color:inherit; font-weight:bold">inthigh)
  3. {
  4. Record*tmp=newRecord[high-low+1];
  5. intindex=0;
  6. intindex1=low,mid=(low+high)/2,index2=mid+1;
  7. while(index1<=mid&&index2<=high){
  8. if(entry[index1]<entry[index2])
  9. tmp[index++]=entry[index1++];
  10. else
  11. tmp[index++]=entry[index2++];
  12. }
  13. while(index1<=mid)
  14. tmp[index++]=entry[index1++];
  15. while(index2<=high)
  16. tmp[index++]=entry[index2++];
  17. for(index=low;index<=high;index++)
  18. entry[index]=tmp[index-low];
  19. delete[]tmp;
  20. traverse(print_out);
  21. cout<<endl;
  22. }
  23. voidSortable_list<Record>::recursive_merge_sort(/*Post:Theentriesofthesortablelistbetween
  24. indexlowandhighhavebeenrearrangedsothat
  25. theirkeysaresortedintonondecreasingorder.
  26. Uses:Thecontiguouslist
  27. */
  28. {
  29. if(high>low){
  30. recursive_merge_sort(low,(high+low)/2);
  31. recursive_merge_sort((high+low)/2+1,high);
  32. merge(low,high);
  33. classRecord>
  34. voidSortable_list<Record>::merge_sort()
  35. /*Post:Theentriesofthesortablelisthavebeenrearrangedsothat
  36. recursive_merge_sort(0,size()-1);
  37. }

【实验结果】



快速排序

快速排序是对冒泡排序的一种改进。
快速排序算法的基本思想是:每一趟排序中找一个点pivot,将表分割成独立的两部分,其中一部分的所有Record都比pivot小,另一部分比pivot大,然后再按此方法对这两部分数据分别进行快速排序。


【相关实验】

1.仍旧使用上述的Sortable_list。
2.根据快速排序的思想,每趟排序将表分成两部分然后仍旧进行快速排序,所以可以通过递归实现,而为了调用递归函数,我们首先编写给定要排序范围的参数的函数recursive_quick_sort(int low,int high)//之所以不将开始的quick_sort直接写作递归函数,是为了避免输入参数而方便用户调用。另外需要一个partition函数,返回每趟排序之后的分点。
3.为了输出每趟排序,我的想法是在每次递归中使用traverse(print_out)输出,但却不是想象的结果。因为递归是每次递归出来之后才执行函数print_out,除了前几次可以看到结构,后来都是在排序好之后…所以我们仍旧通过swap函数实现输出
copy
    intSortable_list<Record>::partition(inthigh)
  1. /*Pre:lowandhigharevalidpositionsoftheSortable_list,withlow<=high.
  2. Post:Thecenter(orleftcenter)entryintherangebetweenindiceslowand
  3. highoftheSortable_listhasbeenchosenasapivot.Allentriesofthe
  4. Sortable_listbetweenindiceslowandhigh,inclusive,havebeenrearranged
  5. sothatchosenwithkeyslessthanthepivotcombeforethepivot
  6. andtheremainingentriescomeafterthepivot.Thefinalpositionofthe
  7. pivotisreturned.
  8. Uses:swap(int,intj)contigIoUslist
  9. */
  10. {
  11. Recordpivot;
  12. inti,0); background-color:inherit">//usedtoscanthroughthelist
  13. last_small;//positionofthelastkeylessthanpivot
  14. swap(low,(low+high)/2);
  15. pivot=entry[low];
  16. last_small=low;
  17. for(i=low+1;i<=high;i++)
  18. //Atthebeginningofeachiterationofthisloop,wehavethefollowingconditions:
  19. //Iflow<j<=last_samllthenentry[j].key<pivot
  20. //Iflast_small<j<ithenentry[j].key>=pivot.
  21. if(entry[i]<pivot){
  22. last_small=last_small+1;
  23. swap(last_small,i);//Movelargeentrytorightandsmalltoleft
  24. swap(low,last_small);//Putthepivotintoitsproperposition.
  25. returnlast_small;
  26. voidSortable_list<Record>::recursive_quick_sort(/*Pre:lowandhigharevalidpositionsintheSortablelist.
  27. Post:TheentriesoftheSortable_listhavebeenrearrangedsothattheirkeys
  28. aresortedintonondecreasingorder.
  29. Uses:Thecontiguouslist,recursive_quick_sort,partition.
  30. intpivot_postion;
  31. if(low<high){
  32. pivot_postion=partition(low,high);
  33. recursive_quick_sort(low,pivot_postion-1);
  34. recursive_quick_sort(pivot_postion+1,153); background-color:inherit; font-weight:bold">voidSortable_list<Record>::quick_sort()
  35. recursive_quick_sort(0,count-1);
  36. //其他函数见源码

【实验结果】


堆排序
堆排序是先将表中Record按大堆(或小堆)存放,使选取最大的Record变的极为容易,每次选取之后再提升堆。实现排序。
继续:
1.仍旧使用上述的Sortable_list。
2.编写heap_sort()函数。按思路,首先应该建堆,然后取堆顶元素,之后对剩下元素重新建堆(提升堆),所以我们需要编写build_heap()函数,其通过inster_heap函数将元素一个个插入堆中。
最后实现heap_sort函数。
3.我们在每次插入堆时调用traverse(print_sort)实现对每趟排序的输出。
copy
    voidSortable_list<Record>::insert_heap(constRecord¤t,0); background-color:inherit">/*Pre:TheentriesoftheSortable_listbetweenindiceslow+1andhigh,
  1. inclusive,formaheap.Theentryinpositionlowwillbediscarded.
  2. Post:TheentrycurrenthasbeeninsertedintotheSortabl_listandtheentries,0); background-color:inherit">rearrangedsothattheentriesbetweenindiceslowandhigh,inclusive
  3. formaheap.
  4. intlarge;//positionofchildofentry[low]withthelargerkey
  5. large=2*low+1;//largeisNowtheleftchildoflow
  6. while(large<=high){
  7. if(large<high&&entry[large]<entry[large+1])
  8. large++;//largeisNowthechildoflowwiththelargestkey.
  9. if(current>=entry[large])
  10. break;//currentbelongsinpositionlow
  11. else{//Promoteentry[large]andmovedownthetree
  12. entry[low]=entry[large];
  13. low=large;
  14. large=2*low+1;
  15. entry[low]=current;
  16. traverse(print_out);
  17. cout<<endl;
  18. classRecord>
  19. voidSortable_list<Record>::build_heap()
  20. /*Post:TheentriesoftheSortable_listhavebeenrearrangedsothat
  21. itbecomesaheap
  22. Uses:Thecontiguouslistandinsert_heap
  23. intlow;
  24. for(low=count/2-1;low>=0;low--){
  25. Recordcurrent=entry[low];
  26. insert_heap(current,low,153); background-color:inherit; font-weight:bold">voidSortable_list<Record>::heap_sort()
  27. /*Post:TheentriesoftheSortable_listhavebeenrearrangedsothattheirkeys
  28. Recordcurrent;//temporarystorageformovingentries
  29. intlast_unsorted;//Entriesbeyondlast_unsortedhavebeensorted.
  30. build_heap();//Firstphase:Turnthelistintoaheap
  31. for(last_unsorted=count-1;last_unsorted>0;last_unsorted--){
  32. current=entry[last_unsorted];//Extractthelastentryfromthelist
  33. entry[last_unsorted]=entry[0];//Movetopofheaptotheend
  34. insert_heap(current,last_unsorted-1);//Restoretheheap
  35. //其他函数见源码

【实验结果】



结果分析

【希尔排序】

1.希尔排序是直接插入的一种改进,在效率上较直接插入排序有较大的改进。
直接插入排序每次只能将Record移动一个位置,即增量increment为1,而希尔排序开始时增量较大,分组较多,每组Record数目少,故各组内直接插入较快;increment递减之后分组逐渐减少,Record增多,但由于已经在increment较大时进行过排序,表更接近于有序状态,新一趟的排序也较快。
2.我在实验中子表的排序是通过定义一个新表,然后调用直接插入函数。但这种方法效率并不高,而且浪费空间;直接对子表进行直接插入的排序是一种更好的方法
3.希尔排序复杂度为:O(nlog2n) d =1希尔与直接插入排序基本一致
4.希尔排序是不稳定的排序算法,即相等元素的顺序可能改变

【归并排序】

1.归并排序在实现上用链式表更为合理,因为合并中需要定义新的表,即使我们通过动态定义再删除,以节省不必要的空间,这些工作仍是有些费时。而链式表只是返回指针,对节点Node的Node<Node_entry>*next部分进行操作,不需要数据的移动。但同时链式表的使用也需要对指针有熟悉的掌握,很容易出错,先画图再编码往往会有更清晰的思路。
2.归并排序的复杂度为:O(nlog2n)
3.归并排序是稳定的排序算法,即相等元素的顺序不会改变

快速排序】

1.复杂度:最好 O(nlog2n),最坏 O(n2)
2.快速排序的最坏情况基于每次划分对主元的选择。基本的快速排序选取第一个元素作为主元。这样在数组已经有序的情况下,每次划分将得到最坏的结果。一种比较常见的优化方法随机化算法,即随机选取一个元素作为主元。这种情况下虽然最坏情况仍然是O(n^2),但最坏情况不再依赖于输入数据,而是由于随机函数取值不佳。
3.快速排序是一种不稳定的排序算法

【堆排序】

1.实现堆排序很重要的一个操作就是建堆
要将初始表调整为一个大根堆,就必须将它所对应的完全二叉树中以每一结点为根的子树都调整为堆。显然只有一个结点的树是堆,而在完全二叉树中,所有序号大于n/2的结点都是叶子,因此以这些结点为根的子树均已是堆。这样,我们只需依次将以序号为n/2,…,1的结点作为根的子树都调整为堆即可。
2.堆[排序的时间,主要由建立初始堆和反复重建堆这两部分的时间开销构成,堆排序的最坏时间复杂度为O(nlog2n)。
3.堆排序是不稳定的排序算法
4.由于建初始堆所需的比较次数较多,所以堆排序不适宜于记录数较少的文件
堆排序每次在大堆中直接选择出顶即最大的元素,这与选择排序极为相似,但他们是不同的,堆排序的性能更为优越。因为选择排序中,为了从表中选出最小的记录,必须进行n-1次比较,然后在剩余表中选出关键字最小的记录,又需要做n-2次比较。事实上,后面的n-2次比较中,有许多比较可能在前面的n-1次比较中已经做过,但由于前一趟排序时未保留这些比较结果,所以后一趟排序时又重复执行了这些比较操作。堆排序可通过树形结构保存部分比较结果,可减少比较次数


(转载请注明作者和出处:http://blog.csdn.net/xiaowei_cqu未经允许请勿用于商业用途)

相关文章

【啊哈!算法】算法3:最常用的排序——快速排序       ...
匿名组 这里可能用到几个不同的分组构造。通过括号内围绕的正...
选择排序:从数组的起始位置处开始,把第一个元素与数组中其...
public struct Pqitem { public int priority; ...
在编写正则表达式的时候,经常会向要向正则表达式添加数量型...
来自:http://blog.csdn.net/morewindows/article/details/6...