图论基础回顾:从数据结构到资金流网络
做资金流网络挖掘,图论是绕不开的基础。说实话,我刚开始接触这个领域时,觉得图论就是数学课上的抽象概念,跟实际业务八竿子打不着。直到有一次做企业资金链路分析,被一个循环转账的问题卡了整整两天——嗯,从那以后我再也不敢小看图论了。
这一节,我们快速过一遍图论的核心知识点。都是干货,不绕弯子。
图的定义与分类
图是什么?说白了,就是一堆节点和连线的集合。节点代表实体,连线代表关系。在资金流网络中,节点可以是公司、账户、个人,连线就是转账、交易、担保这些行为。
图有三种常见分类,我列个表方便你对比:
| 类型 | 定义 | 资金流场景举例 |
|---|---|---|
| 无向图 | 边没有方向,A-B 和 B-A 一样 | 企业之间的担保关系、关联公司 |
| 有向图 | 边有方向,A→B 和 B→A 不同 | 转账记录、资金流向(A 转给 B) |
| 加权图 | 边上带有权重(数值) | 转账金额、交易频次、风险评分 |
我的经验:资金流网络几乎都是有向加权图。方向代表资金流向,权重代表金额大小。我曾经遇到一个案子,只看无向关系发现不了异常,加上方向后立刻定位到某个账户是「资金归集中心」——这就是有向图的威力。
图的存储方式
图建好了,怎么存到计算机里?两种主流方式:邻接矩阵和邻接表。我分别说说。
邻接矩阵
用一个二维数组存图。假设有 n 个节点,就开一个 n×n 的矩阵。matrix[i][j] 表示节点 i 到节点 j 是否有边(或者权重是多少)。
# 有向加权图的邻接矩阵示例
# 节点:0, 1, 2, 3
# 边:0→1(权重5), 1→2(权重3), 2→0(权重2), 2→3(权重4)
matrix = [
[0, 5, 0, 0], # 节点0
[0, 0, 3, 0], # 节点1
[2, 0, 0, 4], # 节点2
[0, 0, 0, 0] # 节点3
]
避坑指南:邻接矩阵适合稠密图(边很多)。但资金流网络通常很稀疏——几万个节点,每个节点只跟几十个账户有交易。用矩阵存的话,大部分空间都是 0,浪费内存。我曾经用邻接矩阵存 10 万个节点的图,内存直接爆了...
邻接表
每个节点维护一个列表,只存它指向的邻居。空间省很多。
# 同样上面的图,用邻接表存
graph = {
0: [(1, 5)], # 节点0 → 节点1,权重5
1: [(2, 3)], # 节点1 → 节点2,权重3
2: [(0, 2), (3, 4)],# 节点2 → 节点0(权重2) 和 节点3(权重4)
3: [] # 节点3没有出边
}
我建议:做资金流挖掘,默认用邻接表。除非你的图节点数少于 1000 且边非常密集,否则邻接表是更优选择。Python 里用字典套列表,操作起来也很顺手。
图的遍历:BFS 与 DFS
遍历就是「走一遍图」。两种走法:广度优先(BFS)和深度优先(DFS)。
广度优先搜索(BFS)
一层一层往外扩。先访问起点,再访问起点的所有邻居,然后访问邻居的邻居... 用队列实现。
from collections import deque
def bfs(graph, start):
visited = set()
queue = deque([start])
visited.add(start)
while queue:
node = queue.popleft()
print(f"访问节点: {node}")
for neighbor, _ in graph.get(node, []):
if neighbor not in visited:
visited.add(neighbor)
queue.append(neighbor)
# 运行
graph = {0: [(1,5)], 1: [(2,3)], 2: [(0,2),(3,4)], 3: []}
bfs(graph, 0)
# 输出: 0 → 1 → 2 → 3
实际场景:BFS 适合找「最短路径」。在资金流中,我想查 A 公司转给 B 公司,中间经过了多少层账户——BFS 一层层往外扩,第一次找到目标时就是最短路径。我做过一个反洗钱项目,用 BFS 找资金传递路径,效果很好。
深度优先搜索(DFS)
一条路走到黑,走不通再回头。用栈(或递归)实现。
def dfs(graph, node, visited=None):
if visited is None:
visited = set()
visited.add(node)
print(f"访问节点: {node}")
for neighbor, _ in graph.get(node, []):
if neighbor not in visited:
dfs(graph, neighbor, visited)
# 运行
dfs(graph, 0)
# 输出: 0 → 1 → 2 → 3
注意:DFS 递归深度可能很大。如果图有上万个节点,Python 默认递归深度(约 1000)会报错。我建议用栈手动实现,或者设置 sys.setrecursionlimit()。曾经有个同事没注意这个,线上程序直接挂了...
图的连通性与强连通分量
连通性解决一个问题:从节点 A 能不能走到节点 B?
- 无向图:如果任意两个节点都能互相到达,叫「连通图」。否则叫「非连通图」,里面分成多个「连通分量」。
- 有向图:情况更复杂。A 能到 B,但 B 不一定能到 A。如果 A 和 B 能互相到达,它们属于同一个「强连通分量」(SCC)。
强连通分量在资金流中特别重要。为什么?因为资金闭环(循环转账)往往就藏在 SCC 里。
我的亲身经历:有一次做企业资金网络分析,发现一批公司之间频繁转账,但金额不大。用 SCC 算法一跑,发现它们属于同一个强连通分量——A 转给 B,B 转给 C,C 又转回 A。这就是典型的「资金空转」模式,后来查实是虚增流水。如果没有 SCC 分析,光看单笔交易根本发现不了。
找 SCC 的经典算法是 Kosaraju 算法或 Tarjan 算法。这里不展开代码,你只需要记住:SCC 是资金流网络中找闭环、找团伙的核心工具。
本章知识体系总览
下面这张图总结了本章的核心内容,我手绘了一个结构图:
嗯,图论基础就这些。别小看这些概念,后面每一章都会用到它们。尤其是 SCC 和 BFS,在资金流路径挖掘中几乎是天天打交道的工具。
一句话总结:图是资金流网络的骨架,邻接表是存储的首选,BFS 找路径,SCC 找闭环。把这四个点吃透,后面实战就顺了。