这里是一个简单版的桶排序,真正的桶排序要更复杂一些,但目前足够我们用了。
题目如下:
期末考试完了老师要将同学们的分数按照从高到低排序。小哼的班上只有 5个同学,这 5个同学分别考了 5分、3分、 5分、2分和8分,考得真是惨不忍睹(满分是 10分)。接下来将分数进行从大到小排序, 排序后是 8 5 5 3 2。你有没有什么好方法编写一段程序,让计算机随机读入 5个数然后将这 5个数从大到小输出?请先想一想,至少想5分钟再往下看吧(__) 。
思路解析:
题目可以用下面的一句话进行概括:
给你5个0~10的乱序数字,将这5个数字按照从大到小的顺序进行输出
明确题目之后,我们该怎么做呢?
- 首先我们创建一个长度为11的数组
int a[] = a[11];
现在我们有了 11 个变量,编号为 a[0]~a[10]。
- 可以发现数组的下标 0~10 正好对应我们的得分范围 0~10 ,我们可以用数组下标来标记分数,用数组的值表示得分的人数,操作如下:
将 a[0]~a[10]都初始化为0,表示这些分数都还没有人得过。
for(int i = 0; i < a.length; i++;){ // 遍历数组 a.length = 11 (数组的长度)
a[i] = 0; // 初始化为0(即赋值为0)
}
举个例子:a[0] 等于 0 就表示目前还没有人得过 0 分,同理 a[1] 等于 0 就表示目前还没有人得过 1 分…… a[10] 等于 0 就表示目前还没有人得过 10 分。
- 接下来对题目所给的5个得分进行处理,得分为 8 就在 a[8] 的基础上加 1,得分为 5 就在 a[5] 的基础上加 1……注意啦!第三个人的分数也是 5分,所以 a[5] 的值需要在此基础上再增加 1,即是将 a[5] 的值从 1 改为 2,表示 5 分出现过了两次。
Scanner sc = new Scanner(System.in);
for(int i = 0; i < 5; i++){
int t = sc.nextInt();
a[t]++;
}
处理完 5 个得分之后,结果如下:
- 最后,我们只需要将出现过的分数打印出来就可以了,出现几次就打印几次。
for(int i = 0; i < a.length; i++){
for(int j = 0; j < a[i]; j++){
System.out.print(i+" ")
}
}
具体如下:
输出结果为:2 3 5 5 8
完整代码如下:
import java.util.Scanner;
public class T1 {
public static void main(String[] args) {
int a[] = new int[11];
for(int i = 0; i < a.length; i++) { // 遍历数组 a.length = 11 (数组的长度)
a[i]=0; // 初始化为0(即赋值为0)
}
// 输入需要排序的数字个数
System.out.println("请输入需要排序的数字个数:");
Scanner s1 = new Scanner(System.in);
int n = s1.nextInt();
// 输入需要排序的数字
System.out.println("请输入需要排序的数字:");
Scanner s2 = new Scanner(System.in);
for(int i = 0; i < n; i++) { // 循环读入需要排序的数字
int t = s2.nextInt();
a[t]++; // 数组里下标为t的值+1,表示数字等于该下标的个数已经有一个了
}
for(int i = 0; i < a.length; i++) {
// 外层循环遍历数组
for(int j = 1; j <= a[i]; j++) {
// 将出现过的分数打印出来,出现几次就打印几次。
System.out.print(i+" ");
}
}
}
}
(这里我没有选择随机生成数字,而是根据提示进行输入,感兴趣的也可以改成随机生成数字)
运行结果如图所示:
小总结:
这个算法就好比有 11 个桶,编号从 0~10。每出现一个数,就在对应编号的桶中放一个 小旗子,最后只要数一数数每个桶中有几个小旗子就 OK 了。
例如: 2 号桶中有 1 个小旗子,表示 2 出现了一次;3号桶中有 1 个小旗子,表示 3 出现了一次;5 号桶中有 2 个小旗子,表示 5 出现了两次;8 号桶中有 1 个小旗子,表示 8 出现了一次。
桶排序的升序排序
可以发现上述代码实现的是将 5 个数按照从小到大的顺序输出,即 升序排序。
完整代码同上(再次展示一下):
import java.util.Scanner;
public class T1 {
public static void main(String[] args) {
int a[] = new int[11];
for(int i = 0; i < a.length; i++) { // 遍历数组 a.length = 11 (数组的长度)
a[i]=0; // 初始化为0(即赋值为0)
}
// 输入需要排序的数字个数
System.out.println("请输入需要排序的数字个数:");
Scanner s1 = new Scanner(System.in);
int n = s1.nextInt();
// 输入需要排序的数字
System.out.println("请输入需要排序的数字:");
Scanner s2 = new Scanner(System.in);
for(int i = 0; i < n; i++) { // 循环读入需要排序的数字
int t = s2.nextInt();
a[t]++; // 数组里下标为t的值+1,表示数字等于该下标的个数已经有一个了
}
for(int i = 0; i < a.length; i++) {
// 外层循环遍历数组
for(int j = 1; j <= a[i]; j++) {
// 将出现过的分数打印出来,出现几次就打印几次。
System.out.print(i+" ");
}
}
}
}
运行结果如图所示:
桶排序的降序排序
那么利用桶排序进行升序排序写完了,但是题目要求的是将 5 个数字按照从大到小的顺序输出,这该怎么办呢?别急,我们只需要改动一个条件即可。
如果你仔细观察了可以发现,桶排序中决定输出顺序的关键就在于最后一个双重循环的外层循环,外层循环的作用是按照从0到10的顺序遍历数组,而我们的内层循环就是根据外层循环读到的下标来判断输出哪个数字。现在外层是升序遍历,但是我们需要的是降序遍历,因此只需要改动一下外层循环的条件即可。
改动代码如下:
// 升序
for(int i = 0; i < a.length; i++){ // 升序从数组的开端开始
for(int j = 0; j <= a[i]; j++){
System.out.print(i+" ");
}
}
// 降序
for(int i = a.length-1; i >= 0; i--){
// 降序从数组的末尾开始,需要注意的是 数组的下标最大值=数组的长度 - 1
for(int j = 0; j <= a[i]; j++){
System.out.print(i+" ");
}
}
完整代码如下:
import java.util.Scanner;
public class T1 {
public static void main(String[] args) {
int a[] = new int[11];
for(int i = 0; i < a.length; i++) { // 遍历数组 a.length = 11 (数组的长度)
a[i]=0; // 初始化为0(即赋值为0)
}
// 输入需要排序的数字个数
System.out.println("请输入需要排序的数字个数:");
Scanner s1 = new Scanner(System.in);
int n = s1.nextInt();
// 输入需要排序的数字
System.out.println("请输入需要排序的数字:");
Scanner s2 = new Scanner(System.in);
for(int i = 0; i < n; i++) { // 循环读入需要排序的数字
int t = s2.nextInt();
a[t]++; // 数组里下标为t的值+1,表示数字等于该下标的个数已经有一个了
}
for(int i = a.length - 1; i >= 0; i--) {
// 外层循环遍历数组
for(int j = 1; j <= a[i]; j++) {
// 将出现过的分数打印出来,出现几次就打印几次。
System.out.print(i+" ");
}
}
}
}
运行结果如图所示:
时间复杂度
最后来说下时间复杂度的问题。
代码中第一个 for 循环(初始化数组的值为0)一共循环了 m 次(m为桶的个数), 第 2 个 for 循环(循环读入排序的数字)一共循环了 n 次(n为待排序数的个数),最后一个 for 的双重循环一共循环了 m+n 次。 所以整个排序算法一共执行了 m+n+m+n次。我们用大写字母 O来表示时间复杂度,因此该算法的时间复杂度是 O(m+n+m+n) 即 O(2*(m+n)) 。我们在说时间复杂度的时候可以忽略较小的常数,最终桶排序的时间复杂度为 O(m+n) 。还有一点,在表示时间复杂度的时候,n 和 m 通常用大写字母即 O(M+N)。
注意:
需要说明的一点是:我们目前学习的简化版桶排序算法,其本质上还不能算是一个真正意义上的排序算法。为什么呢?举个例子:当我们的桶排序算法遇到下面的例子就没辙了。
现在有 5 个人的名字和分数:huhu 5 分、haha 3 分、xixi 5 分、hengheng 2 分、lala 8 分。请按照分数从高到低,输出他们的名字。
即应该输出:lala 、huhu 、xixi 、haha 、hengheng。
发现问题了没有?如果使用我们刚才简化版的桶排序算法仅仅是把分数进行了排序。最终输出的也仅仅是分数,但没有对人本身进行排序。也就是说,我们现在并不知道排序后的分数原本对应着哪一个人!不要着急,往下看——冒泡排序。