Strona główna » Algorytmy » Struktury Danych » Przeszukiwanie w Głąb (DFS)
 

Przeszukiwanie w Głąb (DFS)

Cel

Przeszukiwanie grafu w głąb (DFS) jest używane w badaniu spójności grafu (prostego oraz skierowanego). Ponadto algorytm ten jest wykorzystywany w wielu innych algorytmach np. sortowaniu topologicznym. W tym artykule zostanie wyjaśniona podstawowa implementacja tego algorytmu.

Algorytm

Przeszukiwanie w głąb (DFS) jest to algorytm rekurencyjny. Do działania potrzebna jest tablica o tylu elementach ile jest węzłów w przekazanym grafie, gdzie można przechować informację czy dany element został odwiedzony oraz jaki jest jego poprzednik. Działanie algorytmu jest następujące: przygotuj tablicę, a następnie dla każdego nieodwiedzonego węzła odwiedź go. Odwiedzenie węzła polega na przypisaniu temu węzłowi, że został odwiedzony, a następnie należy odwiedzić wszystkich jego sąsiadów.

Istnieje możliwość zastosowania algorytmu DFS dla dowolnego wierzchołka. Należy pamiętać, że z wierzchołka 1 i 2 uzyska się dwa różne wyniki. Jeśli algorytm ma rozpocząć przeszukiwanie od innego wierzchołka niż pierwszego w grafie to należy wpierw odwiedzić określony wierzchołek startowy zanim zacznie się przchodzić wszystkie po kolei.

Przykład

Poniższy przykład pokaże działanie algorytmu DFS dla poniższego grafu:

Punktem początkowym jest zaznaczony punkt A. W tabeli została przedstawiona aktualna kolejka oraz właściwości poszczególnych obiektów:

WęzełABCDEFGHKomentarz
A0, ☑∞, ☐∞, ☐∞, ☐∞, ☐∞, ☐∞, ☐∞, ☐[A] Przeglądamy sąsiadów A, czyli B, C, D
.B0, ☑A, ☑A, ☐A, ☐∞, ☐∞, ☐∞, ☐∞, ☐[B] Przeglądamy sąsiadów B: E
..E0, ☑A, ☑A, ☐A, ☐B, ☐∞, ☐∞, ☐∞, ☐[B] Przeglądamy sąsiadów B: E
..E0, ☑A, ☑A, ☐A, ☐B, ☑∞, ☐∞, ☐∞, ☐[E] Brak sąsiadów, powrót do B
.B0, ☑A, ☑A, ☐A, ☐B, ☑∞, ☐∞, ☐∞, ☐[B] Brak sąsiadów, powrót do A
A0, ☑A, ☑A, ☐A, ☐B, ☑∞, ☐∞, ☐∞, ☐[A] Przeglądanie kolejnego sąsiada: C
.C0, ☑A, ☑A, ☑A, ☐B, ☑∞, ☐∞, ☐∞, ☐[C] Brak sąsiadów, powrót do A
A0, ☑A, ☑A, ☑A, ☐B, ☑∞, ☐∞, ☐∞, ☐[A] Przeglądanie ostatniego sąsiada: D
.D0, ☑A, ☑A, ☑A, ☑B, ☑D, ☐∞, ☐D, ☐[D] Przeglądanie sąsiadów D: F, H
..F0, ☑A, ☑A, ☑A, ☑B, ☑D, ☑∞, ☐D, ☐[F] Brak sąsiadów, powrót do D
.D0, ☑A, ☑A, ☑A, ☑B, ☑D, ☑∞, ☐D, ☐[D] Przeglądanie kolejnego sąsiada H
..H0, ☑A, ☑A, ☑A, ☑B, ☑D, ☑H, ☐D, ☑[H] Przeglądanie sąsiada G
...G0, ☑A, ☑A, ☑A, ☑B, ☑D, ☑H, ☑D, ☑[G] Brak nieodwiedzonych sąsiadów, powrót do H
..H0, ☑A, ☑A, ☑A, ☑B, ☑D, ☑H, ☑D, ☑[G] Brak nieodwiedzonych sąsiadów, powrót do D
.D0, ☑A, ☑A, ☑A, ☑B, ☑D, ☑H, ☑D, ☑[D] Brak nieodwiedzonych sąsiadów, powrót do A
A0, ☑A, ☑A, ☑A, ☑B, ☑D, ☑H, ☑D, ☑[A] Brak nieodwiedzonych sąsiadów, koniec algorytmu

