C语言初阶之数组讲解(1)(三大排序其二)

目录

数组

数组的定义

需要牢记的数组名的含义

数组的三种排序

1.冒泡排序

什么是冒泡排序

1. 最后一次排序多余,没有必要

2.第几趟排序的最后几个数都是已经是有序了,没有必要在比较

3.有时候可能排序早就完成了

2.选择法排序

3.插入排序(不讲解)


数组

在学习数组之前,大家是否还记得之前讲过的变量和常量?变量是一个储存常量的盒子,在实际情况中我们可能需要很多个盒子,并且这些盒子之间没有联系。那我们要怎么一次性建立许多有关系的盒子呢?这个时候我们需要使用数组

数组的定义

数组是一组相同类型元素的集合,简单来说就是一个大盒子,这个大盒子里又装了许多的小盒子。

我们研究代码的时候总是不断的追求减少代码量,递归和循环就是如此
数组同样可以做到
int a=0;
int b=0;
int c=0;
int d=0;
这个时候我们可以使用数组来完成
int arr[4]={0};
这样看者是不是大大的减少了代码量呢

同时,数组这个大盒子里的小盒子在内存方面是连续存在的,所有在进行很多实际问题时,我们完全可以利用这种性质

int arr[10];   //定义了一个数组名是arr,可以存放十个整形的数组
char ch[20];   //定义了一个数组名是ch,可以存放二十个字符的数组
float farr[5];//定义了一个数组名是farr,可以存放五个单精度浮点数的数组

在声明一个数组的时候,[]里代表的是数组长度,[]旁的是数组名,最左边的是数组中的元素类型

为什么强调声明数组的时候呢,因为声明完数组之后,[]是一种运算符,[]里的数叫做下标。下面讲解一下数组的几个注意事项

1.既然数组在内存方面是连续存在的,那么数组中的每一个元素都是由联系的,找到每一个元素的工具就是下标,数组的每个元素的下标是从0开始的,千万要牢记,并且数组的使用需要通过下标

//下面利用简单的for循环来引用数组的每一个成员
#include<stdio.h>
int main() {
	int arr[10] = { 1,2,3,4,5,6,7,8,9,10 };
	int i = 0;
	for (i = 0; i < 10; i++)
		printf("%d ", arr[i]);
	printf("n");
	return 0;
}
//打印结果是1 2 3 4 5 6 7 8 9 10

2.声明数组是,[]必须放常量,当然c99标准引入了变长数组的概念,使得数组长度可以是一个变量,但是目前只有gcc编译器支持变长数组。

3.初始化数组时,我们应该把我们需要存放的元素放到{}里当我们进行不完全初始化时,剩下的元素默认初始化为0。我们举个例子说明一下

int arr[10];
int arr[10]={1,2,3,4,5,6,7,8,9,10};
int arr[10]={0};
第一种声明时我们没有进行初始化
第二种声明我们把每个元素都进行了初始化,每个数组元素都有对应的值,这种就叫完全初始化
第三种声明我们只赋值了一个0,但是数组的长度为10,并不是所有的成员都被初始化了
这种叫做不完全初始化,而未被初始化的元素统统都会被初始化为0或者

4.数组的长度可以省略,此时编译器会根据声明的内容来确定数组的长度

int arr[]={1,2,3,4,5};
//此时的数组arr的长度是5,因为只初始化了五个数组的成员

需要牢记的数组名的含义

数组名是一个常量指针,是一个不能改变里面内容的已经绑定了的盒子,这个盒子里存放的内容是首元素地址,大家一定要牢记这个概念,但至于什么是常量指针,什么是首元素地址在指针章节会详细讲解,这里不过多深究,另外,注意数组名含义的例外,只有这两个例外

1.sizeof(数组名)   此时的数组名代表整个数组,即单独放在sizeof中的数组名代表整个数组

2.&数组名  这是的数组名也代表整个数组,得到的结果也是整个数组的地址

数组的三种排序

介绍完了数组的含义,接下来上我们今天的主菜,数组的排序,数组这个大盒子里的元素是人为赋值的,所以经常会出现无序的情况,这个时候我们需要去数组进行排序,在学习数组的排序之前,大家有没有在尝试另外三个角的九九乘法表呢?建议大家一定要进行尝试,因为这种两层for循环的思想对接下三种排序的学习尤为重要

1.冒泡排序

我们要讲的三大排序分别是冒泡,选择和插入,那我们先从最简单的冒泡排序开始今天的学习

什么是冒泡排序

冒泡排序是一种计算机科学领域比较简单的算法,通过不断的比较相邻的两个元素,如果顺序错误就进行就交换来实现,两层for循环的冒泡排序每一趟排序都会讲最大或者最小的元素放到最右边,就想冒泡一样,所以称之为冒泡排序,接下来以整形的冒泡排序为例来介绍冒泡排序

