【C语言篇】详解“冒泡排序法”–图示、逐步分析、反思、总结归纳、不断优化

勤奋不是嘴上说说而已,而是实际的行动,在勤奋的苦度中持之以恒,永不退却。业精于勤,荒于嬉;行成于思,毁于随。在人生的仕途上,我们毫不迟疑地选择勤奋,她是几乎于世界上一切成就的催产婆。只要我们拥着勤奋去思考,拥着勤奋的手去耕耘,用抱勤奋的心去对待工作,浪迹红尘而坚韧不拔,那么,我们的生命就会绽放火花,让人生的时光更加的闪亮而精彩。

导读:本篇文章讲解 【C语言篇】详解“冒泡排序法”–图示、逐步分析、反思、总结归纳、不断优化,希望对大家有帮助,欢迎收藏,转发!站点地址:www.bmabk.com,来源:原文

🌍此篇,我们将从最简单的和最原始的方法,来引入冒泡排序法, 进而通过分析过程、图解、不断优化、反思、总结对其深入理解和掌握。


👀博主是一个努力学C的大一生👩‍💻,写文章的初衷也是记录自己学习的所得所获所感,并且希望对大家有所帮助,和大家一起学习进步。可能其中有理解不当或者不准确的、不正确的地方,希望大家多多指出!
🎐最后,希望这篇文章能对大家有所帮助!🌷

🛁冒泡排序

🍭引入

首先在可能对于初学者来说,冒泡排序可能比较陌生。
那么先从一个我们比较熟悉的排序题目入手:输入a、b、c三个数,并且按照从大到小的顺序输出。
对于这个题,只有3个数,所以很容易想到的思路是:先把其中一个和另外两个进行比较,找出最大值,剩下的两个数再相互比较,找出最小值(第二大的值)再依次输出即可。
代码可以如下:

#include<stdio.h>

int main()
{
	int a = 0, b = 0, c = 0;
	int temp = 0;//创建一个“中间变量”来实现交换
	scanf("%d %d %d", &a, &b, &c);

	if (a < b)
	{
		temp = a;
		a = b;
		b = temp;//交换a,b的值,使a中放入的是最大值
	}
	if (a < c)
	{
		temp = a;
		a = c;
		c = temp;//通过两个if语句,两次比较和交换,将a中放入最大值
	}
	if (b < c)
	{
		temp = b;
		b = c;
		c = temp;
	}
	printf("%d %d %d", a, b, c);//a,b,c中存放的依次是从大到小的值
}

3个if 语句,实现了3个数字的从大到小的输出。

那4个数字呢?是不是得比较6次?6个if语句?那5个数字?10个数字?哇,简直不敢想象该怎么怎么通过上面的创建单个变量、if语句实现交换来执行了吧!😥

由此,引入了一种比上述排序法相对简单的一种排序方法–冒泡排序法/气泡排序法(至于为什么要叫这个名字,等你了解了之后就会懂啦哈哈)

✍(图解)介绍实现过程:

假设输入10个数字,需要按照从小到大的顺序把它输出。
首先我们确定可以优化的是:
1,10个数字,像3个数那样创建单个变量来储存,那肯定不太现实。我们可以创建1个数组来储存这10个数字。
2,if语句来比较是不是太过于麻烦(得写多少行呀,而且内容都差不多)

其实,由第二点,可以引出冒泡排序法的实现:
图解:
第一趟:
在这里插入图片描述
第二趟:
在这里插入图片描述

最终经过9趟这种操作,我们会发现,这时候,顺序就是有序的了:
在这里插入图片描述

🕵️‍♀️分析过程:

首先,每一趟,我们通过对数组中元素,按顺序,相邻的元素依次进行比较,比较后的结果是:找到了这些比较数字中,最大的那一个元素,并让它落在了正确的位置(最后一个位置),然后第二趟,因为最大的数字已经排好啦,我们需要根据上面的方法,找出剩下9个数字中的最大值(全部数字中第二大值),放在正确的位置(倒数第二个),随后进行第三趟、第四趟…直至数组中的元素成从小到大的有序排列。

🤸‍♂️总结规律:

①、每1趟,确定好1个数字的位置。当有n-1(n为数组总元素个数)数字的位置确定好时(剩下一个元素随着也就确定了),整个数组中的元素即为有序排列。所以:n个数字的排列需要进行n-1趟
②、每一趟,需要进行剩余数字间的依次比较来确定“最大值”(这一趟中的最大值,不是所有数字中的最大值,已经排好的数字不参与比较哦),那么每一趟比较的次数应该是这一趟需要比较数字个数(假设为m)再减1,即m-1
③每一趟需要比较的次数和这趟为第几趟有关。(第一趟:找出10个数字中的最大值,需要比9次;第二趟,找出剩余9个数字中的较大值,需要比8次,第三趟,7次…第9趟:比较1次)。所有,假设有n个数字需要排序,需要走n-1趟,则第x趟需要比较n-x次。

其实是不是理解了冒泡排序法的过程后,更加能理解为什么它叫这个了吧。我的理解是,大泡泡(大的数字)沉下去,小泡泡(小的数)随着冒泡排序法的进行就浮上去了。

👩‍💻开写代码(逐步实现):

🧐思路:step1:我们需要一个数组来储存我们的元素,首先创建一个数组,并且向它里面输入数据:

#include<stdio.h>
int main()
{
	int arr[10] = {0};//创建一个整形数组
	int i = 0;
	for (i = 0; i < 10; i++)//for循环输入10个数字
	{
		scanf("%d", &arr[i]);
	}
	return 0;
}

step2: 根据上面对冒泡排序法过程的分析,我们需要两个循环,来实现这个过程,因为这个循环与次数有这密切的关系,所以我们选择用两个for循环。
外层for循环:控制趟数(每一趟,排好这一趟中最大值的位置)。
内侧for循环:控制比较次数和交换次数。
💗这里要注意以下前面总结规律中趟数、比较次数和数字个数的关系哦。

	int i = 0;//循环控制变量i、j
	int j = 0;
	for (i = 0; i < N-1; i++)//外层循环:控制趟数
	{
		for (j = 0; j < N - 1 - i; j++)//这里j的作用有2个,第二个是充当元素下标(所以从0开始的好处)
		{
			if (arr[j] > arr[j + 1])//交换
			{
				temp = arr[j + 1];
				arr[j + 1] = arr[j];
				arr[j] = temp;
			}
		}
	}

step3:
进行以上两个循环之后,数组里面元素的排列就是从大到小有序排列了,接下来要做的就很简单,再来个for循环把数组中元素依次输出就OK啦!

for ( i = 0; i < N ; i++)//输出
	{
		printf("%d\n", arr[i]);
	}

代码:

#include<stdio.h>
#define N 10
int main()
{
	int arr[N] = {0};//创建一个整形数组
	int temp = 0;//创建一个变量,作用是在交换元素的过程中起到“空瓶”作用
	int i = 0;
	for (i = 0; i < N; i++)//for循环输入10个数字
	{
		scanf("%d", &arr[i]);
	}

	//循环控制变量i、j
	int j = 0;
	for (i = 0; i < N-1; i++)//外层循环:控制趟数
	{
		for (j = 0; j < N - 1 - i; j++)//这里j的作用有2个,第二个是充当元素下标(所以从0开始的好处)
		{
			if (arr[j] > arr[j + 1])//交换
			{
				temp = arr[j + 1];
				arr[j + 1] = arr[j];
				arr[j] = temp;
			}
		}
	}
	
	for ( i = 0; i < N ; i++)//输出
	{
		printf("%d\n", arr[i]);
	}


	return 0;

}

运行结果:
在这里插入图片描述

🚀代码优化:

1,在完成全部趟数之前,数字已经是有序

假如说我们输入为:1,2,3,4,5,6,7,8,10,9.
这个数组其实已经比较有序了,如果我们用冒泡排序法来进行,它其实第一趟就可以把完成数列的有序化,但是按照这个程序,它不会因为这个时候已经有序了,编译器就不执行后面的趟数了,编译器依旧会傻乎乎的把后面八次原封不动的执行完。这无疑是时间和空间的浪费。那么我们应该如何优化一下,使数字已经排列有序时不再执行后面的操作呢?
我们可以这样想:
如果这一趟进行时,如果数组已经是有序的:交换一次都不发生
如果数组没有达到有序(哪怕像上面只要两个元素交换一下就行):一定会发生交换
利用这一点,我们可以定义一个标记变量:

在这里插入图片描述

