DFS是深度优先遍历,BFS是广度优先遍历,二者各有它擅长的地方,要根据题目分析要使用哪种
介绍
就跟它们的名字一样,DFS是指在某棵树里面遍历是按照深度优先的,也就是说会从根节点一直走到叶子节点,然后回溯,继续走下一条路,直到遍历完数;而BFS是在某棵树里面按照层次遍历,比如说先遍历根节点,然后第一层、第二层......直到遍历结束。
比如说下面这幅图:
使用DFS:A->B->D->H->I->C->E->J->F->G->K
使用BFS:A->B->C->D->E->F->G->H->I->J->K
怎么用
那么怎么用代码来实现深度和广度优先呢?
DFS
我们可以使用数组+链表的方式来存储,比如说上面那副图,把A节点存到数组里面,然后让这个A节点以单链表的形式指向B节点和C节点,存储它们的下标,然后把B节点存到数组中,在按照链表方式存储D节点的下标,其他的以此类推,遍历就会按照这样的方式:先查看根节点链表中是否有节点,如果有,继续向下遍历,直到叶子节点,然后回溯,往其他路径继续向下遍历,直到全部遍历完成。这里有一个技巧,可以设置一个跟数组同样长度的bool类型数组,用来保存某个节点是否已经遍历过,这样如果在图中也可以让每个节点只遍历一次。详细的可以看下面的例题。
模板
int dfs(int u)
{
st[u] = true; // st[u] 表示点u已经被遍历过
for (int i = h[u]; i != -1; i = ne[i])
{
int j = e[i];
if (!st[j]) dfs(j);
}
}
BFS
广度优先遍历我们可以使用队列,首先让根节点入队列,然后往里面存它的子节点,然后再从队列中拿第一个节点,查看是否有子节点,如果有就继续入队列,没有的话就再从队列头拿节点,直到队列为空,此时就已经遍历完成了。
模板
queue<int> q;
st[1] = true; // 表示1号点已经被遍历过
q.push(1);
while (q.size())
{
int t = q.front();
q.pop();
for (int i = h[t]; i != -1; i = ne[i])
{
int j = e[i];
if (!st[j])
{
st[j] = true; // 表示点j已经被遍历过
q.push(j);
}
}
}
例题
DFS
给定一个整数 n,将数字 1∼n 排成一排,将会有很多种排列方法。
现在,请你按照字典序将所有的排列方法输出。
输入格式
共一行,包含一个整数 n。
输出格式
按字典序输出所有排列方案,每个方案占一行。
数据范围
1≤n≤7
输入样例:
3
输出样例:
1 2 3
1 3 2
2 1 3
2 3 1
3 1 2
3 2 1
分析:这道题我们可以这么想,从1-n选一个数,首先遍历它,然后把它设置为已经遍历,这样下次就可以根据这个来判断是否可以使用这个数,比如说首先用一个循环从1到n依次遍历,先遍历1,然后在st这个数组中把下标为1的地方设置为true,来记录1这个数已经使用过了,然后下一次再次从1到n遍历,从1开始,发现它已经使用过了,然后向下遍历,使用2,然后再把它设置为true,依次类推,直到所有的数都已经使用了。
代码:
#include<iostream>
using namespace std;
const int N = 10;
int path[N]; // 用来保存当前路径
int st[N]; // 用来保存某个数是否已经使用过了
int n;
void dfs(int u){
if(n == u){
for(int i = 0; i < n; i++) cout << path[i] << " ";
cout << endl;
return;
}
for(int i = 1; i <= n; i++){ // 这里i从1开始是因为保存的整数是从1开始的
if(!st[i]){
path[u] = i; // 如果整数i还没被使用过,就把i填充到path[u]的位置
st[i] = true; // 将当前整数设置为已经使用过
dfs(u + 1); // 继续填充下一个数
st[i] = false; // 回溯后要把这个数设置为未使用
}
}
}
int main(){
cin >> n;
dfs(0);
return 0;
}
BFS
给定一个 n×m 的二维整数数组,用来表示一个迷宫,数组中只包含 0 或 1,其中 0 表示可以走的路,1 表示不可通过的墙壁。
最初,有一个人位于左上角 (1,1) 处,已知该人每次可以向上、下、左、右任意一个方向移动一个位置。
请问,该人从左上角移动至右下角 (n,m) 处,至少需要移动多少次。
数据保证 (1,1) 处和 (n,m) 处的数字为 0,且一定至少存在一条通路。
输入格式
第一行包含两个整数 n 和 m。
接下来 n 行,每行包含 m 个整数(0 或 1),表示完整的二维数组迷宫。
输出格式
输出一个整数,表示从左上角移动至右下角的最少移动次数。
数据范围
1≤n,m≤100
输入样例:
5 5
0 1 0 0 0
0 1 0 1 0
0 0 0 0 0
0 1 1 1 0
0 0 0 1 0
输出样例:
8
分析:
根据题目直接就能看出这是要求最短路径的问题,那么就使用bfs,因为它是一层一层遍历的,当某一层首先满足条件的时候那一层就是最短的。针对这道题,我们可以这么想,从下标[0, 0]开始,用g数组来保存这个迷宫,d数组用来保存从出发点到某一位置需要的步数,然后创建一个保存pair<int, int>的队列,首先让出发点入队列,用hh指向队列的头,tt指向队列的尾,开始循环直到 hh <= tt不满足为止,先取出队列的头,分别模拟上下左右四个情景,然后看他是否满足条件,这个条件是是否有出界,g数组迷宫那个点是否可以走以及那个点是否已经走过了,如果满足条件了,d数组那个位置就设置成上一个位置 + 1,这样也相对应表示了这个点已经到过了,然后把这个点入队列,直到走完迷宫。
题目要求的是到右下角最短距离,我们做的最终d数组的[n - 1, m - 1]这个位置就是答案。
具体见下面代码。
代码:
#include<cstring>
#include<iostream>
using namespace std;
typedef pair<int, int> PII;
const int N = 101;
int n, m;
int g[N][N], d[N][N]; // g数组记录迷宫,d数组记录走到那里要多少步
PII q[N * N]; // 队列
int bfs(){
int hh = 0, tt = 0;
q[0] = {0, 0}; // 把{0, 0}保存到队列的第一个
memset(d, -1, sizeof d); // 将d数组中所有元素重置为-1,表明还没有到这里
d[0][0] = 0; // 起点到起点距离是0
int dx[4] = { -1, 0, 1, 0 }, dy[4] = { 0, -1, 0, 1 }; // 用这个来模拟上下左右
while(hh <= tt){
auto t = q[hh++];
for(int i = 0; i < 4; i++){
int x = t.first + dx[i], y = t.second + dy[i];
if(x >= 0 && x < n && y >= 0 && y < m && g[x][y] == 0 && d[x][y] == -1){ // 如果这里满足条件,就说明可以走这里,并且入队列
d[x][y] = d[t.first][t.second] + 1;
q[++tt] = { x, y };
}
}
}
return d[n - 1][m - 1];
}
int main(){
cin >> n >> m;
for(int i = 0; i < n; i++)
for(int j = 0; j < m; j++)
cin >> g[i][j];
cout << bfs() << endl;
return 0;
}
以上就是本次介绍的全部了,如果有错误或者不足欢迎指出