Sunteți pe pagina 1din 210

Cuprins

1 Introducere n algoritmi
3
1.1 Limbajul pseudocod . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 4
1.2 Exemple . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 12
1.3 Exercitii . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 18
2 Elemente de analiza algoritmilor
2.1 Metoda substitutiei . . . . . . .
2.2 Schimbarea de variabila . . . .
2.3 Metoda iterativa . . . . . . . .
2.4 Teorema master . . . . . . . . .
2.5 Exercitii . . . . . . . . . . . . .

.
.
.
.
.

.
.
.
.
.

.
.
.
.
.

.
.
.
.
.

.
.
.
.
.

.
.
.
.
.

.
.
.
.
.

.
.
.
.
.

.
.
.
.
.

.
.
.
.
.

.
.
.
.
.

.
.
.
.
.

21
25
25
26
27
28

3 Grafuri. Grafuri neorientate


3.1 Notiuni de baza . . . . . . . . . . . . . . . . . . . . . . . .
3.2 Operatii pe grafuri . . . . . . . . . . . . . . . . . . . . . .
3.3 Moduri de reprezentare . . . . . . . . . . . . . . . . . . . .
3.4 Parcurgerea grafurilor . . . . . . . . . . . . . . . . . . . .
3.4.1 Parcurgerea n latime (BF-Breadth-First) . . . . . .
3.4.2 Parcurgerea D (D - Depth) . . . . . . . . . . . . .
3.4.3 Parcurgerea n adancime (DFS-Depth First Search)
3.5 Componente conexe . . . . . . . . . . . . . . . . . . . . . .
3.6 Muchie critica . . . . . . . . . . . . . . . . . . . . . . . . .
3.7 Exercitii . . . . . . . . . . . . . . . . . . . . . . . . . . . .

.
.
.
.
.
.
.
.
.
.

.
.
.
.
.
.
.
.
.
.

.
.
.
.
.
.
.
.
.
.

.
.
.
.
.
.
.
.
.
.

.
.
.
.
.
.
.
.
.
.

.
.
.
.
.
.
.
.
.
.

.
.
.
.
.
.
.
.
.
.

.
.
.
.
.
.
.
.
.
.

.
.
.
.
.
.
.
.
.
.

.
.
.
.
.
.
.
.
.
.

.
.
.
.
.
.
.
.
.
.

30
30
35
38
42
42
46
47
51
52
57

.
.
.
.
.
.

60
60
61
64
68
70
72

.
.
.
.

83
84
85
87
93

.
.
.
.
.

.
.
.
.
.

.
.
.
.
.

.
.
.
.
.

.
.
.
.
.

.
.
.
.
.

.
.
.
.
.

.
.
.
.
.

.
.
.
.
.

.
.
.
.
.

.
.
.
.
.

.
.
.
.
.

.
.
.
.
.

.
.
.
.
.

4 Grafuri euleriene si hamiltoniene


4.1 Grafuri Euleriene . . . . . . . . . . . . . . . . . . . . . .
4.1.1 Algoritm pentru determinarea unui ciclu eulerian
4.1.2 Algoritmul lui Rosenstiehl . . . . . . . . . . . . .
4.1.3 Algoritmul lui Fleury . . . . . . . . . . . . . . . .
4.2 Grafuri Hamiltoniene . . . . . . . . . . . . . . . . . . . .
4.2.1 Problema comisvoiajorului . . . . . . . . . . . .
5 Arbori. Arbori binari
5.1 Arbori binari . . . . . . . . .
5.1.1 Moduri de reprezentare
5.1.2 Metode de parcurgere .
5.2 Arbori binari de cautare . . .

.
.
.
.

.
.
.
.

.
.
.
.

.
.
.
.

.
.
.
.

.
.
.
.

.
.
.
.

.
.
.
.

.
.
.
.

.
.
.
.

.
.
.
.

.
.
.
.

.
.
.
.

.
.
.
.

.
.
.
.

.
.
.
.
.
.
.
.
.
.

.
.
.
.
.
.
.
.
.
.

.
.
.
.
.
.
.
.
.
.

.
.
.
.
.
.
.
.
.
.

.
.
.
.
.
.
.
.
.
.

.
.
.
.
.
.
.
.
.
.

.
.
.
.
.
.
.
.
.
.

.
.
.
.
.
.
.
.
.
.

.
.
.
.
.
.
.
.
.
.

.
.
.
.
.
.
.
.
.
.

.
.
.
.
.
.
.
.
.
.

6 Arbori oarecare
6.1 Moduri de reprezentare . . . . . . . . . . . . . . .
6.2 Metode de parcurgere . . . . . . . . . . . . . . . .
6.3 Arbori de acoperire de cost minim . . . . . . . . .
6.3.1 Algoritmul lui Boruvka . . . . . . . . . . .
6.3.2 Algoritmul lui Prim . . . . . . . . . . . . .
6.3.3 Structuri de date pentru multimi disjuncte
6.3.4 Algoritmul lui Kruskal . . . . . . . . . . .
6.4 Exercitii . . . . . . . . . . . . . . . . . . . . . . .

.
.
.
.
.
.
.
.

.
.
.
.
.
.
.
.

.
.
.
.
.
.
.
.

.
.
.
.
.
.
.
.

.
.
.
.
.
.
.
.

.
.
.
.
.
.
.
.

.
.
.
.
.
.
.
.

.
.
.
.
.
.
.
.

.
.
.
.
.
.
.
.

.
.
.
.
.
.
.
.

.
.
.
.
.
.
.
.

.
.
.
.
.
.
.
.

.
.
.
.
.
.
.
.

.
.
.
.
.
.
.
.

.
.
.
.
.
.
.
.

104
. 104
. 107
. 112
. 115
. 115
. 118
. 123
. 126

7 Grafuri orientate
7.1 Notiuni de baza . . . . . . . . .
7.2 Parcurgerea grafurilor . . . . .
7.3 Sortarea topologica . . . . . . .
7.4 Componente tare conexe . . . .
7.4.1 Algoritmul lui Kosaraju
7.4.2 Algoritmul lui Tarjan . .
7.4.3 Algoritmul lui Gabow .
7.5 Exercitii . . . . . . . . . . . . .

.
.
.
.
.
.
.
.

.
.
.
.
.
.
.
.

.
.
.
.
.
.
.
.

.
.
.
.
.
.
.
.

.
.
.
.
.
.
.
.

.
.
.
.
.
.
.
.

.
.
.
.
.
.
.
.

.
.
.
.
.
.
.
.

.
.
.
.
.
.
.
.

.
.
.
.
.
.
.
.

.
.
.
.
.
.
.
.

.
.
.
.
.
.
.
.

.
.
.
.
.
.
.
.

.
.
.
.
.
.
.
.

.
.
.
.
.
.
.
.

.
.
.
.
.
.
.
.

.
.
.
.
.
.
.
.
.

150
. 151
. 151
. 152
. 160
. 162
. 164
. 167
. 168
. 170

.
.
.
.
.
.
.
.

.
.
.
.
.
.
.
.

.
.
.
.
.
.
.
.

.
.
.
.
.
.
.
.

.
.
.
.
.
.
.
.

.
.
.
.
.
.
.
.

.
.
.
.
.
.
.
.

.
.
.
.
.
.
.
.

.
.
.
.
.
.
.
.

8 Distante n grafuri
8.1 Drumul minim de la un varf la celelalte varfuri .
8.1.1 Algoritmul lui Moore . . . . . . . . . . .
8.1.2 Algoritmul lui Dijkstra . . . . . . . . . .
8.1.3 Algoritmul lui BellmanKalaba . . . . .
8.2 Drumuri minime ntre toate perechile de varfuri
8.2.1 Algoritmul lui Roy-Floyd-Warshall . . .
8.3 Circuitul Hamiltonian . . . . . . . . . . . . . .
8.4 Graf pe mai multe niveluri . . . . . . . . . . . .
8.5 Exercitii . . . . . . . . . . . . . . . . . . . . . .

.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.

.
.
.
.
.
.
.
.
.

.
.
.
.
.
.
.
.
.

.
.
.
.
.
.
.
.
.

.
.
.
.
.
.
.
.
.

.
.
.
.
.
.
.
.
.

.
.
.
.
.
.
.
.
.

.
.
.
.
.
.
.
.
.

.
.
.
.
.
.
.
.
.

.
.
.
.
.
.
.
.
.

.
.
.
.
.
.
.
.
.

.
.
.
.
.
.
.
.
.

.
.
.
.
.
.
.
.
.

.
.
.
.
.
.
.
.
.

.
.
.
.
.
.
.
.
.

128
128
130
133
137
138
141
144
145

A Probleme. Algoritmi. Complexitate


176
A.0.1 Verificare n timp polinomial . . . . . . . . . . . . . . . . . . . . . . . . 178
A.0.2 Reduceri polinomiale . . . . . . . . . . . . . . . . . . . . . . . . . . . . 179
B Liste. Stive. Cozi
B.1 Stiva . . . . . . . . . . . . . . . . . . . . . . .
B.2 Coada . . . . . . . . . . . . . . . . . . . . . .
B.2.1 Coada - implementare statica . . . . .
B.2.2 Coada circulara - implementare statica
B.3 Liste . . . . . . . . . . . . . . . . . . . . . . .
C Metoda Backtracking

.
.
.
.
.

.
.
.
.
.

.
.
.
.
.

.
.
.
.
.

.
.
.
.
.

.
.
.
.
.

.
.
.
.
.

.
.
.
.
.

.
.
.
.
.

.
.
.
.
.

.
.
.
.
.

.
.
.
.
.

.
.
.
.
.

.
.
.
.
.

.
.
.
.
.

.
.
.
.
.

.
.
.
.
.

.
.
.
.
.

181
181
183
185
186
187
190

Capitolul 1
Introducere n algoritmi
Definitia 1.1 Algoritmul constituie o reprezentare finita a unei metode de calcul ce permite rezolvarea unei anumite probleme. Se poate spune ca un algoritm reprezint
a o secvent
a
finita de operatii, ordonata si complet definita, 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
traducere latina sub titlul de Algorithmi de numero indorum, iar apoi ca Liber algorithmi,
unde 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 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 trasaturi 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. generalitate - algoritmul trebuie sa ofere solutia nu numai pentru o singura problema
ci pentru o ntreaga clasa de probleme;
3

4. finitudine - algoritmul trebuie sa se termine ntr-un timp finit;


5. eficient
a - 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 calculatoarelor 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 structurata, programare orientata pe obiecte,
etc.;
validare - verificarea corectitudinii algoritmului prin metode formale;
analiz
a - 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 adres
a - 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}

Instructiunea de atribuire
Este instructiunea cel mai des utilizata ntr-un algoritm si realizeaza ncarcarea unei variabile
(locatii de memorie) cu o anumita valoare. 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:
aritmetici - + (adunare), (scadere), (nmultire), / (mpartire), (ridicare la putere),
div (catul mpartirii ntregi), mod (restul mpartirii ntregi)
relationali - = (egal), 6= (diferit), < (strict mai mic), (mai mic sau egal), > (strict
mai mare), (mai mare sau egal)
logici - OR sau (disjunctie), AN D sau (conjunctie), N OT sau (negatie)
Cea mai simpla expresie este formata dintr-o variabila sau o constanta (operand). Expresiile 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
4:
Output {Numarul este impar }
5: end if

[
3: else
4:
<instructiune2>
]
5: end if

Instructiunea 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 bazeaz
a pe rezolvarea matematica general
a a ecuatiei de gradul I:
1. dac
a 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 satisfacuta pentru nici o valoare a lui x R sau C.
Spunem n acest caz ca ecuatia este incompatibil
a. Daca b = 0, avem 0x+0 = 0, relatia
fiind adevarat
a pentru orice valoare a lui x R. Spunem ca ecuatia este compatibil
nedeterminat
a.
5

Algorithm 1 Algoritm pentru rezolvarea ecuatiei de gradul I


1:
2:
3:
4:
5:
6:
7:
8:
9:
10:
11:

Input {a, b}
if (a = 0) then
if (b = 0) then
Output { Ecuatie compatibil nedeterminata }
else
Output { Ecuatie incompatibila }
end if
else
x ab
Output { Solutia este:, x }
end if

2. dac
a a 6= 0, ecuatia are o singura solutie, x1 = ab R.
Av
and acest algoritm drept model sa se realizeze un algoritm pentru rezolvarea ecuatiei de
gradul al II-lea, ax2 + bx + c = 0, 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 repetitiva conditionata posterior.
Structura repetitiva conditionata anterior while (sau instructiune de ciclare cu test initial)
are sintaxa:
1:
2:
3:
4:
5:
6:

1: while <expresie-booleana> do
2:
<instructiune>
3: end while

sum 0
i1
while (i n) do
sum sum + i
ii+1
end while

Cat timp <expresie-booleana> este adevarat


a, se executa <instructiune>; daca <expresiebooleana> este falsa chiar la prima evaluare atunci <instructiune> nu ajunge sa fie rulata
niciodata. Acest comportament este exact opus celui corespunzator structurii repetitive
conditionata posterior repeat, unde instructiunea este executata cel putin o data. (Daca o
expresie are valoarea de adevar true spunem atunci ca expresia este adev
arat
a ; 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 catului si restului mp
artirii a doua
numere ntregi, prin scaderi succesive (vezi algoritmul 2).
Mai nt
ai se initializeaz
a catul cu valoarea zero (linia 3) si restul cu valoarea demp
artitului
(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 pe
instructiunile while si repeat sunt mult mai potrivite n cazul n care conditia de terminare
trebuie reevaluata n timpul ciclarii (atunci cand numarul de repetitii nu este cunoscut apriori).
6

Algorithm 2 Algoritm pentru calculul mpartirii ntregi


1:
2:
3:
4:
5:
6:
7:
8:
9:
10:
11:
12:

Input {a, b}
if ((a > 0) (b > 0)) then
cat 0
rest a
while (rest b) do
rest rest b
cat cat + 1
end while
Output {cat, rest}
else
Output {Numerele trebuie sa fie strict pozitive!}
end if

Instructiunea for are urmatoarea sintaxa:


1: for <Var> <Expresie1>, <Expresie2>, 1: sum 0
2: for i 1, n do
Step < p > do
3:
sum sum + i
2:
<instructiune>
4: end for
3: 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 lui p, 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
negative.
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 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 aflarea
elementului maxim, respectiv minim dintr-un sir de elemente. Acest sir de elemente poate fi
pastrat ntr-o structura de date elementara, cunoscuta sub numele de vector. Un vector este
un caz particular de matrice avand o singura linie si n coloane.

Algorithm 3 Algoritm pentru calculul elementului de valoare minima dintrun sir


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

Input {N, x1 , x2 , ..., xN }


min x1
for i 2, N do
if (min > xi ) then
min xi
end if
end for
Output { min }

Prezentam n continuare algoritmul pentru determinarea elementului de valoare minima


ce face parte dintr-un sir de numere naturale (vezi algoritmul 3).
acest algoritm se observa utilizarea enuntului repetitiv for, nt
In
alnit, n general, n
situatiile n care se cunoaste apriori numarul de pasi pe care trebuie sa-l realizeze instructiunea
repetitiv
a.
Mai nt
ai se initializeaz
a variabila min cu x1 , presupun
and ca elementul de valoare
continuare vom compara
minima este primul element din cadrul vectorului x (linia 2). In
valoarea variabilei min cu valoarea fiec
arui element din sir (liniile 3-7): daca vom gasi un element a carui valoare este mai mica dec
at cea a minimului calculat pan
a n momentul curent
(linia 4), vom retine noua valoare n variabila min (linia 5).
Structura repetitiva conditionata posterior repeat. . .until prezinta urmatoarea sintaxa:
1:
2:
3:
4:
5:
6:

1: repeat
2:
<instructiune>
3: until <expresie-booleana>

sum 0
i1
repeat
sum sum + i
ii+1
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.
Not
am cu Sn suma primelor n numere naturale (Sn = 1 + 2 + 3 + . . . + n). Aceasta se
poate calcula ntr-o maniera incremental
a astfel:
S1
S2
S3
S4
Sn
Observatia 1.5

=
=
=
=
...
=

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

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
8

Algorithm 4 Algoritm pentru calculul sumei primelor n numere naturale (prima varianta)
1:
2:
3:
4:
5:
6:
7:
8:

Input {N }
S0
i1
repeat
S S+i
ii+1
until (i > N )
Output {S}

de calcul. Variabila ce retine rezultatul unui proces de nsum


ari succesive se va initializa
ntotdeauna cu 0, valoare ce nu va influenta rezultatul final. O variabila ce pastreaz
a
rezultatul unei sume sau al unui produs se va initializa cu elementul neutru fat
a de
operatia respectiv
a.
3. Atribuirile i = i + 1 si S = S + i (liniile 5 si 6) sunt lipsite de sens din punct de vedere
a atunci cand ne referim la un sistem de calcul electronic, aceste operatii
algebric. Ins
se refer
a la valoarea curent
a si la cea anterioar
a a unei variabile. De asemenea, trebuie
s
a se faca o distinctie clara ntre operatia de atribuire si cea de egalitate: operatia
de atribuire (0 0 ) face ca valoarea curent
a a variabilei din stanga operatorului de
atribuire sa fie initializat
a cu valoarea expresiei din dreapta acestuia, pe cand, operatia
de egalitate verifica daca valoarea elementului din stanga operatorului 0 =0 este egal
a cu
valoarea expresiei din dreapta acestuia.
Astfel, de exemplu n cazul expresiei i i + 1, valoarea curent
a a variabilei i devine
valoarea anterioar
a a aceleiasi variabile incrementat
a cu o unitate.
Suma primelor n numere naturale se constituie n suma termenilor unei progresii aritmetice ce se poate calcula direct cu formula sumei, astfel 1 + 2 + . . . + n = n(n+1)
. Av
and
2
n vedere aceast
a identitate, algoritmul de calcul a sumei primelor n numere naturale se
simplifica foarte mult (vezi algoritmul 5).
Algorithm 5 Algoritm pentru calculul sumei primelor n numere naturale (a doua varianta)
1: Input {N }
2: S n (n + 1)/2
3: Output {S}

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 la definire, urmat de lista parametrilor actuali. Aceasta 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 apelanta si subrutina apelata. 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 S
a se realizeze un algoritm care determina cel mai mare divizor comun a
doua numere naturale.
Teorema 1.7 (Teorema mp
artirii cu rest) Pentru doua numere ntregi a si b, cu b 6=
0, dou
a numere ntregi q si r, unice, astfel nc
at:
a = b q + r, 0 r < |b|
unde a se numeste demp
artitul, b - mp
artitorul, q - catul iar r restul.
Definitia 1.2 Cel mai mare divizor comun (notat cmmdc) a doua numere naturale a si b
este un numar natural d cu propriet
atile:
1. d|a, d|b (d este un divizor comun al lui a si b)
2. d0 N astfel nc
at d0 |a, d0 |b avem d0 |d (oricare alt divizor comun al lui a si b, l divide
si pe d).
Observatia 1.8

1. cmmdc(a, 0) = a.

2. dac
a cmmdc(a, b) = 1 atunci se spune ca numerele a si b sunt prime ntre ele.

3. Intre
cel mai mic multiplu comun si cel mai mare divizor comun exista urmatoarea
relatie:
ab
cmmmc(a, b) =
cmmdc(a, b)
Metoda de calcul pentru a obtine cel mai mare divizor comun a doua numere a si b prin
mp
artiri succesive, cunoscuta sub numele de algoritmul lui Euclid, se poate descrie astfel:
Se mparte a la b si se retine restul r. Daca r este nul atunci cel mai mare divizor comun
caz contrar, se mparte b la r si se pastreaz
este b. In
a noul rest. Calculele se continu
a,
folosindu-se ca demp
artit vechiul mp
artitor iar ca mp
artitor ultimul rest obtinut, pan
a
cand restul obtinut devine nul.

10

Operatiile anterioare pot fi transpuse prin intermediul urmatoarelor formule matematice:


a = bq1 + r1
b = r 1 q2 + r 2
r1 = r2 q3 + r3
...
rk = rk+1 qk+2 + rk+2
...
rn2 = rn1 qn + rn
rn1 = rn qn+1 + rn+1

0 r1 < b
0 r2 < r 1
0 r3 < r 2
0 rk+2 < rk+1
0 rn < rn1
0 rn+1 < rn

Aceste mp
artiri nu se constituie ntr-un proces infinit, deoarece secventa de numere naturale r1 , r2 , . . . , rn+1 , . . . este descresc
atoare (r1 > r2 > . . . > rn > . . .) si marginit
a inferior
de 0. Rezulta ca p N astfel nc
at rp 6= 0, si rp+1 = 0.
Algorithm 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
9:
ab
10:
br
11:
r a mod b
12:
end while
13:
end if
14: end procedure

. Procesul se ncheie atunci cand r ia valoarea 0

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, trebuie mentionat ca n acest proces de calcul catul nu participa 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 0
(linia 8).
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.

11

1.2

Exemple

Exemplul 1.9 S
a se verifice printr-un algoritm daca un numar natural este prim sau nu.
Definitia 1.3 Un numar natural k 2 se numeste prim daca singurii sai divizori naturali
sunt 1 si k.
Pe baza acestei definitii se poate concluziona ca un numar natural k este prim daca nu are
nici un divizor propriu n intervalul [2, k 1].
Algorithm 7 Algoritm pentru verificarea daca un numar este prim
1:
2:
3:
4:
5:
6:
7:
8:
9:
10:
11:
12:
13:
14:
15:
16:
17:
18:
19:

Input {N }
if (n < 2) then
Output {Numarul nu este prim.}
else
i2
prim true
while ((i n 1) (prim = true)) do
if (n mod i = 0) then
prim f alse
else
ii+1
end if
end while
if (prim = true) then
Output {Numarul este prim.}
else
Output {Numarul nu este prim.}
end if
end if

Dac
a n este numar prim, corpul enuntului de ciclare while se va executa de n 2 ori
(vezi algoritmul 7).
Conditia i n 1 poate fi mbun
at
atit
a cu i n/2, deoarece ntre jumatatea numarului
si n nu mai exista nici un alt divizor, pentru orice valoare a lui n.
Dac
a 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. Similar pentru 3, 4, 5, . . .. Astfel
se formeaz
a doua siruri:
corespunde n
2

2
corespunde n
3

3
corespunde n
4

4
..
..
.
.
n
corespunde
k

|{z}
k
|{z}
Sir1

Sir2

Se observa faptul ca sirul 1 este compus din elemente cu valori consecutive, iar sirul 2
nu respect
a aceast
a proprietate. Numerele dintro pereche (p, np ) sunt legate ntre ele astfel:
12

n momentul n care am verificat faptul ca numarul 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 descresc
ator.
Elementele dinprimul sir devin mai mari dec
at cele din al doilea atunci cand k
u n/k
2
k u n k u n. Astfel rezult
a conditia pentru limita superioar
a a ciclarii: i b nc (vezi
algoritmul 8).
Algorithm 8 Algoritm pentru verificarea daca un numar este prim (varianta optimizata)
1:
2:
3:
4:
5:
6:
7:
8:
9:
10:
11:
12:
13:
14:
15:
16:
17:
18:
19:

Input {N }
if (n < 2) then
Output {Numarul nu este prim.}
else
i2
prim true

while ((i b nc) (prim = true)) do


if (n mod i = 0) then
prim f alse
else
ii+1
end if
end while
if (prim = true) then
Output {Numarul este prim.}
else
Output {Numarul nu este prim.}
end if
end if

Exemplul 1.10 Se spune ca un vector este simetric daca primul element este egal cu ultimul,
al doilea cu penultimul, etc. Algoritmul urmator verifica daca un vector de numere ntregi
este simetric.
Vom utiliza doua variabile de indici, ce pornesc, una din stanga, si cealalt
a din dreapta.
At
ata timp cat variabila din stanga este mai mica dec
at variabila din dreapta iar elementele
din vector corespunz
atoare variabilelor de indiciere sunt egale, se incrementeaz
a variabila din
stanga si se decrementeaz
a variabila din dreapta. Dupa par
asirea instructiunii de ciclare, se
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 cealalt
a conditie, (xi = xj ) a fost ndeplinit
a tot timpul (i < j, i, j =
1, n). Deci sirul este simetric n aceast
a situatie.
Exemplul 1.11 Se da urmatorul sir de numere naturale: 1, 1, 2, 1, 2, 3, 1, 2, 3, 4, . . .. Pentru
un numar natural n dat sa se determine cel de-al n-lea element al sirului. De exemplu, cel
de-al 5-lea element al sirului prezentat este 2.
Dup
a cum se observa din constructia sirului, acesta este format din concatenarea mai
multor secvente de numere naturale consecutive, fiecare secvent
a ncep
and cu valoarea 1.
Lungimea fiec
arei subsecvente este cu o unitate mai mare dec
at lungimea subsecventei anterioare: astfel prima subsecvent
a are lungimea 1, a doua subsecvent
a are lungimea 2, a treia
cadrul algoritmului 10 se va ncerca decrementarea varisubsecvent
a are lungimea 3, etc. In
abilei index cu lungimea cate unui sir n ntregime (index index k), atata timp cat este
13

Algorithm 9 Algoritm pentru verificarea simetriei unui vector


1:
2:
3:
4:
5:
6:
7:
8:
9:
10:
11:

Input {N, x1 , x2 , ..., xN }


i 1, j N
while ((i < j) (xi = xj )) do
ii+1
j j1
end while
if (i j) then
Output {Vectorul este simetric.}
else
Output {Vectorul nu este simetric.}
end if

posibil acest lucru. Valoarea cu care ram


ane variabila index reprezint
a cel de-al n-lea element
al sirului.
Algorithm 10 Algoritm pentru determinarea celui deal nlea element
1:
2:
3:
4:
5:
6:
7:
8:

Input {N }
k1
index N
while (k < index) do
index index k
k k+1
end while
Output {Cel de-al n-lea element este, index}

Exemplul 1.12 Se dau doua siruri de numere naturale, a1 , a2 , . . . , an si b1 , b2 , . . . , bn (ai , bi


N). Sa se determine doua numere naturale d si e (d, e N) astfel nc
at:
(a21 + b21 ) (a22 + b22 ) . . . (a2n + b2n ) = d2 + e2
Vom demonstra prin inductie matematica faptul ca
ai , bi N, i = 1, n, d, e N astfel nc
at (a21 + b21 ) (a22 + b22 ) . . . (a2n + b2n ) = d2 + e2 (1.1)
Fie n = 2. Ar trebui sa determinam valorile necunoscutelor d si e astfel nc
at sa avem
(a21 + b21 ) (a22 + b22 ) = d2 + e2 .
Dac
a vom considera
(
d = a1 a2 + b1 b2
e = a1 b2 b1 a2
atunci rezult
a ca
2
2
d +e = (a1 a2 +b1 b2 )2 +(a1 b2 b1 a2 )2 = a21 a22 +b21 b22 +2a1 a2 b1 b2 +a21 b22 +b21 a22 2a1 a2 b1 b2 =
a21 a22 + b21 b22 + a21 b22 + b21 a22 = (a21 + b21 ) (a22 + b22 ),
si relatia anterioar
a devine adevarat
a.
Presupunem ca relatia (1.1) este adevarat
a k n.
Vom arat
a ca ea este adevarat
a si pentru n + 1:
ai , bi N, i = 1, n + 1, d, e N astfel nc
at (a21 + b21 ) (a22 + b22 ) . . . (a2n+1 + b2n+1 ) = d2 + e2
(1.2)
14

Aplic
and de doua ori ipoteza de inductie avem:
(a21 + b21 ) (a22 + b22 ) . . . (a2n + b2n ) (a2n+1 + b2n+1 ) = (u2 + v 2 ) (a2n+1 + b2n+1 ) = d2 + e2 (1.3)
|
{z
}
u2 +v 2

unde e = uan+1 + vbn+1 si e = ubn+1 van+1 .


Pe baza modelului de constructie pentru d si e, prezentat n demonstratia anterioara, am
realizat algoritmul 11.
Algorithm 11 Algoritm pentru calculul elementelor unei expresii
1:
2:
3:
4:
5:
6:
7:
8:
9:
10:

Input {N, a1 , . . . , aN , b1 , . . . , bN }
d a1
e b1
for i 2, N do
u = d ai + e bi
v = d bi e ai
du
ev
end for
Output {d, e}

Exemplul 1.13 Se da un numar de 9 monede de acelasi tip. Printre ele s-a strecurat o
moned
a falsa: ea este mai grea sau mai usoar
a dec
at celelalte. Avem la dispozitie o balant
a
far
a greut
ati. Se cere un algoritm care sa determine moneda falsa, stiind ca putem folosi
cantarul doar pentru 3 operatii de cant
arire.
Fie a1 , a2 , . . . , a9 cele 9 monede. Vom mp
arti monedele n trei grupe egale: A = {a1 , a2 , a3 },
B = {a4 , a5 , a6 } si C = {a7 , a8 , a9 }. Pentru a determina moneda falsa avem nevoie de doua
informatii:
1. n ce grupa se afla moneda falsa;
2. cum este moneda falsa (mai grea sau mai usoar
a fat
a de restul).
Notam cu g() greutatea unei monede sau a unei multimi de monede. Vom compara grupurile
A si B cu ajutorul balantei.
1. g(A) = g(B).
aceast
In
a situatie moneda falsa se afla n cea dea treia grupa, C = {a7 , a8 , a9 }.
Compar
am monedele a7 si a8 , rezult
and una din situatiile:
(a) g(a7 ) = g(a8 ) a9 este moneda falsa.
(b) g(a7 ) < g(a8 ).
Compar
am monedele a7 cu a9 :
i. g(a7 ) = g(a9 ) a8 este moneda falsa si este mai grea dec
at restul monedelor.
ii. g(a7 ) > g(a9 ) a8 > a7 > a9 (imposibil deoarece doua monede trebuie sa fie
egale).
iii. g(a7 ) < g(a9 ) a7 este moneda falsa si este mai usoar
a dec
at restul monedelor.
15

(c) g(a7 ) > g(a8 ).


Se trateaz
a n mod analog cu b).
2. g(A) < g(B).
Compar
am grupa A cu grupa C:
(a) g(A) = g(C) moneda falsa se afla n grupa B si este mai grea.
Compar
am a4 cu a5 :
i. g(a4 ) = g(a5 ) a6 este moneda falsa.
ii. g(a4 ) < g(a5 ) a5 este moneda falsa (deoarece este mai grea).
iii. g(a4 ) > g(a5 ) a4 este moneda falsa.
g(A)<g(B)

(b) g(A) < g(C)

g(B) = g(C) moneda falsa se afla n grupa A si este mai


usoar
a.
Compar
am a1 cu a2 :
i. g(a1 ) = g(a2 ) a3 este moneda falsa.
ii. g(a1 ) < g(a2 ) a1 este moneda falsa (deoarece este mai usoar
a).
iii. g(a1 ) > g(a2 ) a2 este moneda falsa.
(c) g(A) > g(C) - nu poate sa apar
a deoarece am avea g(B) > g(A) > g(C) iar doua
grupe trebuie sa fie egale n greutate.
3. g(A) > g(B).
Rationamentul este analog cu cel de la punctul 2).
Exemplul 1.14 Se dau n numere naturale a1 , a2 , . . . , an (ai N). Se cere sa se determine
numarul de cifre de 0 n care se termina produsul a1 a2 . . . an .
Fie exp2 valoarea exponentului numarului 2 din descompunerea n factori primi a termenului produs a1 a2 . . .an , si exp5 valoarea exponentului numarului 5 din descompunerea n
factori primi a aceluiasi termen. Numarul de zerouri n care se termina produsul a1 a2 . . .an
este min (exp2, exp5) (vezi algoritmul 12).
Exemplul 1.15 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 13).
Compararea a doua numere naturale ce prezint
a acelasi numar de cifre se face ncep
and
de la stanga spre dreapta (de la cifrele mai semnificative catre cele mai putin semnificative).
Fie A = a1 a2 . . . an numarul nostru.Vom nota prin m numarul de cifre ce vor ram
ane 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.
Num
arul initial contine n cifre, pastrate n ordine, n vectorul a: a1 , a2 , ..., an . Solutia se
construieste n mod iterativ, la pasul i aleg
anduse o valoare corespunz
atoare: se determina
prima cifra de valoare maxima si pozitia acesteia n cadrul secventei 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
16

Algorithm 12 Algoritm pentru calculul numarului de zerouri ale unui produs de numere
1:
2:
3:
4:
5:
6:
7:
8:
9:
10:
11:
12:
13:
14:
15:
16:
17:
18:
19:

Input {N, a1 , . . . , aN }
exp2 0
exp5 0
for i 1, N do
while ((ai 6= 0) (ai mod 2 = 0)) do
exp2 exp2 + 1
ai ai /2
end while
while ((ai 6= 0) (ai mod 5 = 0)) do
exp5 exp5 + 1
ai ai /5
end while
end for
if (exp2 < exp5) then
min exp2
else
min exp5
end if
Output {min}

Algorithm 13 Algoritm pentru determinarea numarului maxim obtinut dupa eliminarea a


k cifre
1:
2:
3:
4:
5:
6:
7:
8:
9:
10:
11:
12:
13:
14:
15:
16:

Input {n, a1 , . . . , an , k}
mnk
l0
for i 1, m do
l l+1
jl
max 0
while (j n m + i) do
if (max < aj ) then
max aj
lj
end if
j j+1
end while
Output {max}
end for

reprezinta limita superioara a indexului pana la care se poate alege un element la pasul
curent;
a1 . . .
al . . . anm+i
. . . an
|
{z
}
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
17

la dreapta dincolo de aceasta limita nu se mai pot alege restul de m i cifre);


linia 10 - se actualizeaza valoarea maxima;
linia 11 - se actualizeaza pozitia valorii maxime.
Exemplul 1.16 Vom prezenta doi algoritmi ce determina toate tripletele de numere naturale
(a, b, c) ce verifica relatia a2 + b2 = c2 unde c 100.
Prima varianta de lucru prezint
a generarea tuturor tripletelor (i, j, k) unde 2 i < j <
k n. Dintre acestea se vor alege doar acelea ce verifica 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 ca instructiunea 5 se va executa de:
(n i 1) + (n i 2) + . . . + (n n + 1) =

ni1
X
j=1

j=

(n i) (n i 1)
2

ori.
P
Pni1
P
Pn2 2
(ni)(ni1)
1
Din i = 2, n 2 rezulta c
a n2
j = n2
=
i=2
j=1
i=2
i=2 (i + (1 2n)i +
2
2
P
P
n2
n2
n2 n) = 12 [ i=2 i2 + (1 2n) i=2 i + (n2 n) (n 3)]
1:
2:
3:
4:
5:
6:
7:
8:
9:
10:
11:

Input {n}
for i 2, n 2 do
for j i + 1, n 1 do
for k j + 1, n do
if (k k = i i + j j) then
Output {i, j, k}
end if
end for
end for
end for
return

1:
2:
3:
4:
5:
6:
7:
8:
9:
10:
11:

Input {n}
for i 2, n 1 do
for j i + 1, n do
sii+jj

k b sc
if ((k k = s) (k n)) then
Output {i, j, k}
end if
end for
end for
return

Pentru cea de-a doua varianta, se genereaz


a perechile de numere (i, j) cu proprietatea ca
2 i < j n, se
calculeaz
a s i i + j j si se verifica daca numarul obtinut este patrat
perfect (linia 6: (b sc)2 = 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 totala reprezent
and
numarul de verificari (linia 6) va fi:
n1
n1
n1
X
X
X
n(n 1)
n+1
(n i) =
(n i) (n 1) =
i (n 1) =
2
i=2
i=1
i=1

1.3

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.
18

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. Pentru un numar b, (1 < b < 10) sa se determine toate numerele naturale mai mici
decat o valoare specificata n, (1 n 105 ) care scrise n baza b, contin numai cifrele
0 si 1.
Intrare
7 5000
Iesire
0 1 7 8 49 50 56 57 343 344 350 351 392 393 399 400 2401

4. (a) Pentru un numar natural n dat (n < 32000), sa se determine ultima cifra nenula
a lui n!.
(b) Pentru un numar natural n dat (n < 1000), sa se determine numarul de cifre ale
lui n!.
5. Se da un numar natural n (0 n 106 ).
(a) Se calculeaza suma factorialelor cifrelor numarului n si se obtine un nou numar.
(b) Se calculeaza suma cuburilor cifrelor numarului n.
Dupa aceasta procedeul se repeta aplicandu-se de fiecare data numai una din cele doua
operatii (a) sau (b) (cea cu care s-a nceput). Sa se afiseze sirul de numere astfel
obtinut pana cand elementele lui ncep sa se repete. Se cere lungimea listei pana la
prima repetitie si pozitia elementului care se repeta primul.
6. O secvent
a Farey[45] de ordinul n este un sir de numere rationale
n, (a, b) = 1. Secventele Farey de ordinul 1, 2 si 3 sunt:

a
b

unde 0 a b

0 1
F1 = { , }
1 1
0 1 1
F2 = { , , }
1 2 1
0 1 1 2 1
F3 = { , , , , }
1 3 2 3 1
Pentru un numar natural n (n < 216 ) sa se determine elementele secventei Farey de
ordinul n.
Intrare
7

19

Iesire
(0,1),(1,7),(1,6),(1,5),(1,4),(2,7),(1,3),(2,5),(3,7),(1,2),(4,7),(3,5),
(2,3),(5,7),(3,4),(5,6),(6,7),(1,1)

Indicatie Fie ab11 , ab22 , ab33 trei elemente consecutive ale unei secvente Farey de ordinul n.
Atunci avem relatiile ([67]):
b1 a2 a1 b2 = 1
a1 + a3
a2
=
b2
b1 + b3

(1.4)
(1.5)

Daca ab11 si ab33 sunt doua elemente consecutive ale unei secvente Farey de ordinul n,
3
este un element al secventei Farey de ordinul n + 1.
atunci ab11 +a
+b3
Fie ab11 si ab22 doua elemente consecutive ale unei secvente Farey de ordinul n. Elementul
+a3
urmator ce apartine secventei, ab33 , se poate determina astfel: ab22 = ab11 +b
. k N astfel
3
ncat ka2 = a1 + a3 si kb2 = b1 + b3 a3 = ka2 a1 si b3 = kb2 b1 . Valoarea lui k
trebuie aleasa astfel ncat fractia ab33 sa fie cat mai apropiata de ab22 . Astfel k trebuie sa
1
.
fie cat mai mare, nsa respectand restrictia k n+b
b2
7. Fiind date doua numere ntregi X si Y , sa se determine cea mai mica baza pentru Y
astfel ncat X si Y sa reprezinte aceeasi valoare.
De exemplu numerele 12 si 5 nu sunt egale n baza 10 (1210 6= 510 ), nsa ele sunt egale
daca le consideram ca fiind reprezentate n bazele 3 si respectiv 6: 123 = 510 , 56 = 510 .
Bazele n care se vor considera reprezentate numerele au valoarea maxima 36.
Intrare
12 5
10 A
12 34
123 456
10 2

Iesire
123 = 56
1010 = A11
1217 = 345
no solution
102 = 23

(ACM, 1995)

8. Sa se calculeze 2n pentru 30 < n < 100.

20

Capitolul 2
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 algoritmului. 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 2.1 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 2.2 Timpul mediu de executie al unui algoritm A este o functie TAmed : N N
unde TAmed (n) reprezint
a numarul mediu de instructiuni executate de catre 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 inferioar
a 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 2.3 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 }.
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 2.1 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 aplicand regula valorii maxime
vom avea: O(f (n)) = O(max(7 n5 , n2 , 3 log n)) adica O(f (n)) = O(n5 ).
O(loga n) = O(logb n)
21

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 2.4 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 2.5 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 2.2 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 2.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 }
Observatia 2.3 f (n) = o(g(n)) limn

f (n)
g(n)

= 0.

Definitia 2.7 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 2.4

1. f (n) = (g(n)), g(n) = (h(n)) = f (n) = (h(n)) (tranzitivitate)

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


3. f (n) = (g(n)) g(n) = (f (n)) (simetrie)
Definitia 2.8 O functie f are o crestere exponential
a daca c > 1 a.i. f (x) = (cx ) si
x
d a.. f (x) = O(d ). O functie f este polinomial
a de gradul d daca f (n) = (nd ) si
0
f (n) = O(nd ), d0 d.
Teorema 2.5

Exemplul 2.6
Deoarece

c
g(n) (f (n)), c > 0
g(n)
lim
= 0
g(n) o(f (n))
n f (n)

f (n) o(g(n))

(2.1)

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


xn
xn
xn
xn
1
< 4

=
= ( )n
4
4
n/2
2n
n!
(x )
x
x
x
. . x}
| .{z

(2.2)

dn/2eori

rezult
a ca

xn
=0
n n!
lim

22

(2.3)

log n o(n).

1
log x
(log x)0
x ln 2
= lim
lim
= lim
=0
x x
x (x)0
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.
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 ).
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 dea 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 instructiunilor 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
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 este:
n
X
i=1

t=t

n
X

1 = t n = O(n)

(2.4)

i=1

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


P
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
23

1: i l
2: while (ai < x) do
3:
...
4:
ii+1
5: end while

. calcule ce pot modifica, direct sau indirect, valoarea lui ai

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 teoria
probabilitatilor n vederea obtinerii unei estimari a acestui numar.
Exemplul 2.7 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 doua instructiuni de ciclare imbricate
este O(n m).
Exemplul 2.8 S
a consider
am un alt exemplu: se da P
un sir a de n numere reale, si se doreste
calcularea elementelor unui sir b astfel nc
at bi = 1i ij=1 aj , pentru i = 1, n.
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

Dac
a notam cu o constant
a cx timpul necesar pentru efectuarea unei operatii atomice,
vom obtine: costul efectu
arii 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 .
T (n) = c1 n + c2 n +

n
X
i=1

c3 i +

n
X

c4 i + c5 n + c6 = c1 n + c2 n + c3

i=1

n
X
i=1

i + c4

n
X

i + c5 n + c6

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 asa de detaliata a algoritmului, dar se ncearc
a o evaluare a blocurilor principale cum ar fi instructiunile de ciclare, atribuindule direct valori
corespunz
atoare complexitatii timp, atunci cand este posibil:
T (n) = O(n2 ) + O(n) + O(1) = O(n2 )
24

(2.5)

Vom modifica algoritmul anterior astfel nc


at sa reducem numarul de calcule efectuate:
1:
2:
3:
4:
5:
6:

s0
for i 1, n do
s s + ai
bi si
end for
return

Pentru aceast
a varianta de lucru, complexitatea timp este
T (n) = O(1)(linia 1) + O(n)(liniile 24) + O(1)(linia 6) = O(n)

(2.6)

urma analizei de complexitate, putem concluziona ca cea de-a doua varianta a algoritmului
In
ruleaz
a ntrun timp liniar.

2.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) =
(2.7)
n
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 ca
T (n) cn log n

(2.8)

Presupunem mai ntai ca inecuatia (2.8) are loc pentru k < n, inclusiv pentru n2 , adica
T ( n2 ) c n2 log ( n2 ).
Vom demonstra ca inecuatia (2.8) 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
cn log n

(2.9)

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.

2.2

Schimbarea de variabil
a

Aceasta metoda presupune realizarea unei schimbari de variabila n vederea simplificarii


formulei sau pentru regasirea unei formule deja cunoscuta.
25


Sa consideram ecuatia T (n) = 2T ( n) + log n. Daca nlocuim n cu 2m obtinem:
m

T (2m ) = 2T (2 2 ) + log 2m = 2T (2 2 ) + m

(2.10)

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


m
S(m) = 2S( ) + m
(2.11)
2
Se observa ca aceasta relatie are o forma similara cu cea din (2.7).
Solutia relatiei (2.11) este:
S(m) = O(m log m) T (n) = T (2m ) = S(m) = O(m log m) = O(log n log log n)

(2.12)

In concluzie avem T (n) = O(log n log log n).

2.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 formula
de recurenta
(
1
, daca n = 1
T (n) =
(2.13)
n
T ( 2 ) + 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
= T( ) + 3
8
...
n
= T( k) + k
2
n
Prin urmare atunci cand 1 = 2k k = log n, vom avea
T (n) = 1 + k = 1 + log n T (n) = O(log n).
Fie o alta formula de recurenta:
(
1
, daca n = 1
T (n) =
n
2T ( 2 ) + n , n rest
Inlocuind succesiv pe n cu

(2.14)

(2.15)

n
2

vom obtine urmatoarea serie de identitati:


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

(2.16)

(2.17)

2.4

Teorema master

Metoda master pune la dispozitie o varianta de rezolvare a unor recurente de forma


n
T (n) = aT ( ) + f (n)
(2.18)
b
unde a 1, b > 1 sunt constante, iar f (n) este o functie asimptotic pozitiva. Formula de
recurenta (2.18) 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 2.9 (Teorema master) [35] Fie a 1 si b > 1 doua constante, f (n) o functie
asimptotic pozitiv
a, si T (n) definita de relatia de recurenta T (n) = aT ( nb ) + f (n), unde nb va
fi b nb c sau d nb e.
Atunci T (n) este marginit
a asimptotic dupa cum urmeaz
a:
1. dac
a f (n) = O(nlogb a ) pentru o constant
a > 0, atunci T (n) = (nlogb a );
2. dac
a f (n) = (nlogb a logk n), atunci T (n) = (nlogb a logk+1 n) (de obicei k = 0);
3. dac
a f (n) = (nlogb a+ ) pentru o constant
a > 0, si daca af ( nb ) cf (n) pentru o
constant
a c < 1 si oricare numar n suficient de mare, atunci T (n) = (f (n)).
Corolarul 2.10 Pentru o functie f de tip polinomial unde f (n) = cnk avem:
1. dac
a a > bk atunci T (n) = O(nlogb a );
2. dac
a a = bk atunci T (n) = O(nk log n);
3. dac
a a < bk atunci T (n) = O(nk ).
Exemplul 2.11
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 . Aplicand corolarul
pentru cazul a < bk , se obtine solutia T (n) = O(n3 ).
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. Aplicand 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) =
(nlog4 3+ ) 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.
27

2.5

Exercitii

1. Sa se evalueze complexitatea algoritmului 14, ce realizeaza descompunerea n factori


primi a unui numar natural.
Algorithm 14 Algoritm pentru descompunerea n factori primi a unui numar natural
1:
2:
3:
4:
5:
6:
7:
8:
9:
10:
11:
12:
13:

Input {n}
j2
while (j n) do
if (n mod j = 0) then
k0
while (n mod j = 0) do
k k+1
n n div j
end while
Output {j k }
end if
j j+1
end while

2. 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 ) +

n
2

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


Algorithm 15 Algoritm Calcul5
1: for i 1, n do
2:
jn
3:
while (j 1) do
4:
...
5:
j b 2j c
6:
end while
7: end for

4. Sa se calculeze timpul mediu de lucru T (n) al algoritmului 16.


Algorithm 16 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 b 2i c
9: end while

28

5. Intr-o secventa de n numere naturale (a1 , a2 , . . . , an , ai N, 1 ai 65535, 1 n


107 ) se spune ca un numar este majoritar daca numarul de aparitii este cel putin n2 + 1.
Sa se realizeze un algoritm ce determina daca avem vreun numar majoritar n secventa
de n elemente, si n caz afirmativ sa se afiseze aceasta valoare.
6. Sa se rezolve ecuatiile recurente:
T (n) = 2 T ( n2 ) + n lg n, n = 2k ;
T (n) = 3 T ( n2 ) + c n, n = 2k > 1.
7. Sa se rezolve ecuatia recurenta T (n) = n T 2 ( n2 ), n = 2k , T (1) = 6.
8. Sa se arate ca ex = 1 + x + (x2 ) unde x .

29

Capitolul 3
Grafuri. Grafuri neorientate
3.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 3.1 Se numeste graf o pereche ordonata 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. 3.1: a) Un exemplu de graf neorientat cu 6 varfuri b) Graful complet K5

30

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 3.1 Un graf neorientat, simplu, finit poate fi utilizat drept model de reprezentare
al unei relatii simetrice peste o multime.
Definitia 3.2 Se numeste graf complet de ordinul n, si se noteaz
a cu Kn , un graf cu proprietatea ca oricare doua varfuri distincte ale sale sunt adiacente (x V, y V, x 6= y
[x, y] E).
Exemplul 3.2 S
a consider
am grafurile din figura 3.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 3.1 a));
b) K5 este graful complet cu 5 varfuri. Varfurile 3 si 5 sunt adiacente (vezi figura 3.1 b)).

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

Definitia 3.3 Un graf partial al unui graf dat G = (V, E) este un graf G1 = (V, E1 ) unde
E1 E.
Definitia 3.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 extremit
ati 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 ;
31

- G E1 =< E \ E1 >G ;
- G e = G {e}, graful partial obtinut prin eliminarea unei muchii e.
Exemplul 3.3 Graful partial din figura 3.2 c) se obtine din graful 3.1 b) prin stergerea
muchiilor [2, 4], [2, 5], [3, 5], [4, 5]. Subgraful din figura 3.2 b) este indus de multimea V1 =
{1, 3, 4, 5} din graful complet K5 (H = K5 |V1 ).
Definitia 3.5 Gradul unui varf este egal cu numarul muchiilor incidente cu varful x si se
noteaz
a cu d(x) (d(x) = |{[x, u]|[x, u] E, u V }|). Un varf cu gradul 0 (d(x) = 0) se
numeste v
arf izolat.
Notam (G) = min{dG (u)|u G} si (G) = max{dG (u)|u G}.

Fig. 3.3: Graful lui Petersen

Exemplul 3.4 Graful lui Petersen din figura 3.3 este un exemplu de graf trivalent sau cubic
(toate varfurile grafului au acelasi grad, 3).
Propozitia 3.5 Pentru un graf G = (V, E), V = {x1 , x2 , . . . , xn }, |E| = m, avem urmatoarea
relatie:
n
X
d(xk ) = 2m.
(3.1)
k=1

Astfel n orice graf G exista un numar par de varfuri al caror grad este un numar impar.
Definitia 3.6 Se numeste secventa
grafic
a un sir de numere naturale d1 , d2 , . . . , dn cu
proprietatea ca ele reprezint
a gradele varfurilor unui graf neorientat.
Corolarul 3.6 Pentru ca o secvent
a de numere naturale d1 , d2 , . . . , dn sa fie secvent
a grafic
a,
este necesar ca:
1. k = 1, n, dk n 1;
Pn
ar par.
2.
k=1 dk este un num
Definitia 3.7 Un lant L = [v0 , v1 , . . . , vm ] este o succesiune de varfuri cu proprietatea ca
oricare doua varfuri vecine sunt adiacente ([vi , vi+1 ] E, i = 0, m 1). Varfurile v0 si vm
se numesc extremit
atile lantului, iar m reprezint
a lungimea lantului.
32

Daca varfurile v0 , v1 , . . . , vm sunt distincte doua cate doua, lantul se numeste elementar
(vi 6= vj , i, j = 0, m).
Definitia 3.8 Un lant L pentru care v0 = vm se numeste ciclu.
Definitia 3.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 3.10 Un lant L ce contine fiecare muchie exact o singura data 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 3.7 [1, 2, 3, 1, 4] este un exemplu de lant n graful din figura 3.2 c). Varfurile 1
si 4 sunt extremit
atile lantului. Lantul nu este elementar deoarece varful 1 se nt
alneste 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 3.2 a). [4, 5, 1, 2, 4, 3] este un
acest graf nu exista nici un ciclu eulerian.
lant eulerian, precum si [2, 4, 3, 2, 1, 5, 4]. In

Fig. 3.4: Componente conexe

Definitia 3.11 Un graf se numeste conex daca pentru orice pereche de varfuri x si y exist
a
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 apartin
and grafului initial.
Definitia 3.12 O muchie e E se numeste muchie critic
a dac
a prin eliminarea acesteia
o component
a conex
a se mparte n doua sau mai multe componente conexe.
Exemplul 3.8 Graful din figura 3.4 are doua componente conexe: {1, 2, 3, 4, 5, 6} si {7, 8, 9}.
Muchia [2, 5] este muchie critica.
Definitia 3.13 Un graf planar este un graf ce poate fi reprezentat n plan astfel nc
at
muchiile sale sa nu se intersecteze doua cate doua.
Definitia 3.14 Un graf G = (V, E) se numeste graf bipartit dac
a exista 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 cealalt
a extremitate n multimea V2 ([x, y] E
avem x V1 si y V2 ).
33

Propozitia 3.9 Un graf este bipartit daca si numai daca nu contine cicluri de lungime
impar
a.
Definitia 3.15 Un graf bipartit este complet daca x V1 , y V2 , [x, y] E (G =
(V, E), V = V1 V2 , V1 V2 = ). Daca |V1 | = p, |V2 | = q atunci graful bipartit complet se
noteaz
a Kp,q .

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

figura 3.5 a) este ilustrat un graf bipartit (V1 = {1, 2, 3}, V2 = {4, 5}),
Exemplul 3.10 In
iar n cazul b) avem un graf bipartit complet, K2,3 .
Definitia 3.16 Se numeste izomorfism de la graful G la graful G0 o functie bijectiva :
V (G) V (G0 ) astfel nc
at [u, v] E(G) ([u, v]) E(G0 ).
Dac
a exista un izomorfism de la graful G la graful G0 atunci se spune ca G este izomorf
cu G0 si notam acest lucru astfel: G u G0 .
Definitia 3.17 Un graf etichetat este un graf n care fiecare muchie si varf poate avea
asociat
a 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 transformarea 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.
34

3.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. 3.6: Un exemplu de graf complementar altui graf.

In figura 3.6 b) este reprezentat graful complementar Gc al grafului G = (V, E), din
figura 3.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 3.6 a), prin insertia varfului 6 pe muchia [1, 5] se obtine
graful din figura 3.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. 3.7: a) Graful obtinut prin inserarea varfului 6 n graful din figura 3.6 a). b) Graful obtinut prin
contractia muchiei [3, 4] n graful din figura 3.6 a).

In figura 3.7 b) este reprezentat graful obtinut prin contractia muchiei [3, 4] din graful
G = (V, E) (vezi figura 3.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 3.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}.
35

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

Fig. 3.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 3.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 numesste reuniune disjuncta a grafurilor G1 si
G2 , iar G1 G2 este graful vid.
7. Definim suma grafurilor G1 si G2 ca fiind graful complementar al reuniunii complementarelor 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 3.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 }.
36

Fig. 3.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. 3.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 3.11).
9. Se numeste r
ad
acina patrat
a a grafului G, un graf neorientat H cu proprietatea ca
2
2
H = G (H = 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 3.12).

37

Fig. 3.12: Graful rezultat n urma compunerii grafurilor G1 si G2 din figura 3.11

3.3

Moduri de reprezentare

Fig. 3.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 ordonare. Astfel, numerotam n mod arbitrar varfurile grafului cu 1, 2, . . . , |V |.
1. Matricea de adiacent
a - este o matrice A Mnn Z unde n = |V |, iar ai,j = 1 daca
exista o muchie ntre nodurile numerotate i si j (ntre xi si xj ), sau ai,j = 0 daca nu
exista (
o muchie ntre acestea.
ai,j =

1 , daca [xi , xj ] E
0 , daca [xi , xj ]
/E

Pentru un graf neorientat aceasta matrice este simetrica (ai,j = aj,i ).


Exemplul 3.11 Matricea de adiacent
a corespunz
atoare grafului din figura 3.13 este:

0
1

1
A=
0

0
0

1
0
0
0
1
0
0
0

1
0
0
0
1
0
0
0

38

1
0
0
0
0
1
0
0

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

Fig. 3.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)
. Prin urmare complexitateaspatiu a matricii de adiacenta este
2
2
O(n ).
2. Matricea costurilor - reprezinta o varianta a matricei de adiacent
a ce se obtine n mod
natural luanduse n considerare graful ponderat: fiecarei muchii i se ataseaza un cost
d, iar valorile matricii A se definesc astfel:

ai,j

, daca xi = xj
0
= +
, daca [xi , xj ]
/E

d > 0 , daca [xi , xj ] E

(3.2)

ai,j

, daca xi = xj
0
=
, daca [xi , xj ]
/E

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

(3.3)

sau

Exemplul 3.12 Matricea costurilor corespunz


atoare grafului din figura 3.14 are urm
atoarele
valori:

0
35

20
A=

35
0

50

22

20

15

50
22

44

15

0
10
27

44
10
0

27

Notatia (3.2) este folosita n aplicatii n care se cere un drum de lungime minima iar
(3.3) este folosita n aplicatii n care se cere un drum 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 numar real.
39

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


necesari n d n8 e 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 adiacent
a - pentru fiecare nod se retine lista nodurilor adiacente. Astfel unui
nod xk i se va atasa 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 adiacent
a 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 3.13 Listele de adiacent
a pentru graful din figura 3.13 sunt:
1:
2:
3:
4:

(2,
(1,
(1,
(1,

3, 4)
5)
5)
6)

5:
6:
7:
8:

(2, 3, 7)
(4, 7, 8)
(5, 6)
(6)

Aceste liste de adiacent


a (sau liste de vecini) pot fi reprezentate prin intermediul
tablourilor sau prin intermediul listelor simplu sau dublu nlantuite.
Pentru reprezentarea ce utilizeaza tablouri, se vor defini doua tablouri Cap si List
(Cap R1n , List R2m , m = |E|) unde:

0 , nodul respectiv nu are vecini


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

al varfului xk .
List1,j - indicele varfului ce se afla n lista de vecini a varfului xk .

List2,j

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

semnificatie ca si valoarea NIL sau NULL utilizata n lucrul cu pointeri)


=
i , indica numarul coloanei unde se afla urmatorul vecin din lista

de vecini a nodului xk .

Exemplul 3.14 Pentru exemplul din figura 3.13 avem urmatoarea configuratie pentru
cei doi vectori considerati:
Nod
Cap
List =

1
1

2
4

3
6

4
8

5
10

6
13

7
16

8
18

2 3 4 1 5 1 5 1 6 2 3 7 4 7 8 5 6 6
2 3 0 5 0 7 0 9 0 11 12 0 14 15 0 17 0 0

In limbajul C definim 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 (vezi si figura 3.15):

40

NULL

5
6
3

NULL

1
1
2

7
8

NULL

NULL

NULL

NULL

NULL

NULL

Fig. 3.15: Liste de adiacenta

typedef struct nod{


int nodeIndex;
struct nod *next;
}NOD;
#define MAX 100
NOD* vecini[MAX];

N OD vecini[M AX] este un array de pointeri ce contine n vecini[i] capul listei de


vecini ai nodului de indice i. struct nod next reprezinta un pointer catre urmatoarea
nregistrare de tip NOD ce pastreaza informatii cu privire la urmatorul vecin.
Cantitatea de memorie necesara pentru pastrarea listelor de adiacenta este:
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, iar pentru matricea List este 8m. Astfel complexitatea
spatiu este O(n + m).
reprezentarea folosind pointeri - presupunem ca avem 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 (small, huge, etc.) sau de dimensiunea instrutiunilor (pe 16, 32 sau 64 de
biti).
Astfel se utilizeaza 2n octeti pentru vectorul ce contine adresele primului 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.
Aceasta constituie cea mai simpla modalitate de reprezentare a unui graf. Operatia de
adaugare a unui nod sau a unei muchii se face ntrun timp constant, pe cand, alte
operatii sunt mai costisitoare: de exemplu, determinarea listei de vecini a unui nod
necesita un timp (m).
Exemplul 3.15 Pentru pastrarea n memorie se poate folosi o matrice M cu doua linii
si |E| coloane (M M2|E| ), unde:
41

M1,i - indicele varfului ce se afla n prima extremitate a muchiei i;


M2,i - indicele varfului ce se afla n cea dea doua extremitate a muchiei i.

1 1 1 2 3 4 5 6 6
M=
2 3 4 5 5 6 7 7 8
Vom sintetiza complexitatea timp pentru diverse operatii folosind fiecare dintre modurile
de reprezentare prezentate:
Gradul unui nod xi
(xi , xj ) E?
Urmatorul vecin al lui xi

3.4

Matricea de adiacent
a
O(n)
O(1)
O(n)

Liste de vecini
O(d(xi ))
O(d(xi ))
O(d(xi ))

Lista de muchii
O(m)
O(m)
O(m)

Parcurgerea grafurilor

Pentru un graf dat este important sa se determine o modalitate sistematica de vizitare a


tuturor varfurilor grafului, vizitare ce se realizeaza n scopul prelucrarii informatiei continute
de acestea.

3.4.1

Parcurgerea n l
atime (BF-Breadth-First)

Metoda de parcurgere n latime viziteaza nodurile grafului n felul urmator (vezi algoritmul
17):
se viziteaza mai ntai varful de pornire k;
urmeaza n ordine toti vecinii nca nevizitati ai acestuia;
se continua cu vecinii nca nevizitati ai acestora, s.a.m.d.

Fig. 3.16: Arbore de acoperire n latime

Pentru graful considerat n figura 3.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 sa naintat n graf) se obtine un arbore/padure de parcurgere/vizitare/acoperire.
In figura 3.16 este reprezentat arborele de acoperire n latime rezultat n urma parcurgerii
grafului din exemplu.
42

