今天记录一下用DFS和回溯法实现走迷宫问题,输出一个迷宫中从起点到终点的所有可能的路径。
迷宫我们用一个二维的字符数组表示,其中的0表示路,1表示墙。 为了方便起见,我们从txt文件中读入这个数组,txt文件中的内容如下所示:
接下来我们写一下从文件中读入这个数组的代码:
1 vector initMaze(string fileName = "maze.txt")
2 {
3 ifstream fin;
4 fin.open(fileName);
5 if (!fin)
6 {
7 cout << "open '" << fileName << "' failed!" << endl;
8 return {};
9 }
10
11 vector res;
12 string line;
13 while (getline(fin, line))
14 {
15 trim(line);
16 res.push_back(line);
17 }
18 return res;
19 }
由于文件中的字符之间可能存在空格,于是我们写一个函数过滤掉这些空格,有一说一,C++在很多细节处理方面是远没有python方便的
1 void trim(string &s)
2 {
3 int index = 0;
4 if (!s.empty())
5 {
6 while ((index = s.find(' ', index)) != string::npos)
7 {
8 s.erase(index, 1);
9 }
10 }
11 }
然后我们准备好要用到的数据结构,由于其中很多地方都要用到坐标,所以我们写一个坐标的结构体
1 typedef struct pos
2 {
3 int x;
4 int y;
5 pos(int x, int y) : x(x), y(y) {}
6 pos() : x(-1), y(-1) {}
7 pos(int v[2]) : x(v[0]), y(v[1]) {}
8 pos operator+(pos p)
9 {
10 pos tmp = pos(this->x + p.x, this->y + p.y);
11 return tmp;
12 }
13 pos operator+(int v[2])
14 {
15 pos tmp = pos(this->x + v[0], this->y + v[1]);
16 return tmp;
17 }
18 pos operator-(pos p)
19 {
20 pos tmp = pos(this->x - p.x, this->y - p.y);
21 return tmp;
22 }
23 pos operator-(int v[2])
24 {
25 pos tmp = pos(this->x - v[0], this->y - v[1]);
26 return tmp;
27 }
28 bool operator==(pos p) { return this->x == p.x && this->y == p.y; }
29 bool operator==(int v[2]) { return this->x == v[0] && this->y == v[1]; }
30 } pos;
其中我们主要是定义了一些构造函数和重载了一些运算符。其实C++中的结构体和类基本用法是一样的,唯一的区别就是class默认是private,struct默认是public。
接下来我们写一下走迷宫过程的dfs函数
1 int dfs(stack &s, pos now, pos end, vector<vector> &flag, vector &maze, vector<vector> &path)
2 {
3 if (now == end)
4 {
5 copyStack2Vector(s, path);
6 return 1;
7 }
8 else
9 {
10 flag[now.x][now.y] = 1; //标记当前位置走过
11 int dir[4][2] = {{-1, 0}, {1, 0}, {0, -1}, {0, 1}}; //上、下、左、右
12 for (int i = 0; i < 4; i++)
13 {
14 pos next = now + dir[i];
15 if (!judge(next, end, flag, maze))
16 continue; //如果下一个节点不合法, 那么直接继续判断下一个可能的位置
17 s.push(next);
18 if (dfs(s, next, end, flag, maze, path))
19 {
20 s.pop();
21 }
22 }
23 flag[now.x][now.y] = 0;
24 s.pop(); //说明这边走不通了,把栈顶的位置弹出
25 return 0;
26 }
27 }
这里需要详细解释一下,首先我们看一下递归函数的参数
stack& s, pos now, pos end, vector<vector>& flag, vector& maze, vector<vector>& path
由于走迷宫过程中可能走到某一步的时候发现无法继续前进了,那么我们只能往回退一步(回溯)。那么我们就需要用一种数据结构去记录我们走过的路径,很明显,栈是符合我们要求的(后进先出)。所以这里的 stack& s 就是记录目前我们走过的路径。
pos now 是我们当前的位置,我们每次往前走一步就要更新这个变量。
pos end 是最后的终点位置,始终保持不变。
vector<vector>& flag是用来记录哪些位置是我们走过的,这个主要是为了避免我们又走到当前栈里面存在的位置上。所以一旦一个位置走过,我们就在这个数组里面标记一下。
vector& maze是我们的迷宫矩阵,里面的字符只能是0或者1,flag的维度和maze的维度相同。
vector<vector>& path 是用来记录我们找到的路径,每当我们走到终点的时候,我们就把栈里面的所有位置组成一条路径,再添加到path中。
然后我们再解释一下其中的代码。 既然是递归形式的代码,那么第一步自然是先写好边界条件,就好比n皇后问题一样,第一步是确定递归到什么时候结束,这里明显是当我们到了终点就结束。然后把此时栈里面的路径添加到结果path中。 每次进来,先把当前位置标记为已经在当前的路径当中,然后接下来就是试探性往上下左右某一个方向走一步,所以我们需要判断下一个位置是否可以走,这里是用judge函数判断的,其定义如下:
1 bool judge(pos p, pos end, vector<vector> &flag, vector &maze)
2 {
3 return p.x >= 0 && p.x <= end.x && p.y >= 0 && p.y <= end.y && maze[p.x][p.y] != '1' && flag[p.x][p.y] == 0; //没有越界,没有走过,有路
4 }
如果当前位置没有在迷宫外边,并且当前位置的迷宫中标记为0,同时flag数组没有标记过该位置,也就是还没在当前已经走过的路径中,所以是可以走的 回到dfs函数,如果当前该位置不可以走,那么就去判断下一个位置,一旦可以走,就把下一个位置添加到栈中,然后从下一个位置开始递归。 如果递归返回的是1,说明成功过一次,那么就弹出当前栈顶位置,去判断下一个位置。 如果当前位置的周围的4个位置都走不通,那么就说明当前位置是不能走的,于是我们把当前位置从路径中去掉,也就是最后面的
flag[now.x][now.y] = 0;
s.pop();//说明这边走不通了,把栈顶的位置弹出
return 0;
针对我们上面那个迷宫,程序输出了740种走法,如下所示:
其中@表示起始位置,$表示终点位置,中间的+ - |字符表示路径。
最后,完整程序如下:
1 // Maze.cpp : 此文件包含 "main" 函数。程序执行将在此处开始并结束。
2 //
3
4 #include <iostream>
5 #include <fstream>
6 #include <vector>
7 #include <string>
8 #include <stack>
9
10 using namespace std;
11
12 typedef struct pos {
13 int x;
14 int y;
15
16 pos(int x, int y) : x(x), y(y){}
17 pos() : x(-1), y(-1) {}
18 pos(int v[2]) : x(v[0]), y(v[1]) {}
19
20 pos operator+(pos p) {
21 pos tmp = pos(this->x + p.x, this->y + p.y);
22 return tmp;
23 }
24
25 pos operator+(int v[2]) {
26 pos tmp = pos(this->x + v[0], this->y + v[1]);
27 return tmp;
28 }
29
30 pos operator-(pos p) {
31 pos tmp = pos(this->x - p.x, this->y - p.y);
32 return tmp;
33 }
34
35 pos operator-(int v[2]) {
36 pos tmp = pos(this->x - v[0], this->y - v[1]);
37 return tmp;
38 }
39
40 bool operator==(pos p) {
41 return this->x == p.x && this->y == p.y;
42 }
43
44 bool operator==(int v[2]) {
45 return this->x == v[0] && this->y == v[1];
46 }
47 }pos;
48
49 void trim(string& s)
50 {
51 int index = 0;
52 if (!s.empty()) {
53 while ((index = s.find(' ', index)) != string::npos) {
54 s.erase(index, 1);
55 }
56 }
57 }
58
59 vector<string> initMaze(string fileName = "maze.txt") {
60 ifstream fin;
61 fin.open(fileName);
62 if (!fin) {
63 cout << "open '" << fileName << "' failed!" << endl;
64 return {};
65 }
66 vector<string> res;
67
68 string line;
69 while (getline(fin, line)) {
70 trim(line);
71 res.push_back(line);
72 }
73
74 return res;
75 }
76
77 bool judge(pos p, pos end, vector<vector<int>>& flag, vector<string>& maze) {
78 return p.x >= 0 && p.x <= end.x && p.y >= 0 && p.y <= end.y &&
79 maze[p.x][p.y] != '1' && flag[p.x][p.y] == 0;//没有越界,没有走过,有路
80 }
81
82 void copyStack2Vector(stack<pos> s, vector<vector<pos>>& path) {
83 stack<pos> s2 = s;
84 stack<pos> s3;
85 vector<pos> vec;
86 while (!s2.empty()) {
87 pos p = s2.top();
88 s2.pop();
89 s3.push(p);
90 }
91
92 while (!s3.empty()) {
93 pos p = s3.top();
94 s3.pop();
95 vec.push_back(p);
96 }
97
98 path.push_back(vec);
99 }
100
101 int dfs(stack<pos>& s, pos now, pos end, vector<vector<int>>& flag, vector<string>& maze, vector<vector<pos>>& path) {
102 if (now == end) {
103 copyStack2Vector(s, path);
104 return 1;
105 }
106 else {
107 flag[now.x][now.y] = 1;//标记当前位置走过
108
109 int dir[4][2] = { {-1, 0}, {1, 0}, {0, -1}, {0, 1} };//上、下、左、右
110 for (int i = 0; i < 4; i++) {
111 pos next = now + dir[i];
112 if (!judge(next, end, flag, maze))
113 continue;//如果下一个节点不合法, 那么直接继续判断下一个可能的位置
114
115 s.push(next);
116
117 if (dfs(s, next, end, flag, maze, path)) {
118 s.pop();
119 }
120 }
121
122 flag[now.x][now.y] = 0;
123 s.pop();//说明这边走不通了,把栈顶的位置弹出
124
125 return 0;
126 }
127 }
128
129 void printMaze(vector<string>& maze) {
130 for (auto e : maze) cout << e << endl;
131 cout << endl;
132 }
133
134 void printMaze(vector<vector<unsigned char>>& maze) {
135 for (auto vec : maze) {
136 for (auto e : vec)
137 cout << e << " ";
138 cout << endl;
139 }
140 cout << endl;
141 }
142
143 int checkDirection(pos a, pos b) {
144 int dir[4][2] = { {-1, 0}, {1, 0}, {0, -1}, {0, 1} };//上、下、左、右
145 for (int i = 0; i < 4; i++) {
146 if (a + dir[i] == b) {
147 return i;
148 }
149 }
150 }
151
152 unsigned char printPath(pos pre, pos now, pos next, pos end) {
153 int dir[4][2] = { {-1, 0}, {1, 0}, {0, -1}, {0, 1} };//上、下、左、右
154 if (now == pos(0, 0)) {
155 return '@';
156 }
157 else if (now == end) {
158 return '$';
159 }
160 else {
161 int pre_now = checkDirection(pre, now), now_next = checkDirection(next, now);
162 if ((pre_now == 2 && now_next == 1) || (pre_now == 1 && now_next == 2))
163 return '+';//Left Down
164 else if ((pre_now == 0 && now_next == 3) || (pre_now == 3 && now_next == 0))
165 return '+';//Right Up
166 else if ((pre_now == 2 && now_next == 0) || (pre_now == 0 && now_next == 2))
167 return '+';//Left Up
168 else if ((pre_now == 3 && now_next == 1) || (pre_now == 1 && now_next == 3))
169 return '+';//Left Up
170 else if ((pre_now == 0 && now_next == 1) || (pre_now == 1 && now_next == 0))
171 return '|';//Down Up
172 else if ((pre_now == 2 && now_next == 3) || (pre_now == 3 && now_next == 2))
173 return '-';//Left Right
174 else
175 return '$';
176 }
177
178 return '$';
179 }
180
181 int main()
182 {
183 //std::cout << "Hello World!\n";
184 vector<string> maze = initMaze("maze.txt");
185 if (maze.empty()) return 0;
186
187 int rows = maze.size(), cols = maze[0].size();
188 vector<vector<int>> flag(rows, vector<int>(cols, 0));
189 printMaze(maze);
190
191 pos start = pos(0, 0), end = pos(rows-1, cols-1);
192
193 stack<pos> s;
194 s.push(start);
195
196 vector<vector<pos>> path;
197 dfs(s, start, end, flag, maze, path);
198
199 if (!path.empty()) {
200 for (auto vec : path) {
201 int path_len = vec.size();
202 cout << "path length: " << path_len << endl;
203 vector<vector<unsigned char>> maze_tmp(rows, vector<unsigned char>(cols, '0'));
204 for (int i = 0; i < rows; i++)
205 for (int j = 0; j < cols; j++)
206 maze_tmp[i][j] = maze[i][j];
207
208 for (int i = 0; i < path_len; i++) {
209 pos now = vec[i];
210 pos pre = (i == 0) ? now : vec[i - 1];
211 pos next = (i == path_len - 1) ? now : vec[i + 1];
212 maze_tmp[now.x][now.y] = printPath(pre, now, next, end);
213 }
214
215 printMaze(maze_tmp);
216 }
217
218 cout << "there exists " << path.size() << " paths for this maze" << endl;
219 }
220 else {
221 cout << "there is no path for this maze" << endl;
222 }
223 }
只有0和1的世界是简单的