【问题标题】:2 dimensional maze solver recursive function二维迷宫求解器递归函数
【发布时间】:2015-01-29 05:25:02
【问题描述】:

我正在尝试将二维矩阵实现为迷宫。有一个起点,一个终点(随机选择)。为了使它有点复杂,有障碍和代理。如果老鼠遇到障碍物,它应该回溯并找到正确的路径。如果它遇到代理,它就会被销毁。 这是一个示例 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

如果它向右,它会撞到障碍物,如果它向下,它会撞到一个代理。它不能沿对角线移动。 这让我想到了第二个问题,在这种情况下我将如何终止我的递归函数。 此时它终止的唯一时间是它到达目标或击中代理。

任何帮助将不胜感激。提前致谢

【问题讨论】:

  • 你看过寻路算法吗?这类事情有众所周知的、经过充分研究的解决方案:A*、Dijkstra 等:en.wikipedia.org/wiki/Dijkstra%27s_algorithm
  • 我有,没有帮助。另外,很高兴能对这个问题进行一些解释,并从这里的许多人那里获得 cmets。我真的从 stackoverflow 中学到了很多
  • 没有帮助你怎么办?您应该就您尝试过的算法所遇到的问题提出一个具体问题,而不是自己动手。
  • 如果你的目标是找到一条穿过迷宫的路线,那么我看不出代理和障碍物之间的区别:两者都代表老鼠不能移动到的位置。我在这里错过了什么吗?

标签: java recursion multidimensional-array backtracking maze


【解决方案1】:

好吧,假设我需要以与您解决相同的方式来解决相同的问题。 (我认为最好的解决方案是寻路,正如 cmets 中已经提到的)。

  1. 我会创造

    类点{ 公共整数 x; 公共整数 y; }

并在其中存储坐标。

  1. 我会将老鼠访问过的所有点存储在List&lt;Point&gt; path

在这个解决方案中,前一点没有问题(它是列表中的最后一点)

至于算法终止——您使用带有随机数的算法。所以你不能确定你的老鼠会解决最简单的迷宫,比如

7 1 1

1 1 1

1 1 1

老鼠有可能永远从 (0,0) 移动到 (1,0) 并从 (1,0) 移动到 (0,0)。

所以,让我们再次想象一下,我需要改进你的算法,而不是使用好的算法。

我将存储老鼠从障碍物返回或访问path 列表中的点的次数。 如果这个number &gt; 4 我会命令我的老鼠回到原点(点7)。重新开始旅程。

如果老鼠需要返回,例如 10 次,则算法终止。

同样,您的算法很有趣,看看老鼠如何移动应该很有趣,但它并不能解决问题。它不适用于大迷宫。

尝试实现路径查找。如果您有问题,请提出问题。

祝你好运!

【讨论】:

    【解决方案2】:

    关于样式的快速说明,以便稍后节省一些输入:maze[][]、solution[][] 和 n 都是有效的全局,并且在递归调用之间不会更改(maze 和 solution 只是作为引用传递给相同的数组,并且 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 调用返回一个值(真或假),它将一直返回调用堆栈。因此,如果您碰巧在代理人或障碍物之前找到出口,一切都很好,但这不太可能。因此,在递归时不要尝试单个动作,而是需要尝试所有可能的动作。

    带有一个支持类 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,所以剩下的唯一选择是 对,所以你最终得到了“1”。
    • 如果您先尝试 Right,您仍然会选择“1”。

    从“1”开始,可能的移动是向下和向左:

    • 如果您先尝试 Down,它会返回 true,这会在调用堆栈中冒泡 - 成功!
    • 如果您先尝试 Left,您最终会选择“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 Recursive Maze Solver problems

    哦,关于 Random 的最后两点:

    1. 您不需要每次都创建一个新的 Random - 只需创建一个 全局一个,每次调用 nextInt。
    2. nextInt(n) 返回 0(包括)和 n(不包括)之间,所以你 需要 nextInt(4) 而不是 nextInt(3)。

    希望这对您有所帮助!

    【讨论】:

      【解决方案3】:

      如果你想随机移动,你需要知道你已经在其中的状态,所以你需要一棵树,否则当老鼠在多路时你可以保持最左边的路径。

      现在让我们考虑递归 + 随机。不可能那么难。你可以有一个函数来返回它在其中的点列表,并获得正确的位置作为输入,有一点问题,白痴老鼠可以回到他已经来的方式,所以让我们通过添加来解决它上一点作为我们函数的另一个输入。

      一切就绪。现在我们想知道白痴老鼠是跑到死路还是代理了。为这种情况创建 2 个异常并在递归函数中处理它们怎么样?

      好吧,我认为不会再有任何问题了。实际上我很想自己尝试一下。那会很有趣:D

      祝你白痴老鼠好运

      【讨论】:

        【解决方案4】:

        在提出解决方案之前,我想对您的算法设计进行一些分析。

        您提到您想使用随机游走算法。没问题,这是一种完全可以接受(尽管不一定有效)的寻找路径的方法。但是,您需要注意它有一些含义。

        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)),但你明白了。

        【讨论】:

          猜你喜欢
          • 2019-03-18
          • 1970-01-01
          • 2014-04-09
          • 2023-01-09
          • 1970-01-01
          • 2019-03-02
          • 2017-09-12
          • 1970-01-01
          • 1970-01-01
          相关资源
          最近更新 更多