归并排序

186 阅读1分钟

归并排序

  • 整体是递归,左边排好序+右边排好序+merge让整体有序
  • 让其整体有序的过程里用了排外序方法
  • 利用master公式来求解时间复杂度
  • 当然可以用非递归实现

递归算法每次让左右两边有序,之后进行merge,两边进行比较,左边的第一位和右边的第一位进行比较,谁小将谁拷贝进新的数组中,如果相同优先拷贝左边的,如果某一边offset越界,直接将另一边拷贝进新的数组,最后将新的数组刷回原数组

图片.png

                                   非递归算法图解
/**
     * 递归算法实现
     * @param arr 进行排序的数组
     */
    public static void mergeSort1(int[] arr){
        if (arr == null || arr.length < 2){
            return;
        }

    }

    /**
     * 使数组在L-R上有序
     * @param arr 进行排序的数组
     * @param l 开始下标
     * @param r 结束下标
     */
    public static void process(int[] arr, int l, int r){
        if (l == r){
            return;
        }
        int mid = l + ((r - l) >> 1);
        process(arr,l,mid);
        process(arr,mid + 1,r);
        merge(arr,l,mid,r);
    }

    /**
     * 将两边有序的数组进行合并
     * @param arr 要合并的数组
     * @param l 开始的下标
     * @param m 中间的小标
     * @param r 结束的下标
     */
    public static void merge(int[] arr,int l,int m,int r){
        int[] help = new int[r - l + 1];
        int i = 0;
        int p1 = l;
        int p2 = m + 1;
        while (p1 <= m && p2 <= r){
            help[i++] = arr[p1] <= arr[p2] ? arr[p1++] : arr[p2++];
        }
        while (p1 <= m){
            help[i++] = arr[p1++];
        }
        while (p2 <= r){
            help[i++] = arr[p2++];
        }
        for (i = 0; i < help.length; i++) {
            arr[l + i] = help[i];
        }
    }

图片.png

                                     非递归算法图解
/**
     * 非递归算法实现
     * @param arr 要排序的数组
     */
    public static void mergeSort2(int[] arr){
        if (arr == null || arr.length < 2){
            return;
        }
        int n = arr.length;
        int mergeSize = 1;
        while (mergeSize < n){
            int l = 0;
            while (l < n){
                int m = l + mergeSize - 1;
                if (m >= n){
                    break;
                }
                int r = Math.min(m + mergeSize,n - 1);
                merge(arr,l,m,r);
                l = r + 1;
            }
            if (mergeSize > n / 2){
                break;
            }
            mergeSize <<= 1;
        }
    }

归并排序的时间复杂度:使用master公式进行计算得出:T(N) = 2T(N/2) + O(N^1) 为O(NlogN) merge的过程需要申请辅助数组,所以空间复杂度为O(N) 归并排序的实质是把比较行为变成了有序信息并传递,比O(N^2)的排序快 O(N^2)的排序算法浪费了过多的比较行为

常见的面试题

在一个数组中,一个数左边比它小的数的总和,叫数的小和,所有数的小和累加起来,叫数组小和。求数组小和。 例子: [1,3,4,2,5] 1左边比1小的数:没有 3左边比3小的数:1 4左边比4小的数:1、3 2左边比2小的数:1 5左边比5小的数:1、3、4、 2 所以数组的小和为1+1+3+1+1+3+4+2=16

思路:一个数左边比他小的数的总和等价于一个数右边有几个数比他大,小和就是几个本身,如果使用暴力方法,时间复杂度为O(N^2),使用归并排序,时间复杂度为O(NlogN) *

/**
     * 计算小和
     * @param arr 需要计算的数组
     * @return 小和
     */
    public static int smallSum(int[] arr){
        if (arr == null || arr.length < 2){
            return 0;
        }
        return process(arr,0,arr.length - 1);
    }

    /**
     * 归并排序
     * @param arr 数组
     * @param l 开始下标
     * @param r 结束下标
     * @return 小和
     */
    public static int process(int[] arr,int l,int r){
        if (l == r){
            return 0;
        }
        int m = l + ((r - l) >> 1);
        return process(arr,l,m) + process(arr,m + 1,r) + merge(arr,l,m,r);
    }

    /**
     * 合并排序
     * @param arr 数组
     * @param l 开始下标
     * @param m 中间下标
     * @param r 结束下标
     * @return 小和
     */
    public static int merge(int[] arr,int l,int m,int r){
        int[] help = new int[r - l +1];
        int i = 0;
        int p1 = l;
        int p2 = m + 1;
        int res = 0;
        while (p1 <= m && p2 <= r){
            res += arr[p1] < arr[p2] ? (r - p2 + 1) * arr[p1] : 0;
            help[i++] = arr[p1] < arr[p2] ? arr[p1++] : arr[p2++];
        }
        while (p1 <= m){
            help[i++] = arr[p1++];
        }
        while (p2 <= r){
            help[i++] = arr[p2++];
        }
        return res;
    }

图片.png

                                                     图解