排序

概述

排序算法 思路 時間複雜度 空間複雜度 穩定性
冒泡排序 兩兩比較,將較大(或較小)的元素換到後面,每輪比較後數組後面都是排序好的 O(n^2) O(1) 穩定
插入排序 將待插入的元素依次與前面已排序的元素對比,插入到正確的位置 O(n^2) O(1) 穩定
歸併排序 分治,排序,合併 O(nlogn) O(n) 穩定
快速排序 基準元素,小於基準元素放左邊,大於基準元素放右邊,遞歸 最好O(nlogn)、最壞O(n^2) O(logn) 不穩定
拓撲排序 有向圖、無環、理清依賴關係、廣度優先搜索或深度優先搜索

1.基本的排序算法

  • 冒泡排序(BubbleSort)
  • 插入排序(InsertionSort)

2.常考的排序算法

  • 歸併排序(MergeSort)
  • 快速排序(QuickSort)
  • 拓撲排序(Topological Sort)

3.其餘排序算法

  • 堆排序(Heap Sort)
  • 桶排序(Bucket Sort)

注意:

  • 冒泡排序和插入排序是最基礎的,面試官有時候喜歡拿它們來考察你的基礎知識,而且看看你能不能快速地寫出沒有bug的代碼。
  • 歸併排序、快速排序和拓撲排序的思想是解決絕大部分涉及排序問題的關鍵。
  • 堆排序和桶排序,本節課不做深刻研究,但有時間的話必定要看看,尤爲是桶排序,在必定的場合中(例如知道全部元素出現的範圍時),能在線性的時間複雜度裏解決戰鬥,掌握好它的解題思想能開闊解題思路。

冒泡排序

  • 基本思想

給定一個數組,咱們把數組裏的元素統統倒入到水池中,這些元素將經過相互之間的比較,按照大小順序一個一個地像氣泡同樣浮出水面。javascript

  • 實現

每一輪,從雜亂無章的數組頭部開始,每兩個元素比較大小並進行交換,直到這一輪當中最大或最小的元素被放置在數組的尾部,而後不斷地重複這個過程,直到全部元素都排好位置。其中,核心操做就是元素相互比較。前端

  • 代碼實現
var arr = [2, 1, 7, 9, 5, 8];
// 冒泡排序  O(n^2)  穩定算法
// 在冒泡排序中,通過每一輪的排序處理後,數組後端的數是排好序的
// 每一輪,從數組頭部開始,比較兩個元素,將大的換到後面,則這一輪結束後就將最大的元素換到了數組尾部
// 若是hasChange===flase,說明上一輪未發生位置交換,已經排序好了,就不須要下一輪了
bubbleSort = function (arr) {
  var hasChange = true;
  for (var i = 0; i < arr.length - 1 && hasChange; i++) {
    hasChange = false;
    for (var j = 0; j < arr.length - 1 - i; j++) {
      if (arr[j] > arr[j + 1]) {
        [arr[j], arr[j + 1]] = [arr[j + 1], arr[j]];
        hasChange = true;
      }
    }
  }
  return arr;
};
bubbleSort(arr); // [1,2,5,7,8,9]
  • 空間複雜度: 因爲在整個排序的過程當中,是直接在給定的數組裏面進行元素的兩兩交換,因此空間複雜度是 O(1)。
  • 時間複雜度:冒泡排序的時間複雜度是 O(n^2)。它是一種穩定的排序算法。(穩定是指若是數組裏兩個相等的數,那麼排序先後這兩個相等的數的相對位置保持不變。)

插入排序

  • 基本思想

不斷地將還沒有排好序的數插入到已經排好序的部分。java

  • 特色

在冒泡排序中,通過每一輪的排序處理後,數組後端的數是排好序的;而對於插入排序來講,通過每一輪的排序處理後,數組前端的數都是排好序的。面試

  • 代碼實現
function InsertionSort(arr) {
  var len = arr.length;
  // 將數組的第一個元素看成是已經排序好的,從第二個元素,即 i 從 1 開始遍歷數組
  for (var i = 1; i < len; i++) {
    var temp = arr[i]; // 存 待插入的元素
    for (var j = i; j > 0; j--) {
      // 將待插入的元素與他前面的進行比較,若是比前面的小,就將前面的後移
      if (temp >= arr[j - 1]) {
        break; // 當前考察的數大於前一個數,證實有序,退出循環
      } else {
        arr[j] = arr[j - 1]; // 將前一個數複製到後一個數上
      }
    }
    // 內循環結束,j所指向的位置就是temp值插入的位置
    arr[j] = temp; // 找到考察的數應處於的位置
  }
  console.log(arr);

  return arr;
}
InsertionSort(arr); //  [1, 2, 5, 7, 8, 9]
  • 時間複雜度:

和冒泡排序同樣,插入排序的時間複雜度是 O(n2),而且它也是一種穩定的排序算法。算法

  • 空間複雜度

假設數組的元素個數是 n,因爲在整個排序的過程當中,是直接在給定的數組裏面進行元素的兩兩交換,空間複雜度是 O(1)。後端

歸併排序( Merge Sort)

  • 基本思想

核心是分治,就是把一個複雜的問題分紅兩個或多個相同或類似的子問題,而後把子問題分紅更小的子問題,直到子問題能夠簡單的直接求解,最原問題的解就是子問題解的合併。歸併排序將分治的思想體現得淋漓盡致。數組

  • 實現

一開始先把數組從中間劃分紅兩個子數組,一直遞歸地把子數組劃分紅更小的子數組,直到子數組裏面只有一個元素,纔開始排序。
排序的方法就是按照大小順序合併兩個元素,接着依次按照遞歸的返回順序,不斷地合併排好序的子數組,直到最後把整個數組的順序排好。函數

  • 代碼實現
// 歸併排序
function mergeSort(arr) {
  debugger
  var len = arr.length;
  if (len < 2) {
    return arr;
  }
  // 首先將無序數組劃分爲兩個數組
  var mid = Math.floor(len / 2);
  var left = arr.slice(0, mid);
  var right = arr.slice(mid, len);
  return merge(mergeSort(left), mergeSort(right));
}

// 合併,依次將小的放進新數組
function merge(left, right) {
  var result = [];
  while (left.length > 0 && right.length > 0) {
    if (left[0] < right[0]) {
      result.push(left.shift(0));
    } else {
      result.push(right.shift(0));
    }
  }
  while (left.length > 0) {
    result.push(left.shift(0));
  }
  while (right.length > 0) {
    result.push(right.shift(0));
  }
  return result;
}
console.log(mergeSort(arr)); //  [1, 2, 5, 7, 8, 9]
  • 空間複雜度:因爲合併n個元素須要分配一個大小爲n的額外數組,合併完成以後,這個數組的空間就會被釋放,因此算法的空間複雜度就是O(n)。歸併排序也是穩定的排序算法。
  • 時間複雜度:歸併算法是一個不斷遞歸的過程。

舉例:數組的元素個數是n,時間複雜度是T(n)的函數。學習

解法:把這個規模爲n的問題分紅兩個規模分別爲η/2的子問題,每一個子問題的時間複雜度就是T(n/2),那麼兩個子問題的複雜度就是2×T(n/2)。當兩個子問題都獲得瞭解決,即兩個子數組都排好了序,須要將它們合併,一共有n個元素,每次都要迸行最多n-1次的比較,因此合併的複雜度是o(n)。由此咱們獲得了遞歸複雜度公式:T(n)=2×T(n/2)+O(n)。ui

對於公式求解,不斷地把一個規模爲η的問題分解成規模爲η2的問題,一直分解到規模大小爲1。

若是n等於2,只須要分一次;若是η等於4,須要分2次。這裏的次數是按照規模大小的變化分類的以此類推,對於規模爲η的問題,一共要進行log(η)層的大小切分。在每一層裏,咱們都要進行合併,所涉及到的元素其實就是數組裏的全部元素,所以,每一層的合併複雜度都是O(n),因此總體的複雜度就是 o(nlogn)

建議:歸併算法的思想很重要,其中對兩個有序數組合並的操做,在不少面試題裏都有用到,建議你們必定要把這個算法練熟。

快速排序( Quick Sort)

  • 基本思想:快速排序也採用了分治的思想。
  • 實現:取一個基準元素,比這個元素小的放到左邊數組,比這個元素大的放到右邊數組,而後遞歸地排序兩個子數組,而後把排序好的左數組、基準元素、排序好的右數組合並。
  • 代碼實現
// 快速排序
function quickSort(arr) {
  if (arr.length < 2) {
    return arr;
  }
  var p = arr[0]; // 用第一個元素做爲基準值,比它小的放到左邊數組,比它大的放到右邊數組
  var left = [];
  var right = [];
  for (var i = 1; i < arr.length; i++) {
    if (arr[i] <= p) {
      left.push(arr[i]);
    } else {
      right.push(arr[i]);
    }
  }
  return [...quickSort(left), p, ...quickSort(right)];
}
console.log(quickSort(arr)); // [1, 2, 5, 7, 8, 9]
  • 時間複雜度: 最好狀況O(nlogn),最壞狀況O(n^2)

拓撲排序( Topological Sort)

  • 基本思想和前面介紹的幾種排序不一樣,拓撲排序應用的場合再也不是一個簡單的數組,而是研究圖論裏面頂點和頂點連線之間的性質。拓撲排序就是要將這些頂點按照相連的性質進行排序。

要能實現拓撲排序,得有幾個前提
1.圖必須是有向圖
2.圖裏面沒有環拓撲排序通常用來理清具備依賴關係的任務。

舉例:假設有三門課程A、B、C,若是想要學習課程C就必須先把課程B學完,要學習課程B,還得先學習課程A,因此得出課程的學習順序應該是A->B->C實現1.將問題用一個有向無環圖(DAG, Directed Acyclic Graph)進行抽象表達,定義出哪些是圖的頂點,頂點之間如何互相關聯。2.能夠利用廣度優先搜索或深度優先搜索來進行拓撲排序。

相關文章
相關標籤/搜索