矩阵循环移位

Posted

技术标签:

【中文标题】矩阵循环移位【英文标题】:Matrix Circular Shift 【发布时间】:2009-09-25 17:56:32 【问题描述】:

有谁知道右循环移动矩阵的有效方法?顺便说一句,矩阵是二进制的,但解决非二进制矩阵的方法也可以。

现在,我正在考虑为矩阵的行实现一个循环数组,并在需要移位操作时更新每一行。

我正在考虑的另一种方法是实现一个指向由向量表示的(矩阵的)列的指针向量,并在发生移位操作时交换它们。

例如

1 2 3
4 5 6
7 8 9

右移

3 1 2
6 4 5
9 7 8

如果我还需要将矩阵向下移动,那么所有这些解决方案都会出现另一个问题。要有效地执行这两个操作,完全超出了我的能力范围。

降档

9 7 8
3 1 2
6 4 5

【问题讨论】:

定义位矩阵。矩阵是如何表示的?例如,对尺寸有什么要求?固定的?动态的?等等等等等等。将序列向左或向右旋转只是循环序列并在某个临时变量中记住一个值的问题。 矩阵可以用任何形式表示。存储的数据将是布尔值。每个矩阵的大小范围可以从大约 300x300 到 1000x1000(不一定是正方形)。是的,就是这样。但是,如果我们交换指向列的指针而不是实际复制值,如果我们向左或向右旋转它会更有效。 您可以简单地编写一个用于存储支持快速旋转的矩阵的类,而不是实际移动/复制数据。参见 std::deque 例如: tmp = deque.front(); deque.pop_front(); deque.push_back(tmp);全部在 O(1) 内完成 谢谢,抱歉没提,但我以后需要随机访问矩阵,而 deque 不支持。是的,也许我可以稍后将它复制到向量数组中。 deque 只是与您想要的类似的一个例子。顺便说一句:它确实支持快速随机访问。 【参考方案1】:

可能是这样的,

class matrix 
    std::vector<bool> elements;
    int rows, cols, row_ofs, col_ofs;

    std::size_t index(int r, int c) 
        r = (r + row_ofs) % rows;
        c = (c + col_ofs) % cols;
        return std::size_t(r)*cols + c; // row major layout
    
public:
    matrix() : rows(0), cols(0) 
    matrix(int r, int c)
    : elements(std::size_t(r)*c), rows(r), cols(c) 

    int num_rows() const  return rows; 
    int num_cols() const  return cols; 

    std::vector<bool>::reference operator()(int r, int c) 
        return elements.at(index(r,c));
    

    bool operator()(int r, int c) const 
        return elements.at(index(r,c));
    

    void rotate_left()   col_ofs = (col_ofs+1     ) % cols; 
    void rotate_right()  col_ofs = (col_ofs+cols-1) % cols; 
    void rotate_up()     row_ofs = (row_ofs+1     ) % rows; 
    void rotate_down()   row_ofs = (row_ofs+rows-1) % rows; 
;

(未经测试)

编辑:这是另一种选择:在内部使用 std::deque<:deque> >。 ;-) 是的,它确实支持随机访问。双端队列不是列表。此外,您无需再为模数运算而烦恼。

【讨论】:

难道不是vector 应该真的有问题吗? 在这种情况下,我认为使用 std::vector 没有问题【参考方案2】:

不确定您的确切意思。通常右移应用于缓冲区或行向量。答案将取决于您的矩阵的存储方式。

如果内存布局允许,旋转数组的一种有效方法是将第一个值复制到数组的末尾,然后将指向数组的指针向上移动一个元素。这只有在您为阵列分配足够的空间并且不要旋转太多次时才有效。

或者,您可以只保留数组并有一个指向“左端”的额外指针,注意在您的其他操作中正确处理所有环绕。

否则,您可能需要执行大量的内存复制。

编辑:我看到您刚刚更新了问题以包含此答案。

其他编辑:从示例中,您似乎不需要单独移动行和列。如果是这种情况,那么您只需要存储“左上”索引的坐标并修改所有矩阵运算以适当地在数据结构中查找值。

然后你的问题变成了你想要效率的问题。您要执行许多轮班操作吗?如果不是,那么可能不值得通过额外的查找来减慢所有乘法运算的速度。

如果你确实使用了查找的想法,绝对不要使用 mod 运算符。这是非常低效的。相反,对于移位,只需测试大于行或列的长度,并在需要时减去长度。

【讨论】:

【参考方案3】:

我正在考虑的另一种方法是实现一个指向由向量表示的(矩阵的)列的指针向量,并在发生移位操作时交换它们。

我会为列(水平移位)和行的另一个向量(垂直移位)执行此操作。

我还将创建一个 Matrix 对象来封装您的“真实”矩阵和这两个向量。对象的 getter/setter 将引用这两个向量来访问“真实”矩阵中的数据,并且您将拥有诸如“horizo​​ntalShift(...)”和“verticalShift(...)”之类的方法,它们仅在您的两个向量,就像你建议的那样。

这会是最快的实现吗?还有一种间接访问数据的方式(尽管仍然是 O(1)),并且使用向量的交换将是 O(m) 用于水平移位和 O(n) 用于垂直移位(对于 n x m 矩阵)。

【讨论】:

【参考方案4】:

有些方法可以使转换本身非常快,但在尝试“使用”矩阵时会导致效率低下,例如打印,点\叉积。

例如,如果我有一个定义为“int m[3][2];”的矩阵我可能只使用索引来定义第一列索引。因此,移位只是该索引的加减(不修改数据)。

另一个例子;如果您想将矩阵限制为二进制,您可以将矩阵打包成单个变量并使用位移位(向左\向右旋转)。

然而,这两种方法都会使其他操作更加复杂。

我想这完全取决于矩阵的使用范围以及您希望它的通用程度。

【讨论】:

在位矩阵上循环移位不是非常复杂,以至于簿记的数量可能超过应用程序吗?那么双向(行和列)的变化呢?【参考方案5】:

使用Eigen library很简单:

Eigen::Matrix<int, 3, 3> A;
A << 1, 2, 3,
    4, 5, 6,
    7, 8, 9;
std::cout << A << std::endl << std::endl;
// Right-shift:
A.col(0).swap(A.col(1));
A.col(0).swap(A.col(2));
std::cout << A << std::endl << std::endl;
// Down-shift:
A.row(0).swap(A.row(1));
A.row(0).swap(A.row(2));
std::cout << A << std::endl << std::endl;

有一个非常有用的reference guide 用于 Eigen-MATLAB 对应。

【讨论】:

【参考方案6】:

我通过逆时针移位实现了递归 C++ 版本:

// rotateMatrix.cpp : Defines the entry point for the console application.
//

#include "stdafx.h"
#include <iostream>
using namespace std;

void rotatematrix(int M[][3], int row, int col, int rowLen, int colLen)

    //rowLen & colLen are always the orginal matrix total length
    // playRows & playCols are the size for the current recuision
    // row & col are the starting position related to the original matrix(0,0)
    int playRows = rowLen - 2*row ;
    int playCols = colLen - 2*col;

    if (playCols <= 1 || playRows <= 1)
        return;

    //row,col is the starting point pointing to the top left corner element
    if (rowLen <= 1 || colLen <= 1) return;

    int tmp = M[row][col];

    //left shift the top row by one element
    for (int j = col; j <= playCols + col - 2; ++j)
        M[row][j] = M[row][j + 1];

    // up shift the right colunm by one position
    for (int i = row; i <= playRows + row - 2; ++i)
        M[i][col + playCols - 1] = M[i + 1][col + playCols - 1];

    //right shift the bottom row by one
    for (int j = col + playCols - 2; j >= col; --j)
        M[row+playRows-1][j+1] = M[row+playRows-1][j];

    // down shift the left col by one
    for (int i = row + playRows - 2; i >= row; --i)
        M[i+1][col] = M[i][col];

    M[row + 1][col] = tmp;


    rotatematrix(M, ++row, ++col, rowLen, colLen);


int _tmain(int argc, _TCHAR* argv[])

    // Test Case 1
    /*
    int a[4][4] =   1, 2, 3, 4 ,
     5, 6, 7, 8 ,
     9, 10, 11, 12 ,
     13, 14, 15, 16  ;
    int R = 4, C = 4;*/

    // Tese Case 2
    int R = 3, C = 3;
    int a[3][3] = 1, 2, 3,
    4, 5, 6,
    7, 8, 9
    ;

    for (int i = 0; i<R; i++)
    
        for (int j = 0; j<C; j++)
            cout << a[i][j] << " ";
        cout << endl;
    

    rotatematrix(a, 0, 0, 3, 3);

    // Print rotated matrix
    for (int i = 0; i<R; i++)
    
        for (int j = 0; j<C; j++)
            cout << a[i][j] << " ";
        cout << endl;
    

    return 0;

【讨论】:

【参考方案7】:

我已经编写了逐层旋转数组的代码。

#include<stdio.h>
#include<stdlib.h>

int main()

    int n;
    int value=1;
    scanf("%d",&n);
    int arr[n][n];
    for(int i=0;i<n;i++)
    for(int j=0;j<n;j++)
    arr[i][j]=value++;
    

  
    for(int i=0;i<n;i++)
    
    for(int j=0;j<n;j++)
    printf("%d\t",arr[i][j]);
    printf("\n");
    
    
    for(int r1=0,r2=n-1,c1=0,c2=n-1;r1<=r2;r1++,r2--,c1++,c2--)
    
    int temp=arr[c1][r2];
    
    for(int i=r2;i>r1;i--)
    arr[c1][i]=arr[c1][i-1];
    
    int temp2=arr[c2][r2];
    
    for(int i=c2;i>c1;i--)
    if(i!=c1+1)
    arr[i][r2]=arr[i-1][r2];
    else
    arr[i][r2]=temp;

    temp=arr[c2][r1];
    
    for(int i=r1;i<r2;i++)
    if(i!=r2-1)
    arr[c2][i]=arr[c2][i+1];
    else
    arr[c2][i]=temp2;
    
    for(int i=c1;i<c2;i++)
    if(i!=c2-1)
    arr[i][r1]=arr[i+1][r1];
    else
    arr[i][r1]=temp;
    
    
    
    printf("\n\n");
    for(int i=0;i<n;i++)
    
    for(int j=0;j<n;j++)
    printf("%d\t",arr[i][j]);
    printf("\n");
    
    return 0;

示例代码工作:

【讨论】:

以上是关于矩阵循环移位的主要内容,如果未能解决你的问题,请参考以下文章

循环移位算法

C语言 循环移位

西门子plc循环移位指令的用法

(计算机组成原理)第二章数据的表示和运算-第二节3:定点数的移位运算(算数移位逻辑移位和循环移位)

实现字符串右循环移位

数组循环移位问题