归并排序

这种算法采用了分而治之的思想:

分割:把未排序的列表划分为 n 个子列表,每个包含一个元素(只有一个元素的列表被认为是有序的)。

合并:不停地合并子列表生成新的已排序列表,直到最后合并为一个已排序的列表。

 

两个数组合并时,只需要比较二个数组的第一个数,谁小就先取谁,取了后就在对应数列中删除这个数。然后再进行比较,如果有数列为空,那直接将另一个数列的数据依次取出即可。

 

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);
}

 

具体的实现方式有两种

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 mergeSort_BottomUp(datas) {
        const len = datas.length;
        /*
        按2的倍数对整个数组标段
        */
        for (let size = 1; size < len; size = 2 * size) {
            console.log(`------------size:${size}------------------------------`)
            //循环每个标段
            for (let start = 0; start < len; start = start + 2 * size) {
                console.log(`----start:${start}-------`)
                let left = datas.slice(start,start+size),right = datas.slice(start+size,start+size+size);
                const sortedDatas = merge(left,right);
                console.log(`分段排序后的子数组:${sortedDatas}`);
                //用分段中的值替换原数组中对应下标的值
                for (let k=0;k<sortedDatas.length;k++){
                    console.log(`原数组下标:${k+start},值:${datas[k+start]},替换为:${sortedDatas[k]}`)
                    datas[k+start] = sortedDatas[k];
                }
            }
        }
    }

 

 

归并排序的时间复杂度分析

在处理分过程中递归调用两个分的操作,所花费的时间为2*T(n/2),合的操作时间复杂度则为O(n),因此可以得到以下公式:

总的执行时间 = 2 × 输入长度为n/2sort函数的执行时间 + merge函数的执行时间O(n)

当只有一个元素时,T(1) = O(1)

如果对T(n) = 2 * T(n/2) + O(n)进行左右 / n的操作,得到 T(n) / n = (n / 2) * T(n/2) + O(1)

现在令 S(n) = T(n)/n,则S(1) = O(1),然后利用表达式带入得到S(n) = S(n/2) + O(1)

所以可以得到:S(n) = S(n/2) + O(1) = S(n/4) + O(2) = S(n/8) + O(3) = S(n/2^k) + O(k) = S(1) + O(logn) = O(logn)

综上可得,T(n) = n * log(n) = nlogn

关于归并排序的稳定性,在进行合并过程,在1个或2个元素时,1个元素不会交换,2个元素如果大小相等也不会交换,由此可见归并排序是稳定的排序算法

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