简体   繁体   English

二维迷宫求解器递归函数

[英]2 dimensional maze solver recursive function

I am trying to implement a 2 dimensional matrix as a maze. 我正在尝试将二维矩阵实现为迷宫。 There is a starting point, an ending point (randomly chosen). 有一个起点,一个终点(随机选择)。 And to make it little complicated, there are obstacles and agents. 而且,要使其变得简单一点,就存在障碍和因素。 If the rat runs into an obstacle, it should backtrack and find the correct path. 如果老鼠碰到障碍物,应该回溯并找到正确的路径。 If it runs into an agent, it gets destroyed. 如果遇到代理,它将被销毁。 Here's a sample 4x4 matrix 这是一个示例4x4矩阵

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

Key: 0 is an obstacle, 2 is an agent, 7 is the starting point, 9 is the goal/ending point. 关键:0是障碍,2是代理,7是起点,9是目标/终点。 1 means that is is safe to move there. 1表示可以安全地移动到那里。

The correct solution for this matrix would be: 该矩阵的正确解决方案是:

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

But the rat is not intelligent (at least for this program) , so I am implementing a brute force algorithm, with random moves. 但是老鼠并不聪明(至少对于该程序而言),所以我正在实现带有随机动作的蛮力算法。

I have tried to implement this using a recursive function called mazeUtil(). 我尝试使用称为mazeUtil()的递归函数来实现此目的。 Below is the function: maze[][] is the randomized initial matrix that the rat moves through. 下面是函数:maze [] []是大鼠经过的随机初始矩阵。
solution[][] is the solution matrix that will keep track of the moves. solution [] []是将跟踪移动的解决方案矩阵。
(x, y) is the current position in the grid n is the size of the matrix (it is a square matrix). (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);
          }        
      }
   }

I have to randomize the moves and that's why i have used random function. 我必须随机移动,这就是为什么我使用随机函数。 My function works quite well if it runs into an agent (2). 如果我的函数遇到代理(2),则效果很好。 I have also prevented the rat from going out of boundary. 我还阻止了老鼠越界。 And it doesn't have any problem going through the safe path (1). 通过安全路径(1)毫无问题。 But the problem is when it hits an obstacle. 但是问题是当它遇到障碍时。 I'm thinking about backtracking. 我正在考虑回溯。 How do I add that into my function? 如何将其添加到函数中? Like save the last step, and do the reverse? 喜欢保存最后一步,然后相反吗? And it is quite possible that there is no solution in the maze like this one 像这样的迷宫很可能没有解决方案

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

It would hit an obstacle if it goes right, and hit an agent if it goes down. 如果执行正确,它将遇到障碍,如果发生故障,它将遇到代理。 It cannot move diagonally. 它不能对角移动。 That brings me to my second question, how would I terminate my recursive function in that case. 这使我想到第二个问题,在这种情况下我将如何终止递归函数。 At this point the only time it terminates is when it reaches the goal or hits an agent. 此时,它唯一终止的时间是达到目标或击中特工时。

Any help would be appreciated. 任何帮助,将不胜感激。 Thanks in advance 提前致谢

Well, let's imagine I need to solve the same problem by the same way you are solving it. 好吧,让我们想象一下,我需要以解决问题的方式来解决相同的问题。 (I think the best solution for it is Path finding, as already mentioned in comments). (我认为最好的解决方案是找到路径,正如评论中已经提到的那样)。

  1. I will create 我会创造

    class Point{ public int x; 类Point {public int x; public int y; 公共领域 } }

and store coordinates in it. 并在其中存储坐标。

  1. I will store all points the rat visited in List<Point> path 我将把老鼠拜访过的所有点都存储在List<Point> path

In this solution you do not have problems with previous point (it is the last point in list) 在此解决方案中,您对上一点没有任何问题(这是列表中的最后一点)

