此篇,我们将从最简单的和最原始的方法,来引入冒泡排序法, 进而通过分析过程、图解、不断优化、反思、总结对其深入理解和掌握。
博主是一个努力学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,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;
}
比如1,2,3,6,5,4,7,8,9,10
其实如果按照冒泡排序,前四次都是无用功对吧,因为这个数列的后四项已经是有序啦。那有没有办法省去这个无用功呢?
我们要想,冒泡排序法,它是每一趟,排好一个数,编译器自己是不能直接知道,除了它排好的这些数,前面的是不是有序的,尽管我们能一眼看出来。那站在编译器的角度,虽然它不知道那些没有开始排的数字是不是有序的,但是它可以知道,最后一次交换的元素是第几个,而知道了这个,其实就可以知道从哪里开始是有序的了。
比如上面那个数组:
于是可以再次优化:
创建一个变量,来记录此趟比较中,最后交换位置的地方,下次循环根据这个来确定比较次数。
#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;//每一趟 中设计一个标记变量
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;
}
}
the_last = j;//注意哦,要写在第二层循环的外头,因为要等一趟比完了,才能确定下一趟的j
if (flag == 0)//flag==0说明一次交换都没有发生,直接跳出外层循环
{
break;
}
}
for ( i = 0; i < N ; i++)//输出
{
printf("%d\n", arr[i]);
}
return 0;
}
以上就是我关于冒泡排序法的讲解了。如果有说法不准确、错误的地方,希望大家能多多指出,也希望各位能给出你们的想法和建议,我会虚心接受的。希望这篇文章能够帮助到大家,让我们一起进步!