深度优先搜索
-
概述
-
在搜索到一个新的节点时,立即对新节点进行遍历
- 遍历:进行相关操作
-
遍历采用先入后出的栈或者等价的递归进行实现;
- 递归
- 主函数:提取出满足条件的点
- 辅函数:对满足条件的点进行处理
- 递归
-
对树结构遍历时,总是遍历新节点,看起来就像是一直往深的地方遍历,所以叫深搜;
-
遍历图示:
-
应用场景:
- 检测环路(与图论结合) 记录遍历点的父节点,当一个节点二次遍历时,其父节点不同,那么就存在环; 拓扑排序亦可检测环路:存在入度不为零的点,那么就存在环
- 记忆化搜索: 对已经搜索过的点做标记(开一个跟原数据矩阵相同大小的Boolean类型矩阵),防止重复搜索
-
-
实例:LeetCode695
-
题目描述:
-
概述:
- 采用递归方法,配合主辅函数;
- 主函数:遍历所有的点,挑选出满足搜索条件的点
- 辅函数:处理深搜的递归调用
-
实现细节 :
- 思路:这个是每次遍历得到的结果,都是一条路径
- 方向问题:
- 对于四个方向的遍历,使用数组并赋值为[-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; } }; -
分析与总结:
-
时间分析:
-
总结:
- 一般采用递归且配合第一种函数出口
- 深搜的本质是搜一颗树;
-
-
-
-
实例: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]); } } };
-
-