13.1 深度优先遍历和广度优先遍历算法总结
广度优先搜索算法(Breadth-First-Search,缩写为 BFS),是一种利用队列实现的搜索算法。简单来说,其搜索过程和 “湖面丢进一块石头激起层层涟漪” 类似。
深度优先搜索算法(Depth-First-Search,缩写为 DFS),是一种利用递归实现的搜索算法。简单来说,其搜索过程和 “不撞南墙不回头” 类似。
BFS 的重点在于队列,而 DFS 的重点在于递归。这是它们的本质区别。
DFS 和 BFS 都是常用来遍历搜索树或图的算法。二叉树中的前序、中序和后序遍历都属于DFS,层次遍历属于BFS。DFS常用递归和栈来实现,BFS常用队列来实现。
-
BFS用queue (程序中用的deque,不用deque直接用普通队列一样的)
-
-
按BFS的方法遍历整个树,即用队列先后保存左右子节点,这样就可以一层一层的记录每个节点的val,输出到res中
-
利用了queue先进先出的特点,append时先左再右,这样pop时的顺序也是先左再右
-
-
DFS用stack
-
-
按DFS的方法遍历整个树,即用栈先后保存“右,左”节点(这样pop出来的才会是先全部左,再右)
-
利用了stack后进先出的特点,append时先右再左,这样所有的右子节点都会被保留在栈的底部,而左子节点都先被pop处。与前序遍历是一个道理!
-
13.2 经典题目源码总结
扫雷游戏
方法一:深度优先搜索 + 模拟
class Solution {
int[] dirX = {0, 1, 0, -1, 1, 1, -1, -1};
int[] dirY = {1, 0, -1, 0, 1, -1, 1, -1};
public char[][] updateBoard(char[][] board, int[] click) {
int x = click[0], y = click[1];
if (board[x][y] == 'M') {
// 规则 1
board[x][y] = 'X';
} else{
dfs(board, x, y);
}
return board;
}
public void dfs(char[][] board, int x, int y) {
int cnt = 0;
for (int i = 0; i < 8; ++i) {
int tx = x + dirX[i];
int ty = y + dirY[i];
if (tx < 0 || tx >= board.length || ty < 0 || ty >= board[0].length) {
continue;
}
// 不用判断 M,因为如果有 M 的话游戏已经结束了
if (board[tx][ty] == 'M') {
++cnt;
}
}
if (cnt > 0) {
// 规则 3
board[x][y] = (char) (cnt + '0');
} else {
// 规则 2
board[x][y] = 'B';
for (int i = 0; i < 8; ++i) {
int tx = x + dirX[i];
int ty = y + dirY[i];
// 这里不需要在存在 B 的时候继续扩展,因为 B 之前被点击的时候已经被扩展过了
if (tx < 0 || tx >= board.length || ty < 0 || ty >= board[0].length || board[tx][ty] != 'E') {
continue;
}
dfs(board, tx, ty);
}
}
}
}
方法二:广度优先搜索 + 模拟
class Solution {
int[] dirX = {0, 1, 0, -1, 1, 1, -1, -1};
int[] dirY = {1, 0, -1, 0, 1, -1, 1, -1};
public char[][] updateBoard(char[][] board, int[] click) {
int x = click[0], y = click[1];
if (board[x][y] == 'M') {
// 规则 1
board[x][y] = 'X';
} else{
bfs(board, x, y);
}
return board;
}
public void bfs(char[][] board, int sx, int sy) {
Queue<int[]> queue = new LinkedList<int[]>();
boolean[][] vis = new boolean[board.length][board[0].length];
queue.offer(new int[]{sx, sy});
vis[sx][sy] = true;
while (!queue.isEmpty()) {
int[] pos = queue.poll();
int cnt = 0, x = pos[0], y = pos[1];
for (int i = 0; i < 8; ++i) {
int tx = x + dirX[i];
int ty = y + dirY[i];
if (tx < 0 || tx >= board.length || ty < 0 || ty >= board[0].length) {
continue;
}
// 不用判断 M,因为如果有 M 的话游戏已经结束了
if (board[tx][ty] == 'M') {
++cnt;
}
}
if (cnt > 0) {
// 规则 3
board[x][y] = (char) (cnt + '0');
} else {
// 规则 2
board[x][y] = 'B';
for (int i = 0; i < 8; ++i) {
int tx = x + dirX[i];
int ty = y + dirY[i];
// 这里不需要在存在 B 的时候继续扩展,因为 B 之前被点击的时候已经被扩展过了
if (tx < 0 || tx >= board.length || ty < 0 || ty >= board[0].length || board[tx][ty] != 'E' || vis[tx][ty]) {
continue;
}
queue.offer(new int[]{tx, ty});
vis[tx][ty] = true;
}
}
}
}
}
水域大小
第一种:深度优先遍历
class Solution {
public int[] pondSizes(int[][] land) {
List<Integer> list = new ArrayList<>();
if(land.length==0) return new int [0];
int temp = 0;
for(int i = 0;i<land.length;i++){
for(int j=0;j<land[0].length;j++){
if(land[i][j]==0){
temp = dfs(land,i,j);
list.add(temp);
}
}
}
list.sort((o1,o2)-> o1-o2);
return list.stream().mapToInt(Integer::intValue).toArray();
}
private int dfs(int [][]land,int i,int j){
if(i<0||j<0||i>=land.length||j>=land[0].length||land[i][j]!=0) return 0;
land[i][j] = 3;
int area = 1;
area += dfs(land,i-1,j-1);
area += dfs(land,i-1,j+1);
area += dfs(land,i,j+1);
area += dfs(land,i,j-1);
area += dfs(land,i-1,j);
area += dfs(land,i+1,j);
area += dfs(land,i+1,j-1);
area += dfs(land,i+1,j+1);
return area;
}
}
第二种:广度优先遍历
class Solution {
// 右,下,上,左,左下,右下,左上,右上
// 左下:行加一,列减一
int[][] dire = {{0,1}, {1,0}, {-1,0}, {0,-1}, {1,-1}, {1,1}, {-1,-1}, {-1,1}};
public class Node{
int x, y;
Node(int i, int j) {x=i; y=j;}
}
public int[] pondSizes(int[][] land) {
List<Integer> res = new ArrayList<>();
boolean[][] visit = new boolean[land.length][land[0].length];
for(int i = 0; i < land.length; i++){
for(int j = 0; j < land[0].length; j++){
if(land[i][j]==0 && !visit[i][j]){
res.add(bfs(new Node(i,j), land, visit));
}
}
}
int[] ans = new int[res.size()];
for(int i = 0; i < ans.length; i++){
ans[i] = res.get(i);
}
Arrays.sort(ans);
return ans;
}
private int bfs(Node node, int[][] land, boolean[][] visit){
Queue<Node> q = new LinkedList<>();
q.offer(node);
visit[node.x][node.y] = true;
int cnt = 1;
while(!q.isEmpty()){
node = q.poll();
for(int i = 0; i < 8; i++){
Node tmp = new Node(node.x+dire[i][0], node.y+dire[i][1]);
if(tmp.x<0 || tmp.x>land.length-1 || tmp.y<0 || tmp.y>land[0].length-1){
continue;
}
if(land[tmp.x][tmp.y]==0 && visit[tmp.x][tmp.y]==false){
q.offer(tmp);
visit[tmp.x][tmp.y] = true;
cnt++;
}
}
}
return cnt;
}
}
在每个树行中找最大值
方法一:广度优先搜索
public List<Integer> largestValues(TreeNode root) {
//LinkedList实现队列
Queue<TreeNode> queue = new LinkedList<>();
List<Integer> values = new ArrayList<>();
if (root != null)
queue.add(root);//入队
while (!queue.isEmpty()) {
int max = Integer.MIN_VALUE;
int levelSize = queue.size();//每一层的数量
for (int i = 0; i < levelSize; i++) {
TreeNode node = queue.poll();//出队
max = Math.max(max, node.val);//记录每层的最大值
if (node.left != null)
queue.add(node.left);
if (node.right != null)
queue.add(node.right);
}
values.add(max);
}
return values;
}
第二种:深度优先搜索
public List<Integer> largestValues(TreeNode root) {
List<Integer> res = new ArrayList<>();
helper(root, res, 1);
return res;
}
//level表示的是第几层,集合res中的第一个数据表示的是
// 第一层的最大值,第二个数据表示的是第二层的最大值……
private void helper(TreeNode root, List<Integer> res, int level) {
if (root == null)
return;
//如果走到下一层了直接加入到集合中
if (level == res.size() + 1) {
res.add(root.val);
} else {
//注意:我们的level是从1开始的,也就是说root
// 是第一层,而集合list的下标是从0开始的,
// 所以这里level要减1。
// Math.max(res.get(level - 1), root.val)表示的
// 是遍历到的第level层的root.val值和集合中的第level
// 个元素的值哪个大,就要哪个。
res.set(level - 1, Math.max(res.get(level - 1), root.val));
}
//下面两行是DFS的核心代码
helper(root.left, res, level + 1);
helper(root.right, res, level + 1);
}
被围绕的区域
方法一:深度优先搜索
class Solution {
int n, m;
public void solve(char[][] board) {
n = board.length;
if (n == 0) {
return;
}
m = board[0].length;
for (int i = 0; i < n; i++) {
dfs(board, i, 0);
dfs(board, i, m - 1);
}
for (int i = 1; i < m - 1; i++) {
dfs(board, 0, i);
dfs(board, n - 1, i);
}
for (int i = 0; i < n; i++) {
for (int j = 0; j < m; j++) {
if (board[i][j] == 'A') {
board[i][j] = 'O';
} else if (board[i][j] == 'O') {
board[i][j] = 'X';
}
}
}
}
public void dfs(char[][] board, int x, int y) {
if (x < 0 || x >= n || y < 0 || y >= m || board[x][y] != 'O') {
return;
}
board[x][y] = 'A';
dfs(board, x + 1, y);
dfs(board, x - 1, y);
dfs(board, x, y + 1);
dfs(board, x, y - 1);
}
}
方法二:广度优先搜索
class Solution {
int[] dx = {1, -1, 0, 0};
int[] dy = {0, 0, 1, -1};
public void solve(char[][] board) {
int n = board.length;
if (n == 0) {
return;
}
int m = board[0].length;
Queue<int[]> queue = new LinkedList<int[]>();
for (int i = 0; i < n; i++) {
if (board[i][0] == 'O') {
queue.offer(new int[]{i, 0});
}
if (board[i][m - 1] == 'O') {
queue.offer(new int[]{i, m - 1});
}
}
for (int i = 1; i < m - 1; i++) {
if (board[0][i] == 'O') {
queue.offer(new int[]{0, i});
}
if (board[n - 1][i] == 'O') {
queue.offer(new int[]{n - 1, i});
}
}
while (!queue.isEmpty()) {
int[] cell = queue.poll();
int x = cell[0], y = cell[1];
board[x][y] = 'A';
for (int i = 0; i < 4; i++) {
int mx = x + dx[i], my = y + dy[i];
if (mx < 0 || my < 0 || mx >= n || my >= m || board[mx][my] != 'O') {
continue;
}
queue.offer(new int[]{mx, my});
}
}
for (int i = 0; i < n; i++) {
for (int j = 0; j < m; j++) {
if (board[i][j] == 'A') {
board[i][j] = 'O';
} else if (board[i][j] == 'O') {
board[i][j] = 'X';
}
}
}
}
}
克隆图
方法一:深度优先搜索
class Solution {
private HashMap <Node, Node> visited = new HashMap <> ();
public Node cloneGraph(Node node) {
if (node == null) {
return node;
}
// 如果该节点已经被访问过了,则直接从哈希表中取出对应的克隆节点返回
if (visited.containsKey(node)) {
return visited.get(node);
}
// 克隆节点,注意到为了深拷贝我们不会克隆它的邻居的列表
Node cloneNode = new Node(node.val, new ArrayList());
// 哈希表存储
visited.put(node, cloneNode);
// 遍历该节点的邻居并更新克隆节点的邻居列表
for (Node neighbor: node.neighbors) {
cloneNode.neighbors.add(cloneGraph(neighbor));
}
return cloneNode;
}
}
方法二:广度优先遍历
class Solution {
public Node cloneGraph(Node node) {
if (node == null) {
return node;
}
HashMap<Node, Node> visited = new HashMap();
// 将题目给定的节点添加到队列
LinkedList<Node> queue = new LinkedList<Node> ();
queue.add(node);
// 克隆第一个节点并存储到哈希表中
visited.put(node, new Node(node.val, new ArrayList()));
// 广度优先搜索
while (!queue.isEmpty()) {
// 取出队列的头节点
Node n = queue.remove();
// 遍历该节点的邻居
for (Node neighbor: n.neighbors) {
if (!visited.containsKey(neighbor)) {
// 如果没有被访问过,就克隆并存储在哈希表中
visited.put(neighbor, new Node(neighbor.val, new ArrayList()));
// 将邻居节点加入队列中
queue.add(neighbor);
}
// 更新当前节点的邻居列表
visited.get(n).neighbors.add(visited.get(neighbor));
}
}
return visited.get(node);
}
}
岛屿的最大面积
方法一:深度优先搜索
class Solution {
public int maxAreaOfIsland(int[][] grid) {
int ans = 0;
for (int i = 0; i != grid.length; ++i) {
for (int j = 0; j != grid[0].length; ++j) {
ans = Math.max(ans, dfs(grid, i, j));
}
}
return ans;
}
public int dfs(int[][] grid, int cur_i, int cur_j) {
if (cur_i < 0 || cur_j < 0 || cur_i == grid.length || cur_j == grid[0].length || grid[cur_i][cur_j] != 1) {
return 0;
}
grid[cur_i][cur_j] = 0;
int[] di = {0, 0, 1, -1};
int[] dj = {1, -1, 0, 0};
int ans = 1;
for (int index = 0; index != 4; ++index) {
int next_i = cur_i + di[index], next_j = cur_j + dj[index];
ans += dfs(grid, next_i, next_j);
}
return ans;
}
}
方法二:深度优先搜索 + 栈
class Solution {
public int maxAreaOfIsland(int[][] grid) {
int ans = 0;
for (int i = 0; i != grid.length; ++i) {
for (int j = 0; j != grid[0].length; ++j) {
int cur = 0;
Deque<Integer> stacki = new LinkedList<Integer>();
Deque<Integer> stackj = new LinkedList<Integer>();
stacki.push(i);
stackj.push(j);
while (!stacki.isEmpty()) {
int cur_i = stacki.pop(), cur_j = stackj.pop();
if (cur_i < 0 || cur_j < 0 || cur_i == grid.length || cur_j == grid[0].length || grid[cur_i][cur_j] != 1) {
continue;
}
++cur;
grid[cur_i][cur_j] = 0;
int[] di = {0, 0, 1, -1};
int[] dj = {1, -1, 0, 0};
for (int index = 0; index != 4; ++index) {
int next_i = cur_i + di[index], next_j = cur_j + dj[index];
stacki.push(next_i);
stackj.push(next_j);
}
}
ans = Math.max(ans, cur);
}
}
return ans;
}
}
方法三:广度优先搜索
class Solution {
public int maxAreaOfIsland(int[][] grid) {
int ans = 0;
for (int i = 0; i != grid.length; ++i) {
for (int j = 0; j != grid[0].length; ++j) {
int cur = 0;
Queue<Integer> queuei = new LinkedList<Integer>();
Queue<Integer> queuej = new LinkedList<Integer>();
queuei.offer(i);
queuej.offer(j);
while (!queuei.isEmpty()) {
int cur_i = queuei.poll(), cur_j = queuej.poll();
if (cur_i < 0 || cur_j < 0 || cur_i == grid.length || cur_j == grid[0].length || grid[cur_i][cur_j] != 1) {
continue;
}
++cur;
grid[cur_i][cur_j] = 0;
int[] di = {0, 0, 1, -1};
int[] dj = {1, -1, 0, 0};
for (int index = 0; index != 4; ++index) {
int next_i = cur_i + di[index], next_j = cur_j + dj[index];
queuei.offer(next_i);
queuej.offer(next_j);
}
}
ans = Math.max(ans, cur);
}
}
return ans;
}
}
机器人的运动范围
方法一:深度优先搜索
public int movingCount(int m, int n, int k) {
//临时变量visited记录格子是否被访问过
boolean[][] visited = new boolean[m][n];
return dfs(0, 0, m, n, k, visited);
}
public int dfs(int i, int j, int m, int n, int k, boolean[][] visited) {
//i >= m || j >= n是边界条件的判断,k < sum(i, j)判断当前格子坐标是否
// 满足条件,visited[i][j]判断这个格子是否被访问过
if (i >= m || j >= n || k < sum(i, j) || visited[i][j])
return 0;
//标注这个格子被访问过
visited[i][j] = true;
//沿着当前格子的右边和下边继续访问
return 1 + dfs(i + 1, j, m, n, k, visited) + dfs(i, j + 1, m, n, k, visited);
}
//计算两个坐标数字的和
private int sum(int i, int j) {
int sum = 0;
while (i != 0) {
sum += i % 10;
i /= 10;
}
while (j != 0) {
sum += j % 10;
j /= 10;
}
return sum;
}
方法二:广度优先搜索
public int movingCount(int m, int n, int k) {
//临时变量visited记录格子是否被访问过
boolean[][] visited = new boolean[m][n];
int res = 0;
//创建一个队列,保存的是访问到的格子坐标,是个二维数组
Queue<int[]> queue = new LinkedList<>();
//从左上角坐标[0,0]点开始访问,add方法表示把坐标
// 点加入到队列的队尾
queue.add(new int[]{0, 0});
while (queue.size() > 0) {
//这里的poll()函数表示的是移除队列头部元素,因为队列
// 是先进先出,从尾部添加,从头部移除
int[] x = queue.poll();
int i = x[0], j = x[1];
//i >= m || j >= n是边界条件的判断,k < sum(i, j)判断当前格子坐标是否
// 满足条件,visited[i][j]判断这个格子是否被访问过
if (i >= m || j >= n || k < sum(i, j) || visited[i][j])
continue;
//标注这个格子被访问过
visited[i][j] = true;
res++;
//把当前格子右边格子的坐标加入到队列中
queue.add(new int[]{i + 1, j});
//把当前格子下边格子的坐标加入到队列中
queue.add(new int[]{i, j + 1});
}
return res;
}
//计算两个坐标数字的和
private int sum(int i, int j) {
int sum = 0;
while (i != 0) {
sum += i % 10;
i /= 10;
}
while (j != 0) {
sum += j % 10;
j /= 10;
}
return sum;
}
求根到叶子节点数字之和
方法一:深度优先搜索
class Solution {
public int sumNumbers(TreeNode root) {
return dfs(root, 0);
}
public int dfs(TreeNode root, int prevSum) {
if (root == null) {
return 0;
}
int sum = prevSum * 10 + root.val;
if (root.left == null && root.right == null) {
return sum;
} else {
return dfs(root.left, sum) + dfs(root.right, sum);
}
}
}
方法二:广度优先搜索
class Solution {
public int sumNumbers(TreeNode root) {
if (root == null) {
return 0;
}
int sum = 0;
Queue<TreeNode> nodeQueue = new LinkedList<TreeNode>();
Queue<Integer> numQueue = new LinkedList<Integer>();
nodeQueue.offer(root);
numQueue.offer(root.val);
while (!nodeQueue.isEmpty()) {
TreeNode node = nodeQueue.poll();
int num = numQueue.poll();
TreeNode left = node.left, right = node.right;
if (left == null && right == null) {
sum += num;
} else {
if (left != null) {
nodeQueue.offer(left);
numQueue.offer(num * 10 + left.val);
}
if (right != null) {
nodeQueue.offer(right);
numQueue.offer(num * 10 + right.val);
}
}
}
return sum;
}
}