归并排序
归并排序的的核心在于merge函数(合并过程)
归并排序将排序的数组分为如下两个阶段
归而唯一:阶段一
不管三七二十一,我先把数组一分为二,直到每个数组只剩下一个元素,拆分阶段结束(此时每个子数组都是有序数组)
并而为整:阶段二
不断将相邻的两个有序子数组,合并成为一个大的有序数组,直到合并成为完整的数组,此时整个数组有序!
阶段二
归并排序的merge函数
归并排序的代码实现
//在数组arr[l...r]上进行归并排序
private static void mergeSortInternal(int[] arr, int l, int r) {
// 1.base case
if (l >= r) {
// 区间中只剩下一个元素或者一个元素都没有,空区间
return;
}
int mid = l + ((r - l) >> 1);
// 先在左半数组arr[l..mid]上进行归并排序
mergeSortInternal(arr,l,mid);
// 继续在右半数组arr[mid + 1..r]上进行归并排序
mergeSortInternal(arr,mid + 1,r);
// 现在开始合并左右两个有序子数组!
merge(arr,l,mid,r);
}
//合并arr[l..mid] 和 arr[mid + 1..r]两个有序子数组为一个大的新的有序数组
private static void merge(int[] arr, int l, int mid, int r) {
// 1.先创建一个大小为两个子数组之和的临时数组aux
int[] aux = new int[r - l + 1];
// 2.复制两个子数组内容到aux中
//方式一
// for (int i = l; i <= r; i++) {
// aux[i - l] = arr[i];
// }
//方式二(个人建议使用方式二,但是方式一好理解)
// 使用System类的数组拷贝,和上面的for循环完全一致
System.arraycopy(arr,l,aux,0,(r - l + 1));
int i = l,j = mid + 1;
// k表示当前覆盖到原数组的哪个下标了
for (int k = l; k <= r; k++) {
if (i > mid) {
// 子数组1已经覆盖结束,直接将子数组2的剩余内容覆盖
arr[k] = aux[j - l];
j ++;
}else if (j > r) {
// 子数组2的内容已经覆盖结束,将子数组1的剩余内容覆盖
arr[k] = aux[i - l];
i ++;
}else if (aux[i - l] <= aux[j - l]) {
// 情况3.两个子数组都有元素,且子数组1元素 <= 子数组2
arr[k] = aux[i - l];
i ++;
}else {
// 情况4.两个子数组都有元素,且子数组2元素 < 子数组1
arr[k] = aux[j - l];
j ++;
}
}
}
克隆数组方式二详解
归并排序的优化
代码实现
private static void mergeSortInternal(int[] arr, int l, int r) {
// 1.base case,碰到终止条件时,说明子数组拆分的只剩下一个元素或为空
if (r - l <= 15) {
// 优化2.在小数组区间上使用插入排序进行优化,减少了很多小数组的递归合并过程
insertionSortPart(arr,l,r);
return;
}
int mid = l + ((r - l) >> 1);
// 先在左半数组arr[l..mid]上进行归并排序
mergeSortInternal(arr,l,mid);
// 继续在右半数组arr[mid + 1..r]上进行归并排序
mergeSortInternal(arr,mid + 1,r);
// 若此时arr[mid] 子数组1的最大值 <= arr[mid + 1] 子数组2的最小值 => 整个数组已经有序,压根不需要进行合并!!
// 只有当子数组1和子数组2还有部分元素存在乱序,才需要合并!
// 现在开始合并左右两个有序子数组!
if (arr[mid] > arr[mid + 1]) {
// 优化1.只有当两个子数组部分元素之间还存在乱序,才需要合并
merge(arr,l,mid,r);
}
}
//在arr[l..r]上使用插入排序
private static void insertionSortPart(int[] arr, int l, int r) {
// 有序区间[0..i)
// 无序区间[i..r]
for (int i = l + 1; i <= r; i++) {
for (int j = i; j > l && arr[j] < arr[j - 1]; j--) {
swap(arr,j,j - 1);
}
}
}
关于归并排序的时间复杂度和空间复杂度
空间复杂度:
时间复杂度
时间复杂度是O(nlogn)