目录

假设你有一堆 0 到 1 之间的小数要排序。

与其两两比较,不如准备 10 个桶,分别装 [0, 0.1)、[0.1, 0.2)…… [0.9, 1.0) 的数。

每个桶里的数不多,简单排一下就行。

最后按桶号从 0 到 9 把所有桶里的数依次倒出来,这样全局就有序了。

桶排序(Bucket Sort) 就是这个思路:把数据均匀分配到若干个桶里,各桶内部单独排序,最后合并所有桶。

它是计数排序的”泛化版本”,计数排序相当于每个桶只装一种值,桶排序则允许一个桶装一个范围的值。


它是什么

核心思想 将数据按规则分配到多个桶中,使得落在同一个桶内的数据范围较小且有序关系简单;对各桶分别排序后,按桶序合并即得全局有序结果。

分类整理:先把东西粗略归类到不同篮子里,然后在每个篮子内部再细排。

桶排序的关键就在于均匀分布。

如果数据均匀分散到各个桶里,每个桶的数据量就会很小,桶内排序的成本就极低。

与计数排序的关系

特征计数排序桶排序
每个桶装什么只装相同值的元素装一定范围内的所有值
桶内操作无需排序(直接输出)需要排序(桶内元素少时插入排序更优)
适用数据整数,范围小浮点数或均匀分布的数据
时间O(n+k)平均 O(n),最坏 O(n log n)
稳定性取决于实现取决于桶内排序算法

可以把计数排序看作桶排序的一个特例:桶的数量 = 数据范围,每个桶只存一个值。

伪代码

// 仅为示意(针对 [0,1) 范围浮点数)
function bucketSort(arr, n):
    创建 n 个空桶 buckets[0..n-1]

    // Step 1: 分桶
    for each x in arr:
        idx = floor(x * n)
        buckets[idx].append(x)

    // Step 2: 各桶内排序
    for each bucket in buckets:
        if bucket 不空:
            // 桶内元素少时插入排序更优,演示代码用 std::sort 简化
            sort(bucket)         

    // Step 3: 合并
    result = []
    for i = 0 to n-1:
        result.extend(buckets[i])

    return result

下面用流程图拆解分桶、桶内排序与合并过程。

工作原理流程图

对应伪代码的整体流程:

flowchart LR
    A(["输入数组 arr"]) --> B["创建 k 个空桶<br/>k 通常 = n"]
    B --> C["遍历 arr<br/>arr[i] 放入第 floor(arr[i]*k) 个桶"]
    C --> D["对每个非空桶独立排序<br/>桶内少时插入排序更优"]
    D --> E["按桶 0,1,...,k-1 顺序<br/>合并所有桶内元素"]
    E --> F(["输出有序数组"])

逐步演示

用 [0.78, 0.17, 0.39, 0.26, 0.72, 0.94, 0.21, 0.12, 0.23, 0.68](n=10,10 个桶)跑一遍:

初始数组 (10 个元素, 范围 [0,1)): [0.78, 0.17, 0.39, 0.26, 0.72, 0.94, 0.21, 0.12, 0.23, 0.68]

Step 1 - 分桶:
  0.78 -> 桶[7]    0.17 -> 桶[1]    0.39 -> 桶[3]
  0.26 -> 桶[2]    0.72 -> 桶[7]    0.94 -> 桶[9]
  0.21 -> 桶[2]    0.12 -> 桶[1]    0.23 -> 桶[2]
  0.68 -> 桶[6]

Step 2 - 各桶内排序:
  桶[1]: [0.12, 0.17]        (2 个元素)
  桶[2]: [0.21, 0.23, 0.26]   (3 个元素)
  桶[3]: [0.39]               (1 个元素)
  桶[6]: [0.68]               (1 个元素)
  桶[7]: [0.72, 0.78]         (2 个元素)
  桶[9]: [0.94]               (1 个元素)

Step 3 - 按桶顺序合并:
最终结果: [0.12, 0.17, 0.21, 0.23, 0.26, 0.39, 0.68, 0.72, 0.78, 0.94]

每个桶平均只有 1-3 个元素,桶内仅常数个元素,排序成本接近 O(1)。

总工作量接近线性。


能解决什么问题

适用场景

