归并排序
本学习笔记内容来自 BoBo老师的《算法与数据结构体系课》,入门算法与数据结构不二之选
1.归并排序
思想:分治和递归的思想
宏观语意:
对arr[l,r]部分排序:分治(二分),先对 arr[l,mid]和arr[mid+1,r]排序,然后再将二者合并;递归,每一次排序都可以分为更小的数组排序,不断二分下去,直到数组的只有一个元素。
1.归并
先实现归并过程:
合并思想:两个已排序(例如增序)数组的合并思想是依次比较数组开始的元素,小的放入最终结果的第一个,循环下去比较(不好描述)。而对于一个数组二分后,各自排序完成后的合并也是一样,具体步骤如下:
1.复制数组,在temp中进行比较,然后在赋值到arr本身上;
2.mid是中间点,合并就是将 arr[l,mid]
和arr[mid+1,r]
合并,i
和 j
就分别依次遍历两个小数组,依次比较。
private static <E extends Comparable<E>> void merge(E[] arr, int l,int mid, int r){
E[] temp = Arrays.ofCopyRange(arr,l,r+1);
int i=l,j=mid+1;
//每轮循环为arr[k]赋值;k从[l,r]
for(int k=l;k<=r;k++){
if(i>mid){
arr[k] = temp[j-l];//因为temp从[0,r-l],而 arr从[l,r]
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++;
}
}
2.递归调用
执行递归,并提供一个public方法入口
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-l)/2;
sort(arr,l,mid);
sort(arr,mid+1,r);
merge(arr,l,mid,r);
}
3.整合
import java.util.Arrays;
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 - 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 ++;
}
}
}
public static void main(String[] args){
int n = 100000;
Integer[] arr = ArrayGenerator.generateRandomArray(n, n);
SortingHelper.sortTest("MergeSort", arr);
}
}
4.优化
- 判断是否要merge:对于本身就有序的数据,就根本不用merge,直接比较 arr[mid] 和 arr[mid+1]
- 小数量可以用插入排序替换,但对高级语言可能适得其反,不稳定
- 减少内存开辟,在递归调用前临时开辟一个空间,复制数组
优化前后比较:
import java.util.Arrays;
public class MergeSort {
private MergeSort(){}
//------------------------优化前----------------------------
public static <E extends Comparable> void sort(E[] arr){
sort(arr, 0, arr.length - 1);
}
private static <E extends Comparable> void sort(E[] arr, int l, int r){
if (l >= r)
return;
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);
}
private static <E extends Comparable> 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 ++;
}
}
}
//--------------------优化后 sort2---------------------------------
public static <E extends Comparable> void sort2(E[] arr){
// 递归前就开辟一个临时空间
E[] temp = Arrays.copyOf(arr, arr.length);
sort2(arr, 0, arr.length - 1, temp);
}
private static <E extends Comparable> void sort2(E[] arr, int l, int r, E[] temp){
if (l >= r)
return;
int mid = l + (r - l) / 2;
sort2(arr, l, mid, temp);
sort2(arr, mid + 1, r, temp);
// 进行merge前的判断
if(arr[mid].compareTo(arr[mid + 1]) > 0)
merge2(arr, l, mid, r, temp);
}
private static <E extends Comparable> 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 ++;
}
}
}
public static void main(String[] args){
int n = 5000000;
Integer[] arr = ArrayGenerator.generateRandomArray(n, n);
Integer[] arr2 = Arrays.copyOf(arr, arr.length);
SortingHelper.sortTest("MergeSort", arr);
SortingHelper.sortTest("MergeSort2", arr2);
}
}
5.复杂度分析
插入排序对完全有序的数据是O(n)的,分治思想,空间复杂度O(n)
自底向顶的归并
后面就不用学了,后面有时间再学 视频的2-5没看
6.例题
相对应的Offer51题目,统计逆序对数量。用归并排序的思想,答案就是在归并的最后一个else:当temp[i-l]>temp[j-l]的时候,从[i,mid]范围都是此时和j就形成了逆序对。
参考答案
class Solution {
private int res;
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 - 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);
}
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 ++;
}
}
}
}
以上,结束。