Bootstrap

C++教程(一):超详细的C++矩阵操作和运算(附实例代码,与python对比)

python教程(一):超详细的numpy矩阵操作和运算(附实例代码)

在之前的章节中,我们详细介绍了python中numpy的矩阵操作。但在自动驾驶开发中,我们一般采用C++进行功能开发,因此C++矩阵运算就是自动驾驶开发工程师不可或缺的能力。在C++中没有直接进行矩阵操作的功能函数,需要采用数组或者vector等容器实现,或者引用第三方库,例如Eigen(一个高效的C++模板库,用于矩阵和向量的线性代数运算)、Armadillo(提供简洁语法和高效的矩阵操作,支持线性代数和统计学运算)、Boost uBLAS(Boost库中的矩阵运算模块)。在自动驾驶开发中,我们常用Eigen库,因此本文主要介绍Eigen库的矩阵操作,同时为了方便对比,我们用C++完成之前numpy中所有的操作和运算。

1. 直接运算

1.1 采用数组进行运算

如果矩阵的大小是固定的,简单的矩阵运算可以用C++二维数组来实现。下面是一个简单的矩阵加法、乘法的例子:

#include <iostream>
using namespace std;

// 定义矩阵的大小
const int ROW = 2;
const int COL = 2;

// 矩阵加法
void addMatrices(int A[ROW][COL], int B[ROW][COL], int result[ROW][COL]) {
    for (int i = 0; i < ROW; i++) {
        for (int j = 0; j < COL; j++) {
            result[i][j] = A[i][j] + B[i][j];
        }
    }
}

// 矩阵乘法
void multiplyMatrices(int A[ROW][COL], int B[ROW][COL], int result[ROW][COL]) {
    for (int i = 0; i < ROW; i++) {
        for (int j = 0; j < COL; j++) {
            result[i][j] = 0;
            for (int k = 0; k < COL; k++) {
                result[i][j] += A[i][k] * B[k][j];
            }
        }
    }
}

int main() {
    int A[ROW][COL] = {{1, 2}, {3, 4}};
    int B[ROW][COL] = {{5, 6}, {7, 8}};
    int result[ROW][COL];

    // 矩阵加法
    addMatrices(A, B, result);
    cout << "矩阵加法结果:" << endl;
    for (int i = 0; i < ROW; i++) {
        for (int j = 0; j < COL; j++) {
            cout << result[i][j] << " ";
        }
        cout << endl;
    }

    // 矩阵乘法
    multiplyMatrices(A, B, result);
    cout << "矩阵乘法结果:" << endl;
    for (int i = 0; i < ROW; i++) {
        for (int j = 0; j < COL; j++) {
            cout << result[i][j] << " ";
        }
        cout << endl;
    }

    return 0;
}

矩阵加法结果:
6 8 
10 12 
矩阵乘法结果:
19 22 
43 50

1.2 采用vector进行运算

#include <iostream>
#include <vector>

using namespace std;

// 矩阵加法
void addMatrices(const vector<vector<int>>& A, const vector<vector<int>>& B, vector<vector<int>>& result) {
    int ROW = A.size();
    int COL = A[0].size();
    for (int i = 0; i < ROW; i++) {
        for (int j = 0; j < COL; j++) {
            result[i][j] = A[i][j] + B[i][j];
        }
    }
}

// 矩阵乘法
void multiplyMatrices(const vector<vector<int>>& A, const vector<vector<int>>& B, vector<vector<int>>& result) {
    int ROW = A.size();
    int COL = B[0].size();
    int commonDim = A[0].size(); // A的列数和B的行数必须相同
    for (int i = 0; i < ROW; i++) {
        for (int j = 0; j < COL; j++) {
            result[i][j] = 0;
            for (int k = 0; k < commonDim; k++) {
                result[i][j] += A[i][k] * B[k][j];
            }
        }
    }
}

// 打印矩阵
void printMatrix(const vector<vector<int>>& matrix) {
    for (const auto& row : matrix) {
        for (const auto& elem : row) {
            cout << elem << " ";
        }
        cout << endl;
    }
}

int main() {
    // 定义2x2矩阵
    vector<vector<int>> A = {{1, 2}, {3, 4}};
    vector<vector<int>> B = {{5, 6}, {7, 8}};

    // 创建结果矩阵,初始为0
    vector<vector<int>> result(2, vector<int>(2, 0));

    // 矩阵加法
    addMatrices(A, B, result);
    cout << "矩阵加法结果:" << endl;
    printMatrix(result);

    // 矩阵乘法
    multiplyMatrices(A, B, result);
    cout << "矩阵乘法结果:" << endl;
    printMatrix(result);

    return 0;
}

2. 采用Eigen库进行计算

Eigen 是一个高效且功能强大的 C++ 模板库,主要用于矩阵和向量的数值计算,包括线性代数、矩阵分解、特征值分解等操作。它具有良好的性能优化,广泛应用于科学计算、机器学习、物理模拟等领域。使用Eigen数据库如何进行编译请参考 Linux下vscode配置C++和python编译调试环境

2.1 矩阵创建

与numpy中的array不同,c++对于向量、矩阵和张量用不同的名称表述,分别用Eigen::Vector、Eigen::Matrix和Eigen::Tensor,其中Vector和Matrix提供了固定大小和动态大小两种方式。

2.1.1. 向量表示

固定大小的向量

对于固定大小的向量,Eigen 提供了一些预定义的类型,如 Eigen::Vector2d、Eigen::Vector3d 等,这些类型分别表示 2 维、3 维的双精度向量。

Eigen::Vector3d vec;

动态大小的向量

如果向量的大小在运行时确定,可以使用 Eigen::VectorXd 来创建动态大小的向量。

Eigen::VectorXd vec(5);

2.1.2. 矩阵表示

固定大小的矩阵

Eigen 提供了 Eigen::Matrix 类型来表示固定大小的矩阵。Matrix 类型需要指定数据类型、行数和列数。例如,Eigen::Matrix3d 表示 3x3 的双精度矩阵,Eigen::Matrix2f 表示 2x2 的单精度矩阵。

Eigen::Matrix<double, 2, 3> matrix;

动态大小的矩阵

如果矩阵的大小在运行时确定,可以使用 Eigen::MatrixXd 来创建动态大小的矩阵。

Eigen::MatrixXd matrix(3, 3);

2.1.3. 张量表示

张量是高维数组,在 Eigen 中使用 Eigen::Tensor 来表示。与向量和矩阵不同,张量可以有任意维度。张量的模板参数包括数据类型和维度数。

    // 创建一个 3x3x3 的张量
    Eigen::Tensor<float, 3> tensor(3, 3, 3);

接下来我们举例主要以矩阵为主,向量和张量也类似,特殊情况会特别指出。

2.1.4 普通矩阵
#include <iostream>
#include <Eigen/Dense>  // 引入 Eigen 库

int main() {
    // 使用 Eigen 创建一个 2x3 整数矩阵
    Eigen::Matrix<int, 2, 3> matrix;
    
    // 初始化矩阵
    matrix << 1, 2, 3,
              4, 5, 6;
    
    // 输出矩阵
    std::cout << "矩阵内容:" << std::endl;
    std::cout << matrix << std::endl;

    return 0;
}
2.1.5 特殊矩阵

与python类似,Eigen 库可以直接创建全零矩阵、全 1 矩阵、单位矩阵、随机矩阵、随机整数矩阵、特定范围的矩阵、等间隔数值矩阵以及对角矩阵。

#include <iostream>
#include <Eigen/Dense>

int main() {
    // 1. 创建一个 3x3 的全零矩阵
    Eigen::Matrix3d zeroMatrix = Eigen::Matrix3d::Zero();
    std::cout << "1. 3x3 全零矩阵:\n" << zeroMatrix << "\n\n";

    // 2. 创建一个 3x3 的全 1 矩阵
    Eigen::Matrix3d onesMatrix = Eigen::Matrix3d::Ones();
    std::cout << "2. 3x3 全 1 矩阵:\n" << onesMatrix << "\n\n";

    // 3. 创建一个 4x4 的单位矩阵
    Eigen::Matrix4d identityMatrix = Eigen::Matrix4d::Identity();
    std::cout << "3. 4x4 单位矩阵:\n" << identityMatrix << "\n\n";

    // 4. 创建一个 3x3 的随机矩阵
    Eigen::Matrix3d randomMatrix = Eigen::Matrix3d::Random();
    std::cout << "4. 3x3 随机矩阵:\n" << randomMatrix << "\n\n";

    // 5. 创建一个 3x3 的随机整数矩阵,取值范围为 [-10, 10]
    Eigen::Matrix3i randomIntMatrix = (Eigen::Matrix3i::Random().array() + 1) * 5;
    std::cout << "5. 3x3 随机整数矩阵 ([-10, 10]):\n" << randomIntMatrix << "\n\n";

    // 6. 创建一个包含从 1 到 6 的线性间隔向量
    Eigen::VectorXd linSpaced = Eigen::VectorXd::LinSpaced(6, 1, 6);
    std::cout << "6. 从 1 到 6 的线性间隔向量:\n" << linSpaced << "\n\n";

    // 7. 创建一个 3x3 等间隔数值矩阵,范围从 1 到 9
    Eigen::MatrixXd linSpacedMatrix(3, 3);
    linSpacedMatrix << Eigen::VectorXd::LinSpaced(3, 1, 3),
                       Eigen::VectorXd::LinSpaced(3, 4, 6),
                       Eigen::VectorXd::LinSpaced(3, 7, 9);
    std::cout << "7. 3x3 等间隔数值矩阵:\n" << linSpacedMatrix << "\n\n";

    // 8. 创建一个 3x3 的对角矩阵,使用对角线元素 [1, 2, 3]
    Eigen::Vector3d diagElements(1, 2, 3);
    Eigen::Matrix3d diagMatrix = diagElements.asDiagonal();
    std::cout << "8. 3x3 对角矩阵:\n" << diagMatrix << "\n\n";

    return 0;
}

2.2 矩阵参数

使用 Eigen 库可以获取矩阵和张量的 ndim、shape、size、dtype、itemsize 和 nbytes 等信息。

#include <iostream>
#include <unsupported/Eigen/CXX11/Tensor>
#include <Eigen/Dense>

int main() {
    // 1. 创建一个 3x3 的矩阵
    Eigen::Matrix3d matrix;
    matrix.setRandom(); // 随机初始化矩阵
    std::cout << "矩阵内容:\n" << matrix << "\n\n";

    // 2. 矩阵的维数 (ndim)
    std::cout << "矩阵的维数 (ndim): 2" << std::endl; // 矩阵总是二维的

    // 3. 矩阵的形状 (shape)
    std::cout << "矩阵的形状 (shape): " << matrix.rows() << " x " << matrix.cols() << std::endl;

    // 4. 矩阵的元素总个数 (size)
    std::cout << "矩阵的元素总个数 (size): " << matrix.size() << std::endl;

    // 5. 矩阵中每个元素的字节大小 (itemsize)
    std::cout << "矩阵中每个元素的字节大小 (itemsize): " << sizeof(matrix(0, 0)) << " bytes" << std::endl;

    // 6. 矩阵的总字节数 (nbytes)
    std::size_t matrix_nbytes = matrix.size() * sizeof(matrix(0, 0));
    std::cout << "矩阵的总字节数 (nbytes): " << matrix_nbytes << " bytes\n\n";

    // ---------------------------

    // 7. 创建一个 2x3x4 的张量
    Eigen::Tensor<float, 3> tensor(2, 3, 4);
    tensor.setRandom(); // 随机初始化张量
    std::cout << "张量内容 (部分显示):\n";
    std::cout << "tensor(0, 0, 0) = " << tensor(0, 0, 0) << "\n";
    std::cout << "tensor(1, 2, 3) = " << tensor(1, 2, 3) << "\n\n";

    // 8. 张量的维数 (ndim)
    std::cout << "张量的维数 (ndim): " << Eigen::Tensor<float, 3>::NumDimensions << std::endl;

    // 9. 张量的形状 (shape)
    auto dims = tensor.dimensions();
    std::cout << "张量的形状 (shape): " << dims[0] << " x " << dims[1] << " x " << dims[2] << std::endl;

    // 10. 张量的元素总个数 (size)
    std::cout << "张量的元素总个数 (size): " << tensor.size() << std::endl;

    // 11. 张量中每个元素的字节大小 (itemsize)
    std::cout << "张量中每个元素的字节大小 (itemsize): " << sizeof(tensor(0, 0, 0)) << " bytes" << std::endl;

    // 12. 张量的总字节数 (nbytes)
    std::size_t tensor_nbytes = tensor.size() * sizeof(tensor(0, 0, 0));
    std::cout << "张量的总字节数 (nbytes): " << tensor_nbytes << " bytes" << std::endl;

    return 0;
}

2.3 矩阵索引

在 C++ 的 Eigen 库中,可以通过多种方式进行矩阵索引、切片、以及子矩阵的操作。

1. 基本索引
Eigen 支持通过 () 操作符进行基本的矩阵索引。您可以通过指定矩阵的行和列索引来访问或修改矩阵中的元素。

2. 矩阵切片(子矩阵)
Eigen 提供了 block() 方法,可以方便地提取矩阵的子矩阵。这类似于 Python 中的矩阵切片操作。

3. 布尔索引
Eigen 并不直接支持像 NumPy 那样的布尔索引,但可以通过条件操作生成布尔掩码,然后利用矩阵运算或块操作来实现类似功能。

4. 花式索引(Fancy Indexing)
Eigen 不支持像 NumPy 那样的花式索引(使用数组或列表作为索引)。然而,可以通过手动提取行或列,以及使用 block() 方法结合条件筛选实现类似效果。

5. 子矩阵索引(块索引)
除了前面的 block() 方法,Eigen 还提供了其他方式来获取子矩阵,如 topLeftCorner()、bottomRightCorner()、topRows()、bottomCols() 等。

#include <iostream>
#include <Eigen/Dense>

int main() {
    // 创建一个 4x4 的矩阵并初始化
    Eigen::Matrix4d matrix;
    matrix << 1, 2, 3, 4,
              5, 6, 7, 8,
              9, 10, 11, 12,
              13, 14, 15, 16;

    std::cout << "原始 4x4 矩阵:\n" << matrix << "\n\n";

    // 1. 基本索引
    std::cout << "矩阵中 (1, 2) 处的元素: " << matrix(1, 2) << std::endl;
    matrix(2, 3) = 100;  // 修改矩阵中的元素
    std::cout << "修改后的矩阵:\n" << matrix << "\n\n";

    // 2. 矩阵切片(子矩阵) - 提取子矩阵
    Eigen::Matrix2d block = matrix.block<2, 2>(1, 1);
    std::cout << "2x2 子矩阵 (从 (1, 1) 开始):\n" << block << "\n\n";

    Eigen::MatrixXd dynamic_block = matrix.block(0, 0, 3, 3);  // 3x3 子矩阵
    std::cout << "3x3 动态大小子矩阵:\n" << dynamic_block << "\n\n";

    // 3. 布尔索引 - 将大于 10 的元素置 0
    Eigen::Matrix<bool, 4, 4> mask = (matrix.array() > 10).matrix();
    std::cout << "布尔掩码 (matrix > 10):\n" << mask << "\n\n";

    matrix = (matrix.array() > 10).select(0, matrix);  // 应用布尔掩码
    std::cout << "将大于 10 的元素置 0 后的矩阵:\n" << matrix << "\n\n";

    // 4. 花式索引 - 手动选择特定的行和列
    Eigen::MatrixXd selected_rows(2, matrix.cols());
    selected_rows << matrix.row(0), matrix.row(2);
    std::cout << "选择第 0 行和第 2 行的矩阵:\n" << selected_rows << "\n\n";

    Eigen::MatrixXd selected_cols(matrix.rows(), 2);
    selected_cols << matrix.col(1), matrix.col(3);
    std::cout << "选择第 1 列和第 3 列的矩阵:\n" << selected_cols << "\n\n";

    // 5. 子矩阵索引 - 使用快捷方法提取子矩阵
    Eigen::Matrix2d top_left = matrix.topLeftCorner(2, 2);
    std::cout << "左上角 2x2 子矩阵:\n" << top_left << "\n\n";

    Eigen::Matrix2d bottom_right = matrix.bottomRightCorner(2, 2);
    std::cout << "右下角 2x2 子矩阵:\n" << bottom_right << "\n\n";

    Eigen::MatrixXd top_rows = matrix.topRows(2);
    std::cout << "前两行的矩阵:\n" << top_rows << "\n\n";

    return 0;
}

原始 4x4 矩阵:
 1  2  3  4
 5  6  7  8
 9 10 11 12
13 14 15 16

矩阵中 (1, 2) 处的元素: 7
修改后的矩阵:
  1   2   3   4
  5   6   7   8
  9  10  11 100
 13  14  15  16

2x2 子矩阵 (从 (1, 1) 开始):
 6  7
10 11

3x3 动态大小子矩阵:
 1  2  3
 5  6  7
 9 10 11

布尔掩码 (matrix > 10):
0 0 0 0
0 0 0 0
0 0 1 1
1 1 1 1

将大于 10 的元素置 0 后的矩阵:
 1  2  3  4
 5  6  7  8
 9 10  0  0
 0  0  0  0

选择第 0 行和第 2 行的矩阵:
 1  2  3  4
 9 10  0  0

选择第 1 列和第 3 列的矩阵:
 2  4
 6  8
10  0
 0  0

左上角 2x2 子矩阵:
1 2
5 6

右下角 2x2 子矩阵:
0 0
0 0

前两行的矩阵:
1 2 3 4
5 6 7 8

2.4 矩阵拼接

使用Eigen库可以很方便对矩阵进行水平和垂直拼接,Eigen库还提供了block函数来对矩阵进行更加灵活的操作,允许在矩阵中插入其他矩阵。

#include <iostream>
#include <Eigen/Dense>

int main() {
    Eigen::MatrixXd A(2, 2);
    A << 1, 2,
         3, 4;

    Eigen::MatrixXd B(2, 2);
    B << 5, 6,
         7, 8;

    // 水平拼接:在列方向上拼接两个矩阵
    Eigen::MatrixXd horizontal(A.rows(), A.cols() + B.cols());  // 行数不变,列数为A和B的列数之和
    horizontal << A, B;  // 使用Eigen的<<操作符直接拼接

    std::cout << "水平拼接后的矩阵:\n" << horizontal << std::endl;

    // 垂直拼接:在行方向上拼接两个矩阵
    Eigen::MatrixXd vertical(A.rows() + B.rows(), A.cols());  // 列数不变,行数为A和B的行数之和
    vertical << A,
               B;  // 使用Eigen的<<操作符直接拼接

    std::cout << "垂直拼接后的矩阵:\n" << vertical << std::endl;

    // 块操作:使用block函数在矩阵中插入其他矩阵
    Eigen::MatrixXd C(4, 4);
    C.setZero();  // 初始化为零矩阵

    // 使用block函数来指定位置拼接A和B
    C.block(0, 0, A.rows(), A.cols()) = A;  // 将A放置在左上角
    C.block(2, 2, B.rows(), B.cols()) = B;  // 将B放置在右下角

    std::cout << "块拼接后的矩阵:\n" << C << std::endl;

    return 0;
}

水平拼接后的矩阵:
1 2 5 6
3 4 7 8
垂直拼接后的矩阵:
1 2
3 4
5 6
7 8
块拼接后的矩阵:
1 2 0 0
3 4 0 0
0 0 5 6
0 0 7 8

2.5 矩阵初级运算

Eigen库可以非常方便地进行矩阵的逐元素加减乘除操作。Eigen库重载了基本的数学运算符,因此你可以像对待普通变量一样对矩阵进行逐元素的加、减、乘、除操作。

#include <iostream>
#include <Eigen/Dense>

int main() {
    Eigen::MatrixXd A(2, 2);
    A << 1, 2,
         3, 4;

    Eigen::MatrixXd B(2, 2);
    B << 5, 6,
         7, 8;

    // 逐元素加法
    Eigen::MatrixXd C_add = A + B;
    std::cout << "逐元素加法 A + B:\n" << C_add << std::endl;

    // 逐元素减法
    Eigen::MatrixXd C_sub = A - B;
    std::cout << "逐元素减法 A - B:\n" << C_sub << std::endl;

    // 逐元素乘法 (注意这不是矩阵乘法,而是逐元素相乘)
    Eigen::MatrixXd C_mul = A.array() * B.array();
    std::cout << "逐元素乘法 A .* B:\n" << C_mul << std::endl;

    // 逐元素除法
    Eigen::MatrixXd C_div = A.array() / B.array();
    std::cout << "逐元素除法 A ./ B:\n" << C_div << std::endl;

    return 0;
}

