归并排序的代码实现和注释(Java)

159 阅读2分钟

归并排序


package practice_03_sort;

import java.util.Arrays;

/**
 * 归并排序
 * 先对左边进行排序,再对右边进行排序,然后将左右两边进行合并成一个统一有序的数组
 * 复杂度 O(N*logN)
 */
public class Code01_MergeSort {

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


    /**
     * 将数组从[L ......R]上进行排序
     *
     * @param arr 待排序数组
     * @param L   起始位置
     * @param R   终点位置
     */
    private static void process(int[] arr, int L, int R) {
        if (L == R) {
            return;
        }
        //获取中点位置
        //等价于L+(R-L)/2。 可以预防数据过大时中间递归出现L+R出现溢出的情况
        int mid = L + ((R - L) >> 1);

        //将一直二分到单个数据。可以打印二分情况
        System.out.println("L= " + L + " ,mind= " + mid + " ,R= " + R);

        //对左边进行排序
        process(arr, L, mid);
        //对右边进行排序
        process(arr, mid + 1, R);
        //对排好序的左右进行merge操作
        merge(arr, L, mid, R);

    }

    /**
     * 将两段有序的数据片段合并成一个更大的完全有序的数据列表
     * 示例:左右为 1,5 | 4,9
     * 合并为:1,4,5,9
     * 两个数据段从左至右逐个进行数据比较,完成排序操作
     * <p>
     * 需要注意 L和R在递归后只会是其中的一段数据,而不再是整个数据的起始和结束位置
     *
     * @param arr 待排序数组
     * @param L   起始位置
     * @param M   中间位置
     * @param 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;
        //进行数据merge
        while (p1 <= M && p2 <= R) {
            //i++ 会在执行完语句之后再进行自加操作,如果没有执行就会进行++操作
            //两个片段进行比较:谁小取谁的值,并将游标下移一位(未取数的片段不会执行++操作)
            help[i++] = (arr[p1] <= arr[p2]) ? arr[p1++] : arr[p2++];
        }
        //此时左边或者右边可能还存在有残余数据,此时的数据为有序数据 直接逐一添加的末尾即可
        //以下两个while只会执行其中一个
        while (p1 <= M) {
            help[i++] = arr[p1++];
        }
        while (p2 <= R) {
            help[i++] = arr[p2++];
        }
        //将排序好的数据放回到特定的其实结束段中
        //System.arraycopy(help, 0, arr, L + 0, help.length);
        //IntStream.range(0, help.length).forEach(j -> arr[L + j] = help[j]);
        for (int j = 0; j < help.length; j++) {
            arr[L + j] = help[j];
        }

    }


    public static void main(String[] args) {
        //简单测试
        int[] arr = {0, 89, 3, 5, 63, 9, 7};
        System.out.println(Arrays.toString(arr));
        mergeSort(arr);
        System.out.println(Arrays.toString(arr));
        System.out.println("-------------------");

        //对数器测试
        
    }


}

简单测试结果:

-------------------
[0, 89, 3, 5, 63, 9, 7]
L= 0 ,mind= 3 ,R= 6
L= 0 ,mind= 1 ,R= 3
L= 0 ,mind= 0 ,R= 1//数据会逐一切分到单个数据
L= 2 ,mind= 2 ,R= 3
L= 4 ,mind= 5 ,R= 6
L= 4 ,mind= 4 ,R= 5
[0, 3, 5, 7, 9, 63, 89]
-------------------

结论:归并排序利用了左右段合并时数据的有序性。提前实现了数据的单调性,从而使得在merge时能不进行数据回退,从而不浪费数据之间的比较。实现了O(N*logN)速度