Làm thế nào để bạn xoay một mảng hai chiều?


302

Lấy cảm hứng từ bài đăng của Raymond Chen , nói rằng bạn có một mảng 4 chiều 4 chiều, hãy viết một hàm xoay 90 độ. Raymond liên kết đến một giải pháp bằng mã giả, nhưng tôi muốn xem một số thứ trong thế giới thực.

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

Trở thành:

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

Cập nhật : Câu trả lời của Nick là đơn giản nhất, nhưng có cách nào để làm điều đó tốt hơn n ^ 2 không? Nếu ma trận là 10000x10000 thì sao?


99
Làm thế nào bạn có thể có được đi với ít hơn n ^ 2? Tất cả các yếu tố phải được đọc và thiết lập, và có n ^ 2 yếu tố
erikkallen


9
Bạn là gì Bạn không nói nếu mảng 2D là hình vuông (không phải trong trường hợp chung! Ví dụ: vectơ là ma trận có một chiều là 1), nhưng bạn dường như ngụ ý rằng n là chiều rộng và chiều cao và do đó có các phần tử n² . Sẽ có ý nghĩa hơn khi có n là số phần tử, với n = w × h.
niXar

1
Đây là một cách nhanh chóng để làm điều đó: lưu trữ các chỉ số hàng và cột (giả sử i và j). Transpose mất thời gian liên tục (chỉ cần trao đổi các chỉ số :). Bạn có thể làm tương tự với các phép quay (chơi với các chỉ số).
saadtaame

4
Trong trường hợp n ^ 2 là không khả thi. Bạn có thể tạo một giao diện truy cập từng yếu tố. Sau đó, cho (i, j), áp dụng xoay để (i, j) truy cập phần tử được xoay và trả về. Có thể không phải là soln tốt nhất nhưng hoạt động.
Nhầm lẫn

Câu trả lời:


140

Đây là C #

int[,] array = new int[4,4] {
    { 1,2,3,4 },
    { 5,6,7,8 },
    { 9,0,1,2 },
    { 3,4,5,6 }
};

int[,] rotated = RotateMatrix(array, 4);

static int[,] RotateMatrix(int[,] matrix, int n) {
    int[,] ret = new int[n, n];

    for (int i = 0; i < n; ++i) {
        for (int j = 0; j < n; ++j) {
            ret[i, j] = matrix[n - j - 1, i];
        }
    }

    return ret;
}

6
Chắc chắn, nhưng một giải pháp sử dụng bộ nhớ O (1) thì sao?
AlexeyMK

20
Giải pháp của bạn có độ phức tạp không gian O (n ^ 2). Cần phải làm tốt hơn
Kshitij Jain

6
Làm thế nào về ma trận NXM?
Rohit

18
Độ phức tạp là tuyến tính trong số lượng phần tử trong mảng. Nếu N là số phần tử thì độ phức tạp là O (N). Nếu N là độ dài của cạnh, thì có, độ phức tạp là O (N ^ 2), nhưng điều đó vẫn tối ưu. Bạn phải đọc mọi yếu tố ít nhất một lần. In ma trận là phức tạp tương tự
Alejandro

6
Để xoay -90 độ:ret[i][j] = matrix[j][n - i - 1]
Duncan Luk

387

O (n ^ 2) thời gian và thuật toán không gian O (1) (không có bất kỳ cách giải quyết và công cụ hanky-panky nào!)

Xoay theo +90:

  1. Chuyển
  2. Đảo ngược từng hàng

Xoay theo -90:

Cách 1:

  1. Chuyển
  2. Đảo ngược từng cột

Cách 2:

  1. Đảo ngược từng hàng
  2. Chuyển

Xoay theo +180:

Phương pháp 1 : Xoay theo +90 hai lần

Phương pháp 2 : Đảo ngược từng hàng và sau đó đảo ngược từng cột (Transpose)

Xoay theo -180:

Phương pháp 1 : Xoay bằng -90 hai lần

Phương pháp 2 : Đảo ngược từng cột và sau đó đảo ngược từng hàng

Phương pháp 3 : Xoay theo +180 vì chúng giống nhau


4
Điều này rất hữu ích cho tôi; Tôi đã có thể viết một thuật toán khi tôi biết "phiên bản mã giả" của thao tác này. Cảm ơn!
duma

13
Một trong những câu trả lời SO yêu thích của tôi mọi thời đại. Rất hướng dẫn!
g33kz0r

2
Đây là một JSFiddle triển khai JavaScript nếu có ai quan tâm.
Ông Polywhirl

6
Xoay theo -90: (1) Đảo ngược mỗi hàng; (2) Chuyển vị. Haskell: rotateCW = map reverse . transposerotateCCW = transpose . map reverse
Thomas Eding

5
Sự khác biệt giữa xoay 180 và -180 là gì?
Qian Chen

178

Tôi muốn thêm một chút chi tiết. Trong câu trả lời này, các khái niệm chính được lặp lại, tốc độ chậm và cố ý lặp đi lặp lại. Giải pháp được cung cấp ở đây không phải là nhỏ gọn nhất về mặt cú pháp, tuy nhiên, nó dành cho những người muốn tìm hiểu xoay vòng ma trận là gì và thực hiện kết quả.

Thứ nhất, ma trận là gì? Đối với mục đích của câu trả lời này, một ma trận chỉ là một lưới trong đó chiều rộng và chiều cao là như nhau. Lưu ý, chiều rộng và chiều cao của ma trận có thể khác nhau, nhưng để đơn giản, hướng dẫn này chỉ xem xét các ma trận có chiều rộng và chiều cao bằng nhau ( ma trận vuông ). Và vâng, ma trận là số nhiều của ma trận.

Ma trận ví dụ là: 2 × 2, 3 × 3 hoặc 5 × 5. Hay nói chung hơn, N × N. Một ma trận 2 × 2 sẽ có 4 ô vuông vì 2 × 2 = 4. Một ma trận 5 × 5 sẽ có 25 ô vuông vì 5 × 5 = 25. Mỗi hình vuông được gọi là một yếu tố hoặc mục. Chúng tôi sẽ đại diện cho mỗi phần tử với dấu chấm ( .) trong các sơ đồ bên dưới:

Ma trận 2 × 2

. .
. .

Ma trận 3 × 3

. . .
. . .
. . .

Ma trận 4 × 4

. . . .
. . . .
. . . .
. . . .

Vì vậy, nó có nghĩa là gì để xoay một ma trận? Hãy lấy ma trận 2 × 2 và đặt một số số cho mỗi phần tử để có thể quan sát được phép quay:

0 1
2 3

Xoay cái này 90 độ cho chúng ta:

2 0
3 1

Chúng tôi thực sự đã biến toàn bộ ma trận một lần sang phải giống như quay vô lăng của một chiếc xe hơi. Nó có thể giúp nghĩ về việc lật kèo Ma trận lên phía bên phải của nó. Chúng tôi muốn viết một hàm, bằng Python, lấy một ma trận và xoay một lần sang phải. Chữ ký chức năng sẽ là:

def rotate(matrix):
    # Algorithm goes here.

Ma trận sẽ được xác định bằng cách sử dụng mảng hai chiều:

matrix = [
    [0,1],
    [2,3]
]

Do đó, vị trí chỉ mục đầu tiên truy cập vào hàng. Vị trí chỉ mục thứ hai truy cập vào cột:

matrix[row][column]

Chúng ta sẽ định nghĩa một hàm tiện ích để in một ma trận.

def print_matrix(matrix):
    for row in matrix:
        print row

Một phương pháp xoay một ma trận là thực hiện một lớp tại một thời điểm. Nhưng một lớp là gì? Hãy nghĩ về một củ hành tây. Giống như các lớp của hành tây, khi mỗi lớp được loại bỏ, chúng tôi di chuyển về phía trung tâm. Tương tự khác là một con búp bê Matryoshka hoặc một trò chơi vượt qua.

Chiều rộng và chiều cao của ma trận quy định số lượng lớp trong ma trận đó. Hãy sử dụng các ký hiệu khác nhau cho mỗi lớp:

Ma trận 2 × 2 có 1 lớp

. .
. .

Ma trận 3 × 3 có 2 lớp

. . .
. x .
. . .

Ma trận 4 × 4 có 2 lớp

. . . .
. x x .
. x x .
. . . .

Một ma trận 5 × 5 có 3 lớp

. . . . .
. x x x .
. x O x .
. x x x .
. . . . .

Ma trận 6 × 6 có 3 lớp

. . . . . .
. x x x x .
. x O O x .
. x O O x .
. x x x x .
. . . . . .

Ma trận 7 × 7 có 4 lớp

. . . . . . .
. x x x x x .
. x O O O x .
. x O - O x .
. x O O O x .
. x x x x x .
. . . . . . .

Bạn có thể nhận thấy rằng việc tăng chiều rộng và chiều cao của ma trận lên một, không phải lúc nào cũng tăng số lượng lớp. Lấy các ma trận trên và lập bảng các lớp và kích thước, chúng ta thấy số lớp tăng một lần cho mỗi hai bước tăng chiều rộng và chiều cao:

+-----+--------+
| N×N | Layers |
+-----+--------+
| 1×1 |      1 |
| 2×2 |      1 |
| 3×3 |      2 |
| 4×4 |      2 |
| 5×5 |      3 |
| 6×6 |      3 |
| 7×7 |      4 |
+-----+--------+

Tuy nhiên, không phải tất cả các lớp đều cần quay. Ma trận 1 × 1 giống nhau trước và sau khi quay. Lớp trung tâm 1 × 1 luôn giống nhau trước và sau khi xoay cho dù ma trận tổng thể có lớn đến đâu:

+-----+--------+------------------+
| N×N | Layers | Rotatable Layers |
+-----+--------+------------------+
| 1×1 |      1 |                0 |
| 2×2 |      1 |                1 |
| 3×3 |      2 |                1 |
| 4×4 |      2 |                2 |
| 5×5 |      3 |                2 |
| 6×6 |      3 |                3 |
| 7×7 |      4 |                3 |
+-----+--------+------------------+

Cho ma trận N × N, làm thế nào chúng ta có thể lập trình xác định số lượng lớp chúng ta cần xoay? Nếu chúng ta chia chiều rộng hoặc chiều cao cho hai và bỏ qua phần còn lại, chúng ta sẽ nhận được kết quả như sau.

+-----+--------+------------------+---------+
| N×N | Layers | Rotatable Layers |   N/2   |
+-----+--------+------------------+---------+
| 1×1 |      1 |                0 | 1/2 = 0 |
| 2×2 |      1 |                1 | 2/2 = 1 |
| 3×3 |      2 |                1 | 3/2 = 1 |
| 4×4 |      2 |                2 | 4/2 = 2 |
| 5×5 |      3 |                2 | 5/2 = 2 |
| 6×6 |      3 |                3 | 6/2 = 3 |
| 7×7 |      4 |                3 | 7/2 = 3 |
+-----+--------+------------------+---------+

Chú ý làm thế nào N/2phù hợp với số lượng các lớp cần phải được xoay? Đôi khi số lượng các lớp có thể xoay là một ít hơn tổng số lớp trong ma trận. Điều này xảy ra khi lớp trong cùng được hình thành chỉ có một phần tử (tức là ma trận 1 × 1) và do đó không cần phải xoay. Nó chỉ đơn giản là bị bỏ qua.

Chúng tôi chắc chắn sẽ cần thông tin này trong chức năng của mình để xoay một ma trận, vì vậy hãy thêm nó ngay bây giờ:

def rotate(matrix):
    size = len(matrix)
    # Rotatable layers only.
    layer_count = size / 2

Bây giờ chúng ta biết các lớp là gì và làm thế nào để xác định số lượng các lớp thực sự cần quay, làm thế nào để chúng ta cách ly một lớp duy nhất để chúng ta có thể xoay nó? Đầu tiên, chúng tôi kiểm tra một ma trận từ lớp ngoài cùng, vào bên trong, đến lớp trong cùng. Một ma trận 5 × 5 có tổng cộng ba lớp và hai lớp cần quay:

. . . . .
. x x x .
. x O x .
. x x x .
. . . . .

Trước tiên hãy nhìn vào các cột. Vị trí của các cột xác định lớp ngoài cùng, giả sử chúng ta đếm từ 0, là 0 và 4:

+--------+-----------+
| Column | 0 1 2 3 4 |
+--------+-----------+
|        | . . . . . |
|        | . x x x . |
|        | . x O x . |
|        | . x x x . |
|        | . . . . . |
+--------+-----------+

0 và 4 cũng là vị trí của các hàng cho lớp ngoài cùng.

+-----+-----------+
| Row |           |
+-----+-----------+
|   0 | . . . . . |
|   1 | . x x x . |
|   2 | . x O x . |
|   3 | . x x x . |
|   4 | . . . . . |
+-----+-----------+

Điều này sẽ luôn luôn là trường hợp vì chiều rộng và chiều cao là như nhau. Do đó, chúng ta có thể xác định vị trí cột và hàng của một lớp chỉ bằng hai giá trị (chứ không phải bốn).

Di chuyển vào trong lớp thứ hai, vị trí của các cột là 1 và 3. Và, vâng, bạn đoán nó, nó giống nhau cho các hàng. Điều quan trọng là phải hiểu rằng chúng tôi đã phải tăng và giảm các vị trí hàng và cột khi di chuyển vào lớp kế tiếp.

+-----------+---------+---------+---------+
|   Layer   |  Rows   | Columns | Rotate? |
+-----------+---------+---------+---------+
| Outermost | 0 and 4 | 0 and 4 | Yes     |
| Inner     | 1 and 3 | 1 and 3 | Yes     |
| Innermost | 2       | 2       | No      |
+-----------+---------+---------+---------+

Vì vậy, để kiểm tra từng lớp, chúng tôi muốn một vòng lặp có cả bộ đếm tăng và giảm đại diện cho việc di chuyển vào trong, bắt đầu từ lớp ngoài cùng. Chúng tôi sẽ gọi đây là 'vòng lặp lớp' của chúng tôi.

def rotate(matrix):
    size = len(matrix)
    layer_count = size / 2

    for layer in range(0, layer_count):
        first = layer
        last = size - first - 1
        print 'Layer %d: first: %d, last: %d' % (layer, first, last)

# 5x5 matrix
matrix = [
    [ 0, 1, 2, 3, 4],
    [ 5, 6, 6, 8, 9],
    [10,11,12,13,14],
    [15,16,17,18,19],
    [20,21,22,23,24]
]

rotate(matrix)

Đoạn mã trên lặp qua các vị trí (hàng và cột) của bất kỳ lớp nào cần xoay.

Layer 0: first: 0, last: 4
Layer 1: first: 1, last: 3

Bây giờ chúng ta có một vòng lặp cung cấp vị trí của các hàng và cột của mỗi lớp. Các biến firstlastxác định vị trí chỉ mục của các hàng và cột đầu tiên và cuối cùng. Giới thiệu trở lại bảng hàng và cột của chúng tôi:

+--------+-----------+
| Column | 0 1 2 3 4 |
+--------+-----------+
|        | . . . . . |
|        | . x x x . |
|        | . x O x . |
|        | . x x x . |
|        | . . . . . |
+--------+-----------+

+-----+-----------+
| Row |           |
+-----+-----------+
|   0 | . . . . . |
|   1 | . x x x . |
|   2 | . x O x . |
|   3 | . x x x . |
|   4 | . . . . . |
+-----+-----------+

Vì vậy, chúng ta có thể điều hướng qua các lớp của một ma trận. Bây giờ chúng ta cần một cách điều hướng trong một lớp để chúng ta có thể di chuyển các phần tử xung quanh lớp đó. Lưu ý, các phần tử không bao giờ "nhảy" từ lớp này sang lớp khác, nhưng chúng di chuyển trong các lớp tương ứng.

Xoay từng phần tử trong một lớp xoay toàn bộ lớp. Xoay tất cả các lớp trong một ma trận làm quay toàn bộ ma trận. Câu này rất quan trọng, vì vậy hãy cố gắng hết sức để hiểu nó trước khi tiếp tục.

Bây giờ, chúng ta cần một cách thực sự di chuyển các phần tử, tức là xoay từng phần tử, và sau đó là lớp, và cuối cùng là ma trận. Để đơn giản, chúng tôi sẽ trở lại ma trận 3x3 - có một lớp có thể xoay.

0 1 2
3 4 5
6 7 8

Vòng lặp lớp của chúng tôi cung cấp các chỉ mục của các cột đầu tiên và cuối cùng, cũng như các hàng đầu tiên và cuối cùng:

+-----+-------+
| Col | 0 1 2 |
+-----+-------+
|     | 0 1 2 |
|     | 3 4 5 |
|     | 6 7 8 |
+-----+-------+

+-----+-------+
| Row |       |
+-----+-------+
|   0 | 0 1 2 |
|   1 | 3 4 5 |
|   2 | 6 7 8 |
+-----+-------+

Bởi vì ma trận của chúng ta luôn luôn vuông, chúng ta chỉ cần hai biến firstlastvì các vị trí chỉ mục giống nhau cho các hàng và cột.

def rotate(matrix):
    size = len(matrix)
    layer_count = size / 2

    # Our layer loop i=0, i=1, i=2
    for layer in range(0, layer_count):

        first = layer
        last = size - first - 1

        # We want to move within a layer here.

Các biến đầu tiên và cuối cùng có thể dễ dàng được sử dụng để tham chiếu bốn góc của ma trận. Điều này là do các góc có thể được xác định bằng các hoán vị khác nhau firstlast(không có phép trừ, cộng hoặc bù của các biến đó):

+---------------+-------------------+-------------+
| Corner        | Position          | 3x3 Values  |
+---------------+-------------------+-------------+
| top left      | (first, first)    | (0,0)       |
| top right     | (first, last)     | (0,2)       |
| bottom right  | (last, last)      | (2,2)       |
| bottom left   | (last, first)     | (2,0)       |
+---------------+-------------------+-------------+

Vì lý do này, chúng tôi bắt đầu xoay vòng ở bốn góc bên ngoài - chúng tôi sẽ xoay chúng trước. Hãy làm nổi bật chúng với *.

* 1 *
3 4 5
* 7 *

Chúng tôi muốn trao đổi *với nhau *ở bên phải của nó. Vì vậy, hãy tiếp tục in ra các góc của chúng tôi được xác định chỉ bằng các hoán vị khác nhau của firstlast:

def rotate(matrix):
    size = len(matrix)
    layer_count = size / 2
    for layer in range(0, layer_count):

        first = layer
        last = size - first - 1

        top_left = (first, first)
        top_right = (first, last)
        bottom_right = (last, last)
        bottom_left = (last, first)

        print 'top_left: %s' % (top_left)
        print 'top_right: %s' % (top_right)
        print 'bottom_right: %s' % (bottom_right)
        print 'bottom_left: %s' % (bottom_left)

matrix = [
[0, 1, 2],
[3, 4, 5],
[6, 7, 8]
]

rotate(matrix)

Đầu ra phải là:

top_left: (0, 0)
top_right: (0, 2)
bottom_right: (2, 2)
bottom_left: (2, 0)

Bây giờ chúng tôi có thể dễ dàng trao đổi từng góc trong vòng lặp lớp của chúng tôi:

def rotate(matrix):
    size = len(matrix)
    layer_count = size / 2
    for layer in range(0, layer_count):

        first = layer
        last = size - first - 1

        top_left = matrix[first][first]
        top_right = matrix[first][last]
        bottom_right = matrix[last][last]
        bottom_left = matrix[last][first]

        # bottom_left -> top_left
        matrix[first][first] = bottom_left
        # top_left -> top_right
        matrix[first][last] = top_left
        # top_right -> bottom_right
        matrix[last][last] = top_right
        # bottom_right -> bottom_left
        matrix[last][first] = bottom_right


print_matrix(matrix)
print '---------'
rotate(matrix)
print_matrix(matrix)

Ma trận trước khi xoay góc:

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

Ma trận sau khi xoay góc:

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

Tuyệt quá! Chúng tôi đã xoay thành công từng góc của ma trận. Nhưng, chúng tôi đã không xoay các phần tử ở giữa mỗi lớp. Rõ ràng chúng ta cần một cách lặp trong một lớp.

Vấn đề là, vòng lặp duy nhất trong chức năng của chúng ta cho đến nay (vòng lặp lớp của chúng ta), di chuyển đến lớp tiếp theo trên mỗi lần lặp. Vì ma trận của chúng ta chỉ có một lớp có thể xoay, nên vòng lặp lớp thoát ra sau khi chỉ xoay các góc. Hãy xem điều gì xảy ra với ma trận 5 × 5 lớn hơn (trong đó hai lớp cần quay). Mã chức năng đã bị bỏ qua, nhưng nó vẫn giữ nguyên như trên:

matrix = [
[0, 1, 2, 3, 4],
[5, 6, 7, 8, 9],
[10, 11, 12, 13, 14],
[15, 16, 17, 18, 19],
[20, 21, 22, 23, 24]
]
print_matrix(matrix)
print '--------------------'
rotate(matrix)
print_matrix(matrix)

Đầu ra là:

[20,  1,  2,  3,  0]
[ 5, 16,  7,  6,  9]
[10, 11, 12, 13, 14]
[15, 18, 17,  8, 19]
[24, 21, 22, 23,  4]

Không có gì đáng ngạc nhiên khi các góc của lớp ngoài cùng đã được xoay, nhưng, bạn cũng có thể nhận thấy các góc của lớp tiếp theo (bên trong) cũng đã được xoay. Điều này thật ý nghĩa. Chúng tôi đã viết mã để điều hướng qua các lớp và cũng để xoay các góc của mỗi lớp. Điều này cảm thấy như tiến bộ, nhưng thật không may, chúng ta phải lùi lại một bước. Nó chỉ không tốt khi di chuyển lên lớp tiếp theo cho đến khi lớp trước (bên ngoài) đã được xoay hoàn toàn. Đó là, cho đến khi từng phần tử trong lớp đã được xoay. Chỉ xoay các góc sẽ không làm!

Hít một hơi thật sâu. Chúng ta cần một vòng lặp khác. Một vòng lặp lồng nhau không kém. Vòng lặp mới, lồng nhau, sẽ sử dụng các biến firstlastcộng với một phần bù để điều hướng trong một lớp. Chúng tôi sẽ gọi vòng lặp mới này là 'vòng lặp phần tử'. Vòng lặp phần tử sẽ truy cập từng phần tử dọc theo hàng trên cùng, mỗi phần tử ở phía bên phải, mỗi phần tử dọc theo hàng dưới cùng và mỗi phần tử ở phía bên trái.

  • Di chuyển về phía trước dọc theo hàng trên cùng yêu cầu tăng chỉ số cột.
  • Di chuyển xuống phía bên phải yêu cầu tăng chỉ số hàng.
  • Di chuyển ngược dọc theo đáy đòi hỏi chỉ số cột phải được giảm.
  • Di chuyển lên phía bên trái yêu cầu chỉ số hàng được giảm.

Điều này nghe có vẻ phức tạp, nhưng nó trở nên dễ dàng vì số lần chúng ta tăng và giảm để đạt được những điều trên vẫn giống nhau dọc theo cả bốn phía của ma trận. Ví dụ:

  • Di chuyển 1 phần tử trên hàng trên cùng.
  • Di chuyển 1 phần tử xuống phía bên phải.
  • Di chuyển 1 phần tử về phía sau dọc theo hàng dưới cùng.
  • Di chuyển 1 phần tử lên phía bên trái.

Điều này có nghĩa là chúng ta có thể sử dụng một biến duy nhất kết hợp với firstlastcác biến để di chuyển trong một lớp. Có thể giúp lưu ý rằng việc di chuyển qua hàng trên cùng và xuống phía bên phải đều yêu cầu tăng dần. Trong khi di chuyển ngược dọc theo phía dưới và lên phía bên trái, cả hai đều yêu cầu giảm dần.

def rotate(matrix):
    size = len(matrix)
    layer_count = size / 2

    # Move through layers (i.e. layer loop).
    for layer in range(0, layer_count):

            first = layer
            last = size - first - 1

            # Move within a single layer (i.e. element loop).
            for element in range(first, last):

                offset = element - first

                # 'element' increments column (across right)
                top_element = (first, element)
                # 'element' increments row (move down)
                right_side = (element, last)
                # 'last-offset' decrements column (across left)
                bottom = (last, last-offset)
                # 'last-offset' decrements row (move up)
                left_side = (last-offset, first)

                print 'top: %s' % (top)
                print 'right_side: %s' % (right_side)
                print 'bottom: %s' % (bottom)
                print 'left_side: %s' % (left_side)

Bây giờ chúng ta chỉ cần gán đỉnh trên cùng bên phải, bên phải xuống dưới, dưới cùng bên trái và bên trái lên trên cùng. Đặt tất cả những thứ này lại với nhau, chúng ta có được:

def rotate(matrix):
    size = len(matrix)
    layer_count = size / 2

    for layer in range(0, layer_count):
        first = layer
        last = size - first - 1

        for element in range(first, last):
            offset = element - first

            top = matrix[first][element]
            right_side = matrix[element][last]
            bottom = matrix[last][last-offset]
            left_side = matrix[last-offset][first]

            matrix[first][element] = left_side
            matrix[element][last] = top
            matrix[last][last-offset] = right_side
            matrix[last-offset][first] = bottom

Cho ma trận:

0,  1,  2  
3,  4,  5  
6,  7,  8 

rotateKết quả chức năng của chúng tôi trong:

6,  3,  0  
7,  4,  1  
8,  5,  2  

Ban đầu tôi cảm thấy như "wow, lời giải thích tốt nhất từng có", nhưng sau khi đọc nó một vài lần (để chắc chắn rằng tôi đã không bỏ lỡ bất cứ điều gì quan trọng trong biển từ), ý kiến ​​của tôi đã thay đổi thành "người đàn ông, tôi hiểu rồi, có thể chúng ta giữ cho nó di chuyển chứ? " Vẫn được khuyến khích để lấy những gì phải có hàng giờ để soạn một câu trả lời công phu như vậy.
Abhijit Sarkar

1
@AbhijitSarkar - Cảm ơn bạn đã bỏ phiếu và tôi hy vọng nó ít nhất sẽ giúp ích trong một số cách nhỏ. Tất nhiên, bạn nói đúng, câu trả lời của tôi là dài dòng. Tuy nhiên, điều này là cố ý trái ngược với phần lớn các câu trả lời. Như tôi đã nói ngay từ đầu câu trả lời của mình: "Trong câu trả lời này, các khái niệm chính được lặp lại, tốc độ chậm và cố ý lặp đi lặp lại." Nếu bạn có các chỉnh sửa giữ sự rõ ràng và lặp lại cần thiết nhưng giảm số lượng từ, tôi rất cởi mở với các đề xuất. Hoặc chỉ cần chỉnh sửa :)
Jack

@jack Giải thích thực sự tốt. Tuy nhiên, tôi không thể hiểu được, làm thế nào bạn tìm ra offset = phần tử - đầu tiên và cuối cùng = kích thước - đầu tiên - 1? Có khó khăn để hiểu điều này? Ngoài ra, là bù cuối cùng giống như bù?
ashishjmeshram

1
TL; DR:list(zip(*reversed(your_list_of_lists)))
Boris

127

Con trăn

rotated = list(zip(*original[::-1]))

và ngược chiều kim đồng hồ:

rotated_ccw = list(zip(*original))[::-1]

Cách thức hoạt động:

zip(*original)sẽ hoán đổi trục của mảng 2d bằng cách xếp các mục tương ứng từ danh sách vào danh sách mới. ( *Toán tử báo cho hàm phân phối các danh sách chứa thành các đối số)

>>> list(zip(*[[1,2,3],[4,5,6],[7,8,9]]))
[[1,4,7],[2,5,8],[3,6,9]]

Câu [::-1]lệnh đảo ngược các phần tử mảng (vui lòng xem phần Mở rộng hoặc câu hỏi này ):

>>> [[1,2,3],[4,5,6],[7,8,9]][::-1]
[[7,8,9],[4,5,6],[1,2,3]]

Cuối cùng, kết hợp cả hai sẽ dẫn đến chuyển đổi xoay.

Sự thay đổi vị trí của [::-1] sẽ đảo ngược danh sách theo các cấp độ khác nhau của ma trận.


3
Tôi tin rằng mã này bắt nguồn từ Peter Norvig: norvig.com/python-iaq.html
Josip

Bạn có thể sử dụng zip(*reversed(original))thay vì zip(*original[::-1])để tránh tạo thêm một bản sao của danh sách gốc.
Boris

70

Đây là một trong đó thực hiện xoay vòng tại chỗ thay vì sử dụng một mảng hoàn toàn mới để giữ kết quả. Tôi đã bỏ việc khởi tạo mảng và in nó ra. Điều này chỉ hoạt động cho các mảng vuông nhưng chúng có thể có kích thước bất kỳ. Chi phí bộ nhớ bằng với kích thước của một phần tử của mảng để bạn có thể thực hiện xoay vòng một mảng lớn như bạn muốn.

int a[4][4];
int n = 4;
int tmp;
for (int i = 0; i < n / 2; i++)
{
    for (int j = i; j < n - i - 1; j++)
    {
        tmp             = a[i][j];
        a[i][j]         = a[j][n-i-1];
        a[j][n-i-1]     = a[n-i-1][n-j-1];
        a[n-i-1][n-j-1] = a[n-j-1][i];
        a[n-j-1][i]     = tmp;
    }
}

Tôi có thể thấy ít nhất một lỗi. Nếu bạn định đăng mã, hãy kiểm tra hoặc ít nhất là bạn chưa làm như vậy.
Hugh Allen

1
Ở đâu? Chỉ ra và tôi sẽ sửa nó. Tôi đã kiểm tra nó và nó hoạt động tốt trên cả mảng lẻ và kích thước thậm chí.
dagorym

2
đó là một giải pháp đẹp Tâm trí có thể thực hiện những chiến công như vậy nếu được đặt thành mục đích. từ O (n2) đến O (1)
MoveFast

2
Đó không phải là O (1); vẫn là O (n ^ 2)
duma

11
Nó O (n ^ 2) với bộ nhớ O (1).
Neel

38

Có rất nhiều mã tốt ở đây nhưng tôi chỉ muốn hiển thị những gì đang diễn ra về mặt hình học để bạn có thể hiểu logic mã tốt hơn một chút. Đây là cách tôi sẽ tiếp cận điều này.

trước hết, đừng nhầm lẫn điều này với chuyển vị rất dễ dàng ..

ý tưởng cơ bản là coi nó như các lớp và chúng ta xoay một lớp một lần ..

nói rằng chúng tôi có một chiếc 4x4

1   2   3   4
5   6   7   8
9   10  11  12
13  14  15  16

sau khi chúng ta xoay nó theo chiều kim đồng hồ 90 thì chúng ta nhận được

13  9   5   1
14  10  6   2   
15  11  7   3
16  12  8   4

Vì vậy, hãy phân tách cái này, đầu tiên chúng ta xoay 4 góc về cơ bản

1           4


13          16

sau đó chúng ta xoay viên kim cương sau đây là loại kim cương

    2
            8
9       
        15

và sau đó là viên kim cương xiên thứ 2

        3
5           
            12
    14

Vì vậy, nó chăm sóc các cạnh bên ngoài vì vậy về cơ bản chúng tôi thực hiện một lớp vỏ đó cho đến khi

cuối cùng là hình vuông ở giữa (hoặc nếu nó lẻ chỉ là phần tử cuối cùng không di chuyển)

6   7
10  11

Vì vậy, bây giờ hãy tìm ra các chỉ số của mỗi lớp, giả sử chúng ta luôn làm việc với lớp ngoài cùng, chúng ta đang làm

[0,0] -> [0,n-1], [0,n-1] -> [n-1,n-1], [n-1,n-1] -> [n-1,0], and [n-1,0] -> [0,0]
[0,1] -> [1,n-1], [1,n-2] -> [n-1,n-2], [n-1,n-2] -> [n-2,0], and [n-2,0] -> [0,1]
[0,2] -> [2,n-2], [2,n-2] -> [n-1,n-3], [n-1,n-3] -> [n-3,0], and [n-3,0] -> [0,2]

cứ như vậy cho đến khi chúng ta đi được nửa đường

vì vậy nói chung mô hình là

[0,i] -> [i,n-i], [i,n-i] -> [n-1,n-(i+1)], [n-1,n-(i+1)] -> [n-(i+1),0], and [n-(i+1),0] to [0,i]

"Nửa chừng rìa" nghĩa là gì? Tôi thấy rất nhiều thuật toán lặp cho đến khi N / 2 và các thuật toán khác lặp đến N, nhưng tôi không thể thấy N / 2 đến từ đâu.
PDN

Tôi tin rằng đó là giải pháp tương tự như được đưa ra trong việc bẻ khóa phỏng vấn mã hóa. Nhưng tôi thích giải thích từng bước. Rất đẹp và kỹ lưỡng.
Naphstor

@PDN Câu trả lời này giải thích chi tiết.
Mathias Bynens

35

Như tôi đã nói trong bài viết trước của tôi, đây là một số mã trong C # thực hiện xoay vòng ma trận O (1) cho bất kỳ ma trận kích thước nào. Để đơn giản và dễ đọc, không có kiểm tra lỗi hoặc kiểm tra phạm vi. Mật mã:

static void Main (string [] args)
{
  int [,]
    //  create an arbitrary matrix
    m = {{0, 1}, {2, 3}, {4, 5}};

  Matrix
    //  create wrappers for the data
    m1 = new Matrix (m),
    m2 = new Matrix (m),
    m3 = new Matrix (m);

  //  rotate the matricies in various ways - all are O(1)
  m1.RotateClockwise90 ();
  m2.Rotate180 ();
  m3.RotateAnitclockwise90 ();

  //  output the result of transforms
  System.Diagnostics.Trace.WriteLine (m1.ToString ());
  System.Diagnostics.Trace.WriteLine (m2.ToString ());
  System.Diagnostics.Trace.WriteLine (m3.ToString ());
}

class Matrix
{
  enum Rotation
  {
    None,
    Clockwise90,
    Clockwise180,
    Clockwise270
  }

  public Matrix (int [,] matrix)
  {
    m_matrix = matrix;
    m_rotation = Rotation.None;
  }

  //  the transformation routines
  public void RotateClockwise90 ()
  {
    m_rotation = (Rotation) (((int) m_rotation + 1) & 3);
  }

  public void Rotate180 ()
  {
    m_rotation = (Rotation) (((int) m_rotation + 2) & 3);
  }

  public void RotateAnitclockwise90 ()
  {
    m_rotation = (Rotation) (((int) m_rotation + 3) & 3);
  }

  //  accessor property to make class look like a two dimensional array
  public int this [int row, int column]
  {
    get
    {
      int
        value = 0;

      switch (m_rotation)
      {
      case Rotation.None:
        value = m_matrix [row, column];
        break;

      case Rotation.Clockwise90:
        value = m_matrix [m_matrix.GetUpperBound (0) - column, row];
        break;

      case Rotation.Clockwise180:
        value = m_matrix [m_matrix.GetUpperBound (0) - row, m_matrix.GetUpperBound (1) - column];
        break;

      case Rotation.Clockwise270:
        value = m_matrix [column, m_matrix.GetUpperBound (1) - row];
        break;
      }

      return value;
    }

    set
    {
      switch (m_rotation)
      {
      case Rotation.None:
        m_matrix [row, column] = value;
        break;

      case Rotation.Clockwise90:
        m_matrix [m_matrix.GetUpperBound (0) - column, row] = value;
        break;

      case Rotation.Clockwise180:
        m_matrix [m_matrix.GetUpperBound (0) - row, m_matrix.GetUpperBound (1) - column] = value;
        break;

      case Rotation.Clockwise270:
        m_matrix [column, m_matrix.GetUpperBound (1) - row] = value;
        break;
      }
    }
  }

  //  creates a string with the matrix values
  public override string ToString ()
  {
    int
      num_rows = 0,
      num_columns = 0;

    switch (m_rotation)
    {
    case Rotation.None:
    case Rotation.Clockwise180:
      num_rows = m_matrix.GetUpperBound (0);
      num_columns = m_matrix.GetUpperBound (1);
      break;

    case Rotation.Clockwise90:
    case Rotation.Clockwise270:
      num_rows = m_matrix.GetUpperBound (1);
      num_columns = m_matrix.GetUpperBound (0);
      break;
    }

    StringBuilder
      output = new StringBuilder ();

    output.Append ("{");

    for (int row = 0 ; row <= num_rows ; ++row)
    {
      if (row != 0)
      {
        output.Append (", ");
      }

      output.Append ("{");

      for (int column = 0 ; column <= num_columns ; ++column)
      {
        if (column != 0)
        {
          output.Append (", ");
        }

        output.Append (this [row, column].ToString ());
      }

      output.Append ("}");
    }

    output.Append ("}");

    return output.ToString ();
  }

  int [,]
    //  the original matrix
    m_matrix;

  Rotation
    //  the current view of the matrix
    m_rotation;
}

OK, tôi sẽ giơ tay lên, nó không thực sự sửa đổi gì cho mảng ban đầu khi xoay. Nhưng, trong một hệ thống OO không quan trọng miễn là đối tượng trông giống như nó được quay sang các máy khách của lớp. Hiện tại, lớp Matrix sử dụng các tham chiếu đến dữ liệu mảng ban đầu nên việc thay đổi bất kỳ giá trị nào của m1 cũng sẽ thay đổi m2 và m3. Một thay đổi nhỏ cho hàm tạo để tạo một mảng mới và sao chép các giá trị vào nó sẽ sắp xếp nó ra.


4
Bravo! Đây là một giải pháp rất hay và tôi không biết tại sao nó không phải là câu trả lời được chấp nhận.
martinatime

@martinatime: có lẽ vì nó lớn gấp 5 lần
cóc

@Toad: Chà, viết mã luôn là sự đánh đổi giữa các yêu cầu cạnh tranh: tốc độ, kích thước, chi phí, v.v.
Skizz

15
đúng ... một vấn đề khác là thực tế là ma trận trên thực tế không được quay, nhưng được xoay "đúng lúc". Điều này thật tuyệt khi truy cập một vài yếu tố, nhưng sẽ thật kinh khủng nếu ma trận này được sử dụng trong các tính toán hoặc thao tác hình ảnh. Vì vậy, nói O (1) là không thực sự công bằng.
cóc

23

Trong khi xoay dữ liệu tại chỗ có thể là cần thiết (có lẽ để cập nhật biểu diễn được lưu trữ vật lý), việc thêm một lớp gián tiếp vào truy cập mảng có thể trở nên đơn giản hơn và có thể hiệu quả hơn:

interface IReadableMatrix
{
    int GetValue(int x, int y);
}

Nếu bạn Matrixđã thực hiện giao diện này, thì nó có thể được xoay qua một lớp trang trí như thế này:

class RotatedMatrix : IReadableMatrix
{
    private readonly IReadableMatrix _baseMatrix;

    public RotatedMatrix(IReadableMatrix baseMatrix)
    {
        _baseMatrix = baseMatrix;
    }

    int GetValue(int x, int y)
    {
        // transpose x and y dimensions
        return _baseMatrix(y, x);
    }
}

Xoay + 90 / -90 / 180 độ, lật theo chiều ngang / chiều dọc và tỷ lệ cũng có thể đạt được theo cách này.

Hiệu suất sẽ cần phải được đo lường trong kịch bản cụ thể của bạn. Tuy nhiên, thao tác O (n ^ 2) hiện đã được thay thế bằng lệnh gọi O (1). Đó là một phương pháp gọi ảo mà chậm hơn so với truy cập mảng trực tiếp, vì vậy nó phụ thuộc vào tần suất mà mảng xoay được sử dụng sau khi quay. Nếu nó được sử dụng một lần, thì phương pháp này chắc chắn sẽ thắng. Nếu nó được quay sau đó được sử dụng trong một hệ thống chạy dài trong nhiều ngày, thì vòng quay tại chỗ có thể hoạt động tốt hơn. Nó cũng phụ thuộc vào việc bạn có thể chấp nhận chi phí trả trước hay không.

Như với tất cả các vấn đề hiệu suất, đo lường, đo lường, đo lường!


1
+1 ... Và nếu ma trận thực sự lớn và bạn chỉ truy cập được một vài yếu tố (sử dụng thưa thớt) thì nó thậm chí còn hiệu quả hơn
lothar

16
Có vẻ hơi không công bằng khi gọi đây là giải pháp thời gian O (1). Để giải quyết vấn đề do OP đặt ra, việc này vẫn sẽ mất thời gian O (n ^ 2). Không chỉ vậy, nó sẽ không giải quyết được vấn đề vì nó trả về chuyển vị . Ví dụ đưa ra không có chuyển vị là giải pháp.
Vlad the Impala

5
Bây giờ, nếu tất cả những gì bạn muốn là 3 phần tử đầu tiên của ma trận, thì đây là một giải pháp tốt, nhưng vấn đề là lấy một ma trận biến đổi hoàn toàn (tức là giả sử bạn cần tất cả các phần tử ma trận). Gọi đây là O (1) là phương pháp Hoán đổi mặc định tín dụng của Phân tích thuật toán - bạn chưa giải quyết được vấn đề, bạn vừa đẩy nó cho người khác :)
Ana Betts

4
@Paul Betts: Tôi nhận được quan điểm của bạn, nhưng giống như tôi đã viết ở trên trong các bình luận, ngay cả khi bạn thực sự có ma trận chuyển đổi, bạn vẫn phải viết vòng lặp nếu bạn muốn đọc các giá trị. Vì vậy, đọc tất cả các giá trị từ một ma trận luôn là O (N ^ 2) bất kể. Sự khác biệt ở đây là nếu bạn hoán chuyển, xoay, chia tỷ lệ, chia tỷ lệ lại, v.v., thì bạn vẫn chỉ thực hiện cú đánh O (N ^ 2) một lần. Như tôi đã nói, đây không phải lúc nào cũng là giải pháp tốt nhất, nhưng trong nhiều trường hợp, nó phù hợp và đáng giá. OP dường như đang tìm kiếm một giải pháp kỳ diệu, và điều này gần như bạn sẽ nhận được.
vẽ Noakes

9
Tôi thích câu trả lời này, nhưng tôi muốn chỉ ra một cái gì đó. In ra ma trận trang trí (và thực hiện các lần đọc tuần tự khác nói chung) có thể chậm hơn nhiều so với thực hiện tương tự với ma trận được xoay trong bộ nhớ và không chỉ vì các cuộc gọi phương thức ảo. Đối với một ma trận lớn, bạn sẽ tăng đáng kể số lượng bộ nhớ cache bạn nhận được bằng cách đọc "xuống" thay vì "ngang qua".
Mike Daniels

18

Đây là phiên bản tốt hơn của nó trong Java: Tôi đã tạo nó cho một ma trận có chiều rộng và chiều cao khác nhau

  • h ở đây là chiều cao của ma trận sau khi quay
  • w ở đây là chiều rộng của ma trận sau khi quay

 

public int[][] rotateMatrixRight(int[][] matrix)
{
    /* W and H are already swapped */
    int w = matrix.length;
    int h = matrix[0].length;
    int[][] ret = new int[h][w];
    for (int i = 0; i < h; ++i) {
        for (int j = 0; j < w; ++j) {
            ret[i][j] = matrix[w - j - 1][i];
        }
    }
    return ret;
}


public int[][] rotateMatrixLeft(int[][] matrix)
{
    /* W and H are already swapped */
    int w = matrix.length;
    int h = matrix[0].length;   
    int[][] ret = new int[h][w];
    for (int i = 0; i < h; ++i) {
        for (int j = 0; j < w; ++j) {
            ret[i][j] = matrix[j][h - i - 1];
        }
    }
    return ret;
}

Mã này được dựa trên bài viết của Nick Berardi.


Cảm ơn. Đây là mã Java rõ ràng nhất ở đây. Câu hỏi - Làm thế nào bạn / Nick đến với phần [w - j - 1]? Nhìn vào câu trả lời @tweaking tôi có thể thấy làm thế nào bạn có thể rút ra điều đó thông qua các ví dụ cảm ứng / giải quyết. Chỉ tự hỏi nếu đó là cách nó đã thu được hoặc nó dựa trên một số nguyên tắc toán học liên quan đến Ma trận.
Quest Monger

17

Ruby-way: .transpose.map &:reverse


1
Nó thậm chí còn đơn giản hơn thế: array.reverse.transposexoay một mảng theo chiều kim đồng hồ, trong khi array.transpose.reversexoay nó ngược chiều kim đồng hồ. Không cần map.
Giorgi Gzirishvili

13

Đã có rất nhiều câu trả lời và tôi đã tìm thấy hai câu hỏi phức tạp về thời gian O (1). Sự thật O (1) thuật toán là để lại lưu trữ mảng bị ảnh hưởng, và thay đổi cách bạn chỉ số phần tử của nó. Mục tiêu ở đây là nó không tiêu thụ thêm bộ nhớ và cũng không cần thêm thời gian để lặp lại dữ liệu.

Các góc quay 90, -90 và 180 độ là các phép biến đổi đơn giản có thể được thực hiện miễn là bạn biết có bao nhiêu hàng và cột trong mảng 2D của mình; Để xoay bất kỳ vectơ nào 90 độ, hoán đổi trục và phủ định trục Y. Đối với -90 độ, hoán đổi các trục và phủ định trục X. Trong 180 độ, phủ định cả hai trục mà không trao đổi.

Các phép biến đổi tiếp theo là có thể, chẳng hạn như phản chiếu theo chiều ngang và / hoặc chiều dọc bằng cách phủ định các trục một cách độc lập.

Điều này có thể được thực hiện thông qua ví dụ một phương pháp truy cập. Các ví dụ dưới đây là các hàm JavaScript, nhưng các khái niệm áp dụng như nhau cho tất cả các ngôn ngữ.

 // Get an array element in column/row order
 var getArray2d = function(a, x, y) {
   return a[y][x];
 };

 //demo
 var arr = [
   [5, 4, 6],
   [1, 7, 9],
   [-2, 11, 0],
   [8, 21, -3],
   [3, -1, 2]
 ];

 var newarr = [];
 arr[0].forEach(() => newarr.push(new Array(arr.length)));

 for (var i = 0; i < newarr.length; i++) {
   for (var j = 0; j < newarr[0].length; j++) {
     newarr[i][j] = getArray2d(arr, i, j);
   }
 }
 console.log(newarr);

// Get an array element rotated 90 degrees clockwise
function getArray2dCW(a, x, y) {
  var t = x;
  x = y;
  y = a.length - t - 1;
  return a[y][x];
}

//demo
var arr = [
  [5, 4, 6],
  [1, 7, 9],
  [-2, 11, 0],
  [8, 21, -3],
  [3, -1, 2]
];

var newarr = [];
arr[0].forEach(() => newarr.push(new Array(arr.length)));

for (var i = 0; i < newarr[0].length; i++) {
  for (var j = 0; j < newarr.length; j++) {
    newarr[j][i] = getArray2dCW(arr, i, j);
  }
}
console.log(newarr);

// Get an array element rotated 90 degrees counter-clockwise
function getArray2dCCW(a, x, y) {
  var t = x;
  x = a[0].length - y - 1;
  y = t;
  return a[y][x];
}

//demo
var arr = [
  [5, 4, 6],
  [1, 7, 9],
  [-2, 11, 0],
  [8, 21, -3],
  [3, -1, 2]
];

var newarr = [];
arr[0].forEach(() => newarr.push(new Array(arr.length)));

for (var i = 0; i < newarr[0].length; i++) {
  for (var j = 0; j < newarr.length; j++) {
    newarr[j][i] = getArray2dCCW(arr, i, j);
  }
}
console.log(newarr);

// Get an array element rotated 180 degrees
function getArray2d180(a, x, y) {
  x = a[0].length - x - 1;
  y = a.length - y - 1;
  return a[y][x];
}

//demo
var arr = [
  [5, 4, 6],
  [1, 7, 9],
  [-2, 11, 0],
  [8, 21, -3],
  [3, -1, 2]
];

var newarr = [];
arr.forEach(() => newarr.push(new Array(arr[0].length)));

for (var i = 0; i < newarr[0].length; i++) {
  for (var j = 0; j < newarr.length; j++) {
    newarr[j][i] = getArray2d180(arr, i, j);
  }
}
console.log(newarr);

Mã này giả định một mảng các mảng lồng nhau, trong đó mỗi mảng bên trong là một hàng.

Phương thức cho phép bạn đọc (hoặc ghi) các phần tử (thậm chí theo thứ tự ngẫu nhiên) như thể mảng đã được xoay hoặc biến đổi. Bây giờ chỉ cần chọn đúng chức năng để gọi, có thể bằng cách tham khảo, và bạn đi!

Khái niệm này có thể được mở rộng để áp dụng các phép biến đổi một cách bổ sung (và không phá hủy) thông qua các phương thức truy cập. Bao gồm xoay góc tùy ý và chia tỷ lệ.


Không ai trong số này thực sự xoay từ mảng ban đầu. Đầu tiên, kết quả cuối cùng chỉ đơn giản là hoán vị. Cái thứ hai, bạn dường như vừa xáo trộn các hàng hoặc nhân đôi trên trung tâm ngang. Thứ ba, bạn chỉ đảo ngược các hàng và thứ tư cũng được hoán vị. Không ai trong số họ thực sự "xoay".
SM177Y

Có một số lỗi trong hai ví dụ sau. Tầm thường để sửa chữa. Tôi đã chỉ ra một cách rõ ràng rằng giải pháp này không phải là một vòng quay tại chỗ. Nó là một hàm biến đổi, làm cho nó phù hợp với phép lặp lười biếng.
Jason Oster

Ngoại trừ không có vòng quay, vì vậy bạn không thực sự trả lời những gì OP yêu cầu.
SM177Y

@ SM177Y Một biên tập viên khác đã thêm mã ví dụ không hoạt động vào câu trả lời của tôi. Tôi có thể thấy làm thế nào bạn đã nhầm lẫn bởi nó. Tôi đã sửa các lỗi trong các vòng lặp. Các chức năng như được cung cấp trên thực tế "xoay" dữ liệu trong các mảng.
Jason Oster

Một chi tiết quan trọng nữa là mã ví dụ thực sự rửa sạch câu trả lời ban đầu mà tôi đã cung cấp, nó đang cố gắng minh họa sức mạnh của các phép biến đổi chức năng đối với các giải pháp phức tạp theo không gian thời gian tuyến tính. Với một phép biến đổi chức năng, bạn đã lặp lại hoặc truy cập các phần tử mảng , do đó, phép biến đổi được coi là "miễn phí" theo nghĩa phức tạp không gian và thời gian.
Jason Oster

10

Một vài người đã đưa ra các ví dụ liên quan đến việc tạo ra một mảng mới.

Một vài điều khác cần xem xét:

(a) Thay vì thực sự di chuyển dữ liệu, chỉ cần di chuyển ngang qua mảng "xoay" khác nhau.

(b) Thực hiện xoay tại chỗ có thể phức tạp hơn một chút. Bạn sẽ cần một chút vị trí đầu (có thể gần bằng một hàng hoặc cột có kích thước). Có một bài báo ACM cổ về việc thực hiện chuyển đổi tại chỗ ( http://doi.acm.org/10.1145/355719.355729 ), nhưng mã ví dụ của họ là FORTRAN khó chịu.

Phụ lục:

http://doi.acm.org/10.1145/355611.355612 là một thuật toán chuyển vị tại chỗ khác, được cho là vượt trội.


Tôi đồng ý với điều này. Có một phương pháp xác định bản dịch giữa dữ liệu nguồn và dữ liệu "xoay".
martinatime

8

Câu trả lời của Nick cũng sẽ hoạt động cho một mảng NxM chỉ với một sửa đổi nhỏ (trái ngược với NxN).

string[,] orig = new string[n, m];
string[,] rot = new string[m, n];

...

for ( int i=0; i < n; i++ )
  for ( int j=0; j < m; j++ )
    rot[j, n - i - 1] = orig[i, j];

Một cách để nghĩ về điều này là bạn đã di chuyển tâm của trục (0,0) từ góc trên cùng bên trái sang góc trên cùng bên phải. Bạn chỉ đơn giản là chuyển từ người này sang người khác.


6

Thời gian - O (N), Không gian - O (1)

public void rotate(int[][] matrix) {
    int n = matrix.length;
    for (int i = 0; i < n / 2; i++) {
        int last = n - 1 - i;
        for (int j = i; j < last; j++) {
            int top = matrix[i][j];
            matrix[i][j] = matrix[last - j][i];
            matrix[last - j][i] = matrix[last][last - j];
            matrix[last][last - j] = matrix[j][last];
            matrix[j][last] = top;
        }
    }
}

Đây không phải là O (1). Đây là O (n).
Jason Oster

@JasonOster Tôi tin rằng đây là không gian O (1), vì nó không tiêu tốn thêm dung lượng.
ffledgling

@ffledgling Sai lầm của tôi. O (1) không gian phức tạp, có. Độ phức tạp thời gian O (n).
Jason Oster

Độ phức tạp không gian là O (n) là tốt. Độ phức tạp không gian nên bao gồm không gian của kích thước biến đầu vào. careercup.com/question?id=14952322
Jason Heo

Làm thế nào tôi có thể sửa đổi điều này để làm việc cho một vòng quay ngược chiều kim đồng hồ?
MD XF

5

Đây là phiên bản Ruby của tôi (lưu ý các giá trị không được hiển thị giống nhau, nhưng nó vẫn xoay như mô tả).

def rotate(matrix)
  result = []
  4.times { |x|
    result[x] = []
    4.times { |y|
      result[x][y] = matrix[y][3 - x]
    }
  }

  result
end

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

def print_matrix(matrix)
  4.times { |y|
    4.times { |x|
      print "#{matrix[x][y]} "
    }
    puts ""
  }
end

print_matrix(matrix)
puts ""
print_matrix(rotate(matrix))

Đầu ra:

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

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

4

đây là một phương thức xoay trong không gian, bằng java, chỉ dành cho hình vuông. đối với mảng 2d không vuông, dù sao bạn cũng sẽ phải tạo mảng mới.

private void rotateInSpace(int[][] arr) {
    int z = arr.length;
    for (int i = 0; i < z / 2; i++) {
        for (int j = 0; j < (z / 2 + z % 2); j++) {
            int x = i, y = j;
            int temp = arr[x][y];
            for (int k = 0; k < 4; k++) {
                int temptemp = arr[y][z - x - 1];
                arr[y][z - x - 1] = temp;
                temp = temptemp;

                int tempX = y;
                y = z - x - 1;
                x = tempX;
            }
        }
    }
}

mã để xoay bất kỳ mảng 2d kích thước nào bằng cách tạo mảng mới:

private int[][] rotate(int[][] arr) {
    int width = arr[0].length;
    int depth = arr.length;
    int[][] re = new int[width][depth];
    for (int i = 0; i < depth; i++) {
        for (int j = 0; j < width; j++) {
            re[j][depth - i - 1] = arr[i][j];
        }
    }
    return re;
}

3

Triển khai mã giả +90 của lúm đồng tiền (ví dụ: hoán vị rồi đảo ngược từng hàng) trong JavaScript:

function rotate90(a){
  // transpose from http://www.codesuck.com/2012/02/transpose-javascript-array-in-one-line.html
  a = Object.keys(a[0]).map(function (c) { return a.map(function (r) { return r[c]; }); });
  // row reverse
  for (i in a){
    a[i] = a[i].reverse();
  }
  return a;
}

3

Bạn có thể làm điều này trong 3 bước đơn giản :

1 ) Giả sử chúng ta có một ma trận

   1 2 3
   4 5 6
   7 8 9

2 ) Lấy chuyển vị của ma trận

   1 4 7
   2 5 8
   3 6 9

3 ) Các hàng trao đổi để có được ma trận xoay

   3 6 9
   2 5 8
   1 4 7

Mã nguồn Java cho việc này:

public class MyClass {

    public static void main(String args[]) {
        Demo obj = new Demo();
        /*initial matrix to rotate*/
        int[][] matrix = { { 1, 2, 3 }, { 4, 5, 6 }, { 7, 8, 9 } };
        int[][] transpose = new int[3][3]; // matrix to store transpose

        obj.display(matrix);              // initial matrix

        obj.rotate(matrix, transpose);    // call rotate method
        System.out.println();
        obj.display(transpose);           // display the rotated matix
    }
}

class Demo {   
    public void rotate(int[][] mat, int[][] tran) {

        /* First take the transpose of the matrix */
        for (int i = 0; i < mat.length; i++) {
            for (int j = 0; j < mat.length; j++) {
                tran[i][j] = mat[j][i]; 
            }
        }

        /*
         * Interchange the rows of the transpose matrix to get rotated
         * matrix
         */
        for (int i = 0, j = tran.length - 1; i != j; i++, j--) {
            for (int k = 0; k < tran.length; k++) {
                swap(i, k, j, k, tran);
            }
        }
    }

    public void swap(int a, int b, int c, int d, int[][] arr) {
        int temp = arr[a][b];
        arr[a][b] = arr[c][d];
        arr[c][d] = temp;    
    }

    /* Method to display the matrix */
    public void display(int[][] arr) {
        for (int i = 0; i < arr.length; i++) {
            for (int j = 0; j < arr.length; j++) {
                System.out.print(arr[i][j] + " ");
            }
            System.out.println();
        }
    }
}

Đầu ra:

1 2 3 
4 5 6 
7 8 9 

3 6 9 
2 5 8 
1 4 7 

2

Đây là triển khai của tôi, trong độ phức tạp của bộ nhớ C, O (1), xoay theo vị trí, 90 độ theo chiều kim đồng hồ:

#include <stdio.h>

#define M_SIZE 5

static void initMatrix();
static void printMatrix();
static void rotateMatrix();

static int m[M_SIZE][M_SIZE];

int main(void){
    initMatrix();
    printMatrix();
    rotateMatrix();
    printMatrix();

    return 0;
}

static void initMatrix(){
    int i, j;

    for(i = 0; i < M_SIZE; i++){
        for(j = 0; j < M_SIZE; j++){
            m[i][j] = M_SIZE*i + j + 1;
        }
    }
}

static void printMatrix(){
    int i, j;

    printf("Matrix\n");
    for(i = 0; i < M_SIZE; i++){
        for(j = 0; j < M_SIZE; j++){
            printf("%02d ", m[i][j]);
        }
        printf("\n");
    }
    printf("\n");
}

static void rotateMatrix(){
    int r, c;

    for(r = 0; r < M_SIZE/2; r++){
        for(c = r; c < M_SIZE - r - 1; c++){
            int tmp = m[r][c];

            m[r][c] = m[M_SIZE - c - 1][r];
            m[M_SIZE - c - 1][r] = m[M_SIZE - r - 1][M_SIZE - c - 1];
            m[M_SIZE - r - 1][M_SIZE - c - 1] = m[c][M_SIZE - r - 1];
            m[c][M_SIZE - r - 1] = tmp;
        }
    }
}

2

Đây là phiên bản Java:

public static void rightRotate(int[][] matrix, int n) {
    for (int layer = 0; layer < n / 2; layer++) {
        int first = layer;
        int last = n - 1 - first;
        for (int i = first; i < last; i++) {
           int offset = i - first;
           int temp = matrix[first][i];
           matrix[first][i] = matrix[last-offset][first];
           matrix[last-offset][first] = matrix[last][last-offset];
           matrix[last][last-offset] = matrix[i][last];
           matrix[i][last] = temp;
        }
    }
}

phương pháp đầu tiên xoay lớp nhất đồ, sau đó di chuyển đến lớp bên trong theo phương vuông góc.


2

Từ quan điểm tuyến tính, hãy xem xét các ma trận:

    1 2 3        0 0 1
A = 4 5 6    B = 0 1 0
    7 8 9        1 0 0

Bây giờ hãy chuyển

     1 4 7
A' = 2 5 8
     3 6 9

Và xem xét hành động của A 'trên B hoặc B trên A'.
Tương ứng:

      7 4 1          3 6 9
A'B = 8 5 2    BA' = 2 5 8
      9 6 3          1 4 7

Điều này có thể mở rộng cho bất kỳ ma trận nxn. Và áp dụng khái niệm này một cách nhanh chóng trong mã:

void swapInSpace(int** mat, int r1, int c1, int r2, int c2)
{
    mat[r1][c1] ^= mat[r2][c2];
    mat[r2][c2] ^= mat[r1][c1];
    mat[r1][c1] ^= mat[r2][c2];
}

void transpose(int** mat, int size)
{
    for (int i = 0; i < size; i++)
    {
        for (int j = (i + 1); j < size; j++)
        {
            swapInSpace(mat, i, j, j, i);
        }
    }
}

void rotate(int** mat, int size)
{
    //Get transpose
    transpose(mat, size);

    //Swap columns
    for (int i = 0; i < size / 2; i++)
    {
        for (int j = 0; j < size; j++)
        {
            swapInSpace(mat, i, j, size - (i + 1), j);
        }
    }
}

2

Mã C # để xoay [n, m] mảng 2D 90 độ phải

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace MatrixProject
{
    // mattrix class

    class Matrix{
        private int rows;
        private int cols;
        private int[,] matrix;

        public Matrix(int n){
            this.rows = n;
            this.cols = n;
            this.matrix = new int[this.rows,this.cols];

        }

        public Matrix(int n,int m){
            this.rows = n;
            this.cols = m;

            this.matrix = new int[this.rows,this.cols];
        }

        public void Show()
        {
            for (var i = 0; i < this.rows; i++)
            {
                for (var j = 0; j < this.cols; j++) {
                    Console.Write("{0,3}", this.matrix[i, j]);
                }
                Console.WriteLine();
            }                
        }

        public void ReadElements()
        {
           for (var i = 0; i < this.rows; i++)
                for (var j = 0; j < this.cols; j++)
                {
                    Console.Write("element[{0},{1}]=",i,j);
                    this.matrix[i, j] = Convert.ToInt32(Console.ReadLine());
                }            
        }


        // rotate [n,m] 2D array by 90 deg right
        public void Rotate90DegRight()
        {

            // create a mirror of current matrix
            int[,] mirror = this.matrix;

            // create a new matrix
            this.matrix = new int[this.cols, this.rows];

            for (int i = 0; i < this.rows; i++)
            {
                for (int j = 0; j < this.cols; j++)
                {
                    this.matrix[j, this.rows - i - 1] = mirror[i, j];
                }
            }

            // replace cols count with rows count
            int tmp = this.rows;
            this.rows = this.cols;
            this.cols = tmp;           
        }
    }

    class Program
    {
        static void Main(string[] args)
        {
            Matrix myMatrix = new Matrix(3,4);
            Console.WriteLine("Enter matrix elements:");
            myMatrix.ReadElements();
            Console.WriteLine("Matrix elements are:");
            myMatrix.Show();
            myMatrix.Rotate90DegRight();
            Console.WriteLine("Matrix rotated at 90 deg are:");
            myMatrix.Show();
            Console.ReadLine();
        }
    }
}

Kết quả:

    Enter matrix elements:
    element[0,0]=1
    element[0,1]=2
    element[0,2]=3
    element[0,3]=4
    element[1,0]=5
    element[1,1]=6
    element[1,2]=7
    element[1,3]=8
    element[2,0]=9
    element[2,1]=10
    element[2,2]=11
    element[2,3]=12
    Matrix elements are:
      1  2  3  4
      5  6  7  8
      9 10 11 12
    Matrix rotated at 90 deg are:
      9  5  1
     10  6  2
     11  7  3
     12  8  4

2

PHP:

<?php    
$a = array(array(1,2,3,4),array(5,6,7,8),array(9,0,1,2),array(3,4,5,6));
$b = array(); //result

while(count($a)>0)
{
    $b[count($a[0])-1][] = array_shift($a[0]);
    if (count($a[0])==0)
    {
         array_shift($a);
    }
}

Từ PHP5.6, chuyển vị mảng có thể được thực hiện với một array_map()cuộc gọi chậm . Nói cách khác, các cột được chuyển đổi thành hàng.

Mã: ( Bản trình diễn )

$array = [
    [1, 2, 3, 4],
    [5, 6, 7, 8],
    [9, 0, 1, 2],
    [3, 4, 5, 6]
];
$transposed = array_map(null, ...$array);

$ chuyển

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

1

For i:= 0 to X do For j := 0 to X do graphic[j][i] := graphic2[X-i][j]

X là kích thước của mảng mà đồ họa nằm trong.


1

#transpose là một phương thức chuẩn của lớp Ruby's Array, do đó:

% irb
irb(main):001:0> m = [[1, 2, 3, 4], [5, 6, 7, 8], [9, 0, 1, 2], [3, 4, 5, 6]]
=> [[1, 2, 3, 4], [5, 6, 7, 8], [9, 0, 1, 2], [3, 4, 5, 6]] 
irb(main):002:0> m.reverse.transpose
=> [[3, 9, 5, 1], [4, 0, 6, 2], [5, 1, 7, 3], [6, 2, 8, 4]]

Việc triển khai là một hàm chuyển vị n ^ 2 được viết bằng C. Bạn có thể thấy nó ở đây: http://www.ruby-doc.org/core-1.9.3/Array.html#method-i-transpose bằng cách chọn "nhấp chuyển đổi nguồn "bên cạnh" chuyển vị ".

Tôi nhớ lại tốt hơn các giải pháp O (n ^ 2), nhưng chỉ đối với các ma trận được xây dựng đặc biệt (như ma trận thưa thớt)


1

Mã C để xoay ma trận 90 độ theo chiều kim đồng hồ IN PLACE cho bất kỳ ma trận M * N nào

void rotateInPlace(int * arr[size][size], int row, int column){
    int i, j;
    int temp = row>column?row:column;
    int flipTill = row < column ? row : column;
    for(i=0;i<flipTill;i++){
        for(j=0;j<i;j++){
            swapArrayElements(arr, i, j);
        }
    }

    temp = j+1;

    for(i = row>column?i:0; i<row; i++){
            for(j=row<column?temp:0; j<column; j++){
                swapArrayElements(arr, i, j);
            }
    }

    for(i=0;i<column;i++){
        for(j=0;j<row/2;j++){
            temp = arr[i][j];
            arr[i][j] = arr[i][row-j-1];
            arr[i][row-j-1] = temp;
        }
    }
}

1

đây là triển khai tại chỗ của tôi ở 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

Đây là nỗ lực của tôi cho ma trận xoay 90 độ, là một giải pháp 2 bước trong C. Đầu tiên hoán đổi ma trận tại chỗ và sau đó trao đổi các cols.

#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

@dagorym: Aw, anh bạn. Tôi đã bị treo vào đây như một câu đố "Tôi chán, tôi có thể suy ngẫm". Tôi đã đưa ra mã chuyển vị tại chỗ của mình, nhưng đã đến đây để tìm mã của bạn khá giống với mã của tôi ... à, tốt. Đây là 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
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];
  }
}

Phương pháp C ++ đơn giản, tho sẽ có một bộ nhớ lớn trên một mảng lớn.


Trong số tất cả những câu trả lời tôi đã tìm thấy và kiểm tra câu trả lời này nhỏ gọn và đủ để xoay
dlewin
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.