分子图表示方法:三种基础数据结构
聊到分子图结构,我第一个想跟你分享的就是“表示方法”。说白了,就是怎么把一个分子存进计算机。我在早期做化学数据库时,吃过不少格式不统一的亏,后来才明白——选对表示方法,后面的分析工作能省一半力气。
分子图本质上是个无向图(有时带键级属性),原子是节点,化学键是边。那么问题来了:存成哪种数据结构最合适?我个人习惯根据场景选,主要有三种——邻接矩阵、边列表、化学文件格式。
邻接矩阵
邻接矩阵是图论里最直接的表示法。对于一个有 V 个原子的分子,开一个 V×V 的方阵。如果原子 i 和原子 j 之间有键,矩阵元素 A[i][j] 设为键级,否则为 0。
举个例子,乙醇 (C₂H₅OH) 的邻接矩阵(仅重原子部分):
C1 C2 O
C1 0 1 0
C2 1 0 1
O 0 1 0
你想想看,这结构多清晰。查找两个原子是否相连,O(1) 时间就能搞定。但代价也很明显——空间复杂度 O(V²)。一个含 100 个原子的分子就要 10000 个格子,大部分还是空的。
边列表
边列表就简单了,它就是所有化学键的集合。每一条记录包含两个原子索引和键的类型。
还是乙醇的例子:
0 1 1.0 # C1 - C2 单键
1 2 1.0 # C2 - O 单键
这里我把 C1 编为 0 号,C2 编为 1 号,O 编为 2 号。每条边三个数字:起点、终点、键级。
边列表的优势是节省空间。一个稀疏分子(比如蛋白质)可能有几千个原子,但每个原子平均只连 2-3 个键。用边列表存储,空间复杂度是 O(E),比邻接矩阵省得多。
| 表示方法 | 空间复杂度 | 查询两原子是否相连 | 适用场景 |
|---|---|---|---|
| 邻接矩阵 | O(V²) | O(1) | 稠密图、小分子 |
| 边列表 | O(E) | O(E)(最坏) | 稀疏图、大分子 |
| 化学文件格式 | O(V+E+属性) | 需解析 | 存储、交换 |
边列表的查询性能是短板。你想知道两个原子有没有键,得遍历整个列表。所以在我做的图算法里,边列表通常只作为原始输入,后续会转成其他结构再计算。
化学文件格式:SDF 与 MOL
前面两种是纯数据结构,但工业界和学术界更常用的是标准文件格式。最经典的就是 MDL Molfile 和它的大哥 SDF(Structure-Data File)。
Molfile 的原子块(Atom Block)格式固定:
x y z 元素符号 同位素 电荷 等等
键块(Bond Block)更简单:
起点 终点 键类型 键立体化学
一个完整的乙醇 MOL 文件大概长这样:
ethanol.mol
ChemDraw01011123393D
3 2 0 0 0 0 0 0 0 0 1 V2000
0.8660 0.5000 0.0000 C 0 0 0 0 0 0 0 0 0 0 0 0
0.0000 0.0000 0.0000 C 0 0 0 0 0 0 0 0 0 0 0 0
1.7320 0.0000 0.0000 O 0 0 0 0 0 0 0 0 0 0 0 0
1 2 1 0 0 0 0
2 3 1 0 0 0 0
M END
$$$$
SDF 就是一个 MOL 文件后面挂着一些键值对属性(分子名称、LogP、SMILES 等),再以 $$$$ 结束。多个分子就拼接在一起。所以 SDF 其实是一个“多分子容器”。
这三种表示方法各有各的舞台。在我日常工作中,边列表常用于算法原型验证,邻接矩阵用于图卷积网络(GCN)的输入,而 SDF 是数据交换的“硬通货”。掌握它们,你就拥有了“看分子”的三种不同视角。
理解这三种表示方法,是构建分子分析工具的必修课。我在搭建第一个分子数据库时,就从边列表起步,后面为了让子结构搜索更快才加了邻接矩阵。一步步来,你也能找到自己项目的“最优解”。