DFS_LeetCode(一)

210 阅读2分钟

深度优先搜索

  • 概述

    • 在搜索到一个新的节点时,立即对新节点进行遍历

      • 遍历:进行相关操作
    • 遍历采用先入后出的栈或者等价的递归进行实现;

      • 递归
        • 主函数:提取出满足条件的点
        • 辅函数:对满足条件的点进行处理
    • 对树结构遍历时,总是遍历新节点,看起来就像是一直往深的地方遍历,所以叫深搜;

    • 遍历图示: image-20211122180534791

    • 应用场景:

      • 检测环路(与图论结合) 记录遍历点的父节点,当一个节点二次遍历时,其父节点不同,那么就存在环; 拓扑排序亦可检测环路:存在入度不为零的点,那么就存在环
      • 记忆化搜索: 对已经搜索过的点做标记(开一个跟原数据矩阵相同大小的Boolean类型矩阵),防止重复搜索
  • 实例:LeetCode695

    • 题目描述:

      image-20211122181508379

      image-20211122181542124

    • 概述:

      • 采用递归方法,配合主辅函数;
      • 主函数:遍历所有的点,挑选出满足搜索条件的点
      • 辅函数:处理深搜的递归调用
    • 实现细节 :

      • 思路:这个是每次遍历得到的结果,都是一条路径
      • 方向问题:
        • 对于四个方向的遍历,使用数组并赋值为[-1,0,1,0,-1] (这个是对称的,很好记)
      • 辅函数实现细节:
        • 边界条件的判定:
          • 在本次搜索之前判断:先判断是否越界(在合法范围内搜索),判断放在递归函数之前
          • 在下一次搜索开始时判断,判断放在辅函数第一行
    • 代码示例:(第一种递归方式)

       class Solution {
       public:
           //方向矩阵
           vector<int>direction{-1, 0, 1, 0, -1};
           //主函数
           int maxAreaOfIsland(vector<vector<int>>& grid) {
               //当原矩阵为空,则退出并返回0
               if(grid.empty() || grid[0].empty()) return 0;
               int max_area = 0;
               //遍历所有的点,抽取满足条件的点
               for(int i =0;i < grid.size(); i++){
                   for(int j=0;j<grid[0].size();j++){
                       if(grid[i][j]==1){
                           max_area = max(max_area,dfs(grid,i,j));
                       }
                   }
               }
               return max_area;
           }
       ​
           //辅函数
           int dfs(vector<vector<int>>& grid, int r, int c){
               //这一行不能删掉,删了就会爆栈,为什么?
               if(grid[r][c] == 0) return 0;
               //遍历操作,对结果写入
               grid[r][c] = 0;
               int x,y,area = 1;
               //遍历方向
               for(int i =0;i<4;++i){
                   x = r + direction[i], y = c + direction[i+1];
                   if( x >= 0 && x < grid.size() && y>=0 && y < grid[0].size()){
                       area +=dfs(grid, x, y);
                   }
               }
               return area;
           }
       };
      
    • 代码示例:(第二种递归出口)

       class Solution {
       public:
           vector<int>direction{-1, 0, 1, 0, -1};
           //主函数
           int maxAreaOfIsland(vector<vector<int>>& grid) {
               if(grid.empty() || grid[0].empty()) return 0;
               int max_area = 0;
               for(int i =0;i < grid.size(); i++){
                   for(int j=0;j<grid[0].size();j++){
                       if(grid[i][j]==1){
                           max_area = max(max_area,dfs(grid,i,j));
                       }
                   }
               }
               return max_area;
           }
       ​
           //辅函数
           int dfs(vector<vector<int>>& grid, int r, int c){
               if(r<0 || r>=grid.size() || c<0 || c>=grid[0].size() || grid[r][c] == 0)    return 0;
       ​
               grid[r][c] = 0;
               return 1+dfs(grid,r+1,c)+dfs(grid,r-1,c)+dfs(grid,r,c+1)+dfs(grid,r,c-1);
           }
       };
      
    • 代码示例(栈实现,在工程中常用此法)

       class Solution {
       public:
           vector<int>direction{-1, 0, 1, 0, -1};
           int maxAreaOfIsland(vector<vector<int>>& grid) {
               int m = grid.size(),n=m?grid[0].size():0,local_area,area = 0,x,y;
               for(int i =0;i<m;++i){
                   for(int j=0;j<n;j++){
                       if(grid[i][j]){
                           local_area = 1;
                           grid[i][j]=0;
                           stack<pair<int,int>> island;
                           island.push({i,j});
                           while(!island.empty()){
                               auto [r,c] = island.top();
                               island.pop();
                               for(int k=0;k<4;++k){
                                   x = r + direction[k],y = c + direction[k+1];
                                   if(x>=0 && x<m && y >=0 && y<n && grid[x][y] == 1){
                                       grid[x][y] = 0;
                                       ++local_area;
                                       island.push({x,y});
                                   }
                               }
                           }
                           area = max(area,local_area);
                       }
                   }     
               }
               return area;
           }
       };
      
    • 分析与总结:

      • 时间分析:

        image-20211122185836485

      • 总结:

        • 一般采用递归且配合第一种函数出口
        • 深搜的本质是搜一颗树;
    • 实例:LeetCode 547:

    • 回溯法:

      • 概述:
        • 常用于需要记录节点状态的深搜,在排列,组合,选择方面使用较多;
        • 当发现搜索的节点(子节点)并不是自己的目标,需要回退到上一步的节点继续搜索,并且将当前节点的状态还原;
        • 始终只对原来的图进行搜索,不需要额外空间
      • 回溯法修改
        • 修改最后一位输出:排列组合问题
        • 修改访问标志:矩阵中搜字符串
    • 回溯实例:LeetCode46

      • 概述:

        • 全排列问题
      • 题目分析:

        • 搜索类型模板题;
        • 设共有3位数字,当可以将第一位数字,与第二位,或者第三位交换;对第二位数字,可以与第三位数字交换,注意实现思路
      • 代码实例:

         class Solution {
         public:
             //主函数
             vector<vector<int>> permute(vector<int>& nums) {
                 vector<vector<int>> ans;
                 backtracking(nums,0,ans);
                 return ans;
             }
             //辅函数
             void backtracking(vector<int>& nums,int level, vector<vector<int>>& ans){
                 if(level == nums.size()-1){
                     ans.push_back(nums);
                     return;
                 }
                 for(int i  = level; i<nums.size();i++){
                     swap(nums[i],nums[level]);
                     backtracking(nums,level+1,ans);
                     swap(nums[i],nums[level]);
                 }
             }
         };