Algorithm | Merge Sort

Several sorting algorithms mentioned earlier, selection sort , bubble sort , insertion sort , Hill sort , these sorts can be classified into one category (algorithm implementation, please move here ).

Because they are compared and exchanged element by element in turn, insertion sort has a certain advantage for ordered elements, and Hill sort is to create a comparative advantage. These sorting algorithms are all in the way of inner and outer loops until the end of the data.

Merge sort uses a recursive method to divide the array into countless small sub-arrays, sort the sub-arrays, and then merge them in turn to achieve the purpose of sorting the entire array.

Merge two ordered arrays to form a larger ordered array. The recursive sorting idea derived from this concept: merge sort. To sort an array, you can recursively divide the array into two halves, straight Merge the arrays when they become ordered in the smallest unit (usually the smallest unit is 1).

The overall process can be understood with the following diagram :


  1. Sort the left half of the array first,
  2. Then sort the elements on the right half,
  3. Finally, merge the sorted arrays on the left and right

Looking at the above picture, we can easily think that we need three arrays, the left half of the ordered array, the right half of the ordered array, and then merge the left and right half of the array into a larger ordered array, but the above The diagram just shows an abstract process. In practice, we need to merge many times, so more arrays are needed, which obviously creates a large number of arrays. To avoid this, we can allocate the same size as the original array at one time. Another auxiliary array. When merging, first copy the original array to the auxiliary array, and then use the auxiliary array to merge. The method of merging is 

merge(int[] a, int low, int mid, int height)

Through the merge method, the elements between a[low]...a[mid] and a[mid+1]...a[height] are effectively merged into an ordered array and stored in a[low] ...a[height], in the process of merging, the original array will be copied to the auxiliary array, and then the auxiliary array will be used to sort, and the result will be merged into the original array:

private static void merge(int[] a, int low, int mid, int height) {
        int i = low;
        int j = mid + 1;


        //copy the a to auxiliary
        for (int k = low; k <= height; k++) {
            auxiliary[k] = a[k];
        }

        //merge left and right
        for (int k = low; k <= height; k++) {
            if (i > mid) {
                a[k] = auxiliary[j++];
            } else if (j > height) {
                a[k] = auxiliary[i++];
            } else if (a[i] >= a[j]) {
                a[k] = auxiliary[j++];
            } else {
                a[k] = auxiliary[i++];
            }
        }
    }

A brief description of this method is:

First copy the original array elements from the low...height range to auxiliary (auxiliary is allocated at one time), and then use the following four rules to reorder the left and right array elements:

If the left half is exhausted (i>mid), the right half is used.

If the right half of the elements are exhausted (j>height), the left half of the elements are used.

If the current element in the left half is smaller than the current element in the right half, the current element in the left half is used.

If the current element of the right half is smaller than the current element of the sitting half, the current element of the right half is used.

And every time an element is selected, whether it is the left (i++) or the right (j++), the index will increase, i, j represent the index of the elements in the two arrays, you can imagine carefully, during the sorting process, their The position increases in turn, and the simulated scene of moving backward to find the element

For a better understanding, take a look at the picture below:


According to the process I described above, it can be seen that in the sorting process, the size of elements is constantly compared between aus[0]...a[4] and a[5]...a[9], and always a The smaller elements between [i] (left half of the array) and a[j] (right half of the array) are rearranged at the position of a[k] until the end of the array

The idea of ​​merge sort is that if you can sort two subarrays, you can recursively sort and merge the two subarrays into a larger sorted array.

Now give an implementation of the recursive sorting algorithm:

    static int[] nums = new int[]{
            1, 15, 3, 78,
            34, 23, 46, 2,
            8, 34, 57, 50,
            200, 123, 66, 43,
            33, 100, 356, 21};

    static int[] auxiliary = new int[nums.length];

    public static void main(String[] args) {


        System.out.println("before Merge sort " + Arrays.toString(nums));
//        topToBottomSort(nums, 0, nums.length - 1);
        bottomToTopSort(nums);

        System.out.println("after Merge sort " + Arrays.toString(nums));
    }

    public static void topToBottomSort(int[] nums, int low, int height) {
        if (low >= height) return;

        int mid = low + (height - low) / 2;

        topToBottomSort(nums, low, mid);// sort left
        topToBottomSort(nums, mid + 1, height);//sort right

        merge(nums, low, mid, height); // merge left and right
    }

