算法设计与分析——归并排序

前言

  • 归并排序是典型的分治法案例。

分治法是建基于多项分支递归的一种很重要的算法范式。字面上的解释是“分而治之”,就是把一个复杂的问题分成两个或更多的相同或相似的子问题,直到最后子问题可以简单的直接求解,原问题的解即子问题的解的合并。
——维基百科

  • 我的理解:对于一些问题,直接对其进行求解的消耗较大或极为复杂而难以直接求解,而分治法将一个大问题分解问一组小问题,对每个小问题进行分别求解,由小问题的解,最后组成大问题的解。

  • 归并排序分为两种,递归实现和迭代实现,本文论述递归思想(迭代法后续可能增加)。

算法分析

算法思想

归并排序在于将需要排序的数组一分为二,二分为四,四分为八…直到数组内部只剩余一个数据,便开始归并重组。归并排序有分也有合,重点在于其合的部分,即归并,将两个已经排序好的数组进行合并。
图解思想:
归并排序图解
数组操作详解:
考虑到数组并非一定为偶数个,将数组划分为两个,第一组为n/2个,第二组为n-n/2个,以此类推。

数组操作详解:
比较两个待合并数组的第一个元素,将较小的元素先放入新数组中,然后开始下一轮比较,重复操作,直至两个待合并数组有一个数组已经全部比较完毕(即没有数据了),将另外一组还有数据的数组直接添加到新数组尾部进行合并(原理:待合并数组来自于上一次合并后的结果,内部已经有序,所以在当前合并时,剩余的数据即有序,又全部大于新数组的数据,便可直接添加)。

代码分析

具体代码分析

递归函数分析:

// 递归主函数
function Mergesort(arrayA) {
    // 0.如果当前数组的数据只剩一个了
    if (arrayA.length > 1) {
    	// 1.新建数组 接收分开的数组结果
    	var arrayB = new Array();
    	var arrayC = new Array();
        var length = arrayA.length;
        // Math.floor js向下取整函数 slice js截取数组函数
        // 2.数组复制
        arrayB = arrayA.slice(0, Math.floor(length / 2));
        arrayC = arrayA.slice(Math.floor(length / 2), length);
        // 递归进
        // 3.进入递归 进行划分
        arrayB = Mergesort(arrayB);
        arrayC = Mergesort(arrayC);
        // 合并数组 
        // 4.合并数组
        arrayA = Merge(arrayB, arrayC);
    }
    // 与C语言不同的是 JS需要一个返回值
    // 5.返回数组
    return arrayA;
}
  • 0.只有在数组内数据个数大于1时,才进行划分操作,否则,直接返回当前数组。
  • 1.新建两个准备接收的数组B、C,用于接收本次传入数组所划分出来的两个数据。
  • 2.将传入数组A的前length/2复制给数组B,length-length/2复制给C。
  • 3.将两个已经划分好的数组传入Mergesort函数下一次划分。
  • 4.上一次划分已完毕,并且已进行合并后传回两个排序好的数组,将这个两个数组传入合并函数中,并传回一个合并好的数组A。(需要清楚,当前的arrayB合arrayC是什么?是已经归并完成排序好的数组!而不是第2步划分后的数组。)
  • 5.返回当前合并好的数组。

合并函数分析:

// 合并函数
function Merge(arrayB, arrayC) {
	// 1.准备一个空的合并数组
    var arrayA = new Array();
    var i = 0,
        j = 0,
        k = 0,
        p = arrayB.length,
        q = arrayC.length;
    // 2.
    while (i < p && j < q) {
    	// 3.
        if (arrayB[i] < arrayC[j]) {
            arrayA[k++] = arrayB[i++];
        } else {
            arrayA[k++] = arrayC[j++];
        }
    }
    // 4.判断哪个函数已经比较完
    if (i == p) {
        for (var t = j; t < q; t++) {
            arrayA[k++] = arrayC[t];
        }
    } else {
        for (var t = i; t < q; t++) {
            arrayA[k++] = arrayB[t];
        }
    }
    // 需要返回值
    // 5.
    return arrayA;
}
  • 1.新建一个空数组,用于接收两个数组的合并结果。
  • 2.在两个传入数组没有比较完成的情况下,一直比较下去,直到i=p或j=q完成比较。
  • 3.将两个数组的第一个数据进行比较,较大的那一个加入新数组中,并且自身加1。
  • 4.判断已经全部比较完的数组,将未全部比较的数组直接加入到新数组中。
  • 5.返回合并好的数组

数据案例

测试数据:

// 测试数据
var arr = [3, 9, 1, 4, 6, 5, 2, 10, 9]
arr = Mergesort(arr);
console.log(arr);
  • [3, 9, 1, 4, 6, 5, 2, 10, 9]
  • [ 3, 9, 1, 4 ] [ 6, 5, 2, 10, 9 ]
  • [ 3, 9 ] [ 1, 4 ] [ 6, 5 ] [ 2, 10, 9 ]
  • [ 3 ] [ 9 ] [ 1 ] [ 4 ] [ 6 ] [ 5 ] [ 2 ] [ 10, 9 ]
  • [ 3, 9 ] [ 1, 4 ] [ 5, 6 ] [ 2 ] [ 10 ] [ 9 ]
  • [ 1, 3, 4, 9 ] [ 5, 6 ] [ 2 ] [ 9, 10 ]
  • [ 1, 3, 4, 9 ] [ 5, 6 ] [ 2, 9, 10 ]
  • [ 1, 3, 4, 9 ] [ 2, 5, 6, 9, 10 ]
  • [ 1, 2, 3, 4, 5, 6, 9, 9, 10 ]

算法效率分析

不存在最好或者最坏的情况,是一种稳定算法。
T(n)=2T(n/2)+n/2;
时间复杂度有大O表示法:O(nlogn)。

后记

归并排序虽然是一种稳定的排序算法,但当前子问题个数过多时,仍然会造成算法耗时较高,并且其空间复杂度为O(n),会消耗大量内存,不建议使用。如果对内存要求不严,这的确是一种非常好的算法。

  • 2
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值