Dijkstra algoritm Python & C++ (Näide)

⚡ Nutikas kokkuvõte

Dijkstra algoritm arvutab lühima tee ühest lähtetipust iga teise tippu kaalutud graafis, millel on mittenegatiivsed servad. See ahne meetod on aluseks Google Kaardista marsruutimist, OSPF IP marsruutimist ja lugematuid võrgu lühima tee kasutusjuhtumeid.

  • 🎯 Põhiidee: Dijkstra algoritm laiendab ahnelt lähimat külastamata tippu, ajakohastades naabrite vahemaid, kuni iga kättesaadav sõlm säilitab oma tegeliku lühima hinna allikast.
  • 🔄 Võrreldes parima ja viimase elukuuga (BFS) ning kahjutu eluviisiga (DFS): BFS ja DFS leiavad suvalise tee servade kaalusid arvestamata, samas kui Dijkstra minimeerib kaalutud servade kogukulu.
  • 🧭 Samm-sammult näide: Töödeldud 7 tipuga kaalutud graaf näitab, kuidas vahemaad iteratiivselt uuenevad ja kuidas tee 1-2-6-7 võidab hinnaga 7.
  • 💻 Keele katvus: Mõlemad C++ ja Python implementatsioonid demonstreerivad külgnevusmaatriksi versiooni minimaalse kauguse valikufunktsiooniga.
  • ⚠️ Piirang: Dijkstra meetod ebaõnnestub negatiivsete servade kaalude korral, sest lõplikku sõlme ei vaadata enam kunagi uuesti läbi; negatiivsete servadega graafikute puhul kasutage Bellman-Fordi meetodit.
  • 📊 Keerukus: Naiivne massiiviversioon töötab ajas ja ruumis O(V²); prioriteetsjärjekord vähendab hõredate graafikute puhul aega O(E log V)-ni.

Dijkstra lühima tee algoritm

Mis on lühim tee või lühim vahemaa?

Tee lähtetipust sihttippu, mis maksab minimaalselt, on lühim tee või lühim vahemaa. Graafiteoorias on võimalik, et lähtest sihtkohta on mitu marsruuti. Nende marsruutide hulgast, kui leidub marsruut, mis maksab minimaalselt, nimetame seda lühimaks teeks.

Siin tähendab „maksumus” marsruudil olevate sõlmede arvu või iga serva kulude summat. Teel võib olla üks või mitu serva. Kahe tipu vahelist ühendust nimetatakse „servaks”. On olemas erinevat tüüpi lühima tee algoritme, näiteks Dijkstra algoritm ja Bellman-Fordi algoritm.

Siin arutame Dijkstra algoritmi. Vaatleme järgmist kaalutud graafikut:

Suunamata kaalutud graaf

Suunamata kaalutud graafik

  • Mõiste „kaalutud” tähendab ühest sõlmest teise liikumise kulu. Näiteks sõlmest 1 sõlme 2 liikumisel on kulu ehk kaal 1.
  • Tee sõlmede 1 ja 2 vahel nimetatakse servaks.
  • „Suunamata” tähendab, et saate liikuda ühest sõlmest teise ja tagasi eelmisesse sõlme. Seega, kui proovime leida kõik marsruudid sõlmest 1 sõlme 7, on need järgmised:
Marsruut või teeMaksma
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

Nende nelja marsruudi hulgast näeme, et esimene marsruut maksab 7. Seega on see maksumuse poolest lühim tee.

Lühim tee

Lühim tee

Kuidas Dijkstra algoritm töötab

Dijkstra algoritm suudab leida lühima vahemaa nii suunatud kui ka suunamata kaalutud graafikutes. See algoritm on ahne, kuna see valib alati lühima või alguspunktist lähima sõlme. Mõiste „ahne” tähendab, et algoritm valib tulemuste hulgast parima.

Siin püüame leida lühimaid teid kõigi teiste marsruutide hulgast. Seega leiab Dijkstra algoritm kõik lühimad teed ühest lähtesõlmest. Selle tulemusena käitub see nagu ahne algoritm.

Allolevas näiteosas näete samm-sammult lähenemist. See toimib järgmiselt:

Step 1) Initsialiseeri algsõlm hinnaga 0 ja ülejäänud sõlmed hinnaga lõpmatuseni.
Step 2) Massiivi või loendi haldamine track külastatud sõlmedest.
Step 3) Uuenda sõlme maksumust minimaalse maksumusega. Seda saab teha, võrreldes praegust maksumust tee maksumusega (näidatud näiteosas).
Step 4) Jätka 3. sammuga, kuni kõik sõlmed on külastatud.

Pärast kõigi nende sammude sooritamist leiame tee, mis maksab allikast sihtkohta minimaalselt.

Erinevus Dijkstra ja BFS, DFS vahel

