八大排序算法

转载处 http://blog.csdn.net/hguisu/article/details/7776068

概述

排序有内部排序和外部排序,内部排序是数据记录在内存中进行排序,而外部排序是因排序的数据很大,一次不能容纳全部的排序记录,在排序过程中需要访问外存。

我们这里说说八大排序就是内部排序。

八大排序算法

当n较大,则应采用时间复杂度为O(nlog2n)的排序方法:快速排序、堆排序或归并排序序。

快速排序:是目前基于比较的内部排序中被认为是最好的方法,当待排序的关键字是随机分布时,快速排序的平均时间最短;


1.插入排序—直接插入排序(Straight Insertion Sort)

基本思想:

将一个记录插入到已排序好的有序表中,从而得到一个新,记录数增1的有序表。即:先将序列的第1个记录看成是一个有序的子序列,然后从第2个记录逐个进行插入,直至整个序列有序为止。

要点:设立哨兵,作为临时存储和判断数组边界之用。

直接插入排序示例:

八大排序算法


如果碰见一个和插入元素相等的,那么插入元素把想插入的元素放在相等元素的后面。所以,相等元素的前后顺序没有改变,从原无序序列出去的顺序就是排好序后的顺序,所以插入排序是稳定的。

算法的实现:

  1. voidprint(inta[],intn,inti){
  2. cout<<i<<":";
  3. for(intj=0;j<8;j++){
  4. cout<<a[j]<<"";
  5. }
  6. cout<<endl;
  7. }
  8. voidInsertSort(inta[],intn)
  9. {
  10. for(inti=1;i<n;i++){
  11. if(a[i]<a[i-1]){//若第i个元素大于i-1元素,直接插入。小于的话,移动有序表后插入
  12. intj=i-1;
  13. intx=a[i];//复制为哨兵,即存储待排序元素
  14. a[i]=a[i-1];//先后移一个元素
  15. while(x<a[j]){//查找在有序表的插入位置
  16. a[j+1]=a[j];
  17. j--;//元素后移
  18. }
  19. a[j+1]=x;//插入到正确位置
  20. }
  21. print(a,n,i);//打印每趟排序的结果
  22. }
  23. }
  24. intmain(){
  25. inta[8]={3,1,5,7,2,4,9,6};
  26. InsertSort(a,8);
  27. print(a,8,8);
  28. }

效率:

时间复杂度:O(n^2).

其他的插入排序有二分插入排序,2-路插入排序。

