[关闭]
@qiezhian 2014-11-15T08:40:34.000000Z 字数 10299 阅读 1434

排序算法总结

算法
本文总结最常用的几个排序算法,包括插入、冒泡、shell排序、归并排序、快速排序、堆排序等,并分析其空间和时间复杂度。剩余几种排序算法,如堆排序、基数排序、桶排序等会在下次文章更新中补充。本文给出C++代码并封装为类CSort。本文总结大部分取自《数据结构与算法分析》[1]一书。
本文描述算法都将接收一个含有元素的数组和一个包含元素个数的整数。本文给出的代码假设函数调用者给的参数是合法的。

冒泡排序

原理:冒泡排序算法的运作如下[2]:(从后往前)
1.比较相邻的元素。如果第一个比第二个大,就交换他们两个。
2.对每一对相邻元素作同样的工作,从开始第一对到结尾的最后一对。在这一点,最后的元素应该会是最大的数。
3.针对所有的元素重复以上的步骤,除了最后一个。
4.持续每次对越来越少的元素重复上面的步骤,直到没有任何一对数字需要比较。
分析:冒泡排序就是把小的元素往前调或者把大的元素往后调。比较是相邻的两个元素比较,交换也发生在这两个元素之间。所以,如果两个元素相等,我想你是不会再无聊地把他们俩交换一下的;如果两个相等的元素没有相邻,那么即使通过前面的两两交换把两个相邻起来,这时候也不会交换,所以相同元素的前后顺序并没有改变,所以冒泡排序是一种稳定排序算法。冒泡排序最好的时间复杂度为Θ(N)。若初始文件是反序的,需要进行N-1趟排序,每趟排序要进行n-i次关键字的比较(1≤i≤n-1),且每次比较都必须移动记录三次来达到交换记录位置,此种情况下时间界为Θ(N2)。平均情况下时间界也为Θ(N2)。下面给出的代码是将小的元素往前调。

  1. template<typename T>
  2. void CSort<T>::BubbleSort( T A[], const int N)
  3. {
  4. int i, j;
  5. for( i=0; i<N; i++)
  6. for( j=N-1; j>=i; j--)
  7. if( A[j-1] > A[j] )
  8. Swap( &A[j-1], &A[j] );
  9. }

选择排序

原理[3]比如在一个长度为N的无序数组中,在第一趟遍历N个数据,找出其中最小的数值与第一个元素交换,第二趟遍历剩下的N-1个数据,找出其中最小的数值与第二个元素交换......第N-1趟遍历剩下的2个数据,找出其中最小的数值与第N-1个元素交换,至此选择排序完成。
分析:平均时间复杂度Θ(N2)。选择排序的交换操作介于0(n-1)次之间。选择排序的比较操作为n(n-1)/2次之间。选择排序的赋值操作介于03(n-1)次之间。比较次数Θ(N2),比较次数与关键字的初始状态无关,总的比较次数

N=(n1)+(n2)+...+1=n(n1)/2
交换次数Θ(N),最好情况是,已经有序,交换0次;最坏情况是,逆序,交换n-1次。 交换次数比冒泡排序少多了,由于交换所需CPU时间比比较所需的CPU时间多,n值较小时,选择排序比冒泡排序快。

  1. template<typename T>
  2. void CSort<T>::SelectSort( T A[], const int N)
  3. {
  4. int min, index;
  5. int i, j;
  6. for( i=0; i<N-1; i++)
  7. {
  8. min = A[i];
  9. index = i;
  10. for( j=i; j<N; j++)
  11. if( A[j]<min )
  12. {
  13. min = A[j];
  14. index = j;
  15. }
  16. if( index!=i )
  17. Swap( &A[i], &A[index] );
  18. }
  19. }

插入排序

原理:插入排序 (insertion sort) 是一种基于比较的排序算法。假设元素个数为N,那么插入排序由N-1趟排序完成。对于第P(0<p<N)趟排序,插入排序保证位置0到位置P-1的元素是排过序的。所以经过N-1趟排序之后,整个序列都能保证是有序的。
分析:插入排序最坏情况的时间复杂度为Θ(N2)。最好的情况为初始序列已排序,复杂度为Θ(N),如果输入几乎被排序,则插入排序运行较快。平均情况下,时间复杂度为Θ(N2)
N个互异数的数组的平均逆序数为N(N1)/4,通过交换相邻元素进行排序只能减少一个逆序,所以通过交换相邻元素进行排序的任何算法平均需要Θ(N2)时间。这个时间下界说明,为使一个排序算法以亚二次时间运行,需要对相距较远的元素进行交换,使得每一次交换减少不止一个逆序。

  1. template<typename T>
  2. void CSort<T>::InsertSort(T A[], const int N)
  3. {
  4. if( A==NULL || N<=0 )
  5. return;
  6. int i, j;
  7. //start from index 1
  8. for( i=1; i<N; i++)
  9. for( j=i; j>0 && ( A[j]<A[j-1] ); j--)
  10. Swap( &A[j], &A[j-1]);
  11. }

Shell排序

原理:又称增量缩小排序。先将序列按增量划分为元素个数相同的若干组,使用直接插入排序法进行排序,然后不断缩小增量直至为1,最后使用直接插入排序完成排序。从下面的代码也可以看出,Shell排序与插入排序的关系。当Increment=1时,Shell排序就是插入排序。
交换元素A[i]A[i+k],它们最初是无序的,则减掉的逆序数最少为1个最多为2k-1个。Shell排序就是根据这个事实,在Icrement较大的时候,通过交换一次较远元素可以减少多个逆序。当Increment=1时,序列几乎已经被排好序,所以速度较快。
分析:增量序列的选取对算法性能有很大的影响。常用的增量序列为N/2,N/4 ... 1。Shell排序的时间界分析比较复杂,在使用上述增量序列时,最坏时间界为Θ(N2)

  1. template<typename T>
  2. void CSort<T>::ShellSort( T A[], const int N)
  3. {
  4. int Increment;
  5. int i, j;
  6. for( Increment=N/2; Increment>0; Increment/=2)
  7. for( i=Increment; i<N; i++)
  8. for( j=i; j>=Increment && A[j]<A[j-Increment]; j-=Increment)
  9. Swap( &A[j], &A[j-Increment]);
  10. }

归并排序

原理:归并排序的基本操作是合并两个已排好序的表。对表A进行排序可分解为三步:
1.将表A分解成表B和表C,B和C不相交
2.B、C分别排序
3.合并B、C得到排序后表A
从上述过程可以看出,归并排序是个典型的分治算法,它将问题分解成一些小问题并递归求解,而治的阶段将分的阶段得到的结果补到一起。下面给出的代码从中间位置对表进行分割。
分析:归并排序需要额外的Θ(N)空间用来合并子表B和C,最后再将此合并表数据Copy回表A。归并排序以Θ(NlogN)的最坏时间界运行,所使用的比较次数几乎是最优的。但是它很难用于主存排序,主要问题在于合并两个排序子表需要线性附加内存,还要花费线性数据拷贝时间,严重拖慢了排序速度。

  1. //非递归实现
  2. template<typename T>
  3. void CSort<T>::MergeSortNonR( T A[], const int N)
  4. {
  5. int Increment=1;
  6. if( N<=Increment )
  7. return;
  8. T* Tmp;
  9. Tmp = (T*) malloc( N*sizeof(T) );
  10. if( Tmp==NULL )
  11. return;
  12. int i,j;
  13. for( ; Increment<N; Increment*=2 )
  14. for( i=0; i+Increment<N; i+=2*Increment)
  15. Merge( A, Tmp, i, i+Increment, ( N-1>i+2*Increment-1 ? (i+2*Increment-1):(N-1) ) );
  16. }
  17. //递归实现
  18. template<typename T>
  19. void CSort<T>::MergeSort( T A[], const int N)
  20. {
  21. if( A==NULL || N<=0 )
  22. return;
  23. T *Tmp;
  24. Tmp = (T*)malloc( N*sizeof(T));
  25. if(Tmp)
  26. {
  27. MSort( A, Tmp, 0, N-1);
  28. free( Tmp );
  29. }
  30. else
  31. return;
  32. }
  33. template<typename T>
  34. void CSort<T>:: MSort( T A[], T Tmp[], int Left, int Right)
  35. {
  36. if( Left<Right)
  37. {
  38. int center =(Left + Right)/2;
  39. MSort( A, Tmp, Left,center);
  40. MSort( A, Tmp, center+1,Right);
  41. Merge( A, Tmp, Left, center+1, Right);
  42. }
  43. }
  44. template<typename T>
  45. void CSort<T>::Merge( T A[], T Tmp[], int LPos, int RPos, int RightEnd)
  46. {
  47. int i, j;
  48. int TmpPos, LeftPos, LeftEnd, RightPos;
  49. int NumElements;
  50. NumElements = RightEnd - LPos +1;
  51. TmpPos = LPos;
  52. LeftPos = LPos;
  53. LeftEnd = RPos-1;
  54. RightPos = RPos;
  55. while( LeftPos<=LeftEnd && RightPos<=RightEnd)
  56. {
  57. if( A[LeftPos]<A[RightPos] )
  58. Tmp[TmpPos++] = A[LeftPos++];
  59. else
  60. Tmp[TmpPos++] = A[RightPos++];
  61. }
  62. while( LeftPos<=LeftEnd )
  63. Tmp[TmpPos++] = A[LeftPos++];
  64. while( RightPos<=RightEnd )
  65. Tmp[TmpPos++] = A[RightPos++];
  66. //copy Tmp[] data back to A[]
  67. for( i=LPos; i<=RightEnd; i++)
  68. A[i] = Tmp[i];
  69. }

