4.3 深度广度搜索实例
水管连接
题干
我们有一张N*M大小的地图
我们想要把水管从 (1,1)的铺设起点 铺到 (N,M)的铺设终点
其中我们有两种水管,弯管和直管,他们都可以自由地90°旋转
其中弯管可以连接上下左右任意两个相邻位置,直管则连接相对位置
计算连接方式,如果可以连接则输出路径,否则输出impossible
思路
其实本质上还是我们的深度/宽度搜索,额外添加了下面两个条件:
1.需要考虑进出水口的方向
2.需要给出路径
摆放情况
我们首先讨论摆放情况,有下面六种情况,我们也用1-6的编号表示它们:
1:上到右
2:下到右
3:左到下
4:右到上
5:左到右
6:上到下
状态分析
水管连接和之前的搜索比起来,最重要的一点就是条件
那么我们先结合DFS搜索,把对应的代码写出来:
void dfs(int x , int y , int front){
//注意这个front,它代表的是进水口的方向
//我们在筛选的时候以进水口方向进行一次筛选
//1左边,2上边,3右边,4下边
//越界判断
if(x<1 || x>n || y<1 || y>m){
return ;
}
//判断这个点是否已经被使用过
if(book[x][y] ==1){
return ;
}
book[x][y] = 1 ; //还没走过的话,就先标记这里
//先分析水管为直管的情况
if( a[x][y] >=5 && a[x][y]<=6){
//5 6 代表直管的两个情况
//按照左上右下的顺序进行分析
if(front == 1){
dfs(x,y+1,1) ; //只能使用5号
}
if(front == 2){
dfs(x+1,y,2) ; //6
}
if(front == 3){
dfs(x,y-1,3) ; //5
}
if(front == 4){
dfs(x-1,y,4) ; //6
}
}
book[x][y] ; //因为是DFS算法,所以要记得取消标记哦
return ;
}
对于弯管的处理,其原理是一样的,这里我们就不赘述了
另外,当我们到达(n,m+1)这个点时,就表明产生了完整方案——可以返回了
//判断是否到达终点的Base case
if(x==n && y==m+1){
flag = 1 ; // 标记找到了方案
return ;
}
完整代码(无路径返回)
我们一步步来,先得到不返回路径,只产生方案的搜索:
#include<stdio.h>
int a[51][51] ;
int book[51][51] ,n,m,flag = 0 ;
void dfs(int x , int y , int front){
//注意这个front,它代表的是进水口的方向
//我们在筛选的时候以进水口方向进行一次筛选
//1左边,2上边,3右边,4下边
//判断是否到达终点的Base case
if(x==n && y==m+1){
flag = 1 ; // 标记找到了方案
return ;
}
//越界判断
if(x<1 || x>n || y<1 || y>m){
return ; //碰到了就之前爬
}
//判断这个点是否已经被使用过
if(book[x][y] ==1){
return ; //走过了那也爬
}
book[x][y] = 1 ; //还没走过的话,就先标记这里
//先分析水管为直管的情况
if( a[x][y] >=5 && a[x][y]<=6>){
//5 6 代表直管的两个情况
//按照左上右下的顺序进行分析
if(front == 1){
dfs(x,y+1,1) ; //只能使用5号
}
if(front == 2){
dfs(x+1,y,2) ; //6
}
if(front == 3){
dfs(x,y-1,3) ; //5
}
if(front == 4){
dfs(x-1,y,4) ; //6
}
}
//分析弯管,和直管原理一样
if( a[x][y]>=1 && a[x][y]<=4){
if(front == 1){
dfs(x+1,y,2) ; //3
dfs(x-1,y,4) ; //4
}
if(front == 2){
dfs(x,y+1,1) ; //1
dfs(x,y-1,3) ; //4
}
if(front == 3){
dfs(x-1,y,4) ; //1
dfs(x+1,y,2) ; //2
}
if(front == 4){
dfs(x,y+1,1) ; //2
dfs(x,y-1,3) ; //3
}
}
book[x][y] ; //因为是DFS算法,所以要记得取消标记哦
return ;
}
//在main方法中执行我们的方案
int main(){
int i,j,num = 0 ;
scanf("%d %d",&n,&m);
//地图读入
for(i=1;i<=n:i++)
for(j=1;j<=m;j++)
scanf("%d",&a[i][j]);
//开始搜索,从(1,1)开始
dfs(1,1,1);
//根据flag的最后情况分析结果
if(flag==0) printf("impossible \n");
else printf("找到了方案 \n") ;
}
啊,“找到方案”,对,我们只是找到了方案
想要让这个做法真正有意义,我们就应该输出相应的路径
增加路径的返回
这一步比想象中简单————毕竟我们已经一步步找到了结果了
我们只要在代码中加入一个栈,就可以利用它输出路径了
struct note{
int x ; //横坐标
int y ; //纵坐标
}
s[100];
完整代码(含路径的返回)
#include<stdio.h>
int a[51][51] ;
int book[51][51] ,n,m,flag = 0 ;
//-----------------------------------------
struct note{
int x ; //横坐标
int y ; //纵坐标
}
s[100];
//-----------------------------------------
void dfs(int x , int y , int front){
//注意这个front,它代表的是进水口的方向
//我们在筛选的时候以进水口方向进行一次筛选
//1左边,2上边,3右边,4下边
//判断是否到达终点的Base case
if(x==n && y==m+1){
flag = 1 ; // 标记找到了方案
return ;
}
//越界判断
if(x<1 || x>n || y<1 || y>m){
return ;
}
//判断这个点是否已经被使用过
if(book[x][y] ==1){
return ;
}
book[x][y] = 1 ; //还没走过的话,就先标记这里
//将标记入栈
//-----------------------------------------
top++ ;
s[top].x = x;
s[top].y = y ;
//-----------------------------------------
//先分析水管为直管的情况
if( a[x][y] >=5 && a[x][y]<=6){
//5 6 代表直管的两个情况
//按照左上右下的顺序进行分析
if(front == 1){
dfs(x,y+1,1) ; //只能使用5号
}
if(front == 2){
dfs(x+1,y,2) ; //6
}
if(front == 3){
dfs(x,y-1,3) ; //5
}
if(front == 4){
dfs(x-1,y,4) ; //6
}
}
//分析弯管,和直管原理一样
if( a[x][y]>=1 && a[x][y]<=4){
if(front == 1){
dfs(x+1,y,2) ; //3
dfs(x-1,y,4) ; //4
}
if(front == 2){
dfs(x,y+1,1) ; //1
dfs(x,y-1,3) ; //4
}
if(front == 3){
dfs(x-1,y,4) ; //1
dfs(x+1,y,2) ; //2
}
if(front == 4){
dfs(x,y+1,1) ; //2
dfs(x,y-1,3) ; //3
}
}
book[x][y] = 0 ; //因为是DFS算法,所以要记得取消标记哦
//出栈---------------------------------------
top -- ;
//-------------------------------------------
return ;
}
//在main方法中执行我们的方案
int main(){
int i,j,num = 0 ;
scanf("%d %d",&n,&m);
//地图读入
for(i=1;i<=n:i++)
for(j=1;j<=m;j++)
scanf("%d",&a[i][j]);
//开始搜索,从(1,1)开始
dfs(1,1,1);
//根据flag的最后情况分析结果
if(flag==0) printf("impossible \n");
else printf("找到了方案 \n") ;
}