算法数据结构:归并排序

430 阅读3分钟

1、介绍

归并排序是建立在归并操作上的一种有效的排序算法。该算法是采用分治法(Divide and Conquer)的一个非常典型的应用。将已有序的子集合合并,得到完全有序的集合;即先使每个子集合有序,再使子集合间有序。若将两个有序集合合并成一个有序集合,称为 2-路归并。

2、分析

  1. 把长度为 n 的输入集合,分成两个长度为 n/2 的子集合;
  2. 对这两个子集合分别采用归并排序;
  3. 将两个排序好的子集合合并成一个最终的排序集合。

左边排好序 + 右边排好序 + merge 让整体有序

3、实现

3.1、递归


/**
 * @description: 归并排序-递归实现
 * @author: erlang
 * @since: 2020-09-28 22:31
 */
public class MergeSort {

    /**
     * 递归方法排序
     *
     * @param arr 待排序数组
     */
    public static void mergeSort(int[] arr) {
        if (arr == null || arr.length < 2) {
            return;
        }

        process(arr, 0, arr.length - 1);
    }

    /**
     * @param arr   待处理数组
     * @param left  左边界
     * @param right 有边界
     */
    private static void process(int[] arr, int left, int right) {
        if (left == right) {
            return;
        }
        int mid = left + ((right - left) >> 1);
        process(arr, left, mid);
        process(arr, mid + 1, right);
        merge(arr, left, mid, right);
    }

    /**
     * 合并
     *
     * @param arr   带处理数组
     * @param left  左边界
     * @param mid   中间分界线
     * @param right 右边界
     */
    public static void merge(int[] arr, int left, int mid, int right) {
        int[] help = new int[right - left + 1];
        int index = 0;
        int p1 = left;
        int p2 = mid + 1;
        while (p1 <= mid && p2 <= right) {
            help[index++] = arr[p1] <= arr[p2] ? arr[p1++] : arr[p2++];
        }

        // 把 [left, mid] 或 [mid + 1, right] 中剩余的元素存到 help 数组中
        while (p1 <= mid) {
            help[index++] = arr[p1++];
        }

        while (p2 <= right) {
            help[index++] = arr[p2++];
        }

        // 将 help 中的元素重新放回 arr 的 [left, right] 区间内
        for (int i = 0; i < help.length; i++) {
            arr[left + i] = help[i];
        }
    }

    public static void main(String[] args) {
        ArraySortUtils.testSort(MergeSort::mergeSort, 100, 100);
    }
}

3.2、迭代


/**
 * @description: 归并排序-迭代实现
 * @author: erlang
 * @since: 2020-09-28 22:55
 */
public class MergeSort {

    public static void mergeSort(int[] arr) {
        if (arr == null || arr.length < 2) {
            return;
        }
        int n = arr.length;

        // 当前有序的,左组长度
        int mergeSize = 1;

        while (mergeSize < n) {

            int left = 0;

            while (left < n) {
                int mid = left + mergeSize - 1;

                if (mid >= n) {
                    break;
                }

                int right = Math.min(mid + mergeSize, n - 1);

                merge(arr, left, mid, right);

                left = right + 1;
            }

            // 如果 mergeSize 大于数组长度的二分之一,说明数组已经排完序了
            // 防止 mergeSize << 1 超过 int 的长度
            if (mergeSize > (n >> 1)) {
                break;
            }

            // 每次增加一倍
            mergeSize <<= 1;
        }

    }

    /**
     * 合并
     *
     * @param arr   带处理数组
     * @param left  左边界
     * @param mid   中间分界线
     * @param right 右边界
     */
    public static void merge(int[] arr, int left, int mid, int right) {
        int[] help = new int[right - left + 1];
        int index = 0;
        int p1 = left;
        int p2 = mid + 1;
        while (p1 <= mid && p2 <= right) {
            help[index++] = arr[p1] <= arr[p2] ? arr[p1++] : arr[p2++];
        }

        while (p1 <= mid) {
            help[index++] = arr[p1++];
        }

        while (p2 <= right) {
            help[index++] = arr[p2++];
        }

        for (int i = 0; i < help.length; i++) {
            arr[left + i] = help[i];
        }
    }

