桶排序
桶排序是最快最简单的排序算法。
举例说明
假设班上有 5 个学生,这 5 个学生分别考了 5分、3分、5分、2分和 8分(满分是10分 ^_^)。接下来将分数按从大到小进行排序,排序后是 8 5 5 3 2。
如何编写一段程序,让计算机随机读入 5 个数然后将这 5 个数从大到小输出?
只需要借助一个一维数组就可以解决这个问题。
- 首先我们需要申请一个大小为 11 的数组
int a[11]
,将a[0]~a[10]
都初始化为 0,表示这些分数还都没有人得过。比如,a[0]
等于 0 就表示目前还没有人得过 0 分,a[10]
等于 0 表示目前还没有人得过 10 分。 - 接着处理每个人的分数,第一位学生的分数是 5 分,所以我们将对应的
a[5]
的值在原来的基础增加 1,即将a[5]
的值从 0 改为 1,表示 5 分出现过一次。 - 第二位学生的分数是 3 分,所以我们将对应的
a[3]
的值在原来的基础增加 1,即将a[3]
的值从 0 改为 1,表示 3 分出现过一次。 - 需要注意的是,第三位学生的分数也是 5 分,所以
a[5]
的值需要在其基础上再增加 1,即将a[5]
的值从 1 改为 2,表示 5 分出现过两次。 - 以此类推,第四和第五位学生的分数将在
a[2]
和a[8]
中体现出来。
整个过程如下图所示:
发现没有?
a[0]~a[10]
中的数值其实就是 0 分到 10 分每个分数出现的次数。因此,我们只需要将出现的分数打印出来就达到排序的目的了。
C代码实现
#include <stdio.h>
#define BUCKET_SIZE 11
#define STUDENT_CNT 5
int main(void)
{
int a[BUCKET_SIZE] = {0};
int i, j, t;
/* 循环读入5个数 */
for(i=0; i<STUDENT_CNT; i++) {
scanf("%d", &t); /* 把每一个数读到变量t中 */
a[t]++; /* 进行计数 */
}
/* 打印数组的值 */
for(i=0; i<BUCKET_SIZE; i++) {
printf("%d ", a[i]);
}
printf("\n");
/* 将分数从大到小打印输出 */
for(i=BUCKET_SIZE-1; i>=0; i--) {
for(j=0; j<a[i]; j++) {
printf("%d ", i);
}
}
printf("\n");
return 0;
}
编译并执行代码,如下:
可以看到,数组的值 0 0 1 1 0 2 0 0 1 0 0
和我们前面的分析是一样的,并对 5 个学生的分数按由大到小进行排序,即 8 5 5 3 2
。
这个算法就好比有 11 个桶,编号从 0~10。每出现一个数,就在对应编号的桶中放一个小旗子,最后只要数一下每个桶中有多少个小旗子即可。比如 2 号桶中有 1 个小旗子,表示 2 出现了一次;5 号桶中有 2 个小旗子,表示 5 出现了两次;10 号桶中没有小旗子,表示 10 出现了零次。
这种排序方法我们暂且称为 “桶排序”。其实真正的桶排序要比这个复杂一些,以后再详细讨论。
更进一步,我们可以尝试输入 n 个 0~1000 之间的整数,将它们从大到小排序。显然此时需要 1001 个桶,用来表示 0~1000 之间每个数出现的次数。
复杂度分析
在上述代码中,忽略 “打印数组的值” 块循环,假设前面读入待排序数时循环了 n 次,后面排序时一共循环了 m+n 次(n 为待排序的个数,m 为桶的个数)。用大写字母 O 表示时间复杂度,该算法的时间复杂度为 ,即 。忽略较小的常数,最终桶排序法的时间复杂度可以表示为 。
桶排序是一个非常快的排序算法。但显然,如果需要排列的数值范围很大,就需要创建非常大的数组,因此在内存资源紧缺的场合就不太合适了。