Mihnea trăiește pe o planetă de dimensiunea n x n
, cu linii și coloane enumerate de la 1
la n
. Spunem că celula (l, c)
este intersecția liniei l
cu coloana c
. Fiecare celulă de pe planetă este fie pământ, fie apă.
Pentru a-l ajuta pe Mihnea, intenționezi să creezi cel puțin un tunel. Tunelul îi va permite lui Mihnea să călătorească liber între cele două puncte extreme ale tunelului. Într-adevăr, crearea unui tunel este un efort mare: costul creării unui tunel între celulele (l1, c1)
și (l2, c2)
este (l1 – l2) ^ 2 + (c1 – c2) ^ 2
.
Pentru moment, sarcina ta este să găsești costul minim de a crea câteva tuneluri astfel încât Mihnea să poată călători de la (l1,c1)
la (l2,c2)
. Dacă nu trebuie creat niciun tunel, costul este 0.
In fisierul de intrare mihneapolis.in
, prima linie conține un număr întreg n (1 ≤ n ≤ 50)
— dimensiunea planetei.
A doua linie conține două numere întregi separate prin spațiu l1
și c1 (1 ≤ l1, c1 ≤ n)
– indicând celula în care locuiește Mihnea.
A treia linie conține două numere întregi separate prin spațiu l2
și c2 (1 ≤ l2, c2 ≤ n)
– indicând celula în care Mihnea dorește să meargă.
Fiecare dintre următoarele n
linii conține un șir de n
caractere. Al j
-lea caracter de pe linia i
reprezintă tipul de celulă de la coordonata (i, j)
(1
este teren, 0
este apă).
Este garantat că (l1, c1)
și (l2, c2)
sunt celule de teren.
In fișierul mihneapolis.out
, afișați un număr întreg care reprezintă costul minim de creare a câteva tuneluri, astfel încât Mihnea să poată călători de la (l1,c1)
la (l2,c2)
.
mihneapolis.in
5 1 1 5 5 00001 11111 00111 00110 00110
mihneapolis.out
10
Problemă propusă de @My_Life_Is_Not_Life și @mateiUNU
Considerăm un graf neorientat ponderat (cu costuri) conex G
. Se numește arbore parțial un graf parțial al lui G
care este arbore. Se numește arbore parțial de cost minim un arbore parțial pentru care suma costurilor muchiilor este minimă.
Dacă graful nu este conex, vorbim despre o pădure parțială de cost minim.
Algoritmul lui Kruskal permite determinarea unui arbore parțial de cost minim (APM) într-un graf ponderat cu N
noduri.
Pentru a determina APM-ul se pleacă de la o pădure formată din N
subarbori. Fiecare nod al grafului reprezintă inițial un subarbore. Aceștia vor fi reuniți succesiv prin muchii, până când se obține un singur arbore (dacă graful este conex) sau până când acest lucru nu mai este posibil (dacă graful nu este conex).
Algoritmul este:
Principala dificultate în algoritmul descris mai sus este stabilirea faptului că extremitățile muchiei curente fac sau nu parte din același subarbore. În acest scop vom stabili pentru fiecare subarbore un nod special, numit reprezentant al (sub)arborelui și pentru fiecare nod din graf vom memora reprezentantul său (de fapt al subarborelui din care face parte) într-un tablou unidimensional.
Pentru a stabili dacă două noduri fac sau nu parte din același subarbore vom verifica dacă ele au același reprezentant. Pentru a reuni doi subarbori vom înlocui pentru toate nodurile din subarborele B cu reprezentantul subarborelui A.
Înlocuirile descrise mai sus sunt simple dar lente. Pentru o implementare mai eficientă a algoritmului se poate folosi conceptul de Padure de mulțimi disjuncte, descris în acest articol.
Vom determina, folosind Algoritmul lui Kruskal, arborele parțial de cost minim pentru graful de mai jos.
Muchiile se vor analiza în ordinea crescătoare a costului.
Se adaugă muchia (7,8)
de cost 1
Se adaugă muchia (3,9)
de cost 2
Se adaugă muchia (6,7)
de cost 2
Se adaugă muchia (1,2)
de cost 4
Se adaugă muchia (3,6)
de cost 1
Se ignoră muchia (7,9)
de cost 6
Se adaugă muchia (3,4)
de cost 7
Se ignoră muchia (8,9)
de cost 7
Se adaugă muchia (1,8)
de cost 8
Se ignoră muchia (2,3)
de cost 8
Se adaugă muchia (4,5)
de cost 9
Se ignoră muchia (5,6)
de cost 10
Se ignoră muchia (2,8)
de cost 11
Se ignoră muchia (4,6)
de cost 14
Vom determina, folosind Algoritmul lui Kruskal, arborele parțial de cost minim pentru graful de mai jos:
Muchiile se vor analiza în ordinea următoare:
1. | ![]() |
Se adaugă muchia (7,8) de cost 1 |
2. | ![]() |
Se adaugă muchia (3,9) de cost 2 |
3. | ![]() |
Se adaugă muchia (6,7) de cost 2 |
4. | ![]() |
Se adaugă muchia (1,2) de cost 4 |
5. | ![]() |
Se adaugă muchia (3,6) de cost 1 |
6. | ![]() |
Se ignoră muchia (7,9) de cost 6 |
7. | ![]() |
Se adaugă muchia (3,4) de cost 7 |
8. | ![]() |
Se ignoră muchia (8,9) de cost 7 |
9. | ![]() |
Se adaugă muchia (1,8) de cost 8 |
10. | ![]() |
Se ignoră muchia (2,3) de cost 8 |
11. | ![]() |
Se adaugă muchia (4,5) de cost 9 |
12. | ![]() |
Se ignoră muchia (5,6) de cost 10 |
13. | ![]() |
Se ignoră muchia (2,8) de cost 11 |
14. | ![]() |
Se ignoră muchia (4,6) de cost 14 |
Următoarea secvență determină costul total al APM-ului, folosind algoritmul lui Kruskal. Presupunem că graful are cel mult 100
de noduri.
struct muchie{ int i,j,cost; }; int n , m , t[101]; muchie x[5000]; int main() { cin >> n >> m; for(int i = 0 ; i < m ; ++i) cin >> x[i].i >> x[i].j >> x[i].cost; //sortare tablou x[] după campul cost // ... de completat //initializare reprezentanti for(int i =1 ; i <= n ; ++i) t[i] = i; //determinare APM int S = 0; for(int i = 0 ; i < m ; i ++) if(t[x[i].i] != t[x[i].j]) // extremitatile fac parte din subrabori diferiti { S += x[i].cost; //reunim subarborii int ai = t[x[i].i], aj = t[x[i].j]; for(int j =1 ; j <= n ; ++j) if(t[j] == aj) t[j] = ai; } cout << S << "\n"; return 0; }
Un graf orientat G=(V,E)
este tare conex dacă pentru orice pereche de noduri distincte (x,y)
există cel puțin un drum de la x
la y
și există cel puțin un drum de la y
la x
.
Pentru un graf orientat, se numește componentă tare conexă un subgraf tare conex maximal – prin adăugarea a încă unui nod, subgraful obținut nu mai este tare conex.
Exemplu
Graful de mai sus nu este tare conex. El are trei componente tare conexe.
Verificare tare conexității unui graf orientat poate fi privită ca un caz particular al determinării componentelor tare conexe, deoarece, dacă graful are o singură componentă tare conexă atunci el este tare conex. În continuare vom vedea două metode de determinare a componentelor tare conexe. Ambele folosesc noțiunea de graf transpus, pe care o definim în continuare:
Definiție: Fie G=(V,E)
un graf orientat. Se numește graf transpus al lui G
graful orientat GT=(V,ET)
, cu aceleași mulțimea a nodurilor și pentru orice pereche de noduri are loc: (x,y)
este arc în G dacă și numai dacă (y,x)
este arc în GT
.
Exemplu
Graf orientat inițial | Graful orientat transpus |
![]() |
![]() |
Să observăm că pentru două noduri oarecare x
, y
:
x
la y
poate fi determinată cu o parcurgere (de exemplu în adâncime) în graful G
, pornind din nodul x
;y
la x
poate fi determinată cu o parcurgere în graful GT
, pornind tot din nodul x
.Folosind observații de mai sus, pentru a determina componentele tare conexe folosim următorul algoritm, numit Plus-Minus:
x
al grafului care încă nu a fost plasat într-o componentă tare conexă:
x
, folosind graful G
și le marcăm într-un tablou cu plus;x
, folosind graful GT
și le marcăm într-un tablou cu minus;x
formează o componentă tare conexă;Exemplu:
Fie graful de mai sus. Să determinăm componenta tare conexă din care face parte nodul 6
:
Graful inițial | Graful transpus |
![]() |
![]() |
S-au marcat cu plus nodurile: 5 7 8 |
S-au marcat cu minus nodurile: 1 2 3 4 5 7 8 |
Nodurile marcate de două ori, 5 7 8
, împreună cu nodul inițial, 6
, formează o componentă tare conexă.
Secvență C++:
n, a[][]
– numărul de noduri și matricea de adiacențănrc
– numărul de componente tare conexectc[]
– tablou pentru memorarea componentelor tare conexe: ctc[i] =
numărul de ordine al componentei din care face parte nodul i
s[], p[]
– tablouri pentru marcare nodurilor vizitate în timpul parcurgerilorvoid df1(int x) { s[x] = 1; for(int i =1 ; i <= n ; i ++) if(s[i] == 0 && a[x][i] == 1) df1(i); } void df2(int x) { p[x] = 1; for(int i =1 ; i <= n ; i ++) if(p[i] == 0 && a[i][x] == 1) df2(i); } int main() { ..... for(int i = 1 ; i <= n ; ++i) if(ctc[i] == 0) { for(int j = 1; j <= n ; ++j) s[j] = p[j] = 0; nrc ++; df1(i); df2(i); for(int j = 1; j <= n ; ++j) if(s[j] == 1 && p[j] == 1) ctc[j] = nrc; } .... }
Alt algoritm, mai eficient, pentru determinarea componentelor tare conexe este Algoritmul lui Kosaraju.
Să ne amintim că la parcurgerea în adâncime se pot asocia nodurilor două momente de timp:
d[x]
– momentul când nodul x
este descoperit și adăugat pe stivă: timpul de descoperire a noduluif[x]
– momentul când se termină de vizitat succesorii lui x
, iar nodul x
se elimină de pe stivă: timpul de finalizare a noduluiAceste momente de timp vor fi numere naturale între 1
și 2*n
, unde n
este numărul de noduri din graf.
Algoritmul lui Kosaraju este:
GT
x
timpul de finalizare f[x]
GT
, dar considerăm nodurile în ordinea descrescătoarea timpilor de finalizareExemplu:
Graf orientat inițial | Graful orientat transpus |
![]() |
![]() |
În urma parcurgerii în adâncime a grafului inițial G
nodurile în ordinea finalizării sunt:
Parcurgem graful transpus GT
analizând nodurile în ordinea inversă a timpilor de finalizare:
1
; se vizitează nodurile 3 4
; se determină componenta tare conexă {1,3,4}
;2
(nodurile 1
, 3
și 4
au fost vizitate mai devreme); nu se vizitează alte noduri; se determină componenta tare conexă {2}
;5
; se vizitează nodurile 6 8 7
; se determină componenta tare conexă {5, 6, 7, 8}
.Secvență C++:
#include <iostream> #include <fstream> #include <vector> using namespace std; vector<vector<int> > G, GT; int n , m , contor , nrs; vector<bool> V; vector<int> S; void read() { cin >> n >> m; G = GT = vector<vector<int>>(n + 1); for(int i = 1 ; i <= m ; i++) { int a , b; cin >> a >> b; G[a].push_back(b); GT[b].push_back(a); } } void dfs(int k) { V[k] = true; for(auto x : G[k]) if(!V[x]) dfs(x); S.push_back(k); } void dfsGT(int k) { V[k]=1; for(auto x: GT[k]) if(! V[x]) dfsGT(x); } int main() { read(); V = vector<bool> (n + 1, false); for(int i = 1 ; i <= n ; i ++) if(! V[i]) dfs(i); V = vector<bool> (n + 1, false); for(vector<int>::reverse_iterator it = S.rbegin() ; it != S.rend() ; it ++) if(!V[*it]) { contor ++; dfsGT(*it); } cout<<contor; return 0; }
Complexitatea acestui algoritm este aceea a parcurgerii în adâncime: O(n
2
)
dacă memorăm graful prin matricea de adiacență, O(n+m)
dacă memorăm graful prin liste de adiacență.
Fie G=(V,E)
un graf neorientat conex:
Exemplu:
Graful inițial | Puncte de articulație | Punți | Componente biconexe |
![]() |
![]() |
![]() |
![]() |
Câteva observații:
Pentru a determina punctele de articulație și punțile se poate folosi un algoritm naiv, bazat pe eliminarea succesivă a câte unui nod (muchie) și verificarea conexității. Complexitatea acestor algoritmi este O(n*C)
, respectiv O(m*C)
, unde O(C)
este complexitatea algoritmului de verificarea a conexității (O(n*n)
dacă reprezentăm graful prin matrice de adiacență, O(n+m)
dacă reprezentăm graful prin liste de adiacențe).
În cele ce urmează vom prezentat un algoritm bazat pe parcurgerea în adâncime a grafului. Complexitatea sa este aceeași cu a parcurgerii în adâncime.
Considerăm G=(V,E)
graful dat și A
arborele de parcurgere în adâncime. Reamintim că muchiile graful pot fi doar:
(k,x)
este de întoarcere dacă x
este ascendent al lui k
în arborele A
– nodul x
a fost descoperit anterior nodului k
Pentru graful de mai sus, arborele de parcurgere este următorul – muchiile de parcurgere sunt desenate cu linii continue, cele de întoarcere cu linii întrerupte.
Observăm următoarele:
k
al grafului, diferit de rădăcina arborelui DFS, este punct de articulație dacă și numai dacă are un descendent direct x
în arborele A
cu proprietatea că de la niciun descendent al lui x
sau de la x
nu există muchie de întoarcere la niciun ascendent al lui k
;Pentru a determina punctele de articulație, punțile și componentele biconexe vom determina pentru fiecare nod k
următoarele informații:
nivel[k]
– nivelul pe care se află nodul k
în arborele DFS;
nivel[rădăcină] = 1
;nivel[k] = nivel[tata[k]] + 1
;nma[k]
– nivelul minim la care se poate ajunge din k
, mergând numai pe muchii de arbore și folosind ca ultimă muchie o muchie de întoarcere. Îl vom numi nivelul minim accesibil și este egal cu minimul dintre următoarele 3 valori :
Pentru graful de mai sus aceste valori sunt următoarele:
k |
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
nivel[k] |
1 |
2 |
3 |
4 |
3 |
5 |
4 |
6 |
nma[k] |
1 |
1 |
3 |
1 |
1 |
4 |
4 |
4 |
Cu ajutorul acestor valori, un nod k
care nu este rădăcina arborelui este punct de articulație dacă și numai dacă are cel puțin un fiu x
pentru care nivelul minim accesibil este mai mare sau egal cu nivelul nodului (nivel[k] <= nma[x]
). Să analizăm nodurile:
1
nu este punct de articulație, deoarece este rădăcina arborelui și are un singur descendent direct2
este punct de articulație, deoarece 3
este fiu al lui 2
și nivel[2] <= nma[3]
3
nu este punct de articulație, deoarece nu are niciun fiu4
nu este punct de articulație, deoarece nu are niciun fiu5
este punct de articulație deoarece 7
este fiu al lui 5
și nivel[5] <= nma[7]
6
nu este punct de articulație; pentru singurul fiu al lui 6
, 8
relația este nivel[6] > nma[8]
7
este punct de articulație deoarece 6
este fiu al lui 7
și nivel[7] <= nma[6]
8
nu este punct de articulație deoarece nu are fiiFie (k,x)
o muchie de arbore, în care k
este tatăl lui x
. Această muchie este critică dacă și numai dacă (nivel[k] < nma[x]
). Muchiile de întoarcere nu sunt niciodată muchii critice, deoarece aparțin întotdeauna unor cicluri.
Cele două șiruri de valori se construiesc în timpul parcurgerii DFS. Fie k
nodul curent în parcurgerea DFS:
nivel[k] = nivel[tata[k]] + 1
– această valoare va rămâne neschimbată până la final;nma[k] = nivel[k]
k
. Fie x
un asemenea nod:
x
a fost deja parcurs și nu este tatăl lui k
, muchia (k,x)
este muchie de întoarcere. Dacă este cazul actualizăm nma[k]
la valoarea nivel[x]
(dacă nivel[x]
este mai mic decât valoarea actuală a lui nma[k]
);x
nu a fost încă parcurs, continuăm parcurgerea din x
– îl adăugăm pe stivă/apel recursiv. La eliminarea de pe stivă/ revenirea din autoapel, actualizăm valoarea nma[k]
la valoarea nma[x]
(dacă nma[k]
este mai mare decât nma[x]
, acesta din urmă este calculat deja)Următorul pseudocod descrie cum se calculează pentru fiecare nod al grafului nivelul și nivelul minim accesibil, folosind parcurgerea în adâncime:
subprogram DFS(k, tata) V[k] ← true nivel[k] ← nivel[tata] + 1 nma[k] ← nivel[k] pentru x - nod adiacent cu k dacă x ≠ tata dacă V[x] = true atunci dacă nma[k] > nivel[x] atunci nma[k] ← nivel[x] sf_dacă altfel DFS(x,k) dacă nma[k] > nma[x] atunci nma[k] ← nma[x] sf_dacă // determinare puncte de articulație // determinare punți // determinare componente biconexe sf_dacă sf_dacă sf_pentru sf_subprogram
Pentru a determina punctele de articulație:
x
(revenirea din autoapel) verificăm dacă acest și nodul părinte k
verifică relația nivel[k] <= nma[x]
; în caz afirmativ, decidem că nodul k
este critic;//determinare puncte de articulație dacă nivel[k] ≤ nma[x] și nod ≠ rădăcină atunci //nodul k este punct de articulație sf_dacă
Pentru a determina punțile:
x
/revenirea din autoapel vom determina muchiile de parcurgere (k,x)
pentru care nivelul lui k
este mai mic decât nivelul minim accesibil al lui x
: nivel[k] < nma[x]
.//determinare punți dacă nivel[k] < nma[x] atunci //muchia (k,x) este punte sf_dacă
Pentru a determina componentele biconexe:
S
, pe care adaugăm noduri la vizitarea lor conform parcurgerii și le eliminăm la determinarea unei componente biconexe;
S
este cea a parcurgerii în adâncime;k
avem un descendent direct x
pentru care nivel[k] <= nma[x]
, vom elimina de pe stivă nodurile până la nodul x
, inclusiv acesta;
k
reprezintă o componentă biconexă;x
, nu până la k
, deoarece între acestea, pe stivă, pot fi noduri din altă componentă biconexă.nivel[k] <= nma[x]
are loc întotdeauna pentru rădăcina arborelui DFS, chiar dacă acesta nu este punct de articulație. În acest caz se determină o componentă biconexă din care face rădăcina. Acest lucru se întâmplă și când graful dat este biconex: determinarea componentei biconexe se face la revenirea în rădăcină.... V[k] ← true Adaugă(S,k) .... //determinare componente biconexe dacă nivel[x] ≤ nma[k] atunci câttimp Vârf(S) ≠ x execută Vârf(S) se adugă la componenta curentă Elimină(S) sf_câttimp x se adaugă la componenta curentă Elimină(S) k se adaugă la componenta curentă sf_dacă
În jurul anului 1960 a fost descoperit un algoritm pentru drumuri în grafuri orientate. Descoperirea a fost făcută independent de către trei oameni de stiință în domeniul algoritmilor: Robert Floyd, Bernard Roy și Stephen Warshall.
Algoritmul se regăsește sub diferite denumiri care conțin numele descoperitorilor, este bazat pe programarea dinamică și poate fi utilizat în următoarele două moduri:
x y
dacă există drum de la x
la y
– este de regulă cunoscut sub numele Roy-WarshalAmbele variante permit și reconstituirea unor drumuri între două noduri date.
În 1736, matematicianul elvețian Leonard Euler a rezolvat o problemă cunoscută astăzi ca “Problema celor șapte poduri din Königsberg”. Prin orașul Königsberg, azi Kaliningrad trece un râu pe care sunt două insule, acestea și malurile fiind unite prin poduri ca în imaginea de mai jos.
Leonard Euler a demonstrat că nu este posibil ca o persoană să traverseze fiecare pod o singură dată. În onoarea sa, o categorie specială de grafuri se numesc grafuri euleriene.
Definiții:
Exemplu:
În graful de mai sus ciclul (1 2 4 5 3 6 5 2 3 1)
este eulerian.
Teoremă:
Un graf neorientat fără vârfuri izolate este eulerian dacă și numai dacă este conex și toate vârfurile au grad par.
Un graf neorientat fără vârfuri izolate conține un lanț eulerian, dacă și numai dacă este conex și toate vârfurile au grad par, mai puțin două. Aceste vârfuri vor fi extremitățile lanțului eulerian.
Pentru determinarea unui ciclu eulerian se pot folos mai mulți algoritmi. Unul dintre aceștia este asemănător cu parcurgerea în adâncime. Vom folosi o stivă (eventual memoria STACK prin intermediul recursivității)
1
;k
– nodul din vârful stiveix
adiacente cu k
. Eliminăm muchia [k,x]
și adăugăm nodul x
pe stivă (apel recursiv)
k
într-o listăk
din stivăImportant:
Secvență C++:
Considerăm un graf cu n
vârfuri, memorat prin intermediul matricei de adiacență, A[][]
. Tabloul L[]
reprezintă lista în care se memorează ciclul eulerian. Toate variabilele sunt globale:
void Euler(int k) { for(int i = 1 ; i <= n ; i ++) if(A[k][i] == 1) { A[k][i] = A[i][k] = 0; Euler(i); } L[++p] = k; } ... Euler(1); ...
Algoritmul de mai sus poate fi utilizat și pentru determinarea unui lanț eulerian. Parcurgerea trebuie să înceapă însă din unul dintre vârfurile cu grad impar.
Problema comis voiajorului este o problemă celebră de informatică: Un comis-voiajor (agent comercial) trebuie viziteze n
orașe. Cunoscându-se șoselele existente între orașe, să se determine o modalitate (toate modalitățile) prin care comis-voiajorul poate parcurge fiecare oraș o singură dată și se întoarce în orașul de plecare.
Dacă asociem problemei un graf neorientat, constatăm că trebuie să determinăm un (toate) ciclu elementar care trece prin toate vârfurile grafului. Un astfel de ciclu se numește ciclu hamiltonian.
Definiții:
Pentru un graf orientat se pot definii noțiuni similare, de drum hamiltonian și circuit hamiltonian
Exemplu:
În graful de mai sus sunt evidențiate muchiile care fac parte dintr-un ciclu hamiltonian: (1, 2, 5, 4, 6, 3)
.
În anumite condiții se poate stabili că un graf dat este hamiltonian. Dar aceste condiții sunt “de suficiență”. Dacă nu sunt îndeplinite, nu înseamnă că graful nu este hamiltonian!
Teoreme:
G=(X,U)
un graf neorientat cu \( n \) vârfuri și un lanț hamiltonian \( v_1, v_2, \cdots, v_n \). Dacă \( d(v_1) + d(v_n) \geq n\), atunci graful este hamiltonian, unde \( d(x) \) este gradul vârfului \(x \).G=(X,U)
un graf neorientat cu \( n \) vârfuri. Dacă pentru orice pereche de vârfuri neadiacente distincte \( v_i, v_j \) avem relația \( d(v_i) + d(v_j) \geq n\), atunci graful este hamiltonian.Considerăm un graf neorientat ponderat (cu costuri) conex G
. Se numește arbore parțial un graf parțial al lui G
care este arbore. Se numește arbore parțial de cost minim un arbore parțial pentru care suma costurilor muchiilor este minimă.
Dacă graful nu este conex, vorbim despre o pădure parțială de cost minim.
Algoritmul lui Prim permite determinarea unui arbore parțial de cost minim (APM) într-un graf ponderat cu N
noduri.
Determinarea APM-ului se face astfel:
Observație: arborele parțial de cost minim al unui graf neorientat nu este unic, însă toate APM-urile vor avea același cost.
Mai jos este descris modul în care se aleg nodurile care se adaugă în arbore pentru un graf ponderat cu N=9
noduri:
Nodul ințial este 1
. Costul curent al APM este 0
Se adaugă nodul 2
. Muchia folosită este (1,2)
. Costul curent al APM este 40
Se adaugă nodul 3
. Muchia folosită este (2,3)
. Costul curent al APM este 120
Se adaugă nodul 9
. Muchia folosită este (3,9)
. Costul curent al APM este 140
Se adaugă nodul 6
. Muchia folosită este (3,6)
. Costul curent al APM este 180
Se adaugă nodul 7
. Muchia folosită este (6,7)
. Costul curent al APM este 200
Se adaugă nodul 8
. Muchia folosită este (7,8)
. Costul curent al APM este 210
Se adaugă nodul 4
. Muchia folosită este (3,4)
. Costul curent al APM este 280
Se adaugă nodul 5
. Muchia folosită este (4,5)
. Costul curent al APM este 370
Algoritmul poate fi implementat în mai multe moduri, cu complexități diferite.
N-1
ori):
Față de varianta anterioară se evită căutarea propriu zisă a nodului din arbore, căutându-se efectiv numai nodul din afara acestuia. Pentru aceasta se păstrează un șir d[]
cu următoarea semnificație: pentru un nod k
încă neadăugat în arbore, d[k]
reprezintă costul minim al unei muchii având o extremitate k
și o extremitate în arbore; dacă o asemenea muchie nu există d[k] = INFINIT
.
Dacă determinarea succesivă a nodurilor din afara arborelui se face prin parcurgerea șirului d[]
, complexitatea devine O(N
2
)
.
Se poate evita parcurgerea șirului d[]
prin memorarea nodurilor din arbore într-o structură de date de tip heap, în vârful heap-ului fiind nodul k
pentru care d[k]
are valoare minimă. În acest fel complexitatea algoritmului devine O(N logN)
.
Grafurile au numeroase aplicații în diverse domenii: proiectarea circuitelor electrice, determinarea celui mai scurt drum dintre două localități, rețelele sociale (ex. Facebook), etc.
Primele rezultate legate de teoria grafurilor au fost obținute de matematicianul Leonard Euler, cel care a studiat Problema podurilor din Königsberg, din imaginea de mai jos. A demonstrat că problema nu are soluție, iar în onoarea lui o categorie specială de grafuri au fost numite grafuri euleriene.
Definiție: Se numește graf neorientat o pereche ordonată de mulțimi G=(X,U)
, unde:
X
este o mulțime finită și nevidă de elemente numite vârfuri sau noduri;U
este o mulțime finită de submulțimi cu două elemente din X
, numite muchii.Vom nota în continuare vârfurile cu valori între 1
și n
– unde n
este număru de vârfuri din graf, iar muchiile cu [x,y]
sau (x,y)
, unde x
și y
sunt vârfuri și se numesc extremitățile muchiei.
Un vecin al unui vârf x
este orice vârf y
cu proprietatea că există muchia [x,y]
.
Două vârfuri între care există muchie se numesc adiacente.
Două muchii sunt incidente dacă au o o extremitate comună. Un vârf este incident cu o muchie dacă vârful este extremitate a acelei muchii.
Mulțimea muchiilor are proprietatea de simetrie: dacă [x,y]
este muchie, atunci și [y,x]
este muchie.
Conform definiției:
Exemplu: Fie G=(X,U)
, unde:
X={1,2,3,4,5,6,7,8,9,10,11}
U={[1,4],[1,5],[2,3],[2,8],[3,11],[4,5],[4,9],[7,10],[8,11]}
Definiție Într-un graf neorientat se numește grad al unui vârf numărul de vârful adiacente cu acesta (sau numărul de muchii incidente cu acesta). Gradul unui vărf x
se notează d(x)
(degree).
Observații:
0
se numește izolat. În graful de mai sus, vârful 6
este izolat.1
se numește terminal. În graful de mai sus, vârful 9
este vârf terminal.n
vârfuri este n-1
.Teoremă: Într-un graf neorientat, suma gradelor tuturor vârfurilor este dublul numărului de muchii.
Consecințe:
Întrebare: Este posibil ca într-un grup de
5
persoane, fiecare persoană să aibă exact3
prieteni?
Pentru un graf neorientat G=(X,U)
cu n
vârfuri, matricea de adiacență este o matrice cu n
linii și n
coloane și elemente din {0,1}
, cu: \( A_{i,j} = \left\{ \begin{array}{ll}
1 & \mbox{dacă } [i,j] \in U \\
0 & \mbox{dacă } [i,j] \notin U \end{array} \right. \)
Exemplu: Pentru graful neorientat de mai jos avem următoarea matrice de adiacență:
![]() |
\( A = \left( \begin{array}{ccccc} 0 & 1 & 0 & 0 & 1 \\ 1 & 0 & 0 & 0 & 1 \\ 0 & 0 & 0 & 0 & 0 \\ 0 & 0 & 0 & 0 & 1 \\ 1 & 1 & 0 & 1 & 0 \end{array} \right) \) |
Observații:
0
;x
este egal cu numărul de elemente 1
de pe linia (sau coloana) x
; Lista de muchii a unui graf neorientat reprezintă o mulțime ce conține toate muchiile din graf.
Pentru graful alăturat, lista de muchii este:
\( U = \left\{ [1,2],[1,5],[2,5],[4,5] \right\} \)
Pentru reprezentarea în memorie putem folosi:
struct {int I,J;}
int
Pentru un graf neorientat cu
G=(X,U)
se va memora numărul de vârfuri n
și apoi, pentru fiecare vârf x
, lista vârfurilor adiacente cu x
, adică a vârfurilor y
cu proprietatea că există muchia [x,y]
.
Pentru graful alăturat, listele de adiacență sunt:
1: 2 5 2: 1 5 3: vidă 4: 5 5: 1 2 4
La reprezentarea în memorie trebui avut în vedere că dimensiunile listelor de vecini sunt variabile. De aceea, este neeficientă utilizarea unor tablouri alocate static. Astfel, putem folosi:
n
tablouri unidimensionale alocate dinamic;n
vectori din STL;n
liste simplu (dublu) înlănțuite alocate dinamic.Definiție. Fie G=(X, U)
un graf neorientat. Se numeşte graf parțial al grafului G
, graful neorientat G1=(X, U1)
, unde U1 ⊆ U
.
Din definiție rezultă:
G=(V,U)
, are aceeaşi mulțime de vârfuri ca şi G
, iar mulțimea muchiilor este o submulțime a lui U
sau chiar U
.G=(X, U)
un graf neorientat. Un graf parțial al grafului G
se obține păstrând vârfurile şiDefiniție. Fie G=(X, U)
un graf orientat. Se numeşte subgraf al grafului G
graful neorientat G1=(X1,U1)
unde X1 ⊆ X
iar U1
conține toate arcele din U
care au extremitățile în X1
.
Din definiție rezultă:
G=(X,U)
un graf orientat. Un subgraf al grafului G
, se obține ştergând eventual anumiteDefiniție. Fie G=(X, U)
un graf neorientat. Se numeşte graf complementar al grafului G
, graful neorientat G1=(X, U1)
, cu proprietatea că două vârfuri x
și y
sunt adiacente în G1
dacă și numai dacă nu sunt adiacente în G
.
Exemplu:
Graful inițial | Graf parțial | Subgraf | Graf complementar |
![]() |
![]() |
![]() |
![]() |
S-au eliminat muchiile [1,2] , [3,1] |
S-a eliminat vârfurile 3 5 și toate muchiile incidente cu ele. |
O muchie [x,y] apare în graful complementar dacă și numai dacă nu apare în graful inițial. |
Observații. Un graf neorientat oarecare poate avea mai multe grafuri parțiale și subgrafuri, dar un unic graf complementar. Mai precis:
Teoremă: Fie G
un graf neorientat cu n
vârfuri și m
muchii. Atunci:
G
admite \( 2 ^ m \) grafuri parțiale;G
admite \( 2 ^ n – 1 \) subgrafuri;G
admite un unic graf complementar.Justificare:
Să ne amintim că o mulțime cu a
elemente are \( 2 ^ a \) submulțimi, inclusiv mulțimea vidă și mulțimea inițială. Atunci:
m
muchii, deci \( 2 ^ m \) submulțimi, deci \( 2 ^ m \) grafuri parțiale.0
vârfuri. Similar ca mai sus, sunt \( 2^n – 1 \) subgrafuri.Definiție: Un graf neorientat se numește graf nul dacă mulțimea muchiilor este vidă.
Într-un graf nul toate vârfurile sunt izolate.
Definiție. Fie G=(X, U)
un graf neorientat. Graful G
se numește graf complet dacă oricare două vârfuri
distincte ale sale sunt adiacente. Un graf complet cu n
vârfuri se notează K
n
.
Exemplu: Graful următor este graful K
5
.
Într-un graf complet cu n
vârfuri sunt \( C_n ^2 = { {n*(n-1)} \over 2 } \) muchii și fiecare vârf are gradul n-1
.
Propoziție: Sunt \( 2 ^ {{n*(n-1)} \over 2} \) grafuri neorientate distincte cu n
vârfuri.
Definiție: Un graf în care toate nodurile au acelaşi grad se numește graf regulat.
Exemplu: Graful de mai jos este regulat.
Definiţie: Un graf G=(X, U)
se numește graf bipartit dacă există două mulţimi nevide A
și B
astfel încât X=A ∪ B
, A ∩ B = ∅
şi orice muchie u
a lui G
are o extremitate în A
iar cealaltă în B
. Mulţimile A
şi B
formează o partiţie a lui X
.
Exemplu: Graful următor este bipartit. A={1,2,5,7}
și B={3,4,6}
.
Definiție: Un graf bipartit G=(X,U)
se numește bipartit complet dacă pentru oricare două vârfuri \( x \in A\) și \( y \in B \), există în graf muchia [x,y]
; adică \( [x,y] \in U \).
Exemplu: Graful următor este bipartit complet.
Definiție: Se numește lanț o succesiune de vârfuri \( L = \left[ x_1, x_2, \cdots x_k \right] \) cu proprietatea că oricare două vârfuri consecutive sunt adiacente.
Vârfurile x
1
şi x
k
se numesc extremitățile lanțului. Numărul k-1
se numește lungimea lanțului și este numărul de muchii din care este format.
Lanțul care conține numai vârfuri distincte, două câte două, este lanț elementar.
Lanțul care conține numai muchii distincte este lanț simplu. Dacă muchiile unui lanț nu sunt distincte se numește lanț compus.
Definiție: Se numește ciclu un lanț simplu în care primul vârf este identic cu ultimul. Dacă toate vârfurile sunt distincte, mai puțin primul și ultimul, se numește ciclu elementar.
Lungimea unui ciclu este egală cu numărul de muchii din ciclu. Lungimea minimă a unui ciclu este 3
.
Un ciclu se numește par dacă lungimea sa este pară, respectiv impar în caz contrar.
Un graf neorientat care nu conține niciun ciclu se numește aciclic.
Exemple: În graful de mai jos:
Definiție: Un graf neorientat se numește graf conex dacă pentru oricare două vârfuri x
și y
diferite ale sale, există cel puțin un lanț care le leagă, adică x
este extremitatea inițială și y
este extremitatea finală.
Un graf cu un singur nod este, prin definiție, conex.
Definiție: Se numește componentă conexă a unui graf G=(X,U)
un subgraf H=(Y, V)
, conex, al lui G
care are proprietatea că nu există nici un lanț în G care să lege un vârf din Y
cu un vârf din X – Y
.
Subgraful H
este conex și maximal cu această proprietate (dacă s-ar mai adăuga un vârf nu ar mai fi conex.)
Un graf este conex dacă admite o singură componentă conexă.
Exemple:
Graful următor este conex:
Graful următor nu este conex și are 4
componente conexe.
Definiție: Un graf este biconex dacă este conex şi pentru orice vârf eliminat subgraful generat îşi păstrează proprietatea de conexitate.
Definiție: Se numește arbore un graf conex și aciclic.
Exemplu: Graful următor este arbore:
Observații:
n
vârfuri are n-1
muchii.Un graf parțial care este arbore se numește arbore parțial.
Un graf care nu conține cicluri se mai numește pădure. Într-o pădure fiecare componentă conexă este arbore.
Definiție: Se numește graf hamiltonian un graf care conține un ciclu hamiltonian. Se numește ciclu hamiltonian un ciclu elementar care conține toate vârfurile grafului.
Exemplu: Graful următor este hamiltonian. Un ciclu hamiltonian este: \( [1,4,2,3,7,6,5,1] \)
Teoremă: Un G
un graf neorientat. Dacă are n≥3
vârfuri şi gradul oricărui vârf verifică inegalitatea d(x)≥n/2
atunci G
este hamiltonian.
Definiție: Se numește graf eulerian un graf care conține un ciclu eulerian. Se numește ciclu eulerian un ciclu care conține toate muchiile grafului.
Exemplu: Graful următor este eulerian. Un ciclu eulerian este: \( [1,4,2,1,3,2,7,3,5,7,6,5,1] \)
Teoremă: Un graf G = (X,U)
, fără vârfuri izolate, este eulerian dacă şi numai dacă este conex şi
gradele tuturor vârfurilor sale sunt numere pare.
Definiție. Se numeşte graf orientat sau digraf o pereche ordonată de mulțimi notată G=(V, U)
, unde:
V
este o mulțime finită şi nevidă ale cărei elemente se numesc noduri sau vârfuri;U
este o mulțime de perechi ordonate de elemente distincte din V
ale cărei elemente se numesc arce.Exemplu:
V={1,2,3,4,5,6}
U={(1,6),(2,1),(2,4),(3,2),(4,2),(5,4),(6,1),(6,2),(6,4)}
Observăm că arcele (1,6)
și (6,1)
sunt distincte.
u=(x,y)
, se numesc extremități ale sale nodurile x
şi y
;
x
se numeşte extremitate inițială;y
se numeşte extremitate finală;y
se numește succesor al lui x
;x
se numește predecesor al lui y
.u=(x,y)
(sau u=(y,x)
, sau amândouă), se spune despre nodurile x
şi y
că sunt adiacente;u1
şi u2
sunt două arce ale aceluiaşi graf, se numesc incidente dacă au o extremitate comună. Exemplu: u1=(x,y)
şi u2=(y,z)
sunt incidente;u1=(x,y)
este un arc într-un graf, se spune despre el şi nodul x
, sau nodul y
, că sunt incidente.Definiție. Se numeşte graf orientat o pereche ordonată de mulțimi notată G=(V, U)
, unde:
V
este o mulțime, finită şi nevidă, ale cărei elemente se numesc noduri sau vârfuri;U
este o mulțime, de perechi ordonate de elemente din V
, ale cărei elemente se numesc arce.Această definiție diferă de prima definiție prin faptul ca acum nu se mai spune despre extremitățile unui arc
ca trebuie să fie distincte. În baza acestei definiții, sunt permise şi arce de genul: u=(x,x)
unde x∈V
; aceste arce se numesc bucle.
Exemplu:
Definiție. Se numeşte graf orientat o pereche ordonată de mulțimi notată G=(V, U)
, unde:
V
este o mulțime, finită şi nevidă, ale cărei elemente se numesc noduri sau vârfuri;U
este o familie de perechi ordonate de elemente din V
, numită familia de arce.Această definiție diferă de cea anterioară prin faptul ca acum nu numai că se admit bucle, dar se admit şi mai multe arce identice.
Exemplu:
Observăm că există trei arce (6,2)
.
Observație. Dacă într-un graf orientat numărul arcelor identice nu depăşeşte numărul p
, atunci se numeşte p-graf. Graful de mai sus este un 3-graf.
Definiție. Fie G=(V, U)
un graf orientat și x
un nod al său.
x
, numărul arcelor de forma (x,y)
(adică numărul arcelor care ies din x
), notat d
+
(x)
.x
, numărul arcelor de forma (y,x)
(adică numărul arcelor care intră în x
), notat d
-
(x)
. Exemplu:
Pentru graful alăturat:
d
+
(2)=2
d
-
(2)=3
Teoremă: Într-un graf orientat, suma gradelor exterioare a tuturor nodurilor este egală cu suma gradelor interioare a tuturor nodurilor și cu numărul de arce.
Un nod x
se numește izolat dacă d
+
(x)=d
-
(x)=0
(are gradul interior și gradul exterior egal cu 0
).
Fie G=(V,U)
un graf orientat cu n
noduri, în care nu există mai multe arce de la un nod la altul. Matricea de adiacență a grafului este o matrice cu n
linii și n
coloane și elemente 0
sau 1
, astfel:
A
i,j
=1
dacă există arcul (i,j)
A
i,j
=0
dacă există nu arcul (i,j)
Pentru graful alăturat, matricea de adiacență este:
0 0 0 0 0 1 1 0 0 1 0 0 0 1 0 0 0 0 0 1 0 0 0 0 0 0 0 1 0 0 1 1 0 1 0 0
Observăm că matricea de adiacență:
Pentru reprezentarea în memorie vom folosi un tablou bidimensional ale cărui dimensiuni sunt în concordanță cu numărul de noduri din graf.
Considerăm un graf cu maxim 50
de noduri. În C/C++ vom avea declarația:
int A[51][51];
Lista de arce a unui graf orientat reprezintă o mulțime (familie, dacă arcele se pot repeta) ce conține toate arcele din graf.
Pentru graful alăturat, lista de arce este:
U={(1,6),(2,1),(2,4),(3,2),(4,2),(5,4),(6,1),(6,4)}
Pentru reprezentarea în memorie putem folosi:
struct {int I,J;}
int
Pentru un graf orientat cu G=(V,U)
se va memora numărul de noduri n
și apoi, pentru fiecare nod x
, lista succesorilor lui x
, adică nodurilor y
cu proprietatea că există arcul (x,y)
.
Pentru graful alăturat, acestea sunt:
1: 6 2: 1 4 3: 2 4: 2 5: 4 6: 1 2 4
La reprezentarea în memorie trebui avut în vedere că dimensiunile listelor de succesori sunt variabile. De aceea, este neeficientă utilizarea unor tablouri alocate static. Astfel, putem folosi:
n
tablouri unidimensionale alocate dinamic;n
vectori din STL;n
liste simplu (dublu) înlănțuite alocate dinamic.Definiție. Fie G=(V, U)
un graf orientat. Se numeşte graf parțial al grafului G
, graful orientat G1=(V, U1)
, unde U1 ⊆ U
.
Din definiție rezultă:
G=(V,U)
, are aceeaşi mulțime de vârfuri ca şi G
, iar mulțimea arcelor este o submulțime a lui U
sau chiar U
.G=(V, U)
un graf orientat. Un graf parțial al grafului G
, se obține păstrând vârfurile şiDefiniție. Fie G=(V, U)
un graf orientat. Se numeşte subgraf al grafului G
graful orientat G1=(V1,U1)
unde V1 ⊆ V
iar U1
conține toate arcele din U
care au extremitățile în V1
.
Din definiție rezultă:
G=(V,U)
un graf orientat. Un subgraf al grafului G
, se obține ştergând eventual anumiteExemplu:
Graful inițial | Graf parțial | Subgraf |
![]() |
![]() |
![]() |
S-au eliminat arcele (1,6) , (3,2) , (6,4) |
S-a eliminat nodul 6 și toate arcele incidente cu el. |
Definiție. Fie G=(V, U)
un graf orientat. Graful G
se numește graf complet dacă oricare două vârfuri
distincte ale sale sunt adiacente.
Două vârfuri x
și y
sunt adiacente dacă:
(x,y)
, sau(y,x)
, sau(x,y)
şi (y,x)
.Exemplu:
Teoremă: Numărul de grafuri orientate complete cu n
noduri este 3
n*(n-1)/2
.
Definiție: Un graf orientat este turneu, dacă oricare ar fi două vârfuri i
şi j
, i≠j
, între ele există un singur arc: arcul (i,j)
sau arcul (j,i)
.
Exemplu:
Proprietăți:
2
n*(n-1)/2
grafuri turneu cu n
noduri.Definiție: Fie G=(V, U)
un graf orientat. Se numește lanț, în graful G, o succesiune de arce, notată
L = (u
1
, u
2
,..., u
k
)
cu proprietatea ca oricare două arce consecutive au o extremitate comună (nu are importanță orientarea arcelor).
sau
Definiție: Fie G=(V, U)
un graf orientat. Se numește lanț, în graful G, o succesiune de noduri, notată
L = (x
1
, x
2
,..., x
p
)
cu proprietatea ca oricare două noduri consecutive sunt adiacente.
Lungimea unui lanț este egală cu numărul de arce din care este alcătuit.
Primul nod și ultimul nod dintr-un lanț formează extremitățile lanțului.
Definiție. Fie G=(V, U)
un graf orientat. Se numește drum în graful G
o succesiune de noduri, notată
D = (x
1
, x
2
,..., x
k
)
, cu proprietatea că pentru orice 1≤i<k
, (x
i
,x
i+1
)
este arc în G
.
Lungimea unui drum este egală cu numărul de arce din care este alcătuit.
Pentru un drum D = (x
1
, x
2
,..., x
k
)
, nodurile x
1
și x
k
reprezintă extremitățile – inițială, respectiv finală.
Un lanț (drum) se numește elementar dacă în el nu se repetă noduri. Un lanț (drum) se numește simplu dacă în el nu se repetă arce.
Exemple În graful alăturat:
L=(5,4,2,6,1)
este un lanț elementar, dar nu este drum.
D=(3,2,1,6,4)
este drum elementar.
D=(3,2,1,6,2,4)
este drum neelementar, dar simplu.
Definiție: Se numește circuit un drum simplu în care extremitatea inițială și finală sunt egale. Se numește circuit elementar un circuit în care, cu excepția extremităților, nu se repetă noduri.
Lungimea unui circuit este reprezentată de numărul de arce din care acesta este alcătuit.
Exemple În graful alăturat:
(1,6,2,1)
și (1,6,4,2,1)
sunt circuite elementare.
Definiții: Fie G=(V,U)
un graf orientat.
Graful se numește tare conex dacă între oricare două noduri distincte există cel puțin un drum.
Se numește componentă tare conexă un subgraf tare conex și maximal cu această calitate – dacă am mai adauga un nod, n-ar mai fi tare conex.
Exemplu:
Graful de mai sus nu este tare conex. El conține trei componente tare conexe:
1 3 4
2
5 6 7 8
Observație: Un nod al grafului face parte dintr-o singură componentă tare conexă. Dacă ar face parte din două compoennte tare conexe, ele s-ar “reuni” prin intermediul acelui nod.
Acest articol conține mai multe detalii despre tare-conexitate (algoritmi de verificare a tare conexității, de determinare a componentelor tare-conexe, etc.).
Definiții: Fie un graf orientat G=(V,U)
.
Un drum elementar care conține toate nodurile grafului se numește drum hamiltonian.
Un circuit elementar care conține toate nodurile grafului se numește circuit hamiltonian.
Un graf care conține un circuit hamiltonian se numește graf hamiltonian.
Exemplu: Graful orientat desenat mai jos este hamiltonian, deoarece con ține circuitul hamiltonian (2, 1, 5 , 6, 4, 3, 2)
.
Definiții: Fie un graf orientat G=(V,U)
.
Un drum care conține toate arcele grafului se numește drum eulerian.
Un circuit care conține toate arcele grafului se numește circuit eulerian.
Un graf care conține un circuit eulerian se numește graf eulerian.
Teoremă: Un graf fără noduri izolate este eulerian dacă și numai dacă este conex și pentru fiecare nod, gradul interior este egal cu cel exterior.
Exemplu: Graful orientat de mai jos este eulerian.