    public static void main(String[] args) {
        ArraySortUtils.testSort(MergeSort::mergeSort, 100, 100);
    }
}

4、实战

4.1、小数和

4.1.1、描述

在一个数组中,一个数左边比它小的数的总和,叫数的小和,所有数的小和累加起来,叫数组小和。求数组小和。

例子: [1,3,4,2,5]

  1. 1左边比1小的数:没有
  2. 3左边比3小的数:1
  3. 4左边比4小的数:1、3
  4. 2左边比2小的数:1
  5. 5左边比5小的数:1、3、4、 2
  6. 所以数组的小和为1+1+3+1+1+3+4+2=16
4.1.2、实现

/**
 * @description: 小和
 * @author: erlang
 * @since: 2020-10-13 21:49
 */
public class SmallSum {

    public static int smallSum(int[] arr) {
        if (arr == null || arr.length < 2) {
            return 0;
        }
        return process(arr, 0, arr.length - 1);
    }

    private static int process(int[] arr, int left, int right) {

        if (left == right) {
            return 0;
        }

        int mid = left + ((right - left) >> 1);

        return process(arr, left, mid)
                +
                process(arr, mid + 1, right)
                +
                merge(arr, left, mid, right);
    }

    private static int merge(int[] arr, int left, int mid, int right) {
        int[] help = new int[right - left + 1];
        int index = 0;
        int p1 = left;
        int p2 = mid + 1;
        int sum = 0;
        while (p1 <= mid && p2 <= right) {
            sum += arr[p1] < arr[p2] ? (right - p2 + 1) * arr[p1] : 0;
            help[index++] = arr[p1] < arr[p2] ? arr[p1++] : arr[p2++];
        }

        while (p1 <= mid) {
            help[index++] = arr[p1++];
        }

        while (p2 <= right) {
            help[index++] = arr[p2++];
        }

        for (int i = 0; i < help.length; i++) {
            arr[left + i] = help[i];
        }
        return sum;
    }
    // for test
    public static int comparator(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;
    }

    // for test
    public static void main(String[] args) {
        int testTime = 500000;
        int maxSize = 10;
        int maxValue = 100;
        boolean succeed = true;
        for (int i = 0; i < testTime; i++) {
            int[] arr1 = ArraySortUtils.generateRandomArray(maxSize, maxValue);
            int[] arr2 = ArraySortUtils.copyArray(arr1);
            int smallSum = smallSum(arr1);
            int comparator = comparator(arr2);
            if (smallSum != comparator) {
                System.out.println("smallSum ==> " + smallSum);
                System.out.println("comparator ==> " + comparator);
                succeed = false;
                ArraySortUtils.printArray(arr1);
                ArraySortUtils.printArray(arr2);
                break;
            }
        }
        System.out.println(succeed ? "Nice!" : "Fail!");
    }

}

4.2、数组中的逆序对

4.2.1、描述

在数组中的两个数字,如果前面一个数字大于后面的数字,则这两个数字组成一个逆序对。输入一个数组,求出这个数组中的逆序对的总数。

示例 1:

输入: [7,5,6,4]

输出: 5

4.2.2、实现
class Solution {
    public int reversePairs(int[] nums) {
        if (nums == null || nums.length < 2) {
            return 0;
        }

        return process(nums, 0, nums.length - 1);
    }

     private static int process(int[] arr, int left, int right) {

        if (left == right) {
            return 0;
        }

        int mid = left + ((right - left) >> 1);

        return process(arr, left, mid)
                +
                process(arr, mid + 1, right)
                +
                merge(arr, left, mid, right);
    }

    private static int merge(int[] arr, int left, int mid, int right) {
        int[] help = new int[right - left + 1];
        int index = 0;
        int p1 = left;
        int p2 = mid + 1;
        int sum = 0;
        while (p1 <= mid && p2 <= right) {
            sum += arr[p1] > arr[p2] ? (right - p2 + 1) : 0;
            help[index++] = arr[p1] > arr[p2] ? arr[p1++] : arr[p2++];
        }

        while (p1 <= mid) {
            help[index++] = arr[p1++];
        }

        while (p2 <= right) {
            help[index++] = arr[p2++];
        }

        for (int i = 0; i < help.length; i++) {
            arr[left + i] = help[i];
        }
        return sum;
    }
}

