上一节中的例子中董老师想为他教的五名优秀学生进行成绩排名,我们运用桶排序的算法很快地帮董老师解决了这个问题。现在,董老师提出来新的要求,那就是他想找出成绩最好的那名同学去参加比赛。我们已知这五名同学的名字分别为敏敏(95分)、哲哲(93分)、锐锐(95分)、帅帅(92分)、柔柔(98分),想一想,如果我们运用上一节的内容能否满足董老师的要求呢?
通过观察与思考,我们发现上一节所介绍的简化版的桶排序算法只能对成绩进行排序,最终输出的也仅仅是分数序列,并没有对人本身进行排序。也就是说,我们现在并不知道排序后的最高分原本对应着哪一名同学!怎样才能帮助到董老师呢?这就要涉及到本节要介绍的内容了——冒泡排序。
冒泡排序是基于交换的排序,它的基本思想是:每次比较两个相邻的元素,如果他们的顺序错误就把他们交换过来。冒泡排序相较于桶排序更节省空间,它不仅能对整数进行排序也能对小数进行排序。
下面我们以 21 53 99 81 67 这5个数的降序(从大到小)排列为例,介绍一下冒泡排序的实现过程。
首先我们比较第1位和第2位的大小,现在第1位是21,第2位是53。很显然,21比53要小。我们希望数字按照降序进行排列,也就是说越小的数字应该越靠后,因此需要交换这两个数的顺序。交换后这5个数的顺序为 53 21 99 81 67。
按照刚才的方法,继续比较第2位和第3位的大小,第2位是21,第三位是99.21比99要小,因此需要交换两个数的位置。交换后的顺序为 53 99 21 81 67。
根据以上规则,继续比较第3位和第4位的大小,如果第3位比第4位小,则交换两个数的位置。交换后的顺序为 53 99 81 21 67。
最后,比较第4位和第5位的大小。经过4次比较后原数列变为 53 99 81 67 21。
我们发现4次比较后最小的一个数已经到了最后一位,回忆一下刚才比较的过程:每次都是比较相邻的两个数,如果后面的数比前面的数大,则交换这两个数的的位置。一直比较下去直到最后两个数比较完毕后,最小的数就移动到了最后一个。就如同气泡一样,一步一步往后“翻滚”,直到最后一位。所以这个排序的方法被形象的称为“冒泡排序”。
仔细观察,排序进行到现在我们只讲5个数中最小的一个数移动到了相应的位置。,即只有最小的一个数归位了。每将一个数归位我们将其称为“一趟”。下面我们重复刚才的过程,将剩下的4个数逐一归位。
“第二趟”我们的目标是将第2小的数归位。首先还是比较第1位和第2位,如果第1位比第2位小,则交换这两个数的位置。交换后这5个数的顺序是 99 53 81 67 21。接下来的过程同“第一趟”一样,依次比较第2位和第3位,第3位和第4位即可。注意现在我们已经不必比较第4位和第5位了,因为经过“第一趟”的比较与交换已经确定了第5位上放的是最小的数了。第二趟结束后的顺序为 99 81 67 53 21.
“第三趟”、“第四趟”也是一样的。这里有同学就要问了,这不是已经排好了吗?还要继续吗?当然,这个例子纯属巧合,再换一组例子可能就不是了。想一想,什么样的数据样例需要到最后一趟才能排好序。
从上述过程中我们不难看出“冒泡排序”的原理:每一趟只能确定将一个数归位。总结一下就是:如果有n个数进行排序,只需将n-1个数归位,也就是说要进行n-1趟操作。而每一趟都需要从第1位开始进行相邻两个数的比较,将较小的数放在后面,比较交换完毕后继续比较下面两个相邻数的大小,重复此步骤,直到最后一个数归位。
呼......絮絮叨叨了半天,也不知道有没有介绍清楚,还是直接看代码吧。
#include <stdio.h>
int main()
{
int a[100], i, j, t, n;
scanf("%d", &n); //输入一个数n,表示接下来有n个数
for(i = 1; i <= n; i++)
scanf("%d", &a[i]);
//冒泡排序的核心代码
for(i = 1; i <= n-1; i++) //n个数排序,只用进行n-1趟
{
//从第1位开始比较直到最后一个尚未归位的数
for(j = 1; j <= n-i; j++)
{
//比较大小并交换
if(a[j] > a[j+1]) //升序
//if(a[j] < a[j+1]) //降序
{
t = a[j];
a[j] = a[j+1];
a[j+1] = t;
}
}
}
//输出结果
for(i = 1; i <= n; i++)
printf("%d", a[i]);
getchar();getchar(); //同system("pause");
return 0;
}
输入样例:
10 8 99 50 22 55 1 6 9999 1000 0
输出样例:
0 1 6 8 22 50 55 99 1000 9999
现在我们可以用程序满足董老师的需求了,只需要将上面的代码稍加修改就好了。
#include <stdio.h>
struct student
{
char name[21];
int score;
}; //这里创建了一个结构体用来存储姓名和分数
int main()
{
struct student a[100], t;
int i, j, n;
scanf("%d", &n); //输入一个数n
for(i = 1; i <= n; i++) //循环读入n个人名和分数
scanf("%s %d", a[i].name, &a[i].score);
//按分数从高到低 进行排序
for(i = 1; i <= n-1; i++)
{
for(j = 1; j <= n-i; j++)
{
if(a[j].score < a[j+1].score)
{
t = a[j];
a[j] = a[j+1];
a[j+1] = t;
}
}
}
printf("%s", a[1]); //输出人名
getchar();getchar();
return 0;
}
输入数据:
5 minmin 95 zhezhe 93 ruirui 95 shuaishuai 92 rourou 98
运行结果:
rourou
冒泡排序的核心部分是双重嵌套循环。不难看出冒泡排序的复杂度是O(N²),这是一个非常高的时间复杂度。那还有没有更好的排序算法呢?当然,我们下节继续——快速排序。