迷宫算法问题

时间:2009-08-10 11:34:26

标签: maze

我遇到了一个旨在解决迷宫的算法问题。

我在这里使用了算法。http://www.cs.bu.edu/teaching/alg/maze/

FIND-PATH(x,y)

  1. if(x,y outside maze)返回false
  2. if(x,y is goal)return true
  3. if(x,y not open)return false
  4. 标记x,y作为解决方案路径的一部分
  5. if(FIND-PATH(x,y之前)== true)返回true
  6. if(FIND-PATH(东,x,y)== true)return true
  7. if(FIND-PATH(x,y以南)== true)返回true
  8. if(FIND-PATH(西部x,y)== true)返回true
  9. 取消标记x,y作为解决方案路径的一部分
    1. return false
  10. 这是一个递归解决方案,我修改了它,即使在找到退出后它也会继续,以便它也可以找到其他解决方案。它似乎有用,只是它似乎找到了我知道可能的解决方案总数的一半。

    1. if(x,y is goal)return true更改为return false。
    2. 任何人都知道这种算法可能会出现什么问题,导致总可能解决方案的数量减少一半?我还有一个问题是找到死路径的总数,对此有什么建议吗?

5 个答案:

答案 0 :(得分:2)

似乎缺少的是检查X& Y是否已经被标记为解决方案的一部分,如果是,我们就会中止。 (这应该在第3.5点的某处)

如果不是某个可能有某个环路的迷宫会无法运行并炸毁堆栈

顺便说一句,从我读到的算法算法是基于只有1个解决方案的迷宫

