如何旋转一个二维数组?

359

雷蒙德·陈的帖子的启发,假设你有一个4x4的二维数组,请编写一个将其旋转90度的函数。Raymond在其文章中提供了一种伪代码解决方案,但我想看到一些实际的东西。

[1][2][3][4]
[5][6][7][8]
[9][0][1][2]
[3][4][5][6]

变成:

[3][9][5][1]
[4][0][6][2]
[5][1][7][3]
[6][2][8][4]

更新: Nick的回答最为直接,但是有没有比n^2更好的方法呢?如果矩阵是10000x10000呢?


114
你怎么可能只用少于n^2的步骤?所有元素都必须读取和设置,而且有n^2个元素。 - erikkallen
9
您的n是多少?您没有说明2D数组是否为正方形(一般情况下并非如此!例如,向量是具有一个维度为1的矩阵),但是您似乎在暗示n是宽度和高度,并且因此有n²个元素。更合理的做法是让n成为元素数量,其中n=w×h。 - niXar
1
以下是一种快速的方法:存储行和列索引(例如 i 和 j)。转置只需要常数时间(只需交换索引即可 :))。您可以使用旋转进行相同的操作(玩转索引)。 - mrk
4
如果n^2不可行,您可以创建一个接口来访问每个元素。然后,给定(i,j),对(i,j)应用旋转操作以访问旋转后的元素并返回其值。这可能不是最佳解决方案,但是可以使用。 - Confuse
1
我认为你仍然可以在小于N^2的时间内完成它。但是,需要使用更复杂的数据结构。也许是某种双向链表? - Younes Nj
显示剩余8条评论
64个回答

1

这是我尝试进行矩阵90度旋转的C语言两步解决方案。首先原地转置矩阵,然后交换列。

#define ROWS        5
#define COLS        5

void print_matrix_b(int B[][COLS], int rows, int cols) 
{
    for (int i = 0; i <= rows; i++) {
        for (int j = 0; j <=cols; j++) {
            printf("%d ", B[i][j]);
        }
        printf("\n");
    }
}

void swap_columns(int B[][COLS], int l, int r, int rows)
{
    int tmp;
    for (int i = 0; i <= rows; i++) {
        tmp = B[i][l];
        B[i][l] = B[i][r];
        B[i][r] = tmp;
    }
}


void matrix_2d_rotation(int B[][COLS], int rows, int cols)
{
    int tmp;
    // Transpose the matrix first
    for (int i = 0; i <= rows; i++) {
        for (int j = i; j <=cols; j++) {
            tmp = B[i][j];
            B[i][j] = B[j][i];
            B[j][i] = tmp;
        }
    }
    // Swap the first and last col and continue until
    // the middle.
    for (int i = 0; i < (cols / 2); i++)
        swap_columns(B, i, cols - i, rows);
}



int _tmain(int argc, _TCHAR* argv[])
{
    int B[ROWS][COLS] = { 
                  {1, 2, 3, 4, 5}, 
                      {6, 7, 8, 9, 10},
                          {11, 12, 13, 14, 15},
                          {16, 17, 18, 19, 20},
                          {21, 22, 23, 24, 25}
                        };

    matrix_2d_rotation(B, ROWS - 1, COLS - 1);

    print_matrix_b(B, ROWS - 1, COLS -1);
    return 0;
}

1

这是我的C语言原地实现:

void rotateRight(int matrix[][SIZE], int length) {

    int layer = 0;

    for (int layer = 0; layer < length / 2; ++layer) {

        int first = layer;
        int last = length - 1 - layer;

        for (int i = first; i < last; ++i) {

            int topline = matrix[first][i];
            int rightcol = matrix[i][last];
            int bottomline = matrix[last][length - layer - 1 - i];
            int leftcol = matrix[length - layer - 1 - i][first];

            matrix[first][i] = leftcol;
            matrix[i][last] = topline;
            matrix[last][length - layer - 1 - i] = rightcol;
            matrix[length - layer - 1 - i][first] = bottomline;
        }
    }
}

1
private static int[][] rotate(int[][] matrix, int n) {
    int[][] rotated = new int[n][n];
    for (int i = 0; i < n; i++) {
        for (int j = 0; j < n; j++) {
            rotated[i][j] = matrix[n-j-1][i];
        }
    }
    return rotated;
}

这是用哪种语言编写的? - Don Cruickshank

1
short normal[4][4] = {{8,4,7,5},{3,4,5,7},{9,5,5,6},{3,3,3,3}};

short rotated[4][4];

for (int r = 0; r < 4; ++r)
{
  for (int c = 0; c < 4; ++c)
  {
    rotated[r][c] = normal[c][3-r];
  }
}

这是一个简单的C++方法,但在大数组中会有很大的内存开销。


在所有我找到并测试过的答案中,这个是最简洁且足够旋转的。 - dlewin

1

@dagorym:啊,伙计。我一直把这个当作一个好的“我无聊了,可以思考什么”的难题。我想出了我的原地转置代码,但是到这里发现你的代码跟我的几乎一模一样...唉,好吧。这是Ruby版本。

require 'pp'
n = 10
a = []
n.times { a << (1..n).to_a }

pp a

0.upto(n/2-1) do |i|
  i.upto(n-i-2) do |j|
    tmp             = a[i][j]
    a[i][j]         = a[n-j-1][i]
    a[n-j-1][i]     = a[n-i-1][n-j-1]
    a[n-i-1][n-j-1] = a[j][n-i-1]
    a[j][n-i-1]     = tmp
  end
end

pp a

1

对于 i 从 0 到 X 做如下操作: 对于 j 从 0 到 X 做如下操作: graphic[j][i] := graphic2[X-i][j]

X 是图形所在数组的大小。


1

使用Javascript编写的NxN矩阵解决方案,运行时间为O(N^2),内存占用为O(1)

  function rotate90(matrix){
    var length = matrix.length
    for(var row = 0; row < (length / 2); row++){
      for(var col = row; col < ( length - 1 - row); col++){
        var tmpVal = matrix[row][col];
        for(var i = 0; i < 4; i++){
          var rowSwap = col;
          var colSwap = (length - 1) - row;
          var poppedVal = matrix[rowSwap][colSwap];
          matrix[rowSwap][colSwap] = tmpVal;
          tmpVal = poppedVal;
          col = colSwap;
          row = rowSwap;
        }
      }
    }
  }

0

根据其他答案的丰富经验,我用C#写出了以下代码:

/// <param name="rotation">The number of rotations (if negative, the <see cref="Matrix{TValue}"/> is rotated counterclockwise; 
/// otherwise, it's rotated clockwise). A single (positive) rotation is equivalent to 90° or -270°; a single (negative) rotation is 
/// equivalent to -90° or 270°. Matrices may be rotated by 90°, 180°, or 270° only (or multiples thereof).</param>
/// <returns></returns>
public Matrix<TValue> Rotate(int rotation)
{
    var result = default(Matrix<TValue>);

    //This normalizes the requested rotation (for instance, if 10 is specified, the rotation is actually just +-2 or +-180°, but all 
    //correspond to the same rotation).
    var d = rotation.ToDouble() / 4d;
    d = d - (int)d;

    var degree = (d - 1d) * 4d;

    //This gets the type of rotation to make; there are a total of four unique rotations possible (0°, 90°, 180°, and 270°).
    //Each correspond to 0, 1, 2, and 3, respectively (or 0, -1, -2, and -3, if in the other direction). Since
    //1 is equivalent to -3 and so forth, we combine both cases into one. 
    switch (degree)
    {
        case -3:
        case +1:
            degree = 3;
            break;
        case -2:
        case +2:
            degree = 2;
            break;
        case -1:
        case +3:
            degree = 1;
            break;
        case -4:
        case  0:
        case +4:
            degree = 0;
            break;
    }
    switch (degree)
    {
        //The rotation is 0, +-180°
        case 0:
        case 2:
            result = new TValue[Rows, Columns];
            break;
        //The rotation is +-90°
        case 1:
        case 3:
            result = new TValue[Columns, Rows];
            break;
    }

    for (uint i = 0; i < Columns; ++i)
    {
        for (uint j = 0; j < Rows; ++j)
        {
            switch (degree)
            {
                //If rotation is 0°
                case 0:
                    result._values[j][i] = _values[j][i];
                    break;
                //If rotation is -90°
                case 1:
                    //Transpose, then reverse each column OR reverse each row, then transpose
                    result._values[i][j] = _values[j][Columns - i - 1];
                    break;
                //If rotation is +-180°
                case 2:
                    //Reverse each column, then reverse each row
                    result._values[(Rows - 1) - j][(Columns - 1) - i] = _values[j][i];
                    break;
                //If rotation is +90°
                case 3:
                    //Transpose, then reverse each row
                    result._values[i][j] = _values[Rows - j - 1][i];
                    break;
            }
        }
    }
    return result;
}

