DFS与BFS

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;
}

以上就是本次介绍的全部了,如果有错误或者不足欢迎指出

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值