一、归并排序法的原理
归并过程一览(了解l、r、mid是什么意思)
- mid 什么意思?
- ( l + r)/2 = (0 + 7)/2 ≈3
- mid = 3
三、实现归并过程操作
// 合并两个有序的区间 arr[l,mid] 和 arr[mid+1,r]
private static <E extends Comparable<E>> void merge(E[] arr, int l, int mid, int r) {
E[] temp = Arrays.copyOfRange(arr, l, r + 1);
int i = l, j = mid + 1;
//每轮循环为 arr[k]赋值
for (int k = l; k <= r; k++) {
if (i > mid) {
arr[k] = temp[j - l];
j++;
}else if (j>r){
arr[k] = temp[i-l];
i++;
}else if(temp[i-l].compareTo(temp[j-l])<=0){
arr[k] = temp[i-l];
i++;
}else {
arr[k] = temp[j-l];
j++;
}
// arr[i] 和 arr[j]
}
} 四、实现归并排序算法操作
完整代码
public class MergeSort {
private MergeSort() {
}
public static <E extends Comparable<E>> void sort(E[] arr) {
sort(arr, 0, arr.length - 1);
}
private static <E extends Comparable<E>> void sort(E[] arr, int l, int r) {
if (l >= r) {
return;
}
//int mid = (l + r) / 2;
//上亿级别的数组需要这样优化
int mid = l + (r - l) / 2;
sort(arr, l, mid);
sort(arr, mid + 1, r);
merge(arr, l, mid, r);
}
// 合并两个有序的区间 arr[l,mid] 和 arr[mid+1,r]
private static <E extends Comparable<E>> void merge(E[] arr, int l, int mid, int r) {
E[] temp = Arrays.copyOfRange(arr, l, r + 1);
int i = l, j = mid + 1;
//每轮循环为 arr[k]赋值
for (int k = l; k <= r; k++) {
if (i > mid) {
arr[k] = temp[j - l];
j++;
} else if (j > r) {
arr[k] = temp[i - l];
i++;
} else if (temp[i - l].compareTo(temp[j - l]) <= 0) {
arr[k] = temp[i - l];
i++;
} else {
arr[k] = temp[j - l];
j++;
}
// arr[i] 和 arr[j]
}
}
public static void main(String[] args) {
int n = 100000;
Integer[] arr = ArrayGenerator.generateRandomArray(n,n);
SortingHelper.sortTest("MergeSort",arr);
}
} 控制台输出
五、归并算法的复杂度分析
首先我们先来看三个排序具体所需要的时间(归并排序法、选择排序法、冒泡排序法),如下图:
归并排序法复杂度为——O(nlogn)级别
随着n的逐渐加大,那么这个时间会越来越大!时间差异将会大得恐怖!
六、对归并算法的优化操作
判断是否需要排序才进行合并操作,具体看下面的代码
private static <E extends Comparable<E>> void sort2(E[] arr, int l, int r) {
if (l >= r) {
return;
}
//int mid = (l + r) / 2;
//上亿级别的数组需要这样优化
int mid = l + (r - l) / 2;
sort(arr, l, mid);
sort(arr, mid + 1, r);
if (arr[mid].compareTo(arr[mid+1])>0){
merge(arr, l, mid, r);
}
} 关键代码
if (arr[mid].compareTo(arr[mid+1])>0){
merge(arr, l, mid, r);
}
控制台输出
当我们如果是完全有序的数组,我们的速度将会更快!具体如下图
七、使用插入排序算法来优化归并排序算法
优化思想:在比较小的数组排序中,我们使用插入排序法可能会让代码排序的效率更加的高效,具体代码如下,这里没有给出完全的插入排序算法代码,只有关键代码,具体可以查看我插入排序算法的文章
private static <E extends Comparable<E>> void sort2(E[] arr, int l, int r) {
if (r-l<=15){
InsertionSort.sort3(arr,l,r);
}
//上亿级别的数组需要这样优化
int mid = l + (r - l) / 2;
sort(arr, l, mid);
sort(arr, mid + 1, r);
merge(arr, l, mid, r);
if (arr[mid].compareTo(arr[mid+1])>0){
merge(arr, l, mid, r);
}
} 可以看到,我们对于长度小于等于15的数组,直接调用了插入排序算法的代码,如下
public static <E extends Comparable<E>> void sort3(E[] arr,int l,int r) {
for (int i = 0; i <= r; i++) {
//将 arr[i] 插入到合适的位置
E t = arr[i];
int j;
for (j = i; j - 1 >= l && t.compareTo(arr[j - 1]) < 0; j--) {
arr[j] = arr[j-1];
}
arr[j] = t;
}
} 我们以50万长度数组来测试
八、归并排序法的内存操作
具体操作思想:之前我们每次跑一下这个方法我们就重新创建一个数组,这样内存中的数据会非常的大,在长度很长的时候这是非常不友好的,我们修改一下,让我们只在内存中创建一次数组!
具体代码如下
public static <E extends Comparable<E>> void sort2(E[] arr) {
E[] temp = Arrays.copyOf(arr, arr.length);
sort2(arr, 0, arr.length - 1, temp);
}
private static <E extends Comparable<E>> void sort2(E[] arr, int l, int r, E[] temp) {
if (l >= r) {
return;
}
//int mid = (l + r) / 2;
//上亿级别的数组需要这样优化
int mid = l + (r - l) / 2;
sort2(arr, l, mid, temp);
sort2(arr, mid + 1, r, temp);
if (arr[mid].compareTo(arr[mid + 1]) > 0) {
merge2(arr, l, mid, r, temp);
}
}
// 合并两个有序的区间 arr[l,mid] 和 arr[mid+1,r]
private static <E extends Comparable<E>> void merge2(E[] arr, int l, int mid, int r, E[] temp) {
System.arraycopy(arr, l, temp, l, r - l + 1);
int i = l, j = mid + 1;
//每轮循环为 arr[k]赋值
for (int k = l; k <= r; k++) {
if (i > mid) {
arr[k] = temp[j];
j++;
} else if (j > r) {
arr[k] = temp[i];
i++;
} else if (temp[i].compareTo(temp[j]) <= 0) {
arr[k] = temp[i];
i++;
} else {
arr[k] = temp[j];
j++;
}
// arr[i] 和 arr[j]
}
}
依然是50万长度的数组进行对比测试,如下图
九、实现自底向上的归并排序
我们之前的 归并排序是从顶部一路下来的递归操作,现在我们来看一个自底向上的归并排序操作
// 自底向上的归并排序
static <E extends Comparable<E>> void sortBU(E[] arr) {
E[] temp = Arrays.copyOf(arr, arr.length);
int n = arr.length;
// 遍历合并的区间长度
for (int sz = 1; sz < n; sz += sz) {
// 遍历合并的两个区间的起始位置 i
// 合并 [i,i + sz - 1] 和 [i + sz ,Math.min(i + sz + sz - 1, n - 1)]
for (int i = 0; i + sz < n; i += sz + sz) {
if (arr[i+sz-1].compareTo(arr[i+sz])>0){
merge(arr, i,i + sz - 1, Math.min(i + sz + sz - 1, n - 1), temp);
}
}
}
} 控制台输出
十、数组中的逆序数对数量问题
什么是逆序数对
那么问题来了,我们想要求一个数组中,含有多少个逆序数对,该怎么操作呢?
我们来看力扣的——剑指 Offer 51. 数组中的逆序对
具体如下
我们暴力代码提交力扣,看看什么情况
class Solution {
public int reversePairs(int[] nums) {
int res = 0;
for (int i = 0; i < nums.length; i++) {
for (int j = i + 1; j < nums.length; j++) {
if (nums[i] > nums[j]) {
res++;
}
}
}
return res;
}
} 我们使用归并排序来帮助我们实现
package solution2;
class Solution {
private int res = 0;
public int reversePairs(int[] nums) {
int[] temp = new int[nums.length];
res = 0;
sort(nums, 0, nums.length - 1, temp);
return res;
}
private void sort(int[] arr, int l, int r, int[] temp) {
if (l >= r) {
return;
}
//int mid = (l + r) / 2;
//上亿级别的数组需要这样优化
int mid = l + (r - l) / 2;
sort(arr, l, mid, temp);
sort(arr, mid + 1, r, temp);
if (arr[mid] > arr[mid + 1]) {
merge(arr, l, mid, r, temp);
}
}
// 合并两个有序的区间 arr[l,mid] 和 arr[mid+1,r]
private void merge(int[] arr, int l, int mid, int r, int[] temp) {
System.arraycopy(arr, l, temp, l, r - l + 1);
int i = l, j = mid + 1;
//每轮循环为 arr[k]赋值
for (int k = l; k <= r; k++) {
if (i > mid) {
arr[k] = temp[j];
j++;
} else if (j > r) {
arr[k] = temp[i];
i++;
} else if (temp[i] <= temp[j]) {
arr[k] = temp[i];
i++;
} else {
res += mid - i + 1;
arr[k] = temp[j];
j++;
}
// arr[i] 和 arr[j]
}
}
} 提交力扣代码
这里我们还可以有一种优化思想,因为我们定义了一个res,我们需要对它进行维护,那我们应该怎么优化呢?
package solution2;
class Solution {
public int reversePairs(int[] nums) {
int[] temp = new int[nums.length];
return sort(nums, 0, nums.length - 1, temp);
}
private int sort(int[] arr, int l, int r, int[] temp) {
if (l >= r) {
return 0;
}
int res = 0;
//int mid = (l + r) / 2;
//上亿级别的数组需要这样优化
int mid = l + (r - l) / 2;
res += sort(arr, l, mid, temp);
res += sort(arr, mid + 1, r, temp);
if (arr[mid] > arr[mid + 1]) {
res += merge(arr, l, mid, r, temp);
}
return res;
}
// 合并两个有序的区间 arr[l,mid] 和 arr[mid+1,r]
private int merge(int[] arr, int l, int mid, int r, int[] temp) {
System.arraycopy(arr, l, temp, l, r - l + 1);
int i = l, j = mid + 1,res=0;
//每轮循环为 arr[k]赋值
for (int k = l; k <= r; k++) {
if (i > mid) {
arr[k] = temp[j];
j++;
} else if (j > r) {
arr[k] = temp[i];
i++;
} else if (temp[i] <= temp[j]) {
arr[k] = temp[i];
i++;
} else {
res += mid - i + 1;
arr[k] = temp[j];
j++;
}
// arr[i] 和 arr[j]
}
return res;
}
} 提交力扣
本文作者为DBC,转载请注明。













