本文参考并拓展了 OI-Wiki BFS
BFS 是宽度优先搜索,也叫广度优先搜索。
所谓宽度优先。就是每次都尝试访问同一层的节点。 如果同一层都访问完了,再访问下一层。
这样做的结果是,BFS 算法找到的路径是从起点开始的 最短 合法路径。换言之,这条路径所包含的边数最小。
在 BFS 结束时,每个节点都是通过从起点到该点的最短路径访问的。
算法过程可以看做是图上火苗传播的过程:最开始只有起点着火了,在每一时刻,有火的节点都向它相邻的所有节点传播火苗。
bfs应用
BFS最经典、最常见的应用已经在上面给出。在 BFS 的过程中,我们也可以记录一些额外的信息。比如从开始节点到当前节点的最短路径,还可以记录路径。
时间复杂度 O ( n + m ) O(n+m) O(n+m)
除此以外,还有很多应用:
- 在一个无权图上求从起点到其他所有点的最短路径
- 求出所有连通块
- 如果把一个游戏的动作看做是状态图上的一条边(一个转移),那么 BFS 可以用来找到在游戏中从一个状态到达另一个状态所需要的最小步骤
- 在一个有向无权图中找最小环(从每个点开始 BFS,在我们即将抵达一个之前访问过的点开始的时候,就知道遇到了一个环。图的最小环是每次 BFS 得到的最小环的平均值)
- 找到一定在 ( u , v ) (u,v) (u,v) 最短路上的边。分别从 a 和 b 进行 BFS,得到两个 d 数组。之后对每一条边 ( u , v ) (u,v) (u,v) ,如果 d a [ u ] + 1 + d b [ v ] = d a [ b ] d_a[u]+1+d_b[v]=d_a[b] da[u]+1+db[v]=da[b],则说明该边在最短路上。
- 找到一定在 ( u , v ) (u,v) (u,v) 最短路上的点。分别从 a 和 b 进行 BFS,得到两个 d 数组。之后对每一个点 v v v ,如果 d a [ v ] + d b [ v ] = d a [ b ] d_a[v]+d_b[v]=d_a[b] da[v]+db[v]=da[b],则说明该点在最短路上。
经典例题1
洛谷 P1126 机器人搬重物
#include<bits/stdc++.h>
using namespace std;
int m,n;
int co[105][105];
int ans[105][105][4];
int vis[105][105][4];
int sx, sy, ex, ey;
struct node
{
int x;
int y;
int c;
int v;
node(int xx,int yy,int cc,int vv)
{
x=xx;y=yy;c=cc;v=vv;
}
};
int sum=0x3f3f3f3f;
queue <node> q;
bool check(int x, int y)
{
if(x<1||x>n-1||y<1||y>m-1)return false;
if(co[x][y]==1)return false;
if(co[x][y+1]==1)return false;
if(co[x+1][y]==1)return false;
if(co[x+1][y+1]==1)return false;
return true;
}
void bfs(int xx,int yy,int dd, int cntt)
{
q.push(node(xx,yy,dd,cntt));
while(!q.empty())
{
node nod = q.front();
int x = nod.x;
int y = nod.y;
int d = nod.c;
int cnt = nod.v;
if(vis[x][y][d])
{
q.pop();
continue;
}
ans[x][y][d] = cnt;
vis[x][y][d] = 1;
q.pop();
//cout << x<<' '<<y<< " " << d <<' '<<cnt<<endl;
if(x==ex&&y==ey)
{
sum = min(sum, cnt);
break;
}
int d1 = (d+1)%4;
int d2 = (d+3)%4;
if(ans[x][y][d1] > cnt + 1)
{
if(ans[x][y][d1])
q.push(node(x,y,d1,cnt+1));
}
if(ans[x][y][d2] > cnt + 1)
{
q.push(node(x,y,d2,cnt+1));
}
if(d==0)
{
for(int i=1;i<=3;i++)
{
if(check(x+i,y)==0)break;
else
{
if(ans[x+i][y][d] > cnt+1)
q.push(node(x+i,y,d,cnt+1));
}
}
}
if(d==1)
{
for(int i=1;i<=3;i++)
{
if(check(x,y+i)==0)break;
else
{
if(ans[x][y+i][d] > cnt+1)
q.push(node(x,y+i,d,cnt+1));
}
}
}
if(d==2)
{
for(int i=1;i<=3;i++)
{
if(check(x-i,y)==0)break;
else
{
if(ans[x-i][y][d] > cnt+1)
q.push(node(x-i,y,d,cnt+1));
}
}
}
if(d==3)
{
for(int i=1;i<=3;i++)
{
if(check(x,y-i)==0)break;
else
{
if(ans[x][y-i][d] > cnt+1)
q.push(node(x,y-i,d,cnt+1));
}
}
}
}
}
int main()
{
cin>>n>>m;
memset(co,-1,sizeof co);
memset(ans,0x3f3f3f3f,sizeof ans);
for(int i=1;i<=n;i++)
{
for(int j=1;j<=m;j++)
{
cin>>co[i][j];
}
}
cin>>sx>>sy>>ex>>ey;
char ch; cin>> ch;
int d;
if(ch=='S')d=0;
if(ch=='E')d=1;
if(ch=='N')d=2;
if(ch=='W')d=3;
bfs(sx,sy,d,0);
/*
if(ans[m][m]==0x3f3f3f3f)cout<<"-1";
else cout<<ans[m][m];
*/
if(sum==0x3f3f3f3f)cout<<"-1";
else cout<<sum;
}
双端队列bfs
适用范围
边权值为可能有,也可能没有(由于 BFS 适用于权值为 1 的图,所以一般权值是 0 或 1),或者能够转化为这种边权值的最短路问题。
一般情况下,我们把没有权值的边扩展到的点放到队首,有权值的边扩展到的点放到队尾。这样即可保证像普通 BFS 一样整个队列队首到队尾权值单调不下降。
经典例题2
Codeforces 173B
#include<bits/stdc++.h>
using namespace std;
int m,n;
int vis[1050][1050];
char ch[1050][1050];
struct node
{
int x;
int y;
int v;
int d;
node(int xx,int yy,int vv,int dd)
{
x=xx;y=yy;v=vv;d=dd;
}
};
vector<vector<int> > xx, yy;
int sum=0x3f3f3f3f;
deque <node> q;
int main()
{
cin>>n>>m;
xx.resize(n+1);
yy.resize(m+1);
int sx = 1, sy = -1;
for(int i=1;i<=n;i++)
{
string s;cin>>s;
for(int j=0;j<s.length();j++)
{
if(s[j]=='#')
{
if(i==1&&sy==-1)sy=j+1;
xx[i].emplace_back(j+1);
yy[j+1].emplace_back(i);
}
}
}
if(sy!=-1)q.push_front(node(sx,sy,0,0));
while(!q.empty())
{
node nod = q.front();
q.pop_front();
int x = nod.x;
int y = nod.y;
int v = nod.v;
int d = nod.d;
//cout << x << ' ' << y << ' ' << v << ' ' << d << endl;
if(vis[x][y])continue;
vis[x][y] = 1;
if(x==n)
{
sum = v;
break;
}
for(int i=yy[y].size()-1;i>=0;i--)
{
if(vis[yy[y][i]][y])continue;
if(yy[y][i]<x)
{
if(d==3)q.push_front(node(yy[y][i],y,v,3));
else q.push_back(node(yy[y][i],y,v+1,3));
}
if(yy[y][i]>x)
{
if(d==1)q.push_front(node(yy[y][i],y,v,1));
else q.push_back(node(yy[y][i],y,v+1,1));
}
yy[y].pop_back();
}
for(int i=xx[x].size()-1;i>=0;i--)
{
if(vis[x][xx[x][i]])continue;
if(xx[x][i]<y)
{
if(d==2)q.push_front(node(x,xx[x][i],v,2));
else q.push_back(node(x,xx[x][i],v+1,2));
}
if(xx[x][i]>y)
{
if(d==0)q.push_front(node(x,xx[x][i],v,0));
else q.push_back(node(x,xx[x][i],v+1,0));
}
xx[x].pop_back();
}
}
if(sum == 0x3f3f3f3f)
{
cout << -1;
}
else cout << sum + 1;
}
优先队列bfs
在基于优先队列的 BFS 中,我们每次从队首取出代价最小的结点进行进一步搜索。容易证明这个贪心思想是正确的,因为从这个结点开始扩展的搜索,一定不会更新原来那些代价更高的结点。换句话说,其余那些代价更高的结点,我们不回去考虑更新它。
题目待补