As for algorithm termination -- you use algorithm with randoms. 至于算法终止-您可以将算法与随机数结合使用。 So you can't be sure that your rat will solve the simplest maze like 因此,您无法确定您的老鼠会解决最简单的迷宫,例如

7 1 1 7 1 1

1 1 1 1 1 1

1 1 1 1 1 1

it is possible that rat will move from (0,0) to (1,0) and from (1,0) to (0,0) forever. 老鼠有可能永远从(0,0)移至(1,0),从(1,0)移至(0,0)。

So, let's again imagine that I need to improve your algorithm instead of using good one. 因此,让我们再次想象一下,我需要改进您的算法,而不是使用好的算法。

I will store number of times the rat returned back from obstacle or visited the point in path list. 我将在path列表中存储老鼠从障碍物返回或访问该点的次数。 If this number > 4 I will command to my rat return back to the original point (point 7). 如果此number > 4我将命令我的老鼠回到原始点(第7点)。 And start the journey again. 并重新开始旅程。

If the rat need to return back, for example 10 times, the algorithm terminates. 如果大鼠需要返回,例如10次,则算法终止。

Again, your algorithm is funny, and it should be interesting to see how the rat moves but it does not solve the problem. 同样,您的算法很有趣,看到老鼠如何运动应该很有趣,但是它不能解决问题。 It will not work on big mazes. 它不适用于大迷宫。

Try to implement path finding. 尝试实施路径查找。 If you will have problems -- ask questions. 如果您有问题-提出问题。

Good luck! 祝好运!

if you want to move in random, u need to know the states you've been already in them, so u will need a tree, otherwise u can keep the most left path when the rat is in multi way place. 如果您想随意移动,则需要知道您已经处于其中的状态,因此您将需要一棵树,否则当老鼠处于多向位置时,您可以保持最左边的路径。

now lets think of recursive + random. 现在让我们考虑递归+随机。 it can not be that hard. 不可能那么难。 you can have a function that returns the list of points it has been in them, and get correct position as input, there is a bit of problem and the idiot rat can got back the way he already came from, so lets solve it with adding previous point as another input for our function. 您可以使用一个函数来返回已存在的点的列表,并获取正确的位置作为输入,这有一些问题,白痴老鼠可以找回他已经来过的地方,所以让我们通过添加来解决它前一点作为我们功能的另一个输入。

every thing in place. 每件事都到位。 now we wana know if the idiot rat runs into a dead path or an agent. 现在我们想知道白痴老鼠是走在一条死路还是一个特工。 how about making 2 exceptions for this situations and handling them in recursive function?? 在这种情况下如何创建2个异常并在递归函数中处理它们呢?

well, i don't think there will be any more problems on way. 好吧,我认为途中不会有更多问题。 actually i'm temped to try it myselft. 实际上,我很想亲自尝试一下。 that would be fun :D 那会很有趣:D

good luck with the idiot rat 白痴老鼠祝你好运

I'd like to do some analysis of your algorithm design before proposing a solution. 在提出解决方案之前,我想对您的算法设计进行一些分析。

You mention that you want to use a random walk algorithm. 您提到您要使用随机游走算法。 No problem with that it's a perfectly acceptable (though not necessarily efficient) way to look for a path. 毫无疑问,这是寻找路径的一种完全可以接受(尽管不一定有效)的方式。 However you need to be aware that it has some implications. 但是,您需要意识到它会带来一些影响。

  1. In general random walk will not tell you when there is no solution. 通常,没有解决方案时,随机游走不会告诉您。 If you just keep trying paths at random you will never exhaust the search tree. 如果您只是尝试随机尝试路径,那么您将永远不会耗尽搜索树。
  2. If this is unacceptable (ie it needs to be able to halt when there is no soltuion) then you need to keep a record of paths already attempted and randomise only those not yet attempted. 如果这是不可接受的(即,当没有旋时,它必须能够停止),那么您需要保留已尝试路径的记录,并仅将尚未尝试的路径随机化。
  3. Random walk won't necessarily find the optimal solution unless there is only one solution. 除非只有一个解决方案,否则随机游走不一定会找到最佳解决方案。 In other words if there are loops / multiple paths in your maze then there's no guarantee you are finding the fastest. 换句话说,如果迷宫中存在回路/多条路径,则无法保证您找到最快的路径。

I can't actually see the difference between agents and obstacles in your problem. 我实际上看不到代理和障碍之间的区别。 In both cases you need to backtrack and find another path. 在这两种情况下,您都需要回溯并找到其他路径。 If there is a difference then you'll need to point it out. 如果存在差异,则需要指出。

So assuming your maze could have zero or more successful paths and you are not looking for the optimal path (in which case you really should use A* or similar), the structure of a solution should look something like: 因此,假设您的迷宫有零条或更多条成功的路径,并且您没有在寻找最佳路径(在这种情况下,您确实应该使用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;
}

This uses lots of pseudo-code (eg there is no List.of(item, list)) but you get the idea. 这使用了大量的伪代码(例如,没有List.of(item,list)),但是您知道了。

A quick point on style, to save some typing later: maze[][], solution[][] and n are all effectively global, and do not change between recursive calls (maze and solution are just passed as references to the same arrays, and n never changes). 关于样式的一个简要说明,以后可以保存一些键入内容:maze [] [],solution [] []和n都是有效全局的,并且在递归调用之间不会更改(迷宫和解决方案只是作为对相同数组的引用传递的,并且n永远不变)。 This is purely style, but you can write this as: 这纯粹是样式,但是您可以这样写:

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

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

So on to your solution: the first point is I don't see how you know when you've reached the goal; 接下来是您的解决方案:第一点是我看不出您何时达到目标。 your mazeUtil function does not return anything. 您的mazeUtil函数不返回任何内容。 For this kind of recursion, a general approach is for your solver function to return a boolean: true if the goal has been reached and false if not. 对于这种递归,一种通用方法是让您的求解器函数返回一个布尔值:如果已达到目标,则返回true;否则返回false。 Once you get a true, you just pass it back all the way up the call stack. 一旦得到一个真实的结果,就可以将其一直传递回调用堆栈。 Each time you get a false, you backtrack to the next solution. 每次您得到错误的答案,您就回溯到下一个解决方案。

So I'd suggest: 所以我建议:

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

Next, I'm not sure what the practical difference between an agent and an obstacle is: running in to either causes you to backtrack. 接下来,我不确定代理与障碍之间的实际区别是什么:碰到任何一个都会导致您回溯。 So I'd think that bit of code would be: 所以我认为那段代码将是:

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

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

Then the recursive bit: one point here is you do not ever reset the solution to 0 for failed attempts (actually, as the final algorithm will never backtrack more than a single step this is not actually that important, but it's good to illustrate the general approach). 然后是递归位:此处的一点是,您永远不要将失败的尝试的解决方案重置为0(实际上,由于最终算法永远不会回退超过一个步骤,这实际上并不那么重要,但是很好地说明一般方法)。 Given what we have so far, this should now be something like: 鉴于我们到目前为止所拥有的,现在应该是这样的:

  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);

Right, so far so good, but there's still a problem. 是的,到目前为止还不错,但是仍然存在问题。 As soon as one of the mazeUtil calls returns a value (either true or false) it will return that all the way up the calls stack. 一旦其中一个mazeUtil调用返回一个值(true或false),它将一直沿调用堆栈返回该值。 So if you happen to find the exit before an agent or an obstacle, all good, but that's quite unlikely. 因此,如果您碰巧在特工或障碍物之前找到出口,那很好,但这是不太可能的。 So instead of trying a single move when recursing, you need to try all possible moves. 因此,您无需尝试递归时的任何举动,而是需要尝试所有可能的举动。

