迷宫寻路问题全解
1、深度优先搜索(DFS)+回溯
最基本的板子:
void DFS(int x,int y) { if (x,y都与目标点相同) { 得到一个解; } else { for (int i = 1; i <= 四个方向; i++) if (满足进一步搜索条件) { 为进一步搜索所需要的状态打上标记; DFS(to_x, to_y); 恢复到打标记前的状态;//也就是回溯一步 } } }
适用类型①:求可行解数量
https://www.luogu.org/problemnew/show/P1605
#include <iostream> using namespace std; //上下左右 int direction[4][2] = { {-1,0},{1,0},{0,-1},{0,1} }; int m[10][10]; int N, M, T, cnt; int SX, SY, EX, EY; void DFS(int x, int y) { if (x < 1 || x > N || y < 1 || y > M) return; if (x == EX && y == EY) { cnt++; return; } for (int i = 0; i < 4; i++) { if (m[x + direction[i][0]][y + direction[i][1]] != 1) { m[x + direction[i][0]][y + direction[i][1]] = 1; DFS(x + direction[i][0], y + direction[i][1]); m[x + direction[i][0]][y + direction[i][1]] = 0; } } } int main() { cin >> N >> M >> T; cin >> SX >> SY >> EX >> EY; for (int i = 0; i < T; i++) { int x, y; cin >> x >> y; m[x][y] = 1; } m[SX][SY] = 1; DFS(SX, SY); cout << cnt << endl; return 0; }
适用类型②:输出所有可行解
例题:https://www.luogu.org/problemnew/show/P1238
这类题目需要注意的是,要知道搜索前进方向的顺序,比如本题是:上左右下。
如果题目够严谨的话,一定会写出来的,但如果没写的话,只能根据题目样例去判断。
#include <iostream> using namespace std; //上左右下 int direction[4][2] = { {-1,0},{0,-1},{0,1},{1,0} }; int m[20][20]; int path[250][2]; int N, M, T, cnt; int SX, SY, EX, EY; void DFS(int x, int y,int k) { if (x < 1 || x > M || y < 1 || y > N) return; if (x == EX && y == EY) { cnt++; for (int i = 0; i < k; i++) { cout << "(" << path[i][0] << "," << path[i][1] << ")"; if (i != k - 1)cout << "->"; } cout << endl; return; } for (int i = 0; i < 4; i++) { int tox = x + direction[i][0], toy = y + direction[i][1]; if (m[tox][toy] == 1) { m[tox][toy] = 0; path[k][0] = tox; path[k][1] = toy; DFS(tox, toy, k + 1); m[tox][toy] = 1; } } } int main() { cin >> M >> N; for (int i = 1; i <= M; i++) { for (int j = 1; j <= N; j++) { cin >> m[i][j]; } } cin >> SX >> SY >> EX >> EY; m[SX][SY] = 0; path[0][0] = SX, path[0][1] = SY; DFS(SX, SY, 1); if (cnt == 0)cout << "-1" << endl; return 0; }
注意:
- 当 m , n 较大时,无法胜任,撑死在15左右就嗝屁了(还得是迷宫中障碍的位置比较配合的情况,一般大于10,就要慎重考虑该不该用DFS了)。
- 搜索前进方向的顺序是可能会影响到效率的,如果起点在左上部分,终点在右下部分,理想情况下,优先选择右方向和下方向,可以根据起点和终点的位置关系,调整方向数组的顺序,效率会更高。
- 找到的第一条解,不一定是最短的,应该说一般都不是。
2、广度优先搜索(BFS)
适用类型①:最短路径的长度
题目链接:走迷宫
#include <iostream> #include <stdio.h> #include <queue> using namespace std; #define PAIR make_pair //上下左右 int direction[4][2] = { {-1,0},{1,0},{0,-1},{0,1} }; char m[20][20]; int cnt[250]; int head = 0, tail = 1; int startx = 0, starty = 1, endx = 9, endy = 8; void BFS(int x, int y) { queue<pair<int, int>>q; q.push(PAIR(x, y)); while (!q.empty()) { x = q.front().first; y = q.front().second; q.pop(); for (int i = 0; i < 4; i++) { int tox = x + direction[i][0], toy = y + direction[i][1]; if (m[tox][toy] == '.' && (tox >= startx && tox <= endx && toy >= starty && toy <= endy)) { if (tox == endx && toy == endy) { cout << cnt[head] + 1 << endl;//获得当前的层数 return; } //cnt[head]记录当前到了第几层BFS cnt[tail++] = cnt[head] + 1; m[tox][toy] = '#'; q.push(PAIR(tox, toy)); } } head++; } } int main() { while (true) { head = 0, tail = 1; for (int i = 0; i < 10; i++) { for (int j = 0; j < 10; j++) { if (scanf("%c", &m[i][j]) == EOF) return 0; } getchar(); } m[startx][starty] = '#'; BFS(startx, starty); } return 0; }
适用类型②:找到最短的一条路径
题目链接:http://poj.org/problem?id=3984
注意:
①:BFS搜到的第一条一定是最短的,但是最短的不一定只有一条。题目未说明有唯一解的,要注意题目对解的要求。
②:如果不止一个解,一般题目会给定按照字典序(上下左右用U D L R表示)、优先向某个方向等要求,输出指定解。
③:BFS没有办法想DFS那样直接把路径存下来;只能把每个点的前驱记下来,这样最后到了终点,得到的路径正好是是反过来的。两种选择,1、自行处理倒过来输出。2、BFS直接倒过来搜,即从终点向起点搜,负负得正。
#include <iostream> #include <queue> using namespace std; #define PAIR make_pair //上下左右 int direction[4][2] = { {-1,0},{1,0},{0,-1},{0,1} }; int m[20][20]; int path[15][15][2]; void BFS(int x, int y) { queue<pair<int, int>>q; q.push(PAIR(x, y)); while (!q.empty()) { x = q.front().first; y = q.front().second; q.pop(); for (int i = 0; i < 4; i++) { int tox = x + direction[i][0], toy = y + direction[i][1]; if (m[tox][toy] == 0 && (tox >= 0 && tox <= 4 && toy >= 0 && toy <= 4)) { if (tox == 0 && toy == 0) { cout << "(" << 0 << ", " << 0 << ")" << endl; while (!(x == 4 && y == 4)) { cout << "(" << x << ", " << y << ")" << endl; int from_x = path[x][y][0]; int from_y = path[x][y][1]; x = from_x; y = from_y; } cout << "(" << 4 << ", " << 4 << ")" << endl; return; } m[tox][toy] = 1; path[tox][toy][0] = x; path[tox][toy][1] = y; q.push(PAIR(tox, toy)); } } } } int main() { for (int i = 0; i < 5; i++) { for (int j = 0; j < 5; j++) { cin >> m[i][j]; } } m[4][4] = 0; BFS(4, 4); return 0; }
优化:双向BFS
正向BFS,与反向BFS用不同的值取标记地图,第一次相遇时(某一方发现对方的标记值),一定是一条最短的路径。这个时候,path中记录着两段方向相反的路径,输出的时候需要处理。
同样的,如果题目要求是按照某种顺序、优先某个方向;那么反向的BFS只要反着来就行了。
#include <iostream> #include <stack> #include <queue> using namespace std; #define PAIR make_pair int direction[4][2] = { {-1,0},{1,0},{0,-1},{0,1} }; int m[20][20]; int path[15][15][2]; bool flag = false; int SX = 0, SY = 0, EX = 4, EY = 4; void move_one_step(queue<pair<int,int>>&q,int sign) { int x = q.front().first, y = q.front().second; q.pop(); for (int i = 0; i < 4; i++) { int tox = x + direction[i][0], toy = y + direction[i][1]; if (m[tox][toy] == 0 && (tox >= SX && tox <= EX && toy >= SX && toy <= EY)) { m[tox][toy] = sign; path[tox][toy][0] = x; path[tox][toy][1] = y; q.push(PAIR(tox, toy)); } //发现对方标记值 else if (m[tox][toy] == -sign) { int tempx = tox, tempy = toy; /*输出 起点 到相遇点*/ stack<pair<int, int>>s; while (!(tox == 0 && toy == 0)) { int t1 = tempx, t2 = tempy; s.push(PAIR(tempx, tempy)); tempx = path[t1][t2][0]; tempy = path[t1][t2][1]; } while (!s.empty()) { int xx = s.top().first, yy = s.top().second; s.pop(); cout << "(" << xx << ", " << yy << ")" << endl; } /*----------------*/ /*从相遇点到终点*/ tempx = x, tempy = y; while (!(tempx == 4 && tempy == 4)) { int t1 = tempx, t2 = tempy; cout << "(" << tempx << ", " << tempy << ")" << endl;; tempx = path[t1][t2][0]; tempy = path[t1][t2][1]; } flag = true; return; } } } void BFS(queue<pair<int, int>>&f, queue<pair<int, int>>&r) { int i = 1; while ((!f.empty() || !r.empty()) && !flag) { if (i & 1) move_one_step(f, 2); else move_one_step(r, -2); i++; } } int main() { queue<pair<int, int>>f, r; for (int i = 0; i < 5; i++) { for (int j = 0; j < 5; j++) { cin >> m[i][j]; } } f.push(PAIR(SX, SY)); r.push(PAIR(EX, EY)); m[EX][EY] = -2; m[SX][SY] = 2; cout << "(" << SX << ", " << SY << ")" << endl; BFS(f, r); cout << "(" << EX << ", " << EY << ")" << endl; return 0; }
3、A*搜索
A*找到的第一个解不一定是最短的,所以A*不能用来去找最短路径(在有多解的情况下);
A*的搜索特性限制,如果用来输出所有可行解,就没有使用的意义了。
所以其实迷宫题并不适合用A*解,除非题目要求比较特殊(只有一条路),仅做参考。
采取
适用类型:找唯一的路径
#include <iostream> #include <queue> using namespace std; #define PAIR make_pair //上下左右 int direction[4][2] = { {-1,0},{1,0},{0,-1},{0,1} }; int sx = 0, sy = 0, ex = 4, ey = 4; class Node { public: int x, y, g, h; Node(int x, int y,int g){ this->x = x; this->y = y; this->g = g; h = abs(ex - x) + abs(ey - y); } bool operator<(Node n)const { return n.g + n.h < g + h; } }; int m[20][20]; int path[15][15][2]; int head = 0, tail = 1; int cnt[255]; void BFS(int x, int y) { priority_queue<Node>q; //queue<pair<int, int>>q; q.push(Node(x, y, cnt[head])); while (!q.empty()) { x = q.top().x; y = q.top().y; q.pop(); if (x == 0 && y == 0) { while (true) { cout << "(" << x << ", " << y << ")" << endl; if (x == 4 && y == 4)break; int from_x = path[x][y][0]; int from_y = path[x][y][1]; x = from_x; y = from_y; } return; } for (int i = 0; i < 4; i++) { int tox = x + direction[i][0], toy = y + direction[i][1]; if (m[tox][toy] == 0 && (tox >= 0 && tox <= 4 && toy >= 0 && toy <= 4)) { cnt[tail++] = cnt[head] + 1; m[tox][toy] = 1; path[tox][toy][0] = x; path[tox][toy][1] = y; q.push(Node(tox, toy, cnt[head] + 1)); } } head++; } } int main() { for (int i = 0; i < 5; i++) { for (int j = 0; j < 5; j++) { cin >> m[i][j]; } } m[4][4] = 0; BFS(4, 4); return 0; }