排序算法汇总

来源:互联网 发布:互联网大数据分析 编辑:程序博客网 时间:2024/06/16 15:27

原文在这里 :http://blog.csdn.net/wcyoot/article/details/6427247#

简单排序算法 

1.冒泡法: 

这是最原始,也是众所周知的最慢的算法了。他的名字的由来因为它的工作看来象是冒泡: 

view plain
  1. // 冒泡法排序(升序):O(n^2)  
  2. // 思想:倒序,把比前一个元素小的与前一个交换,就相当于气泡上升。  
  3. void BubbleSort(int* pData,int Count)   
  4. {   
  5.     int iTemp;   
  6.     for(int i=1; i < Count; i++) // i,气泡上升顶点,  
  7.     {   
  8.         for(int j = Count-1; j >= i; j--) // 气泡上升过程  
  9.         {   
  10.             if(pData[j]<pData[j-1])        
  11.             {   
  12.                 iTemp = pData[j-1];   
  13.                 pData[j-1] = pData[j];   
  14.                 pData[j] = iTemp;   
  15.             }   
  16.         }   
  17.     }   
  18. }  


倒序(最糟情况) 
第一轮:10,9,8,7->10,9,7,8->10,7,9,8->7,10,9,8(交换3次) 
第二轮:7,10,9,8->7,10,8,9->7,8,10,9(交换2次) 
第一轮:7,8,10,9->7,8,9,10(交换1次) 
循环次数:6次 
交换次数:6次 

其他: 
第一轮:8,10,7,9->8,10,7,9->8,7,10,9->7,8,10,9(交换2次) 
第二轮:7,8,10,9->7,8,10,9->7,8,10,9(交换0次) 
第一轮:7,8,10,9->7,8,9,10(交换1次) 
循环次数:6次 
交换次数:3次 

上面我们给出了程序段,现在我们分析它:这里,影响我们算法性能的主要部分是循环和交换, 
显然,次数越多,性能就越差。从上面的程序我们可以看出循环的次数是固定的,为1+2+...+n-1。 
写成公式就是1/2*(n-1)*n。 


现在注意,我们给出O方法的定义: 
   若存在一常量K和起点n0,使当n>=n0时,有f(n)<=K*g(n),则f(n) = O(g(n))。


从程序后面所跟的表可以看到,两种情况的循环相同,交换不同。其实交换本身同数据源的 
有序程度有极大的关系,当数据处于倒序的情况时,交换次数同循环一样(每次循环判断都会交换), 
复杂度为O(n*n)。当数据为正序,将不会有交换。复杂度为O(0)。乱序时处于中间状态。正是由于这样的 
原因,我们通常都是通过循环次数来对比算法。 

2.交换法: 

交换法的程序最清晰简单,每次用当前的元素一一的同其后的元素比较并交换。 

view plain
  1. // 交换法排序(升序):O(n^2)  
  2. // 思想:对于每一个元素,如果其后面的元素比它小,立刻与之交换,继续与之后的元素比较,交换  
  3. void ExchangeSort(int* pData,int Count)   
  4. {   
  5.     int iTemp;   
  6.     for(int i=0;i<Count-1;i++)   
  7.     {   
  8.         for(int j=i+1;j<Count;j++)   
  9.         {   
  10.             if(pData[j]<pData[i])   
  11.             {   
  12.                 iTemp = pData[i];   
  13.                 pData[i] = pData[j];   
  14.                 pData[j] = iTemp;   
  15.             }   
  16.         }   
  17.     }   
  18. }   


倒序(最糟情况) 
第一轮:10,9,8,7->9,10,8,7->8,10,9,7->7,10,9,8(交换3次) 
第二轮:7,10,9,8->7,9,10,8->7,8,10,9(交换2次) 
第一轮:7,8,10,9->7,8,9,10(交换1次) 
循环次数:6次 
交换次数:6次 

其他: 
第一轮:8,10,7,9->8,10,7,9->7,10,8,9->7,10,8,9(交换1次) 
第二轮:7,10,8,9->7,8,10,9->7,8,10,9(交换1次) 
第一轮:7,8,10,9->7,8,9,10(交换1次) 
循环次数:6次 
交换次数:3次 

