1. Algorytmy przeszukiwania. Przeszukiwanie wszerz i w głąb. Algorytmy przeszukiwania w głąb i wszerz są najczęściej stosowanymi algorytmami przeszukiwania. Wykorzystuje się je do zbadania istnienia połączenie między dwoma wierzchołkami w grafie. Aby zbadać istnienie połączenia w grafie podajemy dwa wierzchołki: pierwszy - zwany stanem początkowym, oraz drugi - zwany stanem końcowym. W przypadku przeszukiwania grafów skończonych omawiane algorytmy są równoważne, tzn zbieżność algorytmu przeszukiwania w głąb pociąga za sobą zbieżność algorytmu przeszukiwania wszerz i odwrotnie. Jeżeli przeszukujemy graf nieskończony, algorytm przeszukiwania wszerz jest zbieżny, natomiast algorytm przeszukiwania w głąb nie. Brak zbieżności ma miejsce wówczas, gdy przeszukujemy krawiędź nieskończoną, która nie prowadzi do wierzchołka końcowego. Omawiając algorytm posłużymi się przykładem. Zadamy konkretny graf oraz stany: początkowy i końcowy i opiszemy przebieg działania algorytmu. Niech dany będzie graf: Rysunek 1: Graf do przeszukiwania. Dowolny graf można opisać w postaci listy połączeń. Lista zawiera n linii, gdzie n oznacza liczbę wierzchołków grafu. W i tej linii są umiszczone numery wierzchołków, z którymi i ty wierzchołek jest połączony. Lista połączeń dla rozważanego grafu wygląda następująco: 1. 2, 6, 9 2. 1, 3 3. 2, 4 4. 3, 5 5. 4, 8, 11 6. 1, 7 1
7. 6, 8 8. 5, 7 9. 1, 10 10. 9, 11 11. 5, 10 Przeszukiwanie wszerz Przeszukiwanie grafu wszerz polega na odwiedzaniu wszystkich wierzchołków grafu sąsiadujących z wierzchołkiem początkowym, następnie wierzchołków w odległośi 2 od wierzchołka początkowego i tak kolejno. Przy każdym odwiedzeniu należy sprawidzić, czy stan, w którym się znajdujemy jest stanem końcowym. Odwiedzając kolejne wierzchołki należy pamiętać, żeby nie odwiedzać wierzchołków wcześniej odwiedzonych, tzn. każdy wierzchołek możemy odwiedzić dokładnie raz. W przypadku, gdy odwiedzimy wszystkie możliwe wierzchołki i nie znajdziemy stanu końcowego, nie istnieje droga miedzy szukanymi wierzchołkami. Zaletą algorytmu przeszukiwania wszerz jest to, że na pewno nie pominiemy żadnego wierzchołka, zwykle jednak odwiedzamy za dużo wierzchołków, co jest wadą algorytmu. Przeszukiwanie wszerz odbywa się przy użyciu kolejki FIFO (first in first out). Algorytm przebiega następująco: 1. Utwórz kolejkę FIFO 2. Zapisz do kolejki stan początkowy 3. Pobierz z kolejki stan i nazwij go S 4. Jesli (a) S jest poszukiwanym stanem końcowym zwróć SUKCES i zakończ algorytm (b) S=NULL (lista jest pusta) zwróć BRAK ROZWIAZANIA i zakończ algorytm (c) S nie jest poszukiwanym stanem końcowym to generuj wszystkie możliwe stany następujące po S (które można wyprowadzić z S zgodnie z wcześniej ustalonymi regułami a które nie były już rozważane) i zapisz je do kolejki 5. Idz do 3 Zadanie polega na stwierdzeniu, czy w wyżej podanym grafie wierzchołki 1 i 5 są połączone. Wykorzystywane funkcje: make fifo() - funkcja tworzy listę typu FIFO put fifo(x) - funkcja dodaje element x do listy get fifo() - funkcja pobiera elemet z listy Oto przebieg wykonywania algorytmu: 2
Numer Krok Wykonywana Stan Odwiedzone etapu algorytmu operacja kolejki wierzchołki 1 1 make fifo() NULL NULL 2 2 put fifo(1) 1 1 3 3 S:=get fifo() (S=1) NULL 1 4 4 put fifo(2), put fifo(6), 2 6 9 1 2 6 9 put fifo(9) 5 5 Powrót do 3 2 6 9 1 2 6 9 6 3 S:=get fifo() (S=2) 6 9 1 2 6 9 7 4 6 9 3 1 2 3 6 9 put fifo(3) 8 5 Powrót do3 6 9 3 1 2 3 6 9 9 3 S:=get fifo() (S=6) 9 3 1 2 3 6 9 10 4 9 3 7 1 2 3 6 7 9 put fifo(7) 11 5 Powrót do 3 9 3 7 1 2 3 6 7 9 12 3 S:=get fifo() (S=9) 3 7 1 2 3 6 7 9 13 4 3 7 10 1 2 3 6 7 9 10 put fifo(10) 14 5 Powrót do 3 3 7 10 1 2 3 6 7 9 10 15 3 S:=get fifo() (S=3) 7 10 1 2 3 6 7 9 10 16 4 7 10 4 1 2 3 4 6 7 9 10 put fifo(4) 17 5 Powrót do 3 7 10 4 1 2 3 4 6 7 9 10 18 3 S:=get fifo() (S=7) 10 4 1 2 3 4 6 7 9 10 19 4 10 4 8 1 2 3 4 6 7 8 9 10 put fifo(8) 20 5 Powrót do 3 10 4 8 1 2 3 4 6 7 8 9 10 21 3 S:=get fifo() (S=10) 4 8 1 2 3 4 6 7 8 9 10 22 4 4 8 11 1 2 3 4 6 7 8 9 10 11 put fifo(11) 23 5 Powrót do 3 4 8 11 1 2 3 4 6 7 8 9 10 11 24 3 S:=get fifo() (S=4) 8 11 1 2 3 4 6 7 8 9 10 11 25 4 8 11 5 1 2 3 4 5 6 7 8 9 10 11 put fifo(5) 26 5 Powrót do 3 8 11 5 1 2 3 4 5 6 7 8 9 10 11 27 3 S:=get fifo() (S=8) 11 5 1 2 3 4 5 6 7 8 9 10 11 28 4 11 5 1 2 3 4 5 6 7 8 9 10 11 nic 29 5 Powrót do 3 11 5 1 2 3 4 5 6 7 8 9 10 11 30 3 S:=get fifo() (S=11) 5 1 2 3 4 5 6 7 8 9 10 11 31 4 5 1 2 3 4 5 6 7 8 9 10 11 nic 32 5 Powrót do 3 5 1 2 3 4 5 6 7 8 9 10 11 33 3 S:=get fifo() (S=5) NULL 1 2 3 4 5 6 7 8 9 10 11 34 4 NULL 1 2 3 4 5 6 7 8 9 10 11 return(sukces) 3
Przeszukiwanie w głąb Przeszukiwanie grafu w głąb polega na przeszukiwaniu poszczególnych krawędzi grafu. Przechodzimy krawędz najdalej ja się da, jeżeli dana ścieżka nie doprowadziła nas do wierzchołka końcowego wówczas cofamy sie do momentu, z którego możemy pójść inną scieżką. Podobnie jak w przypadku przeszukiwania wszerz, przeszukując graf metodą w głąb pojedynczy wierzchołek może być odwiedziny dokładnie jeden raz. Przy każdym odwiedzeniu należy sprawidzić, czy nie znajdujemy się w stanie końcowym. W przypadku, gdy odwiedzimy wszystkie możliwe wierzchołki i nie znajdziemy stanu końcowego, nie istnieje droga miedzy szukanymi wierzchołkami. Zaletą algorytmu przeszukiwania w głąb jest to, że nie przeszukujemy wszystkich wierzchołków grafu, dodatkowo przeszukując ścieżką prowadzącą bezpośrednio do wierzchołka końcowego możemy odwiedzić minimalną ilość wierzchołków łączących stan początkowy z końcowym. Wadą jest to, że zwykle przszukiwanie odbywa się niewłaściwą scieżką co prowadzi do zabrnięcia w ślepą uliczkę, z której należy się wycofać do wierzchołka, z którego istnieje możliwość pójścia dalej. Przeszukiwanie w głąb odbywa się przy użyciu kolejki LIFO (last in first out) zwanej inaczej STOS. Algorytm przebiega następująco: 1. Utwórz STOS 2. Zapisz na stos stan początkowy 3. Pobierz ze stosu stan i nazwij go S 4. Jesli (a) S jest poszukiwanym stanem końcowym zwróć SUKCES i zakończ algorytm (b) S=NULL (lista jest pusta) zwróć BRAK ROZWIAZANIA i zakończ algorytm (c) S nie jest poszukiwanym stanem końcowym to generuj wszystkie możliwe stany następujące po S (które można wyprowadzić z S zgodnie z wcześniej ustalonymi regułami a które nie były już rozważane) i zapisz je do kolejki 5. Idz do 3 Zadanie polega na stwierdzeniu, czy w wyżej podanym grafie wierzchołki 1 i 5 są połączone. Wykorzystywane funkcje: make stos() - funkcja tworzy STOS put stos(x) - funkcja dodaje element x na stos get stos() - funkcja pobiera elemet ze stosu Oto przebieg wykonywania algorytmu: 4
Numer Krok Wykonywana Stan Odwiedzone etapu algorytmu operacja kolejki wierzchołki 1 1 make stos() NULL NULL 2 2 put stos(1) 1 1 3 3 S:=get stos() (S=1) NULL 1 4 4 put stos(2), put stos(6), 2 6 9 1 2 6 9 put stos(9) 5 5 Powrót do 3 2 6 9 1 2 6 9 6 3 S:=get stos() (S=9) 2 6 1 2 6 9 7 4 put stos(10) 2 6 10 1 2 6 9 10 8 5 Powrót do 3 2 6 10 1 2 6 9 10 9 3 S:=get stos() (S=10) 2 6 1 2 6 9 10 10 4 put stos(11) 2 6 11 1 2 6 9 10 11 11 5 Powrót do 3 2 6 11 1 2 6 9 10 11 12 3 S:=get stos() (S=11) 2 6 1 2 6 9 10 11 13 4 put stos(5) 2 6 5 1 2 5 6 9 10 11 14 5 Powrót do 3 2 6 5 1 2 5 6 9 10 11 15 3 S:=get stos() (S=5) 2 6 1 2 5 6 9 10 11 16 4 Wykonywanie punktu (a) return(sukces) 2 6 1 2 5 6 9 10 11 W tak realizowanym algorytmie poruszamy się wzdłuż jednej krawędzi ale dla danego wierzchołka odwiedzamy wszystkich jego sąsiadów. Możemy skonstruować algorytm tak, aby z danego wierzchołka generować tylko jeden (wybrany) stan następującey po nim. Wówczas algorytm będzie przebiegał następująco: 1. Utwórz STOS 2. Przyjmij S stan początkowy i zapisz na stos 3. Jesli (a) S jest poszukiwanym stanem końcowym zwróć SUKCES i zakończ algorytm (b) S=NULL (lista jest pusta) zwróć BRAK ROZWIAZANIA i zakończ algorytm (c) S nie jest poszukiwanym stanem końcowym to: 4. Idz do 3 i. jeśli istnieje możliwy stan następujący po S to S przyjmij ten stan i zapisz na stos ii. w przeciwnym przypadku zdejmij element ze stosu, następnie S przyjmij stan ze stosu (nie zdejmując elementu ze stosu) 5
Zadanie polega na swierdzeniu, czy wierzchołki 1 i 11 są połączone. Oto przebieg wykonywania algorytmu: Numer Krok Wykonywana Stan Odwiedzone etapu algorytmu operacja stosu wierzchołki 1 1 make stos() NULL NULL 2 2 S=1; put stos(s) 1 1 3 3 Wykonywanie punktu i 1 2 1 2 S=2; put stos(s) 4 4 Powrót do 3 1 2 1 2 5 3 Wykonywanie punktu i 1 2 3 1 2 3 S=3; put stos(s) 6 4 Powrót do 3 1 2 3 1 2 3 7 3 Wykonywanie punktu i 1 2 3 4 1 2 3 4 S=4; put stos(s) 8 4 Powrót do 3 1 2 3 4 1 2 3 4 9 3 Wykonywanie punktu i 1 2 3 4 5 1 2 3 4 5 S=5; put stos(s) 10 4 Powrót do 3 1 2 3 4 5 1 2 3 4 5 11 3 Wykonywanie punktu i 1 2 3 4 5 8 1 2 3 4 5 8 S=8; put stos(s) 12 4 Powrót do 3 1 2 3 4 5 8 1 2 3 4 5 8 13 3 Wykonywanie punktu i 1 2 3 4 5 8 7 1 2 3 4 5 8 7 S=7; put stos(s) 14 4 Powrót do 3 1 2 3 4 5 8 7 1 2 3 4 5 8 7 15 3 Wykonywanie punktu i 1 2 3 4 5 8 7 6 1 2 3 4 5 8 7 6 S=6; put stos(s) 16 4 Powrót do 3 1 2 3 4 5 8 7 6 1 2 3 4 5 8 7 6 17 3 Wykonywanie punktu ii 1 2 3 4 5 8 7 1 2 3 4 5 8 7 6 S=get stos() put stos(s) (S=7) 18 4 Powrót do 3 1 2 3 4 5 8 7 1 2 3 4 5 8 7 6 19 3 Wykonywanie punktu ii 1 2 3 4 5 8 1 2 3 4 5 8 7 6 S=get stos() put stos(s) (S=8) 20 4 Powrót do 3 1 2 3 4 5 8 1 2 3 4 5 8 7 6 21 3 Wykonywanie punktu ii 1 2 3 4 5 1 2 3 4 5 8 7 6 S=get stos(); put stos(s) (S=5) 22 4 Powrót do 3 1 2 3 4 5 1 2 3 4 5 8 7 6 23 3 Wykonywanie punktu i 1 2 3 4 5 11 1 2 3 4 5 8 7 6 11 S=11; put stos(s) 24 4 Powrót do 3 1 2 3 4 5 11 1 2 3 4 5 8 7 6 11 25 3 Wykonywanie punktu a 1 2 3 4 5 11 1 2 3 4 5 8 7 6 11 return(sukces) Taki przebieg algorytmu oprócz odpowiedzi na pytanie czy dwa wierzchołki są ze sobą połączone, generuje drogę prowadzacą od wierzchołka początkowego do wierzchołka końcowego (zwykle nie jest to optymalna droga). Jest to stan stosu w momencie zakończenia działania algorytmu. 6
Zadania Zadanie będą polegały na zastosowaniu powyższych algorytmów do sprawdzenia, czy dwa wierzchołki w grafie są ze sobą połączone. Zadanie 1 Napisać program, w oparciu o padane algorytmy, sprawdzającey, czy dwa wierzchołki w grafie są połączone. Zakładamy, ze program wczytuje graf z pliku o podanej w linii poleceń nazwie. Następnie pyta o numery wierzchołków do sprawdzenia. Wierzchołki numerujemy liczbami naturalnymi z przedziału [1, 100]. W pliku pierwsza linia zawiera liczbę wierzchołków, kolejne są listowym opisem grafu. Tak więc linia 2 zawiera sopis wierzchołków, z którymi łączy się wierzchołek 1, linia 3 - spis wierzchołków, z którymi łączy się wierzchołek 2, itd. Kolejne wierzchołki w linii rozdzielone są spacją. Każda linia na końcu zawiera liczbę 0, która oznacza koniec listy wierzchołków sąsiadujących z danym wierzchołkiem. Oto przykładowy plik z danymi dla rozważanego w przykładach grafu: 11 2 6 9 0 1 3 0 2 4 0 3 5 0 4 8 11 0 1 7 0 6 8 0 5 7 0 1 10 0 9 11 0 5 10 0 Zadanie 2 Napisać program, w oparciu o podany algorytm, sprawdzający czy możliwe jest przejście w labiryncie od jednego miejsca do drugiego. Zakładamy że program wczytuje labirynt z pliku o podanej w linii poleceń nazwie. Następnie pyta się o współrzędne pola startowego i końcowego. Maksymalny rozmiar planszy to 100 wierszy i 100 kolumn. Każde pole na planszy ma numer z przedziału [0, 15]. Numer ten oznacza jekiego typu jest pole, to znaczy gdzie możemy się z niego przemieścić. Oto dostępne pola (lewe górne ma numer 0, prawe dolne - 15, numeracja wierszami): Rysunek 2: Pola labryntu. W pliku pierwsza linia zawiera liczbę wierszy, druga - liczbę kolumn, kolejne natomiast to opis pól w danym wierszu. Tak więc linia 3 zawiera opis pól wiersza 7
1, linia 4 - opis pól wiersza drugiego, itd. Kolejne pola w wierszu rozdzielone są spacjami. Oto przykładowy wygląd labiryntu i odpowiadającego mu pliku z danymi: Rysunek 3: Plansza labiryntu. 4 5 8 9 12 8 5 4 5 14 2 13 4 3 9 0 5 13 15 15 7 6 Ilustrowany na ekranie - na przykład pola odwiedzone niech mają inny kolor. W realizacji tekstowej program powinien wyświetlać (lub zapisywać do pliku) współrzędne odwiedzanych pól. Uwaga Jak łatwo zauważyć labirynt można utożsamiać z grafem. Poszczególne pola labiryntu są wierzchołkami, a rodzaj pola jednoznacznie definiuje listę wieszchołków sąsiadujących z rozważanym. Dla powyższego labiryntu plik opisujący go jako graf wygląda następująco: 20 2 6 0 1 3 0 2 0 5 9 0 4 10 0 1 7 11 0 6 12 0 9 0 4 8 14 0 5 0 6 12 16 0 7 11 13 0 12 14 0 9 13 15 19 0 14 20 0 11 0 0 0 14 20 0 15 19 0 8