快速排序和归并排序的迭代实现
一、 快速排序
快速排序是经典的排序算法,其设计思路是递归的,下面是一段示例代码。
1 void sort(int A[], int n) 2 { 3 if (n <= 0) return; 4 5 int i = 0; 6 for (int j = 0; j < n; j++) 7 if (A[j] < A[0]) 8 swap(A[j], A[++i]); 9 swap(A[0], A[i]); 10 11 sort(A, i); 12 sort(A + i + 1, n - i - 1); 13 }
这段代码能够对大小为 n 的数组 A 原地排序。第3行检查 n 是否合法,若不合法,直接退出。 第 5-9 行实现partition操作 (见于 CLRS,即《算法导论》),第 11-12 行分别对partition后得到的两个部分递归调用 sort。
这3行是不能缺少的,否则会导致递归无法退出。
递归实现的快速排序在平均情况下会创建深度为 log n 的调用栈。
现在介绍快速排序的迭代实现,在进一步说明之前,先列出其中一种实现的源代码。
1 void sort(int A[], int start, int end) // sort A[start ... end] 2 { 3 int s = 0, stack[64]; 4 5 stack[s++] = start, stack[s++] = end; 6 7 while (s > 0) { 8 end = stack[--s]; 9 start = stack[--s]; 10 11 if (start >= end) 12 continue; 13 14 int i = partition(A, start, end); 15 if (i - start >= end - i) { 16 stack[s++] = start, stack[s++] = i - 1; 17 stack[s++] = i + 1, stack[s++] = end; 18 } else { 19 stack[s++] = i + 1, stack[s++] = end; 20 stack[s++] = start, stack[s++] = i - 1; 21 } 22 } 23 }
这段代码实现了对子数组A[start ... end] 的快速排序。这里除了下面几个关键的地方外,不对这段代码做过多的文字说明。
1. stack 数组的大小为 64, 理论上可以对大小为 2 ^ 31 (2 ^ 30 + 2 ^ 29 + ... + 2 ^ 0 + 2 ^ 0) 的数组进行排序。
2. 第 15-21行,对分割后两个子数组大小的比较是必不可少的。若非如此,我们可能需要大小为 2n (n = end - start + 1)的 stack 才能完成排序(请考虑以第一个元素作为锚点分割,初始数组有序的情形)。
二、归并排序
数组归并排序的递归实现原理上比快速排序更简单,通常使用递归的方法实现,算法的时间复杂度为O(nlogn),在每次归并时需要额外的空间(O(n)),这里不再说明。我们知道,对链表,我们也可以采用归并排序的方法对其排序,时间复杂度为 O(nlogn)。链表归并排序的实现同样有递归和迭代两种。
链表排序的递归实现源代码:
1 struct list_node 2 { 3 struct list_node* next; 4 int data; 5 }; 6 7 int list_len(list_node* l) 8 { 9 int ret = 0; 10 while (l != NULL) { 11 l = l->next; 12 ++ret; 13 } 14 return ret; 15 } 16 17 void merge(list_node* l1, list_node* l2) 18 { 19 list_node p; 20 list_node *q = &p; 21 while (l1 != NULL && l2 != NULL) { 22 if (l1->data < l2->data) { 23 q->next = l1; 24 l1 = l1->next; 25 } else { 26 q->next = l2; 27 l2 = l2->next; 28 } 29 q = q->next; 30 } 31 q->next = l1 != NULL ? l1 : l2; 32 return p.next; 33 } 34 35 list_node* sort(list_node* l) { 36 int n = list_len(l); 37 if (n <= 1) 38 return; 39 40 list_node *p = l, *q = NULL; 41 for (int i = n / 2; i > 0; i--) 42 q = p, p = p->next; 43 q->next = NULL; 44 45 l = sort(l); 46 p = sort(p); 47 48 return merge(l, p); 49 }
上述算法的思路: 计算链表的长度,在链表长度的一半的位置将链表切分成两个链表,对这两个链表分别排序,最后将它们合并为一个链表。
SGI STL中针对list<>容器实现了sort方法,在这一方法中采用的是归并排序的迭代形式,下面代码展示出了算法的具体实现。(算法思路待续)
1 #include <iostream> 2 #include <list> 3 #include <iterator> 4 #include <algorithm> 5 using namespace std; 6 7 template <class T> 8 void print(list<T>& lst) { 9 ostream_iterator<T> o_iter(cout, " "); 10 copy(lst.begin(), lst.end(), o_iter); 11 cout << endl; 12 } 13 14 void sort(list<int> &lst) { 15 int size = lst.size(); 16 if (size != 0 && size != 1) { 17 list<int> carry; 18 list<int> counter[64]; 19 int fill = 0; 20 while (!lst.empty()) { 21 carry.splice(carry.begin(), lst, lst.begin()); 22 int i = 0; 23 while(i < fill && !counter[i].empty()) { 24 counter[i].merge(carry); 25 carry.swap(counter[i++]); 26 } 27 carry.swap(counter[i]); 28 if (i == fill) ++fill; 29 } 30 31 for (int i = 1; i < fill; ++i) 32 counter[i].merge(counter[i-1]); 33 lst.swap(counter[fill-1]); 34 } 35 } 36 37 int main() { 38 list<int> lst {1, 4, 2, 8, 5, 7}; 39 sort(lst); 40 print(lst); 41 return 0; 42 }