【编程珠玑】读书笔记 第十一章 排序
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]<<"\t"; 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 请按任意键继续. . .