排序算法之冒泡排序

1.定义

冒泡排序(Bubble Sort)是一种简单的排序算法。

它重复地遍历待排序的序列,一次比较两个元素,如果它们的顺序错误就把它们交换过来。

遍历工作由始至终,直到没有再需要交换,也就是说该序列已经排序完成。

生活中的例子:

假设你有一组朋友(我们称之为“朋友列表”),他们站成一排,并且他们想按照年龄从小到大的顺序重新排列。

一开始,朋友们的顺序是随机的:

小明(25岁)、小红(20岁)、小刚(22岁)、小李(23岁)、小丽(19岁)

现在,我们来进行“冒泡排序”:

  1. 第一轮
    • 小明和小红比较年龄,小明比小红大,所以他们交换位置。
    • 现在顺序是:小红(20岁)、小明(25岁)、小刚(22岁)、小李(23岁)、小丽(19岁)
    • 接着,小明和小刚比较,小明比小刚大,他们交换。
    • 顺序变为:小红(20岁)、小刚(22岁)、小明(25岁)、小李(23岁)、小丽(19岁)
    • ...(继续这个过程,直到队伍的最后)
    • 最终第一轮结束后,年龄最大的小明“浮”到了最右边:小红(20岁)、小刚(22岁)、小李(23岁)、小丽(19岁)、小明(25岁)
  2. 第二轮(此时我们已经知道小明是年龄最大的,所以不再考虑他):
    • 小红和小刚比较,小红比小刚小,所以他们不交换。
    • 接着,小刚和小李比较,小刚比小李小,所以他们也不交换。
    • 但是,小李和小丽比较时,小李比小丽大,所以他们交换位置。
    • 最终第二轮结束后,第二大的小李“浮”到了它应该的位置:小红(20岁)、小刚(22岁)、小丽(19岁)、小李(23岁)、小明(25岁)
  3. 第三轮(此时我们知道小明和小李分别是最大和第二大的,所以不再考虑他们):
    • 小红和小刚比较,小红比小刚小,不交换。
    • 接着,小刚和小丽比较,小刚比小丽大,他们交换位置。
    • 最终第三轮结束后,所有朋友都按照年龄从小到大排好了:小红(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.优劣

优点

  1. 易于理解:冒泡排序算法的逻辑很直观,它重复地遍历要排序的数列,一次比较两个元素,如果顺序错误就交换它们。
  2. 稳定排序:冒泡排序是一种稳定排序算法,即相等的元素在排序后保持原有的顺序。
  3. 空间复杂度低:冒泡排序只需要一个额外的空间(用于交换的临时变量),因此它的空间复杂度是O(1)。

缺点

  1. 时间效率低:对于大数据集,冒泡排序的效率非常低。因为它的时间复杂度是O(n^2)。
  2. 不必要的比较:在排序过程中,如果已经确定一部分元素已经是有序的(例如,在数列的末尾),但冒泡排序仍然会进行不必要的比较和交换操作。但是也可以通过设置上面的哨兵来解决。
  • 17
    点赞
  • 13
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值