一般深度优先搜索问题

概述

  • 一般深度优先搜索问题 => 一般图 + 矩阵(二维数组)
  • DFS + 涂色标记(避免节点重复访问)
  • 搜索节点如何移动
  • 同时能用 DFS 或 BFS 求解的图或矩阵问题优先考虑 BFS

图论

  • 图论(Graph theory),是组合数学分支,和其他数学分支如群论、矩阵论、拓扑学有着密切关系。
  • 图是图论的主要研究对象。图是由若干给定的顶点及连接两顶点的边所构成的图形,这种图形通常用来描述某些事物之间的某种特定关系。顶点用于代表事物,连接两顶点的边则用于表示两个事物间具有这种关系。
  • 图G(V, E) => 由一系列顶点(Vertices)和边(Edges)连接而成

分类

  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

  • 时间复杂度
    1. 邻接表 => O(|V| + |E|) => 和点边都有关系 => 描述点与相邻边的映射
    2. 邻接矩阵 => O(|V|^2) => 和点有关,和边无关
  • 空间复杂度 => O(|V|)

BFS

  • 时间复杂度
    1. 邻接表 => O(|V| + |E|) => 和点边都有关系 => 描述点与相邻边的映射
    2. 邻接矩阵 => O(|V|^2) => 和点有关,和边无关
  • 空间复杂度 => O(|V|)

一般 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) => 以左下角为原点 => 注意移动边界限制

  • 向上移动 => (x, y + 1)
  • 向下移动 => (x, y - 1)
  • 向左移动 => (x - 1, y)
  • 向右移动 => (x + 1, y)
  • 向右上移动 => (x + 1, y + 1)
  • 向左上移动 => (x - 1, y + 1)
  • 向左下移动 => (x - 1, y - 1)
  • 向右下移动 => (x + 1, y - 1)
// 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. 中的邻接矩阵中索引表示事物,在二维矩阵矩阵中的点表示事物
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 202,802评论 5 476
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,109评论 2 379
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 149,683评论 0 335
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,458评论 1 273
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,452评论 5 364
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,505评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,901评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,550评论 0 256
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,763评论 1 296
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,556评论 2 319
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,629评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,330评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,898评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,897评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,140评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,807评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,339评论 2 342

推荐阅读更多精彩内容