Sunteți pe pagina 1din 37

5.

Arbori parţiali
Adeseori apar în practică probleme a căror rezolvare implică
noţiuni şi rezultate din teoria grafurilor. Să considerăm, de exemplu,
proiectarea unei reţele de telecomunicaţii care să conecteze un număr dat
de centrale, de amplasare cunoscută, astfel încât între oricare două
centrale să existe legătură. Unei astfel de probleme i se poate asocia un
graf neorientat în care mulţimea vârfurilor este formată din centralele ce
trebuie interconectate, iar mulţimea muchiilor din perechile de centrale
între care se poate realiza o legătură directă. Pentru ca între oricare două
centrale să existe legătură, ar trebui ca graful să fie conex, dar, ca în
orice problemă practică, intervine factorul de cost şi atunci ar fi de dorit
să se selecteze un număr cât mai mic posibil de legături directe între
centrale, cu alte cuvinte interesează un graf parţial conex minimal al
grafului, deci un arbore.
Definiţie
Fie G = (X, U) un graf neorientat. Numim arbore parţial al
grafului G un graf parţial care este arbore.
Teoremă
Condiţia necesară şi suficientă ca un graf să conţină un arbore
parţial este ca graful să fie conex.
Demonstraţie:
Necesitatea Presupunem că G = (X,U) admite un arbore parţial
H = (X, U'), U' Í U. H, fiind arbore, este conex şi adăugând la H
muchiile din U-U' el rămâne conex. Deci G conex.
Suficienţa Presupunem că G este conex. Dacă G este conex
minimal, el este arborele parţial căutat. Altfel, există o muchie [x,y]ÎU
astfel încât graful G1 = (X, U-{[x,y]}) este conex. Dacă G1 este conex
minimal, arborele parţial căutat este G1, altfel continuăm procedeul de
eliminare a muchiilor până când obţinem un graf conex minimal, care va
fi un arbore parţial a lui G.
Q.E.D.
Observaţie
Procedeul descris are un număr finit de paşi, deoarece la fiecare
pas este eliminată o muchie, iar G are un număr finit de muchii. Mai
mult, putem demonstra următoarea proprietate :
Propoziţie
Fie G = (X, U) conex, |X| = n, |U| = m. Numărul de muchii ce
trebuie înlăturate pentru ca graful să devină un arbore este m-n+1
(numărul ciclomatic al grafului).
Demonstraţie:

178
Presupunem că prin eliminarea unui număr oarecare de muchii din G am
obţinut un graf G' fără cicluri (o pădure). Fiecare din componentele
conexe ale lui G' este un arbore.
Notăm :
- p numărul componentelor conexe,
- ni numărul de vârfuri din componenta conexă i, iÎ{1,2,...,p}
- mi numărul de muchii din componenta conexă i, iÎ{1,2,...,p}.
Evident că mi = ni-1, "iÎ{1,2,...,p}.
Numărul de muchi din G' este
p

 (n  1)  n p
i
i 1
Deci au fost eliminate m-n+p muchii. Când G' este arbore, deci conex
(p=1), numărul muchiilor eliminate este m-n+1.
Q.E.D.
O primă problemă, cu aplicaţii, de exemplu, în chimie ar fi
generarea tuturor arborilor parţiali ai unui graf dat. Pentru acesta vom
folosi metoda backtracking.
Reprezentarea informaţiilor
-Vom reprezenta graful G= (X, U) cu ajutorul matricii muchiilor,
o matrice G cu două linii şi m coloane (m = |U|), în care pentru fiecare
muchie reţinem cele două extremităţi.
-Reprezentăm un arbore parţial ca un vector A cu n-1
componente, în care reţinem indicii din G ai muchiilor arborelui. Pentru a
nu genera de mai multe ori acelaşi arbore, vom conveni ca muchiile să fie
memorate în ordinea crescătoare a indicilor lor din G.
- Pentru a verifica dacă o muchie nou selectată nu formează
cicluri cu muchiile deja selectate, vom ţine evidenţa componentelor
conexe într-un vector c de dimensiune n (n = |X|), c[i] desemnând
componenta conexă din care face parte nodul i, pentru "i Î {1,2,..., n}.
Condiţii interne
1. A[i] Î {1, 2, ..., m}, "i Î {1, 2, ..., n-1}
2. A[i] £ A[i+1], "i Î {1, 2, ..., n-2}
3. c[G[1, A[i]]] ¹ c[G[2, A[i]]], "i Î {1, 2, ..., n-1}(nu se
formează cicluri, extremităţile muchiei fiind în
componente conexe diferite).
procedure ConstrArbore (i: byte); *
//generează toţi arborii parţiali care au primele i-1 muchii
//A[1],...,A[i-1]
*
Programul Generare-Arbori-Parþiali de la sfârºitul capitolului curent genereazã
toþi arborii parþiali ai unui graf conex dat.

179
var j: byte;
begin
if i = n then //am selectat n-1 muchii care nu formează cicluri
AfişareArbore
else
//selectez o muchie cu indice mai mare decât A[i-1]
for j := A[i-1]+1 to m do
if c[G[1, j]] ¹ c[G[2, j]] then
begin
A[i] := j; //selectez muchia j
Unific(c[G[1, j]], c[G[2, j]]);
//unific componentele conexe ale extremităţilor muchiei j
ConstrArbore(i+1);
Separ(c[G[1, j]], c[G[2, j]]);
//restaurez situaţia dinaintea selectării muchiei j
end;
end;

Iniţial, c[i] := i, "i Î {1, 2, ..., n}şi apelăm ConstrArbore(1).

5.1. Arbori parţiali de cost minim

Uneori nu interesează generarea tuturor arborilor parţiali ai unui


graf conex ci numai a celor care satisfac anumite condiţii de optim. De
exemplu, pentru proiectarea reţelei de telecomunicaţii ar fi interesantă
obţinerea unui arbore parţial care să minimizeze cheltuielile.
Vom considera în continuare o funcţie c: U ® R+, care asociază
fiecărei muchii din graf un cost (în exemplul nostru, să spunem, distanţa
între două centrale). Definind costul unui arbore parţial ca fiind suma
costurilor muchiilor arborelui, se pune problema obţinerii unui arbore
parţial de cost minim.
De exemplu, fie următorul graf:

180
Fig. 1.
Acest graf admite mai mulţi arbori parţiali de cost minim, de exemplu:

Fig. 2.

5.1.1. Algoritmul lui Kruskal de determinare a unui arbore


parţial de cost minim*

Fie G = (X, U) un graf conex şi c: U ® R+ o funcţie cost. Pentru


a determina un arbore parţial de cost minim, se pleacă de la o pădure
formată din n arbori (n = ½X½), fiecare arbore fiind format dintr-un
singur vârf. La fiecare pas se selectează o muchie de cost minim care nu
a mai fost selectată şi care nu formează cicluri cu cele deja selectate. Să
considerăm de exemplu, graful din figura 1. Iniţial:

Fig. 3.
Selectând o muchie de cost 1, obţinem:

*
Programul Kruskal de la sfârºitul capitolului curent genereazã un arbore parþial de
cost minim al unui graf conex, utilizând algoritmul lui Kruskal.

