前言
日前,有位同仁向我介绍了迷宫问题,激发了我强烈的兴趣与研究欲望。经过深入思考,终于找到了破解迷宫问题的算法,并且用java语言编写程序得以实现,现将算法和代码公布,欢迎广大程序爱好者前来阅读,如有改善意见也希望能相互交流。
本文的算法和程序均为本人原创,如要转载请标明出处,谢谢!
迷宫问题简介
给一个有指定行数和列数的迷宫,迷宫内的方格由通道和墙壁组成,某人在迷宫内的某个方格内,可以向相邻的方格进行上下左右的移动,如果相邻的方格是通道的话则可以进入该方格,反之不行。再给此人一个终点,要求计算出其从起点到达终点的最短路线和步数。
如下图为一个迷宫,0代表通道,1代表墙壁。
核心思想
1、家族不能绝嗣
要解决迷宫问题,最简单的办法是设计程序得到从起点开始所有走到墙壁或者迷宫边缘的路线,然后判断哪些路线达到了规定的终点,再将这些正确路线存储起来,最后比较路线的长度,得到最短路线即可。但是当人处于迷宫之时,他所能选择的道路可能不止一条,可能既能向下走一步也能向左走一步,这个时候我们就要让其繁衍出对应数量的子嗣来代替他进行继续向不同的路线前进,直到走到终点、墙壁或者迷宫边缘,此时则停止繁衍子嗣。对于程序而言,繁衍子嗣可以使用递归方法来实现,即在方法内部调用方法本身,实现任务的传承。同时使用for循环内嵌套IF语句来检查处在某位置上的人具有多少种可行的走法,最多4种(上下左右均可),最少0种。
2、探险不能回头
上面说到程序要让处于迷宫上的人繁衍子嗣向所有可行的方向进一步,那么如果该子嗣向其父行走方向的反方向走一步,子嗣的子嗣也同样都采用与其父相反的方向前进一步……这样来回往返的话程序就会进入死循环,直接引发堆栈溢出错误。要解决这个问题就要想办法让“一脉相传”的人不能踏入他们的祖辈走过的位置,如果进入则停止繁衍,即该条路线结束。要做到这点,就必须采用集合记录下每一代人在迷宫上的位置,每次繁衍子嗣走进下一步前都要将位置存入集合,存储位置的集合作为方法的参数无限制传递,并且判断下一步将要走进的位置是否在集合中出现过,避免走回头路导致程序死循环。
3、遗产不可继承
上面说到,程序让处于迷宫的人繁衍子嗣进入不能的路线,那么这些下一代也将继承父亲的所有遗产才能完成程序的任务,当然这些遗产指的是参数。但是如果子嗣不止一个,那么其中一个孩子调用父亲的遗产时就会让遗产发生不应该有的变化。用程序来描述就是会改变参数的值,但是这些参数有其他IF分支语句(其他子嗣)需要使用,如果改变会影响程序的运行结果。因此,在执行每个IF语句内的方法体时,应该将传递的参数进行复制后再使用,避免改变传参的参数值。
算法
1、通过二维数组构建迷宫
例如本人采用int型的二维数组maze[][]来构建迷宫,m边上迷宫的行数,n表示迷宫的列数。maze[m][n]的位置,maze[m][n]的值表示迷宫在此处是墙壁还是通道,我采用的是1表示通道,0表示墙壁。
2、编写寻求路径的方法,需要传入的参数为:起点位置,终点位置、包含步骤的集合list1、包含位置的集合list2,当然,集合的初始值均为空。
3、用for循环嵌套if语句判断其在上下左右四个方向是否可以前进一步,即前进一步是否会遇到墙壁或者迷宫边缘,并将此时的位置信息存入list2中。
3、如果可以前进一步,则新建包含步骤的集合list1_1、包含位置的集合list2_2,然后将list1、list2中的数据全部复制进新集合中,并在list2_2中存入步骤信息。
4、判断此时处于的位置是不是终点,是的话将包含步骤信息的集合存入总路线集合中,不是的话调用方法本身继续判断能否往下走。
程序代码
本程序刚写好没有深入优化,可能有点冗长,包含了迷宫的自定义创建、自定义起终点位置、寻找所有可以到达终点的路径、计算最短路径等一切需要用到的功能。其中最核心的是getRoutes(参数)方法,用来从迷宫中找出所有可以达到终点的所有路线并存入集合,此方法写在程序的最下方,读者可以有针对性的阅读。
package javaBase;
import java.util.ArrayList;
import java.util.List;
import java.util.Scanner;
public class MiGong {
static List allRoutes=new ArrayList();
public static void main(String[] args) {
//开始构建迷宫,使用int二维数组
System.out.println("请按照提示开始构建迷宫:");
Scanner input=new Scanner(System.in);
System.out.print("请输入迷宫的行数:");
int m=input.nextInt();
System.out.print("请输入迷宫的列数:");
int n=input.nextInt();
int maze[][]=new int[m][n];
for(int i=0;i<m;i++){
for(int j=0;j<n;j++){
maze[i][j]=1;
}
}
System.out.println("请按照提示输入迷宫第每行中属于墙壁的位置,"
+ "中间用英文“,”隔开,如您可以输入“2,4,6”,表示这一行中第2列、第4列和第6列的位置属于墙壁。"
+ "对于未设定为墙壁的墙壁的位置,程序会将其设定为通道");
for(int i=0;i<m;i++){
String[]wall=new String[m];
int[]walls=new int[m];
//设定迷宫中的墙壁,通过将墙壁所在位置的数值设为0来表示
System.out.print("请输入迷宫第"+(i+1)+"行中属于墙壁的位置:");
String x=input.next();
wall=x.split(",");
for(int a=0;a<wall.length;a++){
walls[a]=Integer.valueOf(wall[a]);
}
for(int j=0;j<n;j++){
for(int b=0;b<walls.length;b++){
if((j+1)==walls[b]){
maze[i][j]=0;
}
}
}
}
System.out.println("构建迷宫已经完成,您构建的迷宫为:(其中“#”表示墙壁,“o”表示通道)");
for(int y1=0;y1<m;y1++){
for(int y2=0;y2<m;y2++){
if(maze[y1][y2]==1){
System.out.print("o ");
}else{
System.out.print("# ");
}
}
System.out.println();
}
//设定起点位置
System.out.println("请输入起点位置在哪一排?哪一列?如您可以输入“1,1”表示从第1排、第1列的位置开始");
String[]wall=new String[m];
int[]walls=new int[m];
String x=input.next();
wall=x.split(",");
for(int a=0;a<wall.length;a++){
walls[a]=Integer.valueOf(wall[a]);
}
int a=walls[0];
int b=walls[1];
//由于表示迷宫的数组从0开始计数,即第0表示第1行或者第1列,因此人工输入的行数和列数需要减去1
a--;
b--;
//获取终点位置
System.out.println("请输入终点位置在哪一排?哪一列?如您可以输入“10,10”表示目的地位置在第10排、第10列");
String[]wall1=new String[m];
int[]walls1=new int[m];
String x1=input.next();
wall1=x1.split(",");
for(int a1=0;a1<wall1.length;a1++){
walls1[a1]=Integer.valueOf(wall1[a1]);
}
int c=walls1[0];
int d=walls1[1];
c--;
d--;
//创建本类的对象,以便调用本类的方法
MiGong migong=new MiGong ();
System.out.println();
System.out.println("开始搜寻路径..");
//创建用来装载行走步骤信息的集合
List route=new ArrayList();
//创建用来装载位置信息的集合
List posit=new ArrayList();
//用数组表示位置
int[]ponit=new int[2];
ponit[0]=0;
ponit[1]=0;
//将初始位置存入集合,避免迷宫内的人返回原始位置
posit.add(ponit);
//调用.getRoutes方法,得到可以到达终点的路线的集合,本方法是本程序的核心,在程序最下面,请读者重点阅读
migong.getRoutes(a, b, c, d, m, n, maze, route,posit);
System.out.println("搜寻路径完成,共找到"+allRoutes.size()+"条路径可达到终点!");
//如果集合为空,则表示没有路线可以到达终点
if(allRoutes.size()==0){
System.out.println("当前无路径可以到达终点");
}
//否则展示每条路线上的行走步骤信息
else{
for(int g=0;g<allRoutes.size();g++){
List routeResult=(List) allRoutes.get(g);
System.out.print("第"+(g+1)+"种路径为:");
for(int h=0;h<routeResult.size();h++){
if(h==(routeResult.size()-1)){
int[] step=(int[]) routeResult.get(h);
if(step[0]==1&&step[1]==1){
System.out.print("向下走一步");
}
if(step[0]==0&&step[1]==1){
System.out.print("向右走一步");
}
if(step[0]==1&&step[1]==-1){
System.out.print("向上走一步");
}
if(step[0]==0&&step[1]==-1){
System.out.print("向左走一步");
}
}else{
int[] step=(int[]) routeResult.get(h);
if(step[0]==1&&step[1]==1){
System.out.print("向下走一步,");
}
if(step[0]==0&&step[1]==1){
System.out.print("向右走一步,");
}
if(step[0]==1&&step[1]==-1){
System.out.print("向上走一步,");
}
if(step[0]==0&&step[1]==-1){
System.out.print("向左走一步,");
}
}
}
System.out.println();
}
System.out.println();
//通过遍历总路线集合,找出集合中包含各个路线集合的长度小哲,即为最短路线
System.out.println("正在计算最短路径步数");
List result1=(List) allRoutes.get(0);
int count=result1.size();
for(int g=1;g<allRoutes.size();g++){
List routeResult=(List) allRoutes.get(g);
int count1=routeResult.size();
if(count1<count){
count=count1;
}
}
System.out.println("计算完成,最短路径的步数是:"+count);
}
}
//用二维数组表示走的方向
//int step[]={1,1};向下走一步
// int step[]={0,1};向右走一步
//int step[]={1,-1};向上走一步
// int step[]={0,-1};向左走一步
public void getRoutes(int a,int b,int c,int d,int m,int n,int maze[][],List route,List posit){
for(int i=1;i<5;i++){
if(i==1){
//向下走一步,行数加1,列数不变
int x=a+1;
int y=b;
//创建新的位置集合,将参数传入的集合内容全部复制给他使用
List posit1=new ArrayList();
posit1.addAll(posit);
//判断走过一步后是否会遇到迷宫边缘
if(x<m&&x>=0&&y<n&&y>=0){
//判断走过一步后是否会遇到墙壁
if(maze[x][y]!=0){
//都没有遇到的话则建立数组储存最新的位置
int[]ponit=new int[2];
ponit[0]=x;
ponit[1]=y;
//判断最新的位置在之前的位置集合中是否出现过,如果出现过则让judge自增,表示走了回头路
int judge=0;
for(int e=0;e<posit1.size();e++){
int[]getPoint=(int[]) posit1.get(e);
if(getPoint[0]==x&&getPoint[1]==y){
judge++;
}
}
//如果judge没有自增表示没有走回头路,此时走这一步是正确的,将最新的位置存入位置集合中,并创建step数组表示步骤 // 信息
if(judge==0){
posit1.add(ponit);
int step[]={1,1};//向下走一步
//构建新的步骤集合,并将参数传递的步骤集合的内容复制给他,并将最新的步骤存储进去
List route1=new ArrayList();
route1.addAll(route);
route1.add(step);
//如果到达终点,则将线路存入总线路集合中
if(x==c&&y==d){
allRoutes.add(route1);
break;
//未到达终点,则调用此方法继续往下一步走
}else{
this.getRoutes(x, y, c, d, m, n, maze, route1,posit1);
}
}
}
}
}
if(i==2){
int x=a;
int y=b+1;
List posit1=new ArrayList();
posit1.addAll(posit);
if(x<m&&x>=0&&y<n&&y>=0){
if(maze[x][y]!=0){
int[]ponit=new int[2];
ponit[0]=x;
ponit[1]=y;
int judge=0;
for(int e=0;e<posit1.size();e++){
int[]getPoint=(int[]) posit1.get(e);
if(getPoint[0]==x&&getPoint[1]==y){
judge++;
}
}
if(judge==0){
posit1.add(ponit);
int step[]={0,1};//向右走一步
List route1=new ArrayList();
route1.addAll(route);
route1.add(step);
if(x==c&&y==d){
allRoutes.add(route1);
break;
}else{
this.getRoutes(x, y, c, d, m, n, maze, route1,posit1);
}
}
}
}
}
if(i==3){
int x=a-1;
int y=b;
List posit1=new ArrayList();
posit1.addAll(posit);
if(x<m&&x>=0&&y<n&&y>=0){
if(maze[x][y]!=0){
int[]ponit=new int[2];
ponit[0]=x;
ponit[1]=y;
int judge=0;
for(int e=0;e<posit1.size();e++){
int[]getPoint=(int[]) posit1.get(e);
if(getPoint[0]==x&&getPoint[1]==y){
judge++;
}
}
if(judge==0){
posit1.add(ponit);
int step[]={1,-1};//向上走一步
List route1=new ArrayList();
route1.addAll(route);
route1.add(step);
if(x==c&&y==d){
this.allRoutes.add(route1);
break;
}else{
this.getRoutes(x, y, c, d, m, n, maze, route1,posit1);
}
}
}
}
}
if(i==4){
int x=a;
int y=b-1;
List posit1=new ArrayList();
posit1.addAll(posit);
if(x<m&&x>=0&&y<n&&y>=0){
if(maze[x][y]!=0){
int[]ponit=new int[2];
ponit[0]=x;
ponit[1]=y;
int judge=0;
for(int e=0;e<posit1.size();e++){
int[]getPoint=(int[]) posit1.get(e);
if(getPoint[0]==x&&getPoint[1]==y){
judge++;
}
}
if(judge==0){
posit1.add(ponit);
int step[]={0,-1};//向左走一步
List route1=new ArrayList();
route1.addAll(route);
route1.add(step);
if(x==c&&y==d){
allRoutes.add(route1);
break;
}else{
this.getRoutes(x, y, c, d, m, n, maze, route1,posit1);
}
}
}
}
}
}
}
}