2.6 向量&矩阵乘法

矩阵的点乘:通过 .array() 方法实现逐元素相乘。
矩阵的乘法:通过 * 实现标准的线性代数矩阵乘法。
向量的点乘:使用 .dot() 函数进行向量的内积计算。
向量的叉乘:使用 .cross() 函数进行三维向量的外积计算。

#include <iostream>
#include <Eigen/Dense>

int main() {
    // 定义矩阵 A 和 B
    Eigen::MatrixXd A(2, 2);
    A << 1, 2,
         3, 4;

    Eigen::MatrixXd B(2, 2);
    B << 5, 6,
         7, 8;

    // 矩阵的逐元素点乘 (逐元素相乘)
    Eigen::MatrixXd elementwiseProduct = A.array() * B.array();
    std::cout << "矩阵的点乘 (逐元素相乘):\n" << elementwiseProduct << std::endl;

    // 矩阵的标准乘法 (矩阵乘法)
    Eigen::MatrixXd matrixProduct = A * B;
    std::cout << "矩阵的乘法 (矩阵乘法):\n" << matrixProduct << std::endl;

    // 定义向量 v1 和 v2
    Eigen::Vector3d v1(1, 2, 3);
    Eigen::Vector3d v2(4, 5, 6);

    // 向量的点乘 (内积)
    double dotProduct = v1.dot(v2);
    std::cout << "向量的点乘 (内积): " << dotProduct << std::endl;

    // 向量的叉乘 (外积)
    Eigen::Vector3d crossProduct = v1.cross(v2);
    std::cout << "向量的叉乘 (外积):\n" << crossProduct << std::endl;

    return 0;
}

2.7 矩阵的高级操作

2.7.1 矩阵属性

在Eigen库中,C++代码可以非常方便地执行矩阵的各种线性代数操作,比如转置、逆、行列式、迹、特征值与特征向量、广义逆矩阵以及矩阵范数。

#include <iostream>
#include <Eigen/Dense>
#include <Eigen/Eigenvalues>  // 用于特征值和特征向量
#include <unsupported/Eigen/MatrixFunctions>  // 用于广义逆

int main() {
    // 定义矩阵 A
    Eigen::MatrixXd A(3, 3);
    A << 1, 2, 3,
         0, 1, 4,
         5, 6, 0;

    // 1. 矩阵的转置
    Eigen::MatrixXd A_transpose = A.transpose();
    std::cout << "矩阵的转置:\n" << A_transpose << std::endl;

    // 2. 矩阵的逆
    Eigen::MatrixXd A_inverse = A.inverse();
    std::cout << "矩阵的逆:\n" << A_inverse << std::endl;

    // 3. 矩阵的行列式
    double determinant = A.determinant();
    std::cout << "矩阵的行列式: " << determinant << std::endl;

    // 4. 矩阵的迹(对角线元素之和)
    double trace = A.trace();
    std::cout << "矩阵的迹: " << trace << std::endl;

    // 5. 矩阵的特征值和特征向量
    Eigen::EigenSolver<Eigen::MatrixXd> eigen_solver(A);
    Eigen::VectorXcd eigenvalues = eigen_solver.eigenvalues();
    Eigen::MatrixXcd eigenvectors = eigen_solver.eigenvectors();
    std::cout << "矩阵的特征值:\n" << eigenvalues << std::endl;
    std::cout << "矩阵的特征向量:\n" << eigenvectors << std::endl;

    // 6. 广义逆矩阵(Moore-Penrose伪逆)
    Eigen::MatrixXd A_pseudo_inverse = A.completeOrthogonalDecomposition().pseudoInverse();
    std::cout << "矩阵的广义逆 (伪逆):\n" << A_pseudo_inverse << std::endl;

    // 7. 矩阵的范数 (默认为L2范数)
    double norm = A.norm();
    std::cout << "矩阵的范数: " << norm << std::endl;

    return 0;
}

矩阵的转置:
1 0 5
2 1 6
3 4 0
矩阵的逆:
-24  18   5
 20 -15  -4
 -5   4   1
矩阵的行列式: 1
矩阵的迹: 2
矩阵的特征值:
(-0.0263528,0)
   (7.25602,0)
  (-5.22967,0)
矩阵的特征向量:
 (0.757698,0)  (-0.49927,0)  (-0.22578,0)
(-0.632128,0) (-0.466742,0) (-0.526348,0)
 (0.162197,0) (-0.729987,0)  (0.819744,0)
矩阵的广义逆 (伪逆):
-24  18   5
 20 -15  -4
 -5   4   1
矩阵的范数: 9.59166

 2.7.2 矩阵分解

在Eigen库中,矩阵分解操作,如LU分解、QR分解和奇异值分解(SVD),都有良好的支持。以下是如何使用Eigen库执行这些分解操作的完整示例代码。

#include <iostream>
#include <Eigen/Dense>
#include <Eigen/Eigenvalues>  // 用于特征值和特征向量
#include <unsupported/Eigen/MatrixFunctions>  // 用于广义逆

int main() {
    // =========================== LU 分解 ===========================
    // 定义一个 3x3 矩阵 A
    Eigen::Matrix3d A;
    A << 4, 3, 2,
         3, 2, 1,
         2, 1, 3;

    // LU分解
    Eigen::PartialPivLU<Eigen::Matrix3d> lu(A);

    // 获取 L 和 U 矩阵
    Eigen::Matrix3d L = lu.matrixLU().triangularView<Eigen::Lower>();
    Eigen::Matrix3d U = lu.matrixLU().triangularView<Eigen::Upper>();

    std::cout << "矩阵 A:\n" << A << std::endl;
    std::cout << "LU 分解的 L 矩阵:\n" << L << std::endl;
    std::cout << "LU 分解的 U 矩阵:\n" << U << std::endl;

    // =========================== QR 分解 ===========================
    // 定义一个 3x3 矩阵 B
    Eigen::Matrix3d B;
    B << 12, -51, 4,
         6, 167, -68,
         -4, 24, -41;

    // QR分解
    Eigen::HouseholderQR<Eigen::Matrix3d> qr(B);

    // 获取 Q 和 R 矩阵
    Eigen::Matrix3d Q = qr.householderQ();
    Eigen::Matrix3d R = qr.matrixQR().triangularView<Eigen::Upper>();

    std::cout << "矩阵 B:\n" << B << std::endl;
    std::cout << "QR 分解的 Q 矩阵:\n" << Q << std::endl;
    std::cout << "QR 分解的 R 矩阵:\n" << R << std::endl;

    // =========================== SVD 分解 ===========================
    // 定义一个 3x3 矩阵 C
    Eigen::Matrix3d C;
    C << 1, 2, 3,
         4, 5, 6,
         7, 8, 9;

    // SVD分解
    Eigen::JacobiSVD<Eigen::Matrix3d> svd(C, Eigen::ComputeFullU | Eigen::ComputeFullV);

    // 获取 U, S 和 V 矩阵
    Eigen::Matrix3d U_svd = svd.matrixU();
    Eigen::Vector3d S_svd = svd.singularValues();
    Eigen::Matrix3d V_svd = svd.matrixV();

    std::cout << "矩阵 C:\n" << C << std::endl;
    std::cout << "SVD 分解的 U 矩阵:\n" << U_svd << std::endl;
    std::cout << "SVD 分解的奇异值 S:\n" << S_svd << std::endl;
    std::cout << "SVD 分解的 V 矩阵:\n" << V_svd << std::endl;

    return 0;
}

矩阵 A:
4 3 2
3 2 1
2 1 3
LU 分解的 L 矩阵:
   4    0    0
 0.5 -0.5    0
0.75  0.5 -1.5
LU 分解的 U 矩阵:
   4    3    2
   0 -0.5    2
   0    0 -1.5
矩阵 B:
 12 -51   4
  6 167 -68
 -4  24 -41
QR 分解的 Q 矩阵:
 -0.857143   0.394286   0.331429
 -0.428571  -0.902857 -0.0342857
  0.285714  -0.171429   0.942857
QR 分解的 R 矩阵:
 -14  -21   14
   0 -175   70
   0    0  -35
矩阵 C:
1 2 3
4 5 6
7 8 9
SVD 分解的 U 矩阵:
 0.214837  0.887231  0.408248
 0.520587  0.249644 -0.816497
 0.826338 -0.387943  0.408248
SVD 分解的奇异值 S:
    16.8481
    1.06837
