归并排序

归并排序(英语:Merge sort,或mergesort),是创建在归并操作上的一种有效的排序算法,效率为O(NlgN)。
1945年由约翰·冯·诺伊曼首次提出。该算法是采用分治法(Divide and Conquer)的一个非常典型的应用,且各层分治递归可以同时进行。

自顶向下的归并排序

递归实现的归并排序是算法设计中分治思想的典型应用。


/**
 *  <p>归并排序
 *
 *  <p>自顶向下
 *
 *  <p>时间复杂度: O(1/2NlgN)到O(NlgN)
 *  <p>空间复杂度: O(N)
 *
 * @author teaho2015@gmail.com
 */
public class MergeSort1 {

    public void sort(Comparable[] arr) {
        Comparable[] temp = new Comparable[arr.length];

        sort(arr, 0, arr.length - 1, temp);
    }

    public void sort(Comparable[] arr, int lf, int rt, Comparable[] temp) {

        if (lf >= rt) {
            return;
        }
        int mid = (lf + rt) / 2;
        //左边
        sort(arr, lf, mid, temp);
        //右边
        sort(arr, mid + 1, rt, temp);
        merge(arr, lf, mid, rt, temp);
    }


    public void merge(Comparable[] arr, int lf, int mid, int rt, Comparable[] temp) {

        int i = lf, j = mid + 1;
        for (int k = lf; k <= rt; k++) {
            temp[k] = arr[k];
        }

        for (int k = lf; k <= rt; k++) {
            if (i > mid) {
                arr[k] = temp[j++];
            } else if(j > rt) {
                arr[k] = temp[i++];
            } else if (temp[i].compareTo(temp[j]) < 0) {
                arr[k] = temp[i++];
            } else {
                arr[k] = temp[j++];
            }
        }

    }
}

自底向上的归并排序

自底向上的实现是通过迭代实现。


/**
 * <p>归并排序
 *
 * <p>自底向上的方式
 *
 *  <p>时间复杂度: O(1/2NlgN)到O(NlgN)
 *  <p>空间复杂度: O(N)
 *
 * @author teaho2015@gmail.com
 */
public class MergeSort2 {


    public void sort(Comparable[] arr) {

        //进行lgN此两两归并
        int len = arr.length;
        Comparable[] temp = new Comparable[arr.length];
        //sz为划分子数组的长度
        for (int sz = 1; sz < len; sz += sz) {
            for (int i = 0; i < len - sz; i += (sz + sz)) {
                merge(arr, i, i + sz - 1, Math.min(i + sz + sz - 1, len - 1), temp);
            }
        }


    }

    public void merge(Comparable[] arr, int lf, int mid, int rt, Comparable[] temp) {

        int i = lf, j = mid + 1;
        for (int k = lf; k <= rt; k++) {
            temp[k] = arr[k];
        }

        for (int k = lf; k <= rt; k++) {
            if (i > mid) {
                arr[k] = temp[j++];
            } else if(j > rt) {
                arr[k] = temp[i++];
            } else if (temp[i].compareTo(temp[j]) < 0) {
                arr[k] = temp[i++];
            } else {
                arr[k] = temp[j++];
            }
        }

    }
}

排序算法的复杂度

在《算法》[1]一书中在归并排序一节谈到一个研究排序算法复杂度的案例。挺值得一说。

命题:没有任何基于比较的算法能够保证使用少于lg(N!)~NlgN次比较将长度为N的数组排序。

证明: 首先,假设没有重复的主键,因为任何排序算法在该情况下讨论才有意义。 我们使用如下二叉树来表示所有比较,假设元素个数为N=3:

algorithm_sort_tree.jpg

树中的节点:

  • 一般节点:i:j,表示a[i]和a[j]之间的比较操作。
  • 叶子节点:表示元素原顺序的最终排序结果。

我们从来没明确构造这棵树,它只是用来描述算法中比较的一个数学工具。 从这棵比较树我们可以得出结论,

  • 这棵树至少有N!个树叶节点。因为N个主键会有N!种排列方式。
  • 而从二叉树的性质出发来看,一个高h的二叉树最多可能有2^h个叶子结点。(即完全树的情况)

所以得出结论: N! <= 叶子结点数量 <= 2^h

h的值就是最坏情况下的比较次数,因此对不等式的两边取对数即可得到任意算法的比较次数至少是lgN!。 根据斯特宁公式对阶乘函数的近似可得路径(即排序比较次数)为: lgN!~NlgN

References

[1]Robert Sedgewick,Kevin Wayne.算法(第4版).中国:人民邮电出版社,2012 [2]归并排序|维基百科

results matching ""

    No results matching ""