Peamine erinevus Dijkstra ja BFS-DFS vahel on see, et Dijkstra on lühima tee leidmise algoritm, samas kui BFS ja DFS on üldised tee leidmise algoritmid. Üldiselt ei arvesta BFS ja DFS tee leidmisel servakulu. Seega ei saa need algoritmid garanteerida lühimat teed.

BFS-i toimimise 2D-ruudustiku demonstratsioon

2D-ruudustiku demonstratsioon BFS

Algosketš, mis näitab BFS-i demonstratsiooni

See demonstratsioon näitab, et BFS leiab ainult tee. See aga ei hooli raja kaalust. BFS (Breadth-First Search) eeldab, et ühest sõlmest teise sõitmine maksab ainult 1.

Vaatame näidisgraafikut:

2D-ruudustiku demonstratsioonigraafiku näidis

Siin leiab BFS tee 2. tasemelt. BFS läbib graafi taseme järjekorras. Seega liigub see nii:

Step 1) Alusta sõlmest "1" ja külasta kõiki külgnevaid sõlmi 2, 3, 4.

Step 2) Märgi sõlmed 2, 3 ja 4 1. taseme sõlmedeks ja külasta nende kõrvalasuvaid sõlmi. See jätkab kõigi külgnevate sõlmede uurimist, kuni jõuab sihtsõlmeni.

DFS-i osas läbib see tee vahemikus 1 kuni 7 järgmiselt:

  • 1→2→3→7 (algne maksumus 10, DFS-i maksumus 3)
  • 1→2→6→7 (algne maksumus 7, DFS-i maksumus 3)
  • 1→3→7 (algne hind 8, DFS-i maksumus 2)
  • 1→4→5→7 (algne maksumus 13, DFS-i maksumus 3)

Nagu näeme, arvutab DFS oma tee maksumuse servade arvuga. DFS teeb järgmist:

  • DFS suudab leida tee allikast (algustipp) sihtkohta.
  • See ei saa garanteerida, kas lähtesõlmest sihtkohta avastatud tee on lühim või mitte.

Dijkstra algoritmi kohaselt valib see servad aga nende maksumuse põhjal. Ahne algoritmina valib see minimaalse maksumusega teed.

Dijkstra algoritmi näide

Dijkstra algoritm kasutab tee kogumaksumuse arvutamiseks kulu või kaalu.

Dijkstrasi algoritmi näide

Dijkstra algoritmi eesmärk on minimeerida kogukulu või kaal. Ülaltoodud näites leiame parimad teed sõlmest 1 kuni sõlme 7, seejärel arvutame kõik kulud.

Dijkstra algoritm leiab lühimad teed kaalude arvutamise teel. See ei otsi kõiki võimalikke teid. Demonstreerime Dijkstra algoritmi näitega. Näiteks palutakse teil leida lühim tee sõlmest 1 sõlme 7.

Selle protsessi jaoks on toodud järgmised sammud:

Step 1) Initsialiseeri algse sõlme maksumuseks 0. Määra "Inf" ülejäänud sõlmedele. See tähendab, et allika ja sõlme vahel puudub tee või teed pole veel külastatud.

Dijkstrasi algoritmi initsialiseerimine

Step 2) Kui valite sõlme 1, märgitakse see külastatuks. Seejärel värskendage kõiki sõlme 1 külgnevaid naabreid. 2, 3 ja 4 on sõlme 1 naabersõlmed.

Kulude värskendamisel peame järgima allolevat protseduuri.

Dijkstrasi algoritmi uuendamise protseduur

Saame iga sõlme maksumust uuendada ülaltoodud valemi abil. Näiteks olime sõlmes 1 ja pidime uuendama selle külgnevate sõlmede 2, 3 ja 4 maksumust. Pärast uuendamist näevad maksumused välja sellised:

Dijkstrasi algoritm pärast esimest värskendust

Step 3) Sõlme „2“ naabrid on 6 ja 3. Uuendame maksumust „6“ juures, võrreldes lõpmatust (praegune väärtus) sõlme 2 maksumusega + tee maksumusega 2-st 6-ni. Lihtsamalt öeldes on sõlme „6“ maksumus 1 + 3 või 4.

Dijkstrasi algoritmi värskendussõlm 6

Sõlm 3 on sõlme 2 naaber. Kuid me arvutasime selle maksumuse eelmises etapis, mis oli 7. Nüüd, kui meie tee on 1-2-3, on sõlm 3 maksumus 10. Tee 1-2- 3 maksab 10, samas kui 1 kuni 3 maksab 7.

Step 4) Sõlme 3 puhul on naabersõlm 7. Seega, võrreldes sõlme 7 praegust väärtust tee maksumusega (7+1) ehk 8, uuendame sõlme 7 maksumust. See on 8. Seega leiame tee sõlmest 1 sõlme 7 ja see on 1→3→7. Maksumus on 8.

Step 5) Sõlme 4 puhul uuendame vastavalt selle kõrvalasuva sõlme maksumust. Seega on sõlme „5” uuendatud maksumus 8. Pärast 4. ja 5. sammu näeb see välja selline:

Dijkstrasi algoritm pärast sammu 4 5

Nüüd on tee 1-3-7 maksumus 8 (varem). Sõlme „7“ ei märgitud külastatuks, kuna me saame sõlme „7“ jõuda sõlmest „6“. Tee „1-2-6“ maksumus oli 4. Seega on tee 1-2-6-7 maksumus 7.

Kuna 7 < 8, on lühim tee lähtetipust „1“ sihttippu „7“ 1-2-6-7 ja maksumus on 7. Varem oli see 1-3-7 ja maksumus oli 8. Seega näeb lõplik graaf välja selline:

Dijkstrasi algoritmi lõplik graaf

Musta joonega tähistatud serv on meie lühim tee 1-st 7-ni ja see maksab meile 7.

Pseudo Code Dijkstra algoritm

Siin on Dijkstra algoritmi pseudokood:

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++ Dijkstra algoritmi rakendamine

Dijkstra algoritmi rakendamiseks kasutades C++, siin on kood:

#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äljund:

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 Dijkstra algoritmi rakendamine

Dijkstra algoritmi rakendamiseks kasutades Python, siin on kood:

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äljund:

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

Näeme, et algoritm arvutab lühima vahemaa lähtesõlmest.

Dijkstra algoritmi rakendamine

Dijkstra algoritmil on palju kasutusvõimalusi. Nende hulgas on see laialdaselt kasutusel võrgustamise valdkonnas. Siin on mõned Dijkstra algoritmi reaalsed kasutusviisid:

Dijkstra Google Kaardid: See algoritm on lühimate teede leidmise selgroog, nagu näeme ülaltoodud koodilõigu väljundist.

Dijkstra algoritmi rakendamine Google kaardid

Google ei kasuta lihtsat Dijkstra algoritmi. Selle asemel kasutab see modifitseeritud versiooni. Sihtkoha valimisel kuvatakse teile mitu teed Google Kaardid. Nende teede hulgast on mõned kasutaja jaoks välja sorteeritud. Need teed valitakse aja põhjal. Seega on aeg lühima tee äärekulu.

Dijkstra IP-marsruutimises: IP-marsruutimine on võrguterminoloogia. See kirjeldab, kuidas teie andmepakett saadetakse vastuvõtjale erinevate teede kaudu. Need teed koosnevad ruuteritest, serveritest ja muudest seadmetest. IP-marsruutimises on erinevat tüüpi protokolle.

Need protokollid aitavad ruuteril leida lühimaid teid andmete saatmiseks. Üks protokolli nimetustest on „OSPF (Open Shortest Path First)”. OSPF kasutab Dijkstra algoritmi. Ruuter peab marsruutide tabelit. Iga ruuter jagab oma tabelit naaberruuteritega. Pärast uuendatud tabeli saamist peavad nad kõik teed uuesti arvutama. Sel ajal kasutab ruuter Dijkstra algoritmi.

Dijkstra algoritmi piirang

Dijkstra algoritm ei saa garanteerida lühimat teed negatiivsete servadega graafis. Dijkstra algoritm järgib järgmisi põhimõtteid:

  • Ühest sõlmest teise viiakse üks lühim tee.
  • Kui lühim tee kahe sõlme vahel on valitud, siis seda enam ei arvutata.

Siin on kaks negatiivsete servadega näidet.

Dijkstrasi algoritmi negatiivsete servade piiramine

Vasakpoolsel graafikul on kolm tippu. Dijkstra töötab graafikul järgmiselt:

Step 1) Algustipp “1” lähtestatakse nulliks. Teistel sõlmedel on lõpmatus.

Dijkstrasi algoritmi 1. sammu piirang

Step 2) Märgi sõlm „1” külastatuks ja lisa see lühimasse teekonda.

Step 3) Allikasõlme 1 kaugus sõlmedest „2“ ja „3“ on seatud lõpmatuks, kuna lühimat teed pole veel arvutatud. Seega iga tee, mis maksab vähem kui lõpmatus, lisatakse lühimale teele (ahne lähenemine).

Step 4) Lähtetipu „1“ kauguse uuendamine punktile „2“. Praegune kaal on 5 (5 < lõpmatus). Samamoodi uuenda sõlme „1“ kaugus punktile „3“ kaaluga 3.

Dijkstrasi algoritmi 4. sammu piirang

Step 5) Kui nüüd kontrollida lühimaid vahemaid sõlmest „1“, siis leiame, et serva 1→2 puhul on lühim vahemaa 5. Seega märgitakse sõlm „2“ külastatuks. Samamoodi märgitakse ka sõlm „3“ külastatuks, kuna lühim vahemaa on 3.

Kui aga vaatleme, siis on olemas tee 1-3-2, mis maksab ainult 2. Kuid Dijkstra näitab, et sõlmest "1" sõlme "2" on lühim vahemaa 5. Seega ei suutnud Dijkstra lühimat vahemaad õigesti arvutada. Põhjus on selles, et Dijkstra on ahne algoritm. Seega, kui sõlm on märgitud külastatuks, siis seda uuesti ei vaadata, kuigi võib olla saadaval lühem tee. See probleem ilmneb ainult siis, kui servadel on negatiivsed kulud või negatiivse kaaluga servad.

Dijkstra ei suuda selles stsenaariumis arvutada kahe sõlme vahelist lühimat teed. Seetõttu on sellel algoritmil mõned puudused. Selle negatiivse serva probleemi lahendamiseks kasutatakse teist algoritmi, mida nimetatakse Bellmani-Fordi algoritmiks. See algoritm saab töötada negatiivsete servadega.

Dijkstra algoritmi keerukus

Ülaltoodud teostus kasutas kahte for-silmust. Need tsüklid jooksevad tippude arvu jaoks. Niisiis, ajaline keerukus on O(V²)Siin on termin „O” tähistus, mis annab eelduse Dijkstra algoritmi jaoks.

Graafi saab salvestada "prioriteedijärjekorra" abil. Prioriteedijärjekord on binaarne kuhja andmestruktuur. See on efektiivsem kui 2D maatriks. Minimaalse hinnaga serval on kõrge prioriteet. Siis on ajaline keerukus O(E log V). Siin on E servade arv ja V on tippude arv.

Ruumi keerukus on O(V²), kuna me kasutame naabrusmaatriksit (2D massiiv). Ruumi keerukust saab optimeerida külgnemisloendi või järjekorra andmestruktuuri abil.

KKK

Robootikas, autonoomsetes sõidukites ja mängu-NPC-des kasutatavad tehisintellektil põhinevad teekonna planeerimise agendid kasutavad Dijkstra algoritmi kaalutud graafikutel madalaima hinnaga marsruutide leidmiseks. Tugevdusõppe keskkonnad toetuvad sellele samuti preemiajaotuste optimaalsete võrdlusteede arvutamiseks.ping ja hindamine.

Jah. Tehisintellektil põhinevad kodeerimisassistendid, näiteks GitHub Copilot ja GPT, saavad Dijkstra algoritmi genereerida. Python, C++või Java, sealhulgas kuhjade abil prioriteetse järjekorra variandid. Samuti saavad nad printida tegeliku lühima tee või kohandada koodi naaberloenditena salvestatud graafikutele.

Dijkstra algoritm töötab minimaalse sõlme leidmiseks lihtsa massiivi abil ajaga O(V²). Binaarse kuhja prioriteedijärjekorra korral langeb see väärtusele O((V + E) log V) ja Fibonacci kuhja korral jõuab see väärtuseni O(E + V log V), mis on parim hõredate graafikute jaoks.

Dijkstra vormistab tipu niipea, kui see valib praeguse minimaalse vahemaa. Hilisem negatiivne serv võib muuta pikema tee odavamaks, kuid vormistatud tippu ei külastata enam kunagi, seega annab algoritm vale lühima vahemaa.

Valige Dijkstra meetod, kui iga serva kaal on mittenegatiivne, kuna see on kiirem aja O((V+E) log V) juures. Valige Bellman-Fordi meetod, kui servad võivad olla negatiivsed või kui teil on vaja tuvastada negatiivse kaaluga tsükleid; selle O(V·E) jooksuaeg on kompromiss.

Google Kaardid kasutavad Dijkstra variante ja järeltulijaid, sealhulgas A* ja ContracTeedevõrkude ja reaalajas liikluse jaoks häälestatud hierarhiad. Dijkstra põhipanus on endiselt ahne laienemise idee minimaalse akumuleeritud kulu abil.

A* laiendab Dijkstra otsingumeetodit, lisades eesmärgi kauguse heuristilise hinnangu, laiendades vähem sõlmi, kui hea heuristika on saadaval. Dijkstra uurib igas suunas, samas kui A* kallutab otsingu sihtmärgi poole, muutes selle praktikas kiiremaks.

Lisaks kaartidele toetab Dijkstra OSPF-i ja IS-IS-i marsruutimisprotokolle internetis, võrgu topoloogia optimeerimist, telefonikõnede marsruutimist, robotilist liikumise planeerimist, sotsiaalvõrgustike lühima ühenduse päringuid ja lennufirmade lennukulude minimeerimist.

Võta see postitus kokku järgmiselt: