飞道的博客

深搜之迷宫类

344人阅读  评论(0)

常见的迷宫类搜索可以分为以下5种类型

1. 找一组可行解(判断是否可行)
2. 所有可行解
3. 最优解(通常见最少步数)
4. 计算连通块数目
5. 求最大连通块

找一组可行解 可以不取消标记(下次不同路径到达这个点还是不可行) 找到一组解即返回结果。但如果是找所有可行解、路径、最小步数一定要取消标记

(一)、一组可行解(判断是否可行)

迷宫游戏

/*题目描述
给一个n行m列的迷宫,'S'表示迷宫的起点,'T'表示迷宫的终点,
'*'表示不能通过的点,'.'表示可以通过的点。现在要求从'S'出发走到'T',
每次只能上下左右走动,并且只能进入能通过的点,每个点只能通过一次,
问是否能够到达终点,如果不可以输出"NO!",可以则打印其中一条路径。
测试数据:
5 6
....S*
.***..
.*..*.
*.***.
.T....

答案:
....m*
.***mm
.*..*m
*.***m
.Tmmmm
 */
package 迷宫类可行解;

import java.util.Scanner;
//使用方向数组 for循环
public class 迷宫游戏2 {
	private static int n;
	private static int m;
	static char[][] maze;
	static boolean[][] visited;
	//方向数组 分别表示上 左 下 右
	static int[][] dire= {{-1,0},{0,-1},{1,0},{0,1}};
	//判断是否在迷宫内
	private static boolean in(int x,int y) {
		return 0<=x&x<n&0<=y&y<m;
	}

	static boolean dfs(int x,int y) {
	//递归出口
		if(maze[x][y]=='T') {
			return true;
		}
		int dx,dy;
		//标记(x,y)已经走过
		maze[x][y]='m'; 
		visited[x][y]=true;
		for(int i=0;i<4;i++) {//在maze[2][0]处往四个方向尝试均失败 后执行后面代码将maze[2][0]恢复为'.' 
			dx=x+dire[i][0];
			dy=y+dire[i][1];
			if(in(dx,dy)&&maze[dx][dy]!='*'&&!visited[dx][dy]) {
				if(dfs(dx,dy)) {//找到出口
					return true;	//返回true到 最近调用函数dfs()
				}
			}
		}
		//回溯代码
		maze[x][y]='.';
		visited[x][y]=false;//找可行解 可以不取消标记(下次不同路径到达这个点还是不可行) 找到一组解即返回结果
		return false;
	}
	//打印路径
	private static void show(char[][] array) {
		for(int i=0;i<n;i++) {
			for(int j=0;j<m;j++) {
				System.out.print(array[i][j]);
			}
			System.out.println();
		}
		
	}
	public static void main(String[] args) {
		Scanner reader=new Scanner(System.in);
		n=reader.nextInt();
		m=reader.nextInt();
		int x = 0,y = 0;//局部变量需初始化
		maze=new char[n][m];
		visited=new boolean[n][m];
		for (int i = 0; i <n; i++) {
			String s=reader.next();
			for (int j = 0; j < m; j++) {
				if(s.charAt(j)=='S') {
				//先找到入口位置
					x=i;
					y=j;}
				maze[i][j]=s.charAt(j);
			}
		}
		if(dfs(x,y)) {
			show(maze);
		}
		else
			System.out.println("NO!");

	}



}

王子救公主(判断是否可行)

 /*题目描述
一天,蒜头君梦见自己当上了王子,但是不幸的是,自己的公主被可恶的巫婆抓走了。
于是蒜头君动用全国的力量得知,自己的公主被巫婆抓进一个迷宫里面。
由于全国只有蒜头君自己可以翻越迷宫外的城墙,蒜头君便自己一人走上的拯救自己公主的路途。
碰巧的是巫婆出去了,迷宫也不大,蒜头君可以直接和公主对话,于是两个人便开始相互靠近。
每一步移动只能朝着上下左右四个方向走一格,不能走进墙所在的位置。
蒜头君救公主心切,一次必须沿着一个方向走两步(允许跨越迷宫中的墙);
公主柔弱,一次只能走一步。问在这个迷宫中,蒜头君是否可以救出公主(
蒜头君和公主相遇后,就能背着公主逃出迷宫了)。

输入格式
第一行输入两个整数 n(1≤n≤100), m(1≤m≤100) 表示迷宫的行和列。
然后有一个 n×m 的地图,地图由'.'、'#'、'w'、'g'这四个部分组成。
'.'表示可以通行的路,'#'表示迷宫的墙,'w'表示王子开始所在的位置,'g'表示公主开始所在的位置。

输出格式
输出王子是不可以救出自己的公主,如果能救出则输出"yes",否则输出"no"。

测试数据:
1 8
w....#.g 
答案:Yes
 */

