归并排序(Merge Sort)

414 阅读2分钟

归并排序(Merge Sort)

冒泡排序及优化

选择排序(SelectionSort)

堆排序(HeapSort)

插入排序及优化

学习自小码哥的《恋上数据结构与算法》,图片来自视频截图。

升序

核心思想

  1. 不断将当前序列平均分割成2个子序列,直到不能再分割(序列中只剩下1个元素)。

  2. 不断将2个子序列合并成一个 有序序列,直到最终只有一个有序序列。

merge两个有序序列为一个有序序列的具体步骤:

真实开发中与上面不同处在于,需要merge的两个序列存在一个数组中,并且是挨在一起的。

所以我们需要备份一半出来,备份左边[begin,mid)

右边提前结束了,需要将左边剩下的覆盖到右边来。

左边提前结束,啥都不用干,直接结束。

代码实现

代码实现主要是递归,切割还是比较简单的:

/**
 * 对[begin,end)范围的数据进行归并排序
 *
 * @param begin 开始
 * @param end   结束
 */
private void sort(int begin, int end) {
    //元素数量小于2  不用排序
    if (end - begin < 2) {
        return;
    }
    //切割 divide
    int middle = (begin + end) >> 1;
    sort(begin, middle);
    sort(middle, end);
    //合并 merge
    merge(begin, middle, end);
}

下面是合并的

/**
 * 合并  将两个有序的数组合并成一个有序的数组
 *
 * @param begin  【begin,middle)
 * @param middle 中间值
 * @param end    [middle,end)
 */
private void merge(int begin, int middle, int end) {
    //左边数组当期所在的点li, 终点le
    int li = 0, le = middle - begin;
    //右边数组(即array上右边)当前所在的点ri, 终点re
    int ri = middle, re = end;
    //已经排序好的坐标
    int ai = begin;

    //备份坐标数组
    for (int i = li; i < le; i++) {
        //注意array取值是begin+i ,sort(begin,end)是递归调用,所以begin不一定是0
        leftArray[i] = array[begin + i];
    }
    //如果左边提前结束,那么后续都不需要做了。即li增长到le
    while (li < le) {
        //左边用li从leftArray上取,右边用ri从array上取
        // ri小心数组越界
        if (ri < re && cmp(array[ri], leftArray[li]) < 0) {
            //右边比较小
            array[ai++] = array[ri++];
        } else {
            // left: 1  6 9 11     right: 1 2 4 5
            // 如果是上面这两种情况合并,使用>标记,
            // 右边的1就会覆盖左边的1,从而造成不稳定,
            // 所以此处应该用  > = 。
            array[ai++] = leftArray[li++];

        }
    }

}

全部代码:

/**
 * 归并排序
 */
@SuppressWarnings("ALL")
public class MergeSort<E extends Comparable<E>> extends Sort<E> {

    private E[] leftArray;

    @Override
    protected void sort() {
        //分配一个最大的空间,后面复用即可,不需要每次merge时,不停创建
        leftArray = (E[]) new Comparable[array.length >> 1];
        sort(0, array.length);

    }

    /**
     * 对[begin,end)范围的数据进行归并排序
     *
     * @param begin 开始
     * @param end   结束
     */
    private void sort(int begin, int end) {
        //元素数量小于2  不用排序
        if (end - begin < 2) {
            return;
        }
        //切割 divide
        int middle = (begin + end) >> 1;
        sort(begin, middle);
        sort(middle, end);
        //合并 merge
        merge(begin, middle, end);
    }

    /**
     * 合并  将两个有序的数组合并成一个有序的数组
     *
     * @param begin  【begin,middle)
     * @param middle 中间值
     * @param end    [middle,end)
     */
    private void merge(int begin, int middle, int end) {
        //左边数组当期所在的点li, 终点le
        int li = 0, le = middle - begin;
        //右边数组(即array上右边)当前所在的点ri, 终点re
        int ri = middle, re = end;
        //已经排序好的坐标
        int ai = begin;

        //备份坐标数组
        for (int i = li; i < le; i++) {
            //注意array取值是begin+i ,sort(begin,end)是递归调用,所以begin不一定是0
            leftArray[i] = array[begin + i];
        }
        //如果左边提前结束,那么后续都不需要做了。即li增长到le
        while (li < le) {
            //左边用li从leftArray上取,右边用ri从array上取
            // ri小心数组越界
            if (ri < re && cmp(array[ri], leftArray[li]) < 0) {
                //右边比较小
                array[ai++] = array[ri++];
            } else {
                // left: 1  6 9 11     right: 1 2 4 5
                // 如果是上面这两种情况合并,使用>标记,
                // 右边的1就会覆盖左边的1,从而造成不稳定,
                // 所以此处应该用  > = 。
                array[ai++] = leftArray[li++];

            }
        }

    }
}