2. 插入排序—希尔排序(Shell`s Sort)

希尔排序是1959 年由D.L.Shell 提出来的,相对直接排序有较大的改进。希尔排序又叫缩小增量排序

基本思想:

先将整个待排序的记录序列分割成为若干子序列分别进行直接插入排序,待整个序列中的记录“基本有序”时,再对全体记录进行依次直接插入排序。

操作方法:

  1. 选择一个增量序列t1,t2,…,tk,其中ti>tj,tk=1;
  2. 按增量序列个数k,对序列进行k 趟排序;
  3. 每趟排序,根据对应的增量ti,将待排序列分割成若干长度为m 的子序列,分别对各子表进行直接插入排序。仅增量因子为1 时,整个序列作为一个表来处理,表长度即为整个序列的长度。

希尔排序的示例:

八大排序算法


算法实现:

我们简单处理增量序列:增量序列d = {n/2 ,n/4, n/8 .....1}n为要排序数的个数

即:先将要排序的一组记录按某个增量dn/2,n为要排序数的个数)分成若干组子序列,每组中记录的下标相差d.对每组中全部元素进行直接插入排序,然后再用一个较小的增量(d/2)对它进行分组,在每组中再进行直接插入排序。继续不断缩小增量直至为1,最后使用直接插入排序完成排序。

  1. voidprint(inta[],intn,inti){
  2. cout<<i<<":";
  3. for(intj=0;j<8;j++){
  4. cout<<a[j]<<"";
  5. }
  6. cout<<endl;
  7. }
  8. /**
  9. *直接插入排序的一般形式
  10. *
  11. *@paramintdk缩小增量,如果是直接插入排序,dk=1
  12. *
  13. */
  14. voidShellInsertSort(inta[],intn,intdk)
  15. {
  16. for(inti=dk;i<n;++i){
  17. if(a[i]<a[i-dk]){//若第i个元素大于i-1元素,直接插入。小于的话,移动有序表后插入
  18. intj=i-dk;
  19. intx=a[i];//复制为哨兵,即存储待排序元素
  20. a[i]=a[i-dk];//首先后移一个元素
  21. while(x<a[j]){//查找在有序表的插入位置
  22. a[j+dk]=a[j];
  23. j-=dk;//元素后移
  24. }
  25. a[j+dk]=x;//插入到正确位置
  26. }
  27. print(a,n,i);
  28. }
  29. }
  30. /**
  31. *先按增量d(n/2,n为要排序数的个数进行希尔排序
  32. *
  33. */
  34. voidshellSort(inta[],intn){
  35. intdk=n/2;
  36. while(dk>=1){
  37. ShellInsertSort(a,n,dk);
  38. dk=dk/2;
  39. }
  40. }
  41. intmain(){
  42. inta[8]={3,1,5,7,2,4,9,6};
  43. //ShellInsertSort(a,8,1);//直接插入排序
  44. shellSort(a,8);//希尔插入排序
  45. print(a,8,8);
  46. }

希尔排序时效分析很难,关键码的比较次数与记录移动次数依赖于增量因子序列d的选取,特定情况下可以准确估算出关键码的比较次数和记录的移动次数。目前还没有人给出选取最好的增量因子序列的方法。增量因子序列可以有各种取法,有取奇数的,也有取质数的,但需要注意:增量因子中除1 外没有公因子,且最后一个增量因子必须为1。希尔排序方法是一个不稳定的排序方法。


3. 选择排序—简单选择排序(Simple Selection Sort)

基本思想:

在要排序的一组数中,选出最小(或者最大)的个数与第1个位置的数交换;然后在剩下的数当中再找最小(或者最大)的与第2个位置的数交换,依次类推,直到第n-1个元素(倒数第二个数)和第n个元素(最后个数)比较为止。

简单选择排序的示例:

八大排序算法

操作方法:

第一趟,从n 个记录中找出关键码最小的记录与第一个记录交换;

第二趟,从第二个记录开始的n-1 个记录中再选出关键码最小的记录与第二个记录交换;

以此类推.....

第i 趟,则从第i 个记录开始的n-i+1 个记录中选出关键码最小的记录与第i 个记录交换,

直到整个序列按关键码有序。


算法实现:

  1. voidprint(inta[],intn,inti){
  2. cout<<"第"<<i+1<<"趟:";
  3. for(intj=0;j<8;j++){
  4. cout<<a[j]<<"";
  5. }
  6. cout<<endl;
  7. }
  8. /**
  9. *数组的最小值
  10. *
  11. *@returnint数组的键值
  12. */
  13. intSelectMinKey(inta[],intn,inti)
  14. {
  15. intk=i;
  16. for(intj=i+1;j<n;++j){
  17. if(a[k]>a[j])k=j;
  18. }
  19. returnk;
  20. }
  21. /**
  22. *选择排序
  23. *
  24. */
  25. voidselectSort(inta[],intn){
  26. intkey,tmp;
  27. for(inti=0;i<n;++i){
  28. key=SelectMinKey(a,n,i);//选择最小的元素
  29. if(key!=i){
  30. tmp=a[i];a[i]=a[key];a[key]=tmp;//最小元素与第i位置元素互换
  31. }
  32. print(a,n,i);
  33. }
  34. }
  35. intmain(){
  36. inta[8]={3,1,5,7,2,4,9,6};
  37. cout<<"初始值:";
  38. for(intj=0;j<8;j++){
  39. cout<<a[j]<<"";
  40. }
  41. cout<<endl<<endl;
  42. selectSort(a,8);
  43. print(a,8,8);
  44. }

简单选择排序的改进——二元选择排序

简单选择排序,每趟循环只能确定一个元素排序后的定位。我们可以考虑改进为每趟循环确定两个元素(当前趟最大和最小记录)的位置,从而减少排序所需的循环次数。改进后对n个数据进行排序,最多只需进行[n/2]趟循环即可。具体实现如下:

  1. voidSelectSort(intr[],intn){
  2. inti,j,min,max,tmp;
  3. for(i=1;i<=n/2;i++){
  4. //做不超过n/2趟选择排序
  5. min=i;max=i;//分别记录最大和最小关键字记录位置
  6. for(j=i+1;j<=n-i;j++){
  7. if(r[j]>r[max]){
  8. max=j;continue;
  9. }
  10. if(r[j]<r[min]){
  11. min=j;
  12. }
  13. }
  14. //该交换操作还可分情况讨论以提高效率
  15. tmp=r[i-1];r[i-1]=r[min];r[min]=tmp;
  16. tmp=r[n-i];r[n-i]=r[max];r[max]=tmp;
  17. }
  18. }

4. 选择排序—堆排序(Heap Sort)

堆排序是一种树形选择排序,是对直接选择排序的有效改进。

基本思想:

堆的定义如下:具有n个元素的序列(k1,k2,...,kn),当且仅当满足

八大排序算法

时称之为堆。由堆的定义可以看出,堆顶元素(即第一个元素)必为最小项(小顶堆)。
若以一维数组存储一个堆,则堆对应一棵完全二叉树,且所有非叶结点的值均不大于(或不小于)其子女的值,根结点(堆顶元素)的值是最小(或最大)的。如:

(a)大顶堆序列:(96, 83,27,38,11,09)

(b) 小顶堆序列:(12,36,24,85,47,30,53,91)

八大排序算法


初始时把要排序的n个数的序列看作是一棵顺序存储的二叉树(一维数组存储二叉树),调整它们的存储序,使之成为一个堆,将堆顶元素输出,得到n 个元素中最小(或最大)的元素,这时堆的根节点的数最小(或者最大)。然后对前面(n-1)个元素重新调整使之成为堆,输出堆顶元素,得到n 个元素中次小(或次大)的元素。依此类推,直到只有两个节点的堆,并对它们作交换,最后得到有n个节点的有序序列。称这个过程为堆排序

因此,实现堆排序需解决两个问题:
1. 如何将n 个待排序的数建成堆;
2. 输出堆顶元素后,怎样调整剩余n-1 个元素,使其成为一个新堆。


首先讨论第二个问题:输出堆顶元素后,对剩余n-1元素重新建成堆的调整过程。
调整小顶堆的方法:

1)设有m 个元素的堆,输出堆顶元素后,剩下m-1 个元素。将堆底元素送入堆顶((最后一个元素与堆顶进行交换),堆被破坏,其原因仅是根结点不满足堆的性质。

2)将根结点与左、右子树中较小元素的进行交换。

3)若与左子树交换:如果左子树堆被破坏,即左子树的根结点不满足堆的性质,则重复方法 (2).

4)若与右子树交换,如果右子树堆被破坏,即右子树的根结点不满足堆的性质。则重复方法 (2).

5)继续对不满足堆性质的子树进行上述交换操作,直到叶子结点,堆被建成。

称这个自根结点到叶子结点的调整过程为筛选。如图:

八大排序算法


再讨论对n 个元素初始建堆的过程。
建堆方法:对初始序列建堆的过程,就是一个反复进行筛选的过程。

1)n 个结点的完全二叉树,则最后一个结点是第八大排序算法个结点的子树。

2)筛选从第八大排序算法个结点为根的子树开始,该子树成为堆。

3)之后向前依次对各结点为根的子树进行筛选,使之成为堆,直到根结点。

如图建堆初始过程:无序序列:(49,38,65,97,76,13,27,49)
八大排序算法


八大排序算法

算法的实现:

从算法描述来看,堆排序需要两个过程,一是建立堆,二是堆顶与堆的最后一个元素交换位置。所以堆排序有两个函数组成。一是建堆的渗透函数,二是反复调用渗透函数实现排序的函数。

  1. voidprint(inta[],intn){
  2. for(intj=0;j<n;j++){
  3. cout<<a[j]<<"";
  4. }
  5. cout<<endl;
  6. }
  7. /**
  8. *已知H[s…m]除了H[s]外均满足堆的定义
  9. *调整H[s],使其成为大顶堆.即将对第s个结点为根的子树筛选,
  10. *
  11. *@paramH是待调整的堆数组
  12. *@params是待调整的数组元素的位置
  13. *@paramlength是数组的长度
  14. *
  15. */
  16. voidHeapAdjust(intH[],ints,intlength)
  17. {
  18. inttmp=H[s];
  19. intchild=2*s+1;//左孩子结点的位置。(i+1为当前调整结点的右孩子结点的位置)
  20. while(child<length){
  21. if(child+1<length&&H[child]<H[child+1]){//如果右孩子大于左孩子(找到比当前待调整结点大的孩子结点)
  22. ++child;
  23. }
  24. if(H[s]<H[child]){//如果较大的子结点大于父结点
  25. H[s]=H[child];//那么把较大的子结点往上移动,替换它的父结点
  26. s=child;//重新设置s,即待调整的下一个结点的位置
  27. child=2*s+1;
  28. }else{//如果当前待调整结点大于它的左右孩子,则不需要调整,直接退出
  29. break;
  30. }
  31. H[s]=tmp;//当前待调整的结点放到比其大的孩子结点位置上
  32. }
  33. print(H,length);
  34. }
  35. /**
  36. *初始堆进行调整
  37. *将H[0..length-1]建成堆
  38. *调整完之后第一个元素是序列的最小的元素
  39. */
  40. voidBuildingHeap(intH[],intlength)
  41. {
  42. //最后一个有孩子的节点的位置i=(length-1)/2
  43. for(inti=(length-1)/2;i>=0;--i)
  44. HeapAdjust(H,i,length);
  45. }
  46. /**
  47. *堆排序算法
  48. */
  49. voidHeapSort(intH[],intlength)
  50. {
  51. //初始堆
  52. BuildingHeap(H,length);
  53. //从最后一个元素开始对序列进行调整
  54. for(inti=length-1;i>0;--i)
  55. {
  56. //交换堆顶元素H[0]和堆中最后一个元素
  57. inttemp=H[i];H[i]=H[0];H[0]=temp;
  58. //每次交换堆顶元素和堆中最后一个元素之后,都要对堆进行调整
  59. HeapAdjust(H,0,i);
  60. }
  61. }
  62. intmain(){
  63. intH[10]={3,1,5,7,2,4,9,6,10,8};
  64. cout<<"初始值:";
  65. print(H,10);
  66. HeapSort(H,10);
  67. //selectSort(a,8);
  68. cout<<"结果:";
  69. print(H,10);
  70. }


分析:

设树深度为k,八大排序算法。从根到叶的筛选,元素比较次数至多2(k-1)次,交换记录至多k 次。所以,在建好堆后,排序过程中的筛选次数不超过下式:

八大排序算法

而建堆时的比较次数不超过4n 次,因此堆排序最坏情况下,时间复杂度也为:O(nlogn )。

5. 交换排序—冒泡排序(Bubble Sort)

基本思想:

在要排序的一组数中,对当前还未排好序的范围内的全部数,自上而下对相邻的两个数依次进行比较和调整,让较大的数往下沉,较小的往上冒。即:每当两相邻的数比较后发现它们的排序与排序要求相反时,就将它们互换。

冒泡排序的示例:

八大排序算法

算法的实现:

  1. voidbubbleSort(inta[],intn){
  2. for(inti=0;i<n-1;++i){
  3. for(intj=0;j<n-i-1;++j){
  4. if(a[j]>a[j+1])
  5. {
  6. inttmp=a[j];a[j]=a[j+1];a[j+1]=tmp;
  7. }
  8. }
  9. }
  10. }


冒泡排序算法的改进

对冒泡排序常见的改进方法是加入一标志性变量exchange,用于标志某一趟排序过程中是否有数据交换,如果进行某一趟排序时并没有进行数据交换,则说明数据已经按要求排列好,可立即结束排序,避免不必要的比较过程。本文再提供以下两种改进算法:

1.设置一标志性变量pos,用于记录每趟排序中最后一次进行交换的位置。由于pos位置之后的记录均已交换到位,故在进行下一趟排序时只要扫描到pos位置即可。

改进后算法如下:

  1. voidBubble_1(intr[],intn){
  2. inti=n-1;//初始时,最后位置保持不变
  3. while(i>0){
  4. intpos=0;//每趟开始时,无记录交换
  5. for(intj=0;j<i;j++)
  6. if(r[j]>r[j+1]){
  7. pos=j;//记录交换的位置
  8. inttmp=r[j];r[j]=r[j+1];r[j+1]=tmp;
  9. }
  10. i=pos;//为下一趟排序作准备
  11. }
  12. }

2.传统冒泡排序中每一趟排序操作只能找到一个最大值或最小值,我们考虑利用在每趟排序中进行正向和反向两遍冒泡的方法一次可以得到两个最终值(最大者和最小者) ,从而使排序趟数几乎减少了一半。

改进后的算法实现为:

  1. voidBubble_2(intr[],intn){
  2. intlow=0;
  3. inthigh=n-1;//设置变量的初始值
  4. inttmp,j;
  5. while(low<high){
  6. for(j=low;j<high;++j)//正向冒泡,找到最大者
  7. if(r[j]>r[j+1]){
  8. tmp=r[j];r[j]=r[j+1];r[j+1]=tmp;
  9. }
  10. --high;//修改high值,前移一位
  11. for(j=high;j>low;--j)//反向冒泡,找到最小者
  12. if(r[j]<r[j-1]){
  13. tmp=r[j];r[j]=r[j-1];r[j-1]=tmp;
  14. }
  15. ++low;//修改low值,后移一位
  16. }
  17. }

6. 交换排序—快速排序(Quick Sort)

基本思想:

1)选择一个基准元素,通常选择第一个元素或者最后一个元素,

2)通过一趟排序讲待排序的记录分割成独立的两部分,其中一部分记录的元素值均比基准元素值小。另一部分记录的元素值比基准值大。

3)此时基准元素在其排好序后的正确位置

4)然后分别对这两部分记录用同样的方法继续进行排序,直到整个序列有序。

快速排序的示例:

(a)一趟排序的过程:

八大排序算法

(b)排序的全过程

八大排序算法

算法的实现:

递归实现:

  1. voidprint(inta[],intn){
  2. for(intj=0;j<n;j++){
  3. cout<<a[j]<<"";
  4. }
  5. cout<<endl;
  6. }
  7. voidswap(int*a,int*b)
  8. {
  9. inttmp=*a;
  10. *a=*b;
  11. *b=tmp;
  12. }
  13. intpartition(inta[],intlow,inthigh)
  14. {
  15. intprivotKey=a[low];//基准元素
  16. while(low<high){//从表的两端交替地向中间扫描
  17. while(low<high&&a[high]>=privotKey)--high;//从high所指位置向前搜索,至多到low+1位置。将比基准元素小的交换到低端
  18. swap(&a[low],&a[high]);
  19. while(low<high&&a[low]<=privotKey)++low;
  20. swap(&a[low],&a[high]);
  21. }
  22. print(a,10);
  23. returnlow;
  24. }
  25. voidquickSort(inta[],intlow,inthigh){
  26. if(low<high){
  27. intprivotLoc=partition(a,low,high);//将表一分为二
  28. quickSort(a,low,privotLoc-1);//递归对低子表递归排序
  29. quickSort(a,privotLoc+1,high);//递归对高子表递归排序
  30. }
  31. }
  32. intmain(){
  33. inta[10]={3,1,5,7,2,4,9,6,10,8};
  34. cout<<"初始值:";
  35. print(a,10);
  36. quickSort(a,0,9);
  37. cout<<"结果:";
  38. print(a,10);
  39. }


分析:

快速排序是通常被认为在同数量级(O(nlog2n))的排序方法中平均性能最好的。但若初始序列按关键码有序或基本有序时,快排序反而蜕化为冒泡排序。为改进之,通常以“三者取中法”来选取基准记录,即将排序区间的两个端点与中点三个记录关键码居中的调整为支点记录。快速排序是一个不稳定的排序方法。


快速排序的改进

在本改进算法中,只对长度大于k的子序列递归调用快速排序,让原序列基本有序,然后再对整个基本有序序列用插入排序算法排序。实践证明,改进后的算法时间复杂度有所降低,且当k取值为8左右时,改进算法的性能最佳。算法思想如下:

  1. voidprint(inta[],intn){
  2. for(intj=0;j<n;j++){
  3. cout<<a[j]<<"";
  4. }
  5. cout<<endl;
  6. }
  7. voidswap(int*a,int*b)
  8. {
  9. inttmp=*a;
  10. *a=*b;
  11. *b=tmp;
  12. }
  13. intpartition(inta[],intlow,inthigh)
  14. {
  15. intprivotKey=a[low];//基准元素
  16. while(low<high){//从表的两端交替地向中间扫描
  17. while(low<high&&a[high]>=privotKey)--high;//从high所指位置向前搜索,至多到low+1位置。将比基准元素小的交换到低端
  18. swap(&a[low],&a[high]);
  19. while(low<high&&a[low]<=privotKey)++low;
  20. swap(&a[low],&a[high]);
  21. }
  22. print(a,10);
  23. returnlow;
  24. }
  25. voidqsort_improve(intr[],intlow,inthigh,intk){
  26. if(high-low>k){//长度大于k时递归,k为指定的数
  27. intpivot=partition(r,low,high);//调用的Partition算法保持不变
  28. qsort_improve(r,low,pivot-1,k);
  29. qsort_improve(r,pivot+1,high,k);
  30. }
  31. }
  32. voidquickSort(intr[],intn,intk){
  33. qsort_improve(r,0,n,k);//先调用改进算法Qsort使之基本有序
  34. //再用插入排序对基本有序序列排序
  35. for(inti=1;i<=n;i++){
  36. inttmp=r[i];
  37. intj=i-1;
  38. while(tmp<r[j]){
  39. r[j+1]=r[j];j=j-1;
  40. }
  41. r[j+1]=tmp;
  42. }
  43. }
  44. intmain(){
  45. inta[10]={3,1,5,7,2,4,9,6,10,8};
  46. cout<<"初始值:";
  47. print(a,10);
  48. quickSort(a,9,4);
  49. cout<<"结果:";
  50. print(a,10);
  51. }


7. 归并排序(Merge Sort)


基本思想:

归并(Merge)排序法是将两个(或两个以上)有序表合并成一个新的有序表,即把待排序序列分为若干个子序列,每个子序列是有序的。然后再把有序子序列合并为整体有序序列。

归并排序示例:

八大排序算法


合并方法:

设r[i…n]由两个有序子表r[i…m]和r[m+1…n]组成,两个子表长度分别为n-i +1、n-m

  1. j=m+1;k=i;i=i; //置两个子表的起始下标及辅助数组的起始下标
  2. 若i>m 或j>n,转⑷ //其中一个子表已合并完,比较选取结束
  3. //选取r[i]和r[j]较小的存入辅助数组rf
    如果r[i]<r[j],rf[k]=r[i]; i++; k++; 转⑵
    否则,rf[k]=r[j]; j++; k++; 转⑵
  4. //将尚未处理完的子表中元素存入rf
    如果i<=m,将r[i…m]存入rf[k…n] //前一子表非空
    如果j<=n , 将r[j…n] 存入rf[k…n] //后一子表非空
  5. 合并结束。
  1. //将r[i…m]和r[m+1…n]归并到辅助数组rf[i…n]
  2. voidMerge(ElemType*r,ElemType*rf,inti,intm,intn)
  3. {
  4. intj,k;
  5. for(j=m+1,k=i;i<=m&&j<=n;++k){
  6. if(r[j]<r[i])rf[k]=r[j++];
  7. elserf[k]=r[i++];
  8. }
  9. while(i<=m)rf[k++]=r[i++];
  10. while(j<=n)rf[k++]=r[j++];
  11. }


归并的迭代算法


1 个元素的表总是有序的。所以对n 个元素的待排序列,每个元素可看成1 个有序子表。对子表两两合并生成n/2个子表,所得子表除最后一个子表长度可能为1 外,其余子表长度均为2。再进行两两合并,直到生成n 个元素按关键码有序的表。

  1. voidprint(inta[],intn){
  2. for(intj=0;j<n;j++){
  3. cout<<a[j]<<"";
  4. }
  5. cout<<endl;
  6. }
  7. //将r[i…m]和r[m+1…n]归并到辅助数组rf[i…n]
  8. voidMerge(ElemType*r,ElemType*rf,inti,intm,intn)
  9. {
  10. intj,k;
  11. for(j=m+1,k=i;i<=m&&j<=n;++k){
  12. if(r[j]<r[i])rf[k]=r[j++];
  13. elserf[k]=r[i++];
  14. }
  15. while(i<=m)rf[k++]=r[i++];
  16. while(j<=n)rf[k++]=r[j++];
  17. print(rf,n+1);
  18. }
  19. voidMergeSort(ElemType*r,ElemType*rf,intlenght)
  20. {
  21. intlen=1;
  22. ElemType*q=r;
  23. ElemType*tmp;
  24. while(len<lenght){
  25. ints=len;
  26. len=2*s;
  27. inti=0;
  28. while(i+len<lenght){
  29. Merge(q,rf,i,i+s-1,i+len-1);//对等长的两个子表合并
  30. i=i+len;
  31. }
  32. if(i+s<lenght){
  33. Merge(q,rf,i,i+s-1,lenght-1);//对不等长的两个子表合并
  34. }
  35. tmp=q;q=rf;rf=tmp;//交换q,rf,以保证下一趟归并时,仍从q归并到rf
  36. }
  37. }
  38. intmain(){
  39. inta[10]={3,1,5,7,2,4,9,6,10,8};
  40. intb[10];
  41. MergeSort(a,b,10);
  42. print(b,10);
  43. cout<<"结果:";
  44. print(a,10);
  45. }

两路归并的递归算法

  1. voidMSort(ElemType*r,ElemType*rf,ints,intt)
  2. {
  3. ElemType*rf2;
  4. if(s==t)r[s]=rf[s];
  5. else
  6. {
  7. intm=(s+t)/2;/*平分*p表*/
  8. MSort(r,rf2,s,m);/*递归地将p[s…m]归并为有序的p2[s…m]*/
  9. MSort(r,rf2,m+1,t);/*递归地将p[m+1…t]归并为有序的p2[m+1…t]*/
  10. Merge(rf2,rf,s,m+1,t);/*将p2[s…m]和p2[m+1…t]归并到p1[s…t]*/
  11. }
  12. }
  13. voidMergeSort_recursive(ElemType*r,ElemType*rf,intn)
  14. {/*对顺序表*p作归并排序*/
  15. MSort(r,rf,0,n-1);
  16. }

8. 桶排序/基数排序(Radix Sort)

说基数排序之前,我们先说桶排序:

基本思想:是将阵列分到有限数量的桶子里。每个桶子再个别排序(有可能再使用别的排序算法或是以递回方式继续使用桶排序进行排序)。桶排序是鸽巢排序的一种归纳结果。当要被排序的阵列内的数值是均匀分配的时候,桶排序使用线性时间(Θ(n))。但桶排序并不是 比较排序,他不受到 O(n log n) 下限的影响。
简单来说,就是把数据分组,放在一个个的桶中,然后对每个桶里面的在进行排序。

例如要对大小为[1..1000]范围内的n个整数A[1..n]排序

首先,可以把桶设为大小为10的范围,具体而言,设集合B[1]存储[1..10]的整数,集合B[2]存储 (10..20]的整数,……集合B[i]存储( (i-1)*10, i*10]的整数,i = 1,2,..100。总共有 100个桶。

然后,对A[1..n]从头到尾扫描一遍,把每个A[i]放入对应的桶B[j]中。 再对这100个桶中每个桶里的数字排序,这时可用冒泡,选择,乃至快排,一般来说任 何排序法都可以。

最后,依次输出每个桶里面的数字,且每个桶中的数字从小到大输出,这 样就得到所有数字排好序的一个序列了。

假设有n个数字,有m个桶,如果数字是平均分布的,则每个桶里面平均有n/m个数字。如果

对每个桶中的数字采用快速排序,那么整个算法的复杂度是

O(n + m * n/m*log(n/m)) = O(n + nlogn - nlogm)

从上式看出,当m接近n的时候,桶排序复杂度接近O(n)

当然,以上复杂度的计算是基于输入的n个数字是平均分布这个假设的。这个假设是很强的 ,实际应用中效果并没有这么好。如果所有的数字都落在同一个桶中,那就退化成一般的排序了。

前面说的几大排序算法 ,大部分时间复杂度都是O(n2),也有部分排序算法时间复杂度是O(nlogn)。而桶式排序却能实现O(n)的时间复杂度。但桶排序的缺点是:

1)首先是空间复杂度比较高,需要的额外开销大。排序有两个数组的空间开销,一个存放待排序数组,一个就是所谓的桶,比如待排序值是从0到m-1,那就需要m个桶,这个桶数组就要至少m个空间。

2)其次待排序的元素都要在一定的范围内等等。

桶式排序是一种分配排序。分配排序的特定是不需要进行关键码的比较,但前提是要知道待排序列的一些具体情况。


分配排序的基本思想:说白了就是进行多次的桶式排序。

基数排序过程无须比较关键字,而是通过“分配”和“收集”过程来实现排序。它们的时间复杂度可达到线性阶:O(n)。

实例:

扑克牌中52 张牌,可按花色和面值分成两个字段,其大小关系为:
花色:梅花< 方块< 红心< 黑心 八大排序算法
面值:2 < 3 < 4 < 5 < 6 < 7 < 8 < 9 < 10 < J < Q < K < A

若对扑克牌按花色、面值进行升序排序,得到如下序列:
八大排序算法

八大排序算法


即两张牌,若花色不同,不论面值怎样,花色低的那张牌小于花色高的,只有在同花况下,大小关系才由面值的大小确定。这就是多关键码排序。

为得到排序结果,我们讨论两种排序方法。
方法1:先对花色排序,将其分为4 个组,即梅花组、方块组、红心组、黑心组。再对每个组分别按面值进行排序,最后,将4 个组连接起来即可。
方法2:先按13 个面值给出13 个编号组(2 号,3 号,...,A 号),将牌按面值依次放入对应的编号组,分成13 堆。再按花色给出4 个编号组(梅花、方块、红心、黑心),将2号组中牌取出分别放入对应花色组,再将3 号组中牌取出分别放入对应花色组,……,这样,4 个花色组中均按面值有序,然后,将4 个花色组依次连接起来即可。

设n 个元素的待排序列包含d 个关键码{k1,k2,…,kd},则称序列对关键码{k1,k2,…,kd}有序是指:对于序列中任两个记录r[i]和r[j](1≤i≤j≤n)都满足下列有序关系:

八大排序算法

其中k1 称为最主位关键码,kd 称为最次位关键码。

两种多关键码排序方法:

多关键码排序按照从最主位关键码到最次位关键码或从最次位到最主位关键码的顺序逐次排序,分两种方法:

最高位优先(Most Significant Digit first)法,简称MSD 法

1)先按k1 排序分组,将序列分成若干子序列,同一组序列的记录中,关键码k1 相等。

2)再对各组按k2 排序分成子组,之后,对后面的关键码继续这样的排序分组,直到按最次位关键码kd 对各子组排序后。

3)再将各组连接起来,便得到一个有序序列。扑克牌按花色、面值排序中介绍的方法一即是MSD 法。

最低位优先(Least Significant Digit first)法,简称LSD 法

1) 先从kd 开始排序,再对kd-1进行排序,依次重复,直到按k1排序分组分成最小的子序列后。

2) 最后将各个子序列连接起来,便可得到一个有序的序列, 扑克牌按花色、面值排序中介绍的方法二即是LSD 法。


基于LSD方法的链式基数排序的基本思想

  “多关键字排序”的思想实现“单关键字排序”。对数字型或字符型的单关键字,可以看作由多个数位或多个字符构成的多关键字,此时可以采用“分配-收集”的方法进行排序,这一过程称作基数排序法,其中每个数字或字符可能的取值个数称为基数。比如,扑克牌的花色基数为4,面值基数为13。在整理扑克牌时,既可以先按花色整理,也可以先按面值整理。按花色整理时,先按红、黑、方、花的顺序分成4摞(分配),再按此顺序再叠放在一起(收集),然后按面值的顺序分成13摞(分配),再按此顺序叠放在一起(收集),如此进行二次分配和收集即可将扑克牌排列有序。

基数排序:

是按照低位先排序,然后收集;再按照高位排序,然后再收集;依次类推,直到最高位。有时候有些属性是有优先级顺序的,先按低优先级排序,再按高优先级排序。最后的次序就是高优先级高的在前,高优先级相同的低优先级高的在前。基数排序基于分别排序,分别收集,所以是稳定的。

算法实现:

  1. VoidRadixSort(NodeL[],length,maxradix)
  2. {
  3. intm,n,k,lsp;
  4. k=1;m=1;
  5. inttemp[10][length-1];
  6. Empty(temp);//清空临时空间
  7. while(k<maxradix)//遍历所有关键字
  8. {
  9. for(inti=0;i<length;i++)//分配过程
  10. {
  11. if(L[i]<m)
  12. Temp[0][n]=L[i];
  13. else
  14. Lsp=(L[i]/m)%10;//确定关键字
  15. Temp[lsp][n]=L[i];
  16. n++;
  17. }
  18. CollectElement(L,Temp);//收集
  19. n=0;
  20. m=m*10;
  21. k++;
  22. }
  23. }





总结

各种排序的稳定性,时间复杂度和空间复杂度总结:

八大排序算法

我们比较时间复杂度函数的情况:

八大排序算法


时间复杂度函数O(n)的增长情况

八大排序算法

所以对n较大的排序记录。一般的选择都是时间复杂度为O(nlog2n)的排序方法。


时间复杂度来说:

(1)平方阶(O(n2))排序
  各类简单排序:直接插入、直接选择和冒泡排序;
(2)线性对数阶(O(nlog2n))排序
  快速排序堆排序归并排序
(3)O(n1+§))排序,§是介于0和1之间的常数。

希尔排序
(4)线性阶(O(n))排序
  基数排序,此外还有桶、箱排序。

说明:

当原表有序或基本有序时,直接插入排序和冒泡排序将大大减少比较次数和移动记录的次数,时间复杂度可降至On);

而快速排序则相反,当原表基本有序时,将蜕化为冒泡排序,时间复杂度提高为On2);

原表是否有序,对简单选择排序、堆排序、归并排序和基数排序的时间复杂度影响不大。

稳定性:

排序算法的稳定性:若待排序的序列中,存在多个具有相同关键字的记录,经过排序, 这些记录的相对次序保持不变,则称该算法是稳定的;若经排序后,记录的相对 次序发生了改变,则称该算法是不稳定的。
稳定性的好处:排序算法如果是稳定的,那么从一个键上排序,然后再从另一个键上排序,第一个键排序的结果可以为第二个键排序所用。基数排序就是这样,先按低位排序,逐次按高位排序,低位相同的元素其顺序再高位也相同时是不会改变的。另外,如果排序算法稳定,可以避免多余的比较;

稳定的排序算法冒泡排序、插入排序、归并排序和基数排序

不是稳定的排序算法:选择排序、快速排序、希尔排序、堆排序

选择排序算法准则:

每种排序算法都各有优缺点。因此,在实用时需根据不同情况适当选用,甚至可以将多种方法结合起来使用。

选择排序算法的依据

影响排序的因素有很多,平均时间复杂度低的算法并不一定就是最优的。相反,有时平均时间复杂度高的算法可能更适合某些特殊情况。同时,选择算法时还得考虑它的可读性,以利于软件的维护。一般而言,需要考虑的因素有以下四点:

1.待排序的记录数目n的大小;

2.记录本身数据量的大小,也就是记录中除关键字外的其他信息量的大小;

3.关键字的结构及其分布情况;

4.对排序稳定性的要求。

设待排序元素的个数为n.

1)当n较大,则应采用时间复杂度为O(nlog2n)的排序方法:快速排序、堆排序或归并排序序。

快速排序:是目前基于比较的内部排序中被认为是最好的方法,当待排序的关键字是随机分布时,快速排序的平均时间最短;
堆排序: 如果内存空间允许且要求稳定性的,

归并排序:它有一定数量的数据移动,所以我们可能过与插入排序组合,先获得一定长度的序列,然后再合并,在效率上将有所提高。

2) 当n较大,内存空间允许,且要求稳定性 =》归并排序

3)当n较小,可采用直接插入或直接选择排序。

直接插入排序:当元素分布有序,直接插入排序将大大减少比较次数和移动记录的次数。

直接选择排序 :元素分布有序,如果不要求稳定性,选择直接选择排序

5)一般不使用或不直接使用传统的冒泡排序。

6)基数排序
它是一种稳定的排序算法,但有一定的局限性:
  1、关键字可分解。

  2
、记录的关键字位数较少,如果密集更好
  3、如果是数字时,最好是无符号的,否则将增加相应的映射复杂度,可先将其正负分开排序。

注明:转载请提示出处:http://blog.csdn.net/hguisu/article/details/7776068

我的同类文章

c/c++(22)数据结构与算法(29)
更多文章
猜你在找
数据结构基础系列(6):树和二叉树 iOS开发高级专题—数据存储 数据结构和算法 有趣的算法(数据结构) 老郭全套iOS开发课程【Objective-C】
八大排序算法 Java 语言实现的八大排序算法 八大排序算法 疯狂java之学习笔记9---------------八大排序算法 总结八大排序算法
<iframe id="iframeu1607657_0" src="http://pos.baidu.com/tcnm?sz=728x90&amp;rdid=1607657&amp;dc=2&amp;di=u1607657&amp;dri=0&amp;dis=0&amp;dai=2&amp;ps=21117x237&amp;coa=at%3D3%26rsi0%3D728%26rsi1%3D90%26pat%3D6%26tn%3DbaiduCustNativeAD%26rss1%3D%2523FFFFFF%26conBW%3D1%26adp%3D1%26ptt%3D0%26titFF%3D%2525E5%2525BE%2525AE%2525E8%2525BD%2525AF%2525E9%25259B%252585%2525E9%2525BB%252591%26titFS%3D%26rss2%3D%2523000000%26titSU%3D0%26ptbg%3D90%26piw%3D0%26pih%3D0%26ptp%3D0&amp;dcb=BAIDU_SSP_define&amp;dtm=BAIDU_DUP_SETJSONADSLOT&amp;dvi=0.0&amp;dci=-1&amp;dpt=none&amp;tsr=0&amp;tpr=1460943749571&amp;ti=%E5%85%AB%E5%A4%A7%E6%8E%92%E5%BA%8F%E7%AE%97%E6%B3%95%20-%20guisu%EF%BC%8C%E7%A8%8B%E5%BA%8F%E4%BA%BA%E7%94%9F%E3%80%82%20%E9%80%86%E6%B0%B4%E8%A1%8C%E8%88%9F%EF%BC%8C%E4%B8%8D%E8%BF%9B%E5%88%99%E9%80%80%E3%80%82%20-%20%E5%8D%9A%E5%AE%A2%E9%A2%91%E9%81%93%20-%20CSDN.NET&amp;ari=1&amp;dbv=0&amp;drs=3&amp;pcs=1012x661&amp;pss=1012x21728&amp;cfv=21&amp;cpl=12&amp;chi=1&amp;cce=true&amp;cec=UTF-8&amp;tlm=1460914949&amp;ltu=http%3A%2F%2Fblog.csdn.net%2Fhguisu%2Farticle%2Fdetails%2F7776068&amp;ltr=https%3A%2F%2Fwww.baidu.com%2Flink%3Furl%3DJoLR94q-Q2Gj3QhihdgtHU_azNtQak5-LXdXSC9bQnR13r65ADRnkoWAtiPzsPfCtgJx9mS0It5T7PJkLp0zs_%26wd%3D%26eqid%3Dc717aebc003e14e40000000357143ab0&amp;ecd=1&amp;psr=1280x800&amp;par=1280x722&amp;pis=-1x-1&amp;ccd=24&amp;cja=true&amp;cmi=35&amp;col=zh-cn&amp;cdo=-1&amp;tcn=1460943750&amp;qn=f1c9560e6f14e3c7&amp;tt=1460943749560.21.436.437" width="728" height="90" align="center,center" vspace="0" hspace="0" marginwidth="0" marginheight="0" scrolling="no" frameborder="0" allowtransparency="true" style="border-width: 0px; vertical-align: bottom; margin: 0px;"></iframe>
查看评论
66楼fw19932016-04-08 00:21发表 [回复]
八大排序算法
楼主 归并排序 的递归实现里
ElemType *rf2;
rf2 是不是存的临时结果啊,这样不申请空间不会有内存问题吗?

是不是要写成 rf2 = new ElemType....?
65楼fw19932016-04-08 00:20发表 [回复]
八大排序算法
楼主 归并排序 的递归实现里
ElemType *rf2;
rf2 是不是存的临时结果啊,这样不申请空间不会有内存问题吗?

是不是要写成 rf2 = new ElemType....?
64楼qq_251985732016-04-07 21:10发表 [回复]
八大排序算法
楼主,貌似堆排序中的
int child = 2*s+1;错了吧,该语句已经将child指向右节点了。
我认为应该把所有child下滤的语句改成child=child*2;同时初始化中将child=2*s,确保child指向的是左节点。

int temp=arr[i];
int child=i*2;
while(child<len)
{
if(child+1<len&&arr[child]>arr[child+1])
child++;
if(arr[i]>arr[child])
{
arr[i]=arr[child];
i=child;
child=child*2;
}
else break;
arr[i]=temp;
}
代码如上;
63楼huangyue20142016-04-06 14:49发表 [回复]
八大排序算法
插入排序:
/**
* 插入排序
*/
public static void insertSort()
{
int[] arrs = {49,38,65,97,76,13,27};
for(int i=1;i<arrs.length;i++)
{
if(arrs[i] >= arrs[i-1])
continue;
else
{
int k = 0;
while(i-1>=0 && arrs[i] < arrs[i-1])
{
int temp = arrs[i];
arrs[i] = arrs[i-1];
arrs[i-1] = temp;
i--;
k++;
}
i=i+k;
}
}
}
62楼maowei_1232016-03-19 11:17发表 [回复]
八大排序算法
选择排序的改进-二元选择排序写错了,知识因为博客的给的数组恰好掩盖了错误,这是我正确的代码,用java实现:
public class Main
public static void main(String[] args){
int[] a = {8,2,5,4,1,9,10,3,7,12,6,4,123,5,32,12234};
SelectSort(a, a.length);
}
public static void SelectSort(int r[], int n){
int i ,j , min ,max, tmp;
for (i=0 ;i <= n/2;i++) {
// 做不超过n/2趟选择排序
min = i; max = n-i-1; //分别记录最大和最小关键字记录位置
for (j= i+1; j< n-i; j++) {
if (r[j] > r[max]) {
max = j ;
continue ;
}
if (r[j]< r[min]) {
min = j ;
}
}
//该交换操作还可分情况讨论以提高效率
tmp = r[i]; r[i] = r[min]; r[min] = tmp;
tmp = r[n - i -1]; r[n - i -1] = r[max]; r[max] = tmp;
print(r); //打印每趟排序的结果
}
}
public static void print(int[] a){
for (int i : a) {
System.out.print(i + " ");
}
System.out.println("");
}
}
61楼饕餮的阿周2016-03-15 17:47发表 [回复]
八大排序算法
太给力了,谢谢楼主
60楼zcxyl2016-03-15 11:33发表 [回复]
八大排序算法
楼主总结的很棒。之前没仔细研究过堆排序,看了以后发现堆排序性能很高。
堆排序那节的堆调整函数,我感觉这样写会更简洁易懂
  1. #defineSWAP(x,y)do\
  2. {\
  3. inttmp=x;\
  4. x=y;\
  5. y=tmp;\
  6. }while(0)
  7. #defineL_CHILD(2*(root)+1)
  8. #defineR_CHILD(2*(root)+2)
  9. staticvoidheap_adjust(int*heap,introot,intlen)
  10. {
  11. if(L_CHILD>=len)
  12. return;
  13. intswap=L_CHILD;
  14. if(R_CHILD<len){
  15. swap=(heap[L_CHILD]<heap[R_CHILD])?L_CHILD:R_CHILD;
  16. }
  17. if(heap[root]>heap[swap]){
  18. SWAP(heap[root],heap[swap]);
  19. heap_adjust(heap,swap,len);
  20. }
  21. }
59楼行者小朱2016-03-07 16:26发表 [回复]
八大排序算法
楼主总结的不错
58楼lst6662016-02-25 12:15发表 [回复]
八大排序算法
+1
57楼KingXLee2016-02-24 17:53发表 [回复]
八大排序算法
很好~~
56楼fangziqh2016-02-15 12:02发表 [回复]
八大排序算法
你好,我今天试了一下你的快速排序改进算法,其中j的范围没有保证,应该有一个判断条件,j>=0
55楼麦兜NC粉2016-01-09 11:52发表 [回复]
八大排序算法
都没人说说堆排序吗 ?
HeapAdjust(int H[],int s, int length) 中 ,H[s] = tmp;应该放在while循环的外面吧
Re:zcxyl2016-03-15 11:40发表 [回复]
八大排序算法
回复best1196:楼主没写错
54楼lushangqiushui2015-12-16 15:23发表 [回复]
八大排序算法
5.2改进冒泡好像会越界,具体没验证,我自己是这样写的:
void BubbleSort2(int aBuf[], int nLen)
{
bool bEnd=true;

int nLow=0;
int nHigh=nLen-1;
int nTmp=0;

while(nLow < nHigh)
{
bEnd=false;

for(int nIdx=nLow; nIdx<nHigh-1; ++nIdx)
{
if(aBuf[nIdx]>aBuf[nIdx+1])
{
nTmp=aBuf[nIdx];
aBuf[nIdx]=aBuf[nIdx+1];
aBuf[nIdx+1]=nTmp;

bEnd=false;
}
}

for(int nIdx=nHigh; nIdx>nLow+1; --nIdx)
{
if(aBuf[nIdx]<aBuf[nIdx-1])
{
nTmp=aBuf[nIdx];
aBuf[nIdx]=aBuf[nIdx-1];
aBuf[nIdx-1]=nTmp;

bEnd=false;
}
}

--nHigh;
++nLow;

if(bEnd) break;
}
}

自己验证没问题,旁观者清,我可能会当局者迷,不知楼主有没有其他见解或指教?
53楼baidu_291625492015-12-13 14:17发表 [回复]
八大排序算法
排序中一和二是否有点出入,while循环内是否应该加一个if判断语句
52楼三目君2015-11-11 15:37发表 [回复]
八大排序算法
快速排序是原址排序,它的空间复杂度应当是O(1)。
1. 你的快速排序源码中,辅助空间仅有一个int privotKey = a[low]; 是常数个数。
2. 参考《算法导论(第3版)》82页中文版原文:“第7章介绍快速排序,它也是一种原址排序算法”
51楼mayfla2015-10-26 14:24发表 [回复]
八大排序算法
总结好用心啊,一直有一个疑问,排序在计算机中占了什么样的位置,为什么要有排序呢?
Re:吃不够的圆儿宝2015-10-30 15:11发表 [回复]
八大排序算法
回复mayfla:排序是一切算法的基础,比如你要二分查找,那么就必须先排序,然后对排序数组进行二分查找
50楼丁国华2015-10-22 16:57发表 [回复]
八大排序算法
感谢楼主的分享,学习了`(*∩_∩*)′
49楼杨晨光2015-10-11 11:39发表 [回复]
八大排序算法
总结的相当NB啊,楼主
48楼Honei_X2015-10-10 00:32发表 [回复]
八大排序算法
写的挺好的,难得把这么多的排序算法都写了一下。挺辛苦的。
47楼Honei_X2015-10-10 00:32发表 [回复]
八大排序算法
写的挺好的,难得把这么多的排序算法都写了一下。挺辛苦的。
46楼tiger3c2015-10-09 14:27发表 [回复]
八大排序算法
插入排序中j--会导致数组下标出现-1,如果数组中最后几位有负数则无法排序。改正后的代码如下:
void InsertSort(int a[], int n)
{
for(int i= 1; i<n; i++){
if(a[i] < a[i-1]){ //若第i个元素大于i-1元素,直接插入。小于的话,移动有序表后插入
int j= i-1;
int x = a[i]; //复制为哨兵,即存储待排序元素
a[i] = a[i-1]; //先后移一个元素
cout<<"a[i-1]:"<<a[i-1]<<endl;
while(x < a[j] ){ //查找在有序表的插入位置
a[j+1] = a[j];
j--; //元素后移
if(j < 0) break; //防止下标出现-1仍然继续while循环会导致程序崩溃当数组内容类似为:{3,2,5,7,1,-4,8,6}时
}
a[j+1] = x; //插入到正确位置
}
print(a,n,i); //打印每趟排序的结果
}

}
Re:lst6662016-02-25 12:14发表 [回复]
八大排序算法
回复tiger3c:a[-1]也不是不行,但是最好不这样,就是内存中一般这个位置一般是垃圾,读出来是个负数。所以,这段实现勉强能运行,如果先a[-1]=100,那这个程序结果就是错的。
while( j>=0 && x < a[j]) 可以这样改下,测试可用。
Re:书生起义2015-12-04 09:33发表 [回复]
八大排序算法
回复tiger3c:你的不对吧,
45楼唐凌峰2015-10-05 11:13发表 [回复]
八大排序算法
在学习中,感谢你的分享
44楼箫沐予2015-09-17 12:05发表 [回复]
八大排序算法
蛮好的,有参照。对于插入排序,因为我是用JS来仿写各种算法,所以while(x<a[j]) 这点上,反而很方便正确。因为当j减到-1后,a[-1]等于undefinded, x<a[j]判断的结果为false,不会再进入循环。
43楼jingzhemodao2015-09-11 10:24发表 [回复]
八大排序算法
你的插入排序写错了,知道不。
42楼gs00382015-09-10 17:53发表 [回复]
八大排序算法
冒泡写错了
for(int i =0 ; i< n-1; ++i) {
for(int j = i; j < n-i-1; ++j) {
if(a[i] > a[j+1])
{
int tmp = a[i] ; a[i] = a[j+1] ; a[j+1] = tmp;
}
}
}
这样子才对~
Re:tiger3c2015-10-09 15:19发表 [回复]
八大排序算法
回复gs0038:按照你的代码请试着排一下如下数组:
int a[8] = {3,2,5,7,1,4,8,6};

运行结果是:1 2 3 5 7 4 8 6显然是错误的
Re:tiger3c2015-10-09 15:14发表 [回复]
八大排序算法
回复gs0038:楼主的程序没错,你提供的修改中代码中既有外循环的下标i又有内循环的下标j好乱,是否有验证过再发表意见?
正确的代码就是:
void bubbleSort(int a[], int n){
for(int i =0 ; i< n-1; ++i) {
for(int j = 0; j < n-i-1; ++j) {
if(a[j] > a[j+1])
{
int tmp = a[j] ; a[j] = a[j+1] ; a[j+1] = tmp;
}
}
}
}
41楼zfxrj2015-08-30 16:36发表 [回复]
八大排序算法
看了第一个就不想看了!
40楼Sunshine_Yin2015-08-05 16:02发表 [回复]
八大排序算法
选择排序while()里应该加一个条件,j>-1,不然程序不对
39楼欢乐的工科小硕2015-07-27 19:49发表 [回复]
八大排序算法
mark 来 看一下
38楼nihaoljq20102015-07-12 17:39发表 [回复]
八大排序算法
感谢楼主分享!
我将每个算法运行了一遍,其中nlogn算法用亿级数量级;
已全部实现。
中间一些算法有误,提示一下楼主:
如归并算法,在待排序数量大于1万时候排序结果会有问题。

修改前: tmp = q; q = rf; rf = tmp;
修改后:
1、MergeSort中,先将rf初始化
for(int k=0;k<lenArr;k++)
rf[k]=r[k];
2、MergeSort中,将tmp = q; q = rf; rf = tmp; 语句修改为
for(int k=0;k<lenArr;k++)
q[k]=rf[k];
Re:wzysws2015-09-06 17:09发表 [回复]
八大排序算法
回复nihaoljq2010:楼主改得好,赞一个。
Re:cpu_125932015-08-30 11:15发表 [回复]
八大排序算法
回复nihaoljq2010:博主的归并迭代排序是有问题,当归并的次数是奇数是排序的结果保存在rf中,当为偶数时结果才在r中。因此在迭代的最后不应该交换指针,而是交换元素的值。
Re:wzysws2015-09-06 17:08发表 [回复]
八大排序算法
回复cpu_12593:跟归并的次数无关,而是跟指针交换的次数。交换偶数次存rf中,奇数次存r中。
37楼刘小壮2015-06-23 12:00发表 [回复]
八大排序算法
  1. voidsort1()
  2. {
  3. inta[]={3,5,67,14,4,6,1,7};
  4. for(inti=1;i<8;i++){
  5. if(a[i]<a[i-1]){
  6. intj=i-1;
  7. intx=a[i];
  8. a[i]=a[i-1];
  9. while(x<a[j-1]){
  10. a[j]=a[j-1];
  11. j--;
  12. }
  13. a[j]=x;
  14. }
  15. }
  16. print(a,8);
  17. }

楼主,我上面这段代码的代码逻辑没问题,但是结果却不对,请教一下你。
Re:pengxiang_7072015-06-30 15:48发表 [回复]
八大排序算法
回复LiuXiaoZhuang_CSDN:这也叫逻辑没问题啊?
while(x < a[j - 1])
这句话你的a数组都越界了吧
36楼自犬邦2015-05-23 20:58发表 [回复]
八大排序算法
博主
tmp = q; q = rf; rf = tmp;

rf是首地址,不能直接赋值的啊
35楼ZYQblog19892015-05-10 18:21发表 [回复]
八大排序算法
您实现的插入算法稍微有点问题,其实只要从第2个开始逐个与前面的比对,到头后再逐渐向后移动光标即可,如下(OC实现的,阅读起来应该无障碍):
- (void)InsertSortWithNumbers:(NSMutableArray *)nums {

NSLog(@"before the sort %@", nums);

for (int i = 1; i < nums.count; i++) {

for (int j = i; j >= 1; j --) {

NSNumber *numj = nums[j];
NSNumber *numjex = nums[j - 1];

if ([numj intValue] < [numjex integerValue]) {
NSNumber *numjexc = [numjex copy];

numjex = [NSNumber numberWithInteger:numj.integerValue];
numj = [NSNumber numberWithInteger:numjexc.integerValue];
nums[j] = numj;
nums[j - 1] = numjex;

} else {

break;
}
}

NSLog(@"after once sort %@", nums);
}
}

概述

排序有内部排序和外部排序,内部排序是数据记录在内存中进行排序,而外部排序是因排序的数据很大,一次不能容纳全部的排序记录,在排序过程中需要访问外存。

我们这里说说八大排序就是内部排序。

八大排序算法

当n较大,则应采用时间复杂度为O(nlog2n)的排序方法:快速排序、堆排序或归并排序序。

快速排序:是目前基于比较的内部排序中被认为是最好的方法,当待排序的关键字是随机分布时,快速排序的平均时间最短;


1.插入排序—直接插入排序(Straight Insertion Sort)

基本思想:

将一个记录插入到已排序好的有序表中,从而得到一个新,记录数增1的有序表。即:先将序列的第1个记录看成是一个有序的子序列,然后从第2个记录逐个进行插入,直至整个序列有序为止。

要点:设立哨兵,作为临时存储和判断数组边界之用。

直接插入排序示例:

八大排序算法


如果碰见一个和插入元素相等的,那么插入元素把想插入的元素放在相等元素的后面。所以,相等元素的前后顺序没有改变,从原无序序列出去的顺序就是排好序后的顺序,所以插入排序是稳定的。

算法的实现:

  1. voidprint(inta[],intn,inti){
  2. cout<<i<<":";
  3. for(intj=0;j<8;j++){
  4. cout<<a[j]<<"";
  5. }
  6. cout<<endl;
  7. }
  8. voidInsertSort(inta[],intn)
  9. {
  10. for(inti=1;i<n;i++){
  11. if(a[i]<a[i-1]){//若第i个元素大于i-1元素,直接插入。小于的话,移动有序表后插入
  12. intj=i-1;
  13. intx=a[i];//复制为哨兵,即存储待排序元素
  14. a[i]=a[i-1];//先后移一个元素
  15. while(x<a[j]){//查找在有序表的插入位置
  16. a[j+1]=a[j];
  17. j--;//元素后移
  18. }
  19. a[j+1]=x;//插入到正确位置
  20. }
  21. print(a,n,i);//打印每趟排序的结果
  22. }
  23. }
  24. intmain(){
  25. inta[8]={3,1,5,7,2,4,9,6};
  26. InsertSort(a,8);
  27. print(a,8,8);
  28. }

效率:

时间复杂度:O(n^2).

其他的插入排序有二分插入排序,2-路插入排序。

2. 插入排序—希尔排序(Shell`s Sort)

希尔排序是1959 年由D.L.Shell 提出来的,相对直接排序有较大的改进。希尔排序又叫缩小增量排序

基本思想:

先将整个待排序的记录序列分割成为若干子序列分别进行直接插入排序,待整个序列中的记录“基本有序”时,再对全体记录进行依次直接插入排序。

操作方法:

  1. 选择一个增量序列t1,t2,…,tk,其中ti>tj,tk=1;
  2. 按增量序列个数k,对序列进行k 趟排序;
  3. 每趟排序,根据对应的增量ti,将待排序列分割成若干长度为m 的子序列,分别对各子表进行直接插入排序。仅增量因子为1 时,整个序列作为一个表来处理,表长度即为整个序列的长度。

希尔排序的示例:

八大排序算法


算法实现:

我们简单处理增量序列:增量序列d = {n/2 ,n/4, n/8 .....1}n为要排序数的个数

即:先将要排序的一组记录按某个增量dn/2,n为要排序数的个数)分成若干组子序列,每组中记录的下标相差d.对每组中全部元素进行直接插入排序,然后再用一个较小的增量(d/2)对它进行分组,在每组中再进行直接插入排序。继续不断缩小增量直至为1,最后使用直接插入排序完成排序。

  1. voidprint(inta[],intn,inti){
  2. cout<<i<<":";
  3. for(intj=0;j<8;j++){
  4. cout<<a[j]<<"";
  5. }
  6. cout<<endl;
  7. }
  8. /**
  9. *直接插入排序的一般形式
  10. *
  11. *@paramintdk缩小增量,如果是直接插入排序,dk=1
  12. *
  13. */
  14. voidShellInsertSort(inta[],intn,intdk)
  15. {
  16. for(inti=dk;i<n;++i){
  17. if(a[i]<a[i-dk]){//若第i个元素大于i-1元素,直接插入。小于的话,移动有序表后插入
  18. intj=i-dk;
  19. intx=a[i];//复制为哨兵,即存储待排序元素
  20. a[i]=a[i-dk];//首先后移一个元素
  21. while(x<a[j]){//查找在有序表的插入位置
  22. a[j+dk]=a[j];
  23. j-=dk;//元素后移
  24. }
  25. a[j+dk]=x;//插入到正确位置
  26. }
  27. print(a,n,i);
  28. }
  29. }
  30. /**
  31. *先按增量d(n/2,n为要排序数的个数进行希尔排序
  32. *
  33. */
  34. voidshellSort(inta[],intn){
  35. intdk=n/2;
  36. while(dk>=1){
  37. ShellInsertSort(a,n,dk);
  38. dk=dk/2;
  39. }
  40. }
  41. intmain(){
  42. inta[8]={3,1,5,7,2,4,9,6};
  43. //ShellInsertSort(a,8,1);//直接插入排序
  44. shellSort(a,8);//希尔插入排序
  45. print(a,8,8);
  46. }

希尔排序时效分析很难,关键码的比较次数与记录移动次数依赖于增量因子序列d的选取,特定情况下可以准确估算出关键码的比较次数和记录的移动次数。目前还没有人给出选取最好的增量因子序列的方法。增量因子序列可以有各种取法,有取奇数的,也有取质数的,但需要注意:增量因子中除1 外没有公因子,且最后一个增量因子必须为1。希尔排序方法是一个不稳定的排序方法。


3. 选择排序—简单选择排序(Simple Selection Sort)

基本思想:

在要排序的一组数中,选出最小(或者最大)的个数与第1个位置的数交换;然后在剩下的数当中再找最小(或者最大)的与第2个位置的数交换,依次类推,直到第n-1个元素(倒数第二个数)和第n个元素(最后个数)比较为止。

简单选择排序的示例:

八大排序算法

操作方法:

第一趟,从n 个记录中找出关键码最小的记录与第一个记录交换;

第二趟,从第二个记录开始的n-1 个记录中再选出关键码最小的记录与第二个记录交换;

以此类推.....

第i 趟,则从第i 个记录开始的n-i+1 个记录中选出关键码最小的记录与第i 个记录交换,

直到整个序列按关键码有序。


算法实现:

  1. voidprint(inta[],intn,inti){
  2. cout<<"第"<<i+1<<"趟:";
  3. for(intj=0;j<8;j++){
  4. cout<<a[j]<<"";
  5. }
  6. cout<<endl;
  7. }
  8. /**
  9. *数组的最小值
  10. *
  11. *@returnint数组的键值
  12. */
  13. intSelectMinKey(inta[],intn,inti)
  14. {
  15. intk=i;
  16. for(intj=i+1;j<n;++j){
  17. if(a[k]>a[j])k=j;
  18. }
  19. returnk;
  20. }
  21. /**
  22. *选择排序
  23. *
  24. */
  25. voidselectSort(inta[],intn){
  26. intkey,tmp;
  27. for(inti=0;i<n;++i){
  28. key=SelectMinKey(a,n,i);//选择最小的元素
  29. if(key!=i){
  30. tmp=a[i];a[i]=a[key];a[key]=tmp;//最小元素与第i位置元素互换
  31. }
  32. print(a,n,i);
  33. }
  34. }
  35. intmain(){
  36. inta[8]={3,1,5,7,2,4,9,6};
  37. cout<<"初始值:";
  38. for(intj=0;j<8;j++){
  39. cout<<a[j]<<"";
  40. }
  41. cout<<endl<<endl;
  42. selectSort(a,8);
  43. print(a,8,8);
  44. }

简单选择排序的改进——二元选择排序

简单选择排序,每趟循环只能确定一个元素排序后的定位。我们可以考虑改进为每趟循环确定两个元素(当前趟最大和最小记录)的位置,从而减少排序所需的循环次数。改进后对n个数据进行排序,最多只需进行[n/2]趟循环即可。具体实现如下:

  1. voidSelectSort(intr[],intn){
  2. inti,j,min,max,tmp;
  3. for(i=1;i<=n/2;i++){
  4. //做不超过n/2趟选择排序
  5. min=i;max=i;//分别记录最大和最小关键字记录位置
  6. for(j=i+1;j<=n-i;j++){
  7. if(r[j]>r[max]){
  8. max=j;continue;
  9. }
  10. if(r[j]<r[min]){
  11. min=j;
  12. }
  13. }
  14. //该交换操作还可分情况讨论以提高效率
  15. tmp=r[i-1];r[i-1]=r[min];r[min]=tmp;
  16. tmp=r[n-i];r[n-i]=r[max];r[max]=tmp;
  17. }
  18. }

4. 选择排序—堆排序(Heap Sort)

堆排序是一种树形选择排序,是对直接选择排序的有效改进。

基本思想:

堆的定义如下:具有n个元素的序列(k1,k2,...,kn),当且仅当满足

八大排序算法

时称之为堆。由堆的定义可以看出,堆顶元素(即第一个元素)必为最小项(小顶堆)。
若以一维数组存储一个堆,则堆对应一棵完全二叉树,且所有非叶结点的值均不大于(或不小于)其子女的值,根结点(堆顶元素)的值是最小(或最大)的。如:

(a)大顶堆序列:(96, 83,27,38,11,09)

(b) 小顶堆序列:(12,36,24,85,47,30,53,91)

八大排序算法


初始时把要排序的n个数的序列看作是一棵顺序存储的二叉树(一维数组存储二叉树),调整它们的存储序,使之成为一个堆,将堆顶元素输出,得到n 个元素中最小(或最大)的元素,这时堆的根节点的数最小(或者最大)。然后对前面(n-1)个元素重新调整使之成为堆,输出堆顶元素,得到n 个元素中次小(或次大)的元素。依此类推,直到只有两个节点的堆,并对它们作交换,最后得到有n个节点的有序序列。称这个过程为堆排序

因此,实现堆排序需解决两个问题:
1. 如何将n 个待排序的数建成堆;
2. 输出堆顶元素后,怎样调整剩余n-1 个元素,使其成为一个新堆。


首先讨论第二个问题:输出堆顶元素后,对剩余n-1元素重新建成堆的调整过程。
调整小顶堆的方法:

1)设有m 个元素的堆,输出堆顶元素后,剩下m-1 个元素。将堆底元素送入堆顶((最后一个元素与堆顶进行交换),堆被破坏,其原因仅是根结点不满足堆的性质。

2)将根结点与左、右子树中较小元素的进行交换。

3)若与左子树交换:如果左子树堆被破坏,即左子树的根结点不满足堆的性质,则重复方法 (2).

4)若与右子树交换,如果右子树堆被破坏,即右子树的根结点不满足堆的性质。则重复方法 (2).

5)继续对不满足堆性质的子树进行上述交换操作,直到叶子结点,堆被建成。

称这个自根结点到叶子结点的调整过程为筛选。如图:

八大排序算法


再讨论对n 个元素初始建堆的过程。
建堆方法:对初始序列建堆的过程,就是一个反复进行筛选的过程。

1)n 个结点的完全二叉树,则最后一个结点是第八大排序算法个结点的子树。

2)筛选从第八大排序算法个结点为根的子树开始,该子树成为堆。

3)之后向前依次对各结点为根的子树进行筛选,使之成为堆,直到根结点。

如图建堆初始过程:无序序列:(49,38,65,97,76,13,27,49)
八大排序算法


八大排序算法

算法的实现:

从算法描述来看,堆排序需要两个过程,一是建立堆,二是堆顶与堆的最后一个元素交换位置。所以堆排序有两个函数组成。一是建堆的渗透函数,二是反复调用渗透函数实现排序的函数。

  1. voidprint(inta[],intn){
  2. for(intj=0;j<n;j++){
  3. cout<<a[j]<<"";
  4. }
  5. cout<<endl;
  6. }
  7. /**
  8. *已知H[s…m]除了H[s]外均满足堆的定义
  9. *调整H[s],使其成为大顶堆.即将对第s个结点为根的子树筛选,
  10. *
  11. *@paramH是待调整的堆数组
  12. *@params是待调整的数组元素的位置
  13. *@paramlength是数组的长度
  14. *
  15. */
  16. voidHeapAdjust(intH[],ints,intlength)
  17. {
  18. inttmp=H[s];
  19. intchild=2*s+1;//左孩子结点的位置。(i+1为当前调整结点的右孩子结点的位置)
  20. while(child<length){
  21. if(child+1<length&&H[child]<H[child+1]){//如果右孩子大于左孩子(找到比当前待调整结点大的孩子结点)
  22. ++child;
  23. }
  24. if(H[s]<H[child]){//如果较大的子结点大于父结点
  25. H[s]=H[child];//那么把较大的子结点往上移动,替换它的父结点
  26. s=child;//重新设置s,即待调整的下一个结点的位置
  27. child=2*s+1;
  28. }else{//如果当前待调整结点大于它的左右孩子,则不需要调整,直接退出
  29. break;
  30. }
  31. H[s]=tmp;//当前待调整的结点放到比其大的孩子结点位置上
  32. }
  33. print(H,length);
  34. }
  35. /**
  36. *初始堆进行调整
  37. *将H[0..length-1]建成堆
  38. *调整完之后第一个元素是序列的最小的元素
  39. */
  40. voidBuildingHeap(intH[],intlength)
  41. {
  42. //最后一个有孩子的节点的位置i=(length-1)/2
  43. for(inti=(length-1)/2;i>=0;--i)
  44. HeapAdjust(H,i,length);
  45. }
  46. /**
  47. *堆排序算法
  48. */
  49. voidHeapSort(intH[],intlength)
  50. {
  51. //初始堆
  52. BuildingHeap(H,length);
  53. //从最后一个元素开始对序列进行调整
  54. for(inti=length-1;i>0;--i)
  55. {
  56. //交换堆顶元素H[0]和堆中最后一个元素
  57. inttemp=H[i];H[i]=H[0];H[0]=temp;
  58. //每次交换堆顶元素和堆中最后一个元素之后,都要对堆进行调整
  59. HeapAdjust(H,0,i);
  60. }
  61. }
  62. intmain(){
  63. intH[10]={3,1,5,7,2,4,9,6,10,8};
  64. cout<<"初始值:";
  65. print(H,10);
  66. HeapSort(H,10);
  67. //selectSort(a,8);
  68. cout<<"结果:";
  69. print(H,10);
  70. }


分析:

设树深度为k,八大排序算法。从根到叶的筛选,元素比较次数至多2(k-1)次,交换记录至多k 次。所以,在建好堆后,排序过程中的筛选次数不超过下式:

八大排序算法

而建堆时的比较次数不超过4n 次,因此堆排序最坏情况下,时间复杂度也为:O(nlogn )。

5. 交换排序—冒泡排序(Bubble Sort)

基本思想:

在要排序的一组数中,对当前还未排好序的范围内的全部数,自上而下对相邻的两个数依次进行比较和调整,让较大的数往下沉,较小的往上冒。即:每当两相邻的数比较后发现它们的排序与排序要求相反时,就将它们互换。

冒泡排序的示例:

八大排序算法

算法的实现:

  1. voidbubbleSort(inta[],intn){
  2. for(inti=0;i<n-1;++i){
  3. for(intj=0;j<n-i-1;++j){
  4. if(a[j]>a[j+1])
  5. {
  6. inttmp=a[j];a[j]=a[j+1];a[j+1]=tmp;
  7. }
  8. }
  9. }
  10. }


冒泡排序算法的改进

对冒泡排序常见的改进方法是加入一标志性变量exchange,用于标志某一趟排序过程中是否有数据交换,如果进行某一趟排序时并没有进行数据交换,则说明数据已经按要求排列好,可立即结束排序,避免不必要的比较过程。本文再提供以下两种改进算法:

1.设置一标志性变量pos,用于记录每趟排序中最后一次进行交换的位置。由于pos位置之后的记录均已交换到位,故在进行下一趟排序时只要扫描到pos位置即可。

改进后算法如下:

  1. voidBubble_1(intr[],intn){
  2. inti=n-1;//初始时,最后位置保持不变
  3. while(i>0){
  4. intpos=0;//每趟开始时,无记录交换
  5. for(intj=0;j<i;j++)
  6. if(r[j]>r[j+1]){
  7. pos=j;//记录交换的位置
  8. inttmp=r[j];r[j]=r[j+1];r[j+1]=tmp;
  9. }
  10. i=pos;//为下一趟排序作准备
  11. }
  12. }

2.传统冒泡排序中每一趟排序操作只能找到一个最大值或最小值,我们考虑利用在每趟排序中进行正向和反向两遍冒泡的方法一次可以得到两个最终值(最大者和最小者) ,从而使排序趟数几乎减少了一半。

改进后的算法实现为:

  1. voidBubble_2(intr[],intn){
  2. intlow=0;
  3. inthigh=n-1;//设置变量的初始值
  4. inttmp,j;
  5. while(low<high){
  6. for(j=low;j<high;++j)//正向冒泡,找到最大者
  7. if(r[j]>r[j+1]){
  8. tmp=r[j];r[j]=r[j+1];r[j+1]=tmp;
  9. }
  10. --high;//修改high值,前移一位
  11. for(j=high;j>low;--j)//反向冒泡,找到最小者
  12. if(r[j]<r[j-1]){
  13. tmp=r[j];r[j]=r[j-1];r[j-1]=tmp;
  14. }
  15. ++low;//修改low值,后移一位
  16. }
  17. }

6. 交换排序—快速排序(Quick Sort)

基本思想:

1)选择一个基准元素,通常选择第一个元素或者最后一个元素,

2)通过一趟排序讲待排序的记录分割成独立的两部分,其中一部分记录的元素值均比基准元素值小。另一部分记录的元素值比基准值大。

3)此时基准元素在其排好序后的正确位置

4)然后分别对这两部分记录用同样的方法继续进行排序,直到整个序列有序。

快速排序的示例:

(a)一趟排序的过程:

八大排序算法

(b)排序的全过程

八大排序算法

算法的实现:

递归实现:

  1. voidprint(inta[],intn){
  2. for(intj=0;j<n;j++){
  3. cout<<a[j]<<"";
  4. }
  5. cout<<endl;
  6. }
  7. voidswap(int*a,int*b)
  8. {
  9. inttmp=*a;
  10. *a=*b;
  11. *b=tmp;
  12. }
  13. intpartition(inta[],intlow,inthigh)
  14. {
  15. intprivotKey=a[low];//基准元素
  16. while(low<high){//从表的两端交替地向中间扫描
  17. while(low<high&&a[high]>=privotKey)--high;//从high所指位置向前搜索,至多到low+1位置。将比基准元素小的交换到低端
  18. swap(&a[low],&a[high]);
  19. while(low<high&&a[low]<=privotKey)++low;
  20. swap(&a[low],&a[high]);
  21. }
  22. print(a,10);
  23. returnlow;
  24. }
  25. voidquickSort(inta[],intlow,inthigh){
  26. if(low<high){
  27. intprivotLoc=partition(a,low,high);//将表一分为二
  28. quickSort(a,low,privotLoc-1);//递归对低子表递归排序
  29. quickSort(a,privotLoc+1,high);//递归对高子表递归排序
  30. }
  31. }
  32. intmain(){
  33. inta[10]={3,1,5,7,2,4,9,6,10,8};
  34. cout<<"初始值:";
  35. print(a,10);
  36. quickSort(a,0,9);
  37. cout<<"结果:";
  38. print(a,10);
  39. }


分析:

快速排序是通常被认为在同数量级(O(nlog2n))的排序方法中平均性能最好的。但若初始序列按关键码有序或基本有序时,快排序反而蜕化为冒泡排序。为改进之,通常以“三者取中法”来选取基准记录,即将排序区间的两个端点与中点三个记录关键码居中的调整为支点记录。快速排序是一个不稳定的排序方法。


快速排序的改进

在本改进算法中,只对长度大于k的子序列递归调用快速排序,让原序列基本有序,然后再对整个基本有序序列用插入排序算法排序。实践证明,改进后的算法时间复杂度有所降低,且当k取值为8左右时,改进算法的性能最佳。算法思想如下:

  1. voidprint(inta[],intn){
  2. for(intj=0;j<n;j++){
  3. cout<<a[j]<<"";
  4. }
  5. cout<<endl;
  6. }
  7. voidswap(int*a,int*b)
  8. {
  9. inttmp=*a;
  10. *a=*b;
  11. *b=tmp;
  12. }
  13. intpartition(inta[],intlow,inthigh)
  14. {
  15. intprivotKey=a[low];//基准元素
  16. while(low<high){//从表的两端交替地向中间扫描
  17. while(low<high&&a[high]>=privotKey)--high;//从high所指位置向前搜索,至多到low+1位置。将比基准元素小的交换到低端
  18. swap(&a[low],&a[high]);
  19. while(low<high&&a[low]<=privotKey)++low;
  20. swap(&a[low],&a[high]);
  21. }
  22. print(a,10);
  23. returnlow;
  24. }
  25. voidqsort_improve(intr[],intlow,inthigh,intk){
  26. if(high-low>k){//长度大于k时递归,k为指定的数
  27. intpivot=partition(r,low,high);//调用的Partition算法保持不变
  28. qsort_improve(r,low,pivot-1,k);
  29. qsort_improve(r,pivot+1,high,k);
  30. }
  31. }
  32. voidquickSort(intr[],intn,intk){
  33. qsort_improve(r,0,n,k);//先调用改进算法Qsort使之基本有序
  34. //再用插入排序对基本有序序列排序
  35. for(inti=1;i<=n;i++){
  36. inttmp=r[i];
  37. intj=i-1;
  38. while(tmp<r[j]){
  39. r[j+1]=r[j];j=j-1;
  40. }
  41. r[j+1]=tmp;
  42. }
  43. }
  44. intmain(){
  45. inta[10]={3,1,5,7,2,4,9,6,10,8};
  46. cout<<"初始值:";
  47. print(a,10);
  48. quickSort(a,9,4);
  49. cout<<"结果:";
  50. print(a,10);
  51. }


7. 归并排序(Merge Sort)


基本思想:

归并(Merge)排序法是将两个(或两个以上)有序表合并成一个新的有序表,即把待排序序列分为若干个子序列,每个子序列是有序的。然后再把有序子序列合并为整体有序序列。

归并排序示例:

八大排序算法


合并方法:

设r[i…n]由两个有序子表r[i…m]和r[m+1…n]组成,两个子表长度分别为n-i +1、n-m

  1. j=m+1;k=i;i=i; //置两个子表的起始下标及辅助数组的起始下标
  2. 若i>m 或j>n,转⑷ //其中一个子表已合并完,比较选取结束
  3. //选取r[i]和r[j]较小的存入辅助数组rf
    如果r[i]<r[j],rf[k]=r[i]; i++; k++; 转⑵
    否则,rf[k]=r[j]; j++; k++; 转⑵
  4. //将尚未处理完的子表中元素存入rf
    如果i<=m,将r[i…m]存入rf[k…n] //前一子表非空
    如果j<=n , 将r[j…n] 存入rf[k…n] //后一子表非空
  5. 合并结束。
  1. //将r[i…m]和r[m+1…n]归并到辅助数组rf[i…n]
  2. voidMerge(ElemType*r,ElemType*rf,inti,intm,intn)
  3. {
  4. intj,k;
  5. for(j=m+1,k=i;i<=m&&j<=n;++k){
  6. if(r[j]<r[i])rf[k]=r[j++];
  7. elserf[k]=r[i++];
  8. }
  9. while(i<=m)rf[k++]=r[i++];
  10. while(j<=n)rf[k++]=r[j++];
  11. }


归并的迭代算法


1 个元素的表总是有序的。所以对n 个元素的待排序列,每个元素可看成1 个有序子表。对子表两两合并生成n/2个子表,所得子表除最后一个子表长度可能为1 外,其余子表长度均为2。再进行两两合并,直到生成n 个元素按关键码有序的表。

  1. voidprint(inta[],intn){
  2. for(intj=0;j<n;j++){
  3. cout<<a[j]<<"";
  4. }
  5. cout<<endl;
  6. }
  7. //将r[i…m]和r[m+1…n]归并到辅助数组rf[i…n]
  8. voidMerge(ElemType*r,ElemType*rf,inti,intm,intn)
  9. {
  10. intj,k;
  11. for(j=m+1,k=i;i<=m&&j<=n;++k){
  12. if(r[j]<r[i])rf[k]=r[j++];
  13. elserf[k]=r[i++];
  14. }
  15. while(i<=m)rf[k++]=r[i++];
  16. while(j<=n)rf[k++]=r[j++];
  17. print(rf,n+1);
  18. }
  19. voidMergeSort(ElemType*r,ElemType*rf,intlenght)
  20. {
  21. intlen=1;
  22. ElemType*q=r;
  23. ElemType*tmp;
  24. while(len<lenght){
  25. ints=len;
  26. len=2*s;
  27. inti=0;
  28. while(i+len<lenght){
  29. Merge(q,rf,i,i+s-1,i+len-1);//对等长的两个子表合并
  30. i=i+len;
  31. }
  32. if(i+s<lenght){
  33. Merge(q,rf,i,i+s-1,lenght-1);//对不等长的两个子表合并
  34. }
  35. tmp=q;q=rf;rf=tmp;//交换q,rf,以保证下一趟归并时,仍从q归并到rf
  36. }
  37. }
  38. intmain(){
  39. inta[10]={3,1,5,7,2,4,9,6,10,8};
  40. intb[10];
  41. MergeSort(a,b,10);
  42. print(b,10);
  43. cout<<"结果:";
  44. print(a,10);
  45. }

两路归并的递归算法

  1. voidMSort(ElemType*r,ElemType*rf,ints,intt)
  2. {
  3. ElemType*rf2;
  4. if(s==t)r[s]=rf[s];
  5. else
  6. {
  7. intm=(s+t)/2;/*平分*p表*/
  8. MSort(r,rf2,s,m);/*递归地将p[s…m]归并为有序的p2[s…m]*/
  9. MSort(r,rf2,m+1,t);/*递归地将p[m+1…t]归并为有序的p2[m+1…t]*/
  10. Merge(rf2,rf,s,m+1,t);/*将p2[s…m]和p2[m+1…t]归并到p1[s…t]*/
  11. }
  12. }
  13. voidMergeSort_recursive(ElemType*r,ElemType*rf,intn)
  14. {/*对顺序表*p作归并排序*/
  15. MSort(r,rf,0,n-1);
  16. }

8. 桶排序/基数排序(Radix Sort)

说基数排序之前,我们先说桶排序:

基本思想:是将阵列分到有限数量的桶子里。每个桶子再个别排序(有可能再使用别的排序算法或是以递回方式继续使用桶排序进行排序)。桶排序是鸽巢排序的一种归纳结果。当要被排序的阵列内的数值是均匀分配的时候,桶排序使用线性时间(Θ(n))。但桶排序并不是 比较排序,他不受到 O(n log n) 下限的影响。
简单来说,就是把数据分组,放在一个个的桶中,然后对每个桶里面的在进行排序。

例如要对大小为[1..1000]范围内的n个整数A[1..n]排序

首先,可以把桶设为大小为10的范围,具体而言,设集合B[1]存储[1..10]的整数,集合B[2]存储 (10..20]的整数,……集合B[i]存储( (i-1)*10, i*10]的整数,i = 1,2,..100。总共有 100个桶。

然后,对A[1..n]从头到尾扫描一遍,把每个A[i]放入对应的桶B[j]中。 再对这100个桶中每个桶里的数字排序,这时可用冒泡,选择,乃至快排,一般来说任 何排序法都可以。

最后,依次输出每个桶里面的数字,且每个桶中的数字从小到大输出,这 样就得到所有数字排好序的一个序列了。

假设有n个数字,有m个桶,如果数字是平均分布的,则每个桶里面平均有n/m个数字。如果

对每个桶中的数字采用快速排序,那么整个算法的复杂度是

O(n + m * n/m*log(n/m)) = O(n + nlogn - nlogm)

从上式看出,当m接近n的时候,桶排序复杂度接近O(n)

当然,以上复杂度的计算是基于输入的n个数字是平均分布这个假设的。这个假设是很强的 ,实际应用中效果并没有这么好。如果所有的数字都落在同一个桶中,那就退化成一般的排序了。

前面说的几大排序算法 ,大部分时间复杂度都是O(n2),也有部分排序算法时间复杂度是O(nlogn)。而桶式排序却能实现O(n)的时间复杂度。但桶排序的缺点是:

1)首先是空间复杂度比较高,需要的额外开销大。排序有两个数组的空间开销,一个存放待排序数组,一个就是所谓的桶,比如待排序值是从0到m-1,那就需要m个桶,这个桶数组就要至少m个空间。

2)其次待排序的元素都要在一定的范围内等等。

桶式排序是一种分配排序。分配排序的特定是不需要进行关键码的比较,但前提是要知道待排序列的一些具体情况。


分配排序的基本思想:说白了就是进行多次的桶式排序。

基数排序过程无须比较关键字,而是通过“分配”和“收集”过程来实现排序。它们的时间复杂度可达到线性阶:O(n)。

实例:

扑克牌中52 张牌,可按花色和面值分成两个字段,其大小关系为:
花色:梅花< 方块< 红心< 黑心 八大排序算法
面值:2 < 3 < 4 < 5 < 6 < 7 < 8 < 9 < 10 < J < Q < K < A

若对扑克牌按花色、面值进行升序排序,得到如下序列:
八大排序算法

八大排序算法


即两张牌,若花色不同,不论面值怎样,花色低的那张牌小于花色高的,只有在同花况下,大小关系才由面值的大小确定。这就是多关键码排序。

为得到排序结果,我们讨论两种排序方法。
方法1:先对花色排序,将其分为4 个组,即梅花组、方块组、红心组、黑心组。再对每个组分别按面值进行排序,最后,将4 个组连接起来即可。
方法2:先按13 个面值给出13 个编号组(2 号,3 号,...,A 号),将牌按面值依次放入对应的编号组,分成13 堆。再按花色给出4 个编号组(梅花、方块、红心、黑心),将2号组中牌取出分别放入对应花色组,再将3 号组中牌取出分别放入对应花色组,……,这样,4 个花色组中均按面值有序,然后,将4 个花色组依次连接起来即可。

设n 个元素的待排序列包含d 个关键码{k1,k2,…,kd},则称序列对关键码{k1,k2,…,kd}有序是指:对于序列中任两个记录r[i]和r[j](1≤i≤j≤n)都满足下列有序关系:

八大排序算法

其中k1 称为最主位关键码,kd 称为最次位关键码。

两种多关键码排序方法:

多关键码排序按照从最主位关键码到最次位关键码或从最次位到最主位关键码的顺序逐次排序,分两种方法:

最高位优先(Most Significant Digit first)法,简称MSD 法

1)先按k1 排序分组,将序列分成若干子序列,同一组序列的记录中,关键码k1 相等。

2)再对各组按k2 排序分成子组,之后,对后面的关键码继续这样的排序分组,直到按最次位关键码kd 对各子组排序后。

3)再将各组连接起来,便得到一个有序序列。扑克牌按花色、面值排序中介绍的方法一即是MSD 法。

最低位优先(Least Significant Digit first)法,简称LSD 法

1) 先从kd 开始排序,再对kd-1进行排序,依次重复,直到按k1排序分组分成最小的子序列后。

2) 最后将各个子序列连接起来,便可得到一个有序的序列, 扑克牌按花色、面值排序中介绍的方法二即是LSD 法。


基于LSD方法的链式基数排序的基本思想

  “多关键字排序”的思想实现“单关键字排序”。对数字型或字符型的单关键字,可以看作由多个数位或多个字符构成的多关键字,此时可以采用“分配-收集”的方法进行排序,这一过程称作基数排序法,其中每个数字或字符可能的取值个数称为基数。比如,扑克牌的花色基数为4,面值基数为13。在整理扑克牌时,既可以先按花色整理,也可以先按面值整理。按花色整理时,先按红、黑、方、花的顺序分成4摞(分配),再按此顺序再叠放在一起(收集),然后按面值的顺序分成13摞(分配),再按此顺序叠放在一起(收集),如此进行二次分配和收集即可将扑克牌排列有序。

基数排序:

是按照低位先排序,然后收集;再按照高位排序,然后再收集;依次类推,直到最高位。有时候有些属性是有优先级顺序的,先按低优先级排序,再按高优先级排序。最后的次序就是高优先级高的在前,高优先级相同的低优先级高的在前。基数排序基于分别排序,分别收集,所以是稳定的。

算法实现:

  1. VoidRadixSort(NodeL[],length,maxradix)
  2. {
  3. intm,n,k,lsp;
  4. k=1;m=1;
  5. inttemp[10][length-1];
  6. Empty(temp);//清空临时空间
  7. while(k<maxradix)//遍历所有关键字
  8. {
  9. for(inti=0;i<length;i++)//分配过程
  10. {
  11. if(L[i]<m)
  12. Temp[0][n]=L[i];
  13. else
  14. Lsp=(L[i]/m)%10;//确定关键字
  15. Temp[lsp][n]=L[i];
  16. n++;
  17. }
  18. CollectElement(L,Temp);//收集
  19. n=0;
  20. m=m*10;
  21. k++;
  22. }
  23. }





总结

各种排序的稳定性,时间复杂度和空间复杂度总结:

八大排序算法

我们比较时间复杂度函数的情况:

八大排序算法


时间复杂度函数O(n)的增长情况

八大排序算法

所以对n较大的排序记录。一般的选择都是时间复杂度为O(nlog2n)的排序方法。


时间复杂度来说:

(1)平方阶(O(n2))排序
  各类简单排序:直接插入、直接选择和冒泡排序;
(2)线性对数阶(O(nlog2n))排序
  快速排序堆排序归并排序
(3)O(n1+§))排序,§是介于0和1之间的常数。

希尔排序
(4)线性阶(O(n))排序
  基数排序,此外还有桶、箱排序。

说明:

当原表有序或基本有序时,直接插入排序和冒泡排序将大大减少比较次数和移动记录的次数,时间复杂度可降至On);

而快速排序则相反,当原表基本有序时,将蜕化为冒泡排序,时间复杂度提高为On2);

原表是否有序,对简单选择排序、堆排序、归并排序和基数排序的时间复杂度影响不大。

稳定性:

排序算法的稳定性:若待排序的序列中,存在多个具有相同关键字的记录,经过排序, 这些记录的相对次序保持不变,则称该算法是稳定的;若经排序后,记录的相对 次序发生了改变,则称该算法是不稳定的。
稳定性的好处:排序算法如果是稳定的,那么从一个键上排序,然后再从另一个键上排序,第一个键排序的结果可以为第二个键排序所用。基数排序就是这样,先按低位排序,逐次按高位排序,低位相同的元素其顺序再高位也相同时是不会改变的。另外,如果排序算法稳定,可以避免多余的比较;

稳定的排序算法冒泡排序、插入排序、归并排序和基数排序

不是稳定的排序算法:选择排序、快速排序、希尔排序、堆排序

选择排序算法准则:

每种排序算法都各有优缺点。因此,在实用时需根据不同情况适当选用,甚至可以将多种方法结合起来使用。

选择排序算法的依据

影响排序的因素有很多,平均时间复杂度低的算法并不一定就是最优的。相反,有时平均时间复杂度高的算法可能更适合某些特殊情况。同时,选择算法时还得考虑它的可读性,以利于软件的维护。一般而言,需要考虑的因素有以下四点:

1.待排序的记录数目n的大小;

2.记录本身数据量的大小,也就是记录中除关键字外的其他信息量的大小;

3.关键字的结构及其分布情况;

4.对排序稳定性的要求。

设待排序元素的个数为n.

1)当n较大,则应采用时间复杂度为O(nlog2n)的排序方法:快速排序、堆排序或归并排序序。

快速排序:是目前基于比较的内部排序中被认为是最好的方法,当待排序的关键字是随机分布时,快速排序的平均时间最短;
堆排序: 如果内存空间允许且要求稳定性的,

归并排序:它有一定数量的数据移动,所以我们可能过与插入排序组合,先获得一定长度的序列,然后再合并,在效率上将有所提高。

2) 当n较大,内存空间允许,且要求稳定性 =》归并排序

3)当n较小,可采用直接插入或直接选择排序。

直接插入排序:当元素分布有序,直接插入排序将大大减少比较次数和移动记录的次数。

直接选择排序 :元素分布有序,如果不要求稳定性,选择直接选择排序

5)一般不使用或不直接使用传统的冒泡排序。

6)基数排序
它是一种稳定的排序算法,但有一定的局限性:
  1、关键字可分解。

  2
、记录的关键字位数较少,如果密集更好
  3、如果是数字时,最好是无符号的,否则将增加相应的映射复杂度,可先将其正负分开排序。

注明:转载请提示出处:http://blog.csdn.net/hguisu/article/details/7776068