Algoritmul de parcurgere utilizeaza o structura de date de tip coad


a n care vor fi introduse 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:
(
1 , daca nodul k a fost vizitat
vizitatk =
0 , daca nodul k nu a fost vizitat.
Algorithm 17 Algoritm de vizitare n latime
1: procedure BFS(k, n, V ecin)

- nodul de la care se porneste vizitarea


k
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 coada
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 coada
14:
call V izitare(i)
. vizitarea nodului i
15:
end if
16:
end for
17:
end while
18: end procedure

Implementarea n limbajul C a algoritmului 17 este urmatoarea:


#include <stdio.h>
#include <mem.h>
#define MAX 100
#define TRUE 1
#define FALSE 0
// Numarul de noduri din graf
int n;
// Matricea de adiacenta
char vecin[MAX][MAX];
// coada
int coada[MAX];
// Indicele primului element din coada
int first;
// Indicele ultimului element din coada
int last;
// Pastreaza starea cozii

43

int vida;
/**
* Initializarea cozii circulare.
*/
void initQueue(void) {
vida = TRUE;
first = 0;
last = MAX;
}
/**
* Intoarce pentru pozitia i, urmatoarea pozitie din coada.
*/
int next(int i) {
return (i + 1) % MAX;
}
/**
* Insereaza elementul a carui valoare este pastrata in variabila k in coada.
*/
void enQueue(int k) {
last = next(last);
coada[last] = k;
if (vida)
vida = FALSE;
}
/**
* Extrage un element din coada.
*/
int deQueue(void) {
int k = coada[first];
first = next(first);
if (first == next(last))
vida = TRUE;
return k;
}
/**
* Parcurge in latime graful pornind de la nodul de inceput k.
*/
void bfs(int k) {
int i;
char vizitat[MAX];
memset(vizitat, 0, sizeof(vizitat));
vizitat[k] = 1;
enQueue(k);
printf("%d ", k);
while (!vida) {
k = deQueue();

44

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


if ((vizitat[i] == 0) && (vecin[k][i] == 1)) {
vizitat[i] = 1;
enQueue(i);
printf("%d ", i);
}
}
}
/**
* Se citesc numarul de noduri precum si matricea de adiacenta.
*/
void readInput(void) {
int i, j;
printf("n = "); scanf("%d",&n);
for (i = 0; i < n - 1; i++)
for (j = i + 1; j < n; j++) {
printf("a[%d,%d] = ", i, j);
scanf("%d", &vecin[i][j]);
vecin[j][i] = vecin[i][j];
}
}
void main(void) {
readInput();
initQueue();
bfs(0);
}

In exemplul anterior functia de citire readInput() este foarte simpla: se citeste mai
ntai numarul de noduri al grafului, si apoi se citeste matricea de adiacenta a grafului.
Variabilele n si vecin sunt declarate drept variabile globale. Deoarece matricea vecin
este simetrica si pentru a reduce numarul de citiri, se vor solicita numai valorile aflate
deasupra diagonalei principale:
void readInput(void) {
int i, j;
printf("n = "); scanf("%d",&n);
for (i = 0; i < n - 1; i++)
for (j = i + 1; j < n; j++) {
printf("a[%d,%d] = ", i, j);
scanf("%d", &vecin[i][j]);
vecin[j][i] = vecin[i][j];
}
}

Functia memset() este o functie de biblioteca a carei declaratie poate fi gasita n headerele mem.h si string.h la Borland C sau memory.h si string.h la Visual C++ 2010 1 .
Functia are urmatoarea semnatura2
1
2

http://msdn.microsoft.com/en-us/library/1fdeehz6.aspx
http://en.wikibooks.org/wiki/C_Programming/Strings#The_memset_function

45

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 si valoarea false n caz contrar.
Coada este implementata static sub forma unei cozi circulare.
Vizitarea ncepe de la nodul 0 (bfs(0)).
Algoritmul BF 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 numarul de noduri intermediare ale acestuia.

3.4.2

Parcurgerea D (D - Depth)

Metoda de parcurgere D este asemanatoare cu metoda de parcurgere n latime (BF ):


la nceput se viziteaza varful de pornire k;
urmeaza n ordine toti vecinii nca nevizitati ai acestuia;
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 (vezi
sectiunea 3.4.1) si metoda de parcurgere n adancime (vezi sectiunea 3.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 D se nlocuieste structura de date de tip coada utilizata n
algoritmul BF, cu o structura de date de tip stiva (vezi algoritmul 18).

Fig. 3.17: Arbore de acoperire pentru parcurgerea D

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


din figura 3.13.
46

Algorithm 18 Algoritm de vizitare D


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

- nodul de la care se porneste vizitarea


k
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:
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 stiva
10:
for i 1, n do
11:
if (vizitati = 0) (vecink,i = 1) then
12:
vizitati 1
. marcarea nodului i ca fiind vizitat
13:
Si
. inserarea nodului i in stiva
14:
call V izitare(i)
. vizitarea nodului i
15:
end if
16:
end for
17:
end while
18: end procedure

3.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 k;
urmeaza, n ordine, 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.
Pentru graful considerat (vezi figura 3.13), n urma parcurgerii n adancime, vom obtine
nodurile n ordinea urmatoare: 1, 2, 5, 3, 7, 6, 4, 8 (vezi figura 3.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 adancime.
In urma parcurgerii n adancime muchiile unui graf pot fi clasificate n urmatoarele categorii:
1. muchie a arborelui de acoperire - muchia [u, v] este o muchie a arborelui de acoperire
daca df s(u) apeleaza direct df s(v) sau invers;
2. muchie de ntoarcere - muchia [u, v] este o muchie de ntoarcere daca df s(u) apeleaza
indirect df s(v) (x V a.i. df s(u) df s(x) df s(v)) sau invers.
47

Fig. 3.18: Arbore de acoperire n adancime

Exemplul 3.16 Pentru graful din figura 3.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;
Algorithm 19 Algoritm de vizitare n adancime (varianta recursiva)
1: procedure DFS(k, n, V ecin)

- nodul vizitat
k
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 (vezi algoritmul 19),
fie n varianta nerecursiva (vezi algoritmul 20). Ca si la algoritmul de parcurgere n latime,
vectorul vizitat
( pastreaza situatia vizitarii nodurilor grafului G:
1 , daca nodul k a fost vizitat
vizitatk =
0 , n caz contrar.
Subrutina DFS (algoritmul 19) ncepe cu marcarea nodului curent ca fiind vizitat (vizitatk
1) si apelul procedurii V izitare (call V izitare(k)). Apoi se cauta primul vecin nevizitat i
al varfului curent k ((vizitati = 0) (vecink,i = 1)) si se reia secventa de operatii pentru
acesta, printrun apel recursiv (call DF S(i)). Enuntul repetitiv for i 1, n 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
48

de vedere al numarului de operatii efectuate, fiind preferata reprezentarii prin matricea de


adiacenta.
Algorithm 20 Algoritm de vizitare n adancime (varianta nerecursiva)
1: procedure DFS Nerecursiv(k, n, V ecin)

- nodul de la care se porneste vizitarea


k
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 stiva
8:
f ound f alse
9:
while (S 6= ) do
. cat timp stiva nu este vida
10:
if ((f ound)) then
11:
Sk
. extragerea nodului curent din stiva
12:
end if
13:
i1
14:
f ound f alse
15:
while (i n) (f ound = 0) 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 stiva
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 20 utilizeaza o 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 20.1320.25 nu este optima deoarece, de fiecare data cand se revine la un nod
parinte, se verifica ntreaga multime V pentru a identifica un vecin nevizitat al acestuia. In
vederea reducerii numarului de verificari, se va utiliza reprezentarea prin liste de adiacenta,
si se salveaza pe stiva, pe langa valoarea nodului curent k, valoarea vecinului nevizitat gasit,
astfel ncat, la revenire, sa se continue cautarea urmatorului vecin nevizitat al nodului curent
k pornind de la acest nod. Daca nu mai exista nici un vecin nevizitat al varfului k (linia
10), atunci se revine la parintele nodului curent aflat 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 aici varianta recursiva pentru usurinta de programare si eleganta ei. Prezentam
n continuare implementarea n limbajul C a algoritmului 19:
#include <stdio.h>

49

#include <mem.h>
#define MAX 100
// Matricea de adiacenta
char vecin[MAX][MAX];
// Vector ce pastreaza starea unui nod: vizitat sau nevizitat
char vizitat[MAX];
// Numarul de varfuri din graf
int n;
// Nodul din care se porneste vizitarea
int nodi;
/**
* Se citesc numarul de noduri precum si matricea de adiacenta.
*/
void readInput(void) {
int i, j;
printf("n = "); scanf("%d",&n);
for (i = 0; i < n - 1; i++)
for (j = i + 1; j < n; j++) {
printf("a[%d,%d] = ", i, j);
scanf("%d", &vecin[i][j]);
vecin[j][i] = vecin[i][j];
}
printf(Nodul initial = ); scanf(%d,&nodi);
memset(vizitat, 0, sizeof(vizitat));
}
/**
* Parcurgerea in adancime pornind din nodul k.
*/
void dfs(int k) {
int i;
vizitat[k] = 1;
printf("%d ",k);
for (i = 0; i < n; i++)
if (vizitat[i] == 0 && vecin[k][i] == 1)
dfs(i);
}
void main(void) {
readInput();
dfs(nodi);
}

Complexitatea algoritmilor de parcurgere


Complexitatea algoritmilor prezentati este O(n2 ) deoarece se utilizeaza matricea de adiacenta drept modalitate de reprezentare a grafului. Daca se utilizeaza reprezentarea prin liste de
50

vecini, atunci complexitatea algoritmilor devine O(n + m), unde m = |E|.


BFS
D
DFS

3.5

Matricea de adiacenta
O(n2 )
O(n2 )
O(n2 )

Liste de vecini
O(n + m)
O(n + m)
O(n + m)

Lista de muchii
O(n + m2 )
O(n + m2 )
O(n + m2 )

Componente conexe

Se poate defini pe multimea varfurilor unui graf neorientat G o relatie astfel: 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. 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
V =

m
[

Vi , Vi Vj = , i, j = 1, m, i 6= j

i=1

si o partitie a multimii muchiilor E


E=

m
[

Ei , Ei Ej = , i, j = 1, m, i 6= j

i=1

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


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

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


unui graf. Pasii algoritmului descrisi n limbaj natural sunt urmatorii (algoritmul 21 prezinta
descrierea formalizata n pseudocod):
Pas 1. Se cauta un nod nca nevizitat.
Pas 2. Incepand cu acesta se parcurg toate nodurile accesibile si nevizitate, avand grija sa
marcam vizitarea acestora. Toate aceste noduri formeaza o componenta conexa.
Pas 3. Daca mai exista noduri nevizitate mergi la pasul Pas 1, altfel algoritmul se termina.
51

3.6

Muchie critic
a

Reamintim faptul ca ntrun graf neorientat G = (V, E), o muchie critica 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 prezentate doua variante de rezolvare.
Solutia I
Pentru simplitate, vom trata situatia n care graful considerat este conex (daca graful nu
este conex se determina numarul de componente conexe 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 (vezi algoritmul 22).
Algorithm 22 Algoritm de determinare a muchiei critice (prima varianta)
1: procedure MuchieCriticaI(n, m, V ecin)

- numarul de noduri din graf


n
Input:
m
- numarul de muchii din graf

V ecin - matricea de adiacent


a
2:
for k 1, m do
3:
elimina muchia k
4:
if (conex(n, vecin) = f alse) then
5:
Output {Muchia k este critica}
6:
end if
7:
adauga muchia k
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)
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

. reface graful initial

. vizitarea n adancime a grafului

Subrutina Conex verifica daca graful identificat prin matricea de adiacenta V ecin este
conex. Subrutina ntoarce valoarea 1 n cazul n care graful considerat este conex si 0 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.
Implementarea n limbajul C:
#include <stdio.h>
#include <mem.h>
#define MAX 100

52

#define TRUE 1
#define FALSE 0
char vecin[MAX][MAX];
char vizitat[MAX];
int n;
/**
* Se citesc numarul de noduri precum si matricea de adiacenta.
*/
void readInput(void) {
int i, j;
printf("n = "); scanf("%d", &n);
for (i = 0; i < n - 1; i++)
for (j = i + 1; j < n; j++) {
printf("a[%d,%d] = ", i, j);
scanf("%d", &vecin[i][j]);
vecin[j][i] = vecin[i][j];
}
}
/**
* Parcurgerea in adancime a grafului pornind din nodul de start k.
*/
void dfs(int k) {
int i;
vizitat[k] = TRUE;
printf("%d ", k);
for (i = 0; i < n; i++)
if ((vizitat[i] == FALSE) && (vecin[k][i] == 1))
dfs(i);
}
/**
* Functia verifica daca graful este conex.
*/
int conex(void) {
int i;
memset(vizitat, 0, sizeof(vizitat));
dfs(0);
for (i = 0; i < n; i++)
if (vizitat[i] == FALSE) {
return FALSE;
}
return TRUE;
}
void main(void) {
int i, j;

53

readInput();
for (i = 1; i < n; i++)
for (j = 0; j < i; j++)
if (vecin[i][j] == 1) {
vecin[i][j] = 0;
vecin[j][i] = 0;
if (conex() == FALSE) {
printf("Muchia (%d,%d) este critica! \n",i,j);
}
vecin[i][j] = 1;
vecin[j][i] = 1;
}
}

Observatia 3.17 Am presupus ca numerotarea nodurilor se face de la 0 si ca exista un nod


cu eticheta 0.
Complexitateatimp a acestui algoritm (presupunem ca avem o singura componenta
conexa), este O(m(n + m)): se ia fiecare muchie (O(m)), se elimina din graf, si se verifica
daca graful ramas este conex (O(n + m)).
Solutia a IIa
Cea dea doua varianta de abordare pentru identificarea unei solutii foloseste urmatoarea
proprietate:
Observatia 3.18 O muchie nu este critica daca ea face parte din cel putin un ciclu elementar 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 - 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:

prenumu , si
lowu = min prenumx , daca [u, x] este muchie de ntoarcere, si

lowy
, y descendent direct al lui u.
Daca prenumu lowv , v descendent direct al lui u, atunci nseamna ca v sau un
descendent al lui v are 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 (vezi algoritmul
23). 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, M uchieCriticaII() si DF S 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 }. Pentru un nod i vecin cu nodul curent
k, deja vizitat, avem o muchie de ntoarcere (nodului k i sa atribuit deja un numar n
preordine): astfel valoarea lui lowk se calculeaza dupa formula lowk min {lowk , prenumi }.
Implementarea n limbajul C a algoritmului 23 este:
54

Algorithm 23 Algoritm de determinare a muchiei critice (a doua varianta)


1: procedure
( MuchieCriticaII(n, V ecin)

n
- numarul de noduri din graf
V ecin - matricea de adiacent
a
for i 1, n do
vizitati 0
end for
counter 0
call DF S critic(1, n, V ecin)
end procedure
procedure DFS critic(k, n, V ecin)
vizitatk 1
counter counter + 1
prenumk counter, lowk counter
for i 1, n do
if (vecink,i = 1) then
if (vizitati = 0) then
tatai k
call DF S critic(i, n, V ecin)
lowk M in(lowk , lowi )
if (prenumk < lowi ) then
Output {Muchia (k,i) este critica}
end if
else
if (tatak 6= i) then
lowk M in(lowk , prenumi )
end if
end if
end if
end for
end procedure

Input:
2:
3:
4:
5:
6:
7:
8:
9:
10:
11:
12:
13:
14:
15:
16:
17:
18:
19:
20:
21:
22:
23:
24:
25:
26:
27:
28:

. vizitarea n adancime a grafului

#include <stdio.h>
#include <mem.h>
#define MAX 100
/**
* Numarul de noduri din graf
*/
int n;
/**
* Matricea de adiacenta
*/
char vecin[MAX][MAX];
/**
* Vector ce pastreaza starea unui nod: vizitat sau nevizitat.
*/
char vizitat[MAX];
/**

55

* Pastreaza tatal fiecarui nod in arborele de acoperire


* in adincime generat de metoda DFS.
*/
int tata[MAX];
/**
* prenum[i] - numerotarea in preordine
*/
int prenum[MAX];
int low[MAX];
/**
* contor global ce numara momentul cand este vizitat un nod.
*/
int counter;
void readInput(void) {
int i, j;
printf("n = "); scanf("%d",&n);
for (i = 0; i < n - 1; i++)
for (j = i + 1; j < n; j++) {
printf("a[%d,%d] = ", i, j);
scanf("%d", &vecin[i][j]);
vecin[j][i] = vecin[i][j];
}
}
int min(int x, int y) {
return (x < y) ? x : y;
}
void dfs(int k) {
int i;
vizitat[k] = 1;
counter++;
prenum[k] = counter; low[k] = counter;
for (i = 0; i < n; i++)
if (vecin[k][i] == 1)
if (vizitat[i] == 0) {
tata[i] = k;
dfs(i);
low[k] = min(low[k], low[i]);
if (prenum[k] < low[i])
printf("%d -> %d \n", k, i);
}
else
if (tata[k] != i)
low[k] = min(low[k], prenum[i]);
/*printf("prenum[%d] = %d low[%d] = %d\n",k,prenum[k],k,low[k]);*/
}
void critic(void) {

56

memset(vizitat, 0, sizeof(vizitat));
counter = 0;
dfs(0);
}
void main(void) {
printf("\n");
readInput();
critic();
}

Fig. 3.19: Algoritmul 23 aplicat pentru graful din figura 3.13

Exemplul 3.19 Pentru graful din figura 3.13, valorile prenum si low sunt cele din figura
3.19 (valoarea din partea dreapt
a a unui varf reprezint
a valoarea sa la numerotarea n preordine, prenum, iar numarul din stanga reprezint
a valoarea calculat
a low). Conditia prenumu <
lowv , unde [u, v] este o muchie a grafului, este ndeplinit
a 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, complexitateatimp 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.

3.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.

57

Fig. 3.20: Un exemplu de graf neorientat cu 8 varfuri

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 3.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 locuri numerotate cu numere ntregi de la 1 la N . Nu exista doua
locuri numerotate cu acelasi numar. Liniile sunt bidirectionale si leaga doua locuri;
fiecare loc are un post telefonic. Din orice loc poate fi apelat oricare alt loc, prin
legatura directa sau conexiuni intermediare. Uneori reteaua cade n unele locuri si
conexiunea aferenta nu mai este posibila. Tehnicienii de la TLC au realizat ca n acest
caz, nu numai ca locul respectiv nu mai poate fi apelat, dar el rupe legatura si ntre
alte locuri pentru care asigura conexiunea. In aceasta situatie spunem ca locul (unde
a aparut caderea) este critic.
Se cere sa de dezvolte un algoritm care sa calculeze 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.
58

7. Intrun 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 ntrun 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 dintrun numar n reprezentand numarul de puncte de intersectie; fiecare punct este identificat prin numarul cu care este numerotat, ntre 1 si n,
urmat de lista punctelor cu care acesta comunica direct printro strada. Prin strad
a 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, dintro
pozitie initiala data ntro 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 un
anumit fel de cultura (de exemplu este specializat numai pe cositul lucernei).
Terenul agricol se modeleaza printro matrice n m. 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 dea lungul a doua elemente
vecine.
Se cere sa se determine numarul de muncitori care pot sa coseasca o cultura data.

59

Capitolul 4
Grafuri euleriene si hamiltoniene
4.1

Grafuri Euleriene

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

Fig. 4.1: Podurile din Konigsberg

Fie G = (V, E) un graf neorientat.


Definitia 4.1 Un lant L ce contine fiecare muchie a unui graf G o singura data se numeste
lant Euler sau lant eulerian. Daca extremitatile 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 (vezi figura 4.2).
Teorema 4.1 (Euler) Un graf G conex (|V | 3) este eulerian daca si numai daca gradul
fiec
arui varf este par.
1

http://en.wikipedia.org/wiki/Seven Bridges of Konigsberg

60

Fig. 4.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 u, 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 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]} are lungimea mai mare decat L, contradictie. Prin urmare u = v. Rezulta ca
L = C este un ciclu de lungime maxima.
Presupunem ca C nu este un 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 G ce contine exact muchiile lui C.
Atunci L2 = [x, . . . , u, v, . . . , x, y] este un lant n G ce are lungimea mai mare decat L,
contradictie. Rezulta atunci ca ciclul C este eulerian G este un graf eulerian.

Teorema 4.2 (Euler) Un graf G conex are un lant eulerian daca si numai daca exista exact
doua varfuri n G al caror grad sa fie impar.
Demonstratie: Se demonstreaza la fel ca la teorema 4.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 G0 = (V 0 , E 0 ),
V 0 = V {w}, E 0 = E {[u, w], [v, w]}, graful obtinut din G prin adaugarea nodului w si a
muchiilor [u, w], [v, w]. Atunci dG0 (u) este par, u V 0 exista un ciclu eulerian C n G0 .
Daca eliminam muchiile [u, w] si [v, w] obtinem un lant eulerian n G.

Corolarul 4.3 Un graf G conex (|V | 3) este eulerian daca si numai daca multimea muchiilor sale poate fi descompus
a n cicluri disjuncte.
Teorema 4.4 Pentru un graf conex cu 2k varfuri de grad impar, k 2, exista k lanturi
n G ale caror multimi de muchii formeaz
a o partitie a lui E, si din care cel mult unul are
lungime impar
a.

4.1.1

Algoritm pentru determinarea unui ciclu eulerian

Pornind de la Teorema 4.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
61

Fig. 4.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 4.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.
Funtia IsEulerian() verifica daca un graf este eulerian conform teoremei 4.1 (vezi algoritmul 24).
Algorithm 24 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.
In algoritmul 25 este prezentata functia EulerRec pentru determinarea unui ciclu eulerian.
62

Procedura F indCycle(G) (vezi algoritmul 25) 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 sa 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(G0 ; k, G11 , G12 , . . . , G1k ) determina componentele conexe ale
grafului G0 = G E(C), G11 , G12 , . . . , G1k fiind cele k componente conexe returnate de
aceasta.
Procedura M ergeCycles(C, C11 , . . . , Ck1 ; C0 ) construieste un ciclul C0 prin adaugarea la
ciclul C, succesiv, a ciclurilor C11 , . . . , Ck1 .
Algorithm 25 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:
G0 G E(C)
10:
call F indComponents(G0 ; 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. 4.4: Grafurile partiale obtinute n urma primelor doua etape ale algoritmului 25

Exemplul 4.5 S
a aplicam algoritmul 25 pentru graful din figura 4.3. Presupunem ca primul
element (elementul de start) este varful u0 = 1. Procedura F indCycle construieste lantul
63

Fig. 4.5: Grafurile partiale obtinute n urma etapelor 3 si 4 ale algoritmului 25

urma eliminarii acestuia


L = [1, 2, 3, 4, 5, 3]. Se observa prezenta ciclului C1 = [3, 4, 5, 3]. In
ram
ane graful din figura 4.4 a) care este un graf conex.
Continu
am procesul de construire a unui ciclu eulerian, prin apelul procedurii F indCycle
pentru noul graf. Aceasta returneaz
a ciclul C2 = [1, 2, 3, 9, 1]. Prin eliminarea muchiilor
continuare se construieste lantul
acestuia, E(C2 ), se obtine graful din figura 4.4 b). In
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 4.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 formeaz
a prin reuniunea ciclurilor intermediare determinate: C =
(((C4 C3 ) C2 ) C1 ) adic
a 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]).

4.1.2

Algoritmul lui Rosenstiehl

Algoritmul lui Rosenstiehl [53] 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 sa ajuns
n u) (vezi algoritmul
26). 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 4.6 S
a aplicam algoritmul 26 pentru graful din figura 4.3. Sa presupunem ca
varful u de la care porneste algoritmul este varful 1. La sfarsitul 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 stiva si astfel u devine 9. La sfarsitul acestuia
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.
64

Algorithm 26 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
6:
while (S 6= ) do
7:
Su
8:
while (e = [u, v] E) (vizite = 0)) do
9:
vizite 1
10:
Su
11:
uv
12:
end while
13:
Lu
14:
end while
15:
return L
16: end function

. Se insereaza pe stiva nodul u


. Se extrage din stiva nodul curent
. Se marcheaz
a muchia e ca fiind utilizata
. Se salveaz
a pe stiva nodul curent u
. Nodul curent devine nodul v
. Se adauga la lista L nodul curent

continuare, deoarece nu mai exista muchii nevizitate, se vor extrage elementele aflate pe
In
stiva S (8, 10, 6, 7, 8, 2, 10, 4, 6, 5, 7, 9, 3, 5, 4, 3, 2, 1), cate unul la fiecare pas, 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 pastrat n lista L.
Prezentam implementarea n limbajul C + + a algoritmului lui Rosenstiehl:
#include
#include
#include
#include
#include
#include
#include

<vector>
<stack>
<list>
<iostream>
<fstream>
<iomanip>
<conio.h>

using namespace std;


#define INPUT_FILE "graf1.txt"
typedef vector<int> Linie;
typedef vector<Linie> Matrice;
int readInput(Matrice& ma) {
int n, i, j, value;
ifstream fin(INPUT_FILE);
fin >> n;
ma = Matrice(n, Linie(n, 0));
for (i = 0; i < n; i++)

65

for (j = 0; j < n; j++) {


fin >> ma[i][j];
}
fin.close();
return n;
}
void print(list<int>& l) {
list<int>::iterator it;
cout << "Ciclu eulerian: [";
for (it = l.begin(); it != l.end(); it++)
cout << *it << ", ";
cout << "]\n";
}
void rosenstiehl(int n, Matrice& ma, int u, list<int>& l) {
stack<int> s;
int v;
s.push(u);
while (!s.empty()) {
u = s.top();
s.pop();
v = 0;
while (v < n) {
if (ma[u][v] == 1) {
ma[u][v] = 0;
ma[v][u] = 0;
s.push(u);
u = v;
v = 0;
}
else
v++;
}
l.push_back(u);
}
}

void main(void) {
Matrice ma;
list<int> l = list<int>();
int n = readInput(ma);
rosenstiehl(n, ma, 0, l);
print(l);
}

Continutul fisierului de intrare graf1.txt corespunzator grafului din figura 4.6 este urmatorul:
66

6
0
1
1
1
1
0

1
0
1
1
1
0

1
1
0
1
0
1

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 ), iar de pe


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

Fig. 4.6: Un alt exemplu de graf eulerian

Pentru a reduce timpul de implementare, am utilizat cateva structuri de date deja existente 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 vector<int> Linie;
typedef vector<Linie> Matrice;
...
Matrice ma;
ma = Matrice(n, Linie(n, 0));

Dupa cum se poate observa din aplicatie, pentru reprezentarea interna a grafului am
ales matricea de adiacent
a.
2

http://en.wikipedia.org/wiki/Standard_Template_Library,
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
3

67

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


unui obiect de tip list<int>::iterator se poate face astfel: list<int>::iterator
it;.
void print(list<int>& l) {
list<int>::iterator it;
cout << "Ciclu eulerian: [";
for (it = l.begin(); it != l.end(); it++)
cout << *it << ", ";
cout << "]\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:
ma[u][v] = 0;
ma[v][u] = 0;

Ciclul eulerian pentru graful din figura 4.6 este: L = [1, 5, 2, 4, 6, 3, 4, 1, 3, 2,


1].

4.1.3

Algoritmul lui Fleury

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

Fig. 4.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 nealeasa 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
7

http://www.sgi.com/tech/stl/Iterators.html

68

critica n graful partial Gk = G {e1 , e2 , . . . , ek } (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 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 27).
Algorithm 27 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 critica n graful
6:
7:
8:
9:
10:

Gk = G {e1 , e2 , . . . , ek }
Lk+1 [Lk , ek+1 , vk+1 ]
k k+1
end while
return Lk
end function

Exemplul 4.7 S
a aplicam algoritmul 27 pentru graful din figura 4.7. La nceput L0 = [u1 ].
Apoi se intra n ciclul while (liniile 4 8): tabelul urmator indica muchia ce a fost aleas
a la
fiecare pas k, precum si configuratia lantului Lk .
Pasul k
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19

Muchia aleas
a
[u1 u4 ]
[u4 u10 ]
[u10 u3 ]
[u3 u2 ]
[u2 u6 ]
[u6 u1 ]
[u1 u5 ]
[u5 u6 ]
[u6 u7 ]
[u7 u2 ]
[u2 u8 ]
[u8 u3 ]
[u3 u9 ]
[u9 u8 ]
[u8 u7 ]
[u7 u13 ]
[u13 u5 ]
[u5 u12 ]
[u12 u4 ]

Lk
L1 = [u1 , [u1 u4 ], u4 ]
L2 = [u1 , [u1 u4 ], u4 , [u4 u10 ], u10 ]
L3 = [u1 , [u1 u4 ], u4 , [u4 u10 ], u10 , [u10 u3 ], u3 ]
L4 = [. . . , [u10 u3 ], u3 , [u3 , u2 ], u2 ]
L5 = [. . . , u2 , [u2 u6 ], u6 ]
L6 = [. . . , u6 , [u6 u1 ], u1 ]
L7 = [. . . , u1 , [u1 u5 ], u5 ]
L8 = [. . . , u5 , [u5 u6 ], u6 ]
L9 = [. . . , u6 , [u6 u7 ], u7 ]
L10 = [. . . , u7 , [u7 u2 ], u2 ]
L11 = [. . . , u2 , [u2 u8 ], u8 ]
L12 = [. . . , u8 , [u8 u3 ], u3 ]
L13 = [. . . , u3 , [u3 u9 ], u9 ]
L14 = [. . . , u9 , [u9 u8 ], u8 ]
L15 = [. . . , u8 , [u8 u7 ], u7 ]
L16 = [. . . , u7 , [u7 u13 ], u13 ]
L17 = [. . . , u13 , [u13 u5 ], u5 ]
L18 = [. . . , u5 , [u5 u12 ], u12 ]
L19 = [. . . , u12 , [u12 u4 ], u4 ]

69

Pasul k
20
21
22
23
24
25
26

Muchia aleas
a
[u4 u11 ]
[u11 u10 ]
[u10 u9 ]
[u9 u13 ]
[u13 u11 ]
[u11 u12 ]
[u12 u1 ]

Lk
L20 = [. . . , u4 , [u4 u11 ], u11 ]
L21 = [. . . , u11 , [u11 u10 ], u10 ]
L22 = [. . . , u10 , [u10 u9 ], u9 ]
L23 = [. . . , u9 , [u9 u13 ], u13 ]
L24 = [. . . , u13 , [u13 u11 ], u11 ]
L25 = [. . . , u11 , [u11 u12 ], u12 ]
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).

4.2

Grafuri Hamiltoniene

Definitia 4.2 Se numeste lant Hamilton sau lant hamiltonian un lant L ce trece o
singura data prin toate varfurile unui graf.
Definitia 4.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 4.8 Fie G = (V, E) un graf neorientat si fie u si v doua varfuri neadiacente ale grafului
(u, v V , u 6= v, [u, v]
/ E), astfel nc
at dG (u) + dG (v) n. Atunci G este hamiltonian
G + [u, v] este hamiltonian.
Demonstratie: Daca G este hamiltonian, atunci cu atat mai mult, G + [u, v]
este hamiltonian.
Presupunem ca G + [u, v] este un graf hamiltonian. Atunci exista un ciclu hamiltonian n G + [u, v] pe care l notam cu C.
1. daca [u, v]
/ C atunci C este un ciclu hamiltonian si n 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 hamiltonian 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 4.9 (Dirac, 1952) Un graf G = (V, E) (|V | 3) este hamiltonian daca u V
at jumatate din numarul de
avem dG (u) n2 (orice varf al grafului are gradul mai mare dec
varfuri din graf ).
70

Teorema 4.10 (Ore, 1961) Un graf G = (V, E) (|V | 3) este hamiltonian daca u, v V
avem dG (u) + dG (v) n unde u 6= v, [u, v]
/ E (pentru oricare doua varfuri dinstincte,
neadiacente, ale grafului suma gradelor lor este mai mare dec
at numarul de varfuri din graf ).
Teorema 4.11 (Chvatal, 1972) Fie un graf G = (V, E) (|V | 3) si d1 , d2 , . . . , dn o
secvent
a grafic
a. Daca este satisfacut
a relatia
k a.i. dk k

n
dnk n k
2

atunci graful este hamiltonian.


Definitia 4.4 Pentru un graf G construim un sir de grafuri G = G1 , G2 , . . . astfel: graful
Gk+1 se obtine din graful Gk prin adaugarea muchiei [uk , vk ], unde varfurile uk , vk V nu
sunt adiacente n Gk si dG (uk ) + dG (vk ) n. Procesul se ncheie n momentul n care nu mai
exista doua varfuri neadiacente distincte astfel nc
at dG (up ) + dG (vp ) n. Gp se numeste
nchiderea lui G si se noteaz
a cu cl(G).
Lema 4.12 Orice graf prezint
a o singura nchidere.
Corolarul 4.13 Un graf G = (V, E) (|V | 3) este hamiltonian daca cl(G) u Kn (dac
a
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 }.
Corolarul 4.14 Un graf G = (V, E) (|V | 3) este hamiltonian daca (G) n2 .
Definitia 4.5 O multime de varfuri A a unui graf G se spune ca este independent
a daca
oricare doua elemente distincte din A sunt independente. Numarul de independent
a al lui G,
notat cu (G), reprezint
a numarul maxim de varfuri dintro multime independent
a.
(G) = 1 daca si numai daca graful G este complet.
Definitia 4.6 Pentru un graf G se numeste conectivitatea lui G, si notam cu (G), numarul
minim de varfuri ale unei taieturi. O taietur
a este o submultime U a lui V astfel nc
at G U
sa fie neconex.
Teorema 4.15 Un graf G = (V, E) av
and ordinul n 3, este hamiltonian daca (G)
(G).
Observatia 4.16 Pentru un graf bipartit G = (V1 , V2 , E) conditia necesar
a pentru a fi hamiltonian este |V1 | = |V2 |.
Problema comisvoiajorului include problema determinarii existentei unui ciclu hamiltonian 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
cicluri hamiltoniene distincte.
Kn avem (n1)!
2
Exemplul 4.17 Graful din figura 3.20 nu este hamiltonian (nu admite un ciclu hamiltonian).
71

Graful din figura 4.6 are mai multe cicluri hamiltoniene: de exemplu C1 = [1, 5, 2, 3,
6, 4, 1] si C2 = [1, 3, 6, 4, 2, 5, 1].
Nod
dG

1
4

2
4

3
4

4 5
4 2

6
2

Teorema lui Dirac (1952) nu se poate aplica deoarece nu este ndeplinit


a conditia orice varf
al grafului are gradul mai mare dec
at jumatate din numarul de varfuri din graf:
dG (5) = 2 <

n
6
= = 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.
Pentru graful din figura 4.8 avem:
Nod
dG

1
3

2
4

3
3

4 5
4 4

6
5

7
3

8
4

conditia teoremei lui Dirac (1952), nu este ndeplinit


a: dG (1) = 3 < 4 = n2 ;
conditia teoremei lui Ore (1961), nu este ndeplinit
a: dG (2) + dG (7) = 4 + 3 = 7 < 8;
observ
am ca nici conditiile teoremei lui Chvatal (1972) nu sunt ndeplinite: fie d1 , d2 , . . . , dn
o secvent
a grafic
a.
Nod
dG

1
3

3 7
3 3

2
4

4
4

5
4

8 6
4 5

Relatia urmatoare nu este satisfacut


a:
k a.i. dk k

4.2.1

n
dnk n k.
2

Problema comisvoiajorului

Un comis-voiajor trebuie sa viziteze n orase etichetate prin 1, . . . , n. Pentru a simplifica problema, acesta va pleca ntotdeauna din orasul numarul 1 si se va ntoarce tot n 1, trec
and prin
fiecare oras o singura data (cu exceptia orasului 1 care va fi vizitat de doua ori). Cunoscand
distantele ntre orase, sa 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:

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

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 28).
72

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

(4.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 ).

Fig. 4.8: Exemplu de graf pentru problema comis-voiajorului

Exemplul 4.18 Matricea costurilor corespunz


atoare
valori:

0 14 6 5
14 0 12 16

6 0

12 0 21
A=
5 16 21 0

20 12 16

24
12 10
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].
73

grafului din figura 4.8 are urmatoarele

20
12

16
0
14
6

24

14
0
10

12

10

10
0

Algorithm 28 Algoritm pentru problema comis-voiajorului


(
A - matricea de costuri ce contine distantele dintre orase
Input:
n - numarul de orase
1: function CanContinue(A, X, k, cost)
2:
if (vizitatxk = 1) (cost + axk1 ,xk > costoptim ) ((k 6= n + 1) (xk = 1))) 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 6= true)) 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:
cost cost axk1 ,xk
34:
end if
35:
end while
optim }
36:
Output {Solutia optima: , xoptim
, . . . , xoptim
1
n+1 , cost
37: end procedure

C4 = [1, 3, 6, 7, 8, 4, 2, 5, 1].
Programul corespunzator scris n limbajul C este urmatorul:
#include <stdio.h>
#include <memory.h>
#define NMAX 100

//numarul maxim de noduri din graf

74

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

#define LIMIT 100000 //lungimea maxima a unei muchii echivalent cu infinit


typedef int Matrice[NMAX][NMAX];
typedef int Vector[NMAX];
long cost_optim; //costul circuitului hamiltonian optim
Vector x_optim; //elementele circuitului hamiltonian optim
/**
* Functia citeste valorile datelor de intrare.
*/
int readInput(Matrice a) {
int n;
int i, j, max = 0;
scanf("%d", &n);
for (i = 1; i <= n; i++)
for (j = 1; j <= n; j++) {
scanf("%d", &a[i][j]);
if (max < a[i][j])
max = a[i][j];
if (a[i][j] < 0)
a[i][j] = LIMIT;
}
cost_optim = n * max;
return n;
}
/**
* Functia verifica daca a + b > c.
*/
int mai_mare(long a, long b, long c) {
if (a + b > c)
return 1;
else

75

return 0;
}
/**
* Functia afiseaza solutia calculata a problemei.
*/
void AfisareSolutie(int n) {
int i;
printf("Costul ciclului optim: %ld \n", cost_optim);
printf("Ciclu hamiltonian: [");
for (i = 1; i < n+1; i++)
printf("%d, ", x_optim[i]);
printf("%d]\n", x_optim[n+1]);
}
/**
* Functia de continuare: aici se verifica daca elementul de pe pozitia k
* nu se mai afla in sirul A.
*/
int CanContinue(int n, Matrice a, int k, long cost, Vector vizitat, Vector x) {
if ((vizitat[x[k]] == 1) || (mai_mare(cost, a[x[k-1]][x[k]], cost_optim))
|| ((k != n+1) && (x[k] == 1)))
return 0;
else
return 1;
}
/**
* Functia salveaza solutia optima (circuitul hamiltonian de cost minim)
* determinata/gasita pana in momentul curent.
*/
void EvaluareSolutie(int n, long cost, Vector x) {
int i;
cost_optim = cost;
for (i = 1; i <= n+1; i++)
x_optim[i] = x[i];
AfisareSolutie(n);
}
/**
* Metoda backtracking implementata.
*/
void ComisVoiajorBacktracking(int n, Matrice a) {
Vector x;
Vector vizitat;
int k, gasit;
long cost;
memset(vizitat, 0, sizeof(vizitat));
x[1] = 1;

76

cost = 0;
k = 2; x[k] = 1;
while (k > 1) {
gasit = 0;
while ((x[k] + 1 <= n) && (gasit == 0)) {
x[k] = x[k] + 1;
gasit = CanContinue(n, a, k, cost, vizitat, x);
}
if (gasit == 1) {
if (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;
cost = cost - a[x[k-1]][x[k]];
}
}
}
void main(void) {
Matrice a;
int n = readInput(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:
...
scanf("%d", &a[i][j]);
if (max < a[i][j])
max = a[i][j];
if (a[i][j] < 0)
a[i][j] = LIMIT;

77

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 n max.
Functia n care se verifica conditiile de continuare este:
int CanContinue(int n, Matrice a, int k, long cost, Vector vizitat, Vector x) {
if ((vizitat[x[k]] == 1) || (mai_mare(cost, a[x[k-1]][x[k]], cost_optim))
|| ((k != n+1) && (x[k] == 1)))
return 0;
else
return 1;
}

Folosim o functie (mai mare()) cu scopul de a realiza verificarea cost + axk1 ,xk +1 >
costoptim :
int 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 EvaluareSolutie(int n, long cost, Vector x) {
int i;
cost_optim = cost;
for (i = 1; i <= n+1; i++)
x_optim[i] = x[i];
AfisareSolutie(n);
}

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


void ComisVoiajorBacktracking(int n, Matrice a) {
Vector x;
Vector vizitat;
int k, gasit;
long cost;
memset(vizitat, 0, sizeof(vizitat));
x[1] = 1;
cost = 0;
k = 2; x[k] = 1;
while (k > 1) {
gasit = 0;
while ((x[k] + 1 <= n) && (gasit == 0)) {
x[k] = x[k] + 1;
gasit = CanContinue(n, a, k, cost, vizitat, x);
8

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

78

}
if (gasit == 1) {
if (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;
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
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;
cost = cost - a[x[k-1]][x[k]];

adica la pasul k = k 1 = 9 1 = 8.

79

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.

80

Fig. 4.10: Pasii efectuati de algoritm pentru determinarea ciclului hamiltonian asociat grafului din figura 4.8

81

Fig. 4.11: Pasii efectuati de algoritm pentru determinarea ciclului hamiltonian asociat grafului din figura 4.8
(continuare)

82

Capitolul 5
Arbori. Arbori binari
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 special
a, numit rad
acina arborelui;
2. celelalte noduri sunt repartizate n n multimi distincte, disjuncte doua cate doua, 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 varf al unui arbore cu radacina constituie radacina unui subarbore compus din varful
respectiv si toti descendentii sai.
Se observa ca un arbore impune o structura de organizare ierarhica asupra elementelor
unei multimi.
Definitia 5.2 Se numeste arbore liber (free tree) un graf G = (V, E) neorientat, conex
si aciclic.
Teorema 5.1 (Propriet
atile arborilor liberi) Fie G = (V, E) un graf neorientat. Urmatoarele
afirmatii sunt adevarate:
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 adaug
am o muchie oarecare n E, graful obtinut contine un ciclu.
Daca se alege un varf sau nod drept rad
acina arborelui, atunci un arbore liber devine

arbore cu rad
acin
a. In general vom folosi termenul de arbore n loc de termenul corect arbore
cu rad
acin
a.
Un v
arf 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.

83

10

12

11

13

14

15

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

Definitia 5.3 Ad
ancimea unui varf este data de lungimea drumului de la rad
acin
a la acel
altimea unui varf se determina ca fiind lungimea celui mai lung drum dintre acel varf
varf. In
altimea rad
si un varf terminal. In
acinii determina n
altimea arborelui. Nivelul unui varf se
calculeaz
a ca diferenta dintre n
altimea arborelui si adancimea acestui varf.
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.

5.1

Arbori binari

Definitia 5.4 Un arbore binar este un arbore oarecare cu rad


acin
a, fiecare varf al sau
avand cel mult doi descendenti, indicandu-se cine este descendentul stang 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 informatia ca un varf are un singur descendent este
suficienta 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 5.5 Se numeste arbore binar plin un arbore binar cu 2k 1 v
arfuri asezate
pe k nivele astfel nc
at pe fiecare nivel i avem 2i1 v
arfuri. Se observa ca fiecare varf al
arborelui are doi descendenti, descendentul stang si descendentul drept, cu exceptia nodurilor
terminale.
Varfurile unui arbore plin sunt numerotate n ordinea nivelurilor, iar n cadrul unui nivel,
de la stanga la dreapta (vezi figura 5.1).
Definitia 5.6 Se numeste arbore binar complet cu n varfuri un arbore binar plin avand
k nivele, unde 2k1 n < 2k , din care se elimina varfurile numerotate n + 1, n + 2, . . . , 2k 1
(vezi figura 5.2).
Un arbore binar poate fi adus la forma unui arbore plin n modul urmator: completam
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.
84

10

12

11

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

5.1.1

Moduri de reprezentare
1

5
4

Fig. 5.3: Exemplu de arbore binar

1. expresii cu paranteze expresia ncepe cu radacina si dupa fiecare varf k urmeaza expresiile 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 N U LL. 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 5.3 avem:
1(2(3(0, 4(0, 0)), 5(6(0, 0), 7(0, 0))), 8(0, 9(0, 0)))
2. forma standard se indica radacina arborelui, iar pentru fiecare varf k se precizeaza
descendentul sau stang si/sau drept.
(
k , daca nodul k este descendentul stang al nodului i
Stangi =
0 , nu exista
(
k , daca nodul k este descendentul drept al nodului i
Drepti =
0 , nu exista
Rad = 1
Pentru arborele din figura 5.3 avem urmatoarea reprezentare:
Nod
Stang
Drept

1 2
2 3
8 5

3 4 5
0 0 6
4 0 7

6 7 8
0 0 0
0 0 9

9
0
0
85

Fig. 5.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;

N od rad; rad este o variabila de tip pointer la N od (variabila pastreaza adresa


unei zone de memorie ce contine date numerice de tip N od). rad desemneaza adresa
radacinii arborelui.
In figura 5.5 avem reprezentarea arborelui din figura 5.4.

Fig. 5.5: Exemplu de arbore binar cu 8 noduri

3. reprezentarea tip tat


a fiecarui varf k i se indica tatal nodului respectiv.
(
k , daca nodul k este parintele nodului i
tatai =
0 , nu exista
86

Nod
Tata

1 2 3
0 1 2

4 5 6
3 2 5

7 8 9
5 1 8

Observatia 5.2 Urm


atoarea numerotare este utilizata pentru anumite tipuri de arbori
binari, si are drept caracteristic
a faptul ca structura arborelui se poate deduce printr-o
numerotare adecvat
a a varfurilor:
(
b 2i c
, i2
T atai =
nu exista , i = 1
(
(
2i
, 2in
2i+1
, 2i+1n
Stangi =
Drepti =
nu exista , 2 i > n
nu exista , 2 i + 1 > n

5.1.2

Metode de parcurgere

Exista mai multe moduri de parcurgere a unui arbore binar. Indiferent de metoda de vizitare
aleasa se parcurge mai ntai subarborele stang si apoi subarborele drept. Dupa momentul n
care un nod k este vizitat fata de subarborele sa stang si drept, avem:
parcurgere n preordine (rad
acin
a, subarbore stang, subarbore drept).
In urma parcurgerii n preordine a arborelui din figura 5.3 rezulta urmatoarea ordine
pentru noduri: 1, 2, 3, 4, 5, 6, 7, 8, 9.

Fig. 5.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.

In figura 5.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
29). 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
87

(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.
Algorithm 29 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)
7:
k stangk
8:
end while
9:
call V izit(k)
10:
while (dreptk = 0) (q = 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

. prelucrarea informatiei din nodul vizitat

Aceasta procedura poate fi utilizata si pentru celelalte moduri de parcurgere, modificarile


necesare referinduse la momentul cand trebuie vizitat nodul curent (prin apelul procedurii V izit - instructiunea call V izit(k)).
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 */

88

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]);
}
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];
}
vizit(i);
while (drept[i] == 0 && q == 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;

89

}
}
if (q == 0)
i = drept[i];
}
}
void main(void){
readInput();
preord(rad);
}

parcurgere n inordine (arbore stang, rad


acin
a, arbore drept): 3, 4, 2, 6, 5, 7, 1, 8, 9.
Metoda de parcurgere in inordine este ilustrata de algoritmul 30. 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). Vizitarea se termina n momentul
n care stiva este vida (vezi algoritmul 30).
Algorithm 30 Algoritm de parcurgere n inordine
1: procedure Inordine(Rad, Stang, Drept)
2:
k rad
3:
q0
4:
S
5:
while (q = 0) do
6:
while (stangk 6= 0) do
7:
Sk
8:
k stangk
9:
end while
10:
call V izit(k)
11:
while (dreptk = 0) (q = 0) do
12:
if (S = ) then
13:
q1
14:
else
15:
Sk
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

. initializare stiva vida

. S.push(k)

. verificare daca stiva este vida

. S.pop(k)

Implementarea n limbajul C a algoritmului 30 este:


#include <stdio.h>

90

#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) {
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) {
cap++;
stack[cap] = i;
i = stang[i];
}
vizit(i);
while ((drept[i] == 0) && (q == 0)) {
if (cap < 0)
q = 1;
else {
i = stack[cap];
cap--;
vizit(i);

91

}
}
if (q == 0)
i = drept[i];
}
}
void main(void) {
readInput();
inord(rad);
}

parcurgere n postordine (subarbore stang, subarbore drept, rad


acin
a): 4, 3, 6, 7, 5, 2, 9, 8, 1
(vezi algoritmul 31).
Algorithm 31 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.
1

5
6

Fig. 5.7: Vizitarea unui arbore pe frontiera

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 5.7).
T
inand cont de momentul vizitarii se poate obtine oricare dintre ordinele de vizitare (preordine, inordine, postordine): pentru preordine vom marca un nod n momentul n care l
92

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

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.
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 5.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.

5.2

Arbori binari de c
autare

Definitia 5.7 [93] Se numeste arbore binar de c


autare un arbore binar ce verifica urmatoarea
proprietate:
pentru orice varf i apartin
and 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
unde Infi este informatia asociat
a 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.
93

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 32):
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.
Algorithm 32 Algoritm de cautare a unei valori ntr-un abore 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

94

Inserarea unui nod ntr-un arbore de c


autare
Aceasta operatie prezinta urmatoarele particularitati (vezi algoritmul 33):
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.
Algorithm 33 Algoritm de inserare ntr-un abore 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 35):
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 34); acesta va fi sters n mod fizic, dar la un pas
ulterior;
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;
95

Fig. 5.9: Arbore binar de cautare. Inserarea unui nod.

se va sterge fizic nodul B.


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

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

typedef struct nod {


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

96

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

97

Algorithm 34 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

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;
if (p == NULL)
p = CreareNod(sir);
else {
ind = strcmp(p->cuvint, sir);
if (ind < 0)

98

Algorithm 35 Algoritm de stergere a unui nod ntr-un abore 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

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);
VisitInord(p->right);
}
}
NOD* LeftmostNod(NOD* parent, NOD* curent) {
while (curent->left != NULL) {

99

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;
}
}
return p;
}
void main(void) {

100

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);
}
}
}

Exercitii:
1. 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.
101

Fig. 5.11: Exemplu de arbore binar complet cu 7 varfuri

Observatia 5.4 O expresie va avea maxim 9 nivele de parantezare. Numarul de reduceri ce pot fi efectuate este finit. Liniile de iesire contin maxim 80 caractere. Caracterele
posibile din termenii de iesire sunt tot X,( si ).
Intrare
(((XX)X)X)
(((XX)(XX)X)
(XX)

Iesire
((XX)(XX))
((XX)((XX)X))
(XX)

(ACM, Bucuresti, 1996)

2. 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.
Pentru arborele din figura 5.11 drumurile de
astfel:
ABC :
ABDE :
ABDF :
AG :

la radacina la fiecare frunza se codifica


00
010
011
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)

3. Scrieti o subrutina nerecursiva care numara nodurile frunza ale unui arbore binar.
4. Realizati o subrutina care sa determine nivelul cu cele mai multe noduri frunze dintrun
arbore binar.
5. Determinati natimea unui arbore binar printro functie nerecursiva.
102

6. 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
4
2
1
2

abroad in_strainatate
baker brutar
calf gamba
dice zaruri
ear ureche

7. 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.
8. Se considera o expresie logica formata din n variabile logice reprezentate printro singura 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.
9. 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.

103

Capitolul 6
Arbori oarecare
Reamintim definitia unui arbore oarecare:
Definitia 6.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 special
a, numit rad
acina arborelui;
2. celelalte noduri sunt repartizate n n multimi disjuncte doua cate doua, A1 , A2 , , An , fiecare
multime Ai constituind la randul ei un arbore.

6.1

Moduri de reprezentare

Pentru reprezentarea arborilor oarecare pot fi utilizate urmatoarele metode:


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

10

Fig. 6.1: Exemplu de arbore oarecare

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


Nod 1 2 3
Fiu 2 0 5
Frate 0 3 4

4 5 6 7
7 0 0 0
0 6 0 8

104

8 9
0 0
9 10

10
0
0

Identificand 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 (N od rad;) este o variabila de


tip pointer la N od (variabila pastreaza adresa unei zone de memorie). rad desemneaza
adresa radacinii arborelui.
rad
1

NULL

NULL

NULL
5
NULL

6
NULL

NULL
NULL

8
NULL

NULL

10

NULL

NULL

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

In figura 6.2 este reprezentat arborele din figura 6.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 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
105

Table 6.2: Reprezentarea arborelui din figura 6.1 folosind liste cu descendenti.
Nod
C

1 2 3
1 0 4

2 3 4
L=
2 3 0

4 5 6
6 0 0
5 6 7
5 0 7

7
0
8
8

8
0
9
9

9 10
0 0

10
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

10

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

In figura 6.3 este reprezentat arborele din figura 6.1 folosind liste cu descendenti.
106

Tata - fiecarui nod k se indica nodul parinte.


Table 6.3: Reprezentarea arborelui din figura 6.1 folosind vectorul tata.
Nod 1 2 3
Tata 0 1 1

4 5 6
1 3 3

7 8 9
4 4 4

10
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
struct nod*
struct nod*
struct nod*
}Nod;

rad

data;
fiu;
tata;
frate;

NULL
1

NULL

NULL
2

NULL

NULL
5

NULL

NULL

NULL

10

NULL

NULL

NULL

NULL

NULL

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

In figura 6.4, reprezentarea din figura 6.2 este mbunatatita prin leg
atura tata.

6.2

Metode de parcurgere

Pentru parcurgerea unui arbore oarecare se pot folosi metodele generale pentru parcurgerea 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:
107

Apreordine: se viziteaza mai ntai radacina, si apoi, n ordine, subarborii sai [93],
[136]. 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 36). Parcurgerea n Apreordine este
exemplificata pe arborele oarecare din figura 6.1.
Algorithm 36 Algoritm de parcurgere n A-preordine
1: procedure APreordine(Rad, F iu, F rate)
2:
k rad
3:
q0
4:
S
5:
while (q = 0) do
6:
if (k 6= 0) then
7:
call V izit(k)
8:
Sk
9:
k f iuk
10:
else
11:
if (S = ) then
12:
q1
13:
else
14:
Sk
15:
k f ratek
16:
end if
17:
end if
18:
end while
19:
return
20: end procedure

. initializare stiva vida

. S.push(k)

. verificare daca stiva este vida

. S.pop(k)

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


radacinii arborelui si apoi radacina arborelui [93], [136]. In urma parcurgerii n A
postordine a arborelui din figura 6.1 se obtine 2, 5, 6, 3, 7, 8, 9, 10, 4, 1.
continuare se prezint
Exemplul 6.1 [37] In
a implementarea n limbajul de programare C
a variantelor recursive pentru fiecare dintre cele doua 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;

108

/**
* 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);

109

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)) {

110

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);
}

111

6.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).
Problema care se pune este sa se determine un arbore T 0 TG cu proprietatea ca
c(T 0 ) = min{c(T )|T TG }
P
unde c(T ) = eET c(e). T 0 se numeste arbore de acoperire de cost minim (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 exemplu, 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 6.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 singura extremitate n S. Atunci arborele partial
de cost minim va contine muchia e.
Demonstratie: Fie T 0 un arbore partial de cost minim. Presupunem prin reducere la
absurd ca e
/ ET 0 . Daca adaugam muchia e la T 0 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 0 , obtinem un alt arbore de acoperire T 00 =
T 0 {e} \ {f }.
Deoarece c(e) < c(f ) vom avea ca c(T 00 ) = c(T 0 ) + c(e) c(f ) c(T 00 ) < c(T 0 ) adica am
| {z }
<0

obtinut un arbore de acoperire T 00 ce are costul mai mic decat T 0 , contradictie cu faptul ca
T 0 este un arbore de acoperire de cost minim.

Definitia 6.2 Se numeste t


aietur
a a grafului G o partitie de doua submultimi a multimii
nodurilor V notata astfel: < S, T > (unde S T = V , S T = ).
Lema 6.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 0 nu contine muchia
f.
Demonstratie: Fie T 0 un arbore partial de cost minim. Presupunem prin reducere la
absurd ca f ET 0 . Daca stergem muchia f din arborele T 0 , 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 0 , obtinem un alt arbore de acoperire T 00 =
T 0 {e} \ {f }.
Deoarece c(e) < c(f ) c(T 00 ) < c(T 0 ) adica am obtinut un arbore de acoperire T 00 ce are
costul mai mic decat T 0 , contradictie.

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 1 = {T11 , T21 , . . . , Tn1 }
112

unde Ti1 = {xi } este un arbore format dintrun singur nod. La pasul k vom avea T k =
{T1k , T2k , . . . , Tnk }.
Se alege un arbore Tik si muchia (u0 , v 0 ) de cost minim dintre toate muchiile (u, v) avand
proprietatea ca o extremitate apartine arborelui Tik (u0 Tik ) si cealalta apartine lui V \ VTik
(v 0 V \ VTik ).
T k+1 se obtine din T k prin reuniunea arborilor Tik si Tjk (i 6= j), unde u0 Tik si v 0 Tjk .
La pasul n 1, multimea T n1 = {T1n1 } va fi compusa dintrun singur element, 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 37)
Algorithm 37 Algoritmul lui Boruvka (varianta schematica)
1: procedure Boruvka1(G, C, n; L)
2:
initializeaza padurea de arbori P compus
a din n arbori, fiecare arbore fiind compus dintrun
3:
4:
5:
6:
7:
8:
9:
10:
11:

singur nod
L
while (|P| > 1) do
for T P do
alege e muchia de cost minim de la T la G \ T
L e . adauga muchia e la lista de muchii alese ce vor forma arborele de acoperire
de cost minim
end for
adauga toate muchiile selectate n cadrul f or-ului anterior la P
end while
end procedure

2. Algoritmul lui Prim (vezi algoritmul 38)


Algorithm 38 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

3. Algoritmul lui Kruskal (vezi algoritmul 39)


113

Algorithm 39 Algoritmul lui Kruskal (varianta schematica)


1: procedure Kruskal1(G, C, n; L)
2:
ordoneaza muchiile n ordine crescatoare dupa cost
3:
L
4:
for each u V do
5:
creaza o multime compusa 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 )[35] si atinge o complexitate de O(m log n) daca se folosesc heapuri Fibonacci [56], sau pairing heaps [128].
In tabelul 6.4 sunt prezentati mai multi algoritmi dezvoltati dea lungul timpului pentru
determinarea arborelui de acoperire minimal si complexitatile lor. Karger, Klein si Tarjan [82]
pornind de la algoritmul lui Boruvka au realizat un algoritm randomizat pentru determinarea
arborelui de acoperire minimal, avand o complexitate liniara, iar Chazelle [32] a dezvoltat
un algoritm avand complexitatea O(n(m, n)) ((m, n) este inversa functiei lui Ackerman).
Pe de alta parte, Pettie si Ramachandran [110] au propus un algoritm demonstrat ca fiind
optimal, avand complexitatea cuprinsa ntre O(n + m) si O(n(m, n)).
Table 6.4: Algoritmi pentru determinarea arborelui de acoperire minim
Anul
Complexitate
1975
E log log V
1976
E log log V
1984 E log V, E + V log V
1986
E log log V
1997
E(V ) log (V )
2000
E(V )
2002
optimal

Autori
Yao
Cheriton-Tarjan
Friedman-Tarjan
Gabow-Galil-Spencer-Tarjan
Chazelle
Chazelle [32]
Pettie-Ramachandran [110]

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:

, daca i = j
0
ci,j =
, daca (i, j)
/E

d > 0 , daca (i, j) E

114

6.3.1

Algoritmul lui Boruvka

Algoritmul lui Boruvka [78] a fost descoperit de catre matematicianul ceh Otakar Boruvka
n 1926 [26], 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 40).
Algorithm 40 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:
fie (u, v) muchia pentru care se obtine valoarea minima min{c(u0 , v 0 )|(u0 , v 0 ) E, u0
9:
10:
11:
12:
13:
14:
15:
16:

U, v 0
/ V \ U}
determina componenta U 0 ce contine pe v
L (u, v)
end for
for U M do
reuneste multimile ce contin pe u si v, U si U 0
end for
end while
end procedure

Exemplul 6.4 S
a consider
am graful din figura 6.5:
G = (V, E), V = {1, 2, 3, 4, 5, 6, 7, 8}
Aplicand algoritmul lui Boruvka, la pasul nt
ai 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 ram
ane 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 component
a conex
a.

6.3.2

Algoritmul lui Prim

Algoritmul a fost descoperit mai ntai de V. Jarnik (1930) [77], si apoi independent de Prim
(1957) [113] si Djikstra (1959) [44].
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 5.1 rezulta faptul ca acest
graf partial aciclic cu n 1 muchii este un arbore (de acoperire).
Conform Propriet
atii 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 41):
115

Fig. 6.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 = u
tatak =
u , daca nodul k 6= u
In momentul n care se modifica dj , se va modifica si valoarea lui tataj = k.

Fig. 6.6: Exemplu de graf ponderat - aplicatie algoritmul lui Prim

116

Algorithm 41 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 6.5 Fie graful din figura 6.6:


G = (V, E), V = {1, 2, 3, 4, 5, 6, 7, 8}
Vom lua nodul initial v0
1 2 3
d
14 6
tata 0 1 1
vizitat 1 0 0

= 1. La nceput,
4 5 6
7
5
1 1 1
1
0 0 0
0

dupa etapa de initializare avem:


8

1
0
117

Dup
a primul pas
1 2
d
14
tata 0 1
vizitat 1 0

al ciclului, select
am muchia (1, 5).
3 4 5 6
7
8
6 21 5 16
1 5 1 5
1
1
0 0 1 0
0
0

La pasul al doilea se
1 2 3
d
14 6
tata 0 1 1
vizitat 1 0 1

alege nodul 3 si muchia (1, 3):


4 5 6
7
8
21 5 12 12
5 1 3
1
3
0 1 0
0
0

La pasul al treilea avem doua noduri ale caror 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
1 2 3
d
14 6
tata 0 1 1
vizitat 1 0 1

este
4
10
8
0

8:
5
5
1
1

6
12
3
1

7
10
8
0

8
6
6
1

La pasul cinci, nodul aflat la


1 2 3 4 5
d
14 6 10 5
tata 0 1 1 8 1
vizitat 1 0 1 0 1

distant
a
6
7
12 10
3
8
1
1

minima este 7:
8
6
6
1

La pasul sase este ales nodul


1 2 3 4 5
d
12 6 10 5
tata 0 4 1 8 1
vizitat 1 0 1 1 1

4:
6
12
3
1

8
6
6
1

7
10
8
1

La final, ultimul nod ales este 2 mpreun


a cu muchia (4, 2).
Trebuie sa remarcam faptul ca algoritmul lui Prim (vezi algoritmul 41) este aproape
identic cu algoritmul lui Dijkstra (vezi algoritmul 56).
Dupa cum am subliniat, implementarea optima se realizeaza folosind niste structuri de
date avansate - heapuri Fibonacci sau pairing heaps (vezi algoritmul 42).

6.3.3

Structuri de date pentru multimi disjuncte

O partitie a unei multimi A este o secventa finita de multimi (submultimi) A1 , . . . , Am


disjuncte
S doua cate doua, cu proprietatea ca reuniunea acestora este chiar multimea A
si Ai Aj = , i, j = 1, m, i 6= j).
(A = m
i=1 Ai
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.
118

Algorithm 42 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
. initializeaz
a coada cu prioritate Q cu multimea vida
6:
for fiecare v V do
7:
Qv
8:
end for
9:
S
. initializeaz
a multimea S cu multimea vida
10:
while Q 6= do
11:
u deleteM in(Q)
. extrage nodul de prioritate minima 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 [35]. Operatiile de baza
ale acestei structuri de date sunt [3]:
init(x, B) - procedura creaza o multime B formata dintrun singur element x;
f ind(x) - ntoarce reprezentantul multimii careia i apartine x;
merge(x, y) - 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 M erge se cauta toate elementele ce
fac parte din multimea de identificator cy si se trec n multimea al carui identificator este cx
(vezi algoritmul 43).
Exemplul 6.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]. Daca acest lucru nu
este posibil atunci folosim un vector auxiliar A ce pastreaz
a valorile elementelor, n cadrul
reprezent
arii utilizanduse indicele acestora. Sa presupunem ca 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:
119

Algorithm 43 Algoritmi pentru operatiile init, find, merge (varianta ntai)


1:
2:
3:
4:
5:
6:
7:
8:
9:
10:
11:
12:
13:
14:
15:
16:

procedure Init(x, u)
cx u
end procedure
function Find(x)
return cx
end function
function Merge(x, y)
setx cx
sety cy
for k 1, n do
if (ck = sety) then
ck setx
end if
end for
return setx
end function

A
C

1
a
1

2
b
2

3
e
3

4
x
1

5
y
1

6
z
2

7
u
2

8
v
3

a2 =0 b0 are semnificatia urmatoare: elementul de pe pozitia 2 are valoarea 0 b0 . c2 = 2 elementul de pe pozitia 2 face parte din multimea de identificator 2. Sau a4 =0 x0 - elementul
de pe pozitia 4 are valoarea 0 x0 , 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 44).
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 N U LL.
Functia M erge 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).

120

Algorithm 44 Algoritmi pentru operatiile init, find, merge (varianta a II-a)


1:
2:
3:
4:
5:
6:
7:
8:
9:
10:
11:
12:
13:
14:
15:
16:
17:
18:
19:
20:
21:
22:
23:
24:
25:
26:
27:

procedure Init(x, k)
Listk new node
Listk .data x
Listk .next N U LL
end procedure
function Find(x)
for i 1, m do
p Listi , reprezentant p
while (p 6= N U LL) (p.data 6= x) do
p p.next
end while
if (p 6= N U LL) then
return reprezentant
end if
end for
return N U LL
end function
function Merge(x, y)
capx F ind(x)
capy F ind(y)
curent capx
while (curent.next 6= N U LL) do
curent curent.next
end while
curent.next capy
return capx
end function

. aloca spatiu pentru un nod al listei

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 45) faptul ca arborele obtinut n urma unor
operatii repetate M erge 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. num
arul 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 .

121

Algorithm 45 Algoritmi pentru operatiile init, find, merge (varianta a III-a)


1:
2:
3:
4:
5:
6:
7:
8:
9:
10:
11:
12:
13:
14:
15:

procedure Init(x)
tatax x
end procedure
function Find(x)
while (x 6= tatax ) do
x tatax
end while
return x
end function
function Merge(x, y)
radx F ind(x)
rady F ind(y)
tatarady radx
return radx
end function

1:
2:
3:
4:
5:
6:
7:
8:
9:

1: function Merge(x, y)
2:
radx F ind(x)
3:
rady F ind(y)
4:
size |tataradx | + |tatarady |
5:
if (|tataradx | > |tatarady |) then
6:
tatarady radx
7:
tataradx size
8:
return radx
9:
else
10:
tataradx rady
11:
tatarady size
12:
return rady
13:
end if
14: end function

procedure Init(x)
tatax 1
end procedure
function Find(x)
while (tatax > 0) do
x tatax
end while
return x
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. n
altimea fiec
arui 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.

122

1:
2:
3:
4:
5:
6:
7:
8:
9:
10:

1: function Merge(x, y)
2:
radx F ind(x)
3:
rady F ind(y)
4:
if (hradx > hrady ) then
5:
tatarady radx
6:
return radx
7:
else
8:
tataradx rady
9:
if (tataradx = tatarady ) then
10:
hrady hrady + 1
11:
end if
12:
return rady
13:
end if
14: end function

procedure Init(x)
tatax x
hx 0
end procedure
function Find(x)
while (tatax 6= x) do
x tatax
end while
return x
end function

Cea dea doua tehnica utilizata pentru a reduce complexitatea timp a operatiilor F ind si
M erge 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

6.3.4

Algoritmul lui Kruskal

Algoritmul lui Kruskal [90] este o ilustrare foarte buna a metodei generale Greedy (vezi
algoritmul 46). 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.

123

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.
Algorithm 46 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 6.7 S
a consider
am graful din figura 6.6. Muchiile grafului si costurile lor sunt
(am ales sa reprezent
am 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
1 1 1 1 2
2
2
3 3
4 4
4 5
6
6
7
2 2 3 5 4
5
6
6 8
5 7
8 6
7
8
8
3 14 6 5 12 16 20 12 12 21 24 10 16 14 6 10
Dup
a asezarea n ordine cresc
atoare a muchiilor dupa cost avem:
A 1 2 3 4
5
6 7
8 9 10 11 12 13 14 15
1 1 1 6 4
7
2 3
3 1
6
2 5
2
4 4
2 5 3 8 8
8
4 6
8 2
7
5 6
6
5 7
3 5 6 6 10 10 12 12 12 14 14 16 16 20 21 24
figura 6.7, sunt ilustrati pasii algoritmului lui Kruskal aplicat pe graful considerat. La
In
nceput se initializeaz
a padurea de arbori, fiecare arbore fiind alcatuit dintrun singur nod.
La pasul nt
ai evaluam muchia (1, 5), si deoarece cele doua extremit
ati fac parte din arbori
distincti, vom selecta aceast
a muchie. Reunim arborii din care fac parte cele doua extremit
ati,
L = {(1, 5)} (vezi figura 6.7 (1)).
124

Fig. 6.7: Algoritmului lui Kruskal exemplificat pe graful din figura 6.6

La pasul al doilea, luam n considerare muchia (1, 3) si multimea muchilor selectate devine
L = {(1, 5), (1, 3)} (vezi figura 6.7 (2)).
La pasul al patrulea, avem muchia (4, 8) avand costul 10. 4 face parte din arborele de
rad
acin
a 4 iar 8 face parte din arborele de rad
acin
a 6. Deoarece extremit
atile muchiei sunt
amplasate n arbori distincti select
am muchia curent
a pentru arborele partial de cost minim,
urma reuniunii arborilor corespunz
L = {(1, 5), (1, 3), (6, 8), (4, 8)}. In
atori celor doua noduri
se obtine configuratia din figura 6.7 (4).
Subliniem faptul ca arborii reprezentati n figura 6.7 sunt diferiti de arborii ce conduc la obtinerea solutiei problemei: arborii din figura constituie suportul necesar pentru
reprezentarea structurii de date pentru multimi disjuncte, ce este utilizata pentru efectu125

area eficienta a operatiilor F ind si M erge. Acest lucru justifica faptul ca desi la pasul al
patrulea select
am 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). Singura leg
atur
a 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 (2, 4): L = {(1, 5), (1, 3), (6, 8),
(4, 8), (7, 8), (2, 4)}.
La pasul al saptelea avem muchia (3, 6). Nodurile 3 si 6 fac parte din arbori diferiti, astfel
nc
at muchia curent
a poate fi selectat
a pentru solutia problemei, L = {(1, 5), (1, 3), (6, 8),
(4, 8), (7, 8), (2, 4), (3, 6)}.

6.4

Exercitii

1. 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
1123113

Iesire
DA
1 2 3 4
2 5 6
3 7
4
5
6
7

(Timisoara-pregatire, 1996)

2. 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.
3. 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.
4. 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 posibile ale tuturor componentelor, precum si lungimile lor. Dintre toate modalitatile de
interconectare posibile se cere cea corespunzatoare arborelui de acoperire minim (interconectarea pentru care suma tuturor circuitelor imprimate are lungimea minima).
Datele de intrare sunt compune 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.

126

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 6.8 Pentru datele de intrare
5 7
1 2 40
0 0

2 3 35

1 5 27

1 4 37

4 5 30

2 4 58

3 4 60

vom obtine rezultatul


Cazul 1: [1 5] [4 5] [2 3] [1 2]
Interconectarea de cost minim are valoarea 132

Vezi si figura 6.8.

Fig. 6.8: Descrierea conexiunilor posibile dintre componentele unei placi electronice

5. Scrieti o subrutina nerecursiva care sa determine cheia minima dintrun arbore oarecare.
6. Determinati naltimea unui arbore oarecare printro functie nerecursiva.
7. Pentru asigurarea securitatii activitatilor dintrun combinat chimic sa apelat la o companie 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
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.
127

Capitolul 7
Grafuri orientate
7.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 7.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 7.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 7.1.
Definitia 7.2 Un graf partial al unui graf orientat G = (V, E) este un graf orientat
G1 = (V, E1 ) unde E1 E.
Exemplul 7.2 Pentru graful G din exemplul anterior (G = (V, E)), consider
am 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 7.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 7.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 7.4 Gradul exterior al unui varf d+ (x) este egal cu numarul arcelor ce au ca
extremitate initial
a pe x. Gradul interior al unui varf d (x) este egal cu numarul arcelor ce
au ca extremitate finala pe x (d+ (x) = |{(x, u)|(x, u) E, u V }|, d (x) = |{(u, x)|(u, x)
E, u V }|).

128

Fig. 7.1: Un exemplu de graf orientat

Definitia 7.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 7.6 Un drum L = [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 7.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 varful 1 la varful 8.
Definitia 7.7 Un drum L pentru care v0 = vp se numeste circuit.
Definitia 7.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 7.9 Un drum L ce contine fiecare arc exact o singura data se numeste drum Eulerian. 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 7.10 Un graf se numeste conex daca pentru orice pereche de varfuri x si y exist
a
un lant de la x la y.
Un graf orientat este complet daca oricare doua varfuri 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.

129

Fig. 7.2: Arbore de acoperire n latime pentru graful orientat din figura 7.1

7.2

Parcurgerea grafurilor

De obicei nsa, n urma vizitarii unui graf neorientat 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 7.2 este prezentat arborele de acoperire n latime corespunzator grafului din
figura 7.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 (vezi algoritmul 47). Pentru aceasta se utilizeaza doua multimi de noduri, V izitat si N eexplorat,
unde V izitat reprezinta multimea nodurilor vizitate iar N eexplorat (N eexplorat 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 ntrunul 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 n arborele de acoperire (u v).
130

Algorithm 47 Algoritm de vizitare a unui graf (model general)


1: procedure
( ParcurgereGraf(u, G)

u - varful de unde se porneste vizitarea


G - graful
V izitat {u}
N eexplorat V izitat
while (N eexplorat 6= ) do
extrage un nod x din N eexplorat
determina y, urmatorul vecin al lui x ce nu a fost vizitat
if (y = N U LL) then
elimina x din N eexplorat
else
if (y
/ V izitat) then
V izitat V izitat {y}
N eexplorat N eexplorat {y}
end if
end if
end while
end procedure

Input:
2:
3:
4:
5:
6:
7:
8:
9:
10:
11:
12:
13:
14:
15:
16:

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).
arc de traversare - arcul (u, v) este un arc de traversare daca df s(v) a fost apelat si sa
terminat nainte de apelul lui df s(u).

Fig. 7.3: Arbore de acoperire n adancime pentru graful orientat din figura 7.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
131

iar postnumv momentul n care prelucrarea nodului v sa 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 7.1 ilustreaza aceste
tipuri de arce: arc de naintare (1, 4), arce de ntoarcere (2, 1), (7, 5), arc de traversare
(5, 2), arce ale arborelui de acoperire (1, 2), (3, 5), (7, 6) (vezi figura 7.3).
Algorithm 48 Algoritm de vizitare n adancime pentru un graf orientat
1: procedure DFSNum(k, n, V ecin)

- nodul curent vizitat


k
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 7.5 Fiind dat un graf neorientat G si doua 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 traversare postnumu > postnumv ;
2. daca (u, v) este un arc de ntoarcere postnumu < postnumv .
132

Lema 7.6 Fiind dat un graf neorientat G pentru oricare doua noduri u, v G avem:
1. u este un descendent al lui v n padurea de arbori de acoperire rezultati n urma vizitarii n
adancime a grafului G intervalul [prenumu , postnumu ] este inclus n intervalul [prenumv , postnumv ];
2. nu exista nici o leg
atur
a ntre u si v n padurea 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 7.7 Fiind dat un graf neorientat G, daca pentru doua noduri oarecare u, v G avem
relatia prenumu < prenumv < postnumu atunci:
prenumu < prenumv < postnumv < postnumu .
exista un drum de la u la v n G.
urma apel
Exemplul 7.8 In
arii procedurii DF SN um(1, 8, V ecin) (vezi algoritmul 48), secventa
de apeluri recursive ale procedurii DF SN um este ilustrat
a prin arborele de acoperire n
adancime din figura 7.3.
Numerotarea nodurilor n preordine si postordine rezultat
a n urma vizitarii este urmatoarea:
1
prenum 1
postnum 16

2
2
3

3
4
15

4
8
9

5
5
14

6
6
13

7
7
12

8
10
11

Pentru arcul (1, 4) avem prenum1 = 1, postnum1 = 16, prenum4 = 8 si postnum4 = 9.


Deoarece relatia prenumu < prenumv < postnumu este adevarat
a conform lemei 7.7 ar
trebui sa avem prenumu < prenumv < postnumv < postnumu si sa 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 adancime).

7.3

Sortarea topologic
a

Definitia 7.11 Un graf orientat si care nu posed


a circuite se numeste graf orientat aciclic
(directed acyclic graph - DAG).

Fig. 7.4: Un graf orientat aciclic

133


Lema 7.9 Intrun
graf orientat aciclic, daca prenumu < prenumv si exista 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 ntro 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 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.
Algorithm 49 Algoritm de sortare topologica a unui graf orientat (prima varianta)
1: procedure
( SortTop1(n, V ecin)

n
- numarul de noduri din graf
V ecin - vector ce contine listele cu vecini ai fiecarui nod
for k 1, n do
dminusk 0
end for
for (u, v) E do
dminusv = dminusv + 1
end for
Q
. se initializeaz
a coada
for k 1, n do
if (dminusk = 0) then
Qk
. se insereaza ntro coada nodurile cu gradul interior 0
end if
end for
while (Q 6= ) do
Qk
. se extrage din coada un nod
Lk
. se insereaza nodul ntr-o lista
w V ecink
. v ia valoarea capului listei de vecini a nodului k
while (w 6= N U LL) do
dminusw.nodeIndex dminusw.nodeIndex 1
if (dminusw.nodeIndex = 0) then
Q w.nodeIndex
. se insereaza n coada nodul w.nodeIndex
end if
w w.next
. se trece la urmatorul vecin
end while
end while
end procedure

Input:
2:
3:
4:
5:
6:
7:
8:
9:
10:
11:
12:
13:
14:
15:
16:
17:
18:
19:
20:
21:
22:
23:
24:
25:
26:

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 ).
Definitia 7.12 Se numeste sortare topologic
a pentru un graf orientat G = (V, E) o ordonare {x1 , x2 , . . . , xn } a nodurilor grafului astfel nc
at pentru orice arc (xi , xj ) s
a avem
134

i < j.
Prin urmare o sortare topologic
a presupune aranjarea liniara a varfurilor unui graf astfel
ncat toate arcele sale sa fie orientate de la stanga la dreapta.
Lema 7.10 Daca un graf orientat G admite o sortare topologic
a atunci G este aciclic.
Lema 7.11 Daca un graf orientat G este aciclic atunci el admite o sortare topologic
a.

Observatia 7.12 Intrun


graf orientat aciclic exista cel putin un nod al carui grad interior
este 0 (nodul respectiv v nu posed
a arce avandul pe v drept extremitate finala).
Pornind de la aceasta observatie se schiteaza urmatorul algoritm [79]: ntrun 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 G0 = (V 0 , E 0 ), unde
V 0 = V \ {v}, E 0 = E|V 0 V 0 .
Algoritmul 49 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).
Exemplul 7.13 Fie graful orientat din figura 7.5. Varful 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
graful rezultat, varful 1 are proprietatea ca d (v) = 0. Se
au drept extremitate initial
a. In
adauga la lista de rezultate, si se elimina din graf mpreun
a cu arcele ce pleac
a din el. Se
continu
a procedeul pan
a cand graful devine vid (ntregul proces poate fi urmarit n figura 7.5).

Fig. 7.5: Sortare topologica cu algoritmul 49 pentru graful orientat din figura 7.4

135

Algorithm 50 Algoritm de sortare topologica a unui graf orientat (a doua varianta)


1: procedure
( SortTop2(n, V ecin)

n
- numarul de noduri din graf
V ecin - matricea de adiacent
a a grafului
for k 1, n do
prenumk 0, postnumk 0
vizitatk 0
end for
for k 1, n do
if (vizitatk = 0) then
call DF SN um(k, n, V ecin)
end if
end for
se ordoneaza descrescator nodurile dupa postnumk
end procedure

Input:
2:
3:
4:
5:
6:
7:
8:
9:
10:
11:
12:

Lema 7.14 Un graf orientat G este aciclic daca si numai daca n urma unei vizitari n
adancime a acestuia nu este nt
alnit nici un arc de ntoarcere.
Ideea algoritmului 50 o reprezinta lema 7.14[132].
Exemplul 7.15 Sa consider
am ca date de intrare pentru algoritmul 50 graful orientat din
figura 7.4. Dupa etapa de initializare (liniile 2 - 5) avem:
1
prenum 0
postnum 0
vizitat 0

2
0
0
0

3
0
0
0

4
0
0
0

5
0
0
0

6
0
0
0

7
0
0
0

Se apeleaz
a mai nt
ai DF SN um(1, 7, V ecin). Secventa rezultat
a de apeluri recursive este
urmatoarea: DF SN um(1, 7, V ecin) DF SN um(2, 7, V ecin) DF SN um(4, 7, V ecin)
DF SN um(5, 7, V ecin) DF SN um(7, 7, V ecin) DF SN um(6, 7, V ecin).
urma acestei secvente valorile vectorilor prenum si postnum sunt urmatoarele:
In
1
prenum 1
postnum 12
vizitat
1

2
2
11
1

3
0
0
0

4
3
10
1

5
4
9
1

6
6
7
1

7
5
8
1

Mai ramane nevizitat un singur nod, 3, drept pentru care vom mai avea un apel DF SN um(3, 7, V ecin)
din procedura principal
a SortT op2:
1
prenum 1
postnum 12
vizitat
1

2
2
11
1

3
13
14
1

4
3
10
1

5
4
9
1

6
6
7
1

7
5
8
1

Ordonarea descresc
atoare a nodurilor multimii V dup
a valorile vectorului postnum conduce la urmatoarea asezare:
postnum 14 12 11 10 9 8 7
3
1 2
4 5 7 6
136

Astfel sortarea topologic


a a nodurilor grafului obtinut
a n urma aplicarii algoritmului 50
este: 3, 1, 2, 4, 5, 7, 6.
Observatia 7.16 O optimizare a algoritmului 50 se refer
a la adaugarea nodului v ntr-o
stiva atunci cand se termina vizitarea acestui nod v si se calculeaz
a postnumv . Aceast
a stiva
la sfarsit va contine nodurile grafului n ordinea descresc
atoare a valorilor postnumv .
Astfel nu mai este nevoie sa ordon
am descresc
ator valorile vectorului postnum pentru a
obtine o sortare topologic
a a nodurilor grafului G. Astfel complexitatea algoritmului 50 devine
O(|V | + |E|).

7.4

Componente tare conexe

Fig. 7.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 7.13 O component
a tare conex
a a unui graful orientat G este o multime maximala de varfuri U V , astfel nc
at, pentru fiecare pereche de varfuri {u, v} (u, v U ) exista
atat un drum de la u la v cat si un drum de la v la u. Astfel se spune ca varfurile u si v
sunt accesibile unul din cel
alalt.
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 7.14 Graful orientat G se numeste tare conex daca 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 echivalent
a daca prezinta proprietatile de reflexivitate, simetrie si
tranzitivitate:
reflexivitate: a a;
137

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 componentele tare conexe.
Pentru determinarea componentelor tare conexe exista mai multi algoritmi, dintre care
amintim algoritmul lui Tarjan, algoritmul lui Kosaraju si algoritmul lui Gabow.

7.4.1

Algoritmul lui Kosaraju

Algoritmul lui KosarajuSharir a fost prezentat de Aho, Hopcroft si Ullman n lucrarea lor [3],
fiind preluat dintrun manuscris al lui S. Rao Kosaraju (M. Sharir la prezentat n lucrarea
[126]).
Algoritmul foloseste graful transpus GT asociat grafului initial G ([11], [29], [35]), 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 conex
a
a grafului G.
Prezentam implementarea n limbajul C a algoritmului anterior:
#include <stdio.h>
#include <mem.h>
#define MAX 100
#define TRUE 1
#define FALSE 0
/**
* Matricea de adiacenta
*/
char vecin[MAX][MAX];
/**
* Vector ce pastreaza starea unui nod: vizitat sau nevizitat.
*/
char vizitat[MAX];
/**

138

* Numarul de varfuri din graf


*/
int n;
int nump;
/**
* Numarul asociat fiecarui varf la vizitarea in postordine.
*/
int postnum[MAX];
void dfs(int k) {
int i;
vizitat[k] = TRUE;
for (i = 0; i < n; i++)
if ((vizitat[i] == FALSE) && (vecin[k][i] > 0))
dfs(i);
nump++;
postnum[k] = nump;
}
void dfs1(int k) {
int i;
vizitat[k] = TRUE;
for (i = 0; i < n; i++)
if ((vizitat[i] == FALSE) && (vecin[k][i] > 0))
dfs1(i);
printf("%d ", k);
}
void readInput(void) {
int i, j;
printf("n = "); scanf("%d", &n);
do{
printf("nod1 nod2 : "); scanf("%d %d", &i, &j);
if (i >= 0)
vecin[i][j] = 1;
} while (i >= 0);
}
void main(void) {
int i, j, k, tmp;
int maxim;
int nod;
readInput();
// prima etapa
memset(vizitat, 0, sizeof(vizitat));
nump = 0;
for (i = 0; i < n; i++)

139

if (vizitat[i] == FALSE)
dfs(i);
// etapa a doua
memset(vizitat, 0, sizeof(vizitat));
for (i = 0; i < n; i++)
for (j = i; j < n; j++) {
tmp = vecin[i][j];
vecin[i][j] = vecin[j][i];
vecin[j][i] = tmp;
}
k = 0;
while (TRUE) {
maxim = 0;
for (i = 0; i < n; i++)
if ((vizitat[i] == FALSE) && (maxim < postnum[i])) {
maxim = postnum[i];
nod = i;
}
if (maxim == 0)
break;
k++;
printf("Componenta %d : ", k);
dfs1(nod);
printf("\n");
}
}

Exemplul 7.17 Dupa parcurgerea n adancime a grafului 7.6, valorile vectorilor vizitat si
postnum sunt urmatoarele:
1
postnum 8
vizitat 1

2
7
1

3
5
1

4 5
6 1
1 1

6
2
1

7
3
1

8
4
1

Fig. 7.7: Graful transpus GT corespunzator grafului din figura 7.6

Se construieste graful transpus, GT (vezi figura 7.7). Se caut


a primul nod u nc
a nevizitat
(vizitatu = 0), caruia i corespunde cea mai mare valoare postnumu . Astfel se identifica
componenta tare conex
a compus
a numai din nodul 1: {1}.
140

Urm
atorul nod, ca valoarea a vectorului postnum ordonat descresc
ator, este 2. Din nodul
2 se parcurg nodurile 3 si 4, rezult
and o alta component
a tare conex
a: {2, 3, 4}.
Urm
atorul nod nevizitat u ce are valoarea prenumu maxim
a este nodul 8. Se identifica
astfel mai nt
ai componenta tare conex
a {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.

7.4.2

Algoritmul lui Tarjan

Algoritmul lui Tarjan [131] este considerat drept o mbunatatire a algoritmului lui Kosaraju
prin aceea ca nu mai este nevoie de parcurgea graful G de doua ori.
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 rad
acina), daca acesta constituie radacina subarborelui corespunzator componentei. Arcul ce are nodulcap drept extremitate finala este cel ce trebuie
eliminat. Dupa ce determinam toate nodurilecap, subarborii arborelui/arborilor de acoperire
n adancime ce i au drept radacini sunt componentele tare conexe.
Algoritmul lui Tarjan are drept scop determinarea nodurilorcap. Astfel 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 DF S a unui nod este
ncheiata: astfel 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 pentru 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 dintrun 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 nodcap (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 51) seamana foarte mult cu algoritmul 23
de determinare a muchiei critice (varianta a II-a).
141

Algorithm 51 Algoritmul lui Tarjan pentru determinarea componentelor tare conexe


1: procedure
( Tarjan(n, V ecin)

n
- numarul de noduri din graf
V ecin - matricea de adiacent
a
for i 1, n do
vizitati 0
end for
counter 0
S
for i 1, n do
if (vizitati = 0) then
call DF ST arjan(i, n, V ecin)
end if
end for
end procedure
procedure DFSTarjan(k, n, V ecin)
Sk
vizitatk 1
counter counter + 1
prenumk counter, lowk counter
for i 1, n do
if (vecink,i = 1) then
if (vizitati = 0) then
call DF ST arjan(i, n, V ecin)
lowk M in(lowk , lowi )
else
if (i S) then
lowk M in(lowk , prenumi )
end if
end if
end if
end for
if (lowk = prenumk ) then
repeat
Su
Output u
until (u = k)
end if
end procedure

Input:
2:
3:
4:
5:
6:
7:
8:
9:
10:
11:
12:
13:
14:
15:
16:
17:
18:
19:
20:
21:
22:
23:
24:
25:
26:
27:
28:
29:
30:
31:
32:
33:
34:
35:
36:

. vizitarea n adancime a grafului

Exemplul 7.18 La nceput valorile vectorilor vizitat si prenum sunt urmatoarele:


1
prenum 0
vizitat 0

2
0
0

3 4
0 0
0 0

5
0
0

6
0
0

7
0
0

8
0
0

Primul element nevizitat este nodul 1 (linia 9), prin urmare se apeleaz
a DF ST arjan(1, 8, V ecin).
Rezult
a o secvent
a de apeluri recursive:
DF SN um(1, 8, V ecin) DF SN um(2, 8, V ecin) DF SN um(3, 8, V ecin) DF SN um(6, 8, V ecin)
DF SN um(5, 8, V ecin).
142

1
prenum 1
vizitat 1
low
1

2
2
1
2

3
3
1
0

4
0
0
0

5
5
1
4

6
4
1
4

7
0
0
0

8
0
0
0

Stiva contine urmatoarele elemente (varful stivei fiind n dreapta): {1, 2, 3, 6, 5}.
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 terminarii apelului DF ST arjan(5, 8, V ecin),
low5 = 4.
Acum la nivelul lui DF ST arjan(6, 8, V ecin), n urma revenirii din DF ST arjan(5, 8, V ecin),
se calculeaz
a low6 = min{low6 , low5 } = min{4, 4} = 4 (linia 22).
Nodul 6 este un nodcap (low6 = prenum6 ) (vezi linia 30), si prin urmare se extrag de pe
stiva toate elementele dintre varful stivei si elementul 6 inclusiv, rezult
and prima component
a
tare conex
a: {5, 6}. Stiva ram
ane cu elementele (varful stivei fiind n dreapta) {1, 2, 3}.
Din nodul 3 se continu
a cu vizitarea nodului 8, nc
a nevizitat:
DF SN um(3, 8, V ecin) DF SN um(8, 8, V ecin) DF SN um(7, 8, V ecin).
Continutul stivei este {1, 2, 3, 8, 7}.
La nivelul apelului DF SN um(7, 8, V ecin) 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 DF SN um(8, 8, V ecin), low8 = min{low8 , low7 } = min{6, 6} = 6 (linia 22).
Avem ca (low8 = prenum8 ) (linia 30) si prin urmare 8 este un nodcap. Se extrag de pe stiva
toate elementele dintre varful stivei si elementul 8 inclusiv, rezult
and a doua component
a tare
conex
a: {7, 8}.
Stiva ram
ane 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
momentul terminarii apelului DF ST arjan(3, 8, V ecin), low3 = 2.
In
Revenind la nivelul apelului DF ST arjan(2, 8, V ecin), se calculeaz
a low2 = min{low2 , low3 } =
min{2, 2} = 2, si se continu
a cu urmatorul nod nc
a nevizitat, 4: DF SN um(2, 8, V ecin)
DF SN um(4, 8, V ecin).
Se adauga nodul 4 pe stiva (stiva devine {1, 2, 3, 4}), low4 = prenum4 = 8.
low4 se calculeaz
a din low4 = min{low4 , prenum3 } = min{8, 3} = 3 (exista arcul (4, 3),
nodul 3 a fost vizitat - vizitat3 = 1 si 3 S - nodul 3 se afla pe stiva).
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 DF ST arjan(2, 8, V ecin), 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 stiva. Astfel low2 r
am
ane cu valoarea 2.
Deoarece low2 = prenum2 , extragem de pe stiva elementele ce determina cea de-a treia
component
a tare conex
a: {4, 3, 2}. Pe stiva mai ram
ane un singur element, {1}, ce va
determina ultima component
a tare conex
a.

143

final, valorile low si prenum sunt urmatoarele:


In
1
prenum 1
vizitat 1
low
1

7.4.3

2
2
1
2

3
3
1
2

4
8
1
3

5
5
1
4

6
4
1
4

7
7
1
6

8
6
1
6

Algoritmul lui Gabow

Algoritmul a fost introdus de catre Joseph Cheriyan and Kurt Mehlhorn n 1996 [34] si apoi
independent de catre Harold Gabow n 1999 [59].
Algoritmul construieste un graf H [59] 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 aleganduse un nod oarecare v H.

Fig. 7.8: Drumul P n algoritmul lui Gabow pentru graful din figura 7.6

La fiecare pas al algoritmului se ncearca sa se augmenteze drumul P = P {w} =


[v1 , . . . , vk , w] prin parcurgerea tuturor arcelor (vk , w):
- 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 ncercanduse 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 (DF S). 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
144

observa faptul ca stiva P pastreaza nodurile rad


acin
a 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
DF SGabow(i, n, V ecin)). Altfel, se verifica daca nodul i nu a fost deja asignat unei componente tare conexe, n caz afirmativ eliminanduse 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
5:
while (prenumi < prenumpeek(P ) ) do
6:
P u
7:
end while
8:
end if
9: end if

.i

La finalul procedurii DF SGabow, 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
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

7.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 redundante. 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
145

Algorithm 52 Algoritmul lui Gabow pentru determinarea componentelor tare conexe


1:
2:
3:
4:
5:
6:
7:
8:
9:
10:
11:
12:
13:
14:
15:
16:
17:
18:
19:
20:
21:
22:
23:
24:
25:
26:
27:
28:
29:
30:
31:
32:
33:
34:
35:
36:
37:
38:

procedure Gabow(n, V ecin)


for i 1, n do
vizitati 0
end for
counter 0
S , P
for i 1, n do
if (vizitati = 0) then
call DF SGabow(i, n, V ecin)
end if
end for
end procedure
procedure DFSGabow(k, n, V ecin)
vizitatk 1
S k, P k
counter counter + 1
prenumk counter
for i 1, n do
if (vecink,i = 1) then
if (vizitati = 0) then
call DF SGabow(i, n, V ecin)
else
if (i S) then
. i nu a fost asignat nc
a unei componente tare conexe
while (prenumi < prenumpeek(P ) ) do
P u
end while
end if
end if
end if
end for
if (k = peek(P )) then
P u
repeat
Su
Output u
until (u = k)
end if
end procedure

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
146

sursa care au trebuit sa fie recompilate.


Intrare
3
MATH
20
0
MAIN
100
2
MATH IO
IO
7
0
3
MATH IO MAIN
0

Iesire
127

(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.
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 aplicarea 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 0 N U 0 , iar n caz afirmativ va afisa 0 DA0 , 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.
147

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:
(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 facultate. 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 exemplu, cursul Mecanica se bazeaza pe cursul Ecuatii Diferentiale, dar cursul
de Ecuatii Diferentiale si preia exemplele din Mecanica. Prin urmare nu exista 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.
148

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.

Fig. 7.9: Pozitiile cartilor ntro biblioteca precum si posibilitatile de deplasare ale robotului

6. Se considera o multime de n elevi dintro clasa. Fiecare elev are cunostinte mai avansate
ntrun anumit domeniu. Pentru ridicarea nivelului clasei, dirigintele vrea sai aranjeze
n grupuri astfel ncat toti elevii dintrun grup sa ajunga sa cunoasca, ntro 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.

149

Capitolul 8
Distante n grafuri
Reteaua de drumuri europene, nationale si judetene dintro 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 consumatorilor casnici si a celor industriali, reteaua de canalizare dintrun oras folosita pentru evacuarea
deseurilor, reteaua CAT V a unui operator de televiziune prin cablu, reteaua de linii de autobuz 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.
Intrun 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 ] de lungime m, costul acestuia va fi dat de urmatoarea formula:
c(D) =

m1
X

c((vi , vi+1 ))

i=0

Notam cu Ms,t multimea drumurilor dintre nodul xs si nodul 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 )

s,t Ms,t

150

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 listele de adiacenta
(listele cu vecini).
Fie u un varf al grafului numit sursa. 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 (algoritmul 53) se bazeaza pe structura algoritmului de parcurgere
n latime al unui graf (breadth first search vezi algoritmul 17). In lucrarea [103], 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 nota cu dk costul drumului minim de la varful u la varful xk , iar tatak varful anterior
lui xk , pe drumul de cost minim de la u la xk .
Algorithm 53 Algoritmul lui Moore
1: procedure Moore1(k, n, V ecin; d, tata)

Input:

k
n

V ecin

d
tata

- nodul sursa
- numarul de noduri din graf
- vector ce contine capetele listelor de vecini

- vectorul distantelor de la nodul sursa la celelalte noduri


Output:
- vector ce contine pentru fiecare nod k, predecesorul acestuia pe drumul
de cost minim de la nodul sursa 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 coada
7:
while (Q 6= ) do
. cat timp coada nu este vida
8:
Qk
. extrage nodul curent din coada
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

Exemplul 8.1 Fie un graf orientat definit de catre urmatoarele liste de vecini:
151

1:
2:
3:
4:
5:
6:

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

7: (8)
8: (6, 7)
9: ()
10: (11)
11: (10)

urma aplicarii algoritmului lui Moore pentru acest graf si avand nodul 1 drept sursa,
In
se obtin urmatoarele valori pentru vectorii d si tata:
d
tata

1
0

2
1
1

3
2
2

4
1
1

5
2
4

6
2
2

7
3
6

8
3
6

9
3
5

10

11

Urm
arind valorile afisate, putem spune ca drumul de lungime minima de la nodul 1 la
nodul 9 are costul 3 si este compus din nodurile [1, 4, 5, 9].
Algoritmul 54 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.
Algorithm 54 Algoritm lui Moore (a doua varianta)
1: procedure Moore2(k, n, C; d, tata)

k - nodul sursa
Input:
n - numarul de noduri din graf

C - matricea costurilor
2:
for i 1, n do
3:
di +
4:
end for
5:
dk 0
6:
Qk
7:
while (Q 6= ) do
8:
Qk
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
13:
end if
14:
end for
15:
end while
16: end procedure

8.1.2

. distanta de la un nod la el nsusi este 0


. inserarea nodului curent k n coada
. cat timp coada nu este vida
. extrage nodul curent din coada

. inserarea nodului i n coada

Algoritmul lui Dijkstra

Fie G = (V, E) un graf orientat unde V = {1, 2, . . . , n} este multimea nodurilor si E este
multimea arcelor (E V V ). Pentru reprezentarea grafului se utilizeaza matricea costurilor
C:

, daca i = j
0
ci,j =
, daca (i, j)
/ E, i 6= j

d > 0 , daca (i, j) E

152

Fie u un varf al grafului numit surs


a. Dorim sa determinam pentru fiecare varf j V, j 6=
u, daca exista, un drum de lungime minima de la u la j. Lungimea unui drum se defineste
ca fiind suma costurilor asociate arcelor ce-l compun.
Algoritmul lui Dijkstra [44] 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 nceput multimea S este formata doar din nodul sursa. La fiecare
pas, se adauga la multimea S un nod k V \ S cu proprietatea ca drumul de la sursa la
acel nod 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, cu exceptia extremitatii finale. Vom utiliza un tablou D ce va pastra pentru
fiecare nod k, lungimea drumului cel mai scurt de la sursa ce trece numai prin noduri din
multimea S (vezi algoritmul 55).
Dupa ce am identificat un astfel de nod k, multimea S se modifica astfel: S = S {k}.
Este posibil ca lungimea unui drum de la sursa la un nod j V \ S, ce are drept noduri
intermediare noduri din multimea S, sa se modifice datorita faptului ca, mai nainte, nodul k
nu fusese luat n considerare, deoarece nu apartinea multimii S. Astfel, se poate ca un drum
de la sursa la nodul j, ce trece prin nodul k, sa fie mai mic decat drumul anterior de la sursa
la nodul j: dk + ck,j < dj .
Algorithm 55 Algoritmul lui Dijkstra (schema generala)
1: procedure Dijkstra1(n, C, u)
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 |k V \ S}
8:
S S {k}
9:
for each j V \ S do
10:
dj min(dj , dk + ck,j )
11:
end for
12:
end for
13: end procedure

Vom utiliza trei vectori (vezi algoritmul 56):


vizitat - vector caracteristic

(
1 , daca nodul k S
vizitatk =
0 , daca nodul k V \ S

d - vectorul distantelor de la nodul u la celelalte noduri ale grafului. In momentul


initial dj = cu,j . Fie k nodul ales la un moment dat. Atunci dj se modifica numai daca
dk + ck,j < dj , fiind actualizat astfel: dj = dk + ck,j .
tatak - contine pentru fiecare nod k nodul anterior j (j S) pe drumul de cost minim
de la u la k. La nceput,
(
0 , daca nodul k = u sau cu,k =
tatak =
u , n rest (k 6= u si cu,k 6= )
153

Un element al acestui vector se poate modifica atunci cand se modifica dj , caz n care
tataj = k.
Algorithm 56 Algoritmul lui Dijkstra
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 Dijkstra2(n, C, u)
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 6= 1) (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

Observatia 8.2 Algoritmul lui Dijkstra prezint


a aseman
ari cu algoritmul de cautare n
latime, la final, vectorul tata p
astr
and un arbore al drumurilor minime ce are drept radacin
a
nodul sursa.

154

j
x

u
k

Fig. 8.1: O cale speciala mai scurta

Demonstrarea corectitudinii algoritmului


In momentul n care alegem nodul k V \ S avand proprietatea ca drumul de la sursa la acel
nod 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, cu exceptia extremitatii finale, acel drum este cel
mai mic drum de la sursa la k dintre toate drumurile posibile. Denumim drum special un
drum de la sursa la un nod k ce are drept noduri intermediare numai elemente din multimea
S, cu exceptia extremitatii finale.
Sa presupunem prin reducere la absurd, ca exista un drum de lungime mai mica de la
nodul sursa la nodul k, ce nu are toate nodurile intermediare din multimea S. Fie j V \ S,
primul nod pe drumul de la nodul sursa la nodul k, ce nu apartine multimii S. Atunci drumul
de la nodul sursa u la nodul k se compune dintr-un drum de la u la j, si un drum de la j la
k. Datorita modului de alegere al lui j, drumul de la u la j are drept noduri intermediare
numai elemente din multimea S (j este primul nod pe drumul de la nodul sursa la nodul
k, care nu apartine multimii S), deci este un drum special si are o lungime mai mica decat
drumul special de la sursa la nodul k. Prin urmare am gasit un alt drum special de lungime
mai mica, ceea ce contrazice modul de alegere al nodului k.
Trebuie sa demonstram ca n orice moment, dk pastreaza lungimea celui mai scurt drum
special de la nodul sursa u la nodul k V \ S. In momentul n care nodul k este adaugat
multimii S, avem grija sa verificam daca nu exista un drum special de la nodul u la un nod
j V \ S care sa aiba o lungime mai mica. Sa presupunem ca pentru un nod j fixat exista
un nod x S astfel ncat drumul special u k + k x + (x, j) sa aiba o lungime mai
mica (vezi figura 8.1). Deoarece nodul x a fost adaugat multimii S naintea nodului k avem
dx dk . Prin urmare dx + cx,j dk + cx,j < dk + costkj + cx,j , adica drumul special de la
sursa la nodul j ce trece prin nodul x are lungimea mai mica decat drumul special compus
din drumul de la nodul sursa la nodul k, drumul de la nodul k la nodul x si arcul (x, j).
Exemplul 8.3 S
a presupunem ca nodul sursa este nodul 1 pentru graful din figura 8.2. Dupa
etapa de initializare vom avea urmatoarele valori:
1
d
tata
vizitat

0
1

2
1
1
0

1
0

4
21
1
0

1
0

155

Fig. 8.2: Un exemplu de graf orientat ponderat

Dup
a prima iteratie avem:
d2 + c2,3 < d3 1 + 8 < +
d2 + c2,4 < d4 1 + < 21
d2 + c2,5 < d5 1 + 4 < +
1
d
tata
vizitat

0
1

2
1
1
1

3
9
2
0

4
21
1
0

5
5
2
0

timpul celei de-a doua iteratii se calculeaz


In
a:
d5 + c5,3 < d3 5 + 3 < 9
d5 + c5,4 < d4 5 + < 21
1
d
tata
vizitat

0
1

2
1
1
1

3
8
5
0

4
21
1
0

5
5
2
1

Dup
a pasul al treilea avem:
d3 + c3,4 < d4 8 + 12 < 21
1
d
tata
vizitat

0
1

2
1
1
1

3
8
5
1

4
20
3
0

5
5
2
1

La sfasitul ultimei iteratii vom obtine:


1
d
tata
vizitat

0
1

2
1
1
1

3
8
5
1

4
20
3
1

5
5
2
1

Implementarea n limbajul C a algoritmului 56 este urmatoarea:

156

#include <stdio.h>
#include <values.h>
#define MAX 100
#define TRUE 1
#define FALSE 0
/* numarul de noduri din graf */
int n;
/* matricea costurilor */
int c[MAX][MAX];
/* vector care pastreaza starea unui nod: vizitat sau nevizitat */
char vizitat[MAX];
/* nodul fata de care se calculeaza drumurile de lungime minima */
int u;
/* tata[i] parintele varfului i in arborele rezultat */
int tata[MAX];
/* distanta de la fiecare nod la u */
int d[MAX];
/**
* Citirea datelor de intrare - +infinit = -1
*/
void readInput(void) {
int i, j;
printf("n = "); scanf("%d", &n);
for (i = 0; i < n; i++)
for (j = 0; j < n; j++) {
scanf("%d", &c[i][j]);
if (c[i][j] < 0)
c[i][j] = MAXINT;
}
printf("Nodul initial : "); scanf("%d", &u);
}
int minim(int* d) {
int j, j0;
int min;
min = MAXINT;
for (j = 0; j < n; j++)
if (!vizitat[j] && d[j] < min) {
min = d[j];
j0 = j;
}
if (min == MAXINT)
return -1;
else
return j0;
}

157

/**
* Functia verifica daca a>b+c; variabilele sunt de tip long
* pentru ca daca suma depaseste 32767 se face trunchiere.
*/
int mai_mare(long a, long b, long c) {
return (a > b+c) ? 1 : 0;
}
void dijkstra(int u) {
int i, k;
//initializari
vizitat[u] = TRUE;
tata[u] = -1;
for (i = 0; i < n; i++)
if (i != u) {
vizitat[i] = FALSE;
d[i] = c[u][i];
tata[i] = u;
}
//partea principala
while (TRUE) {
k = minim(d);
if (k < 0)
break;
vizitat[k] = TRUE;
//actualizare
for (i = 0; i < n; i++)
if (!vizitat[i] && mai_mare(d[i],d[k],c[k][i])) {
tata[i] = k;
d[i] = d[k] + c[k][i];
}
}
}
void printSolution(void) {
int i;
for (i = 0; i < n; i++)
if (i != i0)
if (d[i] == MAXINT)
printf("Nu exista drum de la varful %d la
varful %d. \n", i0, i);
else
printf("Distanta de la varful %d la varful %d
este %d. \n", i0, i, d[i]);
}
void main(void) {
readInput();
dijkstra(u);
printSolution();

158

Utilizarea structurii coad


a cu prioritate n algoritmul lui Dijkstra
Complexitateatimp al algoritmului lui Dijkstra poate fi mbunatatita prin utilizarea structurii de date de coad
a cu prioritate (vezi algoritmul 57).
Algorithm 57 Algoritmul lui Dijkstra (varianta ce foloseste o coada cu prioritate)
1: procedure Dijkstra3(n, C, u)
2:
for i 1, n do
3:
di
4:
tatai N U LL
5:
call Insert(Q, i, di )
. insereaz
a n coada elementul i cu prioritatea di
6:
end for
7:
du 0
8:
call DecreaseKey(Q, u, du )
. actualizeaz
a prioritatea varfului u la du
9:
S
10:
for i 1, n 1 do
11:
k DeleteM in(Q)
. sterge elementul de prioritate minima din coada
12:
S S {k}
13:
for f iecare j V \ S, astfel nc
at (k, j) 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 varfului j la dj
18:
end if
19:
end for
20:
end for
21: end procedure

Analizand algoritmul 57 obtinem ca timpul de lucru al acestuia depinde de formula:


T (n, m) = O(n TInsert + n TDeleteM in + m TDecreaseKey )

(8.1)

unde n = numarul de noduri ale grafului G iar m = numarul de muchii ale aceluiasi graf.
Cele trei operatii ntalnite n cadrul algoritmlui sunt (vezi si capitolul ??):
1. DeleteM in(Q) - sterge nodul ce contine cheia de valoare minima si reorganizeaza structura prin refacerea proprietatii de coad
a cu prioritate.
2. Insert(Q, p, x) - insereaza nodul p n coada cu prioritate Q.
3. DecreaseKey(Q, p, v) - modifica valoarea prioritatii nodului p din coada cu prioritate
Q, atribuindui valoarea v si reorganizeaza aceasta structura.
Astfel pentru diferite implementari ale cozii cu prioritate Q, complexitatea algoritmului
va fi [35]:
1. Lista liniara dublu nlantuita neordonata:
T (n, m) = O(n 1 + n n + m 1) = O(n2 )

159

2. Arbore 2 3:
T (n, m) = O(n 1 + n n + m 1) = O(n2 )
3. Heap-uri Fibonacci:
T (n, m) = O(n 1 + n n + m 1) = O(n2 )

8.1.3

Algoritmul lui BellmanKalaba

Fie G = (V, E) un graf orientat unde V = {x1 , x2 , . . . , xn } este multimea nodurilor si E


este multimea arcelor (E V V ). Se alege un nod t al grafului (t V ) si se calculeaza
drumurile de distanta minima de la toate nodurile u (u V, u 6= t) la acest nod. In algoritmul
BellmanKalaba se va utiliza matricea costurilor C:

, daca i = j
0
ci,j =
, daca (i, j)
/ E, i 6= j

d > 0 , daca (i, j) E


(k)

Algoritmul presupune calculul iterativ al unor vectori V (k) = (i )i=1,n :


(k)

(k1)

= min {ci,j + j
j=1,n

Fig. 8.3: Alt exemplu de graf orientat ponderat

Descrierea etapelor algoritmului:


1. Se construieste matricea V pornind de la matricea costurilor C (V = C).
(1)

(1)

2. Se adauga la matricea V linia V (1) = (i )i=1,n unde i

= ci,t .

3. Pasul k, k = 1, 2, . . . Se calculeaza urmatorii doi vectori pe baza matricei V construita


la pasul anterior, k 1:
(k)

(a) V (k) = (i )i=1,n


(k)

min {ci,j + j(k1) } , daca i 6= t


j=1,n

, daca i = t.
160

(k)

(b) next(k) = (nexti )i=1,n , next(k) nodul spre care exista arce de lungime minima
din nodul i

l , daca min {ci,j + j(k1) } = ci,l + l(k1) , i 6= t


(k)
j=1,n
nexti =
0 , daca (0) = .
i
(k1)

4. Algoritmul se ncheie atunci cand i

(k)

= i , i = 1, n.

Etapele descrise mai sus pot fi urmarite n cadrul algoritmului 58.


Algorithm 58 Algoritmul BellmanKalaba
1: procedure BellmanKalaba(t, n, C; V k , nextk )

t - nodul destinatie
Input:
n - numarul de noduri din graf

C - matricea costurilor
2:
for i 1, n do
3:
for j 1, n do
4:
vi,j ci,j
5:
end for
6:
end for
7:
for i 1, n do
(1)
8:
i ci,t
9:
end for
10:
k1
11:
repeat
12:
k k+1
13:
for i 1, n do
14:
min
15:
for j 1, n do
(k1)
16:
if (min > ci,j + j
) then
17:

(k1)

min ci,j + j
(k)

18:
nexti j
19:
end if
20:
end for
(k)
21:
i min
22:
end for
23:
until (V (k) = V (k1) )
24: end procedure

Dupa ncheierea etapei computationale, valorile drumurilor se obtin din coloana V k iar
componenta drumurilor de valoare minima de la fiecare nod spre nodul t, se poate determina
din vectorii nextk .
Exemplul 8.4 S
a consider
am graful din figura 8.3. Matricea costurilor asociat
a acestui graf
este:

161

C=

5
0
7

7
14
0

8
12
0

12
9
0

29

16
0

Num
arul de noduri al grafului este 7 (n = 7), iar varful catre care se calculeaz
a drumurile
de cost minim de la toate celelalte varfuri este t = 7.
Pentru determinarea valorilor vectorului V (2) se efectueaz
a urmatoarele calcule:
(2)

(1)

1 : a1,j + j

a1,2 + 21 = 5 +
a1,3 + 31 = 6 +
a1,7 + 71 = 29 + 0 = 29

(2)

(1)

2 : a2,j + j

a2,4 + 41 = 7 +
a2,6 + 61 = 7 + 16 = 23

(1)

(2)

3 : a3,j + j

a3,2 + 21 = 7 +
a3,4 + 41 = 14 +
a3,5 + 51 = 8 + =

(2)

(1)

4 : a4,j + j

a4,5 + 51 = 12 +
a4,6 + 61 = 12 + 16 = 28

(2)

(1)

a5,6 + 61 = 9 + 16 = 25

(2)

(1)

a6,7 + 71 = 16 + 0 = 16

5 : a5,j + j
6 : a6,j + j
(2)

(2)

7 : cazul i = t 7

1
2
3
4
5
6
7

V (1)
29

16
0

V (2)
29
23

28
25
16
0

V (3)
28
23
30
28
25
16
0

= 0

V (4)
28
23
30
Deoarece V (3) = V (4) , calculele se opresc la pasul 4.
28
25
16
0

Complexitatea timp a algoritmului BellmanKalaba este O(n3 ).

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 .
162

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 sal aplicam pentru fiecare varf al grafului.
Un astfel de algoritm este algoritmul lui Dijkstra, algoritm ce a cunoscut multiple mbunatatiri fata de varianta orginala, ce se bazeaza pe utilizarea unor structuri de date avansate (de exemplu heap-uri Fibonacci), menite sai optimizeze timpul de executie. Astfel complexitatea
timp n cazul cel mai defavorabil pentru calculul celor mai scurte drumuri pornind dintrun
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).
Algorithm 59 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
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 lui (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 lui (k, j) din C la dk,j
24:
end if
25:
end for
26:
end while
27: end procedure

. sau (ci,j > 0) (ci,j < )

Algoritmul 59 prezinta o varianta a algoritmului lui Dijkstra n care drumurile de lungime


minima ntre oricare doua varfuri sunt calculate incremental si intercalat [41]. 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 fiecaruia. 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 .

163

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).
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.
Notam dki,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 }:
dki,j = dk1
i,j
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
k1
k
k
p1 si p2 sunt drumuri de cost minim si xk
/ {x1 , x2 , . . . , xk1 } avem: dk1
i,k = di,k , dk,j = dk,j .
k1
c(p) = c(p1 ) + c(p2 ) dki,j = dk1
i,k + dk,j

Algoritmul va construi un sir de matrici D0 , D1 , . . . , Dk , . . . , Dn . Pentru fiecare k (1


k n), dki,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 D0 se va initializa astfel:

, daca i = j
0
0
(8.2)
di,j = +
, daca (xi , xj )
/ E, i 6= j

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 } fie
k1
nu contine nodul xk caz n care dki,j = dk1
l contine, si atunci dki,j = dk1
i,j , fie
i,k + dk,j . Prin
k1
k1
urmare dki,j = min{dk1
i,j , di,k + dk,j }.
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 necesarului
de memorie. Se observa ca matricea Dk nu mai este necesara de ndata ce matricea Dk+1 a
fost calculata.
Dn = (dni,j ), dni,j = i,j , xi , xj V.
In algoritmul 60 se poate renunta la indicii superiori, obtinanduse astfel o economie de
spatiu de la (n3 ) la (n2 ) (vezi algoritmul 61).
Exemplul 8.5 Fie graful din figura 8.2. Matricea costurilor asociat
a acestui graf este:

0
1 + 21 +
+ 0
8 + 4

3
+
0
12
+
C=

+ + 9
0 +
+ + 3 + 0
164

Algorithm 60 Algoritmul FloydWarshall


1: procedure FloydWarshall1(n, C; D n )
2:
for i 1, n do
3:
for j 1, n do
4:
d0i,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
k1
k1
10:
dki,j min {dk1
i,j , di,k + dk,j }
11:
end for
12:
end for
13:
end for
14: end procedure

Algorithm 61 Algoritmul FloydWarshall (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

S
irul de matrice Dk , rezultat al aplicarii algoritmului Roy-Floyd-Warshall pentru acest
graf, este:

165


D0

D2

D4

0 1 21
0 8 4

3 0 12

9 0
3 0
0 1 9
0 8
3 4 0
9
3

21 5
4
12 8
0
0

0 1 9
11 0 8
3 4 0
12 13 9
6 7 3

21 5
20 4
12 8
0 17
15 0

D1

D3

0 1 21
0 8 4

3 4 0 12

9 0
3 0

D5

0 1 9
11 0 8
3 4 0
12 13 9
6 7 3

21 5
20 4
12 8
0 17
15 0

0 1 8
10 0 7
3 4 0
12 13 9
6 7 3

20 5
19 4
12 8
0 17
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 )|
un drum de la xi la xj n G}.
Inchiderea tranzitiva a unui graf orientat prezinta multiple aplicatii drept subproblema n
cazul unor probleme computationale cum ar fi: construirea unui automat de parsare utilizat
la realizarea unui compilator, evaluarea interogarilor recursive realizate asupra unei baze de
date sau analiza elementelor accesibile ntro retea de tranzitie asociata unui sistem paralel
sau distribuit.
Prima varianta de a determina nchiderea tranzitiva presupune atribuirea unui cost unitar
(= 1) arcelor din E urmata de aplicarea algoritmului RoyFloydWarshall. 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 (vezi algoritmul 62), se nlocuiesc operatiile
min cu (sau logic) si respectiv + cu (si logic).
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
(8.3)
dki,j =
0 , daca i 6= j si (xi , xj )
/E
k1
k1
dki,j = dk1
i,j (di,k dk,j )

Exemplul 8.6 Fie graful din figura 8.2, din care am eliminat arcul (1, 2). Matricea costurilor asociat
a acestui graf modificat este:

0 21
0 8 4

C=
3 0 12
9 0
3 0

166

Algorithm 62 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 (xi = xj ) (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
k1
k1
14:
dki,j dk1
i,j (di,k dk,j )
15:
end for
16:
end for
17:
end for
18: end procedure

D0

D2

D4

8.3

1
0
1
0
0

0
1
0
0
0

0
1
1
1
1

1
0
1
1
0

0
1
0
0
1

1
0
1
0
0

0
1
0
0
0

0
1
1
1
1

1
0
1
1
0

0
1
0
0
1

1
1
1
1
1

0
1
0
0
0

1
1
1
1
1

1
1
1
1
1

0
1
0
0
1

D1

D3

D5

1
0
1
0
0

0
1
0
0
0

0
1
1
1
1

1
0
1
1
0

0
1
0
0
1

1
1
1
1
1

0
1
0
0
0

0
1
1
1
1

1
1
1
1
1

0
1
0
0
1

1
1
1
1
1

0
1
0
0
0

1
1
1
1
1

1
1
1
1
1

0
1
0
0
1

Circuitul Hamiltonian

Fie G = (V, E) un graf orientat unde V = {x1 , x2 , . . . , xn } reprezinta multimea nodurilor


si E reprezinta multimea arcelor (E V V ). Pentru reprezentarea grafului se utilizeaza
matricea costurilor C:

, daca i = j
0
ci,j = + , daca (xi , xj )
(8.4)
/ E, i 6= j

c > 0 , daca (xi , xj ) E.


Algoritmul va determina un circuit ce va trece o singura data prin fiecare varf si ce va
167

avea cost minim (circuit Hamiltonian).


Se presupune ca circuitul ncepe n nodul x1 deoarece prin rotirea circuitului se poate alege
orice varf drept varf initial. In cazul n care xk este urmatorul nod ce apartine circuitului,
atunci drumul de la xk la x1 este drumul de cost minim de la xk la x1 ce trece exact o data
prin varfurile din V \ {x1 } si ajunge n nodul initial, x1 .
Notam cu dk,S lungimea drumului minim de la xk la x1 ce trece exact o data prin fiecare
varf din S. Avem urmatoarele relatii:
d1,X\{1} =

min {c1,k + dk,X\{1,k} }

2kn

di,S = min{ci,k + dk,S\{k} }


kS

di, = ci,1 .
Algorithm 63 Algoritm de calcul al circuitului Hamiltonian
1: procedure CircuitHamiltonian(n, C; cost, D)
2:
for i 1, n do
3:
di,0 ci,1
4:
end for
5:
for k 1, n 1 do
6:
for i 1, n do
7:
optim +
8:
for j 1, n do
9:
if (j 6= i) (optim > ci,j + dj,k1 ) then
10:
optim ci,j + dj,k1
11:
end if
12:
end for
13:
di,k optim
14:
end for
15:
end for
16:
cost d1,n1
17: end procedure

Din motive de implementare vom modifica putin definitia termenului di,S : di,k = lungimea
drumului minim de la nodul xi la nodul x1 ce trece prin exact k v
arfuri intermediare distincte
ntre ele si diferite de xi .
Atunci vom avea relatia:
di,k = min {ci,j + dj,k1 }, i, j S, i 6= j
jS

(8.5)

unde di,0 = ci,1 lungimea drumului minim de la xi la x1 ce nu are varfuri intermediare


este data de costul arcului (xi , x1 ). Costul circuitului minim este reprezentat de valoarea
termenului d1,n1 (vezi algoritmul 63).

8.4

Graf pe mai multe niveluri

Definitia 8.2 Se numeste graf pe mai multe niveluri un graf orientat G = (V, E) n
care:

168

Fig. 8.4: Graf pe mai multe niveluri

varfurile sunt asezate pe n niveluri numerotate de la 1 la n; fie Vi multimea varfurilor de pe


nivelul i;
pentru orice arc (xi , xj ), exista k {1, 2, . . . , n} astfel nc
at xi Vk si xj Vk1 ;
exista un singur varf pe nivelul 1 (notat cu xs ) si un singur varf pe nivelul n (notat cu xt );
fiec
arui arc (xi , xj ) i se ataseaz
a un cost ci,j .
Intrun astfel de graf se cere sa de identifice un drum de cost minim de la varful initial
xs la varful final xt .
Daca d este un drum optim (de cost minim) de la nodul xs la nodul xt , atunci pentru
orice xi varf intermediar, drumurile de la xs la xi si cel de la xi la xt sunt optime.
Fie xj Vi , i {1, 2, . . . , n 1}. Notam cu dij costul drumului minim de la xj la xt
(consideram ca vom avea dij = + daca nu exista vreun drum de la xj la xt ).
dij =

min

xk Vi+1 ;(xj ,xk )E

n
{cj,k + di+1
k } cu dt = 0

(8.6)

Aceste relatii presupun utilizarea metodei programarii dinamice, mai exact metoda nainte
(vezi algoritmul 64).
Exemplul 8.7 Fie graful pe mai multe niveluri din figura 8.4. Numarul de varfuri este
n = 12.
urma aplicarii algoritmului prezentat pentru acest graf si avand varfurile 1 drept sursa,
In
respectiv 12 destinatie, se obtin urmatoarele valori pentru vectorii d si next:
d
next

1
11
4

2
10
5

3
8
6

4
9
6

5
8
9

6
2
9

8
7
12

9
1
12

10

169

11
2
12

12
0
0

Algorithm 64 Algoritm de calcul al drumului minim ntrun graf pe nivele


1: procedure GrafPeNivele(n, C)
2:
dn 0, nextn 0
3:
for i n 1, 1 ST EP 1 do
4:
di , nexti 0
5:
for j i + 1, n do
6:
if (cij < ) (di > cij + dj ) then
7:
di cij + dj
8:
nexti j
9:
end if
10:
end for
11:
if (d1 = ) then
12:
Output {Nu exista drum}
13:
else
14:
k1
15:
while (k 6= 0) do
16:
Output {k}
17:
k nextk
18:
end while
19:
end if
20:
end for
21: end procedure

i = 11 : j = 12 d11 > c11,12 + d12


i = 10 : j = 11 d10 > c10,11 + d11
j = 12 d10 > c10,12 + d12
i = 9 : j = 10 d9 > c9,10 + d10
j = 11 d9 > c9,11 + d11
j = 12
i=8: j=9
j = 10
j = 11
j = 12

d9 > c9,12 + d12


d8 > c8,9 + d9
d8 > c8,10 + d10
d8 > c8,11 + d11

>+0
>+2
>+0
>+
>+2

> 1 + 0 d9 = 1
>+1
>+
>+2

d8 > c8,12 + d12 > 7 + 0 d8 = 7


...

Prin urmare drumul de lungime minima de la 1 la 12 are costul 11 si trece prin nodurile
[1, 4, 6, 9, 12].
Complexitatea timp a algoritmului prezentat (algoritmul 64) este O(n2 ), ea devenind
O(n + m) daca se utilizeaza reprezentarea grafului prin liste de adiacenta.

8.5

Exercitii

1. Jocul Icosian (1857) atribuit matematicianului irlandez William Hamilton, presupune


determinarea unui ciclu Hamiltonian care sa cuprinda cele 20 de varfuri ale unui dode170

Fig. 8.5: Graful Icosian

Fig. 8.6: O solutie a problemei circuitului Hamiltonian ntr-un dodecaedru

caedru (vezi figura 8.5). In figura 8.6 este ilustrata o solutie a acestei probleme.
2. Cutii ndimensionale Sa consideram o cutie ndimensionala, 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 ndimensionale: trebuie gasita o secventa
de lungime maxima (b1 , b2 , . . .) din grupul de k cutii astfel ncat fiecare cutie bi sa poata
fi introdusa n cutia bi+1 .
Cutia D = (d1 , d2 , . . . , dn ) poate fi introdusa n cutia E = (e1 , e2 , . . . , en ) numai daca
laturile cutiei D pot fi combinate n asa fel cu laturile cutiei E, astfel ncat lungimea
fiecarei laturi a cutiei D sa nu depaseasca lungimea laturii corespunzatoare din cutia
E. De exemplu cutia (2, 6) poate fi introdusa n cutia (7, 3).
Cutiile egale pot fi introduse una ntralta. 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)

3. 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
171

companiei.
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
n 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) si timpul de descarcare al camionului la destinatie, ce este de exact
30 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)

