一次快速排序錯誤引起的思考(2)

  上一次我說到所謂的「非遞歸」快速排序算法,不過是用棧來消除了遞歸,它的運行時間確定比遞歸算法長,咱們不妨來實際實現一下。代碼以下:算法

  1 #include <stdio.h>
  2 #include <stdlib.h>
  3 #include <time.h>
  4 
  5 #define MAX_TOP 10000 /*一個很大的棧*/
  6 #define NUM 500L
  7 
  8 /*有關棧的數據結構*/
  9 struct Region {
 10     long left;
 11     long right;
 12 };
 13 
 14 struct Stack {
 15     struct Region reg[MAX_TOP+1];
 16     long top;
 17 };
 18 
 19 /*對棧進行操做的函數*/
 20 void init_stack(struct Stack *s);
 21 void push_stack(struct Stack *s, struct Region r);
 22 struct Region pop_stack(struct Stack *s);
 23 int is_stack_empty(struct Stack *s);
 24 
 25 /*與排序有關的函數*/
 26 
 27 long partition(double a[], long left, long right);    /*劃分區間*/
 28 void nr_qsort(double a[], long left, long right);
 29 
 30 
 31 int main(void)
 32 {
 33     double a[NUM];    /*待排序數據*/
 34     clock_t t_s, t_e;
 35     long i;
 36     
 37     srand(time(NULL));
 38     for (i = 0; i < NUM; ++i)
 39         a[i] = rand() % 1000000;
 40     
 41     /*統計運行時間*/
 42     t_s = clock();
 43     nr_qsort(a, 0, NUM-1);
 44     t_e = clock();
 45     double t = (t_e - t_s) / (double) CLOCKS_PER_SEC;
 46     printf("Non Recursive quick sort %ld items used time: %f s\n", NUM, t);
 47     
 48     return 0;
 49 }
 50 
 51 
 52 /*implementation*/
 53 
 54 void init_stack(struct Stack *s)
 55 {
 56     s->top = -1;
 57 }
 58 
 59 void push_stack(struct Stack *s, struct Region r)
 60 {
 61     if (s->top == MAX_TOP) {
 62         fprintf(stderr, "Stack overflow!\n");
 63         exit(0);
 64     }
 65     s->reg[++s->top] = r;
 66 }
 67 
 68 struct Region pop_stack(struct Stack *s)
 69 {
 70     if (s->top == -1) {
 71         fprintf(stderr, "Stack underflow!\n");
 72         exit(0);
 73     }
 74     return (s->reg[s->top--]);
 75 }
 76 
 77 int is_stack_empty(struct Stack *s)
 78 {
 79     return (s->top == -1);
 80 }
 81 
 82 /*返回劃分的區間*/
 83 long partition(double a[], long left, long right)
 84 {
 85     double base = a[left];    /*以最左邊的元素做爲比較基準*/
 86 
 87     while (left < right) {
 88         while (left < right && a[right] > base)
 89             --right;
 90         a[left] = a[right];
 91         while (left <right && a[left] < base)
 92             ++left;
 93         a[right] = a[left];
 94     }
 95     a[left] = base;
 96     return    left; 
 97 }
 98 
 99 void nr_qsort(double a[], long left, long right)
100 {
101     struct Stack s;
102     struct Region region, regionlow, regionhi;
103     long p; /*記錄劃分出的分界點*/
104 
105     init_stack(&s);
106     region.left = left;
107     region.right = right;
108     push_stack(&s, region);
109     
110     while (!is_stack_empty(&s)) {
111         region = pop_stack(&s);
112         p = partition(a, region.left, region.right);
113         if (p-1 > region.left) {
114             regionlow.left = region.left;
115             regionlow.right = p - 1;
116             push_stack(&s, regionlow);
117         }
118         if (region.right > p + 1) {
119             regionhi.left = p + 1;
120             regionhi.right = region.right;
121             push_stack(&s, regionhi);
122         }
123     }
124 
125 }

  在代碼的第110行至第122行的while循環中,作的正是用棧消除遞歸的工做。想一想遞歸的算法中,把劃分好的左右區間界限(即left,right)保存到了系統管理的棧中,這裏手動把每次劃分出來的區間分界保存至棧中,當第113和118行的兩個條件不知足時,所在區間的元素都是有序的狀態,此時不進行壓棧操做而向前返回(即遞歸的回調)。關於用棧消除遞歸的算法能夠參考關於數據結構的書籍,好比陳銳的《零基礎學數據結構》有關棧的那一章就有介紹。實際運行兩個程序的結果以下:數據結構

$ ./nr_qsort  #非遞歸算法的快排
Non Recursive quick sort 500 items used time: 0.000261 s
$ ./qsort #遞歸算法的快排 
Quick sort 500 items used time:0.000104 s

  之因此只用了500個數據,是由於超過1000個數據後,非遞歸快排的速度就慢的使人難以忍受。下面是另外兩次關於遞歸算法快排的測試:函數

$ time ./qsort 
Quick sort 1000000 items used time:0.289171 s

real    0m0.372s
user    0m0.332s
sys     0m0.012s

#下面更改NUM即數據的個數爲10000000

$ ./qsort
Segmentation fault #超出棧的大小

$ ulimit -s unlimited #更改棧的大小爲不受限
$ time ./qsort 
Quick sort 10000000 items used time:3.259025 s #成功進行了排序

real    0m4.044s
user    0m3.740s
sys     0m0.172s

  這也印證了上一次談到的系統默認限制帶來的問題。測試

相關文章
相關標籤/搜索