Dijkstrův algoritmus v Python & C++ (Příklad)

⚡ Chytré shrnutí

Dijkstrův algoritmus vypočítává nejkratší cestu z jednoho zdrojového vrcholu do každého dalšího vrcholu ve váženém grafu s nezápornými hranami. Tato chamtivá metoda je základem Google Směrování map, směrování IP OSPF a nespočet případů použití nejkratších cest v síti.

  • 🎯 Základní myšlenka: Dijkstrův algoritmus chamtivě rozšiřuje nejbližší nenavštívený vrchol a aktualizuje vzdálenosti sousedů, dokud každý dosažitelný uzel nemá svou skutečnou nejkratší cenu ze zdroje.
  • 🔄 Vs. BFS a DFS: BFS a DFS najdou libovolnou cestu bez ohledu na váhy hran, zatímco Dijkstra minimalizuje celkové náklady napříč váženými hranami.
  • 🧭 Příklad krok za krokem: Zpracovaný vážený graf se 7 vrcholy ukazuje, jak se vzdálenosti iterativně aktualizují a jak cesta 1-2-6-7 vítězí s cenou 7.
  • 💻 Jazykové pokrytí: Oba C++ a Python Implementace demonstrují verzi s maticí sousednosti s funkcí výběru s minimální vzdáleností.
  • ⚠️ Omezení: Dijkstra selhává u záporných vah hran, protože finalizovaný uzel se nikdy znovu nezvažuje; pro grafy se zápornými hranami použijte Bellman-Fordův model.
  • 📊 Složitost: Naivní verze s polem běží v čase a prostoru O(V²); prioritní fronta snižuje čas na O(E log V) pro řídké grafy.

Dijkstrův algoritmus nejkratší cesty

Jaká je nejkratší cesta nebo nejkratší vzdálenost?

Cesta ze zdrojového vrcholu do cílového vrcholu, která má minimální náklady, je nejkratší cesta nebo nejkratší vzdálenost. V teorii grafů je možné mít více cest ze zdroje do cíle. Pokud mezi těmito cestami existuje trasa s minimálními náklady, nazýváme ji nejkratší cestou.

Zde „náklady“ znamenají počet uzlů v trase nebo součet nákladů na každé hraně. Cesta může mít jednu nebo více hran. Spojení mezi dvěma vrcholy se nazývá „hrana“. Existují různé typy algoritmů pro nejkratší cestu, jako je Dijkstrův algoritmus a Bellman-Fordův algoritmus.

Zde si povíme o Dijkstrově algoritmu. Podívejme se na následující vážený graf:

Neorientovaný vážený graf

Neorientovaný vážený graf

  • Termín „vážený“ označuje náklady na přesun z jednoho uzlu do druhého. Například při přesunu z uzlu 1 do uzlu 2 jsou náklady nebo váha 1.
  • Cesta mezi uzlem 1 a uzlem 2 se nazývá hrana.
  • „Neorientované“ znamená, že se můžete přesunout z jednoho uzlu do druhého a zpět k předchozímu uzlu. Pokud se tedy pokusíme najít všechny trasy z uzlu 1 do uzlu 7, budou vypadat takto:
Trasa nebo CestaStát
1-2-6-7(1+3+3) = 7
1-2-3-7(1+9+1) = 11
1-3-7(7+1) = 8
1-4-5-7(6+2+5) = 13

Z těchto čtyř tras vidíme, že první trasa stojí 7. Je to tedy z hlediska nákladů nejkratší cesta.

Nejkratší cesta

Nejkratší cesta

Jak funguje Dijkstrův algoritmus

Dijkstrův algoritmus dokáže najít nejkratší vzdálenost v orientovaných i neorientovaných vážených grafech. Tento algoritmus je chamtivý, protože vždy vybírá nejkratší nebo nejbližší uzel z počátku. Termín „chamtivý“ znamená, že z množiny výsledků nebo výsledků algoritmus vybere ten nejlepší.

Zde se snažíme najít nejkratší cesty mezi všemi ostatními cestami. Dijkstrův algoritmus tedy najde všechny nejkratší cesty z jednoho zdrojového uzlu. V důsledku toho se chová jako chamtivý algoritmus.

V níže uvedené části „příklad“ uvidíte postup krok za krokem. Funguje následovně:

Krok 1) Inicializujte počáteční uzel s 0 náklady a zbytek uzlů s nekonečnou cenou.
Krok 2) Udržovat pole nebo seznam pro uchování track navštívených uzlů.
Krok 3) Aktualizujte cenu uzlu o minimální cenu. To lze provést porovnáním aktuální ceny s cenou cesty (ukázáno v příkladové části).
Krok 4) Pokračujte krokem 3, dokud nenavštívíte všechny uzly.

Po dokončení všech těchto kroků najdeme cestu, která stojí minimum od zdroje k cíli.

Rozdíl mezi Dijkstrou a BFS, DFS

Hlavní rozdíl mezi Dijkstrou a BFS-DFS spočívá v tom, že Dijkstra je algoritmus pro hledání nejkratší cesty, zatímco BFS a DFS jsou obecné algoritmy pro hledání cesty. V obecných případech BFS a DFS při hledání cesty nezohledňují cenu hran. Tyto algoritmy tedy nemohou zaručit nalezení nejkratší cesty.

2D mřížková ukázka fungování BFS

2D mřížková demonstrace BFS

Algosketch, ukazující ukázku BFS

Tato ukázka ukazuje, že BFS najde pouze cestu. Nezáleží však na hmotnosti cesty. BFS (První vyhledávání podle šířky) předpokládá, že cestování z jednoho uzlu do druhého bude stát pouze 1.

Podívejme se na příklad grafu:

Ukázkový graf 2D mřížky

Zde BFS najde cestu na úrovni 2. BFS prochází grafem v pořadí po úrovních. Takže se pohybuje takto:

Krok 1) Začněte od uzlu „1“ a navštivte všechny sousední uzly 2, 3, 4.

Krok 2) Označí uzly 2, 3, 4 jako uzly úrovně 1 a navštíví jejich sousední uzly. Bude pokračovat v prozkoumávání všech sousedních uzlů, dokud nedosáhne cílového uzlu.

Pokud jde o DFS, bude procházet cestou od 1 do 7 takto:

  • 1→2→3→7 (původní cena 10, cena DFS 3)
  • 1→2→6→7 (původní cena 7, cena DFS 3)
  • 1→3→7 (původní cena 8, cena DFS 2)
  • 1→4→5→7 (původní cena 13, cena DFS 3)

Jak vidíme, DFS vypočítává cenu cesty s počtem hran. DFS provádí následující:

  • DFS může najít cestu ze zdroje (počáteční vrchol) do cíle.
  • Nemůže zaručit, zda zjištěná cesta ze zdrojového uzlu do cíle je nejkratší cestou nebo ne.

Nicméně, z hlediska Dijkstrova algoritmu, vybírá hrany na základě jejich ceny. Jako chamtivý algoritmus vybere cesty s minimálními náklady.

Příklad Dijkstrova algoritmu

Dijkstrův algoritmus používá k výpočtu celkových nákladů na cestu cenu nebo váhu.

Příklad Dijkstraova algoritmu

Cílem Dijkstrova algoritmu je minimalizovat tyto celkové náklady nebo hmotnost. Ve výše uvedeném příkladu najdeme nejlepší cesty z uzlu 1 do uzlu 7 a poté spočítáme všechny náklady.

V Dijkstrově algoritmu se najdou nejkratší cesty výpočtem vah. Nebude se hledat všechny možné cesty. Ukažme si Dijkstrův algoritmus na příkladu. Například máte najít nejkratší cestu z uzlu 1 do uzlu 7.

Pro tento proces jsou kroky uvedeny níže:

Krok 1) Inicializujte počáteční cenu uzlu na 0. Přiřaďte „Informace“ ke zbytku uzlů. To znamená, že mezi zdrojem a uzlem neexistuje žádná cesta, nebo cesta ještě nebyla navštívena.

Inicializace Dijkstraova algoritmu

Krok 2) Když vyberete uzel 1, bude označen jako navštívený. Poté aktualizujte všechny sousední uzly uzlu 1. 2, 3, 4 jsou sousední uzly uzlu 1.

Při aktualizaci ceny musíme postupovat podle níže uvedeného postupu:

Postup aktualizace Dijkstraova algoritmu

Náklady každého uzlu můžeme aktualizovat pomocí výše uvedeného vzorce. Například jsme byli v uzlu 1 a potřebovali jsme aktualizovat náklady jeho sousedních uzlů 2, 3, 4. Po aktualizaci budou náklady vypadat takto:

Dijkstrův algoritmus po první aktualizaci

Krok 3) Pro uzel „2“ jsou sousedy 6 a 3. Aktualizujeme cenu na „6“ porovnáním nekonečna (aktuální hodnoty) s cenou uzlu 2 + cena cesty od 2 do 6. Jednoduše řečeno, uzel „6“ bude mít cenu 1 + 3 nebo 4.

Aktualizace Dijkstraova algoritmu, uzel 6

Uzel 3 je sousedem uzlu 2. V předchozím kroku jsme však vypočítali jeho cenu, která byla 7. Nyní, pokud je naše cesta 1-2-3, uzel 3 bude mít cenu 10. Cesta 1-2- 3 bude stát 10, zatímco 1 až 3 bude stát 7.

Krok 4) Pro uzel 3 je sousedním uzlem uzel 7. Porovnáním aktuální hodnoty uzlu 7 s cenou cesty (7+1) nebo 8 aktualizujeme cenu uzlu 7. To je 8. Najdeme tedy cestu z uzlu 1 do uzlu 7 a ta je 1→3→7. Cena je 8.

Krok 5) U uzlu 4 aktualizujeme odpovídajícím způsobem cenu jeho sousedního uzlu. Uzel „5“ tedy bude mít aktualizovanou cenu 8. Po krocích 4 a 5 bude vypadat takto:

Dijkstrův algoritmus po kroku 4 5

Cesta 1-3-7 má nyní cenu 8 (dříve). Uzel „7“ nebyl označen jako navštívený, protože k uzlu „7“ se dostaneme z uzlu „6“. Cesta „1-2-6“ měla cenu 4. Cesta 1-2-6-7 tedy bude mít cenu 7.

Protože 7 < 8, nejkratší cesta ze zdrojového vrcholu „1“ do cílového vrcholu „7“ bude 1-2-6-7 a cena je 7. Dříve to bylo 1-3-7 a cena byla 8. Výsledný graf tedy bude vypadat takto:

Finální graf Dijkstrasova algoritmu

Hrana označená černou čarou je naše nejkratší cesta od 1 do 7 a bude nás to stát 7.

Nepravý Code Dijkstrův algoritmus

Zde je pseudokód Dijkstrova algoritmu:

Dijkstra(G, S):
  for each vertex V in G
    distance[V] <- Infinity
    previous[V] <- NULL
    if V does not equal S, then,
      (priority queue) Q.push(V)
  distance[S] = 0
  While Q is not empty
    U <- Extract the MIN from Q
    For each unvisited adjacent V of U
      TotalDistance <- distance[U] + edge_cost(U, V)
      if TotalDistance is less than distance[V], then
        distance[V] <- TotalDistance
        previous[V] <- U
  return distance, previous

C++ Implementace Dijkstrova algoritmu

Implementovat Dijkstrův algoritmus pomocí C++, zde je kód:

#include <bits/stdc++.h>
using namespace std;
#define size 7
int minimumDistance(int distance[], bool visited[]) {
  int min = INT_MAX;
  int min_index = INT_MAX;
  for (int i = 0; i < size; i++) {
    if (!visited[i] && distance[i] <= min) {
      min = distance[i];
      min_index = i;
    }
  }
  return min_index;
}
void printParentPath(int parent[], int i) {
  if (parent[i] == -1) {
    return;
  }
  printParentPath(parent, parent[i]);
  cout << i + 1 << " ";
}
void dijkstra(int graph[size][size], int source) {
  int distance[size];
  bool visited[size];
  int parent[size];
  for (int i = 0; i < size; i++) {
    parent[0] = -1;
    distance[i] = INT_MAX;
    visited[i] = false;
  }
  distance[source] = 0;
  for (int i = 0; i < size - 1; i++) {
    int U = minimumDistance(distance, visited);
    visited[U] = true;
    for (int j = 0; j < size; j++) {
      int curr_distance = distance[U] + graph[U][j];
      if (!visited[j] && graph[U][j] &&
          curr_distance < distance[j]) {
        parent[j] = U;
        distance[j] = curr_distance;
      }
    }
  }
  cout << "Vertex\t\tDistance\tPath" << endl;
  for (int i = 1; i < size; i++) {
    cout << source + 1 << "->" << i + 1 << "\t\t" << distance[i] << "\t\t"
         << source + 1 << " ";
    printParentPath(parent, i);
    cout << endl;
  }
}
int main() {
  int graph[size][size] = {{0, 1, 7, 6, 0, 0, 0}, {1, 0, 9, 0, 0, 3, 0},
                           {7, 9, 0, 0, 0, 0, 1}, {6, 0, 0, 0, 2, 0, 0},
                           {0, 0, 0, 2, 0, 0, 0}, {0, 3, 0, 0, 0, 0, 3},
                           {0, 0, 0, 0, 5, 3, 0}};
  dijkstra(graph, 0);
}

Výstup:

Vertex     Distance        Path

1->2           1             1 2
1->3           7             1 3
1->4           6             1 4
1->5           8             1 4 5
1->6           4             1 2 6
1->7           7             1 2 6 7

Python Implementace Dijkstrova algoritmu

Implementovat Dijkstrův algoritmus pomocí Python, zde je kód:

num_of_vertex = 7
def minimumDistance(distance, visited):
    _min = 1e11
    min_index = 1e11
    for i in range(num_of_vertex):
        if not visited[i] and distance[i] <= _min:
            _min = distance[i]
            min_index = i
    return min_index

def printParentNode(parent, i):
    if parent[i] == -1:
        return
    printParentNode(parent, parent[i])
    print("{} ".format(i + 1), end="")

def dijkstra(graph, src):
    distance = list()
    visited = list()
    parent = list()
    for i in range(num_of_vertex):
        parent.append(-1)
        distance.append(1e11)
        visited.append(False)
    distance[src] = 0
    for i in range(num_of_vertex - 1):
        U = minimumDistance(distance, visited)
        visited[U] = True
        for j in range(num_of_vertex):
            curr_distance = distance[U] + graph[U][j]
            if not visited[j] and graph[U][j] and curr_distance < distance[j]:
                parent[j] = U
                distance[j] = curr_distance
    print("Vertex\t\tDistance\tPath")
    for i in range(num_of_vertex):
        print("{}->{}\t\t{}\t\t{} ".format(src + 1, i + 1, distance[i], src + 1), end="")
        printParentNode(parent, i)
        print("")

graph = [
    [0, 1, 7, 6, 0, 0, 0],
    [1, 0, 9, 0, 0, 3, 0],
    [7, 9, 0, 0, 0, 0, 1],
    [6, 0, 0, 0, 2, 0, 0],
    [0, 0, 0, 2, 0, 0, 0],
    [0, 3, 0, 0, 0, 0, 3],
    [0, 0, 0, 0, 5, 3, 0]
]
dijkstra(graph, 0)

Výstup:

Vertex     Distance        Path

1->1           0              1
1->2           1              1 2
1->3           7              1 3
1->4           6              1 4
1->5           8              1 4 5
1->6           4              1 2 6
1->7           7              1 2 6 7

Vidíme, že algoritmus vypočítává nejkratší vzdálenost od zdrojového uzlu.

Aplikace Dijkstrova algoritmu

Dijkstrův algoritmus má širokou škálu využití. Mimo jiné je široce používán v oblasti sítí. Zde je několik reálných použití Dijkstrova algoritmu:

Dijkstra v Google Mapy: Tento algoritmus je základem pro hledání nejkratších cest, jak je vidět z výše uvedeného úryvku kódu.

Aplikace Dijkstrova algoritmu Google Mapy

Google nepoužívá jednoduchý Dijkstrův algoritmus. Místo toho používá jeho upravenou verzi. Když vyberete cíl, zobrazí se vám více cest v Google Mapy. Mezi těmito cestami jsou některé pro uživatele vytříděny. Tyto cesty jsou vybrány na základě „času“. „Čas“ tedy představuje cenu hrany pro nejkratší cestu.

Dijkstra v IP směrování: IP směrování je síťová terminologie. Popisuje, jak je datový paket odesílán příjemci různými cestami. Tyto cesty zahrnují routery, servery a další zařízení. V IP routingu existují různé typy protokolů.

Tyto protokoly pomáhají routeru najít nejkratší cesty pro odeslání dat. Jeden z názvů protokolů je „OSPF (Open Shortest Path First)“. OSPF používá Dijkstrův algoritmus. Router udržuje tabulku tras. Každý router sdílí svou tabulku se sousedními routery. Po obdržení aktualizované tabulky musí znovu vypočítat všechny cesty. V tomto případě router použije Dijkstrův algoritmus.

Omezení Dijkstrova algoritmu

Dijkstrův algoritmus nemůže zaručit nejkratší cestu v grafu se zápornými hranami. Dijkstrův algoritmus se řídí těmito principy:

  • Jedna nejkratší cesta bude vedena z jednoho uzlu do druhého.
  • Jakmile je vybrána nejkratší cesta mezi dvěma uzly, nebude znovu počítána.

Zde si všimněte dvou příkladů s negativními okraji.

Omezení Dijkstrasova algoritmu: negativní hrany

V levém grafu, existují tři vrcholy. Dijkstra bude na grafu fungovat takto:

Krok 1) Počáteční vrchol „1“ bude inicializován na nulu. Ostatní uzly budou mít nekonečno.

Omezení kroku 1 Dijkstrasova algoritmu

Krok 2) Označte uzel „1“ jako navštívený a zařaďte ho do nejkratší cesty.

Krok 3) Vzdálenost zdrojového uzlu 1 k uzlům „2“ a „3“ je nastavena na nekonečno, protože nejkratší cesta teprve musí být vypočítána. Takže jakákoli cesta, která stojí méně než nekonečno, bude přidána k nejkratší cestě (chamtivý přístup).

Krok 4) Aktualizace vzdálenosti od zdrojového vrcholu „1“ na „2“. Aktuální váha bude 5 (5 < nekonečno). Podobně aktualizujte vzdálenost od uzlu „1“ na „3“ s vahou 3.

Omezení kroku 4 Dijkstrasova algoritmu

Krok 5) Pokud nyní zkontrolujeme nejkratší vzdálenosti od uzlu „1“, zjistíme, že 5 je nejkratší vzdálenost pro hranu 1→2. Uzel „2“ bude tedy označen jako navštívený. Podobně bude uzel „3“ také označen jako navštívený, protože nejkratší vzdálenost je 3.

Pokud si ale všimneme, existuje cesta 1-3-2, která bude stát pouze 2. Dijkstra však ukazuje, že z uzlu „1“ do uzlu „2“ je nejkratší vzdálenost 5. Dijkstra tedy nejkratší vzdálenost nevypočítal správně. Důvodem je, že Dijkstra je chamtivý algoritmus. Jakmile je tedy uzel označen jako navštívený, nebude znovu zvažován, i když může být k dispozici kratší cesta. K tomuto problému dochází pouze tehdy, když hrany mají záporné náklady nebo zápornou váhu hran.

Dijkstra v tomto scénáři nedokáže vypočítat nejkratší cestu mezi dvěma uzly. V důsledku toho má tento algoritmus určité nevýhody. K řešení tohoto problému negativních hran se používá jiný algoritmus zvaný „Bellman-Fordův algoritmus“. Tento algoritmus umí pracovat se negativními hranami.

Složitost Dijkstrova algoritmu

Výše uvedená implementace používala dvě smyčky „for“. Tyto smyčky běží pro počet vrcholů. Časová složitost tedy je O(V²)Termín „O“ je zde označení, které udává předpoklad pro Dijkstrův algoritmus.

Graf můžeme uložit pomocí „fronty priorit“. Fronta priorit je binární datová struktura haldy. Bude efektivnější než 2D matice. Hrana s minimálními náklady bude mít vysokou prioritu. Časová složitost pak bude O(E log V). Zde E je počet hran a V je počet vrcholů.

Prostorová složitost je O(V²), protože používáme matici sousednosti (2D pole). Složitost prostoru lze optimalizovat pomocí přilehlého seznamu nebo datové struktury fronty.

Nejčastější dotazy

Agenti pro plánování cest s využitím umělé inteligence v robotice, autonomních vozidlech a herních NPC používají Dijkstrův algoritmus k nalezení nejlevnějších tras na vážených grafech. Prostředí učení s posilováním se na něj také spoléhají k výpočtu optimálních referenčních cest pro rozdělení odměn.ping a hodnocení.

Ano. Asistenti pro kódování s umělou inteligencí, jako je GitHub Copilot a GPT, dokážou generovat Dijkstrův algoritmus v Python, C++nebo Java, včetně variant s prioritní frontou využívající haldy. Mohou také vypsat skutečnou nejkratší cestu nebo přizpůsobit kód grafům uloženým jako seznamy sousedností.

Dijkstrův algoritmus s použitím jednoduchého pole k nalezení minimálního uzlu běží v čase O(V²). U binární haldy s prioritou klesne na O((V + E) log V) a u Fibonacciho haldy dosáhne O(E + V log V), což je nejlepší pro řídké grafy.

Dijkstra finalizuje vrchol, jakmile vybere aktuální minimální vzdálenost. Pozdější negativní hrana by mohla zlevnit delší cestu, ale finalizovaný vrchol se nikdy znovu nenavštíví, takže algoritmus hlásí nesprávnou nejkratší vzdálenost.

Dijkstrovu metodu zvolte, když je každá váha hran nezáporná, protože je rychlejší s O((V+E) log V). Bellman-Fordovu metodu zvolte, když hrany mohou být záporné nebo potřebujete detekovat cykly se zápornou váhou; kompromisem je její doba běhu O(V·E).

Google Mapy používají varianty a nástupce Dijkstry, včetně A* a ContracHierarchie cí, vyladěné pro silniční sítě a reálný provoz. Základní myšlenka chamtivé expanze s minimálními kumulovanými náklady je stále Dijkstrovým klíčovým přínosem.

A* rozšiřuje Dijkstrovu metodu přidáním heuristického odhadu vzdálenosti k cíli a rozšiřuje méně uzlů, pokud je k dispozici dobrá heuristika. Dijkstra prozkoumává ve všech směrech, zatímco A* zkresluje hledání směrem k cíli, což ho v praxi urychluje.

Kromě map Dijkstra podporuje směrovací protokoly OSPF a IS-IS na internetu, optimalizaci topologie sítě, směrování telefonních hovorů, plánování pohybu robotů, dotazy na nejkratší spojení na sociálních sítích a minimalizaci nákladů na lety leteckých společností.

Shrňte tento příspěvek takto: