本课时有配套视频讲解,购买后即可观看(永久有效)
桶排序
一、课上练习
编程练习
(暂无)
二、知识总结
✨ 核心思想
桶排序(Bucket Sort)是一种非比较排序算法。其核心思想是:将待排序元素按照某种映射规则分配到若干个"桶"中,每个桶内部单独排序,最后按桶的顺序依次收集元素。
当数据分布比较均匀时,桶排序可以达到 O(n) 的时间复杂度,打破了基于比较的排序 O(n log n) 的下界。
前提条件:需要知道数据的范围,且数据分布相对均匀。
✨ 算法原理
- 确定桶的数量和范围:根据数据的最大值和最小值,划分若干个等宽的桶
- 分配:遍历数组,将每个元素放入对应的桶中
- 桶内排序:对每个桶内的元素进行排序(可用任意排序算法)
- 收集:按桶的顺序依次取出所有元素
✨ 代码实现
整数桶排序(计数排序风格)
当数据范围不大时(如 0 ~ 10^6),可以直接用数组下标作为桶:
简单桶排序(整数)
1#include <bits/stdc++.h>
2using namespace std;
3
4int cnt[1000001]; // 每个值出现的次数
5
6int main() {
7 int n;
8 scanf("%d", &n);
9 int a[100005];
10 int maxVal = 0;
11 for (int i = 0; i < n; i++) {
12 scanf("%d", &a[i]);
13 cnt[a[i]]++;
14 maxVal = max(maxVal, a[i]);
15 }
16
17 // 按桶的顺序收集
18 int idx = 0;
19 for (int v = 0; v <= maxVal; v++) {
20 while (cnt[v] > 0) {
21 a[idx++] = v;
22 cnt[v]--;
23 }
24 }
25
26 for (int i = 0; i < n; i++) printf("%d ", a[i]);
27 return 0;
28}通用桶排序(浮点数/大范围)
当数据范围很大或是浮点数时,使用多个桶来分段:
通用桶排序
1#include <bits/stdc++.h>
2using namespace std;
3
4void bucketSort(vector<double>& a) {
5 int n = a.size();
6 if (n <= 1) return;
7
8 // 找到最大值和最小值
9 double minVal = *min_element(a.begin(), a.end());
10 double maxVal = *max_element(a.begin(), a.end());
11
12 // 创建 n 个桶
13 int bucketCount = n;
14 double bucketRange = (maxVal - minVal) / bucketCount + 1e-9;
15 vector<vector<double>> buckets(bucketCount);
16
17 // 分配元素到桶中
18 for (double x : a) {
19 int idx = (int)((x - minVal) / bucketRange);
20 if (idx >= bucketCount) idx = bucketCount - 1;
21 buckets[idx].push_back(x);
22 }
23
24 // 桶内排序
25 for (auto& bucket : buckets) {
26 sort(bucket.begin(), bucket.end());
27 }
28
29 // 收集结果
30 int k = 0;
31 for (auto& bucket : buckets) {
32 for (double x : bucket) {
33 a[k++] = x;
34 }
35 }
36}
37
38int main() {
39 vector<double> a = {0.42, 0.32, 0.23, 0.52, 0.25, 0.47, 0.51};
40 bucketSort(a);
41 for (double x : a) printf("%.2f ", x);
42 // 输出: 0.23 0.25 0.32 0.42 0.47 0.51 0.52
43 return 0;
44}✨ 执行示例
以 [0.42, 0.32, 0.23, 0.52, 0.25, 0.47, 0.51](7 个元素)为例,使用 5 个桶:
桶的范围划分(0.23 ~ 0.52,每桶宽度约 0.06):
| 桶编号 | 范围 | 分配的元素 | 桶内排序后 |
|---|---|---|---|
| 0 | [0.23, 0.29) | 0.23, 0.25 | 0.23, 0.25 |
| 1 | [0.29, 0.35) | 0.32 | 0.32 |
| 2 | [0.35, 0.41) | (空) | (空) |
| 3 | [0.41, 0.47) | 0.42 | 0.42 |
| 4 | [0.47, 0.53] | 0.52, 0.47, 0.51 | 0.47, 0.51, 0.52 |
按桶的顺序收集:[0.23, 0.25, 0.32, 0.42, 0.47, 0.51, 0.52]
✨ 解题步骤详解
判断是否适合使用桶排序:
- 数据范围已知且有限:能确定最大值和最小值
- 数据分布较均匀:如果数据严重集中在某个区间,桶排序退化
- 需要线性时间排序:当 O(n log n) 不够快时
桶排序的变体:
- 计数排序:桶的数量 = 值域大小,每个桶最多一种值。适合值域小的整数。
- 基数排序:多趟桶排序,每趟按一个位(个位、十位...)分桶。
竞赛中的应用场景:
- 数据范围小的排序(如分数 0~100)
- 频率统计后按频率排序
- 需要 O(n) 排序来降低总体复杂度
✨ 常见错误
- 桶数量不合理:桶太少,每个桶元素太多,退化为 O(n log n);桶太多,浪费空间
- 桶的边界计算错误:最大值元素可能越界,需要特殊处理
- 忽略数据分布:数据极度不均匀时(如 1, 1, 1, ..., 1000000),桶排序效果很差
- 空桶处理遗漏:收集时忘记跳过空桶
- 浮点精度问题:计算桶编号时的浮点误差导致分配错误
✨ 算法评价
| 特性 | 桶排序 |
|---|---|
| 时间复杂度(最好/平均) | O(n + k),k 为桶数量 |
| 时间复杂度(最坏) | O(n²),所有元素落入同一个桶 |
| 空间复杂度 | O(n + k) |
| 稳定性 | 取决于桶内排序算法(可以做到稳定) |
| 适用场景 | 数据范围已知、分布均匀 |
与比较排序的对比:
| 排序类型 | 下界 | 条件 |
|---|---|---|
| 基于比较的排序 | O(n log n) | 无额外条件 |
| 桶排序/计数排序/基数排序 | O(n) | 需要数据范围/分布信息 |
桶排序打破了 O(n log n) 的排序下界,但代价是需要额外的空间和对数据的先验知识。在竞赛中,桶排序常常以"计数排序"的形式出现,用于值域较小的场景。