非比較排序-計數排序

1.計數排序

 

    前面學習了歸併排序,快速排序時間複雜度爲O(n*logn)而有沒有比這更快的排序算法呢?當然是有的那就是計數排序,首先計數排序並不是比較排序算法,而是利用數組來實現的一種算法,想象一下這樣一個場景,假如給數組{1,4,5,1,3}做一個排序,我們可以看出其中最大的值就是5,但是怎麼利用數組實現排序呢?我們知道數組是一組連續的地址空間,且可以通過下標進行隨機訪問,數組有下標是有序的,我們可以利用下標來實現排序。

    首先我們知道數組{1,4,5,1,3}中最大是5,那我們至少需要一個下標能夠達到5吧,所以我們定義一個長度爲最大值加1長度的計數數組,也就是6,同時給所有下標的值默認爲0,然後遍歷要排序的數組{1,4,5,1,3},每拿到一個數,就在計數數組對應的下標加1,例如第一次遍歷爲1,那麼就在計數數組下標爲1的位置加1,當然如果出現多次依然是累加,比如第一次出現了1,計數數組下標1的位置加1,加1表示出現了1次,下一次又出現了一次1那麼還是加1,此時計數下標爲1的位置值就是2,也就是出現了2次。(看下面圖)

 

 

   最後我們可以看到計數數組的值爲{0,2,0,1,1,1},此時我們只需要將計數數組中對應下標進行輸出即可,比如計數數組中下標爲0的值爲0,此時就是輸出0次,而1出現兩次,那麼輸出兩次1即可。

 

   java代碼實現如下所示

 

 雖然上面代碼實現了排序,但是存在很多問題

    1.如果要排序的數組是這樣的數組{90,93,92,92,95},難道我們還是要根據最大值爲95開一個長度爲96的計數數組嗎?這樣就大大浪費了空間了,我們採用的方式就是用最大值減最小值+1,然後根據最小值做爲偏移量進行計算在計數數組中爲位置,比如90放的位置就是90-最小值90,也就是0的位置,而93則是93-90,也就是3的位置。我們更改代碼後實現如下。

 

 

 

    2.我們解決了數組值不是從0開始浪費空間的問題,下面就是如果是負數或者小數我們應該怎麼解決呢?如果存在負數的情況,那麼我們找到最小的負數,給負數加上一個數,使得他變成正數,當然你不可能只加這一個地方,應該是這個數組每一位都要加上這個值,這樣才能保證不影響大小,但是你不能加了後面就不管了吧,所以在把結果放到結果數組中時,我們應該減去之前加的數;同理如果存在小數的情況,我們只需要找到最小的那個值乘以一個數,使得變爲整數,然後其餘位置全部乘以這個數即可,當然最後將值給返回數組中時,要除以之前乘以的數。

    3.計數排序怎麼實現穩定排序呢?你想想一下我們如果有兩個5,我們只是在計數數組下標爲5的位置加了2次,記錄了出現的次數,我們實際上根本不知道這個5究竟是哪個數,就比如期末考試吧,如果小紅考了95,小陽也考了95,那麼我們只是在對應下標上加了2次,根本不知道是小紅還是小陽。這個問題怎麼解決呢?下面的內容有點燒腦,前方高能。

    首先假定我們有這樣的考試成績,小紅94分,小灰91分,小綠91分,小白92分,也就是要排序的數組爲{94,91,91,92},然後可以得到計數數組爲{2,1,0,1},然後把計數數組變形成下面的樣子。

 

 

     變形後的計數數組實際上就是從下標1開始每一位都加上前面的值,比如下標1的數1加上上一位下標0的數2就是3,下標2的數0加上下標1的數3也就是3,下標3的數1加上下標2的數3就是4。

    這樣做是爲什麼呢?這樣就可以知道對應每個學生考試成績在第幾名了,如果我們再把名次減1,也就是實際上最終返回數組中所在的位置,比如小白92分,我們可以通過92-91(最小值)=1,然後根據1找到,他在計數數組變形版的下標爲1的位置,也就是第三名,然後我們再把3減去1,也就是他應該存放在最終返回數組中的位置。同理如果是小綠我們可以知道他是在計數數組變形中的下標爲0的位置,也就是第二名,然後我們減去1,也就是應該存放在下標爲1的位置,那麼小灰呢?小灰我們知道他是91分那麼這樣我們就可以知道他在計數數組變形中的下標爲0的位置,因爲之前小綠已經減了一次1,所以現在他是第一名,且我們再減1,那麼他就應該存放在下標爲0的位置。

 

    需要注意的是我們在最後遍歷的時候,必須倒着遍歷。爲什麼必須要倒着遍歷,如果不倒着遍歷小灰和小綠的位置就發生了變化,也就不再是穩定的排序了。

 

 java代碼實現如下。

 

    我們來看看計數排序的時間複雜度和空間複雜度,首先我們找最大值和最小值執行了n次,然後計數數組產生值有需要遍歷n次,也就是O(2*n),然後我們再變形了一次計數數組,就是k次,最後我們又遍歷了一次原數組,所以就是O(3*n+k),然後根據大O表示法,我們去掉一個常數階,可以得到時間複雜度爲O(n+k),那麼空間複雜度呢?我們返回數組定義了一個和原始數組一樣長的也就是n,然後定義了一個計數數組k,所以就是O(n+k),如果不考慮結果數組就是O(n)。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章