[R

答案 1 :(得分:1)

不是试图通过迷宫找到一条路,而是需要通过迷宫找到(并因此映射)多种方式。

为了做到这一点,你需要标记你去过的地方(在特定路径上)。如果你达到了你已经去过的地步,你需要将这条路线标记为死胡同。

递归函数仍然是要走的路,但要确保通过递归函数传递(placesIhaveBeen)结构。

当你到达N,S,E,W全部被阻止的点时,递归循环需要中断。 (你以前去过那里,你不能朝那个方向前进,它在迷宫之外) 当你到达目标时,递归循环也需要中断。

如果达到目标 - 将全局变量增加1。 如果你无处可去 - 将你的死胡同增加一个。

我不能为此编写pcode(它需要太长时间),但我相信这个秘密在一个函数中,如果N,S,E和W全部被阻止,则返回true。

除了我的初步答案

关于为什么我把我去过的地方视为“被封锁”,为什么我把它们视为死胡同......

      ########
      #      #  
      # #### #
####### #  # #
        #  # #
####### #  # #
      # #### #
      #      #  
      ########

我会将上面的迷宫部分归类为死胡同,但我不知道如何在没有将我去过的地方视为封锁区域的情况下识别它。

我意识到这会导致以下内容也显示出死胡同,但我无法看到它的方法。

      #######
      #     #  
      # ### #
####### #G# #
        # # #
####### #   #
      # ### #
      #     #  
      #######

答案 2 :(得分:0)

对于死角的数量,你需要这样的东西:

3.5 if(x的北部,y未打开)和(x的y,y未打开)和(x的西部,y未打开)和(x的东部,y未打开)deadends ++

答案 3 :(得分:0)

我尝试在java中进行简单的算法实现。我的结论是你描述的算法有效,即使找到多条路径也是如此。或者,您可能想到了比我更聪明的测试用例。 (请发布您的迷宫,以便我可以尝试我的算法)

我对死胡同计数器的实现可能不是最有效的,但它完成了工作。对于访问的每个当前OPEN节点,它检查4个周围节点:

  • 如果至少有一个邻居是OPEN,那么当前节点不是死胡同
  • 如果多个邻居是VISITED,则当前节点不是死胡同
  • 如果只有一个节点是VISITED(我们在上一步中得到的那个节点)并且没有其他邻居是OPEN,那么当前节点就是一个死胡同

这是我写的java代码(要小心!很长)。如果您愿意,可以选择将路径存储在堆栈中,每次将节点设置为VISITED时推送节点,并在每次将节点设置回OPEN时弹出节点。每次到达目标时,应复制并保存包含当前路径的堆栈。

如果删除了标记为“dead-end-investigation-step”的if块,则此实现几乎与问题中描述的实现完全相同。

package test;

import java.util.HashSet;
import java.util.Set;

public class MazeSolver {

final static int OPEN = 0;
final static int WALL = 1;
final static int GOAL = 2;
final static int VISITED = 3;

static int[][] field = { { 0, 0, 0, 0, 0, 1 }, { 1, 0, 1, 1, 0, 1 },
        { 1, 0, 1, 0, 0, 0 }, { 0, 0, 0, 0, 1, 2 }, { 1, 0, 1, 0, 0, 0 } };

// This is what the field looks like:
//  
// 0 1 1 0 1
// 0 0 0 0 0
// 0 1 1 0 1
// 0 1 0 0 0
// 0 0 0 1 0
// 1 1 0 2 0

static int width = field.length;
static int height = field[0].length;
static int xStart = 0;
static int yStart = 0; // Initiated to start position: (x = 0, y = 0)
static int nrSolutions = 0; // Records number of solutions

// Used for storing id:s of dead end nodes.
// The integer id is (x + y * width)
static Set<Integer> deadEnds = new HashSet<Integer>();

public static void main(String[] arg) {
    System.out.println("Initial maze:");
    printField();

    findPath(xStart, yStart);

    System.out.println("Number of solutions: " + nrSolutions);
    System.out.println("Number of dead ends: " + deadEnds.size());
}

private static void findPath(int x, int y) {

    if (x < 0 || y < 0 || x >= width || y >= height) { // Step 1
        return;
    } else if (field[x][y] == GOAL) { // Step 2
        nrSolutions++;
        System.out.println("Solution nr " + nrSolutions + ":");
        printField();
        return;
    } else if (field[x][y] != OPEN) { // Step 3
        return;
    } else if (isDeadEnd(x, y)) { // Extra dead-end-investigation-step
        int uniqueNodeId = x + y * width;
        deadEnds.add(uniqueNodeId); // Report as dead end
        return;
    }

    field[x][y] = VISITED; // Step 4

    findPath(x, y - 1); // Step 5, go north
    findPath(x + 1, y); // Step 6, go east
    findPath(x, y + 1); // Step 7, go south
    findPath(x - 1, y); // Step 8, go west

    field[x][y] = OPEN; // Step 9

    // Step 10 is not really needed, since the boolean is intended to
    // display only whether or not a solution was found. This implementation
    // uses an int to record the number of solutions instead.
    // The boolean return value would be (nrSolutions != 0)
}

private static boolean isDeadEnd(int x, int y) {
    int nrVisitedNeighbours = 0;

    if (y > 0) { // If northern neighbour exists
        if (field[x][y - 1] == VISITED) {
            nrVisitedNeighbours++;
        } else if (field[x][y - 1] != WALL) {
            return false;
        }
    }
    if (x < width - 1) { // If eastern neighbour exists
        if (field[x + 1][y] == VISITED) {
            nrVisitedNeighbours++;
        } else if (field[x + 1][y] != WALL) {
            return false;
        }
    }
    if (y < height - 1) { // If southern neighbour exists
        if (field[x][y + 1] == VISITED) {
            nrVisitedNeighbours++;
        } else if (field[x][y + 1] != WALL) {
            return false;
        }
    }
    if (x > 0) { // If western neighbour exists
        if (field[x - 1][y] == VISITED) {
            nrVisitedNeighbours++;
        } else if (field[x - 1][y] != WALL) {
            return false;
        }
    }

    if (nrVisitedNeighbours > 1) { // Circular path scenario
        return false;
    }

    return true; // The exhaustive result of this check: this is a dead
    // end
}

private static void printField() {
    for (int yy = 0; yy < field[0].length; yy++) {
        for (int xx = 0; xx < field.length; xx++) {

            System.out.print(field[xx][yy] + " ");
        }
        System.out.println();
    }
    System.out.println();
}
}

package test; import java.util.HashSet; import java.util.Set; public class MazeSolver { final static int OPEN = 0; final static int WALL = 1; final static int GOAL = 2; final static int VISITED = 3; static int[][] field = { { 0, 0, 0, 0, 0, 1 }, { 1, 0, 1, 1, 0, 1 }, { 1, 0, 1, 0, 0, 0 }, { 0, 0, 0, 0, 1, 2 }, { 1, 0, 1, 0, 0, 0 } }; // This is what the field looks like: // // 0 1 1 0 1 // 0 0 0 0 0 // 0 1 1 0 1 // 0 1 0 0 0 // 0 0 0 1 0 // 1 1 0 2 0 static int width = field.length; static int height = field[0].length; static int xStart = 0; static int yStart = 0; // Initiated to start position: (x = 0, y = 0) static int nrSolutions = 0; // Records number of solutions // Used for storing id:s of dead end nodes. // The integer id is (x + y * width) static Set<Integer> deadEnds = new HashSet<Integer>(); public static void main(String[] arg) { System.out.println("Initial maze:"); printField(); findPath(xStart, yStart); System.out.println("Number of solutions: " + nrSolutions); System.out.println("Number of dead ends: " + deadEnds.size()); } private static void findPath(int x, int y) { if (x < 0 || y < 0 || x >= width || y >= height) { // Step 1 return; } else if (field[x][y] == GOAL) { // Step 2 nrSolutions++; System.out.println("Solution nr " + nrSolutions + ":"); printField(); return; } else if (field[x][y] != OPEN) { // Step 3 return; } else if (isDeadEnd(x, y)) { // Extra dead-end-investigation-step int uniqueNodeId = x + y * width; deadEnds.add(uniqueNodeId); // Report as dead end return; } field[x][y] = VISITED; // Step 4 findPath(x, y - 1); // Step 5, go north findPath(x + 1, y); // Step 6, go east findPath(x, y + 1); // Step 7, go south findPath(x - 1, y); // Step 8, go west field[x][y] = OPEN; // Step 9 // Step 10 is not really needed, since the boolean is intended to // display only whether or not a solution was found. This implementation // uses an int to record the number of solutions instead. // The boolean return value would be (nrSolutions != 0) } private static boolean isDeadEnd(int x, int y) { int nrVisitedNeighbours = 0; if (y > 0) { // If northern neighbour exists if (field[x][y - 1] == VISITED) { nrVisitedNeighbours++; } else if (field[x][y - 1] != WALL) { return false; } } if (x < width - 1) { // If eastern neighbour exists if (field[x + 1][y] == VISITED) { nrVisitedNeighbours++; } else if (field[x + 1][y] != WALL) { return false; } } if (y < height - 1) { // If southern neighbour exists if (field[x][y + 1] == VISITED) { nrVisitedNeighbours++; } else if (field[x][y + 1] != WALL) { return false; } } if (x > 0) { // If western neighbour exists if (field[x - 1][y] == VISITED) { nrVisitedNeighbours++; } else if (field[x - 1][y] != WALL) { return false; } } if (nrVisitedNeighbours > 1) { // Circular path scenario return false; } return true; // The exhaustive result of this check: this is a dead // end } private static void printField() { for (int yy = 0; yy < field[0].length; yy++) { for (int xx = 0; xx < field.length; xx++) { System.out.print(field[xx][yy] + " "); } System.out.println(); } System.out.println(); } }

上面的算法向示例迷宫报告了四个不同的解决方案路径和两个死角,并将其硬编码到代码中。


<强>&LT;编辑&gt; 您可能会因为解决方案路径过多而误解解决方案路径的原因?例如,考虑这个迷宫:

此迷宫只有一个有效的解决方案路径。这是因为您只允许访问每个节点一次,因此绕循环路径不是有效的解决方案路径,因为它将访问S的东部和E的北部两次。您使用的算法暗示了解决方案路径的定义。

如果允许多次访问同一节点,那么将会有无限多的解决方案,因为你可以绕圈1,2,3 ......无限次地进行。 的&LT; /编辑&gt;

<强>&LT; EDIT2&GT;

正如您所说的,每次将节点设置为VISITED时都会增加pathLength,并且每次将VISITED节点设置回OPEN时都会减小路径长度。

要记录最短路径长度,我还有一个shortestPath int值,我将其启动到Integer.MAX_VALUE。然后,每次我达到目标,我都这样做:

######
##   #
## # #
#S   #
##E###
######

至于死路一条......我试着用手计算它们,我认为9似乎是正确的。这是由Sareen发布的迷宫,但死角标记(手动)与D:

if(pathLength < shortestPath){
    shortestPath = pathLength;
}

你还能找到吗? 或者我误解了你的死胡同意味着什么? 我认为死胡同意味着:一个节点,你只能从一个方向来。

示例1:

####################
#S # D#      D#D  D#
#  # ##  ##  ### ###
#     #   #   #    #
## #  #   #     ## #
#     ### #####    #
# #   #D  #D #   ###
# ### ### ## # #####
# D#D     #D      E#
####################

上面的迷宫有一个死胡同。

示例2:

######
## ###
## ### 
## ### 
#S  E#
######

上面的迷宫没有死角。即使你在最北边的一个可访问节点上,仍然有两个相邻的非WALL方块。

你有另一个死胡同的定义吗? 的&LT; / EDIT2&GT;

答案 4 :(得分:0)

这是一个样本迷宫

####################
#S #  #       #    #
#  # ##  ##  ### ###
#     #   #   #    #
## #  #   #     ## #
#     ### #####    #
# #   #   #  #   ###
# ### ### ## # #####
#  #      #       E#
####################