1. 程式人生 > >java-陣列排序--計數排序、桶排序、基數排序

java-陣列排序--計數排序、桶排序、基數排序

計數排序引入

不難發現不論是氣泡排序還是插入排序,其排序方法都是通過對每一個數進行兩兩比較進行排序的,這種方法稱為比較排序,實際上對每個數的兩兩比較嚴重影響了其效率,理論上比較排序時間複雜度的最低下限為nlog(n),即任何比較排序的時間複雜度將不會低於nlog(n),那麼有沒有方法能不經過數列比較就能使數列排序呢 ,她們的時間複雜度又是多少呢???

 

計數排序就是一個非比較排序的演算法,一如魚與熊掌不可兼得,她使用了犧牲空間換時間的方法,使的時間複雜度可以達到Ο(n+k)

假設我們有一個數列arr=[3,4,6,4,8,1] 假設max為數列的最大值,此時我們建立一個大小為max+1的輔助陣列sortArr,我們以此遍歷arr陣列,並將數列中的數對應輔助陣列sortArr下標的值+1

那麼輔助陣列中的數,就是其下標在待排序陣列中出現的次數,最後我們通過遍歷輔助陣列sortArr就能獲得一個排序好的序列(為什麼要建立max+1長度的輔助序列:因為要將待排序數列與輔助序列的下標進行關聯,而max應為輔助序列的最後一個下標值,而序列的下標從0開始,有輔助序列的長度應為max+1)

 

 1     public static void countSort01(int[] arr) {
 2         
 3         /* 獲取序列的最大值 */
 4         int max=arr[0];
 5         for (int i = 0; i < arr.length; i++) {
6 if(arr[i]>max) { 7 max=arr[i]; 8 } 9 } 10 11 /* 建立一個長度為max+1的輔助陣列 用來儲存與輔助陣列對應下標的數值 在待排序數列中出現的次數 */ 12 int[] sortArr=new int[max+1]; 13 for (int i = 0; i < arr.length; i++) { 14 /* 遍歷帶排序數列 每次將輔助序列對應下標處的數值+1
*/ 15 sortArr[arr[i]]++; 16 } 17 18 /* 列印輔助序列 與 排序數 */ 19 System.out.println(Arrays.toString(sortArr)); 20 for (int i = 0; i < sortArr.length; i++) { 21 for (int j = 0; j < sortArr[i]; j++) { 22 System.out.print(i+" , "); 23 } 24 } 25 26 }

 

測試一下

    public static void main(String[] args) {
        
        Sort.countSort01(new int[] {6,10,6,8,6,8,12,15,9,6,7});
        
    }
結果:

對引入的改進

 從上面看,我們已經能對一個數組進行基本的排序了,但是仔細想想,此時列印的排序的數是不穩定的,甚至於這個數與原數列失去的聯絡,它僅僅只是表示輔助數列的一個下標值,如果我們是對一個物件進行排序,比如學生[姓名,成績],通過上種方法只能排出所有的成績,而原來此成績是誰的卻無從得知了,無疑這樣的排序不那麼合格,此時,我們對程式碼進行一些改進 這次我們不再儲存出現數字的個數,而是儲存其次序

 1     public static void countSort02(int[] arr) {
 2     
 3         /* 獲取待排序數列的最大值 用來建立輔助陣列 */
 4         int max = arr[0];
 5         for (int i = 0; i < arr.length; i++) {
 6             if (arr[i] > max) {
 7                 max = arr[i];
 8             }
 9         }
10         
11         /* 建立輔助陣列 先在輔助陣列儲存其對應下標在待排序數中出現的次數 (與countSort01同) */
12         int[] sortArr = new int[max + 1];
13         for (int i = 0; i < arr.length; i++) {
14             sortArr[arr[i]]++;
15         }
16         /* 對輔助陣列進行改進 將其儲存的對應待排序數列的次數 轉換為臨時排序數列的下標位置 */
17         for (int i = 1; i < sortArr.length; i++) {
18             sortArr[i] += sortArr[i - 1];
19         }
20         System.out.println("輔助序列: "+Arrays.toString(sortArr));
21         /* 建立臨時排序數列 */
22         int[] tsortArr = new int[arr.length];
23         for (int i = 0; i < arr.length; i++) {
24             tsortArr[--sortArr[arr[i]]] = arr[i];
25         }
26         System.out.println("排序數列: "+Arrays.toString(tsortArr));
27         
28     }

 

 這樣我們就能獲取一個排序數列了 測試一下

1     public static void main(String[] args) {
2         
3         System.out.println("----------引入-------------");
4         Sort.countSort01(new int[] {6,10,6,8,6,8,12,15,9,6,7});
5         System.out.println();
6         System.out.println("----------改進-------------");
7         Sort.countSort02(new int[] {6,10,6,8,6,8,12,15,9,6,7});
8         
9     }

計數排序

因為演算法建立了兩個0-(max+1)的陣列進行輔助,如果是象[2,1,4,5,8]這樣的數列自然沒有問題,但如果是對像[99,97,96,99,100]這樣的數列進行排序 就會造成比較大的空間浪費了,所以我們只需要生成max-min+1長的輔助陣列就ok啦

直接上程式碼

 1     public static void countSort(int[] arr) {
 2 
 3         /* 獲取待排序數列的最大值,與最小值 用來建立輔助陣列 */
 4         int min=arr[0],max = arr[0];
 5         for (int i = 0; i < arr.length; i++) {
 6             if(arr[i]>max) {
 7                 max=arr[i];
 8             }
 9             if(arr[i]<min) {
10                 min=arr[i];
11             }
12         }
13         
14         /* 建立輔助陣列 先在輔助陣列儲存其對應下標在待排序數中出現的次數 (與countSort01同) */
15         int[] sortArr = new int[max + 1];
16         sortArr=new int[max-min+1];
17         for (int i = 0; i < arr.length; i++) {
18             sortArr[arr[i]-min]++;
19         }
20         /* 對輔助陣列進行改進 將其儲存的對應待排序數列的次數 轉換為臨時排序數列的下標位置 */
21         for (int i = 1; i < sortArr.length; i++) {
22             sortArr[i]+=sortArr[i-1];
23         }
24         System.out.println("輔助序列: "+Arrays.toString(sortArr));
25         
26         /* 建立臨時排序數列 */
27         int[] tsortArr=new int[arr.length] ;
28         for (int i = 0; i < arr.length; i++) {
29             tsortArr[--sortArr[arr[i]-min]]=arr[i];
30         }
31         System.out.println("排序數列: "+Arrays.toString(tsortArr));
32     }

 

 

測試:

 1     public static void main(String[] args) {
 2         
 3         System.out.println();
 4         System.out.println("----------引入-------------");
 5         Sort.countSort01(new int[] {6,10,6,8,6,8,12,15,9,6,7});
 6         System.out.println();
 7         System.out.println("----------改進-------------");
 8         Sort.countSort02(new int[] {6,10,6,8,6,8,12,15,9,6,7});
 9         System.out.println();
10         System.out.println("----------計數排序-------------");
11         Sort.countSort(new int[] {6,10,6,8,6,8,12,15,9,6,7});
12         
13     }

 

 

桶排序一

計數排序很快 但是卻有一些限制

1.計數排序只能對整數進行排序,

2.當數列之間比較鬆散,最大最小之差比較大時會浪費比較大的空間浪費

桶排序 (Bucket sort)或所謂的箱排序,是一個排序演算法,工作的原理是將陣列分到有限數量的桶裡。每個桶子再個別排序(有可能再使用別的排序演算法或是以遞迴方式繼續使用桶排序進行排序)。桶排序是鴿巢排序的一種歸納結果。當要被排序的陣列內的數值是均勻分配的時候,桶排序使用線性時間(Θ(n))--百度百科

即桶排序建立多個桶區間 將處在同一區間範圍的數放在一個桶裡,然後再單獨對每個桶中的數列進行排序 當然每個桶區間的範圍是多大就需要看情況了 當桶的區間特別小 每一個桶只儲存一個數 那麼其時間複雜度就與計數排序差不多,單無疑需要花費大量的空間 而設定單個桶的區間太大 比如所有序列都落入到一個桶裡,那麼 其時間複雜度只取決於對單個桶進行排序的演算法的複雜度了

 1     /**
 2      * 桶排序 將待排序數列 放入n個桶中 對於每個數列有
 3      * 最大值 max     最小值 min        桶數 n(最後一個桶只儲存最大值)        所以(1-(n-1))中每個桶的區間大小應為(max-min)/(n-1) 
 4      *               此時有==> 元素i入桶的序列號為:int t=(arr[i]-min)*(n-1)/(max-min)
 5      * @param arr 待排序數列
 6      * @param n  桶數
 7      */
 8     public static void buckSort(double[] arr,int n) {
 9         
10         /* 建立n個桶 使用ArrayList作桶 */
11         List<Double>[] bucks=new ArrayList[n];
12         for (int i = 0; i < bucks.length; i++) {
13             bucks[i]=new ArrayList<>();
14         }
15         
16         /* 獲取數列的最大值最小值 */
17         double max=arr[0],min= arr[0];
18         for (int i = 0; i < arr.length; i++) {
19             if(arr[i]<min) {
20                 min= arr[i];
21             }
22             else if(arr[i]>max) {
23                 max= arr[i];
24             }
25         }
26 
27         /* 將元素以此放入桶中 */
28         for (int i = 0; i < arr.length; i++) {
29             /* 計算元素該入哪個桶 */
30             int t=(int) ((arr[i]-min)*(n-1)/(max-min));
31             bucks[t].add(arr[i]);
32         }
33         /* 對每個桶單獨排序 */
34         for (int i = 0; i < bucks.length; i++) {
35             Collections.sort(bucks[i]);
36             System.out.println(bucks[i]);
37         }
38         
39         /* 將桶中元素 放回原陣列 */
40         for (int i = 0,k=0; i < bucks.length; i++) {
41             for (int j = 0; j < bucks[i].size(); j++) {
42                 arr[k++]=bucks[i].get(j);
43             }
44         }
45         System.out.println("排序數列: "+Arrays.toString(arr));
46     }

 

