彻底理解各种排序算法

原文地址为: 彻底理解各种排序算法

 

彻底理解各种排序算法

分类: 算法   61人阅读  评论(0)  收藏  举报

有个小小的强迫症,总感觉写上转载,使得标题前面是黑色很让人讨厌,但还是要分享过来!

转自:排序

1、冒泡排序(Bubbler Sort)

前面刚说了冒泡排序的坏话,但冒泡排序也有其优点,那就是好理解,稳定,再就是空间复杂度低,不需要额外开辟数组元素的临时保存控件,当然了,编写起来也容易。

其算法很简单,就是比较数组相邻的两个值,把大的像泡泡一样“冒”到数组后面去,一共要执行N的平方除以2这么多次的比较和交换的操作(N为数组元素),其复杂度为Ο(n²),如图:

 

 2、直接插入排序(Straight Insertion Sort)

冒泡法对于已经排好序的部分(上图中,数组显示为白色底色的部分)是不再访问的,插入排序却要,因为它的方法就是从未排序的部分中取出一个元素,插入到已经排好序的部分去,插入的位置我是从后往前找的,这样可以使得如果数组本身是有序(顺序)的话,速度会非常之快,不过反过来,数组本身是逆序的话,速度也就非常之慢了,如图:

3、二分插入排序(Binary Insertion Sort)

这是对直接插入排序的改进,由于已排好序的部分是有序的,所以我们就能使用二分查找法确定我们的插入位置,而不是一个个找,除了这点,它跟插入排序没什么区别,至于二分查找法见我前面的文章(本系列文章的第四篇)。图跟上图没什么差别,差别在于插入位置的确定而已,性能却能因此得到不少改善。(性能分析后面会提到)

4、直接选择排序(Straight Selection Sort)

这是我在学数据结构前,自己能够想得出来的排序法,思路很简单,用打擂台的方式,找出最大的一个元素,和末尾的元素交换,然后再从头开始,查找第1个到第N-1个元素中最大的一个,和第N-1个元素交换……其实差不多就是冒泡法的思想,但整个过程中需要移动的元素比冒泡法要少,因此性能是比冒泡法优秀的。看图:

5、快速排序(Quick Sort)

快速排序是非常优秀的排序算法,初学者可能觉得有点难理解,其实它是一种“分而治之”的思想,把大的拆分为小的,小的再拆分为更小的,所以你一会儿从代码中就能很清楚地看到,用了递归。如图:

其中要选择一个轴值,这个轴值在理想的情况下就是中轴,中轴起的作用就是让其左边的元素比它小,它右边的元素不小于它。(我用了“不小于”而不是“大于”是考虑到元素数值会有重复的情况,在代码中也能看出来,如果把“>=”运算符换成“>”,将会出问题)当然,如果中轴选得不好,选了个最大元素或者最小元素,那情况就比较糟糕,我选轴值的办法是取出第一个元素,中间的元素和最后一个元素,然后从这三个元素中选中间值,这已经可以应付绝大多数情况。

6、改进型快速排序(Improved Quick Sort)

快速排序的缺点是使用了递归,如果数据量很大,大量的递归调用会不会导致性能下降呢?我想应该会的,所以我打算作这么种优化,考虑到数据量很小的情况下,直接选择排序和快速排序的性能相差无几,那当递归到子数组元素数目小于30的时候,我就是用直接选择排序,这样会不会提高一点性能呢?我后面分析。排序过程可以参考前面两个图,我就不另外画了。

7、桶排序(Bucket Sort)

这是迄今为止最快的一种排序法,其时间复杂度仅为Ο(n),也就是线性复杂度!不可思议吧?但它是有条件的。举个例子:一年的全国高考考生人数为500万,分数使用标准分,最低100,最高900,没有小数,你把这500万元素的数组排个序。我们抓住了这么个非常特殊的条件,就能在毫秒级内完成这500万的排序,那就是:最低100,最高900,没有小数,那一共可出现的分数可能有多少种呢?一共有900-100+1=801,那么多种,想想看,有没有什么“投机取巧”的办法?方法就是创建801个“桶”,从头到尾遍历一次数组,对不同的分数给不同的“桶”加料,比如有个考生考了500分,那么就给500分的那个桶(下标为500-100)加1,完成后遍历一下这个桶数组,按照桶值,填充原数组,100分的有1000人,于是从0填到999,都填1000,101分的有1200人,于是从1000到2019,都填入101……如图:

很显然,如果分数不是从100到900的整数,而是从0到2亿,那就要分配2亿个桶了,这是不可能的,所以桶排序有其局限性,适合元素值集合并不大的情况。

8、基数排序(Radix Sort)

基数排序是对桶排序的一种改进,这种改进是让“桶排序”适合于更大的元素值集合的情况,而不是提高性能。它的思想是这样的,比如数值的集合是8位整数,我们很难创建一亿个桶,于是我们先对这些数的个位进行类似桶排序的排序(下文且称作“类桶排序”吧),然后再对这些数的十位进行类桶排序,再就是百位……一共做8次,当然,我说的是思路,实际上我们通常并不这么干,因为C++的位移运算速度是比较快,所以我们通常以“字节”为单位进行桶排序。但下图为了画图方便,我是以半字节(4 bit)为单位进行类桶排序的,因为字节为单位进行桶排得画256个桶,有点难画,如图:

基数排序适合数值分布较广的情况,但由于需要额外分配一个跟原始数组一样大的暂存空间,它的处理也是有局限性的,对于元素数量巨大的原始数组而言,空间开销较大。性能上由于要多次“类桶排序”,所以不如桶排序。但它的复杂度跟桶排序一样,也是Ο(n),虽然它用了多次循环,但却没有循环嵌套。

