[排序] 归并排序 (分治 + 递归)

428 阅读3分钟

目录

  1. 归并排序

一 归并排序

归并排序时间复杂度线性对数阶 O(nlogN)

javaScript中的Array.prototype.sort方法没有规定使用哪种排序算法,允许浏览器自定义,FireFox使用的是归并排序法,而Chrome使用的是快速排序法

归并排序的核心思想分治分治通过递归将问题分解成相同或者类型相关的两个或者多个子问题,直到问题简单到足以解决,然后将子问题的解决方案结合起来,解决原始方案的一种思想。

归并排序通过将复杂的数组分解成足够小的数组(只包含一个元素),然后通过合并两个有序数组(单元素数组可认为是有序数组)来达到综合子问题解决方案的目的。所以归并排序的核心在于如何整合两个有序数组拆分数组只是一个辅助过程

示例:

// 假设有以下数组,对其进行归并排序使其按从小到大的顺序排列:
var arr = [8,7,6,5];
// 对其进行分解,得到两个数组:
[8,7][6,5]
// 然后继续进行分解,分别再得到两个数组,直到数组只包含一个元素:
[8][7][6][5]
// 开始合并数组,得到以下两个数组:
[7,8][5,6]
// 继续合并,得到
[5,6,7,8]
// 排序完成

JavaScript实现(从小到大排序):

function mergeSort(arr) {
    //let count = 0;
    console.log(main(arr));
    //return count;
    function main(arr) {
        // 记得添加判断,防止无穷递归导致callstack溢出,此外也是将数组进行分解的终止条件。
        if(arr.length === 1) return arr;
        // 从中间开始分解,并构造左边数组和右边数组。
        // let mid1 = Math.floor(arr.length/2);
        // let mid2 = arr.length >> 1;
        let mid = arr.length >>> 1;
        //console.log('mid2:',mid2,'mid1:',mid1,'mid:',mid);
        let left = arr.slice(0, mid);
        let right = arr.slice(mid);
        console.log('left:',left,'right:',right);
        // 开始递归调用。
        return merge(arguments.callee(left), arguments.callee(right));
    }
    // 数组的合并函数,left是左边的有序数组,right是右边的有序数组。
    function merge(left, right) {
        // il是左边数组的一个指针,rl是右边数组的一个指针。
        let il = 0,
            rl = 0,
            result = [];
        // 同时遍历左右两个数组,直到有一个指针超出范围。
        while(il < left.length && rl < right.length) {
            //count++;
            // 左边数组的当前项如果小于右边数组的当前项,那么将左边数组的当前项推入result,反之亦然,同时将推入过的指针右移。
            if(left[il] < right[rl]) {
                result.push(left[il++]);
            }
            else {
                result.push(right[rl++]);
            }
        }
        // 记得要将未读完的数组的多余部分读到result。
        return result.concat(left.slice(il)).concat(right.slice(rl));
    }
}
var arr = [3,5,1,6,2];
mergeSort(arr);
// [1, 2, 3, 5, 6]

image.png

注意是因为数组被分解成为了只有一个元素的许多子数组,所以merge函数从单个元素的数组开始合并,当合并的数组的元素个数超过1时,即为有序数组,仍然还可以继续使用merge函数进行合并。

归并排序的性能确实达到了应用级别,但是还是有些不足,因为这里的merge函数新建了一个result数组来盛放合并后的数组,导致空间复杂度增加这里还可以进行优化,使得数组进行原地排序

参考

总结

  • 归并排序的核心思想分治,就是通过递归将问题分解成相同或者类型相关的两个或者多个子问题,直到问题简单到足以解决.
  • 归并排序时间复杂度线性对数阶``O(nlogN)
  • N>>>1就代表N的二进制右移一位二进制右移一位就能得到中间值 10>>>1 是5 10>>1 也是5(此中间值在遇到奇数时,除以2向下取整) 相当于 Math.floor(11/2)