• 【编程珠玑】读书笔记 第十一章 排序


    2013-07-15 20:18:41

    本章以排序为例,展示了编程过程中的一般步骤。

    主要针对插入排序、快速排序进行了讨论。

    之前看过一本书《剑指offer》,感觉写的很好,有具体的实例,对每个题都给出思路以及实例,并对编程中的要点进行说明,代码风格也很好,现在看这本《编程珠玑》,是因为想多了解一些编程以及算法方面的理论。

    书快看完了,个人感觉这本书内容还不错,但是表达方式比较晦涩,不太好理解,比如说代码的思路说明不够清楚、编程的风格也比较糟糕,有些术语也不太好理解,比如脚手架(书中的意思是测试框架)、程序验证(书中的意思是证明编程思路的正确性)、算法调优、代码调优等。

    本章的内容有些表叔还是很晦涩,排序本来是很简单的,而本章中提到的思路有的却是不好理解(有的效率还比较低),看了很长时间,才看懂。

    下面给出根据书中的思路写出的完整代码,以及测试“脚手架”,“脚手架”在此处的测试却是挺好用。后面附上了测试结果,给出了待排序数组为1000,000时每种排序的时间消耗。

    从测试结果可以看到,同样的排序,最快的是采用函数QuickSortAdvaced(使用随机枢轴)与QuickSortImproved_2,标准库函数qsort与QuickSortImproved_1差不多,QuickSortBasic也比较快,且比QuickSortImproved_1差不多快一倍;

    而插入排序很慢,最快的插入排序大概是最快的快速排序QuickSortAdvaced的200倍;

    最慢的插入排序是最快的插入排序的100倍,可见即使是同样的算法,代码的编写对程序的效率也是很重要的,要从代码上进行优化.

    QuickSortBasic在划分时,使用的单向划分,在数组元素都相同的极端情况下,需要n-1次划分,而不是log2(n)次,因此时间复杂度恶化为O(n^2);

    为了改善这种情况,提出了双向划分,即为下面代码中的QuickSortImproved_1,该算法在数组元素都相同的极端情况下,可将规模减半,因此时间复杂度为O(nlog2(n)),为快速排序的最好性能;

    QuickSortImproved_1的双向换分是书中给出的代码,可能是先入为主的原因,之前看到有QuickSortImproved_2中代码的写法,个人感觉QuickSortImproved_1中的写法不是很好理解,且交换次数增加很多,因此有了QuickSortImproved_2,且从运行时间上看,QuickSortImproved_2比QuickSortImproved_1要快1倍还多,若增加输入规模,速度的差别会显著;

    对于QuickSortImproved_2,要注意到,Partion函数主循环while (i < j)内的循环的条件是 while (unsortedArray[j] > unsortedArray[i] && i < j),而非 while (unsortedArray[j] >= unsortedArray[i] && i < j),另外一个循环是while (unsortedArray[j] < unsortedArray[i] && i < j),而非 while (unsortedArray[j] <= unsortedArray[i] && i < j),也就是两个循环都是不包含=的,这样才能保证Partion在数组元素都相同的极端情况下,将规模减半;否则,就会使最坏的情况。

    假设输入数组为已经排好序的数组,那么使用上述的算法(QuickSortBasic、QuickSortImproved_1、QuickSortImproved_2),时间性能也是最差的O(n^2),至此,我们已经做了两种极端情况的分析,一种是数组元素都相同,我们通过双向划分解决,但该方法不能解决数组已经排好序的情况,还有没有其他的极端情况?快速排序的复杂度到底决定于哪些因素?怎样才能达到最佳性能?

    在《算法导论》的7.2节指出,快速排序的运行时间与划分是否对称有关,而后者又与选择了哪一个元素来进行划分有关。如果划分是对称的,那么本算法从渐进意义上来讲,就与合并算法一样快;如果划分是不对称的,那么本算法渐进上就和插入算法一样慢。

    那么,要想得到对称的划分,使用了随机划分的方法,可以得到近似对称的划分,如QuickSortAdvaced所示。QuickSortAdvaced用函数PartionRandomPivot随机选择枢轴元素,从而得到近似对称的划分。

    测试环境:win7 32bit + VS2010

    代码:

      1 #include <iostream>
      2 #include <cassert>
      3 #include <time.h>
      4 using namespace std;
      5 
      6 typedef int DataType;
      7 const int MaxSize = 100000000;
      8 const int SortCutOff = 50;
      9 
     10 //输入合法性检查
     11 void CheckInvalid(int array[],int len)
     12 {
     13     assert(NULL != array && len > 0);
     14 }
     15 
     16 //直接插入排序基本版本
     17 void InsertSortBasic(int unsortedArray[],int len)
     18 {
     19     int i;
     20     int j;
     21 
     22     CheckInvalid(unsortedArray,len);
     23 
     24     for (i = 1;i < len;++i)
     25     {
     26         for (j = i;j >= 0 && unsortedArray[j] < unsortedArray[j - 1];--j)
     27         {
     28             swap(unsortedArray[j],unsortedArray[j - 1]);
     29         }
     30     }
     31 }
     32 
     33 //直接插入排序改进,将for循环中的swap函数改为内联的,提高效率
     34 void InsertSortImproved(int unsortedArray[],int len)
     35 {
     36     int i;
     37     int j;
     38     int tmp;
     39 
     40     CheckInvalid(unsortedArray,len);
     41 
     42     for (i = 1;i < len;++i)
     43     {
     44         for (j = i;j >= 0 && unsortedArray[j] < unsortedArray[j - 1];--j)
     45         {
     46             tmp = unsortedArray[j];
     47             unsortedArray[j] = unsortedArray[j - 1];
     48             unsortedArray[j - 1] = tmp;
     49         }
     50     }
     51 }
     52 
     53 
     54 //直接插入排序的进一步改进
     55 void InsertSortAdvacaed(int unsortedArray[],int len)
     56 {
     57     int i;
     58     int j;
     59     int t;
     60 
     61     CheckInvalid(unsortedArray,len);
     62 
     63     for (i = 1;i < len;++i)
     64     {
     65         t = unsortedArray[i];
     66         for (j = i - 1;j >= 0 && unsortedArray[j] > t;--j)
     67         {
     68             unsortedArray[j + 1] = unsortedArray[j];
     69         }
     70         unsortedArray[j + 1] = t;
     71     }
     72 }
     73 
     74 //快速排序的基本版本,从一个方向进行划分
     75 void QuickSortBasic(int unsortedArray[],int begin,int end)
     76 {
     77     if (begin >= end)
     78     {
     79         return;
     80     }
     81 
     82     int i;
     83     int mid = begin;
     84     int tmp;
     85 
     86     for (i = begin + 1;i <= end;++i) //注意终止条件不是i < end
     87     {
     88         if (unsortedArray[i] < unsortedArray[begin])
     89         {
     90             ++mid;
     91             tmp = unsortedArray[i];
     92             unsortedArray[i] = unsortedArray[mid];
     93             unsortedArray[mid] = tmp;
     94         }
     95     }
     96 
     97     swap(unsortedArray[begin],unsortedArray[mid]);
     98     QuickSortBasic(unsortedArray,begin,mid - 1);
     99     QuickSortBasic(unsortedArray,mid + 1,end);
    100 }
    101 
    102 //快速排序的改进,从两个方向划分
    103 void QuickSortImproved_1(int unsortedArray[],int begin,int end)
    104 {
    105     if (begin >= end)
    106     {
    107         return;
    108     }
    109 
    110     int i, j;
    111     DataType t;
    112     t = unsortedArray[begin];
    113     i = begin;
    114     j = end + 1;
    115 
    116     for (;;) 
    117     {
    118         do i++; 
    119         while (i <= end && unsortedArray[i] < t);
    120         do j--; 
    121         while (unsortedArray[j] > t);
    122         if (i > j)
    123             break;
    124         swap(unsortedArray[i],unsortedArray[j]);
    125     }
    126     swap(unsortedArray[begin],unsortedArray[j]);
    127     QuickSortImproved_1(unsortedArray,begin,j - 1);
    128     QuickSortImproved_1(unsortedArray,j + 1,end);
    129 }
    130 
    131 //将a_unsorted的首个元素放到排序后的位置,返回该数据的位置
    132 int Partion(int unsortedArray[],int begin,int end)
    133 {
    134     int i = begin;
    135     int j = end;
    136     int tmp;
    137 
    138     while (i < j)
    139     {
    140         while (unsortedArray[j] > unsortedArray[i] && i < j)  //从后向前比较,知道发现元素不大于轴a_unsorted[i]
    141         {
    142             --j;    
    143         }
    144 
    145         if (i < j)
    146         {
    147             tmp =  unsortedArray[i];    //交换之后,轴变为a_unsorted[j]
    148             unsortedArray[i] = unsortedArray[j];
    149             unsortedArray[j] = tmp;
    150             ++i;        //缩小范围
    151         }
    152 
    153         while (unsortedArray[i] < unsortedArray[j] && i < j)//从前向后比较,知道发现元素不小于轴a_unsorted[j]
    154         {
    155             ++i;    
    156         }
    157 
    158         if (i < j)
    159         {
    160             tmp = unsortedArray[i];        //交换之后,轴变为a_unsorted[i]
    161             unsortedArray[i] = unsortedArray[j];
    162             unsortedArray[j] = tmp;
    163 
    164             --j;     //缩小范围
    165         }
    166     }
    167 
    168     return i;
    169 }
    170 
    171 //快速排序,更好的写法
    172 void QuickSortImproved_2(int unsortedArray[],int begin,int end)
    173 {
    174     if (begin >= end)
    175     {
    176         return;
    177     }
    178 
    179     int mid = Partion(unsortedArray,begin,end);
    180 
    181     QuickSortImproved_2(unsortedArray,begin,mid - 1);
    182     QuickSortImproved_2(unsortedArray,mid + 1,end);
    183 }
    184 
    185 //产生在[lowBound,upperBound - 1]区间的随机数
    186 int RandomIntGenerate(int lowBound, int upperBound)
    187 {    
    188     return (lowBound + (RAND_MAX * rand() + rand()) % (upperBound - lowBound + 1) );
    189 }
    190 
    191 //将a_unsorted的首个元素放到排序后的位置,返回该数据的位置
    192 int PartionRandomPivot(int unsortedArray[],int begin,int end)
    193 {
    194     int i = begin;
    195     int j = end;
    196     int tmp;
    197 
    198     int pivotIndex =  RandomIntGenerate(begin,end);
    199     swap(unsortedArray[begin],unsortedArray[pivotIndex]);
    200 
    201     while (i < j)
    202     {
    203         while (unsortedArray[j] > unsortedArray[i] && i < j)  //从后向前比较,知道发现元素不大于轴a_unsorted[i]
    204         {
    205             --j;    
    206         }
    207 
    208         if (i < j)
    209         {
    210             tmp =  unsortedArray[i];    //交换之后,轴变为a_unsorted[j]
    211             unsortedArray[i] = unsortedArray[j];
    212             unsortedArray[j] = tmp;
    213             ++i;        //缩小范围
    214         }
    215 
    216         while (unsortedArray[i] < unsortedArray[j] && i < j)//从前向后比较,知道发现元素不小于轴a_unsorted[j]
    217         {
    218             ++i;    
    219         }
    220 
    221         if (i < j)
    222         {
    223             tmp = unsortedArray[i];        //交换之后,轴变为a_unsorted[i]
    224             unsortedArray[i] = unsortedArray[j];
    225             unsortedArray[j] = tmp;
    226 
    227             --j;     //缩小范围
    228         }
    229     }
    230 
    231     return i;
    232 }
    233 
    234 //快速排序的进一步改进,
    235 //随机选择枢轴元素
    236 //且在数组长度小于一定值(此处为SortCutOff)时,用插入排序
    237 void QuickSortAdvaced(int unsortedArray[],int begin,int end)
    238 {
    239     if (begin >= end)
    240     {
    241         return;
    242     }
    243 
    244     if((end - begin + 1) < SortCutOff)
    245     {
    246         InsertSortAdvacaed(unsortedArray,(end - begin + 1));
    247         return;
    248     }
    249 
    250     int mid = PartionRandomPivot(unsortedArray,begin,end);
    251 
    252     QuickSortImproved_2(unsortedArray,begin,mid - 1);
    253     QuickSortImproved_2(unsortedArray,mid + 1,end);
    254 }
    255 
    256 int IntCompare(const void *_p,const void *_q)
    257 {    
    258     int *p = (int *) _p;
    259     int *q= (int *) _q;
    260     return (*p - *q);
    261 }
    262 
    263 void DisplayArray(int array[],int len)
    264 {
    265     CheckInvalid(array,len);
    266 
    267     for (int i = 0;i < len;++i)
    268     {
    269         cout<<array[i]<<"	";
    270     }
    271     cout<<endl;
    272 }
    273 
    274 //测试“脚手架”
    275 void TestDriver()
    276 {    
    277     //int unsortedArray[MaxSize];
    278     int *unsortedArray = new int[MaxSize];
    279     size_t programToTest;
    280     size_t lengthOfUnsortedArray;
    281     int MinRandomInt;
    282     int MaxRandomInt;
    283     size_t i;
    284     int timeStart = 0;
    285     double timeCostAverage = 0;
    286     
    287     cout<<"the identifier of the program is :"<<endl;
    288     cout<<"InsertSortBasic : 11"<<endl;
    289     cout<<"InsertSortImproved : 12"<<endl;
    290     cout<<"InsertSortAdvacaed : 13"<<endl;
    291     cout<<"QuickSortBasic : 21"<<endl;
    292     cout<<"QuickSortImproved_1 : 22"<<endl;
    293     cout<<"QuickSortImproved_2 : 23"<<endl;
    294     cout<<"QuickSortAdvaced : 24"<<endl;
    295     cout<<endl;
    296 
    297     cout<<"please enter the length Of UnsortedArray,MinRandomInt and MaxRandomInt :"<<endl;
    298     cin>>lengthOfUnsortedArray>>MinRandomInt>>MaxRandomInt;
    299     cout<<"please enter the identifier of the program to test (end with ctrl+z): "<<endl;
    300     while (cin>>programToTest)
    301     {
    302         for (i = 0;i < lengthOfUnsortedArray;++i)  //准备待排序数组
    303         {
    304             unsortedArray[i] = RandomIntGenerate(MinRandomInt,MaxRandomInt);
    305         }
    306 
    307     /*    cout<<"the unsorted array is :"<<endl;
    308         DisplayArray(unsortedArray,lengthOfUnsortedArray);*/
    309         timeStart = clock();
    310         switch (programToTest)
    311         {
    312             case 11: 
    313                 cout<<"Test InsertSortBasic..."<<endl;
    314                 InsertSortBasic(unsortedArray,lengthOfUnsortedArray);
    315                 break;
    316 
    317             case 12: 
    318                 cout<<"Test InsertSortImproved..."<<endl;
    319                 InsertSortImproved(unsortedArray,lengthOfUnsortedArray);
    320                 break;
    321 
    322             case 13: 
    323                 cout<<"Test InsertSortAdvacaed..."<<endl;
    324                 InsertSortAdvacaed(unsortedArray,lengthOfUnsortedArray);
    325                 break;
    326             case 21: 
    327                 cout<<"Test QuickSortBasic..."<<endl;
    328                 QuickSortBasic(unsortedArray,0,lengthOfUnsortedArray - 1);
    329                 break;
    330             case 22: 
    331                 cout<<"Test QuickSortImproved_1..."<<endl;
    332                 QuickSortImproved_1(unsortedArray,0,lengthOfUnsortedArray - 1);
    333                 break;
    334             case 23: 
    335                 cout<<"Test QuickSortImproved_2..."<<endl;
    336                 QuickSortImproved_2(unsortedArray,0,lengthOfUnsortedArray - 1);
    337                 break;
    338             case 24: 
    339                 cout<<"Test QuickSortAdvaced..."<<endl;
    340                 QuickSortAdvaced(unsortedArray,0,lengthOfUnsortedArray - 1);
    341                 break;
    342             case 25: 
    343                 cout<<"Test qsort..."<<endl;
    344                 qsort(unsortedArray,lengthOfUnsortedArray,sizeof(DataType),IntCompare);
    345                 break;
    346             default:
    347                 break;
    348         }
    349 
    350         timeCostAverage = 1e9 * ( clock() - timeStart ) / ( CLOCKS_PER_SEC * lengthOfUnsortedArray );
    351         cout<<"the average time cost per data is : "<<timeCostAverage<<" ns"<<endl;
    352 
    353         for (i = 0;i < lengthOfUnsortedArray - 1;++i)
    354         {
    355             if (unsortedArray[i] > unsortedArray[i + 1])
    356             {
    357                 cout<<"sort bug i = "<<i<<endl;
    358             }
    359         }
    360 
    361         /*cout<<"the sorted array is :"<<endl;
    362         DisplayArray(unsortedArray,lengthOfUnsortedArray);*/
    363 
    364         cout<<endl;
    365         cout<<"please enter the identifier of the program to test (end with ctrl+z): "<<endl;
    366     }
    367 
    368     delete [] unsortedArray;
    369 }
    370 
    371 //使用脚手架的测试程序
    372 int main(void)
    373 {
    374     TestDriver();
    375     return 0;
    376 }
    377 
    378 //不用脚手架的测试程序
    379 //int main(void)
    380 //{
    381 //    int unsortedArray[MaxSize];
    382 //    int len = 0;
    383 //    DataType data;
    384 //
    385 //    cout<<"please enter the data of the array ,end with ctrl+z : "<<endl;
    386 //    while (cin>>data)
    387 //    {
    388 //        unsortedArray[len++] = data;
    389 //    }
    390 //    cout<<"the unsorted array is :"<<endl;
    391 //    DisplayArray(unsortedArray,len);
    392 //
    393 //    /*cout<<"Test InsertSortBasic..."<<endl;
    394 //    InsertSortBasic(unsortedArray,len);
    395 //    cout<<"the sorted array is :"<<endl;
    396 //    DisplayArray(unsortedArray,len);*/
    397 //
    398 //
    399 //    //cout<<"Test InsertSortImproved..."<<endl;
    400 //    //InsertSortImproved(unsortedArray,len);
    401 //    //cout<<"the sorted array is :"<<endl;
    402 //    //DisplayArray(unsortedArray,len);
    403 //
    404 //    /*cout<<"Test InsertSortAdvacaed..."<<endl;
    405 //    InsertSortAdvacaed(unsortedArray,len);
    406 //    cout<<"the sorted array is :"<<endl;
    407 //    DisplayArray(unsortedArray,len);*/
    408 //
    409 //    /*cout<<"Test QuickSortBasic..."<<endl;
    410 //    QuickSortBasic(unsortedArray,0,len - 1);
    411 //    cout<<"the sorted array is :"<<endl;
    412 //    DisplayArray(unsortedArray,len);*/
    413 //
    414 //    /*cout<<"Test QuickSortImproved_1..."<<endl;
    415 //    QuickSortImproved_1(unsortedArray,0,len - 1);
    416 //    cout<<"the sorted array is :"<<endl;
    417 //    DisplayArray(unsortedArray,len);*/
    418 //
    419 //    //cout<<"Test QuickSortImproved_2..."<<endl;
    420 //    //QuickSortImproved_2(unsortedArray,0,len - 1);
    421 //    //cout<<"the sorted array is :"<<endl;
    422 //    //DisplayArray(unsortedArray,len);
    423 //
    424 //    cout<<"Test QuickSortAdvaced..."<<endl;
    425 //    QuickSortAdvaced(unsortedArray,0,len - 1);
    426 //    cout<<"the sorted array is :"<<endl;
    427 //    DisplayArray(unsortedArray,len);
    428 //    
    429 //    return 0;
    430 //}

    测试结果:

    (可以看到,同样的排序,最快的是采用函数QuickSortAdvaced(使用随机枢轴)与QuickSortImproved_2,标准库函数qsort与QuickSortImproved_1差不多,QuickSortBasic也比较快,且比QuickSortImproved_1差不多快一倍;

    而插入排序很慢,最快的插入排序大概是最快的快速排序QuickSortAdvaced的200倍;

    最慢的插入排序是最快的插入排序的100倍,可见即使是同样的算法,代码的编写对程序的效率也是很重要的,要从代码上进行优化)

    the identifier of the program is :
    InsertSortBasic : 11
    InsertSortImproved : 12
    InsertSortAdvacaed : 13
    QuickSortBasic : 21
    QuickSortImproved_1 : 22
    QuickSortImproved_2 : 23
    QuickSortAdvaced : 24
    
    please enter the length Of UnsortedArray,MinRandomInt and MaxRandomInt :
    100000 -1000000 1000000
    please enter the identifier of the program to test (end with ctrl+z):
    24
    Test QuickSortAdvaced...
    the average time cost per data is : 860 ns
    
    please enter the identifier of the program to test (end with ctrl+z):
    25
    Test qsort...
    the average time cost per data is : 2330 ns
    
    please enter the identifier of the program to test (end with ctrl+z):
    23
    Test QuickSortImproved_2...
    the average time cost per data is : 860 ns
    
    please enter the identifier of the program to test (end with ctrl+z):
    22
    Test QuickSortImproved_1...
    the average time cost per data is : 2020 ns
    
    please enter the identifier of the program to test (end with ctrl+z):
    21
    Test QuickSortBasic...
    the average time cost per data is : 1080 ns
    
    please enter the identifier of the program to test (end with ctrl+z):
    13
    Test InsertSortAdvacaed...
    the average time cost per data is : 164740 ns
    
    please enter the identifier of the program to test (end with ctrl+z):
    12
    Test InsertSortImproved...
    the average time cost per data is : 297960 ns
    
    please enter the identifier of the program to test (end with ctrl+z):
    11
    Test InsertSortBasic...
    the average time cost per data is : 3.66578e+006 ns
    
    please enter the identifier of the program to test (end with ctrl+z):
    ^Z
    请按任意键继续. . .
  • 相关阅读:
    摘录一篇 这两天对SSO的认识
    获取文本框中的行
    在窗体数据源中过滤记录
    linux下访问windows的共享
    使用Form作Lookup其窗体位置设置
    窗体数据源连接技巧
    给动态创建的控件指定事件
    Object的使用技巧
    显示进度条SysOperationProgress
    如何解决下载的CHM文件无法显示网页问题
  • 原文地址:https://www.cnblogs.com/youngforever/p/3191955.html
Copyright © 2020-2023  润新知