邻接矩阵推荐用 numpy,避免引用错误;邻接表优先选字典套集合以去重和高效查询;稀疏图、遍历多时用邻接表,查边频繁或需矩阵运算时用邻接矩阵。

邻接矩阵:用二维列表还是 NumPy?
邻接矩阵本质是 n × n 的布尔/数值矩阵,索引 i 和 j 对应顶点,值表示是否存在边(或边权)。纯 Python 用 list[list[int]] 可行,但增删顶点、矩阵运算慢;NumPy 更合适,尤其涉及权重、图算法(如 Floyd-Warshall)时。
常见错误:手动初始化 [[0] * n] * n —— 这会创建 n 个指向同一子列表的引用,改一个全改。正确写法是 [[0 for _ in range(n)] for _ in range(n)] 或直接用 np.zeros((n, n), dtype=int)。
- 无向图需同步设
matrix[i][j]和matrix[j][i];有向图只设单向 - 带权图中,缺边建议用
float('inf')(非 -1 或 0),避免与真实权重混淆 - 内存占用固定为
O(n²),顶点数超几千就明显吃紧
邻接表:字典套列表 or 字典套集合?
邻接表用 dict 映射顶点到其邻居集合,最常用结构是 {v: [u1, u2, ...]}。如果只关心连通性、不重复加边,{v: set()} 能自动去重、O(1) 查存在性;但失去顺序和重复边支持(比如多重图)。
典型误用:用 list.append() 频繁查重再插入 —— 效率退化成 O(k) 每次(k 是邻居数)。不如一开始就用 set,最后转 list 输出。
- 顶点名可以是字符串(如
"A")、整数甚至元组,只要可哈希;用字符串时别混用"1"和1 - 添加边前务必检查顶点是否已存在于字典中,否则
KeyError;懒办法是初始化时用defaultdict(list)或defaultdict(set) - 遍历时用
graph.get(v, [])比直接graph[v]更安全
从邻接矩阵转邻接表:别硬遍历全矩阵
如果已有 matrix,想转成邻接表,别写两层 for 循环扫每个格子——尤其当图稀疏时,做了大量无效判断。应先确认矩阵是否对称、是否含权、缺边标记是什么(0?None?float('inf')?),再针对性扫描。
示例(无向无权,缺边为 0):
graph = {i: [] for i in range(len(matrix))}
for i in range(len(matrix)):
for j in range(i + 1, len(matrix)): # 利用对称性,只扫上三角
if matrix[i][j]:
graph[i].append(j)
graph[j].append(i)
- 有向图去掉
j起始条件限制,直接range(len(matrix)) - 带权图中,把
if matrix[i][j]改成if matrix[i][j] != float('inf')等等 - 用 NumPy 时,可用
np.where(matrix != 0)获取非零坐标,再 zip 构造边,更快
选哪种表示?看操作频率,不是看“听起来高级”
邻接矩阵适合频繁查「两点间是否有边」(O(1))或需要整体矩阵运算(如幂运算求路径数);邻接表适合遍历邻居、DFS/BFS、动态增删边(O(1) 平均插入),且省内存。
一个容易被忽略的现实:Python 中,哪怕图只有几百个顶点,如果你主要做 BFS、拓扑排序、连通分量,邻接表配合 collections.deque 几乎总是更稳;而用矩阵做这些,代码反而绕、易索引越界、调试时打印也费劲。
- 小图(
n )两者差异不大,按习惯选 - 读取数据源是边列表(如 CSV 的
from,to,weight),优先建邻接表,别先转矩阵再转回来 - 混合使用不推荐:除非你明确在某步需要矩阵乘法,否则维护两套结构极易不同步
边界情况比想象中多:顶点编号不连续(比如只有 1、5、100)、含自环、空图、单点图……初始化时多想一秒,后面少 debug 十分钟。
Python免费学习笔记(深入):立即使用
在学习笔记中,你将探索 Python 的核心概念和高级技巧!











