排序算法之归并排序

这里是传送门⇒总结:关于排序算法



平均时间复杂度 最优时间复杂度 最差时间复杂度 空间复杂度 稳定性
二路归并排序 O(nlogn) O(nlogn) O(nlogn) O(n) 稳定


归并排序采用分治策略,先划分,再合并。若排序中每次合并的时候是2个有序子序列合并,称“二路归并排序”。这里讨论的就是“二路归并排序”的递归做法(好像递归可能爆栈,如果可以,建议研究下非递归做法)

  • 算法描述
    • 将待排序列分成2部分,分别使2个子序列有序,之后把2个有序子序列合并成更大的有序序列
    • 而使2个子序列有序的方法是把子序列再分成2部分,再分别使2个子序列有序,再合并
    • 而使2个子序列...
    • 直到子序列只有1个元素
  • 合并的具体实现
    • 设置2个指针分别从左到右扫描2个有序子序列,依次寻找2个子序列较小元素放入temp数组,用temp中合并排序好的子序列替换原数组array中没排序好的原子序列
  • JS实现
// 此处传入的array会被直接改变
function Merge(array, left, mid, right) {
    var i = left;
    var j = mid + 1;
    var k = 0;
    var temp = [];
    while (i <= mid && j <= right) {
        if (array[i] <= array[j]) {
            temp[k++] = array[i++];
        } else {
            temp[k++] = array[j++];
        }
    }
    while (i <= mid) {
        temp[k++] = array[i++];
    }
    while (j <= right) {
        temp[k++] = array[j++];
    }
    while (k > 0) {
        array[--j] = temp[--k];
    }
}

// 此处传入的array会被直接改变
function MergeSort(array, left, right) {
    if (left < right) {
        var mid = Math.floor((left + right) / 2);
        MergeSort(array, left, mid);
        MergeSort(array, mid + 1, right);
        Merge(array, left, mid, right);
    }
}

  • 分析
    • 归并排序过程中,需要不断对当前序列进行对半划分,直到子序列长度为1,也就是说每一次划分后序列长度都为原本的1/2。这意味着需要划分log2n次(由n/2k = 1计算可得次数k)
    • 每一次划分之后都会进行合并操作,每一次划分后的合并操作会将长度为n的待排序列遍历一遍。过程如下:在第1次划分中,出现2个长度为n/2的子序列,需要合并1次,每次遍历n个数据;在第2次划分中,出现4个长度为n/4的子序列,需要合并2次,每次遍历n/2个数据;...当第log2n次划分中,出现n个长度为1的子序列,需要合并n/2次,每次遍历2个数据。通过这个过程可以知道每一层(划分+合并)的时间复杂度为O(n)
    • 每一层的时间复杂度为O(n),结合上面分析的划分次数为log2n,可以得到归并排序的时间复杂度为O(nlogn)
    • 在归并排序的Merge函数中,合并2个子序列时,需要借助额外的存储空间。每次合并过程中都需要申请额外的内存空间,但是合并完成后,临时开辟的内存空间就被释放掉了,在任意时刻,只会有一个Merge函数在执行,也就只会有一个临时的内存空间在使用。临时空间再大也不会超过n个数据的大小,所以空间复杂度是O(n)
    • 由于在合并时,如果有两个相同的元素,是先把左边子序列中的元素放进temp,再放右边子序列的相同元素,所以两个相同元素的相对位置不变,即归并排序是稳定的
posted @ 2021-02-23 22:16  有机物与鱼  阅读(91)  评论(0编辑  收藏  举报