Documente Academic
Documente Profesional
Documente Cultură
Dată fiind o mulţime finită U şi S={S1, S2, ... , Sk} o partiţie a mulţimii U, problema constă în a proiecta o
structură de date care să permită executarea eficientă a următoarelor două operaţii fundamentale:
– Find(x): determină mulţimea SiS căreia îi aparţine elementul x, xU;
– Union(Si,Sj): reuneşte mulţimea Si cu mulţimea Sj, obţinându-se un nou element al mulţimii S, ce va
înlocui Si şi Sj.
Observaţii
1. Această problemă este cunoscută şi sub denumirea de problema claselor de echivalenţă (x, y se vor numi
echivalente dacă aparţin aceleiaşi mulţimi SiS, sau, altfel spus, Find(x)=Find(y)), conceptele de relaţie
de echivalenţă şi partiţie reprezentând două abordări diferite ale aceleiaşi structuri matematice.
2. Dat fiind un graf neorientat, componentele conexe ale grafului constituie o partiţie a mulţimii vârfurilor
grafului. Un algoritm de determinare a componentelor conexe ale unui graf neorientat poate fi formulat cu
ajutorul operaţiilor Union–Find astfel:
Pentru i1, 2, ... , n, Si = {i};
Pentru [i, j] muchie în graf
A=Find(i); B=Find(j);
Dacă AB atunci Union(A, B);
O soluţie eficientă este reprezentarea mulţimilor disjuncte cu ajutorul arborilor cu rădăcină. Fiecare arbore
reprezintă o mulţime, iar fiecare nod din arbore un element al mulţimii. Rădăcina arborelui va fi elementul
reprezentativ al mulţimii.
Exemplu
Să considerăm n=10 şi o partiţie a mulţimii {1, 2, …, 9, 10} formată din S1={1, 7, 8, 9}, S2 = {2, 5, 10},
S3={3, 4, 6}. Partiţia S={S1, S2, S3} poate fi reprezentată ca o pădure formată din trei arbori:
1 2 3
7 8 9 5 10 4 6
Reprezentăm arborii prin referinţe ascendente, deci pentru fiecare nod din arbore, cu excepţia rădăcinii,
reţinem legătura spre părintele său: tata(x)= nodul părinte al lui x, sau 0, dacă x este rădăcina arborelui.
Operaţia Find(x) constă în a determina rădăcina arborelui al cărui nod este x.
int Find(int x)
{ while (tata[x]) x=tata[x];
return x; }
Operaţia Union(i,j) se poate realiza transformând unul dintre arbori în subarborele celuilalt. Reuniunea
S1S2 poate fi reprezentată în două moduri:
1 2
sau
2 7 8 9 5 10 1
5 10 7 8 9
Exemplu
Să considerăm partiţia S={{1}, {2}, ..., {n}}. Configuraţia iniţială constă dintr-o pădure în care fiecare
arbore este format doar din rădăcină: tata(i)=0, i1, 2, ..., n. Să considerăm acum următoarea secvenţă
de operaţii Union şi Find: Union(1,2), Find(1), Union(2,3), Find(1), Union(3,4), Find(1), ...,
Find(1), Union(n-1,n). Această secvenţă conduce la un arbore degenerat. Cum timpul necesar operaţiei
Union este constant, cele n-1 operaţii Union au timpul de execuţie de O(n). Dar fiecare operaţie Find(1)
parcurge tot drumul de la nodul 1 până la rădăcină. Cum timpul de execuţie necesar operaţiei Find pentru un
nod de pe nivelul i este O(i), obţinem un timp de execuţie de O(n2) pentru cele n-2 operaţii Find(1).
Putem îmbunătăţi eficienţa operaţiilor Union şi Find, aplicând o regulă de ponderare pentru operaţiile
Union.
Regula de ponderare
Dacă numărul de niveluri din arborele cu rădăcina i este mai mic decât numărul de niveluri din arborele cu
rădăcina j, atunci j va deveni părintele lui i, altfel i va deveni părintele lui j.
Exemplu
Utilizând regula de ponderare secvenţa de operaţii Union şi Find din exemplul precedent va conduce la
următorii arbori:
Iniţial 1 2 … n
Union(1,2) 2 3 … n
Union(2,3) 2 4 … n
… 1 3
Union(n-1,n) 2
1 3 4 … n
În acest caz timpul de execuţie necesar operaţiilor Find este O(1), deoarece arborii obţinuţi au înălţimea
cel mult egală cu 1. Totuşi, există şi cazuri defavorabile.
Lemă
Dacă A este un arbore cu n noduri obţinut în urma aplicării operaţiei Union folosind regula de ponderare,
înălţimea arborelui este cel mult egală cu [log n].
Demonstraţie
Vom proceda prin inducţie după n, numărul de noduri. Pentru n=1, rezultatul este evident. Presupunem că
afirmaţia este adevărată pentru arbori cu i noduri (1in-1), obţinuţi în urma aplicării algoritmului Union.
Să demonstrăm că înalţimea oricărui arbore cu n noduri An, obţinut în urma aplicării algoritmului Union este
cel mult egală cu [log n].
Fie Union(j,k) ultima operaţie Union executată pentru obţinerea arborelui An. Notăm cu m numărul de
noduri din arborele cu rădăcina j. Arborele cu rădăcina k are m-n noduri. Putem presupune, fără a restrânge
1 4 6 8
3 5 7
1 6 4 1 6 4
2 3 5 2 3 5
Teorema 1
Condiţia necesară şi suficientă ca un graf să conţină cel puţin un arbore parţial este ca graful să fie conex.
Demonstraţie
Necesitate. Presupunem că graful admite un arbore parţial. Arborele parţial este conex şi adăugând muchiile
care sunt în graf dar nu sunt în arborele parţial el rămâne conex. Deci graful este conex.
Suficienţa. Presupunem că graful este conex. Dacă graful este conex minimal, el este arborele parţial căutat.
Altfel, există o muchie [x,y] astfel încât graful parţial G1 obţinut prin eliminarea muchiei [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 arborele parţial căutat.
Teorema 2
Fie G un graf conex cu n vârfuri şi m muchii. Numărul de muchii ce trebuie eliminate pentru a obţine un
arbore parţial este m-n+1 (acesta se numeşte numărul ciclomatic al grafului).
Demonstraţie
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 dintre componentele conexe ale lui G' este un arbore. Să notăm cu p numărul componentelor
conexe, ni numărul de vârfuri din componenta conexă i, i{1, 2, ..., p} şi 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 muchii din G' este (n1-1)+(n2-1)+…+(np-1)=n-p. 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.
2
1 4
5 3
9 1
2 3 5 6
7 3 1
2 3 5 6
2 3 5 6
1 4
1
2 3 5 6
1
Prin selectarea muchiei [5,6] s-au unificat componentele conexe ale extremităţilor ei (vârfurile 4, 5 şi 6
formează acum o singură componentă conexă). Selectăm următoarea muchie de cost minim (muchia [1,4]
de cost 2):
2
1 4
1
2 3 5 6
1
Graful are acum 3 componente conexe. Selectăm următoarea muchie de cost minim, muchia [3,4], de
cost 3.
2
1 4
3
1
2 3 5 6
1
Următoarea muchie de cost minim ar fi muchia [3,5]. Dar prin selectarea acestei muchii s-ar forma un
ciclu (deoarece extremităţile muchiei, vârfurile 3 şi 5, sunt în aceeaşi componentă conexă). Prin urmare, nu
vom selecta muchia [3,5], vom analiza următoarea muchie de cost minim, muchia [1,3]. Şi această muchie
ar forma un ciclu dacă ar fi selectată, prin urmare o ignorăm şi selectăm următoarea muchie de cost minim,
muchia [2,3], de cost 7.
2
1 4
3
1
2 3 5 6
7 1
În acest moment sunt selectate 5 muchii care nu formează cicluri, deci am obţinut un arbore parţial. În plus,
acest arbore este de cost minim.
Implementarea algoritmului lui Kruskal
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ă care 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 defini un struct denumit Muchie în care reţinem extremităţile unei muchii şi costul acesteia.
Supraîncărcăm operatorul > pentru compararea muchiilor (deoarece dorim să organizăm muchiile ca un
min-heap).
2. În coada cu prioritate H reţinem muchiile grafului, organizate ca un min-heap în funcţie de cost.
struct Muchie
{int x, y;
double cost;
friend bool operator >(const Muchie&,const Muchie&);
};
bool operator >(const Muchie& m1, const Muchie& m2)
{ return m1.cost>m2.cost; }
void Citire();
void Afisare();
int Find (int);
void Union(int, int);
int main()
{int c1, c2;
Muchie m;
Citire();
while (sol.size()<n-1)
//cat timp nu am selectat cele n-1 muchii
{//extrag din H muchia de cost minim
m=H.top(); H.pop();
//determin componentele conexe din care fac parte
//extremitatile muchiei selectate m
c1=Find(m.x); c2=Find(m.y);
if (c1!=c2)
//m nu formeaza cicluri cu muchiile selectate
{//selectez aceasta muchie
costmin+=m.cost; sol.push_back(m);
//unific compoenentele conexe ale extremitatilor
Union(c1, c2); }
}
Afisare();
return 0;
}
void Citire()
{Muchie m;
int i, nrm;
FILE * fin=fopen("kruskal.in", "r");
fscanf(fin,"%d %d", &n, &nrm);
void Afisare()
{FILE * fout=fopen("kruskal.out", "w");
fprintf(fout,"%.2lf\n", costmin);
for (int i=0; i<n-1; i++)
fprintf(fout,"%d %d\n",sol[i].x,sol[i].y);
fclose(fout);
}
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.
Presupunem prin reducere la absurd că arborele obţinut nu este de cost minim, deci există A'=(a1', a2',
...., an-1') un alt arbore parţial, astfel încât c(A')<c(A).
Fie k = min{i | 1in, aiai'}, primul indice de la care A şi A' diferă. 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 mulţimea
{ai'... an-1'}. Elimin una dintre 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(ai)-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.
2 3 5 6
7 3 1
Iniţial:
3
Selectăm un vârf care este incident cu o muchie de cost minim, muchie incidentă cu vârful 3 (vârful 5 şi
muchia [3,5]).
3 5
3
Selectăm un vârf care este incident cu o muchie de cost minim, muchie incidentă cu unul dintre vârfurile
deja selectate (vârful 6 şi muchia [5,6]).
3 5 6
3 1
Selectăm din nou un vârf incident cu o muchie de cost minim, incidentă cu unul dintre vârfurile subgrafului
construit la pasul anterior (vârful 4 şi muchia [4,6]):
4
1
3 5 6
3 1
Selectând încă un vârf incident cu o muchie de cost minim, muchie care are o extremitate în mulţimea
vârfurilor deja selectate (vârful 1 şi muchia [1,4]).
2
1 4
1
3 5 6
3 1
În sfârşit, selectăm vârful 2 şi muchia [2,3] (muchia de cost minim care are o extremitate 2 şi cealaltă
extremitate în mulţimea vârfurilor deja selectate).
2
1 4
1
2 3 5 6
7 3 1
class ComparVf
{public:
void Citire();
void Prim();
void Afisare();
int main()
{
Citire();
Prim();
Afisare();
return 0;
}
void Citire()
{int i, m, x, y, c;
FILE * fin=fopen("apm.in","r");
fscanf(fin,"%d %d", &n, &m);
for (i=0; i<m; i++)
{ fscanf(fin,"%d %d %d", &x, &y, &c);
G[x].push_back(make_pair(y,c));
G[y].push_back(make_pair(x,c));
}
}
void Prim()
{int i, vfmin, nrsel;
for (i=1; i<=n; i++) {cmin[i]=INF; p[i]=r;}
cmin[r]=0; p[r]=0;
H.push(r); Z[r]=1; nrsel=1;
while (!H.empty() && nrsel<n-1)
{vfmin=H.top(); H.pop();
Z[vfmin]=1; nrsel++; //selectam acest varf
for (i=0; i<G[vfmin].size(); i++)
//parcurg lista de adiacenta a lui vfmin
if (!Z[G[vfmin][i].first])
if (cmin[G[vfmin][i].first] >
G[vfmin][i].second)//optimizez
{ cmin[G[vfmin][i].first] = G[vfmin][i].second;
p[G[vfmin][i].first]=vfmin;
H.push(G[vfmin][i].first);
}
}
}
void Afisare()
{ int i;
int cost=0;
FILE * fout=fopen("apm.out","w");
for (i=1; i<=n; i++) cost += cmin[i];
fprintf(fout,"%d\n%d\n",cost,n-1);
for (i=1; i<=n; i++)
if (i!=r)
fprintf(fout,"%d %d\n", i, p[i]);
fclose(fout);
}
0. https://infoarena.ro/problema/disjoint
1.https://infoarena.ro/problema/apm
2. Urgenţa
http://campion.edu.ro/arhiva/index.php?page=problem&action=view&id=960
3. Flood
http://campion.edu.ro/arhiva/index.php?page=problem&action=view&id=310
4.Entries
http://campion.edu.ro/arhiva/index.php?page=problem&action=view&id=1227
5. Desen
https://infoarena.ro/problema/desen