归并排序就是对数组的左半边和右半边分别排序,然后再合并两个有序数组。
- 归并排序的过程可以在逻辑上抽象成一棵二叉树,树上的每个节点的值可以认为是
nums[lo..hi]
,叶子节点的值就是数组中的单个元素 - 然后,在每个节点的后序位置(左右子节点已经被排好序)的时候执行
merge
函数,合并两个子节点上的子数组 - 这个
merge
操作会在二叉树的每个节点上都执行一遍,执行顺序是二叉树后序遍历的顺序
一句话总结,归并排序实际上就是先对数组不断进行二分,分到只有一个元素为止,此时 merge
方法开始发挥作用,将两个元素为一组,合并为长度为 2 的有序数组,再将两个长度为 2 的有序数组为一组,合并为长度为 4 的有序数组,以此类推
class Merge {
// 用于辅助合并有序数组(不能原地合并,需要借助额外空间)
private static int[] temp;
public static void sort(int[] nums) {
// 避免递归中频繁分配和释放内存可能产生的性能问题
// 提前给辅助数组开辟内存空间
temp = new int[nums.length];
// 原地修改的方式对整个数组进行排序
sort(nums, 0, nums.length - 1);
}
// 定义:将子数组 nums[lo..hi] 进行排序
private static void sort(int[] nums, int lo, int hi) {
if (lo == hi) {
// 单个元素不用排序
return;
}
// 这样写是为了防止溢出,效果等同于 (hi + lo) / 2
// 注意:对于无法整除的情况,Java 中 int 类型会自动向下取整
int mid = lo + (hi - lo) / 2;
// 先对左半部分数组 nums[lo..mid] 排序
sort(nums, lo, mid);
// 再对右半部分数组 nums[mid+1..hi] 排序
sort(nums, mid + 1, hi);
// 将两部分有序数组合并成一个有序数组
merge(nums, lo, mid, hi);
}
// 将 nums[lo..mid] 和 nums[mid+1..hi] 这两个有序数组合并成一个有序数组
private static void merge(int[] nums, int lo, int mid, int hi) {
// 先把 nums[lo..hi] 复制到辅助数组中
// 以便合并后的结果能够直接存入 nums
for (int i = lo; i <= hi; i++) {
temp[i] = nums[i];
}
// 数组双指针技巧,合并两个有序数组
// i => 左半边数组起始下标
// j => 右半边数组起始下标
int i = lo, j = mid + 1;
for (int p = lo; p <= hi; p++) {
if (i == mid + 1) {
// 左半边数组已全部被合并,只需把右半边数组合并过来即可
nums[p] = temp[j++];
} else if (j == hi + 1) {
// 右半边数组已全部被合并,只需把左半边数组合并过来即可
nums[p] = temp[i++];
} else if (temp[i] > temp[j]) {
// 将较小的元素合入,同时下标前进一位,此时是升序
// 只要将 > 改为 < 就可以把结果改为降序
nums[p] = temp[j++];
} else {
nums[p] = temp[i++];
}
}
}
}
归并排序时间复杂度为 O(nlogn)
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。