Algoritmul lui Dijkstra Este un algoritm utilizat pentru determinarea drumurilor de cost minim de la o sursă unică. Este un algoritm de tip Greedy, pentru determinarea drumului minim de la un nod sursă la toate celelalte noduri ale grafului. Pentru a reprezenta reţeaua, presupunem o funcţie valoare astfel încât valoare(i, j) este valoarea arcului de la nodul i la nodul j. Dacă nu există un arc de la nodul i la nodul j atunci valoare(i, j) este setată la o valoare arbitrară mare, care indică costul infinit (imposibilitatea) de a merge direct de la nodul i la nodul j. Dacă toate valorile sunt pozitive, următorul algoritm atribuit lui Dijkstra, determină cel mai scurt drum de la nodul s la nodul t al grafului. Un astfel de drum se numeşte drum special. Pentru a reţine costurile drumurilor speciale vom utiliza un tablou numit distanţă cu proprietatea că distanţăi conţine costul celui mai scurt drum de la nodul s la nodul i. Iniţial distanţăs = 0 şi distanţăi = INFINIT pentru orice nod i diferit de nodul s, unde INFINIT este un număr foarte mare.
7
2 1
1 3
1
3 1
2
4
5
1
4 6 Figura Figura2.1.4 3.3.1
Evident, cel mai scurt drum de la nodul s la un nod oarecare al grafului este dat de arcul (s, j) de valoare minimă. Alegem în continuare un drum în ordinea crescătoare a costurilor până când am determinat drumuri de distanţă minimă de la nodul s la oricare dintre nodurile grafului pentru care există un drum începând din nodul s. Reamintesc, matricea costurilor se defineşte astfel: mcij=
costul arcului, dacă arcul (i,j) există 0, dacă i=j
∞, dacă arcul (i,j) nu există şi este o problemă de minim / (-∞, dacă este o problemă de maxim) Pentru arcele inexistente se reţine valoarea
pentru algoritmii de minim respectiv
pentru algoritmii
de maxim. În practică, aceste valori sunt nişte valori foarte mari pe care în mod normal nu pot lua valorile arcelor. Exemplu: pentru graful din figura 2.1.4 iniţial, matricea costurilor este: 0 1
1 0
7
3 1
0
1 0
2
0
0
Datele se citesc, de obicei din fişier. Pentru exemplul de mai sus, fişierul de intrare ar putea fi următorul (pe prima linie este scris numărul de noduri şi pe următoarele linii sunt scrise câte trei numere reprezentând capetele arcelor şi costul arcului). Ordinea în care sunt scrise arcele nu este unică. graf.txt 6 121 237 251 153 532 341 644 611
Implementarea algoritmului Dijkstra folosind reprezentarea cu matrice de costuri: La citirea datelor din fişier se completează matricea costurilor cu valoarea citită, dacă există arcul sau cu o valoare foarte mare, echivalentul
, dacă arcul nu există.
Se vor folosi trei vectori: -un vector viz, care iniţial are, pentru toate componentele valoarea 0; la alegerea unui nod acesta va deveni „vizitat”, valoarea corespunzătoare nodului în vectorul viz va deveni 1. -un vector dr care reţine pentru fiecare nod, nodul precedent al său, în drumul de cost minim; dacă nu există un predecesor al nodului curent, in vectorul dr se reţine valoarea -1. -un vector lung, care reţine lungimea drumului minim de la nodul de plecare la toate celelalte noduri ale grafului. Iniţial, vectorul lung primeşte valorile din matricea de costuri, această valoare urmând a fi îmbunătăţită. Algoritmul lui Dijkstra:
Pas1. Se crează matricea costurilor, se citeşte nodul de plecare şi se iniţializează vectorii viz, dr şi lung. Se vizitează nodul de plecare, nodul precedent lui se reţine a fi nodul 0, în sensul că nu există un asemenea nod. Pas 2. Pentru celelalte n-1 noduri ale grafului se determină nodul pentru care costul de la nodul curent este minim şi se reţine nodul (k) pentru care se realizează valoarea minimă. Dacă costul drumului la nodul k nu este infinit, se va vizita nodul k. Se actualizează drumurile spre toate nodurile folosind minimul determinat spre nodul k. Dacă există un nod j cu proprietatea că lung[j]>lung[k]+mc[k][j], atunci înseamnă că precedentul lui j este k dr[j]=k şi lung[j]=lung[k]+mc[k][j]. Pas 3. Afişarea drumurilor anterior determinate. Dacă lung[i]=max, înseamnă că nu există drum, altfel se afişează drumul determinat de la nodul iniţial la fiecare din nodurile 1, 2, ..., n. Observaţie: determinarea drumului se realizează printr-o funcţie recursivă drum care parcurge vectorul dr până la întâlnirea valorii 0 din acest vector, valoare care se intâlneşte pentru nodul iniţial. Exemplu: să considerăm graful din figura 2.1.4 şi pentru acesta să considerăm nodul 1 ca fiind
nodul de plecare. Iniţial vectorii dr, lung şi viz sunt:
viz
1 1
2 0
3 0
4 0
5 0
6 0
lung
1 0
2 1
3 -1
4 -1
5 3
6 -1
dr
1 0
2 0
3 0
4 0
5 0
6 0
Modificarea vectorilor dr, lung şi viz va fi prezentată în tabelul următor. Lungimea minimă
1
1
2
1
Nodul selectat
Evoluţia vectorilor viz, dr şi lung viz
1 1
2 1
3 0
4 0
5 0
6 0
lung
1 0
2 1
3 8
4 -1
5 2
6 -1
dr
1 0
2 1
3 2
4 0
5 2
6 0
viz
1 1
2 1
3 0
4 0
5 1
6 0
lung
1 0
2 1
3 4
4 -1
5 2
6 -1
dr
1 0
2 1
3 5
4 0
5 2
6 0
viz
1 1
2 1
3 1
4 0
5 1
6 0
lung
1 0
2 1
3 4
4 5
5 2
6 -1
dr
1 0
2 1
3 5
4 3
5 2
6 0
2
5
3
4
1
6, dar nu se poate forma drum spre 6
viz
1 1
2 1
3 1
4 1
5 1
6 0
lung
1 0
2 1
3 4
4 5
5 2
6 0
dr
1 0
2 1
3 5
4 3
5 2
6 -1
viz
1 1
2 1
3 1
4 1
5 1
6 1
lung
1 0
2 1
3 4
4 5
5 2
6 0
dr
1 0
2 1
3 4
4 5
5 2
6 -1
Programul care realizează #include #define max 32000 int mc[20][20],n,viz[20],dr[20],lung[20]; void citire() { int i,j,x,y,c; ifstream f("graf.txt"); f>>n; for (i=1;i<=n;i++) for (j=1; j<=n;j++) if (i!=j) mc[i][j]=max; else mc[i][j]=0; while (f>>x>>y>>c) mc[x][y]=c; f.close(); } void drum(int i) //realizează afisarea drumului determinat { if (i) { drum(dr[i]); cout<
citire(); Dijkstra();
}
Algoritmul Roy-Floyd Este utilizat pentru a determina drumurile minime pentru toate perechile de vârfuri. Vom considera, pe rând, fiecare nod ca fiind sursă şi vom determin astfel drumurile de la fiecare astfel de sursă la toate celelalte noduri. În final se vor obţine drumurile minime între toate perechile de vârfuri. Algoritmul lui Roy-Floyd: Fie graful G=(X,U), reprezentat prin matricea costurilor. Pas 1. iniţial în matricea ponderilor se reţine numai lungimea drumurilor directe între două noduri, adică nu este permis ca un drum între două noduri să treacă printr-un alt nod. Pas 2. la început încercăm să obţinem drumuri mai scurte, între oricare două noduri i, j din X permiţând ca acestea să poată trece prin nodul 1. Aceasta înseamnă că pentru i, j X , se va face comparaţia mc[i] [j]>mc[i][1]+mc[1][j], adică se compară dacă lungimea drumului direct (care nu trece prin alte noduri) este mai mare decât cea a drumului care trece prin nodul 1. În caz afirmativ, se face atribuirea mc[i][j]=mc[i][1]+mc[1][j]. După acest pas, matricea va reţine lungimea optimă a drumurilor între oricare două noduri, drumuri care pot trece prin nodul 1. Pas 3. Înecrcăm să obţinem drumuri mai scurte, între oricare două noduri i,j din X permiţând ca acestea să poată trece şi prin nodul intermediar 2. Aceasta înseamnă că pentru i, j X , se va face comparaţia mc[i][j]>mc[i] [2]+mc[2][j], adică se compară dacă lungimea drumului care nu trece decât cel mult prin nodul 1 este mai mare decât cea a drumului care trece prin nodul 2. In caz afirmativ se face atribuirea mc[i][j]=mc[i][1]+mc[1][j]. După
acest pas, matricea va reţine lungimea optimă a drumurilor între oricare două noduri, drumuri care pot trece prin nodurile intermediare 1 şi 2. Pas 4. Algoritmul continuă în acest mod prin eventuale îmbunătăţiri succesive ale lungimii drumurilor, considerând ca noduri intermediare 3, 4, ..., n, unde n este numărul de noduri al grafului. Observaţii: - problema care apare este dacă acest algoritm conduce sau nu la soluţia optimă. Observăm dacă drumul optim între două noduri i şi j trece prin nodul k, atunci drumul de la i la k este optim şi drumul de la k la j este optim. Daca, prin absurd, ar exista un drum cu o lungime mai mică între i şi k sau l şi j atunci, în mod evident, drumul de la i la j n-ar fi optim. De aici se trage concluzia că algoritmul caută drumul optim între i şi j, printre noduri k, pentru care se cunoaşte lungimea optimă a drumurilor de la i la k şi de la k la j. La pasul k se cunoaşte lungimea drumului optim între oricare două noduri i şi j drumurile trec prin nodurile 1, 2, ..., k-1. După pasul k se cunoaşte lungimea drumului optim între oricare două noduri i şi j, drumuri care trec numai prin nodurile intermediare 1, 2, ..., k. După pasul n problema este rezolvată. - la pasul k nu se optimizează drumurile de la i la k şi de la k la j, deoarece nu se obţine nici o îmbunătăţire din faptul că aceste drumuri pot trece prin nodul k. Din acest motiv se poate utiliza o singură matrice, pentru care se efectuează calculele ( mc[i][k] şi mc[k][j] rămân nemodificate la acest pas). Exemplu: pentru graful din figura 2.1.4 se porneşte de la matricea costurilor: 0 1
1 0
7
3 1
0
1 0
2
0
0
Conform algoritmului de mai sus, la pasul 1 încercăm să obţinem drumuri care trec prin nodul 1: mc[6][2]=
, mc[6][1]=1, mc[1][2]=1 mc[6][2]>mc[6][1]+mc[1][2]
mc[6][2]=mc[6][1]+mc[1][2]=2 mc[6][5]=
, mc[6][1]=1, mc[1][5]=1 mc[6][5]>mc[6][1]+mc[1][5]
mc[6][5]=mc[6][1]+mc[1][5]=4 Conform algoritmului de mai sus, la pasul 2 încercăm să obţinem drumuri care trec prin nodrile 1 si 2: mc[6][3]=
, mc[6][2]=2, mc[2][3]=7 mc[6][3]>mc[6][2]+mc[2][7]
mc[6][3]=mc[6][2]+mc[2][7]=9 mc[6][5]=4, mc[6][2]=2, mc[2][5]=1 mc[6][5]>mc[6][1]+mc[1][5] =3 mc[6][5]=mc[6][1]+mc[1][5]=3, deci obţinem o îmbunătăţire a valorii mc[6][5] determinată la pasul 1 Algoritmul continuă prin luarea în considerare a nodurilor 3, 4, 5, 6 ca noduri intermediare în determinarea drumurilor minime între oricare două noduri din graf. În final se obţine matricea drumurilor:
0 1
1 0
4 3
5 4
2 1
0
1 0
2
2 5
3 4
0 3
0
Programul care realizează determinarea matricei drumurilor şi apoi determinarea efectivă a drumurilor este realizat mai jos. În plus, algoritmul determină şi drumul efectiv, nodurile intermediare între oricare două noduri i şi j între care se poate construi un drum. Pentru nodurile între care nu se poate construi drum este afişat un mesaj corespunzător. Determinarea drumului se realizează cu un algoritm de tipul Divide et impera: acesta presupune găsirea unui nod intermediar k între i şi j astfel încât mc[i][j]=mc[i][k]+mc[k][j]. Dacă un astfel de nod este găsit, se continuă procedeul de descompunere a drumului, adică încercăm să scriem drumul dintre i şi k precum şi dintre k şi j. Procesul de descompunere se încheie în momentul în care nu msi pot determina între două noduri un nod intermediar. În acest caz se va afişa nodul. Acest lucru se face cel mai simplu folosind o funcţie recursivă, drum, care se apelează pentru fiecare pereche de noduri între care există drum. Algoritmul este scris în cele ce urmează: #include #define max 32000 int mc[20][20],n; void citire() { int i,j,x,y,c; f=fopen("in.txt","r"); fscanf(f,"%d",&n); for (i=1;i<=n;i++) for (j=1; j<=n;j++) if (i!=j) mc[i][j]=max; else mc[i][j]=0; while (!feof(f)) { fscanf(f,"%d %d %d",&x,&y,&c); mc[x][y]=c; } fclose(f); } void afisare() { int i,j; for (i=1;i<=n;i++) { for (j=1;j<=n;j++) if (mc[i][j]!=max) cout<
";
void royfloyd() { int i,j,k; for (k=1;k<=n;k++) for (i=1;i<=n;i++) for (j=1;j<=n;j++) if (j!=k && mc[i][j]>(mc[i][k]+mc[k][j])) mc[i][j]=mc[i][k]+mc[k][j];
} void drum (int i, int j) { int sw=0; for (int k=1;k<=n&&!sw;k++) if(i!=k && j!=k && mc[i][j]==mc[i][k]+mc[k][j]) { drum(i,k); drum(k,j); sw=1; } if (sw==0) cout<"<"<
Observaţii: algoritmul poate fi adaptat şi şi pentru calculul drumurilor de lungime maximă; dar pentru a găsi soluţia corectă, trebuie ca graful să nu aibă circuite. Modificarea apare în testul de comparare a lungimilor laturilor, se schimbă semnul de inegalitate.