算法之深度优先遍历(DFS)
最近在学习DFS和BFS,所以做一些学习的笔记,这里是深度遍历。
首先,比较常见的深度遍历题目就是网格题(可抽象为二维数组)。在LeetCode中常见的是岛屿问题。
思想:
深度优先遍历的思想可以理解为:
- 找到一个起始点S
- 选择一个与S相邻的节点N1,并到达该节点
- 在以该N1未起始点,选择他的相邻节点N2
- …如此循环,直到最后有一个节点Nn没有相邻节点
- 此时,返回到上一个节点N(n-1),选择另外的相邻节点,继续步骤2-4
- 最后就会遍历完全部的节点
这里会有一个问题:第二部会访问N2的相邻节点,那么是不是会重新访问起始点S?这样不就死循环了吗?确实会这样,所以需要解决这个问题,解决办法就是将访问过的节点标记出来,这样就不会重复访问了
上图中,序号即为遍历的顺序,绿色的背景表示已经访问过,已访问的节点不再访问,所以可以避免重复访问的问题(图中没有展示回溯到上一层的过程)
模板:
DFS和BFS都有模板可以使用,所以有必要先将模板展示出来,此处展示的是网格题僧都遍历的模板,其他的都是异曲同工
岛屿中:0表示海洋,1表示陆地,2表示已经访问过
如:
1 1 1 0 1 0 0
0 0 0 1 1 1 0
1 0 0 0 0 0 0
0 0 0 0 0 0 1
// 深度遍历函数
void dfs(int[][] area, int x, int y){
// 如果不在区域内,直接返回
if(!inArea(area,x,y)){
return;
}
// 如果已经访问过,则直接返回,不在访问
if(area[x][y] == 2){
return;
}
//否则遍历其相邻的节点
dfs(area, x-1, y); // 上
dfs(area, x, y+1); // 右
dfs(area, x+1, y); // 下
dfs(area, x, y-1); // 左
}
// 判断是否在区域内
boolean inArea(int[][] area, int x, int y){
// 再区域内的条件:x,y>0 同时x不超过最大行数,y不超过最大列数
return x>=0 && x<area.length && y>=0 && y<area[0].length;
}
例题1:
1.leetcode200
给你一个由 ‘1’(陆地)和 ‘0’(水)组成的的二维网格,请你计算网格中岛屿的数量。
岛屿总是被水包围,并且每座岛屿只能由水平方向和/或竖直方向上相邻的陆地连接形成。
此外,你可以假设该网格的四条边均被水包围。
示例 1:
输入:grid = [
[“1”,“1”,“1”,“1”,“0”],
[“1”,“1”,“0”,“1”,“0”],
[“1”,“1”,“0”,“0”,“0”],
[“0”,“0”,“0”,“0”,“0”]
]
输出:1
代码:
class Solution {
public int numIslands(char[][] grid) {
// 岛屿数量
int count=0;
// 遍历每一个点
for(int i=0; i<grid.length; i++){
for(int j=0; j<grid[0].length; j++){
// 如果该点已被访问过直接跳过
if(grid[i][j] != '1'){
continue;
}
count += dfs(grid,i,j);
}
}
return count;
}
int dfs(char[][] area,int x, int y){
// 如果不在区域内
if(!isArea(area,x,y)) {
return 0;
}
// 不是未访问的陆地
if(area[x][y] != '1'){
return 0;
}
// 标记已访问
area[x][y] = '2';
dfs(area, x-1, y); // 上
dfs(area, x, y+1); // 右
dfs(area, x+1, y); // 下
dfs(area, x, y-1); // 左
return 1;
}
boolean isArea(char[][] area, int x, int y){
return x>=0 && x<area.length && y>=0 && y<area[0].length;
}
}
例题2:
LeetCode .695 岛屿的最大面积
给定一个包含了一些 0 和 1 的非空二维数组 grid 。
一个 岛屿 是由一些相邻的 1 (代表土地) 构成的组合,这里的「相邻」要求两个 1 必须在水平或者竖直方向上相邻。你可以假设 grid 的四个边缘都被 0(代表水)包围着。
找到给定的二维数组中最大的岛屿面积。(如果没有岛屿,则返回面积为 0 。)
来源:力扣(LeetCode)
链接:https://leetcode-cn.com/problems/max-area-of-island
示例 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
代码:
class Solution {
public int maxAreaOfIsland(int[][] grid) {
int max=0; // 最大岛屿面积
int square=0; // 每块岛屿的面积
for(int i=0; i<grid.length; i++){
for(int j=0; j<grid[0].length; j++){
if(grid[i][j] != 1){
continue;
}
square = dfs(grid,i,j,0); // 当前岛屿面积,初始时,该岛屿面积为1
max = max > square ? max : square; // 获取最大岛屿面积
}
}
return max;
}
/*
最大岛屿面,需要新增传入参数:squ---当前已知该岛屿的面积
*/
int dfs(int[][] area, int x, int y, int squ){
if(!inArea(area,x,y)){ // 是否在区域内
return squ; // 不在直接返回传入时的面积
}
if(area[x][y] != 1){
return squ; // 如果该点以访问,也是直接返回传入时的面积
}
area[x][y] = 2; // 以上两个条件都通过,则该点是"新大陆",岛屿面积+1
squ++;
squ = dfs(area,x-1,y,squ); // 循环判断
squ = dfs(area,x+1,y,squ);
squ = dfs(area,x,y-1,squ);
squ = dfs(area,x,y+1,squ);
return squ; // 当该点走投无路时,及得到岛屿面积
}
boolean inArea(int[][] area, int x, int y){
return x>=0 && x<area.length && y>=0 && y<area[0].length;
}
}
此做法时间复杂度低,耗时少,但是空间复杂度较高
更多,待学习。。。。