_values 对应于一个由 Matrix<TValue> 定义的私有二维数组(以[][]的形式)。通过隐式运算符重载,result = new TValue[Columns, Rows] 将这个二维数组转换为Matrix<TValue> 是可行的。 两个属性ColumnsRows是公共属性,用于获取当前实例的列数和行数:

public uint Columns 
    => (uint)_values[0].Length;

public uint Rows 
    => (uint)_values.Length;

当然,假设您更喜欢使用无符号索引来工作 ;-)

所有这些都允许您指定应该旋转多少次以及是否应该向左旋转(如果小于零)或向右旋转(如果大于零)。您可以改进此功能以检查实际度数的旋转,但是如果该值不是90的倍数,则需要抛出异常。有了这个输入,您可以相应地更改该方法:

public Matrix<TValue> Rotate(int rotation)
{
    var _rotation = (double)rotation / 90d;

    if (_rotation - Math.Floor(_rotation) > 0)
    {
        throw new NotSupportedException("A matrix may only be rotated by multiples of 90.").
    }

    rotation = (int)_rotation;
    ...
}

由于度数更准确地表示为double而不是int,但矩阵只能以90的倍数旋转,因此将参数对应到其他可以由所使用的数据结构准确表示的内容会更直观。 int非常完美,因为它可以告诉您旋转多少次以达到某个单位(90),以及方向。 double可能也能告诉您这一点,但它还包括不受此操作支持的值(这本质上是违反直觉的)。


0

这个解决方案不关心正方形或矩形的尺寸,你可以旋转4x5或5x4甚至4x4,它也不关心大小。 请注意,每次调用rotate90方法时,此实现都会创建一个新数组,它不会改变原始数组。

public static void main(String[] args) {
    int[][] a = new int[][] { 
                    { 1, 2, 3, 4 }, 
                    { 5, 6, 7, 8 }, 
                    { 9, 0, 1, 2 }, 
                    { 3, 4, 5, 6 }, 
                    { 7, 8, 9, 0 } 
                  };
    int[][] rotate180 = rotate90(rotate90(a));
    print(rotate180);
}

static int[][] rotate90(int[][] a) {
    int[][] ret = new int[a[0].length][a.length];
    for (int i = 0; i < a.length; i++) {
        for (int j = 0; j < a[i].length; j++) {
            ret[j][a.length - i - 1] = a[i][j];
        }
    }
    return ret;
}

static void print(int[][] array) {
    for (int i = 0; i < array.length; i++) {
        System.out.print("[");
        for (int j = 0; j < array[i].length; j++) {
            System.out.print(array[i][j]);
            System.out.print(" ");
        }
        System.out.println("]");
    }
}

0

基于社区维基算法和这个SO答案用于转置数组,这里是一个Swift 4版本,用于将一些2D数组逆时针旋转90度。这假设matrix是一个2D数组:

func rotate(matrix: [[Int]]) -> [[Int]] {
    let transposedPoints = transpose(input: matrix)
    let rotatedPoints = transposedPoints.map{ Array($0.reversed()) }
    return rotatedPoints
}


fileprivate func transpose<T>(input: [[T]]) -> [[T]] {
    if input.isEmpty { return [[T]]() }
    let count = input[0].count
    var out = [[T]](repeating: [T](), count: count)
    for outer in input {
        for (index, inner) in outer.enumerated() {
            out[index].append(inner)
        }
    }

    return out
}

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