有效地将列/行插入到行/ col-major矢量就地存储的矩阵中

时间:2018-01-18 22:04:34

标签: c++ matrix insert

将行或列有效地插入存储在行中的矩阵并不困难 -  或col-major(分别)向量。将行插入col-major矢量或列到行主矢量中的问题稍微有点了。

例如,给定2x3矩阵存储在向量中的行主要:

1 2 3    <=>    1 2 3 4 5 6 
4 5 6

以及在原始矩阵中第1列之后插入的列7 8,我们得到:

1 7 2 3    <=>    1 7 2 3 4 8 5 6 
4 8 5 6

[在col-major向量中插入一行是类似的。]

C ++中的示例设置:

auto m = 2; // #rows
auto n = 3; // #cols
// row-major vector
auto x = std::vector<double>{1,2,3,4,5,6};

auto const colIndex = 1;
auto const col = std::vector<double>{7,8};
// insert column {7,8} into the 2nd position
// =>{1,7,2,3,4,8,5,6}

在C ++中可以有多种方法来实现这种算法,但是我们正在寻找大型矩阵和多个插入的效率和可扩展性。

我能想到的第一个显而易见的选择是使用std::vector<double>::insert将新元素插入正确的位置:

//option 1: insert in-place
x.reserve(m*(n+1));
for(auto i = 0; i < col.size(); i++)
   x.insert(begin(x) + colIndex + i * (n + 1), col[i]);

,即使对于中等数据大小也是有效但非常慢,因为每次迭代都会调整大小并进行转换。

另一个更直接的选择是创建另一个向量,填充范围[0,colIndex),colIndex,(colIndex,n+1]中的所有列,并将其与原始向量交换:

// option 2: temp vec and swap
{
    auto tmp = std::vector<double>(m*(n+1));

    for(auto i = 0; i < m; i++)
    {
        for(auto j = 0; j < colIndex; j++)
            tmp[j + i * (n + 1)] = x[j + i * n];

        tmp[colIndex + i * (n + 1)] = col[i];

        for(auto j = colIndex + 1; j < n + 1; j++)
            tmp[j + i * (n + 1)] = x[(j - 1) + i * n];
    }

    std::swap(tmp, x);
};

比选项1更快,但需要额外的空间用于矩阵复制并迭代所有元素。

有没有其他方法可以达到这个目标,在速度/空间或两者中都能超越上述目标?

关于ideone的示例代码:https://ideone.com/iXrPfF

3 个答案:

答案 0 :(得分:3)

这个版本可能会更快,特别是在规模上,并且可以作为进一步微优化的基础(如果[且仅当]真的有必要):

// one-time reallocation of the vector to get space for the new column
x.resize(x.size() + col.size());

// we'll start shifting elements over from the right
double *from = &x[m * n];
const double *src = &col[m];
double *to = from + m;

size_t R = n - colIndex; // number of cols left of the insert
size_t L = colIndex;     // number of cols right of the insert

while (to != &x[0]) {
    for (size_t i = 0; i < R; ++i) *(--to) = *(--from);
    *(--to) = *(--src); // insert value from new column
    for (size_t i = 0; i < L; ++i) *(--to) = *(--from);
}

ideone

这并不需要任何临时分配,除了可能的循环微优化之外,它可能和它一样快。为了理解它是如何工作的,我们可以从观察到原始矩阵的右下角元素在源向量中向右移动m个元素开始。从最后一个元素向后工作,在某个时刻插入插入的列向量中的值,并且源向量中的后续元素现在仅向右移动m - 1个元素。使用该逻辑,我们只需构建一个在源阵列上从右到左工作的3相循环。循环迭代m次,每行一次。循环的三个阶段,对应于它的三行代码,是:

  1. 将&#34;的行元素移到右边&#34;插入点。
  2. 从新列插入行值。
  3. 将&#34;的行元素移到左边&#34;插入点的位置(比第1阶段少一个位置)。
  4. 在变量命名方面也有很大的改进空间,算法当然应该用适当的输入参数封装在自己的函数中。一个可能的签名是:

    void insert_column(std::vector<double>& matrix,
        size_t rows, size_t columns, size_t insertBefore,
        const std::vector<double>& column);
    

    从这里开始,还有进一步的改进空间,使用模板使其成为通用的。

    从那里,您可能会发现该算法可能超出矩阵。真正发生的是你拉链&#34;拉链&#34;两个向量连同跳过和偏移(即从元素i开始,在每B个元素之后将A中的元素插入n

答案 1 :(得分:0)

所以我想要的是(完全未经测试的(tm))

x.resize(x.size() + col.size());

for (size_t processed = 0; processed < col.size(); ++processed) {
    // shift the elements for row n (starting at the end) 
    // to their new location
    auto start = x.end()-(processed+1) * rowSize;
    auto end = start + rowSize;
    auto middle = end - (col.size()-processed);
    std::rotate(start, middle, end);

    // replace one of the default value items to be the new value
    x[x.size()- rowSize*(1+processed)] = col[col.size()-processed-1];
}

你的想法就是你 [1,2,3,4,5,6]&amp;添加[a,b,c]

调整大小: [1,2,3,4,5,6,x,x,x]

第一次循环移位: [1,2,3,4,x,x,x,5,6]

第一次循环替换 [1,2,3,4,x,x,c,5,6]

第二次循环移位 [1,2,x,x,3,4,c,5,6]

等等。

由于std :: rotate是线性的,每个项目只会被移动一次;这也应该是线性的。

这与您的选项#1的不同之处在于,每次插入时,您都必须随后移动所有内容;意味着最后的x个元素被移位了col.size()次。

答案 2 :(得分:-1)

替代解决方案可以转置,然后再插入和转置。但是,就地转置是非平凡的(https://en.wikipedia.org/wiki/In-place_matrix_transposition)。请参阅此处的实施https://stackoverflow.com/a/9320349