关于算法:编程理论:解决迷宫问题

Programming theory: Solve a maze

解决迷宫的可能方法是什么?我有两个想法,但我觉得它们不太优雅。

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

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

第二个选项通过标记为1的每个后续项,检查它可以到达的位置(向上、向右、向下、向左),选择一种方式并继续其路径。这甚至比第一个慢。

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

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

编辑首先:谢谢你的回答!

我问题的第二部分是:如果我们有一个多维图,该怎么办?有没有特殊的做法,或者贾斯汀L.的答案也适用于这一点?我认为这不是最好的办法。

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


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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
     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是死胡同,目标是**。当然,在实际的树中,每个节点都有可能有多达三个子节点。好的。

现在,您的目标只是找到要遍历哪些节点才能找到完成点。任何一种树搜索算法都可以。好的。

从树的最深处的**可以很容易地看到正确的解决方案:好的。

1
A B E H M **

请注意,当迷宫中有"循环"时,这种方法只会稍微复杂一点(即,如果可能,没有回溯,则重新进入已经穿过的通道)。检查注释以获得一个好的解决方案。好的。

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

您的第一个解决方案基本上是深度优先搜索,这并没有那么糟糕。实际上这是一个很好的递归搜索。基本上,它说,"总是先采用最右边的方法。如果什么都没有的话,回溯到第一个地方,你可以直行或左行,然后重复。好的。

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

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

请注意,找到**后可以立即停止。好的。

然而,当您实际编写深度优先搜索的代码时,使用递归编程使一切变得容易得多。即使是迭代方法也可以工作,而且您永远不必显式地编程如何回溯。查看链接的文章以了解实现。好的。

另一种搜索树的方法是广度优先解决方案,它通过树的深度进行搜索。它将按以下顺序搜索上面的树:好的。

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

注意,由于迷宫的性质,宽度优先检查的节点的平均数量要高得多。宽度优先是很容易实现的,它有一个要搜索的路径队列,每次迭代都会从队列中弹出一条路径,"分解它",方法是在一个步骤之后获取它可以转化为的所有路径,并将这些新路径放在队列的末尾。没有明确的"下一级"命令来编码,而这些命令只是用来帮助理解。好的。

事实上,搜索树的方法有很多种。我刚刚提到了两种最简单、最直接的方法。好的。

如果你的迷宫很长很深,有循环和疯狂,而且很复杂,我建议使用A*算法,这是行业标准的寻路算法,它结合了广度优先搜索和启发式搜索……有点像"智能广度优先搜索"。好的。

