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 maze[10][10];
int N, M, T, cnt, SX, SY, FX, FY;
void DFS(int x, int y) {
if (x < 1 || x > N || y < 1 || y > M) return;
if (x == FX && y == FY) {
cnt++;
return;
}
for (int i = 0; i < 4; i++) {
int to_x = x + direction[i][0], to_y = y + direction[i][1];
if (maze[to_x][to_y] != 1) {
maze[to_x][to_y] = 1;
DFS(to_x, to_y);
maze[to_x][to_y] = 0;
}
}
}
int main() {
cin >> N >> M >> T;
cin >> SX >> SY >> FX >> FY;
for (int i = 0; i < T; i++) {
int x, y;
cin >> x >> y;
maze[x][y] = 1;
}
maze[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 m , n m,n 较大时,无法胜任,最多在15左右就不行了(还得是迷宫中障碍的位置比较配合的情况,一般大于10,就要慎重考虑该不该用DFS了)。
- 搜索前进方向的顺序是可能会影响到效率的,如果起点在左上部分,终点在右下部分,理想情况下,优先选择右方向和下方向,可以根据起点和终点的位置关系,调整方向数组的顺序,效率会更高。
- 找到的第一条解,不一定是最短的,应该说一般都不是。
2、广度优先搜索(BFS)
适用类型①:最短路径的长度
题目链接:走迷宫
#include <iostream>
#include <queue>
#include <fstream>
using namespace std;
struct Coord {
int x, y, level;
Coord(int x, int y, int level) :x(x), y(y), level(level) {};
};
char maze[15][15];
int direction[4][2] = { {-1,0},{1,0},{0,-1},{0,1} };
int SX = 1, SY = 2, FX = 10, FY = 9;
int BFS() {
queue<Coord>q;
maze[SX][SY] = '#';
q.push(Coord(SX, SY, 0));
while (!q.empty()) {
Coord p = q.front(); q.pop();
int x = p.x, y = p.y;
for (int i = 0; i < 4; i++) {
int tox = x + direction[i][0], toy = y + direction[i][1];
if (tox == FX && toy == FY)
return p.level + 1;
//这里也可以在迷宫边上加一圈全是 '#' 的边界
if (maze[tox][toy] == '.' && (tox >= 1 && tox <= 10 && toy >= 1 && toy <= 10)) {
maze[tox][toy] = '#';
q.push(Coord(tox, toy, p.level + 1));
}
}
}
return -1;
}
int main() {
while (true) {
for (int i = 1; i <= 10; i++) {
for (int j = 1; j <= 10; j++) {
if (scanf("%c", &maze[i][j]) == EOF) return 0;
}
getchar();
}
int res = BFS();
cout << res << endl;
}
return 0;
}
适用类型②:找到最短的一条路径
题目链接:http://poj.org/problem?id=3984
注意:
①:BFS搜到的第一条一定是最短的,但是最短的不一定只有一条。题目未说明有唯一解的,要注意题目对解的要求。
②: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*解,除非题目要求比较特殊(只有一条路),仅做参考。
采取 h ( n ) = ∣ end. x − n ⋅ x ∣ + ∣ end. y − n ⋅ y ∣ \mathrm{h}(\mathrm{n})=\mid \text { end. } \mathrm{x}-\mathrm{n} \cdot \mathrm{x}|+| \text { end. } \mathrm{y}-\mathrm{n} \cdot \mathrm{y} \mid h(n)=∣ end. x−n⋅x∣+∣ end. y−n⋅y∣
适用类型:找唯一的路径
#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;
}