bfs
- BFS
- 1. 多源bfs
- 2.最小步数模型
- 1.魔板
- 2.八数码问题
- 3.双端队列广搜
- 4.双向广搜
- 5.A*算法
BFS
bfs是搜索算法里面最基础的算法,对于队首的点,每次搜索其周围所有的点,然后将其入队。队列里面的点具有两个特性:
(1)单调性,即队列的元素是递增的(可以相等)
(2)不重复,不会有相同的点
1. 多源bfs
一般的问题是求单源bfs,单源的意思就是一个起点,那么多源就是多个起点。
题意就是:遍历每个点,找到距离当前点最近的值为‘1’的点,记录他们之间的距离。
很明显,这是一个最短路问题,而且边权为1,满足这两个条件,可以考虑bfs了。但是有一个问题,通常使用bfs的时候,只有一个起点,这题每个点都是起点,那么怎么做?
实际上,将所有值为‘1’的点作为一个整体,都当成起点即可。因为值为1的点到最近的值为1的点就是其本身。
#include<iostream>
#include<cstring>
#include<algorithm>
#include<queue>
using namespace std;
const int N=1010;
typedef pair<int,int>PII;
char g[N][N];
int dist[N][N];
int n,m;
void bfs()
{
int dx[4]={-1,0,1,0};
int dy[4]={0,1,0,-1};
queue<PII>q;
memset(dist,-1,sizeof dist);
//首先将所有值为1的点入队
for(int i=1;i<=n;i++)
for(int j=1;j<=m;j++)
{
if(g[i][j]=='1')
{
dist[i][j]=0;
q.push({i,j});
}
}
while(!q.empty())
{
PII t=q.front();
q.pop();
int a=t.first;
int b=t.second;
for(int i=0;i<4;i++)
{
int x=a+dx[i];
int y=b+dy[i];
if(x<1||y<1||x>n||y>m)continue;
if(dist[x][y]!=-1)continue;
else
{
dist[x][y]=dist[a][b]+1; //不需要去min,因为第一次遍历到的一定是最小的,初始的起点每个点都是走一圈,每次都是如此。所有对于每个点都是一样的
q.push({x,y});
}
}
}
}
int main()
{
cin>>n>>m;
for(int i=1;i<=n;i++)
for(int j=1;j<=m;j++)
cin>>g[i][j];
bfs();
for(int i=1;i<=n;i++)
{
for(int j=1;j<=m;j++)
cout<<dist[i][j]<<" ";
cout<<endl;
}
return 0;
}
2.最小步数模型
最小步数模型常见的有八数码问题。
这里记录模板和八数码。
核心思想:普通的最小步数只需要记录点的坐标,也就是普通意义上的‘点’。然而可以更加抽象‘点’。将一个状态表示为一个点。以点为面,以面为点。八数码问题就是如此。
1.魔板
思路:将每个数组的状态当作一个点,然后进行一个“扩展”,这里的扩展就是三个基本操作。(对比普通bfs,扩展点就是搜索可以达到的周围邻点)
#include<iostream>
#include<cstring>
#include<queue>
#include<unordered_map>
#include<algorithm>
using namespace std;
unordered_map<string,pair<char,string>>pre; //记录当前的状态是由哪个状态转移过来的。并且记录上一步转移过来使用的是哪种方案
unordered_map<string,int>dist; //记录从起始状态转移到当前状态用了多少步数
char g[2][4];
void set(string state)
{
for (int i = 0; i < 4; i ++ ) g[0][i] = state[i];
for (int i = 7, j = 0; j < 4; i --, j ++ ) g[1][j] = state[i];
}
string get()
{
string res;
for (int i = 0; i < 4; i ++ ) res += g[0][i];
for (int i = 3; i >= 0; i -- ) res += g[1][i];
return res;
}
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);
int v0 = g[0][3], v1 = 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] = v0, g[1][0] = v1;
return get();
}
string move2(string state)
{
set(state);
int v = g[0][1];
g[0][1] = g[1][1];
g[1][1] = g[1][2];
g[1][2] = g[0][2];
g[0][2] = v;
return get();
}
int bfs(string start,string end)
{
//start通过广搜得到end
if(start==end)return 0;
queue<string>q;
q.push(start);
dist[start]=0;
while(!q.empty())
{
auto t=q.front();
q.pop();
//t有三种转移方式
string m[3];
m[0]=move0(t);
m[1]=move1(t);
m[2]=move2(t);
for(int i=0;i<3;i++)
{
if(!dist.count(m[i])) //当前的状态没有出现过
{
dist[m[i]]=dist[t]+1;
pre[m[i]]={'A'+i,t};
q.push(m[i]);
if(m[i]==end)return dist[m[i]];
}
}
}
return -1;
}
int main()
{
string start,end;
for(int i=0;i<8;i++)
{
int x;
cin>>x;
end+=char(x+'0');
}
for(int i=0;i<8;i++)start+=char(i+'1');
int step=bfs(start,end);
cout<<step<<endl;
//cout<<start<<" "<<end<<endl;
//下面输出路径
string res="";
while(end!=start)
{
res+=pre[end].first;
end=pre[end].second;
}
reverse(res.begin(),res.end());
if(step>0)
cout<<res<<endl;
return 0;
}
2.八数码问题
3.双端队列广搜
题意:从左上角到右下角,保证电路连通的情况下,最少扭转多少次线路。
假设两个点直接不需要扭转直接的线路,则两点的距离设置为0,否则为1.那么就变成了一个求最短路问题.
双端队列主要解决图中边的权值只有0或者1的最短路问题
每次从队头取出元素,并进行拓展其他元素时、
若拓展某一元素的边权是0,则将该元素插入到队头
若拓展某一元素的边权是1,则将该元素插入到队尾
#include<iostream>
#include<cstring>
#include<algorithm>
#include<deque>
using namespace std;
const int N=510;
typedef pair<int,int>PII;
#define x first
#define y second
char g[N][N];
int dist[N][N];
bool st[N][N];
int T;
int n,m;
//定义搜索周围的方向,左上,右上,左下,右下
int dx[4]={-1,-1,1,1};
int dy[4]={-1,1,1,-1};
int ix[4]={-1,-1,0,0};
int iy[4]={-1,0,0,-1};
int bfs()
{
memset(dist,0x3f,sizeof dist);
memset(st,false,sizeof st);
deque<PII>q;
dist[0][0]=0;
q.push_back({0,0});
char cs[5]={'\\','/','\\','/'};
while(!q.empty())
{
PII t=q.front();
q.pop_front();
st[t.x][t.y] = true;
for (int i = 0; i < 4; i ++ )
{
int a = t.x + dx[i], b = t.y + dy[i];
if (a < 0 || a > n || b < 0 || b > m) continue;
int ca = t.x + ix[i], cb = t.y + iy[i];
int d = dist[t.x][t.y] + (g[ca][cb] != cs[i]);
if (d < dist[a][b])
{
dist[a][b] = d;
if (g[ca][cb] != cs[i]) q.push_back({a, b});
else q.push_front({a, b});
}
}
}
return dist[n][m];
}
int main()
{
cin>>T;
while(T--)
{
cin>>n>>m;
for(int i=0;i<n;i++)
for(int j=0;j<m;j++)
cin>>g[i][j];
int ans=bfs();
if(m+n&1)cout<<"NO SOLUTION\n";
else cout<<ans<<endl;
}
return 0;
}
4.双向广搜
bfs问题当规模很大的时候,不妨从两端向中间搜索。这样复杂度会降低.但是代码难写一点
题意:给两个字符串,分别是起始状态和结束状态,然后给定几个变换规则。要求找到最少变化次数。
发现,如果单词bfs,那么时间复杂度会超时,所以尝试使用双向bfs。
思路:
两个字符串都同时作为起始状态,只要保证在搜索过程中,两者有一个状态是一样的,那么返回两者路径之和
。
关键在于每次扩展的时候,都需要将那一层所有的状态去和另一边去比较。否则答案不正确。
#include<iostream>
#include<cstring>
#include<algorithm>
#include<unordered_map>
#include<queue>
using namespace std;
const int N=6;
string A,B;
string a[N],b[N];
int n;
int expand(queue<string>&q,unordered_map<string,int>&da,unordered_map<string,int>&db,string a[N],string b[N])
{
int d=da[q.front()];
while(q.size()&&d==da[q.front()])
{
string t=q.front();
q.pop();
for(int i=0;i<n;i++)
for(int j=0;j<t.size();j++)
{
if(t.substr(j,a[i].size())==a[i])
{
string r=t.substr(0,j)+b[i]+t.substr(j+a[i].size());
if(db.count(r))return da[t]+1+db[r];
if(da.count(r))continue;
da[r]=da[t]+1;
q.push(r);
}
}
}
return 10000;
}
int bfs()
{
if(A==B)return 0;
queue<string>qa,qb;
unordered_map<string,int>da,db;
da[A]=0,db[B]=0;
qa.push(A),qb.push(B);
int step=0;
while(qa.size()&&qb.size())
{
int t;
if(qa.size()>qb.size())
t=expand(qb,db,da,b,a);
else
t=expand(qa,da,db,a,b);
if(t<=10)return t;
if(++step==10)return -1;
}
return -1;
}
int main()
{
cin>>A>>B;
while(cin>>a[n]>>b[n])n++;
int t=bfs();
if(t==-1)cout<<"NO ANSWER!";
else cout<<t;
return 0;
}
5.A*算法
#include<iostream>
#include<cstring>
#include<algorithm>
#include<queue>
#include<unordered_map>
using namespace std;
typedef pair<int,string>PIS;
string start,x;
int f(string m) //计算当前状态到终点的状态的估计距离
{
int dt=0;
for(int i=0;i<9;i++)
{
if(m[i]!='x')
{
int t=m[i]-'1'; //实际位置
dt=dt+abs(i/3-t/3)+abs(i%3-t%3);
}
}
return dt;
}
string bfs()
{
string end="12345678x";
unordered_map<string,int>dist;
unordered_map<string,pair<string,int>>last;
priority_queue<PIS,vector<PIS>,greater<PIS>>heap;
dist[start]=0;
heap.push({f(start),start}); //加入起点,记录当前的点和总距离
char oper[]="udlr";
int dx[4]={-1,1,0,0};
int dy[4]={0,0,-1,1};
while(heap.size())
{
auto t =heap.top();
heap.pop();
string state=t.second;
if(state==end)break; //找到最近距离
int x,y;
for(int i=0;i<9;i++)
if(state[i]=='x')
{
x=i/3,y=i%3;
break;
}
string init=state;
for(int i=0;i<4;i++)
{
int a=x+dx[i],b=y+dy[i];
if(a<0||b<0||a>2||b>2)continue;
swap(state[a*3+b],state[x*3+y]);
if(!dist.count(state)||dist[state]>dist[init]+1)
{
dist[state]=dist[init]+1;
heap.push({f(state)+dist[state],state});
last[state]={init,oper[i]};
}
state=init;
}
}
string ans;
while(end!=start)
{
ans+=last[end].second;
end=last[end].first;
}
reverse(ans.begin(),ans.end());
return ans;
}
int main()
{
char c;
while(cin>>c)
{
start+=c;
if(c!='x')x+=c;
}
//逆序对个数为奇数不能到达终点(12345678x)
//终点是没有逆序对的。九宫格里面,x左右交换不改变逆序对数量,上下交换改变偶数对,所以如果起始
//状态是奇数个逆序对,则无法到达终点。
int cnt=0;
for(int i=0;i<8;i++)
for(int j=i+1;j<8;j++)
if(x[i]>x[j])cnt++;
if(cnt%2)cout<<"unsolvable";
else cout<<bfs();
return 0;
}
先记录到这,,,