4. 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 inundaie 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.
5. Sa se determine drumul de lungime minima necesar deplasarii unui cal pe o tabla de
sah (avand dimensiunile 8 8) de la un punct de plecare la unui de sosire, stiind ca pe
tabla exista si gauri. Calul poate fi mutat numai n casutele fara gauri.
(ACM, Eastern Europe, 1994)

6. 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 necontradictoriu. Se cere sa se determine ordinea literelor din noul alfabet.
(ONI, 1991)

7. Sa se implementeze algoritmul lui Dijkstra (algoritmul 57) pentru heapuri Fibonacci,


arbori 2 3 si heapuri binomiale.
8. Un sistem de posta dispune de mai multe oficii postale, un dispecerat si un sistem de
legaturi (cai de comunicatie) ntre acestea. O scrisoare poate sa ajunga de la dispecerat
la oficiul postal caruia i este destinata, fie direct daca exista legatura ntre dispecerat
si acel oficiu, fie indirect trecand prin mai multe oficii ntre care exista legatura pana la
oficiul de destinatie. Intre dispecerat si orice oficiu exista cel putin un drum prin care
o scrisoare poate ajunge la destinatie.
Pentru a evita problemele legate de luarea unor decizii, conducerea postei a hotarat
suprimarea unor cai de comunicatie astfel ncat drumul pe care este nevoita sa-l urmeze
o scrisoare de la dispecerat la orice oficiu sa fie unic. Deoarece trecerea unei scrisori
172

printr-un oficiu intermediar necesita un timp mare (necesar pentru sortarea corespondentei
n functie de directia spre care trebuie trimisa mai departe), se alege ca masura a
calitatii noului sistem de comunicatii numarul de oficii (inclusiv dispeceratul si oficiul
de destinatie) prin care o scrisoare trebuie sa treaca pentru a ajunge de la dispecerat
la destinatie. Conducerea postei doreste ca dintre toate sistemele de comunicatie ce se
pot forma prin suprimarea unor legaturi existente si care respecta conditiile:
O scrisoare poate ajunge de la dispecerat la orice oficiu postal
Drumul pe care o scrisoare poate sa-l urmeze de la dispecerat la orice oficiu postal
este unic
sa-l aleaga pe cel cu masura calitatii (definita mai sus) minima.
In fisierul de intrare se gasesc mai multe seturi de date; un set de date are pe prima
linie numaarul n de oficii. Fiecare oficiu este identificat printr-un numar ntre 1 si n,
oficiul 1 fiind dispeceratul. Pe urmatoarele n linii sunt date (ntr-o ordine arbitrara),
pentru fiecare k (1 k n), oficiile cu care oficiul k comunica direct printr-o legatura.
In fisierul de iesire trebuie sa apara:
Numarul de legaturi ce raman n urma operatiei de suprimare.
Numarul maxim de oficii (inclusiv dispeceratul si oficiul de destinatie) prin care
o scrisoare trebuie sa treaca pentru a ajunge de la dispecerat la destinatie.
Legaturile ramase, prezentate sub forma a n linii, pe fiecare linie afisandu-se
numarul unui oficiu urmat de numerele de identificare ale oficiilor cu care acesta
este legat direct.
Intrare
4
2 3 4
1 3
1 2 4
1 3
5
2 4
1 3
2 4 5
1 3 5
3 4
Iesire
Numarul de legaturi ramase: 3
Numarul maxim de oficii prin care trece o scrisoare: 2
Legaturi ramase:
1:
2 3 4
2:
1
3:
1
4:
1
Numarul de legaturi ramase: 4

173

Numarul maxim de oficii prin care trece o scrisoare: 3


Legaturi ramase:
1:
2 4
2:
1 3
3:
2
4:
1 5
5:
4

9. Profesorul Heif realizeaza niste experimente cu o specie de albine din America de Sud
pe care lea 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 diferenta
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 si
numerotando cu 1, apoi celelalte celule ramase sunt numerotate circular, n sensul
acelor de ceas, ca n figura.
__
__
__
__
__/ \__/ \__/ \__/ \__
__/ \__/ \__/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.
174

(ACM Final, 1999)

10. Intro 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
dintrunul 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)

11. 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 asi scurta drumul catre destinatie.
(a) gasiti lungimea celui mai scurt drum din labirint din locul n care printul intra n
labirint pana n locul n care traieste printesa;
(b) gasiti lungimea celui mai scurt drum daca se sparge un perete.
Castelul este de 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 se 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)

175

Appendix A
Probleme. Algoritmi. Complexitate
Vom considera o problem
a computational
a ca fiind o aplicatie
P : I O

(A.1)

unde I reprezinta multimea intrarilor problemei, multimea instantelor problemei, iar O reprezinta
multimea iesirilor, raspunsurilor, solutiilor. Aceasta problema P pentru fiecare intrare i I
ofera o iesire P(i) O.
Definitia A.1 Daca O = {da, nu} atunci P se va numi problem
a de decizie, P(i)
{da, nu} va fi raspunsul la ntrebarea pusa de P, iar forma uzuala de prezentare a pro-blemei va fi:
P Intrare:
i I.

Intrebare: . . ..
Exemplul A.1 S
a consider
am urmatoarea problem
a:
NrCompus Intrare:
n N, n 2.

Intrebare: Exista p, q N, p, q 2 si n = pq?


Un alt tip de probleme ce apar n mod frecvent sunt cele de c
autare: multimea O contine
pentru fiecare i I macar o solutie acceptabila n raport cu un anumit criteriu precizat, iar
problema cere gasirea unei astfel de solutii.
O clasa speciala de astfel de probleme este cea a problemelor de optimizare, si care pot fi
probleme de maximizare sau de minimizare.
Exemplul A.2 Prezent
am cateva exemple de astfel de probleme de optimizare:
Drum Intrare: G un graf, a, b doua varfuri ale lui G.
Ie
sire:
P un drum n G de la a la b (dac
a ).
sau
DrumMin Intrare: G un graf, a, b doua varfuri ale lui G, o functie de lungime
a muchiilor lui G.
Ie
sire:
P un drum n G de la a la b cu suma lungimilor
muchiilor minima, printre toate drumurile de la a la b n G.
sau
MaxCut Intrare: G graf, o functie de lungime a muchiilor lui G.
Ie
sire:
O bipartitie (S, T ) a multimii varfurilor grafului G
cu suma lungimilor muchiilor dintre cele doua clase maxima.

176

Oricarei probleme de optimizare i se poate asocia o problem


a de decizie (care ne va da
informatii asupra dificultatii ei computationale). Pentru cele doua probleme de optimizare
de mai sus, avem:
DrumMinD Intrare:
G un graf, a, b varfuri ale lui G, k N
o functie de lungime a muchiilor lui G.
Intrebare: Exista P drum n G de la a la b cu suma lungimilor
muchiilor k?
sau
MaxCutD Intrare:
G un graf, k N, o functie de lungime a muchiilor lui G.
Intrebare: Exista (S, T ) bipartitie a multimii varfurilor grafului G
cu suma lungimilor muchiilor dintre cele doua clase k?
Vom considera n continuare doar probleme de decizie.
Pentru a fi rezolvate cu ajutorul calculatorului problemele sunt codificate. Fie o multime
finita, fixata, numita alfabet, iar multimea tuturor cuvintelor peste .
Definitia A.2 Obiectele care apar n descrierea unei probleme (numere rationale, grafuri,
functii, matrici etc.) vor fi reprezentate cu ajutorul unor cuvinte w . Lungimea
cuvantului w se va nota cu |w|. Orice multime de cuvinte peste , adica orice submultime a
lui se numeste limbaj (peste ).
Problemele de decizie au urmatoarea forma: Fiind dat un anumit obiect, are el o anumita
proprietate?. Deoarece obiectele le reprezentam cu ajutorul cuvintelor nseamna ca problema
se reduce la ntrebarea: Fiind dat un cuvant, prezint
a el o anumita proprietate?
Definitia A.3 Vom considera problem
a de decizie o functie
P : {da, nu}

(A.2)

Definitia A.4 Limbajul asociat problemei P este


P 1 (da) = {w|w si P (w) = da}

(A.3)

Definitia A.5 Vom considera ca un algoritm este o functie


A : {da, nu, nedecidabil}

(A.4)

Definitia A.6 Limbajul L este acceptat de algoritmul A daca L = {w|w


si A(w) = da}.
Definitia A.7 Limbajul L este decis de algoritmul A dac
a w L : A(w) = da si
w
/ L : A(w) = nu.
Definitia A.8 Limbajul L este acceptat de algoritmul A n timp polinomial
daca L este acceptat de A si k N astfel nc
at pentru orice w L algoritmul A evalueaz
a
A(w) = da n timpul O(|w|k ).
Definitia A.9 Limbajul L este decis de algoritmul A n timp polinomial dac
a
k N astfel nc
at pentru orice w algoritmul A evalueaz
a A(w) = da, daca w L si
A(w) = nu, daca w
/ , n timpul O(|w|k ).
Definitia A.10 Clasa de complexitate P:
P = {L |A alg. a.. L e decis de A n timp polinomial}.
177

(A.5)

Daca P este o problema de decizie, atunci ea este rezolvabil


a n timp polinomial daca
1
limbajul L = P (da) satisface L P. Se noteaza aceasta P P.
De exemplu, problema DrumM IN D este rezolvabila n timp polinomial daca functia
de lungime (specificata n intrarea ei) prezinta numai valori nenegative. Daca se permit si
valori negative, atunci nu se cunoaste nici o demonstratie a apartenentei DrumM in D P
(si nici nu se crede ca ar exista una). De asemenea, N rCompus P s-a demonstrat destul
de recent, n 2002.
Observatia A.3
1. Daca notam
P = {L |A alg. a.. L este acceptat de A n timp polinomial},
se observa imediat ca P P0 si nu este dificil sa se arate si incluziunea inversa. Prin urmare
avem P = P0 .
2. Se verifica usor ca daca P P atunci si \ P P.

A.0.1

Verificare n timp polinomial

Un algoritm de verificare este o functie


A : {da, nu, nedecidabil}.
Pentru A(x, y), x reprezinta intrarea iar y este certificatul.
Limbajul L este verificat de algoritmul de verificare A daca
L = {w|w si y a.. A(w, y) = da}.
Definitia A.11 Clasa de complexitate NP:
N P = {L |A algoritm de verificare cu timp de lucru polinomial a.i.
L = {x |y , k N a.. |y| = O(|x|k ) si A(x, y) = da}}

(A.6)

Daca P este o problema de decizie, atunci ea este o problema (din) NP daca limbajul
L = P 1 (da) satisface L NP.
NP este mnemonic pentru Nedeterminist Polinomial.
Nu este bine sa asimilam NP cu nepolinomial deoarece
textP NP. Justificarea este imediata: Daca L P, atunci exista A : {da, nu, nedecidabil}
algoritm ce decide L n timp polinomial. Consideram A0 : {da, nu, nedecidabil},
satisfacand A0 (x, x) = A(x) pentru orice x . Se vede usor ca L este verificat de A0 n
timp polinomial.
Din definitiile de mai sus ar fi fost mai normal sa folosim notatia VP (verificabil polinomial ). Sintagma Nedeterminist Polinomial se justifica daca am considera algoritmi nedeterministi n care, dupa fiecare pas, este posibil sa se execute unul dintre pasii specificati
dintro multime finita de pasi succesori. Un astfel de algoritm accepta un cuvant de intrare
daca este posibila o executie care sa conduca la rezultatul da. Se poate arata ca aceasta
definitie a acceptarii nedeterministe este echivalenta cu cea de verificare data mai sus, n
care certificatul este utilizat pentru efectuarea alegerilor corecte ale passilor urmatori ai unei
executii a algoritmului nedeterminist.
NP noteaza clasa problemelor de decizie pentru care raspunsurile afirmative au certificate
care pot fi folosite pentru a demonstra succint (n timp polinomial) corectitudinea lor.
178

Intuitiv, NP este clasa tuturor problemelor de decizie pentru care se poate verifica un
raspuns pozitiv (da) rapid daca ni se da o solutie.
De exemplu, pentru problema M axCut D un raspuns afirmativ are drept certificat o
partitie (S , T ) a multimii varfurilor grafului (iar proprietatea ca suma lungimilor muchiilor
cu o extremitate n S si cealalta n T nu depaseste pragul k se face n timp polinomial).
Prin urmare M axCut D NP.
Daca am considera urmatoarea problema de decizie:
UnDrum Intrare:
G un graf, a, b varfuri ale lui G.
Intrebare: Exista un drum unic de la a la b n G?
Cum s-ar putea justifica un raspuns da la o problema UnDrum? Daca prezentam un
drum anume drept certificat, el nu ne asigura ca nu mai exista s altele. O demonstratie
succinta pare a nu exista. Prin urmare nu se cunoaste daca U nDrum NP.
Definitia A.12 Clasa de complexitate coNP:
co NP = {L | \ L NP}.
O problema de decizie P co NP daca
L = P 1 (da) co NP (echivalent, L = P 1 (nu) NP).
Exemplu de problema din co NP:
NeHam Intrare:
G un graf.
Intrebare: Este adevarat ca n G nu exista un circuit
care sa treaca exact o data prin fiecare varf al sau?
Observatia A.4 P NP co NP.

A.0.2

Reduceri polinomiale

Definitia A.13 Fie L1 , L2 . Spunem ca L1 se reduce polinomial la L2 , si notam


aceasta prin L1 L2 , daca exista f : o functie polinomial calculabil
a astfel nc
at
w : w L1 daca si numai daca f (w) L2 .
f se numeste functie de reducere si algoritmul polinomial F ce calculeaza f se numeste
algoritm de reducere polinomial
a.
Observatia A.5
1. Daca L P si L0 L, atunci L0 P.
Fie A un algoritm polinomial care decide L si F un algoritm de reducere polinomial
a a lui L0
0
0

0
la L. Atunci, A = A F este un algoritm polinomial care decide L . (x , A (x) = da
A(F (x)) = da F (x) L x L0 ; A0 este polinomial deoarece multimea polinoamelor
este nchis
a la operatia de compunere).
2. Relatia este tranzitiv
a: L1 L2 , L2 L3 L1 L3 .
Definitia A.14 Limbajul L este NPdificil (engl. NP-hard) daca L0 NP are loc
L0 L.
Definitia A.15 Limbajul L este NPcomplet daca L NP si L este NPdificil.
Terminologia se transfera pentru probleme de decizie.
Spunem ca problema de decizie P1 se reduce polinomial la problema de decizie P2 , si
notam P1 P2 , daca L1 = P11 (da) L2 = P21 (da).
179

Definitia A.16 Problema de decizie P este NPdificila daca


P 0 NP are loc P 0 P.
Definitia A.17 Problema de decizie P este NPcomplet
a daca
P NP si P este NPdificil
a.
Folosind observatia anterioara si faptul ca P NP rezulta urmatoarea teorema.
Teorema A.6 Daca P o problem
a oarecare NPcomplet
a satisface P P atunci P = NP.
Rezulta ca problemele NPcomplete formeaza o submultime a lui NP ce contine cele mai
dificile probleme. Daca gasim un algoritm polinomial pentru una dintre ele, putem construi
un algoritm polinomial pentru oricare alta problema din NP. Din pacate, desi nu exista o
demonstratie, oamenii cred ca, de fapt, aceste probleme nu admit algoritmi polinomiali de
rezolvare.
Se obisnuieste sa se spuna ca o problema de optimizare este NPdificil
a, daca problema
de decizie asociata este asa. Pentru a fi consistenti fata de definitiile precedente, vom spune
ca o problema oarecare P (nu neaparat de decizie) este NPdificila daca existenta unui
algoritm polinomial pentru P implica P = NP.
Sa prezentam un exemplu pentru clasa de complexitate a problemelor NPcomplete.
Primul om care a demonstrat existenta unei astfel de probleme este Cook, care n 1971 a
aratat ca SAT NP, unde
SAT Intrare:
U = {u1 , . . . , un } o multime finita de variabile booleene.
C = C1 C2 . . . Cm o formula n forma conjunctiva peste U :
Ci = vi1 vi2 . . . viki , i = 1, m unde
ij {1, . . . , n} astfel ncat vij = u sau vij = u .
Intrebare: Exista o atribuire t : U {A, F } astfel ncat t(C) = A?
Evaluarea lui t(C) se bazeaza pe formulele uzuale din calculul boolean:
A A = A, F A = F F = A F = F,
F F = F, F A = A F = A A = A,
F = A, A = F, (F ) = F, (A) = A,

(A.7)
(A.8)
(A.9)

si se poate face n timp polinomial.


3SAT este restrictia lui SAT n care fiecare clauza Ci are exact trei literali (ki = 3), un
literal vij fiind, asa cum este descris mai sus, o variabila sau negatia ei.
Se poate arata usor ca SAT 3SAT si astfel se obtine faptul ca 3SAT este NP
complet
a (apartenenta la NP este clara fiind o restrictie a lui SAT care apartine lui NP, iar
tranzitivitatea relatiei mpreuna cu teorema lui Cook termina demonstratia).
In concluzie, pentru a arata ca o problema de decizie P este NPcomplet
a se procedeaza
astfel:
1. Se arata ca L = P 1 (da) satisface L NP.
2. Se selecteaza un limbaj L0 despre care stim ca este NPcomplet.
3. Se ncearca construirea unui algoritm de reducere F de la L0 la L.
4. Se demonstreaza ca F este algoritm de reducere.
5. Se arata ca F este algoritm polinomial.
180

Appendix B
Liste. Stive. Cozi
Definitia B.1 Lista liniara reprezint
a o multime de elemente omogene (de acelasi tip) cu
urmatoarele caracteristici:
- elementele verifica o relatie liniara de succesiune, adica fiecare element are un singur succesor
si un singur predecesor (cu exceptia primului si ultimului element)
- fiecare element are o anumita pozitie n lista
Un tip special de lista este lista vida (far
a nici un element).

B.1

Stiva

Fig. B.1: Structura de date stiva

stive, s.f. Multime de obiecte de acelasi fel (si cu aceleasi dimensiuni),


STIVA,
asezate ordonat unele peste altele, pentru a forma o gramada; gramada de obiecte
astfel formata. Din gr. stivas. (sursa DEX98[43])
Definitia B.2 Stiva este o structura abstract
a de date1 organizat
a dupa principiul LIFO
(Last In, First Out - ultimul venit, primul iesit). Prin stiva (n limba engleza stack) se ntelege
un tip particular de lista liniara ce prezint
a proprietatea ca toate operatiile de introducere si
extragere ale elementelor din stiva se fac la un singur cap
at al listei, numit varful stivei.
1

Un tip abstract de date este definit printro multime de valori si operatiile asociate definite peste aceasta
multime de valori. Operatiile trebuie sa fie binedefinite si sa fie independente de orice implementare particulara.

181

Pe de alta parte, stiva este o structura de date de tip container deoarece ea depoziteaza
elemente de un anumit tip. Pentru a putea sa operam asupra unei colectii de elemente
pastrate ntro stiva se definesc urmatoarele operatii, n afara de operatiile fundamentale
push si pop:
push(x:object) adauga obiectul x n varful stivei.
pop():object elimina si ntoarce obiectul din varful stivei; daca stiva este goala avem
o situatie ce genereaza o exceptie.
peek():object ntoarce valoarea obiectului din varful stivei fara al extrage.
size():integer ntoarce numarul de obiecte din stiva.
isEmpty():boolean ntoarce true daca stiva nu contine nici un obiect.
isFull():

boolean ntoarce true daca stiva este plina.

init(capacitate:integer) initializeaza stiva.


O stiva poate fi implementata ntrun limbaj de programare prin intermediul unui tablou
de elemente sau prin intermediul unei liste liniare simplu sau dublu nlantuita.
Exemplul B.1 Fie functia MannaPnueli:
(
x1
f (x) =
f (f (x + 2))

, daca x 12
,n rest

sectiunea ?? a fost prezentat un algoritm recursiv de calcul pentru aceast


In
a functie. Vom utiliza structura de date stiva ntrun algoritm iterativ pentru calculul valorilor aceleiasi functii
(vezi algoritmul 66). Stiva va avea rolul de a pastra nivelul de imbricare (numarul de paranteze deschise din formula argumentului functiei).
1: S 0 (0

adauga o parantez
a pe stiva, iar
1: S ch

extrage o parantez
a din stiva.
Exemplul B.2 Se da o expresie aritmetica ce contine operanzi, operatori si paranteze. Se
doreste sa se verifice daca aceast
a expresie este parantezat
a corect: n orice moment al pars
arii
expresiei numarul de paranteze deschise este mai mare sau egal cu numarul de paranteze
nchise, pentru ca la final cele doua valori sa fie egale.
algoritmul 67 vom utiliza o funtie getN extT oken() ce ntoarce valoarea urmatorului
In
element atomic (token) al expresiei ce este analizata.
Se parcurge secventa de token-uri: daca valoarea token-ului curent este parantez
a deschisa
0 0
0 0
( ( ) atunci se salveaz
a pe stiva, daca valoarea este parantez
a nchis
a ( ) ) atunci se extrage
de pe stiva o parantez
a deschisa, iar daca are alta valoare se ignora.
Dac
a se doreste extragerea unui element de pe stiva (a unei paranteze deschise) iar aceasta
este vida nseamn
a ca numarul de paranteze nchise este mai mare la un moment dat dec
at
numarul de paranteze deschise, ceea ce nu este bine din punctul de vedere al corectitudinii
expresiei. Astfel expresia aritmetica (2 (6 + 7)/(4 6 2))/(19 + 1/(7 3)). este corecta
din acest punct de vedere, pe cand expresia ((7 4/3)/2)) ((4 + 5)) 2 7). nu este corect
a.
(Pentru a delimita n mod clar o expresie aritmetica, presupunem ca aceasta se termina cu
un punct - .)
182

Algorithm 65 Algoritmi pentru operatii cu stiva


1:
2:
3:
4:
5:
6:
7:
8:
9:
10:
11:
12:
13:
14:
15:
16:
17:
18:
19:
20:
21:
22:
23:
24:
25:
26:
27:
28:
29:
30:
31:
32:
33:
34:
35:
36:
37:
38:
39:
40:
41:
42:

procedure Init(S, size)


S.capacitate size
S.top 0
end procedure
function isEmpty(S)
if (s.top = 0) then
return true
else
return f alse
end if
end function
function isFull(S)
if (s.top < s.capacitate) then
return f alse
else
return true
end if
end function
procedure Push(S, element)
if (isF ull(S)) then
s.top s.top + 1
s.stivas.top = element
end if
end procedure
function pop(S)
value N U LL
if (isEmpty(S)) then
value s.stivas.top
s.top s.top 1
end if
return value
end function
function peek(S)
value N U LL
if (isEmpty(S)) then
value s.stivas.top
end if
return value
end function
function size(S)
return s.top
end function

B.2

Coada

Definitia B.3 Coada (vezi figura B.2) este o structura abstract


a de date organizat
a dupa
principiul F IF O (First In, First Out - primul venit, primul iesit). O coad
a (n limba engleza
queue) este un tip particular de lista liniara pentru care toate inserarile se realizeaz
a la unul
din capetele listei, si toate extragerile la cel
alalt cap
at.
183

Algorithm 66 Algoritm de calcul al valorilor functiei MannaPnueli