#include<stdio.h>
int main() {
	int arr[10] = { 5,4,6,8,99,77,55,10,100,800 };
	int i, j;          //两个循环变量
	int box;      //再将两个变量进行交换位置的时候,我们需要一个空盒子来作为媒介,而box变量就是 
                  //这个空盒子
	for (i = 0; i < 10; i++)
	{
		j = 0;
	 	for (j = 0; j < 9; j++) {       //每一趟冒泡排序都会把最大的值放到最右边   
			if (arr[j] > arr[j + 1])   //如果前面那项大于后面那项,说明顺序错误,进行交换
			{
				box = arr[j];             //交换的流程
				arr[j] = arr[j + 1];
				arr[j + 1] =box;
			}
		}
	}
	for (i = 0; i < 10; i++) {       
		printf("%dt", arr[i]);      //将排序后的结果打印
	}
	printf("n");
	return 0;
}

这个便是这个程序运行的结果,但是 这个冒泡排序其实有很多的多余操作,我们可以尝试通过观察每一次排序后的结果来进行优化。

#include<stdio.h>
int main() {
	int arr[10] = { 5,4,6,8,99,77,55,10,100,800 };
	int i, j;          
	int box;      
	for (i = 0; i < 10; i++)
	{
		j = 0;
		for (j = 0; j < 9; j++) {       
			if (arr[j] > arr[j + 1])  
			{
				box = arr[j];
				arr[j] = arr[j + 1];
				arr[j + 1] = box;
			}
		}
	for (j = 0; j < 10; j++) {       //每一趟冒泡排序都打印结果
		printf("%dt", arr[j]);         
	}                                  
	printf("n");
	}
	return 0;
}

这段代码的运行结果是这样的

再将数组的数据进行修改,我们再看一下另一种结果

#include<stdio.h>
int main() {
	int arr[10] = { 500,95 ,84,56,800,29,16,358,11,4 };
	int i, j;          
	int box;      
	for (i = 0; i < 10; i++)
	{
		j = 0;
		for (j = 0; j < 9; j++) {       
			if (arr[j] > arr[j + 1])  
			{
				box = arr[j];
				arr[j] = arr[j + 1];
				arr[j + 1] = box;
			}
		}
	for (j= 0; j < 10; j++) {       //每一趟冒泡排序都打印结果
		printf("%dt", arr[j]);         
	}                                  
	printf("n");
	}
	return 0;
}

我们可以很明显的发现几个问题

1. 最后一次排序多余,没有必要

2.第几趟排序的最后几个数都是已经是有序了,没有必要在比较

3.有时候可能排序早就完成了

根据以上的问题,我们将对冒泡排序进行再一次的改进

#include<stdio.h>
int main() {
	int arr[10] = { 500,95 ,84,56,800,29,16,358,11,4 };
	int i, j;          
	int box;   
	int flag = -1;       //用flag作为数组是否有序的标志   
	for (i = 0; i < 9; i++)       //减少一趟冒泡排序
	{
		j = 0;
		for (j = 0; j < 9-i; j++) {         //后i项已经完成了排序,可以不用在比较  
			if (arr[j] > arr[j + 1])    
			{
				box = arr[j];
				arr[j] = arr[j + 1];
				arr[j + 1] = box;
				flag = 0;         //进行了调整,则赋值flag为0
			}
		}
		if (flag)
			break;          //进行了一趟排序之后,如果没有进行交换,
                            //则flag为-1,数组已经有序,表达式为真,跳出循环
	}
	for (j= 0; j < 10; j++) {       
		printf("%dt", arr[j]);         
	}                                  
	printf("n");
	return 0;
}

这串代码很好的解决了上面的问题,减少了没意义的操作,但是这个代码也并不是完美的,后面会告诉大家原因

2.选择法排序

掌握了冒泡排序之后,选择排序就显得比较简单了,选择排序也是不断地比较两个元素,第一次从待排序的数据元素中选出最小(或最大)的一个元素,存放在序列的起始位置,然后再从剩余的未排序元素中寻找到最小(大)元素,然后放到已排序的序列的末尾。 以此类推, 但选择排序被称为是不稳定的排序方法
我们直接在原有的冒泡排序的基础上进行修改

#include<stdio.h>
int main() {
	int arr[10] = { 500,95 ,84,56,800,29,16,358,11,4 };
	int i, j;
	int box;   
	int min;  //不断的让arr[min]成为最小值
	for (i = 0; i <9; i++) {
		min = i;//前i项已经有序
		for (j = i; j <10; j++)
		{
			if (arr[j] < arr[min])  //如果第j项小于arr[min],则第j项为最小项
				min = j;
		}
		box = arr[i];
		arr[i] = arr[min];
		arr[min] = box;
		printf("%dt", arr[i]);  //每一趟找到最小值
	}
	return 0;
}

相信大家在学会了冒泡排序之后,对于选择排序并不难理解

3.插入排序(不讲解)

插入排序相对于前两种排序来说复杂很多,并且标题也是其二,所以今天并不会讲插入排序,但是

插入排序也需要使用两层循环来实现,学到这大家也能理解掌握两层循环的重要性了吧。

在学习插入排序之前,大家一定要尝试解决这样一个实际问题

如何往一个有序的数组插入一个数,并保证数组的有序性?下一期博客会详细讲解这部分内容

今天的博客分享到此结束,希望对你的学习有所帮助!

本图文内容来源于网友网络收集整理提供,作为学习参考使用,版权属于原作者。
THE END
分享
二维码
< <上一篇
下一篇>>