181
Fig. 4.
Deci am unificat arborii corespunzători extremităţilor muchiei
selectate. Selectăm din nou o muchie de costul minim 1:

Fig. 5.
La acest pas nu mai putem selecta o muchie de cost 1, deoarece
s-ar obţine un ciclu. Selectăm muchia de cost 2:

Fig. 6.
Selectând, în final, muchia de cost 3, obţinem un graf fără cicluri
maximal, deci un arbore:

Fig. 7.
La fiecare pas al algoritmului sunt unificaţi doi arbori, cei
corespunzători extremităţilor muchiei selectate. Deci, după n-1 paşi,
pădurea iniţială va fi transformată într-un singur arbore.

182
Pentru implementarea algoritmului este necesară rezolvarea
următoarelor două probleme: cum extragem muchia de cost minim şi
cum testăm dacă muchia selectată formează sau nu cicluri cu cele deja
selectate.
Pentru a extrage minimul, o primă idee ar fi să sortăm muchiile
crescător după cost şi să parcurgem secvenţial muchiile ordonate. În
cazul în care arborele parţial de cost minim este găsit suficient de repede,
un număr mare de muchii rămân netestate şi în acest caz s-ar pierde timp
inutil cu sortarea acestor muchii. O altă idee, mai eficientă, ar fi să
organizăm muchiile grafului ca un min-heap, structură ce permite
extragerea eficientă a minimului.
Pentru a testa dacă o muchie formează cicluri cu muchiile deja
selectate este suficient să testăm dacă extremităţile muchiei se găsesc în
aceeaşi componentă conexă. Pentru aceasta va trebui să ţinem permanent
evidenţa componentelor conexe (arborilor) care se formează.
Reprezentarea informaţiilor
1.Vom memora graful într-un vector G cu m (m = ½U½) componente,
fiecare componentă fiind o înregistrare cele două extremităţi şi costul
muchiei:
Muchie = record
e1, e2: Vf;
cost: real;
end;
2. Arborele parţial de cost mimim se va memora într-un vector A cu n-1
componente ce reţine indicii din G ai muchiilor selectate
3. Evidenţa componentelor conexe o vom ţine cu ajutorul unui vector c
cu n componente, c[i] = componenta conexă căreia îi aparţine vârful i.
Componentele conexe vor fi identificate printr-un reprezentant (vârful cu
indicele cel mai mic din componenta conexă respectivă).
Teoremă
Algoritmul lui Kruskal generează un arbore parţial de cost minim.
Demonstraţie:
1. Algoritmul selectează numărul maxim de muchii care nu
formează cicluri, deci, conform teoremei de caracterizare a arborilor, se
obţine un arbore parţial.
2. Să demonstrăm că arborele parţial obţinut în urma aplicării
algoritmului lui Kruskal este un arbore parţial de cost minim :
Fie A = (a1, a2, ..., an-1) muchiile arborelui rezultat în urma aplicării
algoritmului, în ordinea selectării lor.

