//稳定排序:大小相同的元素排序后它们顺序不变。 // //排序方法选择,n表示数据长度: /* 1、若n较小(n <= 50)可以用插入排序、选择排序,如果再小的话就考虑使用插入排序; 2、若文件初始是大致有序的,应考虑插入排序、冒泡排序; 3、如果n很大5000+,则考虑快速排序、堆排序、归并排序; */ //排序法 平均时间 最差情形 稳定度 额外空间 备注 //冒泡 O(n2) O(n2) 稳定 O(1) n小时较好,大部分已排序时较好 //交换 O(n2) O(n2) 不稳定 O(1) n小时较好 //选择 O(n2) O(n2) 不稳定 O(1) n小时较好 //插入 O(n2) O(n2) 稳定 O(1) 大部分已排序时较好 //基数 O(logRB) O(logRB) 稳定 O(n) B是真数(0-9),R是基数(个十百) // //Shell O(nlogn) O(ns) 1<s<2 不稳定 O(1) s是所选分组 //快速 O(nlogn) O(n2) 不稳定 O(nlogn) n大时较好 //归并 O(nlogn) O(nlogn) 稳定 O(1) n大时较好 //堆 O(nlogn) O(nlogn) 不稳定 O(1) n大时较好 //**************************************************************************************************** //插入排序 //从第一个开始,记录data[i]为temp //将i左边所有比temp大的元素data[j-1]都向后移动一位 //然后将temp插入到空出来的位置 template<class T> void insertSort( T data[], const size_t &n ) { for ( size_t i=0,j=0; i<n; i++ ) { T temp = data[i]; for ( j=i; j>0 && temp < data[j-1]; j--) { data[j] = data[j-1]; } data[j] = temp; } } //**************************************************************************************************** //选择排序 //起始从第一个元素开始i //找到i到n中最小的放在i位置 //i++ template<class T> void selectSort( T data[], const size_t &n ) { for ( size_t i=0,j,least; i<n-1; i++ ) { for ( j=i+1,least=i; j<n; j++ ) { if ( data[j] < data[least] ) { least = j; } } swap( data[least], data[i] ); } } //**************************************************************************************************** //冒泡排序 //想像成一个柱体,最小的在低端,会像起泡一样冒上来 //每次i循环结束都冒上来剩余元素中最小的 //记录每次i循环标志是否发生交换,如果没发生则排序结束,终止循环 template<class T> void bubbleSort( T data[], const size_t &n ) { bool isExchange = false; for ( size_t i=0,j; i<n-1; i++ ) { isExchange = false; for ( j=n-1; j>i; j--) { if ( data[j] < data[j-1] ) { swap( data[j], data[j-1] ); isExchange = true; } } if ( !isExchange ) { return; } } } //**************************************************************************************************** //Shell排序 // //Shell排序通过将数据分成不同的组,先对每一组进行排序 //然后再对所有的元素进行一次插入排序,以减少数据交换和移动的次数。 //演示如下链接 //http://student.zjzk.cn/course_ware/data_structure/web/flashhtml/shell.htm //其中分组的合理性会对算法产生重要的影响。现在多用D.E.Knuth的分组方法。 // //Shell排序比冒泡排序快5倍,比插入排序大致快2倍。 //Shell排序比起QuickSort,MergeSort,HeapSort慢很多。 //但是它相对比较简单,它适合于数据量在5000以下并且速度并不是特别重要的场合。 //它对于数据量较小的数列重复排序是非常好的。 template<class T> void shellFun( T data[], const size_t &n, const size_t &d) { for ( size_t i = d+1; i<n; i++ ) { T temp = data[i]; size_t j = i; while ( j-d>=0 && temp < data[j-d] ) { data[j] = data[j-d]; j -= d; } data[j] = temp; } } template<class T> void shellSort( T data[], const size_t &n ) { size_t d = n; while ( d>1 ) { d = d/3 + 1; shellFun( data, n, d ); } } //**************************************************************************************************** //快速排序(QuickSort) // //快速排序是一个就地排序,分而治之,大规模递归的算法。 //从本质上来说,它是归并排序的就地版本。快速排序可以由下面四步组成。 // //(1) 如果不多于1个数据,直接返回。 //(2) 一般选择序列最左边的值作为支点数据。 //(3) 将序列分成2部分,一部分都大于支点数据,另外一部分都小于支点数据。 //(4) 对两边利用递归排序数列。 // //快速排序比大部分排序算法都要快。尽管我们可以在某些特殊的情况下写出比快速排序快的算法, //但是就通常情况而言,没有比它更快的了。 //快速排序是递归的,对于内存非常有限的机器来说,它不是一个好的选择。 template<class T> void quickFun( T data[], const size_t &first, const size_t &last ) { size_t lower = first+1; size_t upper = last; funtemp( data ); swap( data[first], data[(first+last) >> 1] ); funtemp( data ); T bound = data[first]; while( lower <= upper ) { while ( lower <= upper && data[lower] < bound ) { ++lower; } while ( lower <= upper && bound < data[upper] ) { --upper; } if ( lower < upper ) { swap( data[lower++], data[upper--] ); funtemp( data ); } else { ++lower; } } swap( data[upper], data[first] ); funtemp( data ); if ( 0 < upper && first < upper-1 ) //这里用的size_t不判断upper大于0就悲剧了 { quickFun( data, first, upper-1 ); } if ( upper+1 < last ) { quickFun( data, upper+1, last );; } } template<class T> void quickSort( T data[], const size_t &n ) { if ( n < 2 ) { return; } size_t max = 0; for ( size_t i=1; i<n; i++ ) { if ( data[max] < data[i] ) { max = i; } } swap( data[n-1],data[max]); quickFun( data, 0, n-2 ); } //**************************************************************************************************** //归并排序 //归并排序先分解要排序的序列,从1分成2,2分成4,依次分解, //当分解到只有1个一组的时候,就可以排序这些分组, //然后依次合并回原来的序列中,这样就可以排序所有数据。 //归并排序比堆排序稍微快一点,但是需要比堆排序多一倍的内存空间,因为它需要一个额外的数组。 template<class T> void MergeFun( T L1[], T L2[], const size_t &first, const size_t &last ) { for(size_t i=first; i<=mid; i++) L2[i] = L1[i]; size_t k = last; for(size_t j=mid+1; j<=last; j++) L2[j] = L1[k--]; size_t s1 = first; size_t s2 = last; size_t t = first; while(t<=last) { if(L2[s1] <= L2[s2]) L1[t++] = L2[s1++]; else L1[t++] = L2[s2--]; } } template<class T> void MergeSort( T L1[], T L2[], const size_t &first, const size_t &last ) { if(first >= last) { return; } size_t mid = (first+last)/2; MergeSort(L1, L2, first, mid); MergeSort(L1, L2, mid+1, last); MergeFun(L1, L2, first, mid, last); } //**************************************************************************************************** //基数排序(RadixSort) // //基数排序和通常的排序算法并不走同样的路线。 //它是一种比较新颖的算法,但是它只能用于整数的排序, //如果我们要把同样的办法运用到浮点数上,我们必须了解浮点数的存储格式, //并通过特殊的方式将浮点数映射到整数上,然后再映射回去,这是非常麻烦的事情, //因此,它的使用同样也不多。 //而且,最重要的是,这样算法也需要较多的存储空间。 //**************************************************************************************************** //堆排序(HeapSort) // //堆排序适合于数据量非常大的场合(百万数据)。 // //堆排序不需要大量的递归或者多维的暂存数组。 //这对于数据量非常巨大的序列是合适的。 //比如超过数百万条记录,因为快速排序,归并排序都使用递归来设计算法 //在数据量非常大的时候,可能会发生堆栈溢出错误。 // //堆排序会将所有的数据建成一个堆,最大的数据在堆顶 //然后将堆顶数据和序列的最后一个数据交换 //接下来再次重建堆,交换数据,依次下去,就可以排序所有的数据。