练习:经典搜索题

暴力搜索不管是在平面矩阵还是在树中都是骗分的好方法。
接下来带来两道经典搜索题的题解。

推箱子

POJ 1475 推箱子
推箱子大家都玩过吧。要求推箱子的步数最少,满足条件的情况下人走的步数最少,又满足以上条件的情况下spj。推箱子的动作用大写表示,无解则输出Impossible.
先枚举箱子的上下左右,如果有空位,搜索人能不能走到。

#include<iostream>
#include<cstdio>
#include<cstring>
#include<queue>
#include<algorithm>
#include<string>
#include<math.h>
using namespace std;
int bx,by,sx,sy,tx,ty,m,n,d[]={-1,1,0,0,0,0,-1,1},vis[25][25];//是否走到过
char caozuo[]={'n','s','w','e'},c[25][25];bool biaoji[25][25][4];//标记盒子的位置以及每个方向
struct box{int x,y,sx,sy;string answer;}nowbox,prebox;//sx,sy标记盒子在x,y时人的位置
struct ren{int x,y;string answer;}nowman,preman;
inline char daxie(char c){return (c-32);}//大写

bool bfs2(int s,int e,int a,int b,int p,int q)//判断人能不能走到盒子旁
{
queue<ren> r;
if (s<0||s>m||e<0||e>n||c[s][e]=='#') return false;//不存在的
nowman.x=p,nowman.y=q,nowman.answer="";
memset(vis,0,sizeof vis);
vis[a][b]=1,vis[p][q]=1;
for (r.push(nowman);!r.empty();)//平凡的bfs
  {
  nowman=r.front(),r.pop();
  if (nowman.x==s&&nowman.y==e) return true;
  for (int i=0;i<4;i++)
    {
    preman.x=nowman.x+d[i];
    preman.y=nowman.y+d[i+4];
    if (preman.x>0&&preman.x<=m&&preman.y>0&&preman.y<=n&&!vis[preman.x][preman.y]&&c[preman.x][preman.y]!='#')
      {
      preman.answer=nowman.answer+caozuo[i];//加上走的一步
      vis[preman.x][preman.y]=1;
      r.push(preman);
      }
    }
  }
return false;
}

bool bfs1()//一样的
{
queue<box> b;
nowbox.x=bx,nowbox.y=by,nowbox.answer="";
nowbox.sx=sx,nowbox.sy=sy;
for (b.push(nowbox);!b.empty();)
  {
  nowbox=b.front(),b.pop();
  if (nowbox.x==tx&&nowbox.y==ty) return true;
  for (int i=0;i<4;i++)
    {
    prebox.x=nowbox.x+d[i];
    prebox.y=nowbox.y+d[i+4];
    if (prebox.x>0&&prebox.x<=m&&prebox.y>0&&prebox.y<=n&&!biaoji[prebox.x][prebox.y][i]&&c[prebox.x][prebox.y]!='#')
    if (bfs2(prebox.x-2*d[i],prebox.y-2*d[i+4],nowbox.x,nowbox.y,nowbox.sx,nowbox.sy))
      {
      prebox.sx=nowbox.x;
      prebox.sy=nowbox.y;
      prebox.answer=nowbox.answer+nowman.answer+daxie(caozuo[i]);
      biaoji[prebox.x][prebox.y][i]=1;
      b.push(prebox);
      }
    }
  }
return false;
}

int main()
{
for (int t,cases=0;scanf("%d %d",&m,&n),m,n;puts(""))//做着做着不要忘记还有一个换行
  {
  memset(biaoji,false,sizeof biaoji);
  for (int i=1;i<=m;i++) for (int j=1;j<=n;j++) 
    {
    scanf(" %c",&c[i][j]);
    if (c[i][j]=='S') sx=i,sy=j;
    if (c[i][j]=='T') tx=i,ty=j;
    if (c[i][j]=='B') bx=i,by=j;
    }
  printf("Maze #%d\n",++cases);
  if (bfs1()) cout<<nowbox.answer.c_str()<<endl;
  else puts("Impossible.");
  }
}

