排序算法的比较、选择及其改进

一、引言
  
  排序是计算机科学中最重要的研究问题之一, 它在计算机图形、计算机辅助设计、机器人、模式识别及统计学等领域具有广泛的应用。由于它固有的理论上的重要性,2000年它被列为对科学和工程计算的研究与实践影响最大的10大问题之一。其功能是将一个数据元素的任意序列重新排列成一个按关键字有序的序列。
  
  二、排序算法的性能比较
  
  内部排序算法种类繁多,但就其排序时所遵循的原则而言,大致可分为五大类:插入排序、交换排序、选择排序、归并排序和基数排序。算法性能的比较主要是从时间复杂度、空间复杂度和稳定性三个方面来综合考虑。
  (一)时间复杂度
  从平均性能上看,直接插入排序、简单选择排序、冒泡排序这三种“简单排序”为第一类,其时间复杂度均为O(n2);堆排序、归并排序、快速排序属于第二类,其时间复杂度均为O(nlog2n);基数排序则属于第三类,其时间复杂度为 O(d(n+rd)),也可以写为O(dn),因此最适用于n值很大而关键字较小的序列。
  当原表有序或基本有序时,直接插入排序和冒泡排序将大大减少比较次数和移动记录的次数,时间复杂度可降至O(n);而快速排序则相反,当原表基本有序时,将蜕化为冒泡排序,时间复杂度提高为O(n2);原表是否有序,对简单选择排序、堆排序、归并排序和基数排序的时间复杂度影响不大。
  (二)空间复杂度
  从空间复杂度来看,归并排序最大,对 n 个记录需要附加等量的存储量,其空间复杂度为O(n);基数排序次之,它需要附加较多存储空间用于存储队列指针和用作结点指针域,空间复杂度为O(rd);快速排序单独讨论,其递归算法需要使用堆栈来实现,栈中存放待排序记录序列的首尾位置,在一般情况下需要栈空间O(nlog2n),最坏情况下,所需要的栈空间为O(n);其余排序算法的空间复杂度最小,仅为O(1)。
  (三)稳定性
  从算法的稳定性而言,所有排序算法可分为两类:直接插入排序、简单选择排序、冒泡排序、归并排序和基数排序为第一类,均属于稳定的算法,而快速排序和堆排序则属于第二类,是不稳定算法。
  
  三、排序算法的选择
  
  根据以上的性能比较,我们发现每种排序算法都各有优缺点。因此,在实用时需根据不同情况适当选用,甚至可以将多种方法结合起来使用。
  (一)选择排序算法的依据
  影响排序的因素有很多,平均时间复杂度低的算法并不一定就是最优的。相反,有时平均时间复杂度高的算法可能更适合某些特殊情况。同时,选择算法时还得考虑它的可读性,以利于软件的维护。一般而言,需要考虑的因素有以下四点:
  1.待排序的记录数目n的大小;
  2.记录本身数据量的大小,也就是记录中除关键字外的其他信息量的大小;
  3.关键字的结构及其分布情况;
  4.对排序稳定性的要求。
  (二)选择排序算法的结论
  依据上述在选择排序算法时需考虑的因素,可以得出以下几个结论:
  1.数据量不大时选用插入或选择排序,一般不使用或不直接使用传统的冒泡排序;
  2.当数据量大而又注重空间复杂性时选择快速排序或堆排序等;
  3.在已排序数据上增加若干新数据时,建议使用插入排序;
  4.当数据量大而又允许使用较多附加空间时可选择桶排序。
  
  四、一些排序算法的改进策略
  
  常用的排序算法中,有不少是可以根据实际情况而进一步改进完善的,以充分提高算法的效率。
  (一)简单选择排序的改进二元选择排序
  传统的简单选择排序,每趟循环只能确定一个元素排序后的定位。我们可以考虑改进为每趟循环确定两个元素(当前趟最大和最小记录)的位置,从而减少排序所需的循环次数。改进后对n个数据进行排序,最多只需进行[n/2]趟循环即可。具体实现如下:
  void SelectSort(RecType r[ ],int n) {

/* 本文中RecType为记录类型 ,下同*/
  int i ,j , min ,max;
  for (i=1 ;i< =int( n/2);i++) {
  /* 做不超过 n / 2 趟选择排序 */
  min = i; max = i ;
  for (j= i+1; j<= n-i; j++) {
  /*分别记录最大和最小关键字记录位置*/
  if (r[j].key > r[max].key)
  { max = j ; continue ; }
  if (r[j].key < r[min ].key)
  { min = j ; }
  }/* end of for j */
  r[0]=r[i-1];r[i-1]=r[min];r[min]=r[0];
  r[0]=r[n-i];r[n-i]=r[max];r[max]=r[0];
  /*该交换操作还可分情况讨论以提高效率*/
  } /* end of for i*/
  } /* end of SelectSort */
  (二)冒泡排序算法的改进
  对冒泡排序常见的改进方法是加入一标志性变量exchange,用于标志某一趟排序过程中是否有数据交换,如果进行某一趟排序时并没有进行数据交换,则说明数据已经按要求排列好,可立即结束排序,避免不必要的比较过程。本文再提供以下两种改进算法:
  1.设置一标志性变量pos, 用于记录每趟排序中最后一次进行交换的位置。由于pos位置之后的记录均已交换到位, 故在进行下一趟排序时只要扫描到pos位置即可。
  改进后算法如下:
  void Bubble_1 ( RecType r[ ], int n) {
  i= n;/*初始时, 最后位置保持不变*/
  while ( i> 1) {
  pos= 0; /*每趟开始时, 无记录交换*/
  for (j= 1; j< i; j+ + )
   if ( r[j]. key> r[j+1].key) {
  pos= j; /*记录交换的位置 */
  r[0]= r[j];r[j]=r[j+1];r[j+1]=r[0];
  } /* end of if */
  i= pos; /*为下一趟排序作准备*/
   } /* end of while */
  }/* end of Bubble_1 */
  2.传统冒泡排序中每一趟排序操作只能找一个最大值或最小值,我们考虑利用在每趟排序中进行正向和反向两遍冒泡的方法一次可得到两个最终值(最大者和最小者) , 从而使排序趟数几乎减少了一半。
  改进后的算法实现为:
  void Bubble_2 ( RecType r[ ], int n){
  low = 1; high= n; /*置变量的初始值*/
  while (low < high) {
  for ( j= low; j< high; j+ + )
  /*正向冒泡, 找到最大者*/
  
  if (r[j].key>r[j+1].key) {
  r[0]= r[j];r[j]= r[j+1];
  r[j+1]= r[0];}
  high - -; /*修改high值, 前移一位*/
  for ( j=high; j>low; j- - )
  /*反向冒泡, 找到最小者*/
  if (r[j].key  r[0]= r[j]; r[j]= r[j-1];
  r[j-1]= r[0];}
  low + +; /*修改low值, 后移一位*/
  } /* end of while */
  } /* end of Bubble_2 */
  3.快速排序的改进。在本改进算法中,只对长度大于k的子序列递归调用快速排序,让原序列基本有序,然后再对整个基本有序序列用插入排序算法排序。实践证明,改进后的算法时间复杂度有所降低,且当k取值为 8 左右时,改进算法的性能最佳。算法思想如下:

void QuickSort(RecType r[ ]){
  Qsort(r,1,n);
  /*先调用改进算法Qsort使之基本有序*/
  for(i=2;i<=n;i++){
  /*再用插入排序对基本有序序列排序*/
  r[0]=r[i]; j=i-1;
  while(r[0].key  r[j+1]=r[j]; j=j-1; }
  r[j+1]=r[0];
  } /* end of for */
  } /* end of QuickSort */
  Qsort(RecType r[ ],int low,int high){
  if( highlow > k ) {
  /*长度大于k时递归, k为指定的数*/
  pivot = Partition(r, low, high);
  /* 调用的Partition算法保持不变*/
  Qsort(r, low, pivot - 1);
  Qsort(r, pivot + 1, high);
  } /* end of if */
  } /* end of Qsort */
  
  五、结束语
  
  排序算法应用广泛,是程序设计中的一种重要操作,充分了解各算法的性能优劣和选择的基本原则,在实际应用中具有重要的意义。针对常用的几种排序算法,本文提出了一些相应的改进策略及其详细的C语言实现,实践证明,这些改进算法具有较好的运行效果。

本文转自:全刊杂赏析网http://qkzz.net/article/fd9b8c88-c8d6-4e59-a581-b73bd9af5dfc_3.htm

原文地址:https://www.cnblogs.com/pbreak/p/1775801.html