每日算法 — 使用java实现桶排序:均匀分布数据的分桶策略与局部排序合并

算法概述

桶排序(Bucket Sort)是一种分布式排序算法,由E. J. Isaac和R. C. Singleton于1956年提出。它的工作原理是:

  1. 分桶:将待排序数组划分为若干个”桶”
  2. 分配:将每个元素根据其值分配到对应的桶中
  3. 局部排序:对每个非空桶内部进行排序(通常使用插入排序或快速排序)
  4. 合并:按顺序遍历所有桶,将元素依次取出

当输入数据均匀分布时,每个桶内的元素数量大致相同,此时桶排序的平均时间复杂度为O(n)。

核心思想

假设数据均匀分布在区间[min, max]内,将其划分为k个等宽区间(桶)。对于元素x,其所属桶的索引为:

bucketIndex = (x - min) * k / (max - min + 1)

Java完整实现

import java.util.ArrayList;
import java.util.Collections;
import java.util.Arrays;
import java.util.List;

/**
 * 桶排序完整Java实现
 * 包含基础版本、自适应桶数版本、链表优化版本
 */
public class BucketSort {

    /**
     * 标准桶排序实现
     * 时间复杂度:平均O(n + n²/k + k),最坏O(n²)
     * 空间复杂度:O(n + k)
     * @param arr 待排序数组
     * @param bucketCount 桶的数量
     */
    public static void sort(double[] arr, int bucketCount) {
        if (arr == null || arr.length <= 1) return;

        int n = arr.length;

        // 找到最小值和最大值
        double min = arr[0], max = arr[0];
        for (double num : arr) {
            if (num < min) min = num;
            if (num > max) max = num;
        }

        // 处理所有元素相同的情况
        if (min == max) return;

        // 创建桶:使用ArrayList数组
        @SuppressWarnings("unchecked")
        List<Double>[] buckets = new ArrayList[bucketCount];
        for (int i = 0; i < bucketCount; i++) {
            buckets[i] = new ArrayList<>();
        }

        // 将元素分配到桶中
        double range = max - min;
        for (double num : arr) {
            int bucketIndex = (int) ((num - min) / range * (bucketCount - 1));
            buckets[bucketIndex].add(num);
        }

        // 对每个桶进行排序并合并
        int index = 0;
        for (List<Double> bucket : buckets) {
            // 桶内使用Collections.sort(基于Timsort,稳定排序)
            Collections.sort(bucket);
            for (double num : bucket) {
                arr[index++] = num;
            }
        }
    }

    /**
     * 自动计算最优桶数量
     * 通常桶数等于元素个数的平方根效果较好
     */
    public static void sort(double[] arr) {
        if (arr == null || arr.length <= 1) return;
        int bucketCount = Math.max(1, (int) Math.sqrt(arr.length));
        sort(arr, bucketCount);
    }

    /**
     * 整数版本的桶排序
     */
    public static void sortInt(int[] arr, int bucketCount) {
        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;
        }

        if (min == max) return;

        @SuppressWarnings("unchecked")
        List<Integer>[] buckets = new ArrayList[bucketCount];
        for (int i = 0; i < bucketCount; i++) {
            buckets[i] = new ArrayList<>();
        }

        double range = max - min;
        for (int num : arr) {
            int bucketIndex = (int) ((num - min) / range * (bucketCount - 1));
            buckets[bucketIndex].add(num);
        }

