本人0基础开始学编程,我能学会的,你也一定可以,学会多少写多少。
下载安装请从官网入手,社区版本即可,这里主要使用的软件是VS2019,图标如下。
上一篇:从0开始学c语言-15-数组作为函数参数的地址、值与类型变化深度解析_阿秋的阿秋不是阿秋的博客-优快云博客
目录
冒泡排序
什么是冒泡排序?
int main()
{
int arr[] = { 9,8,7,6,5,4,3,2,1,0 };
return 0;
}
现在我们有这样一个数组,想要让数组中储存的元素按照从小到大的顺序排列。
该怎么弄呢?
你想让大的数字排到最后,那么就需要这个数字和所有数字进行比较,才能确定这个数字是最大的。
我们先让数组中的前两个数字进行比较,如果arr[0]>arr[1],那么我们就把数组中的这两个元素进行交换。画图来说,就像这样。
从图中可以看到,我们把9移到最后共进行了9次比较,我们把两两数字之间的比较叫做冒泡,等能比较的数字都比较完了,这叫做一趟。
第二趟又是什么样呢?
第二趟进行了8次冒泡,那我们一共需要多少趟才能实现数组从小到大的排列顺序呢?
一共10个数字,我们只需要挪动9个数字,也就是9趟就可以实现从小到大的排列顺序。
第一趟需要9次冒泡,
第二趟需要8次冒泡,
第三趟需要7次冒泡,
……
第九趟需要1次冒泡。
代码中如何实现
我们先补全一下主函数代码再细化冒泡排列函数的内容。
补全之前,需要思考的是,既然是函数那么我需要传递过去什么?
数组传过去的是什么?我们在上一篇中已经介绍过,数组传参,传过去的是首元素的地址。
为了能够访问数组方便(也就是下标),我们还需要给数组的元素个数一个变量来储存。
那就有人要说了,我知道函数是10个元素,直接写10不就好了?
但是我们要考虑实用性和适用性,如果数组元素不是10,那这个函数就适用性范围太窄了。
所以代码会是这样。
int main()
{
int arr[] = { 9,8,7,6,5,4,3,2,1,0 };
int a = sizeof(arr) / sizeof(arr[0]); //元素个数
bubble(arr, a);
return 0;
}
现在来细化函数中的内容
我们可以知道,冒泡排序有两个重要数据,那就是趟数和比较对数(后续简称对数),那么就需要两个变量来储存它,以方便我们今后的代码书写。
void bubble(int b[], int c) //b本质是指针,int b[]就是int* b
{
int i = 0; //趟数
int j = 0; //对数
}
一共10个数字,需要比较9趟,且每趟都要进行对数比较,为了方便那我们需要使用循环函数。
void bubble(int b[], int c) //b本质是指针,int b[]就是int* b
{
int i = 0; //趟数
int j = 0; //对数
for (i = 0;i < c-1; i++) //趟冒泡-比较9趟
{
//一趟冒泡排序的过程
}
}
现在需要细化一趟冒泡排序的过程。
我们需要明确的是,对数是指什么?
我们该如何确定每趟所需要的冒泡比较次数(也就是对数)呢?
第一趟需要9次冒泡比较,
第二趟需要8次冒泡比较,
第三趟需要7次冒泡比较,
……
第九趟需要1次冒泡比较。
可以发现趟数加上对数=元素个数。那么可以明确的是第n趟需要 元素个数-趟数 个对数比较。
说到这里,我们还没有明确是哪个变量来负责访问数组元素的。
那么对数能否胜任呢?
比如说第一趟的时候,我们需要9次冒泡比较。
现在对数变量j=0,我们就说b[j]>b[j+1]的时候进行交换,否则继续循环直到比较完所有需要比较的数字之后跳出循环,那么代码就会是这样。
void bubble(int b[], int c) //b本质是指针,int b[]就是int* b
{
int i = 0; //趟数
int j = 0; //对数
for (i = 0;i < c-1; i++) //趟 冒泡-比较9趟
{
//一趟冒泡排序的过程
for (j = 0; j < c - 1 - i; j++) //对 冒泡-比较(元素个数-1-趟数)
{
//交换
if (b[j] > b[j + 1])
{
int tmp = b[j];
b[j] = b[j + 1];
b[j + 1] = tmp;
}
}
}
}
注意代码中对数的确定和上面我文字描述的意思是一样的,只不过因为趟数是从0开始算的,便多减了1。
优化
现在看似好像完整了,但是我们还需要优化一下。
如果数组本身就是从小到大的有序排列,那么这样的函数运行会让运行效率降低。
现在我们假设,数组本身就是从小到大的有序排列,那就是说对数比较中没有进行交换的过程。
那么我们就可以在趟数和对数的代码之间加一个变量来实现我们是否跳出循环的需求。
void bubble(int b[], int c) //b本质是指针,int b[]就是int* b
{
int i = 0; //趟数
int j = 0; //对数
for (i = 0;i < c-1; i++) //趟 冒泡-比较9趟
{
//一趟冒泡排序的过程
int flag = 1;
for (j = 0; j < c - 1 - i; j++) //对 冒泡-比较(元素个数-1-趟数)
{
//交换
if (b[j] > b[j + 1])
{
int tmp = b[j];
b[j] = b[j + 1];
b[j + 1] = tmp;
flag = 0;
}
}
//也就是说一趟比较完,没有进行交换
//那就是说这个数组本身就是有序的
if (flag == 1)
break;
}
}
int main()
{
int arr[] = { 9,8,7,6,5,4,3,2,1,0 };
int a = sizeof(arr) / sizeof(arr[0]); //元素个数
bubble(arr, a);
return 0;
}
现在这个代码就完美了!
还有一个小小的问题需要大家来思考一下,这个答案是否正确就看你上一篇学得如何。
请思考:我们把这行代码挪到函数当中去行不行呢?
int a = sizeof(arr) / sizeof(arr[0]); //元素个数
过度过度
分割线
答案:不行!
已经很明确的是,数组在传参的时候,传递过去的是数组首元素的地址,那么在函数中对数组进行sizeof的计算便只会计算首元素的大小,数组名不会代表整个数组。
什么时候数组名会代表整个数组的大小呢?虽然上篇已经学过了,这里再复习一下。
两个例外:数组名会代表整个数组
1、sizeof(数组名) 中的数组名代表整个数组,计算的是整个数组的大小,单位是字节(函数调用中的sizeof(数组名)中的数组名只包含首元素的大小)
2、&数组名 中的数组名代表整个数组,取出整个数组的地址