【问题标题】:Programming theory: Solve a maze编程理论:解决迷宫
【发布时间】:2011-03-07 01:39:03
【问题描述】:

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

基本情况:我们有一个矩阵,这个矩阵中的元素按照它代表一个迷宫的方式排列,一个进一个出。

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

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

当然,如果我让两个机器人在每个连接点都多线程,那会快一点,但这也不是最好的方法。

需要更好的解决方案来让机器人穿越迷宫。

编辑
首先:感谢您的精彩回答!

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

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

【问题讨论】:

  • 我通常从 END 开始,然后向后工作。它几乎每次都有效!
  • 你可以阅读cut-the-knot.org/ctk/Mazes.shtml 这是一个很好的迷宫介绍
  • 电脑不知道什么是开始或什么是结束,从末端开始根本没有帮助。
  • @Dominique 只是主观观察:人类迷宫设计者倾向于在迷宫出口处仅绘制一个分支。这就是为什么从结尾开始搜索通常会快一点 - 当然,Justin L. ascii 艺术不是这种情况
  • 多维图也可以用树来表示 =)

标签: algorithm maze


【解决方案1】:

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

【讨论】:

  • 请包含一些示例代码,而不仅仅是链接。谢谢!
  • 其实示例代码在我贴的github链接上:)
  • 谢谢朱斌。 Stack Overflow 的总体思路是链接可能会变得无效,因此很高兴在此处为后代提供更完整/自包含的答案! :) 不管怎样,欢迎来到 SO!
【解决方案2】:

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

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

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

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

【讨论】:

    【解决方案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
    

    【讨论】:

    • 这个文件/Users/vshakya/Dropbox/private/graph/maze.txt的格式是什么
    【解决方案4】:

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

    假设您具有以下属性...

    • 您移动一个机器人,并且您希望尽量减少它的移动,而不是它的 CPU 使用率。
    • 该机器人既可以只检查其相邻的单元格,也可以沿着走廊查看是否看到横向。
    • 它有 GPS
    • 它知道目的地的坐标。

    然后你可以设计一个人工智能。哪个...

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

    【讨论】:

      【解决方案5】:

      【讨论】:

        【解决方案6】:

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

        一种 / \ / \ 乙丙 / \ / \ D E F G / \ \ 希杰 / \ LM / \ ** 哦 (可能代表) 开始 + +---+---+ | A C G | +---+ + + + |数据库 | F | Ĵ | +---+---+ +---+---+ | L H E I | +---+ +---+---+ |莫| + +---+ 结束 (忽略树上的左右排序)

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

        您现在的目标只是找到要遍历的节点以找到终点。任何老树搜索算法都可以。

        查看树,只需从树最深处的 **“向上追踪”,就很容易找到正确的解决方案:

        A B E H M **
        

        请注意,当您的迷宫中有“循环”时,这种方法只会稍微稍微复杂一些(即,如果有可能,无需回溯,您就可以重新进入已经走过的通道通过)。检查 cmets 以获得一个不错的解决方案。

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

        您的第一个解决方案基本上是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. 将路径“分解”成每一步后可能出现的路径。 (即,如果您的路径是 Right Left Left Right,那么您的分解路径是 R L L R R 和 R L L R L,不包括穿过墙壁的非法路径)
        4. 如果这些路径之一有目标,那么胜利!否则:
        5. 计算分解后路径的权重,并将它们全部放回队列中(不包括原始路径)
        6. 按权重对队列进行排序,最低的在前。然后从第 2 步开始重复

        这就是 A*,我特别强调了它,因为它或多或少是用于所有寻路应用的行业标准寻路算法,包括从一个边缘移动将地图转移到另一个地图,同时避开越野路径或山脉等。它之所以如此出色,是因为它使用了可能的最短距离启发式,这赋予了它“智能”。 A* 用途广泛,因为在遇到任何问题时,如果您有可用的最短距离启发式方法(我们的方法很简单——直线),您就可以应用它。

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

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

        对不起,长度=P(我倾向于漫无目的)

        【讨论】:

        • 嘿,为了好玩,添加了 ascii 迷宫。希望它有助于理解如何从迷宫中得到一棵树。
        • @Justin:很好的答案。如果迷宫有循环,那么它就变成了一个图形。如果不是递归,而是迭代并使用单独的堆栈结构并在访问节点之前检查堆栈中的节点以避免循环,您仍然可以像树一样遍历它。
        • 是的,这么长时间没人会读到我的回答嘘
        • FWIW,自动程序死锁检查器实际上等同于图的深度优先搜索,其中分支表示不确定性。只是图的编码——一个程序——相当重要。
        • @JAB 优先队列绝对是一个很好的实现,因为抽象会负责排序和选择下一个节点。我所描述的基本上是一个用作优先队列的队列。
        【解决方案7】:

        与 stack-overflow 上的所有问题的答案相同;)

        使用 vi!

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

        看到一个文本编辑器解决一个 ascii 迷宫真是令人着迷,我相信 emacs 的人也有类似的..

        【讨论】:

          【解决方案8】:

          如果机器人可以跟踪它的位置,因此它知道它以前是否去过某个位置,那么深度优先搜索是显而易见的算法。您可以通过对抗性论证表明,不可能在最坏情况下获得比深度优先搜索更好的性能。

          如果您可以使用机器人无法实现的技术,那么广度优先搜索对于许多迷宫的性能可能会更好,Dijkstra 的算法在图中寻找最短路径也是如此。

          【讨论】:

            【解决方案9】:

            我在我的大学竞赛中遇到了类似的问题。科学。培训班。我们想出的解决方案是沿着左侧墙(右侧墙也可以)。这是一些伪代码

            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 为起点和终点。

            对于任何不遵循墙壁的东西,您最终都必须保留一份您去过的地方的清单,以便在必要时可以回溯,当您陷入死胡同时,这样您就不会'不要陷入循环。如果您跟随墙壁,则无需跟踪您去过的地方。虽然你不会找到通过迷宫的最佳路径,但你总会通过它。

            【讨论】:

            • 我怀疑你没有给出完整的算法,因为这个在入口处旋转/摆动。如果您正在尝试 Wall Follower,请记住,如果出口位于岛内(被路径包围),它只会循环。
            【解决方案10】:

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

            如果您查看 Justin 在他的答案中输入的树,那么您会看到叶节点有 3 个墙。修剪树直到找到路径。

            【讨论】:

            【解决方案11】:

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

            我们在新的随机点上运行新的第 1 代机器人,然后我们尝试将第 1 代机器人的道路与第 0 代机器人的道路连接起来,看看我们是否得到一条从头到尾的连续道路。

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

            当然,一代人只能持续有限的时间。

            我不知道该算法的复杂性是否会被证明对于小数据集是实用的。
            该算法还假设我们知道起点和终点。


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

            【讨论】:

            • Monte Carlo 模拟最适用于计算最优解在时间限制下不可行但可以接受部分解的问题。迷宫搜索可以在有限时间内解决,因此除非迷宫是 1,000,000 x 1,000,000 方格,否则我不会推荐此解决方案来解决此问题。
            【解决方案12】:

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

            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
            

            【讨论】:

            • 如果整个迷宫是一个右转的走廊,这个算法会发生什么? :)
            • 啊!你会永远被困!
            • @Mike S:“在广场上走来走去!”
            • 我认为作者试图描述“墙跟随”,您基本上只是跟随左侧(或右侧)的墙。
            • @Gabe,是的,CS 课程简介,我花了 4 个小时自己想出了解决方案。墙跟踪并非没有警告或失败......
            【解决方案13】:

            存在许多解决迷宫的算法:

            http://en.wikipedia.org/wiki/Maze_solving_algorithm

            http://www.astrolog.org/labyrnth/algrithm.htm#solve

            对于机器人来说,Tremaux's algorithm 看起来很有前途。

            【讨论】:

              【解决方案14】:

              如何根据矩阵构建图表并使用广度优先搜索、深度优先搜索或 Dijkstras 算法?

              【讨论】:

                猜你喜欢
                • 2019-02-01
                • 1970-01-01
                • 2020-06-14
                • 2014-05-07
                • 1970-01-01
                • 1970-01-01
                • 1970-01-01
                • 2015-08-31
                相关资源
                最近更新 更多