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

Podobne dokumenty