排序算法 | 思路 | 時間複雜度 | 空間複雜度 | 穩定性 |
---|---|---|---|---|
冒泡排序 | 兩兩比較,將較大(或較小)的元素換到後面,每輪比較後數組後面都是排序好的 | O(n^2) | O(1) | 穩定 |
插入排序 | 將待插入的元素依次與前面已排序的元素對比,插入到正確的位置 | O(n^2) | O(1) | 穩定 |
歸併排序 | 分治,排序,合併 | O(nlogn) | O(n) | 穩定 |
快速排序 | 基準元素,小於基準元素放左邊,大於基準元素放右邊,遞歸 | 最好O(nlogn)、最壞O(n^2) | O(logn) | 不穩定 |
拓撲排序 | 有向圖、無環、理清依賴關係、廣度優先搜索或深度優先搜索 |
給定一個數組,咱們把數組裏的元素統統倒入到水池中,這些元素將經過相互之間的比較,按照大小順序一個一個地像氣泡同樣浮出水面。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]
不斷地將還沒有排好序的數插入到已經排好序的部分。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)。後端
核心是分治,就是把一個複雜的問題分紅兩個或多個相同或類似的子問題,而後把子問題分紅更小的子問題,直到子問題能夠簡單的直接求解,最原問題的解就是子問題解的合併。歸併排序將分治的思想體現得淋漓盡致。數組
一開始先把數組從中間劃分紅兩個子數組,一直遞歸地把子數組劃分紅更小的子數組,直到子數組裏面只有一個元素,纔開始排序。
排序的方法就是按照大小順序合併兩個元素,接着依次按照遞歸的返回順序,不斷地合併排好序的子數組,直到最後把整個數組的順序排好。函數
// 歸併排序 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,時間複雜度是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)
建議:歸併算法的思想很重要,其中對兩個有序數組合並的操做,在不少面試題裏都有用到,建議你們必定要把這個算法練熟。
// 快速排序 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]
要能實現拓撲排序,得有幾個前提
1.圖必須是有向圖
2.圖裏面沒有環拓撲排序通常用來理清具備依賴關係的任務。
舉例:假設有三門課程A、B、C,若是想要學習課程C就必須先把課程B學完,要學習課程B,還得先學習課程A,因此得出課程的學習順序應該是A->B->C實現1.將問題用一個有向無環圖(DAG, Directed Acyclic Graph)進行抽象表達,定義出哪些是圖的頂點,頂點之間如何互相關聯。2.能夠利用廣度優先搜索或深度優先搜索來進行拓撲排序。