2.14779e-16
SVD 分解的 V 矩阵:
  0.479671  -0.776691   0.408248
  0.572368 -0.0756865  -0.816497
  0.665064   0.625318   0.408248

2.7.3 张量计算

在Eigen库中,contract操作类似于张量的缩并(contraction),用于对两个张量进行指定维度上的乘积操作,这类似于NumPy中的np.tensordot函数。在张量计算中,缩并是一种在指定轴上进行乘积并对该轴进行求和的操作。

#include <iostream>
#include <unsupported/Eigen/CXX11/Tensor>

int main() {
    // 定义两个张量 A 和 B
    Eigen::Tensor<float, 2> A(2, 3);  // 2x3 张量
    Eigen::Tensor<float, 2> B(3, 2);  // 3x2 张量

    // 为张量 A 和 B 填充数据
    A.setValues({{1, 2, 3}, {4, 5, 6}});
    B.setValues({{7, 8}, {9, 10}, {11, 12}});

    // 张量缩并 (类似于 np.tensordot(A, B, axes=1))
    // 缩并的维度:A 的第 1 维和 B 的第 0 维
    Eigen::array<Eigen::IndexPair<int>, 1> contraction_dims = { Eigen::IndexPair<int>(1, 0) };
    Eigen::Tensor<float, 2> result = A.contract(B, contraction_dims);

    // 输出结果
    std::cout << "张量 A:\n" << A << std::endl;
    std::cout << "张量 B:\n" << B << std::endl;
    std::cout << "缩并结果 (类似 np.tensordot):\n";
    for (int i = 0; i < result.dimension(0); ++i) {
        for (int j = 0; j < result.dimension(1); ++j) {
            std::cout << result(i, j) << " ";
        }
        std::cout << std::endl;
    }

    return 0;
}

张量 A:
1 2 3
4 5 6
张量 B:
 7  8
 9 10
11 12
缩并结果 (类似 np.tensordot):
58 64 
139 154

2.8 应用案例

2.8.1 解3x3线性方程组

考虑线性方程组:

\begin{cases} x + 2y + 3z = 1 \\ 4x + 5y + 6z = 2 \\ 7x + 4y + 3z = 3 \end{cases}

对应的矩阵形式为:

A = \begin{bmatrix} 1 & 2 & 3 \\ 4 & 5 & 6 \\ 7 & 4 & 3 \end{bmatrix}          B = \begin{bmatrix} 1 \\ 2 \\ 3 \end{bmatrix}

#include <iostream>
#include <Eigen/Dense>

int main() {
    // 定义矩阵 A 和向量 b
    Eigen::Matrix3d A;
    Eigen::Vector3d b;

    // 填充矩阵 A 和向量 b
    A << 1, 2, 3,
         4, 5, 6,
         7, 4, 3;
    b << 1, 2, 3;

    // 求解线性方程组 Ax = b
    Eigen::Vector3d x = A.colPivHouseholderQr().solve(b);

    // 输出结果
    std::cout << "线性方程组的解:\n" << x << std::endl;

    return 0;
}

线性方程组的解:
      1
     -2
1.33333

2.8.2  二次多项式拟合
假设我们有一组数据点,拟合一条二次曲线y = ax^2 + bx + c

#include <iostream>
#include <Eigen/Dense>

int main() {
    // 给定的 x 和 y 数据
    Eigen::VectorXd x(6);
    x << 0, 1, 2, 3, 4, 5;
    Eigen::VectorXd y(6);
    y << 1, 1.8, 3.2, 4.5, 6.1, 8;

    // 构造 Vandermonde 矩阵 (用于多项式拟合的设计矩阵)
    Eigen::MatrixXd A(x.size(), 3);
    for (int i = 0; i < x.size(); ++i) {
        A(i, 0) = x(i) * x(i);  // x^2
        A(i, 1) = x(i);         // x
        A(i, 2) = 1;            // 常数项
    }

    // 使用最小二乘法求解 A * coeffs = y
    Eigen::VectorXd coeffs = A.colPivHouseholderQr().solve(y);

    // 输出拟合的多项式系数
    std::cout << "拟合的二次多项式系数 (a, b, c):\n" << coeffs << std::endl;

    return 0;
}

拟合的二次多项式系数 (a, b, c):
  0.1125
0.843214
0.960714

;