桶排序及其應用
- 2019 年 11 月 6 日
- 筆記

一、思想
一句話總結:
劃分多個範圍相同的區間,每個自區間自排序,最後合併。
桶排序是計數排序的擴展版本,計數排序可以看成每個桶只存儲相同元素,而桶排序每個桶存儲一定範圍的元素,通過映射函數,將待排序數組中的元素映射到各個對應的桶中,對每個桶中的元素進行排序,最後將非空桶中的元素逐個放入原序列中。
桶排序需要盡量保證元素分散均勻,否則當所有數據集中在同一個桶中時,桶排序失效。
二、圖解過程

三、核心代碼
public static void bucketSort(int[] arr){ // 計算最大值與最小值 int max = Integer.MIN_VALUE; int min = Integer.MAX_VALUE; for(int i = 0; i < arr.length; i++){ max = Math.max(max, arr[i]); min = Math.min(min, arr[i]); } // 計算桶的數量 int bucketNum = (max - min) / arr.length + 1; ArrayList<ArrayList<Integer>> bucketArr = new ArrayList<>(bucketNum); for(int i = 0; i < bucketNum; i++){ bucketArr.add(new ArrayList<Integer>()); } // 將每個元素放入桶 for(int i = 0; i < arr.length; i++){ int num = (arr[i] - min) / (arr.length); bucketArr.get(num).add(arr[i]); } // 對每個桶進行排序 for(int i = 0; i < bucketArr.size(); i++){ Collections.sort(bucketArr.get(i)); } // 將桶中的元素賦值到原序列 int index = 0; for(int i = 0; i < bucketArr.size(); i++){ for(int j = 0; j < bucketArr.get(i).size(); j++){ arr[index++] = bucketArr.get(i).get(j); } } }
四、複雜度分析
時間複雜度:O(N + C) 對於待排序序列大小為 N,共分為 M 個桶,主要步驟有:
N 次循環,將每個元素裝入對應的桶中
M 次循環,對每個桶中的數據進行排序(平均每個桶有 N/M 個元素)
一般使用較為快速的排序算法,時間複雜度為 O(NlogN)O(NlogN)O(NlogN),實際的桶排序過程是以鏈表形式插入的。
整個桶排序的時間複雜度為:
O(N)+O(M∗(N/M∗log(N/M)))=O(N∗(log(N/M)+1))O(N)+O(M*(N/M*log(N/M)))=O(N*(log(N/M)+1))O(N)+O(M∗(N/M∗log(N/M)))=O(N∗(log(N/M)+1))
當 N = M 時,複雜度為 O(N)O(N)O(N)
額外空間複雜度:O(N + M)
五、穩定性分析
桶排序的穩定性取決於桶內排序使用的算法。
六、實際案例
案例一:
一年的全國高考考生人數為500 萬,分數使用標準分,最低100 ,最高900 ,沒有小數,要求對這500 萬元素的數組進行排序。
分析:對500W數據排序,如果基於比較的先進排序,平均比較次數為O(5000000*log5000000)≈1.112億。但是我們發現,這些數據都有特殊的條件: 100=<score<=900。那麼我們就可以考慮桶排序這樣一個「投機取巧」的辦法、讓其在毫秒級別就完成500萬排序。
方法:創建801(900-100)個桶。將每個考生的分數丟進f(score)=score-100的桶中。這個過程從頭到尾遍歷一遍數據只需要500W次。然後根據桶號大小依次將桶中數值輸出,即可以得到一個有序的序列。而且可以很容易的得到100分有***人,501分有***人。
實際上,桶排序對數據的條件有特殊要求,如果上面的分數不是從100-900,而是從0-2億,那麼分配2億個桶顯然是不可能的。所以桶排序有其局限性,適合元素值集合併不大的情況。
案例二:
在一個文件中有 10G 個整數,亂序排列,要求找出中位數。
內存限制為 2G。只寫出思路即可(內存限制為 2G的意思就是,可以使用2G的空間來運行程序,而不考慮這台機器上的其他軟件的佔用內存)。這問題可以使用桶排序,當然還有其他更好的方案,下次再講。