Sunteți pe pagina 1din 206

Cuprins

1 Introducere n algoritmi 3
1.1 Limbajul pseudocod . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 4
1.2 Exemple . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 12
1.3 Elemente de analiza algoritmilor . . . . . . . . . . . . . . . . . . . . . . . . . . 16
1.3.1 Metoda substitutiei . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 20
1.3.2 Schimbarea de variabila . . . . . . . . . . . . . . . . . . . . . . . . . . 21
1.3.3 Metoda iterativa . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 21
1.3.4 Teorema master . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 22
1.4 Exercitii . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 25

2 Grafuri. Grafuri neorientate 28


2.1 Notiuni de baza . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 28
2.2 Operatii pe grafuri . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 33
2.3 Moduri de reprezentare . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 36
2.4 Parcurgerea grafurilor . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 40
2.4.1 Parcurgerea n latime (BFS-Breadth First Search) . . . . . . . . . . . . 40
2.4.2 Parcurgerea D (D - Depth) . . . . . . . . . . . . . . . . . . . . . . . . 44
2.4.3 Parcurgerea n adancime (DFS-Depth First Search) . . . . . . . . . . . 45
2.5 Componente conexe . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 49
2.6 Muchie critica . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 50
2.7 Exercitii . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 55

3 Grafuri euleriene si hamiltoniene 58


3.1 Grafuri Euleriene . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 58
3.1.1 Algoritm pentru determinarea unui ciclu eulerian . . . . . . . . . . . . 59
3.1.2 Algoritmul lui Rosenstiehl . . . . . . . . . . . . . . . . . . . . . . . . . 62
3.1.3 Algoritmul lui Fleury . . . . . . . . . . . . . . . . . . . . . . . . . . . . 66
3.2 Grafuri Hamiltoniene . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 68
3.2.1 Problema comisvoiajorului . . . . . . . . . . . . . . . . . . . . . . . . 70

4 Arbori. Arbori binari 80


4.1 Arbori binari . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 81
4.1.1 Moduri de reprezentare . . . . . . . . . . . . . . . . . . . . . . . . . . . 82
4.1.2 Metode de parcurgere . . . . . . . . . . . . . . . . . . . . . . . . . . . . 84
4.2 Arbori binari de cautare . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 90
4.3 Exercitii . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 98

1
5 Arbori oarecare 102
5.1 Moduri de reprezentare . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 102
5.2 Metode de parcurgere . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 105
5.3 Arbori de acoperire de cost minim . . . . . . . . . . . . . . . . . . . . . . . . . 110
5.3.1 Algoritmul lui Boruvka . . . . . . . . . . . . . . . . . . . . . . . . . . . 113
5.3.2 Algoritmul lui Prim . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 113
5.3.3 Structuri de date pentru multimi disjuncte . . . . . . . . . . . . . . . . 116
5.3.4 Algoritmul lui Kruskal . . . . . . . . . . . . . . . . . . . . . . . . . . . 121
5.4 Exercitii . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 124

6 Grafuri orientate 127


6.1 Notiuni de baza . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 127
6.2 Parcurgerea grafurilor . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 129
6.3 Sortarea topologica . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 132
6.4 Componente tare conexe . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 136
6.4.1 Algoritmul lui Kosaraju . . . . . . . . . . . . . . . . . . . . . . . . . . 137
6.4.2 Algoritmul lui Tarjan . . . . . . . . . . . . . . . . . . . . . . . . . . . . 139
6.4.3 Algoritmul lui Gabow . . . . . . . . . . . . . . . . . . . . . . . . . . . 142
6.5 Exercitii . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 145

7 Heap-uri 149
7.1 Heap-uri binare (Min-heapuri sau Max -heapuri) . . . . . . . . . . . . . . . . . 149
7.1.1 Inserarea unui element . . . . . . . . . . . . . . . . . . . . . . . . . . . 151
7.1.2 Stergerea elementului minim . . . . . . . . . . . . . . . . . . . . . . . . 152
7.1.3 Crearea unui heap . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 153
7.2 Ordonare prin metoda HeapSort . . . . . . . . . . . . . . . . . . . . . . . . . . 155
7.3 Aplicatie - Coada cu prioritate . . . . . . . . . . . . . . . . . . . . . . . . . . . 158
7.4 Exercitii . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 161

8 Distante n grafuri 165


8.1 Drumul minim de la un varf la celelalte varfuri . . . . . . . . . . . . . . . . . . 166
8.1.1 Algoritmul lui Moore . . . . . . . . . . . . . . . . . . . . . . . . . . . . 166
8.1.2 Algoritmul lui Dijkstra . . . . . . . . . . . . . . . . . . . . . . . . . . . 167
8.2 Drumuri minime ntre toate perechile de varfuri . . . . . . . . . . . . . . . . . 175
8.2.1 Algoritmul lui Roy-Floyd-Warshall . . . . . . . . . . . . . . . . . . . . 175
8.3 Exercitii . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 182

A Metoda Backtracking 186

2
Capitolul 1

Introducere n algoritmi

Definitia 1.1 Algoritmul constituie o reprezentare finit a a unei metode de calcul ce per-
mite rezolvarea unei anumite probleme. Se poate spune c a un algoritm reprezinta o secventa
finita de operatii, ordonata si complet definit
a, care, pornind de la datele de intrare, produce
rezultate.

Termenul de algoritm i este atribuit matematicianului persan Abu Jafar Mohammed


ibn Musa al-Khowarizmi (sec. VIII-IX), care a scris o carte de matematica cunoscuta n tra-
ducere latina sub titlul de Algorithmi de numero indorum, iar apoi ca Liber algorithmi ,
unde termenul de algorithm provine de la al-Khowarizmi , ceea ce literal nseamna din
orasul Khowarizm. Matematicienii din Evul Mediu ntelegeau prin algoritm o regula (sau
o multime de reguli) pe baza careia se efectuau calcule aritmetice: de exemplu n secolul al
XVI-lea, algoritmii se foloseau la nmultiri sau njumatatiri de numere.
Fiecare propozitie ce face parte din descrierea unui algoritm este, de fapt, o comanda ce
trebuie executata de cineva, acesta putand fi o persoana sau o masina de calcul. De altfel,
un algoritm poate fi descris cu ajutorul oricarui limbaj, de la limbajul natural si pana la
limbajul de asamblare al unui calculator. Denumim limbaj algoritmic un limbaj al carui scop
este acela de a descrie algoritmi.
Algoritmul specifica succesiuni posibile de transformari ale datelor. Un tip de date poate
fi caracterizat printr-o multime de valori ce reprezinta domeniul tipului de date si o multime
de operatii definite peste acest domeniu. Tipurile de date pot fi organizate n urmatoarele
categorii:

1. tipuri de date elementare (de exemplu tipul ntreg, tipul real ) - valorile sunt unitati
atomice de informatie;

2. tipuri de date structurate (de exemplu tipul tablou, tipul nregistrare) - valorile sunt
structuri relativ simple rezultate n urma combinatiei unor valori elementare;

3. tipuri de date structurate de nivel nalt (de exemplu stiva) - se pot descrie independent
de limbaj iar valorile au o structura mai complexa.

Un algoritm trebuie sa posede urmatoarele tr


as
aturi caracteristice:

1. claritate - la fiecare pas trebuie sa specifice operatia pe care urmeaza sa o efectueze


algoritmul asupra datelor de intrare;

2. corectitudine - rezultatele trebuie sa fie corecte;

3
3. generalitate - algoritmul trebuie sa ofere solutia nu numai pentru o singura problema
ci pentru o ntreaga clasa de probleme;
4. finitudine - algoritmul trebuie sa se termine ntr-un timp finit;

5. eficienta - un algoritm poate fi utilizat numai n situatia n care resursele de calcul


necesare acestuia sunt n cantitati rezonabile, si nu depasesc cu mult posibilitatile cal-
culatoarelor la un moment dat.
Un program reprezinta implementarea unui algoritm ntr-un limbaj de programare.
Studiul algoritmilor cuprinde mai multe aspecte:
elaborare - activitatea de concepere a unui algoritm are si un caracter creativ, din
aceasta cauza nefiind posibila deprinderea numai pe cale mecanica. Pentru a facilita
obtinerea unei solutii la o problema concreta se recomanda folosirea tehnicilor generale
de elaborare a algoritmilor la care se adauga n mod hotarator intuitia programatorului;

exprimare - implementarea unui algoritm intr-un limbaj de programare se poate face


utilizand mai multe stiluri: programare structurat
a, programare orientat
a pe obiecte
etc.;

validare - verificarea corectitudinii algoritmului prin metode formale;


analiza - stabilirea unor criterii pentru evaluarea eficientei unui algoritm pentru a-i
putea compara si clasifica.
Un model de reprezentare al memoriei unei masini de calcul este acela al unei structuri
liniare compusa din celule, fiecare celula fiind identificata printr-o adresa si putand pastra o
valoare corespunzatoare unui anumit tip de data. Accesul la celule este facilitat de variabile.
O variabila se caracterizeaza prin:
un identificator - un nume ce refera variabila;

o adresa - desemneaza o locatie de memorie;


un tip de date - descrie tipul valorilor memorate n celula de memorie asociata.

1.1 Limbajul pseudocod


Limbajul natural nu permite o descriere suficient de riguroasa a algoritmilor, de aceea, pentru
reprezentarea acestora se folosesc alte modalitati de descriere precum:
scheme logice;

limbajul pseudocod.
In continuare vom prezenta pricipalele constructii din cadrul limbajului pseudocod.

Intr
ari/iesiri
Citirea datelor de intrare se poate realiza prin intermediul enuntului Input:
1: Input {lista variabile}
Afisarea rezultatelor este reprezentata cu ajutorul instructiunii Output:
1: Output {lista de valori}

4
Instructiunea de atribuire
Este instructiunea cel mai des utilizata ntr-un algoritm si realizeaza ncarcarea unei variabile
(locatii de memorie) cu o anumita valoare. Are urmatoarea sintaxa:
1: < variabila >< expresie >
unde < expresie > este o expresie aritmetica sau logica.
Se evalueaza expresia < expresie > iar rezultatul se atribuie variabilei < variabila >,
memorandu-se n locatia de memorie asociata. Aceasta variabila trebuie sa fie de acelasi tip
de data cu expresia sau un tip de data care sa includa si tipul expresiei.
O expresie este constituita din operanzi si operatori. Operanzii pot fi variabile si valori
constante, iar operatorii pot fi:
operatori aritmetici - + (adunare), (scadere), (nmultire), / (mpartire), (ridicare
la putere), div (catul mpartirii ntregi), mod (restul mpartirii ntregi);

operatori relationali - = (egal), 6= (diferit), < (strict mai mic), (mai mic sau egal),
> (strict mai mare), (mai mare sau egal);

operatori logici - OR sau (disjunctie), AND sau (conjunctie), NOT sau (negatie).
Cea mai simpla expresie este formata dintr-o variabil a sau o constant a (operand). Ex-
presiile mai complicate se obtin din operatii efectuate ntre variabile si constante. La scrierea
expresiilor trebuie sa se tina cont de faptul ca, n momentul evaluarii lor, n primul rand
se vor evalua expresiile din paranteze, iar operatiile se executa n ordinea determinata de
prioritatile lor.

Enunturi de ramificare

1: if <expresie-booleana> then
2: <instructiune1> 1: if (a mod 2 = 0) then
[ 2: Output {Numarul este par }
3: else 3: else
4: <instructiune2> 4: Output {Numarul este impar }
] 5: end if
5: end if
Enuntul if . . . then . . . else evalueaza mai ntai expresia booleana pentru a determina
unul din cele doua drumuri pe care le poate lua executia algoritmului. Ea poate include
optional o clauza else.
Daca <expresie-booleana> se evalueaza la valoarea de adevar true, atunci se executa
<instructiune1> si se continua cu urmatoarea instructiune dupa if. Daca <expresie-booleana>
are valoarea de adevar false, atunci se executa <instructiune2>. <instructiune1> si
<instructiune2> sunt instructiuni compuse ce pot sa contina, la randul lor, o alta instructiune
if.

Exemplul 1.1 Un algoritm simplu este cel ce rezolv a ecuatia de gradul I, ax+b = 0, a, b R
(algoritmul 1). Acesta se bazeaza pe rezolvarea matematica a ecuatiei de gradul I:
1. daca a = 0, atunci ecuatia devine 0 x + b = 0. Pentru cazul n care b 6= 0 avem
0 x + b = 0, egalitate ce nu poate fi satisf a pentru nicio valoare a lui x R sau
acut
C. Spunem, n acest caz, ca ecuatia este incompatibila. Daca b = 0, avem 0 x + 0 =
0, relatia fiind adevarata pentru orice valoare a lui x R. Spunem c a ecuatia este
compatibil nedeterminata.

5
a solutie, x1 = ab R.
2. daca a 6= 0, ecuatia are o singur

Algoritm 1 Algoritm pentru rezolvarea ecuatiei de gradul I


1: Input {a, b}
2: if (a = 0) then
3: if (b = 0) then
4: Output { Ecuatie compatibil nedeterminata }
5: else
6: Output { Ecuatie incompatibila }
7: end if
8: else
9: x ab
10: Output { Solutia este:, x }
11: end if

Avand acest algoritm drept model s a se realizeze un algoritm pentru rezolvarea ecuatiei
2
generale de gradul al II-lea, ax + bx + c = 0, unde a, b, c R.

Enunturi repetitive
Enunturile repetitive permit descrierea unor prelucrari ce trebuie efectuate n mod repetat,
n functie de pozitia conditiei de continuare existand doua variante de structuri repetitive:
structura repetitiva conditionata anterior si structura repetitiv
a conditionat
a posterior.
Structura repetitiva conditionata anterior while (sau instructiune de ciclare cu test
initial) are urmatoarea sintaxa:
1: sum 0
2: i1
1: while <expresie-booleana> do
3: while (i n) do
2: <instructiune>
4: sum sum + i
3: end while
5: i i+1
6: end while
Cat timp <expresie-booleana> este adevarat
a, se executa <instructiune>; daca <expresie-
booleana> este falsa chiar la prima evaluare, atunci <instructiune> nu ajunge sa fie re-
alizata niciodata. Acest comportament este opus celui corespunzator structurii repetitive
conditionata posterior repeat, unde <instructiune> este executata cel putin o data. (Daca
o expresie are valoarea de adevar true spunem atunci ca expresia este adev arata ; daca o
expresie are valoarea de adevar false spunem atunci ca expresia nu este adevarata - este
falsa).

Exemplul 1.2 Vom realiza un algoritm pentru calculul c atului si restului mp artirii a doua
numere ntregi, prin scaderi succesive (a se vedea algoritmul 2).
Mai ntai, se initializeaza catul cu valoarea zero (linia 3) si restul cu valoarea dempartitului
(linia 4). Apoi, atata timp cat restul este mai mare dec at valoarea mp artitorului (liniile 5 -
8), vom incrementa catul cu o unitate, si decrementa restul cu valoarea mp artitorului. La
final, sunt afisate valorile catului si restului.

Un caz particular de structura repetitiva conditionata anterior este for, utilizata n cazul
n care o instructiune sau un grup de instructiuni trebuie sa se repete de 0 sau mai multe
ori - numarul de repetitii fiind cunoscut nainte de nceperea sa. Enunturile repetitive bazate

6
Algoritm 2 Algoritm pentru calculul mpartirii ntregi
1: Input {a, b}
2: if ((a > 0) (b > 0)) then
3: cat 0
4: rest a
5: while (rest b) do
6: rest rest b
7: cat cat + 1
8: end while
9: Output {cat, rest}
10: else
11: Output {Numerele trebuie sa fie strict pozitive!}
12: end if

pe instructiunile while si repeat sunt mult mai potrivite n cazul n care conditia de ter-
minare trebuie reevaluata n timpul cicl
arii (atunci c
and num
arul de repetitii nu este cunoscut
apriori).
Instructiunea for are urmatoarea sintaxa:
1: for <Var> <Expresie1>, <Expresie2>, 1: sum 0
Step < p > do 2: for i 1, n do
2: <instructiune> 3: sum sum + i
3: end for 4: end for
Comportamentul enuntului repetitiv for se descrie cel mai bine prin comparatie cu
enuntul repetitiv while. Astfel secventa urmatoare de limbaj pseudocod
1: for v e1 , e2 ST EP p do
2: < instructiune >
3: end for
este echivalenta cu secventa ce contine enuntul repetitiv while:
1: v e1
2: while (v e2 ) do
3: <instructiune>
4: v v+p
5: end while
e1 reprezinta valoarea initiala, e2 limita finala, iar p pasul de lucru: la fiecare pas, valoarea
variabilei v este incrementata cu valoarea variabilei p, valoarea ce poate fi atat pozitiva cat
si negativa. Daca p 0 atunci v va lua valori n ordine crescatoare, iar daca p < 0 atunci v
va primi valori n ordine descrescatoare.
In cazul n care pasul de incrementare este 1, atunci el se poate omite din enuntul repetitiv
for, variabila contor fiind incrementata automat la fiecare repetitie cu valoarea 1. Daca pasul
de incrementare p are valoarea 1 atunci avem urmatoarele situatii posibile pentru constructia
for:

Expresie1 > Expresie2: <instructiune> nu se executa niciodata;

Expresie1 = Expresie2: <instructiune> se executa exact o data;

Expresie1 < Expresie2: <instructiune> se executa de Expresie2 Expresie1 + 1 ori.

Exemplul 1.3 Un caz destul de frecvent nt


alnit n practic
a este cel n care se cere deter-
minarea elementului de valoare maxim
a, respectiv minim a dintr-un sir de elemente. Acest

7
sir de elemente poate fi pastrat ntr-o structur
a de date elementara, cunoscut a sub numele
de vector. Un vector este un caz particular de matrice av and o singura linie si n coloane.
Prezentam n continuare algoritmul pentru determinarea elementului de valoare minima
ce apartine unui sir de numere naturale (a se vedea algoritmul 3).

Algoritm 3 Algoritm pentru calculul elementului de valoare minima dintr-un sir


1: Input {N, x1 , x2 , . . . , xN }
2: min x1
3: for i 2, N do
4: if (min > xi ) then
5: min xi
6: end if
7: end for
8: Output { min }

In acest algoritm se observa utilizarea enuntului repetitiv for, nt alnit, n general, n


situatiile n care se cunoaste apriori num arul de pasi pe care trebuie s
a-l realizeze instructiunea
repetitiva.
Mai ntai se initializeaza variabila min cu valoarea elementului x1 , presupun and ca ele-
mentul de valoare minima este primul element din cadrul vectorului X (linia 2). In continuare
vom compara valoarea variabilei min cu valoarea fiec arui element din sir (liniile 37): daca
vom gasi un element a carui valoare este mai mic a decat cea a minimului calculat pana la
momentul curent (linia 4), vom retine noua valoare n variabila min (linia 5).

Structura repetitiva conditionata posterior repeat . . . until prezinta urmatoarea sin-


taxa:
1: sum 0
2: i1
1: repeat
3: repeat
2: <instructiune>
4: sum sum + i
3: until <expresie-booleana>
5: i i+1
6: until (i > n)

Atata timp cat <expresie-booleana> este falsa, se executa <instructiune>. Se observa


faptul ca instructiunile dintre repeat si until se vor executa cel putin o data.

Exemplul 1.4 Algoritmul 4, prezentat n continuare, calculeaz


a suma primelor n numere
naturale, S = 1 + 2 + 3 + . . . + n.
Notam cu Sn suma primelor n numere naturale (Sn = 1 + 2 + 3 + . . . + n). Aceasta se
poate calcula ntr-o maniera incremental
a astfel:

S1 = 1
S2 = 1 + 2 = S1 + 2
S3 = (1 + 2) + 3 = S2 + 3
S4 = (1 + 2 + 3) + 4 = S3 + 4
...
Sn = (1 + 2 + . . . + n 1) + n = Sn1 + n

8
Algoritm 4 Algoritm pentru calculul sumei primelor n numere naturale (prima varianta)
1: Input {N }
2: S0
3: i1
4: repeat
5: S S+i
6: ii+1
7: until (i > N )
8: Output {S}

Observatia 1.5 1. Algoritmul 4 utilizeaz


a enuntul repetitiv cu test final, repeat . . .
until.

2. Enunturile S 0 si i 1 au rolul de a atribui valori initiale variabilelor S si i.


Intotdeauna variabilele trebuie initializate corect din punctul de vedere al algoritmului
de calcul. Variabila ce retine rezultatul unui proces de nsum
ari succesive se va initializa
ntotdeauna cu valoarea 0, valoare ce nu va influenta rezultatul final. O variabila ce
pastreaza rezultatul unei operatii (sum
a sau produs) se va initializa cu elementul neutru
fata de operatia respectiva.

3. Atribuirile i = i + 1 si S = S + i (liniile 5 si 6) sunt lipsite de sens din punct de


vedere algebric. Insa, atunci c and ne referim la un sistem de calcul electronic, aceste
operatii se refera la valoarea curenta si la cea anterioar a a unei variabile. De aseme-
nea, trebuie sa se faca o distinctie clara ntre operatia de atribuire si cea de egalitate:
operatia de atribuire () face ca valoarea curent a a variabilei din stanga operatorului
de atribuire sa fie initializata cu valoarea expresiei din dreapta acestuia (n expresia
k i + 2 variabila k primeste valoarea rezultat a n urma evalu arii expresiei i + 2),
pe cand operatia de egalitate verific a dac a valoarea elementului din st anga operatoru-
lui = (sau a expresiei aflate n partea st anga a operatorului) este egal a cu valoarea
expresiei din dreapta acestuia (de exemplu rezultatul evalu arii expresiei k = i + 2) are
valoarea de adevar true daca valoarea variabilei k este egal a cu valoarea rezultata n
urma evaluarii expresiei i + 2 si are valoarea de adev ar false n caz contrar).
Prin urmare, n cazul expresiei i i + 1, valoarea curent
a a variabilei i devine valoarea
anterioara a aceleiasi variabile incrementat
a cu o unitate.

Suma primelor n numere naturale se constituie n suma termenilor unei progresii arit-
metice ce se poate calcula direct cu formula sumei, astfel 1 + 2 + . . . + n = n(n+1)
2
. Avand
n vedere aceasta identitate, algoritmul de calcul a sumei primelor n numere naturale se
simplifica foarte mult (a se vedea algoritmul 5).

Algoritm 5 Algoritm pentru calculul sumei primelor n numere naturale (a doua varianta)
1: Input {N }
2: S n (n + 1)/2
3: Output {S}

9
Proceduri si functii
Procedurile sunt subrutine ale caror instructiuni se executa ori de cate ori acestea sunt apelate
prin numele lor.
Apelarea procedurilor se face n unitatea de program apelanta prin numele procedurii,
primit n momentul definirii, urmat de lista parametrilor actuali. Aceasta lista trebuie sa
corespunda ca numar si tip cu lista parametrilor formali, n situatia n care exista o lista de
parametri formali n antetul subrutinei. Definitia unei proceduri are urmatoarea sintaxa:
1: procedure <nume>(<lista parametri formali>)
2: <instructiune>
3: end procedure
lista parametri formali (optionala) simbolizeaza o lista de identificatori (parametri)
ce permite transferul datelor ntre subrutina apelant a si subrutina apelat
a. Acesti parametri
se specifica prin nume (identificator) urmat, eventual, de tipul de data al parametrului. Mai
multi parametri de acelasi tip pot fi grupati, folosindu-se drept separator virgula.
De fapt, lista parametrilor formali poate fi descrisa mai detaliat astfel:
1: procedure <nume>(<PFI; PFO>)
2: end procedure
unde
PFI = lista parametrilor formali de intrare.
PFO = lista parametrilor formali de iesire.
Enuntul de apel al unei proceduri are urmatoarea sintaxa:
1: CALL <nume>(PAI; PAO)
PAI - lista parametrilor actuali de intrare, ce corespund parametrilor formali de intrare.
Corespondenta se face de la stanga la dreapta si trebuie sa fie de acelasi tip cu parametrii
formali.
PAO - lista parametrilor actuali de iesire.

Exemplul 1.6 Sa se realizeze un algoritm care determin a cel mai mare divizor comun a
doua numere naturale.
Reamintim cateva rezultate teoretice ce vor fi folositoare pentru ntelegerea procesului de
calcul al algoritmului ce va fi prezentat.

Teorema 1.7 (Teorema mp a numere ntregi a si b, cu b 6=


artirii cu rest) Pentru dou
0, doua numere ntregi q si r, unice, astfel nc
at:

a = b q + r, 0 r < |b|

unde a se numeste dempartitul, b mp


artitorul, q c
atul iar r restul.

Definitia 1.2 Cel mai mare divizor comun (notat cmmdc) a dou
a numere naturale a si b
este un numar natural d cu proprietatile:

1. d|a, d|b (d este un divizor comun al lui a si b);

2. d N astfel ncat d |a, d |b avem d |d (oricare alt divizor comun al lui a si b, l divide
si pe d).

Observatia 1.8 1. cmmdc(a, 0) = a.

2. Daca cmmdc(a, b) = 1 se spune c


a numerele a si b sunt prime ntre ele.

10
3. Intre cel mai mic multiplu comun si cel mai mare divizor comun exist
a urmatoarea
relatie:
ab
cmmmc(a, b) = . (1.1)
cmmdc(a, b)
Metoda de calcul pentru a obtine cel mai mare divizor comun a dou a numere a si b prin
mpartiri succesive, cunoscuta sub numele de algoritmul lui Euclid, se poate descrie astfel:
Se mparte a la b si se retine restul r. Dac a r este nul atunci cel mai mare divizor comun

este b. In caz contrar, se mparte b la r si se p astreaza noul rest. Calculele se continua,
folosindu-se ca dempartit vechiul mp artitor, iar, ca mp
artitor, ultimul rest obtinut, pana
cand restul obtinut devine nul.
Operatiile anterioare pot fi transpuse prin intermediul urm atoarelor formule matematice:
a = bq1 + r1 0 r1 < b
b = r1 q2 + r2 0 r 2 < r1
r1 = r2 q3 + r3 0 r 3 < r2
...
rk = rk+1 qk+2 + rk+2 0 rk+2 < rk+1
...
rn2 = rn1 qn + rn 0 rn < rn1
rn1 = rn qn+1 + rn+1 0 rn+1 < rn
Aceste mpartiri nu se constituie ntr-un proces infinit, deoarece secventa de numere natu-
rale r1 , r2 , . . . , rn+1 , . . . este strict descresc
atoare (r1 > r2 > . . . > rn > . . .) si marginita
inferior de 0. Rezulta ca p N astfel nc at rp 6= 0, si rp+1 = 0.

Algoritm 6 Algoritmul lui Euclid pentru calculul cmmdc a doua numere


1: procedure cmmdc(x, y; b)
2: ax
3: by
4: if (b = 0) then
5: ba
6: else
7: r a mod b
8: while (r 6= 0) do Procesul se ncheie atunci c
and r ia valoarea 0
9: ab
10: br
11: r a mod b
12: end while
13: end if
14: end procedure

Daca analizam ntregul proces de calcul, se observa faptul ca dempartitul este mpartitorul
de la etapa anterioara, iar mpartitorul este restul de la etapa anterioara. De asemenea, tre-
buie mentionat ca n acest proces de calcul, catul nu participa n mod activ.
Algoritmul 6 este un foarte bun exemplu de utilizare a instructiunii de ciclare cu test
initial, while. Din analiza algoritmului reiese faptul ca, mai ntai, se efectueaza o evaluare a
restului r (calculul sau, liniile 7 si 11), dupa care se testeaza conditia egalitatii acestuia cu
valoarea 0 (linia 8).

11
Functiile au aceeasi sintaxa ca si procedurile:
1: function <nume>(<parametri>)
2: <instructiune>
3: end function
Functiile pot fi apelate prin numele lor, ca termen al unei expresii.

1.2 Exemple
Exemplul 1.9 Sa se verifice printr-un algoritm dac
a un num
ar natural este prim sau nu.

Definitia 1.3 Un numar natural k 2 se numeste prim dac


a singurii s
ai divizori naturali
sunt 1 si k.

Pe baza acestei definitii se poate concluziona ca un num


ar natural k este prim daca nu are
niciun divizor propriu n intervalul [2, k 1].

Algoritm 7 Algoritm pentru verificarea daca un numar este prim


1: Input {N }
2: if (n < 2) then
3: Output {Numarul nu este prim.}
4: else
5: i2
6: prim true
7: while ((i n 1) (prim = true)) do
8: if (n mod i = 0) then
9: prim f alse
10: else
11: i i+1
12: end if
13: end while
14: if (prim = true) then
15: Output {Numarul este prim.}
16: else
17: Output {Numarul nu este prim.}
18: end if
19: end if

Daca n este numar prim, corpul enuntului de ciclare while se va executa de n 2 ori (a
se vedea algoritmul 7).
Conditia i n 1 poate fi mbunat a cu i n/2, deoarece ntre jum
atit atatea numarului
si n nu mai exista niciun alt divizor, pentru orice valoare a lui n.
Daca 2 este un divizor al numarului n atunci si n/2 este un divizor al lui n. Daca 2 nu
este divizor al lui n atunci nici n/2 nu este divizor al lui n. Un enunt asemanator este valabil

12
si pentru numerele 3, 4, 5, . . .. Astfel se formeaz
a dou
a siruri:
corespunde n
2
2
corespunde n
3
3
corespunde n
4
4
.. ..
. .
corespunde n
k
|{z} k
|{z}
Sir1 Sir2

Se observa faptul ca primul sir (Sir1 ) este compus din elemente cu valori consecutive,
a proprietate. Numerele dintr-o pereche (p, np ) sunt
iar al doilea sir (Sir2 ) nu respecta aceast
legate ntre ele astfel: n momentul n care am verificat faptul c a num arul p nu este divizor
al lui n, implicit am dedus ca nici n/p nu este un divizor al lui n. Prin urmare nu mai este
nevoie sa verificam si valoarea n/p. Primul sir este strict cresc ator iar cel de-al doilea este
strict descrescator. Elementele din primul
sir devin mai mari dec at cele din al doilea atunci
cand k n/k k 2 n k n. Astfel rezult a conditia pentru limita superioara a
ciclarii: i n (a se vedea algoritmul 8).

Algoritm 8 Algoritm pentru verificarea daca un numar este prim (varianta optimizata)
1: Input {N }
2: if (n < 2) then
3: Output {Numarul nu este prim.}
4: else
5: i2
6: prim true

7: while ((i n) (prim = true)) do
8: if (n mod i = 0) then
9: prim f alse
10: else
11: i i+1
12: end if
13: end while
14: if (prim = true) then
15: Output {Numarul este prim.}
16: else
17: Output {Numarul nu este prim.}
18: end if
19: end if

Exemplul 1.10 Se spune ca un vector este simetric dac a primul element este egal cu ultimul,
al doilea cu penultimul etc. Algoritmul urm ator verific
a dac
a un vector de numere ntregi este
simetric.
Vom utiliza doua variabile de indici, ce pornesc, una din st anga, si cealalt
a din dreapta.
Atata timp cat variabila din stanga este mai mica decat variabila din dreapta iar elementele
din vector corespunzatoare variabilelor de indiciere sunt egale, se incrementeaz a variabila din
stanga si se decrementeaza variabila din dreapta. Dup a par
asirea instructiunii de ciclare, se

13
face o verificare pentru determinarea conditiei care nu a mai fost ndeplinit a si a condus la
iesirea din bucla. Daca i j atunci conditia (i < j) nu a mai fost ndeplinit a. Acest lucru
conduce la concluzia ca cealalta conditie, (xi = xj ) a fost ndeplinit
a tot timpul (i < j, i, j =
1, n). Deci sirul este simetric n aceast
a situatie.

Algoritm 9 Algoritm pentru verificarea simetriei unui vector


1: Input {N, x1 , x2 , ..., xN }
2: i 1, j N
3: while ((i < j) (xi = xj )) do
4: ii+1
5: j j 1
6: end while
7: if (i j) then
8: Output {Vectorul este simetric.}
9: else
10: Output {Vectorul nu este simetric.}
11: end if

Exemplul 1.11 Se da un numar format din n cifre. Vom prezenta un algoritm n care se
determina cel mai mare numar obtinut prin eliminarea a k cifre (k < n) dintre cele n (vezi
algoritmul 10).
Compararea a doua numere naturale ce prezint a acelasi num ar de cifre se face ncepand
de la stanga spre dreapta (de la cifrele mai semnificative c atre cele mai putin semnificative).
Fie A = a1 a2 . . . an numarul nostru.Vom nota prin m num arul de cifre ce vor r
amane din
numarul initial dupa eliminarea celor k cifre (m = nk). Problema se reduce la determinarea
celui mai mare numar format cu m cifre dintre cele n cifre initiale.
La nceput numarul contine n cifre, p astrate n ordine, n vectorul A: a1 , a2 , . . . , an .
Solutia se construieste n mod iterativ, la pasul i aleg anduse o valoare corespunzatoare: se
determina prima cifra de valoare maxim a si pozitia acesteia n cadrul subsecventei de limite
l si n m + i, al , . . . , anm+i .

In instructiunea for (linia 4) pentru fiecare pozitie libera din numarul rezultat, se va
alege cifra maxima dintre cele disponibile;

linia 5 - se trece la pozitia urmatoare: l reprezinta indexul din sirul A al cifrei aleasa
la pasul anterior (i 1) si contine pozitia pe care a fost gasita cifra maxima. Cifra
maxima pentru pasul curent va fi cautata n intervalul l . . . n m + i, unde n m + i
reprezinta limita superioara a indexului pana la care se poate alege un element la pasul
curent;
a1 . . . a ...a . . . an
| l {znm+i}
subsecventa pentru care se determin
a cifra maxim
a

liniile 6, 7 - se initializeaza variabila de ciclare j cu l si valoarea maxima cu 0; Deoarece


ai 0, i = 1, n, se poate initializa variabila max (unde se va pastra valoarea maxima)
cu 0;

linia 8 - se cauta cifra maxima pana la limita din dreapta n m + i (daca se merge mai
la dreapta dincolo de aceasta limita nu se mai pot alege restul de m i cifre);
linia 10 - se actualizeaza valoarea maxima;

14
Algoritm 10 Algoritm pentru determinarea numarului maxim obtinut dupa eliminarea a k
cifre
1: Input {n, a1 , . . . , an , k}
2: m nk
3: l0
4: for i 1, m do
5: l l+1
6: jl
7: max 0
8: while (j n m + i) do
9: if (max < aj ) then
10: max aj
11: lj
12: end if
13: j j+1
14: end while
15: Output {max}
16: end for

linia 11 - se actualizeaza pozitia valorii maxime.

Exemplul 1.12 Vom prezenta doi algoritmi ce determin a toate tripletele de numere naturale
(a, b, c) ce verifica relatia a2 + b2 = c2 unde c 100.
Prima varianta de lucru prezinta generarea tuturor tripletelor (i, j, k) unde 2 i < j <
k n. Dintre acestea se vor alege doar acelea ce verific a relatia k k = i i + j j. Daca vom
considera doar instructiunea de ciclare 4, atunci conditia 5 se va verifica de n (j + 1) + 1 =
n j ori. Deoarece j = i + 1, n 1, vom obtine faptul c a instructiunea 5 se va executa de:
ni1
X (n i) (n i 1)
(n i 1) + (n i 2) + . . . + (n n + 1) = j=
j=1
2

ori.
Din i = 2, n 2 rezulta ca

n2 ni1 n2 n2
X X X (n i) (n i 1) 1X 2
j = = (i + (1 2n)i + n2 n)
i=2 j=1 i=2
2 2 i=2
n2 n2
1X 2 X
= [ i + (1 2n) i + (n2 n) (n 3)]
2 i=2 i=2

15
1: Input {n} 1: Input {n}
2: for i 2, n 2 do 2: for i 2, n 1 do
3: for j i + 1, n 1 do 3: for j i + 1, n do
4: for k j + 1, n do 4: s ii+j j

5: if (k k = i i + j j) then 5: k s
6: Output {i, j, k} 6: if ((k k = s) (k n)) then
7: end if 7: Output {i, j, k}
8: end for 8: end if
9: end for 9: end for
10: end for 10: end for
11: return 11: return

Pentru cea de-a doua varianta, cea din dreapta, se genereaz a perechile de numere (i, j) cu
proprietatea ca 2 i < j n, se calculeaz
2 a s i i + j j si se verific
a daca numarul
obtinut este patrat perfect (linia 6: ( s) = s). Conditia de la linia 6 se va executa de
n (i + 1) + 1 = n i ori.
Deoarece linia 2 se va executa de n 1 2 + 1 = n 2 ori, valoarea total a reprezentand
numarul de verificari (linia 6) va fi:
n1 n1 n1
X X X n(n 1)
(n i) = (n i) (n 1) = i (n 1) = n+1
i=2 i=1 i=1
2

1.3 Elemente de analiza algoritmilor


Gradul de dificultate al unei probleme P poate fi pus in evidenta prin timpul de executie al
algoritmului corespunzator si/sau prin spatiul de memorie necesar. Timpul de executie n
cazul cel mai defavorabil ne da durata maxima de executie a unui algoritm. Timpul mediu
de executie se obtine prin nsumarea timpilor de executie pentru toate multimile de date de
intrare urmata de raportarea la numarul acestora.

Definitia 1.4 Timpul de executie n cazul cel mai defavorabil al unui algoritm A este o
functie TA : N N unde TA (n) reprezint a numarul maxim de instructiuni executate de
catre A n cazul unor date de intrare de dimensiune n.

Definitia 1.5 Timpul mediu de executie al unui algoritm A este o functie TAmed : N N
unde TAmed (n) reprezinta numarul mediu de instructiuni executate de c
atre A n cazul unor
date de intrare de dimensiune n.

Fiind data o problema P , o functie T (n) se spune ca este o margine superioar


a daca exista
un algoritm A ce rezolva problema P iar timpul de executie n cazul cel mai defavorabil al
algoritmului A este cel mult T (n).
Fiind data o problema P , o functie T (n) se spune ca este o margine pentru cazul mediu
daca exista un algoritm A ce rezolva problema P iar timpul mediu de executie al algoritmului
A este cel mult T (n).
Fiind data o problema P , o functie T (n) se spune ca este o margine inferioara daca orice
algoritm A ce rezolva problema P va avea cel putin un timp T (n) pentru unele date de intrare
de dimensiune n, atunci cand n tinde la infinit (n ).

Definitia 1.6 Fiind data o functie g : N R vom nota cu O(g(n)) multimea: O(g(n)) =
{f (n)| c > 0, n0 0 a.. 0 f (n) c g(n), n n0 }.

16
Vom spune despre f ca nu creste n mod sigur mai repede decat functia g. Pentru a indica
faptul ca o functie f (n) este un membru al multimii O(g(n)), vom scrie f (n) = O(g(n)), n
loc de f (n) O(g(n)).

Observatia 1.13 Vom prezenta cateva propriet


ati ale lui O():

O(f (n) + g(n)) = O(max(f (n), g(n))).


De exemplu pentru functia f (n) = 7 n5 n2 + 3 log n aplic
and regula valorii maxime,
5 2
vom avea: O(f (n)) = O(max(7 n , n , 3 log n)) adic a O(f (n)) = O(n5 ).

O(loga n) = O(logb n);

f (n) = O(f (n)) (reflexivitate);

f (n) = O(g(n)) si g(n) = O(h(n)) atunci f (n) = O(h(n)) (tranzitivitate).

Definitia 1.7 Fiind data o functie g : N R vom nota cu (g(n)) multimea: (g(n)) =
{f (n)| c1 , c2 > 0, n0 0 a.. 0 c1 g(n) f (n) c2 g(n), n n0 }

Spunem ca g(n) este o margine asimptotic tare pentru f (n).

Definitia 1.8 Fiind data o functie g : N R vom nota cu (g(n)) multimea: (g(n)) =
{f (n)| c > 0, n0 0 a.. 0 c g(n) f (n), n n0 }

La fel cum O furnizeaza o delimitare asimptotica superioara pentru o functie, furnizeaza


o delimitare asimptotica inferioara pentru aceasta.

Teorema 1.14 Pentru orice doua functii f (n) si g(n), avem f (n) = (g(n)) f (n) =
O(g(n)) si f (n) = (g(n)).

Definitia 1.9 Fiind data o functie g : N R vom nota cu o(g(n)) multimea: o(g(n)) =
{f (n)| c > 0, n0 > 0 a.. 0 f (n) < c g(n), n n0 }
f (n)
Observatia 1.15 f (n) = o(g(n)) limn g(n)
= 0.

Definitia 1.10 f (n) (g(n)) g(n) o(f (n))


(g(n)) este o multime ce se defineste astfel:
(g(n)) = {f (n)| c > 0, n0 > 0 a.. 0 c g(n) < f (n), n n0 }

Observatia 1.16 1. f (n) = (g(n)), g(n) = (h(n)) = f (n) = (h(n)) (tranzitivi-


tate);

2. f (n) = (f (n)) (reflexivitate);

3. f (n) = (g(n)) g(n) = (f (n)) (simetrie).

Definitia 1.11 O functie f are o crestere exponential a c > 1 a.i. f (x) = (cx ) si
a dac
d a.. f (x) = O(dx ). O functie f este polinomial a f (n) = (nd ) si
a de gradul d dac
d
f (n) = O(n ), d d.

17
Teorema 1.17
c g(n) (f (n)), c > 0
g(n)
lim = 0 g(n) o(f (n)) (1.2)
n f (n)

f (n) o(g(n))

Exemplul 1.18 pentru x R+ avem xn o(n!).


Deoarece
xn xn xn xn 1
< 4 4 4 n/2
= 2n
= ( )n (1.3)
n! x
| .{z
. . x} (x ) x x
n/2ori

rezulta ca
xn
lim =0 (1.4)
n n!

log n o(n).
1
log x (log x) x ln 2
lim = lim = lim =0
x x x (x) x 1

pentru a, b > 1, a, b R avem loga n (logb n).

Calcularea cu exactitate a timpului de executie al unui program oarecare se poate dovedi


o activitate dificila. De aceea, n practica se utilizeaza estimari ncercandu-se eliminarea
constantelor si simplificarea formulelor ce intervin n cadrul evaluarii.
Daca doua parti ale unui program, P1 si P2 , au timpii de executie corespunzatori T1 (n) si
T2 (n), unde T1 (n) = O(f (n)) si T2 (n) = O(g(n)), atunci programul P1 P2 va avea timpul
de executie T1 (n) + T2 (n) = O(max(f (n), g(n))) (regula sumei ), unde reprezinta operatia
de concatenare.
Prin aplicarea acestei reguli, rezulta faptul ca, timpul necesar executiei unui numar finit
de operatii este, neluand n considerare constantele, caracterizat n principal de catre timpul
de executie al operatiei cele mai costisitoare.
Cea de-a doua regula, regula produsului, spune ca, fiind date doua funtii f (n) si g(n) astfel
ncat T1 (n) = O(f (n)) si T2 (n) = O(g(n)), atunci avem T1 (n) T2 (n) = O(f (n) g(n)).
Pe baza regulilor produsului si sumei putem face urmatoarele reduceri:

O(1) O(log n) O(n) O(n log n) O(n2 ) O(n3) O(2n )

Prezentam n continuare cateva reguli generale pentru evaluarea complexitatii unui algoritm:

timpul de executie al unei instructiuni de atribuire, citire sau afisare a unei variabile
este O(1).

timpul de executie al unei secvente de instructiuni este proportional cu timpul instructiunii


care dureaza cel mai mult.

timpul de executie al unei instructiuni de decizie (if) este timpul executarii instructiu-
nilor de pe ramura aleasa plus timpul necesar evaluarii conditiei.

timpul de executie pentru o instructiune de ciclare este suma, dupa numarul de pasi
pe care i realizeaza instructiunea de ciclare, dintre timpul necesar executarii corpului
instructiunii plus timpul necesar evaluarii conditiei.
1: for i 1, n do

18
2: A(i)
3: end for

Daca instructiunii compuse A(i) i corespunde un timp de executie constant t, ce nu


depinde de i, atunci timpul corespunzator ntregii instructiuni de ciclare for anterioare
este: n n
X X
t=t 1 = t n = O(n) (1.5)
i=1 i=1

In cazul general, timpul de executie al instructiunii compuse A(i) depinde de pasul i,


P
notat cu ti . Astfel timpul total corespunzator instructiunii for este ni=1 ti .
Instructiunile de ciclare al caror numar de pasi depinde de ndeplinirea unei conditii
(while) sau de nendeplinirea acesteia (repeat . . . until), sunt mult mai dificil de
analizat deoarece nu exista o metoda generala de a afla cu exactitate numarul de repetari
al corpului instructiunii. De exemplu, pentru fragmentul urmator
1: i l
2: while (ai < x) do
3: ... calcule ce pot modifica, direct sau indirect, valoarea lui ai
4: ii+1
5: end while

nu se poate preciza de cate ori se va ajunge la realizarea instructiunii de incrementare


din interiorul instructiunii while. In marea majoritate a cazurilor se utilizeaza elemente
de teoria probabilitatilor n vederea obtinerii unei estimari a numarului de repetari al
corpului instructiunii.

Exemplul 1.19 De exemplu


1: for i 1, n do
2: instructiune1
3: end for

are timpul de executie O(n).


1: for i 1, n do
2: for j 1, m do
3: instructiune2
4: end for
5: end for

Timpul de executie pentru secventa de cod ce contine dou


a instructiuni de ciclare imbricate
este O(n m).

Exemplul 1.20 Sa consideram un alt exemplu: se d a un sP


ir A de n numere reale, si se
at bi = i ij=1 aj , pentru i = 1, n.
doreste calcularea elementelor unui sir B astfel nc 1

1: for i 1, n do
2: s0
3: for j 1, i do
4: s s + aj
5: end for
6: bi si
7: end for
8: return

19
Daca notam cu o constanta cx timpul necesar pentru efectuarea unei operatii atomice,
vom obtine: costul efectuarii liniei 1 este c1 n, al liniei 2 este c2 n, al liniei 3 este c3 i, al
liniei 4 este c4 i, al liniei 6 este c5 n iar al liniei 8 este c6 .
n
X n
X n
X n
X
T (n) = c1 n + c2 n + c3 i + c4 i + c5 n + c6 = c1 n + c2 n + c3 i + c4 i + c5 n + c6
i=1 i=1 i=1 i=1
n(n + 1)
= (c3 + c4 ) + n(c1 + c2 + c5 ) + c6
2
1 1
= (c3 + c4 )n2 + (c3 + c4)n + n(c1 + c2 + c5 ) + c6
2 2
1 1
= (c3 + c4 )n2 + [ (c3 + c4 ) + c1 + c2 + c5 ]n + c6 = n2 p + n q + c6
2 2
De obicei nu se efectueaza o analiza atat de detaliat
a a algoritmului, dar se ncearca o eval-
uare a blocurilor principale, cum ar fi instructiunile de ciclare, atribuindu-le direct valori
corespunzatoare complexitatii timp, atunci c
and este posibil:

T (n) = O(n2 ) + O(n) + O(1) = O(n2 ) (1.6)

Vom modifica algoritmul anterior astfel nc


at s
a reducem num
arul de calcule efectuate:
1: s0
2: for i 1, n do
3: s s + ai
4: bi si
5: end for
6: return
Pentru aceasta varianta de lucru, complexitatea timp este urm
atoarea:

T (n) = O(1)(linia 1) + O(n)(liniile 24) + O(1)(linia 6) = O(n) (1.7)


In urma analizei de complexitate, putem concluziona faptul c
a cea de-a doua varianta a
algoritmului ruleaza ntr-un timp liniar.

1.3.1 Metoda substitutiei


Metoda substitutiei presupune mai ntai ghicirea (estimarea) formei solutiei pentru relatia
de recurenta si apoi demonstrarea prin inductie matematica a corectitudinii solutiei alese.
Metoda poate fi aplicata n cazul unor ecuatii pentru care forma solutiei poate fi estimata.
Sa consideram urmatoarea relatie de recurenta:
(
1 , daca n = 1
T (n) = n
(1.8)
2T ( 2 ) + n , n rest

Vom presupune ca solutia acestei relatii de recurenta este T (n) = O(n log n) (notam
log2 n = log n). Folosind metoda inductiei matematice, vom ncerca sa demonstram inegali-
tatea:
T (n) cn log n. (1.9)
Presupunem, mai ntai, ca inecuatia (1.9) are loc pentru k < n, inclusiv pentru n2 , adica
T ( n2 ) c n2 log ( n2 ).

20
Vom demonstra ca inecuatia (1.9) este ndeplinita si pentru n: T (n) = 2T ( n2 ) + n.
n n n
T (n) 2(c log ) + n = cn log + n
2 2 2
= cn log n cn log 2 + n = cn log n cn + n (1.10)
cn log n
Metoda inductiei matematice presupune sa aratam ca solutia respecta si cazurile particulare
(T (1) = 1). Pentru n = 1 vom avea T (1) = c 1 log 1 = 0 ceea ce contrazice relatia T (1) = 1.
Aceasta situatie poate fi rezolvata daca consideram ca T (n) = cn log n, n n0 (n0 este
o constanta).
Din T (2) = 4 si T (3) = 5 vom alege valoarea parametrului c astfel ncat sa fie ndeplinite
inegalitatile T (2) 2c log 2 si T (3) 3c log 3. Se observa ca orice valoare a lui c 2 satisface
inegalitatile anterioare.

1.3.2 Schimbarea de variabil


a
Aceasta metoda presupune realizarea unei schimbari de variabila n vederea simplificarii
formulei sau pentru regasirea unei formule
deja cunoscuta.
Sa consideram ecuatia T (n) = 2T ( n) + log n. Daca nlocuim pe n cu 2m obtinem:
m m
T (2m ) = 2T (2 2 ) + log 2m = 2T (2 2 ) + m (1.11)

Notam cu S(m) = T (2m ) si nlocuind n (1.11), obtinem o noua relatie de recurenta:


m
S(m) = 2S( )+m (1.12)
2
Se observa ca aceasta relatie are o forma similara cu cea din formula (1.8).
Solutia relatiei (1.12) este:

S(m) = O(m log m) T (n) = T (2m ) = S(m) = O(m log m) = O(log n log log n) (1.13)
In concluzie avem T (n) = O(log n log log n).

1.3.3 Metoda iterativ


a
Metoda iterativa este mult mai eficienta deoarece nu presupune ghicirea solutiei, varianta ce
conduce deseori la rezultate gresite si timp pierdut. De exemplu, sa consideram urmatoarea
formula de recurenta: (
1 , daca n = 1
T (n) = (1.14)
T ( n2 ) + 1 , n rest
Pentru rezolvarea acestei recurente vom substitui succesiv pe n cu n2 :
n
T (n) = T ( ) + 1
2
n
= T( ) + 2
4
n (1.15)
= T( ) + 3
8
...
n
= T( k) + k
2
21
Prin urmare atunci cand 1 = 2nk k = log n, vom avea
T (n) = 1 + k = 1 + log n T (n) = O(log n).
Sa consideram o alta formula de recurenta:
(
1 , daca n = 1
T (n) = n
(1.16)
2T ( 2 ) + n , n rest

Inlocuind succesiv pe n cu n
vom obtine urmatoarea serie de identitati:
2

n
T (n) = 2T ( ) + n
2
n n n
= 2(2T ( ) + ) + n = 4T ( ) + 2n
2 2 4
n n n (1.17)
= 4(2T ( ) + ) + 2n = 8T ( ) + 3n
8 4 8
...
n
= 2k T ( k ) + kn
2
n
Deoarece substitutia se opreste atunci cand 2k
= 1, adica pentru k = log n, vom avea:

T (n) = 2k + kn = n + n log n = O(n log n) (1.18)

1.3.4 Teorema master


Metoda master pune la dispozitie o varianta de rezolvare a unor recurente avand forma
urmatoare
n
T (n) = aT ( ) + f (n) (1.19)
b
unde a 1, b > 1 sunt constante, iar f (n) este o functie asimptotic pozitiva. Formula de
recurenta (1.19) descrie timpul de executie al unui algoritm ce presupune descompunerea unei
probleme de dimensiune n n a subprobleme, fiecare avand dimensiunea datelor de intrare nb .
f (n) reprezinta costul asociat mpartirii datelor de intrare cat si costul combinarii rezultatelor
celor a subprobleme.

Teorema 1.21 (Teorema master) [30] Fie a 1 si b > 1 dou a constante, f (n) o functie
asimptotic pozitiva, si T (n) definita de relatia de recurenta T (n) = aT ( nb ) + f (n), unde nb va
fi nb sau nb .
Atunci T (n) este marginita asimptotic dup a cum urmeaz a:

1. daca f (n) = O(nlogb a ) pentru o constant


a > 0, atunci T (n) = (nlogb a );

2. daca f (n) = (nlogb a logk n), atunci T (n) = (nlogb a logk+1 n) (de obicei k = 0);

3. daca f (n) = (nlogb a+ ) pentru o constant a af ( nb ) cf (n) pentru o


a > 0, si dac
constanta c < 1 si oricare numar n suficient de mare, atunci T (n) = (f (n)).

Corolarul 1.22 Pentru o functie f de tip polinomial unde f (n) = cnk avem:

1. daca a > bk atunci T (n) = O(nlogb a );

2. daca a = bk atunci T (n) = O(nk log n);

22
3. daca a < bk atunci T (n) = O(nk ).

Exemplul 1.23 Fie T (n) = 2T ( n2 )+n. Avem a = 2, b = 2, k = 1, f (n) = nk . Aplicand


corolarul pentru cazul a = bk , solutia este T (n) = O(nk log n) = O(n log n).
Fie T (n) = 9T ( n3 ) + n. Avem a = 9, b = 3, k = 1. Aplicand corolarul pentru cazul
a > bk , se obtine solutia T (n) = O(nlog3 9 ) = O(n2 ).
Fie T (n) = 4T ( n2 ) + n3 . Avem a = 4, b = 2, k = 3, f (n) = n3 . Aplic
and corolarul
k 3
pentru cazul a < b , se obtine solutia T (n) = O(n ).
Fie T (n) = 2n T ( n2 ) + nn . Nu se poate aplica Teorema master deoarece a = 2n nu este
constant.
Fie T (n) = 2T ( n2 ) + n log n. Atunci avem a = 2, b = 2, k = 1, f (n) = (nlog2 2 logk n),
si aplicand Teorema master, cazul al doilea, obtinem: T (n) = (n log2 n).
Fie T (n) = 12 T ( n2 ) + n1 . Nu se poate aplica Teorema master deoarece a = 12 < 1.

Fie T (n) = 2T ( n2 ) + log n. Aplic and Teorema master pentru a = 2, b = 2, f (n) =
1
O(n 2 ) obtinem: T (n) = ( n).
Fie T (n) = 3T ( n4 ) + n log n. Aplicand Teorema master pentru a = 3, b = 4, f (n) =
log4 3+
(n ) obtinem: T (n) = (n log n).
Fie T (n) = 16T ( n4 ) n log n. Nu se poate aplica Teorema master deoarece f (n) =
n log n nu este o functie asimptotic pozitiv
a.

Exemplul 1.24 Analiza actiunilor


Deschiderea unei actiuni la o anumit a data calendaristic a se calculeaza drept numarul
maxim de zile consecutive (pana la acea dat a) n care pretul actiunii a fost mai mic sau egal
cu pretul din ziua respectiva. Fie pk pretul unei actiuni n ziua k iar dk deschiderea calculata
n aceeasi zi.
In continuare se prezinta un exemplu de calcul al deschiderii unei actiuni pentru un numar
de 7 zile:

p0 p1 p2 p3 p4 p5 p6
9 6 3 4 2 5 7
d0 d1 d2 d3 d4 d5 d6
1 1 1 2 1 4 6

Algoritm 11 Algoritm de calcul al deschiderii unei actiuni (prima varianta)


1: procedure ComputeSpan1(p, n; d)
2: for k 0, n 1 do
3: j1
4: while (j < k) (pkj pk ) do
5: j j+1
6: end while
7: dk j
8: end for
9: return
10: end procedure

23
Algoritmul 11 calculeaza deschiderea unei actiuni pentru un interval mai lung de timp pe
baza evolutiei preturilor acesteia. Timpul de executie al acestui algoritm este O(n2 ).
Vom prezenta un alt mod de calcul al deschiderii unei actiuni folosind o stiv a (a se vedea
algoritmul 12).
Stiva este o structura de date de tip container deoarece ea depoziteaz a elemente de un
anumit tip. Pentru a putea sa operam asupra unei colectii de elemente p astrate ntr-o stiva,
se definesc urmatoarele operatii, n afar
a de operatiile fundamentale push si pop:

push(x: object) - adauga obiectul x n v


arful stivei.

pop(): object - elimina si ntoarce obiectul din v


arful stivei; dac
a stiva este goala
avem o situatie ce genereaza o exceptie.

peek(): object - ntoarce valoarea obiectului din v


arful stivei f
ar
a a-l extrage.

size(): integer - ntoarce num


arul de obiecte din stiv
a.

isEmpty(): boolean - ntoarce true dac


a stiva nu contine nici un obiect.

isFull(): boolean - ntoarce true dac


a stiva este plin
a.

init(capacitate:integer) - initializeaz
a stiva.

Algoritm 12 Algoritm de calcul al deschiderii unei actiuni (a doua varianta)


1: procedure ComputeSpan2(p, n; d)
2: call init(S)
3: for k 0, n 1 do
4: ind 1
5: while (isEmpty(S) = f alse) (ind = 1) do
6: if (pk ppeek(S)) then
7: call pop(S)
8: else
9: ind 0
10: end if
11: end while
12: if (ind = 1) then
13: h 1
14: else
15: h peek(S)
16: end if
17: dk k h
18: call push(S, k)
19: end for
20: return
21: end procedure

Timpul de executie al noului algoritm este O(n).

24
1.4 Exercitii
1. (a) Sa se determine daca un numar natural este simetric sau nu.
(b) Sa se determine toate cifrele distincte dintr-un numar natural.
(c) Sa se determine reprezentarea n baza 2 a unui numar natural.
(d) Sa se determine forma corespunzatoare n baza 10 a unui numar reprezentat n
baza 2.
(e) Sa se elimine dintrun numar natural toate cifrele de forma 3k + 1 si sa se afiseze
numarul rezultat.
(f) Pentru un numar natural dat sa se construiasca din cifrele acestuia cel mai mare
numar prin amplasarea mai ntai a cifrelor impare si apoi a cifrelor pare.

2. (a) Sa se determine toti divizorii unui numar natural.


(b) Sa se verifice daca doua numere naturale sunt prime ntre ele.
(c) Sa se determine toti divizorii comuni a doua numere naturale.
(d) Sa se calculeze toate numerele prime mai mici decat o valoare specificata.
(e) Sa se descompuna n factori primi un numar natural.
(f) Sa se determine cel mai mare divizor comun a doua numere naturale folosinduse
descompunerea n factori primi.

3. Sa se determine toate numerele naturale n (1 n 106 ) cu proprietatea ca atat n cat


si oglinditul sau sunt numere prime.

4. Pentru o secventa de numere naturale de lungime n, sa se realizeze un algoritm care sa


determine cea mai lunga subsecventa de elemente consecutive avand aceeasi valoare.

5. Sa se realizeze un algoritm care sa determine primele n numere prime, pentru un numar


natural n dat.

6. Sa se realizeze un algoritm pentru calcularea tuturor solutiilor ecuatiei:

3x + y + 4xz = 100, x, y, z N. (1.20)

Indicatie Pentru o valoare z 25 si x 6= 0 (x > 0) avem:

3x + y + 4xz 3x + 4xz 3 + 4z > 100.

Astfel pentru z obtinem ca 0 z 24.


Ecuatia 1.20 se poate scrie astfel:
100 y
3x + y + 4xz = 100 x(3 + 4z) = 100 y x = (1.21)
3 + 4z
100y
 100y   100y 
Daca x 6= 0 si y 0 x = 3+4z
3+4z
x [1, 3+4z
].
Astfel, pentru fiecare pereche de numere (z, x) vom calcula pe y astfel: y = 100 3x
4xz.
De asemenea, n 1.21 daca luam x = 0 obtinem multimea de solutii (0, 100, z), z N.

25
Algoritm 13 Algoritm pentru descompunerea n factori primi a unui numar natural
1: Input {n}
2: j2
3: while (j n) do
4: if (n mod j = 0) then
5: k0
6: while (n mod j = 0) do
7: k k+1
8: n n div j
9: end while
10: Output {j k }
11: end if
12: j j +1
13: end while

7. Sa se evalueze complexitatea algoritmului 13, ce realizeaza descompunerea n factori


primi a unui numar natural.

8. Pentru fiecare dintre urmatoarele relatii de recurenta calculati complexitatea timp:

(a) T (n) = 2T ( n2 ) + n
log n
;
(b) T (n) = 16T ( n4 ) + n!;

(c) T (n) = 3T ( n3 ) + n;
(d) T (n) = 3T ( n3 ) + n2 .

9. Sa se calculeze timpul mediu de lucru T (n) al algoritmului 14.

Algoritm 14 Algoritm Calcul5


1: for i 1, n do
2: jn
3: while (j 1) do
4: ...
5: j 2j
6: end while
7: end for

10. Sa se calculeze timpul mediu de lucru T (n) al algoritmului 15.

Algoritm 15 Algoritm Calcul6


1: i n
2: while (i 1) do
3: ji
4: while (j n) do
5: ...
6: j j2
7: end while
8: i 2i
9: end while

26
11. Sa se rezolve urmatoarele ecuatii recurente:

T (n) = 2 T ( n2 ) + n lg n, n = 2k ;
T (n) = 3 T ( n2 ) + c n, n = 2k > 1.

12. Sa se rezolve ecuatia recurenta T (n) = n T 2 ( n2 ), n = 2k , T (1) = 6.

13. Sa se arate ca ex = 1 + x + (x2 ) unde x .

14. Sa se determine primele n elemente ale sirurilor ak si bk date prin urmatoarele relatii
de recurenta:
5ak + 3 ak + 3
ak+1 = , bk = , k 0, a0 = 1. (1.22)
ak + 3 ak + 1
15. Sa se verifice daca urmatoarele afirmatii sunt adevarate:

(a) n2 O(n3 );
(b) n3 O(n2 );
(c) 2n+1 O(2n );
(d) (n + 1)! O(n!);
(e) f : N R , f O(n) f 2 O(n2 );
(f) f : N R , f O(n) 2f O(2n ).

27
Capitolul 2

Grafuri. Grafuri neorientate

2.1 Notiuni de baz


a
Fie V o multime finita si nevida avand n elemente (V = {x1 , x2 , . . . , xn }). Fie E o multime
finita astfel ncat E V V (unde V V este produsul cartezian al multimii V cu ea nsasi)
si E = {(x, y)|x, y V } (E este multimea perechilor (x, y) cu proprietatea ca x si y apartin
multimii V ).

Definitia 2.1 Se numeste graf o pereche ordonat


a G = (V, E).
Elementele xi V se numesc noduri sau v arfuri. Elementele multimii E sunt arce
sau muchii. O muchie (xk , xl ) E se mai noteaz
a si cu [xk , xl ].

|V | se numeste ordinul grafului G si reprezinta numarul varfurilor acestuia, iar |E| se


numeste dimensiunea grafului G si reprezinta numarul muchiilor/ arcelor grafului G.
Graful = (, ) se numeste graful vid.
Daca ntr-o pereche [xk , xl ] nu tinem cont de ordine atunci graful este neorientat iar
perechea reprezinta o muchie ([xk , xl ] = [xl , xk ]). Daca se introduce un sens fiecarei muchii
atunci aceasta devine arc iar graful se numeste orientat ([xk , xl ] 6= [xl , xk ]). Muchiile ce au
aceleasi varfuri se spune ca sunt paralele. O muchie de forma [u, u] se numeste bucl a.
Un graf G se numeste simplu daca oricare doua varfuri ale sale sunt extremitati pentru
cel mult o muchie. Un graf este finit daca multimile V si E sunt finite.
In continuare vom considera un graf neorientat, simplu si finit. Daca [x, y] E vom
spune ca x si y sunt adiacente, iar muchia [x, y] este incidenta cu varfurile x si y. x si y se

Fig. 2.1: a) Un exemplu de graf neorientat cu 6 varfuri b) Graful complet K5

28
mai numesc capetele muchiei. Doua muchii sunt adiacente daca au un varf comun. Un graf
este trivial daca are un singur varf. Daca E = atunci graful G = (V, E) se numeste graf
nul.

Observatia 2.1 Un graf neorientat, simplu, finit poate fi utilizat drept model de reprezentare
al unei relatii simetrice peste o multime.

Definitia 2.2 Se numeste graf complet de ordinul n, si se noteaz a cu Kn , un graf cu pro-


prietatea ca oricare doua varfuri distincte ale sale sunt adiacente (x V, y V, x 6= y
[x, y] E).

Exemplul 2.2 Sa consideram grafurile din figura 2.1.

a) G = (V, E), V = {1, 2, 3, 4, 5, 6}, E = {[1, 2], [1, 5], [2, 3], [2, 6], [3, 4], [4, 5], [5, 6]} (vezi
figura 2.1 a));

b) K5 este graful complet cu 5 varfuri. V


arfurile 3 si 5 sunt adiacente (vezi figura 2.1 b)).

Fig. 2.2: a) Un exemplu de graf neorientat cu 5 varfuri. b) Subgraf al grafului din figura 2.1 b). c) Graf
partial al grafului din figura 2.1 b).

Definitia 2.3 Un graf partial al unui graf dat G = (V, E) este un graf G1 = (V, E1 ) unde
E1 E.

Definitia 2.4 Un subgraf al unui graf G = (V, E) este un graf H = (V1 , E1 ) unde V1 V
iar muchiile din E1 sunt toate muchiile din E care au ambele extremitati n multimea V1
(E1 = E|V1 V1 = {[x, y]|[x, y] E, x, y V1 }).

Se spune ca graful H este indus sau generat de submultimea de varfuri V1 si se noteaza


H = G|V1 sau H = [V1 ]G .
Iata alte cateva notatii des ntalnite:

- G V1 = subgraful ce se obtine din G prin eliminarea submultimii de varfuri V1 (V1


V );

- G x = subgraful G {x};

- < E1 >G = graful partial al lui G generat de E1 ;

29
- G E1 =< E \ E1 >G ;

- G e = G {e}, graful partial obtinut prin eliminarea unei muchii e.

Exemplul 2.3 Graful partial din figura 2.2 c) se obtine din graful 2.1 b) prin stergerea
muchiilor [2, 4], [2, 5], [3, 5], [4, 5]. Subgraful din figura 2.2 b) este indus de multimea V1 =
{1, 3, 4, 5} din graful complet K5 (H = K5 |V1 ).

Definitia 2.5 Gradul unui varf este egal cu num arul muchiilor incidente cu v
arful x si se
noteaza cu d(x) (d(x) = |{[x, u]|[x, u] E, u V }|). Un v
arf cu gradul 0 (d(x) = 0) se
numeste varf izolat.

Notam (G) = min{dG (u)|u G} si (G) = max{dG (u)|u G}.

Fig. 2.3: Graful lui Petersen

Exemplul 2.4 Graful lui Petersen din figura 2.3 este un exemplu de graf trivalent sau cubic
(toate varfurile grafului au acelasi grad, 3).

Propozitia 2.5 Pentru un graf G = (V, E), V = {x1 , x2 , . . . , xn }, |E| = m, avem urmatoarea
relatie:
Xn
d(xk ) = 2m. (2.1)
k=1

Astfel n orice graf G exista un numar par de varfuri al caror grad este un numar impar.

Definitia 2.6 Se numeste secventa grafica un sir de numere naturale d1 , d2 , . . . , dn cu


proprietatea ca ele reprezinta gradele v
arfurilor unui graf neorientat.

Corolarul 2.6 Pentru ca o secventa de numere naturale d1 , d2 , . . . , dn s


a fie secventa grafica,
este necesar ca:
1. k = 1, n, dk n 1;
Pn
2. k=1 dk este un num ar par.

Definitia 2.7 Un lant L = [v0 , v1 , . . . , vm ] este o succesiune de v


arfuri cu proprietatea ca
a varfuri vecine sunt adiacente ([vi , vi+1 ] E, i = 0, m 1). V
oricare dou arfurile v0 si vm
se numesc extremit atile lantului, iar m reprezint a lungimea lantului.

30
Daca varfurile v0 , v1 , . . . , vm sunt distincte doua cate doua, lantul se numeste elementar
(vi 6= vj , i, j = 0, m).

Definitia 2.8 Un lant L pentru care v0 = vm se numeste ciclu.

Definitia 2.9 Se numeste ciclu hamiltonian un ciclu elementar ce trece prin toate varfurile
grafului. Un graf ce admite un ciclu hamiltonian se numeste graf Hamilton sau graf
hamiltonian.

Definitia 2.10 Un lant L ce contine fiecare muchie exact o singura dat


a se numeste lant
eulerian. Daca v0 = vm si lantul este eulerian atunci ciclul se numeste ciclu eulerian.
Un graf ce contine un ciclu eulerian se numeste graf eulerian.

Exemplul 2.7 [1, 2, 3, 1, 4] este un exemplu de lant n graful din figura 2.2 c). Varfurile 1
si 4 sunt extremitatile lantului. Lantul nu este elementar deoarece v arful 1 se ntalneste de
doua ori, n schimb [1, 2, 3, 4, 1] este un ciclu elementar.
[3, 4, 5, 1, 2, 3] este un ciclu hamiltonian n graful din figura 2.2 a). [4, 5, 1, 2, 4, 3] este un
acest graf nu exist
lant eulerian, precum si [2, 4, 3, 2, 1, 5, 4]. In a nici un ciclu eulerian.

Fig. 2.4: Componente conexe

Definitia 2.11 Un graf se numeste conex dac a pentru orice pereche de v


arfuri x si y exista
un lant de la x la y (x, y V, x y).
Se numeste component a conex a un subgraf conex maximal, adica un subgraf conex n
care nici un varf din subgraf nu este adiacent cu unul din afara lui prin intermediul unei
muchii apartinand grafului initial.

Definitia 2.12 O muchie e E se numeste muchie critic a dac


a prin eliminarea acesteia
o componenta conexa se mparte n dou
a sau mai multe componente conexe.

a componente conexe: {1, 2, 3, 4, 5, 6} si {7, 8, 9}.


Exemplul 2.8 Graful din figura 2.4 are dou
Muchia [2, 5] este muchie critica.

Definitia 2.13 Un graf planar este un graf ce poate fi reprezentat n plan astfel ncat
muchiile sale sa nu se intersecteze dou
a c
ate dou
a.

Definitia 2.14 Un graf G = (V, E) se numeste graf bipartit dac a exist


a o partitie a
multimii nodurilor {V1 , V2 } (V = V1 V2 , V1 V2 = , V1 , V2 6= ) astfel nc
at orice muchie din
E va avea o extremitate n multimea V1 si cealalta extremitate n multimea V2 ([x, y] E
avem x V1 si y V2 ).

31
Propozitia 2.9 Un graf este bipartit dac
a si numai dac
a nu contine cicluri de lungime
impara.

Definitia 2.15 Un graf bipartit este complet dac a x V1 , y V2 , [x, y] E (G =


(V, E), V = V1 V2 , V1 V2 = ). Dac
a |V1 | = p, |V2 | = q atunci graful bipartit complet se
noteaza Kp,q .

Fig. 2.5: Un exemplu de graf bipartit si graf bipartit complet.

Exemplul 2.10 In figura 2.5 a) este ilustrat un graf bipartit (V1 = {1, 2, 3}, V2 = {4, 5}),
iar n cazul b) avem un graf bipartit complet, K2,3 .

Definitia 2.16 Se numeste izomorfism de la graful G la graful G o functie bijectiva :


V (G) V (G ) astfel ncat [u, v] E(G) ([u, v]) E(G ).
Daca exista un izomorfism de la graful G la graful G atunci se spune c
a G este izomorf

cu G si not
am acest lucru astfel: G G .

Definitia 2.17 Un graf etichetat este un graf n care fiecare muchie si v


arf poate avea
asociata o eticheta.

Vom prezenta n continuare cateva operatii dintre cele mai ntalnite ce se pot efectua
asupra unui graf oarecare:

fiind dat un nod se cere lista vecinilor sai. Aceasta operatie este cea mai utilizata pentru
o serie ntreaga de algoritmi pe grafuri;

fiind data o pereche de noduri {u, v} se cere sa se determine daca aceasta constituie o
muchie sau un arc al grafului;

adaugarea sau stergerea unui nod sau a unei muchii/arc;

translatarea dintrun mod de reprezentare n altul. De foarte multe ori modul de


reprezentare sub forma caruia au fost introduse datele, difera de modul de reprezentare
optim recomandat pentru un anumit algoritm. In aceasta situatie este indicata trans-
formarea primului mod de reprezentare n modul de reprezentare optim;

fiind data o muchie sau un nod se cere o informatie asociata acestui element: de exemplu
lungimea muchiei sau distanta de la sursa la nodul specificat.

32
2.2 Operatii pe grafuri
1. Complementarul grafului G = (V, E) se defineste astfel: este graful Gc = (V c , E c ), unde
V c = V si E c = {[x, y]|x, y V, [x, y]
/ E}. Altfel spus E c = (V V ) \ E.

Fig. 2.6: Un exemplu de graf complementar altui graf.

In figura 2.6 b) este reprezentat graful complementar Gc al grafului G = (V, E), din
figura 2.6 a) (V = {1, 2, 3, 4, 5}, E = {[1, 2], [1, 5], [2, 3], [3, 4], [4, 5]}). Conform definitiei
Gc = (V c , E c ) unde V c = V si E c = {[1, 3], [1, 4], [2, 4], [2, 5], [3, 5]}.

2. Graful obtinut din G = (V, E) prin insertia unui varf v / V pe o muchie [x, y] E
este graful Gi = (V i , E i ) unde V i = V {v}, E i = (E \ {[x, y]}) {[x, v], [v, y]}.
Considerand graful din figura 2.6 a), prin insertia varfului 6 pe muchia [1, 5] se obtine
graful din figura 2.7 a).

3. Graful obtinut din G = (V, E) prin contractia unei muchii u = [x, y] la un varf t este
graful Gct = (V ct , E ct ), unde V ct = (V \ {x, y}) {t}.

Fig. 2.7: a) Graful obtinut prin inserarea varfului 6 n graful din figura 2.6 a). b) Graful obtinut prin
contractia muchiei [3, 4] n graful din figura 2.6 a).

In figura 2.7 b) este reprezentat graful obtinut prin contractia muchiei [3, 4] din graful
G = (V, E) (vezi figura 2.6 a)).

4. Se numeste graf reprezentativ al muchiilor unui graf G = (V, E), graful GR = (VR , ER )
n care |VR | = |E| si ER = {[e, f ]|e, f E adiacente} (vezi figura 2.8).

5. Definim graful total al unui graf G = (V, E) ca fiind graful GT = (VT , ET ) unde VT =
V E si ET = {[u, v]|u, v V E iar u si v sunt adiacente sau incidente n G}.

33
Fig. 2.8: Figura b) prezinta graful reprezentativ al muchiilor grafului din figura a).

Fig. 2.9: a) Un exemplu de graf neorientat. b) Graful total al grafului din figura a).

Graful total GT = (VT , ET ), unde VT = {1, 2, 3, 4, v1, v2 , v3 , v4 }, si E = {[1, 2], [1, 4],
[2, 3], [3, 4], [v1, v2 ], [v1 , v4 ], [v2 , v3 ], [v3 , v4 ], [v1 , 1], [v1 , 2], [v2 , 2], [v2 , 3], [v3 , 3], [v3 , 4], [v4 , 4], [v4 , 1]},
corespunde grafului G = (V, E), V = {1, 2, 3, 4}, E = {[1, 2], [1, 4], [2, 3], [3, 4]} (vezi
figura 2.9).

6. Reuniunea si intersectia a doua grafuri se definesc astfel:

daca V1 = V2 atunci: G1 G2 = (V1 , E1 E2 ), G1 G2 = (V1 , E1 E2 );


daca V1 V2 6= atunci: G1 G2 = (V1 V2 , E1 E2 ), G1 G2 = (V1 V2 , E1 E2 );
daca V1 V2 = atunci G1 G2 se numeste reuniune disjunct
a a grafurilor G1 si
G2 , iar G1 G2 este graful vid.

7. Definim suma grafurilor G1 si G2 ca fiind graful complementar al reuniunii comple-


mentarelor celor doua grafuri: G1 G2 = (Gc1 Gc2 )c .
Pentru grafurile complete Kp si Kq avem: Kp Kq = (Kpc Kqc )c = Kp+q . Un alt
exemplu de suma a doua grafuri poate fi urmarit n figura 2.10.

8. Se numeste produsul cartezian al grafurilor G1 = (V1 , E1 ) si G2 = (V2 , E2 ), graful


G1 G2 = (V , E ), unde V = V1 V2 si E = {[(u1 , u2), (v1 , v2 )]|u1 , v1 V1 , u2 , v2
V2 ; u1 = v1 si [u2 , v2 ] E2 sau u2 = v2 si [u1 , v1 ] E1 }.

34
Fig. 2.10: a) Doua grafuri neorientate, G1 si G2 . b) Grafurile complementare Gc1 si Gc2 . c) Reuniunea
grafurilor Gc1 si Gc2 . d) Graful complementar al grafului reuniune (Gc1 Gc2 )c .

Fig. 2.11: Graful produs cartezian a doua grafuri

Fie G1 = (V1 , E1 ) unde V1 = {u1 , v1 } si E1 = {[u1 , v1 ]}, si G2 = (V2 , E2 ), unde V2 =


{u2, v2 , w2 } si E2 = {[u2 , v2 ], [v2 , w2]}, doua grafuri neorientate. Atunci graful produs
cartezian al grafurilor G1 si G2 este G1 G2 = (V , E ) unde:
V = {(u1 , u2 ), (u1, v2 ), (u1, w2 ), (v1 , u2 ), (v1 , v2 ), (v1 , w2 )}, si
E = {[(u1 , u2), (v1 , u2)], [(u1 , u2 ), (u1, v2 )], [(u1 , v2 ), (v1 , v2 )], [(u1 , v2 ), (u1, w2 )],
[(u1 , w2), (v1 , w2 )], [(v1 , u2 ), (v1 , v2 )], [(v1 , v2 ), (v1 , w2 )]} (vezi figura 2.11).

9. Se numeste radacina patrata a grafului G, un graf neorientat H cu proprietatea ca


H 2 = G (H 2 = H H).

10. Operatia de compunere a doua grafuri neorientate G1 = (V1 , E1 ) si G2 = (V2 , E2 ),


notata cu G1 [G2 ], se realizeaza astfel: varful (x1 , y1 ) este adiacent cu varful (x2 , y2) n
graful rezultat daca varful x1 este adiacent cu varful x2 n graful G1 sau (x1 = x2 si
varful y1 este adiacent cu varful y2 n graful G2 ) (vezi figura 2.12).

35
Fig. 2.12: Graful rezultat n urma compunerii grafurilor G1 si G2 din figura 2.11

2.3 Moduri de reprezentare

Fig. 2.13: Un exemplu de graf neorientat

Pentru o multime de noduri V vom presupune un mod de ordonare a nodurilor grafului


(primul nod, al doilea nod, etc.), deoarece reprezentarea unui graf depinde de aceasta or-
donare. Astfel, numerotam n mod arbitrar varfurile grafului cu 1, 2, . . . , |V |.
1. Matricea de adiacenta - este o matrice patratica A Mnn (N), n = |V |, unde ai,j = 1
daca exista o muchie ntre nodurile numerotate i si j (ntre xi si xj ), sau ai,j = 0 daca
nu exist
(a o muchie ntre aceste noduri.
1 , daca [xi , xj ] E
ai,j =
0 , daca [xi , xj ]
/ E.

Observatia 2.11 Pentru un graf neorientat aceast


a matrice este simetric
a (ai,j = aj,i).

Exemplul 2.12 Matricea de adiacenta


A corespunz
atoare grafului din figura 2.13 este:

0 1 1 1 0 0 0 0
1 0 0 0 1 0 0 0

1 0 0 0 1 0 0 0

1 0 0 0 0 1 0 0
A=
0

1 1 0 0 0 1 0

0 0 0 1 0 0 1 1

0 0 0 0 1 1 0 0
0 0 0 0 0 1 0 0

36
Fig. 2.14: Un exemplu de graf neorientat ponderat

Matricea de adiacenta are un numar de n2 elemente, iar elementele neredundante sunt


n numar de n(n1)
2
. Prin urmare complexitatea-spatiu a matricii de adiacenta este
2
O(n ).
2. Matricea costurilor - reprezinta o varianta a matricei de adiacent
a ce se obtine n mod
natural luandu-se n considerare graful ponderat: fiecarei muchii i se ataseaza un cost
d (d R), iar valorile matricii C (C Mnn (R)) se definesc astfel:

0
, daca xi = xj
ci,j = + , daca [xi , xj ]
/E (2.2)


d>0 , daca [xi , xj ] E
sau
0
, daca xi = xj
ci,j = , daca [xi , xj ]
/E (2.3)


d>0 , daca [xi , xj ] E.

Exemplul 2.13 Matricea costurilor corespunz


atoare grafului din figura 2.14 are urm
atoarele
valori:

0 35 7 20
35
0 50
7
0 22
20 0 15
C=


50 22 0 44
15 0 10 27

44 10 0
27 0

Observatia 2.14 Notatia (2.2) este folosit


a n aplicatii n care se opereaz
a cu drumuri
de lungime minima iar notatia (2.3) este folosit a n aplicatii n care se opereaza cu
drumuri de lungime maxima ntre doua noduri.

Matricea costurilor va ocupa un spatiu de memorie mai mare decat cel ocupat de
matricea de adiacenta: pentru reprezentarea unui element al matricii de adiacenta este
suficient un bit, pe cand pentru reprezentarea unui element al matricii costurilor se
foloseste un byte, un ntreg (lung) sau un num
ar real, n functie de valoarea maxima pe
care o poate lua d.

37
Observatia 2.15 Matricea costurilor pentru un graf neorientat este simetrica.

Prin urmare pentru reprezentarea unei matrici de adiacenta de dimensiuni n n sunt


necesari n n8 octeti, pe cand pentru reprezentarea unei matrici de costuri cu elemente
numere ntregi sunt necesari 2 n2 octeti, daca presupunem ca un numar ntreg fara
semn este pastrat pe 2 octeti.

3. Liste de adiacenta - pentru fiecare nod se retine lista tuturor nodurilor sale adiacente.
Mai exact, unui nod xk se ataseaza lista tuturor vecinilor sai.
Aceasta reprezentare se preteaza mai bine pentru grafuri cu un numar mare de noduri si
un numar mic de muchii (graf rar ). In cazul unui graf neorientat, numarul elementelor
din listele de adiacenta este 2 |E|, deoarece o muchie [u, v] va fi prezenta de doua ori,
atat n lista de adiacenta a nodului u cat si n lista de adiacenta a nodului v.

Exemplul 2.16 Listele de adiacent


a pentru graful din figura 2.13 sunt:
1: (2, 3, 4) 5: (2, 3, 7)
2: (1, 5) 6: (4, 7, 8)
3: (1, 5) 7: (5, 6)
4: (1, 6) 8: (6)

Aceste liste de adiacenta (sau liste de vecini) pot fi reprezentate, ca structuri de date,
prin intermediul tablourilor sau prin intermediul listelor simplu sau dublu nlantuite.
Pentru reprezentarea ce utilizeaza tablouri, se vor defini doua tablouri notate Cap si
List (Cap M1n (N), List M22m (N), n = |V |, m = |E|) unde:

0 , daca nodul respectiv nu are vecini

Capk = j , unde j indica numarul coloanei din matricea List unde se afla memorat


primul vecin al varfului xk .

List1,j = indicele unui varf ce se afla n lista de vecini a varfului xk .




0 , daca varful respectiv este ultimul (aceasta valoare are aceeasi

semnificatie ca si valoarea NIL sau NULL utilizata n lucrul cu pointeri)
List2,j =


i , unde i reprezinta numarul coloanei unde se afla memorat urmatorul

nod (vecin) din lista de vecini a nodului xk .

Exemplul 2.17 Pentru exemplul din figura 2.13 avem urm


atoarea configuratie pentru
cei doi vectori considerati, Cap si List:
Nod 1 2 3 4 5 6 7 8
Cap 1 4 6 8 10 13 16 18
 
2 3 4 1 5 1 5 1 6 2 3 7 4 7 8 5 6 6
List =
2 3 0 5 0 7 0 9 0 11 12 0 14 15 0 17 0 0

De exemplu, n limbajul C putem defini urmatoarele structuri de date, avand rolul de a


ne ajuta la mentinerea n memorie a listelor de adiacenta ale unui graf oarecare folosind
liste liniare simplu nlantuite (a se vedea figura 2.15):

38
1 2 3 4 NULL

2 1 5 NULL
3 1 5 NULL
4 1 6 NULL
5 2 3 7 NULL
6 4 7 8 NULL
7 5 6 NULL
8 6 NULL

Fig. 2.15: Liste de adiacenta

typedef str u ct nod {


i nt nodeIndex ;
str u ct nod next ;
}NOD;

#define MAXN 100


NOD v e c i n i [MAXN] ;

NOD* vecini[MAXN] este un array (tablou) de pointeri; un element al tabloului vecini[k]


pastreaza capul listei de vecini ai nodului de indice k.
struct nod *next reprezinta un pointer catre urmatoarea nregistrare de tip NOD,
nregistrare ce contine informatii referitoare la urmatorul vecin.
nodeIndex pastreaza indicele (eticheta) unui nod.
Cantitatea de memorie necesara pentru pastrarea listelor de adiacenta se poate aproxi-
ma astfel:

reprezentarea folosind matricile Cap si List - putem presupune ca n, m 65535


adica sunt suficienti 2 octeti pentru reprezentarea acestor numere n memorie
(216 1 = 65535). In aceasta situatie, cantitatea de memorie necesara pentru
matricea Cap este 2n octeti, iar pentru matricea List este 8m octeti. Astfel
complexitatea-spatiu este O(n + m).
reprezentarea folosind pointeri - presupunem ca folosim 2 octeti pentru informatia
utila si 2 octeti pentru informatia de legatura. Numarul de octeti folositi pentru
reprezentarea unui pointer n limbajul C poate depinde de modelul de memorie
ales (de exemplu small, huge etc.) sau de dimensiunea instructiunilor (pe 16, 32
sau 64 de biti).
Sa presupunem ca se utilizeaza 2n octeti pentru vectorul ce contine adresele primu-
lui element (capul) al fiecarei liste de vecini, si 8m octeti pentru nregistrarile
listelor de vecini.
Din aceasta cantitate de memorie, 4m reprezinta informatie utila (indicii vecinilor),
iar 2n + 4m informatie auxiliara (pointeri).

4. Lista de muchii - ntr-o structura de date se pastreaza lista tuturor muchiilor grafului,
practic, pentru fiecare muchie fiind memorate valorile indicilor nodurilor-extremitati
ale acesteia. Aceasta constituie cea mai simpla modalitate de reprezentare a unui graf.

39
Operatia de adaugare a unui nod sau a unei muchii se realizeaza ntr-un timp constant,
alte operatii fiind mai costisitoare: de exemplu, determinarea listei de vecini a unui nod
necesita un timp (m).

Exemplul 2.18 Pentru pastrarea n memorie se poate folosi o matrice M cu doua linii
si |E| coloane (M M2|E|(N)), unde:
M1,k - indicele varfului ce reprezint
a prima extremitate a muchiei k;
M2,k - indicele varfului ce reprezint
a cea de-a doua extremitate a muchiei k.
 
1 1 1 2 3 4 5 6 6
M=
2 3 4 5 5 6 7 7 8

Complexitatea timp pentru diverse operatii efectuate asupra fiecaruia dintre structurile
de date utilizate de modurile de reprezentare amintite se poate sintetiza astfel:

Matricea de adiacent
a Liste de vecini Lista de muchii
Gradul unui nod xi O(n) O(d(xi )) O(m)
(xi , xj ) E? O(1) O(d(xi )) O(m)
Urm
atorul vecin al lui xi O(n) O(d(xi )) O(m)

2.4 Parcurgerea grafurilor


Pentru un graf dat este important sa se stabileasca o modalitate sistematica de vizitare
a tuturor varfurilor grafului, parcurgere ce se realizeaza n scopul prelucrarii informatiei
continute de catre acestea.

2.4.1 Parcurgerea n l
atime (BFS-Breadth First Search)
Metoda de parcurgere n latime viziteaza nodurile grafului n felul urmator (a se vedea algo-
ritmul 16):

se viziteaza mai ntai varful de pornire (sa l notam k);

urmeaza, n ordine, toti vecinii nca nevizitati ai nodului k;

se continua cu vecinii nca nevizitati ai acestora, s.a.m.d.

Pentru graful considerat n figura 2.13 ordinea de parcurgere este urmatoarea: 1, 2, 3, 4,


5, 6, 7, 8. Daca consideram muchiile folosite n timpul parcurgerilor (muchiile prin intermediul
carora s-a naintat n graf) se obtine un arbore/p adure de parcurgere/vizitare/acoperire. In
figura 2.16 este reprezentat arborele de acoperire n l atime rezultat n urma parcurgerii gra-
fului din exemplu.
Algoritmul de parcurgere utilizeaza o structura de date de tip coad a n care vor fi memo-
rate nodurile vizitate, dar care nu au fost nca prelucrate (nu au fost cercetati vecinii lor).
Reamintim ca numarul de noduri din multimea V este n. Vectorul vizitat pastreaza situatia
vizitarii nodurilor
( grafului G, astfel:
1 , daca nodul k a fost vizitat
vizitatk =
0 , daca nodul k nu a fost vizitat.
Implementarea n limbajul C a algoritmului 16 este urmatoarea:

40
Fig. 2.16: Arbore de acoperire n latime

Algoritm 16 Algoritm de vizitare n latime


1: procedure BFS(k, n, V ecin)

k
- nodul de la care ncepe vizitarea
Input: n - numarul de noduri din graf


V ecin - matricea de adiacent a a grafului
2: for i 1, n do
3: vizitati 0
4: end for
5: vizitatk 1 marcarea nodului curent ca fiind vizitat
6: Qk inserarea nodului curent k n coada
7: call V izitare(k) vizitarea nodului curent
8: while (Q 6= ) do
9: Qk extragere nod curent din coad a
10: for i 1, n do
11: if ((vizitati = 0) (vecink,i = 1)) then
12: vizitati 1 marcarea nodului i ca fiind vizitat
13: Qi inserarea nodului i n coad
a
14: call V izitare(i) vizitarea nodului i
15: end if
16: end for
17: end while
18: end procedure

Listing 2.1: parcurgerebf.c


#include <s t d i o . h>
#include <mem. h>

#define MAXN 100


#define TRUE 1
#define FALSE 0

i nt n ; // Numarul de n o d u r i d i n g r a f
char v e c i n [MAXN] [MAXN] ; // M a t r i c e a de a d i a c e n t a
i nt coada [MAXN] ; // S t r u c t u r a de da te c e p a s t r e a z a e l e m e n t e l e c o z i i
i nt f i r s t ; // I n d i c e l e p r i m u l u i element d i n coada
i nt l a s t ; // I n d i c e l e u l t i m u l u i element d i n coada

41
i nt v i d a ; // P a s t r e a z a s t a r e a c o z i i

/
Initializarea cozii circulare .
/
void i n i t Q u e u e ( void ) {
v i d a = TRUE;
f i r s t = 0;
l a s t = MAXN;
}

/
I n t o a r c e pentr u p o z i t i a k , ur ma to a r ea p o z i t i e d i n coada .
/
i nt next ( i nt k ) {
return ( k + 1 ) % MAXN;
}

/
I n s e r e a z a e l e m e n t u l a c a r u i v a l o a r e e s t e p a s t r a t a i n v a r i a b i l a v i n coada .
/
void enQueue ( i nt v ) {
l a s t = next ( l a s t ) ;
coada [ l a s t ] = v ;
i f ( vida )
v i d a = FALSE ;
}

/
E xtr a g e un element d i n coada .
/
i nt deQueue ( void ) {
i nt v = coada [ f i r s t ] ;
f i r s t = next ( f i r s t ) ;
i f ( f i r s t == next ( l a s t ) )
v i d a = TRUE;
return v ;
}

/
P a r cur g e i n l a t i m e g r a f u l po r nind de l a no dul de s t a r t k .
/
void b f s ( i nt k ) {
i nt i ;
char v i z i t a t [MAXN] ;

memset ( v i z i t a t , 0 , s i z e o f ( v i z i t a t ) ) ;
vizitat [ k] = 1;
enQueue ( k ) ;
p r i n t f ( %d , k ) ;
while ( ! v i d a ) {
k = deQueue ( ) ;
for ( i = 0 ; i < n ; i ++)
i f ( ( v i z i t a t [ i ] == 0 ) && ( v e c i n [ k ] [ i ] == 1 ) ) {
vizitat [ i ] = 1;
enQueue ( i ) ;
p r i n t f ( %d , i ) ;
}
}

42
}

/
Se c i t e s c numarul de n o d u r i precum s i m a t r i c e a de a d i a c e n t a .
/
void r e a d I n p u t ( void ) {
i nt i , j ;

p r i n t f ( n = ) ; s c a n f ( %d ,&n ) ;
for ( i = 0 ; i < n 1 ; i ++)
for ( j = i + 1 ; j < n ; j ++) {
p r i n t f ( a[%d,%d ] = , i , j ) ;
s c a n f ( %d , &v e c i n [ i ] [ j ] ) ;
vecin [ j ] [ i ] = vecin [ i ] [ j ] ;
}
}

void main ( void ) {


readInput ( ) ;
initQueue ( ) ;
bfs (0 );
}

In exemplul anterior, functia de citire readInput() este una foarte simpla: se citeste
mai ntai numarul de noduri al grafului, iar apoi, se citesc elementele matricei de
adiacenta a grafului. Variabilele n si vecin sunt declarate drept variabile globale.
Deoarece matricea vecin este simetrica, pentru a reduce numarul de citiri, se vor
solicita numai valorile aflate deasupra diagonalei principale:
void r e a d I n p u t ( void ) {
i nt i , j ;

p r i n t f ( n = ) ; s c a n f ( %d ,&n ) ;
for ( i = 0 ; i < n 1 ; i ++)
for ( j = i + 1 ; j < n ; j ++) {
p r i n t f ( a[%d,%d ] = , i , j ) ;
s c a n f ( %d , &v e c i n [ i ] [ j ] ) ;
vecin [ j ] [ i ] = vecin [ i ] [ j ] ;
}
}

Functia memset() este o functie de biblioteca a carei declaratie poate fi gasita n header-
ele mem.h si string.h la Borland C sau memory.h si string.h la Visual C++ 2010 1.
Functia respectiva prezinta urmatoarea semnatura2
void *memset(void *dest, int c, size t count);
si are rolul de a initializa cu valoarea c primii count octeti din zona de memorie ce
ncepe de la adresa identificata de pointerul dest.

vida este o variabila globala ce ia valoarea true atunci cand coada nu contine nici un
element, sau valoarea false n caz contrar.
1
http://msdn.microsoft.com/en-us/library/1fdeehz6.aspx
2
http://en.wikibooks.org/wiki/C_Programming/Strings#The_memset_function

43
Structura de date abstracta de tip coada este implementata static sub forma unei cozi
circulare.

Vizitarea ncepe de la nodul 0 (bfs(0)).

Subrutina vizitare(), n principiu, afiseaza valoarea etichetei nodului trimis ca argu-


ment, nsa poate efectua si alte prelucrari necesare asupra nodului curent, n functie de
cerintele specifice ale algoritmului.

Algoritmul BFS este utilizat de catre metoda Branch-and-Bound ca metoda de explorare


a spatiului solutiilor, cat si n cadrul problemelor de determinare a distantei minime de la
un varf la toate celelalte varfuri, n cazul n care lungimea unui drum dintre doua noduri se
considera ca fiind egala cu numarul de noduri intermediare ale acestuia.

2.4.2 Parcurgerea D (D - Depth)


Metoda de parcurgere D este asemanatoare cu metoda de parcurgere n l
atime (BFS):
la nceput se viziteaza varful de pornire (notat cu k);

urmeaza, n ordine, toti vecinii nca nevizitati ai nodului de pornire;

la pasul urmator se considera ultimul nod vizitat v si se ncearca vizitarea vecinilor nca
nevizitati ai acestuia. Daca nodul v nu mai are vecini nevizitati, se continua procesul
de parcurgere cu nodul ce a fost vizitat exact naintea nodului v, etc.
Metoda de parcurgere D este o combinatie ntre metoda de parcurgere n latime (a se
vedea sectiunea 2.4.1) si metoda de parcurgere n adancime (a se vedea sectiunea 2.4.3).
Pentru un nod, se viziteaza toti vecinii nca nevizitati ai acestuia, nsa spre deosebire de
metoda de parcurgere n latime unde ordinea n care au fost vizitate nodurile se pastreaza si
la parcurgere, la metoda de parcurgere D se prelucreaza mereu ultimul nod vizitat.
Pentru aceasta, n algoritmul BFS se nlocuieste structura de date de tip coada, cu o
structura de date de tip stiva si se obtine algoritmul D (a se vedea algoritmul 17).

Fig. 2.17: Arbore de acoperire pentru parcurgerea D

In figura 2.17 poate fi urmarit arborele de acoperire pentru parcurgerea D a grafului G


prezentat n figura 2.13.

44
Algoritm 17 Algoritm de vizitare D
1: procedure D(k, n, V ecin)
2: for i 1, n do
3: vizitati 0
4: end for
5: vizitatk 1 marcarea nodului curent ca fiind vizitat
6: Sk inserarea nodului curent k n stiva
7: call V izitare(k) vizitarea nodului curent
8: while (S 6= ) do
9: Sk extragerea nodului curent din stiv a
10: for j 1, n do
11: if ((vizitatj = 0) (vecink,j = 1)) then se cauta un vecin nevizitat
12: vizitatj 1 marcarea nodului j ca fiind vizitat
13: Sj inserarea nodului j in stiva
14: call V izitare(j) vizitarea nodului j
15: end if
16: end for
17: end while
18: end procedure

2.4.3 Parcurgerea n ad
ancime (DFS-Depth First Search)
In cadrul acestei metode se va merge n adancime ori de cate ori este posibil: prelucrarea
unui varf consta n prelucrarea primului dintre vecinii sai nca nevizitati.

se viziteaza varful de pornire (notat cu k);

urmeaza, primul vecin nca nevizitat al acestuia;

se cauta primul vecin, nca nevizitat, al primului vecin nevizitat al nodului de start,
s.a.m.d.;

se merge n adancime pana cand se ajunge la un varf ce nu are vecini, sau pentru care
toti vecinii sai au fost vizitati. In acest caz, se revine n nodul sau parinte (nodul din
care a fost vizitat nodul curent), si se continua algoritmul, cu urmatorul vecin nca
nevizitat al nodului curent.

Pentru graful considerat (vezi figura 2.13), n urma parcurgerii n adancime, vom obtine
nodurile n ordinea urmatoare: 1, 2, 5, 3, 7, 6, 4, 8 (a se vedea figura 2.18). Daca ne propunem
sa vizitam exact o singura data toate nodurile unui graf, aplicand algoritmul DFS de cate ori
este nevoie, si selectam numai muchiile utilizate n timpul explorarii, rezulta o padure de
arbori. Fiecare dintre acesti arbori constituie un arbore de acoperire n ad ancime.
In urma parcurgerii n adancime, muchiile unui graf pot fi clasificate n urmatoarele ca-
tegorii:

1. muchie a arborelui de acoperire - muchia [u, v] este o muchie a arborelui de acoperire


daca dfs(u) apeleaza direct dfs(v) sau invers;

2. muchie de ntoarcere - muchia [u, v] este o muchie de ntoarcere daca dfs(u) apeleaza
indirect dfs(v) (x V a.. df s(u) df s(x) df s(v)) sau invers, dfs(v) apeleaza
indirect dfs(u).

45
Fig. 2.18: Arbore de acoperire n adancime

Exemplul 2.19 Pentru graful din figura 2.13 avem:

muchiile [1, 2], [2, 5], [3, 5], [4, 6], [5, 7], [6, 7], [6, 8] sunt muchii ale arborelui de acoperire;

muchiile [1, 3], [1, 4] sunt muchii de ntoarcere;

Algoritm 18 Algoritm de vizitare n adancime (varianta recursiva)


1: procedure DFS(k, n, V ecin)

k
- nodul curent ce se viziteaza
Input: n - numarul de noduri din graf


V ecin - matricea de adiacent a a grafului
2: vizitatk 1 marcarea nodului curent ca fiind vizitat
3: call V izitare(k) vizitarea nodului curent
4: for i 1, n do
5: if ((vizitati = 0) (vecink,i = 1)) then
6: call DF S(i, n, vecin) apelul recursiv al subrutinei DF S pentru nodul i
7: end if
8: end for
9: end procedure

Implementarea algoritmului se poate realiza fie n varianta recursiva (a se vedea algoritmul


18), fie n varianta nerecursiva (a se vedea algoritmul 19). In mod asemanator ca si la
algoritmul de parcurgere n latime, vectorul vizitat gestioneaza situatia vizitarii nodurilor
grafului G:(
1 , daca nodul k a fost vizitat
vizitatk =
0 , n caz contrar.
Subrutina DFS (algoritmul 18) ncepe cu marcarea nodului curent ca fiind vizitat (vizitatk
1) (linia 2) si apelul procedurii Vizitare() (call Vizitare(k)) (linia 3). Se cauta apoi
primul vecin nevizitat i al varfului curent k (nod ce verifica conditia (vizitati = 0)(vecink,i =
1)) si se reia secventa de operatii pentru nodul i cu aceasta proprietate, printr-un apel recursiv
(call DFS(i)) (linia 6).

46
Enuntul repetitiv for i 1, n (linia 4) poate fi optimizat astfel ncat sa nu se mai
verifice toate varfurile grafului, ci numai nodurile adiacente cu nodul curent: n aceasta
situatie reprezentarea cu liste de adiacenta este optima din punct de vedere al numarului de
operatii efectuate, fiind preferata reprezentarii prin matricea de adiacenta.

Algoritm 19 Algoritm de vizitare n adancime (varianta nerecursiva)


1: procedure DFS Nerecursiv(k, n, V ecin)

k
- nodul de la care se porneste vizitarea
Input: n - numarul de noduri din graf


V ecin - matricea de adiacent a a grafului
2: for i 1, n do
3: vizitati 0
4: end for
5: vizitatk 1 marcarea nodului curent ca fiind vizitat
6: call V izitare(k) vizitarea nodului curent
7: Sk inserarea nodului curent k n stiv
a
8: f ound f alse
9: while (S 6= ) do c
at timp stiva nu este vida
10: if (f ound) then
11: Sk extragerea nodului curent din stiv a
12: end if
13: i1
14: f ound f alse
15: while ((i n) (f ound = f alse)) do
16: if ((vizitati = 0) (vecink,i = 1)) then
17: vizitati 1 marcarea nodului i ca fiind vizitat
18: Sk inserarea nodului curent k n stiv
a
19: call V izitare(i) vizitarea nodului i
20: ki
21: f ound true
22: else
23: ii+1
24: end if
25: end while
26: end while
27: end procedure

Algoritmul 19 utilizeaza o structura de date de tip stiva S pentru a pastra tot timpul nodul
grafului din care s-a ajuns la nodul curent (tatal nodului curent din arborele de acoperire n
adancime). Secventa de instructiuni 19.13-19.25 nu este optima deoarece, de fiecare data cand
se revine la un nod parinte, se verifica ntreaga multime de noduri V pentru a identifica un
vecin nevizitat al nodului curent. In vederea reducerii numarului de verificari, se poate utiliza
reprezentarea prin liste de adiacenta, si se salveaza pe stiva, pe langa valoarea nodului curent
k, valoarea vecinului nevizitat gasit al acestuia (sa l notam cu u), astfel ncat, la revenire,
sa se continue cautarea urmatorului vecin nevizitat al nodului curent k, pornind de la acest
nod u. Daca nu mai exista nici un vecin nevizitat al varfului k (linia 10), atunci se revine la
parintele nodului curent, pastrat pe stiva. Algoritmul se opreste n cazul n care stiva este
vida (atunci cand nodul curent este radacina arborelui de vizitare n adancime).
Am ales n vederea implementarii, varianta recursiva pentru usurinta de programare si
eleganta ei. Prezentam n continuare aceasta implementare a algoritmului 18 n limbajul C:

47
Listing 2.2: dfsrec.c
#include <s t d i o . h>
#include <mem. h>

#define MAXN 100

char v e c i n [MAXN] [MAXN] ; // M a t r i c e a de a d i a c e n t a


char v i z i t a t [MAXN] ; // Vecto r c e p a s t r e a z a s t a r e a unui nod : v i z i t a t sau nu
i nt n ; // Numarul de v a r f u r i d i n g r a f
i nt no di ; // Nodul d i n c a r e s e p o r n e s t e v i z i t a r e a

/
Se c i t e s c numarul de n o d u r i precum s i m a t r i c e a de a d i a c e n t a .
/
void r e a d I n p u t ( void ) {
i nt i , j ;

p r i n t f ( n = ) ; s c a n f ( %d , &n ) ;
for ( i = 0 ; i < n 1 ; i ++)
for ( j = i + 1 ; j < n ; j ++) {
p r i n t f ( a[%d,%d ] = , i , j ) ;
s c a n f ( %d , &v e c i n [ i ] [ j ] ) ;
vecin [ j ] [ i ] = vecin [ i ] [ j ] ;
}
p r i n t f ( Nodul i n i t i a l = ) ; s c a n f ( %d , &no di ) ;
memset ( v i z i t a t , 0 , s i z e o f ( v i z i t a t ) ) ;
}

/
P a r c u r g e r e a i n adancime po r nind d i n no dul k .
/
void d f s ( i nt k ) {
i nt i ;

vizitat [ k] = 1;
p r i n t f ( %d , k ) ;
for ( i = 0 ; i < n ; i ++)
i f ( v i z i t a t [ i ] == 0 && v e c i n [ k ] [ i ] == 1 )
dfs ( i ) ;
}

void main ( void ) {


readInput ( ) ;
d f s ( no di ) ;
}

Complexitatea algoritmilor de parcurgere


Complexitatea algoritmilor 18 si 19 prezentati este O(n2 ) deoarece se utilizeaza matricea de
adiacenta drept modalitate de reprezentare a grafului. Daca se utilizeaza reprezentarea prin
liste de vecini, atunci complexitatea algoritmilor devine O(n + m), unde m = |E|.

Matricea de adiacent
a Liste de vecini Lista de muchii
BFS 2
O(n ) O(n + m) O(n + m2 )
D O(n2 ) O(n + m) O(n + m2 )
DFS O(n2 ) O(n + m) O(n + m2 )

48
2.5 Componente conexe
Definim pe multimea varfurilor V a unui graf neorientat G o relatie n felul urmator: xy
daca x = y sau exista n G un lant de la x la y.
Se demonstreaza foarte usor ca relatia este o relatie de echivalenta (exercitiu). Se
cunoaste faptul ca o relatie de echivalenta determina pe multimea pe care este definita o
partitie. Componentele conexe vor fi elementele acestei partitii, formate din varfurile ce sunt
echivalente ntre ele (conform relatiei de echivalenta).
Cu alte cuvinte, exista o partitie a multimii varfurilor V
m
[
V = Vi , Vi Vj = , i, j = 1, m, i 6= j (2.4)
i=1

si o partitie a multimii muchiilor E


m
[
E= Ei , Ei Ej = , i, j = 1, m, i 6= j (2.5)
i=1

astfel ncat fiecare graf Gi = (Vi , Ei ), i = 1, m, este conex (Ei = E|Vi Vi ).

Algoritm 20 Algoritm pentru determinarea componentelor conexe

( ComponenteConexe(n, V ecin)
1: procedure
n - numarul de noduri din graf
Input:
V ecin - matricea de adiacenta a grafului
2: for i 1, n do
3: vizitati 0
4: end for
5: cmp conex nr 0
6: for i 1, n do
7: if (vizitati = 0) then
8: cmp conex nr cmp conex nr + 1
9: call DF S(i, n, V ecin) determinarea componentei conexe ce contine nodul i
10: end if
11: end for
12: end procedure

Pentru determinarea componentelor conexe vom utiliza metoda de vizitare n adancime a


unui graf. Pasii algoritmului descrisi n limbaj natural sunt urmatorii (algoritmul 20 prezinta
descrierea formalizata n pseudo-cod):

Pas 1. La nceput, toate nodurile sunt marcate ca nevizitate.

Pas 2. Se cauta un nod nca nevizitat.

Pas 3. Incepand cu acesta se parcurg toate nodurile accesibile si nevizitate, avand grija sa
marcam vizitarea acestora. Toate aceste noduri formeaza o componenta conexa.

Pas 4. Daca mai exista noduri nevizitate, se reia procesul de calcul de la pasul 2, altfel procesul
de calcul se ncheie.

49
2.6 Muchie critic
a
Reamintim faptul ca ntr-un graf neorientat G = (V, E), o muchie critic a este acea muchie
care prin eliminarea ei conduce la cresterea numarului de componente conexe ale grafului.
Se cere sa se determine toate muchiile critice ale unui graf dat. In continuare sunt prezen-
tate doua variante de rezolvare.

Solutia I
Pentru simplitate, vom trata situatia n care graful considerat este conex (daca graful nu este
conex se determina componentele conexe si numarul acestora si se aplica algoritmul pentru
fiecare componenta conexa n parte). Se elimina, pe rand, fiecare muchie a grafului si apoi
se verifica daca graful rezultat mai este conex (a se vedea algoritmul 21).

Algoritm 21 Algoritm de determinare a muchiei critice (prima varianta)


1: procedure MuchieCriticaI(n, m, V ecin)

n
- numarul de noduri din graf
Input: m - numarul de muchii din graf


V ecin - matricea de adiacenta
2: for k 1, m do
3: elimina muchia k
4: if (Conex(n, V ecin) = f alse) then
5: Output {Muchia k este critica}
6: end if
7: adaug a muchia k reface graful initial
8: end for
9: end procedure
10: function Conex(n, V ecin)
11: for i 1, n do
12: vizitati 0
13: end for
14: call DF S(1, n, V ecin) vizitarea n ad
ancime a grafului
15: for i 1, n do
16: if (vizitati = 0) then
17: return f alse
18: end if
19: end for
20: return true
21: end function

Subrutina Conex() verifica daca graful identificat prin matricea de adiacenta Vecin este
conex (este compus dintr-o singura componenta conexa). Subrutina ntoarce valoarea true
n cazul n care graful considerat este conex si false, n caz contrar. Pentru aceasta, la
nceput, se marcheaza toate nodurile ca fiind nevizitate, si se ncearca parcurgerea nodurilor
grafului, prin intermediul unui apel al subrutinei de vizitare, DFS(1, n, Vecin).
Implementarea algoritmului 21 n limbajul C este urmatoarea:
Listing 2.3: muchiecriticav1.c
#include <s t d i o . h>
#include <mem. h>

50
#define MAXN 100
#define TRUE 1
#define FALSE 0

char v e c i n [MAXN] [MAXN] ; // M a t r i c e a de a d i a c e n t a


char v i z i t a t [MAXN] ; // Vecto r c e p a s t r e a z a s t a r e a unui nod : v i z i t a t sau nu
i nt n ; // Numarul de v a r f u r i d i n g r a f

/
Se c i t e s c numarul de n o d u r i precum s i m a t r i c e a de a d i a c e n t a .
/
void r e a d I n p u t ( void ) {
i nt i , j ;

p r i n t f ( n = ) ; s c a n f ( %d , &n ) ;
for ( i = 0 ; i < n 1 ; i ++)
for ( j = i + 1 ; j < n ; j ++) {
p r i n t f ( a[%d,%d ] = , i , j ) ;
s c a n f ( %d , &v e c i n [ i ] [ j ] ) ;
vecin [ j ] [ i ] = vecin [ i ] [ j ] ;
}
}

/
P a r c u r g e r e a i n adancime a g r a f u l u i po r nind d i n no dul de s t a r t k .
/
void d f s ( i nt k ) {
i nt i ;

v i z i t a t [ k ] = TRUE;
p r i n t f ( %d , k ) ;
for ( i = 0 ; i < n ; i ++)
i f ( ( v i z i t a t [ i ] == FALSE) && ( v e c i n [ k ] [ i ] == 1 ) )
dfs ( i ) ;
}

/
F u n c t i a v e r i f i c a daca g r a f u l e s t e conex .
/
i nt conex ( void ) {
i nt i ;

memset ( v i z i t a t , 0 , s i z e o f ( v i z i t a t ) ) ;
dfs (0 );
for ( i = 0 ; i < n ; i ++)
i f ( v i z i t a t [ i ] == FALSE) {
return FALSE ;
}
return TRUE;
}

void main ( void ) {


i nt i , j ;

readInput ( ) ;
for ( i = 1 ; i < n ; i ++)
for ( j = 0 ; j < i ; j ++)
i f ( v e c i n [ i ] [ j ] == 1 ) {

51
vecin [ i ] [ j ] = 0;
vecin [ j ] [ i ] = 0;
i f ( conex ( ) == FALSE) {
p r i n t f ( Muchia (%d,%d ) e s t e c r i t i c a ! \n , i , j ) ;
}
vecin [ i ] [ j ] = 1;
vecin [ j ] [ i ] = 1;
}
}

Observatia 2.20 Am presupus ca numerotarea nodurilor se face de la 0 si c


a exista un nod
cu eticheta 0.

Complexitatea-timp a acestui algoritm, presupunand ca avem o singura componenta


conexa, este O(m(n + m)): se considera fiecare muchie (O(m)), se elimina din graf, si se
verifica daca graful ramas este conex (O(n + m)).

Solutia a II-a
Cea de-a doua varianta de abordare pentru identificarea unei solutii foloseste urmatoarea
proprietate:
Observatia 2.21 O muchie nu este critic
a dac
a ea face parte din cel putin un ciclu elemen-
tar al grafului respectiv.
In urma vizitarii DFS, toate muchiile unui graf se mpart n muchii ale arborelui de
acoperire si muchii de ntoarcere.
Vom numerota toate nodurile grafului n preordine (numerotarea se realizeaza efectiv n
momentul n care un nod este marcat ca fiind vizitat), toate valorile fiind pastrate ntr-un
vector prenum. Fie lowu valoarea unui nod u, calculata dupa formula urmatoare:

prenumu

lowu = min prenumx , daca [u, x] este muchie de ntoarcere (2.6)


lowy , y descendent direct al lui u.

Daca prenumu lowv , v descendent direct al lui u, atunci nseamna ca nodul v sau un
descendent al lui v prezinta o muchie de ntoarcere la u sau la un stramos al acestuia. Astfel
muchia [u, v] apartine unui ciclu elementar, si, prin urmare, nu este muchie critica (a se vedea
algoritmul 22). Prin negatie, daca exista cel putin un varf v, descendent direct al lui u, cu
proprietatea ca prenumu < lowv atunci [u, v] este muchie critica. Dupa fiecare apel recursiv
al subrutinei DFS (linia 16) se verifica gradul de adevar al expresiei (prenumk < lowi ) (linia
18).
Variabila counter este globala pentru cele doua subrutine, MuchieCriticaII() si
DFS critic(). La numerotarea n preordine se folosesc atribuirile: counter counter + 1,
si prenumk counter. Cand un nod i este vecin cu nodul curent k, si nu a fost nca vizitat,
valoarea lui lowk se calculeaza astfel: lowk min {lowk , lowi}. In situatia n care pentru un
nod i, vecin cu nodul curent k, deja vizitat, avem o muchie de ntoarcere (nodului k i s-a
atribuit deja un numar n preordine), atunci valoarea lowk se calculeaza dupa formula

lowk min {lowk , prenumi}.

Implementarea n limbajul C a algoritmului 22 este urmatoarea:

52
Algoritm 22 Algoritm de determinare a muchiei critice (a doua varianta)

( MuchieCriticaII(n, V ecin)
1: procedure
n - num arul de noduri din graf
Input:
V ecin - matricea de adiacent a
2: for i 1, n do
3: vizitati 0
4: end for
5: counter 0
6: call DF S critic(1, n, V ecin) vizitarea n ad
ancime a grafului
7: end procedure
8: procedure DFS critic(k, n, V ecin)
9: vizitatk 1
10: counter counter + 1
11: prenumk counter, lowk counter
12: for i 1, n do
13: if (vecink,i = 1) then
14: if (vizitati = 0) then
15: tatai k
16: call DF S critic(i, n, V ecin)
17: lowk M in(lowk , lowi )
18: if (prenumk < lowi ) then
19: Output {Muchia (k,i) este critica}
20: end if
21: else
22: if (tatak 6= i) then
23: lowk M in(lowk , prenumi )
24: end if
25: end if
26: end if
27: end for
28: end procedure

Listing 2.4: muchiecriticav2.c


#include <s t d i o . h>
#include <mem. h>

#define MAXN 100

char v e c i n [MAXN] [MAXN] ; // M a t r i c e a de a d i a c e n t a


char v i z i t a t [MAXN] ; // Vecto r c e p a s t r e a z a s t a r e a unui nod : v i z i t a t sau nu
i nt n ; // Numarul de n o d u r i d i n g r a f
i nt t a t a [MAXN] ; // P a s t r e a z a t a t a l f i e c a r u i nod i n a r b o r e l e de a c o p e r i r e
// i n a dincime g e n e r a t de metoda DFS .
i nt prenum [MAXN] ; // prenum [ k ] numer o ta r ea i n p r e o r d i n e
i nt low [MAXN] ;
i nt c o u n t e r ; // c o n t o r g l o b a l c e numara momentul cand e s t e v i z i t a t un nod

void r e a d I n p u t ( void ) {
i nt i , j ;

p r i n t f ( n = ) ; s c a n f ( %d , &n ) ;
for ( i = 0 ; i < n 1 ; i ++)

53
for ( j = i + 1 ; j < n ; j ++) {
p r i n t f ( a[%d,%d ] = , i , j ) ;
s c a n f ( %d , &v e c i n [ i ] [ j ] ) ;
vecin [ j ] [ i ] = vecin [ i ] [ j ] ;
}
}

i nt min ( i nt x , i nt y ) {
return ( x < y ) ? x : y ;
}

void d f s ( i nt k ) {
i nt i ;

vizitat [ k] = 1;
c o u n t e r++;
prenum [ k ] = c o u n t e r ; low [ k ] = c o u n t e r ;
for ( i = 0 ; i < n ; i ++)
i f ( v e c i n [ k ] [ i ] == 1 )
i f ( v i z i t a t [ i ] == 0 ) {
tata [ i ] = k ;
dfs ( i ) ;
low [ k ] = min ( low [ k ] , low [ i ] ) ;
i f ( prenum [ k ] < low [ i ] )
p r i n t f ( %d > %d \n , k , i ) ;
}
else
i f ( t a t a [ k ] != i )
low [ k ] = min ( low [ k ] , prenum [ i ] ) ;
/ p r i n t f ( prenum[%d ] = %d low[%d ] = %d\n , k , prenum [ k ] , k , low [ k ] ) ; /
}

void c r i t i c ( void ) {
memset ( v i z i t a t , 0 , s i z e o f ( v i z i t a t ) ) ;
counter = 0;
dfs (0 );
}

void main ( void ) {


p r i n t f ( \n ) ;
readInput ( ) ;
critic ();
}

Exemplul 2.22 Pentru graful din figura 2.13, valorile prenum si low sunt cele din figura
2.19 (valoarea din partea dreapta a unui v arf reprezinta valoarea sa la numerotarea n preor-
dine, prenum, iar numarul din stanga reprezint a valoarea calculat
a low). Conditia prenumu <
lowv , unde [u, v] este o muchie a grafului, este ndeplinita doar pentru u = 6 si v = 8. Prin
urmare [6, 8] este muchie critica n graful considerat.

Deoarece acest algoritm este constituit din algoritmul modificat de vizitare n adancime
a unui graf, complexitatea-timp este O(n + m) atunci cand graful este reprezentat prin liste
de adiacenta si O(n2 ) n situatia n care graful este reprezentat prin matricea de adiacenta.

54
Fig. 2.19: Algoritmul 22 aplicat pentru graful din figura 2.13

2.7 Exercitii
1. Un graf neorientat cu n noduri, G = (V, E) se numeste graf scorpion daca poseda trei
noduri speciale:

(a) acul - dG (u) = 1 si este legat de coada;

(b) coada - dG (u) = 2 si este legata de ac si corp;

(c) corpul - dG (u) = n 2 fiind legat de toate nodurile din graf cu exceptia acului.

Nu exista alte restrictii cu privire la nodurile grafului.


Sa se realizeze un algoritm care determina daca un graf este graf scorpion folosind O(n)
ntrebari de forma Exista o muchie ntre nodurile u si v? .

2. Se considera n bazine dispuse circular si lipite unul de altul n ordinea numerelor de


ordine. Se da lista celor m perechi de bazine ce trebuie unite prin canale. Un numar
de bazine poate sa apara n mai multe perechi. Canalele pot fi realizate n interiorul
sau n exteriorul cercului, cu conditia ca ele sa nu se intersecteze.
Daca problema are solutie, vor fi afisate doua liste: cea a perechilor de bazine unite
prin canale interioare si cea a perechilor de bazine unite prin canale exterioare. Daca
problema nu are solutie, atunci se va raporta acest lucru.
In cazul n care problema are solutie sa se determine toate posibilitatile de unire prin
canale exterioare sau interioare.
(ONI, 1993)

3. Sa se parcurga n latime si n adancime graful din figura 2.20, sa se construiasca arborii


de parcurgere si sa se puna n evidenta muchiile de ntoarcere.

4. Compania TLC (Telephone Line Company) a stabilit o noua retea de cablu telefonic.
Ea leaga mai multe locatii numerotate cu numere ntregi de la 1 la N. Nu exista doua
locatii numerotate cu acelasi numar. Liniile sunt bidirectionale si conecteaza doua
locatii; fiecare locatie are un post telefonic. Din orice locatie poate fi apelata oricare

55
Fig. 2.20: Un exemplu de graf neorientat cu 8 varfuri

alta locatie, prin legatura directa sau conexiuni intermediare. Uneori reteaua cade n
unele locatii si conexiunea aferenta nu mai este posibila. Tehnicienii de la TLC au
realizat ca, n acest caz, nu numai ca locatia respectiva nu mai poate fi apelata, dar
ea ntrerupe legatura si ntre alte locatii pentru care asigura conexiunea. In aceasta
situatie spunem ca locatia (unde a aparut caderea) este critic a.
Se cere sa se elaboreze un algoritm care sa determine numarul tuturor acestor puncte
critice.
(ACM Europa Centrala, 1996)

5. Fiind dat un graf G = (V, E), sa se verifice daca este aciclic.

6. Fiind dat un graf G = (V, E), sa se verifice daca este bipartit.

7. Intr-un oras, intersectiile sunt numerotate de la 1 la n (se considera intersectie nu


numai locul unde se intersecteaza mai multe strazi ci si punctele terminale, capetele
de strazi). Edilii orasului doresc sa numeroteze si strazile orasului, dar ntr-un mod
care sa tina seama de numerotarea intersectiilor, si anume: doua strazi diferite vor
avea numere diferite si n fiecare intersectie trebuie sa soseasca o strada care sa aiba
numarul intersectiei.
Se cere sa se realizeze un algoritm care sa determine o astfel de numerotare, daca exista.
Datele de intrare constau dintr-un numar n reprezentand numarul de puncte de inter-
sectie; fiecare punct este identificat prin numarul cu care este numerotat, ntre 1 si n,
urmat de lista punctelor cu care acesta comunica direct printr-o strada. Prin strada se
ntelege o portiune de drum aflata ntre doua puncte de intersectie, neglijand faptul ca
n practica strazile cuprind mai multe intersectii.

8. Se considera o tabla de sah de dimensiune n n (n 100) pe care sunt dispuse


obstacole. Se cere sa se determine numarul minim de mutari necesare unui nebun
pentru a se deplasa, respectand regulile jocului de sah si ocolind obstacolele, dintr-o
pozitie initiala data ntr-o pozitie finala data.
Se considera ca obstacolele nu coincid cu pozitia initiala si nici cu cea finala a nebunului.
(Concurs studentesc, 1993)

9. Pe o tarla agricola sunt mai multe parcele ce trebuie cosite. Parcelele de diferite culturi
(lucerna, trifoi, iarba, furaje) vor fi cosite de muncitori diferiti ce lucreaza numai la

56
un anumit fel de cultura (de exemplu un muncitor este specializat numai pe cositul
lucernei).
Terenul agricol se reprezinta printr-o matrice n m (n linii si m coloane). Fiecare
element al matricii corespunde unei multimi de un anumit tip. Doua elemente ale
matricii sunt vecine daca au o latura comuna. O parcela este o multime maximala de
elemente astfel ncat un muncitor se poate deplasa ntre oricare doua elemente de-a
lungul a doua elemente vecine.
Se cere sa se determine numarul de muncitori care pot sa coseasca o cultura data.

57
Capitolul 3

Grafuri euleriene si hamiltoniene

3.1 Grafuri Euleriene


In anul 1736, matematicianul Leonhard Euler publica o lucrare asupra problemei podurilor
din Konigsberg1 (a se vedea figura 3.1) n care se prezinta un studiu teoretic asupra lanturilor
si ciclurilor Euleriene.

Fig. 3.1: Podurile din Konigsberg

Sa consideram un graf neorientat G = (V, E).

Definitia 3.1 Un lant L ce contine fiecare muchie a unui graf G o singur a data se numeste
lant Euler sau lant eulerian. Dac a extremit
atile lantului sunt identice si lantul este
eulerian atunci ciclul se numeste ciclu Euler sau ciclu eulerian. Un graf ce contine un
ciclu eulerian se numeste graf eulerian.

O problema mai cunoscuta legata de notiunile de ciclu si lant eulerian este aceea de a
desena cu o singura linie nentrerupta o anumita figura (a se vedea figura 3.2).

Teorema 3.1 (Euler) Un graf G conex (|V | 3) este eulerian dac


a si numai daca gradul
fiecarui varf este par.
1
http://en.wikipedia.org/wiki/Seven Bridges of Konigsberg

58
Fig. 3.2: Doua figuri geometrice ce pot fi desenate folosind o singura linie

Demonstratie: Fie G un graf conex eulerian un ciclu eulerian C. Acesta


trece o singura data prin toate muchiile grafului. Gradul fiecarui varf u este par, deoarece
pentru fiecare muchie incidenta cu u prin intermediul careia se ajunge n varful respectiv,
exista o alta muchie prin intermediul careia se paraseste varful u.
Presupunem ca u V , dG (u) este par. Fie L un lant de lungime maxima n graful
G si fie u si v extremitatile lantului.
Presupunem ca u 6= v. Deoarece dG (v) este par, [v, w] E astfel ncat [v, w] / L.
Lantul L {[v, w]} va avea lungimea mai mare decat lantul initial L, contradictie. Prin
urmare u = v. Rezulta ca L = C este un ciclu de lungime maxima.
Presupunem ca ciclul C nu este ciclu eulerian. Atunci x, y V, [x, y] E, [x, y] / C si
x C (exista o muchie a grafului G ce nu apartine ciclului C cu proprietatea ca cel putin o
extremitate apartine ciclului).
Fie L1 = [x, . . . , u, v, . . . , x] un lant n graful G ce contine exact muchiile ciclului C.
Atunci L2 = [x, . . . , u, v, . . . , x, y] este un lant n G ce are lungimea mai mare decat cea a
lantului L, contradictie. Rezulta atunci ca ciclul C este eulerian G este un graf eulerian. 

Teorema 3.2 (Euler) Un graf G conex are un lant eulerian dac


a si numai dac
a exista exact
doua varfuri n G al caror grad sa fie impar.

Demonstratie: Se demonstreaza la fel ca la teorema 3.1.


Fie G = (V, E) un graf conex ce are exact doua varfuri de grad impar, u si v. Sa
considera un nod w / V mpreuna cu muchiile [u, w] si [v, w]. Fie G = (V , E ), V = V {w},
E = E {[u, w], [v, w]}, graful obtinut din G prin adaugarea nodului w si a muchiilor [u, w],
[v, w]. Atunci dG (u) este par, u V exista un ciclu eulerian C n graful G . Daca
eliminam muchiile [u, w] si [v, w] obtinem un lant eulerian n graful G. 

Corolarul 3.3 Un graf G conex (|V | 3) este eulerian dac


a si numai dac
a multimea muchi-
ilor sale poate fi descompusa n cicluri disjuncte.

Teorema 3.4 Pentru un graf conex cu 2k v arfuri de grad impar, k 1, exist a k lanturi n
G ale caror multimi de muchii formeaz
a o partitie a multimii E, si din care cel mult unul
are lungimea impara.

3.1.1 Algoritm pentru determinarea unui ciclu eulerian


Pornind de la Teorema 3.1 construim un ciclu eulerian. Se considera drept punct de plecare
un varf oarecare al grafului. La fiecare pas se alege pentru varful curent u, un varf adiacent

59
Fig. 3.3: Exemplu de graf eulerian

v, diferit de varful din care sa ajuns n u. Parcurgerea se continua pana n momentul n


care ajungem ntrun varf ce a fost deja vizitat si se nchide ciclul C. Daca ciclul C nu
este eulerian, atunci se elimina din graful G toate muchiile ciclului C, rezultand mai multe
componente conexe G11 , G12 , . . . , G1k .
Pentru fiecare astfel de componenta G1i , ce are cel putin doua varfuri, aplicam recursiv
algoritmul. Conform teoremei 3.1, fiecare componenta G1i este euleriana. Fie Ci1 ciclul
eulerian corespunzator componentei G1i .
Ciclul C are cel putin cate un varf comun cu fiecare ciclu Ci1 . Fie acest varf ui . Definim
un ciclu C0 astfel: pentru fiecare i, i = 1, k, adaugam la ciclul C, n varful ui, ciclul Ci1 .
Ciclul C0 contine toate muchiile grafului G, prin urmare este eulerian.
Functia IsEulerian() verifica daca un graf este eulerian conform teoremei 3.1 (a se vedea
algoritmul 23).

Algoritm 23 Algoritm de verificare daca un graf conex este eulerian


1: function IsEulerian(G = (V, E))
2: if (|V | < 3) then
3: return f alse
4: end if
5: for i 1, n do
6: s0
7: for j 1, n do
8: s s + vecini,j
9: end for
10: if (s mod 2 = 1) then
11: return false
12: end if
13: end for
14: return true
15: end function

Dupa cum se poate vedea din functia IsEulerian() numai componentele conexe ce au
ordinul mai mare sau egal cu 3 vor fi luate n considerare.

60
In algoritmul 24 este prezentata functia EulerRec() pentru determinarea unui ciclu eu-
lerian.
Procedura FindCycle(G) (a se vedea algoritmul 24) construieste un ciclu: se alege un
varf u0 si se cauta prima muchie [u0 , u1 ]. In varful u1 se cauta o muchie [u1 , u2 ] astfel ncat
varful u2 sa fie distinct de varful din care s-a ajuns n u1 (u2 6= u0 ) si sa nu mai fi fost vizitat.
Daca u2 a fost vizitat atunci functia se termina. Se continua procedeul de cautare pana cand
se ajunge la un varf ce a fost vizitat anterior si se returneaza ciclul cuprins ntre cele doua
aparitii ale aceluiasi varf.
Procedura F indComponents(G ; k, G11 , G12 , . . . , G1k ) determina componentele conexe ale
grafului G = G E(C), obtinut prin eliminarea din graful G a muchiilor ciclului C, unde
G11 , G12 , . . . , G1k sunt cele k componente conexe returnate de aceasta.
Procedura MergeCycles(C, C11 , . . . , Ck1 ; C0 ) construieste un ciclul C0 obtinut prin adaugarea
la ciclul C, succesiv, a ciclurilor C11 , . . . , Ck1 .

Algoritm 24 Algoritm de determinare a unui ciclu eulerian ntrun graf conex


1: function EulerRec(G = (V, E))
2: if (IsEulerian(G) = f alse) then
3: return
4: end if
5: call F indCycle(G; C)
6: if (E(G) \ E(C) = ) then
7: return C
8: end if
9: G G E(C)
10: call F indComponents(G ; k, G11 , G12 , . . . , G1k )
11: for i 1, k do
12: Ci1 EulerRec(G1i )
13: end for
14: call M ergeCycles(C, C11 , . . . , Ck1 ; C0 )
15: return C0
16: end function

Fig. 3.4: Grafurile partiale obtinute n urma primelor doua etape ale algoritmului 24

Exemplul 3.5 Sa aplicam algoritmul 24 pentru graful din figura 3.3. Presupunem ca primul
element (elementul de start) este varful u0 = 1. Procedura F indCycle construieste lantul

61
Fig. 3.5: Grafurile partiale obtinute n urma etapelor 3 si 4 ale algoritmului 24

L = [1, 2, 3, 4, 5, 3]. Se observa prezenta ciclului C1 = [3, 4, 5, 3]. In urma elimin arii acestuia
ramane graful din figura 3.4 a) care este un graf conex.
Continuam procesul de construire a unui ciclu eulerian, prin apelul procedurii F indCycle
pentru noul graf. Aceasta returneaza ciclul C2 = [1, 2, 3, 9, 1]. Prin eliminarea muchiilor
acestuia, E(C2 ), se obtine graful din figura 3.4 b). In continuare se construieste lantul
L = [2, 8, 7, 5, 6, 4, 10, 2], ce contine ciclul C3 = [2, 8, 7, 5, 6, 4, 10, 2]. Graful obtinut prin
eliminarea muchiilor ciclului este cel din figura 3.5 a). Apelul procedurii F indCycle conduce
la determinarea lantului L = [6, 7, 9, 8, 10, 6] si a ciclului C4 = [6, 7, 9, 8, 10, 6].
Ciclul eulerian se formeaza prin reuniunea ciclurilor intermediare determinate: C =
(((C4 C3 ) C2 ) C1 ) adica C = [1, 2, 8, 7, 5, 6, 7, 9, 8, 10, 6, 4, 10, 2, 3, 4, 5, 3, 9, 1] (C4 C3 =
[2, 8, 7, 5, 6, 7, 9, 8, 10, 6, 4, 10, 2]).

3.1.2 Algoritmul lui Rosenstiehl


Algoritmul lui Rosenstiehl [51] construieste un lant L care la final va deveni un ciclu eulerian,
folosind pentru aceasta o stiva drept structura de date auxiliara.
Se porneste de la un nod oarecare. Se nainteaza atata timp cat este posibil: pentru
nodul curent u se cauta o muchie incidenta cu el, si care sa nu mai fi fost parcursa la unul
din pasii anteriori. Daca exista o astfel de muchie (e = [u, v]), atunci se salveaza pe stiva
nodul u, iar nodul v devine nodul curent. In momentul n care nodul curent u nu mai are
muchii nevizitate, se adauga lantului L, si se extrage de pe stiva nodul anterior (din care s-a
ajuns n u) (a se vedea algoritmul 25). Vectorul vizit are drept scop sa pastreze situatia
muchiilor: (
1 , daca e E a fost parcursa
vizite =
0 , daca e E nu a fost nca parcursa.

Exemplul 3.6 Sa aplicam algoritmul 25 pentru graful din figura 3.3. S a presupunem ca
varful u de la care porneste algoritmul este v
arful 1. La sf
arsitul primului pas al enuntului
repetitiv while (liniile 6 14) avem valorile:

S = [1, 2, 3, 4, 5, 3, 9], L = [1], u = 1.

La pasul urmator, se extrage valoarea 9 de pe stiv


a si astfel u devine 9. La sf
arsitul acestuia

62
Algoritm 25 Algoritm lui Rosenstiehl de determinare a unui ciclu eulerian ntrun graf
conex
1: function Rosenstiehl(u, G)
2: for e E do
3: vizite 0
4: end for
5: Su Se insereaz
a pe stiv
a nodul u
6: while (S 6= ) do
7: Su Se extrage din stiv
a nodul curent
8: while (e = [u, v] E) (vizite = 0)) do
9: vizite 1 Se marcheaz a muchia e ca fiind utilizat
a
10: Su Se salveaz
a pe stiv
a nodul curent u
11: uv Nodul curent devine nodul v
12: end while
13: Lu Se adaug
a la lista L nodul curent
14: end while
15: return L
16: end function

avem:
S = [1, 2, 3, 4, 5, 3, 9, 7, 5, 6, 4, 10, 2, 8, 7, 6, 10, 8], L = [1, 9], u = 9.
In continuare, deoarece nu mai exista muchii nevizitate, se vor extrage elementele aflate pe
stiva S cate unul la fiecare pas, formand secventa (8, 10, 6, 7, 8, 2, 10, 4, 6, 5, 7, 9, 3, 5, 4, 3, 2, 1),
si se vor introduce n lista L:

P as 20 : S = [], L = [1, 9, 8, 10, 6, 7, 8, 2, 10, 4, 6, 5, 7, 9, 3, 5, 4, 3, 2, 1], u = 1.

Ciclul eulerian obtinut se afla n lista L.

In continuare se prezinta implementarea n limbajul C++ a algoritmului lui Rosenstiehl :

Listing 3.1: rosenstiehl.cpp


#include <v e c t o r >
#include <s t a c k >
#include <l i s t >
#include <i o s t r e a m>
#include <f s t r e a m>
#include <iomanip>
#include <c o n i o . h>

u s i n g namespace s t d ;

#define INPUT FILE g r a f 1 . t x t

typedef v e c t o r <int> L i n i e ;
typedef v e c t o r <L i n i e > M a t r i c e ;

i nt r e a d I n p u t ( M a t r i c e& ma) {
i nt n , i , j , v a l u e ;
i f s t r e a m f i n ( INPUT FILE ) ;

f i n >> n ;
ma = M a t r i c e ( n , L i n i e ( n , 0 ) ) ;

63
for ( i = 0 ; i < n ; i ++)
for ( j = 0 ; j < n ; j ++) {
f i n >> ma [ i ] [ j ] ;
}

fin . close ();


return n ;
}

void p r i n t ( l i s t <int>& l ) {
l i s t <int > : : i t e r a t o r i t ;

co ut << C i c l u e u l e r i a n : [ ;
for ( i t = l . b e g i n ( ) ; i t != l . end ( ) ; i t ++)
co ut << i t << , ;
co ut << ] \ n ;
}

void r o s e n s t i e h l ( i nt n , M a t r i c e& ma , i nt u , l i s t <int>& l ) {


s t a c k <int> s ;
i nt v ;

s . push ( u ) ;
while ( ! s . empty ( ) ) {
u = s . top ( ) ;
s . pop ( ) ;
v = 0;
while ( v < n ) {
i f (ma [ u ] [ v ] == 1 ) {
ma [ u ] [ v ] = 0 ;
ma [ v ] [ u ] = 0 ;
s . push ( u ) ;
u = v;
v = 0;
}
else
v++;
}
l . push ba ck ( u ) ;
}
}

void main ( void ) {


M a t r i c e ma ;
l i s t <int> l = l i s t <int > ( ) ;
i nt n = r e a d I n p u t (ma ) ;

r o s e n s t i e h l ( n , ma , 0 , l ) ;
print ( l ) ;
}

Datele de intrare vor fi preluate dintr-un fisier. Continutul fisierului de intrare graf1.txt
corespunzator grafului din figura 3.6 este urmatorul:
6
0 1 1 1 1 0
1 0 1 1 1 0
1 1 0 1 0 1

64
1 1 1 0 0 1
1 1 0 0 0 0
0 0 1 1 0 0

Pe prima linie avem numarul de varfuri al grafului (cardinalul multimii V ), si ncepand


cu cea de-a doua linie avem valorile matricei de adiacenta, separate prin spatii, cate un rand
al acesteia pe fiecare pe linie.

Fig. 3.6: Un alt exemplu de graf eulerian

Pentru a reduce timpul de implementare, am utilizat cateva structuri de date deja exis-
tente n limbajul C++, mai exact structuri de date implementate cu ajutorul template-urilor
din cadrul librariei Standard Template Library - STL2 3 :

stiva - stack 4. Am folosit o stiva de numere ntregi: stack<int> s;.

lista - list 5 . Pentru a pastra elementele ciclului eulerian am folosit o lista liniara,
informatia utila din cadrul nodurilor fiind constituita din valori ntregi: list<int> l
= list<int>();.

vector - vector 6 . Matricea de adiacenta am implementat-o sub forma unui vector de


vectori cu elemente numere ntregi:
typedef v e c t o r <int> L i n i e ;
typedef v e c t o r <L i n i e > M a t r i c e ;
...
M a t r i c e ma ;
ma = M a t r i c e ( n , L i n i e ( n , 0 ) ) ;

Dupa cum se poate observa din programul anterior, pentru reprezentarea interna a
grafului s-a folosit matricea de adiacent
a.

Parcurgerea elementelor listei se realizeaza cu ajutorul unui iterator - iterator 7 . Declararea


unui obiect de tip list<int>::iterator se poate face astfel: list<int>::iterator
it;.
2
http://en.wikipedia.org/wiki/Standard_Template_Library,
3
http://www.sgi.com/tech/stl/
4
http://www.sgi.com/tech/stl/stack.html
5
http://www.sgi.com/tech/stl/List.html
6
http://www.sgi.com/tech/stl/Vector.html
7
http://www.sgi.com/tech/stl/Iterators.html

65
void p r i n t ( l i s t <int>& l ) {
l i s t <int > : : i t e r a t o r i t ;

co ut << C i c l u e u l e r i a n : [ ;
for ( i t = l . b e g i n ( ) ; i t != l . end ( ) ; i t ++)
co ut << i t << , ;
co ut << ] \ n ;
}

Deoarece muchiile sunt parcurse o singura data n cadrul acestui algoritm, si pentru a
nu mai pastra o structura auxiliara care sa marcheze faptul ca o muchie a fost vizitata, vom
marca parcurgerea unei muchii prin stergerea acesteia din matricea de adiacenta astfel:
ma [ u ] [ v ] = 0 ;
ma [ v ] [ u ] = 0 ;

Ciclul eulerian obtinut pentru graful din figura 3.6 este: L = [1, 5, 2, 4, 6, 3, 4,
1, 3, 2, 1].

3.1.3 Algoritmul lui Fleury


Un alt algoritm pentru determinarea unui ciclu eulerian este algoritmul lui Fleury.

Fig. 3.7: Exemplu de graf eulerian pentru algoritmul lui Fleury

Se porneste cu un varf oarecare al grafului G (G = (V, E), |V | = n, |E| = m). Ideea consta
n a construi un lant prin alegerea la fiecare pas a unei muchii nealeas a la pasii anteriori, si
care, de preferat, sa nu fie muchie critica n graful partial obtinut prin eliminarea muchiilor
deja alese.
Sa consideram ca, la un moment dat, avem construit un lant Lk = [v0 , [v0 , v1 ], v1 , . . . ,
[vk1 , vk ], vk ]. Cautam o muchie ek+1 = [vk , vk+1 ] astfel ncat ek+1 / Lk si care nu este muchie
critica n graful partial Gk = G {e1 , e2 , . . . , ek } (graful obtinut prin eliminarea tuturor
muchiilor lantului Lk ). Daca nu exista decat muchii critice n graful partial Gk incidente cu
vk atunci alegem una dintre ele.
Daca exista o muchie ek+1 cu aceste proprietati atunci construim lantul Lk+1 = [Lk , ek+1, vk+1 ]
(Lk+1 = [v0 , [v0 , v1 ], v1 , . . . , [vk1 , vk ], vk , [vk , vk+1 ], vk+1 ]). In momentul n care E(Lk ) = E(G)
algoritmul lui Fleury se opreste (vezi algoritmul 26).

66
Algoritm 26 Algoritm lui Fleury de determinare a unui ciclu eulerian ntrun graf conex
1: function Fleury(u0 , G = (V, E))
2: k0
3: L0 [u0 ]
4: while (k m) do
5: caut ek+1 = [vk , vk+1 ] a.i. ek+1
/ Lk si ek+1 , de preferat, nu este muchie critic
a n graful
Gk = G {e1 , e2 , . . . , ek }
6: Lk+1 [Lk , ek+1 , vk+1 ]
7: k k+1
8: end while
9: return Lk
10: end function

Exemplul 3.7 Sa aplicam algoritmul 26 pentru graful din figura 3.7. La nceput L0 = [u1 ].
Apoi se intra n ciclul while (liniile 4 8): tabelul urm
ator indic
a muchia ce a fost aleasa
la fiecare pas k, precum si configuratia lantului Lk .

Pasul k Muchia aleasa Lk


1 [u1 u4 ] L1 = [u1 , [u1 u4 ], u4 ]
2 [u4 u10 ] L2 = [u1 , [u1 u4 ], u4 , [u4 u10 ], u10 ]
3 [u10 u3 ] L3 = [u1 , [u1 u4 ], u4 , [u4 u10 ], u10 , [u10 u3 ], u3 ]
4 [u3 u2 ] L4 = [. . . , [u10 u3 ], u3 , [u3 , u2 ], u2 ]
5 [u2 u6 ] L5 = [. . . , u2 , [u2 u6 ], u6 ]
6 [u6 u1 ] L6 = [. . . , u6 , [u6 u1 ], u1 ]
7 [u1 u5 ] L7 = [. . . , u1 , [u1 u5 ], u5 ]
8 [u5 u6 ] L8 = [. . . , u5 , [u5 u6 ], u6 ]
9 [u6 u7 ] L9 = [. . . , u6 , [u6 u7 ], u7 ]
10 [u7 u2 ] L10 = [. . . , u7 , [u7 u2 ], u2 ]
11 [u2 u8 ] L11 = [. . . , u2 , [u2 u8 ], u8 ]
12 [u8 u3 ] L12 = [. . . , u8 , [u8 u3 ], u3 ]
13 [u3 u9 ] L13 = [. . . , u3 , [u3 u9 ], u9 ]
14 [u9 u8 ] L14 = [. . . , u9 , [u9 u8 ], u8 ]
15 [u8 u7 ] L15 = [. . . , u8 , [u8 u7 ], u7 ]
16 [u7 u13 ] L16 = [. . . , u7 , [u7 u13 ], u13 ]
17 [u13 u5 ] L17 = [. . . , u13 , [u13 u5 ], u5 ]
18 [u5 u12 ] L18 = [. . . , u5 , [u5 u12 ], u12 ]
19 [u12 u4 ] L19 = [. . . , u12 , [u12 u4 ], u4 ]
Pasul k Muchia aleasa Lk
20 [u4 u11 ] L20 = [. . . , u4 , [u4 u11 ], u11 ]
21 [u11 u10 ] L21 = [. . . , u11 , [u11 u10 ], u10 ]
22 [u10 u9 ] L22 = [. . . , u10 , [u10 u9 ], u9 ]
23 [u9 u13 ] L23 = [. . . , u9 , [u9 u13 ], u13 ]
24 [u13 u11 ] L24 = [. . . , u13 , [u13 u11 ], u11 ]
25 [u11 u12 ] L25 = [. . . , u11 , [u11 u12 ], u12 ]
26 [u12 u1 ] L26 = [. . . , u12 , [u12 u1 ], u1 ]
La final, lantul are urmatoarea component a: L2 = [u1 , u4 , u10 , u3, u2 , u6 , u1, u5 , u6 , u7, u2 ,
u8 , u3, u9 , u8 , u7, u13 , u5 , u12 , u4 , u11, u10 , u9 , u13 , u11 , u12 , u1 ] (s-au omis din aceast a enumerare
muchiile grafului).

67
3.2 Grafuri Hamiltoniene
Definitia 3.2 Se numeste lant Hamilton sau lant hamiltonian un lant L ce trece o
singura data prin toate varfurile unui graf.

Definitia 3.3 Se numeste ciclu hamiltonian un ciclu elementar ce trece prin toate varfurile
grafului. Un graf ce admite un ciclu hamiltonian se numeste graf hamiltonian.

Problema determinarii daca un graf oarecare G este hamiltonian este o problema dificila,
atentia cercetatorilor ndreptanduse catre enuntarea unor conditii suficiente de existenta a
unui ciclu hamiltonian.

Lema 3.8 Fie G = (V, E) un graf neorientat si fie u si v dou a v


arfuri neadiacente ale grafului
(u, v V , u 6= v, [u, v] at dG (u) + dG (v) n. Atunci G este hamiltonian
/ E), astfel nc
G + [u, v] este hamiltonian.

Demonstratie: Daca G este hamiltonian, atunci cu atat mai mult, graful G+[u, v]
este hamiltonian.
Presupunem ca G + [u, v] este un graf hamiltonian. Atunci exista un ciclu hamil-
tonian n graful G + [u, v] pe care l notam cu C.

1. daca [u, v]
/ C atunci C este un ciclu hamiltonian si n graful G G este un graf
hamiltonian.

2. daca [u, v] C atunci C = [u, v, x3 , x4 , . . . , xn1 , xn , u]. Pentru fiecare muchie [u, xk ]
E putem avea urmatoarele situatii:

(a) [v, xk+1] E. Atunci ciclul C1 = [u, xk , xk1 , . . . , x3 , v, xk+1, . . . , xn , u] este hamil-
tonian n graful G graful G este hamiltonian.

(b) [v, xk+1] / E. Notam dG+[u,v] (u) = k. Atunci dG+[u,v] (v) n k 1. si De aici,
rezulta ca dG (u) + dG (v) < dG+[u,v] (u) + dG+[u,v] (v) n k + k 1 = n 1 < n.
Contradictie cu dG (u) + dG (v) n, u, v V, u 6= v, [u, v] / E.

Prin urmare lema este demonstrata. 

Teorema 3.9 (Dirac, 1952) Un graf G = (V, E) (|V | 3) este hamiltonian daca u V
avem dG (u) n2 (orice varf al grafului are gradul mai mare dec
at jum
atate din numarul de
varfuri din graf ).

Teorema 3.10 (Ore, 1961) Un graf G = (V, E) (|V | 3) este hamiltonian dac a u, v V
avem dG (u) + dG (v) n unde u 6= v, [u, v] / E (pentru oricare doua v
arfuri distincte,
neadiacente, ale grafului suma gradelor lor este mai mare dec
at num
arul de v
arfuri din graf ).

Teorema 3.11 (Chvatal, 1972) Fie un graf G = (V, E) (|V | 3) si d1 , d2 , . . . , dn o


secventa grafica. Daca este satisfacut
a relatia
n
k a.i. dk k dnk n k
2
atunci graful este hamiltonian.

68
Definitia 3.4 Pentru un graf G construim un sir de grafuri G = G1 , G2 , . . . astfel: graful
Gk+1 se obtine din graful Gk prin ad augarea muchiei [uk , vk ], unde v arfurile uk , vk V nu
sunt adiacente n Gk si dG (uk ) + dG (vk ) n. Procesul se ncheie n momentul n care nu
at dG (up ) + dG (vp ) n. Graful Gp se
mai exista doua varfuri neadiacente distincte astfel nc
numeste nchiderea lui G si se noteaz a cu cl(G).
Lema 3.12 Orice graf prezinta o singur
a nchidere.
Corolarul 3.13 Un graf G = (V, E) (|V | 3) este hamiltonian dac
a cl(G) Kn (daca
nchiderea lui G este izomorfa cu graful complet de ordinul n).
Definim (G) = min{dG (u)|u V } si (G) = max{dG (u)|u V }.
a (G) n2 .
Corolarul 3.14 Un graf G = (V, E) (|V | 3) este hamiltonian dac
Definitia 3.5 O multime de varfuri A a unui graf G se spune c a este independenta daca
oricare doua elemente distincte din A sunt independente. Num arul de independenta al lui G,
notat cu (G), reprezinta numarul maxim de v arfuri dintro multime independenta.
(G) = 1 daca si numai daca graful G este complet.
Definitia 3.6 Pentru un graf G se numeste conectivitatea lui G, si not am cu (G),
numarul minim de varfuri ale unei taieturi. O t
aietur
a este o submultime U a lui V astfel
ncat graful G U sa fie neconex.
and ordinul n 3, este hamiltonian daca (G)
Teorema 3.15 Un graf G = (V, E) av
(G).
Observatia 3.16 Pentru un graf bipartit G = (V1 , V2 , E) conditia necesar
a pentru a fi hamil-
tonian este |V1 | = |V2 |.
Problema comisvoiajorului include problema determinarii existentei unui ciclu hamilto-
nian ntrun graf.
Cautarea optimului printre toate variantele de cicluri nu este o solutie fezabila deoarece
pentru un graf G numarul de cicluri poate fi foarte mare. De exemplu pentru graful complet
Kn avem (n1)!
2
cicluri hamiltoniene distincte.
Exemplul 3.17 Graful din figura 2.20 nu este hamiltonian (nu admite un ciclu hamilto-
nian).
Graful din figura 3.6 are mai multe cicluri hamiltoniene: de exemplu ciclurile C1 = [1, 5,
2, 3, 6, 4, 1] si C2 = [1, 3, 6, 4, 2, 5, 1].

Nod 1 2 3 4 5 6
dG 4 4 4 4 2 2
Teorema lui Dirac (1952) nu se poate aplica deoarece nu este ndeplinit
a conditia orice varf
al grafului are gradul mai mare decat jum
atate din num
arul de varfuri din graf:
n 6
dG (5) = 2 < = = 3.
2 2
De asemenea, pentru teorema lui Ore (1961) nu este ndeplinit a conditia pentru oricare doua
varfuri dinstincte, neadiacente, ale grafului suma gradelor lor este mai mare dec at numarul
de varfuri din graf: fie varfurile 5 si 6, neadiacente, pentru care avem
dG (5) + dG (6) = 2 + 2 = 4 < 6.

69
Pentru graful din figura 3.8 avem:

Nod 1 2 3 4 5 6 7 8
dG 3 4 3 4 4 5 3 4

a: dG (1) = 3 < 4 = n2 ;
conditia teoremei lui Dirac (1952), nu este ndeplinit
conditia teoremei lui Ore (1961), nu este ndeplinit
a: dG (2) + dG (7) = 4 + 3 = 7 < 8;
observam ca nici conditiile teoremei lui Chvatal (1972) nu sunt ndeplinite: fie d1 , d2 , . . . , dn
o secventa grafica.
Nod 1 3 7 2 4 5 8 6
dG 3 3 3 4 4 4 4 5

Relatia urmatoare nu este satisf


acut
a:
n
k a.i. dk k dnk n k.
2
3.2.1 Problema comisvoiajorului
Un comis-voiajor trebuie sa viziteze n orase etichetate cu numerele de la 1 la n. Pentru a
simplifica problema, acesta va pleca ntotdeauna din orasul num arul 1 si se va ntoarce tot n
1, trecand prin fiecare oras o singura dat
a (cu exceptia orasului 1 care va fi vizitat de doua
ori). Cunoscand distantele ntre orase, s
a se determine o parcurgere (ciclu hamiltonian) de
cost minim.
Pentru a reprezenta drumurile directe dintre orase (existenta unei legaturi directe ntre
acestea) utilizam matricea costurilor A = (aij ), i, j = 1, n:

0
, daca xi = xj
ai,j = + /E.
, daca [xi , xj ] (3.1)


d > 0 , daca [xi , xj ] E
Vectorul solutiilor rezultat va fi X = (x1 , x2 , . . . , xn+1 ) V . . . V unde x1 = xn+1 = 1:
la pasul i, i = 2, n + 1, se viziteaza orasul xi V . Primul oras, orasul din care se pleaca,
este fixat la 1 (vezi algoritmul 27).
La pasul k (2 k n + 1) se ncearca vizitarea orasului xk + 1 daca nu a fost vizitat
la un pas anterior, si daca costul partial al lantului este mai mic decat costul celui mai bun
ciclu hamiltonian obtinut pana n acel moment n graf:
1 xk < n si vizitatxk +1 = 0 si cost + axk1 ,xk +1 costoptim (3.2)
(pentru k = n + 1 trebuie verificata si conditia xk = 1).
La nceput costul celui mai scurt ciclu hamiltonian poate fi determinat cu un algoritm
de tip Greedy sau i se poate atribui valoarea + (o valoare suficient de mare, de exemplu
n max{ai,j |i, j = 1, n, i 6= j, ai,j 6= }).
Daca conditiile anterioare sunt ndeplinite, atunci xk xk + 1. Se marcheaza orasul ales
la pasul k ca fiind vizitat (vizitatk 1), se actualizeaza costul drumului pana n momentul
curent (cost cost + axk1 ,xk ), si se trece la pasul k + 1.
In caz contrar, nu exista nici o alegere convenabila pentru orasul de pe pozitia k si va
trebui sa ne ntoarcem pentru o alta alegere pentru orasul vizitat la pasul k1. Pentru aceasta
se marcheaza orasul ales la pasul anterior ca nefiind vizitat (vizitatxk 0), si se scade din
costul drumului pana n momentul curent, costul muchiei [xk1 , xk ] (cost cost axk1 ,xk ).

70
Fig. 3.8: Exemplu de graf pentru problema comis-voiajorului

Exemplul 3.18 Matricea costurilor corespunz


atoare grafului din figura 3.8 are urmatoarele
valori:

0 14 6 5
14 0 12 16 20

6 0 12 12

12 0 21 24 10
A= 5 16 21 0

16
20 12 16 0 14 6

24 14 0 10
12 10 6 10 0
Graful respectiv are mai multe cicluri hamiltoniene:

C1 = [1, 2, 4, 5, 6, 7, 8, 3, 1].
C2 = [1, 2, 5, 4, 8, 7, 6, 3, 1].
C3 = [1, 5, 6, 2, 4, 7, 8, 3, 1].
C4 = [1, 3, 6, 7, 8, 4, 2, 5, 1].

Fig. 3.9: Rezultatul rularii programului pentru determinarea ciclului hamiltonian optim

Programul corespunzator scris n limbajul C este urmatorul:

71
Algoritm 27 Algoritm pentru problema comis-voiajorului
(
A - matricea de costuri ce contine distantele dintre orase
Input:
n - num arul de orase
1: function CanContinue(A, X, k, cost)
2: if (vizitatxk = 1) (cost + axk1 ,xk > costoptim ) ((k 6= n + 1) (xk = x1 ))) then
3: return f alse
4: else
5: return true
6: end if
7: end function

8: procedure Evaluare Solutie(X, cost)


9: X optim X
10: costoptim cost
11: end procedure

12: procedure ComisVoiajorBacktracking(A, n)


13: x1 1, cost 0, costoptim
14: k 2, xk 1
15: while (k > 1) do
16: gasit f alse
17: while ((xk + 1 n) (gasit = f alse)) do
18: xk xk + 1
19: gasit CanContinue(A, X, k, cost)
20: end while
21: if (gasit = true) then
22: if (k = n + 1) then
23: call Evaluare Solutie(X, cost + axk1 ,xk )
24: else
25: vizitatxk 1
26: cost cost + axk1 ,xk
27: k k+1
28: xk 0
29: end if
30: else
31: k k1
32: vizitatxk 0
33: if (k > 1) then
34: cost cost axk1 ,xk
35: end if
36: end if
37: end while
38: Output {Solutia optima: , xoptim
1 , . . . , xoptim
n+1 , cost
optim }

39: end procedure

Listing 3.2: hamilton.c


#include <s t d i o . h>
#include <memory . h>

#define NMAX 100 // numarul maxim de n o d u r i d i n g r a f

72
#define LIMIT 100000 // lung imea maxima a u n e i muchii e c h i v a l e n t a cu i n f i n i t

typedef i nt M a t r i c e [NMAX] [NMAX] ;


typedef i nt Vecto r [NMAX] ;

long c o s t o p t i m ; // c o s t u l c i r c u i t u l u i h a m i l t o n i a n optim
Vecto r x o ptim ; // e l e m e n t e l e c i r c u i t u l u i h a m i l t o n i a n optim

/
F u n c t i a c i t e s t e v a l o r i l e d a t e l o r de i n t r a r e .
/
i nt r e a d I n p u t ( M a t r i c e a ) {
i nt n ;
i nt i , j , max = 0 ;

s c a n f ( %d , &n ) ;

for ( i = 1 ; i <= n ; i ++)


for ( j = 1 ; j <= n ; j ++) {
s c a n f ( %d , &a [ i ] [ j ] ) ;
i f (max < a [ i ] [ j ] )
max = a [ i ] [ j ] ;
i f ( a [ i ] [ j ] < 0)
a [ i ] [ j ] = LIMIT ;
}

c o s t o p t i m = n max ;
return n ;
}

/
F u n c t i a v e r i f i c a daca a + b > c .
/
i nt mai mare ( long a , long b , long c ) {
if (a + b > c)
return 1 ;
else
return 0 ;
}

/
Functia a f i s e a z a s o l u t i a c a l c u l a t a a problemei .
/
void A f i s a r e S o l u t i e ( i nt n ) {
i nt i ;

p r i n t f ( C o s t u l c i c l u l u i optim : %l d \n , c o s t o p t i m ) ;
p r i n t f ( Ciclu hamiltonian : [ ) ;
for ( i = 1 ; i < n+1; i ++)
p r i n t f ( %d , , x o ptim [ i ] ) ;
p r i n t f ( %d ] \ n , x o ptim [ n + 1 ] ) ;
}

/
F u n c t i a de c o n t i n u a r e : a i c i s e v e r i f i c a daca e l e m e n t u l de pe p o z i t i a k
nu s e mai a f l a i n s i r u l A.
/
i nt CanContinue ( i nt n , M a t r i c e a , i nt k , long c o s t , Vecto r v i z i t a t , Vecto r x ) {
i f ( ( v i z i t a t [ x [ k ] ] == 1 ) | | ( mai mare ( c o s t , a [ x [ k 1 ] ] [ x [ k ] ] , c o s t o p t i m ) )

73
| | ( ( k != n+1) && ( x [ k ] == 1 ) ) )
return 0 ;
else
return 1 ;
}

/
F u n c t i a s a l v e a z a s o l u t i a optima ( c i r c u i t u l h a m i l t o n i a n de c o s t minim )
d e t e r m i n a t a / g a s i t a pana i n momentul c u r e n t .
/
void E v a l u a r e S o l u t i e ( i nt n , long c o s t , Vecto r x ) {
i nt i ;

cost optim = cost ;


for ( i = 1 ; i <= n+1; i ++)
x o ptim [ i ] = x [ i ] ;
A f i s a r e S o l u t i e (n ) ;
}

/
Metoda b a c k t r a c k i n g implementa ta .
/
void C o m i s V o i a j o r B a c k t r a c k i n g ( i nt n , M a t r i c e a ) {
Vecto r x ;
Vecto r v i z i t a t ;
i nt k , g a s i t ;
long c o s t ;

memset ( v i z i t a t , 0 , s i z e o f ( v i z i t a t ) ) ;
x [1] = 1;
cost = 0;
k = 2; x[ k] = 1;
while ( k > 1 ) {
gasit = 0;
while ( ( x [ k ] + 1 <= n ) && ( g a s i t == 0 ) ) {
x[k] = x [k] + 1;
g a s i t = CanContinue ( n , a , k , c o s t , v i z i t a t , x ) ;
}
i f ( g a s i t == 1 ) {
i f ( k == n+1)
EvaluareSolutie (n , cost + a [ x [ k 1]][ x [ k ] ] , x ) ;
else {
vizitat [ x[k ] ] = 1;
cost = cost + a [ x [ k 1]][ x [ k ] ] ;
k = k + 1;
x[ k] = 0;
}
}
else {
k = k 1;
vizitat [x[ k ] ] = 0;
i f (k > 1)
cost = cost a [ x [ k 1]][ x [ k ] ] ;
}
}
}

void main ( void ) {


Matrice a ;

74
i nt n = r e a d I n p u t ( a ) ;

ComisVoiajorBacktracking (n , a ) ;
}

Continutul fisierului graf1.txt este:


8
0 14 6 -1 5 -1 -1 -1
14 0 -1 12 16 20 -1 -1
6 -1 0 -1 -1 12 -1 12
-1 12 -1 0 21 -1 24 10
5 16 -1 21 0 16 -1 -1
-1 20 12 -1 16 0 14 6
-1 -1 -1 24 -1 14 0 10
-1 -1 12 10 -1 6 10 0

Corespunzator valorii am utilizat n fisierul de test valoarea negativa 1. In timpul


citirii datelor aceasta a fost nlocuita cu o constanta suficient de mare:
...
s c a n f ( %d , &a [ i ] [ j ] ) ;
i f (max < a [ i ] [ j ] )
max = a [ i ] [ j ] ;
i f ( a [ i ] [ j ] < 0)
a [ i ] [ j ] = LIMIT ;

In timpul citirii se determina valoarea maxima, max{ai,j |i, j = 1, n, i 6= j, ai,j 6= }, pentru


a se initializa mai apoi costul optim cu valoarea n max.
Functia n care se verifica conditiile de continuare este urmatoarea:
i nt CanContinue ( i nt n , M a t r i c e a , i nt k , long c o s t , Vecto r v i z i t a t , Vecto r x ) {
i f ( ( v i z i t a t [ x [ k ] ] == 1 ) | | ( mai mare ( c o s t , a [ x [ k 1 ] ] [ x [ k ] ] , c o s t o p t i m ) )
| | ( ( k != n+1) && ( x [ k ] == 1 ) ) )
return 0 ;
else
return 1 ;
}

Folosim o functie (mai mare()) cu scopul de a realiza verificarea inegalitatii cost +


axk1 ,xk +1 > costoptim :
i nt mai mare ( long a , long b , long c ) {
if (a + b > c)
return 1 ;
else
return 0 ;
}

si a preveni eventualele depasiri (overflow )8 n timpul calculelor.


Nu am afisat numai solutia optima, ci am ales sa afisam toate ciclurile hamiltoniene care
mbunatateau solutia anterioara:
void E v a l u a r e S o l u t i e ( i nt n , long c o s t , Vecto r x ) {
i nt i ;

cost optim = cost ;


8
http://en.wikipedia.org/wiki/Arithmetic_overflow

75
for ( i = 1 ; i <= n+1; i ++)
x o ptim [ i ] = x [ i ] ;
A f i s a r e S o l u t i e (n ) ;
}

Partea principala a programului, motorul, este constituita din functia:


void C o m i s V o i a j o r B a c k t r a c k i n g ( i nt n , M a t r i c e a ) {
Vecto r x ;
Vecto r v i z i t a t ;
i nt k , g a s i t ;
long c o s t ;

memset ( v i z i t a t , 0 , s i z e o f ( v i z i t a t ) ) ;
x [1] = 1;
cost = 0;
k = 2; x[ k] = 1;
while ( k > 1 ) {
gasit = 0;
while ( ( x [ k ] + 1 <= n ) && ( g a s i t == 0 ) ) {
x[k] = x [k] + 1;
g a s i t = CanContinue ( n , a , k , c o s t , v i z i t a t , x ) ;
}
i f ( g a s i t == 1 ) {
i f ( k == n+1)
EvaluareSolutie (n , cost + a [ x [ k 1]][ x [ k ] ] , x ) ;
else {
vizitat [ x[k ] ] = 1;
cost = cost + a [ x [ k 1]][ x [ k ] ] ;
k = k + 1;
x[ k] = 0;
}
}
else {
k = k 1;
vizitat [x[ k ] ] = 0;
i f (k > 1)
cost = cost a [ x [ k 1]][ x [ k ] ] ;
}
}
}

Pentru n = 8, multimea Ai va fi alcatuita din elementele {1, 2, . . . , 8}. La nceput, x1 va


primi valoarea 1, iar pentru k = 2, x2 va primi tot valoarea 1:
x1 x2 x3 x4 x5 x6 x7 x8 x9
1 1
Pentru k = 2, se verifica conditiile de continuare pentru x2 = x2 + 1 = 1 + 1 = 2, si
deoarece acestea sunt respectate, se trece la pasul urmator (orasul urmator) (la initializare
avem k = k + 1 = 3, x3 = 0):
x1 x2 x3 x4 x5 x6 x7 x8 x9
1 2 0
Se verifica mai multe valori (1, 2 si 3) pentru x3 , pentru care conditiile de continuare nu
sunt ndeplinite. Pentru x3 = 4 conditiile de continuare sunt ndeplinite, si se trece la pasul
urmator, k = k + 1 = 4:
x1 x2 x3 x4 x5 x6 x7 x8 x9
1 2 4 0

76
Pentru k = 4, prima valoare ce verifica conditiile de continuare este 5:
x1 x2 x3 x4 x5 x6 x7 x8 x9
1 2 4 5 0
In mod asemanator se aleg valorile pentru pasii k = 5, 6, 7, 8:
x1 x2 x3 x4 x5 x6 x7 x8 x9
1 2 4 5 6 3 8 7 0
La pasul k = 9, nici una dintre valorile 1, 2, . . . , 8 nu verifica conditiile de continuare (fiind
la ultimul oras, acesta ar trebui sa fie 1, adica orasul de unde s-a pornit). Astfel, se trece la
pasul anterior:
k = k - 1;
vizitat[x[k]] = 0;
if (k > 1)
cost = cost - a[x[k-1]][x[k]];

adica la pasul k = k 1 = 9 1 = 8.
Valoarea 8, urmatoarea valoare din domeniul de valori neverificata si singura ce mai
ramasese de atribuit pentru x8 , nu verifica conditiile de continuare, si deoarece nu mai sunt
valori de testat la pasul k = 8, se trece la nivelul anterior: k = k 1 = 8 1 = 7.
La pasul k = 7, deoarece nu au mai ramas valori netestate din multimea A7 = {1, 2, . . . , 8},
se trece la pasul k = 6:
x1 x2 x3 x4 x5 x6 x7 x8 x9
1 2 4 5 6 3
Aici continuam cu verificarea valorilor 4, 5, 6 si 7. Pentru x6 = 7 sunt verificate conditiile
de continuare si se poate trece la pasul urmator, k = 7:
x1 x2 x3 x4 x5 x6 x7 x8 x9
1 2 4 5 6 7 0
Doar pentru valoarea 8 sunt ndeplinite conditiile de continuare:
x1 x2 x3 x4 x5 x6 x7 x8 x9
1 2 4 5 6 7 8 0
La pasul k = 8, xk = 3 ndeplineste contiile de continuare si se poate trece la nivelul
urmator, k = 9:
x1 x2 x3 x4 x5 x6 x7 x8 x9
1 2 4 5 6 7 8 3 0
Aici solutia optima gasita este urmatoarea:
x1 x2 x3 x4 x5 x6 x7 x8 x9
1 2 4 5 6 7 8 3 1
avand un cost al ciclului hamiltonian de 105.
Se pastreaza solutia optima identificata pana n acest moment, se afiseaza, si se continua
efectuarea calculelor metodei pana la epuizarea spatiului solutiilor posibile.

77
Fig. 3.10: Pasii efectuati de algoritm pentru determinarea ciclului hamiltonian asociat grafului din figura 3.8

78
Fig. 3.11: Pasii efectuati de algoritm pentru determinarea ciclului hamiltonian asociat grafului din figura 3.8
(continuare)

79
Capitolul 4

Arbori. Arbori binari

Definitia 4.1 Fiind data o multime M de elemente denumite noduri, vom numi arbore o
submultime finita de noduri astfel nc
at:

1. exista un nod cu destinatie speciala, numit r


ad
acina arborelui;
2. celelalte noduri sunt repartizate n n multimi distincte, disjuncte dou
a c
ate dou
a, A1 , A2 , . . . , An ,
fiecare multime Ai constituind la randul ei un arbore.

Aceasta definitie este una recursiva, constructia unui arbore depinzand de alti arbori.
Descendentii directi ai radacinii arborelui sunt radacinile subarborilor A1 , A2 , . . . , An , n 1.
Orice nod al unui arbore cu radacina constituie radacina unui subarbore compus din nodul
respectiv si toti descendentii sai.
Se observa ca un arbore impune o structura de organizare ierarhica asupra elementelor
unei multimi.

Definitia 4.2 Se numeste arbore liber (free tree) un graf G = (V, E) neorientat, conex
si aciclic.

Teorema 4.1 (Proprietatile arborilor liberi) Fie G = (V, E) un graf neorientat. Urmatoarele
afirmatii sunt echivalente:

1. G este un arbore liber.


2. Oricare doua varfuri din G sunt conectate printr-un drum elementar unic.
3. G este conex, dar, daca eliminam o muchie oarecare din E, graful obtinut nu mai este conex.
4. G este conex, si |E| = |V | 1.
5. G este aciclic, si |E| = |V | 1.
6. G este aciclic, dar daca adaugam o muchie oarecare n E, graful obtinut contine un ciclu.

Daca se alege un varf sau nod drept r adacina arborelui, atunci un arbore liber devine

arbore cu radacina. In general vom folosi termenul de arbore n loc de termenul corect arbore
cu radacina.
Un varf terminal (frunza) este un varf fara descendenti. Varfurile care nu sunt terminale
sunt neterminale (sau noduri interioare). De obicei, se considera ca exista o ordonare a
descendentilor aceluiasi parinte.

80
1

2 3

4 5 6 7

8 9 10 11 12 13 14 15

Fig. 4.1: Exemplu de arbore binar plin cu 24 1 varfuri

Definitia 4.3 Adancimea unui varf este dat a de lungimea drumului de la r adacina la acel

varf. Inaltimea unui varf se determina ca fiind lungimea celui mai lung drum dintre acel varf
si un varf terminal. Inaltimea radacinii determin a n
altimea arborelui. Nivelul unui varf se
calculeaza ca diferenta dintre naltimea arborelui si adancimea acestui v arf.

Toate varfurile unui arbore ce au aceeasi adancime se spune ca sunt pe acelasi nivel. Un
arbore oarecare cu radacina este n-ar daca fiecare varf are pana la n descendenti directi.
O multime de arbori disjuncti formeaza o padure.
Din punct de vedere grafic, un arbore se poate reprezenta descriind nodurile cu ajutorul
unor cercuri sau patrate n care se afla informatia aferenta, iar relatia de descendenta prin
legaturile ce unesc nodurile cu fii lor.

4.1 Arbori binari


Definitia 4.4 Un arbore binar este un arbore oarecare cu r ad
acin a, fiecare v
arf al sau
avand cel mult doi descendenti, indicandu-se cine este descendentul st
ang al rad
acinii si cine
este cel drept.

Trebuie mentionat ca un arbore binar nu este un caz particular de arbore oarecare,


deoarece n cazul unui arbore oarecare este suficient sa spunem ca ca un varf are un singur
decendent spre deosebire de cazul arborelui binar cand trebuie precizat daca descendentul
este drept sau stang. Arborele binar ce nu contine nici un nod se numeste arbore vid sau
arbore nul.

Definitia 4.5 Se numeste arbore binar plin un arbore binar cu 2k 1 v


arfuri asezate pe
i1
k nivele astfel ncat pe fiecare nivel i avem 2 v
arfuri.

Se observa ca fiecare varf al arborelui are doi descendenti, descendentul stang si descen-
dentul drept, cu exceptia nodurilor terminale.
In figura 4.1 este prezentat un arbore binar plin n care varfurile sunt asezate pe 4 niveluri
(k = 4). De regula varfurile unui arbore binar plin se numeroteaza n ordinea nivelurilor, iar
n cadrul unui nivel, de la stanga la dreapta.

Definitia 4.6 Se numeste arbore binar complet cu n v arfuri un arbore binar plin avand
k nivele, unde 2k1 n < 2k , din care se elimin arfurile numerotate n+ 1, n+ 2, . . . , 2k 1.
a v

81
1

2 3

4 5 6 7

8 9 10 11 12

Fig. 4.2: Exemplu de arbore binar complet cu 12 varfuri

In figura 4.2 este ilustrat un exemplu de arbore binar complet.


Un arbore binar poate fi transformat ntrun arbore binar plin n modul urmator: com-
pletam arborele cu noi varfuri astfel ncat fiecare varf sa aiba doi descendenti sau nici unul,
renumerotam varfurile iar varfurilor nou introduse li se asociaza o valoare ce va indica faptul
ca ele sunt fictive.

4.1.1 Moduri de reprezentare


Cele mai cunoscute modalitati de reprezentare a arborilor binari sunt:

1. expresii cu paranteze;

2. forma standard ;

3. reprezentarea tip tata.

2 8

3 5 9

4 6 7

Fig. 4.3: Exemplu de arbore binar

1. expresii cu paranteze expresia ncepe cu radacina si dupa fiecare varf k urmeaza expre-
siile subarborilor ce au ca radacini descendentii varfului respectiv, separate prin virgula
si incluse ntre paranteze. Daca un descendent al unui varf nu exista atunci expresia
respectiva este nlocuita cu 0 sau NULL. Un arbore cu un singur varf (radacina),
etichetat cu a1 , se reprezinta astfel: a1 (0, 0). Un arbore format din radacina a1 si doi
descendenti, a2 si a3 , se reprezinta astfel: a1 (a2 (0, 0), a3(0, 0)).
Pentru arborele din figura 4.3 avem:

1(2(3(0, 4(0, 0)), 5(6(0, 0), 7(0, 0))), 8(0, 9(0, 0)))

82
2. forma standard se indica radacina arborelui, iar pentru fiecare varf k se precizeaza
descendentul sau stang si/sau drept.
(
i , daca nodul i este descendentul stang al nodului k
Stangk =
0 , daca nu exista descendentul stang
(
i , daca nodul i este descendentul drept al nodului k
Dreptk =
0 , daca nu exista descendentul drept.
Pentru arborele din figura 4.3 avem urmatoarea reprezentare (Rad = 1):
Nod 1 2 3 4 5 6 7 8 9
Stang 2 3 0 0 6 0 0 0 0
Drept 8 5 4 0 7 0 0 9 0

Fig. 4.4: Exemplu de arbore binar cu 8 noduri

Dupa modelul listei liniare dublu nlatuita, putem folosi o structura de date asemanatoare
pentru reprezentarea unui arbore binar. Un nod al arborelui are urmatoarea configuratie:

typedef struct nod {


TipOarecare data;
struct nod* stang;
struct nod* drept;
}Nod;

Nod rad; rad este o variabila de tip pointer la Nod (variabila pastreaza adresa
unei zone de memorie ce contine date numerice de tip Nod). rad desemneaza adresa
radacinii arborelui.
In figura 4.5 avem reprezentarea arborelui din figura 4.4.

3. reprezentarea tip tata fiecarui varf k i se indica tatal nodului respectiv.


(
i , daca nodul i este parintele nodului k
tatak =
0 , nu exista

Nod 1 2 3 4 5 6 7 8 9
Tata 0 1 2 3 2 5 5 1 8

Observatia 4.2 Urmatoarea numerotare este utilizat a pentru anumite tipuri de arbori
binari, si are drept caracteristic
a faptul c
a structura arborelui se poate deduce printr-o
numerotare adecvata a varfurilor:

83
Fig. 4.5: Exemplu de arbore binar cu 8 noduri

(
2i , i2
T atai =
nu exista , i=1
( (
2i , 2in 2i+1 , 2i+1 n
Stangi = Drepti =
nu exista , 2i>n a , 2i+1>n
nu exist

4.1.2 Metode de parcurgere


Exista mai multe moduri de parcurgere a unui arbore binar. Indiferent de metoda de parcurg-
ere aleasa se parcurge mai ntai subarborele stang si apoi subarborele drept. Dupa momentul
n care un nod k este vizitat fata de subarborii sai stang si drept, avem:

parcurgere n preordine (radacin a, subarbore st


ang, subarbore drept).
In urma parcurgerii n preordine a arborelui din figura 4.3 rezulta urmatoarea ordine
pentru noduri: 1, 2, 3, 4, 5, 6, 7, 8, 9.
In figura 4.6 este prezentata parcurgerea n preordine a unui arbore binar, descrisa n
mai multi pasi, conform descrierii recursive a acestei metode.
Vom da exemplu de o procedura nerecursiva de vizitare n preordine (vezi algoritmul
28). Se pleaca de la radacina si se merge spre stanga atata timp cat este posibil (liniile
58). Daca nu se mai poate merge spre stanga se ncearca continuarea algorimului
din descendentul drept, daca este posibil. Daca nu se poate face un pas spre dreapta
(descendentul drept nu exista linia 10), se va urca n arbore cate un nivel (liniile
1222), pana cand se ajunge n situatia de a se veni din descendentul stang (linia 19)
si se reia algoritmul trecand n descendentul drept, daca exista (linia 25). In momentul
n care sa ajuns n nodul radacina venind din dreapta, algoritmul se ncheie. Datorita
faptului ca atunci cand se urca n arbore trebuie sa stim din ce descendent venim, vom
utiliza si vectorul tata.
Aceasta procedura poate fi utilizata si pentru celelalte moduri de parcurgere, modificarile
necesare referinduse la momentul cand trebuie vizitat nodul curent (prin apelul pro-
cedurii V izit - instructiunea call V izit(k)).

84
Fig. 4.6: Parcurgerea n preordine a unui arbore binar a) arborele initial; b) arborele vizitat n preordine,
primul nivel; c) arborele vizitat n preordine, al doilea nivel; d) arborele vizitat n preordine, al treilea nivel.

Implementarea n limbajul C a algoritmului de parcurgere n preordine este:

#include <stdio.h>

#define MAX 100

/** stang[k] - descendentul stang al nodului k; 0 daca nu exista */


char stang[MAX];
/** drept[k] - descendentul drept al nodului k */
char drept[MAX];
/** tata[k] - parintele nodului k */
char tata[MAX];
/** numarul de noduri din arbore */
int n;
/** nodul radacina */
int rad;

void vizit(int nod) {


printf("%2d ", nod);
}

void readInput(void) {
int k;

printf("n = "); scanf("%d", &n);


printf("rad = "); scanf("%d", &rad);

for (k = 1; k <= n; k++) {


printf("stang[%d] = ", k); scanf("%d", &stang[k]);
}

85
Algoritm 28 Algoritm de parcurgere n preordine
1: procedure Preordine(Rad, Stang, Drept, T ata)
2: k rad
3: q0
4: while (q = 0) do
5: while (stangk 6= 0) do
6: call V izit(k) prelucrarea informatiei din nodul vizitat
7: k stangk
8: end while
9: call V izit(k)
10: while (q = 0) (dreptk = 0) do
11: f ound 0
12: while (q = 0) (f ound = 0) do
13: jk
14: k tatak
15: if (k = 0) then
16: q1
17: else
18: if (j = stangk ) then
19: f ound 1
20: end if
21: end if
22: end while
23: end while
24: if (q = 0) then
25: k dreptk
26: end if
27: end while
28: return
29: end procedure

for (k = 1; k <= n; k++) {


printf("drept[%d] = ", k); scanf("%d", &drept[k]);
}

for (k = 1; k <= n; k++) {


printf("tata[%d] = ", k); scanf("%d", &tata[k]);
}
}

void preord(int rad) {


int i, j;
int q, found;

i = rad; q = 0;
while (q == 0) {
while (stang[i] != 0) {
vizit(i);
i = stang[i];

86
}
vizit(i);
while ((q == 0) && (drept[i] == 0)) {
found = 0;
while ((q == 0) && (found == 0)) {
j = i;
i = tata[i];
if (i == 0)
q = 1;
else
if (j == stang[i])
found = 1;
}
}
if (q == 0)
i = drept[i];
}
}

void main(void){
readInput();
preord(rad);
}

parcurgere n inordine (arbore st


ang, r
ad
acin
a, arbore drept): 3, 4, 2, 6, 5, 7, 1, 8, 9.
Metoda de parcurgere n inordine este ilustrata de algoritmul 29 [93], [123]. Vom utiliza
o stiva S unde se vor introduce nodurile din care se coboara spre stanga (liniile 69).
Atunci cand nu se mai poate merge spre stanga se ncearca continuarea algoritmului din
descendentul drept al nodului curent. Daca acesta nu exista, se reface drumul napoi
spre radacina, compus numai din descendentii stangi (liniile 1118). Parcurgerea se
termina n momentul n care stiva este vida (vezi algoritmul 29).
Implementarea n limbajul C a algoritmului 29 este:

#include <stdio.h>

#define MAX 100

/** stang[k] - descendentul stang al nodului k; 0 daca nu exista */


char stang[MAX];
/** drept[k] - descendentul drept al nodului k */
char drept[MAX];
/** Numarul de noduri din arbore */
int n;
/** Nodul radacina */
int rad;

void vizit(int nod) {


printf("%2d ", nod);
}

void readInput(void) {

87
Algoritm 29 Algoritm de parcurgere n inordine
1: procedure Inordine(Rad, Stang, Drept)
2: k rad
3: q0
4: S initializare stiva vid
a
5: while (q = 0) do
6: while (stangk 6= 0) do
7: Sk S.push(k)
8: k stangk
9: end while
10: call V izit(k)
11: while (q = 0) (dreptk = 0) do
12: if (S = ) then verificare dac
a stiva este vid
a
13: q1
14: else
15: Sk S.pop(k)
16: call V izit(k)
17: end if
18: end while
19: if (q = 0) then
20: k dreptk
21: end if
22: end while
23: return
24: end procedure

int k;

printf("n = "); scanf("%d", &n);


printf("rad = "); scanf("%d", &rad);

for (k = 1; k <= n; k++) {


printf("stang[%d] = ", k); scanf("%d", &stang[k]);
}

for (k = 1; k <= n; k++) {


printf("drept[%d] = ", k); scanf("%d", &drept[k]);
}
}

void inord(int rad) {


int i, cap;
int q;
int stack[MAX];

i = rad; q = 0;
cap = -1;

while (q == 0) {
while (stang[i] > 0) {

88
cap++;
stack[cap] = i;
i = stang[i];
}
vizit(i);
while ((q == 0) && (drept[i] == 0)) {
if (cap < 0)
q = 1;
else {
i = stack[cap];
cap--;
vizit(i);
}
}
if (q == 0)
i = drept[i];
}
}

void main(void) {
readInput();
inord(rad);
}

parcurgere n postordine (subarbore st


ang, subarbore drept, r
ad
acin
a): 4, 3, 6, 7, 5, 2, 9, 8, 1
(vezi algoritmul 30).

Algoritm 30 Algoritm de parcurgere n postordine


1: procedure Postordine(K, Stang, Drept)
2: if (k 6= 0) then
3: call P ostordine(stangk , stang, drept)
4: call P ostordine(dreptk , stang, drept)
5: call V izit(k)
6: end if
7: return
8: end procedure

Modalitatea de vizitare recursiva a arborilor binari este exemplificata la arborii binari de


sortare.
Daca presupunem realizarea unei actiuni de vizitare pe exteriorul frontierei arborelui,
deplasarea efectuandu-se n sens trigonometric si avand drept punct de plecare radacina
acestuia, vom ajunge sa vizitam cel putin o data toate nodurile arborelui (vezi figura 4.7).
T
inand cont de momentul vizitarii se poate obtine oricare dintre metodele de parcurgere
(preordine, inordine, postordine): pentru preordine vom marca un nod n momentul n care
l vizitam pentru prima data, n cazul metodei de vizitare n inordine vom marca un nod
n momentul n care l vizitam pentru prima data daca este frunza, respectiv a doua oara
daca este un nod interior, iar la postordine vom marca un nod n momentul n care l vizitam
pentru ultima oara.

89
1

2 8

3 5 9

6 7

Fig. 4.7: Vizitarea unui arbore pe frontiera

Fig. 4.8: Arbore asociat expresiei aritmetice 2 ((a + b) (c + b))

Arborele asociat unei expresii aritmetice


Oricarei expresii aritmetice i se poate asocia un arbore binar astfel:

fiecarui operator i corespunde un nod neterminal avand drept subarbori stang si drept
cei doi operanzi corespunzatori.

fiecare nod terminal este etichetat cu o variabila sau o constanta.

Arborele din figura 4.8 corespunde expresiei matematice: 2 ((a + b) (c + b)). In urma
vizitarii n postordine a acestui arbore se obtine forma polonez
a postfixat
a asociata expresiei
anterioare: 2ab + cb + . Pentru a obtine valoarea expresiei respective, arborele poate fi
parcurs n postordine.

4.2 Arbori binari de c


autare
Definitia 4.7 [93] Se numeste arbore binar de c
autare un arbore binar ce verifica urmatoarea
proprietate:

90
pentru orice varf i apartinand arborelui, avem
(
Infi Infj , pentru toate varfurile j ce apartin descendentului st
ang al v
arfului i
Infi Infj , pentru toate varfurile j ce apartin descendentului drept al v
arfului i
unde Infi este informatia asociata varfului i. Aceast
a informatie este de un tip de date peste
care avem o relatie de ordine (de obicei un tip numeric sau tipul sir de caractere).
Un arbore binar de cautare mai este cunoscut si sub numele de arbore binar de sortare
deoarece parcurgerea n inordine a unui arbore binar de cautare ne conduce la obtinerea
elementelor vectorului Inf n ordine crescatoare. In principal aceasta structura de date este
utilizata pentru regasirea eficienta a unor informatii.
Operatiile de creare a arborelui, stergere a unui nod, modificare a informatiei asociate
unui nod sau inserare a unui nod se pot realiza ntr-un mod optim astfel ncat sa nu se
distruga proprietatea de arbore de cautare.
Daca dorim ca arborele sa nu contina chei duplicate, vom modifica definitia de mai sus
astfel:
pentru orice varf i apartinand arborelui, avem
(
Infi < Infj , pentru toate varfurile j ce apartin descendentului stang al varfului i
Infi > Infj , pentru toate varfurile j ce apartin descendentului drept al varfului i.

In continuare vom utiliza aceasta definitie pentru descrierea algoritmilor ce urmeaza.

Crearea unui arbore de c


autare
Crearea (construirea) unui arbore de cautare se face aplicand n mod repetat operatia de
inserare.

C
autarea unei informatii ntr-un arbore de c
autare
Informatia asociata unui nod mai poarta numele de cheie.
Sa presupunem ca dorim sa cautam o cheie x ntr-un arbore de cautare. Vom compara
mai ntai cheia x cu informatia asociata radacinii arborelui (vezi algoritmul 31):
1. daca cheia x este mai mica decat cheia radacinii, atunci se va continua cautarea n
subarborele stang;
2. daca cheia x este mai mare decat cheia radacinii, atunci se va continua cautarea n
subarborele drept;
3. daca cheia x este egala cu cheia radacinii, atunci cautarea se ncheie cu succes.

Inserarea unui nod ntr-un arbore de c


autare
Aceasta operatie prezinta urmatoarele particularitati (vezi algoritmul 32):
se verifica prin intermediul operatiei de cautare daca cheia x a nodului care se doreste
sa fie inserat mai exista n arbore;
n cazul n care cautarea se ncheie cu succes, inserarea nu va mai avea loc;
n cazul n care operatia de cautare se termina fara succes, atunci se va crea un nod
nou n locul subarborelui vid unde s-a terminat cautarea.
In figura 4.9 este ilustrata inserarea unui nod ntrun arbore binar.

91
Algoritm 31 Algoritm de cautare a unei valori ntr-un arbore binar de cautare
1: function SearchNode(p, Key)
2: if (p 6= N U LL) then
3: if (p.inf o > key) then
4: return SearchN ode(p.lef t, key)
5: else
6: if (p.inf o < key) then
7: return SearchN ode(p.right, key)
8: else
9: return p
10: end if
11: end if
12: else
13: return N U LL
14: end if
15: end function

Algoritm 32 Algoritm de inserare ntr-un arbore binar de cautare


1: function InsertNode(p, Key)
2: if (p = N U LL) then
3: call CreateN ode(p, key)
4: else
5: if (p.inf o > key) then
6: p.lef t InsertN ode(p.lef t, key)
7: else
8: if (p.inf o < key) then
9: p.right InsertN ode(p.right, key)
10: end if
11: end if
12: end if
13: return p
14: end function

S
tergerea unui nod dintr-un arbore de c
autare
Mai ntai se va cauta nodul ce se doreste sa fie eliminat.
Sa presupunem ca am gasit nodul ce urmeaza a fi sters. Avem urmatoarele situatii (vezi
algoritmul 34):

1. nodul ce urmeaza sa fie sters este un nod terminal - se face stergerea normal avand
grija sa se nlocuiasca legatura din nodul parinte catre el cu null;

2. nodul ce urmeaza sa fie sters are un singur descendent - nodul respectiv se sterge iar
parintele va contine acum noua legatura catre descendentul fostului fiu;

3. nodul ce urmeaza a fi sters (notat A) are doi descendenti:

se determina cel mai din stanga nod (notat B) din subarborele drept al nodului
ce trebuie sters (vezi algoritmul 33); acesta va fi sters n mod fizic, dar la un pas
ulterior;

92
Fig. 4.9: Arbore binar de cautare. Inserarea unui nod.

toate informatiile legate de datele continute n nodul B vor fi copiate n nodul A;

subarborele drept al nodului B se va lega fie ca descendent drept al nodului A


(daca nodul B este descendent direct al nodului A), fie ca descendent stang al
tatalui nodului B;

se va sterge fizic nodul B.

Exemplul 4.3 Fie un arbore binar, eticheta fiec arui nod fiind un sir de caractere (un cuvant).
Se cere sa se realizeze un program care s a creeze un arbore binar de sortare, s a-l parcurga n
inordine si sa permita inserarea si stergerea unui nod specificat.

#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <conio.h>

typedef struct nod {

93
Fig. 4.10: Arbore binar de cautare. Stergerea unui nod. a) Nod fara descendenti b) Nod cu un singur
descendent c) Nod cu doi descendenti.

94
Algoritm 33 Algoritm pentru determinarea celui mai din stanga nod
1: function LeftmostNode(P arent, Curent)
2: while (curent.lef t 6= N U LL) do
3: parent curent
4: curent curent.lef t
5: end while
6: if (parent.right = curent) then
7: parent.right curent.right
8: else
9: parent.lef t curent.right
10: end if
11: return curent
12: end function

char *cuvint;
struct nod *left,*right;
} NOD;

NOD *rad;

NOD* Search(NOD *p, char *sir) {


int ind;

if (p != NULL) {
ind = strcmp(p->cuvint, sir);
if (ind < 0)
return Search(p->right, sir);
else
if (ind > 0)
return Search(p->left, sir);
else
return p;
}
else
return NULL;
}

NOD* CreareNod(char *sir) {


NOD *p;

p = (NOD*)malloc(sizeof(NOD));
p->left = p->right = NULL;
p->cuvint = (char*)malloc(sizeof(char) * (strlen(sir) + 1));
strcpy(p->cuvint, sir);

return p;
}

NOD* Insert(NOD *p, char *sir) {


int ind;

95
Algoritm 34 Algoritm de stergere a unui nod ntr-un arbore binar de cautare
1: function DeleteNode(p, Key)
2: if (p 6= N U LL) then
3: if (p.inf o > key) then
4: p.lef t DeleteN ode(p.lef t, key)
5: else
6: if (p.inf o < key) then
7: p.right DeleteN ode(p.right, key)
8: else
9: if (p.lef t = N U LL) (p.right = N U LL) then
10: if (p.lef t 6= N U LL) then
11: tmp p.lef t
12: else
13: tmp p.right
14: end if
15: call DisposeN ode(p)
16: p tmp
17: else
18: tmp p.right
19: tmp Lef tmostN ode(p, tmp)
20: tmp.lef t p.lef t
21: tmp.right p.right
22: call DisposeN ode(p)
23: p tmp
24: end if
25: end if
26: end if
27: end if
28: return p
29: end function

if (p == NULL)
p = CreareNod(sir);
else {
ind = strcmp(p->cuvint, sir);
if (ind < 0)
p->right = Insert(p->right, sir);
else
if (ind > 0)
p->left = Insert(p->left, sir);
}

return p;
}

void VisitInord(NOD *p) {


if (p != NULL) {
VisitInord(p->left);
printf("[%s] ", p->cuvint);

96
VisitInord(p->right);
}
}

NOD* LeftmostNod(NOD* parent, NOD* curent) {


while (curent->left != NULL) {
parent = curent;
curent = curent->left;
}

if (parent->right == curent)
parent->right = curent->right;
else
parent ->left = curent->right;

return curent;
}

NOD* ElibNod(NOD *p) {


free(p->cuvint);
free(p);
return NULL;
}

NOD* DeleteNod(NOD* p, char *sir) {


NOD *tmp;
int ind;

if (p != NULL){
ind = strcmp(p->cuvint, sir);
if (ind < 0)
p->right = DeleteNod(p->right, sir);
else
if (ind > 0)
p->left = DeleteNod(p->left, sir);
else
if (p->left == NULL || p->right == NULL) {
if (p->left != NULL)
tmp = p->left;
else
tmp = p->right;
ElibNod(p);
p = tmp;
}
else{
tmp = p->right;
tmp = LeftmostNod(p, tmp);
tmp->left = p->left;
tmp->right = p->right;
ElibNod(p);
p = tmp;
}

97
}

return p;
}

void main(void) {
char cuvint[100];
char ch;
NOD *p;

rad = NULL;
while (1) {
printf("***********************************\n");
printf("1. Inserare \n");
printf("2. Cautare\n");
printf("3. Stergere\n");
printf("4. Afisare arbore\n");
printf("0. Exit \n");

ch = getch();
if (ch != 0 && ch != 4) {
printf("Cuvint: "); scanf("%s", cuvint);
}

switch (ch) {
case 1: rad = Insert(rad, cuvint); break;
case 2: p = Search(rad, cuvint);
if (!p)
printf("Cuvintul %s nu a fost gasit! \n", cuvint);
else
printf("Cuvintul %s exista in arbore! \n", cuvint);
break;
case 3: rad = DeleteNod(rad, cuvint); break;
case 4: VisitInord(rad); break;
case 0: exit(1);
}
}
}

4.3 Exercitii
1. (a) Sa se realizeze o subrutina ce determina cea mai mare valoare pastrata ntrun
arbore binar de cautare;
(b) Aceeasi cerinta pentru cea mai mica valoare.
2. Sa se realizeze o subrutina ce determina toate nodurile unui arbore binar de cautare cu
proprietatea ca informatia k asociata unui nod verifica relatia a k b, unde a si b
sunt date.
3. (a) Sa se realizeze un algoritm care determina numarul arborilor binari distincti cu
n noduri, unde n este un numar natural dat.

98
Fig. 4.11: Exemplu de arbore binar complet cu 7 varfuri

(b) Aceeasi problema pentru arbori binari de cautare.

4. Sa consideram o secventa de intrare compusa din termeni definiti recursiv astfel:

(a) X este un termen;

(b) daca A si B sunt termeni, atunci (A B) este un termen;

(c) orice termen se construieste cu ajutorul regulilor (i) si (ii).

Un termen este transformat cu ajutorul urmatoarei reguli de rescriere: sablonul


(((X A) B) C) unde A, B si C sunt termeni, este nlocuit cu ((A C)(B C)).
Reducerea unui termen nseamna aplicarea acestei reguli. Un termen se numeste redus
daca nu contine nici un subtermen care sa poata fi redus. Un termen poate fi considerat
ca subtermen pentru el nsusi.
Sa se realizeze un algoritm care determina pentru un termen de intrare, termenul redus
corespunzator. Un termen contine doar simbolurile X, ( si ), fara spatii ntre ele.
Lungimea unui termen de intrare este 100.

Observatia 4.4 O expresie va avea maxim 9 nivele de parantezare. Num arul de redu-
ceri ce pot fi efectuate este finit. Liniile de iesire contin maxim 80 caractere. Caracterele
posibile din termenii de iesire sunt tot X, ( si ).

Intrare Iesire
(((XX)X)X) ((XX)(XX))
(((XX)(XX)X) ((XX)((XX)X))
(XX) (XX)
(ACM, Bucuresti, 1996)

5. Prin arbore binar complet ntelegem un arbore binar n care un nod are fie doi descendenti,
fie nu are nici unul.
Un arbore binar complet poate fi reprezentat prin codificarea drumurilor de la radacina
la fiecare frunza utilizand numai valorile 0 si 1: atunci cand coboram n arbore spre
stanga se adauga valoarea 0 iar atunci cand coboram spre dreapta se adauga valoarea
1.

99
Pentru arborele din figura 4.11 drumurile de la radacina la fiecare frunza se codifica
astfel:
ABC : 00
ABDE : 010
ABDF : 011
AG : 1
Concatenand toate drumurile de la radacina catre frunze, arborele anterior poate fi
reprezentat prin secventa ce poate fi interpretata ca reprezentarea n baza 2 a unui
numar (000100111).
Fiind date m si n doua numere naturale, se cere sa se determine daca exista un arbore
binar complet a carui reprezentare va contine exact m cifre 0 si n cifre 1 (0 < m, n
100).
(Timisoara-pregatire, 1996)

6. Scrieti o subrutina nerecursiva care numara nodurile frunza ale unui arbore binar.
7. Realizati o subrutina care sa determine nivelul cu cele mai multe noduri frunze dintrun
arbore binar.
8. Determinati natimea unui arbore binar printro functie nerecursiva.
9. Se considera un dictionar ce este implementat sub forma unei structuri de arbore.
Pentru fiecare cuvant se cunoaste traducerea acestuia precum si probabilitatea lui de
aparitie n texte.
Se cere sa se realizeze un algoritm care sa conduca la o cautare optima a cuvintelor
n dictionar. Se mentioneaza faptul ca probabilitatea de a cauta un cuvant care nu se
gaseste n dictionar este zero.
Datele de iesire constau din afisarea arborelui optimal de cautare compus din cuvintele
introduse.
Spre exemplu, un set de date de intrare poate fi:

5
1 abroad in_strainatate
4 baker brutar
2 calf gamba
1 dice zaruri
2 ear ureche

10. O casa de comenzi este interesata de crearea unui program pe calculator care sa tina
evidenta comenzilor n ordinea datelor la care au fost onorate, iar n cadrul fiecarei date
de livrare, n ordine lexicografica dupa numele produsului. Sa se scrie un program care
foloseste structuri de date de tip arbore.
Intrarea este formata din mai multe linii. Pe fiecare linie avem cate o comanda data
sub forma: [numep rodus] [zi] [luna] [an]. Intrarea se termina cu o linie pe care avem
doar &.
Iesirea este formata prin afisarea comenzilor n ordinea datei la care au fost onorate,
iar n cadrul unei date, n ordine lexicografica n functie de numele comenzii.
Indicatie: Se va crea un arbore de cautare dupa data de livrare si n fiecare nod va fi
un arbore de cautare dupa numele comenzii.

100
11. Se considera o expresie logica formata din n variabile logice reprezentate printro sin-
gura litera si operatorii & (AND), | (OR), ! (NOT) (nu avem paranteze). Expresia este
reprezentata sub forma unui arbore binar.
Se cere sa se realizeze un algoritm care pentru o expresie logica data cerceteaza existenta
unei combinatii de valori logice (true/false), pentru care expresia data ia valoarea logica
true.
De exemplu pentru expresia a|!b|c&d solutia este a = f alse, b = f alse, c = f alse,
d = f alse, iar pentru expresia !a&a nu avem solutie.

12. Se considera o expresie matematica reprezentata printr-un arbore binar. Operatorii


corespund operatiilor matematice uzuale +, , , /, si ? (pentru ridicare la putere).
Nu avem paranteze si toti operatorii sunt operatori binari. Operanzii sunt identificati
printro singura litera.
Se cere sa se realizeze un algoritm ce creaza structura de date corespunzatoare unei
expresii date si sa evalueaze expresia pentru o multime de valori ale operanzilor.
Pentru expresia a + b c unde a = 2, b = 3 si c = 1 avem valoarea 1, iar n urma
evaluarii expresiei a b/c?a cu a = 2, b = 9 si c = 3 obtinem valoarea 1.

101
Capitolul 5

Arbori oarecare

Reamintim definitia unui arbore oarecare:

Definitia 5.1 Fiind data o multime M de elemente denumite noduri, vom numi arbore o
submultime finita de noduri astfel nc
at:

1. exista un nod cu destinatie speciala, numit r


ad
acina arborelui;
2. celelalte noduri sunt repartizate n n multimi disjuncte dou
a c a, A1 , A2 , , An , fiecare
ate dou
multime Ai constituind la randul ei un arbore.

5.1 Moduri de reprezentare


Pentru reprezentarea arborilor oarecare pot fi utilizate urmatoarele metode:

legaturi fiu-frate: f iuk - este primul descendent al varfului k, f ratek - urmatorul descen-
dent al tatalui nodului k, ce urmeaza dupa k. Intre descendentii unui varf se presupune
ca definim o relatie de ordine.
Radacina arborelui din figura 5.1 este Rad = 1.

2 3 4

5 6 7 8 9 10

Fig. 5.1: Exemplu de arbore oarecare

Table 5.1: Reprezentarea cu legaturi fiufrate a arborelui din figura 5.1.

Nod 1 2 3 4 5 6 7 8 9 10
Fiu 2 0 5 7 0 0 0 0 0 0
Frate 0 3 4 0 6 0 8 9 10 0

102
Daca identificam F iu cu Stang si F rate cu Drept, unui arbore oarecare i se poate
asocia un arbore binar.
Pentru reprezentarea unui arbore oarecare, modelul de reprezentare fiu-frate n varianta
de alocare statica poate fi usor extins la o varianta de alocare dinamica, folosind pointeri
pentru legaturile catre primul descendent respectiv pentru urmatorul frate. Astfel un
nod al arborelui poate avea urmatoarea configuratie:

typedef struct nod {


TipOarecare data;
struct nod* fiu;
struct nod* frate;
}Nod;

Asemanator cu modelul construit la arbori binari, rad (Nod rad;) este o variabila de
tip pointer la Nod (variabila pastreaza adresa unei zone de memorie). rad desemneaza
adresa radacinii arborelui.

rad

1 NULL

2 3 4 NULL

NULL

5 6 NULL 7 8 9 10 NULL

NULL NULL NULL NULL NULL NULL

Fig. 5.2: Exemplu de arbore oarecare cu 10 noduri reprezentat prin legaturi fiu-frate

In figura 5.2 este reprezentat arborele din figura 5.1 prin leg
aturi fiu-frate.

lista descendentilor. In cadrul acestui mod de reprezentare fiecare varf este descris prin
lista descendentilor sai. Pentru memorare se va utiliza un vector cu n componente:
(
0 , varful respectiv nu are descendenti
Ck =
j , j indica adresa (coloana) unde ncepe lista descendentilor varfului k.

Listele de descendenti se pastreaza prin intermediul unei matrice L cu 2 linii si N 1


coloane:
L1,k - un descendent al varfului a carui lista contine coloana k a matricei date.
(
0 , daca descendentul respectiv este ultimul
L2,k =
j , j indica coloana unde se afla urmatorul descendent

Exploatand mai departe aceasta idee, ntr-un nod al arborelui oarecare se poate pastra o
lista cu adresele descendentilor sai, lista fiind reprezentata sub forma unui tablou alocat

103
Table 5.2: Reprezentarea arborelui din figura 5.1 folosind liste cu descendenti.

Nod 1 2 3 4 5 6 7 8 9 10
C 1 0 4 6 0 0 0 0 0 0
 
2 3 4 5 6 7 8 9 10
L=
2 3 0 5 0 7 8 9 0

static sau dinamic. Oricum modelul se recomanda atunci cand numarul descendentilor
unui nod este limitat superior, sau cand acesta este cunoscut/stabilit n momentul n
care se construieste arborele. Modificarile efectuate asupra arborelui, cum ar fi inserari
de descendenti noi, atunci cand intrarile alocate pentru acesti descendenti sunt deja
alocate nu poate conduce decat la realocarea spatiului de memorie cu un cost reflectat
n complexitatea algoritmului. Astfel daca numarul de descendenti este limitat superior
putem defini

#define NMAX 100


typedef struct nod {
TipOarecare data;
struct nod* children[NMAX];
}Nod;

sau

typedef struct nod {


TipOarecare data;
int no_of_children;
struct nod** children;
}Nod;

atunci cand numarul maxim de descendenti nu poate fi cunoscut decat n momentul


construirii arborelui. Astfel, n acel moment, se aloca spatiu pentru un tablou de
pointeri catre structura de tip Nod definita (children = malloc(sizeof(Nod*) *
no of children)).

rad

1 3

2 0 3 2 4 4

5 0 6 0 7 0 8 0 9 0 10 0

Fig. 5.3: Exemplu de arbore oarecare cu 10 noduri reprezentat prin liste cu descendenti

In figura 5.3 este reprezentat arborele din figura 5.1 folosind liste cu descendenti.

104
Tata - fiecarui nod k se indica nodul parinte.

Table 5.3: Reprezentarea arborelui din figura 5.1 folosind vectorul tata.

Nod 1 2 3 4 5 6 7 8 9 10
Tata 0 1 1 1 3 3 4 4 4 4

Acest mod de reprezentare are dezavantajul ca nu pastreaza n mod explicit ordinea


descendentilor unui nod. Aceasta ordine poate fi dedusa printro numerotare adecvata a
nodurilor. De obicei reprezentarea cu vectorul tata nu este folosita singura ci mpreuna
cu alte moduri de reprezentare, ca o completare.
De exemplu, reprezentarea unui nod propusa la primul punct, poate fi modifcata astfel
ncat sa incorporeze si informatii despre nodul parinte:

typedef struct nod {


TipOarecare data;
struct nod* fiu;
struct nod* tata;
struct nod* frate;
}Nod;

rad
NULL

1 NULL

NULL

2 3 4 NULL

NULL

5 6 NULL 7 8 9 10 NULL

NULL NULL NULL NULL NULL NULL

Fig. 5.4: Exemplu de arbore oarecare cu 10 noduri reprezentat prin legaturi fiu-frate-tata

In figura 5.4, reprezentarea din figura 5.2 este mbunatatita prin leg
atura tata.

5.2 Metode de parcurgere


Pentru parcurgerea unui arbore oarecare se pot folosi metodele generale pentru parcurg-
erea arborelui binar asociat. Arborele binar asociat unui arbore oarecare se obtine n urma
realizarii corespondentei F iu Stang si F rate Drept.
In plus fata de acestea, avem si doua metode de parcurgere pe care putem sa le numim
specifice unui arbore oarecare. Acestea se pot clasifica dupa momentul n care se realizeaza
vizitarea nodului parinte astfel:

105
Apreordine: se viziteaza mai ntai radacina, si apoi, n ordine, subarborii sai [93],
[123]. Metoda este identica cu metoda de parcurgere n preordine a arborelui binar
atasat: 1, 2, 3, 5, 6, 4, 7, 8, 9, 10 (vezi algoritmul 35). Parcurgerea n Apreordine este
exemplificata pe arborele oarecare din figura 5.1.

Algoritm 35 Algoritm de parcurgere n A-preordine


1: procedure APreordine(Rad, F iu, F rate)
2: k rad
3: q0
4: S initializare stiva vid
a
5: while (q = 0) do
6: if (k 6= 0) then
7: call V izit(k)
8: Sk S.push(k)
9: k f iuk
10: else
11: if (S = ) then verificare dac
a stiva este vid
a
12: q1
13: else
14: Sk S.pop(k)
15: k f ratek
16: end if
17: end if
18: end while
19: return
20: end procedure

Apostordine: se viziteaza mai ntai toti subarborii ce au drept radacini, descendentii


radacinii arborelui si apoi radacina arborelui [93], [123]. In urma parcurgerii n A
postordine a arborelui din figura 5.1 se obtine 2, 5, 6, 3, 7, 8, 9, 10, 4, 1.

Exemplul 5.1 [31] In continuare se prezint a implementarea n limbajul de programare C


a variantelor recursive pentru fiecare dintre cele dou
a metode de vizitare ale unui arbore
oarecare.

#include <stdio.h>
#include <conio.h>
#include <stdlib.h>

/**
* Definitii de tipuri necesare cozii
*/
typedef struct nod {
int id;
float info;
struct nod *down, *next;
}NOD;

typedef NOD* TINFO;

106
/**
* Definitii de tipuri pentru reprezentarea arborelui
*/
typedef struct cnod {
TINFO info; //informatia memorata in nod
struct cnod *next; //adresa urmatorului element
}CNOD;

CNOD *prim = NULL, *ultim = NULL;

NOD* citListaDescendenti(NOD *up);

/**
* Functie ce testeaza daca coada e vida
* @return 1 daca coada e vida
* 0 altfel
*/
int isEmpty(void) {
if (prim == NULL)
return 1;
else
return 0;
}

/**
* Functia adauga un element la sfarsitul unei cozi
* unde n reprezinta elementul ce se adauga.
*/
void add(TINFO n) {
CNOD *curent;

curent = (CNOD *) malloc(sizeof(CNOD));


curent->info = n;
curent->next = NULL;
if (!prim) {
prim = ultim = curent;
} else {
ultim->next = curent;
ultim = curent;
}
}

/**
* Functia extrage un element din coada. Returneaza elementul scos.
*/
TINFO get(void) {
CNOD *curent;
TINFO n;

if (prim == ultim) {
n = prim->info;
free(ultim);

107
prim = ultim = NULL;
} else {
curent = prim;
n = prim->info;
prim = prim->next;
free(curent);
}
return n;
}

/**
* Functia realizeaza crearea unui arbore oarecare. Se introduce initial radacina,
* apoi descendentii ei, dupa care se introduc descendentii nodurilor de pe
* nivelul 1, dupa care se introduc descendentii nodurilor de pe nivelul 2 s.a.m.d.
*/
NOD* creare(void) {
NOD *p, *c;

p = (NOD *)malloc(sizeof(NOD));
p->next = NULL;
printf("Dati id:"); scanf("%d", &p->id);
add(p);
while (!isEmpty()) {
c = get();
c->down = citListaDescendenti(c);
}
return p;
}

/**
* Functia citeste informatia asociata unui nod.
* @return 1 daca citirea s-a facut corect
* 0 altfel
*/
int citInfo(NOD *pn) {
printf("Id:");
pn->next = NULL;
pn->down = NULL;
return scanf("%d",&pn->id) == 1;
}

/**
* Functia realizeaza citirea listei de descendenti ai nodului up si
* returneaza adresa primului descendent din lista.
*/
NOD* citListaDescendenti(NOD *up) {
NOD *prim, *ultim, *p;
NOD n;

printf("\nLista de descendenti pt %d (CTRL+Z) daca nu are\n", up->id);


prim = ultim = NULL;
while (citInfo(&n)) {

108
p = (NOD *)malloc(sizeof(NOD));
*p = n;
if (prim == NULL)
prim = ultim = p;
else {
ultim->next = p;
ultim = p;
}
add(p);
}
return prim;
}

/**
* Parcurgerea in A-preordine a arborelui cu radacina p.
*/
void aPreordine(NOD *p) {
NOD *desc;

printf("%d ", p->id);


desc = p->down;
while (desc != NULL) {
aPreordine(desc);
desc = desc->next;
}
}

/**
* Parcurgerea in A-postordine a arborelui cu radacina p.
*/
void aPostordine(NOD *p) {
NOD *desc;

desc = p->down;
while (desc != NULL) {
aPostordine(desc);
desc = desc->next;
}
printf("%d ",p->id);
}

void main(void) {
NOD *rad;

rad = creare();
printf("\n Parcurgerea in A-Preordine este:\n");
aPreordine(rad);
printf("\n Parcurgerea in A-Postordine este:\n");
aPostordine(rad);
}

109
5.3 Arbori de acoperire de cost minim
Fie G = (V, E) un graf neorientat si fie c : E R o functie de cost ce asocieaza o valoare
reala fiecarei muchii. Notam cu TG multimea arborilor partiali ai grafului G (un arbore
partial este un graf partial conex si fara cicluri al grafului initial).
Cerinta problemei este aceea de a determina un arbore T TG avand costul cel mai mic
dintre toti arborii ce apartin multimii TG :

c(T ) = min{c(T )|T TG }


P
unde costul unui arbore T se defineste ca c(T ) = eET c(e). Arborele T ce poseda aceasta
proprietate se numeste arbore de acoperire de cost minim (eng. minimum spanning tree -
MST ). Se mai ntalneste si sub denumirea de arbore partial de cost minim sau arbore partial
minim.
Altfel spus, se cere sa se determine un graf partial conex G1 = (V, E1 ) (E1 E) cu
proprietatea ca suma costurilor tuturor muchiilor este minima, graful partial de cost minim
fiind chiar un arbore.
Determinarea arborelui de acoperire de cost minim are multe aplicatii practice: de exem-
plu, daca se dau n orase precum si costul legaturilor ntre acestea, se cere sa se determine o
conectare a tuturor oraselor astfel ncat oricare doua orase sa fie conectate direct sau indirect
iar costul conectarii sa fie minim.

Lema 5.2 (Proprietatea taieturii) Fie S o submultime de noduri a lui V si e muchia ce are
costul minim dintre toate muchiile ce au o singur
a extremitate n S. Atunci arborele partial
de cost minim va contine muchia e.

Demonstratie: Fie T un arbore partial de cost minim. Presupunem prin reducere la


absurd ca e / ET . Daca adaugam muchia e la T se obtine un ciclu C. In acest ciclu exista
o alta muchie f ce are exact o extremitate n multimea S.
Inlocuind muchia f cu muchia e n arborele T , obtinem un alt arbore de acoperire T =

T {e} \ {f }.
Deoarece c(e) < c(f ) vom avea ca c(T ) = c(T ) + c(e) c(f ) c(T ) < c(T ) adica am
| {z }
<0
obtinut un arbore de acoperire T ce are costul mai mic decat T , contradictie cu faptul ca
T este un arbore de acoperire de cost minim. 

Definitia 5.2 Se numeste t aietur


a a grafului G o partitie de dou
a submultimi a multimii
nodurilor V , notata astfel: < S, T > (unde S T = V si S T = ).

Lema 5.3 (Proprietatea ciclului) Fie C un ciclu si f muchia ce are costul maxim dintre
toate muchiile ce apartin lui C. Atunci arborele partial de cost minim T nu contine muchia
f.

Demonstratie: Fie T un arbore partial de cost minim. Presupunem prin reducere la


absurd ca f ET . Daca stergem muchia f din arborele T , se obtine o taietura < S, V \ S >.
Avem ca f C si o extremitate a lui f apartine lui S. Exista atunci o alta muchie e cu
proprietatea ca e C si e are o extremitate ce apartine lui S.
Inlocuind muchia f cu muchia e n arborele T , obtinem un alt arbore de acoperire T =
T {e} \ {f }.
Deoarece c(e) < c(f ) c(T ) < c(T ) adica am obtinut un arbore de acoperire T ce are
costul mai mic decat T , contradictie. 

110
Majoritatea algoritmilor ce calculeaza arborele de acoperire de cost minim prezinta aceeasi
tehnica generala de calcul. La nceput se porneste cu o padure de arbori, T 0 = {T10 , T20, . . . , Tn0 }
unde Ti0 = {xi }, i = 1, n este un arbore format dintrun singur nod. La pasul k vom avea
multimea T k compusa din n k arbori: T k = {T1k , T2k , . . . , Tnk
k
}.
k
La fiecare pas, se alege un arbore Ti si o muchie (u , v ) avand costul minim dintre toate
muchiile (u, v) cu proprietatea ca o extremitate apartine multimii de noduri a arborelui Tik
(u Tik ) si cealalta extremitate apartine multimii V \ VTik (v V \ VTik ).
Prin urmare, multimea T k+1 se obtine din multimea T k prin reuniunea arborilor Tik si
Tj (i 6= j), unde u Tik si v Tjk (T k+1 = T k \ {Tik , Tjk } {Tik Tjk }).
k

In final, la pasul n 1, multimea T n1 = {T n1 } va fi compusa dintrun singur element,


1
acesta fiind arborele de acoperire de cost minim.
Algoritmii cei mai cunoscuti pentru determinarea arborilor de acoperire de cost minim
sunt:
1. Algoritmul lui Boruvka (vezi algoritmul 36)

Algoritm 36 Algoritmul lui Boruvka (varianta schematica)


1: procedure Boruvka1(G, C, n; L)
2: initializeaz adurea de arbori P compus
a p a din n arbori, fiecare arbore fiind compus dintrun
singur nod
3: L
4: while (|P| > 1) do
5: for T P do
6: alege e muchia de cost minim de la T la G \ T
7: L e adaug a muchia e la lista de muchii alese ce vor forma arborele de acoperire
de cost minim
8: end for
9: adaug a toate muchiile selectate n cadrul f or-ului anterior la P
10: end while
11: end procedure

2. Algoritmul lui Prim (vezi algoritmul 37)

Algoritm 37 Algoritmul lui Prim (varianta schematica)


1: procedure Prim1(n, C, u; L)
2: S {u}, L
3: for i 1, n do
4: di cu,i
5: end for
6: for i 1, n 1 do
7: k min {dk |k V \ S}
8: L (tatak , k)
9: S S {k}
10: for each j V \ S do
11: dj min {dj , ck,j }
12: end for
13: end for
14: end procedure

111
3. Algoritmul lui Kruskal (vezi algoritmul 38)

Algoritm 38 Algoritmul lui Kruskal (varianta schematica)


1: procedure Kruskal1(G, C, n; L)
2: ordoneaz a muchiile n ordine cresc atoare dup a cost
3: L
4: for each u V do
5: creaz
a o multime compus a din {u}
6: end for
7: count 0
8: while count < n 1 do
9: alege muchia (u, v)
10: if (u si v sunt n multimi diferite) then
11: L (u, v)
12: reuneste multimile ce contin pe u si v
13: count count + 1
14: end if
15: end while
16: end procedure

Algoritmul lui Prim implementat simplu are o complexitate O(n2 )[30] si atinge o com-
plexitate de O(m log n) daca se folosesc heapuri Fibonacci [54], sau pairing heaps [115].
In tabelul 5.4 sunt prezentati mai multi algoritmi dezvoltati dea lungul timpului pentru
determinarea arborelui de acoperire minimal si complexitatile lor. Karger, Klein si Tarjan [79]
pornind de la algoritmul lui Boruvka au realizat un algoritm randomizat pentru determinarea
arborelui de acoperire minimal, avand o complexitate liniara, iar Chazelle [26] a dezvoltat
un algoritm avand complexitatea O(n(m, n)) ((m, n) este inversa functiei lui Ackerman).
Pe de alta parte, Pettie si Ramachandran [105] au propus un algoritm demonstrat ca fiind
optimal, avand complexitatea cuprinsa ntre O(n + m) si O(n(m, n)).

Table 5.4: Algoritmi pentru determinarea arborelui de acoperire minim

Anul Complexitate Autori


1975 E log log V Yao
1976 E log log V Cheriton-Tarjan
1984 E log V, E + V log V Friedman-Tarjan
1986 E log log V Gabow-Galil-Spencer-Tarjan
1997 E(V ) log (V ) Chazelle
2000 E(V ) Chazelle [26]
2002 optimal Pettie-Ramachandran [105]

Fie G(V, E) un graf neorientat unde V = {1, 2, ..., n} este multimea nodurilor si E este
multimea muchiilor (E V V ). Pentru reprezentarea grafului se utilizeaza matricea
costurilor C:
0
, daca i = j
ci,j = , daca (i, j) /E


d > 0 , daca (i, j) E

112
5.3.1 Algoritmul lui Boruvka
Algoritmul lui Boruvka [77] a fost descoperit de catre matematicianul ceh Otakar Boruvka
n 1926 [21], si redescoperit apoi de catre alti cercetatori. Dintre acestia, Sollin (1961) este
cel care a mai dat numele algoritmului, acesta fiind cunoscut n literatura de specialitate si
sub numele de algoritmul lui Sollin. Pentru ca acest algoritm sa poata fi aplicat, trebuie ca
muchiile grafului sa aiba costuri distincte (vezi algoritmul 39).

Algoritm 39 Algoritmul lui Boruvka


1: procedure Boruvka2(G, C, n; L)
2: for i 1, n do
3: Vi {i}
4: end for
5: L , M {V1 , . . . , Vn }
6: while (|T | < n 1) do
7: for U M do
8: a min{c(u , v )|(u , v ) E, u
fie (u, v) muchia pentru care se obtine valoarea minim
U, v
/ V \ U}
9: determin a componenta U ce contine pe v
10: L (u, v)
11: end for
12: for U M do
13: reuneste multimile ce contin pe u si v, U si U
14: end for
15: end while
16: end procedure

Exemplul 5.4 Sa consideram graful din figura 5.5:

G = (V, E), V = {1, 2, 3, 4, 5, 6, 7, 8}

Aplicand algoritmul lui Boruvka, la pasul ntai vor fi selectate muchiile (1, 2), (3, 6), (4, 5),

(4, 7) si (7, 8). In urma operatiilor de reuniune a componentelor conexe pe baza muchiilor
selectate, vor mai ramane trei componente conexe n multimea M.
La pasul al doilea sunt alese muchiile (2, 5) si (1, 3) ce conduc, n urma operatiilor de
reuniune, la o singura componenta conex a.

5.3.2 Algoritmul lui Prim


Algoritmul a fost descoperit mai ntai de V. Jarnik (1930) [76], si apoi independent de Prim
(1957) [106] si Djikstra (1959) [38].
Se porneste cu o multime S formata dintr-un singur nod (S = {v0 }). La fiecare pas se
alege muchia de cost minim ce are numai o extremitate n multimea S. Procesul se ncheie
dupa n 1 pasi, rezultand un graf partial aciclic. Din teorema 4.1 rezulta faptul ca acest
graf partial aciclic cu n 1 muchii este un arbore (de acoperire).
Conform Proprietatii taieturii, toate muchiile alese apartin arborelui partial de cost minim
de unde rezulta ca acest arbore de acoperire este un arbore partial minim.
Vom utiliza trei vectori de dimensiune n, unde n reprezinta numarul de varfuri al grafului
(vezi algoritmul 40):

113
Fig. 5.5: Exemplu de graf ponderat - aplicatie algoritmul lui Boruvka

vizitat - vector caracteristic


(
1 , daca nodul k S
vizitatk =
0 , daca nodul k V \ S

d - pentru un nod k / S, dk va contine distanta minima de la k la un nod j S. La


nceput, dj = cv0 ,j . Pentru un nod k ales la un moment dat, dj (j S) se modifica
numai daca ck,j < dj astfel dj = ck,j .

tata - contine pentru fiecare nod k


/ S nodul j S astfel ncat ck,j = min{ck,i|i S}.
La nceput, (
0 , daca nodul k = v0
tatak =
v0 , daca nodul k 6= v0
In momentul n care se modifica dj , se va modifica si valoarea lui tataj = k.

Fig. 5.6: Exemplu de graf ponderat - aplicatie algoritmul lui Prim

114
Algoritm 40 Algoritmul Prim (varianta detaliata)
1: function DistantaMinima(n, vizitat, d)
2: min
3: for j 1, n do
4: if (vizitatj 6= 1) (dj < min) then
5: min dj
6: j0 j
7: end if
8: end for
9: if (min = ) then
10: return 1
11: else
12: return j0
13: end if
14: end function

15: procedure Prim2(n, C, v0 ; d, tata)


16: vizitatv0 1
17: tatav0 0
18: for i 1, n do
19: if (i 6= v0 ) then
20: vizitati 0
21: di cv0 ,i
22: tatai v0
23: end if
24: end for
25: for i 1, n 1 do
26: k DistantaM inima(n, vizitat, d)
27: if (k < 0) then
28: Output Graful nu este conex!
29: return
30: end if
31: vizitatk 1 (k, tatak ) este o muchie ce apartine arborelui partial minim
32: for j 1, n do
33: if (vizitatj =6 1) (dj > ck,j ) then
34: tataj k
35: dj ck,j
36: end if
37: end for
38: end for
39: end procedure

Exemplul 5.5 Fie graful din figura 5.6:


G = (V, E), V = {1, 2, 3, 4, 5, 6, 7, 8}
Vom lua nodul initial v0 = 1. La nceput, dup
a etapa de initializare avem:
1 2 3 4 5 6 7 8
d 14 6 5
tata 0 1 1 1 1 1 1 1
vizitat 1 0 0 0 0 0 0 0

115
Dupa primul pas al ciclului, select
am muchia (1, 5).
1 2 3 4 5 6 7 8
d 14 6 21 5 16
tata 0 1 1 5 1 5 1 1
vizitat 1 0 0 0 1 0 0 0
La pasul al doilea se alege nodul 3 si muchia (1, 3):
1 2 3 4 5 6 7 8
d 14 6 21 5 12 12
tata 0 1 1 5 1 3 1 3
vizitat 1 0 1 0 1 0 0 0
La pasul al treilea avem doua noduri ale c aror distante la noduri din multimea S sunt
egale: 6 si 8. Alegem primul nod - 6 si muchia (3, 6):
1 2 3 4 5 6 7 8
d 14 6 21 5 12 14 6
tata 0 1 1 5 1 3 6 6
vizitat 1 0 1 0 1 1 0 0
Al patrulea nod ales este 8:
1 2 3 4 5 6 7 8
d 14 6 10 5 12 10 6
tata 0 1 1 8 1 3 8 6
vizitat 1 0 1 0 1 1 0 1
La pasul cinci, nodul aflat la distant
a minim
a este 7:
1 2 3 4 5 6 7 8
d 14 6 10 5 12 10 6
tata 0 1 1 8 1 3 8 6
vizitat 1 0 1 0 1 1 1 1
La pasul sase este ales nodul 4:
1 2 3 4 5 6 7 8
d 12 6 10 5 12 10 6
tata 0 4 1 8 1 3 8 6
vizitat 1 0 1 1 1 1 1 1
La final, ultimul nod ales este 2 mpreun
a cu muchia (4, 2).

Trebuie sa remarcam faptul ca algoritmul lui Prim (vezi algoritmul 40) este aproape
identic cu algoritmul lui Dijkstra (vezi algoritmul 62).
Dupa cum am subliniat, implementarea optima se realizeaza folosind niste structuri de
date avansate - heapuri Fibonacci sau pairing heaps (vezi algoritmul 41).

5.3.3 Structuri de date pentru multimi disjuncte


O partitie a unei multimi A este o secventa finita de multimi (submultimi) A1 , . . . , Am
disjuncte
Sm doua cate doua, cu proprietatea ca reuniunea acestora este chiar multimea A
(A = i=1 Ai si Ai Aj = , i, j = 1, m, i 6= j).
Exista mai multe probleme ai caror algoritmi de rezolvare depind de urmatoarele operatii
efectuate asupra elementelor partitiei: verificarea daca doua elemente fac parte din aceeasi
submultime precum si operatia de reuniune a doua submultimi.

116
Algoritm 41 Algoritmul lui Prim folosind structuri de date avansate
1: procedure Prim3(n, C, u)
2: for fiecare v V do
3: dv
4: end for
5: Q initializeaza coada cu prioritate Q cu multimea vid a
6: for fiecare v V do
7: Qv
8: end for
9: S initializeaz
a multimea S cu multimea vid
a
10: while Q 6= do
11: u deleteM in(Q) extrage nodul de prioritate minim a din Q
12: S S {u}
13: for fiecare muchie e = (u, v) E do
14: if (v
/ S) (c(e) < dv ) then
15: actualizeaza prioritatea lui v: dv c(e)
16: end if
17: end for
18: end while
19: end procedure

O structura de date pentru multimi disjuncte memoreaza o colectie de multimi disjuncte


dinamice. Fiecare multime este identificata printrun reprezentant [30]. Operatiile de baza
ale acestei structuri de date sunt [2]:

init(x, B) - procedura creaza o multime B formata dintrun singur element x;

f ind(x) - ntoarce reprezentantul multimii careia i apartine x;

merge(A, B) - reuneste multimile distincte A si B (x A, y B) ntro noua multime


ce contine elementele celor doua multimi.

O astfel de structura de date pentru multimi disjuncte se mai numeste si structura de date
unionfind (eng. unionfind data structure).

Reprezentarea folosind vectori


Vom folosi un vector C de dimensiune n, unde n reprezinta numarul de elemente, iar ck = u
indica faptul ca elementul k apartine multimii u.
Init consta din initializarea lui cx cu identificatorul multimii, u. F ind ntoarce valoarea
lui cx (valoarea identificatorului multimii). In functia Merge se cauta toate elementele ce
fac parte din multimea de identificator cy si se trec n multimea al carui identificator este cx
(vezi algoritmul 42).

Exemplul 5.6 Pentru o putea opera cu modul de reprezentare ales, cel cu vectori, trebuie
ca elementele multimii sa ia valori naturale n intervalul [1,. . . ,n]. Dac a acest lucru nu
este posibil atunci folosim un vector auxiliar A ce p astreaza valorile elementelor, n cadrul
reprezentarii utilizanduse indicele acestora. S a presupunem c a avem multimea de elemente
A = {a, b, e, x, y, z, u, v} si partitia {a, x, y}, {b, z, v}, {e, u}. Atunci conform modului de
reprezentare descris avem:

117
Algoritm 42 Algoritmi pentru operatiile init, find, merge (varianta ntai)
1: procedure Init(x, u)
2: cx u
3: end procedure
4: function Find(x)
5: return cx
6: end function
7: function Merge(x, y)
8: setx cx
9: sety cy
10: for k 1, n do
11: if (ck = sety) then
12: ck setx
13: end if
14: end for
15: return setx
16: end function

1 2 3 4 5 6 7 8
A a b e x y z u v
C 1 2 3 1 1 2 2 3
a2 = b are semnificatia urmatoare: elementul de pe pozitia 2 are valoarea b . c2 = 2 -
elementul de pe pozitia 2 face parte din multimea de identificator 2. Sau a4 = x - elementul
de pe pozitia 4 are valoarea x , si c4 = 1 - elementul de pe pozitia 4 face parte din multimea
de identificator 1.

Reprezentarea folosind liste nl


antuite
In cadrul acestei metode fiecare multime este reprezentata printro lista simplu nlantuita,
elementul reprezentant fiind elementul aflat n capul listei. Un nod al listei va avea un camp
ce pastreaza informatia cu privire la un element al unei multimi, si un camp ce contine
legatura catre urmatorul nod al listei.
Vom folosi un vector de adrese (List), ce contine adresa primului element din fiecare lista
(Listi ).
Procedura Init aloca un nod si initializeaza campurile acestuia. Adresa nodului alocat
este pastrata n Listk (vezi algoritmul 43).
Functia F ind cauta un element printre elementele pastrate de fiecare lista n parte. Se
parcurge vectorul List si se obtine reprezentantul fiecarei submultimi pastrate. Se parcurge
lista (cautare liniara) si se cauta un element cu valoarea x. Daca elementul este gasit atunci
se ntoarce capul listei n care a fost gasit. Daca pana la sfarsit nu a fost identificat elementul
x, atunci functia returneaza valoarea NULL.
Functia Merge reuneste doua liste: practic se adauga o lista la sfarsitul celeilalte. Pentru
a realiza aceasta operatie pe langa adresa primului element avem nevoie de adresa ultimului
element. Acest lucru se poate obtine indirect prin parcurgerea listei de la primul la ultimul
element, fie direct daca n momentul crearii pastram pentru fiecare lista nca o variabila ce
contine adresa ultimului element (last).

118
Algoritm 43 Algoritmi pentru operatiile init, find, merge (varianta a II-a)
1: procedure Init(x, k)
2: Listk new node aloc
a spatiu pentru un nod al listei
3: Listk .data x
4: Listk .next N U LL
5: end procedure
6: function Find(x)
7: for i 1, m do
8: p Listi , reprezentant p
9: while (p 6= N U LL) (p.data 6= x) do
10: p p.next
11: end while
12: if (p 6= N U LL) then
13: return reprezentant
14: end if
15: end for
16: return N U LL
17: end function
18: function Merge(x, y)
19: capx F ind(x)
20: capy F ind(y)
21: curent capx
22: while (curent.next 6= N U LL) do
23: curent curent.next
24: end while
25: curent.next capy
26: return capx
27: end function

Reprezentarea folosind o p
adure de arbori
In cadrul acestei modalitati de reprezentare fiecare multime este pastrata sub forma unui
arbore. Pentru fiecare nod pastram informatia despre parintele sau: tatak reprezinta indicele
nodului parinte al nodului k.
Se poate observa foarte usor (vezi algoritmul 44) faptul ca arborele obtinut n urma unor
operatii repetate Merge poate deveni degenerat (o lista liniara). Pentru a putea sa evitam o
astfel de situatie, reuniunea se poate realiza tinand cont de una din urmatoarele caracteristici:

1. numarul de elemente din fiecare arbore - radacina arborelui ce are mai putine elemente
(notam arborele cu TB ) va deveni descendentul direct al radacinii arborelui ce poseda
mai multe elemente (notat cu TA ). Astfel toate nodurile din arborele TA vor ramane cu
aceeasi adancime, pe cand nodurile din arborele TB vor avea adancimea incrementata
cu 1. De asemenea, arborele rezultat n urma reuniunii va avea cel putin de doua ori
mai multe noduri decat arborele TB .

119
Algoritm 44 Algoritmi pentru operatiile init, find, merge (varianta a III-a)
1: procedure Init(x)
2: tatax x
3: end procedure
4: function Find(x)
5: while (x 6= tatax ) do
6: x tatax
7: end while
8: return x
9: end function
10: function Merge(x, y)
11: radx F ind(x)
12: rady F ind(y)
13: tatarady radx
14: return radx
15: end function

1: function Merge(x, y)
2: radx F ind(x)
3: rady F ind(y)
1: procedure Init(x)
4: size |tataradx | + |tatarady |
2: tatax 1
5: if (|tataradx | > |tatarady |) then
3: end procedure
6: tatarady radx
4: function Find(x)
7: tataradx size
5: while (tatax > 0) do
8: return radx
6: x tatax
9: else
7: end while
10: tataradx rady
8: return x
11: tatarady size
9: end function
12: return rady
13: end if
14: end function
Prin aceasta euristica simpla se urmareste o echilibrare a arborelui rezultat pentru a se
evita cazurile degenerate. Complexitatea timp a procedurii F ind este O(log n).

2. naltimea fiecarui arbore - radacina arborelui ce are naltimea mai mica (notam arborele
cu TB ) va deveni descendentul direct al radacinii arborelui cu naltimea mai mare (notat
cu TA ). Daca naltimile lui TA si TB sunt egale si TA devine subarbore al lui TB atunci
naltimea lui TB creste cu o unitate.
Inaltimea fiecarui subarbore de radacina u se pastreaza ntro variabila noua hu . Pentru
a economisi cantitatea de memorie utilizata, se poate pastra naltimea unui arbore n
vectorul tata: tatax = valoarea naltimii arborelui, atunci cand x reprezinta nodul
radacina al unui arbore.

120
1: function Merge(x, y)
2: radx F ind(x)
1: procedure Init(x) 3: rady F ind(y)
2: tatax x 4: if (hradx > hrady ) then
3: hx 0 5: tatarady radx
4: end procedure 6: return radx
5: function Find(x) 7: else
6: while (tatax 6= x) do 8: tataradx rady
7: x tatax 9: if (hradx = hrady ) then
8: end while 10: hrady hrady + 1
9: return x 11: end if
10: end function 12: return rady
13: end if
14: end function
Cea dea doua tehnica utilizata pentru a reduce complexitatea timp a operatiilor F ind si
Merge este reprezentata de comprimarea drumului. Aceasta consta n a apropia fiecare nod
de radacina arborelui caruia i apartine. Astfel n timpul operatiei F ind(x) se determina mai
ntai radacina arborelui caruia i apartine (rad) si apoi se mai parcurge o data drumul de la
nodul x la radacina, astfel tatay rad, y lantul de la x la rad.
1: function Find(x)
2: yx
3: while (tatay 6= y) do
4: y tatay
5: end while
6: rad y
7: yx
8: while (tatay =6 y) do
9: y tatay
10: tatax rad
11: xy
12: end while
13: return rad
14: end function

5.3.4 Algoritmul lui Kruskal


Algoritmul lui Kruskal [88] este o ilustrare foarte buna a metodei generale Greedy (vezi
algoritmul 45). La nceput se pleaca cu o padure de arbori, fiecare arbore fiind alcatuit
dintrun singur nod. La fiecare pas se alege o muchie: daca cele doua extremitati (noduri)
fac parte din acelasi arbore atunci nu se va adauga muchia curenta la arborele partial respectiv
deoarece ar conduce la un ciclu, ceea ce ar strica proprietatea de graf aciclic. Altfel, daca cele
doua noduri fac parte din arbori partiali distincti, se adauga muchia curenta la multimea de
muchii alese anterior, iar cei doi arbori partiali devin unul singur. La fiecare pas, numarul
de arbori partiali scade cu 1. Dupa n 1 alegeri padurea initiala compusa din n arbori sa
transformat ntrun singur arbore.
Pentru a pastra muchiile grafului vom utiliza o matrice A cu m coloane si 3 linii: primele
doua linii contin extremitatile muchiilor, iar linia a 3-a costul muchiei respective. Pentru a se
aplica o strategie Greedy, datele de intrare muchiile se vor ordona crescator n functie de
costul asociat. Deoarece graful partial respectiv este un arbore cu n noduri el va avea n 1
muchii.

121
O verificare eficienta cu privire la apartenenta la acelasi arbore a celor doua extremitati
ale unei muchii se poate face daca vom utiliza o structura de date pentru multimi disjuncte.
Ca implementare, am ales reprezentarea cu padure de arbori. Vom folosi un vector tata
care, pentru fiecare nod, va pastra tat al acestuia n arborele partial caruia i apartine.
Cand sunt preluate ca date de intrare, muchiile vor fi prezentate n ordinea crescatoare a
extremitatilor lor, de forma i j cost, cu i < j.

Algoritm 45 Algoritmul lui Kruskal


1: procedure Kruskal2(n, m, A; L, cost)
2: for i 1, n do
3: call Init(i)
4: end for
5: cost 0
6: j 1, L
7: for i 1, n 1 do
8: q0
9: while (q = 0) do
10: r1 F ind(A1,j )
11: r2 F ind(A2,j )
12: if (r1 6= r2 ) then
13: cost cost + A3,j
14: call M erge(r1 , r2 )
15: L L {(A1,j , A2,j )}
16: q1
17: end if
18: j j+1
19: end while
20: end for
21: end procedure

Exemplul 5.7 Sa consideram graful din figura 5.6. Muchiile grafului si costurile lor sunt
(am ales sa reprezentam matricea A sub forma unei matrice cu m coloane si 3 linii din motive
de spatiu):
A 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
linia 1 1 1 1 2 2 2 3 3 4 4 4 5 6 6 7
linia 2 2 3 5 4 5 6 6 8 5 7 8 6 7 8 8
linia 3 14 6 5 12 16 20 12 12 21 24 10 16 14 6 10
Dupa asezarea n ordine crescatoare a muchiilor dup a cost avem:
A 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
linia 1 1 1 6 4 7 2 3 3 1 6 2 5 2 4 4
linia 2 5 3 8 8 8 4 6 8 2 7 5 6 6 5 7
linia 3 5 6 6 10 10 12 12 12 14 14 16 16 20 21 24
In figura 5.7, sunt ilustrati pasii algoritmului lui Kruskal aplicat pe graful considerat.
La nceput se initializeaza padurea de arbori, fiecare arbore fiind alc atuit dintrun singur
nod, acesta fiind si radacina arborelui. Apoi la fiecare pas se alege o muchie si se verifica
daca extremitatile acesteia fac parte din arbori diferiti. Dac
a raspunsul este afirmativ atunci
muchia respectiva este selectata, altfel se trece la muchia urm atoare.

122
Fig. 5.7: Algoritmului lui Kruskal exemplificat pe graful din figura 5.6

La pasul ntai evaluam muchia (1, 5), si deoarece cele dou


a extremitati fac parte din arbori
distincti, vom selecta aceasta muchie (L = {(1, 5)}). Reunim arborii din care fac parte cele
doua extremitati, (vezi figura 5.7 (1)).
La pasul al doilea, se ia n considerare muchia (1, 3) si multimea muchilor selectate devine
L = {(1, 5), (1, 3)} (vezi figura 5.7 (2)).
La pasul al patrulea, ajungem la muchia (4, 8) ce are costul 10. Nodul 4 face parte din
arborele de radacina 4 iar nodul 8 face parte din arborele de r
adacin
a 6. Deoarece extremitatile
muchiei sunt amplasate n arbori distincti select am muchia curent a pentru arborele partial

de cost minim, L = {(1, 5), (1, 3), (6, 8), (4, 8)}. In urma reuniunii arborilor corespunzatori
celor doua noduri se obtine configuratia din figura 5.7 (4).

123
Subliniem faptul ca arborii reprezentati n figura 5.7 sunt diferiti de arborii ce con-
duc la obtinerea solutiei problemei: arborii din figur a constituie suportul necesar pentru
reprezentarea structurii de date pentru multimi disjuncte, ce este utilizat a pentru efectuarea
eficienta a operatiilor F ind si Merge. Acest lucru justific a faptul ca desi la pasul al patrulea
selectam muchia (4, 8) pentru arborele partial de cost minim, ea nu se reg aseste n configuratia
(4) (nodul 4 este unit direct cu nodul 6, vezi figura 5.7). Singura leg atura dintre un arbore
suport pentru reprezentarea unei multimi si un arbore folosit pentru obtinerea arborelui partial
de cost minim, este multimea de noduri ce este comun a.
La pasii urmatori, cinci si sase, sunt alese muchiile (7, 8) si respectiv (2, 4): L = {(1, 5), (1, 3),
(6, 8), (4, 8), (7, 8), (2, 4)}.
La pasul al saptelea se evalueaza muchia (3, 6). Nodurile 3 si 6 fac parte din arbori diferiti,
astfel ncat muchia curenta poate fi selectata pentru solutia problemei, L = {(1, 5), (1, 3), (6, 8),
(4, 8), (7, 8), (2, 4), (3, 6)}.

5.4 Exercitii
1. Sa se realizeze o subrutina ce ntoarce numarul de noduri dintrun arbore oarecare.

2. Se da o secventa formata din n numere naturale d1 , d2 , . . . , dn . Sa se realizeze un


algoritm prin care sa se verifice daca exista un arbore cu n noduri ale caror grade sunt
d1 , d2 , . . . , dn .
Daca exista un astfel de arbore, acesta va fi reprezentat prin liste ale nodurilor. O lista
a unui nod contine numarul nodului urmat de fii sai.
Intrare Iesire
1123113 DA
1 2 3 4
2 5 6
3 7
4
5
6
7
(Timisoara-pregatire, 1996)

3. Fie un graf G cu n varfuri si m muchii de cost pozitiv. Alegand un nod, numit nod
central, sa se determine un subarbore al lui G astfel ncat drumurile de la nodul central
la toate celelalte noduri sa aiba lungimea minima.

4. Fiind date n puncte n spatiul R3 determinate prin coordonatele (x, y, z), sa se elaboreze
un algoritm ce determina sfera de raza minima cu centrul ntrunul din punctele date
si care contine n interiorul ei toate cele n puncte.

5. Se considera procesul de proiectare a unei placi electronice cu N componente (1 N


100). Pentru fiecare componenta electronica C se cunoaste numarul de interconexiuni.
Se considera graful determinat de multimea pinilor si multimea interconectarilor posi-
bile ale tuturor componentelor, precum si lungimile lor. Dintre toate modalitatile de
interconectare posibile se cere cea corespunzatoare arborelui de acoperire minim (inter-
conectarea pentru care suma tuturor circuitelor imprimate are lungimea minima).

124
Datele de intrare sunt alcatuite din descrierile mai multor placi electronice. Descrierea
fiecarei placi contine numarul N de componente si numarul M de interconexiuni. O
conexiune se caracterizeaza prin trei valori: doua varfuri, u si v, precum si lungimea
acesteia l.
Datele de iesire constau dintr-un raspuns pentru fiecare multime de date de test, compus
din numarul testului (se ncepe numerotarea cu 1), precum si costul interconectarii de
lungime minima.

Exemplul 5.8 Pentru datele de intrare

5 7
1 2 40 2 3 35 1 5 27 1 4 37 4 5 30 2 4 58 3 4 60
0 0

vom obtine rezultatul

Cazul 1: [1 5] [4 5] [2 3] [1 2]
Interconectarea de cost minim are valoarea 132.

In figura 5.8 este reprezentat graful corespunz


ator datelor de intrare.

Fig. 5.8: Descrierea conexiunilor posibile dintre componentele unei placi electronice

6. Realizati o subrutina nerecursiva care sa determine cheia minima dintrun arbore oare-
care.

7. Determinati naltimea unui arbore oarecare printro functie nerecursiva.

8. Pentru asigurarea securitatii activitatilor dintrun combinat chimic sa apelat la o com-


panie de pompieri. Desfasurarea activitatilor companiei presupune stabilirea locurilor
de instalare a comandamentului precum si a posturilor de supraveghere. Pentru aceasta
sunt disponibile n cadrul combinatului n puncte de control. Pentru fiecare pereche de
puncte de control se cunoaste daca exista o legatura directa ntre ele si, n caz afirmativ,
distanta dintre ele. Se cunoaste, de asemenea, faptul ca ntre oricare doua puncte de
control exista un drum direct sau indirect.
Odata stabilite amplasamentele comandamentului si ale punctelor de supraveghere n
cate unul dintre cele n puncte de control este posibil sa se ajunga de la comandament

125
la fiecare punct de supraveghere parcurgand un anumit drum. Evident este de dorit ca
lungimea acestui drum sa fie minima si valoarea maxima dintre lungimile drumurilor
minime pentru fiecare punct de supraveghere n parte sa fie cat mai mica posibila.
Se cere sa se determine punctul de control n care trebuie instalat comandamentul cat
si drumurile ce vor fi parcurse de la comandament la fiecare punct de supraveghere,
astfel ncat aceste drumuri sa aiba valoarea lungimii minima si cel mai lung dintre ele
sa fie cat mai scurt posibil.

126
Capitolul 6

Grafuri orientate

6.1 Notiuni de baz


a
Fie o multime finita V = {x1 , x2 , . . . , xn }. Fie E V V (unde V V este produsul
cartezian al multimii V cu ea nsasi).
In cazul unui graf orientat, notiunea de muchie este nlocuita cu notiunea de arc (o
pereche de noduri (x, y) devine ordonata, adica (x, y) 6= (y, x)). Pentru un arc (x, y) E,
varful x reprezinta extremitatea initiala a arcului, iar varful y reprezinta extremitatea finala.
Vom spune ca varfurile x si y sunt adiacente.

Definitia 6.1 Un graf orientat este o pereche ordonat a G = (V, E), unde V este o
multime de varfuri sau noduri, iar E este o multime de arce.

Exemplul 6.1 Fie graful orientat G = (V, E), V = {1, 2, 3, 4, 5, 6, 7, 8},


E = {(1, 2), (1, 3), (1, 4), (2, 1), (3, 5), (4, 6), (5, 2), (5, 7), (6, 4), (6, 8), (7, 5), (7, 6)}. Acest graf
se poate reprezenta ca n figura 6.1.

Definitia 6.2 Un graf partial al unui graf orientat G = (V, E) este un graf orientat
G1 = (V, E1 ) unde E1 E.

Exemplul 6.2 Pentru graful G din exemplul anterior (G = (V, E)), consideram graful
partial G1 = (V, E1 ), cu E1 = {(1, 3), (2, 1), (3, 5), (4, 6), (5, 2), (5, 7), (6, 4), (6, 8)}, si V =
{1, 2, 3, 4, 5, 6, 7, 8}.

Definitia 6.3 Un subgraf al unui graf orientat G = (V, E) este un graf orientat H =
(V1 , E1 ) unde V1 V iar arcele din E1 sunt toate arcele din E ce au ambele extremitati n
multimea V1 (E1 = E|V1 V1 ).

Exemplul 6.3 Fie subgrafurile H1 si H2 ale grafului G din exemplul anterior: H1 = (V1 , E1 ),
unde V1 = {1, 2, 3, 5}, iar E1 = {(1, 2), (1, 3), (2, 1), (3, 5), (5, 2)}, si H2 = (V2 , E2 ), unde
V = {4, 6, 7, 8} si E2 = {(4, 6), (6, 4), (6, 8), (7, 6)}.

Definitia 6.4 Gradul exterior al unui v arf d+ (x) este egal cu numarul arcelor ce au ca ex-
tremitate initiala pe x (d+ (x) = |{(x, u)|(x, u) E, u V }|). Gradul interior al unui varf
d (x) este egal cu numarul arcelor ce au ca extremitate final a pe x (d (x) = |{(u, x)|(u, x)
E, u V }|).

127
Fig. 6.1: Un exemplu de graf orientat

Definitia 6.5 Se numeste lant o secvent a de arce L = {l1 , . . . , lp } cu proprietatea ca oricare


doua arce consecutive ale secventei au o extremitate comun a. (L = [v0 , v1 , . . . , vp ] unde
(vi , vi+1 ) E sau (vi+1 , vi ) E, i = 0, p 1).

Definitia 6.6 Un drum D = [v0 , v1 , . . . , vp ] este o succesiune de varfuri cu proprietatea ca


oricare doua varfuri vecine sunt adiacente ((vi , vi+1 ) E, i = 0, p 1).

Daca varfurile v0 , v1 , . . . , vp sunt distincte doua cate doua, drumul se numeste elementar.

Exemplul 6.4 L1 = {(6, 8), (7, 6), (7, 5), (3, 5), (1, 3)} si L2 = {(6, 8), (6, 4), (1, 4)} sunt lanturi
de la varful 8 la varful 1, iar D1 = {(1, 3), (3, 5), (5, 7), (7, 6), (6, 8)} si
D2 = {(1, 4), (4, 6), (6, 8)} sunt drumuri elementare de la v arful 1 la v arful 8.

Definitia 6.7 Un drum D pentru care v0 = vp se numeste circuit.

Definitia 6.8 Se numeste circuit hamiltonian un circuit elementar ce trece prin toate
varfurile grafului. Un graf ce admite un circuit hamiltonian se numeste graf hamiltonian
orientat.

Definitia 6.9 Un drum D ce contine fiecare arc exact o singur a dat


a se numeste drum eu-
lerian. Daca v0 = vp si drumul este eulerian atunci circuitul se numeste circuit eulerian.
Un graf ce contine un circuit eulerian se numeste graf eulerian orientat.

Definitia 6.10 Un graf se numeste conex dac


a pentru orice pereche de v
arfuri x si y exista
un lant Lxy de la x la y.

Definitia 6.11 Un graf orientat este complet dac


a oricare dou
a v
arfuri sunt adiacente.

Metodele utilizate pentru reprezentarea unui graf neorientat se pot adapta n mod natural
pentru reprezentarea unui graf orientat, nlocuind notiunea de muchie cu cea de arc.

128
Fig. 6.2: Arbore de acoperire n latime pentru graful orientat din figura 6.1

6.2 Parcurgerea grafurilor


In urma vizitarii unui graf neorientat, de obicei nu rezulta numai un arbore de acoperire ci o
padure de arbori de acoperire.
In urma unei parcurgeri n latime a unui graf orientat, se ntalnesc urmatoarele tipuri de
arce:

arc al arborelui de acoperire - arcul (u, v) este un arc al arborelui de acoperire n latime.

arc de ntoarcere - arcul (u, v) se numeste arc de ntoarcere daca are sensul contrar unui
drum de la v la u n arborele de acoperire (v u) (se spune ca u este un descendent
al lui v si v este un stramos al lui u).

arc de traversare - arcul (u, v) este un arc de traversare daca v nu este nici descendent
direct, nici stramos al lui u.
In figura 6.2 este prezentat arborele de acoperire n latime corespunzator grafului din
figura 6.1 avand radacina 1: arcele (2, 1), (7, 5), (6, 4) sunt arce de ntoarcere, (5, 2), (7, 6)
sunt arce de traversare, iar celelalte sunt arce ale arborelui de acoperire (de exemplu (1, 3),
(6, 8)).
Vom prezenta schita unui algoritm mai general pentru parcurgerea unui graf (a se vedea
algoritmul 46). Pentru aceasta se utilizeaza doua multimi de noduri, V izitat si Neexplorat,
unde V izitat reprezinta multimea nodurilor vizitate iar Neexplorat (Neexplorat V izitat)
reprezinta multimea nodurilor vizitate dar neexplorate (noduri ce prezinta vecini nca nevizitati).
In urma unei parcurgeri n adancime a unui graf orientat, fiecare arc din multimea arcelor
poate fi ncadrat ntr-unul dintre tipurile urmatoare:

arc al arborelui de acoperire - arcul (u, v) este un arc al arborelui de acoperire daca
call
df s(u) apeleaza df s(v) (df s(u) df s(v)).

arc de naintare - arcul (u, v) este un arc de naintare daca este paralel cu un drum de
la u la v din arborele de acoperire (u v) (nu face parte din arborele de acoperire).

129
Algoritm 46 Algoritm de vizitare a unui graf (model general)

( ParcurgereGraf(u, G)
1: procedure
u - v arful de unde se porneste vizitarea
Input:
G - graful
2: V izitat {u}
3: N eexplorat V izitat
4: while (N eexplorat 6= ) do
5: extrage un nod x din N eexplorat
6: determin a y, urmatorul vecin al lui x ce nu a fost vizitat
7: if (y = N U LL) then
8: elimina x din N eexplorat
9: else
10: if (y
/ V izitat) then
11: V izitat V izitat {y}
12: N eexplorat N eexplorat {y}
13: end if
14: end if
15: end while
16: end procedure

arc de ntoarcere - arcul (u, v) se numeste arc de ntoarcere daca are sensul contrar unui
drum de la v la u din arborele de acoperire (v u).

arc de traversare - arcul (u, v) este un arc de traversare daca df s(v) a fost apelat si s-a
terminat nainte de apelul lui df s(u).

Fig. 6.3: Arbore de acoperire n adancime pentru graful orientat din figura 6.1

Pentru fiecare nod v al unui graf vom introduce doua numere, prenumv si postnumv ,
numere ce depind de ordinea n care sunt ntalnite nodurile acestuia n timpul vizitarii n
adancime: prenumv marcheaza momentul cand este ntalnit nodul v pentru prima oara

130
iar postnumv momentul n care prelucrarea nodului v s-a ncheiat. Variabila counter este
initializata cu valoarea 1:
1: procedure Prenum(v)
2: prenumv counter
3: counter counter + 1
4: end procedure
si
1: procedure Postnum(v)
2: postnumv counter
3: counter counter + 1
4: end procedure
Un arc (u, v) va avea urmatoarele proprietati, n functie de una din cele patru categorii
de arce introduse anterior n care se ncadreaza:

1. arc al arborelui de acoperire: prenumu < prenumv si postnumu > postnumv ;

2. arc de naintare: prenumu < prenumv si postnumu > postnumv ;

3. arc de ntoarcere: prenumu > prenumv si postnumu < postnumv ;

4. arc de traversare: prenumu > prenumv si postnumu > postnumv .

Arborele de acoperire n adancime corespunzator grafului din figura 6.1 ilustreaza aceste
tipuri de arce: arc de naintare (1, 4), arce de ntoarcere - (2, 1), (7, 5), (4, 6) arc de traver-
sare - (5, 2), arce ale arborelui de acoperire - (1, 2), (1, 3), (3, 5), (5, 7), (7, 6), (6, 8), (6, 4) (a
se vedea figura 6.3).

Algoritm 47 Algoritm de vizitare n adancime pentru un graf orientat


1: procedure DFSNum(k, n, V ecin)

k
- nodul curent vizitat
Input: n - numarul de noduri din graf


V ecin - matricea de adiacent a a grafului
2: vizitatk 1 marcarea nodului curent ca fiind vizitat
3: call P renum(k)
4: call V izitare(k) vizitarea nodului curent
5: for i 1, n do
6: if (vizitati = 0) (vecink,i = 1) then
7: call DF SN um(i, n, V ecin) apelul recursiv al subrutinei DF SN um pentru nodul
i
8: end if
9: end for
10: call P ostnum(k)
11: end procedure

Lema 6.5 Fiind dat un graf orientat G si dou a noduri oarecare u, v G ce apartin aceluiasi
arbore de acoperire n adancime rezultat n urma parcurgerii cu metoda DF S a grafului G
avem:

1. daca (u, v) este un arc al arborelui de acoperire sau un arc de naintare sau un arc de traver-
sare postnumu > postnumv ;

131
2. daca (u, v) este un arc de ntoarcere postnumu < postnumv .

a noduri u, v G avem:
Lema 6.6 Fiind dat un graf orientat G pentru oricare dou

1. v este un descendent al lui u n padurea de arbori de acoperire rezultati n urma vizitarii n


adancime a grafului G intervalul [prenumv , postnumv ] este inclus n intervalul [prenumu ,
postnumu ];
2. nu exista nici o legatura ntre u si v n p
adurea de arbori de acoperire rezultati n urma
vizitarii n adancime a grafului G intervalele [prenumu , postnumu ] si [prenumv , postnumv ]
sunt disjuncte;
3. situatii prenumu < prenumv < postnumu < postnumv sau prenumv < prenumu < postnumv
< postnumu nu sunt posibile.

Lema 6.7 Fiind dat un graf neorientat G, dac a noduri oarecare u, v G avem
a pentru dou
relatia prenumu < prenumv < postnumu atunci:

prenumu < prenumv < postnumv < postnumu ;


exista un drum de la u la v n G.

Exemplul 6.8 In urma apelarii procedurii DFSNum(1, 8, Vecin) (a se vedea algoritmul


47), secventa de apeluri recursive ale procedurii DFSNum este ilustrata prin arborele de acoperire
n adancime din figura 6.3.
Numerotarea nodurilor n preordine si postordine, rezultat
a n urma vizitarii este urmatoarea:

1 2 3 4 5 6 7 8
prenum 1 2 4 8 5 6 7 10
postnum 16 3 15 9 14 13 12 11
Pentru arcul (1, 4) avem prenum1 = 1, postnum1 = 16, prenum4 = 8 si postnum4 = 9.
Deoarece relatia prenumu < prenumv < postnumu este adev arat
a conform lemei 6.7 ar
trebui sa avem prenumu < prenumv < postnumv < postnumu si s a existe un drum de la u
la v, lucruri care sunt adevarate (prenum1 < prenum4 < postnum4 < postnum1 si exista un
drum de la 1 la 4 n arborele de acoperire n ad
ancime).

6.3 Sortarea topologic


a

Fig. 6.4: Un graf orientat aciclic

132
Definitia 6.12 Un graf orientat si care nu posed
a circuite se numeste graf orientat aciclic
(directed acyclic graph - DAG).
Lema 6.9 Intr-un graf orientat aciclic, dac
a prenumu < prenumv si exist
a un drum de la
u la v n G, atunci prenumu < prenumv < postnumv < postnumu .
In practica, exista mai multe situatii n care o multime de activitati sau sarcini, trebuie
organizate ntr-o anumita ordine, ntre acestea existand, de obicei, o multime de restrictii sau
dependente. In activitatea de construire a unei cladiri, anumite activitati nu pot fi ncepute
decat dupa finalizarea altor activitati: spre exemplu, nu se poate ncepe ridicarea peretilor
unei constructii decat dupa turnarea fundatiei si finalizarea structurii de rezistenta, nu se
poate realiza instalatia electrica daca nu au fost ridicate zidurile, s.a.m.d.
Sau daca un student doreste sa si alcatuiasca un plan de studiu individual ce va contine
pe langa cursurile obligatorii, si o serie de cursuri optionale, va trebui sa tina cont de anul de
studiu n care se preda fiecare materie, precum si de cerintele obligatorii ale acestora: un curs
nu poate fi inclus n planul de studiu individual al unui student decat daca acesta a parcurs
si a obtinut creditele la toate materiile anterioare cerute explicit n programa cursului.

Algoritm 48 Algoritm de sortare topologica a unui graf orientat (prima varianta)

( SortTop1(n, V ecin)
1: procedure
n - num
arul de noduri din graf
Input:
V ecin - vector ce contine listele cu vecini ai fiec
arui nod
2: for k 1, n do
3: dminusk 0
4: end for
5: for (u, v) E do
6: dminusv = dminusv + 1
7: end for
8: Q se initializeaz
a coada
9: for k 1, n do
10: if (dminusk = 0) then
11: Qk se insereaza ntro coad
a nodurile cu gradul interior 0
12: end if
13: end for
14: while (Q 6= ) do
15: Qk se extrage din coad a un nod
16: Lk se insereaz a nodul ntr-o list
a
17: w V ecink v ia valoarea capului listei de vecini a nodului k
18: while (w 6= N U LL) do
19: dminusw.nodeIndex dminusw.nodeIndex 1
20: if (dminusw.nodeIndex = 0) then
21: Q w.nodeIndex se insereaza n coada nodul w.nodeIndex
22: end if
23: w w.next se trece la urm atorul vecin
24: end while
25: end while
26: end procedure

Dependenta dintre doua activitati A si B o putem modela prin introducerea a doua noduri
n graf xi si xj , asociate celor doua activitati. Daca activitatea A trebuie realizata naintea
activitatii B, atunci se adauga arcul (xi , xj ).

133
Definitia 6.13 Se numeste sortare topologic a pentru un graf orientat G = (V, E) o or-
donare {x1 , x2 , . . . , xn } a nodurilor grafului astfel nc
at pentru orice arc (xi , xj ) sa avem
i < j.
Prin urmare o sortare topologica presupune aranjarea liniara a varfurilor unui graf astfel
ncat toate arcele sale sa fie orientate de la stanga la dreapta.
Lema 6.10 Daca un graf orientat G admite o sortare topologic
a atunci G este aciclic.
Lema 6.11 Daca un graf orientat G este aciclic atunci el admite o sortare topologica.
Observatia 6.12 Intr-un graf orientat aciclic exist a cel putin un nod al c
arui grad interior
este 0 (graful nu poseda arce care sa aib
a nodul v drept extremitate final
a).
Pornind de la aceasta observatie se schiteaza urmatorul algoritm [78]: ntr-un graf G se
determina un nod v astfel ncat gradul sau interior sa fie zero (d (v) = 0); se adauga acest
nod la o lista ce va contine ordonarea topologica, se sterge nodul din graf mpreuna cu toate
arcele ce l au ca extremitate initiala, si se reia algoritmul pentru graful G = (V , E ), unde
V = V \ {v}, E = E|V V .
Algoritmul 48 se termina n cel mult n pasi (|V | = n). Daca se termina mai devreme,
atunci graful G nu este aciclic (la un moment dat nu mai exista nici un nod v astfel ncat
d (v) = 0).

Fig. 6.5: Sortare topologica cu algoritmul 48 pentru graful orientat din figura 6.4

Exemplul 6.13 Fie graful orientat din figura 6.5. V arful 3 are d (v) = 0. Se adauga acest
nod la lista finala ce va contine nodurile ordonate, se sterge nodul mpreun a cu arcele care l

au drept extremitate initiala. In graful rezultat, v arful 1 are proprietatea ca d (v) = 0. Se
adauga la lista de rezultate, si se elimina din graf mpreun a cu arcele ce pleac a din el. Se
continua procedeul pana cand graful devine vid (ntregul proces poate fi urmarit n figura 6.5).

134
Algoritm 49 Algoritm de sortare topologica a unui graf orientat (a doua varianta)

( SortTop2(n, V ecin)
1: procedure
n - numarul de noduri din graf
Input:
V ecin - matricea de adiacenta a grafului
2: for k 1, n do
3: prenumk 0, postnumk 0
4: vizitatk 0
5: end for
6: for k 1, n do
7: if (vizitatk = 0) then
8: call DF SN um(k, n, V ecin)
9: end if
10: end for
11: se ordoneaz a descresc
ator nodurile dupa postnumk
12: end procedure

Lema 6.14 Un graf orientat G este aciclic dac a si numai daca n urma unei vizitari n
adancime a acestuia nu este ntalnit nici un arc de ntoarcere.

Ideea algoritmului 49 o reprezinta lema 6.14[118].

Exemplul 6.15 Sa consideram ca date de intrare pentru algoritmul 49 graful orientat din
figura 6.4. Dupa etapa de initializare (liniile 2 - 5) avem:

1 2 3 4 5 6 7
prenum 0 0 0 0 0 0 0
postnum 0 0 0 0 0 0 0
vizitat 0 0 0 0 0 0 0
Se apeleaza mai ntai DFSNum(1, 7, Vecin). Secventa rezultata de apeluri recursive este
urmatoarea: DFSNum(1, 7, Vecin) DFSNum(2, 7, Vecin) DFSNum(4, 7, Vecin)
DFSNum(5, 7, Vecin) DFSNum(7, 7, Vecin) DFSNum(6, 7, Vecin).
In urma acestei secvente valorile vectorilor prenum si postnum sunt urmatoarele:

1 2 3 4 5 6 7
prenum 1 2 0 3 4 6 5
postnum 12 11 0 10 9 7 8
vizitat 1 1 0 1 1 1 1
Mai ramane nevizitat un singur nod, 3, drept pentru care vom mai avea un apel
DFSNum(3, 7, Vecin) din procedura principal a SortTop2():

1 2 3 4 5 6 7
prenum 1 2 13 3 4 6 5
postnum 12 11 14 10 9 7 8
vizitat 1 1 1 1 1 1 1
Ordonarea descrescatoare a nodurilor multimii V dup
a valorile vectorului postnum, con-
duce la urm
atoarea configuratie:

135
postnum 14 12 11 10 9 8 7
3 1 2 4 5 7 6
Astfel, sortarea topologica a nodurilor grafului obtinut
a n urma aplic
arii algoritmului 49
este: 3, 1, 2, 4, 5, 7, 6.

Observatia 6.16 O optimizare a algoritmului 49 se refer a la adaugarea nodului v ntr-o


stiva atunci cand se termina vizitarea acestui nod v si se calculeaz
a postnumv . Aceasta stiva
va contine la final nodurile grafului n ordinea descresc atoare a valorilor postnumv .
Prin urmare, nu mai este nevoie s a ordonam descresc ator valorile vectorului postnum
pentru a obtine o sortare topologica a nodurilor grafului G, iar complexitatea algoritmului 49
devine O(|V | + |E|).

6.4 Componente tare conexe

Fig. 6.6: Graf orientat. Componente tare conexe.

Fie G = (V, E) un graf orientat unde V = {1, 2, . . . , n} este multimea nodurilor si E este
multimea arcelor (E V V ).

Definitia 6.14 O component a tare conex a a unui graf orientat G este o multime maxi-
mala de varfuri U V , astfel ncat, pentru fiecare pereche de v
arfuri {u, v} (u, v U) exista
atat un drum de la u la v cat si un drum de la v la u. Prin urmare se spune c a varfurile u
si v sunt accesibile unul din celalalt.

Un graf orientat ce are o singura componenta tare conexa astfel ncat U = V este un graf
tare conex. In cazul n care un graf orientat nu este tare conex atunci el se poate descompune
n mai multe componente tare conexe. O astfel de componenta este determinata de un subgraf
al grafului initial.

Definitia 6.15 Graful orientat G se numeste tare conex dac a pentru orice pereche de
varfuri u 6= v, exista un drum de la nodul u la nodul v si un drum de la nodul v la nodul u.

Se poate arata ca relatia de tare conexitate este o relatie de echivalenta. O relatie (notata
cu ) este o relatie de echivalenta daca prezinta proprietatile de reflexivitate, simetrie si
tranzitivitate:

136
reflexivitate: a a;

simetrie: a b b a;

tranzitivitate: a b, b c a c.

O clasa de echivalenta este multimea tuturor elementelor care se afla n relatia ([x] =
{y|x y}). Relatia de echivalenta determina o partitie n clase de echivalenta a multimii
peste care a fost definita. Prin urmare relatia de tare conexitate determina o partitie a
multimii V . Clasele de echivalenta determinate de relatia de tare conexitate sunt componen-
tele tare conexe.
Pentru determinarea componentelor tare conexe exista mai multi algoritmi, dintre care
amintim algoritmul lui Tarjan, algoritmul lui Kosaraju si algoritmul lui Gabow.

6.4.1 Algoritmul lui Kosaraju


Algoritmul lui Kosaraju-Sharir a fost prezentat de Aho, Hopcroft si Ullman n lucrarea lor [2],
fiind preluat dintr-un manuscris al lui S. Rao Kosaraju (M. Sharir l-a prezentat n lucrarea
[110]).
Algoritmul foloseste graful transpus GT asociat grafului initial G ([11], [23], [30]), fiind
compus din urmatorii pasi:

Pas 1. Se realizeaza parcugerea grafului cu algoritmul de vizitare n adancime, pornind de la


un nod arbitrar. In timpul vizitarii, se realizeaza numerotarea n postordine a nodurilor
n cadrul vectorului postnum.

Pas 2. Se obtine un nou graf GT = (V, E T ) prin inversarea sensului arcelor grafului G (E T =
{(u, v)|(v, u) E, u, v V }).

Pas 3. Se cauta nodul nevizitat din graful GT ce are cel mai mare numar atribuit n urma
parcurgerii de la Pasul 1. Din acest nod se initiaza parcugerea grafului cu algoritmul
de vizitare n adancime.

Pas 4. Daca mai raman noduri nevizitate atunci se reia Pasul 3, altfel algoritmul se termina.

Fiecare arbore rezultat n urma parcurgerii de la Pasul 3 constituie o component


a tare conexa
a grafului G.
In continuare se prezinta implementarea n limbajul C a algoritmului anterior:

Listing 6.1: kosaraju.c


#include <s t d i o . h>
#include <mem. h>

#define MAX 100


#define TRUE 1
#define FALSE 0

char v e c i n [MAX] [MAX] ; // M a t r i c e a de a d i a c e n t a


char v i z i t a t [MAX] ; // Vecto r c e p a s t r e a z a s t a r e a unui nod : v i z i t a t sau n e v i z i t a t .
i nt n ; // Numarul de v a r f u r i d i n g r a f

i nt nump ;
i nt postnum [MAX] ; // Numarul a s o c i a t f i e c a r u i v a r f l a v i z i t a r e a i n p o s t o r d i n e .

137
void d f s ( i nt k ) {
i nt i ;

v i z i t a t [ k ] = TRUE;
for ( i = 0 ; i < n ; i ++)
i f ( ( v i z i t a t [ i ] == FALSE) && ( v e c i n [ k ] [ i ] > 0 ) )
dfs ( i ) ;
nump++;
postnum [ k ] = nump ;
}

void d f s 1 ( i nt k ) {
i nt i ;

v i z i t a t [ k ] = TRUE;
for ( i = 0 ; i < n ; i ++)
i f ( ( v i z i t a t [ i ] == FALSE) && ( v e c i n [ k ] [ i ] > 0 ) )
dfs1 ( i ) ;
p r i n t f ( %d , k ) ;
}

void r e a d I n p u t ( void ) {
i nt i , j ;

p r i n t f ( n = ) ; s c a n f ( %d , &n ) ;
do{
p r i n t f ( nod1 nod2 : ) ; s c a n f ( %d %d , &i , &j ) ;
i f ( i >= 0 )
vecin [ i ] [ j ] = 1;
} while ( i >= 0 ) ;
}

void main ( void ) {


i nt i , j , k , tmp ;
i nt maxim ;
i nt nod ;

readInput ( ) ;
// prima eta pa
memset ( v i z i t a t , 0 , s i z e o f ( v i z i t a t ) ) ;
nump = 0 ;
for ( i = 0 ; i < n ; i ++)
i f ( v i z i t a t [ i ] == FALSE)
dfs ( i ) ;
// eta pa a doua
memset ( v i z i t a t , 0 , s i z e o f ( v i z i t a t ) ) ;
for ( i = 0 ; i < n ; i ++)
for ( j = i ; j < n ; j ++) {
tmp = v e c i n [ i ] [ j ] ;
vecin [ i ] [ j ] = vecin [ j ] [ i ] ;
v e c i n [ j ] [ i ] = tmp ;
}
k = 0;
while (TRUE) {
maxim = 0 ;
for ( i = 0 ; i < n ; i ++)
i f ( ( v i z i t a t [ i ] == FALSE) && ( maxim < postnum [ i ] ) ) {
maxim = postnum [ i ] ;
nod = i ;

138
}
i f ( maxim == 0 )
break ;
k++;
p r i n t f ( Componenta %d : , k ) ;
d f s 1 ( nod ) ;
p r i n t f ( \n ) ;
}
}

Exemplul 6.17 Dupa parcurgerea n ad


ancime a grafului 6.6, valorile vectorilor vizitat si
postnum sunt urmatoarele:

1 2 3 4 5 6 7 8
postnum 8 7 5 6 1 2 3 4
vizitat 1 1 1 1 1 1 1 1

Fig. 6.7: Graful transpus GT corespunzator grafului din figura 6.6

Se construieste graful transpus, GT (a se vedea figura 6.7). Se caut a primul nod u nca
nevizitat (vizitatu = 0), caruia i corespunde cea mai mare valoare postnumu . In acest mod,
se identifica componenta tare conexa compus a numai din nodul 1: {1}.
Urmatorul nod, ca valoarea a vectorului postnum ordonat descresc ator, este 2. Din nodul
2 se parcurg nodurile 3 si 4, rezultand o alt
a component a tare conexa: {2, 3, 4}.
Urmatorul nod nevizitat u ce are valoarea prenumu maxim a este nodul 8. Se identifica
mai ntai componenta tare conexa {7, 8} si apoi {5, 6}.

Analiza algoritmului
Algoritmul lui Kosaraju realizeaza doua parcurgeri ale tuturor elementelor grafului G: prima
data parcurge graful G si a doua oara parcurge graful GT . Prin urmare, complexitatea
timp a algoritmului este (|V | + |E|) n cazul n care graful este reprezentat prin liste de
adiacenta si este patratica n |V |2 (O(|V |2 )) atunci cand graful este reprezentat prin matricea
de adiacenta.

6.4.2 Algoritmul lui Tarjan


Algoritmul lui Tarjan [117] este considerat drept o mbunatatire a algoritmului lui Kosaraju
prin aceea ca nu mai este nevoie de parcurgerea grafului G de doua ori.

139
Algoritmul initiaza o parcurgere n adancime a nodurilor grafului G pornind de la un nod
oarecare. O componenta tare conexa a grafului G, daca exista, constituie un subarbore al
arborelui de acoperire n adancime, iar radacina acestui subarbore este reprezentantul clasei
de echivalenta.
Prin urmare componentele tare conexe se obtin prin descompunerea arborelui/arborilor
de acoperire n adancime daca eliminam anumite arce ale acestora. Un nod este capul unei
componente tare conexe (sau radacina), daca acesta constituie radacina subarborelui core-
spunzator componentei. Arcul ce are nodul-cap drept extremitate finala este cel ce trebuie
eliminat. Dupa ce determinam toate nodurile-cap, subarborii arborelui/arborilor de acoperire
n adancime ce i au drept radacini, sunt componentele tare conexe.
Algoritmul lui Tarjan are drept scop determinarea nodurilor-cap. Pentru a pastra o ordine
a vizitarii acestora pe parcursul algoritmului, nodurile vor fi adaugate pe o stiva. Ele vor fi
extrase din stiva n momentul n care procedura de vizitare DFS a unui nod este ncheiata: se
determina daca nodul curent este radacina unei componente tare conexe, si, n caz afirmativ,
toate nodurile care au fost vizitate din nodul curent n cadrul parcurgerii n adancime sunt
marcate ca fiind elemente ale componentei tare conexe.
Vom numerota toate nodurile grafului n preordine (altfel spus, acest numar indica pen-
tru nodul curent numarul de noduri vizitate naintea sa), valorile fiind pastrate n vectorul
prenum (cititorul este invitat sa revada si sectiunea despre Muchie critica, cea de-a doua
solutie). Pentru un nod u G definim lowu astfel:

prenumu

lowu = min prenumx , daca [u, x] este arc de ntoarcere sau de traversare si x S


lowy , y descendent direct al lui u

Daca dintr-un varf u G exista un arc de ntoarcere sau de traversare catre un nod v G
n afara subarborelui de vizitare n adancime determinat de u, atunci acest nod v trebuie sa
fi fost vizitat naintea lui u (prenumu > prenumv ). Daca un astfel de nod nu exista atunci
lowu = prenumu . Stiva S pastreaza nodurile grafului, pe masura ce sunt vizitate, ele fiind
adaugate la S. Daca u este un nod-cap (lowu = prenumu ) se extrag din stiva toate nodurile
dintre varful stivei si u inclusiv: acestea formeaza o componenta tare conexa.
Se observa ca algoritmul lui Tarjan (algoritmul 50) seamana foarte mult cu algoritmul 22
de determinare a muchiei critice (varianta a II-a).

Exemplul 6.18 La nceput valorile vectorilor vizitat si prenum sunt urm


atoarele:

1 2 3 4 5 6 7 8
prenum 0 0 0 0 0 0 0 0
vizitat 0 0 0 0 0 0 0 0
Primul element nevizitat este nodul 1 (linia 9), prin urmare se apeleaz
a DFSTarjan(1,
8, Vecin). Rezulta o secventa de apeluri recursive:
DFSNum(1, 8, Vecin) DFSNum(2, 8, Vecin) DFSNum(3, 8, Vecin)
DFSNum(6, 8, Vecin) DFSNum(5, 8, Vecin).

1 2 3 4 5 6 7 8
prenum 1 2 3 0 5 4 0 0
vizitat 1 1 1 0 1 1 0 0
low 1 2 0 0 4 4 0 0
arful stivei fiind n dreapta): {1, 2, 3, 6, 5}.
Stiva contine urmatoarele elemente (v

140
Algoritm 50 Algoritmul lui Tarjan pentru determinarea componentelor tare conexe

( Tarjan(n, V ecin)
1: procedure
n - numarul de noduri din graf
Input:
V ecin - matricea de adiacenta
2: for i 1, n do
3: vizitati 0
4: end for
5: counter 0
6: S
7: for i 1, n do
8: if (vizitati = 0) then
9: call DF ST arjan(i, n, V ecin) vizitarea n ad
ancime a grafului
10: end if
11: end for
12: end procedure
13: procedure DFSTarjan(k, n, V ecin)
14: Sk
15: vizitatk 1
16: counter counter + 1
17: prenumk counter, lowk counter
18: for i 1, n do
19: if (vecink,i = 1) then
20: if (vizitati = 0) then
21: call DF ST arjan(i, n, V ecin)
22: lowk M in(lowk , lowi )
23: else
24: if (i S) then
25: lowk M in(lowk , prenumi )
26: end if
27: end if
28: end if
29: end for
30: if (lowk = prenumk ) then
31: repeat
32: Su
33: Output u
34: until (u = k)
35: end if
36: end procedure

low5 = min{prenum6 , low5 } = min{4, 5} = 4 (deoarece desi vizitat6 = 1, avem 6 S,


adica nodul 6 se afla pe stiva). Astfel n momentul termin arii apelului DFSTarjan(5, 8,
Vecin), low5 = 4.
Acum la nivelul lui DFSTarjan(6, 8, Vecin), n urma revenirii din DFSTarjan(5, 8,
Vecin), se calculeaza low6 = min{low6 , low5 } = min{4, 4} = 4 (linia 22).
Nodul 6 este un nod-cap (low6 = prenum6 ) (vezi linia 30), si prin urmare se extrag de pe
stiva toate elementele dintre varful stivei si elementul 6 inclusiv, rezultand prima componenta
tare conexa: {5, 6}. Stiva ramane cu elementele (v arful stivei fiind n dreapta) {1, 2, 3}.
Din nodul 3 se continua cu vizitarea nodului 8, nc a nevizitat:

141
DFSNum(3, 8, Vecin) DFSNum(8, 8, Vecin) DFSNum(7, 8, Vecin).
Continutul stivei este {1, 2, 3, 8, 7}.
La nivelul apelului DFSNum(7, 8, Vecin) nu se ia n considerare la calculului valorii lui
low7 nodul 6: desi exista arcul (7, 6), nodul 6 a fost vizitat (vizitati 6= 0) si nu se mai afla
pe stiva (i
/ S). Astfel low7 = min{low7 , prenum8 } = min{7, 6} = 6.
La nivelul lui DFSNum(8, 8, Vecin), low8 = min{low8 , low7 } = min{6, 6} = 6 (linia
22). Avem ca (low8 = prenum8 ) (linia 30) si prin urmare 8 este un nod-cap. Se extrag de pe
stiva toate elementele dintre varful stivei si elementul 8 inclusiv, rezult
and a doua componenta
tare conexa: {7, 8}.
Stiva ramane cu elementele (varful stivei fiind n dreapta) {1, 2, 3}.

1 2 3 4 5 6 7 8
prenum 1 2 3 0 5 4 7 6
vizitat 1 1 1 0 1 1 1 1
low 1 2 0 0 4 4 6 6
In momentul terminarii apelului DFSTarjan(3, 8, Vecin), low3 = 2.
Revenind la nivelul apelului DFSTarjan(2, 8, Vecin), se calculeaz a low2 = min{low2 , low3 } =
min{2, 2} = 2, si se continua cu urmatorul nod nca nevizitat, 4: DFSNum(2, 8, Vecin)
DFSNum(4, 8, Vecin).
Se adauga nodul 4 pe stiva (stiva devine {1, 2, 3, 4}), low4 = prenum4 = 8.
low4 se calculeaza din low4 = min{low4 , prenum3 } = min{8, 3} = 3 (exist a arcul (4, 3),
nodul 3 a fost vizitat - vizitat3 = 1 si 3 S - nodul 3 se afla pe stiv
a).
1 2 3 4 5 6 7 8
prenum 1 2 3 8 5 4 7 6
vizitat 1 1 1 1 1 1 1 1
low 1 2 2 3 4 4 6 6
Ne ntoarcem la nivelul apelului DFSTarjan(2, 8, Vecin), low2 = min{low2 , low4 } =
min{2, 3} = 2. Arcul (2, 5) nu este luat n considerare la calculul lui low2 deoarece nodul 5 a
fost vizitat (vizitat5 = 1) si 5 nu se reg
aseste pe stiv
a. Astfel low2 r
amane cu valoarea 2.
Deoarece low2 = prenum2 , extragem de pe stiv a elementele ce determina cea de-a treia
componenta tare conexa: {4, 3, 2}. Pe stiv a mai r amane un singur element, {1}, ce va
determina ultima componenta tare conex a.
In final, valorile low si prenum sunt urm atoarele:

1 2 3 4 5 6 7 8
prenum 1 2 3 8 5 4 7 6
vizitat 1 1 1 1 1 1 1 1
low 1 2 2 3 4 4 6 6

6.4.3 Algoritmul lui Gabow


Algoritmul a fost introdus de catre Joseph Cheriyan and Kurt Mehlhorn n 1996 [28] si apoi
independent de catre Harold Gabow n 1999 [56].
Algoritmul construieste un graf H [56] ce reprezinta o contractie a grafului original G: unul
sau mai multe noduri din G pot sa corespunda unui nod din H. La nceput, se initializeaza
H = G. Se ncepe construirea unui drum P alegandu-se un nod oarecare v H.
La fiecare pas al algoritmului se ncearca sa se augmenteze drumul P = P {w} =
[v1 , . . . , vk , w] prin parcurgerea tuturor arcelor (vk , w):

142
Fig. 6.8: Drumul P n algoritmul lui Gabow pentru graful din figura 6.6

- daca w
/ P , atunci se adauga nodul w la drumul P (P = [v1 , . . . , vk , w]);

- daca w P , fie w = vj . Contractam circuitul {vj , vj+1 , . . . , vk , w} n graful H: n


locul multimii de varfuri {vj , vj+1, . . . , vk } va ramane doar reprezentantul acesteia, e.g.
nodul ce are valoarea minima n urma numerotarii n preordine;

- daca nu avem nici un arc neverificat care sa aiba nodul vk ca extremitate initiala, se
marcheaza nodul vk ca apartinand unei componente conexe. Se sterge nodul vk din
graful H si din drumul P , mpreuna cu toate arcele ce l au ca extremitate. Daca
P 6= atunci se continua algoritmul ncercandu-se augmentarea drumului P . In caz
contrar, se ncearca initializarea unui drum nou P n H.

Si acest algoritm are la baza metoda de parcurgere n adancime a unui graf (DFS). In
timpul vizitarii, algoritmul utilizeaza doua stive S si P : S contine toate nodurile ce nu au
fost atribuite nca unei componente tare conexe, n ordinea n care au fost ntalnite, iar P
contine toate nodurile despre care nca nu se poate spune nimic cu privire la apartenenta lor
la componente conexe diferite (contine nodurile drumului P din graful H). Altfel spus, se
observa faptul ca stiva P pastreaza nodurile r
adacina ce formeaza o subsecventa a secventei
nodurilor ce se afla la un moment dat n cealalta stiva S.
In cadrul secventei urmatoare, pentru un nod i adiacent cu nodul curent k, se verifica
daca nu a mai fost vizitat. Daca vizitati = 0 atunci se continua vizitarea n adancime
(call DFSGabow(i, n, Vecin)). Altfel, se verifica daca nodul i nu a fost deja asignat unei
componente tare conexe, n caz afirmativ eliminandu-se din drumul P circuitul {vi , . . . , vk , vi }:
1: if (vizitati = 0) then
2: call DF SGabow(i, n, V ecin)
3: else
4: if (i S) then i
5: while (prenumi < prenumpeek(P )) do
6: P u
7: end while
8: end if
9: end if
La finalul procedurii DFSGabow, se verifica daca nodul curent este identic cu cel aflat n
varful stivei P . In caz afirmativ, se extrag de pe stiva toate nodurile dintre varful curent

143
al stivei S si nodul k, si se marcheaza ca fiind elemente ale componentei tare conexe a carei
radacina este nodul k:
if (k = peek(P )) then
P u
repeat
Su
Output u
until (u = k)
end if

Algoritm 51 Algoritmul lui Gabow pentru determinarea componentelor tare conexe


1: procedure Gabow(n, V ecin)
2: for i 1, n do
3: vizitati 0
4: end for
5: counter 0
6: S , P
7: for i 1, n do
8: if (vizitati = 0) then
9: call DF SGabow(i, n, V ecin)
10: end if
11: end for
12: end procedure
13: procedure DFSGabow(k, n, V ecin)
14: vizitatk 1
15: S k, P k
16: counter counter + 1
17: prenumk counter
18: for i 1, n do
19: if (vecink,i = 1) then
20: if (vizitati = 0) then
21: call DF SGabow(i, n, V ecin)
22: else
23: if (i S) then i nu a fost asignat nc
a unei componente tare conexe
24: while (prenumi < prenumpeek(P )) do
25: P u
26: end while
27: end if
28: end if
29: end if
30: end for
31: if (k = peek(P )) then
32: P u
33: repeat
34: Su
35: Output u
36: until (u = k)
37: end if
38: end procedure

144
6.5 Exercitii
1. (Compilator ) In trecut compilatoarele erau foarte simple. In acele vremuri oamenii
preferau sa includa tot programul ntr-un singur fisier. Daca cineva facea o modificare n
program, trebuia recompilat tot codul sursa. Cresterea lungimii programelor conducea
la timpi de compilare tot mai mari, ceea ce constituia o piedica n ceea ce priveste
cresterea complexitatii algoritmilor implementati.
De aceea programatorii au dezvoltat o tehnica pentru eliminarea compilarilor redun-
dante. Ei au nceput prin a sparge programele n mai multe module mici pe care le
compilau separat. Astfel, pentru orice modificare care se opera ntr-un anumit modul,
se compila numai acesta, si nu ntreaga aplicatie. Fiecare modul contine la nceput lista
celorlaltor module pe care le foloseste.
Modulul A trebuie recompilat numai daca a fost modificat sau are n lista sa un modul
B care a fost recompilat la randul sau. In celelalte cazuri nu este necesara recompilarea
modulului A.
Problema cere sa se realizeze un algoritm si pe baza acestuia un program, care sa decida
ce module trebuie recompilate si care nu. Pentru a avea un timp de recompilare minim,
va trebui sa cautam compilarea unui numar cat mai mic de linii.
Prima linie a datelor de intrare contine numarul de module N (1 N 100). Urmeaza
descrierea modulelor. Prima linie a descrierii contine numele modulului. A doua linie
contine numarul liniilor din codul sursa. A treia linie contine numarul M (0 M < N)
de module de care depinde modulul actual. Linia urmatoare contine numele acestor
module, separate printrun spatiu. Numele unui modul nu depaseste 20 de caractere.
Descrierea modulelor este urmata de mai multe blocuri, cate unul pentru fiecare versiune
a programului. Prima linie a fiecarui bloc contine numarul k (1 k N) de module
ce au suferit modificari de la recompilarea versiunii precedente.
Linia urmatoare contine numele modulelor, separate prin spatiu, n care au survenit
modificari. Dupa ultimul bloc exista o singura linie ce contine doar numarul 0.
Pentru fiecare versiune a programului se scrie o linie ce contine numarul liniilor codului
sursa care au trebuit sa fie recompilate.
Intrare Iesire
3 127
MATH
20
0
MAIN
100
2
MATH IO
IO
7
0
3
MATH IO MAIN
0
(Propusa la CEOI 1997)

2. Se da un numar de k (k < 1000) reguli, numerotate de la 1 la k. O regula are forma


x y unde x si y sunt propozitii cu urmatoarea semnificatie: daca propozitia x este
adevarata, atunci proprozitia y este adevarata.

145
Sa consideram pentru k = 5 urmatoarele reguli:
(1) 1 2 (2) 1 3 (3) 3 4 (4) 4 5 (5) 1 5
Profesorul cere sa se demonstreze regula 1 5. Demonstratia optima consta n apli-
carea directa a regulii (5).
(2) (1) (3) (4)
Un elev demonstreaza regula (5) astfel: 2 1 3 4 (1 3, 1 2, 3 4, 4 5). Aceasta
(1)
este o demonstratie corecta, ce contine n plus regula 1 2. Profesorul ar fi fost
(2) (3) (4)
multumit cu demonstratia 2 3 4 (1 3, 3 4, 4 5).
Sa se realizeze un algoritm ce utilizeaza drept date de intrare k, x (propozitia care se
considera adevarata), y (propozitia ce trebuie demonstrata), sirul celor k reguli cat si
sirul numerelor de ordine ale regulilor ce constituie demonstratia elevului.
Algoritmul verifica daca sirul numerelor de ordine ale regulilor constituie o demonstratie.
In caz negativ va afisa NU , iar n caz afirmativ va afisa DA , urmat pe linia urmatoare
de demonstratia elevului din care au fost eliminate afirmatiile inutile. Se considera ca
un sir de reguli fara reguli n plus constituie o demonstratie corecta a propozitiei y
pornind de la propozitia x, daca renuntarea la orice regula ar conduce la o secventa
prin care y nu se mai poate deduce din x.
(Marele premiu PACO, 1997)

3. Dupa cum se poate observa prin reclamele de la televizor, multe companii cheltuiesc
foarte multi bani pentru a convinge oamenii ca ofera cele mai bune servicii la cel mai
scazut pret. O companie de telefoane ofera cercuri de apel (calling circles).
Un abonat poate sa faca o lista cu persoanele pe care le suna cel mai frecvent (si care
constituie cercul sau de prieteni). Daca acesta suna pe cineva inclus n aceasta lista,
si persoana respectiva este, de asemenea, abonata la aceeasi companie, va beneficia de
un discount mai mare decat pentru o convorbire telefonica cu cineva din afara listei.
O alta companie a aflat de aceasta initiativa si se ofera sa determine ea lista de
cunostinte cu care un abonat vorbeste cel mai frecvent la telefon.
LibertyBellPhone este o companie noua de telefoane ce se gandeste sa ofere un plan
de apeluri mult mai avantajos decat alte companii. Ea ofera nu numai reduceri pentru
cercul de apel, cat si determina pentru un abonat acest cerc. Iata cum procedeaza:
compania pastreaza numerele tuturor persoanelor participante la fiecare apel telefonic.
In afara unui abonat, cercul sau de apel consta din toate persoanele pe care le suna si
care l suna, direct sau indirect.
De exemplu, daca Ben l suna pe Alexander, Alexander o suna pe Dolly si Dolly l
suna pe Ben, atunci ei toti fac parte din acelasi cerc. Daca Dolly l mai suna si pe
Benedict iar Benedict o suna pe Dolly, atunci Benedict este n acelasi cerc cu Dolly,
Ben si Alexander. In fine, daca Alexander l suna pe Aron dar Aaron nu l suna pe
Alexander, Ben, Dolly sau Benedict, atunci Aaron nu este n cerc.
Sa se realizeze un algoritm ce determina cercurile de apel, cunoscanduse lista apelurilor
telefonice dintre abonati.
(Finala ACM 1995, Calling Circles)

4. La facultatea X exista doua alternative pentru ca studentii sa aiba timp sa asimileze


cunostintele:

146
(a) Marirea zilei la 30 de ore, si

(b) Reducerea programei scolare.

Optand pentru a doua varianta, Liga Studentilor introduce o platforma program care:

(a) Stabileste care sunt materiile necesare pentru a putea studia o noua materie.
De exemplu, pentru a studia cursul Management este nevoie de cursul Teorie
Economica si de cursul Marketing.

(b) Stabileste care sunt materiile cu adevarat utile dintre toate cele studiate n fac-
ultate. De exemplu, cursul de Masurari electrice nu este util la absolut nimic.

(c) Cere sa se elimine din programa materiile care nu sunt nici folositoare, nici nu
servesc (direct sau indirect) la nvatarea unor materii folositoare.

(d) Cere sa se indice care dintre materii nu pot fi predate n nici o ordine. De ex-
emplu, cursul Mecanica se bazeaza pe cursul Ecuatii Diferentiale, dar cursul
de Ecuatii Diferentiale si preia exemplele din Mecanica. Prin urmare nu ex-
ista nici o ordine n care aceste materii sa fie predate fara a introduce cunostinte
nedemonstrate.

5. Distribuirea cartilor cerute de catre cititorii de la sala de lectura a unei biblioteci este
facuta de catre un robot ce are posibilitatea sa ajunga la orice carte ce poate fi solicitata.
Din pacate, rafturile unde sunt depozitate cartile sunt dispuse astfel ncat robotul nu
poate lua cartile ntrun singur drum. Dupa receptionarea comenzilor de la mai multi
cititori, robotul cunoaste pozitia cartilor n rafturi si drumurile catre acestea. Din
pacate, de la pozitia unei carti, robotul nu se poate deplasa decat spre anumite carti.
Acesta porneste si culege cartile ce sunt accesibile ntrun drum, apoi porneste de la
o alta pozitie de carte si culege acele carti ce sunt accesibile din acel punct si asa mai
departe.
Datele de intrare constau din numarul de carti n precum si numarul m de legaturi
dintre pozitiile acestora (1 n 100, 11 m 10000), urmate de m perechi de
numere naturale, ce semnifica legaturile directe ntre pozitiile cartilor.
Datele de iesire constau din drumurile pe care le face robotul pentru a culege cartile
cerute.

6. Se considera o multime de n elevi dintr-o clasa. Fiecare elev are cunostinte mai avansate
ntr-un anumit domeniu. Pentru ridicarea nivelului clasei, dirigintele vrea sa-i aranjeze
n grupuri astfel ncat toti elevii dintr-un grup sa ajunga sa cunoasca, ntr-o anumita
perioada de timp, toate cunostintele tuturor celorlalti colegi din acelasi grup.
Grupurile de elevi nu si vor schimba cunostintele ntre ele. Se stie ca un elev nu se
poate face nteles de catre oricine. El are o lista de preferinte fata de care el le va
mpartasi cunostintele sale. Relatia de preferinta nu este simetrica.
Sa se determine numarul minim de grupuri n care va fi mpartita clasa precum si
componenta acestora.

147
Fig. 6.9: Pozitiile cartilor ntr-o biblioteca precum si posibilitatile de deplasare ale robotului

148
Capitolul 7

Heap-uri

Un heap (eng. heap = movila) reprezinta o structura de date abstracta organizata sub forma
unei structuri ierarhice (de arbore) si care respecta urmatoarea proprietate: pentru oricare
doua noduri ale arborelui, A si B, astfel ncat tataB = A, avem cheieA cheieB (sau
cheieA cheieB ). Fiecare nod va avea o valoare asociata numita cheie. Se observa faptul ca,
daca este ndeplinita proprietatea ca A, B, tataB = A, cheieA cheieB , atunci ntotdeauna
cheia de valoare maxima se va afla n radacina arborelui.
Structura de date heap poate fi implementata sub mai multe forme: 2 3 heap [116], heap
binar [9], heap binomial [125], heap Fibonacci [54], heap ternar, treap 1 [7], [109].
Principalele operatii definite pentru un heap H sunt:
1. FindMin(H; p) - determina cheia avand cea mai mica valoare si ntoarce o referinta
catre nodul ce o contine;
2. DeleteMin(H) - sterge nodul ce contine cheia de valoare minima si reorganizeaza struc-
tura prin refacerea proprietatii de heap;
3. Insert(H,p) - insereaza nodul p n heap-ul H;

4. Delete(H,p) - sterge nodul p din heap-ul H;

5. DecreaseKey(H,p,v) - modifica valoarea cheii nodului p din heap-ul H, atribuindu-i


valoarea v si reorganizeaza aceasta structura, pastrandu-i proprietatea de heap;

6. MergeHeaps(H1 , H2 , H) - unifica doua heap-uri, desemnate prin H1 si H2 , ntr-una


noua H, ce va contine elementele celor doua heap-uri.
Tabelul 7.1 ([30]) ilustreaza complexitatea-timp a acestor operatii pentru diferite tipuri
de implementari ale structurii de heap.
Structura de date de tip heap prezinta multiple aplicatii dintre care amintim algoritmul
de ordonare heapsort a unei secvente de elemente, diferiti algoritmi din teoria grafurilor
(algoritmi pentru determinarea drumurilor de cost minim) sau algoritmi de selectie (pentru
determinarea valorii minime, maxime, mediane dintr-un vector).

7.1 Heap-uri binare (Min-heapuri sau Max -heapuri)


Definitia 7.1 Un min-heap este un arbore binar complet n care valoarea memorata n
orice nod al sau este mai mica sau egal
a dec
at valorile memorate n nodurile fii ai acestuia.
1
http://acs.lbl.gov/~aragon/treaps.html

149
Table 7.1: Complexitatea-timp a principalelor operatii ale unui heap

Heap binar Heap binomial Heap Fibonacci


FindMin (1) O(log n) (1)
DeleteMin (log n) (log n) O(log n)
Insert (log n) O(log n) (1)
Delete (log n) (log n) O(log n)
DecreaseKey (log n) (log n) (1)
MergeHeaps (n) O(log n) (1)

Fig. 7.1: Un exemplu de heap

Intr-un mod similar se poate defini un max-heap, drept un arbore binar complet n care
valoarea memorata n orice nod este mai mare sau egal
a dec
at valorile memorate n nodurile
fii ai acestuia.

Aceasta structura poate fi interpretata drept un arbore partial ordonat ce este un arbore
binar complet cu n varfuri.
Reprezentarea cea mai adecvata pentru un heap binar (min-heap) este reprezentarea
secventiala. Pentru n noduri vom utiliza un tablou A cu dimensiunea egala cu numarul
de noduri. a1(reprezinta cheia nodului radacina al arborelui.
(
2i daca 2 i n 2i+1 daca 2 i + 1 n
Lef ti = , Righti =
nu exista daca 2 i > n nu exista daca 2 i + 1 > n
(
2i daca i > 1
T atai =
nu exista daca i = 1

Exemplul 7.1 Fie heap-ul binar din figura 7.1. Acesta poate fi descris de urm
atoarea struc-
tura de date secventiala:

Nod 1 2 3 4 5 6 7 8 9 10
cheie 3 5 8 7 8 8 11 10 18 9
Conform definitiei avem:

150
Nod 1 2 3 4 5 6 7 8 9 10
Left 2 4 6 8 10 - - - - -
Right 3 5 7 9 - - - - - -
Tata - 1 1 2 2 3 3 4 4 5

7.1.1 Inserarea unui element


Procedura Insert (a se vedea algoritmul 52) va insera elementul X n heap-ul reprezentat de
A. Se adauga noul element pe ultimul nivel, pe prima pozitie libera ntalnita n parcurgerea
pe nivel de la stanga la dreapta. Se verifica daca este respectata proprietatea structurii de
tip heap de catre configuratia curenta a elementelor si se reorganizeaza aceasta structura, n
cazul n care proprietatea nu este ndeplinita: daca noul element are valoarea etichetei mai
mica decat cea a parintelui sau, atunci face schimb de pozitii cu acesta. Procesul se repeta
pana cand noul element este fie radacina, fie are eticheta mai mare decat cea a parintelui
sau.
Algoritm 52 Algoritm de inserare ntr-un heap
1: procedure Insert(A, last, N, X)
2: if (last n) then
3: Output { Heap plin! }
4: return
5: end if
6: last last + 1
7: alast x adaug
a elementul cel nou pe ultima pozitie
8: i last
9: while ((i > 1) (ai < a i )) do
2
10: ai a i interschimb
a valorile lui ai si a i
2 2
11: i 2i
12: end while
13: end procedure

Fig. 7.2: Inserarea valorii 3 ntr-un heap

151
Exemplul 7.2 In figura 7.2 pot fi urm arite operatiile necesare pentru inserarea unui nod
avand cheia asociata de valoare 3. Se adaug a elementul av and valoarea cheii 3, pe ultima
pozitie a heap-ului. Deoarece valoarea cheii ultimului element 3 este mai mic a dec
at valoarea
cheii parintelui sau 8, se interschimb
a valorile cheilor. Se compar a din nou, valoarea cheii
nodului curent (cheia cu valoarea 3) cu valoarea cheii asociat a p
arintelui s
au (nodul 1). Se
interschimba din nou valorile cheilor. La final, heap-ul arat a ca n ultima configuratie din
figura 7.2.

7.1.2 S
tergerea elementului minim
Procedura DeleteMin (a se vedea algoritmul 53) va elimina nodul radacina din heap, ntorcand
valoarea acestuia prin intermediul variabilei minim, si va reorganiza heap-ul.
In linia 6 se pastreaza valoarea radacinii n variabila minim. Instructiunea urmatoare ia
ultimul element si l aduce pe prima pozitie. In linia 8, se decrementeaza numarul de elemente
existente la momentul respectiv n heap. Apoi se pastreaza n variabila j indicele elementului
ce contine cea mai mica valoare a cheii, dintre cheile corespunzatoare descendentilor nodului
curent (liniile 11-15).
In linia 17 se interschimba pozitia parintelui cu pozitia celui mai mic dintre descendentii
sai, daca este necesar. Instructinea de ciclare while (liniile 10 - 22) se paraseste atunci cand
s-a ajuns la o frunza sau cand nu se mai poate nainta n jos n arbore (linia 20).

Algoritm 53 Algoritm de stergere a elementului minim dintr-un heap


1: procedure DeleteMin(A, last, N ; minim)
2: if (last = 0) then
3: Output { Heap vid! }
4: return
5: end if
6: minim a1
7: a1 alast
8: last last 1
9: i1
10: while (i last
2 ) do
11: if ((2 i = last) (a2i < a2i+1 )) then
12: j 2i
13: else
14: j 2i+1
15: end if
16: if (ai > aj ) then
17: ai aj
18: ij
19: else
20: return
21: end if
22: end while
23: end procedure

In figura 7.3 pot fi urmariti pasii necesari pentru stergerea nodului de valoare minima din
heap-ul considerat drept exemplu.

152
Fig. 7.3: Stergerea valorii minime dintr-un heap

7.1.3 Crearea unui heap

Algoritm 54 Algoritm de creare a unui heap (prima varianta)


1: procedure NewHeap1(A, n)
2: for i 2, n do
3: call Insert(A, i 1, n, ai )
4: end for
5: end procedure

Prima metoda de construire a unui heap binar se bazeaza pe urmatoarea tehnica: se pleaca
initial cu heap-ul format dintr-un singur element si se insereaza pe rand, toate elementele n
heap-ul nou creat (a se vedea algoritmul 54).
Exemplul 7.3 Sa construim o structur a de heap din sirul de elemente 10, 7, 9, 5, 7 . . .. Se
pleaca cu heap-ul format dintr-un singur nod, 10. Apoi, se insereaz a elementul cu valoarea 7
n heap. Deoarece 7 > 10, se interschimb a valorile ntre ele (a se vedea figura 7.4). Urmeaza
sa se insereze nodul a carui valoare asociat a este 9 (acest nod va fi descendentul drept al
radacinii, dupa cum se poate observa din figura 7.4). La final, se adaug a nodul a carui cheie
are valoarea 5, si se restabileste proprietatea de heap prin interschimb ari succesive.
Analizand numarul de operatii efectuate de procedura Insert, se observa ca o valoare
nou introdusa poate sa ajunga pana n radacina arborelui. Astfel, numarul de operatii
(interschimbari) n cazul cel mai defavorabil va fi egal cu naltimea arborelui, h. Deoarece
avem un arbore binar complet cu cel mult n noduri, naltimea acestuia este cel mult [log n]
(h [log n]).
Complexitatea timp n cazul cel mai defavorabil al procedurii NewHeap este egala cu suma
timpilor necesari inserarii tuturor celor n valori, n cazul cel mai defavorabil. Vom tine cont
de faptul ca, n cazul unui arbore binar complet, numarul nodurilor de pe nivelul i este 2i ,
cu exceptia ultimului nivel unde numarul de noduri este mai mic.
n [log n] [log n]
X X X
i
T (n) = TInsert (i) i 2 log n 2i = O(n log n) (7.1)
k=1 i=1 i=1

153
Fig. 7.4: Crearea unui heap prin inserari succesive

Cea de-a doua metoda se bazeaza pe ideea de a construi un heap prin combinari (uni-
uni) repetate de heap-uri, strategie cu o eficienta superioara metodei anterioare. Initial, se
porneste cu o padure de arbori, compusi fiecare numai dintr-un singur nod, radacina. La
fiecare pas i, se construieste un heap nou din ai si doua heap-uri de dimensiunile apropiate:
heap-ul avand radacina a2i si cel cu radacina a2i+1 (a se vedea algoritmul 55).

Algoritm 55 Algoritm de creare a unui heap (a doua varianta)


1: procedure NewHeap2(A, n)
2: for i n2 , 1 ST EP 1 do
3: call P ush(A, i, n)
4: end for
5: end procedure

Se observa faptul ca subrutina Push (a se vedea algoritmul 56) este asemanatoare cu


subrutina DeleteMin, scopul lui Push fiind acela de a reorganiza un heap ntre limitele f irst
si last ale tabloului A.

Exemplul 7.4 Sa construim o structur


a de heap av
and drept date de intrare elementele
secventei:
1 2 3 4 5 6 7
10 7 9 5 7 8 6
Dupa cum se poate observa din figura 7.5, pentru i = 3 se va construi heap-ul compus din
elementele de valori 9, 8 si 6, pentru i = 2 se va construi heap-ul compus din elementele de
valori 7, 5 si 7, iar pentru i = 1 se va construi heap-ul ce are drept r
ad
acin
a elementul de
valoare 10, descendentul stang fiind heap-ul construit la pasul i = 2 iar descendentul drept
fiind heap-ul construit la pasul i = 3.

Fie x un nod aflat pe nivelul i (i = 0, h 1, h = log n). Acest nod va suferi cel mult
h i deplasari n cadrul structurii de heap, migrand catre frunzele arborelui. Numarul de

154
Algoritm 56 Algoritm de reorganizare a unui heap
1: procedure Push(A, f irst, last)
2: i f irst
3: while (i last
2 ) do
4: if ((last = 2 i) (a2i < a2i+1 )) then
5: j 2i
6: else
7: j 2i+1
8: end if
9: if (ai > aj ) then
10: ai aj
11: ij
12: else
13: i last
14: end if
15: end while
16: end procedure

Fig. 7.5: Crearea unui heap prin reorganizari succesive

operatii (deplasari) efectuate de algoritmul NewHeap2 pentru a construi o structura de heap


cu n elemente este:
h1 h h h
X
i j=hi
X
hj
X
h j X j
T (n) 2 (h i) = j2 = 2 j n < 2n = O(n) (7.2)
i=0 j=1 j=1
2 j=1
2j

7.2 Ordonare prin metoda HeapSort


Metoda de ordonare HeapSort [129] are la baza algoritmul general prezentat n subrutina
SortX (a se vedea algoritmul 57). Vom presupune ca elementele sirului initial se afla ntr-o
structura de date de tip lista L, iar rezultatul va fi obtinut n structura de date de tip heap
S. Initial structura S nu contine nici un element.
Subrutina Insert(x, S; S) va adauga elementul x structurii de date S. Subrutina

155
Algoritm 57 Algoritm de ordonare folosind heap-uri (prima varianta)
1: procedure SortX(A, n)
2: for f iecare x L do
3: call Insert(x, S; S)
4: end for
5: while (S 6= ) do
6: call M in(S; y)
7: ouput {y}
8: call Delete(y, S; S)
9: end while
10: end procedure

Min(S; y) ntoarce valoarea minima a lui S n y, iar Delete(y, S; S) sterge elementul y


din S.
Dupa cateva modificari ale algoritmului initial de ordonare se obtine algoritmul HeapSort
(a se vedea algoritmul 58): instructiunile din liniile 2-4 formeaza heap-ul ntr-o maniera
incrementala; pentru toate elementele, n linia 6 se elimina cel mai mic element din fata
heap-ului si se reface proprietatea de arbore partial ordonat (linia 7) ntre limitele 1 si i 1.

Algoritm 58 Algoritm de ordonare folosind heap-uri (a doua varianta)


1: procedure HeapSort(A, n)
2: for i 2i , 1 ST EP 1 do
3: call P ush(A, i, n)
4: end for
5: for i n, 2 ST EP 1 do
6: a1 ai
7: call P ush(A, 1, i 1)
8: end for
9: end procedure

Implementarea n limbajul C a algoritmului de ordonare a unei secvente de numere


folosind heap-uri este urmatoarea:
Listing 7.1: sortheap.c
#include <s t d i o . h>

#define MAXN 100

/
F u n c t i e pentr u c i t i r e a v a l o r i l o r d a t e l o r de i n t r a r e .
/
i nt r e a d I n p u t ( i nt a ) {
i nt i , n ;

p r i n t f ( n= ) ; s c a n f ( %d , &n ) ;
for ( i = 1 ; i <= n ; i ++) {
p r i n t f ( a[%d]= , i ) ; s c a n f ( %d , &a [ i ] ) ;
}
return n ;
}

156
/
F u n c t i e pentr u a f i s a r e a r e z u l t a t e l o r .
/
void l i s t ( i nt a , i nt n ) {
i nt i ;

for ( i = 1 ; i <= n ; i ++)


p r i n t f ( %d , , a [ i ] ) ;
p r i n t f ( \n ) ;
}

/
Reorganizarea unei movile .
@param s t a r t i n d i c e l e p r i m u l u i element a l s e c v e n t e i
@param f i n i s h i n d i c e l e l u l t i m u l u i element a l s e c v e n t e i
@param a v e c t o r u l c e p a s t r e a z a v a l o r i l e m o v i l e i
/
void push ( i nt s t a r t , i nt f i n i s h , i nt a ) {
i nt i , j , aux ;

i = start ;
while ( i <= f i n i s h / 2 ) {
i f ( 2 i == f i n i s h | | a [ 2 i ] < a [ 2 i + 1 ] )
j = 2 i;
else
j = 2 i + 1;
if (a [ j ] < a [ i ]) {
aux = a [ i ] ; a [ i ] = a [ j ] ; a [ j ] = aux ;
i = j;
}
else
return ;
}
}

i nt a [MAXN] ;

void main ( void ) {


i nt n = r e a d I n p u t ( a ) ;
i nt i , j , aux ;

// o r g a n i z a r e a unui heap
for ( i = n / 2 ; i > 0 ; i )
push ( i , n , a ) ;

// d e t e r m i n a r e a minimului s i r e o r g a n i z a r e a heapu l u i
j = n;
while ( j > 1 ) {
aux = a [ 1 ] ; a [ 1 ] = a [ j ] ; a [ j ] = aux ;
j ;
push ( 1 , j , a ) ;
}
l i s t (a , n ) ;
}

157
7.3 Aplicatie - Coad
a cu prioritate
Definitia 7.2 O coad a cu prioritate este o structur a de date abstract a formata din ele-
mente ce au asociata o valoare numita cheie sau prioritate si care suport
a urmatoarele opera-
tii:
Insert(Q, X) - insereaza elementul x n coada cu prioritate denumit
a Q;
ExtractMax(Q) - extrage din coada cu prioritate Q, elementul de valoare maxim
a.

Multitasking-ul este o metoda prin intermediul careia mai multe procese utilizeaza n co-
mun resursele calculatorului (inclusiv procesorul). In situatia unui calculator cu un singur
procesor, se spune ca n orice moment ruleaza cel mult un proces, ceea ce nseamna ca proce-
sorul executa instructiunile unui singur proces la un moment dat. Sarcina alegerii procesului
care sa se afle n executie la un moment dat este o problema de planificare. Operatia de a
opri un proces aflat n executie, pentru a-i aloca altui proces, aflat n asteptare, un timp pro-
cesor se numeste schimbare de context (eng. context switch). Realizarea frecventa a acestor
schimbari de context creaza iluzia executiei n paralel a mai multor programe. In cazul unui
calculator cu mai multe procesoare, multitasking-ul permite executia unui numar de procese
mai mare decat numarul de procesoare.
Sistemul de operare este cel care se ocupa de planificarea proceselor, planificare ce se
ncadreaza ntr-una din urmatoarele strategii:
n cazul sistemelor cu multiprogramare, procesul curent se afla n executie pana n
momentul n care realizeaza o operatie ce presupune asteptarea dupa un eveniment
extern (o operatie de I/O), sau pana cand planificatorul de procese forteaza eliberarea
procesorului.

ntr-un sistem de tip time-sharing, fiecare proces va elibera procesorul n mod voluntar,
dupa expirarea cuantei de timp alocate acestuia, sau n urma aparitiei unui eveniment
hardware cum ar fi o ntrerupere.

n cadrul sistemelor real-time, unui proces aflat n stare de asteptare i se garanteaza


accesul la procesor n cazul aparitiei unui eveniment extern. Astfel de sisteme sunt
proiectate pentru controlul unor dispozitive mecanice cum ar fi robotii industriali.
Comutarea ntre procese consuma timp procesor pentru ca planificatorul de procese sa
nghete starea unui proces si sa dezghete starea altui proces (schimbare de context). Daca
mai multe procese concurente sunt executate pe acelasi procesor si toate efectueaza diverse
calcule, atunci timpul total de executie va fi mai mare decat timpul de executie al unui
program secvential echivalent. Cresterea vitezei sistemului se poate realiza prin ntreteserea
(intercalarea) diferitelor faze ale mai multor procese.
In cadrul unui proces se pot identifica doua tipuri de faze din punct de vedere logic:
faza de calcul si faza de I/O. Faza de calcul se realizeaza exclusiv la nivelul procesorului
utilizandu-se la maxim functiunile acestuia. Faza de I/O (intrare/iesire) presupune un aport
mai mare din partea perifericelor (imprimante, hard discuri, placi de retea etc), procesul
asteptand ca un periferic sa-si termine sarcina. In timp ce un proces se afla ntr-o faza de
I/O asteptand dupa finalizarea unei operatii de catre un dispozitiv periferic, un proces aflat
n faza de calcul poate ocupa procesorul si efectua calculele programate.
In cazul multitasking-ului preemtiv, planificatorul de procese aloca cuante de timp procesor
egale fiecarui proces, asigurandu-le astfel un tratament echitabil. De asemenea, sistemul poate
raspunde rapid unui eveniment extern (cum ar fi sosirea unor date) ce presupune procesarea

158
de catre procesul curent sau de catre un altul. In cazul multitasking-ului non-preemtiv,
planificatorul i da controlul unui proces pana cand acesta se termina, sau elibereaza singur
procesorul.
Sistemele de operare bazate pe kernelul Windows NT 4.0 folosesc un planificator de
procese preemtiv cu prioritati. In momentul n care trebuie sa aleaga un proces pentru a-i da
controlul procesorului, planificatorul de procese va alege procesul ce are prioritatea cea mai
mare si este gata de executie, utilizand o strategie de tip round rubin: daca avem trei procese
avand aceeasi prioritate A, B si C, si un proces D de prioritate mai mica, planificatorul va
alege mai ntai procesul A, apoi procesul B, urmat de procesul C, procesului D alocandu-i
procesorul numai n situatia n care celelalte trei procese nu sunt n starea gata de executie (de
exemplu sunt blocate n asteptarea efectuarii unei operatii I/O). Pentru procesele de prioritate
mica, si care nu au fost executate deloc n ultimele, sa zicem, 3 secunde, planificatorul de
procese aplica urmatorul algoritm pentru a evita fenomenul de nfometare: le atribuie o
prioritate temporara foarte mare, ridicandu-le la nceputul cozii de procese, si le aloca o
cuanta de timp mai lunga decat n mod normal.
Sistemele de operare bazate pe un kernel de tip Unix folosesc un planificator bazat pe
mai multe cozi de prioritati ce foloseste aceasi strategie de tip round rubin n cadrul fiecarei
cozi. Procesele noi sunt inserate ntr-o coada corespunzatoare unor prioritati mai mari, si pe
masura ce petrec un timp procesor mai ndelungat, sunt inserate ntr-o coada corespunzatoare
unor prioritati mai mici. Sistemele de operare de generatie mai noua, bazate pe un kernel
Unix, incrementeaza prioritatea unui proces aflat n stare de nfometare (starvation) pana
cand acesta este executat, dupa care prioritatea acestuia este resetata la valoarea pe care
procesul o avea nainte de a ncepe nfometarea.
Vom simula un planificator de procese bazat pe o coada de prioritati: la un moment
dat, planificatorul alege procesul de prioritate minima, i da controlul procesorului pentru o
perioada de timp variabila nsa limitata. Dupa trecerea acelei perioade de timp, prioritatea
procesului este incrementata cu o valoare ce depinde direct proportional de timpul procesor
petrecut de acesta, si este inserat n coada de prioritati pe pozitia corespunzatoare.
In cadrul fisierului task.h definim structura de date Process precum si doua functii ce
au legatura cu prelucrarea informatiilor asociate unui proces:
long p(Process a); - ntoarce prioritatea unui proces;

void execute(int id); - simuleaza executia unui proces pentru o anumita perioada
de timp.

Listing 7.2: task.h


/ Task . h /
#i fndef TASK
#define TASK

#include <s t d i o . h>


#include < s t d l i b . h>
#include <dos . h>

typedef str u ct p r o c e s s {
i nt i d ;
long p r i o r i t y ;
} Process ;

long p ( P r o c e s s a ) ;
void e x e c u t e ( i nt i d ) ;

159
#endif

In fisierul heap.h se defineste o coada de prioritati (PriorityQueue) implementata sub


forma unui heap:

Process* deleteMin(PriorityQueue*); - ntoarce procesul de prioritate minima avand


grija sa-l stearga din coada de prioritati;

void makeNull(PriorityQueue*); - goleste (videaza) o coada de prioritati;

void insertTask(PriorityQueue*, Process); - adauga un nou proces n coada de


prioritati.

Coada de prioritati se comporta ca un container de obiecte.


Listing 7.3: heap.h
/ Heap . h /
#i fndef HEAP
#define HEAP

#include <mem. h>


#include t a s k . h

#define MAX SIZE 100

/
D e f i n i t i a s t r u c t u r i i PriorityQueue
/
typedef str u ct p r i o r i t y q u e u e {
P r o c e s s e l e m e n t s [ MAX SIZE+ 1 ] ;
i nt l a s t ;
} PriorityQueue ;

Process deleteMin ( PriorityQueue ) ;


void makeNull ( P r i o r i t y Q u e u e ) ;
void i n s e r t T a s k ( P r i o r i t y Q u e u e , P r o c e s s ) ;

#endif

Implementarea functiilor declarate n fisierul header heap.h, se realizeaza n cadrul fisierului


heap.c:

Process* deleteMin(PriorityQueue*); - ntoarce procesul de prioritate minima. Se muta


elementul de pe ultima pozitie pe prima pozitie si se reorganizeaza structura de date
de tip heap (prin reorganizare ntelegem rearanjarea elementelor cu un numar minim
de operatii astfel ncat sa se pastreze proprietatea de heap).

void makeNull(PriorityQueue*); - videaza structura de date de tip heap.

void insertTask(PriorityQueue*, Process); - adauga un nou proces n heap la sfarsit, si


cauta pozitia corespunzatoare acestui proces nou astfel ncat proprietatea de heap sa
fie pastrata.

Fisierul manager.c contine functii ce implementeaza comportamentul planificatorului de


procese.

160
void init(int p) - creaza un proces cu identificatorul p si prioritatea egala cu momentul
de timp n care a fost creat, pe care l insereaza n coada de prioritati.
void select(void) - la un moment dat, selecteaza procesul aflat n varful cozii, i da con-
trolul procesorului pentru o perioada de timp, actualizeaza prioritatea acestuia si-l insereaza
n coada de prioritati.
Daca dorim ca managementul cozii de prioritati sa fie realizat prin intermediul unei liste
n locul structurii de tip heap, este suficient sa nlocuim linia #include "heap.h" cu linia
#include "list.h".

7.4 Exercitii
1. Sa se implementeze structura de date abstracta coada cu prioritate cu ajutorul:

a) unui vector ordonat,

b) unei liste simplu nlantuita,

si sa se elaboreze algoritmii pentru operatiile Insert si Extract. Analizati n aceste


cazuri complexitatea operatiilor.

2. Sa se descrie algoritmi avand complexitate logaritmica ce implementeaza urmatoarele


operatii efectuate asupra unei cozi cu prioritate.

DecreaseKey(H, k, x) - valoarea elementului k din heap-ul H se micsoreaza


(hk min{hk , x}), operatia fiind, eventual, urmata de restaurarea heap-ului.

IncreaseKey(H, k, x) - valoarea elementului k din heap-ul H este marita (hk


max {hk , x}), operatia fiind urmata de restaurarea heap-ului.

Coada cu prioritate este implementata cu ajutorul unui heap.

3. Implementare urmatoare a algoritmului Heapsort este foarte scurta si rapida, avand


avantajul ca nu utilizeaza un vector suplimentar pentru ordonare2 :
Listing 7.4: heapsortv2.c
void h e a p s o r t ( i nt a r r [ ] , unsigned i nt N) {
i nt t ;
unsigned i nt n = N, p a r e n t = N/ 2 , index , c h i l d ;

for ( ; ; ) {
i f ( parent > 0) {
t = a r r [ p a r e n t ] ;
} else {
n;
i f ( n == 0 )
return ;
t = arr [ n ] ;
arr [ n] = arr [ 0 ] ;
}

index = parent ;
2
http://en.wikibooks.org/wiki/Algorithm_Implementation/Sorting/Heapsort

161
c h i l d = index 2 + 1 ;
while ( c h i l d < n ) {
i f ( ( c h i l d + 1 < n ) && ( arr [ child + 1] > arr [ child ] ) ) {
c h i l d ++;
}

if ( arr [ child ] > t ) {


arr [ index ] = arr [ c h i l d ] ;
index = c h i l d ;
c h i l d = index 2 + 1 ;
} else {
break ;
}
}
arr [ index ] = t ;
}
}

Ordonati crescator urmatoarea secventa de numere, pe baza functiei anterioare, utili-


zand numai o coala de hartie si un creion: 5, 100, 25, 20, 10, 5, 7, 80, 90, 1.

4. Sa se elaboreze un algoritm de timp O(n lg k) pentru a interclasa k liste ordonate,


unde n este numarul total de elemente din listele de intrare.

5. Adriana este o mare colectionara de timbre. In fiecare zi se duce la magazinul de pe


strada ei pentru a-si mari colectia. Intr-o zi, vanzatorul (nimeni altul decat Balaurul
Arhirel) s-a gandit sa-i faca o surpriza. A scos dintr-un dulap vechi niste timbre foarte
valoroase pe care erau scrise cu fir de aur si de argint numere naturale. Stiind ca fetita
nu are bani prea multi, Balaurul i-a spus urmatoarele: Eu pot s a mpart timbrele n m
intervale de forma [1, . . . , mi ]. Tu poti s
a iei din orice interval o singur a subsecventa
de maxim k elemente. Desigur, dac a ai ales o subsecvent a din intervalul i vei plati o
anumita suma . . .
Adriana s-a gandit ca ar fi frumos sa-si numeroteze toate cele n pagini ale clasorului
ei cu astfel de timbre. Fiind si o fetita pofticioasa si-a zis: Tare as vrea s
a mananc o
nghetata din banii pe care i am la mine, dar nu stiu dac a o s
a-mi ajung a sa platesc
timbrele. Cum sa fac?
Fiind cunoscute cele m intervale, precum si costurile acestora, ajutati-o pe Adriana sa
cumpere timbrele necesare numerotarii clasorului, platind o suma cat mai mica.
Date de intrare
Pe prima linie a fisierului timbre.in se afla n m k. n reprezinta numarul de pagini ale
clasorului, m reprezinta numarul de intervale, iar k lungimea maxima a unei subsecvente.
Pe urmatoarele m linii se afla doua numere separate printr-un spatiu, mi ci , unde mi
reprezinta marginea superioara a intervalului i, iar ci costul acestuia.
Date de iesire
Pe prima linie a fisierului timbre.out se va afla Smin, reprezentand suma minima pe
care trebuie sa o plateasca Adriana pentru a cumpara timbrele necesare numerotarii
clasorului.
Restrictii: 0 < N < 1001, 0 < M < 10001, 0 < K < 1001, 0 < mi < 100000,
0 < ci < 10000.

162
Pentru a numerota toate cele n pagini ale clasorului, Adriana are nevoie de timbre cu
numerele de la 1 la n.
Exemplu
timbre.in timbre.out
4 3 2
5 3
3
2 1
6 2
Luam subsecventa {1, 2} din al doilea interval si subsecventa {3, 4} din al treilea inter-
val. Obtinem astfel costul minim 3.
(InfoArena, Timbre)

6. Paftenie barbarul a fost capturat de catre dusmanii sai cei mai temuti si aruncat ntr-o
temnita. Temnita este, de fapt, un grid de dimensiune R C. In anumite celule exista
dragoni, unele sunt ocupate de pereti, iar altele sunt libere. Paftenie trebuie sa iasa din
temnita mergand numai prin celule libere (o celula are maxim 4 vecini), si asta stand
cat mai departe de fiorosii dragoni ale caror flacari i pot deteriora vestimentatia (astfel
ncat valoarea minima dintre distantele pana la cel mai apropiat dragon din fiecare din
celulele traseului sau sa fie maxim).
Sa se determine un traseu al lui Paftenie pentru a iesi din temnita astfel ncat distanta
minima pana la cel mai apropiat dragon din fiecare dintre celulele traseului sau sa fie
maxima.
Date de intrare
Pe prima linie a fisierului de intrare barbar.in sunt date doua numere ntregi R si
C, reprezentand numarul liniilor, respectiv al coloanelor temnitei. Pe urmatoarele R
linii se afla cate C caractere, neseparate prin spatii, cu urmatoarele semnificatii: .
celula libera, perete, D dragon, I punctul de plecare al lui Paftenie, O iesirea din
temnita.
Date de iesire
Fisierul de iesire barbar.out va contine pe prima linie un singur numar, reprezentand
valoarea maxima pentru minima dintre distantele pana la cel mai apropiat dragon din
fiecare din celulele traseului. In caz ca nu exista solutie se va afisa valoarea 1.
Restrictii: 1 R, C 1.000.
Se garanteaza ca n temnita exista cel putin un dragon.
Exemplu Pentru datele de intrare

10 10
..........
.I....D...
..........
..D...D...
.*........
D*........
*...D.....
..****....
...O......
..........

163
o solutie posibila este urmatoarea, unde valoarea maxima dintre distantele minime este
2:

..........
.Iooo.D...
....o.....
..D.o.D...
.*..oo....
D*...ooooo
*...D....o
..****...o
...Ooooooo
..........

(preONI 2005, Barbar)

7. Se da un sir de numere ntregi si doua numere x si y (x, y N). Sa se determine


subsecventa de suma maxima a carei lungime se afla n intervalul [x, y].

164
Capitolul 8

Distante n grafuri

Reteaua de drumuri europene, nationale si judetene dintr-o tara, reteaua feroviara reprezentand
infrastructura cailor ferate absolut necesara pentru realizarea transportului de marfuri si
calatori cu trenul, reteaua de fibra optica folosita pentru traficul de date n Internet, reteaua
de transport a energiei electrice folosita pentru alimentarea cu energie electrica a consumato-
rilor casnici si a celor industriali, reteaua de canalizare dintr-un oras folosita pentru evacuarea
deseurilor, reteaua CAT V a unui operator de televiziune prin cablu, reteaua de linii de auto-
buz ce face parte din sistemul public de transport al unui oras sunt exemple tipice de grafuri
cu care interactionam n viata de zi cu zi.
Putem spune ca retelele de transport si cele de comunicatii de date ne influenteaza n
mod direct modul de viata, devenind elemente indispensabile ale omului modern, si astfel,
problemele referitoare la studiul cailor de comunicatie, drumurilor, conexiunilor, capata un
interes special.
De obicei suntem interesati de aspecte precum drumul cel mai scurt sau cel mai lung,
drumul cel mai ieftin sau drumul care se poate parcurge cel mai repede, drumul cel mai
sigur. Teoria grafurilor ne pune la dispozitie mijloacele pentru aflarea raspunsului la multe
astfel de ntrebari.
Definitiile pentru drum, lant, ciclu, circuit au fost prezentate n capitolele anterioare,
mpreuna cu multe alte concepte teoretice. Lungimea unui lant este determinata de numarul
muchiilor sale. In mod analog, lungimea unui drum este egala cu numarul arcelor sale.
Intr-un graf G se introduce o functie de cost ce asocieaza o valoare reala fiecarei muchii
sau arc, dupa caz:
c : E R sau c : V V R
Notam costul unui drum ca fiind suma costurilor arcelor componente. Astfel pentru un
drum D = [v0 , v1 , . . . , vm ], vi V , de lungime m, costul acestuia va fi dat de catre urmatoarea
formula:
m1
X
c(D) = c([vi , vi+1 ]). (8.1)
i=0

Notam cu Ms,t multimea drumurilor dintre nodul sursa xs si nodul destinatie xt , xs , xt


V . Pentru doua noduri oarecare xs si xt , se doreste sa se determine un drum s,t de la xs la
xt a carui valoare sa fie optima (minima sau maxima):
opt
c(s,t )= min c(s,t ). (8.2)
s,t Ms,t

165
8.1 Drumul minim de la un v
arf la celelalte v
arfuri
8.1.1 Algoritmul lui Moore
Fie G = (V, E) un graf orientat unde V = {x1 , x2 , . . . , xn } este multimea nodurilor si E este
multimea arcelor (E V V ). Pentru reprezentarea grafului vom utiliza, ca metoda de
reprezentare, listele de adiacenta (liste de vecini).
Fie u un varf al grafului numit surs a. Dorim sa determinam pentru fiecare varf w
V, w 6= u, daca exista, un drum de lungime minima de la u la w. Lungimea unui drum se
defineste ca fiind numarul arcelor ce l compun.
Algoritmul lui Moore (a se vedea algoritmul 59) se bazeaza pe structura algoritmului de
parcurgere n latime al unui graf (Breadth First Search - a se vedea algoritmul 16). In lucrarea
[98], Moore si prezinta algoritmul astfel:

Write 0 on the source. Then look at all the neighbors of the source and write
1 on them. Then look at all the neighbors of nodes with 1 on them and write 2
on them. And so on.

Vom utiliza un vector de distante D, unde dk reprezinta costul drumului minim de la varful u
la varful xk , iar tatak pastreaza varful anterior lui xk , pe drumul de cost minim de la nodul
u la nodul xk .

Algoritm 59 Algoritmul lui Moore (prima versiune)


1: procedure Moore1(k, n, V ecin; D, T ata)

k
- nodul sursa
Input: n - num arul de noduri din graf


V ecin - vector ce contine capetele listelor de vecini

D
- vectorul distantelor de la nodul sursa la celelalte noduri
Output: T ata - vector ce contine pentru fiecare nod k, predecesorul acestuia pe drumul


de cost minim de la nodul surs a la nodul k
2: for i 1, n do
3: di +
4: end for
5: dk 0 distanta de la un nod la el nsusi este 0
6: Qk inserarea nodului curent k n coad a
7: while (Q 6= ) do c
at timp coada nu este vid a
8: Qk extrage nodul curent din coad a
9: v vecink se pleaca cu primul vecin din lista de vecini
10: while (v 6= N U LL) do
11: if (dv.nodeIndex = +) then v.nodeIndex este indicele nodului vecin
12: dv.nodeIndex dk + 1, tatav.nodeIndex k
13: Q v.nodeIndex inserarea nodului v.nodeIndex n coada
14: end if
15: v v.next se trece la urmatorul vecin
16: end while
17: end while
18: end procedure

166
Exemplul 8.1 Fie un graf orientat reprezentat prin intermediul urm
atoarelor liste cu vecini:

1: (2, 4) 7: (8)
2: (3, 6) 8: (6, 7)
3: (2) 9: ()
4: (1, 5) 10: (11)
5: (4, 6, 9) 11: (10)
6: (7, 8)
In urma aplicarii algoritmului lui Moore pentru acest graf si pentru nodul 1 drept sursa,
se obtin urmatoarele valori pentru vectorii D si Tata:

In urma aplicarii algoritmului lui Moore pentru acest graf si av


and nodul 1 drept sursa,
se obtin urmatoarele valori pentru vectorii d si tata:
1 2 3 4 5 6 7 8 9 10 11
D 0 1 2 1 2 2 3 3 3
Tata 1 2 1 4 2 6 6 5
Urmarind valorile calculate, putem trage concluzia c a drumul de lungime minima de la
nodul 1 la nodul 9, are costul 3 si este compus din nodurile [1, 4, 5, 9].

Algoritmul 60 calculeaza lungimea drumurilor minime de la un nod sursa la toate nodurile


accesibile din acesta, n cazul n care lungimea unui drum se defineste ca fiind suma costurilor
asociate arcelor ce l compun.

Algoritm 60 Algoritm lui Moore (a doua versiune)


1: procedure Moore2(k, n, C; D, T ata)

k - nodul surs
a
Input: n - num arul de noduri din graf


C - matricea costurilor
2: for i 1, n do
3: di +
4: end for
5: dk 0 distanta de la un nod la el nsusi este 0
6: Qk inserarea nodului curent k n coad a
7: while (Q 6= ) do c
at timp coada nu este vid a
8: Qk extrage nodul curent din coad a
9: for fiecare vecin (v = xi ) al lui xk do
10: if (dk + ck,i < di ) then
11: di dk + ck,i , tatai k
12: Qi inserarea nodului i n coada
13: end if
14: end for
15: end while
16: end procedure

8.1.2 Algoritmul lui Dijkstra


Sa consideram G = (V, E) un graf orientat unde V = {1, 2, . . . , n} este multimea nodurilor
iar E este multimea arcelor (E V V ). Pentru reprezentarea grafului vom utiliza matricea

167
costurilor C:
0
, daca i = j
ci,j = , daca (i, j)
/ E, i 6= j


d > 0 , daca (i, j) E
Fie u un varf al grafului numit surs a. Dorim sa determinam pentru fiecare varf w
V, w 6= u, daca exista, un drum de lungime minima de la u la w. Lungimea unui drum se
defineste ca fiind suma costurilor asociate arcelor ce l compun.
Fie p = [x1 , x2 , . . . , xk ] un drum elementar. Se numeste v arf intermediar orice varf u
{x2 , . . . , xk1 } unde u 6= x1 , u 6= xk (v
arful intermediar este diferit de extremitatile drumului
p).
Algoritmul lui Dijkstra [38] utilizeaza metoda generala de elaborare a algoritmilor Greedy,
drumurile de lungime minima fiind generate n ordinea crescatoare a lungimii lor. Vom nota
cu S multimea nodurilor grafului G pentru care se cunoaste (s-a calculat) drumul de lungime
minima de la sursa la un astfel de nod. La nceput, multimea S este compusa doar din
nodul sursa. La fiecare pas, se adauga la multimea S un nod xk V \ S cu proprietatea ca,
drumul de la sursa la acest nod xk , este cel mai mic dintre toate drumurile posibile de la
sursa la noduri din multimea V \ S, cu proprietatea ca, un astfel de drum are drept noduri
intermediare numai elemente din multimea S. Vom utiliza un tablou D, ce va pastra pentru
fiecare nod xk , lungimea drumului cel mai scurt de la sursa ce trece numai prin noduri din
multimea S (a se vedea algoritmul 61).

Algoritm 61 Algoritmul lui Dijkstra (schema generala)


1: procedure Dijkstra1(u, n, C)
2: S {u}
3: for i 1, n do
4: di cu,i
5: end for
6: for i 1, n 1 do
7: k min{dk |xk V \ S}
8: S S {xk }
9: for each xj V \ S do
10: dj min{dj , dk + ck,j }
11: end for
12: end for
13: end procedure

Dupa ce am identificat un astfel de nod xk , multimea S se modifica astfel: S = S {xk }.


Este posibil ca lungimea unui drum de la sursa la un nod xj V \ S, ce are drept noduri
intermediare noduri din multimea S, sa se modifice datorita faptului ca, mai nainte, nodul
xk nu fusese luat n considerare, deoarece nu apartinea multimii S. Astfel, se poate ca un
drum de la sursa la nodul xj , ce trece prin nodul xk , sa fie mai mic decat drumul anterior de
la sursa la nodul xj , ce nu avea printre nodurile sale intermediare varful xk :
dk + ck,j < dj .
Vom utiliza trei vectori (a se vedea algoritmul 62):
Vizitat - vector caracteristic pentru multimea S, unde:
(
1 , daca nodul xj S
vizitatj =
0 , daca nodul xj V \ S

168
Algoritm 62 Algoritmul lui Dijkstra
1: function DistantaMinima(n, V izitat, D)
2: min
3: for j 1, n do
4: if ((vizitatj = 0) (dj < min)) then
5: min dj
6: j0 j
7: end if
8: end for
9: if (min = ) then
10: return 1
11: else
12: return j0
13: end if
14: end function

15: procedure Dijkstra2(u, n, C)


16: vizitatu 1
17: du 0, tatau 0
18: for i 1, n do
19: if (i 6= u) then
20: vizitati 0
21: di cu,i , tatai u
22: end if
23: end for
24: while (true) do
25: k DistantaM inima(n, vizitat, d)
26: if (k < 0) then
27: break forteaza iesirea dintr-o instructiune de ciclare
28: else
29: vizitatk 1
30: for i 1, n do
31: if ((vizitati = 0) (dk + ck,i < di )) then
32: tatai k
33: di dk + ck,i
34: end if
35: end for
36: end if
37: end while
38: end procedure

D - vectorul distantelor de la nodul u la celelalte noduri ale grafului. Asa cum am


precizat anterior, dj pastreaza costul drumului de lungime minima de la nodul sursa u
la nodul xj , drum ce are drept noduri intermediare numai elemente din multimea S.
In momentul initial vom avea dj = cu,j .
Notam cu xk nodul ales la un moment dat. Atunci costul drumului de lungime minima
dj de la nodul sursa u la un nod xj , se modifica numai daca dk + ck,j < dj , fiind
actualizat astfel: dj = dk + ck,j .
tataj - pastreaza pentru fiecare nod xj , nodul anterior xk (xk S) pe drumul de cost

169
minim de la u la xj . La nceput,
(
0 , daca nodul j = u sau cu,j =
tataj =
u , n rest (j 6= u si cu,j 6= ).

Un element al acestui vector se poate modifica atunci cand se modifica valoarea ele-
mentului dj astfel ncat dj = dk + ck,j , caz n care tataj = k.

Observatia 8.2 Algoritmul lui Dijkstra prezint a asemanari cu algoritmul de cautare n


latime, vectorul T ata pastrand la sfarsitul procesului de calcul al distantelor un arbore al
drumurilor minime ce are drept radacin a nodul sursa.

Demonstrarea corectitudinii algoritmului

Fig. 8.1: O cale speciala mai scurta

In momentul n care alegem nodul xk V \ S avand proprietatea ca drumul de la sursa la


acel nod xk este cel mai mic dintre toate drumurile posibile de la sursa la noduri din multimea
V \ S, ce trec numai prin noduri din multimea S, acel drum este cel mai mic drum de la
sursa la xk dintre toate drumurile posibile. Denumim drum special un drum de la sursa la
un nod xk ce are drept noduri intermediare numai elemente din multimea S.
Sa presupunem prin reducere la absurd, ca exista un drum de lungime mai mica de la
nodul sursa la nodul xk , ce nu are toate nodurile intermediare din multimea S. Fie xj V \S,
primul nod ce nu apartine multimii S pe drumul de la nodul sursa la nodul xk . Atunci, drumul
de la nodul sursa u la nodul xk se compune dintr-un drum de la u la xj , si un drum de la xj la
xk . Datorita modului de alegere al lui xj , drumul de la u la xj are drept noduri intermediare
numai elemente din multimea S (xj este primul nod pe drumul de la nodul sursa la nodul
xk , care nu apartine multimii S), deci este un drum special si are o lungime mai mica decat
drumul special de la sursa la nodul xk . Prin urmare, am identificat un alt drum special de
lungime mai mica, ceea ce contrazice modul de alegere al nodului xk .
Trebuie sa demonstram ca, n orice moment, dk pastreaza lungimea celui mai scurt drum
special de la nodul sursa u la nodul xk V \ S. In momentul n care nodul xk este adaugat
multimii S, avem grija sa verificam daca nu exista un drum special de la nodul u la un nod
xj V \ S care sa aiba o lungime mai mica. Sa presupunem ca, pentru un nod xj fixat, exista
un nod w S astfel ncat drumul special u xk xk w (w, xj ) sa aiba o lungime mai
mica (a se vedea figura 8.1). Deoarece nodul w a fost adaugat multimii S naintea nodului
xk , avem dw dk .

170
Prin urmare
dw + cw,j dk + cw,j < dk + costxk w + cw,j , (8.3)
adica drumul special de la sursa la nodul xj ce trece prin nodul w are lungimea mai mica
decat drumul special compus din drumul de la nodul sursa la nodul xk , drumul de la nodul
xk la nodul w si arcul (w, xj ).

Fig. 8.2: Un exemplu de graf orientat ponderat

Exemplul 8.3 Sa presupunem ca nodul surs a este nodul 1 pentru graful din figura 8.2. Dupa
etapa de initializare, vom avea urmatoarele valori:

1 2 3 4 5
D 1 21
Tata 0 1 1 1 1
Vizitat 1 0 0 0 0
In cadrul primei iteratii, dupa selectarea nodului 2, se evalueaz
a urm
atoarele distante:
d2 + c2,3 < d3 1 + 8 < +
d2 + c2,4 < d4 1 + < 21
d2 + c2,5 < d5 1 + 4 < +

1 2 3 4 5
D 1 9 21 5
Tata 0 1 2 1 2
Vizitat 1 1 0 0 0
In timpul celei de-a doua iteratii se evalueaz
a urm
atoarele conditii:
d5 + c5,3 < d3 5 + 3 < 9
d5 + c5,4 < d4 5 + < 21

1 2 3 4 5
D 1 8 21 5
Tata 0 1 5 1 2
Vizitat 1 1 0 0 1
Dupa pasul al treilea, vom actualiza lungimea unui singur drum:
d3 + c3,4 < d4 8 + 12 < 21

171
1 2 3 4 5
D 1 8 20 5
Tata 0 1 5 3 2
Vizitat 1 1 1 0 1
La sfasitul ultimei iteratii valorile vectorilor D, Tata, Vizitat sunt urm
atoarele:

1 2 3 4 5
D 1 8 20 5
Tata 0 1 5 3 2
Vizitat 1 1 1 1 1

Implementarea n limbajul C a algoritmului 62 este urmatoarea:


Listing 8.1: dijkstra.c
#include <s t d i o . h>
#include <v a l u e s . h>

#define MAXN 100


#define TRUE 1
#define FALSE 0
#define MAXINT 30000

/
C i t i r e a d a t e l o r de i n t r a r e + i n f i n i t = 1
/
void r e a d I n p u t ( i nt n , i nt c [ ] [MAXN] , i nt u ) {
i nt i , j ;

p r i n t f ( n = ) ; s c a n f ( %d , n ) ;
for ( i = 0 ; i < n ; i ++)
for ( j = 0 ; j < n ; j ++) {
s c a n f ( %d , &c [ i ] [ j ] ) ;
i f ( c [ i ] [ j ] < 0)
c [ i ] [ j ] = MAXINT;
}
p r i n t f ( Nodul i n i t i a l : ) ; s c a n f ( %d , u ) ;
}

i nt minim ( i nt n , i nt d [ ] , char v i z i t a t [ ] ) {
i nt j , j 0 ;
i nt min ;

min = MAXINT;
for ( j = 0 ; j < n ; j ++)
i f ( ! v i z i t a t [ j ] && d [ j ] < min ) {
min = d [ j ] ;
j0 = j ;
}
i f ( min == MAXINT)
return 1;
else
return j 0 ;
}

/
F u n c t i a v e r i f i c a daca a > b + c .
/

172
i nt mai mare ( long a , long b , long c ) {
return ( a > b+c ) ? 1 : 0 ;
}

void d i j k s t r a ( i nt u , i nt n , i nt c [ ] [MAXN] , i nt d [ ] , i nt t a t a [ ] ) {
/ v e c t o r c e p a s t r e a z a s t a r e a unui nod : v i z i t a t sau n e v i z i t a t /
char v i z i t a t [MAXN] ;
i nt i , j , k ;

// i n i t i a l i z a r i
v i z i t a t [ u ] = TRUE;
t a t a [ u ] = 1;
for ( i = 0 ; i < n ; i ++)
i f ( i != u ) {
v i z i t a t [ i ] = FALSE ;
d[ i ] = c [u ][ i ];
tata [ i ] = u ;
}
// p a r t e a p r i n c i p a l a
for ( i = 1 ; i < n ; i ++) {
k = minim ( n , d , v i z i t a t ) ;
i f (k < 0)
break ;
v i z i t a t [ k ] = TRUE;
// a c t u a l i z a r e
for ( j = 0 ; j < n ; j ++)
i f ( ! v i z i t a t [ j ] && mai mare ( d [ j ] , d [ k ] , c [ k ] [ j ] ) ) {
tata [ j ] = k ;
d[ j ] = d[k] + c [k ][ j ];
}
}
}

void printDrum ( i nt k , i nt t a t a [ ] ) {
i f ( t a t a [ k ] >= 0 )
printDrum ( t a t a [ k ] , t a t a ) ;
else
printf ( [ );
p r i n t f ( %d , k ) ;
}

void p r i n t S o l u t i o n ( i nt u , i nt n , i nt d [ ] , i nt t a t a [ ] ) {
i nt i ;

for ( i = 0 ; i < n ; i ++)


i f ( i != u )
i f ( d [ i ] == MAXINT)
p r i n t f ( Nu e x i s t a drum de l a v a r f u l %d l a v a r f u l %d . \ n , u , i ) ;
else {
p r i n t f ( D i s t a n t a de l a v a r f u l %d l a v a r f u l %d e s t e %d : , u , i , d [ i ] ) ;
printDrum ( i , t a t a ) ;
p r i n t f ( ] \ n ) ;
}
}

void main ( void ) {


/ numarul de n o d u r i d i n g r a f /
i nt n ;
/ m a t r i c e a c o s t u r i l o r /

173
i nt c [MAXN] [MAXN] ;
/ no dul f a t a de c a r e s e c a l c u l e a z a d r u m u r i l e de lung ime minima /
i nt u ;
/ t a t a [ k ] p a r i n t e l e v a r f u l u i k i n a r b o r e l e r e z u l t a t /
i nt t a t a [MAXN] ;
/ d i s t a n t a de l a u l a f i e c a r e nod /
i nt d [MAXN] ;

r e a d I n p u t (&n , c , &u ) ;
d i j k s t r a (u , n , c , d , tata ) ;
printSolution (u , n , d , tata ) ;
}

Utilizarea structurii coad


a cu prioritate n algoritmul lui Dijkstra
Complexitatea-timp a algoritmului lui Dijkstra poate fi mbunatatita prin utilizarea structurii
de date coada cu prioritate (a se vedea algoritmul 63).

Algoritm 63 Algoritmul lui Dijkstra (varianta ce foloseste o coada cu prioritate)


1: procedure Dijkstra3(u, n, C)
2: for i 1, n do
3: di
4: tatai N U LL
5: call Insert(Q, i, di ) insereaz a n coad
a elementul xi cu prioritatea di
6: end for
7: du 0
8: call DecreaseKey(Q, u, du ) actualizeaza prioritatea v
arfului u la du
9: S
10: for i 1, n 1 do
11: k DeleteM in(Q) sterge elementul de prioritate minim a din coada
12: S S {xk }
13: for f iecare xj V \ S, astfel nc
at (xk , xj ) E do
14: if (dj > dk + ck,j ) then
15: dj dk + ck,j
16: tataj k
17: call DecreaseKey(Q, j, dj ) actualizeaza prioritatea v
arfului xj la dj
18: end if
19: end for
20: end for
21: end procedure

Analizand algoritmul 63, obtinem ca timpul de lucru al acestuia depinde de formula:

T (n, m) = O(n TInsert + n TDeleteM in + m TDecreaseKey ) (8.4)

unde n reprezinta numarul de noduri ale grafului G iar m reprezinta numarul de muchii ale
aceluiasi graf. Cele trei operatii ntalnite n cadrul algoritmlui sunt (a se vedea si capitolul
7):

1. DeleteMin(Q) - sterge nodul ce contine cheia de valoare minima si reorganizeaza struc-


tura prin refacerea proprietatii de coad
a cu prioritate.

174
2. Insert(Q,p,x) - insereaza nodul p n coada cu prioritate Q, unde x reprezinta priori-
tatea nodului p.

3. DecreaseKey(Q,p,v) - modifica valoarea prioritatii nodului p din coada cu prioritate


Q, atribuindu-i valoarea v si reorganizeaza aceasta structura.

Astfel, complexitatea algoritmului pentru diferite implementari ale cozii cu prioritate Q


va fi [30]:

1. Lista liniara dublu nlantuita neordonata:

T (n, m) = O(n 1 + n n + m 1) = O(n2 ) (8.5)

2. Arbore 2 3:
T (n, m) = O(n 1 + n n + m 1) = O(n2 ) (8.6)

3. Heap-uri Fibonacci:

T (n, m) = O(n 1 + n n + m 1) = O(n2 ). (8.7)

8.2 Drumuri minime ntre toate perechile de v


arfuri
Fie G = (V, E) un graf orientat, unde V = {x1 , x2 , . . . , xn } reprezinta multimea nodurilor
si E reprezinta multimea arcelor (E V V ). Fiecarui arc din E i se asociaza o valoare
pozitiva ci,j (ci,j 0) ce reprezinta lungimea arcului. Se doreste calcularea drumurilor de
lungime minima pentru oricare pereche de noduri xi , xj .
Pentru a determina drumurile minime ntre toate perechile de varfuri, putem alege un
algoritm ce determina drumurile minime de sursa unica (drumurile minime de la acel varf la
toate celelalte), si pe care sa-l aplicam pentru fiecare varf al grafului.
Un astfel de algoritm este algoritmul lui Dijkstra, algoritm ce a cunoscut multiple mbunata-
tiri fata de varianta orginala, ce se bazeaza pe utilizarea unor structuri de date avansate (de
exemplu heap-uri Fibonacci), menite sa-i optimizeze timpul de executie. Astfel, complexitatea-
timp n cazul cel mai defavorabil pentru calculul celor mai scurte drumuri, pornind dintr-un
varf al unui graf cu n noduri si m arce, este O(m + n log n). Daca aplicam acest algoritm
pentru toate cele n varfuri vom obtine un timp de lucru de O(mn + n2 log n).
Algoritmul 64 prezinta o varianta a algoritmului lui Dijkstra n care drumurile de lungime
minima ntre oricare doua varfuri sunt calculate incremental si intercalat [35]. Matricea D
va pastra pentru fiecare pereche [xi , xj ] lungimea di,j a drumului minim dintre cele explorate
pana la momentul curent. Coada de prioritati C pastreaza toate perechile (i, j), ordonate
crescator n functie de prioritatea di,j a fiecareia. La fiecare pas, se extrage din coada perechea
(i, j) avand prioritatea cea mai mica si se ncearca extinderea drumului [xi , xj ] cu exact un
arc la fiecare extremitate a drumului. Tehnica utilizata este cea a relaxarii, ntalnita si n
cadrul algoritmului lui Bellman, cu scopul de a micsora costul drumului de la xi la xj , prin
parcurgerea tuturor arcelor ce parasesc varful xj si a celor ce sosesc n varful xi .

8.2.1 Algoritmul lui Roy-Floyd-Warshall


Fie p = [x1 , x2 , . . . , xk ] un drum elementar. Definim un varf intermediar orice varf u
{x2 , . . . , xk1 }, u 6= x1 , u 6= xk (varful intermediar este diferit de extremitatile drumului p).

175
Algoritm 64 Algoritmul lui Dijkstra pentru toate perechile de varfuri
1: procedure DijkstraAll(n, C; D)
2: for i 1, n do
3: for j 1, n do
4: if ((i, j) E) then sau (ci,j > 0) (ci,j < )
5: di,j ci,j
6: else
7: di,j
8: end if
9: C (i, j) cu prioritatea di,j
10: end for
11: end for
12: while (C = 6 ) do
13: C (i, j)
14: for k 1, n do
15: if (di,j + cj,k < di,k ) then
16: di,k di,j + cj,k
17: actualizeaza prioritatea perechii (i, k) din C la di,k
18: end if
19: end for
20: for k 1, n do
21: if (ck,i + di,j < dk,j ) then
22: dk,j ck,i + di,j
23: actualizeaza prioritatea perechii (k, j) din C la dk,j
24: end if
25: end for
26: end while
27: end procedure

Fie U = {x1 , x2 , . . . , xk } o multime de noduri. Notam cu MUi,j multimea drumurilor de


la xi la xj ce au drept noduri intermediare elemente din multimea U. Fie p drumul de cost
minim de la xi la xj (p MUi,j ). Un astfel de drum este elementar deoarece am presupus ca
nu exista cicluri al caror cost sa fie negativ.
(k)
Notam di,j costul drumului minim de la xi la xj ce are noduri intermediare numai din
multimea {x1 , x2 , . . . , xk }. Daca xk / p (xk nu apartine drumului p) atunci drumul de
cost minim de la xi la xj avand toate nodurile intermediare din multimea {x1 , x2 , . . . , xk1 }
va fi si drum de cost minim de la xi la xj cu toate nodurile intermediare din multimea
{x1 , x2 , . . . , xk }:
(k) (k1)
di,j = di,j (8.8)
Daca xk este un varf intermediar al drumului p, fie p1 si p2 subdrumuri ale lui p, p = p1 p2
(p1 drumul de la xi la xk avand nodurile intermediare din multimea {x1 , x2 , . . . , xk } si p2
drumul de la xk la xj avand nodurile intermediare din multimea {x1 , x2 , . . . , xk }). Deoarece
p1 si p2 sunt drumuri de cost minim si xk
/ {x1 , x2 , . . . , xk1 } vom avea:
(k1) (k)
di,k = di,k ,
(k1) (k)
dk,j = dk,j .

Prin urmare
(k) (k1) (k1)
c(p) = c(p1 ) + c(p2 ) di,j = di,k + dk,j (8.9)

176
Algoritmul Roy-Floyd-Warshall va construi un sir de matrici D (0) , D (1) , . . . , D (k) , . . . , D (n) .
(k)
Pentru fiecare k, 1 k n, di,j va contine lungimea drumului minim de la nodul xi la nodul
xj ce are drept noduri intermediare numai noduri din multimea {x1 , x2 , . . . , xk }.
Matricea D (0) se va initializa astfel:

0
, daca i = j
(0)
di,j = + , daca (xi , xj )
/ E, i 6= j (8.10)


d > 0 , daca (xi , xj ) E

(un drum ce nu are nici un varf intermediar este determinat de costul legaturii directe dintre
xi si xj ).
Drumul de lungime minima de la xi la xj ce trece numai prin nodurile {x1 , x2 , . . . , xk }
(k) (k1)
fie nu contine nodul xk , caz n care di,j = di,j , fie l contine, si atunci are loc relatia
(k) (k1) (k1) (k)
di,j = di,k + dk,j . Prin urmare, valoarea elementului di,j se va calcula astfel:
(k) (k1) (k1) (k1)
di,j = min{di,j , di,k + dk,j } (8.11)

Desi aceasta ultima relatie sugereaza un algoritm recursiv, o abordare iterativa este mai
eficienta atat n ceea ce priveste complexitatea timp, cat si din punctul de vedere al nece-
sarului de memorie. Se observa ca, matricea D (k) nu mai este necesara de ndata ce matricea
D (k+1) a fost calculata.
(n) (n)
D (n) = (di,j ), di,j = i,j , xi , xj V. (8.12)

Algoritm 65 Algoritmul Floyd-Warshall


1: procedure FloydWarshall1(n, C; D (n))
2: for i 1, n do
3: for j 1, n do
(0)
4: di,j ci,j
5: end for
6: end for
7: for k 1, n do
8: for i 1, n do
9: for j 1, n do
(k) (k1) (k1) (k1)
10: di,j min {di,j , di,k + dk,j }
11: end for
12: end for
13: end for
14: end procedure

In algoritmul 65 se poate renunta la indicii superiori, obtinandu-se astfel o economie de


spatiu de la (n3 ) la (n2 ) (a se vedea algoritmul 66).
Algoritmul 66 ne permite sa calculam distanta minima ntre oricare pereche de noduri
apatinand unui graf G. Se pune problema de a determina si componenta drumului minim
(multimea varfurilor intermediare) dintre doua noduri, nu numai valoarea distantei minime.
Acest lucru se poate realiza prin intermediul unei alte matrici P (k) asociata matricii D (k) .
(k)
Un element Pi,j va pastra nodul intermediar k ce conduce la cea mai mica valoare pentru

177
Algoritm 66 Algoritmul Floyd-Warshall (varianta optimizata)
1: procedure FloydWarshall2(n, C; D)
2: for i 1, n do
3: for j 1, n do
4: di,j ci,j
5: end for
6: end for
7: for k 1, n do
8: for i 1, n do
9: for j 1, n do
10: di,j min {di,j , di,k + dk,j }
11: end for
12: end for
13: end for
14: end procedure

(k) (k)
di,j conform formulei 8.11. Daca pi,j = 0 atunci cel mai scurt drum dintre nodurile xi si xj
este cel direct.
(0)
Valoarea elementelor matricii initiale P (0) este pi,j = 0.
(k)
Valoarea unui element pi,j se va calcula conform formulei urmatoare, tinand cont de
relatia 8.11: (
(k1) (k1) (k1) (k1)
(k) pi,j , daca di,j di,k + dk,j
pi,j = (k1) (k1) (k1) (8.13)
k , daca di,j > di,k + dk,j
Se va calcula secventa de matrici P (0) , P (1) , . . ., P (n) mpreuna cu secventa D (0) , D (1) ,
. . ., D (n) . La final vom avea: P = P (n) , D = D (n) .
Printr-un rationament analog, se poate demonstra ca nu este nevoie sa construim efectiv
sirul de matrici P (0) , P (1) , . . ., P (n) . Algoritmul 67 prezinta varianta extinsa a algoritmului
66, n cadrul acestuia fiind adaugat si suportul pentru determinarea drumului de cost minim
dintre oricare pereche de noduri a grafului.

Exemplul 8.4 Fie graful din figura 8.2. Matricea costurilor asociat
a acestui graf este urmatorul:

0 1 + 21 +
+ 0 8 + 4

C = 3 + 0
12 +
+ + 9 0 +
+ + 3 + 0
Sirul de matrice D k , rezultat al aplic
arii algoritmului Roy-Floyd-Warshall pentru acest
graf, este:

178
Algoritm 67 Algoritmul Floyd-Warshall (reconstruirea drumului optim)
1: procedure FloydWarshall3(n, C; D, P )
2: for i 1, n do
3: for j 1, n do
4: di,j ci,j
5: pi,j 0
6: end for
7: end for
8: for k 1, n do
9: for i 1, n do
10: for j 1, n do
11: if (di,j > di,k + dk,j ) then
12: di,j di,k + dk,j }
13: pi,j k
14: end if
15: end for
16: end for
17: end for
18: end procedure
19: procedure PrintDrum(i, j, P )
20: if (pi,j = 0) then
21: Output {(i,j)}
22: else
23: call P rintDrum(i, pi,j , P )
24: call P rintDrum(pi,j , j, P )
25: end if
26: end procedure


0 1 21 0 1 21

0 8 4

0 8 4
D0 =
3 0 12
D1 =
3 4 0 12

9 0 9 0
3 0 3 0

0 1 9 21 5 0 1 9 21 5

0 8 4


11 0 8 20 4

D2 =
3 4 0 12 8
D3 =
3 4 0 12 8

9 0 12 13 9 0 17
3 0 6 7 3 15 0

0 1 9 21 5 0 1 8 20 5

11 0 8 20 4


10 0 7 19 4

D4 =
3 4 0 12 8
D5 =
3 4 0 12 8

12 13 9 0 17 12 13 9 0 17
6 7 3 15 0 6 7 3 15 0

Inchiderea tranzitiv
a
Definitia 8.1 Fie G = (V, E) un graf orientat, simplu si finit, V = {x1 , x2 , . . . , xn }.

Inchiderea tranzitiv a a grafului G este un graf G+ = (V, E + ), unde E + = {(xi , xj )|

179
un drum de la xi la xj n G}.

Definitia 8.2 O relatie binara peste o multime A = {1, 2, . . . , n} se defineste ca fiind o


submultime a produsului cartezian A A ( A A). (a, b) se mai poate scrie ab.

Definitia 8.3 Inchiderea tranzitiva a relatiei binare este o relatie binar a cu proprietatea

ca i, j {1, . . . , n}, (i, j) i1 , . . . , im {1, 2, . . . , n} astfel nc
at (i1 , i2 ) , (i2 , i3 )
, . . ., (im1 , im ) si i = i1 , j = im .

Notam n = . . . , unde n reprezinta compunerea relatiei cu ea nsasi de n ori.


| {z }
n ori
Atunci se poate defini n modul urmator:
[
= n = ( ) . . . ( . . . ) . . . (8.14)
nN

O relatie binara peste multimea {1, 2, . . . , n} se poate reprezenta printr-o matrice R,


ale carei elemente sunt definite astfel:
(
1 , daca (i, j)
ri,j = (8.15)
0 , daca (i, j) /

Pentru relatia 2 = i corespunde matricea R R, pentru relatia 3 = i


corespunde matricea R R R etc.
Daca notam cu R(k) matricea corespunzatoare relatiei k , atunci avem R(k) = Rk =
. . R}. Conform formulei 8.14, pentru determinarea este nevoie de calculul unei
| .{z
R
k ori
serii de matrici R0 , R1 , . . . , Rn , unde un element ri,j
k
Rk se calculeaza dupa formula:
(
k1 k1 k1
k 1 , daca ri,j = 1 sau (ri,k = 1 rk,j = 1)
ri,j = (8.16)
0 , altfel .

Inchiderea tranzitiva a unei relatii binare prezinta multiple aplicatii, ca subproblema n


cazul unor probleme computationale, cum ar fi: construirea unui automat de parsare utilizat
la realizarea unui compilator, evaluarea interogarilor recursive efectuate asupra unei baze de
date sau analiza elementelor accesibile ntr-o retea de tranzitie asociata unui sistem paralel
sau distribuit.
Prima varianta de a determina nchiderea tranzitiva presupune atribuirea unui cost unitar
(= 1) arcelor multimii E, urmata de aplicarea algoritmului Roy-Floyd-Warshall. Vom obtine
o matrice, n care, di,j = + daca nu exista un drum de la xi la xj sau di,j = c < n daca
exista.
In cadrul celei de-a doua variante de calcul (a se vedea algoritmul 68), se nlocuiesc n
algoritmul 65 operatiile min cu (sau logic) si respectiv + cu (si logic).
Semnificatia valorii unui element al matricii D (k) este urmatoarea: dki,j = 1 daca exista un
drum de la xi la xj n graful G avand varfurile intermediare numai din multimea {x1 , . . . , xk }.
(
1 , daca i = j sau (xi , xj ) E
d0i,j = (8.17)
0 , daca i 6= j si (xi , xj )
/E
Exista astfel un drum de la xi la xj n graful G avand varfurile intermediare numai
din multimea {x1 , . . . , xk } (dki,j = 1) daca exista un drum de la xi la xj avand varfurile

180
Algoritm 68 Algoritm de calcul a nchiderii tranzitive
1: procedure InchidereTranzitiva(n, C; D)
2: for i 1, n do
3: for j 1, n do
4: if ((i = j) (ci,j 6= +)) then
5: d0i,j 1
6: else
7: d0i,j 0
8: end if
9: end for
10: end for
11: for k 1, n do
12: for i 1, n do
13: for j 1, n do
14: dki,j dk1 k1 k1
i,j (di,k dk,j )
15: end for
16: end for
17: end for
18: end procedure

intermediare numai din multimea {x1 , . . . , xk1 } sau daca exista un drum de la xi la xk
avand varfurile intermediare numai din multimea {x1 , . . . , xk1 } si daca exista un drum de
la xk la xj avand varfurile intermediare numai din multimea {x1 , . . . , xk1 }:

dki,j = dk1 k1 k1
i,j (di,k dk,j ) (8.18)

Algoritm 69 Algoritm de calcul a nchiderii tranzitive (varianta)


1: procedure InchidereTranzitiva(n, C; D)
2: for i 1, n do
3: for j 1, n do
4: if ((i = j) (ci,j 6= +)) then
5: di,j 1
6: else
7: di,j 0
8: end if
9: end for
10: end for
11: for k 1, n do
12: for i 1, n do
13: if (di,k = 1) then
14: for j 1, n do
15: di,j di,j dk,j
16: end for
17: end if
18: end for
19: end for
20: end procedure

181
Exemplul 8.5 Fie graful din figura 8.2, din care am eliminat arcul (1, 2). Matricea cos-
turilor asociata acestui graf modificat este:

0 21
0 8 4

C= 3 0 12

9 0
3 0

1 0 0 1 0 1 0 0 1 0

0 1 1 0 1


0 1 1 0 1

D0 =
1 0 1 1 0
D1 =
1 0 1 1 0

0 0 1 1 0 0 0 1 1 0
0 0 1 0 1 0 0 1 0 1

1 0 0 1 0 1 0 0 1 0
0 1 1 0 1 1 1 1 1 1

D2 =
1 0 1 1 0
D3 =
1 0 1 1 0

0 0 1 1 0 1 0 1 1 0
0 0 1 0 1 1 0 1 1 1

1 0 1 1 0 1 0 1 1 0
1 1 1 1 1 1 1 1 1 1

D4 =
1 0 1 1 0 D5 = 1
0 1 1 0

1 0 1 1 0 1 0 1 1 0
1 0 1 1 1 1 0 1 1 1

8.3 Exercitii
1. (Cutii n-dimensionale) Sa consideram o cutie n-dimensionala, data prin lungimea
fiecarei laturi (o cutie bidimensionala este un dreptunghi, o cutie tridimensionala este
un paralelipiped etc.).
Problema cere analizarea unui grup de k cutii n-dimensionale: sa se elaboreze un algo-
ritm ce determina o secventa de cutii de lungime maxima (b1 , b2 , . . .) din grupul de k
cutii, astfel ncat fiecare cutie bi sa poata fi introdusa n cutia bi+1 .
O cutie D = (d1 , d2 , . . . , dn ) poate fi inclusa ntr-o cutie E = (e1 , e2 , . . . , en ) numai daca
exista o permutare a multimii valorilor laturilor cutiei D astfel ncat lungimea fiecarei
laturi a cutiei D, dupa reorganizare, sa nu depaseasca lungimea laturii corespunzatoare
din cutia E.
De exemplu cutia (2, 6) poate fi introdusa n cutia (7, 3) ((6, 2) (7, 3)), iar cutia
(1, 6, 13) poate fi introdusa n cutia (11, 15, 3) ((6, 13, 1) (11, 15, 3)).
Cutiile egale pot fi introduse una ntr-alta. Masurile laturilor sunt numere reale mai
mici sau egale cu 1000. Numarul de cutii nu depaseste 100, iar numarul de dimensiuni
nu depaseste 20.
(Tuymaada, 1997)

2. Sunteti angajat la o companie ce asigura servicii de transport marfuri en-gross. Clientii


au magazine n orase din toata tara si ei sunt aprovizionati din depozitele locale ale
companiei.

182
Pentru o aprovizionare optima, compania vrea sa investeasca n construirea unui depozit
central din care sa se aprovizioneze toate zonele; va fi, deci, necesar ca acest depozit sa
fie plasat ntr-unul din orase, n asa fel ncat timpul total de livrare din acest depozit
catre toate orasele sa fie minim. Camioanele companiei transporta marfa la depozitele
locale si se ntorc la depozitul central. Timpul de livrare este format din timpul necesar
parcurgerii drumului de la depozitul central la oras si napoi (se presupune ca soferul
va urma de fiecare data calea cea mai rapida, iar n fiecare oras depozitul local se afla
amplasat la intrare) iar timpul de descarcare al camionului la destinatie este de exact 30
de minute. Drumurile ce leaga orasele sunt de aceeasi calitate, dar pot exista drumuri
ce iau mai mult timp ntr-un sens decat n sens invers. Pot fi, de asemenea, drumuri
cu sens unic.
Pentru a simplifica modelul, s-a stabilit pentru fiecare oras o lista cu toate drumurile
ce pleaca din oras spre celelalte orase si cat timp ia parcurgerea fiecarui drum.
(Concurs ACM Zona Pacificul de Sud)

3. Se da reteaua hidrografica a unei tari constituita din multimea raurilor si afluentii lor.
Cele N rauri (2 n 1000) sunt numerotate de la 1 la N. Legatura dintre un rau v
si un afluent al sau u este specificata prin perechea (u, v).
Pentru a se putea face estimari cu privire la potentialul risc de inundatie pe cursul unui
rau trebuie sa se calculeze debitul fiecarui rau n parte.
Debitul unui izvor se defineste ca fiind cantitatea de apa ce trece prin sectiunea izvorului
n unitatea de timp. Debitul raului u la varsare va fi egal cu debitul izvorului raului u
plus suma debitelor afluentilor la varsare n raul u.
Se cere sa se realizeze un algoritm care sa calculeze debitul la varsare al fiecarui rau.
4. Sa se determine drumul de lungime minima necesar deplasarii unui cal pe o tabla de sah
(avand dimensiunile 88), de la un punct de plecare la unui de sosire, stiind ca pe tabla
exista si casute-capcana. Calul n drumul sau nu poate trece printr-o casuta-capcana.
(ACM, Eastern Europe, 1994)

5. Un traducator gaseste o carte scrisa cu litere latine, n care nsa ordinea literelor din
alfabet este schimbata. La sfarsitul cartii, se afla un index de cuvinte complet si necon-
tradictoriu.
Se cere sa se elaboreze un algoritm ce determina ordinea literelor din noul alfabet.
(ONI, 1991)

6. Profesorul Heif realizeaza niste experimente cu o specie de albine din America de Sud
pe care le-a descoperit n timpul unei expeditii n jungla Amazonului. Mierea produsa
de aceste albine este superioara din punct de vedere calitativ mierii produse de albinele
din Europa sau din America de Nord. Din pacate, aceste albine nu se nmultesc n
captivitate. Profesorul Heiff crede ca pozitiile larvelor (albine lucratoare, regina) din
fagure depind de conditiile de mediu, care sunt diferite n laborator fata de padurea
tropicala.
Ca un prim pas pentru a-si verifica teoria, profesorul Heiff doreste sa calculeze distanta
dintre pozitiile larvelor. Pentru aceasta el masoara distanta dintre celulele fagurelui n
care sunt plasate larvele. El a numerotat celulele alegand n mod arbitrat una, careia
i-a atribuit valoarea 1, celelalte celule ramase fiind apoi numerotate circular, n sensul
acelor de ceas, ca n figura.

183
__ __ __ __
__/ \__/ \__/ \__/ \__
__/ \__/ \__/53\__/ \__/ \__
/ \__/ \__/52\__/54\__/ \__/ \
\__/ \__/51\__/31\__/55\__/ \__/
/ \__/50\__/30\__/32\__/56\__/ \
\__/49\__/29\__/15\__/33\__/57\__/
/ \__/28\__/14\__/16\__/34\__/ \
\__/48\__/13\__/ 5\__/17\__/58\__/
/..\__/27\__/ 4\__/ 6\__/35\__/ \
\__/47\__/12\__/ 1\__/18\__/59\__/
/..\__/26\__/ 3\__/ 7\__/36\__/ \
\__/46\__/11\__/ 2\__/19\__/60\__/
/..\__/25\__/10\__/ 8\__/37\__/ \
\__/45\__/24\__/ 9\__/20\__/61\__/
/..\__/44\__/23\__/21\__/38\__/ \
\__/70\__/43\__/22\__/39\__/62\__/
/ \__/69\__/42\__/40\__/63\__/ \
\__/ \__/68\__/41\__/64\__/ \__/
/ \__/ \__/67\__/65\__/ \__/ \
\__/ \__/ \__/66\__/ \__/ \__/
\__/ \__/ \__/ \__/ \__/
\__/ \__/ \__/ \__/

De exemplu, doua larve pozitionate n celulele 19 si 30 se afla la distanta de 5 celule.


Unul din drumurile minime ce unesc cele doua celule trece prin celulele 19-7-6-5-15-30.
Sa se scrie un algoritm ce calculeaza distanta minima dintre oricare doua celule.
Datele de intrare constau din doua numere naturale u si v (u, v 10.000) reprezentand
numarul celulelor ntre care se doreste sa se determine distanta minima.
(ACM Final, 1999)

7. Intr-o tara exista N orase, unele dintre ele legate prin autostrazi cu dublu sens. Se
presupune ca din orice oras se poate ajunge n oricare altul. Taxa este aceeasi pentru
orice autostrada (1 Ron), dar trebuie platita din nou la intrarea pe o noua autostrada.
Distanta dintre doua orase este considerata egala cu taxa minima pentru a ajunge
dintr-unul n altul.
Se doreste introducerea unui abonament a carui plata permite circulatia pe autostrazi
fara nici o taxa suplimentara. Costul abonamentului este de 100 de ori mai mare decat
distanta maxima ntre doua perechi de orase.
Sa se scrie un algoritm ce calculeaza costul abonamentului.
(CEOI, 1996)

8. In fiecare noapte, un print intra n subsolurile unui castel unde locuieste o printesa.
Drumul catre printesa este ca un labirint. Sarcina printului este sa se grabeasca si sa
gaseasca drumul prin labirint catre printesa, deoarece trebuie sa se ntoarca n zori.
Acesta are unelte potrivite cu ajutorul carora poate sa sparga numai un singur perete
pentru a-si scurta drumul catre destinatie.
Sa se elaboreze un algoritm care:

184
(a) determina lungimea celui mai scurt drum din labirint din locul n care printul
intra n labirint pana n locul n care traieste printesa;

(b) determina lungimea celui mai scurt drum n conditiile de mai sus, daca se sparge
un perete si numai unul.

Castelul are o forma dreptunghiulara cu m n camere (1 n, m 100). Fiecare


camera poate avea pereti spre E, S, V si N codificati cu un numar ntre 1 si 14: 1, 2,
4, 8. Se stie ca nu exista camere fara nici un perete (codul > 0) si nici camere care sa
aiba pereti n toate directiile (codul < 15).
Lungimea celui mai scurt drum se defineste ca fiind numarul de camere dintre sursa si
destinatie inclusiv.
Datele de intrare constau din M linii ce contin fiecare N numere ntregi reprezentand
codificarea peretilor din fiecare camera, urmate de coordonatele printului si ale printesei.
De exemplu, pentru datele de intrare

5 8
14 10 10 10 10 10 10 9
12 10 10 10 10 10 10 3
5 14 9 14 8 11 14 9
4 10 2 8 3 12 10 1
6 11 14 2 10 2 10 3
1 1 5 8

avem rezultatul 26 si 12.


(ACM Bucuresti, 1999)

185
Appendix A

Metoda Backtracking

Metoda Backtracking este una dintre cele mai cunoscute metode de elaborare a algoritmilor.
Metoda se aplica numai n situatia n care nu exista nici o alta modalitate de rezolvare a
problemei propuse deoarece timpul de executie depinde exponential de dimensiunea datelor
de intrare. Se utilizeaza o structura de tip stiva iar metoda poate fi implementata atat
iterativ cat si recursiv. Metoda se aplica acelor probleme n care solutia se poate reprezenta
sub forma unui vector x = (x1 , x2 , . . . , xn ) A1 A2 . . . An , unde multimile Ai , (i = 1, n)
sunt finite si nevide (|Ai| = ni > 0). In plus, pentru fiecare problema n parte este necesar ca
solutia x1 , x2 , . . . , xn sa satisfaca anumite conditii interne (x1 , x2 , . . . , xn ) (vezi algoritmul
70).

Algoritm 70 Algoritm Backtracking (varianta generala)


1: procedure Backtracking(n, A)
2: k1
3: xk prima valoare din afara domeniului de valori
4: while (k > 0) do
5: gasit f alse
6: while ( valori neverif icate pentru xk ) (gasit 6= true) do
7: xk urmatoarea valoare neverif icata
8: if (k (x1 , x2 , . . . , xk ) = true) then
9: gasit true
10: end if
11: end while
12: if (gasit = true) then
13: if (k = n) then
14: call Af is Solutie(x1 , . . . , xn )
15: else
16: k k+1
17: xk prima valoare din afara domeniului de valori
18: end if
19: else
20: k k1
21: end if
22: end while
23: end procedure

De exemplu, sa consideram doua multimi, S1 = {a, b, c} si S2 = {m, n}. Se doreste sa se


determine perechile (x1 , x2 ), cu x1 A1 si x2 A2 , cu proprietatea ca daca x1 este a sau b

186
atunci x2 nu poate fi n. Rezolvarea conduce la urmatoarele variantele: (a, m), (b, m), (c, m), (c, n).
Din cele sase solutii posibile (a, m), (a, n), (b, m), (b, n), (c, m), (c, n), numai acestea patru
ndeplinesc conditiile interne.
Multimea A = A1 A2 ... An se numeste spatiul solutiilor posibile, iar elementele
x A ce satisfac conditiile interne se numesc solutii rezultat. Ne propunem determinarea
tuturor solutiilor rezultat, eventual pentru a alege dintre ele pe aceea ce minimizeaza sau
maximizeaza o functie obiectiv.
Metoda Backtracking evita generarea tuturor solutiilor posibile (toate elementele pro-
dusului cartezian A1 A2 ... An ). Construirea unei solutii se face n mai multi pasi,
elementele vectorului X primind valori pe rand: elementului xk Ak , k = 1, n, i se atribuie
o valoare numai dupa ce au fost atribuite valori pentru componentele x1 A1 , x2 A2 ,
. . ., xk1 Ak1 . Metoda trece la atribuirea unei valori pentru xk+1 Ak+1 doar daca
xk mpreuna cu x1 , x2 , . . . , xk1 verifica conditiile de continuare, notate cu k (x1 , x2 , . . . , xk ).
Daca aceste conditii k (x1 , x2 , . . . , xk ) sunt ndeplinite se trece la cautarea unei valori pentru
elementul xk+1 Ak+1 al solutiei.
Nendeplinirea conditiilor are urmatoarea semnificatie: pentru orice alegere xk+1 Ak+1 ,
. . . , xn An , nu vom putea ajunge la o solutie rezultat n care conditiile interne sa fie
ndeplinite. In aceasta situatie va trebui sa ncercam o alta alegere pentru xk , acest lucru fiind
posibil doar daca nu am epuizat toate valorile disponibile din multimea Ak . Daca multimea
Ak a fost epuizata va trebui sa micsoram valoarea variabilei k cu o unitate (k k 1), si sa
trecem la alegerea unei alte valori pentru elementul xk1 Ak1 . Micsorarea valorii curente
a variabilei k cu o unitate da numele metodei si semnifica faptul ca atunci cand nu putem
avansa, vom urmari napoi secventa curenta din solutie. Faptul ca valorile v1 , v2 , . . . , vk1 ,
ale componentelor x1 , x2 , . . . , xk1 , satisfac conditiile de continuare, nu este suficient pentru
a garanta obtinerea unei solutii ale carei prime k 1 componente coincid cu aceste valori.
O alegere buna a conditiilor de continuare conduce la reducerea numarului de calcule,
fiind de dorit ca aceste conditii de continuare sa fie nu numai necesare, dar si suficiente
pentru obtinerea unei solutii. Conditiile interne devin chiar conditii de continuare pentru
k = n. Orice vector solutie se obtine progresiv ncepand cu prima componenta si deplasandu-
ne catre ultima, cu eventuale reveniri asupra valorilor atribuite anterior. Anumite valori sunt
consumate n urma unor atribuiri sau ncercari de atribuire esuate din cauza nendeplinirii
conditiilor de continuare.
Exista doua variante fata de metoda standard:

solutiile pot avea numar variabil de componente;

dintre solutii se alege cea care minimizeaza sau maximizeaza o functie cost sau o functie
obiectiv data.

Exemplul A.1 Enuntul problemei: Se cere s a se genereze permut ari de n elemente.


n
Rezolvare: Spatiul solutiilor este A = i=1 Ai , Ai = {1, 2, . . . , n}, |Ai | = n. Solutia va
fi obtinuta n vectorul x = (x1 , x2 , . . . , xn ) A1 A2 . . . An .
La pasul k se ncearca atribuirea unui alt element din multimea Ak lui xk . Vom trece la
pasul k + 1 doar daca sunt ndeplinite conditiile de continuare: (x1 , x2 , . . . , xk ) nu contine
elemente care se repeta. Deoarece aceast a verificare se face la fiecare pas, este suficient sa
verificam daca valoarea elementului xk nu se reg aseste printre valorile x1 , x2 , . . . , xk1 (vezi
functia CanContinue() din algoritmul 71).

Exemplul A.2 Fie n = 4. Atunci Ai va fi compus din elementele Ai = {1, 2, 3, 4}.


Pentru k = 1, x1 va primi valuarea 1 (vezi linia 15): 1

187
Pentru k = 2, se verifica x2 = 1, ns a nu se respect a conditiile de continuare (linia 16).
Astfel se trece la urmatoarea valoare, x2 = x1 + 1 = 2 (linia 15). Pentru aceast a configuratie
conditiile de continuare sunt ndeplinite, si se trece la pasul urm ator, k = k + 1 = 3 (linia
24): 1 2
Se verifica valorile 1, 2 si 3 pentru x3 (linia 16), dintre care, numai ultima satisface
conditiile de continuare: 1 2 3
Ajunsi la ultimul pas, k = 4, se verific urma verificarii
a valorile 1, 2, 3 si 4 pentru x4 . In
conditiilor de continuare pentru x4 = 4, se urm areste trecerea la pasul k = k+1 = 5. Deoarece
suntem la ultimul element al vectorului x (linia 21), se obtine prima solutie, ce se si afiseaza:
1 2 3 4
Mai departe, nu mai exista valori netestate pentru x4 (linia 14), si revenim la nivelul
anterior k = k 1 = 3 (linia 28). Aici urm atoarea valoare din domeniul de valori neverificata
este x3 = 4 (linia 15). Solutia partiala respecta conditiile de continuare (linia 16), astfel ncat
se trece la nivelul urmator (linia 24), k = k + 1 = 4: 1 2 4
Pentru x4 sunt verificate valorile, 1, 2, 3, 4 (liniile 1516), dintre acestea numai valoarea
3, conducand la o solutie finala (linia 21): 1 2 4 3 Algoritmul se continu a n acelasi
mod pana se obtin toate solutiile:

(1, 2, 3, 4)




(1, 2, 4, 3)


(1, 3, 2, 4)





(1, 3, 4, 2)


(1, 4, 2, 3)


(1, 4, 3, 2)




(2, 1, 3, 4)




(2, 1, 4, 3)


(2, 3, 1, 4)





(2, 3, 4, 1)


...

O varianta de implementare a algoritmului 71 n limbajul C este:


#include <stdio.h>

#define TRUE 1
#define FALSE 0
#define NN 100

int n;
int a[NN];

/**
* Functia citeste valorile datelor de intrare.
*/
void readInput(void) {
//se citeste numarul de elemente n
printf("n="); scanf("%d", &n);
}

188
Algoritm 71 Algoritm pentru generare permutari (varianta backtracking)
Input: n - num arul de elemente din multimea initial
a
1: function CanContinue(A, k)
2: for i 1, k 1 do
3: if (ai = ak ) then
4: return f alse
5: end if
6: end for
7: return true
8: end function

9: procedure Permutari(n)
10: k1
11: xk 0
12: while (k > 0) do
13: gasit f alse
14: while (xk + 1 n) (gasit 6= true) do
15: xk xk + 1
16: if ((CanContinue(X, k)) = true) then
17: gasit true
18: end if
19: end while
20: if (gasit = true) then
21: if (k = n) then
22: Output {x1 , . . . , xn }
23: else
24: k k+1
25: xk 0
26: end if
27: else
28: k k1
29: end if
30: end while
31: end procedure

/**
* Functia afiseaza solutia calculata a problemei.
*/
void list(void) {
int i;

for (i = 1; i <= n; i++)


printf("%d ", a[i]);
printf("\n");
}

/**
* Functia de continuare: aici se verifica daca elementul de pe pozitia k
* nu se mai afla in sirul A.
*/

189
int canContinue(int k) {
int i;

for (i = 1; i <= k - 1; i++)


if (a[k] == a[i])
return FALSE;
return TRUE;
}

void run(void) {
int i;
int gata;

//initializare
i = 1; a[i] = 0;
while (i > 0) {
gata = FALSE;
while ((a[i] + 1 <= n) && !gata) {
/**
* cat timp exista elementul urmator si nu sunt verificate
* conditiile de continuare
*/

//treci la elementul urmator


a[i]++;
//verifica conditiile de continuare
if (canContinue(i))
gata = TRUE;
//sau: gata = canContinue(i);
}

if (gata)
//daca s-au verificat conditiile de continuare
if (i == n)
//daca suntem la ultimul element afisam solutia
list();
else { //altfel trecem la elementul urmator
i++;
a[i] = 0;
}
else
//altfel revenim la un element anterior si alegem o alta valoare
i--;
}
}

void main(void) {
read_data();
run();
}

Exemplul A.3 Enuntul problemei: S


a se genereze combin ate m (0
ari de n luate c

190
m n).
Rezolvare: Spatiul solutiilor este A = m i=1 Ai , Ai = {1, 2, . . . , n}, |Ai | = n. Solutia va
fi obtinuta n vectorul x = (x1 , x2 , . . . , xm ) A1 A2 . . . Am (vezi algoritmul 72).
La pasul k se ncearca atribuirea unui alt element din multimea Ak lui xk . Conditia de
continuare se refera la proprietatea c a elementele vectorului solutie trebuie s a respecte relatia
x1 < x2 < . . . < xk . Acest lucru se obtine foarte usor prin urm atorul procedeu: la trecerea la
pasul k + 1, variabila xk+1 va primi, ca valoare initial a, valoarea curent a a variabilei xk (vezi
linia 15 din algorimul 72).
Pentru n = 5 si m = 3 avem: A = 3i=1 Ai = A1 A2 A3 , unde A1 = A2 = A3 =
{1, 2, 3, 4, 5}. Solutiile obtinute sunt:


(1, 2, 3)




(1, 2, 4)




(1, 2, 5)




(1, 3, 4)

(1, 3, 5)


(1, 4, 5)



(2, 3, 4)




(2, 3, 5)




(2, 4, 5)


(3, 4, 5)

Algoritm 72 Algoritm pentru generare combinari (varianta backtracking)


1: procedure Combinari(n, m)
2: k1
3: xk 0
4: while (k > 0) do
5: gasit f alse
6: while (xk + 1 n) (gasit 6= true) do
7: xk xk + 1
8: gasit true
9: end while
10: if (gasit = true) then
11: if (k = m) then
12: Output {x1 , . . . , xm }
13: else
14: k k+1
15: xk xk1
16: end if
17: else
18: k k1
19: end if
20: end while
21: end procedure

In continuare prezentam o implementare a algoritmului 72 n limbajul C:

191
#include <stdio.h>

#define TRUE 1
#define FALSE 0
#define NN 100

int n, m;
int a[NN];

/**
* Functia citeste valorile datelor de intrare.
*/
void readInput(void) {
printf("n="); scanf("%d", &n);
printf("m="); scanf("%d", &m);
}

/**
* Functia afiseaza solutia calculata de functia run().
*/
void list(void) {
int i;

for (i = 1; i <= m; i++)


printf("%d ", a[i]);
printf("\n");
}

void run(void) {
int i;
int gata;

//initializare
i = 1; a[i] = 0;
while (i > 0) {
gata = FALSE;
while ((a[i] + 1 <= n) && !gata) {
/**
* cat timp exista elementul urmator si nu sunt verificate
* conditiile de continuare, treci la elementul urmator
*/
a[i]++;
gata = TRUE;
}

if (gata)
//daca s-au verificat conditiile de continuare
if (i == m)
//daca suntem la ultimul element afisam solutia
list();
else { //altfel trecem la elementul urmator
i++;

192
a[i] = a[i - 1];
}
else
//altfel revenim la un element anterior si alegem o alta valoare
i--;
}
}

void main(void) {
readInput();
run();
}

Exemplul A.4 Problema damelor


Enuntul problemei: Pe o tabla de sah cu n linii si n coloane (n n p atrate), sa se
pozitioneze n dame astfel ncat sa nu se atace reciproc.
Rezolvare: Tabla de sah va fi reprezentat a prin matricea A = (aij ), i, j = 1, n. Doua
dame aflate n pozitiile aij si akl se vor ataca reciproc dac
a:

i = k sau j = l sau |i k| = |j l|

Prima observatie care se poate deduce se refer a la faptul c


a dou
a dame nu trebuie sa se
afle pe aceeasi linie sau pe aceeasi coloan a.
Notam cu V = {1, 2, . . . , n} multimea celor n coloane ale tablei de sah. Vectorul solutiei
rezultat X = (x1 , x2 , . . . , xn ) V ... V are urm atoarea semnificatie: componenta i a
vectorului (i = 1, n) reprezinta linia i de pe tabla de sah. Valoarea xi a acestei componente
reprezinta coloana pe care se aseaza dama de pe linia i (vezi algoritmul 73).
La nceput, toate damele sunt n afara tablei de sah, prin urmare xi = 0, i = 1, n.
La pasul k vom ncerca asezarea damei k (de pe linia k) pe coloana urm atoare (xk + 1).
Initial dama a k-a este pozitionata n afara tablei (xk = 0 / V ). O dam a poate fi asezata pe
o alta coloana daca vechea pozitie satisface conditia xk < n. Noua pozitie (xk + 1) trebuie sa
satisfaca conditiile interne k :
i, 1 i < k, xi 6= xk + 1 (nu sunt pe aceeasi coloan
a);
|i k| =
6 |xi (xk + 1)| (nu sunt pe aceeasi diagonal
a).

Daca xk < n si k sunt adevarate, dama de pe linia k va fi pozitionat a pe coloana xk + 1


si se trece la pasul k + 1, corespunzator pozition
arii damei k + 1. In caz contrar, dama k nu
poate fi asezata pe tabla si va trebui s am pozitionarea damei k 1.
a relu

Exemplul A.5 Problema colorarii h artilor


Enuntul problemei: Sa se coloreze o hart a reprezent and n tari folosind m culori
etichetate 1, . . . , m.
Rezolvare: Datele necesare pentru descrierea h artii vor fi reprezentate de o matrice
A = (aij ), i, j = 1, n, ale carei elemente au urm atoarea semnificatie:
(
1 , daca tara i are frontier
a comun a cu tara j
aij =
0 , n caz contrar

Matricea A Mnn este simetric


a si elementele de pe diagonala principal
a sunt nule:
aij = aji si aii = 0.

193
Algoritm 73 Algoritm Problema damelor
Input: n - num arul de linii/coloane de pe tabla de sah
1: function CanContinue(A, k)
2: for j 1, k 1 do
3: if (aj = ak ) ((k j) = |ak aj |) then
4: return f alse
5: end if
6: end for
7: return true
8: end function

9: procedure DameBacktracking(n)
10: i1
11: xi 0
12: while (i > 1) do
13: gasit f alse
14: while (xi + 1 n) (gasit 6= true) do
15: xi xi + 1
16: if (CanContinue(X, i) = true) then
17: gasit true
18: end if
19: end while
20: if (gasit = true) then
21: if (i = n) then
22: call Af is Solutie(x1 , . . . , xn )
23: else
24: ii+1
25: xi 0
26: end if
27: else
28: i i1
29: end if
30: end while
31: end procedure

Fie C = {c1 , c2 , . . . , cm } multimea culorilor.


Vectorul solutiei rezultat X = (x1 , x2 , . . . , xn ) C ... C, are urm atoarea semnificatie:
tara i are repartizata culoarea xi C, i = 1, n. La nceput, fiec arei t
ari i se va atribui valoarea
0.
La pasul k ncercam sa atribuim o nou a culoare (xk + 1) pentru tara k, k = 1, n, daca
sunt ndeplinite conditiile:

1. xk < m (daca mai exista alte culori neatribuite pentru tara k);
2. pentru i, 1 i < k cu ai,k = 1 avem xi 6= xk + 1.

Daca cele doua conditii anterioare sunt ndeplinite, atunci noua culoare pentru tara k va
caz contrar,
fi xk + 1, si se va trece la pasul k + 1 (stabilirea unei culori pentru tara k + 1). In
tarii k nu i se mai poate atribui o alt a culoare si ne ntoarcem la pasul k 1, corespunzator
alegerii unei noi culori pentru tara k 1. Algoritmul se termin a atunci cand nu mai exista
nici o culoare neverificata pentru tara 1 (vezi algoritmul 74).

194
Algoritm 74 Algoritm Problema colorarii hartilor

A - matricea de adiacent
a/de vecin
atate a t
arilor
Input: n - num arul de tari


m - num arul de culori disponibile
1: function CanContinue(A, X, k)
2: for j 1, k 1 do
3: if (xj = xk ) (aj,k = 1) then
4: return f alse
5: end if
6: end for
7: return true
8: end function

9: procedure ColorBacktracking(A, n, m)
10: k1
11: xk 0
12: while (k > 0) do
13: gasit f alse
14: while (xk + 1 m) (gasit 6= true) do
15: xk xk + 1
16: gasit CanContinue(A, X, k)
17: end while
18: if (gasit = true) then
19: if (k = n) then
20: call Af is Solutie(x1 , . . . , xn )
21: else
22: k k+1
23: xk 0
24: end if
25: else
26: k k1
27: end if
28: end while
29: end procedure

Exemplul A.6 Enuntul problemei: Se d a o suma s si n tipuri de monede av


and valorile
a1 , a2 , . . . , an lei. Realizati un algoritm care s
a determine o modalitate de plat a a sumei s
utilizand un numar minim de monede.
Rezolvare:
#include <stdio.h>

#define FALSE 0
#define TRUE 1

#define NN 100
#define MAX 10000

int suma_plata; //suma ce trebuie platita


int n; //numarul de monezi

195
int limit[NN]; //limit[i] numarul maxim de monede de tipul val[i]
int val[NN]; //val[i] valoarea unei monede
int taken[NN]; //cate monede de valoare val[i] au fost luate
int minim; //numarul minim de monede cu care se poate face plata
int keep[NN]; //solutia optima pina la momentul curent

/**
* Se citesc datele de intrare: suma de plata, numarul de
* tipuri de monezi si valoarea fiecarui tip.
*/
void readInput(void) {
int i;

printf("Suma= "); scanf("%d", &suma_plata);


printf("Numarul de tipuri de monede: "); scanf("%d", &n);

for (i = 0; i < n; i++) {


printf("val[%d]=", i); scanf("%d", &val[i]);
limit[i] = suma_plata / val[i];
}
}

/**
* Se verifica daca sunt satisfacute conditiile de continuare:
* suma partiala sa nu depaseasca valoarea totala de platit.
* @param k - pasul curent
*/
int canContinue(int k) {
int i, suma_tmp;

suma_tmp = 0;
for (i = 0; i <= k; i++)
suma_tmp += val[i] * taken[i];
if ((k == n - 1 && suma_tmp == suma_plata)
|| (k != n - 1 && suma_tmp <= suma_plata))
return TRUE;
else
return FALSE;
}

/**
* Se pastreaza solutia actuala (taken) numai daca este
* mai buna decat cea anterioara (keep).
*/
void final(void) {
int i;
int monezi = 0;

//se numara cate monede sunt in solutie


for (i = 0; i < n; monezi += taken[i], i++);
if (monezi < minim) {
minim = monezi;

196
for (i = 0; i < n; i++)
keep[i] = taken[i];
}
}

/**
* Se afiseaza solutia optima sau mesajul Nu avem solutie.
*/
void print(void) {
int i, first = 0;

if (minim != MAX) { //verificam daca am obtinut o solutie


printf("%d =", suma_plata);
for (i = 0; i < n; i++)
if (keep[i] != 0) {
printf((first == 1) ? " + " : " ");
printf("%d X %d", keep[i], val[i]);
first = 1;
}
printf("\n");
}
else
printf("Nu avem solutie! \n");
}

/**
* Implementarea metodei backtracking.
*/
void run(void) {
int i;
int gata; /* are valoarea TRUE cand sunt verificate conditiile
de continuare */

minim = MAX;
i = 0; taken[i] = -1;
while (i >= 0) {
gata = FALSE;
while ((taken[i] + 1 <= limit[i]) && !gata) {
/* cat timp exista elementul urmator si nu sunt verificate conditiile
de continuare */
taken[i]++; //treci la elementul urmator
//verifica conditiile de continuare
if (canContinue(i))
gata = TRUE;
//sau: gata = canContinue(i);
}
if (gata) //daca s-au verificat conditiile de continuare
if (i == n-1)
//daca suntem la ultimul element pastram solutia
final();
else { //altfel trecem la elementul urmator
i++; taken[i] = -1;

197
}
else
//altfel revenim la un element anterior si alegem o alta valoare
i--;
}
}

void main(void) {
readInput();
run();
print();
}

198
Bibliografie

[1] A. Aho, J. Hopcroft, J. Ullman, On finding lowest common ancestors in trees, Proc. 5th
ACM Symp. Theory of Computing (STOC), pp. 253-265, 1973.
[2] A. V. Aho, J. E. Hopcroft, J. D. Ulmann, Data Structures and algorithms, Addison-
Wesley, 1983.
[3] A. V. Aho, J. D. Ulmann, Foundation of Computer Science, Computer Science Press,
1992.
[4] R. K. Ahuja, J. B. Orlin, A Fast and Simple Algorithm for the Maximum Flow Problem,
Operations Research, vol. 37(5), pp. 748759, 1989.
[5] R. Ahuja, T. Magnanti, J. Orlin, Network Flows, Prentice Hall, 1993.
[6] R. Andonie, I. Garbacea, Algoritmi fundamentali, o perspectiv
a C++, Editura Libris,
1995.
[7] C. Aragon, R. Seidel, Randomized Search Trees, in Proc. of 30th IEEE Symposium on
Foundations of Computer Science, pp. 540546, 1989.
[8] A. Atanasiu, Concursuri de informatic
a. Probleme propuse (1994), Editura Petrion,
Bucuresti, 1995.
[9] M.D. Atkinson, J.-R. Sack, N. Santoro, T. Strothotte, Min-max heaps and generalized
priority queues, Programming techniques and Data structures. Comm. ACM, vol. 29(10),
pp. 996-1000, 1986.
[10] M. Augenstein, A. Tenenbaum, Program efficiency and data structures, Proceedings of
the eighth SIGCSE technical symposium on Computer science education, pp. 2127,
1977.
[11] S. Baase, Computer algorithms. Introduction to Design and Analysis, Addison-Wesley,
1992.
[12] P. Bazavan, Elemente de Teoria Algoritmilor, Editura Sitech, Craiova, 2007.
[13] R. Bellman, On a routing problem, Quarterly Applied Mathematics, XVI(1), pp. 87-90,
1958.
[14] M. A. Bender, M. Farach-Colton, The LCA problem revisited, Proceedings of the 4th
Latin American Symposium on Theoretical Informatics, LNCS, vol. 1776, Springer-
Verlag, pp. 88-94, 2000.
[15] J. Bentley, R. Sedgewick, Fast Algorithms for Sorting and Searching Strings, Proceedings
of the 8th Annual ACM-SIAM Symposium on Discrete Algorithms, 1997.

199
[16] J. Bentley, R. Sedgewick, Ternary Search Trees, Dr. Dobbs Journal, 1998.

[17] C. Bereanu, Algoritmica Grafurilor, Editura Sitech, Craiova, 2006.

[18] C. Berge, Graphes et hypergraphes, Dunod, Paris 1970.

[19] O. Berkman, D. Breslauer, Z. Galil, B. Schieber, si U. Vishkin, Highly parallelizable


problems, in Proceedings of the 21st Annual ACM Symposium on Theory of Computing,
pp. 309-319, 1989.

[20] O. Berkman, U. Vishkin, Recursive Star-Tree Parallel Data Structure, SIAM Journal on
Computing, vol.22(2), pp.221-242, 1993.

[21] O. Boruvka, O jistem problemu minim alnm (About a certain minimal problem), Acta
Societ. Scient. Natur. Moravicae, 3, pp. 37-58, 1926.

[22] R. P. Brent, An improved Monte Carlo factorization algorithm, BIT Numerical Mathe-
matics, Springer, vol. 20(2), pp. 176184, 1980.

[23] D. D. Burdescu, Analiza complexit


atii algoritmilor, Editura Albastra, ClujNapoca,
1998.

[24] D. D. Burdescu, M. Brezovan, M. Cosulschi, Structuri de date arborescente cu aplicatii


n Pascal si C, Reprografia Universitatii din Craiova, 2000.

[25] D. D. Burdescu, Liste, arbori, grafuri, Editura Sitech, Craiova, 2005.

[26] B. Chazelle, A minimum spanning tree algorithm with inverse-Ackerman type complexity,
J. ACM, 47, pp. 1028-1047, 2000.

[27] D. Cherition, R. E. Tarjan, Finding minimum spanning trees, SIAM Journal on Com-
puting, vol. 5, pp. 724-741, 1976.

[28] J. Cheriyan, K. Mehlhorn, Algorithms for dense graphs and networks on the random
access computer, Algorithmica, vol. 15, pp. 521-549, 1996.

[29] B. V. Cherkassky,
Algorithm for construction of maximal flows in networks with com-
plexity of O(V 2 E), Mathematical Methods of Solution of Economical Problems, vol.
7, pp. 112125, 1977.

[30] T. H. Cormen, C. E. Leiserson, R. L. Rivest, Introducere n Algoritmi, Computer Libris


Agora, ClujNapoca, 1999.

[31] M. Cosulschi, M. Gabroveanu, Algoritmi o abordare pragmatic


a, Editia a 2-a, Universi-
taria, Craiova, 2004.

[32] C. Croitoru, Tehnici de baza n optimizarea combinatorie, Editura Univ. Al. I. Cuza Iasi,
Iasi, 1992.

[33] G. B. Dantzig, Linear programming and extensions, University Press, Princeton, 1962.

[34] S. Dasgupta, C. H. Papadimitriou, U. V. Vazirani, Algorithms, McGrawHill, 2006.

[35] C. Demetrescu, G. F. Italiano, Engineering Shortest Path Algorithms, WEA, Springer,


LNCS 3059, pp. 191198, 2004.

200
[36] R. B. Dial, Algorithm 360: shortest-path forest with topological ordering [H], Communi-
cations of the ACM, vol. 12:11, pp. 632633, 1969.

[37] Dictionarul explicativ al limbii rom


ane, Academia Romana, Institutul de Lingvistica
Iorgu Iordan, Editura Univers Enciclopedic, 1998.

[38] E. W. Dijkstra, A note on two problems in connections with graphs, Numerische Math-
ematik, 1, pp. 269-271, 1959.

[39] Y. Dinitz, Algorithm for solution of a problem of maximum flow in a network with power
estimation, Doklady Akademii nauk SSSR, vol. 11, pp. 1277-1280, 1970.

[40] Y. Dinitz, Dinitz Algorithm: The Original Version and Evens Version, in Oded Goldre-
ich, Arnold L. Rosenberg, and Alan L. Selman, Theoretical Computer Science: Essays
in Memory of Shimon Even, Springer, pp. 218-240, 2006.

[41] J. Edmonds, Paths, Trees and Flowers, Canadian J. Math, vol. 17, pp. 449-467, 1965.

[42] J. Edmonds, R. M. Karp, Theoretical improvements in algorithmic efficiency for network


flow problems, Journal of the ACM, vol. 19(2), pp. 248-264, 1972.

[43] J. Egervary, Matrixok kombinatorius tulajdons


agair
ol, (in Hungarian), Matematikai es
Fizikai Lapok, vol. 38 pp. 16-28, 1931.

[44] J. Farey, On a Curious Property of Vulgar Fractions, London, Edinburgh and Dublin
Phil. Mag. 47, 385, 1816.

[45] J. Feng, G. Li, J. Wang, L. Zhou, Finding and ranking compact connected trees for
effective keyword proximity search in XML documents, Information Systems, vol. 35(2),
pp. 186203, 2010.

[46] L. R. Ford, Network flow theory, Technical Report P-923, RAND, Santa Monica, CA,
1956.

[47] L. R. Ford, Jr., D. R. Fulkerson, Maximal Flow Through a Network, Canadian Journal
of Mathematics, 8, pp. 399404, 1956.

[48] L. R. Ford, Jr., D. R. Fulkerson, A Simple Algorithm for Finding Maximal Network
Flows and an Application to the Hitchcock Problem, Canadian Journal of Mathematics,
9, pp. 210218, 1957.

[49] L. R. Ford, Jr., D. R. Fulkerson, Flows in Networks, Princeton University Press, Prince-
ton, 1962.

[50] C. L. Foster, The Design and Analysis of Algorithms, Springer Verlag, 1992.

[51] J.-C. Fournier, Graph Theory and Applications, Wiley-Blackwell, 2009.

[52] E. Fredkin, Trie Memory, Communications of the ACM, 3:(9), pp. 490, 1960.

[53] M. Fredman, R. Sedgewick, R. Sleator, R. Tarjan, The pairing heap: A new form of
self-adjusting heap, Algorithmica, 1, pp. 111129, 1986.

[54] M.L. Fredman, R.E. Tarjan, Fibonacci heaps and their use in improved network opti-
mization algorithms, Journal of the ACM, vol. 34, pp. 596-615, 1987.

201
[55] H. N. Gabow, R. E. Tarjan, A linear-time algorithm for a special case of disjoint set
union, Proceedings of the 15th ACM Symposium on Theory of Computing (STOC), pp.
246-251, 1983.

[56] H. N. Gabow, Path-based depth-first search for strong and biconnected components, In-
formation Processing Letters, pp. 107-114, 2000.

[57] D. Gale, L. S. Shapley, College Admissions and the Stability of Marriage, American
Mathematical Monthly, vol. 69, pp. 914, 1962.
5
[58] Z. Galil, An O(V 3 E f rac23 ) algorithm for the maximal flow problem, Acta Informatica,
vol. 14, pp. 221242, 1980.

[59] Z. Galil, A. Naamad, An O(EV (log V )2 ) algorithm for the maximal flow problem, Jour-
nal of Computer and System Sciences, vol. 21(2), pp. 203217, 1980.

[60] C. Giumale, Introducere n analiza algoritmilor, Editura Polirom, Iasi, 2004.

[61] A. V. Goldberg, A new max-flow algorithm, Technical Report MIT/LCS/TM-291, Lab-


oratory for Computer Science, MIT, 1985.

[62] A. V. Goldberg, R. E. Tarjan, A new approach to the maximum flow problem, in Pro-
ceedings of the 18th ACM Symposium on Theory of Computing, ACM, pp. 136146,
1986.
Tardos, R. E. Tarjan, Network flow algorithms, Algorithms and
[63] A. V. Goldberg, E.
Combinatorics, vol. 9. in B. Korte, L. Lovsz, H. J. Prmel, A. Schrijver, Editors, Paths,
Flows, and VLSI-Layout, Springer-Verlag, Berlin, pp.101-164, 1990.

[64] R. L. Graham, D. E. Knuth, O. Patashnik, Concrete Mathematics: A Foundation for


Computer Science, 2nd Edition, Addison-Wesley, 1994.

[65] D. Gries, The Science of Programming, Springer Verlag, Heidelberg, NewYork, 1981.

[66] L. Guo, F. Shao, C. Botev, J. Shanmugasundaram, XRANK: ranked keyword search over
XML documents, in Proceedings of the 2003 ACM SIGMOD International Conference
on Management of Data, pp. 1627, 2003.

[67] D. Gusfield, Algorithms on Strings, Trees, and Sequences, Cambridge University Press,
1997.

[68] G. H. Hardy, E. M. Wright, An Introduction to the Theory of Numbers, 5th Edition,


Oxford University Press, 1979.

[69] D. Harel, R. E. Tarjan, Fast algorithms for finding nearest common ancestors, SIAM
Journal on Computing, vol. 13(2), pp. 338-355, 1984.

[70] T. E. Harris, F. S. Ross, Fundamentals of a Method for Evaluating Rail Net Capacities,
Research Memorandum RM-1573, The RAND Corporation, Santa Monica, California,
1955.

[71] I. N. Herstein, I. Kaplansky, Matters mathematical, 2nd Edition, Chelsea Publishing


Company, 1978.

202
5
[72] J. E. Hopcroft, R. Karp, An n 2 algorithm for maximum matchings in bipartite graphs,
SIAM Journal on Computing, vol. 2(4), pp.225-231, 1973.

[73] V. Hristidis , N. Koudas , Y. Papakonstantinou, D. Srivastava, Keyword Proximity


Search in XML Trees, IEEE Transactions on Knowledge and Data Engineering, vol.
18(4), pp. 525539, 2006.

[74] D. A. Huffman, A Method for the Construction of Minimum-Redundancy Codes, Pro-


ceedings of the I.R.E., 1952, pp. 1098-1102.

[75] C. Ionescu, I. Zsako, Structuri arborescente, Editura Tehnica, Bucuresti, 1990.

[76] V. Jarnk, O jistem problemu minimalnm (About a certain minimal problem), Prace
Moravske Prrodovedecke Spolecnosti, 6, pp. 5763, 1930.

[77] D. Jungnickel, Graphs, Networks and Algorithms, Algorithms and Computation in Math-
ematics, vol. 5, 3rd Edition, Springer, 2008.

[78] A. B. Kahn, Topological sorting of large networks, Communications of the ACM, vol.
5(11), pp. 558-562, 1962.

[79] D. Karger, P. Klein, R. Tarjan, A randomized linear-time algorithm to find minimum


spanning trees, Journal of ACM, 42, pp. 321328, 1995.

[80] A. V. Karzanov, Determining the maximum flow in the network by the method of pre-
flows, Doklady Akademii nauk SSSR 15, pp. 434-437, 1974.

[81] B. W. Kernigham, D. M. Ritchie, The C Programming Language, 2nd Edition, Prentice


Hall, 1988.

[82] D. C. Kozen, The Design and Analysis of Algorithms. Texts and Monographs in Com-
puter Science, Springer, 1993.

[83] D. Konig, Theorie der endlichen und unendlichen Graphen, Leipzig: Akademische Ver-
lagsgesellschaft, 1936. Translated from German by Richard McCoart, Theory of finite
and infinite graphs, Birkhauser, 1990.

[84] J. Kleinberg, E. Tardos, Algorithm Design, Addison-Wesley, 2005.

[85] D. E. Knuth, Arta programarii calculatoarelor, vol. 1 Algoritmi fundamentali, Teora,


Bucuresti, 1999.

[86] D. E. Knuth, Arta programarii calculatoarelor, vol. 2 Algoritmi seminumerici, Teora,


Bucuresti, 2000.

[87] D. E. Knuth, Arta programarii calculatoarelor, vol. 3 Sortare si C


autare, Teora, Bu-
curesti, 2001.

[88] J. B. Kruskal, On the shortest spanning subtree of a graph and the traveling salesman
problem, Proc. of the American Mathematical Society, 7, pp. 48-50, 1956.

[89] H. W. Kuhn, On combinatorial properties of matrices, Logistics Papers (George Wash-


ington University), vol. 11, pp. 1-11, 1955.

203
[90] H. W. Kuhn, The Hungarian Method for the assignment problem, Naval Research Lo-
gistics Quarterly, vol. 2, pp. 83-97, 1955.

[91] H. W. Kuhn, Variants of the Hungarian method for assignment problems, Naval Research
Logistics Quarterly, vol. 3, pp. 253-258, 1956.

[92] Y. Li , C. Yu , H. V. Jagadish, Schema-free XQuery, in Proceedings of the Thirtieth


International Conference on Very Large Databases (VLDB), pp. 7283, 2004.

[93] L. Livovschi, H. Georgescu, Analiza si sinteza algoritmilor, Ed. Stiintifica si Enciclope-


dica, Bucuresti, 1986.

[94] V. M. Malhotra, M. P. Kumar, S. N. Maheshwari, An O(|V |3 ) algorithm for finding


maximum flows in networks, Information Processing Letters, 7(6), pp.277278, 1978.

[95] K. Mehlhorn, Data Structures and Algorithms: Graph Algorithms and NP-Completeness,
Springer Verlag, 1984.
p
[96] S. Micali, V. V. Vazirani, An O( |V | |E|) algorithm for finding maximum matching
in general graphs, Proc. 21st IEEE Symp. Foundations of Computer Science, pp. 17-27,
1980.

[97] V. Mitrana, Provocarea algoritmilor, Editura Agni, Bucuresti, 1994.

[98] E. F. Moore, The shortest path through a maze, in Proceedings of International Sympo-
sium on the Theory of Switching, Part II, pp. 285-292, 1959. (prezentat la simpozion la
Universitatea Harvard in aprilie 1957)

[99] R. Motwani, Average-case analysis of algorithms for matchings and related problems,
Journal of the ACM, vol. 41(6), pp. 1329-1356, 1994.

[100] J. Munkres, Algorithms for the Assignment and Transportation Problems, Journal of
the Society for Industrial and Applied Mathematics, vol. 5(1), pp. 32-38, 1957.

[101] G. Nivasch, Cycle detection using a stack, Information Processing Letters, vol. 90(3),
pp. 135140, 2004.

[102] I. Odagescu, F. Furtuna, Metode si tehnici de programare, Computer Libris Agora,


ClujNapoca, 1998.

[103] S. Pettie, A faster all-pairs shortest path algorithm for real-weighted sparse graphs, in
Proceedings of 29th International Colloquium on Automata, Languages, and Program-
ming (ICALP02), LNCS Vol. 2380, pp. 85-97, 2002.

[104] S. Pettie, V. Ramachandran, Computing shortest paths with comparisons and additions,
in Proceedings of the 13th Annual ACM-SIAM Symposium on Discrete Algorithms
(SODA02), SIAM, pp. 267-276, 2002.

[105] S. Pettie, V. Ramachandran, An optimal minimum spanning tree algorithm, Journal of


ACM, 49:1634, 2002.

[106] R. C. Prim, Shortest connection networks and some generalizations, Bell System Tech-
nical Journal, 36, pp. 1389-1401, 1957.

204
[107] B. Schieber, U. Vishkin, On finding lowest common ancestors: Simplification and par-
allelization, SIAM J. Comput., vol. 17, pp. 1253-1262, 1988.

[108] A. Schrijver, On the history of the transportation and maximum flowproblems, Mathe-
matical Programming, vol. 91, issue 3, pp. 437-445, 2002.

[109] R. Seidel, C. Aragon, Randomized Search Trees, Algorithmica, vol. 16, pp. 464497,
1996.

[110] M. Sharir, A strong-connectivity algorithm and its applications in data fow analysis,
Computers and Mathematics with Applications, vol. 7(1), pp. 6772, 1981.

[111] Y. Shiloach, U. Vishkin, An O(n2 log n) parallel max-flow algorithm, Journal of Algo-
rithms, vol. 3(2), pp. 128-146, 1982.

[112] S. Skiena, The Algorithm Design Manual, 2nd Edition, Springer, 2008.

[113] D. D. Sleator, An O(EV log V ) algorithm for maximum network flow, Technical Report,
STAN-CS-80-831, 1980.

[114] D. D. Sleator, R. E. Tarjan, A data structure for dynamic trees, Journal of Computer
Sciences, vol. 26, pp. 362391, 1983.

[115] J. Stasko, J. Vitter, Pairing heaps: Experiments and analysis, Communications of the
ACM, vol. 30(3), pp. 234-249, 1987.

[116] T. Takaoka, Theory of 2-3 heaps, Discrete Applied Mathematics, Vol. 126(1), 5th An-
nual International Computing and Combinatories Conference (COCOON99), pp. 115
128, 2003.

[117] R. Tarjan, Depthfirst search and linear graph algorithms, SIAM Journal on Computing,
vol. 1(2), pp. 146160, 1972.

[118] R. E. Tarjan, Edge-disjoint spanning trees and depth-first search, Algorithmica, vol.
6(2), pp. 171-185, 1976.

[119] R. E. Tarjan, Applications of path compression on balanced trees, Journal of the ACM,
vol. 26(4), pp. 690-715, 1979.

[120] R. E. Tarjan, A simple version of Karzanovs blocking flow algorithm, Operation Re-
search Letters, vol. 2, pp.265268, 1984.

[121] I. Tomescu, Grafuri si programare liniar


a, Ed. Didactica si Pedagogica, Bucuresti, 1975.

[122] I. Tomescu, Probleme de combinatoric


a si teoria grafurilor, Ed. Didactica si Pedagogica,
Bucuresti, 1981.

[123] I. Tutunea, Algoritmi, logica si programare, Reprografia Universitatii din Craiova, 1993.

[124] I. Tutunea, S. Pescarus, Algoritmi si programe Pascal. (Culegere de probleme), Repro-


grafia Universitatii din Craiova, 1994.

[125] J. Vuillemin, A data structure for manipulating priority queues, Communications of


the ACM, vol. 21:4, pp. 309315, 1978.

205
[126] J. Vuillemin, A unifying look at data structures, Communications of the ACM, vol.
23:4, pp. 229-239, 1980.

[127] G. A. Waissi, A new Karzanov-type O(n3 ) max-flow algorithm, Mathematical and Com-
puter Modelling, vol. 16(2), pp.6572, 1992.

[128] H. S. Wilf, Algorithms and Complexity, Internet edition, 1996.

[129] J. W. J. Williams, Algorithm 232 - Heapsort, Communications of the ACM, vol. 7(6),
pp. 347-348, 1964.

[130] Y. Xu, Y. Papakonstantinou, Efficient keyword search for smallest LCAs in XML
databases, in Proceedings of the 2005 ACM SIGMOD International Conference on Man-
agement of Data, pp. 527538, 2005.

[131] D. Zaharie, Introducere n proiectarea si analiza algoritmilor, Eubeea, Timisoara, 2008.

206

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