今天记录一下用DFS和回溯法实现走迷宫问题,输出一个迷宫中从起点到终点的所有可能的路径。

 

迷宫我们用一个二维的字符数组表示,其中的0表示路,1表示墙。 为了方便起见,我们从txt文件中读入这个数组,txt文件中的内容如下所示:

python走迷宫dfs_python走迷宫dfs

 

接下来我们写一下从文件中读入这个数组的代码: 

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种走法,如下所示:

python走迷宫dfs_python走迷宫dfs_02

其中@表示起始位置,$表示终点位置,中间的+ - |字符表示路径。

最后,完整程序如下: 

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的世界是简单的