Algorytm Dijkstry – wprowadzenie

Algorytm Dijkstry służy do wyznaczania najkrótszych ścieżek w grafach skierowanych oraz nieskierowanych o dodatnich wagach. Jego zadaniem jest wyznaczenie najkrótszej ścieżkinajkrótsza ścieżkanajkrótszej ścieżki z wierzchołka początkowego do pozostałych osiągalnych wierzchołków.

R112S9fofqK9B1
Edsger W. Dijkstra, twórca algorytmu
Źródło: Hamilton Richards, dostępny w internecie: commons.wikimedia.org [dostęp 18.10.2023], licencja: CC BY-SA 3.0.

Algorytm Dijkstry wykorzystuje podejście zachłanne.

Algorytmy zachłanne charakteryzują się tym, że dokonują wyboru, który jest najlepszy lokalnie, w nadziei na znalezienie najlepszego rozwiązania całego problemu.

Więcej na temat algorytmów zachłannych znajdziesz w e‑materiale Algorytmy zachłanneP1Ed8GL2MAlgorytmy zachłanne i pozostałych e‑materiałach z tej serii.

W kolejnych krokach wybierany jest (spośród tych wierzchołków, które nie zostały jeszcze odwiedzone) ten wierzchołek, do którego prowadzi najkrótsza ścieżka.

Algorytm Dijkstry może przydać ci się przy wyznaczaniu najkrótszej drogi do danego miejsca – w takim wypadku skrzyżowania to węzły grafu, a długości ulic – wagi krawędzi.

Wykorzystuje się go również w sieciach komputerowych, czego przykładem może być trasowanie (z ang. routing), które opisujemy w e‑materiale TrasowaniePmMyRnG3LTrasowanie.

Zasada działania algorytmu

Przyjmijmy następujące założenia:

  • analizowany graf to graf spójnygraf spójnygraf spójny o dodatnich wagach;

  • wierzchołek s to wierzchołek początkowy, od którego wyznaczane będą ścieżki do wszystkich pozostałych wierzchołków;

  • zbiór V to zbiór wszystkich wierzchołków grafu;

  • zbiór S będzie zawierał wierzchołki, dla których długości ścieżekdługość ścieżki (koszt dojścia)długości ścieżek zostały już wyznaczone; na samym początku zbiór S jest pusty;

  • tablica d[], w której dla każdego wierzchołka przechowujemy wyznaczoną długość ścieżki d[u] z wierzchołka początkowego s;

  • tablica p[] jest tablicą poprzedników na najkrótszej ścieżce do wierzchołka początkowego.

Długość ścieżki prowadzącej do wierzchołka początkowego s wynosi 0 (d[s] = 0), a dla wszystkich pozostałych długość ta na początku jest równa . W implementacji zwykle jako nieskończoność przyjmuje się wystarczająco dużą liczbę, co do której mamy pewność, że jest większa niż wartość długości jakiejkolwiek ścieżki – z tego powodu może być też to liczba, która jest większa od liczby wierzchołków pomnożonych przez najdłuższą krawędź grafu.

Ważne!

Oznaczenie w(u,v) przyjmujemy za wagę krawędzi uv.

Lista kroków algorytmu:

  1. Tworzymy dwa zbiory wierzchołków – V oraz S.

  2. Dla wszystkich wierzchołków grafu (oznaczonych jako u) za wyjątkiem początkowego (oznaczonego jako s) ustawiamy długość ścieżek (czyli elementów tablicy d) na nieskończoność.

  3. Wartość d[s] ustawiamy na 0.

  4. Ustawiamy poprzednik każdego wierzchołka (elementy tablicy p) jako niezdefiniowany (możemy użyć np. wartości -1).

  5. Dopóki zbiór V nie jest pusty:

    • Wybieramy ze zbioru V wierzchołek u o najmniejszym koszcie dojścia do niego (d[u]).

    • Wybrany wierzchołek u usuwamy ze zbioru V i dodajemy do zbioru S.

    • Dla każdego sąsiada wierzchołka u (oznaczonego jako v) sprawdzamy, czy d[v] > d[u] + w(u,v).

      • Jeśli d[v] > d[u] + w(u,v), wyznaczamy nową długość ścieżki do wierzchołka v oraz wierzchołek u zostaje poprzednikiem wierzchołka v.

      • Jeśli nie, nic się nie dzieje.

