I_R_W14_M38_Java Podejście zachłanne. Najkrótsza droga w grafie
Implementacja algorytmu Dijkstry w języku Java
W tej sekcji zapoznamy się z implementacją algorytmu Dijkstry w języku Java.
Specyfikacja problemu:
Dane:
listaSasiedztwa– lista sąsiedztwa spójnego grafu nieskierowanego o dodatnich wagachs– indeks wierzchołka początkowego grafuV– liczba naturalna dodatnia; liczba wierzchołków grafu
Wynik:
tablica przechowująca najkrótsze ścieżki od wierzchołka
sdo pozostałych wierzchołków oraz tablica przechowująca poprzedniki kolejnych wierzchołków
Działanie programu przetestujemy dla następującego grafu:

Przyjmijmy założenie, że wierzchołkiem początkowym jest wierzchołek E (o indeksie równym 4).
Oto lista sąsiedztwa tego grafu:
Przypomnijmy, w jaki sposób odczytywać listę sąsiedztwa.
Dla wierzchołka A mamy dwie tablice dwuelementowe. Pierwsza to {1,1}; jej pierwszy element to indeks sąsiedniego wierzchołka, a drugi – waga krawędzi. Odczytujemy to w ten sposób, że wierzchołek A jest połączony z wierzchołkiem B krawędzią o wadze 1. Analogicznie odczytujemy kolejną parę: wierzchołek A jest połączony z wierzchołkiem C krawędzią o wadze 4.
Zapis {-1,-1} oznacza, że dany wierzchołek nie jest połączony krawędzią z wierzchołkiem o danym indeksie. W przypadku wierzchołka A widzimy, że nie ma on połączeń z kolejnymi wierzchołkami (poza B oraz C), ponieważ odpowiednie podtablice przechowują dwie wartości -1.
W naszym przypadku ważona lista sąsiedztwa (czyli lista sąsiedztwa grafu z wagami [ważonego]) jest tablicą trójwymiarową.
Pierwszy wymiar określa liczbę wierzchołków grafu, drugi określa maksymalną liczbę sąsiadów, trzeci zaś składa się z dwóch elementów – jeden służy do przechowywania indeksu wierzchołka‑sąsiada, a drugi do przechowywania wagi krawędzi prowadzącej do tego sąsiada.
Zapiszemy w języku Java program, który dla danego grafu reprezentowanego przez listę sąsiedztwa wyświetlić dwie tablice: tablicę najkrótszych ścieżek do danych wierzchołków z wierzchołka początkowego oraz tablicę ich poprzedników.
Zaczynamy od importu niezbędnej biblioteki import java.util.Arrays;.
W kolejnym kroku definiujemy klasę publiczną GrafDijkstra oraz tworzymy cztery zmienne:
V– zmienna statyczna definiująca liczbę wierzchołków w grafie;maksymalnieSasiadow– zmienna statyczna, której przypisujemy różnicę wartości stałejVoraz liczby 1; wynika to z tego, że dany wierzchołek może mieć za sąsiadów maksymalnie wszystkie pozostałe wierzchołki;NIESKONCZONOSC– zmienna statyczna służąca do inicjowania odległości w algorytmie Dijkstry; przypiszemy jej odpowiednio dużą wartość (minimalna przypisana wartość powinna być większa od iloczynu liczby wierzchołków oraz największej z wag krawędzi);listaSasiedztwa– tablica trójwymiarowa reprezentująca listę sąsiedztwa grafu.
Alternatywą dla wykorzystania zmiennej NIESKONCZONOSC jest użycie stałej Integer.MAX_VALUE.
W kolejnym kroku definiujemy statyczną metodę Dijkstra, która przyjmie dwa parametry:
tablicę sąsiedztwa
listaSasiedztwa,indeks wierzchołka początkowego
s, czyli liczbę naturalną.
W programie konieczne jest zdefiniowanie tablic, z których każda pełni istotną funkcję:
d[V]– tablica długości ścieżek (liczb naturalnych); element na pozycjiioznacza aktualną długość ścieżki od wierzchołka początkowego do wierzchołkai;p[V]– tablica poprzedników najkrótszych ścieżek (liczb naturalnych); element na pozycjiioznacza indeks poprzedniego wierzchołka na najkrótszej ścieżce do wierzchołkai; za brak poprzednika uznajemy wartość-1;S[V]– tablica wierzchołków znajdujących się w zbiorzeS(wartości logicznych); jeśli element na pozycjiijest równytrue, oznacza to, że wierzchołekiznajduje się w zbiorzeS; domyślnie wszystkie elementy są równefalse.
Zapisujemy pętlę for. Będziemy iterować po elementach wcześniej utworzonych tablic. Dla każdego v-tego elementu tablic wykonamy następujące operacje:
v-temu elementowi tablicydprzypiszemy wartośćNIESKONCZONOSC;v-temu elementowi tablicypprzypiszemy wartość równą -1.
Po wyjściu z pętli elementowi tablicy d o indeksie s przypisujemy wartość 0, ponieważ odległość wierzchołka początkowego do siebie samego wynosi 0.
Deklarujemy również zmienną odwiedzony - będzie ona licznikiem wierzchołków, które zostały odwiedzone. Gdy jej wartość będzie równa wartości przechowywanej przez zmienną V (całkowitą liczbę wierzchołków w grafie), znaczy to, że wszystkie wierzchołki zostały przetworzone i algorytm może zakończyć swoje działanie.
Zapisujemy pętlę while, która będzie wykonywać się tak długo, aż znajdziemy najkrótsze ścieżki i poprzedników dla wszystkich wierzchołków grafu.
W pętli deklarujemy zmienną u, którą inicjalizujemy wartością -1. Zmienna ta posłuży jako flaga i będzie wskazywać, czy udało się znaleźć odpowiedni wierzchołek – taki, który nie został jeszcze odwiedzony, a jego odległość od sprawdzanego wierzchołka jest najmniejsza.
W pętli while zapisujemy pętlę for. Iterujemy przez wierzchołki grafu.
W pętli for zapisujemy instrukcję warunkową. By instrukcja warunkowa zwróciła wartość true, muszą zostać spełnione dwa warunki:
sprawdzany wierzchołek nie został jeszcze odwiedzony (
!S[i], gdzieSto tablica wartości logicznych wskazująca, czy wierzchołek został odwiedzony);uwciąż jest równe-1(co oznacza, że jest to pierwszy znaleziony nieodwiedzony wierzchołek) lub tymczasowa odległośćd[i]do tego wierzchołka jest mniejsza niż tymczasowa odległośćd[u]do wierzchołka, który znaleźliśmy wcześniej.
Jeśli oba warunki są spełnione, przypisujemy bieżący indeks i do u. Po zakończeniu tej pętli zmienna u będzie przechowywać indeks nieodwiedzonego wierzchołka o najmniejszej tymczasowej odległości lub pozostanie równa -1, jeśli wszystkie wierzchołki zostały już odwiedzone.
Po wybraniu wierzchołka u z najkrótszą odległością oznaczamy go jako odwiedzony. Następnie zwiększamy licznik odwiedzonych wierzchołków.
Zapisujemy kolejną pętlę for, która będzie iterować po sąsiadach u-tego wierzchołka. Pętla wykonuje się dla każdego wierzchołka u, który został wybrany jako wierzchołek o obecnie najkrótszej odległości od wierzchołka początkowego, ale nie został jeszcze odwiedzony. Pętla ta ma dwa warunki:
i < maksymalnieSasiadow– warunek, którym sprawdzamy, czy nie przekraczamy maksymalnej liczby możliwych sąsiadów (w przypadku grafu pełnego każdy wierzchołek miałbyV - 1sąsiadów);i < listaSasiedztwa[u].length– warunek, którym sprawdzamy, czy osiągnęliśmy koniec listy sąsiadów dla wierzchołkau.
Dla każdego sąsiada wierzchołka u sprawdzamy, czy znaleźliśmy krótszą ścieżkę i możemy zaktualizować znaną odległość od wierzchołka początkowego (s) do tego sąsiada (v).
Pobieramy dane z tablicy listaSasiedztwa.
Przypisujemy zmiennej v (indeksowi wierzchołka sąsiadującego z wierzchołkiem u) wartość listaSasiedztwa[u][i][0], a zmiennej waga (wadze krawędzi między wierzchołkiem u oraz jego sąsiadem‑wierzchołkiem v) – wartość listaSasiedztwa[u][i][1].
Dodajemy warunek przerwania pętli. Jeśli w trakcie iteracji natrafimy na wartość -1 dla v, to znaczy, że osiągnęliśmy koniec listy sąsiadów dla wierzchołka u, zatem nie ma potrzeby dalszego iterowania.
Następnie zapisujemy instrukcję warunkową, w której sprawdzamy, czy obecna odległość do wierzchołka v (czyli wartość przechowywana w d[v]) jest większa niż suma odległości do wierzchołka u (d[u]) i wagi krawędzi między wierzchołkami u oraz v (czyli waga). Innymi słowy, czy przejście przez u do v jest krótsze niż bezpośrednie połączenie do v (jeśli takie istnieje).
Jeśli tak, oznacza to, że znaleźliśmy krótszą ścieżkę do wierzchołka v poprzez wierzchołek u. W konsekwencji aktualizujemy odległość d[v] i ustawiamy wierzchołek u jako poprzednik p[v] dla wierzchołka v.
Po wykonaniu tego fragmentu kodu dla każdego wierzchołka sąsiadującego z u mamy pewność, że odległość d[v] do niego jest najkrótszą znaną odległością, a p[v] przechowuje wierzchołek, przez który prowadzi ta najkrótsza ścieżka.
Dodajemy instrukcje odpowiedzialne za wywołanie funkcji i odpowiednie wyświetlenie wyników.
Oto kompletny kod programu:
Wynik działania programu dla analizowanego grafu:
Spróbuj ręcznie zweryfikować poprawność uzyskanego rozwiązania.
Słownik
suma wag wszystkich krawędzi, z których składa się ścieżka, droga lub cykl
graf, którego połączone krawędziami wierzchołki nie mają ustalonego kierunku, w którym można się po nich poruszać (są nieuporządkowane)
graf, w którym każda para wierzchołków połączona jest drogą
graf którego krawędzie mają przypisane wagi
nieuporządkowana para (niekoniecznie różnych) elementów zbioru wierzchołków, tj. takich, które są ze sobą połączone; w reprezentacji graficznej jest to linia łącząca te wierzchołki; krawędź może łączyć ze sobą jeden wierzchołek (wtedy nazywana jest pętlą)
zestaw V uporządkowanych list, gdzie lista o indeksie i jest listą wszystkich sąsiadów wierzchołka i
taka ścieżka łącząca dwa wierzchołki grafu, której suma wag krawędzi jest jak najmniejsza
liczba krawędzi incydentnych z danym wierzchołkiem
wartość przypisana krawędzi grafu
inaczej: punkt, węzeł; element niepustego zbioru, który wraz ze zbiorem krawędzi tworzy graf