绪论
在我们遇到的一些问题当中,有些问题不能够确切的建立数学模型,或即便有数学模型但该模型的准确方法也不一定能运用现成算法。在要求枚举方案时,常常会遇到这一类问题。
解决这一类问题,我们一般采用搜索的方法解决,即从初始状态出发,运用题目所给出的条件和规则扩展所有可能情况,从中找出满足题意要求的解答。
•
状态:指当前所面临的具体问题
•
转移:指从一个状态到另一状态的一种决策
•
状态和转移可能是题目中已经给出,也可能是需要自己分析出的。一道题的状态与决策可能有多种。
•
产生式系统:把状态通过转移得到的一颗状态树,称作产生式系统
广搜
在广度优先搜索算法中,我们对每个节点进行拓展,深度越小的结点(距离初始最近的)越先得到扩展,下面通过一个具体实例来讨论广度优先算法的一般规律。
例题 八数码
#include<iostream>
#include<algorithm>
#include<queue>
#include<unordered_map>
#include<cstring>
using namespace std;
string start,c;
int dx[4]={1,-1,0,0},dy[4]{0,0,1,-1};//定义方向,dx为向左或者向右,dy向上或者向下
int bfs(string start)
{
string end="123804765";//定义最终的状态
queue<string>q;
unordered_map<string,int>d;//定义一个哈希表来记录每种状态距离起点的距离
q.push(start);d[start]=0;
while(q.size())//判断队列是否为空
{
auto t=q.front();//取出队头元素
q.pop();
int distance=d[t];//记录当前这个值对应的距离
if(t==end) return distance;
int k=t.find('0');//找到空格所在的位置
int x=k/3,y=k%3;//原本是一个二维矩阵,压缩成了一维,画个图可证
for(int i=0;i<4;i++)//开始搜索
{
int a=x+dx[i],b=y+dy[i];//更新xy的坐标
if(a>=0&&a<3&&b>=0&&b<3)//判断是否越界了
{
swap(t[k],t[a*3+b]);//开始寻找旁边的点并交换
if(!d.count(t))//如果他是第一次遍历,即哈希表中出现的次数为0
{
d[t]=distance+1;
q.push(t);
}//恢复原来的状态,为下一个过程做准备
swap(t[k],t[3*a+b]);
}
}
}
return -1;//上述操作没有返回距离说明达不到
}
int main()
{
cin>>start;
cout<<bfs(start);
return 0;
}
例题 离开中山路
#include<iostream>
#include<algorithm>
#include<cstring>
#include<queue>
using namespace std;
const int N=1005;
bool st[N][N];//判断是否走过
char b[N][N];int dis[N][N],n;//储存地图和走过的路径长度当然也可以用一个dis表示
int tx,ty;
struct node
{
int x,y;//定义坐标x,y
};
queue<node>q;//队列的类型为结构体
int dx[4]={-1,1,0,0},dy[4]={0,0,1,-1};
int x1,y1,x2,y2;
int bfs(int x1,int y1)
{
q.push((node){x1,y1});
st[x1][y1]=true;//表示初始这个点已经走过了
while(q.size())
{
int x=q.front().x;int y=q.front().y;//取出对头元素
q.pop();
if(x==x2&&y==y2)
return dis[x][y];
for(int i=0;i<4;i++)//枚举每一个方向
{
tx=x+dx[i],ty=y+dy[i];
if(tx<=0||tx>n||ty>n||ty<=0)continue;//越界了就跳过这一个过程
if(b[tx][ty]=='1'||st[tx][ty])continue;//走过或者不能走的情况下就跳过
dis[tx][ty]=dis[x][y]+1;
st[tx][ty]=true;//当前这个点是tx,ty需要标记已经走过了
q.push((node){tx,ty});
}
}
return -1;
}
int main()
{
cin>>n;
for(int i=1;i<=n;i++)
{
for(int j=1;j<=n;j++)
cin>>b[i][j];
}
cin>>x1>>y1>>x2>>y2;
cout<<bfs(x1,y1);
return 0;
}
例题 马的遍历
#include<iostream>
#include<algorithm>
#include<cstring>
#include<queue>
using namespace std;
const int N=405;
typedef pair<int,int>PII;
int dis[N][N];//用来记录一下到每个点的距离
bool st[N][N];//记录有没有走过
int dx[8]={-1,-2,-2,-1,1,2,2,1};
int dy[8]={-2,-1,1,2,2,1,-1,-2};//定义方向画图克制
int n,m,x0,y0;
void bfs()
{
queue<PII>q;//上述自定义的模版,当然也可以用结构体来实现
dis[x0][y0]=0;st[x0][y0]=true;
q.push({x0,y0});
while(q.size())
{
int x=q.front().first;int y=q.front().second;
q.pop();//取出队首元素并弹出
for(int i=0;i<8;i++)//依次遍历每一个方向
{
int u=x+dx[i],v=y+dy[i];
if(u<=0||u>n||v<=0||v>m||st[u][v])
continue;//越界或者已经走过就跳过
st[u][v]=true;//标记这个点为走过了
q.push({u,v});
dis[u][v]=dis[x][y]+1;
}
}
}
int main()
{
memset(dis,-1,sizeof dis);
cin>>n>>m>>x0>>y0;
bfs();
for(int i=1;i<=n;i++)
{
for(int j=1;j<=m;j++)
printf("%-5d",dis[i][j]);
puts(" ");
}
return 0;
}
例题 好奇怪的游戏
#include<iostream>
#include<algorithm>
#include<cstring>
#include<queue>
using namespace std;
typedef pair<int,int>PII;
int dis[21][21];//路的距离
int dx[12]={-2,-1,1,2,2,2,2,1,-1,-2,2,-2},
dy[12]={2,2,2,2,1,-1,-2,-2,-2,-2,1,-1};//十二个方向,画个图就理解了
bool st[21][21];
queue<PII>q;
int bfs(int x0,int y0)
{
//初始化操作,因为输入的不止一次
memset(dis,0,sizeof dis);
memset(st,false,sizeof st);
while(q.size()) q.pop();
st[x0][y0]=true;
q.push({x0,y0});
while(q.size())
{
int x=q.front().first,y=q.front().second;
q.pop();
if(x==1&&y==1)
return dis[x][y];
for(int i=0;i<12;i++)
{
int u=x+dx[i],v=y+dy[i];
if(u<=0||v<=0||u>20||v>20||st[u][v])
continue;
st[u][v]=true;q.push({u,v});
dis[u][v]=dis[x][y]+1;
}
}
return -1;
}
int main()
{
for(int i=0;i<2;i++)
{
int x,y;cin>>x>>y;
cout<<bfs(x,y)<<"\n";
}
return 0;
}
例题 路障
#include<iostream>
#include<algorithm>
#include<cstring>
#include<queue>
using namespace std;
const int N=2005;
bool st[N][N],map[N][N];
int zx[N],zy[N];//表示要摆上路障的位置
int n;
int dx[4]={-1,1,0,0},
dy[4]={0,0,1,-1};
struct node
{
int x,y,t;
};
queue<node>q;
bool bfs(int x,int y,int t)
{
st[x][y]=true;
q.push((node){x,y,t});
while(q.size())
{
int a=q.front().x,b=q.front().y,c=q.front().t;
q.pop();
if(a==n&&b==n)
return true;
map[zx[c]][zy[c]]=1;//添加障碍物
for(int i=0;i<4;i++)
{
int u=a+dx[i],v=b+dy[i];
if(u<=0||u>n||v<=0||v>n||map[u][v]||st[u][v])
continue;
st[u][v]=true;t=c+1;
q.push((node){u,v,t});
}
}
return false;
}
int main()
{
int t;cin>>t;
while(t--)
{
memset(st,false,sizeof st);
memset(map,false,sizeof map);
cin>>n;
for(int i=1;i<=2*n-2;i++)
scanf("%d%d",&zx[i],&zy[i]);
if(bfs(1,1,0))
puts("Yes");
else
puts("No");
}
}
例题 奇怪的电梯
#include<iostream>
#include<algorithm>
#include<cstring>
#include<queue>
using namespace std;
const int N=205;
int dis[N],c[N],d[N];//可以上升的距离以及按键的次数
bool st[N];
int n,a,b;
queue<int>q;
int bfs()
{
st[a]=true;d[a]=0;
q.push(a);
while(q.size())
{
int t=q.front();
q.pop();
if(t==b)
return d[t];
if(t+dis[t]<=n&&!st[t+dis[t]])//没有超过楼层限制和没有走过的话
{
st[t+dis[t]]=true;
q.push(t+dis[t]);
d[t+dis[t]]=d[t]+1;
}
if(t-dis[t]>=1&&!st[t-dis[t]])//没有超过楼层限制和没有走过的话
{
st[t-dis[t]]=true;
q.push(t-dis[t]);
d[t-dis[t]]=d[t]+1;
}
}
return -1;
}
int main()
{
cin>>n>>a>>b;
for(int i=1;i<=n;i++)//这里表楼层,所以下标一定得从1开始
cin>>dis[i];
cout<<bfs();
return 0;
}
例题 血色先锋队
#include<iostream>
#include<algorithm>
#include<cstring>
#include<queue>
using namespace std;
const int N=505;
typedef pair<int,int>PII;//用来储存x和y,也可以用结构体
queue<PII>q;
int dx[4]={-1,1,0,0},dy[4]={0,0,1,-1};
int n,m,a,b;
int times[N][N];
bool st[N][N];
void bfs()
{
while(q.size())
{
int x=q.front().first,y=q.front().second;
q.pop();
st[x][y]=true;//取出来记得标记已经走过
for(int i=0;i<4;i++)
{
int u=x+dx[i],v=y+dy[i];
if(u<1||u>n||v<1||v>m||st[u][v])
continue;
st[u][v]=true;
q.push({u,v});
times[u][v]=times[x][y]+1;
}
}
}
int main()
{
ios::sync_with_stdio(0),cin.tie(0),cout.tie(0);
cin>>n>>m>>a>>b;
for(int i=1;i<=a;i++)
{
int x,y;cin>>x>>y;
st[x][y]=true;//标记已经被感染了
q.push({x,y});
}
bfs();
for(int i=1;i<=b;i++)
{
int x,y;cin>>x>>y;
cout<<times[x][y]<<"\n";
}
return 0;
}
例题 Meteor Shower S
#include<iostream>
#include<algorithm>
#include<cstring>
#include<queue>
using namespace std;
const int N=605;//数组开大一点,题目说可以大于300
int m[N][N],v[N][N],ans[N][N];//陨石的数量,陨石砸落的地图,时间
bool sta[N][N];//标记有没有被砸过
int sx,sy,st;
typedef pair<int,int>PII;
queue<PII>q;
int dx[5]={0,1,-1,0,0},dy[5]={0,0,0,1,-1};//标记五个方向,陨石砸落下来一个有五个点被砸
int ch(int x)
{
if(x==-1)
return 99999;//如果还没有到达,可以标记到达的时间为很长
else
return x;
}
int bfs()
{
sta[0][0]=true;//初始化00点已经走过
q.push({0,0});
while(q.size())
{
int x=q.front().first,y=q.front().second;
q.pop();//取出队头元素
if(m[x][y]==-1)
return ans[x][y];
int t=ans[x][y]+1;//每次的时间+1
for(int i=1;i<5;i++)//判断能不能走的时候就不用看0,0了所以从1开始
{
int u=x+dx[i],v=y+dy[i];
if(u>=0&&v>=0&&t<ch(m[u][v])&&!sta[u][v])//流星还没有到和没有走过
{
q.push({u,v});sta[u][v]=true;
ans[u][v]=t;;//储存该点的时间
}
}
}
return -1;
}
int main()
{
int n;cin>>n;
memset(m,-1,sizeof m);//初始化陨石的数量都为-1
for(int i=1;i<=n;i++)
{
cin>>sx>>sy>>st;
for(int j=0;j<5;j++)//标记陨石被砸落的地方
{
if(sx>=0&&sy>=0&&(m[sx+dx[j]][sy+dy[j]]==-1||m[sx+dx[j]][sy+dy[j]]>st))//如果在这个范围内,并且还没有标记过,或者标记过了陨石到来的时间没有现在到来的早就把陨石标记为当前时间
m[sx+dx[j]][sy+dy[j]]=st;
}
}
cout<<bfs();
return 0;
}