今天学习了图的两种遍历方法:深度优先遍历(DFS)和广度优先遍历(BFS)。
深度优先遍历(Depth First Search)
深度优先遍历,又称深度优先搜索,可以简称为DFS。DFS就是所谓的“不撞南墙不回头算法”,一直到无路可走的时候才会回头,然后重新选择一条路继续走。
DFS步骤
1.递归下去
2.回溯上来
顾名思义,深度优先,则是以深度为准则,先一条路走到底,直到达到目标。这里称之为递归下去。如果既没有达到目标又无路可走了,那么则退回到上一步的状态,走其他路。这便是回溯上来。
DFS解决全排列
在学习DFS算法时,我们常常会用DFS来解决全排列问题,这也是一道引导题。
题目描述:
输入一个数n(1<=n<=5),输出n的全排列
代码实现
#include <iostream>
using namespace std;
int a[10], n;
bool v[10];
//全局变量未初始化的初值为0
void dfs(int step)
{
if (step == n + 1) { //完成一种排列方式
for (int i = 1; i <= n; ++i)
cout << a[i];
cout << "\n";
return ; //注意这个 return 它的作用不是返回主函数,而是返回上一级的dfs函数
}
for (int i = 1; i <= n; ++i) {
if (v[i] == false) { //该数字还没有被用到
a[step] = i; //将该数字放入第step个盒子
v[i] = true; //该数字被使用,打上标记
dfs(step + 1); //放置下一个盒子
v[i] = 0; //回溯,还原状态,重新选择
}
}
return ;
}
int main() {
cin >> n;
dfs(1); //dfs函数的开始
return 0;
}
其重点在于递归和状态回溯
广度优先遍历(Breadth First Search)
广度优先遍历,又称广度优先搜索,可以简称为BFS。BFS类似于二叉树中的层序遍历,先遍历完这一层的所有对象,如果没有找到答案,则会继续往下一层搜索,逐步求解。
BFS步骤
1.选取
2.标记
DFS的特点是:不管有多少条岔路,先一条路走到底,不成功就返回上一个路口然后再选择下一条岔路。而BFS在面临一个路口时,会把所有的岔路口都记下来,即为选取,然后选择其中一个进入,并将它的分路情况记录下来,这便是标记。如果此时没有找到答案,就会再返回来进入另外一个岔路,并重复这样的操作。
具体可参考下图
从黑点到红点,此为它每一步可以经过的位置,它的下一步即为它的扩展。
一般来说BFS适用于解决两点之间的最短路径问题。
相关题目
1.迷宫
题解:该题是一道很经典的迷宫问题,用深搜和宽搜都能实现,我这里用的是深搜,具体实现如代码所示
AC Code:
#include <iostream>
using namespace std;
bool maze[6][6], tag[6][6]; //maze代表迷宫(true表示有障碍物,false表示为空地),tag代表走过的标记(true表示已经走过了,false表示没有走过)
int N, M, T, SX, SY, FX, FY, p, q, ans;
int _next[4][2] = {{0, 1}, {1, 0}, {0, -1}, {-1, 0}}; // _next表示的是下一步
void dfs(int x, int y)
{
if (x == FX && y == FY) {
ans++; //ans为总方案数,总数加1
return ;
}
for (int i = 0; i < 4; ++i) //顺时针试探,右,下,左,上 四个方向
{
int tx = x + _next[i][0], ty = y + _next[i][1]; //(tx,ty)代表下一步位置的坐标
if (tx < 1 || tx > N || ty < 1 || ty > M) //超出迷宫范围
continue;
if (tag[tx][ty] == false && maze[tx][ty] == false) { //判断下一步没有走过并且没有障碍物
tag[x][y] = true; //走过的地方打上标记
dfs(tx,ty);
tag[x][y] = false; //回溯,还原状态
}
}
return ;
}
int main() {
ios::sync_with_stdio(false);
cin.tie(0);
cin >> N >> M >> T;
cin >> SX >> SY >> FX >> FY;
while (T--) {
cin >> p >> q; //(p, q)代表障碍点的坐标
maze[p][q] = true; // 将障碍点设置为true
}
dfs(SX, SY);
cout << ans;
return 0;
}
题目链接: https://www.luogu.com.cn/problem/P1605
2.马的遍历
题解:也是很经典的问题,可以用STL模板库 <queue> 来宽搜这道题,具体实现如代码所示
AC Code:
#include <iostream>
#include <queue>
#include <iomanip>
#include <cstring>
using namespace std;
struct point
{
int x;
int y;
int step;
};
constexpr int inf = -1;
constexpr int N = 401;
int n, m, x, y;
int dx[8] = {1, 2, 2, 1, -1, -2, -2, -1}; //马走日字,共有八个方向
int dy[8] = {2, 1, -1, -2, -2, -1, 1, 2};
int a[N][N]; //用来标记到某个点所用的步数
bool v[N][N]; //用来标记某个点是否已经被访问
queue<point> q;
void bfs()
{
point st;
st.x = x; st.y = y; st.step = 0;
q.push(st); //初始化队列
v[x][y] = true;
while (!q.empty())
{
int x = q.front().x, y = q.front().y, step = q.front().step;
a[x][y] = step; //到达(x,y)点所需的步数
for (int k = 0; k < 8; ++k) { //八个方向试探
int tx = x + dx[k], ty = y + dy[k];
if (tx >= 1 && tx <= n && ty >= 1 && ty <= m && v[tx][ty] == false) { //-判断下一步的位置是否越界并且有没有被访问过
point temp;
temp.x = tx; temp.y = ty; temp.step = q.front().step + 1; //对当前位置进行扩展
q.push(temp); //扩展的点入队
v[tx][ty] = true; //标记下一步的位置已访问
}
}
q.pop(); //当前点出队
}
}
int main() {
ios::sync_with_stdio(false);
cin.tie(0);
cin >> n >> m >> x >> y;
memset(a, inf, sizeof(a)); //将所有步数设置为 inf(即-1),这样后续没有改变值的点就是无法到达的点
bfs();
for (int i = 1; i <= n; ++i) {
for (int j = 1; j <= m; ++j) {
cout << left << setw(5) << (a[i][j] != inf ? a[i][j] : -1);
}
cout << "\n";
}
return 0;
}
题目链接: https://www.luogu.com.cn/problem/P1443
在DFS中我们说关键点是递归以及回溯,在BFS中,关键点则是状态的选取和标记。另外,这两种遍历方法都运用了数据结构的实现。(可点击 “数据结构” 了解 栈与队列 的具体使用方法及细节)
DFS用递归的形式,用到了栈结构,先进后出。
BFS选取状态,用队列的形式,先进先出。
由于本人水平有限,要了解更多详细内容的话,可点击如下链接进行跳转