讨论 - 创建矩阵时的数组与向量的向量 - 什么是最实用的选择

Discussion - Arrays vs Vector of Vectors when creating matrices - what is the most practical option?

本文关键字:向量 什么 选择 数组 创建 讨论      更新时间:2023-10-16

所以我偶然发现了一本名为"C++年科学计算指南">的C++书的练习

以下是练习:"编写代码,为双精度浮点数 A、B、C 的三个 2 × 2 矩阵动态分配内存,并为 A 和 B 的条目赋值。让 C = A + B.扩展代码,使其计算 C 的条目,然后将 C 的条目打印到屏幕。最后,取消分配内存。同样,检查您是否像上一个练习中一样使用 for 循环正确解除分配了内存。

这引起了我的注意,我尝试使用2D数组来解决这个问题(我能够非常轻松地做到这一点)和 使用向量向量(我失败了)。

我做了很多研究并阅读了一些关于 StackOverFlow 的文章,基本上意见是普遍的 - 在处理矩阵时,选项始终是 2D 数组。

但是知道这里有很多程序员(而且我是C++的新手),我真的很想阅读更多关于这个主题的意见!

PS这是我尝试使用向量向量创建矩阵的失败尝试的一个小片段:

for (int row{ 0 }; row < 2; row++) { // Create Matrix A - goes through the matrix rows

for (int col{ 0 }; col < 2; col++) { // goes through the matrix columns
temp.push_back(rand() % 201); // add random number to the temporary vector
}
matrixA.push_back(temp);
}
// Outputing 
for (int row{ 0 }; row < matrixA.size(); row++) { // goes through the matrix rows
for (int col{ 0 }; col < matrixA.at(row).size(); col++) { // goes through the vectors inside matrixA
cout << matrixA.at(row).at(col) << "t";
}
cout << endl;
}

这是输出: 在此处输入图像描述

问题的根源在于C++没有为多维提供内置支持 数组,以及

矩阵不是数组数组

因此,即使可以在 C/C++ 中有一个数组的数组,例如在double M[3][3]中,这不是表示矩阵的好方法。即使在编译时修复的情况下,这可能仍然可以,vector< vector<double> >方式肯定不好。

为什么:

  • 下标语法M[i][j]仍然反映了这样一个事实,即这是一个数组数组,而不是一个真正的多维数组(M[i,j]在 C/C++ 中是合法的,但意味着完全不同的东西)。M[i][j]语法也与数学用法不匹配。访问矩阵数学家通常 写

    i,j

    而不是

    (一)j

    (对应于M[i][j])

  • 数学和科学计算的经验表明,大多数时候这两个维度通常同样重要。实际上没有外在和内在的维度。切片通常由行和列完成。更复杂的下标模式(如子矩阵或跨字)也很常见。

  • 通常需要扁平化操作,即在线性向量中重新组织矩阵元素。

  • 0 x NM x 0矩阵是无法实现的。这适用于静态案例double A[N][M],也适用于无法0 x N表示的vector< vector<double> >。正确处理空矩阵非常重要。矩阵下标可能会生成空矩阵作为合法大小写。另一方面,矩阵乘法总是需要匹配的内部维度。因此0xN*NxM是有效的情况,应该不会触发任何错误。

  • vector< vector<double> >浪费内存。此外,它比需要的要慢(想想一个 N 大的Nx2矩阵,这有很多开销)。这同样适用于纯 C 中的double **M

  • vector< vector<double> >与外部线性代数库(如LAPACK等)不兼容

  • vector< vector<double> >可以是非矩形的,即需要确保所有内部向量具有相同的长度。

矩阵是它自己的实体

这就是线性算法包中经常完成的方式。有差异,但本质上都非常相似。

  • 最合理的做法是将多维数组视为其自身的实体。它不是一个数组的数组,即使乍一看似乎是一个好主意。

  • 数据存储始终是线性的

  • 矩阵元素的访问映射到此线性存储中。其实很简单。要访问i, j元素,请使用A[i*lda + j]与前导尺寸的长度lda。这并不意味着元素在内存中的某种顺序。这种情况通常称为 C 阶。相反的FORTRAN顺序只是交换ij的含义,或者在更高维度的情况下,颠倒索引的顺序。

  • 为了真正通用,nd数组包括

    struct ndarray {
    double *buffer;
    size_t  size[DIMS];
    size_t strides[DIMS]
    };
    

    这可以实现拥有(即对缓冲区拥有所有权)或非拥有矩阵。后者对于实现零开销子矩阵或列/行下标可能很重要。

    然后访问矩阵的i,j元素 (DIM == 2)

    buffer[ i*strides[0] + j*strides[1] ]
    

    这很容易推广到更高的维度。

    此外,矩阵的转置只需要反转strides数组。无需复制。

    行或列下标、子矩阵或获取跨步子矩阵可以作为零开销操作实现。只需要正确填写strides数组并相应地设置buffersize

    strides必须在创建矩阵时初始化属性,具体取决于是使用 C 还是 FORTRAN 布局。