簡體   English   中英

二維迷宮求解器遞歸函數

[英]2 dimensional maze solver recursive function

我正在嘗試將二維矩陣實現為迷宮。 有一個起點,一個終點(隨機選擇)。 而且,要使其變得簡單一點,就存在障礙和因素。 如果老鼠碰到障礙物,應該回溯並找到正確的路徑。 如果遇到代理,它將被銷毀。 這是一個示例4x4矩陣

1 7 1 1
2 1 1 0
1 0 1 0
1 1 1 9

關鍵:0是障礙,2是代理,7是起點,9是目標/終點。 1表示可以安全地移動到那里。

該矩陣的正確解決方案是:

0 1 1 0
0 0 1 0
0 0 1 0
0 0 1 1

但是老鼠並不聰明(至少對於該程序而言),所以我正在實現帶有隨機動作的蠻力算法。

我嘗試使用稱為mazeUtil()的遞歸函數來實現此目的。 下面是函數:maze [] []是大鼠經過的隨機初始矩陣。
solution [] []是將跟蹤移動的解決方案矩陣。
(x,y)是網格中的當前位置n是矩陣的大小(它是一個正方形矩陣)。

public static void mazeUtil(int maze[][], int solution[][], int x, int y, int n)
   {
      if(x == goal[0] && y == goal[1])
      {
         solution[x][y] = 1;
         return;     
      }


      int check = moveCheck(maze, x, y, n);  

//moveCheck() return 0 for Obstacle, 1 for safe path, 2 for agent, 7 for starting point (also safe path), 9 for goal (safe path)

      if (check == 2){
         solution[x][y] = 1;
         out.println("Oops! Ran into an agent!");
         return;         
      }

      else if(check == 0)
      {
         //What should I put here?
      }

      else if(check == 1 || check == 7 || check == 9)
      {
         solution[x][y] = 1;
         Random newRandom = new Random();
         int temp = newRandom.nextInt(3);

         if(temp == 0){  //move up if possible? x--
            if(x > 0)
               mazeUtil(maze, solution, x-1, y, n);
            else 
               mazeUtil(maze, solution, x+1, y, n);
         }
         else if (temp == 1){
            if (x < n-1)
               mazeUtil(maze, solution, x+1, y, n);
            else
               mazeUtil(maze, solution, x-1, y, n);
         }            
         else if(temp == 2){
            if (y < n-1)
               mazeUtil(maze, solution, x, y+1, n);
            else
               mazeUtil(maze, solution, x,y-1, n);

         }
         else if (temp == 3){
            if (y > 0)
               mazeUtil(maze, solution, x, y-1, n);
            else
               mazeUtil(maze, solution, x, y+1, n);
          }        
      }
   }

我必須隨機移動,這就是為什么我使用隨機函數。 如果我的函數遇到代理(2),則效果很好。 我還阻止了老鼠越界。 通過安全路徑(1)毫無問題。 但是問題是當它遇到障礙時。 我正在考慮回溯。 如何將其添加到函數中? 喜歡保存最后一步,然后相反嗎? 像這樣的迷宮很可能沒有解決方案

7 0 0 9
2 0 1 1
0 1 0 0
1 2 0 1

如果執行正確,它將遇到障礙,如果發生故障,它將遇到代理。 它不能對角移動。 這使我想到第二個問題,在這種情況下我將如何終止遞歸函數。 此時,它唯一終止的時間是達到目標或擊中特工時。

任何幫助,將不勝感激。 提前致謝

好吧,讓我們想象一下,我需要以解決問題的方式來解決相同的問題。 (我認為最好的解決方案是找到路徑,正如評論中已經提到的那樣)。

  1. 我會創造

    類Point {public int x; 公共領域 }

並在其中存儲坐標。

  1. 我將把老鼠拜訪過的所有點都存儲在List<Point> path

在此解決方案中,您對上一點沒有任何問題(這是列表中的最后一點)

至於算法終止-您可以將算法與隨機數結合使用。 因此,您無法確定您的老鼠會解決最簡單的迷宮,例如

7 1 1

1 1 1

1 1 1

老鼠有可能永遠從(0,0)移至(1,0),從(1,0)移至(0,0)。

因此,讓我們再次想象一下,我需要改進您的算法,而不是使用好的算法。

我將在path列表中存儲老鼠從障礙物返回或訪問該點的次數。 如果此number > 4我將命令我的老鼠回到原始點(第7點)。 並重新開始旅程。

如果大鼠需要返回,例如10次,則算法終止。

同樣,您的算法很有趣,看到老鼠如何運動應該很有趣,但是它不能解決問題。 它不適用於大迷宮。

嘗試實施路徑查找。 如果您有問題-提出問題。

祝好運!

如果您想隨意移動,則需要知道您已經處於其中的狀態,因此您將需要一棵樹,否則當老鼠處於多向位置時,您可以保持最左邊的路徑。

現在讓我們考慮遞歸+隨機。 不可能那么難。 您可以使用一個函數來返回已存在的點的列表,並獲取正確的位置作為輸入,這有一些問題,白痴老鼠可以找回他已經來過的地方,所以讓我們通過添加來解決它前一點作為我們功能的另一個輸入。

每件事都到位。 現在我們想知道白痴老鼠是走在一條死路還是一個特工。 在這種情況下如何創建2個異常並在遞歸函數中處理它們呢?

好吧,我認為途中不會有更多問題。 實際上,我很想親自嘗試一下。 那會很有趣:D

白痴老鼠祝你好運

在提出解決方案之前,我想對您的算法設計進行一些分析。

您提到您要使用隨機游走算法。 毫無疑問,這是尋找路徑的一種完全可以接受(盡管不一定有效)的方式。 但是,您需要意識到它會帶來一些影響。

  1. 通常,沒有解決方案時,隨機游走不會告訴您。 如果您只是嘗試隨機嘗試路徑,那么您將永遠不會耗盡搜索樹。
  2. 如果這是不可接受的(即,當沒有旋時,它必須能夠停止),那么您需要保留已嘗試路徑的記錄,並僅將尚未嘗試的路徑隨機化。
  3. 除非只有一個解決方案,否則隨機游走不一定會找到最佳解決方案。 換句話說,如果迷宮中存在回路/多條路徑,則無法保證您找到最快的路徑。

我實際上看不到代理和障礙之間的區別。 在這兩種情況下,您都需要回溯並找到其他路徑。 如果存在差異,則需要指出。

因此,假設您的迷宮有零條或更多條成功的路徑,並且您沒有在尋找最佳路徑(在這種情況下,您確實應該使用A *或類似的路徑),則解決方案的結構應類似於:

public List<Position> findPath(Set<Position> closedSet, Position from, Position to) {
    if (from.equals(to)) 
         return List.of(to);
    while (from.hasNeighboursNotIn(closedSet)) {
        Position pos = from.getRandomNeighbourNotIn(closedSet);
        closedSet.add(pos);
        List<Position> path = findPath(closedSet, pos, to);
        if (!path.isEmpty())
            return List.of(pos, path);
    }
    closedSet.add(from);
    return Collection.EMPTY_LIST;
}

這使用了大量的偽代碼(例如,沒有List.of(item,list)),但是您知道了。

關於樣式的一個簡要說明,以后可以保存一些鍵入內容:maze [] [],solution [] []和n都是有效全局的,並且在遞歸調用之間不會更改(迷宮和解決方案只是作為對相同數組的引用傳遞的,並且n永遠不變)。 這純粹是樣式,但是您可以這樣寫:

  private static int[][] maze;
  private static int[][] solution;
  private static int n;

  public static void mazeUtil(int x, int y) {
    ...
  }

接下來是您的解決方案:第一點是我看不出您何時達到目標。 您的mazeUtil函數不返回任何內容。 對於這種遞歸,一種通用方法是讓您的求解器函數返回一個布爾值:如果已達到目標,則返回true;否則返回false。 一旦得到一個真實的結果,就可以將其一直傳遞回調用堆棧。 每次您得到錯誤的答案,您就回溯到下一個解決方案。

所以我建議:

  public static boolean mazeUtil(int x, int y) {
    // return true if goal found, false otherwise
    ...
  }

接下來,我不確定代理與障礙之間的實際區別是什么:碰到任何一個都會導致您回溯。 所以我認為那段代碼將是:

  if (check == 2) {
    out.println("Oops! Ran into an agent!");
    return false;         
  }

  if (check == 0)
    out.println("Oops! Ran into an obstacle!");
    return false;         
  }

