Programarea algoritmilor pentru calcularea drumului min (Dijkstra, Prim, Kruskal)

Cel mai eficient algoritm cunoscut pentru problema drumurilor optime cu o singură sursă este algoritmul lui Dijkstra, care poate fi descris în mai multe moduri: ca algoritm de tip “greedy” cu o coadă cu priorităti, ca algoritm ce foloseste operatia de “relaxare” (comună si altor algoritmi), ca algoritm cu multimi  de vârfuri sau ca algoritm cu vectori. Diferentele de prezentare provin din structurile de date utilizate. Se foloseste un vector D astfel că d[i] este distanta  minimă de la 1 la i, dintre drumurile care  trec prin noduri deja selectate. O variabilă S de tip multime memorează numerele nodurilor cu distantă   minimă fată de nodul 1, găsite până la un   moment dat. Initial   S={1} si d[i]=cost[1][i], adică se consideră arcul direct de la 1 la i ca drum minim între 1 si i. Pe măsură ce algoritmul evoluează, se actualizează D si S.
S ={1} // S =multime noduri ptr care s-a determinat dist. minima fata de 1
repetă cât timp S contine mai putin de n noduri {
gaseste muchia (x,y) cu x în S si y nu în S care face minim d[x]+cost(x,y)
           adauga y la S
           d[y] = d[x] + cost(x,y)
       }
 La fiecare pas din algoritmul Dijkstra:
 -  Se găseste dintre nodurile j care nu apartin lui S acel nod "jmin" care are distanta minimă fată de nodurile din S;
 -  Se adaugă nodul "jmin" la multimea S;
 -  Se recalculează distantele de la nodul 1 la nodurile care  nu fac parte din S, pentru că distantele la nodurile din S rămân neschimbate;
 -  Se retine în p[j] numărul nodului precedent cel mai apropiat de nodul j (de pe drumul minim de la 1 la j).
Pentru a ilustra modul de lucru al algoritmului Dijkstra considerăm un graf orientat cu următoarele costuri de arce:
(1,2)=5; (1,4)=2; (1,5)=6;
(2,3)=3;
(3,2)=4; (3,5)=4;
(4,2)=2; (4,3)=7; (4,5)=3;
(5,3)=3
Drumurile posibile intre 1 si 3 si costul lor:
1-2-3 = 8; 1-4-3 = 9; 1-4-2-3 = 7;  1-4-5-3 = 8;  1-5-3 = 9;
Drumurile minime de la 1 la celelalte noduri sunt în acest graf:
1-4-2 cost 4
1-4-2-3 cost 7
1-4 cost 2
1-4-5 cost 5
 De observat că într-un drum minim fiecare drum partial este minim; astfel în drumul 1-4-2-3, drumurile partiale 1-4-2 si 1-4 sunt si ele minime.
Evolutia vectorilor D si S pentru acest graf  în cazul algoritmului Dijkstra:
S
D[2]
D[3]
D[4]
D[5]
Nod sel
1
5
M
2
6
4
1,4
4
9
2
5
2
1,4,2
4
7
2
5
5
1,4,2,5
4
7
2
5
3
P[2]
P[3]
P[4]
P[5]
4
2
1
4
C[2]
P[2]
C[3]
P[3]
C[4]
P[4]
C[5]
P[5]
C[6]
P[6]
k
6
1
1
1
5
1
M1
1
M1
1
3
5
3
M2
1
5
1
6
3
4
3
6
5
3
M2
1
2
6
6
3
M2
3
4
5
3
M2
1
M2
6
6
3
M2
3
2
M2
3
M2
1
M2
6
3
2
M2
3
5
M2
3
M2
1
M2
6
M2
2
M2
3

Pas
Arc (Cost)
Acceptabil
Cost total
Afisare
1
1,3 (1)
da
1
1 – 3
2
4,6 (2)
da
3
4 – 6
3
2,5 (3)
da
6
2 – 5
4
3,6 (4)
da
10
3 – 6
5
1,4 (5)
nu
10

6
3,4 (5)
nu
10

7
2,3 (5)
da
15
2 – 3
Vectorul P va arăta în final astfel:
Exemplu de functie pentru algoritmul Dijkstra:
void dijkstra (Net g,int p[]) { // Net este tipul abstract “graf cu costuri”
 int d[M],s[M]; // s= noduri ptr care se stie distanta minima
 int dmin; int jmin,i,j;
  for (i=2;i<=g.n;i++) {
    p[i]=1; d[i]=carc(g,1,i); // distante initiale de la 1 la alte noduri
  }
  s[1]=1;
 for (i=2;i<=g.n;i++) { // repeta de n-1 ori
    // caută nodul j ptr care d[j] este minim
    dmin =MARE;
    for (j=2;j<=g.n;j++) // determina minimul dintre distantele d[j]
      if (s[j]==0 && dmin > d[j]) { // daca j nu e in S si este mai aproape de S
   dmin =d[j]; jmin=j;
      }
    s[jmin]=1; // adauga nodul jmin la S
    for (j=2;j<=g.n;j++) // recalculare distante noduri fata de 1
      if ( d[j] >d[jmin] + carc(g,jmin,j) ) {
   d[j] =d[jmin] + carc(g,jmin,j);
   p[j] =jmin; // predecesorul lui j pe drumul minim
      }
  }
}
In programul principal se apelează repetat functia "drum":
for(j=2;j<=n;j++)
     drum (p,1, j); // afisare drum minim de la 1 la j
Afisarea drumului minim pe baza vectorului "p" se poate face recursiv sau iterativ:
// drum minim intre i si j - recursiv
void drum (int p[], int i,int j) {
  if (j != i)
drum (p,i,p[j]);
  printf ("%d ",j);
}
    // drum minim intre i si j - iterativ  
void drum (int p[], int i,int j){
  int  s[M], sp=0;            // s este o stiva vector cu varful in sp
  printf ("%d ",i); // primul nod de pe calea i~j
  while (j != i) { // pune pe stiva nodurile precedente lui j
     s[++sp]=j;
     j=p[j]; // precesorul lui j
  }
  for( ; sp>=1;sp--) // afisare continut stiva
     printf("%d ",s[sp]);
}
Metoda de ajustare treptată a lungimii drumurilor din vectorul D este o metodă de "relaxare",
folosită si în alti algoritmi pentru drumuri minime sau maxime: algoritmul Bellmann-Ford pentru drumuri minime cu o singură sursă în grafuri cu costuri negative, algoritmul Floyd pentru drumuri minime între oricare pereche de noduri s.a.
Prin relaxarea unei muchii (v,w) se întelege ajustarea costului anterior al drumului către nodul w tinându-se seama si de costul muchiei v-w, deci considerând si un drum către w care trece prin v.
Complexitatea algoritmului Dijkstra este O(n*n) si poate fi redusă la O(m*lg(n)) prin folosirea unei cozi ordonate (min-heap) cu operatie de diminuare a cheii.
Pentru determinarea unui arbore de acoperire de cost minim se cunosc doi algoritmi eficienti
având ca autori pe Kruskal si Prim. Problema   este   de   a   găsi   pentru   un   graf   dat   arborele   de   acoperire   cu   cost   total   minim (MST=Minimum Spanning Tree) sau unul dintre ei, dacă sunt mai multi.
Exemplu: graful neorientat cu 6 noduri si următoarele arce si costuri:
(1,2)=6; (1,3)=1; (1,4)=5;
(2,3)=5; (2,5)=3;
(3,4)=5; (3,5)=6; (3,6)=4;
(4,6)=2;
(5,6)=6;
Arborele minim de acoperire este format din arcele: (1,3),(3,6),(6,4),(3,2),(2,5) si are costul total 1+5+3+4+2=15.
Algoritmul lui Prim seamănă cu algoritmul Dijkstra pentru drumuri minime si foloseste o coadă cu priorităti   de  arce  care  leagă  vârfuri   din  MST  cu  alte  vârfuri   (coada  se  modifică  pe  măsură   ce algoritmul evoluează). Algoritmul lui Prim se bazează pe observatia următoare: fie S o submultime a vârfurilor grafului si R submultimea V-S (vârfuri care nu sunt în S); muchia de cost minim care uneste vârfurile din S cu vârfurile din R face parte din MST. La fiecare pas se face o nouă tăietură în graful rămas si se determină un alt arc din MST; proces repetat de n-1 ori (sau până când S este vidă). Solutia problemei este o multime de arce, deci un vector de perechi de noduri, sau doi vectori de întregi X si Y, cu semnificatia că o pereche x[i]-y[i] reprezintă un arc din MST. Este posibilă si folosirea unui vector de întregi pentru arborele MST. Implementarea mai eficienta a algoritmului Prim folosind 2 vectori:
p [i] = numărul nodului din S cel mai apropiat de  nodul  i din R
c [i] = costul arcului dintre i si p[i]
La fiecare pas se caută în vectorul “c” pentru a găsi nodul  k din R cel mai apropiat de nodul i din S. Pentru a nu mai folosi o multime S, se atribuie lui c[k] o valoare  foarte mare astfel ca nodul k să nu mai fie luat in considerare în pasii următori.
Multimea S este deci implicit multimea nodurilor i cu c[i] foarte mare. Celelalte noduri formează multimea R.
# define M 20           // nr maxim de noduri
# define M1 10000    // un nr. foarte mare (cost arc absent)
# define M2  (M1+1) // alt numar foarte mare (cost arc folosit)
   // alg. Prim pentru arbore minim de acoperire
