数组和算法基础

目录

一维数组的定义和初始化

数组的创建

 数组的初始化

数组的输出

 数组的存放

数组名的调用

 数组的下标越界问题

二维数组的定义和初始化

数组的创建

数组的初始化

数组的输出 

 数组的存储

向函数传递一维数组

数组名的含义

数组作为参数传递给函数

排序和查找

冒泡排序

交换法排序

选择法排序 

特定子项的排序

查找

线性查找

折半查找 

向函数传递二维数组

练习-奇偶排序

一维数组的定义和初始化

数组:一组数,是一组相同类型元素的集合

创建方式

type_t arr_name  [const_n];

元素类型         常量表达式,用来指定数组的大小

数组的创建

int main()
{
	int arr[6];//创建整型数组
	char ch[9];//创建字符数组
	/*int n = 3;
	int arr1[n];*///错误的,不能将变量名作为下标使用
	return 0;
}

 数组的初始化

int main()
{
    int arr1[5] = { 1,2,3,4,5 };//完全初始化
	int arr2[5] = { 1,2,3 };//不完全初始化,自动补0
	int arr3[]={1, 2, 3, 4, 5};//根据初始化内容确定大小,等价为int arr3[5] = { 1,2,3,4,5 };
    return 0;
}
int main()
{
    char ch1[5] = { 'a','b','c' };//不完全初始化,自动补'\0'
    char ch2[] = { 'a','b','c' };//根据初始化内容确定大小
    char ch3[5] = "abc"; //前四个元素为 a b c \0 (\0)
    char ch4[] = "abc";//仅四个元素 a b c \0
    return 0;
}

 当数组在所有函数外定义,或用static定义为静态储存类型时,即使不给数组元素赋初值,那么数组元素也会自动初始化为0,这是在编译阶段完成的

int main()
{
    char a[] = "abc";//四个元素
	char b[] = { 'a','b','c'};//三个元素
	printf("%s\n", a);
	printf("%s\n", b);//没有结束标志,烫烫烫
	printf("%d\n", strlen(a));//3
	printf("%d\n", strlen(b));//随机数
    return 0;
}

数组的输出

int main()
{
	int arr[10] = { 0 };
	arr[4] = 5;//下标引用操作符
	int i = 0;
	int sz = sizeof(arr)/ sizeof(arr[0]);//判断数组元素个数
	for (i = 0; i < sz; i++)
	{
		printf("%d", arr[i]);
	}
	return 0;
}
运行结果:
0000500000

 注意:数组的下标是从0开始的

 数组的存放

int main()
{
	int arr[10] = { 0 };
	int i = 0;
	for (i < 0; i < 10; i++)
	{
		printf("%p\n", &arr[i]);//%p是按地址的格式打印-进行补齐十六进制的打印
	}
	printf("%x\n", 0x12);
	printf("%p\n", 0x12);
	return 0;
}
运行结果:
0000002D780FF668
0000002D780FF66C
0000002D780FF670
0000002D780FF674
0000002D780FF678
0000002D780FF67C
0000002D780FF680
0000002D780FF684
0000002D780FF688
0000002D780FF68C
12
0000000000000012

根据数组中一个元素占据四个字节,所以我们可以发现,一维数组在内存中是连续存放
随着数组下标的增长,地址是由低到高变化的 

数组名的调用

int main()
{
	int arr[5] = { 1,2,3,4,5 };
	int* p = arr;//数组名是数组首元素的地址
	int i = 0;
	for (i = 0; i < 5; i++)
	{
		printf("%d", *p);
		p++;
	}
	return 0;
}
运行结果:
12345

 数组的下标越界问题

int main()
{
	int a=1,c=2,b[5]={0},i;
	printf("%p %p %p\n", b, &c, &a);//用%p格式打印数组b,变量c和a的首地址
	for (i = 0; i <= 8; i++)
	{
		b[i] = i;
		printf("%d ", b[i]);
	}
	printf("\nc=%d,a=%d,i=%d\n", c, a, i);

	return 0;
}

因不同的编译系统为变量分配的内存地址有所不同,因此在不同的系统下运行程序可能会输出不同的值

程序运行起来回报错,输出结果可能为2 1 9,也可能为5 6 9,出现第二种结果的原因是0-4对数组b进行赋值以后,5-8是多余的,造成了下标越界,多余的数则会在之后的访问中出现 ,即访问c,a时,内存中变量a和c的值被修改为了6和5

b[0] b[1] b[2] b[3] b[4] c a i b[8]

 0     0     0     0     0    2 1        

进行赋值后

b[0] b[1] b[2] b[3] b[4] c a i b[8]

 0     1     2     3     4    5 6 9  8

所以,我们应确保元素的正确引用,以免因下标越界而造成对其他存储单元中数据的破坏 

二维数组的定义和初始化

数组的创建

int main()
{

	int arr[3][4];//整型数组三行四列
	char ch[3][5];
	int arr1[][3];//二维数组的行可以省略,列不可以省略
	return 0;
}

但是在创建但不初始化的时候我们是不可以将二维数组的行省略的,因为这样会导致数组的大小不确定 

n维数组用n个下标来确定各元素在数组中的顺序

数组的初始化

int main()
{
	//初始化-创建的同时赋值
	int arr1[3][4] = { 1,2,3,4,5,6,7,8,9,10,11,12 };
	int arr2[3][4] = { 1,2,3,4,5,6,7 };//整型数组自动补0,字符数组自动补\0
    int arr3[][4] = {1,2,3,4,5,6,7,8,9};//按照初始化列表中提供的初值个数来定义数组的大小
                  //{1,2,3,4,5,6,7,8,9,0,0,0};
                  //{{1,2,3,4},{5,6,7,8},{9,0,0,0}};这两句和上面那一句是等价的
	int arr4[3][4] = { {1,2},{3,4},{5,6} };//分别对每一行初始化,每一行不足的自动补0
	return 0;
}

 对于二维数组而言,既可以按元组初始化,也可以按行初始化

arr[3][4]={1,2,3,4,5,6,7,8,9,10,11,12}//按元素初始化

arr[3][4]={{1,2,3,4},{5,6,7,8},{9,10,11,12}}//按行初始化

 上面这两句本质上是等价的

int main()
{
	int i = 0;
	int j = 0;
	int arr[][4] = { {1,2},{3,4},{5,6} };
	int* p = &arr[0][0];
	for (i = 0; i < 12; i++)
	{
		printf("%d ", *p);
		p++;
	}
	return 0;
}
运行结果:
1 2 0 0 3 4 0 0 5 6 0 0

 注意:假设一个二维数组arr[3][4],第一行为arr[0][j],以此类推,所以,我们可以认为arr[i]是一个一维数组的数组名,arr[i][j]是这个一维数组中的某个元素

数组的输出 

int main()
{
	int i = 0;
	int j = 0;
	int arr[3][4] = { {1,2},{3,4},{5,6} };
	for (i = 0; i < 3; i++)
	{
		for (j = 0; j < 4; j++)
		{
			printf("%d ", arr[i][j]);
		}
		printf("\n");
	}
	return 0;
}

C语言中不带下标的数组名具有特殊含义,它代表数组的首地址,因此不能整体引用一个数组,每次只能引用特定下标值的数组元素 

 数组的存储

int main()
{
	int i = 0;
	int j = 0;
	int arr[3][4] = { {1,2},{3,4},{5,6} };
	for (i = 0; i < 3; i++)
	{
		for (j = 0; j < 4; j++)
		{
			printf("arr[%d][%d]=%p ",i,j, &arr[i][j]);
		}
		printf("\n");
	}
	return 0;
}
运行结果:
arr[0][0]=000000B4FCEFFCB8 arr[0][1]=000000B4FCEFFCBC arr[0][2]=000000B4FCEFFCC0 arr[0][3]=000000B4FCEFFCC4
arr[1][0]=000000B4FCEFFCC8 arr[1][1]=000000B4FCEFFCCC arr[1][2]=000000B4FCEFFCD0 arr[1][3]=000000B4FCEFFCD4
arr[2][0]=000000B4FCEFFCD8 arr[2][1]=000000B4FCEFFCDC arr[2][2]=000000B4FCEFFCE0 arr[2][3]=000000B4FCEFFCE4

因此,我们可以看出来,二维数组在内存中也是连续存放,每一行内部是连续的,行与行之间也是连续的 ,存完第一行接着存第二行,以此类推,所以数组的第二维地长度声明是不可以省略的,系统必须知道每一行有多少个元素才能正确的计算出该元素相对于二维数组地第一个元素的偏移量

向函数传递一维数组

数组名的含义

数组名是什么?数组名是数组首元素的地址

int main()
{

	int arr[10] = { 0 };
	int sz = sizeof(arr);
	printf("%d\n", sz);
	printf("%p\n", &arr[0]);
	printf("%p\n", arr);
	printf("%p\n", &arr);
	return 0;
}
运行结果:
40//结果为什么是40?如果数组名是首元素地址的话不应该是4吗?
000000746E5EFA88
000000746E5EFA88
000000746E5EFA88//为什么三者结果是一样的?

对于问题1,我们需要知道虽然数组名在调用时,是将数组地首地址传了过去,但是我们需要知道有两个特例

(1) sizeof(arr)-数组名表示整个数组,计算的是整个数组的大小,单位是字节

(2)&数组名-数组名表示整个数组,取出的是整个数组的地址

对于问题2,我们来看下面这一段代码

int main()
{

	int arr[10] = { 0 };
	printf("%p\n", &arr);
	printf("%p\n", &arr+1);
	printf("%p\n", arr);
	printf("%p\n", arr+1);
	return 0;
}
运行结果:
0000002B06B4F958
0000002B06B4F980//二者相差0x28=40
0000002B06B4F958
0000002B06B4F95C//二者相差4

所以二者虽然看上去一样,但是含义不同

数组作为参数传递给函数

若要把一个数组传递给一个函数,那么只要使用不带方括号的数组名作为函数实参调用函数即可

数组名代表数组第一个元素的地址,因此用数组名作函数实参实际上是将数组的首地址传给被调函数

一维数组作为函数形参时,数组的长度不可以出现在数组名后面的方括号内,通常用另一个整型形参指定数组的长度,当然我们也可以在方括号中写入

用数组名作为函数实参时,形参数组和实参数组既可以同名也可以不同名,因为它们的名字代表的是数组的首地址,它们都指向了内存中的同一段连续的存储单元;而用简单变量作为函数实参时,由实参向形参单向传递的是变量的内容,不是变量的地址,因此无论它们是否同名,它们都代表内存中不同的存储单元

在被调函数中改变形参数组元素值时,实参数组元素值也会随之改变,这种改变并不是形参反向传给实参造成的,而是因为形参和实参具有同一地址,共享同一段内存单元造型成的

排序和查找

冒泡排序

交换法排序

将第一个元素与后面的元素进行比较,一旦比这个元素要小,那么二者就交换顺序,将交换顺序之后的第一个元素再与上回比较的位置继续向后比较,进行n-1次比较之后,再将第二个元素依次进行比较n-2次,以此类推

#include"Bubble_Sort.h"
int main()
{
	int arr[] = { 2,3,1,4,5,0 };
	int sz = sizeof(arr) / sizeof(arr[0]);//计算数组元素个数
	bubble_sort(arr,sz);
	for (int i = 0; i < sz; i++)
	{
		printf("%d ", arr[i]);
	}

	return 0;
}

void bubble_sort(int arr[], int sz)//形参arr本质是指针,arr[]=*arr
{
	int i = 0;
	int j = 0;
	for (j = 0; j < sz - 1; j++)//初始化语句也可以改为j=i+1,判断条件改成j<n
	{
		for (i = 0; i < sz - 1 - j; i++)
		{
			int tmp = 0;
			if (arr[i] > (arr[i+1]))
			{
				tmp = arr[i];
				arr[i] = arr[i + 1];
				arr[i + 1] = tmp;
			}
		}
	}
}

此处将头文件略去,但不代表不需要写 

如果我们将数组名改为指针形式,代码则是如下样式

void Bubble_Sort(int* arr, int sz)
{
	int i = 0;
	int j = 0;
	for(j=0;j<sz-1;j++,arr++)
	{
		for (i = 0; i < sz - 1-j; i++)
		{
			int tmp = 0;
			if (*arr > *(arr + 1 + i))
			{
				tmp = *arr;
				*arr = *(arr + 1 + i);
				*(arr + 1+i) = tmp;
			}
		}
	}
}

选择法排序 

上面所使用的的交换法排序,第一个最多交换n-1次,第二个n-2次...,这样排序起来效率太过于低下,所以我们也可以使用选择法排序

先将第一个数和第二个数比较,再与第三个数比较,以此类推,每次比较之后都将较小的数的下标进行保存,这样我们只需要在每一轮循环最后比较一下我们保存的元素的下标和需要排序的位置数的下标是否相同就可以了,不相同就进行交换,这样每轮最多一次交换操作,一套下来最多n-1次交换操作

void bubble_sort(int* arr, int sz)
{
	int i, j, k, temp;
	for (i = 0; i < sz - 1; i++)
	{
		k = i;
		for (j = i + 1; j < sz; j++)
		{
			if (arr[j] < arr[k])
			{
				k = j;
			}
		}
		if (k != i)
		{
			temp = arr[k];
			arr[k] = arr[i];
			arr[i] = temp;
		}
	}
}

特定子项的排序

对信息进行排序时,通常只使用信息的一个子项作为键值,由键值决定信息的全部子项的排列顺序

假如存在数组a,数组b,且两个数组中的元素个数相等,数组a中的第i个元素对应数组b中第i个元素,当我们需要根据数组b中的元素大小进行排序时,我们可以同时将数组a和数组b作为参数传递给函数,对b中的元素进行冒泡排序,之后再进行交换的操作时加一个将数组a中的元素也进行交换的操作即可

temp1 = arr1[k];arr1[k] = arr1[i]; arr1[i] = temp;

temp2 = arr2[k];arr2[k] = arr2[i]; arr2[i] = temp;

查找

当用户输入相关的一个信息时,我们进行搜索这个元素的过程称为查找,我们所要学习的有线性查找折半查找

线性查找

使用循环查找值为x的数组元素,若找到则返回x在数组中的下表位置,没找到则返回-1

int Search(int arr[], int x, int n)
{
	int i;
	for (i = 0; i < n; i++)
	{
		if (arr[i] == x)
		{
			return i;
		}
	}
	return -1;
}

折半查找 

折半查找也称二分查找,这种查找方式我们之前已经讲过,在这里就不在做过多的说明

注意:

(1)虽然折半查找速度比线性查找要快很多,但是在使用折半查找时需要保证元素的大小顺序是排好的,不可以是乱序,所以我们在使用折半查找之前,一般会对数组进行排序操作

(2)在进行折半查找时,我们一般会将计算中间值mid语句写为:

mid=(left+right)/2

看似没有什么问题,但是如果二者的加和过大,超出了limits.h中定义的有符号整数的极限值,那么将会导致mid变成负数,所以在这里我们不妨将mid的计算语句写为:

mid=较小值+(较大值-较小值)/2

向函数传递二维数组

二维数组向函数传递和一维数组向函数传递实际上并没有太大的区别,唯一要注意的一点是,二维数组作为形参时,第二维的长度不可以省略,不可以像一位数组一样空下之后在后面使用其他整型形参指定数组长度,但是第一维长度可以

练习

奇偶排序

//输入一个数组,进行调整后,使奇数在前,偶数在后,并分别排序
int main()
{
	int arr[] = {8,8,8,0};
	int sz = sizeof(arr) / sizeof(arr[0]);
	int i, j, k, m, tmp;
	int p = sz - 1;
	for (i = 0; i < sz; i++)
	{
		if (arr[i] % 2 == 0)
		{
			int j = 0, temp;
			for (j = p; j >=0; j--)//注意从后往前找更快!!!
			{
				if (arr[j] % 2 != 0 && j >= i)
				{
					temp = arr[i];
					arr[i] = arr[j];
					arr[j] = temp;
					p = j - 1;
					break;
				}
			}
		}
	}
	for (i = 0; i < sz - 1; i++)
	{
		k = i;
		for (j = i + 1; j < sz; j++)
		{
			if (arr[k] > arr[j] && arr[j] % 2 == 1)
			{
				k = j;
			}
		}
		if (k != i)
		{
			tmp = arr[i];
			arr[i] = arr[k];
			arr[k] = tmp;
		}
	}
	for (i = sz-1; i >0; i--)
	{
		k = i;
		for (j = i - 1; j >=0 ; j--)
		{
			if (arr[k] < arr[j] && arr[j] % 2 == 0)
			{
				k = j;
			}
		}
		if (k != i)
		{
			tmp = arr[i];
			arr[i] = arr[k];
			arr[k] = tmp;
		}
	}
	for (i = 0; i < sz; i++)
	{
		printf("%d ", arr[i]);
	}
	return 0;
}

杨辉三角

int main()
{
	int a[1000] = { 1 };
	int i = 0, tmp = 0, j=0;
	for (i = 0; i < 10; i++)
	{
		if (a[i] != 0)
		{
			printf("%d ", a[i]);
		}
		else
		{
			printf("\n");
			for (j = i; j >0; j--)
			{
				tmp = a[j] + a[j - 1];
				a[j] = tmp;
				i =-1;
			}
		}
	}
	return 0;
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值