场景为什么适合
浮点数均匀分布经典应用场景,如 [0,1) 均匀分布的小数
已知大致分布的数据可以据此设计桶的范围和数量
外部排序的预处理大文件可以先分桶,各桶独立排序后合并

局限性

局限说明
依赖数据分布如果数据集中在一个桶里,退化为对该桶的全排序
额外的桶管理开销需要维护多个容器
不是原地排序需要额外的桶空间
稳定性不确定取决于桶内使用的排序算法

退化场景演示

如果数据高度集中:

退化数据: [0.01, 0.02, 0.03, 0.04, 0.05, 0.91, 0.92, 0.93, 0.94, 0.95]

分桶结果:
  桶[0]: [0.01, 0.02, 0.03, 0.04, 0.05]   (5 个元素!)
  桶[9]: [0.91, 0.92, 0.93, 0.94, 0.95]   (5 个元素!)

此时桶内排序的工作量 ≈ 对 5 个元素做全排序

虽然这个例子中退化后仍然可控,但如果 n=100 万且全部集中在 1 个桶里,就退化为对全量数据的常规排序了(std::sort 为 O(n log n),插入排序为 O(n²))。


C++ 实现与复杂度分析

通用 C++ 模板实现 可直接复用的代码:

// 通用模板:适用于任意数值类型(int / double / float 等)
template <typename T>
void bucketSort(std::vector<T>& arr, int bucketCount = 0) {
    int n = static_cast<int>(arr.size());
    if (n <= 1) return;
    if (bucketCount <= 0) bucketCount = n;          // 默认桶数 = 元素数

    // 确定数据范围
    auto [minIt, maxIt] = std::minmax_element(arr.begin(), arr.end());
    T minVal = *minIt, maxVal = *maxIt;
    if (minVal == maxVal) return;

    // 创建桶
    std::vector<std::vector<T>> buckets(bucketCount);

    // 分桶:将每个元素映射到对应桶
    double range = static_cast<double>(maxVal - minVal);
    for (const T& val : arr) {
        int idx = static_cast<int>(
            bucketCount * static_cast<double>(val - minVal) / range);
        if (idx >= bucketCount) idx = bucketCount - 1;   // 边界保护
        buckets[idx].push_back(val);
    }

    // 各桶内部排序 + 按桶号合并
    arr.clear();
    for (auto& bucket : buckets) {
        std::sort(bucket.begin(), bucket.end());
        arr.insert(arr.end(), bucket.begin(), bucket.end());
    }
}

时间复杂度怎么算

分三步计算:

步骤操作平均复杂度最坏复杂度
分桶遍历 + 计算O(n)O(n)
桶内排序取决于桶内元素数O(n)(均匀时每桶 O(1))O(n log n)(std::sort)/ O(n²)(插入排序)
合并遍历所有桶O(n)O(n)

平均情况(数据均匀分布): 每个桶期望有 n/k = O(1) 个元素(设 k=n),桶内排序接近常数时间 → 总计 O(n)。

最坏情况(所有元素进同一桶): 退化为对单个桶做全量排序。若桶内用 std::sort(演示代码做法),最坏 O(n log n);若桶内用插入排序(元素少时更优),最坏 O(n²)。

情况时间复杂度条件
最好O(n)每桶 ≤ 1 个元素
平均O(n)数据均匀分布
最坏O(n log n)(std::sort)/ O(n²)(插入排序)所有元素落入同一桶

空间复杂度

n 个桶 + n 个元素的总副本 → O(n + k),其中 k 为桶数(通常 k=n)→ O(n)。

小结算法:时间平均 O(n)(最坏 O(n log n),用插入排序则为 O(n²))、空间 O(n)、稳定性取决于桶内排序、适用于均匀分布的数据。


全文小结

  • 是什么:按映射规则把数据分到若干桶,各桶独立排序后合并。是计数排序的泛化版。
  • 解决什么 / 用在哪:均匀分布的浮点数排序;已知分布数据的快速排序;外部排序预处理。
  • 局限:数据分布不均时退化;稳定性取决于桶内算法;需要额外桶空间。
  • 复杂度怎么算:分桶 O(n) + 合并 O(n) 固定;桶内排序决定总复杂度。均匀时 O(n),最坏 O(n log n)(std::sort)或 O(n²)(插入排序)。空间 O(n)。