用map存无权有向图最直接:键为顶点id,值为邻居列表;需用computeifabsent安全加边,遍历时用getordefault防空指针,稀疏图下比二维数组更省内存。

用 Map<integer list>></integer> 存无权有向图最直接
Java 里实现邻接表,Map 套 List 是最轻量、最贴近直觉的方式。键是顶点编号(比如 int 类型的节点 ID),值是它能直达的所有邻居节点列表。
常见错误是把 Map<integer set>></integer> 当默认选择——除非你真需要去重或频繁查存在性,否则 List 更省空间、遍历更快,且插入顺序可控(对某些 BFS/DFS 实现有隐含影响)。
实操建议:
- 初始化时用
new HashMap(),别用new LinkedHashMap()除非你依赖插入顺序; - 每个新节点第一次出现时,必须显式调用
put(node, new ArrayList()),否则后续get(node).add(...)会触发NullPointerException; - 如果图稀疏(边数远小于
V²),这种结构比二维布尔数组节省大量内存。
加边操作必须判空 + 初始化,否则 NullPointerException 高发
很多人写 graph.get(u).add(v) 就完事,但 get(u) 返回 null 是常态——尤其当 u 还没作为键存过。
立即学习“Java免费学习笔记(深入)”;
典型错误现象:Exception in thread "main" java.lang.NullPointerException: Cannot invoke "java.util.List.add(Object)" because the return value of "java.util.Map.get(Object)" is null。
正确做法是封装一个安全的加边方法:
public void addEdge(int u, int v) {
graph.computeIfAbsent(u, k -> new ArrayList<>()).add(v);
}
computeIfAbsent 是关键:它既避免重复初始化,又天然线程不安全但够用(单线程图构建场景)。不要手写 if (!graph.containsKey(u)) graph.put(u, new ArrayList()); —— 多一次哈希查找,还可能漏掉并发竞争。
无向图要双向加边,但别重复初始化邻居列表
无向图本质是每条边对应两个有向边:u → v 和 v → u。但如果你分别调用 addEdge(u, v) 和 addEdge(v, u),两次都会触发 computeIfAbsent,这没问题;真正容易错的是手动管理时忘了其中一边。
使用场景注意点:
- 读取边列表(如从文件或数组)时,循环里必须对每对
(u,v)调两次addEdge; - 如果后续要支持删边,
List的remove(Object)是 O(n),此时换成Set更合适,但得接受额外开销; - 顶点编号不连续(比如只有 1、5、100 出现在边中),
Map自动跳过空洞,比数组更省心。
遍历时别直接用 for (int neighbor : graph.get(u)) 做空指针防御
运行期 u 可能根本不在图中(比如孤立节点未显式加入,或输入数据有误),这时 graph.get(u) 返回 null,增强 for 循环直接崩。
安全写法永远是先确认键存在,或用 getOrDefault:
for (int neighbor : graph.getOrDefault(u, Collections.emptyList())) {
// 处理 neighbor
}
性能上,Collections.emptyList() 是不可变单例,零分配;别用 new ArrayList(),每次调用都新建对象。
容易被忽略的一点:如果图构建阶段漏了某个节点(比如只加了边没显式声明顶点),它不会出现在 graph.keySet() 中,但你的算法可能默认所有顶点编号在 [0, n) 范围内——这时候得额外维护一个顶点集 Set<integer></integer> 或预初始化所有可能顶点。










