推出一個新系列,《看圖輕鬆理解數據結構和算法》,主要使用圖片來描述常見的數據結構和算法,輕鬆閱讀並理解掌握。本系列包括各類堆、各類隊列、各類列表、各類樹、各類圖、各類排序等等幾十篇的樣子。mysql
計數排序(Counting Sort)算法由 Harold H. Seward 在1954年發明,它不是一種基於元素比較的排序算法,而是將待排序數組元素轉化爲計數數組的索引值,從而間接使待排序數組具備順序性。算法
整個過程包含三個數組:待排序數組A、計數數組B和輸出數組C。簡單來講,就是經過統計待排序數組A中元素不一樣值的分佈直方圖,生成計數數組B,而後計算計數數組B的前綴和(此步操做能夠當作計算待排序數組A中每一個元素的位置信息),最後經過逆序循環將元素對應賦值到輸出數組C中,輸出數組C便是最終排序結果。sql
從整個過程也能夠看到使用了額外的數組,因此它是一種以空間換時間的作法。數組
計數排序的時間複雜度爲Ο(n+k),其中n爲待排序數組長度,k爲計數數組長度(簡單狀況下能夠認爲k是待排序數組中最大值)。在整個計數排序過程當中涉及到若干個循環操做,其中初始化計數數組與計算計數數組前綴和這兩個循環每一個最多執行(k+1)次,因此這裏時間複雜度爲O(k)。而初始化輸出數組、統計待排序數組分佈直方圖、賦值到輸出數組這三個循環每一個執行n次,因此這裏的時間複雜度爲O(n)。因而,整個過程全部操做的時間複雜度爲Ο(n+k)。網絡
咱們知道在全部基於比較的排序算法中,最低的時間複雜度爲O(n * logn),因此能夠看到計數排序的時間複雜度可以比基於比較的排序算法更優,但當k很大而n又較小時,計數排序的效率反而不如基於比較的排序算法。數據結構
設待排序數組爲 A,計數數組爲 B,輸出數組爲 C,則計數排序的操做步驟以下:併發
嚴格的計數排序算法通常認爲具備穩定性,既不會打亂待排序數組中值相等的元素的順序。但有時在沒必要考慮穩定性的狀況下,咱們能夠簡化算法的過程。好比咱們在對單純的整數數組排序時就能夠不考慮排序的穩定性,由於一百個整數3中每一個3都是相同的,沒必要區分哪一個3要在另外一個3的前面。機器學習
在不用考慮穩定性的狀況下,咱們只須要一個計數數組做爲輔助便可,直接統計待排序數組的分佈直方圖,而後根據計數數組依次賦值待排序數組的元素便可完成排序工做。數據結構和算法
如今假設咱們有10個整數組成一個待排序數組A,元素分別爲3,1,4,4,2,0,1,5,0,1
。假設計數數組B長度已經肯定爲6,則它的索引值爲0-5,恰好是待排序數組中元素的取值範圍。學習
從頭至尾循環一遍,待排序數組A[0]=3,對應到計數數組B[3],則執行B[3]累加1。
接着A[1]=1,對應到B[1],則B[1]累加1。
繼續爲A[2]=4,對應到B[4],則B[4]累加1。
再往下爲A[3]=4,對應到B[4],則B[4]繼續累加1,此時能夠看到它的值已經變爲2。
相似地,把待排序數組中剩下的其餘元素都對應到計數數組中進行累加操做,最終結果以下:
目前爲止工做已經完成了一大半了,咱們獲得了計數數組B,它表示的是什麼呢?其實就是待排序數組元素出現的次數,好比B[0]=2表示0出現了2次,B[1]=3表示1出現了三次。因此最後一步就是按出現次數將值賦值回原來的數組中。
B[0]=2,說明有,2個0,分別將其賦值到A[0]和A[1],注意其中賦值一次須要將次數減1。
接着B[1]=3,說明有3個1,以此將其賦值到A[2]、A[3]和A[4]。
同理地,將計數數組B中剩餘的其餘元素也賦值到待排序數組A中指定的位置,最終結果以下:
此時待排序數組A便是已完成排序的結果。
前面說到的是不考慮排序穩定性的狀況,而咱們實際使用計數排序時其實更可能是須要考慮計數排序的。好比美國職業籃球聯盟(NBA)某賽季西部其中是個球隊的勝場數以下:
球隊 | 勝場 |
---|---|
火箭 | 65 |
雷霆 | 48 |
勇士 | 58 |
馬刺 | 47 |
爵士 | 48 |
開拓者 | 49 |
鵜鶘 | 48 |
森林狼 | 47 |
如今若是要使用計數排序算法根據勝場對球隊進行排序,若是仍是忽略排序的穩定性的話,那麼排序後可能會打亂原來數組中相同值的元素。好比48勝場的有雷霆、爵士和鵜鶘三個球隊,排序前的順序是雷霆-爵士-鵜鶘,但排序後多是爵士-鵜鶘-雷霆,這就是非穩定性表現。
那麼計數排序是如何解決穩定性問題的呢?主要就是對計數排序數組進行前綴和運算,而且引入額外的一個數組來解決。
以上面NBA球隊爲例,看一個具備穩定性的計數排序過程。一共有8個球隊,因此待排序數組長度爲8。而計數數組B長度爲待排序數組最大值減去最小值再加1,即65-47+1=19。此外要注意到,由於取值範圍是47-65,而數組的索引範圍是0-18,因此這裏實際上是要作一個便宜的,其中差值爲47。最後再初始化一個輸出數組,長度與待排序數組同樣。
對待排序數組從頭至尾循環一遍,待排序數組A[0]=65,對應到計數數組B[18],則執行B[18]累加1。
接着A[1]=48,對應到B[1],則B[1]累加1。
繼續爲A[2]=58,對應到B[11],則B[11]累加1。
相似地,把待排序數組中剩下的其餘元素都對應到計數數組中進行累加操做,最終結果以下:
接下去是計算計數數組B的前綴和,前面有說過,計算前綴和其實就是計算小於或等於數組索引值的個數。B[1]=B[0]+B[1]=2+3=5,說明小於等於48的個數是5。
接着是B[2]=B[2]+B[1]=5+1=6,說明小於等於49的個數是6。
相似地,計算數組中剩下的前綴和,最終結果以下:
最後是經過逆序循環並根據計數數組的信息將待排序數組中的元素存放到輸出數組中,這裏計數數組能夠當作是一個定位器。
從後往前循環,爲何要逆序循環呢?由於這樣能夠保證排序的穩定性。由於A[7]=47,因此對應B[0],而B[0]=2,說明「森林狼」及其前面一共有2個球隊,那麼"森林狼"應該放到C[1]處,此外要將B[0]的值減1。
接着由於A[6]=48,因此對應B[1],而B[1]=5,說明「鵜鶘」及其前面一共有5個球隊,那麼"鵜鶘"應該放到C[4]處,此外要將B[1]的值減1。
接着由於A[5]=49,因此對應B[2],而B[2]=6,說明「開拓者」及其前面一共有6個球隊,那麼"開拓者"應該放到C[5]處,此外要將B[2]的值減1。
相似地,將待排序中剩餘的元素一個個放到輸出數組中,這裏獲得的輸出數據便是最終排好序的數組,最終結果以下。
-------------推薦閱讀------------
個人開源項目彙總(機器&深度學習、NLP、網絡IO、AIML、mysql協議、chatbot)
跟我交流,向我提問:
歡迎關注: