Algorytm Dijkstry

Algorytm Dijkstry
Ilustracja
Ilustracja działania algorytmu
Rodzaj

Znajdowanie najkrótszej ścieżki

Struktura danych

graf

Złożoność
Czasowa

O ( E log V ) {\displaystyle O(E\cdot \log V)}

Pamięciowa

O ( E + V log V ) {\displaystyle O(E+V\cdot \log V)} - przy użyciu kopca Fibonacciego

Algorytm Dijkstry, opracowany przez holenderskiego informatyka Edsgera Dijkstrę, służy do znajdowania najkrótszej ścieżki z pojedynczego źródła w grafie o nieujemnych wagach krawędzi.

Działanie

Mając dany graf z wyróżnionym wierzchołkiem (źródłem) algorytm znajduje odległości od źródła do wszystkich pozostałych wierzchołków. Łatwo zmodyfikować go tak, aby szukał wyłącznie (najkrótszej) ścieżki do jednego ustalonego wierzchołka, po prostu przerywając działanie w momencie dojścia do wierzchołka docelowego, bądź transponując tablicę incydencji grafu.

Algorytm Dijkstry znajduje w grafie wszystkie najkrótsze ścieżki pomiędzy wybranym wierzchołkiem a wszystkimi pozostałymi, przy okazji wyliczając również koszt przejścia każdej z tych ścieżek[1].

Algorytm Dijkstry jest przykładem algorytmu zachłannego[2].

Algorytm

Przez s {\displaystyle s} oznaczamy wierzchołek źródłowy, w ( i , j ) {\displaystyle w(i,j)} to waga krawędzi ( i , j ) {\displaystyle (i,j)} w grafie.

  • Stwórz tablicę d {\displaystyle d} odległości od źródła dla wszystkich wierzchołków grafu. Na początku d [ s ] = 0 , {\displaystyle d[s]=0,} zaś d [ v ] = {\displaystyle d[v]=\infty } dla wszystkich pozostałych wierzchołków.
  • Utwórz kolejkę priorytetową Q {\displaystyle Q} wszystkich wierzchołków grafu. Priorytetem kolejki jest aktualnie wyliczona odległość od wierzchołka źródłowego s . {\displaystyle s.}
  • Dopóki kolejka nie jest pusta:
    • Usuń z kolejki wierzchołek u {\displaystyle u} o najniższym priorytecie (wierzchołek najbliższy źródła, który nie został jeszcze rozważony)
    • Dla każdego sąsiada v {\displaystyle v} wierzchołka u {\displaystyle u} dokonaj relaksacji poprzez u : {\displaystyle u{:}} jeśli d [ u ] + w ( u , v ) < d [ v ] {\displaystyle d[u]+w(u,v)<d[v]} (poprzez u {\displaystyle u} da się dojść do v {\displaystyle v} szybciej niż dotychczasową ścieżką), to d [ v ] := d [ u ] + w ( u , v ) . {\displaystyle d[v]:=d[u]+w(u,v).}

Na końcu tablica d {\displaystyle d} zawiera najkrótsze odległości do wszystkich wierzchołków.

Dodatkowo możemy w tablicy poprzednik przechowywać dla każdego wierzchołka numer jego bezpośredniego poprzednika na najkrótszej ścieżce, co pozwoli na odtworzenie pełnej ścieżki od źródła do każdego wierzchołka – przy każdej relaksacji w ostatnim punkcie u {\displaystyle u} staje się poprzednikiem v . {\displaystyle v.}

Zastosowanie

Z algorytmu Dijkstry można skorzystać przy obliczaniu najkrótszej drogi do danej miejscowości. Wystarczy przyjąć, że każdy z punktów skrzyżowań dróg to jeden z wierzchołków grafu, a odległości między punktami to wagi krawędzi.

Jest często używany w sieciach komputerowych, np. przy trasowaniu (przykładowo w protokole OSPF).

Pseudokod

Dijkstra(G,w,s):
   dla każdego wierzchołka v w V[G] wykonaj
      d[v] := nieskończoność
      poprzednik[v] := niezdefiniowane
   d[s] := 0
   Q := V
   dopóki Q niepuste wykonaj
      u := Zdejmij_Min(Q)
      dla każdego wierzchołka v – sąsiada u wykonaj
         jeżeli d[v] > d[u] + w(u, v) to
            d[v] := d[u] + w(u, v)
            poprzednik[v] := u

   Wyświetl("Droga wynosi: " + d[v])

Dowód poprawności

Oznaczmy przez S {\displaystyle S} zbiór wierzchołków, które zostały już zdjęte z kolejki. Dowód opiera się na następujących dwóch faktach (niezmiennikach), prawdziwych przez cały czas trwania algorytmu:

  1. Dla każdego wierzchołka v S , {\displaystyle v\in S,} liczba d [ v ] {\displaystyle d[v]} jest długością najkrótszej ścieżki od s {\displaystyle s} do v . {\displaystyle v.}
  2. Dla każdego wierzchołka v S , {\displaystyle v\notin S,} d [ v ] {\displaystyle d[v]} jest długością najkrótszej ścieżki do v {\displaystyle v} prowadzącej tylko przez wierzchołki z S . {\displaystyle S.}

Na początku oba fakty są oczywiste ( S {\displaystyle S} jest zbiorem pustym). Przy zdejmowaniu wierzchołka u {\displaystyle u} z kolejki wiemy, na podstawie faktu 2, że nie da się do niego dojść żadną krótszą drogą przez wierzchołki z S . {\displaystyle S.} Z drugiej strony, ponieważ u {\displaystyle u} ma najniższy priorytet, przejście przez jakikolwiek inny wierzchołek spoza S {\displaystyle S} dałoby od razu co najmniej tak samo długą ścieżkę. A zatem dołączając wierzchołek u {\displaystyle u} do S , {\displaystyle S,} zachowujemy prawdziwość faktu 1. Następnie musimy uwzględnić fakt, że najkrótsza ścieżka do jakiegoś wierzchołka v {\displaystyle v} po wierzchołkach z nowego zbioru S {\displaystyle S} może teraz zawierać wierzchołek u . {\displaystyle u.} Ale wtedy musi on być ostatnim na niej wierzchołkiem (do każdego innego dałoby się dojść krócej, nie używając u {\displaystyle u} ), a zatem jej długość równa jest d [ u ] + w ( u , v ) {\displaystyle d[u]+w(u,v)} i zostanie prawidłowo obliczona w następnym kroku algorytmu.

Złożoność

Złożoność obliczeniowa algorytmu Dijkstry zależy od liczby V {\displaystyle V} wierzchołków i E {\displaystyle E} krawędzi grafu. O rzędzie złożoności decyduje implementacja kolejki priorytetowej:

  • wykorzystując „naiwną” implementację poprzez zwykłą tablicę, otrzymujemy algorytm o złożoności O ( V 2 ) {\displaystyle O(V^{2})}
  • w implementacji kolejki poprzez kopiec, złożoność wynosi O ( E log V ) {\displaystyle O(E\log V)}
  • po zastąpieniu zwykłego kopca kopcem Fibonacciego złożoność zmienia się na O ( E + V log V ) {\displaystyle O(E+V\log V)}

Pierwszy wariant jest optymalny dla grafów gęstych (czyli jeśli E = Θ ( V 2 ) {\displaystyle E=\Theta (V^{2})} ), drugi jest szybszy dla grafów rzadkich ( E = Θ ( V ) ) , {\displaystyle (E=\Theta (V)),} trzeci jest bardzo rzadko używany ze względu na duży stopień skomplikowania i niewielki w porównaniu z nim zysk czasowy.

Problemy i algorytmy pokrewne

Algorytm Dijkstry nie działa, jeśli w grafie występują krawędzie z ujemnymi wagami – w tym wypadku używa się wolniejszego, lecz bardziej ogólnego algorytmu Bellmana-Forda. Jeśli graf nie jest ważony (wszystkie wagi mają wielkość 1), zamiast algorytmu Dijkstry wystarczy algorytm przeszukiwania grafu wszerz.

Algorytm A* jest pewnym uogólnieniem algorytmu Dijkstry, które pozwala przeszukiwać tylko część grafu, jednak wymaga dodatkowej wstępnej informacji (heurystyki) o odległościach wierzchołków.

Algorytm Prima znajdowania minimalnego drzewa rozpinającego oparty jest o bardzo podobny pomysł co algorytm Dijkstry.

Przypisy

  1. Algorytm Dijkstry. edu.i-lo.tarnow.pl. [zarchiwizowane z tego adresu (2009-01-25)]..
  2. Algorytm Dijkstry.

Bibliografia

  • E. W. Dijkstra: A note on two problems in connexion with graphs. In Numerische Mathematik, 1 (1959), S. 269–271.
  • Cormen, Thomas H.; Leiserson, Charles E.; Rivest, Ronald L.; Stein, Clifford Wprowadzenie do algorytmów, wyd. 7, WNT 2007, ISBN 83-204-3149-2.

Linki zewnętrzne

  • Algorytm Dijkstry na wazniak.mimuw.edu.pl
  • Implementacja w języku C++
  • Animacja algorytmu Dijkstry. optlab-server.sce.carleton.ca. [zarchiwizowane z tego adresu (2014-12-20)].
  • Przykład wykonania algorytmu Dijkstry krok po kroku