BFS
宽度优先搜索: 对每个搜索到的结点向外扩展,每次搜索一层的子节点.
特点: 对于边权相等的图,可以求最短路.
BFS基本模板:
void bfs()
{
queue<int> q;
st[1] = 1; //从一号点开始
q.push(1);
while (q.size()){
int u = q.front();
q.pop();
for (int i = h[u]; i != -1; i = ne[i]){
int j = e[i];
if (!st[j]){
st[j] = 1;
q.push(j);
}
}
}
}
BFS基本应用:
-
Flood Fill(洪水灌溉模型求解联通问题)
-
最短路模型
-
最小步数模型
-
多源BFS
- 双端队列广搜
BFS的优化
BFS 相对于 DFS 优化较少
-
双向广搜
-
A*
Flood Fill 模型
AcWing 1097. 池塘计数 原题链接
算法思路:
Flood Fill 模板题,遍历一遍所有结点,每次没有询问的结点,进行BFS, 每次BFS会找到一个联通块
#include <iostream>
#include <cstring>
#include <algorithm>
#include <queue>
using namespace std;
typedef pair<int,int> PII;
const int N = 1010;
char g[N][N];
int n,m;
int st[N][N];
int res = 0;
int dx[8] = {1,0,-1,0,1,-1,1,-1};
int dy[8] = {0,-1,0,1,-1,1,1,-1};
void bfs(int x, int y)
{
queue<PII> q;
q.push({x,y});
st[x][y] = 1;
while (q.size()){
auto it = q.front();
q.pop();
for (int i = 0; i < 8; i ++ ){
int xx = it.first + dx[i];
int yy = it.second + dy[i];
if (xx >= 1 && xx <= n && yy >= 1 && yy <= m && !st[xx][yy] && g[xx][yy] == 'W'){
q.push({xx,yy});
st[xx][yy] = 1;
}
}
}
}
int main()
{
cin >> n >> m;
char c = getchar();
for (int i = 1; i <= n; i ++ )
scanf("%s",g[i] + 1);
for (int i = 1; i <= n; i ++ )
for (int j = 1; j <= m; j ++ )
if (!st[i][j] && g[i][j] == 'W'){
bfs(i, j);
res ++;
}
cout << res << endl;
return 0;
}
AcWing 1098. 城堡问题 原题链接
算法思路:
同样是Flood Fill 模板题,要注意的是对每个点的处理.
使用二进制处理每个点, $1 -> 2 ^ 0 -> 1$, $2 -> 2 ^ 1 -> 10$, $4 -> 2 ^ 2 -> 100$, $8 -> 2 ^ 3 -> 1000$.
使用 1 << i & k 判断对应二进制位是否为0
#include <iostream>
#include <cstring>
#include <queue>
using namespace std;
typedef pair<int,int> PII;
const int N = 60;
int mp[N][N];
int st[N][N];
int dx[4] = {0,-1,0,1};
int dy[4] = {-1,0,1,0};
int n,m;
int bfs(int x, int y)
{
int res = 1;
queue<PII> q;
q.push({x,y});
st[x][y] = 1;
while (q.size()){
auto it = q.front();
q.pop();
int t = mp[it.first][it.second];
for (int i = 0; i < 4; i ++ ){
int xx = it.first + dx[i];
int yy = it.second + dy[i];
if (!(1 << i & t) && xx >= 1 && xx <= n && yy >= 1 && yy <= m && !st[xx][yy]){
q.push({xx,yy});
st[xx][yy] = 1;
res ++;
}
}
}
return res;
}
int main()
{
int res = 0;
int ans = 0;
cin >> n >> m;
for (int i = 1; i <= n; i ++ )
for (int j = 1; j <= m; j ++ )
cin >> mp[i][j];
for (int i = 1; i <= n; i ++ )
for (int j = 1; j <= m; j ++ )
if (!st[i][j]){
ans = max(ans,bfs(i,j));
res ++;
}
cout << res << endl << ans << endl;
return 0;
}
AcWing 1106. 山峰和山谷 原题链接
算法思路:
题目涉及Flood Fill 的同时处理联通块之间的关系(山峰与山谷), 在每次BFS向外扩展时, 当遇到联通块以外的结点,处理结点之间的关系.
#include <iostream>
#include <cstring>
#include <queue>
using namespace std;
typedef pair<int,int> PII;
const int N = 1010;
int mp[N][N];
int st[N][N];
int n;
int higher;
int lower;
int dx[8] = {1,0,-1,0,1,-1,1,-1};
int dy[8] = {0,1,0,-1,1,-1,-1,1};
void bfs(int x, int y)
{
queue<PII> q;
q.push({x,y});
st[x][y] = 1;
while (q.size()){
auto it = q.front();
q.pop();
for (int i = 0; i < 8; i ++ ){
int xx = it.first + dx[i];
int yy = it.second + dy[i];
if (xx < 1 || xx > n || yy < 1 || yy > n)
continue;
if (mp[x][y] == mp[xx][yy] && !st[xx][yy]){
q.push({xx,yy});
st[xx][yy] = 1;
}
if (mp[xx][yy] != mp[x][y]){
if (mp[xx][yy] > mp[x][y])
higher = 1;
else
lower = 1;
}
}
}
}
int main()
{
cin >> n;
for (int i = 1; i <= n; i ++ )
for (int j = 1; j <= n; j ++ )
scanf("%d",&mp[i][j]);
int res1 = 0;
int res2 = 0;
for (int i = 1; i <= n; i ++ )
for (int j = 1; j <= n; j ++ )
if (!st[i][j]){
higher = 0;
lower = 0;
bfs(i, j);
if (!higher)
res1 ++;
if (!lower)
res2 ++;
}
cout << res1 << ' ' << res2 << endl;
return 0;
}
最短路模型
AcWing 1076. 迷宫问题 原题链接
算法思路 :
BFS求最短路,注意点是使用pre数组记录路径:
pre[i][j] = {x,y} 表示从$(x,y)$走到$(i,j)$,反向记录路径.输出时向前导
#include <iostream>
#include <cstring>
#include <queue>
using namespace std;
typedef pair<int,int> PII;
const int N = 1010;
int mp[N][N];
int st[N][N];
int n;
PII pre[N][N];
int dx[4] = {0, 1, 0, -1};
int dy[4] = {1, 0, -1, 0};
void bfs(int x, int y)
{
memset(pre, -1 ,sizeof pre);
queue<PII> q;
q.push({x,y});
pre[x][y] = {0,0};
while (q.size()){
auto it = q.front();
q.pop();
for (int i = 0; i < 4; i ++ ){
int xx = it.first + dx[i];
int yy = it.second + dy[i];
if (xx < 0 || xx >= n || yy < 0 || yy >= n)
continue;
if (mp[xx][yy])
continue;
if (pre[xx][yy].first != -1)
continue;
q.push({xx,yy});
pre[xx][yy] = {it.first,it.second}; //pre[xx][yy] = {x,y} 表示从(x,y) -> (xx,yy)
}
}
}
int main()
{
cin >> n;
for (int i = 0; i < n; i ++ )
for (int j = 0; j < n; j ++ )
cin >> mp[i][j];
bfs(n - 1, n - 1); //倒着向前走
PII end(0,0); //最后一个点是(0,0)
while (1){
cout << end.first << ' ' << end.second << endl;
if (end.first == n - 1 && end.second == n - 1)
break;
end = pre[end.first][end.second]; //向前导
}
return 0;
}
AcWing 188. 武士风度的牛 原题链接
算法思路:
求最短距离,注意8个方向如何表示
#include <iostream>
#include <cstring>
#include <queue>
using namespace std;
const int N = 160;
typedef pair<int,int> PII;
char mp[N][N];
int st[N][N];
int n,m;
int dist[N][N];
int dx[8] = {-2, -1, 1, 2, 2, 1,-1,-2};
int dy[8] = { 1, 2, 2, 1,-1,-2,-2,-1};
int ex,ey;
int bfs(int sx, int sy)
{
queue<PII> q;
q.push({sx, sy});
st[sx][sy] = 1;
dist[sx][sy] = 0;
while (q.size()){
auto it = q.front();
q.pop();
for (int i = 0; i < 8; i ++ ){
int xx = it.first + dx[i];
int yy = it.second + dy[i];
if (xx < 1 || xx > n || yy < 1 || yy > m)
continue;
if (st[xx][yy] || mp[xx][yy] == '*')
continue;
q.push({xx,yy});
dist[xx][yy] = dist[it.first][it.second] + 1;
st[xx][yy] = 1;
}
}
return dist[ex][ey];
}
int main()
{
cin >> m >> n;
int sx,sy;
for (int i = 1; i <= n ; i ++ )
scanf("%s",mp[i] + 1);
for (int i = 1; i <= n; i ++ )
for (int j = 1; j <= m; j ++ )
if (mp[i][j] == 'K'){
sx = i;
sy = j;
}else if (mp[i][j] == 'H'){
ex = i;
ey = j;
}
int res = bfs(sx, sy);
cout << res << endl;
return 0;
}
AcWing 1100. 抓住那头牛 原题链接
算法思路 :
一维求最短路,由于题目没有给出数轴的边界范围,需要判断边界范围(BFS不能无限扩展)
每次向外扩展时,是对下一个节点的合法性进行判断
#include <iostream>
#include <cstring>
#include <queue>
using namespace std;
const int INF = 0x3f3f3f3f;
const int N = 2e5 + 10;
int n, k;
int dist[N];
int main()
{
int res = INF;
cin >> n >> k;
memset(dist, -1, sizeof dist);
queue<int> q;
q.push(n);
dist[n] = 0;
while (q.size()){
int t = q.front();
q.pop();
//需要求出下一个状态,注意是对下一个状态是否合法进行判断
if (t + 1 <= N && dist[t + 1] == -1){
q.push(t + 1);
dist[t + 1] = dist[t] + 1;
}
if (t - 1 >= 0 && dist[t - 1] == -1){
q.push(t - 1);
dist[t - 1] = dist[t] + 1;
}
if (t * 2 <= N && dist[t * 2] == -1){
q.push(2 * t);
dist[2 * t] = dist[t] + 1;
}
}
cout << dist[k] << endl;
return 0;
}
多源BFS :
AcWing 173. 矩阵距离 原题链接
算法思路 :
从多个点到一个确定点的最短距离, 与一般的多点到多点的最短距离不同,我们可以假设一个虚拟原点,该虚拟原点到各个起点的距离为0, 问题转化为单源最短路.
针对该题,可以将所有起始点直接入队.
#include <iostream>
#include <cstring>
#include <algorithm>
#include <queue>
using namespace std;
typedef pair<int,int> PII;
const int N = 1010;
char g[N][N];
int dist[N][N];
int dx[4] = {0, 1, 0, -1};
int dy[4] = {1, 0,-1, 0};
int main()
{
memset(dist, -1, sizeof dist);
int n,m;
cin >> n >> m;
queue<PII> q;
char c = getchar();
for (int i = 1; i <= n; i ++ )
scanf("%s",g[i] + 1);
for (int i = 1; i <= n; i ++ )
for (int j = 1; j <= m; j ++ )
if (g[i][j] == '1'){
q.push({i,j});
dist[i][j] = 0;
}
while (q.size()){
auto it = q.front();
q.pop();
for (int i = 0; i < 4; i ++ ){
int xx = it.first + dx[i];
int yy = it.second + dy[i];
if (xx < 1 || xx > n || yy < 1 || yy > m)
continue;
if (dist[xx][yy] != -1)
continue;
q.push({xx,yy});
dist[xx][yy] = dist[it.first][it.second] + 1;
}
}
for (int i = 1; i <= n; i ++ ){
for (int j = 1; j <= m; j ++ )
cout << dist[i][j] << ' ';
cout << endl;
}
return 0;
}
最小步数模型
AcWing 1107. 魔板 原题链接
算法思路:
区别最短路与最小步数 : 最短路一般指图内一点到另一点的最短距离,最小步数一般指状态之间的转移的最小次数.
模拟状态与状态之间的最短步数, 对于每个状态,使用Hash(map)存储每个状态.
这道题练代码实现能力
#include <iostream>
#include <cstring>
#include <queue>
#include <map>
#include <string>
#include <algorithm>
using namespace std;
char g[2][5];
map<string,int> dist;
map<string, pair<char,string>> pre;
void set(string state)
{
for (int i = 0; i < 4; i ++ )
g[0][i] = state[i];
for (int i = 7, j = 0; j < 4; j ++, i -- )
g[1][j] = state[i];
return ;
}
string get()
{
string state;
for (int i = 0; i < 4; i ++ )
state += g[0][i];
for (int i = 3; i >= 0; i -- )
state += g[1][i];
//cout << state << "->" << endl;
return state;
}
string move0(string state)
{
set(state);
for (int i = 0; i < 4; i ++ )
swap(g[0][i], g[1][i]);
return get();
}
string move1(string state)
{
set(state);
char s1 = g[0][3], s2 = g[1][3];
for (int i = 3; i > 0; i -- ){
g[0][i] = g[0][i - 1];
g[1][i] = g[1][i - 1];
}
g[0][0] = s1;
g[1][0] = s2;
return get();
}
string move2(string state)
{
set(state);
char c = g[0][1];
g[0][1] = g[1][1];
g[1][1] = g[1][2];
g[1][2] = g[0][2];
g[0][2] = c;
return get();
}
int bfs(string start, string eend)
{
if (start == eend)
return 0;
queue<string> q;
q.push(start);
dist[start] = 0;
while (q.size()){
string t = q.front();
q.pop();
string m[4];
m[0] = move0(t);
m[1] = move1(t);
m[2] = move2(t);
/*
cout << t << endl;
for (int i = 0; i < 3; i ++ )
cout << "->" << m[i] << endl;
return 0;
*/
for (int i = 0; i < 3; i ++ ){
string state = m[i];
if (!dist.count(state)){
dist[state] = dist[t] + 1;
pre[state] = {i + 'A', t};
q.push(state);
if (state == eend)
return dist[eend];
}
}
}
}
int main()
{
int x;
string start, eend;
for (int i = 0; i < 8; i ++ ){
cin >> x;
eend += char(x + '0');
}
for (int i = 1; i <= 8; i ++ )
start += char(i + '0');
int ans = bfs(start, eend);
//return 0;
string res;
while (eend != start){
res += pre[eend].first;
eend = pre[eend].second;
}
cout << ans << endl;
reverse(res.begin(), res.end());
if (ans)
cout << res << endl;
return 0;
}
双向广搜(BFS 优化):
AcWing 190. 字串变换 原题链接
算法思路 :
使用两个队列同时向最终状态进行搜索,可以减少搜索空间.题目另一个难点在代码实现,使用Hash存储每个状态.
(具体见代码)
#include <iostream>
#include <queue>
#include <cstring>
#include <map>
#include <algorithm>
#include <string>
using namespace std;
int n;
string opa[7];
string opb[7];
queue<string> qa;
queue<string> qb;
map<string,int> dista;
map<string,int> distb;
int extend(queue<string>& q, map<string,int>& dista, map<string,int>& distb, string opa[], string opb[])
{
string state = q.front();
q.pop();
for (int i = 0; i < n; i ++ ) //枚举所有变化方式
for (int j = 0; j < state.size(); j ++ ){ //枚举当前字符串是否可以转换
if (state.substr(j, opa[i].size()) == opa[i]){ //substr(i,len): 从i起取长为len的字符串
string next = state.substr(0,j) + opb[i] + state.substr(j + opa[i].size());
if (distb.count(next)) return distb[next] + dista[state] + 1; //在反向中找到next,会合成功
if (!dista.count(next)){
dista[next] = dista[state] + 1;
q.push(next);
}
}
}
return 99;
}
int bfs(string A, string B)
{
dista[A] = 0;
distb[B] = 0;
qa.push(A); //正向
qb.push(B); //反向
int t;
while (qa.size() && qb.size()){ //二者同时不空,若空一个,则二者不连通
if (qa.size() <= qb.size()) //正向扩展(正向空间更小)
t = extend(qa, dista, distb, opa, opb);
else
t = extend(qb, distb, dista, opb, opa);
if (t <= 10) //找到一个使正反方向会合且操作数小于10的结果
return t;
}
return 99;
}
int main()
{
string A, B;
cin >> A >> B;
while (cin >> opa[n] >> opb[n]) //所有操作
n ++;
int res = bfs(A, B);
if (res > 10)
puts("NO ANSWER!");
else
cout << res << endl;
return 0;
}