Sunteți pe pagina 1din 38

CAPITOLUL 3

GRAFURI ORIENTATE

3.1 Introducere

Se nume te graf orientat sau digraf un ansamblul format din


două mulŃimi notate V şi E, unde V este o mulŃime finită şi nevidă de
elemente, iar E o mulŃime de perechi ordonate cu elemente distincte
din mulŃimea V. Elementele mulŃimii V se numesc vârfuri iar mulŃimea V
se mai numeşte şi mulŃimea nodurilor sau vârfurilor. Elementele
mulŃimii E se numesc arce iar mulŃimea E se numeşte mulŃimea arcelor
grafului.
Formal, definiŃia unui graf orientat poate fi scrisă astfel:

V = {x1 , x2 ,..., xn }
G (V , E ) = 
 E = {(a, b) / a, b ∈ V , (a, b) ≠ (b, a )}

O definiŃie intuitivă este următoarea:

Un graf este o mulŃime de obiecte (numite vârfuri) legate între


ele printr-o mulŃime de arce cărora le sunt atribuite direcŃii. În general
se notează cu n numărul de vârfuri şi cu m numărul de arce. Un digraf
poate fi reprezentat geometric ca o mulŃime de puncte legate între ele
prin linii care au sensuri ilustrate prin săgeŃi:

fig.3.1: graf orientat

163
3.2 NoŃiuni de bază

• vârf este un element al mulŃimii de obiecte:


• arc (xi,xj) este o legătură dintre două vârfuri;
• vârfuri adiacente sunt două vârfuri între care există un
arc;
• arce incidente sunt două arce care au un capăt (vârf)
comun;
• arc incident la un vârf este un arc care are unul dintre
capete în vârful respectiv;
• gradul intern al unui vârf x notat d-(x) este egal cu
numărul de arce care „intră” în vârful x;
• gradul extern al unui vârf x notat d+(x) este egal cu
numărul de arce care „pleacă” din vârful x;
• vârf izolat este un vârf cu gradul intern şi gradul extern 0;

ProprietăŃi ale gradelor vârfurilor unui graf orientat cu n vârfuri


• 0 ≤ d-(x) ≤ n-1
• 0 ≤ d+(x) ≤ n-1
• d-(1) + d-(2) +.. + d-(n) = d+(1) + d+(2) +...+
d+(n) = m

164
3.3. Drum, circuit, tare-conexitate

• drum într-un graf orientat este o succesiune de vârfuri vi1,


vi2, vi3, ..., vik cu proprietatea că între oricare două
vârfuri alăturate din respectiva succesiune există un arc, deci
există arcele (vi1,vi2), (vi2,vi3),..., (vik-1,vik).
• drum simplu este un drum în care arcele care-l formează sunt
diferite între ele.
• drum multiplu este un drum care nu este simplu;
• drum elementar este un drum în care oricare vârf care îl
formează apare în acel drum o singură dată.
• drum neelementar este un drum în care cel puŃin un vârf apare
de cel puŃin două ori.
• lungimea unui drum este egală cu numărul de arce care îl
alcătuiesc. Dacă un arc este parcurs de mai multe ori (în cazul
drumurilor multiple, el se numără de fiecare dată când e
parcurs).

• circuit într-un graf orientat este un drum în care primul vârf şi


ultimul vârf sunt identice. Într-un circuit arcele care îl formează
sunt distincte.
• circuit elementar este un circuit în care oricare vârf care îl
formează apare o singură dată (cu excepŃia primului);
• circuit neelementar este un circuit în care cel puŃin un vârf
apare de cel puŃin două ori.
• lungimea unui circuit este egală cu numărul de arce care îl
alcătuiesc.

Un graf tare conex este un graf în care există cel puŃin un drum
între oricare două vârfuri.
Dacă un graf nu este tare conex, el este format din componente
tare conexe. O componentă tare conexă este un subgraf maximal tare
conex al unui graf dat. Graful din figura 3.2 are trei componente conexe
formate din următoarele submulŃimi de vârfuri: {1}, {2,5}, {3,4,6}

165
3
1
2
4

5 6
fig.3.2: Graf orientat cu 3 componente tare conexe

Se poate determina dacă un graf orientat este tare conex utilizând


următorul algoritm:
1. se face o traversare a grafului plecând dintr-
un vârf oarecare x, parcurgând arcele în sens
direct; fie M1 submulŃimea vârfurilor vizitate
prin această traversare;
2. se face o traversare a grafului plecând dintr-
un vârf oarecare x, parcurgând arcele în sens
invers; fie M2 submulŃimea vârfurilor vizitate
prin această traversare;
3. dacă M1∩M2 = {1,2,...,n} atunci
graful este tare conex
altfel
M1∩M2 reprezintă o componentă tare conexă.

166
3.4 Metode de reprezentare a digrafurilor

Sunt similare cu metodele de reprezentare a grafurilor


neorientate. Vom prezenta doar diferenŃele care apar. Formulele pentru
complexitatea operaŃiilor uzuale date la grafurile neorientate sunt
valabile şi în cazul digrafurilor.

3.4.1 Matricea de adiacenŃă

Se notează de regulă cu A. Pentru un graf cu n vârfuri, această matrice


are n linii şi n coloane. Elementele ei primesc valori după formula:
 0 pentru i = j
a i , j = 1 daca exista arcul (i, j)
 0 daca nu exista arcul (i, j)

De exemplu, pentru digraful din figura 2, matricea de adiacenŃă este

0 0 0 0 0 0
1 0 0 1 1 0
A= 0 0 0 1 0 0
0 0 0 0 0 1
0 1 0 1 0 1
0 0 1 0 0 0

ProprietăŃile matricelor de adiacenŃă:


• diagonala principală conŃine numai valoarea 0;
• nu este simetrică fata de diagonala principala
• gradul extern al vârfului x este egal cu numărul de valori egale
cu 1 din linia
• gradul intern al vârfului x este egal cu numărul de valori egale
cu 1 din coloana x
• pentru un vârf izolat, linia şi coloana corespunzătoare din
matricea de adiacenŃă conŃin numai valoarea 0
• numărul de valori egale cu 1 din matrice este egal cu m;

167
3.4.2 Liste de adiacenŃă

Pentru fiecare vârf se construieşte o listă cu vârfurile spre care


„pleacă” arce din respectivul vârf.
De exemplu, pentru graful orientat din figura 3.2, listele de adiacenŃă
sunt următoarele:
1:
2: 1,5,4
3: 4
4: 6
5: 6,4,2
6: 3
ProprietăŃile listelor de adiacenŃă:
• gradul extern al unui vârf este egal cu numărul de valori din lista
lui;
• gradul intern al unui vârf este egal cu numărul de lise în care
apare acel vârf;
• numărul total de valori care apar în toate listele este egal cu m.
Pentru reducerea complexităŃii diverselor operaŃii referitoare la un graf
este indicat să sortăm aceste liste.
Tot pentru reducerea complexităŃii operaŃiilor în programe este utilă
crearea unui al doilea grup de liste de adiacenŃă în care pentru vârful i
vom avea o listă cu vârfurile de la care „vin” arce la i. Pentru graful
orientat din figura 3.2, aceste liste de adiacenŃă sunt următoarele:
1: 2
2: 5
3: 6
4: 2,3,5
5: 2
6: 4,5

168
3.4.3 Lista de arce

Se construieşte o listă cu arcele grafului.


De exemplu, pentru graful din figura 3.2, lista de arce este următoarea:

(2,1), (2,5), (2,4), (6,3), (3,4), (5,4), (5,2),


(5,6), (4,6)

ProprietăŃile listei de arce:


• numărul total de perechi care apare este egal cu m;
• gradul extern al unui vârf este egal cu numărul de apariŃii ale
vârfului respectiv în prima poziŃie din perechile din listă;
• gradul intern al unui vârf este egal cu numărul de apariŃii ale
vârfului respectiv în a doua poziŃie din perechile din listă.

Programul următor aplică cele trei modalităŃi de reprezentare şi


determină componentele tare conexe. Implementarea celor trei
modalităŃi de reprezentare se face astfel:
- matricea de adiacenŃă printr-o matrice cu elemente de tip bool;
- listele de adiacenŃă printr-un tablou de vectori; pentru reducerea
complexităŃii operaŃiei de căutare în aceste liste ele au fost
sortate;
- lista de arce printr-un vector de perechi a câte două valori;
Am presupus că numărul maxim de vârfuri este 100. Pentru alte valori,
se modifică declaraŃia #define. Câteva observaŃii referitoare la acest
program:
- am scris două funcŃii care calculează gradul intern pentru un vârf
pe baza matricei de adiacenŃă (gradintern) şi a listelor de
adiacenŃă (gradinternla); programul le apelează pe ambele
pentru a afişa gradele interne pentru toate vârfurile din digraf;
- funcŃia dfsd implementează traversarea digrafului parcurgând
arcele în sens direct iar funcŃia dfsi implementează traversarea
digrafului parcurgând arcele în sens invers;
- tabloul ctcx este un tablou de marcaje: ctcx[vf] va reŃine
numărul componentei conexe căreia îi aparŃine vârful vf.
- graful este dat în fişierul „graf.in” cu următorul conŃinut:
o pe prima linie n şi m cu semnificaŃiile utilizate şi până
acum;
o pe următoarele m linii câte două valori i j cu
semnificaŃia: există arc de la vârful i la vârful j;

169
//program 3.1 metode de reprezentare a digrafurilor
//determinare componente tare conexe
#include <iostream>
#include <fstream>
#include <deque>
#include <algorithm>
#include <vector>
#define nmax 101
using namespace std;
int n,m;
bool A[nmax][nmax]; //matricea de adiacenta
vector <pair <int,int> > LARCE; //lista de arce
deque <int> LA[nmax]; //listele de adiacenta
int ctcx[nmax];
void citiremat(bool A[nmax][nmax], int &n, int &m,
char numefisier[])
{ ifstream fin (numefisier);
fin >> n >> m;
for (int i=1;i<=n;i++)
for (int j=1;j<=n;j++)
A[i][j]=0;
for (int i=1;i<=m;i++)
{
int x,y;
fin >> x >> y;
A[x][y] = 1;
}
fin.close();
}
void afisaremat(bool A[nmax][nmax], int n)
{ for (int i=1;i<=n;i++)
for (int j=1;j<=n;j++)
{ cout << A[i][j]<<' ';
if (j==n)
cout << endl;
}
}
int gradinternma(bool A[nmax][nmax], int n, int
varf)
{ int s=0,i;
if (varf<1 || varf > n)
{ cout << "varf inexistent" << endl;
return -1;
}
for (i=1;i<=n;i++)
170
s+=A[i][varf];
return s;
}
void citirela(deque <int> LA[], int &n, int &m, char
numefisier[])
{ ifstream fin (numefisier);
fin >> n >> m;
for (int i=1;i<=m;i++)
{ int x,y;
fin >> x >> y;
LA[x].push_back(y);
}
for (int i=1;i<=n;i++)
sort(LA[i].begin(), LA[i].end());
fin.close();
}
void afisaredeq(deque <int> q)
{ for (int i=0;i<q.size();i++)
cout << q[i] << ' ';
cout << endl;
}
int gradinternla(deque <int> LA[], int n, int varf)
{ if (varf<1 || varf > n)
{ cout << "varf inexistent" << endl;
return -1;
}
int s=0;
for (int i=1;i<=n;i++)
s +=
binary_search(LA[i].begin(),LA[i].end(),varf);
return s;
}
bool existaarcla(deque <int> LA[], int &n, int v1,
int v2)
{ if (v1<1 ||v1>n ) return false;
if (v2<1 ||v2>n ) return false;
return
binary_search(LA[v1].begin(),LA[v1].end(),v2);
}
bool existaarcma(bool A[nmax][nmax], int n, int v1,
int v2)
{ if (v1<1 ||v1>n ) return false;
if (v2<1 ||v2>n ) return false;
return A[v1][v2];
}
171
void citirelarce(vector <pair <int,int> > &LM, int
&n, int &m, char numefisier[])
{ ifstream fin (numefisier);
fin >> n >> m;
for (int i=1;i<=m;i++)
{ pair <int,int> aux;
fin >> aux.first >> aux.second;
LM.push_back(aux);
}
fin.close();
}
void afisarelarce(vector <pair <int,int> > LM)
{ int i;
for (i=0;i<LM.size()-1; i++)
cout<< LM[i].first << '-' << LM[i].second<<" ; ";
cout << LM[i].first << '-' << LM[i].second<< endl;
}
bool viz1[nmax], viz2[nmax];
void dfsd(int x)
{ viz1[x] = true;
for (int i=1;i<=n;i++)
if (A[x][i]==1 && viz1[i] == false)
{ viz1[i]=true;
dfsd(i);
}
}
void dfsi(int x)
{ viz2[x] = true;
for (int i=1;i<=n;i++)
if (A[i][x]==1 && viz2[i] == false)
{ viz2[i]=true;
dfsi(i);
}
}
int main()
{ citiremat(A,n,m,"graf.in");
cout << "Matricea de adiacenta\n";
afisaremat(A,n);
cout<< lista gradelor interne din matrice:\n"<<;
for (int k=1;k<=n;k++)
cout<<k<<":"<<gradinternma(A,n,k)<<" ";
cout<<endl;
cout << "\nListe de adiacenta\n";
citirela(LA,n,m,"graf.in");
172
for (int i=1;i<=n;i++)
{ cout << i << " : ";
afisaredeq(LA[i]);
}
cout << endl;
cout << "lista gradelor interne: " << endl;
for (int k=1;k<=n;k++)
cout<<k<<":"<<gradinternla(LA,n,k)<<" ";
cout << endl;
cout << "\nLista arcelor:\n";
citirelarce(LARCE,n,m,"graf.in");
sort(LARCE.begin(), LARCE.end());
afisarelarce(LARCE);
int v1,v2;
cout<<"dati 2 noduri (intre 1 si "<<n<<"): ";
cin >> v1 >> v2;
bool rez = existaarcma(A,n,v1,v2);
if (rez)
cout<<"exista arcul intre cele doua varfuri\n";
else
cout << "nu exista arc intre aceste varfuri\n";
bool rez1 = existaarcla(LA,n,v1,v2);
if (rez1)
cout<<"exista arcul intre cele doua varfuri\n";
else
cout << "nu exista arcul intre aceste varfuri\n";
int k=0;
for (int i=1;i<=n;i++)
{ if (ctcx[i]==0)
{ k++;
cout<<"componenta tare conexa "<<k<<endl;
fill(viz1+1,viz1+n+1,false);
fill(viz2+1,viz2+n+1,false);
dfsd(i);
dfsi(i);
for (int j=1;j<=n;j++)
if (viz1[j] == true && viz2[j]==true)
{ ctcx[j]=k;
cout << j << ' ';
}
cout << endl;
}
}
return 0;
}
173
Pentru digraful din figura 3.2 programul va afişa în fereastra consolă
următoarele:
Matricea de adiacenta
0 0 0 0 0 0
1 0 0 1 1 0
0 0 0 1 0 0
0 0 0 0 0 1
0 1 0 1 0 1
0 0 1 0 0 0
lista gradelor interne din matrice:
1:1 2:1 3:1 4:3 5:1 6:2

Liste de adiacenta
1 :
2 : 1 4 5
3 : 4
4 : 6
5 : 2 4 6
6 : 3
lista gradelor interne:
1:1 2:1 3:1 4:3 5:1 6:2

Lista arcelor:
2-1 ; 2-4 ; 2-5 ; 3-4 ; 4-6 ; 5-2 ; 5-4 ; 5-6 ; 6-3

dati 2 noduri (intre 1 si 6): 4 6


exista arcul intre cele doua varfuri date
exista arcul intre cele doua varfuri date

componenta tare conexa 1


1
componenta tare conexa 2
2 5
componenta tare conexa 3
3 4 6

174
3.5 Drumuri de cost minim în grafuri orientate

În acest capitol vom presupune că se dă un graf orientat în care


fiecare arc are ataşat un cost pozitiv (vezi figura 3.2). Astfel de grafuri
mai sunt denumite grafuri ponderate. Prin drum de cost minim între
două vârfuri x şi y vom înŃelege un drum de la x la y care are
proprietatea că suma costurilor arcelor care îl compun este minimă.
Evident pot exista mai multe drumuri de cost minim.
Problema determinării drumurilor de cost minim se poate pune în mai
multe moduri:
1. Determinarea drumurilor de cost minim de la un vârf (denumit
vârf sursă) la toate celelalte vârfuri; Această variantă a
problemei se numeşte „problema drumurilor de cost minim de
sursă unică”.
2. Determinarea drumurilor de cost minim de la toate vârfurile
grafului la un vârf (denumit vârf destinaŃie). Această problemă se
rezolvă la fel ca prima problemă inversând în prealabil sensurile
arcelor şi păstrând costurile!
3. Determinarea drumurilor de cost minim de la un vârf (denumit
vârf sursă) la un alt vârf denumit vârf destinaŃie. Această
problemă se rezolvă prin varianta 1. Nu se cunosc algoritmi cu
complexitate mai mică decât cei care rezolvă problema
drumurilor de cost minim de sursă unică 1;
4. Determinarea drumurilor de cost minim între oricare pereche de
vârfuri. Evident că această variantă se poate rezolva aplicând
varianta 1 pentru fiecare vârf al grafului. Vom vedea că există un
algoritm dedicat pentru rezolvarea acestei probleme.

4 2
1
9 8 6 9 9
4 2
3 3
1 6
3 1 7
7
5 2
fig.3.3: graf ponderat

MenŃionăm că există algoritmi care determină drumurile de cost minim


şi pentru grafuri orientate în care arcele au costuri negative, cu condiŃia
ca în acele grafuri să nu existe cicluri de cost negativ.

Majoritatea algoritmilor care determină drumuri de cost minim


(între care şi cei doi pe care îi vom prezenta in cele ce urmează) se
175
bazează pe o operaŃie specifică denumită „operaŃia de relaxare a unui
cost”. Aceasta urmăreşte să optimizeze costul drumului determinat la
un moment dat între două vârfuri, i şi j, comparându-l cu costul
drumului de la i la un vârf intermediar k şi de la acel vârf k la vârful j.
Evident dacă suma celor două costuri (de la i la k şi de la k la j este
mai mică decât costul „drumului direct” de la i la j, atunci vom
micşora costul drumului de la i la j „mergând” prin vârful k. Pentru
figura 3.4, notând cu cij costul drumului de la vârful i la vârful j,
operaŃia de relaxare:
dacă cij > cik + ckj atunci
cij  cik + ckj
predecesorul vârfului j  k

j
cij
i ckj
cik k

fig.3.4: relaxarea costului drumului


de la vâtful i la vârful j

3.5.1 Reprezentarea grafurilor ponderate

Reprezentarea grafurilor ponderate constituie „punctul de plecare”


pentru algoritmii pe care îi vom prezenta în continuare. ModalităŃile de
reprezentare sunt asemănătoare cu cele a grafurilor care nu au ataşate
costuri la muchii sau arce.

A) Matricea ponderilor

Vom nota această matrice cu H Dacă notăm cu C(x,y) costul arcului


(x,y), matricea ponderilor are următoarea definiŃie:

0 pentru i = j

hi , j = ∞ daca nu exista arcul (i, j)
C(i, j) daca exista arcul (i, j)

În programe vom folosi pentru ∞ o valoare suficient de mare, dar nu


INT_MAX deoarece variabile având această valoare vor fi implicate în
operaŃii de adunare ceea ce ar determina erori de rulare sau rezultate
176
eronate dacă respectivele variabile ar avea această valoare maximă a
tipului int.
Pentru graful din figura 3.3, matricea ponderilor are următoarea
configuraŃie:

0 469 ∞ ∞
∞ 09 ∞ ∞ 9
∞ ∞ 011 7
H=
8 ∞ 20 3 ∞
∞ ∞ 7 ∞ 0 2
∞ ∞ 3 ∞ ∞ 0

B) Liste de adiacenŃă ponderate


Pentru fiecare vârf vf din graf vom reŃine un şir de perechi de valori;
valorile din fiecare pereche vor reprezenta un vârf spre care există arc
de la vf şi costul ataşat acelui arc. De exemplu, pentru graful din
figura 3.3, aceste liste de adiacenŃă au următoarea configuraŃie:
1: (2,4),(3,6),(4,9)
2: (3,9),(4,9)
3: (4,1),(5,1),(6,7)
4: (1,8),(3,2),(5,3)
5: (3,7),(6,2)
6: (3,3)

3.5.2 Algoritmul Dijkstra

A fost descoperit de Edsger W. Dijkstra în 1956 şi publicat trei ani mai


târziu. [4]
El se aplică pentru rezolvarea problemei drumurilor de cost minim de
sursă unică: determină drumurile de cost minim de la un vârf dat, pe
care îl vom nota s la celelalte vârfuri ale grafului. Algoritmul
construieşte două tablouri d şi T având următoarea semnificaŃie:
• d[i] = valoarea drumului de cost minim de la
sursă (vârful s) la vârful i; d[s]=0.
• T[i] = predecesorul vârfului i pe drumul de
cost minim de la sursă (vârful s) la vârful i;
T[s]=0.

177
Tabloul d ne dă valoarea drumului de cost minim de la vârful sursă la
fiecare dintre celelalte vârfuri ale grafului iar tabloul T ne permite să
determinăm arcele din care sunt formate aceste drumuri.
Algoritmul mai utilizează un tablou v cu semnificaŃia:
• v[i] = true dacă vârful i a fost analizat şi
false în caz contrar.

Vom prezenta o formă a algoritmului care se bazează pe matricea


ponderilor. În algoritm am notat cu ∞ o valoare suficient de mare (vezi
explicaŃiile de la subcapitolul 3.3.1).

1. construieşte H (matricea ponderilor)


2. iniŃializează tabloul d cu ∞
3. iniŃializează tabloul v cu false
4. T[s]  0
5. d[s]  0
6. pentru k  1,n-1 execută
a. determină p astfel încât
d[p] = min (d[i], i=1,2,...,n) şi
v[i]=false
b. v[p]  true
c. pentru i  1,n execută
i. dacă d[i] > d[p] + H[p][i] atunci
d[i]  d[p] + H[p][i]
T[i]  p
sfarşitdacă
sfarşitpentru
sfarşitpentru

Complexitatea algoritmului este O(n2).


Nu este dificil să rescriem algoritmul utilizând listele de adiacenŃă
ponderate. Utilizând minheap-uri Fibonacci, complexitatea algoritmului
poate fi redusă la O(m+n×log n) [6].
Figura 3.5 ilustrează evoluŃia algoritmului pentru graful din figura 3.3.
Câteva precizări:
- pentru tabloul v am notat cu f valoarea false şi cu t valoarea
true;
- notaŃia p este cea din pasul 6.a al algoritmului de mai sus
(vârful care furnizează minimul din tabloul d)

178
p=1
2 4 2
1 1
9 6
4 4
3 6 3 6

5 5
d: 0 oo oo oo oo oo d: 0 4 6 9 oo oo
T: 0 -1 -1 -1 -1 -1 T: 0 1 1 1 -1 -1
v: f f f f f f v: t f f f f f
p=2 p=3
4 2 4 2
1 1
9 6 9 6 9
4 4
3 6 1 3 6
1
5 5
d: 0 4 6 9 oo 13 d: 0 4 6 7 7 13
T: 0 1 1 1 -1 2 T: 0 1 1 3 3 2
v: t t f f f f v: t t t f f f
p=5 p=4
4 4 2
1 1
6 6
4 4
1 3 6 1 3 6
1 1
5 2 5 2

d: 0 4 6 7 7 9 d: 0 4 6 7 7 9
T: 0 1 1 3 3 5 T: 0 1 1 3 3 5
v: t t t f t f v: t t t t t f
Fig. 3.5 Desfăşurarea algoritmului Dijsktra

Programul 3.2 implementează algoritmul Dijsktra.


Graful este dat prin fişierul denumit în program „grafponderat.in”
cu structura:
- pe prima linie n şi m cu semnificaŃiile utilizate şi până acum;
- pe următoarele m linii câte trei valori i j c cu semnificaŃia: arc
de la i la j cu costul c;
//programu 3.2 algoritmul Dijsktra
#include <iostream>
#include <fstream>
#define nmax 101

179
using namespace std;
int h[nmax][nmax],T[nmax],d[nmax];
bool v[nmax];
int s,i,j,k,p,n,m;
int oo = 1000000;
ifstream fin("grafponderat.in");
void afisaretab(int a[], int n)
{for (i=1;i<=n;i++)
{ cout.width(6); cout << a[i];}
cout << endl;
}
int main()
{ fin >> n >> m;
for (i=1;i<=n;i++)
for (j=1;j<=n;j++)
if (i==j)h[i][j] = 0;
else h[i][j] =oo;
for (i=1;i<=m;i++)
{ int x,y,cost;
fin >> x >> y >> cost;
h[x][y] = cost;
}
for (i=1;i<=n;i++)
{ for (j=1;j<=n;j++)
{cout.width(4);
if (h[i][j]==oo) cout <<"oo";
else cout << h[i][j];
}
cout << endl;
}
cout << endl;
fill(v,v+n+1,false);
fill(d,d+n+1,oo);
fill(T,T+n+1,-1);
cout << "sursa = "; cin >> s;
d[s]=0; T[s]=0;
for (k=1;k<=n-1;k++)
{ int minim = oo;
for (i=1;i<=n;i++)
if (d[i] <= minim && v[i]==false)
minim=d[i],p=i;
v[p] = true;
for (i=1;i<=n;i++)
if (d[i] > d[p] + h[p][i])
{ d[i] = d[p] + h[p][i];
180
T[i] = p;
}
}
cout << "tabloul d:"<<endl;
afisaretab(d,n);
cout << "tabloul T:"<<endl;
afisaretab(T,n);
return 0;
}
Pentru digraful din figura 3.3 programul va afişa în fereastra consolă
următoarele:
0 4 6 9 oo oo
oo 0 9 oo oo 9
oo oo 0 1 1 7
8 oo 2 0 3 oo
oo oo 7 oo 0 2
oo oo 3 oo oo 0
sursa = 1
tabloul d:
0 4 6 7 7 9
tabloul T:
0 1 1 3 3 5

3.5.3 Algoritmul Roy-Floyd

Cunoscut şi ca algoritmul Floyd – Warshall, a apărut la sfârşitul


anilor 50 şi începutul anilor 60 ca rezultat al studiilor efectuate de
Robert Floyd, Bernard Roy şi Stephen Warshall.
El se aplică pentru a determina drumurile de cost minim între
oricare două vârfuri ale unui graf. Algoritmul determină două matrice D
şi T în care elementele au următoarea semnificaŃie:
Di,j = costul minim al unui drum de la vârful i la
vârful j
Ti,j = predecesorul vârfului j pe drumul de cost
minim de la vârful i la vârful j

181
Algoritmul este următorul:

1. construieşte matricea ponderilor H


2. D  H
3. pentru i  1,n execută
pentru j  1,n execută
dacă exista arcul(i,j) atunci
T[i][j]  i
sfârşitdacă
sfârşitpentru
sfârşitpentru
4. pentru k  1,n execută
pentru i  1,n execută
pentru j  1,n execută
dacă D[i][j] > D[i][k]+ D[k][j] atunci
//*
D[i][j]  D[i][k]+ D[k][j]
T[i][j]  T[k][j]
sfârşitdacă
sfârşitpentru
sfârşitpentru
sfârşitpentru

Se poate observa că testul din instrucŃiunea dacă


marcată cu * are şanse să fie îndeplinit numai dacă cele trei valori i,j
şi k sunt diferite. Totuşi introducerea acestui test suplimentar nu
micşorează complexitatea algoritmului. Aceasta este O(n3).
Implementarea algoritmului este dată în programul 3.3. FuncŃia
retinedrum determină vârfurile care alcătuiesc drumul de cost minim
între vârfurile date ca parametri: v1 sursa şi v2 destinaŃia. Principiul ei
este unul „clasic”:

1 iniŃializează vârful curent cu vârful


destinaŃie
2 cât timp vârful curent ≠ 0
a. pune vârful curent pe stivă
b. vârful curent  predecesorul vârfului
curent
Utilizăm o stivă pentru a reŃine vârfurile; astfel la ieşirea din funcŃie
putem afişa vârfurile în ordinea inversă faŃă de cum le-a parcurs
algoritmul, deci în ordinea lor firească (de la sursă la destinaŃie).

182
//program 3.3 Algoritmul Roy-Floyd
#include <iostream>
#include <fstream>
#include <stack>
#define nmax 101
using namespace std;
int h[nmax][nmax],T[nmax][nmax],D[nmax][nmax];
int oo = 100000;
void afisardrum(int v1, int v2, stack<int>&st)
{ while (v2 != 0)
{ st.push(v2);
v2 = T[v1][v2];
}
}
void afisarematrice(int a[nmax][nmax], int n)
{for (int i=1;i<=n;i++)
{ for (int j=1;j<=n;j++)
{cout.width(4);
if (a[i][j]==oo) cout <<"oo";
else cout << a[i][j];
}
cout << endl;
}
}
int main()
{ int s,i,j,k,p,n,m;
ifstream fin("ponderi.in");
stack <int> st;
fin >> n >> m;
for (i=1;i<=n;i++)
for (j=1;j<=n;j++)
if (i==j)h[i][j] = 0;
else h[i][j] =oo;
for (i=1;i<=m;i++)
{ int x,y,cost;
fin >> x >> y >> cost;
h[x][y] = cost;
}
//afisarematrice(h,n);
cout << endl;
for (i=1;i<=n;i++)
for (j=1;j<=n;j++)
{ D[i][j] = h[i][j];
if (D[i][j] != oo && i!=j)
T[i][j] = i;
183
}
for (k=1;k<=n;k++)
for (i=1;i<=n;i++)
for (j=1;j<=n;j++)
if (D[i][j] > D[i][k] + D[k][j])
{ D[i][j] = D[i][k] + D[k][j];
T[i][j] = T[k][j];
}
cout << "Matricea D"<<endl;
afisarematrice(D,n);
cout << "Matricea T"<<endl;
afisarematrice(T,n);
cout << "\nDrumul de la varful 5 la varful 1\n";
afisardrum(5,1,st);
while (!st.empty())
{ cout << st.top() << ' ';
st.pop();
}
return 0;
}

Pentru digraful din figura 3.3 programul va afişa în fereastra consolă


următoarele:
Matricea D
0 4 6 7 7 9
17 0 9 9 10 12
9 13 0 1 1 3
8 12 2 0 3 5
14 18 5 6 0 2
12 16 3 4 4 0
Matricea T
0 1 1 3 3 5
4 0 2 2 3 5
4 1 0 3 3 5
4 1 4 0 4 5
4 1 6 3 0 5
4 1 6 3 3 0
Drumul de la varful 5 la varful 1
5 6 3 4 1

Un algoritm asemănător permite determinarea închiderii


tranzitive a unui graf orientat. Închiderea tranzitivă a unui graf G =
(V,E) este un graf G*=(V,E*), unde mulŃimea arcelor E* va conŃine
arcul (i,j) numai dacă în graful G există drum de la vârful i la vârful

184
j. Pentru aceasta se atribuie fiecărui arc din G costul 1 şi în urma
aplicării algoritmului Roy-Floyd se foloseşte matricea D astfel:
dacă D[i][j]<n atunci
există drum de la vârful i la vârful j.

185
3.6 Flux maxim într-o reŃea de transport

3.6.1 DefiniŃii

Fie un graf orientat G = (V,E).


O reŃea de transport este un graf orientat care îndeplineşte următoarele
condiŃii:
• există un singur vârf denumit vârf sursă pe care îl vom cu srs
care are d+(srs)>0 şi d-(srs)=0
• există un singur vârf denumit vârf destinaŃie pe care îl vom cu
dst care are d+(dst)=0 şi d-(dst)>0
• fiecărui arc (i,j) îi este asociată o valoare nenegativă
denumită capacitate pe care o vom nota c(i,j)
• fiecare vârf al grafului diferit de srs şi dst se găseşte pe un
drum care leagă vârful srs de vârful dst
Figura 3.6 reprezintă un graf orientat de tip reŃea de transport.

2 7 3
5 4
3 3 2
1 4 5 9
3 5 9
6 4 6
7 8

fig.3.6: reŃea de transport

Un astfel de graf este adecvat pentru a modela situaŃii din lumea


reală: sursa poate fi o unitate de producŃie iar destinaŃia un loc de
desfacere a produselor fabricate în acea unitate de producŃie.
Drumurile care apar între sursă şi destinaŃie corespund posibilităŃii de
transport a produselor. Vârfurile intermediare ale grafului pot
reprezenta depozite temporare pentru produse iar capacitatea asociată
unui arc poate reprezenta valoarea maximă care poate fi transportată
pe porŃiunea respectivă de drum. Printr-un astfel de graf se pot modela
şi siutaŃii în care avem mai multe unităŃi de producŃie şi mai multe
puncte de desfacere. În acest caz, se adaugă un vârf sursă fictiv de la
care pleacă câte un arc spre fiecare vârf sursă propriu-zis; capacitatea
fiecăruia dintre aceste arce va fi egală cu suma capacităŃilor arcelor
care pleacă din sursa la care este incident. Analog, dacă sunt mai
186
multe destinaŃii, se poate adăuga o un vârf destinaŃie fictiv iar de la
fiecare destinaŃie propriu-zisă se adaugă un arc spre această destinaŃie
fictivă. Capacitatea unui astfel de arc care leagă una dintre destinaŃiile
iniŃiale cu destinaŃia fictivă va fi egală cu suma capacităŃilor de la arcele
care intră în destinaŃie iniŃială.

Un flux într-o reŃea de transport este o funcŃie φ:V×V  R care


îndeplineşte următoarele condiŃii:
• 0 ≤ φ(i,j) ≤c(i,j) (fluxul asociat unui arc nu poate depăşi
capacitatea arcului respectiv;
• ∑ϕ (i, j ) = ∑ϕ ( j, i ) (această condiŃie arată că suma fluxurilor
j∈V j∈V

asociate arcelor care „intră” într-un vârf oarecare i este egală


cu suma fluxurilor asociate arcelor care „ies” din vârful i);

Se defineşte valoarea fluxului dintr-o reŃea de transport ca fiind

Γ = ∑ϕ ( srs, j )
j∈V

adică suma fluxurilor asociate arcelor care pleacă din vârful sursă.
Evident această valoare va fi egală cu suma fluxurilor asociate arcelor
care intră în vârful destinaŃie.
Figura 3.7 ilustrează un flux pentru reŃeaua de transport din fig. 3.6.
Valorile fluxului ataşat fiecărui arc sunt scrise în chenar. Valoarea
fluxului din reŃeaua ilustrată în această figură este 6.

2 7 3
5 1
1 4 1
2 3 2
1 4 5 9
4 1 2
3 1 5 3
4 6 4
6 7 8
1 4
fig.3.7: reŃea de transport

3.6.2 Determinarea fluxului maxim într-o reŃea de trasport

În cele ce urmează vom prezenta algoritmul Ford-Fulkerson [5] pentru


a determina fluxul maxim care poate fi asociat unei reŃele de transport.
Pentru descrierea acestui algoritm vom defini câteva noŃiuni ajutătoare:
187
• drum într-o reŃea de transport este un lanŃ elementar între nodul
sursă şi cel destinaŃie (adică arcele pot fi parcurse atât în sens
direct cât şi în sens invers);
• valoarea reziduală a unui arc este o funcŃie care asociază
fiecărui arc din graf o valoare calculată după formula:

c(i, j ) − ϕ (i, j ) daca arcul (i, j) e parcurs in sens direct


vr (i, j ) = 
ϕ (i, j) daca arcul (i, j) e parcurs in sens invers

• drum în creştere este un drum în reŃeaua de transport în care


valoarea reziduală a fiecărui arc este strict pozitivă.
• capacitatea reziduală a unui drum în creştere notată ε, este
minimul valorilor reziduale ale arcelor care alcătuiesc acel drum.

Cu aceste definiŃii, algoritmul Ford-Fulkerson are următoarea descriere:

1. se asociază reŃelei de transport un flux de


valoare nulă
2. se caută un drum în creştere
3. dacă acesta există şi are capacitatea
reziduală ε atunci
a) se modifică fluxul pe arcele care
alcătuiesc drumul în creştere determinat
astfel:
i. dacă arcul (i,j) este parcurs în sens
direct atunci
φ(i,j) = φ(i,j) + ε
ii. dacă arcul (i,j) este parcurs în sens
invers atunci
φ(i,j) = φ(i,j) – ε
b) se reia pasul 2

 altfel (când nu mai există drum în


creştere) algoritmul se opreşte iar
fluxum maxim este valoarea fluxului
din acest moment (egal cu suma
fluxurilor asociate arcelor care
pleacă din vârful sursă)

Complexitatea acestui algoritm este O(m×F), unde prin F am notat


valoarea maximă a fluxului. Edmonds–Karp [3] au demonstrat că timpul
de rulare a algoritmului este mai scurt şi nu mai depinde de valoarea

188
fluxului dacă se caută la fiecare iteraŃie drumul de lungime minimă.
Pentru aceasta, drumurile în creştere se vor determina printr-un
algoritm foarte asemănător cu cel de traversare în lăŃime. Deosebirea
faŃă de cel prezentat în capitolele anterioare este că în acest caz se va
putea parcurge un arc şi în sens invers dacă fluxul asociat lui la
momentul efectuării parcurgerii este strict pozitiv. Complexitatea
algoritmului în acest caz este O(n×m2). O complexitate și mai bună
este dată de algoritmul de preflux [8] care a condus la algoritmul de flux
maxim prezentat de Goldberg şi Tarjan cu complexitatea O(n2×m).
Să ilustrăm aplicarea algoritmului Edmonds–Karp pentru reŃeaua
de transport din figura 3.6:

2 7 3
5 0
0 4 0
2 3 2
1 4 5 9
0 0 0
3 0 5 0
9 0
6 4 6
7 8
0 0

fig.3.8.a: flux iniŃial nul


Drumul în creştere de lungime minimă pentru această configuraŃie este
1,4,3,9. Valorile reziduale ale arcelor care îl compun sunt: (1,4):2, (4,5):3,
(5,9):2 => valoarea reziduală a drumului în creştere este 2.

2 7 3
5 0
0 4 0
2 3 2
1 4 5 9
2 2 2
3 0 5 0
9 0
6 4 6
7 8
0 0
fig.3.8.b: flux actualizat după determinarea primului drum în creştere.
Drumul în creştere de lungime minimă pentru această configuraŃie este
1,6,7,8,9. (ObservaŃie: Drumul 1,2,3,5,9 nu este în creştere deoarece arcul
(5,9) este „saturat”: valoarea fluxului este egală cu valoarea capacităŃii).
Valorile reziduale ale arcelor care îl compun sunt: (1,6):3, (6,7):4, (7,8):6,
(8,9):9 => valoarea reziduală a drumului în creştere este 3.

189
2 7 3
5 0
0 4 0
2 3 2
1 4 5 9
2 2 2
3 3 5 0
9 3
6 2 6
7 8
3 3
fig.3.8.c: fluxul după determinarea celui de-al doilea drum în creştere.
Drumul în creştere de lungime minimă pentru această configuraŃie este
1,2,3,5,4,7,8,9. (ObservaŃie: arcul 5,4 este parcurs în sens invers!). Valorile
reziduale ale arcelor care îl compun sunt: (1,2):5, (2,3):7, (3,5):4, (5,4):2,
(4,7):5, (7,8):3, (8,9):6 => valoarea reziduală a drumului în creştere este 2.

2 7 3
5 2
2 4 2
2 3 2
1 4 5 9
2 0 2
3 3 5 2
9 5
6 2 6
7 8
3 5
fig.3.8.d: fluxul după determinarea celui de-al treilea drum în creştere. Nu
mai există drumuri în creştere, deci fluxul maxim din reŃea este 7.

Programul 3.4 implementează algoritmul Ford-Folkerson.

//program 3.4 Flux maxim


#include <fstream>
#include <iostream>
#include <queue>
using namespace std;
int a[101][101], c[101][101], f[101][101];
int viz[101],T[101];
int n,m,lung,minim;
ifstream fin("retea.in");
struct arc{int n1,n2;};
arc drum[101];
int bf()
{
int i,j;
queue <int> q;
fill(viz,viz+101,0);fill(T,T+101,0);
q.push(1);
190
viz[1] = 1;
while (q.empty() != 1)
{ i = q.front();
if (i == n)
return 1;
q.pop();
for (j = 1;j<=n;j++)
{ if (viz[j] == 0 && c[i][j] - f[i][j] > 0)
{//arc parcurs in sens direct
viz[j] = 1;
T[j] = i;
q.push(j);
}
if (viz[j] == 0 && f[j][i] > 0)
{//arc parcurs in sens invers
viz[j] = 1;
T[j] = i;
q.push(j);
}
}
}
return 0;
}
void calcdrum()
{
int i=0,b=n,a;
minim = 10000000;
a = T[b];
while (a!=0)
{
drum[i].n1 = a;
drum[i].n2 = b;
if (c[a][b] > f[a][b])
{if (minim > c[a][b] - f[a][b])
minim = c[a][b] - f[a][b];
}
else
if (minim > f[b][a])
minim = f[b][a];
i++;
b = a;
a = T[b];
}
lung = i;
}
191
int main()
{
int i,j,k,ok=1;
fin >> n >> m;
for (i=0;i<m;i++)
{fin >> i >> j;
fin >> c[i][j];
}
while (ok == 1)
{
ok=bf(); //returneaza 1 daca gaseste un drum
if (ok == 1)
{ calcdrum(); //calculeaza si minimul
cout<<"\n\ngasit un drum de lungime "<< lung;
cout<<" capacitate reziduala "<< minim << endl;
cout << "format din arcele: ";
for (k=0;k<lung;k++)
{ i = drum[k].n1;
j = drum[k].n2;
cout<< i << '-' << j << " , ";
if (c[i][j] - f[i][j] > 0)
f[i][j] += minim;
else
f[j][i] -= minim;
}
}
}
int fluxmax = 0;
for (i=1;i<=n;i++)
fluxmax += f[1][i];
cout<<"\n\nflux maxim= "<<fluxmax<<endl;
cout<<"starea fiecarui arc:capacitate/flux\n";
for (i=1;i<=n;i++)
for (j=1;j<=n;j++)
if (c[i][j]>0)
{ cout<<'('<<i<<'-'<<j<<"):";
cout<<c[i][j]<<'/'<<f[i][j]<<endl;
}
return 0;
}

Pentru reŃeaua de transport din figura 3.6 programul va afişa în


fereastra consolă următoarele:

192
gasit un drum de lungime 3 capacitate reziduala 2
format din arcele: 5-9 , 4-5 , 1-4 ,

gasit un drum de lungime 4 capacitate reziduala 3


format din arcele: 8-9 , 7-8 , 6-7 , 1-6 ,

gasit un drum de lungime 7 capacitate reziduala 2


format din arcele: 8-9 , 7-8 , 4-7 , 5-4 , 3-5 , 2-3
, 1-2 ,
flux maxim= 7
starea fiecarui arc:capacitate/flux
(1-2):5/2
(1-4):2/2
(1-6):3/3
(2-3):7/2
(3-5):4/2
(4-5):3/0
(4-7):3/2
(5-9):2/2
(6-7):4/3
(7-8):6/5
(8-9):9/5

193
3.7 Sortarea topologică

Sortarea topologică a vârfurilor unui digraf reprezintă o ordonare


liniară acestor vârfuri astfel încât dacă există arcul (i,j), atunci în
structura de date care rezultă prin această sortare, vârful i va apare
înaintea vârfului j. Sortarea topologică se poate face numai dacă
digraful este acilcic. Această sortare poate fi văzută ca o aranjare a
vârfurilor digrafului pe o axă orizontală astfel încât toate arcele din
respectivul digraf sunt orientate de la stânga la dreapta. Ordinea în
care vor apare vârfurile digrafului în structura liniară nu este unică.
Digrafurile aciclice sunt utilizate în numeroase aplicații pentru a
preciza o ordine de desfășurare a unor evenimente. Pentru a ilustra
această afirmație vom folosi exemplul dat de Cormen et al în [2]: este
vorba de ordinea în care cineva se îmbracă dimineața. În figura 3.9
diverselor articole de îmbrăcăminte implicate în această acțiune le
corespund vârfurile digrafului iar arcele care apar în digraf indică
ordinea firească în care sunt îmbrăcate aceste articole (de exemplu
pantalonii înaintea curelei!). O sortare topologică a acestui digraf ne va
da o ordine pentru îmbrăcare. Algoritmul de sortare topologică are la
bază traversarea în adâncime a digrafului cu determinarea timpilor la
care fiecare vârf a fost atins şi ulterior terminat (algoritmul implementat
în programul 2.3, adaptat pentru digrafuri). Ceea ce trebuie făcut
suplimentar faŃă de algoritmul 2.3 este ca atunci când un vârf este
marcat ca terminat (şi deci colorat în negru) el să fie inserat în prima
poziŃie a structurii liniare care va rezulta prin acest tip de sortare.
Complexitatea algoritmului este cea a traversării grafului în adâncime.
Programul 3.5 implementează algoritmul de sortare topologică.
Structura liniară care rezultă din acest algoritm este o listă dublu
înlănŃuită implementată pe baza containerul list. Pentru o afişare
corespunzătoare exemplului de mai sus şi ilustrată prin figura 3.9 am
declarat un tablou de string-uri în care numărul de ordine al fiecărui
vârf (ales într-un mod arbitrar) este pus în corespondenŃă cu numele
articolului din respectivul vârf. Aşa cum scriam şi în paragraful anterior,
programul va afişa o ordine pentru îmbrăcare.

194
lenjerie 7 ciorapi 5
15/16 9/12
ceas 8
17/18
pantaloni 6 pantofi 9
13/14 10/11

curea 3 cămaşă 2
6/7 5/8

cravată 1
1/4

sacou 4
2/3

fig.3.9: digraf aciclic ilustrând ordinea în care trebuie desfăşurate anumite


activităŃi (i.e. îmbrăcate anumite articole). Numerele din chenare
reprezintă o numerotare arbitrară a vârfurilor din digraf. Cele două
numere de sub denumirea fiecărui articol reprezintă timpii când vârful a
fost atins şi respectiv terminat în algoritmul de parcurgere în adâncime.

//program 3.5: sortare topologica


#include <iostream>
#include <fstream>
#include <algorithm>
#include <queue>
#include <list>
#include <string>
#define nmax 101
#define alb 0
#define gri 1
#define negru 2
using namespace std;

int culoare[nmax];
int T[nmax]; //pentru memorarea predecesorilor
int tg[nmax], tn[nmax], timp;
int n,m;
deque <int> LA[nmax];
list <string> listatopo;
195
string numeactiune[nmax] =
{"","cravata","camasa","curea","sacou","ciorapi",
"pantaloni", "lenjerie","ceas","pantofi"
};
void citirelistead(deque <int> LA[], int &n, int &m,
char numefisier[])
{ ifstream fin (numefisier);
fin >> n >> m;
for (int i=1;i<=m;i++)
{
int x,y;
fin >> x >> y;
LA[x].push_back(y);
}
for (int i=1;i<=n;i++)
sort(LA[i].begin(), LA[i].end());
fin.close();
}

void dfs(int x, list<string> &lista)


{ culoare[x] = gri;
timp++;
tg[x] = timp;
for (int i=0;i<LA[x].size();i++)
{
int vecin = LA[x][i];
if (culoare[vecin]==alb)
{
T[vecin]=x;
dfs(vecin,lista);
}
}
timp++;
tn[x] = timp;
culoare[x] = negru;
listatopo.push_front(numeactiune[x]);
}

void afisarelist(list <string> lst)


{
list<string>::iterator it;
for (it=lst.begin();it != lst.end();it++)
cout << *it << " ";
}

196
int main()
{
citirelistead(LA,n,m,"digraf.in");
fill(culoare, culoare+n+1, alb);
timp = 0;
for (int x=1;x<=n;x++) /*1*/
if (culoare[x] == alb)
{
dfs(x,listatopo);
}
cout << "nodurile sortate topologic:\n";
afisarelist(listatopo);
return 0;
}

Pentru digraful aciclic din figura 3.9 programul va afişa în fereastra


consolă următoarele:
nodurile sortate topologic:
ceas lenjerie pantaloni ciorapi pantofi camasa curea
cravata sacou

Dacă inversăm ordinea de parcurgere a vârfurilor din funcŃia main(),


adică scriem instrucŃiunea notată /*1*/ în formatul
for (int x=n;x>=1;x--)

rezultatul obŃinut în fereastra consolă va fi:

nodurile sortate topologic:


camasa cravata ciorapi lenjerie pantaloni pantofi
curea sacou ceas

Aceasta reprezintă de asemenea o sortare topologică corectă.

197
3.8 Probleme propuse

Problema 1. Un graf orientat are următoarea matrice de adiacenŃă:


0 1 1 1 0 1
1 0 0 0 1 1
1 1 0 1 1 1
1 1 0 0 0 0
1 1 0 1 0 1
0 0 0 0 0 0
DeterminaŃi componentele tare conexe ale acestui graf.

Problema 2. Matricea ponderilor asociata unui graf orientat cu 9


vârfuri este următoarea:

0 ∞ ∞ ∞ ∞ 1 5 12 ∞
11 0 ∞ 13 7 ∞ 4 2 ∞
1 ∞ 0 9 8 3 ∞ ∞ ∞
6 8 ∞ 0 ∞ ∞ ∞ 8 2
13 2 ∞ ∞ 0 8 ∞ 13 ∞
13 12 ∞ 10 ∞ 0 ∞ ∞ 3
∞ 8 ∞ ∞ ∞ 8 0 ∞ 3
9 4 1 ∞ ∞ 6 ∞ 0 9
∞ 8 ∞ ∞ 3 ∞ ∞ ∞ 0

AplicaŃi algoritmul Dijsktra de la curs având ca vârf sursă vârful 5 din


acest graf şi precizaŃi conŃinutul tablourilor notate cu d şi T în algoritmul
descris la subcapitolul 3.3.2.

Problema 3. Se presupune că avem un graf orientat cu n vârfuri în


care muchiile au costuri strict pozitive şi că acestui graf i s-a aplicat
algoritmul Dijsktra în forma subcapitolul 3.3.2. rezultând tablourile d şi
T. ScrieŃi o funcŃie parametri T şi x unde T are semnificaŃia de mai sus
iar x este un număr natural, 1≤x≤n care returnează lungimea
drumului de cost minim de la vârful sursa la vârful x. Se reaminteşte că
lungimea unui drum este egală cu numărul de vârfuri prin care acesta
trece.

198
Problema 4. ConcepeŃi un algoritm pentru determinarea drumurilor de
cost minim care leagă un unic vârf sursă cu toate celelalte vârfuri din
digraf mai eficient decât algoritmul Dijsktra.
(IndicaŃie: sortaŃi topologic vârfurile grafului, apoi parcurgeŃi structura
liniară rezultată şi aplicaŃi operaŃiunea de relaxare fiecărui vârf spre
care pleacă câte un arc din vârful curent.)

Problema 5. În urma aplicării algoritmului Roy-Floyd asupra unui graf


orientat cu 9 vârfuri a fost obŃinută următoarea matrice a predecesorilor
(matricea T din algoritmul de la subcapitolul 3.3.3):

0 5 8 6 9 1 1 2 6
3 0 8 2 2 1 2 2 7
3 5 0 6 3 1 1 2 6
4 5 8 0 9 1 1 4 4
3 5 8 2 0 1 2 2 7
3 5 8 6 9 0 2 2 6
3 5 8 6 9 7 0 2 7
3 8 8 6 3 1 1 0 6
3 5 8 2 9 1 2 2 0

PrecizaŃi arcele care alcătuiesc următoarele drumuri de cost minim:


• de la varful 3 la varful 5
• de la varful 2 la varful 1
• de la varful 1 la varful 8

Problema 6. Cuplaj maxim într-un graf bipartit


Presupunem că este dat un graf bipartit, cu mulŃimea nodurilor V
şi mulŃimea muchiilor notată E. Se numeşte cuplaj într-un graf bipartit o
mulŃime C inclusă în E, care are proprietatea că oricare două muchii
din această mulŃime NU sunt incidente. Un cuplaj este maxim dacă are
un număr maximal de muchii (orice muchie am încerca să adăugăm în
acea mulŃime, această nouă muchie va fi incidentă cu cel puŃin una
dintre muchiile deja existente).
Fişierul graf.in conŃine:
- pe prima linie două numere natural n şi m, 3≤n≤100, 2≤m≤10000. n
şi m reprezintă numărul de noduri şi respectiv numărul de muchii
dintr-un graf bipartit;

199
- pe următoarele m linii câte două numere v1 şi v2 1≤v1,v2≤n,
reprezentând câte o muchie a grafului;
DeterminaŃi un cuplaj maxim în acest graf.
(IndicaŃie: transformaŃi graful într-o reŃea de transport în care muchiile
vor fi orientate de la una dintre partiŃii la cealaltă partiŃie – vezi
observaŃiile finale de la subcapitolul 3.4.1. Fiecare muchie
(transformată deci într-un arc) va avea capacitate egală cu 1. CalculaŃi
fluxul maxim din această reŃea iar valoarea acestuia va reprezenta
numărul de muchii din cuplajul maxim).

200

S-ar putea să vă placă și