Chciałbym dodać trochę więcej szczegółów. W tej odpowiedzi kluczowe pojęcia są powtarzane, tempo jest powolne i celowo powtarzalne. Przedstawione tutaj rozwiązanie nie jest najbardziej kompaktowe pod względem składniowym, jest jednak przeznaczone dla tych, którzy chcą dowiedzieć się, czym jest rotacja macierzy i wynikająca z niej implementacja.
Po pierwsze, czym jest matryca? Na potrzeby tej odpowiedzi macierz jest po prostu siatką, w której szerokość i wysokość są takie same. Uwaga: szerokość i wysokość matrycy mogą być różne, ale dla uproszczenia w tym samouczku uwzględniono tylko matryce o równej szerokości i wysokości ( matryce kwadratowe ). I tak, macierze to liczba mnoga macierzy.
Przykładowe macierze to: 2 × 2, 3 × 3 lub 5 × 5. Lub, bardziej ogólnie, N × N. Matryca 2 × 2 będzie miała 4 kwadraty, ponieważ 2 × 2 = 4. Matryca 5 × 5 będzie miała 25 kwadratów, ponieważ 5 × 5 = 25. Każdy kwadrat nazywa się elementem lub wpisem. Będziemy reprezentować każdy element kropką ( .
) na poniższych schematach:
Matryca 2 × 2
. .
. .
Matryca 3 × 3
. . .
. . .
. . .
Matryca 4 × 4
. . . .
. . . .
. . . .
. . . .
Co to znaczy obrócić matrycę? Weźmy macierz 2 × 2 i umieśćmy liczby w każdym elemencie, aby można było zaobserwować obrót:
0 1
2 3
Obracanie o 90 stopni daje nam:
2 0
3 1
Dosłownie raz obróciliśmy całą matrycę w prawo, podobnie jak obracanie kierownicy samochodu. Pomóc może „przewrócenie” matrycy na jej prawą stronę. Chcemy napisać w Pythonie funkcję, która pobiera macierz i obraca się raz w prawo. Podpis funkcji będzie:
def rotate(matrix):
# Algorithm goes here.
Macierz zostanie zdefiniowana za pomocą dwuwymiarowej tablicy:
matrix = [
[0,1],
[2,3]
]
Dlatego pierwsza pozycja indeksu uzyskuje dostęp do wiersza. Druga pozycja indeksu uzyskuje dostęp do kolumny:
matrix[row][column]
Zdefiniujemy funkcję narzędziową do drukowania matrycy.
def print_matrix(matrix):
for row in matrix:
print row
Jedną z metod obracania matrycy jest robienie z niej warstwy na raz. Ale czym jest warstwa? Pomyśl o cebuli. Podobnie jak warstwy cebuli, gdy każda warstwa jest usuwana, przesuwamy się w kierunku środka. Inne analogie to lalka Matryoshka lub gra pass-the-parcel.
Szerokość i wysokość matrycy decydują o liczbie warstw w tej macierzy. Użyjmy różnych symboli dla każdej warstwy:
Matryca 2 × 2 ma 1 warstwę
. .
. .
Matryca 3 × 3 ma 2 warstwy
. . .
. x .
. . .
Matryca 4 × 4 ma 2 warstwy
. . . .
. x x .
. x x .
. . . .
Matryca 5 x 5 ma 3 warstwy
. . . . .
. x x x .
. x O x .
. x x x .
. . . . .
Matryca 6 × 6 ma 3 warstwy
. . . . . .
. x x x x .
. x O O x .
. x O O x .
. x x x x .
. . . . . .
Matryca 7 × 7 ma 4 warstwy
. . . . . . .
. x x x x x .
. x O O O x .
. x O - O x .
. x O O O x .
. x x x x x .
. . . . . . .
Można zauważyć, że zwiększenie szerokości i wysokości matrycy o jeden nie zawsze zwiększa liczbę warstw. Biorąc powyższe macierze i zestawiając warstwy i wymiary, widzimy, że liczba warstw wzrasta raz na każde dwa przyrosty szerokości i wysokości:
+-----+--------+
| N×N | Layers |
+-----+--------+
| 1×1 | 1 |
| 2×2 | 1 |
| 3×3 | 2 |
| 4×4 | 2 |
| 5×5 | 3 |
| 6×6 | 3 |
| 7×7 | 4 |
+-----+--------+
Jednak nie wszystkie warstwy wymagają obracania. Macierz 1 × 1 jest taka sama przed i po obrocie. Centralna warstwa 1 × 1 jest zawsze taka sama przed i po obrocie, bez względu na to, jak duża jest ogólna matryca:
+-----+--------+------------------+
| 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 |
+-----+--------+------------------+
Biorąc pod uwagę macierz N × N, w jaki sposób możemy programowo określić liczbę warstw, które musimy obrócić? Jeśli podzielimy szerokość lub wysokość przez dwa i zignorujemy resztę, otrzymamy następujące wyniki.
+-----+--------+------------------+---------+
| 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 |
+-----+--------+------------------+---------+
Zauważ jak N/2
pasuje liczba warstw, które należy obrócić? Czasami liczba warstw obrotowych jest o jeden mniejsza od całkowitej liczby warstw w matrycy. Dzieje się tak, gdy najbardziej wewnętrzna warstwa jest utworzona tylko z jednego elementu (tj. Matrycy 1 × 1) i dlatego nie musi być obracana. Po prostu zostaje zignorowany.
Niewątpliwie będziemy potrzebować tych informacji w naszej funkcji, aby obrócić macierz, więc dodajmy ją teraz:
def rotate(matrix):
size = len(matrix)
# Rotatable layers only.
layer_count = size / 2
Teraz wiemy, jakie są warstwy i jak określić liczbę warstw, które faktycznie wymagają obrotu, w jaki sposób izolujemy pojedynczą warstwę, abyśmy mogli ją obrócić? Po pierwsze, sprawdzamy matrycę od zewnętrznej warstwy, do wewnątrz, do wewnętrznej warstwy. Matryca 5 × 5 ma łącznie trzy warstwy i dwie warstwy, które wymagają obrotu:
. . . . .
. x x x .
. x O x .
. x x x .
. . . . .
Najpierw spójrzmy na kolumny. Położenie kolumn określających zewnętrzną warstwę, przy założeniu, że liczymy od 0, to 0 i 4:
+--------+-----------+
| Column | 0 1 2 3 4 |
+--------+-----------+
| | . . . . . |
| | . x x x . |
| | . x O x . |
| | . x x x . |
| | . . . . . |
+--------+-----------+
0 i 4 to także pozycje rzędów dla najbardziej zewnętrznej warstwy.
+-----+-----------+
| Row | |
+-----+-----------+
| 0 | . . . . . |
| 1 | . x x x . |
| 2 | . x O x . |
| 3 | . x x x . |
| 4 | . . . . . |
+-----+-----------+
Tak będzie zawsze, ponieważ szerokość i wysokość są takie same. Dlatego możemy zdefiniować pozycje kolumny i wiersza warstwy za pomocą tylko dwóch wartości (zamiast czterech).
Przechodząc do drugiej warstwy, pozycje kolumn wynoszą 1 i 3. I tak, zgadliście, to samo dla rzędów. Ważne jest, aby zrozumieć, że musieliśmy zarówno zwiększać, jak i zmniejszać pozycje wierszy i kolumn podczas przechodzenia do wewnątrz do następnej warstwy.
+-----------+---------+---------+---------+
| Layer | Rows | Columns | Rotate? |
+-----------+---------+---------+---------+
| Outermost | 0 and 4 | 0 and 4 | Yes |
| Inner | 1 and 3 | 1 and 3 | Yes |
| Innermost | 2 | 2 | No |
+-----------+---------+---------+---------+
Tak więc, aby sprawdzić każdą warstwę, chcemy pętli z licznikami rosnącymi i malejącymi, które reprezentują ruch do wewnątrz, zaczynając od najbardziej zewnętrznej warstwy. Nazwiemy to naszą „pętlą warstw”.
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)
Powyższy kod zapętla pozycje (wierszy i kolumn) dowolnych warstw, które wymagają obrócenia.
Layer 0: first: 0, last: 4
Layer 1: first: 1, last: 3
Mamy teraz pętlę zapewniającą pozycje wierszy i kolumn każdej warstwy. Zmienne first
i last
identyfikują pozycję indeksu pierwszego i ostatniego wiersza i kolumny. Wracając do naszych tabel wierszy i kolumn:
+--------+-----------+
| 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 | . . . . . |
+-----+-----------+
Możemy więc poruszać się po warstwach matrycy. Teraz potrzebujemy sposobu poruszania się po warstwie, abyśmy mogli przenosić elementy wokół tej warstwy. Uwaga: elementy nigdy nie „przeskakują” z jednej warstwy na drugą, ale poruszają się w obrębie odpowiednich warstw.
Obracanie każdego elementu w warstwie powoduje obrót całej warstwy. Obracanie wszystkich warstw w matrycy powoduje obrót całej matrycy. To zdanie jest bardzo ważne, więc staraj się jak najlepiej je zrozumieć, zanim przejdziesz dalej.
Teraz potrzebujemy sposobu faktycznego przemieszczania elementów, tj. Obracania każdego elementu, a następnie warstwy, a ostatecznie macierzy. Dla uproszczenia powrócimy do matrycy 3x3 - która ma jedną obrotową warstwę.
0 1 2
3 4 5
6 7 8
Nasza pętla warstw zawiera indeksy pierwszej i ostatniej kolumny, a także pierwszego i ostatniego wiersza:
+-----+-------+
| 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 |
+-----+-------+
Ponieważ nasze macierze są zawsze kwadratowe, potrzebujemy tylko dwóch zmiennych, first
a last
ponieważ pozycje indeksu są takie same dla wierszy i kolumn.
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.
Zmienne pierwsza i ostatnia mogą być łatwo wykorzystane do odniesienia do czterech rogów macierzy. Wynika to z faktu, że same rogi można zdefiniować za pomocą różnych kombinacji first
i last
(bez odejmowania, dodawania lub przesunięcia tych zmiennych):
+---------------+-------------------+-------------+
| 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) |
+---------------+-------------------+-------------+
Z tego powodu rozpoczynamy obrót w czterech zewnętrznych rogach - najpierw je obrócimy. Wyróżnijmy je za pomocą *
.
* 1 *
3 4 5
* 7 *
Chcemy zamienić każdy *
z *
prawej strony. A więc przejdźmy do wydruku naszych narożników zdefiniowanych przy użyciu tylko różnych kombinacji first
i 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)
Dane wyjściowe powinny wynosić:
top_left: (0, 0)
top_right: (0, 2)
bottom_right: (2, 2)
bottom_left: (2, 0)
Teraz możemy dość łatwo zamienić każdy z rogów z naszej pętli warstw:
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)
Matryca przed obracaniem narożników:
[0, 1, 2]
[3, 4, 5]
[6, 7, 8]
Matryca po obróceniu narożników:
[6, 1, 0]
[3, 4, 5]
[8, 7, 2]
Świetny! Z powodzeniem obróciliśmy każdy narożnik matrycy. Ale nie obróciliśmy elementów na środku każdej warstwy. Oczywiście potrzebujemy sposobu iteracji w obrębie warstwy.
Problem polega na tym, że jedyna jak dotąd pętla w naszej funkcji (nasza pętla warstw) przesuwa się do następnej warstwy przy każdej iteracji. Ponieważ nasza matryca ma tylko jedną warstwę obrotową, pętla warstwy wychodzi po obróceniu tylko narożników. Spójrzmy na to, co dzieje się z większą matrycą 5 × 5 (gdzie dwie warstwy wymagają obrotu). Kod funkcji został pominięty, ale pozostaje taki sam jak powyżej:
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)
Dane wyjściowe to:
[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]
Nie powinno być zaskoczeniem, że rogi zewnętrznej warstwy zostały obrócone, ale można również zauważyć, że rogi kolejnej warstwy (do wewnątrz) również zostały obrócone. To ma sens. Napisaliśmy kod do poruszania się po warstwach, a także do obracania rogów każdej warstwy. To wydaje się postępem, ale niestety musimy cofnąć się o krok. Po prostu przejście do następnej warstwy nie jest dobre, dopóki poprzednia (zewnętrzna) warstwa nie zostanie całkowicie obrócona. To znaczy, dopóki każdy element w warstwie nie zostanie obrócony. Obracanie tylko narożników nie zadziała!
Weź głęboki oddech. Potrzebujemy kolejnej pętli. Zagnieżdżona pętla nie mniej. Nowe zagnieżdżonej pętli użyje first
i last
zmienne, a także przesunięcie poruszać się w warstwie. Tę nową pętlę nazwiemy „pętlą elementów”. Pętla elementów odwiedzi każdy element wzdłuż górnego rzędu, każdy element po prawej stronie, każdy element wzdłuż dolnego rzędu i każdy element po lewej stronie.
- Przesunięcie do przodu wzdłuż górnego wiersza wymaga zwiększenia indeksu kolumny.
- Przesunięcie w dół po prawej stronie wymaga zwiększenia indeksu wierszy.
- Przesunięcie do tyłu wzdłuż dołu wymaga zmniejszenia indeksu kolumny.
- Przesunięcie w górę po lewej stronie wymaga zmniejszenia indeksu wiersza.
Brzmi to skomplikowanie, ale jest to łatwe, ponieważ liczba inkrementacji i dekrementacji w celu osiągnięcia powyższego pozostaje taka sama na wszystkich czterech bokach matrycy. Na przykład:
- Przenieś 1 element w górnym rzędzie.
- Przesuń 1 element w dół po prawej stronie.
- Przesuń 1 element do tyłu wzdłuż dolnego rzędu.
- Przesuń 1 element w górę po lewej stronie.
Oznacza to, że możemy użyć pojedynczej zmiennej w połączeniu ze zmiennymi first
i last
, aby poruszać się w warstwie. Warto zauważyć, że poruszanie się w górnym rzędzie i w dół po prawej stronie wymaga zwiększania. Podczas przesuwania się do tyłu wzdłuż dołu i do góry po lewej stronie oba wymagają zmniejszenia.
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)
Teraz musimy po prostu przypisać górę do prawej strony, prawej strony do dołu, od dołu do lewej strony i lewej strony do góry. Łącząc to wszystko, otrzymujemy:
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
Biorąc pod uwagę matrycę:
0, 1, 2
3, 4, 5
6, 7, 8
Nasza rotate
funkcja powoduje:
6, 3, 0
7, 4, 1
8, 5, 2