思考
- 爲什麼插入排序比冒泡排序更受歡迎?
- 如何用快排思想在O(n)內查找第K大元素?
- 如何根據年齡給100萬用戶數據排序?(線性排序)
- 如何實現一個通用的、高性能的排序函數?(排序優化)
如何分析一個排序算法?
是否原地排序 | 是否穩定 | 最好、最壞、平均時間複雜度 | 空間複雜度 | 是否基於比較 | |
---|---|---|---|---|---|
冒泡排序 | 是 | 是 | O(n)、 O(n^2)、 O(n^2) | O(1) | 是 |
插入排序 | 是 | 是 | O(n)、 O(n^2)、 O(n^2) | O(1) | 是 |
選擇排序 | 是 | 否 | O(n^2)、 O(n2)、O(n2) | O(1) | 是 |
希爾排序 | 是 | 否 | O(n)、O(n2)、O(n1.3) | O(1) | 是 |
快速排序 | 是 | 否 | O(nlogn)、O(n^2)、O(nlogn) | O(logn)~O(n) | 是 |
歸併排序 | 否 | 是 | O(nlogn)、O(nlogn)、O(nlogn) | O(n) | 是 |
計數排序 | 否 | 是 | O(n+k)、O(n+k)、O(n+k),k 是數據範圍 | O(n+k) | 否 |
桶排序 | 否 | 是 | O(n)、O(n)、O(n) | O(N+M),N表示待排數據個數,M表示桶個數 | 否 |
基數排序 | 否 | 是 | O(nk)、O(nk)、O(n*k),k 是維度 | O(n+k) | 否 |
堆排序 | 是 | 否 | O(nlogn)、O(nlogn)、O(nlogn) | O(1) | 是 |
排序算法的執行效率
- 1、最好情況、最壞情況、平均情況時間複雜度
- 2、時間複雜度的係數、常數 、低階
- 時間複雜度反映數據規模 n 很大時的一個增長趨勢,常忽略係數、常數、低階
- 實際軟件開發中,數據規模可能比較小,比較同一階時間複雜度的排序算法時,不能忽略係數、常數、低階
- 3、比較次數和交換(或移動)次數
- 基於比較的算法會涉及兩種操作:元素比較大小、元素交換或移動
- 分析排序算法執行效率時,應該要考慮比較次數(或移動)次數
排序算法的內存消耗
- 原地排序(Sorted in place):特指空間複雜度爲 O(1) 的排序算法
- 算法的內存消耗可以通過空間複雜度來衡量
排序算法的穩定性
- 穩定的排序算法: 指待排序的序列中存在值相等的元素時,經過排序後,相等元素之間原有的先後順序不變
如何選擇合適的排序算法?
- 對小規模數據進行排序,可以選擇時間複雜度是 O(n^2) 的算法
- 對大規模數據進行排序,時間複雜度是 O(nlogn) 的算法更加高效
- 爲了兼顧任意規模數據的排序,一般都會首選時間複雜度是 O(nlogn) 的排序算法來實現排序函數
如何優化快速排序?
- 快速排序出現 O(n^2)時間複雜度的主要原因是因爲分區節點選擇不夠合理
- 最理想的分區節點是:被分區點分開的兩個分區中,數據的數量差不多
- 方法
- 1、三數取中法(五數取中、十數取中等):從區間的首、尾、中間,分別取出一個數,然後對比大小,取這 3 個數的中間值作爲分區點
- 2、隨機法:每次從要排序的區間中,隨機選擇一個元素作爲分區點
- 3、等等
解答思考題
-
1. 爲什麼插入排序比冒泡排序更受歡迎?
從代碼實現上來看,冒泡排序的數據交換要比插入排序的數據移動要複雜,冒泡排序需要 3 個賦值操作,而插入排序只需要 1 個。
// 冒泡排序中數據的交換操作: if (a[j] > a[j+1]) { // 交換 int tmp = a[j]; a[j] = a[j+1]; a[j+1] = tmp; flag = true; } // 插入排序中數據的移動操作: if (a[j] > value) { a[j+1] = a[j]; // 數據移動 } else { break; }
-
2. 如何用快排思想在O(n)內查找第K大元素?
比如,4, 2, 5, 12, 3 這樣一組數據,第 3 大元素就是 4。
我們選擇數組區間 A[0…n-1] 的最後一個元素 A[n-1] 作爲 pivot,對數組 A[0…n-1] 原地分區,這樣數組就分成了三部分,A[0…p-1]、A[p]、A[p+1…n-1]。
如果 p+1=K,那 A[p] 就是要求解的元素;如果 K>p+1, 說明第 K 大元素出現在 A[p+1…n-1] 區間,我們再按照上面的思路遞歸地在 A[p+1…n-1] 這個區間內查找。同理,如果 K<p+1,那我們就在 A[0…p-1] 區間查找。
我們再來看,爲什麼上述解決思路的時間複雜度是 O(n)?
第一次分區查找,我們需要對大小爲 n 的數組執行分區操作,需要遍歷 n 個元素。第二次分區查找,我們只需要對大小爲 n/2 的數組執行分區操作,需要遍歷 n/2 個元素。依次類推,分區遍歷元素的個數分別爲、n/2、n/4、n/8、n/16.……直到區間縮小爲 1。
如果我們把每次分區遍歷的元素個數加起來,就是:n+n/2+n/4+n/8+…+1。這是一個等比數列求和,最後的和等於 2n-1。所以,上述解決思路的時間複雜度就爲 O(n)。 -
3. 如何根據年齡給100萬用戶數據排序?(線性排序)
假設年齡的範圍最小 1 歲,最大不超過 120 歲。可以遍歷這 100 萬用戶,根據年齡將其劃分到這 120 個桶裏,然後依次順序遍歷這 120 個桶中的元素。這樣就得到了按照年齡排序的 100 萬用戶數據。
-
4. 如何實現一個通用的、高性能的排序函數?(排序優化)
1.數據量不大時,可以採取用時間換空間的思路
2.數據量大時,優化快排分區點的選擇
3.防止堆棧溢出,可以選擇在堆上手動模擬調用棧解決
4.在排序區間中,當元素個數小於某個常數是,可以考慮使用O(n^2)級別的插入排序
5.用哨兵簡化代碼,每次排序都減少一次判斷,儘可能把性能優化到極致
參考鏈接
- 《數據結構與算法之美》專欄 王爭