归并排序时间复杂度为O(n*logn),需要和待排序列相同大小的辅助空间,是一种稳定排序
非递归实现的归并排序效率要高得多
归并排序属于分治法的一种,分治法定义为:将原问题划分为n个规模较小而结构与原问题相似的子问题,递归解决这些子问题,然后合并结果,得出原问题的解。因此分治法有以下三个步骤:
- 分解:将原问题分解为一系列结构相似的子问题
- 递归求解:对子问题递归求解
- 合并(非必须,如二分查找、求乱序序列第K大的数均没有合并步骤):将子问题的解合并,得出原问题的解
归并排序的步骤为:
- 将待排序序列自中心点分割为两个子序列,递归上述步骤直至子序列长度为1不能再分割
- 自递归的最底层反向向上进行归并(merge)操作,具体为将两个各自有序的序列S1和S2合并成一个有序的新序列S3。递归最顶端的归并操作完成后,整个排序过程结束
代码(升序):
void merge(int a[], int left, int mid, int right){
assert(a != NULL);
assert(left >= 0);
assert(mid >= left);
assert(right >= mid);
int len1 = mid-left+1;
int len2 = right-mid;
int * b1 = (int *)malloc((len1+1)*sizeof(int)); //需要额外辅助空间
int * b2 = (int *)malloc((len2+1)*sizeof(int));
memcpy(b1, a+left, len1*sizeof(int));
memcpy(b2, a+mid+1, len2*sizeof(int));
b1[len1] = INT_MAX; //哨兵
b2[len2] = INT_MAX;
int p = 0, q = 0;
for(int i = left; i <= right; i++){
if(b1[p] <= b2[q]) a[i] = b1[p++]; //为保证排序的稳定性,这里必须是<=而不是<
else a[i] = b2[q++];
}
free(b1);
free(b2);
}
void merge_sort(int a[], int left, int right){
assert(a != NULL);
assert(left >= 0);
assert(right >= left);
if(left < right){
int mid = left+(right-left)/2;
merge_sort(a, left, mid);
merge_sort(a, mid+1, right);
merge(a, left, mid, right);
}
}
Tips:
- 分割序列时,由于整数除法的特性,应将序列分为子序列(low, mid)和(mid+1, high),而不是(low, mid-1)和(mid, high),因为若按后一种分法,当序列只含两个元素时会出问题。设当前序列为(n, n+1),low=n, high=n+1,故mid=(low+high)/2=(n+n+1)/2=n(C的整数除法特性)。此时按后一种分法,子序列分别为(n, n-1)和(n, n+1),这明显是错误的
- 在归并函数内设置哨兵变量能够简化操作,不需要每次判断所归并的子序列是否为空。哨兵变量的置应比可能取的值都大,假设为∞,上面代码内设置为INT_MAX,这是C标准库定义的表示int类型最大值的常量,定义在<limits.h>中