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*算法,这是行业标准的寻路算法,它结合了广度优先搜索和启发式搜索……有点像"智能广度优先搜索"。好的。
它基本上是这样工作的:好的。
这是一个*,我特别强调了这一点,因为它或多或少是行业标准的寻路算法,适用于所有寻路应用,包括从地图的一个边缘移动到另一个边缘,同时避免非公路路径或山脉等。它工作得很好,因为它使用了尽可能短的距离启发式算法,这使得它是"智慧"。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的人有一个等价物。