Algoritmo di Dijsktra: C++, Python Esempio di codice

Qual รจ il percorso piรน breve o la distanza piรน breve?

Un percorso dal vertice di origine al vertice di destinazione che costa un minimo รจ il percorso piรน breve o la distanza piรน breve. Nella teoria dei grafi รจ possibile avere piรน percorsi da una sorgente a una destinazione. Tra questi percorsi, se esiste un percorso che costa un importo minimo, possiamo chiamarlo algoritmo del percorso piรน breve.

Qui โ€œCostoโ€ indica il numero di nodi nel percorso o la somma dei costi su ciascun percorso. Un percorso puรฒ avere uno o piรน bordi. La connessione tra due vertici รจ chiamata โ€œspigoloโ€. Esistono vari tipi di algoritmi del percorso piรน breve, come l'algoritmo di Dijkstra, l'algoritmo di Bellman-Ford, ecc.

Qui discutiamo dell'algoritmo di Dijkstra

Diamo un'occhiata al seguente grafico ponderato:

Un grafico ponderato non orientato
Un grafico ponderato non orientato
  • Il termine โ€œponderatoโ€ significa spostare i costi da un nodo allโ€™altro. Ad esempio, spostandosi dal nodo 1 al nodo 2, il costo o peso รจ 1.
  • Il percorso tra il nodo 1 e il nodo 2 รจ chiamato bordo.
  • "Non indirizzato" significa che puoi spostare un nodo su un altro e tornare al nodo precedente. Quindi, se proviamo a trovare tutti i percorsi dal nodo 1 al nodo 7, allora sarร :
Itinerario o percorso Costo
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

Tra questi quattro percorsi, possiamo vedere che il primo percorso ci costerร  7. Quindi รจ il percorso piรน breve in termini di costo.

Percorso piรน breve

Percorso piรน breve

Come funziona l'algoritmo di Dijkstra

L'algoritmo di Dijkstra puรฒ trovare la distanza piรน breve nei grafi pesati sia diretti che non orientati. Questo algoritmo รจ avido perchรฉ sceglie sempre il nodo piรน corto o piรน vicino all'origine. Il termine โ€œavidoโ€ significa che tra una serie di esiti o risultati, lโ€™algoritmo sceglierร  il migliore tra loro.

Qui stiamo cercando di trovare i percorsi piรน brevi tra tutti gli altri percorsi. Quindi, l'algoritmo di Dijkstra trova tutti i percorsi piรน brevi da un singolo nodo di destinazione. Di conseguenza, si comporta come a algoritmo avido.

Nella sezione "esempio" di seguito vedrai l'approccio passo passo. Funziona come segue:

Passo 1) Inizializza il nodo iniziale con 0 costi e il resto del nodo come Costo Infinito.
Passo 2) Mantieni un array o un elenco per tenere traccia dei nodi visitati
Passo 3) Aggiorna il costo del nodo con il costo minimo. Puรฒ essere fatto confrontando il costo attuale con il costo del percorso. (La dimostrazione รจ mostrata nella sezione di esempio)
Passo 4) Continuare il passaggio 3 finchรฉ non viene visitato tutto il nodo.

Dopo aver completato tutti questi passaggi, troveremo il percorso che costa il minimo dalla fonte alla destinazione.

Differenza tra Dijkstra e BFS, DFS

La differenza principale tra Dijkstra e BFS-DFS รจ che Dijkstra รจ l'algoritmo di pathfinding piรน breve e BFS, DFS รจ l'algoritmo di pathfinding. In generale, BFS e DFS non considerano il costo durante la ricerca del percorso. Pertanto, questi algoritmi non possono garantire il percorso piรน breve.

Dimostrazione in griglia 2D di come funziona BFS

Dimostrazione della griglia 2D

Algosketch, che mostra la dimostrazione BFS

Questa dimostrazione indica che BFS trova solo il percorso. Tuttavia, non si preoccupa del peso del percorso. BFS (Ricerca in base all'ampiezza) presuppone che viaggiare da un nodo a un altro nodo costerร  solo 1.

Ma vediamo un grafico di esempio:

Dimostrazione della griglia 2D

Qui trova un percorso nel livello 2. BFS attraversa il grafico in ordine di livello. Quindi viaggia come:

Passo 1) Inizia dal nodo โ€œ1โ€ e visita tutti i nodi adiacenti 2,3,4

Passo 2) Contrassegna il nodo 2,3,4 come livello 1 e visita i nodi adiacenti. Continuerร  ad esplorare tutti i nodi adiacenti fino a raggiungere il nodo di destinazione.

In termini di DFS, attraverserร  il percorso da 1 a 7 come segue:

  • 1โ†’2โ†’3โ†’7 (Costo originale 10, costo DFS 3)
  • 1โ†’2โ†’6โ†’7 (Costo originale 7, costo DFS 3)
  • 1โ†’3โ†’7 (Costo originale 8, costo DFS 2)
  • 1โ†’4โ†’5โ†’7 (Costo originale 13, costo DFS 3)

Come vediamo, DFS calcola il costo del percorso con il numero di profonditร  o il numero di bordi.

DFS esegue le seguenti operazioni:

  • DFS puรฒ trovare un percorso dall'origine (vertice iniziale) alla destinazione.
  • Non puรฒ garantire se il percorso scoperto dal nodo di origine alla destinazione sia il percorso piรน breve o meno.

Tuttavia, in termini di algoritmo Dijkstra, sceglierร  i bordi in base al loro costo. Come un algoritmo avido, sceglierร  il percorso migliore o quello a costo minimo.

Esempio dell'algoritmo di Dijkstra

L'algoritmo di Dijkstra utilizza il costo o il peso per calcolare il costo totale del percorso.

Esempio dell'algoritmo di Dijkstra

L'obiettivo dell'algoritmo di Dijkstra รจ ridurre al minimo questo costo o peso totale. Nell'esempio mostrato sopra, troviamo i percorsi migliori dal nodo 1 al nodo 7, quindi calcoliamo tutti i costi.

Nell'algoritmo di Dijkstra, troverร  i percorsi piรน brevi calcolando i pesi. Non cercherร  tutti i percorsi possibili. Dimostriamo l'algoritmo di Dijkstra con un esempio. Ad esempio, ti รจ stato chiesto di trovare il percorso piรน breve dal nodo 1 al nodo 7.

Per questo processo, i passaggi sono indicati di seguito:

Passo 1) Inizializza il costo del nodo iniziale su 0.

Resto del nodo, assegna โ€œInfโ€. Significa che non esiste alcun percorso tra il vertice iniziale (la sorgente) e il nodo, oppure il percorso non รจ ancora stato visitato.

Esempio dell'algoritmo di Dijkstra

Passo 2) Quando selezioni il nodo 1, verrร  contrassegnato come visitato. Quindi aggiorna tutti i vicini adiacenti del nodo 1. 2,3,4 sono i nodi vicini del nodo 1.

Durante l'aggiornamento di un costo, dobbiamo seguire la procedura seguente:

Esempio dell'algoritmo di Dijkstra

Possiamo aggiornare il costo di ciascun nodo utilizzando la formula sopra. Ad esempio, eravamo al nodo 1 e dovevamo aggiornare il costo del nodo adiacente 2,3,4.

Dopo l'aggiornamento, il costo o il peso sarร  simile a questo:

Esempio dell'algoritmo di Dijkstra

Passaggio 3) Per il nodo โ€œ2โ€, i vicini sono 6 e 3. Stiamo aggiornando il costo a โ€œ6โ€ confrontando l'infinito (valore attuale). Il costo del nodo 2 + costo del percorso da 2 a 6. In parole povere, il nodo โ€œ6โ€ avrร  il costo di 1+3 o 4.

Esempio dell'algoritmo di Dijkstra

Il nodo 3 รจ vicino al nodo 2. Tuttavia, abbiamo calcolato il suo costo nel passaggio precedente, che era 7. Ora, se il nostro percorso รจ 1-2-3, il nodo 3 avrร  un costo pari a 10. Percorso 1-2- 3 costeranno 10, mentre da 1 a 3 costeranno 7.

Passaggio 4) Per il nodo 3, il nodo vicino รจ 7. Quindi, confrontando il valore attuale del nodo 7 con il costo del percorso (7+1) o 8, aggiorneremo il costo del nodo 7. Cioรจ 8.

Quindi, troviamo un percorso dal nodo 1 al nodo 7, ed รจ 1โ†’ 3โ†’ 7. Il costo รจ 8.

Passaggio 5) Per il nodo 4, aggiorneremo di conseguenza il costo del nodo adiacente. Quindi, il nodo โ€œ5โ€ avrร  un costo aggiornato pari a 8. Dopo il passaggio 4,5, sarร  simile a questo:

Esempio dell'algoritmo di Dijkstra

Adesso il percorso 1-3-7 ha il costo di 8 (in precedenza). Il nodo โ€œ7โ€ non รจ stato contrassegnato come visitato perchรฉ possiamo raggiungere il nodo โ€œ7โ€ dal nodo โ€œ6โ€. Il percorso โ€œ1-2-6โ€ aveva un costo pari a 4. Quindi il percorso 1-2-6-7 avrร  un costo pari a 7.

Poichรฉ 7 < 8, ecco perchรฉ il percorso piรน breve dal vertice di origine โ€œ1โ€ al vertice di destinazione โ€œ7โ€ sarร  1-2-6-7 e il costo รจ 7. In precedenza era 1-3-7 e il costo erano le 8.

Quindi, il grafico finale sarร  simile a questo:

Esempio dell'algoritmo di Dijkstra

Il bordo contrassegnato da una linea nera รจ il nostro percorso piรน breve da 1 a 7 e ci costerร  7.

Algoritmo di Pseudocodice Dijkstra

Ecco lo pseudo-codice per l'algoritmo di Dijkstra

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

C++ implementazione dell'algoritmo di Dijkstra

Per implementare l'algoritmo di Dijkstra utilizzando C++, ecco il codice:

#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] & amp; & 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] & amp; & graph[U][j] & amp; &
          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);
}

Produzione:

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 implementazione dell'algoritmo di Dijkstra

Per implementare l'algoritmo di Dijkstra utilizzando python, ecco il codice:

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] & lt; = _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 & lt;
    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)

Produzione:

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

Possiamo vedere che l'algoritmo calcola la distanza piรน breve dal nodo sorgente.

Applicazione dell'algoritmo di Dijkstra

Il Dijkstra Algo ha una vasta gamma di utilizzi. Tra questi, รจ ampiamente utilizzato nel campo del networking. Ecco alcuni esempi di utilizzo nella vita reale dell'algoritmo di Dijkstra:

Dijkstra nella mappa di Google: Fondamentalmente, questo algoritmo รจ la spina dorsale per trovare i percorsi piรน brevi. Come possiamo vedere dall'output del frammento di codice sopra.

Applicazione dell'algoritmo di Dijkstra

Google non utilizza il semplice algoritmo Dijkstra. Utilizza invece una versione modificata. Quando selezioni una destinazione, ti mostra piรน percorsi nella mappa di Google. Tra questi percorsi, alcuni percorsi sono smistati per l'utente. Questi percorsi selezionati sono basati sul โ€œtempoโ€. Quindi, il โ€œtempoโ€ รจ un costo vantaggio per il percorso piรน breve.

Dijkstra nel routing IP: Instradamento IP รจ una terminologia di rete. Significa come il tuo pacchetto di dati viene inviato al ricevitore attraverso percorsi diversi. Questi percorsi sono costituiti da router, server, ecc. Nel routing IP esistono diversi tipi di protocolli.

Questi protocolli aiutano il router a trovare i percorsi piรน brevi per inviare i dati. Uno dei nomi dei protocolli รจ "OSPF (Open Shortest Path First)". OSPF utilizza l'algoritmo di Dijkstra. Il router mantiene una tabella di percorsi. Ogni router condivide la sua tabella con i router vicini. Dopo aver ricevuto la tabella aggiornata, devono calcolare di nuovo tutti i percorsi. A quel punto, il router utilizza l'algoritmo di Dijkstra.

Limitazione dell'algoritmo di Dijkstra

L'algoritmo Dijkstra non puรฒ garantire il percorso piรน breve nel grafico del bordo negativo. L'algoritmo di Dijkstra segue queste metodologie:

  • Da un nodo all'altro verrร  preso il percorso piรน breve.
  • Una volta selezionato il percorso piรน breve tra due nodi, questo non verrร  ricalcolato.

Qui, notate due esempi con bordi negativi.

Limitazione dell'algoritmo di Dijkstra

Nel grafico a sinistra, ci sono tre vertici. Dijkstra eseguirร  il grafico come segue:

Passo 1) Il vertice iniziale โ€œ1โ€ verrร  inizializzato a zero. Gli altri nodi avranno infinito.

Limitazione dell'algoritmo di Dijkstra

Passo 2) Contrassegna il nodo โ€œ1โ€ come visitato e includilo nel percorso piรน breve.

Passo 3) La distanza del nodo sorgente 1 dai nodi โ€œ2โ€ e โ€œ3โ€ รจ impostata su infinito, poichรฉ il percorso piรน breve deve ancora essere calcolato. Pertanto, qualsiasi percorso che costerร  meno dell'infinito verrร  aggiunto al percorso piรน breve (approccio goloso).

Passo 4) Aggiornamento della distanza dal vertice di origine o dal nodo di origine da โ€œ1โ€ a โ€œ2โ€. Il peso attuale sarร  5 (5

Limitazione dell'algoritmo di Dijkstra

Passo 5) Ora, se controlliamo le distanze piรน brevi dal nodo โ€œ1โ€, troviamo che 5 รจ la distanza piรน breve per il bordo 1โ€“> 2. Quindi, il nodo โ€œ2โ€ verrร  contrassegnato come visitato.

Allo stesso modo, anche il nodo โ€œ3โ€ verrร  contrassegnato come visitato poichรฉ la distanza piรน breve รจ 3.

Tuttavia, se osserviamo, c'รจ un percorso 1-3-2 che costerร  solo 2. Ma Dijkstra mostra che dal nodo "1" al nodo "2", la distanza piรน breve รจ 5. Quindi, Dijkstra non รจ riuscita a calcolare il percorso piรน breve distanza correttamente. Il motivo per cui รจ successo รจ che Dijkstra รจ un algoritmo avido. Pertanto, una volta che un nodo viene contrassegnato come visitato, non verrร  riconsiderato, anche se potrebbe essere disponibile un percorso piรน breve. Questo problema si verifica solo quando i bordi hanno costi negativi o bordi con peso negativo

Dijkstra non riesce a calcolare il percorso piรน breve tra due nodi in questo scenario. Di conseguenza, questo algoritmo presenta alcuni inconvenienti. Per risolvere questo problema del bordo negativo, viene utilizzato un altro algoritmo chiamato โ€œAlgoritmo Bellman-Fordโ€. Questo algoritmo puรฒ funzionare con bordi negativi.

Complessitร  dell'algoritmo di Dijkstra

L'implementazione sopra ha utilizzato due cicli "for". Questi cicli vengono eseguiti per il numero di vertici. Quindi, la complessitร  temporale รจ O(V2). Qui, il termine โ€œ0โ€ indica una notazione che fornisce un presupposto per lโ€™algoritmo di Dijkstra.

Possiamo memorizzare il grafico utilizzando la โ€œCoda prioritariaโ€. Una coda con prioritร  รจ una struttura di dati heap binaria. Sarร  piรน efficiente di una matrice 2D. Un vantaggio che avrร  un costo minimo avrร  una prioritร  alta.

Quindi la complessitร  temporale sarร  O(E log(V)). Dove E รจ il numero di spigoli e V รจ il numero di vertici.

La complessitร  dello spazio รจ O(V2), poichรฉ stiamo utilizzando una matrice di adiacenza (matrice 2D). La complessitร  dello spazio puรฒ essere ottimizzata utilizzando una lista di adiacenza o una struttura dati di coda.

Riassumi questo post con: