归并排序是分治思想的一个应用,其将原问题分解成规模比较小的问题求解,然后再组合成原问题的解。在讲解归并排序之前,先要编写一个将两个有序数组合并成一个有序数组的程序。
int* merge(int *a1, int *a2, int n1, int n2) { int *result = (int *)malloc(sizeof(int)*(n1 + n2)); int *temp = result; int i = 0, j = 0; while (i < n1 && j < n2) { if (*a1 < *a2) { *temp++ = *a1++; i++; } else { *temp++ = *a2++; j++; } } while (i < n1) { *temp++ = *a1++; i++; } while (j < n2) { *temp++ = *a2++; j++; } return result; }上述程序将有序数组a1和a2合并成一个新的有序数组,其中n1和n2分别为a1和a2的大小。合并的方法很简单,依次遍历数组a1和a2,将小的元素插入到新数组对应位置中即可。因为a1和a2本身就是有序的,所以合并之后的数组也是有序的。
例如a1={1,2,3,4,5},a2={2,3,5,6,7,8,9},i和j分别指向a1和a2的当前元素,初始化为i=0,j=0,合并的新数组为result,那么合并的流程如下:
result={1},i=1,j=0
result={1,2},i=2,j=0
result={1,2,2},i=2,j=1
result={1,2,2,3},i=3,j=1
result={1,2,2,3,3},i=3,j=2
......
result={1,2,2,3,3,4,5,5,6},i=5,j=4
result={1,2,2,3,3,4,5,5,6,7},i=5,j=5
......
result={1,2,2,3,3,4,5,5,6,7,8,9}
特别的,针对上述合并过程,对于只有一个元素的数组,合并过程就是简单的比较过程,因为只有一个元素的数组肯定是有序的。
归并排序就是基于上述合并过程工作的。归并排序将要排序的数组划对半分成两个数组,然后对这两个数组调用同样的归并过程,最后将它们合并。
假设有一个8元素数组,其归并排序的过程如下图的归并树所示。
其实归并排序是一个自底向上的过程,例如上图中的8元素数组,其先将数组划分成单元素,然后相邻两个元素进行合并,得到4个新的有序数组,然后继续相邻两个数组进行合并,得到两个新的有序数组,最后再合并得到数组排序后的结果。上图的归并树的每一层的合并过程,不管是多少组数组进行两两合并,其过程也只是遍历一次原数组的全部元素而已,故其时间复杂度为O(n),而树的高度为lg(n),所以归并排序的时间复杂度为O(nlg(n))。
void mergeSort(int *arr, int n) { if (n <= 1) { return; } mergeSort(arr, n / 2); mergeSort(arr + n / 2, n - n / 2); int *result = merge(arr, arr + n / 2, n / 2, n - n / 2); for (int i = 0; i < n; i++) { *arr++ = *(result+i); } free(result); }由于归并排序是一种变址排序,所以上述代码在归并完成后需要对原数组重新赋值。其实如果没有必要,也可以不这样做,直接返回排序后的数组即可。