算法(排序算法之冒泡排序及其变式)

本文详细介绍了冒泡排序算法,包括单向冒泡排序和双向冒泡排序的原理与实现。通过示例和图解,阐述了冒泡排序如何将无序数组转换为有序,并讨论了优化策略,如使用标志变量来处理已排序数组,以及双向冒泡如何更有效地排序。最后提供了C语言实现的代码示例。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

(此篇实现语言为C语言)

冒泡排序可能是我们在学习谭浩强C语言里面比较痛苦的算法之一了,不少人也因为缺少对于双重循环的把控而出现写不出以及处处出错的问题。今天也是来谈谈作为交换排序之一的冒泡排序究竟是如何实现的。


单向冒泡排序

在这里为大家准备一个待排序的数组:

 而我们的冒泡排序是如何将这个无序的数组转化为有序的呢?

首先要明白,冒泡排序是两个数之间进行比较,将较大的数字放置在小的数字之后(由小到大排序,由大到小反之,本文全篇探讨由小到大排序),而每次我们的比较完了以后步长为1地往下继续比较,这样便会出现有趣的现象:

 (画的可能有点抽象,大家请自行脑补一下。。。)我们每次进行两两比较,大的元素后置,也就是大的泡泡往上不断往上水面上浮,每次都会将最大的元素后置,也就是最大的泡泡到了最接近水面的地方,而到第一次的排序的最后,我们也会发现,最大的元素被放置到了最后。

 虽然前面仍然是无序的,但我们至少把最大的元素放到了最后不是?于是我们每次将数组遍历的长度减一,把最大的元素放到最后,那么最后不就是一个有序的数组吗?相当于每次最大的泡泡都会冒出水面,第二次的时候,第二大的泡泡直接作为最大的泡泡往上冒出水面,以此类推,直至所有的泡泡冒出水面。

大伙也可以自己尝试脑子里跑一边第二轮的冒泡排序

 九轮之后,我们将九个元素全部置成我们脑海中逻辑数组(也就是每次减少一个遍历长度的所抽象出来的数组)的最大位上,即得到了一个有序数组{ 1 ,2 ,3 ,4 ,5 ,6 ,7 ,8 ,9  }

那我们的代码应该如何实现呢,每次比较最大的元素都需要后置,且每次比较完成,数组的遍历长度就需要减一.

void bubble(int* arr, int len)
{
    //控制总的冒泡次数的循环,我们每进行一次就会把最大元素置到最后
	for (int i = 0; i < len; i++) 
        //控制执行一次冒泡的循环,每次遍历长度减一通过len-i-1来控制
		for (int j = 0; j < len - i - 1; j++)
		{
			//简简单单的判断交换啦
			if (arr[j] > arr[j + 1])
			{
				int tem = arr[j];
				arr[j] = arr[j + 1];
				arr[j + 1] = tem;
			}
		}
}

通过内外两层循环,外层控制总的冒泡次数,内层控制执行一次的冒泡排序,整个冒泡排序也就在我们的掌握之中咯。

但是,如果我们给出的数组是本身是有序的呢?

是不是那么多次的冒泡其实是没有必要的,小的泡泡完全没必要跟脑袋上的泡泡比较看看是不是要往上浮,我们只需要在第一轮的全遍历中知道这是一个有序数组然后直接出循环即可。

这里,就需要一个flag标志变量了,我们的pro版本也仅需要一个flag就升级成功。

void bubble(int* arr, int len)
{
	int flag = 1;//默认这是一个有序数组
	for (int i = 0; i < len; i++) //控制总的冒泡次数的循环,我们每进行一次就会把最大元素置到最后
	{
        //控制执行一次冒泡的循环,每次遍历长度减一通过len-i-1来控制
		for (int j = 0; j < len - i - 1; j++)
		{
			//简简单单的判断交换啦
			if (arr[j] > arr[j + 1])
			{
				int tem = arr[j];
				arr[j] = arr[j + 1];
				arr[j + 1] = tem;
				flag = 0;//出现了一次交换,他不是有序的,修改标志位
			}
		}
		if (flag == 1)//如果为有序数组,直接返回,无需比较
			return;
	}
}

双向冒泡排序

我们的单向冒泡程序完成,但是我们同时需要想到一个问题,我们每一次的冒泡过程是不是已结束我们的索引就需要回到最初的位置重新进行下一遍的冒泡,这就没有实现时间的统筹安排嘛,你想想,我们走一遍过去是一个冒泡,走回来还是一个冒泡,这不就是来回都有事干,这才不浪费时间,我们的计算机打工人也能被完美的调用起来。 

在这里也是对我们循环掌握度提高了更高层次的要求。

接下来是图解流程:

向右迈步,将最大元素右置 ,而接下来:

那我们下一轮呢?自己不妨想一下:

 就这样我们执行双向冒泡 length/2取整 次(length为数组长度)之后我们便能得到有序的数组。

接下来我们来程序实现:

