归并排序
@Author: 张海拔
@Update: 2014-01-14
@Link: http://www.cnblogs.com/zhanghaiba/p/3518324.html
1 /* 2 *Author: ZhangHaiba 3 *Date: 2014-1-13 4 *FIle: merge_sort.c 5 * 6 *merge sort demo 7 */ 8 9 #include <stdio.h> 10 #define N 512 11 #define INF 0x7fffffff //sentinel 12 13 void merge_sort(int *, int, int); 14 void set_array(int *, int); 15 void show_array(int *, int); 16 17 int array[N]; 18 int left_tmp[N/2 + 10]; //save INF to a[array_len] as sentinel 19 int right_tmp[N/2 + 10]; 20 21 int main(void) 22 { 23 int n; 24 25 scanf("%d", &n); 26 set_array(array, n); 27 merge_sort(array, 0, n-1); 28 show_array(array, n); 29 return 0; 30 } 31 32 33 void merge_sort(int *a, int l, int r) 34 { 35 if (l >= r) return; 36 else { 37 int m = l + (r-l)/2; //partition 38 merge_sort(a, l, m); 39 merge_sort(a, m+1, r); 40 int left_len = m-l+1, right_len = r-m, i, j = l; 41 for (i = 0; i < left_len; ++i, ++j) 42 left_tmp[i] = a[j]; 43 left_tmp[i] = INF; 44 for (i = 0; i < right_len; ++i, ++j) 45 right_tmp[i] = a[j]; 46 right_tmp[i] = INF; 47 for (i = j = 0; l <= r; ++l) //merge order list 48 a[l] = left_tmp[i] <= right_tmp[j] ? left_tmp[i++] : right_tmp[j++]; 49 } 50 } 51 52 void set_array(int *a, int n) 53 { 54 int i; 55 56 for (i = 0; i < n; ++i) 57 scanf("%d", a+i); 58 } 59 60 61 void show_array(int *a, int n) 62 { 63 int i; 64 65 for (i = 0; i < n; ++i) 66 printf(i == n-1 ? "%d\n" : "%d ", a[i]); 67 }
归并排序,是分治法的典范。
如果你理解了二叉树的前序遍历和后序遍历,就不难写出归并排序。
归并和快排一样,有一个划分过程。不过这里的归并完全是二分的。划分在前序遍历过程中(边划分边处理子数组),而归并体现在后序遍历过程中。
归并过程:首先复制左右子数组分别放在left_tmp和right_tmp数组中,然后通过有序表的合并算法放回根数组[l, r]中。
显然归并过程是从叶子数组开始的(回溯开始于前进的结束),而叶子数组只有一个元素,必定是有序的。所以说归并的过程核心是有序表的合并算法。
有序表的合并算法其时间复杂度显然是O(n),从整个二分归并树来看,横向看子数组的连起来的长度肯定是n,纵向看树高为lgn。
所以算法的时间复杂度相当稳定,就是O(n*lgn)。
上述实现中,注意:
(1)引入INF存入临时数组有效数据的下一个位置作为哨兵,这样方便简单实现有序表合并算法,因此临时数组长度应该略大于N/2,防止极端情况数组越界。
(2)归并排序一大特征就是:该算法是稳定的。所以在有序表合并时,注意要用"<="而非"<",这样才能确保相同关键字排序后相对前后位置不会改变。