void prim (Net g, int x[ ], int y[ ]){
  int c[M], cmin;
  int p[M], i,j,k;
  int n=g.n; // n = nr de varfuri
  for(i=2;i<=n;i++) {
    p[i]=1;  c[i]=carc (g,1,i); // costuri initiale
  }
  for(i=2;i<=n;i++) {
 // cauta nodul k cel mai apropiat de un nod din mst
    cmin = c[2];  k=2;
    for(j=2;j<=n;j++)
     if ( c[j] < cmin) {
       cmin=c[j];  k=j;
     }
     x[i-1]=p[k]; y[i-1]= k; // retine muchie de cost minim in x si y
     c[k]=M2;
      // ajustare costuri in U
    for(j=2;j<=n;j++)
     if (carc(g,k,j) < c[j] && c[j] < M2) {
       c[j]= carc(g,k,j);  p[j] =k;
     }
  }
}
 Evolutia vectorilor “c” si “p” pentru exemplul dat este următoarea:
Au fost necesare două constante mari: M1 arată că nu există un arc între două noduri, iar M2 arată că acel arc a fost inclus în MST si că va fi ignorat în continuare.
Vectorul “p” folosit în programul anterior corespunde reprezentării unui arbore printr-un singur vector, de predecesori.
Complexitatea algoritmului Prim cu vectori este O(n*n), dar poate fi redusă la O(m*lg(n)) prin folosirea unui heap pentru memorarea costurilor arcelor dintre U si V.

Ideea algoritmului Kruskal este de a alege la fiecare pas arcul de cost minim dintre cele rămase (încă neselectate), dacă el nu formează ciclu cu arcele deja incluse în MST (selectate). Conditia ca un arc (x,y) să nu formeze ciclu cu celelalte arce selectate se poate exprima astfel:  nodurile x si y trebuie să se afle în componente conexe diferite. Initial fiecare nod formează o componentă conexă, iar apoi o componentă   conexă  contine  toate  nodurile  acoperite  cu  arce  din  MST,  iar  nodurile  neacoperite formează alte componente conexe.
Algoritmul Kruskal pentru găsirea unui arbore de acoperire de cost minim foloseste două tipuri abstracte de date: o coadă cu priorităti si o colectie de multimi disjuncte si poate fi descris astfel:
 citire date si creare coada de arce
repetă {
      extrage arcul de cost minim din coada
         dacă arc acceptabil atunci {
      afisare arc
   actualizare componente conexe
         }
     } până când toate nodurile conectate
 Un arc care leagă două noduri dintr-o aceeasi componentă conexă va forma un ciclu cu arcele selectate anterior si nu poate fi acceptat. Va fi acceptat numai un arc care leagă între ele noduri aflate în două componente conexe diferite.
Pentru reteaua cu 6 noduri si 10 arce (1,2)=6; (1,3)=1;  (1,4)=5;  (2,3)=5; (2,5)=3;
(3,4)=5; (3,5)=6;  (3,6)=4;  (4,6)=2; (5,6)=6
Evolutia algoritmului Kruskal este următoarea:
Toate nodurile din graf trebuie să se afle în componentele conexe. Initial sunt atâtea componente (multimi) câte noduri există. Atunci când un arc este acceptat, se reunesc   cele două multimi (componente) care contin extremitătile arcului în una singura; în felul acesta numărul de componente conexe se reduce treptat până când ajunge egal cu 1 (toate nodurile legate într-un graf conex care este chiar arborele de acoperire cu cost minim).
Evolutia componentelor conexe pentru exemplul anterior:
   Pas                     Componente conexe
       1                   {1}, {2},{3},{4},{5},{6}
       2                    {1,3}, {2},{4},{5},{6}
       3                     {1,3}, {2,5}, {4,6}
       4                      {1,3,4,6}, {2,5}
       7                         {1,2,3,4,5,6}
 In programul următor graful este un vector de arce, ordonat crescător după costuri înainte de a fi folosit. Exemplu:
typedef struct { int v,w,cost ;} Arc;
   // compara arce dupa cost (ptr qsort)
int cmparc (const void * p, const void* q) {
  Arc * pp =(Arc*) p; Arc *qq=(Arc*) q;
  return pp->cost -qq->cost;
}
    // algoritmul Kruskal
 void main ( ) {
  DS ds;   Arc arce[M], a;
  int x,y,n,na,mx,my,nm,k;
  printf ("nr.noduri în graf: ");  scanf ("%d", &n);
  initDS (ds,n); // ds = colectie de multimi disjuncte
  printf ("Lista de arce cu costuri: \n");
  nm=0; // nr de muchii in graf
  while ( scanf ("%d%d%d",&a.v,&a.w,&a.cost) > 0)
     arce[nm++]=a;
  qsort (arce, nm, sizeof(Arc), cmparc);    // ordonare lista arce
  k=0; // nr arc extras din coada
  for ( na=n-1; na > 0; na--) {
     a=arce[k++]; // următorul arc de cost minim
     x=a.v; y=a.w; // x, y = extremitati arc
     mx= findDS (ds,x); my=findDS (ds,y);
     if (mx !=my ) { // daca x si y in componente conexe diferite
       unifDS (ds,x,y); // atunci se reunesc cele doua componente
       printf ("%d - %d \n",x,y); // si se scrie arcul gasit ptr mst
     }
  }
}
Complexitatea algoritmului Kruskal depinde de modul de implementare al colectiei de multimi disjuncte si este în cel mai bun caz O(m*lg(n)) pentru o implementare eficientă a tipului DS (este practic timpul de ordonare a listei de arce).

Popular Posts

Expresii frazeologice

Corespondenta economica

Exam la filozofie: Primele 24 intrebari

Analiza economico - financiara

Motive

Integrale

Finantele Intreprinderii exam

Dreptul Afacerilor T1

Genuri si specii

Integrarea Economica