把這三個拿到一起來說,是因為這三種排序思想很像。
計數排序、基數排序、桶排序則屬於非比較排序。非比較排序是通過確定每個元素之前,應該有多少個元素來排序。針對數組arr,計算arr[i]之前有多少個元素,則唯一確定了arr[i]在排序后數組中的位置。
非比較排序只要確定每個元素之前的已有的元素個數即可,所有一次遍歷即可解決。算法時間復雜度O(n)。
非比較排序時間復雜度底,但由於非比較排序需要占用空間來確定唯一位置。所以對數據規模和數據分布有一定的要求。
(這里再說一下其他排序)
常見的快速排序、歸並排序、堆排序、冒泡排序等屬於比較排序。在排序的最終結果里,元素之間的次序依賴於它們之間的比較。每個數都必須和其他數進行比較,才能確定自己的位置。
在冒泡排序之類的排序中,問題規模為n,又因為需要比較n次,所以平均時間復雜度為O(n²)。在歸並排序、快速排序之類的排序中,問題規模通過分治法消減為logN次,所以時間復雜度平均O(nlogn)。
比較排序的優勢是,適用於各種規模的數據,也不在乎數據的分布,都能進行排序。可以說,比較排序適用於一切需要排序的情況。
1.計數排序:
計數排序需要占用大量空間,它僅適用於數據比較集中的情況。比如 [0~100],[10000~19999] 這樣的數據。
計數排序的基本思想是:對每一個輸入的元素arr[i],確定小於 arr[i] 的元素個數
。
所以可以直接把 arr[i] 放到它輸出數組中的位置上。假設有5個數小於 arr[i],所以 arr[i] 應該放在數組的第6個位置上。
過程:
待排序數組 int[] arr = new int[]{4,3,6,3,5,1};
輔助計數數組 int[] help = new int[max - min + 1]; //該數組大小為待排序數組中的最大值減最小值+1
輸出數組 int[] res = new int[arr.length];
1.求出待排序數組的最大值max=6, 最小值min=1
2.實例化輔助計數數組help,help數組中每個下標對應arr中的一個元素,help用來記錄每個元素出現的次數
3.計算 arr 中每個元素在help中的位置 position = arr[i] - min,此時 help = [1,0,2,1,1,1]; (3出現了兩次,2未出現)
4.根據 help 數組求得排序后的數組,此時 res = [1,3,3,4,5,6]
1 public static int[] countSort1(int[] arr){ 2 if (arr == null || arr.length == 0) { 3 return null; 4 } 5 6 int max = Integer.MIN_VALUE; 7 int min = Integer.MAX_VALUE; 8 9 //找出數組中的最大最小值 10 for(int i = 0; i < arr.length; i++){ 11 max = Math.max(max, arr[i]); 12 min = Math.min(min, arr[i]); 13 } 14 15 int help[] = new int[max]; 16 17 //找出每個數字出現的次數 18 for(int i = 0; i < arr.length; i++){ 19 int mapPos = arr[i] - min; 20 help[mapPos]++; 21 } 22 23 int index = 0; 24 for(int i = 0; i < help.length; i++){ 25 while(help[i]-- > 0){ 26 arr[index++] = i+min; 27 } 28 } 29 30 return arr; 31 }
另一種實現:
需要三個數組:
待排序數組 int[] arr = new int[]{4,3,6,3,5,1};
輔助計數數組 int[] help = new int[max - min + 1]; //該數組大小為待排序數組中的最大值減最小值+1
輸出數組 int[] res = new int[arr.length];
1.求出待排序數組的最大值max=6, 最小值min=1
2.實例化輔助計數數組help,help用來記錄每個元素之前出現的元素個數
3.計算 arr 每個數字應該在排序后數組中應該處於的位置,此時 help = [1,1,3,4,5,6];
4.根據 help 數組求得排序后的數組,此時 res = [1,3,3,4,5,6]
1 public static int[] countSort2(int[] arr){ 2 int max = Integer.MIN_VALUE; 3 int min = Integer.MAX_VALUE; 4 5 //找出數組中的最大最小值 6 for(int i = 0; i < arr.length; i++){ 7 max = Math.max(max, arr[i]); 8 min = Math.min(min, arr[i]); 9 } 10 11 int[] help = new int[max - min + 1]; 12 13 //找出每個數字出現的次數 14 for(int i = 0; i < arr.length; i++){ 15 int mapPos = arr[i] - min; 16 help[mapPos]++; 17 } 18 19 //計算每個數字應該在排序后數組中應該處於的位置 20 for(int i = 1; i < help.length; i++){ 21 help[i] = help[i-1] + help[i]; 22 } 23 24 //根據help數組進行排序 25 int res[] = new int[arr.length]; 26 for(int i = 0; i < arr.length; i++){ 27 int post = --help[arr[i] - min]; 28 res[post] = arr[i]; 29 } 30 31 return res; 32 }
2.桶排序
桶排序可用於最大最小值相差較大的數據情況,比如[9012,19702,39867,68957,83556,102456]。
但桶排序要求數據的分布必須均勻,否則可能導致數據都集中到一個桶中。比如[104,150,123,132,20000], 這種數據會導致前4個數都集中到同一個桶中。導致桶排序失效。
桶排序的基本思想是:把數組 arr 划分為n個大小相同子區間(桶),每個子區間各自排序,最后合並
。
計數排序是桶排序的一種特殊情況,可以把計數排序當成每個桶里只有一個元素的情況。
1.找出待排序數組中的最大值max、最小值min
2.我們使用 動態數組ArrayList 作為桶,桶里放的元素也用 ArrayList 存儲。桶的數量為(max-min)/arr.length+1
3.遍歷數組 arr,計算每個元素 arr[i] 放的桶
4.每個桶各自排序
5.遍歷桶數組,把排序好的元素放進輸出數組
1 public static void bucketSort(int[] arr){ 2 3 int max = Integer.MIN_VALUE; 4 int min = Integer.MAX_VALUE; 5 for(int i = 0; i < arr.length; i++){ 6 max = Math.max(max, arr[i]); 7 min = Math.min(min, arr[i]); 8 } 9 10 //桶數 11 int bucketNum = (max - min) / arr.length + 1; 12 ArrayList<ArrayList<Integer>> bucketArr = new ArrayList<>(bucketNum); 13 for(int i = 0; i < bucketNum; i++){ 14 bucketArr.add(new ArrayList<Integer>()); 15 } 16 17 //將每個元素放入桶 18 for(int i = 0; i < arr.length; i++){ 19 int num = (arr[i] - min) / (arr.length); 20 bucketArr.get(num).add(arr[i]); 21 } 22 23 //對每個桶進行排序 24 for(int i = 0; i < bucketArr.size(); i++){ 25 Collections.sort(bucketArr.get(i)); 26 } 27 28 System.out.println(bucketArr.toString()); 29 30 }
3.基數排序
基數排序已經不再是一種常規的排序方式,它更多地像一種排序方法的應用,基數排序必須依賴於另外的排序方法。基數排序的總體思路就是將待排序數據拆分成多個關鍵字進行排序,也就是說,基數排序的實質是多關鍵字排序。
如果按照習慣思維,會先比較百位,百位大的數據大,百位相同的再比較十位,十位大的數據大;最后再比較個位。人得習慣思維是最高位優先方式。但一旦這樣,當開始比較十位時,程序還需要判斷它們的百位是否相同--這就認為地增加了難度,計算機通常會選擇最低位優先法。
基數排序方法對任一子關鍵字排序時必須借助於另一種排序方法,而且這種排序方法必須是穩定的。對於多關鍵字拆分出來的子關鍵字,它們一定位於0-9這個可枚舉的范圍內,這個范圍不大,因此用桶式排序效率非常好。對於多關鍵字排序來說,程序將待排數據拆分成多個子關鍵字后,對子關鍵字排序既可以使用桶式排序,也可以使用任何一種穩定的排序方法。
1 import java.util.Arrays; 2 3 public class MultiKeyRadixSortTest { 4 5 public static void main(String[] args) { 6 int[] data = new int[] { 1100, 192, 221, 12, 23 }; 7 print(data); 8 radixSort(data, 10, 4); 9 System.out.println("排序后的數組:"); 10 print(data); 11 } 12 13 public static void radixSort(int[] data, int radix, int d) { 14 // 緩存數組 15 int[] tmp = new int[data.length]; 16 // buckets用於記錄待排序元素的信息 17 // buckets數組定義了max-min個桶 18 int[] buckets = new int[radix]; 19 20 for (int i = 0, rate = 1; i < d; i++) { 21 22 // 重置count數組,開始統計下一個關鍵字 23 Arrays.fill(buckets, 0); 24 // 將data中的元素完全復制到tmp數組中 25 System.arraycopy(data, 0, tmp, 0, data.length); 26 27 // 計算每個待排序數據的子關鍵字 28 for (int j = 0; j < data.length; j++) { 29 int subKey = (tmp[j] / rate) % radix; 30 buckets[subKey]++; 31 } 32 33 for (int j = 1; j < radix; j++) { 34 buckets[j] = buckets[j] + buckets[j - 1]; 35 } 36 37 // 按子關鍵字對指定的數據進行排序 38 for (int m = data.length - 1; m >= 0; m--) { 39 int subKey = (tmp[m] / rate) % radix; 40 data[--buckets[subKey]] = tmp[m]; 41 } 42 rate *= radix; 43 } 44 45 } 46 47 public static void print(int[] data) { 48 for (int i = 0; i < data.length; i++) { 49 System.out.print(data[i] + "\t"); 50 } 51 System.out.println(); 52 } 53 54 }