Przykład wykorzystania

Zastosujmy algorytm Dijkstry dla przykładowego grafu w celu wyznaczenia najkrótszych ścieżek z wierzchołka A do wszystkich pozostałych.

Oto przykładowy graf:

R1JumOOoOfy4z
Źródło: Contentplus.pl Sp. z o.o., licencja: CC BY-SA 3.0.

Jak już zostało wskazane, wierzchołkiem początkowym jest wierzchołek A.

Linia 1. s znak równości A.

Naszym zadaniem jest wyznaczenie najkrótszych ścieżek prowadzących z wierzchołka początkowego A do wszystkich pozostałych wierzchołków oraz najkrótszych ścieżek pomiędzy wierzchołkiem początkowym a wszystkimi pozostałymi.

Tworzymy dwa zbiory.

Zbiór S początkowo jest pusty.

Linia 1. S znak równości otwórz nawias klamrowy zamknij nawias klamrowy.

Zbiór V zawiera wszystkie wierzchołki zbioru

Linia 1. V znak równości otwórz nawias klamrowy A przecinek B przecinek C przecinek D przecinek E przecinek F przecinek G zamknij nawias klamrowy.

Tworzymy dwie n-elementowe tablice: d oraz p. Liczba n to liczba wierzchołków grafu.

Tablica d przechowuje długości najkrótszych ścieżek do poszczególnych wierzchołków z wierzchołka początkowego.

Tablica p przechowuje informację na temat poprzedników na ścieżce do danego wierzchołka.

Utwórzmy tabelę, w której będziemy aktualizować stan poszczególnych ścieżek.

u

A

B

C

D

E

F

G

d[u]

p[u]

Uzupełniamy początkowy stan tabeli zgodnie z krokami 2–4 algorytmu.

u

A

B

C

D

E

F

G

d[u]

0

p[u]

-1

-1

-1

-1

-1

-1

-1

W zbiorze V szukamy wierzchołka u o najmniejszym koszcie dojścia do niego (d[u]). To początek wykonywania algorytmu, zatem jest to wierzchołek początkowy, którego d[u] = 0 (ponieważ jest to wierzchołek A, to d[A] = 0). Długości ścieżek do pozostałych wierzchołków są większe.

Wierzchołek przenosimy zatem ze zbioru V do S. Przeglądamy wszystkich sąsiadów przeniesionego wierzchołka. To wierzchołki C oraz B.

Linia 1. d otwórz nawias kwadratowy B zamknij nawias kwadratowy znak równości ∞. Linia 2. d otwórz nawias kwadratowy C zamknij nawias kwadratowy znak równości ∞. Linia 4. d otwórz nawias kwadratowy A zamknij nawias kwadratowy znak równości 0.

Sprawdzamy warunek:

Linia 1. d otwórz nawias kwadratowy B zamknij nawias kwadratowy zamknij nawias ostrokątny d otwórz nawias kwadratowy A zamknij nawias kwadratowy plus 1.

Jest prawdziwy, ponieważ suma 0 oraz 1 jest mniejsza od nieskończoności, zatem:

Linia 1. d otwórz nawias kwadratowy B zamknij nawias kwadratowy ← d otwórz nawias kwadratowy A zamknij nawias kwadratowy plus 1.

W konsekwencji uzupełniamy pierwszą komórkę tabeli i do p[B] wpisujemy A, czyli poprzednik na ścieżce, a do d[B] wpisujemy 1, czyli długość ścieżki.

u

A

B

C

D

E

F

G

d[u]

0

1

p[u]

-1

A

-1

-1

-1

-1

-1

Analogicznie postępujemy dla wierzchołka C.

u

A

B

C

D

E

F

G

d[u]

0

1

4

p[u]

-1

A

A

-1

-1

-1

-1

Stan zbiorów:

Linia 1. S znak równości otwórz nawias klamrowy A zamknij nawias klamrowy. Linia 2. V znak równości otwórz nawias klamrowy B przecinek C przecinek D przecinek E przecinek F przecinek G zamknij nawias klamrowy.

Ponownie ze zbioru V wybieramy wierzchołek u o najniższym koszcie dojścia do niego (d[u]).

Tym razem jest to wierzchołek B. Przenosimy go do zbioru S i sprawdzamy jego sąsiadów.

Linia 1. d otwórz nawias kwadratowy A zamknij nawias kwadratowy znak równości 0. Linia 2. d otwórz nawias kwadratowy E zamknij nawias kwadratowy znak równości ∞. Linia 3. d otwórz nawias kwadratowy F zamknij nawias kwadratowy znak równości ∞. Linia 4. d otwórz nawias kwadratowy G zamknij nawias kwadratowy znak równości ∞. Linia 6. d otwórz nawias kwadratowy B zamknij nawias kwadratowy znak równości 1.

Sprawdzamy warunki.

Linia 1. d otwórz nawias kwadratowy A zamknij nawias kwadratowy zamknij nawias ostrokątny d otwórz nawias kwadratowy B zamknij nawias kwadratowy plus 1. Linia 2. d otwórz nawias kwadratowy E zamknij nawias kwadratowy zamknij nawias ostrokątny d otwórz nawias kwadratowy B zamknij nawias kwadratowy plus 1. Linia 3. d otwórz nawias kwadratowy F zamknij nawias kwadratowy zamknij nawias ostrokątny d otwórz nawias kwadratowy B zamknij nawias kwadratowy plus 3. Linia 4. d otwórz nawias kwadratowy G zamknij nawias kwadratowy zamknij nawias ostrokątny d otwórz nawias kwadratowy B zamknij nawias kwadratowy plus 6.

Patrząc od góry, pierwszy warunek nie jest spełniony, ponieważ zero jest mniejsze od 2, zatem przechodzimy dalej.

Pozostałe warunki są spełnione – nieskończoność jest większa od 2, 4 oraz 7.

Zatem:

Linia 1. d otwórz nawias kwadratowy E zamknij nawias kwadratowy ← d otwórz nawias kwadratowy B zamknij nawias kwadratowy plus 1. Linia 2. d otwórz nawias kwadratowy F zamknij nawias kwadratowy ← d otwórz nawias kwadratowy B zamknij nawias kwadratowy plus 3. Linia 3. d otwórz nawias kwadratowy G zamknij nawias kwadratowy ← D otwórz nawias kwadratowy B zamknij nawias kwadratowy plus 6.

Uzupełniamy tabelę.

u

A

B

C

D

E

F

G

d[u]

0

1

4

2

4

7

p[u]

-1

A

A

-1

B

B

B

Stan zbiorów:

Linia 1. S znak równości otwórz nawias klamrowy A przecinek B zamknij nawias klamrowy. Linia 2. V znak równości otwórz nawias klamrowy C przecinek D przecinek E przecinek F przecinek G zamknij nawias klamrowy.

Kolejnym wierzchołkiem w zbiorze V, do którego ścieżka (d[u]) jest najkrótsza, jest wierzchołek E.

Przenosimy go do zbioru S i sprawdzamy jego sąsiadów oraz odpowiednie warunki.

Linia 1. d otwórz nawias kwadratowy B zamknij nawias kwadratowy znak równości 1. Linia 3. d otwórz nawias kwadratowy E zamknij nawias kwadratowy znak równości 2. Linia 5. d otwórz nawias kwadratowy B zamknij nawias kwadratowy zamknij nawias ostrokątny d otwórz nawias kwadratowy E zamknij nawias kwadratowy plus 1.

Nie, zatem nic się nie zmienia.

Stan zbiorów:

Linia 1. S znak równości otwórz nawias klamrowy A przecinek B przecinek E zamknij nawias klamrowy. Linia 2. V znak równości otwórz nawias klamrowy C przecinek D przecinek F przecinek G zamknij nawias klamrowy.

Kolejnym wierzchołkiem w zbiorze V, do którego ścieżka (d[u]) jest najkrótsza, jest wierzchołek C.

Przenosimy go do zbioru S i sprawdzamy jego sąsiadów oraz odpowiednie warunki.

