一般深度优先搜索问题

2022-09-26  本文已影响0人  bowen_wu

概述

图论

分类

  1. 有向图 => 有向边组成的图
    1. 有向边e => e = (u, v) => u 为边的起始顶点,v 为边的终止顶点
    2. 有向无环图(DAG) => 无法从某个顶点出发经过若干条边回到该点的有向图 => 只存在于有向图
  2. 无向图
  3. 带权图 => 边有权值

图的表示

  1. 邻接矩阵 => Adjacency Matrix => 容易变为稀疏矩阵,开辟空间巨大,浪费内存
    1. 无向图节点 i 的 => 第 i 行或第 i 列非 0 个数
    2. 有向图节点 i 的出度 => 第 i 行(行表示起点)非 0 个数
    3. 有向图节点 i 的入度 => 第 i 列(列表示终点)非 0 个数
  2. 邻接表 => Adjacency List => 描述节点与相邻边的映射,围绕数据结构去建模
    1. 链表
    2. Map<Node, List<Node>>
    3. 出度方便计算,入度需要遍历,或者创建一个逆邻接表,用来表示入度的节点

图搜索

DFS

BFS

一般 DFS 问题

图中 DFS

public class UndirectedGraph {
    // 邻接表 => key: 当前节点  value: 邻接节点集合
    private final Map<Node, List<Node>> adjacencyList;

    // 是否被访问涂色过
    private final boolean[] marked;

    // 统计连通分量
    private int connectComponentCount;

    public UndirectedGraph(int vertexCount) {
        this.adjacencyList = new HashMap<>();
        this.marked = new boolean[vertexCount];
        this.connectComponentCount = 0;
    }

    // Method
    public void addVertex(Node v) {
        if (!adjacencyList.containsKey(v)) {
            adjacencyList.put(v, new ArrayList<>());
        }
    }

    public void addEdge(Node u, Node v) {
        if (!adjacencyList.containsKey(u)) {
            addVertex(u);
        }
        if (!adjacencyList.containsKey(v)) {
            addVertex(v);
        }
        adjacencyList.get(u).add(v);
        adjacencyList.get(v).add(u);
    }

    public void printGraph() {
        for (Node key : adjacencyList.keySet()) {
            System.out.println(key.getNo() + ": " + adjacencyList.get(key).stream().map(Node::getNo).map(String::valueOf).collect(Collectors.joining(",", "[", "]")));
        }
    }

    // DFS 模板 => 遍历全图
    public void dfsInGraph() {
        Arrays.fill(marked, false);
        for (Node startNode : adjacencyList.keySet()) {
            if (!marked[startNode.getNo()]) {
                helper(startNode);
                connectComponentCount++;
            }
        }
    }

    // DFS 模板 => 以 start 节点为起点在某一连通分量上 DFS
    public void dfsInComponent(Node start) {
        Arrays.fill(marked, false);
        helper(start);
    }

    // DFS helper 函数
    public void helper(Node start) {
        marked[start.getNo()] = true;
        System.out.println("DFS current node: " + start.getNo() + " value: " + start.getValue());

        for (Node adjNode : adjacencyList.get(start)) {
            if (!marked[adjNode.getNo()]) {
                // 没有被访问过
                helper(adjNode);
            }
        }
    }

    public static void main(String[] args) {
        // 0 - 1 - 4
        // |   | /
        // 2 - 3
        UndirectedGraph undirectedGraph = new UndirectedGraph(7);
        Node zero = new Node(0, 1);
        Node one = new Node(1, 2);
        Node two = new Node(2, 3);
        Node three = new Node(3, 4);
        Node four = new Node(4, 5);
        Node five = new Node(5, 6);
        Node six = new Node(6, 7);
        undirectedGraph.addEdge(zero, one);
        undirectedGraph.addEdge(zero, two);
        undirectedGraph.addEdge(one, four);
        undirectedGraph.addEdge(one, three);
        undirectedGraph.addEdge(three, two);
        undirectedGraph.addEdge(three, four);
        undirectedGraph.addEdge(five, six);
        undirectedGraph.printGraph();
        System.out.println("Starting from node 0");
        undirectedGraph.dfsInComponent(zero);
        System.out.println("Starting from node 1");
        undirectedGraph.dfsInComponent(one);
        System.out.println("Traversal graph");
        undirectedGraph.dfsInGraph();
        System.out.println(undirectedGraph.connectComponentCount);
    }

}

class Node {
    int no;
    int value;

    public Node(int no, int value) {
        this.no = no;
        this.value = value;
    }

    public int getNo() {
        return no;
    }

    public void setNo(int no) {
        this.no = no;
    }

    public int getValue() {
        return value;
    }

    public void setValue(int value) {
        this.value = value;
    }
}

模板

import java.util.HashMap;