从运行的表格来看,交换几乎和冒泡一样糟。事实确实如此。循环次数和冒泡一样 
也是1/2*(n-1)*n,所以算法的复杂度仍然是O(n*n)。由于我们无法给出所有的情况,所以 
只能直接告诉大家他们在交换上面也是一样的糟糕(在某些情况下稍好,在某些情况下稍差)。 

3.选择法: 

现在我们终于可以看到一点希望:选择法,这种方法提高了一点性能(某些情况下) 
这种方法类似我们人为的排序习惯:从数据中选择最小的同第一个值交换,在从剩下的部分中 
选择最小的与第二个交换,这样往复下去。 

view plain
  1. // 选择法排序(升序):O(n^2)  
  2. // 思想:从前往后,对于每一个元素,均从其与之后所有元素中选择最小的一个交换这个元素  
  3. void SelectSort(int* pData,int Count)   
  4. {   
  5.     int iTemp;   
  6.     int iPos;   
  7.     for(int i=0;i<Count-1;i++)   
  8.     {   
  9.         iTemp = pData[i];   
  10.         iPos = i;   
  11.         for(int j=i+1;j<Count;j++) // 从其后找最小的一个  
  12.         {   
  13.             if(pData[j]<iTemp)   
  14.             {   
  15.                 iTemp = pData[j];   
  16.                 iPos = j;   
  17.             }   
  18.         }   
  19.         pData[iPos] = pData[i]; // 交换最小的元素  
  20.         pData[i] = iTemp;   
  21.     }   
  22. }   


倒序(最糟情况) 
第一轮:10,9,8,7->(iTemp=9)10,9,8,7->(iTemp=8)10,9,8,7->(iTemp=7)7,9,8,10(交换1次) 
第二轮:7,9,8,10->7,9,8,10(iTemp=8)->(iTemp=8)7,8,9,10(交换1次) 
第一轮:7,8,9,10->(iTemp=9)7,8,9,10(交换0次) 
循环次数:6次 
交换次数:2次 

其他: 
第一轮:8,10,7,9->(iTemp=8)8,10,7,9->(iTemp=7)8,10,7,9->(iTemp=7)7,10,8,9(交换1次) 
第二轮:7,10,8,9->(iTemp=8)7,10,8,9->(iTemp=8)7,8,10,9(交换1次) 
第一轮:7,8,10,9->(iTemp=9)7,8,9,10(交换1次) 
循环次数:6次 
交换次数:3次 
遗憾的是算法需要的循环次数依然是1/2*(n-1)*n。

 

所以算法复杂度为O(n*n)。 
我们来看他的交换。由于每次外层循环只产生一次交换(只有一个最小值)。所以f(n)<=n 
所以我们有f(n)=O(n)。所以,在数据较乱的时候,可以减少一定的交换次数。 

4.插入法: 

插入法较为复杂,它的基本工作原理是抽出牌,在前面的牌中寻找相应的位置插入,然后继续下一张 

view plain
  1. // 插入法排序:O(n^2)  
  2. // 思想:把数组分为有序区和无序区两个部分,初始以起始第一个元素为有序区  
  3. // 每次取出无序区的第一个元素,相当于把有序区容量加1,把取出的元素与有序区元素倒序比较  
  4. // 如果取出的元素较小,则把该有序区元素后移,同时继续比较,直到其不小于有序区该位置元素  
  5. // 把取出的元素,写入到该位置的下一位。  
  6. // 由于空位一直随着比较迁移,不要再做其他移动工作。  
  7. // 就相当于码扑克牌,从排队里抓起一张,然后插入到合适的位置,同时也把比它大的牌后移。  
  8. void InsertSort(int* pData,int Count)   
  9. {   
  10.     int iTemp;   
  11.     int iPos;   
  12.     for(int i=1;i<Count;i++)   
  13.     {   
  14.         iTemp = pData[i];   
  15.         iPos = i-1;   
  16.         while((iPos>=0) && (iTemp<pData[iPos]))   // 找寻要插入的位置  
  17.         {   
  18.             pData[iPos+1] = pData[iPos];        // 要插入的位置还在前面,要把当前元素后移,留出空位  
  19.             iPos--;   
  20.         }   
  21.         pData[iPos+1] = iTemp; // 插入  
  22.     }   
  23. }   


倒序(最糟情况) 
第一轮:10,9,8,7->9,10,8,7(交换1次)(循环1次) 
第二轮:9,10,8,7->8,9,10,7(交换1次)(循环2次) 
第一轮:8,9,10,7->7,8,9,10(交换1次)(循环3次) 
循环次数:6次 
交换次数:3次 

其他: 
第一轮:8,10,7,9->8,10,7,9(交换0次)(循环1次) 
第二轮:8,10,7,9->7,8,10,9(交换1次)(循环2次) 
第一轮:7,8,10,9->7,8,9,10(交换1次)(循环1次) 
循环次数:4次 
交换次数:2次 

上面结尾的行为分析事实上造成了一种假象,让我们认为这种算法是简单算法中最好的,其实不是, 
因为其循环次数虽然并不固定,我们仍可以使用O方法。从上面的结果可以看出,循环的次数f(n)<= 
1/2*n*(n-1)<=1/2*n*n。所以其复杂度仍为O(n*n)(这里说明一下,其实如果不是为了展示这些简单 
排序的不同,交换次数仍然可以这样推导)。现在看交换,从外观上看,交换次数是O(n)(推导类似 
选择法),但我们每次要进行与内层循环相同次数的‘=’操作。正常的一次交换我们需要三次‘=’ 
而这里显然多了一些,所以我们浪费了时间。 

高级排序算法:

5、归并排序:

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

 

归并排序具体工作原理如下(假设序列共有n个元素):

  1. 将序列每相邻两个数字进行归并操作,形成floor(n / 2)个序列,排序后每个序列包含两个元素
  2. 将上述序列再次归并,形成floor(n / 4)个序列,每个序列包含四个元素
  3. 重复步骤2,直到所有元素排序完毕

 

view plain
  1. // 归并排序--归并函数  
  2. void Merge(int* pData, int first, int mid, int last)  
  3. {  
  4.     //申请空间,使其大小为两个已经排序序列之和,该空间用来存放合并后的序列  
  5.     int *pTemp = new int[last-first+1];  
  6.     int begin1 = first, begin2 = mid + 1;  
  7.     int i;  
  8.     for(i = 0; begin1 <= mid && begin2 <= last; ++i)  
  9.     {  
  10.         //比较两个指针所指向的元素,选择相对小的元素放入到合并空间,并移动指针到下一位置  
  11.         if(pData[begin1] <= pData[begin2])  
  12.             pTemp[i] = pData[begin1++];  
  13.         else  
  14.             pTemp[i] = pData[begin2++];  
  15.     }  
  16.     //若有剩余,直接拷贝出来粘到合并序列尾  
  17.     if(begin1 <= mid)      
  18.         memcpy_s(pTemp + i, (last-first+1)*sizeof(int), pData + begin1, (mid-begin1+1)*sizeof(int));  
  19.     else if(begin2 <= last)  
  20.         memcpy_s(pTemp + i, (last-first+1)*sizeof(int), pData + begin2, (last-begin2+1)*sizeof(int));  
  21.     //将排序好的序列拷贝回数组中  
  22.     memcpy_s(pData + first, (last-first+1)*sizeof(int), pTemp, (last-first+1)*sizeof(int));  
  23.     delete pTemp;  
  24. }  
  25. // 归并排序  
  26. // 思路:分组排序,把排序后的数组归并  
  27. void MergeSort(int* pData, int first, int last)  
  28. {  
  29.     int mid = 0;  
  30.     if(first < last)  
  31.     {  
  32.         mid = (first+last)/2;  
  33.         MergeSort(pData, first, mid);  
  34.         MergeSort(pData, mid+1, last);  
  35.         Merge(pData, first, mid, last);  
  36.     }  
  37. }  
 

