演算法從入門到「放棄」(5)- 計數排序

來自專欄 AnotherWorld

本系列文章

演算法從入門到「放棄」(1)- 什麼是演算法?

演算法從入門到「放棄」(2)- 分而治之和解決循環

排序系列

1. 演算法從入門到「放棄」(3)- 快速排序

2. 演算法從入門到「放棄」(4)- 插入排序

本文提煉

計數排序演算法,一種穩定的線性時間整數排序演算法。

別緊張,我只是數數有多少魚!


什麼是計數排序?

定義來自於維基百科,計數排序,Counting Sort.

計數排序是一種穩定的線性時間整數排序演算法。計數排序使用一個額外的輔助數組/計數數組C,其中第i 個元素是待排序數組A 中值等於i 的元素的個數。然後根據數組C 來將A 中的元素排到正確的位置。

計數排序,以及它在基數排序(Radix Sort) 中的應用,都是由哈羅德·h·蘇厄德在1954年發明的。

基本思想

輸入:一系列的無序數字組成的輸入數組A,裡面有n 個元素,每個元素都是非負的整數,數組中元素值的範圍是0到k

經過:過程中,我們需要目標數組B[1...n] 用來儲存最終結果,計數數組C[0...k] 用來暫時儲存過程中數據。具體經過如下:

初始狀態下,待排序數組A 有n 個數,計數數組C 中根據A 中元素值的範圍0到k 定下有幾個數+1(+1 是為了給0一個位子);然後根據數組A 中各個元素的個數確定計數數組中每個位置的個數。舉個栗子,比如A 中元素有2,5,3,0,2,3,0,3,元素值的範圍為0到5,那麼計數數組中就會只有5+1=6 個元素(0至5);因為A 中0 有2個,所以C 中0 的位置是2,同理,可以知道整個計數數組C 中1的位置是0, 2的位置是2, 3的位置是3, 4的位置是0, 5的位置是1。

接著,在計數數組C 中,從1的位置開始(為什麼不是從0的位置開始?因為不存在C[-1]),C[i] = C[i] + C[i-1]。這個循環結束後,我們現在的計數數組變為2, 2, 4, 7, 7, 8.

最後一步,套用如下偽代碼:

for j = A.length downto 1 //從原來的數組的長度開始降到1為止 B[C[A[j]]] = A[j] C[A[j]] = C[A[j]] - 1

代碼解釋起來比較複雜,我們直接看過程

j 一開始為A 的長度,8。第一行:A[j]=3,C[3]=7,B[7]=A[8]=3;第二行:C[3]=C[3]-1=7-1=6. 第一次迭代之後,B數組有了第一個數,B[7]=3,C數組變為了2, 2, 4, 6, 7, 8.

我們再迭代一次。j 現在降成了7。第一行:A[j]=A[7]=0,C[0]=2,B[2]=A[7]=0;第二行:C[0]=C[0]-1=2-1=1. 第二次迭代之後,B數組增加了第二個數,B[2]=0, C數組變為了1, 2, 4, 6, 7, 8.

迭代方法如上所示,一直持續到j 變為1.

輸出:如上面那個例子,最終結果就是B數組為0, 0, 2, 2, 3, 3, 3, 5.

圖解

這裡有一個University of San Francisco 電腦科學部門專門針對計數演算法創建的網頁,可以隨意輸入數組,大小,然後把排序的過程展示給我們。

過程很清晰,怎麼從輸入數組A,輔助數組C到輸出數組B

評價演算法好壞

分類:排序演算法

數據結構:數組

(下面的n 是數組中元素的個數,k 是數組中最大的值)

最壞時間複雜度:O(n+k)

最優時間複雜度:O(n+k)

平均時間複雜度:O(n+k);非常穩定

空間複雜度:O(n+k);也有極端情況,當數組中最大值遠小於數組中元素的數量,空間這時候會被運用得很有效率,O(k).

實例分析

我們假設有一個輸入數組A, [3,4,2,1,0,0,4,3,4,2].

這裡的元素共10個,n為10;最大值k 為4.

輸入數組如下圖:

具體過程參考基本思想中的經過部分。大家可以先推一遍,不懂的可以先參考這個網站(網站有一個漏洞,顯示k 為5,我們理解為4即可;但不影響理解,因為在後面的步驟中是對的,計數數組C 範圍還是從0到4)。不明白的部分歡迎在留言區貼上,我看到都會回復哦。

也歡迎大家把你的想法和過程分享在留言區哦。

流程展示圖

圖片來源:Efficient Algorithms

代碼展示

偽代碼

A 為輸入數組;B 為輸出數組;k 為最大值


在下一篇文章中,將著重講的是冒泡排序演算法。

如果你覺得我的文章有用,順手點個贊,關注下我的專欄或則留下你的評論吧!


推薦閱讀:
相關文章