Dijkstrin algoritam u Python & C++ (Primjer)

⚡ Pametni sažetak

Dijkstrin algoritam izračunava najkraći put od jednog izvornog vrha do svakog drugog vrha u ponderiranom grafu s nenegativnim bridovima. Ova pohlepna metoda podupire Google Rutiranje mapa, usmjeravanje OSPF IP-a i bezbrojni slučajevi korištenja najkraćeg puta u mreži.

  • 🎯 Osnovna ideja: Dijkstrin algoritam pohlepno proširuje najbliži neposjećeni vrh, ažurirajući udaljenosti susjeda sve dok svaki dostižni čvor ne ima svoju stvarnu najkraću cijenu iz izvora.
  • 🔄 U usporedbi s BFS-om i DFS-om: BFS i DFS pronalaze bilo koji put bez razmatranja težina bridova, dok Dijkstra minimizira ukupni trošak preko ponderiranih bridova.
  • 🧭 Primjer korak po korak: Obrađeni graf sa 7 ponderiranih vrhova pokazuje kako se udaljenosti iterativno ažuriraju i kako put 1-2-6-7 pobjeđuje s cijenom od 7.
  • 💻 Jezična pokrivenost: Oboje C++ i Python Implementacije demonstriraju verziju matrice susjednosti s funkcijom odabira minimalne udaljenosti.
  • ⚠️ Ograničenje: Dijkstra ne uspijeva na negativnim težinama bridova jer se finalizirani čvor nikada ne razmatra ponovno; koristite Bellman-Fordov model za grafove s negativnim bridovima.
  • 📊 Složenost: Naivna verzija s nizom izvršava se u vremenu i prostoru O(V²); red prioriteta smanjuje vrijeme na O(E log V) za rijetke grafove.

Dijkstrin algoritam najkraćeg puta

Koji je najkraći put ili najkraća udaljenost?

Put od izvornog vrha do odredišnog vrha koji košta minimalno je najkraći put ili najkraća udaljenost. U teoriji grafova moguće je imati više ruta od izvora do odredišta. Među tim rutama, ako postoji ruta koja košta minimalno, nazivamo je najkraćim putem.

Ovdje „trošak“ znači broj čvorova u ruti ili zbroj troškova na svakom bridu. Put može imati jedan ili više bridova. Veza između dva vrha naziva se „brid“. Postoje različite vrste algoritama za najkraći put, kao što su Dijkstrin algoritam i Bellman-Fordov algoritam.

Ovdje raspravljamo o Dijkstrinom algoritmu. Pogledajmo sljedeći ponderirani graf:

Neusmjereni ponderirani graf

Neusmjereni ponderirani graf

  • Pojam „ponderirano“ označava trošak premještanja s jednog čvora na drugi. Na primjer, premještanjem s čvora 1 na čvor 2, trošak ili ponder je 1.
  • Put između čvora 1 i čvora 2 naziva se rub.
  • „Neusmjereno“ znači da se možete kretati s jednog čvora na drugi i natrag na prethodni čvor. Dakle, ako pokušamo pronaći sve rute od čvora 1 do čvora 7, one će biti:
Ruta ili putTrošak
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

Među ove četiri rute, možemo vidjeti da prva ruta košta 7. Dakle, to je najkraći put u smislu troškova.

Najkraći put

Najkraći put

Kako radi Dijkstrin algoritam

Dijkstrin algoritam može pronaći najkraću udaljenost i u usmjerenim i u neusmjerenim ponderiranim grafovima. Ovaj algoritam je pohlepni jer uvijek bira najkraći ili najbliži čvor iz ishodišta. Pojam "pohlepni" znači da će algoritam između skupa ishoda ili rezultata odabrati najbolji od njih.

Ovdje pokušavamo pronaći najkraće putove među svim ostalim rutama. Dakle, Dijkstrin algoritam pronalazi sve najkraće putove od jednog izvornog čvora. Kao rezultat toga, ponaša se kao pohlepni algoritam.

U odjeljku "primjer" u nastavku vidjet ćete postupni pristup. Funkcionira na sljedeći način:

Korak 1) Inicijalizirajte početni čvor s cijenom 0, a ostale čvorove s beskonačnom cijenom.
Korak 2) Održavati niz ili listu za čuvanje track posjećenih čvorova.
Korak 3) Ažurirajte trošak čvora s minimalnim troškom. To se može učiniti usporedbom trenutnog troška s troškom puta (prikazano u odjeljku s primjerima).
Korak 4) Nastavite korak 3 dok se ne posjete svi čvorovi.

Nakon dovršetka svih ovih koraka pronaći ćemo put koji košta minimalno od izvora do odredišta.

Razlika između Dijkstre i BFS, DFS

