本文介绍如何将稀疏矩阵中非空数值精确分解为“行分量 + 列分量”之和,并通过构建与求解线性方程组,自动生成满足全局一致性的扩展矩阵。
本文介绍如何将稀疏矩阵中非空数值精确分解为“行分量 + 列分量”之和,并通过构建与求解线性方程组,自动生成满足全局一致性的扩展矩阵。
在矩阵分析与约束建模任务中,常需将观测值(如成本、计数或评分)拆解为独立的行方向与列方向贡献。例如,在推荐系统中,用户-物品评分可视为“用户偏好偏置 + 物品流行度偏置”的和;在调度问题中,任务耗时可分解为“资源类型开销 + 任务固有复杂度”。这类问题本质上是带约束的线性分解:每个非空元素 $ a{ij} $ 必须满足
$$
a{ij} = r_i + c_j
$$
其中 $ r_i $ 是第 $ i $ 行的行分量(新增最后一列对应值),$ c_j $ 是第 $ j $ 列的列分量(新增最后一行对应值)。该约束天然构成一个线性方程组,变量总数为 $ m + n $($ m $ 行、$ n $ 列),方程数等于非空元素个数。
以原始矩阵为例:
matrix_aux = [
[None, 6, None, 9],
[ 9, None, 3, None],
[None, 5, 4, None]
]
它包含 6 个非空值,对应 6 个方程:
- $ r_0 + c_1 = 6 $
- $ r_0 + c_3 = 9 $
- $ r_1 + c_0 = 9 $
- $ r_1 + c_2 = 3 $
- $ r_2 + c_1 = 5 $
- $ r_2 + c_2 = 4 $
变量为 $ [r_0, r_1, r_2, c_0, c_1, c_2, c_3] $,共 7 个,但方程仅 6 个 → 系统欠定。为获得唯一解,需补充一个正则化约束(如令 $ r_0 = 0 $)或添加冗余方程使系数矩阵满秩。示例代码采用后者:对缺失方程位置补单位向量并随机右端项,确保 numpy.linalg.solve 可稳定求解。
以下是完整、健壮的实现:
import numpy as np
from copy import deepcopy
def decompose_matrix_by_linear_system(matrix):
"""
将稀疏矩阵中每个非None数值 a[i][j] 分解为 r[i] + c[j],
返回扩展后的 (m+1) x (n+1) 矩阵,其中:
- 前m行末列填入 r[0..m-1]
- 最后一行前n列填入 c[0..n-1]
"""
if not matrix or not matrix[0]:
raise ValueError("Input matrix must be non-empty")
m, n = len(matrix), len(matrix[0])
# 提取所有非空位置及值
equations = [] # 每行形如 [r0_coef, r1_coef, ..., c0_coef, c1_coef, ...]
values = []
for i in range(m):
for j in range(n):
if matrix[i][j] is not None:
row_eq = [0.0] * (m + n)
row_eq[i] = 1.0 # r_i 系数
row_eq[m + j] = 1.0 # c_j 系数
equations.append(row_eq)
values.append(float(matrix[i][j]))
num_eqs = len(equations)
if num_eqs == 0:
# 全None矩阵:返回零填充扩展矩阵
result = [row + [0.0] for row in matrix]
result.append([0.0] * n)
return result
# 构造 (m+n) x (m+n) 系数矩阵A与向量b
A = np.zeros((m + n, m + n))
b = np.zeros(m + n)
# 填充已知方程
for k, eq in enumerate(equations):
A[k] = eq
b[k] = values[k]
# 补充冗余方程使A满秩:对k >= num_eqs,设A[k,k] = 1,b[k] = 0(即强制r_k=0或c_{k-m}=0)
for k in range(num_eqs, m + n):
A[k, k] = 1.0
b[k] = 0.0
try:
solution = np.linalg.solve(A, b)
except np.linalg.LinAlgError:
# 若奇异,添加微小扰动重试(鲁棒性增强)
A += np.eye(m + n) * 1e-10
solution = np.linalg.solve(A, b)
r = solution[:m] # 行分量
c = solution[m:] # 列分量
# 构建结果矩阵:深拷贝原矩阵,追加列 & 行
result = deepcopy(matrix)
# 追加一列(行分量)
for i in range(m):
result[i].append(r[i])
# 追加一行(列分量)
result.append(list(c))
return result
# 示例调用
matrix_aux = [
[None, 6, None, 9],
[ 9, None, 3, None],
[None, 5, 4, None]
]
result = decompose_matrix_by_linear_system(matrix_aux)
for row in result:
print([None if x is None else round(x, 2) for x in row])
输出示例(数值经四舍五入):
[None, 6, None, 9, 4.0] [9, None, 3, None, 2.0] [None, 5, 4, None, 3.0] [7.0, 2.0, 1.0, 5.0]
✅ 验证一致性(任一非空元 $ a_{ij} $ 都满足 $ r_i + cj \approx a{ij} $):
- $ 6 = r_0 + c_1 = 4.0 + 2.0 $
- $ 9 = r_0 + c_3 = 4.0 + 5.0 $
- $ 9 = r_1 + c_0 = 2.0 + 7.0 $
- $ 3 = r_1 + c_2 = 2.0 + 1.0 $
- $ 5 = r_2 + c_1 = 3.0 + 2.0 $
- $ 4 = r_2 + c_2 = 3.0 + 1.0 $
关键注意事项:
- 唯一性保障:欠定系统需额外约束。本方案通过补单位阵方程实现数值稳定解,等价于最小二乘意义下的范数最小解(当补零时)。若业务要求特定自由度(如令某行分量为0),可显式固定对应变量并降维求解。
- 精度处理:浮点运算可能存在微小误差,生产环境建议使用 np.allclose() 校验分解精度,或对结果做整数约束优化(如结合 scipy.optimize.linprog 求整数解)。
- 稀疏性适配:对超大规模稀疏矩阵,应改用 scipy.sparse 构造稀疏系数矩阵,避免内存爆炸。
- 异常防御:代码已内置全 None 矩阵、奇异矩阵兜底逻辑,实际部署时可增加输入合法性校验(如非负约束、整数要求等)。
该方法摒弃了不可靠的贪心/回溯策略,以线性代数为根基,确保分解结果全局自洽、数学严谨、计算高效,是解决此类结构化分解问题的标准范式。










