博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
归并排序
阅读量:4502 次
发布时间:2019-06-08

本文共 3021 字,大约阅读时间需要 10 分钟。

分治法

归并排序是完全遵循分治策略的排序算法。什么是分治法?

分治法,即将原问题分解为几个规模较小的子问题,递归的求解这些子问题,之后再合并这些子问题的解,最终得到原问题的解。

归并排序

归并排序遵照分治法的思想,可分为三个步骤:

  • 分解,将大小为\(n\)的数列分为两个大小为\(\frac{n}{2}\)的子数列
  • 使用归并排序递归排序子数列
  • 合并已排序的子数列,完成原数列的排序

同时,我们知道递归不可能无线递归下去,当分解到基本情形的时候,我们可以直接获得该问题的答案,这也是递归的出口。如果一个问题永远无法递归到基本情形,则分治法不适用。

归并排序(merge sort)的基本情形为:子数列的大小为1时,此时数列天生已排序好,开始向上回溯。

归并排序

上图可见,排序数列\((38,27,43,3,9,82,10)\),我们不断的分拆数列,直至每个子数列大小为1。然后,再将子数列两两归并,逐步回溯。最终,完成排序。

关键伪代码

以上对归并的描述可以总结以下步骤:

merge-sort(A):        if(A.length >= 1)            array A divide into array L and array R            merge-sort(L);            merge-sort(R);            merge(L,R);

可以看到,核心思想就是将数组Adivide成两个子数组,在对子数组进行归并。然后merge L和R两个子数组。

merge的核心伪代码如下:

for k in (1...A.length):    if L[i] <= R[j]        A[k] = L[i];        ++i;    else        L[i] >= R[j]        A[k] = R[j];        ++j;

merge方法也是归并排序的核心部分,但是他的思想却很简单。

由于子数组LR都是有序的,因此只需要逐个对比两个数组的元素,其中小的元素放进数组A中,直到数组A被填满为止。

代码实现(Java)

/** * @author: luzj * @date: * @description: 归并排序 * 大致思想: * 0 将数组A拆成B和C * 1 对B和C单独排序 * 2 合并B和C成一个有序数组 * 3 B和C的排序方式同A,此为递归的基础 */public class MergeSort {    /**     * 排序     * @param A     * @param p     * @param r     */    void sort(Integer[] A, int p, int r) {        if (p < r) {//开始回归的基本条件,确保最小数组大小为1结束递归            int q = Math.floorDiv(p + r, 2);            sort(A, p, q);            sort(A, q + 1, r);            merge(A, p, q, r);        }    }    /**     * 合并两个子数组     * 其实是将左右两个已经排序的子数组,进行原址排序     * @param A     * @param p 左边子数组的边界     * @param q 左右子数组的分界点     * @param r 右边子数组的边界     * 该方法的增长率为:O(n)     */    void merge(Integer[] A, int p, int q, int r) {        int n1 = q - p + 1;//左边子数组的元素个数        int n2 = r - q; //右边子数组的元素个数        //将A数组拆成两个数组来装        Integer[] L = new Integer[n1 + 1];        Integer[] R = new Integer[n2 + 1];        for (int i = 0; i < n1; i++) {            L[i] = A[p + i];        }        for (int j = 0; j < n2; j++) {            R[j] = A[q + j + 1];        }        //哨兵,当某个子数组已经遍历完后还接着遍历时,哨兵将很好的阻止越界并且将遍历转向另一个子数组        L[n1] = Integer.MAX_VALUE;        R[n2] = Integer.MAX_VALUE;        int i = 0, j = 0;        //重排,每次都进行比较,将较小的装进A数组(如果是降序排列,则塞较大的)        for (int k = p; k <= r; k++) {            if (L[i] <= R[j]) {                A[k] = L[i];                i++;            } else {                A[k] = R[j];                j++;            }        }    }    public static void main(String[] args) {        Integer[] A = {5,2,4,7,1,3,2,6};        new MergeSort().sort(A,0,A.length-1);        for (int i = 0; i < A.length; i++) {            System.out.print(A[i]+" ");        }    }}

时间复杂度

归并排序的时间复杂度为:\(nlg(n)\)

我们可以大致算一笔账:

每一次数组将会分裂成两份,即从中间一分为二,最终形成一个二叉树。那么这个二叉树多少层呢,答案是\(log_2{n}+1\)

同时注意到,每一次递归的时间复杂度都来自merge(A) 方法,而merge方法复杂度为\(c*{n_i}\).每一层都有\(c({n_1+n_2+...+n_i})\),即\(c*(n)\)的复杂度。因此,可以看到每一层不管有多少个节点,他的复杂度都是\(c*{n}\).

最终可得,merger-sort的复杂度为:\(O(nlg({n}))\)

这里还有一个结论,就是所有基于比较的排序方法,他的性能上限都是\(O(n*lg{(n)})\),因此快排的平均时间复杂度也不超过这个数。在《算法导论》中,有一个基于决策树的精彩论述,有兴趣的同学可以研读一下。

小结

归并排序使用分治法,时间复杂度为\(nlg(n)\)

参考

  • 《》

转载于:https://www.cnblogs.com/Franken-Fran/p/merger-sort.html

你可能感兴趣的文章
第七章 路由 68 路由-前端路由和后端路由的概念
查看>>
dpkg包管理
查看>>
前端JS利用canvas的drawImage()对图片进行压缩
查看>>
一键切换皮肤的解决思想及iframe嵌套时寻找下级iframe的方法
查看>>
van-dialog 组件调用 报错
查看>>
VC++中的__super::
查看>>
DS1-14
查看>>
c# Mongodb两个字段不相等 MongoDB原生查询
查看>>
排序算法-冒泡排序
查看>>
finally 的作用是什么?
查看>>
嵌入式Linux的调试技术
查看>>
CSS3
查看>>
用友U9 基础使用文件所在目录
查看>>
iOS CALayer 学习(1)
查看>>
jquery 分页控件(一)
查看>>
StackAndQueue(栈与队列)
查看>>
URLOS安装、升级、卸载
查看>>
最新dedecms网页游戏开服表发号网站源码模板
查看>>
在win7下配置sql2005允许远程访问
查看>>
aspose.cell 设置excel里面的文字是超链接
查看>>