推箱子问题现在是已经解决了,但是运行程序后,一个中等难度的地图就需要算2分钟左右才能算出来,那么怎样才能算的更快呢?
我们再来分析一下这个问题,我们的最终目的是把箱子推到目的地,并不关心工人在哪,也不关心工人如何移动,而我们上一次解决问题时把工人的每一次移动都记录了下来,但工人大部分时间都在空地上乱跑,进行了大量无用的计算。现在我们只考虑箱子的移动而不考虑工人的移动,再重新进行计算。问题来了:BoxWorker类中还需要记录工人的位置吗?答案是必须记录!因为在箱子把路封死的情况下,工人在箱子的一侧和另一侧出现会导致不同的结果。
现在开始优化:
1、Point类没什么变化。
2、public class BoxWorker {
private char cdire;//工人推动的方向
private Point pworker;//工人位置
private Point[] arrpbox;//箱子位置
private BoxWorker prebw;//上一步的位置}
吃一堑长一智,为了输出方便,这里增加了2个成员:prebw表示上一步的位置,这样很容易就能找到回去的路进行输出了,cdire表示工人推动的方向,如果没有这个变量,通过当前位置和上一步位置也能计算出来,不过这里增加了这个变量就更方便了。这个类的其他变化不大,多了几个set、get而已。
3、public class PushBox {
private int[][] arrtu;//图
private Point pworker;//工人
private Point[] arrpbox;//箱子位置
private Point[] arrpdes;//目标位置
private List<BoxWorker> lsbwchecked;//走过的图
private Stack<BoxWorker> stkbw;//深度优先算法栈
private Queue<BoxWorker> quebw;//宽度优先算法队列
private Stack<BoxWorker> stkresult;//储存结果
private int boundx=0,boundy=0;//arrtu边界
private int boundbox=0;//box边界}
由于不记录每一步工人的位置,那么在每一次判断箱子能否移动之前就先要把工人能走到的位置全部找到,才能判断箱子能否移动
我们把当前工人所有能达到的地方全部标记为1,这样就把这些位置都认为是工人,然后判断箱子能否移动。
private void setSpaceisWorker(int x,int y){
if(this.arrtu[x-1][y]==0){
this.arrtu[x-1][y]=1;
setSpaceisWorker(x-1,y);
}
if(this.arrtu[x+1][y]==0){
this.arrtu[x+1][y]=1;
setSpaceisWorker(x+1,y);
}
if(this.arrtu[x][y-1]==0){
this.arrtu[x][y-1]=1;
setSpaceisWorker(x,y-1);
}
if(this.arrtu[x][y+1]==0){
this.arrtu[x][y+1]=1;
setSpaceisWorker(x,y+1);
}
}
因为标记了许多地方为工人,所以在下次使用之前还需要把图清空。
private void clearArr(){
for(int i=1;i<boundx-1;i++){
for(int j=1;j<boundy-1;j++){
if(arrtu[i][j]!=5){
arrtu[i][j]=0;
}
}
}
}
和上一节一样,我们需要一个判断是否找到结果的方法。
private boolean isFind(Point[] pbox){
//箱子是否达到目的地,达到返回true
for(int i=0;i<this.boundbox;i++){
if(!this.atDes(pbox[i])){
return false;
}
}
return true;
}
同样,为了缩小搜索范围,需要一个判断路径是否有继续搜索价值的方法
private boolean isEnd(Point[] pbox,int x,int y){
//箱子不可移动并且不在目标点中则结束
this.arrtu[x][y]=1;
for(int i=0;i<boundbox;i++){
if(isDead(pbox[i]) && !this.atDes(pbox[i])){
this.arrtu[x][y]=4;
return true;
}
}
this.arrtu[x][y]=4;
return false;
}
同样,我们还需要一个移动方法和反向移动方法。
private void move(Point pbox1,Point pw,int x,int y){
//向前移动
pw.x=pbox1.x;
pw.y=pbox1.y;
pbox1.x=pbox1.x+x;
pbox1.y=pbox1.y+y;
}
private void moveBack(Point pbox1,Point pw,int x,int y){
//箱子向后移动,同时工人后移
pbox1.x=pbox1.x-x;
pbox1.y=pbox1.y-y;
pw.x=pbox1.x-x;
pw.y=pbox1.y-y;
}
最后就剩搜索了
public int searchLFS(){
int[][] dire={{-1,1,0,0}{0,0,-1,1}};//移动的四个方向
char[] cdire={'U','D','L','R'};//移动方向的记录
//宽度优先遍历
this.quebw.offer(new BoxWorker(this.pworker,this.arrpbox));
while(!this.quebw.isEmpty()){
BoxWorker bw=this.quebw.poll();
Point pw=new Point(bw.getPWorker().x,bw.getPWorker().y);
Point[] pbox=new Point[this.boundbox];
this.copyArr(bw.getPBox(),pbox);
this.clearArr();
this.setArrWorker(pbox, pw);
for(int i=0;i<this.boundbox;i++){
for(int j=0;j<4;j++
if(arrtu[pbox[i].x-dire[j][0]][pbox[i].y-dire[j][1]]==1 && arrtu[pbox[i].x+dire[j][0]][pbox[i].y+dire[j][1]]<2){
int x=pbox[i].x;
int y=pbox[i].y;
move(pbox[i],pw,dire[j][0],dire[j][1]);
Point newpw=new Point(pw.x,pw.y);
Point[] newpbox=new Point[this.boundbox];
this.copyArr(pbox, newpbox);
BoxWorker bwnew=new BoxWorker(newpw,newpbox);
if(!lsbwchecked.contains(bwnew)){
bwnew.setPreBw(bw);
bwnew.setDire(cdire[j]);
lsbwchecked.add(bwnew);
if(this.isFind(pbox)){
return 2;
}
if(!this.isEnd(pbox,x,y)){
this.quebw.offer(bwnew);
}
}
moveBack(pbox[i],pw,-1,0);
}}
}
}
return 0;
}
最后就是输出了,利用俄罗斯方块中的图形知识,稍加改动,就可以实现在图像中实现工人推箱子的解决方案了。
同样一个问题用优化了的算法解决,速度快了N倍,这才叫思路决定出路!思考的方向稍有