概念
归并排序是建立在归并操作上的一种有效,稳定的排序算法,该算法是采用分治法(Divide and Conquer)的一个非常典型的应用。将已有序的子序列合并,得到完全有序的序列;即先使每个子序列有序,再使子序列段间有序。若将两个有序表合并成一个有序表,称为二路归并。
示意图
代码实现
递归算法
个人对于递归算法的理解是:
- 把数组均分为左右两个子数组(假设子数组是有序数组,如果length%2 !=0,则右边多一个数据 ),然后进行有序数组合并
- 左右数组重复上一步的操作
- 直到子数组的个数为1,则停止
代码实现:
/**
* 归并排序(递归 长度为1500的时候会出现调用栈溢出的问题)
* @param {*} arr
* @returns
*/
sortByMergeRecursion(arr) {
if (!(arr instanceof Array) || arr.length < 2) {
return arr;
}
const list = arr.concat();
let length = list.length;
const merge = (left, right) => {
let result = [];
while (left.length > 0 && right.length > 0) {
if (left[0] < right[0]) {
result.push(left.shift());
} else {
result.push(right.shift());
}
}
result = result.concat(left).concat(right);
return result;
};
const middle = Math.floor(length / 2);
const left = list.slice(0, middle);
const right = list.slice(middle);
return merge(
this.sortByMergeRecursion(left),
this.sortByMergeRecursion(right)
);
}
非递归算法
当数组长度大于1500时递归算法存在调用栈溢出的问题。因此如果数组长度大于1500时,需要使用非递归算法。
网上有很多讲解的,但是讲得都语焉不详。
这边个人的理解是:
- 将数组划分为数组长度的2的约数(如果是结果是小数,则向上取整,可以使用
Math.ceil()
函数),然后对每个子数组进行合并(在第一次分组时,每个子数组只会有2个或者1个数据) - 每次循环
merge
的数据长度为2*i(i表示第几次,i从1开始) - 直到2^i >= 数据长度,则说明已经完成了所有有序数组的合并,循环结束
/**
* 归并排序(非递归算法)
* @param {*} arr
*/
sortByMergePass(arr) {
if (!(arr instanceof Array) || arr.length < 2) {
return arr;
}
const list = arr.concat();
let L = list.length;
let evenLength = 1;
/**
*
* @param {原始列表} list
* @param {左下标} left
* @param {中间标} middle
* @param {右下标} right
*/
const merge = (list, left, middle, right) => {
let temp = [];
let rightStart = middle;
const leftStart = left;
const count = right - left + 1;
const leftEnd = middle - 1;
// 以下本质是 合并两个有序数组
while (left <= leftEnd && rightStart <= right) {
if (list[left] < list[rightStart]) {
temp.push(list[left]);
left++;
} else {
temp.push(list[rightStart]);
rightStart++;
}
}
if (left > leftEnd) {
//左边的已经全部合并完成,,右侧剩下的元素说明都比已经合并的小,则需要把右侧的全部放入到数组中
while (rightStart <= right) {
temp.push(list[rightStart]);
rightStart++;
}
}
if (rightStart > right) {
while (left <= leftEnd) {
temp.push(list[left]);
left++;
}
}
list.splice(leftStart, count, ...temp);
};
for (let t = 0; Math.pow(2, t) < L; t++) {
for (let left = 0; left < L; left += evenLength * 2) {
let middle = left + evenLength < L ? left + evenLength : left;
let right = left + evenLength * 2 - 1;
if (right > L - 1) {
right = L - 1;
}
if (middle <= right) {
merge(list, left, middle, right);
}
}
// 每次的步幅长度乘以2
evenLength *= 2;
}
return list;
}