归并排序及相关题目

归并排序及相关题目

  • 归并排序

    一、递归实现
    1. 取数组的中间值
    2. 分别对左右两遍的数组进行相同处理
    3. 最后进行合并(重点)
      • 使用辅助数组来记录排序后的结果
      • 将数组分为了左组和右组
      • 通过三个指针来进行标记(i,p1,p2),分别指向的是辅助数组,左组和右组
      • 在p1和p2都不越界的情况下,通过比较p1和p2的数值,如果p1<p2那么就p1对应值给辅助数组,且指向下一个数(p2同理)
      • 最终要么p1越界,要么p2越界,把还没越界的那组后续的数全部赋值到辅助数组中
      • 最后将辅助数组的值填充到原始数组中
    //递归实现
    public static void mergeSort1(int[] arr) {
        if (arr == null || arr.length < 2) {
            return;
        }
        process(arr, 0, arr.length - 1);
    }
    
    private 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);
    }
    
    private 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++];
        }
        //要么p1越界,要么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];
        }
    }
    
    二、非递归实现
    1. 考虑步长问题
      • 步长不能超过边界
      • 防止int数值的溢出
      • 每次步长*2
    2. 考虑左组和右组的边界问题
      • 定义M变量表示左组的末尾,通过它和总长比较来判断左组是否够长,不够则直接跳出循环
      • 如果左组够长,则考虑右组的边界,右组够则为左组末尾 + 步长,不够则为数组末尾,所以取两者的最小值即可
      • 合并并将左组初始值初始化为右组末尾+1
    //非递归实现
    public static void mergeSort2(int[] arr) {
        if (arr == null || arr.length < 2) {
            return;
        }
        int N = arr.length;
        //步长
        int mergeSize = 1;
        while (mergeSize < N) { //logN
            //当前左组,第一个位置
            int L = 0;
            while (L < N) {
                //M表示左组末尾,假设左组数够,那么肯定是小于N
                int M = L + mergeSize - 1;
                //左组不够,直接结束
                if (M >= N) {
                    break;
                }
                //右组够则为左组末尾 + 步长,不够则为数组末尾
                int R = Math.min(M + mergeSize, N - 1);
                // L .... M     M + 1 .... R
                merge(arr, L, M, R);
                L = R + 1;
            }
            //防止int溢出
            if (mergeSize > N / 2) {
                break;
            }
            mergeSize <<= 1;
        }
    }
    
    //对数器
    public static int[] generateArr(int maxSize, int maxvalue) {
        int[] arr = new int[(int) (Math.random() * (maxSize + 1))];
        for (int i = 0; i < arr.length; i++) {
            arr[i] = (int) (Math.random() * (maxvalue + 1)) - (int) (Math.random() * (maxvalue + 1));
        }
        return arr;
    }
    
    public static int[] copyArr(int[] arr) {
        int[] newArr = new int[arr.length];
        for (int i = 0; i < arr.length; i++) {
            newArr[i] = arr[i];
        }
        return newArr;
    }
    
    public static boolean isEqual(int[] arr1, int[] arr2) {
        if (arr1 == null ^ arr2 == null) {
            return false;
        }
        if (arr1 == null && arr2 == null) {
            return true;
        }
        if (arr1.length != arr2.length) {
            return false;
        }
        for (int i = 0; i < arr1.length; i++) {
            for (int j = 0; j < arr2.length; j++) {
                if (arr1[i] != arr2[j]) {
                    return false;
                }
            }
        }
        return true;
    }
    
    public static void main(String[] args) {
        int testTimes = 10000;
        int maxSize = 100;
        int maxValue = 100;
        System.out.println("begin");
        for (int i = 0; i < testTimes; i++) {
            int[] arr1 = generateArr(maxSize, maxValue);
            mergeSort1(arr1);
            int[] arr2 = copyArr(arr1);
            mergeSort2(arr2);
            if (!isEqual(arr1, arr2)) {
                System.out.println("fail");
                break;
            }
        }
    }
    
  • 将数组排好序并返回小和(小和:当前数左边比它小的数的和)

    1. 取数组的中间值
    2. 分别对左右两遍的数组进行相同处理和合并
    3. 返回左右两边的处理值和合并的值的和
    4. 合并(重点)
      • 求当前数之前比它小的数 -> 反向角度思考 ->就是求左组数比右组数小的个数
      • 因为是在有序的基础上,所以只要左组数比右组第一个数小,那么就比它后面的数都小
    public static int smallSum(int[] arr) {
        if (arr == null || arr.length < 2) {
            return 0;
        }
        return process(arr, 0, arr.length - 1);
    }
    
    /**
         * arr[L..R]既要排好序,也要求小和返回
         * 所有merge时,产生的小和,累加
         * 左 排序   merge
         * 右 排序  merge
         * merge
         */
    public static int process(int[] arr, int L, int R) {
        if (L == R) {
            return 0;
        }
        //L < R
        int mid = L + ((R - L) >> 1);
        return
            process(arr, L, mid) + process(arr, mid + 1, R) + merge(arr, L, mid, R);
    }
    
    //在归并排序的合并的基础上,进行比较求值
    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++];
        }
        for (i = 0; i < help.length; i++) {
            arr[L + i] = help[i];
        }
        return res;
    }
    
    //for test  直接通过两数组遍历比较取值
    public static int test(int[] arr) {
        if (arr == null || arr.length < 2) {
            return 0;
        }
        int res = 0;
        for (int i = 1; i < arr.length; i++) {
            for (int j = 0; j < i; j++) {
                res += arr[j] < arr[i] ? arr[j] : 0;
            }
        }
        return res;
    }
    
  • 将数组排好序并返回逆序对个数(逆序对:左边比右边数小的组合)

    1. 其实就是在归并排序基础上进行一些操作
    2. 假设我们是以左组为角度的话,当比较的时候两组对应的值相等时,应先将右组的复制给辅助数组
    public static int reversePairNumber(int[] arr) {
        if (arr == null || arr.length < 2) {
            return 0;
        }
        return process(arr, 0, arr.length - 1);
    }
    
    /**
         * arr[L..R]既要排好序,也要求逆序对数量返回
         * 所有merge时,产生的逆序对数量,累加,返回
         * 左 排序 merge并产生逆序对数量
         * 右 排序 merge并产生逆序对数量
         */
    public static int process(int[] arr, int l, int r) {
        if (l == r) {
            return 0;
        }
        //l<r
        int mid = l + ((r - l) >> 1);
        return process(arr, l, mid) + process(arr, mid + 1, r) + merge1(arr, l, mid, r);
    }
    
    /**
         * 这里合并的适合是跟常规反着进行合并的,当然也可以从左到右进行比较(merge2)
         */
    //右组中有多少个数比左组当前得数小
    private static int merge1(int[] arr, int l, int mid, int r) {
        int[] help = new int[r - l + 1];
        int i = help.length - 1;
        int p1 = mid;
        int p2 = r;
        int res = 0;
        //注意这里mid指的是左组的末尾
        while (p1 >= l && p2 > mid) {
            //左组和右组数相等时,一定要拷贝右边的!
            // 谁大拷贝谁
            //个数:p2 - mid
            //这里个数的计算,最好是举一个例子来看否则不好理解
            res += arr[p1] > arr[p2] ? (p2 - mid) : 0;
            help[i--] = arr[p1] > arr[p2] ? arr[p1--] : arr[p2--];
        }
        while (p1 >= l) {
            help[i--] = arr[p1--];
        }
        while (p2 > mid) {
            help[i--] = arr[p2--];
        }
        for (i = 0; i < help.length; i++) {
            arr[l + i] = help[i];
        }
        return res;
    }
    //merge1中是返回来进行排序的,感觉有点别扭
    private static int merge2(int[] arr, int l, int mid, int r) {
        int[] help = new int[r - l + 1];
        int i = 0;
        int p1 = l;
        int p2 = mid + 1;
        int ans = 0;
        while (p1 <= mid && p2 <= r) {
            ans += arr[p1] > arr[p2] ? (p2-mid) : 0;
            help[i++] = arr[p1] < arr[p2] ? arr[p1++] : arr[p2++];
        }
        while (p1 <= mid) {
            help[i++] = arr[p1++];
        }
        while (p2 <= r) {
            help[i++] = arr[p2++];
        }
        for (i = 0; i < help.length; i++) {
            arr[l + i] = help[i];
        }
        return ans;
    }
    
    //for test
    public static int test(int[] arr) {
        int ans = 0;
        for (int i = 0; i < arr.length; i++) {
            for (int j = i + 1; j < arr.length; j++) {
                if (arr[i] > arr[j]) {
                    ans++;
                }
            }
        }
        return ans;
    }
    
    public static void main(String[] args) {
        int testTimes = 10000;
        int maxSize = 8;
        int maxValue = 20;
        System.out.println("begin");
        for (int i = 0; i < testTimes; i++) {
            int[] arr1 = generateArr(maxSize, maxValue);
            int[] arr2 = copyArr(arr1);
            int ans1 = reversePairNumber(arr1);
            int ans2 = test(arr2);
            if (ans1 != ans2) {
                System.out.println("ans1:"+ans1);
                System.out.println("ans2:"+ans2);
                System.out.println("fail");
                break;
            }
        }
        System.out.println("end");
    }
    
  • 将数组排好序并返回左数比有数两倍大的数的总个数

    1. 依旧是在归并排序的基础上来进行实现
    2. 定义了windowR指针,这个指针采用的是左闭右开[M+1,windowR)的原则,得到右组中满足条件的最终位置,从而算出个数
    public static int biggerTwice(int[] arr) {
        if (arr == null || arr.length < 2) {
            return 0;
        }
        return process(arr, 0, arr.length - 1);
    }
    
    private static int process(int[] arr, int l, int r) {
        if (l == r) {
            return 0;
        }
        int mid = l + ((r - l) >> 1);
        return process(arr, l, mid) + process(arr, mid + 1, r) + merge(arr, l, mid, r);
    }
    
    private static int merge(int[] arr, int l, int mid, int r) {
        //[L ... M ] [M+1 ... R]
        int ans = 0;
        //小技巧,使用左闭右开,不会退的一个指针
        //目前囊括进来的数,是从[M+1,windowR)
        int windowR = mid + 1;
        for (int i = l; i <= mid; i++) {
            //windowR不超过边界,且当前数大于windowR指针对应的数的两倍,则指向下一个数
            while (windowR <= r && arr[i] > (arr[windowR] << 1)) {
                windowR++;
            }
            //刚开始则为添加0个
            ans += windowR - mid - 1;
        }
        int[] help = new int[r - l + 1];
        int i = 0;
        int p1 = l;
        int p2 = mid + 1;
        while (p1 <= mid && p2 <= r) {
            help[i++] = arr[p1] < arr[p2] ? arr[p1++] : arr[p2++];
        }
        while (p1 <= mid) {
            help[i++] = arr[p1++];
        }
        while (p2 <= r) {
            help[i++] = arr[p2++];
        }
        for (i = 0; i < help.length; i++) {
            arr[l + i] = help[i];
        }
        return ans;
    }
    
    public static int test(int[] arr) {
        int ans = 0;
        for (int i = 0; i < arr.length; i++) {
            for (int j = i + 1; j < arr.length; j++) {
                if (arr[i] > (arr[j] << 1)) {
                    ans++;
                }
            }
        }
        return ans;
    }
    
    public static void main(String[] args) {
        int testTime = 500000;
        int maxSize = 100;
        int maxValue = 100;
        System.out.println("测试开始");
        for (int i = 0; i < testTime; i++) {
            int[] arr1 = generateArr(maxSize, maxValue);
            int[] arr2 = copyArr(arr1);
            if (biggerTwice(arr1) != test(arr2)) {
                System.out.println("Oops!");
                break;
            }
        }
        System.out.println("测试结束");
    }
    
  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值