常見排序算法的最好、最壞、平均時間複雜度以及空間複雜度

思考

  • 爲什麼插入排序比冒泡排序更受歡迎?
  • 如何用快排思想在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.用哨兵簡化代碼,每次排序都減少一次判斷,儘可能把性能優化到極致

參考鏈接

  • 《數據結構與算法之美》專欄 王爭
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章