它基本上是这样工作的:好的。

  • 把一条路排成一列(你只走一步就进入迷宫的那条路)。路径的"权重"由其当前长度+其与末端的直线距离(可通过数学计算得出)得出。
  • 从队列中弹出权重最小的路径。
  • 将路径"分解"为每一条可能经过一步的路径。(例如,如果您的路径是右左右,则分解的路径是R L L R和R L L R L,不包括穿过墙壁的非法路径)
  • 如果其中一条路有目标,那就胜利吧!否则:
  • 计算分解路径的权重,并将所有分解路径放回队列(不包括原始路径)
  • 按重量对队列进行排序,最低者优先。然后重复步骤2
  • 这是一个*,我特别强调了这一点,因为它或多或少是行业标准的寻路算法,适用于所有寻路应用,包括从地图的一个边缘移动到另一个边缘,同时避免非公路路径或山脉等。它工作得很好,因为它使用了尽可能短的距离启发式算法,这使得它是"智慧"。A*是如此的通用,因为在任何问题下,如果你有一个尽可能短的距离启发式可用(我们的很容易——直线),你可以应用它。好的。

    但值得注意的是,*并不是你唯一的选择。好的。

    事实上,维基百科中的树遍历算法只列出了97个!(最好的仍会出现在之前链接的页面上)好的。

    对不起,长度=P(我喜欢闲聊)好的。好啊。


    有很多迷宫求解算法:

    http://en.wikipedia.org/wiki/maze_求解算法

    http://www.astrolog.org/labyrnth/algrithm.htm求解

    对于机器人来说,Tremax的算法看起来很有前途。


    一个有趣的方法,至少我觉得它有趣,是使用细胞自动机。简而言之,由3个"墙"单元包围的"空间"单元变成了一个"墙"单元。在最后,剩下的唯一的空间单元是通往出口的路线上的单元。

    如果你看贾斯汀在他的答案中写的树,你会发现叶节点有三个墙。修剪这棵树直到你有一条路。


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

    1
    2
    3
    4
    5
    6
    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


    如何从矩阵中构建一个图,并使用广度优先搜索、深度优先搜索或dijkstras算法?


    这是一个非常简单的表示来模拟C++中的迷宫。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    72
    73
    74
    75
    76
    77
    78
    79
    80
    81
    82
    83
    84
    85
    86
    87
    88
    89
    90
    91
    92
    93
    94
    95
    96
    97
    98
    99
    100
    101
    102
    103
    104
    105
    106
    107
    108
    109
    110
    111
    112
    113
    114
    115
    116
    117
    118
    119
    120
    121
    122
    123
    124
    125
    126
    127
    128
    129
    130
    131
    132
    133
    134
    135
    136
    137
    138
    139
    140
    141
    142
    143
    144
    145
    146
    147
    148
    149
    150
    151
    152
    153
    154
    155
    156
    157
    158
    159
    160
    161
    162
    163
    164
    165
    166
    167
    168
    169
    170
    171
    172
    173
    174
    175
    176
    177
    178
    179
    180
    181
    182
    183
    184
    185
    186
    187
    188
    189
    190
    191
    192
    193
    194
    195
    196
    197
    198
    199
    200
    201
    202
    203
    204
    205
    206
    207
    208
    209
    210
    211
    212
    #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


    只是一个想法。为什么不以蒙特卡洛的方式在那里扔一些机器人呢?让我们称第一代僵尸为gen0。我们只将机器人程序与Gen0保持距离,因为Gen0有一些连续的道路:-从一开始到某一点或者-从某一点到最后

    我们在新的随机点中运行一个新的gen1机器人程序,然后我们尝试将gen1机器人程序的道路与gen0机器人程序的道路连接起来,看看我们是否从开始到结束都有一条连续的道路。

    所以对于genn,我们尝试连接gen0,gen1,…,genn-1形式的僵尸。

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

    我不知道该算法的复杂性是否会证明对小数据集是可行的。同时,该算法假定我们知道起始点和结束点。

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


    有许多算法和许多不同的设置指定哪种算法最好。这只是一个关于有趣环境的想法:

    假设您具有以下属性…

    • 你移动一个机器人,你想要最小化它的移动,而不是它的CPU使用。
    • 这个机器人要么只能检查它的邻近细胞,要么沿着走廊看,要么看,要么看不到交叉通道。
    • 它有GPS。
    • 它知道目的地的坐标。

    然后你可以设计一个自动识别系统…

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

    如果机器人能跟踪到它的位置,那么它就知道它以前是否去过某个位置,那么深度优先搜索是一个显而易见的算法。你可以用一个敌对的论点来证明,最坏情况下的性能是不可能比深度优先搜索更好的。

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


    我的大学毕业典礼上也遇到了类似的问题。SCI。课程。我们提出的解决方案是沿着左手边的墙(右手边的墙也会起作用)。这是一些伪代码

    1
    2
    3
    4
    5
    6
    7
    8
    While Not At End
        If Square To Left is open,
            Rotate Left
            Go Forward
        Else
            Rotate Right
        End If
    Wend

    基本上就是这样。复杂的部分是跟踪你的面向哪个方向,并根据这个方向找出你左边的网格位置。它适用于我提出的任何测试用例。有趣的是,教授们的解决方案是:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    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

    这对大多数简单的迷宫都很有效,但在如下迷宫中失败:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    SXXXXXXXXXXXXX
       X         X
       X         X
       X         X
     XXX         X
     X X         X
     X XXXXXXXXXXX     XXXE
     X                 X
     XXXXXXXXXXXXXXXXXXX

    以S和E为起点和终点。

    如果有什么东西跟不上墙,你就得把你去过的地方列一个清单,这样当你陷入死胡同时,你可以在必要的时候回溯,这样你就不会陷入循环。如果你沿着墙走,就没有必要跟踪你去过的地方。虽然你找不到穿过迷宫的最佳路径,但你总能通过迷宫。


    不是专门针对您的情况,但我遇到了几个编程竞赛问题,我发现Lee的算法非常方便快速编码。它不是所有情况下最有效的,但很容易启动。这是我为一个比赛而设计的。


    求解迷宫的最佳方法是使用连通性算法,如联合查找(union find),这是假设路径压缩完成的准线性时间算法。

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

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

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


    这个azkaban算法也可以帮助你,http://journals.analysiofalgorithms.com/2011/08/efficient-maze-solving-approach-with.html


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

    使用vi!

    http://www.texteditors.org/cgi-bin/wiki.pl?六迷宫

    看到一个文本编辑器解决一个ASCII迷宫真的很有意思,我相信Emacs的人有一个等价物。