Sunteți pe pagina 1din 24

Capitolul 5.

ALGORITMI AVANSAȚI PE GRAFURI PENTRU


GRUPELE DE EXCELENȚĂ

5.1. Puncte de articulație – critice

Proprietățile de conexitate a grafurilor formează o parte importantă în teoria grafurilor.


Grafurile conexe pot fi utilizate pentru a descrie o mulțime de sisteme care apar în viața reală.

De exemplu, dacă într-o rețea de calculatoare se defectează server-ul principal, atunci


este posibil ca rețeaua să nu mai funcționeze corect. Este foarte posibil să existe perechi de
calculatoare care nu mai pot comunica. În termenii teoriei grafurilor, graful care modelează
rețeaua și-a pierdut conexitatea.

Modalitatea prin care graful și-a pierdut conexitatea îl reprezintă eliminarea unui nod și
a tuturor muchiilor adiacente cu acesta.

Nodurile unui graf a căror eliminare duc la dispariția conexității se numesc noduri sau
puncte de articulație (puncte critice sau noduri critice).

Exemplu: pentru graful din figura de mai jos, nodul 2 este un exemplu de punct de
articulație, deoarece prin eliminarea lui, graful îți pierde proprietatea de conexitate.

Figura 5.1.1

În cazul în care graful nu este conex, punctele de articulație pot fi definite ca fiind acele noduri
a căror eliminare duce la creșterea numărului de componente conexe ale grafului.
Sarcină de lucru: Fie un graf neorientat conex G=(X,U). Să se determine mulțimea
punctelor de articulație din graf.

Studiu preliminar:

Varianta 1: Cea mai simplă modalitate de determinare a punctelor de articulație dintr-un graf
conex este eliminarea, pe rând, a câte unui nod, împreună cu muchiile adicente, și verificarea
conexității grafului rămas după eliminarea nodului respectiv. Ordinul de complexitate al
operației de verificare a conexității este O(m). Așadar, ordinul de complexitate al acestui
algoritm de determinare a punctelor de articulație este O(m*n).

pentru i=1,n execută


dacă conex(G\{i})=false atunci
scrie i
sfârșit dacă
sfârșit pentru

Varianta 2: Un algoritm eficient pentru determinarea punctelor de articulație poate fi realizat


printr-un algoritm cu ordinul de complexitate O(m+n). Algoritmul se bazează pe o parcurgere
DF a grafurilor în care se rețin mai multe informații despre fievare nod, acestea conducând în
final, la identificarea punctelor de articulație.

În arborele obținut prin parcurgerea DF nu pot exista muchii de traversare, ci doar muchii de
înaintare și de întoarcere. Astfel, rădăcina arborelui DF va fi punct de articulație dacă și numai
dacă care cel puțin doi descendenți. Prin eliminarea rădăcinii se va rupe conexitatea grafului
deoarece nu există muchii care să treacă de la un subarbore al rădăcinii la altul.

Un nod i al grafului, diferit de rădăcină, poate fi punct de articulație dacă și numai dacă are cel
puțin un fiu j cu proprietatea că nu există nici un nod în arborele de rădăcină j care să fie legat
printr-o muchie de întoarcere de un predecesor al nodului i.

Prezentarea algoritmului eficient: pentru fiecare nod x Є X în parte, se va păstra:


- nivelul pe care acesta se află în parcurgerea DF, memorat în vectorul niv pe poziția x
(niv[x]), pentru a verifica dacă dintr-un anumit nod se poate ajunge deasupra unui alt
nod
- nodul părinte în arborele DF, reținut în vectorul t pe poziția x (t[x]), datorită faptului că
în cadrul parcurgerii DF, pentru fiecare nod va trebui să luăm în considerare toate
muchiile care pornesc din nodul respectiv, cu excepția celei care îl unește de părintele
său
- nivelul minim care poate fi atins din x folosind descendenții săi și cel mult o muchie de
întoarcere. Acest număr va fi reținut în vectorul Min pe poziția x (Min[x]).

Dacă dintr-un nod x nu se poate ajunge pe un nivel strict mai mic decât al tatălui său din
arborele DF (niv[t[x]]≤Min[x]), atunci t[x] este punct de articulație; eliminarea acestuia,
împreună cu muchiile adiacente, ar duce la izolarea nodului x.

Practic: pentru graful din figura de mai jos, există două puncte de articulație: nodul 3 și nodul
5.

Figura 5.1.2
Arborele DF al acestuia, cu rădăcina în nodul 1 are patru nivele:

t=(0, 5, 1, 6, 3, 3)
niv=(1, 4, 2, 4, 3, 3)
Min=(1, 4, 1, 2, 1, 2)

Figura 5.1.3
Min[3]=1 deoarece nivelurile minime atinse de descendenții săi sunt:

- nivelul 1 pentru nodul 5


- nivelul 2 pentru nodurile 4 și 6
- nivelul 4 pentru nodul 2

Nivelul minim atins din nodul 3 prin intermediul descendenților săi și al unei muchii de
întoarcere este nivelul 1 (Min[3]=1) și cum pentru nodul 3 există descendentul direct nodul 6,
care nu poate atinge un nivel mai mic decât cel pe care este situat el, rezultă că nodul 3 este
punct de articulație. În mod asemănător se procedează și pentru nodul 5.

Implementarea în limbajul Pascal a programului care determină mulțimea punctelor critice

Considerăm următoarele declarații globale:

type plista=^lista;
lista=record
nod:integer;
urm:plista;
end;
var G=array[1..1000] of plista;
T,niv,Min:array[1..1000] of integer;
n,m,i,nr,rad:integer;
c,viz:array[1..1000] of 0..1;
în care:

- graful G este memorat cu ajutorul listelor de adiacență


- vectorul c reține pentru fiecare nod, valoarea 0 dacă nodul este critic și 1 în caz contrar
- vectorul viz, precizează starea unui nod în timpul parcurgerii: vizitat sau nevizitat
- variabila nr determină numărul de descendenți ai nodului considerat rădăcină (rad) în
parcurgerea DF

type plista=^lista;
lista=record
nod:integer;
urm:plista;
end;
var G:array[1..1000] of plista;
T,niv,Min:array[1..1000] of integer;
n,m,i,nr,rad:integer;
c,viz:array[1..1000] of byte;
procedure DF(nod:integer);
var p:plista;
begin
viz[nod]:=1;
Min[nod]:=niv[nod];
p:=G[nod];
while p<>nil do begin
if viz[p^.nod]=0 then begin
niv[p^.nod]:=niv[nod]+1;
T[p^.nod]:=nod;
if nod=rad then inc(nr);
DF(p^.nod);
if Min[nod]>Min[p^.nod] then Min[nod]:=Min[p^.nod];
if Min[p^.nod]>=niv[nod] then c[nod]:=1;
end
else
if (p^.nod<>T[nod])and(Min[nod]>niv[p^.nod]) then
Min[nod]:=niv[p^.nod];
p:=p^.urm
end;
end;

procedure puncte_critice;
begin
for i:=1 to n do
if viz[i]=0 then
begin
niv[i]:=1;
rad:=i;
nr:=0;
DF(i);
if nr>0 then c[rad]:=1
else c[rad]:=0;
end;
for i:=1 to n do
if c[i]<>0 then write(i,' ');
end;

procedure adauga(i,j:integer);
var p:plista;
begin
new(p); p^.nod:=j; p^.urm:=G[i]; G[i]:=p;
end;

procedure citeste_graf;
var i,j,k:longint;
f:text;
begin
assign(f,'graf.in'); reset(f);
readln(f,n,m);
for k:=1 to m do begin
readln(f,i,j);
adauga(i,j); adauga(j,i);
end;
end;

begin
citeste_graf;
puncte_critice
end.

5.2. Muchii critice – punți

În secțiunea 5.1. am arătat faptul că un graf își poate pierde conexitatea dacă este
eliminat un nod, împreună cu toate muchiile adiacente. Există cazuri în care, conexitatea este
pierdută, chiar dacă este eliminată o singură muchie.

Muchiile unui graf a căror eliminare duc la pierderea conexității poartă denumirea de
punți sau muchii critice.

Practic: graful din figura de mai jos conține o muchie critică între nodurile 2 și 5.
Figura 5.2.1

Sarcină de lucru: Pentru un graf neorientat conex G=(X,U), să se determine mulțimea


muchiilor critice.

Studiu preliminar:

Varianta 1: la fel ca și în cazul punctelor de articulație, cea mai simplă modalitate de


determinare a punților dintr-un graf conex este eliminarea, pe rând, a câte unei muchii, fără a
elimina niciunul din nodurile adiacente, și verificarea conexității grafului rămas după
eliminarea muchiei respective.

pentru i=1,m execută


dacă conex(G\[i,j])=false atunci
scrie i,’ ’,j
sfârșit dacă
sfârșit pentru
Ordinul de complexitate al acestui algoritm este O(m2), deoarece pentru fiecare muchie
eliminată se verifică dacă graful rămas este sau nu conex, iar ordinul de complexitate al
operației de verificare a conexității este O(m).

Varianta 2: este un algoritm cu ordinul de complexitate O(m+n). Pentru a determina muchiile


critice se folosește tot o parcurgere DF modificată, pornind de la următoarea observație:
muchiile critice sunt muchii care nu apar în niciun ciclu. Prin urmare, o muchie de
întoarcere nu poate fi critică, deoarece o astfel de muchie închide întotdeauna un ciclu, iar
eliminarea lor nu strică proprietatea de conexitate. Așadar, doar muchiile de avansare ale unui
graf pot fi critice. Cum un graf conex conține exact n-1 muchii de avansare, întotdeauna vom
avea cel mult n-1 muchii critice. Pentru fiecare astfel de muchie va trebui să verificăm dacă
face sau nu parte dintr-un ciclu. Să presupunem că verificăm dacă o muchie de avansare [i,j]
este sau nu punte în graf. Muchia va fi critică dacă și numai dacă nu există nicio muchie de
întoarcere care leagă un nod i al arborelui sau un predecesor al acestuia de rădăcina j.

Prezentarea algoritmului eficient: pentru un nod x Є X se va memora:

- numărul nivelului pe care acesta se află în parcurgerea DF, memorat în vectorul niv pe
poziția x (niv[x])
- nodul părinte în arborele DF, reținut în vectorul t pe poziția x (t[x]), datorită faptului că
în cadrul parcurgerii DF, pentru fiecare nod va trebui să luăm în considerare toate
muchiile care pornesc din nodul respectiv, cu excepția celei care îl unește de părintele
său
- numărul minim al nivelului care poate fi atins din x folosind descendenții săi și cel
mult o muchie de întoarcere. Acest număr va fi reținut în vectorul Min pe poziția x
(Min[x]).

Implementarea în limbajul Pascal a programului care determină mulțimea muchiilor critice

type plista=^lista;
lista=record
nod:integer;
c:boolean;
urm:plista;
end;
var G:array[1..1000] of plista;
T,niv,Min:array[1..1000] of integer;
n,m,i:integer;
p:plista;
viz:array[1..1000] of byte;
procedure DF(nod:integer);
var p:plista;
begin
viz[nod]:=1;
Min[nod]:=niv[nod];
p:=G[nod];
while p<>nil do begin
if viz[p^.nod]=0 then begin
niv[p^.nod]:=niv[nod]+1;
T[p^.nod]:=nod;
DF(p^.nod);
if Min[nod]>Min[p^.nod] then Min[nod]:=Min[p^.nod];
if Min[p^.nod]>niv[nod] then p^.c:=true;
end
else
if (p^.nod<>T[nod])and(Min[nod]>niv[p^.nod]) then
Min[nod]:=niv[p^.nod];
p:=p^.urm
end;
end;

procedure muchii_critice;
begin
for i:=1 to n do
if viz[i]=0 then
begin
niv[i]:=1;
DF(i);
end;

for i:=1 to n do
begin
p:=G[i];
while p<>nil do
begin
if p^.c then write(i,' ',p^.nod);
p:=p^.urm;
end
end
end;

procedure adauga(i,j:integer);
var p:plista;
begin
new(p); p^.nod:=j; p^.urm:=G[i]; G[i]:=p;
end;
procedure citeste_graf;
var i,j,k:longint;
f:text;
begin
assign(f,'graf.in'); reset(f);
readln(f,n,m);
for k:=1 to m do begin
readln(f,i,j);
adauga(i,j); adauga(j,i);
end;
end;

begin
citeste_graf;
muchii_critice
end.

5.3. Componente biconexe

Noțiunea de componente biconexe este strâns legată de cea a punctelor de articulație.

Un graf biconex este un graf fără puncte de articulație, adică unul care rămâne conex
în urma ștergerii oricărui nod. O componentă biconexă este un subgraf biconex maximal în
raport cu această proprietate.

Un graf care nu este biconex are mai mult de o componentă biconexă. Spre deosebire de cazul
componentelor conexe, nodurile pot aparține mai multor componente biconexe, iar acele
noduri care aparțin mai multor componente biconexe sunt chiar punctele de articulație ale
grafului.

Verificarea biconexității este o operație simplă, deoarece implică doar verificarea existenței
unui punct de articulație. Dacă graful nu conține niciun astfel de nod, atunci el este biconex,
iar dacă el conține cel puțin un astfel de nod, nu este biconex.
Sarcină de lucru: Fie un graf neorientat conex G=(X,U). Să se determine muchiile
fiecărei componente biconexe a grafului G.

Studiu preliminar: algoritmul de determinare a componentelor biconexe ale unui graf se


bazează și el pe parcurgerea DF a grafului respectiv. Datorită faptului că o componentă
biconexă nu conține niciun punct critic, o astfel de componentă poate fi identificată în
momentul în care este găsit un asfel de nod.

Prezentarea algoritmului: față de algoritmul de determinare a punctelor critice ale unui graf,
singura diferență o reprezintă introducerea unei stive care va conține muchiile. Aceasta poate
fi implementată atât static cât și dinamic.

Pentru fiecare nod x se memorează:

- părintele nodului, datorită faptului că, în cadrul parcurgerii DF, pentru fiecare nod va
trebui să luăm în considerare toate muchiile care pornesc din nodul respectiv, cu
excepția celei care îl unește de părintele său (T[x])
- nivelul minim care poate fi atins din fiecare nod folosind descendenții acestuia și cel
mult o muchie de întoarcere (Min[x])
- în timpul parcurgerii DF se vor afișa muchiile din fiecare componentă biconexă, într-o
stivă. Când se determină un nod x din graf care nu poate ajunge pe un nivel strict mai
mic decât al părintelui său din arborele DF (niv[T[x]]≤Min[x]), se afișează o nouă
componentă biconexă care va fi formată din muchiile din stivă, operația de extragere
din stivă oprindu-se în momentul găsirii muchiei (T[x],x) în vârful stivei.

Practic: pentru graful din figura 5.5. a), se obțin 3 componente biconexe (figura 5.4. b)):

Figura 5.3.1 Figura 5.3.1


a) b)
Etapele algoritmului:

1. În urma parcurgerii DF se vor obține vectorii:

t=(0, 5, 1, 6, 3, 3)
niv=(1, 4, 2, 4, 3, 3)
Min=(1, 4, 1, 2, 1, 2)

Figura 5.3.2

2. Determinarea componentelor biconexe:


a. În momentul găsirii nodului 6 care nu poate ajunge mai sus, stiva conține muchiile:
st=((1,3), (3,6), (4,6), (3,4)). Se elimină muchii din stivă până la găsirea muchiei
(t[6],6)=(3,6). La final, stiva va conține st=((1,3)).
b. Se găsește nodul 2 care nu poate ajunge mai sus: st=((1,3), (3,5), (2,5)). Se afișează
componenta biconexă formată din succesiunea muchiilor până la muchia (2,5). La final, stiva
va conține st=((1,3), (3,5)).
c. Se găsește nodul 3 care nu poate ajunge mai sus: st=((1,3), (3,5), (1,5)). Se afișează
componenta biconexă până la muchia (1,3)
d. Stiva devine vidă.

Implementarea în limbajul C++ a programului care determină muchiile fiecărei componente


biconexe

#include <fstream.h>
define MAX_N 101
define MAX_M 1001
struct lista {
int nod;
lista *urm;} *G[MAX_N];
int n,m,T[MAX_N], Min[MAX_N], niv[MAX_N],st[MAX_M][2],lg;
char viz[MAX_N];

void adaug(int i, int j)


{
lista *p;
p=new lista; p->nod=j; p->urm=G[i]; G[i]=p;
}

void citire_graf(void)
{
int i,j,k;
fstream f("graf.in", ios::in);
f>>n>>m;
for (k=1;k<=m; k++)
{
f>>i>>j;
adaug(i,j); adaug(j,i);
}
f.close();
}

void ad_st(int i, int j)


{
st[lg][0]=i;
st[lg++][1]=j;
}

void el_st(int *i, int *j)


{
*i=st[--lg][0];
*j=st[lg][1];
}

void DF(int nod)


{
lista *p;
int x,y;
viz[nod]=1; Min[nod]=niv[nod];
for (p=G[nod]; p!=NULL; p=p->urm)
{
if (p->nod!=T[nod]&&niv[nod]>niv[p->nod]) ad_st(p->nod, nod);
if (!viz[p->nod])
{
niv[p->nod]=niv[nod]+1;
T[p->nod]=nod;
DF(p->nod);
if (Min[nod]>Min[p->nod]) Min[nod]=Min[p->nod];
if (Min[p->nod]>=niv[nod])
{
do
{
el_st(&x, &y);
cout<<x<<" "<<y;
} while ((x!=nod || y!=p->nod) &&(x!=p->nod || y!=nod));
cout<<endl;
}
}
else
if (p->nod!=T[nod] && Min[nod]>niv[p->nod])
Min[nod]=niv[p->nod];
}
}

int main()
{
citire_graf();
DF(1);
return 0;
}

Complexitatea algoritmului: algoritmul este o variantă modificată a unei parcurgeri în


adâncime, aceasta având ordinul de complexitate O(m+n). Pe lângă această parcurgere, se
creează o stivă cu muchiile grafului. Fiecare muchie va fi inserată în stivă o singură dată și va
fi eliminată din stivă exact o data, timpul necesar acestei operații fiind O(m). Algoritmul
pentru identificarea nodurilor care fac parte dintr-o anumită componentă biconexă are ordinul
de complexitate O(m). Deci, algoritmul de determinarea componentelor biconexe ale unui graf
are ordinul de complexitate O(m+n).
5.4. Algoritmul lui Dantzing pentru drumuri minime de extremitate inițială dată

Sarcină de lucru: Se consideră un graf orientat cu n noduri, G=(X,U), o funcție


cost:𝑈 × 𝑅+ și un nod de plecare p. Se cere determinarea tuturor drumurilor de cost minim, de
la nodul p la toate celelalte noduri ale grafului.

Studiu preliminar: Pentru rezolvarea problemei se va folosi algoritmul lui Dantzing, care
obține un graf parțial al lui G și în care orice drum ce pleacă din p este minim. În cadrul
algoritmului sunt eliminate acele arce ale grafului care nu fac parte din niciun drum de cost
minim cu plecare din p.

Descrierea algoritmului: Algoritmul se bazează pe o metodă de marcare a vârfurilor în


ordinea apropierii lor de nodul de plecare p. La fiecare pas se determină un nou vârf, fie acesta
k, cel mai apropiat de p, ținând cont de costurile minime determinate succesiv.

Arcele care se vor adăuga grafului parțial, trebuie să respecte următoarele condiții:

- au extremitatea inițială într-un nod x deja selectat


- au extremitatea finală în nodul k
- d[x]+cost[x,k]=min, unde d[x] este distanța minimă de la p la x, iar cost[x,k] este
costul arcului (x,k)

Algoritmul se oprește fie când au fost selectate toate nodurile, fie când nu mai există drumuri
de la p la nodurile neselectate.
Determinarea tuturor drumurilor de la nodul p la toate celelalte noduri ale grafului se poate
face printr-o căutare recursivă în graful parțial obținut.
Practic: Fie graful din figura de mai jos

Figura 5.4.1
- se alege ca nod de plecare nodul 1
- se selectează nodul 2, d[2]=2. La graful parțial va fi adăugat arcul (1,2)
- se selectează nodul 3, d[3]=2. La graful parțial va fi adăugat arcul (1,3)
- se selectează nodul 5, d[5]=3. La graful parțial vor fi adăugate arcele (2,5) și (3,5)
- se selectează nodul 4, d[4]=4. La graful parțial vor fi adăugate arcele (2,4) și (5,4)
- se selectează nodul 6, d[6]=6. La graful parțial va fi adăugat arcul (4,6).

Se determină astfel costul minim de la 1 la 6, acesta fiind 6 și poate fi obținut prin oricare din
următoarele drumuri: 1 2 4 6, 1 2 5 4 6, 1 3 5 4 6.

Implementarea algoritmului în limbajul C++

void det_drum()
{int v1,v2;
v[1]=vi;sel[vi]=1;d[vi]=0;
for (j=2;j<=n;j++)
{min=10000;
for (i=1;i<=m;i++)
{v1=u[i][0];v2=u[i][1];
if (sel[v1]==1&&sel[v2]==0&&d[v1]+cost[v1][v2]<min)
{min=d[v1]+cost[v1][v2];
vmin=v2;}}
if (min<10000)
{v[j]=vmin;d[vmin]=min;sel[vmin]=1;}
else if (!sel[vf]){cout<<"nu exista drum";exit(1);}
}}
void tip()
{for (k=0,j=2;j<=i;j++)
k+=cost[v[x[j-1]]][v[x[j]]];
if (k==d[vf]){for(j=1;j<=i;j++) cout<<v[x[j]])<<" ";
}}
void afisare()
{cout<<” drumul minim are lungimea ”<<d[vf];
x[1]=x[2]=1;i=2;
while (i)
if (i>n)i--;
else if (x[i]<n) {x[i]++;if (a[v[x[i-1]]][v[x[i]]]==1)
if (v[x[i]]==vf) tip();
else x[++i]=x[i-1];} else x[i--]=0;}
5.5. Algoritmul lui Bellman – Ford

Sarcină de lucru: Fie un graf orientat G=(X,U) cu n noduri și o funcție c:U →Ɍ. Se
desemnează un nod de plecare x0. Pentru orice nod k∊X se cere determinarea drumului de cost
minim de la x0 la k. Se vor detecta situațiile în care există circuite de cost negativ care includ
nodul x0.

Studiu preliminar: Algoritmul Bellman – Ford determină drumurile de cost minim dintre un
nod desemnat ca nod de plecare și restul nodurilor acesibile lui chiar dacă există costuri
negative pe arce. Aceste rezultate sunt furnizate numai în situația în care nodul de plecare nu
face parte dintr-un circuit de cost negativ.

Strategia algoritmului este aceea de a minimiza succesiv costul drumului de la x0 la orice vârf
k din graf (d[k]) până la obținerea costului minim.

Astfel se verifică pentru fiecare arc (j, k) dacă participă la minimizarea distanței de la x0 la k,
adică dacă respectă condiția: d[k]> d[j]+ c[j][k].

Operația de minimizare a distanțelor se repetă de n ori.

În final, existența unui circuit negativ face ca la o nouă trecere prin mulțimea arcelor să fie în
continuare posibilă minimizarea costului unui drum. În acest caz algoritmul evidențiază
prezența circuitului negativ ce cuprinde nodul sursă.

Algoritmul în pseudocod:

pentru i=1,n execută


T[i]=0, d[i]=INF
sfârșit_pentru
d[x0]=0
pentru i=1,n execută
ok=0
pentru j=1,n execută //minimizări distanțe
pentru k=1,n execută
dacă d[j]<>INF și c[i,k]<>INF atunci
dacă d[k]>d[j]+c[j,k] atunci
d[k]=d[j]+c[j,k], T[k]=j, ok=1
sfârșit_dacă
sfârșit_dacă
sfârșit_pentru
sfârșit_pentru
sfârșit_pentru
dacă ok=1 scrie "Circuit negativ!"
altfel
pentru i=1,n execută
dacă T[i]<>0 atunci scrie i
sfârșit_pentru
sfârșit_dacă

Exemplu: Pentru graful din figura de mai jos,

Figura 5.7.1 Figura 5.7.2


alegem ca nod de plecare nodul 1 alegem ca nod de plecare nodul 2
La final, se obține: La final, se obține:
T: 0 1 0 0 0 0 T: 4 0 5 2 2
d: 0 25 ∞ ∞ ∞ ∞ d: 17 0 25 9 15
Observație:

Algoritmul Bellman Ford prezentat reține câte un singur drum de cost minim de la
vârful sursă la fiecare dintre celelalte vârfuri ale grafului.

Pentru a obține toate drumurile de cost minim de la sursă la fiecare dintre celelalte
vârfuri, se pot reține, pentru fiecare vârf y din graf, toate vârfurile care îl precedă pe y pe un
drum de cost minim de la x0 la y. Pentru aceasta, T va fi un tablou bidimensional în care
informațiile sunt reținute astfel:

- T[i][0] este numărul vârfurilor din care se poate atinge vârful i obținând un cost minim
- T[i][k] este cel de-al k-lea nod din care se poate atinge vârful i obținând un cost minim,
unde k este strict pozitiv.

Complexitate

Complexitatea algoritmului Bellman Ford este O(n3), deoarece algoritmul execută de n


ori secvența de minimizare a distanțelor având complexitatea O(n2).

Dacă graful este memorat în liste de adiacență, complexitatea algoritmului este O(nxm).
5.6.Fluxuri în rețele

O rețea de transport este un graf orientat G=(X,U) care satisface următoarele condiții:
- există un unic nod, numit sursă, s∊X în care nu intră nici un arc
- există un unic nod, numit destinație, d∊X din care nu iese nici un arc
- pentru fiecare nod x∊X-{s,d} există cel puțin un drum de la sursă la destinație
- pe mulțimea arcelor se definește o funcție C:U→R+ numită funcție de capacitate.

Se numește flux în reteaua de transport G o funcție f: U→R+ care îndeplinește


simultan următoarele condiții:
- condiția de conservare: pentru orice nod x din X, cu excepția sursei și a destinației,
suma fluxurilor de pe arcele care intră în x este egală cu suma fluxurilor de pe arcele care ies
din x
- condiția de mărginire: fluxul de pe orice arc este mai mic sau egal decât capacitatea
lui.

Se numește graf rezidual asociat unei rețele de transport, un graf GR=(X,UR) cu


proprietatea că (i,j)∊ UR, dacă și numai dacă sunt îndeplinite simultan următoarele condiții:
- (i,j)∊U
- fji>0 sau cij>fij

Se numește drum de creștere în graful rezidual, un drum de la sursă la destinație.

Exemplu: Fie rețeaua de transport de mai jos.

Figura 5.6.1
Pentru fiecare arc există asociat un număr reprezentând capacitatea:
125 233 467
137 342 545
244 356 569
Considerăm că în această rețea există un flux și anume:
1254 2333 4674
1370 3422 5451
2441 3561 5690
Graful rezidual asociat rețelei de transport este format din următoarele arce:
121 323 644
214 432 544
137 355 451
243 531 569
421 463

5.6.1. Algoritmul lui Ford – Fulkerson

Sarcină de lucru: Fie o rețea de transport G=(X,U), în care pentru fiecare arc există
asociată o capacitate și un flux inițial nul. Considerând un nod S ca sursă și un nod D ca
destinație, să se determine fluxul maxim care străbate rețeaua de la sursă la destinație.

Algoritmul lui Ford – Fulkerson determină valoarea fluxului maxim și unul dintre
acestea.

Pentru rețeaua din exemplul de mai sus, fluxul maxim este 12, distribuit astfel:
125 244 466
137 342 566
231 356

Pașii algoritmului sunt următorii:


repetă
construiește graful rezidual GR asociat rețelei de transport G
dacă există un drum de creștere atunci saturează acest drum
până_când nu mai există drum de creștere în GR

Saturarea drumului se realizează astfel


Pas 1. Se alege minimul dintre:
- cij-fij pentru arcele (i,j) din drum care corespund unor arce nesaturate din G
- fji pentru arcele (i,j) care corespund unor arce (j,i) din G pe care există flux.
Pas 2. Pentru fiecare arc (i,j) din drumul de creștere, arcului asociat din G i se modifică fluxul
atașat astefel:
- fij se mărește cu min dacă cij>fij
- fji se micșorează cu min dacă fji>0