183
Presupunem prin reducere la absurd că arborele obţinut nu este
de cost minim, deci există A' = (a 1', a2', ...., an-1') un alt arbore parţial,
astfel încât c(A') < c(A).
Fie k = min{i | 1 £ i £ n, ai ¹ ai'}, primul indice de la care A şi A’
diferă.
Deci A = (a1, a2, ..., ai-1, ai, ..., an-1)
A' = (a1, a2, ..., ai-1, ai', .., an-1'), cu ai ¹ ai' .
Evident c(ai) £ c(aj'), "j Î {i, ..., n-1}, altfel algoritmul ar fi
selectat muchia aj' în loc de ai, deoarece ar fi avut cost mai mic şi nu
formează cicluri cu a1,...,ai-1. Adaug la A' muchia ai. Se formează un ciclu
în care intervin doar muchii din {ai'...an-1'}. Elimin una din muchiile
ciclului diferită de ai. Se obţine un arbore A" = (a1,..., ai-1, ai, ai+1"..., an-
1") care are i muchii comune cu A. În plus c(A")-c(A') = c(a i)-c(aj') £ 0
Þ c(A") £ c(A').
Repetăm procedeul de înlocuire a muchiilor din A' cu muchiile
din A. Obţinem c(A') ³ c(A") ³ ... ³ c(A).
Dar am presupus că c(A') < c(A) Þ contradicţie. Deci A este un
arbore parţial de cost minim.
Q.E.D.
Complexitatea algoritmului
Organizarea muchiilor ca un min-heap este de O(m), m = ½U½.
Algoritmul cercetează în cel mai defavorabil caz toate cele m muchii
pentru a selecta n-1, la fiecare pas fiind necesară extragerea muchiei de
cost minim, operaţie de O(log m) = O(log n). Selectarea unei muchii
implică şi o operaţie de unificare a doi arbori, al cărei timp de execuţie
depinde de metoda de unificare aleasă.

5.1.2. Algoritmul lui Prim de determinare a unui arbore


parţial de cost minim*

Ca şi algoritmul lui Kruskal, algoritmul lui Prim utilizează o


strategie Greedy. Iniţial se pleacă de la un arbore format dintr-un singur
vârf. La fiecare pas se selectează o muchie de cost minim astfel încât
mulţimea A a muchiilor selectate şi mulţimea Y a vârfurilor unite de
acestea să formeze un arbore.
De exemplu, să considerăm graful din figura 1 şi vârful de start 5. Iniţial

*
Programul Prim de la sfârºitul capitolului curent genereazã un arbore parþial de cost
minim al unui graf conex, utilizând algoritmul lui Prim.

184
Fig. 8.
Selectăm o muchie de cost minim care să fie incidentă cu vârful 5:

Fig. 9.
Selectăm o muchie de cost minim, care să fie incidentă cu unul din
vârfurile din subgraful obţinut la pasul anterior:

Fig. 10.
Selectez o muchie de cost 1, incidentă cu unul din vârfurile din subgraful
anterior:

Fig. 11.
Selectând cea de a patra muchie, obţinem un arbore parţial de cost
minim:

Fig. 12.
La fiecare pas se selectează un nou vârf, adiacent cu unul din vârfurile
subgrafului, astfel încât muchia corespunzătoare să fie de cost minim.
Nodul nou adăugat va fi terminal şi deci nu se vor obţine cicluri, iar
subgraful construit este la fiecare pas conex, deci arbore.
Reprezentarea informaţiilor

185
1. Reprezentăm graful prin matricea costurilor, Cnxn

R 0, dacă i j
||c(LMMNi,jOPPQ), dacă LMMNi,jOPPQÎU
C i, j  S
||, dacă LMMNi,jOPPQU
T
2. Z va fi mulţimea vârfurilor neselectate în subgraf (Z = X-Y).
3. Vom utiliza un vector key, de dimensiune n, în care pentru fiecare vârf
x Î Z vom reţine costul minim al muchiilor ce unesc vârful x cu un vârf v
din subgraf:
key[x] = min{c([x, v])½v Î Y}, "x Î X\Y.
Evident, dacă astfel de muchii nu există key[x] = +.
4. Reţinem arborele parţial de cost minim, memorând vârfurile grafului în
ordinea în care au fost atinse într-un vector p de dimensiune n.
p[x] = vârful din care a fost atins x.

procedure Prim;
//determinã un APM al unui graf; matricea costurilor c, numãrul de
// vârfuri n ºi vârful de start r sunt variabile globale
var x, j, i: Vf; key: array[ Vf ] of real;
Z: set of Vf; p: array[ Vf ] of Vf;
begin
//iniţializare
for x := 1 to n do key[x] ¬ +;
key[r] : 0; p[r] := 0; Z : [1, 2, ..., n] - [r];
while Z ¹ [] do //există vârfuri neselectate
begin
i : ExtrageMin(Z); //extrage din Z vârful de cheie minimă
for j := 1 to n do //actualizez cheile vârfurilor din Z
if C[i, j] ¹  then // i şi j sunt adiacente
if (j Î Z) and (key[j] > C[i, j]) then
begin
p[j] := i;
key[j] := C[i, j];
end
end
end;
Complexitatea algoritmului

186
Algoritmul execută n-1 paşi, la fiecare pas selectând un vârf din
graf de cheie minimă şi reactualizând cheile vârfurilor neselectate,
operaţie de O(n). Deci algoritmul este de ordinul O(n2).

5.2. Arbori parţiali BFS

Breadth-First-Search este tehnica de explorare a grafurilor în


lăţime.
Dat fiind un graf conex G = (X, U) şi un nod sursă s Î X,
metoda BFS impune vizitarea mai întâi a nodului s, apoi a tuturor
nodurilor nevizitate adiacente cu s, apoi a tuturor nodurilor nevizitate
adiacente nodurilor adiacente cu s, ş.a.m.d.
De exemplu, pentru graful din figura de mai jos, parcurgerea
BFS, cu nodul sursă s = 6, este: 6, 4, 5, 8, 9, 2, 3, 7, 10, 11, 1, 12.

Fig. 13.
Reţinând toate muchiile utilizate în timpul parcurgerii obţinem
arborele parţial BFS, cu rădăcina s = 6 (figura 14): [6,4], [6,5], [6,8],
[6,9], [4,2], [4,3], [5,7], [8,10], [9,11], [2,1], [11,12].

Fig. 14.
Observaţie
Pentru orice vârf v din arbore, lanţul unic care uneşte rădăcina s
de v reprezintă lanţul cu număr minim de muchii de la s la v în graf.

Reprezentarea informaţiilor
1. Reprezentăm graful prin listele de adiacenţă.

187
G: array[Vf] of Lista;
Deci pentru fiecare vârf din graf reţinem lista vârfurilor adiacente
cu vârful respectiv.
2. Arborele parţial BFS îl reprezentăm cu ajutorul unui vector T în care
pentru fiecare vârf reţinem vârful din care a fost atins în timpul
parcurgerii BFS.
T: array[Vf] of Vf;
3. Utilizăm un vector boolean V, în care pentru fiecare vârf din graf
reţinem dacă a fost sau nu atins în timpul parcurgerii BFS.
V: array[Vf] of boolean;
4. Pentru parcurgerea grafului în lăţime vom utiliza o coadă pe care o
iniţializăm cu vârful sursă. La fiecare pas extragem un element din
coadă, vizităm toate vârfurile nevizitate adiacente cu vârful extras şi le
inserăm în coadă, reţinând pentru fiecare vârf vizitat vârful din care a fost
atins, pentru reconstituirea arborelui parţial BFS.
Observaţie
G, T, n (numărul de vârfuri din graf) şi s (vârful sursă) sunt
variabile globale.
procedure BFS;*
//parcurge în lăţime graful G, începând cu vârful s construind
//arborele parţial BFS
var C: Coada; q: Lista; i: Vf;
V: array[ Vf ] of boolean;
begin
for i := 1 to n do V[i] := false;
C Ü s; //iniţializez coada cu vârful sursă
while C ¹ [] do
begin
x Ü C; //extrage un vârf x din coadă
q := G[x];
while q ¹ nil do //parcurg lista de adiacenţă a nodului x
begin
i := q^.inf;
if not V[i] then // nodul i este nevizitat
begin
V[i] := true;
T[i] := x;//reţin vârful din care a fost atins i
C Ü i; //introduc vârful i în coadă
end;
*
Programul Arbori-Parþiali-BF-DF de la stârºitul capitolului curent genereazã
arborii parþiali BFS ºi DFS ai unui graf conex dat.

188
q := q^.urm;
end;
end;
end;
Observaţii
1. Dacă graful G nu este conex parcurgând BFS fiecare componentă
conexă obţinem o pădure, formată din arborii parþiali corespunzători
fiecărei componente conexe.
2. Complexitatea algoritmului, în cazul în care graful este reprezentat
prin listele de adiacenţă, este de O(n+m), unde n este numărul de vârfuri,
iar m numărul de muchii din graf.

5.3. Arbori parţiali DFS

O altă tehnică de parcurgere (explorare) a grafurilor este metoda


Depth-First-Search (parcurgerea în adâncime).
Dat fiind G un graf conex şi un nodul sursă s vizităm mai întâi
nodul s, apoi primul nod nevizitat adiacent cu s, mergând în adâncime cât
este posibil. Când un nod x nu mai are vecini nevizitaţi ne întoarcem să
cercetăm dacă nodul din care a fost atins x mai are sau nu vecini
nevizitaţi şi continuăm parcurgerea.
De exemplu, pentru graful din figura 13, parcurgerea după
metoda DFS, cu nodul iniţial s = 6, determină următoarea ordine de
vizitarea a nodurilor: 6, 4,2,1,3,7,5,8,9,10,11,12. Marcând muchiile
utilizate prin vizitarea nodurilor obţinem un arbore parţial numit arbore
parţial DFS, cu rădăcina s = 6 (figura 15): [6,4], [4,2], [2,1], [2,3], [3,7],
[7,5], [5,8], [8,9], [9,10], [10,11], [11,12].

Fig. 15.

Observaţie
Reprezentarea informaţiilor se face în acelaşi mod ca la
parcurgerea BFS, în plus vectorul V fiind de asemeni variabilă globală.
Algoritmul poate fi descris folosind o procedură recursivă DFS,
pe care iniţial o apelăm cu parametrul s, astfel:

189
procedura DFS(x: Vf);
//parcurge vârfurile nevizitate ale grafului începând cu
x
begin
V[x] := true;
q := G[x];
while q ¹ nil do //parcurg lista de adiacenţă a vârfului x
begin
i := q^.inf;
if not V[i] then //i este nevizitat
begin
T[i] := x; //reţin vârful din care a fost atins i
DFS(i); //parcurge vârfurile nevizitate începând cu i
end;
q := q^.urm
end;
end;
Observaţii
1. Dacă graful G nu este conex parcurgând DFS fiecare componentă
conexă obţinem o pădure, formată din arborii parþiali corespunzători
fiecărei componente conexe.
2. Complexitatea algoritmului, în cazul în care graful este reprezentat
prin listele de adiacenţă este de O(n+m), unde n este numărul de vârfuri,
iar m numărul de muchii din graf.

5.4. Aplicaþie. Descompunerea unui graf în componente biconexe

Definiţie
Fie G = (X, U) un graf neorientat conex. Vârful v Î X se
numeşte punct de articulaţie dacă subgraful obţinut prin eliminarea
vârfului v şi a muchiilor incidente cu acesta nu mai este conex.
De exemplu, pentru graful din figura 16 punctele de articulaţie
sunt 1,3,5,7.

190
Fig. 16.
Definiţie
Un graf se numeşte biconex dacă nu are puncte de articulaţie.
În multe aplicaţii practice, ce se pot modela cu ajutorul grafurilor,
nu sunt de dorit punctele de articulaţie. Revenind la exemplul cu reţeaua
de telecomunicaţii, dacă o centrală dintr-un punct de articulaţie se
defectează rezultatul este nu numai întreruperea comunicării cu centrala
respectivă ci şi cu alte centrale.
Definiţie
O componentă biconexă a unui graf este un subgraf biconex
maximal cu această proprietate.
De exemplu, pentru graful din figura 16 componentele biconexe
sunt:

Fig. 17.
Pentru a descompune graful în componente biconexe vom utiliza
proprietăţile parcurgerii DFS. Parcurgând graful DFS putem clasifica
muchiile grafului în:
-muchii care aparţin arborelui parţial DFS (tree edges);
-muchii [u, v] care nu aparţin arborelui şi care unesc vârful u cu
un strămoş al său v în arborele parţial DFS numite muchii de întoarcere
(back edges). Acestea sunt marcate în exemplu punctat.
De exemplu graful de mai sus poate fi redesenat, clasificând muchiile
ţinând cont de arborele parţial DFS cu rădăcina 3:

191
Fig. 18.
Observăm că rădăcina arborelui parţial DFS este punct de articulaţie
dacă şi numai dacă are cel puţin doi descendenţi, între vârfuri din
subarbori diferiţi ai rădăcinii neexistând muchii. Mai mult, un vârf x
oarecare nu este punct de articulaţie dacă şi numai dacă din orice fiu y al
lui x poate fi atins un strămoş al lui x pe un lanţ format din descendenţi ai
lui x şi o muchie de întoarcere (un drum “de siguranţă” între x şi y).
Pentru fiecare vârf x al grafului putem defini :
dfn(x)  numărul de ordine al vârfului x în parcurgerea DFS a grafului
(depth-first-number).

De exemplu:
x 0 1 2 3 4 5 6 7 8 9
dfn(x 2 1 3 0 4 5 6 7 8 9
)

Dacă x este un strămoş al lui y în arborele parţial DFS atunci


dfn(x) < dfn(y).
De asemeni pentru fiecare vârf x din graf putem defini :
low(x)  numărul de ordine al primului vârf din parcurgerea DFS ce
poate fi atins din x pe un alt lanţ decât lanţul unic din arborele parţial
DFS.
low(x) = min{dfn(x), min{ low(y) | y fiu al lui x }, min{dfn(y) | [x,y]
muchie de întoarcere}.

De exemplu:
x 0 1 2 3 4 5 6 7 8 9
dfn(x 2 1 3 0 4 5 6 7 8 9
)

192
low(x 2 1 1 0 1 5 5 5 8 9
)
Deci putem caracteriza mai exact punctele de articulaţie dintr-un graf
astfel:
x este punct de articulaţie dacă şi numai dacă este rădăcina unui
arbore parţial DFS cu cel puţin doi descendenţi sau, dacă nu este
rădăcină, are un fiu y astfel încât low(y) ³ dfn(x).
Pentru exemplul din figura 16:
- nodul 3 este punct de articulaţie deoarece este rădăcina
arborelui parţial DFS şi are doi descendenţi,
- nodul 7 este punct de articulaţie deoarece low(8)=8 ³dfn(7)=7,
- nodul 5 este punct de articulaţie deoarece low(6)=5³dfn(5)=5,
- nodul 1 este punct de articulaţie deoarece low(0)=2³dfn(1)=1.
Modificăm procedura DFS, pentru a calcula pentru fiecare vârf din
graf valorile dfn şi low.
Intrare:-graful G, reprezentat prin listele de adiacenţă;
Ieşire:-vectorii dfn şi low.
Utilizăm o variabilă globală num, pentru a calcula numărul de ordine
al vârfului curent în parcurgerea în adâncime.
//Iniţializare
num := 0;
for x := 1 to n do dfn[x] := -1;
DfnLow(s, -1) // s este rădăcina arborelui parţial DFS

procedure DfnLow(u, tu: Vf);


//parcurge DFS graful G începând cu vârful u, calculând
// valorile dfn[u] ºi low(u); tu este tatăl lui u
var q: Lista; x: Vf;
begin
dfn[u] := num; low[u] := dfn[u]; inc(num);
q := G[u];
while q ¹ nil do //parcurg lista de adiacenţă a lui u
begin
x := q^.inf;
if dfn[x] = -1 then //x este nevizitat
begin
DfnLow(x, u)
low[u] := min(low[u], low[x]);
//funcţia min returnează minimul argumentelor ei
end
else
193
if x ¹ tu then low[u] := min(low[u], dfn[x]);
q := q^.urm;
end
end;
Vom folosi această idee de calcul recursiv al valorilor dfn şi low
pentru descompunerea în componente biconexe a unui graf neorientat
conex.
Reprezentarea informaţiilor se face în acelaşi mod ca la
parcurgerea DFS, dar vectorul V nu mai este necesar, pentru vârfurile
nevizitate dfn fiind -1. În plus, vom folosi o stivă S în care vom reţine
muchiile din graf (atât cele care aparţin arborelui cât şi cele de
întoarcere) în ordinea în care sunt întâlnite în timpul parcurgerii. Atunci
când identificăm o componentă biconexă, mai exact identificăm un nod u
care are un fiu x astfel încât low(x) ³ dfn(u), eliminăm din stivă şi afişăm
toate muchiile din componenta biconexă respectivă.
//Iniţializare
num := 0;
S Ü (s, -1);
//stiva este iniţializată cu o muchie fictivă [s,-1], s fiind sursa
for x := 1 to n do dfn[x] := -1;
Biconex(s, -1) // s este rădăcina arborelui parţial DFS

procedure biconex(u, tu: Vf);*


//afişează componentele biconexe , parcurgând graful începând
// cu vârful u; tu este tatăl lui u
var q: Lista; x: Vf;
begin
dfn[u] := num; low[u] := dfn[u]; inc(num);
q := G[u];
while q ¹ nil do // parcurg lista de adiacenţă a lui u
begin
x := q^.inf;
if (tu ¹ x) and (dfn[x] < dfn[u]) then S Ü [u, x];
//dacă tux sau dfn(x)>dfn(u) muchia (u,x) a fost deja
//reţinută în stivă
if dfn[x]  -1 then //x este nevizitat
begin
Biconex(x, u)

*
Programul Componente-Biconexe de la sfârºitul capitolului curent realizeazã
descompunerea în componente biconexe a unui graf neorientat conex.

194
low[u] := min(low[u], low[x]);
if low[x] ³ dfn[u] then
//am identificat o nouă componentă biconexă
Afisare(x, u) // extrage din S şi afişează muchiile
//componentei biconexe curente
else
if x ¹ tu then low[u] := min(low[u], dfn[x]);
end;
q := q^.urm;
end;
end;
Observaţie
Oricare două componente biconexe au cel mult un vârf comun,
deci nici o muchie nu poate fi în două componente biconexe.
Să urmărim execuţia algoritmului pe următorul exemplu:

Fig. 19.
Arborele parţial DFS este:

Fig. 20.

Iniţial:
x 0 1 2 3 4 5 6 7
dfn(x) -1 -1 -1 -1 -1 -1 -1 -1
low(x)
num 0

195
S:
3 -1
Apelez biconex(3, -1)
x 0 1 2 3 4 5 6 7
dfn(x) -1 -1 -1 0 -1 -1 -1 -1
low(x) 0

num 1
x¬1
S:
3 1
3 -1
Apelez biconex(1, 3)
x 0 1 2 3 4 5 6 7
dfn(x) -1 1 -1 0 -1 -1 -1 -1
low(x) 1 0

num 2
x¬0
S:
1 0
3 1
3 -1
Apelez biconex(0, 1):
x 0 1 2 3 4 5 6 7
dfn(x) 2 1 -1 0 -1 -1 -1 -1
low(x) 2 1 0

num 3
x¬1
Revin în biconex(1, 3)
low[1] ¬ min(low[1], low[0])  low[1]
low[0] > dfn[3]. Am obţinut o componentă biconexă, afişez [1,0].
S:
3 1
3 -1
x¬2
S:
196
1 2
3 1
3 -1
Apelez biconex(2, 1)
x 0 1 2 3 4 5 6 7
dfn(x) 2 1 3 0 -1 -1 -1 -1
low(x) 2 1 3 0

num 4
x¬1
x¬4
S:
2 4
1 2
3 1
3 -1
Apelez biconex(4, 2)
x 0 1 2 3 4 5 6 7
dfn(x) 2 1 3 0 4 -1 -1 -1
low(x) 2 1 3 0 4

num 5
x¬2
x¬3
S:
4 3
2 4
1 2
3 1
3 -1
low[4] ¬ min(low[4], dfn[3]) = 0
x 0 1 2 3 4 5 6 7
dfn(x) 2 1 3 0 4 -1 -1 -1
low(x) 2 1 3 0 0
Revin în biconex(2, 1)
low[2] ¬ min(low[2], low[4])  0
x 0 1 2 3 4 5 6 7

197
dfn(x) 2 1 3 0 4 -1 -1 -1
low(x) 2 1 0 0 0
Revin în biconex(1, 3)
low[1] ¬ min(low[1], low[2]) = 0
x 0 1 2 3 4 5 6 7
dfn(x) 2 1 3 0 4 -1 -1 -1
low(x) 2 0 0 0 0
low[1] ¬ min(low[1], dfn[2])
Revin în biconex(3, -1)
low[3] ¬ min(low[3], low[1]) Þ low[1]  dfn[3].
Am obţinut o nouă componentă biconexă: [4,3], [2,4], [1,2], [3,1].
S:
3 -1
x¬4
low[3] ¬ min(low[3], dfn[4])  0
x¬5
S:
3 5
3 -1
Apelez biconex(5, 3)
x 0 1 2 3 4 5 6 7
dfn(x) 2 1 3 0 4 5 -1 -1
low(x) 1 0 0 0 0 5

num 6
x¬3
x¬6
S:
5 6
3 5
3 -1
Apelez biconex(6, 5)
x 0 1 2 3 4 5 6 7
dfn(x) 2 1 3 0 4 5 6 -1
low(x) 1 0 0 0 0 5 6

num 7
x¬5
198
x¬7
S:
6 7
5 6
3 5
3 -1
Apelez biconex(7, 6)
x 0 1 2 3 4 5 6 7
dfn(x) 2 1 3 0 4 5 6 7
low(x) 1 0 0 0 0 5 6 7

num 8
x¬5
S:
7 5
6 7
5 6
3 5
3 -1
low[7] ¬ min(low[7], dfn[5]) = 5
x 0 1 2 3 4 5 6 7
dfn(x) 2 1 3 0 4 5 6 7
low(x) 1 0 0 0 0 5 6 5
x¬6
Revin în biconex(6, 5)
low[6] ¬ min(low[6], low[7])  5
x 0 1 2 3 4 5 6 7
dfn(x) 2 1 3 0 4 5 6 7
low(x) 1 0 0 0 0 5 5 5
low[6] ¬ min(low[6], dfn[7])  5
Revin în biconex (5, 3)
low[5] ¬ min(low[5], low[6])  5
low[6] > dfn[5], deci afişez o nouă componentă biconexă:
[7,5],[6,7],[5,6].
S:
3 5
3 -1
Revin în biconex(3, -1)

199
low[3]  min(low[3], low[5])  0
low[5]  dfn[3], deci afişez o nouă componentă biconexă: [5,3].
S:
3 -1
Stop

Teoremă
Procedura biconex(s,-1) generează componentele biconexe ale
grafului conex G.
Demonstraţie:
Vom lua în considerare cazul în care n, numărul de vârfuri din G,
este cel puţin 2 (dacă n  1, G are o singură componentă biconexă,
formată dintr-un singur vârf).
Vom face demonstraţia prin inducţie după B, numărul de
componente biconexe.
P(1) B  1 Û graful este biconex: rădăcina s a arborelui parţial
DFS va avea un singur fiu, să-l notăm x. Apelul biconex(x, s) a explorat
toate muchiile grafului şi le-a introdus în stiva S. Cum x este singurul
vârf pentru care low[x] ³ dfn[s], muchiile grafului vor fi afişate într-o
singură componentă biconexă.
P(B) Să presupunem acum că algoritmul funcţionează corect
pentru toate grafurile conexe cu cel mult B componente biconexe.
P(B+1) Vom demonstra că algoritmul funcţionează pentru toate
grafurile conexe cu cel mult B+1 componente biconexe.
Fie G un graf cu B+1 componente biconexe şi fie x primul vârf
pentru care este îndeplinită condiţia low[x] ³ dfn[u]. Până în acest
moment nu a fost identificată nici o componentă biconexă. În urma
apelului biconex(x, u), toate muchiile incidente cu descendenţi ai lui x se
află în stiva S, deasupra muchiei [u, x]. Cum x este primul vârf care
îndeplineşte condiţia low[x] ³ dfn[u], deducem că descendenţii lui x nu
sunt puncte de articulaţie. Cum u este punct de articulaţie, rezultă că
muchiile situate în S, deasupra muchiei [u, x], inclusiv, formează o
componentă biconexă. Muchiile acestei componente biconexe sunt
extrase din stivă şi afişate, deci, de la acest pas algoritmul revine la
aflarea componentelor biconexe ale unui graf G’, obţinut din G prin
eliminarea unei componente biconexe. Cum G’ are B componente
biconexe, conform ipotezei inductive, algoritmul determină corect
componentele biconexe ale lui G’.
Q.E.D.
Observaţie

200
Algoritmul de descompunere în componente biconexe a unui graf
reprezentat prin listele de adiacenţă este liniar (O(n+m)), unde n este
numărul de vârfuri, iar m numărul de muchii din graf.

5.5. Problemã rezolvatã


Problema bârfei
-Baraj, Arad 1992-
Se consideră n persoane P1, P2, ..., Pn care doresc fiecare să
transmită propria bârfa celorlalte persoane. Numim instrucţiune o
pereche (i, j) având următorul efect: persoana Pi transmite persoanei Pj
propria sa bârfă, dar şi eventualele bârfe primite anterior prin instrucţiuni
de la alte persoane. Din păcate anumite perechi de persoane, citite de la
intrare, se duşmănesc şi deci nu comunică între ele. Să se determine,
dacă este posibil, o secvenţă de instrucţiuni prin care fiecare persoană să
cunoască bârfele tuturor celorlalte persoane.
Soluţie:
Asociem problemei un graf neorientat astfel:
-mulţimea vârfurilor este formată din cele n persoane.
-între două vârfuri există muchie dacă cele două persoane
corespunzatoare comunică.
Problema are soluţie dacă şi numai dacă graful asociat problemei
este conex.
Dacă graful este conex, atunci admite un arbore parţial.
Determinăm un arbore parţial DFS al grafului dat, cu rădăcina 1.
Parcurgând acest arbore în postordine, toate bârfele vor fi recepţionate
de vârful 1, fiecare muchie utilizată reprezentând o instrucţiune.
Parcurgând arborele în preordine, nodul 1 va transmite bârfele tuturor
celorlalte persoane. Sunt astfel necesare 2n-2 instrucţiuni.
Observaţie
Numărul de 2n-2 instrucţiuni este optim.
Reprezentarea informaţiilor
-Reprezentăm graful prin matricea de adiacenţă:
{ 1, dacă i şi j comunică;
g i, j  0, altfel.
-Reprezentăm arborele parţial DFS ca pe un arbore binar, utilizând
reprezentarea fiu-frate.
-Utilizam un vector v de dimensiune n, pentru a marca dacă un vârf a
fost sau nu atins în timpul parcurgerii DFS:
{
vi  1 , dacă i a fost vizitat;
0, altfel.
program barfa;

201
const NMaxPers = 7;
type Pers = 0..NMaxPers;
Arbore = ^NodArbore;
NodArbore = record
p: Pers;
tata, fiu, frate: Arbore
end;
var g: array[Pers, Pers] of 0..1;
n, i: Pers; conex: boolean;
v: array[Pers] of 0..1;
A: Arbore;
fout: text;

procedure citire;
var f: text; i, j: Pers;
begin
assign(f, 'barfa.in'); reset(f);
readln(f, n);
for i := 1 to n do
for j := 1 to n do
g[i,j] := 1;
while not eof(f) do
begin
readln(f, i, j);
g[i,j] := 0; g[j,i] := 0
end;
close(f);
end;

procedure DFS(x: Arbore);


{vizitam varful x; tx este tatal lui x}
var i: Pers; q, u: Arbore;
begin
u := nil;{ultimul fiu al lui x}
for i := 1 to n do
if (g[x^.p, i] = 1) and (v[i] = 0) then
begin
v[i] := 1;
new(q); q^.p := i; q^.tata := x;
q^.fiu := nil; q^.frate := nil;
if u = nil then {i este primul fiu al lui x}
x^.fiu := q
else
u^.frate := q; {i este primul frate al lui u}
u := q;
DFS(q);
end;
end;
procedure postordine(x: Arbore);
{afiseaza instructiunile corespunzatoare parcurgerii postordine
a arborelui DFS}
var q: Arbore;
begin
q := x^.fiu;

202
while q <> nil do
begin
postordine(q);
q := q^.frate;
end;
if x<> A then writeln(fout,'(',x^.p,',',x^.tata^.p,')');
end;

procedure preordine(x: Arbore);


var q: Arbore;
begin
if x <> A then writeln(fout,'(',x^.tata^.p,',',x^.p,')');
q := x^.fiu;
while q <> nil do
begin
preordine(q);
q := q^.frate;
end;
end;

begin {program principal}


citire;
assign(fout,'barfa.out'); rewrite(fout);
v[1] := 1; A^.p := 1;
DFS(A);
conex := true;
for i := 1 to n do
if v[i] = 0 then
begin
conex := false;
break
end;
if not conex then {graful este neconex}
writeln(fout,'Nu exista solutii!')
else
begin
postordine(A);
preordine(A);
end;
close(fout);
end.

5.6. Exerciţii

1.Demonstraţi prin inducţie după numărul de vârfuri din graf că


algoritmul lui Prim produce un arbore parţial de cost minim.
2. Fie G = (X, U) un graf neorientat conex. Muchia [x, y] se
numeşte punte dacă prin eliminarea ei din graf, graful parţial obţinut nu
este conex. Scrieţi un algoritm care determină toate punţile unui graf
conex. Algoritmul să funcţioneze în timp de O(n+m) (n =½x½, m =½U½).
Observaţie

203
O muchie este punte dacă şi numai dacă nu aparţine unui ciclu.
3. Fie G = (X, U) un graf neorientat conex cu funcţia pondere
asociată c: U ® R+*; ½U½ ³ ½X½.
a). Fie T un arbore parţial de cost minim pentru G. Demonstraţi
că există o muchie [u, v] Î T şi [x, y]  T astfel încât T-{[u,v]}È{[x,y]}
este al doilea cel mai bun arbore parţial de cost minim (SAPM).
b). Fie T un arbore parţial pentru G. Pentru oricare două vârfuri
u, v Î X definim max(u, x) ca fiind o muchie de pondere maximă de pe
lanţul unic între u şi v în T.
Descrieţi un algoritm cu timpul de execuţie de O(½X½2) astfel încât,
dat T, să calculeze max(u, v), " u, v Î X.
4. Fie G = (X, U) un graf neorientat conex. Numim distanţa
dintre vârfurile x şi y, notată d(x, y), numărul de muchii al celui mai scurt
lanţ care uneşte x cu y.
Notăm: e(x) = excentricitatea vârfului x:
e( x)  max d( x, y)
yÎX

d(G) = diametrul grafului G


d(G)  max e( x)
xÎX
r(G) = raza grafului G
r (G)  min e( x)
xÎX
c(G) = centrul grafului G
n
c(G)  x Î X e( x)  r (G) q
a). Demonstraţi că c(G) este format dintr-un vârf sau din două
vârfuri adiacente.
b). Arătaţi că d(G) £ 2*r(G).
c). Descrieţi un algoritm care să calculeze raza, diametrul şi
centrul unui graf conex dat.
5. Arbori parţiali DS
O altă tehnică de parcurgere în adâncime a nodurilor unui graf
este metoda Depth-Search. Această metodă îmbină cele două metode de
parcurgere prezentate, în sensul că urmează acelaşi algoritm ca metoda
Breadth-First-Search numai că utilizează o stivă, ca şi metoda Depth-
First-Search.
De exemplu, pentru graful din figura 13, parcurgerea după
metoda DS, cu nodul iniţial s = 6, determină următoarea ordine de
vizitare a nodurilor: 6, 4, 5, 8, 9, 10, 11, 12, 7, 3, 2, 1. Marcând muchiile
utilizate prin vizitarea nodurilor obţinem un arbore parţial numit arbore

204
parţial DS, cu rădăcina s = 6 (figura 21): [6,4], [6,5], [6,8], [6,9], [9,10],
[9,11], [11,12], [10,7, [7,3], [3,2], [2,1].

Fig. 21.
Scrieţi un program care realizează parcurgerea în adâncime dupa
metoda DS a unui graf, cu obţinerea arborelui parţial DS.
6. Problema sponsorului (Olimpiada Naþionalã de Informaticã,
Suceava 1996)
RATUC Suceava, unul dintre sponsorii olimpiadei, îşi propune să
îmbunătăţească transportul în comun în localitate. Directorul va pune la
dispoziţie o schemă pe care sunt reprezentate staţiile, numerotate pentru
simplificare, de la 1 la n şi cele k linii directe între staţii, astfel încât între
oricare două staţii există legătură, eventual cu schimbarea mijlocului de
transport. Trebuie să determinaţi dacă există cel puţin o linie directă prin
blocarea căreia legătura, directă sau indirectă, între cel puţin două staţii
să fie întreruptă. Dacă astfel de linii există, să se propună înfiinţarea unui
număr cât mai mic de linii directe între staţiile existente astfel încât prin
blocarea unei singure linii directe, oricare ar fi aceasta, circulaţia între
oricare două staţii să fie posibilă; se alege soluţia pentru care suma
ocolurilor pe traseele varianta (măsurate în număr de linii directe) să fie
cât mai mică.

205
Anexã

program Generare_Arbori_Partiali;
const NMaxVf = 20;
NMaxMuchii = NMaxVf*(NMaxVf-1) div 2;
type Vf = 1..NMaxVf;
NrMuchie = 1..NMaxMuchii;
Graf = array[1..2, NrMuchie] of NrMuchie;
Arbore = array[0..NMaxVf-1] of NrMuchie;
var n: Vf; m: NrMuchie;
g:Graf; a: Arbore;
c: array[Vf] of Vf; nr: word; fout: text;

procedure Initializare;
var i: NrMuchie; j: Vf; fin: text;
begin
assign(fin, 'ap.in'); assign(fout, 'ap.out');
rewrite(fout); reset(fin);
readln(fin, n, m);
for i := 1 to m do readln(fin, g[1,i], g[2,i]);
for j := 1 to n do c[j] := j;
close(fin)
end;

procedure ScrieArb;
var i: Vf;
begin
inc(nr);
writeln(fout, 'Arborele ',nr);
for i := 1 to n-1 do
write(fout,'[', g[1,a[i]],',',g[2,a[i]],'] ');
writeln(fout)
end;

procedure ConstrArb(i: Vf);


var j: NrMuchie; k, Nou, Vechi: Vf; aux: set of Vf;
begin
if i = n then
ScrieArb
else
for j := a[i-1]+1 to m do
if c[g[1, j]] <> c[g[2, j]] then
{muchia j nu formeaza cicluri cu cele deja selectate}
begin
a[i] := j;
{unific componentele conexe ale extremitatilor
muchiei j}
aux := [];{retin varfurile ce au fost trecute din
componenta conexa Vechi in Nou}
Nou := c[g[1, j]]; Vechi := c[g[2, j]];
for k := 1 to n do
if c[k] = Vechi then
begin

206
c[k] := Nou;
aux := aux+[k];
end;
ConstrArb(i+1);
{restaurez situatia dinaintea apelului }
for k := 1 to n do
if k in aux then c[k] := Vechi;
end
end;

begin {program principal }


Initializare;
ConstrArb(1);
close(fout);
end.

program Kruskal;
const NMaxVf = 20;
NMaxMuchii = NMaxVf*(NMaxVf-1) div 2;
type Vf = 1..NMaxVf;
NrMuchie = 1..NMaxMuchii;
Muchie = record
e1, e2: Vf;
cost: word
end;
Graf = array[NrMuchie] of Muchie;
Arbore = array[1..NMaxVf-1] of Muchie;
var n, i, min, max, NrMSel: Vf; k:Muchie;
m: NrMuchie;
g:Graf; a: Arbore;
c: array[Vf] of Vf;

procedure Initializare;
var i: NrMuchie; j: Vf; fis: text;
begin
assign(fis, 'Kruskal.in'); reset(fis);
readln(fis, n, m);
for i := 1 to m do readln(fis, g[i].e1, g[i].e2, g[i].cost);
for j := 1 to n do c[j] := j;
close(fis)
end;

procedure CombHeap(i, m: NrMuchie);


var parinte, fiu: NrMuchie; v: Muchie;
begin
v := g[i];
parinte := i;
fiu := 2*i;
while fiu <= m do
begin
if fiu < m then
if g[fiu].cost > g[fiu+1].cost then fiu := fiu+1;
if v.cost > g[fiu].cost then

207
begin
g[parinte] := g[fiu];
parinte := fiu;
fiu := fiu*2;
end
else
fiu := m+1;
end;
g[parinte] := v;
end;

procedure ConstrHeap;
var i: NrMuchie;
begin
for i := m div 2 downto 1 do CombHeap(i, m);
end;

function maxim(a, b: word): word;


begin
maxim := a;
if b > a then maxim := b
end;

function minim(a, b: word): word;


begin
minim := a;
if b < a then minim := b;
end;

procedure Afisare;
var i: Vf; CostAPM: word;
begin
if NrMSel= n-1 then
begin
CostAPM := 0;
writeln('Arborele partial de cost minim este :');
for i := 1 to n-1 do
begin
writeln('[',a[i].e1,',',a[i].e2,'] cost=',a[i].cost);
CostAPM := CostAPM+a[i].cost
end;
writeln('Costul APM=',CostAPM);
end
else
writeln('Graful nefiind conex nu admite arbori partiali. ');
readln
end;

begin {program principal }


Initializare;
ConstrHeap;
while (NrMSel < n) and (m > 0) do
begin
{extrag din heap muchia de cost minim}

208
k := g[1];
g[1] := g[m];
dec(m);
CombHeap(1, m);
if c[k.e1] <> c[k.e2] then {nu se formeaza ciclu}
begin
inc(NrMSel);
a[NrMSel] := k;
{contopesc conponentele conexe ale extremitatilor
muchiei selectate }
min := minim(c[k.e1], c[k.e2]);
max := maxim(c[k.e1], c[k.e2]);
for i := 1 to n do
if c[i] = max then c[i] := min
end
end;
Afisare
end.

program Prim;
const NMaxVf = 20;
NMaxMuchii = NMaxVf*(NMaxVf-1) div 2;
Inf = MaxLongInt;
type Vf = 1..NMaxVf;
Graf = array[Vf,Vf] of real;
var n, r, i, VfMin: Vf;
G: Graf;
p: array[Vf] of 0..NMaxVf;
Z: set of Vf;
key: array[Vf] of real;
KeyMin: real;

procedure Initializare;
var i, j, k, nrv: Vf; c: real;
fin: text;
begin
assign(fin, 'prim.in'); reset(fin);
readln(fin, n, r);
for i := 1 to n do
for j := 1 to n do
G[i,j] := Inf;
for i := 1 to n do
begin
G[i,i] := 0;
read(fin, nrv); {nrv-numarul de varfuri adiacente cu i}
for j := 1 to nrv do
begin
{citesc varfurile adiacente cu i si costurile
muchiilor corespunzatoare}
read(fin, k, c);
G[i,k] := c;
end;
readln(fin);

209
end;
for i := 1 to n do
begin
key[i] := G[r, i];
p[i] := r
end;
Z := [1..n]-[r]; p[r] := 0;
close(fin);
end;

procedure AfisareAPM;
var i: Vf; cost: real;
begin
cost := 0;
writeln('Muchiile APM sunt: ');
for i := 1 to n do
if i <> r then
begin
write('[',p[i],',',i,'] ');
cost := cost+G[i,p[i]];
end;
writeln;
writeln('Costul APM ', cost:7:2);
readln
end;

begin
Initializare;
while Z <> [] do
begin
KeyMin := Inf; {determin nodul din Z de cheie minima}
for i := 1 to n do
if (i in Z) and (KeyMin > key[i]) then
begin
KeyMin := key[i];
VfMin := i
end;
Z := Z-[VfMin];
for i := 1 to n do {actualizez cheile varfurilor din Z}
if (i in Z) and (G[i, VfMin] < key[i]) then
begin
p[i] :=VfMin;
key[i] := g[i, VfMin]
end
end;
AfisareAPM
end.

program Arbori_Partiali_BF_DF;
const NrMaxVf = 20;
type Vf = 0..NrMaxVf;
Lista = ^NodLista;

210
NodLista = record
inf: Vf;
urm: Lista;
end;
Graf = array[Vf] of Lista;
Arbore = array[Vf] of Vf;
var n: Vf;{numarul de varfuri din graf}
s: Vf;{varful sursa}
G: Graf;
AB, AD: Arbore;{arborii partiali obtinuti prin parcurgere
BF, respectiv DF}
V: array[Vf] of boolean;

procedure Initializare;
var fin: text; i, j: Vf; p: Lista;
begin
assign(fin, 'graf.in'); reset(fin);
readln(fin ,n); readln(fin, s);
for i := 1 to n do
begin
G[i] := nil; V[i] := false;
while not seekeoln(fin) do
begin
read(fin, j);
new(p); p^.inf := j;
p^.urm := G[i]; G[i] := p;
end;
readln(fin);
end;
V[s] := true;
close(fin);
end;

procedure DFS(x: Vf);


var q: Lista;
begin
q := G[x];
while q <> nil do {parcurg lista de adiacenta a lui x}
begin
if not V[q^.inf] then {varful q^.inf este nevizitat}
begin
V[q^.inf] := true; AD[q^.inf] := x;
DFS(q^.inf);
end;
q := q^.urm;
end;
end;

procedure BFS;
type Coada = ^NodCoada;
NodCoada = record
inf: Vf;
urm: Coada
end;

211
var C, SfC: Coada;
q: Lista; x: Vf;
p: Coada;
begin {initializez coada}
new(C); C^.inf := s; C^.urm := nil; SfC := C;
for x := 1 to n do V[x] := false; V[s] := true;
while C <> nil do
begin
x := C^.inf;{primul varf din coada}
q := G[x];
while q <> nil do {parcurg lista de adiacenta a lui x}
begin
if not V[q^.inf] then {varful q^.inf nevizitat}
begin
V[q^.inf] := true; AB[q^.inf] := x;
{inserez q^.inf in coada}
new(p); p^.inf := q^.inf;
p^.urm := nil; SfC^.urm := p; SfC := p;
end;
q := q^.urm;
end;
p := C; C := C^.urm;
dispose(p);{am extras din coada vaful x}
end;
end;

procedure AfisareArbore(A: Arbore);


var i: Vf;
begin
for i := 1 to n do
if A[i] <> 0 then write('[',i,',',A[i],'] ');
writeln;
end;

begin {program principal}


Initializare;
DFS(s);
BFS;
writeln('Arborele partial DFS este:');
AfisareArbore(AD);
writeln('Arborele partial BFS este:');
AfisareArbore(AB);
readln;
end.

program Componente_Biconexe;
const NMaxVf = 20;
type Vf = -1..NMaxVf;
Stiva = ^NodStiva;
NodStiva = record
f, t: Vf;
urm: Stiva

212
end;
Lista = ^NodLista;
NodLista = record
v: Vf;
leg: Lista;
end;
Graf = array[0..NMaxVf] of Lista;
var S: Stiva; G: Graf;
low, dfn: array[0..NMaxVf] of Vf;
nr, n, sursa:Vf; num: 0..NMaxVf;

procedure Initializare;
var ns: Vf; i, j: Vf; p: Lista; fin: text;
begin
assign(fin, 'biconex.in'); reset(fin);
readln(fin, n); readln(fin, sursa);
for i := 0 to n do
begin
G[i] := nil;
readln(fin, ns);
for j := 1 to ns do
begin
new(p);
read(fin, p^.v);
p^.leg := G[i]; G[i] := p;
end;
readln(fin);
dfn[i] := -1
end;
close(fin);
new(S); S^.f := sursa; S^.t := -1; S^.urm := nil;
end;

procedure Afisare_Comp_Biconexa(x, u: Vf);


var p: Stiva; a, b: Vf;
begin
inc(nr);{nr-numarul de componente biconexe}
writeln('muchiile componentei biconexe ',nr,' sunt:');
repeat
p := S; a := p^.t; b := p^.f; S := S^.urm;
write('(',a,',',b,') ');
dispose(p);
until (a = u) and (b = x);
writeln
end;

function min (a, b: Vf): Vf;


begin
if a < b then min := a
else min := b
end;

procedure Biconex(u, tu: Vf);


{calculeaza dfn si low si afiseaza componentele biconexe}

213
var p: Lista; q: Stiva; x: Vf;
begin
dfn[u] := num; low[u] := dfn[u]; inc(num);
p := G[u];
while p <> nil do {parcurg lista de adiacenta a lui u}
begin
x := p^.v;
if (x <> tu) and (dfn[x] < dfn[u])then
begin{insereaza in stiva S muchia(u,x)}
new(q); q^.f := x; q^.t := u;
q^.urm := S; S := q;
end;
if dfn[x] < 0 then {x nu a mai fost vizitat}
begin
Biconex (x, u);
low[u] := min(low[u], low[x]);
if low[x] >= dfn[u] then {u punct de articulatie;
am identificat o componenta biconexa, ce
contine muchiile din stiva S pana la (u,x)}
Afisare_Comp_BiconexA(x,u);
end
else
if x <> tu then low[u] := min(low[u], dfn[x]);
p:=p^.leg
end;
end;

begin {program principal}


Initializare;
Biconex(sursa, -1);
readln
end.

214

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