Look at the method name topToBottomSort, which is called top-down sorting. From the perspective of method implementation, the array is always sorted into smaller arrays first, and a[low]....a[height] is always divided into a[ low]....a[mid] and a[mid+1].....a[height], sort them recursively, and finally merge them into the final result by merge

The process of merging is:



The above two pictures are the trajectory diagrams of merge sort, it can be seen that,

To sort a[0]...a[15], first sort a[0]...a[7],

To sort a[0]....a[7], first sort a[0]...a[3],

To sort a[0]...a[3], first sort a[0]...a[1],

To sort a[0]...a[1], sort a[0] and a[1] first. At this time, a[0], a[1] can no longer be separated, as a single element From the point of view, they are already sorted arrays of length 1, which can be merged directly

After a[0] and a[1] are merged, then a[2] and a[3] are merged, and then a[4]...a[7] is sorted, and the same is the same sorting process. When they are recursively divided into an array with only one element, the merge sort can be started, which is the same as the above process, and so on, and finally merged into a[0]...a[15]

This recursive sorting method divides the whole into zeros, recursively divides the sorting behavior into smaller arrays, and finally merges the sorting results of many extremely small arrays into a larger array, thereby realizing the sorting of the entire array. This is " The classic application of "divide and conquer" in sorting algorithms. We divide the problem to be solved into the solutions of many small problems, and then combine all the solutions of the small problems to finally constitute the solution of this problem.

There is another sorting idea, which is the complete reverse of the above process, called bottom-up sorting.

It is to first divide this array into many extremely small arrays (length 1), and then merge two by two, then four or four, and eight or eight...the end of the straight array.

The implementation of sorting is:

    public static void bottomToTopSort(int[] nums) {
        final int N = nums.length;
        for (int size = 1; size < N; size = size + size) {//the size is length of sub array
            for (int low = 0; low < N - size; low += size + size) {
                merge(nums, low, low + size - 1, Math.min(low + size + size - 1, N - 1));
            }
        }
    }

    private static void merge(int[] a, int low, int mid, int height) {
        int i = low;
        int j = mid + 1;


        //copy the a to auxiliary
        for (int k = low; k <= height; k++) {
            auxiliary[k] = a[k];
        }

        //merge left and right
        for (int k = low; k <= height; k++) {
            if (i > mid) {
                a[k] = auxiliary[j++];
            } else if (j > height) {
                a[k] = auxiliary[i++];
            } else if (a[i] >= a[j]) {
                a[k] = auxiliary[j++];
            } else {
                a[k] = auxiliary[i++];
            }
        }
    }

Use the following picture to deepen the understanding of the sorting process:


First divide into sub-arrays of length 1, merge them in pairs, and then double the size, that is, merge four or four, then double again, and merge eight or eight until the end of the array!

We have done the actual  algorithm|algorithm performance measurement (select|insert|hill)  , among which the Hill sorting is the best performance, and the time for sorting 100w random data is 193ms.

Now when the size of the array is expanded by 16 times (tens of millions), when there are 16777216 (1<<24), the sorting time is: 4663ms,

The sorting time of the merge sort algorithm on this data scale is: 1425ms, which is more than 2 times faster.

The next article will introduce, quick sort, similar to the sorting idea of ​​merge sort, and one of the classic applications of "divide and conquer idea".

The sorting performance of quicksort is sometimes faster than merge sort, which is very worth learning!

Algorithm implementation please go here

Next: Quick Sort




Guess you like

Origin http://43.154.161.224:23101/article/api/json?id=325811767&siteId=291194637