遇到图的代码题:分析与解决指南
一、图是什么?
图(Graph)是一种非线性数据结构,由顶点(Vertex) 和边(Edge) 组成,用于表示对象之间的关系。数学表示为 G = (V, E)
,其中:
V
是顶点集合E
是连接顶点的边集合
图的分类:
二、解决什么问题
图结构主要用于解决以下三类问题:
- 关系建模问题:社交网络(顶点=用户,边=好友关系)、交通网络(顶点=车站,边=路线)
- 路径查找问题:最短路径(导航)、关键路径(项目管理)
- 连通性问题:网络连接检测、社交圈分析
三、核心分析方法
遇到图类问题时,采用以下系统分析方法:
1. 问题识别框架
2. 算法选择矩阵
问题类型 | 推荐算法 | 时间复杂度 | 适用场景 |
---|---|---|---|
遍历所有节点 | BFS/DFS | O(V+E) | 社交网络好友推荐 |
最短路径(无权) | BFS | O(V+E) | 最少转机路线 |
最短路径(带权) | Dijkstra | O((V+E)logV) | 地图导航 |
最小生成树 | Prim/Kruskal | O(ElogV) | 网络布线优化 |
拓扑排序 | Kahn算法 | O(V+E) | 任务依赖调度 |
强连通分量 | Kosaraju/Tarjan | O(V+E) | 社交圈子分析 |
四、解决步骤
1. 图的表示
邻接表(推荐):
java
// Java 17+ 语法
List<List<Integer>> graph = new ArrayList<>();
// 添加顶点
IntStream.range(0, n).forEach(i -> graph.add(new ArrayList<>()));
// 添加边 (无向图)
graph.get(u).add(v);
graph.get(v).add(u);
邻接矩阵:
java
int[][] matrix = new int[n][n];
matrix[u][v] = 1; // 无权图
matrix[u][v] = weight; // 带权图
2. 算法实现模板
BFS模板:
java
void bfs(List<List<Integer>> graph, int start) {
boolean[] visited = new boolean[graph.size()];
Queue<Integer> queue = new ArrayDeque<>();
queue.offer(start);
visited[start] = true;
while (!queue.isEmpty()) {
int node = queue.poll();
// 处理当前节点
for (int neighbor : graph.get(node)) {
if (!visited[neighbor]) {
visited[neighbor] = true;
queue.offer(neighbor);
}
}
}
}
DFS模板:
java
void dfs(List<List<Integer>> graph, int node, boolean[] visited) {
visited[node] = true;
// 处理当前节点
for (int neighbor : graph.get(node)) {
if (!visited[neighbor]) {
dfs(graph, neighbor, visited);
}
}
}
3. 特殊问题处理技巧
- 环检测:DFS中维护递归栈,或使用拓扑排序
- 最短路径记录:BFS/Dijkstra中维护
prev[]
数组 - 连通分量:DFS/BFS计数遍历次数
- 双向BFS:优化两点间路径搜索
五、应用场景实例
场景1:社交网络好友推荐(BFS应用)
java
List<Integer> recommendFriends(List<List<Integer>> graph, int user, int level) {
boolean[] visited = new boolean[graph.size()];
Queue<Integer> queue = new ArrayDeque<>();
List<Integer> result = new ArrayList<>();
int currentLevel = 0;
queue.offer(user);
visited[user] = true;
while (!queue.isEmpty() && currentLevel <= level) {
int size = queue.size();
for (int i = 0; i < size; i++) {
int node = queue.poll();
if (currentLevel == level && node != user) {
result.add(node);
}
for (int neighbor : graph.get(node)) {
if (!visited[neighbor]) {
visited[neighbor] = true;
queue.offer(neighbor);
}
}
}
currentLevel++;
}
return result;
}
场景2:课程安排(拓扑排序)
java
// Kahn算法实现拓扑排序
List<Integer> topologicalSort(int numCourses, int[][] prerequisites) {
// 构建图与入度数组
List<List<Integer>> graph = new ArrayList<>();
int[] indegree = new int[numCourses];
for (int i = 0; i < numCourses; i++) {
graph.add(new ArrayList<>());
}
for (int[] edge : prerequisites) {
graph.get(edge[1]).add(edge[0]); // edge[1] → edge[0]
indegree[edge[0]]++;
}
// BFS初始化
Queue<Integer> queue = new ArrayDeque<>();
for (int i = 0; i < numCourses; i++) {
if (indegree[i] == 0) queue.offer(i);
}
// 拓扑排序
List<Integer> result = new ArrayList<>();
while (!queue.isEmpty()) {
int course = queue.poll();
result.add(course);
for (int next : graph.get(course)) {
if (--indegree[next] == 0) {
queue.offer(next);
}
}
}
return result.size() == numCourses ? result : Collections.emptyList();
}
六、重要注意事项
- 避免死循环:DFS/BFS必须使用
visited
数组记录访问状态 - 稠密图 vs 稀疏图:
- 稀疏图(边少):使用邻接表
- 稠密图(边多):考虑邻接矩阵
- Java特性优化:
- 使用
ArrayDeque
代替LinkedList
提升队列性能 - Java 14+ 使用
record
表示边:record Edge(int to, int weight) {}
- 使用
- 并行处理:Java 8+ 使用并行流处理大型图java
graph.parallelStream().forEach(this::processNode);
- 内存优化:对于超大规模图,考虑分块处理或使用磁盘存储
七、总结
解决图类代码题的系统方法:
- 识别问题类型:确定是路径、连通性还是排序问题
- 选择数据结构:邻接表(通用)或邻接矩阵(稠密图)
- 选择算法:
- 遍历:BFS/DFS
- 最短路径:BFS(无权)、Dijkstra(带权非负)
- 依赖排序:拓扑排序
- 实现与优化:使用模板代码,注意环检测和状态记录
- 边界处理:空图、孤立节点、自环等情况
关键思维:将现实问题抽象为顶点和边的关系,再选择合适的图算法解决。掌握BFS/DFS模板和拓扑排序,能解决80%的图类问题。遇到新问题时,先问:"这个问题的顶点是什么?边表示什么关系?"