1: function MannaIterativ(x)
2:
call init(S)
3:
call push(S, 0 (0 )
4:
while (isEmpty(S) = f alse) do
5:
call pop(S)
6:
if (x 12) then
7:
xx1
8:
else
9:
call push(S, 0 (0 )
10:
call push(S, 0 (0 )
11:
xx+2
12:
end if
13:
end while
14:
return x
15: end function

Fig. B.2: Structura de date coada

O coada completa (dequeue) este o lista liniara n care operatiile de inserare si stergere
sunt realizate la ambele capete ale listei.
O coada simpla se poate implementa prin intermediul unei liste liniare simplu nlantuita,
fiind nevoie de doua variabile referinta pentru a indica elementul din fata (head ), respectiv
elementul din spate (rear ).
De asemenea, asemanator cu stiva, coada este o structura de date de tip container deoarece
ea pastreaza elemente de acelasi tip. Definim urmatoarele operatii pentru a manipula elementele unei colectii pastrata ntro coada:
enqueue(x:object) insereaza obiectul x la sfarsitul cozii.
dequeue():

object ntoarce primul element din coada si l elimina.

front():object ntoarce valoarea elementului aflat la nceputul cozii fara al extrage


propriuzis.
size():

integer ntoarce numarul de elemente aflate n coada la un moment dat.

isEmpty():boolean ntoarce true daca coada nu contine nici un obiect.


isFull():

boolean ntoarce true daca coada este plina.

init(capacitate:integer) initializeaza coada.

184

Algorithm 67 Algoritm pentru verificarea corectitudinii unei expresii


1:
2:
3:
4:
5:
6:
7:
8:
9:
10:
11:
12:
13:
14:
15:
16:
17:
18:
19:
20:
21:
22:

call init(S)
token getN extT oken()
while (token 6=0 .0 ) do
if (token =0 (0 ) then
call push(S, 0 (0 )
else
if (token =0 )0 ) then
if (isEmpty(S) = true) then
call pop(S)
else
Output Expresie incorecta: #paranteze deschise #paranteze inchise
return
end if
end if
end if
token getN extT oken()
end while
if (isEmpty(S) = true) then
Output Parantezele deschise se inchid corect cu parantezele inchise
else
Output Expresie incorecta: #paranteze deschise #paranteze inchise
end if

Fig. B.3: Structura de date coada implementata static - pozitia primului element este fixa, si pozitia ultimului
element este variabila.

B.2.1

Coada - implementare static


a

Pentru implementarea statica a structurii de coada vom utiliza urmatoarele variabile (vezi
figura B.3 si algoritmul 68):
capacitate pastreaza numarul de elemente maxim ce poate fi stocat n aceasta structura.
last indicele ultimului element adaugat n coada.
coada vector alocat static cu un anumit numar de elemente.
Operatiile enQueue(), f ront(), isF ull() si isEmpty() au o complexitate O(1), iar operatia
deQueue() are o complexitate de O(n) deoarece la fiecare extragere a unui element ntregul
vector coada este deplasat spre stanga cu o pozitie (vezi algoritmul 68).

185

Algorithm 68 Algoritmi pentru operatii cu o coada implementata static


1:
2:
3:
4:
5:
6:
7:
8:
9:
10:
11:
12:
13:
14:
15:
16:
17:
18:
19:
20:
21:
22:
23:
24:
25:
26:
27:
28:
29:
30:
31:
32:
33:
34:
35:
36:
37:
38:
39:
40:
41:
42:
43:
44:
45:

procedure Init(C, size)


c.capacitate size
c.last 0
end procedure
function isEmpty(C)
if (c.last = 0) then
return true
else
return f alse
end if
end function
function isFull(C)
if (c.last < c.capacitate) then
return f alse
else
return true
end if
end function
procedure enQueue(C, element)
if (isF ull(C)) then
c.last c.last + 1
c.coadac.last = element
end if
end procedure
function deQueue(C)
value N U LL
if (isEmpty(C)) then
value c.coada1
for i = 1, c.last 1 do
c.coadai c.coadai+1
end for
s.last c.last 1
end if
return value
end function
function front(C)
value N U LL
if (isEmpty(C)) then
value c.coada1
end if
return value
end function
function size(C)
return c.last
end function

B.2.2

Coada circular
a - implementare static
a

Daca dorim ca si operatia de extragere a primului element din coada, deQueue(), sa aiba o
complexitate O(1), vom utiliza doua variabile, f irst si last, unde prima indica varful cozii iar
186

cea dea doua spatele cozii. Se observa ca fata si spatele cozii migreaz
a spre dreapta, dupa
un numar de operatii de inserare si extragere, lasand un numar de locasuri libere n dreapta.
Ideea naturala este de a utiliza si celulele ramase libere din partea dreapta a tabloului: atunci
cand ajunge la limita din stanga, last continua cu prima valoare din partea stanga.
Aceasta varianta de implementare induce o problema noua: trebuie sa facem diferenta
dintre o coada plina (spatele cozii ajunge din urma capul cozii) si o coada goala (atunci cand
capul cozii ajunge din urma spatele cozii). Exista mai multe metode pentru rezolvarea acestei
situatii: se pastreaza tot timpul o celula neocupata, se utilizeaza nca o variabila ce indica
starea cozii (plina, goala sau nici una dintre aceste doua stari), se utilizeaza doua variabile
ce pastreaza tot timpul numarul de inserari si numarul de extrageri si cu ajutorul acestora
se calculeaza numarul curent de elemente aflate n coada.
Vom implementa cea dea doua tehnica descrisa mai sus, structura de date bazanduse
pe urmatoarele variabile:
f irst indicele primului element din coada.
last indicele ultimului element adaugat n coada.
empty variabila booleana ce primeste valoarea true atunci cand coada nu contine nici
un element.
coada vector alocat static cu M AX elemente.
La nceput, atunci cand coada nu contine nici un element, vom avea f irst = 0 si last =
M AX 1 (M AX - numarul maxim de elemente).

B.3

Liste

Conform definitiei B.1, o lista este o colectie de elemente ntre care este stabilita o anumita
ordine. Avem mai multe forme sub care poate fi ntalnita o lista:
list
a liniara simplu nl
antuit
a fiecare nod contine o referinta catre elementul urmator
(next). Ultimul element nu are element succesor asa cum se poate observa n figura
B.4.

Fig. B.4: Structura generala a unei liste liniare simplu nlantuita

list
a circular
a simplu nl
antuita elementul succesor al ultimului element al listei este
primul element (vezi figura B.5).

Fig. B.5: Structura generala a unei liste circulare simplu nlantuita

187

Algorithm 69 Algoritmi pentru operatii cu o coada circulara implementata static


1:
2:
3:
4:
5:
6:
7:
8:
9:
10:
11:
12:
13:
14:
15:
16:
17:
18:
19:
20:
21:
22:
23:
24:
25:
26:
27:
28:
29:
30:
31:
32:
33:
34:
35:
36:
37:
38:
39:
40:
41:
42:
43:
44:
45:

procedure Init(C, size)


c.f irst 0
c.last M AX 1
c.vida true
end procedure
function next(k)
return (k + 1) mod M AX
end function
function isEmpty(C)
return c.vida
end function
function isFull(C)
if ((next(c.last) = c.f irst) (c.vida = f alse)) then
return true
else
return f alse
end if
end function
procedure enQueue(C, element)
if (isF ull(C)) then
c.last next(c.last)
c.coadac.last = element
if (c.vida = true) then
c.vida f alse
end if
end if
end procedure
function deQueue(C)
value N U LL
if (isEmpty(C)) then
value c.coadac.f irst
c.f irst next(c.f irst)
if (c.f irst = next(c.last)) then
c.vida true
end if
end if
return value
end function
function front(C)
value N U LL
if (isEmpty(C)) then
value c.coadac.f irst
end if
return value
end function

list
a liniara dublu nl
antuit
a fiecare nod contine o referinta catre elementul precedent
(prec) si catre elementul urmator (next). Primul element nu are element precedent, iar
ultimul element nu are element succesor (vezi figura B.6).
188

Fig. B.6: Structura generala a unei liste liniare dublu nlantuita

list
a circular
a dublu nl
antuit
a elementul succesor al ultimului element al listei este
primul element, iar elementul predecesor al primului element al listei devine ultimul
element al listei (vezi figura B.7).

Fig. B.7: Structura generala a unei liste circulare dublu nlantuita

189

Appendix C
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).
Algorithm 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
190

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 produsului 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 deplasandune 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 C.1 Enuntul problemei: Se cere sa 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 obtinut
a n vectorul x = (x1 , x2 , . . . , xn ) A1 A2 . . . An .
La pasul k se ncearc
a 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 repet
a. 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 C.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
191

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 urmator, 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
urma verificarii
Ajunsi la ultimul pas, k = 4, se verifica valorile 1, 2, 3 si 4 pentru x4 . In
conditiilor de continuare pentru x4 = 4, se urmareste trecerea la pasul k = k+1 = 5. Deoarece
suntem la ultimul element al vectorului x (linia 21), se obtine prima solutie, ce se si afiseaz
a:
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 urmatoarea valoare din domeniul de valori neverificat
a
este x3 = 4 (linia 15). Solutia partiala respect
a conditiile de continuare (linia 16), astfel nc
at
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, conduc
and la o solutie finala (linia 21): 1 2 4 3 Algoritmul se continu
a n acelasi
mod pan
a 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);
}

192

Algorithm 71 Algoritm pentru generare permutari (varianta backtracking)


Input: n - numarul 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.
*/

193

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 C.3 Enuntul problemei: Sa se genereze combin


ari de n luate cate m (0
194

m n).
Rezolvare: Spatiul solutiilor este A = m
ia va
i=1 Ai , Ai = {1, 2, . . . , n}, |Ai | = n. Solut
fi obtinut
a n vectorul x = (x1 , x2 , . . . , xm ) A1 A2 . . . Am (vezi algoritmul 72).
La pasul k se ncearc
a atribuirea unui alt element din multimea Ak lui xk . Conditia de
continuare se refer
a la proprietatea ca elementele vectorului solutie trebuie sa respecte relatia
x1 < x2 < . . . < xk . Acest lucru se obtine foarte usor prin urmatorul 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)

Algorithm 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

continuare prezent
In
am o implementare a algoritmului 72 n limbajul C:
195

#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++;

196

a[i] = a[i - 1];


}
else
//altfel revenim la un element anterior si alegem o alta valoare
i--;
}
}
void main(void) {
readInput();
run();
}

Exemplul C.4 Problema damelor


Enuntul problemei: Pe o tabla de sah cu n linii si n coloane (n n patrate), sa se
pozitioneze n dame astfel nc
at 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 daca:
i = k sau j = l sau |i k| = |j l|
Prima observatie care se poate deduce se refer
a la faptul ca doua dame nu trebuie sa se
afle pe aceeasi linie sau pe aceeasi coloan
a.
Not
am cu V = {1, 2, . . . , n} multimea celor n coloane ale tablei de sah. Vectorul solutiei
rezultat X = (x1 , x2 , . . . , xn ) V ... V are urmatoarea semnificatie: componenta i a
vectorului (i = 1, n) reprezint
a linia i de pe tabla de sah. Valoarea xi a acestei componente
reprezint
a coloana pe care se aseaz
a 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 urmatoare (xk + 1).
Initial dama a k-a este pozitionat
a n afara tablei (xk = 0
/ V ). O dama poate fi asezat
a pe
o alta coloan
a 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 diagonala).
Dac
a 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, corespunz


ator pozition
arii damei k + 1. In caz contrar, dama k nu
poate fi asezat
a pe tabla si va trebui sa relu
am pozitionarea damei k 1.
Exemplul C.5 Problema color
arii hartilor
Enuntul problemei: Sa se coloreze o harta reprezent
and n t
ari folosind m culori
etichetate 1, . . . , m.
Rezolvare: Datele necesare pentru descrierea hartii vor fi reprezentate de o matrice
A = (aij ), i, j = 1, n, ale carei elemente au urmatoarea semnificatie:
(
1 , dac
a tara i are frontier
a comun
a cu tara j
aij =
0 , n caz contrar
Matricea A Mnn este simetrica si elementele de pe diagonala principal
a sunt nule:
aij = aji si aii = 0.
197

Algorithm 73 Algoritm Problema damelor


Input: n - numarul 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:
ii1
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 urmatoarea semnificatie:
tara i are repartizat
a culoarea xi C, i = 1, n. La nceput, fiec
arei t
ari i se va atribui valoarea
0.
La pasul k ncerc
am sa atribuim o noua 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.
Dac
a 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
t
arii k nu i se mai poate atribui o alta culoare si ne ntoarcem la pasul k 1, corespunz
ator
alegerii unei noi culori pentru tara k 1. Algoritmul se termina atunci cand nu mai exista
nici o culoare neverificat
a pentru tara 1 (vezi algoritmul 74).
198

Algorithm 74 Algoritm Problema colorarii hartilor

atate a t
arilor
A - matricea de adiacenta/de vecin
Input: n - numarul de tari

m - numarul 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 C.6 Enuntul problemei: Se da o suma s si n tipuri de monede avand valorile


a1 , a2 , . . . , an lei. Realizati un algoritm care sa determine o modalitate de plata 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

199

int
int
int
int
int

limit[NN];
val[NN];
taken[NN];
minim;
keep[NN];

//limit[i] numarul maxim de monede de tipul val[i]


//val[i] valoarea unei monede
//cate monede de valoare val[i] au fost luate
//numarul minim de monede cu care se poate face plata
//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
* suma partiala sa
* @param k - pasul
*/
int canContinue(int
int i, suma_tmp;

sunt satisfacute conditiile de continuare:


nu depaseasca valoarea totala de platit.
curent
k) {

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;

200

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;

201

}
else
//altfel revenim la un element anterior si alegem o alta valoare
i--;
}
}
void main(void) {
readInput();
run();
print();
}

202

Bibliografie
[1] W. Ackermann, Zum Hilbertschen Aufbau der reellen Zahlen, Journal Mathematische
Annalen, Springer, vol. 99:1, pp. 118133, 1928.
[2] 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.
[3] A. V. Aho, J. E. Hopcroft, J. D. Ulmann, Data Structures and algorithms, AddisonWesley, 1983.
[4] A. V. Aho, J. D. Ulmann, Foundation of Computer Science, Computer Science Press,
1992.
[5] R. Andonie, I. Garbacea, Algoritmi fundamentali, o perspectiv
a C++, Editura Libris,
1995.
[6] A. W. Appel, G. J. Jacobson, The worlds fastest Scrabble program, Communications of
the ACM, vol. 31:5, pp. 572578, 1988.
[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 informatica. 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] Gh. Barbu, I. Vaduva, M. Bolosteanu, Bazele informaticii, Editura Tehnica, Bucuresti,
1997.
[13] T. Balanescu, S. Gavrila, H. Georgescu, M. Gheorghe, L. Sofonea, I. Vaduva, Pascal si
Turbo Pascal, Editura. Tehnica, Bucuresti, 1992.
[14] P. Bazavan, Elemente de Teoria Algoritmilor, Editura Sitech, Craiova, 2007.
[15] R. Bellman, Dynamic Programming, Princeton University Press, 1957, Dover paperback
edition, 2003.
203

[16] R. Bellman, On a routing problem, Quarterly Applied Mathematics, XVI(1), pp. 87-90,
1958.
[17] M. A. Bender, M. Farach-Colton, The LCA problem revisited, Proceedings of the 4th
Latin American Symposium on Theoretical Informatics, LNCS, vol. 1776, SpringerVerlag, pp. 88-94, 2000.
[18] J. Bentley, R. Sedgewick, Fast Algorithms for Sorting and Searching Strings, Proceedings
of the 8th Annual ACM-SIAM Symposium on Discrete Algorithms, 1997.
[19] J. Bentley, R. Sedgewick, Ternary Search Trees, Dr. Dobbs Journal, 1998.
[20] J. Bentley, Programming Pearls, 2nd Edition, Addison-Wesley, 2000.
[21] C. Bereanu, Algoritmica Grafurilor, Editura Sitech, Craiova, 2006.
[22] 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.
[23] O. Berkman, U. Vishkin, Recursive Star-Tree Parallel Data Structure, SIAM Journal on
Computing, vol.22(2), pp.221-242, 1993.
[24] M. de Berg, M. van Kreveld, M. Overmars, O. Schwartzkopf, Computational Geometry:
Algorithms and Applications, Springer, 2000.
[25] A. Binstock, J. Rex, Practical algorithms for programmers, 8th Edition, Addison-Wesley,
1999.
[26] O. Boruvka, O jistem problemu minimalnm (About a certain minimal problem), Acta
Societ. Scient. Natur. Moravicae, 3, pp. 37-58, 1926.
[27] G. Brassard, P. Bratley, Algorithmics. Theory and Practice, Prentice Hall, 1988.
[28] R. P. Brent, An improved Monte Carlo factorization algorithm, BIT Numerical Mathematics, Springer, vol. 20(2), pp. 176184, 1980.
[29] D. D. Burdescu, Analiza complexit
atii algoritmilor, Editura Albastra, ClujNapoca,
1998.
[30] D. D. Burdescu, M. Brezovan, M. Cosulschi, Structuri de date arborescente cu aplicatii
n Pascal si C, Reprografia Universitatii din Craiova, 2000.
[31] D. D. Burdescu, Liste, arbori, grafuri, Editura Sitech, Craiova, 2005.
[32] B. Chazelle, A minimum spanning tree algorithm with inverse-Ackerman type complexity,
J. ACM, 47, pp. 1028-1047, 2000.
[33] D. Cherition, R. E. Tarjan, Finding minimum spanning trees, SIAM Journal on Computing, vol. 5, pp. 724-741, 1976.
[34] J. Cheriyan, K. Mehlhorn, Algorithms for dense graphs and networks on the random
access computer, Algorithmica, vol. 15, pp. 521-549, 1996.

204

[35] T. H. Cormen, C. E. Leiserson, R. L. Rivest, Introducere n Algoritmi, Computer Libris


Agora, ClujNapoca, 1999.
[36] M. Cosulschi, Asupra unor probleme clasice de permutari, ELSE Software, nr. 1213,
pp. 131136, 1999.
[37] M. Cosulschi, M. Gabroveanu, Algoritmi o abordare pragmatic
a, Editia a 2-a, Universitaria, Craiova, 2004.
[38] M. Cosulschi, M. Gabroveanu, N. Constantinescu, Usage of Advanced Data Structure for
Improving Efficiency for Large (n,m) Permutations Inspired from The Josephus Problem,
Romanian Journal of Information Science and Technology (ROMJIST), vol. 12(1), pp.
1324, 2009.
[39] S. Dasgupta, C. H. Papadimitriou, U. V. Vazirani, Algorithms, McGrawHill, 2006.
[40] A. L. Delcher, S. Kasif, R. D. Fleischmann, J. Paterson, O. White, S. L. Salzberg,
Alignment of whole genomes, Nucleic Acid Research, 27, pp. 23692376, 1999.
[41] C. Demetrescu, G. F. Italiano, Engineering Shortest Path Algorithms, WEA, Springer,
LNCS 3059, pp. 191198, 2004.
[42] R. B. Dial, Algorithm 360: shortest-path forest with topological ordering [H], Communications of the ACM, vol. 12:11, pp. 632633, 1969.
[43] Dictionarul explicativ al limbii rom
ane, Academia Romana, Institutul de Lingvistica
Iorgu Iordan, Editura Univers Enciclopedic, 1998.
[44] E. W. Dijkstra, A note on two problems in connections with graphs, Numerische Mathematik, 1, pp. 269-271, 1959.
[45] J. Farey, On a Curious Property of Vulgar Fractions, London, Edinburgh and Dublin
Phil. Mag. 47, 385, 1816.
[46] 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.
[47] P. M. Fenwick, A new data structure for cumulative frequency tables, Software-Practice
and Experience, vol.24, no.3, pp. 327-336, 1994.
[48] D. E. Ferguson, Fibonaccian searching, Communications of the ACM, vol. 3(12), pp.
648, 1960.
[49] Robert W. Floyd, Algorithm 245 - Treesort 3, Communications of the ACM, vol. 7(12),
pp. 701, 1964.
[50] Robert W. Floyd, Nondeterministic algorithms, Journal of the ACM (JACM), vol. 14(4),
pp. 636644, 1967.
[51] L. R. Ford, Network flow theory, Technical Report P-923, RAND, Santa Monica, CA,
1956.
[52] C. L. Foster, The Design and Analysis of Algorithms, Springer Verlag, 1992.
205

[53] J.-C. Fournier, Graph Theory and Applications, Wiley-Blackwell, 2009.


[54] E. Fredkin, Trie Memory, Communications of the ACM, 3:(9), pp. 490, 1960.
[55] M. Fredman, R. Sedgewick, R. Sleator, R. Tarjan, The pairing heap: A new form of
self-adjusting heap, Algorithmica, 1, pp. 111129, 1986.
[56] M.L. Fredman, R.E. Tarjan, Fibonacci heaps and their use in improved network optimization algorithms, Journal of the ACM, vol. 34, pp. 596-615, 1987.
[57] 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.
[58] H. N. Gabow, J. L. Bentley, R. E. Tarjan, Scaling and related techniques for geometry
problems, Proceedings of the 16th ACM Symposium on Theory of Computing (STOC),
pp. 135-143, 1984.
[59] H. N. Gabow, Path-based depth-first search for strong and biconnected components, Information Processing Letters, pp. 107-114, 2000.
[60] Colectia Gazeta de Informatica - GInfo.
[61] C. Giumale, L. Negreanu, S. Calinoiu, Proiectarea si analiza algoritmilor. Algoritmi de
sortare, Editura All, Bucuresti, 1997.
[62] C. Giumale, Introducere n analiza algoritmilor, Editura Polirom, Iasi, 2004.
[63] R. L. Graham, D. E. Knuth, O. Patashnik, Concrete Mathematics: A Foundation for
Computer Science, 2nd Edition, Addison-Wesley, 1994.
[64] D. Gries, The Science of Programming, Springer Verlag, Heidelberg, NewYork, 1981.
[65] 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.
[66] D. Gusfield, Algorithms on Strings, Trees, and Sequences, Cambridge University Press,
1997.
[67] G. H. Hardy, E. M. Wright, An Introduction to the Theory of Numbers, 5th Edition,
Oxford University Press, 1979.
[68] D. Harel, R. E. Tarjan, Fast algorithms for finding nearest common ancestors, SIAM
Journal on Computing, vol. 13(2), pp. 338-355, 1984.
[69] I. N. Herstein, I. Kaplansky, Matters mathematical, 2nd Edition, Chelsea Publishing
Company, 1978.
[70] T. H. Hibbard, An Empirical Study of Minimal Storage Sorting, Communications of the
ACM, vol. 6, pp. 206213, 1963.
[71] C. A. R. Hoare, Quicksort, Computer Journal, 5, pp.1015, 1962.
[72] E. Horowitz, Fundamentals of Programming Languages, Springer Verlag, 1983.
206

[73] E. Horowitz, Fundamentals of Data Structures in C++, Computer Science Press, 1995.
[74] 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.
[75] D. A. Huffman, A Method for the Construction of Minimum-Redundancy Codes, Proceedings of the I.R.E., 1952, pp. 1098-1102.
[76] C. Ionescu, I. Zsako, Structuri arborescente, Editura Tehnica, Bucuresti, 1990.
[77] V. Jarnk, O jistem problemu minimalnm (About a certain minimal problem), Prace
Moravske Prrodovedecke Spolecnosti, 6, pp. 5763, 1930.
[78] D. Jungnickel, Graphs, Networks and Algorithms, Algorithms and Computation in Mathematics, vol. 5, 3rd Edition, Springer, 2008.
[79] A. B. Kahn, Topological sorting of large networks, Communications of the ACM, vol.
5(11), pp. 558-562, 1962.
[80] A. Karatsuba, Y. Ofman, Multiplication of multiple numbers by mean of automata,
Dokadly Akad. Nauk SSSR 145, no. 2, 1962.
[81] A. A. Karatsuba, The complexity of computations, Proc. Steklov Inst. Math., Vol. 211,
pp. 169183, 1995.
[82] D. Karger, P. Klein, R. Tarjan, A randomized linear-time algorithm to find minimum
spanning trees, Journal of ACM, 42, pp. 321328, 1995.
[83] B. W. Kernigham, D. M. Ritchie, The C Programming Language, 2nd Edition, Prentice
Hall, 1988.
[84] B. W. Kernigham, R. Pike, The Practice of Programming, Addison-Wesley, 1999.
[85] D. C. Kozen, The Design and Analysis of Algorithms. Texts and Monographs in Computer Science, Springer, 1993.
[86] J. Kleinberg, E. Tardos, Algorithm Design, Addison-Wesley, 2005.
[87] D. E. Knuth, Arta program
arii calculatoarelor, vol. 1 Algoritmi fundamentali, Teora,
Bucuresti, 1999.
[88] D. E. Knuth, Arta program
arii calculatoarelor, vol. 2 Algoritmi seminumerici, Teora,
Bucuresti, 2000.
[89] D. E. Knuth, Arta program
arii calculatoarelor, vol. 3 Sortare si Cautare, Teora, Bucuresti, 2001.
[90] 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.
[91] C. Levcopoulos, O. Petersson, Heapsort - Adapted for Presorted Files, in Proceedings of
the Workshop on Algorithms and Data Structures, LNCS, vol. 382, Springer-Verlag, pp.
499-509, 1989.
207

[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 Enciclopedica, Bucuresti, 1986.
[94] A. M. Lloyd, An O(n log m) algorithm for the Josephus problem, Journal of Algorithms,
vol. 4, no. 3, 1983.
[95] Z. Manna, A. Pnueli, The validity problem of the 91-function, Stanford Artificial Intelligence Project, Memo No. 68, 1968.
[96] Z. Manna, A. Pnueli, Formalization of properties of recursively defined functions, in
Proceedings of the first annual ACM symposium on Theory of computing, pp. 201210,
1969.
[97] Z. Manna, Mathematical Theory of Computation, New York, McGraw-Hill, 1974.
[98] U. Manber, Introduction to algorithms, AddisonWesley, Reading, Massachusets, 1989.
[99] J. J. McConnell, Analysis of algorithms: An active learning approach, Jones and Bartlett,
2001.
[100] K. Mehlhorn, Data Structures and Algorithms:
Completeness, Springer Verlag, 1984.

Graph Algorithms and NP-

[101] V. Mitrana, Provocarea algoritmilor, Editura Agni, Bucuresti, 1994.


[102] M. Mocanu, G. Marian, C. Badica, C. Badica, 333 probleme de programare, Teora,
Bucuresti, 1994.
[103] E. F. Moore, The shortest path through a maze, in Proceedings of International Symposium on the Theory of Switching, Part II, pp. 285-292, 1959. (prezentat la simpozion
la Universitatea Harvard in aprilie 1957)
[104] B. Moret, H. Shapiro, Algorithms from P to NP. vol I Design and Efficiency, Benjamin/Cummings, Redwood City, CA, 1991.
[105] G. Nivasch, Cycle detection using a stack, Information Processing Letters, vol. 90(3),
pp. 135140, 2004.
[106] I. Odagescu, F. Furtuna, Metode si tehnici de programare, Computer Libris Agora,
ClujNapoca, 1998.
[107] Y. Perl, A. Itai, H. Avni, Interpolation search-a log logN search, CACM, vol 21(7), pp.
550553, 1978.
[108] S. Pettie, A faster all-pairs shortest path algorithm for real-weighted sparse graphs, in
Proceedings of 29th International Colloquium on Automata, Languages, and Programming (ICALP02), LNCS Vol. 2380, pp. 85-97, 2002.
[109] 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.
208

[110] S. Pettie, V. Ramachandran, An optimal minimum spanning tree algorithm, Journal of


ACM, 49:1634, 2002.
[111] V. R. Pratt, Shellsort and Sorting Networks, Garland Publishing, New York, 1979.
[112] M. Preparata, M. Shamos, Computational Geometry: An Introduction, Texts and
Monographs in Computer Science, Springer, 1993.
[113] R. C. Prim, Shortest connection networks and some generalizations, Bell System Technical Journal, 36, pp. 1389-1401, 1957.
[114] D. Rancea, Limbajul Pascal. Algoritmi fundamentali, Computer Libris Agora, Cluj
Napoca, 1999.
[115] B. Schieber, U. Vishkin, On finding lowest common ancestors: Simplification and parallelization, SIAM J. Comput., vol. 17, pp. 1253-1262, 1988.
[116] A. Schonhage, V. Strassen, Schnelle Multiplikation grosser Zahlen, Computing, 7, pp.
281-292, 1971.
[117] R. Sedgewick, Analysis of Shellsort and Related Algorithms, LNCS, vol. 1136, pp. 111,
1996.
[118] R. Sedgewick, Algorithms in C, 2nd Edition, Addison-Wesley, 1998.
[119] R. Seidel, C. Aragon, Randomized Search Trees, Algorithmica, vol. 16, pp. 464497,
1996.
[120] M. Sipser, Introduction to the Theory of Computation, PWS Publishing Co., 1997.
[121] H. Sharp, Cardinality of finite topologies, J. Combinatorial Theory, vol. 5, pp.82-86,
1968.
[122] B. Shneiderman, Jump Searching: A Fast Sequential Search Technique, CACM, vol.
21(10), pp. 831834, 1978.
[123] S. Skiena, Josephus Problem., #1.4.3 in Implementing Discrete Mathematics: Combinatorics and Graph Theory with Mathematica, Addison-Wesley, Reading, MA, vol. 4,
no. 3, 1990.
[124] S. Skiena, The Algorithm Design Manual, 2nd Edition, Springer, 2008.
[125] L. D. Serbanati, Limbaje de programare si compilatoare, Editura Academiei, Bucuresti,
1987.
[126] M. Sharir, A strong-connectivity algorithm and its applications in data fow analysis,
Computers and Mathematics with Applications, vol. 7(1), pp. 6772, 1981.
[127] D.L. Shell, A high-speed sorting procedure, Communications of the ACM, vol. 2(7), pp.
30-32, 1959.
[128] J. Stasko, J. Vitter, Pairing heaps: Experiments and analysis, Communications of the
ACM, vol. 30(3), pp. 234-249, 1987.

209

[129] V. Strassen, Gaussian elimination is not optimal, Numerische Mathematik, vol. 14, pp.
354-356, 1969.
[130] T. Takaoka, Theory of 2-3 heaps, Discrete Applied Mathematics, Vol. 126(1), 5th Annual International Computing and Combinatories Conference (COCOON99), pp. 115
128, 2003.
[131] R. Tarjan, Depthfirst search and linear graph algorithms, SIAM Journal on Computing,
vol. 1(2), pp. 146160, 1972.
[132] R. E. Tarjan, Edge-disjoint spanning trees and depth-first search, Algorithmica, vol.
6(2), pp. 171-185, 1976.
[133] R. E. Tarjan, Applications of path compression on balanced trees, Journal of the ACM,
vol. 26(4), pp. 690-715, 1979.
[134] I. Tomescu, Grafuri si programare liniara, Ed. Didactica si Pedagogica, Bucuresti, 1975.
[135] S. Tudor, Tehnici de programare, L&S Infomat, Bucuresti, 1996.
[136] I. Tutunea, Algoritmi, logic
a si programare, Reprografia Universitatii din Craiova, 1993.
[137] I. Tutunea, S. Pescarus, Algoritmi si programe Pascal. (Culegere de probleme), Reprografia Universitatii din Craiova, 1994.
[138] J. Vuillemin, A data structure for manipulating priority queues, Communications of
the ACM, vol. 21:4, pp. 309315, 1978.
[139] J. Vuillemin, A unifying look at data structures, Communications of the ACM, vol.
23:4, pp. 229-239, 1980.
[140] M. A. Weiss, Data Structures and Algorithm Analysis, Benjamin Cummings, 1992.
[141] M. A. Weiss, Data structures and problem solving using Java, Pearson Addison Wesley,
2006.
[142] D. Wells, The Penguin Dictionary of Curious and Interesting Numbers, Middlesex,
Penguin Books, 1986.
[143] H. S. Wilf, Algorithms and Complexity, Internet edition, 1996.
[144] J. W. J. Williams, Algorithm 232 - Heapsort, Communications of the ACM, vol. 7(6),
pp. 347-348, 1964.
[145] S. Winograd, A new algorithm for inner product, IEEE Trans. Computers, C-17, pp.
693694, 1968.
[146] N. Wirth, Algorithms and Data Structures, Prentice Hall, 1986.
[147] Y. Xu, Y. Papakonstantinou, Efficient keyword search for smallest LCAs in XML
databases, in Proceedings of the 2005 ACM SIGMOD International Conference on Management of Data, pp. 527538, 2005.
[148] D. Zaharie, Introducere n proiectarea si analiza algoritmilor, Eubeea, Timisoara, 2008.

210

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