写在开头:
二维指针数组和普通二维数组的区别只是行之间不连续存放,按照相同的方式访问就行
对于一维数组模拟的二维数组,需要人工计算索引转换去访问二维数组的元素,能不用就不用!
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 的每一行,核心逻辑拆解:
p:指向arr的第 0 行(首行);p + i:指针偏移i行,指向arr的第i行;*(p + i):解引用得到第i行的首地址(等价于arr[i]);*(p + i) + j:在第i行的基础上偏移j列,指向arr[i][j]的地址;*(*(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数组写成全局变量,这样就不用在函数参数里面传来传去!

869

被折叠的 条评论
为什么被折叠?



