前言
日前,有位同仁向我介紹了迷宮問題,激發了我強烈的興趣與研究慾望。經過深入思考,終於找到了破解迷宮問題的算法,並且用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);
}
}
}
}
}
}
}
}