《搜索算法——DFS、BFS、回溯》

目录

  • 深搜
    • 200. 岛屿数量
    • 695. 岛屿的最大面积
    • 130. 被围绕的区域
    • 547. 省份数量
    • 417. 太平洋大西洋水流问题
  • 回溯
  • 广搜
    • 111. 二叉树的最小深度
    • 752. 打开转盘锁
  • 深搜与广搜结合
    • 934. 最短的桥

深搜

深搜DFS,在搜索到一个新节点时,立即对该新节点进行遍历,需要用到栈实现,或者使用与栈等价的递归实现。
深搜也可以用来检测环路:记录每个遍历过的节点的父节点,若有一个节点被再次遍历且父节点不同,则说明有环。
有时我们可能会需要对已经搜索过的节点进行标记,以防止在遍历时重复搜索某个节点,这种做法叫做状态记录或者记忆化。

200. 岛屿数量

class Solution {
public:void dfs(vector<vector<char>>& grid,int m,int n,int i,int j){//如果越界或者为海域,退出if(i < 0 || i >= m || j < 0 || j >= n || grid[i][j] == '0') return;//标记为海域grid[i][j] = '0';dfs(grid,m,n,i-1,j);dfs(grid,m,n,i+1,j);dfs(grid,m,n,i,j-1);dfs(grid,m,n,i,j+1);return ;}int numIslands(vector<vector<char>>& grid) {int m = grid.size();int n = grid[0].size();int time = 0;for(int i = 0; i < m; i++){for(int j = 0; j < n; j++){if(grid[i][j] == '1'){dfs(grid,m,n,i,j);time++;}}}return time;}
};

695. 岛屿的最大面积

一般来说,深搜可以分为主函数和辅助函数。
主函数用于遍历所有的所有搜索位置,判断是否可以开始搜索,如果可以即用辅助函数进行搜索。
辅助函数负责深搜的递归调用或者(栈)实现。

class Solution {
public:vector<int> direction {-1,0,1,0,-1};//辅助函数int dfs(vector<vector<int>>& grid, int r, int c){if(grid[r][c] == 0) return 0;//否则清除标志grid[r][c] = 0;//此时岛屿已经有1面积了,接下来进行对四个方向进行深搜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);}//最后返回以r,c为起点的岛屿面积,并且在地图上清除这个岛屿,防止多次搜索。return area;}//主函数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;//否则说明该点为岛屿,面积+1,同时清除记录grid[r][c] = 0;//返回以该点搜索起点的面积结果return 1 + dfs(grid,r-1,c) + dfs(grid,r,c+1) + dfs(grid,r+1,c) + dfs(grid,r,c-1);
}

130. 被围绕的区域

class Solution {
public:void dfs(vector<vector<char>>& board,int m,int n,int i,int j){//递归退出条件if(i < 0 || i >= m || j < 0 || j >= n || board[i][j] == 'X' || board[i][j] == 'A') return;//否则进行标记,然后继续深搜board[i][j] = 'A';dfs(board,m,n,i-1,j);dfs(board,m,n,i+1,j);dfs(board,m,n,i,j-1);dfs(board,m,n,i,j+1);return;}void solve(vector<vector<char>>& board) {int m = board.size();int n = board[0].size();//用dfs将边界的相连的0全部置为Afor(int i = 0; i < m ; i++){dfs(board,m,n,i,0);dfs(board,m,n,i,n-1);}for(int j = 0; j < n ; j++){dfs(board,m,n,0,j);dfs(board,m,n,m-1,j);}//进行覆盖操作,只有为A的不进行覆盖for(int i = 0; i < m; i++){for(int j = 0; j < n; j++){if(board[i][j] == 'A') board[i][j] = 'O';else board[i][j] = 'X';}}return;}
};

547. 省份数量

分析:求无向图中的连通域个数,输入矩阵即为无向图的邻接矩阵

深搜思路:遍历所有城市,对于每个城市,如果该城市没有被访问过,则从该城市开始深度优先搜索,通过矩阵得到与该城市直接相连的城市有哪些,这些城市与该城市属于同一个连通分量,然后对这些城市继续深搜,直到同一个连通分量的所有城市都被访问到,就可以得到一个省份。

class Solution {
public:void dfs(vector<vector<int>>& isConnected,int i,vector<bool>& visited) {for(int j = 0; j < isConnected.size(); j++){//继续遍历与顶点相邻的顶点,使用visited数组防止重复访问if(isConnected[i][j] == 1 && visited[j] == false){//标记当前访问过的顶点visited[j] = true;dfs(isConnected,j,visited);}}return;}int findCircleNum(vector<vector<int>>& isConnected) {int n = isConnected.size();int count = 0;//标识顶点是否被访问vector<bool> visited(n,false);for(int i = 0; i < n; i++){//如果当前顶点没有被访问,说明是一个新的连通域,遍历这个连通域且计数+1if(!visited[i]){dfs(isConnected,i,visited);count++;}}return count;}
};

417. 太平洋大西洋水流问题

1、边界可以去往旁边的一个海洋
也就是说边界与海洋是连通的,所以需要从外往内扩散,获得与边界连通的区域(也就是高度相等或者高度比当前区域高的位置)
2、为了防止重复遍历,添加visited数组
3、将连通的部分放到两个子集中,一个是和太平洋连通的区域,一个是和大西洋连通的区域。然后对两个求交集即可。

class Solution {
public:vector<int> direction{-1,0,1,0,-1};bool Isvaild(int x,int y,vector<vector<int>>& matrix){if(x >= 0 && x < matrix.size() && y >= 0 && y < matrix[0].size()) return true;else return false;}//辅函数void dfs(vector<vector<int>>& matrix,vector<vector<bool>>& can_reach,int r,int c){//如果这个点遍历过了,退出if(can_reach[r][c]) return;can_reach[r][c] = true;int x,y;//向四个方向扩散for(int i = 0; i < 4; i++){x = r + direction[i];y = c + direction[i+1];if(Isvaild(x,y,matrix) && matrix[r][c] <= matrix[x][y])dfs(matrix,can_reach,x,y);}}//主函数vector<vector<int>> pacificAtlantic(vector<vector<int>>& matrix){if(matrix.empty() || matrix[0].empty()) return {};vector<vector<int>> ans;int m = matrix.size();int n = matrix[0].size();//记录能与p洋连通的区域vector<vector<bool>> can_reach_p(m,vector<bool>(n,false));//记录能与a洋连通的区域vector<vector<bool>> can_reach_a(m,vector<bool>(n,false));//遍历上下边界for(int i = 0; i < m; i++){//扩散深搜dfs(matrix,can_reach_p,i,0);dfs(matrix,can_reach_a,i,n-1);}//遍历左右边界for(int j = 0; j < n; j++){//扩散深搜dfs(matrix,can_reach_p,0,j);dfs(matrix,can_reach_a,m-1,j);}//将所有遍历结果进行取交集for(int i = 0; i < m; i++){for(int j = 0; j < n; j++){if(can_reach_p[i][j] && can_reach_a[i][j])ans.push_back(vector<int>{i,j});}}return ans;}
};

回溯

回溯之前,已经做过一些题目了,都是一些经典题目,直接放在这儿。
算法题复习(回溯)

广搜

模板如下:

//计算从起点start到终点target的最短距离
int BFS(Node start,Node target)
{queue<Node> q;	//核心数据结构Set<Node> visited; //避免走回头路q.push(start);		//将起点加入队列visited.insert(start);int steps = 0;	//记录扩散步数while(!q.empty()){//更新步数step++;int sz = q.size();//将当前队列中的所有节点向四周扩散for(int i = 0; i < sz; i++){Node cur = q.front();q.pop();//判断是否到达终点if(cur == target) return step;//将cur相邻的节点加入队列for(Node x: cur.adj()){//如果没有遍历过if(visited.find(x) == visited.end()){q.push(x);visited.insert(x);}}}}
}

cur.adj表示与cur相邻的节点。visited主要是防止走回头路,二叉树结构没有子节点到父节点的指针,所以不会走回头路不需要visited。
不同于深度优先搜索,广搜是一层一层遍历的,因此需要用到先入先出的队列,常常用来处理最短路径问题。

深搜和广搜都可以处理可达性问题,即从一个节点开始是否能到达另一个节点。因为深搜可以利用递归快速实现,所以很多人习惯使用深搜。但是实际工程中,很少使用递归,容易产生栈溢出。

111. 二叉树的最小深度

start:root根节点
target:最靠近根节点的的叶子节点

if(cur.left == nullptr && cur.right == nullptr) //到达了叶子节点
class Solution {
public:int minDepth(TreeNode* root) {int result = 0;queue<TreeNode*> que;if(root == nullptr) return 0;que.push(root);while(!que.empty()){result++;int sz = que.size();for(int i = 0; i < sz; i++){TreeNode* cur = que.front();que.pop();//如果走到终点,返回结果if(cur->left == nullptr && cur->right == nullptr) return result;//否则继续if(cur->left != nullptr) que.push(cur->left);if(cur->right != nullptr) que.push(cur->right);}}return result;}
};

752. 打开转盘锁

计算从初始状态“0000”拨出目标状态的最少次数,如果永远无法拨出,则返回-1.
列表 deadends 包含了一组死亡数字,一旦拨轮的数字和列表里的任何一个元素相同,这个锁将会被永久锁定,无法再被旋转。
从0000开始,按照广度的思想,转一次有8种可能:
1000、9000、0100、0900、…
可以抽象成一幅图,每个节点有8个相邻的节点,求最短距离,此时可以用上BFS。
使用下面的初步代码,是按照BFS来寻找的最小密码组合,不过显然是有问题的。

class Solution {
public:string plusOne(string s,int j){string result = s;if(result[j] == '9') result[j] = '0';else result[j] = result[j] + 1;return result;}string minusOne(string& s,int j){string result = s;if(result[j] == '0') result[j] = '9';else result[j] = result[j] - 1;return result;}int openLock(vector<string>& deadends, string target) {queue<string> q;string start = "0000";q.push(start);int time = 0;while(!q.empty()){time++;int sz = q.size();for(int i = 0; i < sz; i++){string cur = q.front();q.pop();//判断是否到达终点if(cur == target) return time;//将一个节点相邻的节点加入队列for(int j = 0; j < 4; j++){string up = plusOne(cur,j);q.push(up);string down = minusOne(cur,j);q.push(down);}}}return time;}
};

存在的问题如下:
1、会走回头路,比如"0000"拨到"1000",但是等从队列中拿出"1000",还会拨出"0000"
2、没有对deadends进行处理,按道理来说,这些死亡密码不能出现,遇到这些密码的时候需要跳过。
修改后的代码如下:

class Solution {
public:string plusOne(string s,int j){string result = s;if(result[j] == '9') result[j] = '0';else result[j] = result[j] + 1;return result;}string minusOne(string& s,int j){string result = s;if(result[j] == '0') result[j] = '9';else result[j] = result[j] - 1;return result;}int openLock(vector<string>& deadends, string target) {//记录需要跳过的死亡密码set<string> deads;for(string s : deadends) deads.insert(s);//记录已经穷举过的密码,防止走回头陆set<string> visited;queue<string> q;string start = "0000";q.push(start);visited.insert(start);int time = 0;while(!q.empty()){int sz = q.size();for(int i = 0; i < sz; i++){string cur = q.front();q.pop();//判断是否合法if(deads.find(cur) != deads.end()) continue; //判断是否到达终点if(cur == target) return time;//将一个节点相邻的节点加入队列,如果是存在过的就不需要加入队列了for(int j = 0; j < 4; j++){string up = plusOne(cur,j);if(visited.find(up) == visited.end()){q.push(up);visited.insert(up);}string down = minusOne(cur,j);if(visited.find(down) == visited.end()){q.push(down);visited.insert(down);}}}time++;}return -1;}
};

深搜与广搜结合

934. 最短的桥

class Solution {
public:vector<int> direction{-1,0,1,0,-1};void dfs(vector<vector<int>>& A,queue<pair<int,int>>& points,int m,int n,int i,int j){//如果越界了或者遍历过了,不继续向深处遍历if(i < 0 || j < 0 || i >= m || j >= n || A[i][j] == 2) return;//如果是海域,插入队列中if(A[i][j] == 0){points.push({i,j});return ;}A[i][j] = 2;//继续深度遍历dfs(A,points,m,n,i-1,j);dfs(A,points,m,n,i+1,j);dfs(A,points,m,n,i,j-1);dfs(A,points,m,n,i,j+1);return;} int shortestBridge(vector<vector<int>>& A) {int m = A.size();int n = A[0].size();//深度遍历,寻找第一个岛屿int flag = 0;queue<pair<int,int>> points;for(int i = 0; i < m; i++){if(flag == 1) break;for(int j = 0; j < n; j++){//深度遍历完一个岛屿后,立即退出if(A[i][j] == 1){flag = 1;dfs(A,points,m,n,i,j);break;}}}//开始广度遍历int level = 0;while(!points.empty()){int N = points.size();level++;while(N--){auto [r,c] = points.front();points.pop();//向四周广度遍历for(int k = 0; k < 4; k++){int x = r + direction[k];int y = c + direction[k+1];if(x >= 0 && x < m && y >= 0 && y < n){//如果还是第1个岛屿if(A[x][y] == 2) continue;if(A[x][y] == 1) return level;//如果还是0,则是继续入队列points.push({x,y});//将该坐标并入第1个岛屿A[x][y] = 2;}}}}return level;}
};

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/377057.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

AP in R

AP聚类算法是目前十分火的一种聚类算法&#xff0c;它解决了传统的聚类算法的很多问题。不仅简单&#xff0c;而且聚类效果还不错。这里&#xff0c;把前两天学习的AP算法在R语言上面的模拟&#xff0c;将个人笔记拿出来与大家分享一下&#xff0c;不谈AP算法的原理&#xff0c…

nginx 模块解析

nginx的模块非常之多&#xff0c;可以认为所有代码都是以模块的形式组织&#xff0c;这包括核心模块和功能模块&#xff0c;针对不同的应用场合&#xff0c;并非所有的功能模块都要被用到&#xff0c;附录A给出的是默认configure&#xff08;即简单的http服务器应用&#xff09…

python关键字和保留字_Python关键字

python关键字和保留字关键词 (Keywords) Keywords are the reserved words in Python programming language (and, any other programming languages like C, C, Java, etc) whose meanings are defined and we cannot change their meanings. In python programming languages…

《LeetcodeHot100非困难题补录》

最近比较闲&#xff0c;也比较焦虑&#xff0c;刷刷题吧 目录11. 盛最多水的容器22. 括号生成31. 下一个排列48. 旋转图像49. 字母异位词分组56. 合并区间75. 颜色分类79. 单词搜索114. 二叉树展开为链表141. 环形链表148. 排序链表152. 乘积最大子数组169. 多数元素207. 课程表…

Java里String.split需要注意的用法

我们常常用String的split()方法去分割字符串&#xff0c;有两个地方值得注意&#xff1a; 1. 当分隔符是句号时(".")&#xff0c;需要转义&#xff1a; 由于String.split是基于正则表达式来分割字符串&#xff0c;而句号在正则表达式里表示任意字符。 //Wrong: //Str…

C# Socket 例子(控制台程序)

服务器代码 using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Net; using System.Net.Sockets; using System.IO;namespace TCPListener {class Program{static void Main(string[] args){const int BufferSize 1024;Con…

Scala中的值类

Value classes are a special mechanism in Scala that is used to help the compiler to avoid allocating run time objects. 值类是Scala中的一种特殊机制&#xff0c;用于帮助编译器避免分配运行时对象。 This is done by defining a subclass of AnyVal. The only parame…

《MySQL8.0.22:Lock(锁)知识总结以及源码分析》

目录1、关于锁的一些零碎知识&#xff0c;需要熟知事务加锁方式&#xff1a;Innodb事务隔离MVCC多版本并发控制常用语句 与 锁的关系意向锁行级锁2、锁的内存结构以及一些解释3、InnoDB的锁代码实现锁系统结构lock_sys_tlock_t 、lock_rec_t 、lock_table_tbitmap锁的基本模式的…

关于ORA-04021解决办法(timeout occurred while waiting to lock object)

某个应用正在锁定该表或者包 表为 select b.SID,b.SERIAL#,c.SQL_TEXT from v$locked_object a, v$session b, v$sqlarea c where a.SESSION_ID b.SID and b.SQL_ADDRESS c.ADDRESS and c.sql_text like %table_name% 包为 select B.SID,b.USERNAME,b.MACHINE FROM V$ACCESS …

HtmlAutoTestFrameWork

前段时间做的自动化测试的是Silverlight的&#xff0c;框架都已经搭好。突然测试发现这里还有一个要发送邮件的html页面&#xff0c;并且将另外启动浏览器&#xff0c;于是今天下午把这个html的也写出来。用法 &#xff1a; HtmlAutoTestFrameWork htf new HtmlAutoTestFrameW…

L8ER的完整形式是什么?

L8ER&#xff1a;稍后 (L8ER: Later) L8ER is an abbreviation of "Later". L8ER是“ Later”的缩写 。 It is an expression, which is commonly used in messaging or chatting on social media networking sites like Facebook, Yahoo Messenger, and Gmail, etc…

Randomize select algorithm 随机选择算法

从一个序列里面选择第k大的数在没有学习算法导论之前我想最通用的想法是给这个数组排序&#xff0c;然后按照排序结果返回第k大的数值。如果使用排序方法来做的话时间复杂度肯定至少为O&#xff08;nlgn&#xff09;。 问题是从序列中选择第k大的数完全没有必要来排序&#xff…

《Linux杂记:一》

目录CPU负载和CPU利用率CPU负载很高,利用率却很低的情况负载很低,利用率却很高常用linux命令常用的文件、目录命令常用的权限命令常用的压缩命令CPU负载和CPU利用率 可以通过 uptime , w 或者 top 命令看到CPU的平均负载。 Load Average :负载的3个数字,比如上图的0.57、0.4…

IOS Plist操作

代码&#xff1a;copy BUNDLE下的plist文件 到 library下面。 bundle下不支持些&#xff0c;library&#xff0c;doc路径支持读与写。 (void)copyUserpigListToLibrary {NSFileManager *fileManager [NSFileManager defaultManager];NSArray *paths NSSearchPathForDirector…

《线程管理:线程基本操作》

目录线程管理启动线程与&#xff08;不&#xff09;等待线程完成特殊情况下的等待&#xff08;使用trycath或rall&#xff09;后台运行线程线程管理 启动线程与&#xff08;不&#xff09;等待线程完成 提供的函数对象被复制到新的线程的存储空间中&#xff0c;函数对象的执行…

scala特质_Scala的特质

scala特质Scala特质 (Scala traits) Traits in Scala are like interfaces in Java. A trait can have fields and methods as members, these members can be abstract and non-abstract while creation of trait. Scala中的特性类似于Java中的接口 。 特征可以具有作为成员的…

优化PHP代码的40条建议(转)

优化PHP代码的40条建议 40 Tips for optimizing your php Code 原文地址&#xff1a;http://reinholdweber.com/?p3 英文版权归Reinhold Weber所有&#xff0c;中译文作者yangyang&#xff08;aka davidkoree&#xff09;。双语版可用于非商业传播&#xff0c;但须注明英文版作…

Iptables入门教程

转自&#xff1a;http://drops.wooyun.org/tips/1424 linux的包过滤功能&#xff0c;即linux防火墙&#xff0c;它由netfilter 和 iptables 两个组件组成。 netfilter 组件也称为内核空间&#xff0c;是内核的一部分&#xff0c;由一些信息包过滤表组成&#xff0c;这些表包含内…

《线程管理:传递参数、确定线程数量、线程标识》

参考《c Concurrency In Action 》第二章做的笔记 目录传递参数量产线程线程标识传递参数 thread构造函数的附加参数会拷贝至新线程的内存空间中&#xff0c;即使函数中的采纳数是引用类型&#xff0c;拷贝操作也会执行。如果我们期待传入一个引用&#xff0c;必须使用std::re…

手把手玩转win8开发系列课程(14)

这节的议程就是——添加appbar appbar是出现在哪儿了&#xff0c;出现在屏幕的底部。他能使用户能用手势或者使用鼠标操作程序。metro UI 重点是在主要的控件使用许多控件&#xff0c;使其用户使用win8电脑更加的方便。而appBar使其用户体验更好。在这节中&#xff0c;我将告诉…