「这是我参与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):
- 利用队列实现
- 从源节点开始依次按照宽度进队列,然后弹出
- 每弹出一个点,把该节点所有没有进过队列的邻接点放入队列
- 直到队列变空
数据结构:队列、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):
- 利用栈实现
- 从源节点开始把节点按照深度放入栈,然后弹出
- 每弹出一个点,把该节点下一个没有进过栈的邻接点放入栈
- 直到栈变空
数据结构:Set(不走环机制,禁止走环路)、递归或栈
入栈就打印
栈中保存的是从哪走到哪的整个路径过程
分析过程:
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;
}
}
}
}
复制代码
五、总结
图的宽度优先遍历:螃蟹横着走
图的深度优先遍历:一条路没走完就走到死,走完了就往上看还有哪些路没走完