现实中的大矩阵往往大部分元素是 0(比如社交网络、物理仿真)。存一堆 0 太浪费,稀疏矩阵只存非零元素,省内存、算得快。本章讲怎么构建和使用稀疏矩阵。
33.1 为什么需要稀疏矩阵
一个 1000×1000 的稠密矩阵要存 100 万个元素(约 8 MB)。但如果它只有 1000 个非零元素(其余都是 0),稠密存储就浪费了 99.9% 的空间。
稀疏矩阵只存非零元素,能把内存和计算量降几个数量级。
| 存储 | 所有元素 | 只存非零元素 |
| 适用 | 大部分非零 | 大部分是 0(非零 < ~10%) |
| 大小 | 适合小矩阵 | 适合超大稀疏矩阵 |
33.2 用 Triplet 构建稀疏矩阵
稀疏矩阵不能像稠密那样 << 填值,要用三元组(Triplet):(行, 列, 值) 的列表。
#include <Eigen/Sparse>
#include <iostream>
#include <vector>
int main() {
// 1. 收集三元组 (行, 列, 值)
std::vector<Eigen::Triplet<double>> triplets;
triplets.push_back({0, 0, 1.0});
triplets.push_back({0, 1, 0.5});
triplets.push_back({1, 1, 2.0});
triplets.push_back({2, 2, 3.0});
triplets.push_back({2, 4, 0.3});
triplets.push_back({3, 3, 4.0});
triplets.push_back({4, 4, 5.0});
// 2. 创建 5×5 稀疏矩阵,填入三元组
Eigen::SparseMatrix<double> S(5, 5);
S.setFromTriplets(triplets.begin(), triplets.end());
// 3. 转成稠密矩阵打印
std::cout << Eigen::MatrixXd(S) << std::endl;
std::cout << "非零元素个数 = " << S.nonZeros() << std::endl; // 7
return 0;
}
运行结果:
1 0.5 0 0 0
0 2 0 0 0
0 0 3 0 0.3
0 0 0 4 0
0 0 0 0 5
非零元素个数 = 7
要点:
- Eigen::Triplet<double>(行, 列, 值) 描述一个非零元素
- S.setFromTriplets(begin, end) 批量填入
- 用 Eigen::MatrixXd(S) 转成稠密矩阵查看
33.3 稀疏矩阵的基本操作
S.rows(); S.cols(); // 尺寸
S.nonZeros(); // 非零元素个数
S.coeff(i, j); // 读元素(慢,别在循环里频繁用)
S.insert(i, j) = value; // 插入单个元素
稀疏矩阵读单个元素用 coeff()(不是 ()),且遍历效率低,尽量用三元组批量构建。
33.4 用稀疏求解器解方程
稀疏矩阵配合稀疏求解器,能高效解大规模方程组:
#include <Eigen/Sparse>
#include <iostream>
#include <vector>
int main() {
const int n = 5;
// 构建稀疏矩阵:主对角线 2,上下对角线 -1(对称正定)
std::vector<Eigen::Triplet<double>> triplets;
for (int i = 0; i < n; i++) {
triplets.push_back({i, i, 2.0}); // 主对角线
if (i + 1 < n) {
triplets.push_back({i + 1, i, –1.0}); // 下对角线
triplets.push_back({i, i + 1, –1.0}); // 上对角线(保持对称)
}
}
Eigen::SparseMatrix<double> S(n, n);
S.setFromTriplets(triplets.begin(), triplets.end());
Eigen::VectorXd b(n);
b << 1.5, 2, 3.3, 4, 5;
// 稀疏求解器(SimplicialLDLT 适合对称正定)
Eigen::SimplicialLDLT<Eigen::SparseMatrix<double>> solver;
solver.compute(S);
if (solver.info() == Eigen::Success) {
Eigen::VectorXd x = solver.solve(b);
std::cout << "解 = " << x.transpose() << std::endl;
std::cout << "残差 = " << (S * x – b).norm() << std::endl;
} else {
std::cout << "分解失败" << std::endl;
}
return 0;
}
运行结果(数值请以上面程序在你机器上的输出为准,浮点末位可能有微小差异):
解 = 6.4 11.3 14.2 13.8 9.4
残差 = 2.66454e-15
残差是 1e-15 量级,也就是"实际上等于 0"——不要指望它打印成 0,浮点运算总会留下这种极小的尾巴(第 29 章的残差同理)。
稀疏求解器有:
- SimplicialLDLT / SimplicialLLT:对称正定
- SparseLU:一般方阵
- ConjugateGradient / BiCGSTAB:迭代法(超大矩阵)
33.5 稀疏 vs 稠密怎么选
矩阵大部分是 0(非零 < ~10%),且很大 → 稀疏 SparseMatrix
矩阵比较"稠密"或尺寸小 → 稠密 MatrixXd
经验:小矩阵(几百以内)用稠密就行;几万×几万的稀疏矩阵才需要 SparseMatrix。稀疏矩阵有额外开销,小矩阵用它反而慢。
33.6 小结
- 稀疏矩阵只存非零元素,省内存、算得快。
- 用 Triplet(行、列、值)批量构建,setFromTriplets 填入。
- 稀疏求解器:SimplicialLDLT(对称正定)、SparseLU(一般)、迭代法(超大)。
- 非零占比低 + 矩阵大才用稀疏。
下一章,学习性能优化与别名陷阱——避开 Eigen 最经典的坑。




