思路
- 归并排序用到的是分治的思想:
- 分割:递归地将数组平均分成两个子数组subarr1和subarr2,直到子数组长度为1,无法再次分割
- 归并:合并排序好的两个子数组,且使得合并后的数组仍是有序的
归并排序动画示意:
动图来自维基百科,侵删!
- 所要做的工作主要就两个:
- 递归分割
- 归并
1. 递归分割
private static void mergeSort(int[] arr, int left, int right) {
// 直到子数组长度为1,无法再次分割,return返回,停止分割
if (left >= right) return;
// 计算数组中间下标
// 奇数个元素,左子数组元素比右子数组多一个
// 偶数个则元素个数相等
int mid = (left + right) / 2;
// 递归分割左右子数组
mergeSort(arr, left, mid);
mergeSort(arr, mid + 1, right);
// 将两个子数组合并
merge(arr, left, mid, right);
}
- 递归分治,相当于用一颗完全二叉树去分割原数组,根据完全二叉树的特性,时间复杂度为O(logn)。
- 方法通过将下标作为传参直接在原数组所在内存上进行递归操作,mid变量占用额外储存空间O(1)。
2. 归并
private static void merge(int[] arr, int left, int mid, int right) {
// 临时辅助数组,储存右子数组
int[] temp = new int[right - mid];
// 将右子数组copy到辅助数组temp中
for (int i = mid + 1; i <= right; i++) {
temp[i - mid - 1] = arr[i];
}
int tempIndex = temp.length - 1;
// 将排序好的数组B合并到排序好的数组A中(假设A数组的总长度可以容纳B)
while (tempIndex >= 0 && mid >= left) {
arr[right--] = temp[tempIndex] > arr[mid]? temp[tempIndex--] : arr[mid--];
}
// 若辅助数组(数组B)还没比较完,而A的前半部分有效长度的数字已经比较完了,则依次将B加入A
while (tempIndex >= 0) {
arr[right--] = temp[tempIndex--];
}
}
- 每次合并操作,占用额外储存空间为O(n/2)。
- 赋值操作次数O(n)次,比较O(n/2)~O(n-1)次,因此每次归并时间复杂度为O(n)。
总的来说,归并排序空间复杂度不超过O(n),时间复杂度为O(nlogn)。
- 随机生成数组的方法:
public static int[] getRandomArr(int length, int rangeL, int rangeR) {
if (rangeL >= rangeR) throw new RuntimeException("范围输入有误");
int[] arr = new int[length];
for (int i = 0; i < length; i++) {
arr[i] = (int) (Math.random() * (rangeR - rangeL + 1)) + rangeL;
}
return arr;
}
总的代码如下:
public class MergeSort {
public static void main(String[] args) {
int[] arr = getRandomArr(4, 1, 10);
for (int i : arr) System.out.print(i + " ");
mergeSort(arr);
System.out.println();
for (int i : arr) System.out.print(i + " ");
}
public static void mergeSort(int[] arr) {
mergeSort(arr, 0, arr.length - 1);
}
private static void mergeSort(int[] arr, int left, int right) {
// 直到子数组长度为1,无法再次分割,return返回,停止分割
if (left >= right) return;
// 计算数组中间下标
// 奇数个元素,左子数组元素比右子数组多一个
// 偶数个则元素个数相等
int mid = (left + right) / 2;
// 递归分割左右子数组
mergeSort(arr, left, mid);
mergeSort(arr, mid + 1, right);
// 将两个子数组合并
merge(arr, left, mid, right);
}
private static void merge(int[] arr, int left, int mid, int right) {
// 临时辅助数组,储存右子数组
int[] temp = new int[right - mid];
// 将右子数组copy到辅助数组temp中
for (int i = mid + 1; i <= right; i++) {
temp[i - mid - 1] = arr[i];
}
int tempIndex = temp.length - 1;
// 将排序好的数组B合并到排序好的数组A中(假设A数组的总长度可以容纳B)
while (tempIndex >= 0 && mid >= left) {
arr[right--] = temp[tempIndex] > arr[mid]? temp[tempIndex--] : arr[mid--];
}
// 若辅助数组(数组B)还没比较完,而A的前半部分有效长度的数字已经比较完了,则依次将B加入A
while (tempIndex >= 0) {
arr[right--] = temp[tempIndex--];
}
}
public static int[] getRandomArr(int length, int rangeL, int rangeR) {
if (rangeL >= rangeR) throw new RuntimeException("范围输入有误");
int[] arr = new int[length];
for (int i = 0; i < length; i++) {
arr[i] = (int) (Math.random() * (rangeR - rangeL + 1)) + rangeL;
}
return arr;
}
}