Algoritmul în pseudocod

Ford_Fulkerson(G=(X,U,C,flux,s,d)
flux=0;
cât timp există drum de la s la d în GR execută
găsește drum de creștere s, x2, ..., d ∊ GR //GR graf rezidual
m=min(CR(xi,xi+1)) //CR capacitatea reziduală a arcului
flux(xi,xi+1)=flux(xi,xi+1)+m //saturează acest drum
flux(xi+1,xi)=flux(xi+1,xi)-m
flux_max=flux_max+m
sfârșit_cât_timp
scrie flux_max

Determinarea drumului de creștere din graful rezidual asociat se realizează printr-o parcurgere
BF a acestuia.
În implementarea algoritmului utilizăm următoarele variabile:
- tabloul C reține capacitatea tuturor arcelor rețelei de transport
- tabloul F memorează fluxul de pe care fiecare arc al rețelei
- tabloul T este folosit în parcurgerea BF pentru reținerea nodurilor de pe drumul de
creștere.

function min(x,y:integer):integer;
begin
if x<y then min:=x
else min:=y
end;
function BF(s,d:integer):integer;
var p,u,nod,i:integer;
Q:array[0..max_n] of integer;
begin
fillchar(T,sizeof(T),0);
BF:=0;
p:=0; u:=0; Q[p]:=s; T[s]:=-1;
while p<=u do
begin
nod:=Q[p];
for i:=1 to n do
if (T[i]=0) and (C[nod,i]>F[nod,i]) then
begin
inc(u); Q[u]:=i;
T[i]:=nod;
if i=d then BF:=1;
end;
inc(p);
end;
end;
procedure flux_maxim;
var i,k:integer;
begin
flux:=0;
while BF(s,d)<>0 do
begin
k:=inf;
i:=d;
while i<>s do
begin
k:=min(k,C[T[i],i]-F[T[i],i];
i:=T[i];
end;
i:=d;
while i<>s do
begin
inc(F[T[i],i],k);
dec(F[i,T[i]],k);
i:=T[i];
end;
inc(flux,r);
end;
end;

5.6.2. Algoritmul lui Dinic

Sarcină de lucru: Fie o rețea de transport G=(X,U), în care pentru fiecare arc există
asociată o capacitate și un flux inițial nul. Considerând un nod S ca sursă și un nod D ca
destinație, să se determine fluxul maxim care străbate rețeaua de la sursă la destinație.

Algoritmul lui Dinic folosește o parcurgere BF (ca și Ford – Fulkerson) pentru a construi
graful rezidual asociat rețelei de transport și pentru a găsi un drum de creștere de la sursă la
destinație. Nu se încearcă găsirea unui singur drum de creștere. Ci se folosește arborele BF
pentru a găsi un număr maximal de drumuri de creștere (nu neapărat maxim). Așadar, se
parcurg nodurile într-o ordine oarecare, iar pentru fiecare nod x atins în parcurgerea BF se
verifică dacă există o muchie în graful rezidual de la x la destinație, caz în care se verifică
dacă drumul de la sursă la x din arborele BF are capacitatea reziduală >0. Fiecare drum astfel
determinat este saturat (cu capacitatea reziduală minimă de pe drum), iar parcurgerea continuă.
Complexitatea algoritmului tinde către O(nxm).

Algoritmul implementat în limbajul Pascal

Considerăm următoarele declarații:


const max_n=101;
var C,F:array[0..max_n,0..max_n] of integer;
m,n,s,d:integer;
T:array[0..max_n] of byte;

procedure flux_maxim;
var i,j,k:integer;
begin
flux:=0;
while bf(s,d)<>0 do
for i:=1 to n do
begin
if (T[i]=-1) or (C[i,d]<=F[i,d]) then continue;
k:=C[i,d]-FF[i,d]; j:=i;
while j<>s do
begin
k:=min(k,C[T[j],j]-F[T[j],j]; j:=T[j];
end;
if k=0 then continue;
inc(F[i,d],k); dec(F[d,i],k); j:=i;
while j<>s do
begin
j:=T[j]; inc(F[T[j],j],k); dec(F[j,T[j],k];
end;
inc(flux,k);
end;
end;

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