        int index = 0;
        for (List<Integer> bucket : buckets) {
            Collections.sort(bucket);
            for (int num : bucket) {
                arr[index++] = num;
            }
        }
    }

    /**
     * 优化版本:使用插入排序处理小桶,减少递归开销
     */
    public static void sortOptimized(double[] arr) {
        if (arr == null || arr.length <= 1) return;

        int n = arr.length;
        double min = arr[0], max = arr[0];
        for (double num : arr) {
            if (num < min) min = num;
            if (num > max) max = num;
        }

        if (min == max) return;

        int bucketCount = Math.max(1, n / 10); // 每个桶平均10个元素
        @SuppressWarnings("unchecked")
        List<Double>[] buckets = new ArrayList[bucketCount];
        for (int i = 0; i < bucketCount; i++) {
            buckets[i] = new ArrayList<>();
        }

        double range = max - min;
        for (double num : arr) {
            int bucketIndex = (int) ((num - min) / range * (bucketCount - 1));
            buckets[bucketIndex].add(num);
        }

        int index = 0;
        for (List<Double> bucket : buckets) {
            // 小桶使用插入排序,大桶使用快速排序
            if (bucket.size() <= 20) {
                insertionSort(bucket);
            } else {
                Collections.sort(bucket);
            }
            for (double num : bucket) {
                arr[index++] = num;
            }
        }
    }

    /**
     * 对List进行插入排序
     */
    private static void insertionSort(List<Double> list) {
        for (int i = 1; i < list.size(); i++) {
            double 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 sortAdaptive(double[] arr) {
        if (arr == null || arr.length <= 1) return;

        int n = arr.length;
        double min = arr[0], max = arr[0];
        for (double num : arr) {
            if (num < min) min = num;
            if (num > max) max = num;
        }

        if (min == max) return;

        // 使用更多桶以提高均匀分布数据的性能
        int bucketCount = n;
        @SuppressWarnings("unchecked")
        List<Double>[] buckets = new ArrayList[bucketCount];
        for (int i = 0; i < bucketCount; i++) {
            buckets[i] = new ArrayList<>();
        }

        double range = max - min;
        for (double num : arr) {
            // 使用更精细的分桶
            int bucketIndex = Math.min(
                (int) ((num - min) / range * bucketCount),
                bucketCount - 1
            );
            buckets[bucketIndex].add(num);
        }

        int index = 0;
        for (List<Double> bucket : buckets) {
            if (bucket.size() > 1) {
                Collections.sort(bucket);
            }
            for (double num : bucket) {
                arr[index++] = num;
            }
        }
    }

    /**
     * 验证排序正确性
     */
    public static boolean isSorted(double[] arr) {
        for (int i = 1; i < arr.length; i++) {
            if (arr[i] < arr[i - 1]) return false;
        }
        return true;
    }

    /**
     * 打印桶分布情况(调试用)
     */
    public static void printBucketDistribution(double[] arr, int bucketCount) {
        double min = arr[0], max = arr[0];
        for (double num : arr) {
            if (num < min) min = num;
            if (num > max) max = num;
        }

        int[] bucketSizes = new int[bucketCount];
        double range = max - min;
        for (double num : arr) {
            int idx = (int) ((num - min) / range * (bucketCount - 1));
            bucketSizes[idx]++;
        }

        System.out.println("桶分布情况(" + bucketCount + "个桶):");
        for (int i = 0; i < bucketCount; i++) {
            double lower = min + range * i / bucketCount;
            double upper = min + range * (i + 1) / bucketCount;
            System.out.printf("桶%d [%.2f, %.2f): %d个元素%n", i, lower, upper, bucketSizes[i]);
        }
    }

    /**
     * 生成均匀分布的测试数据
     */
    public static double[] generateUniformData(int size, double min, double max) {
        double[] arr = new double[size];
        for (int i = 0; i < size; i++) {
            arr[i] = min + Math.random() * (max - min);
        }
        return arr;
    }

    /**
     * 性能测试
     */
    public static void performanceTest() {
        int[] sizes = {1000, 10000, 100000};
        System.out.println("桶排序性能测试(均匀分布数据):");
        System.out.println("规模      桶排序(ms) Arrays.sort(ms)");

        for (int size : sizes) {
            double[] arr1 = generateUniformData(size, 0, 1000);
            double[] arr2 = arr1.clone();

            long start1 = System.currentTimeMillis();
            sort(arr1);
            long time1 = System.currentTimeMillis() - start1;

            long start2 = System.currentTimeMillis();
            Arrays.sort(arr2);
            long time2 = System.currentTimeMillis() - start2;

            System.out.println(size + "     " + time1 + "       " + time2);
        }
    }

    public static void main(String[] args) {
        // 测试基本排序
        double[] arr = {0.42, 0.32, 0.33, 0.52, 0.37, 0.47, 0.51};
        System.out.println("原始数组:" + Arrays.toString(arr));
        sort(arr, 5);
        System.out.println("排序结果:" + Arrays.toString(arr));
        System.out.println("是否正确排序:" + isSorted(arr));
        System.out.println();

        // 测试整数版本
        int[] intArr = {54, 32, 67, 89, 12, 45, 78, 23};
        System.out.println("整数原始数组:" + Arrays.toString(intArr));
        sortInt(intArr, 4);
        System.out.println("整数排序结果:" + Arrays.toString(intArr));
        System.out.println();

        // 测试均匀分布数据
        double[] uniform = generateUniformData(20, 0, 100);
        System.out.println("均匀分布原始数据:" + Arrays.toString(uniform));
        printBucketDistribution(uniform, 5);
        sort(uniform, 5);
        System.out.println("排序结果:" + Arrays.toString(uniform));
        System.out.println();

        // 性能测试
        performanceTest();
    }
}

分桶策略详解

桶数量的选择

桶数量k的选择直接影响性能:

  • k太小:每个桶内元素过多,局部排序开销增大
  • k太大:空桶增多,空间浪费严重
  • 经验值:通常取 k ≈ √n 或 k = n/10

均匀分布的假设

桶排序的最佳性能依赖于数据均匀分布的假设。当数据不均匀时(如大量数据集中在某个区间),某些桶会过载,导致性能退化到O(n²)。

非均匀数据的处理

  1. 自适应桶大小:根据数据密度动态调整桶的边界
  2. 更多桶:增加桶数量以降低每个桶的负载
  3. 递归桶排序:对过载的桶递归应用桶排序

复杂度分析

情况 时间复杂度 空间复杂度 说明
最好情况 O(n + k) O(n + k) 数据均匀分布,每个桶元素数量相近
平均情况 O(n + n²/k + k) O(n + k) 当k≈√n时,平均为O(n)
最坏情况 O(n²) O(n + k) 所有元素落入同一个桶

与计数排序、基数排序的对比

特性 计数排序 桶排序 基数排序
数据类型 整数 浮点数/整数 整数/字符串
范围要求 有限范围 无严格要求 按位处理
稳定性 稳定 依赖桶内排序 稳定
最好时间 O(n+k) O(n+k) O(d×(n+k))
额外空间 O(n+k) O(n+k) O(n+k)
分布要求 无 均匀分布最优 无

应用场景

  • 浮点数排序:计数排序难以处理浮点数,桶排序天然支持
  • 外部排序:数据量超过内存时,可将每个桶写入文件分别排序
  • 数据分布分析:分桶过程本身可以揭示数据的分布特征
  • 直方图计算:统计每个桶的元素数量即可得到数据分布直方图

总结

桶排序是一种优雅的分治排序策略,通过合理的分桶将全局排序问题分解为多个局部排序问题。在数据均匀分布的理想情况下,它能达到线性时间复杂度。理解桶排序有助于掌握分布式计算和并行排序的核心思想。