含义
归并排序采用,该算法采用经典的分治(divide-and-conquer)策略(分治法将问题分(divide)成一些小的问题然后递归求解,而治(conquer)的阶段则将分的阶段得到的各答案"修补"在一起,即分而治之)。
首选是『分』,将一个数组反复分为两个小数组,直到每个数组都只有一个元素(所以这里可以采用递归);
其实是『治』,从最小数组开始,两两数组按大小顺序合并(左数组元素 left[0]与右数组元素right[0]依次比较,哪个比较小就将哪个数组的值放入新数组,然后再继续比较left[0]和right[1],或者是left[1]和right[0]。可以看出数组left,right都只需遍历一遍,所以对两个有序数组的排序的时间复杂度为O(n)。当有一个为空数组时,剩下一个肯定都是比新数组元素大的元素,直接放到新数组就好)
下面是图解:
时间复杂度分析:分的过程需要三步:log8 = 3,而每一步都需要遍历一次8个元素,所以8个元素共需要运行 8log8 次指令,那么对于 n 个元素,时间复杂度为 O(nlogn)。
合并相邻有序子序列(治)
// 分:
function mergeSort(arr) { 定义拆分函数
let len = arr.length;
if (len < 2) {
return arr;
}
let mid = Math.floor(len/2) // 数组长度/2
let left = arr.slice(0, mid) // slice 方法返回一个新数组,slice(start, end),
// 而 splice(index-数组位置, num-添加或删除的元素个数, item1, ...itemX-元素)会改变原来的数组
let right = arr.slice(right, len)
let mergeSortLeft = mergeSort(left) // 递归调用当前方法继续对数组进行拆分
let mergeSortRight = mergeSort(right)
}
// 治:
const merge = (left, right) => { // 合并左右数组
const result = []
while(left.length && right.length) { // 必须两个数组都有元素再这样比较
if (left[0] <= right[0]) { // 相等时也要加入此条件,不然排序会乱
result.push(left.shift()) // 将数组中第一个元素放入新数组中并在原来数组中删除这个元素
// shift()方法用于移除数组中的第一个值,并返回此元素,
} else {
result.push(right.shift())
}
}
// 将剩余元素全部推进新数组
while (left.length) {
result.push(left.shift())
}
while (right.length) {
result.push(right.shift())
}
// 或者也可以这样
if (left.length) {
result.push(...left)
}
if (right.length) {
result.push(...right)
}
}
归并排序是稳定排序,它也是一种十分高效的排序,能利用完全二叉树特性的排序一般性能都不会太差。就是归并排序的优化版本。从上文的图中可看出,每次合并操作的平均时间复杂度为O(n),而完全二叉树的深度为|log2n|。总的平均时间复杂度为O(nlogn)。而且,归并排序的最好,最坏,平均时间复杂度均为O(nlogn)。