快速排序

原理:将数组S排序的基本算法由四步组成:
1.如果S中元素个数为0或1,则返回
2.取S中任意元素v,称之为枢纽元
3.将S中其他元素分成两个不相交集合S1S2S1中元素均小于v,S2中元素均大于v
4.返回排序后S: quicksort(S1),v,quicksort(S2)
分析:快速排序是在实践中最快的已知排序算法,它的平均运行时间界Θ(NlogN),它的最坏情形时间界为Θ(N2)。一个影响因素是第2步中枢纽元的选取,它影响到第3步分割,继而影响排序性能。一半常用的策略取中间位置元素,或者在首位、中间位、最后位中选择三者的中值作为枢纽元。下面代码给出的是取中间位置元素作为枢纽元。

  1. template<typename T>
  2. void CSort<T>::QuickSort(T A[],const int N)
  3. {
  4. QSort( A, 0, N-1);
  5. }
  6. template<typename T>
  7. void CSort<T>::QSort( T A[], int Left, int Right)
  8. {
  9. if( A==NULL || Left>=Right )
  10. return;
  11. int index;
  12. int i, j;
  13. index = ( Left+Right )/2;
  14. Swap( &A[index],&A[Right] );//swap the pivot value to the last position of A[]
  15. for( i=Left-1, j=Right;;)
  16. {
  17. while( A[++i]<A[Right] );
  18. while( A[--j]>A[Right] );
  19. if( i<j )
  20. Swap( &A[i], &A[j] );
  21. else
  22. break;
  23. }
  24. Swap( &A[i], &A[Right] );//swap back
  25. QSort( A, Left, i-1);
  26. QSort( A, i+1, Right);
  27. }

堆排序

堆排序首先花费Θ(N)时间建立二叉堆,然后执行N次DeleteMin操作,按照操作顺序,最小的元素先例开该堆,最后得到已排序序列。堆排序关键在于理解二叉堆的构造,会在下篇文章中介绍。这里先给出代码。

  1. template<typename T>
  2. void CSort<T>::PercDown( T A[], int i, int N)
  3. {
  4. int nChild;
  5. T Tmp;
  6. for( Tmp=A[i]; (2*i+1)<N; i=nChild)
  7. {
  8. nChild = ( 2*i+1 );
  9. if( ( nChild!=N-1 ) && ( A[nChild+1]>A[nChild] ) )
  10. nChild++;
  11. if( A[nChild] > Tmp )
  12. A[i] = A[nChild];
  13. else
  14. break;
  15. }
  16. A[i] = Tmp;
  17. }
  18. template<typename T>
  19. void CSort<T>::BuildHeap(T A[], const int N)
  20. {
  21. int i;
  22. for( i=N/2; i>=0; i--)
  23. PercDown( A, i, N);
  24. }
  25. template<typename T>
  26. void CSort<T>::HeapSort( T A[], const int N)
  27. {
  28. int i;
  29. BuildHeap( A, N);
  30. for( i=N-1; i>=0; i--)
  31. {
  32. Swap( &A[0], &A[i] );
  33. PercDown( A, 0, i);
  34. }
  35. }

性能分析

算法 插入排序 冒泡排序 选择排序 Shell排序 堆排序 快速排序
已排序 O(N)
全逆序 O(N2)
平均情况 O(N2)

实现

