桶排序
算法定义
桶排序(Bucket Sort)是一种分布式排序算法,它将待排序数据分配到多个有序的 "桶" 中,对每个桶内的元素单独排序(通常使用其他排序算法),最后将所有桶中的元素按顺序合并,得到最终的有序数组。
桶排序的核心思想是利用数据的分布特性,通过映射函数将数据均匀分配到不同桶中,从而减少单个桶内的排序开销。
算法思路
桶排序的步骤如下:
- 创建桶:根据数据范围和分布,创建一定数量的空桶(通常是数组或链表)。
- 分配元素:遍历待排序数组,根据映射函数将每个元素放入对应的桶中。
- 桶内排序:对每个非空桶内的元素进行排序(可使用插入排序、快速排序等)。
- 合并结果:按桶的顺序依次将所有桶中的元素取出,拼接成有序数组。
例如,排序[0.897, 0.565, 0.656, 0.1234, 0.665, 0.3434]:
- 创建 10 个桶(索引 0-9)
- 按
num * 10的整数部分分配:0.897→8 号桶,0.565→5 号桶等 - 对每个桶内元素排序后合并:
[0.1234, 0.3434, 0.565, 0.656, 0.665, 0.897]
算法复杂度
- 最好时间复杂度:O (n + k) - n 为元素总数,k 为桶数,当数据均匀分布且桶内无需排序时
- 最坏时间复杂度:O (n²) - 所有元素落入同一桶,且使用 O (n²) 排序算法时
- 平均时间复杂度:O (n + k + n log (n/k)) - 假设数据均匀分布,每个桶有 n/k 个元素
- 空间复杂度:O (n + k) - 需要存储 n 个元素和 k 个桶
优点与缺点
- 优点:
- 当数据分布均匀时,效率接近线性时间 O (n)
- 可并行化处理(每个桶可独立排序)
- 适合处理大规模数据
- 对浮点数排序友好
- 缺点:
- 对数据分布敏感,分布不均时效率大幅下降
- 需要额外空间存储桶
- 不适合处理值域范围极大的数据
- 实现较复杂,需设计合适的映射函数
- 桶排序的复杂度是值域相关的。当值域很大并且分布不均匀时,桶排序需要增加轮数,效率随之降低。
- 桶排序不是比较排序,对于一些需要比较才能完成排序的问题(如精确的分数比较),桶排序就不能实现。
所以这会有由桶排序所进化成的基数排序
应用场景
- 数据分布均匀的场景(如成绩、身高、体重等)
- 浮点数排序(如 0-1 区间的小数)
- 大规模数据的外部排序(结合磁盘存储)
- 数据库系统中的范围查询优化
- 哈希表中的数据排序
各版本优化说明
- 基本版(浮点数):
- 处理 0-1 区间的浮点数,映射函数简单(
num * n) - 使用 ArrayList 作为桶,实现简单直观
- 适合演示桶排序的基本原理
- 处理 0-1 区间的浮点数,映射函数简单(
- 整数优化版:
- 自动计算数据范围,动态确定桶数量和每个桶的范围
- 对小规模桶使用插入排序,大规模桶使用快速排序
- 桶数量通常设为√n,平衡时间和空间效率
- 链表桶优化:
- 使用 LinkedList 代替 ArrayList 作为桶,减少动态扩容开销
- 适合数据分布不均匀、桶大小波动大的场景
- 利用链表的特性优化插入操作
- 并行桶排序:
- 按 CPU 核心数设置桶数量,支持多线程并行排序
- 大幅提升大规模数据的排序效率
- 适合多核处理器环境,充分利用硬件资源
桶排序是一种高效的分布式排序算法,其性能高度依赖数据分布。在实际应用中,合理设计映射函数和选择桶内排序算法是发挥其优势的关键。当数据均匀分布时,桶排序可以达到接近线性的时间复杂度,是处理大规模数据的理想选择。
代码
import java.util.*;
@SuppressWarnings("unchecked") // 抑制泛型数组相关的未检查警告
public class BucketSort {
// 1. 基本版桶排序(处理0-1区间的浮点数)
public static void bucketSortBasic(double[] arr) {
if (arr == null || arr.length <= 1) {
return;
}
int n = arr.length;
// 创建n个桶,添加注解抑制警告
List<Double>[] buckets = new ArrayList[n];
for (int i = 0; i < n; i++) {
buckets[i] = new ArrayList<>();
}
// 将元素分配到桶中
for (double num : arr) {
int bucketIndex = (int) (num * n); // 映射函数:num * n
buckets[bucketIndex].add(num);
}
// 对每个桶排序并合并结果
int index = 0;
for (List<Double> bucket : buckets) {
if (!bucket.isEmpty()) {
Collections.sort(bucket);
for (double num : bucket) {
arr[index++] = num;
}
}
}
}
// 2. 优化版1:处理整数的桶排序
public static void bucketSortIntegers(int[] arr) {
if (arr == null || arr.length <= 1) {
return;
}
// 找出最大值和最小值,确定桶的范围
int min = arr[0], max = arr[0];
for (int num : arr) {
if (num < min) min = num;
if (num > max) max = num;
}
// 计算桶的数量和每个桶的范围
int bucketCount = (int) Math.sqrt(arr.length) + 1;
int range = (max - min) / bucketCount + 1;
// 创建桶
List<Integer>[] buckets = new ArrayList[bucketCount];
for (int i = 0; i < bucketCount; i++) {
buckets[i] = new ArrayList<>();
}
// 分配元素到桶
for (int num : arr) {
int bucketIndex = (num - min) / range;
buckets[bucketIndex].add(num);
}
// 桶内排序并合并
int index = 0;
for (List<Integer> bucket : buckets) {
if (!bucket.isEmpty()) {
if (bucket.size() <= 15) {
insertionSort(bucket);
} else {
Collections.sort(bucket);
}
for (int num : bucket) {
arr[index++] = num;
}
}
}
}
// 3. 优化版2:使用链表桶和自定义排序(减少扩容开销)
public static void bucketSortLinkedList(int[] arr) {
if (arr == null || arr.length <= 1) {
return;
}
int min = arr[0], max = arr[0];
for (int num : arr) {
if (num < min) min = num;
if (num > max) max = num;
}
// 修复1:合理计算桶数量(参考其他优化版)
int bucketCount = (int) Math.sqrt(arr.length) + 1;
// 修复2:计算每个桶的范围,避免索引越界
int range = (max - min) / bucketCount + 1;
// 使用链表作为桶
LinkedList<Integer>[] buckets = new LinkedList[bucketCount];
for (int i = 0; i < bucketCount; i++) {
buckets[i] = new LinkedList<>();
}
// 分配元素(修复3:使用range计算索引)
for (int num : arr) {
int bucketIndex = (num - min) / range;
buckets[bucketIndex].add(num);
}
// 合并结果
int index = 0;
for (LinkedList<Integer> bucket : buckets) {
if (!bucket.isEmpty()) {
bucket.sort(null);
for (int num : bucket) {
arr[index++] = num;
}
}
}
}
// 4. 优化版3:并行桶排序(多线程处理)
public static void bucketSortParallel(int[] arr) throws InterruptedException {
if (arr == null || arr.length <= 1) {
return;
}
int min = arr[0], max = arr[0];
for (int num : arr) {
if (num < min) min = num;
if (num > max) max = num;
}
int bucketCount = Runtime.getRuntime().availableProcessors();
int range = (max - min) / bucketCount + 1;
List<Integer>[] buckets = new ArrayList[bucketCount];
for (int i = 0; i < bucketCount; i++) {
buckets[i] = new ArrayList<>();
}
// 分配元素
for (int num : arr) {
int bucketIndex = (num - min) / range;
buckets[bucketIndex].add(num);
}
// 多线程并行排序
Thread[] threads = new Thread[bucketCount];
for (int i = 0; i < bucketCount; i++) {
final int bucketIdx = i;
threads[i] = new Thread(() -> {
if (!buckets[bucketIdx].isEmpty()) {
Collections.sort(buckets[bucketIdx]);
}
});
threads[i].start();
}
// 等待所有线程完成
for (Thread thread : threads) {
thread.join();
}
// 合并结果
int index = 0;
for (List<Integer> bucket : buckets) {
for (int num : bucket) {
arr[index++] = num;
}
}
}
// 辅助方法:对List进行插入排序
private static void insertionSort(List<Integer> list) {
for (int i = 1; i < list.size(); i++) {
int key = list.get(i);
int j = i - 1;
while (j >= 0 && list.get(j) > key) {
list.set(j + 1, list.get(j));
j--;
}
list.set(j + 1, key);
}
}
public static void main(String[] args) throws InterruptedException {
double[] arr1 = {0.897, 0.565, 0.656, 0.1234, 0.665, 0.3434};
int[] arr2 = {43, 32, 22, 78, 63, 57, 91, 13, 25, 87};
int[] arr3 = {43, 32, 22, 78, 63, 57, 91, 13, 25, 87};
int[] arr4 = {43, 32, 22, 78, 63, 57, 91, 13, 25, 87};
bucketSortBasic(arr1);
System.out.println("基本版(浮点数): " + Arrays.toString(arr1));
bucketSortIntegers(arr2);
System.out.println("整数优化版: " + Arrays.toString(arr2));
bucketSortLinkedList(arr3);
System.out.println("链表桶优化: " + Arrays.toString(arr3));
bucketSortParallel(arr4);
System.out.println("并行桶排序: " + Arrays.toString(arr4));
}
}
代码2
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
/**
* 桶排序
*/
public class Bucket {
public static void main(String[] args) {
int[] nums = SortUtils.arrays();
bucketSortExam(nums);
System.out.println("桶排序:" + Arrays.toString(nums));
}
private static void bucketSort(int[] nums) {
int INTERVAL = 100; // 定义桶的大小
int min = Integer.MAX_VALUE;
int max = Integer.MIN_VALUE;
for (int num : nums) { // 找到数组元素的范围
min = Math.min(min, num);
max = Math.max(max, num);
}
int count = (max - min + 1); // 计算出桶的数量
int bucketSize = (count % INTERVAL == 0) ? (count / INTERVAL) : (count / INTERVAL + 1);
List<Integer>[] buckets = new List[bucketSize];
for (int num : nums) { // 把所有元素放入对应的桶里面
int quotient = (num - min) / INTERVAL;
if (buckets[quotient] == null) buckets[quotient] = new ArrayList<>();
buckets[quotient].add(num);
}
int cur = 0;
for (List<Integer> bucket : buckets) {
if (bucket != null) {
bucket.sort(null); // 对每个桶进行排序
for (Integer integer : bucket) { // 还原桶里面的元素到原数组
nums[cur++] = integer;
}
}
}
}
public static void bucketSort2(int[] arr) {
//最大最小值
int max = arr[0];
int min = arr[0];
int length = arr.length;
for (int i = 1; i < length; i++) {
if (arr[i] > max) {
max = arr[i];
} else if (arr[i] < min) {
min = arr[i];
}
}
//桶列表
ArrayList<ArrayList<Integer>> bucketList = new ArrayList<>();
for (int i = 0; i < length; i++) {
bucketList.add(new ArrayList<>());
}
//每个桶的存数区间
float section = (float) (max - min) / (float) (length - 1);
//数据入桶
for (int i = 0; i < length; i++) {
//当前数除以区间得出存放桶的位置 减1后得出桶的下标
int num = (int) (arr[i] / section) - 1;
if (num < 0) {
num = 0;
}
bucketList.get(num).add(arr[i]);
}
//桶内排序
for (int i = 0; i < bucketList.size(); i++) {
//jdk的排序速度当然信得过
Collections.sort(bucketList.get(i));
}
//写入原数组
int index = 0;
for (ArrayList<Integer> arrayList : bucketList) {
for (int value : arrayList) {
arr[index] = value;
index++;
}
}
}
// https://www.cxyxiaowu.com/587.html
// 待处理
int min = Integer.MAX_VALUE;
int max = Integer.MIN_VALUE;
public int maximumGap(int[] nums) {
if (nums == null || nums.length < 2) {
return 0;
}
int min = Integer.MAX_VALUE;
int max = Integer.MIN_VALUE;
for (int i : nums) {
min = Math.min(min, i);
max = Math.max(max, i);
}
// 分配桶的长度和个数是桶排序的关键
// 在 n 个数下,形成的两两相邻区间是 n - 1 个,比如 [2,4,6,8] 这里
// 有 4 个数,但是只有 3 个区间,[2,4], [4,6], [6,8]
// 因此,桶长度 = 区间总长度 / 区间总个数 = (max - min) / (nums.length - 1)
int bucketSize = Math.max(1, (max - min) / (nums.length - 1));
// 上面得到了桶的长度,我们就可以以此来确定桶的个数
// 桶个数 = 区间长度 / 桶长度
// 这里考虑到实现的方便,多加了一个桶,为什么?
// 还是举上面的例子,[2,4,6,8], 桶的长度 = (8 - 2) / (4 - 1) = 2
// 桶的个数 = (8 - 2) / 2 = 3
// 已知一个元素,需要定位到桶的时候,一般是 (当前元素 - 最小值) / 桶长度
// 这里其实利用了整数除不尽向下取整的性质
// 但是上面的例子,如果当前元素是 8 的话 (8 - 2) / 2 = 3,对应到 3 号桶
// 如果当前元素是 2 的话 (2 - 2) / 2 = 0,对应到 0 号桶
// 你会发现我们有 0,1,2,3 号桶,实际用到的桶是 4 个,而不是 3 个
// 透过例子应该很好理解,但是如果要说根本原因,其实是开闭区间的问题
// 这里其实 0,1,2 号桶对应的区间是 [2,4),[4,6),[6,8)
// 那 8 怎么办?多加一个桶呗,3 号桶对应区间 [8,10)
Bucket[] buckets = new Bucket[(max - min) / bucketSize + 1];
for (int i = 0; i < nums.length; ++i) {
int loc = (nums[i] - min) / bucketSize;
if (buckets[loc] == null) {
buckets[loc] = new Bucket();
}
buckets[loc].min = Math.min(buckets[loc].min, nums[i]);
buckets[loc].max = Math.max(buckets[loc].max, nums[i]);
}
int previousMax = Integer.MAX_VALUE;
int maxGap = Integer.MIN_VALUE;
for (int i = 0; i < buckets.length; ++i) {
if (buckets[i] != null && previousMax != Integer.MAX_VALUE) {
maxGap = Math.max(maxGap, buckets[i].min - previousMax);
}
if (buckets[i] != null) {
previousMax = buckets[i].max;
maxGap = Math.max(maxGap, buckets[i].max - buckets[i].min);
}
}
return maxGap;
}
// ===练习===
public static void bucketSortExam(int[] nums) {
int INTERVAL = 100;
int min = Integer.MIN_VALUE;
int max = Integer.MAX_VALUE;
for (int num : nums) {
min = Math.min(num, min);
max = Math.max(num, max);
}
// 计算桶数
int count = max - min + 1;
int bucketSize = count / INTERVAL == 0 ? count / INTERVAL : count / INTERVAL + 1;
List<Integer>[] buckets = new List[bucketSize];
for (int num : nums) {
int quotient = (num - min) / INTERVAL;
if (buckets[quotient] == null) buckets[quotient] = new ArrayList<>();
buckets[quotient].add(num);
}
int cur = 0;
for (List<Integer> bucket : buckets) {
if (bucket != null) {
// 对每个桶进行排序
bucket.sort(null);
// 还原桶里面的元素到原数组
for (Integer integer : bucket) {
nums[cur++] = integer;
}
}
}
}
}
资料
https://www.cxyxiaowu.com/587.html