Documente online.
Zona de administrare documente. Fisierele tale
Am uitat parola x Creaza cont nou
 HomeExploreaza
upload
Upload




Arbori partiali

Matematica


ALTE DOCUMENTE

KILOGRAMUL - submultiplii si multiplii
Masina de calcul a lui Raymundus - Lullus ca sistem de memorie magica
Siruri
PROBLEMA PROPUSA PENTRU CONCURS Matematica - CLASA A VIII A -
PERMUTARI
Calculul ecuatiilor matriciale
Inmultirea numerelor naturale - Exersare
Cuadrivectori
Asimptote - probleme
Datele si reprezentarea lor - Reprezentarea numerelor

Arbori partiali

Adeseori apar în practica probleme a caror rezolvare implica notiuni si rezultate din teoria grafurilor. Sa consideram, de exemplu, proiectarea unei retele de telecomunicatii care sa conecteze un numar dat de centrale, de amplasare cunoscuta, astfel încât între oricare doua centrale sa existe legatura. Unei astfel de probleme i se poate asocia un graf neorientat în care multimea vârfurilor este formata din centralele ce trebuie interconectate, iar multimea muchiilor din perechile de centrale între care se poate realiza o legatura directa. Pentru ca între oricare doua centrale sa existe legatura, ar trebui ca graful sa fie conex, dar, ca în orice problema practica, intervine factorul de cost si atunci ar fi de dorit sa se selecteze un numar cât mai mic posibil de legaturi directe între centrale, cu alte cuvinte intereseaza un graf partial conex minimal al grafului, deci un arbore.



Definitie

Fie G = (X, U) un graf neorientat. Numim arbore partial al grafului G un graf partial care este arbore.

Teorema

Conditia necesara si suficienta ca un graf sa contina un arbore partial este ca graful sa fie conex.

Demonstratie:

Necesitatea Presupunem ca G = (X,U) admite un arbore partial H = (X, U'), U' U. H, fiind arbore, este conex si adaugând la H muchiile din U-U' el ramâne conex. Deci G conex.

Suficienta Presupunem ca G este conex. Daca G este conex minimal, el este arborele partial cautat. Altfel, exista o muchie [x,y] U astfel încât graful G = (X, U-) este conex. Daca G este conex minimal, arborele partial cautat este G , altfel continuam procedeul de eliminare a muchiilor pâna c nd obtinem un graf conex minimal, care va fi un arbore partial a lui G.

Q.E.D.

Observatie

Procedeul descris are un numar finit de pasi, deoarece la fiecare pas este eliminata o muchie, iar G are un numar finit de muchii. Mai mult, putem demonstra urmatoarea proprietate :

Propozitie

Fie G = (X, U) conex, |X| = n, |U| = m. Numarul de muchii ce trebuie înlaturate pentru ca graful sa devina un arbore este m-n+1 (numarul ciclomatic al grafului).

Demonstratie:

Presupunem ca prin eliminarea unui numar oarecare de muchii din G am obtinut un graf G' fara cicluri (o padure). Fiecare din componentele conexe ale lui G' este un arbore.

Notam :

- p numarul componentelor conexe,

- ni numarul de vârfuri din componenta conexa i, i

- mi numarul de muchii din componenta conexa i, i

Evident ca mi = ni "i

Numarul de muchi din G' este

Deci au fost eliminate m-n+p muchii. Când G' este arbore, deci conex (p=1), numarul muchiilor eliminate este m-n+1.

Q.E.D.

O prima problema, cu aplicatii, de exemplu, în chimie ar fi generarea tuturor arborilor partiali ai unui graf dat. Pentru acesta vom folosi metoda backtracking.

Reprezentarea informatiilor

-Vom reprezenta graful G= (X, U) cu ajutorul matricii muchiilor, o matrice G cu doua linii si m coloane (m = |U|), în care pentru fiecare muchie retinem cele doua extremitati.

-Reprezentam un arbore partial ca un vector A cu n-1 componente, în care retinem indicii din G ai muchiilor arborelui. Pentru a nu genera de mai multe ori acelasi arbore, vom conveni ca muchiile sa fie memorate în ordinea crescatoare a indicilor lor din G.

- Pentru a verifica daca o muchie nou selectata nu formeaza cicluri cu muchiile deja selectate, vom tine evidenta componentelor conexe într-un vector c de dimensiune n (n = |X|), c[i] desemnând componenta conexa din care face parte nodul i, pentru "i

Conditii interne

A[i] "i

A[i] A[i+1], "i

c[G[1, A[i]]] c[G[2, A[i]]], "i (nu se formeaza cicluri, extremitatile muchiei fiind în componente conexe diferite).

procedure ConstrArbore (i: byte);

//genereaza toti arborii partiali care au primele i-1 muchii //A[1],...,A[i-1]

var j: byte;

begin

if i = n then //am selectat n-1 muchii care nu formeaza cicluri

AfisareArbore

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 extremitatilor muchiei j

ConstrArbore(i+1);

Separ(c[G[1, j]], c[G[2, j]]);

//restaurez situatia dinaintea selectarii muchiei j

end;

end;

Initial, c[i] := i, "i si apelam ConstrArbore(1).

5.1. Arbori partiali de cost minim

Uneori nu intereseaza generarea tuturor arborilor partiali ai unui graf conex ci numai a celor care satisfac anumite conditii de optim. De exemplu, pentru proiectarea retelei de telecomunicatii ar fi interesanta obtinerea unui arbore partial care sa minimizeze cheltuielile.

Vom considera în continuare o functie c: U R+, care asociaza fiecarei muchii din graf un cost (în exemplul nostru, sa spunem, distanta între doua centrale). Definind costul unui arbore partial ca fiind suma costurilor muchiilor arborelui, se pune problema obtinerii unui arbore partial de cost minim.

De exemplu, fie urmatorul graf:

Fig. 1.

Acest graf admite mai multi arbori partiali de cost minim, de exemplu:

Fig. 2.

Algoritmul lui Kruskal de determinare a unui arbore partial de cost minim*

Fie G = (X, U) un graf conex si c: U R+ o functie cost. Pentru a determina un arbore partial de cost minim, se pleaca de la o padure formata din n arbori (n = X ), fiecare arbore fiind format dintr-un singur vârf. La fiecare pas se selecteaza o muchie de cost minim care nu a mai fost selectata si care nu formeaza cicluri cu cele deja selectate. Sa consideram de exemplu, graful din figura 1. Initial:

Fig. 3.

Selectând o muchie de cost 1, obtinem:

Fig. 4.

Deci am unificat arborii corespunzatori extremitatilor muchiei

selectate. Selectam 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 obtine un ciclu. Selectam muchia de cost 2:

Fig. 6.

Selectând, în final, muchia de cost 3, obtinem un graf fara cicluri maximal, deci un arbore:

Fig. 7.

La fiecare pas al algoritmului sunt unificati doi arbori, cei corespunză 23523t1913x ;tori extremitatilor muchiei selectate. Deci, dupa n-1 pasi, padurea initiala va fi transformata într-un singur arbore.

Pentru implementarea algoritmului este necesara rezolvarea urmatoarelor doua probleme: cum extragem muchia de cost minim si cum testam daca muchia selectata formeaza sau nu cicluri cu cele deja selectate.

Pentru a extrage minimul, o prima idee ar fi sa sortam muchiile crescator dupa cost si sa parcurgem secvential muchiile ordonate. În cazul în care arborele partial de cost minim este gasit suficient de repede, un numar mare de muchii ramân netestate si în acest caz s-ar pierde timp inutil cu sortarea acestor muchii. O alta idee, mai eficienta, ar fi sa organizam muchiile grafului ca un min-heap, structura ce permite extragerea eficienta a minimului.

Pentru a testa daca o muchie formeaza cicluri cu muchiile deja selectate este suficient sa testam daca extremitatile muchiei se gasesc în aceeasi componenta conexa. Pentru aceasta va trebui sa tinem permanent evidenta componentelor conexe (arborilor) care se formeaza.

Reprezentarea informatiilor

1.Vom memora graful într-un vector G cu m (m = U ) componente, fiecare componenta fiind o înregistrare cele doua extremitati si costul muchiei:

Muchie = record

e1, e2: Vf;

cost: real;

end;

2. Arborele partial de cost mimim se va memora într-un vector A cu n-1 componente ce retine indicii din G ai muchiilor selectate

3. Evidenta componentelor conexe o vom tine cu ajutorul unui vector c cu n componente, c[i] = componenta conexa careia îi apartine vârful i. Componentele conexe vor fi identificate printr-un reprezentant (vârful cu indicele cel mai mic din componenta conexa respectiva).

Teorema

Algoritmul lui Kruskal genereaza un arbore partial de cost minim.

Demonstratie:

1. Algoritmul selecteaza numarul maxim de muchii care nu formeaza cicluri, deci, conform teoremei de caracterizare a arborilor, se obtine un arbore partial.

2. Sa demonstram ca arborele partial obtinut în urma aplicarii algoritmului lui Kruskal este un arbore partial de cost minim :

Fie A = (a1, a2, ..., an-1) muchiile arborelui rezultat în urma aplicarii algoritmului, în ordinea selectarii lor.

Presupunem prin reducere la absurd ca arborele obtinut nu este de cost minim, deci exista A' = (a1', a2', ...., an-1') un alt arbore partial, astfel încât c(A') < c(A).

Fie k = min, primul indice de la care A si A' difera.

Deci A = (a , a , ..., ai-1, ai, ..., an-1

A' = (a , a , ..., ai-1, ai', .., an-1'), cu ai ai

Evident c(ai c(aj "j , altfel algoritmul ar fi selectat muchia aj' în loc de ai, deoarece ar fi avut cost mai mic si nu formeaza cicluri cu a ,...,ai-1. Adaug la A' muchia ai. Se formeaza un ciclu în care intervin doar muchii din . Elimin una din muchiile ciclului diferita de ai. Se obtine un arbore A" = (a ,..., ai-1, ai, ai+1"..., an-1") care are i muchii comune cu A. În plus c(A")-c(A') = c(ai)-c(aj 0 c(A") c(A').

Repetam procedeul de înlocuire a muchiilor din A' cu muchiile din A. Obtinem c(A') c(A") c(A).

Dar am presupus ca c(A') < c(A) contradictie. Deci A este un arbore partial de cost minim.

Q.E.D.

Complexitatea algoritmului

Organizarea muchiilor ca un min-heap este de O(m), m = U . Algoritmul cerceteaza în cel mai defavorabil caz toate cele m muchii pentru a selecta n-1, la fiecare pas fiind necesara extragerea muchiei de cost minim, operatie de O(log m) = O(log n). Selectarea unei muchii implica si o operatie de unificare a doi arbori, al carei timp de executie depinde de metoda de unificare aleasa.

Algoritmul lui Prim de determinare a unui arbore partial de cost minim*

Ca si algoritmul lui Kruskal, algoritmul lui Prim utilizeaza o strategie Greedy. Initial se pleaca de la un arbore format dintr-un singur vârf. La fiecare pas se selecteaza o muchie de cost minim astfel încât multimea A a muchiilor selectate si multimea Y a vârfurilor unite de acestea sa formeze un arbore.

De exemplu, sa consideram graful din figura 1 si vârful de start 5. Initial

Fig. 8.

Selectam o muchie de cost minim care sa fie incidenta cu vârful 5:

Fig. 9.

Selectam o muchie de cost minim, care sa fie incidenta cu unul din vârfurile din subgraful obtinut la pasul anterior:

Fig. 10.

Selectez o muchie de cost 1, incidenta cu unul din vârfurile din subgraful anterior:

Fig. 11.

Selectând cea de a patra muchie, obtinem un arbore partial de cost minim:

Fig. 12.

La fiecare pas se selecteaza un nou vârf, adiacent cu unul din vârfurile subgrafului, astfel încât muchia corespunzatoare sa fie de cost minim. Nodul nou adaugat va fi terminal si deci nu se vor obtine cicluri, iar subgraful construit este la fiecare pas conex, deci arbore.

Reprezentarea informatiilor

Reprezentam graful prin matricea costurilor, Cnxn

2. Z va fi multimea 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 retine costul minim al muchiilor ce unesc vârful x cu un vârf v din subgraf:

key[x] = min, "x X\Y.

Evident, daca astfel de muchii nu exista key[x] = +

4. Retinem arborele partial 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;

//determina un APM al unui graf; matricea costurilor c, numarul de

// v rfuri n si 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

//initializare

for x := 1 to n do key[x]

key[r] 0; p[r] := 0; Z := [1, 2, ..., n] - [r];

while Z do //exista vârfuri neselectate

begin

i := ExtrageMin(Z); //extrage din Z vârful de cheie minima

for j := 1 to n do //actualizez cheile vârfurilor din Z

if C[i, j] then // i si 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

Algoritmul executa n-1 pasi, la fiecare pas selectând un vârf din graf de cheie minima si reactualizând cheile vârfurilor neselectate, operatie de O(n). Deci algoritmul este de ordinul O(n

5.2. Arbori partiali BFS

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

Dat fiind un graf conex G = (X, U) si un nod sursa 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, s.a.m.d.

De exemplu, pentru graful din figura de mai jos, parcurgerea BFS, cu nodul sursa s = 6, este: 6, 4, 5, 8, 9, 2, 3, 7, 10, 11, 1, 12.

Fig. 13.

Retinând toate muchiile utilizate în timpul parcurgerii obtinem arborele partial BFS, cu radacina 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.

Observatie

Pentru orice vârf v din arbore, lantul unic care uneste radacina s de v reprezinta lantul cu numar minim de muchii de la s la v în graf.

Reprezentarea informatiilor

Reprezentam graful prin listele de adiacenta.

G: array[Vf] of Lista;

Deci pentru fiecare vârf din graf retinem lista vârfurilor adiacente cu vârful respectiv.

Arborele partial BFS îl reprezentam cu ajutorul unui vector T în care pentru fiecare vârf retinem vârful din care a fost atins în timpul parcurgerii BFS.

T: array Vf] of Vf;

Utilizam un vector boolean V, în care pentru fiecare vârf din graf retinem daca a fost sau nu atins în timpul parcurgerii BFS.

V: array[Vf] of boolean;

4. Pentru parcurgerea grafului în latime vom utiliza o coada pe care o initializam cu vârful sursa. La fiecare pas extragem un element din coada, vizitam toate vârfurile nevizitate adiacente cu vârful extras si le inseram în coada, retinând pentru fiecare vârf vizitat vârful din care a fost atins, pentru reconstituirea arborelui partial BFS.

Observatie

G, T, n (numarul de vârfuri din graf) si s (vârful sursa) sunt variabile globale.

procedure BFS;*

//parcurge în latime graful G, începând cu vârful s construind //arborele partial 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; //initializez coada cu vârful sursa

while C [] do

begin

x C; //extrage un vârf x din coada

q := G[x];

while q nil do //parcurg lista de adiacenta a nodului x

begin

i := q^.inf;

if not V[i] then // nodul i este nevizitat

begin

V[i] := true;

T[i] := x;//retin vârful din care a fost atins i

C i; //introduc vârful i în coada

end;

q := q^.urm;

end;

end;

end;

Observatii

1. Daca graful G nu este conex parcurgând BFS fiecare componenta conexa obtinem o padure, formata din arborii partiali corespunzatori fiecarei componente conexe.

2. Complexitatea algoritmului, în cazul în care graful este reprezentat prin listele de adiacenta, este de O(n+m), unde n este numarul de vârfuri, iar m numarul de muchii din graf.

5.3. Arbori partiali DFS

O alta tehnica de parcurgere (explorare) a grafurilor este metoda Depth-First-Search (parcurgerea în adâncime).

Dat fiind G un graf conex si un nodul sursa s vizitam 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 nevizitati ne întoarcem sa cercetam daca nodul din care a fost atins x mai are sau nu vecini nevizitati si continuam parcurgerea.

De exemplu, pentru graful din figura 13, parcurgerea dupa metoda DFS, cu nodul initial s = 6, determina urmatoarea ordine de vizitarea a nodurilor: 6, 4,2,1,3,7,5,8,9,10,11,12. Marcând muchiile utilizate prin vizitarea nodurilor obtinem un arbore partial numit arbore partial DFS, cu radacina 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.

Observatie

Reprezentarea informatiilor se face în acelasi mod ca la parcurgerea BFS, în plus vectorul V fiind de asemeni variabila globala.

Algoritmul poate fi descris folosind o procedura recursiva DFS, pe care initial o apelam cu parametrul s, astfel:

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 adiacenta a vârfului x

begin

i := q^.inf;

if not V[i] then //i este nevizitat

begin

T[i] := x; //retin vârful din care a fost atins i

DFS(i); //parcurge vârfurile nevizitate începând cu i

end;

q := q^.urm

end;

end;

Observatii

1. Daca graful G nu este conex parcurgând DFS fiecare componenta conexa obtinem o padure, formata din arborii partiali corespunzatori fiecarei componente conexe.

2. Complexitatea algoritmului, în cazul în care graful este reprezentat prin listele de adiacenta este de O(n+m), unde n este numarul de vârfuri, iar m numarul de muchii din graf.

5.4. Aplicatie. Descompunerea unui graf în componente biconexe

Definitie

Fie G = (X, U) un graf neorientat conex. Vârful v X se numeste punct de articulatie daca subgraful obtinut prin eliminarea vârfului v si a muchiilor incidente cu acesta nu mai este conex.

De exemplu, pentru graful din figura 16 punctele de articulatie sunt 1,3,5,7.

Fig. 16.

Definitie

Un graf se numeste biconex daca nu are puncte de articulatie.

În multe aplicatii practice, ce se pot modela cu ajutorul grafurilor, nu sunt de dorit punctele de articulatie. Revenind la exemplul cu reteaua de telecomunicatii, daca o centrala dintr-un punct de articulatie se defecteaza rezultatul este nu numai întreruperea comunicarii cu centrala respectiva ci si cu alte centrale.

Definitie

O componenta biconexa a unui graf este un subgraf biconex maximal cu aceasta proprietate.

De exemplu, pentru graful din figura 16 componentele biconexe sunt:

Fig. 17.

Pentru a descompune graful în componente biconexe vom utiliza proprietatile parcurgerii DFS. Parcurgând graful DFS putem clasifica muchiile grafului în:

-muchii care apartin arborelui partial DFS (tree edges);

-muchii [u, v] care nu apartin arborelui si care unesc vârful u cu un stramos al sau v în arborele partial 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 tinând cont de arborele partial DFS cu radacina 3:

Fig. 18.

Observam ca radacina arborelui partial DFS este punct de articulatie daca si numai daca are cel putin doi descendenti, între vârfuri din subarbori diferiti ai radacinii neexistând muchii. Mai mult, un vârf x oarecare nu este punct de articulatie daca si numai daca din orice fiu y al lui x poate fi atins un stramos al lui x pe un lant format din descendenti ai lui x si o muchie de întoarcere (un drum "de siguranta" între x si y).

Pentru fiecare vârf x al grafului putem defini :

dfn(x) numarul 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

Daca x este un stramos al lui y în arborele partial DFS atunci

dfn(x) < dfn(y).

De asemeni pentru fiecare vârf x din graf putem defini :

low(x) numarul de ordine al primului vârf din parcurgerea DFS ce poate fi atins din x pe un alt lant decât lantul unic din arborele partial DFS.

low(x) = min, min.

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

low(x)

2

1

1

0

1

5

5

5

8

9

Deci putem caracteriza mai exact punctele de articulatie dintr-un graf astfel:

x este punct de articulatie daca si numai daca este radacina unui arbore partial DFS cu cel putin doi descendenti sau, daca nu este radacina, are un fiu y astfel încât low(y) dfn(x).

Pentru exemplul din figura 16:

- nodul 3 este punct de articulatie deoarece este radacina arborelui partial DFS si are doi descendenti,

- nodul 7 este punct de articulatie deoarece low(8)=8 dfn(7)=7,

- nodul 5 este punct de articulatie deoarece low(6)=5 dfn(5)=5,

- nodul 1 este punct de articulatie deoarece low(0)=2 dfn(1)=1.

Modificam procedura DFS, pentru a calcula pentru fiecare vârf din graf valorile dfn si low.

Intrare:-graful G, reprezentat prin listele de adiacenta;

Iesire:-vectorii dfn si low.

Utilizam o variabila globala num, pentru a calcula numarul de ordine al vârfului curent în parcurgerea în adâncime.

Initializare

num := 0;

for x := 1 to n do dfn[x] := -1;

DfnLow(s, -1) // s este radacina arborelui partial DFS

procedure DfnLow(u, tu: Vf);

//parcurge DFS graful G începând cu vârful u, calculând

// valorile dfn[u] si low(u); tu este tatal 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 adiacenta 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]);

//functia min returneaza minimul argumentelor ei

end

else

if x tu then low[u] := min(low[u], dfn[x]);

q := q^.urm;

end

end;

Vom folosi aceasta idee de calcul recursiv al valorilor dfn si low pentru descompunerea în componente biconexe a unui graf neorientat conex.

Reprezentarea informatiilor se face în acelasi mod ca la parcurgerea DFS, dar vectorul V nu mai este necesar, pentru vârfurile nevizitate dfn fiind -1. În plus, vom folosi o stiva S în care vom retine muchiile din graf (atât cele care apartin arborelui cât si cele de întoarcere) în ordinea în care sunt întâlnite în timpul parcurgerii. Atunci când identificam o componenta biconexa, mai exact identificam un nod u care are un fiu x astfel încât low(x) dfn(u), eliminam din stiva si afisam toate muchiile din componenta biconexa respectiva.

Initializare

num := 0;

S (s, -1)

//stiva este initializata cu o muchie fictiva [s,-1], s fiind sursa

for x := 1 to n do dfn[x] := -1;

Biconex(s, -1) // s este radacina arborelui partial DFS

procedure biconex(u, tu: Vf);*

//afiseaza componentele biconexe , parcurgând graful începând

// cu vârful u; tu este tatal 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 adiacenta a lui u

begin

x := q^.inf;

if (tu x) and (dfn[x] < dfn[u]) then S [u, x];

//daca tu x sau dfn(x)>dfn(u) muchia (u,x) a fost deja

//retinuta în stiva

if dfn[x] -1 then //x este nevizitat

begin

Biconex(x, u)

low[u] := min(low[u], low[x]);

if low[x] dfn[u] then

//am identificat o noua componenta biconexa

Afisare(x, u) // extrage din S si afiseaza muchiile //componentei biconexe curente

else

if x tu then low[u] := min(low[u], dfn[x]);

end;

q := q^.urm;

end;

end;

Observatie

Oricare doua componente biconexe au cel mult un vârf comun, deci nici o muchie nu poate fi în doua componente biconexe.

Sa urmarim executia algoritmului pe urmatorul exemplu:

Fig. 19.

Arborele partial DFS este:

Fig. 20.

Initial:

 

x

 

dfn(x)

 

low(x)

num

 

S:

Apelez biconex(3, -1)

x

dfn(x)

low(x)

num

x

S:

Apelez biconex(1, 3)

x

dfn(x)

low(x)

num

x

S:

Apelez biconex(0, 1):

x

dfn(x)

low(x)

num

x

Revin în biconex(1, 3)

low[1] min(low[1], low[0]) low[1]

low[0] > dfn[3]. Am obtinut o componenta biconexa, afisez [1,0].

S:

x

S:

Apelez biconex(2, 1)

x

dfn(x)

low(x)

num

x

x

S:

Apelez biconex(4, 2)

x

dfn(x)

low(x)

num

x

x

S:

low[4] min(low[4], dfn[3])

x

dfn(x)

low(x)

Revin în biconex(2, 1)

low[2] min(low[2], low[4])

x

dfn(x)

low(x)

Revin în biconex(1, 3)

low[1] min(low[1], low[2])

x

dfn(x)

low(x)

low[1] min(low[1], dfn[2])

Revin în biconex(3, -1)

low[3] min(low[3], low[1]) low[1] dfn[3].

Am obtinut o noua componenta biconexa: [4,3], [2,4], [1,2], [3,1].

S:

x

low[3] min(low[3], dfn[4])

x

S:

Apelez biconex(5, 3)

x

dfn(x)

low(x)

num

x

x

S:

Apelez biconex(6, 5)

x

dfn(x)

low(x)

num

x

x

S:

Apelez biconex(7, 6)

x

dfn(x)

low(x)

num

x

S:

low[7] min(low[7], dfn[5])

x

dfn(x)

low(x)

x

Revin în biconex(6, 5)

low[6] min(low[6], low[7])

x

dfn(x)

low(x)

low[6] min(low[6], dfn[7])

Revin în biconex (5, 3)

low[5] min(low[5], low[6])

low[6] > dfn[5], deci afisez o noua componenta biconexa: [7,5],[6,7],[5,6].

S:

Revin în biconex(3, -1)

low[3] min(low[3], low[5])

low[5] dfn[3], deci afisez o noua componenta biconexa: [5,3].

S:

Stop

Teorema

Procedura biconex(s,-1) genereaza componentele biconexe ale grafului conex G.

Demonstratie:

Vom lua în considerare cazul în care n, numarul de vârfuri din G, este cel putin 2 (daca n 1, G are o singura componenta biconexa, formata dintr-un singur vârf).

Vom face demonstratia prin inductie dupa B, numarul de componente biconexe.

P(1) B graful este biconex: radacina s a arborelui partial DFS va avea un singur fiu, sa-l notam x. Apelul biconex(x, s) a explorat toate muchiile grafului si le-a introdus în stiva S. Cum x este singurul vârf pentru care low[x] dfn[s], muchiile grafului vor fi afisate într-o singura componenta biconexa.

P(B) Sa presupunem acum ca algoritmul functioneaza corect pentru toate grafurile conexe cu cel mult B componente biconexe.

P(B+1) Vom demonstra ca algoritmul functioneaza pentru toate grafurile conexe cu cel mult B 1 componente biconexe.

Fie G un graf cu B 1 componente biconexe si fie x primul vârf pentru care este îndeplinita conditia low[x] dfn[u]. Pâna în acest moment nu a fost identificata nici o componenta biconexa. În urma apelului biconex(x, u), toate muchiile incidente cu descendenti ai lui x se afla în stiva S, deasupra muchiei [u, x]. Cum x este primul vârf care îndeplineste conditia low[x] dfn[u], deducem ca descendentii lui x nu sunt puncte de articulatie. Cum u este punct de articulatie, rezulta ca muchiile situate în S, deasupra muchiei [u, x], inclusiv, formeaza o componenta biconexa. Muchiile acestei componente biconexe sunt extrase din stiva si afisate, deci, de la acest pas algoritmul revine la aflarea componentelor biconexe ale unui graf G', obtinut din G prin eliminarea unei componente biconexe. Cum G' are B componente biconexe, conform ipotezei inductive, algoritmul determina corect componentele biconexe ale lui G'.

Q.E.D.

Observatie

Algoritmul de descompunere în componente biconexe a unui graf reprezentat prin listele de adiacenta este liniar (O(n m)), unde n este numarul de vârfuri, iar m numarul de muchii din graf.

5.5. Problema rezolvata

Problema b rfei

-Baraj, Arad 1992-

Se considera n persoane P1, P2, ..., Pn care doresc fiecare sa transmita propria b rfa celorlalte persoane. Numim instructiune o pereche (i, j) av nd urmatorul efect: persoana Pi transmite persoanei Pj propria sa b rfa, dar si eventualele b rfe primite anterior prin instructiuni de la alte persoane. Din pacate anumite perechi de persoane, citite de la intrare, se dusmanesc si deci nu comunica între ele. Sa se determine, daca este posibil, o secventa de instructiuni prin care fiecare persoana sa cunoasca b rfele tuturor celorlalte persoane.

Solutie

Asociem problemei un graf neorientat astfel:

-multimea v rfurilor este formata din cele n persoane.

-între doua v rfuri exista muchie daca cele doua persoane corespunzatoare comunica.

Problema are solutie daca si numai daca graful asociat problemei este conex.

Daca graful este conex, atunci admite un arbore partial. Determinam un arbore partial DFS al grafului dat, cu radacina 1. Parcurg nd acest arbore în postordine, toate b rfele vor fi receptionate de v rful 1, fiecare muchie utilizata reprezent nd o instructiune. Parcurg nd arborele în preordine, nodul 1 va transmite b rfele tuturor celorlalte persoane. Sunt astfel necesare 2n-2 instructiuni.

Observatie

Numarul de 2n-2 instructiuni este optim.

Reprezentarea informatiilor

-Reprezentam graful prin matricea de adiacenta:

-Reprezentam arborele partial DFS ca pe un arbore binar, utiliz nd reprezentarea fiu-frate.

-Utilizam un vector v de dimensiune n, pentru a marca daca un v rf a fost sau nu atins în timpul parcurgerii DFS:

program barfa;

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);

var i: Pers; q, u: Arbore;

begin

u := nil;

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

x^.fiu := q

else

u^.frate := q;

u := q;

DFS(q);

end;

end;

procedure postordine(x: Arbore);

var q: Arbore;

begin

q := x^.fiu;

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

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

writeln(fout,'Nu exista solutii!')

else

begin

postordine(A);

preordine(A);

end;

close(fout);

end.

Exercitii

1.Demonstrati prin inductie dupa numarul de vârfuri din graf ca algoritmul lui Prim produce un arbore partial de cost minim.

2. Fie G = (X, U) un graf neorientat conex. Muchia [x, y] se numeste punte daca prin eliminarea ei din graf, graful partial obtinut nu este conex. Scrieti un algoritm care determina toate puntile unui graf conex. Algoritmul sa functioneze în timp de O(n+m) (n = x , m = U

Observatie

O muchie este punte daca si numai daca nu apartine unui ciclu.

3. Fie G = (X, U) un graf neorientat conex cu functia pondere asociata c: U R U X

a). Fie T un arbore partial de cost minim pentru G. Demonstrati ca exista o muchie [u, v] T si [x, y] T astfel încât T- este al doilea cel mai bun arbore partial de cost minim (SAPM).

b). Fie T un arbore partial pentru G. Pentru oricare doua vârfuri u, v X definim max(u, x) ca fiind o muchie de pondere maxima de pe lantul unic între u si v în T.

Descrieti un algoritm cu timpul de executie de O( X ) astfel încât, dat T, sa calculeze max(u, v), " u, v X.

4. Fie G = (X, U) un graf neorientat conex. Numim distanta dintre vârfurile x si y, notata d(x, y), numarul de muchii al celui mai scurt lant care uneste x cu y.

Notam: e(x) = excentricitatea vârfului x:

d(G) = diametrul grafului G

r(G) = raza grafului G

c(G) = centrul grafului G

a). Demonstrati ca c(G) este format dintr-un vârf sau din doua vârfuri adiacente.

b). Aratati ca d(G) 2*r(G).

c). Descrieti un algoritm care sa calculeze raza, diametrul si centrul unui graf conex dat.

5. Arbori partiali DS

O alta tehnica de parcurgere în adâncime a nodurilor unui graf este metoda Depth-Search. Aceasta metoda îmbina cele doua metode de parcurgere prezentate, în sensul ca urmeaza acelasi algoritm ca metoda Breadth-First-Search numai ca utilizeaza o stiva, ca si metoda Depth-First-Search.

De exemplu, pentru graful din figura 13, parcurgerea dupa metoda DS, cu nodul initial s = 6, determina urmatoarea ordine de vizitare a nodurilor: 6, 4, 5, 8, 9, 10, 11, 12, 7, 3, 2, 1. Marcând muchiile utilizate prin vizitarea nodurilor obtinem un arbore partial numit arbore partial DS, cu radacina 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.

Scrieti un program care realizeaza parcurgerea în adâncime dupa metoda DS a unui graf, cu obtinerea arborelui partial DS.

6. Problema sponsorului (Olimpiada Nationala de Informatica, Suceava 1996)

RATUC Suceava, unul dintre sponsorii olimpiadei, îsi propune sa îmbunatateasca transportul în comun în localitate. Directorul va pune la dispozitie o schema pe care sunt reprezentate statiile, numerotate pentru simplificare, de la 1 la n si cele k linii directe între statii, astfel încât între oricare doua statii exista legatura, eventual cu schimbarea mijlocului de transport. Trebuie sa determinati daca exista cel putin o linie directa prin blocarea careia legatura, directa sau indirecta, între cel putin doua statii sa fie întrerupta. Daca astfel de linii exista, sa se propuna înfiintarea unui numar cât mai mic de linii directe între statiile existente astfel încât prin blocarea unei singure linii directe, oricare ar fi aceasta, circulatia între oricare doua statii sa fie posibila; se alege solutia pentru care suma ocolurilor pe traseele varianta (masurate în numar de linii directe) sa fie cât mai mica.


Anexa

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

begin

a[i] := j;

aux := [];

Nou := c[g[1, j]]; Vechi := c[g[2, j]];

for k := 1 to n do

if c[k] = Vechi then

begin

c[k] := Nou;

aux := aux+[k];

end;

ConstrArb(i+1);

for k := 1 to n do

if k in aux then c[k] := Vechi;

end

end;

begin

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

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

Initializare;

ConstrHeap;

while (NrMSel < n) and (m > 0) do

begin

k := g[1];

g[1] := g[m];

dec(m);

CombHeap(1, m);

if c[k.e1] <> c[k.e2] then

begin

inc(NrMSel);

a[NrMSel] := k;

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);

for j := 1 to nrv do

begin

read(fin, k, c);

G[i,k] := c;

end;

readln(fin);

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;

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

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;

NodLista = record

inf: Vf;

urm: Lista;

end;

Graf = array[Vf] of Lista;

Arbore = array[Vf] of Vf;

var n: Vf;

s: Vf;

G: Graf;

AB, AD: Arbore;

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

begin

if not V[q^.inf] then

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;

var C, SfC: Coada;

q: Lista; x: Vf;

p: Coada;

begin

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;

q := G[x];

while q <> nil do

begin

if not V[q^.inf] then

begin

V[q^.inf] := true; AB[q^.inf] := x;

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);

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

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

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);

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);

var p: Lista; q: Stiva; x: Vf;

begin

dfn[u] := num; low[u] := dfn[u]; inc(num);

p := G[u];

while p <> nil do

begin

x := p^.v;

if (x <> tu) and (dfn[x] < dfn[u])then

begin

new(q); q^.f := x; q^.t := u;

q^.urm := S; S := q;

end;

if dfn[x] < 0 then

begin

Biconex (x, u);

low[u] := min(low[u], low[x]);

if low[x] >= dfn[u] then

Afisare_Comp_BiconexA(x,u);

end

else

if x <> tu then low[u] := min(low[u], dfn[x]);

p:=p^.leg

end;

end;

begin

Initializare;

Biconex(sursa, -1);

readln

end.



Programul Generare-Arbori-Partiali de la sf rsitul capitolului curent genereaza toti arborii partiali ai unui graf conex dat.

Programul Kruskal de la sf rsitul capitolului curent genereaza un arbore partial de cost minim al unui graf conex, utiliz nd algoritmul lui Kruskal.

Programul Prim de la sf rsitul capitolului curent genereaza un arbore partial de cost minim al unui graf conex, utiliz nd algoritmul lui Prim.

Programul Arbori-Partiali-BF-DF de la st rsitul capitolului curent genereaza arborii partiali BFS si DFS ai unui graf conex dat.

Programul Componente-Biconexe de la sf rsitul capitolului curent realizeaza descompunerea n componente biconexe a unui graf neorientat conex.


Document Info


Accesari: 7175
Apreciat: hand-up

Comenteaza documentul:

Nu esti inregistrat
Trebuie sa fii utilizator inregistrat pentru a putea comenta


Creaza cont nou

A fost util?

Daca documentul a fost util si crezi ca merita
sa adaugi un link catre el la tine in site


in pagina web a site-ului tau.




eCoduri.com - coduri postale, contabile, CAEN sau bancare

Politica de confidentialitate | Termenii si conditii de utilizare




Copyright © Contact (SCRIGROUP Int. 2024 )