測試

1     
2     public static void main(String[] args) {
3         
4         buckSort(new double[] {1.25,3.22,0.56,5.35,1.24,5.22,4.23,4.22,3.56,3.88,4.23},5);
5         
6     }

 

桶排序二

上述桶排序用在一個固定區段的浮點數序列中,還是挺好的,但如果對[0-1)區間的序列進行排序,我們可以對其x10,使其變成一個[0-10)之間的無序數列,並將其按照個位數值,將其放入0-9的桶中

見程式碼

 1     public static void buckSort(double[] arr) {
 2         
 3         /* 建立10個桶 使用ArrayList作桶 */
 4         List<Double>[] bucks=new ArrayList[10];
 5         for (int i = 0; i < bucks.length; i++) {
 6             bucks[i]=new ArrayList<>();
 7         }
 8         
 9         /* 遍歷待排序數列 ,將其元素x10並放入相應的桶中 */
10         for (int i = 0; i < arr.length; i++) {
11             int t=(int) (arr[i]*10);
12             bucks[t].add(arr[i]);
13         }
14         
15         /* 對每個桶單獨排序 */
16         for (int i = 0; i < bucks.length; i++) {
17             Collections.sort(bucks[i]);
18             System.out.println("桶序列"+i+": "+bucks[i]);
19         }
20         
21         /* 將桶中元素 放回原陣列 */
22         for (int i = 0,k=0; i < bucks.length; i++) {
23             for (int j = 0; j < bucks[i].size(); j++) {
24                 arr[k++]=bucks[i].get(j);
25             }
26         }
27         System.out.println("排序數列: "+Arrays.toString(arr));
28 
29     }
30     

 

測試

    
    public static void main(String[] args) {

        buckSort(new double[] {0.25,0.33,0.11,0.26,0.65,0.84,0.96,0.44});
        
    }
    

 

 

基數排序

基數排序時根據基數不同 將不同的數分配到不同的桶中,(最低位優先)基數排序先對根據數列的個位數 將其放入0-9的二維陣列中 然後以此對十位數 百位數等進行相同操作 最後得到一個有序數列,當然最高位優先其思想也是一樣

程式碼

 1     public static void radixSort(int[] arr) {
 2         
 3         /* 建立一個10*arr.length的二維陣列 */
 4         int[][] duck=new int[10][arr.length];
 5         
 6         /* 先獲取最大值 */
 7         int max=arr[0];
 8         for (int i = 0; i < arr.length; i++) {
 9             if(arr[i]>max) {
10                 max=(int) (arr[i]+1);
11             }
12         }
13 
14         for (int i = 1; max>0 ; i*=10) {
15             /* 記錄每個桶的下標 */
16             int[] count=new int[10];
17             
18             for (int j = 0; j < arr.length; j++) {
19                 int t=(arr[j]/i)%10;
20                 duck[t][count[t]++]=arr[j];
21             }
22             /* 將桶中的數放回原陣列 等待下一位數的排序 */
23             for (int j = 0,c=0; j < 10; j++) {
24                 for (int k = 0; k < count[j]; k++) {
25                     arr[c++]=duck[j][k];
26                 }
27             }
28             
29             max/=i;
30         }
31         System.out.println(Arrays.toString(arr));
32         
33     }

 

測試

    public static void main(String[] args) {

        Sort.radixSort(new int[] {6,10,25,80,612,8,12,15,9,6,7});
        
    }

 

 


 

參考:

桶排序一:

漫畫:什麼是桶排序?


 如果有地方寫的錯誤,或者有什麼疑問與建議,歡迎大家提出來 願與大家一同進步