归并排序

本学习笔记内容来自 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]合并,ij就分别依次遍历两个小数组,依次比较。

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 ++;
            }
        }
    }
}

以上,结束。

posted @ 2021-12-04 13:42  又一个蛇佬腔  阅读(53)  评论(0编辑  收藏  举报