Algorytm kończy działanie, ponieważ wierzchołek A nie ma już więcej sąsiadów, a wszystkie pozostałe wierzchołki zostały już odwiedzone. Z tego można wywnioskować, że graf jest spójny, ponieważ z wierzchołka A zostały osiągnięte wszystkie inne wierzchołki. Na koniec została uzyskana następująca tablica:

WęzełABCDEFGH
Poprzednik0AAABDHD

Na jej podstawie można prześledzić działanie algorytmu. Należy pamiętać, że znalezione tutaj drogi z pewnego wierzchołka do innego wcale nie muszą być najkrótsze. Przy takim zadaniu należy zastosować wtedy algorytm BFS.

Implementacja

Algorytm zostanie podzielony na dwie części: funkcję DFS(), która przygotuje tablicę i zainicjuje odwiedzanie oraz funkcję odwiedzWezel(), która będzie rekurencyjnie przechodziła po grafie dalej.

DFS

Poniżej została przedstawiona funkcja DFS(), która przyjmuje dwa argumenty: macierz - macierz sąsiedztwa grafu oraz start - wierzchołek początkowy od którego ma zostać rozpoczęte przeszukiwanie.

  1. static dane[] DFS(int[,] macierz, int start) {
  2.   dane[] tab = new dane[macierz.GetLength(0)];
  3.   for (int i = 0; i < macierz.GetLength(0); i++) {
  4.     tab[i].odwiedzony = false;
  5.     tab[i].poprzednik = -1;
  6.   }
  7.   odwiedzWezel(macierz, ref tab, start);
  8.   for (int i = 0; i < macierz.GetLength(0); i++)
  9.     if (!tab[i].odwiedzony)
  10.       odwiedzWezel(macierz, ref tab, i);
  11.   return tab;
  12. }

(2.) Inicjalizacja tablicy i (3. - 6.) jej początkowe ustawienie. Następnie (7.) odwiedź węzeł początkowy, a potem (8. - 10.) pozostałe, nieodwiedzone wierzchołki alfabetycznie. Na koniec (11.) zwróć obliczoną tablice.

Odwiedzanie wierzchołka

Odwiedzenie wierzchołka polega na przypisaniu, że został odwiedzony (bez tego rekurencja byłaby nieskończona), a następnie odwiedza wszystkich nieodwiedzonych sąsiadów wierzchołka przypisując im, że poprzednikiem jest aktualnie odwiedzony wierzchołek.

  1. static void odwiedzWezel(int[,] macierz, ref dane[] tab, int u) {
  2.   tab[u].odwiedzony = true;
  3.   for (int i = 0; i < macierz.GetLength(0); i++) {
  4.     if (macierz[u, i] > 0 && !tab[i].odwiedzony) {
  5.       tab[i].poprzednik = u;
  6.       odwiedzWezel(macierz, ref tab, i);
  7.     }
  8.   }
  9. }

(2.) Zaznacz, że wierzchołek został odwiedzony i (3.) wyszukaj sąsiadów, którzy (4.) nie zostali odwiedzeni, a następnie (5.) zaktualizuj ich poprzednika oraz (6.) odwiedź je.

Testowanie funkcji

Działanie napisanej funkcji można sprawdzić poniższym fragmentem kodu, który wczyta od użytkownika dane, a następnie wypisze wynik.

  1. static void wypiszdane(int i, dane d) {
  2.   Console.Write("{0}\t", i);
  3.   if (!d.odwiedzony) {
  4.     Console.Write("nieodwiedzony");
  5.   } else {
  6.     if (d.poprzednik == -1)
  7.       Console.Write("brak");
  8.     else Console.Write("{0}", d.poprzednik);
  9.   }
  10.   Console.WriteLine();
  11. }
  12. static void Main(string[] args) {
  13.   Console.Write("Podaj ile jest wierzchołków w grafie\n n = ");
  14.   int n = Convert.ToInt32(Console.ReadLine());
  15.   Console.Write("Podaj węzeł początkowy\n s = ");
  16.   int s = Convert.ToInt32(Console.ReadLine());
  17.   int[,] macierz = new int[n, n];
  18.   for (int i = 0; i < n; i++) {
  19.     string[] str = Console.ReadLine().Split(' ');
  20.     for (int j = 0; j < n; j++) {
  21.       macierz[i, j] = Convert.ToInt32(str[j]);
  22.     }
  23.   }
  24.   dane[] tab = DFS(macierz, s);
  25.   Console.WriteLine("Węzeł\tPoprzednik");
  26.   for (int i = 0; i < n; i++)
  27.     wypiszdane(i, tab[i]);
  28.   Console.ReadKey();
  29. }
Zadania
Zadanie 1
Kod źródłowy Zadanie 1Kod źródłowy Zadanie 1