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/2
phù 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 first
và last
xá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 first
và last
vì 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 first
và last
(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 first
và last
:
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 first
và last
cộ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 first
và last
cá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
rotate
Kết quả chức năng của chúng tôi trong:
6, 3, 0
7, 4, 1
8, 5, 2