文章目录
- 前言
- 1 BFS
-
- 1.1 BFS模板
- 1.2 二叉树的最小深度
- 1.3 打开转盘锁
- 2 双向BFS
- 3 DFS
-
- 3.1 岛屿的数量
- 3.2 岛屿的最大面积
- 3.3 填海造陆问题
- 3.4 岛屿的周长
前言
- DFS 是线,BFS 是面
- 一般来说在找最短路径的时候使用 BFS,其他时候还是 DFS 使用得多一些(主要是递归代码好写)
- BFS空间复杂度高,DFS时间复杂度高。
- 无权图的最短路径
- 在程序实现 BFS 时需要考虑以下问题:
队列:用来存储每一轮遍历得到的节点
标记:对于遍历过的节点,应该将它标记,防止重复遍历 - 学习资料
int BFS(Node start, Node target) {
Queue q;
// 核心数据结构
Set visited;
// 避免走回头路q.offer(start);
// 将起点加入队列
visited.add(start);
int step = 0;
// 记录扩散的步数while (q not empty) {
int sz = q.size();
/* 将当前队列中的所有节点向四周扩散 */
for (int i = 0;
i < sz;
i++) {
Node cur = q.poll();
/* 划重点:这里判断是否到达终点 */
if (cur is target)
return step;
/* 将 cur 的相邻节点加入队列 */
for (Node x : cur.adj())
if (x not in visited) {
q.offer(x);
visited.add(x);
}
}
/* 划重点:更新步数在这里 */
step++;
}
}
1.2 二叉树的最小深度 BFS
分析:更改终点情况;以为是子树不用考虑历史结点问题
int minDepth(TreeNode root) {
if (root == null) return 0;
Queue q = new LinkedList<>();
q.offer(root);
// root 本身就是一层,depth 初始化为 1
int depth = 1;
while (!q.isEmpty()) {
int sz = q.size();
/* 将当前队列中的所有节点向四周扩散 */
for (int i = 0;
i < sz;
i++) {
TreeNode cur = q.poll();
/* 判断是否到达终点 */
if (cur.left == null && cur.right == null)
return depth;
/* 将 cur 的相邻节点加入队列 */
if (cur.left != null)
q.offer(cur.left);
if (cur.right != null)
q.offer(cur.right);
}
/* 这里增加步数 */
depth++;
}
return depth;
}
DFS
public int minDepth(TreeNode root) {
if (root == null) return 0;
int left = minDepth(root.left);
int right = minDepth(root.right);
if (left == 0 || right == 0) return left + right + 1;
return Math.min(left, right) + 1;
}
1.3 打开转盘锁
![leetcode刷题|???算法——搜索(最短路径BFS与DFS)](https://img.it610.com/image/info8/4ed3cd29c19241daad6c08989cf265ab.jpg)
文章图片
分析
- 可看成8叉树,四位每一位有两个分支
- 八个结果看作一层,相当于一个操作数,step+1;
- 终止条件是结果==target,肯定是最短的先触发,最短路径。
- 字符串比较记得用equels。大坑
- == 基本类型比较的是数值相等,包装类比较是的是否同一个对象,,重写equels的类可能会比较数值。
//往上拨
String plusOne(String s, int j) {
char[] ch = s.toCharArray();
if (ch[j] == '9')
ch[j] = '0';
else
ch[j] += 1;
return new String(ch);
}
// 将 s[i] 向下拨动一次
String minusOne(String s, int j) {
char[] ch = s.toCharArray();
if (ch[j] == '0')
ch[j] = '9';
else
ch[j] -= 1;
return new String(ch);
}int openLock(String[] deadends, String target) {
// 记录需要跳过的死亡密码
Set> deads = new HashSet<>();
for (String s : deadends) deads.add(s);
// 记录已经穷举过的密码,防止走回头路
Set> visited = new HashSet<>();
Queue> q = new LinkedList<>();
// 从起点开始启动广度优先搜索
int step = 0;
q.offer("0000");
visited.add("0000");
while (!q.isEmpty()) {
int sz = q.size();
/* 将当前队列中的所有节点向周围扩散 */
for (int i = 0;
i < sz;
i++) {
String cur = q.poll();
/* 判断是否到达终点 */
if (deads.contains(cur))
continue;
if (cur.equals(target))
return step;
/* 将一个节点的未遍历相邻节点加入队列 */
for (int j = 0;
j < 4;
j++) {
//每个结点两种操作
String up = plusOne(cur, j);
if (!visited.contains(up)) {
q.offer(up);
visited.add(up);
}
String down = minusOne(cur, j);
if (!visited.contains(down)) {
q.offer(down);
visited.add(down);
}
}
}
/* 在这里增加步数 */
step++;
}
// 如果穷举完都没找到目标密码,那就是找不到了
return -1;
}
优化
可以不需要dead这个哈希集合,可以直接将这些元素初始化到visited集合中,要注意visit的添加位置,不能重复添加。
这种方法可以极限测试会超时
2 双向BFS
- 刚才讨论的二叉树最小高度的问题,你一开始根本就不知道终点在哪里,也就无法使用双向 BFS;但是第二个密码锁的问题,是可以使用双向 BFS 算法来提高效率的,代码稍加修改即可
- start结点和target结点的来回切换扫描,通过temp临时结点来切换两个结点,同时判断相交的触发条件
- visit集合做备忘录防止重复遍历,和deadends集合一样作用,排除无用结点。
- 双向的含义,一个结点去遍历下一层,一个结点存储上一次遍历的一层结点,做相遇的触发条件。
![leetcode刷题|???算法——搜索(最短路径BFS与DFS)](https://img.it610.com/image/info8/cbeaba7b9d8f45258baf4076fbfd7d0c.jpg)
文章图片
int openLock(String[] deadends, String target) {
Set> deads = new HashSet<>();
for (String s : deadends) deads.add(s);
// 用集合不用队列,可以快速判断元素是否存在
Set> q1 = new HashSet<>();
Set> q2 = new HashSet<>();
Set> visited = new HashSet<>();
int step = 0;
q1.add("0000");
q2.add(target);
while (!q1.isEmpty() && !q2.isEmpty()) {
// 哈希集合在遍历的过程中不能修改,用 temp 存储扩散结果
Set> temp = new HashSet<>();
/* 将 q1 中的所有节点向周围扩散 */
for (String cur : q1) {
/* 判断是否到达终点 */
if (deads.contains(cur))
continue;
if (q2.contains(cur))
return step;
visited.add(cur);
/* 将一个节点的未遍历相邻节点加入集合 */
for (int j = 0;
j < 4;
j++) {
String up = plusOne(cur, j);
if (!visited.contains(up))
temp.add(up);
String down = minusOne(cur, j);
if (!visited.contains(down))
temp.add(down);
}
}
/* 在这里增加步数 */
step++;
// temp 相当于 q1
// 这里交换 q1 q2,下一轮 while 就是扩散 q2
q1 = q2;
q2 = temp;
}
return -1;
}
leetdoe752双向BFS解法
package com.zknode.BFS_and_DFS;
import java.util.HashSet;
import java.util.LinkedList;
public class ti_752 {
//单个字符的加操作
public String pulsStr(String str,int j){
char[] chars = str.toCharArray();
if(chars[j] =='9'){
chars[j] = '0';
}else {
chars[j] += 1;
}
return new String(chars);
}
public String minusStr(String str, int j){
char[] chars = str.toCharArray();
if(chars[j] == '0'){
chars[j] = '9';
}else{
chars[j]-=1;
}
return new String(chars);
}public int openLock(String[] deadends, String target) {
//死亡集合
//HashSet dead = new HashSet<>();
//for(String Str:deadends){
//dead.add(Str);
//}
//历史集合+死亡集合
HashSet> history = new HashSet<>();
for(String i:deadends){
history.add(i);
}
//遍历节点队列
LinkedList> que = new LinkedList<>();
LinkedList> que2= new LinkedList<>();
que.offer("0000");
que2.offer(target);
//寻找的层数
int step = 0;
while(!que.isEmpty() && !que2.isEmpty()){
LinkedList> temp = new LinkedList<>();
//BFS的一层
for(String node:que){
//历史路径查询+死亡结点查询 ,不考虑该路径
if(history.contains(node)){
continue;
}if(que2.contains(node)){
return step;
}//统一添加历史结点位置,在出队列后加,也可以在入队列里加
history.add(node);
//四个字母分别动两下
for(int j =0 ;
j<4 ;
j++){
String plusnum = pulsStr(node,j);
//历史路径查询+死亡结点查询 ,不考虑该路径
if(!history.contains(plusnum)){
temp.offer(plusnum);
}
String minusnum = minusStr(node,j);
if(!history.contains(minusnum)){
temp.offer(minusnum);
}
}
}
step++;
que = que2;
que2 = temp;
}
return -1;
}
public static void main(String[] args) {
ti_752 ti_752 = new ti_752();
String[] str = new String[]{"0201","0101","0102","1212","2002"};
System.out.println(ti_752.openLock(str,"0202"));
}
}
3 DFS
- DFS算法的核心:做选择再递归,回溯算法就是DFS的一种表现形式
void dfs(int[][] grid, int r, int c) {
// 判断 base case
if (!inArea(grid, r, c)) {
return;
}
// 如果这个格子不是岛屿,直接返回
if (grid[r][c] != 1) {
return;
}
grid[r][c] = 2;
// 将格子标记为「已遍历过」// 访问上、下、左、右四个相邻结点
dfs(grid, r - 1, c);
dfs(grid, r + 1, c);
dfs(grid, r, c - 1);
dfs(grid, r, c + 1);
}// 判断坐标 (r, c) 是否在网格中
boolean inArea(int[][] grid, int r, int c) {
return 0 <= r && r < grid.length
&& 0 <= c && c < grid[0].length;
}
3.1 岛屿的数量 详情
问题
给你一个由 ‘1’(陆地)和 ‘0’(水)组成的的二维网格,请你计算网格中岛屿的数量。
输入:grid =分析
[ [“1”,“1”,“1”,“1”,“0”],
[“1”,“1”,“0”,“1”,“0”],
[“1”,“1”,“0”,“0”,“0”],
[“0”,“0”,“0”,“0”,“0”] ]
输出:1
- 岛屿问题最常用的就是DFS。当然也可以使用BFS和并查集。
class Solution {
public void dfs(char[][] grid,int i,int j)
{
int nr = grid.length;
int nc = grid[0].length;
if(i>nr-1 || j>nc-1 ||i<0||j<0)
return;
if(grid[i][j]!='1')
return;
grid[i][j]='2';
dfs(grid,i+1,j);
dfs(grid,i,j+1);
dfs(grid,i,j-1);
dfs(grid,i-1,j);
}
public int numIslands(char[][] grid) {
int count=0;
if(grid.length==0 || grid ==null) return 0;
for(int i=0;
i
3.2 岛屿的最大面积 问题
示例 1:分析
[[0,0,1,0,0,0,0,1,0,0,0,0,0],
[0,0,0,0,0,0,0,1,1,1,0,0,0],
[0,1,1,0,1,0,0,0,0,0,0,0,0],
[0,1,0,0,1,1,0,0,1,0,1,0,0],
[0,1,0,0,1,1,0,0,1,1,1,0,0],
[0,0,0,0,0,0,0,0,0,0,1,0,0],
[0,0,0,0,0,0,0,1,1,1,0,0,0],
[0,0,0,0,0,0,0,1,1,0,0,0,0]]
对于上面这个给定矩阵应返回 6。注意答案不应该是 11 ,因为岛屿只能包含水平或垂直的四个方向的 1 。
- 依次扫描为1的网格,扫到之后DFS计算面积。
public int maxAreaOfIsland(int[][] grid) {
int res = 0;
for (int r = 0;
r < grid.length;
r++) {
for (int c = 0;
c < grid[0].length;
c++) {
if (grid[r][c] == 1) {
int a = area(grid, r, c);
res = Math.max(res, a);
}
}
}
return res;
}int area(int[][] grid, int r, int c) {
if (!inArea(grid, r, c)) {
return 0;
}
if (grid[r][c] != 1) {
return 0;
}
grid[r][c] = 2;
return 1
+ area(grid, r - 1, c)
+ area(grid, r + 1, c)
+ area(grid, r, c - 1)
+ area(grid, r, c + 1);
}boolean inArea(int[][] grid, int r, int c) {
return 0 <= r && r < grid.length
&& 0 <= c && c < grid[0].length;
}
3.3 填海造陆问题 问题
- leetcode827
- 两遍 DFS:第一遍 DFS 遍历陆地格子,计算每个岛屿的面积并标记岛屿;第二遍 DFS 遍历海洋格子,观察每个海洋格子相邻的陆地格子
3.4 岛屿的周长 问题
分析
代码
推荐阅读
- Leet|单源点求最短路径的三种常用的方法
- 图论|BFS最短路径的两种打印方法
- java|Go 1.18 二进制文件的信息嵌入
- python|一文看懂 Go 泛型核心设计
- 编程语言|“Go语言第一课”结课了
- spring|java springboot前后端不分离项目
- 大数据项目总结|基于Java+springmvc+vue 健康管理系统
- Java毕业设计|基于Java+SpringMvc+vue+element实现博物馆平台系统
- java项目精品实战案例|基于Java+SpringMvc+vue+element实现驾校管理系统详细设计