Breadth First Search(BFS) and Depth First Search(DFS) - HolmesJJ/Data-Structures-and-Algorithms GitHub Wiki

入门链接

图Graph, 深度优先遍历(DFS), 广度优先遍历(BFS)【数据结构和算法入门9】
图的存储及广度、深度优先搜索
洛谷 普及组试炼场 - 深度优先搜索(DFS)
洛谷 普及组试炼场 - 广度优先搜索(BFS)

图解

graph
广度优先搜索BFS 深度优先搜索DFS
graph graph

详解

图的广度优先搜索(BFS)和深度优先搜索(DFS)算法解析
数据结构:图之DFS与BFS的复杂度分析
无权重最短路径问题:广度优先搜索 & Java 实现

标准写法

广度优先搜索BFS(邻接表)

public class BFSTraverse<T> {

    // 访问路径
    private ArrayList<Node<T>> path;
    // 标记结点是否已经被访问
    private boolean[] visited;
    private AdjacencyList<T> adjList;
    // 构造辅助队列
    Queue<ArrayList<AdjListNode>> qn;

    public BFSTraverse(AdjacencyList<T> adjList) {
        this.adjList = adjList;
        this.visited = new boolean[adjList.size()];
        Arrays.fill(visited, false);
    }

    /**
     * 通过辅助队列,实现Bfs算法
     *     1. 先将一个链表的头节点加入队列
     *     2. 通过for,链表内每一个节点所对应的链表的头结点加入队列中
     *     3. 改变节点的状态, 将节点加入到路径中即可
     */
    public void BFS() {
        if (adjList == null || adjList.getNodes().size() == 0) {
            return;
        }
        path = new ArrayList<>();
        qn = new LinkedList<>();
        // 确保所有结点被访问过,得出的结果正确性毋庸置疑
        for (int i = 0; i < adjList.size(); i++) {
            // 判断结点是否被访问过
            if (!visited[i]) {
                // 将该结点标记访问过
                visited[i] = true;
                // 添加到路径中
                path.add(adjList.getNodes().get(i));
                // 添加改链表的头结点
                qn.add(adjList.getNodes().get(i).getRoot());
                // 对该结点的链表进行访问
                BFSVisit();
            }
        }
    }

    private void BFSVisit() {
        while (!qn.isEmpty()) {
            // 从第一个开始出队
            ArrayList<AdjListNode> list = qn.remove();
            // 这个 for 用于将所有该链表里的 结点 加入到队列中
            for (AdjListNode adjListNode : list) {
                // 判断结点是否被访问过
                if (!visited[adjListNode.getIndex()]) {
                    // 将该结点标记访问过
                    visited[adjListNode.getIndex()] = true;
                    // 添加到路径中
                    path.add(adjList.getNodes().get(adjListNode.getIndex()));
                    // 将该节点所代表的链表加入到队列中
                    qn.add(adjList.getNodes().get(adjListNode.getIndex()).getRoot());
                }
            }
        }
    }

    public void print() {
        System.out.print("搜索路径: " + path.get(0).getData());
        for (int i = 1; i < path.size(); i++) {
            System.out.print(" -> " + path.get(i).getData());
        }
        System.out.println();
    }
}

深度优先搜索DFS(邻接表)

public class DFSTraverse<T> {

    // 访问路径
    private ArrayList<Node<T>> path;
    // 标记结点是否已经被访问
    private boolean[] visited;
    private AdjacencyList<T> adjList;

    public DFSTraverse(AdjacencyList<T> adjList) {
        this.adjList = adjList;
        this.visited = new boolean[adjList.size()];
        Arrays.fill(visited, false);
    }

    /**
     * 第一个for用于遍历所有的结点 只有全部结点都被访问过才有可能返回
     * 遍历步骤:
     *      1. 如果该节点未被访问过,则接下来访问它的链表
     *      2. 将链表中每一个元素当做新的链表头进行递归操作
     *      3. path用于记录访问顺序
     *
     * 总结 :
     *      第一个for确保所有节点都被访问过
     *      visited则确保每条链表都被检查过,但不一定进行操作
     */
    public void DFS() {
        if (adjList == null || adjList.getNodes().size() == 0) {
            return;
        }
        path = new ArrayList<>();
        // 确保所有结点被访问过,得出的结果正确性毋庸置疑
        for (int i = 0; i < adjList.size(); i++) {
            // 判断结点是否被访问过
            if (!visited[i]) {
                // 将该结点标记访问过
                visited[i] = true;
                // 添加到路径中
                path.add(adjList.getNodes().get(i));
                // 对该结点的链表进行访问
                DFSVisit(adjList.getNodes().get(i).getRoot());
            }
        }
    }

    private void DFSVisit(ArrayList<AdjListNode> list) {
        // list.get(i).getIndex()代表某个结点的下标
        for (int i = 0; i < list.size(); i++) {
            // 判断结点是否被访问过
            if (!visited[list.get(i).getIndex()]) {
                // 将该结点标记访问过
                visited[list.get(i).getIndex()] = true;
                // 添加到路径中
                path.add(adjList.getNodes().get(list.get(i).getIndex()));
                // 对该节点的链表进行访问
                DFSVisit(adjList.getNodes().get(list.get(i).getIndex()).getRoot());
            }
        }
    }

    public void print() {
        System.out.print("搜索路径: " + path.get(0).getData());
        for (int i = 1; i < path.size(); i++) {
            System.out.print(" -> " + path.get(i).getData());
        }
        System.out.println();
    }
}

测试

graph
ArrayList<Node<String>> nodes = new ArrayList<>();
nodes.add(new Node<String>("V1"));
nodes.add(new Node<String>("V2"));
nodes.add(new Node<String>("V3"));
nodes.add(new Node<String>("V4"));
nodes.add(new Node<String>("V5"));
nodes.add(new Node<String>("V6"));
nodes.add(new Node<String>("V7"));
nodes.add(new Node<String>("V8"));

AdjacencyList<String> adjList = new AdjacencyList<String>(nodes);
adjList.addEdge(0, 1, 0);
adjList.addEdge(0, 2, 0);
adjList.addEdge(1, 3, 0);
adjList.addEdge(2, 5, 0);
adjList.addEdge(2, 6, 0);
adjList.addEdge(3, 7, 0);
adjList.addEdge(4, 1, 0);
adjList.addEdge(4, 7, 0);
adjList.addEdge(5, 6, 0);

DFSTraverse<String> dfs = new DFSTraverse<String>(adjList);
dfs.DFS();
dfs.print();
// 搜索路径: V1 -> V2 -> V4 -> V8 -> V3 -> V6 -> V7 -> V5

BFSTraverse<String> bfs = new BFSTraverse<String>(adjList);
bfs.BFS();
bfs.print();
// 搜索路径: V1 -> V2 -> V3 -> V4 -> V6 -> V7 -> V8 -> V5

广度优先搜索BFS 和 深度优先搜索DFS

广度优先搜索BFS 深度优先搜索DFS
通俗解释 类似层次遍历,一行一行搜索 一头扎到底
父边 红边为各个结点的父边 红边为各个结点的父边
graph graph
父边构成一棵树,无环 父边构成一棵树,无环
父边结点s到其它各个结点的最短路径 父边不是结点s到其它各个结点的最短路径
时间复杂度(邻接表) O(V + E) O(V + E)
时间复杂度(邻接矩阵) O(V^2) O(V^2)
数据结构 队列Queue
每次访问一个结点,把所有该结点的未访问的邻结点都添加到队列
栈Stack
每次访问一个结点,把所有该结点的未访问的邻结点都添加到栈
访问了每一个结点
访问了每一条边
没有访问每一条路径
这是BFS不能用来找到**加权图的最短路径**的原因
访问了每一个结点
访问了每一条边
没有访问每一条路径
这是DFS不能用来找到**加权图的最短路径**的原因
  • 父边Parent Edge:通过BFS/DFS,通过某条边e第一次访问到当前的结点v,则e为v的父边
  • 使用BFS或DFS遍历每一条路径是非常昂贵的
    • graph
    • 如上图,每个菱形都有2种选择(选上两条边或下两条边),从src到dist有2^(n/4)种不同的路径,n为边数

要点

  • 广度优先搜索BFS可用于求无权图的最短路径
  • 啥时候O(E +V) 可以看成是O(E +V) = O(E),Connected graph 的时候,因为不connected的话V可能远大于E,connected的话E至少是V-1

适用场景

迷宫可以以二维数组来存储表示。0表示通路,1表示障碍
graph

Leetcode

网格中的最短路径

⚠️ **GitHub.com Fallback** ⚠️