Algorithm learning notes -- merge sort and its application

Posted by willcodeforfoo on Wed, 17 Jun 2020 08:55:47 +0200

preface

In the algorithm course of Zuoshen, there are some gains about merging and sorting, which are recorded here for future reference.

principle

In the idea of divide and conquer, the task of sorting array arr[] (scale is N) is divided into three steps: sorting left and right sides (scale bits N/2) and merging (scale is N). The left (or right) half sorting can be divided into two smaller sorting operations and one merging operation. Recursively until the size to be sorted is 1

Recursive implementation

    /**
     * Suppose that the left and right sides have been sorted, merge the left and right sides into an ordered sequence
     * @param arr	Array to sort
     * @param left  arr Left boundary include
     * @param right arr Right boundary include
     */
    public static void mergeSort(int[] arr,int left,int right){
        //boundary condition 
        if(left>=right){
            return;
        }
        int mid =left+((right-left)>>1);
        //Sort left
        sort(arr, left, mid);
        //Sort right
        sort(arr, mid + 1, right);
        //Left right merger
        merge(arr,left,mid,right);
    }

    /**
     * Suppose that the left and right sides have been sorted, merge the left and right sides into an ordered sequence
     * @param arr
     * @param left
     * @param mid
     * @param right
     */
    private static void merge(int[] arr, int left, int mid, int right) {

        int i = left, j = mid + 1, k=0;
        int[] tmp = new int[right - left + 1];
        while (i<=mid&&j<=right){
            tmp[k++] = arr[i] <= arr[j] ? arr[i++] : arr[j++];
        }

        while (i <= mid) tmp[k++] = arr[i++];
        while (j <= right) tmp[k++] = arr[j++];

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

Non recursive implementation

All recursion can be changed to non recursion. Recursion is top-down, so the corresponding non recursive writing is bottom-up logic.

    /**
     * Non recursive version of merge sort
     * @param arr
     */
    public static void mergeSort2(int[] arr){
        if(arr==null||arr.length<2){
            return;
        }
		//i refers to the size of the data to be merged on the left, with the trend change of 2^i, which controls how many times the merging operation needs to be carried out
        for (int i = 1; i < arr.length; i=i<<1) {
            //J refers to the starting pointer of the left half in each merging process, so the range of the left half is [j,j+i-1], and the range of the right half is [J + I, min (j + 2 * i-1), considering the insufficient length, arr.length -1)]
            for (int j = 0; j +i < arr.length; j=j+2*i) {
                merge(arr,j,j+i-1,Math.min(j+2*i-1,arr.length-1));
            }
        }
    }

    /**
     * Suppose that the left and right sides have been sorted, merge the left and right sides into an ordered sequence
     * @param arr
     * @param left
     * @param mid
     * @param right
     */
    private static void merge(int[] arr, int left, int mid, int right) {

        int i = left, j = mid + 1, k=0;
        int[] tmp = new int[right - left + 1];
        while (i<=mid&&j<=right){
            tmp[k++] = arr[i] <= arr[j] ? arr[i++] : arr[j++];
        }

        while (i <= mid) tmp[k++] = arr[i++];
        while (j <= right) tmp[k++] = arr[j++];

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

Expand interview questions

Finding small sum of array

Example:

Let the array arr={1,3,4,2,5}, and the small sum of the array refers to the sum of the smaller numbers on the left side of each number. as
1: 0
3: 1
4: 1,3
2: 1
5: 1,3,4,2
The small sum of array arr is 1 + 1 + 3 + 1 + 1 + 3 + 4 + 2 = 16.

Ideas:

The short answer method, which can be brutally cracked through double traversal, has a time complexity of N2. It's obvious that there is no point in the interview. Is there a better way? The whole topic is to find out which numbers on the left side of each number in the array are smaller than it, and add them up. With the same idea of divide and conquer, the total sum of small sum should be equal to the sum of small sum of left half and right half and the sum of small sum in the process of + merge.
The key point of the optimization is the merge process. The small sum of this step refers to the number on the left side is smaller than it for each number on the right side. Therefore, the internal order of the left or right side does not affect the result of the small sum of the merge, so you can use the above merge sorting to squeeze out the small sum of this step.
In the merging process, the left range is [left,mid], the left pointer is I, the right range is [mid+1,right], and the right pointer is J. When arr[i] < arr [J], because the right side is ordered, then arr[i] must be smaller than arr[j+1], arr[j+2] arr[right], so we don't need to traverse the back again, and directly add (right-j+1)*arr[i] into the small sum. This step is the key point to generate performance optimization

code:
/**
 * Finding the small sum of an array
 *
 * @param arr
 * @param left
 * @param right
 * @return
 */
public static int getArrSmallerSum(int[] arr, int left, int right) {
    if (left >= right) {
        return 0;
    }
    int mid = left + ((right - left) >> 1);
    int leftSum = getArrSmallerSum(arr, left, mid);
    int rightSum = getArrSmallerSum(arr, mid + 1, right);
    int mergeSum = merge2(arr, left, mid, right);
    return leftSum + rightSum + mergeSum;
}

/**
 * Suppose that the left and right sides have been sorted respectively, and the left and right sides are combined into an orderly sequence,
 * And returns the small sum in the merge process
 *
 * @param arr
 * @param left
 * @param mid
 * @param right
 */
private static int merge2(int[] arr, int left, int mid, int right) {
    int sum = 0;
    int i = left, j = mid + 1, k = 0;
    int[] tmp = new int[right - left + 1];
    while (i <= mid && j <= right) {
        if (arr[i] < arr[j]) {
            //The key is this line!!!
            sum = sum + arr[i] * (right - j + 1);
            tmp[k++] = arr[i++];
        } else {
            tmp[k++] = arr[j++];
        }
    }
    while (i <= mid) tmp[k++] = arr[i++];
    while (j <= right) tmp[k++] = arr[j++];
    for (int l = 0; l < tmp.length; l++) {
        arr[left + l] = tmp[l];
    }
    return sum;
}

Count the number of descending pairs of arrays

Example:

In the array arr={3,1,4,2,5,7,4}, the descending pair refers to the pair consisting of the number on the left side greater than the number on the right, such as:
3: {3,1},{3,2}
1: {}
4: {4,2}
5: {5,4}
7: {7,4}
The number of array arr descending pairs is 2 + 0 + 1 + 1 + 1 = 5

Ideas:

The last question is to know which number is smaller on the left of each number in the array. This problem is to know which number on the right side of each number in the array is smaller than it. Whether to change the soup without changing the dressing or to modify it based on the merging order, but this time it is concerned that when arr [i] > arr[j] is in the merging process, there should be (mid-i+1) numbers that can form a descending pair with arr[j]

code:
    /**
     * Count the number of descending pairs in an array
     * @param arr
     * @param left
     * @param right
     * @return
     */
    public static int getArrDesCount(int[] arr, int left, int right) {
        if (left >= right) {
            return 0;
        }
        int mid = left + ((right - left) >> 1);
        int leftSum = getArrDesCount(arr, left, mid);
        int rightSum = getArrDesCount(arr, mid + 1, right);
        int mergeSum = merge3(arr, left, mid, right);
        return leftSum + rightSum + mergeSum;
    }

    /**
     * Suppose that the left and right sides have been sorted respectively, and the left and right sides are combined into an orderly sequence,
     * And returns the number of descending pairs in the merge process
     *
     * @param arr
     * @param left
     * @param mid
     * @param right
     */
    private static int merge3(int[] arr, int left, int mid, int right) {
        int sum = 0;
        int i = left, j = mid + 1, k = 0;
        int[] tmp = new int[right - left + 1];
        while (i <= mid && j <= right) {
            if (arr[i] <= arr[j]) {
                tmp[k++] = arr[i++];
            } else {
                //The key is this line!!!
                sum=sum+(mid-i+1);
                tmp[k++] = arr[j++];
            }
        }
        while (i <= mid) tmp[k++] = arr[i++];
        while (j <= right) tmp[k++] = arr[j++];
        for (int l = 0; l < tmp.length; l++) {
            arr[left + l] = tmp[l];
        }
        return sum;
    }