Я хотів би додати трохи детальніше. У цій відповіді ключові поняття повторюються, темп повільний і навмисно повторюваний. Надане тут рішення не є найбільш синтаксично компактним, проте воно призначене для тих, хто бажає дізнатися, що таке обертання матриці та що реалізується в результаті.
По-перше, що таке матриця? Для цілей цієї відповіді матриця - це просто сітка, де ширина і висота однакові. Зауважимо, ширина та висота матриці можуть бути різними, але для простоти в цьому підручнику розглядаються лише матриці з однаковою шириною та висотою ( квадратні матриці ). І так, матриці - це множина матриці.
Прикладними матрицями є: 2 × 2, 3 × 3 або 5 × 5. Або, загальніше, N × N. Матриця 2 × 2 матиме 4 квадрати, оскільки 2 × 2 = 4. Матриця 5 × 5 матиме 25 квадратів, оскільки 5 × 5 = 25. Кожен квадрат називається елементом або записом. Ми зобразимо кожен елемент із періодом ( .
) на діаграмах нижче:
2 × 2 матриці
. .
. .
3 × 3 матриця
. . .
. . .
. . .
4 × 4 матриця
. . . .
. . . .
. . . .
. . . .
Отже, що означає обертати матрицю? Візьмемо матрицю 2 × 2 і покладемо кілька чисел у кожен елемент, щоб можна було спостерігати обертання:
0 1
2 3
Поворот цього на 90 градусів дає нам:
2 0
3 1
Ми буквально повернули всю матрицю праворуч так само, як повернути кермо автомобіля. Це може допомогти подумати про "перекидання" матриці на її праву сторону. Ми хочемо написати функцію в Python, яка приймає матрицю і обертається один раз праворуч. Підпис функції буде:
def rotate(matrix):
# Algorithm goes here.
Матриця буде визначена за допомогою двовимірного масиву:
matrix = [
[0,1],
[2,3]
]
Тому перша позиція індексу має доступ до рядка. Друга позиція індексу отримує доступ до стовпця:
matrix[row][column]
Ми визначимо функцію утиліти для друку матриці.
def print_matrix(matrix):
for row in matrix:
print row
Один із способів обертання матриці - це робити її пошарово. Але що таке шар? Подумайте про цибулю. Так само, як шари цибулі, коли кожен шар видаляється, ми рухаємося до центру. Інші аналогії - лялька Матрьошка або гра-передача.
Ширина та висота матриці диктують кількість шарів у цій матриці. Давайте використовувати різні символи для кожного шару:
Матриця 2 × 2 має 1 шар
. .
. .
Матриця 3 × 3 має 2 шари
. . .
. x .
. . .
Матриця 4 × 4 має 2 шари
. . . .
. x x .
. x x .
. . . .
Матриця 5 × 5 має 3 шари
. . . . .
. x x x .
. x O x .
. x x x .
. . . . .
Матриця 6 × 6 має 3 шари
. . . . . .
. x x x x .
. x O O x .
. x O O x .
. x x x x .
. . . . . .
Матриця 7 × 7 має 4 шари
. . . . . . .
. x x x x x .
. x O O O x .
. x O - O x .
. x O O O x .
. x x x x x .
. . . . . . .
Ви можете помітити, що збільшення ширини та висоти матриці на одиницю не завжди збільшує кількість шарів. Беручи вищезазначені матриці та табулюючи шари та розміри, ми бачимо, що кількість шарів збільшується один раз на кожні два кроки ширини та висоти:
+-----+--------+
| N×N | Layers |
+-----+--------+
| 1×1 | 1 |
| 2×2 | 1 |
| 3×3 | 2 |
| 4×4 | 2 |
| 5×5 | 3 |
| 6×6 | 3 |
| 7×7 | 4 |
+-----+--------+
Однак не всі шари потребують обертання. Матриця 1 × 1 однакова до і після обертання. Центральний шар 1 × 1 завжди однаковий до і після обертання незалежно від того, наскільки велика загальна матриця:
+-----+--------+------------------+
| 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 |
+-----+--------+------------------+
З огляду на матрицю N × N, як ми можемо програмно визначити кількість шарів, які нам потрібно обертати? Якщо розділити ширину або висоту на два і ігнорувати решту, ми отримаємо наступні результати.
+-----+--------+------------------+---------+
| 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 |
+-----+--------+------------------+---------+
Зауважте, як N/2
відповідає кількість шарів, які потрібно обертати? Іноді кількість обертових шарів на одиницю менше загальної кількості шарів у матриці. Це відбувається, коли внутрішній шар складається лише з одного елемента (тобто матриці 1 × 1), і тому його не потрібно обертати. Він просто ігнорується.
Нам, безсумнівно, потрібна ця інформація в нашій функції для обертання матриці, тому давайте додамо її зараз:
def rotate(matrix):
size = len(matrix)
# Rotatable layers only.
layer_count = size / 2
Тепер ми знаємо, що таке шари і як визначити кількість шарів, які насправді потребують обертання, як ми виділяємо один шар, щоб ми могли його обертати? По-перше, ми оглядаємо матрицю від самого зовнішнього шару, всередину, до самого внутрішнього шару. Матриця 5 × 5 має три шари загалом і два шари, які потребують обертання:
. . . . .
. x x x .
. x O x .
. x x x .
. . . . .
Розглянемо спочатку стовпці. Положення стовпців, що визначають самий зовнішній шар, якщо вважати, що ми рахуємо з 0, дорівнюють 0 і 4:
+--------+-----------+
| Column | 0 1 2 3 4 |
+--------+-----------+
| | . . . . . |
| | . x x x . |
| | . x O x . |
| | . x x x . |
| | . . . . . |
+--------+-----------+
0 і 4 - також позиції рядків для самого зовнішнього шару.
+-----+-----------+
| Row | |
+-----+-----------+
| 0 | . . . . . |
| 1 | . x x x . |
| 2 | . x O x . |
| 3 | . x x x . |
| 4 | . . . . . |
+-----+-----------+
Це завжди буде так, оскільки ширина і висота однакові. Тому ми можемо визначити позиції стовпців та рядків шару лише з двома значеннями (а не з чотирма).
Просуваючись всередину до другого шару, положення стовпців дорівнює 1 і 3. І, так, ви здогадалися, це так само для рядків. Важливо розуміти, що нам потрібно було збільшувати та зменшувати позиції рядків та стовпців при переході всередину до наступного шару.
+-----------+---------+---------+---------+
| Layer | Rows | Columns | Rotate? |
+-----------+---------+---------+---------+
| Outermost | 0 and 4 | 0 and 4 | Yes |
| Inner | 1 and 3 | 1 and 3 | Yes |
| Innermost | 2 | 2 | No |
+-----------+---------+---------+---------+
Отже, для огляду кожного шару нам потрібна петля з лічильниками, що збільшуються і зменшуються, що представляють собою переміщення всередину, починаючи з самого зовнішнього шару. Ми назвемо це нашим "циклом шару".
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)
Код, що знаходиться вище, проходить через (рядок і стовпець) позиції будь-яких шарів, які потребують обертання.
Layer 0: first: 0, last: 4
Layer 1: first: 1, last: 3
Тепер у нас є петля, що забезпечує положення рядків і стовпців кожного шару. Змінні first
та last
ідентифікують позицію індексу першого та останнього рядків та стовпців. Посилання на наші таблиці та рядки та стовпці:
+--------+-----------+
| 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 | . . . . . |
+-----+-----------+
Таким чином ми можемо орієнтуватися по шарах матриці. Тепер нам потрібен спосіб навігації всередині шару, щоб ми могли переміщувати елементи навколо цього шару. Зауважте, елементи ніколи не «перескакують» з одного шару на інший, але вони рухаються всередині відповідних шарів.
Обертання кожного елемента в шарі обертає весь шар. Обертання всіх шарів у матриці обертає всю матрицю. Це речення дуже важливе, тому, будь ласка, постарайтеся, щоб зрозуміти це, перш ніж продовжувати.
Тепер нам потрібен спосіб фактично переміщення елементів, тобто обертання кожного елемента, а згодом і шару, і в кінцевому рахунку матриці. Для простоти ми повернемося до матриці 3x3, яка має один обертовий шар.
0 1 2
3 4 5
6 7 8
Наш цикл шарів містить індекси першого та останнього стовпців, а також першого та останнього рядків:
+-----+-------+
| 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 |
+-----+-------+
Оскільки наші матриці завжди квадратні, нам потрібно всього дві змінні, first
і last
, оскільки позиції індексу однакові для рядків і стовпців.
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.
Перші та останні змінні можуть бути легко використані для посилання на чотири кути матриці. Це тому, що самі кути можна визначити, використовуючи різні перестановки first
та last
(без віднімання, додавання чи зміщення цих змінних):
+---------------+-------------------+-------------+
| 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) |
+---------------+-------------------+-------------+
З цієї причини ми починаємо обертання у зовнішніх чотирьох кутах - ми повернемо їх першими. Давайте виділимо їх *
.
* 1 *
3 4 5
* 7 *
Ми хочемо поміняти місцями кожного *
з *
праворуч від нього. Тож давайте продовжимо роздрукувати наші кути, визначені, використовуючи лише різні перестановки first
та 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)
Вихід повинен бути:
top_left: (0, 0)
top_right: (0, 2)
bottom_right: (2, 2)
bottom_left: (2, 0)
Тепер ми могли легко поміняти місцями кожен кут зсередини нашого шару шару:
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)
Матриця перед обертаннями кутів:
[0, 1, 2]
[3, 4, 5]
[6, 7, 8]
Матриця після обертових кутів:
[6, 1, 0]
[3, 4, 5]
[8, 7, 2]
Чудово! Ми успішно обертали кожен кут матриці. Але ми не обертаємо елементи посередині кожного шару. Очевидно, що нам потрібен спосіб ітерації всередині шару.
Проблема полягає в тому, що єдиний цикл у нашій функції досі (наш цикл шарів) переходить до наступного шару на кожній ітерації. Оскільки наша матриця має лише один обертовий шар, петля шару виходить після обертання лише кутів. Давайте розглянемо, що відбувається з більшою матрицею 5 × 5 (де два шари потрібно обертати). Код функції пропущено, але він залишається таким же, як і вище:
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)
Вихід:
[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]
Це не повинно дивувати, що кути самого зовнішнього шару повернуті, але, можливо, ви також помітите, що кути наступного шару (всередину) також повернуті. Це має сенс. Ми написали код для навігації по шарах, а також для обертання кутів кожного шару. Це відчувається як прогрес, але, на жаль, ми повинні зробити крок назад. Просто переходити на наступний шар просто не буде, поки попередній (зовнішній) шар повністю не повернеться. Тобто, поки кожен елемент у шарі не повернеться. Обертання лише кутів не обійдеться!
Зробити глибокий подих. Нам потрібна ще одна петля. Вкладена петля не менше. Новий, вкладений цикл, буде використовувати first
і last
змінні, плюс зсув , щоб переміщатися в межах шару. Ми будемо називати цей новий цикл нашим "циклом елементів". Цикл елементів буде відвідувати кожен елемент уздовж верхнього ряду, кожен елемент праворуч, кожен елемент уздовж нижнього ряду та кожен елемент вгору ліворуч.
- Для просування вперед по верхньому рядку потрібно збільшувати індекс стовпців.
- Якщо рухатись правою стороною вниз, потрібно збільшувати індекс рядків.
- Для переміщення назад по нижній частині потрібно зменшити індекс стовпця.
- Для руху лівою стороною потрібно зменшити індекс рядка.
Це звучить складно, але це легко, тому що кількість разів, де ми збільшуємо та зменшуємо досягнення вищезгаданого, залишається однаковим по всіх чотирьох сторонах матриці. Наприклад:
- Перемістіть 1 елемент у верхньому ряду.
- Перемістіть 1 елемент вправо.
- Перемістіть 1 елемент назад по нижньому ряду.
- Перемістіть 1 елемент вгору лівою стороною.
Це означає, що ми можемо використовувати одну змінну в поєднанні зі змінними first
та last
для переміщення всередині шару. Це може допомогти зауважити, що переміщення по верхньому ряду та вниз правою стороною вимагає збільшення. Під час руху назад по нижній та лівій стороні вгору потрібно обидві декременти.
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)
Тепер нам просто потрібно присвоїти верхню частину праворуч, праву частину донизу, нижню - ліву сторону, а ліву частину - верхню. Збираючи це все разом, ми отримуємо:
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
Дано матрицю:
0, 1, 2
3, 4, 5
6, 7, 8
Наша rotate
функція призводить до:
6, 3, 0
7, 4, 1
8, 5, 2