快速排序过程的优化(续)

来源:互联网 发布:c语言指针 p跟p的区别 编辑:程序博客网 时间:2024/06/05 17:22


        

        之前的一篇文章对快速排序进行了初步优化,减少算法本身的数据比较和交换次数。另外,还可能会遇到这样的情况,即快速排序在数据已经基本有序的情况下会得到最差的时间复杂度On^2),要避免这种情况,从以下几个方面着手,继续对算法进行优化:
        1.
在对数组元素进行排序前,选择一种随机化算法堆数组的元素进行随机化处理。尽量选择能够在线性时间复杂度上对数组进行随机化的方法。这样就可以在虽大程度上避免排序算法的性能出现最差的情况。(感觉实用性不大,平白增加了时间开销,未测试效果)

        2.选择枢轴元素的时候采用随机的方法。注意,

        (1)取随机数的时候,要给随机数产生函数一个种子,一般取系统当时的时间即可。而且为了增加效率,srand((unsigned)time(NULL))只在主函数中执行一次就可以了。

        (2)以C++语言为例,系统时间要包含time.h这个头文件,虽然sys/time.h这个头文件也可以使程序正常工作,但是从测试结果来看,后者使程序的执行时间大大增加,大概增加到20倍左右。

        (3)本文的源代码中虽然这两个文件头文件都有,但是程序实际上应该是调用了time.h中的相关函数。本程序中的sys/time.h实际上是为getCurrentTime()函数服务的。
        3.
选择枢轴的时候采用首、尾、中三数取中的方法。

        4.当需要排序的序列大小降低到一定程度的时候,使用其他简单排序的算法,以避免不断的递归创建函数栈等开销。在这里当需要排序的序列长度k=25时,使用插入法进行排序。对于k值的确定,请看第5小节的测试。


  不同的排序策略对排序效率的影响(三数取中算法采用实现方式一,见后文):

数据类型

随机数据1

随机数据2

随机数据3

随机数据4

正序数据

倒序数据

相等数据

固定枢轴

280

281

280

281

——

——

169

随机枢轴

307

305

301

307

168

180

193

三数取中

305

303

303

306

104

255461

178

固定枢轴+插入排序

289

291

293

291

——

——

145

随机枢轴+插入排序

265

267

267

267

108

128

149

三数取中+插入排序

269

268

268

273

81

255589

148

   不同的排序策略对排序效率的影响(三数取中算法采用实现方式二,见后文):

数据类型

随机数据1

随机数据2

随机数据3

随机数据4

正序数据

倒序数据

相等数据

固定枢轴

299

301

301

300

——

——

180

随机枢轴

315

321

325

323

185

184

213

三数取中

285

285

286

288

125

144

185

固定枢轴+插入排序

274

277

276

276

——

——

152

随机枢轴+插入排序

274

272

269

272

115

139

155

三数取中+插入排序

297

288

289

292

88

119

153


快速排序核心代码:

//快速排序1,最左边为枢轴void quick_sort_1(int a[], int left, int right){if(left < right){int pivot = a[left];int i = left, j = right;while(i < j){while(i < j && a[j] > pivot){j--;}if(i < j){a[i] = a[j];i++;}while(i < j && a[i] < pivot){i++;}if(i < j){a[j] = a[i];j--;}}a[i] = pivot;quick_sort_1(a, left, i - 1);quick_sort_1(a, i + 1, right);}}

        以上核心源代码中需要注意的细节:

        (1)第7行中,用的是a[j] > pivot,而不是a[j] >= pivot。虽然可能会因此增加数据的移动量,但是对于所有的数据都想等这种极端状况下,仍然能够保证O(nlogn)的时间复杂度。经过对100万随机数据进行测试,发现性能下降不是很明显。第14行也类似。

        (2)第12行和第19行。该元素处理完毕后,即可让游标移到下一个相邻的位置,否则while循环会额外进行一次判定。并且对于本程序第7行代码的比较大小(即使用>而非>=)的方式而言,游标的这种移动方式是必须的,否则的话游标会停滞不前,永远没有机会移动从而造成死循环。

        (3)对于本程序(见后面)中的SelectPivotMid()函数,有两种实现方式:

        实现方式一运用了看似较为复杂的比较方式。相对于数据交换来说,进行比较所花费的时间要少很多。对于函数SelectPivotMid()实现方式一来说,虽然看似比较次数较多,但是对于任何一个实际的过程,最多只会进行三次比较和一次交换,而实现方式二最多会进行三次比较和三次交换。在时间效率上应该是实现方式一略优。但在实际的执行过程中,实现方式一对于倒序数组排序时间不知为什么非常大,尚未找到原因。实现方式二一切正常。方式一已找到原因,参见:快速排序中枢轴元素从首、尾、中间三个元素取中间值的算法探究

        SelectPivotMid()算法实现一:

void SelectPivotMid(int a[], int left, int right){int pivotPos = left;int mid = (right + left)/2;if(a[mid] <= a[left]){if(a[left] <= a[right]){pivotPos = left;}else{if(a[mid] <= a[right]){pivotPos = right;}else{pivotPos = mid;}}}else{if(a[mid] <= a[right]){pivotPos = mid;}else{if(a[left] <= a[right]){pivotPos = right;}else{pivotPos = left;}}}int tmp = a[pivotPos];a[pivotPos] = a[left];a[left] = tmp;}

           SelectPivotMid()算法实现二:

void SelectPivotMid_2(int arr[],int low,int high){int mid = low + ((high - low) >> 1);//计算数组中间的元素的下标//使用三数取中法选择枢轴if (arr[mid] > arr[high])//目标: arr[mid] <= arr[high]{swap(arr[mid],arr[high]);}if (arr[low] > arr[high])//目标: arr[low] <= arr[high]{swap(arr[low],arr[high]);}if (arr[mid] > arr[low]) //目标: arr[low] >= arr[mid]{swap(arr[mid],arr[low]);}//此时,arr[mid] <= arr[low] <= arr[high]//return arr[low];//low的位置上保存这三个位置中间的值//分割时可以直接使用low位置的元素作为枢轴,而不用改变分割函数了}




        5.K取值的优化实验:确定一个最佳值,保证快速排序和插入排序的结果最优,下面的实验:

        优化前:固定枢轴元素

        优化后:固定枢轴元素 + 插入排序

        数据条件:1000万个随机产生的整数,进行排序,当快速排序规模大于k值时利用快速排序,否则利用插入排序

        试验结果:k值在20~25附近时效果最好,优化幅度在5%左右。

K

优化前(ms

优化后(ms

优化幅度(%

50

3384

3415

-0.9

40

3367

3399

-1

30

3362

3224

4.1

30

3355

3226

3.8

25

3359

3195

4.9

25

3366

3195

5.1

20

3360

3189

5.1

20

3376

3195

5.4

15

3361

3202

4.7

15

3357

3194

4.9

10

3357

3244

3.4

10

3361

3253

3.2

8

3368

3297

2.1

5

3360

3354

0.2


以下是本算法所有程序的完整源代码:

#include <iostream>#include <fstream>#include <string>#include <stdlib.h>#include <time.h>#include <sys/time.h>#define MAX 1000000using namespace std;int flag = 25;void readNum(int a[]){string filename;ifstream infile("data_100w_4.txt", ios::in);string textline = "";int i = 0;while(getline(infile, textline, '\n')){a[i] = atoi(string(textline).c_str());i++;}infile.close();}void SelectPivotRandom(int a[], int left, int right){int pivotPos = rand()%(right - left) + left;int tmp = a[pivotPos];a[pivotPos] = a[left];a[left] = tmp;}void SelectPivotMid(int a[], int left, int right){int pivotPos = left;int mid = (right + left)/2;if(a[mid] <= a[left]){if(a[left] <= a[right]){pivotPos = left;}else{if(a[mid] <= a[right]){pivotPos = right;}else{pivotPos = mid;}}}else{if(a[mid] <= a[right]){pivotPos = mid;}else{if(a[left] <= a[right]){pivotPos = right;}else{pivotPos = left;}}}int tmp = a[pivotPos];a[pivotPos] = a[left];a[left] = tmp;}void SelectPivotMid_2(int arr[],int low,int high){int mid = low + ((high - low) >> 1);//计算数组中间的元素的下标//使用三数取中法选择枢轴if (arr[mid] > arr[high])//目标: arr[mid] <= arr[high]{swap(arr[mid],arr[high]);}if (arr[low] > arr[high])//目标: arr[low] <= arr[high]{swap(arr[low],arr[high]);}if (arr[mid] > arr[low]) //目标: arr[low] >= arr[mid]{swap(arr[mid],arr[low]);}//此时,arr[mid] <= arr[low] <= arr[high]//return arr[low];//low的位置上保存这三个位置中间的值//分割时可以直接使用low位置的元素作为枢轴,而不用改变分割函数了}void insert_sort(int a[],int left, int right){for(int i = left + 1; i <= right; i++){int tmp = a[i];for(int j = i - 1; j >= left; j--){if(tmp < a[j]){a[j+1] = a[j];}else{a[j+1] = tmp;break;}}}}//快速排序1,最左边为枢轴void quick_sort_1(int a[], int left, int right){if(left < right){int pivot = a[left];int i = left, j = right;while(i < j){while(i < j && a[j] > pivot){j--;}if(i < j){a[i] = a[j];i++;}while(i < j && a[i] < pivot){i++;}if(i < j){a[j] = a[i];j--;}}a[i] = pivot;quick_sort_1(a, left, i - 1);quick_sort_1(a, i + 1, right);}} //快速排序2,随机枢轴void quick_sort_2(int a[], int left, int right){if(left < right){SelectPivotRandom(a, left, right);int pivot = a[left];int i = left, j = right;while(i < j){while(i < j && a[j] > pivot){j--;}if(i < j){a[i] = a[j];i++;}while(i < j && a[i] < pivot){i++;}if(i < j){a[j] = a[i];j--;}}a[i] = pivot;quick_sort_2(a, left, i - 1);quick_sort_2(a, i + 1, right);}}//快速排序3,首、尾、中三数取中为枢轴void quick_sort_3(int a[], int left, int right){if(left < right){SelectPivotMid(a, left, right);int pivot = a[left];int i = left, j = right;while(i < j){while(i < j && a[j] > pivot){j--;}if(i < j){a[i] = a[j];i++;}while(i < j && a[i] < pivot){i++;}if(i < j){a[j] = a[i];j--;}}a[i] = pivot;quick_sort_3(a, left, i - 1);quick_sort_3(a, i + 1, right);}}//快速排序4,固定枢轴 + 插入排序void quick_sort_4(int a[], int left, int right){if(left < right){if(right - left < flag){insert_sort(a, left, right);}else{int pivot = a[left];int i = left, j = right;while(i < j){while(i < j && a[j] > pivot){j--;}if(i < j){a[i] = a[j];i++;}while(i < j && a[i] < pivot){i++;}if(i < j){a[j] = a[i];j--;}}a[i] = pivot;quick_sort_4(a, left, i - 1);quick_sort_4(a, i + 1, right);}}}//快速排序5,随机枢轴 + 插入排序void quick_sort_5(int a[], int left, int right){if(left < right){if(right - left < flag){insert_sort(a, left, right);}else{SelectPivotRandom(a, left, right);int pivot = a[left];int i = left, j = right;while(i < j){while(i < j && a[j] > pivot){j--;}if(i < j){a[i] = a[j];i++;}while(i < j && a[i] < pivot){i++;}if(i < j){a[j] = a[i];j--;}}a[i] = pivot;quick_sort_5(a, left, i - 1);quick_sort_5(a, i + 1, right);}}}//快速排序6,枢轴三者取一 + 插入排序void quick_sort_6(int a[], int left, int right){if(left < right){if(right - left < flag){insert_sort(a, left, right);}else{SelectPivotMid(a, left, right);int pivot = a[left];int i = left, j = right;while(i < j){while(i < j && a[j] > pivot){j--;}if(i < j){a[i] = a[j];i++;}while(i < j && a[i] < pivot){i++;}if(i < j){a[j] = a[i];j--;}}a[i] = pivot;quick_sort_6(a, left, i - 1);quick_sort_6(a, i + 1, right);}}}int verify(int a[]){for(int i = 0; i < MAX - 1; i++){if(a[i] > a[i+1])return -1;}return 0;}long getCurrentTime(){struct timeval tv;gettimeofday(&tv, NULL);return tv.tv_sec * 1000 + tv.tv_usec / 1000;}int main(){int a[MAX] = {0};int b[MAX] = {0};int c[MAX] = {0};int d[MAX] = {0};int e[MAX] = {0};int f[MAX] = {0};readNum(a);readNum(b);readNum(c);readNum(d);readNum(e);readNum(f);srand((unsigned)time(NULL));cout << "Number Counts:" << MAX << endl;cout << "Sort Result:" << endl;long time_1 = getCurrentTime();cout << "quick_sort_1 had spend ";quick_sort_1(a, 0, MAX-1);long time_2 = getCurrentTime();cout << time_2 - time_1 << " ms, sort result : ";cout << (verify(a) == 0 ? "True" : "False") << endl;long time_3 = getCurrentTime();cout << "quick_sort_2 had spend ";quick_sort_2(b, 0, MAX-1);long time_4 = getCurrentTime();cout << time_4 - time_3 << " ms, sort result : ";cout << (verify(b) == 0 ? "True" : "False") << endl;long time_5 = getCurrentTime();cout << "quick_sort_3 had spend ";quick_sort_3(c, 0, MAX-1);long time_6 = getCurrentTime();cout << time_6 - time_5 << " ms, sort result : ";cout << (verify(c) == 0 ? "True" : "False") << endl;long time_7 = getCurrentTime();cout << "quick_sort_4 had spend ";quick_sort_4(d, 0, MAX-1);long time_8 = getCurrentTime();cout << time_8 - time_7 << " ms, sort result : ";cout << (verify(d) == 0 ? "True" : "False") << endl;long time_9 = getCurrentTime();cout << "quick_sort_5 had spend ";quick_sort_5(e, 0, MAX-1);long time_10 = getCurrentTime();cout << time_10 - time_9 << " ms, sort result : ";cout << (verify(e) == 0 ? "True" : "False") << endl;long time_11 = getCurrentTime();cout << "quick_sort_6 had spend ";quick_sort_6(f, 0, MAX-1);long time_12 = getCurrentTime();cout << time_12 - time_11 << " ms, sort result : ";cout << (verify(f) == 0 ? "True" : "False") << endl;return 1;}




3 0
原创粉丝点击