常用排序算法总结:归并排序

119 阅读3分钟

分析归并排序之前,我们先来了解一下分治算法。

分治算法的基本思想是将一个规模为N的问题分解为K个规模较小的子问题,这些子问题相互独立且与原问题性质相同。求出子问题的解,就可得到原问题的解。

分治算法的一般步骤:

  1. 分解,将要解决的问题划分成若干规模较小的同类问题;
  2. 求解,当子问题划分得足够小时,用较简单的方法解决;
  3. 合并,按原问题的要求,将子问题的解逐层合并构成原问题的解。

归并排序先将一个无序的N长数组切成N个有序子序列(只有一个数据的序列认为是有序序列), 然后两两合并,再将合并后的N/2(或者N/2 + 1)个子序列继续进行两两合并,以此类推得到一个完整的有序数组。过程如下图所示: image.png 归并排序是分治算法的典型应用。 归并排序算法稳定,数组需要O(n)的额外空间,链表需要O(log(n))的额外空间,时间复杂度为O(nlog(n)),算法不是自适应的,不需要对数据的随机读取。

/**
     * 归并排序,主要是做了拆分之后再排序的操作。
     *
     * @param arrays 待排序的数组
     * @param start  指向数组第一个元素
     * @param end    指向数组最后一个元素
     */
    public static void mergeSort(int[] arrays, int start, int end) {
        // 如果只有一个元素,那就不用排序了
        if (start == end) {
            System.out.println("数组拆分已经结束。");
        } else {
            // 取中间的数,进行拆分
            int middle;
            // 左少右多的方式
            if ((start + end) % 2 == 0) {
                middle = (start + end) / 2 - 1;
            } else {
                middle = (start + end) / 2;
            }
            // 左多右少的方式
//            middle = (start + right) / 2;
            // 左边的数不断进行拆分
            mergeSort(arrays, start, middle);
            // 右边的数不断进行拆分
            mergeSort(arrays, middle + 1, end);
            // 合并
            merge(arrays, start, middle + 1, end);
        }
    }


    /**
     * 合并数组
     *
     * @param arrays 排好序的素组
     * @param start      指向数组第一个元素
     * @param middle      指向数组分隔的元素
     * @param end      指向数组最后的元素
     */
    public static void merge(int[] arrays, int start, int middle, int end) {
        // 左边的数组的大小
        int[] leftArray = new int[middle - start];
        // 右边的数组大小
        int[] rightArray = new int[end - middle + 1];
        // 往这两个数组填充数据
        for (int i = start; i < middle; i++) {
            leftArray[i - start] = arrays[i];
        }
        for (int i = middle; i <= end; i++) {
            rightArray[i - middle] = arrays[i];
        }
        System.out.println("leftArray + rightArray两个数组的值:" + leftArray + rightArray);
        int i = 0, j = 0;
        // arrays数组的第一个元素
        int k = start;
        //比较这两个数组的值,哪个小,就往数组上放
        while (i < leftArray.length && j < rightArray.length) {
            // 谁比较小,谁将元素放入大数组中,移动指针,继续比较下一个
            // 等于的情况是保证“稳定”
            if (leftArray[i] <= rightArray[j]) {
                arrays[k] = leftArray[i];
                i++;
                k++;
            } else {
                arrays[k] = rightArray[j];
                j++;
                k++;
            }
        }
        // 如果左边的数组还没比较完,右边的数都已经完了,那么将左边的数抄到大数组中(剩下的都是大数字)
        while (i < leftArray.length) {
            arrays[k] = leftArray[i];
            i++;
            k++;
        }
        // 如果右边的数组还没比较完,左边的数都已经完了,那么将右边的数抄到大数组中(剩下的都是大数字)
        while (j < rightArray.length) {
            arrays[k] = rightArray[j];
            k++;
            j++;
        }
    }