当前位置:   article > 正文

深度优先搜索(DFS),看这一篇就够了。

深度优先

一,定义:

深度优先搜索的思路和树的先序遍历很像,下面是百度百科上的定义:

深度优先遍历图的方法是,从图中某顶点v出发:

(1)访问顶点v;

(2)依次从v的未被访问的邻接点出发,对图进深度优先遍历;直至图中和v有路径相通的顶点都被访问;

(3)若此时图中尚有顶点未被访问,则从一个未被访问的顶点出发,重新进行深度优先遍历,直到图中所有顶点均被访问过为止。 当然,当人们刚刚掌握深度优先搜索的时候常常用它来走迷宫.事实上我们还有别的方法,那就是广度优先搜索(BFS).

 对于定义的理解,可以结合斐波那契数列(虽然用递归来写斐波那契是一种很糟糕的写法)来进行理解,如下图:

其中,右边这个树上的顺序是这样的:

        可以结合遍历的思想来理解DFS;

        DFS的题目大致可以分为两类:

1,对图的连通性进行检验:如迷宫问题,图的条件搜索。

2,DFS搜索顺序和规则问题,通过你穷举所有答案,找出符合条件的解。即爆搜问题。

         看到这里,你可能会有些疑惑具体是怎样的问题,本文就针对DFS的原理进行,常见的题型进行了总结,附上代码和解题思路。

二,原理与分析

1,DFS连通性分析:

在测试连通性是,DFS的思路是与人们的思想是一致的,在一条路上,我是否可以在这条路上一直走下去,如果走不通,那我就返回原来的节点,换个方向,再沿着一条路走下去,直到成功。

针对连通性问题,我们还可以再进行分类 :

1,无需回溯

        在这种问题,只需要在每一步中,将搜索到的节点抛弃掉,对于当前搜索到的节点进行计数,最终统计所有能到达的点。

        下面给出两个例题:
        例1,红与黑(简单)

  1. /*
  2. * @Author: your name
  3. * @Date: 2022-02-11 13:39:15
  4. * @LastEditTime: 2022-02-11 13:56:51
  5. * @LastEditors: Please set LastEditors
  6. * @Description: 打开koroFileHeader查看配置 进行设置: https://github.com/OBKoro1/koro1FileHeader/wiki/%E9%85%8D%E7%BD%AE
  7. * @FilePath: \All code\26.cpp
  8. */
  9. #include<bits/stdc++.h>
  10. using namespace std;
  11. #define MAXN 100
  12. char mp[MAXN][MAXN];
  13. bool vis[MAXN][MAXN];
  14. int W, H;
  15. int ans;
  16. int dx[4] = { 0, 1, 0, -1 };
  17. int dy[4] = { 1, 0, -1, 0 };
  18. void init()
  19. {
  20. for (int i = 0; i < MAXN; i++)
  21. {
  22. for (int j = 0; j < MAXN; j++)
  23. {
  24. vis[i][j] = false;
  25. }
  26. }
  27. ans = 1;//由于在初始点位置,所以一开始就是一块黑砖
  28. }
  29. void dfs(int x, int y)//常用DFS套路
  30. {
  31. for (int i = 0; i < 4; i++)
  32. {
  33. int newx = x + dx[i];
  34. int newy = y + dy[i];
  35. if (newx >= 1 && newx <= H && newy >= 1 && newy <= W && mp[newx][newy] == '.' && vis[newx][newy] == false)
  36. {
  37. ans++;
  38. vis[newx][newy] = true;
  39. dfs(newx, newy);
  40. }
  41. }
  42. }
  43. int beginx, beginy;
  44. int main()
  45. {
  46. while (1)
  47. {
  48. cin >> W >> H;
  49. if (W == 0 && H == 0)
  50. {
  51. break;
  52. }
  53. init();
  54. for (int i = 1; i <= H; i++)
  55. for (int j = 1; j <= W; j++)
  56. {
  57. cin >> mp[i][j];
  58. if (mp[i][j] == '@')
  59. {
  60. beginx = i;
  61. beginy = j;
  62. }
  63. }
  64. dfs(beginx, beginy);
  65. cout << ans << endl;
  66. }
  67. return 0;
  68. }

例2,Lake Counting(染色法简单)

  1. /*
  2. * @Author: your name
  3. * @Date: 2022-01-22 21:06:31
  4. * @LastEditTime: 2022-01-22 21:32:59
  5. * @LastEditors: Please set LastEditors
  6. * @Description: 打开koroFileHeader查看配置 进行设置: https://github.com/OBKoro1/koro1FileHeader/wiki/%E9%85%8D%E7%BD%AE
  7. * @FilePath: \All code\9.cpp
  8. */
  9. #include <bits/stdc++.h>
  10. using namespace std;
  11. #define MAXN 105
  12. bool vis[MAXN][MAXN];
  13. char mp[MAXN][MAXN];
  14. int row,cow;
  15. int dx[8] = {0, 0, -1, 1, -1, -1, 1, 1};
  16. int dy[8] = {-1, 1, 0, 0, -1, 1, -1, 1};
  17. void dfs(int x,int y)
  18. {
  19. vis[x][y] = 1;
  20. for(int i =0;i<8;i++)
  21. {
  22. int newx = x+dx[i];
  23. int newy = y+dy[i];
  24. if(newx>=0&&newy>=0&&newx<row&&newy<cow&&mp[newx][newy] == 'W'&&vis[newx][newy] ==0)
  25. {
  26. dfs(newx,newy);
  27. }
  28. }
  29. }
  30. int main()
  31. {
  32. int i, j;
  33. int count =0;
  34. cin >> row >> cow;
  35. for (i = 0; i < row; i++)
  36. for (j = 0; j < cow; j++)
  37. {
  38. cin >> mp[i][j];
  39. }
  40. for (i = 0; i < row; i++)
  41. for (j = 0; j < cow; j++)
  42. {
  43. if(mp[i][j] == 'W'&&vis[i][j] == 0)//碰到一个后,就最这个区域进行DFS,并进行标记
  44. {
  45. dfs(i,j);
  46. count++;
  47. }
  48. }
  49. cout<<count;
  50. return 0;
  51. }

有了这两个粒例子,相信大家开始对DFS有了一个大概的认识。

二,需要回溯:迷宫类问题

        这一类问题就像我在刚刚导论里面提及的,如果这条路不同,则需要设置回溯,进行恢复现场。最经典的莫过于走迷宫问题(简单):

  1. /*
  2. * @Author: your name
  3. * @Date: 2022-01-22 21:06:31
  4. * @LastEditTime: 2022-01-22 22:12:20
  5. * @LastEditors: Please set LastEditors
  6. * @Description: 打开koroFileHeader查看配置 进行设置: https://github.com/OBKoro1/koro1FileHeader/wiki/%E9%85%8D%E7%BD%AE
  7. * @FilePath: \All code\9.cpp
  8. */
  9. #include <bits/stdc++.h>
  10. using namespace std;
  11. #define MAXN 505
  12. bool vis[MAXN][MAXN];
  13. char mp[MAXN][MAXN];
  14. int row, cow;
  15. int dx[4] = {0, 0, -1, 1};
  16. int dy[4] = {-1, 1, 0, 0};
  17. bool flag = false;
  18. void dfs(int x, int y)
  19. {
  20. vis[x][y] = 1;
  21. for (int i = 0; i < 4; i++)
  22. {
  23. int newx = x + dx[i];
  24. int newy = y + dy[i];
  25. if (newx >= 0 && newy >= 0 && newx < row && newy < cow && vis[newx][newy] == 0)
  26. {
  27. if (mp[newx][newy] == '.')
  28. dfs(newx, newy);
  29. if (mp[newx][newy] == 'E')
  30. flag = true;
  31. }
  32. }
  33. }
  34. void init()
  35. {
  36. for (int i = 0; i < row; i++)
  37. for (int j = 0; j < cow; j++)
  38. {
  39. vis[i][j] = 0;
  40. }
  41. }
  42. int main()
  43. {
  44. int i, j;
  45. int count = 0;
  46. int row_s, cow_s;
  47. while (cin >> row >> cow)
  48. {
  49. flag = false;
  50. for (i = 0; i < row; i++)
  51. for (j = 0; j < cow; j++)
  52. {
  53. cin >> mp[i][j];
  54. if (mp[i][j] == 'S')
  55. {
  56. row_s = i;
  57. cow_s = j;
  58. }
  59. }
  60. init();
  61. dfs(row_s, cow_s);
  62. if (flag == true)
  63. {
  64. cout << "Yes" << endl;
  65. }
  66. else
  67. {
  68. cout << "No" << endl;
  69. }
  70. }
  71. return 0;
  72. }

除此之外,还有一个例题,用于计数的:马走日​​​​​​​​​​​​

       这个题只是将迷宫问题的路径选择和成功条件进行了一个改变,也是简单的搜索问题:

  1. /*
  2. * @Author: your name
  3. * @Date: 2022-02-11 14:44:55
  4. * @LastEditTime: 2022-02-11 15:30:56
  5. * @LastEditors: Please set LastEditors
  6. * @Description: 打开koroFileHeader查看配置 进行设置: https://github.com/OBKoro1/koro1FileHeader/wiki/%E9%85%8D%E7%BD%AE
  7. * @FilePath: \All code\28.cpp
  8. */
  9. #include <iostream>
  10. using namespace std;
  11. #define MAXN 15
  12. int mp[MAXN][MAXN];
  13. bool vis[MAXN][MAXN];
  14. int row, cow, newx, newy;
  15. int ans;
  16. int dx[8] = { -2, -2, 2, 2, -1, -1, 1, 1 };
  17. int dy[8] = { 1, -1, 1, -1, -2, 2, -2, 2 };
  18. void init()
  19. {
  20. for (int i = 0; i < MAXN; i++)
  21. {
  22. for (int j = 0; j < MAXN; j++)
  23. {
  24. vis[i][j] = false;
  25. }
  26. }
  27. ans = 0;
  28. }
  29. void dfs(int x, int y, int depth)
  30. {
  31. for (int i = 0; i < 8; i++)
  32. {
  33. int newx = x + dx[i];
  34. int newy = y + dy[i];
  35. if (newx >= 1 && newx <= row && newy >= 1 && newy <= cow && vis[newx][newy] == false)
  36. {
  37. vis[newx][newy] = true;
  38. if (depth == row * cow - 1)//由于这里是newx,并不是已经递归到新一层中,所以这里需要减1
  39. {
  40. ans++;
  41. }
  42. dfs(newx, newy, depth + 1);
  43. vis[newx][newy] = false;
  44. }
  45. }
  46. }
  47. int main()
  48. {
  49. int T;
  50. cin >> T;
  51. while (T--)
  52. {
  53. cin >> row >> cow >> newx >> newy;
  54. init();
  55. vis[newx + 1][newy + 1] = true;
  56. dfs(newx + 1, newy + 1, 1);//这里要注意,我在从第一步开始的时候,就已经算是处在第一层了,不是第0层。
  57. cout << ans << endl;
  58. }
  59. return 0;
  60. }

         我在开始这一道题的时候,就犯下了这样一个错误:将截止条件的层数看错。大家在写代码的时候要注意这个问题。

总结一下DFS的模板

function dfs(当前状态){
    if(当前状态 == 目的状态){
        ···
    }
    for(···寻找新状态){
        if(状态合法){
            vis[访问该点];
            dfs(新状态);
            ?是否需要恢复现场->vis[恢复访问]
        } 
    }
    if(找不到新状态){
        ···
    }
}

 2,穷举解决问题

例:部分和问题:

 

 

  1. #include <bits/stdc++.h>
  2. using namespace std;
  3. int n, k;
  4. int a[35];
  5. bool dfs(int id, int sum)
  6. {
  7. if (sum == k)
  8. return 1;
  9. if (id > n)
  10. return 0;
  11. if (1 == dfs(id + 1, sum + a[id]))
  12. return 1;
  13. if (1 == dfs(id + 1, sum))
  14. return 1;
  15. return 0;
  16. }
  17. int main()
  18. {
  19. cin >> n >> k;
  20. for (int i = 1; i <= n; i++)
  21. {
  22. cin >> a[i];
  23. }
  24. if (1 == dfs(1, 0))
  25. {
  26. puts("YES");
  27. }
  28. else
  29. {
  30. puts("NO");
  31. }
  32. return 0;
  33. }

也很简单,只要一次在每一步中讨论当前这个数是否选取,即可。对于这个问题,如果是想要找出所有相加为k的序列中,个数最少的一个,也可以利用穷举的方法来写。同时也可以利用动态规划的知识来解题。读者可以下去自己思考。

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/喵喵爱编程/article/detail/859392
推荐阅读
相关标签
  

闽ICP备14008679号