算法篇:冒泡排序

冒泡排序

  冒泡排序是低效的排序算法,通过不断交换 相邻逆序对 来实现排序。在该算法中因为越大的元素会经由交换慢慢“浮”到数列的顶端,故名冒泡排序。
  冒泡排序一般是学到的第一个排序算法,实现较为简单,且容易理解。
  冒泡排序的平均时间复杂度为 O ( n 2 ) O(n^2) O(n2),最坏情况下时间复杂度为 O ( n 2 ) O(n^2) O(n2)。如果遍历时检查是否进行了交换,那么最好情况下时间复杂度为 O ( n ) O(n) O(n)

1. 常规冒泡排序

在这里插入图片描述

  从头开始遍历,依次比较相邻的两个元素,如果两个元素大小关系不符合顺序则交换两个元素。每次遍历,都可以在剩余的元素中选出一个最值放到合适的位置上。

任意情况
复杂度 O ( n 2 ) O(n^2) O(n2)
void bubbleSort(int a[], int length)
{
	for (int i = length - 1; i > 0; i--) {
		for (int j = 0; j < i; j++) {
			if (a[j] > a[j + 1]) {
				int temp = a[j];
				a[j] = a[j+1];
				a[j+1] = temp;
			}
		}
	}
}

  上面算法的缺点在于不管数列是否已经有序,都要继续遍历,不管数列中的值如何,时间复杂度都为   O ( n 2 ) \ O(n^2)  O(n2)
  这对于小范围内无序的数列来说会花费过多不必要的时间。例如,仅仅是交换有序数列中某两个相邻的数,上述算法依然是要遍历 n − 1 n-1 n1次,实际仅需要遍历一次即可。下面的算法就是对这种情况做出的改进。

2. 可辨别数列有序的冒泡排序

  对上面那种冒泡排序做了一个改进,设立了标志位,记录遍历过程中是否有发生过交换(有相邻逆序对就会交换)。如果遍历一次后没有发生过交换,说明每两个相邻元素之间都满足 a i ⩽ a i + 1 a_i \leqslant a_{i+1} aiai+1 的关系,那么整个数列的元素之间的关系是 a 0 ⩽ a 1 ⩽ a 2 ⩽ ⋯ ⩽ a n − 2 ⩽ a n − 1 a_0 \leqslant a_1 \leqslant a_2 \leqslant \cdots \leqslant a_{n-2} \leqslant a_{n-1} a0a1a2an2an1,此时数列有序,排序已经完成,可以停止遍历。

最坏情况最好情况
复杂度 O ( n 2 ) O(n^2) O(n2) O ( n ) O(n) O(n)
void bubbleSort(int a[], int length)
	{
		for (int i = length - 1; i > 0; i--) {
			//遍历前初始化为:(没有进行过交换)
			bool exchange = false;
			
			for (int j = 0; j < i; j++) {
				if (a[j] > a[j + 1]) {
					int temp = a[j];
					a[j] = a[j+1];
					a[j+1] = temp;
					
					//记录此次遍历进行了交换
					exchange = true;
				}
			}

			// 没有进行过交换说明已经排序完成,退出
			if (!exchange)
				break;
		}
	}

  这个算法只对混乱程度很小的数列较为有效,如果数列的元素值是随机的,那么在中间就完成排序的情况非常少,这样排序消耗的时间实际和常规冒泡排序差不多。

3. 鸡尾酒排序——改进的冒泡排序

  鸡尾酒排序又称 双向冒泡排序,主要是通过发生交换的位置来确定有序区间和无序区间的分界点,缩小遍历区间,从而减少遍历时间。

最坏情况最好情况
复杂度 O ( n 2 ) O(n^2) O(n2) O ( n ) O(n) O(n)

  在可辨别数列有序的冒泡算法中,仅仅是记录每次遍历是否进行过交换来判断数列是否有序。其实还可以记录发生交换时的位置。
  在冒泡排序中(假设要按照升序排序),遍历时如果相邻两个元素大小关系不符,就会交换它们两个的位置,如果没有进行交换,说明它们两个的大小关系是符合的。如果在遍历的后半部分没有发生交换,说明这后半部分数已经有序了(如果有 a ⩽ b a \leqslant b ab b ⩽ c b \leqslant c bc,那么有 a ⩽ b ⩽ c a \leqslant b \leqslant c abc)。并且冒泡排序遍历时会把最大值一直往后移动,所以每一次交换,当前位置上一定是遍历过的部分的最值。而后面这部分没有发生交换,按照之前的 a ⩽ b ⩽ c a \leqslant b \leqslant c abc 关系,这部分的值都会比前面这部分大(或者都相等)。因此这部分的元素已经是在正确位置上了,下一次就不需要再遍历了,即使遍历,也不会有更大的值来使这部分元素进行交换。

在这里插入图片描述

  因此在遍历时将发生交换的位置设置为遍历的边界,只需要遍历边界以内的部分即可。同时,还能通过双向遍历的方式缩小两边的边界。等到左右边界重合或左边界大于右边界时,排序就完成了。
  这个就是鸡尾酒排序算法,在数列混乱程度较小的情况下比常规冒泡排序快得多。

在这里插入图片描述

最坏情况下最好情况下
复杂度 O ( n 2 ) O(n^2) O(n2) O ( n ) O(n) O(n)
//鸡尾酒排序
void cocktailSort(int a[], int length)
{
	int left = 0, right = length - 1;	//遍历的范围
	int bound;

	while (left < right) {
		//从左边界遍历至右边界,以最后一次交换的位置作为边界
		bound = left;	//初始值:如果没有发生交换,则取起始位置
		for (int i = left; i < right; i++) {
			if (a[i] > a[i + 1]) {
				int temp = a[i];
				a[i] = a[i + 1];
				a[i + 1] = temp;

				bound = i;	
			}
		}

		//修改右边界
		right = bound;

		//从右边界遍历至左边界,以最后一次交换的位置作为边界
		bound = right;//初始值:如果没有发生交换,则取起始位置
		for (int i = right; i > left; i--) {
			if (a[i - 1] > a[i]) {
				int temp = a[i - 1];
				a[i - 1] = a[i];
				a[i] = temp;

				bound = i;
			}
		}

		//修改左边界
		left = bound;
	}
}
  • 2
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 1
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

依稀_yixy

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值