归并排序

归并排序的时间复杂度是 O(n log n),不论是最坏、最好还是平均情况,因此它是一个非常高效的排序算法。

归并排序的空间复杂度是 O(n),因为它需要额外的空间来存储合并过程中的临时数组。

示意图

实现

  • 先拆分(Divide):不断将数组拆成左右两半,直到每个子数组只剩一个元素。

    1. 递归法(Top-down)

    先将数组拆分到不可分割为止,然后再进行组合、排序,直到资料全部排序完成。

        function mergeSort_TopBottom(arr){
            //如果数组元素只有1个或0个,则认为是有序数组
            if(arr.length <=1) return arr;
            //将数组对半劈开,直到只有1个元素为止
            const middle = Math.floor(arr.length/2);
            const leftArr = mergeSort(arr.slice(0,middle));
            const rightArr = mergeSort(arr.slice(middle));
            return merge(leftArr,rightArr);
        }

    使用递归可以很清楚的描述算法过程,缺点是函数频繁地自我调用。长度为n的数组最终会调用mergeSort_TopBottom()函数 2n-1次,这意味着一个长度超过1500的数组会在Firefox上发生栈溢出错误。因此实际开发中应该使用迭代来实现同样的功能

     

    2. 迭代法(Bottom-up)

    迭代法不是将整个数组分成不同的部分,而是使用不同大小的间隔在数组上循环。最小以2为单位拆分,进行排序,再组合成下一个单位4,以此类推,直到排序完成。 如图所示:
     

     

    // 迭代版归并排序
    function mergeSortIterative(arr) {
        let n = arr.length;
        let step = 1;
    
        // 外层循环控制步长,逐渐增大合并的子数组的大小
        while (step < n) {
            for (let i = 0; i < n; i += step * 2) {
                // 合并相邻的两个子数组
                let left = arr.slice(i, i + step);
                let right = arr.slice(i + step, i + step * 2);
                arr.splice(i, step * 2, ...merge(left, right));  // 合并后放回原数组
            }
            step *= 2;  // 每次步长翻倍
        }
    
        return arr;
    }

     代码关键点说明:

    外层循环负责每次 增大合并的子数组的大小。我们首先从最小的子数组开始(每个子数组只有一个元素),然后逐步增大步长,直到整个数组被合并成一个有序数组。

    • 第一次外层循环时,step = 1,我们会合并相邻的两个单元素子数组。

    • 第二次外层循环时,step = 2,我们会合并相邻的两个子数组(每个子数组有两个元素)。

    • 第三次外层循环时,step = 4,我们会合并相邻的两个子数组(每个子数组有四个元素)。

    • 依此类推,直到最终整个数组合并成一个有序数组。

    内层循环负责 逐个处理子数组。每次循环都需要获取两个子数组,每个子数组的长度是 step,合并后的子数组大小是 step * 2,下一个需要读取的子数组的开始下标就是:i = i+ step*2
    slice的第二个参数是结束索引的位置(千万不要记成是长度)

     

  • 再合并(Merge):从最小子数组开始,逐步将其合并成有序数组。
  1. 依次比较左右两个数组中对应位置的元素,将较小的元素插入到 tempArr 数组。
  2. 最后,剩余的元素(如果有的话)会被直接加入到 result 中。
function merge(left,right){
    const temArr = [];
    // 不停的比较剩下未排序的两个数组
    while(left.length && right.length){
        if(left[0]<=right[0]) temArr.push(left.shift());
        else temArr.push(right.shift());
    }
    // 左边或右边可能还有剩余的元素,剩余的元素肯定比之前的元素大,合并即可
    return temArr.concat(left,right);
}

 

posted @ 2022-04-20 09:51  我是格鲁特  阅读(78)  评论(0)    收藏  举报