搜索0: BFS,隐式图

这里列出的搜索方法,以点为基础进行的搜索,与搜索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

  1. 将所有的起点一开始都放到队列里去,然后按正常BFS就可以了
  2. 建立了一个map映射存储客户坐标与cost的关系,但注意,因为客户坐标会重复,而map去重复,所以需要一点trick:
if(mp.find(point(tx,ty))!=mp.end()) //存在 
  {
   mp[point(tx,ty)]+=c;
  }
  else
  mp[point(tx,ty)]=c;
  1. 注意要用long long ,printf(“%lld",sum)不要落下!

2016-04-9 带有时间戳的bfs

  1. 为vis增加一个维度,记录何时来访问。
  2. 用一个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);     
       }
     }
    
  } 
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值