計數排序詳解以及java實現


前言

我們知道,通過比較兩個數大小來進行排序的算法(比如插入排序,合並排序,以及上文提到的快速排序等)的時間復雜度至少是Θ(nlgn),這是因為比較排序對應的決策樹的高度至少是Θ(nlgn),所以排序最壞情況肯定是Θ(nlgn)。那有沒有哪種排序算法的時間復雜度是線性的(Θ(n))呢?(因為我們如果要對一個數組排序,肯定至少要考察每個元素,因此可以推斷Θ(n)是所有排序算法的下界)。答案是:在一定條件下,是有的。

思考過程

為了更好的理解計數排序,我們先來想象一下如果一個數組里所有元素都是整數,而且都在0-k以內。那對於數組里每個元素來說,如果我能知道數組里有多少項小於或等於該元素。我就能准確地給出該元素在排序后的數組的位置。

 

拿上圖這個數組來說,元素5之前有8個元素小於等於5(含5本身),因此我排序后5所在的位置肯定是8.所以我只要構造一個(k+1)大小的數組,里面存下所有對應A中每個元素之前的元素個數,理論上就能在線性時間內完成排序。

算法過程

根據以上說明,我們能得出計數算法的過程:

  1. 初始化一個大小為(k+1)的數組C(所有元素初始值為0),遍歷整個待排序數組A,將A中每個元素對應C中的元素大小+1。操作結果見下圖:

我們可以得到原數組中有2個0,0個1,2個2,3個3,0個4,1個5.

2.我們將C中每個i位置的元素大小改成C數組前i項和(基於之前的算法思考,我們不難理解這么做的道理):

 

3.OK,現在我們已經快看到成功的曙光了。現在要做的就是初始化一個和A同樣大小的數組B用於存儲排序后數組,然后倒序遍歷A中元素(后面會提到為何要倒序遍歷),通過查找C數組,將該元素放置到B中相應的位置,同時將C中對應的元素大小-1(表明已經放置了一個這樣大小的元素,下次再放同樣大小的元素,就要往前擠一個位置)。遍歷完A數組后,就完成了所有的排序工作(只畫出了前3步):

最后排序結果B:

我們現在回過頭來思考一下為什么要限定A中是整數而且要限定元素大小?以及這個計數算法的時間復雜度是多少?

首先第一個問題,要知道我們要在C數組中存儲所有A中對應元素之前的元素個數,因此如果不是整數或者大小范圍無限大的話,我們就沒法構造C數組,加之我們要對C數組遍歷操作,如果K太大的話,這個算法的線性復雜度也就沒有任何意義了。所以限制是整數純粹只是為了限制C數組的大小,如果你想提出另外一種有限范圍的限制,比如都是整數或者0.5結尾的小數(1.5,3.5等)也是可以的,只要將C的數組大小變成2k+2就可以了,只不過這種假設幾乎沒有任何實際意義而已。

對於第二個問題,我們來看看算法過程:第一步我們遍歷了A數組,因此操作時間是Θ(n),第二步遍歷C數組操作時間是Θ(k),第三步遍歷A數組插入B,因此操作時間是也是Θ(n)。加起來時間復雜度就是Θ(n+k)。據此我們也能得到該算法的適用場景僅限於k較小的情況,如果k很大的話,就不如使用比較排序效率高了。

細心的讀者應該還記得我在前文提過要解釋為何要倒序遍歷A數組,我們觀察一下A數組中的3,我們可以看到有3個元素都等於3,對應位置:3,6,8。這3個3最后在5,6,7位置

我們是把8位置的3放在了7位置上,6位置的3放在了6位置上,3位置的3放在了5位置上。也就是說所有元素仍保持了之前的相對位置,我們稱這個性質為排序的穩定性。有可能有人會覺得這個穩定性看起來沒什么用,單純從計數排序結果看,確實沒什么用處,但是當在其他地方用到計數排序時,穩定性就非常有用了,比如我們在下一篇博客將要談到的基數排序

最后附上計數排序的java代碼:

package sort;
public class CountSort {

    private static int[] countSort(int[] array,int k)
    {
        int[] C=new int[k+1];//構造C數組
        int length=array.length,sum=0;//獲取A數組大小用於構造B數組  
        int[] B=new int[length];//構造B數組
        for(int i=0;i<length;i++)
        {
            C[array[i]]+=1;// 統計A中各元素個數,存入C數組
        }
        for(int i=0;i<k+1;i++)//修改C數組
        {
            sum+=C[i];
            C[i]=sum;    
        }
        for(int i=length-1;i>=0;i--)//遍歷A數組,構造B數組
        {
            
            B[C[array[i]]-1]=array[i];//將A中該元素放到排序后數組B中指定的位置
            C[array[i]]--;//將C中該元素-1,方便存放下一個同樣大小的元素
            
        }
        return B;//將排序好的數組返回,完成排序
        
    }
    public static void main(String[] args)
    {
        int[] A=new int[]{2,5,3,0,2,3,0,3};
        int[] B=countSort(A, 5);
        for(int i=0;i<A.length;i++)
        {
            System.out.println((i+1)+"th:"+B[i]);
        }
    }
}

 排序結果:


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM