原文:https://blog.csdn.net/u013480600/article/details/45066957?utm_source=copy
BFS学习总结
给你一个n*m的网格迷宫,迷宫中有些格子不能走,其他的格子都能走。然后给你起点与终点,问你从起点走到终点最少需要多少步?
上面的问题就是一个典型的BFS问题,对于这类问题来说,只要你掌握了这类问题的关键思想,其实他们都是可以用类似的思路来做的。
你可以把BFS问题想象成:从一个父亲(起点状态)生儿子(后继状态),儿子又生孙子(后继状态)的过程,只要这个家族中出生了一个满意的后代(终点状态),这个家族就不生了。
但是如果这个家族中有两个完全一样的人出生(他们的辈分不一定相同),那么后出生的人应该停止让他继续产生后代(状态去重),因为他的所有后代肯定与前面那个人的所有后代一模一样且出生时间反而更晚。
整个后代的繁殖过程是通过队列来实现的。
一般的BFS问题我是按下面步骤来做的:
1)设计状态
对于上面的问题,我把当前人的位置以及人到该位置所花费的最小时间t,即x和y坐标(行与列坐标)与时间t作为一个有效状态。严格来说x和y才算状态属性,而t只能算状态的当前值。而我们要求的是终点状态的最小时间t值。
对于每个有效状态X,它可以继续延伸出后继状态。比如本题的X状态的后继状态就是当人在X点然后向上下左右4个方向走时所产生的后继状态(假设4个方向的格子都能走)。
对于相同的状态我们只处理一次,如果某个状态的后继状态是之前我们已经处理过的状态,那么直接抛弃这个后继状态,不入队列(想想为什么)。
什么样的状态是一个有效的状态?当给你任意一个BFS状态后,你能根据这个状态把当前BFS网格地图(或BFS全局状态)整个还原出来,那么这个状态就是一个有效的状态。(想想是不是)
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)查找目标状态
这个就是当我们在生成后继状态的时候看看是否有目标状态出现(比如坐标在终点的状态)。对于本题来说,只要出现了一个目标状态,我们直接退出即可。因为后面出现的目标状态所花的时间肯定更多(想想为什么)。
5)代码模板
#include<algorithm>
#include<cstdio>
#include<cstring>
#include<cmath>
#include<cstdlib>
#include<iostream>
#include<iomanip>
#include<list>
#include<map>
#include<queue>
#include<sstream>
#include<stack>
#include<string>
#include<set>
#include<vector>
using namespace std;
#define PI acos(-1.0)
#define EPS 1e-8
#define MOD 1e9+7
#define LL long long
#define ULL unsigned long long //1844674407370955161
#define INT_INF 0x7f7f7f7f //2139062143
#define LL_INF 0x7f7f7f7f7f7f7f7f //9187201950435737471
const int dr[]={0, 0, -1, 1, -1, -1, 1, 1};
const int dc[]={-1, 1, 0, 0, -1, 1, -1, 1};
// ios::sync_with_stdio(false);
// 那么cin, 就不能跟C的 scanf,sscanf, getchar, fgets之类的一起使用了。
const int maxn=100;
bool vst[maxn][maxn]; // 访问标记
int dir[4][2]={{0,1,0,-1}
{1,0,-1,0}}; // 方向向量
struct State // BFS 队列中的状态数据结构
{
int x,y; // 坐标位置
int Step_Counter; // 搜索步数统计器
};
State a[maxn];
bool CheckState(State s) // 约束条件检验
{
if(!vst[s.x][s.y] && ...) // 满足条件
return 1;
else // 约束条件冲突
return 0;
}
void bfs(State st)
{
queue <State> q; // BFS 队列
State now,next; // 定义2 个状态,当前和下一个
st.Step_Counter=0; // 计数器清零
q.push(st); // 入队
vst[st.x][st.y]=1; // 访问标记
while(!q.empty())
{
now=q.front(); // 取队首元素进行扩展
if(now==G) // 出现目标态,此时为Step_Counter 的最小值,可以退出即可
{
...... // 做相关处理
return;
}
for(int i=0;i<4;i++)
{
next.x=now.x+dir[i][0]; // 按照规则生成 下一个状态
next.y=now.y+dir[i][1];
next.Step_Counter=now.Step_Counter+1; // 计数器加1
if(CheckState(next)) // 如果状态满足约束条件则入队
{
q.push(next);
vst[next.x][next.y]=1; //访问标记
}
}
q.pop(); // 队首元素出队
}
return ;
}
int main()
{
......
return 0;
}