常见的迷宫类搜索可以分为以下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
查看评论