Zainspirowany postem Raymonda Chena , powiedz, że masz dwuwymiarową tablicę 4x4, napisz funkcję, która obraca ją o 90 stopni. Raymond prowadzi do rozwiązania w pseudo-kodzie, ale chciałbym zobaczyć kilka rzeczy z prawdziwego świata.
[1][2][3][4]
[5][6][7][8]
[9][0][1][2]
[3][4][5][6]
Staje się:
[3][9][5][1]
[4][0][6][2]
[5][1][7][3]
[6][2][8][4]
Aktualizacja : odpowiedź Nicka jest najprostsza, ale czy istnieje sposób, aby to zrobić lepiej niż n ^ 2? Co jeśli matryca miała wymiary 10000 x 10000?
algorithm
matrix
multidimensional-array
swilliams
źródło
źródło
Odpowiedzi:
Tutaj jest w C #
źródło
ret[i][j] = matrix[j][n - i - 1]
Czas O (n ^ 2) i algorytm przestrzenny O (1) (bez żadnych obejść i cholernie dziwnych rzeczy!)
Obróć o +90:
Obróć o -90:
Metoda 1:
Metoda 2:
Obróć o +180:
Metoda 1 : Obróć dwukrotnie o +90
Metoda 2 : Odwróć każdy wiersz, a następnie odwróć każdą kolumnę (transponuj)
Obróć o -180:
Metoda 1 : Obróć dwukrotnie o -90
Metoda 2 : Odwróć każdą kolumnę, a następnie odwróć każdy wiersz
Metoda 3 : Obróć o +180, ponieważ są one takie same
źródło
rotateCW = map reverse . transpose
irotateCCW = transpose . map reverse
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:
Obracanie o 90 stopni daje nam:
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:
Macierz zostanie zdefiniowana za pomocą dwuwymiarowej tablicy:
Dlatego pierwsza pozycja indeksu uzyskuje dostęp do wiersza. Druga pozycja indeksu uzyskuje dostęp do kolumny:
Zdefiniujemy funkcję narzędziową do drukowania matrycy.
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
Matryca 4 × 4 ma 2 warstwy
Matryca 5 x 5 ma 3 warstwy
Matryca 6 × 6 ma 3 warstwy
Matryca 7 × 7 ma 4 warstwy
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:
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:
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.
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:
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:
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:
0 i 4 to także pozycje rzędów dla najbardziej zewnętrznej warstwy.
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.
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”.
Powyższy kod zapętla pozycje (wierszy i kolumn) dowolnych warstw, które wymagają obrócenia.
Mamy teraz pętlę zapewniającą pozycje wierszy i kolumn każdej warstwy. Zmienne
first
ilast
identyfikują pozycję indeksu pierwszego i ostatniego wiersza i kolumny. Wracając do naszych tabel wierszy i kolumn: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ę.
Nasza pętla warstw zawiera indeksy pierwszej i ostatniej kolumny, a także pierwszego i ostatniego wiersza:
Ponieważ nasze macierze są zawsze kwadratowe, potrzebujemy tylko dwóch zmiennych,
first
alast
ponieważ pozycje indeksu są takie same dla wierszy i kolumn.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
ilast
(bez odejmowania, dodawania lub przesunięcia tych zmiennych):Z tego powodu rozpoczynamy obrót w czterech zewnętrznych rogach - najpierw je obrócimy. Wyróżnijmy je za pomocą
*
.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 kombinacjifirst
ilast
:Dane wyjściowe powinny wynosić:
Teraz możemy dość łatwo zamienić każdy z rogów z naszej pętli warstw:
Matryca przed obracaniem narożników:
Matryca po obróceniu narożników:
Ś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:
Dane wyjściowe to:
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
ilast
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.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:
Oznacza to, że możemy użyć pojedynczej zmiennej w połączeniu ze zmiennymi
first
ilast
, 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.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:
Biorąc pod uwagę matrycę:
Nasza
rotate
funkcja powoduje:źródło
list(zip(*reversed(your_list_of_lists)))
Pyton:
i przeciwnie do ruchu wskazówek zegara:
Jak to działa:
zip(*original)
zamieni osie tablic 2d układając odpowiednie elementy z list w nowe listy. (*
Operator mówi funkcji, aby podzieliła zawarte listy na argumenty)Te
[::-1]
elementy rachunku odwraca array (patrz Rozszerzony plastry czy to pytanie ):Wreszcie połączenie tych dwóch elementów spowoduje transformację obrotu.
Zmiana umieszczenia
[::-1]
spowoduje odwrócenie list na różnych poziomach matrycy.źródło
zip(*reversed(original))
zamiast,zip(*original[::-1])
aby uniknąć tworzenia dodatkowej kopii oryginalnej listy.Oto taki, który wykonuje obrót w miejscu zamiast używać całkowicie nowej tablicy do przechowywania wyniku. Zrezygnowałem z inicjalizacji tablicy i wydrukowałem ją. Działa to tylko dla tablic kwadratowych, ale mogą mieć dowolny rozmiar. Narzut pamięci jest równy rozmiarowi jednego elementu tablicy, dzięki czemu można wykonać obrót tak dużej tablicy, jak chcesz.
źródło
Jest tu mnóstwo dobrego kodu, ale chcę tylko pokazać, co się dzieje geometrycznie, aby lepiej zrozumieć logikę kodu. Oto jak do tego podejdę.
po pierwsze, nie należy tego mylić z transpozycją, która jest bardzo łatwa ..
podstawową ideą jest traktowanie jej jak warstw i obracamy jedną warstwę naraz.
powiedzmy, że mamy 4x4
po obróceniu go o 90 zgodnie z ruchem wskazówek zegara otrzymujemy
więc rozłóżmy to, najpierw obrócimy zasadniczo 4 rogi
następnie obracamy następujący diament, który jest trochę krzywo
a następnie drugi przekrzywiony diament
tak, że dba o zewnętrzną krawędź, więc zasadniczo wykonujemy tę powłokę na raz
w końcu środkowy kwadrat (lub jeśli jest dziwny tylko ostatni element, który się nie porusza)
więc teraz zastanówmy się nad wskaźnikami każdej warstwy, załóżmy, że zawsze pracujemy z najbardziej zewnętrzną warstwą
i tak dalej, aż do połowy krawędzi
więc ogólnie wzór jest
źródło
Jak powiedziałem w poprzednim poście, oto kod w języku C #, który implementuje obrót macierzy O (1) dla macierzy dowolnego rozmiaru. Dla zwięzłości i czytelności nie ma sprawdzania błędów ani sprawdzania zasięgu. Kod:
OK, podniosę rękę, tak naprawdę nie robi żadnych modyfikacji oryginalnej tablicy podczas obracania. Ale w systemie OO nie ma znaczenia, dopóki obiekt wygląda, jakby został obrócony do klientów klasy. W tej chwili klasa Matrix używa odniesień do oryginalnych danych macierzy, więc zmiana dowolnej wartości m1 spowoduje również zmianę m2 i m3. Niewielka zmiana w konstruktorze, aby utworzyć nową tablicę i skopiować do niej wartości, rozwiąże ten problem.
źródło
Podczas gdy obracanie danych w miejscu może być konieczne (być może w celu zaktualizowania fizycznie przechowywanej reprezentacji), staje się łatwiejsze i być może bardziej wydajne, aby dodać warstwę pośrednią do dostępu do tablicy, być może interfejs:
Jeśli
Matrix
już implementujesz ten interfejs, możesz go obrócić za pomocą klasy dekoratora, takiej jak ta:Obracanie o + 90 / -90 / 180 stopni, obracanie w poziomie / w pionie i skalowanie można również osiągnąć w ten sposób.
Wydajność musiałaby być mierzona w konkretnym scenariuszu. Jednak operacja O (n ^ 2) została teraz zastąpiona wywołaniem O (1). Jest to wirtualne wywołanie metody, które jest wolniejsze niż bezpośredni dostęp do tablicy, więc zależy od tego, jak często obrócona tablica jest używana po rotacji. Jeśli zastosuje się go raz, to podejście na pewno by wygrało. Jeśli jest obrócony, a następnie używany przez długi czas w systemie, rotacja w miejscu może działać lepiej. Zależy to również od tego, czy możesz zaakceptować koszty początkowe.
Podobnie jak w przypadku wszystkich problemów z wydajnością, mierz, mierz, mierz!
źródło
To lepsza wersja w Javie: Zrobiłem to dla matrycy o innej szerokości i wysokości
Ten kod oparty jest na poście Nicka Berardi.
źródło
Rubinowy sposób:
.transpose.map &:reverse
źródło
array.reverse.transpose
obraca tablicę zgodnie z ruchem wskazówek zegara, podczas gdyarray.transpose.reverse
obraca ją przeciwnie do ruchu wskazówek zegara. Nie ma takiej potrzebymap
.Jest już wiele odpowiedzi i znalazłem dwie twierdzące, że złożoność czasowa O (1). rzeczywistym O (1) algorytm jest pozostawienie przechowywanie tablicy nietknięte i zmienić sposób wskaźnik jego elementy. Chodzi tutaj o to, aby nie zużywał dodatkowej pamięci ani nie wymagał dodatkowego czasu na iterację danych.
Obracanie o 90, -90 i 180 stopni to proste transformacje, które można wykonać, o ile wiesz, ile wierszy i kolumn znajduje się w tablicy 2D; Aby obrócić dowolny wektor o 90 stopni, zamień osie i neguj oś Y. Dla -90 stopni zamień osie i neguj oś X. Dla 180 stopni neguj obie osie bez zamiany.
Możliwe są dalsze przekształcenia, takie jak odbicie lustrzane w poziomie i / lub w pionie poprzez niezależne negowanie osi.
Można tego dokonać np. Metodą akcesora. Poniższe przykłady to funkcje JavaScript, ale pojęcia dotyczą jednakowo wszystkich języków.
Ten kod zakłada tablicę zagnieżdżonych tablic, przy czym każda tablica wewnętrzna jest rzędem.
Ta metoda umożliwia odczytywanie (lub zapisywanie) elementów (nawet w losowej kolejności), tak jakby tablica została obrócona lub przekształcona. Teraz wystarczy wybrać odpowiednią funkcję do wywołania, prawdopodobnie przez odniesienie, i gotowe!
Pojęcie to można rozszerzyć, aby zastosować transformacje w sposób addycyjny (i nieniszczący) za pomocą metod akcesorów. W tym dowolne obroty kątów i skalowanie.
źródło
Kilka osób już podało przykłady, które wymagają stworzenia nowej tablicy.
Kilka innych rzeczy do rozważenia:
(a) Zamiast faktycznie przenosić dane, po prostu przejdź w inny sposób do „obróconej” tablicy.
(b) Wykonanie obrotu w miejscu może być nieco trudniejsze. Potrzebujesz trochę miejsca na zarysowania (prawdopodobnie mniej więcej równego rozmiarowi jednego wiersza lub kolumny). Istnieje starożytna praca ACM na temat dokonywania transpozycji w miejscu ( http://doi.acm.org/10.1145/355719.355729 ), ale ich przykładowy kod to paskudny fortuna z ładunkiem.
Uzupełnienie:
http://doi.acm.org/10.1145/355611.355612 to kolejny, podobno lepszy, lokalny algorytm transpozycji.
źródło
Odpowiedź Nicka działałaby również na macierz NxM z niewielką modyfikacją (w przeciwieństwie do NxN).
Jednym ze sposobów myślenia o tym jest przeniesienie środka osi (0,0) z lewego górnego rogu do prawego górnego rogu. Po prostu transponujesz z jednego na drugi.
źródło
Czas - O (N), Spacja - O (1)
źródło
Oto moja wersja Ruby (zauważ, że wartości nie są wyświetlane tak samo, ale wciąż się obraca zgodnie z opisem).
Wyjście:
źródło
oto metoda rotacji w przestrzeni, według java, tylko dla kwadratu. w przypadku nie kwadratowej tablicy 2d i tak będziesz musiał utworzyć nową tablicę.
kod, aby obrócić tablicę 2d dowolnego rozmiaru, tworząc nową tablicę:
źródło
Implementacja pseudokodu +90 dołka (np. Transpozycja, a następnie odwrócenie każdego wiersza) w JavaScript:
źródło
Możesz to zrobić w 3 prostych krokach :
1 ) Załóżmy, że mamy matrycę
2 ) Weź transpozycję macierzy
3 ) Zamień rzędy, aby uzyskać obróconą matrycę
Kod źródłowy Java dla tego:
Wynik:
źródło
To jest moja implementacja, w złożoności pamięci C, O (1), w miejscu obrotu, 90 stopni w prawo:
źródło
Oto wersja Java:
metoda najpierw obraca najbardziej rutynową warstwę, a następnie przechodzi do warstwy wewnętrznej pośrodku.
źródło
Z liniowego punktu widzenia rozważmy macierze:
Teraz weź transpozycję A
I rozważ działanie A 'na B lub B na A'.
Odpowiednio:
Można to rozszerzyć dla dowolnej macierzy nxn. I szybkie zastosowanie tej koncepcji w kodzie:
źródło
Kod C # do obracania [n, m] Tablice 2D o 90 stopni w prawo
Wynik:
źródło
PHP:
Od PHP 5.6 transpozycja macierzy może być wykonywana za pomocą eleganckiego
array_map()
wywołania. Innymi słowy, kolumny są konwertowane na wiersze.Kod: ( Demo )
$ transponowane:
źródło
For i:= 0 to X do For j := 0 to X do graphic[j][i] := graphic2[X-i][j]
X to rozmiar tablicy, w której znajduje się grafika.
źródło
#transpose jest standardową metodą klasy Ruby's Array, a zatem:
Implementacja jest funkcją transpozycji n ^ 2 napisaną w C. Możesz ją zobaczyć tutaj: http://www.ruby-doc.org/core-1.9.3/Array.html#method-i-transpose wybierając „kliknij przełączać źródło „obok” transpozycji ”.
Pamiętam rozwiązania lepsze niż O (n ^ 2), ale tylko dla specjalnie skonstruowanych matryc (takich jak matryce rzadkie)
źródło
Kod C dla obrotu matrycy o 90 stopni w prawo W MIEJSCU dla dowolnej matrycy M * N
źródło
oto moja implementacja In Place w C
źródło
Oto moja próba rotacji matrycy o 90 stopni, która jest 2-etapowym rozwiązaniem w C. Najpierw transponuj matrycę na miejsce, a następnie zamień cols.
źródło
@dagorym: Aw, stary. Trzymałem się tego jako dobrej łamigłówki „Jestem znudzony, co mogę rozważać”. Wymyśliłem swój lokalny kod transpozycji, ale dotarłem tutaj, by znaleźć twój prawie identyczny z moim ... och, cóż. Tutaj jest w Ruby.
źródło
Prosta metoda C ++, ponieważ w dużej tablicy byłby duży narzut pamięci.
źródło