Documente Academic
Documente Profesional
Documente Cultură
Logic
Logic
CUPRINS
Prefa ..................................................................................vii
1. Introducere .......................................................................... 11
1.1. Noiuni despre limbaj ........................................................................13
1.2. Noiuni despre notaia asimptotic..................................................14
2. Algoritmi de sortare............................................................. 21
2.1. Bubble sort .........................................................................................23
2.2. Insertion sort ......................................................................................24
2.3. Quicksort ............................................................................................27
2.4. Merge sort ..........................................................................................33
2.5. Heapsort .............................................................................................37
2.6. Counting sort......................................................................................45
2.7. Radix sort............................................................................................48
2.8. Concluzii .............................................................................................55
3. Tehnici de programare......................................................... 57
3.1. Recursivitate ......................................................................................59
3.2. Backtracking .......................................................................................68
3.3. Divide et impera ................................................................................82
3.4. Greedy ................................................................................................89
3.5. Programare dinamic ........................................................................96
4. Algoritmi matematici ......................................................... 109
4.1. Noiuni despre aritmetica modular ............................................. 111
4.2. Algoritmul lui Euclid........................................................................ 112
4.3. Algoritmul lui Euclid extins............................................................. 114
4.4. Numere prime ................................................................................. 116
4.5. Algoritmul lui Gauss........................................................................ 130
4.6. Exponenierea logaritmic ............................................................. 136
4.7. Inveri modulari, funcia totenial ............................................... 143
4.8. Teorema chinez a resturilor ......................................................... 145
4.9. Principiul includerii i al excluderii ................................................ 150
iii
Algoritmic
iv
Cuprins
vi
Prefa
Prefa
Aceast carte este util tuturor celor care doresc s studieze
conceptele fundamentale ce stau la baza programrii calculatoarelor,
mbinnd principalele direcii de cercetare pe care un viitor programator sau
absolvent al domeniului informatic ar trebui s le parcurg i s le
cunoasc.
Cartea este conceput ca o colecie de probleme demonstrative a
cror rezolvare acoper elemente de programare procedural, tehnici de
programare, algoritmi i structuri de date, inteligen artificial i nu n
ultimul rnd programare dinamic.
Pentru fiecare problem n parte sunt construii algoritmii clasici de
rezolvare, completai cu explicaia funcionrii acestora, iar n completare,
acolo unde este necesar, problemele dispun i de prezentarea noiunilor
teoretice, a conceptelor generale i particulare aferente construirii unui
algoritm optimizat.
Organizare
vii
Algoritmic
viii
Prefa
Convenii utilizate
ix
Algoritmic
Despre Autori
La data publicrii acestei cri
x
Introducere
1. Introducere
Acest prim capitol are ca scop familiarizarea cititorului cu
elementele constructive ale crii, cunotiinele iniiale necesare nelegerii
materialul de fa, conveniile de scriere i prezentare a secvenelor de cod,
tot aici sunt cuprinse noiunile generale privind analiza complexitii
algoritmilor prin studiul timpului de execuie i cantitii de memorie
utilizat de ctre acetia (notaia asimptotic).
11
Capitolul 1
CUPRINS
12
Introducere
13
Capitolul 1
14
Introducere
Secvena 1
Secvena 2
15
Capitolul 1
()
= 0.5 2 0.5 .
Secvena 3
...
int st[maxn], k = 1;
...
for ( int i = 1; i <= N; ++i )
{
while ( st[k] >= A[i] )
--k;
st[++k] = A[i];
}
16
Introducere
Secvena 4
Secvena 5
= , , 1 , > 0
17
Capitolul 1
1.
for ( int i = 1; i <= N; ++i )
for ( int j = 1; j <= M; ++j )
++k;
2.
for ( int i = 1; i <= N; ++i )
for ( int j = 1; j + i <= N; ++j )
++k;
3.
for ( int i = 1; i <= N; ++i )
for ( int j = 1; j <= N; ++j )
if ( (i + j) % 2 == 0 )
for ( int k = 1; k <= j; ++k )
cout << i + j + k <<'\n';
4.
for ( int i = 1; i * i <= N; ++i )
for ( int j = 1; j <= N; j *= 2 )
cout << i + j << '\n';
5.
for ( int i = 1; i <= N*N; i *= 2 ) cout << i << endl;
18
Introducere
6.
for ( int i = 1; i <= N; ++i )
for ( int j = 1; j * j <= N; j++ )
cout << i + j << '\n';
7.
int f(int k)
{
if ( !k ) return 1;
8.
for ( int i = 1; i <= 2010; ++i )
for ( int j = 1; j <= N; ++j )
for ( int k = 2010; k; --k )
if ( i + j > j + k )
cout << "Ok!\n";
9.
while ( st <= dr )
{
if ( A[st] == A[dr] )
{
for ( int i = st; i <= dr; ++i )
cout << A[i] << ' ';
cout << endl;
}
++st;
--dr;
}
10.
void f(int N)
{
if ( !N ) return;
19
Capitolul 1
20
Algoritmi de sortare
2. Algoritmi de
sortare
Problema sortrii unor date dup un anumit criteriu este una dintre
cele mai vechi probleme care face obiectul de studiu al informaticii. Exist o
gam foarte larg de algoritmi care rezolv aceast problem, ct i nite
rezultate teoretice importante cu privire la corectitudinea i eficiena acestor
algoritmi.
Acest capitol prezint detaliat o serie de algoritmi de sortare
reprezentativi pentru clasele din care acetia fac parte. Fiecare algoritm este
prezentat din punct de vedere al complexitii asimptotice, al eficienei
practice, al memoriei suplimentare folosite, al stabilitii, al
optimizrilor suportate i este nsoit de o implementare n limbajul C++.
Demonstraiile unor rezultate la care se face referire nu vor fi prezentate,
punndu-se accentul pe ntelegerea modului de funcionare al algoritmilor i
al implementrii acestora ntr-un limbaj de programare.
Prin memorie suplimentar nelegem memoria necesar execuiei
algoritmului, fr s lum n considerare vectorul ce reine numerele ce
trebuiesc sortate.
Prin stabilitate nelegem proprietatea unui algoritm de sortare de a
pstra ordinea relativ a dou elemente cu chei de sortare identice. De
exemplu, dac ar trebui s sortm perechile (2, 3), (1, 4), (2, 5), (1, 2) dup
prima component, un algoritm care ar produce sortarea: (1, 4), (1, 2), (2, 3),
(2, 5) ar putea fi stabil, pe cnd un algoritm care ar produce orice alt sortare
sigur nu ar fi stabil.
21
Capitolul 2
CUPRINS
22
Algoritmi de sortare
23
Capitolul 2
i 1 2 3 4 5
A 6 4 3 8 7
i 1 2 3 4 5
A 4 4 6 8 7
24
Algoritmi de sortare
i 1 2 3 4 5
A 3 4 6 8 7
i 1 2 3 4 5
A 3 4 6 8 7
i 1 2 3 4 5
A 3 4 6 7 8
25
Capitolul 2
A[j+1] = V;
}
}
26
Algoritmi de sortare
2.3. Quicksort
Quicksort, sau sortarea rapid, este cel mai eficient algoritm de
sortare, comparativ cu ceilali algoritmi de aceiai complexitate. Din pcate,
pentru a fi cu adevrat performant att pe cazul mediu ct i pe cazul cel mai
defavorabil, algoritmul necesit anumite optimizri care complic puin
codul, rezultnd un program mai complex dect pentru celelalte sortri.
Sortarea rapid este un algoritm de tip divide et impera i
funcioneaz astfel:
Fie Quicksort(A, st, dr) o funcie care sorteaz intervalul [x, y]
al vectorului A.
Fie Partitie(A, st, dr) o funcie care reordoneaz intervalul [x, y]
al vectorului A astfel nct toate elementele mai mici sau egale
cu A[st] s se afle la nceputul vectorului i toate elementele mai
mari sau egale cu A[st] s se afle la sfritul vectorului.
Elementul A[st] se numete element pivot.
Funcia Quicksort(A, st, dr) este implementat astfel:
o Dac st < dr execut
P = Partitie(A, st, dr)
Apeleaz recursiv Quicksort(A, st, P)
Apeleaz recursiv Quicksort(A, P + 1, dr)
La finalul algoritmului, vectorul A va fi sortat.
27
Capitolul 2
i 1 2 3 4
A 1 2 3 4
i 1 2 3 4
A 1 2 3 4
28
Algoritmi de sortare
i 1 2 3 4
A 1 2 3 4
i 1 2 3 4
A 1 2 3 4
i 1 2 3 4
A 1 2 3 4
29
Capitolul 2
int Partitie(int A[], int st, int dr) void Quicksort(int A[], int st, int dr)
{ {
int V = A[st]; if ( st < dr )
--st; ++dr; {
while ( st < dr ) int P = Partitie(A, st, dr);
{ Quicksort(A, st, P);
do Quicksort(A, P+1, dr);
--dr; }
while ( st < dr && A[dr] > V ); }
do
++st; Execiiu: modificai funcia Partitie
while ( st < dr && A[st] < V ); astfel nct aceasta s returneze, n
O(N), al k-lea cel mai mic element al
if ( st < dr )
{ vectorului A. De exemplu, dac
int tmp = A[st]; A = {1, 7, 5, 2, 4} i k = 3, se va
A[st] = A[dr]; returna 4.
A[dr] = tmp;
}
}
return dr;
}
30
Algoritmi de sortare
31
Capitolul 2
int Partitie(int A[], int st, int dr) void Quicksort(int A[], int st, int dr)
{ // numar aleator din [st, dr] {
int poz = st + rand() % (dr-st+1); while ( st < dr )
int tmp = A[poz]; {
A[poz] = A[st]; int P = Partitie(A, st, dr);
A[st] = tmp; if ( P - st < dr - P - 1 )
{
int V = A[st]; Quicksort(A, st, P);
--st; ++dr; st = P + 1;
while ( st < dr ) }
{ else
do {
--dr; Quicksort(A, P + 1, dr);
while ( st < dr && A[dr] > V ); dr = P;
do }
++st; }
while ( st < dr && A[st] < V ); }
if ( st < dr )
{
int tmp = A[st];
A[st] = A[dr];
A[dr] = tmp;
}
}
return dr;
}
32
Algoritmi de sortare
33
Capitolul 2
34
Algoritmi de sortare
1 2
=
(1) (2) ()
35
Capitolul 2
36
Algoritmi de sortare
2.5. Heapsort
Heapsort, cunoscut i sub numele de sortare prin ansamble, este
un algoritm de sortare cu timpul de execuie O(Nlog N) i memorie
auxiliar O(1). Algoritmul este, practic, o optimizare a algoritmului de
sortare prin selecie (Selection sort), algoritm care funcioneaz
determinnd la fiecare pas elementul de valoarea maxim i mutndu-l pe
ultima poziie liber a vectorului. Deoarece trebuie s determinm N
maxime, iar determinarea unui maxim implic verificarea tuturor
elementelor vectorului, complexitatea acestui algoritm este O(N2). Heapsort
folosete structura de date numit heap pentru a determina cele N maxime,
rezultnd un timp de execuie de O(Nlog N). Pentru a nelege mai bine ce
este acela un heap, vom ncepe prin prezentarea unor noiuni teoretice.
37
Capitolul 2
i 1 2 3 4 5 6 7
A 19 13 15 5 6 12 14
38
Algoritmi de sortare
i 1 2 3 4 5 6 7 8
A 19 13 15 5 6 12 14 18
i 1 2 3 4 5 6 7 8
A 19 13 15 18 6 12 14 5
i 1 2 3 4 5 6 7 8
A 19 18 15 13 6 12 14 5
39
Capitolul 2
i 1 2 3 4 5 6 7
A 19 13 15 5 6 12 14
i 1 2 3 4 5 6
A 14 13 15 5 6 12
i 1 2 3 4 5 6
A 15 13 14 5 6 12
40
Algoritmi de sortare
41
Capitolul 2
42
Algoritmi de sortare
Schimb = FiuSt;
if ( FiuDr <= N )
if ( A[FiuDr] > A[Schimb] )
Schimb = FiuDr;
43
Capitolul 2
void Introsort(int A[], int N, int st, int dr, int Adanc)
{
if ( st < dr )
{
if ( (1 << Adanc) > N )
Heapsort(A, st, dr);
else
{
int P = Partitie(A, st, dr);
Introsort(A, N, st, P, Adanc + 1);
Introsort(A, N, P+1, dr, Adanc + 1);
}
}
}
44
Algoritmi de sortare
45
Capitolul 2
i 1 2 3 4 5 6
A 4 7 2 2 1 3
i 0 1 2 3 4 5 6 7
V 0 1 2 1 1 0 0 1
Ceea ce nseamn c avem zero elemente cu valoarea 0, un element
cu valoarea 1, dou elemente cu valoarea 2, un element cu valoarea 3
.a.m.d.
Acum, pentru a sorta efectiv vectorul A, parcurgem toate poziiile i
ale vectorului V i punem n A valoarea i de V[i] ori.
Este clar c sortarea prin numrare este foarte eficient atunci cnd
avem un numr mare de valori mici ce trebuiesc sortate. Dezavantajele
acestei metode sunt c nu putem sorta dect numere naturale.
Pentru a extinde metoda la numere ntregi din intervalul
[-MaxV, MaxV], putem aduna fiecrui element valoarea MaxV,
transformnd astfel toate numerele ntregi n umere naturale. Astfel se
dubleaz ns memoria folosit.
Pentru a putea sorta numere reale pozitive, despre care tim c au un
anumit numr X de cifre dup virgul, putem s le nmulim pe fiecare cu
10X, dup care s le sortm ca fiind numere naturale. Dac numerele pot fi i
46
Algoritmi de sortare
negative, se pot combina cele dou metode. Pentru numere reale ns,
algoritmul devine destul de ineficient, deoarece memoria folosit crete de
10X ori, iar timpul de execuie devine mai mare. Pentru alte tipuri de date,
folosirea acestei metode poate fi mult mai dificil, sau chiar imposibil. De
exemplu, cum putem sorta alfabetic nite iruri de caractere folosind aceast
metod? Dar nite perechi de numere dup prima component?
delete []V;
}
+1
8 ()
47
Capitolul 2
i 1 2 3 4 5 6 7 8 9 10
A 430 027 325 088 145 111 034 932 353 007
Vom ncepe prin a sorta mai nti numerele dup cifra unitilor. Va
rezulta urmtorul vector:
i 1 2 3 4 5 6 7 8 9 10
A 430 111 932 353 034 325 145 027 007 088
i 1 2 3 4 5 6 7 8 9 10
A 007 111 325 027 430 932 034 145 353 088
48
Algoritmi de sortare
i 1 2 3 4 5 6 7 8 9 10
A 007 027 034 088 111 145 325 353 430 932
Definiia 1: Un numr natural care are cea mai mare cifr C poate fi
considerat un numr n toate bazele mai mari dect C. De exemplu, numrul
5213 poate fi considerat un numr n toate bazele mai mari dect 5. Sistemul
(baza) n care un numr este scris se marcheaz prin trecerea bazei ca indice
al numrului.
Definiia 2: O cifr a unui numr natural n baza 2 se numete bit. 8
bii = 1 byte.
= 1 2 , {0,1, ,9}
49
Capitolul 2
10 = 1 10 1 + 2 10 2 + + 100
= 1 2 , {0,1, ,9}
2 = (1 21 + 2 22 + + 20 )10
.
De exemplu, 10112 = 1110
= 1 2 , {0,1, ,9}
50
Algoritmi de sortare
p q p&q
1 1 1
1 0 0
0 1 0
0 0 0
p q p|q
1 1 1
1 0 1
0 1 1
0 0 0
51
Capitolul 2
i 1 2 3
A 01101110 00010111 11101001
i 1 2 3
A 00010111 11101001 01101110
Deoarece 01112 = 710, 10012 = 910 i 11102 = 1410. Vom sorta acum
numerele dup urmtoarea grup de de patru bii:
i 1 2 3
A 00010111 01101110 11101001
52
Algoritmi de sortare
i 0 1 2 3 4 5 6
A 3 4 3 2 1 1 7
i 0 1 2 3 4 5 6
A 1 4 8 11 13 14 15
53
Capitolul 2
110101101102 &
000000111112
000000101102 = 2210
Pentru a afla valoarea urmtorilor cinci bii, vom deplasa mai nti
numrul iniial cu cinci poziii ctre dreapta i vom aplica operaia I pe
numrul rezultat prin deplasare:
110101101102 >> 000001101012 &
5 000000111112
000001101012 000000101012 = 2110
void Sortare(unsigned int A[], int N, unsigned int T[], int Gr, int V[],
int Poz[])
{
for ( int i = 0; i < MaxVal; ++i ) V[i] = 0;
for ( int i = 1; i <= N; ++i ) ++V[ AflaGrupa(A[i], Gr) ];
Poz[0] = 1;
for ( int i = 1; i < MaxVal; ++i ) Poz[i] = Poz[i - 1] + V[i - 1];
for ( int i = 1; i <= N; ++i ) T[ Poz[ AflaGrupa(A[i], Gr) ]++ ] = A[i];
}
Sortare(A, N, T, 0, V, Poz);
Sortare(T, N, A, 1, V, Poz);
delete []T; delete []V; delete []Poz;
}
54
Algoritmi de sortare
2.8. Concluzii
Am prezentat n acest capitol opt algoritmi de sortare, metode de
implementare a acestora, optimizri i situaiile n care fiecare se potrivete
cel mai bine.
55
Capitolul 2
56
Tehnici de programare
3. Tehnici de
programare
Acest capitol prezint principalele tehnici tehnici de programare
folosite n rezolvarea problemelor: recursivitatea, backtracking, divide et
impera, greedy i programare dinamic. Aceste tehnici sunt nsoite de
aplicaii practice clasice, cum ar fi problema turnurilor din Hanoi i
generarea permutrilor, aranjamentelor, combinrilor etc.
Acest capitol este foarte important, ntruct orice problem poate fi
rezolvat printr-un algoritm care se ncadreaz ntr-una dintre tehnicile
menionate. Recomandm aadar stpnirea acestora.
57
Capitolul 3
CUPRINS
58
Tehnici de programare
3.1. Recursivitate
S pornim de la definiia de baz: o funcie este recursiv dac n
definiia ei se folosete o referire la ea nsi.
Din aceast definiie putem considera modelul general al unui
algoritm recursiv de forma:
rec(param_formali) { rec(param_formali) }
Funcia factorial
1 = 0
=
1 > 0
int f(int n)
{
if ( n == 0 ) return 1;
else return f(n - 1) * n ;
}
+1 = 0
, = ( 1, 1) > 0 = 0
( 1, , 1 ) > 0 > 0
59
Capitolul 3
0 = 0
: , = 1 = 1
1 + ( 2) 2
Conform definiiei:
int F(int n)
{
if ( n == 0 ) return 0;
else if ( n == 1) return 1;
else return F(n 1) + F(n 2);
}
= 0
, =
(, % ) > 0
60
Tehnici de programare
a) Fractali
Fig. 3.1.1. a)
Urmtorul subprogram:
fractal (x, y, l)
{
patrat(x, y, l);
fractal((x l) / 2, (y l) / 2, l / 2)
}
Fig. 3.1.1. b)
61
Capitolul 3
fractal (x,y,l)
{
if ( l>5 ) //se refer la dimensiunea n pixeli
{
patrat(x, y, l)
fractal((x l) / 2, (y l) / 2, l / 2)
fractal((x + l) / 2, (y + l) / 2, l / 2)
}
}
Fig. 3.1.1. c)
62
Tehnici de programare
63
Capitolul 3
64
Tehnici de programare
65
Capitolul 3
#include <iostream>
using namespace std;
int main()
{
Hanoi (3,'A','B','C');
return 0;
}
66
Tehnici de programare
67
Capitolul 3
#include <iostream>
using namespace std;
int main()
{
Hanoi4tije(3, 'A', 'B', 'C', 'D');
return 0;
}
3.2. Backtracking
Tehnica backtracking este o tehnic de programare, implementat
de obicei recursiv, care construiete treptat soluia unei probleme, iar n
cazul n care soluia construit se dovedete a fi invalid (sau ne intereseaz
mai multe soluii), revine la un pas precedent pentru a schimba o alegere
fcut. Acest lucru se continu, de obicei, pn cnd au fost explorate toate
posibilitile sau pn cnd am gsit una sau mai multe soluii valide. De
multe ori nu este necesar explorarea tuturor posibilitilor, putnd elimina
68
Tehnici de programare
69
Capitolul 3
Exemplu:
perm.in perm.out
3 123
132
213
231
312
321
70
Tehnici de programare
1 1
K Sol
i 1 2 3
Fol true false false
2 2
1 1
K Sol
i 1 2 3
Fol true true false
3 3
2 2
1 1
K Sol
i 1 2 3
Fol true true true
71
Capitolul 3
3 3
2 2
1 1
K Sol
i 1 2 3
Fol true true false
i 1 2 3
Fol true false true
#include <fstream>
using namespace std;
72
Tehnici de programare
Exerciii:
a) Ct de rapid este algoritmul pentru valori mai mari dect 8?
b) Mai putei gsi optimizri pentru acest algoritm? Dar alt
algoritm, care abordeaz diferit problema? (Indiciu: folosii
interschimbri)
c) Modificai algoritmul astfel nct s gseasc numai a P-a
permutare n ordine lexicografic.
d) Modificai algoritmul astfel nct s genereze toate permutrile
unei mulimi citite din fiier, mulime care poate avea elemente
care se repet.
b) Generarea aranjamentelor
Dndu-se dou numere naturale N i P (1 P N), ne propunem s
generm toate aranjamentele de N luate cte P ale mulimii numerelor
!
naturale. Aranjamente de N luate cte P se noteaz , iar =
!
(numrul aranjamentelor de N luate cte P). Reamintim c reprezint
modalitile de a aranja N obiecte n P poziii, innd cont de ordinea
acestora (de exemplu, aranjamentul 1 3 2 este diferit de aranjamentul 1 2 3).
73
Capitolul 3
Exemplu:
aran.in aran.out
32 12
13
21
23
31
32
i 1 2 3
Fol true false false
2 2
1 1
K Sol
i 1 2 3
Fol true true false
74
Tehnici de programare
2 3
1 1
K Sol
i 1 2 3
Fol true false true
#include <fstream>
75
Capitolul 3
c) Generarea combinrilor
Exemplu:
comb.in comb.out
42 12
13
14
23
24
34
76
Tehnici de programare
1 1
K Sol
77
Capitolul 3
return;
}
Exerciii:
a) Ct de rapid este generarea aranjamentelor i a combinrilor
pentru valori mari, dar apropiate, ale lui N i P?
b) Scriei un program care afieaz toate numerele naturale de trei
cifre care pot forma cu cifrele 1, 3, 4 i 5. Ce algoritm vei
folosi?
c) Modificai ultimii doi algoritmi astfel nct s afieze
aranjamentele, respectiv combinrile, n ordine invers-
lexicografic.
78
Tehnici de programare
Exemplu:
part.in part.out
4 1111
112
13
22
4
Explicaie: 1 + 1 + 1 + 1 = 2 + 1 + 1 = 3 + 1 = 4
79
Capitolul 3
80
Tehnici de programare
return;
}
Exerciii:
a) Modificai algoritmul astfel nct doar s numere partiiile
existente, nu s le i afieze.
b) Impunei condiia ca numerele folosite ntr-o partiie s fie
distincte.
c) Impunei condiia ca diferena n modul a doi termeni consecutivi
s fie cel puin 2
81
Capitolul 3
e) Concluzii
82
Tehnici de programare
a) Determinarea minimului
Exemplu:
minim.in minim.out
7 2
9 7 8 3 4 2 11
83
Capitolul 3
#include <fstream>
using namespace std;
const int maxN = 1001;
void citire(int &N, int A[])
{
ifstream in("minim.in");
in >> N;
for ( int i = 1; i <= N; ++i ) in >> A[i];
in.close();
}
int main()
{
int N, A[maxN];
citire(N, A);
ofstream out("minim.out");
out << Minim(A, 1, N);
out.close();
return 0;
}
84
Tehnici de programare
b) Cutarea binar
Se dau N numere naturale ordonate cresctor i M ntrebri de
forma numrul Xi se gsete sau nu printre cele N numere? la care trebuie
s se rspund ct mai eficient.
Datele de intrare se citesc din fiierul cbinara.in: pe prima linie
numerele N i M separate prin spaiu, pe urmtoarea linie cele N numere
naturale separate prin spaiu, iar pe urmtoarele M linii cele M ntrebri. Pe
linia i a fiierului de ieire cbinara.out vei afia DA sau NU, n funcie de
rspunsul la ntrebarea respectiv.
Exemplu:
cbinara.in cbinara.out
95 DA
4 6 8 9 12 15 15 16 21 NU
4 DA
5 DA
9 DA
21
15
85
Capitolul 3
86
Tehnici de programare
#include <fstream>
using namespace std;
87
Capitolul 3
ofstream out("cbinara.out");
for ( int i = 1, x; i <= M; ++i )
{
in >> x;
in.close(); out.close();
}
int main()
{
int N, M, A[maxN];
rezolvare(N, M, A);
return 0;
}
88
Tehnici de programare
Exerciii:
a) Modificai algoritmul astfel nct s returneze cea mai mare
poziie a unui element cutat.
b) Modificai algoritmul astfel nct s lucreze cu un ir sortat
descresctor.
c) Modificai algoritmul de sortare prin inserie astfel nct s
foloseasc algoritmul de cutare binar pentru determinarea
poziiei n care trebuie inserat un element.
a) Concluzii
3.4. Greedy
Tehnica de programare greedy (n traducere liber: tehnica
lcomiei) se bazeaz pe selectarea succesiv a unor optime locale pentru a
determina ntr-un final optimul global. Tehnica se folosete, de obicei, n
cazul problemelor n care se cere determinarea unui minim sau maxim
respectnd anumite constrngeri dependente de problem.
Datorit faptului c algoritmii greedy iau la fiecare pas decizia cea
mai favorabil existent la acel pas, fr a lua n considerare cum ar putea
afecta aceast decizie ntreg traseul algoritmului, exist posibilitatea ca
aceti algoritmi s nu determine ntotdeauna un optim, ci doar o soluie care
respect constrngerile problemei, dar care nici mcr nu este neaprat
apropiat de optimul global pentru datele date. Din acest motiv, nainte de a
implementa o strategie greedy pentru rezolvarea unei probleme, este
recomandat s se demonstreze matematic corectitudinea strategiei alese.
Dac se poate demonstra c metoda aleas conduce ntotdeauna la
gsirea unui optim global, folosirea unei strategii greedy este de cele mai
multe ori preferabil celorlalte alternative, cum ar fi metodele backtracking
sau divide et impera, deoarece algoritmii greedy sunt, n general, mai
rapizi, avnd o complexitate polinomial, adic O(Nk), unde N reprezint
dimensiunea datelor de intrare, iar k este o constant natural.
89
Capitolul 3
a) Problema spectacolelor
Exemplu:
spectacole.in spectacole.out
5 3
14
47
35
89
67
90
Tehnici de programare
XP nu face parte din soluia optim. Dac XP ar face parte din soluia
optim, ar trebui s se afle cel puin pe poziia P + 1, ceea ce nseamn c ar
ncepe dup YP, contrazicndu-se astfel modul de funcionare al
algoritmului.
mai puin important) atunci cnd se lucreaz cu structuri, mai ales dac
acestea sunt foarte mari, deoarece dac parametrii nu sunt de tip referin,
fiecare apel al funciei va lucra cu o copie a obiectelor transmise ca
argument funciei, iar aceast copiere poate afecta drastic performana unui
program.
Exerciii:
a) Modificai algoritmul astfel nct s afieze indicii iniiali ai
spectacolelor selectate.
b) Implementai un algoritm de sortare pentru sortarea datelor.
c) Dac fiecare spectacol ar avea asociat un cost i am dori s
organizm un numr maxim de spectacole, dar care s coste ct
93
Capitolul 3
Exemplu:
suma.in suma.out
39 6
94
Tehnici de programare
c) Concluzii
Algoritmii greedy sunt de obicei mai rapizi dect ali algoritmi, mai
ales dect metodele exhaustive cum este metoda backtracking. Un
dezavantaj al acestora este c programatorul trebuie s se bazeze foarte mult
pe intuiie i pe experien pentru a putea ajunge la un algoritm corect, iar
aparenta corectitudine a unui algoritm poate fi neltoare, aa cum am
artat la ultima problem.
Dac avei de ales ntre un algoritm greedy a crui corectitudine
poate fi demonstrat i un algoritm mai puin eficient, este clar c alegerea
trebuie fcut n favoarea algoritmului greedy. Dac nu se poate demonstra
corectitudinea algoritmului greedy ns, iar importan gsirii unui optim
pentru fiecare caz posibil este foarte mare, atunci este de preferat folosirea
altui algoritm, chiar dac este mai puin eficient.
95
Capitolul 3
96
Tehnici de programare
a) irul Fibonacci
97
Capitolul 3
Exemplu:
fibo.in fibo.out
6 8
i 0 1 2 3 4 5 6
F 0 1 1 2 3 5 8
#include <fstream>
98
Tehnici de programare
int main()
{
int N, F[maxN];
ifstream in("fibo.in");
in >> N;
in.close();
F[0] = 0, F[1] = 1;
for ( int i = 2; i <= N; ++i )
F[i] = F[i - 2] + F[i - 1];
ofstream out("fibo.out");
out << F[N] << endl;
out.close();
return 0;
}
Exerciii:
a) Implementai algoritmul care folosete memorie O(1).
b) Numerele Fibonacci devin mari foarte rapid. Care este cel mai
mare numr Fibonacci care poate fi reinut de tipul de date int?
c) Dac s-ar da mai multe numere pentru care trebuie calculat
funcia F, cel mai mare dintre acestea fiind K, cum s-ar putea
calcula funcia eficient pentru fiecare?
b) Problema triunghiului
99
Capitolul 3
Exemplu:
triunghi.in triunghi.out
5 50
4 4 5 2 20 19
35
142
7 5 6 20
1 1 7 19 9
100
Tehnici de programare
[] = = 1
=
max 1 , 1 1 + []
i\j 0 1 2 3 4 5
0
1 inf 4 inf
2 inf 7 9 inf
3 inf 8 13 11 inf
4 inf 15 18 19 31 inf
5 inf 16 19 26 50 40
101
Capitolul 3
#include <fstream>
in.close();
}
102
Tehnici de programare
103
Capitolul 3
Exemplu:
pascal.in pascal.out
32 3
73 35
10 8 45
104
Tehnici de programare
= 1
1
+ 1
Implementare
#include <fstream> int main()
{
using namespace std; int N, K, A[maxN][maxN];
const int maxN = 13; preprocesare(A);
Exerciii:
a) Explicai de ce folosirea formulei combinrilor este o metod
mai puin eficient.
b) Scriei o funcie recursiv care folosete recurena combinrilor
pentru a calcula fiecare rspuns. Este aceast abordare eficient?
c) Dac am calcula mai multe linii ale triunghiului lui Pascal, care
este prima linie care conine rezultate greite? De ce se ntmpl
acest lucru?
d) Tehnica memoizrii
Am prezentat pn acum doi algoritmi care folosesc metoda nainte
pentru rezolvarea problemelor. Aa cum am menionat la nceputul
capitolului, metoda napoi poate fi mbuntit folosind tehnica
105
Capitolul 3
106
Tehnici de programare
return memo[x][y];
}
107
Capitolul 3
s fim ateni la mai multe cazuri particulare. Funcia va trebui apelat pentru
fiecare element al ultimei linii din triunghiul dat.
Exerciii:
a) Cum putem reconstitui soluia la problema triunghiului dac
folosim funcia recursiv anterioar?
b) Dac ne intereseaz drumul minim numai pn la un singur
element de pe ultima linie a triunghiul, care abordare este mai
eficient?
c) Scriei o funcie recursiv ce folosete memoizare pentru calculul
combinrilor.
d) Explicai de ce memoizarea nu mbuntete funcia factorial
sau funcia de rezolvare a problemei turnurilor din Hanoi.
e) Comparai o funcie recursiv memoizat cu o funcie iterativ
echivalent acesteia. Care este mai eficient?
e) Concluzii
Programarea dinamic este o tehnic de programare foarte
folositoare pentru rezolvarea problemelor de numrare sau de gsire a
optimelor. Este o alternativ mai rapid dect metoda backtracking i mai
corect dect metoda greedy.
Dezavantajul principal al acestei metode st n faptul c unele
formule de recuren pot fi neintuitive i dificil de implementat eficient, dar,
de cele mai multe ori, efortul unei implementri corecte merit fcut,
datorit eficienei acestor algoritmi.
Atunci cnd implementarea iterativ a unei formule de recuren ar fi
prea dificil, se poate folosi tehnica memoizrii pentru a pstra eficiena
algoritmului i a simplifica implementarea.
Corectitudinea unui algoritm de programare dinamic poate fi
verificat cu un algoritm backtracking, iar la rndul su, programarea
dinamic poate fi folosit pentru a testa dac o strategie greedy este sau nu
corect.
108
Algoritmi matematici
4. Algoritmi
matematici
Acest capitol prezint algoritmii care au la baz noiuni elementare
de matematic. Aceti algoritmi sunt folosii, de cele mai multe, ori pentru
rezolvarea unor probleme strict matematice, cum ar fi rezolvarea unor
ecuaii sau sisteme de ecuaii, determinarea numerelor cu anumite
proprieti, rezolvarea unor probleme de geometrie sau calculul unor
formule complexe cu ajutorul calculatorului.
Algoritmii prezentai se axeaz mai mult pe teoria numerelor, acetia
fiind cei mai des ntlnii n domeniul informaticii i totodat cei mai
studiai.
109
Capitolul 4
CUPRINS
110
Algoritmi matematici
111
Capitolul 4
Scznd la fiecare pas numrul mai mic din numrul mai mare,
rmnem n final cu dou numere egale cu 4, iar acest numr reprezint
c.m.m.d.c. pentru 12 i 8.
112
Algoritmi matematici
113
Capitolul 4
114
Algoritmi matematici
115
Capitolul 4
iar 460 1 (mod 51). Aa cum vom vedea n seciunile ce urmeaz, acest
lucru are diverse aplicaii i n ali algoritmi.
return ;
}
a = tb;
b = ta - tb*(X / Y);
}
Exerciii:
a) Modificai funcia astfel nct s returneze i c.m.m.d.c. al
numerelor X i Y.
b) Modificai funcia astfel nct s returneze soluiile ecuaiei
aX + bY = c, unde c este un numr dat, iar restul variabilelor au
aceeai semnificaie de pn acum. Exist ntotdeauna soluie?
c) Scriei o funcie echivalent, dar care s nu foloseasc apeluri
recursive.
d) Scriei un program care citete dou numere X i Y i determin
inversul multiplicativ al lui X modulo Y dac acesta exist, iar
dac nu afieaz un mesaj corspunztor.
116
Algoritmi matematici
ln
117
Capitolul 4
a) Metode clasice
118
Algoritmi matematici
return true;
}
119
Capitolul 4
120
Algoritmi matematici
2 3 5 7 11 13
17 19 23
121
Capitolul 4
void eratosthenes_clasic(int N)
{
bool *lista = new bool[N + 1]; // lista[i] = true daca numarul i
// este prim si false in caz contrar
delete []lista;
}
(operatorul ~ schimb toi biii unui numr n opusul lor, adica 1 devine 0 i
0 devine 1. Operatorul & se consider cunoscut de la seciunea Radix sort).
Pentru a verifica valoarea unui bit, vom folosi o funcie
bool verif(unsigned char lista[], int nr)
lista[0] lista[1]
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1
123
Capitolul 4
11111111 &
11111101
11111101
lista[0] lista[1]
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
1 1 1 1 1 1 1 1 1 1 1 1 1 1 0 1
lista[0] lista[1]
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
1 1 1 1 1 1 1 1 0 1 1 0 1 1 0 1
124
Algoritmi matematici
void eratosthenes_biti(int N)
{
int dim = N / 8 + 1;
unsigned char *lista = new unsigned char[dim];
delete []lista;
}
125
Capitolul 4
void eratosthenes_final(int N)
{
int dim = N / 8 / 2 + 1;
unsigned char *lista = new unsigned char[dim];
126
Algoritmi matematici
void eratosthenes_bitset(int N)
{
bitset<100000> lista; // clasa bitset nu suporta alocare dinamica
lista.set(); // seteaza toti bitii pe 1 (true)
for ( int i = 3; i*i <= N; i += 2 )
if ( lista[i] )
for ( int j = i*i; j <= N; j += i )
lista[j] = 0;
128
Algoritmi matematici
delete []prim;
}
129
Capitolul 4
1 : 2 + 3 = 4
2 : + 2 = 3
1 : 3 + 2 + 2 = 2
2 : + + 3 = 1
3 : 4 + 3 + = 5
1
2 1 + 2
3
4
3 1 + 3
3
130
Algoritmi matematici
1 : 3 + 2 + 2 = 2
1 7 1
2 : + =
3 3 3
1 5 23
3 : =
3 3 3
Sistemul devine:
1 : 3 + 2 + 2 = 2
1 7 1
2 : + =
3 3 3
3 : 4 = 8
= 8
= 13
= 2
1 : 11 1 + 12 2 + + 1 = 1
2 : 21 1 + 22 2 + + 2 = 2
.
.
.
: 1 1 + 2 2 + + =
131
Capitolul 4
11 12 1 1
21 22 2 2
. .
= . .
. .
1 2
11 12 13 1 1
0 22 23 2 2
0 0 33 3 3
= . .
. .
. .
0 0 0
132
Algoritmi matematici
1 0 00 1
0 1 00 2
0 0 10 3
= . .
. .
. .
0 0 01
133
Capitolul 4
134
Algoritmi matematici
Exerciii:
a) Adugai condiii care verific dac un sistem nu are soluie i
afieaz un mesaj corespunztor n acest caz.
b) ncercai s gsii un sistem pentru care cele dou variante
prezentate al algoritmului dau rspunsuri diferite.
135
Capitolul 4
return rez;
}
1 = 0
= 2 2
1
136
Algoritmi matematici
137
Capitolul 4
1 1 ( )
138
Algoritmi matematici
if ( exponentiere_log(a, p - 1, p) != 1 )
return false; // SIGUR nu e prim
}
139
Capitolul 4
1 1 ( )
140
Algoritmi matematici
1 (1)
i
2 1 0 < (2)
141
Capitolul 4
bool miller_rabin_test(int p)
{
int a[] = {2, 7, 61, 0}, s = 0, d = p - 1;
while ( d % 2 == 0 )
{
d /= 2;
++s;
}
if ( x == 1 )
return false;
else if ( x == p - 1 )
{
doi = false;
break;
}
}
142
Algoritmi matematici
1
=
=1
143
Capitolul 4
144
Algoritmi matematici
return R;
}
Exerciii:
a) Scriei, folosind funcia phi, un program care calculeaz inveri
modulari. Verificai dac aceast metod este mai rapid sau nu
dect folosirea algoritmului lui Euclid extins.
b) Scriei un program care calculeaz cte fracii ireductibile cu
numitorul i numrtorul mai mici dect un numr N exist.
2 3
3 5
2 7
145
Capitolul 4
Pentru exemplul dat, gsim prima dat 57 = 35, care verific prima
relaie i nu le afecteaz pe celelalte. Pasul urmtor este s gsim un
multiplu al numerelor 3 i 7 care verific a doua relaie i nu le afecteaz pe
prima i pe ultima. Un astfel de numr este 63. La fel se gsete i numrul
30.
1 ( 1 )
2 ( 2 )
.
.
.
( )
146
Algoritmi matematici
Fie = 1 2 . Fie = 1 . Calculm
= 1 ( ). Soluia sistemului este
= ( ) .
=1
( ) =
=1
Lucru echivalent cu
( ) =
=1
deoarece | . Din = rezult 0
0 , . Este de ajuns aadar s
demonstrm c ( ), lucru adevrat deoarece
1 ( ), iar 1 1 .
147
Capitolul 4
#include <fstream>
using namespace std;
struct congruenta
{
int a, n;
};
in >> k;
for ( int i = 1; i <= k; ++i )
in >> T[i].a >> T[i].n;
in.close();
}
148
Algoritmi matematici
a = tb;
b = ta - tb*(X / Y);
}
int a, b;
for ( int i = 1; i <= k; ++i )
{
euclid_ext(P[i], T[i].n, a, b);
Q[i] = a;
}
int X = 0;
for ( int i = 1; i <= k; ++i )
X += (T[i].a * P[i] * Q[i]) % N;
return X;
}
int main()
{
int k;
congruenta T[maxk];
citire(k, T);
ofstream out("TCR.out"); out << rezolvare(k, T); out.close();
return 0;
}
149
Capitolul 4
Unde S este numrul care se cere i <x, y> reprezint cel mai mic
multiplu comun al numerelor x i y.
n rezolvarea problemei am folosit principiul includerii i al
excluderii, principiu care ne ajut s determinm cardinalul reuniunii mai
multor mulimi. Dac avem n mulimi notate A1 , A2 , ..., An atunci:
150
Algoritmi matematici
=
=1
= 1 2 + + (1)1 1 2
=1 11 <2
Exerciii:
a) Scriei un program care rezolv problema de mai sus pentru
cazul general.
b) Scriei un program care citete n mulimi de numere ntregi dintr-
un fiier i calculeaz cardinalul reuniunii lor folosind principiul
includerii i al excluderii.
c) Scriei un program care citete n mulimi de numere ntregi dintr-
un fiier i calculeaz cardinalul reuniunii lor fr a folosi
principiul includerii i al excluderii. Care metod este mai simplu
de implementat? dar mai eficient?
151
Capitolul 4
0 =0
= 1 =1
2 + 1 >1
Atunci:
( + 1) ()
1 1
=
1 0 () ( 1)
( + 2) ( + 1)
=
( + 1) ()
152
Algoritmi matematici
7. Dac avem:
= 1 1 2 2 , 1
atunci:
= + 1 , =
=1
153
Capitolul 4
atunci:
+1
1
= , =
1
=1
154
Algoritmi matematici
0 1 2 3 4 5 6
6 5 4 1 0 9 2
while ( x )
{
X[ ++A[0] ] = x % 10;
x /= 10;
}
i 0 1 2 3 4 5 i 0 1 2 3 4 5
A 5 9 9 6 2 1 B 5 9 8 2 4 9
155
Capitolul 4
i 0 1 2 3 4 5 6
C 0 8
transport = 1
i 0 1 2 3 4 5 6
C 0 8 8
transport = 1
i 0 1 2 3 4 5 6
C 0 8 8 9 6 0
transport = 1
i 0 1 2 3 4 5 6
C 6 8 8 9 6 0 1
156
Algoritmi matematici
if ( transport )
C[i] = transport;
C[0] = i;
}
i 0 1 2 3 i 0 1 2 3
A 3 1 3 1 B 2 9 9 0
i 0 1 2 3
A 3 2 3 1
157
Capitolul 4
i 0 1 2 3
A 3 2 3 1
i 0 1 2 3
A 3 2 3 0
158
Algoritmi matematici
Practic, dac un numr are mai multe cifre ca cellalt, acel numr
este mai mare. Dac ambele numere au acelai numr de cifre, atunci se
compar numerele cifr cu cifr, ncepnd de la cea mai semnificativ cifr.
Aceaste comparaii fie vor determina care numr este mai mare, fie vor
determina c numerele sunt egale.
return 0; // A == B
}
159
Capitolul 4
i 0 1 2 3 4
A 4 3 7 1 3
transport = 0
i 0 1 2 3 4
A 4 9 7 1 3
transport = 3
i 0 1 2 3 4
A 4 9 4 2 1
transport = 4
i 0 1 2 3 4 5
A 5 9 4 2 1 4
160
Algoritmi matematici
while ( transport )
{
A[ ++A[0] ] = transport % 10;
transport /= 10;
}
}
1213
413
(*)
3639
1213
4852
(+)
500969
161
Capitolul 4
i 0 1 2 3 4 5 6 7
A 4 3 1 2 1 0 0 0
B 3 3 1 4 0 0 0 0
Rez 6 0 0 0 0 0 0 0
i 0 1 2 3 4 5 6 7
Rez 6 9 3 12 0 0 0 0
i 0 1 2 3 4 5 6 7
Rez 6 9 6 13 4 0 0 0
A treia cifr:
i 0 1 2 3 4 5 6 7
Rez 6 9 6 19 6 8 0 0
162
Algoritmi matematici
Ultima cifr:
i 0 1 2 3 4 5 6 7
Rez 6 9 6 19 9 9 4 0
i 0 1 2 3 4 5 6 7
Rez 6 9 6 9 9 9 4 0
i 0 1 2 3 4 5 6 7
Rez 6 9 6 9 0 9 4 0
i 0 1 2 3 4 5 6 7
Rez 6 9 6 9 0 0 4 0
i 0 1 2 3 4 5 6 7
Rez 6 9 6 9 0 0 5 0
101 < 10
101 < 10
163
Capitolul 4
int s = 0, transport = 0;
for ( int i = 1; i <= C[0]; ++i )
{
s = C[i] + transport;
C[i] = s % 10;
transport = s / 10;
}
if ( transport )
C[ ++C[0] ] = transport;
}
164
Algoritmi matematici
62117 : 13 = 04778
0
62
52
101
91
101
91
107
104
3
165
Capitolul 4
return rest;
}
166
Algoritmi backtracking
5. Algoritmi
backtracking
Am prezentat pn acum descrierea general a tehnicii de
programare numit backtracking, mpreun cu nite probleme elementare
care se rezolv cu ajutorul acestei tehnici. Problemele prezentate anterior nu
au ns nicio aplicabilitate intrinsec, acestea aparnd de cele mai multe ori
doar ca subprobleme n cadrul altor probleme mai complexe.
167
Capitolul 5
CUPRINS
168
Algoritmi backtracking
Exemplu:
labirint.in labirint.out
2 11
00 12
00 22
11
21
22
169
Capitolul 5
fie back(k, lin, col, N, A, st) funcia la care am fcut referire mai sus.
Aceast funcie poate fi implementat astfel:
Reine (lin, col) n st[k]
Dac (lin, col) == (N, N) afieaz coninuturile stivei st
Altfel execut:
o Pentru fiecare vecin valid (n_lin, n_col) execut
A[lin][col] = true
Apeleaz recursiv
back(k + 1, n_lin, n_col, N, A, st)
A[lin][col] = false
st A
true false
(1, 1)
false false
st A
(1, 2) true true
(1, 1) false false
st A
(2, 2) true true
(1, 2)
(1, 1) false false
171
Capitolul 5
if ( valid(n_lin, n_col, N, A) )
{
A[lin][col] = true;
back(k+1, n_lin, n_col, N, A, st, out);
A[lin][col] = false;
}
}
}
172
Algoritmi backtracking
Exemplu:
cal.in cal.out
5 304
173
Capitolul 5
void back(int k, int lin, int col, int N, bool A[maxn][maxn], stiva st[],
int &nr) // nr va contine rezultatul cerut
{
st[k].lin = lin; st[k].col = col;
if ( k == N*N )
{
++nr;
return;
}
for ( int i = 0; i < 8; ++i )
{
int n_lin = lin + dx[i];
int n_col = col + dy[i];
if ( valid(n_lin, n_col, N, A) )
{
A[lin][col] = true;
back(k + 1, n_lin, n_col, N, A, st, nr);
A[lin][col] = false;
}
}
}
174
Algoritmi backtracking
Exemplu:
sub.in sub.out
2 3
2
23
1
13
12
123
Vom folosi o stiv de valori booleane st, unde st[i] = 1 dac numrul
i face parte din submulimea curent i 0 n caz contrar. La fiecare pas k
vom depune n stiv valoarea 0, dup care vom trece la pasul urmtor. La
revenire din recursivitate vom depune n stiv valoarea 1, dup care vom
efectua nc un apel recursiv. Cnd am ajuns la pasul k > N, afim
numerele de ordine a poziiilor pe care se gsete 1 n stiv. Dac exist cel
puin o poziie pe care se gsete 1, trecem la urmtoarea linie la sfrit, n
caz contrar fiind vorba de mulimea vid. n pseudocod algoritmul este
urmtorul: fie back(k, N, st) funcia care rezolv problema:
Dac k > N execut
o Pentru fiecare i de la 1 la N execut
Dac st[i] == 1 afieaz i
o Dac s-a afiat cel puin un numr, treci la linie nou
Altfel execut
o Pentru fiecare i de la 0 la 1 execut
st[k] = i
Apeleaz recursiv back(k + 1, N, st)
175
Capitolul 5
176
Algoritmi backtracking
return;
}
Exemplu:
regine.in regine.out
8 92
177
Capitolul 5
O soluie este:
return true;
}
// nr va contine rezultatul
void back(int k, int N, int st[], bool fol[], int &nr)
{
if ( k > N )
{
++nr;
return;
}
fol[i] = true;
back(k + 1, N, st, fol, nr);
fol[i] = false;
}
}
Exerciii:
a) Implementai un program care folosete backtracking n plan
pentru rezolvarea problemei.
b) Comparai timpul de execuie al celor doi algoritmi. ncercai s
gsii optimizri.
179
Capitolul 5
1. =
2. = , ,
Exemplu:
partitii.in partitii.out
3 {1, 2, 3}
{1, 2} {3}
{1, 3} {2}
{1} {2, 3}
{1} {2} {3}
180
Algoritmi backtracking
181
Capitolul 5
if ( !primul )
out << "} ";
}
182
Algoritmi generali
6. Algoritmi
generali
Exist algoritmi care nu pot fi ncadrai ntr-o anume categorie fr a
defini nite categorii fie foarte restrictive, fie foarte vagi. Acetia au, de
obicei, aplicabilitate n nite probleme practice foarte specifice. n cele ce
urmeaz vom prezenta civa astfel de algoritmi, care considerm c i
merit totui propria seciune, datorit eleganei acestora i datorit
aplicaiilor teoretice care pot fi gsite pentru acetia.
183
Capitolul 6
CUPRINS
184
Algoritmi generali
Exemplu:
kmp.in kmp.out
abbbbbabaabbbaab 1
abbbaab
185
Capitolul 6
i 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
S1[i] a b b b b b a b a a b b b a a b
S2[j] a b b b a a b
j 1 2 3 4 5 6 7
i 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
S1[i] a b b b b b a b a a b b b a a b
S2[j] a b b b a a b
j 1 2 3 4 5 6 7
i 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
S1[i] a b b b b b a b a a b b b a a b
S2[j] a b b b a a b
j 1 2 3 4 5 6 7
186
Algoritmi generali
i 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
S1[i] a b b b b b a b a a b b b a a b
S2[j] a b b b a a b
j 1 2 3 4 5 6 7
187
Capitolul 6
i 1 2 3 4 5 6 7
S2[i] a b b b a a b
[i] 0 0 0 0 1 1 2
188
Algoritmi generali
int main()
{
char S1[maxn + 1], S2[maxn + 1];
int pi[maxn];
ifstream in("kmp.in");
in.getline(S1 + 1, maxn - 1);
in.getline(S2 + 1, maxn - 1);
in.close();
Exemplu:
expr.in expr.out
7*2/3+6-(2+1) 7.66667
190
Algoritmi generali
191
Capitolul 6
return ret;
}
192
Algoritmi generali
return ret;
}
// +, -, * sau /
ret = eval(expr, nivel + 1, k);
while ( expr[k] == oper[nivel][0] ||
expr[k] == oper[nivel][1] )
{
int poz = k++;
ret = operatie(ret,
eval(expr, nivel + 1, k),
expr[poz]);
}
return ret;
}
193
Capitolul 6
2
nelegem ab, este de fapt egal cu 21 = 2, nu cu (21 )2 = 4). Aceste
subtiliti, precum i implementarea suportului pentru funcii, sunt intuitive
i uor de implementat n varianta recursiv, dar mai dificil de implementat
n varianta itertiv.
Prezentm n continuare funciile relevante pentru varianta iterativ.
195
Capitolul 6
Exerciii:
a) Modificai variantele recursive astfel nct s construiasc ntr-un
vector dat ca parametru forma polonez postfixat a expresiei
evaluate.
b) Aceeai cerin pentru forma polonez prefixat. Forma polonez
prefixat i are toi operatorii urmai de operanzii asociai. De
exemplu, 2 + 3 * 2 + * 3 2 2.
c) Folosii o singur stiv att pentru formarea formei poloneze ct
i pentru evaluarea acesteia, n cadrul algoritmului iterativ.
d) Gsii un algoritm iterativ care construiete forma polonez
prefixat a unei expresii.
e) Implementai funciile sin i cos att n variantele recursive ct i
n varianta iterativ prezentat.
f) Considerai existena parantezelor drepte n cadrul expresiei date.
Modificai algoritmii dai astfel nct acestea s fie tratate ca
nsemnnd ridicarea la ptrat a subexpresiei din interior. De
exemplu, [3+2]*2-1 = 49.
196
Introducere n S.T.L.
7. Introducere
n S.T.L.
Biblioteca S.T.L. pune la dispoziia programatorilor C++ mai multe
structuri de date generice, lucru care scutete programatorii de timpul i
efortul implementrii acestor structuri de la zero. n aceeai bibliotec se
regsesc i diferii algoritmi care pot reduce timpul necesar rezolvrii unei
probleme.
Avantajul folosii bibliotecii S.T.L. const, n primul rnd, n
reducerea timpului necesar implementrii unui algoritm. n al doilea rnd,
aceste containere au fost implementate de o echip de profesioniti de-a
lungul unei perioade lungi de timp i testate foarte riguros, deci putem fi
siguri de corectitudinea i eficiena acestora.
n cele ce urmeaz vom prezenta pe scurt principalele containere i
algoritmi din S.T.L. i modul de folosire a acestora n nite situaii concrete.
197
Capitolul 7
CUPRINS
198
Introducere n S.T.L.
a) Containerul vector
vector<T> aniPari;
vector<int> aniPari;
for ( int i = 2002; i <= 2012; i += 2 )
aniPari.push_back(i);
199
Capitolul 7
aniPari.pop_back();
vector<int>::iterator it = aniPari.begin();
while ( it != aniPari.end() && *it != 2010 )
++it;
aniPari.insert(it, 1990);
200
Introducere n S.T.L.
vector<int> aniPari;
for ( int i = 2002; i <= 2012; i += 2 )
aniPari.push_back(i);
Pentru a compara dac doi vectori sunt egali (au toate elementele de
pe pe poziii identice egale) putem folosi pur i simplu operatorul ==.
Acesta se poate aplica i altor containere.
sort(numere.begin(), numere.end());
b) Containerul deque
deque<int> minusPlus;
201
Capitolul 7
minusPlus.pop_front();
for ( int i = -9; i < 0; ++i )
minusPlus.push_front(i);
deque<int>::iterator it;
for ( it = minusPlus.begin(); it != minusPlus.end(); ++it )
cout << *it << " ";
-1 -2 -3 -4 -5 -6 -7 -8 -9 2 3 4 5 6 7 8
Un deque este mai eficient dect un vector atunci cnd avem mai
multe operaii de inserare, deoarece nu au loc realocri de memorie. Deque-
urile au ns o implementare intern mai complex, care poate s le fac mai
ineficiente n unele situaii.
Un deque nu trebuie folosit dect dac avem nevoie s tergem i s
adugm elemente n ambele capete ale unei structuri liniare, situaie care
apare n unii algoritmi.
202
Introducere n S.T.L.
c) Containerul list
list este un container care are la baz o list dublu nlnuit. Acest
lucru nseamn c fiecare element are o locaie de memorie imprevizibil i
cte un pointer la elementul precedent i urmtor din list.
O list suport inserarea i tergerea elementelor de oriunde n timp
constant, mutarea elementelor n timp constant i iterarea elementelor n
timp liniar.
Comparativ cu vectorii i deque-urile, listele sunt mai eficiente
atunci cnd efectum multe inserri, mutri i tergeri de elemente din list.
list<int> nrPrime;
nrPrime.push_back(2);
nrPrime.push_back(5);
cout << nrPrime.front() << " " << nrPrime.back(); // afiseaza 2 5
list<int>::iterator it = nrPrime.begin();
while ( it != nrPrime.end() && *it != 5 )
++it;
nrPrime.insert(it, 3);
203
Capitolul 7
nrPrime.erase(nrPrime.begin());
Putem terge elemente din list i pe baza valorii lor, caz n care nu
trebuie s folosim iteratori. Pentru acest lucru vom folosi funcia remove.
Secvena de mai jos terge numrul 15 dintr-o list de numere prime, dac
acesta exist. Dac nu exist, nu se ntmpl nimic.
nrPrime.remove(15);
int main()
{
list<int> nrPrime;
nrPrime.push_back(2);
nrPrime.push_back(5);
nrPrime.push_back(666013);
204
Introducere n S.T.L.
nrPrime.sort();
nrPrime.unique();
for ( list<int>::iterator it = nrPrime.begin(); it != nrPrime.end(); ++it )
cout << *it << " ";
Sau:
a) Containerul stack
205
Capitolul 7
Ultimul intrat
...
Primul intrat
Fig. 7.2.1.1. o stiv L.I.F.O.
stack<string> studenti;
studenti.push("Ionescu");
studenti.push("Popescu");
studenti.push("Georgescu");
// Georgescu
cout << "Primul care isi va sti nota este: " << studenti.top();
206
Introducere n S.T.L.
b) Containerul queue
queue<string> cumparatori;
cumparatori.push("Vlad");
cumparatori.push("Alex");
cumparatori.push("George");
207
Capitolul 7
cumparatori.pop();
cout << cumparatori.front(); // afiseaza Alex
c) Containerul priority_queue
priority_queue<int> note;
208
Introducere n S.T.L.
struct cmp
{
bool operator () (const int &x, const int &y) const
{
return x % 17 < y % 17;
}
};
int main()
{
priority_queue<int, vector<int>, cmp> note;
note.push(80); note.push(97); note.push(100); note.push(30);
return 0;
}
209
Capitolul 7
set<int> nrUnice;
multiset<int> nrMultiple;
210
Introducere n S.T.L.
if ( rezultatSet.second == false )
cout << "Numarul " << *rezultatSet.first
<< " exista deja in set." << endl;
else
cout << "Numarul " << *rezultatSet.first
<< " a fost inserat in set." << endl;
set<int>::iterator it;
multiset<int>::iterator jt;
for ( int i = 0; i < 5; ++i )
{
if ( (it = nrUnice.find(rand() % 17)) == nrUnice.end() )
cout << "Elementul cautat nu exista in set" << endl;
else
cout << "Elementul " << *it << " exista in set" << endl;
211
Capitolul 7
nrUnice.insert(10); nrUnice.insert(12);
nrUnice.insert(9); nrUnice.insert(7);
cout << nrUnice.erase(10) << endl; // afiseaza 1 (s-a sters un 10)
cout << nrUnice.erase(13) << endl; // afiseaza 0 (nu s-a sters nimic)
cout << *nrUnice.begin() << endl; // afiseaza 7, cel mai mic numar
// conform relatiei <
nrUnice.erase(nrUnice.begin()); // se sterge 7, primul element
cout << *nrUnice.begin() << endl; // afiseaza 9
nrMultiple.insert(2010);
nrMultiple.insert(2011);
nrMultiple.insert(2010);
cout << nrMultiple.erase(2010) << endl; // afiseaza 2 (s-au sters
// ambele valori 2010)
nrMultiple.insert(1); nrMultiple.insert(1);
cout << nrMultiple.count(1) << endl; // afiseaza 2
212
Introducere n S.T.L.
struct cmp
{
bool operator () (const int &x, const int &y) const
{
return x % 17 < y % 17;
}
};
213
Capitolul 7
214
Introducere n S.T.L.
215
Capitolul 7
216
Introducere n S.T.L.
217
Capitolul 7
c) Containerul bitset
bitset<2011> aniBisecti;
218
Introducere n S.T.L.
bitset<5> test;
test.set(); // test = 11111 in baza 2
Atenie: dac valoarea dintr-un set de bii este prea mare pentru a fi
reprezentabil pe un ntreg unsigned long, va aprea o eroare!
bitset<32> numar(2010);
// afiseaza 00000000000000000000011111011010
cout << numar.to_string() << endl;
bitset<32> numar(2010);
numar >>= 1;
// afiseaza 00000000000000000000001111101101
cout << numar.to_string() << endl;
numar = ~numar;
219
Capitolul 7
// afiseaza 11111111111111111111110000010010
cout << numar.to_string() << endl;
numar ^= 31;
// afiseaza 11111111111111111111110000001101
cout << numar.to_string() << endl;
a) Algoritmul for_each
220
Introducere n S.T.L.
bool Impar(int x)
{
return x % 2 == 1;
}
int main()
{
vector<int> numere;
numere.push_back(10); numere.push_back(20);
numere.push_back(15); numere.push_back(35);
return 0;
}
221
Capitolul 7
d) Algoritmul equal
int main()
{
int vec1[4] = {1, 2, 3, 4}, vec2[4] = {18, 19, 20, 21};
cout << equal(vec1, vec1 + 4, vec2, EgalMod17) << endl; // afiseaza 1
return 0;
}
e) Algoritmul unique
222
Introducere n S.T.L.
De exemplu:
int main()
{
vector<int> numere;
for ( int i = 1; i <= 10; ++i )
{
numere.push_back(i - 1);
numere.push_back(i);
}
// numere contine 0 1 1 2 2 3 ... 9 9 10
return 0;
}
f) Algoritmul copy
223
Capitolul 7
g) Algoritmul reverse
vecInit = vec;
reverse(vec.begin(), vec.end());
if ( vec == vecInit ) // comparare folosind ==,
// putem folosi si functia equals
cout << "Vectorul dat este palindrom";
else
cout << "Vectorul dat nu este palindrom";
h) Algoritmul rotate
Funcia rotate primete trei parametri: prim, mij, ult i rotete
secvena [prim, ult) n aa fel nct elementul indicat de mij s devin
primul element. Nu se returneaz nimic. De exemplu:
vector<int> numere;
for ( int i = 1; i <= 10; ++i )
numere.push_back(i);
i) Algoritmul random_shuffle
224
Introducere n S.T.L.
vector<int> nrPrime;
nrPrime.push_back(2); nrPrime.push_back(3);
nrPrime.push_back(5); nrPrime.push_back(7);
if ( binary_search(nrPrime.begin(), nrPrime.end(), 3) )
cout << "3 este numar prim!" << endl;
Returneaz un pointer ctre cel mai mic, respectiv cel mai mare
element al unei colecii. De exemplu:
vector<int> nrPrime;
nrPrime.push_back(2); nrPrime.push_back(3);
nrPrime.push_back(5); nrPrime.push_back(7);
226
Algoritmi genetici
8. Algoritmi
genetici
Pentru a nelege algoritmii genetici, n primul rnd trebuie s
nelegem i s cuantificm modelul evoluiei naturale (darwiniste). Modelul
evolutiv presupune existena unui habitat (a unui spaiu de evoluie)
guvernat de legi locale (condiiile de mediu) n care speciile (populaiile
reprezentate de indivizi) se supun urmtorului mecanism:
1. Pe baza seleciei, un numr restrns de indivizi din populaia
iniial vor constitui populaia intermediar de prini (algoritmul
de selecie trebuie s respecte paradigma conform creia un
individ mai bine adaptat s aibe anse mai mari de supravieuire).
2. Din indivizii selectai ca i prini, pe baza operatorilor genetici
(mutaie, ncruciare, ...), se va reconstitui o nou populaie.
CUPRINS
228
Algoritmi genetici
<
1 = 0
229
Capitolul 8
nseamn c c1 este soluia cea mai bun (optimul global), iar pentru
1 < 2 ,
230
Algoritmi genetici
Fig. 8.1.2.
Acest mod se poate realiza cel mai eficient prin procesarea mai
multor posibile soluii simultan, cu diferite puncte de plecare aleatoare.
c) Selecia
Exist mai multe tipuri de selecie, toate acestea avnd scopul ca
implementarea capacitii de supravieuire a unei soluii s fie proporional
cu valoarea funciei de adecvare, aici fiind de fapt implementat paradigma
evoluiei darwiniste survival of the fittest. Una dintre cele mai simple
metode de selecie este selecia bazat pe ordonare (ierarhie), n care se
ordoneaz populaia de soluii astfel nct adaptarea lor s fie
descresctoare, dup care se selecteaz primii n indivizi dorii.
232
Algoritmi genetici
1 2 3 4 5 6
1 1 1 1 1 1
233
Capitolul 8
d) Operatorii genetici
, 0 , 1 , , 1 , , +1 , , 1
1110001010011111 4, 1 0, 0 1 11101010011111
234
Algoritmi genetici
= 0 , 1 , , 1
0, 1 , , 1 , , +1 , 1
= 0 , 1 , , 1
sau
0 , 1 , , 1 , , +1 , 1
= 0 , 1 , , 1
0 , 1 , , 1 | < +1
= 0 , 1 , , 1
0 , 1 , , 0 1 , 0 , 0 +1 , , 1 1 , 1 , 1 +1 , , 2 1 , 2 , 2 +1 ,
Exemplu:
expresie.in expresie.out
4 18 4412
**+
236
Algoritmi genetici
237
Capitolul 8
Cromozom Informaie
A 4 * 4 * 2 + 3
B 1 * 4 * 1 + 2
C 4 * 4 * 1 + 2
Fig. 8.2.1. Operatorul de recombinare aplicat problemei prezentate
#include <fstream>
#include <algorithm>
#include <cstdlib>
238
Algoritmi genetici
239
Capitolul 8
240
Algoritmi genetici
Exerciii:
a) Comparai performana algoritmului cu performana celorlali doi
algoritmi menionai.
b) Cum afecteaz constantele de la nceputul programului timpul de
execuie i memoria folosit?
c) Cum am putea modifica operatorii genetici dac numerele
folosite n expresie ar trebui s fie distincte?
Exemplu:
sistem.in sistem.out
32 312
A1 + A2 - A3 = 2
A1 * A2 / A3 = 1
Explicaie:
3+12=2
3 *1/ 2 =1
Se poate observa c i permutarea (1, 3, 2) ar fi fost valid.
241
Capitolul 8
=1
unde f(i) este rezultatul evalurii expresiei i dac nlocuim
fiecare necunoscut cu permutarea reprezentat de individul
curent, iar g(i) este rezultatul pe care trebuie s l aib expresia i,
adic numrul din dreapta egalului expresiei i. Am gsit o soluie
atunci cnd exist un individ X pentru care F2(X) = 0.
242
Algoritmi genetici
void calc_fitness()
{
for ( int cr = 1; cr < maxpop; ++cr )
{
A[cr].fitness = 0;
for ( int i = 1; i <= M; ++i )
{
int k = 1;
A[cr].fitness += abs(eval(k, cr, A, i) - egal[i]);
}
}
sort(A + 1, A + maxpop);
}
243
Capitolul 8
void noua_gen()
{
int v[maxn];
for ( int i = 1; i <= N; ++i )
v[i] = 0;
for ( int i = maxeli + 1; i < maxpop; ++i )
{
if ( rand() < prob_recomb )
{
int x1 = 1 + rand() % maxeli;
int poz = 1 + rand() % N;
for ( int j = 1; j <= poz; ++j )
{
v[ A[x1].var[j] ] = i;
A[i].var[j] = A[x1].var[j];
}
for ( int j = 1; j <= N; ++j )
if ( v[j] != i )
{
++poz;
A[i].var[poz] = j;
}
}
if ( rand() < prob_mutatie )
{
int x1 = 1 + rand() % N;
int x2 = 1 + rand() % N;
swap(A[i].var[x1], A[i].var[x2]);
}
}
}
Exerciiu:
Implementai n ntregime un program care rezolv problema,
folosind, pe rnd, ambii operatori genetici menionai, precum i ambele
funcii de adecvare descrise. Comparai, pe mai multe date de intrare,
performanele acestora.
244
Algoritmi de programare dinamic
9. Algoritmi de
programare
dinamic
Am prezentat ntr-un capitol anterior noiunile de baz ale metodei
programrii dinamice. n acelai capitol am prezentat cteva probleme
elementare rezolvate, urmnd n acest capitol s prezentm mai multe
aplicaii, att clasice ct i mai avansate, ale programrii dinamice. Tot aici
vom face tranziia de la implementrile mai apropiate de limbajul C folosite
pn acum la implementri C++ care profit mai mult de avantajele oferite
de limbajul C++, cum ar fi librria S.T.L.
245
Capitolul 9
CUPRINS
246
Algoritmi de programare dinamic
Exemplu:
lee.in lee.out
4 6
0111 11
0100 21
0000 31
1110 32
33
34
44
247
Capitolul 9
Iniial avem:
A D
0111 0
0100
0000
1110
248
Algoritmi de programare dinamic
A D
0111 0
0100 1
0000 2
1110
Primul vecin al poziiei (3, 1), este (4, 1), poziia invalid deoarece
conine un zid. Al doilea vecin este poziia (3, 2), poziie valid. Funcia se
autoapeleaz pentru aceast poziie i obinem:
A D
0111 0
0100 1
0000 23
1110
Din poziia (3, 2) prima dat se ncearc vecinul (4, 2), care este ns
un zid. Se va merge n continuare la stnga nc doi pai, pn obinem:
A D
0111 0
0100 1
0000 23 4 5
1110
Din poziia (3, 4) funcia se va apela prima dat pentru poziia (4, 4),
obinndu-se urmtoarea configuraie:
A D
0111 0
0100 1
0000 2 3 4 5
1110 6
249
Capitolul 9
A D
0111 0
0100 16
0000 2 3 4 5
1110 6
Singurul apel recursiv valid din poziia (2, 4) este pentru poziia (2,
3), obinndu-se:
A D
0111 0
0100 1 7 6
0000 2 3 4 5
1110 6
A D
0111 0
0100 1 5 6
0000 2 3 4 5
1110 6
#include <fstream>
250
Algoritmi de programare dinamic
251
Capitolul 9
Iniial avem:
A D
0111 0
0100
0000
1110
p, u
Q: (1, 1)
252
Algoritmi de programare dinamic
A D
0111 0
0100 1
0000
1110
p, u
Q: (1, 1) (2,1)
La urmtorul pas se extrage Q[p], adic (2, 1). Singurul vecin valid
este (3, 1), care se actualizeaz i se introduce n coad:
A D
0111 0
0100 1
0000 2
1110
p, u
Q: (1, 1) (2,1) (3,1)
Similar, singurul vecin valid al poziiei Q[p] = (3, 1) este (3, 2), care
se va introduce n coad i se va actualiza. La urmtorul pas se va extrage
(3, 2) din coad i se va introduce singurul vecin valid al acestei poziii,
(3, 3):
A D
0111 0
0100 1
0000 2 34
1110
p, u
Q: (1, 1) (2,1) (3,1) (3, 2) (3, 3)
Se extrage (3, 3) din Q. Poziia (3, 3) are doi vecini valizi: (3, 4) i
(2, 3), care se actualizeaz i se introduc amndoi n coad:
253
Capitolul 9
A D
0111 0
0100 15
0000 2 34 5
1110
p u
Q: (1, 1) (2,1) (3,1) (3, 2) (3, 3) (3, 4) (2, 3)
A D
0111 0
0100 15 6
0000 2 34 5
1110 6
p u
Q: (1, 1) (2,1) (3,1) (3, 2) (3, 3) (3, 4) (2, 3) (4, 4) (2, 4)
254
Algoritmi de programare dinamic
while ( p <= u )
{
pereche poz = Q[p++]; // extragerea primului element din coada
for ( int i = 0; i < 4; ++i )
{
int newx = poz.x + dx[i];
int newy = poz.y + dy[i];
256
Algoritmi de programare dinamic
while ( !Q.empty() )
{
pair<int, int> poz = Q.front(); // extragerea primului element
Q.pop(); // stergerea efectiva a primului element
Exerciii:
a) Considerm c o persoan pornete din (1, 1) i alta din (N, N).
Cele dou persoane se mic exact n acelai timp. Scriei un
program care determin coordonatele spre care acestea ar trebui
s se ndrepte pentru a se ntlni ct mai rapid.
b) Dai un exemplu pe care soluia recursiv efectueaz cu mult mai
muli pai dect e necesar.
c) Modificai funcia de afiare a drumului astfel nct s afieze
toate drumurile minime existente.
257
Capitolul 9
Exemplu:
subsecv.in subsecv.out
10 11
-6 1 -3 4 5 -1 3 -8 -9 1
258
Algoritmi de programare dinamic
return max;
}
return max;
}
259
Capitolul 9
i 1 2 3 4 5 6 7 8 9 10
A[i] -6 1 -3 4 5 -1 3 -8 -9 1
S[i] -6
i 1 2 3 4 5 6 7 8 9 10
A[i] -6 1 -3 4 5 -1 3 -8 -9 1
S[i] -6 1
260
Algoritmi de programare dinamic
i 1 2 3 4 5 6 7 8 9 10
A[i] -6 1 -3 4 5 -1 3 -8 -9 1
S[i] -6 1 -2 4 9 8 11 3 -6 1
return max;
}
Exerciii:
a) Modificai implementrile date pentru a afia i poziiile de
nceput i de sfrit a unei subsecvene de sum maxim.
b) Se cere o subsecven de produs maxim, iar numerele sunt reale.
Rezolvai problema att pentru numere strict pozitive ct i
pentru numere nenule (dar care pot fi negative).
c) Se d o matrice i se cere determinarea unui dreptunghi de sum
maxim. Ultimul algoritm prezentat poate fi extins pentru
rezolvarea problemei n O(N3). Cum?
261
Capitolul 9
Exemplu:
subsir.in subsir.out
10 5
6 3 8 9 1 2 10 4 -1 11 6 8 9 10 11
262
Algoritmi de programare dinamic
i 1 2 3 4 5 6 7 8 9 10
A[i] 6 3 8 9 1 2 10 4 -1 11
L[i] 1 1 1 1 1 1 1 1 1 1
P[i] 0 0 0 0 0 0 0 0 0 0
i 1 2 3 4 5 6 7 8 9 10
A[i] 6 3 8 9 1 2 10 4 -1 11
L[i] 1 1 2 1 1 1 1 1 1 1
P[i] 0 0 1 0 0 0 0 0 0 0
263
Capitolul 9
i 1 2 3 4 5 6 7 8 9 10
A[i] 6 3 8 9 1 2 10 4 -1 11
L[i] 1 1 2 3 1 2 4 3 1 5
P[i] 0 0 1 3 0 5 4 6 0 7
#include <fstream>
in >> N;
for ( int i = 1; i <= N; ++i )
in >> A[i];
in.close();
}
264
Algoritmi de programare dinamic
int cmlscm(int A[], int N, int L[], int P[]) int main()
{ {
for ( int i = 1; i <= N; ++i ) int N;
L[i] = 1, P[i] = 0; int A[maxn], L[maxn], P[maxn];
return sol;
}
void reconst(int poz, int P[], int A[],
ofstream &out)
{
if ( P[poz] )
reconst(P[poz], P, A, out);
265
Capitolul 9
La pasul (1), dac A[i] este mai mare dect toate elementele diferite
de infinit din L, atunci A[i] se va suprascrie peste cea mai din stnga
valoare egal cu infinit. Putem implementa acest pas eficient n timp O(log
N) folosind o cutare binar.
i 1 2 3 4 5 6 7 8 9 10
A[i] 6 3 8 9 1 2 10 4 -1 11
L[i] inf inf inf inf inf inf inf inf inf inf
P[i]
i 1 2 3 4 5 6 7 8 9 10
A[i] 6 3 8 9 1 2 10 4 -1 11
L[i] 6 inf inf inf inf inf inf inf inf inf
P[i] 1
266
Algoritmi de programare dinamic
i 1 2 3 4 5 6 7 8 9 10
A[i] 6 3 8 9 1 2 10 4 -1 11
L[i] 3 inf inf inf inf inf inf inf inf inf
P[i] 1 1
i 1 2 3 4 5 6 7 8 9 10
A[i] 6 3 8 9 1 2 10 4 -1 11
L[i] -1 2 4 10 11 inf inf inf inf inf
P[i] 1 1 2 3 1 2 4 3 1 5
#include <fstream>
in >> N;
for ( int i = 1; i <= N; ++i )
in >> A[i];
in.close();
}
267
Capitolul 9
int cbin(int st, int dr, int val, int L[]) int cmlscm(int A[], int N, int L[],
{ int P[])
while ( st < dr ) {
{ int lg = 0;
int m = (st + dr) / 2; for ( int i = 1; i <= N; ++i )
if ( L[m] < val ) {
st = m + 1; L[i] = inf;
else
dr = m; int k = cbin(1, lg + 1, A[i], L);
}
// creste lungimea celui mai lung
return st; // subsir?
} if ( L[k] == inf )
++lg;
void reconst(int N, int A[], int P[],
int val, ofstream &out) L[k] = A[i];
{ P[i] = k;
for ( int i = N; i; --i ) }
if ( P[i] == val )
{ return lg;
reconst(i - 1, A, P, val - 1, out); }
out << A[i] << ' ';
int main()
break; {
} int N, A[maxn], L[maxn], P[maxn];
} citire(A, N);
ofstream out("subsir.out");
out.close();
return 0;
}
Exemplu:
sircom.in sircom.out
gatcbccgaatabbat 10
gcbcataabbaggaacba gcbcatabba
Rezolvare
Pentru a rezolva problema vom ncerca s gsim o formul de
recuren pentru calculul lungimii celui mai lung subir comun. Fie L[i][j] =
lungimea celui mai lung subir comun al secvenelor A[1, i] i B[1, j],
pentru 1 i lungime(A) i 1 j lungime(B). S presupunem c avem
calculate toate valorile matricii L care preced elementul (p + 1, q + 1) (sunt
fie pe aceeai linie i pe o coloan precedent, fie pe o linie precedent). Se
disting dou cazuri:
1. Dac A[p + 1] == B[q + 1], atunci putem aduga caracterul
A[p + 1] celui mai lung subir comun al secvenelor A[1, p] i
B[1, q], obinnd, pentru secvenele A[1, p + 1] i B[1, q + 1] un
subir comun de lungime maxim care este mai lung cu un
caracter. Aadar, L[p + 1][q + 1] = L[p][q] + 1.
2. Dac A[p + 1] != B[q + 1], atunci nu putem extinde niciun
subir de lungime maxim calculat anterior i va trebui s salvm
n L[p + 1][q + 1] lungimea celui mai lung subir de lungime
maxim calculat pn acuma. Aceast valoare este dat de
maximul dintre L[p][q + 1] i L[p + 1][q].
269
Capitolul 9
return L[A.length()][B.length()];
}
270
Algoritmi de programare dinamic
if ( A[x - 1] == B[y - 1] )
{
reconst(x - 1, y - 1, A, B, L, out);
out << A[x - 1];
}
else
{
if ( L[x - 1][y] > L[x][y - 1] )
reconst(x - 1, y, A, B, L, out);
else
reconst(x, y - 1, A, B, L, out);
}
}
int main()
{
string A, B;
int L[maxn][maxn];
citire(A, B);
ofstream out("sircom.out");
out.close();
return 0;
}
271
Capitolul 9
liniei curente, iar cellalt vector, L2, va reprezenta chiar linia curent. Noua
form a formulei de recuren este:
1 1 + 1 = []
2 =
max
(1 , 2 1 )
return L1[B.length()];
}
Exerciii:
a) Afiai ntreaga matrice L pentru a nelege mai bine formula de
recuren.
b) Afiai toate subirurile comune de lungime maxim.
c) n implementarea de mai sus am transmis parametrii A i B prin
referin. Unde era indicat s se foloseasc transmitere prin
referin constant?
d) Scriei o implementare care folosete vectori clasici de caractere
n loc de tipul string.
e) Scriei un program care afieaz acel subir comun de lungime
maxim care este primul din punct de vedere alfabetic.
f) Se poate evita copierea vectorului L2? Dac da, cum?
Exemplu:
inmopt.in inmopt.out
3 64
4325
273
Capitolul 9
#include <fstream>
274
Algoritmi de programare dinamic
return M[1][N];
}
Exerciii:
a) De ce i trebuie s porneasc de la N 1 i nu de la 1? Ce se
ntmpl dac i merge de la 1 la N?
b) Concepei o modalitate de a reconstitui soluia. Pentru exemplul
dat, o reconstituire a soluiei ar putea fi (A1*A2)*A3.
275
Capitolul 9
Exemplu:
rucsac1.in rucsac1.out
4 13 22
10 9 32
4 10
5 12
13 20
276
Algoritmi de programare dinamic
F[j Gi] + Vi este mai mare dect F[i], nseamn c putem obine o soluie
mai bun adugnd obiectul i obiectelor cu greutatea j Gi, a cror valoare
este F[j Gi].
Este important s iterm variabila j de la C la Gi i nu invers
deoarece, n caz contrar, am putea ajunge n situaia de a folosi un obiect de
mai multe ori: s presupunem c pentru a calcula un F[k] se folosete
valoarea F[k Gi]. Atunci, dac pentru a calcula F[k + Gi] se va folosi
valoarea F[k], obiectul i va fi folosit de dou ori, lucru nepermis. Iterndu-l
pe j de la C la Gi ne asigurm c fiecare obiect va fi folosit o singur dat n
calculul lui F.
Complexitatea algoritmului este O(NC), deoarece parcurgem pentru
fiecare obiect citit vectorul F (de lungime C) pentru a-l actualiza.
Complexitatea este pseudopolinomial, dar n practic de cele mai multe
ori algoritmul este mai eficient dect ar sugera acest rezultat, deoarece nu se
parcurge aproape niciodat ntreg vectorul F. Memoria suplimentar este
O(C).
Pentru a putea reconstitui soluia, vom folosi un vector P unde
P[i] = ultimul element care a intrat n calculul valorii F[i]. Pentru a afla
soluia, vom proceda similar cu celelalte probleme, atta doar c nu mai
avem nevoie de o funcie recursiv, deoarece de data aceasta nu ne
intereseaz ordinea de afiare i c va trebui s pornim de la suma greutile
obiectelor alese de ctre algoritm i nu de la C.
#include <fstream>
in.close();
}
277
Capitolul 9
while ( P[C] )
{
out << P[C] << ' ';
C -= A[ P[C] ].G;
}
}
Exerciii:
a) Afiai indicii obiectelor cresctor.
b) Afiai vectorii F i P dup fiecare actualizare a lor.
c) Dai exemplu de un set de date de intrare pentru care algoritmul
execut un numr maxim de operaii.
278
Algoritmi de programare dinamic
rucsac2.in rucsac2.out
4 13 32
10 9 223
4 10
5 12
13 20
279
Capitolul 9
Exerciii:
a) Rezolvai o variant a problemei n care fiecare obiect i poate fi
folosit de cel mult Nri ori.
b) Rezolvai o variant a problemei n care obiectele pot avea
greuti negative.
c) Implementai un algoritm greedy pentru rezolvarea celor dou
probleme. Ct de mare poate ajunge s fie diferena dintre soluia
optim i soluia dat de algoritmul greedy?
d) Implementai un algoritm genetic pentru rezolvarea celor dou
probleme. Ct de aproape de soluia optim este acesta?
Comparai rezultatele algoritmului genetic cu rezultatele
algoritmului greedy.
Exemplu:
plata1.in plata1.out
7 23 1367
8 3 2 5 7 3 10
280
Algoritmi de programare dinamic
281
Capitolul 9
void dinamica(int A[], int N, int S) void random(int A[], int N, int S)
{ {
bool F[maxn]; int sel[maxn], nesel[maxn];
int P[maxn]; // sel[0], nesel[0] sunt numarul de
for ( int i = 0; i <= S; ++i ) // elemente
F[i] = P[i] = 0; // 0 == false sel[0] = sel[0] = 0;
int stmp = 0;
F[0] = true; srand((unsigned)time(0));
for ( int i = 1; i <= N; ++i ) for ( int i = 1; i <= N; ++i )
for ( int j = S; j >= A[i]; --j ) if ( rand() % 2 )
{ sel[ ++sel[0] ] = i, stmp += A[i];
F[j] |= F[j - A[i]]; else
nesel[ ++nesel[0] ] = i;
if ( F[j - A[i]] && !P[j] )
P[j] = i; while ( stmp != S )
} if ( stmp > S )
{
ofstream out("plata1.out"); int poz = 1 + (rand() % sel[0]);
while ( S ) stmp -= A[ sel[poz] ];
{ nesel[ ++nesel[0] ] = sel[poz];
out << P[S] << ' '; sel[poz] = sel[ sel[0]-- ];
S -= A[ P[S] ]; }
} else
out.close(); {
} int poz = 1 + (rand() % nesel[0]);
stmp += A[ nesel[poz] ];
sel[ ++sel[0] ] = nesel[poz];
nesel[poz] = nesel[ nesel[0]-- ];
}
ofstream out("plata1.out");
for ( int i = 1; i <= sel[0]; ++i )
out << sel[i] << ' ';
out.close();
}
Exerciii:
a) Concepei un algoritm care afieaz soluia cu numr minim de
numere.
b) Cum se pot afia toate soluiile?
c) Ce se ntmpl dac pot exista numere mai mari ca S? Dar dac
exist i numere negative?
d) ncercai s gsii date de intrare pe care algoritmul randomizat
s ruleze mult timp.
282
Algoritmi de programare dinamic
Exemplu:
plata2.in plata2.out
7 23 11233
8 3 2 5 7 3 10
Exerciii:
a) Scriei o implementare recursiv pentru ultimele patru probleme
prezentate. Folosii tehnica memoizrii.
283
Capitolul 9
Exemplu:
nrpart.in nrpart.out
7 15
100 190569292
Explicaie:
7=7
1+6=7
1+1+5=7
...
284
Algoritmi de programare dinamic
1. Numrm doar partiiile pantru care cel mai mic numr folosit
este K, acestea fiind n numr de nrpart(N K, K), deoarece,
dac adugm numrul K fiecrei partiii a numrului N K
(care nu va conine termeni mai mici dect K) atunci obinem
partiii a numrului N.
2. Numrm doar partiiile lui N care conin termeni strict mai mari
dect K. Acestea vor fi n numr de nrpart(N, K + 1), deoarece
o partiie cu termeni de valoare cel puin K care nu conine
termeni de valoare K trebuie s aib toi termenii cel puin
K + 1.
285
Capitolul 9
Exerciii:
a) Scriei un program care afieaz numrul de partiii ale lui N
formate doar din numere prime.
b) Care este complexitatea algoritmului de numrare a partiiilor?
c) Scriei un program care folosete o implementare iterativ a
formulei de recuren.
286
Algoritmi de programare dinamic
Exemplu:
lev.in lev.out
afara 3
afacere
287
Capitolul 9
Aadar:
D[p + 1][q + 1] = 1 + minim(D[p][q],D[p + 1][q],D[p][q + 1]).
return D[A.length()][B.length()];
}
288
Algoritmi de programare dinamic
Exerciii:
a) Complexitatea algoritmului de calcul a distanei Levenshtein este
O(NM), unde N i M reprezint lungimile celor dou iruri.
Putem ns optimiza algoritmul dac tim c putem transforma
irul A n irul B ntr-un numr relativ mic de operaii k. Cum ne
poate ajuta aceast informaie?
b) Considerm existena unor costuri pentru fiecare operaie precum
i pentru caracterele asupra crora se efectueaz operaii. Scriei
un program care rezolv aceast variant a problemei.
c) Scriei un program care afieaz noul ir A pentru fiecare
operaie efectuat.
d) Scriei un program care determin numrul minim de caractere
care trebuie inserate ntr-un ir pentru a-l transforma ntr-un
palindrom.
Exemplu:
joc.in joc.out
3 115
4 5 6 3 2 1
9 100 6 8 4 7
289
Capitolul 9
Vom alege maximul celor dou cazuri, deci S[i][j] = max(C1, C 2).
Ordinea de completare a matricei S este similar cu ordinea folosit
n algoritmul de rezolvare a recurenei pentru problema nmulirii optime a
unui ir de matrice. Vom ncepe cu i de la 2N 2 i cu j de la i + 2,
asigurndu-ne n acest mod c nu vom folosi valori ale matricei necalculate
nc.
Iniializarea valorilor S[i][i + 1] este necesar deoarece la fiecare pas
fie vom scdea 2 din j fie vom aduna 2 la i, aceast iniializare avnd rolul
de a evita unele cazuri particulare care pot aprea din cauza acestui lucru.
#include <fstream>
using namespace std;
const int maxn = 101;
290
Algoritmi de programare dinamic
return S[1][N];
}
int main()
{
int N, V[maxn], S[maxn][maxn];
citire(V, N);
ofstream out("joc.out");
out << joc(V, N, S);
out.close();
return 0;
}
Exerciii:
a) Modificai algoritmul astfel nct s afieze fiecare numr ales
mpreun cu juctorul care a ales acel numr.
b) Rezolvai problema considernd c se pot alege k numere
consecutive dintr-un capt al irului.
c) Reducei memoria folosit de algoritm la O(N).
d) Modificai implementarea prezentat astfel nct s afieze care
juctor ctig jocul.
e) Rezolvai problema considernd 3 juctori i 3N numere.
291
Capitolul 9
Exemplu:
rmq.inrmq.out
93 1
819344526 2
13 9
58
33
Fie M[i][j] = cel mai mic numr din subsecvena A[j, j + 2i 1].
Altfel spus, M[i][j] reprezint cel mai mic numr din subsecvena care
ncepe pe poziia j i are lungimea 2i. Vom prezenta mai nti modul de
construcie al acestei matrici iar apoi algoritmul prin care vom rspunde la
ntrebri.
Pentru i = 0 obinem subsecvene de forma A[j, j], aadar avem
M[0][j] = A[j] pentru fiecare element j.
Fie log2[i] = parte ntreag din log 2(i) pentru fiecare i de la 0 la n.
Pentru fiecare i astfel nct 0 < i log2[N] vom calcula vectorul M[i] astfel:
pentru fiecare j 1 astfel nct s aib loc j + 2i 1 N vom efectua operaia
M[i][j] = min(M[i 1][j], M[i 1][j + 2i 1]).
S demonstrm c acest mod de calculare este corect:
292
Algoritmi de programare dinamic
293
Capitolul 9
i\j 1 2 3 4 5 6 7 8 9
0 8 1 9 3 4 4 5 2 6
1
1
3
3
1
4
4
2
2
1
1
3
2
3
2
2
1
3
1
Fig. 9.14.1. Modul de execuie al algoritmului R.M.Q.
294
Algoritmi de programare dinamic
#include <fstream>
using namespace std;
const int maxn = 101;
const int maxlog = 7;
int main()
{
int N, T, log2[maxn], M[maxlog][maxn];
ifstream in("rmq.in");
citire(M, N, T, in);
preproc(N, M, log2);
ofstream out("rmq.out");
int x, y;
while ( T-- )
{
in >> x >> y;
int L = log2[y - x + 1];
295
Capitolul 9
Exerciii:
a) Modificai algoritmul astfel nct s afieze poziia numrului
minim n irul dat.
b) Modificai algoritmul astfel nct s afieze cel mai mare element
din ir, precum i poziia acestuia.
c) Extindei algoritmul pentru gsirea celui mai mic sau celui mai
mare element dintr-un dreptunghi al unei matrice.
d) Ce se ntmpl dac interschimbm cele dou dimensiuni ale
matricei? Comparaii timpii de execuie a celor dou variante de
implementare i ncercai s explicai eventualele diferene.
Exemplu:
paran.in paran.out
3 1
100
-2 -1
296
Algoritmi de programare dinamic
i
1 + 1 [] [ + 1][] = 1
= + 1 [] = 2
= + 1 + [] [ + 1][] = 3
298
Algoritmi de programare dinamic
T[i][j] = t;
F[i][j] = f;
}
return T[1][N];
}
299
Capitolul 9
9.15. Concluzii
Am prezentat n acest capitol probleme a cror soluii folosesc
programarea dinamic. Metoda programrii dinamice este o metod foarte
util pentru rezolvarea problemelor de informatic, dar este i metoda cea
mai grea de stpnit, ntruct problemele care se rezolv printr-un algoritm
de programare dinamic pot fi foarte variate, deci este nevoie de experien
pentru a putea gsi anumite recurene..
Propunem aadar spre rezolvare urmtoarele probleme:
300
Algoritmi de geometrie computaional
10. Algoritmi de
geometrie
computaional
Toate problemele de informatic au la baz probleme matematice,
informatica fiind de fapt o ramur a matematicii aplicate. Pn acum am
prezentat probleme i algoritmi care au o legturi cu domenii precum
algebra, teoria numerelor i logica matematic.
n acest capitol vom prezenta metode de rezolvare a unor probleme
de geometrie cu ajutorul calculatorului. Acest domeniu de studiu se numete
geometrie computaional. Aceast ramur a informaticii are aplicaii
practice importante n programe de grafic (aplicaii CAD, aplicaii de
modelare 2d i 3d etc.), proiectarea circuitelor integrate i altele.
301
Capitolul 10
CUPRINS
302
Algoritmi de geometrie computaional
float t = 0.0;
for ( int i = 0; i < 20; ++i ) t += 0.1;
t *= 10000000;
cout << (int)t;
struct Punct
{
double x, y;
Punct(double abscisa, double ordonata) : x(abscisa), y(ordonata) {}
Punct() {}
};
...
Punct P(1, 2);
cout << P.x << << P.y; // afiseaza 1 2
304
Algoritmi de geometrie computaional
struct Dreapta
{
double a, b, c;
Dreapta(double p, double q, double r) : a(p), b(q), c(r) {}
Dreapta() {}
};
...
Dreapta d(1, -1, 0); // reprezinta dreapta din figura anterioar
305
Capitolul 10
2 1
= .
2 1
306
Algoritmi de geometrie computaional
1 : 1 + 1 + 1 = 0
2 : 2 + 2 + 2 = 0
2 1 1 2
=
1 2 2 1
307
Capitolul 10
return true;
}
308
Algoritmi de geometrie computaional
. . . . . . . . < 0
Dac aceste dou condiie sunt ndeplinite, atunci tim sigur c cele
dou segmente se intersecteaz. Ne-am putea pune acum problema
determinrii punctului de intersecie dintre acestea. Dac tim c dou
309
Capitolul 10
struct Segment
{
Punct A, B;
Segment(Punct P1, Punct P2) : A(P1), B(P2) {}
Segment() {}
};
int Orientare(const Punct &A, const Punct &B, const Punct &C)
{
double temp = (B.y A.y)*(C.x A.x) (C.y A.y)*(B.x A.x);
if ( temp < 0 )
return -1;
else if ( temp == 0 )
return 0;
else
return 1;
}
310
Algoritmi de geometrie computaional
1 1 2 3 1
= + 2 + +
2 1 2 2 3 1
Y1 Y2 Y3 ... YN Y1
+ + + + +
Fig. 10.6.2. Vizualizarea formulei de arie a unui poligon
311
Capitolul 10
312
Algoritmi de geometrie computaional
| | = + 1
2
[] = . . , . . + 1
= +1
2
313
Capitolul 10
314
Algoritmi de geometrie computaional
parcursul algoritmului, minim va conine cel mai mic element din secvena
secv.
O analiz intuitiv a algoritmului este simpl: de fiecare dat cnd
dm de un element mai mic dect cel presupus a fi minimul global, revizuim
presupunerea fcut, considernd acest nou element ca fiind minimul global.
Dup parcurgerea tuturor elementelor, vom avea evident adevratul minim
global.
Algoritmul lui Jarvis are un raionament aproape identic. Fie P1
punctul ales care face sigur parte din nfurtoarea convex. Vom
presupune c segmentul [P1PnewPct] este o muchie a nfurtorii convexe.
Iniial vom considera newPct = 2. Parcurgem acum toate celelalte puncte
date. Dac exist un punct Pq, astfel nct Orientare(P1, Pq, PnewPct) > 0
(sau mai mic ca zero, nu are importan atta timp ct suntem consisteni n
alegere) atunci segmentul [P1Pq] are mai multe anse s aib restul
punctelor ntr-o singur parte dect segmentul [P1PnewPct]. Acest lucru se va
clarifica imediat. Vom seta newPct = q i vom continua algoritmul, cutnd
(fr a reporni cutarea!) un alt q astfel nct Orientare(P 1, Pq, PnewPct) > 0
(atenie, newPct este acuma egal cu vechiul q).
La finalul acestui pas, PnewPct va face sigur parte din nfurtoarea
convex. Mai mult, segmentul [P1PnewPct] va avea toate punctele date ntr-o
singur parte.
Se reia algoritmul de la urmtorul punct de pe nfurtoarea
convex, adic PnewPct. Acesta va juca acum rolul lui P1. Se continu pn
cnd se ajunge din nou la punctul de nceput. Deoarece se execut O(N) pai
pentru fiecare punct de pe nfurtoarea convex deducem c timpul de
execuie a ntregului algoritm este O(Nh).
316
Algoritmi de geometrie computaional
#include <iostream>
#include <vector>
317
Capitolul 10
struct Punct
{
double x, y;
Punct(double abscisa, double ordonata) : x(abscisa), y(ordonata) {}
Punct() {}
// se vor compara puncte, deci trebuie sa
// supraincarcam operatorii de egalitate
bool operator ==(const Punct &other)
{
return x == other.x && y == other.y;
}
bool operator !=(const Punct &other)
{
return x != other.x || y != other.y;
}
};
int Orientare(const Punct &A, const Punct &B, const Punct &C)
{
double temp = (B.y - A.y)*(C.x - A.x) - (C.y - A.y)*(B.x - A.x);
if ( temp < 0 )
return -1;
else if ( temp == 0 )
return 0;
else
return 1;
}
double x, y;
for ( int i = 1; i <= N; ++i )
{
cin >> x >> y;
P.push_back(Punct(x, y));
}
}
318
Algoritmi de geometrie computaional
vector<Punct> CH;
CH.push_back(P[start]);
for ( int i = 0; i < CH.size(); ++i )
{
Punct nextPct = (CH[i] == P[0]) ? P[1] : P[0];
if ( nextPct != CH[0] )
CH.push_back(nextPct);
}
return CH;
}
int main()
{
int N;
vector<Punct> P;
Citire(P, N);
vector<Punct> CH = Jarvis(P);
cout << endl;
for ( int i = 0 ; i < CH.size(); ++i )
cout << CH[i].x << " " << CH[i].y << endl;
return 0;
}
Exerciii:
a) n ce ordine se afieaz punctele de pe nfurtoare?
b) Cum se poate modifica algoritmul astfel nct punctele de pe
nfurtoare s fie afiate n alt ordine?
c) Dai exemplu de un set de puncte pe care algoritmul efectueaz
un numr nefavorabil de pai.
319
Capitolul 10
320
Algoritmi de geometrie computaional
321
Capitolul 10
vector<Punct> Graham(vector<Punct> P)
{
int start = 0; // vectorii incep de la 0
for ( int i = 1; i < P.size(); ++i )
if ( P[i].x<P[start].x || (P[i].x == P[start].x && P[i].y>P[start].y) )
start = i;
Sorter::Sort(P, start);
P.insert(P.begin(), P[P.size() - 1]);
int nrH = 2;
for ( int i = 3; i < P.size(); ++i )
{
while ( nrH > 1 && Orientare(P[nrH - 1], P[nrH], P[i]) > 0 )
--nrH;
++nrH;
swap(P[nrH], P[i]);
}
322
Liste nlnuite
11. Liste
nlnuite
Acest capitol prezint noiunile elementare despre liste nlnuite
(simplu nlnuite, dublu nlnuite i circulare). Vor fi prezentate noiuni
teoretice i detalii de implementare.
Listele nlnuite au anumite avantaje i dezavantaje relativ la
tablouri (vectori). Principalul dezavantaj este c nu suport accesul aleator:
pentru a accesa al k-lea element al unei liste, este necesar s parcurgem
toate elementele anterioare. Principalul avantaj este c listele suport
inserri mai eficiente, mai ales la sfritul i nceputul acestora (acestea se
pot face n timp constant.
Aadar, alegerea dintre liste i tablouri trebuie fcut n funcie de
natura problemei pe care vrem s o rezolvm. Vom prezenta n acest capitol
i alte avantaje i dezavantaje.
323
Capitolul 11
CUPRINS
324
Liste nlnuite
int *V;
325
Capitolul 11
struct nod
{
T informatie;
nod *link_;
};
326
Liste nlnuite
struct nod
{
int info;
nod * link_;
}
327
Capitolul 11
328
Liste nlnuite
329
Capitolul 11
// legtura
Temp->link_ = New;
// valoarea de returnat
return Old;
}
330
Liste nlnuite
//legtura
Temp->link_ = New;
//valoarea de returnat
return Old;
}
}
331
Capitolul 11
Este foarte important ordinea (1) i (2), deoarece n caz contrar prin
Temp->link_ = New; (1)
Spre adresa nodului 3 nu mai pointeaz nimic i
New->link_ = Temp->link_; (2)
Creeaz structura din figura de mai jos, n care se pierd toate datele
de la nodul 3 ncolo.
332
Liste nlnuite
// daca noua valoarea este mai mica sau egala cu cea a primului
// element, atunci aceasta se adauga la inceput
if ( New->info <= Old->info )
{
New->link_ = Old; Old = New;
}
else
{
// caut unde trebuie adaugat noul nod
nod *Temp;
for ( Temp = Old; Temp->link_ != NULL; Temp = Temp->link_ )
if ( New->info <= Temp->link_->info )
break; // am gasit pozitia pe care trebuie adaugat nodul
if ( Temp->link_ == NULL ) // adaugare la sfarsit
{
New->link_ = NULL; Temp->link_ = New;
}
else // nodul se adauga undeva in interior
{
New->link_ = Temp->link_; Temp->link_ = New;
}
}
}
return Old;
}
333
Capitolul 11
delete ToDel;
}
return Old;
}
Dac lista are un singur element sau este NULL, atunci returnm
NULL;
335
Capitolul 11
delete ToDel;
}
else
{
nod *Temp;
for ( Temp = Old; Temp->link_->link_; Temp = Temp->link_ );
delete ToDel;
}
}
return Old;
}
336
Liste nlnuite
Fig. 11.2.9. tergerea unui nod din interiorul unei liste nlnuite
337
Capitolul 11
view_rev(Old->link_);
cout << Old->info << ' ';
}
338
Liste nlnuite
a) Stiva
b) Coada
Se deriveaz din list, prin adugarea condiiei prin care ultimul nod
nu va pointa la NULL, ci va pointa la nceputul listei. Aceast condiie
restricioneaz adugarea i tergerea unor elemente la funciile add_mid i
del_mid, deoarece lista nu mai are nceput i sfrit.
340
Liste nlnuite
Exemplu:
cavaleri.in cavaleri.out
6 24135
341
Capitolul 11
nod *Temp;
for ( Temp = LISTA; Temp->link_ != NULL; Temp = Temp->link_ );
Temp->link_ = LISTA; // lista devine circulara
while ( nr < N - 1 )
{
for ( int i = 0; i < nr; ++i )
LISTA = LISTA->link_;
LISTA->link_ = LISTA->link_->link_;
delete Tmp;
++nr;
}
out.close();
}
int main()
{
int N;
ifstream in("cavaleri.in");
in >> N;
in.close();
return 0;
}
342
Liste nlnuite
struct nod
{
T info;
nod *link_;
nod *_link; // pointer catre nodul anterior
};
343
Capitolul 11
struct Lista
{
nod *adrp;
nod *adru;
};
344
Liste nlnuite
struct nod
{
int info;
nod *link_;
nod *_link;
};
Fig. 11.4.4. Adugarea unui nod la nceputul unei liste dublu nlnuite
Old->_link = New;
Old = New;
return Old;
}
345
Capitolul 11
Fig. 11.4.5. Adugarea unui nod la sfritul unei liste dublu nlnuite
nod *Temp;
for ( Temp = Old; Temp->link_ != NULL; Temp = Temp->link_ );
Temp->link_ = New;
New->_link = Temp;
return Old;
}
346
Liste nlnuite
Fig. 11.4.6. Adugarea unui nod n interiorul unei liste dublu nlnuite
347
Capitolul 11
348
Liste nlnuite
Dup care:
349
Capitolul 11
350
Liste nlnuite
#include <iostream>
struct nod
{
int info;
nod *link_;
nod *_link;
};
if ( Old != NULL )
Old->_link = New;
Old = New;
}
Old->link_ = New;
New->_link = Old;
Old = New;
}
351
Capitolul 11
Temp->link_->_link = New;
Temp->link_ = New;
}
}
}
}
delete ToDel;
}
352
Liste nlnuite
if ( Temp->link_ != NULL )
{
nod *ToDel = Temp->link_;
Temp->link_ = Temp->link_->link_;
if ( Temp->link_ != NULL )
Temp->link_->_link = Temp;
delete ToDel;
}
}
}
353
Capitolul 11
1
Profesor de informatic la Universitatea Stanford.
354
Teoria grafurilor
12. Teoria
grafurilor
Teoria grafurilor este un domeniu al matematicii care se ocup cu
studiul structurilor matematice numite grafuri. Un graf este o reprezentare
abstract a relaiilor existente ntre elementele unui set suport. Elementele
din setul suport se numesc noduri, iar relaiile existente ntre acestea se
numesc muchii sau arce (termen folosit uneori n cazul grafurilor orientate).
355
Capitolul 12
CUPRINS
356
Teoria grafurilor
357
Capitolul 12
12. Un graf complet este un graf fr bucle care are muchie ntre
oricare dou noduri. Numrul de muchii al unui graf neorientat
complet este egal cu:
( 1)
2
( 1)
.
Numrul de grafuri neorientate cu N noduri este dat de formula:
(1)
2 2
358
Teoria grafurilor
359
Capitolul 12
360
Teoria grafurilor
361
Capitolul 12
a) Declaraii
b) Iniializri
graf *G[maxn];
for ( int i = 1; i < maxn; ++i ) G[i] = NULL;
362
Teoria grafurilor
363
Capitolul 12
364
Teoria grafurilor
365
Capitolul 12
return 0;
}
366
Teoria grafurilor
367
Capitolul 12
#include <fstream>
using namespace std;
const int maxn = 101;
int main()
{
int N, T[maxn];
bool V[maxn];
ifstream in("graf.in"); ofstream out("graf.out");
in >> N;
for ( int i = 1; i <= N; ++i )
{
in >> T[i];
V[i] = false;
}
for ( int i = 1; i <= N; ++i )
V[ T[i] ] = true;
for ( int i = 1; i <= N; ++i )
if ( !V[i] )
out << i << ' ';
in.close();
out.close();
return 0;
}
d) Alte probleme
368
Teoria grafurilor
Programul urmtor citete din fiierul dfs.in un graf dat prin lista de
muchii i afieaz n fiierul dfs.out nodurile grafului n ordinea n care au
fost parcurse de ctre algoritm, pornind de la nodul 1.
370
Teoria grafurilor
Exemplu:
dfs.in dfs.out
45 1324
13
23
31
34
14
371
Capitolul 12
n cazul arborilor binari (arbori n care fiecare nod are cel mult doi
fii), mai exist i parcurgerea n ordine. Aceasta presupune prelucrearea
nodului curent dup apelul recursiv pentru subarborele stng i nainte de
apelul recursiv pentru subarborele drept. O astfel de implementare este
lsat ca exerciiu pentru cititor.
Aceste trei parcurgeri au aplicaii n algoritmica arborilor de expresii
i n determinarea componentelor tare conexe a unui graf.
372
Teoria grafurilor
373
Capitolul 12
#include <fstream>
#include <vector>
int x, y;
for ( int i = 1; i <= M; ++i )
{
in >> x >> y;
G[x].push_back(y);
G[y].push_back(x);
}
in.close();
}
374
Teoria grafurilor
return false;
}
int main()
{
int N, M;
int pred[maxn];
vector<int> G[maxn];
citire(G, N, M);
for ( int i = 1; i <= N; ++i )
pred[i] = -1;
ofstream out("dfs.out");
out << DFS(G, 1, pred);
out.close();
return 0;
}
Exerciii:
a) Modificai algoritmul n aa fel nct s i afieze un ciclu n
cazul existenei unuia.
b) Cum s-ar putea rezolva problema pentru grafuri orientate?
c) Scriei un program care afieaz toate ciclurile existente ntr-un
graf.
375
Capitolul 12
Exemplu:
dfs.in dfs.out
5 3
12
23
24
45
376
Teoria grafurilor
#include <fstream>
#include <vector>
377
Capitolul 12
ofstream out("dfs.out");
out << max;
out.close();
return 0;
}
Exerciii:
a) Rezolvai aceeai problem pe un graf ponderat (fiecare muchie
are asociat un anumit cost).
b) Modificai programul n aa fel nct s afieze i nodurile
drumului.
c) Implementare iterativ
378
Teoria grafurilor
ales dac folosim vectori pentru reinerea listelor de adiacen sau liste
nlnuite pe care nu dorim s le distrugem n timpul prelucrrii. Funcia
prezentat este doar orientativ; cititorul este sftuit s o studieze i s o
mbunteasc. n practic, astfel de implementri sunt foarte rar ntlnite
sau necesare.
if ( !depus )
--k;
}
}
Exerciii:
a) Folosii parcurgerea n adncime pentru a determina
componentele conexe ale unui graf.
b) Implementai varianta iterativ a parcurgerii n adncime
folosind liste nlnuite.
379
Capitolul 12
380
Teoria grafurilor
381
Capitolul 12
C.pop();
}
out.close();
}
382
Teoria grafurilor
383
Capitolul 12
384
Teoria grafurilor
cul[1] = 1;
for ( int i = 2; i <= N; ++i )
cul[i] = 0;
C.push(1);
while ( !C.empty() )
{
int nod = C.front();
C.push( G[nod][i] );
}
else if ( cul[ G[nod][i] ] == cul[nod] ) // trebuie schimbata
// culoarea
return false;
C.pop();
}
return true;
}
Exerciii:
a) Implementai algoritmul prezentat folosind parcurgerea n
adncime.
b) Considerm funciile f definite pe mulimea numerelor naturale
cu valori tot n mulimea numerelor naturale. Dai exemple de
funcii pentru care graful format din muchiile (x, f(x)) este
bipartit i de funcii pentru care acelai graf nu este bipartit, cnd
x parcurge pe rnd numerele naturale.
385
Capitolul 12
b) Sortarea topologic
386
Teoria grafurilor
queue<int> C;
for ( int i = 1; i <= N; ++i )
if ( gr[i] == 0 )
C.push(i);
while ( !C.empty() )
{
int nod = C.front();
out << nod << ' ';
if ( gr[ G[nod][i] ] == 0 )
C.push( G[nod][i] );
}
C.pop();
}
}
387
Capitolul 12
388
Teoria grafurilor
389
Capitolul 12
390
Teoria grafurilor
391
Capitolul 12
Semnificaia primului set de valori este evident. Cel de-al doilea set
de valori ne ajut s verificm pentru un nod nod dac tergerea lui
pstreaz subarborele su conectat de restul grafului (adic dac nod este
sau nu punct de articulaie). Dac tergerea lui nod pstreaz graful conex,
atunci minim[i] trebuie s fie strict mai mic dect D[nod], unde i este un fiu
al lui nod (cu alte cuvinte, exist o muchie de ntoarcere de la unul dintre
descendenii lui nod la unul dintre strmoii si). Altfel, dac minim[i] este
mai mare sau egal cu D[nod], nod este nod critic.
392
Teoria grafurilor
393
Capitolul 12
void DFS(vector<int> G[], int D[], int minim[], int nod, int tata, int ad,
ofstream &out)
{
D[nod] = minim[nod] = ad;
int nrf = 0; // nr de fii ai lui *nod* in arborele DFS
bool critic = 0;
for ( int i = 0; i < G[nod].size(); ++i )
if ( G[nod][i] != tata ) // am grija sa nu merg inapoi de unde am venit
if ( D[ G[nod][i] ] == -1 )
{
++nrf;
DFS(G, D, minim, G[nod][i], nod, ad + 1, out);
minim[nod] = min(minim[nod], minim[ G[nod][i] ]);
Exerciii:
a) Problema se poate rezolva i mai intuitiv, dar mai puin eficient.
Care ar fi un algoritm naiv de rezolvare?
b) Modificai programul prezentat astfel nct s afieze muchiile
critice, adic acele muchii a cror nlturare ar deconecta graful.
c) Modificai programul prezentat astfel nct s afieze
componentele biconexe ale grafului.
394
Teoria grafurilor
395
Capitolul 12
396
Teoria grafurilor
397
Capitolul 12
398
Teoria grafurilor
399
Capitolul 12
400
Teoria grafurilor
401
Capitolul 12
return;
}
V[nod] = true;
for ( int i = 1; i <= N; ++i )
if ( G[nod][i] )
hamilton(G, N, i, nr + 1,
c + G[nod][i], cmin,
V, st, sol);
V[nod] = false;
}
402
Teoria grafurilor
404
Teoria grafurilor
405
Capitolul 12
406
Teoria grafurilor
Exerciii:
a) Ce se ntmpl dac folosim const int inf = 1 << 30; ?
b) Modificai algoritmul astfel nct s determine dac un graf este
conex (sau tare conex).
c) Memorai, pentru fiecare pereche (i, j) din cadrul algoritmului de
calculare a costurilor minime, nodul k intermediar ales. Scriei o
funcie de reconstituire care folosete aceste informaii pentru a
gsi mai eficient drumurile.
d) Modificai algoritmul de reconstituire astfel nct s afieze
nodurile unui drum n loc de muchiile unui drum.
408
Teoria grafurilor
409
Capitolul 12
drum(P[N], P, out);
out << N << ' ';
}
410
Teoria grafurilor
int main()
{
int N, M, D[maxn], P[maxn];
vector<PER> G[maxn];
citire(G, N, M);
Dijkstra(G, N, D, P);
ofstream out("dijkstra.out");
out << D[N] << '\n'; // costul minim
drum(N, P, out); // drumul in sine
out.close();
return 0;
}
411
Capitolul 12
Noul algoritm este mult mai eficient atunci cnd nu avem foarte
multe muchii, dar este dificil de implementat. Din fericire, biblioteca S.T.L.
ne vine n ajutor cu tipul priority_queue, care este de fapt un heap. Acest
container a fost prezentat pe scurt deja. Singurul lucru care ngreuneaz
puin implementarea este faptul c avem nevoie de o modalitate de a ordona
coada de prioriti dup un criteriu dat de noi, deoarece n aceasta vom
reine etichetele nodurilor, iar ordonarea vrem s se fac dup distanele
minime pn la acele noduri. Mai mult, tim c priority_queue se comport
ca un max-heap, iar noi avem nevoie de un min-heap.
Pentru a rezolva aceste probleme, vom insera n coad perechi de
forma (D[nod], nod), folosind utilitarul pair. tim c acest container are
definite relaii de ordine n funcie de prima component (avem nevoie de
412
Teoria grafurilor
vector<PER>::iterator i;
for ( i = G[min].begin(); i != G[min].end(); ++i )
if ( D[min] + i->second < D[ i->first ] )
{
D[ i->first ] = D[min] + i->second;
P[ i->first ] = min;
413
Capitolul 12
414
Teoria grafurilor
415
Capitolul 12
416
Teoria grafurilor
Pasul 1
i 1 2 3 4
Q 1
D 0 inf inf inf
P 0
417
Capitolul 12
Pasul 2 Pasul 3
i 1 2 3 4 i 1 2 3 4
Q 1 2 3 Q 1 2 3
D 0 4 2 inf D 0 4 1 inf
P 0 1 1 P 0 1 2
Pasul 4 Pasul 5
i 1 2 3 4 i 1 2 3 4
Q 1 2 3 4 Q 1 2 3 4
D 0 4 1 5 D 0 4 1 5
P 0 1 2 3 P 0 1 2 3
418
Teoria grafurilor
queue<int> Q; Q.push(1);
while ( !Q.empty() )
{
int nod = Q.front();
V[nod] = false;
vector<PER>::iterator i;
for ( i = G[nod].begin(); i != G[nod].end(); ++i )
if ( D[nod] + i->second < D[ i->first ] )
{
D[ i->first ] = D[nod] + i->second;
P[ i->first ] = nod;
if ( !V[ i->first ] )
{
Q.push( i->first );
V[ i->first ] = true;
}
}
Q.pop();
}
}
419
Capitolul 12
420
Teoria grafurilor
cst, iar fiecare linie reprezint nodurile care se afl n fiecare gleat la pasul
respectiv. n practic, se trece i peste gleile goale.
421
Capitolul 12
Faptul c vom opri algoritmul atunci cnd toate cozile sunt goale va
face algoritmul mult mai rapid n practic, datorit faptului c nu vor exista
aproape niciodat un numr mare de distane minime distincte.
Implementarea prezentat conine toate optimizrile discutate. Acest
algoritm este indicat a fi folosit n cazurile n care costurile muchiilor sunt
mici sau distanele minime se repet des. Datorit faptului c folosim
indexarea dup distane, este clar c algoritmul lui Dial nu va funciona
corect n cazul existenei distanelor negative. n cazul general, rmne
aadar preferabil algoritmul Bellman Ford sau algoritmul lui Dijkstra
implementat cu heap-uri.
Menionm c n cadrul implementrii prezentate am presupus c
lungimea maxim a unui arc este 1000. Mai mult, am folosit un vector de
1024 de cozi pentru ca operaia modulo s se poat efectua mai eficient cu
ajutorul operaiilor pe bii. Implementarea conine doar funcia relevant.
422
Teoria grafurilor
423
Capitolul 12
424
Teoria grafurilor
425
Capitolul 12
queue<int> Q;
Q.push(1);
while ( !Q.empty() )
{
int nod = Q.front();
vector<int>::iterator i;
for ( i = G[nod].begin(); i != G[nod].end(); ++i )
if ( F[nod][*i] < C[nod][*i] && !V[*i] )
{
P[*i] = nod;
Q.push(*i);
V[*i] = true;
}
Q.pop();
}
return V[N];
}
426
Teoria grafurilor
int flux_total = 0;
while ( Drum(G, N, C, F, P, V) )
{
int min = inf;
flux_total += min;
for ( int x = N; x != 1; x = P[x] )
{
F[ P[x] ][x] += min;
F[x][ P[x] ] -= min;
}
}
return flux_total;
}
427
Capitolul 12
vector<int>::iterator i;
for ( i = G[nod].begin(); i != G[nod].end(); ++i )
if ( F[nod][*i] < C[nod][*i] && !V[*i] )
{
P[*i] = nod;
Q.push(*i);
V[*i] = 1;
}
}
// ...
while ( Drum(G, N, C, F, P, V) )
{
vector<int>::iterator i;
for ( i = G[N].begin(); i != G[N].end(); ++i )
if ( F[*i][N] < C[*i][N] && V[*i] )
{
P[N] = *i;
int min = inf;
for ( int x = N; x != 1; x = P[x] )
if ( C[ P[x] ][x] - F[ P[x] ][x] < min )
min = C[ P[x] ][x] - F[ P[x] ][x];
if ( !min )
continue;
flux_total += min;
for ( int x = N; x != 1; x = P[x] )
{
F[ P[x] ][x] += min;
F[x][ P[x] ] -= min;
}
}
}
428
Teoria grafurilor
429
Capitolul 12
vector<int>::iterator i;
for ( i = G[min].begin(); i != G[min].end(); ++i )
if ( D[min] + CS[min][*i] < D[*i] &&
C[min][*i] - F[min][*i] > 0 )
{
D[*i] = D[min] + CS[min][*i];
P[*i] = min;
430
Teoria grafurilor
if ( tmp_cst == inf )
break;
return cst_min;
}
431
Capitolul 12
432
Teoria grafurilor
433
Capitolul 12
434
Teoria grafurilor
435
Capitolul 12
return false;
}
436
Teoria grafurilor
do
{
ok = false;
} while ( ok );
ofstream out("cuplaj.out");
for ( int i = 1; i <= N; ++i )
if ( dr[i] )
out << i << ' ' << dr[i] << '\n';
out.close();
}
int main()
{
int N, M;
vector<int> G[maxn];
citire(G, N, M);
HopcroftKarp(G, N);
return 0;
}
437
Capitolul 12
438
Teoria grafurilor
Mai mult, atunci cnd adugm o muchie (x, y), reunim practic
arborele din care face parte x cu arborele din care face parte y. Deoarece un
arbore este identificat n mod unic prin rdcina sa, este de ajuns s unim
rdcinile arborilor lui x i y. Acest lucru l vom face cu ajutorul unei funcii
Merge(x, y) care fie seteaz T[x] = y fie T[y] = x, adic unul dintre arbori
devine subarbore al celuilalt, formndu-se astfel un singur arbore. Cnd se
adaug muchia (x, y), trebuie efectuat apelul Merge( Find(x), Find(y) ).
439
Capitolul 12
#include <fstream>
#include <vector>
#include <algorithm>
using namespace std;
const int maxn = 101;
const int maxm = 201;
440
Teoria grafurilor
int main()
{
int N, M;
muchie E[maxm];
citire(E, N, M);
Kruskal(E, N, M);
return 0;
}
441
Capitolul 12
Algoritmul lui Prim este un alt algoritm de tip greedy folosit pentru
determinarea arborelui parial de cost minim. Acesta are complexitile
O(N2) i O(Mlog N), n funcie de implementarea folosit. Vom prezenta
pe larg doar varianta de implementare n O(N2), deoarece aceasta este
preferabil celorlali algoritmi atunci cnd avem de a face cu un graf dens,
iar varianta n complexitate O(Mlog N) difer de complexitatea
algoritmului lui Kruskal doar printr-o constant i n plus algoritmul lui
Kruskal este mai uor de implementat.
442
Teoria grafurilor
443
Capitolul 12
#include <fstream>
#include <vector>
#include <utility>
444
Teoria grafurilor
12.13. Concluzii
Am prezentat n acest capitol noiunile elementare care stau la baza
algoritmilor de grafuri. Cititorii experimentai poate c au observat lipsa
abordrii unor teme legate de arbori, cum ar fi determinarea L.C.A. sau cum
ar fi arborii binari de cutare. Aceste teme vor fi abordate n cadrul
capitolului Structuri avansate de date.
445
Capitolul 12
13. Structuri
avansate de
date
Am prezentat pn acum o serie de algoritmi fundamentali nsoii de
aplicaii ale acestora i de probleme teoretice care se pot rezolva cu ajutorul
acestora. Au fost prezentate principalele tehnici de programare, biblioteca
S.T.L. i diverse metode de optimizare a algoritmilor. n acest ultim capitol
vom prezenta cteva structuri de date care joac un rol foarte important n
algoritmic, n special n probleme de optimizare.
Practic, acest capitol prezint metode de a rspunde eficient la
interogri de genul se gsete un anumit obiect ntr-o colecie de obiecte
dat anterior?, ntrebri nsoite i de actualizri de tipul adug un nou
obiect coleciei date atnerior. Vom analiza cazurile favorabile, medii i
defavorabile a mai multor structuri de date i vom discuta situaiile n care
fiecare structur este preferabil celorlalte.
Acest capitol va folosi noiuni de grafuri, liste, operaii pe bii,
recursivitate, tehnici de programare, matematic i S.T.L., aa c
recomandm cu trie stpnirea tuturor capitolelor anterioare nainte de
parcurgerea acestui capitol final.
447
Capitolul 13
CUPRINS
448
Structuri avansate de date
L[4] 4 NULL
L[3] 4 8 NULL
L[2] 1 4 8 NULL
L[1] 1 4 6 8 10 NULL
L[0] 0 1 2 3 4 6 7 8 9 10 NULL
Fig. 13.1.1. O list de salt pentru un anumit set de date
449
Capitolul 13
L[4] 4 NULL
L[3] 4 8 NULL
L[2] 1 4 8 NULL
L[1] 1 4 6 8 10 NULL
L[0] 0 1 2 3 4 6 7 8 9 10 NULL
Fig. 13.1.2. Modul de cutare a unei valori ntr-o list de salt
450
Structuri avansate de date
d) Detalii de implementare
Deoarece codul complet al tuturor operaiilor ar fi prea voluminos i
greu de neles la prima vedere, vom prezenta pe rnd i cu explicaii fiecare
structur i metod.
n primul rnd vom folosi o structur Node, care va reprezenta un
nod, caracterizat prin informaia reinut i legaturile acestuia.
n al doilea rnd vom folosi o structur List, care va conine
nceputul listei, pentru a putea evita nite cazuri particulare i a simplifica
implementarea. Aadar, structurile folosite sunt:
451
Capitolul 13
452
Structuri avansate de date
return false;
}
453
Capitolul 13
f) mbuntiri
454
Structuri avansate de date
Exerciii:
a) Implementai variant determinist a listelor de salt.
b) Implementarea prezentat pune accentul pe simplitate. De
exemplu, ideal ar fi s i eliberm memoria aferent unui nod
dup tergerea acestuia. Ce alte optimizri s-ar mai putea face i
cum ar putea fi acestea implementate?
c) Listele de salt pot fi folosite pentru sortarea unui ir de numere.
Comparai sortarea prin liste de salt cu restul algoritmilor de
sortare.
d) Scriei un program care citete un ir de numere rspunde la mai
multe ntrebri de genul care este al k-lea cel mai mic element
din ir? n timp O(log N) pentru fiecare ntrebare.
e) Scriei o funcie care returneaz poziia unui element n list.
f) Scriei o funcie care returneaz valoarea elementului de pe o
anumit poziie.
g) Implementai o clas numit SkipList.
455
Capitolul 13
H[0] 5 10 0 NULL
H[1] 31 1 11 NULL
H[2] 132 17 2 7 NULL
H[3] 13 8 NULL
H[4] 4 29 NULL
Fig 13.2.1. O tabel de dispersie (hash table)
la nceputul listei H[h(x)], iar inserarea unui element la nceputul unei liste
nlnuite se face n timp constant.
d) Detalii de implementare
n primul rnd trebuie s stabilim dimensiunea tabelei de dispersie i
funcia pe care o vom folosi. Pentru majoritatea problemelor se folosete o
funcie simpl de genul h(x) = x % P, unde P este un numr prim sau
h(x) = x % 2k, pentru a putea calcula mai rapid operaia modulo folosind
operaii pe bii. Vom alege cea de-a doua variant din motive de eficien.
Am putea folosi i de data aceasta dou structuri: una care va reine
doar un membru val, care va reprezenta numrul reinut de un anumit
obiect, i un pointer ctre urmtorul element din list, reprezentnd practic o
list nlnuit i o a doua structur care va declara i iniializa mai multe
liste nlnuite, constituind tabela de dispersie. Recomandm cititorilor s
incorporeze i funciile de gestiune a tabelei ntr-o clas, att pentru aceast
structur de date ct i pentru celelalte din acest capitol. n acest fel, vei
putea refolosi foarte uor i intuitiv codul.
Putem folosi ns clasele list sau vector din S.T.L. pentru a obine o
implementare mai scurt. La grafuri am folosit vector deoarece nu aveam
457
Capitolul 13
458
Structuri avansate de date
f) mbuntiri i aplicaii
Putem extinde tabelele de disperse pentru numere raionale i pentru
iruri de caractere. O funcie de dispersie pentru numere reale poate fi
h(x) = [{Ax}P], unde:
51
0 < A < 1, preferndu-se (conform lui Knuth) =
2
0.618033989
{x} partea fracionar a lui x
459
Capitolul 13
460
Structuri avansate de date
if ( found )
return true; // S2 este subsecventa a lui S1
}
461
Capitolul 13
Iar n general:
if ( hashS1 == hashS2 )
return true; // PROBABIL true
return false;
}
463
Capitolul 13
Exerciii:
a) Modificai funcia de mai sus astfel nct s afieze toate poziiile
de potrivire.
b) Propunei algoritmi pentru testarea calitii unei funcii de
dispersie, att pentru numere naturale ct i pentru iruri de
caractere.
c) Se d un ir de N numere naturale aleatoare. Se cere a gsirea a
patru numere din ir a cror sum este S. Cum se poate rezolva
problema n O(N2) cu ajutorul tabelelor de dispersie?
d) Elaborai un test pe care soluia gsit pentru problema
anterioar s aib timpul de execuie O(N4).
Exemplu:
RMQ2.in RMQ2.out
73 -6
1 -6 8 10 13 5 4 1
125
251
146
464
Structuri avansate de date
465
Capitolul 13
466
Structuri avansate de date
467
Capitolul 13
b) Detalii de implementare
Ne mai intereseaz modalitatea de memorare a unui arbore de
intervale. Vom folosi aceeai idee ca la heap-uri: dac A este arborele de
intervale, fiii unui nod k vor fi A[2k] respectiv A[2k + 1]. A va trebui s
fie de dimensiune cel puin 2N 1 (exist N + N / 2 + N / 4 + ... noduri).
Arborele nu este neaprat s fie complet ns, aa c va trebui s verificm
dac apelurile recursive se fac pentru un nod care chiar exist n arbore.
Pentru a evita aceste verificri putem declara tabloul A ca fiind de
dimensiune 2P 2N 1. Practic, vom completa arborele cu nite
pseudonoduri pn cnd acesta va deveni un arbore binar complet.
468
Structuri avansate de date
469
Capitolul 13
470
Structuri avansate de date
471
Capitolul 13
472
Structuri avansate de date
Euler[k++] = nod;
}
}
Exerciii:
a) Scriei un program care citete un arbore ponderat i rspunde
eficient la ntrebri de genul care este lungimea drumului dintre
nodurile x i y?
b) Scriei un program care citete un ir de numere ntregi i
rspunde eficient la ntrebri de genul care este subsecvena de
sum maxim dintre poziiile x i y? Implementai i actualizri.
c) Scriei un program care citete un tablou cu N elemente din
mulimea {0, 1}. Numrul 0 reprezint faptul c acea poziie este
473
Capitolul 13
Exemplu:
sume.in sume.out
83 22
1 -3 8 7 9 1 3 4 20
126
2 4 -4
135
474
Structuri avansate de date
475
Capitolul 13
476
Structuri avansate de date
S[i] pe v, unde i crete cu 2k la fiecare pas, atta timp ct i este mai mic sau
egal cu N.
Algoritmul funciei Actual(i, v) este:
Ct timp i <= N execut
o S[i] = S[i] + v
o i = i + 2k, unde k = numrul zerourilor terminale ale lui i
b) Detalii de implementare
int Calcul_k(int i)
{
int k = 0;
while ( (i & 1) == 0 ) // cat timp cel mai putin semnificativ bit (cel mai
// din dreapta) e 0
{
++k;
i >>= 1;
}
return k;
}
i 1011000 &
i1 1010111
i & (i 1) 1010000
#include <fstream>
478
Structuri avansate de date
// update
void Actual(int N, int i, int v, int S[])
{
for ( ; i <= N; i += i ^ (i & (i - 1)) )
S[i] += v;
}
479
Capitolul 13
d) Extinderi
480
Structuri avansate de date
Exerciii:
a) Se consider problemele prezentate, dar de data aceasta n loc de
sum se cere produsul elementelor din subsecven, respectiv
submatrice. Cum se poate evita lucrul cu numere mari?
b) Extindei arborii indexai binar pentru rezolvarea unei probleme
similare n spaiul tridimensional.
c) Gsii forme echivalente ale expresiei i ^ (i & (i - 1)).
b) Detalii de implementare
n primul rnd s vedem cum vom reine acest arbore. Fiind vorba de
un arbore n care fiecare nod poate avea un numr relativ mare de fii
482
Structuri avansate de date
(considerm c fiecare nod poate avea 26 de fii, cte unul pentru fiecare
liter din alfabet), vom folosi o structur nod cu urmtoarele cmpuri:
rasp folosit doar de nodurile terminale, ne indic numrul
cuvintelor din trie care au acest nod terminal, adic numrul de
apariii al unui anumit cuvnt.
nrf folosit de toate nodurile, ne indic numrul de fii nevizi ai
nodului curent.
next[26] folosit de toate nodurile, reprezint un vector de
pointeri, fiecare indicnd un anumit fiu. next[0] va indica fiul
etichetat cu a, next[1] fiul etichetat cu b i aa mai departe pn
la next[25] care va indica fiul etichetat cu z.
nod *next[maxa];
483
Capitolul 13
int val = *cuv - 'a'; // retine eticheta nodului urmator: 'a' - 'a' = 0,
// 'b' - 'a' = 1 etc.
if ( rad->next[val] == 0 ) // daca nodul nu exista, el trebuie creat
{
rad->next[val] = new nod;
++rad->nrf; // trebuie incrementat numarul de fii al nodului curent
}
484
Structuri avansate de date
485
Capitolul 13
int main()
{
int N, cod;
string cuv;
nod *trie = new nod;
ifstream in("trie.in");
in >> N;
while ( N-- )
{
in >> cod >> cuv;
486
Structuri avansate de date
switch ( cod )
{
case 0:
Insert(trie, cuv.c_str());
break;
case 1:
cout << Apar(trie, cuv.c_str()) << '\n';
break;
case 2:
Del(trie, trie, cuv.c_str());
break;
}
}
in.close();
return 0;
}
c) Aplicaii
487
Capitolul 13
Exerciii:
a) Scriei o funcie care afieaz toate cuvintele dintr-un trie n
ordine lexicografic.
b) Scriei o funcie care primete ca argumente rdcina unui trie i
un cuvnt cuv i afieaz cel mai lung prefix comun dintre
cuvntul cuv i orice alt cuvnt din trie.
c) Se d un vector A cu N numere naturale. Scriei un program care
gsete o subsecven Ai, Ai + 1, ..., Aj, cu 1 i j N astfel
nct valoarea Ai xor Ai + 1 xor ... xor Aj s fie maxim.
Reamintim tabelul de adevr al operaiei xor:
x y x xor y
1 0 1
0 1 1
1 1 0
0 0 0
489
Capitolul 13
490
Structuri avansate de date
Complexitatea acestei funcii este tot O(log N), din exact aceleai
motive enunate pentru funcia de cutare.
491
Capitolul 13
Cazul I
Nodul pe care vrem s-l tergem nu are fii
Acesta este cel mai convenabil caz. Tot ce trebuie s facem este s
identificm nodul care trebuie ters i s-l eliminm din arbore.
492
Structuri avansate de date
Cazul II
Nodul pe care vrem s-l tergem are un singur fiu
Cazul III
Nodul pe care vrem s-l tergem are doi fii
Alegnd un nod care respect una dintre cele dou condiii de mai
sus ne asigurm c nu va exista niciun nod n arbore care s nu respecte
493
Capitolul 13
494
Structuri avansate de date
Se poate observa din cele dou figuri anterioare c 8 este cea mai
mare valoare din arbore mai mic dect 9, iar 4 este cea mai mic valoare
din arbore mai mare dect 3.
Pentru a rezolva problema iniial, adic tergerea unui nod care are
doi fii, vom nlocui aadar nodul respectiv fie cu predecesorul su n
parcurgerea n ordine, fie cu succesorul su n aceast parcurgere, care se
poate determina uor aa cum am artat. Predecesorul sau succesorul cu care
nlocuim nodul pe care vrem s-l tergem va fi la rndul su ters conform
algoritmilor afereni primelor dou cazuri.
Sau:
d) Cazuri defavorabile
496
Structuri avansate de date
e) Detalii de implementare
struct nod
{
int val; // valoarea aferenta nodului curent
nod *st, *dr; // pointeri la subarborele stang respectiv drept
497
Capitolul 13
498
Structuri avansate de date
T->val = (*pred)->val;
if ( (*pred)->st == NULL )
RemoveCazI(*pred);
else
RemoveCazII(*pred);
}
499
Capitolul 13
f) Ali algoritmi
500
Structuri avansate de date
Aadar, putem afla cea mai mic valoare din arbore n timp mediu
O(log N) cu ajutorul urmtoarei funcii:
return T->val;
}
Putem afla cea mai mare valoare din arbore aflnd care este cel mai
din dreapta nod al arborelui. Acest lucru este corect deoarece algoritmul de
parcurgere n ordine furnizeaz ultimul rezultat umplnd stiva cu apeluri
recursive pentru fiul drept al nodului curent.
return T->val;
}
501
Capitolul 13
Altfel
o returneaz kMinim(T.st, k)
Exerciii
a) Scriei o funcie care determin al k-lea cel mai mare element
dintr-un arbore binar de cutare.
b) Prezentai dou abordri pentru ca un arbore binar de cutare s
suporte inserarea mai multor valori identice. Care este mai
avantajoas?
503
Capitolul 13
Un treap este un arbore binar n care fiecare nod are asociate dou
entiti: o valoare (sau cheie) i o prioritate. Valorile nodurilor treap-ului
vor respecta proprietile unui arbore binar de cutare, iar prioritile
nodurilor vor respecta proprietile unui heap. Valorile reprezint datele
inserate de ctre utilizator, iar prioritile vor fi nite numere aleatoare
atribuite fiecriui nod.
Vom presupune i aici c oricare dou valori din arbore sunt
distincte.
504
Structuri avansate de date
505
Capitolul 13
a) Echilibrarea arborelui
Echilibrarea arborelui este necesar atunci cnd inserarea sau
tergerea unui nod face ca un nod al arborelui s nu mai respecte
proprietatea de heap. Pentru a restabili aceast proprietate vom efectua o
rotaie a acelui nod spre dreapta sau spre stnga, dup caz:
dac prioritatea fiului stng al lui T este mai mare dect
prioritatea lui T, atunci se efectueaz o rotaie spre dreapta a
fiului stng.
dac prioritatea fiului drept al lui T este mai mare dect
prioritatea lui T, atunci se efectueaz o rotaie spre stnga a fiului
drept.
506
Structuri avansate de date
Funcia RotDr(T), care rotete fiul drept al lui T spre stnga poate fi
scris astfel:
temp = T.dr
T.dr = temp.st
temp.st = T
T = temp
Iar funcia RotSt(T), care rotete fiul stng al lui T spre dreapta
poate fi scris astfel:
temp = T.st
T.st = temp.dr
temp.dr = T
T = temp
507
Capitolul 13
508
Structuri avansate de date
509
Capitolul 13
e) Detalii de implementare
struct nod
{
int val; // valoarea nodului curent
int pr; // prioritatea nodului curent
nod *st, *dr; // fiul stang respectiv drept
nod(int v) : val(v)
{
pr = rand(); // fiecare nod primeste o prioritate aleatoare
st = dr = NULL;
}
};
510
Structuri avansate de date
Remove(x, T);
}
}
511
Capitolul 13
512
Structuri avansate de date
Exerciii:
a) Scriei un program care determin numrul de treap-uri distincte
cu N valori de la 1 la N i cu prioriti distincte de la 1 la N. De
exemplu, pentru N = 3 exist 6 astfel de treap-uri. Dou treap-uri
T1 i T2 se consider diferite dac:
T1.valoare este diferit de T2.valoare sau T1.prioritate
este diferit de T2.prioritate.
Treap-ul T1.stnga difer de T2.stnga sau T1.dreapta
difer de T2.dreapta.
b) Rezolvai aceleai probleme de la arbori binari de cutare
folosind treap-uri.
c) Scriei o funcie Split care primete ca argument un numr ntreg
x i ntoarce dou treap-uri A i B astfel nct A s conin doar
valori mai mici dect x i B doar valori mai mari dect x.
d) Scriei o funcie Join care primete ca argumente dou treap-uri
A, B i o valoare x, cu semnificaia de mai sus i unete treap-
urile A i B ntr-un singur treap.
513
Capitolul 13
13.8. Concluzii
Sperm c acest ultim capitol, ct i ntreaga lucrare, v-au fost i v
vor fi n continuare folositoare n studiul algoritmilor. Cititorii care au
parcurs temeinic materialul pus la dispoziie n aceast carte ar trebui s aib
deja o nelegere clar a noiunilor algoritmice elementare i a metodelor de
rezolvare a problemelor aferente acestui domeniu.
Autorii
514
Bibliografie
BIBLIOGRAFIE
1. Adrian Alexandrescu Programarea modern n C++. Programare
generic i modele de proiectare aplicate, Teora, Bucureti, 2002.
2. Alfred V. Aho, John E. Hopcroft, Jeffrey D. Ullman, Data Structures
and Algorithms, Addison-Wesley, 1983.
3. Alfred V. Aho, John E. Hopcroft, Jeffrey D. Ullman, The Design and
Analysis of Computer Algorithms, Addison-Wesley, 1974.
4. Bla Bollobs, Random Graphs, Academic Press, 1985.
5. C. A. R. Hoare, Algorithm 63 (partition) and algorithm 65 (find),
Communications of the ACM, 4(7):321-322, 1961.
6. C. A. R. Hoare, Quicksort, Computer Journal, 5(1):10-15, 1962.
7. C. Y. Lee, An algorithm for path connection and its applications, IRE
Transactions on Electronic Computers, EC-10(3):346-365, 1961.
8. Cay Horstmann, Practical Object - Oriented Development in C++ and
Java, Wiley Computer Publishing, 2000, New York.
9. Cecilia R. Aragon, Raimund Seidel, Randomized Search Trees,
Algorithmica 16 (4/5): 464497, 1996.
10. Cecilia R. Aragon, Raimund Seidel, Randomized Search Trees,
Proceedings of the 30th Symposium on Foundations of Computer
Science (FOCS 1989), Washington, D.C.: IEEE Computer Society
Press, pp. 540545, 1989.
11. Constantin Popescu, Dan Noje, Ioan Mang, Horea Oros, Programarea
n limbajul C, Editura Universitii din Oradea, 2002.
12. David E. Goldberg, The Design of Innovation: Lessons from and for
Competent Genetic Algorithms, Addison-Wesley, Reading, MA., 2002.
13. Donald E. Knuth, James H. Morris, Jr., Vaughan R. Pratt, Fast pattern
matching in strings, SIAM Journal on Computing, 6(2):323-350, 1977.
14. Edward F. Moore, The shortest path through a maze, Proceedings of the
International Symposium on the Theory of Switching, pages 285-292.
Harvard University Press, 1959.
15. Edward M. Reingold, Jrg Nievergelt, Narsingh Deo, Combinatorial
Algorithms: Theory and Practice, Prentice-Hall, 1977.
16. Eric Bach, Number-theoretic algorithms, n Annual Review of Computer
Science, volume 4, pages 119- 172. Annual Reviews, Inc., 1990.
17. Frank Harary, Graph Theory, Addison-Wesley, 1969.
515
Algoritmic
516
Bibliografie
517
Algoritmic
56. Robert E. Tarjan, Jan van Leeuwen, Worst-case analysis of set union
algorithms, Journal of the ACM, 31(2):245-281, 1984.
57. Robert S. Boyer, J. Strother Moore, A fast string-searching algorithm,
Communications of the ACM, 20(10):762-772, 1977.
58. Robert Sedgewick Implementing quicksort programs, Communications
of the ACM, 21(10):847-857, 1978.
59. Robert Sedgewick, Algorithms, Addison-Wesley, second edition, 1988.
60. Robert W. Floyd, Algorithm 97 (SHORTEST PATH), Communications
of the ACM, 5(6):345, 1962.
61. Robert W. Floyd, Ronald L. Rivest, Expected time bounds for selection,
Communications of the ACM, 18(3):165-172, 1975.
62. Sara Baase, Computer Algorithms: Introduction to Design and Analysis.
Addison-Wesley, second edition, 1988.
63. Shimon Even, Graph Algorithms, Computer Science Press, 1979.
64. Thomas H. Cormen, Charles E. Leiserson, Ronald L. Rivest, Clifford
Stein Introduction to Algorithms, second edition, The MIT Press,
Cambridge, Massachusetts, 2001.
65. William Pugh, Skip lists: a probabilistic alternative to balanced trees,
Communications of the ACM 33 (6): 668-676, 1990.
66. Wolfgang Banzhaf, Peter Nordin, Robert Keller, Frank Francone,
Genetic Programming An Introduction, Morgan Kaufmann, San
Francisco, CA., 1998.
518