桶排序故事:图书馆的书籍分类

111 阅读4分钟

一、图书馆的故事:如何快速整理书籍

图书馆新到了 1000 本图书,需要按照书名的首字母(A-Z)进行分类。图书管理员想到了一个办法:

  1. 准备 26 个书架:每个书架对应一个字母(A 书架放 A 开头的书,B 书架放 B 开头的书,以此类推)

  2. 分类投放:将每本书放到对应的书架上

  3. 书架内排序:对每个书架上的书进行单独排序(比如按书名拼音)

  4. 合并结果:按书架顺序依次取下所有书,形成最终排序

这就是桶排序的核心思想:分桶 + 桶内排序 + 合并

二、桶排序的 Java 代码实现

java

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

public class BucketSort {
    public static void sort(double[] array) {
        if (array == null || array.length == 0) return;
        
        // 1. 确定桶的数量
        int bucketCount = 10;  // 假设分为10个桶
        
        // 2. 创建桶
        List<List<Double>> buckets = new ArrayList<>(bucketCount);
        for (int i = 0; i < bucketCount; i++) {
            buckets.add(new ArrayList<>());
        }
        
        // 3. 将元素分配到桶中
        double max = array[0];
        double min = array[0];
        for (double num : array) {
            if (num > max) max = num;
            if (num < min) min = num;
        }
        
        double range = (max - min) / bucketCount;
        for (double num : array) {
            int bucketIndex = (int) ((num - min) / range);
            if (bucketIndex >= bucketCount) bucketIndex = bucketCount - 1;  // 处理最大值
            buckets.get(bucketIndex).add(num);
        }
        
        // 4. 对每个桶进行排序
        for (List<Double> bucket : buckets) {
            Collections.sort(bucket);
        }
        
        // 5. 合并所有桶的结果
        int index = 0;
        for (List<Double> bucket : buckets) {
            for (double num : bucket) {
                array[index++] = num;
            }
        }
    }
}

三、桶排序的核心步骤解析

1. 确定桶的数量和范围

根据数据分布,确定合适的桶数量,并计算每个桶的范围:

java

// 示例数组:[0.42, 0.32, 0.33, 0.52, 0.37, 0.47, 0.51]
// 桶数量 = 5
// 范围计算:(0.52 - 0.32) / 5 = 0.04
// 桶0: [0.32, 0.36)
// 桶1: [0.36, 0.40)
// 桶2: [0.40, 0.44)
// 桶3: [0.44, 0.48)
// 桶4: [0.48, 0.52]
2. 将元素分配到桶中

根据元素的值,将其放入对应的桶:

java

// 分配结果:
// 桶0: [0.32, 0.33]
// 桶1: [0.37]
// 桶2: [0.42]
// 桶3: [0.47]
// 桶4: [0.52, 0.51]
3. 桶内排序

对每个桶内的元素进行排序(使用其他排序算法,如插入排序、快速排序):

java

// 排序后:
// 桶0: [0.32, 0.33]
// 桶1: [0.37]
// 桶2: [0.42]
// 桶3: [0.47]
// 桶4: [0.51, 0.52]
4. 合并结果

按桶的顺序依次取出元素,形成最终排序结果:

java

// 合并后:[0.32, 0.33, 0.37, 0.42, 0.47, 0.51, 0.52]

四、桶排序的性能分析

  • 时间复杂度:O (n + k)(平均情况)

    • n:元素个数
    • k:桶的数量

    最坏情况:O (n²)(所有元素集中在一个桶)

  • 空间复杂度:O(n + k)

  • 稳定性:取决于桶内排序算法(若使用稳定排序,则桶排序稳定)

  • 适用条件

    • 数据均匀分布(避免桶内元素过多)
    • 取值范围已知(便于确定桶的数量和范围)

五、桶排序的优化与变种

1. 动态调整桶数量

根据数据范围和分布动态确定桶的数量:

java

// 改进版:桶数量 = 元素个数的平方根
int bucketCount = (int) Math.sqrt(array.length) + 1;
2. 处理整数数据

对于整数,可以根据数值范围直接映射到桶:

java

// 示例:排序整数 [12, 23, 34, 45, 56, 67, 78]
// 桶0: [10-19][12]
// 桶1: [20-29][23]
// 桶2: [30-39][34]
// ...
3. 桶内排序算法选择

对于小规模数据,桶内可使用插入排序;对于大规模数据,可使用快速排序:

java

// 桶内排序优化
if (bucket.size() <= 10) {
    insertionSort(bucket);  // 小规模数据用插入排序
} else {
    Collections.sort(bucket);  // 默认使用归并排序(稳定)
}

六、桶排序 vs 其他排序算法

排序算法时间复杂度空间复杂度稳定性适用场景
桶排序O(n + k)O(n + k)可选数据均匀分布的场景
快速排序O(n log n)O(log n)不稳定通用场景
归并排序O(n log n)O(n)稳定大规模数据、链表排序
计数排序O(n + k)O(k)稳定数据范围小的整数排序

七、总结:桶排序的核心思想

桶排序的本质是 "分而治之":

  1. 分桶:将数据按范围分配到多个桶中

  2. 桶内排序:每个桶独立排序

  3. 合并:按桶的顺序合并结果

就像图书馆整理书籍,通过分类和子分类,将大规模排序问题转化为多个小规模排序问题。桶排序特别适合处理均匀分布的数据,效率远超基于比较的排序算法。但它需要预先了解数据分布,并合理设置桶的数量,否则可能导致性能下降。