Vorrei aggiungere qualche dettaglio in più. In questa risposta, i concetti chiave si ripetono, il ritmo è lento e intenzionalmente ripetitivo. La soluzione fornita qui non è la più sintatticamente compatta, ma è destinata a coloro che desiderano apprendere cos'è la rotazione della matrice e l'implementazione risultante.
Innanzitutto, cos'è una matrice? Ai fini di questa risposta, una matrice è solo una griglia in cui larghezza e altezza sono uguali. Nota, la larghezza e l'altezza di una matrice possono essere diverse, ma per semplicità, questo tutorial considera solo le matrici con uguale larghezza e altezza ( matrici quadrate ). E sì, matrici sono il plurale di matrice.
Le matrici di esempio sono: 2 × 2, 3 × 3 o 5 × 5. O, più in generale, N × N. Una matrice 2 × 2 avrà 4 quadrati perché 2 × 2 = 4. Una matrice 5 × 5 avrà 25 quadrati perché 5 × 5 = 25. Ogni quadrato è chiamato elemento o voce. Rappresenteremo ogni elemento con un punto ( .
) nei diagrammi seguenti:
Matrice 2 × 2
. .
. .
Matrice 3 × 3
. . .
. . .
. . .
Matrice 4 × 4
. . . .
. . . .
. . . .
. . . .
Quindi, cosa significa ruotare una matrice? Prendiamo una matrice 2 × 2 e mettiamo alcuni numeri in ciascun elemento in modo da poter osservare la rotazione:
0 1
2 3
Ruotandolo di 90 gradi ci dà:
2 0
3 1
Abbiamo letteralmente girato l'intera matrice una volta verso destra, proprio come girare il volante di un'auto. Può essere utile pensare di "inclinare" la matrice sul lato destro. Vogliamo scrivere una funzione, in Python, che prende una matrice e ruota una volta verso destra. La firma della funzione sarà:
def rotate(matrix):
# Algorithm goes here.
La matrice verrà definita utilizzando una matrice bidimensionale:
matrix = [
[0,1],
[2,3]
]
Pertanto la prima posizione dell'indice accede alla riga. La seconda posizione dell'indice accede alla colonna:
matrix[row][column]
Definiremo una funzione di utilità per stampare una matrice.
def print_matrix(matrix):
for row in matrix:
print row
Un metodo per ruotare una matrice è quello di farlo uno strato alla volta. Ma cos'è uno strato? Pensa a una cipolla. Proprio come gli strati di una cipolla, mentre ogni strato viene rimosso, ci spostiamo verso il centro. Altre analogie sono una bambola Matryoshka o un gioco di pacchi.
La larghezza e l'altezza di una matrice determinano il numero di strati in quella matrice. Usiamo simboli diversi per ogni livello:
Una matrice 2 × 2 ha 1 strato
. .
. .
Una matrice 3 × 3 ha 2 strati
. . .
. x .
. . .
Una matrice 4 × 4 ha 2 strati
. . . .
. x x .
. x x .
. . . .
Una matrice 5 × 5 ha 3 strati
. . . . .
. x x x .
. x O x .
. x x x .
. . . . .
Una matrice 6 × 6 ha 3 strati
. . . . . .
. x x x x .
. x O O x .
. x O O x .
. x x x x .
. . . . . .
Una matrice 7 × 7 ha 4 strati
. . . . . . .
. x x x x x .
. x O O O x .
. x O - O x .
. x O O O x .
. x x x x x .
. . . . . . .
Si può notare che l'incremento della larghezza e dell'altezza di una matrice di uno non aumenta sempre il numero di livelli. Prendendo le matrici sopra e tabulando i livelli e le dimensioni, vediamo il numero di livelli aumentare una volta ogni due incrementi di larghezza e altezza:
+-----+--------+
| N×N | Layers |
+-----+--------+
| 1×1 | 1 |
| 2×2 | 1 |
| 3×3 | 2 |
| 4×4 | 2 |
| 5×5 | 3 |
| 6×6 | 3 |
| 7×7 | 4 |
+-----+--------+
Tuttavia, non tutti gli strati devono essere ruotati. Una matrice 1 × 1 è la stessa prima e dopo la rotazione. Lo strato centrale 1 × 1 è sempre lo stesso prima e dopo la rotazione, indipendentemente dalla dimensione complessiva della matrice:
+-----+--------+------------------+
| 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 |
+-----+--------+------------------+
Data la matrice N × N, come possiamo determinare a livello di codice il numero di strati che dobbiamo ruotare? Se dividiamo la larghezza o l'altezza per due e ignoriamo il resto otteniamo i seguenti risultati.
+-----+--------+------------------+---------+
| 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 |
+-----+--------+------------------+---------+
Notare come N/2
corrisponde il numero di livelli che devono essere ruotati? A volte il numero di strati ruotabili è uno in meno del numero totale di strati nella matrice. Ciò si verifica quando lo strato più interno è formato da un solo elemento (cioè una matrice 1 × 1) e pertanto non è necessario ruotarlo. Viene semplicemente ignorato.
Indubbiamente avremo bisogno di queste informazioni nella nostra funzione per ruotare una matrice, quindi aggiungiamole ora:
def rotate(matrix):
size = len(matrix)
# Rotatable layers only.
layer_count = size / 2
Ora sappiamo quali sono i layer e come determinare il numero di layer che devono effettivamente essere ruotati, come isoliamo un singolo layer in modo da poterlo ruotare? Innanzitutto, ispezioniamo una matrice dallo strato più esterno, verso l'interno, allo strato più interno. Una matrice 5 × 5 ha tre strati in totale e due strati che devono essere ruotati:
. . . . .
. x x x .
. x O x .
. x x x .
. . . . .
Diamo un'occhiata alle colonne per prime. La posizione delle colonne che definiscono il livello più esterno, supponendo che contiamo da 0, sono 0 e 4:
+--------+-----------+
| Column | 0 1 2 3 4 |
+--------+-----------+
| | . . . . . |
| | . x x x . |
| | . x O x . |
| | . x x x . |
| | . . . . . |
+--------+-----------+
0 e 4 sono anche le posizioni delle righe per il livello più esterno.
+-----+-----------+
| Row | |
+-----+-----------+
| 0 | . . . . . |
| 1 | . x x x . |
| 2 | . x O x . |
| 3 | . x x x . |
| 4 | . . . . . |
+-----+-----------+
Questo sarà sempre il caso poiché la larghezza e l'altezza sono uguali. Pertanto possiamo definire le posizioni di colonna e riga di un livello con solo due valori (anziché quattro).
Spostandosi verso l'interno verso il secondo livello, la posizione delle colonne è 1 e 3. E, sì, hai indovinato, è lo stesso per le righe. È importante capire che abbiamo dovuto sia aumentare che diminuire le posizioni di riga e colonna quando ci si sposta verso l'interno al livello successivo.
+-----------+---------+---------+---------+
| Layer | Rows | Columns | Rotate? |
+-----------+---------+---------+---------+
| Outermost | 0 and 4 | 0 and 4 | Yes |
| Inner | 1 and 3 | 1 and 3 | Yes |
| Innermost | 2 | 2 | No |
+-----------+---------+---------+---------+
Quindi, per ispezionare ogni livello, vogliamo un loop con contatori sia in aumento che in diminuzione che rappresentano lo spostamento verso l'interno, a partire dal livello più esterno. Lo chiameremo il nostro "loop di layer".
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)
Il codice sopra scorre attraverso le posizioni (riga e colonna) di tutti i livelli che devono essere ruotati.
Layer 0: first: 0, last: 4
Layer 1: first: 1, last: 3
Ora abbiamo un loop che fornisce le posizioni delle righe e delle colonne di ogni livello. Le variabili first
e last
identificano la posizione dell'indice della prima e dell'ultima riga e colonna. Facendo riferimento alle nostre tabelle di righe e colonne:
+--------+-----------+
| 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 | . . . . . |
+-----+-----------+
Quindi possiamo navigare attraverso i livelli di una matrice. Ora abbiamo bisogno di un modo per navigare all'interno di un livello in modo da poter spostare elementi attorno a quel livello. Nota, gli elementi non saltano mai da un livello all'altro, ma si muovono all'interno dei rispettivi livelli.
Ruotando ciascun elemento in un livello si ruota l'intero livello. Ruotando tutti i livelli in una matrice si ruota l'intera matrice. Questa frase è molto importante, quindi fai del tuo meglio per capirla prima di andare avanti.
Ora, abbiamo bisogno di un modo per spostare effettivamente gli elementi, cioè ruotare ciascun elemento, e successivamente il livello, e infine la matrice. Per semplicità, torneremo a una matrice 3x3 - che ha uno strato ruotabile.
0 1 2
3 4 5
6 7 8
Il nostro loop di livelli fornisce gli indici della prima e dell'ultima colonna, nonché della prima e dell'ultima riga:
+-----+-------+
| 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 |
+-----+-------+
Poiché le nostre matrici sono sempre quadrate, abbiamo bisogno solo di due variabili first
e last
, poiché le posizioni dell'indice sono le stesse per righe e colonne.
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.
Le variabili prima e ultima possono essere facilmente utilizzate per fare riferimento ai quattro angoli di una matrice. Questo perché gli angoli stessi possono essere definiti usando varie permutazioni di first
e last
(senza sottrazione, aggiunta o offset di tali variabili):
+---------------+-------------------+-------------+
| 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) |
+---------------+-------------------+-------------+
Per questo motivo, iniziamo la nostra rotazione dai quattro angoli esterni: li ruoteremo per primi. Evidenziamoli con *
.
* 1 *
3 4 5
* 7 *
Vogliamo scambiare ciascuno *
con il *
a destra di esso. Quindi andiamo avanti con una stampa dei nostri angoli definiti usando solo varie permutazioni di 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)
L'output dovrebbe essere:
top_left: (0, 0)
top_right: (0, 2)
bottom_right: (2, 2)
bottom_left: (2, 0)
Ora possiamo scambiare abbastanza facilmente ciascuno degli angoli all'interno del nostro loop di livelli:
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)
Matrice prima di ruotare gli angoli:
[0, 1, 2]
[3, 4, 5]
[6, 7, 8]
Matrice dopo aver ruotato gli angoli:
[6, 1, 0]
[3, 4, 5]
[8, 7, 2]
Grande! Abbiamo ruotato con successo ogni angolo della matrice. Ma non abbiamo ruotato gli elementi nel mezzo di ogni livello. Chiaramente abbiamo bisogno di un modo per scorrere all'interno di un livello.
Il problema è che l'unico loop nella nostra funzione finora (il nostro loop di layer) si sposta al layer successivo su ogni iterazione. Poiché la nostra matrice ha un solo strato ruotabile, l'anello di livello esce dopo aver ruotato solo gli angoli. Diamo un'occhiata a ciò che accade con una matrice più grande, 5 × 5 (in cui due strati devono essere ruotati). Il codice funzione è stato omesso, ma rimane lo stesso di sopra:
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)
L'output è:
[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]
Non dovrebbe sorprendere che gli angoli del livello più esterno siano stati ruotati, ma potresti anche notare che sono stati ruotati anche gli angoli del livello successivo (verso l'interno). Questo ha senso. Abbiamo scritto codice per navigare tra i livelli e anche per ruotare gli angoli di ciascun livello. Sembra un progresso, ma sfortunatamente dobbiamo fare un passo indietro. Passare al livello successivo non è affatto utile fino a quando il livello precedente (esterno) non viene completamente ruotato. Cioè, fino a quando ogni elemento nel livello è stato ruotato. Ruotare solo gli angoli non lo farà!
Fai un respiro profondo. Abbiamo bisogno di un altro ciclo. Un ciclo nidificato non meno. Il nuovo ciclo nidificato utilizzerà le variabili first
e last
, oltre a un offset per navigare all'interno di un livello. Chiameremo questo nuovo loop il nostro "elemento loop". Il loop degli elementi visiterà ogni elemento lungo la riga superiore, ogni elemento in basso a destra, ogni elemento in basso e ogni elemento in alto a sinistra.
- Lo spostamento in avanti lungo la riga superiore richiede l'incremento dell'indice di colonna.
- Lo spostamento verso il basso sul lato destro richiede l'incremento dell'indice di riga.
- Lo spostamento all'indietro nella parte inferiore richiede che l'indice di colonna sia ridotto.
- Lo spostamento verso l'alto richiede la riduzione dell'indice di riga.
Sembra complesso, ma è reso semplice perché il numero di volte che incrementiamo e diminuiamo per ottenere quanto sopra rimane lo stesso lungo tutti e quattro i lati della matrice. Per esempio:
- Sposta 1 elemento nella riga superiore.
- Sposta 1 elemento verso il basso sul lato destro.
- Sposta 1 elemento all'indietro lungo la riga inferiore.
- Sposta 1 elemento in alto a sinistra.
Ciò significa che possiamo usare una singola variabile in combinazione con le variabili first
e last
per spostarci all'interno di un livello. Potrebbe essere utile notare che spostarsi attraverso la riga superiore e il lato destro richiede entrambi un incremento. Muovendosi all'indietro lungo il fondo e verso l'alto sul lato sinistro entrambi richiedono un decremento.
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)
Ora dobbiamo semplicemente assegnare la parte superiore alla parte destra, la parte destra alla parte inferiore, la parte inferiore alla parte sinistra e la parte sinistra alla parte superiore. Mettendo tutto insieme otteniamo:
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
Data la matrice:
0, 1, 2
3, 4, 5
6, 7, 8
La nostra rotate
funzione si traduce in:
6, 3, 0
7, 4, 1
8, 5, 2