Documente Academic
Documente Profesional
Documente Cultură
George Mardale
Cuprins
9 Analiza ecientei algoritmilor
9.1 Ce este analiza algoritmilor? . . . . . . . . . . . . . . . . . .
9.2 Notatia asimptotica . . . . . . . . . . . . . . . . . . . . . . .
9.2.1 O notatie pentru ordinul de marime al timpului de exe
cutie al unui algoritm . . . . . . . . . . . . . . . . . .
9.3 Tehnici de analiza algoritmilor . . . . . . . . . . . . . . . . .
9.3.1 Sortarea prin selectie . . . . . . . . . . . . . . . . . .
9.3.2 Sortarea prin insertie . . . . . . . . . . . . . . . . . .
9.3.3 Turnurile din Hanoi . . . . . . . . . . . . . . . . . . .
9.4 Analiza algoritmilor recursivi . . . . . . . . . . . . . . . . . .
9.4.1 Metoda iteratiei . . . . . . . . . . . . . . . . . . . . .
9.4.2 Inductia constructiva . . . . . . . . . . . . . . . . . .
9.4.3 Recurente liniare omogene . . . . . . . . . . . . . . .
9.4.4 Recurente liniare neomogene . . . . . . . . . . . . . .
9.4.5 Schimbarea variabilei . . . . . . . . . . . . . . . . . .
9.5 Implementarea algoritmilor . . . . . . . . . . . . . . . . . . .
10 Structuri de date
10.1 Cum implementam structurile de date?
10.2 Stive . . . . . . . . . . . . . . . . . .
10.3 Cozi . . . . . . . . . . . . . . . . . .
10.4 Liste nlantuite . . . . . . . . . . . .
10.5 Arbori . . . . . . . . . . . . . . . . .
10.5.1 Notiuni generale . . . . . . .
10.5.2 Arbori binari . . . . . . . . .
10.5.3 Arbori binari de cautare . . .
10.6 Tabele de repartizare . . . . . . . . .
10.6.1 Tratarea coliziunilor . . . . .
3
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
15
. 16
. 18
.
.
.
.
.
.
.
.
.
.
.
.
19
21
21
23
24
25
25
26
26
28
31
33
.
.
.
.
.
.
.
.
.
.
40
43
45
51
55
64
64
65
69
83
90
CUPRINS
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
108
. 109
. 110
. 114
. 114
. 115
. 115
. 117
. 119
. 119
. 124
. 126
. 127
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
137
. 138
. 138
. 143
. 145
. 146
. 149
. 152
. 157
13 Algoritmi Greedy
13.1 Problema spectacolelor (selectarea activitatilor) .
13.1.1 Demonstrarea corectitudinii algoritmului
13.1.2 Solutia problemei spectacolelor . . . . .
13.2 Elemente ale strategiei Greedy . . . . . . . . . .
13.2.1 Proprietatea de alegere Greedy . . . . . .
13.2.2 Substructura optima . . . . . . . . . . .
13.3 Minimizarea timpului mediu de asteptare . . . .
13.4 Interclasarea optima a mai multor siruri ordonate
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
167
. 168
. 169
. 171
. 173
. 176
. 176
. 177
. 180
.
.
.
.
.
.
.
.
CUPRINS
14 Programare dinamica
14.1 Istoric si descriere . . . . . . . . . . . . . . . . . . . . . .
14.2 Primii pasi n programarea dinamica . . . . . . . . . . . .
14.2.1 Probleme de recurenta matematica tratate dinamic
14.3 Fundamentare teoretica . . . . . . . . . . . . . . . . . . .
14.4 Principiul optimalitatii . . . . . . . . . . . . . . . . . . .
14.4.1 Metoda nainte si metoda napoi . . . . . . . .
14.4.2 Determinarea efectiva a solutiei optime . . . . . .
14.5 nmultirea unui sir de matrice . . . . . . . . . . . . . . . .
14.6 Subsir comun de lungime maxima . . . . . . . . . . . . .
14.7 Distanta Levensthein . . . . . . . . . . . . . . . . . . . .
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
186
. 187
. 188
.188
. 198
. 201
. 204
. 205
. 208
. 215
. 220
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
255
. 255
. 255
. 256
. 256
. 256
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
236
236
237
240
240
241
Introducere
Nu calculati capacitatea unui pod
numarnd persoanele care
traverseaza acum rul not.
Auzita la o prezentare
INTRODUCERE
Lansarea versiunii 2 a limbajului Java a fost o dovad a succesului imens de
care s-au bucurat versiunile anterioare ale limbajului, dar i a dezvoltrii limba
jului n sine, a evoluiei sale ascendente din punct de vedere al facilitilor pe
care le ofer, ct i al performanelor pe care le realizeaz.
Cum este organizat aceast carte?
Avnd n vedere popularitatea extraordinar de care se bucur limbajul Java
n cadrul programatorilor din ntreaga lume, am considerat util scrierea unei
lucrri n limba romn care s fie accesibil celor care doresc s nvee sau s
aprofundeze acest limbaj. Ideea care a stat la baza realizrii acestei cri a fost
aceea de a prezenta nu numai limbajul Java n sine, ci i modul n care se imple
menteaz algoritmii i structurile de date fundamentale n Java, elemente care
sunt indispensabile oricrui programator cu pretenii. Aadar, cartea nu este
destinat numai celor care doresc s acumuleze noiuni despre limbajul Java n
sine, ci i celor care intenioneaz s i aprofundeze i rafineze cunotinele
despre algoritmi i s i dezvolte un stil de programare elegant. Ca o con
secin, am structurat cartea n dou volume: prima volum (disponibil separat)
este orientat spre prezentarea principalelor caracteristici ale limbajului Java, n
timp ce volumul al doilea (cel de fa) constituie o abordare a algoritmilor din
perspectiva limbajului Java. Finalul primului volum cuprinde un grup de cinci
anexe, care prezint mai amnunit anumite informaii cu caracter mai special,
deosebit de utile pentru cei care ajung s programeze n Java. Am ales aceast
strategie deoarece a dobndi cunotine despre limbajul Java, fr a avea o imag
ine clar despre algoritmi, nu reprezint un progres real pentru un programator.
Iar scopul nostru este acela de a v oferi posibilitatea s devenii un programator
pentru care limbajul Java i algoritmii s nu mai constituie o necunoscut.
Cele dou volume cuprind numeroase soluii Java complete ale problemelor
prezentate. Mai este necesar o remarc: deseori am optat, att n redactarea co
dului surs ct i n prezentarea teoretic a limbajului sau a algoritmilor, pentru
pstrarea terminologiei n limba englez n defavoarea limbii romne. Am luat
aceast decizie, innd cont de faptul c muli termeni s-au consacrat n acest
format, iar o eventual traducere a lor le-ar fi denaturat nelesul.
Primul volum al crii cuprinde opt capitole:
Capitolul 1 reprezint o prezentare de ansamblu a tehnologiei Java. Capi
tolul debuteaz cu istoria limbajului, ncepnd cu prima versiune i pn la cea
curent. n continuare, sunt nfiate cteva detalii despre implementrile ex
istente ale limbajului. Implementarea Java a firmei Sun este tratat separat, n
detaliu, fiind i cea pe care s-au testat aplicaiile realizate pe parcursul crii.
Capitolul 2 este cel care d startul prezentrii propriu-zise a limbajului Java,
8
INTRODUCERE
INTRODUCERE
INTRODUCERE
INTRODUCERE
INTRODUCERE
Pe Internet
Pentru comoditatea cititorilor, am decis sa punem la dispozitia lor codul
sursa complet al tuturor programelorprezentate pe parcursulcelor doua volume
ale lucrarii n cadrul unei pagini web conceputa special pentru interactiunea cu
cititorii. De asemenea, pagina web a cartii va gazdui un forum unde cititorii
vor putea oferi sugestii n vederea mbunatatirii continutului lucrarii, vor putea
schimba opinii n legatura cu diversele aspecte prezentate, adresa ntrebari autorilor etc. Adresele la care veti gasi aceste informatii sunt:
http://www.albastra.ro/carti/v178/
http://www.danciu.ro/apj/
Multumiri
n ncheiere, dorim sa adresam multumiri colegilor si prietenilor nostri care
ne-au acordat ajutorul n realizarea acestei lucrari: Vlad Petric (care a avut o
13
INTRODUCERE
contribuie esenial la structurarea capitolului 14), Alexandru Blu (autor al
anexei A), Iulia Tatomir (a parcurs i comentat cu mult rbdare de mai multe
ori ntreaga carte) i Rul Furnic (a parcurs i comentat capitolele mai delicate
ale lucrrii).
14
9.1
Cantitatea de timp pe care orice algoritm o cere pentru execuie depinde aproape ntotdeauna de cantitatea de date de intrare pe care o proceseaz. Este
de ateptat c sortarea a 10.000 de elemente s necesite mai mult timp dect
sortarea a 10 elemente. Timpul de execuie al unui algoritm este astfel o funcie
de dimensiunea datelor de intrare. Valoarea exact a acestei funcii depinde
de mai muli factori, cum ar fi viteza calculatorului pe care ruleaz progra
mul, calitatea compilatorului i, nu de puine ori, calitatea programului. Pentru
un program dat, care ruleaz pe un anumit calculator, putem reprezenta grafic
timpul de execuie. n Figura 9.1 am realizat un astfel de grafic pentru patru
programe. Curbele reprezint patru funcii care sunt foarte des ntlnite n analiza algoritmilor: liniar, n log n, ptratic i cubic. Dimensiunea datelor
de intrare variaz de la 1 la 100 de elemente, iar timpii de execuie de la 0 la 5
milisecunde. O privire rapid asupra graficelor din Figura 9.1 i Figura 9.2 ne
lmurete c ordinea preferinelor pentru timpii de execuie este liniar, n log n,
ptratic i cubic.
S lum ca exemplu descrcarea (download-area) unui fiier de pe Internet.
S presupunem c la nceput apare o ntrziere de dou secunde (pentru a stabili
conexiunea), dup care descrcarea se va face la 1.6 KB/sec. n aceast situ
aie, dac fiierul de adus are N kilobaii, timpul de descrcare a fiierului este
descris de formula T ( N ) = N/1.6 + 2. Aceasta este o funcie liniar. Se
poate calcula uor c descrcarea unui fiier de 80K va dura aproximativ 52 de
secunde, n timp ce descrcarea unui fiier de dou ori mai mare (160K) va dura
102 secunde, deci cam de dou ori mai mult. Aceast proprietate, n care tim
pul este practic direct proporional cu cantitatea de date de intrare, este specific
unui algoritm liniar, i constituie adeseori o situaie ideal. Aa cum se vede
din grafice, unele curbe neliniare pot conduce la timpi de execuie foarte mari.
n acest capitol vom analiza urmtoarele probleme: cu ct este mai bun o
curb n comparaie cu o alt curb, cum putem calcula pe care curb se situeaz
un anumit algoritm sau cum putem proiecta algoritmi care s nu se situeze pe
curbele nefavorabile.
O funcie cubic este o funcie al crei termen dominant este N3, nmulit
cu o constant. De exemplu, 107V3 + N2 + 40N + 80 este o funcie cubic.
16
17
9.2
Notaia asimptotic
incat
n >
Cu alte cuvinte, 0(f) (se citete "ordinul luif") este mulimea tuturor funci
ilor t mrginite superior de un multiplu real pozitiv al lui /, pentru valori sufi
cient de mari ale argumentului n. Vom conveni s spunem c t este n ordinul
lui / (sau, echivalent, t este n 0(f), sau t G 0(/) ) chiar i atunci cnd t(n)
este negativ sau nedefinit pentru anumite valori n < no In mod similar, vom
vorbi despre ordinul lui / chiar i atunci cnd valoarea f(n) este negativ sau
nedefinit pentru un numr finit de valori ale lui n; n acest caz, vom alege no
suficient de mare, astfel nct pentru n > no acest lucru s nu mai apar. De
exemplu, vom vorbi despre ordinul lui n/log n , chiar dac pentru n=0 i n=l
funcia nu este definit. In loc de t 6 O(f), uneori este mai convenabil s
folosim notaia t(n) G 0(/(n)), subnelegnd aici c t(n) if(n) sunt funcii.
Fie un algoritm dat i fie o funcie t : N [0, oo), astfel nct o anumit
implementare a algoritmului s necesite cel mult t(n) uniti de timp pentru a
rezolva un caz de mrime n, unde n G N. Principiul invarianei1 ne asigur
atunci c orice implementare a algoritmului necesit un timp n ordinul lui t.
Cu alte cuvinte, acest algoritm necesit un timp n ordinul lui / pentru orice
funcie / : N [0, oo) pentru care t G O(f). n particular avem relaia:
t G 0(t) . Vom cuta, n general, s gsim cea mai simpl funcie/ astfel nct
teO(f).
Exemplu: Fie funcia t(n) = 3n2 9n + 13. Pentru n suficient de mare, vom
avea relaia t(n) < 4n2. n consecin, lund c = 4, putem spune c t{n) G
0(n2). La fel de bine puteam s spunem c t(n) G 0(13n2 \[2n + 12.5),
dar pe noi ne intereseaz s gsim o expresie ct mai simpl. Este adevrat
i relaia t(n) G 0(n4) dar, aa cum vom vedea mai trziu, suntem interesai
de a mrgini ct mai strns ordinul de mrime al algoritmului, pentru a putea
obiectiva ct mai bine durata sa de execuie.
Proprietile de baz ale lui O(f) sunt date ca exerciii (1 - 5) i ar fi reco
mandabil s le studiai nainte de a trece mai departe.
1 Acest principiu afirm c dou implementri diferite ale aceluiai algoritm nu difer, ca efi
cien, dect cel mult printr-o constant multiplicativ.
19
incat
n>
Exist o anumit dualitate ntre notaiile 0(f) i fi(/): pentru dou funcii
oarecare /, g : N [0, oo), avem:
/ G 0(g) dac i numai dac g G
N,
9.3
1, i, 6 i j);
. println ( i + " >" +
1,6 i j,j);
j);
dac
dac
n= 1
n> 1
9.4
9.4.1
Metoda iteraiei
Inducia constructiv
dac
altfel
n= 1
Exist din fericire i tehnici care pot fi folosite aproape automat pentru a re
zolva anumite clase de recurene. Vom ncepe prin a considera ecuaii recurente
liniare omogene, adic ecuaii de forma:
26
(*)
1 = 3"
Schimbarea variabilei
dac
dac
dac
a <bk
a = bk
a > bk
Implementarea algoritmilor
//afiare rezultate
Sy stern . out . p ri n t (" S i rul ordonat este: ");
for (int i = 0; i < s.length; i++)
{
System . out . prin ( s [ i ] + " ");
)
System, out. println() ;
26
27 /** Programul principal.*/
28 public static void main (String[] args)
29 j
30
//citirea elementelor irului
31
System, out. println ("Introducei elementele irului" +
32
" (pe aceeai linie, separate prin spaiu):");
33
int [] s = Reader . readlntArray ();
34
35
//ordonarea irului s
36
ordonare ( s ) ;
37
34
//afiare rezultate
System . out . p ri nt (" S i ru 1 ordonat este: ");
for ( int i = 0; i < s . length ; i ++)
{
Sy stern . out . p ri n t ( s [ i ] + " ");
}
System . out . p ri n tl n ( ) ;
Rezumat
Capitolul de fa a realizat o scurt introducere n domeniul vast al analizei
algoritmilor. Ideea cea mai important care reiese de aici este c algoritmii
35
Noiuni fundamentale
0(f): notaie utilizat pentru a determina termenul dominant al funciei f .
Cu ajutorul ei se limiteaz superior timpul de execuie al unui algoritm.
fi(/): notaie utilizat pentru a limita inferior timpul de execuie al unui
algoritm.
0(/): notaie utilizat pentru a arta c timpul de execuie al unui algoritm
este limitat att inferior ct i superior de cte un multiplu real al aceleai funcii
/algoritm liniar: algoritm care are timpul de execuie 0(n).
algoritm exponenial: algoritm al crui timp de execuie crete exponenial
n raport cu dimensiunea datelor de intrare (0(an)).
algoritm polinomial: algoritm al crui timp de execuie este mrginit su
perior de un polinom (0(nk)).
ecuaie caracteristic: ecuaie polinomial ataat recurenelor liniare, prin
a crei rezolvare se gsete soluia recurenei.
inducia constructiv: o variant a induciei matematice care permite de
monstrarea unei aseriuni parial sau incomplet enunate precum i descoperirea
specificaiilor care lipsesc din aseriunea respectiv.
metoda iteraiei: metod simpl de rezolvare a recurenelor liniare n care
se execut primii pai ai recurenei dup care se intuiete forma general care
se demonstreaz prin inducie.
recuren liniar: formul de recuren n care termenul al n-lea este ex
primat ca o combinaie liniar a termenilor precedeni.
schimbarea variabilei: tehnic utilizat pentru a reduce anumite formule
de recuren la o form liniar.
36
Exerciii
1 . Care din urmtoarele afirmaii sunt adevrate?
(a) n2 G 0(n3)
(b) n3 G 0(n2)
(c) 2"+1 G 0(2")
(d) (n+1)! G 0(n!)
(e) pentru orice funcie / : N -> R*, / 0(n) => [f2 G 0{n2)}
(f) pentru orice funcie / : N -> R*, / G 0(n) => [2/ G 0(2")]
2. Demonstrai c relaia " G O" este tranzitiv: dac / G 0(<?) i g G 0(/i),
atunci / G 0(h). Deducei de aici c dac g G 0(/i), atunci O (5) C
0(/i).
3. Gsii dou funcii /, g : N -> R*, astfel nct f 0(g) i g 0(f).
Soluie: f(n) = n,g(n) = n1+sinn.
37
39
Figura 10.1: nchiderea datelor ntr-o cutie neagra. Datele pot accesate doar
prin invocarea unei operatii permise.
Modul n care datele sunt aezate n acea cutie neagr i modul n care ope
raiile se execut devin detalii irelevante1. Astfel de detalii determin eficiena
programului, dar nu i afecteaz structura logic.
Abstractizarea datelor trebuie privit mai degrab ca o facilitate care uureaz
efortul de programare, i nu ca o nou constrngere n privina stilului de pro
gramare. Pentru a nelege mai bine aceste noiuni, s vedem cum poate fi
definit simplificat o stiv ca tip abstract de date. Stiva este o colecie de date
omogene (de acelai tip) asupra creia se pot realiza urmtoarele operaii:
PUSH(X) - are ca efect depunerea lui X pe vrful stivei;
POP(X) - are ca efect ncrcarea valorii din vrful stivei n parametrul X
i eliminarea vrfului stivei.
Modul n care cele dou operaii (PUSH i POP) sunt implementate i modul
n care datele sunt reinute n stiv (static, nlnuit etc.) nu trebuie s transpar
utilizatorului; pe el pur i simplu nu trebuie s l intereseze acest lucru. Este
exact ca i curentul electric: atunci cnd acionm comutatorul de la veioz,
tim c becul se va aprinde; care este procesul prin care filamentul becului se
ncinge i emite lumin nu ne privete. Tot astfel, i listele, cozile, arborii binari
mpreun cu operaiile care se fac asupra lor pot fi privite ca tipuri abstracte de
date.
Unui tip abstract de date mulime putem s-i asociem operaii cum ar fi
reuniune, intersecie, dimensiune, complementar. ntr-o alt situaie, putem
avea nevoie numai de operatorii de reuniune i apartenen, care definesc un alt
tip abstract de date (TAD), care poate s aib o cu totul alt organizare intern,
deoarece, aa cum am spus, operaiile sunt cele care definesc TAD-ul.
n consecin, rolul acestui capitol este de a prezenta modul n care se con
struiesc aceste tipuri abstracte de date (sau structuri de date) pentru a le putea
utiliza apoi n crearea de programe robuste, lizibile i eficiente. Ideea de baz
este c implementarea operaiilor unui TAD se realizeaz o singur dat n
cadrul unei aplicaii, i orice alt parte a aplicaiei va utiliza structura de date
prin itermediul interfeei pe care aceasta o expune.
Dac din diverse motive anumite detalii de implementare trebuie schimbate,
acest lucru se va face uor prin modificarea rutinelor care realizeaz operai
ile din TAD. Aceste schimbri nu vor afecta n nici un fel restul programului,
deoarece interfaa se menine neschimbat. n exemplul nostru cu stiva, dac
decidem s trecem de la alocarea secvenial (static) a elementelor stivei la
1 Irelevante pentru cel care utilizeaz structura de date. Noi ne vom ocupa n acest capitol tocmai
de modul de construcie al acestei cutii negre, pentru a o putea utiliza apoi ori de cte ori este
necesar.
42
10.1
43
n schimb ca interfaa s fie mai curat, mai flexibil, i, de obicei, mai uor de
implementat.
Toate structurile de date sunt uor de implementat dac nu ne punem proble
ma eficienei. Acest lucru permite s adugm componente "ieftine" n program
doar pentru depanare. Putem apoi nlocui aceste implementri "ieftine" cu im
plementri care au o performan (n timp i/sau n spaiu) mai bun i care sunt
adecvate pentru procesarea unei cantiti mai mari de informaie. Deoarece
interfeele sunt fixate, aceste nlocuiri nu necesit practic nici o modificare n
programele care folosesc aceste structuri de date.
Vom descrie structurile de date prin intermediul interfeelor. De exem
plu, stiva este precizat prin intermediul interfeei Stack. Clasa care imple
menteaz aceast interfa va implementa toate metodele specificate n Stack,
la care se mai pot aduga anumite funcionaliti.
Ca un exemplu, n Listing 10.1 este descris o interfa pentru clasa Memory
Cell, utilizat n primul volum, la capitolul Motenire, seciunea Implementa
rea de componente generice. Interfaa descrie funciile disponibile; clasa con
cret trebuie s defineasc aceste funcii. Implementarea interfeei este prezen
tat n Listing 10.2.
Este important de reinut faptul c structurile de date definite n acest capitol
stocheaz referine ctre elementele inserate, i nu copii ale elementelor. Am
ales aceast variant deoarece este bine ca n structura de date s fie plasate
obiecte nemodificabile (cum ar fi String, Integer, etc.) pentru ca un uti
lizator extern s nu poat s schimbe starea unui obiect care este nglobat ntr-o
structur de date.
Fiecare dintre structurile prezentate este implementat complet, mpreun
cu un program de test pentru a verifica corectitudinea implementrii structurilor
de date. Pentru o mai bun organizare, clasele utilizate n aceste programe sunt
mprite pe pachete, ceea ce nseamn c fiecare fiier surs este salvat ntr-un
44
10.2. STIVE
7
8
9
10
11
12
13
{
storedValue = x;
14
15
16
10.2
Stive
O stiva este o structura de date n care orice tip de acces este permis doar
asupra ultimului element inserat. Comportamentulunei stive este foarte asema
nator cu cel al unei gramezi de farfurii. Ultima farfurie adaugata va plasata
n vrf ind, n consecinta, usor de accesat, n timp ce farfuriile puse cu mai
mult timp n urma (aate sub alte farfurii) vor mai greu de accesat, putnd
periclita stabilitatea ntregii gramezi. Astfel, stiva este adecvata n situatiile n
care avem nevoie sa accesam doar elementul din vrf. Toate celelalte elemente
45
10.2. STIVE
Figura 10.2: Inserarea n stiv se face prin push ( ) , accesul prin top ( ) , iar
tergerea prin pop ( ) .
push
pop,top
Stiva
sunt inaccesibile.
Cele trei operaii naturale de inserare, tergere i cutare, sunt denumite
n cazul unei stive, push, pop i top. Cele trei operaii sunt ilustrate n
Figura 10.2, iar o interfa Java pentru o stiv abstract este prezentat n List
ing 10.4. Interfaa declar i o metod topAndPop ( ) care combin dou
operaii, consultare i extragere. n cazul nostru, metodele pop ( ) , top ( ) i
topAndPop ( ) pot arunca o excepie UnderflowException n cazul n
care se ncearc accesarea unui element cnd stiva este goal. Aceast excepie
va trebui s fie prins pn la urm de o metod apelant. Clasa Underflow
Exception este definit n Listing 10.3, i ea este practic identic cu clasa
Exception. Important pentru noi este c difer tipul, ceea ce ne permite s
prindem doar aceast excepie cu o instruciune try-catch .
Listing 10.6 prezint un exemplu de utilizare a stivei n care se folosete o
clas S t a c kAr care reine elementele stivei ntr-un ir (array) . Clasa S t a c kAr
46
10.2. STIVE
package d a t a s t ruc t ur e s ;
import exceptions .;
Interfata pentru o stiva. Stiva expune metode pentru
manipularea ( adaugarea , stergerea , consultarea )
5
6
elementului din va rfu l ei/
7 public interface Stack
8 {
public void push(Object x);
9
3
4/
10
11
12
13
14
15
16
17
18
19
20
10.2. STIVE
Implementarea interfeei Stack este realizat folosind irul de obiecte elements (linia 8 n clasa StackAr). De aici i numele de stiv bazat pe
iruri. Practic, elementele stivei sunt pstrate ntr-un ir de obiecte, pe care
se realizeaz operaiile expuse de interfa: top ( ) , pop ( ) , push ( ) etc.
Stiva are o capacitate iniial de elemente (DEFAULT_CAPACITY). Dac pe
msur ce se fac adugri n stiv, aceast capacitate este atins, atunci stiva
i mrete capacitatea cu un numr precizat de elemente (n cadrul metodei
increaseStackSize ( )). Operaia este ns ascuns privirilor utilizatoru
lui, pentru c stiva i mrete dimensiunea fr ca utilizatorul s precizeze sau
s aib cunotin de acest lucru. Nivelul de "umplere" al stivei este specifi
cat prin intermediul variabilei topPosition, care indic i poziia vrfului
stivei.
Listing 10.5: Implementarea unei structuri de tip stiv folosind iruri
i package d at a s t r uc t u re s ;
2
3 import exceptions .*;
4/** Implementarea unei stive folosind un sir */
5 public elass StackAr implements Stack
6!
7 /** Sir care retine elementele stivei*/
8 private Object [] elements ;
9 /** Dimensiunea implicita a stivei*/
10 private static final int DEFAULTCAPACITY = 10;
11 /** Poziia vrfului stivei */
12 private int topPosition;
13
14 /** Constructor care aloca memorie pentru elements si
s * iniializeaz vrful stivei */
i6 public StackAr ( )
" 1
is
elements = new Obj ect [DEFAULT_CAPACITY ] ;
19
topPosition = 1;
20 }
21
22 /* * Adaug elementul x in stiva . */
23 public void push(Object x)
24 j
25
if (topPosition == elements . length 1)
{
27
increaseStackSize ();
}
29
30
elements [++topPosition ] = x;
31 )
32
33 /* * Mrete dimensiunea ( capacitatea ) stivei cu
48
}
/** Returneaza elementul din vrful stivei
* (ultimul adugat).*/
public Object top() throws UnderflowException
{
if ( isEmpty ( ) )
{
throw new UnderflowException (" Stiva vida.");
)
return elements [ topPosition ] ;
}
/** Returneaza elementul din vrful stivei si
* il elimina apoi din stiva.*/
public Object topAndPop () throws UnderflowException
{
if ( isEmpty ( ) )
{
throw new UnderflowException (" Stiva vida.");
)
return elements [ topPosition
];
10.2. STIVE
84
85
86
B7
88
89
90
91
92
93
94
*
96 }
10.3. COZI
Figura 10.3: Modelul unei cozi: adugarea la coad se face prin enqueue ( ) ,
accesul prin getFr ont ( ) , tergerea prin dequeue ( ) .
enqueue
dequeue, getFront
Queue
10.3
Cozi
O alt structur simpl de date este coada. n multe situaii este important
s avem acces i/sau s tergem ultimul element inserat. Dar, ntr-un numr la
fel de mare de situaii, acest lucru nu numai c nu mai este important, este chiar
nedorit. De exemplu, ntr-o reea de calculatoare care au acces la o singur im
primant este normal ca dac n coada de ateptare se afl mai multe documente
spre a fi tiprite, prioritatea s i fie acordat documentului cel mai vechi. Acest
lucru nu numai c este corect, dar este i necesar pentru a garanta c documen
tul nu ateapt la infinit. Astfel, pe sistemele mari este normal s se foloseasc
cozi de tiprire.
Operaiile fundamentale suportate de cozi sunt:
enqueue - inserarea unui element la captul cozii;
dequeue - tergerea primului element din coad;
getFront - accesul la primul element din coad.
Figura 10.3 ilustreaz operaiile pe o coad. Tradiional, metodele de
queue ( ) i getFront ( ) sunt combinate ntr-una singur. Prima metod
returneaz primul element, dup care l scoate din coad, n timp ce cea de-a
doua returneaz primul element fr a-1 scoate din coad.
Implementarea structurii de coad este asemntoare pn la un punct cu
cea a stivei. Coada pstreaz elementele ntr-un ir de obiecte cu o capacitate
iniial, iar dac prin adugri repetate aceast capacitate este atins, atunci ea
va fi mrit automat. Spre deosebire de stiv, coada are doi indici care indic
poziiile de nceput i de sfrit ale cozii.
Listing 10.7 ilustreaz interfaa pentru o coad, n timp ce Listing 10.8 pre
zint o implementare a interfeei anterioare, bazat pe iruri. Spre deosebire de
51
10.3. COZI
stiv, n care adugarea i extragerea unui element au loc la acelai capt, n
cazul cozii adugarea are loc la final, dar tergerea se face de la nceput. Astfel,
pentru a putea utiliza irul elements la ntreaga lui capacitate, elementele lui
sunt privite "circular", ca i cnd ultimul element ar fi legat de primul.
Listing 10.7: Interfa pentru coad
i package d at a s t r uc t u re s ;
2
3 import exceptions .*;
4/** Interfaa pentru o coada. Expune metode pentru adugarea
5* unui element , tergerea primului element , consultarea
6 * primului element */
7 public interface Queue
!
9 public void enqueue ( Obj ect x);
10
11 public Object getFront() throws UnderflowException ;
12
13 public Object dequeue() throws UnderflowException;
14
15 public boolean isEmpty();
16
17 public void makeEmpty ( ) ;
,8 }
Listing 10.8: Implementarea unei structuri de tip coad folosind iruri
1 package d at a s t r uc t u re s ;
2
3 import exceptions .*;
4/** Implementarea unei cozi folosind un tablou. */
5 public class QueueAr implements Queue
6!
7 /** Tablou care retine elementele din coada*/
8 private Object [] elements ;
9 /** Indicele primului element*/
10 private int front ;
11 /** Indicele ultimului element*/
12 private int back ;
13 /** Dimensiunea cozii */
14 private int currentSize ;
15 /** Numrul de elemente alocat iniial pentru coada*/
16 private final static int DEFAULT_CAPACITY = 10;
L7
s /* * C onstructo r care aloca memorie pentru elementele cozii
19 * si seteaz valorile atributelor*/
20 public QueueAr ( )
1
22
elements = new Object [DEFAULT_CAPACTTY ] ;
52
10.3. COZI
23
24
25
26
27
28
29
30
31
32
33
34
35
37
38
39
40
41
42
43
44
45
46
47
4s
49
50
52
53
54
55
56
57
59
60
61
62
63
64
65
66
67
68
69
70
71
^
makeEmpty ( ) ;
}
/* * ntoarce true daca este vida . */
public boolean isEmpty ()
{
return currentSize == 0;
}
/** Adaug elementul x in coada. */
public void enqueue(Object x)
{
if (currentSize == elements.length)
!
increaseQueueSize();
}
back = increment ( back ) ;
elements [ back ] = x;
currentSize ++;
}
/** Elimina toate elementele din coada. */
public void makeEmpty ()
{
currentSize = 0;
front = 0;
back = 1 ;
}
/** Extrage primul element din cadrul cozii*@throws Unde rflowException daca coada este goala*/
public Object dequeue() throws UnderflowException
{
if ( isEmpty ( ) )
!
throw new UnderflowException (" Coada vida");
}
currentSize ;
Object returnValue = elements [ front ] ;
front = increment ( front ) ;
return returnValue ;
}
/** ntoarce primul element din cadrul cozii
*@throws UnderflowException daca coada este goala . */
public Object getFront() throws UnderflowException
{
53
10.3. COZI
73
i f ( isEmpty ( ) )
{
throw new Underflo wException (" Coada vida.");
}
75
77
78
79
80
81
82
83
84
85
s6
ss
89
90
91
92
93
94
95
96
,7
98
99
IDO
oi
102
103
104
105
106
107
108
109
i io
u, }
return elements[front];
}
/* *
* Incrementeaz circular indicele din coada. */
*/
private int increment(int x)
{
if ( + + x == elements . length )
{
x = 0;
}
return x;
}
/** Incrementeaz dimensiunea cozii cu DEFAULT_CAPACITY atunci
* cand coada este plina . */
private void increaseQueueSize()
{
Object[] newQueue = new Object [ elements . length +
DEFAULTCAPACITY ] ;
for ( int i = 0; i < elements . length ; i ++)
{
newQueue [ i ] = elements [i];
front = increment ( front ) ;
}
elements = newQueue;
front = 0 ;
back = currentSize 1;
}
54
10.4
Liste nlnuite
i package d at a s truc tu re s ;
! /* *
i * Un nod al listei nlnuite . Clasa este vizibila
i * doar in pachet ( packagefr i endly ), deoarece este
* pentru uzul intern al listei.
, */
' class ListNode
<!
) /* * Valoarea coninuta de nod. */
i Object element ;
/** Legtura ctre nodul urmtor.*/
'. ListNode next ;
i
>
>
i
20
first
0,2
56
a3
1
2
3
4
5
public LinkedList()
header = new ListNode ( nuli ) ;
public boolean isEmpty ()
return header . next == nuli ;
public void makeEmpty()
24
header . next = nuli ;
25 }
26 }
Listing 10.14: Interfa pentru un iterator abstract de list
i package datastructures ;
2
3 import exceptions .*;
4
5 /* *
6 * Interfaa iterator pentru parcurgerea elementele- r unei
i * liste nlnuite abstracte ( simplu nlnuita , dublu
8 * nlnuita , circulara, etc.)
9 */
io public interface Listltr
" {
12 /** Insereaz un element la poziia curenta.*/
13 void insert(Object x) throws ItemNotFoundException;
14
15 /* *
16
* Seteaz poziia curenta pe elementul x daca
17
* il gsete in lista.
18
*/
19 boolean find(Object x);
20
21 /** terge elementul x din lista. */
22 void remove(Object x) throws ItemNotFoundException;
23
24 /** Verifica daca lista a fost parcursa in totalitate. */
25 boolean isInList ();
26
27 /** Obine elementul aflat pe poziia curenta . */
28 Object retrieve();
29
30 /** Seteaz poziia naintea primului element . */
31 void zeroth ( ) ;
59
34
35
36
37
38
39
40
41
43
44
45
46
47
48
49
50
si
*
53
54
55
57
59
6o
62
current = itr;
return true ;
}
/** terge obiectul x din lista
* throws ItemNotFoundException daca x nu se afla in lista*/
public void remove (Object x) throws ItemNotFoundException
j
ListNode itr = t h e Li s t . header ;
while (itr.next != nuli && ! i t r . next . element, equals(x))
{
i t r = i t r . next ;
}
77
79
80
82
83
84
85
86
87
88
89
90
92
93
94
95
96
*
98
99
IDO
101
102
103
104
105
106
107
108
109
i io
ni
112
113
62
10.5. ARBORI
10.5
Arbori
10.5.1
Noiuni generale
Vom trece acum s studiem cele mai importante structuri neliniare care apar
n algoritmii pentru calculatoare: arborii. n general vorbind, structura arbores
cent implic o relaie de ramificare ntre noduri, foarte asemntoare celei n
tlnite la crengile unui arbore din natur.
Una dintre definiiile cele mai rspndite ale arborilor (nu neaprat binari)
este urmtoarea (dup D.E. Knuth):
Definiie: Un arbore (Figura 10.5) este o mulime finit T de unul sau mai
multe noduri, care are proprietile:
i) exist un nod special, numit rdcina arborelui;
ii) toate celelalte noduri din T sunt repartizate n mulimi T , Ti , . . . , Tm
disjuncte, fiecare mulime la rndul su fiind un arbore. Arborii T, Ti, . . . , Tm
se numesc subarborii rdcinii.
Se observ c definiia de mai sus este recursiv (recursivitatea este prezen
tat n capitolul 12): am definit un arbore pe baza unor arbori. Totui, privind cu
atenie definiia ne dm seama c nu se pune problema circularitii, deoarece
un arbore cu un singur nod este alctuit doar din rdcin, iar arborii cu n > 1
noduri sunt definii pe baza arborilor cu mai puin de n noduri. Exist i definiii
nerecursive ale arborilor3, dar definiia recursiv este mai adecvat, deoarece
vom vedea c recursivitatea pare s fie o trstur inerent operaiilor pe struc
turi arborescente. Caracterul recursiv al arborilor este de altfel prezent i n
natur, deoarece mugurii arborilor tineri cresc i se dezvolt n subarbori, care
la rndul lor fac muguri i aa mai departe.
Nodul rdcin al fiecrui subarbore se numete fiul (sau copilul) rdcinii,
iar rdcina este tatl (sau printele) fiecrui nod rdcin din subarbori.
3De exemplu, n teoria grafurilor, un arbore este definit ca un graf conex i fr cicluri.
64
10.5. ARBORI
Arbori binari
Definiie: Un arbore binar este un arbore n care orice nod are cel mult doi
fii.
Figura 10.7 arat c un arbore binar const ntr-o rdcin i doi subarbori
T8 i T((, oricare din ei putnd s fie vid (s lipseasc).
O proprietate deosebit de important a arborilor binari este c adncimea
medie a unui arbore binar cu n noduri este considerabil mai mic dect n. Se
poate arta c adncimea medie a unui arbore binar este proporional cu Jn,
iar adncimea medie a unui caz particular de arbore binar, arborele binar de
65
10.5. ARBORI
Figura 10.8: Exemple de arbori binari (a) arbore binar degenerat (b) arbore
binar nedegenerat
NIVEL
10.5. ARBORI
0 referin ctre fiul din dreapta. Dac un nod nu are fiu n stnga atunci left
este nuli, analog, dac nu are fiu n dreapta, atunci right este nuli. Putem
astfel defini clasa BinaryNode, care reine un nod al unui arbore binar:
1
*
3
4
5
6
7
Informaia util din cadrul nodului este reinut de atributul element, care
este de tip Ob j e ct , deci poate referi o instan de orice tip. Figura 10.9 prezin
t modul n care se reprezint nlnuit arborii binari din Figura 10.8. Variabila
root din Figura 10.9 este o referin care indic rdcina arborelui.
Exist mai muli algoritmi pentru manevrarea structurilor arborescente, i o
idee care apare de foarte multe ori este noiunea de parcurgere, de "deplasare"
prin arbore. Parcurgerea unui arbore este de fapt o metod de examinare sis
tematic a nodurilor arborelui n aa fel nct fiecare nod s fie vizitat o singur
67
10.5. ARBORI
dat. Parcurgerea complet a unui arbore ne ofer o aranjare linear a nodurilor,
i operarea multor algoritmi este simplificat dac tim care este urmtorul nod
la care ne vom deplasa ntr-o astfel de secven, pornind de la un nod dat.
Exist trei moduri principale n care un arbore binar poate fi parcurs: nodurile
se pot vizita n preordine, inordine i n postordine. Vom descrie aceste trei
metode recursiv, ca i definiia arborelui.
Dac un arbore binar este vid (nu are nici un nod) parcurgerea lui nu pre
supune nici o operaie; n caz contrar parcurgerea comport trei etape, descrise
n tabelul urmtor:
Preordine
Inordine
Postordine
Se viziteaz rdcina
Se parcurge subarborele stng Se parcurge subarborele stng
Se parcurge subarborele stng
Se viziteaz rdcina
Se parcurge subarborele drept
Se viziteaz rdcina
Se parcurge subarborele drept Se parcurge subarborele drept
Pentru arborele din Figura 10.8 gsim c nodurile n preordine sunt:
ABDHIECFG
deoarece mai nti se viziteaz rdcina A, apoi subarborele stng (B D H I
E) i apoi subarborele drept (C F G).
Similar, parcurgerea n inordine are ca rezultat irul:
HDIBEAFCG
iar parcurgerea n postordine:
HIDEBFGCA
Numele de preordine, inordine i postordine deriv, bineneles de la pozi
ia relativ a rdcinii fa de subarbori. O posibil metod de a parcurge un
arbore n preordine este dat n Listing 10.17. Metoda primete ca parametru o
referin ctre rdcina subarborelui care este vizitat. Evident c la primul apel,
parametrul este chiar rdcina arborelui (referina r oot din Figura 10.9). Vom
vedea cum se integreaz parcurgerea unui arbore n cadrul unei clase complete
n paragraful 10.5.3.
Listing 10.17: Metod generic pentru parcurgerea n preordine a unui arbore
binar
i public void preord ( BinaryNode t)
^(
3 if( t != nuli )
4 (
5
process( t . element ) ;
6
preord ( t.left ) ;
7
preord ( t.right ) ;
8 )
>)
68
10.5. ARBORI
Celelalte dou parcurgeri se realizeaz absolut analog, prin simpla reordonare a instruciunilor de parcurgere.
10.5.3
Una dintre cele mai importante aplicaii ale arborilor este utilizarea acestora
n probleme de cutare. Proprietatea care face ca un arbore binar s devin un
arbore binar de cutare este: pentru oricare nod X al arborelui toate nodurile
din subarborele stng sunt mai mici dect X, i toate nodurile din subarborele
drept sunt mai mari dect X. Aadar, fa de arborii binari obinuii, arborii
de cutare mai adaug o relaie de ordine ntre elemente. Pentru a putea fi
comparate, nodurile nu var mai conine obiecte de tip Ob j ect, ca n paragraful
10.5.2, ci instane ale clasei Comparable. Arborele binar de cutare este
o structur de date n care, pe lng cutare rapid, putem aduga sau terge
eficient elemente. Figura 10.10 ilustreaz operaiile de baz permise asupra
unui arbore binar de cutare.
insert
find,remove
De exemplu, arborii din Figura 10.8 nu sunt arbori binari de cutare, deoa
rece, n ambele cazuri, n stnga nodului A se afl nodul B, care are valoare mai
mare dect A (dac considerm ordinea alfabetic).
S studiem cu atenie arborii din Figura 10.11. La prima vedere, ambii
par s fie arbori binari de cutare; examinnd totui mai minuios arborele din
dreapta, constatm c nodul 7, dei este mai mare dect rdcina, 6, se afl n
stnga ei, ceea ce contravine regulii arborilor de cutare.
69
10.5. ARBORI
Figura 10.1 1 : Doi arbori binari. Doar arborele din stnga este i arbore binar de
cutare.
10.5. ARBORI
package exceptions ;
11
12
13
14
15
16
10.5. ARBORI
10.5. ARBORI
10.5. ARBORI
10.5. ARBORI
realizez apelul recursiv al metodei insert ( ) : subarborelui stng (sau drept)
curent i se atribuie rezultatul inserrii nodului x curent. La un moment dat,
metoda va crea un nou nod, iar adresa acelui nod va fi ntoars ctre nodul
printe.
tergerea elementului minim i a celui maxim: tergerea elementului minim
const n gsirea lui, dup care se "ridic" subarborele drept n locul lui. Metoda
removeMin ( ) de la liniile 152-170 realizeaz exact acest lucru. Observai c
aici, pentru variaie, ciclul while de la liniile 105-108 ale metodei f indMin ( )
a fost nlocuit cu un apel recursiv. "Ridicarea" subarborelui drept n locul ele
mentului ters se face simplu, prin atribuirea de la linia 166:
t = t . right ;
tergerea elementului maxim se realizeaz absolut analog.
tergerea unui nod din arbore: Operaia de tergere a unui nod este cea
mai delicat operaie pe arborele binar de cutare, deoarece pe lng eliminarea
nodului mai implic i operaii suplimentare pentru a pstra structura de arbore
binar. n cazul operaiei de tergere, dup ce am gsit nodul care trebuie ters
trebuie s considerm mai multe posibiliti:
Dac nodul care trebuie ters este o frunz, el poate fi ters imediat prin
nlocuirea legturii printelui su cu nuli;
Dac nodul care trebuie ters are doar un singur fiu (indiferent c este
stng sau drept), tergerea lui se face prin ajustarea legturii printelui
su pentru a-1 "ocoli" (Figura 10.13);
Cazul complicat apare atunci cnd nodul care trebuie ters are doi fii.
Strategia general n acest caz este de a nlocui cheia nodului care tre
buie ters cu cea mai mic cheie din subarborele su drept (care este
uor de gsit) dup care nodul cu aceast cheie se terge folosind metoda
removeMin ( ) descris anterior (Figura 10.14).
tergerea unui nod din arbore se realizeaz folosind metoda remove ( Com
par able ) din liniile 28-32, care la rndul ei deleg problema ctre remove( Compar able, BinaryNode) de la liniile 172-199. Ca orice metod recursiv, remove ( ) ncepe la linia 175 cu condiia de terminare:
if ( t == nuli )
75
10.5. ARBORI
Figura 10.13: tergerea unui nod (4) cu un singur fiu. Legtura 2-3 "ocolete"
nodul 4.
76
10.5. ARBORI
care este adevrat cnd subarborele curent este vid. n aceast situaie elemen
tul x nu este gsit i se arunc o ItemNotFoundException.
Dac subarborele nu este vid, se compar elementul care trebuie ters cu r
dcina i, dac sunt diferite, se coboar fie pe subarborele drept fie pe cel stng.
In cazul n care elementul care trebuie ters a fost gsit (adic x . compareTo ( t . element) este 0) trebuie s eliminm nodul respectiv din arbore avnd
ns mare grij s nu pierdem structura de arbore binar de cutare. Dac nodul
curent are descendeni att n stnga ct i n dreapta, adic
t.left != nuli && t . right != nuli
este true, vom folosi artificiul descris anterior: vom aduce n locul nodului
ters, cel mai mic nod din subarborele drept (care este evident mai mare dect
toate nodurile din subarborele stng, deci se va pstra structura de arbore de
cutare):
t . element = findMin ( t . ri ght ). element ;
dup care vom apela removeMin ( ) pentru a terge elementul minim din acest
subarbore:
t. right = removeMin ( t . ri ght )
Dac nodul curent nu are descendeni n ambele pri, operaia de tergere
este mult mai simpl: pur i simplu se "urc" subarborele nevid n locul nodului
care a fost ters (altfel spus, nodul ters este "ocolit"):
t = (t.left != nuli)? t.left : t. right ;
Metodele isEmpty ( ) i makeEmpty ( ) sunt banale i nu le vom detalia
aici.
Listing 10.21: Implementarea arborelui binar de cutare
i package datastructures ;
2
3 import exceptions .*;
4/** Arbore binar de cutare alocat nlnuit . Toate
5 * cutrile se fac pe baza metodei compareTo ( ) . Expune
6* metode pentru inserare, tergere, cutare etc. */
7 public class BinarySearchTree implements SearchTree
>{
9 /** Referina ctre rdcina arborelui*/
10 protected BinaryNode root ;
11
12 /* * Constructor care iniializeaz rdcina cu nuli */
13 public BinarySearchTree ()
77
10.5. ARBORI
14
{
root = null ;
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
51
52
49
50
53
54
55
56
57
58
59
60
61
62
63
78
10.5. ARBORI
/** ntoarce true daca arborele este vid */
public boolean isEmpty ()
{
return root == nuli ;
}
/**Videaza arborele, setnd rdcina la nuli */
public void makeEmpty ()
{
root = nuli ;
protected BinaryNode find ( Comparable x, BinaryNode t)
throws ItemNotFoundException
{
while (t != nuli)
!
f ( x . compareTo ( t . element ) < 0)
t = t . left ;
Ise if ( x . compareTo ( t . element ) > 0)
t = t . ri ght ;
1se
return t; //element gsit
79
10.5. ARBORI
protected BinaryNode findMax ( BinaryNode t)
throws ItemNotFoundException
{
if ( t == nuli)
{
throw new ItemNotFoundException (" Element negasit");
}
while (t.right != nuli)
{
t = t . ri ght ;
}
return t ;
protected BinaryNode i n s e rt ( Comparable x, BinaryNode t)
throws DuplicateltemException
{
if ( t == nuli)
t = new BinaryNode (x , nuli, nuli);
else if ( x . compareTo ( t . element ) < 0)
t . left = insert (x , t . left );
else if ( x . compareTo ( t . element ) > 0)
t.right = insert(x, t.right);
else
throw new DuplicateltemException (" Elementul exista deja");
return t ;
protected BinaryNode removeMin ( BinaryNode t)
throws ItemNotFoundException
{
if ( t == nuli)
{
throw new ItemNotFoundException (" Element negasit");
if ( t . left != nuli )
{
t.left = removeMin ( t . 1 e ft ) ;
80
10.5. ARBORI
else
t = t . ri ght
return t ;
protected BinaryNode remove ( Comparable x, BinaryNode t)
throws ItemNotFoundException
if (t == nuli)
throw new ItemNotFoundException (" Element negasit");
f ( x . compareTo ( t . element ) < 0)
t.left = remove(x, t.left);
lse if ( x . compareTo ( t . element ) > 0)
t.right = remove(x, t.right);
lse if (t.left != nuli && t . ri ght != nuli)
t . element = findMin ( t . ri ght ). element ;
t.right = removeMin ( t . ri ght ) ;
1se
t = (t.left != nuli) ? t.left : t.right;
return t ;
2(12 }
Listing 10.22 prezint modul n care arborele binar de cutare poate fi
folosit pentru obiecte de tip String.
Interfaa SearchTree mai are dou metode suplimentare: una pentru a
gsi cel mai mic element i una pentru a gsi cel mai mare element. Se poate
arta c transpirnd un pic mai mult se poate gsi foarte eficient i cel mai mic
al k-lea element, pentru oricare k trimis ca parametru.
S vedem care sunt timpii de execuie pentru operaiile pe un arbore bi
nar de cutare. Este normal s sperm c timpii de execuie pentru f ind ( ) ,
81
10.5. ARBORI
insert ( ) i remove ( ) s fie logaritmici, deoarece aceasta este valoarea
pe care o vom obine pentru cutarea binar (paragraful 12.3). Din nefericire,
pentru cea mai simpl implementare a arborelui binar de cutare, acest lucru nu
este adevrat. Timpul mediu de execuie este ntr-adevr logaritmic, dar n cazul
cel mai nefavorabil (cnd arborele este "dezechilibrat") timpul de execuie este
0 ( n ) , caz care apare destul de frecvent. Totui, prin aplicarea anumitor trucuri
de algoritmic se pot obine anumite structuri mai complexe (arbori bicolori)
care au ntr-adevr un cost logaritmic pentru fiecare operaie.
Listing 10.22: Model de program care utilizeaz arbori de cutare. Programul
va afia: Gsit Cristi;Alina nu a fost gsit
1 import d at a s t ru c t ure s . * ;
2 import exceptions .*;
3/** Clasa simpla de test pentru arborii binari de cutare.*/
4 public elass TestSearchTree
5!
6 public static void main ( String [ ] args)
' 1
b
SearchTree t = new Binary SearchTree ( ) ;
10
11
12
rv
result = (String) t . find ( " Cri s ti " ) ;
System . out . prin (" Gsit " + result + "; ");
atch ( ItemNotFoundException e)
System . out . prin (" C ri s ti nu a fost gsit; ");
ry
result = (String) t . find (" Alina ") ;
System . out . prin (" Gsit " + result + " ;");
}
catch ( ItemNotFoundException e)
82
10.6
Tabele de repartizare
Exist foarte multe aplicaii care necesit o cutare dinamic bazat doar
pe un nume. O aplicaie clasic este tabela de simboluri a unui compilator. Pe
msur ce compileaz programul, compilatorul trebuie s rein numele (mpre
un cu tipul, durata de via, locaia de memorie) tuturor identificatorilor care
au fost declarai. Atunci cnd vede un identificator n afara unei instruciuni de
declarare, compilatorul verific s vad dac acesta a fost declarat. Dac a fost,
compilatorul verific informaia adecvat din tabela de simboluri.
Avnd n vedere faptul c arborele binar de cutare permite acces logaritmic
la obiecte cu denumiri oarecare, de ce am avea nevoie de o alt structur de date?
Rspunsul este c arborele binar de cutare poate s dea un timp de execuie
liniar pentru accesul unui element, iar pentru a ne asigura de cost logaritmic
este nevoie de algoritmi mult mai sofisticai.
Tabela de repartizare (engl. hashtable) este o structur de date care evit
timpul de execuie liniar, ba mai mult, suport aceste operaii n timp (mediu)
constant. Astfel, timpul de acces la un element din tabel nu depinde de numrul
de elemente care sunt n tabel. n acelai timp, tabela de repartizare nu folosete
neaprat alocarea nlnuit (ca arborele binar). Aceasta face ca tabela de repar
tizare s fie rapid n practic. Un alt avantaj fa de arborele binar de cutare
este c elementele stocate n tabela de repartizare nu trebuie s implementeze
interfaa Compar able. Acum probabil c v ntrebai: bine, dar dac tabela de
repartizare este att de eficient, de ce se mai folosec arbori binari? Rspunsul
este c arborii binari, dei au operaii de inserare i acces care se execut n timp
logaritmic dispun de operaii pe care tabele de repartizare nu le suport. Astfel,
nu este posibil s parcurgem eficient elementele dintr-o tabel de repartizare.
Practic, tabelele de repartizare ofer un suport eficient pentru numai trei ope
raii: adgarea unui element, tergerea unui element i cutarea unui element.
Parcurgerea elementelor, calculul minimului sau maximului nu sunt suportate.
83
insert
nd, remove
Tabela de repartizare
package d at a s t r uc t u re s ;
import exceptions .;
Interfata expusa de o tabela de repartizare. Ofera
4/
84
2s
return QuadraticProbingTable . hash ( value , tableSize);
29 }
31) }
Pentru a calcula numrul asociat unui obiect de tip MyString n cadrul
85
package d at a s t r uc t u re s ;
3/
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
i package datastructures ;
2
3 import exceptions .*;
4/** Implementeaz o tabela cu repartizare nchisa, in care
5 * elementele tabelei de repartizare se rein intrun tablou */
6 public abstract class ProbingHashTable
7 implements HashTable
>{
9 protected HashEntry [] elements ;
10 private int currentSize;
11 private static final int DEFAULT_TABLE_SIZE = 11;
12
13 /** Calculeaz poziia unui element in cadrul tabelei*/
14 protected abstract int findPos ( Hashable x);
15
16 /** Aloca memorie si intializeaza elementele tabloului */
17 public ProbingHashTable ()
18 {
19
elements = new HashEntry [DEFAULT_TABLE_SIZE] ;
20
makeEmpty ( ) ;
2, }
22
23 /** Golete tabela de repartizare, si elibereaz toate
24
* referinele din tabloul elements */
25 public void makeEmpty ()
26 [
27
currentSize = 0;
28
29
for ( int i =0; i < elements . length ; i ++)
30
{
31
elements [i] = nuli;
}
33 }
34
35 /** ntoarce elementul cu cheia x.hash()
36
* throws ItemNotF oundException daca elementul nu e gsit */
37 public Hashable find (Hashable x)
38 throws ItemNotFoundException
39 {
40
int currentPos = findPos(x);
41
42
assertFound(currentPos, " Element negasit");
43
44
return elements [currentPos]. element;
45 }
46
47 /** Verifica daca elementul de pe poziia currentPos
48
* exista si este activ.
49
* throws ItemNotF oundException in caz contrar */
87
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
hashVal %= tableSize;
115
116
if ( hashVal < 0)
{
hashVal += tableSize ;
}
117
118
119
120
121
return hashVal ;
122
123
124
int collisionNum = 0;
9
int currentPos = x . hash ( array . length ) ;
io
11
while (array[currentPos] != nuli &&
12
!array[currentPos ]. element . equals (x ) )
{
14
collisionNum + + ;
15
currentPos += 2 * collisionNum 1;
16
17
if (currentPos >= array . length )
{
19
currentPos = array . length ;
20
}
}
22
23
return currentPos ;
24
* i
26 }
numrul asociat elementului trebuie s fie mai mic dect dimensiunea tabelei
(numrul nu este altceva dect un indice al irului element s).
10.6.1
Tratarea coliziunilor
10.7
Cozi de prioritate
Figura 10.16: Modelul pentru coada de prioritate. Doar elementul minim este
accesibil.
insert
ndMin, deleteMin
Coada de prioritate
package datastructures ;
import exceptions .;
/C
Interfata care descrie operatiile expuse de o coada
5
de prioritati.
6
7
/
8 public interface PriorityQu eue
9 {
/ Adauga elementul x in coada de prioritati/
10
11
void insert(Comparable x) ;
/ Sterge si intoarce elementul minim.
/
12
Comparable deleteMin () throws UnderflowException ;
13
14
/ Intoarce elementul minim din coada/
Comparable findMin () throws UnderflowException ;
15
16
/ Goleste coada de prioritati/
void makeEmpty ( ) ;
17
18
/ Intoarce true daca coada este vida/
boolean isEmpty () ;
19
3
4
20
21
10.7.1
Ansamble
Figura 10.19: Doi arbori binari complei. Numai arborele din stnga este un
ansamblu (un min-ansamblu). In arborele din dreapta nodul 40 are ca fiu nodul
34, ceea ce ncalc proprietatea de ordonare.
Figura 10.21 : Crearea unei guri n locul rdcinii prin extragerea valorii mini
me. Din acest moment se caut locul potrivit pentru a insera ultimul nod, 29; se
vor urca pe rnd nodurile 14, 23, 25, dup care se aeaz 29 n locul lsat liber
de 25
26
27
28
29
30
31
32
33
34
35
37
38
40
4i
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
63
64
65
66
67
68
100
if (currentSize == elements.length 1)
!
Comparable [ ] oldelements = elements ;
71
72
73
74
75
77
}
80
XI
82
83
84
85
B6
ss
89
90
91
92
93
94
95
96
,7
98
99
oo
101
102
103
104
105
106
107
108
109
1 II)
ui
112
113
4
115
116
1 17
118
}
/** ntoarce elementul minim din ansamblu.
* throws Unse rflowException Daca ansamblul este gol */
public Comparable findMin () throws UnderflowException
{
if ( isEmpty ( ) )
!
throw new UnderflowException ( "Heap vid " ) ;
}
if ( ! orderOK)
{
fixHeap ( ) ;
}
return elements [ 1 ] ;
}
/** Transforma irul elements intrun ansamblu filtrnd
* toate nodurile care nu sunt frunze pana ajunge la rdcina */
private void fixHeap ()
{
for (int i = currentSize / 2; i > 0; i )
{
percolateDown ( i ) ;
}
orderOK = true ;
}
/** Filtreaz elementul de pe poziia hole asezandul in
* sub ar b o rele cu rdcina hole la locul cuvenit * /
private void percolateDown (int hole)
{
int child ;
Comparable tmp = elements [ hole ] ;
while(hole * 2 <= currentSize)
101
child
hole * 2 ;
];
percolateDown (1);
return minltem ;
/** true daca ansamblul este vid */
public boolean isEmpty()
{
return currentSize == 0;
/** Golete ansamblul setnd dimensiunea la 0 */
public void makeEmpty()
{
currentSize = 0;
102
Rezumat
Am prezentat pe scurt n cadrul acestui capitol cele mai importante structuri
de date utilizate de programatori n practic. Fiecare structur de date ofer o
interfa clar cu toate operaiile pe care le permite, implementarea ei putnd
fi fcut n mai multe moduri. Stiva i coada sunt structuri de date elementare,
care ofer operaii de adugare respectiv tergere la un singur capt n timp
constant. Listele nlnuite permit operaii de adugare i tergere arbitrare i
au avantajul fa de iruri c inserarea unui element nu implic deplasarea ele
mentelor aflate la dreapta. Arborii binari de cutare permit inserarea, ctuarea
103
11.Metoda Backtracking
Prezentare general
11.2
Prezentarea metodei
Atribuie i avanseaz
Acest tip de modificare are loc atunci cnd mai exist valori neconsumate
pentru componenta curent, Xk, (deci Ck C Vk), iar valoarea aleas, Vk, are
proprietatea c (v, . . . ,Vk) respect condiiile de continuare. In acest caz va
loarea t>k se atribuie lui Xk i se adaug mulimii Ck, dup care se avanseaz
la componenta urmtoare, Xk+i- Aceast modificare a configuraiei poate fi
reprezentat n felul urmtor:
Vk
,Vk-l
-,Vk-l,
vk
..,Ck-i,CkU{vk}
Xk j Xk+A i
Xk+A,
<j>,.
Xi X2 X3 xa
11.2.2
#2 X3 Xa
<f> <t> <t>
ncercare euat
Acest tip de modificare are loc atunci cnd, ca i n cazul anterior, mai ex
ist valori neconsumate pentru componenta curent, Xk , dar valoarea Vk aleas
nu respect condiiile de continuare. n acest caz, Vk este adugat mulimii Ck
(deci este consumat), dar nu se avanseaz la componenta urmtoare. Modifi
carea este notat prin:
Vk
114 Ck-i
Xk i Xk+1 1
Ck, 4>,--
,Vk-l
, Ck-i
Xk i
Xk+A
Ck U {vk},(f>,
A
A
{A}
11.2.3
2 XS X4
<f> <t> <t>
A
{A}
2 XS X4
{A}(f> (f)
Revenire
Acest tip de transformare apare atunci cnd toate valorile pentru compo
nenta Xk au fost consumate (Ck = Vk), deci nu mai avem alte posibiliti de a
da valori acestei componente. n acest caz se revine la componenta precedent,
Xk-i , ncercndu-se atribuirea unei noi valori acestei componente. Este impor
tant de remarcat faptul c revenirea la Xk-i implic faptul c pentru Xk se vor
ncerca din nou toate variantele posibile, deci mulimea Ck trebuie din nou s
fie vid. Transformarea este notat prin:
-,Vk-l
Xk i Xfc--i ,
Ck, 4>,-
Xk 1 3 Xk 3
Ck-1, <t>,-
,Vk-2
3
1
2
{1,2,3}{1}{1,2}
11.2.4
X4
{1,2,3,4}
3
1
{1.2,3}{1}
x3 x4
{1,2}^
,v
sol
; Un_l
Xn
115
12
3
4
{1}{1,2}{1,2,3}{1,2,3,4}
1
2
3
{1}{1, 2} {1,2, 3}
sol
X4
{1,2,3,4}
Xi
Vi
x2
(f>
...
...
xr,
(f>
M
A M
{A}{M}
^2
sol I
A
{A}
X2
{M}
M
A
{A}
X2
{M,N}
B
{A,B}
Xi X2
{A}<fi
B M
{A,B}{M}
x2
N
B
{A,B}
x2
{M}
C
{A,B,C}
X2 \
B
{A,B}
x2
{M,N}
Xi X2
{A,B}cf>
M
* J
C
N
{A,B,C}{M,N}
11.3
N
C
M
/
{A,B,C}
{M}
*l
sol
C
{A,B,C}
sol l
X2
{M,N}
C
{A,B,C}
x2
{M}
Xi
X2
{A,B,C}cf>
Algoritmul de mai sus funcioneaz pentru cazul cel mai general, dar este
destul de dificil de programat din cauza lucrului cu mulimile Ck i Vk Din
fericire, adeseori n practic mulimile Vk au forma
Vk = {1,2,. ..,*}
deci fiecare mulime Vk poate fi reprezentat foarte simplu prin numrul su
de elemente, Sk- Pentru a simplifica i mai mult lucrurile, n cadrul procesului
de construire a unei soluii vom alege valorile pentru fiecare component Xk n
ordine cresctoare, pornind de la 1 i pn la Sk- In aceast situaie, mulimea
de valori consumate Ck va fi de forma {1, 2, . . . , Vk} i, drept consecin, va
putea fi reprezentat doar prin valoarea t>k
Consideraiile de mai sus permit nlocuirea algoritmului anterior, bazat pe
mulimi, cu un algoritm simplificat care lucreaz numai cu numere.
Dac n Exemplul 1 vom conveni s reprezentm pe A, B, C prin valorile
1, 2, 3, iar pe M i N prin 1 i 2, configuraiile care se succed n procesul de
cutare pot fi reprezentate simplificat astfel:
(|
1
l 2 ) _^ ( 1
1
2 ) _^ ( 1
etc
|)^(1
X2 )
if (k == n) // am gsit o soluie
!
retSol();// afi sam s o lut ia
k
; //revenire dupa gsirea unei soluii
8
9
ii
else
!
13
15
ie
s
20
21
22
23
}
}
27 }
11.4
11.4.1
S se genereze
i
^
3
5
7
io
"
i ^ j.
return a;
}
63
64 }
Prin rularea programului urmtor se vor genera permutrile mulimii intro
duse de la tastatur.
Listing 11.4: Rezolvarea problemei permutrilor
i import io . Reader ;
2
3 /* *
4* Program care genereaz permutrile unei mulimi
5* introduse de la tastatura .
6 */
7 public class Permutri
8!
9 /** Testeaz daca elementul adugat exista deja.*/
10 public static boolean continuare(int[] x, int k)
>' !
12
for (int i = 0; i < k; ++i)
{
if (x[k] == x[i ])
{
i6
return f al s e ;
}
}
19
20
return true ;
22
23 /** Construiete un string care conine soluia curenta.*/
24 public static void retSol(int[] s , int[] x, int nrSol)
* !
26
System . out . p ri n t (" Permutarea " + nrSol + ": " );
27
for (int i = 0; i < s.length; i++)
28
{
29
System . out . prin ( s [x [ i ] 1] + " ");
30
}
31
System . out . println ( ) ;
32 }
33
34 /* *
35 * Backtracking standard pentru determinarea
36 * permutrilor mulimii .
37 */
38 public static void backtracking (int [] s)
39 (
122
in t k = 0 ;
//aloca memorie pentru irul de indici
int[] x = new int [ s . length ] ;
int nrSol = 0;
//iniializeaz x
for (int i = 0; i < x. length ; i ++)
!
x[i ] = 0;
}
4s
50
51
52
//procesul de backtracking
while (k >= 0)
{
if ( k = = x . length) //am gsit o soluie
{
retSol(s,x, + +nrSol) ; // afieaz soluia
k
; // revenire dupa ce o soluie a fost gsita
54
56
57
59
60
61
el se
{
if (x[k] < x. length) //valori neconsumate?
!
//se ia urmtoarea valoare neconsumata
x [k ] ++ ;
63
64
65
66
67
68
69
70
73
75
] = 0; //revenire
}
}
79
si
82
B3
84
85
86
87
89
11.4.2
Xi^Xj,
Vi,j = l,m
Exemplu:
1
2
3
5
7
9
"
i
^
3
4
11.4.3
Problema damelor
xk {1, 2, . . . , n},
1
2
3
5
7
io
%i\ = \k
^|
11.4.4
true
false
1
2
3
5
7
io
"
Rezumat
In acest capitol am prezentat metoda bactracking, care se reduce n esen
la parcurgerea exhaustiv a spaiului de cutare, n care se elimin cu grij
configuraiile care nu pot conduce la o soluie. Metoda backtracking se aplic
oricrei probleme a crei soluie se poate scrie sub form de ir, cu fiecare element al irului lund valori n cadrul unei mulimi finite. Elementul estenial
care determin eficiena cutrii este reprezentat de condiiile de continuare care
129
dac nu se agreaz
altfel
X\l\ ^ i\
{i
1
0
/ 0 1
10
0 0
1 0 \
0 0
11
V 1
0 )
136
12.Divide et impera
12.1
Introducere n recursivitate
kw(E
'
pentru
pentru
h
1n algoritmica, prin procedura se ntelege o functie care nu returneaza nici o valuare (de exem
plu, n Java o metoda care returneaza void)
138
pentru
pentru
n> 1
n = 0, 1
Procedura AfisLin este apelat de procedura AfisMat descris mai jos, care
afieaz, linie cu linie, o ntreag matrice pe care o primete ca parametru:
AfisMatfa, 5)
2181; AfisMat(a,n);...
Controlul va fi apoi preluat de ctre procedura AfisMat, care intr n ciclul
pentru cu apelul: AfisLin(a,n,i) aflat, s zicem, la linia 2198.
In acest moment controlul va fi preluat de ctre procedura AfisLin, dar nu
nainte de a aduga la vrful stivei linia de la care s-a fcut apelul, valorile
parametrilor i a variabilei locale i:
2145;/act(0);
2145;/act(l);
2145;/act(2);
2145;/act(3);
2145;/act(4);
xxxx; fact(5);
fact(l) fiind calculat, se poate reveni la calculul nmulirii 2*fact(l) = 2,
apoi, fact(2) fiind calculat se revine la calculul nmulirii 3* fact(2)=6 etc,
pn se calculeaz 5*/<zct(4)=120 i se revine n programul apelant.
S vedem acum modul n care se realizeaz calculul recursiv al irului lui
Fibonacci. Vom vedea c timpul de calcul al acestei recurene este incomparabil
mai mare fa de calculul factorialului. S presupunem c funcia/;^ se apeleaz
cu parametrul n = 3. n aceast situaie, se depune pe stiv apelul fib(3)
mpreun cu linia de unde s-a realizat apelul (de exemplu, 2160). n linia 2160
a procedurii are loc apelul recursiv: fib < fib(n 1) + fib(n 2) care n
cazul nostru, n fiind 3, presupune calcularea sumei fib(2) + fib{\). Aceast
sum nu poate fi calculat nainte de a-1 calcula pe fib(2). Calculul lui fib(2)
presupune calcularea sumei fib(l) + fib{0). fib{\) i fib(0) se calculeaz
direct la urmtorul apel recursiv, dup care se calculeaz suma lor, rezultnd
c fib(2) = 2. Abia acum se revine la suma fib(2) + fib{\) i se calculeaz
fib(l), dup care se revine i se calculeaz fib(3).
Modul de calcul al lui fib(n) recursiv se poate reprezenta foarte sugestiv
arborescent. Rdcina arborelui este fib(n), iar cei doi fii sunt apelurile recur
sive pe care fib(n) le genereaz, i anume fib(n 1) i fib(n 2). Apoi se
reprezint apelurile recursive generate de fib(n 2) ca n Figura 12.1.
Din Figura 12.1 se observ c anumite valori ale irului lui Fibonacci se
calculeaz (inutil) de mai multe ori. fib(n) i fib(n 1) se calculeaz o dat,
fib(n 2) se calculeaz de dou ori, fib(n 3) de 3 ori etc. Aceasta explic
de ce n capitolul 9 am obinut o complexitate exponenial pentru varianta recursiv de calcul a irului lui Fibonacci.
142
12.1.2
}
Reamintim c Reader este o clas ajuttoare pentru citirea de date de la
tastatur i a fost definit n cadrul primului volum, fiind reluat i n cadrul
acestui volum, la paragraful 1 1 .4. 1 .
Este important de notat c pentru ca metoda s funcioneze corect, variabila
a trebuie declarat ca variabil local; astfel, toate valorile citite vor fi salvate pe
stiv, de unde vor fi extrase succesiv (n ordinea invers citirii) dup ntlnirea
caracterului
Exemplu: Transformarea unui numr din baza 10 ntr-o baz b, mai mic
dect 10.
S ne reamintim algoritmul clasic de trecere din baza 10 n baza b. Numrul
se mparte la b i se reine restul. Ctul se mparte din nou la b i se reine restul
i se continu acest procedeu pn cnd ctul devine mai mic dect b. Rezultatul
se obine prin scrierea n ordine invers a resturilor obinute.
Formularea recursiv a acestei rezolvri pentru trecerea unui numr n n
baza b este:
pentru n > b
pentru n < b
funcie transform(n:integer)
rest = n mod b
dac n > b atunci transform(ndivb)
scrie rest
return
int rest=n%b;
if(n >= b)
{
transform(n/b);
}
System,out.print(rest);
'
}
144
12.2
Divide et Impera este o metod special prin care se pot aborda anumite ca
tegorii de probleme. Ca i celelalte metode de elaborare a algoritmilor, Divide et
Impera se bazeaz pe un principiu extrem se simplu: se descompune problema
iniial n dou (sau mai multe) subprobleme de dimensiune mai mic, dup
care soluia problemei iniiale se obine combinnd soluiile subproblemelor n
care a fost descompus. Procedeul de descompunere se repet pn cnd, dup
descompuneri succesive, se ajunge la probleme de dimensiune mic, pentru care
exist rezolvare direct.
Evident, nu orice gen de problem se preteaz la a fi abordat cu Divide et
Impera. Din descrierea de mai sus reiese c o problem abordabil cu aceast
metod trebuie s aib dou proprieti:
1 . S se poat descompune n subprobleme;
2. Soluia problemei iniiale s se poat construi simplu pe baza soluiei
subproblemelor.
Modul n care metoda a fost descris, conduce n mod natural la o implementare
recursiv, avnd n vedere faptul c i subproblemele se rezolv n acelai mod
cu problema iniial. Iat care este forma general a unei funcii Divide et Im
pera:
12.3
Cutare binar
return
}
Poziia pe care se gsete elementul el n irul s este obinut prin apelul:
poz = binarySearch ( s , el, 0, s.length 1)
Listing 12.2 prezint o clas simpl care utilizeaz metoda cutrii binare
pentru a gsi un numr ntr-un ir citit de la tastatur:
Listing 12.2: Rezolvarea problemei cutrii binare
1 import j ava . io . * ;
2 import io . Reader ;
3
4 /* *
5 * Program ce verifica daca un element introdus
6 * de la tastatura se gsete in cadrul unui sir ordonat.
7 */
8 public class BinarySearch
{
10 /** Metoda de cutare a elementului el in irul s . */
11 public static int search(int[] s, int el, int low,
147
int high )
{
if ( low <= high )
{
int mid = (low + high) / 2;
i6
17
s
if ( el == s [mid ] )
{
//element gsit
return mid ;
}
e1se
{
if ( el < s [mid])
{
//caut in prima jumtate a subsirului
return search (s , el, low , mid 1 ) ;
}
e1se
{
//caut in a doua jumtate a subsirului
return search ( s , el , mid + 1 , high ) ;
}
}
20
21
22
23
25
26
27
28
29
30
31
32
33
34
}
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
59
60
61
148
return 1 ;
}
/** Programul principal.*/
public static void main (String[] args)
j
//citirea elementelor irului
System. out.println(" Introducei elementele irului in " +
"ordine cresctoare (pe aceeai linie):");
int [] s = Reader . readlntArray ();
//citirea elementului cutat
System, out. prin (" Introducei elementul cutat: ");
int el = Reader . readlnt ();
//cutarea elementului
int poz = search (s, el, 0, s.length 1);
//afiarea rezultatului cutrii
i f ( poz > 1 )
{
System . out . println (" Elementul " + el +
" a fost gsit in sir pe poziia " +
poz ) ;
63
64
65
66
68 }
69 }
12.4
,o }
Metoda de interclasare n acest caz este analoag cu metoda de interclasare
obinuit a dou iruri, diferena constnd n faptul c acum se interclaseaz
dou jumti ale aceluiai ir, iar rezultatul se va depune n final tot n irul
interclasat. Listing 12.4 prezint implementarea complet a sortrii prin inter
clasare, aplicat pe un ir care este preluat de la tastatur.
Listing 12.4: Soluia algoritmului de sortare Mergesort
1 import java . io . * ;
2 import io . Reader ;
4 /* *
5 * Program ce ordoneaz un sir de numere ntregi
6 * folosind metoda MergeSort .
7 */
8 public elass MergeSort
!
10 /** Metoda de interclasare a celor 2 subsiruri.*/
11 public static void intercls(int low, int mid,
12
int high , int [ ] s )
'3 {
14
int i = low;
15
int j = mid + 1 ;
16
17
int[] inter = new int[high + 1];
s
int k = low;
19
20
// int e r cla s ar e a elementelor
21
while (( i <= mid) && (j <= high ))
22
{
if (s[i] <= s[j])
{
25
inter[k + + ] = s[i + + ] ;
)
27
eIse
28
{
29
inter[k + + ] = s[j+ + ];
}
)
32
33
//au mai rmas elemente din primul subsir?
34
for ( int 1 = i ; 1 <= mid ; 1++)
{
36
inter[k + + ] = s[l];
}
38
150
44
45
46
48
49
50
51
52
53
54
55
57
5s
59
60
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
8o
82 }
83 )
151
Sortarea rapid este, aa cum i spune i numele, cea mai rapid metod
de sortare prin comparaii cunoscut n prezent. Exist foarte multe variante
ale acestei metode, o parte dintre ele avnd doar rolul de a micora timpul de
execuie n cazul cel mai nefavorabil. Vom prezenta aici varianta clasic, despre
care vei remarca cu surprindere c este neateptat de simpl. Enunul proble
mei este identic cu cel de la sortarea prin interclasare, i anume:
S se ordoneze cresctor un ir de numere ntregi.
Metoda de sortare rapid prezentat n acest paragraf este, dintr-un anumit
punct de vedere, complementara metodei Mergesort. Diferena dintre cele dou
metode este dat de faptul c, n timp ce la Mergesort mai nti vectorul se
mprea n dou pri dup care se sorta fiecare parte i apoi se interclasau cele
dou jumti, la Quicksort mprirea se face n aa fel nct cele dou iruri
s nu mai necesite a fi interclasate dup sortare, adic primul ir s conin
doar elemente mai mici (nu neaprat ordonate) dect elementele celui de-al
doilea ir. Rezult de aici c n cazul lui Quicksort, etapa de recombinare este
trivial, deoarece problema este astfel mprit n subprobleme nct s nu mai
fie necesar interclasarea irurilor. Etapele lui Divide et Impera pot fi descrise
n aceast situaie astfel:
1 . Divide: mparte irul de n elemente care urmeaz a fi sortat n dou iruri,
astfel nct elementele din primul ir s fie mai mici dect elementele din
al doilea ir;
2. Stpnete: Sorteaz recursiv cele dou subiruri utiliznd sortarea rapid;
3. Combin: irul sortat este obinut din concatenarea celor dou subiruri
sortate.
Funcia care realizeaz mprirea n subprobleme (astfel nct elementele primu
lui ir s fie mai mici dect elementele celui de-al doilea) se datoreaz lui C. A.
Hoare, care a gsit o metod de a realiza aceast mprire (numit partiionare)
n timp liniar.
Metoda de partiionare rearanjeaz elementele tabloului n funcie de primul
element, numit pivot, astfel nct elementele mai mici dect primul element sunt
trecute n stnga lui, iar elementele mai mari dect primul element sunt trecute
n dreapta lui. De exemplu, dac avem vectorul:
o = (7,8,5,2,3),
152
In acest moment low i high s-au suprapus (au devenit egale), deci partiionarea s-a ncheiat. Pivotul este pe poziia a 4-a, care este de fapt i poziia
luifinal n irul sortat.
Metoda part it ion ( ) din Listing 12.5 primete ca parametri limitele
inferioar, respectiv superioar ale irului care se partiioneaz i returneaz
poziia pe care se afl pivotul n finalul partiionrii. Poziia pivotului este im
portant deoarece ne d locul n care irul va fi desprit n dou subiruri.
Dou observaii importante merit fcute referitor la metodapartition ( ) :
1. Variabila pozPivot poate lua valoarea false dac pivotul este indi
cat de low, sau true dac pivotul este indicat de high. Atribuirea
pozPivot =\pozPivot are ca efect schimbarea strii acestei variabile
din false n true sau invers;
2. Metoda se folosete n mod inteligent de transmiterea prin valoare a para
metrilor, deoarece modific variabilele low i high bazndu-sepe faptul
c aceast modificare nu va afecta valorile lui low si high din metoda
quickSort ( ) .
Metoda de ordonare propriu-zis din Listing 12.6 respect structura Divide et
Impera obinuit, doar c funcia de recombinare a soluiilor nu mai este nece
sar, deoarece am realizat partiionarea nainte de apel.
154
1
2
3
5
6
7
high ;
}
eIse
{
low++;
)
32
34
}
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
return low ;
}
/** Metoda de sortare a irului s . */
public static void sort(int[] s, int low, int high)
j
if (low < high) //subsirul are cel puin 2 elemente
{
int mid = p art i t i o n ar e ( s , low, high);
sort(s, low, mid 1);
sort(s, mid + 1, high);
}
}
/** Programul principal.*/
public static void main (String[] args)
j
//citirea elementelor irului
System . out . p ri n 1 1 n ( " I n t r o du c e t i elementele irului " +
"(pe aceeai linie):");
int [] s = Reader . readlntArray ();
68
69
70 }
VI }
156
Expresii aritmetice
36
3V
38
39
40
41
42
43
{
return x.valueOf(x. charAt ( low ) ) ;
}
//elimina parantezele exterioare inutile
while ( x . charAt ( low ) == '(' && x . charAt ( hi gh ) == ')'
&& parant (x , low , high ))
{
low++;
high ;
)
//caut locul unde irul poate fi "rupt" in doua
for (int i = 0; i < op.length; i ++)
{
int nrp = 0;
52
53
55
se
58
59
6o
62
64
66
67
68
69
71
72
73
74
75
77
78
79
80
82
83
84
85
86
87
88
89
90
91
92
93
160
)
}
//daca sa ajuns aici, irul nu a putut fi "rupt" in
//doua, deci expresia este incorecta
return "irul este incorect" ;
}
/** Programul principal.*/
public static void main (String[] args)
{
//matricea celor 4 operatori standard
char [][] op = { {' + ', ' ' } , { ' * ' , ' / * } };
''5
96
91
100
System . out . println ( "Forma poloneza postfixata a
101
"expresiei aritmetice este : " +
io:
polonez(x, 0, x.length() 1, op));
103 }
104 }
Rezumat
La nceputul acestui capitol am prezentat elemente eseniale referitoare la
recursivitate. Am vzut care este mecanismul care st la baza acestei tehnici
i faptul c un apel recursiv nu difer n esen cu nimic de un apel obinuit.
Iat care sunt regulile de baz ale recursivitii, pe care este bine s le reinei i
aplicai ntotdeauna:
1. Condiia de terminare: cel puin o instan a problemei trebuie ntot
deauna s se poat rezolva fr a utiliza recursivitatea;
2. Progresul: orice apel recursiv trebuie s progreseze ctre condiia de terminare;
3. Crede i nu cerceta: ntotdeauna trebuie s presupunei c apelul recursiv
funcioneaz (fr a v pune problema cum);
4. Suprapunere de apeluri: evitai s executai acelai lucru de dou ori,
prin rezolvarea aceleiai instane a problemei n apeluri recursive distince
(cum am fcut la irul lui Fibonacci).
Recursivitatea are multe aplicaii concrete, cteva dintre ele fiind prezentate
chiar n cadrul acestui capitol.
Introducerea elementelor principale ale recursivitii a fost urmat apoi de
prezentarea metodei divide et impera, precum i a celor trei etape fundamen
tale care o caracterizeaz: divide, cucerete, combin. Cutarea binar este
o metod de a gsi rapid un element n cadrul unui ir ordonat. Quicksort
i Mergesort sunt metode deosebit de eficiente de a ordona un ir. Problema
expresiilor aritmetice presupune trecerea unei expresii din forma standard n
forma polonez postfixat.
161
Erori frecvente
1. Cea mai frecvent eroare la nceptori este de a uita s stabileasc o
condiie de terminare pentru apelurile recursive.
2. Fii ateni ca fiecare apel recursiv s constituie un pas ctre condiia de
terminare, altfel recursia este incorect.
3. Trebuie evitat suprapunerea apelurilor recursive, deoarece ele tind s ge
nereze algoritmi de complexitate exponenial.
4. Complexitatea algoritmilor recursivi trebuie calculat folosind formule
de recuren. Nu putei presupune c un apel recursiv are o complexitate
n timp liniar.
5. In cazul unui apel recursiv, doar variabilele locale i parametri actuali se
salveaz pe stiv. Nu v bazai pe faptul c variabilele definite n afara
funciei sunt salvate la apelul recursiv.
6. Pe de alt parte, evitai s declarai variabile locale sau parametri formali
care nu sunt necesari pentru metoda recursiv, pentru a nu umple stiva cu
informaii inutile.
7. Dei multe probleme admit descompunerea n dou sau mai multe subprobleme, nu ntotdeauna soluia problemei iniiale se poate obine pe
baza soluiei subproblemelor.
162
pentru
altfel
cmmdc(b, \a b\)
cmmdc{a, b) = | ^
a mod 6^0
pentru
altfel
a^b
[ nil
Ack(m,n) = < Ack(m 1, 1)
^ Ack{m - 1, Ack(m, n - 1))
pentru
pentru
altfel
m=0
n=0
/~<k
/~<k
i r~<k 1
k\{n-k)\
FM = { f(f\
x + 2))
pentru
altfel
x > 12
pentru
pentru
n>0
n=0
dac
dac
n= 1
n> 1
166
13.Algoritmi Greedy
Am constatat ca cu ct muncesc
mai mult, cu att am mai mult
noroc.
Thomas Jeferson
13.1
13.1.1
Soluia problemei spectacolelor este dat n Listing 13.1. Metoda selectSpectacole ( ) de la liniile 12-33 este transpunerea n Java a funciei SELECTSPECTACOLE-GREEDY. selectSpectacole ( ) primete ca parametri do
u iruri reprezentnd timpul de nceput respectiv timpul de sfrit al fiecrui
spectacol i ntoarce un vector cu specacolele care au fost planficate, n ordinea
cresctoare a timpului de ncepere. Ordonarea cresctoare a spectacolelor este
realizat de metoda ordonare ( ) de la liniile 35-70, care, pentru simplitate,
folosete metoda bulelor. De remarcat faptul c metoda ordonare ( ) trebuie
s interschimbe i valorile din irul s, pentru a menine consistena cu irul t.
Listing 13.1: Soluia problemei spectacolelor
1 import java. util.*;
2 import j ava . io . * ;
3 import io . Reader ;
4
5 /* *
6 * Problema spectacolelor ( selectarea activitilor prin
i * metoda Greedy )
8 */
9 public class Spectacole
,o {
ii /** Selectarea spectacolelor. */
12 public static Vector selectSpectacole(int[] s, int[] t)
{
u
Vector sol = new Vector () ;
15
16
if (s.length == 0) return sol;
17
s
//primul spectacol face parte din soluie
19
sol.addElement (new Integer (0));
20
21
int j = 0;
22
23
for (int i = 1; i < s.length ; i ++)
!
if (s[i] >= t[j])
!
27
sol.addElement( new Integer(i ));
28
j = i;
29
}
30
}
31
32
return sol ;
)
34
35 /** Ordonarea timpilor de terminare a spectacolelor. */
171
38
39
40
41
42
43
44
45
46
47
48
49
50
54
55
56
57
5S
59
60
61
62
63
64
65
66
68
69
70
71
72
73
74
75
76
77
79
80
si
82
83
84
85
172
}
while ( k = = 1 ) ;
}
/** Programul principal. */
public static void main (String[] args)
j
//citirea numrului de spectacole
Sy stem . out . p ri n t ( " I n t r o duc e t i numrul de spectacole: ");
int n = Reader . readlnt ( ) ;
//citirea timpilor de ncepere si terminare ai spectacolelor
Sy stem . out . p ri n 1 1 n ( " I n t r o du c e t i timpii de incepere si " +
"terminare ai spectacolelor:");
i n t | ] s = new i n t [ n ] ;
i n t | ] t = new i n t [ n ] ;
for(int i =0; i < s. length; i ++)
{
System . out . p ri n t (" s [ "
s[i] = Reader . re adl n t
System . out . p ri n t (" t [ "
t[i] = Reader . re adl nt
+ i + "] = ");
() ;
+ i + "] = ");
() ;
}
92
93
94
95
96
97
98
99
oo
101
102
103
104
105
106
107
10K
109 }
1 LO }
13.2
13.2.1
13.2.2
Substructur optim
deci exist un client (6) care necesit un timp mai lung de deservire, i care este
servit nainte (de o).
Interschimbm pe ia cu n /; cu alte cuvinte, clientul care a fost servit al
fc-lea va fi servit acum al a-lea i invers. Obinem o nou ordine de servire
care este de preferat deoarece
177
Prin metoda Greedy, selectnd permanent clientul cu timpul cel mai mic de
deservire, obinem deci ntotdeauna planificarea optim a clienilor. Problema
poate fi generalizat i pentru un sistem cu mai multe staii de servire.
Implementarea algoritmului se reduce la o banal ordonare a clienilor cresc
tor dup timpul de deservire i este prezentat n Listing 13.2.
Listing 13.2: Soluia problemei minimizrii timpului de ateptare
1 import java . io . * ;
2 import io . Reader ;
4 /* *
5 * Program pentru minimizarea timpului de ateptare al unui
6 * client pentru a fi deservit de o staie (metoda Greedy).
7 */
8 public elass MinimTimp
!
io /** Ordonarea timpilor de ateptare ai clienilor. */
n public static void ordonare(int[] t)
i
13
//ordonare prin " bubble sort"
14
15
//daca interschimbam valorile din sir
16
//atunci k este 1, altfel k este 0
17
int k;
s
int aux ;
19
20
do
{
22
//la inceput nu sunt schimbri in iruri
23
k = 0;
24
25
for (int i = 0; i < t . length 1; i ++)
{
if (t[i] > t[i + 1])
28
{
29
// inter schimbam valorile din t
30
aux = t [ i ] ;
178
38
39
40
41
42
43
44
45
46
47
48
49
}
while ( k = = 1 ) ;
}
/** Calculeaz, timpul total minim de ateptare . */
public static int calculTimpMinim(int[] t)
{
int s = 0;
System, out . prin In ( " Ordinea de deservire este:");
for (int i = 1; i <= t.length; i ++)
!
s += (t.length i + 1) * t[i 1];
51
52
53
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
" 1
179
140
150
10
90
130
10
40
110
50
30
20
30
10
30
30
20
20
80
60
10
50
de la 1 la si
vrfuri neterminale numerotate de la d la
.
Denim pentru un arbore oarecare A de acest tip lungimea externa ponderata:
f
unde
este adncimea vrfului i. Este usor de observat ca numarul total de
deplasari de elemente pentru strategia corespunzatoare lui A este chiar .
Solutia optima a problemei noastre este atunci arborele (strategia) pentru care
lungimea externa ponderata este minima.
qi + 92
Qi
12
Rezumat
n acest capitol am prezentat metoda Greedy, care se poate aplica probleme
lor care respect substructura optim i principiul alegerii Greedy. n cazul aces
tei metode, soluia se construiete succesiv, la fiecare pas realiznd o alegere
optim local, fr a reveni asupra deciziilor anterioare. Soluia construit astfel,
va fi o soluie optim a problemei de rezolvat. Am vzut modul n care se poate
demonstra corectitudinea unui algoritm utiliznd un procedeu general, care se
poate aplica la muli algoritmi de acest tip.
Noiuni fundamentale
funcie de selecie: funcie care indic cel mai promitor dintre candidaii
nc nefolosii la un moment dat.
proprietatea de alegere Greedy: se poate ajunge la o soluie optim global,
realiznd alegeri (Greedy) optime local.
problem de optimizare: problem n care se cere minimizarea sau maxi
mizarea unei funcii obiectiv.
substructura optim (principiul optimalitii): o problem evideniaz o
substructur optim dac o soluie optim a problemei conine soluii optime
ale subproblemelor.
Erori frecvente
1 . Nu ncercai s aplicai aceast metod orbete, fr a verifica dac pro
blema respect substructura optim i proprietatea de alegere Greedy.
183
185
14.1
Istoric si descriere
14.2
1
2
3
4
5
f
7
8
2Este interesant de remarcat faptul c recurgerea la tabele a dat i numele metodei. Astfel,
n cercetrile operaionale (domeniul din care provine aceast metod) termenul "programare" se
refer la un set de reguli care se aplic pe un tabel i nu la scrierea unui program pentru calculator.
188
i
*
3
4
5
7
x
9
,o
ii
,2 )
1 import java . io . * ;
2 import io . Reader ;
4 public elass Fibonacci
5!
6 public static int f i b o n ac c ii t e r ( in t n)
> 1
8
// . . .
" )
10
ii public static void main ( String args [ ] )
<2 !
13
System . out . p ri n t (" n = ");
14
int n = Reader . readlnt O;
15
16
i f ( n < 1 ) return ;
17
s
System . out . p ri n 1 1 n (" Termenul " + n + " din irul" +
19
" Fibonacci este : " + fi b o n a c c i 1 1 e r ( n 1));
20 }
21 }
Clasa Fibonacci din Listing 14.3 citete un numr ntreg n de la tastatur
i calculeaz valoarea irului lui Fibonacci la poziia n (altfel spus, termenul de
ordin n al irului) folosind metoda f ibonaccilter ( ) .
Calculul combinrilor
n cadrul calculului irului lui Fibonacci din paragraful anterior am uti
lizat un tabel unidimensional pentru a reine valorile irului. In exemplul care
urmeaz vom prezenta o problem n rezolvarea creia este necesar utilizarea
unui tablou bidimensional (utilizarea tablourilor bidimensionale este mai frec
vent n rezolvarea problemelor de programare dinamic).
Este cunoscut faptul c prin combinri de n luate cte k (notate C(n, k)) se
nelege numrul de submulimi care conin k elemente ale unei mulimi cu n
elemente. Formula matematic pentru combinri de n luate cte k este:
n'
C{n>k) = w^w.
Nu este ns avantajos ca numerele C(n, k) s fie calculate direct, folosind
formula de mai sus. Formula de recuren C(n, k) = C(n l,k) + C(n
l,k 1) este mult mai rapid, deoarece utilizeaz numai adunri i elimin
operaiile de mprire i nmulire care sunt mai costisitoare n timp. Metoda
190
1
2
3
4
5
<>
7
*
9
,o
"
tnO tnn 1-
1
2
3
4
5
6
7
9
"
12
,3
C(l,l)
C(2,l)
C(3,l)
C(4,l)
C(2,2)
C(3,2)
C(4,2)
C(3,3)
C(4,3)
C(4,4)
De exemplu, pentru n = 2, irurile nchise corect sunt ()() i (()), deci rspun
sul este 2.
Soluia (celebr) a acestei probleme este reprezentat de aa-numitele nu
mere catalane (vezi [Cormen], pag. 261). Totui, datorit scopului acestui para
graf, vom da o alt soluie a problemei, bazat pe o relaie simpl de recuren.
Este cert c orice ir de paranteze nchise corect ncepe cu o parantez des
chis "(" S considerm acum paranteza care nchide aceast prim parantez.
Ceea ce se afl ntre aceste dou paranteze este tot un ir de paranteze care se
nchid corect; la fel i pentru irul care se afl n dreapta lor. Deci, un ir S de
paranteze care se nchid corect se poate scrie ca (Sl)S2, unde ST i S2 sunt
alte iruri de paranteze care se nchid corect, posibil vide.
Lungimea maxim a lui ST este 2 n 2 (atins cnd S2 este vid), iar cea
minim este 0 (atins cnd S2 are lungimea 2 n 2). S notm cu P(n)
194
pentru n = 0
pentru n > 1
Implementarea recurenei de mai sus este simpl i este prezentat n Listing 14.7.
Listing 14.7: Soluia problemei parantezelor
1 import j ava . io . * ;
2 import io . Reader ;
3
4 public class Paranteze
5{
6 public static int p ar an t e z el t e r ( in t n)
' {
s
int[] p = new int[n + 1];
p[0] = 1;
]()
ii
for (int i = 1; i <= n; i ++)
!
13
for ( int k = 0 ; k <= i - 1 ; k++)
!
15
p[i]+=p[k]*p[i k 1];
}
}
18
19
return p [n ] ;
20 }
21
22 public static void main(String args [ ] )
* {
24
System . out . prin ( "n = ");
25
int n = Reader . re adl n t ( ) ;
26
27
if ( n < 0) return ;
28
29
Sy stern . out . p ri n 1 1 n (" Numrul de iruri cu " + 2 * n +
30
" paranteze care se inchid corect este : " +
31
p ar an t ez el t er ( n ) ) ;
195
i
^
3
4
5
*
7
8
io
16
17
18
19
20
21
22
Astfel, termenii S(i,j) sunt calculai n 0(1) i nu n 0(n), cum erau calculai
nainte. Modificarea algoritmului pentru noua formul este simpl i o lsm ca
exerciiu.
n concluzie, dei problemele prezentate n aceast seciune nu sunt pro
bleme de programare dinamc propriu-zise, deoarece nu sunt de optimizare,
ele constituie un excelent exerciiu pentru nelegerea principiilor programrii
dinamice, i mai ales pentru deprinderea abilitii de a gsi relaii de recuren
adecvate pentru exprimarea soluiei.
14.3
Fundamentare teoretic
de pe pozitia
14.4
Principiul optimalitii
Desigur c structurarea n subprobleme ar fi fost lipsit de sens dac nu near fi permis s rezolvm subproblemele corespunztoare unui nivel bazndu-ne
pe soluiile subproblemelor deja rezolvate. Am anticipat deci faptul c soluia
unei subprobleme se determin pe baza soluiei subproblemelor de dimensiune
mai mic, deja rezolvate. n aceast situaie, vom spune c soluia subproblemei
x provine din soluiile subproblemelor y\ , yi , . . . yn .
Problemele de programare dinamic sunt n general probleme de optimizare,
iar problema triunghiului nu face nici ea excepie. Din acest motiv, este necesar
ca i subproblemele n care am descompus problema original s fie tot de opti
mizare, n fine, cea mai important caracteristic a problemelor de programare
dinamic este urmtoarea:
Dac soluia subproblemei x provine din soluiile subproblemelor
Vi > 2/2 ; Vn i soluia subproblemei x este optimal, atunci i solui
ile subproblemelor yi , y2 , yn sunt optimale.
Aceast proprietate poart numele de principiul optimalitii i constituie piatra
de temelie a programrii dinamice.
n general, dup ce am realizat o structurare a problemei iniiale n subpro
bleme, trebuie verificat principiul optimalitii. Aceast verificare se face cel
mai adesea prin reducere la absurd.
Observaie: Dificultatea nu const n a verifica principiul optimalitii, ci n
a gsi o structurare a problemei n subprobleme care s verifice acest principiu.
S demonstrm acum faptul c problema triunghiului de numere respect
principiul optimalitii. O soluie a unei subprobleme const ntr-un traseu
optimal care pornete din vrful triunghiului corespunztor subproblemei i
ajunge la baza triunghiului. Notm acest traseu cu a^, cifc+i, . . . , an, unde a&
este un element de pe linia k, dk+i este un element de pe linia k + 1, iar an
este un element de pe ultima linie. Pentru a se respecta principiul optimali
tii trebuie ca i subtraseul Ofc+i , . . . ,an s fie optimal pentru sub-triunghiul
201
Vi = 1 . . .n - 1, j = 1 . . .i.
m^j)
mu = zn,
14.4.1
24
25
26
27
206
32
33
34
}
public
{
int s
int i
int j
36
37
38
39
41)
4i
42
43
44
46
47
48
49
50
i++;
}
53
54
55
56
58
59
60
61
62
63
64
65
66
67
68
69
70
}
Sy stem . out . p r i n 1 1 n ( ) ;
}
public static void main(String args [ ] )
{
System, out. prin (" Dimensiune triunghi: ");
int n = Reader . re adl n t ( ) ;
int[][] z = new int[n][n];
System . out . p ri n 1 1 n (" E lementele triunghiului:");
for (int i = 0; i < n; i ++)
{
for ( int j = 0 ; j <= i ; j ++)
{
System . out . p ri n t (" z [ " + i + "][" + j
+"]=");
z[i][j] = Reader . readlnt () ;
}
}
73
74
System, out. println(" Suma maxima = " + drumMaximTriunghi ( z ) ) ;
}
76 }
207
sau
(A^As.
Unii cititori i pot pune n mod legitim ntrebarea: innd cont de faptul c
nmulirea matricelor este asociativ, oricum am pune parantezele, rezultatul
final al calcului va fi acelai. Aadar ce sens are s ne preocupm de ordinea
de realizare a operaiilor de nmulire? Rspunsul este c numrul de operaii
elementare de nmulire este diferit, funcie de modul n care alegem s punem
parantezele. De exemplu, pentru n = 3, s presupunem c cele 3 matrice au
respectiv dimensiunile (10, 50), (50, 20) i (20, 1). Numrul de operaii necesar
pentru a nmuli dou matrice de dimensiune (m,n) i (n,p) este m n p,
dup cum reiese clar din algoritmul din Listing 14.11 (considerm ca barometru
operaia de nmulire scalar a[i][fc] *
Dac vom calcula produsul celor
3 matrice dup primul mod (Ai{A2A3)), vom face 50 x 20 x 1 = 1000 de
operaii pentru a calcula produsul ^42^3, plus nc 10 x 50 x 1 = 500 de operaii
pentru a nmuli rezultatul cu A. Aadar, n total vom realiza 1000 + 500 =
1500 de nmuliri scalare. Dac vom calcula produsul celor 3 matrice n al
doilea mod ((A1A2)A3), vom face 10 x 50 x 20 = 10000 de operaii pentru a
calcula produsul AXA2 plus nc 10 x 20 x 1 = 400 operaii pentru a calcula
produsul rezultatului cu A3. Vom face aadar un total de 10400 de operaii ceea
ce nseamn cam de 7 ori mai mult dect n varianta precedent. n concluzie,
are sens s ne punem problema de a gsi cea mai eficient metod de a realiza
aceast nmulire.
O soluie imediat a problemei ar fi s se gseasc toate modurile posibile
de parantezare a irului de matrice i s se aleag cea pentru care numrul de
208
9000
0
3750
2250
0
6250
5625
3750
0
6000
7250
2750
1250
0
13000
12500
13250
10500
8750
0
56
57
58
59
60
61
62
63
64
65
66
67
6s
69
70
71
)
public static void main ( String [] args )
{
Sy stern . out . p ri nt (" Numrul de matrice: ");
int n = Reader . re adl n t ( ) ;
if ( n < 1 ) return ;
int[] d = new int[n + 1];
System . out . p ri n 1 1 n (" S i rul dimensiunilor: ");
for (int i =0; i < n + 1; i ++)
{
System . out . p rin t (" d [ " + i + "]=");
d[i] = Reader . readl nt () ;
}
73
74
Sy stem . out . p ri n 1 1 n (" Numrul minim de operaii necesar " +
75
"inmultirii matricilor: " + s i rM at ri c e ( d ) ) ;
76 }
"I
14.6
ptri = 0 sau j = 0,
ptri = l-.m,j = L.nsixi = Xj,
ptri = l..m,j = L.nsixi ^Xj.
Calculul recurenei de mai sus se face simplu, folosind metoda subsirComunMaximal ( ) din Listing 14.15.
20
22
23
m; ++i)
< n; + + j)
== y.charAt(j))
1 [i ][j ] + 1 ;
Math . max ( 1 [ i ] [ j + 1 ] , 1 [ i + 1 ] [ j ] ) ;
*
26
27 return 1 [m] [ n ] ;
29 }
19
20
21
22
23
26
27
28
29
30
31 }
Clasa Subs ir Comun din Listing 14.17 citete dou stringuri de la tas
tatur, dup care afieaz subirul lor comun de lungime maxim.
Listing 14.17: Soluia complet a problemei subirului comun maximal
1 import j ava . io . * ;
2 import io . Reader ;
3
4 public class SubsirComun
5 {
6 private static String x;
7 private static String y;
8 private static int[][] 1;
9
10 public static int subsirComunMaximal ()
11 {
12
// . . .
13
System . out . p ri n 1 1 n (" S ub irul comun maximal: " +
14
determinaSubsir (m, n));
15
16
return 1 [m] [ n ] ;
,7 }
19
20
21
22
23
24
25
29
Sy stem . out . p ri n t (" y = ");
30
y = Reader . re ad S tri n g () ;
31
32
Sy stem . out . p ri n t In (" Lungimea subsirului comun maximal: "
33
+ subsirComunMaximal ());
34 }
35 )
14.7
Distana Levensthein
pentru i
pentru j
pentru Xi
pentruxi
=0
=0
= yj
^ yj
221
A
a
1
g
o
r
i
t
m
A
0
1
2
3
4
5
6
7
8
a
1
0
1
2
3
4
5
6
7
b
2
1
1
2
3
4
5
6
7
o
3
2
2
2
2
3
4
5
6
r
4
3
3
3
3
2
3
4
5
i
5
4
4
4
4
3
2
3
4
g
6
5
5
4
5
4
3
3
4
e
7
6
6
5
6
5
4
4
4
n
8
7
7
6
7
6
5
5
5
2 7^
atunci alegem poziia nvecinat de valoare minim. Dac valoarea
minim s-a obinut pentru (m l,n 1), atunci ultima operaie a fost de
nlocuire; dac valoarea minim s-a obinut pentru elementul de pe poziia (m
\,ri), atunci ultima operaie a fost de inserare, iar dac valoarea minim s-a
obinut pentru (m, n 1), ultima operaie a fost de tergere.
Figura 14.6 prezint matricea d pentru irurile algoritm i aborigen, iar
elementele ngroate corespund soluiei date la nceputul paragrafului. Algo
ritmul de reconstituire este asemntor cu cel de la problema subirului comun
de lungime maxim din paragraful precedent i este redat n liniile 61-98 din
Listing 14.19.
Listing 14.19: Soluia complet a problemei distanei Levensthein
1 import j ava . io . * ;
2 import io . Reader ;
3
4 public class Levensthein
5 {
6 private static String x;
7 private static String y;
8 private static int [][] d ;
9
10 public static int di s t an t aL e ven s t hein ( )
11 {
12
int m = x . length ( ) ;
13
int n = y . length ( ) ;
14
d = new int[m+ l][n + 1];
15
223
24
25
26
27
28
29
30
33
34
36
39
40
42
43
44
45
46
47
48
49
50
si
52
53
55
57
ss
59
60
61
62
63
64
65
224
return m;
}
public static void determinaSecventa(int i, int j)
j
if ( i != 0 && j != 0)
{
if ( x . charAt ( i 1 ) == y . charAt ( j 1))
{
determinaSecventa ( i 1 , j 1);
}
else
!
int m = min ( d [ i 1] [ j 1 ] ,
d[i-l][j],
d [ i ] [ j 1 ]):
71
74
if (m == d[i-l][j -1])
!
System, out. println( " inlocuire: " +
x . charAt ( i 1 ) + " cu " +
y . charAt ( j 1 ));
determinaSecventa ( i 1 , j 1);
}
else if (m == d[i-l][j ])
!
System . out . println (" tergere: " +
x . charAt ( i 1 ) +
" de pe poziia " + ( i - 1));
determinaSecventa ( i 1 , j);
}
else
{
System . out . println (" inserare : " +
y . charAt ( j 1 ) +
" pe p o z i t i a " + i ) ;
determinaSecventa ( i , j 1);
}
77
7s
79
80
82
84
85
86
87
88
89
90
91
92
93
94
96
}
}
98
99
100
101
102
103
104
105
106
107
108
109
III) }
}
public static void main(String args[])
{
System . out . prin ( "x = ");
x = Reader . readString ( ) ;
System . out . prin ( "y = ");
y = Reader . readString () ;
System . out . p ri ntl n (" Numrul minim de operaii: "
+ di s t ant aLe ven s thein ( ) ) ;
}
225
Erori frecvente
1. Se confund principiul optimalitii cu reciproca lui, creznd c prin com
binarea a dou subsoluii optime se obine tot o soluie optim.
2. Se aplic metoda programrii dinamice pentru subprobleme care nu res
pect principiul optimalitii.
3. Nu trebuie s ne avntm s rezolvm toate problemele care respect
principiul optimalitii folosind programarea dinamic. Trebuie verificat
nainte c nu putem aplica strategii mai simple, cum ar fi Greedy.
4. Suprapunerea apelurilor recursive trebuie evitat, deoarece exist posibi
litatea de a genera algoritmi exponeniali.
5. Calculul complexitii n timp a algoritmilor recursivi se face pe baza
unei formule recurente. Nu v bazai pe faptul c un apel recursiv are
timp liniar.
226
bi
230
5. Mere, pere.
Se consider n camere distincte, situate succesiv una dup cealalt astfel
nct din camera numrul i se poate trece doar n camera numrul i + 1
(i = 1,2,. ..,n 1 ) . In fiecare camer se afl un anumit numr de mere i
de pere. O persoan avnd la dispoziie un rucsac suficient de ncptor,
iniial gol, pornete din camera 1, trece prin camerele 2, 3, . . . , n i iese.
La intrarea n fiecare camer persoana trebuie s descarce rucsacul i s
ncarce fie toate merele, fie toate perele din camera respectiv, dup care
trece n urmtoarea camer. Se presupune c pentru fiecare fruct trans
portat dintr-o camer ntr-alta persoana consum cte o calorie. S se pre
cizeze ce fructe trebuie s ncarce persoana respectiv n fiecare camer
astfel nct dup parcurgerea celor n camere s consume un numr minim
de calorii i s se precizeze acest numr.
Indicaie
Strategia Greedy de a alege la fiecare pas cantitatea de fructe mai mic nu
conduce ntotdeauna la soluia optim (gsii un contraexemplu!). Prin
cipiul optimalitii se formuleaz astfel: presupunem c avem o soluie
optim pentru subproblema generat de camerele i, i + 1, . . . , n; atunci
subsoluia acesteia pornind din camera i+1 este optim pentru subproble
ma generat de camerele i + l,i + 2, . . . ,nn care se pleac cu fructele
alese n camera i + 1 n cadrul soluiei optime. Cu alte cuvinte, dac
n camera i + 1 s-au ales mere, atunci subsoluia respectiv este optim
pentru subproblema generat de camerele i+l,i+2, . . . ,nn care se pre
supune c se pornete cu mere (dac se pornete cu pere se poate obine
o soluie mai bun).
Vom nota cu vrii numrul merelor i cu pi numrul perelor din camera
i, i = 1,2, ... ,n. De asemenea, vom nota cu crrii costul optim (numrul
minim de calorii) obinut cnd se pleac cu mere din camera i i cu cpi
costul optim obinut cnd se pleac cu pere din camera i. Evident, soluia
problemei este dat de min(cpi, crai). Formula de recuren este dat de
observaia c dac plecm cu un anumit tip de fruct din camera i, putem
231
(
_ J
mn, i = n
mi + cpi+1
+ (r^ + mi+i) + cpi+2
rai + (rai + rai+i) + . . . + (rai + . . . mn-i) + cpn
mi + (mi + rai+i) + . . . + (rai + . . . ra)
Se acord 3 puncte dac dou caractere care se afl unul sub cellalt sunt
egale i se acord o penalizare de un punct dac cele dou caractere sunt
diferite. Pentru exemplul de mai sus se acord 4*3-3*1=9 puncte.
Fiind date dou iruri ADN, A i B (nefiind obligatoriu ca acestea s
aib aceeai dimensiune), se cere s se determine o aliniere pentru care
punctajul acordat este maxim. (Pentru exemplul precedent, alinierea dat
este optim).
232
_ I
Skl ~ I
k, pentrul = 0
l, pentru k = 0
Sk-ii-i +3, pentru Ak = Bt
max{sk-ii,ski-i,sk-u-i} - 1, pentruAkBx
8. Problema vrjitorului.
Indiana Jones intr ntr-un labirint unde gsete un vrjitor. Acesta i
pune n fa n lzi, fiecare lad coninnd un anumit numr precizat (m,)
233
Valoarea lui p din formula de mai sus poate fi calculat cu uurin dup
formula p = (l rrik) modn (atenie la cazul n care l rrik este negativ
- putei aduga pentru siguran un n la sum). Reconstituirea soluiei se
face reinnd pentru fiecare element M\~i o valoare p\~i care indic lada j
pentru care s-a obinut valoarea optim, sau este 0 dac M\~i = M^-u.
cu
235
15.1
Prezentare general
15.1.1
Fundamente teoretice
Metoda Branch & bound utilizeaz cteva noiuni a cror nelegere este
strict necesar pentru a putea deprinde mecanismul de funcionare a metodei.
Unei probleme de tipul Branch & bound i se asociaz un arbore oarecare
(nu neaprat arbore binar), n care fiecare nod reprezint o configuraie. Con
figuraia iniial reprezint rdcina arborelui i conine datele de intrare ale
problemei. Prin efectuarea operaiilor (mutrilor) permise asupra configuraiei
iniiale se obin alte configuraii, care vor constitui nodurile aflate pe nivelul 2 al
237
2
6
10
14
3
7
15
4
8
11
12
2
6
10
14
3
7
11
15
4
8
12
15.2
15.2.1
Enunul problemei
Figura 15.3: Mutrile permise pentru celula liber din tabela 15.1.
1
5
9
13
1
5
9
13
sus
2
6
3
10
7
14 15
JOS
2
3
7
6
10
14 15
4
8
11
12
4
8
11
12
dreapta
1
2
3
5
6
8
10
7
9
13 14 15
stnga
1
2
3
5
6
10
7
9
13 14 15
4
11
12
4
8
11
12
are patru celule vecine (de exemplu, dac se afl pe prima linie, sau pe prima
coloan) mutrile respective nu sunt posibile.
Asemntor, pe fiecare dintre cele patru configuraii obinute se pot realiza
alte mutri, generndu-se astfel un nou set de configuraii. Toate aceste confi
guraii poart numele de stri ale jocului. O stare poate fi obinut pornind de
la starea iniial creia i se aplic o secven de mutri permise. Spaiul strilor
unei configuraii iniiale reprezint mulimea tuturor strilor care pot fi obinute
pornind de la starea iniial.
Generarea configuraiilor continu pn cnd se ajunge la configuraia fi
nal. Uneori este posibil ca starea final s nu poat fi obinut pornind de la
starea iniial, caz n care problema noastr nu va avea soluie.
15.2.2
Rezolvarea problemei
final, cele dou valori sunt egale: celula 1 conine valoarea 1, celula 2 conine
valoarea 2 etc... Celulei libere i se atribuie valoarea 16.
Considerm funcia position(i) ca fiind numrul celulei care conine valoa
rea i. De exemplu, n configuraia iniial prezentat n Figura 15.1, avem:
position(3) = 3
position(7) = 11
position(ll) = 12 etc.
Pentru celula liber avem position(16) = 7, ceea ce nseamn c spaiul liber
se afl pe poziia 7 n configuraia iniial.
De asemenea, considerm funcia less(i) ca fiind numrul de celule j, cu
j < i, pentru care position(j) > position(i). Cu alte cuvinte, aceast funcie
calculeaz cte celule cu valori mai mici dect valoarea celulei curente se gsesc
dup ea n configuraia iniial. De exemplu, n Figura 15.1, pentru celula
care conine valoarea 8, exist o celul cu valoare mai mic aflat pe o poziie
mai mare, i anume, celula cu valorea 7. Aadar, less(8)=l. Analog, se pot
determina i celelalte valori: less(13)=l, less(5)=0, less(ll)=0, etc.
Ultima consideraie se refer la poziia spaiului liber n cadrul configuraiei
iniiale. Considerm o variabil x care are valoarea 1, dac spaiul liber se afl
n configuraia iniial pe una din poziiile marcate cu * n Figura 15.4, sau 0,
dac spaiul liber se afl pe una dintre poziiile nemarcate n aceeai figur.
Avnd aceste notaii, putem enuna urmtorul rezultat:
Teorema 15.2.1 Configuraia final poate fi obinut din configuraia iniial
dac i numai dac
x + ^2\6 less(i) este un numr par.
Demonstraia acestei teoreme se bazeaz pe observaii simple asupra con
figuraiilor posibile i o putei gsi pe multe situri care trateaz aceast problem
242
244
int n = 4 ;
i n t [ ] [ ] x = new i n t [ n ] [ n ] ;
obtineConfiguratialnitiala(x);
if (existaSolutie(x))
{
cautaSolutie(x);
}
52
53
55
57
58
59
60
61
62
63
64
65
66
67
68
69
70
73
77
78
e1se
{
System. out.println( "NU exista soluii !!!");
}
}
/** Citete configuraia iniiala a jocului. */
public static void obtineConfiguratiaInitiala(int[][] x)
{
for ( int i = 0; i < x.length ; i ++)
{
for (int j = 0; j < x.length ; j ++)
{
System . out . p ri n t (" x [ " + i +
"]["+ j + "]=");
x[i][j] = Reader . readlnt ( ) ;
}
}
}
/** Determina daca jocul are soluie . */
public static boolean existaSolutie(int[][] x)
{
int s = 0;
for (int i = 1 ; i <= 16; i ++)
!
s+=less(x, i);
}
s += determinaPozSpatiu (x ) ;
if ( s % 2 == 0) return true ;
else return false ;
)
/** Funcia po sition . * /
public static int position(int[][] x, int el)
{
for (int i = 0; i < x.length ; i ++)
{
for (int j = 0; j < x.length ; j ++)
j
if ( el == x[i ][j ])
!
return i * x.length + j + 1;
}
}
}
return 0;
247
}
/** Funcia le s s . * /
public static int less(int[][] x , int i)
{
intnr=0;
86
88
89
90
92
93
94
,5
96
97
98
99
oo
101
102
103
104
105
106
107
ies
109
i io
112
114
1 19
120
121
122
123
124
125
126
127
128
248
return nr ;
}
/** Determina poziia spaiului pe grila de joc. */
public static int determinaPozSpatiu(int[][] x)
{
for (int i = 0; i < x.length; i++)
{
for (int j =0; j <x.length; j ++)
{
// 4 * 4 = 1 6 = spaiu liber
if (x[i][j]==x.length * x.length)
{
if (( i % 2 == 0 && j % 2 == 1) II
(i % 2 == 1 && j % 2 == 0))
{
return 1 ;
}
else
{
return 0;
}
}
)
}
return 0 ;
}
/** Caut soluia jocului pe baza configuraiei iniiale. */
public static void cautaSolutie(int[][] x)
{
//lista nodurilor active
Vector noduri Active = new Vector ();
//nodul expandat ( implicit este configuraia iniiala )
int[][] nodExpandat = x;
//pstreaz succesiunea de mutri efectuate
Vector mutri = new Vector ();
for ( ; ; )
{
distanta++;
noduriActive = determinaNoduriActive ( nodExpandat ,
noduriActive ) ;
if ( noduriActive . size ( ) == 0) return ;
nodExpandat = determinaNodExpandat ( noduri Active ) ;
mutri . addElement ( nodExpandat ) ;
if ( gc ( nodExpandat ) == 0)
{
a fi s e a z a S o 1 u t i e ( mutri ) ;
return ;
}
int minPos = noduri Active . indexOf ( nodExpandat ) ;
noduriActive .removeElementAt(minPos);
val ori F . removeElement At ( minPos ) ;
}
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
'74
176
}
/* *
* Determina nodurile active din mulimea crora se
* va alege nodul expandat .
*/
public static Vector determinaNoduriActive (int [] [] x,
Vector noduriActive)
{
//caut spaiul gol in interiorul configuraiei
int i 1 = 0 ;
int j 1 = 0 ;
for (int i = 0; i < x.length ; i ++)
{
for (int j = 0; j < x.length ; j ++)
{
if (x[i][j] == x.length * x.length)
{
i1 = i ;
jl = j ;
}
}
}
178
249
xRight [ i 1 ] [ j 1 + 1] = aux ;
noduriActive. addElement ( xRight ) ;
valoriF . addElement (new Integer( distanta ));
}
return noduriActive ;
}
/* *
* Metoda utila de copiere a valorilor dintrun sir sursa
* intrun sir destinaie.
*/
public static void copie (int[][] src, int [] [] dest)
{
for (int i = 0; i < src . length ; i ++)
{
for (int j =0; j < src. length; j ++)
{
dest [i ][j ] = src [i ][j ];
}
}
}
/** Determina nodul expandat pe baza nodurilor active. */
public static int [ ] [ ] determinaNodExpandat (Vector noduriActive)
{
int minPos = 0 ;
int min = Integer .MAXVALUE;
for (int i = 0; i < noduriActive. size ( ) ; i ++)
{
int [ ] [ ] el = (int[][]) noduriActive. elementAt(i);
i f ( cc ( e 1 , i)< min )
{
min = cc ( el , i ) ;
minPos = i ;
}
}
return (int[][]) noduriActive. elementAt( minPos ) ;
}
/** Calculul funciei c. */
public static int cc(int[][] x , int j)
{
return gc(x) + f(j );
}
/** Calculul funciei g. */
251
Rezumat
Capitolul de fa a prezentat metoda Branch & bound de elaborare a al
goritmilor. Asemntoare metodei Backtracking, aceast metod este mai rar
utilizat dect Backtracking-ul, de aceea a fost prezentat n mai puine detalii.
S-a insistat ns asupra mecanismului ei de funcionare, care se bazeaz pe o
funcie de cost asociat nodurilor din arborele de stri. Funcia de cost trebuie
aleas n funcie de problema care trebuie rezolvat. Problema puzzle-ului cu
15 elemente este un bun exemplu pentru aprofundarea cunotinelor de Branch
and bound.
Noiuni fundamentale
arbore de stri: arbore oarecare generat de aplicarea mutrilor permise de
problem unei configuraii iniiale.
funie de cost: funcie special care difer de la problem la problem i
este asociat unui nod din arborele de stri. Cu ajutorul ei se elimin subarborii
care nu duc la rezultatul dorit, obinndu-se o reducere semnificativ a timpului
de aflare a soluiei.
nod activ: nod care a fost obinut prin expandarea unui alt nod i care nu a
fost nc, la rndul lui, expandat.
nod expandat: nodul curent, pentru care au fost generai fiii (s-au realizat
mutrile permise de problem)
nod rspuns: configuraia final la care trebuie s se ajung prin efectuarea
mutrilor permise asupra configuraiei iniiale.
Exerciii
Teorie
1. Fie S = x +
less(i) pentru o configuraie oarecare a problemei
puzzle cu 15 elemente. Demonstrai c S mod 2 este invariant pentru
253
254
16.1
Backtracking
16.2
Divide et impera
Divide et impera se aplic problemelor care se pot descompune n subprobleme, astfel nct soluia problemei originale s se poat obine uor din solui
ile subproblemelor. Subproblemele se mpart la rndul lor n subprobleme pn
255
16.3. GREEDY
cnd se ajunge la subprobleme triviale, care admit soluie imediat. Exprimarea
rezolvrii este recursiv, ca i algoritmii care utilizeaz aceast metod.
Timpul de calcul este, n general, de forma nk log n. Cele mai eficiente
metode de sortare (Mergesort, Quicksort) se bazeaz pe aceast metod.
16.3
Greedy
16.4
Programare dinamic
16.5
16.5. BRANCH&BOUND
re) ci se urmrete alegerea variantelor care sunt mai promitoare n vederea
obinerii unei soluii. Aceasta nseamn c fiecare posibil variant este evaluat
dup anumite criterii specifice fiecrei probleme n parte, alegndu-se la fiecare
pas varianta evaluat a fi optim.
Tabel sintez:
Tabelul urmtor prezint o sintez a informaiilor din acest paragraf.
Metoda
Condiii de aplicare
Timp de lu
Construire
cru (n gene
soluie
ral)
Backtracking
ir cu elemente lund exponenial
incremental
valori n mulimi finite
nk * logn
Divide et Im- Soluia se poate con
top-down
pera
strui pe baza soluiilor
subproblemelor
Greedy
Probleme de opti
polinomial
incremental
mizare care respect
principiul optimalitii
i al alegerii Greedy
Programare
Probleme de opti
polinomial
bottom-up
dinamic
mizare care respect
principiul optimalitii
Branch
& Probleme de opti
difer funcie parcurgere
bound
mizare dificile n de problem
least-cost
care aplicarea celor
lalte metode nu este
adecvat.
257
Bibliograe
[Andonie]
[Balanescu]
[Boian]
[Cormen]
[Danciu]
[Eckel]
[Horrowitz]
[Norton]
P. Norton, W. Stanek - Peter Nortons Guide to Java Programming, Sams.net Publishing, 1996, ISBN 1-57521-088-6
[Roman]
[Sorin]
[Tomescu]
BIBLIOGRAFIE
260
Index
n(/),2i
e(/),2i
acces secvenial, 56
ActiveX, 7
alegere optim, 167
algoritm, 15
algoritm exponenial, 36
algoritm liniar, 36
algoritm polinomial, 36
algoritm recursiv, 25
apel recursiv, 139, 142
arbore, 104, 237
arbore binar, 104
arbore binar de cutare, 69
arbore de stri, 253
autoapelare, 138
dequeue, 5 1
DFS, 238
distan, 220
divide et impera, 12, 255
E-node, 238
enqueue, 51
enterprise, 7
expandare, 238
expresie aritmetic, 157
fezabil, 174
FIFO, 238
forma polonez, 157
formule de recuren, 210
frunz, 104,237,238
funcie de selecie, 174, 183
funcie obiectiv, 174
funcie de cost, 239, 253
getFront, 5 1
greedy, 12, 256
261
INDEX
GUI, 7
hash, 104
hashtable, 83
inteligen artificial, 238
interclasare, 180
internet, 7
iterator, 57, 104
Java 2 Platform, 7
LC (Least Cost), 239
LIFO, 238
list nlnuit, 55
lungime extern ponderat, 181
pager, 7
partiionare, 152
PDA, 7
pivot, 152
principiul alegerii greedy, 168
principiul invarianei, 19
principiul optimalitii, 168, 183, 186,
226
probare liniar, 9 1
probare ptratic, 9 1
problem de optimizare, 183, 226
programare dinamic, 12, 226, 256
proprietatea de alegere greedy, 1 73,
183
quicksort, 152, 162
rdcin, 237
Reader, 120
recuren, 138, 21 1
recuri vitate, 137
relaie de recuren, 221
relaii Moivre, 28
Shockwave, 7
soluie optim, 226
soluie optimal, 237
sortare, 137
spaiul soluiilor posibile, 109
spaiul strilor, 236
stiv, 45
stiva programului, 142
strategie euristic, 173
strategie greedy, 1 67
structur de date, 40
subproblem, 137, 187,226
substructur optim, 168, 173, 176,
183, 186, 216
succesor, 238
Sun Microsystems, 7
INDEX
263