众所周知,归并算法主要有两种方法实现,递归、迭代(插一句,一般递归都可用迭代实现),且时间复杂度都是O(nlog(n)),递归网上版本较多,简单提一嘴,主要是讲解迭代的 JS 实现
归并算法思路
- 看一张《学习JavaScript数据结构与算法(第3版)》的图就一目了然了,先将数组拆分为小数组,再排序并合并回去,一看到此图自然就会联想到递归方法了,这正是递归调用栈的步骤展示
递归版
- 优缺点:递归一般优点是代码量少,缺点是较抽象、调用栈较大
- 思路:通过递归去拆分数组,通过
merge
函数去合并子数组
var sortArray = function (nums) {
if (nums.length < 2) return nums
let mid = Math.floor(nums.length / 2)
let left = nums.slice(0, mid), right = nums.slice(mid)
// 合并两个数组
let merge = (l, r) => {
let res = []
while (l.length && r.length) {
l[0] < r[0] ? res.push(l.shift()) : res.push(r.shift())
}
return [...res, ...l, ...r]
}
return merge(sortArray(left), sortArray(right))
}
迭代版
- 优缺点:据网上结论是效率会高很多,但在力扣实测并不如意。是用的第912题“排序数组”,顺便还测了冒泡排序和快速排序,结果一目了然。归并排序迭代法的效率反而降低了,当然,也许是使用的场景不对
- 思路:通过循环直接去找到子数组,合并,继续找,继续合并…合并完当前子数组后继续合并得到的大数组,如此反复下去,直接看代码比较抽象,先模拟下迭代过程
原数组:[8,7,6,5,4,3,2,1]
循环1,步长为2^0,子数组长度为1,将两个~合并成一个长度为2的大~:
[[8,7],6,5,4,3,2,1] -> [[7,8],6,5,4,3,2,1]
[7,8,[6,5],4,3,2,1] -> [7,8,[5,6],4,3,2,1]
... -> [7,8,5,6,3,4,1,2]
循环2,步长为2^1:
[[7,8,5,6],3,4,1,2] -> [[5,6,7,8],3,4,1,2]
[5,6,7,8,[3,4,1,2]] -> [5,6,7,8,[1,2,3,4]]
循环3,步长为2^2:
[5,6,7,8,1,2,3,4] -> [1,2,3,4,5,6,7,8]
- 代码
var sortArray = function (nums) {
// 合并两个数组,用了许多API简化书写,和递归的合并同理,但需要额外处理
merge = (arr, left, center, right) => {
let res = []
let l = arr.slice(left, center)
let r = arr.slice(center, right + 1)
while (l.length && r.length) {
l[0] < r[0] ? res.push(l.shift()) : res.push(r.shift())
}
res.push(...l, ...r)
for (let i = left; i <= right; i++) {
arr[i] = res[i - left]
}
return arr
}
// 双循环,找到子数组
for (let step = 1; step < nums.length; step *= 2) {
let offset = step * 2;
for (let start = 0; start < nums.length; start += offset) {
// 与边界比较,不得长于数组长度
nums = merge(nums, start,
Math.min(nums.length - 1, start + step),
Math.min(nums.length - 1, start + offset - 1))
}
}
return nums
}
如果觉得对你有帮助的话,点个赞呗~
反正发文又不赚钱,交个朋友呗~
如需转载,请注明出处foolBirdd