归并排序(Merge sort)是建立在归并操作上的一种有效的排序算法。
该算法是采用 分治法(Divide and Conquer)的一个非常典型的应用。
作为一种典型的分而治之思想的算法应用,归并排序的实现由两种方法:
归并排序是用分治思想,分治模式在每层递归上有三个步骤:
n
个元素分成含 n / 2
个元素的子序列Math.floor(n / 2)
个序列,排序后每个序列包含两个元素Math.floor(n / 4)
个序列,每个序列包含四个元素O(nlogn)
O(n)
O(nlogn)
O(n)
不管元素在什么情况下都要做这些步骤,所以花销的时间是不变的,所以该算法的最优时间复杂度和最差时间复杂度及平均时间复杂度都是一样的为:O( nlogn )
归并的空间复杂度就是那个临时的数组和递归时压入栈的数据占用的空间:n + logn
;所以空间复杂度为: O(n)
。
归并排序算法中,归并最后到底都是相邻元素之间的比较交换,并不会发生相同元素的相对位置发生变化,故是稳定性算法。
// 采用自上而下的递归方法// 分(切割)function mergeSort(arr) {let len = arr.length;// 当分裂到数组元素仅存一个或没有时,表示无法再继续分裂了// 则返回数组进行归并if (len <= 1) {return arr;}// 计算切割点let mid = Math.floor(len / 2),// 递归切割左子数组,然后归并为有序数组left = mergeSort(arr.slice(0, mid)),// 递归切割右子数组,然后归并为有序数组right = mergeSort(arr.slice(mid));return merge(left, right);}// 治(归并)function merge(left, right) {let sorted = [];while (left.length && right.length) {if (left[0] <= right[0]) {// 左边数组首个元素比右边数组首个元素小// 将左边数组首个元素推出原数组,并推入已排序数组sorted.push(left.shift());} else {// 右边数组首个元素比左边数组首个元素小// 将有边数组首个元素推出原数组,并推入已排序数组sorted.push(right.shift());}}// 走到这步,说明某边数组已经空了// 所以如果另一边数组还有一个元素,则直接推出原数组,并推入已排序数组while (left.length) sorted.push(left.shift());while (right.length) sorted.push(right.shift());return sorted;}
如果我们有两个已经排序好的数组,如何将他们归并成一个数组?
我们可以开辟一个临时数组来辅助我们的归并,也就是说他比我们插入排序也好,选择排序也好多使用了存储的空间,也就是说他需要 O(n)
的额外空间来完成这个排序。只不过现在计算机中时间的效率要比空间的效率重要得多。无论是内存也好还是硬盘也好可以存储的数据越来越多,所以设计一个算法,时间复杂度是要优先考虑的。