第一次听说用矩阵快速幂来计算图路径时,我的反应和大多数算法工程师一样:"这俩八竿子打不着的东西怎么扯上关系了?"直到在社交网络分析项目中遇到需要计算大规模图节点间k步可达路径的需求,传统DFS/BFS方法在千万级节点图上完全跑不动,这才让我真正体会到这个数学技巧的威力。
矩阵快速幂算法本质上是将矩阵乘法与快速幂思想结合,能在O(logN)时间复杂度内完成矩阵的N次幂运算。而图的邻接矩阵恰好能用矩阵乘法表示路径组合——这个看似简单的发现,却为解决图论中的路径计数、最短路径等经典问题提供了全新的思路。特别是在需要计算固定步长路径的场景下,相比传统图遍历算法有着数量级的性能优势。
任何图结构都可以用邻接矩阵A表示,其中A[i][j]=1表示节点i到j存在直接边。当我们计算A²时,矩阵乘法定义中的"乘"变为逻辑与,"加"变为逻辑或,结果矩阵A²[i][j]实际上计算了从i到j长度为2的路径数。这个性质可以推广到Aⁿ计算n步路径。
以简单的有向图为例:
code复制节点关系:0→1→2→0
邻接矩阵A:
[[0,1,0],
[0,0,1],
[1,0,0]]
计算A²得到:
[[0,0,1],
[1,0,0],
[0,1,0]]
确实反映了各节点间两步可达的情况。
直接计算Aⁿ需要O(n)次矩阵乘法。而快速幂算法利用二分思想:
这使得计算复杂度降至O(logN)。对于n=100的情况,传统方法需要99次乘法,而快速幂仅需:
A¹⁰⁰ = (A⁵⁰)² = ((A²⁵)²)² = ... 总共只需8次矩阵乘法。
实际场景中的图往往是稀疏的(如社交网络)。直接使用稠密矩阵会浪费大量空间。我们采用CSR(Compressed Sparse Row)格式存储:
python复制class SparseMatrix:
def __init__(self, data, indices, indptr, shape):
self.data = data # 非零值
self.indices = indices # 列索引
self.indptr = indptr # 行指针
self.shape = shape
矩阵乘法时只需遍历非零元素:
python复制def sparse_matmul(A, B):
# 实现稀疏矩阵乘法
result_data = []
result_indices = []
result_indptr = [0]
for i in range(A.shape[0]):
row_result = {}
for k_idx in range(A.indptr[i], A.indptr[i+1]):
k = A.indices[k_idx]
for j_idx in range(B.indptr[k], B.indptr[k+1]):
j = B.indices[j_idx]
row_result[j] = row_result.get(j, 0) + A.data[k_idx] * B.data[j_idx]
result_indices.extend(sorted(row_result.keys()))
result_data.extend([row_result[j] for j in sorted(row_result.keys())])
result_indptr.append(len(result_indices))
return SparseMatrix(result_data, result_indices, result_indptr, (A.shape[0], B.shape[1]))
对于超大规模矩阵,我们使用GPU加速。以PyTorch为例:
python复制import torch
def gpu_matpow(A, power):
A_gpu = torch.sparse_coo_tensor(
torch.stack([torch.LongTensor(A.row), torch.LongTensor(A.col)]),
torch.FloatTensor(A.data),
size=A.shape
).cuda()
result = torch.eye(A.shape[0]).cuda()
while power > 0:
if power % 2 == 1:
result = torch.sparse.mm(result, A_gpu)
A_gpu = torch.sparse.mm(A_gpu, A_gpu)
power = power // 2
return result
在微博转发预测中,我们需要计算用户A的推文在3次转发后可能到达的用户群体。传统方法需要多次遍历关注关系图,而矩阵方法只需计算邻接矩阵的3次幂:
python复制# 构建关注关系邻接矩阵
adj_matrix = build_adjacency_matrix(user_relations)
# 计算3-hop传播范围
reachable = matrix_pow(adj_matrix, 3)
# 获取种子用户的影响范围
seed_users = [123, 456]
influence = reachable[seed_users].sum(axis=0)
城市道路网络可以建模为图,其中边权重代表通行时间。通过改进的矩阵快速幂算法,可以高效计算任意两点间在特定时间窗口内可达的所有路径:
python复制def timed_reachability(adj_matrix, time_window):
# 将权重转换为时间分片矩阵
time_slices = [adj_matrix <= t for t in range(1, time_window+1)]
result = sum(matrix_pow(slice, k)
for k in range(1, time_window+1)
for slice in time_slices)
return result > 0
当矩阵太大无法放入内存时,采用分块策略:
python复制def block_matrix_pow(A, power, block_size=1024):
n = A.shape[0]
result = sparse.eye(n)
for i in range(0, n, block_size):
for j in range(0, n, block_size):
block = A[i:i+block_size, j:j+block_size]
# 对每个分块应用快速幂
powered_block = sparse_matrix_pow(block, power)
# 合并结果...
return result
使用内存映射文件处理超大规模矩阵:
python复制import numpy as np
# 创建内存映射
matrix = np.memmap('graph.mmap', dtype='float32',
mode='w+', shape=(N, N))
# 分块处理
for i in range(0, N, chunk_size):
chunk = matrix[i:i+chunk_size]
process_chunk(chunk)
当路径数量极大时,可以采用对数域计算或模运算:
python复制def matrix_pow_mod(A, power, mod):
result = sparse.eye(A.shape[0])
while power > 0:
if power % 2 == 1:
result = (result @ A) % mod
A = (A @ A) % mod
power = power // 2
return result
对于包含负权重的图(如金融交易网络),需要修改矩阵乘法语义:
python复制def weighted_matmul(A, B):
# 使用(min, +)代数替代常规乘法
n = A.shape[0]
result = np.full((n,n), np.inf)
for i in range(n):
for k in range(n):
if A[i,k] == np.inf: continue
for j in range(n):
result[i,j] = min(result[i,j], A[i,k]+B[k,j])
return result
对于频繁变动的图结构,可以使用增量更新算法:
python复制class DynamicGraph:
def __init__(self, initial_graph):
self.cache = {1: initial_graph}
def update_edge(self, u, v, weight):
# 清空受影响幂次的缓存
self.cache = {1: self._apply_update(u, v, weight)}
def get_path_matrix(self, k):
if k not in self.cache:
lower = self.get_path_matrix(k//2)
self.cache[k] = matmul(lower, lower)
if k % 2 == 1:
self.cache[k] = matmul(self.cache[k], self.cache[1])
return self.cache[k]
在随机游走模型中,矩阵元素表示转移概率。计算Aⁿ可以得到n步后的概率分布:
python复制def probability_after_steps(transition_matrix, steps, initial_dist):
powered = matrix_pow(transition_matrix, steps)
return initial_dist @ powered
这个技巧被广泛应用于PageRank、推荐系统等场景。