二维迷宫求解器递归函数

时间:2015-01-29 05:25:02

标签: java recursion multidimensional-array backtracking maze

我正在尝试将二维矩阵实现为迷宫。有一个起点,一个终点(随机选择)。为了使它变得复杂,有障碍和代理人。如果老鼠遇到障碍物,它应该回溯并找到正确的路径。如果它遇到代理,它就会被破坏。 这是一个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()的递归函数来实现它。 以下是功能: 迷宫[] []是大鼠经过的随机化初始矩阵 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

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

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

4 个答案:

答案 0 :(得分:1)

好吧,让我们想象一下,我需要以解决问题的方式来解决同样的问题。 (我认为最好的解决方案是路径查找,正如评论中已经提到的那样。)

  1. 我将创建

    class Point { public int x; 公共的; }

  2. 并在其中存储坐标。

    1. 我会在List<Point> path
    2. 中存储老鼠访问过的所有积分

      在此解决方案中,您没有遇到上一点的问题(这是列表中的最后一点)

      对于算法终止 - 您使用带有randoms的算法。所以你无法确定你的老鼠会解决最简单的迷宫

      7 1 1

      1 1 1

      1 1 1

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

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

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

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

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

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

      祝你好运!

答案 1 :(得分:0)

如果你想随机移动,你需要知道你已经在他们身上的状态,所以你需要一棵树,否则你可以在老鼠处于多方位置时保持最左边的路径。 / p>

现在让我们想起递归+随机。它不会那么难。你可以有一个函数返回它已经存在的点列表,并获得正确的位置作为输入,有一些问题,白痴老鼠可以回来他已经来自的方式,所以让我们解决它添加前一点作为我们功能的另一个输入。

每件事都到位。现在我们知道这只白痴老鼠是否会遇到死路或经纪人。如何为这种情况制作2个例外并在递归函数中处理它们?

好吧,我不认为会有更多的问题。实际上我很想尝试myselft。这很有趣:D

与白痴老鼠好运

答案 2 :(得分:0)

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

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

  1. 一般来说,当没有解决方案时,随机游走不会告诉你。如果你只是随意地继续尝试路径,你将永远不会耗尽搜索树。
  2. 如果这是不可接受的(即,当没有溶解时它需要能够停止),那么你需要保留已经尝试过的路径的记录,并且只对那些尚未尝试的路径进行随机化。
  3. 除非只有一个解决方案,否则随机游走不一定能找到最佳解决方案。换句话说,如果您的迷宫中存在循环/多个路径,则无法保证您找到最快的路径。
  4. 我实际上无法看到问题中代理人和障碍者之间的区别。在这两种情况下,您都需要回溯并找到另一条路径。如果存在差异,那么您需要指出它。

    因此假设您的迷宫可能有零个或多个成功路径并且您没有寻找最佳路径(在这种情况下您确实应该使用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(项目,列表)),但你明白了。

答案 3 :(得分:0)

关于样式的快速点,稍后保存一些类型:迷宫[] [],解决方案[] []和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,所以剩下的唯一选择是 是的,所以你最终得到了“1”。
  • 如果你先试试Right,你仍然会以“1”结束。

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

  • 如果你先尝试Down,它会返回true,这会调用堆栈 - 成功!
  • 如果你先尝试左手,你就会以“7”结束,所以递归到上一步。

这就是所有可能发生的事情。所以使用*来返回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. 已尝试过所有可能的路径。
  3. 对于一只完全无知的老鼠,没有办法发现第二个!

    考虑以下迷宫:

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

    完全无知的老鼠将永远地在左上方和右上方徘徊,所以程序永远不会终止!

    解决这个问题的方法是,老鼠必须至少有点智能,并记住它已经存在的位置(这也会使可解决的迷宫在大多数情况下运行得更快,并且沿着整个路径回溯而不是仅针对单个节点) 。但是,这个答案已经变得有点太长了,所以如果你对此感兴趣,我会在这里向你推荐我的另一个迷宫解决答案:Java Recursive Maze Solver problems

    哦,在Random上只有两个最后一点:

    1. 您不需要每次都创建一个新的随机 - 只需创建一个 全局并且每次都调用nextInt。
    2. nextInt(n)在0(包括)和n(不包括)之间返回,所以你 需要nextInt(4)而不是nextInt(3)。
    3. 希望这一切都有帮助!

相关问题