BFS学习总结
给你一个n*m的网格迷宫,迷宫中有些格子不能走,其他的格子都能走。然后给你起点与终点,问你从起点走到终点最少需要多少步?
上面的问题就是一个典型的BFS问题,对于这类问题来说,只要你掌握了这类问题的关键思想,其实他们都是可以用类似的思路来做的。
你可以把BFS问题想象成:从一个父亲(起点状态)生儿子(后继状态),儿子又生孙子(后继状态)的过程,只要这个家族中出生了一个满意的后代(终点状态),这个家族就不生了。
但是如果这个家族中有两个完全一样的人出生(他们的辈分不一定相同),那么后出生的人应该停止让他继续产生后代(状态去重),因为他的所有后代肯定与前面那个人的所有后代一模一样且出生时间反而更晚。
整个后代的繁殖过程是通过队列来实现的。
一般的BFS问题我是按下面步骤来做的:
1)设计状态
对于上面的问题,我把当前人的位置以及人到该位置所花费的最小时间t,即x和y坐标(行与列坐标)与时间t作为一个有效状态。严格来说x和y才算状态属性,而t只能算状态的当前值。而我们要求的是终点状态的最小时间t值。
对于每个有效状态X,它可以继续延伸出后继状态。比如本题的X状态的后继状态就是当人在X点然后向上下左右4个方向走时所产生的后继状态(假设4个方向的格子都能走)。
对于相同的状态我们只处理一次,如果某个状态的后继状态是之前我们已经处理过的状态,那么直接抛弃这个后继状态,不入队列(想想为什么)。
2)记录同类状态最优值
就是要记录当达到任意一个同类状态(对于本题我们把坐标值相同的状态看成是同一类的)时的最优效果。比如本题,令dist[i][j]==t表示当人到达(i, j)这类状态时,所需要的最小时间t。这个最小时间就是我们要的最优效果。
3)状态剪枝
每个状态的后继状态一般都有好几个,这样我们只要做几轮出队入队操作,队列中的状态就是变成指数级别了。所以对于那些出现多次的同类状态,我们只保存一个入队列。且如果上面dist[4][5]==2(即花了2步从起点走到(4,5)点),那么当某个后继状态是(4,5,3)时,我们果断抛弃这个状态(想想为什么,其实(4,5,2)我们就可以抛弃)。
(其实通过下面的题目可以知道,有些状态需要很多元素来表示,直接导致如果用dist数组来判定重复将会定义dist为dist[][][][][][][][][][]这样,所以这时候我们采取的策略是:HASH判重+保存所有已出现的非重复状态)
4)查找目标状态
这个就是当我们在生成后继状态的时候看看是否有目标状态出现(比如坐标在终点的状态)。对于本题来说,只要出现了一个目标状态,我们直接退出即可。因为后面出现的目标状态所花的时间肯定更多(想想为什么)。
下面给出一个我写的上面问题的大致解法代码:
#include<cstdio> #include<queue> #include<cstring> using namespace std; const int maxn=100+5; int dx[]={-1,1,0,0};//上下左右 int dy[]={0,0,-1,1}; int n,m; int sr,sc;//起点 int er,ec;//终点 struct Node//BFS状态 { int r,c; int dist; Node(){} Node(int r,int c,int dist):r(r),c(c),dist(dist){} }; int mp[maxn][maxn];//1格可行,0格为障碍 int dist[maxn][maxn];//最小距离 int BFS() { queue<Node> Q; memset(dist,-1,sizeof(dist)); Q.push(Node(sr,sc,0)); dist[sr][sc]=0; while(!Q.empty()) { Node x=Q.front(); Q.pop(); for(int dir=0;dir<4;dir++)//向4个方向走 { int nr=x.r+dx[dir]; int nc=x.c+dy[dir]; if(nr>=1 && nr<=n && nc>=1 && nc<=m && mp[nr][nc])//不能越过网格边界且当前格可行 { if(dist[nr][nc]==-1 || dist[nr][nc]>dist[x.r][x.c]+1)//状态去重 { dist[nr][nc]=dist[x.r][x.c]+1; Q.push(Node(nr,nc,dist[nr][nc])); if(nr==er && nc==ec) return dist[nr][nc];//到达终点 } } } } return -1;//无法到达终点 } int main() { while(scanf("%d%d",&n,&m)==2 && n && m) { for(int i=1;i<=n;i++) for(int j=1;j<=m;j++) { scanf("%d",&mp[i][j]);//1格可行,0格为障碍 if(mp[i][j]==2)//起点 { mp[i][j]=1; sr=i; sc=j; } else if(mp[i][j]==3)//终点 { mp[i][j]=1; er=i; ec=j; } } printf("%d\n",BFS());//输出起点到终点最小距离 } return 0; }
BFS题解应用
UVA 11624 Fire!(图论BFS):解题报告!
POJ 3278 Catch That Cow(图论:BFS):解题报告!
POJ 3414 Pots(图论:BFS):解题报告!
POJ 1324 Holedox Moving(图论:BFS):解题报告!
POJ 2243 Knight Moves(BFS或DFS):解题报告!
POJ 3984 迷宫问题(BFS:迷宫最短路径且输出路径):解题报告!
POJ 1915 Knight Moves(DFS/BFS):解题报告!
POJ 1753 Flip Game(BFS+状态压缩):解题报告!
POJ 1606 Jugs(BFS:找最短路径并输出):解题报告!
POJ 1077 Eight(BFS:输出路径):解题报告!
POJ 3346 Treasure of theChimp Island(BFS):解题报告!
POJ 2046 Gap(BFS+hash判重):解题报告!
POJ 3322 Bloxorz I(BFS:求迷宫最短路径):解题报告!
POJ3221 Diamond Puzzle(BFS:最短路):解题报告!
POJ2110 Mountain Walking(BFS/DFS+二分+枚举区间):解题报告!
POJ 2920 Mine Map(BFS):解题报告!
POJ 1465 Multiple(BFS+同余剪枝):解题报告!
HDU 1728 逃离迷宫(BFS):解题报告!
HDU 1240 Asteroids!(BFS):解题报告!
HDU 2579 Dating with girls(2)(BFS):解题报告!
HDU 1226超级密码(数位BFS):解题报告!
HDU 1072 Nightmare(BFS):解题报告!
HDU 1495 非常可乐(BFS:3杯倒水):解题报告!
HDU 1430 魔板(BFS+HASH+置换):解题报告!
HDU 2612 Find a way(BFS):解题报告!
HDU 1180 诡异的楼梯(BFS:时间动态图):解题报告!
HDU 1547 Bubble Shooter(BFS):解题报告!
HDU 1312 Red and Black(简单BFS):解题报告!
HDU 1253 胜利大逃亡(简单三维BFS):解题报告!
HDU 1401 Solitaire(棋盘状态BFS):解题报告!
HDU1175 连连看(BFS):解题报告!
HDU 1242 Rescue(BFS或BFS+优先队列):解题报告!
HDU 2531 Catch him(BFS:判断是否存在路径):解题报告!