如果已经知道搜索的开始状态和结束状态, 要找一个满足某种条件的一条路径(一般是最短路径),为了避免无谓的“组合爆炸”产生,就可以采取双向广度搜索算法,也就是从开始状态和结束状态同时开始搜索,一个向前搜,一个向后找。 这样做的好处是什么? 我们不妨假设每次搜索的分支因子是r,如果最短的路径长为L的话(也就是搜了L层),那么,用一般的BFS算法(不考虑去掉重复状态),总的搜索状态数是r^L(^表示乘方运算);而如果采取双向BFS算法,那么,从前往后搜,我们只需要搜索L/2层,从后往前搜,我们也只要搜L/2层,因此,搜索状态数是2*(r^(L/2)),比普通BFS就快了很多了。 双向BFS算法的实质还是BFS,只不过两边同时开始BFS而已。还是可以利用队列来实现:可以设置两个队列,一个用于向前的BFS,另一个用于向后的BFS,利用这两个队列,同时从前、后开始层次遍历搜索树。
双向广搜模版:
void BFS_bothsides()//双向BFS
{
if(s1.state==s2.state)//起点终点相同时要特判
{
//do something
found=true;
return;
}
bool found=false;
memset(visited,0,sizeof(visited)); // 判重数组
while(!Q1.empty()) Q1.pop(); // 正向队列
while(!Q2.empty()) Q2.pop(); // 反向队列
//======正向扩展的状态标记为1,反向扩展标记为2
visited[s1.state]=1; // 初始状态标记为1
visited[s2.state]=2; // 结束状态标记为2
Q1.push(s1); // 初始状态入正向队列
Q2.push(s2); // 结束状态入反向队列
while(!Q1.empty() || !Q2.empty())
{
if(!Q1.empty())
BFS_expand(Q1,true); // 在正向队列中搜索
if(found) // 搜索结束
return ;
if(!Q2.empty())
BFS_expand(Q2,false); // 在反向队列中搜索
if(found) // 搜索结束
return ;
}
}
void BFS_expand(queue<Status> &Q,bool flag)
{
s=Q.front(); // 从队列中得到头结点s
Q.pop()
for( 每个s 的子节点 t )
{
t.state=Gethash(t.temp); // 获取子节点的状态
if(flag) // 在正向队列中判断
{
if(visited[t.state]!=1)// 没在正向队列出现过
{
if(visited[t.state]==2) // 该状态在反向队列中出现过
{
各种操作;
found=true;
return;
}
visited[t.state]=1; // 标记为在在正向队列中
Q.push(t); // 入队
}
}
else // 在正向队列中判断
{
if (visited[t.state]!=2) // 没在反向队列出现过
{
if(visited[t.state]==1) // 该状态在正向向队列中出现过
{
各种操作;
found=true;
return;
}
visited[t.state]=2; // 标记为在反向队列中
Q.push(t); // 入队
}
}
}
}
下面上一道例题熟悉下代码:
题意:
解开密码锁,从初始状态到开锁状态问最少做几次操作。操作可以是上下转动(上下加减1,特别的:9 + 1 = 1, 1 - 0 = 9 )和交换两个相邻位(最左面的和最右面的不相邻)。一共8+3=11种状态扩展
标程:
#include<cstring>
#include<cstdio>
#include<queue>
using namespace std;
const int MAXN=10010;
int vis[MAXN];
int step[MAXN];
struct Node
{
int num[5];
int status;
}st,ed;
int flag;
int minStep;
int toInt(int num[])
{
return num[0]*1000+num[1]*100+num[2]*10+num[3];
}
void toArray(int num,int arr[])
{
arr[0]=num/1000;
arr[1]=(num%1000)/100;
arr[2]=(num%100)/10;
arr[3]=num%10;
}
void BFS_expand(queue<Node> & que,int choose)
{
Node curr=que.front(),next;
que.pop();
for(int i=0;i<4;i++)
{
for(int j=0;j<2;j++)
{
next=curr;
if(j==0)
{
next.num[i]=curr.num[i]+1;
if(next.num[i]==10) next.num[i]=1;
}
else
{
next.num[i]=curr.num[i]-1;
if(next.num[i]==0) next.num[i]=9;
}
next.status=toInt(next.num);
int dis=step[curr.status]+1;
if(choose==0)
{
if(vis[next.status]!=1)
{
if(vis[next.status]==2)
{
flag=true;
minStep=step[next.status]+dis;
return;
}
step[next.status]=dis;
vis[next.status]=1;
que.push(next);
}
}
else
{
if(vis[next.status]!=2)
{
if(vis[next.status]==1)
{
flag=true;
minStep=step[next.status]+dis;
return;
}
step[next.status]=dis;
vis[next.status]=2;
que.push(next);
}
}
}
}
for(int i=0;i<3;i++)
{
next=curr;
next.num[i]=curr.num[i+1];
next.num[i+1]=curr.num[i];
next.status=toInt(next.num);
int dis=step[curr.status]+1;
if(choose==0)
{
if(vis[next.status]!=1)
{
if(vis[next.status]==2)
{
minStep=dis+step[next.status];
flag=true;
return;
}
vis[next.status]=1;
step[next.status]=dis;
que.push(next);
}
}
else
{
if(vis[next.status]!=2)
{
if(vis[next.status]==1)
{
minStep=dis+step[next.status];
flag=true;
return;
}
vis[next.status]=2;
step[next.status]=dis;
que.push(next);
}
}
}
}
void TBFS()
{
if(st.status==ed.status)//起点终点相同时要特判
{
minStep=0;
return;
}
queue<Node> q1;
queue<Node> q2;
q1.push(st);
q2.push(ed);
memset(vis,0,sizeof(vis));
vis[st.status]=1;
vis[ed.status]=2;
step[st.status]=0;
step[ed.status]=0;
flag=false;
while(!q1.empty()||!q2.empty())
{
if(!q1.empty())
{
BFS_expand(q1,0);
}
if(flag) return;
if(!q2.empty())
{
BFS_expand(q2,1);
}
if(flag) return;
}
}
int main()
{
int t,s1,s2;
scanf("%d",&t);
while(t--)
{
scanf("%d%d",&s1,&s2);
toArray(s1,st.num);
st.status=s1;
toArray(s2,ed.num);
ed.status=s2;
TBFS();
printf("%d\n",minStep);
}
return 0;
}