图介绍及图的遍历

「这是我参与2022首次更文挑战的第38天,活动详情查看:2022首次更文挑战」。

一、图的定义

  • 由点的集合和边的集合构成
  • 虽然存在有向图和无向图的概念,但实际上都可以用有向图来表达
  • 边上可能带有权值

有向图:边带方向

无向图:可以认为边有两个方向,它指向别人,别人再指向它

二、图结构的表达

  • 邻接表法
  • 邻接矩阵法
  • 除此之外还有其他众多的方式

1、邻接表法

key:每个节点,value:它的邻居

也可以表示带有权重的边

这张表可以用HashMap<T, List<V>>表示

2、邻接矩阵法

matrix表示,a节点能到b节点,用某个值表示,不能到用正无穷表示,自己到自己为0

三、图结构模板定义

1、边的定义模型

public class Edge {

    public int weight;
    public Node from;
    public Node to;

    public Edge(int weight, Node from, Node to) {
        this.weight = weight;
        this.from = from;
        this.to = to;
    }
}
复制代码

2、点(节点)的定义模型

public class Node {

    public int value;
    public int in; // 哪些节点指向它的个数
    public int out; // 它指向哪些节点的个数
    public ArrayList<Node> nexts; // 它指向的节点(邻居)
    public ArrayList<Edge> edges; // 它指向的节点(邻居)所用的边

    public Node(int value) {
        this.value = value;
        in = 0;
        out = 0;
        nexts = new ArrayList<>();
        edges = new ArrayList<>();
    }
}
复制代码

3、图的定义模型

public class Graph {

    public HashMap<Integer, Node> nodes; // 点
    public HashSet<Edge> edges; // 边

    public Graph() {
        nodes = new HashMap<>();
        edges = new HashSet<>();
    }
}
复制代码

4、图的生成

public class GraphGenerator {

    // 图的生成
    // matrix 所有的边
    // N*3 的矩阵
    // [weight, from节点上面的值,to节点上面的值]
    // [ 5 , 0 , 7]
    // [ 3 , 0,  1]
    // [ 2 , 1,  7]
    public static Graph createGraph(int[][] matrix) {
        Graph graph = new Graph();
        for (int i = 0; i < matrix.length; i++) {
            // 拿到每一条边, matrix[i] 
            int weight = matrix[i][0];
            int from = matrix[i][1];
            int to = matrix[i][2];
            if (!graph.nodes.containsKey(from)) {
                graph.nodes.put(from, new Node(from));
            }
            if (!graph.nodes.containsKey(to)) {
                graph.nodes.put(to, new Node(to));
            }
            Node fromNode = graph.nodes.get(from);
            Node toNode = graph.nodes.get(to);
            Edge newEdge = new Edge(weight, fromNode, toNode);
            fromNode.nexts.add(toNode);
            fromNode.out++;
            toNode.in++;
            fromNode.edges.add(newEdge);
            graph.edges.add(newEdge);
        }
        return graph;
    }
}
复制代码

四、图的遍历

1、图的宽度优先遍历

图宽度优先遍历(BFS):

  1. 利用队列实现
  2. 从源节点开始依次按照宽度进队列,然后弹出
  3. 每弹出一个点,把该节点所有没有进过队列的邻接点放入队列
  4. 直到队列变空

数据结构:队列、Set(去重)

不去重的话,有可能宽度优先遍历就跑不完了,结束不了

// 从node出发,进行宽度优先遍历
public static void bfs(Node start) {
    if (start == null) {
        return;
    }
    Queue<Node> queue = new LinkedList<>();
    HashSet<Node> set = new HashSet<>();
    queue.add(start);
    set.add(start);
    while (!queue.isEmpty()) {
        Node cur = queue.poll();
        System.out.println(cur.value);
        for (Node next : cur.nexts) {
            if (!set.contains(next)) {
                set.add(next);
                queue.add(next);
            }
        }
    }
}
复制代码

2、图的深度优先遍历

图深度优先遍历(DFS):

  1. 利用栈实现
  2. 从源节点开始把节点按照深度放入栈,然后弹出
  3. 每弹出一个点,把该节点下一个没有进过栈的邻接点放入栈
  4. 直到栈变空

数据结构:Set(不走环机制,禁止走环路)、递归或栈

入栈就打印

栈中保存的是从哪走到哪的整个路径过程

image.png

分析过程:

a 入栈,a 进Set,Set:{a},栈:{a},运动轨迹 a

打印a

a弹栈,栈{},a 的邻居节点有{b,c,d,k}

遍历a的邻居节点{b,c,d,k}

b是否在Set中,没有,b进Set,Set:{a,b},则 a 入栈,b 入栈,栈:{a,b},运动轨迹 a->b

打印b

弹栈b,b的邻居有{e,c},栈:{a}

遍历b的邻居节点{e,c}

e是否在Set中,没有,e进Set,Set:{a,b,e},则 b 入栈,e 入栈,栈:{a,b,e},运动轨迹 a->b->e

打印e

弹栈e,e的邻居有{c},栈:{a,b}

遍历e的邻居节点{c}

c是否在Set中,没有,c进Set,Set:{a,b,e,c},则 e 入栈,c 入栈,栈:{a,b,e,c},运动轨迹 a->b->e->c

打印c

弹栈c,c的邻居有{d},栈:{a,b,e}

遍历c的邻居节点{d}

d是否在Set中,没有,d进Set,Set:{a,b,e,c,d},则 c 入栈,d 入栈,栈:{a,b,e,c,d},运动轨迹 a->b->e->c->d

打印d

弹出d,d没有邻居,弹出c,c有邻居d,d在Set中,弹出e,e有邻居c,c在Set中,弹出b,b有邻居{e,c},e和c都在Set中,弹出a,a有邻居{b,c,d,k},b、c、d都在Set中,而k不在Set中,k进Set,Set:{a,b,e,c,d,k},则 a 入栈,k 入栈,栈:{a,k},运动轨迹 a->k

打印k

弹出k,k的邻居有{d},d在Set中,弹出a,a的邻居有{b,c,d,k},而{b,c,d,k}都在Set中,至此完毕

public static void dfs(Node node) {
    if (node == null) {
        return;
    }
    Stack<Node> stack = new Stack<>();
    HashSet<Node> set = new HashSet<>();
    stack.add(node);
    set.add(node);
    System.out.println(node.value); // 入栈就打印
    while (!stack.isEmpty()) {
        Node cur = stack.pop();
        for (Node next : cur.nexts) {
            if (!set.contains(next)) {
                stack.push(cur);
                stack.push(next);
                set.add(next);
                System.out.println(next.value); // 入栈就打印
                break;
            }
        }
    }
}
复制代码

五、总结

图的宽度优先遍历:螃蟹横着走

图的深度优先遍历:一条路没走完就走到死,走完了就往上看还有哪些路没走完

猜你喜欢

转载自juejin.im/post/7068245921619787812