编程理论:解决迷宫

时间:2010-06-22 22:17:13

标签: algorithm maze

解决迷宫的可能方法有哪些?
我有两个想法,但我认为它们不是很优雅。

基本情况:我们有一个矩阵,这个矩阵中的元素以一种代表迷宫的方式排序,只有一种方式,一种方式。

我的第一个想法是将一个机器人穿过迷宫,沿着一侧,直到它离开迷宫。我认为这是一个非常缓慢的解决方案。

第二个项目通过标记为1的每个连续项目,检查它可以去哪里(向上,向右,向下,向左)选择一种方式并继续其路径。这甚至比第一个慢。

当然,如果我在每个交叉点使两个机器人多线程,它会更快一点,但这也不是最佳方式。

需要更好的解决方案才能通过迷宫发送机器人。

修改
第一:感谢您的回答!

我的问题的第二部分是:如果我们有一个多维图表,该怎么办?是否有特殊的做法,或Justin L.的答案是否也适用于此? 我认为这不是这种情况的最佳方式。

第三个问题:
这些迷宫求解器算法中哪一个是最快的? (纯粹假设)

14 个答案:

答案 0 :(得分:156)

你可以把你的迷宫想象成一棵树。

     A
    / \
   /   \
  B     C
 / \   / \
D   E F   G
   / \     \
  H   I     J
 / \
L   M
   / \
  **  O

(which could possibly represent)

        START
        +   +---+---+
        | A   C   G |
    +---+   +   +   +
    | D   B | F | J |
+---+---+   +---+---+
| L   H   E   I |
+---+   +---+---+
    | M   O |
    +   +---+
    FINISH

(ignoring left-right ordering on the tree)

每个节点都是路径的交汇点。 D,I,J,L和O是死路,**是目标。 当然,在您的实际树中,每个节点都有可能拥有三个子项。

您的目标现在只是找到要遍历的节点以查找完成。任何ol'树搜索算法都可以。

查看树,通过简单地从树的最深部分的“跟踪”来看到正确的解决方案很容易:

A B E H M **

请注意,当你的迷宫中有“循环”时,这种方法变得只会稍微更复杂(即,如果可能的话,没有回溯,你重新进入你已经遍历过的一个段落通过)。检查注释以获得一个很好的解决方案。

现在,让我们看看您提到的第一个解决方案,应用于此树。

你的第一个解决方案基本上是Depth-First Search,实际上并没有那么糟糕。它实际上是一个非常好的递归搜索。基本上,它说,“总是首先采取最右边的方法。如果没有任何东西,回溯到第一个地方,你可以直接或离开,然后重复。

深度优先搜索将按以下顺序搜索上面的树:

A B D (backtrack) E H L (backtrack) M ** (backtrack) O (backtrack thrice) I
(backtrack thrice) C F (backtrack) G J

请注意,您可以在找到**后立即停止。

然而,当您实际编写深度优先搜索时,使用递归编程会使一切变得更加容易。即使迭代方法也可以工作,您也不必明确地编写如何回溯的方法。查看链接的文章以了解实现。

另一种搜索树的方法是Breadth-First解决方案,它通过深度搜索树。它按以下顺序搜索上面的树:

A (next level) B C (next level) D E F G (next level)
H I J (next level) L M (next level) ** O

请注意,由于迷宫的性质,广度优先具有更高的平均检查节点数。广度优先通过拥有一个搜索路径队列很容易实现,每次迭代都会从队列中弹出一个路径,通过获取一步之后可以转换成的所有路径来“爆炸它”,并放置这些新路径在队列的末尾。没有明确的“下一级”命令来编码,而这些命令只是为了帮助理解。

事实上,有一整个expansive list of ways to search a tree。我刚刚提到了两种最简单,最直接的方式。

如果您的迷宫非常非常长,并且有环路和疯狂,并且很复杂,我建议使用A*算法,这是行业标准寻路算法,它结合了广度优先搜索和启发式算法......有点像“智能广度优先搜索”。

