![]() |
![]() ![]()
Autor artykułu: mgr Jerzy Wałaszek, wersja1.0 |
©2011 mgr
Jerzy Wałaszek
|
Napiszemy prosty program, który w sposób wizualny ilustruje działanie metody DFS. Polem doświadczalnym będzie labirynt, który zrealizujemy w tablicy kwadratowej. Labirynt jest oczywiście grafem zrealizowanym w tablicy dwuwymiarowej. Przykład obrazuje tablicę o wymiarze 9 x 9. Wymiary x i y labiryntu muszą spełniać warunek:
wymiar = 1 + 2 x k, k = 2,3,...
|
Najpierw wypełnimy wszystkie komórki ścianami - czyli specjalną wartością, która oznacza, iż nie można wejść do danego segmentu labiryntu. Na ekranie ściany będą wyświetlane jako segmenty ciemnoniebieskie. | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
W labiryncie utworzymy komory - będą to jakby odpowiedniki węzłów w grafie. Komory będą od siebie odległe o dwie komórki. Komorę zaznaczymy jako pustą - specjalna wartość, która oznacza, iż można przejść do danego segmentu. Na ekranie puste segmenty będą wyświetlane w kolorze żółtym. | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
Wykorzystując funkcję pseudolosową, utworzymy korytarze leżące na lewo od komór (w grafie będą one odpowiadały krawędziom). Korytarz będzie tworzony, jeśli wyrażenie rand() % stała przyjmie wartość zero. Wartość stałej wpływa na gęstość tworzenia korytarzy. Generacje korytarzy rozpoczniemy od drugiej kolumny komór. | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
Teraz utworzymy w podobny sposób korytarze pionowe u góry komór, poczynając od drugiego wiersza komór. W efekcie powstanie prosty labirynt korytarzy. | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
Na koniec w labiryncie określimy komorę startową S oraz końcową K. Algorytm DFS rozpocznie przechodzenie labiryntu od komory S. Jeśli dojdzie do komory K, ścieżka zostanie znaleziona. | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
Przechodząc przez labirynt, procedura DFS będzie wpisywała do komórek informację, skąd przyszła - to odpowiednik rozwijania nici wzdłuż korytarzy labiryntu. Umożliwi jej to późniejsze wycofanie się, jeśli skończy się droga. Trafiwszy do komory, DFS sprawdza, czy może przejść odpowiednio w lewo, w górę, w dół lub w prawo. Jeśli któryś z tych kierunków jest wolny (tzn. nie zawiera ściany ani nie był już wcześniej odwiedzony), DFS przechodzi zgodnie z tym kierunkiem. Gdy żaden nie jest wolny, DFS cofa się, zwijając nić, czyli wpisując do komórki informację, że została odwiedzona (odpowiednik końca przetwarzania wierzchołka w algorytmie DFS), Jeśli dojdzie do komory końcowej, ścieżka będzie znaleziona - w naszym programie ścieżkę będziemy oznaczali kolorem czerwonym. Dzięki takiemu rozwiązaniu DFS nie musi posiadać stosu - sam graf (czyli nasz labirynt) zawiera wszelkie niezbędne informacje, Które DFS potrzebuje do przechodzenia przez labirynt. |
Ponieważ operujemy w programie kolorem, wykorzystujemy opracowaną w naszej szkole bibliotekę newconio. Informacje na temat tej biblioteki znajdziecie tutaj.
Code::Blocks |
// Demonstracja działania procedury DFS // wykorzystanej do poszukiwania drogi // w labiryncie. //------------------------------------- // (C)2011 Koło Informatyczne // I LO w Tarnowie //------------------------------------- #include <iostream> #include <string> #include <ctime> #include "newconio.h" using namespace std; // Stałe const int PUSTE = 0; // pusta komora/korytarz const int SCIANA = 1; // komórka zawierająca ścianę const int W_LEWO = 2; // komórka z nitką w lewo const int W_PRAWO = 3; // komora z nitką w prawo const int W_GORE = 4; // komora z nitką w górę const int W_DOL = 5; // komora z nitką w dół const int ODWIEDZONE = 6; // komora już odwiedzona, bez nitki const int KROK = 10; // opóźnienie kroków w ms const int LX = 79; // rozmiar labiryntu w poziomie = 1 + 2 * k const int LY = 47; // rozmiar labiryntu w pionie // Zmienne globalne int labirynt[LX][LY]; // labirynt int xs,ys; // współrzędne punktu startowego int xk,yk; // współrzędne punktu końcowego int xp,yp; // współrzędne poszukiwacza drogi int lk; // licznik kroków poszukiwacza // Funkcja wyświetla przekazany jej tekst na środku ostatniego // wiersza ekranu, oczekuje na wciśnięcie przez użytkownika // klawisza i zwraca jego kod. //------------------------------------------------------------ int czekaj(string t) { int klawisz; fillrect(' ',0xf0,0,49,79,49); textcolor(RED); textbackground(WHITE); center(49,_pl(t)); while(!(klawisz = getch())); return klawisz; } // Funkcja tworzy labirynt w tablicy labirynt //------------------------------------------- void tworz_labirynt() { int x,y,c; // wypełniamy cały labirynt ścianami; for(x=0; x<LX; x++) for(y=0; y<LY; y++) labirynt[x][y] = SCIANA; // tworzymy komory i korytarze. Komory tworzone są co 2 komórki. // Korytarze łączą komory z lewej i z góry for(x=1; x<LX; x+=2) for(y=1; y<LY; y+=2) { labirynt[x][y] = PUSTE; // komora c = 0; // licznik korytarzy if(rand() % 3 == 0) { if(x > 1) labirynt[x-1][y] = PUSTE; // korytarz z lewej c++; // zwiększamy licznik korytarzy } if(rand() % 3 == 0) { if(y > 1) labirynt[x][y-1] = PUSTE; // korytarz z góry c++; // zwiększamy licznik korytarzy } if(c == 0) // jeśli nie został utworzony korytarz, { // tworzymy go z lewej albo u góry if(rand() % 2) { if(x > 1) labirynt[x-1][y] = PUSTE; } else { if(y > 1) labirynt[x][y-1] = PUSTE; } } } // zerujemy współrzędne początku, końca ścieżki, poszukiwacza // oraz licznik kroków xs = ys = xk = yk = xp = yp = lk = 0; } // Procedura wyświetla labirynt na ekranie //---------------------------------------- void pokaz_labirynt() { int x,y,rxs,rxk,rys,ryk; if(xp) // jeśli poszukiwacz jest w labiryncie, { rxs = xp-1; rxk = xp+1; // to wyświetlimy tylko fragment rys = yp-1; ryk = yp+1; // wokół niego - reszta labiryntu już wyświetlona } else { rxs = 0; rxk = LX-1; rys = 0; ryk = LY-1; } for(x = rxs; x <= rxk; x++) for(y = rys; y <= ryk; y++) switch(labirynt[x][y]) { case PUSTE : putxy(' ',0xe0,x,y); break; case SCIANA : putxy(' ',0x10,x,y); break; case ODWIEDZONE : putxy(' ',0xf0,x,y); break; default : putxy(' ',0x40,x,y); break; } if(xp + yp > 0) putxy('P',0xe4,xp,yp); // Poszukiwacz if(xs + ys > 0) putxy('S',0x0c,xs,ys); // Start if(xk + yk > 0) putxy('K',0x0a,xk,yk); // Koniec if(lk) // Licznik kroków { gotoxy(0,48); textattr(0x0e); cout << _pl("Liczba kroków : "); textcolor(LIGHTRED); cout << lk; } } // Procedura umożliwia użytkownikowi wybranie // pozycji startowej i końcowej w labiryncie //------------------------------------------ void ustaw_start_koniec(string t,int & x, int & y) { x = y = 1; // rozpoczynamy w lewym górnym rogu while(true) { pokaz_labirynt(); // wyświetlamy labirynt switch(czekaj(t)) // czekamy na klawisz { // i przesuwamy punkt wg klawisza case 72: if(y > 1) y -= 2; // w górę break; case 77: if(x < LX-2) x += 2; // w w prawo break; case 80: if(y < LY-2) y += 2; // w dół break; case 75: if(x > 1) x -= 2; // w lewo break; case 13: return; // klawisz ENTER kończy } } } // Procedura DFS szukająca drogi w labiryncie // od punktu startowego do punktu końcowego //------------------------------------------- void szukaj_wyjscia() { fillrect(' ',0xf0,0,49,79,49); textcolor(BLACK); textbackground(WHITE); center(49,_pl("Poszukiwanie ścieżki do punktu końcowego")); xp = xs; // ustawiamy poszukiwacza w punkcie startowym yp = ys; labirynt[xp][yp] = ODWIEDZONE; // zaznaczamy tę komórkę jako odwiedzoną // DFS while(true) { lk++; // zwiększamy licznik pokaz_labirynt(); // wyświetlamy labirynt (fragment) delay(KROK); // odczekujemy if(xp == xk && yp == yk) // jeśli w punkcie końcowym, kończymy { putxy('K',0xf4,xp,yp); return; } // droga w lewo? if(labirynt[xp-1][yp] == PUSTE) { xp--; // przechodzimy w lewo labirynt[xp][yp] = W_LEWO; // rozwijamy nić w lewo continue; // wracamy na początek pętli } // brak drogi w lewo. Droga w górę? if(labirynt[xp][yp-1] == PUSTE) { yp--; // przechodzimy w górę labirynt[xp][yp] = W_GORE; // rozwijamy nić w górę continue; // wracamy na początek pętli } // w lewo i w górę brak drogi. Droga w prawo? if(labirynt[xp+1][yp] == PUSTE) { xp++; // przechodzimy w prawo labirynt[xp][yp] = W_PRAWO; // rozwijamy nić w prawo continue; // wracamy na początek pętli } // w lewo, w górę i w prawo brak drogi. Droga w dół? if(labirynt[xp][yp+1] == PUSTE) { yp++; // przechodzimy w dół labirynt[xp][yp] = W_DOL; // rozwijamy nić w dół continue; // wracamy na początek pętli } // nie udało się pójść w żadnym z dostępnych kierunków, gdyż // albo była tam ściana, albo już tam byliśmy. Próbujemy zatem // cofnąć się po nici do poprzedniego segmentu labiryntu. switch(labirynt[xp][yp]) { case ODWIEDZONE : return; // wróciliśmy na początek - brak ścieżki. case W_LEWO : labirynt[xp++][yp] = ODWIEDZONE; break; case W_PRAWO : labirynt[xp--][yp] = ODWIEDZONE; break; case W_GORE : labirynt[xp][yp++] = ODWIEDZONE; break; case W_DOL : labirynt[xp][yp--] = ODWIEDZONE; break; } } } int main() { _cinit(); srand(time(NULL)); fullscreen(true); cursoroff(); tworz_labirynt(); ustaw_start_koniec("Wybierz punkt startowy i naciśnij ENTER",xs,ys); ustaw_start_koniec("Wybierz punkt końcowy i naciśnij ENTER",xk,yk); szukaj_wyjscia(); czekaj("Koniec przejścia. Naciśnij dowolny klawisz, aby zakończyć program."); textattr(7); cursoron(); fullscreen(false); return 0; } |
![]() ![]() |
Zwróć uwagę na sposób zachowania się algorytmu DFS. Jeśli ścieżka nie zostanie znaleziona, to DFS przechodzi cały dostępny labirynt. Fragmenty nieodwiedzone przez DFS są częściami labiryntu odseparowanymi od przeglądanej części. Dzięki tej własności DFS można wykorzystywać do badania, czy graf jest spójny - uruchamiamy DFS, a następnie sprawdzamy, czy zostały odwiedzone wszystkie wierzchołki. Jeśli tak, graf jest spójny. Jeśli nie, nie jest - proste?
![]() | I Liceum Ogólnokształcące |
Pytania proszę przesyłać na adres email: i-lo@eduinf.waw.pl
W artykułach serwisu są używane cookies. Jeśli nie chcesz ich otrzymywać,
zablokuj je w swojej przeglądarce.
Informacje dodatkowe