Skip to content

4.1 图的表示与应用

"数据获取往往比算法本身更重要。" — Skiena War Story


学习目标

  • 掌握图的两种基本表示:邻接矩阵与邻接表
  • 理解不同表示的时间和空间复杂度
  • 选择合适的图表示
  • 认识数据获取的挑战

一个关于数据获取的故事

Skiena 讲过一个真实案例:

某公司要分析社交网络。工程师花了两周优化 BFS 算法,把性能提升了 30%。

然后数据团队说:"数据源 API 有速率限制,爬取完整数据需要三个月。"

真正的问题是数据获取,不是算法优化。

这个故事告诉我们:在图算法中,获取图本身往往比优化算法更关键


图的基本概念

图的定义

图 G = (V, E) 由顶点集 V 和边集 E 组成。

  • 无向图:边没有方向
  • 有向图:边有方向
  • 加权图:边有权重

常见图类型

类型特性示例
无向图边无方向社交网络(好友关系)
有向图边有方向网页链接、关注关系
加权图边有权重道路网络、航班网络
稀疏图E << V²大多数现实图
稠密图E ≈ V²完全图

邻接矩阵

定义

用 V × V 矩阵 A 表示图:

  • A[i][j] = 1(或权重)表示有边 i → j
  • A[i][j] = 0(或 ∞)表示无边

示例

图结构:
0 → 1 → 2
↑   ↓
└───┘

邻接矩阵:
    0  1  2
0 [ 0  1  0 ]
1 [ 0  0  1 ]
2 [ 1  0  0 ]

复杂度

操作复杂度
空间O(V²)
判断边是否存在O(1)
遍历邻居O(V)
添加边O(1)

适用场景

  • 稠密图:E ≈ V²
  • 频繁判断边是否存在
  • 图规模较小

邻接表

定义

每个顶点维护一个邻居列表。

示例

图结构:
0 → 1 → 2
↑   ↓
└───┘

邻接表:
0: [1]
1: [2, 0]
2: [0]

复杂度

操作复杂度
空间O(V + E)
判断边是否存在O(degree(v))
遍历邻居O(degree(v))
添加边O(1)

适用场景

  • 稀疏图:E << V²
  • 频繁遍历邻居
  • 大规模图

表示选择

因素邻接矩阵邻接表
图密度稠密稀疏
内存O(V²)O(V+E)
边查询O(1)O(degree)
邻居遍历O(V)O(degree)
实现简单稍复杂

经验法则:默认选邻接表,除非明确需要 O(1) 边查询。


Python 实现

邻接矩阵

python
class GraphMatrix:
    def __init__(self, n):
        self.n = n
        self.matrix = [[0] * n for _ in range(n)]
    
    def add_edge(self, u, v, weight=1):
        self.matrix[u][v] = weight
    
    def has_edge(self, u, v):
        return self.matrix[u][v] != 0
    
    def neighbors(self, u):
        return [v for v in range(self.n) if self.matrix[u][v] != 0]

邻接表

python
from collections import defaultdict

class GraphList:
    def __init__(self):
        self.graph = defaultdict(list)
    
    def add_edge(self, u, v, weight=1):
        self.graph[u].append((v, weight))
    
    def neighbors(self, u):
        return self.graph[u]
    
    def vertices(self):
        return list(self.graph.keys())

LLM 融合点

任务:让 LLM 选择表示

给 LLM 一个图应用场景,让它选择表示方法。

场景:分析 Twitter 关注网络
- 用户数:3 亿
- 关注关系:约 100 亿
- 需要频繁查询"A 关注 B 吗"
- 需要遍历用户的关注列表

问题:
1. 选择邻接矩阵还是邻接表?
2. 理由是什么?
3. 如果需要 O(1) 边查询,如何优化?

判断 LLM 回答是否正确

  • 是否考虑了稀疏性(3 亿用户,100 亿边 → 稀疏图)
  • 是否权衡了边查询 vs 邻居遍历
  • 是否提出了解决方案(如布隆过滤器辅助)

小结

两种表示

  • 邻接矩阵:O(V²) 空间,O(1) 边查询
  • 邻接表:O(V+E) 空间,O(degree) 邻居遍历

选择原则

  • 稀疏图 → 邻接表
  • 稠密图 → 邻接矩阵
  • 频繁边查询 → 邻接矩阵或辅助结构

现实挑战

  • 数据获取往往比算法优化更关键
  • 图数据可能分散在多个数据源
  • API 速率限制是常见瓶颈

参考文献

  • Skiena, The Algorithm Design Manual, 3rd ed., Chapter 5
  • CLRS, Introduction to Algorithms, 4th ed., Chapter 22

新时代的算法课程