Eu gostaria de adicionar um pouco mais de detalhes. Nesta resposta, conceitos-chave são repetidos, o ritmo é lento e intencionalmente repetitivo. A solução fornecida aqui não é a mais sintaticamente compacta; é, no entanto, destinada a quem deseja aprender o que é a rotação da matriz e a implementação resultante.
Em primeiro lugar, o que é uma matriz? Para os fins desta resposta, uma matriz é apenas uma grade em que a largura e a altura são iguais. Observe que a largura e a altura de uma matriz podem ser diferentes, mas, para simplificar, este tutorial considera apenas matrizes com largura e altura iguais ( matrizes quadradas ). E sim, matrizes é o plural de matriz.
As matrizes de exemplo são: 2 × 2, 3 × 3 ou 5 × 5. Ou, mais geralmente, N × N. Uma matriz 2 × 2 terá 4 quadrados porque 2 × 2 = 4. Uma matriz 5 × 5 terá 25 quadrados porque 5 × 5 = 25. Cada quadrado é chamado de elemento ou entrada. Representaremos cada elemento com um ponto (.
) nos diagramas abaixo:
Matriz 2 × 2
. .
. .
Matriz 3 × 3
. . .
. . .
. . .
Matriz 4 × 4
. . . .
. . . .
. . . .
. . . .
Então, o que significa girar uma matriz? Vamos pegar uma matriz 2 × 2 e colocar alguns números em cada elemento para que a rotação possa ser observada:
0 1
2 3
Girar isso em 90 graus nos dá:
2 0
3 1
Nós literalmente giramos a matriz toda uma vez para a direita, assim como girar o volante de um carro. Pode ajudar a "inclinar" a matriz para o lado direito. Queremos escrever uma função, em Python, que pega uma matriz e gira uma vez para a direita. A assinatura da função será:
def rotate(matrix):
# Algorithm goes here.
A matriz será definida usando uma matriz bidimensional:
matrix = [
[0,1],
[2,3]
]
Portanto, a primeira posição do índice acessa a linha. A segunda posição do índice acessa a coluna:
matrix[row][column]
Definiremos uma função utilitária para imprimir uma matriz.
def print_matrix(matrix):
for row in matrix:
print row
Um método de girar uma matriz é fazer uma camada de cada vez. Mas o que é uma camada? Pense em uma cebola. Assim como as camadas de uma cebola, à medida que cada camada é removida, avançamos em direção ao centro. Outras analogias são uma boneca matryoshka ou um jogo de empacotar.
A largura e a altura de uma matriz determinam o número de camadas nessa matriz. Vamos usar símbolos diferentes para cada camada:
Uma matriz 2 × 2 possui 1 camada
. .
. .
Uma matriz 3 × 3 possui 2 camadas
. . .
. x .
. . .
Uma matriz 4 × 4 tem 2 camadas
. . . .
. x x .
. x x .
. . . .
Uma matriz 5 × 5 possui 3 camadas
. . . . .
. x x x .
. x O x .
. x x x .
. . . . .
Uma matriz 6 × 6 tem 3 camadas
. . . . . .
. x x x x .
. x O O x .
. x O O x .
. x x x x .
. . . . . .
Uma matriz 7 × 7 tem 4 camadas
. . . . . . .
. x x x x x .
. x O O O x .
. x O - O x .
. x O O O x .
. x x x x x .
. . . . . . .
Você pode perceber que incrementar a largura e a altura de uma matriz em uma unidade nem sempre aumenta o número de camadas. Tomando as matrizes acima e tabulando as camadas e dimensões, vemos o número de camadas aumentar uma vez a cada dois incrementos de largura e altura:
+-----+--------+
| N×N | Layers |
+-----+--------+
| 1×1 | 1 |
| 2×2 | 1 |
| 3×3 | 2 |
| 4×4 | 2 |
| 5×5 | 3 |
| 6×6 | 3 |
| 7×7 | 4 |
+-----+--------+
No entanto, nem todas as camadas precisam ser rotacionadas. Uma matriz 1 × 1 é a mesma antes e depois da rotação. A camada 1 × 1 central é sempre a mesma antes e depois da rotação, independentemente do tamanho da matriz geral:
+-----+--------+------------------+
| 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 |
+-----+--------+------------------+
Dada a matriz N × N, como podemos determinar programaticamente o número de camadas que precisamos rotacionar? Se dividirmos a largura ou a altura por dois e ignorarmos o restante, obteremos os seguintes resultados.
+-----+--------+------------------+---------+
| 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 |
+-----+--------+------------------+---------+
Note como N/2
corresponde ao número de camadas que precisam ser giradas? Às vezes, o número de camadas rotativas é um a menos o número total de camadas na matriz. Isso ocorre quando a camada mais interna é formada por apenas um elemento (ou seja, uma matriz 1 × 1) e, portanto, não precisa ser girada. Simplesmente é ignorado.
Indubitavelmente, precisaremos dessas informações em nossa função para girar uma matriz, então vamos adicioná-las agora:
def rotate(matrix):
size = len(matrix)
# Rotatable layers only.
layer_count = size / 2
Agora sabemos o que são camadas e como determinar o número de camadas que realmente precisam ser rotacionadas. Como isolamos uma única camada para podermos rotacioná-la? Primeiro, inspecionamos uma matriz da camada mais externa, para dentro, até a camada mais interna. Uma matriz 5 × 5 possui três camadas no total e duas camadas que precisam ser rotacionadas:
. . . . .
. x x x .
. x O x .
. x x x .
. . . . .
Vejamos as colunas primeiro. A posição das colunas que definem a camada mais externa, assumindo que contamos de 0, são 0 e 4:
+--------+-----------+
| Column | 0 1 2 3 4 |
+--------+-----------+
| | . . . . . |
| | . x x x . |
| | . x O x . |
| | . x x x . |
| | . . . . . |
+--------+-----------+
0 e 4 também são as posições das linhas da camada mais externa.
+-----+-----------+
| Row | |
+-----+-----------+
| 0 | . . . . . |
| 1 | . x x x . |
| 2 | . x O x . |
| 3 | . x x x . |
| 4 | . . . . . |
+-----+-----------+
Esse sempre será o caso, pois a largura e a altura são as mesmas. Portanto, podemos definir as posições da coluna e da linha de uma camada com apenas dois valores (em vez de quatro).
Indo para a segunda camada, a posição das colunas é 1 e 3. E, sim, você adivinhou, é o mesmo para as linhas. É importante entender que tivemos que incrementar e diminuir as posições de linha e coluna ao mover para dentro para a próxima camada.
+-----------+---------+---------+---------+
| Layer | Rows | Columns | Rotate? |
+-----------+---------+---------+---------+
| Outermost | 0 and 4 | 0 and 4 | Yes |
| Inner | 1 and 3 | 1 and 3 | Yes |
| Innermost | 2 | 2 | No |
+-----------+---------+---------+---------+
Portanto, para inspecionar cada camada, queremos um loop com contadores crescentes e decrescentes que representem o movimento para dentro, a partir da camada mais externa. Vamos chamar isso de nosso 'loop de camada'.
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 código acima percorre as posições (linha e coluna) de todas as camadas que precisam ser rotacionadas.
Layer 0: first: 0, last: 4
Layer 1: first: 1, last: 3
Agora temos um loop fornecendo as posições das linhas e colunas de cada camada. As variáveis first
e last
identificam a posição do índice da primeira e da última linhas e colunas. Voltando às nossas tabelas de linhas e colunas:
+--------+-----------+
| 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 | . . . . . |
+-----+-----------+
Para que possamos navegar pelas camadas de uma matriz. Agora, precisamos de uma maneira de navegar dentro de uma camada para que possamos mover elementos ao redor dessa camada. Observe que os elementos nunca 'pulam' de uma camada para outra, mas se movem dentro de suas respectivas camadas.
Girar cada elemento em uma camada gira a camada inteira. A rotação de todas as camadas em uma matriz gira a matriz inteira. Esta frase é muito importante, então tente o seu melhor para entendê-la antes de prosseguir.
Agora, precisamos de uma maneira de realmente mover elementos, ou seja, girar cada elemento e, posteriormente, a camada e, finalmente, a matriz. Por simplicidade, reverteremos para uma matriz 3x3 - que possui uma camada rotativa.
0 1 2
3 4 5
6 7 8
Nosso loop de camada fornece os índices da primeira e da última coluna, bem como a primeira e a última linha:
+-----+-------+
| 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 |
+-----+-------+
Como nossas matrizes são sempre quadradas, precisamos de apenas duas variáveis first
e last
, como as posições do índice são as mesmas para linhas e colunas.
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.
As variáveis first e last podem ser facilmente usadas para referenciar os quatro cantos de uma matriz. Isso ocorre porque os próprios cantos podem ser definidos usando várias permutações de first
e last
(sem subtração, adição ou deslocamento dessas variáveis):
+---------------+-------------------+-------------+
| 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) |
+---------------+-------------------+-------------+
Por esse motivo, iniciamos nossa rotação nos quatro cantos externos - nós os rodaremos primeiro. Vamos destacá-los com *
.
* 1 *
3 4 5
* 7 *
Queremos trocar cada *
um *
pela direita. Então, vamos em frente e imprima nossos cantos definidos usando apenas várias permutações de first
e 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)
A saída deve ser:
top_left: (0, 0)
top_right: (0, 2)
bottom_right: (2, 2)
bottom_left: (2, 0)
Agora, podemos facilmente trocar cada um dos cantos de dentro do nosso loop de camada:
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)
Matriz antes de girar cantos:
[0, 1, 2]
[3, 4, 5]
[6, 7, 8]
Matriz depois de girar os cantos:
[6, 1, 0]
[3, 4, 5]
[8, 7, 2]
Ótimo! Giramos com sucesso cada canto da matriz. Mas não rotacionamos os elementos no meio de cada camada. Claramente, precisamos de uma maneira de iterar dentro de uma camada.
O problema é que o único loop em nossa função até agora (nosso loop de camada) passa para a próxima camada em cada iteração. Como nossa matriz possui apenas uma camada rotativa, o loop da camada sai após a rotação apenas dos cantos. Vejamos o que acontece com uma matriz 5 × 5 maior (onde duas camadas precisam ser rotacionadas). O código da função foi omitido, mas permanece o mesmo que acima:
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)
A saída é:
[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]
Não deve ser surpresa que os cantos da camada mais externa tenham sido girados, mas você também pode observar que os cantos da próxima camada (para dentro) também foram girados. Isso faz sentido. Escrevemos código para navegar pelas camadas e também para girar os cantos de cada camada. Parece um progresso, mas infelizmente devemos dar um passo atrás. Não adianta passar para a próxima camada até que a camada anterior (externa) tenha sido totalmente girada. Ou seja, até que cada elemento da camada seja girado. Girar apenas os cantos não serve!
Respire fundo. Precisamos de outro loop. Um loop aninhado não menos. O novo loop aninhado usará as variáveis first
e last
, além de um deslocamento para navegar dentro de uma camada. Vamos chamar esse novo loop de 'elemento loop'. O loop do elemento visitará cada elemento ao longo da linha superior, cada elemento no lado direito, cada elemento ao longo da linha inferior e cada elemento no lado esquerdo.
- Para avançar na linha superior, é necessário aumentar o índice da coluna.
- Mover para o lado direito requer que o índice da linha seja incrementado.
- Mover para trás ao longo da parte inferior requer que o índice da coluna seja diminuído.
- Mover para o lado esquerdo requer que o índice da linha seja diminuído.
Isso parece complexo, mas é fácil porque o número de vezes que aumentamos e diminuímos para alcançar o acima mencionado permanece o mesmo nos quatro lados da matriz. Por exemplo:
- Mova 1 elemento pela linha superior.
- Mova 1 elemento para o lado direito.
- Mova 1 elemento para trás ao longo da linha inferior.
- Mova 1 elemento para o lado esquerdo.
Isso significa que podemos usar uma única variável em combinação com as variáveis first
e last
para mover-se dentro de uma camada. Pode ser útil observar que a movimentação pela linha superior e pelo lado direito requer um incremento. Enquanto se move para trás ao longo da parte inferior e da esquerda, ambos exigem decréscimo.
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)
Agora, basta atribuir o topo ao lado direito, o lado direito ao fundo, o fundo ao lado esquerdo e o lado esquerdo ao topo. Juntando tudo isso, obtemos:
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
Dada a matriz:
0, 1, 2
3, 4, 5
6, 7, 8
Nossa rotate
função resulta em:
6, 3, 0
7, 4, 1
8, 5, 2