Sunteți pe pagina 1din 44

Capitolul 4.

ALGORITMI DE PRELUCRARE A GRAFURILOR

4.1. Parcurgerea grafurilor neorientate ......................................................................................... 2


4.1.1. Metoda de parcurgere BF (Breadth First – ”în lățime”) ...................................................... 2
4.1.2. Metoda de parcurgere DF (Depth First – ”în adâncime”).................................................... 6
4.2. Conexitate. Componente conexe .............................................................................................. 9
4.3. Grafuri hamiltoniene .............................................................................................................. 14
4.4. Grafuri euleriene ..................................................................................................................... 18
4.5. Arborele parțial de cost minim .............................................................................................. 23
4.5.1. Algoritmul lui Kruskal........................................................................................................ 23
4.5.2. Algoritmul lui Prim ............................................................................................................. 26
4.6. Tare conexitate ........................................................................................................................ 29
4.7. Drumuri minime și maxime în grafuri orientate .................................................................. 35
4.7.1. Algoritmul lui Roy – Floyd – drumuri minime între oricare vârfuri ............................. 36
4.7.2. Algoritmul lui Dijkstra – drumuri minime de sursă unică.............................................. 42
Capitolul 4. ALGORITMI DE PRELUCRARE A GRAFURILOR

4.1. Parcurgerea grafurilor neorientate

Parcurgerea grafurilor neorientate reprezintă o modalitate de a ajunge o singură dată


în fiecare nod al grafului, pornind de la un nod dat i și parcurgând muchii adiacente. Trecerea
de la un nod la altul se face într-o anumită ordine, dată de un anumit criteriu. Această acțiune
reprezintă vizitarea sau traversarea nodurilor grafului, scopul acestei vizitări fiind acela de
prelucrare a informației asociată nodurilor.
În continuare, prezentăm cele mai cunoscute metode de parcurgere a grafurilor neorientate, și
anume parcurgerea BF (”în lățime”) și parcurgerea DF(”în adâncime”).

4.1.1. Metoda de parcurgere BF (Breadth First – ”în lățime”)

I. Activități de învățare
Principiul metode este următorul:
- se vizitează mai întâi nodul de plecare, fie acesta i;
- se vizitează apoi toate nodurile adiacente cu nodul i, fie acestea j1, j2, ...., jk, în această
ordine;
- se vizitează toate nodurile adiacente cu nodul j1, apoi cu j2, ...., apoi cu jk;
- .....
- algoritmul continuă în acest mod până când au fost vizitate toate nodurile.

Exemplu: considerăm graful din figura de mai jos pentru care aplicăm parcurgerea BF,
pornind de la noduri diferite:

Parcurgerea BF
De la nodul 1: 1 2 3 4 6 5 7 8
De la nodul 2: 2 1 6 3 4 5 7 8
De la nodul 3: 3 1 5 2 4 7 8 6
De la nodul 4: 4 1 6 2 3 5 7 8
De la nodul 5: 5 3 7 8 1 2 4 6
De la nodul 6: 6 2 4 1 3 5 7 8
De la nodul 7: 7 5 3 8 1 2 4 6
Figura 4.1.1.1 De la nodul 8: 8 5 3 7 1 2 4 6
Descriere algoritm: Strategia parcurgerii BF funcționează respectând mecanismul de tip
FIFO. Ea se bazează pe traversarea tuturor muchiilor disponibile din nodul curent către
nodurile adiacente nedescoperite, care vor fi astfel vizitate. După acest proces, nodul explorat
este scos din coadă, prelucrându-se succesiv toate nodurile ajunse ăn vârful cozii.

În construcția practică a algoritmului, trebuie ales la un moment dat, dintre toți vecinii unui
nod, acela care nu a fost vizitat încă. Pentru a face posibil acest lucru folosim un vector
VIZITAT de dimensiune n, definit astfel: pentru  j  {1, 2, …, n}

0, 𝑑𝑎𝑐ă 𝑛𝑜𝑑𝑢𝑙 𝑗 𝑛𝑢 𝑎 𝑓𝑜𝑠𝑡 𝑣𝑖𝑧𝑖𝑡𝑎𝑡


VIZITAT[j]=
1, 𝑑𝑎𝑐𝑎 𝑛𝑜𝑑𝑢𝑙 𝑗 𝑎 𝑓𝑜𝑠𝑡 𝑣𝑖𝑧𝑖𝑡𝑎𝑡

Pentru implementarea algoritmului folosim o structură de tip coadă (implementată static


printr-un vector c) în care prelucrarea unui nod i aflat la un capăt al cozii (vârful cozii) constă
în introducerea la celălalt capăt al cozii (coada cozii) a tuturor nodurilor j vecine cu i,
nevizitate încă.

Folosim următoarele notații:


- p – poziția primului element din coadă;
- u – poziția ultimului element din coadă.
Algoritmul în pseudocod: elevii pot lucra pe grupe de câte 2 sau 3 pentru a încerca descrierea
pas cu pas a algoritmului de parcurgere în lățime, conform prezentărilor teoretice.

Pas 1: pentru j=1,n execută VIZITAT[j]←0 (inițial nici un nod nu a fost vizitat).
Pas 2: se prelucrează nodul de pornire i:
2.1. C[1] ←i (se adaugă în coadă nodul i);
2.2. VIZITAT[i] ←1 (nodul i se marchează ca vizitat);
Pas 3: cât timp coada nu este vidă (p≤u) execută
3.1. i← C[p] (prelucrăm nodul i aflat la începutul cozii);
3.2. pentru toți vecinii j ai lui i, nevizitați încă, execută
3.2.1. u←u+1; C[u] ←j (se adaugă nodul j în coadă);
3.2.2. VIZITAT[j] ←1 (se marchează nodul ca vizitat);
3.3. p←p+1 (se reia de la pasul 3.1. unde se trece la următorul element ce va fi scos
din coadă);
Pas 4: se afișează coada.
Complexitatea algoritmului: Algoritmul necesită o memorie O(n) pentru tabloul VIZ și tot la
fel pentru coada C. Cazul cel mai nefavorabil este cel în care vârful inițial i este conectat cu
toate celelalte vârfuri ale grafului. În acest caz, în coadă se vor afla n-1 vârfuri.
Se poate verifica corectitudinea algoritmului, în sensul că a fost prelucrat, o singură dată,
fiecare vârf. Primul ciclu for (inițializarea) necesită un timp O(n).
Ciclul while se execută de cel mult n-1 ori ( i este vizitat anterior). În corpul ciclului while
există un nou ciclu for ce contine selecția simplă if care se va executa de câte n ori la fiecare
reluare a lui while. Prin acest for se parcurge linia corespunzătoare vârfului analizat din
matricea de adiacență.
Celelalte instructiuni prezente în algoritm (de asignare, incrementare) nu afectează ordinul de
complexitate al algoritmului .
Asadar, complexitatea algoritmului BF, în cazul reprezentării grafului prin matricea sa de
adiacenta, este O(n(n-1)+n) = O(n2).
Dacă graful G este reprezentat prin liste de adiacență, ciclul initial se executa tot în
O(n) timpi. Lista Lj, adică lista vecinilor vârfului j, va conține dj (valența vârfului j) elemente.
Prin urmare, la fiecare execuție a lui while, ciclul for din corpul său, se va executa într-un
timp cdj, unde c este o constantă, iar j reprezintă vârful care urmează a fi prelucrat și scos din
C. Se stie că 𝑑𝑘 =2m. Deci timpul total este O(n+m).
II. Exerciții de fixare a cunoștințelor teoretice

a. Sarcină de lucru 1- elevii vor lucra individual, pe caiet, la clasă

Întrebări cu răspuns scurt:


1) Definiți principiile parcurgerii în lățime a unui graf. În ce structură de date se
memorează nodurile parcurse?
2) Cum se marchează nodurile deja parcurse?
3) Care este timpul de calcul pentru algoritmul de parcurgere în lățime a unui graf?

Alegeți răspunsul corect:


1) Fie graful G=(X,U) din figura de mai jos. Care dintre următoarele succesiuni de
noduri reprezintă o parcurgere în lățime a grafului G?

a. 2 1 3 4 6 7 5
b. 1 2 5 3 7 4
c. 2 1 5 7 3 4 6
d. 1 5 3 2 7 6 4

Figura 4.1.1.2

2) Se consideră graful G=(X,U) în care U={(1,2), (1,3), (1,4), (2,5), (3,5), (5,7), (6,7),
(6,8)}. Care este parcurgerea în lățime dacă se consideră nodul 1 ca nod de plecare?
a. 1 2 3 4 5 6 7 8 c. 1 2 3 4 5 7 6 8
b. 1 2 5 7 6 8 3 4 d. 1 2 3 4 5 7 8 6

b. Sarcină de lucru 2: Fie G=(X,U) un graf neorientat memorat prin matricea de


adiacență. Realizați un program C++ care să implementeze algoritmul de parcurgere în BF.

Activitatea se poate desfășura individual la calculator urmând algoritmul descris în lecția


teoretică.

(Vezi Anexa 1)
4.1.2. Metoda de parcurgere DF (Depth First – ”în adâncime”).

I. Activități de învățare

Principiul metodei este următorul:

- se vizitează mai întâi nodul de plecare, fie acesta i;


- se vizitează apoi primul dintre vecinii nevizitați ai lui i, fie acesta j;
- se vizitează apoi primul dintre vecinii nevizitați ai lui j;
- ....
- algoritmul continuă în acest mod, iar atunci când nu mai este posibil acest lucru, se
face un pas înapoi spre nodul din care am plecat ultima dată, și plecăm, dacă este
posibil spre următorul vecin nevizitat incă. Ori de câte ori suntem în impas, facem un
pas înapoi, dar atâta timp cât și acest lucru este posibil.
De aici se deduce că parcurgerea unui graf în adâncime este o problemă specifică tehnicii
backtracking.

Se observă un procedeu recursiv de parcurgere a grafului. Această tehnică de parcurgere


conduce la efectuarea unui număr relativ mare de apeluri recursive înainte de a se întoarce
dintr-un apel.
Exemplu: considerăm același graf ca la parcurgerea BF și aplicăm parcurgerea DF:

Parcurgerea DF
De la nodul 1: 1 2 6 4 3 5 7 8
De la nodul 2: 2 1 3 5 7 8 4 6
De la nodul 3: 3 1 2 6 4 5 7 8
De la nodul 4: 4 1 2 6 3 5 7 8
De la nodul 5: 5 3 1 2 6 4 7 8
De la nodul 6: 6 2 1 3 5 7 8 4
De la nodul 7: 7 5 3 1 2 6 4 8
Figura 4.1.2.1 De la nodul 8: 8 5 3 1 2 6 4 7

Descriere algoritm: Pentru implementarea algoritmului DF se folosește vectorul VIZITAT


cu aceeași semnificație ca și la algoritmul BF, dar se înlocuiește coada cu o stivă S care ne
permite în fiecare moment să plecăm de la nodul curent la primul dintre vecinii săi nevizitați,
acesta fiind plasat în vârful stivei. Cu acest nod din vârful stivei se continuă în același mod.
Pentru a ști care va fi următorul nod ce va fi vizitat după nodul j (când acesta există), folosim
un vector URM. Pentru a găsi nodul, se parcurge linia j din matricea de adiacență A asociată
grafului, până este găsit un vecin al lui j nevizitat încă. Dacă este găsit, este introdus în vârful
stivei și se mărește corespunzător și ”pointerul de stivă” p. Dacă sunt mai mulți vecini, se
alege acela care are cel mai mic număr de ordine. Dacă nu este găsit un astfel de nod, se
coboară în stiva (p se micșorează cu 1) pentru a aplica același procedeu următorului element
din S.

Algoritmul în pseudocod: elevii pot lucra pe grupe de câte 2 sau 3 pentru a încerca
descrierea pas cu pas a algoritmului DF, conform prezentărilor teoretice.
Pas 1: pentru j=1,n execută
VIZITAT[j]←0 (inițial nici un nod nu a fost vizitat)
URM[j]←0 (pornind din 0, determinăm prima valoare posibilă pentru
primul nod spre care se poate pleca din nodul j )
Pas 2: se prelucrează nodul de pornire i:
2.1. S[1] ←i (se adaugă în stivă nodul i);
2.2. VIZITAT[i] ←1 (nodul i se marchează ca vizitat);
2.3. p←1 (poinerul de stivă p pointează spre elementul din vârful stivei);
Pas 3: cât timp stiva nu este vidă (p≥1) execută
3.1. j← S[p] (scoate nodul din vârful stivei);
3.2. determină primul dintre vecinii k ai lui j, nevizitați încă: URM[j] ←k
3.3. daca nu există un asemenea k atunci p←p-1
altfel
scrie k;
VIZITAT[k] ←1 (vârful k este vizitat);
p←p+1 (se mărește poinerul de stivă pentru a-l memora în stivă pe k);
S[p] ←k (vârful k devine nodul ce trebuie analizat).

Complexitatea algoritmului: Complexitatea timp a algoritmilor de parcurgere a unui graf


depinde de modalitatea de reprezentare a acestuia.
Astfel, ordinul de complexitate al algoritmului DF este O(n2) dacă se foloseşte matricea de
adiacenţă. Practic, pornind de la un nod i, se caută pe linia i a matricei toate nodurile
adiacente cu el și pentru toate cele găsite se procedează în mod analog.
Dacă se folosesc listele de adiacenţă, ordinul de complexitate este O(m). Pornind de la un nod
i, se caută toate nodurile adiacente cu el, dar acestea sunt deja grupate în lista de adiacență
asociată nodului respectiv și numărul lor corespunde numărului de muchii incidente acestuia.
Algoritmul va selecta, pe rând, toate muchiile, de unde complexitatea O(m).
II. Exerciții de fixare a cunoștințelor teoretice

a. Sarcină de lucru 1- elevii vor lucra individual, pe caiet, la clasă

Întrebări cu răspuns scurt:


1) Definiți principiile parcurgerii în adâncime a unui graf. În ce structură de date se
memorează nodurile parcurse?
2) Cum se marchează nodurile deja parcurse?
3) Care este timpul de calcul pentru algoritmul de parcurgere în adâncime a unui graf?

Alegeți răspunsul corect:


1) Fie graful G=(X,U) din figura de mai jos. Care dintre următoarele succesiuni de
noduri reprezintă o parcurgere în lățime a grafului G?

a. 2 1 3 4 5 7 6
b. 1 2 5 7 3 4
c. 2 1 3 4 6 7 5
d. 2 1 3 4 6 5 7

Figura 4.1.2.2
2) Se consideră graful G=(X,U) în care U={(1,2), (1,3), (1,4), (2,5), (3,5), (3,6), (4,6),
(5,7), (6,7), (6,8)}. Care este parcurgerea în adâncime pentru graful dat, dacă se consideră ca
nod de plecare nodul 2?
a. 12345678 c. 12354678
b. 12536478 d. 13254687
3) Se consideră graful G=(X,U) în care U={(1,2), (1,3), (2,3), (3,4), (4,5), (4,6)}. Care
sunt nodurile de plecare pentru care parcurgerile în lățime și în adâncime nu coincid?
a. 1 2 b. 4 2 c. 4 5 6 d. 3 4
b. Sarcină de lucru 2: Fie G=(X,U) un graf neorientat memorat prin matricea de
adiacență. Realizați un program Pascal/C++ care să implementeze algoritmul de parcurgere
în DF.
Activitatea se poate desfășura individual la calculator urmând algoritmul descris în lecția
teoretică.
(Vezi Anexa 2)
4.2. Conexitate. Componente conexe

I. Activități de învățare

Un graf G=(X,U) se numește conex dacă oricare ar fi două noduri x și y din graf, există un
lanț care le leagă.
Se numește componentă conexă a grafului G=(X,U), un subgraf conex G1=(X1,U1) al lui G,
cu proprietatea că nu exisă niciun lanț care să lege un nod din X1 cu un nod din X- X1.

Exemplu: Pentru graful di figura 4.2.1 b) avem trei componente conexe: C1={1, 4},
C2={2, 3, 6}, C3={5}

a) b)
Graf conex Graf neconex
Figura 4.2.1

Observații:
- Dacă numărul componentelor conexe dintr-un graf este mai mare decât 1, atunci graful
nu este conex.
- Un graf conex are o singură componentă conexă care conține toate nodurile grafului.
Sarcină de lucru: Fie G=(X,U) un graf neorientat cu n noduri și m muchii. Să se realizeze un
algoritm care să decidă dacă graful dat este sau nu conex.
Descriere algoritm: Pentru a verifica conexitatea unui graf se pornește de la o observație
evidențiată și anume ” Un graf conex are o singură componentă conexă care conține toate
nodurile grafului”. Altfel spus, dacă în urma unei parcurgeri (BF sau DF) s-au vizitat toate
nodurile, indiferent de nodul de plecare, atunci graful este conex.
Folosind algoritmul de parcurgere BF, utilizăm două structuri de date:
- o coadă C care va memora pe rând nodurile vizitate, în ordinea vizitării lor
- un vector viz care marchează nodurile vizitate cu 1 și pe cele nevizitate cu 0.
După parcurgere, se testează dacă în vectorul viz există vreun element 0, caz în care graful nu
este conex. Dacă toate elementele sunt 1, graful este conex.
II. Exerciții de fixare a cunoștințelor teoretice

a. Sarcină de lucru 1- elevii vor lucra individual, pe caiet, la clasă

Completați răspunsul corect:

1) Fie graful din figura de mai jos:

Figura 4.2.2
a. Numărul de componente conexe este ................................
b. Numărul minim de muchii care trebuie adăugate pentru ca graful să devină
conex este ........ iar muchiile sunt (precizati o variantă): .................................................
2) Fie un graf neorientat cu n noduri și p componente conexe. Numărul minim de muchii
care trebuie adăugate pentru ca graful să devină conex este ..........
3) Fie un graf neorientat cu n coduri.
a. Numărul minim de componente conexe este ......... iar numărul de muchii
trebuie să fie cel puțin .................
b. Numărul maxim de componente conexe este ..... iar numărul de muchii este .....

Alegeți răspunsul corect:


1) Fie graful din figura de mai jos:

Figura 4.2.3
2) Fie un graf neorientat cu n noduri, m muchii și p componente conexe. Care din
următoarele relații este adevărată:
a. m ≤ (n-p)2 c. 2m ≤ (n-p+1) (n-p)
2
b. 2m ≤ (n-p) + (n-p) d. n+p >m

b. Sarcină de lucru 2: Algoritmul de verificare a conexitătii unui graf, implementat


în limbajele de programare Pascal / C++
Elevii vor lucra individual la calculator.
(Vezi Anexa 3)
Sarcină de lucru 3: Fiind dat un graf neorientat G=(X,U) cu n noduri, să se afișeze nodurile
fiecărei componente conexe.

Descriere algoritm:
Elevii pot lucra individual sau pe grupe de câte 2.

Pasul 1: Se inițializează numărul de componente conexe: nc=0


Pasul 2: Se repetă până când nu mai există noduri nevizitate, următoarele operații:
Pasul 2.1. Se caută un nod nevizitat
Pasul 2.2. Se mărește cu 1 numărul de componente conexe: nc=nc+1
Pasul 2.3. Se parcurg și se afișează nodurile accesibile din acest nod, fosolind
parcurgerea DF (acestea formează componenta conexă nc)

Implementarea în limbaj de programare a algoritmului: Elevii vor lucra individual


fiecare la calculator.

(Vezi Anexa 4)

Sarcină de lucru 4: Fiind dat un graf neorientat G=(X,U), să se descompună în componente


conexe, apoi să se adauge un număr minim de muchii astfel încât graful să devină conex.
Numărul de noduri și matricea de adiacență aferentă grafului, se citesc dintr-un fișier text care
conține pe primul rând numărul de noduri n și numărul de muchii m, separate prin spațiu,
apoi, pe fiecare din următoarele m rânduri, două valori, separate prin spațiu, ce reprezintă
extremitățile unei muchii.

Descrierea algoritmului: considerăm ca exemplu, graful din figura de mai jos:

Figura 4.2.4
Componentele conexe ale acestui graf sunt: C1={1, 2, 3, 4}, C2={5, 6, 7}, C3={8}

Memorăm într-un vector L numărul de ordine al componentei conexe din care face parte
fiecare nod al grafului. Astfel, pentru exemplul dat, vectorul L va fi următorul:

i 1 2 3 4 5 6 7 8
L[i] 1 1 1 1 2 2 2 3
Pentru a transforma graful într-un graf conex, trebuie să avem o singură componentă conexă.
Pentru aceasta, vom lua un nod x din componenta C1 și-l vom uni, pe rând, cu câte un nod din
celelalte componente conexe. Practic, unim nodul 1 din C1 cu nodul 5 din C2, respectiv cu
nodul 8 din C3, în final obținându-se graful din figura de mai jos:

Figura 4.2.5

Algoritmul în pseudocod

Pasul 1: descompunem graful în componente conexe și reținem în vectorul L numărul de


ordine al componentei conexe din care face parte fiecare nod i
Pasul 1.1. inițializăm vectorul L cu 0
Pasul 1.2. alegem ca nod de plecare nodul x=1
Pasul 1.3. se actualizează componenta conexă nc și parcurgem graful în lățime,
plecând de la nodul x si introducem nodurile în componenta conexa nc
Pasul 1.4. căutăm primul nod x care nu a fost deja inclus într-o componentă conexă
Pasul 1.5. dacă mai există un astfel de nod, se revine la pasul 1.3.
Pasul 2: adăugăm muchiile necasare transformării grafului în graf conex
Pasul 2.1. căutăm primul nod x care face parte din componenta C1
Pasul 2.2. căutăm primul nod y care face parte din componenta C2
Pasul 2.3. unim x cu y, afișăm muchia adăugată și revenim la pasul 2.2. până când se
unște x cu primul nod y din componenta nc

Implementarea în limbaj de programare a algoritmului: Elevii vor lucra individual


fiecare la calculator sau pe grupe la laborator.

(Vezi Anexa 5)

Sarcină de lucru 5: Fiind dat un graf neorientat conex G=(X,U) cu n noduri și m muchii citite
de la tastatură, să se determine un nod p care poate fi eliminat astfel încât subgraful generat de
mulțimea X-{p} să rămână conex.

Descriere algoritm: Pentru a obține un subgraf, trebuie să eliminăm un nod p și toate


muchiile incidente cu acesta.

Plecăm dintr-un nod oarecare, fie acesta start. Ne deplasăm pe muchii, trecând prin noduri
nevizitate încă, atâta timp cât este posibil. Notăm cu p nodul în care ne aflăm în fiecare
moment. Când din p nu ne mai putem deplasa folosind noduri nevizitate, înseamnă că nodul p
poate fi eliminat, împreună cu muchiile incidente cu acesta, deoarece el nu afectează
conexitatea grafului.

Exemplu: Fie graful din figura 4.2.6 a)

a) b)
Figura 4.2.6

Considerăm nodul de plecare start=4. În acest caz deplasarea se face pe traseul: 4 1 2 3.


Ajunși în nodul 3 nu se mai poate merge la nodul 5 prin noduri nevizitate, motiv pentru care
se elimină nodul 3 împreună cu muchiile incidente cu acesta: (3,2) și (3,4), iar subgraful
rezultat este cel din figura 4.10. b). Traseul parcurs (4, 1, 2, 3) care este un lanț se micșorează
astfel cu o muchie devenind (4, 1, 2) și rămâne nu dispare. În mod asemănător se întâmplă și
cu celelalte lanțuri ale grafului inițial. Astfel, atributul conexității, conform căreia oricare ar fi
două noduri există un lanț care le leagă, se păstrează și în subgraful obținut.

Implementarea în limbaj de programare: Elevii vor lucra individual fiecare la calculator


sau pe grupe la laborator.

(Vezi Anexa 6)
4.3. Grafuri hamiltoniene

I. Activități de învățare

În 1857, matematicianul irlandez William Hamilton a inventat Jocul Icosian care consta
în găsirea unui ciclu care să treacă o singură dată prin fiecare din cele 20 de vârfuri ale unui
dodecaedru (făcut din lemn și care avea în fiecare vârf câte un cui cu o floare mare),
deplasarea făcându-se pe muchiile dodecaedrului.

Figura 4.3.1

Soluția acestei probleme este: C={4,5,10,15,20,19,14,9,13,18,17,16,11,6,1,2,7,12,8,3,4}

Într-un graf G=(X,U), un ciclu elementar care conține toate nodurile grafului se
numește ciclu hamiltonian.

Un graf care admite un ciclu hamiltonian se numește graf hamiltonian.

Un lanț elementar care conține toate nodurile grafului se numește lanț hamiltonian.

Evemplu: graful din figura 4.3.2. a) este hamiltonian deoarece conține un ciclu hamiltonian
C={1, 2, 3, 5, 4, 1}, pe când graful din figura 4.12. b) nu este hamiltonian.

a) b)
Graf hamiltonian Graful nu este hamiltonian
Figura 4.3.2
O problemă înrudită cu aceea a găsirii într-un graf a unui ciclu hamiltonian este:

Problema comis-voiajorului. Un voiajor comercial trebuie să prezinte în n orașe produsele


firmei pe care o reprezintă, după care se întoarce în orașul din care a plecat. Cunoscându-se
costul deplasăriimîntre oricare două dintre cele n orașe, se cere să se determine un traseu care
să viziteze o singură dată cele n orașe și care să aibă costul total minim.

Cu alte cuvinte, problema cere să se determine un ciclu hamiltonian de cost minim în graful
Kn ale cărui noduri sunt cele n orașe iar costul unui ciclu este suma costurilor muchiilor sale.

Uneori, în anumite condiții, se poate decide dacă un graf este hamiltonian.

Teoremă: Graful complet Kn este hamiltonian.

Teoremă: Dacă G=(X,U) este un graf cu n≥3 noduri, astfel încât gradul fiecărui vârf xЄX
satisface condiția d(x) ≥ n/2 atunci garful G este hamiltonian.

Pentru rezolvarea algoritmică a problemei existenței unui ciclu hamiltonian se


folosește metoda backtracking care rezolvă problema într-un timp exponențial, deoarece nu
se cunosc algoritmi care să rezolve problema în timp polinomial.

O asemenea problemă, pentru care nu se cunosc algoritmi de complexitate polinomială iar


algoritmii cunoscuți se caracterizează printr-o complexitate exponențială a calculelor
efectuate pe orice drum care descrie ramificațiile procesului de căutare a soluției, se numește
problemă NP (nedeterminist polinomial) – completă. Problema determinării unui ciclu
hamiltonian este NP – completă.

Algoritmul care determină toate ciclurile hamiltoniene existente într-un graf

Ciclurile se generează utilizând un vector st cu n componente.

Condițiile interne ce trebuie satisfăcute sunt următoarele:

- st[i] ≠ st[j], ∀ (i,j) ∈ {1, 2, …, n}× {1, 2, …, n}, i ≠ j


- (st[1],st[2]), (st[2],st[3]), …, (st[n-1],st[n]) Є U și (st[n],st[1]) Є U

Pentru a nu genera de mai multe ori același ciclu, fixăm st[1]=1.

Pas 1. sol=0 {numără ciclurile hamiltoniene}


st[1]=1 {se fixează primul nod al ciclului}
k=2
st[2]=1 {se pune în st[2] valoarea 1 din care se va deduce prima valoare posibilă a
acestei componente}
Pas 2. cât timp k>1 execută
ok=0
cât timp (st[k]+1<=n) și (ok=0) execută
st[k]=st[k]+1
ok=1 {poziționarea elementului în stiva soluție este bună}
pentru i=1,k-1 execută
dacă st[i]=st[k] atunci {nodurile trebuie să fie distincte}
ok=0
sfârșit_dacă
sfârșit_pentru
dacă a[st[k-1],st[k]]=0 atunci {se testează existența
muchiei}
ok=0
sfârșit_dacă
sfârșit_cât_timp
dacă v=0 atunci k=k-1
altfel
dacă (k=n) și (a[st[n],1]=1) atunci
sol=sol+1
scrie ciclul hamiltonian
altfel
dacă k<n atunci
k=k+1 {se merge la următoarea componentă, care se
inițializează cu 1}
st[k]=1
sfârșit_dacă
sfârșit_dacă
sfârșit_dacă
sfârșit_cât_timp
II. Exerciții de fixare a cunoștințelor teoretice

a. Sarcină de lucru 1 – elevii se pot organiza în grupe mici sau pot lucra individual.

Completați răspunsul corect:


Fie graful din figura de mai jos:

Figura 4.3.3
1) Secvența de noduri (2, 3, 5, 6, 1, 4, 2) este un ......................................... și are lungimea ....
2) În general, lungimea unui ciclu hamiltonian este egală cu .....................
3) Lanțul hamiltonian (1, 4, 3, 2, 5, 6) este lanț .......................................... și are lungimea .....
4) În general, lungimea unui lanț hamiltonian este egală cu .....................
5) Fie graful parțial G1 obținut prin eliminarea muchiilor (2,3) și (2,5). Secvența (3,5,6,1,4,2)
este lanț hamiltonian? Graful G1 este hamiltonian?

Alegeți răspunsul corect:


1) Fie G un graf complet cu n=5 noduri. Care este numărul minim de muchii ce trebuie
eliminate astfel încât graful să fie hamiltonian?
a. 0 b. 1 c. 2 d. 4
2) Care este numărul maxim de muchii pe care-l poate avea un graf hamiltonian cu 6
noduri?
a. 12 b. 13 c. 14 d. 15
3) Fie un graf nu n=7 noduri și m=21 muchii. Numărul de cicluri hamiltoniene din graf este:
a. 10 c. 720
b. 360 d. 20

b. Sarcină de lucru 2 – Implementați în limbaj de programare Problema comis


voiajorului
Activitatea se va desfășura individual la calculator urmând algoritmul descris în lecția
teoretică sau pe grupe, la laborator
4.4.Grafuri euleriene

I. Activități de învățare

Fie un graf G=(X,U). Un ciclu care conţine toate muchiile grafului se numeşte ciclu
eulerian. Un graf care conține un ciclu eulerian se numeşte graf eulerian.
Lanțul eulerian este un lanț simplu care conține toate muchiile grafului.

Exemplu: Graful din figura 4.4.1 a) este graf eulerian deoarece conține un ciclu eulerian
C=(1, 3, 2, 5, 3, 4, 5, 1), pe când graful din figura 4.4.1 b) nu este eulerian.

a) b)
Graf eulerian Graful nu este eulerian
Figura 4.4.1

Primul care s-a ocupat de acest tip de grafuri a fost Leonard Euler. El a plecat de la o
problemă practică și anume, ”Problema celor 7 poduri din Koenigsberg” (Poduri peste rîul
Pregel din Koenigsberg – localitate din Prusia sec. XVIII ): Să se găsească dacă este posibil,
o modalitate de traversare a tuturor celor 7 poduri (notate de la “a” la “g”) parcurgîndu-le o
singură dată pe fiecare, cu reîntoarcere în locul de plecare. Notatia “A” – “D” reprezinta
portiuni de uscat
Problema podurilor poate fi “transcrisă” folosind notaţii specifice teoriei grafurilor
prin utilizarea unor segmente (ce marchează calea de urmat) şi cercuri (ce marchează
intersecţia unor căi figurate prin segmente).

Transcrierea “Problemei celor 7 poduri”


prin folosirea grafurilor
Faptul că un graf este eulerian nu înseamnă că nu are noduri izolate.

Teoremă: Un graf G=(X,U), fără vârfuri izolate, este eulerian dacă şi numai dacă este conex
şi gradele tuturor vârfurilor sunt numere pare.

Algoritm pentru a verifica dacă un graf este eulerian

Pas 1. Calculăm gradele tuturor nodurilor


Pas 2. dacă not grad_impar atunci
dacă graf_conex atunci
scrie ”Graf eulerian”
altfel
scrie ”Graful nu este conex, deci nici eulerian”
altfel
scrie ”Graful are noduri cu grade impare, deci nu este eulerian”
II. Exerciții de fixare a cunoștințelor teoretice

a. Sarcină de lucru 1 – elevii vor lucra individual, pe caiet, la clasă

Completați răspunsul corect:


Fie graful din figura de mai jos:

Figura 4.4.2
1) Secvența de noduri (1, 5, 4, 3, 2, 5, 6, 1) este un ..................................... și are lungimea ....
2) În general, lungimea unui ciclu eulerian este egală cu .....................
3) Lanțul eulerian (3, 4, 5, 1, 6, 5, 2, 3) este lanț ........................................ și are lungimea .....
4) În general, lungimea unui lanț eulerian este egală cu .....................
5) Fie graful parțial G1 obținut prin eliminarea muchiilor (1,2) și (1,5). Secvența (1,6,5,4,3,2)
este lanț eulerian? Graful G1 este eulerian?

Alegeți răspunsul corect:


1) Fie G un graf complet cu n=4 noduri. Care este numărul minim de muchii ce trebuie
eliminate astfel încât graful să fie eulerian?
a. 0 b. 1 c. 2 d. 4
2) Care este numărul maxim de muchii pe care-l poate avea un graf eulerian cu 6 noduri?
a. 13 c. 10
b. 12 d. 15
3) Dacă într-un graf eulerian fără noduri izolate există un ciclu eulerian care este elementar,
atunci graful este:
a. complet c. hamiltonian
b. regulat d. bipartit

b. Sarcină de lucru 2 - Implementați în limbaj de programare Algoritmul care


verifică dacă un graf este eulerian, respectiv hamiltonian
Activitatea se va desfășura individual la calculator urmând algoritmul descris în lecția
teoretică sau pe grupe, la laborator
(Vezi Anexa 7)
Sarcină de lucru 3 – Ciclu eulerian: Fie G=(X,u) un graf neorientat conex în care fiecare
nod are gradul par. Să se determine un ciclu eulerian al grafului dat.

Determinarea algoritmului: pentru a construi un ciclu eulerian se va parcurge graful


folosind o strategie asemănătoare cu cea a parcurgerii în adâncime a unui graf, strategie
care respectă mecanismul LIFO.
Înaintarea către un nod adiacent nodului curent se va face simultan cu eliminarea muchiei
respective. În felul acesta, nodurile nu vor mai fi marcate (cum erau la parcurgerea DF)
pentru a fi vizitate de mai multe ori.
De fiecare dată când nodul curent este eliminat din stivă, acesta este afișat sau salvat într-
o coadă, care va reține, în ordine, nodurile ciclului eulerian.
Procesul se repetă până când stiva devine vidă, deci toate nodurile au fost traversate.
Complexitatea algoritmului este O(m+n).

Algoritmul în pseudocod:
ciclu_euler(nod)
k=prim_vecin(nod)
cât_timp lista_vecinilor(nod)≠ ∅ execută
elimin(k,nod)
elimin(nod,k)
ciclu_euler(k)
k=următor_vecin(nod)
sfârșit_cât_timp
adaug_coada(nod)
sfârșit_ciclu_euler
Exemplu: Fie graful din figura de mai jos

Figura 4.4.3
Considerăm nodul de pornire în determinarea ciclului eulerian, nod=1.
Pasul 1: se parcurge începând cu nodul 1
Stiva=(1, 2, 3, 1)
Coada=∅
Pasul 2: iese din stivă nodul 1 și se salvează în coadă
Stiva=(1, 2, 3)
Coada=(1)
Pasul 3: se continuă parcurgerea
Stiva=(1, 2, 3, 4, 2, 5, 3)
Coada=(1)
Pasul 4: iese din stivă nodul 3 și se salvează în coadă
Stiva=(1, 2, 3, 4, 2, 5)
Coada=(1, 3)
Pasul 5: se continuă parcurgerea
Stiva=(1, 2, 3, 4, 2, 5, 4, 6, 5)
Coada=(1, 3)
Pasul 6: iese din stivă nodul 5 și se salvează în coadă
Stiva=(1, 2, 3, 4, 2, 5, 4, 6)
Coada=(1, 3, 5)
Pasul 7: toate nodurile sunt extrase din stivă și introduse în coadă
Stiva=∅
Coada=(1, 3, 5, 6, 4, 5, 2, 4, 3, 2, 1)

Pentru implementarea algoritmului în limbajul de programare, activitatea se va


desfășura individual la calculator urmând algoritmul descris sau pe grupe, la
laborator
4.5.Arborele parțial de cost minim

Fie G=(X,U) un graf neorientat conex. Fiecare muchie (i,j) are asociat un cost cij≥0.
Se cere un arbore parțial al lui G, astfel încât suma costului muchiilor sale să fie minimă.
Un astfel de arbore se numește arbore parțial de cost minim.
Pentru rezolvarea acestei probleme, vom descrie doi algoritmi algoritmul lui Kruskal și
algoritmul lui Prim, ambii algoritmi încadrându-se în tehnica Greedy.

4.5.1. Algoritmul lui Kruskal

I. Activități de învățare

Prezentare
Notăm cu n numărul de vârfuri din graf. Iniţial considerăm că nici o muchie din
graf nu a fost selectată, deci fiecare vârf din graf este vârf izolat. Cu alte cuvinte, la
momentul iniţial avem o pădure formată din n arbori, fiecare arbore fiind format dintr-un
singur vârf.
La fiecare pas se selectează o muchie de cost minim care nu a mai fost selectată şi care nu
formează cicluri cu muchiile deja selectate.
Exemplu: Fie graful din figura de mai jos:

Figura 4.5.1.1

Pasul 1: Selectăm o muchie de cost minim. (În cazul nostru, de cost 1).
În graful parţial selectat există n - 1 = 4 arbori, pentru că am unificat arborii
corespunzători extremităţilor muchiei selectate. Arborii sunt: { 1, 3 }; { 2 }; { 4 }; { 5 }.
Pasul 2: Selectăm din nou o muchie de cost minim.(Costul minim fiind 1).
În graful parţial selectat există n - 2 = 3 arbori. Arborii sunt: { 1, 3, 4 }; { 2 }; { 5 }.
Pasul 3: La acest pas nu mai putem selecta o muchie de cost 1, deoarece s-ar
obţine un ciclu. Selectăm muchia de cost 2. Arborii sunt: { 1, 2, 3, 4 }; { 5 }.
Pasul 4: Selectând, în final, muchia de cost 3, obţinem un graf fără cicluri cu n-1
muchii, deci un arbore.

Algoritmul în pseudocod
Pas 1. Se ordonează crescător vectorul G, în funcție de costuri
Pas 2. pentru j=1,n execută C[j]=j {inițial, fiecare nod face parte dintr-un
subarbore}
Pas 3. cost=0 {costul total}
k=0 {numără muchiile selectate}
i=1 {prima muchie posibil a fi selectată}
Pas 4. cât timp k<n-1 execută
dacă C[G[i].x]<>C[G[i].y] atunci
k=k+1; scrie G[i] {alege muchia i}
cost=cost+G[i].c {actualizează costul }
nr1=C[G[i].x]
nr2=C[G[i].y]
pentru j=1,n execută
dacă C[j]=nr2 atunci
C[j]=nr1 {se unifică subarborii}
sfârșit_dacă
sfârșit_pentru
sfârșit_dacă
i=i+1 {trec la următoarea muchie}
sfârșit_cât_timp
Pas 5. scrie cost

Complexitatea algoritmului
Sortarea se face în O(m x log(m)). Pentru fiecare muchie, se testează apartenența celor
două noduri cu care este incidentă, la arbori diferiți. Această testare se face în O(log(m)).
Cum avem m muchii testate, testele se efectuează în O(m x log(m)). Deci, eficiența
algoritmului este O(m x log(m)).
II. Exerciții de fixare a cunoștințelor teoretice

a. Sarcină de lucru 1 – elevii vor lucra individual, pe caiet, la clasă

1) Pentru graful din figura de mai jos, alcătuiți un APM, folosind algoritmul lui
Kruskal.

Figura 4.5.1.2
2) Pentru graful din figura 4.5.1.3, care este graful parțial obținut după selectarea a
trei muchii prin algoritmul lui Kruskal?

Figura 4.5.1.3

a) b) c)

b. Sarcină de lucru 2 – Să se implementeze în limbaj de programare algoritmul lui


Kruskal.

Activitatea se va desfășura individual la calculator urmând algoritmul descris în lecția


teoretică sau pe grupe, la laborator.
4.5.2. Algoritmul lui Prim

I. Activități de învățare

Prezentare
Ca şi algoritmul lui Kruskal, algoritmul lui Prim utilizează o strategie Greedy.
Iniţial se pleacă de la un arbore format dintr-un singur vârf. La fiecare pas se selectează o
muchie de cost minim astfel încât mulţimea muchiilor selectate şi mulţimea vârfurilor
unite de acestea să formeze un arbore.

Exemplu: Considerăm graful din figura 4.5.1.1 şi vârful de start 5.


Pasul 1: Iniţial selectăm o muchie de cost minim care să fie incidentă cu vârful 5.
Selectăm muchia [2,5].
Pasul 2: Selectăm o muchie de cost minim, care să fie incidentă cu unul din vârfurile din
subgraful obţinut la pasul anterior. În cazul nostru selectăm muchia [1,2].
Pasul 3: Selectez o muchie de cost 1, incidentă cu unul din vârfurile din subgraful
anterior. Oricare dintre muchiile [1,3] şi [1,4] poate fi selectată. Selectăm muchia [1,3].
Pasul 4: Selectând cea de a patra muchie, muchia [3,4], obţinem un arbore parţial de cost
minim.
La fiecare pas se selectează un nou vârf, adiacent cu unul din vârfurile subgrafului, astfel
încât muchia corespunzătoare să fie de cost minim. Nodul nou adăugat va fi terminal şi
deci nu se vor obţine cicluri, iar subgraful construit este la fiecare pas conex, deci arbore.

Algoritmul în pseudocod
Pas 1. Se ordonează crescător vectorul U, în funcție de costuri
Pas 2. pentru j=1,n execută VIZ[j]=0 {inițial toate nodurile sunt nevizitate}
Pas 3. VIZ[U[1].x]=1; VIZ[U[1].y]=1 {vizitează extremitățile primei muchii}
scrie U[1] {afișează prima muchie}
cost=U[1].c {costul total}
Pas 4. pentru k=1,n-2 execută
Pas 4.1. caut muchia de cost minim cu o singură extremitate
vizitată; fie aceasta i
Pas 4.2. scrie U[i]
vizitează cealaltă extremitate
cost=cost+U[i].c {actualizează costul}
sfârșit_pentru
Pas 5. scrie cost
Complexitatea algoritmului
Graful va fi indus prim matricea costurilor. Muchia (i,j) de cost minim va fi căutată
pentru toate nodurile cu VIZ[i]=1 și VIZ[j]=0, de unde rezultă că identificarea unei
muchii se face în O(n2). Cum se selectează n-1 muchii, algoritmul va avea complexitatea
O(n3).

II. Exerciții de fixare a cunoștințelor teoretice

a. Sarcină de lucru 1 – elevii vor lucra individual, pe caiet, la clasă


3) Pentru graful din figura de mai jos, alcătuiți un APM, folosind algoritmul lui
Prim.

Figura 4.5.2.1
4) Algoritmul lui Prim a selectat după primul pas muchiile selectate în figura 4.5.2.2.
Care va fi următoarea muchie selectată?

Figura 4.5.2.2
a) (1,2) b) (4,5) c) (1,5)
5) Pentru graful din figura 4.5.2.3, care este graful parțial obținut după selectarea a trei
muchii prin algoritmul lui Prim?

Figura 4.5.2.3
a) b) c)

b. Sarcină de lucru 2 – Să se implementeze în limbaj de programare algoritmul lui Prim.

Activitatea se va desfășura individual la calculator urmând algoritmul descris în lecția


teoretică sau pe grupe, la laborator.
4.6.Tare conexitate

I. Activități de învățare

Un graf orientat G=(X,U) este conex, dacă oricare ar fi două vârfuri ale sale, există un
lanț care le leagă .

Exemplu:

Figura 4.6.1 Figura 4.6.2


Graf conex Graf neconex

Graful din figura 4.6.1. de mai sus este conex. Oricum am lua două noduri putem
ajunge de la unul la celălalt pe un traseu de tip lanț (se face abstracție de orientare).

De exemplu, de la nodul 3 la nodul 2, putem ajunge pe traseul (3, 1, 2), stabilind astfel lanțul
{u1, u3}. Acest lanț nu este unic, alte soluții fiind {u2, u5} și {u4, u3} .

Graful din figura 4.6.1 nu este conex. Luând submulțimea de noduri {2, 3, 4}, putem
spune că între oricare doua noduri din această submulțime există cel puțin un lanț. La fel și în
submulțimea de noduri {5, 1, 6}. Dar nu putem găsi un lanț între un nod din prima
submulțime și un nod din a doua submulțime.

Se numește componentă conexă a grafului G=(X,U), un subgraf G1=(X1,U1) a lui G,


conex, cu proprietatea că nu există nici un lanț care să lege un nod din X1 cu un nod din X-X1
(pentru orice nod, nu există un lanț între acel nod și nodurile care nu fac parte din subgraf).

Cu alte cuvinte, o componenta conexă a grafului G este un subgraf conex al lui G


maximal în raport cu această proprietate.

Exemplu: Pentru graful G=(X, U) din figura 4.6.2. de mai sus, cu X={1,2,3,4,5,6} și U={u1,
u2, u3, u4, u5}, nu este conex, el având două componente conexe:
 G1=(X1,U1), unde X1={2, 3, 4} și U1={u1, u4, u5};
 G2=(X2,U2), unde X2={1, 5, 6} și U2={u2, u3};

Se poate observa că, în cadrul grafurilor orientate, noțiunile de graf conex şi componentă
conexă sunt absolut similare cu cele de la grafuri neorientate. În plus însă, la grafurile
orientate mai întâlnim şi noțiunile de graf tare conex şi componenta tare conexă.

Graf tare conex. Componente tare conexe

Un graf orientat G=(X,U) este tare conex, dacă pentru oricare două noduri x și y X,
există un drum de la x la y precum şi un drum de la y la x.

Exemplu: Fie graful din figura de mai jos

Figura 4.6.3
Graf tare conex
Oricum am lua două noduri x,y{1, 2, 3, 4}, căutăm să vedem dacă există drum de la x la y
dar şi un drum de la y la x.

 x=1, y=2:  x=1, y=4:  x=1, y=3:


 de la 1 la 2:  de la 1 la  de la 2 la 4:
(1, 3, 2) 4: (1,2,3,4) (2,3,4)
 de la 2 la 1:  de la 4 la  de la 4 la 2:
(2,3,4,1) 1: (4,2,1) (4,1,2)
 x=1, y=3:  x=2, y=3:  x=1, y=4:
 de la 1 la 3:  de la 2 la 3:  de la 3 la 4:
(1,2,3) (2,1,3) (3,2,4)
 de la 3 la 1:  de la 3 la 2:  de la 4 la 3:
(3,4,1) (3,4,1,2) (4,2,3)
În stabilirea drumurilor trebuie să fim atenţi la orientare arcelor prin care trecem,
pentru a respecta definiţia noţiunii de drum. Mai exact, toate arcele drumului trebuie să aibă
aceeaşi orientare.

De exemplu, în graful anterior la nodul x=2 la nodul y=1 nu se poate stabili drumul (2, 3, 1),
pentru că, deşi există arcul (2,3), nu există arcul (3,1).

Fiind dat un graf orientat G=(X,U), se numeşte componentă tare conexă a lui G, un
subgraf G1=(X1,U1) , tare conex, şi maximal în raport cu această proprietate (adică pentru
orice nod xX-X1, subgraful indus de X1{x} nu mai este tare conex ).

Exemplu: Pentru graful din figura de mai jos avem 3 componente tare conexe, acestea fiind:

C1={1}, C2={2, 6, 7, 8} și C3={3, 4, 5, 9}.

Figura 4.6.4

Algoritmul de descompunere a unui graf în componente tare conexe


Considerăm un graf orientat G=(X,U) cu n vârfuri. Plecăm de la matricea de adiacență
a, și formăm matricea drumurilor, cu algoritmul Roy-Warshall.
Definim trei vectori de mulţimi: S, P și C definiți astfel:

 S[i]  mulţimea vârfurilor care sunt extremităţi finale ale drumurilor ce pleacă
din nodul i (adică numărul valorilor de 1 de pe linia i a matricei drumurilor);
 P[i]  mulţimea vârfurilor care sunt extremități iniţiale ale drumurilor ce intră
în nodul i ( adică numărul valorilor de 1 de pe coloana i a matricei drumurilor).
 C[i] reprezintă o componentă tare conexă.

Mai definim o mulţime L, care va conţine la finalul fiecărui pas, nodurile deja incluse în
componentele conexe existente pană în acel moment. Inițial L este mulţimea vidă. Imediat
după ce formăm o componentă tare conexă, pentru a marca acest fapt, introducem în
mulțimea L nodurile componentei.
Număram în variabila nc câte componente tari conexe avem.

Componenta tare conexă din care face parte nodul i, este mulţimea: (S[i]  P[i])  {i}.

Algoritmul în pseudocod:

Pas 1 VIZ= ∅ {mulțimea nodurilor incluse deja într-o componentă tare conexă}
nc=0 {numărul de componente tare conexe}
Pas 2. pentru i=1,n execută

dacă i VIZ atunci


determină S(i)
{mulțimea nodurilor la care putem ajunge pe drumuri ce pleacă
din nodul i}
determină P(i)
{mulțimea nodurilor de la care, dacă plecăm, putem ajunge la
nodul i}
comp(nc)=S(i) ∩ P(i) ∪{i} {componenta conexă care conține nodul i}
scrie comp(nc)
VIZ=VIZ ∪ comp(nc)
sfîrșit_dacă
sfârșit_pentru
II. Exerciții de fixare a cunoștințelor teoretice

a. Sarcină de lucru 1 – elevii vor lucra individual, pe caiet, la clasă

Fie graful din figura de mai jos:

Figura 4.6.5
1) Câte componente conexe are graful din figura 4.6.5?
a. 3 c. 2
b. 4 d. 1
2) Câte componente tare conexe are graful din figura 4.6.5?
a. 3 c. 1
b. 6 d. 4
3) Care este numărul maxim de arce care pot fie eliminate astfel încât numărul de
componente conexe să rămână neschimbat?
a. 2 c. 0
b. 1 d. 3
4) Fie graful din figura de mai jos:

Figura 4.6.6
Completați răspunsurile:

a) Numărul de componente tare conexe este......


b) Componenta conexă din care face parte nodul 2, mai conține și nodurile ............

b. Sarcină de lucru 2 – Implementați în limbaj de programare algoritmul care


determină componentele tare conexe ale unui graf orientat.
Sarcină de lucru 3 – Realizați un program care să verifice proprietatea de tare conexitate
a unui graf orientat.
Indicații:
- Se determină componentele tare conexe și daca se obține o singură componentă
tare conexă, atunci graful este tare conex
- Folosind algoritmul lui Roy-Warshall, se determină matricea drumurilor și dacă
aceasta are toate elementele egale cu 1 (cu excepția celor de pe diagonala
principală), atunci graful este tare conex.

Activitatea se poate desfășura individual la calculator urmând algoritmul descris în


lecția teoretică sau pe grupe, la laborator
4.7. Drumuri minime și maxime în grafuri orientate

I. Activități de învățare

Considerăm un graf orientat G=( X, U) cu n vârfuri, în care fiecărui arc îi este


asociat un număr întreg numit cost. Semnificația acestui cost poate fi foarte variată, în
funcție de domeniul pe care îl descrie graful. De exemplu, dacă graful reprezintă harta
unui oraș în care arcele sunt străzile iar vârfurile sunt intersecțiile dintre străzi, atunci
putem vorbi despre costul deplasării unui automobil între două intersecții, de-a lungul
unei străzi. Acesta s-ar putea măsura în cantitatea de benzină consumată, calculată prin
prisma lungimii străzii în m sau km.
Apar următoarele întrebări:
1. Un turist se află în intersecția x. Care este traseul de lungime minimă dintre
intersecția x și intersecția y?
Sursă unică, destinație unică.
2. Care sunt traseele de lungime minimă între intersecția x și toate celelalte
intersecții?
Sursă unică, destinație multiplă.
3. Care sunt traseele de lungime minimă între oricare două intersecții?
Sursă multiplă, destinație multiplă.
În toate cazurile, se cere lungimea unui drum pentru care suma costurilor arcelor este
minimă. Un astfel de drum este numit drum de cost minim (drum optim).
Pentru rezolvarea acestor probleme vom utiliza algoritmul Roy – Floyd care rezolvă
cazul 3 (sursă multiplă, destinație multiplă), precum și algoritmul lui Dijkstra care
rezolvă cazul 2 (sursă unică, destinație multiplă). Din rezultatele furnizate de cei doi
algoritmi, se poate obține răspunsul la cazul 1. De asemenea, dacă algoritmul lui Dijkstra
este aplicat de n ori, se poate rezolva și cazul 3 (sursă multiplă, destinație multiplă).
În vederea implementării algoritmilor de drum minim/maxim, definim în continuare,
matricea costurilor.
Matricea costurilor (matricea ponderilor)
Pentru evidențierea tuturor arcelor unui graf cu n vârfuri se poate defini o matrice A, cu n
linii si n coloane. Există două forme ale acestei matrici:
Forma 1
𝒄, 𝒅𝒂𝒄ă ∃ 𝒖𝒏 𝒂𝒓𝒄 𝒅𝒆 𝒄𝒐𝒔𝒕 𝒄 > 0 𝑑𝑒 𝑙𝑎 𝑖 𝑙𝑎 𝑗
𝒂[𝒊, 𝒋] = 𝟎, 𝒅𝒂𝒄ă 𝒊 = 𝒋
+∞, 𝒅𝒂𝒄ă 𝒏𝒖 𝒆𝒙𝒊𝒔𝒕ă 𝒂𝒓𝒄 𝒅𝒆 𝒍𝒂 𝒊 𝒍𝒂 𝒋

Forma 2
𝒄, 𝒅𝒂𝒄ă ∃ 𝒖𝒏 𝒂𝒓𝒄 𝒅𝒆 𝒄𝒐𝒔𝒕 𝒄 > 0 𝑑𝑒 𝑙𝑎 𝑖 𝑙𝑎 𝑗
𝒂[𝒊, 𝒋] = 𝟎, 𝒅𝒂𝒄ă 𝒊 = 𝒋
−∞, 𝒅𝒂𝒄ă 𝒏𝒖 𝒆𝒙𝒊𝒔𝒕ă 𝒂𝒓𝒄 𝒅𝒆 𝒍𝒂 𝒊 𝒍𝒂 𝒋

Forma 1 a matricei costurilor, se folosește pentru determinarea drumurilor de cost minim


între două vârfuri, iar forma 2 este utilizată în aflarea drumurilor de cost maxim.
Dacă dorim să citim matricea costurilor, evident că nu putem introduce de la tastatura
„+∞” și atunci vom da un număr foarte mare de la tastatură.

4.7.1. Algoritmul lui Roy – Floyd – drumuri minime între oricare vârfuri

Sarcină de lucru: Se consideră un graf orientat cu n vârfuri, pentru care se dă


matricea costurilor de forma 1. Se cere ca, pentru fiecare pereche de vârfuri (i,j), să se
tipărească costul drumului minim de la i la j.

Descriere algoritm: Plecăm de la următoarea idee: dacă drumul minim între două vârfuri
oarecare i și j trece printr-un vârf k, atunci drumurile de la i la k și de la k la j sunt la
rândul lor minime. Pentru fiecare pereche de vârfuri (i,j), cu i, j ∈ {1, 2, . . . , n},
procedăm astfel:
Dăm lui k pe rând valorile 1, 2, . . . , n, pentru că vârful k despre care vorbeam mai sus
poate fi, cel puțin teoretic, orice vârf al grafului. Pentru fiecare k:
 Dacă ai,j > ai,k+ ak,j atunci ai,j :=ai,k+ ak,j (adică, dacă suma dintre costul drumului
de la i la k si costul drumului de la k la j este mai mică decât costul drumul de la i
la j, atunci drumul inițial de la i la j este înlocuit cu drumul indirect i→k→j);
 Altfel, matricea costurilor rămâne neschimbată.
În finalul algoritmului costurile drumurilor minime se găsesc în matricea costurilor, ale
cărei elemente au următoarea semnificație:

𝑐𝑜𝑠𝑡𝑢𝑙 𝑑𝑟𝑢𝑚𝑢𝑙𝑢𝑖 𝑚𝑖𝑛𝑖𝑚 𝑑𝑒 𝑙𝑎 𝑖 𝑙𝑎 𝑗, 𝑑𝑎𝑐ă 𝑒𝑥𝑖𝑠𝑡ă


𝑎 𝑖, 𝑗 =
+∞, î𝑛 𝑐𝑎𝑧 𝑐𝑜𝑛𝑡𝑟𝑎𝑟
Dacă dorim să obținem și drumurile minime, atunci procedăm astfel:
 utilizăm în plus o matrice D, pătratică, de dimensiune n, ale cărei elemente sunt
vârfuri;
 odată cu inițializarea matricei costurilor, vom inițializa și matricea D, astfel:
𝑖, 𝑑𝑎𝑐ă 𝑎𝑖𝑗 < ∞ ș𝑖 𝑖 ≠ 𝑗
𝑑 𝑖, 𝑗 =
∅, 𝑑𝑎𝑐ă 𝑎𝑖𝑗 = ∞ 𝑠𝑎𝑢 𝑖 = 𝑗

Pe măsură ce se actualizează matricea costurilor C, vom actualiza și matricea D astfel:

 dacă ai,j < ai,k+ ak,j atunci di,j rămâne neschimbat;


 dacă ai,j > ai,k+ ak,j atunci se inițializează di,j cu dk,j ( am găsit un drum de cost mai
mic, folosind vârful intermediar k).
În final elementele matricei D vor avea următoarea semnificație:
di,j= vârful ce poate precede vârful j în drumul minim de la i la j.

Algoritmul în pseudocod
Pasul 1. Inițializăm matricea costurilor C și matricea D după formulele precizate mai sus.
Pasul 2. pentru k=1,n execută
pentru i=1,n execută
pentru j=1,n execută
dacă a[i,k]+a[k,j]<a[i,j] atunci
a[i,j]=a[i,k]+a[k,j]
d[i,j]=d[k,j]
sfârșit_dacă
sfârșit_pentru
sfârșit_pentru
sfârșit_pentru

Exemplu:
Considerăm graful din figura de mai jos:
Figura 4.7.1.1

Inițial, matricile A și D sunt:


Matricea costurilor asociată grafului:
𝟎 𝟐 ∞ ∞ ∞ 𝟒 ∅ 1 ∅ ∅ ∅ 1
𝟑 𝟎 ∞ ∞ 𝟓 𝟏 2 ∅ ∅ ∅ 2 2
𝟏 ∞ 𝟎 ∞ ∞ ∞ 3 ∅ ∅ ∅ ∅ ∅
A= D=
∞ ∞ ∞ 𝟎 ∞ 𝟑 ∅ ∅ ∅ ∅ ∅ 4
∞ ∞ 𝟑 ∞ 𝟎 ∞ ∅ ∅ 5 ∅ ∅ ∅
∞ ∞ ∞ ∞ 𝟐 𝟎 ∅ ∅ ∅ ∅ 6 ∅

Pentru fiecare vârf intermediar k=1, 2, ..., n, se parcurg perechile de vârfuri (i,j), cu
proprietatea că i≠k și j≠k; dacă ai,j > ai,k+ ak,j atunci ai,j se înlocuiește cu ai,k+ ak,j și di,j se
înlocuiește cu dk,j .
Se poate observa faptul că la fiecare pas k, elementele care rămân neschimbate sunt cele
de pe linia k (i≠k), coloana k (j≠k) și de pe diagonala principală (ai,j=0, deci valoare
minimă). Dacă elementul ai,j rămâne neschimbat, atuci și elementul corespunzător din
matricea D, di,j rămâne neschimbat.
De asemenea, rămân neschimbate elementele de pe linia i și coloana j pentru care ai,j <
ai,k+ ak,j (nu există drum de la i la j prin intermediarul k, sau există dar costul său este mai
mic sau egal cu costul drumului direct de la i la j).
Prezentăm conținutul matricilor A și D după fiecare iterație a variabilei intermediare k.
0 2 ∞ ∞ ∞ 4 ∅ 1 ∅ ∅ ∅ 1
3 0 ∞ ∞ 5 1 2 ∅ ∅ ∅ 2 2
1 3 0 ∞ ∞ 5 3 1 ∅ ∅ ∅ 1
K=1 A= D=
∞ ∞ ∞ 0 ∞ 3 ∅ ∅ ∅ ∅ ∅ 4
∞ ∞ 3 ∞ 0 ∞ ∅ ∅ 5 ∅ ∅ ∅
∞ ∞ ∞ ∞ 2 0 ∅ ∅ ∅ ∅ 6 ∅
0 2 ∞ ∞ 7 3 ∅ 1 ∅ ∅ 2 2
3 0 ∞ ∞ 5 1 2 ∅ ∅ ∅ 2 2
1 3 0 ∞ 8 4 3 1 ∅ ∅ 2 2
K=2 A= D=
∞ ∞ ∞ 0 ∞ 3 ∅ ∅ ∅ ∅ ∅ 4
∞ ∞ 3 ∞ 0 ∞ ∅ ∅ 5 ∅ ∅ ∅
∞ ∞ ∞ ∞ 2 0 ∅ ∅ ∅ ∅ 6 ∅
0 2 ∞ ∞ 7 3 ∅ 1 ∅ ∅ 2 2
3 0 ∞ ∞ 5 1 2 ∅ ∅ ∅ 2 2
1 3 0 ∞ 8 4 3 1 ∅ ∅ 2 2
K=3 A= D=
∞ ∞ ∞ 0 ∞ 3 ∅ ∅ ∅ ∅ ∅ 4
4 6 3 ∞ 0 7 3 1 5 ∅ ∅ 2
∞ ∞ ∞ ∞ 2 0 ∅ ∅ ∅ ∅ 6 ∅
0 2 ∞ ∞ 7 3 ∅ 1 ∅ ∅ 2 2
3 0 ∞ ∞ 5 1 2 ∅ ∅ ∅ 2 2
1 3 0 ∞ 8 4 3 1 ∅ ∅ 2 2
K=4 A= D=
∞ ∞ ∞ 0 ∞ 3 ∅ ∅ ∅ ∅ ∅ 4
4 6 3 ∞ 0 7 3 1 5 ∅ ∅ 2
∞ ∞ ∞ ∞ 2 0 ∅ ∅ ∅ ∅ 6 ∅
0 2 10 ∞ 7 3 ∅ 1 5 ∅ 2 2
3 0 8 ∞ 5 1 2 ∅ 5 ∅ 2 2
1 3 0 ∞ 8 4 3 1 ∅ ∅ 2 2
K=5 A= D=
∞ ∞ ∞ 0 ∞ 3 ∅ ∅ ∅ ∅ ∅ 4
4 6 3 ∞ 0 7 3 1 5 ∅ ∅ 2
6 8 5 ∞ 2 0 3 1 5 ∅ 6 ∅
0 2 8 ∞ 5 3 ∅ 1 5 ∅ 6 2
3 0 6 ∞ 3 1 2 ∅ 5 ∅ 6 2
1 3 0 ∞ 6 4 3 1 ∅ ∅ 6 2
K=6 A= D=
9 11 8 0 5 3 3 1 5 ∅ 6 4
4 6 3 ∞ 0 7 3 1 5 ∅ ∅ 2
6 8 5 ∞ 2 0 3 1 5 ∅ 6 ∅

Exemplificăm în continuare cum se determină drumul de cost minim dintre două vârfuri,
precum și costul său. Fie aceste vârfuri 1 și 3. Atunci avem:
- din matricea A se poate vedea care este costul drumului minim de la 1 la 3: acesta
este a13=8;
- pentru a determina care este drumul minim de la 1 la 3, plecăm de la d13=5;
conform definiției matricei D rezultă că vârful 5 este vârful anterior lui 3 pe drumul de
cost minim de la 1 la 3 (în acest moment avem drumul 1, 5, 3); în continuare se consideră
d15=6 și rezultă că vârful anterior lui 5 este 6 (drumul este în acest moment 1, 6, 5, 3);
d16=2, deci vârful anterior lui 6 este 2 (drumul este 1, 2, 6, 5, 3); d12=1 adică este chiar
vârful de plecare, ceea ce înseamnă că am determinat drumul de cost minim: 1, 2, 6, 5, 3.
Asemănător se pot obține și celelalte drumuri de cost minim.
Dacă pentru două vârfuri i și j avem aij=∞, atunci nu există drum de la i la j (de exemplu
a54=∞, deci nu există nici drum de la vârful i la vârful j).
În vederea implementării algoritmului, utilizăm următoarele subprograme:
- subprogramul citire – realizează citirea datelor de intrare din fișierul graf.in și
construiește matricea costurilor A; inițial matricea A se inițializează cu ∞, excepție
diagonala principală care este 0; fișierul conține pe prima linie, separate prin spațiu,
numărul de vârfuri n și numărul de arce m, iar pe fiecare din următoarele m linii, trei
valori, separate prin spațiu, ce reptezintă estremitățile arcului și costul arcului; după
citirea fiecărui arc, se completează în matricea A, costul arcului respectiv;
- subprogramul initD – inițializează matricea D prin intermediul căreia vom
reconstitui drumurile de cost minim;
- subprogramul recursiv drum_minim cu doi parametri i și j, care reconstituie
vârfurile de pe drumul de cost minim de la i la j; aceste vârfuri se obțin în ordine inversă,
deci pentru afisarea lor în ordinea dorită (de la i la j), instrucțiunea de afișare va fi plasată
după apelul recursiv;
- subprogramul afisare – realizează afișarea pentru fiecare pereche de vârfuri între
care există drum, drumul de cost minim (deoarece subprogramul drum_minim obține
doar vârfurile anterioare lui j pe drumul minim de la i la j, inclusiv j și exclusiv i, acesta
din urma fiind afișat separat, înainte de apelul subprogramului drum_minim).

II. Exerciții de fixare a cunoștințelor teoretice

a. Sarcină de lucru 1 – elevii vor lucra individual, pe caiet, la clasă

1) Se consideră graful din figura 4.7.1.2. Care este costul drumului minim de la
nodul 1 la nodul 4?

Figura 4.7.1.2
2) Se consideră graful din figura 4.7.1.3. Să se indice un drum de cost minim de la
vârful 1 la vârful 4.

Figura 4.7.1.3
3) Se consideră matricea drumurilor de cost minim din figura 4.7.1.4, asociată unui
graf orientat. Care dintre grafurile de mai jos corespunde matricii date?
1 2 3 4
1 0 4 7 6
2 ∞ 0
3 ∞
3 ∞ ∞ 0 ∞
4 ∞ ∞ 2 0
Figura 4.7.1.4

a) b)

b. Sarcină de lucru 2 – Să se implementeze în limbaj de programare algoritmul care


determină pentru fiecare pereche de vârfuri (i,j) costul drumului minim de la i la j.
Activitatea se va desfășura individual la calculator urmând algoritmul descris în lecția
teoretică sau pe grupe, la laborator.
4.7.2. Algoritmul lui Dijkstra – drumuri minime de sursă unică

I. Activități de învățare

Sarcină de lucru: Se consideră un graf orientat cu n noduri, pentru care se dă


matricea costurilor în forma 1 și un nod de plecare start. Să se determine costul drumului
minim, pentru toate nodurile i, pentru care există drum de la start la i.

În implementarea algoritmului, folosim următoarele structuri:

Vectorul VIZ care marchează nodurile grafului pentru care s-a găsit drum de la
start la ele
Vectorul d ale cărui elemente au următoarea semnificație:

𝑐𝑜𝑠𝑡𝑢𝑙 𝑑𝑟𝑢𝑚𝑢𝑙𝑢𝑖 𝑚𝑖𝑛𝑖𝑚 𝑑𝑒 𝑙𝑎 𝑠𝑡𝑎𝑟𝑡 𝑙𝑎 𝑖, 𝑑𝑎𝑐ă 𝑖 ∈ 𝑉𝐼𝑍


𝑑 𝑖, 𝑗 =
𝑐𝑜𝑠𝑡𝑢𝑙 𝑑𝑟𝑢𝑚𝑢𝑙𝑢𝑖 𝑚𝑖𝑛𝑖𝑚 𝑑𝑒 𝑙𝑎 𝑠𝑡𝑎𝑟𝑡 𝑙𝑎 𝑖 𝑐𝑎𝑟𝑒 𝑓𝑜𝑙𝑜𝑠𝑒ș𝑡𝑒 𝑛𝑜𝑑𝑢𝑟𝑖 𝑑𝑜𝑎𝑟 𝑑𝑖𝑛 𝑉𝐼𝑍, 𝑑𝑎𝑐ă 𝑖 ∉ 𝑉𝐼𝑍

Pentru a determina și unul din drumurile minime de la start la i, utilizăm vectorul


prec în care prec[i] este precedentul nodului i pe drumul minim de la start la i. Inițial
avem:
0, 𝑑𝑎𝑐ă 𝑎 𝑖0 , 𝑖 = ∞ 𝑠𝑎𝑢 𝑖 = 𝑖0
𝑝𝑟𝑒𝑐 𝑖 =
𝑖0 , 𝑑𝑎𝑐ă 𝑎 𝑖0 , 𝑖 ∞ ș𝑖 𝑖 ≠ 𝑖0

La fiecare pas, dacă dk+c[k,j]<dj atunci prec[j]=k.

Algoritmul în pseudocod

Pasul 1. pentru i=1,n execută


d[i]=a[start,i]
viz[i]=0
dacă a[start,i]< ∞ atunci prec[i]=start
altfel prec[i]=0
sfârșit_dacă
sfârșit_pentru
Pasul 2. viz[start]=1; prec[start]=0
Pasul 3. căutăm un nod k astfel încât k∉ viz și drumul minim de la start la k să aibă cel
mai mic cost, adică d[k]=min{d[i,j], j∉ viz}
dacă nu găsim niciun nod pentru care costul drumului minim să fie mai
mic decât , atunci algoritmul se încheie, altfel se trece la pasul 4
Pasul 4. viz[k]=1 {adăugăm în viz nodul găsit}
Pasul 5. actualizăm valorile lui d pentru elementele care nu sunt în viz
pentru j=1,n execută
dacă viz[j]=0 și d[j]>d[k]+a[k,j] atunci
d[j]=d[k]+a[k,j]
prec[j]=k
sfârșit_dacă
sfârșit_pentru
se revine la Pasul 3

Exemplu: fie graful din figura de mai jos

Figura 4.7.2.1
Fie start=1 și căutăm drumurile de cost minim de la 1 la celelalte noduri ale grafului.

Pasul 1. Pasul 2.

viz=(0,0,0,0,0,0) viz[1]=1, atunci viz=(1,0,0,0,0,0)

d=(0,2,∞, ∞, ∞,4) prec[1]=0, atunci prec=(0,1,0,0,0,1)

prec=(0,1,0,0,0,1)

Pasul 3.

alegem k=2 deoarece d[2]=2 alegem k=3 deoarece d[3]=8


Pasul 4. viz=(1,1,0,0,0,0) Pasul 4. viz=(1,1,1,0,1,1)
Pasul 5. d=(0,2,∞, ∞, 7,3) , Pasul 5. d=(0,2,8, ∞, 5,3) ,
prec=(0,1,0,0,2,2) prec=(0,1,5,0,6,2)
min{d[j], j∉viz}=∞ și algorimul se
alegem k=6 deoarece d[6]=2 încheie
Pasul 4. viz=(1,1,0,0,0,1)
Pasul 5. d=(0,2,∞, ∞, 5,3) ,
prec=(0,1,0,0,6,2) Drumurile de cost minim obținute sunt:

alegem k=5 deoarece d[5]=5 de la 1 la 2 1, 2 cu costul 2


Pasul 4. viz=(1,1,0,0,1,1) de la 1 la 3 1, 2, 6, 5, 3 cu costul 8
Pasul 5. d=(0,2,8, ∞, 5,3) , de la 1 la 4 Nu există drum
prec=(0,1,5,0,6,2) de la 1 la 5 1, 2, 6, 5 cu costul 5
de la 1 la 6 1, 2, 6 cu costul 3
II. Exerciții de fixare a cunoștințelor teoretice

a. Sarcină de lucru 1 – elevii vor lucra individual, pe caiet, la clasă


1) Algoritmul lui Dijkstra poate determina toate drumurile de cost minim de la vârful
de start la fiecare dintre celelalte vârfuri ale grafului dacă pentru oricare vârf din graf vom
reţine mulţimea tuturor vârfurilor care îl precedă pe un drum de cost minim de la vârful
de start.
a) Adevărat b) Fals
2) Se consideră matricea costurilor asociată unui graf orientat, din figura 4.7.2.2.
Care dintre grafurile de mai jos corespunde matricii respective?
1 2 3 4 5
1 0 4
∞ ∞ ∞
2 ∞ 0 ∞ 15 5
3 1 ∞ 0 ∞ ∞
4 ∞ ∞ ∞ 0 8
5 ∞ ∞ 7 10 0
Figura 4.7.2.2

a) b)

3) Se consideră graful orientat din figura 4.7.2.3. Să se indice două drumuri distincte
de cost minim de la vârful de start la vârful 3.

Figura 4.7.2.3
b. Sarcină de lucru 2 - Implementați în limbaj de programare algoritmul care
determină costul drumului minim de la un nod start la toate celelalte noduri din graf.
Activitatea se poate desfășura individual la calculator urmând algoritmul descris în
lecția teoretică sau pe grupe, la laborator.

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