Esercitazioni di Informatica e Sistemi

Algoritmo di Dijkstra.

Ricerca del cammino minimo dal nodo i al nodo j su un grafo pesato (con pesi≥0).

Il grafo è rappresentato tramite la matrice delle adiacenze e i nodi sono indicati con un numero intero da 0 a n-1.

Strutture dati

Variabili intere

Algoritmo

nodoAttuale ← inizio
visitato[nodoAttuale] ← 1
distanza[inizio] ← 0
minDistanza = 0
finchè (nodoAttuale  diverso da fine) e (ci sono ancora nodi raggiungibili)
    per tutti i nodi j
      Aggiungi al costo del cammino percorso, il costo tra il nodoAttuale e il nodo j 
      se il costo ottenuto è minore del costo per raggiungere i
         sostituisci il costo (distanza[j]) con il costo tra ila nodoAttuale e il percorso verso j 
         aggiorna il nodo di provenienza
      Scelta del nodo a cui si è giunti con un percorso di costo minimo nodo e che non sia ancora stato visitato
      (questo diventa nodoAttuale).
      il nodo scelto diventa nodoAttuale. Non dovrà essere riesaminato, quindi lo si marca come visitato
fine finchè

Programma

#include <cstdlib>
#include <iostream>
#include <fstream>
#include <climits>

using  namespace std;

int main() { 
/*

Dichiarazioni delle variabili intere

*/

I dati verranno forniti nel file di testo "input.txt" e i risultati verranno registrati nel file di testo "output.txt".

Dichiarare i due file: in e out:

  ifstream in("input.txt"); 
  ofstream out("output.txt");

Il file input.txt contiene:

I nodi sono numerati a partire da zero allo scopo di metterli in corrispondenza con gli indici dei vettori

Lettura della prima riga del file:

  int n;  //numero dei nodi

  in >> n;   // numero dei nodi
  in >> inizio;  // nodo di partenza
  in >> fine;   // nodo di arrivo

Per verifica, viene preparato un messaggio nel file di output:

  out << n << " nodi" << endl;  // messaggio di output
  out << "ricerca del percorso di costo minimo da " << inizio << " a " << fine << endl;

Dichiarazione della matrice delle adiacenze:

  int **g; // g è  un array di puntatori a puntatori int 
  g = new int *[n]; // assegna a g l'indirizzo di un array di n puntatori a int
  for (k=0; k<n; k++) 
    g[k] = new int[n]; // ad ognuno degli n puntatori assegna l'indirizzo di un array di n int

Prima di assegnare i costi tra i nodi adiacenti, si assume che tra tutti i nodi i e j non ci sia un percorso. Si assegna il valore predefinito INT_MAX ad ogni elemento della matrice delle adiacenze.

  for (i=0; i<n; i++)
    for (j=0; j<n; j++) 
      g[i][j] = INT_MAX;

Dal file di input vengono lette le righe contenenti una coppia di nodi e il costo del cammino tra essi

  while (!in.eof()) {
    in >> i;       // lettura del numero del nodo
    in >> j;       // lettura di un successore del nodo i
    in >> g[i][j]; // costo del cammino da i a j
  }
  in.close();

Dichiarare i vettori:

  distanze è un puntatore a int 
  creare un array di n int ed assegnare il suo indirizzo a distanze

  provenienze è un puntatore a int 
  creare un array di n int ed assegnare il suo indirizzo a provenienze

  visitato è un puntatore a int 
  creare un array di n int ed assegnare il suo indirizzo a visitato

Assegnare i valori iniziali agli elementi dei vettori.

Un elemento j del vettore distanze è associato ad un nodo j, questo elemento rappresenta la minima distanza del nodo j dal nodo di partenza.

  for (i=0; i<n; i++) 
    distanze[i] = INT_MAX;

L'assegnazione del valore INT_MAX ad un nodo serve per specficare che il nodo non risulta ancora adiacente a nessuno dei nodi visitati.

Un elemento j del vettore provenienze specifica qual è il nodo precedente lungo il cammino di costo minimo. Il valore iniziale di ogni elemento dell'array è -1 per indicare che il nodo non è stato ancora raggiunto.

  for (i=0; i<n; i++) 
    provenienze[i] = -1;

L'array visitato è un vettore di booleani. Per ciascun nodo specifica 0 se non visitato, 1 se visitato

  for (i=0; i<n; i++)
    visitato[i] = 0;

Il cammino inizia dal nodo inizio e il costo di questo primo passo è 0.

  nodoAttuale= inizio;
  distanze[inizio] = minDistanza = 0; // anche il costo del cammino è 0

Tra tutti i successori del nodoAttuale si cerca quello non ancora visitato ed avente il minimo costo:

  while (nodoAttuale != fine && minDistanza != INT_MAX) {
    minDistanza = INT_MAX;
    for (j=0; j<n; j++)
      if (!visitato[j] && distanze[j] < minDistanza) {
        minDistanza =  distanze[j];
        nodoAttuale = j;
      }

A questo punto si è trovato il nodo a cui si giunge con il minimo costo. Viene scelto per proseguire il cammino.

Il nodo scelto viene marcato come visitato per non essere più considerato

    visitato[nodoAttuale] = 1; 

valutazione dei costi tra tutti i successori del nodo attuale e aggiornamento dei costi dei cammini:

  for (j=0; j<n; j++)
      if (g[nodoAttuale][j] != INT_MAX 
          &&
          distanze[j] > distanze[nodoAttuale] + g[nodoAttuale][j]
         ) {
              distanze[j] = distanze[nodoAttuale] + g[nodoAttuale][j];
              provenienze[j] = nodoAttuale;
           }
  }

stampa dei risultati:

  if (!visitato[fine])
    out << "non esiste cammino da nodo " << inizio << " a " << fine << endl;
  else {
    out << "cammino di costo " << distanze[fine];
    i = fine;
    out << endl << "percorso a ritroso: ";
    while (i != inizio) {
      out << i << " ";
      i = provenienze[i];
    }
    out << i << " ";
  }
  out.close();
  return EXIT_SUCCESS;
}

Esempio di file di input:

il file input.txt

6 1 5
1 2 5
2 3 1
3 4 3
4 5 2
5 1 6
1 4 4

il file prodotto in output:

6 nodi
ricerca del percorso di costo minimo da 1 a 5
cammino di costo 6
percorso a ritroso: 5 4 1

Problemi:

Modificare i costi e verificare che viene scelto il percorso di costo minimo.

introdurre la matrice delle adiacenze di un grafo più complesso

Associare un nome ad ogni numero di nodo e e stampare il percorso di costo minimo specificando i nomi anzichè i numeri dei nodi.

Esempio:

Si consideri il seguente grafo:

La matrice delle adiacenze è


 012345
 00126
1013
202
301
401
50

Assegnazioni iniziali:

nodoAttuale = minDistanza = 0
distanza[nodoAttuale] = 0

Vettore distanze:

Nodo Numero:012345
costo da Nodo Iniziale0

Un elemento j del vettore distanze rappresenta la minima distanza del nodo j dal nodo inizio.

Vettore Provenienze

Nodo Numero:012345
precedente:-1-1-1-1-1-1

Un elemento j del vettore provenienze specifica il nodo precedente lungo il cammino di costo minimo. Il valore -1 significa provenienza sconosciuta

Vettore Visitato

Nodo Numero:012345
visitato:000000

Per ciascun nodo 0 indica nodo non visitato, 1 indica nodo visitato.

Ciclo di ricerca del cammino di costo minimo.

    while (nodoAttuale!=fine) {

La condizione specificata è soddisfatta: il nodo attuale 0 è diverso dal nodo di arrivo 5, quindi si entra nel ciclo.

Ciclo di ricerca del minimo in un vettore:


Primo passo del ciclo.

Si cerca il nodo non ancora visitato ed avente costo minimo dal nodo di partenza.

      minDistanza=INT_MAX;
      for (i=0; i<n; i++)
        if (!visitato[i] && distanze[i] < minDistanza ){
          minDistanza =  distanze[i];
          nodoAttuale=i;
        }
      visitato[nodoAttuale]=1;

Il nodo 0 diventa nodoAttuale. Viene marcato come visitato (per non esaminarlo più) e si aggiorna la variabile minDistanza.

Vettore Visitato

Nodo Numero:012345
visitato:100000

per tutte le possibili destinazioni del nodo 0 Calcola il costo del cammino ed aggiorna la provenienza.

Si esaminano tutti i valori della riga 0 della matrice delle adiacenze.

Solo per i nodi adiacenti al nodo 0 si aggiorna il vettore delle distanze, a condizione che si trovi un cammino più conveniente di quello già trovato e si aggiorna anche la provenienza.

  for (i=0; i<n; i++)
    if ( g[nodoAttuale][i]!=INT_MAX && distanze[i] > distanze[nodoAttuale] + g[nodoAttuale][i]) {
      distanze[i] = distanze[nodoAttuale] + g[nodoAttuale][i];
      provenienze[i]=nodoAttuale;
    }

Vettore distanze:

Nodo Numero:012345
costo dal Nodo Iniziale0126

Vettore Provenienze

Nodo Numero:012345
precedente:-100-10-1

Secondo passo del ciclo.

Si cerca il nodo non ancora visitato ed avente costo minimo dal nodo di partenza.

il nodoAttuale è 0 diverso da 5 (nodo di arrivo). Nel vettore distanze si cerca il nodo non ancora visitato ed avente il minimo costo. Si trova il nodo 1, che diventa nodoAttuale e viene marcato come visitato.

Vettore Visitato

Nodo Numero:012345
visitato:110000

Sulla riga 1 della matrice delle adiacenze si cerca la colonna j avente distanza minore.

per tutte le possibili destinazioni del nodo 1 Calcola il costo del cammino ed aggiorna la provenienza

Vettore distanze:

Nodo Numero:012345
costo dal Nodo Iniziale01224

Vettore Provenienze

Nodo Numero:012345
precedente:-10011-1

Terzo passo del ciclo.

Si cerca il nodo non ancora visitato ed avente costo minimo dal nodo di partenza.

il nodoAttuale è 1 diverso da 5 (nodo di arrivo). Nel vettore distanze si cerca il nodo non ancora visitato ed avente il minimo costo. Si trova il nodo 2, che diventa nodoAttuale e viene marcato come visitato.

Vettore Visitato

Nodo Numero:012345
visitato:111000

Nella matrice delle adiacenze si valutano tutte le possibili destinazioni raggiungibili dal nodo 2. Si vede che si raggiunge il nodo 3 con costo 4, mentre il vettore distanze indica che lo stesso nodo 3 è stato già raggiunto con costo 2, quindi in questo ciclo il vettore distanze e il vettore provenienza non vengono aggiornati.


Quarto passo del ciclo.

Si cerca il nodo non ancora visitato ed avente costo minimo dal nodo di partenza.

il nodoAttuale è 2 diverso da 5 (nodo di arrivo). Nel vettore distanze si cerca il nodo non ancora visitato ed avente il minimo costo. Si trova il nodo 3, che diventa nodoAttuale e viene marcato come visitato.

Vettore Visitato

Nodo Numero:012345
visitato:111100

Vettore distanze:

Nodo Numero:012345
costo dal Nodo Iniziale01223

Un elemento j del vettore distanze rappresenta la minima distanza del nodo j dal nodo inizio.

Vettore Provenienze

Nodo Numero:012345
precedente:-10013-1

Quinto passo del ciclo.

Si cerca il nodo non ancora visitato ed avente costo minimo dal nodo di partenza.

il nodoAttuale è 3 diverso da 5 (nodo di arrivo). Nel vettore distanze si cerca il nodo non ancora visitato ed avente il minimo costo. Si trova il nodo 4, che diventa nodoAttuale e viene marcato come visitato.

Vettore Visitato

Nodo Numero:012345
visitato:111110

Per tutte le possibili destinazioni del nodo 4 si calcola il costo del cammino e si aggiorna la provenienza.

Vettore distanze:

Nodo Numero:012345
costo dal Nodo Iniziale012234

Un elemento j del vettore distanze rappresenta la minima distanza del nodo j dal nodo inizio.

Vettore Provenienze

Nodo Numero:012345
precedente:-100134

Sesto passo del ciclo.

Si cerca il nodo non ancora visitato ed avente costo minimo dal nodo di partenza.

il nodoAttuale è 4 diverso da 5 (nodo di arrivo). Nel vettore distanze si cerca il nodo non ancora visitato ed avente il minimo costo. Si trova il nodo 5, che diventa nodoAttuale e viene marcato come visitato.

Vettore Visitato

Nodo Numero:012345
visitato:111111

Per tutte le possibili destinazioni del nodo 5 si calcola il costo del cammino e si aggiorna la provenienza.

Vettore distanze:

Nodo Numero:012345
costo dal Nodo Iniziale012234

Vettore Provenienze

Nodo Numero:012345
precedente:-100134

A questo punto il nodo Attuale è uguale al nodo di arrivo e il ciclo non viene ripetuto. A iniziare dal nodo di arrivo, si cerca nel vettore delle provenienze la successione di nodi da attraversare per percorrere il cammino di costo minimo.


Risultati:

cammino di costo 4

percorso a ritroso rilevato dal vettore delle provenienze: 5 4 3 1 0