1. 基本思想
分配排序不需要比较关键字的大小,根据关键字各位上的值,进行若干趟“分配”和“收集”实现排序。
桶排序将待排序序列划分成若干个区间,每个区间形象的看作一个桶,如果桶中的记录多于一个则使用较快的排序方法进行排序,把每个同种的记录收集起来,最终得有序序列。
这个排序方法真的很形象,速度还不慢,举个例子吧:假如有 10 个学生的成绩,(60,75,54,70,83,48,80,12,75*,92)
,对该成绩序列进行桶排序。需要进行以下步骤:
- 分配
学生成绩的范围在:0~100之间,可以划分 10 个桶,0~9,10~19,20~39,...,90~100
,将学生成绩依次放入桶中,如下图所示:
- 利用比较先进的排序算法对每个桶内的数据进行排序
如果桶内多于一个记录,可以使用较为先进的排序算法进行排序,最好的选择就是快速排序,也可以对每个桶继续使用桶排序。在这用插入排序举个例子,排序后的桶如下图:
- 收集
将每个桶内的记录依次收集起来,得到一个有序的序列(12,48,54,68,70,75,75*,80,83,92)
桶排序需要注意的几个问题:
- 桶排序的数据最好是均匀分布的。如果有 10 个学生成绩都在 90 分以上,那么 10 个记录都会分配在一个桶内,桶排序就退化成了一般的排序。理想的情况下,当数据均匀分布,桶的数量
m
足够大时,那么每个桶内最多只有一个记录,不需要再进行排序,只需要 O ( n ) O(n) O(n)的时间将所有记录分配到桶中,再用 O ( n ) O(n) O(n)的时间收集起来即可,桶排序的时间复杂度可以达到 0 ( n ) 0(n) 0(n)。 但是这样做空间复杂度较大,是以空间换时间的做法。关于时间复杂度的讨论会在后面性能分析处做详细分析。 - 桶排序针对不同的数据选择的划分方法是不同的。例如序列
(2, 56,1278, 685,70,7570, 22529, 580, 7, 82)
, 可以按照位数划分桶,1位数,2位数,3位数,4位数,5位数。 - 桶内排序时使用的比较排序算法也有可能不同。可以使用直接插入排序,也可以使用快速排序
2. 代码实现
桶排序比较抽象,采用常用的数字对其进行验证凸显不了它的特点和用途,在此来一道常见的设计性问题:
问题: 要对大小为 [1,1000]
的 n
个整数 a[0,...,n - 1]
递增排序,设计相应的桶排序算法。
解答: 假设每个桶的大小为 10,总共需要 1000/10 = 100
个桶,一种更有效的方法是求出 a
中的最大元素值 max
和最小元素值 min
,设置桶个数 num = (max - min + 1) / 10 + 1
。然后对 a
数组从头到尾扫描一遍,把每个 a[i]
放入对应的桶 B[k](k = (a[i] - min + 1) / 10)
,再对这些痛排序,最后依次输出每个桶里面的元素。对应的算法代码如下:
// 桶排序(递增)
#define BUCKSIZE 20
typedef struct {
int data[BUCKSIZE];
int count; // 桶中元素的个数
} BuckType; // 桶类型
void BucketSort(int array[], int size) {
int max, min, num, pos;
BuckType *pB;
max = min = array[0];
for (int i = 1; i < size; ++i) {
if (array[i] > max) {
max = array[i];
}
else if (array[i] < min) {
min = array[i];
}
}
num = (max - min + 1) / 10 + 1; // 求出桶的个数
pB = (BuckType*)malloc(sizeof(BuckType) * num);
memset(pB, 0, sizeof(BuckType) * num);
for (int i = 0; i < size; ++i) { // 将数组元素分配进桶
int k = (array[i] - min + 1) / BUCKSIZE; // 求出array[i]对应的桶号,在此为10个桶
(pB + k)->data[(pB + k)->count] = array[i];
(pB + k)->count++;
}
pos = 0;
for (int i = 0; i < num; ++i) {
QuickSort((pB + i)->data, 0, (pB + i)->count); // 单个桶快速排序
for (int j = 0; j < (pB + i)->count; ++j) {
array[pos++] = (pB + i)->data[j];
}
}
}
测试数据:int array[] = { 3, 9, 1, 4, 2, 8, 2, 7, 5, 3, 6, 11, 9, 4, 2, 5, 0, 6 };
这组数据缺失没有说服力…桶排序随机硬核250个数字:
下面是快排250个随机数,发现,我去这桶排序真的无语,和快排时间一样,虽然我内部就是调用的快排啊~~
至于这个计时器,先卖个关子,在最后会测试各个排序算法的快慢程度会用到,也会给出源码的~~
3. 性能分析
时间复杂度
假设有 n
个元素、m
个桶,如果元素值是平均分配的,则每个桶里面平均有 n / m
个元素。如果对每个桶中的元素采用快速排序,那么桶排序算法的时间复杂度是
O
(
n
+
m
∗
n
/
m
∗
l
o
g
(
n
/
m
)
)
=
O
(
n
+
n
l
o
g
n
−
n
l
o
g
m
)
。
O(n+m * n / m*log{(n/m))}=O(n+nlogn-nlogm)。
O(n+m∗n/m∗log(n/m))=O(n+nlogn−nlogm)。当 m
接近 n
的时候,桶排序的时间复杂度接近
O
(
n
)
O(n)
O(n)。
用着快排,比快排还快…
空间复杂度
- O ( n + m ) O(n + m) O(n+m)
排序稳定性
- 稳定
来自百度百科:百度百科 桶排序
这不应该取决于外部排序规则的稳定性吗…在此不要钻牛角尖…