引入
双向BFS指的是,从起点和终点同时往中间搜;
这种搜法是对普通BFS的一个优化;
下面是单向BFS和双向BFS的直观感受;
应用场景
一般应用于需要将整体抽象成一个状态的搜索;
因为这样的搜索状态很多,直接单向搜的话,可能会TLE、MLE;
而像武士风度的牛(求最短路)、城堡问题(求连通块及其大小)的优化效果不明显;
实现方式
方式一、
起点扩展一层、终点扩展一层,以此类推;
方式二、
相对于方式一是一个优化;
从当前状态较少的一边开始搜,避免极端情况(即一边状态很多,一边状态很少);
例题
字串变换
优化程序的简单计算
如果单向搜索的话,假设有6个规则,变换10次;
那么时间为 6 10 6^{10} 610;
如果我们双向搜索,只需要搜 2 ∗ 6 5 2*6^5 2∗65;
可以看出是极大的优化了;
思路
这里采用从状态总数较少的一边进行扩展,具体见代码注释;
Code
#include <iostream>
#include <cstdio>
#include <algorithm>
#include <unordered_map>
#include <queue>
#include <string>
using namespace std;
typedef long long ll;
const int N = 1e5 + 10;
string start,over;
string ra[10],rb[10];//规则 ra(i) -> rb(i)
int n;
typedef unordered_map<string,int> umsi;
int extend(queue<string> &que,umsi& da,umsi& db,string ra[],string rb[]){
string u = que.front();
que.pop();
//枚举起点
for(int i=0;i<u.size();++i){
//枚举规则
for(int j=0;j<n;++j){
//如果匹配的上
if(u.substr(i,ra[j].size()) == ra[j]){
//按规则变换
string now = u.substr(0,i) + rb[j] + u.substr(i+ra[j].size());
//如果另一个队列存在now 直接返回
if(db.count(now)) return db[now] + da[u] + 1;
//不重复入队
if(da.count(now)) continue;
da[now] = da[u] + 1;
que.push(now);
}
}
}
//没有接触到对方
return -1;
}
int bfs(){
if(start == over) return 0;
queue<string> qa,qb;//从起点搜的队列、从终点搜的队列
umsi disa,disb;//距离
disa[start] = disb[over] = 0;
qa.push(start);
qb.push(over);
//必须两边队列都有值才能继续扩展
int res;
while(!qa.empty() && !qb.empty()){
//从起点扩展
if(qa.size() <= qb.size()){
res = extend(qa,disa,disb,ra,rb);
}//从终点扩展
else{
res = extend(qb,disb,disa,rb,ra);
}
if(res!= -1 && res <= 10) return res;
}
return -1;
}
void solve(){
cin >> start >> over;
//[0,n)
while(cin >> ra[n] >> rb[n]) ++n;
int ans = bfs();
if(ans == -1) cout << "NO ANSWER!\n";
else cout << ans << '\n';
}
int main(){
std::ios::sync_with_stdio(false),cin.tie(0),cout.tie(0);
solve();
return 0;
}
小A与小B
题面
思路
强制双向BFS属于是;
需要注意的是,小B走两格,不一定要直着走,可以转弯的;
那我们对于小B来说,扩展两次即可;
注意每次扩展要将当前队列中的点全部扩展一遍;
还有一点,这题目的小A是很神奇的角色,因为他可以穿模;
像上一题中,我们扩展是要求两个队列都有元素的情况;
但是这里扩展只需要一边有元素即可,请看这组数据;
小A穿模卡过去找小B,就很神奇;
因此这里必须写成这样
while(!queA.empty() || !queB.empty())
不能写成这样
while(!queA.empty() && !queB.empty())
Code
#include <iostream>
#include <cstdio>
#include <algorithm>
#include <queue>
#include <utility>
using namespace std;
typedef long long ll;
const int N = 1e3 + 10;
int dx[] = {1,-1,0,0,-1,1,1,-1};
int dy[] = {0,0,1,-1,1,1,-1,-1};
int n,m;
char G[N][N];
typedef pair<int,int> pii;
queue<pii> queA;
queue<pii> queB;
bool visA[N][N];
bool visB[N][N];
int step;
bool extend(queue<pii>& que,bool visA[N][N],bool visB[N][N],int k){
int sz = que.size();
while(sz--){ //一次扩展一圈
auto u = que.front();
que.pop();
for(int i=0,xx,yy;i<k;++i){
xx = u.first + dx[i];
yy = u.second + dy[i];
if(xx < 1 || xx > n || yy < 1 || yy > m) continue;
if(G[xx][yy] == '#' || visA[xx][yy]) continue;
if(visB[xx][yy]) return 1;
visA[xx][yy] = 1;
que.push({xx,yy});
}
}
return 0;
}
int bfs(){
int ax,ay,bx,by;
for(int i=1;i<=n;++i)
for(int j=1;j<=m;++j){
if(G[i][j] == 'C') ax = i,ay = j;
else if(G[i][j] == 'D') bx = i,by = j;
}
if(ax == bx && ay == by) return 0;
queA.push({ax,ay});
queB.push({bx,by});
visA[ax][ay] = 1;
visB[bx][by] = 1;
step = 0;
while(!queA.empty() || !queB.empty()){
++step;
if(extend(queA,visA,visB,8)) return step;
if(extend(queB,visB,visA,4)) return step;
if(extend(queB,visB,visA,4)) return step;
}
return -1;
}
void solve(){
cin >> n >> m;
char ch;
for(int i=1;i<=n;++i){
for(int j=1;j<=m;++j){
while(cin >> ch,ch == ' ');
G[i][j] = ch;
}
}
int res = bfs();
if(res != -1){
cout << "YES\n";
cout << res << '\n';
}
else cout << "NO\n";
}
int main(){
std::ios::sync_with_stdio(false),cin.tie(0),cout.tie(0);
solve();
return 0;
}