歸併排序

歸併排序

將數組分爲有序的兩部分數組

假設 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]

排序完成

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 。
  }
}
複製代碼
相關文章
相關標籤/搜索