9、性能分析和总结

先不分析复杂度为Ο(n)的算法,因为速度太快,而且有些条件限制,我们先分析前六种算法,即:冒泡,直接插入,二分插入,直接选择,快速排序和改进型快速排序。

我的分析过程并不复杂,尝试产生一个随机数数组,数值范围是0到7FFF,这正好可以用C++的随机函数rand()产生随机数来填充数组,然后尝试不同长度的数组,同一种长度的数组尝试10次,以此得出平均值,避免过多波动,最后用Excel对结果进行分析,OK,上图了。

最差的一眼就看出来了,是冒泡,直接插入和直接选择旗鼓相当,但我更偏向于使用直接选择,因为思路简单,需要移动的元素相对较少,况且速度还稍微快一点呢,从图中看,二分插入的速度比直接插入有了较大的提升,但代码稍微长了一点点。

令人感到比较意外的是快速排序,3万点以内的快速排序所消耗的时间几乎可以忽略不计,速度之快,令人振奋,而改进型快速排序的线跟快速排序重合,因此不画出来。看来要对快速排序进行单独分析,我加大了数组元素的数目,从5万到150万,画出下图:

可以看到,即便到了150万点,两种快速排序也仅需差不多半秒钟就完成了,实在快,改进型快速排序性能确实有微略提高,但并不明显,从图中也能看出来,是不是我设置的最小快速排序元素数目不太合适?但我尝试了好几个值都相差无几。

最后看线性复杂度的排序,速度非常惊人,我从40万测试到1200万,结果如图:

可见稍微调整下算法,速度可以得到质的飞升,而不是我们以前所认为的那样:再快也不会比冒泡法快多少啊?

我最后制作一张表,比较一下这些排序法:

还有一个最后:附上我的代码。

  1. #include "stdio.h"  
  2. #include "stdlib.h"  
  3. #include "time.h"  
  4. #include "string.h"  
  5.   
  6. void BubblerSort(int *pArray, int iElementNum);  
  7. void StraightInsertionSort(int *pArray, int iElementNum);  
  8. void BinaryInsertionSort(int *pArray, int iElementNum);  
  9. void StraightSelectionSort(int *pArray, int iElementNum);  
  10. void QuickSort(int *pArray, int iElementNum);  
  11. void ImprovedQuickSort(int *pArray, int iElementNum);  
  12. void BucketSort(int *pArray, int iElementNum);  
  13. void RadixSort(int *pArray, int iElementNum);  
  14.   
  15. //Tool functions.  
  16. void PrintArray(int *pArray, int iElementNum);  
  17. void StuffArray(int *pArray, int iElementNum);  
  18.   
  19. inline void Swap(int& a, int& b);  
  20.   
  21. #define SINGLE_TEST  
  22.   
  23. int main(int argc, char* argv[])  
  24. {  
  25.     srand(time(NULL));  
  26. #ifndef SINGLE_TEST  
  27.     int i, j, iTenTimesAvg;  
  28.     for(i=50000; i<=1500000; i+=50000)  
  29.     {  
  30.         iTenTimesAvg = 0;  
  31.         for(j=0; j<10; j++)  
  32.         {  
  33.             int iElementNum = i;  
  34.             int *pArr = new int[iElementNum];  
  35.             StuffArray(pArr, iElementNum);  
  36.             //PrintArray(pArr, iElementNum);  
  37.             clock_t ctBegin = clock();  
  38.             ImprovedQuickSort(pArr, iElementNum);  
  39.             //PrintArray(pArr, iElementNum);  
  40.             clock_t ctEnd = clock();  
  41.             delete[] pArr;  
  42.   
  43.             iTenTimesAvg += ctEnd-ctBegin;  
  44.         }  
  45.         printf("%d\t%d\n", i, iTenTimesAvg/10);  
  46.     }  
  47. #else  
  48.     //Single test  
  49.     int iElementNum = 100;  
  50.     int *pArr = new int[iElementNum];  
  51.     StuffArray(pArr, iElementNum);  
  52.     PrintArray(pArr, iElementNum);  
  53.     clock_t ctBegin = clock();  
  54.     QuickSort(pArr, iElementNum);  
  55.     clock_t ctEnd = clock();  
  56.     PrintArray(pArr, iElementNum);  
  57.     delete[] pArr;  
  58.     int iTenTimesAvg = ctEnd-ctBegin;  
  59.     printf("%d\t%d\n", iElementNum, iTenTimesAvg);  
  60. #endif  
  61.     return 0;  
  62. }  
  63.   
  64. void BubblerSort(int *pArray, int iElementNum)  
  65. {  
  66.     int i, j, x;  
  67.     for(i=0; i<iElementNum-1; i++)  
  68.     {  
  69.         for(j=0; j<iElementNum-1-i; j++)  
  70.         {  
  71.             if(pArray[j]>pArray[j+1])  
  72.             {  
  73.                 //Frequent swap calling may lower performance.  
  74.                 //Swap(pArray[j], pArray[j+1]);  
  75.   
  76.                 //Do you think bit operation is better? No! Please have a try.  
  77.                 //pArray[j] ^= pArray[j+1];  
  78.                 //pArray[j+1] ^= pArray[j];  
  79.                 //pArray[j] ^= pArray[j+1];  
  80.                   
  81.                 //This kind of traditional swap is the best.  
  82.                 x = pArray[j];  
  83.                 pArray[j] = pArray[j+1];  
  84.                 pArray[j+1] = x;  
  85.             }  
  86.         }  
  87.     }  
  88. }  
  89.   
  90. void StraightInsertionSort(int *pArray, int iElementNum)  
  91. {  
  92.     int i, j, k;  
  93.     for(i=0; i<iElementNum; i++)  
  94.     {  
  95.         int iHandling = pArray[i];  
  96.         for(j=i; j>0; j--)  
  97.         {  
  98.             if(iHandling>=pArray[j-1])  
  99.                 break;  
  100.         }  
  101.   
  102.         for(k=i; k>j; k--)  
  103.             pArray[k] = pArray[k-1];  
  104.         pArray[j] = iHandling;  
  105.     }  
  106. }  
  107.   
  108. void BinaryInsertionSort(int *pArray, int iElementNum)  
  109. {  
  110.     int i, j, k;  
  111.     for(i=0; i<iElementNum; i++)  
  112.     {  
  113.         int iHandling = pArray[i];  
  114.           
  115.         int iLeft = 0;  
  116.         int iRight = i-1;  
  117.         while(iLeft<=iRight)  
  118.         {  
  119.             int iMiddle = (iLeft+iRight)/2;  
  120.             if(iHandling < pArray[iMiddle])  
  121.             {  
  122.                 iRight = iMiddle-1;  
  123.             }  
  124.             else if(iHandling > pArray[iMiddle])  
  125.             {  
  126.                 iLeft = iMiddle+1;  
  127.             }  
  128.             else  
  129.             {  
  130.                 j = iMiddle + 1;  
  131.                 break;  
  132.             }  
  133.         }  
  134.   
  135.         if(iLeft>iRight)  
  136.             j = iLeft;  
  137.           
  138.         for(k=i; k>j; k--)  
  139.             pArray[k] = pArray[k-1];  
  140.         pArray[j] = iHandling;  
  141.     }  
  142. }  
  143.   
  144. void StraightSelectionSort(int *pArray, int iElementNum)  
  145. {  
  146.     int iEndIndex, i, iMaxIndex, x;  
  147.   
  148.     for(iEndIndex=iElementNum-1; iEndIndex>0; iEndIndex--)  
  149.     {  
  150.         for(i=0, iMaxIndex=0; i<iEndIndex; i++)  
  151.         {  
  152.             if(pArray[i]>=pArray[iMaxIndex])  
  153.                 iMaxIndex = i;  
  154.         }  
  155.         x = pArray[iMaxIndex];  
  156.         pArray[iMaxIndex] = pArray[iEndIndex];  
  157.         pArray[iEndIndex] = x;  
  158.     }      
  159. }  
  160.   
  161. void BucketSort(int *pArray, int iElementNum)  
  162. {  
  163.     //This is really buckets.  
  164.     int buckets[RAND_MAX];  
  165.     memset(buckets, 0, sizeof(buckets));  
  166.     int i;  
  167.     for(i=0; i<iElementNum; i++)  
  168.     {  
  169.         ++buckets[pArray[i]-1];  
  170.     }  
  171.   
  172.     int iAdded = 0;  
  173.     for(i=0; i<RAND_MAX; i++)  
  174.     {  
  175.         while((buckets[i]--)>0)  
  176.         {  
  177.             pArray[iAdded++] = i;  
  178.         }  
  179.     }  
  180. }  
  181.   
  182. void RadixSort(int *pArray, int iElementNum)  
  183. {  
  184.     int *pTmpArray = new int[iElementNum];  
  185.   
  186.     int buckets[0x100];  
  187.     memset(buckets, 0, sizeof(buckets));  
  188.     int i;  
  189.     for(i=0; i<iElementNum; i++)  
  190.     {  
  191.         ++buckets[(pArray[i])&0xFF];  
  192.     }  
  193.       
  194.     //Convert number to offset  
  195.     int iPrevNum = buckets[0];  
  196.     buckets[0] = 0;  
  197.     int iThisNum;  
  198.     for(i=1; i<0x100; i++)  
  199.     {  
  200.         iThisNum = buckets[i];  
  201.         buckets[i] = buckets[i-1] + iPrevNum;  
  202.         iPrevNum = iThisNum;  
  203.     }  
  204.   
  205.     for(i=0; i<iElementNum; i++)  
  206.     {  
  207.         pTmpArray[buckets[(pArray[i])&0xFF]++] = pArray[i];  
  208.     }  
  209.   
  210.     //////////////////////////////////////////////////////////////////////////  
  211.     memset(buckets, 0, sizeof(buckets));  
  212.     for(i=0; i<iElementNum; i++)  
  213.     {  
  214.         ++buckets[(pTmpArray[i]>>8)&0xFF];  
  215.     }  
  216.   
  217.     //Convert number to offset  
  218.     iPrevNum = buckets[0];  
  219.     buckets[0] = 0;  
  220.     iThisNum;  
  221.     for(i=1; i<0x100; i++)  
  222.     {  
  223.         iThisNum = buckets[i];  
  224.         buckets[i] = buckets[i-1] + iPrevNum;  
  225.         iPrevNum = iThisNum;  
  226.     }  
  227.   
  228.     for(i=0; i<iElementNum; i++)  
  229.     {  
  230.         pArray[buckets[((pTmpArray[i]>>8)&0xFF)]++] = pTmpArray[i];  
  231.     }  
  232.   
  233.     delete[] pTmpArray;  
  234. }  
  235.   
  236. void QuickSort(int *pArray, int iElementNum)  
  237. {  
  238.     int iTmp;  
  239.   
  240.     //Select the pivot make it to the right side.  
  241.     int& iLeftIdx = pArray[0];  
  242.     int& iRightIdx = pArray[iElementNum-1];  
  243.     int& iMiddleIdx = pArray[(iElementNum-1)/2];  
  244.     if(iLeftIdx>iMiddleIdx)  
  245.     {  
  246.         iTmp = iLeftIdx;  
  247.         iLeftIdx = iMiddleIdx;  
  248.         iMiddleIdx = iTmp;  
  249.     }  
  250.     if(iRightIdx>iMiddleIdx)  
  251.     {  
  252.         iTmp = iRightIdx;  
  253.         iRightIdx = iMiddleIdx;  
  254.         iMiddleIdx = iTmp;  
  255.     }  
  256.     if(iLeftIdx>iRightIdx)  
  257.     {  
  258.         iTmp = iLeftIdx;  
  259.         iLeftIdx = iRightIdx;  
  260.         iRightIdx = iTmp;  
  261.     }  
  262.   
  263.     //Make pivot's left element and right element.  
  264.     int iLeft = 0;  
  265.     int iRight = iElementNum-2;  
  266.     int& iPivot = pArray[iElementNum-1];  
  267.     while (1)  
  268.     {  
  269.         while (iLeft<iRight && pArray[iLeft]<iPivot) ++iLeft;  
  270.         while (iLeft<iRight && pArray[iRight]>=iPivot) --iRight;  
  271.         if(iLeft>=iRight)  
  272.             break;  
  273.         iTmp = pArray[iLeft];  
  274.         pArray[iLeft] = pArray[iRight];  
  275.         pArray[iRight] = iTmp;  
  276.     }  
  277.   
  278.     //Make the i  
  279.     if(pArray[iLeft]>iPivot)  
  280.     {  
  281.         iTmp = pArray[iLeft];  
  282.         pArray[iLeft] = iPivot;  
  283.         iPivot = iTmp;  
  284.     }  
  285.   
  286.     if(iLeft>1)  
  287.         QuickSort(pArray, iLeft);  
  288.   
  289.     if(iElementNum-iLeft-1>=1)  
  290.         QuickSort(&pArray[iLeft+1], iElementNum-iLeft-1);  
  291.   
  292. }  
  293.   
  294. void ImprovedQuickSort(int *pArray, int iElementNum)  
  295. {  
  296.     int iTmp;  
  297.       
  298.     //Select the pivot make it to the right side.  
  299.     int& iLeftIdx = pArray[0];  
  300.     int& iRightIdx = pArray[iElementNum-1];  
  301.     int& iMiddleIdx = pArray[(iElementNum-1)/2];  
  302.     if(iLeftIdx>iMiddleIdx)  
  303.     {  
  304.         iTmp = iLeftIdx;  
  305.         iLeftIdx = iMiddleIdx;  
  306.         iMiddleIdx = iTmp;  
  307.     }  
  308.     if(iRightIdx>iMiddleIdx)  
  309.     {  
  310.         iTmp = iRightIdx;  
  311.         iRightIdx = iMiddleIdx;  
  312.         iMiddleIdx = iTmp;  
  313.     }  
  314.     if(iLeftIdx>iRightIdx)  
  315.     {  
  316.         iTmp = iLeftIdx;  
  317.         iLeftIdx = iRightIdx;  
  318.         iRightIdx = iTmp;  
  319.     }  
  320.       
  321.     //Make pivot's left element and right element.  
  322.     int iLeft = 0;  
  323.     int iRight = iElementNum-2;  
  324.     int& iPivot = pArray[iElementNum-1];  
  325.     while (1)  
  326.     {  
  327.         while (iLeft<iRight && pArray[iLeft]<iPivot) ++iLeft;  
  328.         while (iLeft<iRight && pArray[iRight]>=iPivot) --iRight;  
  329.         if(iLeft>=iRight)  
  330.             break;  
  331.         iTmp = pArray[iLeft];  
  332.         pArray[iLeft] = pArray[iRight];  
  333.         pArray[iRight] = iTmp;  
  334.     }  
  335.       
  336.     //Make the i  
  337.     if(pArray[iLeft]>iPivot)  
  338.     {  
  339.         iTmp = pArray[iLeft];  
  340.         pArray[iLeft] = iPivot;  
  341.         iPivot = iTmp;  
  342.     }  
  343.       
  344.     if(iLeft>30)  
  345.         ImprovedQuickSort(pArray, iLeft);  
  346.     else  
  347.         StraightSelectionSort(pArray, iLeft);  
  348.       
  349.     if(iElementNum-iLeft-1>=30)  
  350.         ImprovedQuickSort(&pArray[iLeft+1], iElementNum-iLeft-1);  
  351.     else  
  352.         StraightSelectionSort(&pArray[iLeft+1], iElementNum-iLeft-1);  
  353. }  
  354.   
  355. void StuffArray(int *pArray, int iElementNum)  
  356. {  
  357.     int i;  
  358.     for(i=0; i<iElementNum; i++)  
  359.     {  
  360.         pArray[i] = rand();  
  361.     }  
  362. }  
  363.   
  364. void PrintArray(int *pArray, int iElementNum)  
  365. {  
  366.     int i;  
  367.     for(i=0; i<iElementNum; i++)  
  368.     {  
  369.         printf("%d ", pArray[i]);  
  370.     }  
  371.     printf("\n\n");  
  372. }  
  373.   
  374. void Swap(int& a, int& b)  
  375. {  
  376.     int c = a;  
  377.     a = b;  
  378.     b = c;  
  379. }  


转载请注明本文地址: 彻底理解各种排序算法

有个小小的强迫症,总感觉写上转载,使得标题前面是黑色很让人讨厌,但还是要分享过来!

转自:排序

1、冒泡排序(Bubbler Sort)

前面刚说了冒泡排序的坏话,但冒泡排序也有其优点,那就是好理解,稳定,再就是空间复杂度低,不需要额外开辟数组元素的临时保存控件,当然了,编写起来也容易。

其算法很简单,就是比较数组相邻的两个值,把大的像泡泡一样“冒”到数组后面去,一共要执行N的平方除以2这么多次的比较和交换的操作(N为数组元素),其复杂度为Ο(n²),如图:

 

 2、直接插入排序(Straight Insertion Sort)

冒泡法对于已经排好序的部分(上图中,数组显示为白色底色的部分)是不再访问的,插入排序却要,因为它的方法就是从未排序的部分中取出一个元素,插入到已经排好序的部分去,插入的位置我是从后往前找的,这样可以使得如果数组本身是有序(顺序)的话,速度会非常之快,不过反过来,数组本身是逆序的话,速度也就非常之慢了,如图:

3、二分插入排序(Binary Insertion Sort)

这是对直接插入排序的改进,由于已排好序的部分是有序的,所以我们就能使用二分查找法确定我们的插入位置,而不是一个个找,除了这点,它跟插入排序没什么区别,至于二分查找法见我前面的文章(本系列文章的第四篇)。图跟上图没什么差别,差别在于插入位置的确定而已,性能却能因此得到不少改善。(性能分析后面会提到)

4、直接选择排序(Straight Selection Sort)

这是我在学数据结构前,自己能够想得出来的排序法,思路很简单,用打擂台的方式,找出最大的一个元素,和末尾的元素交换,然后再从头开始,查找第1个到第N-1个元素中最大的一个,和第N-1个元素交换……其实差不多就是冒泡法的思想,但整个过程中需要移动的元素比冒泡法要少,因此性能是比冒泡法优秀的。看图:

5、快速排序(Quick Sort)

快速排序是非常优秀的排序算法,初学者可能觉得有点难理解,其实它是一种“分而治之”的思想,把大的拆分为小的,小的再拆分为更小的,所以你一会儿从代码中就能很清楚地看到,用了递归。如图:

其中要选择一个轴值,这个轴值在理想的情况下就是中轴,中轴起的作用就是让其左边的元素比它小,它右边的元素不小于它。(我用了“不小于”而不是“大于”是考虑到元素数值会有重复的情况,在代码中也能看出来,如果把“>=”运算符换成“>”,将会出问题)当然,如果中轴选得不好,选了个最大元素或者最小元素,那情况就比较糟糕,我选轴值的办法是取出第一个元素,中间的元素和最后一个元素,然后从这三个元素中选中间值,这已经可以应付绝大多数情况。

6、改进型快速排序(Improved Quick Sort)

快速排序的缺点是使用了递归,如果数据量很大,大量的递归调用会不会导致性能下降呢?我想应该会的,所以我打算作这么种优化,考虑到数据量很小的情况下,直接选择排序和快速排序的性能相差无几,那当递归到子数组元素数目小于30的时候,我就是用直接选择排序,这样会不会提高一点性能呢?我后面分析。排序过程可以参考前面两个图,我就不另外画了。

7、桶排序(Bucket Sort)

这是迄今为止最快的一种排序法,其时间复杂度仅为Ο(n),也就是线性复杂度!不可思议吧?但它是有条件的。举个例子:一年的全国高考考生人数为500万,分数使用标准分,最低100,最高900,没有小数,你把这500万元素的数组排个序。我们抓住了这么个非常特殊的条件,就能在毫秒级内完成这500万的排序,那就是:最低100,最高900,没有小数,那一共可出现的分数可能有多少种呢?一共有900-100+1=801,那么多种,想想看,有没有什么“投机取巧”的办法?方法就是创建801个“桶”,从头到尾遍历一次数组,对不同的分数给不同的“桶”加料,比如有个考生考了500分,那么就给500分的那个桶(下标为500-100)加1,完成后遍历一下这个桶数组,按照桶值,填充原数组,100分的有1000人,于是从0填到999,都填1000,101分的有1200人,于是从1000到2019,都填入101……如图:

很显然,如果分数不是从100到900的整数,而是从0到2亿,那就要分配2亿个桶了,这是不可能的,所以桶排序有其局限性,适合元素值集合并不大的情况。

8、基数排序(Radix Sort)

基数排序是对桶排序的一种改进,这种改进是让“桶排序”适合于更大的元素值集合的情况,而不是提高性能。它的思想是这样的,比如数值的集合是8位整数,我们很难创建一亿个桶,于是我们先对这些数的个位进行类似桶排序的排序(下文且称作“类桶排序”吧),然后再对这些数的十位进行类桶排序,再就是百位……一共做8次,当然,我说的是思路,实际上我们通常并不这么干,因为C++的位移运算速度是比较快,所以我们通常以“字节”为单位进行桶排序。但下图为了画图方便,我是以半字节(4 bit)为单位进行类桶排序的,因为字节为单位进行桶排得画256个桶,有点难画,如图:

基数排序适合数值分布较广的情况,但由于需要额外分配一个跟原始数组一样大的暂存空间,它的处理也是有局限性的,对于元素数量巨大的原始数组而言,空间开销较大。性能上由于要多次“类桶排序”,所以不如桶排序。但它的复杂度跟桶排序一样,也是Ο(n),虽然它用了多次循环,但却没有循环嵌套。

