点击上方蓝字关注我们
归并排序的基本思想核心是分治,就是把一个复杂的问题分成两个或多个相同或相似的子问题,然后把子问题分成更小的子问题,直到子问题可以简单的直接求解,最原问题的解就是子问题解的合并。
算法思想
归并排序的主要思想是分治法,排序的方法就是按照大小顺序合并两个元素,接着依次按照递归的返回顺序,不断地合并排好序的子数组,直到最后把整个数组的顺序排好。
主要过程是:
- 将n个元素从中间切开,分成两部分。(左边可能比右边多1个数)
- 将步骤1分成的两部分,再分别进行递归分解。直到所有部分的元素个数都为1
- 从最底层开始逐步合并两个排好序的数列
算法图解
举个例子,数组:[10,80,70,30,40]
- 分解
- 分解1
- 分解2
- 分解3
- 归并
- 归并1
- 归并2
- 归并3
以上是归并算法操作过程的简单图示。
算法实现
代码实现:
public class MergeSort {
public static void main(String[] args) {
int[] arrays = {90,80,70,30,40};
mergeSort(arrays);
System.out.println(Arrays.toString(arrays));
}
public static void mergeSort(int[] array) {
if (array == null || array.length == 0)
return;
int[] temp = new int[array.length];
mergeSort(array, 0, array.length - 1, temp);
}
// 归并
private static void mergeSort(int array[], int first, int last, int temp[]) {
if (first < last) {
int mid = (first + last) / 2;
mergeSort(array, first, mid, temp); // 递归归并左边元素
mergeSort(array, mid + 1, last, temp); // 递归归并右边元素
mergeArray(array, first, mid, last, temp); // 再将二个有序数列合并
}
}
/**
* 合并两个有序数列
* array[first]~array[mid]为第一组
* array[mid+1]~array[last]为第二组
* temp[]为存放两组比较结果的临时数组
*/
private static void mergeArray(int array[], int first, int mid, int last, int temp[]) {
int i = first, j = mid + 1; // i为第n次分解后第一组的起点, j为第n次分解后第二组的起点
int m = mid, n = last; // m为第n次分解后第一组的终点, n为第n次分解后第二组的终点
int k = 0; // k用于指向temp数组当前放到哪个位置
// 比较合并:将两个有序序列循环比较, 填入数组temp
while (i <= m && j <= n) {
if (array[i] <= array[j]) {
temp[k++] = array[i++];
}
else
temp[k++] = array[j++];
}
// 如果比较完毕, 第一组还有数剩下, 则全部填入temp
while (i <= m) {
temp[k++] = array[i++];
}
// 如果比较完毕, 第二组还有数剩下, 则全部填入temp
while (j <= n) {
temp[k++] = array[j++];
}
// 将排好序的数填回到array数组的对应位置
for (i = 0; i < k; i++) {
array[first + i] = temp[i];
}
}
}
输出结果:
[30, 40, 70, 80, 90]
复杂度分析
空间复杂度
由于合并 n 个元素需要分配一个大小为 n 的额外数组,合并完成之后,这个数组的空间就会被释放,所以算法的空间复杂度就是 O(n)。归并排序也是稳定的排序算法。
时间复杂度
归并算法是一个不断递归的过程。
如何计算时间复杂度?
答:数组的元素个数是 n,时间复杂度是 T(n) 的函数。
把这个规模为 n 的问题分成两个规模分别为 n/2 的子问题,每个子问题的时间复杂度就是 T(n/2),那么两个子问题的复杂度就是 2×T(n/2)。
当两个子问题都得到了解决,即两个子数组都排好了序,需要将它们合并,一共有 n 个元素,每次都要进行最多 n-1 次的比较,所以合并的复杂度是 O(n)。由此我们得到了递归复杂度公式:T(n) = 2×T(n/2) + O(n)。
对于公式求解,不断地把一个规模为 n 的问题分解成规模为 n/2 的问题,一直分解到规模大小为 1。如果 n 等于 2,只需要分一次;如果 n 等于 4,需要分 2 次。这里的次数是按照规模大小的变化分类的。
以此类推,对于规模为 n 的问题,一共要进行 log(n) 层的大小切分。在每一层里,我们都要进行合并,所涉及到的元素其实就是数组里的所有元素,因此,每一层的合并复杂度都是 O(n),所以整体的复杂度就是 O(nlogn)。
排序算法 | 最好时间 | 平均时间 | 最坏时间 | 辅助存储 | 稳定性 | 备注 |
归并排序 | O(nlog(n)) | O(nlog(n)) | O(nlog(n)) | Ο(n) | 稳定 | n大时比较好 |
总结
归并排序需要一个跟待排序数组同等空间的临时数组,因此,使用归并排序时需要考虑是否有空间上的限制。如果没有空间上的限制,归并排序是一个不错的选择。100万的随机数字,归并排序大约耗时150毫秒。
建议:归并算法的思想很重要,其中对两个有序数组合并的操作,在很多面试题里都有用到,建议大家一定要把这个算法练熟。
—END—
扫码关注
点个表示朕
已阅