它基本上是这样的:

  1. 将一条路径放入队列(您只走一步直进迷宫的路径)。路径的“权重”由当前长度+与终点的直线距离给出(可以用数学方法计算)
  2. 弹出队列中权重最低的路径。
  3. “爆炸”路径进入一步之后的每条路径。 (即,如果你的路径是右左左右,则你的爆炸路径是R L L R R和R L L R L,不包括穿过墙壁的非法路径)
  4. 如果其中一条路径有目标,那么胜利!否则:
  5. 计算爆炸路径的权重,并将所有路径重新放入队列(不包括原始路径)
  6. 按重量排序队列,先排序最低。然后从步骤#2重复
  7. 那是 A * ,我特别强调它,因为它或多或少是所有路径寻找应用程序的行业标准寻路算法,包括从一个边缘移动它可以在避开越野路径或山脉等情况下运行到另一个地图。它运行良好,因为它使用最短距离启发式,这使它具有“智能”。 A *是如此多才多艺,因为如果你有任何问题,如果你有一个最短的距离启发式(我们很容易 - 直线),你可以应用它。

    但是值得注意的是,A * 不是是您唯一的选择。

    事实上,wikipedia category of tree traversal algorithms仅列出了97个! (最好的仍然是this page之前链接的)

    抱歉长度= P(我倾向于絮絮叨叨)

答案 1 :(得分:13)

答案 2 :(得分:11)

一种有趣的方法,至少我发现它很有趣,就是使用细胞自动机。简而言之,被3“壁”细胞包围的“空间”细胞变成“壁”细胞。最后,剩下的唯一空间单元是通往出口的路径。

如果你看一下Justin的树,他就会看到叶子节点有三面墙。修剪树直到你有一条路径。

答案 3 :(得分:4)

如何使用广度优先搜索,深度优先搜索或Dijkstras算法从矩阵构建图形?

答案 4 :(得分:4)

这是我最喜欢的算法之一......

1) Move forward
2) Are you at a wall?
2a) If yes, turn left
3) Are you at the finish?
3a) If no, go to 1
3b) If yes, solved

答案 5 :(得分:3)

这是一个非常简单的表示,用于在C ++中模拟迷宫:)

#ifndef vAlgorithms_Interview_graph_maze_better_h
#define vAlgorithms_Interview_graph_maze_better_h

static const int kMaxRows = 100;
static const int kMaxColumns = 100;

class MazeSolver
    {
private:
    char m_matrix[kMaxRows][kMaxColumns]; //matrix representation of graph
    int rows, cols; //actual rows and columns

    bool m_exit_found;
    int m_exit_row, m_exit_col;
    int m_entrance_row, m_entrance_col;

    struct square //abstraction for data stored in every verex
        {
        pair<int, int> m_coord; //x and y co-ordinates of the matrix
        square* m_parent; //to trace the path backwards

        square() : m_parent(0) {}
        };

    queue<square*> Q;

public:
    MazeSolver(const char* filename)
        : m_exit_found(false)
        , m_exit_row(0)
        , m_exit_col(0)
        , m_entrance_row(0)
        , m_entrance_col(0)
        {
        ifstream file;
        file.open(filename);

        if(!file)
            {
            cout << "could not open the file" << endl << flush;
            // in real world, put this in second phase constructor
            }
        init_matrix(file);
        }
    ~MazeSolver()
        {
        }
    void solve_maze()
        {
        //we will basically use BFS: keep pushing squares on q, visit all 4 neighbors and see
        //which way can we proceed depending on obstacle(wall)

        square* s = new square();
        s->m_coord = make_pair(m_entrance_row, m_entrance_col);

        Q.push(s);

        while(!m_exit_found && !Q.empty())
            {
            s = Q.front();
            Q.pop();

            int x = s->m_coord.first;
            int y = s->m_coord.second;
            //check if this square is an exit cell
            if(x == m_exit_row && y == m_exit_col)
                {
                m_matrix[x][y] = '>'; // end of the path
                m_exit_found = true;
                //todo: try breaking? no= queue wont empty
                }
            else
                {
                //try walking all 4 neighbors and select best path
                //NOTE: Since we check all 4 neighbors simultaneously,
                //      the path will be the shortest path
                walk_path(x-1, y, s);
                walk_path(x+1, y, s);
                walk_path(x, y-1, s);
                walk_path(x, y+1, s);
                }
            } /* end while */

        clear_maze(); //unset all previously marked visited shit

        //put the traversed path in maze for printing
        while(s->m_parent)
            {
            m_matrix[s->m_coord.first][s->m_coord.second] = '-';
            s = s->m_parent;
            } /* end while */
        }

    void print()
        {
        for(int i=0; i<rows; i++)
            {
            for(int j=0; j<cols; j++)
                cout << m_matrix[i][j];
            cout << endl << flush;
            }
        }

private:
    void init_matrix(ifstream& file)
        {
        //read the contents line-wise
        string line;
        int row=0;
        while(!file.eof())
            {
            std::getline(file, line);
            for(int i=0; i<line.size(); i++)
                {
                m_matrix[row][i] = line[i];
                }
            row++;
            if(line.size() > 0)
                {
                cols = line.size();
                }
            } /* end while */
        rows = row - 1;

        find_exit_and_entry();
        m_exit_found = false;
        }

    //find and mark ramp and exit points
    void find_exit_and_entry()
        {
        for(int i=0; i<rows; i++)
            {
            if(m_matrix[i][cols-1] == ' ')
                {
                m_exit_row = i;
                m_exit_col = cols - 1;
                }
            if(m_matrix[i][0] == ' ')
                {
                m_entrance_row = i;
                m_entrance_col = 0;
                }
            } /* end for */
        //mark entry and exit for testing
        m_matrix[m_entrance_row][m_entrance_col] = 's';
        m_matrix[m_exit_row][m_exit_col] = 'e';
        }

    void clear_maze()
        {
        for(int x=0; x<rows; x++)
            for(int y=0; y<cols; y++)
                if(m_matrix[x][y] == '-')
                    m_matrix[x][y] = ' ';
        }
        // Take a square, see if it's the exit. If not, 
        // push it onto the queue so its (possible) pathways
        // are checked.
    void walk_path(int x, int y, square* parent)
        {
        if(m_exit_found) return;
        if(x==m_exit_row && y==m_exit_col)
            {
            m_matrix[x][y] = '>';
            m_exit_found = true;
            }
        else
            {
            if(can_walk_at(x, y))
                {
                //tag this cell as visited
                m_matrix[x][y] = '-';

                cout << "can walk = " << x << ", " << y << endl << flush;

                //add to queue
                square* s = new square();
                s->m_parent = parent;
                s->m_coord = make_pair(x, y);
                Q.push(s);
                }
            }
        }

    bool can_walk_at(int x, int y)
        {
        bool oob = is_out_of_bounds(x, y);
        bool visited = m_matrix[x][y] == '-';
        bool walled = m_matrix[x][y] == '#';

        return ( !oob && !visited && !walled);
        }
    bool is_out_of_bounds(int x, int y)
        {
        if(x<0 || x > rows || y<0 || y>cols)
            return true;
        return false;
        }
    };


void run_test_graph_maze_better()
        {
        MazeSolver m("/Users/vshakya/Dropbox/private/graph/maze.txt");
        m.print();
        m.solve_maze();
        m.print();
        }


#endif

答案 6 :(得分:2)

只是一个想法。为什么不以蒙特卡洛时尚的方式在那里扔一些机器人。 我们称之为第一代机器人gen0。 我们只保留gen0的机器人以这种方式连续道路:
- 从一开始到某一点
或 - 从某一点到最后

我们在新的随机点中运行一个新的机器人1,然后我们尝试将gen1的机器人的道路与gen0的道路连接起来,看看我们是否从头到尾连续行驶。

因此,对于genn,我们尝试连接机器人gen0,gen1,...,genn-1。

当然,一代人只能持续一段时间。

我不知道算法的肤色是否适用于小型数据集。
该算法假设我们知道起点和终点。


一些好的创意网站:
http://citeseerx.ist.psu.edu/
http://arxiv.org/

答案 7 :(得分:1)

我的一个大学比赛中遇到了类似的问题。科学。课程。我们提出的解决方案是遵循左侧墙(右侧墙也可以正常工作)。这是一些伪代码

