1.定义
冒泡排序(Bubble Sort)是一种简单的排序算法。
它重复地遍历待排序的序列,一次比较两个元素,如果它们的顺序错误就把它们交换过来。
遍历工作由始至终,直到没有再需要交换,也就是说该序列已经排序完成。
生活中的例子:
假设你有一组朋友(我们称之为“朋友列表”),他们站成一排,并且他们想按照年龄从小到大的顺序重新排列。
一开始,朋友们的顺序是随机的:
小明(25岁)、小红(20岁)、小刚(22岁)、小李(23岁)、小丽(19岁)
现在,我们来进行“冒泡排序”:
- 第一轮:
- 小明和小红比较年龄,小明比小红大,所以他们交换位置。
- 现在顺序是:小红(20岁)、小明(25岁)、小刚(22岁)、小李(23岁)、小丽(19岁)
- 接着,小明和小刚比较,小明比小刚大,他们交换。
- 顺序变为:小红(20岁)、小刚(22岁)、小明(25岁)、小李(23岁)、小丽(19岁)
- ...(继续这个过程,直到队伍的最后)
- 最终第一轮结束后,年龄最大的小明“浮”到了最右边:小红(20岁)、小刚(22岁)、小李(23岁)、小丽(19岁)、小明(25岁)
- 第二轮(此时我们已经知道小明是年龄最大的,所以不再考虑他):
- 小红和小刚比较,小红比小刚小,所以他们不交换。
- 接着,小刚和小李比较,小刚比小李小,所以他们也不交换。
- 但是,小李和小丽比较时,小李比小丽大,所以他们交换位置。
- 最终第二轮结束后,第二大的小李“浮”到了它应该的位置:小红(20岁)、小刚(22岁)、小丽(19岁)、小李(23岁)、小明(25岁)
- 第三轮(此时我们知道小明和小李分别是最大和第二大的,所以不再考虑他们):
- 小红和小刚比较,小红比小刚小,不交换。
- 接着,小刚和小丽比较,小刚比小丽大,他们交换位置。
- 最终第三轮结束后,所有朋友都按照年龄从小到大排好了:小红(20岁)、小丽(19岁)、小刚(22岁)、小李(23岁)、小明(25岁)
这个过程就是冒泡排序的一个生动示例。在每一步中,我们都比较相邻的“朋友”(元素),如果顺序不对就交换他们,直到整个列表排序完成。
2.思想
对任何给定的数组,进行多次遍历,每次都从数组的第一个元素开始,比较相邻的两个元素,如果它们的顺序错误,就交换它们。
经过第一次遍历后,最大的元素就位于数组的末尾了,因为它会像气泡一样"浮"到数组的尾部。
然后,算法对数组的前n-1个元素进行同样的操作。依此类推,直到整个数组排序完成。
3.举例
待排序数组:[64, 34, 25, 12, 22]
第一轮排序:
- 比较 64 和 34,64 > 34,交换位置,得到
[34, 64, 25, 12, 22]
- 比较 64 和 25,64 > 25,交换位置,得到
[34, 25, 64, 12, 22]
- 比较 64 和 12,64 > 12,交换位置,得到
[34, 25, 12, 64, 22]
- 比较 64 和 22,64 > 22,交换位置,得到
[34, 25, 12, 22, 64]
第一轮过后,将64这个最大的元素固定到了最后的位置,共进行了4次交换。
第二轮排序:
- 因为64的位置已经固定,所以只对前4个进行排序。
- 比较 34 和 25,34 > 25,交换位置,得到
[25, 34, 12, 22, 64]
- 比较 34 和 12,34 > 12,交换位置,得到
[25, 12, 34, 22, 64]
- 比较 34 和 22,34 > 22,交换位置,得到
[25, 12, 22, 34, 64]
第二轮过后,将34这个第二大的元素固定到了倒数第二个位置,共进行了3次交换。
第三轮排序:
- 因为64和34的位置已经确定,只需对前3个进行排序。
- 比较 25 和 12,25 > 12,交换位置,得到
[12, 25, 22, 34, 64]
- 比较 25 和 22,25 > 22,交换位置,得到
[12, 22, 25, 34, 64]
第三轮过后,将25这个第三大的元素位置确定,共进行了2次交换。
第四轮排序:
- 因为64、34和25的位置已经确定,只需对前两个进行排序。
- 比较 12 和 22,12 < 22,不交换,得到
[12, 22, 25, 34, 64]
第四轮过后,没有发生交换,因为前两个元素已经是有序的。此时总共5个元素,已经排序好4个,从而最后一个自然而然就是排好序的了。
4.总结
假设数组中有5个元素,看懂下面的规律可以事半功倍
轮数 每轮交换的次数
1 4
2 3
3 2
4 1
设总的元素个数为n,那么由上边的排序过程可以看出:
(1)总计需要进行(n-1)轮排序,也就是(n-1)次大循环
(2)每轮排序比较的次数逐轮减少
(3)如果发现在某趟排序中,没有发生一次交换, 可以提前结束冒泡排序(使用哨兵---详见下面的代码优化)
5.伪代码
规律:轮数依次+1
轮数+交换的次数为n-1
for(i = 0; i < len - 1;i++){//控制轮数
for(j = 0;j < len - 1 -i;j++){//控制每轮交换的次数
if(a[j] > a[j + 1]){//就是前面的数比后面的数要大,所以要交换位置
交换数据
}
}
}
6.代码
#include<stdio.h>
void output_array(int *p,int len){
int i = 0;
for(i = 0;i < len;i++){
printf("%d ",p[i]);
}
printf("\n");
return;
}
void bubble_sort(int *p,int len){
int i = 0,j = 0;
//比较轮数
for(i = 0;i < len -1;i++){
//每一轮交换的次数
for(j = 0;j < len - 1 - i;j++){
//p[j] == *(p + j)
if(p[j] > p[j + 1]){
//交换数据
int temp = p[j];
p[j] = p[j + 1];
p[j +1] = temp;
}
}
}
return;
}
int main(){
int a[5] = {50,40,30,20,10};
int len = sizeof(a) / sizeof(a[0]);
printf("原始数据为:\n");
output_array(a,len);
printf("经过冒泡排序之后的数据为:\n");
bubble_sort(a,len);
output_array(a,len);
return 0;
}
7.运行
8.优化代码
比如说有一个数组元素是[10,20,30,40,50],发现已经是有序的了,那为什么还要继续进行排序呢,所以这个优化的排序算法就是通过设置一个“哨兵”来进行判断数组元素是否有序,如果有序,就直接退出循环。
#include<stdio.h>
void output_array(int *p, int len){
int i = 0;
for(i = 0; i < len; i++){
printf("%d ", p[i]);
}
printf("\n");
return;
}
int bubble_sort(int *p, int len, int *swap_count){
int flag = 0;
int i = 0, j = 0;
*swap_count = 0; // 初始化交换次数为0
for (i = 0; i < len - 1; i++) {
flag = 0; // 每轮开始前重置flag为0
for (j = 0; j < len - 1 - i; j++) {
if (p[j] > p[j + 1]) {
flag = 1; // 发生了交换,将flag置为1
int temp = p[j];
p[j] = p[j + 1];
p[j + 1] = temp;
(*swap_count)++; // 增加交换次数
}
}
//如果没有发生交换,则退出循环
//怎么判断的呢?
//因为刚开始初始化的是哨兵flag为0,然后如果发生了交换,会置为1
//置为1之后执行下面的if语句,发现是0,所以接着进行外部的循环
//然后如果没有发生交换,flag不还是0吗,所以执行下面的if语句,
//即!0 = 1为真,执行break跳出循环了
if (!flag) {
break;
}
}
return 0;
}
int main(){
int swap_count = 0; // 定义一个变量来记录交换次数
int a[5] = {10, 20, 30, 40, 50};
int len = sizeof(a) / sizeof(a[0]);
printf("原始数据为:\n");
output_array(a, len);
printf("经过冒泡排序之后的数据为:\n");
bubble_sort(a, len, &swap_count); // 传递swap_count的地址
output_array(a, len);
printf("冒泡排序执行交换了%d次\n", swap_count);
return 0;
}
9.优劣
优点
- 易于理解:冒泡排序算法的逻辑很直观,它重复地遍历要排序的数列,一次比较两个元素,如果顺序错误就交换它们。
- 稳定排序:冒泡排序是一种稳定排序算法,即相等的元素在排序后保持原有的顺序。
- 空间复杂度低:冒泡排序只需要一个额外的空间(用于交换的临时变量),因此它的空间复杂度是O(1)。
缺点
- 时间效率低:对于大数据集,冒泡排序的效率非常低。因为它的时间复杂度是O(n^2)。
- 不必要的比较:在排序过程中,如果已经确定一部分元素已经是有序的(例如,在数列的末尾),但冒泡排序仍然会进行不必要的比较和交换操作。但是也可以通过设置上面的哨兵来解决。