一.对DFS的理解
DFS讲究深度优先,即先将一种分支的所有满足条件的情况走完。若是接下来的分支不满足条件,则返回上一个分支继续搜索其余的分支情况直至找到答案。方法的实现重点在于对所走分支的记载以及失败情况判断后的回溯,当然还要一次次搜索间具有相似性,以便于递归调用。
二.DFS的应用
1.全球变暖
你有一张某海域NxN像素的照片,"."表示海洋、"#"表示陆地,如下所示:
.......
.##....
.##....
....##.
..####.
...###.
.......
其中"上下左右"四个方向上连在一起的一片陆地组成一座岛屿。例如上图就有 2 座岛屿。由于全球变暖导致了海面上升,科学家预测未来几十年,岛屿边缘一个像素的范围会被海水淹没。具体来说如果一块陆地像素与海洋相邻(上下左右四个相邻像素中有海洋),它就会被淹没。
例如上图中的海域未来会变成如下样子:
.......
.......
.......
.......
....#..
.......
.......
请你计算:依照科学家的预测,照片中有多少岛屿会被完全淹没。
输入描述
第一行包含一个整数N(1≤N≤1000)N (1≤N≤1000)。以下N行N列代表一张海域照片。
照片保证第 1 行、第 1 列、第N行、第N列的像素都是海洋。
输出一个整数表示答案。
这个题目中的判断是否为一片岛屿就可以使用BFS检验。首先将整个地图转为二维字符数组,在创建一个等规模的二维数组用来记录对应位置的地图是否被检索过。用循环搜索出陆地所在格,向上下左右四个方向搜索,找出相连的陆地,同时将搜索过的陆地作上标记。找到整个岛屿之后再从未做标记的地图中继续搜索,依次而言。
public class DFS全球变暖 {
//用于标记的地图
static int[][] vis=new int[1010][1010];
static int flag=0;
//走向下一格
static int[][] d= {{0,1}, {0,-1}, {1,0}, {-1,0}};
public static void main(String[] args) {
Scanner scan=new Scanner(System.in);
int n=scan.nextInt();
char[][] map=new char[n][n];
for(int i=0;i<n;i++) {
map[i]=(scan.next()).toCharArray();
}
int ans=0;
for(int i=0;i<n;i++){
for(int j=0;j<n;j++) {
if(vis[i][j]==0 && map[i][j]=='#') {
flag=0;
dfs(map,i,j);
if(flag==0) {
ans++;
}
}
}
}
System.out.println(ans);
}
public static void dfs(char[][] map,int x,int y) {
//标记已经过
vis[x][y]=1;
if(map[x][y+1]=='#' && map[x][y-1]=='#' &&
map[x+1][y]=='#' && map[x-1][y]=='#') {
flag=1;
}
for(int i = 0; i < 4; i++){
int nx = x + d[i][0], ny = y + d[i][1];
if(vis[nx][ny]==0 && map[nx][ny]=='#') {
//对下一步递归调用
dfs(map,nx,ny);
}
}
}
}
2.路径之谜
小明冒充 X 星球的骑士,进入了一个奇怪的城堡。
城堡里边什么都没有,只有方形石头铺成的地面。
假设城堡地面是 n×n 个方格。
按习俗,骑士要从西北角走到东南角。可以横向或纵向移动,但不能斜着走,也不能跳跃。每走到一个新方格,就要向正北方和正西方各射一箭。(城堡的西墙和北墙内各有 n 个靶子)同一个方格只允许经过一次。但不必走完所有的方格。如果只给出靶子上箭的数目,你能推断出骑士的行走路线吗?有时是可以的,比如上图中的例子。
本题的要求就是已知箭靶数字,求骑士的行走路径(测试数据保证路径唯一)
输入描述
第一行一个整数 N (0≤N≤20),表示地面有 N×N 个方格。
第二行 N 个整数,空格分开,表示北边的箭靶上的数字(自西向东)
第三行 N 个整数,空格分开,表示西边的箭靶上的数字(自北向南)
输出描述
输出一行若干个整数,表示骑士路径。
为了方便表示,我们约定每个小格子用一个数字代表,从西北角开始编号: 0,1,2,3 ⋯⋯
比如,上图中的方块编号为:
0 1 2 3
4 5 6 7
8 9 10 11
12 13 14 15
BFS在此用于搜索前进的路线,与前一题类似。不同的是此题需要增加回溯的功能,全球变暖中失败可以直接结束方法的调用,表明该分支该标记成陆地的的都标记了,而此处走不通就不能不管了,因为有可能还要再次走这一格,要还原拔走的箭数,同时将方格设为未经过。
public class DFS路径之谜 {
static Scanner in=new Scanner(System.in);
static int[] dx= {0,1,0,-1};
static int[] dy= {1,0,-1,0};
static int n=in.nextInt();
static int[][] v=new int[n][n];//visited
static int[] b=new int[n];
static int[] x=new int[n];
static int[] vs=new int[n*n];//记载路径
public static void main(String[] args) {
for(int i=0;i<n;i++) {
b[i]=in.nextInt();
}
for(int i=0;i<n;i++) {
x[i]=in.nextInt();
}
dfs(0, 0, 0);
}
public static void dfs(int startx,int starty,int step) {
//已访问
v[startx][starty]=1;
vs[step]=startx*n+starty;
//拔箭
b[starty]--;
x[startx]--;
//是否到达终点且箭拔完了
if(startx==n-1&&starty==n-1&&check()) {
for(int i=0;i<=step;i++) {
System.out.print(vs[i]+" ");
}
return;
}
for(int k=0;k<4;k++) {
//用于取相邻格子
int tx=startx+dx[k],ty=starty+dy[k];
//不超过界限
if(tx>=0&&tx<n&&ty>=0&&ty<n&&v[tx][ty]!=1) {
//是否有箭可拔
if(b[ty]>0&&x[tx]>0) {
//去往下一格
v[tx][ty]=1;
dfs(tx, ty, step+1);
//无路可走就回溯,再进行上一轮未完的择路
v[tx][ty]=0;
}
}
}
//回溯
b[starty]++;
x[startx]++;
}
public static boolean check() {
for(int i=0;i<n;i++) {
if(b[i]!=0||x[i]!=0) {
return false;
}
}
return true;
}
}
最终,DFS在竞赛中并不是都能用的,必须满足样例情况较少,不然会有超时的问题。