While Not At End
    If Square To Left is open,
        Rotate Left
        Go Forward
    Else
        Rotate Right
    End If
Wend

基本上就是这样。复杂的部分是跟踪您面向的方向,并根据此方向确定左侧的网格位置。它适用于我反对它的任何测试用例。有趣的是,教授的解决方案有以下几点:

While Not At End
    If Can Go North
        Go North
    ElseIf Can Go East
        Go East
    ElseIf Can Go South
        Go South
    ElseIf Can Go West 
        Go West
    EndIf
Wend

哪个适用于大多数简单的迷宫,但在迷宫中失败,如下所示:

SXXXXXXXXXXXXX
   X         X
   X         X
   X         X
 XXX         X
 X X         X
 X XXXXXXXXXXX     XXXE
 X                 X
 XXXXXXXXXXXXXXXXXXX

S和E是开始和结束。

任何不跟随墙壁的东西,你最终都要记下你曾经去过的地方,这样你就可以在必要时回溯,当你陷入死胡同时,你就不要陷入困境。如果你跟着墙,就没有必要跟踪你去过的地方。虽然你找不到通过迷宫的最佳路径,但你总能通过它。

答案 8 :(得分:1)

如果机器人可以跟踪它的位置,那么它知道它是否已经到过某个位置,那么深度优先搜索就是明显的算法。您可以通过对抗性论证表明,与深度优先搜索相比,不可能获得更好的最坏情况性能。

如果您可以使用机器人无法实现的技术,那么广度优先搜索可能会对许多迷宫执行得更好,就像Dijkstra在图中找到最短路径的算法一样。

答案 9 :(得分:1)

有许多算法,以及许多不同的设置,它们指定哪种算法最佳。 这只是关于一个有趣设置的一个想法:

我们假设您有以下属性......

  • 你移动一个机器人,你想最小化它的移动,而不是它的CPU使用率。
  • 机器人既可以检查其相邻的单元格,也可以沿着走廊查看或看不到交叉路径。
  • 它有 GPS
  • 它知道目的地的坐标。

然后你可以设计A.I.这...

  • 每次收到有关迷宫的新信息时都会绘制地图。
  • 计算所有未观察到的位置(以及自身和目的地)之间的最小已知路径长度
  • 可以根据周围结构优先考虑未观察到的位置以进行检查。 (如果不可能从那里到达目的地......)
  • 可以根据方向和距离到目的地,对未观察到的位置进行优先排序。
  • 可以根据收集信息的经验优先考虑未观察到的位置以进行检查。 (平均可看多远,走多远?)
  • 可以优先考虑未观察到的位置,以找到可能的快捷方式。 (经验:有很多循环?)

答案 10 :(得分:0)

与堆栈溢出的所有问题相同的答案;)

使用vi!

http://www.texteditors.org/cgi-bin/wiki.pl?Vi-Maze

看到一个文本编辑器解决ascii-maze真的很有意思,我确信emacs的人有一个等价的...

答案 11 :(得分:0)

答案 12 :(得分:0)

解决迷宫的最佳方法是使用连接算法,例如union-find,这是一种准线性时间算法,假设路径压缩完成。

Union-Find是一种数据结构,它告诉您集合中的两个元素是否是可传递的。

要使用union-find数据结构来解决迷宫,首先使用邻居连接数据来构建union-find数据结构。然后联合查找被压缩。为了确定迷宫是否可解决,比较入口和出口值。如果它们具有相同的值,则它们是连接的并且迷宫是可解的。最后,为了找到解决方案,您可以从入口开始,检查与其每个邻居关联的根。一旦找到与当前单元格具有相同根的先前未访问过的邻居,就会访问该单元格并重复该过程。

这种方法的主要缺点是,如果有多条路径,它不会告诉你通过迷宫的最短路径。

答案 13 :(得分:0)

不是专门针对您的情况,但我遇到了几个编程竞赛问题,我发现Lee's algorithm非常方便快速编写代码。它不是所有情况下最有效的,但很容易发出。这是one我参加了比赛。