归并排序
将数组分为有序的两部分
假设 a = [1,3,5] b = [2,4,6] 如何合并 a,b ?
答案是 先新建一个 c = [];
拿出 a 中最小的和 b 中最小的比较得到最小的数
c.push( minNum )
然后将最小的数移出队列 或者将指针后移;
循环上面的步骤直到完成所有元素的比较
也就是
while(a.length&&b.length){
if(a[0]>b[0]){
c.push(b.shift())
}else{
c.push(a.shift())
}
}
while(a.length){
c.push(a.shift())
}
while(b.length){
c.push(b.shift())
}
但实际上我们需要排序的数组是无序的
怎么让他变得有序?
思路
将数组一分为二递归地让他们有序
答案是将他们二分 (一个数组变成两个)
不断的二分
分成每一个数组只有一个元素
比如 [3,1,4,2]
二分一次
[3,1] [4,2]
二分两次
[3] [1] [4] [2]
现在所有数组都是有序的
开始归并 [3] [1]
得到
[1,3]
归并 [4] [2]
得到
[2,4]
归并
[1,3] [2,4]
得到
[1,2,3,4]
排序完成
demo
function mergeSort(arr, middle = Math.floor(arr.length / 2) || 1, temp = []){
if(arr.length>2){// 分割长度大于2的数组
const leftArr = mergeSort(arr.slice(0,middle)); // 二分
const rightArr = mergeSort(arr.slice(middle)); // 二分
while(leftArr.length){ // 归并两个有序数组
if(leftArr[0]>rightArr[0]){
temp.push(rightArr.shift())
}else{
temp.push(leftArr.shift())
}
}
while(rightArr.length){
temp.push(rightArr.shift())
}
return temp;
} else if(arr.length === 2) { // 长度等于2
if(arr[0]>arr[1]){ // 使得数组升序
const tempNum = arr[0];
arr[0] = arr[1];
arr[1] = tempNum;
return arr;
} else { // 默认升序
return arr;
}
} else { // 长度为一的数组是有序的 直接返回
return arr;
}
}
上面的例子没有考虑空间复杂度并不科学
实际上我们没有必要使用递归
完全可以虚拟的二分
不需要将一个数组真的分成两个
只需要告诉计算机你将每个元素都看作一个数组
然后通过'指针'去传递他们即可
left right 标记两个靠在一起的待归并的有顺数组组合 边界用 middle 标记。
[ 3, 1, 4, 2 ]
| - | - | - | - |
L M R
| - - - | - - - |
L - - M - - R
| - - - - - - - |
以下是具体实现
考虑内存的归并排序
将一个数组看作是 N 个长度为一的数组组合起来的数组,两两归并。
function mergePass(arr = [], temp = new Array(arr.length), N = arr.length, length = 1){ // 将每个元素看作是相邻的数组长度为1。
let t; // 迭代深度。
for (t = 0; Math.pow(2,t) < N; t++, length *= 2) { // 每次跳过的长度翻倍。
const even = t%2 === 0; // 复用 arr 和 temp 来回赋值。
for (let left = 0; left < N; left += 2 * length) { // 左边数组起始位置 left 从0开始。
const middle = left + length < N ? left + length : left; // 右边数组起始位置 middle 就是left + 一个数组长度length 但是不要超过 N 。
const right = left + (2 * length) < N ? left + (2 * length) : N; // 右边界 right 就是 left + 两个数组长度。
merge(even ? arr : temp, even ? temp : arr, left, middle, right); // 合并每两个相邻的数组。
}
}
merge(arr, temp, 0, Math.pow(2,t-1), N); // 上面的迭代深度始终少一次在这里补足。
arr = temp; // 将稳定的数组赋值给 arr 释放掉 temp 。
return arr; // 返回 arr 。
}
function merge(arr, temp, left, middle, right){
const leftEnd = middle - 1; // 通过右边数组的起始位置得到左边数组的结束位置。
while (left <= leftEnd && middle < right) { // 如果‘指针’没有越界。
if (arr[left] > arr[middle]) { // 如果左边数组第一个元素比右边数组第一个元素大。
temp[left + middle - leftEnd -1] = arr[middle++]; // 将右边数组最小的放入有序数组 temp(初始值为空)。
} else {
temp[left + middle - leftEnd -1] = arr[left++]; // 将左边数组最小的放入有序数组 temp(初始值为空)。
}
}
while(left > leftEnd && middle < right){ // 如果左边数组放完了,右边数组还有元素。
temp[left + middle - leftEnd -1] = arr[middle++]; // 那么依次将右边数组剩余的元素放入 temp 。
}
while(left <= leftEnd && middle >= right){ // 如果右边数组放完了,左边数组还有元素
temp[left + middle - leftEnd -1] = arr[left++]; // 那么依次将左边数组剩余的元素放入 temp 。
}
}