八数码

POJ 1077 Eight
八数码也是经典问题。这题是spj,只要输出的路径是正确的都给对。
这里用了A*算法。

#include<cstdio>
#include<queue>
#include<cstring>
#include<algorithm>
#include<ctype.h>
#include<string>
using namespace std;
const int boss=4e5;
struct node{
int c[3][3],hash,f,g,h,x,y;//hash即为状态
bool operator <(const node n1) const{return h!=n1.h?h>n1.h:g>n1.g;}
bool check(){return x<0||y<0||x>=3||y>=3?0:1;}
};

int jiecheng[]={1,1,2,6,24,120,720,5040,40320},d[]={1,-1,0,0,0,0,-1,1},vis[boss+10],pre[boss+10],zhongdian=322560;//终点的hash是322560
int getstate(node n)//求一个图的状态
{
int opt[12],k=0;
for (int i=0;i<3;i++) for (int j=0;j<3;++j) opt[k++]=n.c[i][j];
int sum=0;
for (int i=0;i<9;i++) 
  {
  int cnt=0;
  for (int j=0;j<i;j++) if (opt[i]<opt[j]) cnt++;
  sum+=cnt*jiecheng[i];
  }
return sum;
}

int gujia(node n)//估计还有多少步到达
{
int sum=0;
for (int i=0;i<3;i++) for (int j=0;j<3;++j) if (n.c[i][j])
  {
  int x=(n.c[i][j]-1)/3,y=(n.c[i][j]-1)%3;
  sum+=abs(x-i)+abs(y-j);
  }
return sum;
}

bool judge(node n)//判断逆序对是否是偶数,不是直接剪枝
{
int opt[12],k=0;
for (int i=0;i<3;i++) for (int j=0;j<3;++j) opt[k++]=n.c[i][j];
int sum=0;
for (int i=0;i<9;i++) for (int j=i+1;j<9;++j) if (opt[i]&&opt[j]&&opt[i]>opt[j]) sum++;
return !(sum&1);
}

void a_star(node st)//A*算法,具体可以看看别人的博客
{
priority_queue<node> q;
for (q.push(st);!q.empty();)
  {
  node n=q.top();q.pop();
  for (int i=0;i<4;i++) 
    {
    node xia=n;
    xia.x+=d[i],xia.y+=d[i+4];
    if (!xia.check()) continue;
    swap(xia.c[xia.x][xia.y],xia.c[n.x][n.y]);
    xia.hash=getstate(xia);
    if (vis[xia.hash]==-1)
      {
      xia.h=gujia(xia);
      xia.g++;
      xia.f=xia.g+xia.h;
      pre[xia.hash]=n.hash;
      q.push(xia);
      vis[xia.hash]=i;
      }
    if (xia.hash==zhongdian) return;
    }
  }
}

void print()//输出路径
{
int nextit=zhongdian;char pr[]={'d','u','l','r'};
string answer;answer.clear();
for (;pre[nextit]!=-1;nextit=pre[nextit]) answer+=pr[vis[nextit]];
int len=answer.size();
for (int i=len-1;i>=0;i--) putchar(answer[i]);
puts("");
}

int main()
{
for (char str[100];gets(str)!=NULL;print())
  {
  node t;
  memset(vis,-1,sizeof vis);
  memset(pre,-1,sizeof pre);
  for (int k=0,i=0;str[k]!='\0';k++)
    if (isdigit(str[k])) t.c[i/3][i++%3]=str[k]-'0';
    else if (str[k]=='x') t.x=i/3,t.y=i%3,t.c[t.x][t.y]=0,i++;
  t.hash=getstate(t);
  vis[t.hash]=-2;
  t.g=0,t.h=gujia(t);
  t.f=t.g+t.h;
  if (!judge(t)) {puts("unsolvable");continue;}
  if (t.hash==zhongdian) {puts("");continue;}
  a_star(t);
  }
}

谢谢大家的观看。

  • 1
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值