思路:dfs分别搜处蒜头君可以到哪些点 公主可以到哪些点,有重合的点即他们会相遇

package 迷宫类可行解;

import java.util.Scanner;

public class 蒜头君救公主 {
	static int n,m;
	static boolean vis[][][];
	static int map[][];
	private static void dfs(int x,int y,int d) {
		//d为0是表示是蒜头君步长为2-0 为1则为公主步长为2-1
		//将越界、访问过、墙壁的位置这些非法位置return掉
		if(x<0||x>=n||y<0||y>=m||vis[x][y][d]||map[x][y]=='#') {
			return;
		}
		//标记访问过
		vis[x][y][d]=true;
		//分别往四个方向搜索
		dfs(x-(2-d),y,d);
		dfs(x+(2-d),y,d);
		dfs(x,y-(2-d),d);
		dfs(x,y+(2-d),d);
		//由于是求可行解 不需要回溯取消标记
	}
	public static void main(String[] args) {
		Scanner reader=new Scanner(System.in);
		int x=0,y=0,x1=0,y1=0;//局部变量调用需要初始化
		boolean flag = false;
		n=reader.nextInt();
		m=reader.nextInt();
		vis=new boolean[n][m][2];
		map=new int[n][m];
		for (int i = 0; i <n; i++) {
			String s=reader.next();
			for(int j=0;j<m;j++) {
				if(s.charAt(j)=='w') {
					x=i;
					y=j;
				}
				else if(s.charAt(j)=='g') {
					x1=i;
					y1=j;
				}
				map[i][j]=s.charAt(j);
			}
		}
		dfs(x,y,0);
		dfs(x1,y1,1);
		for(int i=0;i<n;i++) {
			for(int j=0;j<m;j++) {
				if(vis[i][j][0]&&vis[i][j][1]) {//有交集则可以救出
					flag=true;
				}
			}
		}
		if(flag)
			System.out.println("Yes");
		else
			System.out.println("No");

	}

}

(二)、所有可行解

迷宫的方案数

/*
给一个n行m列的迷宫,'S'表示迷宫的起点,'e'表示迷宫的终点,
'#'表示不能通过的点,'.'表示可以通过的点。现在要求从'S'出发走到'e',
每次只能上下左右走动,并且只能进入能通过的点,每个点只能通过一次,
现在要求你求出有多少种通过迷宫的方案。
输入:
第一行输入行数n和列数m
接下来n行输入n行m列的迷宫

输出:
通过迷宫的方案数目

测试数据
5 5
S..##
.#.##
.#.##
.#...
....e
结果:8

测试数据二
5 5
S####
.####
.####
.####
....e
结果:1
 */
package 迷宫类可行解;

import java.util.Scanner;

public class 迷宫解的方案数 {
	private static int n;
	private static int m;
	private static int count=0;
	static char[][] maze;
	static boolean[][] visited;
	//上 左 下 右
	static int[][] dire= {{-1,0},{0,-1},{1,0},{0,1}};
	
	private static boolean in(int x,int y) {
		return 0<=x&&x<n&&0<=y&&y<m;
	}

	static void dfs(int x,int y) {
		System.out.println(x+" "+y+" "+count);
		if(maze[x][y]=='e') {
			count++;
			show(maze);//应该在这里输出 走的路线
			return;
		}
		int dx,dy;
		visited[x][y]=true;//访问之后标记为true
		//maze[x][y]='m';
		for(int i=0;i<4;i++) {//以(x,y)为起点往四个方向走
			dx=x+dire[i][0];
			dy=y+dire[i][1];
			if(in(dx,dy)&&maze[dx][dy]!='#'&&!visited[dx][dy]) {
				//maze[dx][dy]='m';		//错误写法		
				dfs(dx,dy);			
				//maze[dx][dy]='.';
				//每次一return 后面的visited[x][y]=false;就不会执行 不会被修改过来
			}
		}
		//maze[x][y]='.';
		visited[x][y]=false;//找全部解需取消标记 否则不同方案经过的公共点会被标记为true
	}
	private static void show(char[][] array) {
		for(int i=0;i<n;i++) {
			for(int j=0;j<m;j++) {
				System.out.print(array[i][j]);
			}
			System.out.println();
		}
		
	}
	public static void main(String[] args) {
		Scanner reader=new Scanner(System.in);
		n=reader.nextInt();
		m=reader.nextInt();
		int x = 0,y = 0;//局部变量需初始化
		maze=new char[n][m];
		visited=new boolean[n][m];
		for (int i = 0; i <n; i++) {
			String s=reader.next();
			for (int j = 0; j < m; j++) {
				if(s.charAt(j)=='S') {
					x=i;
					y=j;}
				maze[i][j]=s.charAt(j);
			}
		}
		
		dfs(x,y);
		System.out.println(count);

	}
}

在内层这里时错误的 将注释部分打开在自己电脑跑一下就知道了

马的覆盖点

思路:带限制深搜步数的所有可行解

/*
 蒜头君迷上了中国象棋,在和一个大师的巅峰对决中处于下风.他知道自己再走三步大师就会赢下这一局。于是蒜头君想背水一战。他想知道这个马三步可以到达的位置,是否有好的对策可以给大师致命一击。直接想出马三步能到达的所有位置,现在的小信已经大脑不能够用了。于是他找到他的好朋友你来帮忙解决这个问题。马走动的方法是一直一斜,即先横着或直着走一格,然后再斜着走一个对角线,俗称“马走日”。马一次可走的选择点可以达到四周的八个点,故有“八面威风”之说。如果在要去的方向有别的棋子挡住,马就无法走过去,俗称“蹩马腿”(当然这里就没有蹩马腿了)。
输入:
第一行输入两个整数 n (1 ≤ x ≤ 100), m(1≤ m ≤ 100) 代表棋盘行和列的大小。
第二行输入两个整数 x (1 ≤ x ≤ n), y (1 ≤ y ≤ m) 代表马开始所的位置。

输出:
输出整个棋盘,'.'代表棋盘上可以落子的点。'#'这个代表马三步能到达的点。。

测试数据:
10 9
10 1
答案:
.........
.........
.........
.#.#.....
#.#.#....
####.#...
#####.#..
##.###...
#.###.#..
######...
 */
package 迷宫类可行解;
import java.util.Scanner;

public class 马的覆盖点 {
	static int n;
	static int m;
	static char[][]chess;
	static boolean[][] vis;
	private static void dfs(int x, int y,int step) {
		if(step>3||x<1||x>n||y<1||y>m) {//将深搜步数大于3 或者越界这些不合法数据返回 
			return;}
		step++;
		chess[x][y]='#';

		dfs(x-2,y-1,step);
		dfs(x-1,y-2,step);
		dfs(x+1,y-2,step);
		dfs(x+2,y-1,step);
		
		dfs(x+2,y+1,step);
		dfs(x+1,y+2,step);
		dfs(x-1,y+2,step);
		dfs(x-2,y+1,step);
		
		
	}


	public static void main(String[] args) {
		Scanner reader=new Scanner(System.in);
		int x,y;
		n=reader.nextInt();
		m=reader.nextInt();
		x=reader.nextInt();
		y=reader.nextInt();
		chess=new char[n+1][m+1];
		for (int i = 1; i <=n; i++) {
			for(int j=1;j<=m;j++) {
				chess[i][j]='.';
			}
		}
		dfs(x,y,0);
		for(int i=1;i<=n;i++) {
			for(int j=1;j<=m;j++) {
				System.out.print(chess[i][j]);
			}
			System.out.println();
		}

	}


	
}

(三)、最优解(求迷宫最短步数)

迷宫最短路

/*问题描述
给一个n行m列的迷宫,'S'表示迷宫的起点,'T'表示迷宫的终点,
'*'表示不能通过的点,'.'表示可以通过的点。现在要求从'S'出发走到'T',
每次只能上下左右走动,并且只能进入能通过的点,每个点只能通过一次,
问是否能够到达终点,如果不可以输出"NO!",可以则输出最短步数。

输入:
第一行输入行数n和列数m
接下来n行输入n行m列的迷宫

输出:
如果不可以输出"NO!",可以则输出最短步数

测试数据:
5 6
....S*
.**...
.*..*.
*..**.
.T....

答案:7
 */
package 迷宫类最优解;

import java.util.Scanner;
public class 迷宫最短路 {
	private static int n;
	private static int m;
	private static int ans=1000000;
	private static boolean flag;
	static char[][] maze;
	static boolean[][] visited;
	static int[][] dire= {{-1,0},{0,1},{1,0},{0,-1}};
	
	private static boolean in(int x,int y) {
		return 0<=x&x<n&0<=y&y<m;
	}

	static void dfs(int x,int y,int step) {
		System.out.println(x+" "+y+" "+step);
		if(step>=ans) {//最优性剪枝
			return;
		}
		if(maze[x][y]=='T') {
			if(step<ans)
				ans=step;
			flag=true;
			System.out.println(ans);
			return;//到了终点return 返回最近调用的那一层
		}
		int dx,dy;
		visited[x][y]=true;
		for(int i=0;i<4;i++) {
			dx=x+dire[i][0];
			dy=y+dire[i][1];
			if(in(dx,dy)&&maze[dx][dy]!='*'&&!visited[dx][dy]) {
				System.out.println("before:"+dx+" "+dy+" step="+step);
				dfs(dx,dy,step+1);//不为step++(调用结束才++)  而为++step(或step+1)
				//回溯 四个方向都不行的话退点
				System.out.println(dx+" "+dy+" step="+step);
				//System.out.println("----------------------");
				//return 返回上一层调用
				//此处如果写return只会按dire数组方向搜索返回一组可行解或者无解
			}
		}
		visited[x][y]=false;//找全部解 需取消标记 下次到达这点的路径步长可能不一样
	}
	private static void show(char[][] array) {
		for(int i=0;i<n;i++) {
			for(int j=0;j<m;j++) {
				System.out.print(array[i][j]);
			}
			System.out.println();
		}
		
	}
	public static void main(String[] args) {
		Scanner reader=new Scanner(System.in);
		n=reader.nextInt();
		m=reader.nextInt();
		int x = 0,y = 0;//局部变量需初始化
		maze=new char[n][m];
		visited=new boolean[n][m];
		for (int i = 0; i <n; i++) {
			String s=reader.next();
			for (int j = 0; j < m; j++) {
				if(s.charAt(j)=='S') {
					x=i;
					y=j;}
				maze[i][j]=s.charAt(j);
			}
		}
		show(maze);
		dfs(x,y,0);
		if(flag) {
			System.out.println(ans);
		}
		else
			System.out.println("NO!");
	}



}

(4)、连通块数目

踏青

/*
 蒜头君和他的朋友周末相约去召唤师峡谷踏青。他们发现召唤师峡谷的地图是由一块一块格子组成的,有的格子上是草丛,有的是空地。草丛通过上下左右 4 个方向扩展其他草丛形成一片草地,任何一片草地中的格子都是草丛,并且所有格子之间都能通过上下左右连通。如果用'#'代表草丛,'.'代表空地,
下面的峡谷中有 2 片草地。
##..
..##
处在同一个草地的 2 个人可以相互看到,空地看不到草地里面的人。他们发现有一个朋友不见了,现在需要分头去找,每个人负责一片草地,蒜头君想知道他们至少需要多少人。
输入格式
第一行输入n, m(1≤n,m≤100) 表示峡谷大小
接下来输入 n 行字符串表示峡谷的地形
输出格式
输出至少需要多少人

测试数据

5 6
.#....
..#...
..#..#
...##.
.#....
结果:5
 */

思路:以每一块草丛为起点dfs向外拓展 同时将遍历到的草丛用visited标记 一次深搜下来便是以该草丛为中心的草地

package 连通块数目;
import java.util.Scanner;

public class 踏青 {
	private static int n;
	private static int m;
	static char[][] maze;
	static boolean[][] visited;
	static int[][]dir= {{-1,0},{0,-1},{1,0},{0,1}};
	
	static void dfs(int x,int y) {
		//System.out.println(x+" "+y);
		int dx,dy;
		visited[x][y]=true;
		for(int i=0;i<4;i++) {
			dx=x+dir[i][0];
			dy=y+dir[i][1];
			if(in(dx,dy)&&maze[dx][dy]=='#'&&!visited[dx][dy]) {
				dfs(dx,dy);
			}
		}
		//visited[x][y]=false; 	错误代码走过的又被取消标记	
	}
	private static boolean in(int x, int y) {
		// TODO Auto-generated method stub
		return x>=0&&x<n&&y>=0&&y<m;
	}
	public static void main(String[] args) {
		Scanner reader=new Scanner(System.in);
		n=reader.nextInt();
		m=reader.nextInt();
		int x = 0,y = 0,count=0;//局部变量需初始化
		maze=new char[n][m];
		visited=new boolean[n][m];
		for (int i = 0; i <n; i++) {
			String s=reader.next();
			for (int j = 0; j < m; j++) {
				maze[i][j]=s.charAt(j);
			}
		}
		for (int i = 0; i <n; i++) {
			for (int j = 0; j < m; j++) {
				if(maze[i][j]=='#'&&!visited[i][j]) {
						dfs(i,j);
						count++;
					}				
			}
		}
		System.out.println(count);		
	}

}

(5)、最大连通块

/*题目描述
这一天蒜头君生日,他的朋友们一起来给蒜头君买一个大的蛋糕过生日。游戏做完后到了切蛋糕的时刻了,朋友们知道蒜头君喜欢吃蛋糕,便让蒜头君自己给自己切一块最大的。蒜头君看朋友们这么热情也就不客气了。这块蛋糕是由 R×C 的网格构成,每个网格上面都放有不同的水果。蒜头君把这些水果分为两类,一类是自己喜欢吃的水果,用'#'来表示;一类是自己不喜欢吃的水果,用'.'来表示。蒜头君对切出的蛋糕有如下要求:
 1. 切出的蛋糕连成一块(可以不为矩形,但必须在网格上连通)
 2. 切出的蛋糕只包含自己喜欢吃的水果
请问,蒜头君最大可以吃到多大的蛋糕?

输入格式:
第一行输入两个被空格隔开的整数 R(1≤R≤1000) 和 C(1≤C≤1000)。
然后会有一个 R×C 的网格,由'#'和'.'组成。

输出格式
输出一个整数,表示蒜头君可以吃到的蛋糕最大是多少(即对应到网格中的格子数)。

测试数据:
 5 6
 .#....
 ..#...
 ..#..#
 ...##.
 .#....
 答案:2
 */

思路:求最大连通块

package 最大连通块;

import java.util.Scanner;

public class 最大蛋糕块 {
	static int n;
	static int m;
	static int ans;
	static int cnt;
	static char[][] maze;
	static boolean[][] visited ;
	static void dfs(int x,int y) {
		if(x<0||x>=n||y<0||y>=m||visited[x][y]||maze[x][y]=='.') {
			return;
		}
		//在dfs函数里面计数 每访问一个网格 蛋糕便多了一格
		cnt++;
		visited[x][y]=true;//不能取消标记 就是要把所有蛋糕都走完
		dfs(x-1,y);
		dfs(x,y-1);
		dfs(x+1,y);
		dfs(x,y+1);
	}
	public static void main(String[] args) {
		Scanner reader=new Scanner(System.in);
		n=reader.nextInt();
		m=reader.nextInt();
		maze=new char[n][m];
		visited=new boolean[n][m];
		for(int i=0;i<n;i++) {
			String s=reader.next();
			for(int j=0;j<m;j++) {
				maze[i][j]=s.charAt(j);
			}		
		}
		for(int i=0;i<n;i++) {
			for(int j=0;j<m;j++) {
				if(maze[i][j]=='#'&&!visited[i][j]) {
					cnt=0;//每次求连通块 计数器首先要清零
					dfs(i,j);
					//如果新得到的蛋糕块更大
					if(cnt>ans)
						ans=cnt;
				}
			}		
		}
		System.out.println(ans);
	}
}

转载:https://blog.csdn.net/weixin_44855907/article/details/104735220
查看评论
* 以上用户言论只代表其个人观点,不代表本网站的观点或立场