前言
聲明:參考來源互聯網,有任何爭議可以留言。站在前人的肩上,我們才能看的更遠。
本教程純手打,致力於最實用教程,不需要什麼獎勵,只希望多多轉發支持。
歡迎來我公衆號,希望可以結識你,也可以催更,微信搜索:JavaPub
有任何問題都可以來談談 !
如果看上一篇**計數排序,你有沒有這樣疑問,當每個數據之間跨度過大(如從 0-2億 數字中排序 20 個數),就需要大量空間消耗。桶排序就是對計數排序**的改進。
1.桶排序(Bucket sort)
百度百科
:
桶排序 (Bucket sort)或所謂的箱排序,是一個排序算法,工作的原理是將數組分到有限數量的桶子裏。每個桶子再個別排序(有可能再使用別的排序算法或是以遞歸方式繼續使用桶排序進行排序)。桶排序是 鴿巢排序 的一種歸納結果。當要被排序的數組內的數值是均勻分配的時候,桶排序使用線性時間(Θ(n))。但桶排序並不是 比較排序,他不受到 O(n log n) 下限的影響。
繼續 -->
桶排序是**計數排序的升級版。它利用了函數的映射關係**,高效與否的關鍵就在於這個映射函數的確定。爲了使桶排序更加高效,我們需要做到這兩點:
- 在額外空間充足的情況下,儘量增大桶的數量
- 使用的映射函數能夠將輸入的 N 個數據均勻的分配到 K 個桶中
同時,對於桶中元素的排序,選擇何種比較排序算法對於性能的影響至關重要。
桶排序是將待排序集合中處於同一個值域的元素存入同一個桶中,也就是根據元素值特性將集合拆分爲多個區域,則拆分後形成的多個桶,從值域上看是處於有序狀態的。對每個桶中元素進行排序,則所有桶中元素構成的集合是已排序的。
快速排序是將集合拆分爲兩個值域,這裏稱爲兩個桶,再分別對兩個桶進行排序,最終完成排序。桶排序則是將集合拆分爲多個桶,對每個桶進行排序,則完成排序過程。兩者不同之處在於,快排是在集合本身上進行排序,屬於原地排序方式,且對每個桶的排序方式也是快排。桶排序則是提供了額外的操作空間,在額外空間上對桶進行排序,避免了構成桶過程的元素比較和交換操作,同時可以自主選擇恰當的排序算法對桶進行排序。
2.原理
2.1.關鍵
-
元素值域的劃分,也就是元素到桶的映射規則。映射規則需要根據待排序集合的元素分佈特性進行選擇,若規則設計的過於模糊、寬泛,則可能導致待排序集合中所有元素全部映射到一個桶上,則桶排序向比較性質排序算法演變。若映射規則設計的過於具體、嚴苛,則可能導致待排序集合中每一個元素值映射到一個桶上,則桶排序向計數排序方式演化。
-
排序算法的選擇,從待排序集合中元素映射到各個桶上的過程,並不存在元素的比較和交換操作,在對各個桶中元素進行排序時,可以自主選擇合適的排序算法,桶排序算法的複雜度和穩定性,都根據選擇的排序算法不同而不同。
2.2.算法過程
- 根據待排序集合中最大元素和最小元素的差值範圍和映射規則,確定申請的桶個數;
- 遍歷待排序集合,將每一個元素移動到對應的桶中;
- 對每一個桶中元素進行排序,並移動到已排序集合中。
步驟 3 中提到的已排序集合,和步驟 1、2 中的待排序集合是同一個集合。與計數排序不同,桶排序的步驟 2 完成之後,所有元素都處於桶中,並且對桶中元素排序後,移動元素過程中不再依賴原始集合,所以可以將桶中元素移動回原始集合即可。
- 示意圖
元素分配到不同桶中:
然後,元素在每個桶中排序:
3.代碼
基於 Java 的代碼,代碼邏輯很好理解,使用到插入排序,如果不理解,點擊傳送。
package utils;
import java.util.Arrays;
/**
* @author wangshiyu rodert
* @date 2020/6/21 15:13
* @description
*/
public class BucketSort {
public static void main(String[] args) throws Exception {
int[] array = {2, 1, 5, 3, 4};
BucketSort bucketSort = new BucketSort();
int[] sort = bucketSort.sort(array);
System.out.println(Arrays.toString(sort));
}
private static final InsertSort insertSort = new InsertSort();
public int[] sort(int[] sourceArray) throws Exception {
// 對 arr 進行拷貝,不改變參數內容
int[] arr = Arrays.copyOf(sourceArray, sourceArray.length);
return bucketSort(arr, 5);
}
private int[] bucketSort(int[] arr, int bucketSize) throws Exception {
if (arr.length == 0) {
return arr;
}
int minValue = arr[0];
int maxValue = arr[0];
for (int value : arr) {
if (value < minValue) {
minValue = value;
} else if (value > maxValue) {
maxValue = value;
}
}
int bucketCount = (int) Math.floor((maxValue - minValue) / bucketSize) + 1;//向下取整 + 1
int[][] buckets = new int[bucketCount][0];
// 利用映射函數將數據分配到各個桶中
for (int i = 0; i < arr.length; i++) {
int index = (int) Math.floor((arr[i] - minValue) / bucketSize);
buckets[index] = arrAppend(buckets[index], arr[i]);
}
int arrIndex = 0;
for (int[] bucket : buckets) {
if (bucket.length <= 0) {
continue;
}
// 對每個桶進行排序,這裏使用了插入排序
bucket = insertSort.sort(bucket);
for (int value : bucket) {
arr[arrIndex++] = value;
}
}
return arr;
}
/**
* 自動擴容,並保存數據
*
* @param arr
* @param value
*/
private int[] arrAppend(int[] arr, int value) {
arr = Arrays.copyOf(arr, arr.length + 1);
arr[arr.length - 1] = value;
return arr;
}
}
class InsertSort {
//插入排序
public int[] sort(int[] sourceArray) throws Exception {
// 對 arr 進行拷貝,不改變參數內容
int[] arr = Arrays.copyOf(sourceArray, sourceArray.length);
// 從下標爲1的元素開始選擇合適的位置插入,因爲下標爲0的只有一個元素,默認是有序的
for (int i = 1; i < arr.length; i++) {
// 記錄要插入的數據
int tmp = arr[i];
// 從已經排序的序列最右邊的開始比較,找到比其小的數
int j = i;
while (j > 0 && tmp < arr[j - 1]) {
arr[j] = arr[j - 1];
j--;
}
// 存在比其小的數,插入
if (j != i) {
arr[j] = tmp;
}
}
return arr;
}
}
返回結果:
[1, 2, 3, 4, 5]
Arrays.copyOf()
方法理解:用於複製指定的數組內容以達到擴容的目的,該方法對不同的基本數據類型都有對應的重載方法。
4.擴展閱讀
真題:347. Top K Frequent Elements (Medium),給定一個非空的整數數組,返回其中出現頻率前 k 高的元素。
Given a non-empty array of integers, return the k most frequent elements.
- 題解:
//基於桶排序求解「前 K 個高頻元素」
class Solution {
public List<Integer> topKFrequent(int[] nums, int k) {
List<Integer> res = new ArrayList();
// 使用字典,統計每個元素出現的次數,元素爲鍵,元素出現的次數爲值
HashMap<Integer,Integer> map = new HashMap();
for(int num : nums){
if (map.containsKey(num)) {
map.put(num, map.get(num) + 1);
} else {
map.put(num, 1);
}
}
//桶排序
//將頻率作爲數組下標,對於出現頻率不同的數字集合,存入對應的數組下標
List<Integer>[] list = new List[nums.length+1];
for(int key : map.keySet()){
// 獲取出現的次數作爲下標
int i = map.get(key);
if(list[i] == null){
list[i] = new ArrayList();
}
list[i].add(key);
}
// 倒序遍歷數組獲取出現順序從大到小的排列
for(int i = list.length - 1;i >= 0 && res.size() < k;i--){
if(list[i] == null) continue;
res.addAll(list[i]);
}
return res;
}
}