Strona główna » Algorytmy » Struktury Danych » Przeszukiwanie Wszerz (BFS)
 

Przeszukiwanie Wszerz (BFS)

Cel

Algorytm przeszukiwania grafu wszerz (BFS) może służyć do sprawdzenia czy dany graf jest spójny, albo do znalezienia najkrótszej drogi pomiędzy dwoma wybranymi wierzchołkami grafu. Bardzo często jest on również wykorzystywany do sprawdzenia czy dany graf jest dwudzielny.

Algorytm

Algorytm Poszukiwania Wszerz wymaga określenia punktu początkowego oraz przypisania każdemu wierzchołkowi dwóch właściwości: odległość o wartości początkowej nieskończoność, oraz poprzednik, który początkowo jest pusty. Wyjątkiem jest punkt startowy, którego odległość początkowa wynosi 0. Początkowo kolejka składa się tylko z punktu początkowego.

Dopóki kolejka nie jest pusta pobieramy pierwszy jej element (usuwając go z kolejki). Następnie sprawdzamy, który z jego sąsiadów nie został odwiedzony tzn. nie ma ustawionego poprzednika. Sąsiadów wybieramy alfabetycznie (lub rosnąco) jeśli nie zostało wskazane inaczej w zadaniu. Dla każdego nieodwiedzonego dotychczas sąsiada ustawiamy odległość na odległość wybranego elementu z kolejki i dodajemy wagę krawędzi (dla grafu bez wag przyjmujemy 1) i ustawiamy poprzednika na aktualnie wybrany element. Algorytm kończy się, gdy kolejka będzie pusta.

Przykład

Przypuśćmy, że mam następujący graf:

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

KolejkaABCDEFGHDodane do kolejki
-0, -∞, -∞, -∞, -∞, -∞, -∞, -∞, --
A0, -1, A1, A1, A∞, -∞, -∞, -∞, -B, C, D
B, C, D0, -...2, B∞, -∞, -∞, -E
C, D, E0, -....2, C∞, -∞, -F
D, E, F0, -.....∞, -∞, --
E, F0, -.....∞, -∞, --
F0, -.....∞, -∞, --
-0, -1, A1, A1, A2, B2, C∞, -∞, --

Z tabeli można wyczytać, że w odległości 1 od punktu początkowego A znajdują się węzły B, C oraz D, a w odległości 2 węzły E, F. Warto zwrócić uwagę, że do punktu F można dojść w drogą tej samej długości idąc kolejno A > C > F, albo A > D > F. Punkty G i H nie zostały odwiedzone, więc nie istnieje żadna droga do nich z węzła A. To świadczy również o tym, że graf nie jest spójny.

Implementacja

Napisz funkcję BFS, która na podstawie podanej macierzy sąsiedztwa oraz początkowego wypisze tablice z informacjami jak daleko jest z punktu początkowego do danego i który węzeł jest poprzednikiem. Przetestuj działanie funkcji.

Rozwiązanie

Wynik

Dane dotyczące węzła tj. jego odległość oraz poprzednik będą przechowywane w specjalnym typie.

  1. struct dane {
  2.   int odleglosc;
  3.   int poprzednik;
  4. };

Dane dotyczące węzła tj. jego odległość oraz poprzednik będą przechowywane w specjalnym typie.

Funkcja BFS

Oto przykładowe rozwiązanie funkcji realizującej algorytm BFS.

  1. dane* BFS(int** macierz, int n, int start) {
  2.   dane* tab = new dane[n];
  3.   for (int i = 0; i < n; i++) {
  4.     tab[i].odleglosc = INT32_MAX;
  5.     tab[i].poprzednik = -1;
  6.   }
  7.   tab[start].odleglosc = 0;
  8.   queue<int> q;
  9.   q.push(start);
  10.   while (q.size() != 0) {
  11.     int u = q.front();
  12.     q.pop();
  13.     for (int i = 0; i < n; i++) {
  14.       if (macierz[u][i] > 0 && tab[i].odleglosc == INT32_MAX) {
  15.         tab[i].odleglosc = tab[u].odleglosc + 1;
  16.         tab[i].poprzednik = u;
  17.         q.push(i);
  18.       }
  19.     }
  20.   }
  21.   return tab;
  22. }

(2. - 6.) Przygotuj tabelę na zapis informacji o przejściach i (7.) oznacz punkt początkowy. Następnie (8.) utwórz kolejke i (9.) włóż do niej węzeł początkowy. (10.) Dopóki kolejka nie jest pusta (11. - 12.) pobierz pierwszy element z kolejki i (13.) rozpocznij sprawdzanie jego sąsiadów. Jeśli (14.) krawędź do sąsiada istnieje i nie był odwiedzony to (15. - 16.) odwiedź go i (17.) dołącz na koniec kolejki.

Testowanie funkcji

Do przetestowania napisanej funkcji można skorzystać z poniższego fragmentu kodu, który wczytuje od użytkownika potrzebne dane, a następnie wypisuje odpowiednio sformatowany wynik.

  1. int main () {
  2.   int n, s;
  3.   cout << "Podaj ile jest wierzchołków w grafie\n n = ";
  4.   cin >> n;
  5.   cout << "Podaj węzeł początkowy\n s = ";
  6.   cin >> s;
  7.   int** macierz = new int*[n];
  8.   for (int i = 0; i < n; i++) {
  9.     macierz[i] = new int[n];
  10.     for (int j = 0; j < n; j++) {
  11.       cin >> macierz[i][j];
  12.     }
  13.   }
  14.   dane* tab = BFS(macierz, n, s);
  15.   cout << "Wezel\tOdl.\tPoprzednik" << endl;
  16.   for (int i = 0; i < n; i++)
  17.     wypiszdane(i, tab[i]);
  18.   system("pause");
  19.   return 0;
  20. }

Do ładnego sformatowania danych dotyczących węzła została dopisana funkcja wypiszdane(). Rozpoznaje ona niektóre wartości domyślne i na tej podstawie wyświetla odpowiedni opis zamiast liczb.

  1. void wypiszdane(int i, dane d) {
  2.   cout << i << "\t";
  3.   if (d.odleglosc == INT32_MAX) {
  4.     cout << "nieosiagalny";
  5.   } else {
  6.     cout << d.odleglosc << "\t";
  7.     if (d.poprzednik == -1)
  8.       cout << "brak";
  9.     else cout << d.poprzednik;
  10.   }
  11.   cout << endl;
  12. }

W celu przetestowania grafu przedstawionego w przykładzie należałoby podać n = 8, s = 0 oraz wpisać nastepującą macierz:

  1. 0  1  1  1  0  0  0  0
  2. 1  0  0  0  1  0  0  0
  3. 1  0  0  0  0  1  0  0
  4. 1  0  0  0  0  1  0  0
  5. 0  1  0  0  0  1  0  0
  6. 0  0  1  1  1  0  0  0
  7. 0  0  0  0  0  0  0  1
  8. 0  0  0  0  0  0  1  0
Zadania
Zadanie 1
Kod źródłowy Zadanie 1Kod źródłowy Zadanie 1