Documente Academic
Documente Profesional
Documente Cultură
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).
Î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.
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 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.
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:
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.
Î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
Studiu preliminar:
- 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]).
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.
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.
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.
- 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)):
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
#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 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();
}
int main()
{
citire_graf();
DF(1);
return 0;
}
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.
Arcele care se vor adăuga grafului parțial, trebuie să respecte următoarele condiții:
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.
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].
Î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:
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
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.
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
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
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;
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).
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;