算法中的 DFS 和 BFS

背景描述

最近学习了 极客大学–算法训练营 的课程,这一篇文章总结一下算法中的 DFSBFS 相关知识内容。其实有关 DFSBFS 的知识内容并不多,也不是非常难,类似于套公式一般直接套上去,就可以解决问题,不过在 Leetcode 网站上刷题就会发现,的确很多都能够通过 DFSBFS 来解决,但是有些题比较隐晦,需要进行思路转换才能抓住问题的关键。

深度优先遍历(DFS)

深度优先搜索算法(英语:Depth-First-Search,DFS) 是一种用于遍历或搜索树或图的算法。这个算法会尽可能深的搜索树的分支。当节点v的所在边都己被探寻过,搜索将回溯到发现节点v的那条边的起始节点。这一过程一直进行到已发现从源节点可达的所有节点为止。

DFS 是一种利用栈(Stack)或者递归实现的搜索算法。

递归实现

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
import java.util.ArrayList;
import java.util.List;
import java.util.Stack;

public class DepthFirstSearch {

public List<Integer> dfs(TreeNode root) {
List<Integer> result = new ArrayList<>();
helper(root, result);
return result;
}

private void helper(TreeNode node, List<Integer> result) {
if (node == null) {
return;
}
result.add(node.val);
helper(node.left, result);
helper(node.right, result);
}

public class TreeNode {
public int val;
public TreeNode left, right;
public TreeNode(int val) {
this.val= val;
}
}
}

Stack 实现

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
import java.util.ArrayList;
import java.util.LinkedList;
import java.util.List;
import java.util.Queue;
import java.util.Stack;

public class DepthFirstSearch {

public List<Integer> dfs(TreeNode root) {
List<Integer> result = new ArrayList<>();
Stack<TreeNode> stack = new Stack<>();
stack.add(root);
while (!stack.isEmpty()) {
// 弹出栈顶元素
TreeNode currentNode = stack.pop();
result.add(currentNode.val);
if (currentNode.right != null) {
// 深度优先遍历,先遍历左边,后遍历右边,栈先进后出
stack.push(currentNode.right);
}
if (currentNode.left != null) {
stack.push(currentNode.left);
}
}
return result;
}

public class TreeNode {
public int val;
public TreeNode left, right;
public TreeNode(int val) {
this.val= val;
}
}
}

广度优先遍历(BFS)

广度优先搜索算法(英语:Breadth-First Search,缩写为BFS),又译作宽度优先搜索,或横向优先搜索,是一种图形搜索算法。简单的说,BFS是从根节点开始,沿着树的宽度遍历树的节点。如果所有节点均被访问,则算法中止。

BFS 是一种是一种利用队列(Queue)实现的搜索算法,其实现思想为:

  • 队列不为空则循环;
  • 将未访问的邻接点压入队列后面,然后从前面取出并访问(这样就做到了广度优先)。

Queue 实现

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
import java.util.ArrayList;
import java.util.LinkedList;
import java.util.List;
import java.util.Queue;

public class BreadthFirstSearch {

public List<Integer> bfs(TreeNode root) {
List<Integer> result = new ArrayList<>();
// 利用队列实现优先搜索算法
Queue<TreeNode> queue = new LinkedList<>();
queue.add(root);
while (!queue.isEmpty()) {
TreeNode currentNode = queue.poll();
result.add(currentNode.val);
if (currentNode.left != null) {
queue.add(currentNode.left);
}
if (currentNode.right != null) {
queue.add(currentNode.right);
}
}
return result;
}

public class TreeNode {
public int val;
public TreeNode left, right;
public TreeNode(int val) {
this.val= val;
}
}
}

括号生成问题

22. 括号生成

Difficulty: 中等

数字 n 代表生成括号的对数,请你设计一个函数,用于能够生成所有可能的并且 有效的 括号组合。

示例:

1
2
3
4
5
6
7
8
输入:n = 3
输出:[
"((()))",
"(()())",
"(())()",
"()(())",
"()()()"
]

这个题目乍一看和 BFS、DFS 完全是不沾边的,但是通过将具体题目过程进行抽象转换,也是可以用 BFS、DFS 实现的;具体思路是将要生成结果看做是 root,将左右括号使用的数量作为左右节点向外扩展,扩展到最后,左右括号数量都是 0,那么就得到了一个最终的结果。在扩展的过程中,如果一层层处理,那就是 BFS 了;如果是在一侧一直处理,处理完一侧再回溯继续处理,就是 DFS 了。

括号生成问题 DFS 代码(递归实现)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
class Solution {
public List<String> generateParenthesis(int n) {
List<String> result = new ArrayList<>();
generateOneByOne("", result, n, n);
return result;
}

private void generateOneByOne(String s, List<String> result, int left, int right) {
if (left == 0 && right == 0) {
result.add(s);
return;
}
if (left > 0) {
generateOneByOne(s + "(", result, left - 1, right);
}
if (right > left) {
generateOneByOne(s + ")", result, left, right - 1);
}
}
}

括号生成问题 DFS 代码(Stack 实现)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
class Solution {

class Node {
private String res;
private int left;
private int right;
public Node (String res, int left, int right) {
this.res = res;
this.left = left;
this.right = right;
}
}

public List<String> generateParenthesis(int n) {
List<String> result = new ArrayList<>();
if (n == 0) {
return result;
}
Stack<Node> stack = new Stack<>();
stack.add(new Node("", n, n));
while (!stack.isEmpty()) {
Node currentNode = stack.pop();
if (currentNode.left == 0 && currentNode.right == 0) {
result.add(currentNode.res);
}
if (currentNode.right > currentNode.left) {
stack.push(new Node(currentNode.res + ")", currentNode.left, currentNode.right - 1));
}
if (currentNode.left > 0) {
stack.push(new Node(currentNode.res + "(", currentNode.left - 1, currentNode.right));
}
}
return result;
}
}

括号生成问题 BFS 代码(Queue 实现)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
class Solution {

class Node {
private String res;
private int left;
private int right;
public Node (String res, int left, int right) {
this.res = res;
this.left = left;
this.right = right;
}
}

public List<String> generateParenthesis(int n) {
List<String> result = new ArrayList<>();
if (n == 0) {
return result;
}
Queue<Node> queue = new LinkedList<>();
queue.add(new Node("", n, n));
while (!queue.isEmpty()) {
Node currentNode = queue.poll();
if (currentNode.left == 0 && currentNode.right == 0) {
result.add(currentNode.res);
}
if (currentNode.left > 0) {
queue.add(new Node(currentNode.res + "(", currentNode.left - 1, currentNode.right));
}
if (currentNode.right > currentNode.left) {
queue.add(new Node(currentNode.res + ")", currentNode.left, currentNode.right - 1));
}
}
return result;
}
}

其他题目(持续补充)

102. 二叉树的层序遍历

该题目本来是一道典型的 BFS 题目,但是在输出结果上有特殊的要求,所以要在循环队列时记录当前的层次,这样才能满足结果输出。

参考资料

感谢您的阅读,本文由 董宗磊的博客 版权所有。如若转载,请注明出处:董宗磊的博客(https://dongzl.github.io/2020/05/23/27-Algorithm-DFS-BFS/
Java 线程池中 Future & FutureTask 使用总结
PT-OSC 添加唯一索引导致数据丢失问题分析