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}); }
參考:
桶排序一:
漫畫:什麽是桶排序?
如果有地方寫的錯誤,或者有什麽疑問與建議,歡迎大家提出來 願與大家一同進步
java-數組排序--計數排序、桶排序、基數排序