Ćwiczyłem przed zbliżającym się konkursem programistycznym i natknąłem się na pytanie, które mnie całkowicie oszołomiło. Jednak czuję, że jest to koncepcja, której powinienem się nauczyć teraz, zamiast trzymać kciuki, że nigdy się nie pojawi.
Zasadniczo chodzi o figurę rycerza na szachownicy. Masz dwa dane wejściowe: lokalizację początkową i lokalizację końcową. Celem jest obliczenie i wydrukowanie najkrótszej ścieżki, jaką może pokonać rycerz, aby dostać się do miejsca docelowego.
Nigdy nie miałem do czynienia z rzeczami o najkrótszej ścieżce i nawet nie wiem, od czego zacząć. Jaką logiką się posługuję, aby rozwiązać ten problem?
PS Jeśli ma to jakiekolwiek znaczenie, chcą, abyś uzupełnił normalne ruchy skoczka, umożliwiając mu również przesunięcie się do czterech rogów kwadratu utworzonego przez (potencjalnie) osiem ruchów, które może wykonać rycerz, biorąc pod uwagę, że środek kwadratu jest lokalizacja rycerza.
źródło
Odpowiedzi:
Masz tutaj wykres, na którym wszystkie dostępne ruchy są połączone (wartość = 1), a niedostępne ruchy są odłączone (wartość = 0), rzadka macierz wyglądałaby tak:
Najkrótszą ścieżkę dwóch punktów na wykresie można znaleźć za pomocą http://en.wikipedia.org/wiki/Dijkstra's_algorithm
Pseudo-kod ze strony wikipedia:
EDYTOWAĆ:
Introduction to Algorithms
ISBN 0-262-03384-4. Lub możesz wypróbować wikipedię, http://en.wikipedia.org/wiki/List_of_algorithmsźródło
EDYCJA: Zobacz odpowiedź Simona , gdzie poprawił przedstawiony tutaj wzór.
W rzeczywistości istnieje wzór O (1)
To jest obraz, który zrobiłem, aby go zwizualizować (kwadraty, do których rycerz może sięgnąć w N- tym ruchu, są pomalowane na ten sam kolor).
Czy widzisz tutaj wzór?
Chociaż widzimy wzór, naprawdę trudno jest znaleźć funkcję,
f( x , y )
która zwraca liczbę ruchów potrzebnych do przejścia z kwadratu( 0 , 0 )
do kwadratu( x , y )
Ale oto formuła, która działa, kiedy
0 <= y <= x
Uwaga: to pytanie zostało zadane w dniu 1 SACO 2007,
a rozwiązania są tutaj
źródło
2 * floor((y - delta) / 3) + delta
idelta - 2 * floor((delta - y) / 4)
. To jest oficjalne rozwiązanie z tej strony konkursowej, ale jest błędne. To pierwsze równanie (odif
) zwraca błędne odpowiedzi. Na szachownicy [-1000..1000] x [-1000..1000], która jest duża (ale logicznie nieograniczona) 2001x2001, dana odpowiedź liczy 2669329 z 4004001 pól (66,66%). Czy ktoś zna działające rozwiązanie bez żadnych pętli?Oto poprawne rozwiązanie O (1), ale w przypadku, gdy skoczek porusza się tylko jak skoczek w szachy i na nieskończonej szachownicy:
https://jsfiddle.net/graemian/5qgvr1ba/11/
Kluczem do znalezienia tego jest zauważenie wzorców, które pojawiają się podczas rysowania planszy. Na poniższym diagramie liczba w kwadracie to minimalna liczba ruchów potrzebna do osiągnięcia tego kwadratu (możesz użyć wyszukiwania wszerz, aby to znaleźć):
Ponieważ rozwiązanie jest symetryczne na osiach i przekątnych, narysowałem tylko przypadek x> = 0 i y> = x.
Lewy dolny blok jest pozycją początkową, a liczby w blokach reprezentują minimalną liczbę ruchów, aby dotrzeć do tych bloków.
Należy zwrócić uwagę na 3 wzorce:
(Upewnij się, że oba zestawy przekątnych są widoczne od lewej górnej do prawej dolnej. Mają stałą liczbę ruchów. Przekątne w lewym górnym rogu są znacznie bardziej złożone).
Możesz uzyskać formuły dla każdego. Żółte bloki to szczególne przypadki. Tak więc rozwiązaniem jest:
z najtrudniejszymi grupami pionowymi:
Zobacz skrzypce w innych przypadkach.
Może są jakieś prostsze lub bardziej eleganckie wzory, które przegapiłem? Jeśli tak, bardzo chciałbym je zobaczyć. W szczególności dostrzegam pewne ukośne wzory w niebieskich pionowych obudowach, ale ich nie zbadałem. Niezależnie od tego, to rozwiązanie nadal spełnia ograniczenie O (1).
źródło
Bardzo ciekawy problem, z którym ostatnio się spotkałem. Po patrząc pewne rozwiązania I próbowano odzyskać formułę analityczną (
O(1) time and space complexity
) podany na SACO 2007 Dzień 1 rozwiązań .Przede wszystkim chcę docenić Graeme Pyle za bardzo ładną wizualizację, która pomogła mi naprawić formułę.
Z jakiegoś powodu (może dla uproszczenia, piękna lub po prostu pomyłka) przenieśli się
minus
dofloor
operatora, w wyniku czego otrzymali złą formułęfloor(-a) != -floor(a) for any a
.Oto poprawna formuła analityczna:
Formuła działa dla wszystkich par (x, y) (po zastosowaniu osi i symetrii ukośnej) z wyjątkiem przypadków narożnych (1,0) i (2,2), które nie spełniają wzoru i są zakodowane na stałe w następującym fragmencie:
function distance(x,y){ // axes symmetry x = Math.abs(x); y = Math.abs(y); // diagonal symmetry if (x < y) { t = x;x = y; y = t; } // 2 corner cases if(x==1 && y == 0){ return 3; } if(x==2 && y == 2){ return 4; } // main formula var delta = x-y; if(y>delta){ return delta - 2*Math.floor((delta-y)/3); } else{ return delta - 2*Math.floor((delta-y)/4); } } $body = $("body"); var html = ""; for (var y = 20; y >= 0; y--){ html += '<tr>'; for (var x = 0; x <= 20; x++){ html += '<td style="width:20px; border: 1px solid #cecece" id="'+x+'_'+y+'">'+distance(x,y)+'</td>'; } html += '</tr>'; } html = '<table>'+html+'</table>'; $body.append(html);
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
Uwaga: jQuery służy tylko do ilustracji, kod patrz
distance
funkcja.źródło
Tak, Dijkstra i BFS dadzą ci odpowiedź, ale myślę, że szachowy kontekst tego problemu dostarcza wiedzy, która może dać rozwiązanie, które jest znacznie szybsze niż ogólny algorytm najkrótszej ścieżki, szczególnie na nieskończonej szachownicy.
Dla uproszczenia opiszmy szachownicę jako płaszczyznę (x, y). Celem jest znalezienie najkrótszej ścieżki od (x0, y0) do (x1, y1) przy użyciu tylko kroków kandydujących (+ -1, + -2), (+ -2, + -1) i (+ -2 , + -2), jak opisano w PS pytania
Oto nowa obserwacja: narysuj kwadrat z narożnikami (x-4, y-4), (x-4, y + 4), (x + 4, y-4), (x + 4, y + 4) . Ten zbiór (nazwij go S4) zawiera 32 punkty. Najkrótsza droga od dowolnego z tych 32 punktów do (x, y) wymaga dokładnie dwóch ruchów .
Najkrótsza ścieżka z dowolnego z 24 punktów w zbiorze S3 (zdefiniowana podobnie) do (x, y) wymaga co najmniej dwóch ruchów .
Dlatego, jeśli | x1-x0 |> 4 lub | y1-y0 |> 4, najkrótsza ścieżka od (x0, y0) do (x1, y1) jest dokładnie o dwa ruchy większa niż najkrótsza ścieżka od (x0, y0) do S4. Ten ostatni problem można szybko rozwiązać za pomocą prostej iteracji.
Niech N = max (| x1-x0 |, | y1-y0 |). Jeśli N> = 4, to najkrótsza ścieżka od (x0, y0) do (x1, y1) ma ceil (N / 2) kroki.
źródło
Powyższa odpowiedź O (1) [ https://stackoverflow.com/a/8778592/4288232 autor: Mustafa Serdar Şanlı] tak naprawdę nie działa. (Sprawdź (1,1) lub (3,2) lub (4,4), pomijając oczywiste skrajne przypadki (1,0) lub (2,2)).
Poniżej znajduje się dużo brzydsze rozwiązanie (python), które działa (z dodanymi „testami”):
źródło
above
lubbelow
nie działa na SO.To, co musisz zrobić, to pomyśleć o możliwych ruchach skoczka jako o wykresie, gdzie każda pozycja na planszy jest węzłem, a możliwe ruchy na inną pozycję jako krawędź. Nie ma potrzeby stosowania algorytmu Dijkstry, ponieważ każda krawędź ma taką samą wagę lub odległość (wszystkie są równie łatwe lub krótkie). Możesz po prostu przeprowadzić wyszukiwanie BFS od punktu początkowego do pozycji końcowej.
źródło
Rozwiązanie od pierwszych zasad w Pythonie
Po raz pierwszy napotkałem ten problem w teście Codility. Dali mi 30 minut na rozwiązanie tego problemu - zajęło mi znacznie więcej czasu, zanim doszedłem do tego wyniku! Problem polegał na tym, że ile ruchów potrzebuje skoczek, aby przejść od 0,0 do x, y używając tylko legalnych ruchów skoczka. x i y były mniej więcej nieograniczone (więc nie mówimy tutaj o prostej szachownicy 8x8).
Chcieli rozwiązania O (1). Chciałem rozwiązania, w którym program wyraźnie rozwiązuje problem (tj. Chciałem czegoś bardziej oczywistego niż wzorzec Graeme'a - wzorce mają zwyczaj załamania się tam, gdzie nie patrzysz) i naprawdę nie chciałem polegać na bezsporna formuła, jak w rozwiązaniu Mustafy
Więc oto moje rozwiązanie, jakie jest warte. Zacznij, tak jak inni, zauważając, że rozwiązanie jest symetryczne względem osi i przekątnych, więc musimy rozwiązać tylko dla 0> = y> = x. Dla uproszczenia wyjaśnienia (i kodu) zamierzam odwrócić problem: skoczek zaczyna od x, y i celuje w 0,0.
Załóżmy, że ograniczymy problem do okolic źródła. W odpowiednim czasie dojdziemy do tego, co właściwie oznacza `` zwycięstwo '', ale na razie zapiszmy kilka rozwiązań w ściągawce (początek w lewym dolnym rogu):
Zatem mając x, y na siatce, możemy po prostu odczytać liczbę ruchów do początku.
Jeśli zaczęliśmy poza polem startowym, musimy wrócić do tego. Wprowadzamy „linię środkową”, która jest linią reprezentowaną przez y = x / 2. Każdy skoczek znajdujący się w pozycji x, y w tej linii może wrócić do ściągawki, wykonując serię ruchów na godzinę 8 (czyli: (-2, -1) ruchów). Jeśli x, y leży powyżej linii środkowej, będziemy potrzebować kolejnych ruchów na godzinie 8 i 7, a jeśli leży poniżej linii środkowej, będziemy potrzebować sekwencji o godzinie 8 i 10. zegar się rusza. Dwie rzeczy, na które należy zwrócić uwagę:
Spójrzmy więc na ruchy powyżej linii środkowej. Twierdzimy, że:
(dx; dy) = (2,1; 1,2) (n8; n7) (notacja macierzowa, bez składu matematycznego - wektor kolumnowy (dx; dy) równa się macierzy kwadratowej pomnożonej przez wektor kolumnowy (n8; n7) - ilość ruchów na godz. 8 i ilość ruchów na godz. 7) i podobnie;
(dx; dy) = (2,2; 1, -1) (n8; n10)
Twierdzę, że dx, dy będzie w przybliżeniu równe (x, y), więc (x-dx, y-dy) będzie w pobliżu źródła (jakiekolwiek „sąsiedztwo” się okaże).
Dwa wiersze w kodzie, które obliczają te terminy, są rozwiązaniem tych warunków, ale zostały wybrane tak, aby miały kilka przydatnych właściwości:
(Czy chciałbyś mieć na to dowody?) Tak więc odległość skoczka będzie sumą n7, n8, n10 i ściągawki [x-dx, y-dy], a nasza ściągawka sprowadza się do tego:
To jeszcze nie koniec historii. Spójrz na 3 w dolnym rzędzie. Jedyne sposoby, w jakie możemy to osiągnąć, to:
Podobną optymalizację można zastosować z 4 w prawym górnym rogu. Oprócz rozpoczęcia w tym miejscu, jedynym sposobem na to jest przejście z (4,3) o godzinie ósmej. Tego nie ma w ściągawce, ale gdyby tak było, jego odległość wyniosłaby 3, ponieważ zamiast tego moglibyśmy mieć godzinę 7 do (3,1), która ma odległość tylko 2. Więc powinniśmy cofnąć się o jeden Ruch na godz. Ósmej, a następnie o godz. 7 do przodu.
Musimy więc dodać jeszcze jeden numer do ściągawki:
(Uwaga: istnieje cały ładunek optymalizacji śledzenia wstecznego z (0,1) i (0,2), ale ponieważ solver nigdy nas tam nie zabierze, nie musimy się o nie martwić.)
Oto więc kod Pythona do oceny tego:
Nawiasem mówiąc, jeśli chcesz poznać rzeczywistą trasę, ten algorytm również to zapewnia: jest to po prostu ciąg n7 ruchów na godzinę 7, po których następują (lub przeplatane) n8 ruchów na godzinę 8, n10 10- porusza się godzina i jakikolwiek taniec jest dyktowany przez ściągawkę (która sama może znajdować się w ściągawce).
Teraz: jak udowodnić, że to prawda. Nie wystarczy porównać tych wyników z tabelą poprawnych odpowiedzi, ponieważ sam problem jest nieograniczony. Ale możemy powiedzieć, że jeśli odległość skoczka kwadratu s wynosi d, to jeśli {m} jest zbiorem prawidłowych ruchów z s, odległość skoczka (s + m) musi wynosić d-1 lub d + 1 dla wszystkich m. (Potrzebujesz na to dowodu?) Ponadto musi istnieć co najmniej jeden taki kwadrat o odległości d-1, chyba że s jest początkiem. Możemy więc udowodnić poprawność, pokazując tę właściwość dla każdego kwadratu. A zatem:
Alternatywnie, możemy udowodnić poprawność dowolnego kwadratu s, ścigając trasę od s w dół do początku. Najpierw sprawdź s pod względem zasadności jak powyżej, a następnie wybierz dowolne s + m takie, że odległość (s + m) == d-1. Powtarzaj, aż dotrzemy do początku.
Howzat?
źródło
Nie studiowałem jeszcze wykresów… biorąc pod uwagę problem implementacji go za pomocą prostych tablic, nie mogłem znaleźć innego rozwiązania niż to. Potraktowałem pozycje nie jako rangi i pliki (zwykły zapis szachowy), ale jako indeksy tablicowe. FYI, to jest tylko dla szachownicy 8 * 8. Wszelkie porady dotyczące ulepszeń są zawsze mile widziane.
* Komentarze powinny wystarczyć do zrozumienia logiki. Zawsze możesz jednak zapytać.
* Sprawdzone na kompilatorze DEV-C ++ 4.9.9.2 (oprogramowanie Bloodshed).
źródło
Myślę, że to też może ci pomóc ...
i używając programowania dynamicznego, aby uzyskać rozwiązanie.
PS: Trochę używa BFS bez konieczności zadeklarowania węzłów i krawędzi grafu.
źródło
Oto rozwiązanie tego konkretnego problemu zaimplementowane w Perlu. Pokaże jedną z najkrótszych ścieżek - w niektórych przypadkach może być więcej niż jedna.
Nie korzystałem z żadnego z opisanych wyżej algorytmów - ale fajnie byłoby porównać to z innymi rozwiązaniami.
źródło
źródło
Po prostu kod ruby z odpowiedzi jsfiddle Graeme'a Pyle'a powyżej , rozłożony na cały dodatkowy kod i przekonwertowany pozostały do ruby tylko po to, aby uzyskać rozwiązanie przez jego algorytm, wydaje się działać. Jednak wciąż testujemy:
Jedynym zamiarem jest zaoszczędzenie komuś czasu na konwersji kodu, jeśli ktoś potrzebuje pełnego kodu.
źródło
oto wersja PHP funkcji Julesa Maya
źródło
Oto mój program. To nie jest idealne rozwiązanie. Istnieje wiele zmian do wprowadzenia w funkcji rekursji. Ale ten efekt końcowy jest doskonały. Próbowałem trochę zoptymalizować.
źródło
Oto wersja C oparta na kodzie Mustafa Serdar Şanlı, który działa dla skończonej tablicy:
Przetestuj tutaj, sprawdzając rozwiązanie rekurencyjne
źródło