快速排序是目前基於關鍵字的內部排序算法中平均性能最好的,它採用了分治策略,這既是快速排序的優勢也是它的缺點。從快速排序的算法描述上咱們能夠發現它具備遞歸的結構:算法
(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
之前在學排序算法的時候,專門有講怎樣根據實際問題來選擇合適的排序算法,可是我圖「省事」,就只用快排和簡單選擇排序。遇到了這個問題也讓我對算法的選擇和實現上有了更多認識,同時也瞭解到用棧消除遞歸在有些場合(好比系統棧空間受限)的重要意義。操作系統