Linia 1. d otwórz nawias kwadratowy A zamknij nawias kwadratowy znak równości 0. Linia 2. d otwórz nawias kwadratowy D zamknij nawias kwadratowy znak równości ∞. Linia 3. d otwórz nawias kwadratowy G zamknij nawias kwadratowy znak równości 7. Linia 5. d otwórz nawias kwadratowy C zamknij nawias kwadratowy znak równości 4. Linia 7. d otwórz nawias kwadratowy A zamknij nawias kwadratowy zamknij nawias ostrokątny d otwórz nawias kwadratowy C zamknij nawias kwadratowy plus 4. Linia 8. d otwórz nawias kwadratowy D zamknij nawias kwadratowy zamknij nawias ostrokątny d otwórz nawias kwadratowy C zamknij nawias kwadratowy plus 5. Linia 9. d otwórz nawias kwadratowy G zamknij nawias kwadratowy zamknij nawias ostrokątny d otwórz nawias kwadratowy C zamknij nawias kwadratowy plus 2. Linia 11. d otwórz nawias kwadratowy D zamknij nawias kwadratowy ← d otwórz nawias kwadratowy C zamknij nawias kwadratowy plus 5. Linia 12. d otwórz nawias kwadratowy G zamknij nawias kwadratowy ← d otwórz nawias kwadratowy C zamknij nawias kwadratowy plus 2.

Uzupełniamy tabelę. Zwróć uwagę, że zmieniamy wcześniej wpisaną wartość.

u

A

B

C

D

E

F

G

d[u]

0

1

4

9

2

4

6

p[u]

-1

A

A

C

B

B

C

Stan zbiorów:

Linia 1. S znak równości otwórz nawias klamrowy A przecinek B przecinek C przecinek E zamknij nawias klamrowy. Linia 2. V znak równości otwórz nawias klamrowy D przecinek F przecinek G zamknij nawias klamrowy.

Kolejnym wierzchołkiem w zbiorze V, do którego ścieżka (d[u]) jest najkrótsza, jest wierzchołek G.

Przenosimy go do zbioru S i sprawdzamy jego sąsiadów oraz odpowiednie warunki.

Linia 1. d otwórz nawias kwadratowy B zamknij nawias kwadratowy znak równości 1. Linia 2. d otwórz nawias kwadratowy C zamknij nawias kwadratowy znak równości 4. Linia 4. d otwórz nawias kwadratowy G zamknij nawias kwadratowy znak równości 7. Linia 6. d otwórz nawias kwadratowy B zamknij nawias kwadratowy zamknij nawias ostrokątny d otwórz nawias kwadratowy G zamknij nawias kwadratowy plus 6. Linia 7. d otwórz nawias kwadratowy C zamknij nawias kwadratowy zamknij nawias ostrokątny d otwórz nawias kwadratowy G zamknij nawias kwadratowy plus 2.

W tabeli nic się nie zmienia.

Stan zbiorów:

Linia 1. S znak równości otwórz nawias klamrowy A przecinek B przecinek C przecinek E przecinek G zamknij nawias klamrowy. Linia 2. V znak równości otwórz nawias klamrowy D przecinek F zamknij nawias klamrowy.

Kolejnym wierzchołkiem w zbiorze V, do którego ścieżka (d[u]) jest najkrótsza, jest wierzchołek D.

Przenosimy go do zbioru S i sprawdzamy jego sąsiadów oraz odpowiednie warunki.

Linia 1. d otwórz nawias kwadratowy C zamknij nawias kwadratowy znak równości 4. Linia 2. d otwórz nawias kwadratowy F zamknij nawias kwadratowy znak równości 4. Linia 4. d otwórz nawias kwadratowy D zamknij nawias kwadratowy znak równości 9. Linia 6. d otwórz nawias kwadratowy C zamknij nawias kwadratowy zamknij nawias ostrokątny d otwórz nawias kwadratowy D zamknij nawias kwadratowy plus 5. Linia 7. d otwórz nawias kwadratowy F zamknij nawias kwadratowy zamknij nawias ostrokątny d otwórz nawias kwadratowy D zamknij nawias kwadratowy plus 12.

W tabeli nic się nie zmienia.

Stan zbiorów:

Linia 1. S znak równości otwórz nawias klamrowy A przecinek B przecinek C przecinek D przecinek E przecinek G zamknij nawias klamrowy. Linia 2. V znak równości otwórz nawias klamrowy F zamknij nawias klamrowy.

Ostatnim wierzchołkiem w zbiorze V jest wierzchołek F.

Przenosimy go do zbioru S i sprawdzamy jego sąsiadów oraz odpowiednie warunki.

