Tematică științifică · 5.8
Arbori. Arbori parțiali de cost minim
Arborele ca graf conex fără cicluri, teorema de caracterizare și proprietățile arborilor; arborele parțial și arborele parțial de cost minim; algoritmii lui Kruskal și Prim, cu exemplificarea etapelor și programe în C++ pentru verificarea unui arbore, determinarea unui arbore parțial și a arborelui parțial de cost minim; subiectele date la examen.
Pentru a lega între ele toate casele unui sat printr-o rețea de cablu se folosesc cât mai puține legături: fiecare casă trebuie să fie conectată, iar o legătură care închide un ciclu poate lipsi fără ca vreo casă să rămână izolată. Graful unei asemenea rețele este un arbore. Dacă fiecare legătură posibilă are un cost, rețeaua cea mai ieftină corespunde unui arbore parțial de cost minim al grafului legăturilor posibile.
Lecția prezintă:
- arborele, teorema de caracterizare și proprietățile arborilor;
- arborele parțial și determinarea lui printr-o parcurgere;
- arborele parțial de cost minim și algoritmii lui Kruskal și Prim.
Noțiuni preliminare
Arborii se definesc cu ajutorul următoarelor noțiuni:
- graful neorientat este o pereche
G = (X, U), în careXeste o mulțime finită și nevidă de noduri, iarUo mulțime de muchii; - lanțul elementar este un lanț în care nodurile sunt distincte;
- ciclul este un lanț simplu ale cărui extremități coincid;
- graful este conex dacă între oricare două noduri distincte există un lanț;
- graful parțial al lui
Gare aceleași noduri caGși o parte dintre muchiile lui.
Terminologia completă este în subcapitolul Grafuri orientate și neorientate. Terminologie, iar conexitatea în subcapitolul Conexitate și tare conexitate.
Arborele
Definiția arborelui
Un arbore este un graf neorientat conex și fără cicluri. Manualele îl numesc și arbore liber, pentru a-l deosebi de arborele cu rădăcină.
Exemplu. Graful G15 are nodurile X = {1, 2, …, 8} și muchiile
U = {[1,2], [1,3], [2,4],
[2,5], [3,6], [6,7],
[6,8]}
Graful este conex și nu conține cicluri, deci este arbore. Are 8 noduri și 7 muchii. Nodurile 4, 5, 7 și 8 au gradul 1: sunt noduri terminale.
G15Arborele desenat are nodul 1 sus, dar definiția nu privilegiază niciun nod: oricare nod poate fi desenat în partea de sus. Arborele în care se alege un nod numit rădăcină este prezentat în subcapitolul Arbori cu rădăcină și arbori binari.
Contraexemple. Graful din stânga este conex, dar conține ciclul (1, 2, 3, 4, 1).
Graful din dreapta nu conține cicluri, dar nu este conex. Niciunul nu este arbore.
Teorema de caracterizare
Teoremă. Fie G un graf neorientat cu n noduri și m muchii. Următoarele
afirmații sunt echivalente:
Geste arbore (definiția:Geste conex și nu conține cicluri);Gnu conține cicluri și arem = n − 1muchii;Geste conex și arem = n − 1muchii;Gnu conține cicluri și este maximal cu această proprietate: dacă se leagă printr-o muchie nouă două noduri neadiacente, graful obținut are un ciclu;Geste conex și este minimal cu această proprietate: prin eliminarea oricărei muchii graful devine neconex;- oricare două noduri ale lui
Gsunt legate printr-un lanț elementar și numai unul.
Oricare dintre afirmații se poate lua ca definiție a arborelui. Justificările de mai jos arată ideile pe care se sprijină echivalențele.
Un arbore cu n noduri are n − 1 muchii. Un arbore cu cel puțin 2 noduri are un
nod terminal: altfel, pornind dintr-un nod și mergând de fiecare dată pe o muchie
nefolosită, s-ar ajunge a doua oară într-un nod, deci s-ar forma un ciclu. Eliminând un
nod terminal și muchia lui, se obține un arbore cu un nod mai puțin. Repetând eliminarea
până rămâne un singur nod, se elimină n − 1 noduri și tot atâtea muchii.
Lanțul dintre două noduri este unic. Arborele este conex, deci lanțul există. Dacă
între nodurile x și y ar exista două lanțuri elementare diferite, porțiunile prin care
ele diferă ar forma împreună un ciclu.
Minimalitate și maximalitate. Dacă se elimină muchia [x, y], singurul lanț dintre
x și y dispare, deci graful devine neconex. Dacă se adaugă muchia [x, y], ea
formează un ciclu cu lanțul care exista deja între x și y.
Proprietăți ale arborilor
Din teorema de caracterizare rezultă:
- orice arbore cu
n ≥ 2noduri are cel puțin două noduri terminale; - adăugarea unei muchii la un arbore formează exact un ciclu;
- un graf fără cicluri cu
nnoduri șipcomponente conexe aren − pmuchii, fiindcă fiecare componentă este un arbore; - dintr-un graf conex cu
nnoduri șimmuchii trebuie eliminatem − n + 1muchii pentru a se obține un arbore.
Condiția m = n − 1 singură nu este suficientă: graful cu nodurile {1, 2, 3, 4, 5} și
muchiile [1,2], [1,5], [2,5], [3,4] are 4 muchii, dar conține ciclul (1, 2, 5, 1) și
nu este conex.
Verificarea unui arbore
Enunț. Se citește un graf neorientat cu n noduri și m muchii. Să se verifice dacă
graful este arbore.
Programul folosește afirmația 3 din teorema de caracterizare: graful este arbore dacă are
n − 1 muchii și este conex. Conexitatea se verifică printr-o parcurgere în adâncime
pornită din nodul 1.
#include <iostream>
using namespace std;
const int MAX = 101;
int a[MAX][MAX], viz[MAX], n, m;
void citire() {
int x, y;
cin >> n >> m;
for (int k = 1; k <= m; k++) {
cin >> x >> y;
a[x][y] = a[y][x] = 1;
}
}
void dfs(int x) {
viz[x] = 1;
for (int j = 1; j <= n; j++)
if (a[x][j] == 1 && viz[j] == 0)
dfs(j);
}
/* Graful este conex daca parcurgerea din nodul 1 viziteaza toate nodurile. */
int conex() {
dfs(1);
for (int i = 1; i <= n; i++)
if (viz[i] == 0)
return 0;
return 1;
}
int main() {
citire();
if (m != n - 1)
cout << "Graful nu este arbore: are " << m << " muchii, nu " << n - 1 << "\n";
else if (conex() == 0)
cout << "Graful nu este arbore: nu este conex\n";
else
cout << "Graful este arbore\n";
return 0;
}
Pentru arborele G15 programul afișează Graful este arbore. Pentru graful cu ciclu din
contraexemplu afișează Graful nu este arbore: are 4 muchii, nu 3, iar pentru graful cu
muchiile [1,2], [1,5], [2,5], [3,4] afișează Graful nu este arbore: nu este conex.
Descrierea soluției. Numărul muchiilor se compară direct cu n − 1. Numai dacă
această condiție este îndeplinită se apelează conex, care pornește parcurgerea dfs
din nodul 1 și verifică dacă toate nodurile au fost vizitate. Cu matricea de adiacență,
timpul este de ordinul O(n²). Rezolvarea subiectului de la Titularizare 2024 folosește
aceeași idee.
Arborele parțial
Definiție
Un arbore parțial al grafului G este un graf parțial al lui G care este arbore.
Arborele parțial păstrează toate nodurile grafului și n − 1 dintre muchii.
Teoremă. Un graf neorientat conține un arbore parțial dacă și numai dacă este conex.
Dacă graful conține un arbore parțial, el este conex, fiindcă arborele parțial leagă deja oricare două noduri. Invers, dacă graful este conex și conține un ciclu, se elimină o muchie a ciclului; graful rămâne conex, fiindcă nodurile muchiei eliminate sunt legate prin restul ciclului. Eliminarea se repetă până nu mai rămân cicluri, iar graful obținut este un arbore parțial.
Un graf conex are, în general, mai mulți arbori parțiali.
Determinarea unui arbore parțial
O parcurgere a unui graf conex vizitează fiecare nod o singură dată. Muchia pe care
parcurgerea ajunge prima dată într-un nod nevizitat face parte dintr-un arbore parțial:
se aleg astfel n − 1 muchii, câte una pentru fiecare nod în afară de nodul de pornire,
iar ele nu formează cicluri, fiindcă fiecare muchie aleasă duce într-un nod nou.
Exemplu. Graful ponderat G16, desenat în secțiunea următoare, are muchiile
[1,2], [1,3], [2,3], [2,4], [3,4], [3,5], [4,5], [4,6], [5,6], [5,7], [6,7].
Parcurgerea în lățime din nodul 1 alege muchiile
[1,2], [1,3], [2,4], [3,5], [4,6], [5,7].
Enunț. Se citește un graf neorientat conex. Să se afișeze muchiile unui arbore parțial.
#include <iostream>
using namespace std;
const int MAX = 101;
int a[MAX][MAX], viz[MAX], coada[MAX], n, m;
void citire() {
int x, y;
cin >> n >> m;
for (int k = 1; k <= m; k++) {
cin >> x >> y;
a[x][y] = a[y][x] = 1;
}
}
/* Parcurgerea in latime din nodul pl. Muchia [coada[p], j] pe care
nodul j intra in coada face parte din arborele partial. */
void BF(int pl) {
int p = 1, u = 1;
coada[1] = pl;
viz[pl] = 1;
while (p <= u) {
for (int j = 1; j <= n; j++)
if (a[coada[p]][j] == 1 && viz[j] == 0) {
u++;
coada[u] = j;
viz[j] = 1;
cout << "[" << coada[p] << "," << j << "] ";
}
p++;
}
}
int main() {
citire();
BF(1);
cout << "\n";
return 0;
}
Pentru graful G16 programul afișează [1,2] [1,3] [2,4] [3,5] [4,6] [5,7].
Descrierea soluției. Subprogramul BF este parcurgerea în lățime din subcapitolul
Parcurgerea grafurilor,
completată cu afișarea muchiei [coada[p], j] în momentul în care nodul j intră în
coadă. Parcurgerea în adâncime se folosește la fel: se afișează muchia [x, j] înaintea
apelului recursiv pentru j. Arborele parțial obținut depinde de parcurgere și de nodul
de pornire.
Arborele parțial de cost minim
Definiții
Se consideră un graf conex G = (X, U); fiecărei muchii u i se asociază un număr
pozitiv c(u), numit costul muchiei.
- Costul unui graf parțial
Heste suma costurilor muchiilor luiH. - Un arbore parțial de cost minim (APM) al lui
Geste un arbore parțial al luiGal cărui cost este cel mai mic dintre costurile tuturor arborilor parțiali.
Exemplu. Graful G16 are 7 noduri și 11 muchii, cu costurile scrise lângă muchii.
Arborele parțial obținut mai sus prin parcurgerea în lățime are costul
2 + 4 + 7 + 6 + 9 + 12 = 40. Arborele parțial de cost minim are costul 29.
G16Teoremă. Dintre grafurile parțiale conexe ale unui graf conex cu costuri pozitive, cel de cost minim este un arbore.
Dacă graful parțial conex de cost minim ar conține un ciclu, prin eliminarea unei muchii a ciclului s-ar obține un graf parțial tot conex, cu costul mai mic, ceea ce nu se poate. Prin urmare, problema legării tuturor nodurilor cu cost minim se reduce la determinarea unui arbore parțial de cost minim.
Arborele parțial de cost minim nu este, în general, unic: dacă mai multe muchii au același cost, pot exista mai mulți arbori parțiali cu același cost minim. Costul minim este însă unic.
Principiul algoritmilor
Algoritmii lui Kruskal și Prim folosesc metoda greedy: construiesc arborele muchie cu muchie și, la fiecare pas, aleg muchia cea mai ieftină care păstrează proprietățile cerute, fără a reveni asupra alegerii. Alegerea se sprijină pe următoarea proprietate.
Proprietate. Dacă nodurile grafului se împart în două mulțimi nevide, o muchie de cost minim dintre cele care au câte o extremitate în fiecare mulțime face parte dintr-un arbore parțial de cost minim.
Dacă un arbore parțial de cost minim T nu conține această muchie [x, y], lanțul din T
dintre x și y trece de la o mulțime la cealaltă pe o muchie [p, q], cu costul cel
puțin c([x, y]). Înlocuind în T muchia [p, q] cu [x, y], se obține tot un arbore
parțial, cu costul cel mult egal cu al lui T, deci tot de cost minim.
Cei doi algoritmi diferă prin felul în care aleg cele două mulțimi:
- Kruskal pornește de la
narbori formați din câte un nod și unește, la fiecare pas, doi arbori prin muchia cea mai ieftină care îi leagă; - Prim pornește de la un singur nod și adaugă, la fiecare pas, muchia cea mai ieftină care leagă arborele de un nod din afara lui.
Algoritmul lui Kruskal
Descrierea algoritmului
Graful se memorează prin lista muchiilor, ordonată crescător după cost. Pentru a ști în
ce arbore se află fiecare nod se folosește vectorul comp, cu comp[i] = numărul
arborelui care conține nodul i.
Etapele algoritmului:
- Muchiile se sortează crescător după cost.
- Fiecare nod formează un arbore separat:
comp[i] = i. - Muchiile se examinează în ordinea costurilor. Muchia
[x, y]se alege dacăcomp[x] ≠ comp[y], adică extremitățile ei sunt în arbori diferiți; altfel ar închide un ciclu și se respinge. - După alegerea muchiei, cei doi arbori se unesc: toate nodurile cu
compegal cucomp[y]primesc valoareacomp[x]. - Algoritmul se oprește după alegerea a
n − 1muchii.
Algoritm
Kruskal():
sortează muchiile u[1..m] după cost
pentru j ← 1, n execută
comp[j] ← j
sfpentru
i ← 1; k ← 0; total ← 0
cât timp k < n − 1 execută
dacă comp[u[i].x] ≠ comp[u[i].y]
atunci
k ← k + 1
total ← total + u[i].c
scrie u[i].x, u[i].y
compY ← comp[u[i].y]
compX ← comp[u[i].x]
pentru j ← 1, n execută
dacă comp[j] = compY atunci
comp[j] ← compX
sfdacă
sfpentru
sfdacă
i ← i + 1
sfcât timp
Structuri de date
struct muchie {
int x, y, c;
} u[MAXM];
Fiecare element al vectorului u reține extremitățile x, y și costul c al unei
muchii.
Variabila k numără muchiile alese, iar i este poziția muchiei examinate în lista
sortată. Valorile compX și compY se rețin înainte de unire, fiindcă comp[u[i].y]
se modifică în timpul structurii repetitive.
Exemplificarea etapelor
Pentru graful G16, lista muchiilor sortată crescător după cost este:
| Muchia | [4,5] | [1,2] | [2,3] | [1,3] | [3,4] | [3,5] | [2,4] | [5,6] | [4,6] | [6,7] | [5,7] |
|---|---|---|---|---|---|---|---|---|---|---|---|
| Costul | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 12 |
Inițial comp = (1, 2, 3, 4, 5, 6, 7). Muchiile se examinează astfel:
i | Muchia | comp[x], comp[y] | Decizia | comp[1..7] după pas | k | total |
|---|---|---|---|---|---|---|
| 1 | [4,5], cost 1 | 4, 5 | se alege | 1, 2, 3, 4, 4, 6, 7 | 1 | 1 |
| 2 | [1,2], cost 2 | 1, 2 | se alege | 1, 1, 3, 4, 4, 6, 7 | 2 | 3 |
| 3 | [2,3], cost 3 | 1, 3 | se alege | 1, 1, 1, 4, 4, 6, 7 | 3 | 6 |
| 4 | [1,3], cost 4 | 1, 1 | se respinge | nemodificat | 3 | 6 |
| 5 | [3,4], cost 5 | 1, 4 | se alege | 1, 1, 1, 1, 1, 6, 7 | 4 | 11 |
| 6 | [3,5], cost 6 | 1, 1 | se respinge | nemodificat | 4 | 11 |
| 7 | [2,4], cost 7 | 1, 1 | se respinge | nemodificat | 4 | 11 |
| 8 | [5,6], cost 8 | 1, 6 | se alege | 1, 1, 1, 1, 1, 1, 7 | 5 | 19 |
| 9 | [4,6], cost 9 | 1, 1 | se respinge | nemodificat | 5 | 19 |
| 10 | [6,7], cost 10 | 1, 7 | se alege | 1, 1, 1, 1, 1, 1, 1 | 6 | 29 |
După examinarea primelor patru muchii există arborii {1, 2, 3}, desenat cu roșu,
{4, 5}, desenat cu verde, și nodurile izolate 6 și 7. Muchia [1,3], punctată, a fost
respinsă: nodurile 1 și 3 erau deja în același arbore, iar muchia ar fi închis ciclul
(1, 2, 3, 1).
La pasul 5, muchia [3,4] unește cei doi arbori. Algoritmul se oprește după pasul 10,
când k = 6 = n − 1; muchia [5,7] nu mai este examinată. Arborele parțial de cost minim
are muchiile [4,5], [1,2], [2,3], [3,4], [5,6], [6,7] și costul 29.
Etapele se pot urmări pas cu pas în simulatorul algoritmului lui Kruskal, pe un alt graf cu 7 noduri.
Programul
Enunț. Se citește un graf neorientat conex cu n noduri și m muchii, dat prin lista
muchiilor, fiecare cu costul ei. Să se afișeze muchiile unui arbore parțial de cost minim și
costul lui.
Date de intrare. Pe prima linie n și m, pe fiecare dintre următoarele m linii
extremitățile și costul unei muchii. Pentru graful G16:
7 11
1 2 2
1 3 4
2 3 3
2 4 7
3 4 5
3 5 6
4 5 1
4 6 9
5 6 8
5 7 12
6 7 10
Materialul acesta se citește pe educamp.ro și nu se tipărește.
S-a încheiat minutul de citit liber.
Ce cuprinde subcapitolul
- Noțiuni preliminare
- Arborele
- Arborele parțial
- Arborele parțial de cost minim
- Algoritmul lui Kruskal
- Algoritmul lui Prim
- Compararea algoritmilor
- Probleme care se rezolvă cu arbori parțiali de cost minim
- Arborii la examen
- Greșeli frecvente
- Verificare rapidă
- Exerciții de fixare
Continuă lectura ca și cursant
Cel puțin un subcapitol din fiecare capitol este disponibil gratuit și integral. Pentru a citi toate celelalte subcapitole ale disciplinei, te înscrii la cursul de pregătire.
Prima săptămână este gratuită, fără plată și fără card. Dacă vrei să vezi mai întâi cum este prezentată materia, poți reveni la primul subcapitol al capitolului.
100 RON / lună, pentru o disciplină
Ce cuprinde:
- Două întâlniri de câte două ore, în fiecare lună
- Tot suportul de curs publicat până acum la disciplina aleasă
- Capitole noi în fiecare săptămână, cuprinse în luna plătită, fără costuri suplimentare
- Material organizat după structura programei de examen
- Acces de pe orice dispozitiv, folosind același cont
- Prima săptămână gratuită, fără card și fără reînnoire automată
Începe săptămâna gratuită Sunt cursant — login
Află când publicăm materiale noi
Materia este publicată treptat, capitol cu capitol. Înscrie-te pentru a primi un e-mail atunci când apare un capitol nou de informatică.
Nu am putut înregistra adresa. Verifică e-mailul și materia aleasă, apoi încearcă din nou.
Surse
- „Informatică. Manual pentru clasa a XI-a”, Editura Didactică și Pedagogică, București
- Gheorghe, M. (coord.), Tătărâm, M., Achinca, C., Năstase, C., „Informatică. Manual pentru clasa a XI-a”, Editura Corint, București, 2008
- Huțanu, V., Tudor, S., „Informatică. Manual pentru clasa a XI-a”, Editura L&S Soft, București, 2006
- Oprescu, D., Bejan Ienulescu, L., „Informatică, varianta C++. Manual pentru clasa a XI-a”, Editura Niculescu, București, 2007
- Programa pentru examenul de definitivare în învățământ, disciplina Informatică, cap. 5 „Teoria grafurilor”
- Programa pentru concursul de ocupare a posturilor didactice, disciplina Informatică, cap. 5 „Teoria grafurilor”
- Subiecte și bareme publicate, Definitivat și Titularizare, informatică, 2003–2026