下面的两道题的from,其实就是常用的pre数组 --编辑于2020.06.25
A-迷宫
问题描述:
5× 5的二维数组,仅由0、1两数字组成,表示迷宫,0表示可以走,1不可以走,要求找出一条从左上角到右下角的路径,并输出路径
思路:
典型的搜索问题,这里可以用BFS实现,主要的困难是如何记录路径并输出,要记录路径至少实现两点:储存点和存储点之间的关系,最初的想法是在结构体中加上pre和num两个变量来再加上一个数组存储到过的点,num表示点在数组中的位置,pre表示其前驱,后来受到第二题的启发,发现map可以既存储路径,还能储存关系,最后用map实现的路径输出,同样,map也可以代替visited数组判断是否访问过,但是,map查找的复杂度是log,而数组查找的复杂度是O(1),相比较来说,数组更省时间,但是代码量多。用unorder_map?更好?
总结:
输出路径只要记录点和点之间的关系即可,map可实现状态到状态的转移,从而实现递归输出,用数组可以模拟此过程。
代码:
1 #include <cstdio> 2 #include <iostream> 3 #include <queue> 4 #include <map> 5 using namespace std; 6 7 struct point 8 { 9 int x,y; 10 bool operator < (const point &a) const 11 { 12 if(x!=a.x) return x<a.x; 13 else return y<a.y; 14 } 15 }; 16 map<point,point> from; //建立从点到点的映射,来输出路径 17 18 int dx[]={-1,0,1,0}; 19 int dy[]={0,1,0,-1}; 20 21 queue<point> q; 22 int maze[10][10],visited[10][10]; 23 int sx=0,sy=0,tx=4,ty=4; 24 void output(point &t) 25 { 26 if(t.x==0&&t.y==0) 27 { 28 printf("(0, 0) "); 29 return; 30 } 31 output( from[t] ); 32 printf("(%d, %d) ",t.x,t.y); 33 } 34 int main() 35 { 36 int n=5; 37 for(int i=0;i<5;i++) 38 for(int j=0;j<5;j++) 39 scanf("%d",&maze[i][j]); 40 q.push( {sx,sy} ); 41 visited[sx][sy]=1; 42 while( !q.empty() ) 43 { 44 point now=q.front(); 45 q.pop(); 46 for(int i=0;i<4;i++) 47 { 48 int x=dx[i]+now.x, 49 y=dy[i]+now.y; 50 if(x>=0&&x<=4&&y>=0&&y<=4&& 51 visited[x][y]==0&&maze[x][y]!=1) //visited可以用map替代但前者时间更优 52 { 53 point t={x,y}; 54 from[ t ] =now; 55 q.push( t ); 56 visited[x][y]=1; 57 if(x==4&&y==4) 58 { 59 output( t); 60 return 0; 61 } 62 } 63 } 64 } 65 return 0; 66 }
B-倒水
问题描述:
两个容量为A、B的杯子,有无尽的水可以浪费,要求经过下列过程使某个杯子里的水是C单位: "fill A" 表示倒满A杯,"empty A"表示倒空A杯,"pour A B" 表示把A的水倒到B杯并且把B杯倒满或A倒空。要求输出倒水过程。
思路:
为什么叫隐式图问题?我的理解是:所有能够到达的状态是事先不知道的,到达一个状态之后才能确定下一个状态。如何扩展队列里的状态?对A、B杯进行6种操作,每次操作都能得到一种新的状态,入队,继续搜索,如此BFS。关于倒水过程的输出,和上一题思路一样,map+递归。这里有一个点,是我刚开始没注意到,感觉没问题,提交WA,后来打算明天再说,晚上洗刷的时候突然想起来的,在结构体status中有变量num,表示由哪种操作得到这个状态,因为map用于用户自定义类型时,要重载<,起初我这样写
1 struct status 2 { 3 int a,b; 4 int num; //当前操作的编号 5 bool operator <(const status &x) const 6 { 7 if(a!=x.a) return a<x.a; 8 else if(b!=x.b) return b<x.b; 9 else return num<x.num; 10 } 11 };
后来想到第9行,不能写,因为无论什么操作得来的,只要ab相同,状态就是相同,把第九行加了注释,再提交就AC了。还有一个问题是,最初我在refresh中判断是否到达了C,但是我发现这样的话,while循环不能及时停止,又把特判移到了while中,但是前者比后者能更早结束,传参的话太麻烦,有无更好的解决办法?暂时没想到
总结:隐式图BFS,按照规则从最初状态扩展状态,map+递归输出
代码:
#include <cstdio> #include <iostream> #include <map> #include <queue> using namespace std; char step[10][20]={"","empty A","empty B","fill A","fill B","pour A B","pour B A"}; int a,b,c; struct status { int a,b; int num; //当前操作的编号 bool operator <(const status &x) const { if(a!=x.a) return a<x.a; else if(b!=x.b) return b<x.b; //else return num<x.num; } }; map<status,status> from; queue<status> q; void output(status t) { if( from.find(t)==from.end() || (t.a==0&&t.b==0) ) { return; //回溯到了最初状态 } output( from[t] ); printf("%s ",step[ t.num ]); } void refresh(status s,status t) { if( from.find(t)==from.end() ) //状态t没有被访问到过 { from[t]=s; //状态t由状态s生成 q.push(t); } } void bfs() { status t={0,0,0}; q.push( t ); // visited[a][b]=1; //可以直接用map判断是否访问过,但是不如visited的时间复杂度好 while( !q.empty() ) { status now=q.front(); q.pop(); if(now.a==c||now.b==c) //now=当前状态,tar=target目标状态 { output(now); cout<<"success"<<endl; return ; } //判断可能的每一步 status tar; if(now.a>0) //empty A { tar={0,now.b,1}; refresh(now,tar); } if(now.b>0) //empty B { tar={now.a,0,2}; refresh(now,tar); } if(now.a<a) //fill A { tar={a,now.b,3}; refresh(now,tar); } if(now.b<b) //fill B { tar={now.a,b,4}; refresh(now,tar); } if(now.a>0) // pour A B { if(now.a+now.b<=b) //把A倒空 { tar={0,now.a+now.b,5}; refresh(now,tar); } else //把B倒满 { tar={now.a-(b-now.b),b,5}; refresh(now,tar); } } if(now.b>0) //pour B A { if(now.b+now.a<=a) //把B倒空 { tar={now.a+now.b,0,6}; refresh(now,tar); } else //把A倒满 { tar={a,now.b-(a-now.a),6}; refresh(now,tar); } } } } int main() { //freopen("a.in","r",stdin); while( scanf("%d %d %d",&a,&b,&c)==3 ) bfs(); }