排序-8-归并排序

467 阅读1分钟

初识归并排序

归并排序采用分治的思想,大致分为两个步骤:

1. 分组

2. 归并

此外,归并分为三个步骤:

示例,以两个长度为 4 的数组为例:

1. 创建一个额外的大数组,用于存储归并的结果,长度是两个小数组之和

创建 p1、p2、p3 指针用于记录当前操作位置

2. 逐一比较两个小数组中的元素,把较小的元素优先放入大数组中,

由于 1 < 2,所以把 1 放入大数组中,p1、p 各向右移动一位

由于 2 < 3,所以把 3 放入大数组中,p2、p各向右移动一位

...

直到左侧小数组没有元素可用

3. 从另外一个还有剩余元素的数组中,把剩余的元素按顺序复制到大数组尾部

这样一来两个有序小的数组就合并成为了一个有序的大数组

代码实现

    function merge(arr:Array<number>, start:number, end:number, middle:number) {
        // 新建合并数组,设置指针
        let tempArray = new Array(end - start + 1);
        let p1 = start;
        let p2 = middle + 1;
        let p = 0;
        while(p1 <= middle && p2 <= end) {
            if(arr[p1] <= arr[p2] ) {
                tempArray[p++] = arr[p1++]
            } else {
                tempArray[p++] = arr[p2++]
            }
        }
        // 如果数组中还有剩余 则依次放入大数组中
        while(p1 <= middle) {
            tempArray[p++] = arr[p1++]
        }
        while(p2 <= end) {
            tempArray[p++] = arr[p2++]
        }
        // 合并数组放入原数组
        for (let index = 0; index < tempArray.length; index++) {
            arr[start + index]  = tempArray[index]        
        }
    }
    
    
    function sort(arr:Array<number>, start:number, end:number) {
        if(start < end) {
            /* 
                这里找到middle为 start 和 end 的 "中位数"
                    即 start = 5; end = 10; 则 middle = 7;
                需注意的是 这里采取的是向下舍入即:
                    5,6,7,8,9,10 共 六个元素,则 middle=7; 5,6,7为一组;8,9,10为一组
                    6,7,8,9,10 共五个元素,则 middle=8; 6,7,8为一组;9,10为一组
    
    
                公式为: Math.floor((end-start)/2) + start
                    
                转换位运算符公式为:Math.floor(A / Math.pow(2, B)) => Math.floor(A / (2 ** B)) => (A >> B)
    
    
                所以:Math.floor((end-start)/2) + start === ((end - start)>>1) + start
            */
            // 折半分为两个小数组 并递归
            let middle = ((end - start)>>1) + start;
            sort(arr, start, middle);
            sort(arr, middle + 1, end);
    
    
            // 合并两个小数组
            merge(arr, start, end, middle)
        }
        return arr
    }
    
    
    function main() {
        const arr = [5, 8, 6, 3, 9, 2, 1, 7];
        console.log(sort(arr, 0, arr.length - 1))
    }
    
    
    main();

小结:

时间复杂度:

归并排序把数组一层一层折半分组,如果数组长度为 n,则折半层数为 logn,每一层运算量为 n,

  所以时间复杂度为 O(nlogn)

空间复杂度:

创建最大额外空间为 n(每次归并所创建的空间都会随着方法结束而被释放,所以只取最大空间即可),递归次数为 logn,即 n + logn,所以空间复杂度为 O(n)

归并排序为稳定排序,示例如下:

摘要总结公众号:程序员小灰 文章