【编程珠玑】读书笔记 第十一章 排序
2013-07-15 20:54
295 查看
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的双向换分是书中给出的代码,可能是先入为主的原因,之前看到有[b]QuickSortImproved_2中代码的写法,个人感觉[b]QuickSortImproved_1中的写法不是很好理解,且交换次数增加很多,因此有了[b]QuickSortImproved_2,且从运行时间上看,[b]QuickSortImproved_2比[b]QuickSortImproved_1要快1倍还多,若增加输入规模,速度的差别会显著;[/b][/b][/b][/b][/b]
对于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在数组元素都相同的极端情况下,将规模减半;否则,就会使最坏的情况。
假设输入数组为已经排好序的数组,那么使用上述的算法([b]QuickSortBasic、[b]QuickSortImproved_1、[b]QuickSortImproved_2[/b][/b]),时间性能也是最差的O(n^2),至此,我们已经做了两种极端情况的分析,一种是数组元素都相同,我们通过双向划分解决,但该方法不能解决数组已经排好序的情况,还有没有其他的极端情况?快速排序的复杂度到底决定于哪些因素?怎样才能达到最佳性能?[/b]
在《算法导论》的7.2节指出,快速排序的运行时间与划分是否对称有关,而后者又与选择了哪一个元素来进行划分有关。如果划分是对称的,那么本算法从渐进意义上来讲,就与合并算法一样快;如果划分是不对称的,那么本算法渐进上就和插入算法一样慢。
那么,要想得到对称的划分,使用了随机划分的方法,可以得到近似对称的划分,如QuickSortAdvaced所示。QuickSortAdvaced用函数PartionRandomPivot随机选择枢轴元素,从而得到近似对称的划分。
测试环境:win7 32bit + VS2010
代码:
测试结果:
(可以看到,同样的排序,最快的是采用函数QuickSortAdvaced(使用随机枢轴)与QuickSortImproved_2,标准库函数qsort与QuickSortImproved_1差不多,QuickSortBasic也比较快,且比QuickSortImproved_1差不多快一倍;
而插入排序很慢,最快的插入排序大概是最快的快速排序QuickSortAdvaced的200倍;
最慢的插入排序是最快的插入排序的100倍,可见即使是同样的算法,代码的编写对程序的效率也是很重要的,要从代码上进行优化)
本章以排序为例,展示了编程过程中的一般步骤。
主要针对插入排序、快速排序进行了讨论。
之前看过一本书《剑指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的双向换分是书中给出的代码,可能是先入为主的原因,之前看到有[b]QuickSortImproved_2中代码的写法,个人感觉[b]QuickSortImproved_1中的写法不是很好理解,且交换次数增加很多,因此有了[b]QuickSortImproved_2,且从运行时间上看,[b]QuickSortImproved_2比[b]QuickSortImproved_1要快1倍还多,若增加输入规模,速度的差别会显著;[/b][/b][/b][/b][/b]
对于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在数组元素都相同的极端情况下,将规模减半;否则,就会使最坏的情况。
假设输入数组为已经排好序的数组,那么使用上述的算法([b]QuickSortBasic、[b]QuickSortImproved_1、[b]QuickSortImproved_2[/b][/b]),时间性能也是最差的O(n^2),至此,我们已经做了两种极端情况的分析,一种是数组元素都相同,我们通过双向划分解决,但该方法不能解决数组已经排好序的情况,还有没有其他的极端情况?快速排序的复杂度到底决定于哪些因素?怎样才能达到最佳性能?[/b]
在《算法导论》的7.2节指出,快速排序的运行时间与划分是否对称有关,而后者又与选择了哪一个元素来进行划分有关。如果划分是对称的,那么本算法从渐进意义上来讲,就与合并算法一样快;如果划分是不对称的,那么本算法渐进上就和插入算法一样慢。
那么,要想得到对称的划分,使用了随机划分的方法,可以得到近似对称的划分,如QuickSortAdvaced所示。QuickSortAdvaced用函数PartionRandomPivot随机选择枢轴元素,从而得到近似对称的划分。
测试环境:win7 32bit + VS2010
代码:
#include <iostream> #include <cassert> #include <time.h> using namespace std; typedef int DataType; const int MaxSize = 100000000; const int SortCutOff = 50; //输入合法性检查 void CheckInvalid(int array[],int len) { assert(NULL != array && len > 0); } //直接插入排序基本版本 void InsertSortBasic(int unsortedArray[],int len) { int i; int j; CheckInvalid(unsortedArray,len); for (i = 1;i < len;++i) { for (j = i;j >= 0 && unsortedArray[j] < unsortedArray[j - 1];--j) { swap(unsortedArray[j],unsortedArray[j - 1]); } } } //直接插入排序改进,将for循环中的swap函数改为内联的,提高效率 void InsertSortImproved(int unsortedArray[],int len) { int i; int j; int tmp; CheckInvalid(unsortedArray,len); for (i = 1;i < len;++i) { for (j = i;j >= 0 && unsortedArray[j] < unsortedArray[j - 1];--j) { tmp = unsortedArray[j]; unsortedArray[j] = unsortedArray[j - 1]; unsortedArray[j - 1] = tmp; } } } //直接插入排序的进一步改进 void InsertSortAdvacaed(int unsortedArray[],int len) { int i; int j; int t; CheckInvalid(unsortedArray,len); for (i = 1;i < len;++i) { t = unsortedArray[i]; for (j = i - 1;j >= 0 && unsortedArray[j] > t;--j) { unsortedArray[j + 1] = unsortedArray[j]; } unsortedArray[j + 1] = t; } } //快速排序的基本版本,从一个方向进行划分 void QuickSortBasic(int unsortedArray[],int begin,int end) { if (begin >= end) { return; } int i; int mid = begin; int tmp; for (i = begin + 1;i <= end;++i) //注意终止条件不是i < end { if (unsortedArray[i] < unsortedArray[begin]) { ++mid; tmp = unsortedArray[i]; unsortedArray[i] = unsortedArray[mid]; unsortedArray[mid] = tmp; } } swap(unsortedArray[begin],unsortedArray[mid]); QuickSortBasic(unsortedArray,begin,mid - 1); QuickSortBasic(unsortedArray,mid + 1,end); } //快速排序的改进,从两个方向划分 void QuickSortImproved_1(int unsortedArray[],int begin,int end) { if (begin >= end) { return; } int i, j; DataType t; t = unsortedArray[begin]; i = begin; j = end + 1; for (;;) { do i++; while (i <= end && unsortedArray[i] < t); do j--; while (unsortedArray[j] > t); if (i > j) break; swap(unsortedArray[i],unsortedArray[j]); } swap(unsortedArray[begin],unsortedArray[j]); QuickSortImproved_1(unsortedArray,begin,j - 1); QuickSortImproved_1(unsortedArray,j + 1,end); } //将a_unsorted的首个元素放到排序后的位置,返回该数据的位置 int Partion(int unsortedArray[],int begin,int end) { int i = begin; int j = end; int tmp; while (i < j) { while (unsortedArray[j] > unsortedArray[i] && i < j) //从后向前比较,知道发现元素不大于轴a_unsorted[i] { --j; } if (i < j) { tmp = unsortedArray[i]; //交换之后,轴变为a_unsorted[j] unsortedArray[i] = unsortedArray[j]; unsortedArray[j] = tmp; ++i; //缩小范围 } while (unsortedArray[i] < unsortedArray[j] && i < j)//从前向后比较,知道发现元素不小于轴a_unsorted[j] { ++i; } if (i < j) { tmp = unsortedArray[i]; //交换之后,轴变为a_unsorted[i] unsortedArray[i] = unsortedArray[j]; unsortedArray[j] = tmp; --j; //缩小范围 } } return i; } //快速排序,更好的写法 void QuickSortImproved_2(int unsortedArray[],int begin,int end) { if (begin >= end) { return; } int mid = Partion(unsortedArray,begin,end); QuickSortImproved_2(unsortedArray,begin,mid - 1); QuickSortImproved_2(unsortedArray,mid + 1,end); } //产生在[lowBound,upperBound - 1]区间的随机数 int RandomIntGenerate(int lowBound, int upperBound) { return (lowBound + (RAND_MAX * rand() + rand()) % (upperBound - lowBound + 1) ); } //将a_unsorted的首个元素放到排序后的位置,返回该数据的位置 int PartionRandomPivot(int unsortedArray[],int begin,int end) { int i = begin; int j = end; int tmp; int pivotIndex = RandomIntGenerate(begin,end); swap(unsortedArray[begin],unsortedArray[pivotIndex]); while (i < j) { while (unsortedArray[j] > unsortedArray[i] && i < j) //从后向前比较,知道发现元素不大于轴a_unsorted[i] { --j; } if (i < j) { tmp = unsortedArray[i]; //交换之后,轴变为a_unsorted[j] unsortedArray[i] = unsortedArray[j]; unsortedArray[j] = tmp; ++i; //缩小范围 } while (unsortedArray[i] < unsortedArray[j] && i < j)//从前向后比较,知道发现元素不小于轴a_unsorted[j] { ++i; } if (i < j) { tmp = unsortedArray[i]; //交换之后,轴变为a_unsorted[i] unsortedArray[i] = unsortedArray[j]; unsortedArray[j] = tmp; --j; //缩小范围 } } return i; } //快速排序的进一步改进, //随机选择枢轴元素 //且在数组长度小于一定值(此处为SortCutOff)时,用插入排序 void QuickSortAdvaced(int unsortedArray[],int begin,int end) { if (begin >= end) { return; } if((end - begin + 1) < SortCutOff) { InsertSortAdvacaed(unsortedArray,(end - begin + 1)); return; } int mid = PartionRandomPivot(unsortedArray,begin,end); QuickSortImproved_2(unsortedArray,begin,mid - 1); QuickSortImproved_2(unsortedArray,mid + 1,end); } int IntCompare(const void *_p,const void *_q) { int *p = (int *) _p; int *q= (int *) _q; return (*p - *q); } void DisplayArray(int array[],int len) { CheckInvalid(array,len); for (int i = 0;i < len;++i) { cout<<array[i]<<"\t"; } cout<<endl; } //测试“脚手架” void TestDriver() { //int unsortedArray[MaxSize]; int *unsortedArray = new int[MaxSize]; size_t programToTest; size_t lengthOfUnsortedArray; int MinRandomInt; int MaxRandomInt; size_t i; int timeStart = 0; double timeCostAverage = 0; cout<<"the identifier of the program is :"<<endl; cout<<"InsertSortBasic : 11"<<endl; cout<<"InsertSortImproved : 12"<<endl; cout<<"InsertSortAdvacaed : 13"<<endl; cout<<"QuickSortBasic : 21"<<endl; cout<<"QuickSortImproved_1 : 22"<<endl; cout<<"QuickSortImproved_2 : 23"<<endl; cout<<"QuickSortAdvaced : 24"<<endl; cout<<endl; cout<<"please enter the length Of UnsortedArray,MinRandomInt and MaxRandomInt :"<<endl; cin>>lengthOfUnsortedArray>>MinRandomInt>>MaxRandomInt; cout<<"please enter the identifier of the program to test (end with ctrl+z): "<<endl; while (cin>>programToTest) { for (i = 0;i < lengthOfUnsortedArray;++i) //准备待排序数组 { unsortedArray[i] = RandomIntGenerate(MinRandomInt,MaxRandomInt); } /* cout<<"the unsorted array is :"<<endl; DisplayArray(unsortedArray,lengthOfUnsortedArray);*/ timeStart = clock(); switch (programToTest) { case 11: cout<<"Test InsertSortBasic..."<<endl; InsertSortBasic(unsortedArray,lengthOfUnsortedArray); break; case 12: cout<<"Test InsertSortImproved..."<<endl; InsertSortImproved(unsortedArray,lengthOfUnsortedArray); break; case 13: cout<<"Test InsertSortAdvacaed..."<<endl; InsertSortAdvacaed(unsortedArray,lengthOfUnsortedArray); break; case 21: cout<<"Test QuickSortBasic..."<<endl; QuickSortBasic(unsortedArray,0,lengthOfUnsortedArray - 1); break; case 22: cout<<"Test QuickSortImproved_1..."<<endl; QuickSortImproved_1(unsortedArray,0,lengthOfUnsortedArray - 1); break; case 23: cout<<"Test QuickSortImproved_2..."<<endl; QuickSortImproved_2(unsortedArray,0,lengthOfUnsortedArray - 1); break; case 24: cout<<"Test QuickSortAdvaced..."<<endl; QuickSortAdvaced(unsortedArray,0,lengthOfUnsortedArray - 1); break; case 25: cout<<"Test qsort..."<<endl; qsort(unsortedArray,lengthOfUnsortedArray,sizeof(DataType),IntCompare); break; default: break; } timeCostAverage = 1e9 * ( clock() - timeStart ) / ( CLOCKS_PER_SEC * lengthOfUnsortedArray ); cout<<"the average time cost per data is : "<<timeCostAverage<<" ns"<<endl; for (i = 0;i < lengthOfUnsortedArray - 1;++i) { if (unsortedArray[i] > unsortedArray[i + 1]) { cout<<"sort bug i = "<<i<<endl; } } /*cout<<"the sorted array is :"<<endl; DisplayArray(unsortedArray,lengthOfUnsortedArray);*/ cout<<endl; cout<<"please enter the identifier of the program to test (end with ctrl+z): "<<endl; } delete [] unsortedArray; } //使用脚手架的测试程序 int main(void) { TestDriver(); return 0; } //不用脚手架的测试程序 //int main(void) //{ // int unsortedArray[MaxSize]; // int len = 0; // DataType data; // // cout<<"please enter the data of the array ,end with ctrl+z : "<<endl; // while (cin>>data) // { // unsortedArray[len++] = data; // } // cout<<"the unsorted array is :"<<endl; // DisplayArray(unsortedArray,len); // // /*cout<<"Test InsertSortBasic..."<<endl; // InsertSortBasic(unsortedArray,len); // cout<<"the sorted array is :"<<endl; // DisplayArray(unsortedArray,len);*/ // // // //cout<<"Test InsertSortImproved..."<<endl; // //InsertSortImproved(unsortedArray,len); // //cout<<"the sorted array is :"<<endl; // //DisplayArray(unsortedArray,len); // // /*cout<<"Test InsertSortAdvacaed..."<<endl; // InsertSortAdvacaed(unsortedArray,len); // cout<<"the sorted array is :"<<endl; // DisplayArray(unsortedArray,len);*/ // // /*cout<<"Test QuickSortBasic..."<<endl; // QuickSortBasic(unsortedArray,0,len - 1); // cout<<"the sorted array is :"<<endl; // DisplayArray(unsortedArray,len);*/ // // /*cout<<"Test QuickSortImproved_1..."<<endl; // QuickSortImproved_1(unsortedArray,0,len - 1); // cout<<"the sorted array is :"<<endl; // DisplayArray(unsortedArray,len);*/ // // //cout<<"Test QuickSortImproved_2..."<<endl; // //QuickSortImproved_2(unsortedArray,0,len - 1); // //cout<<"the sorted array is :"<<endl; // //DisplayArray(unsortedArray,len); // // cout<<"Test QuickSortAdvaced..."<<endl; // QuickSortAdvaced(unsortedArray,0,len - 1); // cout<<"the sorted array is :"<<endl; // DisplayArray(unsortedArray,len); // // return 0; //}
测试结果:
(可以看到,同样的排序,最快的是采用函数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 请按任意键继续. . .
相关文章推荐
- [编程珠玑]-第十一章:快速排序及第k小元素
- 编程珠玑第十一章----排序
- 【编程珠玑】第十一章 排序 (插入排序和快速排序的深度优化)
- 编程珠玑 第十一章 第 六题 选择排序和希尔排序
- 【编程珠玑】第十一章 排序 (插入排序和快速排序的深度优化)
- 编程珠玑---读书笔记---第11章排序
- 【编程珠玑-读书笔记】用位图解决排序问题--仔细分析问题的重要性
- 编程珠玑 -- 利用位图排序
- 编程珠玑开篇--磁盘文件排序问题
- 编程珠玑----快速排序的变形
- 编程珠玑 读书笔记
- 编程珠玑 Pearls(11 .排序)
- 编程珠玑之第一章习题6给每个整数不超过10次的100w数据排序的测试用例
- 编程珠玑---读书笔记---生成随机整数的有序子集
- 读书笔记 ASP.NET 2.0编程珠玑
- 【编程珠玑】第一章电话号码排序
- 【编程珠玑】读书笔记 第五章 编程小事
- 【编程珠玑】读书笔记 第十二章 取样问题
- 编程珠玑之快速排序
- 编程珠玑--位图在排序中的使用