最后给出本文完整代码,包括sort.hpp文件与sort.cpp文件。

  1. /********************************************************************************
  2. FileName: sort.h
  3. Version: 14.6.11
  4. Author: Qie Zhian
  5. Data: 2014-6-11
  6. Comment:
  7. *********************************************************************************/
  8. #ifndef _SORT_H_
  9. #define _SORT_H_
  10. #include "stdlib.h"
  11. #include "stdio.h"
  12. template<typename T>
  13. class CSort
  14. {
  15. private:
  16. void Swap( T *a, T *b);
  17. // for HeapSort() function
  18. void BuildHeap( T A[], const int N);
  19. void PercDown( T A[], int i, int N);
  20. //for MergeSort() function
  21. void MSort( T A[], T Tmp[], int Left, int Right);
  22. void Merge( T A[], T Tmp[], int LPos, int RPos, int RightEnd);
  23. //for QuickSort() function
  24. void QSort( T A[], int Left, int Right);
  25. public:
  26. void InsertSort(T A[], const int N);
  27. void BucketSort( T A[], const int N);
  28. void ShellSort( T A[], const int N);
  29. void MergeSort( T A[], const int N);
  30. void MergeSortNonR( T A[], const int N);
  31. void QuickSort( T A[], const int N);
  32. void BubbleSort( T A[], const int N);
  33. void SelectSort( T A[], const int N);
  34. void HeapSort( T A[], const int N);
  35. };
  36. template<typename T>
  37. void CSort<T>::Swap( T *a, T *b)
  38. {
  39. T Tmp;
  40. Tmp = *a;
  41. *a = *b;
  42. *b = Tmp;
  43. }
  44. template<typename T>
  45. void CSort<T>::PercDown( T A[], int i, int N)
  46. {
  47. int nChild;
  48. T Tmp;
  49. for( Tmp=A[i]; (2*i+1)<N; i=nChild)
  50. {
  51. nChild = ( 2*i+1 );
  52. if( ( nChild!=N-1 ) && ( A[nChild+1]>A[nChild] ) )
  53. nChild++;
  54. if( A[nChild] > Tmp )
  55. A[i] = A[nChild];
  56. else
  57. break;
  58. }
  59. A[i] = Tmp;
  60. }
  61. template<typename T>
  62. void CSort<T>::BuildHeap(T A[], const int N)
  63. {
  64. int i;
  65. for( i=N/2; i>=0; i--)
  66. PercDown( A, i, N);
  67. }
  68. template<typename T>
  69. void CSort<T>::HeapSort( T A[], const int N)
  70. {
  71. int i;
  72. BuildHeap( A, N);
  73. for( i=N-1; i>=0; i--)
  74. {
  75. Swap( &A[0], &A[i] );
  76. PercDown( A, 0, i);
  77. }
  78. }
  79. template<typename T>
  80. void CSort<T>::InsertSort(T A[], const int N)
  81. {
  82. if( A==NULL || N<=0 )
  83. return;
  84. int i, j;
  85. //start from index 1
  86. for( i=1; i<N; i++)
  87. for( j=i; j>0 && ( A[j]<A[j-1] ); j--)
  88. Swap( &A[j], &A[j-1]);
  89. }
  90. template<typename T>
  91. void CSort<T>::MergeSortNonR( T A[], const int N)
  92. {
  93. int Increment=1;
  94. if( N<=Increment )
  95. return;
  96. T* Tmp;
  97. Tmp = (T*) malloc( N*sizeof(T) );
  98. if( Tmp==NULL )
  99. return;
  100. int i,j;
  101. for( ; Increment<N; Increment*=2 )
  102. for( i=0; i+Increment<N; i+=2*Increment)
  103. Merge( A, Tmp, i, i+Increment, ( N-1>i+2*Increment-1 ? (i+2*Increment-1):(N-1) ) );
  104. }
  105. template<typename T>
  106. void CSort<T>::MergeSort( T A[], const int N)
  107. {
  108. if( A==NULL || N<=0 )
  109. return;
  110. T *Tmp;
  111. Tmp = (T*)malloc( N*sizeof(T));
  112. if(Tmp)
  113. {
  114. MSort( A, Tmp, 0, N-1);
  115. free( Tmp );
  116. }
  117. else
  118. return;
  119. }
  120. template<typename T>
  121. void CSort<T>:: MSort( T A[], T Tmp[], int Left, int Right)
  122. {
  123. if( Left<Right)
  124. {
  125. int center =(Left + Right)/2;
  126. MSort( A, Tmp, Left,center);
  127. MSort( A, Tmp, center+1,Right);
  128. Merge( A, Tmp, Left, center+1, Right);
  129. }
  130. }
  131. template<typename T>
  132. void CSort<T>::Merge( T A[], T Tmp[], int LPos, int RPos, int RightEnd)
  133. {
  134. int i, j;
  135. int TmpPos, LeftPos, LeftEnd, RightPos;
  136. int NumElements;
  137. NumElements = RightEnd - LPos +1;
  138. TmpPos = LPos;
  139. LeftPos = LPos;
  140. LeftEnd = RPos-1;
  141. RightPos = RPos;
  142. while( LeftPos<=LeftEnd && RightPos<=RightEnd)
  143. {
  144. if( A[LeftPos]<A[RightPos] )
  145. Tmp[TmpPos++] = A[LeftPos++];
  146. else
  147. Tmp[TmpPos++] = A[RightPos++];
  148. }
  149. while( LeftPos<=LeftEnd )
  150. Tmp[TmpPos++] = A[LeftPos++];
  151. while( RightPos<=RightEnd )
  152. Tmp[TmpPos++] = A[RightPos++];
  153. //copy Tmp[] data back to A[]
  154. for( i=LPos; i<=RightEnd; i++)
  155. A[i] = Tmp[i];
  156. }
  157. template<typename T>
  158. void CSort<T>::ShellSort( T A[], const int N)
  159. {
  160. int Increment;
  161. int i, j;
  162. for( Increment=N/2; Increment>0; Increment/=2)
  163. for( i=Increment; i<N; i++)
  164. for( j=i; j>=Increment && A[j]<A[j-Increment]; j-=Increment)
  165. Swap( &A[j], &A[j-Increment]);
  166. }
  167. template<typename T>
  168. void CSort<T>::QuickSort(T A[],const int N)
  169. {
  170. QSort( A, 0, N-1);
  171. }
  172. template<typename T>
  173. void CSort<T>::QSort( T A[], int Left, int Right)
  174. {
  175. if( A==NULL || Left>=Right )
  176. return;
  177. int index;
  178. int i, j;
  179. index = ( Left+Right )/2;
  180. Swap( &A[index],&A[Right] );//swap the pivot value to the last position of A[]
  181. for( i=Left-1, j=Right;;)
  182. {
  183. while( A[++i]<A[Right] );
  184. while( A[--j]>A[Right] );
  185. if( i<j )
  186. Swap( &A[i], &A[j] );
  187. else
  188. break;
  189. }
  190. Swap( &A[i], &A[Right] );
  191. QSort( A, Left, i-1);
  192. QSort( A, i+1, Right);
  193. }
  194. template<typename T>
  195. void CSort<T>::BubbleSort( T A[], const int N)
  196. {
  197. int i, j;
  198. for( i=0; i<N; i++)
  199. for( j=N-1; j>=i; j--)
  200. if( A[j-1] > A[j] )
  201. Swap( &A[j-1], &A[j] );
  202. }
  203. template<typename T>
  204. void CSort<T>::SelectSort( T A[], const int N)
  205. {
  206. int min, index;
  207. int i, j;
  208. for( i=0; i<N-1; i++)
  209. {
  210. min = A[i];
  211. index = i;
  212. for( j=i; j<N; j++)
  213. if( A[j]<min )
  214. {
  215. min = A[j];
  216. index = j;
  217. }
  218. if( index!=i )
  219. Swap( &A[i], &A[index] );
  220. }
  221. }
  222. #endif
  223. #endif
  1. /*****************************************************************************
  2. FileName: sort.cpp
  3. Version: 14.6.11
  4. Author: Qie
  5. Data: 2014-6-11
  6. Comment:
  7. ******************************************************************************/
  8. #include <iostream>
  9. #include "sort.hpp"
  10. using namespace std;
  11. int main()
  12. {
  13. int A[]={9,8,7,6,5,4,3,2,1,2,5,6};
  14. int N=sizeof(A)/sizeof(int);
  15. CSort<int> sort_int;
  16. // sort_int.InsertSort(A,N);
  17. // sort_int.MergeSort(A,N);
  18. // sort_int.ShellSort(A,N);
  19. // sort_int.QuickSort(A,N);
  20. // sort_int.BubbleSort(A,N);
  21. // sort_int.SelectSort(A,N);
  22. // sort_int.HeapSort(A,N);
  23. sort_int.MergeSortNonR(A,N);
  24. int i;
  25. for(i=0;i<N;i++)
  26. cout<< A[i]<<endl;
  27. }

[1] Mark Allen Weiss ,数据结构与算法分析--C语言描述,第二版,机械工业出版社
[2] http://baike.baidu.com/view/254413.htm?fr=aladdin
[3] http://blog.csdn.net/cjf_iceking/article/details/7914554
添加新批注
在作者公开此批注前,只有你和作者可见。
回复批注