Zadanie 1: Przeszukiwanie przestrzeni stanów
Transkrypt
Zadanie 1: Przeszukiwanie przestrzeni stanów
Informatyka, studia dzienne, inż. I st. semestr VI Sztuczna inteligencja i systemy eksperotwe 2010/2011 Prowadzący: mgr inż. Jacek Pintera piątek, 11:15 Data oddania: Ocena: Sebastian Drabiński Paweł Tarasiuk 150856 151021 Zadanie 1: Przeszukiwanie przestrzeni stanów∗ 1. Cel Celem ćwiczenia laboratoryjnego było opanowanie problematyki przeszukiwania przestrzeni stanów, w szczególności w przypadku braku dostępu do jawnej postaci grafu. Prezentowanym w ćwiczeniu zastosowaniem metod przeszukiwania jest rozwiązywanie puzzli zwanych potocznie “piętnastką”. W ramach ćwiczenia przygotowany został zgodny z zaleceniami projekt napisany w języku C++ oraz drobne narzędzie służące do wizualizacji znalezionych rozwiązań, napisane w języku Python 2 z wykorzystaniem dostępnych dla tego języka bindingów biblioteki GTK+ w wersji drugiej. 2. Wprowadzenie Stworzony projekt rozwiązuje „piętnastkę” (czyli płaskie prostokątne puzzle składające się z prostokątnych elementów i jednego pustego pola, które można przesuwać) w oparciu o metody przeszukiwania grafu. Każdy układ elementów na planszy (wraz z pustym polem) nazywamy stanem układanki. Stanami sąsiednimi wobec danego stanu są wszystkie te stany, które można osiągnąć wykonując pojedynczy ruch, czyli zamieniając miejscami puste pole z elementem posiadającym z nim wspólną krawędź. Stanem końcowym nazywamy układankę ułożoną, co oznacza że elementy muszą być umieszczone na ∗ SVN: pietnastka@ https://serce.ics.p.lodz.pl/svn/labs/sise/jp_pt1115/SebPab/ 1 układance po kolei, a puste pole znajduje się w prawym dolnym rogu układanki. W prawdziwych piętnastkach często rozwiązaniem jest ułożenie z puzzli obrazka - dla uproszczenia jednak w niniejszych rozważaniach nadamy elementom numery, tak aby naszym celem było ich posortowanie (przy pustym polu umieszczonym na końcu układanki). Przez rozwiązanie układanki rozumieć będziemy wskazanie sekwencji ruchów, która pozwoli doprowadzić od danego stanu początkowego do ułożonej układanki. Możliwymi stanami układanki (rozumiemy tutaj wszystkie takie stany, które można osiągnąć wysypując z niej elementy i wstawiając je ręcznie - w dalszej części tej sekcji sprawozdania wyjaśnimy, że dokładnie połowa z nich umożliwia rozwiązanie układanki) są wszystkie permutacje elementów na planszy. Dla układanki mającej w wierszy oraz k kolumn, mamy zatem n = w ∗ k elementów układanki (licząc element pusty), oraz n! możliwych stanów. Ze względu na architekturę rozwiązujących problem komputerów, istotne dla nas będą spostrzeżenia 12! < 232 < 13! oraz 20! < 264 < 21!. Traktując stan układanki jako wierzchołek grafu, zaś dozwolony pojedynczy ruch - jako krawędź, możemy zauważyć, że jeżeli istnieje możliwość rozwiązania układanki o danym stanie początkowym, to stan końcowy musi znajdować się w tej samej spójnej składowej grafu, co stan początkowy. Można zatem szukać stanu końcowego w oparciu o klasyczne metody przeszukiwania grafu: przeszukiwanie grafu wgłąb (DFS) oraz przeszukiwanie grafu wszerz (BFS). Szybkość szukania rozwiązania można poprawić wybierając kolejność przeszukiwanych wierzchołków w sposób nietrywialny, mający na celu odrzucanie przeszukiwania niepotrzebnych części grafu i preferowanie stanów bliższych rozwiązaniu - są to metody heurystyczne, które także zostały zaimplementowane w ramach ćwiczenia. Graf stanów układanki o rozmiarach większych niż 1x2 posiada dwie spójne składowe, zawierające równe liczby wierzchołków. Dwa stany układanki należą do tej samej spójnej składowej, jeżeli po przesunięciu pustego pola w to samo miejsce w obu z nich (w naszej implementacji zawsze przesuwamy puste pole w prawy dolny róg) permutacje pozostałych elementów w obu stanach będą miały tą samą parzystość (czyli liczba inwersji umożliwiająca sprowadzenie jednego ze stanów do drugiego będzie parzysta). Łatwo zauważyć, że przy wykonywaniu dozwolonego ruchu na układance, przynależność do tak zdefiniowanego zbioru jest niezmiennikiem. W celu odczytywania sekwencji ruchów stanowiących rozwiązania, dla każdego rozważonego stanu potrzebujemy informacji o poprzedniku, z którego do niego dotarliśmy (albo - równoważnie - o ruchu który nas do danego stanu doprowadził). Ponadto, w celu uniknięcia rozważania tych samych stanów wiele razy, musimy posiadać informację o zbiorze stanów odwiedzonych. 3. Opis implementacji Kluczową częścią projektu są źródła w języku C++, z których budowany jest plik wykonywalny, obsługujący zgodne z instrukcją do ćwiczenia parametry wykonania za pomocą biblioteki getopt. Program, w zależności od argumentów i rozmiarów planszy, dobiera algorytm do rozwiązywania jej. 2 Dostępne są algorytmy BFS oraz DFS - każdy z nich w dwóch wersjach: wersji klasycznej wykorzystującej kontenery STL (mapa dla poprzedników + informacji o odwiedzeniu, oraz stack/queue jako kolejka), oraz w wersji zoptymalizowanej pod kątem pamięci wykorzystującej wyłącznie zwykłe tablice. Druga ze wspomnianych wersji charakteryzuje się tym, że pozwala rozwiązać dowolną układankę do 12 elementów, nie przekraczając przy tym 2, 5 GB wykorzystanej pamięci operacyjnej. Pozostałe algorytmy pozwalają natomiast na dokonanie próby zmierzenia się z większymi układankami - do 20 elementów włącznie. Tak postawione limity wynikają z tego, że dla układanek do 12 elementów można zbudować w pamięci tablice, które dla każdego elementu będą zawierały informację o ruchu, jaki do niego doprowadził (8 bitów) oraz o poprzedniku w kolejce (w tym wypadku - 32 bity, bo jeżeli liczba elementów nie przekracza 12, to numer stanu nie przekracza 231 ). Limit „do 20 elementów włącznie” w przypadku pozostałych algorytmów wynika natomiast z tego, że korzystamy intensywnie z możliwości zapisywania numerów stanów w postaci pojedynczej liczby, a największy typ liczbowy natywnie obsługiwany przez maszyny testowe ma 64 bity. Zmiana implementacji byłaby możliwa, ale kłopotliwa, gdyż użycie gotowych bibliotek do dużych liczb drastycznie zwiększyłoby zużycie pamięci, zaś użycie tablic - znacznie podwyższyłoby czasy wykonania. Zastosowane rozwiązanie pozwoliło osiągnąć wydajność pozwalającą na wykonanie dużych testów, co ma naszym zdaniem większą wartość badawczą niż rozważanie odrobinę większych układanek połączone z mniejszą liczbą wykonanych testów (która byłaby skutkiem dłuższych czasów wykonania naszego programu). Zważywszy, że nie każdą układankę udało się rozwiązać przy posiadanych zasobach, uważamy że możliwy rozmiar układanek jest wystarczający dla analizy naszej implementacji. Poza naiwnymi metodami przeszukiwania grafu, zaimplementowane zostały dwie specyficzne dla problemu niniejszej układanki metody heurystyczne. Posiadają one wspólny szkielet, przypominający ideę działania algorytmu Dijkstry. Tworzony jest kopiec ze stanami sąsiednimi względem przeszukanych, gdzie o priorytecie danego stanu decyduje wartość funkcji przydzielającej mu punkty. Założenia wobec takiej funkcji są proste - im bliżej od danego stanu jest do rozwiązania układanki, tym więcej powinien on otrzymać punktów. Nawet tak naiwne podejście okazało się skuteczne (zostanie to wyjaśnione w wynikach testów i ich omówieniu). Pierwsza funkcja nadająca priorytet stanom jest bardzo prosta - oblicza ona sumę odległości w metryce miejskiej każdego elementu układanki od pola, w którym powinien on się znajdować. Brzmi to niesamowicie naiwnie, a jednak na losowych testach okazuje się mieć miażdżącą przewagę nad metodami BFS oraz DFS. Druga funkcja używana do heurystyk została opracowana poprzez metodę prób i błędów wspomaganą naszym aparatem intuicyjnym oraz obserwacjami przebiegu rozwiązywania dla różnych funkcji liczących punkty (to jest po prostu najlepsza funkcja, jaką udało się nam wymyślić). Pierwszym kryterium do przydzielania punktów jest powierzchnia sumy ułożonych od góry wierszy oraz ułożonych od lewej strony kolumn - oznacza to po prostu dążenie do redukcji rozmiarów układanki, do pewnego prostokąta zawierającego jej prawy dolny róg. Kryterium drugorzędnym jest pomiar liczby elementów z najbliż3 szego układanego wiersza/kolumny, których odległość miejska od miejsca w układance nie przekracza 1 (pod uwagę brany jest wiersz lub kolumna, w zależności od tego, jak dużą część całości wiersza/kolumny stanowi liczba spełniających kryterium elementów). Jeżeli pozostały do ułożenia tylko dwie kolumny bądź dwa wiersze, do punktacji w drugim kryterium brany jest także pod uwagę jeden element układanego wiersza/kolumny, który znajduje się w promieniu 2 (w metryce miejskiej) od swojego miejsca przeznaczenia. Pesymistyczna złożoność czasowa wszystkich metod jest równa liczbie wszystkich osiągalnych stanów, czyli O(n!), gdzie n oznacza liczbę pól układanki. Ustawiona na sztywno złożoność pamięciowa metod DFS i BFS do 12 pól (opartych o tablice) wynosiła O(n!) (przy czym wartość wyrażenia n! jest tu mnożona przez niedużą stałą). W przypadku pozostałych metod złożoność pamięciowa oraz obliczeniowa wynosi O(n! log(n!)), przy stałej większej niż w przypadku tablic - jednak zważywszy, że nie każdy przypadek jest pesymistyczny, nawet BFS i DFS zaimplementowane w oparciu o taki model mogą się okazać przydatne (szczególnie BFS). Złożoność ta dotyczy także heurystyk, które mimo tak opisanych złożoności pesymistycznych są bez porównania szybsze i wymagają mniej pamięci niż BFS i DFS oparte na tablicach wynika to z faktu, iż heurystyki ze statystycznego punktu punktu widzenia nie mają szans na rozważenie istotnie dużej części stanów osiągalnych. Zważywszy, że obaj autorzy niniejszej implementacji pracują na systemach opartych o jądro Linux ze standardową obsługą wirtualnego systemu plików /proc, zaprojektowane metody sprawdzania dostępności wystarczającej ilości wolnej pamięci operacyjnej są oparte na analizie wirtualnego pliku /proc/meminfo. Podmiana odpowiednich procedur na inne wersje w przypadku innych systemów operacyjnych mogłaby być łatwo osiągnięta za pomocą odpowiednich dyrektyw preprocesora. 4. Materiały i metody Badania wykonywane były na testach, które można sklasyfikować w następujący sposób: — małych (poniżej 12 pól) - takich testów można było wykonać dużo, każdy algorytm da sobie z nimi radę — średnich (12 pól) - wykonywanie ich na algorytmach BFS/DFS to koszmar, ale jest technicznie możliwe - mimo czasów sięgających nawet godziny, wykonaliśmy kilka takich testów — dużych (12-20 pól) - dla pokazania, jak bardzo udana jest najlepsza heurystyka — łatwych (do 20 pól, 10 losowych ruchów wykonanych na stanie końcowym) - nawet jeżeli były duże, mogły być rozważane algorytmem BFS, a jeżeli były średnie - łatwą heurystyką Spośród każdego rodzaju testów (poza łatwymi, które są grupą specyficzną) przygotowane zostały trzy testy losowe i jeden standardowy, tzn. z pustym polem w lewym górnym rogiem układanki i pozostałymi elementami posortowanymi rosnąco. Jeżeli tak opisany układ był nierozwiązywalny, dokonywano na nim inwersji pomiędzy elementami z numerami 1 oraz 2. 4 W przypadku testów średnich nie-łatwych, metody BFS oraz DFS zostały wykonane tylko na układzie standardowym oraz pierwszym układzie losowym, z kolejnościami przeglądania: jedną narzuconą oraz losową. W przypadku pozostałych testów, jeżeli metody DFS/BFS były brane pod uwagę, wykonywano je dla trzech różnych narzuconych kolejności przeszukiwania oraz dla kolejności losowej. 5 5. Wyniki Poniższa tabela zawiera informacje o wszystkich wykonanych testach. Czasy podane są w sekundach, z dokładnością do jednej setnej sekundy. Test 2x2 smpl1 2x2 smpl2 2x2 smpl3 2x2 rand1 2x2 rand2 2x2 rand3 2x2 std 2x3 smpl1 2x3 smpl2 2x3 smpl3 DLGP DPLG LDPG random LDGP LGPD PDGL random DPLG PDLG PLGD random GLDP LDGP LPDG random DPGL LDPG LGPD random DLPG GLDP PLDG random LDPG PDGL PLGD random DLPG GLDP PDLG random DGLP DLGP PDGL random DPGL GDPL PLDG random BFS Ruchy 2 2 2 2 2 2 2 2 2 2 2 2 4 4 4 4 0 0 0 0 0 0 0 0 2 2 2 2 10 10 10 10 10 10 10 10 10 10 10 10 Czas 0, 00 0, 00 0, 00 0, 00 0, 00 0, 00 0, 00 0, 00 0, 00 0, 00 0, 00 0, 00 0, 00 0, 00 0, 00 0, 00 0, 00 0, 00 0, 00 0, 00 0, 00 0, 00 0, 00 0, 00 0, 00 0, 00 0, 00 0, 00 0, 00 0, 00 0, 00 0, 00 0, 00 0, 00 0, 00 0, 00 0, 00 0, 00 0, 00 0, 00 DLGP DPLG LDPG random LDGP LGPD PDGL random DPLG PDLG PLGD random GLDP LDGP LPDG random DPGL LDPG LGPD random DLPG GLDP PLDG random LDPG PDGL PLGD random DLPG GLDP PDLG random DGLP DLGP PDGL random DPGL GDPL PLDG random DFS Ruchy 10 10 10 2 10 2 2 10 2 10 10 10 4 8 8 8 0 0 0 0 0 0 0 0 2 10 10 2 92 152 186 40 162 170 34 28 46 46 20 184 6 Czas 0, 00 0, 00 0, 00 0, 00 0, 00 0, 00 0, 00 0, 00 0, 00 0, 00 0, 00 0, 00 0, 00 0, 00 0, 00 0, 00 0, 00 0, 00 0, 00 0, 00 0, 00 0, 00 0, 00 0, 00 0, 00 0, 00 0, 00 0, 00 0, 00 0, 00 0, 00 0, 00 0, 00 0, 00 0, 00 0, 00 0, 00 0, 00 0, 00 0, 00 Heurystyka 1. Ruchy Czas Heurystyka 2. Ruchy Czas 2 0, 00 2 0, 01 2 0, 00 2 0, 00 2 0, 00 2 0, 01 8 0, 00 4 0, 00 0 0, 00 0 0, 00 0 0, 00 0 0, 01 2 0, 00 2 0, 00 10 0, 00 10 0, 01 14 0, 00 10 0, 00 20 0, 00 10 0, 01 Test 2x3 rand1 2x3 rand2 2x3 rand3 2x3 std 2x4 smpl1 2x4 smpl2 2x4 smpl3 2x4 rand1 2x4 rand2 2x4 rand3 2x4 std 3x3 smpl1 DLPG LPDG PDGL random GDPL GLPD LPDG random GDPL LDGP PLDG random DGLP LGPD PLDG random LDPG LGPD PDLG random LGPD LPGD PDGL random PGDL PGLD PLGD random GDPL LPGD PDLG random DLGP LPDG PDGL random PDLG PGDL PLDG random DLGP DLPG LGPD random DLPG GDLP LDPG random BFS Ruchy 20 20 20 20 16 16 16 16 18 18 18 18 15 15 15 15 10 10 10 10 10 10 10 10 10 10 10 10 26 26 26 26 30 30 30 30 14 14 14 14 22 22 22 22 10 10 10 10 Czas 0, 00 0, 00 0, 00 0, 00 0, 00 0, 00 0, 00 0, 00 0, 00 0, 00 0, 00 0, 00 0, 00 0, 00 0, 00 0, 00 0, 00 0, 00 0, 00 0, 00 0, 00 0, 00 0, 00 0, 00 0, 00 0, 00 0, 00 0, 00 0, 14 0, 13 0, 13 0, 14 0, 17 0, 17 0, 17 0, 17 0, 01 0, 01 0, 01 0, 01 0, 07 0, 07 0, 07 0, 08 0, 01 0, 01 0, 01 0, 01 DLPG LPDG PDGL random GDPL GLPD LPDG random GDPL LDGP PLDG random DGLP LGPD PLDG random LDPG LGPD PDLG random LGPD LPGD PDGL random PGDL PGLD PLGD random GDPL LPGD PDLG random DLGP LPDG PDGL random PDLG PGDL PLDG random DLGP DLPG LGPD random DLPG GDLP LDPG random DFS Ruchy 186 216 80 228 178 168 258 194 142 114 182 28 79 121 313 45 688 168 800 9938 7538 12670 90 10594 76 76 3088 12408 7106 17172 13440 10066 11468 4996 1004 7638 2694 6350 5068 7536 7994 8170 4452 3196 41648 104624 101018 84260 7 Czas 0, 00 0, 00 0, 00 0, 00 0, 00 0, 01 0, 00 0, 01 0, 00 0, 00 0, 00 0, 00 0, 00 0, 01 0, 00 0, 00 0, 01 0, 00 0, 01 0, 23 0, 27 0, 13 0, 00 0, 12 0, 00 0, 00 0, 03 0, 19 0, 09 0, 18 0, 15 0, 11 0, 16 0, 05 0, 01 0, 08 0, 03 0, 07 0, 05 0, 08 0, 09 0, 26 0, 04 0, 03 3, 26 1, 43 1, 58 1, 07 Heurystyka 1. Ruchy Czas Heurystyka 2. Ruchy Czas 32 0, 00 20 0, 00 44 0, 00 16 0, 00 24 0, 00 18 0, 00 27 0, 00 15 0, 00 14 0, 05 14 0, 01 10 0, 00 16 0, 00 10 0, 03 10 0, 00 38 0, 05 26 0, 01 30 0, 05 30 0, 03 22 0, 05 14 0, 00 44 0, 05 28 0, 01 52 0, 55 10 0, 00 Test 3x3 smpl2 3x3 smpl3 3x3 rand1 3x3 rand2 3x3 rand3 3x3 std 2x5 smpl1 2x5 smpl2 2x5 smpl3 2x5 rand1 2x5 rand2 2x5 rand3 GPLD LDGP LGDP random LDGP LGDP PDLG random DGLP GPDL GPLD random DLGP LGPD LPGD random DPGL LDPG LPGD random DLPG GLPD LDPG random DPLG LGPD PGLD random DGLP DGPL LGPD random GPLD LDGP LDPG random DLGP GDPL PGLD random DPLG LDGP PDGL random GDPL GLDP LGPD random BFS Ruchy 10 10 10 10 8 8 8 8 20 20 20 20 22 22 22 22 22 22 22 22 22 22 22 22 10 10 10 10 10 10 10 10 10 10 10 10 38 38 38 38 44 44 44 44 34 34 34 34 Czas 0, 01 0, 01 0, 01 0, 01 0, 00 0, 00 0, 00 0, 00 0, 50 0, 48 0, 49 0, 56 0, 87 0, 85 0, 82 0, 85 1, 05 0, 87 0, 87 0, 93 0, 96 0, 93 0, 95 0, 96 0, 01 0, 01 0, 01 0, 01 0, 01 0, 01 0, 01 0, 01 0, 01 0, 01 0, 01 0, 01 14, 61 14, 37 14, 49 14, 83 20, 12 20, 12 20, 18 20, 22 9, 40 9, 03 8, 94 9, 28 GPLD LDGP LGDP random LDGP LGDP PDLG random DGLP GPDL GPLD random DLGP LGPD LPGD random DPGL LDPG LPGD random DLPG GLPD LDPG random DPLG LGPD PGLD random DGLP DGPL LGPD random GPLD LDGP LDPG random DLGP GDPL PGLD random DPLG LDGP PDGL random GDPL GLDP LGPD random DFS Ruchy 68878 1508 86686 10 59794 90836 25926 111346 114612 88792 89586 104782 79104 102052 113368 84700 41500 48316 75296 26408 90096 67848 28562 103278 166 1309326 3524 271840 674272 68 1338780 804030 170744 1064356 1316942 603524 1122958 87496 103868 969852 50480 1122492 934088 505816 228148 977418 1319216 1135708 8 Czas 0, 87 0, 02 2, 10 0, 00 2, 85 1, 88 0, 30 1, 87 1, 63 1, 12 1, 29 1, 49 2, 48 1, 66 1, 78 1, 06 0, 48 0, 56 2, 64 0, 28 1, 97 2, 71 3, 36 2, 31 0, 00 23, 66 0, 05 3, 21 10, 51 0, 00 21, 25 28, 37 2, 12 25, 99 23, 10 7, 49 19, 35 1, 38 1, 24 13, 47 0, 63 20, 08 11, 59 6, 05 3, 56 12, 75 22, 66 18, 19 Heurystyka 1. Ruchy Czas Heurystyka 2. Ruchy Czas 22 0, 57 18 0, 01 68 0, 54 8 0, 00 88 0, 56 20 0, 12 44 0, 55 26 0, 02 60 0, 56 26 0, 03 46 0, 55 24 0, 01 10 1, 13 10 0, 00 12 1, 64 10 0, 00 32 1, 67 10 0, 01 230 1, 63 48 0, 30 122 1, 63 52 1, 03 116 1, 63 50 0, 25 Test 2x5 std 2x6 smpl1 2x6 smpl2 2x6 smpl3 2x6 rand1 DPLG GLDP LDGP random GDLP PDLG PGDL random GDPL PDGL PLGD random DGLP GPLD PDGL random PDLG random BFS Ruchy 31 31 31 31 10 10 10 10 10 10 10 10 10 10 10 10 50 50 Czas 4, 90 4, 86 4, 83 4, 61 0, 01 0, 02 0, 01 0, 02 0, 01 0, 02 0, 02 0, 02 0, 02 0, 02 0, 02 0, 02 1729, 77 1843, 69 DPLG GLDP LDGP random PDLG random DFS Ruchy 80807 842495 1007471 1019415 91798194 109345918 Czas 1, 00 29, 45 12, 84 14, 98 1267, 77 1612, 20 2x6 rand2 2x6 rand3 2x6 std 3x4 smpl1 3x4 smpl2 3x4 smpl3 3x4 rand1 LPDG random DPLG GDLP PLDG random GDLP LGPD LPGD random DGPL PLDG PLGD random LDPG random 38 38 10 10 10 10 10 10 10 10 10 10 10 10 28 28 230, 40 244, 30 0, 04 0, 02 0, 04 0, 03 0, 03 0, 03 0, 03 0, 03 0, 03 0, 03 0, 03 0, 02 182, 92 268, 39 LPDG random LDPG random 120894456 135219330 11766504 89400020 1637, 33 2123, 99 174, 53 1285, 46 3x4 rand2 3x4 rand3 3x4 std 2x7 smpl1 2x7 smpl2 PGDL random DLGP GLDP PDGL random LDGP LPGD PGLD random 33 33 10 10 10 10 10 10 10 10 959, 08 1037, 63 0, 02 0, 02 0, 02 0, 02 0, 02 0, 02 0, 02 0, 01 PGDL random 136509929 152366351 9 2769, 31 3533, 84 Heurystyka 1. Ruchy Czas Heurystyka 2. Ruchy Czas 101 1, 68 31 0, 02 70 57, 03 10 0, 00 10 0, 00 10 0, 01 10 0, 00 10 0, 00 256 58, 46 70 1, 88 240 214 57, 35 57, 63 56 52 0, 48 1, 72 74 57, 03 44 0, 08 10 174, 52 10 0, 00 10 174, 42 10 0, 01 106 243, 27 10 0, 00 140 249, 27 42 0, 59 164 156 243, 70 244, 19 46 44 4, 75 0, 32 65 247, 54 39 0, 13 10 0, 00 10 0, 01 Test 2x7 smpl3 2x7 2x7 2x7 2x7 3x5 smpl2 3x5 smpl3 LDPG PGLD PLDG random PDGL PDLG PGLD random DPLG GDLP LGPD random 10 10 10 10 10 10 10 10 10 10 10 10 0, 03 0, 03 0, 03 0, 03 0, 06 0, 07 0, 06 0, 06 0, 04 0, 07 0, 07 0, 07 rand1 rand2 rand3 std 2x8 smpl1 2x8 smpl2 2x8 smpl3 2x8 2x8 2x8 2x8 Czas 0, 02 0, 02 0, 02 0, 02 DFS Ruchy rand1 rand2 rand3 std 3x5 smpl1 3x5 3x5 3x5 3x5 DLPG DPLG PGLD random BFS Ruchy 10 10 10 10 GDPL LPDG LPGD random GLDP GLPD LGPD random GPDL PGDL PLGD random 10 10 10 10 10 10 10 10 10 10 10 10 0, 02 0, 02 0, 02 0, 02 0, 02 0, 02 0, 02 0, 02 0, 02 0, 02 0, 02 0, 02 rand1 rand2 rand3 std 4x4 smpl1 4x4 smpl2 DGLP PGDL PLDG random LDGP LPDG PLDG random 10 10 10 10 10 10 10 10 0, 08 0, 12 0, 11 0, 11 0, 07 0, 10 0, 10 0, 07 10 Czas Heurystyka 1. Ruchy Czas Heurystyka 2. Ruchy Czas 10 0, 00 86 84 72 45 8, 16 1, 67 38, 04 0, 09 10 0, 00 10 0, 00 10 0, 02 106 78 94 60 0, 58 0, 07 0, 08 0, 32 10 0, 01 10 0, 01 10 0, 00 132 120 112 58 0, 19 2, 44 3, 59 0, 26 10 0, 01 10 0, 00 Test 4x4 smpl3 4x4 4x4 4x4 4x4 2x9 smpl2 2x9 smpl3 GPDL LGDP PLGD random DGLP DPGL PDLG random DLPG LDPG PDLG random 10 10 10 10 10 10 10 10 10 10 10 10 0, 02 0, 02 0, 02 0, 02 0, 03 0, 04 0, 04 0, 04 0, 04 0, 03 0, 04 0, 03 rand1 rand2 rand3 std 3x6 smpl1 3x6 smpl2 3x6 smpl3 3x6 3x6 3x6 3x6 Czas 0, 07 0, 06 0, 06 0, 04 DFS Ruchy rand1 rand2 rand3 std 2x9 smpl1 2x9 2x9 2x9 2x9 DLGP PDGL PLGD random BFS Ruchy 10 10 10 10 DPGL GDLP GLPD random DGLP GPLD PGLD random DGPL GDLP GLPD random 10 10 10 10 10 10 10 10 10 10 10 10 0, 07 0, 10 0, 14 0, 08 0, 15 0, 11 0, 08 0, 10 0, 12 0, 11 0, 11 0, 10 rand1 rand2 rand3 std 2x10 smpl1 2x10 smpl2 DGPL LPGD PLDG random GDLP LGPD PDGL random 10 10 10 10 2 2 2 2 0, 03 0, 05 0, 05 0, 04 0, 00 0, 00 0, 00 0, 00 11 Czas Heurystyka 1. Ruchy Czas Heurystyka 2. Ruchy Czas 10 0, 00 96 94 80 70 0, 26 0, 24 0, 61 0, 31 10 0, 01 10 0, 00 10 0, 00 146 158 224 61 3, 33 5, 00 96, 00 0, 21 12 0, 00 12 0, 00 10 0, 00 140 138 130 57 23, 59 11, 82 3, 02 0, 36 10 0, 01 2 0, 00 Test 2x10 smpl3 2x10 2x10 2x10 2x10 Czas 0, 00 0, 01 0, 01 0, 01 DFS Ruchy rand1 rand2 rand3 std 4x5 smpl1 4x5 smpl2 4x5 smpl3 4x5 4x5 4x5 4x5 DGPL DPGL LGPD random BFS Ruchy 8 8 8 8 GLPD LGDP PGDL random DLGP LDPG LPGD random DGLP GLPD PLGD random 10 10 10 10 10 10 10 10 6 6 6 6 0, 11 0, 17 0, 17 0, 21 0, 16 0, 10 0, 11 0, 20 0, 01 0, 01 0, 01 0, 01 rand1 rand2 rand3 std 12 Czas Heurystyka 1. Ruchy Czas Heurystyka 2. Ruchy Czas 8 0, 00 210 +∞ 234 74 10, 21 +∞ 46, 70 0, 54 10 0, 01 10 0, 00 6 0, 01 132 198 120 97 2, 06 12, 71 0, 84 18, 27 6. Dyskusja Wyniki wyraźnie wskazują na przewagę nawet prostej heurystyki nad naiwnym przeszukiwaniem grafu, oraz na to, że heurystyka 2. okazała się trafnym pomysłem i bez porównania przewyższyła wszystkie pozostałe metody. Metody BFS oraz DFS na testach losowych oraz standardowych wykonywały się w porównywalnych czasach. Testy „średnie”, czyli szczyt możliwości wspomnianych metod dla testów przypadkowych (dające czasy sięgające nawet 30 − 50min) wskazują na przewagę szybkości metody BFS nad metodą DFS - wyjątkiem jest test 2x6 rand1. Test ten charakteryzuje się tym, że nie da się go rozwiązać w liczbie ruchów mniejszej niż 50 (taki jest wynik metody BFS, która - z definicji - zawsze znajduje najkrótszą ścieżkę) - dlatego okazało się, że przeszukanie wszystkich możliwych stanów w szerz przy dużym promieniu jest bardziej czasochłonne od konsekwentnego brnięcia wgłąb przy przeszukiwaniu stanów. Dla mniejszych testów jednakże czasy DFS i BFS były porównywalne. Na metodę DFS należy jednak spojrzeć inaczej - wskazuje ona bardzo długie ścieżki, ale jest zupełnie rozsądnym rozwiązaniem w celu sprawdzania, czy rozwiązanie w ogóle istnieje. Ścieżki o długościach sięgających 150 milionów ruchów (przy średnich testach) nie mają praktycznego zastosowania, a jedynie uniemożliwiają rozsądnym studentom wgranie kompletu wyników obliczeń na svn. Jest to oczywiście wniosek bardziej ogólny, gdyż problem sprawdzania, czy układankę da się rozwiązać, jest w przypadku „piętnastki” zbyt prosty, aby angażować do niego metody przeszukiwania grafu. Kolejnym wnioskiem na temat metod BFS oraz DFS jest wpływ kolejności przeszukiwania wierzchołków na czas pracy oraz długość znalezionego rozwiązania. Jakkolwiek oczywistym jest, że długość rozwiązania znalezionego przez metodę BFS nie zależy od kolejności, interesujące jest, że także wpływ na czas dla metody BFS jest znikomy (jedynym wyjątkiem jest tutaj test 3x4 rand1, w którym kolejność przeszukiwania lewo-dół-prawo-góra okazała się wyjątkowo szczęśliwa). W przypadku DFS wahania czasu wykonania w zależności od kolejności przeszukiwania są znaczne (dobrym przykładem kogą tu być wszystkie testy 2x5 - różnice między najgorszym a najlepszym dla metody DFS przypadkiem są tam co najmniej sześciokrotne). Ciekawostką stworzoną z myślą o metodzie BFS są testy „proste”. Powstały one poprzez wykonanie 10 losowych dozwolonych ruchów na ułożonej układance, przy czym żaden ruch nie mógł być cofnięciem poprzedniego. Mimo takiej metody układania testów, niektóre z nich okazały się jeszcze prostsze niż 10 ruchów - przypadkiem patologicznym jest tutaj test 2x10 smpl2, który udało się ułożyć w dwóch ruchach. Testy te (ze względu na swoją specyfikę) nie były dla metody DFS w żadnym stopniu prostsze od testów losowych. Na żadnym z nich heurystyka 1. nie osiągnęła znacznej przewagi nad BFS, natomiast na wielu z nich było na odwrót (jednym z wielu przykładów są testy proste o rozmiarze 3x4). Heurystyka 2. wykazała się jednak skutecznością i 13 osiągnęła czasy podobne do BFS, czyli w całości mieszczące się w granicach błędu pomiarowego. Heurystyka 1. nie nadawała się na testy większe od średnich (12 pól), gdyż ze względu na algorytm obsługujący obie heurystyki, zużycie pamięci szybko rosło w miarę wykonania programu - zatem heurystyka mogła znaleźć rozwiązanie umiarkowanie szybko, albo wcale. Jednakże postanowiliśmy nie zmieniać jej i zaprezentować taką właśnie formę, gdyż pomimo prostoty postępowania i wrażenia, jakie robi heurystyka 1. w porównaniu z heurystyką 2. - uważamy ją za sukces. Wszystkie testy „średnie” heurystyka 1. pozwalała rozwiązywać w czasach poniżej 2min 10s, podczas gdy w przypadku BFS niektóre testy wymagały 30min, zaś w przypadku DFS - nawet godziny. Także długości znajdywanych rozwiązań są interesujące, gdyż na testach przypadkowych były co najwyżej 6x dłuższe od optimum wyznaczonego przez BFS, a na testach prostych - najwyżej 11x dłuższe. Heurystyka 2., pomimo nietrywialnego algorytmu przydzielania priorytetów (które w przypadku obu heurystyk miało złożoność O(w ∗ k)), zawsze była szybsza (nawet do 1900x - test 3x4 std) i znajdywała krótsze (nawet czterokrotnie - test 2x6 rand1) ścieżki od heurystyki 1. Aby nie przesadzić z wychwalaniem jej, zwrócimy uwagę, że nie ze wszystkimi testami stworzonymi przez generator zdołała sobie ona poradzić - spośród testów „dużych”, które testowane były wyłącznie dla tej metody, test 2x10 rand2 jako jedyny przerósł możliwości rozwiązywania układanek tą metodą na posiadanym przez nas sprzęcie. Zużycie pamięci RAM zbliżyło się do 4 GB, po czym zadziałał wewnętrzny mechanizm samobójstwa programu. Okazał się on skuteczny, gdyż praca została przerwana na tyle wcześnie, że zużycie pamięci nie uniemożliwiło kontynuowania prac nad sprawozdaniem. Pomimo tej jednostkowej porażki, uważamy heurystykę 2. za nadający się do praktycznego użycia algorytm szukania rozwiązań układanek. Szczególnie dobrze poradził on sobie na układankach bliskich kwadratowym, to znaczy takich, gdzie różnica między liczbą wierszy a liczbą kolumn była niewielka. 7. Wnioski Z wykonanego ćwiczenia można wysnuć następujące wnioski, zaczynając od najprostszych: — Metoda BFS zawsze wskazywała najkrótszą ścieżkę. — Ścieżki znajdywane przez metodę DFS nawet dla niewielkich testów były zniechęcające (np. ponad 100000 ruchów dla układanki 3x3, którą dało się ułożyć w 10 ruchach). Dla największych rozważanych przy metodzie DFS testów znalezione rozwiązania miały nawet po ponad 150 milionów ruchów. — Nawet prosta heurystyka pozwoliła osiągnąć wyniki o rzędy wielkości lepsze od metod naiwnych (BFS i DFS), przy czym znajdywane ścieżki mają zupełnie zadowalające długości. 14 — Dobry sposób przydzielania priorytetów przeszukiwanym stanom (heurystyka 2.) zapewnia kolejne rzędy wielkości przyspieszenia, nawet przy jednoczesnym skróceniu wynikowych ścieżek. — Udało się wylosować taki test przypadkowy, który okazał się bardzo pesymistyczny dla najlepszej z opracowanych metod heurystycznych - podczas gdy wszystkie inne testy rozwiązano tą metodą w czasach poniżej 2min, jeden test nie został rozwiązany przed wyczerpaniem pamięci operacyjnej (próba rozwiązywania trwała prawie 10min). — Odpowiednio spreparowane testy (możliwe do ułożenia w nie więcej niż 10 ruchów) nie dały żadnych korzyści metodzie DFS, podczas gdy metodzie BFS pozwoliły osiągnąć lepsze rezultaty niż prostej heurystyce. — Czasy wykonania metod DFS i BFS dla małych testów były porównywalne. Czas wykonania metody DFS znacznie wahał się w zależności od kolejności przeszukiwania sąsiadów bieżącego stanu. — Dla największych testów przypadkowych które mogły być rozwiązywane metodami BFS i DFS, czas wykonania metody BFS był krótszy niż DFS, gdyż było bardzo wiele stanów które mogły być przeszukiwane nadaremno. Jednakże jeżeli prawidłowe rozwiązanie wymagało wykonania aż 50 ruchów, szybszy okazał się być DFS. Literatura — — — — — — — — http://en.wikipedia.org/wiki/Fifteen_puzzle http://en.wikipedia.org/wiki/Breadth-first_search http://en.wikipedia.org/wiki/Depth-first_search http://en.wikipedia.org/wiki/Dijkstra%27s_algorithm http://www.kernel.org/doc/man-pages/ http://www.cplusplus.com/reference/stl/ http://docs.python.org/ Szczepan Jeleński, Lilavati - rozrywki matematyczne, Wydawnictwa Szkolne i Pedagogiczne, Bydgoszcz 1968 — Thomas H. Cormen, Charles E. Leiserson, Ronald L. Rivest i Clifford Stein, Wprowadzenie do algorytmów, wydanie ósme, Wydawnictwa Naukowo-Techniczne 15