二、矩阵,整型二维数组操作

写在开头:

二维指针数组和普通二维数组的区别只是行之间不连续存放,按照相同的方式访问就行

对于一维数组模拟的二维数组,需要人工计算索引转换去访问二维数组的元素,能不用就不用!

1.  矩阵连通,

读取一个 �×�N×N 矩阵(3≤�≤203≤N≤20)和一个坐标 (�,�)(x,y)(行列索引均从1开始),计算该坐标对应值的连接数。连接规则如下:

  • 从 (�,�)(x,y) 出发,与其值相同且四方向相邻(上下左右)的元素视为连接;

  • 递归遍历所有连接元素的相邻位置,统计总连接数(含自身)。

原题为从txt文件读入

输入格式
  • 首行为整数 �N,表示矩阵维度;

  • 后续 �N 行为矩阵元素,每行包含 �N 个整数(以空格分隔);

  • 最后一行包含两个整数 �x 和 �y(以空格分隔

代码实现(自己写的):

与常规周围位置的方向数组的区别,题目要求从左上开始,按顺时针螺旋,即右下左上4个方向一循环,定义direct[4][2]分别为这个方向的方向数组,然后和常规题目一样,先填充,再根据方向数组计算下一个位置是否合法,不合法则说明此方向上已经到边界了,需要改变到下一个方向(按顺时针),即d=(d+1)%4

法一:

#include <stdio.h>

#include <stdlib.h>

int isconnect(int N,int* arr, int i, int j,int* visited);

void main() {

    int N;

    scanf("%d", &N);

    int* arr = (int*)malloc(sizeof(int) * N * N); //int型可以用一维数组指针模拟二维数组!

    int* visited= (int*)malloc(sizeof(int) * N * N);

    for (int i = 0; i < N; i++) {

        for (int j = 0; j < N; j++)

        {

            scanf("%d", &arr[i*N+j]);

            visited[i * N + j] = 0;

        }

    }

    int x, y;

    scanf("%d %d", &x, &y);

    int i = x - 1; //注意这里的处理,传入从0开始的索引方便计算

    int j = y - 1;

    int count = isconnect(N, arr, i, j, visited);

    printf("%d\n", count);

    free(arr);

    free(visited);

}

int isconnect(int N, int* arr, int i, int j, int* visited) {

    if (visited[i * N + j]) {

        return 0;

    }

    visited[i * N + j] = 1; //先标记已访问,后面递归到该元素时就不会重复统计!

    int temp = arr[i * N + j];

    int a = 0; //int a; 只有作为「全局变量」或「静态局部变量(static int a;)」时,才会自动 // 初始化为 0;作为「局部自动变量」(函数内直接 int a;)时,是内存随机垃圾值

    int b = 0;

    int c = 0;

    int d = 0;

    if (i - 1 >= 0 ) { //边界不满足,不存在则跳过,存在才比较值是否相同

        if (arr[(i - 1) * N + j] == temp) {

             a = isconnect(N, arr, i - 1, j, visited);

        }

    }

    if (i + 1 < N) {

       

        if (arr[(i + 1) * N + j] == temp) {

             b = isconnect(N, arr, i + 1, j, visited);

        }

}

    if (j - 1 >= 0 ) {

        if (arr[i * N + j - 1] == temp) {

             c = isconnect(N, arr, i, j - 1, visited);

        }

    }

    if (j + 1 < N) {

        if (arr[i * N + j + 1] == temp) {

             d = isconnect(N, arr, i, j + 1, visited);

        }

    }

    return 1+a+b+c+d; //这里的1是传入的这个元素本身也算上
                      // 再加上周围4个位置上的连通数量(这4个位置再递归统计它们各自的周围4个位置)

}

法二(推荐):

int dir[4][2] = {  // 左、上、右、下
		{0,-1},
		{-1,0},
		{0,1},
		{1,0}
};

int islt(int** arr, int** visited, int x, int y,int m,int n) {

    if (visited[x][y]) return 0;

	if (!arr[x][y]) {
		visited[x][y] = 1;
		return 0;
	}
	
	// 当前位置是1了
	int sum = 1;
	visited[x][y] = 1;
	for (int i= 0; i < 4; i++)
	{
		int nx = x + dir[i][0];
		int ny = y + dir[i][1];
		if (nx >= 0 && nx < m && !visited[nx][ny]) {
			sum+=islt(arr, visited, nx, ny, m, n);
		}
	}
	return sum;
}

void main() {
	int m, n;
	scanf("%d %d", &m, &n);
	int** arr = (int**)malloc(sizeof(int*) * m);
	for (int i = 0; i < m; i++)
	{
		arr[i] = (int*)malloc(sizeof(int) * n);
		for (int j = 0; j < n; j++)
		{
			scanf("%d", &arr[i][j]);
		}
	}

// 初始化visited数组
	int** visited = (int**)malloc(sizeof(int*) * m);
	for (int i = 0; i < m; i++)
	{
		visited[i] = (int*)malloc(sizeof(int) * n);
		for (int j = 0; j < n; j++)
		{
			visited[i][j] = 0;
		}
	}

int count = 0;
	for (int i = 0; i < m; i++)
	{
		for (int j = 0; j < n; j++)
		{
			if (islt(arr, visited, i, j, m, n) != 0) {
				count++;
			}
		}
	}
	printf("%d\n", count);
}

2.  

核心: 二维数组在内存里就是连续存放的一维块,“跨行” 本质上就是指针偏移一行的元素总数(列数) 个位置,这正是理解数组指针遍历的核心。

// 定义3行4列的二维数组 int arr[3][4] = { {1, 2, 3, 4}, {2, 4, 6, 8}, {3, 6, 9, 12} };

// 正确声明数组指针p,指向arr的第一行(int[4]类型) int (*p)[4] = arr;

通过双循环,用指针访问数组的每一个元素, 有3种方式理解:

for (int i = 0; i < 3; i++) {
        // 内层循环遍历列(j从0到3,对应4列)
        for (int j = 0; j < 4; j++) {
            // 核心访问方式:通过指针p访问arr[i][j]
            // 方式1:最贴合指针本质的写法(推荐理解)
            int value = *(*(p + i) + j);
            // 方式2:简化写法,等价于方式1(推荐实际使用)
            // int value = (p + i)[j];
            // 方式3:和普通数组写法一致,完全等价
            // int value = p[i][j];
            
            printf("arr[%d][%d] = %d\t", i, j, value);
        }
        printf("\n"); // 每行结束换行
    }

int (*p)[4]指向包含 4 个 int 元素的一维数组的指针,正好匹配二维数组 arr 的每一行,核心逻辑拆解:

  1. p:指向 arr 的第 0 行(首行);
  2. p + i:指针偏移 i 行,指向 arr 的第 i 行;
  3. *(p + i):解引用得到第 i 行的首地址(等价于 arr[i]);
  4. *(p + i) + j:在第 i 行的基础上偏移 j 列,指向 arr[i][j] 的地址;
  5. *(*(p + i) + j):最终解引用,得到 arr[i][j] 的值。

补充:p[i][j] 是 C 语言的语法糖,完全等价于 *(*(p + i) + j),写法更简洁,实际开发中更常用,相当于直接把指针p当二维数组名称arr用!

数组名是一个 “指向数组首元素的常量指针,可以通过数组名+偏移再解引用来遍历数组元素

3.

卷积操作

data.txt:

1 2 3
4 5 6
7 8 9
1 0
0 1

原始特征矩阵为3*3,卷积核为2*2,步长为1,没有padding时:

步长从0到1,和卷积核规模一致,发现最后输出与卷积核尺寸一致

tips: 一般是4个for循环解决,两两一组,一组遍历输出结果每个位置,一组遍历卷积核元素、特征图中对应元素,相乘相加得到的结果存入结果位置 !

void main() {
	int arr[3][3];
	int core[2][2];
	FILE* in = fopen("data.txt", "r");
	for (int i = 0; i < 3; i++)
	{
		for (int j = 0; j < 3; j++) {
			fscanf(in, "%d", &arr[i][j]);
		}
	}
	for (int i = 0; i < 2; i++)
	{
		for (int j = 0; j < 2; j++) {
			fscanf(in, "%d", &core[i][j]);
		}
	}
	// 求卷积
	int res[2][2]={0};
		for (int i = 0; i < 2; i++)
		{
			for (int j = 0; j < 2; j++) 
			{
				for (int a = 0; a < 2; a++)
				{
					for (int b = 0; b < 2; b++) { 
		// 固定套路:卷积核的每个元素跟步长一样
						res[i][j] += core[a][b] * arr[i + a][j + b];
					}
				}
			}
		}
	for (int i = 0; i < 2; i++)
	{
		for (int j = 0; j < 2; j++) {
			printf("%d ", res[i][j]);
		}
		printf("\n");
	}
	fclose(in);
}

4.

复习PAT 1050题 螺旋(顺时针按降序填充矩阵)

关键点:越界或已经填充过时,通过方向数组d=(d+1)%4取模,因为右下左上4个方向来改变转向实现顺时针向内旋转,从而从00位置出发填充完整个矩阵

5. 

复习PAT 1068题 邻近点像素差且唯一(通过8行2列,即不包含自身的方向数组去遍历周围8个邻近位置)

关于函数接收二维数组时,注意区分该二维数组是否是连续存储还是二维指针数组

如果是二维指针数组可以用int**类型去接收,然后arr[i][j]就能访问了

6.

矩阵乘法(注意不是卷积),如矩阵A为m*n型、矩阵B为n*k型,A*B得到矩阵C为m*k型

核心逻辑:通过三层嵌套循环实现:

  • 外层循环遍历 C的行(对应也为矩阵A 的行); //  i
  • 中层循环遍历 C 的列(对应也为矩阵B 的列);//  j
  • 内层循环遍历 A 的列(也是B 的行,二者一定要相等才能作乘法),累加计算 C 中每个元素的值  // k
for (int i = 0; i < m; i++)
	{
		for (int j = 0; j < k; j++) {
			int sum = 0;
			for (int k = 0; k < n; k++)
			{
				sum += A[i][k] * B[k][j];
			}
			C[i][j] = sum;
		}
	}

7.

对于一个二维字符数组构成的"汉字/图形",要想倒过来("福倒了")

把遍历方式换成从最后一行最后一列向前遍历即可!

8.

hot100合并区间:

思路:

1.以区间的起始值(start) 为关键字,对所有区间升序排序(防止漏掉、重复)

2.定义创建一个结果数组,先将排序后的第一个区间加入结果(作为初始合并区间)

3.定义一个变量last_end记录最近一次的区间的右端点

4.从第二个区间开始遍历排序后的区间,如果当前区间的左端点 <= last_end,则结果数组的len不用+1,只需要取res[len][1]=max(last_end,arr[i][1])(修改结果中区间的右端点),如果当前区间的左端点 > last_end,则结果数组

memcpy(res[len++], arr[i], sizeof(int) * 2);(加入新区间到结果中

9.

洛谷模拟:

实现矩阵的顺时针、逆时针旋转90度:

// 反转一维数组(用于反转列时提取列元素)
void reverse(int* arr, int len) {
	int l = 0, r = len - 1;
	while (l < r) {
		int temp = arr[l];
		arr[l] = arr[r];
		arr[r] = temp;
		l++;
		r--;
	}
}

void xz(int** arr, int n,int z) {
	// 先转置
	for (int i = 0; i < n; i++)
	{
注意:只需要遍历上三角矩阵对角线以外的元素作交换就完成转置了
        如果遍历全部元素相当于交换两次又还原了,没有交换!
		for (int j = i+1; j < n; j++)
		{
			int temp = arr[i][j];
			arr[i][j] = arr[j][i];
			arr[j][i] = temp;
		}
	}
    if (z) {
		// 逆时针旋转90度
		//再反转每列
		int* temp = (int*)malloc(sizeof(int) * n);
		for (int i = 0; i < n; i++)
		{
			int count = 0;
			for (int j = 0; j < n; j++)
			{
				temp[count++] = arr[j][i];
			}
            reverse(temp, n);
			for (int j = 0; j < n; j++)
			{
				arr[j][i] = temp[j];
			}
		}
	}
else
	{
		// 顺时针旋转90度
		//再反转每行
		
		for (int i = 0; i < n; i++)
		{
			reverse(arr[i], n);
		}
	}
}

void main() {
	int n, m;// n是方阵规模、m是操作次数
	scanf("%d %d", &n, &m);
	int** arr = (int**)malloc(sizeof(int*) * n);
	int tc = 0;
	for (int i = 0; i < n; i++)
    {
		arr[i] = (int*)malloc(sizeof(int) * n);
		for (int j = 0; j < n; j++)
		{
			arr[i][j] = ++tc;
			
		}
	}
    //int x, y, r, z;
	int z;
	for (int i = 0; i < m; i++)
	{
		//scanf("%d %d %d %d", &x, &y, &r, &z);
		scanf("%d", &z);
		xz(arr, n,z);
        for (int j = 0; j < n; j++)
		{
			for (int k = 0; k < n; k++)
			{
				printf("%d ", arr[j][k]);
			}
			printf("\n");
		}
	}

}

10.

复习pgcode 川大 单词方阵,对周围8个方向,每个方向连续深入4次进行比较的思路(直接用for a控制次数,并把a当作数组索引,参与比较):

for (int k = 0; k < 8; k++)
		{
			// 8个方向,每个方向最多匹配4次
			int x = i;
			int y = j;
			int flag = 1;
			for (int a = 0; a < 4; a++)
            {
				x += dir[k][0];
				y += dir[k][1];
				if (s[x][y] != hx[a]) {
					flag = 0; // 不匹配
					break;
				}
			}
			if (flag) {
				// 匹配
				x = i;
				y = j;
				for (int a = 0; a < 4; a++)
                {
					x += dir[k][0];
					y += dir[k][1];
					visited[x][y] = 1; // 需要保留
					visited[i][j] = 1;
				}
			}
		}

另外注意细节:可以把visited数组写成全局变量,这样就不用在函数参数里面传来传去!

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值