冒泡排序及其优化

简介

冒泡排序是最基础与直观的排序方法之一。想象烧开水的时候,因为气泡的密度比水小,小气泡会从水壶底部慢慢升起。而冒泡排序之所以叫冒泡排序,正是因为这种排序算法的每一个元素都像小气泡一样,根据自身大小,一点一点向着数组的一侧移动。

定义

基本思想: 依次对相邻两个数比较大小,较大的数下沉,较小的数冒起来。
如下所示:对于原始数据为 arr[7] = {14,12,2,10,100,5,-5} 的数组
第一次判断14与12,交换: 12,14,2,10,100,5,-5
第二次判断14与2,交换: 12,2,14,10,100,5,-5
第三次判断14与10,交换: 12,2,10,14,100,5,-5
第四次判断14与100,不交换: 12,2,10,14,100,5,-5
第五次判断100与5,交换: 12,2,10,14,5,100,-5
第六次判断100与-5,交换: 12,2,10,14,5,-5,100
这样就完成了一轮判断,这一轮将数组最大值100放到了数组最后一位arr[6]上。
接下来只需要对下标为0到5的数进行新一轮的比较即可。
这样进行每一轮判断,直到没有可以交换的数,排序就完成了

算法与优化

常规方法

根据上面分析,可以看出,我们在进行每一次大循环的时候,还要进行一个小循环来遍历相邻元素并交换。所以我们需要要有两层循环。对于数组arr[n]:
外层循环: 即主循环,需要辅助我们找到当前第 i 小的元素来让它归位。所以我们会一直遍历 n-1 次,每次归位1个,当后面n-1个都归位了,那么剩下的一位也归位了。
内层循环: 即副循环,需要辅助我们进行相邻元素之间的比较和换位,把大的或者小的浮到水面上。所以我们会一直遍历 n-1-i 次这样可以保证没有归位的尽量归位,而归位的就不用再比较了。
代码如下:

void BubbleSort(vector<int>& arr) {
	int temp = 0;
	for (int i = 0;i < arr.size() - 1;++i) {
		for (int j = 0;j < arr.size() - 1 - i ++j) {
			if (arr[j] > arr[j+1]) {
				temp = arr[j];
				arr[j] = arr[j + 1];
				arr[j + 1] = temp;
			}
		}
	}
}

第一次优化

常规方法直接进行了两次循环,这些循环很多时候是多余的。
比如对于5,1,2,3,4 ,第一轮循环后,已经达到1,2,3,4,5,但是常规方法会继续进行接下来的几轮,实际上数据已经没有变化了。
针对这种情况,我们可以想到,如果某一轮中已经没有产生数据交换了,那么排序实际已经完成了,可以直接终止,因此我们可以增加一个flag,用于判断每一轮是否存在数据交换,如果没有,则完成排序,代码如下:

void BubbleSort(vector<int>& arr) {
	int temp = 0;
	for (int i = 0;i < arr.size() - 1;++i) {
		bool flag = false;//增加flag,判断是否发生数据交换
		for (int j = 0;j < arr.size() - 1 - i;++j) {
			if (arr[j] > arr[j+1]) {
				flag = true;//如果发生数据交换,标志位变化
				temp = arr[j];
				arr[j] = arr[j + 1];
				arr[j + 1] = temp;
			}
		}
		if (!flag)//如果某轮未发生数据交换,结束排序
			break;
	}
}

第二次优化

进一步的,可以考虑具体每一次变化,如果某轮到一个位置后的所有数据不发生交换了,那么下一轮也不会再发生交换了(这里大家可以自己画图分析一下为什么(>▽<))
比如对于3,2,1,4,5 ,第一轮循环中,除了将3与2交换,将3与1交换,达到2,1,3,4,5,后面位置的数据没有再交换。那么下一轮只需要判断2与1就可以了。
针对这种情况,我们可以记录每一轮最后一次数据交换的位置,下一轮只要判断到这个位置就可以了,代码如下:

void BubbleSort(vector<int>& arr) {
	int temp = 0;
	int count = 0;
	int pos = arr.size() - 1;
	for (int i = 0;i < arr.size() - 1;++i) {
		bool flag = false;
		int tempPos = pos;
		for (int j = 0;j < pos;++j) {//只判断到最后一次数据交换的位置
			if (arr[j] > arr[j+1]) {
				flag = true;
				tempPos = j;//更新最后一次数据交换的位置
				temp = arr[j];
				arr[j] = arr[j + 1];
				arr[j + 1] = temp;
			}
		}
		pos = tempPos;//记录最后一次数据交换的位置
		if (!flag)
			break;
	}
}

性能分析

由以上分析可以看到,冒泡排序经过双重循环,经优化后最少只需要循环一轮就可以完成排序,因此最好时间复杂度是O(n) 。
由于冒泡排序是交换排序,每一次只交换相应位置上的数值,不需要消耗额外的空间。
两个相等数相邻时,冒泡排序不会交换顺序;当两个相等元素比较远时,也只是会把他们交换到相邻的位置。他们的位置前后关系不会发生任何变化,所以算法是稳定的。
综上:
时间复杂度O(n^2)
最好时间复杂度O(n)
最好时间复杂度O(n)
额外空间1
稳定

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值