void double_bubble(int* arr, int len)
{
	int i = 0;//提供冒泡遍历所需索引
	//最外侧循环,控制整个双向冒泡循环的次数,以及提供每次的双向循环的起始位置
	for (int t = 0; t < len/2; t++)
	{
		//向右迈步进行冒泡,从t位置开始,相当于去掉已冒泡的最小元素
		for (i = t; i < len - t - 1; i++)
		{
			//简简单单的交换
			if (arr[i] > arr[i + 1])
			{
				int tem = arr[i];
				arr[i] = arr[i + 1];
				arr[i + 1] = tem;
			}
		}
		//向左迈步进行冒泡,从向右迈步后所产生的逻辑最右侧开始,直到逻辑最左侧
		for (; i > t; i--)
		{
			//简简单单的交换
			if (arr[i] < arr[i - 1])
			{
				int tem = arr[i];
				arr[i] = arr[i - 1];
				arr[i - 1] = tem;
			}
		}
	}
}

无独有偶,我们应该也要向之前单向冒泡一样考虑最好的状况,即有序数列,仅需一个标志变量即可,十分的简单,既然在之前已经知道了单向冒泡要去如何添加标志变量,双向冒泡可以自己尝试一下添加标志变量。

void double_bubble(int* arr, int len)
{
	int i = 0;//提供冒泡遍历所需索引
	int flag = 1;//默认这是一个有序数组
	//最外侧循环,控制整个双向冒泡循环的次数,以及提供每次的双向循环的起始位置
	for (int t = 0; t < len/2; t++)
	{
		//向右迈步进行冒泡,从t位置开始,相当于去掉已冒泡的最小元素
		for (i = t; i < len - t - 1; i++)
		{
			//简简单单的交换
			if (arr[i] > arr[i + 1])
			{
				int tem = arr[i];
				arr[i] = arr[i + 1];
				arr[i + 1] = tem;
				flag = 0;
			}
		}
		if (flag == 1)
			return;
		//向左迈步进行冒泡,从向右迈步后所产生的逻辑最右侧开始,直到逻辑最左侧
		for (; i > t; i--)
		{
			//简简单单的交换
			if (arr[i] < arr[i - 1])
			{
				int tem = arr[i];
				arr[i] = arr[i - 1];
				arr[i - 1] = tem;
			}
		}
	}
}

实验全部代码

#include<stdio.h>
#define ARR_LEN 9

//单向冒泡
void bubble(int* arr, int len)
{
	int flag = 1;//默认这是一个有序数组
	for (int i = 0; i < len; i++) //控制总的冒泡次数的循环,我们每进行一次就会把最大元素置到最后
	{
		for (int j = 0; j < len - i - 1; j++)//控制执行一次冒泡的循环,每次遍历长度减一通过len-i-1来控制
		{
			//简简单单的判断交换啦
			if (arr[j] > arr[j + 1])
			{
				int tem = arr[j];
				arr[j] = arr[j + 1];
				arr[j + 1] = tem;
				flag = 0;//出现了一次交换,他不是有序的,修改标志位
			}
		}
		if (flag == 1)//如果为有序数组,直接返回,无需比较
			return;
	}
}

//双向冒泡
void double_bubble(int* arr, int len)
{
	int i = 0;//提供冒泡遍历所需索引
	int flag = 1;//默认这是一个有序数组
	//最外侧循环,控制整个双向冒泡循环的次数,以及提供每次的双向循环的起始位置
	for (int t = 0; t < len/2; t++)
	{
		//向右迈步进行冒泡,从t位置开始,相当于去掉已冒泡的最小元素
		for (i = t; i < len - t - 1; i++)
		{
			//简简单单的交换
			if (arr[i] > arr[i + 1])
			{
				int tem = arr[i];
				arr[i] = arr[i + 1];
				arr[i + 1] = tem;
				flag = 0;
			}
		}
		if (flag == 1)
			return;
		//向左迈步进行冒泡,从向右迈步后所产生的逻辑最右侧开始,直到逻辑最左侧
		for (; i > t; i--)
		{
			//简简单单的交换
			if (arr[i] < arr[i - 1])
			{
				int tem = arr[i];
				arr[i] = arr[i - 1];
				arr[i - 1] = tem;
			}
		}
	}
}

void show_bubble(int* arr, int len)
{
	for (int i = 0; i < len; i++)
		printf("%d ", arr[i]);
}

int main()
{
	int arr[] = { 2,3,1,6,7,8,9,4,5 };
	int arr1[] = { 2,3,1,6,7,8,9,4,5 };
	
	//int brr[] = { 1,2,3,4,5,6,7,8,9 };
	printf("this is result of arr bubble\n");
	bubble(arr,ARR_LEN);
	show_bubble(arr, ARR_LEN);

	printf("\nthis is result of arr double_bubble\n");
	double_bubble(arr1, ARR_LEN);
	show_bubble(arr1, ARR_LEN);
	

	return 0;
}

 (如有问题,欢迎指正)

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

青锋杨

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

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

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

打赏作者

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

抵扣说明:

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

余额充值