#include<stdio.h>
#define N 10
int main()
{
	int arr[N] = {0};//创建一个整形数组
	int temp = 0;//创建一个变量,作用是在交换元素的过程中起到“空瓶”作用
	int i = 0;
	for (i = 0; i < N; i++)//for循环输入10个数字
	{
		scanf("%d", &arr[i]);
	}

	//循环控制变量i、j
	int j = 0;
	for (i = 0; i < N-1; i++)//外层循环:控制趟数
	{
		int flag = 0;//每一趟 中设计一个标记变量
		for (j = 0; j < N - 1 - i; j++)//这里j的作用有2个,第二个是充当元素下标(所以从0开始的好处)
		{
			if (arr[j] > arr[j + 1])//交换
			{
				flag = 1;//如果有元素发生了交换,标记为1

				temp = arr[j + 1];
				arr[j + 1] = arr[j];
				arr[j] = temp;
			}
		}
		if (flag == 0)//flag==0说明一次交换都没有发生
		{
			break;
		}

	}
	
	for ( i = 0; i < N ; i++)//输出
	{
		printf("%d\n", arr[i]);
	}


	return 0;

}

2,大多数已经有序

比如1,2,3,6,5,4,7,8,9,10
其实如果按照冒泡排序,前四次都是无用功对吧,因为这个数列的后四项已经是有序啦。那有没有办法省去这个无用功呢?
我们要想,冒泡排序法,它是每一趟,排好一个数,编译器自己是不能直接知道,除了它排好的这些数,前面的是不是有序的,尽管我们能一眼看出来。那站在编译器的角度,虽然它不知道那些没有开始排的数字是不是有序的,但是它可以知道,最后一次交换的元素是第几个,而知道了这个,其实就可以知道从哪里开始是有序的了。
比如上面那个数组:
在这里插入图片描述
于是可以再次优化:
创建一个变量,来记录此趟比较中,最后交换位置的地方,下次循环根据这个来确定比较次数。

  • temp_index:每次循环中,随着元素交换,更新&记录交换元素下标;循环结束后,该变量存储此趟最后一个被交换元素下标
  • the_last:为上次循环temp_index的值,用于确定此次内层循环次数,即一趟的比较次数
    在这里插入图片描述
#define _CRT_SECURE_NO_WARNINGS 1
#include<stdio.h>
#define N 10
int main()
{
	int arr[N] = { 0 };//创建一个整形数组
	int temp = 0;//创建一个变量,作用是在交换元素的过程中起到“空瓶”作用
	int i = 0;
	for (i = 0; i < N; i++)//for循环输入10个数字
	{
		scanf("%d", &arr[i]);
	}

	//循环控制变量i、j
	int j = 0;
	int the_last = N - 1;

	for (i = 0; i < N - 1; i++)//外层循环:控制趟数
	{
		int flag = 0;//每一趟 中设计一个标记变量
		int temp_index = 0;//标记最后一个交换元素下标
		for (j = 0; j < the_last; j++)//这里j的作用有2个,第二个是充当元素下标(所以从0开始的好处)
		{
			if (arr[j] > arr[j + 1])//交换
			{
				flag = 1;//如果有元素发生了交换,标记为1

				temp = arr[j + 1];
				arr[j + 1] = arr[j];
				arr[j] = temp;
				temp_index = j;//每次交换,更新这个临时下标的值
			}
		}
		the_last = temp_index;//注意哦,要写在第二层循环的外头,因为要等一趟比完了,才能确定下一趟的j<the_last

		if (flag == 0)//flag==0说明一次交换都没有发生,直接跳出外层循环
		{
			break;
		}

	}

	for (i = 0; i < N; i++)//输出
	{
		printf("%d\n", arr[i]);
	}


	return 0;

}

🐬以上就是我关于冒泡排序法的讲解了。如果有说法不准确、错误的地方,希望大家能多多指出,也希望各位能给出你们的想法和建议,我会虚心接受的。希望这篇文章能够帮助到大家,让我们一起进步!🐳

版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 举报,一经查实,本站将立刻删除。

文章由极客之音整理,本文链接:https://www.bmabk.com/index.php/post/142481.html

(0)
飞熊的头像飞熊bm

相关推荐

发表回复

登录后才能评论
极客之音——专业性很强的中文编程技术网站,欢迎收藏到浏览器,订阅我们!