將數組分爲有序的兩部分數組
假設 a = [1,3,5] b = [2,4,6] 如何合併 a,b ?ui
答案是 先新建一個 c = [];spa
拿出 a 中最小的和 b 中最小的比較獲得最小的數指針
c.push( minNum )code
而後將最小的數移出隊列 或者將指針後移;排序
循環上面的步驟直到完成全部元素的比較遞歸
也就是隊列
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())
}
複製代碼
但實際上咱們須要排序的數組是無序的內存
怎麼讓他變得有序?it
將數組一分爲二遞歸地讓他們有序
答案是將他們二分 (一個數組變成兩個)
不斷的二分
分紅每個數組只有一個元素
好比 [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]
排序完成
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 。
}
}
複製代碼