图论基础:从定义到表示方法

图论听起来有点抽象,但其实它就在我们身边。你打开手机的地图导航,里头那个路网就是一张图;你在数据库里查药物和靶点的关系,画出来也是一张图。说白了,图就是研究"东西怎么连在一起"的学问。

我在做药物知识图谱的时候,每天打交道的就是图。顶点代表药物、靶点、疾病,边代表它们之间的相互作用。理解了图论基础,后面的特征提取才能站得住脚。好,我们直接进入正题。

图的定义:顶点与边

图是什么?数学定义很简单:图 G = (V, E)。V 是顶点集合,E 是边集合。

顶点(Vertex),你可以把它理解为"实体"。在药物知识图谱里,顶点可以是阿司匹林、布洛芬,也可以是 COX-1、COX-2 这些靶点。边(Edge)就是"关系",比如"阿司匹林抑制COX-1"这条边,就连接了两个顶点。

我刚开始接触图论时,总觉得顶点和边太简单了。但后来发现,真正决定图结构质量的,恰恰是你怎么定义顶点和边。定义得太粗,信息都丢了;定义得太细,图又大得跑不动。这个平衡点,我建议你从业务目标反推——先想清楚你要用图来回答什么问题,再去定边界。

举个例子,一个最简单的药物-靶点图:

顶点集 V = {阿司匹林, 布洛芬, COX-1, COX-2}
边集 E = {(阿司匹林, COX-1), (阿司匹林, COX-2), (布洛芬, COX-1), (布洛芬, COX-2)}

每条边代表"抑制"关系。你看,图就是这么直观。

有向图与无向图:方向决定语义

边有了,但方向呢?这取决于关系的性质。

有向图的边带箭头,表示单向关系。比如"药物→靶点"是抑制,反过来不成立,那就得用有向边。我个人习惯把药物-靶点相互作用建模为有向图,因为方向里藏着作用机制。

无向图的边没有方向,表示对称关系。比如药物-药物联合使用,A 和 B 联用,B 和 A 也联用,方向没意义。蛋白质-蛋白质相互作用也经常用无向图建模。

你想想看,选错方向会怎样?明明是无向关系,你非加个箭头,后续计算出来的特征就会带偏;反过来,有向关系你当无向处理,也会丢失关键信息。方向选对了,图结构才能忠实反映真实世界。

特性 有向图 无向图
边的方向 有箭头,单向 无箭头,对称
适用场景 抑制、激活、调控 结合、联合、相互作用
邻接矩阵对称性 不对称 对称
药物图谱例子 药物→靶点(抑制) 药物-药物(联合)

图的表示方法:存到计算机里

定义清楚了,下一步就是存起来。计算机不认识顶点和边,它只认识数字和数组。最常见的两种表示方法是邻接矩阵和邻接表。

邻接矩阵

假设有 n 个顶点,就建一个 n×n 的矩阵。第 i 行第 j 列的值表示顶点 i 到顶点 j 是否有边。有向图和无向图都能表示,只不过无向图矩阵是对称的。

拿前面的药物-靶点例子来说,4 个顶点,邻接矩阵长这样:

# 顶点顺序:[阿司匹林, 布洛芬, COX-1, COX-2]
adj_matrix = [
    [0, 0, 1, 1],   # 阿司匹林 → COX-1, COX-2
    [0, 0, 1, 1],   # 布洛芬 → COX-1, COX-2
    [0, 0, 0, 0],   # COX-1 没有出边
    [0, 0, 0, 0]    # COX-2 没有出边
]

简单直观,但问题也很明显:如果图有 1 万个顶点,矩阵就是 1 亿个元素。大部分元素是 0,太浪费了。

邻接表

邻接表就聪明多了——每个顶点只存它"认识"的邻居。用哈希表或列表来实现:

# 同样是上面的有向图
adj_list = {
    '阿司匹林': ['COX-1', 'COX-2'],
    '布洛芬':   ['COX-1', 'COX-2'],
    'COX-1':    [],
    'COX-2':    []
}

我只存有边的信息,空顶点不占空间。在药物知识图谱里,顶点动不动就几十万,但每个顶点平均连接的边可能只有几条。这时候邻接表的内存效率完胜邻接矩阵。

核心要点:选择表示方法时,先看图的密度。稠密图(边多)用邻接矩阵,存取快;稀疏图(边少)用邻接表,省内存。药物知识图谱几乎都是稀疏图,所以我 90% 的场景都在用邻接表。

图论基础核心概念 图 (Graph) 顶点 (Vertex) 边 (Edge) 有向图 无向图 邻接矩阵 邻接表 图由顶点和边组成,按方向分为有向图和无向图,用邻接矩阵或邻接表存储

两种表示方法的对比与选择

邻接矩阵和邻接表各有长短,我遇到过不少人在这个选择上翻车。来,直接对比一下:

对比维度 邻接矩阵 邻接表
内存占用 O(n²),固定大小 O(n+e),e 为边数
查询两顶点是否有边 O(1),直接取下标 O(d),d 为顶点度
遍历某顶点的邻居 O(n),要扫一整行 O(d),直达邻居列表
适合图类型 稠密图 稀疏图
药物图谱适用性 差(顶点多、边稀疏) 好(省内存、遍历快)

实用技巧:如果你不确定图是稠密还是稀疏,先算一下边数 e 和顶点数 n 的比值。e ≈ n² 是稠密,e ≈ n 是稀疏。药物知识图谱里这个比值通常在 1~10 之间,妥妥的稀疏图。选邻接表就对了。

注意:我踩过的坑是——用邻接矩阵存了一个 5 万顶点的药物相似性图。矩阵 25 亿个元素,内存直接爆了。后来换成邻接表,内存从 20 GB 降到了 200 MB。遇到大规模图,千万别贪邻接矩阵的查询速度,先算算内存账。

另外还有一个容易被忽略的点:当你需要在图上做大量"插入/删除边"的操作时,邻接表的动态扩展更灵活。邻接矩阵一旦确定大小,改起来就得重新分配整块内存。嗯,这点在你构建动态知识图谱时会特别明显。

好,图论基础就聊到这儿。顶点和边搭起了骨架,有向/无向赋予了语义,邻接矩阵和邻接表让图能在计算机里跑起来。这些东西看似基础,但往后 29 章的特征提取方法,全都建在这几块基石上。你把这个底打牢了,后面学起来会顺很多。