DFS训练

DFS,即深度优先搜索(Depth-First-Search),是一种用于遍历或搜索树或图的算法。其思想主要可以归纳为以下几点:

一、基本思想

  • 从起始节点(或源节点)开始,沿着一条路径尽可能深地访问,直到到达最深处(即无法再继续扩展的节点)。
  • 当到达最深处或无法继续扩展时,回溯到前一个节点,并尝试探索其他未访问过的路径。
  • 这个过程会递归进行,直到所有的节点都被访问过。

二、节点状态

在DFS中,每个节点都有三种可能的状态:

  • 未访问:节点尚未被访问过。
  • 已访问但未探索完所有相邻节点:节点已被访问,但其所有相邻节点尚未都被访问。
  • 已访问且已探索完所有相邻节点:节点及其所有相邻节点都已被访问。

三、核心要素

  • 回溯:当探索到某一步时,发现无法继续或达不到目标,就退回一步重新选择,这种走不通就退回再走的技术称为回溯法。满足回溯条件的某个状态的点称为“回溯点”。
  • 剪枝:减小搜索树规模、尽早排除搜索树中不必要的分支的一种手段。
  • 数据结构:DFS通常使用栈(Stack)这种数据结构来存储待扩展的节点。栈的特点是后进先出(LIFO),这符合DFS先扩展最新产生的节点的原则。

题目

A - 蘑菇

你身处充满着蘑菇与花朵的峡谷中,花是无害的,但蘑菇是有毒的,你现在站在一朵花上,只能向附近上下左右的花朵移动。
编写一个程序,计算你总共能够接触到多少朵花(每朵花只能计数一次,计数包括初始的花)。

Input

   包括多个数据集合。

第一行是两个整数 m 和 n,分别n行m列的峡谷区域。n 和 m 都不超过 20。

在接下来的 n行中,每行包括 m 个字符。每个字符表示一个物体,规则如下 1)‘.’:花 2)‘#’:蘑菇 3)‘@’:花,并且你站在这朵花上。这片峡谷上只有一个'@’


当在一行中读入的是两个零时,表示输入结束。

Output

   对每个数据集合,分别输出一行 

输出总共能够接触到的花朵数目

Sample

InputOutput
6 9 
....#. 
.....# 
...... 
...... 
...... 
...... 
...... 
#@...# 
.#..#. 
0 0
4

代码

#include <stdio.h>
#include <string.h>
#include <stdlib.h>


// 全局变量,用于存储起始点的坐标
int x1 = -1, y1 = -1;  
// 网格的行数和列数
int n, m;
// 存储网格信息的二维字符数组
char a[24][24];  
// 标记网格中每个点是否已访问的二维布尔数组
bool b[24][24];
// 存储可达空白单元格数量的全局计数器
int count;       
// 存储四个方向(右、下、左、上)的偏移量
int Next[4][2] = {{0, 1}, {1, 0}, {-1, 0}, {0, -1}};

// 深度优先搜索函数
void dfs(int x, int y) {    
    // 如果当前点是障碍物或已访问,则返回
    if (a[x][y] == '#' || b[x][y]) return;
    // 可达空白单元格数量加一
    count++;
    // 标记当前点为已访问
    b[x][y] = true; 
    // 遍历四个方向
    for (int i = 0; i < 4; i++) { 
        int tx = x + Next[i][0]; // 计算新点的x坐标
        int ty = y + Next[i][1]; // 计算新点的y坐标
        // 检查新点是否在网格内、未访问且是空白单元格
        if (tx >= 1 && tx <= n && ty >= 1 && ty <= m && !b[tx][ty] && a[tx][ty] == '.') {
            dfs(tx, ty); // 递归调用dfs
        }
    }
}

// 查找起始点'@'的函数
void find() {
    for (int i = 1; i <= n; i++) {
        for (int j = 1; j <= m; j++) {
            if (a[i][j] == '@') {
                x1 = i; // 存储起始点的x坐标
                y1 = j; // 存储起始点的y坐标
                return; // 找到后直接返回
            }
        }
    }
}

int main() {
    // 循环读取输入,直到遇到m=0, n=0为止
    while (~scanf("%d %d", &m, &n)) {  
        if (m == 0 && n == 0) break; // 遇到结束标志,退出循环
        memset(a, 0, sizeof(a)); // 清空网格数组
        memset(b, 0, sizeof(b)); // 清空访问标记数组
        // 读取网格信息
        for (int i = 1; i <= n; i++) {
            scanf("%s", a[i] + 1); // 注意:数组下标从1开始,因此+1来跳过数组的第一个元素(未使用)
        }

        find(); // 查找起始点
        count = 0; // 重置计数器
        dfs(x1, y1); // 从起始点开始进行深度优先搜索
        printf("%d\n", count); // 输出可达空白单元格的数量
    }
    return 0;
}

B - 油田

某公司负责探测地下油层,每次处理一个大的矩形区域。先创建一个网格,将土地划分为许多方形块,然后用传感设备分别探测每个地块,以确定该地块是否含有石油。一块含有石油的土地叫做pocket。如果两个pocket边相邻或对角相邻,则它们属于同一油层的一部分。你的工作是确定在一个网格有多少不同的油层。

Input

输入包含多组数据。每组数据都以包含m和n的一行开始,m和n是网格中行和列的数量(1 <= m <= 100,1 <= n <= 100),由一个空格分隔。如果m = 0,则表示输入结束。下面是m行,每行有n个字符(不包括行尾字符)。每个字符对应一块土地,要么是“*”,代表没有油,要么是“@”,代表一个pocket。

Output

输出网格有多少不同的油层。

Sample Input

1 1
*
3 5
*@*@*
**@**
*@*@*
1 8
@@****@*
5 5 
****@
*@@*@
*@**@
@@@*@
@@**@
0 0

Sample Output

0
1
2
2

代码

#include <stdio.h>
#include <string.h>
#include <stdlib.h>

int m, n; // 地图的行数和列数
int step[8][2] = { // 八个方向的移动数组,用于DFS遍历
    {1, 0}, {0, 1}, {1, 1}, {-1, 0},
    {0, -1}, {-1, -1}, {-1, 1}, {1, -1}
};
char a[105][105]; // 存储地图数据的二维字符数组
bool b[105][105]; // 标记数组,记录位置是否被访问过
int sum; // 记录岛屿(或相反,水域的连通区域)的数量

// 深度优先搜索函数
void dfs(int x, int y) {
    for (int i = 0; i < 8; i++) { // 遍历八个方向
        int tx = x + step[i][0]; // 计算新位置的x坐标
        int ty = y + step[i][1]; // 计算新位置的y坐标
        if (a[tx][ty] == '@' && tx > 0 && tx <= m && ty > 0 && ty <= n && !b[tx][ty]) {
            // 检查边界条件并标记位置为已访问
            b[tx][ty] = true;
            dfs(tx, ty); // 递归调用DFS
        }
    }
}

int main() {
    while (~scanf("%d %d", &m, &n)) { // 读取地图尺寸,直到输入为0 0时停止
        if (m == 0) break;
        sum = 0; // 初始化岛屿(或水域连通区域)数量计数器
        memset(a, 0, sizeof(a)); // 初始化地图数组
        memset(b, 0, sizeof(b)); // 初始化标记数组
        for (int i = 1; i <= m; i++) scanf("%s", a[i] + 1); // 读取地图数据,注意从1开始索引
        for (int i = 1; i <= m; i++) {
            for (int j = 1; j <= n; j++) {
                if (a[i][j] == '@' && !b[i][j]) {
                    // 标记位置为已访问,并启动DFS来遍历整个连通区域
                    b[i][j] = true;
                    dfs(i, j);
                    sum++; // 增加连通区域计数器
                }
            }
        }
        printf("%d\n", sum); // 输出连通区域的数量
    }
    
    return 0;
}

C - 迷宫

烤乐滋不小心走入了一个迷宫,迷宫可以看成是由n * n的格点组成,每个格点只有2种状态,.和#,前者表示可以通行后者表示不能通行。

迷宫的规则是只能移动到上下左右的相邻格点上,现在烤乐滋想要从点A走到点B,问在不走出迷宫的情况下能不能办到。

如果起点或者终点有一个不能通行(为#),则看成无法办到。

Input

第1行是测试数据的组数k,后面跟着k组输入。

每组测试数据的第1行是一个正整数n (1 <= n <= 100),表示迷宫的规模是n * n的。接下来是一个n * n的矩阵,矩阵中的元素为.或者#。再接下来一行是4个整数x1,y1,x2,y2,描述A处在第x1行, 第y1列,B处在第x2行, 第y2列。注意,x1,y1,x2,y2全部是从0开始计数的。

output

k行,每行输出对应一个输入。能办到则输出“YES”,否则输出“NO”。

Sample

InputOutput
2
3
.##
..#
#..
0 0 2 2
5
.....
###.#
..#..
###..
...#.
0 0 4 0
YES
NO

代码

#include <iostream>
#include <cstring>
using namespace std;

int n, k, ha, la, hb, lb;
char a[105][105];
bool b[105][105];
bool flag;
int step[4][2] = {{1, 0}, {0, 1}, {-1, 0}, {0, -1}};

void dfs(int x, int y) {
    if (flag) return;
    if (x == hb && y == lb) {
        flag = true;
        return;
    }
    for (int i = 0; i < 4; i++) {
        int tx = x + step[i][0];
        int ty = y + step[i][1];
        if (a[tx][ty] == '.' && tx >= 0 && tx < n && ty >= 0 && ty < n && !b[tx][ty]) {
            b[tx][ty] = true;
            dfs(tx, ty);
        }
    }
}

int main() {
    cin >> k;
    while (k--) {
        cin >> n;
        flag = false;
        memset(a,0, sizeof(a)); 
        memset(b, 0, sizeof(b));
        for (int i = 0; i < n; i++) {
            scanf("%s", a[i]); 
        }
        scanf("%d%d%d%d", &ha, &la, &hb, &lb);
        b[ha][la] = true;
        if(a[ha][la]!='#'&&a[hb][lb]!='#') dfs(ha, la);
        if (flag) printf("YES\n");
        
        else printf("NO\n");
    }
    return 0;
}

 D - 跳马问题

马在中国象棋以日字形规则移动。

请编写一段程序,给定n*m大小的棋盘,以及马的初始位置(x,y),要求不能重复经过棋盘上的同一个点,计算马可以有多少途径遍历棋盘上的所有点。

Input

第一行为整数T(T < 10),表示测试数据组数。
每一组测试数据包含一行,为四个整数,分别为棋盘的大小以及初始位置坐标n,m,x,y。(0<=x<=n-1,0<=y<=m-1, m < 10, n < 10)

Output

每组测试数据包含一行,为一个整数,表示马能遍历棋盘的途径总数,0为无法遍历一次。

Sample

InputOutput
1
5 4 0 0
32

 代码

#include <bits/stdc++.h>
using namespace std;
int T;
int d[8][2]={{1,2},{1,-2},{-1,2},{-1,-2},{2,1},{2,-1},{-2,1},{-2,-1}};
int a[15][15];
bool b[15][15];
int sum,ans,sy,sx,n,m;
void dfs(int x,int y){
	if(sum>=n*m-1){
		ans++;
		return ;
	}
	for(int i=0;i<8;i++){
		int tx=x+d[i][0];
		int ty=y+d[i][1];
		if(ty<0||tx<0||tx>=n||ty>=m) continue;
		if(b[tx][ty]) continue;
		b[tx][ty]=1;
		sum++;
		dfs(tx,ty);
		b[tx][ty]=0;
		sum--;
	}
} 
int main(){
	scanf("%d",&T);
	while(T--){
		scanf("%d%d%d%d",&n,&m,&sx,&sy);
		sum=ans=0;
		memset(b,0,sizeof(b));
		b[sx][sy]=1;
		dfs(sx,sy);
		
		printf("%d\n",ans);
	} 
	return 0;
}

E - 游戏关卡

烤烤做梦中

他进入了一个沉浸式游戏:站在一个2行m列的区域,他必须从左上角(1,1)出发,最终到达右下角(2,m)才算完成这一关卡。

作为一个熟练的步行者,他可以从一个格子走到相邻的八个格子 (当然,他只能在游戏区域移动)

有些格子有陷阱,一旦走到这些格子,游戏失败。

请你帮助烤烤确定是否有可能通过关卡。

烤烤梦中一共有t个关卡要通过。

Input

第一行包含一个整数t(1≤t≤100)-关卡的数量。

对于每组数据,首先输入整数m(3≤m≤100)—关卡区域的列数。
接下来的两行描述关卡。所有格子由字符“0”和“1”组成。字符’0’对应一个安全单元格,字符’1’对应一个陷阱单元格。
(数据输入保证单元格(1,1)和(2,m)是安全的。)

Output

对于每组数据,如果能够完成关卡,则输出YES,否则输出NO。

Example

Input

4
3
000
000
4
0011
1100
4
0111
1110
6
010101
101010

Output

YES
YES
NO
YES

代码

#include <bits/stdc++.h>
using namespace std;
char a[5][105];
int d[5][2]={{0,1},{1,0},{-1,0},{1,1},{-1,1}};
bool vis[5][105];
int t,m;
bool flag;
void dfs(int x,int y){
	//printf("%d %d\n",x,y);
	if(flag) return;
	if(x==2&&y==m) {
		flag=1;
		return;
	}
	if(a[x][y+1]=='1'){
		if(a[x-1][y+1]=='1'||a[x+1][y+1]=='1') {
			return;
		}
	}
	int tx,ty;
	for(int i=0;i<5;i++){
		tx=x+d[i][0];
		ty=y+d[i][1];
		if(!vis[tx][ty]&&a[tx][ty]=='0'&&tx>0&&ty>0&&ty<=m&&tx<=2) {
			vis[tx][ty]=1;
			dfs(tx,ty);
		}
	}
}
int main(){
	scanf("%d",&t);
	while(t--){
		flag=0;
		memset(a,0,sizeof(a));
		memset(vis,0,sizeof(vis));
		scanf("%d",&m);
		for(int i=1;i<=2;i++) scanf("%s",a[i]+1); 
		vis[1][1]=1;
		dfs(1,1);
		if(flag==1) printf("YES\n");
		else printf("NO\n");
	}
	return 0;
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

j2189259313

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值