class DFSInGraph {
    public void dfsInGraph(int nodeNum, int[][] edges, int[][] adjacencyMatrix) {
        // 0. 邻接表 => Map<Node, List<Node>> => key: current node  value: adjacency nodes 
        //    邻接矩阵 => int[][] => 索引值代表节点(事物),(x, y) 的值表示是否相邻
        // 1. check input
        if (adjacencyMatrix == null || adjacencyMatrix.length == 0 || adjacencyMatrix[0] == null || adjacencyMatrix[0].length == 0) {
            return;
        }

        // 2. 边的数组 => 构建邻接表 => 非必须
        Map<Integer, List<Integer>> adjacencyList = new HashMap<>();
        for (int i = 0; i < nodeNum; i++) {
            adjacencyList.put(i, new ArrayList<>());
        }
        for (int i = 0; i < edges.length; i++) {
            int u = edges[i][0];
            int v = edges[i][1];
            adjacencyList.get(u).add(v);
            adjacencyList.get(v).add(u);
        }

        // 3. marked
        boolean[] visited = new boolean[adjacencyMatrix.length];
        int connectComponentCount = 0;

        // 4. traversal adjacency matrix
        for (int i = 0; i < adjacencyMatrix.length; i++) {
            if (!visited[i]) {
                helper(adjacencyMatrix, adjacencyList, visited, i);
                connectComponentCount++;
            }
        }

        // 4. traversal adjacency list
        for (int node : adjacencyList.keySet()) {
            if (visited[node]) {
                helper(adjacencyMatrix, adjacencyList, visited, node);
                connectComponentCount++;
            }
        }
    }

    private void helper(int[][] adjacencyMatrix, Map<Integer, List<Integer>> adjacencyList, boolean[] visited, int start) {
        // marked
        visited[start] = true;

        // traversal adjacency node in adjacencyMatrix
        for (int i = 0; i < adjacencyMatrix[start].length; i++) {
            if (specialCondition && !visited[i]) {
                helper(adjacencyMatrix, visited, i);
            }
        }

        // traversal adjacency node in adjacencyList
        for (int item : adjacencyList.get(start)) {
            if (specialCondition && !visited[item]) {
                helper(adjacencyMatrix, adjacencyList, visited, item);
            }
        }
    }
}

二维问题

分类

  1. 迷宫问题 => 二维数组上的回溯法
  2. 岛屿问题 => 连通分量

特点

重点

  1. DFS + 涂色标记(标记已访问过的位置)
  2. 剪枝

二维空间移动

坐标 (x, y) => 以左下角为原点 => 注意移动边界限制

// version1
final int[][] directions = {{0, 1}, {1, 0}, {0, -1}, {-1, 0}};
for (int[] offset : directions) {
    Point next = new Point(cur.x + offset[0], current.y + offset[1]);
}

// version2
int[] dx = {1, 0, -1, 0};
int[] dy = {0, 1, 0, -1};
for (int i = 0; i < 4; i++) {
    Point next = new Point(cur.x + dx[i], cur.y + dy[i]);
}

模板

考虑问题

  1. specialCondition 是什么?=> 开始能做 DFS 是否有特殊条件?
  2. pruning
  3. how to move
  4. is need backtracking
class DFSInMatrix {
    public void dfsInMatrix(int[][] matrix) {
        if (matrix == null || matrix.length == 0 || matrix[0] == null || matrix[0].length == 0) {
            return;
        }

        // marked 
        int m = matrix.length;
        int n = matrix[0].length;
        boolean[][] visited = new boolean[m][n];
        int connectedComponentCount = 0;

        // traversal
        for (int i = 0; i < m; i++) {
            for (int j = 0; j < n; j++) {
                if (specialCondition && !visited[i][j]) {
                    dfs(matrix, visited, i, j);
                    connectedComponentCount++;
                }
            }
        }
    }

    private void dfs(int[][] matrix, boolean[][] visited, int x, int y) {
        // pruning => 此处的 pruning 也可以放置在 move 中
        if (condition) {
            return;
        }

        // marked
        visited[x][y] = true;

        // move
        int[] dx = {1, 0, -1, 0};
        int[] dy = {0, 1, 0, -1};
        for (int i = 0; i < 4; i++) {
            int newX = x + dx[i];
            int newY = y + dy[i];

            // 此处可以进行 pruning => if (checkRange(matrix, newX, newY) && !visited[newX][newY] && specialCondition)
            if (checkRange(matrix, newX, newY) && !visited[newX][newY]) {
                dfs(matrix, visited, newX, newY);
            }
        }
    }

    private boolean checkRange(int[][] matrix, int x, int y) {
        return x >= 0 && x < matrix.length && y >= 0 && y < matrix[0].length;
    }
}

知识点

  1. 度 => 图中的节点有几个边就有几个度
  2. 连通分量 => 一个图有几个互不相干的部分
  3. 中的邻接矩阵中索引表示事物,在二维矩阵矩阵中的点表示事物
上一篇 下一篇

猜你喜欢

热点阅读