📘 图论入门与邻接表详解
—— 零基础也能看懂的图论学习笔记
一、什么是图论?
图论(Graph Theory) 是数学和计算机科学中的一个重要分支,研究“图”这种结构。
✅ 图(Graph) ≠ 柱状图/折线图
它是由 顶点(Vertex/Node) 和 边(Edge) 组成的结构,用来表示对象之间的关系。
🌟 常见应用场景:
- 社交网络:人是顶点,朋友关系是边
- 地图导航:城市是顶点,道路是边
- 课程依赖:课程是顶点,先修关系是边
- 网页链接:网页是顶点,超链接是边
二、图的基本组成
概念 | 说明 |
---|---|
顶点(Vertex) | 表示一个对象,如人、城市、网页 |
边(Edge) | 表示两个顶点之间的连接或关系 |
图 G = (V, E) | V 是顶点集合,E 是边集合 |
举例:
A —— B —— C
- 顶点:A, B, C
- 边:A-B, B-C
三、图的类型
类型 | 说明 | 示例 |
---|---|---|
无向图 | 边没有方向,A—B 等价于 B—A | 朋友关系 |
有向图 | 边有方向,A→B 不一定可逆 | 网页链接、课程依赖 |
加权图 | 每条边有“权重”(如距离、成本) | 导航系统中的道路长度 |
简单图 | 无自环、无重复边 | 大多数算法题 |
多重图 | 允许重复边或自环 | 少见 |
四、基本术语
术语 | 含义 |
---|---|
度(Degree) | 一个顶点连接的边数(有向图分入度/出度) |
路径(Path) | 从一个顶点到另一个顶点经过的边序列 |
环(Cycle) | 起点和终点相同的路径 |
连通图 | 任意两点都有路径(无向图) |
强连通 | 任意两点可互相到达(有向图) |
树(Tree) | 无环的连通无向图 |
五、图的存储方式
有两种主流方式:
1. 邻接矩阵(Adjacency Matrix)
- 用二维数组
matrix[i][j]
表示是否有边 - 空间复杂度:O(V²)
- 适合稠密图
2. ✅ 邻接表(Adjacency List) ← 推荐初学者掌握
六、邻接表详解(重点!)
1. 什么是邻接表?
每个顶点维护一个列表,记录它连接的所有邻居。
📌 本质:“谁和我相连”
2. 如何建邻接表?(三步法)
✅ 步骤 1:初始化
# 顶点编号 0 ~ n-1(连续整数)
n = 5
g = [[] for _ in range(n)] # g[i] 是顶点 i 的邻居列表
⚠️ 错误写法:
g = [[]] * n
→ 所有子列表共享引用!
✅ 正确写法:g = [[] for _ in range(n)]
✅ 步骤 2:添加边
无向图(双向)
u, v = 1, 2
g[u].append(v)
g[v].append(u)
有向图(单向)
u, v = 1, 2 # u → v
g[u].append(v)
# 不加反向边
✅ 步骤 3:处理重复边(可选)
seen = set()
for a, b in edges:
if (a, b) not in seen:
seen.add((a, b))
g[b].append(a) # 例如课程依赖
3. 邻接表示例
示例 1:无向图
A — B — C
g = {
'A': ['B'],
'B': ['A', 'C'],
'C': ['B']
}
示例 2:有向图(课程依赖)
prerequisites = [[1,0], [2,1]] # 0→1, 1→2
g = [[1], [2], []]
图:0 → 1 → 2
4. 如何“读”邻接表?
给你一个邻接表,如何还原图?
g = [[1], [2], [0]]
- 顶点:0, 1, 2
- 读每行:
- 0 → 1
- 1 → 2
- 2 → 0
- 画图 → 发现环:
0→1→2→0
📌 判断有向/无向:
- 对称 → 无向
- 不对称 → 有向
5. 邻接表 vs 邻接矩阵
对比项 | 邻接表 | 邻接矩阵 |
---|---|---|
空间 | O(V + E) | O(V²) |
适合 | 稀疏图 | 稠密图 |
查边 | O(度) | O(1) |
遍历邻居 | 快 | 慢 |
✅ 推荐使用邻接表:大多数算法题中图是稀疏的。
七、邻接表 + 常见算法
1. BFS(广度优先搜索)
from collections import deque
def bfs(start, g):
visited = [False] * len(g)
queue = deque([start])
order = []
while queue:
node = queue.popleft()
if not visited[node]:
visited[node] = True
order.append(node)
for neighbor in g[node]:
if not visited[neighbor]:
queue.append(neighbor)
return order
用途:最短路径(无权图)、层序遍历
2. DFS(深度优先搜索)
def dfs(node, g, visited, order):
visited[node] = True
order.append(node)
for neighbor in g[node]:
if not visited[neighbor]:
dfs(neighbor, g, visited, order)
用途:检测环、拓扑排序、连通性
八、经典应用:课程表问题(拓扑排序)
判断是否能完成所有课程(即图是否有环):
def canFinish(numCourses, prerequisites):
g = [[] for _ in range(numCourses)]
for a, b in prerequisites:
g[b].append(a) # b → a
colors = [0] * numCourses # 0:未访问, 1:访问中, 2:已完成
def dfs(x):
colors[x] = 1
for y in g[x]:
if colors[y] == 1:
return True # 发现环
if colors[y] == 0 and dfs(y):
return True
colors[x] = 2
return False
for i in range(numCourses):
if colors[i] == 0 and dfs(i):
return False # 有环
return True
📌 核心:用 colors
标记状态,g
存图,dfs
检测环。
九、学习建议
- 从画图开始:先画几个小图,练习 BFS/DFS 手动遍历
- 动手实现:用 Python 写邻接表 + BFS/DFS
- 刷题巩固:
- LeetCode 200. 岛屿数量(DFS/BFS)
- LeetCode 207. 课程表(拓扑排序)
- LeetCode 133. 克隆图
- 理解本质:图是“关系”的抽象,邻接表是“谁连谁”的记录
十、总结口诀
🔑 邻接表三步走:
g = [[] for _ in range(n)]
- 遍历边,取出
u, v
- 加边:
- 无向:
g[u].append(v)
+g[v].append(u)
- 有向:
g[u].append(v)
- 无向:
🔑 图论一句话:
顶点是对象,边是关系,邻接表是存储,BFS/DFS 是遍历工具。