Linia 1. d otwórz nawias kwadratowy B zamknij nawias kwadratowy znak równości 1. Linia 2. d otwórz nawias kwadratowy D zamknij nawias kwadratowy znak równości 9. Linia 4. d otwórz nawias kwadratowy F zamknij nawias kwadratowy znak równości 4. Linia 6. d otwórz nawias kwadratowy B zamknij nawias kwadratowy zamknij nawias ostrokątny d otwórz nawias kwadratowy F zamknij nawias kwadratowy plus 3. Linia 7. d otwórz nawias kwadratowy D zamknij nawias kwadratowy zamknij nawias ostrokątny d otwórz nawias kwadratowy F zamknij nawias kwadratowy plus 12.

W tabeli nic się nie zmienia.

Uzupełniona tabela prezentuje się następująco:

u

A/p>

B

C

D

E

F

G

d[u]

0

1

4

9

2

4

6

p[u]

-1

A

A

C

B

B

C

Algorytm Dijkstry nazywamy zachłannym, ponieważ na każdym etapie dokonuje wyboru lokalnie najkorzystniejszego z nadzieją znalezienia rozwiązania globalnie optymalnego. Taką decyzją jest wybranie wierzchołka o najmniejszym koszcie, a następnie dodanie go do zbioru S.

Zastosowanie algorytmów zachłannych nie zawsze gwarantuje uzyskanie najlepszych możliwych wyników. W przypadku algorytmu Dijkstry rzeczywiście tak jest – odnajdujemy najkrótsze ścieżki.

Słownik

długość ścieżki (koszt dojścia)
długość ścieżki (koszt dojścia)

suma wag wszystkich krawędzi, z których składa się ścieżka, droga lub cykl

graf
graf

struktura składająca się z niepustego zbioru wierzchołków oraz rodziny krawędzi; dla grafu G zbiór wierzchołków oznaczamy VG (ang. verticies – wierzchołki), a rodzinę krawędzi –EG (ang. edges – krawędzie); liczbę elementów zbioru VG oznaczamy literą  n; liczbę elementów rodziny EGm

graf spójny
graf spójny

graf, w którym każda para wierzchołków połączona jest drogą

graf ważony
graf ważony

graf którego krawędzie mają przypisane wagi

najkrótsza ścieżka
najkrótsza ścieżka

taka ścieżka łącząca dwa wierzchołki grafu, której suma wag krawędzi jest jak najmniejsza

krawędź
krawędź

nieuporządkowana para (niekoniecznie różnych) elementów zbioru wierzchołków

incydencja
incydencja

relacja między wierzchołkami a łączącą je krawędzią grafu; mówimy, że dwa sąsiednie wierzchołki połączone pewną krawędzią są incydentne z tą krawędzią

izomorfizm
izomorfizm

właściwość grafów polegająca na tym, że między grafami występuje wzajemnie jednoznaczna odpowiedniość wierzchołków; liczba krawędzi łączących dane dwa wierzchołki jednego grafu jest równa liczbie krawędzi łączących odpowiadające im wierzchołki drugiego grafu

lista sąsiedztwa
lista sąsiedztwa

zestaw |V| uporządkowanych list, gdzie lista o indeksie jest listą wszystkich sąsiadów wierzchołka

macierz
macierz

dwuwymiarowa tablica elementów (zwykle liczbowych), licząca n wierszy i m kolumn

macierz incydencji
macierz incydencji

macierz zerojedynkowa rozmiaru |V|×|E|, dla której każda krawędź grafu musi mieć unikatowy indeks; wyraz jest równy , o ile krawędź o indeksie jest incydentna do wierzchołka

macierz sąsiedztwa
macierz sąsiedztwa

macierz zerojedynkowa rozmiaru |V|×|V|, w której wyraz jest równy , jeśli wierzchołki oraz są sąsiednie, a  w przeciwnym przypadku; macierz sąsiedztwa jest symetryczna dla grafów nieskierowanych

stopień wierzchołka
stopień wierzchołka

liczba krawędzi incydentnych z danym wierzchołkiem

typ generyczny
typ generyczny

klasa lub interfejs sparametryzowany względem typów podanych przy deklaracji; do określenia parametrów typu używamy nawiasów ostrych (<>); typy generyczne umożliwiają napisanie ogólnej definicji, która działa z różnymi typami, pozwalając tym samym na ponowne użycie kodu bez konieczności definiowania nowych definicji klas lub interfejsu działających na innym typie zmiennych

waga krawędzi
waga krawędzi

wartość przypisana krawędzi grafu

wierzchołek
wierzchołek

element należący do zbioru wierzchołków