快速排序过程的优化(续)
来源:互联网 发布:c语言指针 p跟p的区别 编辑:程序博客网 时间:2024/06/05 17:22
之前的一篇文章对快速排序进行了初步优化,减少算法本身的数据比较和交换次数。另外,还可能会遇到这样的情况,即快速排序在数据已经基本有序的情况下会得到最差的时间复杂度O(n^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;}
- 快速排序过程的优化(续)
- 快速排序过程的优化
- 快速排序的优化
- 快速排序的优化
- 优化的快速排序
- 快速排序的优化
- 快速排序的优化
- 快速排序的优化
- 快速排序的优化
- 快速排序的优化
- 优化的快速排序
- 快速排序的优化
- 快速排序的优化
- 快速排序的优化
- 快速排序的优化(转载)
- 快速排序的优化算法
- 没有优化的快速排序
- java快速排序的优化
- C语言实现数据复制
- 字符串 (扫一遍 + 计数)
- C语言写入文件
- 【数据结构&&算法系列】KMP算法介绍及实现(c++ && java)
- xtuoj-stack
- 快速排序过程的优化(续)
- 如何启动eclipse时提示选择工作空间||删除workspace空间的目录
- easymock教程-目录
- Apache虚拟目录和虚拟主机的配置
- 关于Android UI布局如何适应各种分辨率手机的问题
- Struts2框架提供的结果类型及result的name属性和type属性
- POJ 1125Stockbroker Grapevine(floyd最短路)
- 使用import简化spring的配置文件
- gnuradio下实现:读symbol进行ifft再写文件