BFS简介
参考链接
广度优先搜索一层一层地进行遍历,每层遍历都以上一层遍历的结果作为起点,遍历一个距离能访问到的所有节点。需要注意的是,遍历过的节点不能再次被遍历。
第一层:
0->{1,2,6,5}
第二层:
6->{4}
5->{3,4}
第三层:
3->{}
4->{}
每一层遍历的节点都与根节点距离相同。设 di 表示第 i 个节点与根节点的距离,推导出一个结论:对于先遍历的节点 i 与后遍历的节点 j,有 di <= dj。利用这个结论,可以求解最短路径等 最优解 问题:第一次遍历到目的节点,其所经过的路径为最短路径。应该注意的是,使用 BFS 只能求解无权图的最短路径,无权图是指从一个节点到另一个节点的代价都记为 1。
在程序实现 BFS 时需要考虑以下问题:
- 队列:用来存储每一轮遍历得到的节点;
- 标记:对于遍历过的节点,应该将它标记,防止重复遍历。
DFS简介
深度优先遍历顾名思义就是可着一条路去遍历。以下图举例,程序会优先去走一条路,比如会先a->b->e;之后以程序语言来说就是e是叶子节点了,所以结束递归,返回b的位置,此时b会取遍历f,路径就是a->b->f,之后就是返回a,a->c,a->d。其实上述描述语言为前序遍历,当前->左->右。
在程序实现 DFS 时需要考虑以下问题:
栈:用栈来保存当前节点信息,当遍历新节点返回时能够继续遍历当前节点。可以使用递归栈。
标记:和 BFS 一样同样需要对已经遍历过的节点进行标记。
回溯简介
回溯算法实际上一个类似枚举的搜索尝试过程,主要是在搜索尝试过程中寻找问题的解,当发现已不满足求解条件时,就 “回溯” 返回,尝试别的路径。回溯法是一种选优搜索法,按选优条件向前搜索,以达到目标。但当探索到某一步时,发现原先选择并不优或达不到目标,就退回一步重新选择,这种走不通就退回再走的技术为回溯法,而满足回溯条件的某个状态的点称为 “回溯点”。许多复杂的,规模较大的问题都可以使用回溯法,有“通用解题方法”的美称。
回溯算法的基本思想是:从一条路往前走,能进则进,不能进则退回来,换一条路再试。
并查集简介
DFS题目
690. 员工的重要性
题目链接。本题需要考虑的就是不是直系下属的重要程度也需要考虑入内。
1.dfs解法:
dfs的思路就是一条路一条路的去走。
以该例子举例
[[1,5,[2,3]],[2,3,[4]],[3,4,[]],[4,1,[]]]
1
程序的流程是:首先找到员工1,之后找到直系下属员工2,之后找到非直系下属4,最后在非直系下属4的时候结束寻找。也就是传递的始终是当前要找的员工id。
路径为:1->2->4,1->3。
/*
// Employee info
class Employee {
// It's the unique id of each node;
// unique id of this employee
public int id;
// the importance value of this employee
public int importance;
// the id of direct subordinates
public List<Integer> subordinates;
};
*/
class Solution {
//不是直系下属的也需要添加进去。
int impo;
public int getImportance(List<Employee> employees, int id) {
//还是dfs最好用我觉得
//id与对应的员工信息
Map<Integer,Employee>map = new HashMap<>();
for(Employee e:employees)
map.put(e.id,e);
dfs(map,id);
return impo;**加粗样式**
}
public void dfs(Map<Integer,Employee>map,int id)
{
Employee e = map.get(id);
impo += e.importance;
for(Integer i:e.subordinates)
{
dfs(map,i);
}
return ;
}
}
2.bfs算法
bfs算法是一层一层的遍历。
以该例子举例
[[1,5,[2,3]],[2,3,[4]],[3,4,[]],[4,1,[]]]
1
程序的流程是,一层一层的去遍历,首先遍历的是我们要找的父员工,然后去遍历父员工的所有直系下属员工,之后遍历所有直系下员工的所有直系下属员工。也就是说每一回遍历传递的是一个列表,列表中存储当前层的所有id。
路径为:
第一层:1;
第二层:2,3;
第三层:4。
/*
// Employee info
class Employee {
// It's the unique id of each node;
// unique id of this employee
public int id;
// the importance value of this employee
public int importance;
// the id of direct subordinates
public List<Integer> subordinates;
};
*/
class Solution {
//不是直系下属的也需要添加进去。
int impo;
public int getImportance(List<Employee> employees, int id) {
//还是dfs最好用我觉得
//id与对应的员工信息
Map<Integer,Employee>map = new HashMap<>();
for(Employee e:employees)
map.put(e.id,e);
List<Integer>list = new ArrayList<>();
list.add(id);
bfs(map,list);
return impo;
}
public void bfs(Map<Integer,Employee>map,List<Integer>l)
{
List<Integer>list=new ArrayList<>();
for(Integer i:l)
{
Employee e = map.get(i);
impo+=e.importance;
list.addAll(e.subordinates);
}
if(list.size()!=0)
bfs(map,list);
return ;
}
}
547.朋友圈
dfs解法
这道题我的思路就是深度优先遍历。其实是两层遍历,第一层是遍历每个学生,第二层是遍历每个学生和他有好友关系的学生。第一层的含义其实是循环一次就代表一个朋友圈,因为我们要做的是有记忆功能的dfs。我们使用一个boolean型数组来存储,flag【i】代表i是否已经在一个朋友圈里了。当该学生不在朋友圈中,此时进入dfs递归。在dfs中我们去把该学生的好友关系挨个寻找一遍,以此类推,直到学生的好友关系的那些好友都已经在某个朋友圈当中了。
首先要理解第一个循环走一次就代表一个朋友圈,因为我会在这一次里把所有与该A学生有关的学生全部找到(不是直接有关系,有间接关系的也会找到),这些都在一个朋友圈内。下一次去找的学生是与他们一点关系都没有的学生,然后和这个学生有关系的学生们会组成另一个朋友圈。所以第一个循环走几次就代表有几个朋友圈。
其次,dfs中会遇到两个误导。
1.是否可以从(M【i】数组)i位置往后走,因为i位置以前的学生我都看过了。
答案是否定的。因为比如有四个学生。有可能第一个学生和第二个学生没关系,和第三个学生有关系,那么我们去找第三个学生的时候,就直接去看第三个和第四个学生是否有关系,那万一第三个和第二个学生还有关系呢?
更改做法:所以我们每次都必须从0开始往后找。
2.是否可以在dfs后再将flag【j】赋值为true呢?
答案是否定的。设想第二个学生和第三个学生有关系,那么我们会在M【2】数组的第三个位置进入dfs循环,之后又会在M【3】数组的第二个位置进入上一个dfs循环,这样就会一直循环。
**更改做法:**我们在进入dfs的时候将flag【i】设为true,因为我们是从i学生走过来的,所以我们要将i学生放入我们的朋友圈。
class Solution {
int circles = 0;
//有存储记忆
boolean[]flag;
public int findCircleNum(int[][] M) {
//代表i是否已经在一个朋友圈了
flag = new boolean[M.length];
for(int i=0;i<M.length;i++){
if(flag[i])
continue;
//循环几次就是有几个朋友圈(因为能循环,就代表这个学生没有被加入到朋友圈之中)
circles++;
dfs(M,flag,i);
}
return circles;
}
public void dfs(int[][]M,boolean[]flag,int i) {
//列i之前的不用看了,肯定都看过了。
//因为i行之前的行都看过了。
//要看i学生,是否能和后面的学生继续互为朋友关系。
//上面的是错的,因为比如有四个学生
//有可能第一个学生和第二个学生没关系,和第三个学生有关系,
//那么我们去找第三个学生的时候,就直接去看第三个和第四个学生是否有关系,那万一第三个和第二个学生还有关系呢?
//我们把第i个同学加进我们的朋友圈。
flag[i]=true;
for(int j=0;j<M[0].length;j++)
{
if(flag[j]||i==j)
continue;
if(M[i][j]==1)
{
dfs(M,flag,j);
//这会导致我们一直在dfs循环中,比如第二个和第三个有关系,那么第三个也和第二个有关系。
//就会不断的循环dfs(M,flag,1),和dfs(M,flag,2),去dfs一次就应该把当前循环的那个
//学生加入到朋友圈之中,但不能再dfs之后,应该在dfs初始就加入,防止陷入死循环。
//flag[j]=false;
}
}
}
}
200.岛屿数量
题目链接
本题实在是与朋友圈太像了,与陆地相连的陆地都算是一个岛屿中的陆地,找到数组中所有的岛屿。
dfs解法
使用具有记忆性的dfs。同样是首先循环判断数组中的每个值,当我们发现一个值为1,且他没有被寻找过,那我们就进入dfs递归。在dfs递归中,我们去继续判断该位置的上下左右位置。递归的结束条件是当位置越界或者该值为0(水)或该值被访问过了。
最后判断第一次循环中,走了多少else分支,就代表有多少个岛屿。具体见代码。
class Solution {
//尝试dfs
//其实思路就是当找到一个1后,就将所有与其垂直与水平连通(直接与间接)的1放一起组成岛屿,
//然后看有多少个这样的岛屿
char[][]grid;
//必须带有记忆性
boolean [][]flag;
int rows;
int columns;
public int numIslands(char[][] grid) {
this.grid = grid;
//防止有空
if(grid==null||grid.length==0)
return 0;
rows = grid.length;
columns = grid[0].length;
flag = new boolean [rows][columns];
int island=0;
for(int i=0;i<rows;i++)
{
for(int j=0;j<columns;j++)
{
if(flag[i][j]||grid[i][j]=='0')
continue;
//能执行几次该语句就说明有多少个独立的岛屿
else{
island++;
dfs(i,j);
}
}
}
return island;
}
public void dfs(int i,int j)
{
//判断越界以及是否为水以及是否已经判断过。
if(i<0||i>=rows||j<0||j>=columns||grid[i][j]=='0'||flag[i][j])
return ;
//该值找过了
flag[i][j]=true;
//水平垂直方向
dfs(i-1,j);
dfs(i+1,j);
dfs(i,j-1);
dfs(i,j+1);
}
}
417.太平洋大西洋水流问题
题目链接。
要找到一个位置,它既可以将水流流到太平洋也可以流到大西洋。从位置(i,j)流动到(k,t)的条件为matrix[i][j]>=matrix[k][t]。
dfs解法
- 最初的想法是对每个位置去判断是否可以到达太平洋或者大西洋,后来想到这样递归的时间成本太大,所以最后思路是从太平洋(第一行和第一列)和大西洋的海岸(最后一行,最后一列)开始往陆地去寻找。
- 使用两个boolean数组来存放该位置是否可以到达大西洋/太平洋
- 需要注意的就是dfs要传入四个参数,前两个为位置i和j,第三个为对应海洋的数组,第四个为一个高度值第四个代表的是什么呢,可以理解为当前位置对应的高度值,只有大于等于该高度值才可以满足该位置能够到达对应海洋。
- 当所有海洋的边界(以及可到达海洋边界的点)都递归结束后,可以循环所有点了,将同时满足两个数组的值添加进list。
- 本算法的好处是节省时间。以上述例子的第二行来举例:32344,如果以正常每个位置都判断的算法,会判断3,判断2,判断3,判断4,判断4,而2,3,4,4还会继续向左判断。如果以我们的算法判断,在判断到2的时候就会停止判断,因为2<3,后面的水都无法从这里走到3。
class Solution {
//原本思路是需要从大陆判断到海洋边界(每一块陆地去判断能否到达太平洋和大西洋)
//后来发现可能会超时,那我们从海洋边界逆向判断吧。
int[][]matrix;
int rows;
int columns;
public List<List<Integer>> pacificAtlantic(int[][] matrix) {
List<List<Integer>>results = new ArrayList<>();
this.matrix=matrix;
if(matrix==null||matrix.length==0)
return results;
rows = matrix.length;
columns = matrix[0].length;
//是否能到达太平洋
boolean[][]tai = new boolean[rows][columns];
//是否能到达大西洋
boolean[][]da = new boolean[rows][columns];
//第一行最后一行
for(int j=0;j<columns;j++)
{
dfs(0,j,tai,-1);
dfs(rows-1,j,da,-1);
}
//第一列,最后一列
for(int i=0;i<rows;i++)
{
dfs(i,0,tai,-1);
dfs(i,columns-1,da,-1);
}
for(int i=0;i<rows;i++)
{
for(int j=0;j<columns;j++)
{
if(tai[i][j]&&da[i][j])
{
List<Integer>list = new ArrayList<>();
list.add(i);
list.add(j);
results.add(list);
}
}
}
return results;
}
//传入4个参数,前两个为当前位置,flag为存储能否到达当前洋的数组,last是上一个值(也是能到达flag海岸的最小值)
public void dfs(int i,int j,boolean[][]flag,int last)
{
//越界
if(i<0||i>=rows||j<0||j>=columns||matrix[i][j]<last)
return ;
//已经可以达到了。
if(flag[i][j])
return;
flag[i][j]=true;
dfs(i,j+1,flag,matrix[i][j]);
dfs(i,j-1,flag,matrix[i][j]);
dfs(i+1,j,flag,matrix[i][j]);
dfs(i