矩阵的原地转置

22

如果一个大小为m*n的矩阵被表示为单个大小为m*n的数组,那么是否可能原地转置该矩阵?

通常的算法:

transpose(Matrix mat,int rows, int cols ){
    //construction step
    Matrix tmat;
    for(int i=0;i<rows;i++){
      for(int j=0;j<cols;j++){
       tmat[j][i] = mat[i][j];
      }
    }
 }

除非矩阵是方阵,否则它不适用于单个数组。 如果没有,需要增加的最小内存量是多少?

编辑: 我已经尝试了所有种类的

for(int i=0;i<n;++i) {
  for(int j=0;j<i;++j) {
     var swap = m[i][j];
     m[i][j] = m[j][i];
     m[j][i] = swap;
  }
}

这是不正确的。在这个特定的例子中,m 甚至不存在。在一个单行矩阵中,mat[i][j]=mat[i*m+j],其中 trans[j][i]=trans[i*n+j]


16
可能性存在,但并不简单。通常情况下,维基百科会给出答案。http://en.wikipedia.org/wiki/In-place_matrix_transposition - harold
不久之前,我在不知道正确术语的情况下提出了一个类似的问题。 - Christian Ammer
谢谢哈罗德!我将会实现并在这里发布。 - UmNyobe
我已经删除了我的回答,因为它不正确。 - Gregor
@close-voters 这个问题有什么不够建设性的地方吗? - Christian Rau
2
有趣。我想有时候你可能需要这个。在进行查找时,我可能只会保持矩阵不变并交换i和j。 - Rusty Rob
3个回答

20

Wikipedia - Following the cycles算法描述的启发,我设计了以下C++实现:

#include <iostream>  // std::cout
#include <iterator>  // std::ostream_iterator
#include <algorithm> // std::swap (until C++11)
#include <vector>

template<class RandomIterator>
void transpose(RandomIterator first, RandomIterator last, int m)
{
    const int mn1 = (last - first - 1);
    const int n   = (last - first) / m;
    std::vector<bool> visited(last - first);
    RandomIterator cycle = first;
    while (++cycle != last) {
        if (visited[cycle - first])
            continue;
        int a = cycle - first;
        do  {
            a = a == mn1 ? mn1 : (n * a) % mn1;
            std::swap(*(first + a), *cycle);
            visited[a] = true;
        } while ((first + a) != cycle);
    }
}

int main()
{
    int a[] = { 0, 1, 2, 3, 4, 5, 6, 7 };
    transpose(a, a + 8, 4);
    std::copy(a, a + 8, std::ostream_iterator<int>(std::cout, " "));
}

该程序对2×4矩阵进行原地转置。
0 1 2 3
4 5 6 7

将以行主序列表示的{0, 1, 2, 3, 4, 5, 6, 7}转化为4 × 2矩阵

0 4
1 5
2 6
3 7

represented by the row-major ordering {0, 4, 1, 5, 2, 6, 3, 7}transpose 的参数 m 表示行数,列数 n 由行数和序列大小确定。该算法需要 m × n 位辅助存储来存储已交换元素的信息。序列的索引使用以下方案映射:
00
12
24
36
41
53
65
77

一般情况下,映射函数如下所示:

如果 idx < (m × n),则 idx → (idx × n) mod (m × n - 1);否则 idx → idx

我们可以在这个序列中识别出四个循环: { 0 }, { 1, 2, 4 }, {3, 5, 6}{ 7 }。每个循环都可以独立于其他循环进行转置。变量cycle最初指向第二个元素(第一个元素无需移动,因为0 → 0)。位数组visited保存已经转置的元素,并指示需要移动索引1(第二个元素)。索引1与索引2进行交换(映射函数)。现在索引1持有索引2的元素,并将此元素与索引4的元素进行交换。现在索引1持有索引4的元素。索引4的元素应该到达索引1,它已经在正确的位置上了,循环的转置已经完成,所有被触及的索引都已被标记为已访问。变量cycle增加到第一个未被访问的索引,即3。该过程继续进行到所有循环都被转置为止。

这是确实正确的...但我无法理解它的复杂度(坏情况的时间复杂度为O((mn)^3/2)) - UmNyobe
引用维基百科的话:“已知算法在最优情况下的计算成本为O(MN log MN),具有线性对数级别的最坏情况成本。” - Christian Ammer
你的还是高得多... 不过,作为一个答案,它很好。 - UmNyobe
1
@nhahtdh:感谢您的测试。我已经纠正了算法。最初,我尝试实现它而不需要辅助存储。 - Christian Ammer
@UmNyobe:关于Christian Ammer的回答,通过聚合分析,时间复杂度确实是O(mn),因为每个元素只移动一次,每个元素也仅检查一次以查看其是否被访问,你有什么看法? - user1636239
显示剩余2条评论

3
问题在于,任务设置不正确。如果您指的是通过使用相同的矩阵来表示“相同的位置”,那么这是一个正确的任务。但是,当您谈论将其写入内存中的相同区域时,“矩阵被表示为大小为m * n的单个数组”,您必须添加它是如何表示的。否则,除了读取该矩阵的函数-只需在其中交换索引即可-以外,无需更改任何内容。
您想要转置内存中的矩阵表示,使“通过索引读取/设置此矩阵的函数保持相同”。是吗?
此外,我们不知道矩阵是按行还是按列写入内存的,因此无法编写算法。好吧,让我们说“它是按行写入的”。是吗?
如果我们设置这两个缺失的条件,则任务变得正确且不难解决。
我们应该简单地通过线性索引获取矩阵中的每个元素,找到其行/列对,进行转置,找到另一个结果线性索引并将值放入新位置。问题在于,只有在方阵的情况下,变换才是自对称的,因此实际上无法在现场完成。或者,如果我们找到整个索引转换映射,稍后在矩阵上使用它,那么它就可以完成。
“起始矩阵A:” m-行数 n-列数 nm-元素数量 li-线性索引 i-列号 j-行号
“结果矩阵B:” lir-结果线性索引
“转换数组trans”
//preparation
for (li=0;li<nm;li++){
    j=li / n;
    i=li-j*n;
    lir=i*m+j;
    trans[li]=lir;
}

// transposition
for (li=0;li<nm;li++){
   cur=li;
   lir=trans[cur];
   temp2=a[lir];
   cur=lir;
   while (cur!=li){
      lir=trans[cur];
      temp1=a[cur];
      a[cur]=temp2;
      temp2=temp1;
      check[cur]=1;
      cur=lir;
   }
}

只有单元格中存在较重的元素时,自动转置才有意义。

可以将trans[]数组实现为一个函数。


同一位置 = 同一内存; 转置 = 转置数据本身,而不是访问; mn = row1 | row2 | ... | rown = 按行编写; 到目前为止,您已经正确陈述了问题。现在您的算法是错误的,正如我所说,我已经尝试过您提出的建议。请使用 [1,2,3],[4,5,6] 在内存中尝试它 [1,2,3,4,5,6]。 - UmNyobe
我在一个被数千人使用的程序中使用了这个算法。很可能是你的代码出现了错误,请把代码放在这里。(我不坚持说我的代码没有错误 - 我是凭记忆写的) - Gangnus
它只适用于方阵。即使是算法背后的通用思想。 - UmNyobe
请查看修改后的答案。你是正确的,之前确实只适用于方阵。 - Gangnus

2

在一般情况下,高效地完成这项任务需要一些努力。非方阵和内部与外部算法不同。节省大量的精力,只需使用FFTW。我以前写过更完整的文章,包括样例代码。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接