快速排序是目前基于关键字的内部排序算法中平均性能最好的,它采用了分治策略,这既是快速排序的优势也是它的缺点。从快速排序的算法描述上咱们能够发现它具备递归的结构:算法
(1)肯定一个分界,将待排序的数组分为左、右两个部分;数组
(2)使全部小(大)于临界值的数据移到左部分,大(小)于临界值的数据移到右部分;函数
(3)这时左、右两个部分红为了两个独立的数组,分别对它们执行(1)(2)(3)的操做,直到全部数据都是有序的状态为止。性能
照这样的描述咱们不难写出快排的代码,我平时遇到排序的问题,只要数据量上了100,想都不想就用快排来解决,可是当我用下面这个程序测试时却出现了问题:测试
1 #include <stdio.h> 2 #include <time.h> 3 #include <stdlib.h> 4 5 #define NUM 10000000 /*待排序的数据量*/ 6 7 void quick_sort(double a[], long left, long right); 8 9 int main(void) 10 { 11 clock_t t_s, t_e; 12 long i; 13 double a[NUM]; 14 15 srand(time(NULL)); 16 for (i = 0; i < NUM; ++i) { 17 a[i] = rand(); 18 } 19 20 t_s = clock(); 21 quick_sort(a, 0, NUM-1); 22 t_e = clock(); 23 double t = (t_e - t_s) / (double)CLOCKS_PER_SEC; /*计算排序用时*/ 24 25 printf("Quick sort %d items used time:%f s\n", NUM, t); 26 27 return 0; 28 } 29 30 void quick_sort(double a[], long left, long right) 31 { 32 long i = left; 33 long j = right; 34 double mid = a[(i + j) / 2]; /*以中间元素做为比较的基准*/ 35 36 while (i <= j) { 37 while (a[i] < mid) 38 ++i; 39 while (mid < a[j]) 40 --j; 41 if (i <= j) { 42 double t = a[i]; 43 a[i] = a[j]; 44 a[j] =t; 45 ++i; 46 --j; 47 } 48 } 49 50 if (i < right) quick_sort(a, i, right); 51 if (left < j) quick_sort(a, left, j); 52 }
我在Linux上运行这个程序出现了"Segmentation fault "错误,而当NUM==1000000时却没有这个错误。查阅相关资料得知这是因为程序递归次数太多,大量的压栈使程序占用的栈空间超过了操做系统所规定的大小,从而出现的内存错误。ui
我用ulimit -s指令的获得的结果是8192,也就是说个人系统默认给每一个程序分配的大概是8M的栈空间。用指令ulimit -s unlimited使栈空间变成实际内存大小后,上面的程序就能够顺利运行而不出错误了(由于Linux上不像Windows能够把栈的大小写入可执行文件中,因此只能用ulimit -s更改的方法了)。难道由于栈的限制,快速排序可以处理的数据量就有上限了吗?那还不如用选择排序——虽然慢,但至少不会出错,因而我找到了这篇文章:快速排序的非递归实现。其实说是“非递归”,只不过是用本身管理的栈来消除递归,算法本质上没有区别,并且从这篇文章做者的测试来看,用栈的方法比用递归的方法反而更慢(做者将其解释为:“用栈的效率比递归高,可是在这个程序中局部变量也就是要每次压栈的数据不多,栈的优点体现不出来,反而更慢……”,我认为这种观点是不对的,因为递归能够理解为有了一个“系统帮你自动管理的栈”,它的效率确定是要比你本身管理的栈要高的,何况你在进行弹栈和压栈操做时又调用了新函数,算上调用的开支,用栈的方法确定比递归慢),不过栈在这里的优点是能够不用考虑操做系统的问题,并且可以处理的数据量只和内存大小有关,没必要受到操做系统对栈空间大小的限制(即便用栈,快排也比不少排序算法要快得多)。spa
之前在学排序算法的时候,专门有讲怎样根据实际问题来选择合适的排序算法,可是我图“省事”,就只用快排和简单选择排序。遇到了这个问题也让我对算法的选择和实现上有了更多认识,同时也了解到用栈消除递归在有些场合(好比系统栈空间受限)的重要意义。操作系统