WIth a supporting class Point, containing a simple x and y pair: 与支持类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;
  }

And I think that's about as far as you can go with a totally ignorant rat! 我认为这与一只完全无知的老鼠差不多! To see how this works, consider the following maze: 要查看其工作原理,请考虑以下迷宫:

7 1
0 9

Starting at "7", possible moves are Down and Right. 从“ 7”开始,可能的移动是向下和向右。

  • If you try Down first, it returns false, so the only option left is Right, so you end up on the "1". 如果先尝试按下Down,则返回false,因此剩下的唯一选项是Right,因此最终以“ 1”结尾。
  • If you try Right first, you still end up on the "1". 如果先尝试“向右”,则最终仍会显示“ 1”。

From the "1", possible moves are Down and Left: 从“ 1”开始,可能的移动是向下和向左:

  • If you try Down first, it returns true, which bubbles up the call stack - success! 如果您先尝试Down,它会返回true,从而使调用堆栈冒泡-成功!
  • If you try Left first, you end up on the "7", so recurse to the previous step. 如果首先尝试“左”,则最终出现在“ 7”上,因此请返回上一步。

And that's all that can ever happen. 这就是所有可能发生的事情。 So using * for a return-false-backtrack, and ! 因此,将*用作return-false-backtrack和! for a success, any of the following are possible: 为了取得成功,可能有以下任何一种情况:

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!

So for a solvable maze, and a truly random generator, this will eventually solve the maze, although it could take a very long time. 因此,对于可解决的迷宫和真正的随机生成器,尽管可能需要很长时间,但最终将解决迷宫问题。 Something to note with this though, is it does not really backtrack that much: only ever a single step from a 2 or 0 node. 但是要注意的一点是,它实际上并没有回溯那么多:仅从2或0节点开始只有一步。

However, there's still the problem of the unsolveable maze, and I don't think that is possible given a totally ignorant rat. 但是,仍然存在无法解决的迷宫问题,对于完全无知的老鼠,我认为这是不可能的。 The reason for this is that for a brute force recursion like this, there are only two possible termination conditions: 这样做的原因是,对于像这样的蛮力递归,只有两种可能的终止条件:

  1. The goal has been found. 目标已经找到。
  2. All possible paths have been tried. 已尝试所有可能的路径。

And with a totally ignorant rat, there is no way to detect the second! 对于一只完全无知的老鼠,无法检测到第二只老鼠!

Consider the following maze: 考虑以下迷宫:

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

The totally ignorant rat will just wander left and right across the top row forever, and so the program will never terminate! 完全无知的老鼠将永远在第一行中左右徘徊,因此程序永远不会终止!

The solution to this is that the rat must be at least a bit intelligent, and remember where it has been (which will also make the solveable maze run quicker in most cases and backtrack along entire paths instead of only for single nodes). 解决方案是,老鼠必须至少具有一定的智能,并记住它的所在位置(这也将使可解决的迷宫在大多数情况下运行更快,并沿着整个路径回溯而不是仅针对单个节点)。 However, this answer is getting a bit too long already, so if you're interested in that I'll refer you to my other maze-solving answer here: Java Recursive Maze Solver problems 但是,这个答案已经太久了,因此,如果您对此感兴趣,我将在这里为您介绍其他迷宫解决方法: Java递归迷宫求解器问题

Oh, just two final points on Random: 哦,关于Random的最后两点:

  1. You don't need to create a new Random each time - just create a global one and call nextInt each time. 您不需要每次都创建一个新的Random,只需创建一个全局的Random并每次都调用nextInt即可。
  2. nextInt(n) returns between 0 (inclusive) and n (exclusive), so you need nextInt(4) not nextInt(3). nextInt(n)返回0(包括)和n(不包括)之间的值,因此您需要nextInt(4)而不是nextInt(3)。

Hope this all helps! 希望对您有帮助!

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM