繁体   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