Я хотел бы добавить немного больше деталей. В этом ответе ключевые понятия повторяются, темп медленный и намеренно повторяющийся. Представленное здесь решение не является наиболее синтаксически компактным, однако оно предназначено для тех, кто хочет узнать, что такое поворот матрицы и какова ее реализация.
Во-первых, что такое матрица? Для целей этого ответа матрица - это просто сетка, в которой ширина и высота одинаковы. Обратите внимание, что ширина и высота матрицы могут быть разными, но для простоты в этом руководстве рассматриваются только матрицы с одинаковой шириной и высотой ( квадратные матрицы ). И да, матрицы - это множественное число от матрицы.
Примеры матриц: 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 | . . . . . |
+-----+-----------+
Таким образом, мы можем перемещаться по слоям матрицы. Теперь нам нужен способ навигации внутри слоя, чтобы мы могли перемещать элементы вокруг этого слоя. Обратите внимание, что элементы никогда не «перепрыгивают» с одного слоя на другой, но они перемещаются внутри своих соответствующих слоев.
Вращение каждого элемента в слое вращает весь слой. Вращение всех слоев в матрице вращает всю матрицу. Это предложение очень важно, поэтому, пожалуйста, постарайтесь понять его, прежде чем двигаться дальше.
Теперь нам нужен способ реально перемещать элементы, то есть вращать каждый элемент, а затем слой и, в конечном итоге, матрицу. Для простоты мы вернемся к матрице 3х3, которая имеет один вращающийся слой.
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 могут быть легко использованы для ссылки на четыре угла матрицы. Это потому, что сами углы могут быть определены с использованием различных перестановок 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