然后是遞歸位:此處的一點是,您永遠不要將失敗的嘗試的解決方案重置為0(實際上,由於最終算法永遠不會回退超過一個步驟,這實際上並不那么重要,但是很好地說明一般方法)。 鑒於我們到目前為止所擁有的,現在應該是這樣的:

  if (check == 9) {
    out.println("Found the goal!");
    return true;         
  }

  if (check == 1 || check == 7) {
    // add current position to solution
    solution[x][y] = 1;

    // generate random move within bounds
    int nextX = ...
    int nextY = ...

    if (mazeUtil(nextX, nextY)) {
      // we've found the solution, so just return up the call stack
      return true;
    }

    // this attempt failed, so reset the solution array before returning
    solution[x][y] = 0;

    return false;
  }

  // shouldn't ever get here...
  throw new IllegalStateException("moveCheck returned unexpected value: " + check);

是的,到目前為止還不錯,但是仍然存在問題。 一旦其中一個mazeUtil調用返回一個值(true或false),它將一直沿調用堆棧返回該值。 因此,如果您碰巧在特工或障礙物之前找到出口,那很好,但這是不太可能的。 因此,您無需嘗試遞歸時的任何舉動,而是需要嘗試所有可能的舉動。

與支持類Point一起,包含簡單的x和y對:

  if (check == 1 || check == 7) {
    // add current position to solution
    solution[x][y] = 1;

    // generate an array of all up/down/left/right points that are within bounds
    // - for a random path need to randomise the order of the points
    Point[] points = ... 

    for (Point next : points) {
      if (mazeUtil(next.x, next.y)) {
        // we've found the solution, so just return up the call stack
        return true;
      }
    }

    // this attempt failed, so reset the solution array before returning
    solution[x][y] = 0;

    return false;
  }

我認為這與一只完全無知的老鼠差不多! 要查看其工作原理,請考慮以下迷宮:

7 1
0 9

從“ 7”開始,可能的移動是向下和向右。

  • 如果先嘗試按下Down,則返回false,因此剩下的唯一選項是Right,因此最終以“ 1”結尾。
  • 如果先嘗試“向右”,則最終仍會顯示“ 1”。

從“ 1”開始,可能的移動是向下和向左:

  • 如果您先嘗試Down,它會返回true,從而使調用堆棧冒泡-成功!
  • 如果首先嘗試“左”,則最終出現在“ 7”上,因此請返回上一步。

這就是所有可能發生的事情。 因此,將*用作return-false-backtrack和! 為了取得成功,可能有以下任何一種情況:

R-D!
R-L-D*-R-D!
R-L-R-L-R-L-R-L (keep going for a long, long time....) R-L-R-D!

因此,對於可解決的迷宮和真正的隨機生成器,盡管可能需要很長時間,但最終將解決迷宮問題。 但是要注意的一點是,它實際上並沒有回溯那么多:僅從2或0節點開始只有一步。

但是,仍然存在無法解決的迷宮問題,對於完全無知的老鼠,我認為這是不可能的。 這樣做的原因是,對於像這樣的蠻力遞歸,只有兩種可能的終止條件:

  1. 目標已經找到。
  2. 已嘗試所有可能的路徑。

對於一只完全無知的老鼠,無法檢測到第二只老鼠!

考慮以下迷宮:

7 1 1 1
0 0 0 0
0 0 0 0
1 1 1 9

完全無知的老鼠將永遠在第一行中左右徘徊,因此程序永遠不會終止!

解決方案是,老鼠必須至少具有一定的智能,並記住它的所在位置(這也將使可解決的迷宮在大多數情況下運行更快,並沿着整個路徑回溯而不是僅針對單個節點)。 但是,這個答案已經太久了,因此,如果您對此感興趣,我將在這里為您介紹其他迷宮解決方法: Java遞歸迷宮求解器問題

哦,關於Random的最后兩點:

  1. 您不需要每次都創建一個新的Random,只需創建一個全局的Random並每次都調用nextInt即可。
  2. nextInt(n)返回0(包括)和n(不包括)之間的值,因此您需要nextInt(4)而不是nextInt(3)。

希望對您有幫助!

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM