欢迎光临
我们一直在努力

第 33 章 · 稀疏矩阵

现实中的大矩阵往往大部分元素是 0(比如社交网络、物理仿真)。存一堆 0 太浪费,稀疏矩阵只存非零元素,省内存、算得快。本章讲怎么构建和使用稀疏矩阵。

33.1 为什么需要稀疏矩阵

一个 1000×1000 的稠密矩阵要存 100 万个元素(约 8 MB)。但如果它只有 1000 个非零元素(其余都是 0),稠密存储就浪费了 99.9% 的空间。

稀疏矩阵只存非零元素,能把内存和计算量降几个数量级。

稠密(MatrixXd)稀疏(SparseMatrix)
存储 所有元素 只存非零元素
适用 大部分非零 大部分是 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 最经典的坑。


练习题

  • 用 Triplet 构建一个 3×3 的稀疏单位矩阵,转成稠密打印。
  • 构建一个对角稀疏矩阵,统计它的 nonZeros。
  • 用稀疏求解器解一个稀疏方程组。
  • 说说什么时候该用稀疏矩阵、什么时候用稠密矩阵。
  • 稀疏矩阵读单个元素用什么方法?
  • 赞(0)
    未经允许不得转载:171主机测评 » 第 33 章 · 稀疏矩阵
    分享到: 更多 (0)

    评论 抢沙发

    • 昵称 (必填)
    • 邮箱 (必填)
    • 网址