博文地址html
個人GitHub | 個人博客 | 個人微信 | 個人郵箱 |
---|---|---|---|
baiqiantao | baiqiantao | bqt20094 | baiqiantao@sina.com |
今天講的是三種時間複雜度是 O(n)
的排序算法:桶排序、計數排序、基數排序。git
由於這些排序算法的時間複雜度是線性的,因此咱們把這類排序算法叫做線性排序
(Linear sort)。github
之因此能作到線性的時間複雜度,主要緣由是,這三個算法是非基於比較
的排序算法,都不涉及元素之間的比較操做。算法
這幾種排序算法理解起來都不難,時間、空間複雜度分析起來也很簡單,可是對要排序的數據要求很苛刻
,咱們學習的重點是掌握這些排序算法的適用場景
。編程
O(n+k)
,計數排序時間複雜度是O(n+k)
,基數排序時間複雜度是O(n*k)
O(n)
,計數排序空間複雜度是O(k)
,基數排序空間複雜度是O(n+k)
核心思想是將要排序的數據分到幾個有序的桶
裏,每一個桶裏的數據再單獨進行排序
。桶內排完序以後,再把每一個桶裏的數據按照順序依次取出,組成的序列就是有序的了。數組
k=n/m
個元素。O(k * logk)
。O(m * k * logk)
,由於 k=n/m
,因此整個桶排序的時間複雜度就是 O(n*log(n/m))
。log(n/m)
就是一個很是小的常量,這個時候桶排序的時間複雜度接近 O(n)
。O(nlogn)
的排序算法了。所謂的外部排序就是數據存儲在外部磁盤中,數據量比較大,內存有限,沒法將數據所有加載到內存中。微信
好比說咱們有 10GB 的訂單數據,咱們但願按訂單金額進行排序,可是咱們的內存有限,只有幾百 MB,這個時候該怎麼辦呢?函數
(00,01,02...99)
。若是訂單金額分佈不均勻,能夠對數據較多的區間再次使用桶排序
劃分爲更小的區間
。性能
計數排序實際上是桶排序的一種特殊狀況。學習
當要排序的 n 個數據所處的範圍並不大的時候,好比最大值是 k,咱們就能夠把數據劃分紅 k 個桶。每一個桶內的數據值都是相同的,省掉了桶內排序的時間。
好比,高考考生成績排名。
這個動圖並不許確,其更像是桶排序的過程,由於看起來他是先把待排的元素一個個的放到了桶裏,這樣的空間複雜度只能是
O(n)
,就無法優化爲O(k)
。
假設有 8 個考生,其成績放在一個數組 A[8]
中:2,5,3,0,2,3,0,3
。考生的成績從 0 到 5 分,咱們使用大小爲 6 的數組 C[6]
表示桶,其中下標爲分數
、值爲對應的考生個數
。
C[6]
的值:[2,0,2,3,0,1]
R[8]
中,會保存在下標爲 4,5,6 的位置那咱們如何快速計算出每一個分數的考生在有序數組中對應的存儲位置呢?
C[6]
數組順序求和,求和後 C[k]
裏存儲的就是小於等於分數 k 的考生個數。順序求和後 C[6] = [2,2,4,7,7,8]
從後到前
依次掃描數組 A:2,5,3,0,2,3,0,3
C[3]
要減 1,變成 6。從數組 A 中取數,也是能夠從頭開始取,可是就不是穩定排序算法了(由於最早取到的元素會被放到後面)
數據範圍不大
的場景中,若是數據範圍 k 比要排序的數據 n 大不少,就不適合用計數排序了。非負整數
排序,若是要排序的數據是其餘類型的,要將其在不改變相對大小的狀況下,轉化爲非負整數。桶排序空間複雜度是
O(n)
,而計數排序空間複雜度是O(k)
假設咱們有 10 萬個手機號碼,但願將這 10 萬個手機號碼從小到大排序,你有什麼比較快速的排序方法呢?
這個問題裏有這樣的規律:假設要比較兩個手機號碼 a,b 的大小,若是在前面幾位中,a 手機號碼已經比 b 手機號碼大了,那後面的幾位就不用看了。
先按照最後一位來排序手機號碼,而後再按照倒數第二位從新排序,以此類推,最後按照第一位從新排序。通過 11 次排序以後,手機號碼就都有序了。
注意,這裏按照每位來排序的排序算法必定要是
穩定的
。
O(n)
O(k*n)
O(n)
能夠分割
出獨立的位來比較有遞進的關係
,若是 a 數據的高位比 b 數據大,那剩下的低位就不用比較了數據範圍不能太大
,要能夠用線性排序算法來排序,不然,基數排序的時間複雜度就沒法作到 O(n)
了實際上,有時候要排序的數據並不都是等長的,好比排序牛津字典中的 20 萬個英文單詞。這時,咱們能夠把全部的單詞補齊
到相同長度(好比在後面補0
),這樣就能夠繼續用基數排序了。
O(n^2)
的算法O(nlogn)
的算法更加高效爲了兼顧任意規模數據的排序,通常都會首選時間複雜度是 O(nlogn)
的排序算法來實現排序函數。
O(nlogn)
,可是歸併排序不是原地排序算法,空間複雜度是 O(n)
,佔用空間過大O(logn)
,雖然快速排序在最壞狀況下的時間複雜度是 O(n^2)
,可是有不少方法能夠優化時間複雜度退化爲O(n^2)
的主要緣由是由於咱們分區點
選得不夠合理。
爲了提升快速排序算法的性能,咱們要儘量地讓每次分區都比較平均。最理想的分區點是:被分區點分開的兩個分區中,數據的數量差很少。
兩個比較經常使用、比較簡單的分區算法:
雖然說 qsort()
從名字上看,很像是基於快速排序算法實現的,實際上它並不只僅用了快排這一種算法。
qsort()
會優先使用歸併排序
qsort()
會改成用快速排序
qsort()
選擇分區點的方法就是三數取中法
qsort()
經過本身實現一個堆上的棧,手動模擬遞歸
來解決遞歸太深會致使堆棧溢出
的問題qsort()
就退化爲比較簡單、不須要遞歸的插入排序
O(n^2)
時間複雜度的算法並不必定比 O(nlogn)
的算法執行時間長qsort()
插入排序的算法實現中,也利用了哨兵這種編程技巧。雖然哨兵可能只是少作一次判斷,可是畢竟排序函數是很是經常使用、很是基礎的函數,性能的優化要作到極致。2021-8-13