5、工具类

5.1、MergeSortUtils


/**
 * @description: 归并排序工具类
 * @author: erlang
 * @since: 2020-10-12 22:32
 */
public class MergeSortUtils {

    /**
     * 合并
     *
     * @param arr   带处理数组
     * @param left  左边界
     * @param mid   中间分界线
     * @param right 右边界
     */
    public static void merge(int[] arr, int left, int mid, int right) {
        int[] help = new int[right - left + 1];
        int index = 0;
        int p1 = left;
        int p2 = mid + 1;
        while (p1 <= mid && p2 <= right) {
            help[index++] = arr[p1] <= arr[p2] ? arr[p1++] : arr[p2++];
        }

        while (p1 <= mid) {
            help[index++] = arr[p1++];
        }

        while (p2 <= right) {
            help[index++] = arr[p2++];
        }

        for (int i = 0; i < help.length; i++) {
            arr[left + i] = help[i];
        }
    }
}

5.2、ArraySortUtils


import java.util.Arrays;
import java.util.function.Consumer;

/**
 * @description: 数组排序工具
 * @author: erlang
 * @since: 2020-08-29 10:49
 */
public class ArraySortUtils {

    /**
     * 随机生成待测试的数组
     *
     * @param maxSize  数组最大长度
     * @param maxValue 数组中最大的值
     * @return 返回生成的数组
     */
    public static int[] generateRandomArray(int maxSize, int maxValue) {
        int[] arr = new int[(int) ((maxSize + 1) * Math.random())];
        for (int i = 0; i < arr.length; i++) {
            arr[i] = (int) ((maxValue + 1) * Math.random()) - (int) (maxValue * Math.random());
        }
        return arr;
    }

    /**
     * 输出数组的元素
     *
     * @param arr 数组
     */
    public static void printArray(int[] arr) {
        System.out.println("");
        System.out.println("+++++++++++++++++++++++++++++++++++++++++++++++++++START");
        for (int value : arr) {
            System.out.print(value + " ");
        }
        System.out.println("");
        System.out.println("+++++++++++++++++++++++++++++++++++++++++++++++++++END");
    }

    public static void comparator(int[] arr) {
        Arrays.sort(arr);
    }

    /**
     * 重新复制一个新数组
     *
     * @param arr 待复制的数组
     * @return 返回复制的新数组
     */
    public static int[] copyArray(int[] arr) {
        if (arr == null) {
            return null;
        }
        int[] res = new int[arr.length];
        System.arraycopy(arr, 0, res, 0, arr.length);
        return res;
    }

    /**
     * 比较两个数组的元素是否相等
     *
     * @param arr1 待比较的数组 1
     * @param arr2 待比较的数组 2
     * @return 返回校验结果 true/false
     */
    public static boolean isEqual(int[] arr1, int[] arr2) {
        if (arr1 == null && arr2 == null) {
            return true;
        }
        if (arr1 == null || arr2 == null) {
            return false;
        }
        if (arr1.length != arr2.length) {
            return false;
        }
        for (int i = 0; i < arr1.length; i++) {
            if (arr1[i] != arr2[i]) {
                return false;
            }
        }
        return true;
    }

    /**
     * 测试排序方法的排序结果是否正确
     *
     * @param consumer 回调用户自定义的排序方法
     * @param maxSize  数组最大长度
     * @param maxValue 数组中最大的数,即 0 - maxValue
     */
    public static void testSort(Consumer<int[]> consumer, int maxSize, int maxValue) {
        int testTime = 5000;
        boolean succeed = true;
        for (int i = 0; i < testTime; i++) {
            // 生成新数组
            int[] arr1 = ArraySortUtils.generateRandomArray(maxSize, maxValue);
            // 复制新数组
            int[] arr2 = copyArray(arr1);
            // 回调用户自定义的排序方法,对 arri1 排序
            consumer.accept(arr1);
            // 使用系统自带的排序方法,对 arr2 排序
            comparator(arr2);
            // 比较两种处理结果是否一致
            if (!isEqual(arr1, arr2)) {
                succeed = false;
                printArray(arr1);
                printArray(arr2);
                break;
            }
        }
        // 输出测试结果
        System.out.println(succeed ? "Nice!" : "Fail!");
    }
}