Glavna razlika između Dijkstre i BFS-DFS-a je u tome što je Dijkstra algoritam za pronalaženje najkraćeg puta, dok su BFS i DFS opći algoritmi za pronalaženje puta. U općim slučajevima, BFS i DFS ne uzimaju u obzir cijenu ruba prilikom pronalaženja puta. Dakle, ovi algoritmi ne mogu jamčiti najkraći put.

2D mrežna demonstracija rada BFS-a

Demonstracija 2D mreže BFS

Algosketch, prikazuje BFS demonstraciju

Ova demonstracija pokazuje da BFS pronalazi samo put. Međutim, ne mari za težinu staze. BFS (Pretraživanje u širinu) pretpostavlja da će putovanje od jednog čvora do drugog koštati samo 1.

Pogledajmo primjer grafa:

Primjer grafa demonstracije 2D mreže

Ovdje BFS pronalazi put na razini 2. BFS prelazi graf po razinama. Dakle, putuje ovako:

Korak 1) Počnite od čvora „1“ i posjetite sve susjedne čvorove 2, 3, 4.

Korak 2) Označi čvorove 2, 3, 4 kao razinu 1 i posjeti njihove susjedne čvorove. Nastavit će istraživati ​​sve susjedne čvorove dok ne dođe do odredišnog čvora.

Što se tiče DFS-a, proći će put od 1 do 7 na sljedeći način:

  • 1→2→3→7 (Originalni trošak 10, DFS trošak 3)
  • 1→2→6→7 (Originalni trošak 7, DFS trošak 3)
  • 1→3→7 (Originalni trošak 8, DFS trošak 2)
  • 1→4→5→7 (Originalni trošak 13, DFS trošak 3)

Kao što vidimo, DFS izračunava cijenu puta s brojem bridova. DFS radi sljedeće:

  • DFS može pronaći put od izvora (početnog vrha) do odredišta.
  • Ne može jamčiti je li put otkriven od izvornog čvora do odredišta najkraći put ili nije.

Međutim, u smislu Dijkstrinog algoritma, on bira rubove na temelju njihove cijene. Kao pohlepni algoritam, odabrat će putove s minimalnom cijenom.

Primjer Dijkstrinog algoritma

Dijkstrin algoritam koristi cijenu ili težinu za izračun ukupne cijene puta.

Primjer Dijkstrasovog algoritma

Cilj Dijkstrinog algoritma je minimizirati ovaj ukupni trošak ili težinu. U gore prikazanom primjeru pronalazimo najbolje putove od čvora 1 do čvora 7, zatim izračunavamo sve troškove.

U Dijkstrinom algoritmu, pronaći će se najkraći putevi izračunavanjem težina. Neće se tražiti sve moguće puteve. Demonstrirajmo Dijkstrin algoritam na primjeru. Na primjer, zamoljeni ste da pronađete najkraći put od čvora 1 do 7.

U nastavku su navedeni koraci za ovaj postupak:

Korak 1) Inicijalizirajte početnu cijenu čvora na 0. Dodijelite "Inf" do ostatka čvorova. To znači da ne postoji put između izvora i čvora ili put još nije posjećen.

Inicijalizacija Dijkstraovog algoritma

Korak 2) Kada odaberete čvor 1, bit će označen kao posjećen. Zatim ažurirajte sve susjedne čvorove čvora 1. 2, 3, 4 su susjedni čvorovi čvora 1.

Dok ažuriramo trošak, moramo slijediti postupak u nastavku:

Postupak ažuriranja Dijkstraovog algoritma

Trošak svakog čvora možemo ažurirati pomoću gornje formule. Na primjer, bili smo na čvoru 1 i trebali smo ažurirati trošak susjednih čvorova 2, 3 i 4. Nakon ažuriranja, troškovi će izgledati ovako:

Dijkstrasov algoritam nakon prvog ažuriranja

Korak 3) Za čvor „2“, susjedi su 6 i 3. Ažuriramo trošak na „6“ uspoređujući beskonačnost (trenutnu vrijednost) s troškom čvora 2 + trošak puta od 2 do 6. Jednostavno rečeno, čvor „6“ imat će trošak 1 + 3 ili 4.

Čvor ažuriranja Dijkstraovog algoritma 6

Čvor 3 je susjed čvora 2. Međutim, izračunali smo njegov trošak u prethodnom koraku, koji je bio 7. Sada, ako je naš put 1-2-3, čvor 3 će imati trošak 10. Put 1-2- 3 će koštati 10, dok će 1 do 3 koštati 7.

Korak 4) Za čvor 3, susjedni čvor je 7. Dakle, uspoređujući trenutnu vrijednost čvora 7 s cijenom puta (7+1) ili 8, ažurirat ćemo cijenu čvora 7. To je 8. Dakle, pronalazimo put od čvora 1 do čvora 7, a on je 1→3→7. Cijena je 8.

Korak 5) Za čvor 4, ažurirat ćemo cijenu susjednog čvora u skladu s tim. Dakle, čvor "5" imat će ažuriranu cijenu od 8. Nakon koraka 4 i 5, izgledat će ovako:

Dijkstrasov algoritam nakon koraka 4 5

Sada put 1-3-7 ima cijenu 8 (prije). Čvor "7" nije označen kao posjećen jer do čvora "7" možemo doći iz čvora "6". Put "1-2-6" imao je cijenu 4. Dakle, put 1-2-6-7 imat će cijenu 7.

Kako je 7 < 8, najkraći put od izvornog vrha "1" do odredišnog vrha "7" bit će 1-2-6-7, a cijena je 7. Prije je bila 1-3-7, a cijena je bila 8. Dakle, konačni graf će izgledati ovako:

Konačni graf Dijkstrasovog algoritma

Rub označen crnom crtom je naš najkraći put od 1 do 7, a koštat će nas 7.

Nadimak Code Dijkstrin algoritam

Evo pseudokoda za Dijkstrin algoritam:

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++ Implementacija Dijkstrinog algoritma

Za implementaciju Dijkstrinog algoritma pomoću C++, evo koda:

#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);
}

Izlaz:

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 Implementacija Dijkstrinog algoritma

Za implementaciju Dijkstrinog algoritma pomoću Python, evo koda:

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)

Izlaz:

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

Možemo vidjeti da algoritam izračunava najkraću udaljenost od izvornog čvora.

Primjena Dijkstra algoritma

Dijkstrin algoritam ima širok raspon primjena. Među njima, široko se koristi u području umrežavanja. Evo nekih primjena Dijkstrinog algoritma u stvarnom životu:

Dijkstra u Google Karte: Ovaj algoritam je osnova za pronalaženje najkraćih putova, kao što možemo vidjeti iz gornjeg isječka koda.

Primjena Dijkstra algoritma Google Karte

Google ne koristi jednostavni Dijkstrin algoritam. Umjesto toga, koristi modificiranu verziju. Kada odaberete odredište, prikazuje vam više putova u Google Karte. Među tim putovima, neki su sortirani za korisnika. Ti se putovi odabiru na temelju „vremena“. Dakle, „vrijeme“ je cijena ruba za najkraći put.

Dijkstra u IP usmjeravanju: IP usmjeravanje je mrežna terminologija. Opisuje kako se vaš podatkovni paket šalje primatelju putem različitih putova. Ti putovi se sastoje od usmjerivača, poslužitelja i druge opreme. U IP usmjeravanju postoje različite vrste protokola.

Ovi protokoli pomažu usmjerivaču da pronađe najkraće putove za slanje podataka. Jedan od naziva protokola je „OSPF (Open Shortest Path First - Prvo Otvori Najkraći Put)“. OSPF koristi Dijkstrin algoritam. Usmjerivač održava tablicu ruta. Svaki usmjerivač dijeli svoju tablicu sa susjednim usmjerivačima. Nakon što prime ažuriranu tablicu, moraju ponovno izračunati sve putove. U tom trenutku usmjerivač koristi Dijkstrin algoritam.

Ograničenje Dijkstrinog algoritma

Dijkstrin algoritam ne može jamčiti najkraći put u grafu s negativnim bridovima. Dijkstrin algoritam slijedi ove principe:

  • Od jednog čvora do drugog ići će jedan najkraći put.
  • Jednom kada se odabere najkraći put između dva čvora, neće se ponovno izračunati.

Ovdje primijetite dva primjera s negativnim rubovima.

Ograničenje negativnih rubova Dijkstrasovog algoritma

Na lijevom grafu, Postoje tri vrha. Dijkstra će se izvršavati na grafu na sljedeći način:

Korak 1) Početni vrh "1" bit će inicijaliziran na nulu. Ostali čvorovi će imati beskonačnost.

Ograničenje Dijkstrasovog algoritma korak 1

Korak 2) Označi čvor "1" kao posjećen i uvrsti ga u najkraći put.

Korak 3) Udaljenost izvornog čvora 1 do čvorova „2“ i „3“ postavljena je na beskonačnost, budući da najkraći put tek treba izračunati. Dakle, svaki put koji košta manje od beskonačnosti bit će dodan najkraćem putu (pohlepni pristup).

Korak 4) Ažuriranje udaljenosti od izvornog vrha "1" na "2". Trenutna težina bit će 5 (5 < beskonačno). Slično tome, ažurirajte udaljenost od čvora "1" na "3" s težinom 3.

Ograničenje Dijkstrasovog algoritma korak 4

Korak 5) Ako sada provjerimo najkraće udaljenosti od čvora „1“, otkrit ćemo da je 5 najkraća udaljenost za rub 1→2. Dakle, čvor „2“ bit će označen kao posjećen. Slično tome, čvor „3“ također će biti označen kao posjećen jer je najkraća udaljenost 3.

Međutim, ako promatramo, postoji put 1-3-2 koji će koštati samo 2. Ali Dijkstra pokazuje da je od čvora "1" do čvora "2" najkraća udaljenost 5. Dakle, Dijkstra nije ispravno izračunao najkraću udaljenost. Razlog je taj što je Dijkstra pohlepni algoritam. Dakle, nakon što je čvor označen kao posjećen, neće se ponovno razmatrati, iako bi mogao biti dostupan kraći put. Ovaj problem se javlja samo kada rubovi imaju negativne troškove ili rubove negativne težine.

Dijkstra ne uspijeva izračunati najkraći put između dva čvora u ovom scenariju. Kao rezultat toga, ovaj algoritam ima neke nedostatke. Za rješavanje ovog problema negativnih rubova koristi se drugi algoritam nazvan "Bellman-Fordov algoritam". Taj algoritam može raditi s negativnim rubovima.

Složenost Dijkstrinog algoritma

Gornja implementacija koristila je dvije petlje "za". Ove se petlje pokreću za određeni broj vrhova. Dakle, vremenska složenost je O(V²)Ovdje je izraz „O“ oznaka koja daje pretpostavku za Dijkstrin algoritam.

Graf možemo pohraniti pomoću "reda prioriteta". Red prioriteta je binarna struktura podataka hrpe. Bit će učinkovitija od 2D matrice. Rub s minimalnim troškovima imat će visoki prioritet. Tada će vremenska složenost biti O(E log V). Ovdje je E broj bridova, a V broj vrhova.

Složenost prostora je O(V²), budući da koristimo matricu susjedstva (2D niz). Složenost prostora može se optimizirati korištenjem popisa susjedstva ili strukture podataka reda čekanja.

Pitanja i odgovori

Agenti za planiranje puta umjetne inteligencije u robotici, autonomnim vozilima i NPC-ovima u igrama koriste Dijkstrin algoritam za pronalaženje ruta s najnižim troškovima na ponderiranim grafovima. Okruženja za učenje s potkrepljenjem također se oslanjaju na njega za izračunavanje optimalnih referentnih putova za raspodjelu nagrada.ping i evaluacija.

Da. AI asistenti za kodiranje poput GitHub Copilota i GPT-a mogu generirati Dijkstrin algoritam u Python, C++, ili Java, uključujući varijante reda s prioritetom korištenjem hrpa. Također mogu ispisati stvarni najkraći put ili prilagoditi kod grafovima pohranjenim kao liste susjednosti.

Koristeći jednostavan niz za pronalaženje minimalnog čvora, Dijkstrin algoritam se izvršava u vremenu O(V²). S redom prioriteta binarne hrpe pada na O((V + E) log V), a s Fibonaccijevom hrpom doseže O(E + V log V), što je najbolje za rijetke grafove.

Dijkstra finalizira vrh čim odabere trenutnu minimalnu udaljenost. Kasniji negativni rub mogao bi učiniti dulji put jeftinijim, ali finalizirani vrh se nikada ne posjećuje ponovno, pa algoritam prijavljuje netočnu najkraću udaljenost.

Odaberite Dijkstrin model kada je svaka težina brida nenegativna jer je brži pri O((V+E) log V). Odaberite Bellman-Fordov model kada bridovi mogu biti negativni ili trebate detektirati cikluse s negativnom težinom; njegovo vrijeme izvođenja O(V·E) je kompromis.

Google Karte koriste varijante i nasljednike Dijkstre, uključujući A* i Contraccione hijerarhije, podešene za cestovne mreže i promet u stvarnom vremenu. Temeljna ideja pohlepnog širenja minimalnim akumuliranim troškovima i dalje je Dijkstrin ključni doprinos.

A* proširuje Dijkstru dodavanjem heurističke procjene udaljenosti do cilja, proširujući manji broj čvorova kada je dostupna dobra heuristika. Dijkstra istražuje u svim smjerovima, dok A* pristrasno pretražuje prema cilju, što ga čini bržim u praksi.

Osim karata, Dijkstra omogućuje OSPF i IS-IS protokole usmjeravanja na internetu, optimizaciju topologije mreže, usmjeravanje telefonskih poziva, planiranje kretanja robotike, upite za najkraće veze na društvenim mrežama i minimiziranje troškova leta zrakoplovnim kompanijama.

Sažmite ovu objavu uz: