C/C++语言基础--一维和二维数组详解(包含基础排序,二分,推箱子等案例,代码均可运行)

本专栏目的

  • 更新C/C++的基础语法,包括C++的一些新特性
  • 无论C还是C++,还是其他语言写的程序,对于数组概念基本上都相同,只是定义新式不同和封装程度不同而已
  • 欢迎收藏+点赞+关注,本人会持续更新

一维数组

数组是什么?

数组:相同数据类型的集合,多个变量共同使用一个变量名称,并用下标加以区分

为什么要有数组?

  • 如:现在需要存储全班同学的年龄,那么有n个同学,则需要定义n个变量,定义和管理都十分的麻烦。但是我们用数组只需要一行代码就可以定义完成。

    //普通方式
    int age1,age2,age3,age4,age5...agen;
    //使用数组
    int age[n];
    

数组初始化

初始化只能在定义数组的同时进行

//不同的初始化方式测试
int arr[8];						//不初始化
int arr[8]=0;					//error 数组初始化需要代扣好的初始值设定项列表(大括号初始化)
int arr[8]={0};					//true	告诉编译器,所有的都按一个方式初始化
int arr[8]={6};					//true	只有第一个为6,剩下的元素都自动初始化为0
int arr[8]={1,2,3,4,5,6};		//true
int arr[8]={0,1,2,3,4,5,6,7,8};	//error	初始值太多,超出了数组的存储范围
int arr[8]={,,,5,2,0};			//error 初始化只从从左到右,前面和中间都不能省略
int arr[]={1,2,3,4,5};			//true	数组里有几个元素?未指定数组大小,编译器会自动推导出来
int arr[];						//error	既不告诉编译器要多大内存,也不说有几个元素,编译器并不知道改怎么分配内存
int arr[]

输出数组

输出一个元素:数组名[下标]

printf("%d",arr[0]);

输出所有元素:需要用循环输出

for(int i=0;i<8;i++)
{
    printf("%d",arr[i]);
}

注意:

  • 数组的下标是从0开始的,因为下标是当前元素相对于第一个元素的偏移量
  • 输出的时候下标不能超出最大下标,最大小标为数组大小减一

数组的输入

int arr[8];
for(int i=0;i<8;i++)
{
    scanf("%d",&arr[i]);
}

数组的插入、删除

数组不擅长插入(添加)和删除元素。数组的优点在于它是连续的,所以查找数据速度很快。但这也是它的一个缺点。正因为它是连续的,所以当插入一个元素时,插入点后所有的元素全部都要向后移;而删除一个元素时,删除点后所有的元素全部都要向前移,这称为伪删除

//插入
int arr[10] = { 5,2,0 };
int maxSize = 10;		//数组最大存储数量
int curSize = 3;		//数组当前存储元素的有效个数
int pos = -1;			//要删除或者插入的位置
int data = -1;			//要插入的数据
printf("请输入要插入的pos和data:");
scanf_s("%d%d", &pos, &data);
if (curSize == maxSize)
{
	printf("数组已满,无法插入~\n");
}
else
{
	//如果能插入,但是下标输入错误,那么就插到有效元素的后面
	if (pos<0 || pos >= maxSize)
	{
		pos = curSize;
		printf("下标输入错误,自动插入到有效元素的后一个\n");
	}
	//把插入点后面所有的数据往后移动
	for (int i = curSize; i > pos; i--)
	{
		arr[i] = arr[i - 1];
	}
	arr[pos] = data;
	curSize++;
}
//删除
printf("\n请输入要删除的pos:");
scanf_s("%d", &pos);
if (curSize == 0)
{
	printf("数组为空,无需删除~\n");
}
else
{
	//不能够乱删除,只要不是有效元素的下标统统不用理会
	if (pos < 0 || pos >= curSize)
	{
		printf("pos不合法,无法删除\n");
	}
	else
	{
		for (int i = pos; i < maxSize; i++)
		{
			arr[i] = arr[i + 1];
		}
		curSize--;
	}
}

数组排序

冒泡排序

  • 核心思想:假设第 i 位数就是第 i 大小的数
//冒泡排序
int* bubbleSort(int arr[], int n)
{
	for (int i = 0; i < n - 1; i++)
	{
		for (int j = 0; j < n - i - 1; j++)
		{
			if (arr[j] > arr[j + 1])
			{
				int t = arr[j];
				arr[j] = arr[j + 1];
				arr[j + 1] = t;
			}
		}
	}
	return arr;
}

选择排序

  • 核心思想:找到di i 大的数组 放入第 i 位
//选择排序,选择第一小 放在第一位,第二小放在第二位
void selectSort(int arr[],int n)
{
	for (int i = 0; i < n-1; i++)
	{
		int min = i;
		for (int j = i+1; j < n; j++)
		{
			if (arr[j] < arr[min])
			{
				min = j;
			}
		}
		if (min != i)
		{
			int temp = arr[i];
			arr[i] = arr[min];
			arr[min] = temp;
		}
	}
}
/*例如
int i=0;//i:0 1 2 3 4
6  9  8  1  0     5个数
第一趟:0 9 8 1 6
第二趟:0 1 8 9 6
第三趟:0 1 6 9 8
第四趟:0 1 6 8 9
*/

插入排序

  • 核心思想:假设前 i 位有序
  • 挑战:结合二分书写完成插入排序
//插入排序,假设前 i  个有序
void insertSort(int arr[], int n)
{
	for (int i = 1; i < n; i++)
	{
		int temp = arr[i];
		int j;
		for (j = i - 1; temp < arr[j]&& j >= 0; j--)
		{
				arr[j+1] = arr[j];	
		}
        //如果需要插入
		if (i != j+1)
		{
			arr[j + 1] = temp;
		}
	}
}
/*例如
int i=0;//i: 1 2 3 4
6  9  8  1  0     5个数
第一趟:6 9 8 1 0
第二趟:6 8 9 1 0
第三趟:1 6 8 9 0
第四趟:0 1 6 8 9
*/

二分查找

二分查找是一种知名度很高的查找算法,在对有序数列进行查找时效率远高于传统的顺序查找

下面这张动图对比了二者的效率差距。

在这里插入图片描述

二分查找的基本思想就是通过把目标数和当前数列的中间数进行比较,从而确定目标数是在中间数的左边还是右边,将查找的范围缩小一半,反复重复该过程,不断缩小范围,直到找到目标 (即目标数等于中间数时),或者确定目标数并不在该数列中。

// 注意:二分必有序
int find(int a[], int x, int l, int r) // 在数组中查找 x 
{
	while (l < r)
	{
		int mid = l + r >> 1;
		if (a[mid] >= x) r = mid;
		else l = mid + 1;
	}

	// 找不到
	if (a[l] != x)
		return -1;

	return l;
}

案例

  • 先对数组进行排序,后查找 6 这个元素的下标位置
#include <stdio.h>

void selectSort(int arr[], int n)
{
	for (int i = 0; i < n - 1; i++)
	{
		int min = i;
		for (int j = i + 1; j < n; j++)
		{
			if (arr[j] < arr[min])
			{
				min = j;
			}
		}
		if (min != i)
		{
			int temp = arr[i];
			arr[i] = arr[min];
			arr[min] = temp;
		}
	}
}

int find(int a[], int x, int l, int r)
{
	while (l < r)
	{
		int mid = l + r >> 1;
		if (a[mid] >= x) r = mid;
		else l = mid + 1;
	}

	// 找不到
	if (a[l] != x)
		return -1;

	return l;
}

int main()
{
	int a[] = { 66, 99, 8 ,10, 6, 2, 30, 5, 7 };
	selectSort(a, sizeof(a) / sizeof(a[0]));

	for (int i = 0; i < sizeof(a) / sizeof(a[0]); i++)
		printf("%d ", a[i]);

	putchar('\n');

	int res = find(a, 6, 0, sizeof(a) / sizeof(a[0]) - 1);  // 查找数字 6 
	if (res != -1)
		printf("%d\n", res);  // 输出第三位下标
	else
		puts("不存在\n");

	return 0;
}

输出:

2 5 6 7 8 10 30 66 99
2

二维,三维数组和推箱子

二维数组与一维数组相似,但是用法上要比一维数组复杂一点。后面的编程中,二维数组用的相对较少,因为二维数组的本质就是一维数组,只不过形式上是二维的。

二维数组

**定义:**数据类型 数组名[ROW][COL]

  • ROW 二维数组的行

  • COL 二维数组的列

    对于二维数组,可以看成一张表格,方便理解。但是下标和一维数组一样,都是从0开始的

内存理解:

  • 二维数组是由行和列组成的,所以说,要获取到一个元素,需要用两个下标表示

  • 可以理解为二维数组的每个元素都是一维数组

    在这里插入图片描述

初始化

// 一行一行赋值
int a[3][4] = { {1,2,3,4},{5,6,7,8},{9,10,11,12} };
// 将所有数据写在一行,原理:二维数组的本质就是一维数据,内存也是连续的
int a2[3][4] = { 1,2,3,4,5,6,7,8,9,10,11,12 };
// 只对部分元素赋初始值,没有赋值的都自动初始化为0
int a3[3][4] = { {1,2},{5},{9} };
// 对全部元素清零
int a4[3][4] = { 0 };

运行输出结果:

a:
1 2 3 4
5 6 7 8
9 10 11 12
a2:
1 2 3 4
5 6 7 8
9 10 11 12
a3:
1 2 0 0
5 0 0 0
9 0 0 0
a4:
0 0 0 0
0 0 0 0

注意

  • 初始化的时候可以省略行(ROW),但是不能省略列

在这里插入图片描述

二维数组使用

  • 二维数组的元素也称为双下标变量,其表示的形式为:数组名[下标][下标]
  • 下标变量和数组定义在形式中有些相似,但这两者具有完全不同的含义。
    • 数组定义的方括号中给出的是某一维的长度,即可取下标的最大值;
    • 而数组元素中的下标是该元素在数组中的位置标识。
    • 前者只能是常量,后者可以是常量、变量或表达式。

访问所有元素:需要使用双重循环,分别遍历行和列,如对上面定义的 3 行 4 列输出

for (int i = 0; i < 3; i++)
{
	for (int j = 0; j < 4; j++)
	{
		printf("%d ", a[i][j]);
	}
	putchar('\n');
}

三维数组

三维数组也与一维数组相似,但是用法上要比二维数组还复杂一点。三维数组用的极少。三维数组可以看成,每个元素都是一个二维数组的一维数组。

定义:数据类型 数组名[LEVEL][ROW][COL]

  • LEVEL 三维数组的层

  • ROW 三维数组的行

  • COL 三维数组的列

    对于三维数组可以理解为一个本子,每一页都是一层,而每一页上的表格就是对于的二维数组

内存理解:

  • 三维数组是由很多个二维数组构成的,所以说,要获取到一个元素,需要:
    • 本质 :一维数组
    • 找到层数 即哪一个二维数组
    • 找到二维数组之后遍历就方便了
      -例子: 用三维数组实现推箱子的关卡跳转

在这里插入图片描述

推箱子

  • 环境:vs2022

推箱子游戏时一款很有趣味的游戏,其开发过程有一定的技巧和方法,其中涉及到二维数组等基础知识以及键盘操作。

游戏设计步骤:

  • 1,初始化数据
  • 2,绘制界面
  • 3,人及箱子移动

基本属性定义:

在这里插入图片描述

WALL:▓

DEST:☆

BOX:□

PLAYER:♀

BOX+DEST:★

PLAYER+DEST:♂

分析

推箱子:
	界面元素:空地 墙 箱子 人 目的在 
	转化: 空地 0    墙 1     目的地 2   箱子  3      玩家  4
	   1.数据最多的是空地,应该用一个简单的数据表示空地
	   2.按顺序来转化,数据不能冲突
思考? 数据应该如何储存
	一:根据经验,或者根据玩游戏的界面分析,得出应该用一个二维数组
如何操作:
	传统的方法,按上下左右键

代码

#include<stdio.h>
#include<conio.h>
#include<stdbool.h>
#include<stdlib.h>


#define ROW 10
#define COL 10

enum tuiXiangZi{SPACE,WALL,DEST,BOX,PLAYER};

void printfMap(int map[][COL]);
void pushBox(int map[][COL]);
bool pass(int map[][COL]);

int main()
{
	//打印地图
	int map[ROW][COL] = { 
		{0,0,0,0,0,0,0,0,0,0},
		{0,0,0,1,1,1,0,0,0,0},
		{0,0,0,1,2,1,0,0,0,0},
		{0,0,0,1,0,1,1,1,1,1},
		{1,1,1,1,3,3,0,0,2,1},
		{1,2,3,0,4,0,1,1,1,1},
		{1,1,1,1,1,3,1,0,0,0},
		{0,0,0,0,1,2,1,0,0,0},
		{0,0,0,0,1,1,1,0,0,0},
		{0,0,0,0,0,0,0,0,0,0}
	};
	while (true) {
		printfMap(map);
		if (pass(map))
		{
			printf("恭喜你,过关了!");
		}
		pushBox(map);
		system("cls");
	}

	return 0;
}

void printfMap(int map[][COL])
{
	for (int i = 0; i < ROW; i++)
	{
		for (int j = 0; j < COL; j++)
		{
			//printf("%d ", map[i][j]);                 //一步一步,随时调整
			switch (map[i][j])
			{
			case SPACE:
				printf(" ");
				break;
			case WALL:
				printf("▓");
				break;
			case DEST:
				printf("☆");
				break;
			case BOX:
				printf("□");
				break;
			case PLAYER:
				printf("|");
				break;
			case BOX + DEST:
				printf("★");
				break;
			case PLAYER+DEST:
				printf("。");
				break;
			}
		}
		printf("\n");
	}
}

void pushBox(int map[][COL])
{
	//玩家当前坐标
	int r;
	int c;

	for (int i = 0; i < ROW; i++)
	{
		for (int j = 0; j < COL; j++)
		{
			if (map[i][j] == PLAYER || map[i][j] == PLAYER + DEST)
			{
				r = i;
				c = j;
				goto end;
			}
		}
	}
end:;                        //注意goto语法

	//获取键盘按键 getchar();  输入之后不显示内容并且不用按回车,头文件“conio.h”
	int key = _getch();
	switch (key)
	{
	case 'w':
	case 'W':
	case 72:
		//如果玩家前面是空地
		if (map[r - 1][c] == SPACE || map[r - 1][c] == DEST)
		{
			//空地变玩家
			map[r - 1][c] += PLAYER;
			//删除玩之前坐标
			map[r][c] -= PLAYER;
		}
		else if (map[r - 1][c] == BOX || map[r - 1][c] == BOX + DEST) 
		{
			if (map[r - 2][c] == SPACE || map[r - 2][c] == DEST)
			{
				//1.把箱子推向前
				map[r - 2][c] += BOX;
				//2.删除原来位置的箱子
				map[r - 1][c] -= BOX;
				//3.玩家向前
				map[r - 1][c] += PLAYER;
				//4.删除原来玩家的位置
				map[r][c] -= PLAYER;
			}
		}
		break;
	case 's':
	case 'S':
	case 80:
		//如果玩家前面是空地
		if (map[r + 1][c] == SPACE || map[r + 1][c] == DEST)
		{
			//空地变玩家
			map[r + 1][c] += PLAYER;
			//删除玩之前坐标
			map[r][c] -= PLAYER;
		}
		else if (map[r + 1][c] == BOX || map[r + 1][c] == BOX + DEST)
		{
			if (map[r + 2][c] == SPACE || map[r + 2][c] == DEST)
			{
				//1.把箱子推下
				map[r + 2][c] += BOX;
				//2.删除原来位置的箱子
				map[r + 1][c] -= BOX;
				//3.玩家向下
				map[r + 1][c] += PLAYER;
				//4.删除原来玩家的位置
				map[r][c] -= PLAYER;
			}
		}
		break;
	case 'a':
	case 'A':
	case 75:
		//如果玩家左是空地
		if (map[r][c - 1] == SPACE || map[r][c - 1] == DEST)
		{
			//空地变玩家
			map[r][c - 1] += PLAYER;
			//删除玩之前坐标
			map[r][c] -= PLAYER;
		}
		else if (map[r][c - 1] == BOX || map[r][c - 1] == BOX + DEST)
		{
			if (map[r][c - 2] == SPACE || map[r][c - 2] == DEST)
			{
				//1.把箱子推向左
				map[r][c - 2] += BOX;
				//2.删除原来位置的箱子
				map[r][c - 1] -= BOX;
				//3.玩家向左
				map[r][c - 1] += PLAYER;
				//4.删除原来玩家的位置
				map[r][c] -= PLAYER;
			}
		}
		break;
	case 'd':
	case 'D':
	case 77:
		//如果玩家右是空地
		if (map[r][c + 1] == SPACE || map[r][c + 1] == DEST)
		{
			//空地变玩家
			map[r][c + 1] += PLAYER;
			//删除玩之前坐标
			map[r][c] -= PLAYER;
		}
		else if (map[r][c + 1] == BOX || map[r][c + 1] == BOX + DEST)
		{
			if (map[r][c + 2] == SPACE || map[r][c + 2] == DEST)
			{
				//1.把箱子推向右
				map[r][c + 2] += BOX;
				//2.删除原来位置的箱子
				map[r][c + 1] -= BOX;
				//3.玩家向右
				map[r][c + 1] += PLAYER;
				//4.删除原来玩家的位置
				map[r][c] -= PLAYER;
			}
		}
		break;
	}

}

bool pass(int map[][COL])
{
	for (int i = 0; i < ROW; i++)
	{
		for (int j = 0; j < COL; j++)
		{
			if (map[i][j] == BOX)
			{
				return false;
			}
		}
	}

	return true;
}

效果

在这里插入图片描述

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值