比较操作的次数介于(nlogn) / 2nlogn − n + 1。 赋值操作的次数是(2nlogn)。 归并算法的空间复杂度为:Θ (n)

6、快速排序:

首先我们选择一个中间值middle程序中我们使用数组中间值,然后 
把比它小的放在左边,大的放在右边(具体的实现是从两边找,找到一对后交换)。然后对两边分别使 
用这个过程(最容易的方法——递归)。 

view plain
  1. #include <utility>  
  2. // 快速排序,递归算法  
  3. // 思路:取数组中间值作为划分元素,使该值左端元素均不大于它,右端元素均不小于它。该值在排序后的位置就定了。  
  4. void QuickSort1(int* pData, int left, int right)    // left和right表示要排序的范围  
  5. {   
  6.     int i,j;   
  7.     int middle,iTemp;   
  8.     i = left;   
  9.     j = right;   
  10.     middle = pData[(left+right)/2]; //求中间值 ,作为划分元素  
  11.     do  
  12.     {   
  13.         while((pData[i]<middle) && (i<right)) //从左扫描大于中值的数   
  14.             i++;        
  15.         while((pData[j]>middle) && (j>left)) //从右扫描大于中值的数   
  16.             j--;   
  17.         if(i<=j) //找到了一对值   
  18.         {   
  19.             //交换,使左端元素均不大于划分元素,右端元素均不小于划分元素  
  20.             std::swap(pData[i], pData[j]);  
  21.             i++;   
  22.             j--;   
  23.         }   
  24.     }while(i<=j);//如果两边扫描的下标交错,就停止(完成一次)   
  25.     //当左边部分有值(left<j),递归左半边   
  26.     if(left<j)   
  27.         QuickSort1(pData,left,j);   
  28.     //当右边部分有值(right>i),递归右半边   
  29.     if(right>i)   
  30.         QuickSort1(pData,i,right);   
  31. }   
  32. // 快速排序,非递归算法  
  33. // 思路:同上  
  34. // 优化:1、当划分组元素个数较少时,直接使用插入法排序,提高效率。  
  35. //       2、选择左、中、右三个元素中处于中间的那个元素为划分元素,防止仅选择中间元素作为换分元素时,中间元素恰好为最小或最大值的情况。  
  36. void QSort(int* pData, int nCount, int m = -1)  
  37. {  
  38.     static const int M=7, NSTACK=64;    // M是使用直接插入法排序的子数组大小,NSTACK是所需的辅助存储器  
  39.     int i,ir,j,jstack=-1,l=0;           // i,j为循环算子,ir为当前分组右端,l当前为分组左端,jstack为当前栈数组滑标  
  40.     int a;  
  41.     int istack[NSTACK];                 // 堆栈,存放分组范围,i存放分组范围左端,i+1存放分组范围右端  
  42.     if (m > 0) nCount = min(m,nCount);  // m是可选参数,如果m>0,标识只对前m个元素排序  
  43.     ir=nCount-1;                        // 初始分组,ir为最右端位置,l为起始点位置  
  44.     for (;;)  
  45.     {   
  46.         if (ir-l < M)                // 当子数组足够小时,插入法排序  
  47.         {  
  48.             for (j=l+1;j<=ir;j++)    // 插入法排序算法  
  49.             {  
  50.                 a=pData[j];  
  51.                 for (i=j-1;i>=l;i--)   
  52.                 {  
  53.                     if (pData[i] <= a) break;  
  54.                     pData[i+1]=pData[i];  
  55.                 }  
  56.                 pData[i+1]=a;  
  57.             }  
  58.             if (jstack < 0) break;   // 堆栈为NULL,数组已经排序完成  
  59.              // 弹出堆栈,并开始新一轮划分  
  60.             ir=istack[jstack--];    // 右端  
  61.             l=istack[jstack--];     // 左端  
  62.         }   
  63.         else        // 快速排序核心算法  
  64.         {  
  65.             int k=(l+ir) >> 1;        // k为当前分组范围的中点  
  66.             std::swap(pData[k],pData[l+1]);     // 先把中间元素存放在第二位置  
  67.             // 挑选左、中、右三元素的中值作为划分元素a  
  68.             // 同时重新排列以使a[l]<=a[l+1]<=a[ir]  
  69.             if (pData[l] > pData[ir])   
  70.             {  
  71.                 std::swap(pData[l],pData[ir]);  
  72.             }  
  73.             if (pData[l+1] > pData[ir])   
  74.             {  
  75.                 std::swap(pData[l+1],pData[ir]);  
  76.             }  
  77.             if (pData[l] > pData[l+1])   
  78.             {  
  79.                 std::swap(pData[l],pData[l+1]);  
  80.             }  
  81.             i=l+1; // 为划分初始化指针,左指针  
  82.             j=ir;  // 右指针  
  83.             a=pData[l+1]; // 取划分元素  
  84.             for (;;) // 开始内循环  
  85.             {   
  86.                 do i++; while (pData[i] < a); // 从数组首部向后寻找大于a的元素  
  87.                 do j--; while (pData[j] > a); // 从数组尾部向前寻找小于a的元素  
  88.                 if (j < i) break;              // 指针交汇,划分结束  
  89.                 std::swap(pData[i],pData[j]); // 各自找到元素,交换元素  
  90.             } // 结束内循环,此时j左端的均元素均小于j右端的元素,且j位置的元素小于当前划分元素a.  
  91.             pData[l+1]=pData[j]; // 插入划分元素,此时该元素位置已定  
  92.             pData[j]=a;  
  93.             // 准备入栈,此时各指针顺序:[l...ji...ir],j处元素就是以排好序的划分元素a  
  94.             jstack += 2;  
  95.             // 将指针压入堆栈中更大的数组,立刻处理较小的数组  
  96.             if (jstack >= NSTACK) throw("NSTACK too small in sort.");  
  97.             if (ir-i+1 >= j-l)       // 右端较长,压入右端部分  
  98.             {  
  99.                 istack[jstack]=ir;  
  100.                 istack[jstack-1]=i;  
  101.                 ir=j-1;             // 修改右端指针  
  102.             }   
  103.             else                    // 压入左端部分入栈  
  104.             {  
  105.                 istack[jstack]=j-1;  
  106.                 istack[jstack-1]=l;  
  107.                 l=i;                // 修改左端指针  
  108.             }  
  109.         }  
  110.     }  
  111. }  


