这里列出的搜索方法,以点为基础进行的搜索,与搜索2等后边所列的搜索方法,不一样,图的表示也不太一样
1. 1 BFS模板:maze
1.1.1 关键:无权最短路的解题方法
1.1.2 题目大意
输入一个5*5的地图,0,1表示,1的点不能走,要求求出(0,0)到(4,4)的最短路径
1.1.3做法
初始化
这个题思路较为明确,首先用一个01数组模拟迷宫,一个vis数组记录某个点是否被访问,一个dis数组记录某个可访问点距离源点的距离,两个常量数组表示上下左右移动的方向。
int a[maxn][maxn],dis[maxn][maxn],n,m;
bool vis[maxn][maxn];
int sx,sy,tx,ty;//开始和结束的点
int dx[4]={1,0,-1,0}; //运动方向
int dy[4]={0,-1,0,1};
memset(vis,0,sizeof vis);
memset(dis,63,sizeof dis);
memset(a,0,sizeof a);
struct point
{ int x,y;
point(int x1,int y1) {
x=x1; y=y1; }
};
queue<point> q;
bfs
在开始寻找的时候,从队列中取出一个位置元素,访问它上下左右的点,若这些点可以被访问,且没有被访问的,则加入队列,并记录这个点的距离dis。以此循环,直到队列为空。
前期准备
void bfs()
{
q.push(point(sx,sy));
vis[sx][sy]=1;
dis[sx][sy]=0;
while(!q.empty())
{
point p= q.front();
q.pop();
for(int i=0;i<4;i++) //4个方向,或者是其他的运动形式
{
int nx=p.x+dx[i];
int ny=p.y+dy[i];
if(!vis[nx][ny]&&!a[nx][ny]&&nx>=0&&nx<n&&ny>=0&&ny<m)
{
//cout<<p.x<<" "<<p.y<<" "<<nx<<" "<<ny<<endl;
vis[nx][ny]=1; //更新vis
dis[nx][ny]=dis[p.x][p.y]+1;//更新距离
q.push(point(nx,ny));
}
}
}
}
路径输出
在本题中采用非递归输出,即从终点开始上下左右搜索,寻找距离源点比终点距离源点少1的的点,之后将这个点压入一个栈中,再去这个点的位置,上下左右查找,再找距离少1的点,以此类推,直到到达源点。最后再将栈中的序列输出即可
个人调试方法
for(int i=0;i<n;i++)
{
for(int j=0;j<m;j++)
{
printf("%d |vis:%d ",dis[i][j],vis[i][j]);
}
cout<<endl;
}
1.1.4 代码
#include<cstdio>
#include<iostream>
#include<cstring>
#include<queue>
#include<stack>
#define maxn 1000
using namespace std;
int a[maxn][maxn],dis[maxn][maxn],n,m;
bool vis[maxn][maxn];
int sx,sy,tx,ty;//开始和结束的点
int dx[4]={1,0,-1,0}; //运动方向
int dy[4]={0,-1,0,1};
struct point
{
int x,y;
point(int x1,int y1) {
x=x1;
y=y1;
}
};
queue<point> q;
stack<point> s;
void bfs()
{
q.push(point(sx,sy));
vis[sx][sy]=1;
dis[sx][sy]=0;
while(!q.empty())
{
point p= q.front();
q.pop();
for(int i=0;i<4;i++) //4个方向
{
int nx=p.x+dx[i];
int ny=p.y+dy[i];
if(!vis[nx][ny]&&!a[nx][ny]&&nx>=0&&nx<n&&ny>=0&&ny<m)
{
//cout<<p.x<<" "<<p.y<<" "<<nx<<" "<<ny<<endl;
vis[nx][ny]=1; //更新vis
dis[nx][ny]=dis[p.x][p.y]+1;//更新距离
q.push(point(nx,ny));
}
}
}
}
void outpath(point p) //通过dis输出路径
{
point p1=p;
s.push(p1);
while(p1.x!=0||p1.y!=0)
{
for(int i=0;i<4;i++) //4个方向
{
int nx=p1.x+dx[i];
int ny=p1.y+dy[i];
if(nx>=0&&nx<n&&ny>=0&&ny<m&&1+dis[nx][ny]==dis[p1.x][p1.y])
{
p1.x=nx;
p1.y=ny;
s.push(p1);
break;
}
}
}
while(!s.empty())
{
point t = s.top();
s.pop();
printf("(%d, %d)\n",t.x,t.y);
}
}
int main()
{
n=m=5;
// freopen("in.txt","r",stdin);
for(int i=0;i<n;i++)
for(int j=0;j<m;j++)
scanf("%d",a[i]+j);
sx =sy=0;
tx=ty=4;
memset(vis,0,sizeof vis);
memset(dis,63,sizeof dis);
bfs();
outpath(point(n-1,m-1));
return 0;
}
1.2 隐式图BFS
1.2.1 关键 构建状态隐式图与转移函数,搜寻可能的点
1.2.2 题目大意
给两个杯子的容量,以及目标的水量,给出倒水的方法
input
输入包含多组数据。每组数据输入 A, B, C 数据范围 0 < A <= B 、C <= B <=1000 、A和B互质。
output:
输出将由一系列的指令组成。这些输出行将导致任何一个罐子正好包含C单位的水。每组数据的最后一行输出应该是“success”。输出行从第1列开始,不应该有空行或任何尾随空格
1.2.3 做法
这个题目的关键是状态的构建与更新,解答中通过6个状态转移函数,来实现状态的更新。之后,利用bfs的方式,从初始状态出发,依次检查6个状态的变化,将其中的还没有出现过的状态加入队列之中,并在一个map<Status,bool> vis 中记录这个状态已经被访问,若访问过程中发现,A或B杯中的水已经够了C,则BFS会被及时终止。
除此之外,利用一个map<Status,Status> prepath ,记录状态之间的变化关系,将变化前后(S1,S2)两个状态建立对应关系prepath[S2]=S1,在输出变化序列的时候,使用非递归的方法,从最终状态开始,不断寻找变化之前的状态(即prepath的值),直到初始状态,就可以得到完整的变化路径。此外,因为输出的是变化方式,所以每一个状态还要用一个变量from记录这个变量是怎么来的(也就是通过哪个状态变化函数得来的)。
要注意的是,这个题如果使用递归的输出方法,会TLE。
1.2.4 代码
#include<cstdio>
#include<iostream>
#include<cstring>
#include<string>
#include<queue>
#include<stack>
#include<map>
using namespace std;
int A,B,C;
struct Status
{
int a,b;
string from;// 记录status是怎么来的
Status(){from = "0"; }
Status(int _a, int _b,string _from="0") {
a=_a; b=_b;
from=_from;
}
bool operator<(const Status &t) const
{
return a==t.a? b<t.b: a<t.a; //x是第一关键字,y是第二关键字
}
// 需要定义等于?
Status A2B()
{
Status c;
c.a = max(a+b-B,0);//A是否完全倒出来
c.b = min(a+b,B);
c.from="pour A B\n" ;
return c;
}
Status B2A()
{
Status c;
c.b = max(a+b-A,0);//B是否完全倒出来
c.a = min(a+b,A);
c.from="pour B A\n" ;
return c;
}
Status fillA()
{
return Status(A,b,"fill A\n" );
}
Status fillB()
{
return Status(a,B,"fill B\n");
}
Status emptyA()
{
return Status(0,b,"empty A\n");
}
Status emptyB()
{
return Status(a,0,"empty B\n");
}
};
queue<Status> q;
map<Status,bool> vis;
map<Status, Status> prepath;
stack<Status> s;
//void outpath(Status t)
//{
// if(t.a==0&&t.b==0) return;
// else
// {
// outpath(prepath[t]);
// //if(t.from!="0")
// cout<<t.from;
// }
//}
void outpath(Status t)
{
while(t.from!="0")
{
s.push(t);
t=prepath[t];
}
while(!s.empty())
{
cout<<s.top().from;
s.pop();
}
}
void check(Status c,Status src)
{
if(vis[c]) return;
vis[c]=true;
q.push(c);
prepath[c]=src;
}
void bfs()
{
q.push(Status(0,0,"0"));
vis[Status(0,0)]=true;
while(!q.empty())
{
Status t= q.front();
if(t.a==C||t.b==C)
{
outpath(t);
printf("success\n");
return;
}
q.pop();
check(t.A2B(),t);
check(t.B2A(),t);
check(t.fillB(),t);
check(t.fillA(),t);
check(t.emptyB(),t);
check(t.emptyA(),t);
}
}
int main()
{
//freopen("in.txt","r",stdin);
while(~scanf("%d%d%d",&A,&B,&C))
{
while (!q.empty())q.pop();
vis.clear();
prepath.clear();
bfs();
}
return 0;
}
其他的bfs类型:
2014-9-4 多起点BFS
- 将所有的起点一开始都放到队列里去,然后按正常BFS就可以了
- 建立了一个map映射存储客户坐标与cost的关系,但注意,因为客户坐标会重复,而map去重复,所以需要一点trick:
if(mp.find(point(tx,ty))!=mp.end()) //存在
{
mp[point(tx,ty)]+=c;
}
else
mp[point(tx,ty)]=c;
- 注意要用long long ,printf(“%lld",sum)不要落下!
2016-04-9 带有时间戳的bfs
- 为vis增加一个维度,记录何时来访问。
- 用一个map记录,障碍点存在对应的的时间
bfs:
timesp=p.last; //p点的时间戳
for(int i=0;i<4;i++) //4个方向,或者是其他的运动形式
{
int nx=p.x+dx[i];
int ny=p.y+dy[i];
point np(nx,ny,timesp+1);
if(!vis[nx][ny][timesp+1]&&nx>0&&nx<=n&&ny>0&&ny<=m)
{
if(mp.find(np)!=mp.end()) // 是障碍点
{
if(timesp+1<mp[np].a||timesp+1>mp[np].b)//在这个时间不是障碍
{
if(nx==n&&ny==m) return timesp+1;
vis[nx][ny][timesp+1]=1; //更新vis
q.push(np);
}
}
else // 不是障碍点
{
if(nx==n&&ny==m) return timesp+1;
vis[nx][ny][timesp+1]=1; //更新vis
q.push(np);
}
}
}