看圖輕鬆理解計數排序

前言

推出一個新系列,《看圖輕鬆理解數據結構和算法》,主要使用圖片來描述常見的數據結構和算法,輕鬆閱讀並理解掌握。本系列包括各類堆、各類隊列、各類列表、各類樹、各類圖、各類排序等等幾十篇的樣子。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,則計數排序的操做步驟以下:併發

  1. 若是計數數組 B 的長度尚未肯定,那麼就先執行肯定操做,其實就是尋找待排序數組中的最小值和最大值,而後用計數數組的全部元素用來表示最小值到最大值之間的全部值,好比最小值和最大值分別爲20和30,則計數數組長度爲 30-20+1=11,因而數組下標爲0到10,分別表示20到30,即須要作一個偏移。假如計數數組長度事先已知道則省略此步。
  2. 統計待排序數組A中不一樣元素值的分佈直方圖,即將不一樣元素值出現的次數賦值到計數數組B對應的元素上。
  3. 對計數數組B執行計算前綴和操做,此步操做實際上就是計算小於或等於計數數組索引值的個數,好比 B[4]=5 表示小於等於4的元素有5個。
  4. 根據計數數組B的位置信息,經過逆序循環將待排序數組中的全部元素賦值到輸出數組C中指定位置,最終獲得的輸出數組C便是最終排序結果。

不考慮穩定性狀況

嚴格的計數排序算法通常認爲具備穩定性,既不會打亂待排序數組中值相等的元素的順序。但有時在沒必要考慮穩定性的狀況下,咱們能夠簡化算法的過程。好比咱們在對單純的整數數組排序時就能夠不考慮排序的穩定性,由於一百個整數3中每一個3都是相同的,沒必要區分哪一個3要在另外一個3的前面。機器學習

在不用考慮穩定性的狀況下,咱們只須要一個計數數組做爲輔助便可,直接統計待排序數組的分佈直方圖,而後根據計數數組依次賦值待排序數組的元素便可完成排序工做。數據結構和算法

如今假設咱們有10個整數組成一個待排序數組A,元素分別爲3,1,4,4,2,0,1,5,0,1。假設計數數組B長度已經肯定爲6,則它的索引值爲0-5,恰好是待排序數組中元素的取值範圍。學習

image

從頭至尾循環一遍,待排序數組A[0]=3,對應到計數數組B[3],則執行B[3]累加1。

image

接着A[1]=1,對應到B[1],則B[1]累加1。

image

繼續爲A[2]=4,對應到B[4],則B[4]累加1。

image

再往下爲A[3]=4,對應到B[4],則B[4]繼續累加1,此時能夠看到它的值已經變爲2。

image

相似地,把待排序數組中剩下的其餘元素都對應到計數數組中進行累加操做,最終結果以下:

image

目前爲止工做已經完成了一大半了,咱們獲得了計數數組B,它表示的是什麼呢?其實就是待排序數組元素出現的次數,好比B[0]=2表示0出現了2次,B[1]=3表示1出現了三次。因此最後一步就是按出現次數將值賦值回原來的數組中。

B[0]=2,說明有,2個0,分別將其賦值到A[0]和A[1],注意其中賦值一次須要將次數減1。

image
image

接着B[1]=3,說明有3個1,以此將其賦值到A[2]、A[3]和A[4]。

image
image
image

同理地,將計數數組B中剩餘的其餘元素也賦值到待排序數組A中指定的位置,最終結果以下:

image

此時待排序數組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。最後再初始化一個輸出數組,長度與待排序數組同樣。

image

對待排序數組從頭至尾循環一遍,待排序數組A[0]=65,對應到計數數組B[18],則執行B[18]累加1。

image

接着A[1]=48,對應到B[1],則B[1]累加1。

image

繼續爲A[2]=58,對應到B[11],則B[11]累加1。

image

相似地,把待排序數組中剩下的其餘元素都對應到計數數組中進行累加操做,最終結果以下:

image

接下去是計算計數數組B的前綴和,前面有說過,計算前綴和其實就是計算小於或等於數組索引值的個數。B[1]=B[0]+B[1]=2+3=5,說明小於等於48的個數是5。

image
image

接着是B[2]=B[2]+B[1]=5+1=6,說明小於等於49的個數是6。

image
image

相似地,計算數組中剩下的前綴和,最終結果以下:

image

最後是經過逆序循環並根據計數數組的信息將待排序數組中的元素存放到輸出數組中,這裏計數數組能夠當作是一個定位器。

從後往前循環,爲何要逆序循環呢?由於這樣能夠保證排序的穩定性。由於A[7]=47,因此對應B[0],而B[0]=2,說明「森林狼」及其前面一共有2個球隊,那麼"森林狼"應該放到C[1]處,此外要將B[0]的值減1。

image
image

接着由於A[6]=48,因此對應B[1],而B[1]=5,說明「鵜鶘」及其前面一共有5個球隊,那麼"鵜鶘"應該放到C[4]處,此外要將B[1]的值減1。

image
image

接着由於A[5]=49,因此對應B[2],而B[2]=6,說明「開拓者」及其前面一共有6個球隊,那麼"開拓者"應該放到C[5]處,此外要將B[2]的值減1。

image
image

相似地,將待排序中剩餘的元素一個個放到輸出數組中,這裏獲得的輸出數據便是最終排好序的數組,最終結果以下。

image

計數排序的侷限

  • 計數排序對於有小數的狀況比較力不從心,好比數組中的元素包含了3.1415,這種狀況下計數數組就很差建立了。
  • 對於數組內的最大最小元素差值很大的狀況,計數排序的代價將變得很大,同時致使效率很低。好比待排序數組一共有50個元素,其中最大是 10000000,最小是1,那麼計數數組長度將是 10000000,這樣作明顯有問題。

-------------推薦閱讀------------

個人開源項目彙總(機器&深度學習、NLP、網絡IO、AIML、mysql協議、chatbot)

爲何寫《Tomcat內核設計剖析》

2018彙總數據結構算法篇

2018彙總機器學習篇

2018彙總Java深度篇

2018彙總天然語言處理篇

2018彙總深度學習篇

2018彙總JDK源碼篇

2018彙總Java併發核心篇

2018彙總讀書篇


跟我交流,向我提問:

歡迎關注:

相關文章
相關標籤/搜索