这里我没有给出行为的分析,因为这个很简单,我们直接来分析算法:首先我们考虑最理想的情况 
1.数组的大小是2的幂,这样分下去始终可以被2整除。假设为2的k次方,即k=log2(n)。 
2.每次我们选择的值刚好是中间值,这样,数组才可以被等分。 
第一层递归,循环n次,第二层循环2*(n/2)...... 
所以共有n+2(n/2)+4(n/4)+...+n*(n/n) = n+n+n+...+n=k*n=log2(n)*n 
所以算法复杂度为O(log2(n)*n) 
其他的情况只会比这种情况差,最差的情况是每次选择到的middle都是最小值或最大值,那么他将变 
成交换法(由于使用了递归,情况更糟)。

第二种方法采用了一些优化方法,效率要更高,避免了出现最坏的情况 。
如果你担心这个问题,你可以使用堆排序,这是一种稳定的O(log2(n)*n)算法,但是通常情况下速度要慢 
于快速排序(因为要重组堆)。

7、堆排序

堆排序的完整实现。代表了一个大公司的整个生存循环:雇佣N/2个工人,雇佣N/2个潜在的管理者,有一个值为升迁的过程,它是一种超级Peter原理的排序;最后按注定的过程:每个原来的雇员被一次提升为总裁。

 

堆排序的过程是:

  1. 创建一个堆H[0..n-1]
  2. 把堆首(最大值)和堆尾互换
  3. 把堆的尺寸缩小1,并调用shift_down,目的是把新的数组顶端数据调整到相应位置,即重建堆
  4. 重复步骤2,直到堆的尺寸为1

view plain
  1. // 元素提升(上移),从[l,r]的元素中提出最大值作为堆[l,r]的堆顶。  
  2. // 创建堆时:假设堆的形式是公司组织的二叉树结构,首先有N/2个元素作为员工,然后每进入一个元素作为潜在管理者,  
  3. //           选择两个员工作为其直属下级,并从下级中选择较大值作为这两个员工的上级  
  4. //           并把新来的员工插入到被升级的员工的位置上去,直至进入N/4个员工,选出N/4个上层节点;  
  5. //           然后再进入节点时,就作为更高层次的潜在管理者,需要从下一级中找寻较大的作为此位置的真正管理者,如果下级的要升级,要带动更下级的升级  
  6. // 重建堆时:每次从堆顶的直接下一级中选择最大元素晋升为老板,同时带动更下级的升迁。  
  7. // 堆的特点:二叉树的形式,根为最大值(大根堆),节点向上的通路是相对排序的,但横向相邻的元素却没有必然的大小。  
  8. //  
  9. // 把一个线性数组理解为一个二叉树,那么索引i处元素的直接下级的第一个元素为2i+1。同层元素顺序存储  
  10. // 证明:设i所在树的层为a,其在改层第b个节点,则 i = 2^a-1+b, 其直接下级的第一个元素索引 j = 2^(a+1)-1+2b = 2i+1  
  11. void shift_down(int* pData, int l, int r)  
  12. {  
  13.     int j, jold;  
  14.     int a = pData[l];  
  15.     jold = l;       // jold用于存储a归位时的位置  
  16.     j = 2 * l + 1;  // j为l的直接下属的第一个元素  
  17.     while (j <= r)   
  18.     {  
  19.         // 与下属中较大的比较,找到比后面相邻的元素较大的元素  
  20.         if(j < r && pData[j] < pData[j+1]) j++;  
  21.         // 找到a所在的层,停止筛选。否则更新a,继续  
  22.         if (a >= pData[j]) break;      
  23.         // 把较大的元素提前  
  24.         pData[jold] = pData[j];  
  25.         jold = j;       // 更新a将要归位的位置  
  26.         j = 2 * j + 1;  
  27.     }  
  28.     pData[jold] = a;        // 将a归位  
  29. }  
  30. // 堆排序  
  31. void HeapSort(int* pData, int nCount)  
  32. {  
  33.     // 创建堆  
  34.     for(int i = nCount/2-1; i >= 0; i--)  
  35.         // 堆创建时,i作为shift_down的左范围从n/2-1 to 0  
  36.         shift_down(pData, i, nCount-1);  
  37.     // 堆排序  
  38.     for(int i = nCount-1; i > 0; i--)  
  39.     {  
  40.         // 堆选择时,shift_down的右范围从n-2 to 0  
  41.         std::swap(pData[0], pData[i]);  // 在数组尾部清出一个空间,使堆顶弹出并存入其中  
  42.         shift_down(pData, 0, i-1);      // 重建堆  
  43.     }  
  44. }  

堆排序的平均时间复杂度为O(nlogn),空间复杂度为Θ(1)

8、SHELL排序

view plain
  1. // 希尔排序,其中m数组中已有m个元素已排序  
  2. // 思路:直接插入法的变形。选取一个逐渐递减的增量序列,按此增量对数组进行分组按直接插入法排序,然后减小增量再分组排序  
  3. void ShellSort(int* pData, int nCount, int m)  
  4. {  
  5.     int i, j, inc, n = nCount;  
  6.     int v;  
  7.     if (m > 0) n = min(m, n);  
  8.     inc = 1; // 确定初始增量  
  9.     do  
  10.     {  
  11.         inc *= 3;  
  12.         inc++;  
  13.     } while (inc <= n);  
  14.     do  
  15.     {   
  16.         // 部分排序的循环  
  17.         inc /= 3;       // 减小增量  
  18.         for (i = inc; i < n; i++)          
  19.         { // 直接插入法的外循环  
  20.             v = pData[i];  
  21.             j = i;  
  22.             while (pData[j - inc] > v)  
  23.             { // 直接插入法的内循环  
  24.                 pData[j] = pData[j - inc];  
  25.                 j -= inc;  
  26.                 if (j < inc) break;  
  27.             }  
  28.             pData[j] = v;  
  29.         }  
  30.     } while (inc > 1);  
  31. }  
 

其他排序 

9、双向冒泡: 又叫鸡尾酒排序

通常的冒泡是单向的,而这里是双向的,也就是说还要进行反向的工作。 
代码看起来复杂,仔细理一下就明白了,是一个来回震荡的方式。 

view plain
  1. // 双向冒泡排序  
  2. void Bubble2Sort(int* pData, int nCount)   
  3. {   
  4.     int i;   
  5.     int left = 1;   
  6.     int right =nCount -1;   
  7.     int t;   
  8.     do   
  9.     {   
  10.         //正向的部分   
  11.         for (i = right; i >= left; i--)   
  12.         {   
  13.             if(pData[i] < pData[i-1])   
  14.             {   
  15.                 std::swap(pData[i], pData[i-1]);  
  16.                 t = i;   
  17.             }   
  18.         }   
  19.         left = t+1;   
  20.         //反向的部分   
  21.         for(i = left; i < right + 1; i++)   
  22.         {   
  23.             if(pData[i] < pData[i-1])   
  24.             {   
  25.                 std::swap(pData[i], pData[i-1]);  
  26.                 t = i;   
  27.             }   
  28.         }   
  29.         right = t-1;   
  30.     }while(left <= right);   
  31. }   

双向冒泡排序最糟或是平均所花费的次数都是O(n2),但如果序列在一开始已经大部分排序过的话,会接近O(n)

10、计数排序

算法的步骤如下:

  1. 找出待排序的数组中最大和最小的元素
  2. 统计数组中每个值为i的元素出现的次数,存入数组C的第i
  3. 对所有的计数累加(从C中的第一个元素开始,每一项和前一项相加)
  4. 反向填充目标数组:将每个元素i放在新数组的第C(i)项,每放一个元素就将C(i)减去1

view plain
  1. // 计数排序  
  2. void CountSort(int* pData, int nCount)  
  3. {  
  4.     int i, j;  
  5.     // 计算排序数组的可取值范围  
  6.     int minNum = pData[0], maxNum = pData[0];  
  7.     for(i = 1; i < nCount; i++)  
  8.     {  
  9.         if(pData[i] < minNum)  
  10.             minNum = pData[i];  
  11.         else if(pData[i] > maxNum)  
  12.             maxNum = pData[i];  
  13.     }  
  14.     // 计数数组  
  15.     int range = maxNum - minNum + 1;  
  16.     int *pTemp = new int[range];  
  17.     memset(pTemp, 0, range * sizeof(int));  
  18.     // 如果元素存在,对应位置计数加1  
  19.     for(i = 0; i < nCount; ++i)  
  20.     {  
  21.         pTemp[pData[i]-minNum]++;  
  22.     }  
  23.     // 按顺序输出  
  24.     int k = 0;  
  25.     for(i = minNum; i <= maxNum; ++i)  
  26.         for(j = 0; j < pTemp[i - minNum]; ++j)  
  27.             pData[k++] = i;  
  28.     delete pTemp;  
  29. }  
 

当输入的元素是 n 个 0 到 k 之间的整数时,它的运行时间是 Θ(n + k)。

11、基数排序

将所有待比较数值(正整数)统一为同样的数位长度,数位较短的数前面补零. 然后, 从最低位开始, 依次进行一次排序.这样从最低位排序一直到最高位排序完成以后, 数列就变成一个有序序列.
基数排序的方式可以采用LSD(Least significant digital)或MSD(Most significant digital),LSD的排序方式由键值的最右边开始,而MSD则相反,由键值的最左边开始。

view plain
  1. // 基数排序  
  2. // 思路:对数组元素每一位进行排序  
  3. void RadixSort(int* pData, int nCount, int maxDigits)  
  4. {  
  5.     const int base = 10;  
  6.     int arCountSort[10];  
  7.     int* pTemp = new int[nCount];  
  8.     int i, j;  
  9.     int t = 1;  
  10.     // LSD  
  11.     for(i = 0; i < maxDigits; ++i)  
  12.     {  
  13.         // 计数排序算子  
  14.         memset(arCountSort, 0, sizeof(int) * 10);  
  15.         for(j = 0; j < nCount; ++j)  
  16.         {  
  17.             arCountSort[pData[j]/t%base]++;  
  18.         }  
  19.         // 为了定位原元素的位置  
  20.         for(j = 1; j < 10; ++j)  
  21.         {  
  22.             arCountSort[j] += arCountSort[j-1];  
  23.         }  
  24.         // 按位排序好的数组写入临时数组里  
  25.         // 此处要用倒序,因为低位已经排过序,同一组优先进低位排序中靠后的  
  26.         for(j = nCount-1; j >= 0; --j)  
  27.         {  
  28.             int splitDigit = pData[j]/t%base;  
  29.             pTemp[arCountSort[splitDigit] - 1] = pData[j];  
  30.             arCountSort[splitDigit]--;  
  31.         }  
  32.         memcpy_s(pData, sizeof(int) * nCount, pTemp, sizeof(int) * nCount);  
  33.         t *= base;  
  34.     }  
  35. }  


MSD算法代码:

view plain
  1. // MSD法,基数排序+计数排序  
  2. void RadixSort(int* pData, int index, int nCount, int maxDigits)  
  3. {  
  4.     if(nCount <= 1 || maxDigits <= 0)  
  5.         return;  
  6.     const int base = 10;    
  7.     int arCountSort[base];      // 各位元素计数数组  
  8.     int arCountSortTemp[base];  
  9.     int* pTemp = new int[nCount];    
  10.     int i;    
  11.     int t = (int)pow((float)base, maxDigits-1);  
  12.       
  13.     // 计数排序算子    
  14.     memset(arCountSort, 0, sizeof(int) * base);    
  15.     for(i = index; i < index + nCount; ++i)    
  16.     {    
  17.         arCountSort[pData[i]/t%base]++;    
  18.     }  
  19.     memcpy_s(arCountSortTemp, sizeof(int)*base, arCountSort, sizeof(int)*base);  
  20.     // 为了定位原元素的位置,此处每个arCountSort数组存放的是当前位前所有值的个数  
  21.     for(i = 1; i < base; ++i)    
  22.     {    
  23.         arCountSort[i] += arCountSort[i-1];    
  24.     }  
  25.     for(i = 0; i < base; ++i)  
  26.     {  
  27.         arCountSort[i] -= arCountSortTemp[i];  
  28.     }  
  29.     // 正序输出  
  30.     for(i = index; i < index + nCount; ++i)  
  31.     {  
  32.         int splitDigit = pData[i]/t%base;    
  33.         // splitDigit也为当前值在arCountSort表示的桶内的索引  
  34.         pTemp[arCountSort[splitDigit]] = pData[i];    
  35.         arCountSort[splitDigit]++;    
  36.     }  
  37.     memcpy_s(pData + index, sizeof(int) * nCount, pTemp, sizeof(int) * nCount);  
  38.     // 递归  
  39.     RadixSort(pData, index, arCountSort[0], maxDigits-1);  
  40.     for(i = 1; i < base; ++i)  
  41.     {  
  42.         RadixSort(pData, index + arCountSort[i-1], arCountSort[i] - arCountSort[i-1], maxDigits-1);  
  43.     }  
  44.     delete pTemp;  
  45. }  
  46. void RadixSortMSD(int* pData, int nCount, int maxDigits)  
  47. {  
  48.     RadixSort(pData, 0, nCount, maxDigits);  
  49.     printf_s("基数排序结果:");  
  50.     for(int i = 0; i < nCount; i++)  
  51.     {  
  52.         printf_s("%d ", pData[i]);  
  53.     }  
  54.     printf_s("/n");  
  55. }  
0 0