9、性能分析和总结

先不分析复杂度为Ο(n)的算法,因为速度太快,而且有些条件限制,我们先分析前六种算法,即:冒泡,直接插入,二分插入,直接选择,快速排序和改进型快速排序。

我的分析过程并不复杂,尝试产生一个随机数数组,数值范围是0到7FFF,这正好可以用C++的随机函数rand()产生随机数来填充数组,然后尝试不同长度的数组,同一种长度的数组尝试10次,以此得出平均值,避免过多波动,最后用Excel对结果进行分析,OK,上图了。

最差的一眼就看出来了,是冒泡,直接插入和直接选择旗鼓相当,但我更偏向于使用直接选择,因为思路简单,需要移动的元素相对较少,况且速度还稍微快一点呢,从图中看,二分插入的速度比直接插入有了较大的提升,但代码稍微长了一点点。

令人感到比较意外的是快速排序,3万点以内的快速排序所消耗的时间几乎可以忽略不计,速度之快,令人振奋,而改进型快速排序的线跟快速排序重合,因此不画出来。看来要对快速排序进行单独分析,我加大了数组元素的数目,从5万到150万,画出下图:

可以看到,即便到了150万点,两种快速排序也仅需差不多半秒钟就完成了,实在快,改进型快速排序性能确实有微略提高,但并不明显,从图中也能看出来,是不是我设置的最小快速排序元素数目不太合适?但我尝试了好几个值都相差无几。

最后看线性复杂度的排序,速度非常惊人,我从40万测试到1200万,结果如图:

可见稍微调整下算法,速度可以得到质的飞升,而不是我们以前所认为的那样:再快也不会比冒泡法快多少啊?

我最后制作一张表,比较一下这些排序法:

还有一个最后:附上我的代码。

  1. #include "stdio.h"  
  2. #include "stdlib.h"  
  3. #include "time.h"  
  4. #include "string.h"  
  5.   
  6. void BubblerSort(int *pArray, int iElementNum);  
  7. void StraightInsertionSort(int *pArray, int iElementNum);  
  8. void BinaryInsertionSort(int *pArray, int iElementNum);  
  9. void StraightSelectionSort(int *pArray, int iElementNum);  
  10. void QuickSort(int *pArray, int iElementNum);  
  11. void ImprovedQuickSort(int *pArray, int iElementNum);  
  12. void BucketSort(int *pArray, int iElementNum);  
  13. void RadixSort(int *pArray, int iElementNum);  
  14.   
  15. //Tool functions.  
  16. void PrintArray(int *pArray, int iElementNum);  
  17. void StuffArray(int *pArray, int iElementNum);  
  18.   
  19. inline void Swap(int& a, int& b);  
  20.   
  21. #define SINGLE_TEST  
  22.   
  23. int main(int argc, char* argv[])  
  24. {  
  25.     srand(time(NULL));  
  26. #ifndef SINGLE_TEST  
  27.     int i, j, iTenTimesAvg;  
  28.     for(i=50000; i<=1500000; i+=50000)  
  29.     {  
  30.         iTenTimesAvg = 0;  
  31.         for(j=0; j<10; j++)  
  32.         {  
  33.             int iElementNum = i;  
  34.             int *pArr = new int[iElementNum];  
  35.             StuffArray(pArr, iElementNum);  
  36.             //PrintArray(pArr, iElementNum);  
  37.             clock_t ctBegin = clock();  
  38.             ImprovedQuickSort(pArr, iElementNum);  
  39.             //PrintArray(pArr, iElementNum);  
  40.             clock_t ctEnd = clock();  
  41.             delete[] pArr;  
  42.   
  43.             iTenTimesAvg += ctEnd-ctBegin;  
  44.         }  
  45.         printf("%d\t%d\n", i, iTenTimesAvg/10);  
  46.     }  
  47. #else  
  48.     //Single test  
  49.     int iElementNum = 100;  
  50.     int *pArr = new int[iElementNum];  
  51.     StuffArray(pArr, iElementNum);  
  52.     PrintArray(pArr, iElementNum);  
  53.     clock_t ctBegin = clock();  
  54.     QuickSort(pArr, iElementNum);  
  55.     clock_t ctEnd = clock();  
  56.     PrintArray(pArr, iElementNum);  
  57.     delete[] pArr;  
  58.     int iTenTimesAvg = ctEnd-ctBegin;  
  59.     printf("%d\t%d\n", iElementNum, iTenTimesAvg);  
  60. #endif  
  61.     return 0;  
  62. }  
  63.   
  64. void BubblerSort(int *pArray, int iElementNum)  
  65. {  
  66.     int i, j, x;  
  67.     for(i=0; i<iElementNum-1; i++)  
  68.     {  
  69.         for(j=0; j<iElementNum-1-i; j++)  
  70.         {  
  71.             if(pArray[j]>pArray[j+1])  
  72.             {  
  73.                 //Frequent swap calling may lower performance.  
  74.                 //Swap(pArray[j], pArray[j+1]);  
  75.   
  76.                 //Do you think bit operation is better? No! Please have a try.  
  77.                 //pArray[j] ^= pArray[j+1];  
  78.                 //pArray[j+1] ^= pArray[j];  
  79.                 //pArray[j] ^= pArray[j+1];  
  80.                   
  81.                 //This kind of traditional swap is the best.  
  82.                 x = pArray[j];  
  83.                 pArray[j] = pArray[j+1];  
  84.                 pArray[j+1] = x;  
  85.             }  
  86.         }  
  87.     }  
  88. }  
  89.   
  90. void StraightInsertionSort(int *pArray, int iElementNum)  
  91. {  
  92.     int i, j, k;  
  93.     for(i=0; i<iElementNum; i++)  
  94.     {  
  95.         int iHandling = pArray[i];  
  96.         for(j=i; j>0; j--)  
  97.         {  
  98.             if(iHandling>=pArray[j-1])  
  99.                 break;  
  100.         }  
  101.   
  102.         for(k=i; k>j; k--)  
  103.             pArray[k] = pArray[k-1];  
  104.         pArray[j] = iHandling;  
  105.     }  
  106. }  
  107.   
  108. void BinaryInsertionSort(int *pArray, int iElementNum)  
  109. {  
  110.     int i, j, k;  
  111.     for(i=0; i<iElementNum; i++)  
  112.     {  
  113.         int iHandling = pArray[i];  
  114.           
  115.         int iLeft = 0;  
  116.         int iRight = i-1;  
  117.         while(iLeft<=iRight)  
  118.         {  
  119.             int iMiddle = (iLeft+iRight)/2;  
  120.             if(iHandling < pArray[iMiddle])  
  121.             {  
  122.                 iRight = iMiddle-1;  
  123.             }  
  124.             else if(iHandling > pArray[iMiddle])  
  125.             {  
  126.                 iLeft = iMiddle+1;  
  127.             }  
  128.             else  
  129.             {  
  130.                 j = iMiddle + 1;  
  131.                 break;  
  132.             }  
  133.         }  
  134.   
  135.         if(iLeft>iRight)  
  136.             j = iLeft;  
  137.           
  138.         for(k=i; k>j; k--)  
  139.             pArray[k] = pArray[k-1];  
  140.         pArray[j] = iHandling;  
  141.     }  
  142. }  
  143.   
  144. void StraightSelectionSort(int *pArray, int iElementNum)  
  145. {  
  146.     int iEndIndex, i, iMaxIndex, x;  
  147.   
  148.     for(iEndIndex=iElementNum-1; iEndIndex>0; iEndIndex--)  
  149.     {  
  150.         for(i=0, iMaxIndex=0; i<iEndIndex; i++)  
  151.         {  
  152.             if(pArray[i]>=pArray[iMaxIndex])  
  153.                 iMaxIndex = i;  
  154.         }  
  155.         x = pArray[iMaxIndex];  
  156.         pArray[iMaxIndex] = pArray[iEndIndex];  
  157.         pArray[iEndIndex] = x;  
  158.     }      
  159. }  
  160.   
  161. void BucketSort(int *pArray, int iElementNum)  
  162. {  
  163.     //This is really buckets.  
  164.     int buckets[RAND_MAX];  
  165.     memset(buckets, 0, sizeof(buckets));  
  166.     int i;  
  167.     for(i=0; i<iElementNum; i++)  
  168.     {  
  169.         ++buckets[pArray[i]-1];  
  170.     }  
  171.   
  172.     int iAdded = 0;  
  173.     for(i=0; i<RAND_MAX; i++)  
  174.     {  
  175.         while((buckets[i]--)>0)  
  176.         {  
  177.             pArray[iAdded++] = i;  
  178.         }  
  179.     }  
  180. }  
  181.   
  182. void RadixSort(int *pArray, int iElementNum)  
  183. {  
  184.     int *pTmpArray = new int[iElementNum];  
  185.   
  186.     int buckets[0x100];  
  187.     memset(buckets, 0, sizeof(buckets));  
  188.     int i;  
  189.     for(i=0; i<iElementNum; i++)  
  190.     {  
  191.         ++buckets[(pArray[i])&0xFF];  
  192.     }  
  193.       
  194.     //Convert number to offset  
  195.     int iPrevNum = buckets[0];  
  196.     buckets[0] = 0;  
  197.     int iThisNum;  
  198.     for(i=1; i<0x100; i++)  
  199.     {  
  200.         iThisNum = buckets[i];  
  201.         buckets[i] = buckets[i-1] + iPrevNum;  
  202.         iPrevNum = iThisNum;  
  203.     }  
  204.   
  205.     for(i=0; i<iElementNum; i++)  
  206.     {  
  207.         pTmpArray[buckets[(pArray[i])&0xFF]++] = pArray[i];  
  208.     }  
  209.   
  210.     //////////////////////////////////////////////////////////////////////////  
  211.     memset(buckets, 0, sizeof(buckets));  
  212.     for(i=0; i<iElementNum; i++)  
  213.     {  
  214.         ++buckets[(pTmpArray[i]>>8)&0xFF];  
  215.     }  
  216.   
  217.     //Convert number to offset  
  218.     iPrevNum = buckets[0];  
  219.     buckets[0] = 0;  
  220.     iThisNum;  
  221.     for(i=1; i<0x100; i++)  
  222.     {  
  223.         iThisNum = buckets[i];  
  224.         buckets[i] = buckets[i-1] + iPrevNum;  
  225.         iPrevNum = iThisNum;  
  226.     }  
  227.   
  228.     for(i=0; i<iElementNum; i++)  
  229.     {  
  230.         pArray[buckets[((pTmpArray[i]>>8)&0xFF)]++] = pTmpArray[i];  
  231.     }  
  232.   
  233.     delete[] pTmpArray;  
  234. }  
  235.   
  236. void QuickSort(int *pArray, int iElementNum)  
  237. {  
  238.     int iTmp;  
  239.   
  240.     //Select the pivot make it to the right side.  
  241.     int& iLeftIdx = pArray[0];  
  242.     int& iRightIdx = pArray[iElementNum-1];  
  243.     int& iMiddleIdx = pArray[(iElementNum-1)/2];  
  244.     if(iLeftIdx>iMiddleIdx)  
  245.     {  
  246.         iTmp = iLeftIdx;  
  247.         iLeftIdx = iMiddleIdx;  
  248.         iMiddleIdx = iTmp;  
  249.     }  
  250.     if(iRightIdx>iMiddleIdx)  
  251.     {  
  252.         iTmp = iRightIdx;  
  253.         iRightIdx = iMiddleIdx;  
  254.         iMiddleIdx = iTmp;  
  255.     }  
  256.     if(iLeftIdx>iRightIdx)  
  257.     {  
  258.         iTmp = iLeftIdx;  
  259.         iLeftIdx = iRightIdx;  
  260.         iRightIdx = iTmp;  
  261.     }  
  262.   
  263.     //Make pivot's left element and right element.  
  264.     int iLeft = 0;  
  265.     int iRight = iElementNum-2;  
  266.     int& iPivot = pArray[iElementNum-1];  
  267.     while (1)  
  268.     {  
  269.         while (iLeft<iRight && pArray[iLeft]<iPivot) ++iLeft;  
  270.         while (iLeft<iRight && pArray[iRight]>=iPivot) --iRight;  
  271.         if(iLeft>=iRight)  
  272.             break;  
  273.         iTmp = pArray[iLeft];  
  274.         pArray[iLeft] = pArray[iRight];  
  275.         pArray[iRight] = iTmp;  
  276.     }  
  277.   
  278.     //Make the i  
  279.     if(pArray[iLeft]>iPivot)  
  280.     {  
  281.         iTmp = pArray[iLeft];  
  282.         pArray[iLeft] = iPivot;  
  283.         iPivot = iTmp;  
  284.     }  
  285.   
  286.     if(iLeft>1)  
  287.         QuickSort(pArray, iLeft);  
  288.   
  289.     if(iElementNum-iLeft-1>=1)  
  290.         QuickSort(&pArray[iLeft+1], iElementNum-iLeft-1);  
  291.   
  292. }  
  293.   
  294. void ImprovedQuickSort(int *pArray, int iElementNum)  
  295. {  
  296.     int iTmp;  
  297.       
  298.     //Select the pivot make it to the right side.  
  299.     int& iLeftIdx = pArray[0];  
  300.     int& iRightIdx = pArray[iElementNum-1];  
  301.     int& iMiddleIdx = pArray[(iElementNum-1)/2];  
  302.     if(iLeftIdx>iMiddleIdx)  
  303.     {  
  304.         iTmp = iLeftIdx;  
  305.         iLeftIdx = iMiddleIdx;  
  306.         iMiddleIdx = iTmp;  
  307.     }  
  308.     if(iRightIdx>iMiddleIdx)  
  309.     {  
  310.         iTmp = iRightIdx;  
  311.         iRightIdx = iMiddleIdx;  
  312.         iMiddleIdx = iTmp;  
  313.     }  
  314.     if(iLeftIdx>iRightIdx)  
  315.     {  
  316.         iTmp = iLeftIdx;  
  317.         iLeftIdx = iRightIdx;  
  318.         iRightIdx = iTmp;  
  319.     }  
  320.       
  321.     //Make pivot's left element and right element.  
  322.     int iLeft = 0;  
  323.     int iRight = iElementNum-2;  
  324.     int& iPivot = pArray[iElementNum-1];  
  325.     while (1)  
  326.     {  
  327.         while (iLeft<iRight && pArray[iLeft]<iPivot) ++iLeft;  
  328.         while (iLeft<iRight && pArray[iRight]>=iPivot) --iRight;  
  329.         if(iLeft>=iRight)  
  330.             break;  
  331.         iTmp = pArray[iLeft];  
  332.         pArray[iLeft] = pArray[iRight];  
  333.         pArray[iRight] = iTmp;  
  334.     }  
  335.       
  336.     //Make the i  
  337.     if(pArray[iLeft]>iPivot)  
  338.     {  
  339.         iTmp = pArray[iLeft];  
  340.         pArray[iLeft] = iPivot;  
  341.         iPivot = iTmp;  
  342.     }  
  343.       
  344.     if(iLeft>30)  
  345.         ImprovedQuickSort(pArray, iLeft);  
  346.     else  
  347.         StraightSelectionSort(pArray, iLeft);  
  348.       
  349.     if(iElementNum-iLeft-1>=30)  
  350.         ImprovedQuickSort(&pArray[iLeft+1], iElementNum-iLeft-1);  
  351.     else  
  352.         StraightSelectionSort(&pArray[iLeft+1], iElementNum-iLeft-1);  
  353. }  
  354.   
  355. void StuffArray(int *pArray, int iElementNum)  
  356. {  
  357.     int i;  
  358.     for(i=0; i<iElementNum; i++)  
  359.     {  
  360.         pArray[i] = rand();  
  361.     }  
  362. }  
  363.   
  364. void PrintArray(int *pArray, int iElementNum)  
  365. {  
  366.     int i;  
  367.     for(i=0; i<iElementNum; i++)  
  368.     {  
  369.         printf("%d ", pArray[i]);  
  370.     }  
  371.     printf("\n\n");  
  372. }  
  373.   
  374. void Swap(int& a, int& b)  
  375. {  
  376.     int c = a;  
  377.     a = b;  
  378.     b = c;  
  379. }  

猜你喜欢

转载自blog.csdn.net/wangchaoqi1985/article/details/80828147