java学习日志~2 推箱子算法

为什么要写一个推箱子呢?源于以前喜欢玩这个游戏,可是到了一定关数的时候就太难了,可能几天也想不出来,那时候就想如果有个软件能够在给出问题后,自动生成步骤来解决这个问题多好啊,但最终都没有动手去解决。这次恰好刚看完java书,趁热打铁。

1、看到推箱子游戏很自然的就联想到用2维数组来构造数据结构,分别用数值0、1、4、5来代表空地、工人、箱子、墙,直观、方便,然而在深入思考后发现,2维数组所占空间太大,而且在解决游戏时,我们只关心工人如何走,箱子如何动,对墙和空地毫不关心,二维数组的大部分内容是无用的,有了这部分思考,我们来构建我们需要的数据。

2、创建Point类,在俄罗斯方块中我们用的Point类是java.awt.Point,感觉不是特别好用,这次自己写一个

public class Point implements Comparable<Point>{
 public int x;
 public int y;
 public Point(){};
 public Point(int tmpx,int tmpy){
  this.x=tmpx;
  this.y=tmpy;
 }
 public Point(Point p){
  this.x=p.x;
  this.y=p.y;
 }

为了方便,这里的x,y均设为public,同时复写equals,toString,hashCode,实现compareTo方法

2、创建BoxWorker类,此类保存工人移动后工人和箱子的位置,如果工人、箱子位置完全相同,则认为此图已遍历过。

public class BoxWorker {
 private boolean flag; //记录工人移动到此位置时箱子是否移动,主要为moveback时确定箱子是否移动
 private Point pworker;//工人位置
 private List<Point> lspbox;//箱子位置
 public BoxWorker(Point p,List<Point> lsp){
  this.pworker=new Point();
  this.lspbox=new ArrayList<Point>();
  this.flag=false;
  this.add(p, lsp);
 }
 public BoxWorker(Point p,List<Point> lsp,boolean f){
  this(p,lsp);
  this.flag=f;
 }
 public Point getPWorker(){
  return this.pworker;
 }
 public List<Point> getLsPBox(){
  return this.lspbox;
 }
 public boolean getBoxMove(){
  return this.flag;
 }
 public void add(Point p,List<Point> lsp){
  
  this.pworker.x=p.x;
  this.pworker.y=p.y;
  
  this.lspbox.clear();
  Iterator<Point> iterlsp=lsp.iterator();
  while(iterlsp.hasNext()){
   Point plsp=iterlsp.next();
   this.lspbox.add(new Point(plsp.x,plsp.y));
  }
 }

然后复写了equals,toString,hashCode

3、到了这里需要的数据格式已经创建完了,我们来创建一个解决问题的类PushBox。

public class PushBox {
 int[][] arrtu;//图
 Point pworker;//工人
 List<Point> lspbox;//箱子位置
 List<Point> lspdes;//目标位置
 List<BoxWorker> lsbwchecked;//已遍历过的图
 Stack<BoxWorker> stkbw;//深度优先算法栈
 Stack<Point> stkp;//深度优先走过的路
 Queue<BoxWorker> quebw;//宽度优先算法队列}

这个类我只捡主要的方法说,首先要有一个判断是否找到答案的方法,此方法只被内部搜索函数调用,所以定义为私有

private boolean isFind(List<Point> ls){
  //箱子是否达到目的地,达到返回true
  if(ls.isEmpty()){
   return false;
  }
  if(this.lspdes.isEmpty()){
   return false;
  }
  Iterator<Point> iterpbox=ls.iterator();
  while(iterpbox.hasNext()){
   if(!this.lspdes.contains(iterpbox.next())){
    return false;
   }
  }
  return true;
 }

为了搜索更加快捷,我们需要一个函数来判断是否继续沿此方向继续搜索

private boolean isEnd(List<Point> ls){
  this.setArrBox(ls,4);
  //箱子不可移动并且不在目标点中则结束
  Iterator<Point> iterp=ls.iterator();
  while(iterp.hasNext()){
   Point p=iterp.next();
   if(isDead(p) && !this.lspdes.contains(p)){
    this.setArrBox(ls,0);
    return true;
   }
  }
  this.setArrBox(ls,0);
  return false;
 }

还需要一个判断工人能否移动的函数

private boolean canMove(Point pw,List<Point> ls,int x,int y){}这里没有贴代码,总共3个判断1、是否越界;2、移动方向连续两个物体;3、此图是否已经遍历过

前期的各种判断都写完了,那就开始推箱子吧~_~

private boolean move(Point pw,List<Point> ls,int x,int y){//第一个参数是工人坐标,第2个参数是箱子坐标,第3、4个参数是移动方向

为了记录工人移动时,箱子是否移动,特意加了返回值

这里为了避免大量重复代码,用一个move代替了4个方向上的moveup等,吸取了俄罗斯方块上的经验
  //向前移动
  boolean flag=false;//箱子是否移动
  //修改lspbox
   Iterator<Point> iterbox=ls.iterator();
   while(iterbox.hasNext()){
    Point p=iterbox.next();
    if(p.x==pw.x+x && p.y==pw.y+y){
     p.x=p.x+x;
     p.y=p.y+y;
     flag=true;
    }
   }
  //修改pworker,stkresult,lsbwchecked

这个stkresult好像最后没啥用,所以在成员变量那里没有写
  stkresult.push((new Point(pw.x+x,pw.y+y)));
  pw.x=pw.x+x;
  pw.y=pw.y+y;
 // System.out.println(pw+"#"+ls);
  lsbwchecked.add(new BoxWorker(pw,ls,flag));
  //level++;
  return flag;
 }

有向前移动就得有向后退,要不然就真得撞南墙了

private BoxWorker moveBack(Point pw,List<Point> ls,int x,int y,boolean flag){
  //向后移动
  //修改lspbox
  if(flag){ 
   Iterator<Point> iterbox=ls.iterator();
   while(iterbox.hasNext()){
    Point p=iterbox.next();
    if(p.x==pw.x+x && p.y==pw.y+y){
     p.x=p.x-x;
     p.y=p.y-y;
    }
   }
  }
  //修改pworker,stkresult
  //stkresult.pop();
  //System.out.print(p+";");
  pw.x=pw.x-x;
  pw.y=pw.y-y;
  //lsbwchecked.add(new BoxWorker(this.pworker,this.lspbox));
  //level--;
  return new BoxWorker(pw,ls);
 }

这下就真的该开始搜索了,关于搜索的函数,我写了三个,一个找最短路径的,两个是一条路走到黑-----深度搜索,用递归的那个深度搜索运行了几次没成功,后来没有继续研究了,这里放一个找最短路径的函数

public int searchLFS(){
  boolean flag;
  this.quebw.offer(new BoxWorker(this.pworker,this.lspbox));
  this.stkp.push(new Point(this.pworker.x,this.pworker.y));
  while(!this.quebw.isEmpty()){
   BoxWorker bw=this.quebw.poll();
   Point pw=bw.getPWorker();
   List<Point> lsp=bw.getLsPBox();
   if(canMove(pw,lsp,-1,0)){
    flag=move(pw,lsp,-1,0);//把能移动的图加入到队列
    stkp.push(new Point(-1,0));
    if(this.isFind(lsp)){
     System.out.println(bw);
     return 2;
    }
    if(!this.isEnd(lsp)){
     this.quebw.offer(new BoxWorker(pw,lsp,flag));
    }
    moveBack(pw,lsp,-1,0,flag);
   }
   if(canMove(pw,lsp,1,0)){
    flag=move(pw,lsp,1,0);
    stkp.push(new Point(1,0));
    if(this.isFind(lsp)){
     System.out.println(bw);
     return 2;
    }
    if(!this.isEnd(lsp)){
     this.quebw.offer(new BoxWorker(pw,lsp,flag));
    }
    moveBack(pw,lsp,1,0,flag);
   }
   if(canMove(pw,lsp,0,-1)){
    flag=move(pw,lsp,0,-1);
    stkp.push(new Point(0,-1));
    if(this.isFind(lsp)){
     System.out.println(bw);
     return 2;
    }
    if(!this.isEnd(lsp)){
     this.quebw.offer(new BoxWorker(pw,lsp,flag));
    }
     moveBack(pw,lsp,0,-1,flag);
   }
   if(canMove(pw,lsp,0,1)){
    flag=move(pw,lsp,0,1);
    stkp.push(new Point(0,1));
    if(this.isFind(lsp)){
     System.out.println(bw);
     return 2;
    }
    if(!this.isEnd(lsp)){
     this.quebw.offer(new BoxWorker(pw,lsp,flag));
    }
    moveBack(pw,lsp,0,1,flag);
   }
  }
  return 0;
 }

做到这里推箱子解决问题的方案是找到了,但别急,还没有输出呢,有方案但看不到,和没有不是一样么,这时候才发现自己的BoxWorker类应该有一个把方案串起来的数据,没经验学费总要交的,可是现在又不想该基础的东西了,能有其他办法吗?答案是肯定的,因为我们有一个存储工人走过的图的集合lsbwchecked,可以用它来进行输出,虽然麻烦点,但总是有个输出了

public void printlsbw(){}定义了一个函数把结果输出。

到了这里,问题已经解决了,但思考以上程序,可优化的地方还很多,比如:每次判断是否结束当前搜索时isEnd函数中是对所有箱子进行判断,而实际上只需要对当前移动了的箱子进行判断即可;再比如可以把4个方向的移动建一个数组,那样在search中用一个循环就能解决问题,减少代码行数,提高代码可读性;这里就不一一列举了,与人共勉吧。


  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
´问题描述: 码头仓库是划分为n×m个格子的矩形阵列。有公共边的格子是相邻格子。当前仓库中 有的格子是空闲的;有的格子则已经堆放了沉重的货物。由于堆放的货物很重,单凭仓库管 理员的力量是无法移动的。仓库管理员有一项任务,要将一个小箱子推到指定的格子上去。 管理员可以在仓库中移动,但不能跨过已经堆放了货物的格子。管理员站在与箱子相对的空 闲格子上时,可以做一次推动,把箱子推到另一相邻的空闲格子。推箱时只能向管理员的对 面方向推。由于要推动的箱子很重,仓库管理员想尽量减少推箱子的次数。 ´编程任务: 对于给定的仓库布局,以及仓库管理员在仓库中的位置和箱子的开始位置和目标位置, 设计一个解推箱子问题的分支限界法, 计算出仓库管理员将箱子从开始位置推到目标位置所 需的最少推动次数。 ´数据输入: 由文件input.txt提供输入数据。输入文件第 1 行有 2个正整数 n和 m(1<=n,m<=100) , 表示仓库是n×m个格子的矩形阵列。接下来有 n行,每行有 m个字符,表示格子的状态。 S 表示格子上放了不可移动的沉重货物; w 表示格子空闲; M 表示仓库管理员的初始位置; P 表示箱子的初始位置; K 表示箱子的目标位置。 ´结果输出: 将计算出的最少推动次数输出到文件 output.txt。如果仓库管理员无法将箱子从开始位 置推到目标位置则输出“No solution!” 。 输入文件示例 输出文件示例 input.txt output.txt

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值