Sunteți pe pagina 1din 346

ALGORITMI

utili la olimpiadele de informatic 

clasele 9, 10, 11, 12

... draft (ciorn ) ...


*** Nobody is perfect ***

Ph.D. Adrian R bâea

9 decembrie 2020
Dedicaµie

I would like to dedicate this book ...

1
to myself ...
in a time when ...
I will not be able ...
2
to be.

That is because ...

3
When I Die Nobody Will Remember Me

1
I Dedicate This Book to Myself  By Carol Lynne
2
To be, or not to be ..., Hamlet, Act III, Scene I, William Shakespeare, 1564-1616
3
https://www.youtube.com/watch?v=eMtcDkSh7fU

ii
Prefaµ 
Stilul acestor c rµi este ... ca ³i cum a³ vorbi cu nepoµii mei (³i chiar cu mine însumi!) ... încercând
împreun  s  g sim o rezolvare cât mai bun  pentru o problem  dat  la olimpiad .
Ideea, de a scrie aceste culegeri de probleme date la olimpiadele de informatic , a ap rut acum
câµiva ani când am întrebat un student (care nu reu³ea s  rezolve ni³te probleme foarte simple):
Ce te faci dac  un elev, care ³tie c  e³ti student ³i c  studiezi ³i informatic , te roag , din când în
când, s -l ajuµi s  rezolve câte o problem  de informatic  dat  la gimnaziu la olimpiad , sau pur
³i simplu ca tem  de cas , ³i tu, aproape de ecare dat , nu îl poµi ajuta? Eu cred c  nu este prea
bine ³i poate c  ... te faci ... de râs! Pe vremea mea (!), când eram elev de gimnaziu, un student
era ca un fel de ... zeu! Cu trecerea anilor am înµeles c  nu este chiar a³a! “i înc  ceva: nu am
reu³it s  înµeleg de ce, atunci când cineva nu poate s  rezolve corect o problem  de informatic 
de clasa a 6-a, dat  la olimpiada de informatic  sau ca tem  de cas , folose³te aceast  scuz : eu
nu am f cut informatic  în liceu! ³i acest cineva este zeul sau zeiµa despre care vorbeam!.
4
Sunt convins c  este important s  studiem cu atenµie cât mai multe probleme rezolvate! Cred
cred c  sunt utile ³i primele versiuni în care sunt prezentate chiar ³i numai enunµurile ³i indicaµiile
"ociale" de rezolvare. Acestea se g sesc în multe locuri; aici încerc s  le pun pe toate la un loc !
Limbajul de programare se alege în funcµie de problema pe care o avem de rezolvat. Cu ni³te
ani în urm  alegerea era mai simpl : dac  era o problem  de calcul se alegea Fortran iar dac  era
o problem  de prelucrarea masiv  a datelor atunci se alegea Cobol. Acum alegerea este ceva mai
5 6
dicil ! :-) Vezi, de exemplu, IOI2020 ³i IOI2019 , IOI2015 .
Cred c , de cele mai multe ori, este foarte greu s  gândim "simplu" ³i s  nu "ne complic m"
atunci când caut m o rezolvare pentru o problem  dat  la olimpiad . Acesta este motivul pentru
care vom analiza cu foarte mare atenµie atât exemplele date în enunµurile problemelor cât ³i
"restricµiile" care apar acolo (ele sigur "ascund" ceva interesant din punct de vedere al algoritmului
7
de rezolvare!) .
Am început câteva c rµi (pentru clasele de liceu) cu mai mulµi ani în urm , pentru perioada
2000-2007 ([29] - [33]), cu anii în ordine cresc toare!). A urmat o pauz  de câµiva ani (destul de
mulµi!). Am observat c  acele cursuri s-au împr ³tiat un pic pe net ([48] - [56])! Încerc acum
s  ajung acolo unde am r mas ... plecând mereu din prezent ... pân  când nu va mai  posibil ...
a³a c , de aceast  dat , anii sunt în ordine ... descresc toare! :-)
Codurile surs  sunt cele ociale (publicate pe site-urile olimpiadelor) sau publicate pe alte
site-uri (dac  mi s-a p rut c  sunt utile ³i se poate înv µa câte ceva din ele).
Pentru liceu perioada acoperit  este de azi (pân  când va exista acest azi pentru mine!)
pân  în anul 2000 (aveam deja perioada 2000-2007!).
Pentru gimnaziu perioada acoperit  este de azi pân  în anul 2010 (nu am prea mult timp
disponibil ³i, oricum, calculatoarele folosite la olimpiade înainte de 2010 erau ceva mai 'slabe' ³i
8
... restricµiile de memorie, din enunµurile problemelor, par 'ciudate' acum!). “i indc  a venit
vorba despre calculatoare mai  slabe sau mai  puternice: laptopul meu¯t u este puµin mai  slab
decât cel mai puternic calculator din lume în 1985 dar ³i ... un pic mai  puternic decât cel mai
9
puternic calculator din lume în 1983. (armaµia este valabil  acum, în 2020).
4
Se poate observa din Coduri surs  c  orice problem  are numeroase soluµii, atât ca algoritmi de rezolvare
cât ³i ca stil de programare! Studiind aceste coduri ... avem ce înv µa ... de³i uneori pare c  'se trage cu tunul' ...
5
IOI2019 ³i IOI2020 au a permis utilizarea limbajelor de programare C++ ³i Java
6
IOI2015 a permis utilizarea limbajelor de programare C++, Java, Pascal, Python ³i Rubi (...)
7
8
Vezi cele 5 secunde pentru Timp maxim de executare/test din problema avârcolaci - ONI2014 clasa a 11-a
Când eram eu elev/student un calculator obi³nuit executa în jur de 1.000.000 de operaµii pe secund , acum
execut  1.000.000.000 de operaµii pe secund , iar mai târziu ... cine stie ce va mai ?!
9
https://en.wikipedia.org/wiki/List_of_fastest_computers

iii
În perioada 2017-2020 cele mai puternice calculatoare din lume au fost: în noiembrie 2017 în
China, în noiembrie 2019 în SUA ³i ... în iunie 2020 în Japonia (Fugaku: 415 petaops, adic 
15 10
415 ˜ 10 operaµii pe secund , adic  ... 415 milioane de ... miliarde de ... operaµii pe secund ).
11
O mic  observaµie: în 2017 a fost prima ediµie a olimpiadei EJOI în Bulgaria ³i ... tot în
12
Bulgaria a fost ³i prima ediµie a olimpiadei IOI în 1989.
Dar ... prima ediµie a olimpiadei IMO (International Mathematical Olympiad) a fost în
13
România în 1959. Tot în România s-au µinut ediµiile din anii 1960, 1969, 1978, 1999 ³i 2018.
Revenind la ...  culegerile noastre ... mai departe, probabil, va urma completarea unor
informaµii în Rezolv ri detaliate ... pentru unele probleme numai (tot din cauza lipsei timpului
necesar pentru toate!). Prioritate vor avea problemele de gimnaziu (nu pentru c  sunt mai 'u³oare'
ci pentru c  ... elevii de liceu se descurc  ³i singuri!). Totu³i, vor  prezentate ³i Rezolv ri
detaliate ale problemelor de liceu (pe care le-am considerat în mod subiectiv!) utile.

Îmi aduc aminte c  exista o interesant  vorb  de duh printre programatorii din generaµia mea:
nu se trage cu tunul într-o musc  . Sensul este: nu se scrie un cod complicat dac  se poate
scrie un cod simplu ³i clar! Asta încerc eu în Rezolv ri detaliate .
Vom încerca, împreun , ³i câteva probleme de ... IOI ... dar asta este o treab  ... nu prea
u³oar ! Cred totu³i c  este mai bine s  prezint numai enunµuri ale problemelor date la IOI în
ultimii câµiva ani! (asta a³a, ca s  vedem cum sunt problemele la acest nivel!). Cei care ajung
acolo sau vor s  ajung  acolo (la IOI) nu au nevoie de ajutorul meu! Se descurc  singuri! La
Indicaµii de rezolvare voi prezenta numai ... numele algoritmilor clasici folosiµi în rezolvare.

ALGORITMI utili la olimpiadele de informatic  , separat pentru gimnaziu ³i liceu, sper s 


e de folos, a³a cum cred c  sunt [1] - [28], [34] - [47], [57] - [82], ... ³i multe alte c rµi ³i site-uri!.
O alt  mic  observaµie: ce am strâns ³i am scris în aceste c rµi se adreseaz  celor interesaµi de
aceste teme! Nu cârcota³ilor! Sunt evidente sursele de pe net (³i locurile în care au fost folosite).
Nu sunt necesare preciz ri suplimentare!
“i un ultim gând: criticile ³i sfaturile sunt utile dac  au valoare! Dac  sunt numai a³a ...
cum critic  lumea la un meci de fotbal ... sau cum, pe banc  în parc, î³i d  cu p rerea despre
rezolvarea problemelor economice ale µ rii ... atunci ... !!!

"I'm only responsible for what I say,


14
not for what you understand."

Adrese interesante (rezultatele elevilor români):


https://stats.ioinformatics.org/halloffame/
https://stats.ioinformatics.org/tasks/
http://stats.ioinformatics.org/results/ROU

Adresele acestor cursuri:


http://adrianrabaea.scienceontheweb.net/
https://www.scribd.com/user/243528817/Adrian-Rabaea
https://drive.google.com/drive/folders/1hC5PZuslCdS95sl37SW46H-qy59GRDGZ

Adrese utile (programe ³colare):


http://www.ise.ro/wp-content/uploads/2017/01/Informatica-si-TIC.pdf
http://programe.ise.ro/Portals/1/Curriculum/Progr_Lic/TH/Informatica_teoretic_vocatio
nal_intensiv_clasa%20a%20IX-a.pdf
http://programe.ise.ro/Portals/1/Curriculum/Progr_Lic/TH/Informatica_teoretic_vocatio
nal_intensiv_clasa%20a%20X_a.pdf
http://programe.ise.ro/Portals/1/Curriculum/Progr_Lic/TH/Informatica_teoretic_vocatio
nal_intensiv_clasa%20a%20XI-a.pdf

Bistriµa, Ph.D. Adrian R bâea


9 decembrie 2020

10
https://www.top500.org/lists/top500/
11
https://ejoi.org/about/
12
https://stats.ioinformatics.org/olympiads/
13
https://en.wikipedia.org/wiki/International_Mathematical_Olympiad
14
https://www.etsy.com/listing/604809336/john-wayne-quotes-i-am-only-responsible
"Acknowledgements"
15
"I want to thank God most of all because without God I wouldn't be able to do any of this."

Bistriµa, 9 decembrie 2020

Adrian R.

15
I.d.k.: "I don't know who the author is."

v
Despre autor16
nume: R bâea Aurel-Adrian, 18.03.1953 - ...
adresa: Str. Valea Ghinzii nr. 21 B, Bistriµa, România
telefon: +40 728 18 03 53 +40 363 10 25 10
email: adrian1803@gmail.com
Lector universitar - Universitatea Tehnic  din Cluj Napoca - Centrul
Universitar Nord din Baia Mare, Facultatea de “tiinµe, Str. Victoriei,
nr. 76, Baia Mare, România, (pensionat: 01.10.2018)
http://www.stiinte.utcluj.ro/
Discipline predate (1992-2018):
Algoritmi ³i structuri de date, Algoritmi în teoria opµiunilor nanciare, Bazele matematice
ale calculatoarelor, Bazele tehnologiei informaµiei, Birotic , Capitole speciale de inteligenµ 
articial , Capitole speciale de teoria algoritmilor, Calcul paralel, Informatic  economic ,
Instruire asistat  de calculator, Limbaje de programare; Programare orientat  pe obiecte,
Programare procedural , Structuri de date,

Studii doctorale în informatic  economic  - Diplom  de doctor (1997-2002):


Institutµia: Academia de Studii Economice, Bucure³ti;
17
Titlul tezei: Algoritmi paraleli ³i aplicaµii pe ma³ini virtual paralele
18
Conduc tor ³tiinµic: Prof. dr. ing. Gheorghe Dodescu
Teme studiate: utilizarea algoritmilor paraleli în teoria opµiunilor nanciare

Studii de specializare în informatic  - Certicat anul V - 'master' (1978-1979):


Instituµia: Facultatea de matematic  ³i informatic , Bucure³ti;
Titlul tezei: Probleme la limit  pentru procese cu cre³teri independente ³i aplicaµii în teoria
a³tept rii
19
Conduc tor ³tiinµic: Prof. dr. Constantin Tudor

Studii universitare de licenµ  în informatic  - Diplom  de licenµ  (1974-1978):


Instituµia: Facultatea de matematic  ³i informatic , Bucure³ti;
Titlul tezei: Metode de comparaµie multipl  în analiza dispersional 
20
Conduc tor ³tiinµic: Prof. dr. Ion V duva

Locuri de munc : (1979-2018):


- (2018-2009) Universitatea Tehnic  din Cluj-Napoca, Centrul Universitar Nord din Baia-
Mare, Facultatea de “tiinµe, Departamentul de Matematic -Informatic 
- (2009-1992) Universitatea "Ovidius" din Constanµa, Facultatea de Matematic  ³i Informa-
tic , Departamentul de Informatic 
- (1992-1979) Centrul de Informatic  ³i organizare CINOR, Bucure³ti
https://scholar.google.com/citations?user=-sSE_1wAAAAJ&hl=en
https://www.scopus.com/authid/detail.uri?origin=resultslist&authorId=56122389200&zone=
http://www.facebook.com/adrian.rabaea

16
https://stiinte.utcluj.ro/files/cv/CV%20Rabaea_Adrian.pdf
17
http://opac.biblioteca.ase.ro/opac/bibliographic_view/149021
18
http://www.ionivan.ro/2015-PERSONALITATI/Dodescu.htm
19
https://sites.google.com/site/ciprianatudor/Home/professor-constantin-tudor
20
https://ro.wikipedia.org/wiki/Ion_V%C4%83duva

vi
Cuprins

Prefaµ  iii
Cuprins vii
Lista gurilor xiii
Lista tabelelor xiv
Lista programelor xv
1 Structuri de date 1
1.1 Date ³i structuri de date . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 1
1.1.1 Date . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 1
1.1.2 Structuri de date . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 2
1.2 Structuri ³i tipuri de date abstracte . . . . . . . . . . . . . . . . . . . . . . . . . . . 3
1.2.1 Structuri de date abstracte . . . . . . . . . . . . . . . . . . . . . . . . . . . 3
1.2.2 Tipuri de date abstracte . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 3
1.3 Structuri de date elementare . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 3
1.3.1 Liste . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 3
1.3.2 Stive ³i cozi . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 4
1.3.3 Grafuri . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 5
1.3.4 Arbori binari . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 5
1.3.5 Heap-uri . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 6
1.3.6 Structuri de mulµimi disjuncte . . . . . . . . . . . . . . . . . . . . . . . . . 7

2 Algoritmi 8
2.1 Etape în rezolvarea problemelor . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 8
2.2 Algoritmi . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 8
2.2.1 Ce este un algoritm? . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 8
2.2.2 Propriet µile algoritmilor . . . . . . . . . . . . . . . . . . . . . . . . . . . . 10
2.2.3 Tipuri de prelucr ri . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 10
2.3 Descrierea algoritmilor . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 10
2.3.1 Limbaj natural . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 11
2.3.2 Scheme logice . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 11
2.3.3 Pseudocod . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 11
2.4 Limbaj algoritmic . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 12
2.4.1 Declararea datelor . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 12
2.4.2 Operaµii de intrare/ie³ire . . . . . . . . . . . . . . . . . . . . . . . . . . . . 12
2.4.3 Prelucr ri liniare . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 12
2.4.4 Prelucr ri alternative . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 13
2.4.5 Prelucr ri repetitive . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 13
2.4.6 Subalgoritm . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 14
2.4.7 Probleme rezolvate . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 14
2.4.8 Probleme propuse . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 17
2.5 Instrucµiuni corespondente limbajului algoritmic . . . . . . . . . . . . . . . . . . . 18
2.5.1 Declararea datelor . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 18
2.5.2 Operaµii de intrare/ie³ire . . . . . . . . . . . . . . . . . . . . . . . . . . . . 19
2.5.3 Prelucr ri liniare . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 20
2.5.4 Prelucr ri alternative . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 20
2.5.5 Prelucr ri repetitive . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 20

vii
2.5.6 Subprograme . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 21
2.5.7 Probleme rezolvate . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 21
2.5.8 Probleme propuse . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 32

3 Analiza complexit µii algoritmilor 35


3.1 Scopul analizei complexit µii . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 35
3.1.1 Complexitatea spaµiu . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 36
3.1.2 Complexitatea timp . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 36
3.2 Notaµia asimptotic  . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 36
3.2.1 Denire ³i propriet µi . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 37
3.2.2 Clase de complexitate . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 38
3.2.3 Cazul mediu ³i cazul cel mai defavorabil . . . . . . . . . . . . . . . . . . . . 39
3.2.4 Analiza asimptotic  a structurilor fundamentale . . . . . . . . . . . . . . . 39
3.3 Exemple . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 39
3.3.1 Calcularea maximului . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 39
3.3.2 Sortarea prin selecµia maximului . . . . . . . . . . . . . . . . . . . . . . . . 40
3.3.3 Sortarea prin inserµie . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 40
3.3.4 Sortarea rapid  (quicksort) . . . . . . . . . . . . . . . . . . . . . . . . . . . 41
3.3.5 Problema celebrit µii . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 42
3.4 Probleme . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 43
3.4.1 Probleme rezolvate . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 43
3.4.2 Probleme propuse . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 44

4 Recursivitate 46
4.1 Funcµii recursive . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 46
4.1.1 Funcµii numerice . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 46
4.1.2 Funcµia lui Ackerman . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 48
4.1.3 Recursii imbricate . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 48
4.2 Proceduri recursive . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 49

5 Analiza algoritmilor recursivi 50


5.1 Relaµii de recurenµ  . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 50
5.1.1 Ecuaµia caracteristic  . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 50
5.1.2 Soluµia general  . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 50
5.2 Ecuaµii recurente neomogene . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 52
5.2.1 O form  simpl  . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 52
5.2.2 O form  mai general  . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 53
5.2.3 Teorema master . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 53
5.2.4 Transformarea recurenµelor . . . . . . . . . . . . . . . . . . . . . . . . . . . 54
5.3 Probleme rezolvate . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 57

6 Algoritmi elementari 61
6.1 Operaµii cu numere . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 61
6.1.1 Minim ³i maxim . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 61
6.1.2 Divizori . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 62
6.1.3 Numere prime . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 62
6.2 Algoritmul lui Euclid . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 62
6.2.1 Algoritmul clasic . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 62
6.2.2 Algoritmul lui Euclid extins . . . . . . . . . . . . . . . . . . . . . . . . . . . 63
6.3 Operaµii cu polinoame . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 63
6.3.1 Adunarea a dou  polinoame . . . . . . . . . . . . . . . . . . . . . . . . . . . 64
6.3.2 Înmulµirea a dou  polinoame . . . . . . . . . . . . . . . . . . . . . . . . . . 64
6.3.3 Calculul valorii unui polinom . . . . . . . . . . . . . . . . . . . . . . . . . . 64
6.3.4 Calculul derivatelor unui polinom . . . . . . . . . . . . . . . . . . . . . . . . 65
6.4 Operaµii cu mulµimi . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 65
6.4.1 Apartenenµa la mulµime . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 66
6.4.2 Diferenµa a dou  mulµimi . . . . . . . . . . . . . . . . . . . . . . . . . . . . 66
6.4.3 Reuniunea ³i intersecµia a dou  mulµimi . . . . . . . . . . . . . . . . . . . . 66
6.4.4 Produsul cartezian a dou  mulµimi . . . . . . . . . . . . . . . . . . . . . . . 67
6.4.5 Generarea submulµimilor unei mulµimi . . . . . . . . . . . . . . . . . . . . . 67
6.5 Operaµii cu numere întregi mari . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 68
6.5.1 Adunarea ³i sc derea . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 68
6.5.2 Inmulµirea ³i împ rtirea . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 69
6.5.3 Puterea . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 70
6.6 Operaµii cu matrice . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 71
6.6.1 Înmulµirea . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 71
6.6.2 Inversa unei matrice . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 71

7 Algoritmi combinatoriali 73
7.1 Principiul includerii ³i al excluderii ³i aplicaµii . . . . . . . . . . . . . . . . . . . . . 73
7.1.1 Principiul includerii ³i al excluderii . . . . . . . . . . . . . . . . . . . . . . . 73
7.1.2 Num rul funcµiilor surjective . . . . . . . . . . . . . . . . . . . . . . . . . . 73
7.1.3 Num rul permut rilor f r  puncte xe . . . . . . . . . . . . . . . . . . . . . 75
7.2 Principiul cutiei lui Dirichlet ³i aplicaµii . . . . . . . . . . . . . . . . . . . . . . . . 75
7.2.1 Problema subsecvenµei . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 76
7.2.2 Problema sub³irurilor strict monotone . . . . . . . . . . . . . . . . . . . . . 76
7.3 Numere remarcabile . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 77
7.3.1 Numerele lui Fibonacci . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 77
7.3.2 Numerele lui Catalan . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 78
7.4 Descompunerea în factori primi . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 80
7.4.1 Funcµia lui Euler . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 80
7.4.2 Num rul divizorilor . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 81
7.4.3 Suma divizorilor . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 81
7.5 Partiµia numerelor . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 82
7.5.1 Partiµia lui n în exact k termeni . . . . . . . . . . . . . . . . . . . . . . . . 82
7.5.2 Partiµia lui n în cel mult k termeni . . . . . . . . . . . . . . . . . . . . . . . 83
7.5.3 Partiµii multiplicative . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 83
7.6 Partiµia mulµimilor . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 83
7.7 Probleme rezolvate . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 84

8 Algoritmi de c utare 85
8.1 Problema c ut rii . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 85
8.2 C utarea secvenµial  . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 85
8.3 C utare binar  . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 86
8.4 Inserare în tabel  . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 87
8.5 Dispersia . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 88

9 Algoritmi elementari de sortare 89


9.1 Introducere . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 89
9.2 Sortare prin selecµie . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 89
9.3 Sortare prin inserµie . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 93
9.3.1 Inserµie direct  . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 93
9.3.2 Inserµie binar  . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 94
9.4 Sortare prin interschimbare . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 95
9.5 Sortare prin mic³orarea incrementului - shell . . . . . . . . . . . . . . . . . . . . . 96

10 Liste 97
10.1 Liste liniare . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 97
10.2 Cozi . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 101
10.3 Stive . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 104
10.4 Evaluarea expresiilor aritmetice prexate . . . . . . . . . . . . . . . . . . . . . . . 105
10.5 Operaµii asupra listelor . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 106

11 Algoritmi divide et impera 110


11.1 Tehnica divide et impera . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 110
11.2 Ordinul de complexitate . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 110
11.3 Exemple . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 111
11.3.1 Sortare prin partitionare - quicksort . . . . . . . . . . . . . . . . . . . . . . 111
11.3.2 Sortare prin interclasare - MergeSort . . . . . . . . . . . . . . . . . . . . . . 112
11.3.3 Placa cu g uri . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 113
11.3.4 Turnurile din Hanoi . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 114
11.3.5 Înjum t µire repetat  . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 117
12 Algoritmi BFS-Lee 120
12.1 Prezentare general  . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 120
12.2 Probleme rezolvate . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 122
12.2.1 Romeo ³i Julieta - OJI2004 clasa a X-a . . . . . . . . . . . . . . . . . . . . 122
12.2.2 Sudest - OJI2006 clasa a X-a . . . . . . . . . . . . . . . . . . . . . . . . . . 125
12.2.3 Muzeu - ONI2003 clasa a X-a . . . . . . . . . . . . . . . . . . . . . . . . . . 130
12.2.4 P ianjen ONI2005 clasa a X-a . . . . . . . . . . . . . . . . . . . . . . . . . . 134
12.2.5 Algoritmul Edmonds-Karp . . . . . . . . . . . . . . . . . . . . . . . . . . . 139
12.2.6 Cuplaj maxim . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 141

13 Metoda optimului local - greedy 146


13.1 Metoda greedy . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 146
13.2 Algoritmi greedy . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 146
13.3 Exemple . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 147
13.3.1 Problema continu  a rucsacului . . . . . . . . . . . . . . . . . . . . . . . . . 147
13.3.2 Problema plas rii textelor pe o band  . . . . . . . . . . . . . . . . . . . . . 148
13.3.3 Problema plas rii textelor pe m benzi . . . . . . . . . . . . . . . . . . . . . 148
13.3.4 Maximizarea unei sume de produse . . . . . . . . . . . . . . . . . . . . . . . 149
13.3.5 Problema staµiilor . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 149
13.3.6 Problema cutiilor . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 149
13.3.7 Problema sub³irurilor . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 150
13.3.8 Problema intervalelor disjuncte . . . . . . . . . . . . . . . . . . . . . . . . . 150
13.3.9 Problema alegerii taxelor . . . . . . . . . . . . . . . . . . . . . . . . . . . . 150
13.3.10 Problema acoperirii intervalelor . . . . . . . . . . . . . . . . . . . . . . . . . 151
13.3.11 Algoritmul lui Prim . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 151
13.3.12 Algoritmul lui Kruskal . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 156
13.3.13 Algoritmul lui Dijkstra . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 158
13.3.14 Urgenµa - OJI2002 cls 11 . . . . . . . . . . . . . . . . . . . . . . . . . . . . 166
13.3.15 Reactivi - OJI2004 cls 9 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 169
13.3.16 Pal - ONI2005 cls 9 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 172
13.3.17 “anµ - ONI2006 cls 9 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 176
13.3.18 Cezar - OJI2007 cls 11 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 179

14 Metoda backtracking 185


14.1 Generarea produsului cartezian . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 185
14.1.1 Generarea iterativ  a produsului cartezian . . . . . . . . . . . . . . . . . . . 185
14.1.2 Generarea recursiv  a produsului cartezian . . . . . . . . . . . . . . . . . . 189
14.2 Metoda bactracking . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 191
14.2.1 Bactracking iterativ . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 192
14.2.2 Backtracking recursiv . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 192
14.3 Probleme rezolvate . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 193
14.3.1 Generarea aranjamentelor . . . . . . . . . . . . . . . . . . . . . . . . . . . . 193
14.3.2 Generarea combin rilor . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 196
14.3.3 Problema reginelor pe tabla de ³ah . . . . . . . . . . . . . . . . . . . . . . . 203
14.3.4 Turneul calului pe tabla de ³ah . . . . . . . . . . . . . . . . . . . . . . . . . 204
14.3.5 Problema color rii h rµilor . . . . . . . . . . . . . . . . . . . . . . . . . . . 206
14.3.6 Problema vecinilor . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 208
14.3.7 Problema labirintului . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 210
14.3.8 Generarea partiµiilor unui num r natural . . . . . . . . . . . . . . . . . . . 211
14.3.9 Problema parantezelor . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 214
14.3.10 Algoritmul DFS de parcurgere a grafurilor . . . . . . . . . . . . . . . . . . . 215
14.3.11 Determinarea componentelor conexe . . . . . . . . . . . . . . . . . . . . . . 217
14.3.12 Determinarea componentelor tare conexe . . . . . . . . . . . . . . . . . . . 218
14.3.13 Sortare topologic  . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 219
14.3.14 Determinarea nodurilor de separare . . . . . . . . . . . . . . . . . . . . . . 222
14.3.15 Determinarea muchiilor de rupere . . . . . . . . . . . . . . . . . . . . . . . 223
14.3.16 Determinarea componentelor biconexe . . . . . . . . . . . . . . . . . . . . . 224
14.3.17 Triangulaµii - OJI2002 clasa a X-a . . . . . . . . . . . . . . . . . . . . . . . 226
14.3.18 Partiµie - ONI2003 clasa a X-a . . . . . . . . . . . . . . . . . . . . . . . . . 229
14.3.19 Scuµa - ONI2003 clasa a X-a . . . . . . . . . . . . . . . . . . . . . . . . . . 234
15 Programare dinamic  238
15.1 Prezentare general  . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 238
15.2 Probleme rezolvate . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 239
15.2.1 Inmulµirea optimal  a matricelor . . . . . . . . . . . . . . . . . . . . . . . . 239
15.2.2 Sub³ir cresc tor maximal . . . . . . . . . . . . . . . . . . . . . . . . . . . . 241
15.2.3 Sum  maxim  în triunghi de numere . . . . . . . . . . . . . . . . . . . . . . 244
15.2.4 Sub³ir comun maximal . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 245
15.2.5 Distanµa minim  de editare (Levenshtein) . . . . . . . . . . . . . . . . . . . 250
15.2.6 Problema rucsacului 0  1 . . . . . . . . . . . . . . . . . . . . . . . . . . . 261
15.2.7 Problema schimbului monetar . . . . . . . . . . . . . . . . . . . . . . . . . . 263
15.2.8 Problema travers rii matricei . . . . . . . . . . . . . . . . . . . . . . . . . . 263
15.2.9 Problema segment rii vergelei . . . . . . . . . . . . . . . . . . . . . . . . . . 265
15.2.10 Triangularizarea poligoanelor convexe . . . . . . . . . . . . . . . . . . . . . 267
15.2.11 Algoritmul Roy-Floyd-Warshall . . . . . . . . . . . . . . . . . . . . . . . . . 267

16 Potrivirea ³irurilor 269


16.1 Un algoritm inecient . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 269
16.2 Un algoritm ecient - KMP . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 270
16.3 Probleme rezolvate . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 274
16.3.1 Circular - Campion 2003-2004 Runda 6 . . . . . . . . . . . . . . . . . . . . 274
16.3.2 Cifru - ONI2006 baraj . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 276

17 Geometrie computaµional  280


17.1 Determinarea orient rii . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 280
17.2 Testarea convexit µii poligoanelor . . . . . . . . . . . . . . . . . . . . . . . . . . . . 280
17.3 Aria poligoanelor convexe . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 281
17.4 Poziµia unui punct faµ  de un poligon convex . . . . . . . . . . . . . . . . . . . . . 281
17.5 Poziµia unui punct faµ  de un poligon concav . . . . . . . . . . . . . . . . . . . . . 282
17.6 Înf ³ur toarea convex  . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 282
17.6.1 Împachetarea Jarvis . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 282
17.6.2 Scanarea Craham . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 285
17.7 Dreptunghi minim de acoperire a punctelor . . . . . . . . . . . . . . . . . . . . . . 292
17.8 Cerc minim de acoperire a punctelor . . . . . . . . . . . . . . . . . . . . . . . . . . 292
17.9 Probleme rezolvate . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 293
17.9.1 Seceta - ONI2005 clasa a IX-a . . . . . . . . . . . . . . . . . . . . . . . . . 293
17.9.2 Antena - ONI2005 clasa a X-a . . . . . . . . . . . . . . . . . . . . . . . . . 302
17.9.3 Mo³ia lui P cal  - OJI2004 clasa a XI-a . . . . . . . . . . . . . . . . . . . . 306
17.9.4 Partiµie - ONI2006 baraj . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 307
17.9.5 Triunghi - ONI2007 cls 9 . . . . . . . . . . . . . . . . . . . . . . . . . . . . 310

18 Teoria jocurilor 314


18.1 Jocul NIM . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 314
18.1.1 Prezentare general  . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 314
18.1.2 Exemple . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 314

19 Alµi algoritmi 315


19.1 Secvenµ  de sum  maxim  . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 315
19.1.1 Prezentare general  . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 315
19.1.2 Exemple . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 315
19.2 Cuvinte ... a, b, c, d . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 315
19.3 Algoritmul Belmann-Ford . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 316
19.3.1 Algoritmul Belmann-Ford pentru grafuri neorientate . . . . . . . . . . . . . 316
19.3.2 Alg Belmann-Ford pentru grafuri orientate . . . . . . . . . . . . . . . . . . 318
19.3.3 Alg Belmann-Ford pentru grafuri orientate aciclice . . . . . . . . . . . . . . 320

Anexa A Un pic de matematic ! 324


A.1 ... . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 324
A.1.1 Prezentare general  . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 324
A.1.2 Exemple . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 324
Anexa B Un pic de programare! 325
B.1 ... . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 325
B.1.1 Prezentare general  . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 325
B.1.2 Exemple . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 325

Glosar 326
Bibliograe 327
Lista autorilor 330
Lista gurilor
1.1 List  liniar  . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 3
1.2 Arbore binar plin . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 6
1.3 Arbore binar complet . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 6
1.4 Max-heap . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 7

2.1 Zone în matrice p tratic  . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 17

10.1 Ad ugarea unui element în list  . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 98


10.2 Eliminarea unui element din list  . . . . . . . . . . . . . . . . . . . . . . . . . . . . 99
10.3 Coad  de a³teptare implementat  ca list  . . . . . . . . . . . . . . . . . . . . . . . 103
10.4 Suprimarea primului element din list  . . . . . . . . . . . . . . . . . . . . . . . . . 106
10.5 Concatenarea a dou  liste prin append . . . . . . . . . . . . . . . . . . . . . . . . . 107
10.6 Concatenarea a dou  liste prin concat . . . . . . . . . . . . . . . . . . . . . . . . . 107
10.7 Transformarea listei prin inversarea leg turilor . . . . . . . . . . . . . . . . . . . . 108
10.8 List  circular  cu gard  . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 109

11.1 Dreptunghi de arie maxim  în placa cu g uri . . . . . . . . . . . . . . . . . . . . . 113

15.1 Distanµa minim  de editare . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 250


15.2 bababa  abaacab . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 252
15.3 aabaac  baabacc . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 252
15.4 Ai1 À Bj 1 ³i Ai Bj . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 253
15.5 Ai1 À Bj 1 ³i Ai j Bj . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 253
15.6 Ai1 À Bj . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 253
15.7 Ai À Bj 1 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 253
15.8 transform m Ai1 º Bj 1 ³i ai bj . . . . . . . . . . . . . . . . . . . . . . . . . 254
15.9 transform m Ai1 º Bj 1 ³i înlocuim ai cu bj . . . . . . . . . . . . . . . . . . . . 255
15.10transform m Ai1 º Bj ... ³i ³tergem ai de la sfâr³itul lui Ai . . . . . . . . . . . 255
15.11transform m Ai º Bj 1 ³i inser m bj la sfâr³itul lui Ai . . . . . . . . . . . . . . 255

16.1 Deplasare optim  . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 271


16.2 Funcµia next . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 272

17.1 Dreptunghi minim de acoperire . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 292


17.2 a) pentru setul 1 de date ³i b) pentru setul 2 de date . . . . . . . . . . . . . . . . . 308

19.1 abcd . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 315

xiii
Lista tabelelor
2.1 Tipurile primitive de date în Java . . . . . . . . . . . . . . . . . . . . . . . . . . . . 19

3.1 Funcµii de complexitate . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 38

15.1 Triunghi de numere . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 244


15.2 f izicº matematic . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 251
15.3 º
matematic f izic . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 251
15.4 bababaº abaacab . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 251
15.5 abaacabº bababa . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 251
15.6 aabaacº baabacc . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 252
15.7 baabaccº aabaac . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 252

xiv
Lista programelor
2.5.1 DescFibo.java . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 22
15.2.1 distanµa (de editare) Levenshtein . . . . . . . . . . . . . . . . . . . . . . . . . . . 254
15.2.2 distEdit.java . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 255
15.2.3 distEdit.cpp . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 258

xv
Capitolul 1

Structuri de date

Înainte de a elabora un algoritm, trebuie s  ne gândim la modul în care reprezent m datele.

1.1 Date ³i structuri de date

1.1.1 Date

Datele sunt entit µi purt toare de informaµie. În informatic , o dat  este un model de repre-
zentare a informaµiei, accesibil unui anumit procesor (om, unitate central , program), model cu
care se poate opera pentru a obµine noi informaµii despre fenomenele, procesele ³i obiectele lumii
reale. În functie de modul lor de organizare, datele pot : elementare (simple) sau structurate.
Datele elementare au caracter atomic, în sensul c  nu pot  descompuse în alte date mai simple.
Astfel de date sunt cele care iau ca valori numere sau ³iruri de caractere. O dat  elementar  apare
ca o entitate indivizibil  atât din punct de vedere al informaµiei pe care o reprezint  cât ³i din
punct de vedere al procesorului care o prelucreaz .
O dat  elementar  poate  privit  la nivel logic (la nivelul procesorului uman) sau la nivel zic
(la nivelul calculatorului).
Din punct de vedere logic, o dat  poate  denit  ca un triplet de forma

identif icator, atribute, valori.

Din punct de vedere zic, o dat  poate  denit  ca o zon  de memorie de o anumit  lungime,
situat  la o anumit  adres  absolut , în care sunt memorate în timp ³i într-o form  specic 
valorile datei.
Identicatorul este un simbol asociat datei pentru a o distinge de alte date ³i pentru a o putea
referi în cadrul programului.
Atributele sunt propriet µii ale datei ³i precizeaz  modul în care aceasta va  tratat  în cadrul
procesului de prelucrare. Dintre atribute, cel mai important este atributul de tip care dene³tete
apartenenµa datei la o anumit  clas  de date.
O clas  de date este denit  de natura ³i domeniul valorilor datelor care fac parte din clasa
respectiv , de operaµiile specice care se pot efectua asupra datelor ³i de modelul de reprezentare
intern  a datelor. Astfel, exist  date de tip întreg, de tip real, de tip logic, de tip ³ir de caractere,
etc.
O mulµime de date care au acelea³i caracteristici se nume³te tip de date. Evident, un tip de date
este o clas  de date cu acela³i mod de interpretare logic  ³i reprezentare zic  ³i se caracterizeaz 
prin valorile pe care le pot lua datele ³i prin operaµiile care pot  efectuate cu datele de tipul
respectiv.
De exemplu, tipul întreg se caracterizeaz  prin faptul c  datele care îi aparµin pot lua doar
valori întregi, ³i asupra lor pot  efectuate operaµii aritmetice clasice (adunare, sc dere, înmulµire,
împ rµire în mulµimea numerelor întregi, comparaµii).
Se poate considera c  datele organizate sub forma tablourilor unidimensionale formeaz  tipul
vector iar datele organizate sub forma tablourilor bidimensionale formeaz  tipul matrice.
În funcµie de natura elementelor care o compun, o structur  de date poate :

1
CAPITOLUL 1. STRUCTURI DE DATE 2

a omogen , atunci când toate elementele au acela³i tip;

a neomogen , atunci când elementele componente au tipuri diferite.

În functie de num rul datelor care o compun, o structur  de date poate :


a static , atunci când num rul de componente este xat;
a dinamic , atunci când num rul de componente este variabil.
Din punct de vedere al modului în care sunt utilizate datele pot :
a Constante. Valoarea lor nu este ³i nu poate  modicat  în cadrul algoritmului, ind
xat  de la începutul acestuia. O constant  este o dat  care p streaz  aceea³i valoare
pe tot parcursul procesului de prelucrare. Pentru constantele care nu au nume, îns ³i
valoarea lor este cea prin care se identic . Constante care au nume (identicator)
sunt iniµializate cu o valoare în momentul declar rii.
a Variabile. Valoarea lor poate  modicat  în cadrul algoritmului. În momentrul
declar rii lor, variabilele pot  iniµializate (li se atribuie o valoare) sau pot  neini-
µializate (nu li se atribuie nici o valoare). O variabil  este o dat  care nu p streaz 
neap rat aceea³i valoare pe parcursul procesului de prelucrare.
Tipul unei date trebuie s  e precizat, în cadrul programului de prelucrare, printr-o declaraµie
de tipce precede utilizarea respectivei constante sau variabile.
Valorile datei pot  numere, sau valori de adev r, sau ³iruri de caractere, etc.

1.1.2 Structuri de date

Datele apar frecvent sub forma unor colecµii de date de diferite tipuri, menite s  faciliteze
prelucrarea în cadrul rezolv rii unei anumite probleme concrete.
Datele structurate, numite uneori ³i structuri de date, sunt constituite din mai multe date
elementare (uneori de acela³i tip, alteori de tipuri diferite), grupate cu un anumit scop ³i dup 
anumite reguli.
Exemple.
1. Un ³ir nit de numere reale a1 , a2 , ..., an poate  reprezentat ca o dat  structurat  (tablou
unidimensional sau vector).
2. O matrice

Z
^ a1,1 a1,2  a1,n [
_
^
^ _
_
^
^ a2,1 a2,2  a2,n _
_
^
^ _
_
^
^     _
_
^
^ a _
_
\ m,1 am,1  am,n ]
poate  reprezentat  ca o dat  structurat  (tablou bidimensional) specicînd ecare element prin
doi indici (de linie ³i de coloan ).
O structur  de date este deci o colecµie de date, eventual de tipuri diferite, pe care s-a denit
o anumit  organizare ³i c reia îi este specic un anumit mod de identicare a elementelor compo-
nente. Componetele unei structuri de date pot  identicate prin nume sau prin ordinea pe care
o ocup  în cadrul structurii.
Dac  accesul la o anumit  component  a structurii de date se poate face f r  s  µinem seama
de celelalte componente, vom spune c  structura de date este cu acces direct. În schimb, dac 
accesul la o component  a structurii de date se poate face numai µinând cont de alte câmpuri ale
structurii (în conformitate cu ordinea structurii, printr-un proces de traversare) atunci vom spune
c  structura este cu acces secvenµial.
Structurile de date pot  create pentru a  depozitate în memoria intern  (aceste structuri de
date se numesc structuri interne) sau în memoria extern  (se numesc structuri externe, sau ³iere).
Structurile interne au un caracter de date temporare (ele dispar odat  cu încetarea activit µii de
prelucrare) iar cele externe au un caracter de date permanente (mai bine spus, de lung  durat ).
Dac  pe lâng  componentele structurii se înregistreaz  pe suport ³i alte date suplimentare
care s  materializeze relaµia de ordonare, atunci structura de date respectiv  este explicit , în caz
contrar este implicit . De exemplu, structura de date de tip tablou este o structur  implicit  de
date iar structura de date de tip list  liniar  este o structur  explicit  de date.
Asupra structurilor de date se pot efectua operaµii care se refer  structura respectiv  sau la
valorile datelor componente. Cele mai importante operaµii sunt:
CAPITOLUL 1. STRUCTURI DE DATE 3

 operaµia de creare, care const  în memorarea pe suportul de memorie a structurii de date


în forma sa iniµial ,
 operaµia de consultare, care const  în accesul la elementele structurii în vederea prelucr rii
valorilor acestora, ³i
 operaµia de actualizare, care const  în ad ugarea de noi elemente, sau eliminarea elementelor
care nu mai sunt necesare, sau modicarea valorilor unor componente ale structurii.
Toate structurile de date la fel organizate ³i pe care s-au denit acelea³i operaµii, poart  numele
de tip de structur  de date. Dac  analiz m îns  operaµiile care se efectueaz  asupra unei structuri
de date, vom putea vedea c  toate acestea se reduc la executarea, eventual repetat , a unui grup
de operaµii specice numite operaµii de baz .

1.2 Structuri ³i tipuri de date abstracte

1.2.1 Structuri de date abstracte

Abstractizarea datelor reprezint  de fapt concentrarea asupra esenµialului, ignorând detaliile


(sau altfel spus, conteaz  "ce" nu "cum").
St pânirea aplicaµiilor complexe se obµine prin descompunerea în module.
Un modul trebuie s  e simplu, cu complexitatea ascuns  în interiorul lui, ³i s  aib  o interfaµ 
simpl  care s  permit  folosirea lui f r  a cunoa³te implementarea.
O structur  de date abstract  este un modul constând din date ³i operaµii. Datele sunt ascunse
în interiorul modulului ³i pot  accesate prin intermediul operaµiilor. Structura de date este
abstract  deoarece este cunoscut  numai interfaµa structurii, nu ³i implementarea (operaµiile sunt
date explicit, valorile sunt denite implicit, prin intermediul operaµiilor).

1.2.2 Tipuri de date abstracte

Procesul de abstractizare se refer  la dou  aspecte:

ˆ abstractizarea procedural , care separ  propriet µile logice ale unei acµiuni de detaliile imple-
ment rii acesteia

ˆ abstractizarea datelor, care separ  propriet µile logice ale datelor de detaliile reprezent rii
lor

O structur  de date abstracte are un singur exemplar (o singur  instanµ ). Pentru a crea mai
multe exemplare ale structurii de date abstracte se dene³te un tip de date abstract. În Java, de
exemplu, clasa asigur  un mod direct de denire a oric rui tip de date abstract.

1.3 Structuri de date elementare

1.3.1 Liste

O este o colecµie de elemente de informaµie (noduri) aranjate într-o anumit  ordine. Lun-
list 
gimea unei liste este num rul de noduri din list . Structura corespunzatoare de date trebuie s 
ne permit  s  determin m ecient care este primul/ultimul nod în structur  ³i care este predece-
sorul/succesorul unui nod dat (dac  exist ). Iat  cum arat  cea mai simpl  list , lista liniar :

capul coada
listei listei

Figura 1.1: List  liniar 


CAPITOLUL 1. STRUCTURI DE DATE 4

O este o list  în care, dup  ultimul nod, urmeaz  primul nod, deci ecare nod
list  circular 
are succesor³i predecesor.
Câteva dintre operaµiile care se efectueaz  asupra listelor sunt: inserarea (ad ugarea) unui nod,
extragerea (³tergerea) unui nod, concatenarea unor liste, num rarea elementelor unei liste etc.
Implementarea unei liste se realizeaz  în dou  moduri: secvenµial ³i în nµuit.
Implementarea secvential  se caracterizeaz  prin plasarea nodurilor în locaµii succesive de me-
morie, în conformitate cu ordinea lor în list . Avantajele acestui mod de implementare sunt accesul
rapid la predecesorul/succesorul unui nod ³i g sirea rapid  a primului/ultimului nod. Dezavanta-
jele sunt modalit µile relativ complicate de inserarea/³tergere a unui nod ³i faptul c , în general,
nu se folose³te întreaga memorie alocat  listei.
Implementarea înl nµuit  se caracterizeaz  prin faptul c  ecare nod conµine dou  p rµi: in-
formaµia propriu-zis  ³i adresa nodului succesor. Alocarea memoriei pentru ecare nod se poate
face în mod dinamic, în timpul rul rii programului. Accesul la un nod necesit  parcurgerea tuturor
predecesorilor s i, ceea ce conduce la un consum mai mare de timp pentru aceast  operaµie. În
schimb, operaµiile de inserare/³tergere sunt foarte rapide. Se consum  exact atât spaµiu de memo-
rie cât este necesar dar, evident, apare un consum suplimentar de memorie pentru înregistrarea
leg turii c tre nodul succesor. Se pot folosi dou  adrese în loc de una, astfel încât un nod s 
conµin  pe lang  adresa nodului succesor ³i adresa nodului predecesor. Obµinem astfel o list 
dublu inl nµuit , care poate  traversat  în ambele direcµii.
Listele înl nµuite pot  reprezentate prin tablouri. În acest caz, adresele nodurilor sunt de fapt
indici ai tabloului.
O alternativ  este s  folosim dou  tablouri val ³i next astfel: s  memor m informaµia ecarui
nod i în locaµia vali, iar adresa nodului s u succesor în locaµia nexti. Indicele locaµiei primului
nod este memorat în variabila p. Vom conveni ca, pentru cazul listei vide, s  avem p 0 ³i
nextu 0 unde u reprezint  ultimul nod din list . Atunci, valp va conµine informaµia primului
nod al listei, nextp adresa celui de-al doilea nod, valnextp informaµia din al doilea nod,
nextnextp adresa celui de-al treilea nod, etc. Acest mod de reprezentare este simplu dar
apare problema gestion rii locaµiilor libere. O soluµie este s  reprezent m locaµiile libere tot sub
forma unei liste înlanµuite. Atunci, ³tergerea unui nod din lista iniµial  implic  inserarea sa în
lista cu locaµii libere, iar inserarea unui nod în lista iniµial  implic  ³tergerea sa din lista cu locaµii
libere. Pentru implementarea listei de locaµii libere, putem folosi acelea³i tablouri dar avem nevoie
de o alt  variabil , f reehead, care s  conµin  indicele primei locaµii libere din val ³i next. Folosim
acelea³i convenµii: dac  f reehead 0 înseamn  c  nu mai avem locaµii libere, iar nextul 0
unde ul reprezint  ultima locaµie liber .
Vom descrie in continuare dou  tipuri de liste particulare foarte des folosite.

1.3.2 Stive ³i cozi

O stiv  este o list  liniar  cu proprietatea c  operaµiile de inserare/extragere a nodurilor se


fac în/din coada listei. Dac  nodurile A, B, C sunt inserate într-o stiv  în aceast  ordine, atunci
primul nod care poate  ³ters/extras este C. În mod echivalent, spunem c  ultimul nod inserat
este singurul care poate  ³ters/extras. Din acest motiv, stivele se mai numesc ³i liste LIFO (Last
In First Out).
Cel mai natural mod de reprezentare pentru o stiv  este implementarea secvenµial  într-un
tablou S 1..n, unde n este num rul maxim de noduri. Primul nod va  memorat în S 1, al
doilea în S 2, iar ultimul în S top, unde top este o variabil  care conµine adresa (indicele)
ultimului nod inserat. Iniµial, când stiva este vid , avem (prin convenµie) top 0.
O coad  este o list  liniar  în care inser rile se fac doar în capul listei, iar ³tergerile/extragerile
se fac doar din coada listei. Din acest motiv, cozile se mai numesc ³i liste FIFO (First In First
Out).
O reprezentare secvenµial  pentru o coad  se obµine prin utilizarea unui tablou C 0..n  1,
pe care îl trat m ca ³i cum ar  circular: dup  locaµia C n  1 urmeaz  locaµia C 0. Fie tail
variabila care conµine indicele locaµiei predecesoare primei locaµii ocupate ³i e head variabila care
conµine indicele locaµiei ocupate ultima oar . Variabilele head ³i tail au aceea³i valoare atunci ³i
numai atunci când coada este vid . Iniµial, avem head tail 0.
Trebuie s  observ m faptul c  testul de coad  vid  este acela³i cu testul de coad  plin . Dac 
am folosi toate cele n locaµii la un moment dat, atunci nu am putea distinge între situaµia de "coad 
plin " ³i cea de "coad  vid ", deoarece în ambele situaµii am avea head tail. În consecinµ , vom
folosi efectiv, în orice moment, cel mult n  1 locaµii din cele n ale tabloului C .
CAPITOLUL 1. STRUCTURI DE DATE 5

1.3.3 Grafuri

Un graf este o pereche G $ V, M %, unde V este o mulµime de vârfuri, iar M N V  V este


o mulµime de muchii. O muchie de la vârful a la vârful b este notat  cu perechea ordonat  a, b,
dac  graful este orientat, ³i cu multimea ra, bx, dac  graful este neorientat.
Dou  vârfuri unite printr-o muchie se numesc adiacente. Un vârf care este extremitatea unei
singure muchii se nume³te vârf terminal.
Un drum este o succesiune de muchii de forma

a1 , a2 , a2 , a3 , ..., an1 , an 

sau de forma
ra1 , a2 x, ra2 , a3 x, ..., ran1 , an x

dup  cum graful este orientat sau neorientat. Lungimea drumului este egal  cu num rul muchiilor
care îl constituie. Un drum simplu este un drum în care nici un vârf nu se repet . Un ciclu este
un drum care este simplu, cu excepµia primului ³i ultimului vârf, care coincid. Un graf aciclic este
un graf f r  cicluri.
Un graf neorientat este conex, dac  între oricare dou  vârfuri exist  un drum. Pentru grafuri
orientate, aceast  notiune este înt rit : un graf orientat este tare conex, dac  între oricare dou 
vârfuri i ³i j exist  un drum de la i la j ³i un drum de la j la i.
Vârfurilor unui graf li se pot ata³a informaµii (numite valori), iar muchiilor li se pot ata³a
informaµii numite uneori lungimi sau costuri.
Exist  cel puµin trei moduri de reprezentare ale unui graf:
a Printr-o matrice de adiacenµ  A, în care Ai, j  true dac  vârfurile i ³i j sunt adiacente, iar
Ai, j  f alse în caz contrar. O alt  variant  este s -i d m lui Ai, j  valoarea lungimii muchiei
dintre vârfurile i ³i j , considerand Ai, j  ™ atunci când cele dou  vârfuri nu sunt adiacente.
Cu aceast  reprezentare, putem verica u³or dac  dou  vârfuri sunt adiacente. Pe de alt  parte,
dac  dorim s  a m toate vârfurile adiacente unui vârf dat, trebuie s  analiz m o întreag  linie
din matrice. Aceasta necesit  n operaµii (unde n este num rul de vârfuri în graf), independent de
num rul de muchii care conecteaz  vârful respectiv.
a Prin liste de adiacenµ , adic  prin ata³area la ecare vârf i a listei de vârfuri adiacente
(pentru grafuri orientate, este necesar ca muchia s  plece din i). Într-un graf cu m muchii, suma
lungimilor listelor de adiacent  este 2m, dac  graful este neorientat, respectiv m, dac  graful este
orientat. Dac  num rul muchiilor în graf este mic, aceast  reprezentare este preferabil  din punct
de vedere al memoriei necesare. Totu³i, pentru a determina dac  dou  vârfuri i ³i j sunt adiacente,
trebuie s  analiz m lista de adiacent  a lui i (³i, posibil, lista de adiacent  a lui j ), ceea ce este
mai puµin ecient decât consultarea unei valori logice în matricea de adiacenµ .
a Printr-o list  de muchii. Aceast  reprezentare este ecient  atunci când avem de examinat
toate muchiile grafului.

1.3.4 Arbori binari

Un arbore este un graf neorientat, aciclic ³i conex. Sau, echivalent, un arbore este un graf
neorientat în care exist  exact un drum între oricare dou  vârfuri.
Un arbore reprezentat pe niveluri se nume³te arbore cu r d cin . Vârful plasat pe nivelul 0
se nume³te r d cina arborelui. Pe ecare nivel i % 0 sunt plasate vârfurile pentru care lungimea
drumurilor care le leag  de r d cin  este i.
Vârfurile de pe un nivel i % 0 legate de acela³i vârf j de pe nivelul i  1 se numesc descendenµii
direcµi (ii) vârfului j iar vârful j se nume³te ascendent direct (tat ) al acestor vârfuri.
Dac  exist  un drum de la un vârf i de pe nivelul ni la un vârf j de pe nivelul nj % ni, atunci
vârful i se nume³te ascendent al lui j , iar vârful j se nume³te descendent al lui i.
Un vârf terminal (sau frunz ) este un vârf f r  descendenµi. Vârfurile care nu sunt terminale
se numesc neterminale.
Un arbore în care orice vârf are cel mult doi descendenµi se nume³te arbore binar.
Într-un arbore cu r d cin  (reprezentat pe niveluri), adâncimea unui vârf este lungimea dru-
mului dintre r d cin  ³i acest vârf iar în lµimea unui vârf este lungimea celui mai lung drum dintre
acest vârf ³i un vârf terminal.
În lµimea arborelui este în lµimea r d cinii.
CAPITOLUL 1. STRUCTURI DE DATE 6

k
Într-un arbore binar, num rul maxim de vârfuri aate pe nivelul k este 2 . Un arbore binar
k1 k1
de în lµime k are cel mult 2  1 vârfuri, iar dac  are exact 2  1 vârfuri, se nume³te arbore
plin.
Varfurile unui arbore plin se numeroteaza în ordinea nivelurilor. Pentru acela³i nivel, numero-
tarea se face în arbore de la stânga la dreapta.

1 nivelul 0

2 3 nivelul 1

4 5 6 7 nivelul 2

8 9 10 11 12 13 14 15 nivelul 3

Figura 1.2: Arbore binar plin

Un arbore binar cu n vârfuri ³i de în lµime k este complet, dac  se obµine din arborele binar
plin de în lµime k , prin eliminarea, dac  este cazul, a vârfurilor numerotate cu n  1, n  2, ...,
k1
2  1.
Acest tip de arbore se poate reprezenta secvenµial folosind un tablou T , punând vârfurile de
k k k1
adâncime k , de la stânga la dreapta, în poziµiile T 2 , T 2  1, ..., T 2  1 (cu posibila
excepµie a ultimului nivel care poate  incomplet).

1 nivelul 0

2 3 nivelul 1

4 5 6 7 nivelul 2

8 9 10 11 12 nivelul 3

Figura 1.3: Arbore binar complet

Tat l unui vârf reprezentat în T i, i % 0, se a  în T i©2. Fiii unui vârf reprezentat în T i
se a , dac  exist , în T 2i ³i T 2i  1.

1.3.5 Heap-uri

Un max-heap (heap="gramad  ordonat ", în traducere aproximativ ) este un arbore binar


complet, cu urm toarea proprietate: valoarea ecarui vârf este mai mare sau egal  cu valoarea
ec rui u al s u.
Un min-heap este un arbore binar complet în care valoarea ecarui vârf este mai mic  sau
egal  cu valoarea ec rui u al s u.
Acela³i heap poate  reprezentat secvenµial prin urm torul tablou:

11 7 10 7 7 9 2 4 6 5 7 3

Caracteristica de baz  a acestei structuri de date este c  modicarea valorii unui vârf se face
foarte ecient, p strându-se proprietatea de heap.
De exemplu, într-un max-heap, dac  valoarea unui vârf cre³te, astfel încât dep ³e³te valoarea
tat lui, este sucient s  schimb m între ele aceste dou  valori ³i s  continu m procedeul în mod
ascendent, pân  când proprietatea de heap este restabilit . Dac , dimpotriv , valoarea vârfului
CAPITOLUL 1. STRUCTURI DE DATE 7

11 nivelul 0

7 10 nivelul 1

7 7 9 2 nivelul 2

4 6 5 7 3 nivelul 3

Figura 1.4: Max-heap

scade, astfel încât devine mai mic  decât valoarea cel puµin a unui u, este sucient s  schimb m
intre ele valoarea modicat  cu cea mai mare valoare a iilor, apoi s  continu m procesul în mod
descendent, pân  când proprietatea de heap este restabilit .
Heap-ul este structura de date ideal  pentru extragerea maximului/minimului dintr-o mulµime,
pentru inserarea unui vârf, pentru modicarea valorii unui vârf. Sunt exact operaµiile de care avem
nevoie pentru a implementa o list  dinamic  de priorit µi: valoarea unui vârf va da prioritatea
evenimentului corespunzator.
Evenimentul cu prioritatea cea mai mare/mic  se va aa mereu la radacina heap-ului, iar
prioritatea unui eveniment poate  modicat  în mod dinamic.

1.3.6 Structuri de mulµimi disjuncte

S  presupunem c  avem N elemente, numerotate de la 1 la N . Numerele care identic 


elementele pot , de exemplu, indici intr-un tablou unde sunt memorate valorile elementelor. Fie
o partitie a acestor N elemente, format  din submulµimi dou  câte dou  disjuncte: S1, S2, ... .
Presupunem c  ne intereseaz  reuniunea a dou  submulµimi, Si < Sj .
Deoarece submulµimile sunt dou  câte dou  disjuncte, putem alege ca etichet  pentru o sub-
mulµime oricare element al ei. Vom conveni ca elementul minim al unei mulµimi s  e eticheta
mulµimii respective. Astfel, multimea r3, 5, 2, 8x va  numit  "multimea 2".
Vom aloca tabloul set1..N , în care ec rei locatii seti i se atribuie eticheta submulµimii
care conµine elementul i. Avem atunci proprietatea: seti & i, pentru 1 & i & N . Reuniunea
submulµimilor etichetate cu a ³i b se poate realiza astfel:
procedure reuniune a, b
i  a;
j b
if i % j
then interschimb  i ³i j

for k j to N do
if setk j

then setk i
Capitolul 2

Algoritmi

2.1 Etape în rezolvarea problemelor


Principalele etape care se parcurg în rezolvarea unei probleme sunt:
(a) Stabilirea datelor iniµiale ³i a obiectivului (ce trebuie determinat).
(b) Alegerea metodei de rezolvare.
(c) Aplicarea metodei pentru date concrete.
Exemplu.
2
S  presupunem c  problema este rezolvarea, în R, a ecuaµiei x  3x  2 0.

(a) Datele iniµiale sunt reprezentate de c tre coecienµii ecuaµiei iar obiectivul este
determinarea r d cinilor reale ale ecuaµiei.
(b) Vom folosi metoda de rezolvare a ecuaµiei de gradul al doilea având forma general 
2
ax  bx  c 0. Aceast  metod  poate  descris  astfel:
2
Pasul 1. Se calculeaz  discriminantul: ∆ b  4ac.
Pasul 2. Dac  ∆ % 0
Ó
atunci ecuaµia are dou  r d cini reale distincte: x1,2 b
2a

altfel, dac  ∆ 0
atunci ecuaµia are o r d cina real  dubl : x1,2 b
2a
altfel ecuaµia nu are r d cini reale.
(c) Aplicarea metodei pentru datele problemei (a 1, b 3, c 2) conduce la rezul-
tatul: x1 1, x2 2.

2.2 Algoritmi

2.2.1 Ce este un algoritm?


Un algoritm este o succesiune de operaµii aritmetice ³i/sau logice care, aplicate asupra
unor date, permit obµinerea rezultatului unei probleme din clasa celor pentru care a
fost conceput.

S  observ m c  nu apare în deniµie cuvântul "calculator"; algoritmii nu au neap rat leg -


tur  cu calculatorul. Totu³i, în acest curs ne vom concentra aproape exclusiv pe algoritmi care
pot  implementaµi rezonabil pe calculator. Altfel spus, ecare pas din algoritm trebuie astfel
gândit încât ori este suportat direct de c tre limbajul de programare favorit (operaµii aritmetice,
cicluri, recursivitate, etc) ori este asem n tor cu ceva înv µat mai înainte (sortare, c utare binar ,
parcurgere în adâncime, etc).

8
CAPITOLUL 2. ALGORITMI 9

Secvenµa de pa³i prin care este descris  metoda de rezolvare a ecuaµiei de gradul al doilea
(prezentat  în secµiunea anterioar ) este un exemplu de algoritm. Calculul efectuat la Pasul 1
este un exemplu de operaµie aritmetic , iar analiza semnului discriminantului (Pasul 2) este un
exemplu de operaµie logic .
Descrierea unui algoritm presupune precizarea datelor iniµiale ³i descrierea prelucr rilor efec-
tuate asupra acestora. Astfel, se poate spune c :
algoritm = date + prelucr ri
Al-Khwarizmi a fost cel care a folosit pentru prima dat  reguli precise ³i clare pentru a descrie
procese de calcul (operaµii aritmetice fundamentale) în lucrarea sa "Scurt  carte despre calcul
algebric". Mai târziu, aceast  descriere apare sub denumirea de algoritm în "Elementele lui
Euclid". Algoritmul lui Euclid pentru calculul celui mai mare divizor comun a dou  numere
naturale este, se pare, primul algoritm cunoscut în matematic .
În matematic  noµiunea de algoritm a primit mai multe deniµii: algoritmul normal al lui A.
A. Markov, algoritmul operaµional al lui A. A. Leapunov, ma³ina Turing, funcµii recursive, sisteme
POST. S-a demonstrat c  aceste deniµii sunt echivalente din punct de vedere matematic.
În informatic  exist  de asemenea mai multe deniµii pentru noµiunea de algoritm. De exemplu,
în [41] noµiunea de algoritm se dene³te astfel:
Un algoritm este sistemul virtual
A M, V, P, R, Di, De, M i, M e
constituit din urm toarele elemente:
M - memorie intern  format  din locaµii de memorie ³i utilizat  pentru stocarea tem-
porar  a valorilor variabilelor;
V - mulµime de variabile denite în conformitate cu raµionamentul R, care utilizeaz 
memoria M pentru stocarea valorilor din V ;
P - proces de calcul reprezentat de o colecµie de instrucµiuni/comenzi exprimate într-un
limbaj de reprezentare (de exemplu, limbajul pseudocod); folosind memoria virtu-
al  M ³i mulµimea de variabile V , instrucµiunile implementeaz /codic  tehnicile
³i metodele care constituie raµionamentul R; execuµia instrucµiunilor procesului de
calcul determin  o dinamic  a valorilor variabilelor; dup  execuµia tuturor instruc-
µiunilor din P , soluµia problemei se a  în anumite locaµii de memorie corespunz -
toare datelelor de ie³ire De;
R- raµionament de rezolvare exprimat prin diverse tehnici ³i metode specice dome-
niului din care face parte clasa de probleme supuse rezolv rii (matematic , zic ,
chimie etc.), care îmbinate cu tehnici de programare corespunz toare realizeaz 
acµiuni/procese logice, utilizând memoria virtual  M ³i mulµimea de variabile V ;
Di - date de intrare care reprezint  valori ale unor parametri care caracterizeaz  ipo-
tezele de lucru/st rile iniµiale ale problemei ³i care sunt stocate în memoria M prin
intermediul instrucµiunilor de citire/intrare care utilizeaz  mediul de intrare M i;
De - date de ie³ire care reprezint  valori ale unor parametri care caracterizeaz  soluµia
problemei/st rile nale; valorile datelor de ie³ire sunt obµinute din valorile unor
variabile generate de execuµia instrucµiunilor din procesul de calcul P , sunt stocate
în memoria M , ³i înregistrate pe un suport virtual prin intermediul instrucµiunilor
de scriere/ie³ire care utilizeaz  mediul de ie³ire M e; ;
M i - mediu de intrare care este un dispozitiv virtual de intrare/citire pentru preluarea
valorilor datelor de intrare ³i stocarea acestora în memoria virtual  M ;
M e - mediu de ie³ire care este un dispozitiv virtual de ie³ire/scriere pentru prelua-
rea datelor din memoria virtual  M ³i înregistrarea acestora pe un suport virtual
(ecran, hârtie, disc magnetic, etc.).
Un limbaj este un mijloc de transmitere a informaµiei.
Exist  mai multe tipuri de limbaje: limbaje naturale (englez , român , etc), limbaje ³tiinµice
(de exemplu limbajul matematic), limbaje algoritmice, limbaje de programare (de exemplu Pascal,
C, Java), etc.
Un limbaj de programare este un limbaj articial, riguros întocmit, care permite
descrierea algoritmilor astfel încât s  poat   transmi³i calculatorului cu scopul ca
acesta s  efectueze operaµiile specicate.
CAPITOLUL 2. ALGORITMI 10

Un program este un algoritm tradus într-un limbaj de programare.

2.2.2 Propriet µile algoritmilor

Principalele propriet µi pe care trebuie s  le aib  un algoritm sunt:

a Generalitate. Un algoritm trebuie s  poat   utilizat pentru o clas  întreag  de


probleme, nu numai pentru o problem  particular . Din aceast  cauz , o metod  de
rezolvare a unei ecuaµii particulare nu poate  considerat  algoritm.
a Finitudine. Orice algoritm trebuie s  permit  obµinerea rezultatului dup  un num r
nit de prelucr ri (pa³i). Din aceast  cauz , o metod  care nu asigur  obµinerea
rezultatului dup  un num r nit de pa³i nu poate  considerat  algoritm.
a Determinism. Un algoritm trebuie s  prevad , f r  ambiguit µi ³i f r  neclarit µi,
modul de soluµionare a tuturor situaµiilor care pot s  apar  în rezolvarea problemei.
Dac  în cadrul algoritmului nu intervin elemente aleatoare, atunci ori de câte ori se
aplic  algoritmul aceluia³i set de date de intrare trebuie s  se obµin  acela³i rezultat.

2.2.3 Tipuri de prelucr ri


Prelucr rile care intervin într-un algoritm pot  simple sau structurate.

a Prelucr rile simple sunt atribuiri de valori variabilelor, eventual prin evaluarea unor
expresii;
a Prelucr rile structurate pot  de unul dintre tipurile:

 Liniare. Sunt secvenµe de prelucr ri simple sau structurate care sunt


efectuate în ordinea în care sunt specicate;
 Alternative. Sunt prelucr ri caracterizate prin faptul c  în funcµie de
realizarea sau nerealizarea unei condiµii se alege una din dou  sau mai multe
variante de prelucrare;
 Repetitive. Sunt prelucr ri caracterizate prin faptul c  aceea³i prelucrare
(simpl  sau structurat ) este repetat  cât timp este îndeplinit  o anumit 
condiµie.

2.3 Descrierea algoritmilor


Algoritmii nu sunt programe, deci ei nu trebuie specicaµi într-un limbaj de programare. De-
taliile sintactice, de exemplu din Pascal, C/C++ sau Java, nu au nici o importanµ  în elaborarea-
/proiectarea algoritmilor.
Pe de alt  parte, descrierea în limba român  (ca ³i în limba englez  [14]) în mod uzual nu
este o idee mai bun . Algoritmii au o serie de structuri - în special condiµionale, repetitive, ³i
recursivitatea - care sunt departe de a putea  descrise prea u³or în limbaj natural. La fel ca orice
limb  vorbit , limba româna este plin  de ambiguit µi, subînµelesuri ³i nuanµe de semnicaµie, iar
algoritmii trebuie s  e descri³i cu o acurateµe maxim posibil .
Cea mai bun  metod  de a descrie un algoritm este utilizarea limbajului pseudocod. Acesta
folose³te structuri ale limbajelor de programare ³i matematicii pentru a descompune algoritmul în
pa³i elementari (propoziµii simple), dar care pot  scrise folosind matematica, româna curat , sau
un amestec al celor dou .
Modul exact de structurare a pseudocodului este o alegere personal .
O descriere foarte bun  a algoritmului arat  structura intern  a acestuia, ascunde detaliile care
nu sunt semnicative, ³i poate  implementat  u³or de c tre orice programator competent în orice
limbaj de programare, chiar dac  el nu înµelege ce face acel algoritm. Un pseudocod bun, la fel ca
³i un cod bun, face algoritmul mult mai u³or de înµeles ³i analizat; el permite de asemenea, mult
mai u³or, descoperirea gre³elilor.
Pe de alt  parte, proba clar  se poate face numai pe baza unui program care s  dea rezultatele
corecte! Oamenii sunt oameni! Cineva poate s  insiste c  algoritmul lui este bun de³i ... nu este!
“i atunci ... program m!
CAPITOLUL 2. ALGORITMI 11

2.3.1 Limbaj natural


Exemple.
1. Algoritmul lui Euclid. Permite determinarea celui mai mare divizor comun (cmmdc) a dou 
numere naturale a ³i b. Metoda de determinare a cmmdc poate  descris  în limbaj natural dup 
cum urmeaz .
Se împarte a la b ³i se reµine restul r. Se consider  ca nou deîmp rµit vechiul împ rµitor ³i ca
nou împarµitor restul obµinut la împ rµirea anterioar . Operaµia de împ rµire continu  pân  se
obµine un rest nul. Ultimul rest nenul (care a fost ³i ultimul împ rµitor) reprezint  rezultatul.
Se observ  c  metoda descris  îndepline³te propriet µile unui algoritm: poate  aplicat  oric rei
perechi de numere naturale iar num rul de prelucr ri este nit (dup  un num r nit de împ rµiri
se ajunge la un rest nul).
De asemenea se observ  c  prelucrarea principal  a algoritmului este una repetitiv , condiµia
utilizat  pentru a analiza dac  s-a terminat prelucrarea ind egalitatea cu zero a restului.
2. Schema lui Horner. Permite determinarea câtului ³i restului împ rµirii unui polinom
n n1
P X  an X  an1 X  ...  a1 X  a0 0 la un binom de forma X  b.
O modalitate simpl  de a descrie metoda de rezolvare este schema urm toare:
an an1 ... ak ... a2 a1 a0
b an bcn1  an1 ... bck  ak ... bc2  a2 bc1  a1 bc0  a0
... ...
cn1 cn2 ... ck1 ... c1 c0 P b
Valorile cn1 , cn2 , ..., c1 , c0 reprezint  coecienµii câtului, iar ultima valoare calculat  repre-
zint  valoarea restului (valoarea polinomului calculat  în b).
“i în acest caz prelucrarea principal  este una repetitiv  constând în evaluarea expresiei bck  ak
pentru k luând, în aceast  ordine, valorile n  1, n  2, ..., 2, 1, 0.

2.3.2 Scheme logice

Scrierea unui program pornind de la un algoritm descris într-un limbaj mai mult sau mai puµin
riguros, ca în exemplele de mai sus, este dicil  întrucât nu sunt pu³i în evidenµ  foarte clar pa³ii
algoritmului.
Modalit µi intermediare de descriere a algoritmilor, între limbajul natural sau cel matematic
³i un limbaj de programare, sunt schemele logice ³i limbajele algoritmice.
Schemele logicesunt descrieri grace ale algoritmilor în care ec rui pas i se ata³eaz 
un simbol grac, numit bloc, iar modul de înl nµuire a blocurilor este specicat prin
segmente orientate.
Schemele logice au avantajul c  sunt sugestive dar ³i dezavantajul c  pot deveni dicil de
urm rit în cazul unor prelucr ri prea complexe. Acest dezavantaj, dar ³i evoluµia modului de
concepere a programelor, fac ca schemele logice s  e din ce în ce mai puµin folosite (în favoarea
limbajelor algoritmice).

2.3.3 Pseudocod

Un limbaj algoritmic este o notaµie care permite exprimarea logicii algoritmilor într-un mod
formalizat f r  a  necesare reguli de sintax  riguroase, ca în cazul limbajelor de programare.
Un limbaj algoritmic mai este denumit ³i pseudocod. Un algoritm descris în pseudocod conµine
atât enunµuri care descriu operaµii ce pot  traduse direct într-un limbaj de programare (unui
enunµ în limbaj algoritmic îi corespunde o instrucµiune în program) cât ³i enunµuri ce descriu
prelucr ri ce urmeaz  a  detaliate abia în momentul scrierii programului.
Nu exist  un anumit standard în elaborarea limbajelor algoritmice, ecare programator putând
s  conceap  propriul pseudocod, cu condiµia ca acesta s  permit  o descriere clar  ³i neambigu  a
algoritmilor. Se poate folosi sintaxa limbajului de programare preferat, în care apar enunµuri de
prelucr ri. De exemplu:
for ecare vârf v din V
{
culoare[v] = alb;
distanta[v] = innit;
predecesor[v]=-1;
CAPITOLUL 2. ALGORITMI 12

2.4 Limbaj algoritmic


În continuare prezent m un exemplu de limbaj algoritmic.

2.4.1 Declararea datelor

Datele simple se declar  sub forma:


$tip% $nume%;
unde $tip% poate lua una dintre valorile: byte, short, int, long, oat, double, boolean, char.
Tablourile unidimensionale se declar  sub forma:
$tip% $nume% [n1 ..n2 ];
Elementele vectorului pot  accesate cu ajutorul unui indice, care poate lua valori între n1 ³i
n2 , sub forma:
$nume%[i]
unde i poate lua orice valoare între n1 ³i n2 .
În cazul tablourilor bidimensionale, o declaraµie de forma:
$tip% $nume%[m1 ..m2 ][n1 ..n2 ];
specic  o matrice cu m2  m1  1 linii ³i n2  n1  1 coloane. Fiecare element se specic  prin
doi indici:
$nume%[i][j]
unde i reprezint  indicele liniei ³i poate avea orice valoare între m1 ³i m2 iar j reprezint  indicele
coloanei ³i poate avea orice valoare între n1 ³i n2 .

2.4.2 Operaµii de intrare/ie³ire

Preluarea valorilor pentru datele de intrare este descris  sub forma:


read v1 , v2 , ...;
unde v1 , v2 , ... sunt nume de variabile.
A³area rezultatelor este descris  sub forma:
write e1 , e2 , ...;
unde e1 , e2 , ... sunt expresii (în particular pot  constante sau variabile).
Operaµia de atribuire. Operaµia de atribuire a unei valori c tre o variabil  se descrie prin:
v = $expresie%;
unde v este un nume de variabil , $expresie% desemneaz  o expresie aritmetic  sau logic , iar
"=" este operatorul de atribuire. Pentru acesta din urm  pot  folosite ³i alte simboluri, ca de

exemplu ":=" sau " ". Expresiile pot  descrise conform regulilor utilizate în matematic .

2.4.3 Prelucr ri liniare

O secvenµ  de prelucr ri se descrie în modul urm tor:


$prel_1%;
$prel_2%;
...
$prel_n%;
sau
$prel_1%; $prel_2%; ... $prel_n%;
O astfel de scriere indic  faptul c  în momentul execuµiei prelucr rile se efectueaz  în ordinea
în care sunt specicate.
CAPITOLUL 2. ALGORITMI 13

2.4.4 Prelucr ri alternative

O prelucrare alternativ  complet  (cu dou  ramuri) este descris  prin:


if $condiµie% $prel_1% else $prel_2%;
sau sub forma
if $condiµie% then $prel_1% else $prel_2%;
unde $condiµie% este o expresie relaµional . Aceast  prelucrare trebuie înµeleas  în modul urm -
tor: dac  condiµia este adev rat  atunci se efectueaz  prelucrarea $prel_1%, altfel se efectueaz 
$prel_2%.
O prelucrare alternativ  cu o singur  ramur  se descrie prin:
if $condiµie% $prel%;
sau
if $condiµie% then $prel%;
iar execuµia ei are urm torul efect: dac  condiµia este satisfacut  atunci se efectueaz  prelucra-
rea specicat , altfel nu se efectueaz  nici o prelucrare ci se trece la urm toarea prelucrare a
algoritmului.

2.4.5 Prelucr ri repetitive

Prelucr rile repetitive pot  de trei tipuri:


a cu test iniµial,
a cu test nal ³i
a cu contor.
Prelucrarea repetitiv  cu test iniµial se descrie prin: Prelucrarea repetitiv  cu test iniµial se
descrie prin:
while $condiµie% $prel%;
sau
while $condiµie% do $prel%;
În momentul execuµiei, atât timp cât condiµia este adevarat , se va executa instrucµiunea. Dac 
condiµia nu este la început satisf cut , atunci instrucµiunea nu se efectueaz  niciodat .
Prelucrarea repetitiv  cu test nal se descrie prin:
do $prel% while $condiµie%;
Prelucrarea se repet  pân  când condiµia specicat  devine fals . În acest caz prelucrarea se
efectueaz  cel puµin o dat , chiar dac  condiµia nu este satisfacut  la început.
Prelucrarea repetitiv  cu contor se caracterizeaz  prin repetarea prelucr rii de un num r pres-
tabilit de ori ³i este descris  prin:
for i i1 , i2 , ..., in $prel%;
sau
for i i1 , i2 , ..., in do $prel%;
unde i este variabila contor care ia, pe rând, valorile i1 , i2 , ..., in în aceast  ordine, prelucrarea
ind efectuat  pentru ecare valoare a contorului.
Alte forme utilizate sunt:
for i vi to vf do $prel%;
în care contorul ia valori consecutive cresc toare între vi ³i vf , ³i
for i vi downto vf do $prel%;
în care contorul ia valori consecutive descresc toare între vi ³i vf .
CAPITOLUL 2. ALGORITMI 14

2.4.6 Subalgoritm

În cadrul unui algoritm poate s  apar  necesitatea de a specica de mai multe ori ³i în diferite
locuri un grup de prelucr ri. Pentru a nu le descrie în mod repetat ele pot constitui o unitate
distinct , identicabil  printr-un nume, care este numit  subalgoritm. Ori de câte ori este nece-
sar  efectuarea grupului de prelucr ri din cadrul subalgoritmului se specic  numele acestuia ³i,
eventual, datele curente asupra c rora se vor efectua prelucrarile. Aceast  acµiune se nume³te apel
al subalgoritmului, iar datele specicate al turi de numele acestuia ³i asupra c rora se efectueaz 
prelucrarile se numesc parametri.
În urma traducerii într-un limbaj de programare un subalgoritm devine un subprogram.
Un subalgoritm poate  descris în felul urm tor:
$nume_subalg% ($tip% $nume_p1%, $tip% $nume_p2%, ... )
{
...
/* prelucr ri specice subalgoritmului */
...
return $nume_rezultat%;
}
unde $nume_subalg% reprezint  numele subalgoritmului iar nume_p1, nume_p2, ... reprezint 
numele parametrilor. Ultimul enunµ, prin care se returneaz  rezultatul calculat în cadrul subal-
goritmului, este optional.
Modul de apel depinde de modul în care subalgoritmul returneaz  rezultatele sale. Dac  su-
balgoritmul returneaz  efectiv un rezultat, printr-un enunµ de forma

return $nume_rezultat%;
atunci subalgoritmul se va apela în felul urm tor:

v=$nume_subalg%(nume_p1, nume_p2, ...);

Ace³ti subalgoritmi corespund subprogramelor de tip funcµie.


Dac  în subalgoritm nu apare un astfel de enunµ, atunci el se va apela prin:

$nume_subalg%(nume_p1, nume_p2, ...);


variant  care corespunde subprogramelor de tip procedur .
Observaµie. Prelucr rile care nu sunt detaliate în cadrul algoritmului sunt descrise în limbaj
natural sau limbaj matematic. Comentariile suplimentare vor  cuprinse între /* ³i */. Dac 
pe o linie a descrierii algoritmului apare simbolul // atunci tot ce urmeaz  dup  acest simbol,
pe aceea³i linie cu el, este interpretat ca ind un comentariu (deci, nu reprezint  o prelucrare a
algoritmului).

2.4.7 Probleme rezolvate

1. Algoritmului lui Euclid.


Descrierea în pseudocod a algoritmului lui Euclid este urm toarea:
int a, b, d, i, r;
read a, b;
if (a$b) { d=a; i=b; } else { d=b; i=a; };
r = d % i;
while (r ! 0) { d=i; i=r; r=d % i; };
write i;

2. Schema lui Horner.


Descrierea în pseudocod a schemei lui Horner este urm toarea:
CAPITOLUL 2. ALGORITMI 15

int n, a, b, i;
read n, a, b;
int a[0..n], c[0..n-1];
for i=n,0,-1 read a[i];
c[n-1]=b*a[n];
for i=1,n-1 c[n-i-1]=b*c[n-i]+a[n-i];
val:=b*c[1]+a[1];
write val;

3. Conversia unui num r natural din baza 10 în baza 2.


Fie n un num r întreg pozitiv. Pentru a determina cifrele reprezentarii în baza doi a acestui
num r se poate folosi urm toarea metod :
Se împarte n la 2, iar restul va reprezenta cifra de rang 0. Câtul obµinut la împartirea anterioar 
se împarte din nou la 2, iar restul obµinut va reprezenta cifra de ordin 1 ³.a.m.d. Secvenµa de
împ rµiri continu  pîn  la obµinerea unui cât nul.
Descrierea în pseudocod a acestui algoritm este:
int n, d, c, r;
read n;
d = n;
c = d / 2; /* câtul împ rµirii întregi a lui d la 2 */
r = d % 2; /* restul împ rµirii întregi a lui d la 2 */
write r;
while (c != 0) {
d = c;
c = d / 2; /* câtul împ rµirii întregi a lui d la 2 */
r = d % 2; /* restul împ rµirii întregi a lui d la 2 */
write r;
}

4. Conversia unui num r întreg din baza 2 în baza 10.


Dac  bk bk1 ...b1 b0 reprezint  cifrele num rului în baza 2, atunci valoarea în baza 10 se obµine
efectuînd calculul:
k k1
bk bk1 ...b1 b0 10 bk 2  bk1 2  ...  b1 2  b0

De³i calculul de mai sus este similar cu evaluarea pentru X 2 a polinomului


k k1
P X  bk X  bk1 X  ...  b1 X  b0

prelucrare pentru care ar putea  folosit algoritmul corespunz tor schemei lui Horner, în continuare
prezent m o alt  variant  de rezolvare a acestei probleme, care folose³te un subalgoritm pentru
calculul puterilor unui num r întreg:
int k, i, s; putere(int a, int n)
read k; {
int b[0..k]; int i, p;
read b; p = 1;
s = 0; for i=2,n p = p*a;
for i=0,k s = s+b[i] * putere(2,i); return p;
write s; }

5. S  se scrie un algoritm pentru determinarea tuturor divizorilor naturali ai unui num r


întreg.
Rezolvare. Fie n num rul ai c rui divizori trebuie determinaµi. Evident 1 ³i ¶n¶ sunt divizori ai
lui n. Pentru a determina restul divizorilor este sucient ca ace³tia s  e c utaµi printre elementele
mulµimii r2, 3, ..., ¶n¶x cu x desemnând partea întreag  a lui x.
Algoritmul poate descris în modul urm tor:
int n, d;
read n;
write 1; /* a³area primului divizor */
for d 2, ¶n¶©2
if (d divide pe n) then write d;
write ¶n¶ /* a³area ultimului divizor */
CAPITOLUL 2. ALGORITMI 16

6. S  se scrie un algoritm pentru determinarea celui mai mare element dintr-un ³ir de numere
reale.
Rezolvare. Fie x1 , x2 , ..., xn ³irul analizat. Determinarea celui mai mare element const  în
iniµializarea unei variabile de lucru max (care va conµine valoarea maximului) cu x1 ³i compararea
acesteia cu ecare dintre celelalte elemente ale ³irului. Dac  valoarea curent  a ³irului, xk , este
mai mare decât valoarea variaabilei max atunci acesteia din urm  i se va da valoarea xk . Astfel,
dup  a k  1 comparaµie variabila max va conµine valoarea maxim  din sub³irul x1 , x2 , ..., xk .
Algoritmul poate  descris în modul urm tor:
int k, n;
read n;
double x[1..n], max; /* vectorul ³i variabila de lucru */
read x; /* preluarea elementelor ³irului */
max = x[1];
for k = 2, n
if (max $ x[k]) then max = x[k];
write max;

7. S  se aproximeze, cu precizia ε, limita ³irului

= k!1 .
n
sn
k 0

Rezolvare. Calculul aproximativ (cu precizia ε) al limitei ³irului sn const  în calculul sumei
nite sk , unde ultimul termen al sumei, tk 1
k!
, are proprietatea tk $ ε. Întrucât tk1 tk
k1
,
aceast  relaµie va  folosit  pentru calculul valorii termenului curent (permiµând mic³orarea nu-
m rului de calcule).
double eps, t, s;
int k;
k=1; /* iniµializare indice */
t=1; /* iniµializare termen */
s=1; /* iniµializare suma */
do {
s=s+t; /* ad ugarea termenului curent */
k=k+1;
t=t/k; /* calculul urm torului termen */
} while (t ' eps);
s=s+t; (* ad ugarea ultimului termen *)
write s;

8. Fie A o matrice cu m linii ³i n coloane, iar B o matrice cu n linii ³i p coloane, ambele având
elemente reale. S  se determine matricea produs C A  B .
Rezolvare. Matricea C va avea m linii ³i p coloane, iar ecare element se determin  efectuând
suma:
=a
n
ci,j i,k bk,j , 1 & i & m, 1 & j & p.
k 1

În felul acesta calculul elementelor matricei C se efectueaz  prin trei cicluri imbricate (unul
pentru parcurgerea liniilor matricei C , unul pentru parcurgerea coloanelor matricei C , iar unul
pentru efectuarea sumei specicate mai sus).
int m, n, p; /* dimensiunile matricelor */
read m, n, p;
double a[1..m][1..n], b[1..n][1..p], c[1..m][1..p]; /* matrice */
int i, j, k; /* indici */
read a; /* citirea matricei a */
read b; /* citirea matricei b */
for i=1,m
for j=1,p {
c[i,j]=0;
for k=1,n c[i][j]=c[i][j]+a[i][k]*b[k][j];
}
write c;
CAPITOLUL 2. ALGORITMI 17

2.4.8 Probleme propuse

1. Fie D o dreapt  de ecuaµie ax  by  c 0 ³i (C) un cerc de centru O x0 , y0  ³i raz  r. S 


se stabileasc  poziµia dreptei faµ  de cerc.
Indicaµie. Se calculeaz  distanµa de la centrul cercului la dreapta D utilizând formula:

¶ax0  by0  c¶
d Ó .
2 2
a  b
Dac  d ' r  ε atunci dreapta este exterioar  cercului, dac  d & r  ε atunci dreapta este secant ,
iar dac  r  ε $ d $ r  ε atunci este tangent  (la implementarea egalitatea între dou  numere
reale ...).

2. S  se genereze primele n elemente ale ³irurilor ak ³i bk date prin relaµiile de recurenµ :


5ak  3 ak  3
ak1
ak  3
, bk
ak  1
, k ' 0, a0 1.

3. S  se determine r d cina p trat  a unui numur real pozitiv a cu precizia ε 0.001, folosind
relaµia de recurenµ :
1 a
xn1 x  , x1 a.
2 n xn
Precizia se consider  atins  când ¶xn1  xn ¶ $ ε.

4. Fie A o matrice p tratic  de dimensiune n. S  se transforme matricea A, prin interrschim-


b ri de linii ³i de coloane, astfel încât elementele de pe diagonala principal  s  e ordonate cres-
c tor.

5. S  se determine cel mai mare divizor comun al unui ³ir de numere întregi.

6. S  se calculeze coecienµii polinomului


a, b " Z, n " N.
n
P X  aX  b ,

7. Fie A o matrice p tratic . S  se calculeze suma elementelor din ecare zon  (diagonala
principal , diagonala secundar , etc.) marcat  în gura urm toare:

a. b. c.

Figura 2.1: Zone în matrice p tratic 

8. Fie x1 , x2 , ..., xn " Z r d cinile unui polinom cu coecienµi întregi:


n n1
P X  an X  an1 X  ...  a1 X  a0 .
S  se determine coecienµii polinomului.

9. S  se determine toate r d cinile raµionale ale polinomului P X  care are coecienµi întregi.

140. Fie P1 , P2 , ..., Pn  un poligon convex dat prin coordonatele carteziene ale vârfurilor sale
(în ordine trigonometric ). S  se calculeze aria poligonului.
CAPITOLUL 2. ALGORITMI 18

11. Fie f  a, b R o funcµie continu  cu proprietatea c  exist  un unic ξ " a, b care are
proprietatea c  f ξ  0. S  se aproximeze ξ cu precizia ε 0.001 utilizând metoda bisecµiei.

12. Fie P ³i Q polinoame cu coecienµi întregi. S  se determine toate r d cinile raµionale


comune celor dou  polinoame.

13. S  se determine toate numerele prime cu maxim 6 cifre care r mân prime ³i dup  "r stur-
narea" lor (r sturnatul num rului abcdef este f edcba).

2.5 Instrucµiuni corespondente limbajului algoritmic

2.5.1 Declararea datelor

Datele simple se declar  sub forma:


$tip% $nume%;
sau
$tip% $nume% literal;
unde $tip% poate lua una dintre urm toarele valori: byte, short, int, long, oat, double,
boolean, char. În exemplul urm tor sunt prezentate câteva modalit µi de declarare pentru
diferite tipuri de variabile.
class Literali
{
public static void main(String args[])
{
long l1 = 5L;
long l2 = 12l;
int i1hexa = 0x1;
int i2hexa = 0X1aF;
int i3octal = 01;
long i4octal = 012L;
long i5LongHexa = 0xAL;
float f1 = 5.40F;
float f2 = 5.40f;
float f3 = 5.40e2f;
float f4 = 5.40e+12f;
float f5 = 5.40; // da eroare, trebuie cast
double d1 = 5.40; // implicit este double !
double d2 = 5.40d;
double d3 = 5.40D;
double d4 = 5.40e2;
double d5 = 5.40e+12d;
char c1 = ’r’;
char c2 = ’\u4567’;
}
}
Java dene³te mai multe tipuri primitive de date. Fiecare tip are o anumit  dimensiune, care
este independent  de caracteristicile ma³inii gazd . Astfel, spre deosebire de C/C++, unde un
întreg poate  reprezentat pe 16, 32 sau 64 de biµi, în funcµie de arhitectura ma³inii, o valoare de
tip întreg în Java va ocupa întotdeauna 32 de biµi, indiferent de ma³ina pe care ruleaz . Aceast 
consecvenµ  este esenµial  deoarece o aceea³i aplicaµie va trebui s  ruleze pe ma³ini cu arhitectur 
pe 16, 32 sau 64 de biµi ³i s  produc  acela³i rezultat pe ecare ma³in  în parte.
Variabilele pot  iniµializate la declararea lor sau în momentul utiliz rii lor efective. Dac 
valoarea nu este specicat  explicit atunci variabila se iniµializeaz  cu o valoare iniµial  implicit .
Tabelul anterior prezint  câteva exemple în acest sens.
CAPITOLUL 2. ALGORITMI 19

Tip Dimensiune Valoare Valoare Valoare Cifre


(octeµi) minima maxima initiala semnicative
7 7
byte 1 2 2 1 0
15 15
short 2 2 2 1 0
31 31
int 4 2 2 1 0
63 63
long 8 2 2 1 0
oat 4 +-1.4E-45 +-3.4E+38 0 6-7
double 8 +-4.94E-324 +-1.79E+308 0 14-15
boolean 1 f alse
char 2 null
Tabela 2.1: Tipurile primitive de date în Java

Conversiile între diferitele tipuri sunt permise (acolo unde au semnicaµie). Se vede din tabel
c  unele tipuri de variabile au posibilitatea s  reprezinte un spectru mai mare de numere decât
altele.
În afara tipurilor de baz , limbajul Java suport  ³i tipuri de date create de utilizator, de
pild  variabile de tip clas , interfaµ  sau tablou. Ca ³i celelalte variabile, dac  nu sunt explicit
iniµializate, valoarea atribuit  implicit este null.
Modicatorul static este folosit pentru a specica faptul c  variabila are o singur  valoare,
comun  tuturor instanµelor clasei în care ea este declarat . Modicarea valorii acestei variabile
din interiorul unui obiect face ca modicarea s  e vizibil  din celelalte obiecte. Variabilele statice
sunt iniµializate la înc rcarea codului specic unei clase ³i exist  chiar ³i dac  nu exist  nici o
instanµ  a clasei respective. Din aceast  cauz , ele pot  folosite de metodele statice.
Tablourile unidimensionale se declar  sub forma:

$tip%[ ] $nume% =new $tip%[n];


sau

$tip% $nume%[ ] =new $tip%[n];


Elementele vectorului pot  accesate cu ajutorul unui indice, sub forma:

$nume%[i]
unde i poate lua orice valoare între 0 ³i n  1.
În cazul tablourilor bidimensionale, o declaraµie de forma:

$tip%[ ] [ ] $nume% = new $tip%[m][n];


sau

$tip% $nume% [ ] [ ] = new $tip%[m][n];


specic  o matrice cu m linii ³i n coloane. Fiecare element se specic  prin doi indici:

$nume%[i][j]
unde i reprezint  indicele liniei ³i poate avea orice valoare între 0 ³i m  1 iar j reprezint  indicele
coloanei ³i poate avea orice valoare între 0 ³i n  1.

2.5.2 Operaµii de intrare/ie³ire

Preluarea unei valori de tip int de la tastatur  se poate face sub forma:

BufferedReader br = new BufferedReader(


new InputStreamReader(System.in));
int vi=Integer.parseInt(br.readLine());
iar dintr-un ³ier (de exemplu fis.in), sub forma:
CAPITOLUL 2. ALGORITMI 20

StreamTokenizer st = new StreamTokenizer(


new BufferedReader(
new FileReader("fis.in")));
st.nextToken(); int vi = (int) st.nval;
Scrierea valorii unei variabile v pe ecran se poate face sub forma:
System.out.print(v);
iar într-un ³ier (de exemplu fis.out), sub forma:
PrintWriter out = new PrintWriter(
new BufferedWriter(
new FileWriter("fis.out")));
out.print(v);
out.close();

2.5.3 Prelucr ri liniare

O secvenµ  de prelucr ri se descrie în modul urm tor:


$instr_1%;
$instr_2%;
...
$instr_n%;
sau
$instr_1%; $instr_2%; ... $instr_n%;
O astfel de scriere indic  faptul c  în momentul execuµiei instrucµiunile se efectueaz  în ordinea
în care sunt specicate.

2.5.4 Prelucr ri alternative

O prelucrare alternativ  complet  (cu dou  ramuri) este descris  prin:


if ($condiµie%) $instr_1% else $instr_2%;
unde $condiµie% este o expresie relaµional . Aceast  prelucrare trebuie înµeleas  în modul urm -
tor: dac  condiµia este adev rat  atunci se efectueaz  prelucrarea $instr_1%, altfel se efectueaz 
$instr_2%.
O prelucrare alternativ  cu o singur  ramur  se descrie prin:
if ($condiµie%) $instr%;
iar execuµia ei are urm torul efect: dac  condiµia este satisfacut  atunci se efectueaz  instruc-
µiunea specicat , altfel nu se efectueaz  nici o prelucrare ci se trece la urm toarea prelucrare a
algoritmului.

2.5.5 Prelucr ri repetitive

Prelucr rile repetitive pot  de trei tipuri:


a cu test iniµial,
a cu test nal ³i
a cu contor.
Prelucrarea repetitiv  cu test iniµial se descrie prin:
while ($condiµie%) $instr%;
În momentul execuµiei, atât timp cât condiµia este adevarat , se va executa prelucrarea. Dac 
condiµia nu este la început satisf cut , atunci prelucrarea nu se efectueaz  niciodat .
Prelucrarea repetitiv  cu test nal se descrie prin:
CAPITOLUL 2. ALGORITMI 21

do $instr% while ($condiµie%);


Instrucµiunea se repet  pân  când condiµia specicat  devine fals . În acest caz prelucrarea se
efectueaz  cel puµin o dat , chiar dac  condiµia nu este satisfacut  la început.
Prelucrarea repetitiv  cu contor se caracterizeaz  prin repetarea prelucr rii de un num r pres-
tabilit de ori ³i este descris  prin:
for($instr1% ; $conditie%; $instr2%) $instr3%;
În general <instr1> reprezint  etapa de iniµializare a contorului, <instr2> reprezint  etapa
de incrementare a contorului, <instr3> reprezint  instrucµiunea care se execut  în mod repetat
cât timp condiµia <conditie> are valoarea true.

2.5.6 Subprograme

În cadrul unui program poate s  apar  necesitatea de a specica de mai multe ori ³i în diferite
locuri un grup de prelucr ri. Pentru a nu le descrie în mod repetat ele pot constitui o unitate
distinct , identicabil  printr-un nume, care este numit  subprogram sau, mai precis, funcµie (dac 
returneaz  un rezultat) sau procedur  (dac  nu returneaz  nici un rezultat). În Java funcµiile ³i
procedurile se numesc metode. Ori de câte ori este necesar  efectuarea grupului de prelucr ri
din cadrul programului se specic  numele acestuia ³i, eventual, datele curente asupra c rora se
vor efectua prelucrarile. Aceast  acµiune se nume³te apel al subprogramului, iar datele specicate
al turi de numele acestuia ³i asupra c rora se efectueaz  prelucrarile se numesc parametri.
Un subprogram poate  descris în felul urm tor:
$tipr% $nume_sp% ($tipp1% $numep1%, $tipp2% $numep2%, ... )
{
...
/* prelucr ri specice subprogramului */
...
return $nume_rezultat%;
}
unde $tipr% reprezint  tipul rezultatului returnat (void dac  subprogramul nu returneaz  nici
un rezultat), $nume_sp% reprezint  numele subprogramului, iar numep1, numep2, ... reprezint 
numele parametrilor. Ultimul enunµ, prin care se returneaz  rezultatul calculat în cadrul subpro-
gramului, trebuie pus numai dac  $tipr% nu este void.
Modul de apel depinde de modul în care subprogramul returneaz  rezultatele sale. Dac  sub-
programul returneaz  efectiv un rezultat, printr-un enunµ de forma
return $nume_rezultat%;
atunci subprogramul se va apela în felul urm tor:
v=$nume_sp%(nume_p1, nume_p2, ...);
Aceste subprograme corespund subprogramelor de tip funcµie.
Dac  în subprogram nu apare un astfel de enunµ, atunci el se va apela prin:
$nume_sp%(nume_p1, nume_p2, ...);
variant  care corespunde subprogramelor de tip procedur .
Observaµie. Prelucr rile care nu sunt detaliate în cadrul algoritmului sunt descrise în limbaj
natural sau limbaj matematic. Comentariile suplimentare vor  cuprinse între /* ³i */. Dac 
pe o linie a descrierii algoritmului apare simbolul // atunci tot ce urmeaz  dup  acest simbol,
pe aceea³i linie cu el, este interpretat ca ind un comentariu (deci, nu reprezint  o prelucrare a
programului).

2.5.7 Probleme rezolvate

1. Descompunere Fibonacci. S  se descompun  un num r natural, de cel mult 18-19 cifre, în


sum  de cât mai puµini termeni Fibonacci.
Rezolvare: Programul urm tor calculeaz  ³i a³eaz  primii 92 de termeni din ³irul Fibonacci
(mai mult nu este posibil f r  numere mari!), ³i descompune num rul x introdus de la tastatur .
CAPITOLUL 2. ALGORITMI 22

Metoda static int maxFibo ( long nr ) returneaz  indicele celui mai mare element din
³irul lui Fibonacci care este mai mic sau egal cu parametrul nr.

Listing 2.5.1: DescFibo.java


1 import java.io.*;
2 class DescFibo
3 {
4 static int n=92;
5 static long[] f=new long[n+1];
6
7 public static void main (String[]args) throws IOException
8 {
9 long x,y;
10 int iy, k, nrt=0;
11
12 BufferedReader br = new BufferedReader(
13 new InputStreamReader(System.in));
14 System.out.print("x = ");
15 x=Long.parseLong(br.readLine());
16 f[0]=0; f[1]=1; f[2]=1;
17 for(k=3;k<=n;k++) f[k]=f[k-1]+f[k-2];
18 for(k=0;k<=n;k++) System.out.println(k+" : "+f[k]);
19 System.out.println(" "+Long.MAX_VALUE+" = Long.MAX_VALUE");
20 System.out.println("x = "+x);
21 while(x>0)
22 {
23 iy=maxFibo(x);
24 y=f[iy];
25 nrt++;
26 System.out.println(nrt+" : "+x+" f["+iy+"] = "+y);
27 x=x-y;
28 }
29 }
30
31 static int maxFibo(long nr)
32 {
33 int k;
34 for(k=1;k<=n;k++) if (f[k]>nr) break;
35 return k-1;
36 }
37 }

De exemplu, pentru x = 5678 pe ecran apare:


1 : 5678 f[19] = 418
2 : 1497 f[16] = 987
3 : 510 f[14] = 377
4 : 133 f[11] = 89
5 : 44 f[9] = 34
6 : 10 f[6] = 8
7 : 2 f[3] = 2

n n 2
2. Fie Sn x1  x2 unde x1 ³i x2 sunt r d cinile ecuaµiei cu coecienµi întregi ax  bx  c 0
(vom considera a 1!). S  se a³eze primii 10 termeni ai ³irului Sn ³i s  se precizeze în dreptul
ec rui termen dac  este num r prim, iar dac  nu este num r prim s  se a³eze descompunerea
în factori.
Rezolvare:

class e02
{
public static void main(String[] args)
{
int a, b, c, nnp=0, s, p, n=10, k;
long[] ss=new long[n+1];

a=1;b=1;c=2;
s=-b/a;
p=c/a;
CAPITOLUL 2. ALGORITMI 23

ss[1]=s;
ss[2]=s*s-2*p;
for(k=3;k<=n;k++) ss[k]=s*ss[k-1]-p*ss[k-2];
for(k=1;k<=n;k++)
if(esteprim(Math.abs(ss[k])))
System.out.println(k+" : "+ss[k]+" PRIM "+(++nnp));
else
{
System.out.print(k+" : "+ss[k]+" = ");
descfact(Math.abs(ss[k]));
}
System.out.println("nnp = "+nnp);
}// main

static void descfact(long nr)


{
long d=2;
if((nr==0)||(nr==1)){System.out.println(); return;}
while(nr%d==0){System.out.print(d+""); nr=nr/d;}
d=3;
while((d*d<=nr)&&(nr!=1))
{
while(nr%d==0){System.out.print(d+" "); nr=nr/d;}
d=d+2;
}
if(nr!=1) System.out.println(nr);
else System.out.println();
}

static boolean esteprim(long nr)


{
if((nr==0)||(nr==1)) return false;
if((nr==2)||(nr==3)) return true;
if(nr%2==0) return false;
long d=3;
while((nr%d!=0)&&(d*d<=nr)) d=d+2;
if(nr%d==0) return false; else return true;
}
}// class
Pe ecran apar urm toarele rezultate:
1 : -1 =
2 : -3 PRIM 1
3 : 5 PRIM 2
4 : 1 =
5 : -11 PRIM 3
6 : 9 = 3 3
7 : 13 PRIM 4
8 : -31 PRIM 5
9 : 5 PRIM 6
10 : 57 = 3 19
nnp = 6
Press any key to continue...

αx
3. Se consider  funcµia f x P xe unde P x este un polinom de grad n cu coecienµi
întregi. S  se a³eze toate derivatele pân  la ordinul m ale funcµiei f , ³i, în dreptul coecienµilor
polinoamelor care apar în aceste derivate, s  se precizeze dac  respectivul coecient este num r
prim, iar dac  nu este num r prim s  se a³eze descompunerea în factori. De asemenea, s  se
a³eze care este cel mai mare num r prim care apare, ³i care este ordinul derivatei în care apare
acest cel mai mare num r prim.
CAPITOLUL 2. ALGORITMI 24

αx
Rezolvare: Derivata funcµiei f are forma Q xe unde Q este un polinom de acela³i grad cu
polinomul P . Toat  rezolvarea problemei se reduce la determinarea coecienµilor polinomului Q
în funcµie de coecienµii polinomului P .

class e03
{
static long npmax=1,pmax=0;

public static void main(String[] args)


{
int n=7, m=10, alfa=1, k;
long[] p=new long[n+1];
p[7]=1; p[3]=1; p[0]=1;
afisv(p,0);
for(k=1;k<=m;k++)
{
System.out.print("derivata = "+k);
p=deriv(p,alfa);
afisv(p,k);
}
System.out.println(npmax+" "+pmax);
System.out.println("GATA!!!");
}

static long[] deriv(long[] a,int alfa)


{
int n=a.length-1, k;
long[] b=new long[n+1];
b[n]=a[n]*alfa;
for(k=0;k<=n-1;k++) b[k]=(k+1)*a[k+1]+a[k]*alfa;
return b;
}

static void afisv(long[] x,int ppp)


{
int n=x.length-1;
int i;
System.out.println();
for(i=n;i>=0;i--)
if(esteprim(Math.abs(x[i])))
{
System.out.println(i+" : "+x[i]+" PRIM ");
if(npmax<Math.abs(x[i]))
{
npmax=Math.abs(x[i]);
pmax=ppp;
}
}
else
{
System.out.print(i+" : "+x[i]+" = ");
descfact(Math.abs(x[i]));
}
System.out.println();
}

static void descfact(long nr)


{
long d=2;
if((nr==0)||(nr==1))
{
CAPITOLUL 2. ALGORITMI 25

System.out.println();
return;
}
while(nr%d==0)
{
System.out.print(d+" ");
nr=nr/d;
}
d=3;
while((d*d<=nr)&&(nr!=1))
{
while(nr%d==0)
{
System.out.print(d+" ");
nr=nr/d;
}
d=d+2;
}
if(nr!=1) System.out.println(nr);
else System.out.println();
}

static boolean esteprim(long nr)


{
if((nr==0)||(nr==1)) return false;
if((nr==2)||(nr==3)) return true;
if(nr%2==0) return false;
long d=3;
while((nr%d!=0)&&(d*d<=nr)) d=d+2;
if(nr%d==0) return false; else return true;
}
}// class

4. R d cini raµionale. S  se determine toate r d cinile raµionale ale unei ecuaµii cu coecienµi
întregi.
Rezolvare: Se caut  r d cini raµionale formate din fracµii în care num r torul este divizor
al termenului liber iar numitorul este divizor al termenului dominant. Programul care urmeaz 
genereaz  coecienµii ecuaµiei, plecând de la fracµii date (ca r d cini), ³i apoi determin  r d cinile
raµionale

class RadaciniRationale // generez p_i/q_i


{
static int k=0;

public static void main(String[] args)


{
int[] p={1,1,2,3, 3, 1}, q={2,3,3,2,-2,-1};
int[] a=genPol(p,q);
int n=a.length-1,alfa,beta;
int moda0=Math.abs(a[0]),modan=Math.abs(a[n]);
for(alfa=1;alfa<=moda0;alfa++)
{
if(moda0%alfa!=0) continue;
for(beta=1;beta<=modan;beta++)
{
if(modan%beta!=0) continue;
if(cmmdc(alfa,beta)!=1) continue;
if (f(a,alfa,beta)==0)
System.out.println("x["+(++k)+"] = "+alfa+"/"+beta+" ");
if (f(a,-alfa,beta)==0)
CAPITOLUL 2. ALGORITMI 26

System.out.println("x["+(++k)+"] = -"+alfa+"/"+beta+" ");


}// for beta
}// for alfa
}// main

static int[] genPol(int[] a, int[] b) // X-a_i/b_i==>b_i X - a_i


{
int n=a.length;
int[] p={-a[0],b[0]},//p=b[0] X -a[0]
q={13,13}; // q initializat "aiurea" - pentru dimensiune !
afisv(p);
for(int k=1;k<n;k++)
{
q[0]=-a[k];
q[1]=b[k];
p=pxq(p,q);
afisv(p);
}
return p;
}// genPol()

static int[] pxq(int[] p,int[] q)


{
int gradp=p.length-1, gradq=q.length-1;
int gradpq=gradp+gradq;
int[] pq=new int[gradpq+1];
int i,j,k;
for(k=0;k<=gradpq;k++) pq[k]=0;
for(i=0;i<=gradp;i++)
for(j=0;j<=gradq;j++) pq[i+j]+=p[i]*q[j];
return pq;
}

static int f(int[]a,int alfa, int beta)


{
int n=a.length-1,k,s=0;
for(k=0;k<=n;k++) s+=a[k]*putere(alfa,k)*putere(beta,n-k);
return s;
}

static int putere(int a, int n)


{
int p=1;
for(int k=1;k<=n;k++) p*=a;
return p;
}

static int cmmdc(int a, int b)


{
int d,i,c,r;
if (a>b) {d=a; i=b;} else {d=b; i=a;}
r=123; // ca sa inceapa while !!!
while (r > 0){c=d/i; r=d%i; d=i; i=r;}
return d;
}

static void afisv(int[] a)


{
for(int i=a.length-1;i>=0;i--) System.out.print(a[i]+" ");
System.out.println();
CAPITOLUL 2. ALGORITMI 27

}// afisv()
}// class

5. S  se a³eze frecvenµa cifrelor care apar în

= 21 C
n
n
f n k nk
k 0

n
neµinând cont de faptul c  f n are o expresie mult mai simpl , ³i anume 2 . Suma trebuie
calculat  simulând operaµiile de adunare, înmulµire ³i împ rµire la 2, cu numere mari.
Rezolvare: Funcµia se pune sub forma:

=2
n
1 nk n
f n Cnk
2n
k 0

Se calculeaz  suma, ³i apoi se fac n împ rµiri succesive la 2.

class e05
{
public static void main (String[]args)
{
int n, k;
int[] s;
int[] p;
for(n=10;n<=12;n++)
{
s=nrv(0);
for(k=0;k<=n;k++)
{
p=inm(comb(n+k,n),putere(2,n-k));
s=suma(s,p);
}
afisv(s);
for(k=1;k<=n;k++) s=impartLa2(s);
System.out.print(n+" : ");
afisv(s);
fcifre(s);
}
System.out.println("GATA");
}//main()

static int[] impartLa2(int[] a)


{
int na,nb,k,t=0;
na=a.length-1;
if(a[na]==1) nb=na-1; else nb=na;
int[] b=new int[nb+1];
if(na==nb)
for(k=na;k>=0;k--) {a[k]+=10*t; b[k]=a[k]/2; t=a[k]%2;}
else
{
t=a[na];
for(k=na-1;k>=0;k--){a[k]+=10*t; b[k]=a[k]/2; t=a[k]%2;}
}
return b;
}

static void fcifre(int[] x)


{
int i;
CAPITOLUL 2. ALGORITMI 28

int[] f=new int[10];


for(i=0;i<x.length;i++) f[x[i]]++;
System.out.println();
for(i=0;i<=9;i++) System.out.println(i+" : "+f[i]);
System.out.println();
}

static int[] suma(int[] x, int[] y)


{
int i, j, t, ncx=x.length, ncy=y.length, ncz;
if(ncx>ncy) ncz=ncx+1; else ncz=ncy+1;
int[] xx=new int[ncz];
int[] yy=new int[ncz];
int[] z=new int[ncz];
for(i=0;i<ncx;i++) xx[i]=x[i];
for(j=0;j<ncy;j++) yy[j]=y[j];
t=0;
for(i=0;i<ncz;i++){z[i]=xx[i]+yy[i]+t; t=z[i]/10; z[i]=z[i]%10;}
if(z[ncz-1]!= 0) return z;
else
{
int[]zz=new int[ncz-1];
for(i=0;i<=ncz-2;i++) zz[i]=z[i];
return zz;
}
}

static int[] inm(int[]x,int[]y)


{
int t, n=x.length, m=y.length, i, j;
int[][]a=new int[m][n+m];
int[]z=new int[m+n];
for(j=0;j<m;j++)
{
t=0;
for(i=0;i<n;i++)
{
a[j][i+j]=y[j]*x[i]+t;
t=a[j][i+j]/10;
a[j][i+j]=a[j][i+j]%10;
}
a[j][i+j]=t;
}
t=0;
for(j=0;j<m+n;j++)
{
z[j]=0;
for(i=0;i<m;i++) z[j]=z[j]+a[i][j];
z[j]=z[j]+t;
t=z[j]/10;
z[j]=z[j]%10;
}
if(z[m+n-1]!= 0) return z;
else
{
int[]zz=new int[m+n-1];
for(i=0;i<=m+n-2;i++)
zz[i]=z[i];
return zz;
}
CAPITOLUL 2. ALGORITMI 29

static void afisv(int[]x)


{
int i;
for(i=x.length-1;i>=0;i--) System.out.print(x[i]);
System.out.print(" *** "+x.length);
System.out.println();
}

static int[] nrv(int nr)


{
int nrrez=nr, nc=0;
while(nr!=0) {nc++; nr=nr/10;}
int[]x=new int [nc];
nr=nrrez;
nc=0;
while(nr!=0){x[nc]=nr%10; nc++; nr=nr/10;}
return x;
}

static int[] putere (int a, int n)


{
int[] rez;
int k;
rez=nrv(1);
for(k=1;k<=n;k++) rez=inm(rez,nrv(a));
return rez;
}

static int[] comb (int n, int k)


{
int[] rez;
int i, j, d;
int[]x=new int[k+1];
int[]y=new int[k+1];
for(i=1;i<=k;i++) x[i]=n-k+i;
for(j=1;j<=k;j++) y[j]=j;
for(j=2;j<=k;j++)
{
for(i=1;i<=k;i++)
{
d=cmmdc(y[j],x[i]);
y[j]=y[j]/d;
x[i]=x[i]/d;
if(y[j]==1) break;
}
}
rez=nrv(1);
for(i=1;i<=k;i++) rez=inm(rez,nrv(x[i]));
return rez;
}

static int cmmdc (int a,int b)


{
int d,i,c,r;
if (a>b) {d=a;i=b;} else{d=b;i=a;}
while (i!=0){c=d/i; r=d%i; d=i; i=r;}
return d;
}
CAPITOLUL 2. ALGORITMI 30

}// class

6. S  se a³eze S n, 1, S n, 2, ..., S n, m (inclusiv suma cifrelor ³i num rul cifrelor pentru
ecare num ) ³tiind c 
S n  1, m S n, m  1  mS n, m
³i
S n, 1 S n, n 1, ¾n ' m.
Se vor implementa operaµiile cu numere mari.
Rezolvare: Matricea de calcul este subdiagonal . Se completeaz  cu 1 prima coloan  ³i diago-
nala principal , iar apoi se determin  celelalte elemente ale matricei folosind relaµia dat  (aranjat 
puµin altfel!). Matricea de calcul va avea de fapt trei dimensiuni (numerele devin foarte mari, a³a
c  elementul Si,j trebuie s  conµin  vectorul cifrelor valorii sale).

class e06
{
public static void main(String[] args)
{
int n=50, m=40, i, j;
int[][][] s=new int[n+1][m+1][1];
for(i=1;i<=n;i++)
{
if(i<=m) s[i][i]=nr2v(1);
s[i][1]=nr2v(1);
for(j=2;j<=min(i,m);j++)
s[i][j]=suma(s[i-1][j-1],inm(nr2v(j),s[i-1][j]));
if(i<=m) s[i][i]=nr2v(1);
}
for(i=1;i<=m;i++)
{
System.out.print("\n"+i+" : "+s[n][i].length+" ");
afissumac(s[n][i]);
afisv(s[n][i]);
}
}

static int[] suma(int[] x,int[] y){...}


static int[] nr2v(int nr){...}
static int[] inm(int[]x, int[]y){...}
static void afisv(int[]x){...}

static void afissumac(int[]x)


{
int i,s=0;
for(i=x.length-1;i>=0;i--) s+=x[i];
System.out.print(s+" ");
}

static int min(int a, int b) { return (a<b)?a:b; }


}// class

Pe ecran apar urm toarele valori (numerele devin foarte mari!):


1 : 1 1 1
2 : 15 64 562949953421311
3 : 24 102 119649664052358811373730
4 : 29 138 52818655359845224561907882505
5 : 33 150 740095864368253016271188139587625
6 : 37 170 1121872763094011987454778237712816687
7 : 39 172 355716059292752464797065038013137686280
8 : 41 163 35041731132610098771332691525663865902850
CAPITOLUL 2. ALGORITMI 31

9 : 43 189 1385022509795956184601907089700730509680195
10 : 44 205 26154716515862881292012777396577993781727011
11 : 45 177 267235754090021618651175277046931371050194780
12 : 46 205 1619330944936279779154381745816428036441286410
13 : 46 232 6238901276275784811492861794826737563889288230
14 : 47 205 16132809270066494376125322988035691981158490930
15 : 47 162 29226457001965139089793853213126510270024300000
16 : 47 216 38400825365495544823847807988536071815780050940
17 : 47 198 37645241791600906804871080818625037726247519045
18 : 47 225 28189332813493454141899976735501798322277536165
19 : 47 165 16443993651925074352512402220900950019217097000
20 : 46 237 7597921606860986900454469394099277146998755300
21 : 46 198 2820255028563506149657952954637813048172723380
22 : 45 189 851221883077356634241622276646259170751626380
23 : 45 198 211092494149947371195608696099645107168146400
24 : 44 192 43397743800247894833556570977432285162431400
25 : 43 168 7453802153273200083379626234837625465912500
26 : 43 186 1076689601597672801650712654209772574328212
27 : 42 189 131546627365808405813814858256465369456080
28 : 41 155 13660054661277961013613328658015172843800
29 : 40 165 1210546686654900169010588840430963387720
30 : 38 185 91860943867630642501164254978867961752
31 : 37 155 5985123385551625085090007793831362560
32 : 36 164 335506079163614744581488648870187520
33 : 35 153 16204251384884158932677856617905110
34 : 33 144 674833416425711522482381379544960
35 : 32 126 24235536318546124501501767693750
36 : 30 135 750135688292101886770568010795
37 : 29 141 19983209983507514547524896035
38 : 27 132 457149347489175573737344245
39 : 25 114 8951779743496412314947000
40 : 24 93 149377949042637543000150

7. S  se a³eze B1 , B2 , ..., Bn ³tiind c 

= C B ,B
n
k
Bn1 n k 0 1.
k 0

Se vor implementa operaµiile cu numere mari.


Rezolvare: Vectorul de calcul va avea de fapt dou  dimensiuni (numerele devin foarte mari,
a³a c  elementul Bi trebuie s  conµin  vectorul cifrelor valorii sale).

class e07
{
public static void main(String[] args)
{
int n=71; // n=25 ultimul care incape pe long
int k,i;
int[][] b=new int[n+1][1];
int[] prod={1};

b[0]=nr2v(1);
for(i=1;i<=n;i++)
{
b[i]=nr2v(0);;
for(k=0;k<=i-1;k++)
{
prod=inm(comb(i-1,k),b[k]);
b[i]=suma(b[i],prod);
}
CAPITOLUL 2. ALGORITMI 32

System.out.print(i+" : ");
afisv(b[i]);
}
System.out.println(" "+Long.MAX_VALUE);
System.out.println("... Gata ...");
}

static int[] suma(int[] x,int[] y){...}


static int[] nr2v(int nr){...}
static int[] inm(int[]x, int[]y){...}
static void afisv(int[]x){...}

static int[] comb(int n,int k)


{
int i,j,d;
int[] rez;
int[] x=new int[k+1];
int[] y=new int[k+1];
for(i=1;i<=k;i++) x[i]=n-k+i;
for(j=1;j<=k;j++) y[j]=j;
for(j=2;j<=k;j++)
for(i=1;i<=k;i++)
{
d=cmmdc(y[j],x[i]);
y[j]=y[j]/d;
x[i]=x[i]/d;
if(y[j]==1) break;
}
rez=nr2v(1);
for(i=1;i<=k;i++) rez=inm(rez,nr2v(x[i]));
return rez;
}

static int cmmdc(int a,int b) {...}


}

2.5.8 Probleme propuse

n n n
1. Fie Sn x1  x2  x3 unde x1 , x2 ³i x3 sunt r d cinile ecuaµiei cu coecienµi întregi
3 2
ax  bx  cx  d 0 (vom considera a 1!). S  se a³eze primii 10 termeni ai ³irului Sn ³i s 
se precizeze în dreptul ec rui termen dac  este num r prim, iar dac  nu este num r prim s  se
a³eze descompunerea în factori.

2. S  se a³eze frecvenµa cifrelor care apar în

=C
n1
k n1k
f n n1 n k  1!
k 0

n
neµinând cont de faptul c  f n are o expresie mult mai simpl , ³i anume n . Suma trebuie
calculat  simulând operaµiile cu numere mari.

3. S  se a³eze frecvenµa cifrelor care apar în

=C k
n1
n1 k k1 nk
f n n  n n  k
k 1

n
neµinând cont de faptul c  f n are o expresie mult mai simpl , ³i anume n . Suma trebuie
calculat  simulând operaµiile cu numere mari.
CAPITOLUL 2. ALGORITMI 33

4. S  se calculeze
1 1 1
f n n 1  p
1  p
... 1  p

1 2 m

i i i
unde n p11 p22 ...pmm reprezint  descompunerea în factori primi a lui n.

5. S  se calculeze

φ n card rk " N©1 & k & n, cmmdc k, n 1x .

=φ n
6. S  se calculeze
f n 
d¶n

unde φ este funcµia de la exerciµiul anterior, neµinând cont de faptul c  f n are o expresie mult
mai simpl , ³i anume n.

7. S  se calculeze n
1 1 1
f n n! 1    ... 
.
1! 2! n!

8. S  se calculeze

=
m
mk k 1 λ1 2 λ2 n λn
f m, n, λ1 , λ2 , ..., λn  1 Cm Ck  Ck1  ... Ckn1  .
k 1

9. S  se calculeze
1 λ1 2 λ2 n λn
g m, n, λ1 , λ2 , ..., λn  Cm  Cm1  ... Cmn1 

implementând operaµiile cu numere mari.

10. S  se calculeze
1 1 2 2 n n
f n  2n!  Cn 2 2n  1!  Cn 2 2n  2!  ...  1 2 n! .
2n

11. S  se calculeze
1 n
Cn C
n  1 2n
implementând operaµiile cu numere mari.

12. S  se a³eze P 100, 50 (inclusiv suma cifrelor ³i num rul cifrelor) ³tiind c 

P n  k, k  P n, 1  P n, 2  ...  P n, k 

³i
P n, 1 P n, n 1, ¾n ' k ' 1.
Se vor implementa operaµiile cu numere mari.

r
13. S  se determine cel mai mic num r natural r, astfel încât p e, unde p este o permutare
dat  ³i e este permutarea identic .

14. S  se a³eze C100 ³tiind c 

=C
n
Cn k1 Cnk , C0 1.
k 1

Se vor implementa operaµiile cu numere mari.


CAPITOLUL 2. ALGORITMI 34

15. S  se a³eze E100 ³tiind c 

En E2 En1  E3 En2  ...  En1 E2 , E1 E2 1.

Se vor implementa operaµiile cu numere mari.

16. S  se calculeze
=
m1
1 k k n
S n, m 1 Cm m  k 
m!
k 0

17. S  se a³eze C100 ³tiind c 


=C F .
n
k
Cn n k
k 1

unde Fk este termen Fibonacci. Se vor implementa operaµiile cu numere mari.

18. S  se a³eze C100 ³tiind c 


=C 2 F .
n
k k
Cn n k
k 1

unde Fk este termen Fibonacci. Se vor implementa operaµiile cu numere mari.

19. S  se determine puterea a zecea a unui polinom dat.


Capitolul 3

Analiza complexit µii algoritmilor

3.1 Scopul analizei complexit µii


În general exist  mai multi algoritmi care rezolv  aceea³i problem . Dorim s  exprim m
ecienµa algoritmilor sub forma unui criteriu care s  ne permit  s  alegem din mai mulµi algoritmi
pe cel optim. Exist  mai multe moduri în care putem exprima ecienµa: prin timpul necesar
pentru execuµia algoritmului sau prin alte resurse necesare (de exemplu memoria). În ambele
cazuri îns , avem o dependenµ  de dimensiunea cazului studiat.
Se pune problema de alegere a unei unit µi de m sur  pentru a exprima ecienµa teoretic  a
unui algoritm. O importanµ  deosebit  în rezolvarea acestei probleme o are principiul invarianµei.
Acesta ne arat  c  nu este necesar s  folosim o astfel de unitate.
Principiul invarianµei: dou  implement ri diferite ale aceluia³i algoritm nu difer  în e-
cienµ  cu mai mult de o constant  multiplicativ .
Implementarea unui algoritm presupune elementele legate de calculatorul folosit, de limbajul
de programare si îndemânarea programatorului (cu condiµia ca acesta s  nu modice algoritmul).
Datorit  principiului invarianµei vom exprima ecienµa unui algoritm în limitele unei constante
multiplicative.
Un algoritm este compus din mai multe instrucµiuni, care la rândul lor sunt compuse din mai
multe operaµii elementare. Datorit  principiului invarianµei nu ne intereseaz  timpul de execuµie
a unei operaµii elementare, ci numai num rul lor, dar ne intereseaz  care ³i ce sunt operaµiile
elementare.

Deniµia 1. O operaµie elementar  este o operaµie al c rui timp de execuµie poate  m rginit
superior de o constant  care depinde numai de particularitatea implement rii (calculator, limbaj
de programare etc).

Deoarece ne intereseaz  timpul de executie în limita unei constante multiplicative, vom consi-
dera doar num rul operaµiilor elementare executate într-un algoritm, nu ³i timpul exact de execuµie
al operaµiilor respective.
Este foarte important ce anume denim ca operaµie elementar . Este adunarea o operaµie
elementara? Teoretic nu este, pentru c  depinde de lungimea celor doi operanzi. Practic, pentru
operanzi de lungime rezonabil  putem s  consider m c  adunarea este o operaµie elementar . Vom
considera în continuare c  adun rile, sc derile, înmulµirile, împ rµirile, operaµiile modulo (restul
împ arµirii întregi), operaµiile booleene, comparaµiile ³i atribuirile sunt operaµii elementare.
Uneori ecienµa difer  dac  µinem cont numai de unele operaµii elementare ³i le ignor m pe
celelalte (de exemplu la sortare: comparaµia ³i interschimbarea). De aceea în analiza unor algoritmi
vom considera o anumit  operaµie elementar , care este caracteristic  algoritmului, ca operaµie
barometru, neglijându-le pe celelalte.
De multe ori, timpul de executie al unui algoritm poate varia pentru cazuri de m rime identic .
De exemplu la sortare, dac  introducem un ³ir de n numere gata sortat, timpul necesar va cel mai
mic dintre timpii necesari pentru sortarea oricarui alt ³ir format din n numere. Spunem c  avem
de-a face cu cazul cel mai favorabil. Dac  ³irul este introdus în ordine invers , avem cazul cel mai
defavorabil ³i timpul va  cel mai mare dintre timpii de sortare a ³irului de n numere.
Exist  algoritmi în care timpul de execuµie nu depinde de cazul considerat.
Dac  dimensiunea problemei este mare, îmbun t µirea ordinului algoritmului este esenµial , în
timp ce pentru timpi mici este sufcient  performanµa hardware.

35
CAPITOLUL 3. ANALIZA COMPLEXITۥII ALGORITMILOR 36

Elaborarea unor algoritmi ecienµi presupune cuno³tinµe din diverse domenii (informatic ,
matematic  ³i cuno³tiinµe din domeniul c ruia îi aparµine problema practic  a c rui model este
studiat, atunci când este cazul).

Exemplul 1. Elaboraµi un algoritm care returneaz  cel mai mare divizor comun (cmmdc) a doi
termeni de rang oarecare din ³irul lui Fibonacci.

“irul lui Fibonacci, fn fn1  fn2 , este un exemplu de recursivitate în cascad  ³i calcularea
efectiv  a celor doi termeni fm fn , urmat  de calculul celui mai mare divizor al lor, este total
neindicat . Un algoritm mai bun poate  obµinut dac  µinem seama de rezultatul descoperit de
Lucas în 1876:
cmmdc fm , fn  fcmmdc m,n
Deci putem rezolva problema calculând un singur termen al ³irului lui Fibonacci.
Exist  mai mulµi algoritmi de rezolvare a unei probleme date. Prin urmare, se impune o analiz 
a acestora, în scopul determin rii ecienµei algoritmilor de rezolvare a problemei ³i pe cât posibil a
optimalit µii lor. Criteriile în funcµie de care vom stabili ecienµa unui algoritm sunt complexitatea
spaµiu (memorie utilizat ) ³i complexitatea timp (num rul de operaµiilor elementare).

3.1.1 Complexitatea spaµiu


Prin complexitate spaµiu înµelegem dimensiunea spaµiului de memorie utilizat de program.
Un program necesit  un spaµiu de memorie constant, independent de datele de intrare, pentru
memorarea codului, a constantelor, a variabilelor ³i a structurilor de date de dimensiune constant 
alocate static ³i un spaµiu de memorie variabil, a c rui dimensiune depinde de datele de intrare,
constând din spaµiul necesar pentru structurile de date alocate dinamic, a c ror dimensiune de-
pinde de instanµa problemei de rezolvat ³i din spaµiul de memorie necesar apelurilor de proceduri
³i funcµii.
Progresele tehnologice fac ca importanµa criteriului spaµiu de memorie utilizat s  scad , prio-
ritar devenind criteriul timp.

3.1.2 Complexitatea timp


Prin complexitate timp înµelegem timpul necesar execuµiei programului.
Înainte de a evalua timpul necesar execuµiei programului ar trebui s  avem informaµii detaliate
despre sistemul de calcul folosit.
Pentru a analiza teoretic algoritmul, vom presupune c  se lucreaz  pe un calculator "clasic", în
sensul c  o singur  instrucµiune este executat  la un moment dat. Astfel, timpul necesar execuµiei
programului depinde numai de num rul de operaµii elementare efectuate de algoritm.
Primul pas în analiza complexit µii timp a unui algoritm este determinarea operaµiilor elemen-
tare efectuate de algoritm ³i a costurilor acestora.
Consider m operaµie elementar  orice operaµie al c rei timp de execuµie este independent de
datele de intrare ale problemei.
Timpul necesar execuµiei unei operaµii elementare poate  diferit de la o operaµie la alta, dar
este xat, deci putem spune c  operaµiile elementare au timpul m ginit superior de o constant .
F r  a restrânge generalitatea, vom presupune c  toate operaµiile elementare au acela³i timp
de execuµie, ind astfel necesar  doar evaluarea num rului de operaµii elementare, nu ³i a timpului
total de execuµie a acestora.
Analiza teoretic  ignor  factorii care depind de calculator sau de limbajul de programare ales
³i se axeaz  doar pe determinarea ordinului de m rime a num rului de operaµi elementare.
Pentru a analiza timpul de execuµie se folose³te deseori modelul Random Access Machine
(RAM), care presupune: memoria const  într-un ³ir innit de celule, ecare celul  poate stoca cel
mult o dat , ecare celul  de memorie poate  accesat  într-o unitate de timp, instrucµiunile sunt
executate secvenµial ³i toate instrucµiunile de baz  se execut  într-o unitate de timp.
Scopul analizei teoretice a algoritmilor este de fapt determinarea unor funcµii care s  limiteze
superior, respectiv inferior comportarea în timp a algoritmului. Funcµiile depind de caracteristicile
relevante ale datelor de intrare.

3.2 Notaµia asimptotic 


CAPITOLUL 3. ANALIZA COMPLEXITۥII ALGORITMILOR 37

3.2.1 Denire ³i propriet µi


Deniµia 2. Numim ordinul lui f, mulµimea de funcµii

O f rt  N R ¶¿c % 0, ¿n0 " N a.î. t n & cf n, ¾n % n0 x (3.2.1)

Rezult  c  O f  este mulµimea tuturor funcµiilor m rginite superior de un multiplu real pozitiv
al lui f , pentru valori sucient de mari ale argumentului.
Dac  t n " O f  vom spune c  t este de ordinul lui f sau în ordinul lui f .
Fie un algoritm dat ³i o funcµie t  N R , astfel încât o anumit  implementare a algoritmului
s  necesite cel mult t n unit µi de timp pentru a rezolva un caz de marime n.
Principiul invarianµei ne asigur  c  orice implementare a algoritmului necesit  un timp în
ordinul lui t. Mai mult, acest algoritm necesit  un timp în ordinul lui f pentru orice functie
f N R pentru care t " O f . În particular t " O t. Vom c uta s  g sim cea mai simpl 
funcµie astfel încât t " O f .
Pentru calculul ordinului unei funcµii sunt utile urm toarele propriet µi:
Proprietatea 1. O f  ¿ f " O g
O g ³i "O
g f
Proprietatea 2. O f  L O g  ¿ f " O g  ³i g Š O f
Proprietatea 3. O f  g  O max f, g 
Pentru calculul mulµimilor O f  ³i O g  este util  proprietatea urm toare:
Proprietatea 4. Fie f, g  N R

. Atunci

n
lim
f n
™ g n
"R 
 O f O g

lim
n ™
f n
g n
0  O f  L O g.
Reciproca nu este în general valabil .
2
Fie de exemplu, t n n  3n  2, atunci

¼O n
2
n  3n  2 2 2
lim 1  3n  2 O n 
n ™ n2

¼O n
2
n  3n  2
3n  2 L O n 
2 3
lim 0 
n ™ n3
1
lim
n ™
ln n
Ó
n
lim
n ™
n
1
Ó
lim Ó
n ™ n
2
0 ¼ O ln n L O Ón
2 n
Ó
dar O n L © O ln n
m
Dac  p este un polinom de gradul m în variabila n, atunci O p O n .
Notaµia asimptotic  dene³te o relaµie de ordine parµial  între funcµii.
Pentru f, g  N R not m f T g dac  O f  N O g .
˜

Aceast  relaµie are propriet µile corespunz toare unei relaµii de ordine, adic :


a) reexivitate: f T f
b) antisimetrie: dac  f T g ³i g T f atunci f g
c) tranzitivitate: f T g ³i g T h, implic  f T h.
Dar nu este o relaµie de ordine! Exist  ³i funcµii astfel încât f T © g (f Š O g ) ³i g T
© f
(g Š O f ). De exemplu f n n, g n n
1sin n
.
Putem deni ³i o relaµie de echivalenµ : f  g , dac  O f  O g . În mulµimea O f  putem
înlocui orice funcµie cu o funcµie echivalent  cu ea. De exemplu: ln n  log n  log2 n.
Notând cu O 1 mulµimea funcµiilor m rginite superior de o constant  ³i considerând m " N
, m ' 2, obµinem ierarhia:
Ó
O 1 L O log n L O n L O n L O n log n L O n  L O 2  L O n!
m n

³i evident O n  L O n  L ... L O n  pentru m ' 4.


2 3 m

Aceast  ierarhie corespunde ierarhiei algoritmilor dup  criteriul performanµei. Pentru o pro-
blem  dat , dorim sa realiz m un algoritm cu un ordin situat cât mai în stânga în aceast  ierarhie.
CAPITOLUL 3. ANALIZA COMPLEXITۥII ALGORITMILOR 38

Notatia O f  este pentru a delimita superior timpul necesar unui algoritm.


Not m TA n timpul necesar execuµiei algoritmului A.
˜
Fie f  N R o funcµie arbitrar . Spunem c  algoritmul este de ordinul lui f n (³i not m
TA n " O f n), dac  ³i numai dac  exist  c % 0 ³i n0 " N, astfel încât TA n & c f n,
¾n ' n0 .
De exemplu:
a) Dac  TA n 3n  2, atunci TA n " O n, pentru c  3n  2 & 4n, ¾n ' 2.
Mai general, dac  TA n a n  b, a % 0, atunci TA n " O n pentru c  exist  c a  1 % 0
³i n0 b " N, astfel încât a n  b & a  1 n, ¾n ' b.
b) Dac  TA n 10n  4n  2, atunci TA n " O n , pentru c  10n  4n  2 & 11n , ¾n ' 5.
2 2 2 2

Mai general, dac  T A n an  bn  c, a % 0, atunci T A n " O n , pentru c  an  bn  c &


2 2 2

a  1n , ¾n ' max b, c  1.


2

c) Dac  TA n 6 2  n , atunci T A n " O 2 , pentru c  TA n & 7 2 ,¾n ' 4.


n 2 n n

 ...  a1 n  a0 , atunci T A n " O n . Aceasta rezult  din:


k k1 k
Dac  TA n ak n  ak1 n
 ...  a1 n  a0 ¶ & ¶ak ¶n  ¶ak1 ¶n  ...  ¶a1 ¶n  ¶a0 ¶ &
k k1 k k1
TA n ¶TA n¶ ¶ak n  ak1 n
¶ak ¶  ¶ak1 ¶  ...  ¶a1 ¶  ¶a0 ¶n , ¾n ' 1 ³i alegând c ¶ak ¶  ¶ak1 ¶  ...  ¶a1 ¶  ¶a0 ¶ ³i n 1
k

rezult  TA n " O n .
k

3.2.2 Clase de complexitate

Notaµia O ofer  o limit  superioar  a timpului de execuµie a unui algoritm.


Un algoritm cu TA n " O 1 necesit  un timp de execuµie constant. Un algoritm cu TA n "
O n se nume³te liniar. Dac  TA n " O n  algoritmul se nume³te p tratic, iar dac  TA n "
2

O n , cubic. Un algoritm cu TA n " O n  se nume³te polinomial, iar dac  TA n " O 2 


3 k n

algoritmul se nume³te exponenµial.


Tabelul urm tor ilustreaz  comportarea a cinci din cele mai importante funcµii de complexitate.
2 3 n
O log n O n O n.log n O n  O n  O 2 
(logaritmic) (liniar) (log-liniar) (p tratic) cubic (exponenµial)
0 1 0 1 1 2
1 2 2 4 8 4
2 4 8 16 64 16
3 8 24 64 512 256
4 16 64 256 4096 65536
5 32 160 1024 32768 4294967296
Tabela 3.1: Funcµii de complexitate

Dac  TA n " O 2 , pentru n 40, pe un calculator care face 10 de operaµii pe secund ,


n 9

sunt necesare aproximativ 18 minute. Pentru n 50, acela³i program va rula 13 zile pe acest
13
calculator, pentru n 60, vor  necesari peste 310 ani, iar pentru n 100 aproximativ 4.10 ani.
Utilitatea algoritmilor polinomiali de grad mare este de asemenea limitat . De exemplu, pentru
10 9
O n , pe un calculator care execut  10 operaµii pe secund  sunt necesare 10 secunde pentru
13
n 10, aproximativ 3 ani pentru n 100 ³i circa 3.10 ani pentru n 1000.
Uneori este util s  determin m ³i o limit  inferioar  pentru timpul de execuµie a unui algoritm.
Notaµia matematic  este Ω.
Deniµie: Spunem c  TA n " Ω f n dac  ³i numai dac  ¿c % 0 ³i n0 " N astfel încât
TA n ' c f n, ¾n ' n0 .
De exemplu:
a) dac  TA n 3n  2, atunci TA n " Ω n, pentru c  3n  2 ' 3n, ¾n ' 1;
b) dac  TA n 10n  4n  2, atunci TA n " Ω n, pentru c  10n  4n  2 ' n2, ¾n ' 1;
2 2

c) dac  TA n 6 2  n , atunci TA n " Ω 2 , pentru c  6 2  n ' 2 , ¾n ' 1.


n 2 n n 2 n

Exist  funcµii f care constituie atât o limit  superioar  cât ³i o limit  inferioar  a timpului de
 ...  a1 n  a0 , ak % 0 atunci
k k1
execuµie a algoritmului. De exemplu, dac  TA n ak n  ak1 n
TA n " Ω n .
k

Deniµie : Spunem c  TA n " Θ f n dac  ³i numai dac  ¿c1 , c2 % 0 ³i n0 " N astfel încât
c1 f n & TA n & c2 f n, ¾n ' n0 .
În acest caz f n constituie atât o limit  inferioar  cât ³i o limit  superioar  pentru timpul
de execuµie a algoritmului. Din acest motiv Θ se poate numi ordin exact. Se poate ar ta u³or c 
CAPITOLUL 3. ANALIZA COMPLEXITۥII ALGORITMILOR 39

k k1
Θ f n O f n = Ω f n. De asemenea, dac  TA n ak n  ak1 n  ...  a1 n  a0 ,
ak % 0 atunci TA n " Θ n .
k

3.2.3 Cazul mediu ³i cazul cel mai defavorabil

Am ar tat c  timpul de execuµie al unui algoritm este direct proporµional cu num rul de


operaµii elementare ³i am stabilit o notaµie asimptotic  pentru timpul de execuµie. Totu³i, num rul
de operaµii elementare efectuate de algoritm poate varia considerabil pentru diferite seturi de date
de intrare.
Determinarea complexit µii timp a algoritmului ca o funcµie de caracteristicile datelor de intrare
este o sarcin  u³oar  doar pentru algoritmi relativ simpli, dar în general problema este dicil  ³i
din aceast  cauz  analiz m complexitatea algoritmilor în medie sau în cazul cel mai defavorabil.
Complexitatea în cazul cel mai defavorabil este num rul maxim de operaµii elementare efectuate
de algoritm.
Dar chiar dac  este cunoscut cazul cel mai defavorabil, datele utilizate efectiv în practic  pot
conduce la timpi de execuµie mult mai mici. Numero³i algoritmi foarte utili au o comportare
convenabil  în practic , dar foarte proast  în cazul cel mai defavorabil.
Cel mai cunoscut exemplu este algoritmul de sortare rapid  (quicksort) care are complexitatea
2
în cazul cel mai defavorabil de O n , dar pentru datele întâlnite în practic  funcµioneaz  în
O n log n.
Determinarea complexit µii în medie necesit  cunoa³terea repartiµiei probabilistice a datelor de
intrare ³i din acest motiv analiza complexit µii în medie este mai dicil de realizat. Pentru cazuri
simple, de exemplu un algoritm de sortare care acµioneaza asupra unui tablou cu n componente
întregi aleatoare sau un algoritm geometric pe o mulµime de N puncte în plan de coordonate
aleatoare cuprinse în intervalul 0, 1, putem caracteriza exact datele de intrare.
Dac  not m:

ˆ D - spaµiul datelor de intrare

ˆ p d - probabilitatea apariµiei datei d " D la intrarea algoritmului

ˆ TA d - num rul de operaµii elementare efectuate de algoritm pentru d " D

atunci este
=pd
complexitatea medie
 TA d.
d"D

3.2.4 Analiza asimptotic  a structurilor fundamentale


Consider m problema determin rii ordinului de complexitate în cazul cel mai defavorabil
pentru structurile algoritmice: secvenµial , alternativ  ³i repetitiv .
Presupunem c  structura secvenµial  este constituit  din prelucr rile A1 , A2 , ..., Ak ³i ecare
dintre acestea are ordinul de complexitate O gi n, 1 & i & n. Atunci structura va avea ordinul
de complexitate O maxrg1 n, ..., gk nx.
Dac  condiµia unei structuri alternative are cost constant iar prelucr rile celor dou  variante
au ordinele de complexitate O g1 n respectiv O g2 n atunci costul structurii alternative va 
O maxrg1 n, g2 nx.
În cazul unei structuri repetitive pentru a determina ordinul de complexitate în cazul cel mai
defavorabil se consider  num rul maxim de iteraµii. Dac  acesta este n iar în corpul ciclului
prelucr rile sunt de cost constant atunci se obµine ordinul O n.

3.3 Exemple

3.3.1 Calcularea maximului

Fiind date n elemente a1 , a2 , ..., an , s  se calculeze maxra1 , a2 , ..., an x.


CAPITOLUL 3. ANALIZA COMPLEXITۥII ALGORITMILOR 40

max = a[1];
for i = 2 to n do
if a[i] > max
then max = a[i];
Vom estima timpul de execuµie al algoritmului în funcµie de n, num rul de date de intrare. Fi-
ecare iteraµie a ciclului for o vom considera operaµie elementar . Deci complexitatea algoritmului
este O n, atât în medie cât ³i în cazul cel mai defavorabil.

3.3.2 Sortarea prin selecµia maximului


Sort m cresc tor vectorul a, care are n componente.
for j=n,n-1,...,2
{
max=a[1];
pozmax=1;
for i=2,3,...,j
{
if a[i]>max { a[i]=max; pozmax=i; }
a[pozmax]=a[j];
a[j]=max;
}
}
Estim m complexitatea algoritmului în funcµie de n, dimensiunea vectorului. La ecare iteraµie
a ciclului for exterior este calculat maxra1 , a2 , ..., aj x ³i plasat pe poziµia j , elementele de la j  1
la n ind deja plasate pe poziµiile lor denitive.
Conform exemplului anterior, pentru a calcula maxra1 , a2 , ..., aj x sunt necesare j  1 operaµii
elementare, în total 1  2  ...  n  1 n n  1©2. Deci complexitatea algoritmului este de
2
O n . S  observ m c  timpul de execuµie este independent de ordinea iniµial  a elementelor
vectorului.

3.3.3 Sortarea prin inserµie


Este o metod  de asemenea simpl , pe care o utiliz m adesea când ordon m c rµile la jocuri
de c rµi.
for i=2,3,...,n
{
val=a[i];
poz=i;
while a[poz-1]>val
{
a[poz]=a[poz-1];
poz=poz-1;
}
a[poz]=val;
}
Analiz m algoritmul în funcµie de n, dimensiunea vectorului ce urmeaz  a  sortat. La ecare
iteraµie a ciclului for elementele a1 , a2 , ..., ai1 sunt deja ordonate ³i trebuie s  inser m valorea ai
pe poziµia corect  în ³irul ordonat. În cazul cel mai defavorabil, când vectorul este iniµial ordonat
descresc tor, ecare element ai va  plasat pe prima poziµie, deci ciclul while se execut  de i  1
ori. Considerând drept operaµie elementar  comparaµia apoz  1 % val urmat  de deplasarea
elementului de pe poziµia poz 1, vom avea în cazul cel mai defavorabil 12... n1 n n1©2
2
operaµii elementare, deci complexitatea algoritmului este de O n .
S  analiz m comportarea algoritmului în medie. Consider m c  elementele vectorului sunt
distincte ³i c  orice permutare a lor are aceea³i probabilitate de apariµie. Atunci probabilitatea
ca valoarea ai s  e plasat  pe poziµia k în ³irul a1 , a2 , ..., ai , k " r1, 2, ...., ix este 1©i. Pentru i
xat, num rul mediu de operaµii elementare este:

= 1i =k
i i
1 1 i i  1 i1 i1
k  1  1   i
 1
i i 2 2 2
k 1 k 1
CAPITOLUL 3. ANALIZA COMPLEXITۥII ALGORITMILOR 41

Pentru a sorta cele n elemente sunt necesare

=i 21
n
 1 n n  1 n n  1 n n  1
  1  n  1
  1

2 2 2 2 4
i 2

2
operaµii elementare. Deci complexitatea algoritmului în medie este tot O n .

3.3.4 Sortarea rapid  (quicksort)


Acest algoritm a fost elaborat de C.A.R. Hoare în 1960 ³i este unul dintre cei mai utilizaµi
algoritmi de sortare.

void quicksort(int st, int dr)


{
int m;
if st<dr
{
m=divide(st, dr);
quicksort(st, m-1);
quicksort(m+1, dr);
}
}
Iniµial apel m quicksort(1,n).
Funcµia divide are rolul de aplasa primul element (a[st]) pe poziµia sa corect  în ³irul ordonat.
În stânga sa se vor g si numai elemente mai mici, iar în dreapta numai elemente mai mari decât
el.

int divide(int st, int dr)


{
int i, j, val;
val=a[st];
i=st; j=dr;
while(i<j)
{
while((i<j) && (a[j] >= val)) j=j-1;
a[i]=a[j];
while((i<j) && (a[i] <= val)) i=i+1;
a[j]=a[i];
}
a[i]=val;
return i;
}

Observaµie : Vectorul a este considerat variabil  global .


În cazul cel mai defavorabil, când vectorul a era iniµial ordonat, se fac n  1 apeluri succesive
ale procedurii quicksort, cu parametrii 1, n, 1, n  1, ..., 1, 2 (dac  vectorul a era iniµial
ordonat descresc tor) sau 1, n, 2, n, ..., n  1, n (dac  vectorul a era ordonat cresc tor).
La ecare apel al procedurii quicksort este apelat  funcµia divide(1,i) (respectiv
divide(i, n)) care efectueaz  i  1, (respectiv n  i  1) operaµii elementare. În total num rul
de operaµii elementare este n  1  n  2  ...  1 n n  1©2. Complexitatea algoritmului
2
în cazul cel mai defavorabil este de O n .
S  analiz m comportarea algoritmului în medie. Vom consider m c  orice permutare a elemen-
telor vectorului are aceea³i probabilitate de apariµie ³i not m cu Tn num rul de operaµii elementare
efectuate pentru a sorta n elemente.
Probabilitatea ca un element al vectorului s  e plasat pe poziµia k în vectorul ordonat, este
de 1©n.

dac  n 0 sau n
<
0, 1
Tn w1
n  1, dac  n % 1
n
n k 1 Tk1  Tnk  
CAPITOLUL 3. ANALIZA COMPLEXITۥII ALGORITMILOR 42

(pentru a ordona cresc tor n elemente, determin m poziµia k în vectorul ordonat a primului


element, ceea ce necesit  n  1 operaµii elementare, sort m elementele din stânga, ceea ce necesit 
Tk1 operaµii elementare, apoi cele din dreapta, necesitând Tnk operaµii elementare).
Problema se reduce la a rezolva relaµia de recurenµ  de mai sus. Mai întâi observ m c 

T0  T1  ...  Tn1 T n  1  ...  T1  T0 .

Deci,
=T
n
2
Tn n1 n k1
k 1

Înmulµim ambii membri ai acestei relaµii cu n. Obµinem:

=T
n
nTn n n  1  2 k1
k 1

Sc zând din aceast  relaµie, relaµia obµinut  pentru n  1, adic 

=T
n1
n  1Tn1 n  1 n  2  2 k1
k 1

obµinem
nTn  n  1Tn1 n n  1  n  1 n  2  2Tn1
de unde rezult 
nTn 2 n  1  n  1Tn1
Împ rµind ambii membri cu n n  1 obµinem

= k kk
n
Tn Tn1 2 n  1 Tn2 2 n  1 2 n  2 T2  1
n    ...  2
n1 n n  1 n1 n n  1 n  1n 3
k 3
 1

Deci

= = k1  2 = k1  2 ln n
n n n
Tn T2 1 1 1 T2 2
 2   
  2
n1 3 k1 k k1 3 n1
k 3 k 3 k 1

Deci, în medie, complexitatea algoritmului este de O n log n.

3.3.5 Problema celebrit µii


Numim celebritate o persoan  care este cunoscut  de toat  lumea, dar nu cunoa³te pe nimeni.
Se pune problema de a identica o celebritate, dac  exist , într-un grup de n persoane pentru care
relaµiile dintre persoane sunt cunoscute.
Putem reformula problema în limbaj de grafuri astfel: ind dat un digraf cu n vârfuri, vericaµi
dac  exist  un vârf cu gradul exterior 0 ³i gradul interior n  1.
Reprezent m graful asociat problemei prin matricea de adiacenµ  ann

1, dac  persoana i cunoaste persoana j;


ai,j w
0, altfel.

O prim  soluµie ar  s  calcul m pentru ecare persoan  p din grup num rul de persoane pe
care p le cunoa³te (out) ³i num rul de persoane care cunosc persoana p (in). Cu alte cuvinte,
pentru ecare vârf din digraf calcul m gradul interior ³i gradul exterior. Dac  g sim o persoan 
pentru care out 0 ³i in n  1, aceasta va  celebritatea c utat .
celebritate=0;
for p=1,2,...,n
{
in=0; out=0;
for j=1,2,...,n
{
in=in+a[j][p];
CAPITOLUL 3. ANALIZA COMPLEXITۥII ALGORITMILOR 43

out=out+a[p][j];
}
if (in=n-1) and (out = 0) celebritate=p;
}
if celebritate=0 writeln(’Nu exista celebritati !’)
else writeln(p, ’ este o celebritate.’);
2
Se poate observa cu u³urinµ  c  algoritmul este de O n . Putem îmbun t µi algoritmul f când
observaµia c  atunci când test m relaµiile dintre persoanele x ³i y apar urm toarele posibilit µii:
ax, y  0 ³i în acest caz y nu are nici o ³ans  s  e celebritate, sau
ax, y  1 ³i în acest caz x nu poate  celebritate.
Deci la un test elimin m o persoan  care nu are ³anse s  e celebritate.
F când succesiv n  1 teste, în nal vom avea o singur  persoan  candidat la celebritate.
R mâne s  calcul m num rul de persoane cunoscute ³i num rul de persoane care îl cunosc pe
acest candidat, singura celebritate posibil .
candidat=1;
for i=2,n
if a[candidat][i]=1 candidat=i;
out=0;
in=0;
for i=1,n
{
in=in+a[i][candidat];
out=out+a[candidat][i];
}
if (out=0) and (in=n-1) write(candidat, ’ este o celebritate .’)
else write(’Nu exista celebritati.’);
În acest caz algoritmul a devenit liniar.

3.4 Probleme

3.4.1 Probleme rezolvate

Problema 1. Care armaµii sunt adevarate:


n "O n 
2 3
a)
b) n " O n 
3 2

c) 2
n1
" O 2n 
d) n  1! " O n!
e) ¾f  N R , f " O n
˜
¼f 2
" O n2 
f ) ¾f  N R , f " O n
˜
¼2 f
" O 2n 
Rezolvare:
a) Armaµia este adevarat  pentru c : limn
3
™
n
2

n3
0 ¼n 2
"O 3
n .
b) Armaµia este fals  pentru c : limn ™
n
n2
™

c) Armaµia este adevarat  pentru c : limn ™ 22n


n1
2 ¼O 2 n1

n
O 2 .
n1!
d) Armaµia este fals  pentru c : limn ™ n! limn n1
™
¼
™
1
e) Armaµia este adevarat  pentru c : f " O n ¿c % 0 ³i ¿n0 " N astfel încât f n $ c n,

¾n % n0 . Rezult  c  ¿c1 c astfel încâ f n $ c1 n , ¾n % n0 , deci f " O n .


2 2 2 2 2

e) Armaµia este adevarat  pentru c : f " O n ¼


¿c % 0 ³i ¿n0 " N astfel încât f n $ c n,

¾n % n0 . Rezult  c  ¿c1 $ 2c n 2c 2n c1 2n , ¾n % n0 , deci 2f " O 2n .


c f n
2 astfel încâ 2
Ó Ó
Problema 2. Ar taµi c  log n " O n dar n Š O log n.

Indicaµie: Prelungim Ódomeniile funcµiilor pe R , pe care sunt derivabile, ³i aplic m relula lui
L'Hôspital pentru log n© n.
CAPITOLUL 3. ANALIZA COMPLEXITۥII ALGORITMILOR 44

Problema 3. Demonstraµi urm toarele armaµii:

i loga "Θ logb n, pentru oricare a, b % 1

=i " Θ n ,
n
ii
k
k1  pentru oricare k "N
i 1

= 1i " Θ n log n
n
iii 
i 1
iv  log n! " Θ n log n

Indicaµii: La punctul iii se µine cont de relaµia

= 1i  γ
™

 ln n
i 1

unde γ  0.5772 este constanta lui Euler.


La punctul iv  din n! $ n , rezult  log n! $ n log n, deci log n! " O n log n. Trebuie s  g sim
n

³i o margine inferioar . Pentru 0 & i & n  1 este adev rat  relaµia

n  i i  1 ' n

Deoarece
n  1 1 n ' n
2 n
n! n 1 n  1 2 n  2 3... 2
rezult  2 log n! ' n log n, adic  log n! ' 0.5n log n, deci log n! " Ω n log n.
Relaµia se poate demonstra ³i folosind aproximarea lui Stirling
Ó n n
n!  2πn  e 1  Θ 1©n

3.4.2 Probleme propuse

1. Ar taµi c :
3 6 " 3
a) n  10 n Θ n 
b) n  6 2 " Θ  n 
n n
2 n 2

c) 2n  n log n " Θ n 
2 2

d) n  n  n log n " Θ n log n, k ' 1


k k k

e) loga n " Θ logb n, a, b % 0, a j 1, b j 1.


˜
2. Pentru oricare doua functii f, g  N R demonstraµi c 

O f  g O max f, g  (3.4.1)

unde suma ³i maximul se iau punctual.



3. Fie f, g  N R Demonstraµi c :

i lim
n
f n
™ g n
"R 
 O f O g , ii lim
n
f n
™ g n
0  O f  L O g
Observaµie: Implicaµiile inverse nu sunt în general adev rate, deoarece se poate întampla ca
limitele s  nu existe.
4. Demonstraµi prin inducµie c  pentru a determina maximul a n numere sunt necesare n  1
comparaµii.
5. Care este timpul de execuµie a algoritmului quicksort pentru un vector cu n componente
egale?
6. S  consider m urm torul algoritm de sortare a unui vector a cu n componente:
do
{
ok=true;
for i=1,n-1
if a[i]>a[i+1] { aux=a[i]; a[i]=a[i+1]; a[i+1]= aux; }
ok=false;
} while !ok;
CAPITOLUL 3. ANALIZA COMPLEXITۥII ALGORITMILOR 45

Analizaµi algoritmul în medie ³i în cazul cel mai defavorabil.


7. Analizaµi complexitatea algoritmului de interclasare a doi vectori ordonaµi, a cu n compo-
nente, respectiv b cu m componente :

i=1; j=1; k=0;


while (i <= n) and (j <= m)
{
k=k+1;
if a[i] < b[j] { c[k]=a[i]; i=i+1; }
else { c[k]=b[j]; j=j+1; }
}
for t=i,n { k=k+1; c[k]=a[t]; }
for t=j,m { k=k+1; c[k]=b[t]; }
8. Fiind dat a, un vector cu n componente distincte, vericaµi dac  o valoare dat  x se g se³te
sau nu în vector. Evaluaµi complexitatea algoritmului în cazul cel mai defavorabil ³i în medie.
9. Se d  a un vector cu n componente. Scrieµi un algoritm liniar care s  determine cea mai
lung  secvenµ  de elemente consecutive de valori egale.
¬
10. Fie T un text. Vericaµi în timp liniar dac  un text dat T este o permutare circular  a
lui T .
11. Fie X x1 , x2 , ..., xn  o secvenµ  de numere întregi. Fiind dat x, vom numi multiplicitate
a lui x în X num rul de apariµii ale lui x în X . Un element se nume³te majoritar dac  multi-
plicitatea sa este mai mare decât n©2. Descrieµi un algoritm liniar care s  determine elementul
majoritar dintr-un ³ir, dac  un astfel de element exist .
12. Fie ra1 , a2 , ..., an x ³i rb1 , b2 , ..., bm x, dou  mulµimi de numere întregi, nenule (m $ n).
S  se determine rx1 , x2 , ..., xm x, o submulµime a mulµimii ra1 , a2 , ..., an x pentru care funcµia
f x1 , x2 , ..., xm  a1 x1  a2 x2  ...  an xm ia valoare maxim , prin doi algoritmi de comple-
xitate diferit .
Capitolul 4

Recursivitate

Deniµiile prin recurenµ  sunt destul de curente în matematic : progresia aritmetic , progresia
geometric , ³irul lui Fibonacci, limite de ³iruri, etc.

4.1 Funcµii recursive

4.1.1 Funcµii numerice

Pentru calculul termenilor ³irului lui Fibonacci, a transcriere literal  a formulei este urm toa-
rea:

static int fib(int n) {


if (n <= 1)
return 1;
else
return fib(n-1) + fib(n-2);
}
fib este o funcµie care utilizeaz  propriul nume în deniµia proprie. De asemenea, dac  argumentul
n este mai mic decât 1 returneaz  valoarea 1 iar în caz contrar returneaz  f ib n  1  f ib n  2.
În Java este posibil, ca de altfel în multe alte limbaje de programare (Fortran, Pascal, C,
etc), s  denim astfel de funcµii recursive. Dealtfel, toate ³irurile denite prin recurenµ  se scriu în
aceast  manier  în Java, cum se poate observa din urm toarele dou  exemple numerice: factorialul
³i triunghiul lui Pascal.

static int fact(int n) {


if (n != 1)
return 1;
else
return n * fact (n-1);
}

fibo(4) fact(4)

fibo(3) fibo(2) fact(3)

fibo(2) fibo(1) fibo(1) fibo(0) fact(2)

fibo(1) fibo(0) fact(1)

46
CAPITOLUL 4. RECURSIVITATE 47

static int comb(int n, int p) {


if ((p == 0) || (p == n))
return 1;
else
return comb(n-1, p) + comb(n-1, p-1);
}

comb(4,2)

comb(3,2) comb(3,1)

comb(2,2) comb(2,1) comb(2,1) comb(2,0)

comb(1,1) comb(1,0) comb(1,1) comb(1,0)

Ne putem întreba cum efectueaz  Java calculul funcµiilor recursive. Putem s  r spundem prin
urm rirea calculelor în cazul calculului lui f ibo 4. Reamintim c  argumentele sunt transmise
prin valoare în acest caz, iar un apel de funcµie const  în evaluarea argumentului, apoi lansarea
în execuµie a funcµiei cu valoarea argumentului. Deci
f ibo 4 f ibo 3  f ibo 2
f ibo 2  f ibo 1  f ibo 2
f ibo 1  f ibo 1  f ibo 1  f ibo 2
1  f ibo 1  f ibo 1  f ibo 2
1  1  f ibo 1  f ibo 2
2  f ibo 1  f ibo 2
2  1  f ibo 2
3  f ibo 2
3  f ibo 1  f ibo 1
3  1  f ibo 1
3  1  1
32
5
Exist  deci un num r semnicativ de apeluri succesive ale funcµiei f ib (9 apeluri pentru calculul
lui f ibo 4). S  not m prin Rn num rul apelurilor funcµiei f ibo pentru calculul lui f ibo n.
Evident R0 R1 1, ³i Rn 1  Rn1  Rn2 pentru n % 1. Punând Rn Rn  1, obµinem c 
¬

Rn Rn1  Rn2 pentru n % 1, ³i R1 R0 2. Rezult  Rn 2 f ibo n ³i de aici obµinem c 


¬ ¬ ¬ ¬ ¬ ¬

Rn 2 f ibo n  1. Num rul de apeluri recursive este foarte mare! Exist  o metod  iterativ 
simpl  care permite calculul lui f ibo n mult mai repede.
n
f ibo n 1 1 f ibo n  1 1 1 1



... 
 

f ibo n  1 1 0 f ibo n  2 1 0 0

u 1 1 u0




v 1 0 v0
static int fibo(int n) {
int u, v;
int u0, v0;
int i;
u = 1; v = 1;
for (i = 2; i <= n; ++i) {
u0 = u; v0 = v;
u = u0 + v0;
v = v0;
}
return u;
}
CAPITOLUL 4. RECURSIVITATE 48

Se poate calcula ³i mai repede folosind ultima form  ³i calculând puterea matricei ...
Pentru a rezuma, o regul  bun  este s  nu încerc m s  intr m în meandrele detaliilor apelurilor
recursive pentru a înµelege sensul unei funcµii recursive. În general este sufucient s  înµelegem
sintetic funcµia. Funcµia lui Fibonacci este un caz particular în care calculul recursiv este foarte
lung. Cam la fel se întâmpl  (dac  nu chiar mai r u!) ³i cu triunghiul lui Pascal. Dar nu
aceasta este situaµia în general. Nu numai c  scrierea recursiv  se poate dovedi ecace, dar ea este
totdeauna natural  ³i deci cea mai estetic . Ea nu face decât s  respecte deniµia matematic 
prin recurenµ . Este o metod  de programare foarte puternic .

4.1.2 Funcµia lui Ackerman

“irul lui Fibonacci are o cre³tere exponenµial . Exist  funcµii recursive care au o cre³tere mult
mai rapid . Prototipul este funcµia lui Ackerman. În loc s  denim matematic aceast  funcµie,
este de asemenea simplu s  d m deniµia recursiv  în Java.

static int ack(int m, int n) {


if (m == 0)
return n+1;
else
if (n == 0)
return ack (m-1, 1);
else
return ack(m-1, ack(m, n-1));
}
n  2, ack 2, n  2n, ack 3, n  n
Se poate verica c  ack 0, n n  1, ack 1, n 2 ,
ack 5, 1  ack 4, 4  2 % 1080 , adic  num rul atomilor din univers [10].
65536

4.1.3 Recursii imbricate

Funcµia lui Ackerman conµine dou  apeluri recursive imbricate ceea ce determin  o cre³tere
rapid . Un alt exemplu este "funcµia 91" a lui MacCarty [10]:

static int f(int n) {


if (n > 100)
return n-10;
else
return f(f(n+11));
}
Pentru aceast  funcµie, calculul lui f 96 d 

f 96 f f 107 f 97 ... f 100 f f 111 f 101 91.

Se poate ar ta c  aceast  funcµie va returna 91 dac  n & 100 ³i n  10 dac  n % 100. Aceast 
funcµie anecdotic , care folose³te recursivitatea imbricat , este interesant  pentru c
nu este evident
c  o astfel de deniµie d  d  acela³i rezultat.
Un alt exemplu este funcµia lui Morris [10] care are urm toarea form :

static int g(int m, int n) {


if (m == 0)
return 1;
else
return g(m-1, g(m, n));
}

Ce valoare are g 1, 0? Efectul acestui apel de funcµie se poate observa din deniµia ei: g 1, 0
g 0, g 1, 0. Se declan³eaz  la nesfâr³it apelul g 1, 0. Deci, calculul nu se va termina niciodat !
CAPITOLUL 4. RECURSIVITATE 49

4.2 Proceduri recursive


Procedurile, la fel ca ³i funcµiile, pot  recursive ³i pot suporta apeluri recursive. Exemplul
clasic este cel al turnurilor din Hanoi. Pe 3 tije din faµa noastr , numerotate 1, 2 ³i 3 de la stânga
la dreapta, sunt n discuri de dimensiuni diferite plasate pe tija 1 formând un con cu discul cel mai
mare la baz  ³i cel mai mic în vârf. Se dore³te mutarea discurilor pe tija 3, mutând numai câte un
singur disc ³i neplasând niciodat  un disc mai mare peste unul mai mic. Un raµionament recursiv
permite scrierea soluµiei în câteva rânduri. Dac  n & 1, problema este trivial . Presupunem
problema rezolvat  pentru mutarea a n  1 discuri de pe tija i pe tija j (1 & i, j & 3). Atunci,
exist  o soluµie foarte simpl  pentru mutarea celor n discuri de pe tija i pe tija j :
1. se mut  primele n  1 discuri (cele mai mici) de pe tija i pe tija k 6  i  j ,
2. se mut  cel mai mare disc de pe tija i pe tija j ,
3. se mut  cele n  1 discuri de pe tija k pe tija j .

static void hanoi(int n, int i, int j) {


if (n > 0) {
hanoi (n-1, i, 6-(i+j));
System.out.println (i + " -> " + j);
hanoi (n-1, 6-(i+j), j);
}
}

Aceste câteva linii de program arat  foarte bine cum generalizând problema, adic  mutarea
de pe oricare tij  i pe oricare tij j , un program recursiv de câteva linii poate rezolva o problem 
apriori complicat . Aceasta este forµa recursivit µii ³i a raµionamentului prin recurenµ .

pasul 1

A B C A B C
a) b)
pasul 2

pasul 3

A B d) C A B c) C
Capitolul 5

Analiza algoritmilor recursivi

Am v zut în capitolul precedent cât de puternic  ³i util  este recursivitatea în elaborarea


unui algoritm. Cel mai important câ³tig al exprim rii recursive este faptul c  ea este natural  ³i
compact .
Pe de alt  parte, apelurile recursive trebuie folosite cu discern mânt, deoarece solicit  ³i ele
resursele calculatorului (timp si memorie).
Analiza unui algoritm recursiv implic  rezolvarea unui sistem de recurenµe. Vom vedea în
continuare cum pot  rezolvate astfel de recurenµe.

5.1 Relaµii de recurenµ 


O ecuaµie în care necunoscutele sunt termenii xn , xn1 , ...xnk ai unui ³ir de numere se nume³te
relaµie de recurenµ  de ordinul k . Aceast  ecuaµie poate  satisf cut  de o innitate de ³iruri. Ca
s  putem rezolva ecuaµia (relaµia de recurenµ ) mai avem nevoie ³i de condiµii iniµiale, adic  de
valorile termenilor x0 , x1 , ..., xk1 . De exemplu relaµia de recurenµ 
n  2Cn1 4n  2Cn , pentru n ' 0, C0 1
este de ordinul 1.
Dac  un ³ir xn de numere satisface o formul  de forma
a0 xn  a1 xn1  ...  ak xnk 0, k ' 1, ai " R, a0 , ak j 0 (5.1.1)
atunci ea se nume³te relaµie de recurenµ  de ordinul k cu coecienµi constanµi. Coecienµii sunt
constanµi în sensul c  nu depind de valorile ³irului xn .
O astfel de formul  este de exemplu Fn2 Fn1  Fn , F0 0, F1 1, adic  relaµia de
recurenµ  care dene³te ³irul numerelor lui Fibonacci. Ea este o relaµie de recurenµ  de ordinul 2
cu coecienµi constanµi.

5.1.1 Ecuaµia caracteristic 

G sirea expresiei lui xn care s  satisfac  relaµia de recurenµ  se nume³te rezolvarea relaµiei de
recurenµ .F când substituµia
n
xn r
obµinem urm toarea ecuaµie, numit  ecuaµie caracteristic :

2 k
a0  a1 r  a2 r  ...  ak r 0 (5.1.2)

5.1.2 Soluµia general 

Soluµia general  a relaµiei de recurenµ  omogen  de ordinul k cu coecienµi constanµi este de


forma
=c x
k
i
xn i n (5.1.3)
i 1

50
CAPITOLUL 5. ANALIZA ALGORITMILOR RECURSIVI 51

unde txn ¶i " r1, 2, ..., k xz sunt soluµii liniar independente ale relaµiei de recurenµ  (se mai numesc
i

³i sistem fundamental de soluµii). Pentru determinarea acestor soluµii distingem urm toarele


cazuri:
a Ecuaµia caracteristic  admite r d cini reale ³i distincte
n
Dac  r1 , r2 , ..., rk sunt r d cini reale ale ecuaµiei caracteristice, atunci ri sunt soluµii ale relaµiei
de recurenµ .
n
Într-adev r, introducând expresiile ri în relaµia de recurenµ , obµinem:
n n1 n2 nk n 2 k
a0 ri  a1 ri  a2 ri  ...  ak ri ri a0  a1 ri  a2 ri  ...  ak ri  0
Dac  r d cinile ri (i 1, 2, ..., k ) sunt distincte, atunci relaµia de recurenµ  are soluµia general 
n n n
xn c1 r1  c2 r2  ...  ck rk (5.1.4)
unde coecienµii c1 , c2 , ..., ck se pot determina din condiµiile iniµiale.
a Ecuaµia caracteristic  admite r d cini reale multiple
Fie r o r d cin  multipl  de ordinul p a ecuaµiei caracteristice. Atunci
n n 2 n p1 n
r , nr , n r , ..., n r
sunt soluµii liniar independente ale relaµiei de recurenµ  ³i
p1 n
xn c1  c2 n  ...  cp1 n r (5.1.5)
este o soluµie a relaµiei de recurenµ . Acest lucru se mai poate demonstra u³or dac  µinem cont de
faptul c  o r d cin  multipl  de ordinul p a unui polinom P x este r d cin  ³i a polinoamelor
¬ ¬¬ p1
derivate P x, P x, ..., P x.
Soluµia general  este suma dintre soluµia general  corespunz toare r d cinilor simple ale ecu-
aµiei caracteristice ³i soluµia general  corespunz toare r d cinilor multiple.
Dac  ecuaµia caracteristic  are r d cinile simple r1 , r2 , ..., rs ³i r d cinile multiple
rs1 , rs2 , ..., rst de multiplicitate p1 , p2 , ..., pt (s  p1  p2  ...  pt k ), atunci soluµia gene-
ral  a relaµiei de recurenµ  este
n n n
xn c1 r1  c2 r2  ...  cs rs 
1 1 1 p1 1
c1  c2 n  ...  cp 1 n
1

...
t t 1 p 1
c1  c2 n  ...  cpt 1 n t 

1 1 t t
unde c1 , ..., cs , c1 , ..., cp1 1 , ..., c1 , ..., cpt 1 sunt constante, care se pot determina din condiµiile
iniµiale.
a Ecuaµia caracteristic  admite r d cini complexe simple
ib
Fie r ae a cos b  i sin b o r d cin  complex . Ecuaµia caracteristic  are coecienµi reali,
ib
deci ³i conjugata r̄ ae a cos b  i sin b este r d cin  pentru ecuaµia caracteristic . Atunci
soluµiile corespunz toare acestora în sistemul fundamental de soluµii pentru recurenµa liniar  ³i
omogen  sunt
1 n 2 n
xn a cos bn, xn a sin bn.
a Ecuaµia caracteristic  admite r d cini complexe multiple Dac  ecuaµia caracteristic 
admite perechea de r d cini complexe
bj0
ib ib
r ae , r̄ ae
de ordin de multiplicitate k , atunci soluµiile corespunz toare acestora în sistemul fundamental de
soluµii sunt
1 n 2 n k k1 n
xn a cos bn, xn na cos bn, ..., xn n a cos bn,
k1 n k2 n 2k k1 n
xn a sin bn, xn na sin bn, ..., xn n a sin bn,
Pentru a obµine soluµia general  a recurenµei omogene de ordinul n cu coecienµi constanµi se
procedeaz  astfel:
1. Se determin  r d cinile ecuaµiei caracteristice
2. Se scrie contribuµia ec rei r d cini la soluµia general .
3. Se însumeaz  ³i se obµine soluµia general  în funcµie de n constante arbitrare.
4. Dac  sunt precizate condiµiile iniµiale atunci se determin  constantele ³i se obµine o
soluµie unic .
CAPITOLUL 5. ANALIZA ALGORITMILOR RECURSIVI 52

5.2 Ecuaµii recurente neomogene

5.2.1 O form  simpl 

Consider m acum recurenµe de urm toarea form  mai general 


n
a0 tn  a1 tn1  ...  ak tnk b p n

unde b este o constant , iar p n este un polinom în n de grad d. Ideea general  este s  reducem
un astfel de caz la o form  omogen .
De exemplu, o astfel de recurenµ  poate :
n
tn  2tn1 3

În acest caz, b 3 ³i p n 1. Înmulµim recurenµa cu 3, ³i obµinem


n1
3tn  6tn1 3

Înlocuind pe n cu n  1 în recurenµa iniµial , avem


n1
tn1  2tn 3

Sc dem aceste dou  ecuaµii


tn1  5tn  6tn1 0
Am obµinut o recurenµ  omogen . Ecuaµia caracteristic  este:
2
x  5x  6 0

adic  x  2 x  3 0. Intuitiv, observ m c  factorul x  2 corespunde p rµii stângi a recurenµei


iniµiale, în timp ce factorul x  3 a ap rut ca rezultat al calculelor efectuate pentru a sc pa de
partea dreapt .
Generalizând acest procedeu, se poate ar ta c , pentru a rezolva ecuaµia iniµial , este sucient
s  lu m urm toarea ecuaµie caracteristic :
k k1 d1
a0 x  a1 x  ...  ak  x  b 0

Odat  ce s-a obµinut aceast  ecuaµie, se procedeaz  ca în cazul omogen.


Vom rezolva acum recurenµa corespunzatoare problemei turnurilor din Hanoi:

tn 2tn1  1, n 1

iar t0 0. Rescriem recurenµa astfel


tn  2tn1 1
care este de forma general  prezentat  la început, cu b 1 si p n 1. Ecuaµia caracteristic  este
atunci x  2 x  1 0, cu soluµiile 1 ³i 2. Soluµia general  a recurenµei este:
n n
tn c1 1  c2 2

Avem nevoie de dou  condiµii iniµiale. “tim c  t0 0; pentru a g si cea de-a doua condiµie
calcul m
t1 2t0  1 1.
Din condiµiile iniµiale, obµinem
n
tn 2  1.
Dac  ne intereseaz  doar ordinul lui tn , nu este necesar s  calcul m efectiv constantele în
soluµia general . Dac  ³tim c  tn c1 1  c2 2 , rezult  tn " O 2 .
n n n

Din faptul c  num rul de mut ri a unor discuri nu poate  negativ sau constant, deoarece
avem în mod evident tn ' n, deducem c  c2 % 0. Avem atunci tn " Ω 2  ³i deci, tn " Θ 2 .
n n

Putem obµine chiar ceva mai mult. Substituind soluµia general  înapoi în recurenµa iniµial , g sim
n n1
1 tn  2tn1 c1  c2 2  2 c1  c2 2  c1

Indiferent de condiµia iniµial , c1 este deci 1.


CAPITOLUL 5. ANALIZA ALGORITMILOR RECURSIVI 53

5.2.2 O form  mai general 

O ecuaµie recurent  neomogen  de form  mai general  este:

= a Tn
k
n n
j  j b1 pd1 n  b2 pd2 n  ...
j 0

în care
d d1
pd n n  c1 n  ...  cd
Ecuaµia caracteristic  complet  este:

=a
k
kj d1 1 d2 1
 j r r  b1  r  b2  ... 0
j 0

Exemplul 3: Tn 2T n  1  n  2 , n ' 1, T0 0.
n

Acestui caz îi corespund b1 1, p1 n n, d1 1 ³i b2 2, p2 n 1, d2 0, iar ecuaµia


caracteristic  complet  este:
2
r  2 r  1 r  2 0
cu soluµia:
n n n
T n c1 1  c2 n 2  c3 2n  c4 n 2
~
„T 0 0 ~
„c1  c3 0 ~
„c1 2
„
„ „
„ „
„
„T „c „c

1
„
„ 1 2T 0  1  2 3 „
„ 1  c2  2c3  2c4 3 „
„ 2 1
‚
„ 2 ‚
„ ‚
„
„
„T 2 2T 1  2  2 12 „
„c  2c2  4c3  8c4 12 „
„c 2
„
„ „ 1
„ „ 3
„
„T 3 2T 2  3  2
3
35 „c  3c2  8c3  24c4 35 „c 1
€ € 1 € 4
Deci
n1 n n
T n 2  n2  n 2 O n 2 .

5.2.3 Teorema master

De multe ori apare relaµia de recurenµ  de forma

T n aT n©b  f n (5.2.6)

unde a ³i b sunt constante iar f n este o funcµie (aplicarea metodei Divide et Impera conduce de
obicei la o astfel de ecuaµie recurent ). A³a numita teorem  Master d  o metod  general  pentru
rezolvarea unor astfel de recurenµe când f n este un simplu polinom. Soluµia dat  de teorema
master este:
 cu ε % 0 atunci T n Θ n b 
log aε log a
1. dac  f n O n b
logb a logb a
2. dac  f n Θ n  atunci T n Θ n lg n
3. dac  f n Ω n
logb aε
 ³i a f & c f n cu c $ 1 atunci T n Θ f n.
n
b
Din p cate, teorema Master nu funcµioneaz  pentru toate funcµiile f n, ³i multe recurenµe
utile nu sunt de forma (5.2.6). Din fericire îns , aceasta este o tehnic  de rezolvare a celor mai
multe relaµii de recurenµ  provenite din metoda Divide et Impera.
Pentru a rezolva astfel de ecuaµii recurente vom reprezenta arborele generat de ecuaµia recur-
siv . R d cina arborelui conµine valoarea f n, ³i ea are noduri descendente care sunt noduri
r d cin  pentru arborele provenit din T n©b.
CAPITOLUL 5. ANALIZA ALGORITMILOR RECURSIVI 54

i i
Pe nivelul i se a  nodurile care conµin valoarea a f n©b . Recursivitatea se opre³te când se
obµine un caz de baz  pentru recurenµ .
Presupunem c  T 1 f 1.
Cu aceast  reprezentare este foarte clar c  T n este suma valorilor din nodurile arborelui.
Presupunând c  ecare nivel este plin, obµinem
2 2 3 3 k k
T n f n  af n©b  a f n©b   a f n©b   ...  a f n©b 
k
unde k este adâncimea arborelui de recursivitate. Din n©b 1 rezult  k logb n. Ultimul termen
k log n log a
diferit de zero în sum  este de forma a a b n b (ultima egalitate ind întâlnit  în liceu!).
Acum putem u³or enunµa ³i demonstra teorema Master.

Teorema 1. (Teorema Master) Relaµia de recurenµ  T n aT n©b  f n are urm toarea


soluµie:

ˆ dac  af n©b αf n unde α$1 atunci T n Θ f n;

ˆ dac  af n©b βf n unde β % 1 atunci T n Θ n


logb a
;

ˆ dac  af n©b f n atunci T n Θ f n logb n;

Demonstraµie: Dac  f n este un factor constant mai mare decât f b©n, atunci prin inducµie
se poate ar ta c  suma este a unei progresii geometrice descresc toare. Suma în acest caz este o
constant  înmulµit  cu primul termen care este f n.
Dac  f n este un factor constant mai mic decât f b©n, atunci prin inducµie se poate ar ta
c  suma este a unei progresii geometrice cresc toare. Suma în acest caz este o constant  înmulµit 
log a
cu ultimul termen care este n b .
Dac  af b©n f n, atunci prin inducµie se poate ar ta c  ecare din cei k  1 termeni din
sum  sunt egali cu f n.
Exemple.
1. Selecµia aleatoare: T n T 3n©4  n.
Aici af n©b 3n©4 iar f n n, rezult  α 3©4, deci T n Θ n.
2. Algoritmul de multiplicare al lui Karatsuba: T n 3T n©2  n.
log 3
Aici af n©b 3n©2 iar f n n, rezult  α 3©2, deci T n Θ n 2 .
3. Mergesort: T n 2T n©2  n.
Aici af n©b n, iar f n n, rezult  α 1 deci T n Θ n log2 n.
Folosind accea³i tehnic  a arborelui recursiv, putem rezolva recurenµe pentru care nu se poate
aplica teorema Master.

5.2.4 Transformarea recurenµelor


CAPITOLUL 5. ANALIZA ALGORITMILOR RECURSIVI 55

La Mergesort am avut o relai e de recurenµ  de forma T n 2T n©2  n ³i am obµinut


soluµia T n O n log2 n folosind teorema Master (metoda arborelui de recursivitate). Aceast 
modalitate este corect  dac  n este o putere a lui 2, dar pentru alte valori ale lui n aceast 
recurenµ  nu este corect . Când n este impar, recurenµa ne cere s  sort m un num r elemente
care nu este întreg! Mai r u chiar, dac  n nu este o putere a lui 2, nu vom atinge niciodat  cazul
de baz  T 1 0.
Pentru a obµine o recurenµ  care s  e valid  pentru orice valori întregi ale lui n, trebuie s 
determin m cu atenµie marginile inferioar  ³i superioar :

T n T n©2$  T *n©20  n.

Metoda transform rii domeniului rescrie funcµia T n sub forma S f n, unde f n este o
funcµie simpl  ³i S  are o recurenµ  mai u³oar .
Urm toarele inegalit µi sunt evidente:

T n & 2T *n©20  n & 2T n©2  1  n.

Acum denim o nou  funcµie S n T n  α, unde α este o constant  necunoscut , aleas 
astfel încât s  e satisf cut  recurenµa din teorema Master S n & S n©2  O n. Pentru a obµine
valoarea corect  a lui α, vom compara dou  versiuni ale recurenµei pentru funcµia S n  α:

S n & 2S n©2  O n T n  α & 2T n©2  α  O n


w
T n & 2T n©2  1  n T n  α & 2T n  α©2  1  n  α

Pentru ca aceste dou  recurenµe s  e egale, trebuie ca n©2  α n  α©2  1, care implic 
α 2. Teorema Master ne spune acum c  S n O n log n, deci

T n S n  2 O n  2 log n  2 O n log n.

Un argument similar d  o ajustare a marginii inferioare T n Ω n log n.


Deci, T n Θ n log n este un rezultat întemeiat de³i am ignorat marginile inferioar  ³i
superioar  de la început!
Transformarea domeniului este util  pentru înl turarea marginilor inferioar  ³i superioar , ³i
a termenilor de ordin mic din argumentele oric rei recurenµe care se potrive³te un pic cu teorema
master sau metoda arborelui de recursivitate.
Exist  în geometria computaµional  o structur  de date numit  arbore pliat, pentru care costul
operatiei de c utare îndepline³te relaµia de recurenµ 

T n T n©2  T n©4  1.
Aceasta nu se potrive³te cu teorema master, pentru c  cele dou  subprobleme au dimensiuniÓ
diferite, ³i utilizând metoda arborelui de recursivitate nu obµinem decât ni³te margini slabe n $$
T n $$ n.
Dac  nu au forma standard, ecuaµiile recurente pot  aduse la aceast  form  printr-o schimbare
de variabil . O schimbare de variabil  aplicabil  pentru ecuaµii de recurenµ  de tip multiplicativ
este:
n 2
k

k log n
De exemplu, e
T n 2 T n©2  n log n, n % 1
k
Facem schimbarea de variabil  t k  T 2  ³i obµinem:
k
t k   2 t k  1 k 2 , deci b 2, p k  k, d 1

Ecuaµia caracteristic  complet  este:


3
r  2 0

cu soluµia
k k 2 k
t k c1 2  c2 k 2  c3 k 2
Deci
c1 n  c2 n log n  c3 n log n " O n log n¶n
2 2 k
T n 2 
CAPITOLUL 5. ANALIZA ALGORITMILOR RECURSIVI 56

Uneori, printr-o schimbare de variabil , putem rezolva recurenµe mult mai complicate. În
exemplele care urmeaz , vom nota cu T n termenul general al recurenµei si cu tk termenul noii
recurenµe obµinute printr-o schimbare de variabil .
Presupunem pentru început c  n este o putere a lui 2.
Un prim exemplu este recurenta

T n 4T n©2  n, n % 1
k k
în care înlocuim pe n cu 2 , notam tk T 2  T n ³i obµinem
k
tk 4tk1  2

Ecuaµia caracteristic  a acestei recurenµe liniare este

x  4 x  2 0
k k
³i deci, tk c1 4  c2 2 . Înlocuim la loc pe k cu log2 n
2
T n c1 n  c2 n

Rezult 

T n " O n ¶n este o putere a lui 2


2

Un al doilea exemplu îl reprezint  ecuaµia

4T n©2  n , n % 1
2
T n

Procedând la fel, ajungem la recurenta


k
tk 4tk1  4

cu ecuaµia caracteristic 
2
x  4 0
2 2
³i soluµia general  tk c1 4  c2 k4 .
Atunci,
2 2
T n c1 n  c2 n lg n
³i obµinem
T n " O n log n¶n este o putere a lui 2
2

În sfâr³it, s  consider m ³i exemplul

T n 3T n©2  cn, n % 1
c ind o constant . Obµinem succesiv
k k1 k
T 2  3T 2   c2

k
tk 3tk1  c2
cu ecuaµia caracteristic 

x  3 x  2 0
k k
tk c1 3  c2 2
lg n
T n c1 3  c2 n

³i, deoarece
lg b lg a
a b
obµinem
lg 3
T n c1 n  c2 n

deci,
T n " O n
lg 3
¶n este o putere a lui 2
CAPITOLUL 5. ANALIZA ALGORITMILOR RECURSIVI 57

Putem enunµa acum o proprietate care este util  ca reµet  pentru analiza algoritmilor cu
recursivit µi de forma celor din exemplele precedente.
Fie T  N º 
R o funcµie eventual nedescresc toare

aT n©b  cn , n % n0
k
T n

unde: n0 ' 1, b ' 2 si k ' 0 sunt întregi; a ³i c sunt numere reale pozitive; n©n0 este o putere a
lui b. Atunci avem
~ pentru a $ b ;
k k
„
„Θ n ,
„
„
T n "
k k
‚
„Θ n log n, pentru a b ;
„
„
„Θ nlogb a , pentru a % b ;
k
€

5.3 Probleme rezolvate


1. S  se rezolve ecuaµia:

Fn2 Fn1  Fn , F0 0, F1 1.

Ecuaµaia caracteristic  corespunz toare


2
r  r1 0

are soluµiile Ó Ó
1 5 1 5
r1 , r2 .
2 2
Soluµia general  este
Ó n Ó n
1 5 1 5
Fn c1   c2  .
2 2
Determin m constantele c1 ³i c2 din condiµiile iniµiale F0 0 ³i F1 1. Rezolvând sistemul

c1  c2 0
w Ó Ó
c1  12 5   c2 
1 5
2
 1

obµinem c1 Ó15 ³i c1  Ó15 .


Deci, soluµia relaµiei de recurenµ  care dene³te numerele lui Fibonacci este:
Ó n Ó n
1 1 5 1 1 5
Fn Ó   Ó 
5 2 5 2

2. S  se rezolve relaµia de recurenµ :

xn3 xn2  8xn1  12xn , x0 0, x1 2, x2 3.

Ecuaµia caracteristic  corespunz toare este:


3 2
r  r  8r  12 0

³i are soluµiile: r1 r2 2 ³i r3 3.


Soluµia general  este de forma:
n n
xn c1  nc2 2  c3 3 .

Din condiµiile iniµiale rezult  constantele: c1 51 , c2 1


2
³i c3 
1
5
.
Soluµia general  este:
n 1 n 1 n
xn  
2  3 .
2 5 5

3. S  se rezolve relaµia de recurenµ :

xn3 6xn2  12xn1  8xn , x0 0, x1 2, x2 4.


CAPITOLUL 5. ANALIZA ALGORITMILOR RECURSIVI 58

Ecuaµia caracteristic  corespunz toare este:


3 2
r  6r  12r  8 0

³i are soluµiile: r1 r2 r3 2.
Soluµia general  este de forma:
2 n
xn c1  c2 n  c3 n 2 .

Din condiµiile iniµiale rezult  constantele: c1 0, c2 23 ³i c3  21 .


Soluµia general  este:
3 1 2 n 2 n1
xn  n  n
2 3n  n 2 .
2 2

4. S  se rezolve relaµia de recurenµ :

xn2 2xn1  2xn , x0 0, x1 1.

Ecuaµia caracteristic  corespunz toare este:


2
r  2r  2 0

³i are soluµiile: r1 1  i ³i r2 1  i care se pot scrie sub form  trigonometric  astfel:


Ó π π Ó π π
r1 2 cos  i sin , r2 2 cos  i sin .
4 4 4 4
Soluµiile fundamentale sunt:
1 Ó n nπ 2 Ó n nπ
xn  2 cos , xn  2 sin .
4 4
Soluµia general  este de forma:
Ó n nπ nπ
xn  2 c1 cos  c2 sin .
4 4
Din condiµiile iniµiale rezult  constantele: c1 0 si c2 1.
Soluµia general  este:
Ó n nπ
xn  2 sin .
4

5. S  se rezolve relaµia de recurenµ :

xn3 4xn2  6xn1  4xn , x0 0, x1 1, x2 1.

Ecuaµia caracteristic  corespunz toare este:


3 2
r  4r  6r  4 0

³i are soluµiile: r1 2, r2 1  i ³i r3 1  i.
Soluµia general  este de forma:
n Ó n nπ Ó n nπ
xn c1 2  c2  2 cos  c3  2 sin .
4 4
Din condiµiile iniµiale rezult  constantele: c1  ,
1
2
c2 1
2
si c3 3
2
.
Soluµia general  este:
Ó n
n1  2 nπ nπ
xn 2  cos  3 sin .
2 4 4

6. S  se rezolve relaµia de recurenµ :

T n  3T n  1  4T n  2 0, n ' 2, T 0 0, T 1 1.
2
Ecuaµia caracteristic  r  3r  4 0 are soluµiile r1 1, r2 4, deci
n n
T n c1 1  c2 4
CAPITOLUL 5. ANALIZA ALGORITMILOR RECURSIVI 59

Constantele se determin  din condiµiile iniµiale:

 wcc
1
c1  c2 0 1 
w 1
5
c1  4c2 1 2 5

Soluµia este:
1 n n
T n 4  1 .
5

7. S  se rezolve relaµia de recurenµ :

T n 5T n  1  8T n  2  4T n  3, n ' 3, cu T 0 0, T 1 1, T 2 2.

Ecuaµia caracteristic :

r
3
 5r
2
 8r  4 0 r 1 1, r2 r3 2

deci
n n
T n c1 1  c2 2  c3 n2n

Determinarea constantelor
~
„c1  c2 0 ~
„c1 2
„ „
„
„
‚
„
„
c1  2c2  2c3 1  „
„
‚
„
„
c2 2
„
„c1  4c2  8c3 2 „
„c3 
1
€ € 2

Deci
n1 n n n1 n1
T n 2  2  2 2  n2  2.
2

8. S  se rezolve relaµia de recurenµ :

T n 4T n©2  n lg n.

În acest caz, avem af n©b 2n lg n  2n, care nu este tocmai dublul lui f n n lg n. Pentru
n sucient de mare, avem 2f n % af n©b % 1.9f n.
Suma este m rginit  ³i inferior ³i superior de c tre serii geometrice cresc toare, deci soluµia
log 4 2
este T n Θ n 2  Θ n . Acest truc nu merge în cazurile doi ³i trei ale teoremei Master.
9. S  se rezolve relaµia de recurenµ :

T n 2T n©2  n lg n.

Nu putem aplica teorema Master pentru c  af n©b n© lg n  1 nu este egal  cu f n


n© lg n, iar diferenµa nu este un factor constant.
Trebuie s  calcul m suma pe ecare nivel ³i suma total  în alt mod. Suma tuturor nodurilor
de pe nivelul i este n© lg n  i. În particular, aceasta înseamn  c  adâncimea arborelui este cel
mult lg n  1.

= =
lg n1 lg n
n n
T n nHlg n Θ n lg lg n.
lg n  i j
i 0 j 1

10. (Quicksort aleator). S  se rezolve relaµia de recurenµ :

T n T 3n©4  T n©4  n.

În acest caz nodurile de pe acela³i nivel al arborelui de recursivitate au diferite valori. Nodurile
din orice nivel complet (adic , deasupra oric rei frunze) au suma n, deci este la fel ca în ultimul
caz al teoremei Master ³i orice frunz  are nivelul între log4 n ³i log4©3 n.
Pentru a obµine o margine superioar , vom supraevalua T n ignorând cazurile de baz  ³i
extinzând arborele în jos c tre nivelul celei mai adânci frunze.
Similar, pentru a obµine o margine inferioar  pentru T n, vom subevalua T n contorizând
numai nodurile din arbore pân  la nivelul frunzei care este cea mai puµin adânc . Aceste observaµii
ne dau marginile inferioar  ³i superioar :

n log4 n & T n & n log4©3 n.


CAPITOLUL 5. ANALIZA ALGORITMILOR RECURSIVI 60

Deoarece aceste margini difer  numai printr-un factor constant, avem c  T n Θ n log n.
11. (Selecµie determinist ). S  se rezolve relaµia de recurenµ :

T n T n©5  T 7n©10  n.

Din nou, avem un arbore recursiv "trunchiat". Dac  ne uit m numai la nivelurile complete
ale arborelui, observ m c  suma pe nivel formeaz  o serie geometric  descresc toare T n
n  9n©10  81n©100  ..., deci este ca în primul caz al teoremei Master. Putem s  obµinem
o margine superioar  ignorând cazurile de baz  în totalitate ³i crescând arborele spre innit, ³i
putem obµine o margine inferioar  contorizând numai nodurile din nivelurile complete. În ambele
situaµii, seriile geometrice sunt majorate de termenul cel mai mare, deci T n Θ n.

12. S  se rezolve relaµia de recurenµ :


Ó Ó
T n 2 n T n  n.
i
Avem cel mult lg lg n niveluri dar acum avem nodurile de pe nivelul i care au suma 2 n. Avem
o serie geometric  cresc toare a sumelor nivelurilor, la fel ca în cazul doi din teorema Master, deci
T n este majorat  de suma nivelurilor cele mai adânci. Se obµine:
lg lg n
T n Θ 2 n Θ n log n.

13. S  se rezolve relaµia de recurenµ :


Ó Ó
T n 4 n T n  n.
i
Suma nodurilor de pe nivelul i este 4 n. Avem o serie geometric  cresc toare, la fel ca în cazul
doi din teorema master, deci nu trebuie decât s  avem grij  de aceste niveluri. Se obµine
lg lg n 2
T n Θ 4 n Θ n log n.
Capitolul 6

Algoritmi elementari

6.1 Operaµii cu numere

6.1.1 Minim ³i maxim

S  presupunem c  dorim s  determin m valorile minim  ³i maxim  dintru-un vector x1..n.


Proced m astfel:
vmin = x[1];
vmax = x[1];
for i=2, n
vmin = minim(vmin, x[i])
vmax = maxim(vmax, x[i])
Evident se fac 2n  2 comparaµii. Se poate mai repede? Da! Împ rµim ³irul în dou  ³i
determin m vmin ³i vmax în cele dou  zone. Compar m vmin1 cu vmin2 ³i stabilim vminm.
La fel pentru vmax. Prelucrarea se repet  pentru cele dou  zone (deci se folose³te recursivitatea).
Apar câte dou  comparaµii în plus de ecare dat . Dar câte sunt în minus? Presupunem c  n este
o putere a lui 2 ³i T n este num rul de comparaµii. Atunci

T n 2T n©2  2 ³i T 2 1.

Cum rezolv m aceast  relaµie de recurenµ ? B nuim c  soluµia este de forma T n an  b.


Atunci a ³i b trebuie s  satisfac  sistemul de ecuaµii

2a  b 1
w
an  b 2 an©2  b  2
care are soluµia b 2 ³i a 3©2, deci (pentru n putere a lui 2), T n 3n©2  2, adic  75% din
algoritmul anterior. Se poate demonstra c  num rul de comparaµii este 3 *n©20  2 pentru a aa
minimum ³i maximum.
O idee similar  poate  aplicat  pentru varianta secvenµial . Presupunem c  ³irul are un
num r par de termeni. Atunci, algoritmul are forma:
vmin = minim(x[1],x[2])
vmax = maxim(x[1],x[2])
for(i=3;i<n;i=i+2)
cmin = minim(x[i],x[i+1])
cmax = maxim(x[i],x[i+1])
if cmin < vmin
vmin = cmin
if vmax > cmax
vmax = cmax
Fiecare iteraµie necesit  trei comparaµii, iar iniµializarea variabilelor necesit  o comparaµie.
Ciclul se repet  de n  2©2 ori, deci avem un total de 3n©2  2 comparaµii pentru n par.

61
CAPITOLUL 6. ALGORITMI ELEMENTARI 62

6.1.2 Divizori

Fie n un num r natural. Descompunerea în facori primi


α α α
n p1 1 p2 2 ... pk k (6.1.1)

se nume³te descompunere canonic . Dac  not m prin d n num rul divizorilor lui n " N, atunci:

d n 1  α1  1  α2 ... 1  αk  (6.1.2)

Pentru calculul lui d n putem folosi urm torul algoritm:

static int ndiv(int n)


{
int d,p=1,nd;

d=2;nd=0;
while(n%d==0){nd++;n=n/d;}
p=p*(1+nd);

d=3;
while(d*d<=n)
{
nd=0;
while(n%d==0){nd++;n=n/d;}
p=p*(1+nd);
d=d+2;
}
if(n!=1) p=p*2;
return p;
}

6.1.3 Numere prime

Pentru testarea primalit µi unui num r putem folosi urm torul algoritm:

static boolean estePrim(int nr)


{
int d;
if(nr<=1) return false;
if(nr==2) return true;
if(nr%2==0) return false;
d=3;
while((d*d<=nr)&&(nr%d!=0)) d=d+2;
if(d*d>nr) return true; else return false;
}

6.2 Algoritmul lui Euclid

6.2.1 Algoritmul clasic

Un algoritm pentru calculul celui mai mare divizor comun (cmmdc) a dou  numere naturale
poate  descompunerea lor în factori ³i calculul produsului tuturor divizorilor comuni. De exemplu
dac  a 1134 2 ˜ 3 ˜ 3 ˜ 3 ˜ 3 ˜ 7 ³i b 308 2 ˜ 2 ˜ 7 ˜ 11 atunci cmmdc a, b 2 ˜ 7 14.
Descompunerea în factoriÓ
a unui num r natural n poate necesita încercarea tuturor numerelor
naturale din intervalul 2, n.
Un algoritm ecient pentru calculul cmmdc a, b este algoritmul lui Euclid.
CAPITOLUL 6. ALGORITMI ELEMENTARI 63

static int cmmdc(int a, int b}


{
int c;
if (a < b) { c = a; a = b; b = c; }
while((c=a%b) != 0) { a = b; b = c;}
return b;
}
Pentru a 1134 ³i b 308 se obµine:
a0 1134, b0 308;
a1 308, b1 210;
a2 210, b2 98;
a3 98, b3 14.
Lema 1. cmmdc a  x ˜ b, b cmmdc a, b.
Demonstraµie: Pentru început ar t m c  cmmdc a  x ˜ b, b % cmmdc a, b. Presupunem c 
d divide a ³ b, deci a c1 ˜ d ³i b c2 ˜ d. Atunci d divide a  x ˜ b pentru c  a  x ˜ b c1  x ˜ c2  ˜ d.
Demonstr m ³i inegalitatea contrar  cmmdc a  x ˜ b, b $ cmmdc a, b. Presupunem c 
d divide a  x ˜ b ³i b, deci a  x ˜ b c3 ˜ d ³i b c2 ˜ d. Atunci d divide a pentru c 
a a  x ˜ b  x ˜ b c3  x ˜ c2  ˜ d. De aici rezult  c 
cmmdc b, c cmmdc c, b cmmdc a mod b, b gcd a, b.
Prin inducµie rezult  c  cel mai mare divizor comun al ultimelor dou  numere este egal cu cel
mai mare divizor comun al primelor dou  numere. Dar pentru cele dou  numere a ³i b din nal,
cmmdc a, b b, pentru c  b divide a.

6.2.2 Algoritmul lui Euclid extins

Pentru orice dou  numere intregi pozitive, exist  x ³i y (unul negativ) astfel încât x ˜ a  y ˜ b
cmmdc a, b. Aceste numere pot  calculate parcurgând înapoi algoritmul clasic al lui Euclid.
Fie ak ³i bk valorile lui a ³i b dup  k iteraµii ale buclei din algoritm. Fie xk ³i yk numerele care
indeplinesc relaµia xk ˜ ak  yk ˜ bk cmmdc ak , bk  cmmdc a, b. Prin inductie presupunem
c  xk ³ yk exist , pentru c  la sfâr³it, când bk divide ak , putem lua xk 0 ³ yk 1.
Presupunând c  xk ³i yk sunt cunoscute, putem calcula xk1 ³i yk1 .
ak bk1 ³i bk ak1 mod bk1 ak1  dk1 ˜ bk1 , unde
dk1 ak1 ©bk1 (împ rµire întreag ).
Substituind aceste expresii pentru ak ³i bk obµinem
cmmdc a, b xk ˜ ak  yk ˜ bk
xk ˜ bk1  yk ˜ ak1  dk1 ˜ bk1 
yk ˜ ak1  xk  yk ˜ dk1  ˜ bk1 .
Astfel, µinând cont de relaµia xk1 ˜ ak1  yk1 ˜ bk1 cmmdc a, b, obµinem
xk1 yk ,
yk1 xk  yk ˜ dk1 .
Pentru 1134 ³i 308, obµinem:
a0 1134, b0 308, d0 3;
a1 308, b1 210, d1 1;
a2 210, b2 98, d2 2;
a3 98, b3 14, d3 7.
³i de asemenea, valorile pentru xk ³i yk :
x3 0, y3 1;
x2 1, y2 01˜2 2;
x1 2, y1 12˜1 3;
x0 3, y1 2  3 ˜ 3 11.
Desigur relaµia 3 ˜ 1134  11 ˜ 308 14 este corect . Soluµia nu este unic . S  observ m c 
3  k ˜ 308 ˜ 1134  11  k ˜ 1134 ˜ 308 14, pentru orice k , ceea ce arat  c  valorile calculate
pentru x x0 ³i y y0 nu sunt unice.

6.3 Operaµii cu polinoame


CAPITOLUL 6. ALGORITMI ELEMENTARI 64

Toate operaµiile cu polinoame obi³nuite se fac utilizând ³iruri de numere care reprezint  coe-
cienµii polinomului. Not m cu a si b vectorii coecienµilor polinoamelor cu care se opereaz  ³i cu
m ³i n gradele lor. Deci
m n
a X am X  ...  a1 X  a0 ³i b X  bn X  ...  b1 X  b0 .

6.3.1 Adunarea a dou  polinoame

Este asem n toare cu adunarea numerelor mari.

static int[] sumap(int[] a, int[] b)


{
int m,n,k,i,j,minmn;
int[] s;
m=a.length-1;
n=b.length-1;
if(m<n) {k=n; minmn=m;} else {k=m; minmn=n;}
s=new int[k+1];
for(i=0;i<=minmn;i++) s[i]=a[i]+b[i];
if(minmn<m) for(i=minmn+1;i<=k;i++) s[i]=a[i];
else for(i=minmn+1;i<=k;i++) s[i]=b[i];
i=k;
while((s[i]==0)&&(i>=1)) i--;
if(i==k) return s;
else
{
int[] ss=new int[i+1];
for(j=0;j<=i;j++) ss[j]=s[j];
return ss;
}
}

6.3.2 Înmulµirea a dou  polinoame

Evident, gradul polinomului produs p a b este m  n iar coecientul pk este suma tuturor
produselor de forma ai bj unde i  j k , 0 & i & m ³i 0 & j & n.

static int[] prodp(int[] a, int[] b)


{
int m,n,i,j;
int[] p;
m=a.length-1;
n=b.length-1;
p=new int[m+n+1];
for(i=0;i<=m;i++)
for(j=0;j<=n;j++)
p[i+j]+=a[i]*b[j];
return p;
}

6.3.3 Calculul valorii unui polinom

Valoarea unui polinom se calculeaz  ecient cu schema lui Horner:

a x ... an x  an1  x  an2  x  ...  a1  x  a0

static int valp(int[] a, int x)


{
int m,i,val;
CAPITOLUL 6. ALGORITMI ELEMENTARI 65

m=a.length-1;
val=a[m];
for(i=m-1;i>=0;i--)
val=val*x+a[i];
return val;
}

6.3.4 Calculul derivatelor unui polinom

Fie
n n1
b X bn X  bn1 X  ...  b1 X  b0

derivata de ordinul 1 a polinomului


m m1
a X am X  am1 X  ...  a1 X  a0 .
Dar
¬ m1 m2
a X m am X  m  1 am1 X  ...  2 a2 X  a1 .
Rezult  c 
n m1
³i
bi i  1 ai1 pentru 0 & i & n.
static int[] derivp(int[] a)
{
int m,n,i;
int[] b;
m=a.length-1;
n=m-1;
b=new int[n+1];
for(i=0;i<=n;i++)
b[i]=(i+1)*a[i+1];
return b;
}
¬
Pentru calculul valorii v a x a derivatei polinomului a în x este sucient apelul
v=valp(derivp(a),x);.
Dac  vrem s  calcul m derivata de ordinul k ' 0 a polinomului a, atunci
static int[] derivpk(int[] a,int k)
{
int i;
int[] b;
m=a.length-1;
b=new int[m+1];
for(i=0;i<=n;i++)
b[i]=a[i];
for(i=1;i<=k;i++)
b=derivp(b);
return b;
}
k
Pentru calculul valorii v a x a derivatei de ordinul k a polinomului a în x este sucient
apelul
v=valp(derivpk(a,k),x);.

6.4 Operaµii cu mulµimi


O mulµime A se poate memora într-un vector a, ale c rui elemente sunt distincte. Folosind
vectorii putem descrie operaµiile cu mulµimi.
CAPITOLUL 6. ALGORITMI ELEMENTARI 66

6.4.1 Apartenenµa la mulµime

Testul de apartenenµ  a unui element x la o multime A, este prezentat în algoritmul urm tor:

static boolean apartine(int[] a, int x)


{
int i,n=a.length;
boolean ap=false;
for(i=0;i<n;i++)
if(a[i]==x) {ap=true; break;}
return ap;
}

6.4.2 Diferenµa a dou  mulµimi

Diferenµa a dou  mulµimi este dat  de mulµimea

C AB rx¶x " A, x Š B x


Not m card A m.
static int[] diferenta(int[] a, int[] b)
{
int i, j=0, m=a.length;
int[] c=new int[m];
for(i=0;i<m;i++)
if(!apartine(b,a[i]) c[j++]=a[i];
if(j==m) return c;
else
{
int[] cc=new int[j];
for(i=0;i<j;i++) cc[i]=c[i];
return cc;
}
}

6.4.3 Reuniunea ³i intersecµia a dou  mulµimi

Reuniunea a dou  mulµimi este multimea:

C A<B A < B  A.

Introducem în C toate elementele lui A ³i apoi elementele lui B  A.

static int[] reuniune(int[] a, int[] b)


{
int i, j, m=a.length, n=b.length;
int[] c=new int[m+n];
for(i=0;i<m;i++) c[i]=a[i];
j=m;
for(i=0;i<n;i++) if(!apartine(a,b[i]) c[j++]=b[i];
if(j==m+n) return c;
else
{
int[] cc=new int[j];
for(i=0;i<j;i++) cc[i]=c[i];
return cc;
}
}
CAPITOLUL 6. ALGORITMI ELEMENTARI 67

Intersecµia a dou  mulµimi este multimea:

C A=B rx¶x " A ³i x " B x


static int[] reuniune(int[] a, int[] b)
{
int i, j, m=a.length;
int[] c=new int[m];
j=0;
for(i=0;i<m;i++) if(apartine(b,a[i]) c[j++]=a[i];
if(j==m) return c;
else
{
int[] cc=new int[j];
for(i=0;i<j;i++) cc[i]=c[i];
return cc;
}
}

6.4.4 Produsul cartezian a dou  mulµimi

Produs cartezian a doua multimi este multimea:

AB r x, y ¶x " A ³i y " B x


Putem stoca produsul cartezian sub forma unei matrice C cu dou  linii ³i m  n coloane.
Fiecare coloan  a matricei conµine câte un element al produsului cartezian.

static int[][] prodc(int[] a, int[] b)


{
int i, j, k, m=a.length, n=b.length;
int[][] c=new int[2][m*n];
k=0;
for(i=0;i<m;i++)
for(j=0;j<n;j++)
{
c[0][k]=a[i];
c[1][k]=b[j];
k++;
}
return c;
}

De exemplu, pentru A r1, 2, 3, 4x ³i B r1, 2, 3x, matricea C este


0 1 2 3 4 5 6 7 8 9 10 11
linia 0 1 1 1 2 2 2 3 3 3 4 4 4
linia 1 1 2 3 1 2 3 1 2 3 1 2 3

6.4.5 Generarea submulµimilor unei mulµimi

Generarea submulµimilor unei multimi A ra1 , a2 , ..., an x, este identic  cu generarea submul-
µimilor mulµimii de indici r1, 2, ..., nx.
O submulµime se poate memora sub forma unui vector cu n componente, unde ecare com-
ponent  poate avea valori 0 sau 1. Componenta i are valoarea 1 dac  elementul ai aparµine
submulµimii ³i 0 în caz contrar. O astfel de reprezentare se nume³te reprezentare prin vector
caracteristic.
Generarea tuturor submulµimilor înseamn  generarea tuturor combinaµiilor de 0 ³i 1 care pot
 reµinute de vectorul caracteristic V , adic  a tuturor numerelor în baza 2 care se pot reprezenta
folosind n cifre.
CAPITOLUL 6. ALGORITMI ELEMENTARI 68

Pentru a genera adunarea în binar, µinem cont c  trecerea de la un ordin la urm torul se face
când se obµine suma egal  cu 2, adic  1  1 102 .
poziµia 1 2 3 4
De exemplu, pentru n 4, vom folosi un vector v
valoarea
iniµial 0 0 0 0 ³i adun m 1
obµinem 0 0 0 1 ³i adun m 1
obµinem 0 0 0 2 care nu este permis, ³i trecem la ordinul urm tor
obµinem 0 0 1 0 ³i adun m 1
obµinem 0 0 1 1 ³i adun m 1
obµinem 0 0 1 2 care nu este permis, ³i trecem la ordinul urm tor
obµinem 0 0 2 0 care nu este permis, ³i trecem la ordinul urm tor
obµinem 0 1 0 0 ³i a³a mai departe
obµinem pân  când
obµinem 1 1 1 1
n
Aceste rezultate se pot reµine într-o matrice cu n linii ³i 2 coloane.
0 1 2 3 4 5 6 7 8 9 A B C D E F
a1 0 0 0 0 0 0 0 0 1 1 1 1 1 1 1 1 0
a2 0 0 0 0 1 1 1 1 0 0 0 0 1 1 1 1 1
a3 0 0 1 1 0 0 1 1 0 0 1 1 0 0 1 1 2
a4 0 1 0 1 0 1 0 1 0 1 0 1 0 1 0 1 3
Ultima coloan  conµine num rul liniei din matrice. Coloana 0 reprezint  mulµimea vid , co-
loana F reprezint  întreaga mulµime, ³i, de exemplu, coloana 5 reprezint  submultimea ra2 , a4 x
iar coloana 7 reprezint  submultimea ra2 , a3 , a4 x.

static int[][] submultimi(int n)


{
int i, j, nc=1;
int[] v=new int[n+1];
int[][] c;
for(i=1;i<=n;i++) nc*=2;
c=new int[n][nc];
for(i=1;i<=n;i++) v[i]=0;
j=0;
while(j<nc)
{
v[n]=v[n]+1;
i=n;
while(v[i]>1) { v[i]=v[i]-2; v[i-1]=v[i-1]+1; i--; }
for(i=1;i<=n;i++) c[j][i-1]=v[i];
j++;
}
return c;
}

6.5 Operaµii cu numere întregi mari


Operaµiile aritmetice sunt denite numai pentru numere reprezentate pe 16, 32 sau 64 biµi.
Dac  numerele sunt mai mari, operaµiile trebuie implementate de utilizator.

6.5.1 Adunarea ³i sc derea

Adunarea ³i sc derea sunt directe: aplicând metodele din ³coala elementar .


static int[] suma(int[] x, int[] y)
{
int nx=x.length;
CAPITOLUL 6. ALGORITMI ELEMENTARI 69

int ny=y.length;
int nz;
if(nx>ny)
nz=nx+1;
else
nz=ny+1;
int[] z=new int[nz];
int t,s,i;
t=0;
for (i=0;i<=nz-1;i++)
{
s=t;
if(i<=nx-1)
s=s+x[i];
if(i<=ny-1)
s=s+y[i];
z[i]=s%10;
t=s/10;
}
if(z[nz-1]!=0)
return z;
else
{
int[] zz=new int[nz-1];
for (i=0;i<=nz-2;i++) zz[i]=z[i];
return zz;
}
}

6.5.2 Inmulµirea ³i împ rtirea


Metoda înv µat  în ³oal  este corect .

static int[] produs(int[]x,int[]y)


{
int nx=x.length;
int ny=y.length;
int nz=nx+ny;
int[] z=new int[nz];
int[] [] a=new int[ny][nx+ny];
int i,j;
int t,s;
for(j=0;j<=ny-1;j++)
{
t=0;
for(i=0;i<=nx-1;i++)
{
s=t+y[j]*x[i];
a[j][i+j]=s%10;
t=s/10;
}
a[j][i+j]=t;
}
t=0;
for(j=0;j<=nz-1;j++)
{
s=0;
for(i=0;i<=ny-1;i++)
s=s+a[i][j];
s=s+t;
z[j]=s%10;
CAPITOLUL 6. ALGORITMI ELEMENTARI 70

t=s/10;
}
if(z[nz-1]!=0)
return z;
else
{
int[] zz=new int [nz-1];
for(j=0;j<=nz-2;j++)
zz[j]=z[j];
return zz;
}
}

6.5.3 Puterea
n
Presupunem c  vrem s  calcul m x . Cum facem acest lucru? Este evident c  urm toarea
secvenµ  funcµioneaz :

for (p = 1, i = 0; i < n; i++) p *= x;


Presupunând c  toate înmulµirile sunt efectuate într-o unitate de timp, acest algoritm are
complexitatea O n. Totu³i, putem s  facem acest lucru mai repede! Presupunând, pentru
k
început, c  n 2 , urm torul algoritm este corect:

for (p = x, i = 1; i < n; i *= 2) p *= p;
Aici num rul de treceri prin ciclu este egal cu k log2 n.
Acum, s  consider m cazul general. Presupunem c  n are expresia binar  bk , bk1 , ..., b1 , b0 .
Atunci putem scrie

=
k
i
n 2.
i 0,bi 1

Deci,

5
k i
n 2
x x .
i 0,bi 1

int exponent_1(int x, int n)


{
int c, z;
for (c = x, z = 1; n != 0; n = n / 2)
{
if (n & 1) /* n este impar */
z *= c;
c *= c;
}
return z;
}

int exponent_2(int x, int n)


{
if (n == 0)
return 1;
if (n & 1) /* n este impar */
return x * exponent_2(x, n - 1);
return exponent_2(x, n / 2) * exponent_2(x, n / 2);
}

int exponent_3(int x, int n)


{
int y;
if (n == 0)
CAPITOLUL 6. ALGORITMI ELEMENTARI 71

return 1;
if (n & 1) /* n este impar */
return x * exponent_3(x, n - 1);
y = exponent_3(x, n / 2);
return y * y;
}

6.6 Operaµii cu matrice

6.6.1 Înmulµirea

O funcµie scris  în C/C++:

void matrix_product(int** A, int** B, int** C)


{
for (i = 0; i < n; i++)
for (j = 0; j < n; j++)
{
C[i][j] = 0;
for (k = 0; k < n; k++)
C[i][j] += A[i][k] * B[k][j];
}
}

6.6.2 Inversa unei matrice

O posibilitate este cea din ³coal . Aceasta presupune calculul unor determinanµi. Determi-
nantul det A se dene³te recursiv astfel:

=
n1
ij
det A 1 ˜ ai,j ˜ det Ai,j .
i 0

unde ai,j este element al matricei iar Ai,j este submatricea obµinut  prin eliminarea liniei i ³i a
coloanei j .

int determinant(int n, int[][] a)


{
if (n == 1)
return a[0][0];
int det = 0;
int sign = 1;
int[][] b = new int[n - 1][n - 1];
for (int i = 0; i < n; i++)
{
for (int j = 0; j < i; j++)
for (int k = 1; k < n; k++)
b[j][k - 1] = a[j][k];
for (int j = i + 1; j < n; j++)
for (int k = 1; k < n; k++)
b[j - 1][k - 1] = a[j][k];
det += sign * a[i][0] * determinant(n - 1, b);
sign *= -1;
}
}
CAPITOLUL 6. ALGORITMI ELEMENTARI 72

Folosind determinanµi, inversa matricei se poate calcula folosind regula lui Cramer. Presupu-
nem c  A este inversabil  ³i e B bi,j  matricea denit  prin
ij
bi,j 1 ˜ det Ai,j © det A.
1 T T
Atunci A B , unde B este transpusa matricei B .
Capitolul 7

Algoritmi combinatoriali

7.1 Principiul includerii ³i al excluderii ³i aplicaµii

7.1.1 Principiul includerii ³i al excluderii

Fie A ³i B dou  mulµimi nite. Not m prin ¶A¶ cardinalul mulµimii A. Se deduce u³or c :

¶A < B ¶ ¶A¶  ¶B ¶  ¶A = B ¶.

Fie A o mulµime nit  ³i A1 , A2 , ..., An submulµimi ale sale. Atunci num rul elementelor lui A
care nu apar în nici una din submulµimile Ai (i 1, 2, ..., n) este egal cu:

=A = =
n
n
¶A¶  ¶ i¶  ¶Ai = Aj ¶  ¶Ai = Aj = Ak ¶  ...  1 ¶A1 = A2 = ... = An ¶
i 1 1&i$j &n 1&i$j $k&n

Se pot demonstra prin inducµie matematic  urm toarele formule:

A = A = = A
n n n
n1
¶ i¶ ¶ i¶  ¶Ai = Aj ¶  ¶Ai = Aj = Aj ¶  ...  1 ¶ i¶
i 1 i 1 1&i$j &n 1&i$j $k&n i 1

A = A = = A
n n n
n1
¶ i¶ ¶ i¶  ¶Ai < Aj ¶  ¶Ai < Aj < Aj ¶  ...  1 ¶ i¶
i 1 i 1 1&i$j &n 1&i$j $k&n i 1

7.1.2 Num rul funcµiilor surjective

Se dau mulµimile X rx1 , x2 , ..., xm x ³i Y ry1 , y2 , ..., yn x.


Fie Sm,n num rul funcµiilor surjective f  X Y. º
Fie A rf ¶f  X º
Y x (mulµimea tuturor funcµiilor denite pe X cu valori în Y ) ³i
Ai rf ¶f  X º
Y, yi Š f X x (mulµimea funcµiilor pentru care yi nu este imaginea nici unui
element din X ).
Atunci
A
n
Sm,n ¶A¶  ¶ i¶
i 1
Folosind principiul includerii ³i al excluderii, obµinem

=A =
n
n
Sm,n ¶A¶  ¶ i¶  ¶Ai = Aj ¶  ...  1 ¶A1 = A2 = ... = An ¶
i 1 1&i$j &n

m m m
Se poate observa u³or c  ¶A¶ n , ¶Ai ¶ n  1 , ¶Ai = Aj ¶ n  2 , etc.
k
Din Y putem elimina k elemente în Cn moduri, deci

= A
k
k m
¶ ij ¶ Cn n  k 
1&i1 $i2 $...$ik &n j 1

73
CAPITOLUL 7. ALGORITMI COMBINATORIALI 74

Rezult :
m 1 m 2 m n1 n1
Sm,n n  Cn n  1  Cn n  2  ...  1 Cn
Observaµii:
1. Deoarece A1 = A2 = ... = An o ³i pentru c  nu poate exista o funcµie care s  nu ia nici o
valoare, ultimul termen lipse³te.
2. Dac  n m atunci num rul funcµiilor surjective este egal cu cel al funcµiilor injective, deci
Sm,n n! ³i se obµine o formul  interesant :

=
n1
k k n
n! 1 Cn n  k 
k 0

class Surjectii
{
public static void main (String[]args)
{
int m, n=5, k, s;
for(m=2;m<=10;m++)
{
s=0;
for(k=0;k<=n-1;k++)
s=s+comb(n,k)*putere(-1,k)*putere(n-k,m);
System.out.println(m+" : "+s);
}
System.out.println("GATA");
}

static int putere (int a, int n)


{
int rez=1, k;
for(k=1;k<=n;k++) rez=rez*a;
return rez;
}

static int comb (int n, int k)


{
int rez, i, j, d;
int[] x=new int[k+1];
int[] y=new int[k+1];
for(i=1;i<=k;i++) x[i]=n-k+i;
for(j=1;j<=k;j++) y[j]=j;
for(j=2;j<=k;j++)
for(i=1;i<=k;i++)
{
d=cmmdc(y[j],x[i]);
y[j]=y[j]/d;
x[i]=x[i]/d;
if(y[j]==1) break;
}
rez=1;
for(i=1;i<=k;i++) rez=rez*x[i];
return rez;
}

static int cmmdc (int a,int b)


{
int d,i,c,r;
if (a>b) {d=a;i=b;} else{d=b;i=a;}
while (i!=0) { c=d/i; r=d%i; d=i; i=r; }
return d;
}
}
CAPITOLUL 7. ALGORITMI COMBINATORIALI 75

7.1.3 Num rul permut rilor f r  puncte xe

Fie X r1, 2, ..., nx. Dac  p este o permutare a elementelor mulµimii X , spunem c  num rul
i este un punct x al permut rii p, dac  p i i (1 & i & n).
Se cere s  se determine num rul D n al permut rilor f r  puncte xe, ale mulµimii X . S 
not m cu Ai mulµimea celor n  1! permut ri care admit un punct x în i (dar nu obligatoriu
numai acest punct x!). Folosind principiul includerii ³i al excluderii, num rul permut rilor care
admit cel puµin un punct x este egal cu:

=A = A .
n n
n1
¶A1 < A2 < ... < An ¶ ¶ i¶  ¶Ai = Aj ¶  ...  1 ¶ i¶
i 1 1&i$j &n i 1

Dar
¶Ai1 = Ai2 = ... = Aik ¶ n  k !
deoarece o permutare din mulµimea Ai1 = Ai2 = ... = Aik are puncte xe în poziµiile i1 , i2 , ..., ik ,
celelalte poziµii conµinând o permutare a celor n  k elemente r mase (care pot avea sau nu puncte
k
xe!). Cele k poziµii i1 , i2 , ..., ik pot  alese în Cn moduri, deci
1 2 n1 n
¶A1 < A2 < ... < An ¶ Cn n  1!  Cn n  2!  ...  1 Cn .

Atunci

D n n!  ¶A1 < A2 < ... < An ¶


1 2 n n
n!  Cn n  1!  Cn n  2!  ...  1 Cn
n
1 1 1 1
n! 1     ... 
.
1! 2! 3! n!

De aici rezult  c 
D n 1
e , lim
n! n ™

deci, pentru n mare, probabilitatea ca o permutare a n elemente, aleas  aleator, s  nu aib  puncte
xe, este de e  0.3678.
1

Se poate demonstra u³or c :


n1
D n  1 n  1D n  1
D n  1 n D n  D n  1 .

class PermutariFixe
{
public static void main(String [] args)
{
long n=10,k,s=0L,xv,xn; // n=22 maxim pe long !
if((n&1)==1) xv=-1L; else xv=1L;
s=xv;
for(k=n;k>=3;k--) { xn=-k*xv; s+=xn; xv=xn; }
System.out.println("f("+n+") = "+s);
}
}

7.2 Principiul cutiei lui Dirichlet ³i aplicaµii


Acest principiu a fost formulat prima dat  de Dirichle (1805-1859).
În forma cea mai simpl  acest principiu se enunµ  astfel:

Dac  n obiecte trebuie împ rµite în mai puµin de n mulµimi, atunci exist  cel puµin o
mulµime în care vor  cel puµin dou  obiecte.

Mai general, principiul lui Dirichlet se poate enunµa astfel:


CAPITOLUL 7. ALGORITMI COMBINATORIALI 76

Fiind date m obiecte, care trebuie împ rµite în n mulµimi, ³i un num r natural k astfel
încât m % kn, atunci, în cazul oric rei împ rµiri, va exista cel puµin o mulµime cu cel
puµin k  1 obiecte.

Pentru k 1 se obµine formularea anterioar .


Cu ajutorul funcµiilor, principiul cutiei se poate formula astfel:
Fie A ³i B dou  mulµimi nite cu ¶A¶ % ¶B ¶ ³i funcµia f  A º B. Atunci, exist 
b " B cu proprietatea c  ¶f b¶ ' 2. Dac  not m ¶A¶
1
n ³i ¶B ¶ r atunci
b¶ ' r &.
1 n
¶f

Demonstr m ultima inegalitate. Dac  aceasta nu ar  adev rat , atunci


n
b¶ $ ! r ' , ¾b " B.
1
¶f

Dar mulµimea B are r elemente, deci

n =f
b"B

1 n
b¶ $ r r n

ceea ce este o contradicµie.

7.2.1 Problema subsecvenµei

Se d  un ³ir nit a1 , a2 , ..., an de numere întregi. Exist  o subsecvenµ  ai , ai1 , ..., aj cu pro-
prietatea c  ai  ai1  ...  aj este un multiplu de n.
S  consider m urm toarele sume:

s1 a1 ,
s2 a1  a2 ,
...
sn a1  a2  ...  an .

Dac  exist  un k astfel sk este multiplu de n atunci i 1 ³i j k .


Dac  nici o sum  parµial  sk nu este multiplu de n, atunci resturile împ rµirii acestor sume
parµiale la n nu pot  decât în mulµimea r1, 2, ..., n  1x. Pentru c  avem n sume parµiale ³i numai
n  1 resturi, înseamn  c  exist  cel puµin dou  sume parµiale (sk1 ³i sk2 , unde k1 $ k2 ) cu acela³i
rest. Atunci subsecvenµa c utat  se obµine luând i k1  1 ³i j k2 .

7.2.2 Problema sub³irurilor strict monotone

Se d  ³irul de numere reale distincte a1 , a2 , ..., amn1 . Atunci, ³irul conµine un sub³ir cresc tor
de m  1 elemente:

ai1 $ ai $ ... $ ai
2 m1
unde 1 & i1 $ i2 $ ... $ im 1 & mn  1,


sau un sub³ir descresc tor de n  1 elemente

aj1 $ aj $ ... $ aj
2 n1
unde 1 & j1 $ j2 $ ... $ jn 1 & mn  1,


sau ambele tipuri de sub³iruri.


Fiec rui element al ³irului îi asociem perechea de numere naturale xi , yi  unde xi este lungimea
maxim  a sub³irurilor cresc toare care încep cu ai iar yi este lungimea maxim  a sub³irurilor
descresc toare care încep în ai .
Presupunem c  armaµia problemei nu este adev rat , adic : pentru toate numerele naturale
xi ³i yi avem 1 & xi & m ³i 1 & yi & n. Atunci perechile de numere xi , yi  pot avea mn elemente
distincte.
Deoarece ³irul are mn  1 termeni, exist  un ai ³i un aj pentru care perechile de numere xi , yi 
³i xj , yj  sunt identice (xi xj , yi yj ), dar acest lucru este imposibil (cei doi termeni ai ³i aj
ar trebui s  coincid ), ceea ce este o contradiµie.
Deci exist  un sub³ir cresc tor cu m  1 termeni sau un sub³ir descresc tor cu n  1 termeni.
CAPITOLUL 7. ALGORITMI COMBINATORIALI 77

7.3 Numere remarcabile

7.3.1 Numerele lui Fibonacci

Numerele lui Fibonacci se pot deni recursiv prin:

F0 0, F1 1, Fn Fn1  Fn2 pentru n ' 2. (7.3.1)

Primele numere Fibonacci sunt:

0, 1, 1, 2, 3, 5, 8, 13, 21, 34, 55, 89, 144, 233, 377, 610, 987, 1597, ...

Se poate ar ta c 
1 Ó n Ó n
Fn Ó 1  5  1  5  .
n
2 5
Numerele lui Fibonacci satisfac multe identit µi interesante, ca de exemplu:
n
1 1 Fn1 Fn


(7.3.2)
1 0 Fn Fn1
2 n
Fn1 Fn1  Fn 1 (7.3.3)
Fnm Fm Fn1  Fm1 Fn (7.3.4)
Fnk multiplu de Fk (7.3.5)
(7.3.6)

³i

F2  F4  ...  F2n F2n1  1 (7.3.7)


F1  F3  ...  F2n1 F2n (7.3.8)
2 2 2
F1  F2  ...  Fn Fn Fn1 (7.3.9)
2
F1 F2  F2 F3  ...  F2n1 F2n F2n (7.3.10)
2
F1 F2  F2 F3  ...  F2n F2n1 F2n1  1 (7.3.11)

Teorema 2. Orice num r natural n se poate descompune într-o sum  de numere Fibonacci. Dac 
nu se folosesc în descompunere numerele F0 ³i F1 ³i nici dou  numere Fibonacci consecutive,
atunci aceast  descompunere este unic  abstracµie f când de ordinea termenilor.

Folosind aceast  descompunere, numerele naturale pot  reprezentate asem n tor reprezent rii


în baza 2. De exemplu

19 1 13  0 8  1 5  0 3  0 2  1 1 101001F

În aceast  scriere nu pot exista dou  cifre 1 al turate.

import java.io.*;
class DescFibo
{
static int n=92;
static long[] f=new long[n+1];

public static void main (String[]args) throws IOException


{
int iy, k, nrt=0;
long x,y; // x=1234567890123456789L; cel mult!
BufferedReader br=new BufferedReader(new InputStreamReader(System.in));
System.out.print("x = ");
x=Long.parseLong(br.readLine());
f[0]=0;
f[1]=1;
CAPITOLUL 7. ALGORITMI COMBINATORIALI 78

f[2]=1;
for(k=3;k<=n;k++) f[k]=f[k-1]+f[k-2];
for(k=0;k<=n;k++) System.out.println(k+" : "+f[k]);
System.out.println(" "+Long.MAX_VALUE+" = Long.MAX_VALUE");
System.out.println(" x = "+x);
while(x>0)
{
iy=maxFibo(x);
y=f[iy];
nrt++;
System.out.println(nrt+" : "+x+" f["+iy+"] = "+y);
x=x-y;
}
}

static int maxFibo(long nr)


{
int k;
for(k=1;k<=n;k++) if (f[k]>nr) break;
return k-1;
}
}

7.3.2 Numerele lui Catalan

Numerele
1 n
Cn C
n  1 2n
se numesc numerele lui Catalan. Ele apar în multe probleme, ca de exemplu: num rul arborilor
binari, num rul de parantez ri corecte, num rul drumurilor sub diagonal  care unesc punctele
0, 0 ³i n, n formate din segmente orizontale ³i verticale, num rul secvenµelor cu n biµi în
care num rul cifrelor 1 nu dep ³e³te num rul cifrelor 0 în nici o poziµie plecând de la stânga spre
dreapta, num rul segmentelor care unesc 2n puncte în plan f r  s  se intersecteze, num rul ³irurilor
x1 , x2 , ..., x2n  în care xi " r1, 1x ³i x1  x2  ...  x2n 0 cu proprietatea x1  x2  ...  xi ' 0
pentru orice i 1, 2, ..., 2n  1, num rul modurilor de a triangulariza un poligon, ³i multe altele.
Numerele lui Catalan sunt soluµie a urm toarei ecuaµii de recurenµ :

Cn1 C0 Cn  C1 Cn1  ...  Cn C0 , pentru n ' 0 ³i C0 1.

Numerele lui Catalan veric  ³i relaµia:


4n  2
Cn1 C
n2 n
O implementare cu numere mari este:
class Catalan
{
public static void main (String[]args)
{
int n;
int[] x;
for(n=1;n<=10;n++)
{
x=Catalan(n);
System.out.print(n+" : ");
afisv(x);
}
}

static int[] inm(int[]x,int[]y)


{
CAPITOLUL 7. ALGORITMI COMBINATORIALI 79

int i, j, t, n=x.length, m=y.length;


int[][]a=new int[m][n+m];
int[]z=new int[m+n];
for(j=0;j<m;j++)
{
t=0;
for(i=0;i<n;i++)
{
a[j][i+j]=y[j]*x[i]+t;
t=a[j][i+j]/10;
a[j][i+j]=a[j][i+j]%10;
}
a[j][i+j]=t;
}
t=0;
for(j=0;j<m+n;j++)
{
z[j]=t;
for(i=0;i<m;i++) z[j]=z[j]+a[i][j];
t=z[j]/10;
z[j]=z[j]%10;
}
if(z[m+n-1]!= 0) return z;
else
{
int[]zz=new int[m+n-1];
for(i=0;i<=m+n-2;i++) zz[i]=z[i];
return zz;
}
}

static void afisv(int[]x)


{
int i;
for(i=x.length-1;i>=0;i--) System.out.print(x[i]);
System.out.print(" *** "+x.length);
System.out.println();
}

static int[] nrv(int nr)


{
int nrrez=nr;
int nc=0;
while(nr!=0) { nc++; nr=nr/10; }
int[]x=new int [nc];
nr=nrrez;
nc=0;
while(nr!=0) { x[nc]=nr%10; nc++; nr=nr/10; }
return x;
}

static int[] Catalan(int n)


{
int[] rez;
int i, j, d;
int[] x=new int[n+1];
int[] y=new int[n+1];
for(i=2;i<=n;i++) x[i]=n+i;
for(j=2;j<=n;j++) y[j]=j;
for(j=2;j<=n;j++)
CAPITOLUL 7. ALGORITMI COMBINATORIALI 80

for(i=2;i<=n;i++)
{
d=cmmdc(y[j],x[i]);
y[j]=y[j]/d;
x[i]=x[i]/d;
if(y[j]==1) break;
}
rez=nrv(1);
for(i=2;i<=n;i++) rez=inm(rez,nrv(x[i]));
return rez;
}

static int cmmdc (int a,int b)


{
int d,i,c,r;
if (a>b) {d=a;i=b;} else{d=b;i=a;}
while (i!=0) { c=d/i; r=d%i; d=i; i=r; }
return d;
}
}

7.4 Descompunerea în factori primi

7.4.1 Funcµia lui Euler

Funcµia φ n a lui Euler ne d  num rul numerelor naturale mai mici ca n ³i prime cu n.


Num rul n poate  descompus în factori primi sub forma:
α α α
n p1 1 p2 2 ...pmm

Not m cu Ai mulµimea numerelor naturale mai mici ca n care sunt multipli de pi . Atunci avem:
n n n
¶Ai ¶
pi , ¶Ai = Aj ¶ pi pj , ¶Ai = Aj = Ak ¶ pi pj pk , ...

Rezult :

= pn = =
m
n n m n
φ n n 
pi pj 
pi pj pk  ...  1
p1 p2 ...pm
i
i 1 1&i$j &m 1&i$j $k&m

care este tocmai dezvoltarea produsului

1 1 1
φ n n 1  p
1  p
... 1  p

1 2 m

class Euler
{
static long[] fact;
public static void main (String[]args)
{
long n=36L; // Long.MAX_VALUE=9.223.372.036.854.775.807;
long nrez=n;
long[] pfact=factori(n);
// afisv(fact);
// afisv(pfact);
int k,m=fact.length-1;
for(k=1;k<=m;k++) n/=fact[k];
for(k=1;k<=m;k++) n*=fact[k]-1;
System.out.println("f("+nrez+") = "+n);
CAPITOLUL 7. ALGORITMI COMBINATORIALI 81

static long[] factori(long nr)


{
long d, nrrez=nr;
int nfd=0; // nr. factori distincti
boolean gasit=false;
while((nr!=1)&(nr%d==0)) { nr=nr/d; gasit=true; }
if(gasit) {nfd++;gasit=false;}
d=3;
while(nr!=1)
{
while((nr!=1)&(nr%d==0)) { nr=nr/d; gasit=true; }
if(gasit) {nfd++;gasit=false;}
d=d+2;
}
nr=nrrez;
fact=new long[nfd+1];
long[] pf=new long[nfd+1];
int pfc=0; // puterea factorului curent
nfd=0; // nr. factori distincti
gasit=false;
d=2;
while((nr!=1)&(nr%d==0)) { nr=nr/d; gasit=true; pfc++; }
if(gasit) {fact[++nfd]=d;pf[nfd]=pfc;gasit=false;pfc=0;}
d=3;
while(nr!=1)
{
while((nr!=1)&(nr%d==0)) { nr=nr/d; gasit=true; pfc++; }
if(gasit) {fact[++nfd]=d;pf[nfd]=pfc;gasit=false;pfc=0;}
d=d+2;
}
return pf;
}//descfact

static void afisv(long[] a)


{
for(int i=1;i<a.length;i++) System.out.print(a[i]+" ");
System.out.println();
}
}

7.4.2 Num rul divizorilor

Fie
e e e
n f1 1 f2 2 ... fk k
descompunerea lui n în factori primi ³i ndiv n num rul divizorilor lui n. Atunci

ndiv n 1  e1  1  e2  ... 1  ek .

7.4.3 Suma divizorilor

Fie
e e e
n f1 1 f2 2 ... fk k
descompunerea lui n în factori primi ³i sdiv n suma divizorilor lui n. Atunci

=d
1e1  1e2  1ek 
f1  1 f2  1 fk  1
sdiv n ... .
f1  1 f2  1 fk  1
d¶n
CAPITOLUL 7. ALGORITMI COMBINATORIALI 82

Demonstraµie:
Fie n ab cu a j b ³i cmmdc a, b 1. Pentru orice divizor d al lui n, d ai bj , unde ai este
divizor al lui a iar bj este divizor al lui b. Divizorii lui a sunt: 1, a1 , a2 , ..., a. Divizorii lui b sunt:
1, b1 , b2 , ..., b.
Sumele divizorilor lui a ³i b sunt:

sdiv a 1  a1  a2  ...  a

sdiv b 1  b1  b2  ...  b.

= d =a b =a =b
Dar
sdiv ab i j i j sdiv a sdiv b
d¶ab i,j i j

De aici rezult  c :


e e e e e e
sdiv f1 1 f2 2 ... fk k  sdiv f1 1  sdiv f2 2  ... sdiv fk k 

³i mai departe rezult  relaµia dat  iniµial!

7.5 Partiµia numerelor

7.5.1 Partiµia lui n în exact k termeni

Fie P n, k  num rul modalit µilor de a descompune num rul natural n ca sum  de exact k


termeni nenuli, considerând dou  descompuneri ca ind distincte dac  difer  prin cel puµin un
termen (deci, f r  a µine cont de ordinea termenilor).
Atunci
P n, k  P n  k, 1  P n  k, 2  ...  P n  k, k 
unde
P i, 1 P i, i 1 pentru ¾i '1
³i
n a1  a2  ...  ak ; a1 ' a2 ' ... ' ak ' 1.
Demonstraµie:
Ultimul termen ak poate  1 sau ' 2.
Pentru ak 1 avem P n  1, k  1 posibilit µi de alegere a factorilor a1 , a2 , ..., ak1 astfel
încât n  1 a1  a2  ...  ak1 ³i a1 ' a2 ' ... ' ak1 ' 1
Pentru ak ' 2 putem s  sc dem 1 din toµi factorii descompunerii lui n ³i obµinem n  k
a1  a2  ...  ak unde a1 ' a2 ' ... ' ak ' 1, deci num rul descompunerilor lui n cu ak ' 2 este
¬ ¬ ¬ ¬ ¬ ¬

P n  k, k .
Obµinem relaµia
P n, k  P n  1, k  1  P n  k, k 
care poate  transformat  astfel:

P n, k  P n  1, k  1  P n  k, k 
P n  1, k  1 P n  2, k  2  P n  k, k  1
P n  2, k  2 P n  3, k  3  P n  k, k  2
...
P n  k  3, 3 P n  k  2, 2  P n  k, 3
P n  k  2, 2 P n  k  1, 1  P n  k, 2

Prin reducere ³i µinând cont c  P n  k  1, 1 1 P n  k, 1 obµinem relaµia dat  la


început.
CAPITOLUL 7. ALGORITMI COMBINATORIALI 83

7.5.2 Partiµia lui n în cel mult k termeni

Fie A n, k  num rul modalit µilor de a descompune num rul natural n ca sum  de cel mult


k termeni nenuli, considerând dou  descompuneri ca ind distincte dac  difer  prin cel puµin un
termen (deci, f r  a µine cont de ordinea termenilor).
Atunci
A n, k  A n, k  1  A n  k, k 
Evident A i, j  A i, i pentru orice j % i iar A n, n reprezint  num rul partiµiilor lui n.
(1) Num rul partiµiilor în cel mult k factori este egal cu num rul partiµiilor în exact k factori
plus num rul partiµiilor în cel mult k  1 termeni.
(2) Dat  ind o partiµie a lui n în exact k termeni nenuli, putem sc dea 1 din ecare termeni,
obµinând o partiµie a lui n  k în cel mult k termeni nenuli. Astfel, exist  o corespondenµ 
bijectiv  între partiµiile lui n în exact k termeni ³i partiµiile lui n  k în cel mult k factori.

7.5.3 Partiµii multiplicative

Fie A n, k  num rul modalit µilor de a descompune num rul natural n ca sum  de cel mult


k termeni nenuli, considerând dou  descompuneri ca ind distincte dac  difer  prin cel puµin un
termen (deci, f r  a µine cont de ordinea termenilor).

7.6 Partiµia mulµimilor


Fie S n, k  num rul modalit µilor de a partiµiona o mulµime A cu n elemente în k submulµimi
nevide, considerând dou  partiµii ca ind distincte dac  difer  prin cel puµin o submulµime (deci,
f r  a µine cont de ordinea submulµimilor).
Atunci
S n, k  S n  1, k  1  k S n  1, k 
unde
S i, 1 S i, i 1 pentru ¾i '1
³i
A A1 < A2 < ... < Ak ; Ai = Aj o pentru i j j.
Demonstraµie:
Fie
A ra1 , a2 , ..., an x

o mulµime cu n elemente ³i
A1 , A2 , ..., Ak
o partiµie oarecare.
Elementul an poate 

(1) într-o submulµime cu un singur element (chiar el!), sau

(2) într-o submulµime cu cel puµin 2 elemente (printre care se g ³e³te ³i el!).

Num rul partiµiilor de tipul (1) este S n  1, k  1 (f r  elementul an r mân n  1 elemente


³i k  1 submulµimi în partiµie).
Num rul partiµiilor de tipul (2) este k S n  1, k  (eliminând elementul an din submulµimea
în care se a , acea submulµime r mâne nevid !). Se pare c  nu este prea clar (sau evident!) de
ce apare totu³i factorul k în expresia k S n  1, k ! S  privim puµin altfel lucrurile: consider m
toate partiµiile mulµimii ra1 , a2 , ..., an1 x care au k submulµimi; num rul acestora este S n  1, k ;
introducerea elementului an în aceste partiµii se poate face în oricare din cele k submulµimi; deci
k S n  1, k  reprezint  num rul partiµiilor din cazul (2).
CAPITOLUL 7. ALGORITMI COMBINATORIALI 84

7.7 Probleme rezolvate


1. S  se determine num rul arborilor binari cu n vârfuri.
Rezolvare: Fie b n num rul arborilor binari cu n vârfuri. Prin convenµie b0 1. Prin desene
b1 1, b2 2, b3 5, ³i: dac  x m r d cina arborelui, ne mai r mân n  1 vârfuri care pot
ap rea în subarborele stâng sau drept; dac  în subarborele stâng sunt k vârfuri, în subarborele
drept trebuie s  e n  1  k vârfuri; cu ace³ti subarbori se pot forma în total bk bn1k arbori;
adunând aceste valori pentru k 0, 1, ..., n  1 vom obµine valoarea lui bn . Deci, pentru n ' 1

=b b
n1
bn b0 bn1  b1 bn2  ...  bn1 b0 k n1k
k 0

Se obµine
1 n
C bn
n  1 2n
2. Care este num rul permut rilor a n obiecte cu p puncte xe?
p
Rezolvare: Deoarece cele p puncte xe pot  alese în Cn moduri, ³i cele n  p puncte r mase
nu mai sunt puncte xe pentru permutare, rezult  c  num rul permut rilor cu p puncte xe este
egal cu
p
Cn D n  p
deoarece pentru ecare alegere a celor p puncte xe exist  D n  p permut ri ale obiectelor
r mase, f r  puncte xe.
21
3. Fie X r1, 2, ..., nx. Dac  E n reprezint  num rul permut rilor pare ale mulµimii X
f r  puncte xe, atunci
1 n1
E n D n  1 n  1 .
2
Rezolvare: Fie Ai mulµimea permut rilor pare p astfel încât p i i. Deoarece num rul
permut rilor pare este 12 n!, rezult  c 

1
E n n!  ¶A1 < ... < An ¶
2
1 1 2 n n
n!  Cn n  1!  Cn n  2!  ...  1 Cn .
2
•inând cont c 
¶Ai1 = Ai2 = ... = Aik ¶ n  k !
rezult  formula cerut .

21
Dac  i $ j, ³i în permutare i apare dup  j, spunem c  avem o inversiune. O permutare este par  dac  are un
num r par de inversiuni
Capitolul 8

Algoritmi de c utare

8.1 Problema c ut rii


Problema c ut rii este: se d  un vector a cu n elemente ³i o valoare x de acela³i tip cu
elementele din a. S  se determine p astfel încât x ap sau 1 dac  nu exist  un element cu
valoarea v în a.
O tabel  cu câmpuri care nu sunt de acela³i tip se poate organiza cu ajutorul vectorilor dac 
num rul de coloane este sucient de mic. De exemplu, o tabel  cu trei informaµii: num r curent,
nume, telefon poate  organizat  cu ajutorul a doi vectori (nume ³i telefon) iar num rul curent
este indicele din vector.

8.2 C utarea secvenµial 

static int cauta (String x) {


for (int i = 0; i < N; ++i)
if (x.equals(nume[i])) return telefon[i];
return 1;
}
se poate scrie ³i sub forma:

static int cauta (String x) {


int i = 0;
while (i < N && !x.equals(nume[i])) ++i;
if (i < N) return telefon[i];
else return 1;
}
O alt  posibilitate este de a pune o santinel  în cap tul tabelei.

static int cauta (String x) {


int i = 0;
nume[N] = x; telefon[N] = 1;
while (! x.equals(nume[i])) ++i;
return tel[i];
}

Scrierea procedurii de c utare într-un tabel de nume este în acest caz mai ecient , pentru c 
nu se face decât un singur test în plus aici (în loc de dou  teste). C utarea secvenµial  se mai
nume³te ³i c utare linear , pentru c  se execut  N ©2 operaµii în medie, ³i N operaµii în cazul cel
mai defavorabil. Într-un tablou cu 10.000 elemente, c utarea execut  5.000 operaµii în medie, ceea
ce înseamn  un consum de timp de aproximativ 0.005 ms (milisecunde).
Iat  un program complet care utilizeaz  c utarea linear  într-o tabel .

85
CAPITOLUL 8. ALGORITMI DE C€UTARE 86

class Tabela {

final static int N = 6;


static String nume[] = new String[N+1];
static int telefon[] = new int[N+1];

static void initializare() {


nume[0] = "Paul"; telefon[0] = 2811;
nume[1] = "Robert"; telefon[1] = 4501;
nume[2] = "Laura"; telefon[2] = 2701;
nume[3] = "Ana"; telefon[3] = 2702;
nume[4] = "Tudor"; telefon[4] = 2805;
nume[5] = "Marius"; telefon[5] = 2806;
}

static int cauta(String x) {


for (int i = 0; i < N; ++i)
if (x.equals(nume[i]))
return tel[i];
return 1;
}

public static void main (String args[]) {


initializare();
if (args.length == 1)
System.out.println(cauta(args[0]));
}
}

Cel mai simplu algoritm care rezolv  aceast  problem  este c utarea liniar , care testeaz 
elementele vectorului unul dupa altul, începând cu primul.

p=-1;
for(i=0;i<n;i++)
if(x==a[i]) { p=i; break; }

S  analiz m complexitatea acestui algoritm pe cazul cel mai defavorabil, acela în care v nu
se g se³te în a. În acest caz se va face o parcurgere complet  a lui a. Considerând ca operaµie
elementar  testul v ai, num rul de astfel de operaµii elementare efectuate va  egal cu
numarul de elemente al lui a, adic  n. Deci complexitatea algoritmului pentru cazul cel mai
defavorabil este O n.

8.3 C utare binar 


O alt  tehnic  de c utare în tabele este c utarea binar . Presupunem c  tabela de nume este
sortat  în ordine alfabetic  (cum este cartea de telefoane). În loc de a c uta secvenµial, se compar 
cheia de c utare cu numele care se af  la mijlocul tabelei de nume. Dac  acesta este acela³i, se
returneaz  num rul de telefon din mijloc, altfel se reîncepe c utarea în prima jum tate (sau în a
doua) dac  numele c utat este mai mic (respectiv, mai mare) decât numele din mijlocul tabelei.
static int cautareBinara(String x) {
int i, s, d, cmp;
s = 0; d = N 1;
do {
i = (s + d) / 2;
cmp = x.compareTo(nume[i]);
if (cmp == 0)
CAPITOLUL 8. ALGORITMI DE C€UTARE 87

return telefon[i];
if (cmp < 0)
d = i - 1;
else
s = i + 1;
} while (s <= d);
return -1;
}
Num rul CN de comparaµii efectuate pentru o tabel  de dimensiune N este CN 1  CN ©2$ ,
unde C0 1. Deci CN  log2 N .
De acum înaninte, log2 N va  scris mai simplu log N .
Dac  tabela are 10.000 elemente, atunci CN  14. Acesta reprezint  un beneciu considerabil
în raport cu 5.000 de operaµii necesare la c utarea linear .
Desigur c utarea secvenµial  este foarte u³or de programat, ³i ea se poate folosi pentru tabele
de dimensiuni mici. Pentru tabele de dimensiuni mari, c utarea binar  este mult mai interesant .
Se poate obµine un timp sub-logaritmic dac  se cunoa³te distribuµia obiectelor. De exemplu, în
cartea de telefon, sau în dicµionar, se ³tie apriori c  un nume care începe cu litera V se a  c tre
sfâr³it. Presupunând distribuµia uniform , putem face o "regul  de trei simpl " pentru g sirea
indicelui elementului de referinµ  pentru comparare, în loc s  alegem mijlocul, ³i s  urm m restul
algoritmului de c utare binar . Aceast  metod  se nume³te c utare prin interpolare. Timpul de
c utare este O log log N , ceea ce înseamn  cam 4 operaµii pentru o tabel  de 10.000 elemente ³i
9
5 operaµii pentru 10 elemente în tabel . Este spectaculos!
O implementare iterativ  a c ut rii binare într-un vector ordonat (cresc tor sau descresc tor)
este:
int st, dr, m;
boolean gasit;
st=0;
dr=n-1;
gasit=false;
while((st < dr) && !gasit)
{
m=(st+dr)/2;
if(am]==x)
gasit=true;
else if(a[m] > x)
dr=m-1;
else st=m+1;
}
if(gasit) p=m; else p=-1;
Algoritmul poate  descris, foarte elegant, recursiv.

8.4 Inserare în tabel 


La c utarea secvenµial  sau binar  nu am fost preocupaµi de inserarea în tabel  a unui nou
element. Aceasta este destul de rar  în cazul c rµii de telefon dar în alte aplicaµii, de exemplu
lista utilizatorilor unui sistem informatic, este frecvent utilizat .
Vom vedea cum se realizeaz  inserarea unui element într-o tabel , în cazul c ut rii secvenµiale
³i binare.
Pentru cazul secvenµial este sucient s  ad ug m la cap tul tabelei noul element, dac  are loc.
Dac  nu are loc, se apeleaz  o procedur  error care a³eaz  un mesaj de eroare.
void inserare(String x, int val) {
++n;
if (n >= N)
error ("Depasire tabela");
numem[n] = x;
telefon[n] = val;
}
CAPITOLUL 8. ALGORITMI DE C€UTARE 88

Inserarea se face deci în timp constant, de ordinul O 1.


În cazul c ut rii binare, trebuie menµinut tabelul ordonat. Pentru inserarea unui element nou
în tabel , trebuie g sit  poziµia sa printr-o c utare binar  (sau secvenµial ), apoi se deplaseaz 
toate elementele din spatele ei spre dreapta cu o poziµie pentru a putea insera noul element în
locul corect. Aceasta necesit  log n  n opereµii. Inserarea într-o tabel  ordonat  cu n elemente
este deci de ordinul O n.

8.5 Dispersia
O alt  metod  de c utare în tabele este dispersia. Se utilizeaz  o funcµie h de grupare a cheilor
(adesea ³iruri de caractere) într-un interval de numere întregi. Pentru o cheie x, h x este locul
unde se af  x în tabel . Totul este în ordine dac  h este o aplicaµie injectiv . De asemenea, este
bine ca funcµia aleas  s  permit  evaluarea cu un num r mic de operaµii. O astfel de funcµie este
m1 m2
h x x1 B  x2 B  ...  xm1 B  xm  mod N.

De obicei se ia B 128 sau B 256 ³i se presupune c  dimensiunea tabelei N este un num r


prim. De ce? Pentru c  înmulµirea puterilor lui 2 se poate face foarte u³or prin operaµii de
deplasare pe biµi, numerele ind reprezentate în binar. În general, la ma³inile (calculatoarele)
moderne, aceste operaµii sunt net mai rapide decât înmulµirea numerelor oarecare. Cât despre
alegerea lui N , aceasta se face pentru a evita orice interferenµ  între între înmulµirile prin B ³i
împ rµirile prin N . Într-adev r, dac  de exemplu B N 256, atunci h x x m este funcµia h
care nu depinde decât de ultimul caracter al lui x. Scopul este de a avea o funcµie h, de dispersie,
simplu de calculat ³i având o bun  distribuµie pe intervalul 0, N  1. Calculul funcµiei h se face
prin funcµia h(x,m), unde m este lungimea ³irului x,

static int h(String x){


int r = 0;
for (int i = 0; i < x.length(); ++i)
r = ((r * B) + x.charAt(i)) % N;
return r;
}
Deci funcµia h d  pentru toate cheile x o intrare posibil  în tabel . Se poate apoi verica
dac  x numeh x. Dac  da, c utarea este terminat . Dac  nu, înseamn  c  tabela conµine
¬
o alt  cheie astfel încât h x  h x. Se spune atunci c  exist  o coliziune, ³i tabela trebuie s 
gestioneze coliziunile. O metod  simpl  este de a lista coliziunile într-un tabel col paralel cu
tabelul nume. Tabela de coliziuni d  o alt  intrare i în tabela de nume unde se poate g si cheia
¬
c utat . Dac  nu se g se³te valoarea x în aceast  nou  intrare i, se continu  cu intrarea i dat 
de i coli. Se continu  astfel cât timp coli j 1.
¬

static int cauta(String x) {


for (int i = h(x); i != 1; i = col[i])
if (x.equals(nume[i]))
return telefon[i];
return 1;
}

Astfel, procedura de c utare consum  un timp mai mare sau egal ca lungimea medie a claselor
de echivalenµ  denite pe tabel  de valorile h x, adic  de lungimea medie a listei de coliziuni.
Dac  funcµia de dispersie este perfect uniform , nu apar coliziuni ³i determin  toate elemen-
tele printr-o singur  comparaµie. Acest caz este foarte puµin probabil. Dac  num rul mediu de
elemente având aceea³i valoare de dispersie este k N ©M , unde M este num rul claselor de echi-
valenµ  denite de h, c utarea ia un timp de N ©M . Dispersia nu face decât s  reduc  printr-un
factor constant timpul c ut rii secvenµiale. Interesul faµ  de dispersie este pentru c  adesea este
foarte ecace, ³i u³or de programat.
Capitolul 9

Algoritmi elementari de sortare

Tablourile sunt structuri de baz  în informatic . Un tablou reprezint , în funcµie de dimen-


siunile sale, un vector sau o matrice cu elemente de acela³i tip. Un tablou permite accesul direct
la un element, ³i noi vom utiliza intens aceast  proprietate în algoritmii de sortare pe care îi vom
considera.

9.1 Introducere
Ce este sortarea? Presupunem c  se d  un ³ir de N numere întregi ai , ³i se dore³te aranjarea
lor în ordine cresc toare, în sens larg. De exemplu, pentru N 10, ³irul

18, 3, 10, 25, 9, 3, 11, 13, 23, 8

va deveni
3, 3, 8, 9, 10, 11, 13, 18, 23, 25.
Aceast  problem  este clasic  în informatic  ³i a fost studiat  în detaliu, de exemplu, în [20].
În proctic  se întâlne³te adesea aceast  problem . De exemplu, stabilirea clasamentului între
studenµi, construirea unui dicµionar, etc. Trebuie f cut  o distinµie între sortarea unui num r
mare de elemente ³i a unui num r mic de elemente. În acest al doilea caz, metoda de sortare este
puµin important .
Un algoritm amuzant const  în a vedea dac  setul de c rµi de joc din mân  este deja ordonat.
Dac  nu este, se d  cu ele de p mânt ³i se reîncepe. Dup  un anumit timp, exist  riscul de a avea
c rµile ordonate. Desigur, poate s  nu se termine niciodat , pentru noi, aceast  sortare.
O alt  tehnic  (discutând serios de data aceasta), frecvent utilizat  la un joc de c rµi, const 
în a vedea dac  exist  o transpoziµie de efectuat. Dac  exist , se face interschimbarea c rµilor
de joc ³i se caut  o alt  transpoziµie. Procedeul se repet  pân  când nu mai exist  transpoziµii.
Aceast  metod  funcµioneaz  foarte bine pentru o bun  distribuµie a c rµilor.
Este u³or de intuit c  num rul obiectelor de sortat este important. Nu este cazul s  se caute o
metod  sosticat  pentru a sorta 10 elemente. Cu alte cuvinte, nu se trage cu tunul într-o musc !
Exemplele tratate într-un curs sunt întotdeauna de dimensiuni limitate, din considerente pe-
dagogice nu este posibil de a prezenta o sortare a mii de elemente.

9.2 Sortare prin selecµie


În cele ce urmeaz , presupunem c  trebuie s  sort m un num r de întregi care se g sesc într-un
tablou (vector) a. Algoritmul de sortare cel mai simplu este prin selecµie. El const  în g sirea
poziµiei în tablou a elementului cu valoarea cea mai mic , adic  întregul m pentru care ai ' am
pentru orice i. Odat  g sit  aceast  poziµie m, se schimb  între ele elementele a1 ³i am .
Apoi se reîncepe aceast  operaµie pentru ³irul a2 , a3 , ..., aN , tot la fel, c utându-se elementul
cel mai mic din acest ³ir ³i interschimbându-l cu a2 . “i a³a mai departe pân  la un moment dat
când ³irul va conµine un singur element.

89
CAPITOLUL 9. ALGORITMI ELEMENTARI DE SORTARE 90

C utarea celui mai mic element într-un tablou este un prim exerciµiu de programare. Deter-
minarea poziµiei acestui element este foarte simpl , ea se poate efectua cu ajutorul instrucµiunilor
urm toare:

m = 0;
for (int j = 1; j < N; ++j)
if (a[j] < a[m])
m = i;
Schimbarea între ele a celor dou  elemente necesit  o variabil  temporar  t ³i se efectueaz 
prin:

t = a[m]; a[m] = a[1]; a[1] = t;


Acest set de operaµii trebuie reluat prin înlocuirea lui 1 cu 2, apoi cu 3 ³i a³a mai departe pân 
la N . Aceasta se realizeaz  prin introducerea unei variabile i care ia toate valorile între 1 ³i N .
Toate acestea sunt ar tate în programul care urmeaz .
De aceast  dat  vom prezenta programul complet.
Procedurile de achiziµionare a datelor ³i de returnare a rezultatelor sunt deasemenea prezentate.
Pentru alµi algoritmi, ne vom limita la descrierea efectiv  a sort rii.

class SortSelectie
{
final static int N = 10;
static int[] a = new int[N];

static void initializare()


{
int i;
for (i = 0; i < N; ++i)
a[i] = (int) (Math.random() * 128);
}

static void afisare()


{
int i;
for (i = 0; i < N; ++i)
System.out.print (a[i] + " ");
System.out.println();
}

static void sortSelectie()


{
int min, t;
int i, j;
for (i = 0; i < N 1; ++i)
{
min = i;
for (j = i+1; j < N; ++j)
if (a[j] < a[min])
min = j;
t = a[min];
a[min] = a[i];
a[i] = t;
}
}

public static void main (String args[])


{
initializare();
afisare();
sortSelectie();
CAPITOLUL 9. ALGORITMI ELEMENTARI DE SORTARE 91

afisare();
}
}

Un exemplu de execuµii este:


0 1 2 3 4 5 6 7 0 1 2 3 4 5 6 7
7

12 4 30 3 4 23 14
   3 12 4 30 7

4 23 14

i m i m
0 1 2 3 4 5 6 7 0 1 2 3 4 5 6 7
3 12
 
4 30 7 4 23 14
 3 4
 
12 30 7 4 23 14

i m i m
0 1 2 3 4 5 6 7 0 1 2 3 4 5 6 7
3 4 12

30 7 4

23 14
 3 4 4

30 7 12

23 14

i m i m
0 1 2 3 4 5 6 7 0 1 2 3 4 5 6 7
3 4 4 30
 
7 12 23 14
 3 4 4 7
 
30 12 23 14

i m i m
0 1 2 3 4 5 6 7 0 1 2 3 4 5 6 7
3 4 4 7 30
 
12 23 14
 3 4 4 7 12

30

23 14

i m i m
0 1 2 3 4 5 6 7 0 1 2 3 4 5 6 7

 
3 4 4 7 12 30 23 14 3 4 4 7 12 14 23 30
  
i m i m
0 1 2 3 4 5 6 7 0 1 2 3 4 5 6 7
3 4 4 7 12 14 23

30
 3 4 4 7 12 14 23

30

im im

3
0 1
4
2
4
3
7
4
12
5
14
6
23
7
30
 0
3
1
4
2
4 7
3
12
4
14
5 6
23
7
30
Este u³or de calculat num rul de operaµii necesare. La ecare iteraµie, se pleac  de la elementul
ai ³i se compar  succesiv cu ai1 , ai2 , ..., aN . Se fac deci N  i comparaµii. Se începe cu i 1 ³i
se termin  cu i N  1. Deci, se fac N  1  N  2  ...  2  1 N N  1©2 comparaµii ³i
2
N  1 interschimb ri. Sortarea prin selecµie execut  un num r de comparaµii de ordinul N .
O variant  a sort rii prin selecµie este metoda bulelor. Principiul ei este de a parcurge ³irul
a1 , a2 , ..., aN  inversând toate perechile de elemente consecutive aj  1, aj  neordonate. Dup 
prima parcurgere, elementul maxim se va aa pe poziµia N . Se reîncepe cu prexul a, a2 , ..., aN 1 ,
..., a1 , a2 .
Procedura corespunz toare utilizeaz  un indice i care marcheaz  sfâr³itul prexului în sortare,
³i un indice j care permite deplasarea c tre marginea i.
Se poate calcula de asemenea foarte u³or num rul de operaµii ³i se obµine un num r de ordinul
2
O N  comparaµii ³i, eventual interschimb ri (dac , de exemplu, tabloul este iniµial în ordine
descresc toare).

static void sortBule() {


int t;
for (int i = N 1; i >= 0; i)
for (int j = 1; j <= i; ++j)
if (a[j 1] > a[j]) {
t = a[j 1]; a[j 1] = a[j]; a[j] = t;
}
}
CAPITOLUL 9. ALGORITMI ELEMENTARI DE SORTARE 92

0 1 2 3 4 5 6 7 0 1 2 3 4 5 6 7

 
7 12 4 30 3 4 23 14 7 4 12 30 3 4 23 14
  
j i j i
0 1 2 3 4 5 6 7 0 1 2 3 4 5 6 7

 
7 4 12 30 3 4 23 14 7 4 12 3 30 4 23 14
  
j i j i
0 1 2 3 4 5 6 7 0 1 2 3 4 5 6 7

 
7 4 12 3 30 4 23 14 7 4 12 3 4 30 23 14
  
j i j i
0 1 2 3 4 5 6 7 0 1 2 3 4 5 6 7

  
7 4 12 3 4 30 23 14 7 4 12 3 4 23 30 14
 
j i j i
0 1 2 3 4 5 6 7 0 1 2 3 4 5 6 7

 
7 4 12 3 4 23 30 14 7 4 12 3 4 23 14 30

ji ji
Dup  prima parcurgere 30 a ajuns pe locul s u (ultimul loc în vector).
0 1 2 3 4 5 6 7 0 1 2 3 4 5 6 7
7 4 12 3 4 23 14 30
  
4 7 12 3 4 23 14
 
30

j i j i
0 1 2 3 4 5 6 7 0 1 2 3 4 5 6 7
4 7 12 3

4 23 14

30
 4 7 3 12

4 23 14

30

j i j i
0 1 2 3 4 5 6 7 0 1 2 3 4 5 6 7
4 7 3 12 4

23 14

30
 4 7 3 4 12

23 14

30

j i j i
0 1 2 3 4 5 6 7 0 1 2 3 4 5 6 7
4 7 3 4 12 23 14

30
 4 7 3 4 12 14 23

30

ji ji
Dup  a doua parcurgere 23 a ajuns pe locul s u (penultimul loc în vector).
0 1 2 3 4 5 6 7 0 1 2 3 4 5 6 7
4 7 3 4 12 14 23 30
  
4 3 7 4 12 14 23 30
 
j i j i
0 1 2 3 4 5 6 7 0 1 2 3 4 5 6 7
4 3 7 4

12 14

23 30
 4 3 4 7

12 14

23 30

j i j i
Dup  a treia parcurgere 14 a ajuns pe locul s u (de fapt era deja pe locul s u de la începutul
acestei parcurgeri; s-au mai aranjat totu³i câteva elemente!).
0 1 2 3 4 5 6 7 0 1 2 3 4 5 6 7
4 3 4 7 12 14 23 30
  
3 4 4 7 12 14 23 30
 
j i j i
Dup  a patra parcurgere 12 a ajuns pe locul s u (de fapt era deja pe locul s u de la începutul
acestei parcurgeri; oricum, la aceast  parcurgere s-au mai aranjat câteva elemente!).
La urm toarea parcurgere nu se efectueaz  nici o interschimbare de elemente. Vectorul este
deja sortat, a³a c  urm toarele parcurgeri se fac, de asemenea, f r  s  se execute nici o interschim-
bare de elemente. O idee bun  este introducerea unei variabile care s  contorizeze num rul de
interschimb ri din cadrul unei parcurgeri. Dac  nu s-a efectuat nici o interschimbare atunci vec-
torul este deja sortat a³a c  se poate întrerupe execuµia urm toarelor parcurgeri. Programul
CAPITOLUL 9. ALGORITMI ELEMENTARI DE SORTARE 93

modicat este:

static void sortBule() {


int t, k;
for (int i = N 1; i >= 0; i)
{
k=0;
for (int j = 1; j <= i; ++j)
if (a[j 1] > a[j]) {t = a[j 1]; a[j 1] = a[j]; a[j] = t; k++;}
if(k==0) break;
}
}

9.3 Sortare prin inserµie


O metod  complet diferit  este sortarea prin inserµie. Aceasta este metoda utilizat  pentru
sortarea unui pachet de c rµi de joc. Se ia prima carte, apoi a doua ³i se aranjeaz  în ordine
cresc toare. Se ia a treia carte ³i se plaseaz  pe poziµia corespunz toare faµ  de primele dou 
c rµi, ³i a³a mai departe. Pentru cazul general, s  presupunem c  primele i  1 c rµi sun deja
sortate cresc tor. Se ia a i-a carte ³i se plaseaz  pe poziµia corespunz toare relativ la primele i  1
c rµi deja sortate. Se continu  pân  la i N .

9.3.1 Inserµie direct 

Dac  determinarea poziµiei corespunz toare, în sub³irul format de primele i  1 elemente, se


face secvenµial, atunci sortarea se nume³te sortare prin inserµie direct . Procedura care realizeaz 
aceastsortare este:

static void sortInsertie() {


int j, v;
for (int i = 1; i < N; ++i) {
v = a[i]; j = i;
while (j > 0 && a[j-1] > v) {
a[j] = a[j-1];
j;
}
a[j] = v;
}
}
Pentru plasarea elementului ai din vectorul nesortat (elementele de pe poziµiile din stânga ind
deja sortate) se parcurge vectorul spre stânga plecând de la poziµia i  1. Elementele vizitate se
deplaseaz  cu o poziµie spre dreapta pentru a permite plasarea elementului ai (a c rui valoare
a fost salvat  n variabila temporar  v ) pe poziµia corespunz toare. Procedura conµine o mic 
eroare, dac  ai este cel mai mic element din tablou, c ci va ie³i din tablou spre stânga. Se poate
remedia aceast  situaµie plasând un element a0 de valoare max_int. Se spune c  a fost plasat 
o santinel  la stânga tabloului a. Aceasta nu este întotdeauna posibil, ³i atunci trebuie ad ugat
un test asupra indicelui j în bucla while. Un exemplu numeric este cel care urmeaz :
Inserarea lui 12 nu necesit  deplas ri de elemente.
0 1 2 3 4 5 6 7 0 1 2 3 4 5 6 7
7 12 4 30 3 4 23 14
    4 7 12 30 3 4 23 14

j i j i
Inserarea lui 30 nu necesit  deplas ri de elemente.
0 1 2 3 4 5 6 7 0 1 2 3 4 5 6 7
4

7 12 30 3

4 23 14
  3 4 7 12 30

4 23 14

j i j i
CAPITOLUL 9. ALGORITMI ELEMENTARI DE SORTARE 94

0 1 2 3 4 5 6 7 0 1 2 3 4 5 6 7
3 4 7

12 30 4

23 14
 3 4 4

7 12 30

23 14

j i j i
0 1 2 3 4 5 6 7 0 1 2 3 4 5 6 7
3 4 4 7 12 30
 
23 14
 3 4 4 7 12 23
 
30 14

j i j i
0 1 2 3 4 5 6 7 0 1 2 3 4 5 6 7

 
3 4 4 7 12 23 30 14 3 4 4 7 12 14 23 30
  
j i j i
Num rul de comparaµii pentru inserarea unui element în secvenµa deja sortat  este egal cu
num rul de inversiuni plus 1.
Fie ci num rul de comparaµii. Atunci

ci 1  cardraj ¶aj % ai , j $ ix
Pentru o permutare π corespunz toare unui ³ir de sort ri, unde num rul de inversiuni este
inv π , num rul total de comparaµii pentru sortarea prin inserµie este

=c
N
Cπ i N  1  inv π .
i 2

Deci, num rul mediu de comparaµii este

CN
1
=
N ! π"S
Cπ N  1
N N  1
4
N N  3
4
 1
N

unde Sn reprezint  grupul permut rilor de n elemente.


2
De³i ordinul de cre³tere este tot N , aceast  metod  de sortare este mai ecient  decât sortarea
prin selecµie. Cea mai bun  metod  de sortare necesit  n log2 n comparaµii.
În plus, ea are o proprietate foarte bun : num rul de operaµii depinde puternic de ordinea
iniµial  din tablou. În cazul în care tabloul este aproape ordonat, ³i drept urmare exist  puµine
inversiuni ³i sunt necesare puµine operaµii, spre deosebire de primele dou  metode de sortare.
Sortarea prin inserare este deci o metod  de sortare bun  dac  tabloul de sortat are ³ansa de
a  aproape ordonat.

9.3.2 Inserµie binar 

Metoda anterioar  se poate îmbunat µi dac  µinem cont de faptul c  secvenµa în care se face
inserarea este deja ordonat , iar în loc s  se fac  inserµia direct  în aceast  secvenµ , c utarea
poziµiei pe care se face inserarea se face prin c utare binar . Un program complet este:

import java.io.*;
class SortInsBinApl
{
static int[] a={3,8,5,4,9,1,6,4};

static void afiseaza()


{
int j;
for(j=0; j<a.length; j++)
System.out.print(a[j] + " ");
System.out.println();
}

static int pozitiaCautBin(int p, int u, int x)


{
int i=u+1;
CAPITOLUL 9. ALGORITMI ELEMENTARI DE SORTARE 95

while (p <= u)
{
i=(p+u)/2;
if (x>a[i])
p=i+1;
else if (x<a[i])
u=i-1;
else return i;
}
return p;
}

static void deplasare(int k, int i)


{
if (i != k)
{
int x=a[k];
for(int j=k; j>=i+1; j--) a[j]=a[j-1];
a[i]=x;
}
}

static void sorteaza()


{
int N=a.length,i;
for(int k=1;k<=N-1;k++)
{
i=pozitiaCautBin(0,k-1,a[k]);
deplasare(k,i);
}
}

public static void main(String[] args)


{
afiseaza();
sorteaza();
afiseaza();
}
}

9.4 Sortare prin interschimbare


Aceast  metod  folose³te interschimbarea ca ³i caracteristic  principal  a metodei de sortare.
În cadrul acestei metode se compar  ³i se interschimb  perechi adiacente de chei pan  când toate
elementele sunt sortate.

static void interschimbare(int a[])


{
int x, i, n=a.length;
boolean schimb = true;
while (!schimb)
{
schimb=false;
for(i=0; i<n-1; i++)
if (a[i]>a[i+1])
{
x = a[i];
a[i] = a[i+1];
a[i+1] = x;
CAPITOLUL 9. ALGORITMI ELEMENTARI DE SORTARE 96

schimb=true;
}
}
}

9.5 Sortare prin mic³orarea incrementului - shell


Prezent m metoda pe urm torul ³ir:

44, 55, 12, 42, 94, 18, 6, 67.

Se grupeaz  elementele aate la 4 poziµii distanµ , ³i se sorteaz  separat. Acest proces este
numit numit 4 sort. Rezult  ³irul:

44, 18, 06, 42, 94, 55, 12, 67

Apoi se sorteaz  elementele aate la 2 poziµii distant . Rezult :

6, 18, 12, 42, 44, 55, 94, 97

Apoi se sorteaz  sirul rezultat într-o singur  trecere: 1 - sort

6, 12, 18, 42, 44, 55, 94, 97

Se observ  urm toarele:

ˆ un proces de sortare i  sort combin  2 grupuri sortate în procesul 2i  sort anterior

ˆ în exemplul anterior s-a folosit secventa de incremenµi 4, 2, 1 dar orice secvenµ , cu condiµia
ca cea mai n  sortare s  e 1  sort. În cazul cel mai defavorabil, în ultimul pas se face
totul, dar cu multe comparaµii ³i interschimb ri.

ˆ dac  cei t incrementi sunt h1 , h2 , .. ht , ht 1 ³i hi1 $ hi , ecare hi -sort se poate implementa


ca ³i o sortate prin insertie direct .

void shell(int a[], int n)


{
static int h[] = {9, 5, 3, 1};
int m, x, i, j, k, n=a.length;
for (m=0; m<4; m++)
{
k = h[m];
/* sortare elemente aflate la distanta k in tablul a[] */
for (i=k; i<n; i++)
{
x = a[i];
for (j = i-k; (j>=0) && (a[j]>x); j-=k) a[j+k] = a[j];
a[j+k] = x;
}
}
}
Capitolul 10

Liste

Scopul listelor este de a genera un ansamblu nit de elemente al c rui num r nu este xat
apriori. Elementele acestui ansamblu pot  numere întregi sau reale, ³iruri de caractere, obiecte
informatice complexe, etc. Nu suntem acum interesaµi de elementele acestui ansamblu ci de
operaµiile care se efectueaz  asupra acestuia, independent de natura elementelor sale.
Listele sunt obiecte dinamice, în sensul c  num rul elementelor variaz  în cursul execuµiei
programului, prin ad ug ri sau ³tergeri de elemente pe parcursul prelucr rii. Mai precis, operaµiile
permise sunt:
ˆ testarea dac  ansamblul este vid
ˆ ad ugarea de elemente
ˆ vericarea dac  un element este în ansamblu
ˆ ³tergerea unui element

10.1 Liste liniare


Fiecare element al listei este conµinut într-o celul  care conµine în plus adresa elementului
urm tor, numit ³i pointer. Java permite realizarea listelor cu ajutorul claselor ³i obiectelor: ce-
lulele sunt obiecte (adic  instanµe ale unei clase) în care un câmp conµine o referinµ  c tre celula
urm toare. Referinµa c tre prima celul  este conµinut  într-o variabil .
class Lista {
int continut;
Lista urmator;
Lista (int x, Lista a) {
continut = x;
urmator = a;
}
}
Instrucµiunea new Lista(x,a) construie³te o nou  celul  cu câmpurile x ³i a. Func tia
Lista(x,a) este un constructor al clasei Lista (un constructor este o funcµie nestatic  care
se distinge prin tipul rezultatului s u care este cel al clasei curente, ³i prin absenµa numelui
de identicare). Obiectul null aparµine tuturor claselor ³i reprezint  în cazul listelor marcajul
de sfâr³it de list . De asemenea new Lista(2, new Lista(7, new Lista(11,null)))
reprezint  lista 2, 7, 11.
static boolean esteVida (Lista a) {
return a == null;
}
Procedura adauga insereaz  un element în capul listei. Aceast  modalitate de a introduce
elemente în capul listei este util  pentru ca num rul de operaµii necesare ad ug rii de elemente s 
e independent de m rimea listei; este sucient  modicarea valorii capului listei, ceea ce se face
simplu prin:

97
CAPITOLUL 10. LISTE 98

static Lista adaug (int x, Lista a) {


return new Liste (x, a); // capul vechi se va regasi dupa x
}

cap cap

e4 e3 e2 e1 e4 e3 e2 e1

Figura 10.1: Ad ugarea unui element în list 

Funcµia cauta, care veric  dac  elementul x este în lista a, efectueaz  o parcurgere a listei.
Variabila a este modicat  iterativ prin a=a.urmator pentru a parcurge elementele listei pân 
se g se³te x sau pân  se g se³te sfâr³itul listei (a null).
static boolean cauta (int x, Lista a) {
while (a != null) {
if (a.continut == x)
return true;
a = a.urmator;
}
return false;
}
Funcµia cauta poate  scris  în mod recursiv:
static boolean cauta (int x, Lista a) {
if (a == null)
return false;
else if (a.continut == x)
return true;
else
return cauta(x, a.urmator);
}
sau sub forma:
static boolean cauta (int x, Lista a) {
if (a == null)
return false;
else return (a.continut == x) || cauta(x, a.urmator);
}
sau sub forma:
static boolean cauta (int x, Lista a) {
return a != null && (a.continut == x || cauta(x, a.urmator));
}
Aceast  sciere recursiv  este sistematic  pentru funcµiile care opereaz  asupra listelor. Tipul
list veric  ecuaµia urm toare:

Lista rLista_vidax h Element  Lista

unde h este sau exclusiv iar  este produsul cartezian. Toate procedurile sau funcµiile care
opereaz  asupra listelor se pot scrie recursiv în aceast  manier . De exemplu, lungimea listei se
poate determina prin:
static int lungime(Lista a) {
if (a == null) return 0;
else return 1 + longime(a.urmator);
}
CAPITOLUL 10. LISTE 99

care este mai elegant  decât scrierea iterativ :

static int lungime(Lista a) {


int lg = 0;
while (a != null) {
++lg;
a = a.urmator;
}
return lg;
}
Alegerea între maniera recursiv  sau iterativ  este o problem  subiectiv  în general. Pe de
alt  parte se spune c  scrierea iterativ  este mai ecient  pentru c  folose³te mai puµin  memorie.
Graµie noilor tehnici de compilare, acest lucru este mai puµin adev rat; ocuparea de memorie
suplimentar , pentru calculatoarele de ast zi, este o problem  foarte puµin critic .
Eliminarea unei celule care conµine x se face modicând valoarea câmpului urmator conµinut
în predecesorul lui x: succesorul predecesorului lui x devine succesorul lui x. Un tratament parti-
cular trebuie f cut dac  elementul care trebuie eliminat este primul element din list . Procedura
recursiv  de eliminare este foarte compact  în deniµia sa:

static Lista elimina (int x, Lista a) {


if (a != null)
if (a.continut == x)
a = a.urmator;
else
a.urmator = elimina (x, a.urmator);
return a;
}

cap cap

e4 e3 e2 e1 e4 e3 e2 e1

Figura 10.2: Eliminarea unui element din list 

O procedur  iterativ  solicit  un plus de atenµie.

static Lista elimina (int x, Lista a) {


if (a != null)
if (a.continut == x)
a = a.urmator;
else {
Lista b = a ;
while (b.urmator != null && b.urmator.continut != x)
b = b.urmator;
if (b.urmator != null)
b.urmator = b.urmator.urmator;
}
return a;
}

În cadrul funcµiilor anterioare, care modic  lista a, nu se dispune de dou  liste distincte, una
care conµine x ³i alta identic  cu precedenta dar care nu mai conµine x. Pentru a face acest lucru,
trebuie recopiat  o parte a listei a într-o nou  list , cum face programul urm tor, unde exist 
o utilizare puµin important  de memorie suplimentar . Oricum, spaµiul de memorie pierdut este
recuperat de culeg torul de spaµiu de memorie GC (Garbage Colection) din Java, dac  nu mai este
folosit  lista a. Cu tehnicile actuale ale noilor versiuni ale GC, recuperarea se efectueaz  foarte
rapid. Aceasta este o diferenµ  important  faµ  de limbajele de programare precum Pascal sau C,
unde trebuie s  m preocupaµi de spaµiul de memorie pierdut, dac  trebuie s - l reutiliz m.
CAPITOLUL 10. LISTE 100

static Lista elimina (int x, Lista a) {


if (a != null)
return null;
else if (a.continut == x)
return a.urmator;
else
return new Lista (a.continut, elimina (x, a.urmator));
}

Exist  o tehnic , utilizat  adesea, care permite evitarea unor teste pentru eliminarea unui
element dintr-o list  ³i, în general, pentru simplicarea program rii operaµiilor asupra listelor.
Aceasta const  în utilizarea unui fanion / santinel  care permite tratarea omogen  a listelor indi-
ferent dac  sunt vide sau nu. În reprezentarea anterioar  lista vid  nu a avut aceea³i structur  ca
listele nevide. Se utilizeaz  o celul  plasat  la începutul listei, care nu are informaµie semnicativ 
în câmpul continut; adresa adev ratei prime celule se a  în câmpul urmator al acestei celule.
Astfel obµinem o list  p zit , avantajul ind c  lista vid  conµine numai garda ³i prin urmare un
num r de programe, care f ceau un caz special din lista vid  sau din primul element din list , de-
vin mai simple. Aceast  noµiune este un pic echivalent  cu noµiunea de santinel  pentru tablouri.
Se utilizeaz  aceast  tehnic  în proceduri asupra ³irurilor prea lungi.
De asemenea, se pot deni liste circulare cu gard  / paznic în care în prima celul  în câmpul
urmator este ultima celul  din list .
Pentru implementarea listelor se pot folosi perechi de tablouri : primul tablou, numit continut,
conµine elementele de informaµii, iar al doilea, numit urmator, conµine adresa elementului urm tor
din tabloul continut.
Procedura adauga efectueaz  numai 3 operaµii elementare. Este deci foarte ecient . Procedu-
rile cauta ³i elimina sunt mai lungi pentru c  trebuie s  parcurg  întreaga list . Se poate estima
c  num rul mediu de operaµii este jum tate din lungimea listei. C utarea binar  efectueaz  un
num r logaritmic iar c utarea cu tabele hash (de dispersie) este ³i mai rapid .
Exemplu 1. Consider m construirea unei liste de numere prime mai mici decât un num r
natural n dat. Pentru construirea acestei liste vom începe, în prima faz , prin ad ugarea numerelor
de la 2 la n începând cu n ³i terminând cu 2. Astfel numerele vor  în list  în ordine cresc toare.
Vom utiliza metoda clasic  a ciurului lui Eratostene: consider m succesiv elementele listei în
ordine cresc toare ³i suprim m toµi multiplii lor. Aceasta se realizeaz  prin procedura urm toare:

static Lista Eratostene (int n) {


Lista a=null;
int k;
for(int i=n; i>=2; --i) {
a=adauga(i,a);
}
k=a.continut;
for(Lista b=a; k*k<=n; b=b.urmator) {
k=b.continut;
for(int j=k; j<=n/k; ++j)
a=elimina(j*k,a);
}
return a;
}
Exemplu 2. Pentru ecare intrare i din intervalul 0..n  1 se construie³te o list  simplu
înl nµuit  format  din toate cheile care au h x i. Elementele listei sunt intr ri care permit
accesul la tabloul de nume ³i numere de telefon. Procedurile de c utare ³i inserare a unui element
x într-un tablou devin proceduri de c utare ³i ad ugare într-o list . Tot astfel, dac  funcµia h
este r u aleas , num rul de coliziuni devine important, multe liste devin de dimensiuni enorme
³i f râmiµarea risc  s  devin  la fel de costisitoare ca ³i c utarea într-o list  simplu înl nµuit 
obi³nuit . Dac  h este bine aleas , c utarea este rapid .

Lista al[] = new Lista[N 1];

static void insereaza (String x, int val) {


int i = h(x);
al[i] = adauga (x, al[i]);
CAPITOLUL 10. LISTE 101

static int cauta (String x) {


for (int a = al[h(x)]; a != null; a = a.urmator) {
if (x.equals(nume[a.continut]))
return telefon[a.continut];
}
return -1;
}

10.2 Cozi
Cozile sunt liste liniare particulare utilizate în programare pentru gestionarea obiectelor care
sunt în a³teptarea unei prelucr ri ulerioare, de exemplu procesele de a³teptare a resurselor unui
sistem, nodurile unui graf, etc. Elementele sunt ad ugate sistematic la coad  ³i sunt eliminate din
capul listei. În englez  se spune strategie FIFO (First In First Out), în opoziµie cu strategia LIFO
(Last In Firs Out) utilizat  la stive.
Mai formalizat, consider m o mulµime de elemente E , mulµimea cozilor cu elemente din E
este notat  Coada E , coada vid  (care nu conµine nici un element) este C0 , iar operaµiile asupra
cozilor sunt: esteV ida, adauga, valoare, elimina:

ˆ esteV ida este o aplicaµie denit  pe Coada E  cu valori în rtrue, f alsex, esteV ida C  aste
egal  cu true dac  ³i numai dac  coada C este vid .

ˆ adauga este o aplicaµie denit  pe E  Coada E  cu valori în Coada E , adauga x, C  este


coada obµinut  plecând de la coada C ³i inserând elementul x la sfâr³itul ei.

ˆ valoare este o aplicaµie denit  pe Coada E   C0 cu valori în E care asociaz  unei cozi C ,
nevid , elementul aat în cap.

ˆ elimina este o aplicaµie denit  pe Coada E   C0 cu valori în Coada E  care asociaz  unei
cozi nevide C o coad  obµinut  plecând de la C ³i eliminând primul element.

Operaµiile asupra cozilor satisfac urm toarele relaµii:

ˆ Pentru C j C0
 elimina adauga x, C  adauga x, elimina C 
 valoare adauga x, C  valoare C 
ˆ Pentru toate cozile C

 esteV ida adauga x, C  f alse


ˆ Pentru coada C0

 elimina adauga x, C0  C0
 valoare adauga x, C0  x
 esteV ida C0  true
O prim  idee de realizare sub form  de programe a operaµiilor asupra cozilor este de a împru-
muta tehnica folosit  în locurile unde clienµii stau la coad  pentru a  serviµi, de exemplu la gar 
pentru a lua bilete, sau la cas  într-un magazin. Fiecare client care se prezint  obµine un num r ³i
clienµii sunt apoi chemaµi de c tre funcµionarul de la ghi³eu în ordinea cresc toare a numerelor de
ordine primite la sosire. Pentru gestionarea acestui sistem, gestionarul trebuie s  cunoasc  dou 
numere: num rul obµinut de c tre ultimul client sosit ³i num rul obµinut de c tre ultimul client
servit. Not m aceste dou  numere inceput ³i sf arsit ³i gestion m sistemul în modul urm tor:

ˆ coada de a³teptare este vid  dac  ³i numai dac  inceput sf arsit,


ˆ atunci când sose³te un nou client, se incrementeaz  sf arsit ³i se d  acest num r clientului
respectiv,
CAPITOLUL 10. LISTE 102

ˆ atunci când funcµionarul este liber el poate servi un alt client, dac  coada nu este vid ,
incrementeaz  inceput ³i cheam  posesorul acestui num r.

În cele ce urmeaz  sunt prezentate toate operaµiile în Java utilizând tehnicile urm toare: se
reatribuie num rul 0 unui nou client atunci când se dep ³e³te un anumit prag pentru valoarea
sf arsit. Se spune c  este un tablou (sau tampon) circular, sau coad  circular .
class Coada {
final static int MaxC = 100;
int inceput;
int sfarsit;
boolean plina, vida;
int continut[];

Coada () {
inceput = 0; sfarsit = 0;
plina= false; vida = true;
info = new int[MaxC];
}

static Coada vida(){


return new Coada();
}

static void facVida (Coada c) {


c.inceput = 0; c.sfarsit = 0;
c.plina = false; c.vida = true;
}

static boolean esteVida(Coada c) {


return c.vida;
}

static boolean estePlina(Coada c) {


return c.plina;
}

static int valoare(Coada c) {


if (c.vida)
erreur ("Coada Vida.");
return c.info[f.inceput];
}

private static int succesor(int i) {


return (i+1) % MaxC;
}

static void adaug(int x, Coada c) {


if (c.plina)
erreur ("Coada Plina.");
c.info[c.sfarsit] = x;
c.sfarsit = succesor(c.sfarsit);
c.vida = false;
c.plina = c.sfarsit == c.inceput;
}

static void elimina (Coada c) {


if (c.vida)
erreur ("Coada Vida.");
c.inceput = succesor(c.inceput);
c.vida = c.sfarsit == c.inceput;
CAPITOLUL 10. LISTE 103

c.plina = false;
}
}

inceput sfarsit

e1 e2 ... en

Figura 10.3: Coad  de a³teptare implementat  ca list 

O alt  modalitate de gestionare a cozilor const  în utilizarea listelor înl nµuite cu gard  în care se
cunosc adresele primului ³i ultimului element. Aceasta d  operaµiile urm toare:

class Coada {
Lista inceput;
Lista sfarsit;

Coada (Lista a, Lista b) {


inceput = a;
sfarsit = b;
}

static Coada vida() {


Lista garda = new Lista();
return new Coada (garda, garda);
}

static void facVida (Coada c) {


Lista garda = new Lista();
c.inceput = c.sfarsit = garda;
}

static boolean esteVida (Coada c) {


return c.inceput == c.sfarsit;
}

static int valoare (Coada c) {


Lista b = c.inceput.next;
return b.info;
}

static void adauga (int x, Coada c) {


Lista a = new Lista (x, null);
c.sfarsit.next = a;
f.sfarsit = a;
}

static void elimina (Coada c) {


if (esteVida(c))
erreur ("Coada Vida.");
c.inceput = c.inceput.next;
}
}

Cozile se pot implementa e cu ajutorul tablourilor e cu ajutorul listelor simplu înl nµuite.


Scrierea programelor const  în primul rând în alegerea structurilor de date pentru reprezentarea
cozilor. Ansamblul funcµiilor care opereaz  asupra cozilor se pot plasa într-un modul care mani-
CAPITOLUL 10. LISTE 104

puleaz  tablouri sau liste. Utilizarea cozilor se face cu ajutorul funcµiilor vida, adauga, valoare,
elimina. Aceasta este deci interfaµa cozilor care are importanµ  în programe complexe.

10.3 Stive
Noµiunea de stiv  intervine în mod curent în programare, rolul s u principal ind la imple-
mentarea apelurilor de proceduri. O stiv  se poate imagina ca o cutie în care sunt plasate obiecte
³i din care se scot în ordinea invers  faµa de cum au fost introduse: obiectele sunt puse unul peste
altul în cutie ³i se poate avea acces numai la obiectul situat în vârful stivei. În mod formalizat,
se consider  o mulµime E , mulµimea stivelor cu elemente din E este notat  Stiva E , stiva vid 
(care nu conµine nici un element) este S0 , operaµiile efectuate asupra stivelor sunt vida, adauga,
valoare, elimina, ca ³i la re. De aceast  dat , relaµiile satisf cute sunt urm toarele:
ˆ elimina adauga x, S  S
ˆ esteV ida adauga x, S  f alse

ˆ valoare adauga x, S  x
ˆ esteV ida S0  true
Cu ajutorul acestor relaµii se pot exprima toate operaµiile relative la stive.
Realizarea operaµiilor asupra stivelor se poate face utilizând un tablou care conµine elementele
³i un indice care indic  poziµia vârfului stivei.

class Stiva {
final static int maxP = 100;
int inaltime;
Element continut[];

Stiva() {
inaltime = 0;
continut = new Element[maxP];
}

static Fir vid () {


return new Stiva();
}

static void facVida (Stiva s) {


s.inaltime = 0;
}

static boolean esteVida (Stiva s) {


return s.inaltime == 0;
}

static boolean estePlina (Stiva s) {


return s.inaltime == maxP;
}

static void adauga (Element x, Stiva s) throws ExceptionStiva {


if (estePlina (s))
throw new ExceptionStiva("Stiva plina");
s.continut[s.inaltime] = x;
++ s.inaltime;
}

static Element valoare (Stiva s) throws ExceptionStiva {


if (esteVida (s))
throw new ExceptionStiva("Stiva vida");
CAPITOLUL 10. LISTE 105

return s.continut[s.inaltime-1];
}

static void supprimer (Stiva s) throws ExceptionStiva {


if (esteVida (s))
throw new ExceptionStiva ("Stiva vida");
s.inaltime;
}
}
unde

class ExceptionStiva extends Exception {


String text;

public ExceptionStiva (String x) {


text = x;
}
}
Stivele se pot implementa atât cu ajutorul tablourilor (vectorilor) cât ³i cu ajutorul listelor
simplu înl nµuite.

10.4 Evaluarea expresiilor aritmetice prexate


Vom ilustra utilizarea stivelor printr-un program de evaluare a expresiilor aritmetice scrise
sub o form  particular . În programare o expresie aritmetic  poate conµine numere, variabile
³i operaµii aritmetice (ne vom limita numai la + ³i *). Vom considera ca intr ri numai numere
naturale.
Expresiile prexate conµin simbolurile: numere naturale, +, *, ( ³i ). Dac  e1 ³i e2 sunt expresii
prexate atunci e1 e2  ³i ˜e1 e2  sunt expresii prexate.
Pentru reprezentarea unei expresii prexate în Java, vom utiliza un tablou ale c rui elemente
sunt entit µile expresiei. Elementele tabloului sunt obiecte cu trei câmpuri: primul reprezint 
natura entit µii (simbol sau num r), al doilea reprezint  valoarea entit µii dac  aceasta este num r,
iar al treilea este este un simbol dac  entitatea este simbol.

class Element {
boolean esteOperator;
int valoare;
char simbol;
}
Vom utiliza funcµiile denite pentru stiv  ³i procedurile denite în cele ce urmeaz .

static int calcul (char a, int x, int y) {


switch (a) {
case ’+’: return x + y;
case ’*’: return x * y;
}
return 1;
}

Procedura de evaluare const  în stivuirea rezultatelor intermediare, stiva conµinând operatori


³i numere, dar niciodat  nu va conµine consecutiv numere. Se examineaz  succesiv entit µile
expresiei dac  entitatea este un operator sau un num r ³i dac  vârful stivei este un operator,
atunci se plaseaz  în stiv . Dac  este un num r ³i vârful stivei este de asemenea un num r,
acµioneaz  operatorul care precede vârful stivei asupra celor dou  numere ³i se repet  operaµia
asupra rezultatului g sit.
De exemplu, pentru expresia

(+ (* (+ 35 36) (+ 5 6)) (* (+ 7 8) (*9 9 )))


CAPITOLUL 10. LISTE 106

evaluarea decurge astfel:


9
5 7 * *
35 + + + + 15 15
+ + 71 71 71 * * * * *
* * * * * * 781 781 781 781 781 781
+ + + + + + + + + + + + +

static void insereaza (Element x, Stiva s) throws ExceptionStiva {


Element y, op;
while (!(Stiva.esteVida(s) || x.esteOperator
|| Stiva.valoare(s).esteOperator)) {
y = Stiva.valoare(s);
Stiva.elimina(s);
op = Stiva.valoare(s);
Stiva.elimina(s);
x.valoare = calcul(op.valsimb, x.valoare, y.valoare);
}
Stiva.adauga(x,s);
}

static int calcul (Element u[]) throws ExceptionStiva {


Stiva s = new Stiva();
for (int i = 0; i < u.length ; ++i) {
insereaza(u[i], s);
}
return Stiva.valoare(s).valoare;
}

În acest caz, este util  prezentarea unui program principal care utilizeaz  aceste funcµii.

public static void main (String args[]) {


Element exp[] = new Element [args.length];
for (int i = 0; i < args.length; ++i) {
String s = args[i];
if (s.equals("+") || s.equals("*"))
exp[i] = new Element (true, 0, s.charAt(0));
else
exp[i] = new Element (false, Integer.parseInt(s), ’ ’);
}
try { System.out.println(calcul(exp)); }
catch (ExceptionStiva x) {
System.err.println("Stiva " + x.nom);
}
}

10.5 Operaµii asupra listelor


În aceast  secµiune sunt prezentaµi câµiva algoritmi de manipulare a listelor. Ace³tia sunt
utilizaµi în limbajele de programare care au listele ca structuri de baz . Funcµia Tail este o
primitiv  clasic ; ea suprim  primul element al listei.

a Tail(a)

e1 e2 e3 e4 e1 e2 e3 e4

Figura 10.4: Suprimarea primului element din list 


CAPITOLUL 10. LISTE 107

class Lista {
Object info;
Lista next;
Lista(Object x, Lista a) {
info = x;
next = a;
}

static Lista cons (Object x, Lista a) {


return new Lista (x, a);
}

static Object head (Lista a) {


if (a == null)
erreur ("Head d’une liste vide.");
return a.info;
}

static Lista tail (Lista a) {


if (a == null)
erreur ("Tail d’une liste vide.");
return a.next;
}
Procedurile asupra listelor construiesc o list  plecând de la alte dou  liste, pun o list  la
cap tul celeilalte liste. În prima procedur  append, cele dou  liste nu sunt modicate; în a doua
procedur , concat, prima list  este transformat  pentru a reµine rezultatul. Totusi, se poate
remarca faptul c , dac  append copiaz  primul s u argument, partajeaz  nalul listei rezultat
cu al doilea argument.

a b

a1 a2 a3 b1 b2 b3 b4

append(a,b}

a1 a2 a3

Figura 10.5: Concatenarea a dou  liste prin append

static Lista append (Lista a, Lista b) {


if (a == null)
return b;
else
return adauga(a.info, append (a.next, b)) ;
}

a b

a1 a2 a3 b1 b2 b3 b4

concat(a,b}

Figura 10.6: Concatenarea a dou  liste prin concat


CAPITOLUL 10. LISTE 108

static Lista concat(Lista a, Lista b)


{
if (a == null)
return b;
else
{
Lista c = a;
while (c.next != null)
c = c.next;
c.next = b;
return a;
}
}

Aceast  ultim  procedur  se poate scrie recursiv:

static Lista concat (Lista a, Lista b) {


if (a == null)
return b;
else {
a.next = concat (a.next, c);
return a;
}
}
Procedura de calcul de oglindire a unei liste a const  în construirea unei liste în care elementele
listei a sunt în ordine invers . Realizarea acestei proceduri este un exerciµiu clasic de programare
2
asupra listelor. D m aici dou  soluµi, una iterativ , cealalt  recursiv , complexitatea este O n 
deci p tratic , dar clasic . Noua metod  nReverse modic  argumentul s u, în timp ce Reverse
nu-l modic  ci construie³te o alt  list  pentru rezultat.

static Lista nReverse (Lista a) {


Lista b = null;
while (a != null) {
Lista c = a.next;
a.next = b; b = a; a = c;
}
return b;
}

b c

e1 e2 e3 e4 e5 nill

Figura 10.7: Transformarea listei prin inversarea leg turilor

static Lista Reverse (Lista a) {


if (a == null)
return a;
else
return append(Reverse (a.next), adauga(a.info, null));
}

Se poate scrie o versiune iterativ  a versiunii recursive, graµie unei funcµii auxiliare care acumuleaz 
rezultatul într-unul din argumentele sale:
CAPITOLUL 10. LISTE 109

static Lista nReverse (Lista a) {


return nReverse1(null, a);
}

static Lista nReverse1 (Lista b, Lista a) {


if (a == null)
return b;
else
return nReverse1(adauga(a.info, b), a.next);
}

Un alt exerciµiu formator const  în a administra liste în care elementele sunt aranjate în ordine
cresc toare. Procedura de ad ugare devine ceva mai complex  pentru c  trebuie g sit  poziµia
celulei care trebuie ad ugat  dup  parcurgerea unei p rµi a listei.
Nu trat m acest exerciµiu decât în cazul listelor circulare cu gard . Pentru o astfel de list ,
valoarea câmpului inf o din prima celul  nu are nici o semnicaµie (este celula de gard ). Câmpul
next din ultima celul  conµine adresa primei celule.

a1 a2 a3 a4 a5 a6
garda

Figura 10.8: List  circular  cu gard 

static Lista insereaza (int v, Lista a) {


Lista b = a;
while (b.next != a && v > head(b.next))
b = b.next;
b.next = adauga(v, b.next);
a.info = head(a) + 1;
return a;
}
Capitolul 11

Algoritmi divide et impera

11.1 Tehnica divide et impera


22
Divide et impera este o tehnic  de elaborare a algoritmilor care const  în:
ˆ Descompunerea repetat  a problemei (subproblemei) ce trebuie rezolvat  în subprobleme
mai mici.
ˆ Rezolvarea în acela³i mod (recursiv) a tuturor subproblemelor.
ˆ Compunerea subsoluµiilor pentru a obµine soluµia problemei (subproblemei) iniµiale.
Descompunerea problemei (subproblemelor) se face pân  n  momentul în care se obµin sub-
probleme de dimensiuni atât de mici încât au soluµie cunoscut  sau pot  rezolvate prin tehnici
elementare.
Metoda poate  descris  astfel:
procedure divideEtImpera(P, n, S )
if (n & n0 )
then rezolv  subproblema P prin tehnici elementare
else
împarte P în P1 , ..., Pk de dimensiuni n1 , ..., nk
divideEtImpera(P1 , n1 , S1 )
...
divideEtImpera(Pa , nk , Sk )
combin  S1 , ..., Sk pentru a obµine S
Exemplele tipice de aplicare a acestei metode sunt algoritmii de parcurgere a arborilor binari
³i algoritmul de c utare binar .

11.2 Ordinul de complexitate


Vom presupune c  dimensiunea ni a subproblemei i satisface relaµia ni & nb , unde b % 1.
Astfel, pasul de divizare reduce o subproblem  la altele de dimensiuni mai mici, ceea ce asigur 
terminarea subprogramului recursiv.
Presupunem c  divizarea problemei în subprobleme ³i compunerea soluµiilor subproblemelor ne-
k
cesit  un timp de ordinul O n . Atunci, complexitatea timp T n a algoritmului divideEtImpera
este dat  de relaµia de recurenµ :

O 1 , dac  n & n0
T n w (11.2.1)
O n  , dac  n % n0
n k
a T b


Teorema 3. Dac  n % n0 atunci:

~ a%b
log a k
„
„O n b  , dac 
„
„ k k
T n ‚
„O n logb n , dac  a b (11.2.2)
„
„
„O nk  a$b
k
€ , dac 
22
divide-and-conquer, în englez 

110
CAPITOLUL 11. ALGORITMI DIVIDE ET IMPERA 111

m
Demonstraµie:Putem presupune, f r  a restrânge generalitatea, c  n b n0 . De asemenea,
presupunem c  T n c n0 dac  n & n0 ³i T n a T nb   c n dac  n % n0 . Pentru n % n0
k k

avem:
n k
T n aT   cn
b
m1 k
aT b n0   cn
m2 n k k
a aT b n0   c 
 cn
b
2 m2 n k k
a T b n 0   c a   n 
b
...
m m1 n k n k k
a T n 0   c a  m1
 ...  a   n 
b b
m k m1 k k m1 k k m k k
a cn0  c a b n0  ...  a b  n0  b  n0 
k k m
k m b b
cn0 a 1  a  ...   a 

=
m k i
m b
ca 
a
i 0

k
unde am notat cn0 prin c. Distingem cazurile:

1. a % bk . Seria <m
i 0 
b
k

a
i
este convergent  ³i deci ³irul sumelor parµiale este convergent. De
m logb n logb a
aici rezult  c  T n O a  O a  O n 
k m km k k k
2. a b . Rezult  c  a b cn ³i de aici T n O n m O n logb n.

3. a $ b . Avem T n
k m bk m km k
O a a
  O b  O n .

11.3 Exemple
Dintre problemele clasice care se pot rezolva prin metoda divide et impera menµion m:

ˆ c utare binar 

ˆ sortare rapid  (quickSort)

ˆ problema turnurilor din Hanoi

ˆ sortare prin interclasare - Merge sort

ˆ dreptunghi de arie maxim  în placa cu g uri, etc

11.3.1 Sortare prin partitionare - quicksort

Se bazeaz  pe metoda interschimb rii, îns  din nou, interschimbarea se face pe distanµe mai
mari. Astfel, având tabloul a, se aplic  urm torul algoritm:

1. se alege la întâmplare un element x al tabloului

2. se scaneaz  tabloul a la stânga lui x pân  când se g se³te un element ai %x


3. se scaneaz  tabloul la dreapta lui x pân  când se g se³te un element aj $x
4. se interschimb  ai cu aj
CAPITOLUL 11. ALGORITMI DIVIDE ET IMPERA 112

5. se repet  pa³ii 2, 3, 4 pân  când scan rile se vor întâlni pe undeva la mijlocul tabloului. În
acel moment, tabloul a va  partitionat în 2 astfel, la stânga lui x se vor g si elemente mai
mici ca ³i x, la dreapta, elemente mai mari ca ³i x. Dup  aceasta, se aplic  acela³i proces
sub³irurilor de la stânga ³i de la dreapta lui x, pân  când aceste sub³iruri sunt sucient de
mici (se reduc la un singur element).

class QuickSort
{
static int x[]={3,5,2,6,4,1,8,2,4,3,5,3};

public static void main(String[]args)


{
quickSort(0,x.length-1,x);
for(int i=0;i<x.length;i++) System.out.print(x[i]+" ");
}//main

static void quickSort(int st,int dr,int a[])


{
int i=st, j=dr, x, temp;
x=a[(st+dr)/2];
do
{
while (a[i]<x) i++;
while (a[j]>x) --j;
if(i<=j)
{
temp=a[i]; a[i]=a[j]; a[j]=temp;
j--;
i++;
}
} while(i<=j);
if(st<j) quickSort(st,j,a);
if(i<dr) quickSort(i,dr,a);
}// quickSort(...)
}//class

Aceasta metoda are complexitatea n log n, în practic  (în medie)!

11.3.2 Sortare prin interclasare - MergeSort

Ideea este de a utiliza interclasarea în etapa de asamblare a soluµiilor.


În urma rezolv rii recursive a subproblemelor rezult  vectori ordonaµi ³i prin interclasarea lor
obµinem vectorul iniµial sortat. Vectorii de lungime 1 sunt evident consideraµi sortaµi.
Pasul de divizare se face în timpul O 1. Faza de asamblare se face în timpul O m1  m2 
unde n1 ³i n2 sunt lungimile celor doi vectori care se interclaseaz .
Din Teorema 3 pentru a 2, b 2 ³i k 1 rezult  c  ordinul de complexitate al algoritmului
MergeSort este O n log2 n unde n este dimensiunea vectorului iniµial supus sort rii.

class MergeSort
{
static int x[]={3,5,2,6,4,1,8,2,4,3,5,3};

public static void main(String[]args)


{
mergeSort(0,x.length-1,x);
for(int i=0;i<x.length;i++) System.out.print(x[i]+" ");
}//main

public static int[] mergeSort(int st,int dr,int a[])


{
CAPITOLUL 11. ALGORITMI DIVIDE ET IMPERA 113

int m,aux;
if((dr-st)<=1)
{
if(a[st]>a[dr]) { aux=a[st];a[st]=a[dr];a[dr]=aux;}
}
else
{
m=(st+dr)/2;
mergeSort(st,m,a);
mergeSort(m+1,dr,a);
interclasare(st,m,dr,a);
}
return a;
}// mergeSort(...)

public static int[] interclasare(int st,int m,int dr,int a[])


{
// interclasare pozitii st,...,m cu m+1,...,dr
int i,j,k;
int b[]=new int[dr-st+1];
i=st;
j=m+1;
k=0;
while((i<=m)&&(j<=dr))
{
if(a[i]<=a[j]) { b[k]=a[i]; i++;} else { b[k]=a[j]; j++; }
k++;
}

if(i<=m) for(j=i;j<=m; j++) { b[k]=a[j]; k++; }


else for(i=j;i<=dr;i++) { b[k]=a[i]; k++; }

k=0;
for(i=st;i<=dr;i++) { a[i]=b[k]; k++; }
return a;
}//interclasare(...)
}//class

11.3.3 Placa cu g uri

Se consider  o plac  dreptunghiular  în care exist  n g uri punctiforme de coordonate cunos-


cute. S  se determine pe aceast  plac  un dreptunghi de arie maxim , cu laturile paralele cu
laturile pl cii, care s  nu conµin  g uri în interior ci numai eventual pe laturi.
Vom considera placa într-un sistem cartezian. Consider m o subproblem  de forma urm toare:
dreptunghiul determinat de diagonala x1, y1 (din stânga-jos) ³i x2, y2 din dreapta-sus este
analizat pentru a vedea dac  în interior conµine vreo gaur ; dac  nu conµine, este o soluµie posibil 
(se va alege cea de arie maxim ); dac  exist  o gaur  în interiorul s u, atunci se consider  patru
subprobleme generate de cele 4 dreptunghiuri obµinute prin descompunerea pe orizontal  ³i pe
vertical  de cele dou  drepte paralele cu axele care trec prin punctul care reprezint  gaura.

1
4
3
2

Figura 11.1: Dreptunghi de arie maxim  în placa cu g uri

import java.io.*;
CAPITOLUL 11. ALGORITMI DIVIDE ET IMPERA 114

class drArieMaxima
{
static int x1,y1,x2,y2,n,x1s,y1s,x2s,y2s,amax;
static int[] x;
static int[] y;

public static void main (String[] args) throws IOException


{
int i;
StreamTokenizer st=new StreamTokenizer(
new BufferedReader(new FileReader("dreptunghi.in")));
PrintWriter out=new PrintWriter(
new BufferedWriter(new FileWriter("dreptunghi.out")));

st.nextToken(); x1=(int) st.nval;


st.nextToken(); y1=(int) st.nval;
st.nextToken(); x2=(int) st.nval;
st.nextToken(); y2=(int) st.nval;
st.nextToken(); n=(int) st.nval;

x=new int [n+1];


y=new int [n+1];
for(i=1;i<=n;i++)
{
st.nextToken(); x[i]=(int) st.nval;
st.nextToken(); y[i]=(int) st.nval;
}
dr(x1,y1,x2,y2);
out.println(amax);
out.println(x1s+" "+y1s+" "+x2s+" "+y2s);
out.close();
}

static void dr(int x1,int y1,int x2,int y2)


{
int i,s=(x2-x1)*(y2-y1);
if(s<=amax) return;

boolean gasit=false;
for(i=1;i<=n;i++)
if((x1<x[i])&&(x[i]<x2)&&(y1<y[i])&&(y[i]<y2))
{
gasit=true;
break;
}
if(gasit)
{
dr(x1,y1,x[i],y2);
dr(x[i],y1,x2,y2);
dr(x1,y[i],x2,y2);
dr(x1,y1,x2,y[i]);
}
else { amax=s; x1s=x1; y1s=y1; x2s=x2; y2s=y2; }
}
}

11.3.4 Turnurile din Hanoi

Se dau trei tije verticale A, B2 ³i C. Pe tija A se g sesc n discuri de diametre diferite, aranjate
în ordine descresc toare a diametrelor de la baz  spre vârf. Se cere s  se g seasc  o strategie de
CAPITOLUL 11. ALGORITMI DIVIDE ET IMPERA 115

mutare a discurilor de pe tija A pe tija C respectând urm toarele reguli:

a la un moment dat se va muta un singur disc (cel care se a  deasupra celorlalte
discuri pe o tij );
a un disc poate  a³ezat doar peste un disc de diametru mai mare decât al s  sau pe
o tij  goal .

Împ rµim problema astfel:


a se mut  primele n  1 discuri de pe tija surs  A pe tija intermediar  B
a se mut  ultimul disc de pe tija surs  A pe tija destinaµie C
a se mut  cele n  1 discuri de pe tija intermediar  B pe tija destinaµie C

pasul 1

A B C A B C
a) b)
pasul 2

pasul 3

A B d) C A B c) C

import java.io.*;
class Hanoi
{
static int nrMutare=0;
public static void main(String[] args) throws IOException
{
int nrDiscuri;

BufferedReader br = new BufferedReader(new InputStreamReader(System.in));

System.out.print("Introduceti numarul discurilor: ");


nrDiscuri=Integer.parseInt(br.readLine());

solutiaHanoi(nrDiscuri, ’A’, ’B’, ’C’);


}// main()

static void solutiaHanoi(int nrDiscuri, char tijaSursa,


char tijaIntermediara, char tijaDestinatie)
{
if(nrDiscuri==1)
System.out.println(++nrMutare + " Disc 1 " +
tijaSursa + " ==> "+ tijaDestinatie);
else
{
solutiaHanoi(nrDiscuri-1,tijaSursa,tijaDestinatie,tijaIntermediara);

System.out.println(++nrMutare + " Disk " + nrDiscuri +


" " + tijaSursa + " --> "+ tijaDestinatie);
solutiaHanoi(nrDiscuri-1,tijaIntermediara,tijaSursa,tijaDestinatie);
}
}// solutiaHanoi()
}// class

Introduceti numarul discurilor: 4


1 Disc 1 A ==> B
CAPITOLUL 11. ALGORITMI DIVIDE ET IMPERA 116

2 Disk 2 A --> C
3 Disc 1 B ==> C
4 Disk 3 A --> B
5 Disc 1 C ==> A
6 Disk 2 C --> B
7 Disc 1 A ==> B
8 Disk 4 A --> C
9 Disc 1 B ==> C
10 Disk 2 B --> A
11 Disc 1 C ==> A
12 Disk 3 B --> C
13 Disc 1 A ==> B
14 Disk 2 A --> C
15 Disc 1 B ==> C

import java.io.*;
class HanoiDisc
{
static int nrMutare=0;
static final int MAX_NR_DISCURI=9;
static int[] a=new int[MAX_NR_DISCURI],
b=new int[MAX_NR_DISCURI],
c=new int[MAX_NR_DISCURI];
static int na,nb,nc; // na = nr. discuri pe tija A; etc. ...
static int discMutat;

public static void main(String[] args) throws IOException


{
BufferedReader br=new BufferedReader(new InputStreamReader(System.in));
int nrDiscuri=0;
while((nrDiscuri<1)||(nrDiscuri>MAX_NR_DISCURI))
{
System.out.print("Numar discuri"+"[1<=...<="+MAX_NR_DISCURI+"]: ");
nrDiscuri = Integer.parseInt(br.readLine());
}
na=nrDiscuri;
nb=0;
nc=0;
for(int k=0;k<na;k++) a[k]=na-k;
afisareDiscuri(); System.out.println();
solutiaHanoi(nrDiscuri, ’A’, ’B’, ’C’);
}// main()

static void solutiaHanoi(int nrDiscuri, char tijaSursa,


char tijaIntermediara, char tijaDestinatie)
{
if (nrDiscuri==1)
{
mutaDiscul(tijaSursa, tijaDestinatie);
System.out.print(++nrMutare + " Disc " + "1" + " " +
tijaSursa + " ==> "+ tijaDestinatie + " ");
afisareDiscuri(); System.out.println();
}
else
{
solutiaHanoi(nrDiscuri-1,tijaSursa,tijaDestinatie,tijaIntermediara);
mutaDiscul(tijaSursa, tijaDestinatie);
System.out.print(++nrMutare + " Disc " + nrDiscuri +
" " + tijaSursa + " --> "+ tijaDestinatie + " ");
afisareDiscuri();
System.out.println();
CAPITOLUL 11. ALGORITMI DIVIDE ET IMPERA 117

solutiaHanoi(nrDiscuri-1, tijaIntermediara, tijaSursa, tijaDestinatie);


}
} // solutiaHanoi()

static void mutaDiscul(char tijaSursa, char tijaDestinatie)


{
switch (tijaSursa)
{
case ’A’: discMutat=a[(na--)-1]; break;
case ’B’: discMutat=b[(nb--)-1]; break;
case ’C’: discMutat=c[(nc--)-1];
}

switch (tijaDestinatie)
{
case ’A’: a[(++na)-1]=discMutat; break;
case ’B’: b[(++nb)-1]=discMutat; break;
case ’C’: c[(++nc)-1]=discMutat;
}
}// mutaDiscul()

static void afisareDiscuri()


{
System.out.print(" A(");
for(int i=0;i<na;i++) System.out.print(a[i]);
System.out.print(") ");
System.out.print(" B(");
for(int i=0;i<nb;i++) System.out.print(b[i]);
System.out.print(") ");
System.out.print(" C(");
for(int i=0;i<nc;i++) System.out.print(c[i]);
System.out.print(") ");
}// afisareDiscuri()
}// class

Numar discuri[1<=...<=9]: 4
A(4321) B() C()
1 Disc 1 A ==> B A(432) B(1) C()
2 Disc 2 A --> C A(43) B(1) C(2)
3 Disc 1 B ==> C A(43) B() C(21)
4 Disc 3 A --> B A(4) B(3) C(21)
5 Disc 1 C ==> A A(41) B(3) C(2)
6 Disc 2 C --> B A(41) B(32) C()
7 Disc 1 A ==> B A(4) B(321) C()
8 Disc 4 A --> C A() B(321) C(4)
9 Disc 1 B ==> C A() B(32) C(41)
10 Disc 2 B --> A A(2) B(3) C(41)
11 Disc 1 C ==> A A(21) B(3) C(4)
12 Disc 3 B --> C A(21) B() C(43)
13 Disc 1 A ==> B A(2) B(1) C(43)
14 Disc 2 A --> C A() B(1) C(432)
15 Disc 1 B ==> C A() B() C(4321)

11.3.5 Înjum t µire repetat 

Se consider  un ³ir de numere naturale x1 , x2 , ..., xn ³i o succesiune de decizii d1 , d2 , ... unde


di " rS, Dx au urm toarea semnicaµie: la pasul i, ³irul r mas în acel moment se împarte în dou 
sub³iruri cu num r egal elemente (dac  num rul de elemente este impar, elementul situat la mijloc
se elimin ) ³i se elimin  jum tatea din partea stâng  a ³irului dac  di S (dac  di D se elimin 
jum tatea din partea drept ).
CAPITOLUL 11. ALGORITMI DIVIDE ET IMPERA 118

Deciziile se aplic  în ordine, una dup  alta, pân  când r mâne un singur element. Se cere acest
element.

class Injumatatire1
{
static int n=10;
static char[] d={’X’,’S’,’D’,’S’,’S’}; // nu folosesc d_0 ... !

public static void main(String[]args)


{
int st=1,dr=n,m,i=1;
boolean impar;

while(st<dr)
{
System.out.println(st+" ... "+dr+" elimin "+d[i]);
m=(st+dr)/2;
if((dr-st+1)%2==1) impar=true; else impar=false;

if(d[i]==’S’) // elimin jumatatea stanga


{
st=m+1;
}
else // elimin jumatatea dreapta
{
dr=m;
if(impar) dr--;
}
i++;
System.out.println(st+" ... "+dr+" a ramas! \n");
}
}//main
}//class

1 ... 10 elimin S
6 ... 10 a ramas!

6 ... 10 elimin D
6 ... 7 a ramas!

6 ... 7 elimin S
7 ... 7 a ramas!

Toate elementele care pot r mâne:

class Injumatatire2
{
static int n=10;
static char[] d=new char[10];

public static void main(String[]args)


{
divide(1,n,1);
}//main

static void divide(int st0,int dr0, int i)


{
int st,dr,m;
boolean impar;

if(st0==dr0)
{
CAPITOLUL 11. ALGORITMI DIVIDE ET IMPERA 119

for(m=1;m<=i;m++) System.out.print(d[m]);
System.out.println(" --> "+st0);
return;
}

m=(st0+dr0)/2;
if((dr0-st0+1)%2==1) impar=true; else impar=false;

// elimin jumatatea stanga


st=m+1;
d[i]=’S’;
if(st<=dr0) divide(st,dr0,i+1);

// elimin jumatatea dreapta


dr=m;
if(impar) dr--;
d[i]=’D’;
if(st0<=dr) divide(st0,dr,i+1);
}// divide(...)
}//class

SSS --> 10
SSD --> 9
SDS --> 7
SDD --> 6
DSS --> 5
DSD --> 4
DDS --> 2
DDD --> 1
Capitolul 12

Algoritmi BFS-Lee

12.1 Prezentare general 

import java.io.*;
class BFS // nodurile sunt de la 1 la n
{ // distanta minima dintre nod "sursa" si nod "dest"
static final int oo=0x7fffffff; // infinit
static final int WHITE=0, GRAY=1, BLACK=2;
static int[][] a; // matricea de adiacenta
static int[] color; // pentru bfs
static int[] p; // predecesor
static int[] d; // distante catre sursa
static int[] q; // coada
static int ic; // inceput coada = prima pozitie ocupata din care scot !
static int sc; // sfarsit coada = prima pozitie libera pe care voi pune !
static int n,m; // varfuri, muchii

public static void main(String[] args) throws IOException


{
int i,j,k,nods,nodd; // nod_sursa, nod_destinatie

StreamTokenizer st=new StreamTokenizer(


new BufferedReader(new FileReader("bfs.in")));
PrintWriter out=new PrintWriter(
new BufferedWriter(new FileWriter("bfs.out")));

st.nextToken(); n=(int)st.nval;
st.nextToken(); m=(int)st.nval;
st.nextToken(); nods=(int)st.nval;
st.nextToken(); nodd=(int)st.nval;

a=new int[n+1][n+1];
color=new int[n+1];
p=new int[n+1];
d=new int[n+1];
q=new int[m+1];

for(k=1;k<=m;k++)
{
st.nextToken(); i=(int)st.nval;
st.nextToken(); j=(int)st.nval;
a[i][j]=1;

120
CAPITOLUL 12. ALGORITMI BFS-LEE 121

a[j][i]=1;
}

bfs(nods,nodd);

System.out.println("Distanta("+nods+","+nodd+") = "+d[nodd]);
System.out.print("drum : "); drum(nodd); System.out.println();
out.close();
}//main

static void videzcoada()


{
ic=0;
sc=0; // coada : scot <-- icoada...scoada <-- introduc
}

static boolean coadaEsteVida()


{
return (sc==ic);
}

static void incoada(int v)


{
q[sc++]=v;
color[v]=GRAY;
}

static int dincoada()


{
int v=q[ic++];
color[v]=BLACK;
return v;
}

static void bfs(int start, int fin)


{
int u, v;
for(u=1; u<=n; u++)
{
color[u]=WHITE;
d[u]=oo;
}
color[start]=GRAY;
d[start]=0;

videzcoada();
incoada(start);
while(!coadaEsteVida())
{
u=dincoada();

// Cauta nodurile albe v adiacente cu nodul u si pun v in coada


for(v=1; v<=n; v++)
{
if(a[u][v]==1) // v in Ad[u]
{
if(color[v]==WHITE) // neparcurs deja
{
color[v]=GRAY;
d[v]=d[u]+1;
CAPITOLUL 12. ALGORITMI BFS-LEE 122

p[v]=u;
if(color[fin]!=WHITE) break; // optimizare; ies din for
incoada(v);
}
}
}//for

color[u]=BLACK;
if(color[fin]!=WHITE) break; // am ajuns la nod_destinatie
}//while
}//bfs

static void drum(int u) // nod_sursa ---> nod_destinatie


{
if(p[u]!=0) drum(p[u]);
System.out.print(u+" ");
}
}//class

/*
9 12 2 8 Distanta(2,8) = 4
2 5 drum : 2 3 4 7 8
1 2
2 3
3 1
3 4
4 5
5 6
6 4
7 8
8 9
9 7
7 4
*/

12.2 Probleme rezolvate

12.2.1 Romeo ³i Julieta - OJI2004 clasa a X-a

În ultima ecranizare a celebrei piese shakespeariene Romeo ³i Julieta tr iesc într-un ora³ mo-
dern, comunic  prin e-mail ³i chiar învaµ  s  programeze. Într-o secvenµ  tulbur toare sunt pre-
zentate fr mât rile interioare ale celor doi eroi încercând f r  succes s  scrie un program care s 
determine un punct optim de întâlnire.
Ei au analizat harta ora³ului ³i au reprezentat-o sub forma unei matrice cu n linii ³i m coloane,
în matrice ind marcate cu spaµiu zonele prin care se poate trece (str zi lipsite de pericole) ³i cu
X zonele prin care nu se poate trece. De asemenea, în matrice au marcat cu R locul în care se
a  locuinµa lui Romeo, iar cu J locul în care se a  locuinµa Julietei.
Ei se pot deplasa numai prin zonele care sunt marcate cu spaµiu, din poziµia curent  în oricare
dintre cele 8 poziµii învecinate (pe orizontal , vertical  sau diagonale).
Cum lui Romeo nu îi place s  a³tepte ³i nici s  se lase a³teptat n-ar  tocmai bine, ei au hot rât
c  trebuie s  aleag  un punct de întâlnire în care atât Romeo, cât ³i Julieta s  poat  ajunge în
acela³i timp, plecând de acas . Fiindc  la întâlniri amândoi vin într-un suet, ei estimeaz  timpul
necesar pentru a ajunge la întâlnire prin num rul de elemente din matrice care constituie drumul
cel mai scurt de acas  pân  la punctul de întâlnire. “i cum probabil exist  mai multe puncte de
întâlnire posibile, ei vor s  îl aleag  pe cel în care timpul necesar pentru a ajunge la punctul de
întâlnire este minim.
Cerinµ 
CAPITOLUL 12. ALGORITMI BFS-LEE 123

Scrieµi un program care s  determine o poziµie pe hart  la care Romeo ³i Julieta pot s  ajung 
în acela³i timp. Dac  exist  mai multe soluµii, programul trebuie s  determine o soluµie pentru
care timpul este minim.
Datele de intrare
Fi³ierul de intrare rj.in conµine:
 pe prima linie numerele naturale N M , care reprezint  num rul de linii ³i respectiv de coloane
ale matricei, separate prin spaµiu;
 pe ecare dintre urm toarele N linii se a  M caractere (care pot  doar R, J , X sau spaµiu)
reprezentând matricea.
Datele de ie³ire
Fi³ierul de ie³ire rj.out va conµine o singur  linie pe care sunt scrise trei numere naturale
separate prin câte un spaµiu tmin x y , având semnicaµia:
 x y reprezint  punctul de întâlnire (x - num rul liniei, y - num rul coloanei);
 tmin este timpul minim în care Romeo (respectiv Julieta) ajunge la punctul de întâlnire.

Restricµii ³i preciz ri
a 1 $ N, M $ 101
a Liniile ³i coloanele matricei sunt numerotate începând cu 1.
a Pentru datele de test exist  întotdeauna soluµie.
Exemple
rj.in
5 8
XXR XXX
X X X
J X X X
XX
XXX XXXX
rj.out
4 4 4
Explicaµie:
Traseul lui Romeo poate : (1,3), (2,4), (3,4), (4,4). Timpul necesar lui Romeo pentru a ajunge
de acas  la punctul de întâlnire este 4.
Traseul Julietei poate : (3,1), (4,2), (4,3), (4,5). Timpul necesar Julietei pentru a ajunge de
acas  la punctul de întâlnire este deasemenea 4.
În plus 4, este punctul cel mai apropiat de ei cu aceast  proprietate.
Timp maxim de executare: 1 secund /test

Indicaµii de rezolvare *

Mihai Stroe, Ginfo nr. 14/4 aprilie 2004


Problema se rezolv  folosind algoritmul lui Lee.
Se aplic  acest algoritm folosind ca puncte de start poziµia lui Romeo ³i poziµia Julietei.
Vom folosi o matrice D în care vom pune valoarea 1 peste tot pe unde nu se poate trece,
valoarea 2 în poziµia în care se a  Romeo iniµial, valoarea 3 în poziµia în care se a  Julieta
iniµial ³i valoarea 0 în rest.
La ecare pas k vom parcurge aceast  matrice ³i în poziµiile vecine celor care au valoarea 2
vom pune valoarea 2, dac  acestea au valoarea 0 sau 3. Dac  o poziµie are valoare 3, înseamn  c 
la un moment de timp anterior Julieta se putea aa în poziµia respectiv . La acela³i pas k vom
mai parcurge matricea o dat  ³i în poziµiile vecine celor care au valoarea 3 vom pune valoarea 3,
dac  acestea au valoarea 0.
Dac  la pasul k poziµia vecin  uneia care are valoarea 3, are valoarea 2, atunci ne vom opri ³i
k reprezint  momentul minim de timp dup  care cei doi se întâlnesc, iar poziµia care are valoare
2 reprezint  locul întâlnirii.
La prima vedere s-ar p rea c  num rul k nu reprezint  momentul de timp minim la care cei doi
se întâlnesc. Vom demonstra c  algoritmul este corect prin metoda reducerii la absurd. Pentru
aceasta avem în vedere c  poziµiile mar­cate cu 2 reprezint  toate locurile în care se poate aa
CAPITOLUL 12. ALGORITMI BFS-LEE 124

Romeo dup  cel mult k pa³i, iar cele marcate cu 2 reprezint  toate locurile în care se poate aa
Julieta dup  cel mult k pa³i. Dac  k nu reprezint  momentul de timp minim la care cei doi se
întâlnesc înseamn  c  acesta a fost determinat mai devreme ³i algoritmul s-a oprit deja.
Analiza complexit µii
Ordinul de complexitate al operaµiei de citire a datelor de intrare este O M N .
Ordinul de complexitate al acestui algoritm este O kM N , unde k reprezint  momentul în
care cei doi se întâlnesc.
Ordinul de complexitate al operaµiei de scriere a rezultatului este O 1.
În concluzie, ordinul de complexitate al algoritmului de rezolvare a acestei probleme este
O kM N .

Rezolvare detaliat 

Codul surs  *

import java.io.*;
class RJ
{
static final int zid=10000;
static int m,n;
static int[][] xr,xj;
static int[] qi=new int[5000]; // coada sau coada circulara mai bine !
static int[] qj=new int[5000]; // coada

static int ic, sc; // ic=inceput coada

public static void main(String[] args) throws IOException


{
long t1,t2;
t1=System.currentTimeMillis();

int i,j,ir=0,jr=0,ij=0,jj=0, imin, jmin, tmin;


String s;

BufferedReader br=new BufferedReader(new FileReader("rj.in"));


StreamTokenizer st=new StreamTokenizer(br);
PrintWriter out=new PrintWriter(
new BufferedWriter(new FileWriter("rj.out")));

st.nextToken(); m=(int)st.nval;
st.nextToken(); n=(int)st.nval;

xr=new int[m+2][n+2]; // matrice bordata cu zid !


xj=new int[m+2][n+2]; // matrice bordata cu zid !

br.readLine(); // citeste CRLF !!!


for(i=1;i<=m;i++)
{
s=br.readLine();
for(j=1;j<=n;j++)
if(s.charAt(j-1)==’X’) xr[i][j]=xj[i][j]=zid; // zid!
else if(s.charAt(j-1)==’R’) {ir=i; jr=j; xr[i][j]=1;}
else if(s.charAt(j-1)==’J’) {ij=i; jj=j; xj[i][j]=1;}
}

for(i=0;i<=m+1;i++) xr[i][0]=xr[i][n+1]=xj[i][0]=xj[i][n+1]=zid; // E si V
for(j=0;j<=n+1;j++) xr[0][j]=xr[m+1][j]=xj[0][j]=xj[m+1][j]=zid; // N si S
CAPITOLUL 12. ALGORITMI BFS-LEE 125

ic=sc=0; // coada vida;


qi[sc]=ir; qj[sc]=jr; sc++; // (ir,jr) --> coada
while(ic!=sc)
{
i=qi[ic]; j=qj[ic]; ic++; // scot din coada
fill(xr,i,j);
}

ic=sc=0; // coada vida;


qi[sc]=ij; qj[sc]=jj; sc++; // (ij,jj) --> coada
while(ic!=sc)
{
i=qi[ic]; j=qj[ic]; ic++; // scot din coada
fill(xj,i,j);
}

tmin=10000;
imin=jmin=0;
for(i=1;i<=m;i++)
for(j=1;j<=n;j++)
if(xr[i][j]==xj[i][j])
if(xj[i][j]!=0) // pot exista pozitii ramase izolate !
if(xr[i][j]<tmin) {tmin=xr[i][j]; imin=i; jmin=j;}

out.println(tmin+" "+imin+" "+jmin);


out.close();
t2=System.currentTimeMillis();
System.out.println("Timp = "+(t2-t1));
}// main()

static void fill(int[][] x,int i, int j)


{
int t=x[i][j]; // timp
if(x[i-1][j]==0) {x[i-1][j]=t+1; qi[sc]=i-1; qj[sc]=j; sc++;} // N
if(x[i-1][j+1]==0) {x[i-1][j+1]=t+1; qi[sc]=i-1; qj[sc]=j+1; sc++;} // NE
if(x[i-1][j-1]==0) {x[i-1][j-1]=t+1; qi[sc]=i-1; qj[sc]=j-1; sc++;} // NV

if(x[i+1][j]==0) {x[i+1][j]=t+1; qi[sc]=i+1; qj[sc]=j; sc++;} // S


if(x[i+1][j+1]==0) {x[i+1][j+1]=t+1; qi[sc]=i+1; qj[sc]=j+1; sc++;} // SE
if(x[i+1][j-1]==0) {x[i+1][j-1]=t+1; qi[sc]=i+1; qj[sc]=j-1; sc++;} // SV

if(x[i][j+1]==0) {x[i][j+1]=t+1; qi[sc]=i; qj[sc]=j+1; sc++;} // E


if(x[i][j-1]==0) {x[i][j-1]=t+1; qi[sc]=i; qj[sc]=j-1; sc++;} // V
}// fil(...)
}// class

12.2.2 Sudest - OJI2006 clasa a X-a

Fermierul Ion deµine un teren de form  p trat , împ rµit în N  N p trate de latur  unitate,
pe care cultiv  carto. Pentru recoltarea cartolor fermierul folose³te un robot special proiectat
în acest scop. Robotul porne³te din p tratul din stânga sus, de coordonate 1, 1 ³i trebuie s 
ajung  în p tratul din dreapta jos, de coordonate N, N .
Traseul robotului este programat prin memorarea unor comenzi pe o cartel  magnetic . Fiecare
comand  specic  direcµia de deplasare (sud sau est) ³i num rul de p trate pe care le parcurge
în direcµia respectiv . Robotul strânge recolta doar din p tratele în care se opre³te între dou 
comenzi.
Din p cate, cartela pe care se a  programul s-a deteriorat ³i unitatea de citire a robotului
nu mai poate distinge direcµia de deplasare, ci numai num rul de pa³i pe care trebuie s -i fac 
robotul la ecare comand . Fermierul Ion trebuie s  introduc  manual, pentru ecare comand ,
CAPITOLUL 12. ALGORITMI BFS-LEE 126

direcµia de deplasare.
Cerinµ 
Scrieµi un program care s  determine cantitatea maxim  de carto pe care o poate culege
robotul, în ipoteza în care Ion specic  manual, pentru ecare comand , direcµia urmat  de
robot. Se va determina ³i traseul pe care se obµine recolta maxim .
Datele de intrare
Fi³ierul de intrare sudest.in are urm toarea structur :

ˆ Pe linia 1 se a  num rul natural N , reprezentând dimensiunea parcelei de teren.

ˆ Pe urm toarele N linii se a  câte N numere naturale, separate prin spaµii, reprezentând
cantitatea de carto din ecare p trat unitate.

ˆ Pe linia N  2 se a  un num r natural K reprezentând num rul de comenzi aate pe cartela


magnetic .

ˆ Pe linia N  3 se a  K numerele naturale C1 , ...,CK , separate prin spaµii, reprezentând


num rul de pa³i pe care trebuie s -i efectueze robotul la ecare comand .

Datele de ie³ire
Fi³ierul de iesire sudest.out va conµine pe prima linie cantitatea maxim  de carto recoltat 
de robot. Pe urm toarele K  1 linii vor  scrise, în ordine, coordonatele p tratelor unitate ce
constituie traseul pentru care se obµine cantitate maxim  de carto, câte un p trat unitate pe o
linie. Coordonatele scrise pe aceea³i linie vor  separate printr-un spaµiu. Primul p trat de pe
traseu va avea coordonatele 11, iar ultimul va avea coordonatele N N . Dac  sunt mai multe trasee
pe care se obµine o cantitate maxim  de carto recoltat  se va a³a unul dintre acestea.
Restricµii ³i preciz ri
ˆ 5&N & 100
ˆ 2&K &2˜N 2
ˆ 1 & C1 , ..., CK & 10
ˆ Cantitatea de carto dintr-un p trat de teren este num r natural între 0 ³i 100.

ˆ Pentru ecare set de date de intrare se garanteaz  c  exist  cel puµin un traseu.

ˆ Se consider  c  robotul strânge recolta ³i din p tratul de plecare 1, 1 ³i din cel de sosire
N, N ).
ˆ Pentru determinarea corect  a cantit µii maxime recoltate se acord  50% din punctajul
alocat testului respectiv; pentru cantitate maxim  recoltat  ³i traseu corect se acord  100%.

Exemplu
sudest.in sudest.out Explicaµii
6 29 Un alt traseu posibil este:
121041 11 11
133511 31 13
2 2 1 2 1 10 51 15
453926 61 25
113201 65 65
10 2 4 6 5 10 66 66
5 dar costul s u este 1  1  4  1  5  10 22
22141
Timp maxim de execuµie/test: 1 secund 
CAPITOLUL 12. ALGORITMI BFS-LEE 127

Indicaµii de rezolvare *

soluµia comisiei
Reprezentarea informaµiilor

ˆ N - num rul de linii

ˆ K - num rul de comenzi

ˆ AN maxN max; - memoreaz  cantitatea de produs

ˆ C N maxN max; - C ij  = cantitatea maxim  de carto culeas  pe un traseu ce por-


ne³te din 1, 1 ³i se termin  în i, j , respectând condiµiile problemei

ˆ P N maxN max; - P ij  = pasul la care am ajuns în poziµia i, j culegând o cantitate


maxim  de carto

ˆ M ove2 ˜ N max; - memoreaz  cele K comenzi

Parcurg ³irul celor k mut ri. La ecare mutare marchez poziµiile în care pot ajunge la mutarea
respectiv .
Mai exact, parcurg toate poziµiile în care am putut ajunge la pasul precedent (cele marcate în
matricea P corespunz tor cu num rul pasului precedent) ³i pentru ecare poziµie veric dac  la
pasul curent pot s  execut mutarea la sud.
În caz armativ, veric dac  în acest caz obµin o cantitate de carto mai mare decât cea
obµinut  pân  la momentul curent (dac  da, reµin noua cantitate, ³i marchez în matricea P
poziµia în care am ajuns cu indicele mut rii curente).
În mod similar procedez pentru o mutare spre est.

Codul surs  *

Variant  dup  soluµia ocial :

import java.io.*;
class Sudest1
{
static StreamTokenizer st;
static PrintWriter out;
static final int Nmax=101;
static int N, K;
static int[][] A=new int[Nmax][Nmax]; // A[i][j]=cantitatea de cartofi
static int[][] C=new int[Nmax][Nmax]; // C[i][j]=cantitatea maxima ...
static int[][] P=new int[Nmax][Nmax]; // pas
static int[] Move=new int[2*Nmax]; // comenzile

public static void main(String[] args) throws IOException


{
st=new StreamTokenizer( new BufferedReader(new FileReader("sudest.in")));
out=new PrintWriter(new BufferedWriter(new FileWriter("sudest.out")));
read_data();
init();
solve();
}// main(...)

static void read_data() throws IOException


{
int i,j,cmd;
st.nextToken(); N=(int)st.nval;
for(i=1;i<=N;i++)
for(j=1;j<=N;j++) { st.nextToken(); A[i][j]=(int)st.nval;}
st.nextToken(); K=(int)st.nval;
for(cmd=1;cmd<=K;cmd++) { st.nextToken(); Move[cmd]=(int)st.nval;}
CAPITOLUL 12. ALGORITMI BFS-LEE 128

}// read_data()

static void init()


{
int i,j;
for(i=1;i<=N;i++) for(j=1;j<=N;j++) {C[i][j]=-1; P[i][j]=255;}
}// init()

static boolean posibil(int x,int y) {return 1<=x && 1<=y && x<=N && y<=N;}

static void solve()


{
int i,j, cmd;
P[1][1]=0;
C[1][1]=A[1][1];
for(cmd=1; cmd<=K; cmd++)
for(i=1; i<=N; i++)
for(j=1; j<=N; j++)
if(P[i][j]==cmd-1)
{
if(posibil(i+Move[cmd],j)) // SUD
if(C[i][j]+A[i+Move[cmd]][j]>C[i+Move[cmd]][j])
{
P[i+Move[cmd]][j]=cmd;
C[i+Move[cmd]][j]=C[i][j]+A[i+Move[cmd]][j];
}
if(posibil(i,j+Move[cmd])) // EST
if(C[i][j]+A[i][j+Move[cmd]]>C[i][j+Move[cmd]])
{
P[i][j+Move[cmd]]=cmd;
C[i][j+Move[cmd]]=C[i][j]+A[i][j+Move[cmd]];
}
}// if
out.println(C[N][N]); drum(N,N,K); out.close();
}// solve()

static void drum(int x, int y, int pas)


{
int i;
boolean gasit;
if(x==1 && y==1) out.println("1 1");
else
{
gasit=false;
if(posibil(x,y-Move[pas]))
if(C[x][y-Move[pas]]==C[x][y]-A[x][y] && P[x][y-Move[pas]]==pas-1)
{
drum(x,y-Move[pas],pas-1);
out.println(x+" "+y);
gasit=true;
}
if(!gasit)
if(posibil(x-Move[pas],y))
if(C[x-Move[pas]][y]==C[x][y]-A[x][y] && P[x-Move[pas]][y]==pas-1)
{
drum(x-Move[pas],y,pas-1);
out.println(x+" "+y);
}
}// else
}// drum(...)
CAPITOLUL 12. ALGORITMI BFS-LEE 129

}// class
Variant  folosind coad :

import java.io.*; // 11, ...,nn --> 1,...,n*n pozitii in matrice !


class Sudest2
{
static StreamTokenizer st;
static PrintWriter out;
static final int nmax=101;
static int n, k;
static int[][] a=new int[nmax][nmax]; // a[i][j]=cantitatea de cartofi
static int[][] c=new int[nmax][nmax]; // c[i][j]=cantitatea maxima ...
static int[][] p=new int[nmax][nmax]; // pozitia anterioara optima
static int[] move=new int[2*nmax]; // comenzile
static int ic,sc,scv,qmax=100;
static int[] q=new int[qmax]; // coada

public static void main(String[] args) throws IOException


{
st=new StreamTokenizer(new BufferedReader(new FileReader("sudest.in")));
out=new PrintWriter(new BufferedWriter(new FileWriter("sudest.out")));
read_data();
init();
solve();
}// main(...)

static void read_data() throws IOException


{
int i,j,cmd;
st.nextToken(); n=(int)st.nval;
for(i=1;i<=n;i++)
for(j=1;j<=n;j++) { st.nextToken(); a[i][j]=(int)st.nval;}
st.nextToken(); k=(int)st.nval;
for(cmd=1;cmd<=k;cmd++) { st.nextToken(); move[cmd]=(int)st.nval;}
}// read_data()

static void init()


{
int i,j;
for(i=1;i<=n;i++) for(j=1;j<=n;j++) {c[i][j]=-1; p[i][j]=255;}
}// init()

static void solve()


{
int i,j,ii,jj,cmd;
p[1][1]=0; c[1][1]=a[1][1];
ic=0; sc=1; q[ic]=1; // pozitia [1][1] --> q
scv=1; // ultimul din coada la distanta 1 de [1][1]
for(cmd=1; cmd<=k; cmd++)
{
while(ic!=scv)
{
i=(q[ic]-1)/n+1; j=(q[ic]-1)%n+1; ic=(ic+1)%qmax; // scot din coada
// propag catre Sud
ii=i+move[cmd];
jj=j;
if((ii>=1)&&(ii<=n))
if(c[i][j]+a[ii][jj]>c[ii][jj])
{
c[ii][jj]=c[i][j]+a[ii][jj];
CAPITOLUL 12. ALGORITMI BFS-LEE 130

p[ii][jj]=(i-1)*n+j;
q[sc]=(ii-1)*n+jj; sc=(sc+1)%qmax; // pun in coada
}

// propag catre Est


jj=j+move[cmd];
ii=i;
if((jj>=1)&&(jj<=n))
if(c[i][j]+a[ii][jj]>c[ii][jj])
{
c[ii][jj]=c[i][j]+a[ii][jj];
p[ii][jj]=(i-1)*n+j;
q[sc]=(ii-1)*n+jj; sc=(sc+1)%qmax; // pun in coada
}
}// while
scv=sc;
}// for

out.println(c[n][n]); drum(n,n); out.close();


}// solve()

static void drum(int i, int j)


{
if(i*j==0) return;
drum((p[i][j]-1)/n+1,(p[i][j]-1)%n+1);
out.println(i+" "+j);
}// drum(...)
}// class

12.2.3 Muzeu - ONI2003 clasa a X-a

Sunteµi un participant la Olimpiada Naµional  de Informatic . În programul olimpiadei


intr  ³i câteva activit µii de divertisment. Una dintre ele este vizitarea unui muzeu. Acesta are o
structur  de matrice dreptunghiular  cu M linii ³i N coloane; din orice camer  se poate ajunge
în camerele vecine pe direcµiile nord, est, sud ³i vest (dac  aceste camere exist ). Pentru poziµia
i, j  deplasarea spre nord presupune trecerea în poziµia i  1, j , spre est în i, j  1, spre sud
în i  1, j  ³i spre vest în i, j  1.
Acest muzeu are câteva reguli speciale. Fiecare camer  este marcat  cu un num r între 0 ³i
10 inclusiv. Mai multe camere pot  marcate cu acela³i num r. Camerele marcate cu num rul 0
pot  vizitate gratuit. Într-o camer  marcat  cu num rul i (i % 0) se poate intra gratuit, dar nu
se poate ie³i din ea decât dac  ar taµi supraveghetorului un bilet cu num rul i. Din fericire, orice
camer  cu num rul i (i % 0) ofer  spre vânzare un bilet cu num rul i; o dat  cump rat acest bilet,
el este valabil în toate camerele marcate cu num rul respectiv. Biletele pot avea preµuri diferite,
dar un bilet cu num rul i va avea acela³i preµ în toate camerele în care este oferit spre vânzare.
Dumneavoastr  intraµi în muzeu prin colµul de Nord-Vest (poziµia 1, 1 a matricei) ³i doriµi
s  ajungeµi la ie³irea situat  în colµul de Sud-Est (poziµia M, N  a matricei). O dat  ajuns acolo
primiµi un bilet gratuit care v  permite s  vizitaµi tot muzeul.
Poziµiile 1, 1 ³i M, N  sunt marcate cu num rul 0.
Cerinµ 
Cunoscându-se structura muzeului, determinaµi o strategie de parcurgere a camerelor, astfel
încât s  ajungeµi în camera M, N  pl tind cât mai puµin. Dac  exist  mai multe variante, alegeµi
una în care este parcurs un num r minim de camere (pentru a câ?tiga timp ³i pentru a avea mai
mult timp pentru vizitarea integral  a muzeului).
Date de intrare
Prima linie a ³ierului de intrare muzeu.in conµine dou  numere întregi M ³i N , separate
printr-un spaµiu, num rul de linii, respectiv de coloane, al matricei care reprezint  muzeul. Urm -
toarele M linii conµin structura muzeului; ecare conµine N numere întregi între 0 ³i 10 inclusiv,
separate prin spaµii. Linia M  2 conµine 10 numere întregi între 0 ³i 10000 inclusiv, reprezentând
costurile biletelor 1, 2, 3, ...10 în aceast  ordine.
Date de ie³ire
CAPITOLUL 12. ALGORITMI BFS-LEE 131

În ³ierul muzeu.out veµi a³a:


pe prima linie suma minim  necesar  pentru a ajunge din 1, 1 în M, N ;
pe a doua linie num rul minim de mut ri L efectuate dintr-o camer  într-o camer  vecin ,
pentru a ajunge din 1, 1 în M, N ;
pe a treia linie L caractere din multimea N , E , S , V reprezentând deplas ri spre Nord, Est,
Sud sau Vest.
Restricµii ³i preciz ri
2&N & 50
Exemplu:
muzeu.in muzeu.out
56 12
000002 9
011143 EEEEESSSS
010000
015100
000100
1000 5 7 100 12 1000 1000 1000 1000 1000
Timp maxim de executare: 1 secund /test.

Indicaµii de rezolvare *

Mihai Stroe, GInfo nr. 13/6 - octombrie 2003


Se observ  c  num rul variantelor de cump rare a biletelor (cump r / nu cump r biletul 1,
10
biletul 2, ..., biletul 10) este 2 1024.
Se genereaz  ecare din aceste variante. Pentru o astfel de variant , se calculeaz  costul ³i se
transform  matricea iniµial  într-o matrice cu 0 ³i 1, în care 0 reprezint  o camer  pentru care se
pl te³te biletul (sau nu e nevoie de bilet) ³i 1 reprezint  o camer  pentru care nu se cump ra bilet
(deci în care nu se intr ).
Pentru o astfel de matrice, problema determin rii celui mai scurt drum de la 1, 1 la M, N 
se rezolv  cu algoritmul lui Lee.
Se rezolv  aceast  problem  pentru toate cele 1024 variante. Eventual, dac  la un moment dat
exist  o soluµie cu un cost mai bun decât al variantei curente, aceasta nu mai este abordat .
Evident, problema determin rii celui mai scurt drum se poate rezolva ³i pe matricea iniµial ,
µinând cont la ecare pas de biletele cump rate; cum algoritmul lui Lee este folosit de obicei pentru
o matrice cu 0 ³i 1, am folosit iniµial convenµia respectiv  pentru a u³ura înþelegerea.
Se alege soluµia de cost minim ³i, în caz de egalitate, cea cu num r minim de camere.
Analiza complexit µii
Operaµia de citire a datelor are ordinul de complexitate O M N .
C
Fie C num rul de numere de marcaj diferite din matrice. Pentru ecare dintre cele 2 variante,
g sirea drumului minim cu algoritmul lui Lee are complexitatea O M N .
Operaµia de scriere a soluµiei are ordinul de complexitate O M N  pentru cazul cel mai
defavorabil.
C
Ordinul de complexitate al algoritmului de rezolvare a acestei probleme este O M N 2 .
Algoritmul funcµioneaz  datorit  restricµiei impuse pentru C (cel mult 10 numere de marcaj).
C
Considerând 2 o constant , ordinul de complexitate devine O M N .

Codul surs 

import java.io.*;
class Muzeu
{
static final int qdim=200; // dimensiune coada circulara
static final int sus=1, dreapta=2, jos=3, stanga=4;

static int m,n; // dimensiuni muzeu


static int ncmin; // nr camere minim
static int cc,cmin=50*50*10000/2; // cost curent/minim
static boolean amAjuns;
CAPITOLUL 12. ALGORITMI BFS-LEE 132

static int[][] x=new int[51][51]; // muzeu


static int[][] c=new int[51][51]; // costul (1,1) --> (i,j)
static int[][] d=new int[51][51]; // directii pentru "intoarcere"
static int[][] dsol=new int[51][51];

static int[] cb=new int[11]; // costuri bilete


static boolean[] amBilet=new boolean[11];

static int[] qi=new int[qdim]; // coada pentru i din pozitia (i,j)


static int[] qj=new int[qdim]; // coada pentru j din pozitia (i,j)
static int ic, sc; // ic=inceput coada

public static void main(String[] args) throws IOException


{
int i,j;
StreamTokenizer st=new StreamTokenizer(
new BufferedReader(new FileReader("10-muzeu.in")));

st.nextToken(); m=(int)st.nval;
st.nextToken(); n=(int)st.nval;

for(i=1;i<=m;i++)
for(j=1;j<=n;j++) { st.nextToken(); x[i][j]=(int)st.nval; }

for(i=1;i<=10;i++) {st.nextToken(); cb[i]=(int)st.nval; }

amBilet[0]=true; // 0 ==> gratuit


for(i=0;i<=1023;i++)
{
bilete(i);
cc=0; for(j=1;j<=10;j++) if(amBilet[j]) cc+=cb[j];
if(cc>cmin) continue;

amAjuns=false;
matriceCosturi();

if(!amAjuns) continue;
if(cc>cmin) continue;
if(cc<cmin) { cmin=cc; ncmin=c[m][n]; copieDirectii(); }
else // costuri egale
if(c[m][n]<ncmin) { ncmin=c[m][n]; copieDirectii(); }
}

d=dsol; // schimbare "adresa" ... ("pointer"!) ...


afisSolutia();
}// main()

static void bilete(int i)


{
int j;
for(j=1;j<=10;j++)
{
if(i%2==1) amBilet[j]=true; else amBilet[j]=false;
i/=2;
}
}// bilete(...)

static void matriceCosturi()


{
CAPITOLUL 12. ALGORITMI BFS-LEE 133

int i,j;
for(i=1;i<=m;i++)
for(j=1;j<=n;j++) c[i][j]=0; // curat "numai" traseul !
ic=sc=0; // coada vida
qi[sc]=1; qj[sc]=1; sc=(sc+1)%qdim; // (1,1) --> coada circulara !
c[1][1]=1; // cost 1 pentru pozitia (1,1) (pentru marcaj!)
while(ic!=sc) // coada nevida
{
i=qi[ic]; j=qj[ic]; ic=(ic+1)%qdim;
vecini(i,j);
if(amAjuns) break;
}
}//matriceCosturi()

static void copieDirectii()


{
int i,j;
for(i=1;i<=m;i++) for(j=1;j<=n;j++) dsol[i][j]=d[i][j];
}// copieDirectii()

static void vecini(int i, int j)


{
int t=c[i][j]; // "timp" = nr camere parcurse

if((i-1>=1)&&(c[i-1][j]==0)&&amBilet[x[i-1][j]]) // N
{
c[i-1][j]=t+1; qi[sc]=i-1; qj[sc]=j; sc=(sc+1)%qdim;
d[i-1][j]=jos;
if((i-1==m)&&(j==n)) {amAjuns=true; return;}
}

if((j+1<=n)&&(c[i][j+1]==0)&&amBilet[x[i][j+1]]) // E
{
c[i][j+1]=t+1; qi[sc]=i; qj[sc]=j+1; sc=(sc+1)%qdim;
d[i][j+1]=stanga;
if((i==m)&&(j+1==n)) {amAjuns=true; return;}
}

if((i+1<=m)&&(c[i+1][j]==0)&&amBilet[x[i+1][j]])// S
{
c[i+1][j]=t+1; qi[sc]=i+1; qj[sc]=j; sc=(sc+1)%qdim;
d[i+1][j]=sus;
if((i+1==m)&&(j==n)) {amAjuns=true; return;}
}

if((j-1>=1)&&(c[i][j-1]==0)&&amBilet[x[i][j-1]]) // V
{
c[i][j-1]=t+1; qi[sc]=i; qj[sc]=j-1; sc=(sc+1)%qdim;
d[i][j-1]=dreapta;
if((i==m)&&(j-1==n)) {amAjuns=true; return;}
}
}// vecini(...)

static void afisSolutia() throws IOException


{
int i,j;
PrintWriter out = new PrintWriter(
new BufferedWriter(new FileWriter("muzeu.out")));

out.println(cmin);
CAPITOLUL 12. ALGORITMI BFS-LEE 134

out.println(ncmin-1);

i=m; j=n; // (m,n) --> (1,1)


while((i!=1)||(j!=1)) // folosesc "c" care nu mai e necesara !
if(d[i][j]==sus) { c[i-1][j]=jos; i--; }
else if(d[i][j]==jos) { c[i+1][j]=sus; i++;}
else if(d[i][j]==dreapta) { c[i][j+1]=stanga; j++; }
else if(d[i][j]==stanga) { c[i][j-1]=dreapta; j--; }
else System.out.println("Eroare la traseu ... (m,n)-->(1,1)!");

i=1; j=1; // (1,1) --> (m,n)


while((i!=m)||(j!=n))
{
if(c[i][j]==sus) {out.print("N"); i--;}
else if(c[i][j]==jos) {out.print("S"); i++;}
else if(c[i][j]==dreapta) {out.print("E"); j++;}
else if(c[i][j]==stanga) {out.print("V"); j--;}
else System.out.println("Eroare la traseu ... (1,1)--(m,n)!");
}
out.close();
}//afisSolutia()
}// class

12.2.4 P ianjen ONI2005 clasa a X-a

Un p ianjen a µesut o plas , în care nodurile sunt dispuse sub forma unui caroiaj cu m linii
(numerotate de la 0 la m  1) ³i n coloane (numerotate de la 0 la n  1) ca în gur . Iniµial, oricare
dou  noduri vecine (pe orizontal  sau vertical ) erau unite prin segmente de plas  de lungime 1.
În timp unele porµiuni ale plasei s-au deteriorat, devenind nesigure. Pe plas , la un moment dat,
se g sesc p ianjenul ³i o musc , în noduri de coordonate cunoscute.
0 1 2 3 4 5 6
0

2 pozitie paianzen

7 pozitie musca

Cerinµ 
S  se determine lungimea celui mai scurt traseu pe care trebuie s -l parcurg  p ianjenul,
folosind doar porµiunile sigure ale plasei, pentru a ajunge la musc . De asemenea, se cere un astfel
de traseu.
Datele de intrare
Fi³ierul de intrare paianjen.in conµine:
 pe prima linie dou  numere naturale m n, separate printr-un spaµiu, reprezentând num rul
de linii ³i respectiv num rul de coloane ale plasei;
 pe a doua linie dou  numere naturale lp cp, separate printr-un spaµu, reprezentând linia ³i
respectiv coloana nodului în care se a  iniµial p ianjenul;
 pe linia a treia dou  numere naturale lm cm separate printr-un spaµiu, reprezentând linia
³i respectiv coloana pe care se a  iniµial musca;
 pe linia a patra, un num r natural k , reprezentând num rul de porµiuni de plas  deteriorate;
 pe ecare dintre urm toarele k linii, câte patru valori naturale l1 c1 l2 c2, separate prin câte
un spaµiu, reprezentând coordonatele capetelor celor k porµiuni de plas  deteriorate (linia ³i apoi
coloana pentru ecare cap t).
CAPITOLUL 12. ALGORITMI BFS-LEE 135

Datele de ie³ire
Fi³ierul de ie³ire paianjen.out va conµine pe prima linie un num r natural min reprezentând
lungimea drumului minim parcurs de p ianjen, exprimat în num r de segmente de lungime 1. Pe
urm toarele min  1 linii sunt scrise nodurile prin care trece p ianjenul, câte un nod pe o linie.
Pentru ecare nod sunt scrise linia ³i coloana pe care se a , separate printr-un spaµiu.
Restricµii ³i preciz ri
a 1 & m, n & 140
a 1 & k & 2 ˜ m ˜ n  m  n  1
a Lungimea drumului minim este cel mult 15000
a Pentru datele de test exist  întotdeauna soluµie. Dac  problema are mai multe soluµii, se va
a³a una singur .
a Porµiunile nesigure sunt specicate în ³ierul de intrare într-o ordine oarecare. Oricare dou 
porµiuni nesigure orizontale se pot intersecta cel mult într-un cap t. De asemenea, oricare dou 
porµiuni nesigure verticale se pot intersecta cel mult într-un cap t.
a Se acord  30% din punctaj pentru determinarea lungimii drumului minim ³i 100% pentru
rezolvarea ambelor cerinµe.
Exemplu
paianjen.in paianjen.out Explicaµie
9 7 8 Problema corespunde gurii de mai sus.
2 3 2 3 Traseul optim este desenat cu linie groas ,
7 4 2 2 iar porµiunile nesigure sunt desenate punctat.
8 3 2
2 4 2 5 4 2
2 3 3 3 5 2
3 0 3 1 6 2
3 3 3 5 6 3
4 4 5 4 7 3
6 4 6 5 7 4
6 5 7 5
7 2 7 3
Timp maxim de execuµie/test: 1 secund  pentru Windows ³i 0.1 secunde pentru Linux.

Indicaµii de rezolvare *

prof. Carmen Popescu, C. N. "Gh. Laz r" Sibiu


Plasa de p ianjen se memoreaz  într-o matrice A cu M linii ³i N coloane, ecare element
reprezentând un nod al plasei. Ai, j  va codica pe patru biµi direcµiile în care se poate face
deplasarea din punctul i, j : bitul 0 este 1 dac  p ianjenul se poate deplasa în sus, bitul 1 este 1
dac  se poate deplasa la dreapta, bitul 2 - în jos, bitul 3 - la stânga.
Rezolvarea se bazeaz  pe parcurgerea matriciei ³i reµinerea nodurilor parcurse într-o structur 
de date de tip coad  (parcurgere BF - algoritm Lee).
Drumul minim al p ianjenului se reµine într-o alt  matrice B , unde B i, j  este 0 dac  nodul
i, j  nu este atins, respectiv o valoare pozitiv  reprezentând pasul la care a ajuns paianjenul în
drumul lui spre musc . Deci elementul B lm, cm va conµine lungimea drumului minim.
Reconstituirea drumului minim se face pornind de la poziµia mu³tei, utilizând, de asemenea un
algoritm de tip BF, cu oprirea c ut rii în jurul nodului curent în momentul detect rii unui nod
de pas mai mic cu o unitate decât cel al nodului curent.

TESTE
CAPITOLUL 12. ALGORITMI BFS-LEE 136

# m n k min Obs
0 10 8 11 16 dimensiune mic , re puµine rupte
1 10 8 46 74 soluµie unic , foarte multe re rupte
2 2 2 0 2 caz particular, nici un r rupt
3 140 140 20 278 re puµine rupte, dimensiune mare
4 131 131 2000 103 multe re rupte, dimensiune mare
5 100 130 12771 12999 traseu în spiral  solutie unic 
6 100 7 23 15 traseu scurt, greu de g sit cu backtracking
7 138 138 9381 9050 multe re rupte, drum în "serpentine"
8 140 140 38365 555 rele interioare rupte, traseul pe frontier 
9 138 138 273 274 o "barier " pe mijlocul tablei, cu o "fant "

O soluµie backtracking simplu obµine maxim 20 de puncte, iar îmbun t µit maxim 30 de puncte.

Codul surs 

import java.io.*; // test 3 eroare date lm=144 ???


class Paianjen4 // coada circulara (altfel trebuie dimensiune mare !)
{ // traseu fara recursivitate (altfel depaseste stiva !)
static final int qdim=200; // dimensiune coada circulara
static final int sus=1, dreapta=2, jos=4, stanga=8;
static int[][] p=new int[140][140]; // plasa
static int[][] c=new int[140][140]; // costul ajungerii in (i,j)
static int[][] d=new int[140][140]; // directii pentru intoarcere de la musca!

static int m,n; // dimensiunile plasei


static int lp,cp; // pozitie paianjen(lin,col)
static int lm,cm; // pozitie musca(lin,col)
static int[] qi=new int[qdim]; // coada pentru i din pozitia (i,j)
static int[] qj=new int[qdim]; // coada pentru j din pozitia (i,j)
static int ic, sc; // inceput/sfarsit coada

public static void main(String[] args) throws IOException


{
long t1,t2;
t1=System.currentTimeMillis();
citescDate();
matriceCosturi();
afisSolutia();
t2=System.currentTimeMillis();
System.out.println("TIME = "+(t2-t1)+" millisec ");
}// main()

static void citescDate() throws IOException


{
int nrSegmenteDeteriorate, k,i,j,l1,c1,l2,c2;
int i1,i2,j1,j2;

StreamTokenizer st=new StreamTokenizer(


new BufferedReader(new FileReader("paianjen.in")));
st.nextToken(); m=(int)st.nval;
st.nextToken(); n=(int)st.nval;
st.nextToken(); lp=(int)st.nval;
st.nextToken(); cp=(int)st.nval;
st.nextToken(); lm=(int)st.nval;
st.nextToken(); cm=(int)st.nval;
st.nextToken(); nrSegmenteDeteriorate=(int)st.nval;
CAPITOLUL 12. ALGORITMI BFS-LEE 137

for(i=0;i<m;i++) for(j=0;j<n;j++) p[i][j]=0xF; // 1111=toate firele !


for(k=1;k<=nrSegmenteDeteriorate;k++)
{
st.nextToken();l1=(int)st.nval;
st.nextToken();c1=(int)st.nval;
st.nextToken();l2=(int)st.nval;
st.nextToken();c2=(int)st.nval;

i1=min(l1,l2); i2=max(l1,l2);
j1=min(c1,c2); j2=max(c1,c2);

if(j1==j2) // ruptura verticala


{
p[i1][j1]^=jos; // sau ... p[i1][j1]-=jos; ... !!!
for(i=i1+1;i<=i2-1;i++)
{
p[i][j1]^=jos; // 0 pe directia jos
p[i][j1]^=sus; // 0 pe directia sus
}
p[i2][j1]^=sus;
}
else
if(i1==i2) // ruptura orizontala
{
p[i1][j1]^=dreapta; // 0 pe directia dreapta
for(j=j1+1;j<=j2-1;j++)
{
p[i1][j]^=dreapta;
p[i1][j]^=stanga;
}
p[i1][j2]^=stanga; // 0 pe directia stanga
}
else System.out.println("Date de intrare ... eronate !");
}// for k
}//citescDate()

static void matriceCosturi()


{
int i,j;
ic=sc=0; // coada vida
qi[sc]=lp; qj[sc]=cp; sc=(sc+1)%qdim; // (lp,cp) --> coada !
c[lp][cp]=1; // cost 1 pentru pozitie paianjen (pentru marcaj!)
while(ic!=sc) // coada nevida
{
i=qi[ic]; j=qj[ic]; ic=(ic+1)%qdim;
fill(i,j);
if(c[lm][cm]!=0) break; // a ajuns deja la musca !
}// while
}//matriceCosturi()

static void fill(int i, int j)


{
int t=c[i][j]; // timp !

if((i-1>=0)&&(c[i-1][j]==0)&&ok(i,j,sus)) // N
{
c[i-1][j]=t+1;
qi[sc]=i-1;
qj[sc]=j;
sc=(sc+1)%qdim;
CAPITOLUL 12. ALGORITMI BFS-LEE 138

d[i-1][j]=jos;
}

if((j+1<=n-1)&&(c[i][j+1]==0)&&ok(i,j,dreapta)) // E
{
c[i][j+1]=t+1;
qi[sc]=i;
qj[sc]=j+1;
sc=(sc+1)%qdim;
d[i][j+1]=stanga;
}

if((i+1<=m-1)&&(c[i+1][j]==0)&&ok(i,j,jos)) // S
{
c[i+1][j]=t+1;
qi[sc]=i+1;
qj[sc]=j;
sc=(sc+1)%qdim;
d[i+1][j]=sus;
}

if((j-1>=0)&&(c[i][j-1]==0)&&ok(i,j,stanga)) // V
{
c[i][j-1]=t+1;
qi[sc]=i;
qj[sc]=j-1;
sc=(sc+1)%qdim;
d[i][j-1]=dreapta;
}
}// fill(...)

static boolean ok(int i, int j, int dir)


{
if((p[i][j]&dir)!=0) return true; else return false;
}// ok(...)

static int min(int a, int b)


{
if(a<b) return a; else return b;
}

static int max(int a, int b)


{
if(a>b) return a; else return b;
}

static void afisSolutia() throws IOException


{
int i,j;
PrintWriter out=new PrintWriter(
new BufferedWriter(new FileWriter("paianjen.out")));
out.println(c[lm][cm]-1);

i=lm;
j=cm;
while((i!=lp)||(j!=cp)) // folosesc matricea c care nu mai e necesara !
if(d[i][j]==sus) { c[i-1][j]=jos; i--; }
else if(d[i][j]==jos) { c[i+1][j]=sus; i++;}
else if(d[i][j]==dreapta) { c[i][j+1]=stanga; j++; }
else if(d[i][j]==stanga) { c[i][j-1]=dreapta; j--; }
CAPITOLUL 12. ALGORITMI BFS-LEE 139

else System.out.println("Eroare la traseu ... !");

i=lp;
j=cp;
while((i!=lm)||(j!=cm))
{
out.println(i+" "+j);
if(c[i][j]==sus) i--;
else if(c[i][j]==jos) i++;
else if(c[i][j]==dreapta) j++;
else if(c[i][j]==stanga) j--;
else System.out.println("Eroare la traseu ... !");
}
out.println(i+" "+j); // pozitia pentru musca !
out.close();
}//afisSolutia()
}// class

12.2.5 Algoritmul Edmonds-Karp


import java.io.*;
class FluxMaxim
{
static final int WHITE=0, GRAY=1, BLACK=2;
static final int MAX_NODES=10;
static final int oo=0x7fffffff;
static int n, m; // nr noduri, nr arce
static int[][] c=new int[MAX_NODES+1][MAX_NODES+1]; // capacitati
static int[][] f=new int [MAX_NODES+1][MAX_NODES+1]; // flux
static int[] color=new int[MAX_NODES+1]; // pentru bfs
static int[] p=new int[MAX_NODES+1]; // predecesor (ptr. drum crestere)
static int ic, sc; // inceput coada, sfarsit coada
static int[] q=new int[MAX_NODES+2]; // coada

public static void main(String[] args) throws IOException


{
int s,t,i,j,k,fluxm; // fluxm=flux_maxim
StreamTokenizer st=new StreamTokenizer(
new BufferedReader(new FileReader("fluxMaxim.in")));
PrintWriter out=new PrintWriter(
new BufferedWriter(new FileWriter("fluxMaxim.out")));
st.nextToken(); n=(int)st.nval;
st.nextToken(); m=(int)st.nval;
st.nextToken(); s=(int)st.nval;
st.nextToken(); t=(int)st.nval;

for(k=1;k<=m;k++)
{
st.nextToken(); i=(int)st.nval;
st.nextToken(); j=(int)st.nval;
st.nextToken(); c[i][j]=(int)st.nval;
}

fluxm=fluxMax(s,t);

System.out.println("\nfluxMax("+s+","+t+") = "+fluxm+" :");


for(i=1;i<=n;i++)
{
for(j=1;j<=n;j++) System.out.print(maxim(f[i][j],0)+"\t");
System.out.println();
}
CAPITOLUL 12. ALGORITMI BFS-LEE 140

out.print(fluxm); out.close();
}// main()

static int fluxMax(int s, int t)


{
int i, j, u, min, maxf = 0;
for(i=1; i<=n; i++) for(j=1; j<=n; j++) f[i][j]=0;

// Cat timp exista drum de crestere a fluxului (in graful rezidual),


// mareste fluxul pe drumul gasit
while(bfs(s,t))
{
// Determina cantitatea cu care se mareste fluxul
min=oo;
for(u=t; p[u]!=-1; u=p[u]) min=minim(min,c[p[u]][u]-f[p[u]][u]);

// Mareste fluxul pe drumul gasit


for(u=t; p[u]!=-1; u=p[u])
{
f[p[u]][u]+=min;
f[u][p[u]]-=min; // sau f[u][p[u]]=-f[p[u]][u];
}

maxf += min;
System.out.print("drum : ");
drum(t);
System.out.println(" min="+min+" maxf="+maxf+"\n");
}// while(...)

// Nu mai exista drum de crestere a fluxului ==> Gata !!!


System.out.println("Nu mai exista drum de crestere a fluxului !!!");
return maxf;
}// fluxMax(...)

static boolean bfs(int s, int t) // s=sursa t=destinatie


{
// System.out.println("bfs "+s+" "+t+" flux curent :");
// afism(f);
int u, v;
boolean gasitt=false;

for(u=1; u<=n; u++) { color[u]=WHITE; p[u]=-1; }


ic=sc=0; // coada vida
incoada(s);
p[s]=-1;

while(ic!=sc)
{
u=dincoada();

// Cauta nodurile albe v adiacente cu nodul u si pune v in coada


// cand capacitatea reziduala a arcului (u,v) este pozitiva
for(v=1; v<=n; v++)
if(color[v]==WHITE && ((c[u][v]-f[u][v])>0))
{
incoada(v);
p[v]=u;
if(v==t) { gasitt=true; break;}
}
CAPITOLUL 12. ALGORITMI BFS-LEE 141

if(gasitt) break;
}//while

return gasitt;
}// bfs(...)

static void drum(int u)


{
if(p[u]!=-1) drum(p[u]);
System.out.print(u+" ");
}// drum(...)

static void afism(int[][] a)


{
int i,j;
for(i=1;i<=n;i++)
{
for(j=1;j<=n;j++) System.out.print(a[i][j]+"\t");
System.out.println();
}
// System.out.println();
}// afism(...)

static int minim(int x, int y) { return (x<y) ? x : y; }

static int maxim(int x, int y) { return (x>y) ? x : y; }

static void incoada(int u)


{
q[sc++]=u;
color[u]=GRAY;
}

static int dincoada()


{
int u=q[ic++];
color[u]=BLACK;
return u;
}
}// class

/*
6 10 1 6 drum : 1 2 4 6 min=12 maxf=12
1 2 16 drum : 1 3 5 6 min= 4 maxf=16
1 3 13 drum : 1 3 5 4 6 min= 7 maxf=23
2 3 4 Nu mai exista drum de crestere a fluxului !!!
2 4 12 fluxMax(1,6) = 23 :
3 2 10 0 12 11 0 0 0
3 5 14 0 0 0 12 0 0
4 3 9 0 0 0 0 11 0
4 6 20 0 0 0 0 0 19
5 4 7 0 0 0 7 0 4
5 6 4 0 0 0 0 0 0
*/

12.2.6 Cuplaj maxim


/* Culori - Descrierea problemei:
Doi elfi au pus pe o masa n patratele si m cerculete. Unul a ales patratelele
si celalalt cerculetele si au desenat pe ele mai multe benzi colorate. Apoi au
inceput sa se joace cu patratelele si cerculetele. Au decis ca un cerculet
CAPITOLUL 12. ALGORITMI BFS-LEE 142

poate fi amplasat pe un patratel daca exista cel putin o culoare care apare
pe ambele. Ei doresc sa formeze perechi din care fac parte un cerculet si un
patratel astfel incat sa se obtina cat mai multe perechi.

Date de intrare: Fisierul de intrare de intrare contine pe prima linie


numarul n al patratelelor. Pe fiecare dintre urmatoarele n linii sunt
descrise benzile corespunzatoare unui patratel. Primul numar de pe o astfel
de linie este numarul b al benzilor, iar urmatoarele b numere reprezinta
codurile celor b culori. Urmatoarea linie contine numarul m al cerculetelor.
Pe fiecare dintre urmatoarele m linii sunt descrise benzile corespunzatoare
unui cerculet. Primul numar de pe o astfel de linie este numarul b al
benzilor, iar urmatoarele b numere reprezinta codurile celor b culori.
Numerele de pe o linie vor fi separate prin câte un spatiu. Patratelele si
cerculetele vor fi descrise in ordinea data de numarul lor de ordine.

Date de iesire: Fisierul de iesire trebuie sa contina pe prima linie numarul


k al perechilor formate. Fiecare dintre urmatoarele k va contine cate doua
numere, separate printr-un spatiu, reprezentand numerele de ordine ale unui
patratel, respectiv cerc, care formeaza o pereche.

Restrictii si precizari: numarul patratelelor este cuprins intre 1 si 100;


numarul cerculetelor este cuprins intre 1 si 100; patratelele sunt identificate
prin numere cuprinse intre 1 si n; cerculetele sunt identificate prin numere
cuprinse intre 1 si m; numarul benzilor colorate de pe cerculete si patratele
este cuprins intre 1 si 10; un patratel sau un cerc nu poate face parte din
mai mult decat o pereche; daca exista mai multe solutii trebuie determinata
doar una dintre acestea.
Exemplu
INPUT.TXT OUTPUT.TXT
3 2
1 1 1 1 1 . 1 \
1 2 3 2 / . \
1 3 s=0 - 2 . 2 --- n+m+1=t
4 \ . / /
2 1 2 3 . 3 / /
1 3 . /
2 3 4 . 4 /
1 4 Timp de executie: 0,5 secunde/test */

import java.io.*; // u=0 ==> v=1,2,...,n


class CuplajMaximCulori // u=1,..,n ==> v=n+1,..,n+m
{ // u=n+1,..,n+m ==> v=1,2,.,n sau n+m+1(=t)
static final int WHITE=0, GRAY=1, BLACK=2;
static final int oo=0x7fffffff;
static int n, m, ic, sc;
static int[][] c, f; // capacitati, flux
static boolean[][] cp, cc; // cp = culoare patratel, cc = culoare cerc
static int[] color, p, q; // predecesor, coada

public static void main(String[] args) throws IOException


{
citire();
capacitati();
scrie(fluxMax(0,n+m+1));
}// main()

static void citire() throws IOException


{
int i,j,k,nc;
StreamTokenizer st=new StreamTokenizer(
CAPITOLUL 12. ALGORITMI BFS-LEE 143

new BufferedReader(new FileReader("CuplajMaximCulori.in")));


st.nextToken(); n=(int)st.nval; cp=new boolean[n+1][11];
for(i=1;i<=n;i++)
{
st.nextToken(); nc=(int)st.nval;
for(k=1;k<=nc;k++)
{
st.nextToken(); j=(int)st.nval;
cp[i][j]=true;
}
}
st.nextToken(); m=(int)st.nval; cc=new boolean[m+1][11];
for(i=1;i<=m;i++)
{
st.nextToken(); nc=(int)st.nval;
for(k=1;k<=nc;k++)
{
st.nextToken(); j=(int)st.nval;
cc[i][j]=true;
}
}
}// citire()

static void capacitati()


{
int i,ic,j,jc;
c=new int[n+m+2][n+m+2];
for(i=1;i<=n;i++)
{
c[0][i]=1;
for(ic=1;ic<=10;ic++)
if(cp[i][ic])
for(j=1;j<=m;j++)
if(cc[j][ic]) c[i][j+n]=1;
}
for(j=1;j<=m;j++) c[j+n][n+m+1]=1;
}// capacitati()

static int fluxMax(int s, int t)


{
int i,j,u,min,maxf=0;

f=new int[n+m+2][n+m+2];
p=new int[n+m+2];
q=new int[n+m+2];
color=new int[n+m+2];

for(i=0;i<=n+m+1;i++)
for(j=0;j<=n+m+1;j++) f[i][j]=0;

while(bfs(s,t))
{
min=oo;
for(u=t;p[u]!=-1;u=p[u]) min=minim(min,c[p[u]][u]-f[p[u]][u]);
for(u=t;p[u]!=-1;u=p[u])
{
f[p[u]][u]+=min;
f[u][p[u]]-=min; // sau f[u][p[u]]=-f[p[u]][u];
}
CAPITOLUL 12. ALGORITMI BFS-LEE 144

maxf+=min;
}// while(...)

return maxf;
}// fluxMax(...)

static boolean bfs(int s, int t)


{
int u, v;
boolean gasitt=false;
for(u=0;u<=n+m+1;u++) {color[u]=WHITE; p[u]=-1;}
ic=sc=0;
q[sc++]=s; color[s]=GRAY; // s --> coada
p[s]=-1;
while(ic!=sc)
{
u=q[ic++]; color[u]=BLACK;
if(u==0)
{
for(v=1;v<=n;v++)
if((color[v]==WHITE)&&((c[u][v]-f[u][v])>0))
{
q[sc++]=v; color[v]=GRAY; // incoada(v);
p[v]=u;
}
}
else if(u<=n)
{
for(v=n+1;v<=n+m;v++)
if((color[v]==WHITE)&&((c[u][v]-f[u][v])>0))
{
q[sc++]=v; color[v]=GRAY; // incoada(v);
p[v]=u;
}
}
else
{
for(v=n+m+1;v>=1;v--)
if((color[v]==WHITE)&&((c[u][v]-f[u][v])>0))
{
q[sc++]=v; color[v]=GRAY; // incoada(v);
p[v]=u;
if(v==t) {gasitt=true; break;}
}
if(gasitt) break; // din while !
}
}// while()

return gasitt;
}// bfs()

static int minim(int x, int y) { return (x<y) ? x : y; }

static int maxim(int x, int y) { return (x>y) ? x : y; }

static void scrie(int fluxm) throws IOException


{
int i,j;
PrintWriter out=new PrintWriter(
CAPITOLUL 12. ALGORITMI BFS-LEE 145

new BufferedWriter(new FileWriter("CuplajMaximCulori.out")));


out.println(fluxm);
for (i=1;i<=n;i++)
if(f[0][i]>0)
for(j=1;j<=m;j++)
if(f[i][j+n]>0)
{
out.println(i+" "+j);
break;
}
out.close();
}// scrie(...)
}// class
Capitolul 13

Metoda optimului local - greedy

13.1 Metoda greedy


Metoda Greedy are în vedere rezolvarea unor probleme de optim în care optimul global se
determin  din estim ri succesive ale optimului local.
Metoda Greedy se aplic  urm torului tip de problem : dintr-o mulµime de elemente C (candi-
daµi la construirea soluµiei problemei), se cere s  se determine o submulµime S , (soluµia problemei)
care îndepline³te anumite condiµii. Deoarece este posibil s  existe mai multe soluµii se va alege
soluµia care maximizeaz  sau minimizeaz  o anumit  funcµie obiectiv.
O problem  poate  rezolvat  prin tehnica (metoda) Greedy dac  îndepline³te proprietatea:
¬ ¬
dac  S este o soluµie, iar S este inclus  în S , atunci ³i S este o soluµie.
Pornind de la aceast  condiµie, iniµial se presupune c  S este mulµimea vid  ³i se adaug 
succesiv elemente din C în S , ajungând la un optim local. Succesiunea de optimuri locale nu
asigur , în general, optimul global. Dac  se demonstreaz  c  succesiunea de optimuri locale conduce
la optimul global, atunci metoda Greedy este aplicabil  cu succes.
Exist  urm toarele variante ale metodei Greedy:

1. Se pleac  de la soluµia vid  pentru multimea S ³i se ia pe rând câte un element din mulµimea
C . Dac  elementul ales îndepline³te condiµia de optim local, el este introdus în mulµimea S .
2. Se ordoneaz  elementele mulµimii C ³i se veric  dac  un element îndepline³te condiµia de
apartenenµ  la mulµimea S .

13.2 Algoritmi greedy


Algoritmii greedy sunt în general simpli ³i sunt folosiµi la rezolvarea unor probleme de optimi-
zare. În cele mai multe situaµii de acest fel avem:

ˆ o mulµime de candidaµi (lucr ri de executat, vârfuri ale grafului, etc.)

ˆ o funcµie care veric  dac  o anumit  mulµime de candidaµi constituie o soluµie posibil , nu
neap rat optim , a problemei

ˆ o funcµie care veric  dac  o mulµime de candidaµi este fezabil , adic  dac  este posibil s 
complet m aceast  mulµime astfel încât s  obµinem o soluµie posibil , nu neap rat optim ,
a problemei

ˆ o funcµie de selecµie care indic  la orice moment care este cel mai promiµ tor dintre candidaµii
înc  nefolosiµi

ˆ o funcµie obiectiv care d  valoarea unei soluµii (timpul necesar execut rii tuturor lucr rilor
într-o anumit  ordine, lungimea drumului pe care l-am g sit, etc) ³i pe care urm rim s  o
optimiz m (minimiz m/maximiz m)

Pentru a rezolva problema de optimizare, c ut m o soluµie posibil  care s  optimizeze valoarea
funcµiei obiectiv.

146
CAPITOLUL 13. METODA OPTIMULUI LOCAL - GREEDY 147

Un algoritm greedy construie³te soluµia pas cu pas.


Iniµial, mulµimea candidaµilor selectaµi este vid .
La ecare pas, încerc m s  ad ug m la aceast  mulµime pe cel mai promiµ tor candidat,
conform funcµiei de selecµie. Dac , dup  o astfel de ad ugare, mulµimea de candidaµi selectaµi nu
mai este fezabil , elimin m ultimul candidat ad ugat; acesta nu va mai  niciodat  considerat.
Dac , dup  ad ugare, mulµimea de candidaµi selectaµi este fezabil , ultimul candidat ad ugat
va r mâne de acum încolo în ea. De ecare dat  când l rgim mulµimea candidaµilor selectaµi,
veric m dac  aceast  mulµime constituie o soluµie posibil  a problemei. Dac  algoritmul greedy
funcµioneaz  corect, prima soluµie g sit  va  considerat  soluµie optim  a problemei.
Soluµia optim  nu este în mod necesar unic : se poate ca funcµia obiectiv s  aib  aceea³i valoare
optim  pentru mai multe soluµii posibile.
Descrierea formal  a unui algoritm greedy general este:
function greedy C  // C este mulµimea candidaµilor
S  o // S este mulµimea în care construim soluµia
while not solutie S  and C j o do
x 
un element din C care maximizeaz /minimizeaz  select x
C 
C  rxx
if f ezabil S < rxx then S S < rxx 
if solutie S  then return S
else return "nu exist  soluµie"

13.3 Exemple
Dintre problemele clasice care se pot rezolva prin metoda greedy menµion m: plata restului
cu num r minim de monezi, problema rucsacului, sortare prin selecµie, determinarea celor mai
scurte drumuri care pleac  din acela³i punct (algoritmul lui Dijkstra), determinarea arborelui de
cost minim (algoritmii lui Prim ³i Kruskal), determinarea mulµimii dominante, problema color rii
intervalelor, codicarea Human, etc.

13.3.1 Problema continu  a rucsacului

Se consider  n obiecte. Obiectul i are greutatea gi ³i valoarea vi (1 & i & n). O persoan  are
un rucsac. Fie G greutatea maxim  suportat  de rucsac. Persoana în cauz  dore³te s  pun  în
rucsac obiecte astfel încât valoarea celor din rucsac s  e cât mai mare. Se permite fracµionarea
obiectelor (valoarea p rµii din obiectul fracµionat ind direct proporµional  cu greutatea ei!).
Not m cu xi " 0, 1 partea din obiectul i care a fost pus  în rucsac. Practic, trebuie s 
maximiz m funcµia
=x c .
n
f x i i
i 1

Pentru rezolvare vom folosi metoda greedy. O modalitate de a ajunge la soluµia optim  este
de a considera obiectele în ordinea descresc toare a valorilor utilit µilor lor date de raportul vgi
i
(i 1, ..., n) ³i de a le înc rca întregi în rucsac pân  când acesta se umple. Din aceast  cauz 
presupunem în continuare c 
v1 v2 vn
g ' g ' ... ' g .
1 2 n

Vectorul x x1 , x2 , ..., xn  se nume³te soluµie posibil  dac 

" 0, 1, ¾i


<
xi 1, 2, ..., n
w
xi gi & G
n
i 1

iar o soluµie posibil  este soluµie optim  dac  maximizeaz  funcµia f .


Vom nota Gp greutatea permis  de a se înc rca în rucsac la un moment dat. Conform strategiei
greedy, procedura de rezolvare a problemei este urm toarea:
procedure rucsac 
Gp  G
for i 1, n
if Gp % gi
CAPITOLUL 13. METODA OPTIMULUI LOCAL - GREEDY 148

then Gp G p  gi
else
xi  Gp
gi
for ji  1, n
xj 0 
Vectorul x are forma x 1, ..., 1, xi , 0, ..., 0 cu xi " 0, 1 ³i 1 & i & n.
Propoziµia 1. Procedura rucsac  furnizeaz  o soluµie optim .

Demonstraµia se g se³te, de exemplu, în [24] la pagina 226.

13.3.2 Problema plas rii textelor pe o band 

S  presupunem c  trebuie s  plas m n texte T1 , T2 , ..., Tn , de lungimi date L1 , L2 , ..., Ln , pe


o singur  band  sucient de lung . Atunci când este necesar  citirea unui text sunt citite toate
textele situate înaintea lui pe band .
Modalitatea de plasare a celor n texte pe band  corespunde unei permut ri p a mulµimii
r1, 2, ..., nx, textele ind a³ezate pe band  în ordinea Tp 1 Tp 2 ...Tp n .
Într-o astfel de aranjare a textelor pe band , timpul mediu de citire a unui text este:

= =L
n k
1
f p n p i
k 1i 1

Se dore³te determinarea unei permut ri care s  asigure o valoare minim  a timpului mediu de
citire. Rezolvarea problemei este foarte simpl :

a se sorteaz  cresc tor textele în funcµie de lungimea lor ³i


a se plaseaz  pe band  în ordinea dat  de sortare.
Urm toarea propoziµie ([24] pagina 99) ne permite s  m siguri c  în acest mod ajungem la o
plasare optim .
Propoziµia 2. Dac  L1 & L2 & ... & Ln atunci plasarea textelor corespunz toare permutarii
identice este optim .

Demonstraµie: Fie p p1 , p2 , ..., pn  o plasare optim . Dac  exist  i $ j cu Lp i ' Lp j 


¬
atunci consider m permutarea p obµinut  din p prin permutarea elementelor de pe poziµiile i ³i
j . Atunci
¬
f p   f p n  j  1 Lp i  Lp j    n  i  1 Lp j   Lp i 
deci
Lp i  j  i & 0.
¬
f p   f p Lp j 

Cum p este o plasare optim  ³i f p  & f p rezult  c  f p  f p deci ³i p este o plasare


¬ ¬ ¬

optim . Aplicând de un num r nit de ori acest raµionament, trecem de la o permutare optim 
la alt  permutare optim  pân  ajungem la permutarea identic . Rezult  c  permutarea identic 
corespunde unei plas ri optime.

13.3.3 Problema plas rii textelor pe m benzi

S  presupunem c  trebuie s  plas m n texte T1 , T2 , ..., Tn , de lungimi date L1 , L2 , ..., Ln , pe


m benzi sucient de lungi. Atunci când este necesar  citirea unui text sunt citite toate textele
situate înaintea lui pe banda respectiv .
Se dore³te determinarea unei plas ri a celor n texte pe cele m benzi care s  asigure o valoare
minim  a timpului mediu de citire. Rezolvarea problemei este foarte simpl :

a se sorteaz  cresc tor textele în funcµie de lungimea lor ³i


a plasarea textelor pe benzi se face în ordinea dat  de sortare, începând cu
primul text (cu cea mai mic  lungime) care se plaseaz  pe prima band ,
iar mai departe
a ecare text se plaseaz  pe banda urm toare celei pe care a fost plasat
textul anterior.
CAPITOLUL 13. METODA OPTIMULUI LOCAL - GREEDY 149

Presupunând c  L1 & L2 & ... & Ln , se poate observa c  textul Ti va  plasat pe banda
i  im & m în continuarea celor aate deja pe aceast  band  iar dup  textul Ti , pe aceea³i band 
1

cu el, vor  plasate textele i  m, i  2m, ..., deci înc  ni


m
& texte (demonstraµiile se pot consulta,
de exemplu, în [24]).

13.3.4 Maximizarea unei sume de produse

Se dau mulµimile de numere întregi A ra1 , a2 , ..., an x ³i B rb1 , b2 , ..., bm x, unde n & m.
¬
S  se determine o submulµime B rx1 , x2 , ..., xn x a lui B astfel încât valoarea expresiei E
a1 x1  a2 x2  an xn s  e maxim .
Vom sorta cresc tor elementele celor dou  mulµimi. Putem presupune acum c  a1 & a2 & ... &
an ³i b1 & b2 & ... & bm .
Dac  toate elementele din A sunt pozitive vom lua xn bm , xn1 bm1 , ³i a³a mai departe.
Dac  toate elementele din A sunt negative vom lua x1 b1 , x2 b2 , ³i a³a mai departe.
Dac  primele n1 elemente din A sunt strict negative ³i ultimele n2 elemente din A sunt pozitive
sau nule (n1  n2 n) atunci vom lua x1 b1 , x2 b2 , ..., xn1 bn1 ³i xn bm , xn1 bm1 , ...,
xnn2 1 bmn2 1 .

13.3.5 Problema staµiilor

Se consider  o mulµime de numere naturale A ra1 , a2 , ..., an x care reprezint  coordonatele


a n staµii pe axa real . Vom presupune a1 $ a2 $ ... $ an . S  se determine o submulµime cu
num r maxim de staµii cu proprietatea c  distanµa dintre dou  staµii al turate este cel putin d (o
distanµ  dat ).
Rezolvarea problemei este urm toarea:

a se alege staµia 1,
a se parcurge ³irul staµiilor ³i se alege prima staµie întâlnit  care este la distanµ  cel
puµin d faµ  de staµia aleas  anterior; se repet  acest pas pân  când s-au vericat toate
staµiile.

Propoziµia 3. Exist  o soluµie optim  care conµine staµia 1.

Demonstraµie: Fie B rai1 , ai2 , ..., aim x o soluµie a problemei care nu conµine staµia 1 (deci
ai1 % a1 ). Evident ¶ai2  ai1 ¶ ' d. Staµia i1 se poate înlocui cu staµia 1 pentru c  ¶ai2  a1 ¶
¶ai2  ai1  ai1  a1 ¶ ¶ai2  ai1 ¶  ¶ai1  a1 ¶ % d.
Dup  selectarea staµie 1 se pot selecta (pentru obµinerea unei soluµii optime) numai staµii
situate la distanµe cel puµin d fat  de staµia 1. Pentru aceste staµii repet m strategia sugerat  de
propoziµia anterioar .

13.3.6 Problema cutiilor

Se dore³te obµinerea unei conguraµii de n numere plasate pe n  1 poziµii (o poziµie ind


liber ) dintr-o conguraµie iniµial  dat  în care exist  o poziµie liber  ³i cele n numere plasate în
alt  ordine. O mutare se poate face dintr-o anumit  poziµie numai în poziµia liber .
Prezent m algoritmul de rezolvare pe baza urm torului exemplu:
1 2 3 4 5 6 7
iniµial 3 1 2 6 5 4
nal 1 2 3 4 5 6
rezolvat 

Vom proceda astfel: cutia goal  din conguraµia iniµial  se a  pe poziµia 3 dar pe aceast 
poziµie, în conguraµia nal , se a  num rul 3; c ut m num rul 3 din conguraµia iniµial  (îl
g sim pe poziµia 1) ³i îl mut m pe poziµia cutiei goale; acum, cutia goal  se a  pe poziµia 1; vom
repeta acest raµionament pân  când poziµiile cutiilor goale, în cele dou  conguraµii, coincid.
1 2 3 4 5 6 7
modicat 1 3 2 6 5 4
nal 1 2 3 4 5 6
rezolvat  
CAPITOLUL 13. METODA OPTIMULUI LOCAL - GREEDY 150

1 2 3 4 5 6 7
modicat 1 3 2 6 5 4
nal 1 2 3 4 5 6
rezolvat  
1 2 3 4 5 6 7
modicat 1 2 3 6 5 4
nal 1 2 3 4 5 6
rezolvat    
Acum vom c uta o cutie nerezolvat  ³i vom muta num rul din acea cutie în cutia goal .
1 2 3 4 5 6 7
modicat 1 2 3 6 5 4
nal 1 2 3 4 5 6
rezolvat    

Repet m raµionamentul prezentat la început ³i vom continua pân  când toate numerele ajung
pe poziµiile din conguraµia nal .
1 2 3 4 5 6 7
modicat 1 2 3 6 4 5
nal 1 2 3 4 5 6
rezolvat     
1 2 3 4 5 6 7
modicat 1 2 3 4 5 6
nal 1 2 3 4 5 6
rezolvat      

13.3.7 Problema sub³irurilor

S  se descompun  un ³ir de n numere întregi în sub³iruri strict cresc toare astfel încât numerele
lor de ordine din ³irul iniµial s  e ordonate cresc tor în sub³irurile formate ³i num rul sub³irurilor
s  e minim.
Metota de rezolvare este urm toarea: se parcurg elementele ³irului iniµial unul dup  altul ³i
pentru ecare element xi se caut  un sub³ir existent la care xi se poate ad uga la sfâr³it; dac 
nu exist  un astfel de sub³ir se creeaz  un sub³ir nou care va conµine ca prim element pe xi ; dac 
exist  mai multe sub³iruri la care se poate ad uga xi , se va alege acela care are cel mai mare ultim
element.
Observaµie: Ultimele elemente din ecare sub³ir (care sunt ³i elemente maxime din aceste
sub³iruri) formeaz  un ³ir descresc tor. Acest fapt permite utilizarea c ut rii binare pentru
determinarea sub³irului potrivit pentru elementul xi atunci când prelucr m acest element.

13.3.8 Problema intervalelor disjuncte

Se consider  n intervale închise pe axa real  a1 , b1 , a2 , b2 , ..., an , bn . Se cere selectarea
unor intervale disjuncte astfel încât num rul acestora s  e maxim.
Metoda de rezolvare este urm toarea: se sorteaz  intervalele cresc tor dup  cap tul din
dreapta; se selecteaz  primul interval; se parcurg intervalele, unul dup  altul, pân  se g se³te
un interval ai , bi  disjunct cu ultimul interval ales ³i se adaug  acest interval la soluµie, el deve-
nind astfel "ultimul interval ales"; acest procedeu continu  pân  când nu mai r mân intervale de
analizat.

Propoziµia 4. Exist  o soluµie optim  care conµine primul interval dup  sortare.

13.3.9 Problema alegerii taxelor

Se dau dou  ³iruri cu câte 2n numere întregi ecare, x x1 , x2 , ..., x2n  ³i y y1 , y2 , ..., y2n 
reprezentând taxe. S  se determine ³irul z z1 , z2 , ..., z2n , unde zi " rxi , yi x 1 & i & 2n, astfel
încât suma tuturor elementelor din ³irul z s  e minim  ³i acest ³ir s  conµin  exact n elemente
din ³irul x ³i n elemente din ³irul y .
CAPITOLUL 13. METODA OPTIMULUI LOCAL - GREEDY 151

Metoda de rezolvare este urm toarea: se construie³te ³irul t x  y (în care ti xi  yi ) ³i se


sorteaz  cresc tor; se aleg din ³irul x elementele corespunz toare primelor n elemente din ³irul t
sortat iar celelalte n elemente se iau din ³irul y .

13.3.10 Problema acoperirii intervalelor

Se consider  n intervale închise a1 , b1 , a2 , b2 , ..., an , bn . S  se determine o mulµime cu


num r minim de alemente C rc1 , c2 , ..., cm x care s  "acopere" toate cele n intervale date (spunem
c  ci "acoper " intervalul ak , bk  dac  ci " ak , bk ).
Metoda de rezolvare este urm toarea: se sorteaz  intervalele cresc tor dup  cap tul din
dreapta. Presupunem c  b1 & b2 & ... & bn . Primul punct ales este c1 b1 . Parcurgem in-
tervalele pân  cand g sim un interval ai , bi  cu ai % c1 ³i alegem c2 bi . Parcurgem mai departe
intervalele pân  cand g sim un interval aj , bj  cu aj % c2 ³i alegem c3 bj . Repet m acest
procedeu pân  când termin m de parcurs toate intervalele.

13.3.11 Algoritmul lui Prim

Determinarea arborelui minim de acoperire pentru un graf neorientat.

Alg prim de ordinul O n3 


import java.io.*; // arbore minim de acoperire: algoritmul lui Prim
class Prim // O(n^3)
{
static final int oo=0x7fffffff;
static int n,m;
static int[][] cost;
static boolean[] esteInArbore;
static int[] p; // predecesor in arbore

public static void main(String[]args) throws IOException


{
int nods=3; // nod start
int i, j, k, costArbore=0,min,imin=0,jmin=0;

StreamTokenizer st=new StreamTokenizer(


new BufferedReader(new FileReader("prim.in")));
PrintWriter out=new PrintWriter(
new BufferedWriter(new FileWriter("prim.out")));

st.nextToken(); n=(int)st.nval;
st.nextToken(); m=(int)st.nval;

cost=new int[n+1][n+1];
esteInArbore=new boolean [n+1];
p=new int[n+1];

for(i=1;i<=n;i++) for(j=1;j<=n;j++) cost[i][j]=oo;


for(k=1;k<=m;k++)
{
st.nextToken(); i=(int)st.nval;
st.nextToken(); j=(int)st.nval;
st.nextToken(); cost[i][j]=cost[j][i]=(int)st.nval;
}

esteInArbore[nods]=true;
for(k=1;k<=n-1;k++) // sunt exact n-1 muchii in arbore !!!
O(n)
{
CAPITOLUL 13. METODA OPTIMULUI LOCAL - GREEDY 152

min=oo;
for(i=1;i<=n;i++) // O(n)
{
if(!esteInArbore[i]) continue;
for(j=1;j<=n;j++) // O(n)
{
if(esteInArbore[j]) continue;
if(min>cost[i][j]) { min=cost[i][j]; imin=i; jmin=j; }
}//for j
}//for i

esteInArbore[jmin]=true;
p[jmin]=imin;
costArbore+=min;
}//for k

for(k=1;k<=n;k++) if(p[k]!=0) out.println(k+" "+p[k]);


out.println("cost="+costArbore);
out.close();
}//main
}//class

/*
6 7 1 3
1 2 3 2 3
1 3 1 4 3
2 3 2 5 4
3 4 1 6 4
4 5 1 cost=7
5 6 3
4 6 2
*/

Alg Prim cu distanµe


import java.io.*; // arbore minim de acoperire: algoritmul lui Prim
class PrimDist // O(n^2)
{
static final int oo=0x7fffffff;
static int n,m;
static int[][] cost;
static boolean[] esteInArbore;
static int[] p; // predecesor in arbore
static int[] d; // distante de la nod catre arbore

public static void main(String[]args) throws IOException


{
int nodStart=3; // nod start
int i, j, k, costArbore=0,min,jmin=0;

StreamTokenizer st=new StreamTokenizer(


new BufferedReader(new FileReader("prim.in")));
PrintWriter out=new PrintWriter(
new BufferedWriter(new FileWriter("prim.out")));

st.nextToken(); n=(int)st.nval;
st.nextToken(); m=(int)st.nval;

cost=new int[n+1][n+1];
esteInArbore=new boolean [n+1];
p=new int[n+1];
CAPITOLUL 13. METODA OPTIMULUI LOCAL - GREEDY 153

d=new int[n+1];

for(i=1;i<=n;i++)
for(j=1;j<=n;j++)
cost[i][j]=oo;
for(i=1;i<=n;i++) d[i]=oo;

for(k=1;k<=m;k++)
{
st.nextToken(); i=(int)st.nval;
st.nextToken(); j=(int)st.nval;
st.nextToken(); cost[i][j]=cost[j][i]=(int)st.nval;
}

d[nodStart]=0;

for(k=1;k<=n;k++) // O(n)
{
min=oo;
for(j=1;j<=n;j++) // O(n)
{
if(esteInArbore[j]) continue;
if(min>d[j]) { min=d[j]; jmin=j; }
}//for j

esteInArbore[jmin]=true;
d[jmin]=0;
costArbore+=min;

for(j=1;j<=n;j++) // actualizez distantele nodurilor O(n)


{
if(esteInArbore[j]) continue; // j este deja in arbore
if(cost[jmin][j]<oo) // am muchia (jmin,j)
if(d[jmin]+cost[jmin][j]<d[j])
{
d[j]=d[jmin]+cost[jmin][j];
p[j]=jmin;
}
}
}//for k

for(k=1;k<=n;k++) if(p[k]!=0) out.println(k+" "+p[k]);


out.println("cost="+costArbore);
out.close();
}//main
}//class

/*
6 7 1 3
1 2 3 2 3
1 3 1 4 3
2 3 2 5 4
3 4 1 6 4
4 5 1 cost=7
5 6 3
4 6 2
*/

Alg Prim cu heap


import java.io.*; // arbore minim de acoperire: algoritmul lui Prim
CAPITOLUL 13. METODA OPTIMULUI LOCAL - GREEDY 154

class PrimHeap // folosesc distantele catre arbore


{ // pastrate in MinHeap ==> O(n log n) ==> OK !!!
static final int oo=0x7fffffff;
static int n,m;
static int[][] w; // matricea costurilor
static int[] d; // distante de la nod catre arbore
static int[] p; // predecesorul nodului in arbore
static int[] hd; // hd[i]=distanta din pozitia i din heap
static int[] hnod; // hnod[i]= nodul din pozitia i din heap
static int[] pozh; // pozh[i]=pozitia din heap a nodului i

public static void main(String[]args) throws IOException


{
StreamTokenizer st=new StreamTokenizer(
new BufferedReader(new FileReader("prim.in")));
PrintWriter out=new PrintWriter(
new BufferedWriter(new FileWriter("prim.out")));
int i,j,k,cost,costa=0,nods;

st.nextToken(); n=(int)st.nval;
st.nextToken(); m=(int)st.nval;
st.nextToken(); nods=(int)st.nval;

w=new int[n+1][n+1];
for(i=1;i<=n;i++) for(j=1;j<=n;j++) w[i][j]=oo;

for(k=1;k<=m;k++)
{
st.nextToken(); i=(int)st.nval;
st.nextToken(); j=(int)st.nval;
st.nextToken(); cost=(int)st.nval;
w[j][i]=w[i][j]=cost;
}

prim(nods);

for(i=1;i<=n;i++) // afisez muchiile din arbore


if(i!=nods) {out.println(p[i]+" "+i);costa+=w[p[i]][i];}
out.println("costa="+costa);
out.close();
}//main

static void prim(int nods)


{
int u,v,q,aux;

d=new int [n+1];


p=new int [n+1];
hd=new int[n+1];
hnod=new int[n+1];
pozh=new int[n+1];

for(u=1;u<=n;u++)
{
hnod[u]=pozh[u]=u;
hd[u]=d[u]=oo;
p[u]=0;
}

q=n; // q = noduri nefinalizate = dimensiune heap


CAPITOLUL 13. METODA OPTIMULUI LOCAL - GREEDY 155

d[nods]=0;
hd[pozh[nods]]=0;

urcInHeap(pozh[nods]);
while(q>0) // la fiecare pas adaug un varf in arbore
{
u=extragMin(q);
if(u==-1) { System.out.println("graf neconex !!!"); break; }
q--;
for(v=1;v<=q;v++) // noduri nefinalizate = in heap 1..q
if(w[u][hnod[v]]<oo) // cost finit ==> exista arc (u,v)
relax(u,hnod[v]); // relax si refac heap
}
}//prim(...)

static void relax(int u,int v)


{
if(w[u][v]<d[v])
{
d[v]=w[u][v];
p[v]=u;
hd[pozh[v]]=d[v];
urcInHeap(pozh[v]);
}
}//relax(...)

static int extragMin(int q) // in heap 1..q


{
// returnez valoarea minima (din varf!) si refac heap in jos
// aducand ultimul in varf si coborand !

int aux,fiu1,fiu2,fiu,tata,nod;

aux=hd[1]; hd[1]=hd[q]; hd[q]=aux; // 1 <---> q

aux=hnod[1]; hnod[1]=hnod[q]; hnod[q]=aux;

pozh[hnod[q]]=q;
pozh[hnod[1]]=1;

tata=1;
fiu1=2*tata;
fiu2=2*tata+1;

while(fiu1<=q-1) //refac heap de sus in jos pana la q-1


{
fiu=fiu1;
if(fiu2<=q-1)
if(hd[fiu2]<hd[fiu1])
fiu=fiu2;

if(hd[tata]<=hd[fiu])
break;

pozh[hnod[fiu]]=tata;
pozh[hnod[tata]]=fiu;

aux=hd[fiu]; hd[fiu]=hd[tata]; hd[tata]=aux;

aux=hnod[fiu]; hnod[fiu]=hnod[tata]; hnod[tata]=aux;


CAPITOLUL 13. METODA OPTIMULUI LOCAL - GREEDY 156

tata=fiu;
fiu1=2*tata;
fiu2=2*tata+1;
}

return hnod[q]; // hnod[1] a ajuns deja (!) in hnod[q]


}// extragMin(...)

static void urcInHeap(int nodh)


{
int aux,fiu,tata,nod;

nod=hnod[nodh];
fiu=nodh;
tata=fiu/2;
while((tata>0)&&(hd[fiu]<hd[tata]))
{
pozh[hnod[tata]]=fiu;
hnod[fiu]=hnod[tata];

aux=hd[fiu]; hd[fiu]=hd[tata]; hd[tata]=aux;

fiu=tata;
tata=fiu/2;
}
pozh[nod]=fiu;
hnod[fiu]=nod;
}
}//class

/*
6 7 3
1 2 3 3 1
1 3 1 3 2
2 3 2 3 4
3 4 1 4 5
4 5 1 4 6
5 6 3 costa=7
4 6 2
*/

13.3.12 Algoritmul lui Kruskal


import java.io.*; // Arbore minim de acoperire : Kruskal
class Kruskal
{
static int n,m,cost=0;
static int[] x,y,z,et;

public static void main(String[] args) throws IOException


{
int k;
StreamTokenizer st=new StreamTokenizer(
new BufferedReader(new FileReader("kruskal.in")));
PrintWriter out=new PrintWriter(
new BufferedWriter(new FileWriter("kruskal.out")));

st.nextToken(); n=(int)st.nval;
st.nextToken(); m=(int)st.nval;
CAPITOLUL 13. METODA OPTIMULUI LOCAL - GREEDY 157

x=new int[m+1];
y=new int[m+1];
z=new int[m+1];
et=new int[n+1];

for(k=1;k<=m;k++)
{
st.nextToken(); x[k]=(int)st.nval;
st.nextToken(); y[k]=(int)st.nval;
st.nextToken(); z[k]=(int)st.nval;
}

kruskal();

System.out.println("cost="+cost);
out.println(cost);
out.close();
}//main

static void kruskal()


{
int nm=0,k,etg1,etg2;

for(k=1;k<=n;k++) et[k]=k;

qsort(1,m,z);

for(k=1;k<=m;k++)
{
if(et[x[k]]!=et[y[k]])
{
nm++;
cost+=z[k];
System.out.println(x[k]+" "+y[k]);
etg1=et[x[k]];
etg2=et[y[k]];
for(int i=1;i<=n;i++)
if(et[i]==etg2) et[i]=etg1;
}
if(nm==n-1)break;
}
}//kruskal

static void qsort(int p, int u, int []x)


{
int k=poz(p,u,x);
if(p<k-1) qsort(p,k-1,x);
if(k+1<u) qsort(k+1,u,x);
}

static void invers(int i, int j, int x[])


{
int aux;
aux=x[i]; x[i]=x[j]; x[j]=aux;
}

static int poz(int p, int u, int z[])


{
int k,i,j;
CAPITOLUL 13. METODA OPTIMULUI LOCAL - GREEDY 158

i=p; j=u;
while(i<j)
{
while((z[i]<=z[j])&&(i<j)) i++;
while((z[i]<=z[j])&&(i<j)) j--;
if(i<j) { invers(i,j,z); invers(i,j,x); invers(i,j,y); }
}
return i; //i==j
}//poz
}//class

13.3.13 Algoritmul lui Dijkstra


Alg Dijkstra cu distanµe, în graf neorientat
import java.io.*; // drumuri minime de la nodSursa
class Dijkstra // O(n^2)
{
static final int oo=0x7fffffff;
static int n,m;
static int[][] cost;
static boolean[] esteFinalizat;
static int[] p; // predecesor in drum
static int[] d; // distante de la nod catre nodSursa

public static void main(String[]args) throws IOException


{
int nodSursa=1; // nod start
int i, j, k, min,jmin=0;

StreamTokenizer st=new StreamTokenizer(


new BufferedReader(new FileReader("dijkstraNeorientat.in")))
PrintWriter out=new PrintWriter(
new BufferedWriter(new FileWriter("dijkstraNeorientat.out")));

st.nextToken(); n=(int)st.nval;
st.nextToken(); m=(int)st.nval;

cost=new int[n+1][n+1];
esteFinalizat=new boolean [n+1];
p=new int[n+1];
d=new int[n+1];

for(i=1;i<=n;i++) for(j=1;j<=n;j++) cost[i][j]=oo;


for(i=1;i<=n;i++) d[i]=oo;

for(k=1;k<=m;k++)
{
st.nextToken(); i=(int)st.nval;
st.nextToken(); j=(int)st.nval;
st.nextToken(); cost[i][j]=cost[j][i]=(int)st.nval;
}

d[nodSursa]=0;

for(k=1;k<=n;k++) // O(n)
{
min=oo;
for(j=1;j<=n;j++) // O(n)
{
if(esteFinalizat[j]) continue;
CAPITOLUL 13. METODA OPTIMULUI LOCAL - GREEDY 159

if(min>d[j]) { min=d[j]; jmin=j; }


}//for j
esteFinalizat[jmin]=true;
for(j=1;j<=n;j++) // actualizez distantele nodurilor // O(n)
{
if(esteFinalizat[j]) continue; // j este deja in arbore
if(cost[jmin][j]<oo) // am muchia (jmin,j)
if(d[jmin]+cost[jmin][j]<d[j])
{
d[j]=d[jmin]+cost[jmin][j];
p[j]=jmin;
}
}
}//for k
for(k=1;k<=n;k++)
{
System.out.print(nodSursa+"-->"+k+" dist="+d[k]+" drum: ");
drum(k);
System.out.println();
}
out.close();
}//main
static void drum(int k) // s --> ... --> k
{
if(p[k]!=0) drum(p[k]);
System.out.print(k+" ");
}
}//class
/*
6 7 1-->1 dist=0 drum: 1
1 2 4 1-->2 dist=3 drum: 1 3 2
1 3 1 1-->3 dist=1 drum: 1 3
2 3 2 1-->4 dist=2 drum: 1 3 4
3 4 1 1-->5 dist=3 drum: 1 3 4 5
5 4 1 1-->6 dist=4 drum: 1 3 4 6
5 6 3
4 6 2
*/

Alg Dijkstra cu heap, în graf neorientat


import java.io.*; // distante minime de la nodSursa
class DijkstraNeorientatHeap // pastrate in MinHeap ==> O(n log n) ==> OK !!!
{
static final int oo=0x7fffffff;
static int n,m;
static int[][]w; // matricea costurilor
static int[]d; // distante de la nod catre arbore
static int[]p; // predecesorul nodului in arbore
static int[] hd; // hd[i]=distanta din pozitia i din heap
static int[] hnod; // hnod[i]= nodul din pozitia i din heap
static int[] pozh; // pozh[i]=pozitia din heap a nodului i

public static void main(String[]args) throws IOException


{
int i,j,k,cost,costa=0,nodSursa;
StreamTokenizer st=new StreamTokenizer(
new BufferedReader(new FileReader("dijkstraNeorientat.in")))
PrintWriter out=new PrintWriter(
new BufferedWriter(new FileWriter("dijkstraNeorientat.out")));
st.nextToken(); n=(int)st.nval;
CAPITOLUL 13. METODA OPTIMULUI LOCAL - GREEDY 160

st.nextToken(); m=(int)st.nval;
w=new int[n+1][n+1];
for(i=1;i<=n;i++) for(j=1;j<=n;j++) w[i][j]=oo;
for(k=1;k<=m;k++)
{
st.nextToken(); i=(int)st.nval;
st.nextToken(); j=(int)st.nval;
st.nextToken(); cost=(int)st.nval;
w[j][i]=w[i][j]=cost;
}

nodSursa=1;
dijkstra(nodSursa);

for(k=1;k<=n;k++)
{
System.out.print(nodSursa+"-->"+k+" dist="+d[k]+" drum: ");
drum(k);
System.out.println();
}
out.close();
}//main

static void dijkstra(int nods)


{
int u,v,q,aux;

d=new int [n+1];


p=new int [n+1];
hd=new int[n+1];
hnod=new int[n+1];
pozh=new int[n+1];

for(u=1;u<=n;u++)
{
hnod[u]=pozh[u]=u;
hd[u]=d[u]=oo;
p[u]=0;
}
q=n; // q = noduri nefinalizate = dimensiune heap
d[nods]=0;
hd[pozh[nods]]=0;

urcInHeap(pozh[nods]);

while(q>0) // la fiecare pas adaug un varf in arbore


{
u=extragMin(q);
if(u==-1) { System.out.println("graf neconex !!!"); break; }
q--;
for(v=1;v<=q;v++) // noduri nefinalizate = in heap 1..q
if(w[u][hnod[v]]<oo) // cost finit ==> exista arc (u,v)
relax(u,hnod[v]); // relax si refac heap
}
}// dijkstra()

static void relax(int u,int v)


{
if(d[u]+w[u][v]<d[v])
{
CAPITOLUL 13. METODA OPTIMULUI LOCAL - GREEDY 161

d[v]=d[u]+w[u][v];
p[v]=u;
hd[pozh[v]]=d[v];
urcInHeap(pozh[v]);
}
}// relax(...)

static int extragMin(int q) // in heap 1..q


{
// returnez valoarea minima (din varf!) si refac heap in jos
int aux,fiu1,fiu2,fiu,tata,nod;

aux=hd[1]; hd[1]=hd[q]; hd[q]=aux; // 1 <---> q


aux=hnod[1]; hnod[1]=hnod[q]; hnod[q]=aux;
pozh[hnod[q]]=q;
pozh[hnod[1]]=1;

tata=1;
fiu1=2*tata;
fiu2=2*tata+1;

while(fiu1<=q-1) //refac heap de sus in jos pana la q-1


{
fiu=fiu1;
if(fiu2<=q-1)
if(hd[fiu2]<hd[fiu1]) fiu=fiu2;
if(hd[tata]<=hd[fiu]) break;

pozh[hnod[fiu]]=tata;
pozh[hnod[tata]]=fiu;

aux=hd[fiu]; hd[fiu]=hd[tata]; hd[tata]=aux;


aux=hnod[fiu]; hnod[fiu]=hnod[tata]; hnod[tata]=aux;

tata=fiu;
fiu1=2*tata;
fiu2=2*tata+1;
}

return hnod[q]; // hnod[1] a ajuns deja (!) in hnod[q]


} // extragMin(...)

static void urcInHeap(int nodh)


{
int aux,fiu,tata,nod;

nod=hnod[nodh];
fiu=nodh;
tata=fiu/2;

while((tata>0)&&(hd[fiu]<hd[tata]))
{
pozh[hnod[tata]]=fiu;
hnod[fiu]=hnod[tata];
aux=hd[fiu]; hd[fiu]=hd[tata]; hd[tata]=aux;
fiu=tata;
tata=fiu/2;
}
pozh[nod]=fiu;
hnod[fiu]=nod;
CAPITOLUL 13. METODA OPTIMULUI LOCAL - GREEDY 162

} // urcInHeap(...)

static void drum(int k) // s --> ... --> k


{
if(p[k]!=0) drum(p[k]);
System.out.print(k+" ");
}
}//class

/*
6 7 1-->1 dist=0 drum: 1
1 2 4 1-->2 dist=3 drum: 1 3 2
1 3 1 1-->3 dist=1 drum: 1 3
2 3 2 1-->4 dist=2 drum: 1 3 4
3 4 1 1-->5 dist=3 drum: 1 3 4 5
5 4 1 1-->6 dist=4 drum: 1 3 4 6
5 6 3
4 6 2
*/

Alg Dijkstra cu distanµe, în graf orientat


import java.io.*; // drumuri minime de la nodSursa
class Dijkstra // O(n^2)
{
static final int oo=0x7fffffff;
static int n,m;
static int[][] cost;
static boolean[] esteFinalizat;
static int[] p; // predecesor in drum
static int[] d; // distante de la nod catre nodSursa

public static void main(String[]args) throws IOException


{
int nodSursa=1; // nod start
int i, j, k, min,jmin=0;

StreamTokenizer st=new StreamTokenizer(


new BufferedReader(new FileReader("dijkstraOrientat.in")));
PrintWriter out=new PrintWriter(
new BufferedWriter(new FileWriter("dijkstraOrientat.out")));

st.nextToken(); n=(int)st.nval;
st.nextToken(); m=(int)st.nval;

cost=new int[n+1][n+1];
esteFinalizat=new boolean [n+1];
p=new int[n+1];
d=new int[n+1];

for(i=1;i<=n;i++)
for(j=1;j<=n;j++)
cost[i][j]=oo;
for(i=1;i<=n;i++) d[i]=oo;

for(k=1;k<=m;k++)
{
st.nextToken(); i=(int)st.nval;
st.nextToken(); j=(int)st.nval;
st.nextToken(); cost[i][j]=(int)st.nval;
}
CAPITOLUL 13. METODA OPTIMULUI LOCAL - GREEDY 163

d[nodSursa]=0;

for(k=1;k<=n;k++) // O(n)
{
min=oo;
for(j=1;j<=n;j++) // O(n)
{
if(esteFinalizat[j]) continue;
if(min>d[j]) { min=d[j]; jmin=j; }
}//for j
esteFinalizat[jmin]=true;
for(j=1;j<=n;j++) // actualizez distantele nodurilor // O(n)
{
if(esteFinalizat[j]) continue; // j este deja in arbore
if(cost[jmin][j]<oo) // am muchia (jmin,j)
if(d[jmin]+cost[jmin][j]<d[j])
{
d[j]=d[jmin]+cost[jmin][j];
p[j]=jmin;
}
}
}//for k
for(k=1;k<=n;k++)
{
System.out.print(nodSursa+"-->"+k+" dist="+d[k]+" drum: ");
if(d[k]<oo) drum(k); else System.out.print("Nu exista drum!");
System.out.println();
}
out.close();
}//main

static void drum(int k) // s --> ... --> k


{
if(p[k]!=0) drum(p[k]);
System.out.print(k+" ");
}
}//class
/*
6 7 1-->1 dist=0 drum: 1
1 2 4 1-->2 dist=4 drum: 1 2
1 3 1 1-->3 dist=1 drum: 1 3
2 3 2 1-->4 dist=2 drum: 1 3 4
3 4 1 1-->5 dist=2147483647 drum: Nu exista drum!
5 4 1 1-->6 dist=4 drum: 1 3 4 6
5 6 3
4 6 2 */

Alg Dijkstra cu heap, în graf orientat


import java.io.*; // distante minime de la nodSursa
class DijkstraOrientatHeap // pastrate in MinHeap ==> O(n log n) ==> OK !!!
{
static final int oo=0x7fffffff;
static int n,m;

static int[][]w; // matricea costurilor


static int[]d; // distante de la nod catre arbore
static int[]p; // predecesorul nodului in arbore

static int[] hd; // hd[i]=distanta din pozitia i din heap


CAPITOLUL 13. METODA OPTIMULUI LOCAL - GREEDY 164

static int[] hnod; // hnod[i]= nodul din pozitia i din heap


static int[] pozh; // pozh[i]=pozitia din heap a nodului i

public static void main(String[]args) throws IOException


{
int i,j,k,cost,costa=0,nodSursa;
StreamTokenizer st=new StreamTokenizer(
new BufferedReader(new FileReader("dijkstraOrientat.in")));
PrintWriter out=new PrintWriter(
new BufferedWriter(new FileWriter("dijkstraOrientat.out")));

st.nextToken(); n=(int)st.nval;
st.nextToken(); m=(int)st.nval;

w=new int[n+1][n+1];

for(i=1;i<=n;i++)
for(j=1;j<=n;j++)
w[i][j]=oo;

for(k=1;k<=m;k++)
{
st.nextToken(); i=(int)st.nval;
st.nextToken(); j=(int)st.nval;
st.nextToken(); cost=(int)st.nval;
w[i][j]=cost;
}

nodSursa=1;
dijkstra(nodSursa);

for(k=1;k<=n;k++)
{
if(d[k]<oo)
{
System.out.print(nodSursa+"-->"+k+" dist="+d[k]+" drum: ");
drum(k);
}
else System.out.print(nodSursa+"-->"+k+" Nu exista drum! ");
System.out.println();
}

out.close();
}//main

static void dijkstra(int nods)


{
int u,v,q,aux;

d=new int [n+1];


p=new int [n+1];
hd=new int[n+1];
hnod=new int[n+1];
pozh=new int[n+1];

for(u=1;u<=n;u++) { hnod[u]=pozh[u]=u; hd[u]=d[u]=oo; p[u]=0; }


q=n; // q = noduri nefinalizate = dimensiune heap
d[nods]=0;
hd[pozh[nods]]=0;
CAPITOLUL 13. METODA OPTIMULUI LOCAL - GREEDY 165

urcInHeap(pozh[nods]);

while(q>0) // la fiecare pas adaug un varf in arbore


{
u=extragMin(q);

if(u==-1) { System.out.println("graf neconex !!!"); break; }

q--;
for(v=1;v<=q;v++) // noduri nefinalizate = in heap 1..q
if(w[u][hnod[v]]<oo) // cost finit ==> exista arc (u,v)
relax(u,hnod[v]); // relax si refac heap
}
}//dijkstra(...)

static void relax(int u,int v)


{
if(d[u]+w[u][v]<d[v])
{
d[v]=d[u]+w[u][v];
p[v]=u;
hd[pozh[v]]=d[v];
urcInHeap(pozh[v]);
}
}// relax(...)

static int extragMin(int q) // in heap 1..q


{
// returnez valoarea minima (din varf!) si refac heap in jos
int aux,fiu1,fiu2,fiu,tata,nod;

aux=hd[1]; hd[1]=hd[q]; hd[q]=aux; // 1 <---> q


aux=hnod[1]; hnod[1]=hnod[q]; hnod[q]=aux;
pozh[hnod[q]]=q;
pozh[hnod[1]]=1;

tata=1;
fiu1=2*tata;
fiu2=2*tata+1;
while(fiu1<=q-1) //refac heap de sus in jos pana la q-1
{
fiu=fiu1;
if(fiu2<=q-1)
if(hd[fiu2]<hd[fiu1]) fiu=fiu2;

if(hd[tata]<=hd[fiu]) break;

pozh[hnod[fiu]]=tata;
pozh[hnod[tata]]=fiu;
aux=hd[fiu]; hd[fiu]=hd[tata]; hd[tata]=aux;
aux=hnod[fiu]; hnod[fiu]=hnod[tata]; hnod[tata]=aux;

tata=fiu;
fiu1=2*tata;
fiu2=2*tata+1;
}

return hnod[q]; // hnod[1] a ajuns deja (!) in hnod[q]


} // extragMin(...)
CAPITOLUL 13. METODA OPTIMULUI LOCAL - GREEDY 166

static void urcInHeap(int nodh)


{
int aux,fiu,tata,nod;

nod=hnod[nodh];
fiu=nodh;
tata=fiu/2;
while((tata>0)&&(hd[fiu]<hd[tata]))
{
pozh[hnod[tata]]=fiu;
hnod[fiu]=hnod[tata];

aux=hd[fiu]; hd[fiu]=hd[tata]; hd[tata]=aux;

fiu=tata;
tata=fiu/2;
}

pozh[nod]=fiu;
hnod[fiu]=nod;
} // urcInHeap(...)

static void drum(int k) // s --> ... --> k


{
if(p[k]!=0) drum(p[k]);
System.out.print(k+" ");
}
}//class

/*
6 7 1-->1 dist=0 drum: 1
1 2 4 1-->2 dist=4 drum: 1 2
1 3 1 1-->3 dist=1 drum: 1 3
2 3 2 1-->4 dist=2 drum: 1 3 4
3 4 1 1-->5 Nu exista drum!
5 4 1 1-->6 dist=4 drum: 1 3 4 6
5 6 3
4 6 2
*/

13.3.14 Urgenµa - OJI2002 cls 11

Autorit µile dintr-o zon  de munte intenµioneaz  s  stabileasc  un plan de urgenµ  pentru a


reacµiona mai ecient la frecventele calamit µi naturale din zon . În acest scop au identicat N
puncte de interes strategic ³i le-au numerotat distinct de la 1 la N . Punctele de interes strategic
sunt conectate prin M c i de acces având priorit µi în funcµie de importanµ . Între oricare dou 
puncte de interes strategic exist  cel mult o cale de acces ce poate  parcurs  în ambele sensuri ³i
cel puµin un drum (format din una sau mai multe c i de acces) ce le conecteaz .
În cazul unei calamit µi unele c i de acces pot  temporar întrerupte ³i astfel între anumite
puncte de interes nu mai exist  leg tur . Ca urmare pot rezulta mai multe grupuri de puncte în
a³a fel încât între oricare dou  puncte din acela³i grup s  existe m car un drum ³i între oricare
dou  puncte din grupuri diferite s  nu existe drum.
Autorit µile estimeaz  gravitatea unei calamit µi ca ind suma priorit µilor c ilor de acces
distruse de aceasta ³i doresc s  determine un scenariu de gravitate maxim , în care punctele de
interes strategic s  e împ rµite într-un num r de K grupuri.
Date de intrare
Fi³ierul de intrare URGENTA.IN are urm torul format:
N M K
i1 j1 p1 - între punctele i1 ³i j1 exist  o cale de acces de prioritate p1
CAPITOLUL 13. METODA OPTIMULUI LOCAL - GREEDY 167

i2 j2 p2 - între punctele i2 ³i j2 exist  o cale de acces de prioritate p2


...
iM jM pM - între punctele iM ³i jM exist  o cale de acces de prioritate pM

Date de ie³ire
Fi³ierul de ie³ire URGENTA.OUT va avea urm torul format:
gravmax - gravitatea maxim 
C - num rul de c i de acces întrerupte de calamitate
k1 h1 - între punctele k1 ³i h1 a fost întrerupt  calea de acces
k2 h2 - între punctele k2 ³i h2 a fost întrerupt  calea de acces
...
kC hC - între punctele kC ³i hC a fost întrerupt  calea de acces
Restricµii ³i preciz ri
0 $ N $ 256
N  2 $ M $ 32385
0$K $N 1
Priorit µile c ilor de acces sunt întregi strict pozitivi mai mici decât 256.
Un grup de puncte poate conµine între 1 ³i N puncte inclusiv.
Dac  exist  mai multe soluµii, programul va determina una singur .
Exemplu
URGENTA.IN URGENTA.OUT
7 11 4 27
121 8
132 13
173 17
243 24
342 34
351 37
361 45
375 56
455 67
564
673
Timp maxim de executare: 1 secund  / test

Codul surs 
import java.io.*; // arbore minim de acoperire: algoritmul lui Prim O(n^2)
class Urgenta // sortare O(n^2) ... si una slaba merge!
{
static final int oo=0x7fffffff;
static int n,m,ncc,gravmax,costmax,nrm; // ncc = nr componente conexe
static int[][] cost;
static boolean[] esteInArbore;
static int[] p; // predecesor in arbore
static int[] d; // distante de la nod catre arbore
static int[] a1; // a1[k]=varful 1 al muchiei k din arbore
static int[] a2; // a2[k]=varful 2 al muchiei k din arbore
static int[] ac; // a1[k]=costul muchiei k din arbore

public static void main(String[]args) throws IOException


{
int nodStart=3; // nod start
int i, j, k, costArbore=0,min,jmin=0,aux;

StreamTokenizer st= new StreamTokenizer(


new BufferedReader(new FileReader("urgenta.in")));
PrintWriter out= new PrintWriter(
CAPITOLUL 13. METODA OPTIMULUI LOCAL - GREEDY 168

new BufferedWriter(new FileWriter("urgenta.out")));

st.nextToken(); n=(int)st.nval;
st.nextToken(); m=(int)st.nval;
st.nextToken(); ncc=(int)st.nval;

cost=new int[n+1][n+1];
esteInArbore=new boolean [n+1];
p=new int[n+1];
d=new int[n+1];
a1=new int[n];
a2=new int[n];
ac=new int[n];

for(i=1;i<=n;i++) for(j=1;j<=n;j++) cost[i][j]=oo;


for(i=1;i<=n;i++) d[i]=oo;

costmax=0;
for(k=1;k<=m;k++)
{
st.nextToken(); i=(int)st.nval;
st.nextToken(); j=(int)st.nval;
st.nextToken(); cost[i][j]=cost[j][i]=(int)st.nval;
costmax+=cost[i][j];
}

// alg Prim
d[nodStart]=0;

for(k=1;k<=n;k++) // O(n)
{
min=oo;
for(j=1;j<=n;j++) // O(n)
{
if(esteInArbore[j]) continue;
if(min>d[j]) { min=d[j]; jmin=j; }
}//for j

esteInArbore[jmin]=true;
d[jmin]=0;
costArbore+=min;

for(j=1;j<=n;j++) // actualizez distantele nodurilor // O(n)


{
if(esteInArbore[j]) continue; // j este deja in arbore
if(cost[jmin][j]<oo) // am muchia (jmin,j)
if(d[jmin]+cost[jmin][j]<d[j])
{
d[j]=d[jmin]+cost[jmin][j];
p[j]=jmin;
}
}
}//for k

k=0;
for(i=1;i<=n;i++)
if(p[i]!=0)
{
//System.out.println(i+" "+p[i]+" --> "+cost[i][p[i]]);
k++;
CAPITOLUL 13. METODA OPTIMULUI LOCAL - GREEDY 169

a1[k]=i;
a2[k]=p[i];
ac[k]=cost[i][p[i]];
}
//System.out.println("cost="+costArbore);

gravmax=costmax-costArbore; // deocamdata, ...

//System.out.println("gravmax ="+gravmax);

// trebuie sa adaug la gravmax primele ncc-1 costuri mari (sort!)


// din arborele minim de acoperire

// sortez descrescator ac (pastrand corect a1 si a2)


// care are n-1 elemente
for(k=1;k<=n-1;k++) // de n-1 ori (bule)
{
for(i=1;i<=n-2;i++)
if(ac[i]<ac[i+1])
{
aux=ac[i]; ac[i]=ac[i+1]; ac[i+1]=aux;
aux=a1[i]; a1[i]=a1[i+1]; a1[i+1]=aux;
aux=a2[i]; a2[i]=a2[i+1]; a2[i+1]=aux;
}
}

// primele ncc-1 ...


for(i=1;i<=ncc-1;i++) gravmax+=ac[i];

// sterg muchiile ramase in arbore ...


for(i=ncc;i<=n-1;i++)
{
cost[a1[i]][a2[i]]=cost[a2[i]][a1[i]]=oo; //sterg
}

out.println(gravmax);

// determin numarul muchiilor ...


nrm=0;
for(i=1;i<=n-1;i++)
for(j=i+1;j<=n;j++)
if(cost[i][j] < oo)
nrm++;
out.println(nrm);

// afisez muchiile ...


for(i=1;i<=n-1;i++)
for(j=i+1;j<=n;j++)
if(cost[i][j] < oo)
out.println(i+" "+j);

out.close();
}//main
}//class

13.3.15 Reactivi - OJI2004 cls 9

Într-un laborator de analize chimice se utilizeaz  N reactivi.


Se ³tie c , pentru a evita accidentele sau deprecierea reactivilor, ace³tia trebuie s  e stocaµi
în condiµii de mediu speciale. Mai exact, pentru ecare reactiv x, se precizeaz  intervalul de
CAPITOLUL 13. METODA OPTIMULUI LOCAL - GREEDY 170

temperatur  minx , maxx  în care trebuie s  se încadreze temperatura de stocare a acestuia.


Reactivii vor  plasaµi în frigidere.
Orice frigider are un dispozitiv cu ajutorul c ruia putem stabili temperatura (constant ) care
va  în interiorul acelui frigider (exprimat  într-un num r întreg de grade Celsius).
Cerinµ 
Scrieµi un program care s  determine num rul minim de frigidere necesare pentru stocarea
reactivilor chimici.
Date de intrare
Fi³ierul de intrare react.in conµine:
 pe prima linie num rul natural N , care reprezint  num rul de reactivi;
 pe ecare dintre urm toarele N linii se a  min max (dou  numere întregi separate printr-un
spaµiu); numerele de pe linia x  1 reprezint  temperatura minim , respectiv temperatura maxim 
de stocare a reactivului x.
Date de ie³ire
Fi³ierul de ie³ire react.out va conµine o singur  linie pe care este scris num rul minim de
frigidere necesar.

Restricµii ³i preciz ri
a 1&N & 8000
a 100 & minx & maxx & 100 (numere întregi, reprezentând grade Celsius), pentru orice x de
la 1 la N
a un frigider poate conµine un num r nelimitat de reactivi

Exemple
react.in react.out react.in react.out react.in react.out
3 2 4 3 5 2
-10 10 25 -10 10
-25 57 10 12
20 50 10 20 -20 10
30 40 7 10
78
Timp maxim de execuµie: 1 secund /test

Indicaµii de rezolvare - descriere soluµie *

Soluµie prezentat  de Mihai Stroe, GInfo nr. 14/4


Problema se poate rezolva prin metoda greedy.
O variant  mai explicit  a enunµului este urm toarea:
"Se consider  N intervale pe o ax . S  se aleag  un num r minim de puncte
astfel încât ecare interval s  conµin  cel puµin unul dintre punctele alese."

Facem o prim  observaµie: pentru orice soluµie optim  exist  o soluµie cu acela³i num r de
puncte (frigidere), în care ecare punct s  e sfâr³itul unui interval. Aceasta se poate obµine
mutând ecare punct spre dreapta, pân  când ar ajunge la sfâr³itul intervalului care se termin 
cel mai repede, dintre intervalele care îl conµin. Se observ  c  noua soluµie respect  restricµiile din
enunµ.
În continuare ne vom concentra pe g sirea unei soluµii de acest tip.
Sort m reactivii dup  sfâr³itul intervalului. Pentru intervalul care se termin  cel mai repede,
alegem ultimul punct al s u ca temperatur  a unui frigider. Se observ  c  aceast  alegere este
cea mai bun , dintre toate alegerile unui punct în intervalul respectiv, în sensul c  mulµimea
intervalelor care conµin punctul este mai mare (conform relaµiei de incluziune), decât mulµimea
corespunz toare oric rei alte alegeri. Acest fapt este adev rat, deoarece mutarea punctului mai la
stânga nu duce la selectarea unor noi intervale.
Intervalele care conµin punctul respectiv sunt eliminate (reactivii corespunz tori pot  plasaµi
într-un frigider), iar procesul continu  cu intervalele r mase, în acela³i mod.
Analiza complexit µii
Not m cu D num rul de temperaturi întregi din intervalul care conµine temperaturile din
enunµ. Se observ  c  D este cel mult 201.
CAPITOLUL 13. METODA OPTIMULUI LOCAL - GREEDY 171

Citirea datelor de intrare are ordinul de complexitate O N .


Sortarea intervalelor dup  cap tul din dreapta are ordinul de complexitate O N logN .
Urmeaz  F pa³i, unde F este num rul de frigidere selectate. Deoarece ecare frigider este
setat la o temperatur  întreag , F & D.
În cadrul unui pas, determinarea intervalului care se termin  cel mai repede, pe baza vectorului
sortat, are ordinul de complexitate O 1. Eliminarea intervalelor care conµin un anumit punct
(sfâr³itul intervalului care se termin  cel mai repede) are ordinul de complexitate O N .
A³area rezultatului are ordinul de complexitate O 1.
În concluzie, ordinul de complexitate al algoritmului de rezolvare a acestei probleme este O N
D  N logN ; deoarece în general D % logN , consider m ordinul de complexitate ca ind O N D.

Codul surs 

import java.io.*;
class Reactivi
{
static int n; // n=nr reactivi
static int ni=0; // ni=nr interschimbari in quickSort
static int nf=0; // n=nr frigidere
static int[] ngf; // ng=nr grade in frigider
static int[] x1,x2; // limite inferioara/superioara

public static void main(String[] args) throws IOException


{
int i,j;
StreamTokenizer st=new StreamTokenizer(
new BufferedReader(new FileReader("Reactivi.in")));
PrintWriter out=new PrintWriter(
new BufferedWriter(new FileWriter("Reactivi.out")));
st.nextToken(); n=(int)st.nval;
x1=new int[n+1];
x2=new int[n+1];
ngf=new int[n+1];
for(i=1;i<=n;i++)
{
st.nextToken(); x1[i]=(int)st.nval;
st.nextToken(); x2[i]=(int)st.nval;
}
sol();
out.println(nf);
out.close();
}// main

static void sol()


{
int i;
quickSort(1,n);
i=1; nf=1; ngf[nf]=x2[i];
i++;
while(i<n)
{
while((i<=n)&&(x1[i]<=ngf[nf])) i++;
if(i<n) ngf[++nf]=x2[i++];
}
}

static void quickSort(int p, int u)


{
int i,j,aux;
CAPITOLUL 13. METODA OPTIMULUI LOCAL - GREEDY 172

i=p; j=u;
while(i<j)
{
while((i<j)&&((x2[i]<x2[j])||
((x2[i]==x2[j])&&(x1[i]>=x1[j])))) i++;
if(i!=j)
{
aux=x1[i]; x1[i]=x1[j]; x1[j]=aux;
aux=x2[i]; x2[i]=x2[j]; x2[j]=aux;
}
while((i<j)&&((x2[i]<x2[j])||
((x2[i]==x2[j])&&(x1[i]>=x1[j])))) j--;
if(i!=j)
{
aux=x1[i]; x1[i]=x1[j]; x1[j]=aux;
aux=x2[i]; x2[i]=x2[j]; x2[j]=aux;
}
}
if(p<i-1) quickSort(p,i-1);
if(i+1<u) quickSort(i+1,u);
}
}

13.3.16 Pal - ONI2005 cls 9


Autor: Silviu G nceanu
Prinµul Algorel este în încurc tur  din nou: a fost prins de Spânul cel Negru în încercarea
sa de a o salva pe prinµes  ³i acum este închis în Turnul cel Mare.
Algorel poate evada dac  g se³te combinaµia magic  cu care poate deschide poarta turnului.
Prinµul ³tie cum se formeaz  aceast  combinaµie magic : trebuie s  utilizeze toate cifrele scrise
pe u³a turnului pentru a obµine dou  numere palindroame, astfel încât suma lor s  e minim , iar
aceast  sum  este combinaµia magic  ce va deschide u³a.
Primul num r palindrom trebuie s  aib  cel puµin L cifre, iar cel de-al doilea poate avea orice
lungime diferit  de 0. Numerele palindroame formate nu pot începe cu cifra 0. Acum interveniµi
dumneavoastr  în poveste, ind prietenul s u cel mai priceput în algoritmi.
Prin noul super-telefon al s u, prinµul transmite num rul de apariµii a ec rei cifre de pe u³a
turnului precum ³i lungimea minim  L a primului num r, iar dumneavoastr  trebuie s -i trimiteµi
cât mai repede numerele cu care poate obµine combinaµia magic .
Cerinµ 
Având datele necesare, aaµi dou  numere palindroame cu care se poate obµine combinaµia
magic .
Date de intrare
Prima linie a ³ierului pal.in conµine un num r întreg L reprezentând lungimea minim  a
primului num r. Urmeaz  10 linii: pe linia i  2 se va aa un num r întreg reprezentând num rul
de apariµii ale cifrei i, pentru i cu valori de la 0 la 9.
Date de ie³ire
Prima linie a ³ierului de ie³ire pal.out conµine primul num r palidrom, iar cea de-a doua
linie conµine cel de-al doilea num r palindrom. Dac  exist  mai multe soluµii se va scrie doar una
dintre ele.
Restricµii ³i preciz ri
a În total vor  cel mult 100 de cifre
a 1 & L $ 100 ³i L va  mai mic decât num rul total de cifre
a Pentru datele de test va exista întotdeauna soluµie: se vor putea forma din cifrele scrise pe
u³a turnului dou  numere care încep cu o cifr  diferit  de 0, iar primul num r s  aib  cel puµin L
cifre
a Un num r este palindrom dac  el coincide cu r sturnatul s u. De exemplu 12321 ³i 7007
sunt numere palindroame, în timp ce 109 ³i 35672 nu sunt.
CAPITOLUL 13. METODA OPTIMULUI LOCAL - GREEDY 173

a Pentru 30% dintre teste, num rul total de cifre va  cel mult 7; pentru alte 40% din teste
num rul total de cifre va  cel mult 18, iar pentru restul de 30% din teste num rul total de cifre
va  mai mare sau egal cu 30.
a Fiecare linie din ³ierul de intrare ³i din ³ierul de ie³ire se termin  cu marcaj de sfâr³it de
linie.
Exemplu
pal.in pal.out Explicaµie
5 10001 Pentru acest exemplu avem L 5,
3 222 3 cifre de 0, 2 cifre de 1³i 3 cifre de 2.
2 Cifrele de la 3 la 9 lipsesc
3 de pe u³a turnului.
0
0 Cele dou  palindroame cu care
0 se genereaz  combinaµia magic 
0 sunt 10001 ³i 222.
0 Combinaµia magic  va  suma acestora
0 ³i anume 10223 (care este suma minim 
0 pe care o putem obµine).
Timp maxim de execuµie/test: 1 sec sub Windows ³i 1 sec sub Linux

Indicaµii de rezolvare - descriere soluµie

Soluµia ocial , Silviu G nceanu


Problema se rezolv  utilizând tehnica greedy. Not m num rul total de cifre cu N C . Se observ 
c , pentru ca suma celor dou  numere palindroame s  e minim , trebuie ca lungimea maxim  a
celor dou  numere s  e cât mai mic . A³adar, pentru început, se stabile³te lungimea exact  a
primului num r (care va  cel mai lung), în funcµie de L în modul urm tor:
1. dac  L $ N C ©2, atunci lungimea primului palindrom va  aleas  astfel încât cele dou 
numere s  e cât mai apropiate ca lungime
2. dac  L % N C ©2 apar cazuri particulare ³i se stabile³te dac  lungimea primului palindrom
cre³te sau nu cu o unitate.
Având lungimile numerelor stabilite putem merge mai departe utilzând strategia greedy, obser-
vând c  cifrele mai mici trebuie s  ocupe poziµii cât mai semnicative. Pentru a realiza acest lucru
se vor completa în paralel cele dou  numere cu cifrele parcurse în ordine cresc toare stabilind în
care din cele dou  numere se vor poziµiona. De asemenea, trebuie avut în vedere ca niciunul din
cele dou  numere s  nu înceap  cu cifra 0.
Datele de test au fost construite astfel încât ³i soluµii neoptime s  obµin  puncte, gradat, în
funcµie de optimiz rile efectuate asupra implement rii.

Codul surs 

import java.io.*; // la inceput cifre mici in p1


class Pal // apoi in ambele in paralel!
{
static int L;
static int NC=0;
static int nc1,nc2;
static int ncfi=0; // nr cifre cu frecventa impara
static int[] fc=new int[10]; // frecventa cifrelor
static int[] p1; // palindromul 1
static int[] p2; // palindromul 2

public static void main(String []args) throws IOException


{
int i;
StreamTokenizer st=new StreamTokenizer(
new BufferedReader(new FileReader("pal.in")));
CAPITOLUL 13. METODA OPTIMULUI LOCAL - GREEDY 174

PrintWriter out = new PrintWriter (


new BufferedWriter( new FileWriter("pal.out")));

st.nextToken();L=(int)st.nval;
for(i=0;i<=9;i++) { st.nextToken(); fc[i]=(int)st.nval;}

for(i=0;i<=9;i++) NC+=fc[i]; // nr total cifre


for(i=0;i<=9;i++) ncfi=ncfi+(fc[i]%2); // nr cifre cu frecventa impara

nc1=L;
nc2=NC-nc1;
while(nc1<nc2) {nc1++; nc2--;}
if((ncfi==2)&&(nc1%2==0)) {nc1++; nc2--;}

p1=new int[nc1];
p2=new int[nc2];
switch(ncfi)
{
case 0: impare0(); break;
case 1: impare1(); break;
case 2: impare2(); break;
default: System.out.println("Date de intrare eronate!");
}
corectez(p1);
corectez(p2);

for(i=0;i<p1.length;i++) out.print(p1[i]);
out.println();
for(i=0;i<p2.length;i++) out.print(p2[i]);
out.println();
int[] p12=suma(p1,p2);// pentru ca in teste rezultat = suma!
for(i=p12.length-1;i>=0;i--) out.print(p12[i]);
out.println();
out.close();
}//main

static void impare0() // cea mai mare cifra la mijloc


{
int i,k1,k2;
int imp1=nc1/2, imp2=nc2/2;

if(nc1%2==1) // numai daca nc1 si nc2 sunt impare !


for(i=9;i>=0;i--)
if(fc[i]>0)
{
p1[imp1]=i; fc[i]--;
p2[imp2]=i; fc[i]--;
break;
}
k1=0;
k2=0;
while(k1<nc1-nc2) {incarcPozitia(k1,p1); k1++;}// incarc numai p1
while((k1<imp1)||(k2<imp2))
{
if(k1<imp1) {incarcPozitia(k1,p1); k1++;}
if(k2<imp2) {incarcPozitia(k2,p2); k2++;}
}
}
CAPITOLUL 13. METODA OPTIMULUI LOCAL - GREEDY 175

static void impare1()


{
int i,k1,k2;
int imp1=nc1/2, imp2=nc2/2;

for(i=0;i<=9;i++)
if(fc[i]%2==1) { p1[imp1]=i; fc[i]--; break;}
for(i=0;i<=9;i++)
if(fc[i]%2==1) { p2[imp2]=i; fc[i]--; break;}

k1=0;
k2=0;
while(k1<nc1-nc2) {incarcPozitia(k1,p1); k1++;} // incarc numai p1
while((k1<imp1)||(k2<imp2))
{
if(k1<imp1) {incarcPozitia(k1,p1); k1++;}
if(k2<imp2) {incarcPozitia(k2,p2); k2++;}
}
}

static void impare2()


{
int i,k1,k2;
int imp1=nc1/2, imp2=nc2/2;

for(i=0;i<=9;i++)
if(fc[i]%2==1) { p1[imp1]=i; fc[i]--; break;}
for(i=0;i<=9;i++)
if(fc[i]%2==1) { p2[imp2]=i; fc[i]--; break;}

k1=0;
k2=0;
while(k1<nc1-nc2) {incarcPozitia(k1,p1); k1++;}// incarc numai p1
while((k1<imp1)||(k2<imp2))
{
if(k1<imp1) { incarcPozitia(k1,p1); k1++; }
if(k2<imp2) { incarcPozitia(k2,p2); k2++; }
}
}

static void corectez(int[] x)


{
int pozdif0,val, n=x.length;
pozdif0=0;
while(x[pozdif0]==0) pozdif0++;
if(pozdif0>0)
{
val=x[pozdif0];
x[0]=x[n-1]=val;
x[pozdif0]=x[n-pozdif0-1]=0;
}
}

static void incarcPozitia(int k, int[] x)


{
int i;
int n=x.length;
for(i=0;i<=9;i++)
if(fc[i]>0)
{
CAPITOLUL 13. METODA OPTIMULUI LOCAL - GREEDY 176

x[k]=i; fc[i]--;
x[n-k-1]=i; fc[i]--;
break;
}
}

static int[] suma(int[] x, int[] y)


{
int[] z=new int[(x.length>y.length) ? (x.length+1) : (y.length+1)];
int k,t=0;
for(k=0;k<=z.length-2;k++)
{
z[k]=t;
if(k<x.length) z[k]+=x[k];
if(k<y.length) z[k]+=y[k];
t=z[k]/10;
z[k]=z[k]%10;
}
z[z.length-1]=t;
if(z[z.length-1]!=0) return z;
else
{
int[] zz=new int[z.length-1];
for(k=0;k<zz.length;k++) zz[k]=z[k];
return zz;
}
}
}//class

13.3.17 “anµ - ONI2006 cls 9

Cei n deµinuµi ai unei închisori, numerotaµi de la 1 la n, trebuie s  sape un ³anµ dispus în linie
dreapt  între dou  puncte A ³i B , situate la distanµa de 250 km unul de cel lalt, pe care exist 
251 borne kilometrice numerotate de la 0 la 250. Fiecare deµinut are îns  o pretenµie, "e doar
democraµie, nu?": el dore³te s  sape doar undeva în zona dintre borna x ³i borna y . Pe lâng 
aceste pretenµii închisoarea se confrunt  ³i cu o alt  problem : nu are sucienµi paznici angajaµi.
Cerinµ 
Cunoscându-se num rul n de deµinuµi ³i pretenµiile lor, s  se determine locul (locurile) unde
vor  pu³i deµinuµii s  sape într-o zi de munc , respectându-se pretenµiile lor, astfel încât num rul
de paznici necesari pentru a p zi cei n deµinuµi s  e minim. Intervalul în care poate p zi un
paznic nu poate conµine dou  sau mai multe zone disjuncte dintre cele exprimate de deµinuµi în
preferinµele lor.
Date de intrare
Fi³ierul de intrare sant.in are pe prima linie num rul n de deµinuµi. Pe ecare dintre urm -
toarele n linii exist  câte dou  numere naturale, ai bi , separate printr-un spaµiu (ai & bi ), care
reprezint  pretenµia deµinutului. Mai exact pe linia i  1 (1 & i & n) este descris  pretenµia
deµinutului cu num rul de ordine i.
Date de ie³ire
Fi³ierul de ie³ire sant.out va conµine pe prima linie num rul natural k reprezentând num rul
minim de paznici necesari pentru paza celor n deµinuµi sco³i la lucru. Pe urm toarele 2k linii vor
 descrise locurile unde vor  pu³i s  sape deµinuµii, astfel: ecare pereche de linii 2j, 2j  1
va conµine pe linia 2j trei numere naturale p xj yj , separate prin câte un spaµiu reprezentând
num rul de ordine al paznicului ³i bornele kilometrice xj c si yj unde va p zi paznicul p, iar pe
linia 2j  1 vor  scrise numerele de ordine ale deµinuµilor care sap  în aceast  zon , separate prin
câte un spaµiu, ordonate cresc tor.
Restricµii ³i preciz ri
ˆ 1 & n & 10000
CAPITOLUL 13. METODA OPTIMULUI LOCAL - GREEDY 177

ˆ 0 & ai & bi & 250, pentru orice i, 1 & i & n


ˆ 0 & xj & yj & 250, pentru orice j , 1 & j & k
ˆ un deµinut poate s  sape ³i într-un singur punct ("în dreptul bornei kilometrice numerotat 
cu x")

ˆ în cazul în care exist  mai multe soluµii se va a³a una singur 

ˆ numerele de ordine ale paznicilor vor  scrise în ³ier în ordine cresc toare

ˆ numerotarea paznicilor începe de la 1

Exemplu
.in .out Explicaµie
3 2 sunt necesari 2 paznici: paznicul 1 va p zi între
0 20 1 8 13 borna 8 ³i borna 13, iar deµinuµii p ziµi sunt 1 ³i 2;
8 13 1 2 paznicul 2 va p zi între borna 30 ³i borna 60, iar
30 60 2 30 60 deµinutul p zit este 3
3
4 3 sunt necesari 3 paznici: paznicul 1 va p zi între
10 203 1 10 20 borna 10 ³i borna 20, iar deµinutul p zit este 1;
2 53 1 paznicul 2 va p zi la borna 5, iar deµinuµii p ziµi
30 403 2 55 sunt 2 ³i 4; paznicul 3 va p zi între borna 30 ³i
5 7 33 2 4 borna 40, iar deµinutul p zit este 3
3 30 40
3
5 2 sunt necesari 2 paznici: paznicul 1 va p zi la
10 30 1 30 30 borna 30, iar deµinuµii p ziµi sunt 1, 2, 3 ³i 4;
30 32 1 234 paznicul 2 va p zi între borna 27 ³i borna 28,
0 30 2 27 28 iar deµinutul p zit este 5
27 30 5
27 28 !Soluµia nu este unic !

Timp maxim de execuµie/test: 1 secund  (Windows), 0.5 secunde (Linux)

Indicaµii de rezolvare - descriere soluµie

Soluµia comisiei
Problema cere, de fapt, determinarea num rului minim de intersecµii între segmentele deter-
minate de kilometrul minim ³i maxim între care sap  un deµinut.
Pentru a le determina procedez astfel:

ˆ ordonez intervalele cresc tor dup  kilometrul minim ³i descresc tor dup  kilometrul maxim

ˆ pun primul deµinut (deci cel cu intervalul de s pare cel mai mare) în grija primului paznic

ˆ pentru toate celelalte

 caut un paznic care mai p ze³te deµinuµi ³i care poate p zi ³i acest deµinut (adic 
intersecµia segmentelor s  e nevid )
 dac  g sesc
 ajustez intervalul de s pare ca s  poat  s pa ³i acest deµinut
 altfel (dac  nu g sesc)
 mai am nevoie de un paznic ³i îl dau în grija acestuia

Datorit  faptului c  intervalele sunt sortate, cresc tor dup  cap tul inferior, în momentul când
un interval nu se mai intersecteaz  cu cel deja determinat, este sigur c  nici un alt interval ce îl
urmeaz  nu se mai intersecteaz , lucru ce asigur  determinarea num rului minim de paznici.
CAPITOLUL 13. METODA OPTIMULUI LOCAL - GREEDY 178

Codul surs 

import java.io.*;
class Sant
{
static int n;
static int[] x1=new int[10001];
static int[] x2=new int[10001];
static int[] o=new int[10001];

static void qsort(int li,int ls)


{
int val,aux,i,j;

i=li;
j=ls;
val=x2[(i+j)/2];

while(i<j)
{
while(x2[i]<val) i++;
while(x2[j]>val) j--;
if(i<=j)
{
aux=x1[i]; x1[i]=x1[j]; x1[j]=aux;
aux=x2[i]; x2[i]=x2[j]; x2[j]=aux;
aux=o[i]; o[i]=o[j]; o[j]=aux;
i++;
j--;
}
}// while

if(li<j) qsort(li,j);
if(i<ls) qsort(i,ls);
}// qsort(...)

public static void main(String[] args) throws IOException


{
int k,np,xp;
StreamTokenizer st=new StreamTokenizer(
new BufferedReader(new FileReader("sant.in")));
PrintWriter out=new PrintWriter(
new BufferedWriter(new FileWriter("sant.out")));

st.nextToken(); n=(int)st.nval;
for(k=1;k<=n;k++)
{
st.nextToken(); x1[k]=(int)st.nval;
st.nextToken(); x2[k]=(int)st.nval;
o[k]=k;
}

qsort(1,n);

np=1;
xp=x2[1];
for(k=2;k<=n;k++) if(x1[k]>xp) { np++; xp=x2[k]; }
out.println(np);
CAPITOLUL 13. METODA OPTIMULUI LOCAL - GREEDY 179

// inca o data pentru ...!


np=1;
xp=x2[1];
out.println(np+" "+xp+" "+xp);
out.print(o[1]+" ");
for(k=2;k<=n;k++)
{
if(x1[k]>xp)
{
out.println();
np++;
xp=x2[k];
out.println(np+" "+xp+" "+xp);
out.print(o[k]+" ");
}
else out.print(o[k]+" ");
}// for k
out.close();
}// main(...)
}// class

13.3.18 Cezar - OJI2007 cls 11

În Roma antic  exist  n a³ez ri senatoriale distincte, câte una pentru ecare dintre cei n
senatori ai Republicii. A³ez rile senatoriale sunt numerotate de la 1 la n, între oricare dou 
a³ez ri existând leg turi directe sau indirecte. O leg tur  este direct  dac  ea nu mai trece
prin alte a³ez ri senatoriale intermediare. Edilii au pavat unele dintre leg turile directe dintre
dou  a³ez ri (numind o astfel de leg tur  pavat  "strad "), astfel încât între oricare dou  a³ez ri
senatoriale s  existe o singur  succesiune de str zi prin care se poate ajunge de la o a³ezare
senatorial  la cealalt .
Toµi senatorii trebuie s  participe la ³edinµele Senatului. În acest scop, ei se deplaseaz  cu
lectica. Orice senator care se deplaseaz  pe o strad  pl te³te 1 ban pentru c  a fost transportat
cu lectica pe acea strad .
La alegerea sa ca prim consul, Cezar a promis c  va dota Roma cu o lectic  gratuit  care s 
circule pe un num r de k str zi ale Romei astfel încât orice senator care va circula pe str zile
respective, s  poat  folosi lectica gratuit  f r  a pl ti. Str zile pe care se deplaseaz  lectica
gratuit  trebuie s  e legate între ele (zborul, metroul sau teleportarea neind posibile la acea
vreme).
În plus, Cezar a promis s  stabileasc  sediul s lii de ³edinµe a Senatului într-una dintre a³ez rile
senatoriale aate pe traseul lecticii gratuite. Problema este de a alege cele k str zi ³i amplasarea
sediului s lii de ³edinµe a Senatului astfel încât, prin folosirea transportului gratuit, senatorii, în
drumul lor spre sala de ³edinµe, s  fac  economii cât mai însemnate. În calculul costului total de
transport, pentru toµi senatorii, Cezar a considerat c  ecare senator va c l tori exact o dat  de
la a³ezarea sa pân  la sala de ³edinµe a Senatului.
Cerinµ 
Scrieµi un program care determin  costul minim care se poate obµine prin alegerea adecvat 
a celor k str zi pe care va circula lectica gratuit  ³i a locului de amplasare a s lii de ³edinµ  a
Senatului.
Date de intrare
Fi³ierul cezar.in conµine
a pe prima linie dou  valori n k separate printr-un spaµiu reprezentând num rul total de
senatori ³i num rul de strazi pe care circul  lectica gratuit 
a pe urm torele n  1 linii se a  câte dou  valori i j separate printr-un spaµiu, reprezentând
numerele de ordine a dou  a³ez ri senatoriale între care exist  strad .
Date de ie³ire
Pe prima linie a ³ierului cezar.out se va scrie costul total minim al transport rii tuturor
senatorilor pentru o alegere optim  a celor k str zi pe care va circula lectica gratuit  ³i a locului
unde va  amplasat  sala de ³edinµe a Senatului.
CAPITOLUL 13. METODA OPTIMULUI LOCAL - GREEDY 180

Restricµii ³i preciz ri
a 1 $ n & 10000, 0 $ k $ n
a 1 & i, j & n , i j j
a Oricare dou  perechi de valori de pe liniile 2, 3, ..., n din ³ierul de intrare reprezint  dou 
str zi distincte.
a Perechile din ³ierul de intrare sunt date astfel încât respect  condiµiile din problem .
a Pentru 25% din teste n & 30, pentru 25% din teste 30 $ n & 1000, pentru 25% din teste
1000 $ n & 3000, pentru 10% din teste 3000 $ n & 5000, pentru 10% din teste 5000 $ n & 10000.
Exemplu

cezar.in cezar.out Explicaµie


13 3 11 Costul minim se obµine, de exemplu, pentru
12 alegerea celor 3 str zi între a³ez rile
23 5-7, 7-8, 8-10 ³i a s lii de ³edinµe a Senatului
28 în a³ezarea 8 (dup  cum este evidenµiat
78 în desen).
75
54 Exist  ³i alte alegeri pentru care se obµine
56 soluµia 11.
89
8 10
10 11
10 12
10 13
Timp maxim de execuµie/test: 0.5 secunde

Indicaµii de rezolvare - descriere soluµie *

O implementare posibil  utilizeaz  metoda GREEDY, O n ˜ n  k  sau O n  k  ˜ log n.


Se elimin  succesiv, dintre frunzele existente la un moment dat, frunza de cost minim. Toate
nodurile au costul iniµial 1. La eliminarea unei frunze, se incrementeaz  cu 1 costul tat lui acesteia.
Validitatea metodei rezult  din observaµia c , la eliminarea unei frunze oarecare, tat l acesteia
poate deveni frunz  la rândul lui, dar cu un cost strict mai mare decât al frunzei eliminate.
Se poate reµine arborele cu ajutorul listelor de adiacenµ  (liniare sau organizate ca arbori de
c utare), iar frunzele se pot memora într-un minheap de costuri, structur  care se actualizeaz  în
timp logaritmic.

Codul surs 

Varianta 1:

import java.io.*;
class cezar
{
static StreamTokenizer st;
static PrintWriter out;

static int n, ns;


CAPITOLUL 13. METODA OPTIMULUI LOCAL - GREEDY 181

static int[] v1 = new int[10001];


static int[] v2 = new int[10001];
static int[] g = new int[10001]; // gradele
static int[] ca = new int[10001]; // ca[k]=costul acumulat in nodul k !!!
static int[] nde = new int[10001]; // nde[k]=nr "descendenti" eliminati pana i

static void citire() throws IOException


{
int i,j,k;

st.nextToken(); n=(int)st.nval;
st.nextToken(); ns=(int)st.nval;

for(i=1;i<=n;i++) g[i]=ca[i]=nde[i]=0; // curatenie ...

for(k=1;k<=n-1;k++)
{
st.nextToken(); i=(int)st.nval;
st.nextToken(); j=(int)st.nval;

v1[k]=i;
v2[k]=j;
g[i]++; g[j]++;
}
}// citire(...)

static int tata(int i) // mai bine cu lista de adiacenta ...


{
int k,t;
t=-1; // initializarea aiurea ...
for(k=1;k<=n-1;k++) // n-1 muchii
{
if( (v1[k]==i) && (g[v2[k]] > 0) ) {t=v2[k]; break;}
else
if( (v2[k]==i) && (g[v1[k]] > 0) ) {t=v1[k]; break;}
}
return t;
}

static void eliminm() // frunze(g=1) cu cost=min


{
int i, imin, timin;
int min;

min=Integer.MAX_VALUE;
imin=-1; // initializare aiurea ...
for(i=1;i<=n;i++) // mai bine cu heapMIN ...
if(g[i]==1) // i=frunza
if(nde[i]+1<min) // ... aici este testul CORECT ... !!!
{
min=nde[i]+1;
imin=i;
}

timin=tata(imin);
g[imin]--; g[timin]--;
ca[timin]=ca[timin]+ca[imin]+nde[imin]+1;
nde[timin]=nde[timin]+nde[imin]+1; // nr descendenti eliminati
}// elimin()
CAPITOLUL 13. METODA OPTIMULUI LOCAL - GREEDY 182

public static void main(String[] args) throws IOException


{
int k,senat=0,cost=0;
st=new StreamTokenizer(new BufferedReader(new FileReader("cezar20.in")));
out=new PrintWriter(new BufferedWriter(new FileWriter("cezar.out")));

citire();

for(k=1;k<=n-1-ns;k++) eliminm();

for(k=1;k<=n;k++)
if(g[k]>0)
{
cost+=ca[k];
senat=k;
}

System.out.println("\n"+cost+" "+senat);
out.println(cost+" "+senat);
out.close();
//afisv(g,1,n);
}// main
}// class

Varianta 2:
import java.io.*;
class cezar // cost initial = 1 pentru toate nodurile ...
{
static StreamTokenizer st;
static PrintWriter out;

static int n, ns,s=0;

static int[] v1 = new int[10001];


static int[] v2 = new int[10001];
static int[] g = new int[10001]; // gradele
static int[] ca = new int[10001]; // ca[k]=costul acumulat in nodul k !!!

static void afisv(int[] v,int i1, int i2)


{
int i;
for(i=i1;i<=i2;i++)
{
System.out.print(v[i]+" ");
if(i%50==0) System.out.println();
}
System.out.println();
}

static void citire() throws IOException


{
int i,j,k;

st.nextToken(); n=(int)st.nval;
st.nextToken(); ns=(int)st.nval;

for(i=1;i<=n;i++) // curatenie ...


{
CAPITOLUL 13. METODA OPTIMULUI LOCAL - GREEDY 183

g[i]=0;
ca[i]=1; // cost initial in nodul i
}

for(k=1;k<=n-1;k++)
{
st.nextToken(); i=(int)st.nval;
st.nextToken(); j=(int)st.nval;

v1[k]=i;
v2[k]=j;
g[i]++; g[j]++;
}

//afisv(v1,1,n-1);
//afisv(v2,1,n-1);
//afisv(g,1,n);
}// citire(...)

static int tata(int i) // mai bine cu liste de adiacenta ...


{
int k,t;
t=-1; // initializarea aiurea ...
for(k=1;k<=n-1;k++) // este mai bine cu lista de adiacenta ?
{
if( (v1[k]==i) && (g[v2[k]]>0)) {t=v2[k]; break;}
else
if( (v2[k]==i) && (g[v1[k]]>)) {t=v1[k]; break;}
}
return t;
}

static void eliminm() // frunze(g=1) cu cost=min


{
int i, imin, timin;
int min;

min=Integer.MAX_VALUE;
imin=-1; // initializare aiurea ...
for(i=1;i<=n;i++) // mai bine cu heapMIN ...
if(g[i]==1) // i=frunza
if(ca[i]<min) // cost acumulat
{
min=ca[i];
imin=i;
}

timin=tata(imin);

g[imin]--; g[timin]--;

ca[timin]=ca[timin]+min;
s+=min;

//System.out.println(" Elimin nodul "+imin+


// " timin = "+timin+" ca = "+ca[timin]);
}// elimin()

public static void main(String[] args) throws IOException


{
CAPITOLUL 13. METODA OPTIMULUI LOCAL - GREEDY 184

int k,senat=0;
st=new StreamTokenizer(new BufferedReader(new FileReader("cezar20.in")));
out=new PrintWriter(new BufferedWriter(new FileWriter("cezar.out")));

citire();

for(k=1;k<=n-1-ns;k++)
{
eliminm();
}

//afisv(c,1,n);

for(k=1;k<=n;k++)
if(g[k]>0)
{
senat=k;
break;
}

System.out.println("\n"+s+" "+senat);

out.println(s+" "+senat);
out.close();
}// main
}// class
Capitolul 14

Metoda backtracking

Metoda backtracking se utilizeaz  pentru determinarea unei submulµimi a unui produs cartezian
de forma S1  S2  ...  Sn (cu mulµimile Sk nite) care are anumite propriet µi. Fiecare element
s1 , s2 , ..., sn  al submulµimii produsului cartezian poate  interpretat ca soluµie a unei probleme
concrete.
În majoritatea cazurilor nu oricare element al produsului cartezian este soluµie ci numai cele
care satisfac anumite restricµii. De exemplu, problema determin rii tuturor combin rilor de m
elemente luate câte n (unde 1 & n & m) din mulµimea r1, 2, ..., mx poate  reformulat  ca problema
n
determin rii submulµimii produsului cartezian r1, 2, ..., mx denit  astfel:

r s1 , s2 , ..., sn  " r1, 2, ..., mxn ¶si j sj , ¾i j j, 1 & i, j & nx.


Metoda se aplic  numai atunci când nu exist  nici o alt  cale de rezolvare a problemei propuse,
deoarece timpul de execuµie este de ordin exponenµial.

14.1 Generarea produsului cartezian


Pentru încep tori, nu metoda backtracking în sine este dicil de înµeles ci modalitatea de
generare a produsului cartezian.

14.1.1 Generarea iterativ  a produsului cartezian

Presupunem c  mulµimile Si sunt formate din numere întregi consecutive cuprinse între o
valoare min ³i o valoare max. De exemplu, dac  min 0, max 9 atunci Si r0, 1, ..., 9x. Dorim
s  gener m produsul cartezian S1  S2  ...  Sn (de exemplu, pentru n 4). Folosim un vector
cu n elemente a a1 , a2 , ..., an  ³i vom atribui ec rei componente ai valori cuprinse între min
³i max.
0 1 2 3 4 5

0 1 ... ... n n+1


Ne trebuie un marcaj pentru a preciza faptul c  o anumit  component  este goal  (nu are
plasat  în ea o valoare valid ). În acest scop folosim variabila gol care poate  iniµializat 
cu orice valoare întreag  care nu este în intervalul min, max. Este totu³i util  iniµializarea
gol=min-1;.
Cum începem generarea produsului cartezian?
O prim  variant  este s  atribuim tuturor componentelor ai valoarea min ³i avem astfel un
prim element al produsului cartezian, pe care putem s -l a³ m.
0 1 2 3 4 5
0 0 0 0
0 1 ... ... n n+1
Trebuie s  construim urm toarele elemente ale produsului cartezian. Este util , în acest mo-
ment, imaginea kilometrajelor de ma³ini sau a contoarelor de energie electic , de ap , de gaze,
etc. Urm toarea conguraµie a acestora este

185
CAPITOLUL 14. METODA BACKTRACKING 186

0 1 2 3 4 5
0 0 0 1 dup  care urmeaz 
0 1 ... ... n n+1
0 1 2 3 4 5
0 0 0 2
0 1 ... ... n n+1
Folosim o variabil  k pentru a urm ri poziµia din vector pe care se schimb  valorile. La început
k n ³i, pe aceast  poziµie k , valorile se schimb  crescând de la min c tre max. Se ajunge astfel
la conguraµia (k 4 n aici!)
0 1 2 3 4 5
0 0 0 9
0 1 ... ... n n+1
Ce se întâmpl  mai departe? Apare conguraµia
0 1 2 3 4 5
0 0 1 0 într-un mod ciudat!
0 1 ... ... n n+1
Ei bine, se poate spune c  aici este cheia metodei backtraching! Dac  reu³im s  înµelegem acest
pas de trecere de la o conguraµie la alta ³i s  formaliz m ce am observat, atunci am descoperit
singuri aceast  metod !
Pe poziµia k n, odat  cu apariµia valorii 9 max, s-au epuizat toate valorile posibile, a³a
c  vom considera c  poziµia k este goal  ³i vom trece pe o poziµie mai la stânga, deci k k  1
(acum k 3).
0 1 2 3 4 5
0 0 0
0 1 ... ... n n+1
Pe poziµia k 3 se af  valoarea 0 care se va schimba cu urm toarea valoare (dac  exist  o
astfel de valoare & max), deci în acest caz cu 1.
0 1 2 3 4 5
0 0 1
0 1 ... ... n n+1
Dup  plasarea unei valori pe poziµia k , se face pasul spre dreapta (k k  1).
0 1 2 3 4 5
0 0 1
0 1 ... ... n n+1
Aici (dac  nu am ie³it în afara vectorului, în urma pasului f cut spre dreapta!), în cazul nostru
k 4 ³i poziµia este goal , se plaseaz  prima valoare valid  (deci valoarea min).
0 1 2 3 4 5
0 0 1 0
0 1 ... ... n n+1
Cum trecerea de la o valoare la alta se face prin cre³terea cu o unitate a valorii vechi iar acum
facem trecerea gol min, înµelegem de ce este util s  iniµializ m variabila gol cu valoarea
min  1.
S  consider m c  s-a ajuns la conguraµia
0 1 2 3 4 5
0 0 9 9
0 1 ... ... n n+1
Aici k 4 n ³i 9 max. Pe poziµia k nu se mai poate pune o nou  valoare ³i atunci:
a se pune gol pe poziµia k
a se face pasul spre stânga, deci k k1
0 1 2 3 4 5
0 0 9
0 1 ... ... n n+1
Aici k 3 ³i 9 max. Pe poziµia k nu se mai poate pune o nou  valoare ³i atunci:
a se pune gol pe poziµia k
a se face pasul spre stânga, deci k k1
0 1 2 3 4 5
0 0
0 1 ... ... n n+1
Aici k 2 ³i 0 $ max. Pe poziµia k se poate pune o nou  valoare ³i atunci:
a se pune 0  1 1 urm toarea valoare pe poziµia k
a se face pasul spre dreapta, deci k k1
CAPITOLUL 14. METODA BACKTRACKING 187

0 1 2 3 4 5
0 1
0 1 ... ... n n+1
Aici k 3 ³i gol=-1$ max. Pe poziµia k se poate pune o nou  valoare:
a se pune gol+1 1  1 0 urm toarea valoare pe poziµia k
a se face pasul spre dreapta, deci k k1
0 1 2 3 4 5
0 1 0
0 1 ... ... n n+1
Aici k 4 ³i gol=-1$ max. Pe poziµia k se poate pune o nou  valoare:
a se pune gol+1 1  1 0 urm toarea valoare pe poziµia k
a se face pasul spre dreapta, deci k k1
0 1 2 3 4 5
0 1 0 0
0 1 ... ... n n+1
iar aici k n  1 ³i am ajuns în afara vectorului! Nu-i nimic! Se a³eaz  soluµia 0100 ³i
se face pasul la stânga. Aici k 4 ³i 0 $ max. Pe poziµia k se poate pune o nou  valoare:
a se pune 0  1 1 urm toarea valoare pe poziµia k
a se face pasul spre dreapta, deci k k1
0 1 2 3 4 5
0 1 0 1
0 1 ... ... n n+1
A ap rut în mod evident urm toarea regul : ajungând pe poziµia k & n, încerc m s  punem
urm toarea valoare (gol are ca "urm toarea valoare" pe min) pe aceast  poziµie ³i
a dac  se poate: se pune urm toarea valoare ³i se face pasul spre dreapta;
a dac  nu se poate: se pune gol=min  1 ³i se face pasul spre stânga.
Presupunem c  s-a ajuns la conguraµia
0 1 2 3 4 5
9 9 9 9 care se a³eaz  ca soluµie. Ajungem la k 4
0 1 ... ... n n+1
0 1 2 3 4 5
9 9 9 9 se gole³te poziµia ³i ajungem la k 3
0 1 ... ... n n+1
0 1 2 3 4 5
9 9 9 se gole³te poziµia ³i ajungem la k 2
0 1 ... ... n n+1
0 1 2 3 4 5
9 9 se gole³te poziµia ³i ajungem la k 1
0 1 ... ... n n+1
0 1 2 3 4 5
9 se gole³te poziµia ³i ajungem la k 0
0 1 ... ... n n+1
0 1 2 3 4 5
iar aici k 0 ³i am ajuns în afara vectorului! Nu-i nimic! Aici
0 1 ... ... n n+1
s-a terminat generarea produsului cartezian!
n
Putem descrie acum algoritmul pentru generarea produsului cartezian S , unde S
rmin, min  1, ..., maxx:
a structuri de date:
 a1..n vectorul soluµie
 k "poziµia" în vectorul soluµie, cu convenµiile k 0 precizeaz  terminarea gener rii, iar
k n  1 precizeaz  faptul c  s-a construit o soluµie care poate  a³at ;
a proces de calcul:
1. se construie³te prima soluµie
2. se plaseaz  poziµia în afara vectorului (în dreapta)
3. cât timp nu s-a terminat generarea
4. dac  este deja construit  o soluµie
5. se a³eaz  soluµia ³i se face pasul spre stânga
6. altfel, dac  se poate m ri valoarea pe poziµia curent 
7. se m re³te cu 1 valoarea ³i se face pasul spre dreapta
CAPITOLUL 14. METODA BACKTRACKING 188

8. altfel, se gole³te poziµia curent  ³i se face pasul spre stânga


3'. revenire la 3.
Implementarea acestui algoritm în Java este urm toarea:

class ProdCartI1 // 134.000 ms pentru 8 cu 14


{
static int n=8, min=1, max=14, gol=min-1;
static int[] a=new int[n+1];

static void afis(int[] a)


{
for(int i=1;i<=n;i++) System.out.print(a[i]);
System.out.println();
}

public static void main (String[] args)


{
int i, k;
long t1,t2;
t1=System.currentTimeMillis();
for(i=1;i<=n;i++) a[i]=min;
k=n+1;
while (k>0)
if (k==n+1) {/* afis(a); */ --k;}
else { if (a[k]<max) ++a[k++]; else a[k--]=gol; }
t2=System.currentTimeMillis();
System.out.println("Timp = "+(t2-t1)+" ms");
}
}

O alt  variant  ar putea  iniµializarea vectorului soluµie cu gol ³i plasarea pe prima po-
ziµie în vector. Nu mai avem în acest caz o soluµie gata construit  dar algoritmul este acela³i.
Implementarea în acest caz este urm toarea:

class ProdCartI2 // 134.000 ms pentru 8 cu 14


{
static int n=8, min=1, max=14, gol=min-1;
static int[] a=new int[n+1];

static void afis(int[] a)


{
for(int i=1;i<=n;i++) System.out.print(a[i]);
System.out.println();
}

public static void main (String[] args)


{
int i, k;
long t1,t2;
t1=System.currentTimeMillis();
for(i=1;i<=n;i++) a[i]=gol;
k=1;
while (k>0)
if (k==n+1) {/* afis(a); */ --k;}
else { if (a[k]<max) ++a[k++]; else a[k--]=gol; }
t2=System.currentTimeMillis();
System.out.println("Timp = "+(t2-t1)+" ms");
}
}
CAPITOLUL 14. METODA BACKTRACKING 189

14.1.2 Generarea recursiv  a produsului cartezian

class ProdCartR1 // 101.750 ms pentru 8 cu 14


{
static int n=8, min=1,max=14;
static int[] a=new int[n+1];

static void afis(int[] a)


{
for(int i=1;i<=n;i++) System.out.print(a[i]);
System.out.println();
}

static void f(int k)


{
int i;
if (k>n) {/* afis(a); */ return; };
for(i=min;i<=max;i++) { a[k]=i; f(k+1); }
}

public static void main (String[] args)


{
long t1,t2;
t1=System.currentTimeMillis();
f(1);
t2=System.currentTimeMillis();
System.out.println("Timp = "+(t2-t1)+" ms");
}
}

class ProdCartR2 // 71.300 ms pentru 8 cu 14


{
static int n=8, min=1,max=14;
static int[] a=new int[n+1];

static void afis(int[] a)


{
for(int i=1;i<=n;i++) System.out.print(a[i]);
System.out.println();
}

static void f(int k)


{
int i;
for(i=min;i<=max;i++) { a[k]=i; if (k<n) f(k+1); }
}

public static void main (String[] args)


{
long t1,t2;
t1=System.currentTimeMillis();
f(1);
t2=System.currentTimeMillis();
System.out.println("Timp = "+(t2-t1)+" ms");
}
}

class ProdCartCar1
{
static char[] x;
CAPITOLUL 14. METODA BACKTRACKING 190

static int n,m;


static char[] a={0,’A’,’X’};

public static void main(String[] args)


{
n=4;
m=a.length-1;
x=new char[n+1];
f(1);
}

static void f(int k)


{
int i;
for(i=1;i<=m;i++)
{
x[k]=a[i];
if(k<n) f(k+1);
else afisv();
}
}

static void afisv()


{
int i;
for(i=1; i<=n; i++)
System.out.print(x[i]);
System.out.println();
}
}//class

class ProdCartCar2
{
static char[] x;
static int n;
static int[] m;
static char[][] a = { {0},
{0,’A’,’B’},
{0,’1’,’2’,’3’}
};

public static void main(String[] args)


{
int i;
n=a.length-1;
m=new int[n+1];
for(i=1;i<=n;i++) m[i]=a[i].length-1;
x=new char[n+1];
f(1);
}

static void f(int k)


{
int j;
for(j=1;j<=m[k];j++)
{
x[k]=a[k][j];
if(k<n) f(k+1); else afisv();
}
}
CAPITOLUL 14. METODA BACKTRACKING 191

static void afisv()


{
int i;
for(i=1; i<=n; i++) System.out.print(x[i]);
System.out.println();
}
}// class

14.2 Metoda bactracking


Se folose³te pentru rezolvarea problemelor care îndeplinesc urm toarele condiµii:

1. nu se cunoa³te o alt  metod  mai rapid  de rezolvare;

2. soluµia poate  pus  sub forma unui vector x x1 , x2 , ..., xn  cu xi " Ai , i 1, ..., n;

3. mulµimile Ai sunt nite.

Tehnica backtracking pleac  de la urm toarea premis :

dac  la un moment dat nu mai am nici o ³ans  s  ajung la soluµia c utat , renunµ s 
continui pentru o valoare pentru care ³tiu c  nu ajung la nici un rezultat.

Specicul metodei const  în maniera de parcurgere a spaµiului soluµiilor.


a soluµiile sunt construite succesiv, la ecare etap  ind completat  câte o component ;
a alegerea unei valori pentru o component  se face într-o anumit  ordine astfel încât s  e
asigurat  o parcurgere sistematic  a spaµiului A1  A2  ...  An ;
a la completarea componentei k se veric  dac  soluµia parµial  x1 , x2 , ..., xk  veric  condiµiile
induse de restricµiile problemei (acestea sunt numite condiµii de continuare);
a dac  au fost încercate toate valorile corespunz toare componentei k ³i înc  nu a fost g  sit  o
soluµie, sau dacse dore³te determinarea unei noi soluµii, se revine la componenta anterioar  (k  1)
³i se încearc  urm toarea valoare corespunz  toare acesteia, ³.a.m.d.
a procesul de c utare ³i revenire este continuat pân  când este g sit  o soluµie (dac  este
sucient  o soluµie) sau pân  când au foste testate toate conguraµiile posibile (dac  sunt necesare
toate soluµiile).
În gur  este ilustrat modul de parcurgere a spaµiului soluµiilor în cazul gener rii produsului
4
cartezianr0, 1x .
În cazul în care sunt specicate restricµii (ca de exemplu, s  nu e dou  componente al turate
egale) anumite ramuri ale structurii arborescente asociate sunt abandonate înainte de a atinge
lungimea n.
CAPITOLUL 14. METODA BACKTRACKING 192

0 1 x1

0 1 0 1 x2

0 1 0 1 0 1 0 1 x3

0 1 0 1 0 1 0 1 0 1 0 1 0 1 0 1 x4

0 1 x1

0 1 0 1 x2

0 1 0 1 x3

0 1 0 1 x4

În aplicarea metodei pentru o problem  concret  se parcurg urm toarele etape:


a se alege o reprezentare a soluµiei sub forma unui vector cu n componente;
a se identic  mulµimile A1 , A2 , ..., An ³i se stabile³te o ordine n
tre elemente care s  indice
modul de parcurgere a ec rei mulµimi;
a pornind de la restricµiile problemei se stabilesc condiµiile de validitate ale soluµiilor parµiale
(condiµiile de continuare).
În aplicaµiile concrete soluµia nu este obµinut  numai în cazul în care k n ci este posibil s  e
satisf cut  o condiµie de g sire a soluµiei pentru k $ n (pentru anumite probleme nu toate soluµiile
conµin acela³i num r de elemente).

14.2.1 Bactracking iterativ

Structura general  a algoritmului este:


for(k=1;k$=n;k++) x[k]=gol; iniµiarizarea vectorului soluµie
k=1; poziµionare pe prima component 
while (k%0) cât timp exist  componente de analizat
if (k==n+1) dac  x este soluµie
{as(x); k;} atunci: a³are soluµie ³i pas stânga
else altfel:
{
if(x[k]$max[k]) dac  exist  elemente de încercat
if(posibil(1+x[k])) dac  1+x[k] este valid 
++x[k++]; atunci m resc ³i fac pas dreapta
else ++x[k]; altfel m resc ³i r mân pe pozitie
else x[k]=gol; altfel golesc ³i fac pas dreapta
}
Vectorul soluµie x conµine indicii din mulµimile A1 , A2 , ..., An . Mai precis, xk  i înseamn 
c  pe poziµia k din soluµia x se a  ai din Ak .

14.2.2 Backtracking recursiv

Varianta recursiv  a algoritmului backtracking poate  realizat  dup  o schem  asem n toare


cu varianta recursiv . Principiul de funcµionare al functiei f (primul apel este f 1) corespunz tor
unui nivel k este urm torul:
 în situaµia în care avem o soluµie, o a³ m ³i revenim pe nivelul anterior;
CAPITOLUL 14. METODA BACKTRACKING 193

 în caz contrar, se iniµializeaz  nivelul ³i se caut  un succesor;


 când am g sit un succesor, veric m dac  este valid. În caz armativ, procedura se autoa-
peleaz  pentru k  1; în caz contrar urmând a se continua c utarea succesorului;
 dac  nu avem succesor, se trece la nivelul inferior k  1 prin ie³irea din procedura recursiv 
f.

14.3 Probleme rezolvate

14.3.1 Generarea aranjamentelor

Reprezentarea soluµiilor: un vector cu n componente.


Mulµimile Ak : r1, 2, ..., mx.
Restricµiile ³i condiµiile de continuare: Dac  x x1 , x2 , ..., xn  este o soluµie ea trebuie s 
respecte restricµiile: xi j xj pentru oricare i j j . Un vector cu k elemente x1 , x2 , ..., xk  poate
conduce la o soluµie numai dac  satisface condiµiile de continuare xi j xj pentru orice i j j , unde
i, j " r1, 2, ..., k x.
Condiµia de g sire a unei soluµii: Orice vector cu n componente care respect  restricµiile este
o soluµie. Când k n  1 a fost g sit  o soluµie.
Prelucrarea soluµiilor: Fiecare soluµie obµinut  este a³at .

class GenAranjI1
{
static int n=2, min=1,max=4, gol=min-1;
static int[] a=new int[n+1];

static void afis(int[] a)


{
for(int i=1;i<=n;i++)
System.out.print(a[i]);
System.out.println();
}

static boolean gasit(int val, int pozmax)


{
for(int i=1;i<=pozmax;i++)
if (a[i]==val) return true;
return false;
}

public static void main (String[] args)


{
int i, k;
for(i=1;i<=n;i++) a[i]=gol;
k=1;
while (k>0)
if (k==n+1) {afis(a); --k;}
else
{
if(a[k]<max)
if(!gasit(1+a[k],k-1)) ++a[k++]; // maresc si pas dreapta
else ++a[k]; // maresc si raman pe pozitie
else a[k--]=gol;
}
}
}

class GenAranjI2
{
CAPITOLUL 14. METODA BACKTRACKING 194

static int n=2, min=1,max=4;


static int gol=min-1;
static int[] a=new int[n+1];

static void afis()


{
for(int i=1;i<=n;i++) System.out.print(a[i]);
System.out.println();
}

static boolean posibil(int k)


{
for(int i=1;i<k;i++) if (a[i]==a[k]) return false;
return true;
}

public static void main (String[] args)


{
int i, k;
boolean ok;
for(i=1;i<=n;i++) a[i]=gol;
k=1;
while (k>0)
{
ok=false;
while (a[k] < max) // caut o valoare posibila
{
++a[k];
ok=posibil(k);
if(ok) break;
}
if(!ok) k--;
else if (k == n) afis();
else a[++k]=0;
}
}
}

class GenAranjR1
{
static int n=2, m=4, nsol=0;
static int[] a=new int[n+1];

static void afis()


{
System.out.print(++nsol+" : ");
for(int i=1;i<=n;i++) System.out.print(a[i]+" ");
System.out.println();
}

static void f(int k)


{
int i,j;
boolean gasit;
for(i=1; i<=m; i++)
{
if(k>1) // nu este necesar !
{
gasit=false;
for(j=1;j<=k-1;j++)
if(i==a[j])
CAPITOLUL 14. METODA BACKTRACKING 195

{
gasit=true;
break; // in for j
}
if(gasit) continue; // in for i
}
a[k]=i;
if(k<n) f(k+1); else afis();
}
}

public static void main(String[] args)


{
f(1);
}
}// class

class GenAranjR2
{
static int[] a;
static int n,m;

public static void main(String[] args)


{
n=2;
m=4;
a=new int[n+1];
f(1);
}

static void f(int k)


{
boolean ok;
int i,j;
for(i=1;i<=m;i++)
{
ok=true; // k=1 ==> nu am in stanga ... for nu se executa !
for(j=1;j<k;j++)
if(i==a[j])
{
ok=false;
break;
}
if(!ok) continue;
a[k]=i;
if(k<n) f(k+1);
else afisv();
}
}

static void afisv()


{
int i;
for(i=1; i<=n; i++)
System.out.print(a[i]);
System.out.println();
}
}
CAPITOLUL 14. METODA BACKTRACKING 196

14.3.2 Generarea combin rilor

Sunt prezentate mai multe variante (iterative ³i recursive) cu scopul de a vedea diferite moda-
lit µi de a câ³tiga timp în execuµie (mai mult sau mai puµin semnicativ!).
class GenCombI1a // 4550 ms cu 12 22 // 40 ms cu 8 14 fara afis
{ // 7640 ms cu 8 14 cu afis
static int n=8, min=1,max=14;
static int gol=min-1,nv=0;
static int[] a=new int[n+1];

static void afis(int[] a)


{
int i;
System.out.print(++nv+" : ");
for(i=1;i<=n;i++)
System.out.print(a[i]);
System.out.println();
}

public static void main (String[] args)


{
int i, k;
long t1,t2;
t1=System.currentTimeMillis();
for(i=0;i<=n;i++)
a[i]=gol; // initializat si a[0] care nu se foloseste!!
k=1;
while (k>0)
if (k==n+1) { afis(a); --k; }
else
{
if(a[k]<max)
if(1+a[k]>a[k-1])
++a[k++]; // maresc si pas dreapta (k>1) ...
else ++a[k]; // maresc si raman pe pozitie
else a[k--]=gol;
}
t2=System.currentTimeMillis();
System.out.println("Timp = "+(t2-t1)+" ms");
}
}// class
class GenCombI1b // 3825 ms 12 22 sa nu mai merg la n+1 !!!!
{
static int n=12, min=1,max=22;
static int gol=min-1;
static int[] a=new int[n+1];

static void afis(int[] a)


{
for(int i=1;i<=n;i++) System.out.print(a[i]);
System.out.println();
}

public static void main (String[] args)


{
int i;
long t1,t2;
t1=System.currentTimeMillis();
for(i=0;i<=n;i++) a[i]=gol; // initializat si a[0] care nu se foloseste!!
CAPITOLUL 14. METODA BACKTRACKING 197

int k=1;
while (k>0)
{
if(a[k]<max)
if(1+a[k]>a[k-1])
if(k<n) ++a[k++]; // maresc si pas dreapta (k>1) ...
else { ++a[k]; /* afis(a); */}// maresc, afisez si raman pe pozitie
else ++a[k]; // maresc si raman pe pozitie
else a[k--]=gol;
}
t2=System.currentTimeMillis();
System.out.println("Timp = "+(t2-t1)+" ms");
}
}// class

class GenCombI2a // 1565 ms 12 22


{
static int n=12, min=1,max=22;
static int gol=min-1;
static int[] a=new int[n+1];

static void afis(int[] a)


{
for(int i=1;i<=n;i++) System.out.print(a[i]);
System.out.println();
}

public static void main (String[] args)


{
int i, k;
long t1,t2;
t1=System.currentTimeMillis();
for(i=0;i<=n;i++)
a[i]=gol; // initializat si a[0] care nu se foloseste!!
k=1;
while (k>0)
if (k==n+1) {/* afis(a); */ --k;}
else
{
if(a[k]<max-(n-k)) // optimizat !!!
if(1+a[k]>a[k-1]) ++a[k++]; // maresc si pas dreapta (k>1) ...
else ++a[k]; // maresc si raman pe pozitie
else a[k--]=gol;
}
t2=System.currentTimeMillis();
System.out.println("Timp = "+(t2-t1)+" ms");
}
}

class GenCombI2b // 1250 ms 12 22


{
static int n=12, min=1,max=22;
static int gol=min-1;
static int[] a=new int[n+1];

static void afis(int[] a)


{
for(int i=1;i<=n;i++)
System.out.print(a[i]);
System.out.println();
}
CAPITOLUL 14. METODA BACKTRACKING 198

public static void main (String[] args)


{
int i, k; long t1,t2;
t1=System.currentTimeMillis();
for(i=0;i<=n;i++)
a[i]=gol; // initializat si a[0] care nu se foloseste!!
k=1;
while (k>0)
{
if(a[k]<max-(n-k)) // optimizat !!!
if(1+a[k]>a[k-1])
if(k<n) ++a[k++]; // maresc si pas dreapta (k>1) ...
else { ++a[k]; /* afis(a); */}// maresc, afisez si raman pe pozitie
else ++a[k]; // maresc si raman pe pozitie
else a[k--]=gol;
}
t2=System.currentTimeMillis();
System.out.println("Timp = "+(t2-t1)+" ms");
}
}

class GenCombI3a // 835 ms 12 22


{
static int n=12, min=1, max=22, gol=min-1;
static int[] a=new int[n+1];

static void afis(int[] a)


{
for(int i=1;i<=n;i++) System.out.print(a[i]);
System.out.println();
}

public static void main (String[] args)


{
int i, k; // si mai optimizat !!!
long t1,t2; t1=System.currentTimeMillis();
for(i=0;i<=n;i++)
a[i]=i-gol-1; // initializat si a[0] care nu se foloseste!!
k=1;
while (k>0)
if (k==n+1) {/* afis(a); */ --k;}
else
{
if(a[k]<max-(n-k)) // optimizat !!!
if(1+a[k]>a[k-1]) ++a[k++]; // maresc si pas dreapta (k>1) ...
else ++a[k]; // maresc si raman pe pozitie
else a[k--]=k-gol-1; // si mai optimizat !!!
}
t2=System.currentTimeMillis();
System.out.println("Timp = "+(t2-t1)+" ms");
}
}

class GenCombI3b // 740 ms 12 22


{
static int n=12, min=1, max=22, gol=min-1;
static int[] a=new int[n+1];

static void afis(int[] a)


{
CAPITOLUL 14. METODA BACKTRACKING 199

for(int i=1;i<=n;i++) System.out.print(a[i]);


System.out.println();
}

public static void main (String[] args)


{
int i, k;
long t1,t2;
t1=System.currentTimeMillis();
for(i=0;i<=n;i++) a[i]=i-gol-1; // si mai optimizat !!!
k=1;
while (k>0)
{
if(a[k]<max-(n-k)) // optimizat !!!
{
++a[k]; // maresc
if(a[k]>a[k-1])
if(k<n) k++; // pas dreapta
/* else afis(a); */ // afisez
}
else a[k--]=k-gol-1; // si mai optimizat !!!
}
t2=System.currentTimeMillis();
System.out.println("Timp = "+(t2-t1)+" ms");
}
}
class GenCombR1a // 2640 ms 12 22
{
static int[] x;
static int n,m;

public static void main(String[] args)


{
long t1,t2;
t1=System.currentTimeMillis();
n=12; m=22;
x=new int[n+1];
f(1);
t2=System.currentTimeMillis();
System.out.println("Timp = "+(t2-t1)+" ms");
}

static void f(int k)


{
for(int i=1;i<=m;i++)
{
if(k>1) if(i<=x[k-1]) continue;
x[k]=i;
if(k<n) f(k+1);/* else afisv(); */
}
}

static void afisv()


{
for(int i=1; i<=n; i++) System.out.print(x[i]);
System.out.println();
}
}

class GenCombR1b // 2100 ms 12 22


CAPITOLUL 14. METODA BACKTRACKING 200

{
static int n=12,m=22;
static int[] a=new int[n+1];

static void afis(int[] a)


{
for(int i=1;i<=n;i++) System.out.print(a[i]);
System.out.println();
}

static void f(int k)


{
for(int i=1;i<=m;i++ )
{
if (i<=a[k-1]) continue; // a[0]=0 deci merge !
a[k]=i;
if(k<n) f(k+1); /* else afis(a); */
}
}

public static void main (String [] args)


{
long t1,t2;
t1=System.currentTimeMillis();
f(1);
t2=System.currentTimeMillis();
System.out.println("Timp = "+(t2-t1)+" ms");
}//main
}//class

class GenCombR2a // 510 ms 12 22


{
static int n=12, m=22, nsol=0, ni=0; ;
static int[] x=new int[n+1];

static void afisx()


{
System.out.print(++nsol+" ==> ");
for(int i=1;i<=n;i++) System.out.print(x[i]+" ");
System.out.println();
}

static void afis(int k) // pentru urmarirea executiei !


{ // ni = numar incercari !!!
int i;
System.out.print(++ni+" : ");
for(i=1;i<=k;i++) System.out.print(x[i]+" ");
System.out.println();
}

static void f(int k)


{
for(int i=k; i<=m-n+k; i++) // imbunatatit !
{
if(k>1)
{
// afis(k-1);
if(i<=x[k-1]) continue;
}
x[k]=i;
if(k<n) f(k+1); /* else afisx(); */
CAPITOLUL 14. METODA BACKTRACKING 201

}
}

public static void main(String[] args)


{
long t1,t2;
t1=System.currentTimeMillis();
f(1);
t2=System.currentTimeMillis();
System.out.println("Timp = "+(t2-t1)+" ms");
}
}// class

class GenCombR2b // 460 ms 12 22


{
static int n=12, m=22, nsol=0,ni=0;
static int[] x=new int[n+1];

static void afisx()


{
System.out.print(++nsol+" ==> ");
for(int i=1;i<=n;i++) System.out.print(x[i]+" ");
System.out.println();
}

static void afis(int k) // pentru urmarirea executiei !


{ // ni = numar incercari !!!
System.out.print(++ni+" : ");
for(int i=1;i<=k;i++) System.out.print(x[i]+" ");
System.out.println();
}

static void f(int k)


{
for(int i=k; i<=m-n+k; i++) // imbunatatit !
{
if(i<=x[k-1]) continue;
x[k]=i;
if(k<n) f(k+1); /* else afisx(); */
}
}

public static void main(String[] args)


{
long t1,t2;
t1=System.currentTimeMillis();
f(1);
t2=System.currentTimeMillis();
System.out.println("Timp = "+(t2-t1)+" ms");
}
}// class

class GenCombR3a // 165 ms 12 22


{
static int n=12;
static int m=22;
static int[] x=new int[n+1];
static int nsol=0,ni=0;

static void afisx()


{
CAPITOLUL 14. METODA BACKTRACKING 202

int i;
System.out.print(++nsol+" ==> ");
for(i=1;i<=n;i++) System.out.print(x[i]+" ");
System.out.println();
}

static void afis(int k)


{
int i;
System.out.print(++ni+" : ");
for(i=1;i<=k;i++) System.out.print(x[i]+" ");
System.out.println();
}

static void f(int k)


{
int i;
for (i=x[k-1]+1; i<=m-n+k; i++) // si mai imbunatatit !!!
{
if(k>1)
{
// afis(k-1);
if(i<=x[k-1]) continue;
}
x[k]=i;
if(k<n) f(k+1); /* else afisx(); */
}
}

public static void main(String[] args)


{
long t1,t2;
t1=System.currentTimeMillis();
f(1);
t2=System.currentTimeMillis();
System.out.println("Timp = "+(t2-t1)+" ms");
}
}// class

class GenCombR3b // 140 ms 12 22


{
static int n=12;
static int m=22;
static int[] x=new int[n+1];
static int nsol=0;

static void afisx()


{
int i;
System.out.print(++nsol+" : ");
for(i=1;i<=n;i++) System.out.print(x[i]+" ");
System.out.println();
}

static void f(int k)


{
int i;
for (i=x[k-1]+1; i<=m-n+k; i++)
{
x[k]=i;
if(k<n) f(k+1); /* else afisx(); */
CAPITOLUL 14. METODA BACKTRACKING 203

}
}

public static void main(String[] args)


{
long t1,t2;
t1=System.currentTimeMillis();
f(1);
t2=System.currentTimeMillis();
System.out.println("Timp = "+(t2-t1)+" ms");
}
}// class

14.3.3 Problema reginelor pe tabla de ³ah

O problem  clasic  de generare a conguraµiilor ce respect  anumite restricµii este cea a am-
plas rii damelor pe o tabl  de ³ah astfel încât s  nu se atace reciproc.
Reprezentarea soluµiei: un vector x unde xi reprezint  coloana pe care se a  regina dac  linia
este i.
Restricµii ³i condiµii de continuare: xi j xj pentru oricare i j j (reginele nu se atac  pe
coloan ) ³i ¶xi  xj ¶ j ¶i  j ¶ (reginele nu se atac  pe diagonal ).
Sunt prezentate o variant  iterativ  ³i una recursiv .

class RegineI1
{
static int[] x;
static int n,nv=0;

public static void main(String[] args)


{
n=5;
regine(n);
}

static void regine(int n)


{
int k;
boolean ok;
x=new int[n+1];
for(int i=0;i<=n;i++) x[i]=i;
k=1;
x[k]=0;
while (k>0)
{
ok=false;
while (x[k] <= n-1) // caut o valoare posibila
{
++x[k];
ok=posibil(k);
if(ok) break;
}
if(!ok) k--;
else if (k == n) afis();
else x[++k]=0;
}
}//regine()

static boolean posibil(int k)


{
for(int i=1;i<=k-1;i++)
CAPITOLUL 14. METODA BACKTRACKING 204

if ((x[k]==x[i])||((k-i)==Math.abs(x[k]-x[i]))) return false;


return true;
}

static void afis()


{
int i;
System.out.print(++nv+" : ");
for(i=1; i<=n; i++) System.out.print(x[i]+" ");
System.out.println();
}
}// class

class RegineR1
{
static int[] x;
static int n,nv=0;

public static void main(String[] args)


{
n=5;
x=new int[n+1];
f(1);
}

static void f(int k)


{
boolean ok;
int i,j;
for(i=1;i<=n;i++)
{
ok=true;
for(j=1;j<k;j++)
if((i==x[j])||((k-j)==Math.abs(i-x[j]))) {ok=false; break;}
if(!ok) continue;
x[k]=i;
if(k<n) f(k+1); else afisv();
}
}

static void afisv()


{
int i;
System.out.print(++nv+" : ");
for(i=1; i<=n; i++) System.out.print(x[i]+" ");
System.out.println();
}
}

14.3.4 Turneul calului pe tabla de ³ah

import java.io.*;
class Calut
{
static final int LIBER=0,SUCCES=1,ESEC=0,NMAX=8;
static final int[] a={0,2,1,-1,-2,-2,-1,1,2}; // miscari posibile pe Ox
static final int[] b={0,1,2,2,1,-1,-2,-2,-1}; // miscari posibile pe Oy
static int[][] tabla=new int[NMAX+1][NMAX+1]; // tabla de sah
CAPITOLUL 14. METODA BACKTRACKING 205

static int n,np,ni=0; // np=n*n

public static void main(String[] args) throws IOException


{
BufferedReader br=new BufferedReader(
new InputStreamReader(System.in));
while ((n<3)||(n>NMAX))
{
System.out.print("Dimensiunea tablei de sah [3.."+NMAX+"] : ");
n=Integer.parseInt(br.readLine());
}
np=n*n;
for(int i=0;i<=n;i++)
for(int j=0;j<=n;j++) tabla[i][j]=LIBER;
tabla[1][1]=1;
if(incerc(2,1,1)==SUCCES) afisare();
else System.out.println("\nNu exista solutii !!!");
System.out.println("\n\nNumar de incercari = "+ni);
} // main

static void afisare()


{
System.out.println("\r----------------------------------");
for(int i=1;i<=n;i++)
{
System.out.println();
for(int j=1;j<=n;j++) System.out.print("\t"+tabla[i][j]);
}
}// afisare()

static int incerc(int i, int x, int y)


{
int xu,yu,k,rezultatIncercare; ni++;
k=1;
rezultatIncercare=ESEC;
while ((rezultatIncercare==ESEC)&&(k<=8))// miscari posibile cal
{
xu=x+a[k]; yu=y+b[k];
if((xu>=1)&&(xu<=n)&&(yu>=1)&&(yu<=n))
if(tabla[xu][yu]==LIBER)
{
tabla[xu][yu]=i;
// afisare();
if (i<np)
{
rezultatIncercare=incerc(i+1,xu,yu);
if(rezultatIncercare==ESEC) tabla[xu][yu]=LIBER;
}
else rezultatIncercare=SUCCES;
}
k++;
}// while
return rezultatIncercare;
}// incerc()
}// class

Pe ecran apar urm toarele rezultate:

Dimensiunea tablei de sah [3..8] : 5


----------------------------------
CAPITOLUL 14. METODA BACKTRACKING 206

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

Numar de incercari = 8839

14.3.5 Problema color rii h rµilor

Se consider  o hart  cu n µ ri care trebuie colorat  folosind m $ n culori, astfel încât oricare
dou  µ ri vecine s  e colorate diferit. Relaµia de vecin tate dintre µ ri este reµinut  într-o matrice
n  n ale c rei elemente sunt:

1 dac  i este vecin  cu j


vi,j w
0 dac  i nu este vecin  cu j

Reprezentarea soluµiilor: O soluµie a problemei este o modalitate de colorare a µ  rilor ³i poate


 reprezentat  printru-un vector x x1 , x2 , ..., xn  cu xi " r1, 2, ..., mx reprezentând culoarea
asociat  µ rii i. Mulµimile de valor ale elementelor sint A1 A2 ... An r1, 2, ..., mx.
Restricµii ³i condiµii de continuare: Restricµia ca dou  µ ri vecine s  e colorate diferit se
specic  prin: xi j xj pentru orice i ³i j având proprietatea vi,j 1. Condiµia de continuare
pe care trebuie s  o satisfac  soluµia parµial  x1 , x2 , ..., xk  este: xk j xi pentru orice i $ k cu
proprietatea c  vi,k 1.

class ColorareHartiI1
{
static int nrCulori=3;// culorile sunt 1,2,3
static int[][] harta=
{// CoCaIaBrTu
{2,1,1,1,1},// Constanta (0)
{1,2,1,0,0},// Calarasi (1)
{1,1,2,1,0},// Ialomita (2)
{1,0,1,2,1},// Braila (3)
{1,0,0,1,2} // Tulcea (4)
};
static int n=harta.length;
static int[] culoare=new int[n];
static int nrVar=0;

public static void main(String[] args)


{
harta();
if(nrVar==0)
System.out.println("Nu se poate colora !");
} // main

static void harta()


{
int k; // indicele pentru tara
boolean okk;
k=0; // prima pozitie
culoare[k]=0; // tara k nu este colorata (inca)
while (k>-1) // -1 = iesit in stanga !!!
{
okk=false;
while(culoare[k] < nrCulori)// selectez o culoare
{
++culoare[k];
okk=posibil(k);
CAPITOLUL 14. METODA BACKTRACKING 207

if (okk) break;
}
if (!okk)
k--;
else if (k == (n-1))
afis();
else culoare[++k]=0;
}
} // harta

static boolean posibil(int k)


{
for(int i=0;i<=k-1;i++)
if((culoare[k]==culoare[i])&&(harta[i][k]==1))
return false;
return true;
} // posibil

static void afis()


{
System.out.print(++nrVar+" : ");
for(int i=0;i<n;i++)
System.out.print(culoare[i]+" ");
System.out.println();
} // scrieRez
} // class

Pentru 3 culori rezultatele care apar pe ecran sunt:

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

class ColorareHartiR1
{
static int[] x;
static int[][] a=
{
{0,0,0,0,0,0},
{0,2,1,1,1,1},// Constanta (1)
{0,1,2,1,0,0},// Calarasi (2)
{0,1,1,2,1,0},// Ialomita (3)
{0,1,0,1,2,1},// Braila (4)
{0,1,0,0,1,2} // Tulcea (5)
};
static int n,m,nv=0;

public static void main(String[] args)


{
n=a.length-1;
m=3; // nr culori
x=new int[n+1];
f(1);
}

static void f(int k)


{
boolean ok;
CAPITOLUL 14. METODA BACKTRACKING 208

int i,j;
for(i=1;i<=m;i++)
{
ok=true;
for(j=1;j<k;j++)
if((i==x[j])&&(a[j][k]==1)) {ok=false; break;}
if(!ok) continue;
x[k]=i;
if(k<n) f(k+1); else afisv();
}
}

static void afisv()


{
System.out.print(++nv+" : ");
for(int i=1; i<=n; i++) System.out.print(x[i]+" ");
System.out.println();
}
}

14.3.6 Problema vecinilor

Un grup de n persoane sunt a³ezate pe un rând de scaune. Între oricare doi vecini izbucnesc
conicte. Rearanjaµi persoanele pe scaune astfel încât între oricare doi vecini "certaµi" s  existe
una sau cel mult dou  persoane cu care nu au apucat s  se certe! A³aµi toate variantele de
rea³ezare posibile.
Vom rezolva problema prin metada backtracking. Presupunem c  persoanele sunt numerotate
la început, de la stanga la dreapta cu 1, 2, ..., n. Consider m ca soluµie un vector x cu n componente
pentru care xi reprezint  "poziµia pe care se va aa persoana i dup  rea³ezare".
Pentru k % 1 dat, condiµiile de continuare sunt:

a) xj j xk , pentru j 1, ..., k  1 (x trebuie s  e permutare)


b) ¶xk  xk1 ¶ 2 sau 3 (între persoana k ³i vecinul s u anterior trebuie s  se ae una sau dou 
persoane)
1
În locul solutiei x vom lista permutarea y x unde yi reprezint  persoana care se aseaz  pe
locul i.

class VeciniR1
{
static int[] x;
static int n,m,nsol=0;

public static void main(String[] args)


{
n=7, m=7;
x=new int[n+1];
f(1);
}

static void f(int k)


{
boolean ok;
int i,j;
for(i=1;i<=m;i++)
{
ok=true;
for(j=1;j<k;j++) if(i==x[j]) {ok=false; break;}
if(!ok) continue;
if((Math.abs(i-x[k-1])!=2)&&(Math.abs(i-x[k-1])!=3)) continue;
CAPITOLUL 14. METODA BACKTRACKING 209

x[k]=i;
if(k<n) f(k+1); else afis();
}
}

static void afis()


{
int i;
System.out.print(++nsol+" : ");
for(i=1; i<=n; i++) System.out.print(x[i]+" ");
System.out.println();
}
}

import java.io.*;
class Vecini
{
static int nrVar=0,n;
static int[] x,y;

public static void main(String[] args) throws IOException


{
BufferedReader br=new BufferedReader(
new InputStreamReader(System.in));
while ((n<3)||(n>50))
{
System.out.print("numar persoane [3..50] : ");
n=Integer.parseInt(br.readLine());
}
x=new int[n];
y=new int[n];
reasez(0);
if(nrVar==0) System.out.println("Nu se poate !!!");
} // main

static void reasez(int k)


{
int i,j;
boolean ok;
if (k==n) scrieSolutia();// n=in afara vectorului !!!
else for(i=0;i<n;i++)
{
ok=true;
j=0;
while ((j<k-1) && ok) ok=(i != x[j++]);
if (k>0)
ok=(ok&&((Math.abs(i-x[k-1])==2)||(Math.abs(i-x[k-1])==3)));
if (ok) { x[k]=i; reasez(k+1);}
}
} // reasez

static void scrieSolutia()


{
int i;
for(i=0;i<n;i++) y[x[i]]=i;// inversarea permutarii !!!
System.out.print(++nrVar+" : ");
for(i=0;i<n;i++)
System.out.print(++y[i]+" "); // ca sa nu fie 0,1,...,n-1
System.out.println(); // ci 1,2,...,n (la afisare)
} // scrieRez
} // class
CAPITOLUL 14. METODA BACKTRACKING 210

14.3.7 Problema labirintului

Se d  un labirint sub form  de matrice cu m linii ³i n coloane. Fiecare element al matricei


reprezint  o camer  a labirintului. Într-una din camere, de coordonate x0 ³i y0 se g  se³te un om.
Se cere s  se g  seasc  toate ie³irile din labirint.
O prim  problem  care se pune este precizarea modului de codicare a ie³irilor din ecare
camer  a labirintului.
Dintr-o poziµie oarecare i, j  din labirint, deplasarea se poate face în patru direcµii: Nord, Est,
Sud ³i Vest considerate în aceast  ordine (putem alege oricare alt  ordine a celor patru direcµii,
dar odat  aleas  aceasta se p streaz  pân  la sfâr³itul problemei).
Fiecare camer  din labirint poate  caracterizat  printr-un ³ir de patru cifre binare asoci-
ate celor patru direcµii, având semnicaµie faptul c  acea camer  are sau nu ie³iri pe direcµiile
considerate.
De exemplu, dac  pentru camera cu poziµia 2, 4 exist  ie³iri la N ³i S, ei îi va corespunde
³irul 1010 care reprezint  num rul 10 în baza zece.
Prin urmare, codic m labirintul printr-o matrice aij  cu elemente între 1 ³ai 15. Pentru a
testa u³or ie³irea din labirint, matricea se bordeaz  cu dou  linii ³i dou  coloane de valoare egal 
cu 16.
Ne punem problema determin rii ie³irilor pe care le are o camer .
O camer  are ie³irea numai spre N dac  ³i numai dac  aij &&8 j 0.
Drumul parcurs la un moment dat se reµine într-o matrice cu dou  linii, d, în care:
 d1k  reprezint  linia camerei la care s-a ajuns la pasul k ;
 d2k  reprezint  coloana camerei respective.
La g sirea unei ie³iri din labirint, drumul este a³at.
Principiul algoritmului este urm torul:
 se testeaz  dac  s-a ie³it din labiritn (adic  aij  16);
 în caz armativ se a³eaz  drumul g sit;
 în caz contrar se procedeaz  astfel:
a se reµin în matricea d coordonatele camerei vizitate;
a se veric  dac  drumul arcurs a mai trecut prin aceast  camer , caz în care se iese din
procedur ;
a se testeaz  pe rând ie³irile spre N, E, S, V ³i acolo unde este g sit  o astfel de ie³ire se
reapeleaz  procedura cu noile coordonate;
a înaintea ie³irii din procedur  se decrementeaz  valoarea lui k .

import java.io.*;
class Labirint
{
static final char coridor=’.’, start=’x’,
gard=’H’, pas=’*’, iesire=’E’;
static char[][] l;
static int m,n,x0,y0;
static boolean ok;

public static void main(String[] args) throws IOException


{
int i,j,k;
StreamTokenizer st = new StreamTokenizer(
new BufferedReader(new FileReader("labirint.in")));
st.nextToken(); m=(int)st.nval;
st.nextToken(); n=(int)st.nval;
l=new char[m][n];
for(i=0;i<m;i++)
{
st.nextToken();
for(j=0;j<n;j++) l[i][j]=st.sval.charAt(j);
}

st.nextToken(); x0=(int)st.nval;
st.nextToken(); y0=(int)st.nval;
CAPITOLUL 14. METODA BACKTRACKING 211

ok=false;
gi(x0,y0);
l[x0][y0]=start;
PrintWriter out = new PrintWriter(
new BufferedWriter(new FileWriter("labirint.out")));
for(i=0;i<m;i++)
{
if (i>0) out.println();
for(j=0;j<n;j++) out.print(l[i][j]);
}
if (!ok) out.println("NU exista iesire !");
out.close();
}// main()

static void gi(int x,int y)


{
if ((x==0)||(x==n-1)||(y==0)||(y==m-1)) ok=true;
else
{
l[x][y]=pas;
if(l[x][y+1]==coridor||l[x][y+1]==iesire) gi(x,y+1);
if(!ok&&(l[x+1][y]==coridor||l[x+1][y]==iesire)) gi(x+1,y);
if(!ok&&(l[x][y-1]==coridor||l[x][y-1]==iesire)) gi(x,y-1);
if(!ok&&(l[x-1][y]==coridor||l[x-1][y]==iesire)) gi(x-1,y);
l[x][y]=coridor;
}
if (ok) l[x][y]=pas;
}
}// class

De exemplu, pentru sierul de intrare: labirint.in


8 8
HHHHHHEH
H....H.H
H.HHHH.H
H.HHHH.H
H....H.H
H.HHHH.H
H......H
HHHHHHEH
2 2
se obµine ³ierul de ie³ire: labirint.out
HHHHHHEH
H....H.H
H*xHHH.H
H*HHHH.H
H*...H.H
H*HHHH.H
H******H
HHHHHH*H

14.3.8 Generarea partiµiilor unui num r natural

S  se a³eze toate modurile de descompunere a unui num r natural n ca sum  de numere


naturale.
Vom folosi o procedur  f care are doi parametri: componenta la care s-a ajuns (k ) ³i o valoare
v (care conµine diferenµa care a mai r mas pân  la n).
CAPITOLUL 14. METODA BACKTRACKING 212

Iniµial, procedura este apelat  pentru nivelul 1 ³i valoarea n. Imediat ce este apelat , procedura
va apela o alta pentru a³area vectorului (iniµial a³eaz  n).
Din valoarea care se g se³te pe un nivel, S k , se scad pe rând valorile 1, 2, ..., S k   1, valori
cu care se apeleaz  procedura pentru nivelul urm tor.
La revenire se reface valoarea existent .

class PartitieNrGenerare // n = suma de numere


{
static int dim=0, nsol=0, n=6;
static int[] x=new int[n+1];

public static void main(String[] args)


{
long t1,t2;
t1=System.currentTimeMillis();
f(n,n,1);
t2=System.currentTimeMillis();
System.out.println("nsol = "+nsol+" timp = "+(t2-t1)+"\n");
}

static void f(int val, int maxp, int poz)


{
if(maxp==1) { nsol++; dim=poz-1; afis2(val,maxp); return; }
if(val==0) { nsol++; dim=poz-1; afis1(); return; }
int maxok=min(maxp,val);
for(int i=maxok;i>=1;i--)
{
x[poz]=i;
f(val-i,min(val-i,i),poz+1);
}
}

static void afis1()


{
System.out.print("\n"+nsol+" : ");
for(int i=1;i<=dim;i++) System.out.print(x[i]+" ");
}

static void afis2(int val,int maxip)


{
int i;
System.out.print("\n"+nsol+" : ");
for(i=1;i<=dim;i++) System.out.print(x[i]+" ");
for(i=1;i<=val;i++) System.out.print("1 ");
}

static int min(int a,int b) { return (a<b)?a:b; }


}

Pentru descompunerea ca sum  de numere impare, programul este:

class PartitieNrImpare1 // n = suma de numere impare


{ // nsol = 38328320 1Gata!!! timp = 8482
static int dim=0,nsol=0;
static int[] x=new int[161];

public static void main(String[] args)


{
long t1,t2;
int n=160, maxi=((n-1)/2)*2+1;
t1=System.currentTimeMillis();
CAPITOLUL 14. METODA BACKTRACKING 213

f(n,maxi,1);
t2=System.currentTimeMillis();
System.out.println("nsol = "+nsol+" timp = "+(t2-t1)+"\n");
}

static void f(int val, int maxip, int poz)


{
if(maxip==1)
{
nsol++;
// dim=poz-1;
// afis2(val,maxip);
return;
}
if(val==0)
{
nsol++;
// dim=poz-1; afis1();
return;
}
int maxi=((val-1)/2)*2+1;
int maxiok=min(maxip,maxi);
for(int i=maxiok;i>=1;i=i-2)
{
x[poz]=i;
f(val-i,min(maxiok,i),poz+1);
}
}

static void afis1()


{
System.out.print("\n"+nsol+" : ");
for(int i=1;i<=dim;i++) System.out.print(x[i]+" ");
}

static void afis2(int val,int maxip)


{
int i;
System.out.print("\n"+nsol+" : ");
for(i=1;i<=dim;i++) System.out.print(x[i]+" ");
for(i=1;i<=val;i++) System.out.print("1 ");
}

static int max(int a,int b) { return (a>b)?a:b; }


static int min(int a,int b) { return (a<b)?a:b; }
}// class

O versiune optimizat  este:

class PartitieNrImpare2 // n = suma de numere impare ;


{ // optimizat timp la jumatate !!!
static int dim=0,nsol=0; // nsol = 38328320 2Gata!!! timp = 4787
static int[] x=new int[161];

public static void main(String[] args)


{
long t1,t2;
int n=160, maxi=((n-1)/2)*2+1;
t1=System.currentTimeMillis();
f(n,maxi,1);
t2=System.currentTimeMillis();
CAPITOLUL 14. METODA BACKTRACKING 214

System.out.println("nsol = "+nsol+" timp= "+(t2-t1));


}

static void f(int val, int maxip, int poz)


{
if(maxip==1)
{
nsol++;
// dim=poz-1; afis2(val);
return;
}
if(val==0)
{
nsol++;
// dim=poz-1; afis1();
return;
}

int maxi=((val-1)/2)*2+1;
int maxiok=min(maxip,maxi);
for(int i=maxiok;i>=3;i=i-2)
{
x[poz]=i;
f(val-i,i,poz+1);
}
nsol++;
// dim=poz-1;
// afis2(val);
}

static void afis1()


{
System.out.print("\n"+nsol+" : ");
for(int i=1;i<=dim;i++) System.out.print(x[i]+" ");
}

static void afis2(int val)


{
System.out.print("\n"+nsol+" : ");
for(int i=1;i<=dim;i++) System.out.print(x[i]+" ");
for(int i=1;i<=val;i++) System.out.print("1 ");
}

static int max(int a,int b) { return (a>b)?a:b; }


static int min(int a,int b) { return (a<b)?a:b; }
}// class

14.3.9 Problema parantezelor

Problema cere generarea tuturor combinaµiilor de 2n paranteze (n paranteze de deschidere ³i


n paranteze de închidere) care se închid corect.

class ParantezeGenerare // 2*n paranteze


{
static int nsol=0;
static int n=4;
static int n2=2*n;
static int[] x=new int[n2+1];

public static void main(String[] args)


CAPITOLUL 14. METODA BACKTRACKING 215

{
long t1,t2;
t1=System.currentTimeMillis();
f(1,0,0);
t2=System.currentTimeMillis();
System.out.println("nsol = "+nsol+" timp = "+(t2-t1)+"\n");
}

static void f(int k, int npd, int npi)


{
if(k>n2) afis();
else
{
if(npd<n) { x[k]=1; f(k+1,npd+1,npi); }
if(npi<npd) { x[k]=2; f(k+1,npd,npi+1); }
}
}

static void afis()


{
int k;
System.out.print(++nsol+" : ");
for(k=1;k<=n2;k++)
if(x[k]==1) System.out.print("( ");
else System.out.print(") ");
System.out.println();
}
}// class

14.3.10 Algoritmul DFS de parcurgere a grafurilor

Algoritmul DFS de parcurgerea in adâncime a grafurilor neorientate foloseste tehnica backtra-


ckink.

import java.io.*; // arborele DFS (parcurgere in adancime)


class DFS // momentele de descoperire si finalizare a nodurilor
{ // drum intre doua varfuri
static final int WHITE=0, GRAY=1, BLACK=2;
static int n,m,t;
static int[] d,f,p,color; // descoperit,finalizat,predecesor,culoare
static int[][] a;

public static void main(String[] args) throws IOException


{
StreamTokenizer st=new StreamTokenizer(
new BufferedReader(new FileReader("dfs.in")));

int i,j,k,nods,nodd; // nods=nod_start_DFS, nod_destinatie (drum!)

st.nextToken(); n=(int)st.nval;
st.nextToken(); m=(int)st.nval;
st.nextToken(); nods=(int)st.nval;
st.nextToken(); nodd=(int)st.nval;

a=new int[n+1][n+1];
d=new int[n+1];
f=new int[n+1];
p=new int[n+1];
color=new int[n+1];
CAPITOLUL 14. METODA BACKTRACKING 216

for(k=1;k<=m;k++)
{
st.nextToken(); i=(int)st.nval;
st.nextToken(); j=(int)st.nval;
a[i][j]=1;
a[j][i]=1;
}

for(i=1;i<=n;i++) // oricum erau initializati implicit, dar ... !!!


{
color[i]=WHITE;
p[i]=-1;
}

t=0;
dfs(nods);

System.out.print("drum : "); drum(nodd); System.out.println();


System.out.print("Descoperit :\t"); afisv(d);
System.out.print("Finalizat :\t"); afisv(f);
}//main

static void dfs(int u)


{
int v;
color[u]=GRAY;
d[u]=++t;
for(v=1;v<=n;v++) // listele de adiacenta ... !!!
if(a[u][v]==1) // v in Ad(u) !!!
if(color[v]==WHITE)
{
p[v]=u;
dfs(v);
}
color[u]=BLACK;
f[u]=++t;
}//dfs

static void drum(int u) // nod_sursa ---> nod_destinatie


{
if(p[u]!=-1) drum(p[u]);
System.out.print(u+" ");
}// drum(...)

static void afisv(int[] x)


{
int i;
for(i=1;i<=n;i++) System.out.print(x[i]+"\t");
System.out.println();
}
}//class

/*
6 7 3 4 drum : 3 1 4
1 4 Descoperit : 2 5 1 3 4 8
4 6 Finalizat : 11 6 12 10 7 9
6 1
5 3
2 5
1 3
CAPITOLUL 14. METODA BACKTRACKING 217

4 5
*/

14.3.11 Determinarea componentelor conexe


import java.io.*; // determinarea componentelor conexe
class CompConexe
{
static int n,m,ncc;
static int [] cc;
static int[][] a;

public static void main(String[] args) throws IOException


{
int i,j,k;
StreamTokenizer st=new StreamTokenizer(
new BufferedReader(new FileReader("compConexe.in")));

st.nextToken(); n=(int)st.nval;
st.nextToken(); m=(int)st.nval;

a=new int[n+1][n+1];

for(k=1;k<=m;k++)
{
st.nextToken(); i=(int)st.nval;
st.nextToken(); j=(int)st.nval;
a[i][j]=a[j][i]=1;
}

cc=new int[n+1];

ncc=0;
for(i=1;i<=n;i++)
if(cc[i]==0)
{
ncc++;
conex(i);
}

for(i=1;i<=ncc;i++)
{
System.out.print(i+" : ");
for(j=1;j<=n;j++)
if(cc[j]==i)
System.out.print(j+" ");
System.out.println();
}
}//main

static void conex(int u)


{
cc[u]=ncc;
for(int v=1;v<=n;v++)
if((a[u][v]==1)&&(cc[v]==0))
conex(v);
}//conex
}//class

/*
9 7 1 : 1 2 3
CAPITOLUL 14. METODA BACKTRACKING 218

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

14.3.12 Determinarea componentelor tare conexe


// determinarea componentelor tare conexe (in graf orientat!)
// Algoritm: 1. dfs(G) pentru calculul f[u]
// 2. dfs(G_transpus) in ordinea descrescatoare a valorilor f[u]
// OBS: G_transpus are arcele din G "intoarse ca sens"
// Lista este chiar o sortare topologica !!!

import java.io.*;
class CompTareConexe
{
static final int WHITE=0, GRAY=1, BLACK=2;
static int n,m,t=0,nctc,pozLista;
static int [] ctc,f,color,lista;
static int[][] a; // matricea grafului
static int[][] at; // matricea grafului transpus (se poate folosi numai a !)

public static void main(String[] args) throws IOException


{
int i,j,k;
StreamTokenizer st=new StreamTokenizer(
new BufferedReader(new FileReader("compTareConexe.in")));

st.nextToken(); n=(int)st.nval;
st.nextToken(); m=(int)st.nval;

a=new int[n+1][n+1]; at=new int[n+1][n+1]; ctc=new int[n+1];


f=new int[n+1]; lista=new int[n+1]; color=new int[n+1];

for(k=1;k<=m;k++)
{
st.nextToken(); i=(int)st.nval;
st.nextToken(); j=(int)st.nval;
a[i][j]=1;
at[j][i]=1; // transpusa
}

for(i=1;i<=n;i++) color[i]=WHITE;

pozLista=n;
for(i=1;i<=n;i++) if(color[i]==WHITE) dfsa(i);

nctc=0;
for(i=1;i<=n;i++) color[i]=WHITE;
for(i=1;i<=n;i++)
if(color[lista[i]]==WHITE) { nctc++; dfsat(lista[i]); }

for(i=1;i<=nctc;i++)
{
System.out.print(i+" : ");
for(j=1;j<=n;j++) if(ctc[j]==i) System.out.print(j+" ");
System.out.println();
CAPITOLUL 14. METODA BACKTRACKING 219

}
}//main

static void dfsa(int u)


{
int v;
color[u]=GRAY;
for(v=1;v<=n;v++) if((a[u][v]==1)&&(color[v]==WHITE)) dfsa(v);
color[u]=BLACK; f[u]=++t; lista[pozLista--]=u;
}

static void dfsat(int u) // se poate folosi "a" inversand arcele !


{
int j;
color[u]=GRAY;
ctc[u]=nctc;
for(j=1;j<=n;j++)
if((at[u][lista[j]]==1)&&(color[lista[j]]==WHITE)) dfsat(lista[j]); //"at"
//if((a[lista[j]][u]==1)&&(color[lista[j]]==WHITE)) dfsat(lista[j]); //"a"
color[u]=BLACK;
}
}//class

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

14.3.13 Sortare topologic 


Folosind parcurgerea în adâncime
// Sortare Topologica = ordonare lineara a varfurilor (in digraf)
// (u,v)=arc ==> "... u ... v ..." in lista ("u se termina inaintea lui v")
// Algoritm: 1. DFS pentru calcul f[u], u=nod
// 2. cand u=terminat ==> plasaz in lista pe prima pozitie libera
// de la sfarsit catre inceput
// Solutia nu este unica (cea mai mica lexicografic = ???)
// O(n*n)= cu matrice de adiacenta
// O(n+m)= cu liste de adiacenta

import java.io.*;
class SortTopoDFS
{
static final int WHITE=0, GRAY=1, BLACK=2;
static int n,m,t,pozl; // varfuri, muchii, time, pozitie in lista
static int[] d; // descoperit
static int[] f; // finalizat
static int[] color; // culoare
static int[] lista; // lista
static int[][] a; // matricea de adiacenta
CAPITOLUL 14. METODA BACKTRACKING 220

public static void main(String[] args) throws IOException


{
int i,j,k,nods; // nods=nod_start_DFS
StreamTokenizer st=new StreamTokenizer(
new BufferedReader(new FileReader("sortTopo.in")));

st.nextToken(); n=(int)st.nval;
st.nextToken(); m=(int)st.nval;

a=new int[n+1][n+1]; d=new int[n+1]; f=new int[n+1];


color=new int[n+1]; lista=new int[n+1];

for(k=1;k<=m;k++)
{
st.nextToken(); i=(int)st.nval;
st.nextToken(); j=(int)st.nval;
a[i][j]=1;
}

for(i=1;i<=n;i++) color[i]=WHITE;
t=0;
pozl=n;
for(nods=1;nods<=n;nods++)
if(color[nods]==WHITE) dfs(nods);

for(i=1;i<=n;i++) System.out.print(lista[i]+" ");


System.out.println();
}//main

static void dfs(int u)


{
int v;
color[u]=GRAY;
d[u]=++t;
for(v=1;v<=n;v++) // mai bine cu liste de adiacenta ... !!!
if(a[u][v]==1) // v in Ad(u) !!!
if(color[v]==WHITE) dfs(v);
color[u]=BLACK;
f[u]=++t;
lista[pozl]=u;
--pozl;
}//dfs
}//class

/*
6 4 5 6 3 4 1 2
6 3
1 2
3 4
5 6
*/

Folosind gradele interioare


// Sortare Topologica = ordonare lineara a varfurilor (in digraf)
// (u,v)=arc ==> "... u ... v ..." in lista ("u se termina inaintea lui v")
// Algoritm: cat_timp exista noduri neplasate in lista
// 1. aleg un nod u cu gi[u]=0 (gi=gradul interior)
// 2. u --> lista (pe cea mai mica pozitie neocupata)
// 3. decrementez toate gi[v], unde (u,v)=arc
// OBS: pentru prima solutie lexicografic: aleg u="cel mai mic" (heap!)
CAPITOLUL 14. METODA BACKTRACKING 221

// OBS: Algoritm="stergerea repetata a nodurilor de grad zero"

import java.io.*;
class SortTopoGRAD
{
static final int WHITE=0, BLACK=1; // color[u]=BLACK ==> u in lista
static int n,m,pozl; // varfuri, muchii, pozitie in lista
static int[] color; // culoare
static int[] lista; // lista
static int[] gi; // grad interior
static int[][] a; // matricea de adiacenta

public static void main(String[] args) throws IOException


{
int u,i,j,k;
StreamTokenizer st=new StreamTokenizer(
new BufferedReader(new FileReader("sortTopo.in")));

st.nextToken(); n=(int)st.nval;
st.nextToken(); m=(int)st.nval;

a=new int[n+1][n+1];
color=new int[n+1];
lista=new int[n+1];
gi=new int[n+1];

for(k=1;k<=m;k++)
{
st.nextToken(); i=(int)st.nval;
st.nextToken(); j=(int)st.nval;
a[i][j]=1;
gi[j]++;
}

for(i=1;i<=n;i++) color[i]=WHITE;
pozl=1;
for(k=1;k<=n;k++) // pun cate un nod in lista
{
u=nodgi0();
micsorezGrade(u);
lista[pozl++]=u;
color[u]=BLACK;
}

for(i=1;i<=n;i++) System.out.print(lista[i]+" ");


System.out.println();
}//main

static int nodgi0() // nod cu gradul interior zero


{
int v,nod=-1;
for(v=1;v<=n;v++) // coada cu prioritati (heap) este mai buna !!!
if(color[v]==WHITE)
if(gi[v]==0) {nod=v; break;}
return nod;
}

static void micsorezGrade(int u)


{
int v;
CAPITOLUL 14. METODA BACKTRACKING 222

for(v=1;v<=n;v++) // lista de adiacenta este mai buna !!!


if(color[v]==WHITE)
if(a[u][v]==1) gi[v]--;
}
}//class

/*
6 4 1 2 5 6 3 4
6 3
1 2
3 4
5 6
*/

14.3.14 Determinarea nodurilor de separare


import java.io.*; // determinarea nodurilor care strica conexitatea
class NoduriSeparare // in graf neorientat conex
{
static int n,m;
static int [] cc;
static int[][] a;

public static void main(String[] args) throws IOException


{
int i,j,k;
StreamTokenizer st=new StreamTokenizer(
new BufferedReader(new FileReader("noduriSeparare.in")));
PrintWriter out=new PrintWriter(
new BufferedWriter(new FileWriter("noduriSeparare.out")));
st.nextToken(); n=(int)st.nval;
st.nextToken(); m=(int)st.nval;
a=new int[n+1][n+1];
for(k=1;k<=m;k++)
{
st.nextToken(); i=(int)st.nval;
st.nextToken(); j=(int)st.nval;
a[i][j]=a[j][i]=1;
}
for(i=1;i<=n;i++) if(!econex(i)) System.out.print(i+" ");
out.close();
}//main

static boolean econex(int nodscos)


{
int i, ncc=0;
int[] cc=new int[n+1];

for(i=1;i<=n;i++)
if(i!=nodscos)
if(cc[i]==0)
{
ncc++;
if(ncc>1) break;
conex(i,ncc,cc,nodscos);
}
if(ncc>1) return false; else return true;
}// econex()

static void conex(int u,int et,int[]cc,int nodscos)


{
CAPITOLUL 14. METODA BACKTRACKING 223

cc[u]=et;
for(int v=1;v<=n;v++)
if(v!=nodscos)
if((a[u][v]==1)&&(cc[v]==0)) conex(v,et,cc,nodscos);
}//conex
}//class

14.3.15 Determinarea muchiilor de rupere


import java.io.*; // determinarea muchiilor care strica conexitatea
class MuchieRupere // in graf neorientat conex
{
static int n,m; static int [] cc; static int[][]a;

public static void main(String[] args) throws IOException


{
int i,j,k;
StreamTokenizer st=new StreamTokenizer(
new BufferedReader(new FileReader("muchieRupere.in")));
PrintWriter out=new PrintWriter(
new BufferedWriter(new FileWriter("muchieRupere.out")));
st.nextToken(); n=(int)st.nval;
st.nextToken(); m=(int)st.nval;
a=new int[n+1][n+1];
for(k=1;k<=m;k++)
{
st.nextToken(); i=(int)st.nval;
st.nextToken(); j=(int)st.nval;
a[i][j]=1; a[j][i]=1;
}
for(i=1;i<=n;i++)
for(j=i+1;j<=n;j++)
{
if(a[i][j]==0) continue;
a[i][j]=a[j][i]=0;
if(!econex()) System.out.println(i+" "+j);
a[i][j]=a[j][i]=1;
}
out.close();
}//main

static boolean econex()


{
int i, ncc;
cc=new int[n+1];
ncc=0;
for(i=1;i<=n;i++)
{
if(cc[i]==0)
{
ncc++;
if(ncc>1) break;
conex(i,ncc);
}
}
if(ncc==1) return true; else return false;
}// econex()

static void conex(int u,int et)


{
cc[u]=et;
CAPITOLUL 14. METODA BACKTRACKING 224

for(int v=1;v<=n;v++)
if((a[u][v]==1)&&(cc[v]==0))
conex(v,et);
}//conex
}//class

/*
9 10 1 8
7 2 2 7
5 1 3 9
1 8 7 9
9 4
6 9
6 4
4 1
9 5
9 7
9 3
*/

14.3.16 Determinarea componentelor biconexe


// Componenta biconexa = componenta conexa maximala fara muchii de rupere
import java.io.*; // noduri = 1,...,n
class Biconex // liste de adiacenta pentru graf
{
// vs=varf stiva; m=muchii; ncb=nr componente biconexe
// ndr=nr descendenti radacina (in arbore DFS), t=time in parcurgerea DFS

static final int WHITE=0, GRAY=1,BLACK=2;


static int n,ncb,t,ndr,vs,m=0,root; // root=radacina arborelui DFS
static int[][] G; // liste de adiacenta
static int[] grad,low,d; // grad nod, low[], d[u]=moment descoperire nod u

static int[][] B; // componente biconexe


static int[] A; // puncte de articulare
static int[] color; // culoarea nodului
static int[] fs,ts; // fs=fiu stiva; ts=tata stiva

public static void main(String[] args) throws IOException


{
init();
root=3; // radacina arborelui (de unde declansez DFS)
vs=0; // pozitia varfului stivei unde este deja incarcat un nod (root)
fs[vs]=root; // pun in stiva "root" si
ts[vs]=0; // tata_root=0 (nu are!)
t=0; // initializare time; numerotarea nodurilor in DFS

dfs(root,0); // (u,tatau) tatau=0 ==> nu exista tatau


if(ncb==1) System.out.println("Graful este Biconex");
else
{
System.out.println("Graful NU este Biconex");
if(ndr>1) A[root]=1;
System.out.print("Puncte de articulare : ");
afisv(A);
System.out.print("Numar componente Biconexe : ");
System.out.println(ncb);
for(int i=1;i<=ncb;i++)
{
System.out.print("Componenta Biconexa "+i+" : ");
CAPITOLUL 14. METODA BACKTRACKING 225

afisv(B[i]);
}
}
}//main()

static int minim(int a, int b) { return a<b?a:b; } // minim()

static void init() throws IOException


{
int i,j,k;
StreamTokenizer st=new StreamTokenizer(
new BufferedReader(new FileReader("biconex.in")));
st.nextToken(); n=(int)st.nval;
st.nextToken(); m=(int)st.nval;

d=new int[n+1]; // vectorii sunt initializati cu zero


low=new int[n+1]; grad=new int[n+1]; color=new int[n+1]; // 0=WHITE !
A=new int[n+1]; G=new int[n+1][n+1]; B=new int[n+1][n+1];
fs=new int[m+1]; ts=new int[m+1];

for(k=1;k<=m;k++)
{
st.nextToken(); i=(int)st.nval;
st.nextToken(); j=(int)st.nval;
G[i][++grad[i]]=j; G[j][++grad[j]]=i;
}
}//Init()

static void dfs(int u, int tatau) /* calculeaza d si low */


{
int fiuu,i;

d[u]=++t;
color[u]=GRAY;
low[u]=d[u];

for(i=1;i<=grad[u];i++)
{
fiuu=G[u][i]; // fiuu = un descendent al lui u
if(fiuu==tatau) continue; // este aceeasi muchie
if((color[fiuu]==WHITE)|| // fiuu nedescoperit sau
(d[fiuu]<d[u])) // (u,fiuu) este muchie de intoarcere
{
/* insereaza in stiva muchia (u,fiuu) */
vs++;
fs[vs]=fiuu;
ts[vs]=u;
}
if(color[fiuu]==WHITE) /* fiuu nu a mai fost vizitat */
{
if(u==root) ndr++; // root=caz special (maresc nrfiiroot)

dfs(fiuu,u);

// acum este terminat tot subarborele cu radacina fiuu !!!


low[u]=minim(low[u],low[fiuu]);
if(low[fiuu]>=d[u])
// "=" ==> fiuu intors in u ==> ciclu "agatat" in u !!!
// ">" ==> fiuu nu are drum de rezerva !!!
{
CAPITOLUL 14. METODA BACKTRACKING 226

/* u este un punct de articulatie; am identificat o componenta


biconexa ce contine muchiile din stiva pana la (u,fiuu) */
if(low[fiuu]!=low[u]) // (u,fiuu) = bridge (pod)
System.out.println("Bridge: "+fiuu+" "+u);
if(u!=root) A[u]=1; // root = caz special
compBiconexa(fiuu,u);
}
}
else // (u,fiuu) = back edge
low[u]=minim(low[u],d[fiuu]);
}
color[u]=BLACK;
} // dfs(...)

static void compBiconexa(int fiu, int tata)


{
int tatas,fius;
ncb++;
do
{
tatas=ts[vs]; fius=fs[vs];
vs--;
B[ncb][tatas]=1; B[ncb][fius]=1;
} while(!((tata==tatas)&&(fiu==fius)));
} // compBiconexa(...)

static void afisv(int[] x) // indicii i pentru care x[i]=1;


{
for(int i=1;i<=n;i++) if(x[i]==1) System.out.print(i+" ");
System.out.println();
}// afisv(...)
}//class

/*
8 9 <-- n m Bridge: 8 1
1 8 8 Bridge: 5 3
1 2 | Graful NU este Biconex
1 3 6 1 Puncte de articulare : 1 3 5
3 4 | \ / \ Numar componente Biconexe : 4
2 4 | 5 --- 3 2 Componenta Biconexa 1 : 1 8
3 5 | / \ / Componenta Biconexa 2 : 1 2 3 4
5 7 7 4 Componenta Biconexa 3 : 5 6 7
5 6 Componenta Biconexa 4 : 3 5
6 7
*/

14.3.17 Triangulaµii - OJI2002 clasa a X-a

O triangulaµie a unui poligon convex este o mulµime format  din diagonale ale poligonului
care nu se intersecteaz  în interiorul poligonului ci numai în vârfuri ³i care împart toat  suprafaµa
poligonului în triunghiuri.
Fiind dat un poligon cu n vârfuri notate 1, 2, ..., n s  se genereze toate triangulaµiile distincte
ale poligonului. Dou  triangulaµii sunt distincte dac  difer  prin cel puµin o diagonal .
Datele de intrare: în ³ierul text triang.in se a  pe prima linie un singur num r natural
reprezentând valoarea lui n (n & 11).
Datele de ie³ire: în ³ierul text triang.out se vor scrie:
- pe prima linie, num rul de triangulaµii distincte;
- pe ecare din urm toarele linii câte o triangulaµie descris  prin diagonalele ce o compun. O
diagonal  va  precizat  prin dou  numere reprezentând cele dou  vârfuri care o denesc; cele
CAPITOLUL 14. METODA BACKTRACKING 227

dou  numere ce denesc o diagonal  se despart prin cel puµin un spaµiu, iar între perechile de
numere ce reprezint  diagonalele dintr-o triangulaµie se va l sa de asemenea minimum un spaµiu.
Exemplu
triang.in triang.out
5 5
1 3 1 4
2 4 2 5
5 2 5 3
3 5 3 1
4 2 1 4

Timp maxim de executare:


7 secunde/test pe un calculator la 133 MHz.
3 secunde/test pe un calculator la peste 500 MHz.

Rezolvare detaliat 

Se genereaza toate combinatiile de diagonale care formeaza o triangulaµie.

import java.io.*; // merge si n=12 in 3 sec


class Triangulatii
{
static int n; // numar varfuri poligon
static int ndt=n-3; // numar diagonale in triangulatie
static int nd=n*(n-3)/2; // numarul tuturor diagonalelor
static int[] x;
static int[] v1,v2;
static int nsol=0;
static PrintWriter out;

public static void main(String[] args) throws IOException


{
long t1,t2;
t1=System.currentTimeMillis();
StreamTokenizer st=new StreamTokenizer(
new BufferedReader(new FileReader("triang.in")));
out=new PrintWriter(new BufferedWriter(new FileWriter("triang.out")));
st.nextToken(); n=(int)st.nval;
ndt=n-3;
nd=n*(n-3)/2;
x=new int[ndt+1];
v1=new int[nd+1];
v2=new int[nd+1];

if(n==3) out.println(0);
else
{
out.println(catalan(n-2));
diagonale();
f(1);
}
out.close();
t2=System.currentTimeMillis();
System.out.println("nsol = "+nsol+" Timp = "+(t2-t1));
}
CAPITOLUL 14. METODA BACKTRACKING 228

static void afisd() throws IOException


{
int i;
++nsol;
for(i=1;i<=ndt;i++) out.print(v1[x[i]]+" "+v2[x[i]]+" ");
out.println();
}

static void diagonale()


{
int i,j,k=0;
i=1;
for(j=3;j<=n-1;j++) {v1[++k]=i; v2[k]=j;}

for(i=2;i<=n-2;i++)
for(j=i+2;j<=n;j++){v1[++k]=i; v2[k]=j;}
}

static boolean seIntersecteaza(int k, int i)


{
int j; // i si x[j] sunt diagonalele !!!
for(j=1;j<=k-1;j++)
if(((v1[x[j]]<v1[i])&&(v1[i]<v2[x[j]])&&(v2[x[j]]<v2[i]) )||
((v1[i]<v1[x[j]])&&(v1[x[j]]<v2[i])&&(v2[i]<v2[x[j]])))
return true;
return false;
}

static void f(int k) throws IOException


{
int i;
for(i=x[k-1]+1; i<=nd-ndt+k; i++)
{
if(seIntersecteaza(k,i)) continue;
x[k]=i;
if(k<ndt) f(k+1); else afisd();
}
}

static int catalan(int n)


{
int rez;
int i,j;
int d;
int[] x=new int[n+1];
int[] y=new int[n+1];

for(i=2;i<=n;i++) x[i]=n+i;
for(j=2;j<=n;j++) y[j]=j;

for(j=2;j<=n;j++)
for(i=2;i<=n;i++)
{
d=cmmdc(y[j],x[i]);
y[j]=y[j]/d;
x[i]=x[i]/d;
if(y[j]==1) break;
}
rez=1;
CAPITOLUL 14. METODA BACKTRACKING 229

for(i=2;i<=n;i++) rez*=x[i];
return rez;
}

static int cmmdc (int a,int b)


{
int d,i,c,r;
if(a>b) {d=a;i=b;} else{d=b;i=a;}
while(i!=0) {c=d/i; r=d%i; d=i; i=r;}
return d;
}
}// class

14.3.18 Partiµie - ONI2003 clasa a X-a

Se dene³te o partiµie a unui num r natural n ca ind o scriere a lui n sub forma:

n n1  n2  ...  nk , k ' 1
unde n1 , n2 , ..., nk sunt numere naturale care veric  urm toarea relaµie:

n1 ' n2 ' ... ' ni ' ... ' nk ' 1

Cerinµ 
Fiind dat un num r natural n, s  se determine câte partiµii ale lui se pot scrie, conform
cerinµelor de mai sus, ³tiind c  oricare num r ni dintr-o partiµie trebuie s  e un num r impar.
Datele de intrare
Fi³ierul partitie.in conµine pe prima linie num rul n
Datele de ie³ire
Fi³erul partitie.out va conµine pe prima linie num rul de partiµii ale lui n conform cerinµelor
problemei.
Restricµii ³i preciz ri
a 1&N & 160
Exemplu
partitie.in partitie.out
7 5
Explicaµii:
Cele cinci partiµii sunt:
a 1+1+1+1+1+1+1
a 1+1+1+1+3
a 1+1+5
a 1+3+3
a 7

Timp maxim de executare: 3 secunde/test

Indicaµii de rezolvare *

Stelian Ciurea
Problema se poate rezolva în mai multe moduri:
Soluµia comisiei se bazeaz  pe urm toarele formule ³i teoreme de combinatoric :
 num rul de partiµii ale unui num r n în k p rµi (nu neap rat distincte) este

P n, k  P n  k, 1  P n  k, 2  ...  P n  k, k 

cu P n, 1 P n, n 1 ³i P n, k  0 dac  n $ k .
 num rul de partiµii ale unui num  n în k p rµi distincte este

P n, k  P n  k k  1©2, k 
CAPITOLUL 14. METODA BACKTRACKING 230

 num rul de partiµii ale unui num r n în k p rµi impare care se pot ³i repeta este egal cu
num rul de partiµii ale unui num r n în k p rµi distincte.
Problema se poate rezolva ³i prin backtracking; f r  prea mari optimiz ri se poate obµine
rezultatul în mai puµin de 3 secunde pentru n $ 120. Pentru valori mai mari ale lui n, se poate
l sa programul s  ruleze ³i se reµin rezultatele într-un vector cu valori iniµiale.
Rezultatul pentru n 160 are 8 cifre, deci nu este necesar  implementarea operaµiilor cu
numere mari!

Mihai Stroe, GInfo nr. 13/6 - octombrie 2003


Problema se poate rezolva în mai multe moduri.
O idee bun  de rezolvare a problemei constã în folosirea metodei program rii dinamice. Se
poate construi o matrice A, unde Ai,k reprezint  num rul de partiµii ale num rului i cu numere
impare, din care ultimul este cel mult egal cu k . Un element din A se calculeaz  observând c  o
partiµie a lui i cu numere impare, cel mult egale cu k , este format  dintr-un un num r M , mai
mic sau egal cu k , ³i alte numere, mai mici sau egale cu M . De aici rezult  relaµia:

Ai,k =
M 1,...,k;M &i;
AiM,M
M impar

Iniµial A0,0 este 1. La implementare, pentru a economisi spaµiu, se poate alege o variant  în
care Ai,k s  reprezinte num rul de partiµii ale lui i cu numere impare, din care ultimul este cel
mult egal cu al k -lea num r impar, adic  2 k  1.
Dup  calcularea elementelor matricei, soluµia pentru num ul i se g se³te în Ai,i . Aceste valori
pot  salvate întrun vector de constante, care este transformat într-un nou program, în acela³i mod
ca la problema "Circular" de la clasa a IX-a. Aceast  metod  conduce la o rezolvare instantanee
a testelor date în concurs.
O alt  metod , bazat  pe vectorul de constante, ar  însemnat generarea soluµiilor folosind
metoda backtracking. Un backtracking l sat s  se execute în timpul concursului ar  putut genera
soluµiile pentru toate valorile lui N , dup  care se putea folosi metoda vectorului de constante, în
timp ce un backtracking folosit ca soluµie a problemei ar  obþinut punctajul maxim doar pentru
testele mici ³i medii (pentru valori ale lui N mai mici decât 130).
Limitele problemei ³i timpul de execuþie de 3 secunde permit rezolv rii prin backtracking ³
bµinerea unui punctaj mulµumitor.
Analiza complexit µii
Pentru o rezolvare care se bazeaz  pe metoda vectorului de constante, ordinul de complexitate
al soluµiei nale ar  fost O 1; soluµia const  înn citirea valorii lui N ³i a³area rezultatului
memorat.
Soluµia descris  anterior, bazat  pe metoda program rii dinamice, are ordinul de complexitate
3
O n . Invit m cititorul s  caute metode mai eciente.
Ordinul de complexitate al unei soluµii care se bazeaz  pe metoda backtracking este exponen-
µial.

Codul surs 

class PartitieNrGenerare // n = suma de numere


{
static int dim=0,nsol=0;
static int n=6;
static int[] x=new int[n+1];

public static void main(String[] args)


{
long t1,t2;
t1=System.currentTimeMillis();
f(n,n,1);
t2=System.currentTimeMillis();
System.out.println("nsol = "+nsol+" timp = "+(t2-t1)+"\n");
}// main(...)
CAPITOLUL 14. METODA BACKTRACKING 231

static void f(int val, int maxp, int poz)


{
if(maxp==1)
{
nsol++;
dim=poz-1;
afis2(val,maxp);
return;
}
if(val==0)
{
nsol++;
dim=poz-1;
afis1();
return;
}

int i;
int maxok=min(maxp,val);
for(i=maxok;i>=1;i--)
{
x[poz]=i;
f(val-i,min(val-i,i),poz+1);
}
}// f(...)

static void afis1()


{
int i;
System.out.print("\n"+nsol+" : ");
for(i=1;i<=dim;i++) System.out.print(x[i]+" ");
}// afis1()

static void afis2(int val,int maxip)


{
int i;
System.out.print("\n"+nsol+" : ");
for(i=1;i<=dim;i++) System.out.print(x[i]+" ");
for(i=1;i<=val;i++) System.out.print("1 ");
}afis2(...)

static int min(int a,int b) { return (a<b)?a:b; }


}// class

class PartitieNrImpare1 // n = suma de numere impare


{ // nsol = 38328320 timp = 8482
static int dim=0,nsol=0;
static int[] x=new int[161];

public static void main(String[] args)


{
long t1,t2;
int n=160;
int maxi=((n-1)/2)*2+1;
t1=System.currentTimeMillis();
f(n,maxi,1);
t2=System.currentTimeMillis();
System.out.println("nsol = "+nsol+" timp = "+(t2-t1)+"\n");
}// main(...)

static void f(int val, int maxip, int poz)


CAPITOLUL 14. METODA BACKTRACKING 232

{
if(maxip==1)
{
nsol++;
// dim=poz-1;
// afis2(val,maxip);
return;
}
if(val==0)
{
nsol++;
// dim=poz-1;
// afis1();
return;
}

int maxi=((val-1)/2)*2+1;
int maxiok=min(maxip,maxi);
int i;
for(i=maxiok;i>=1;i=i-2)
{
x[poz]=i;
f(val-i,min(maxiok,i),poz+1);
}
}// f(...)

static void afis1()


{
int i;
System.out.print("\n"+nsol+" : ");
for(i=1;i<=dim;i++) System.out.print(x[i]+" ");
}// afis1()

static void afis2(int val,int maxip)


{
int i;
System.out.print("\n"+nsol+" : ");
for(i=1;i<=dim;i++) System.out.print(x[i]+" ");
for(i=1;i<=val;i++) System.out.print("1 ");
}// afis2(...)

static int max(int a,int b) { return (a>b)?a:b; }

static int min(int a,int b) { return (a<b)?a:b; }


}// class

import java.io.*;
class PartitieNrImpare2 // n = suma de numere impare ;
{ // nsol = 38328320 timp = 4787
static int dim=0,nsol=0;
static int[] x=new int[161];

public static void main(String[] args) throws IOException


{
long t1,t2;
t1=System.currentTimeMillis();

int n,i;
StreamTokenizer st=new StreamTokenizer(
new BufferedReader(new FileReader("partitie.in")));
PrintWriter out=new PrintWriter(new BufferedWriter(
CAPITOLUL 14. METODA BACKTRACKING 233

new FileWriter("partitie.out")));

st.nextToken(); n=(int)st.nval;

int maxi=((n-1)/2)*2+1;
f(n,maxi,1);

out.print(nsol);
out.close();
t2=System.currentTimeMillis();
System.out.println("nsol = "+nsol+" timp= "+(t2-t1));
}// main(...)

static void f(int val, int maxip, int poz)


{
if(maxip==1)
{
nsol++;
dim=poz-1;
afis2(val);
return;
}
if(val==0)
{
nsol++;
dim=poz-1;
//afis1();
return;
}

int maxi=((val-1)/2)*2+1;
int maxiok=min(maxip,maxi);
int i;
for(i=maxiok;i>=3;i=i-2)
{
x[poz]=i;
f(val-i,i,poz+1);
}
nsol++;
dim=poz-1;
//afis2(val);
}// f(...)

static void afis1()


{
int i;
System.out.print("\n"+nsol+" : ");
for(i=1;i<=dim;i++) System.out.print(x[i]+" ");
}// afis1()

static void afis2(int val)


{
int i;
System.out.print("\n"+nsol+" : ");
for(i=1;i<=dim;i++) System.out.print(x[i]+" ");
for(i=1;i<=val;i++) System.out.print("1 ");
}// afis2(...)

static int max(int a,int b) { return (a>b)?a:b; }


CAPITOLUL 14. METODA BACKTRACKING 234

static int min(int a,int b) { return (a<b)?a:b; }


}// class

14.3.19 Scuµa - ONI2003 clasa a X-a

Majoritatea participanµilor la ONI2003 au auzit, în copil rie, povestea Scuµei Ro³ii. Pentru


cei care o ³tiu, urmeaz  partea a doua; pentru cei care nu o ³tiu, nu v  faceµi griji, cunoa³terea
ei nu este necesar  pentru a rezolva aceast  problem . Povestea nu spune ce s-a întâmplat pe
drumul pe care Scuµa Ro³ie s-a întors de la bunicuµ . Veµi aa am nunte în continuare.
Pe drum, ea s-a întâlnit cu Lupul (fratele lupului care a p r sit povestea în prima parte).
Acesta dorea s  o m nânce, dar a decis s -i acorde o ³ans  de sc pare, provocând-o la un concurs
de cules ciupercuµe.
Scuµa Ro³ie se a  în poziµia 1, 1 a unei matrice cu N linii ³i N coloane, în ecare poziµie
a matricei ind amplasate ciupercuµe. Lupul se a  în poziµia 1, 1 a unei alte matrice similare.
Pe parcursul unui minut, atât Scuµa, cât ³i Lupul se deplaseaz  într-una din poziµiile vecine
(pe linie sau pe coloan ) ³i culeg ciupercuµele din poziµia respectiv . Dac  Scuµa Ro³ie ajunge
într-o poziµie în care nu sunt ciupercuµe, va pierde jocul. Dac  la sfâr³itul unui minut ea are mai
puµine ciupercuµe decât Lupul, ea va pierde jocul de asemenea. Jocul începe dup  ce amândoi
participanµii au cules ciupercuµele din poziµiile lor iniµiale (nu conteaz  cine are mai multe la
începutul jocului, ci doar dup  un num r întreg strict pozitiv de minute de la început). Dac 
Scuµa Ro³ie pierde jocul, Lupul o va mânca.
Înainte de începerea jocului, Scuµa Ro³ie l-a sunat pe Vân tor, care i-a promis c  va veni
într-un sfert de ora (15 minute) pentru a o salva. Deci Scuµa Ro³ie va  liber  s  plece dac  nu
va pierde jocul dup  15 minute.
Din acest moment, scopul ei este nu numai s  r mân  în viaµ , ci ³i s  culeag  cât mai multe
ciupercuµe, pentru a le duce acas  (dup  ce va veni, vân torul nu o va mai l sa s  culeag ).
Lupul, cunoscut pentru l comia sa proverbial , va alege la ecare minut mutarea în câmpul
vecin cu cele mai multe ciupercuµe (matricea sa este dat  astfel încât s  nu existe mai multe
posibilit µi de alegere la un moment dat).
Povestea spune c  Scuµa Ro³ie a plecat acas  cu co³uleµul plin de ciupercuµe, folosind indi-
caµiile date de un program scris de un concurent la ONI 2003 (nu vom da detalii suplimentare
despre alte aspecte, cum ar  c l toria în timp, pentru a nu complica inutil enunµul problemei).
S   fost acest program scris de c tre dumneavoastr ? Vom vedea...
Cerinµ 
Scrieµi un program care s  o ajute pe Scuµa Ro³ie s  r mân  în joc ³i s  culeag  cât mai
multe ciupercuµe la sfâr³itul celor 15 minute!
Date de intrare
Fi³ierul scuta.in are urm toarea structur :
N - dimensiunea celor dou  matrice
a11 a12 ...a1n -matricea din care culege Scuµa Ro³ie
a21 a22 ...a2n
...
an1 an2 ...ann
b11 b12 ...b1n - matricea din care culege Lupul
b21 b22 ...b2n
...
bn1 bn2 ...bnn
Date de ie³ire
Fi³ierul scuta.out are urm toarea structur :
N R - num rul total de ciupercuµe culese
d1 d2 ...d15 - direcµiile pe care s-a deplasat Scuµa Ro³ie, separate prin câte un spaµiu (direcµiile
pot  N, E, S, V indicând deplas ri spre Nord, Est, Sud, Vest; poziµia 1, 1 este situat  în colµul
de Nord-Vest al matricei)
Restricµii
a 4 & N & 10;
a valorile din ambele matrice sunt numere naturale mai mici decât 256;
a nici Scuµa Ro³ie ³i nici Lupul nu vor p r si matricele corespunz toare;
a dup  ce unul din juc tori culege ciupercuµele dintr-o poziµie, în poziµia respectiv  r mân 0
ciupercuµe;
a pe testele date, Scuµa Ro³ie va avea întotdeauna posibilitatea de a rezista 15 minute.
CAPITOLUL 14. METODA BACKTRACKING 235

Exemplu
scuta.in scuta.out
4 137
2234 SSSEEENVVNEENVV
5678
9 10 11 12
13 14 15 16
1234
5678
9 10 11 12
13 14 15 16
Explicaµie:
Scuµa Ro³ie a efectuat acelea³i mut ri cu cele efectuate de Lup ³i a avut tot timpul o ciuper-
cuµ  în plus. În nal ea a cules toate ciupercuµele din matrice.
Timp maxim de executare: 1 secund /test

Indicaµii de rezolvare *

Mihai Stroe, GInfo nr. 13/6 - octombrie 2003


Problema se rezolv  folosind metoda backtracking în plan.
Se observ  c  mut rile Lupului sunt independente de mut rile Scuµei, astfel ele pot  deter-
minate imediat dup  citirea datelor.
În acest punct al rezolv rii, Scuµa trebuie s  mute la ecare moment, astfel încât s  r mân 
în joc, iar în nal s  strâng  cât mai multe ciupercuµe. Restricµiile sunt date de enunµul problemei
³i de num rul de ciupercuµe culese de Lup, care este cunoscut la ecare moment.
Rezolvarea prin metoda backtracking încearc  la ecare pas, pe rând, ecare mutare din cele
cel mult patru posibile (teoretic; de fapt, cel mult trei mut ri sunt posibile în ecare moment,
deoarece Scuµa nu se va întoarce în poziµia din care tocmai a venit).
Se urm re³te respectarea restricµiilor impuse. În momentul g sirii unei soluµii, aceasta este
comparat  cu soluµia optim  g sit  pân  atunci ³i dac  este mai bun  este reµinut .
Înc  nu a fost g sit  o rezolvare polinomial  pentru aceast  problem  (³i este improbabil ca o
astfel de rezolvare s  existe), dar limitele mici ³i faptul c  num rul de mut ri disponibile începe
s  scad  destul de repede, în multe cazuri permit un timp de execuµie foarte bun.
Analiza complexit µii
Fie M num rul de mut ri pe care le are de efectuat Scuµa.
2
Operaµiile de citire ³i scriere a datelor au ordinul de complexitate O N , respectiv O M , deci
nu vor  luate în calcul. Ordinul de complexitate este dat de rezolvarea prin metoda backtracking.
Având în vedere c  la ecare pas Scuµa poate alege dintre cel mult 3 mut ri (deoarece nu se
M
poate întoarce în poziµia din care a venit), ordinul de complexitate ar  O 3 . De fapt, num rul
maxim de st ri examinate este mult mai mic; de exemplu, primele dou  mut ri ofer  dou  variante
de alegere în loc de trei. Alte restricµii apar datorit  limit rii la o matrice de 10 10 elemente.
Cum num rul M este cunoscut ³i mic, s-ar putea considera c  ordinul de complexitate este
limitat superior, deci constant (constanta introdus  ind totu³i destul de mare).

Codul surs 

import java.io.*;
class Scufita
{
static int n, maxc=0;
static int[][] a,b;
static int[] x,y;
static char[] tc=new char[16];
static char[] tmax=new char[16];

public static void main(String[] args) throws IOException


{
int i,j;
CAPITOLUL 14. METODA BACKTRACKING 236

StreamTokenizer st=new StreamTokenizer(


new BufferedReader(new FileReader("scufita.in")));
PrintWriter out=new PrintWriter(
new BufferedWriter(new FileWriter("scufita.out")));

st.nextToken(); n=(int)st.nval;

a=new int[n+2][n+2];
b=new int[n+2][n+2];
x=new int[17];
y=new int[17];

for(i=1;i<=n;i++)
for(j=1;j<=n;j++) { st.nextToken(); a[i][j]=(int)st.nval; }
for(i=1;i<=n;i++)
for(j=1;j<=n;j++) { st.nextToken(); b[i][j]=(int)st.nval; }

culegeLupul(1,1,1);
f(1,1,1);

out.println(maxc);
for(i=1;i<=15;i++) out.print(tmax[i]);
out.println();
out.close();
}// main()

static void f(int i, int j, int k) throws IOException


{
int aij=a[i][j];

x[k]=x[k-1]+a[i][j];
a[i][j]=0;

if(k==16)
{
if(x[16]>maxc)
{
maxc=x[16];
for(int ii=1;ii<=15;ii++) tmax[ii]=tc[ii];
}
a[i][j]=aij;
return;
}

if((a[i-1][j]>0)&&(a[i-1][j]+x[k]>=y[k+1])) { tc[k]=’N’; f(i-1,j,k+1); }


if((a[i+1][j]>0)&&(a[i+1][j]+x[k]>=y[k+1])) { tc[k]=’S’; f(i+1,j,k+1); }
if((a[i][j-1]>0)&&(a[i][j-1]+x[k]>=y[k+1])) { tc[k]=’V’; f(i,j-1,k+1); }
if((a[i][j+1]>0)&&(a[i][j+1]+x[k]>=y[k+1])) { tc[k]=’E’; f(i,j+1,k+1); }

a[i][j]=aij;
}//f(...)

static void culegeLupul(int i, int j, int k)


{
if(k>16) return;
y[k]=y[k-1]+b[i][j];
b[i][j]=0;
if((b[i-1][j]>b[i+1][j])&&(b[i-1][j]>b[i][j-1])&&(b[i-1][j]>b[i][j+1]))
culegeLupul(i-1,j,k+1);
else if((b[i+1][j]>b[i][j-1])&&(b[i+1][j]>b[i][j+1]))
CAPITOLUL 14. METODA BACKTRACKING 237

culegeLupul(i+1,j,k+1);
else if(b[i][j-1]>b[i][j+1])
culegeLupul(i,j-1,k+1);
else culegeLupul(i,j+1,k+1);
}// culegeLupul(...)
}// class
Capitolul 15

Programare dinamic 

15.1 Prezentare general 


Folosirea tehnicii program rii dinamice solicit  experienµ , intuiµie ³i abilit µi matematice. De
foarte multe ori rezolv rile date prin aceast  tehnic  au ordin de complexitate polinomial.
În literatura de specialitate exist  dou  variante de prezentare a acestei tehnici. Prima dintre
ele este mai degrab  de natur  deductiv  pe când a doua folose³te gândirea inductiv .
Prima variant  se bazeaz  pe conceptul de subproblem . Sunt considerate urm toarele aspecte
care caracterizeaz  o rezolvare prin programare dinamic :

ˆ Problema se poate descompune recursiv în mai multe subprobleme care sunt caracterizate
de optime parµiale, iar optimul global se obµine prin combinarea acestor optime parµiale.

ˆ Subproblemele respective se suprapun. La un anumit nivel, dou  sau mai multe subprobleme
necesit  rezolvarea unei aceea³i subprobleme. Pentru a evita risipa de timp rezultat  în urma
unei implement ri recursive, optimele parµiale se vor reµine treptat, în maniera bottom-up,
în anumite structuri de date (tabele).

A doua variant  de prezentare face apel la conceptele intuitive de sistem, stare ³i decizie.
O problem  este abordabil  folosind tehnica program rii dinamice dac  satisface principiul de
optimalitate sub una din formele prezentate în continuare.
Fie secvenµa de st ri S0 , S1 , ..., Sn ale sistemului.

ˆ Dac  d1 , d2 , ..., dn este un ³ir optim de decizii care duc la trecerea sistemului din starea S0 în
starea Sn , atunci pentru orice i (1 & i & n) di1 , di2 , ..., dn este un ³ir optim de decizii care
duc la trecerea sistemului din starea Si în starea Sn . Astfel, decizia di depinde de deciziile
anterioare di1 , ..., dn . Spunem c  se aplic  metoda înainte.

ˆ Dac  d1 , d2 , ..., dn este un ³ir optim de decizii care duc la trecerea sistemului din starea S0
în starea Sn , atunci pentru orice i (1 & i & n) d1 , d2 , ..., di este un ³ir optim de decizii care
duc la trecerea sistemului din starea S0 în starea Si . Astfel, decizia di1 depinde de deciziile
anterioare d1 , ..., di . Spunem c  se aplic  metoda înapoi.

ˆ Dac  d1 , d2 , ..., dn este un ³ir optim de decizii care duc la trecerea sistemului din starea S0
în starea Sn , atunci pentru orice i (1 & i & n) di1 , di2 , ..., dn este un ³ir optim de decizii
care duc la trecerea sistemului din starea Si în starea Sn ³i d1 , d2 , ..., di este un ³ir optim
de decizii care duc la trecerea sistemului din starea S0 în starea Si . Spunem c  se aplic 
metoda mixt .

Indiferent de varianta de prezentare, rezolvarea prin programare dinamic  presupune g sirea ³i


rezolvarea unui sistem de recurenµe. În prima variant  avem recurenµe între subprobleme, în a doua
variant  avem recurenµe în ³irul de decizii. În afara cazurilor în care recurenµele sunt evidente,
este necesar  ³i o justicare sau o demonstraµie a faptului c  aceste recurenµe sunt cele corecte.
Deoarece rezolvarea prin recursivitate duce de cele mai multe ori la ordin de complexitate
exponenµial, se folosesc tabele auxiliare pentru reµinerea optimelor parµiale iar spaµiul de soluµii

238
CAPITOLUL 15. PROGRAMARE DINAMIC€ 239

se parcurge în ordinea cresc toare a dimensiunilor subproblemelor. Folosirea acestor tabele d  ³i


numele tehnicii respective.
Asem n tor cu metoda "divide et impera", programarea dinamic  rezolv  problemele com-
binând soluµiile unor subprobleme. Deosebirea const  în faptul c  "divide et impera" partiµioneaz 
problema în subprobleme independente, le rezolv  (de obicei recursiv) ³i apoi combin  soluµiile lor
pentru a rezolva problema initial , în timp ce programarea dinamic  se aplic  problemelor ale c -
ror subprobleme nu sunt independente, ele având "sub-subprobleme" comune. În aceast  situaµie,
se rezolv  ecare "sub-subproblem " o singur  dat  ³i se folose³te un tablou pentru a memora
soluµia ei, evitându-se recalcularea ei de câte ori subproblema reapare.
Algoritmul pentru rezolvarea unei probleme folosind programarea dinamic  se dezvolta în 4
etape:

1. caracterizarea unei soluµii optime (identicarea unei modalit ti optime de rezolvare, care
satisface una dintre formele principiului optimalit µii),

2. denirea recursiv  a valorii unei soluµii optime,

3. calculul efectiv al valorii unei soluµii optime,

4. reconstituirea unei soluµii pe baza informaµiei calculate.

15.2 Probleme rezolvate

15.2.1 Inmulµirea optimal  a matricelor

Consider m n matrice A1 , A2 , ..., An , de dimensiuni d0  d1 , d1  d2 , ..., dn1  dn . Produsul


A1  A2  ...  An se poate calcula în diverse moduri, aplicând asociativitatea operaµiei de înmultire
a matricelor.
Numim înmulµire elementar  înmulµirea a dou  elemente.
În funcµie de modul de parantezare difer  num rul de înmulµiri elementare necesare pentru
calculul produsului A1  A2  ...  An .
Se cere parantezare optimal  a produsului A1  A2  ...  An (pentru care costul, adic  num rul
total de înmulµiri elementare, s  e minim).
Exemplu:
Pentru 3 matrice de dimensiuni 10, 1000, 1000, 10 ³i 10, 100, produsul A1  A2  A3 se
poate calcula în dou  moduri:

1. A1  A2   A3 necesitând 1000000+10000=1010000 înmulµiri elementare

2. A1  A2  A3 , necesitând 1000000+1000000=2000000 înmulµiri.

Reamintim c  num rul de înmulµiri elementare necesare pentru a înmulµi o matrice A, având


n linii ³i m coloane, cu o matrice B , având m linii ³i p coloane, este n ˜ m ˜ p.
Solutie

1. Pentru a calcula A1  A2  ...  An , în nal trebuie s  înmulµim dou  matrice, deci vom
paranteza produsul astfel: A1  A2  ...  Ak   Ak1  ...  An . Aceast  observaµie se
aplic  ³i produselor dintre paranteze. Prin urmare, subproblemele problemei iniµiale constau
în determinarea parantez rii optimale a produselor de matrice de forma Ai  Ai1  ...  Aj ,
1 & i & j & n. Observ m c  subproblemele nu sunt independente. De exemplu, calcularea
produsului Ai  Ai1  ...  Aj ³i calcularea produsului Ai1  Ai2  ...  Aj 1 , au ca
subproblem  comun  calcularea produsului Ai1  ...  Aj .

2. Pentru a reµine soluµiile subproblemelor, vom utiliza o matrice M , cu n linii ³i n coloane, cu


semnicatia:
M ij  = num rul minim de înmulµiri elementare necesare pentru a calcula produsul Ai 
Ai1  ...  Aj , 1 & i & j & n.
Evident, num rul minim de înmulµiri necesare pentru a calcula A1  A2  ...  An este
M 1n.
CAPITOLUL 15. PROGRAMARE DINAMIC€ 240

3. Pentru ca parantezarea s  e optimal , parantezarea produselor A1  A2  ...  Ak ³i Ak1 


...  An trebuie s  e de asemenea optimal . Prin urmare elementele matricei M trebuie s 
satisfac  urmatoarea relaµie de recurenµ :

M ii 0, i 1, 2, ..., n.

M ij  min rM ik   M k  1j   di  1  dk   dj x


i&k$j

Cum interpret m aceast  relaµie de recurenµ ? Pentru a determina num rul minim de înmul-
µiri elementare pentru calculul produsului Ai  Ai1  ...  Aj , x m poziµia de parantezare
k în toate modurile posibile (între i ³i j  1), ³i alegem varianta care ne conduce la minim.
Pentru o poziµie k xata, costul parantez rii este egal cu numarul de înmulµiri elementare
necesare pentru calculul produsului Ai  Ai1  ...  Ak , la care se adaug  num rul de înmul-
µiri elementare necesare pentru calculul produsului Ak1  ...  Aj ³i costul înmulµirii celor
dou  matrice rezultate (di1  dk  dj ).
Observ m c  numai jum tatea de deasupra diagonalei principale din M este utilizat . Pentru
a construi soluµia optim  este util  ³i reµinerea indicelui k , pentru care se obtine minimul.
Nu vom considera un alt tablou, ci-l vom reµine, pe poziµia simetric  faµ  de diagonala
principala (M j i).

4. Rezolvarea recursiv  a relaµiei de recurenµ  este inecient , datorit  faptului c  subproble-


mele se suprapun, deci o abordare recursiv  ar conduce la rezolvarea aceleia³i subprobleme
de mai multe ori. Prin urmare vom rezolva relaµia de recurenµ  în mod bottom-up: (deter-
min m parantezarea optimal  a produselor de dou  matrice, apoi de 3 matrice, 4 matrice,
etc).

import java.io.*;
class InmOptimalaMatrice
{
static int nmax=20;
static int m[][]=new int[100][100];
static int p[]=new int[100];
static int n,i,j,k,imin,min,v;

public static void paranteze(int i,int j)


{
int k;
if(i<j)
{
k=m[j][i];
if(i!=k)
{
System.out.print("(");
paranteze(i,k);
System.out.print(")");
}//if
else paranteze(i,k);

System.out.print(" * ");

if(k+1!=j)
{
System.out.print("(");
paranteze(k+1,j);
System.out.print(")");
}//if
else paranteze(k+1,j);
}//if
else System.out.print("A"+i);
}//paranteze
CAPITOLUL 15. PROGRAMARE DINAMIC€ 241

public static void main(String[]args) throws IOException


{
BufferedReader br=new BufferedReader(new InputStreamReader(System.in));

System.out.print("numarul matricelor: ");


n=Integer.parseInt(br.readLine());

System.out.println("Dimensiunile matricelor:");
for(i=1;i<=n+1;i++)
{
System.out.print("p["+i+"]=");
p[i]=Integer.parseInt(br.readLine());
}

for(i=n;i>=1;i--)
for(j=i+1;j<=n;j++)
{
min=m[i][i]+m[i+1][j]+p[i]*p[i+1]*p[j+1];
imin=i;
for(k=i+1;k<=j-1;k++)
{
v=m[i][k]+m[k+1][j]+p[i]*p[k+1]*p[j+1];
if(min>v) { min=v; imin=k; }
}
m[i][j]=min;
m[j][i]=imin;
}//for i,j

System.out.println("Numarul minim de inmultiri este: "+m[1][n]);


System.out.print("Ordinea inmultirilor: ");
paranteze(1,n);
System.out.println();
}//main
}//class
/*
numarul matricelor: 8
Dimensiunile matricelor:
p[1]=2
p[2]=8
p[3]=3
p[4]=2
p[5]=7
p[6]=2
p[7]=5
p[8]=3
p[9]=7
Numarul minim de inmultiri este: 180
Ordinea inmultirilor: ((((A1 * A2) * A3) * (A4 * A5)) * (A6 * A7)) * A8
*/

15.2.2 Sub³ir cresc tor maximal

Fie un ³ir A a1 , a2 , ..., an . Numim sub³ir al ³irului A o succesiune de elemente din A, în


ordinea în care acestea apar în A:
ai1 , ai2 , ..., aik , unde 1 & i1 $ i2 $ ... $ ik & n.
Se cere determinarea unui sub³ir cresc tor al ³irului A, de lungime maxim .
De exemplu, pentru

A 8, 3, 6, 50, 10, 8, 100, 30, 60, 40, 80


CAPITOLUL 15. PROGRAMARE DINAMIC€ 242

o soluµie poate 
3, 6, 10, 30, 60, 80.
Rezolvare
1. Fie Ai1 ai1 & ai2 & ... & aik  cel mai lung sub³ir cresc tor al ³irului A. Observ m c 
el coincide cu cel mai lung sub³ir cresc tor al ³irului ai1 , ai1 1 , ..., an . Evident Ai2 ai 2 &
ai3 & ... & aik  este cel mai lung sub³ir cresc tor al lui ai2 , ai2 1 , ..., an , etc. Prin urmare,
o subproblem  a problemei iniµiale const  în determinarea celui mai lung sub³ir cresc tor care
începe cu ai , i r1, .., nx.
Subproblemele nu sunt independente: pentru a determina cel mai lung sub³ir cresc tor care
începe cu ai , este necesar s  determin m cele mai lungi sub³iruri cresc toare care încep cu aj ,
ai & aj , j ri  1, .., nx.
2. Pentru a reµine soluµiile subproblemelor vom considera doi vectori l ³i poz , ecare cu n
componente, având semnicaµia:
li lungimea celui mai lung sub³ir cresc tor care începe cu ai;
poz i poziµia elementului care urmeaz  dup  ai în cel mai lung sub³ir cresc tor care începe
cu ai, dac  un astfel de element exist , sau 1 dac  un astfel de element nu exist .
3. Relaµia de recurenµ  care caracterizeaz  substructura optimal  a problemei este:

ln 1; poz n 1;

li max r1  lj ¶ai & aj x


j i1,n

unde poz i indicele j pentru care se obµine maximul li.


Rezolv m relaµia de recurenµ  în mod bottom-up:

int i, j;
l[n]=1;
poz[n]=-1;
for (i=n-1; i>0; i--)
for (l[i]=1, poz[i]=-1, j=i+1; j<=n; j++)
if (a[i] <= a[j] && l[i]<1+l[j])
{
l[i]=1+l[j];
poz[i]=j;
}
Pentru a determina soluµia optim  a problemei, determin m valoarea maxim  din vectorul l,
apoi a³ m soluµia, începând cu poziµia maximului ³i utilizând informaµiile memorate în vectorul
poz :

//determin maximul din vectorul l


int max=l[1], pozmax=1;
for (int i=2; i<=n; i++)
if (max<l[i])
{
max=l[i]; pozmax=i;
}
cout<<"Lungimea celui mai lung subsir crescator: " <<max;
cout<<"\nCel mai lung subsir:\n";
for (i=pozmax; i!=-1; i=poz[i]) cout<<a[i]<<’ ’;

Secvenµele de program de mai sus sunt scrise în c/C++.

Programele urm toare sunt scrise în Java:

import java.io.*;
class SubsirCrescatorNumere
{
static int n;
static int[] a;
static int[] lg;
CAPITOLUL 15. PROGRAMARE DINAMIC€ 243

static int[] poz;

public static void main(String []args) throws IOException


{
int i,j;
StreamTokenizer st=new StreamTokenizer(
new BufferedReader(new FileReader("SubsirCrescatorNumere.in")));
PrintWriter out = new PrintWriter (
new BufferedWriter( new FileWriter("SubsirCrescatorNumere.out")));

st.nextToken();n=(int)st.nval;
a=new int[n+1];
lg=new int[n+1];
poz=new int[n+1];
for(i=1;i<=n;i++) { st.nextToken(); a[i]=(int)st.nval; }
int max,jmax;
lg[n]=1;
for(i=n-1;i>=1;i--)
{
max=0;
jmax=0;
for(j=i+1;j<=n;j++)
if((a[i]<=a[j])&&(max<lg[j])) { max=lg[j]; jmax=j; }
if(max!=0) { lg[i]=1+max; poz[i]=jmax; }
else lg[i]=1;
}
max=0; jmax=0;
for(j=1;j<=n;j++)
if(max<lg[j]){ max=lg[j]; jmax=j; }
out.println(max);
int k;
j=jmax;
for(k=1;k<=max;k++) { out.print(a[j]+" "); j=poz[j]; }
out.close();
}//main
}//class

import java.io.*;
class SubsirCrescatorLitere
{

public static void main(String[] args) throws IOException


{
int n,i,j,jmax,k,lmax=-1,pozmax=-1;
int [] l,poz;
String a;
StreamTokenizer st=new StreamTokenizer(
new BufferedReader(new FileReader("SubsirCrescatorLitere.in")));
PrintWriter out=new PrintWriter(
new BufferedWriter(new FileWriter("SubsirCrescatorLitere.out")));
st.nextToken();
a=st.sval;
n=a.length();
out.println(a+" "+n);
l=new int[n];//l[i]=lg.celui mai lung subsir care incepe cu a[i]
poz=new int[n];//poz[i]=pozitia elementului care urmeaza dupa a[i]
for(i=0;i<n;i++) poz[i]=-1;
l[n-1]=1;
poz[n-1]=-1;
for(i=n-2;i>=0;i--)
CAPITOLUL 15. PROGRAMARE DINAMIC€ 244

{
jmax=i;
for(j=i+1;j<n;j++)
if((a.charAt(i)<=a.charAt(j))&&(1+l[j]>1+l[jmax])) jmax=j;
l[i]=1+l[jmax];
poz[i]=jmax;
if(l[i]>lmax) { lmax=l[i]; pozmax=i; }
}
out.print("Solutia ");
k=pozmax;
out.print("("+l[pozmax]+") : ");
for(j=1;j<=lmax;j++)
{
out.print(a.charAt(k));
k=poz[k];
}
out.close();
} // main
}// class

15.2.3 Sum  maxim  în triunghi de numere

S  consider m un triunghi format din n linii (1 $ n & 100), ecare linie conµinând numere
întregi din domeniul 1, 99, ca în exemplul urm tor:

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

Tabela 15.1: Triunghi de numere

Problema const  în scrierea unui program care s  determine cea mai mare sum  de numere
aate pe un drum între num rul de pe prima linie ³i un num r de pe ultima linie. Fiecare num r
din acest drum este situat sub precedentul, la stânga sau la dreapta acestuia. (IOI, Suedia 1994)
Rezolvare
1. Vom reµine triunghiul într-o matrice p tratic  T , de ordin n, sub diagonala principal . Sub-
problemele problemei date constau în determinarea sumei maxime care se poate obµine din numere
aate pe un drum între numarul T ij , pân  la un num r de pe ultima linie, ecare num r din
acest drum ind situat sub precedentul, la stânga sau la dreapta sa. Evident, subproblemele nu
sunt independente: pentru a calcula suma maxim  a numerelor de pe un drum de la T ij  la
ultima linie, trebuie s  calcul m suma maxim  a numerelor de pe un drum de la T i  1j  la
ultima linie ³i suma maxim  a numerelor de pe un drum de la T i  1j  1 la ultima linie.
2. Pentru a reµine soluµiile subproblemelor, vom utiliza o matrice S , p tratic  de ordin n, cu
semnicaµia
S ij  suma maxim  ce se poate obµine pe un drum de la T ij  la un element de pe
ultima linie, respectând condiµiile problemei.
Evident, soluµia problemei va  S 11.
3. Relaµia de recurenµa care caracterizeaz  substructura optimal  a problemei este:

S ni T ni, i r1, 2, ..., nx

S ij  T ij   maxrS i  1j , S i  1j  1x


4. Rezolv m relaµia de recurenµ  în mod bottom-up:

int i, j;
for (i=1; i<=n; i++) S[n][i]=T[n][i];
for (i=n-1; i>0; i--)
CAPITOLUL 15. PROGRAMARE DINAMIC€ 245

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


{
S[i][j]=T[i][j]+S[i+1][j];
if (S[i+1][j]<S[i+1][j+1])
S[i][j]=T[i][j]+S[i+1][j+1]);
}
Exerciµiu: A³aµi ³i drumul în triunghi pentru care se obµine soluµia optim .

15.2.4 Sub³ir comun maximal

Fie X x1 , x2 , ..., xn  ³i Y y1 , y2 , ..., ym  dou  ³iruri de n, respectiv m numere întregi.


Determinaµi un sub³ir comun de lungime maxim .
Exemplu
Pentru X 2, 5, 5, 6, 2, 8, 4, 0, 1, 3, 5, 8 ³i Y 6, 2, 5, 6, 5, 5, 4, 3, 5, 8 o soluµie posibil  este:
Z 2, 5, 5, 4, 3, 5, 8.
Solutie
1. Not m cu Xk x1 , x2 , ..., xk  (prexul lui X de lungime k ) ³i cu Yh y1 , y2 , ..., yh 
prexul lui Y de lungime h. O subproblem  a problemei date const  în determinarea celui mai
lung sub³ir comun al lui Xk , Yh . Notam cu LCS Xk , Yh  lungimea celui mai lung sub³ir comun
al lui Xk , Yh .
Utilizând aceste notaµii, problema cere determinarea LCS Xn , Ym , precum ³i un astfel de
sub³ir.
Observaµie
1. Dac  Xk Yh atunci

LCS Xk , Yh  1  LCS Xk1 , Yh1 .

2. Dac  Xk j Y h atunci
LCS Xk, Y h max LCS Xk1 , Yh , LCS Xk , Yh1 .

Din observaµia precedent  deducem c  subproblemele problemei date nu sunt independente ³i


c  problema are substructur  optimal .
2. Pentru a reµine soluµiile subproblemelor vom utiliza o matrice cu n  1 linii ³i m  1 coloane,
denumit  lcs. Linia ³i coloana 0 sunt utilizate pentru iniµializare cu 0, iar elementul lcsk h va
 lungimea celui mai lung sub³ir comun al ³irurilor Xk si Yh .
3. Vom caracteriza substructura optimal  a problemei prin urm toarea relaµie de recurenµ :

lcsk 0 lcs0h 0, k r1, 2, .., nx, h r1, 2, .., mx

lcsk h 1  lcsk  1h  1, dac  xk  y h


max lcsk h  1, lcsk  1h, dac  xk  $% y h
Rezolv m relaµia de recurenµ  în mod bottom-up:

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


for (int h=1; h<=m; h++)
if (x[k]==y[h])
lcs[k][h]=1+lcs[k-1][h-1];
else
if (lcs[k-1][h]>lcs[k][h-1])
lcs[k][h]=lcs[k-1][h];
else
lcs[k][h]=lcs[k][h-1];
Deoarece nu am utilizat o structur  de date suplimentar  cu ajutorul c reia s  memor m
soluµia optim , vom reconstitui soluµia optim  pe baza rezultatelor memorate în matricea lcs.
Prin reconstituire vom obµine soluµia în ordine invers , din acest motiv vom memora solutia într-
un vector, pe care îl vom a³a de la sfâr³it c tre început:
CAPITOLUL 15. PROGRAMARE DINAMIC€ 246

cout<<"Lungimea subsirului comun maximal: " <<lcs[n][m];


int d[100];
cout<<"\nCel mai lung subsir comun este: \n";
for (int i=0, k=n, h=m; lcs[k][h]; )
if (x[k]==y[h])
{
d[i++]=x[k];
k--;
h--;
}
else
if (lcs[k][h]==lcs[k-1][h])
k--;
else
h--;
for (k=i-1;k>=0; k--)
cout<< d[k] <<’ ’;

Secvenµele de cod de mai sus sunt scrise în C/C++. Programul urm tor este scris în Java
³i determin  toate soluµiile. Sunt determinate cea mai mic  ³i cea mai mare soluµie, în ordine
lexicograc . De asemenea sunt a³ate matricele auxiliare de lucru pentru a se putea urm rii mai
u³or modalitatea de determinare recursiv  a tuturor soluµiilor.

import java.io.*; // SubsirComunMaximal


class scm
{
static PrintWriter out;
static int [][] a;
static char [][] d;

static String x,y;


static char[] z,zmin,zmax;
static int nsol=0,n,m;
static final char sus=’|’, stanga=’-’, diag=’*’;

public static void main(String[] args) throws IOException


{
citire();
n=x.length();
m=y.length();
out=new PrintWriter(new BufferedWriter( new FileWriter("scm.out")));

int i,j;
matrad();
out.println(a[n][m]);
afism(a);
afism(d);

z=new char[a[n][m]+1];
zmin=new char[z.length];
zmax=new char[z.length];

System.out.println("O solutie oarecare");


osol(n,m);// o solutie

System.out.println("\nToate solutiile");
toatesol(n,m,a[n][m]);// toate solutiile
out.println(nsol);
System.out.print("SOLmin = ");
afisv(zmin);
System.out.print("SOLmax = ");
CAPITOLUL 15. PROGRAMARE DINAMIC€ 247

afisv(zmax);
out.close();
}

static void citire() throws IOException


{
BufferedReader br=new BufferedReader(new FileReader("scm.in"));
x=br.readLine();
y=br.readLine();
}

static void matrad()


{
int i,j;
a=new int[n+1][m+1];
d=new char[n+1][m+1];
for(i=1;i<=n;i++)
for(j=1;j<=m;j++)
if(x.charAt(i-1)==y.charAt(j-1)) // x.charAt(i)==y.charAt(j) !
{
a[i][j]=1+a[i-1][j-1];
d[i][j]=diag;
}
else
{
a[i][j]=max(a[i-1][j],a[i][j-1]);
if(a[i-1][j]>a[i][j-1]) d[i][j]=sus; else d[i][j]=stanga;
}
}

static void osol(int lin, int col)


{
if((lin==0)||(col==0)) return;

if(d[lin][col]==diag)
osol(lin-1,col-1);
else
if(d[lin][col]==sus)
osol(lin-1,col);
else osol(lin,col-1);
if(d[lin][col]==diag) System.out.print(x.charAt(lin-1));
}

static void toatesol(int lin, int col,int k)


{
int i,j;
if(k==0)
{
System.out.print(++nsol+" ");
afisv(z);
zminmax();
return;
}
i=lin+1;
while(a[i-1][col]==k)//merg in sus
{
i--;
j=col+1;
while(a[i][j-1]==k) j--;
if( (a[i][j-1]==k-1)&&(a[i-1][j-1]==k-1)&&(a[i-1][j]==k-1))
CAPITOLUL 15. PROGRAMARE DINAMIC€ 248

{
z[k]=x.charAt(i-1);
toatesol(i-1,j-1,k-1);
}
}//while
}

static void zminmax()


{
if(nsol==1)
{
copiez(z,zmin);
copiez(z,zmax);
}
else
if(compar(z,zmin)<0)
copiez(z,zmin);
else
if(compar(z,zmax)>0) copiez(z,zmax);
}

static int compar(char[] z1, char[] z2)//-1=<; 0=identice; 1=>


{
int i=1;
while(z1[i]==z2[i]) i++;// z1 si z2 au n componente 1..n
if(i>n)
return 0;
else
if(z1[i]<z2[i])
return -1;
else return 1;
}

static void copiez(char[] z1, char[] z2)


{
int i;
for(i=1;i<z1.length;i++) z2[i]=z1[i];
}

static int max(int a, int b)


{
if(a>b) return a; else return b;
}

static void afism(int[][]a)// difera tipul parametrului !!!


{
int n=a.length;
int i,j,m;
m=y.length();

System.out.print(" ");
for(j=0;j<m;j++) System.out.print(y.charAt(j)+" ");
System.out.println();

System.out.print(" ");
for(j=0;j<=m;j++) System.out.print(a[0][j]+" ");
System.out.println();

for(i=1;i<n;i++)
{
CAPITOLUL 15. PROGRAMARE DINAMIC€ 249

System.out.print(x.charAt(i-1)+" ");
m=a[i].length;

for(j=0;j<m;j++) System.out.print(a[i][j]+" ");


System.out.println();
}
System.out.println("\n");
}

static void afism(char[][]d)// difera tipul parametrului !!!


{
int n=d.length;
int i,j,m;
m=y.length();

System.out.print(" ");
for(j=0;j<m;j++) System.out.print(y.charAt(j)+" ");
System.out.println();

System.out.print(" ");
for(j=0;j<=m;j++) System.out.print(d[0][j]+" ");
System.out.println();

for(i=1;i<n;i++)
{
System.out.print(x.charAt(i-1)+" ");
m=d[i].length;

for(j=0;j<m;j++) System.out.print(d[i][j]+" ");


System.out.println();
}
System.out.println("\n");
}

static void afisv(char[]v)


{
int i;
for(i=1;i<=v.length-1;i++) System.out.print(v[i]);
for(i=1;i<=v.length-1;i++) out.print(v[i]);
System.out.println();
out.println();
}
}

Pe ecran apar urm toarele rezultate:

1 3 2 4 6 5 a c b d f e
0 0 0 0 0 0 0 0 0 0 0 0 0
1 0 1 1 1 1 1 1 1 1 1 1 1 1
2 0 1 1 2 2 2 2 2 2 2 2 2 2
3 0 1 2 2 2 2 2 2 2 2 2 2 2
4 0 1 2 2 3 3 3 3 3 3 3 3 3
5 0 1 2 2 3 3 4 4 4 4 4 4 4
6 0 1 2 2 3 4 4 4 4 4 4 4 4
a 0 1 2 2 3 4 4 5 5 5 5 5 5
b 0 1 2 2 3 4 4 5 5 6 6 6 6
c 0 1 2 2 3 4 4 5 6 6 6 6 6
d 0 1 2 2 3 4 4 5 6 6 7 7 7
e 0 1 2 2 3 4 4 5 6 6 7 7 8
f 0 1 2 2 3 4 4 5 6 6 7 8 8
CAPITOLUL 15. PROGRAMARE DINAMIC€ 250

1 3 2 4 6 5 a c b d f e

1 * - - - - - - - - - - -
2 | - * - - - - - - - - -
3 | * - - - - - - - - - -
4 | | - * - - - - - - - -
5 | | - | - * - - - - - -
6 | | - | * - - - - - - -
a | | - | | - * - - - - -
b | | - | | - | - * - - -
c | | - | | - | * - - - -
d | | - | | - | | - * - -
e | | - | | - | | - | - *
f | | - | | - | | - | * -

O solutie oarecare
1346acdf
Toate solutiile
1 1346acdf
2 1246acdf
3 1345acdf
4 1245acdf
5 1346abdf
6 1246abdf
7 1345abdf
8 1245abdf
9 1346acde
10 1246acde
11 1345acde
12 1245acde
13 1346abde
14 1246abde
15 1345abde
16 1245abde
SOLmin = 1245abde
SOLmax = 1346acdf

15.2.5 Distanµa minim  de editare (Levenshtein)

S  presupunem c  trebuie s  transform m cuvântul  zic în cuvântul  matematic folosind


cât mai puµine operaµii permise asupra literelor primului cuvânt.
Operaµiile permise sunt:
ˆ înlocuirea unei litere cu o alt  liter 
ˆ ³tergerea unei litere
ˆ inserarea unei litere

În desen este prezentat  o variant  de rezolvare:


ˆ înlocuim literele f, i, z cu m, a, t Figura 15.1: Distanµa mi-
ˆ inser m literele e, m, a, t nim  de editare
ˆ p str m neschimbate literele i, c
Au fost efectuate 7 operaµii permise (3 înlocuiri ³i 4 inser ri).
CAPITOLUL 15. PROGRAMARE DINAMIC€ 251

i ai operaµia j bj
1 f m 1 m
2 i m 2 a
3 z m 3 t
i 4 e
i 5 m
i 6 a
i 7 t
4 i 8 i
5 c 9 c
Tabela 15.2: f izic º matematic

Dac  trebuie s  transform m cuvântul  matematic în cuvântul  zic, folosind cât mai
puµine operaµii permise asupra literelor primului cuvânt, putem proceda astfel (folosim acela³i
desen!):
ˆ înlocuim literele m, a, t cu f, i, z
ˆ ³tergem literele e, m, a, t
ˆ p str m neschimbate literele i, c
Au fost efectuate 7 operaµii permise (3 înlocuiri ³i 4 ³tergeri).

i ai operaµia j bj
1 m m 1 f
2 a m 2 i
3 t m 3 z
4 e s
5 m s
6 a s
7 t s
8 i 4 i
9 c 5 c
Tabela 15.3: matematic º f izic

Din aceste dou  exemple nu trebuie tras  concluzia c , atunci când invers m ordinea cuvintelor,
este sucient s  permut m între ele numai i 
s (inserare 
³tergere).

i ai operaµia j bj i ai operaµia j bj
1 b s 1 a s
2 a 1 a 2 b 1 b
3 b 2 b 3 a 2 a
4 a 3 a 4 a m 3 b
5 b m 4 a 5 c s
i 5 c 6 a 4 a
6 a 6 a 7 b 5 b
i 7 b i 6 a
Tabela 15.4: bababa º abaacab Tabela 15.5: abaacab º bababa
COST transformare = 4 în ambele situaµii.
Pa³ii (parcurgând primul cuvânt: bababa) prezentaµi în Tabela 15.4 sunt:
CAPITOLUL 15. PROGRAMARE DINAMIC€ 252

ˆ b se ³terge
ˆ a se p streaz 
ˆ b se p streaz 
ˆ a se p streaz 
ˆ b se modic  în a
ˆ se insereaz  c
ˆ a se p streaz 
ˆ se insereaz  b

23
Desenul corespunz tor celor dou  tabele este:

Figura 15.2: bababa  abaacab


Un alt exemplu:

i ai operaµia j bj i ai operaµia j bj
0 i 1 b 1 b s
1 a 2 a 2 a 1 a
2 a 3 a 3 a 2 a
3 b 4 b 4 b 3 b
4 a 5 a 5 a 4 a
5 a m 6 c 6 c m 5 a
6 c 7 c 7 c 6 c
Tabela 15.6: aabaac º baabacc Tabela 15.7: baabacc º aabaac
COST transformare = 2 în ambele situaµii.
Pa³ii (parcurgând primul cuvânt: aabaac) prezentaµi în Tabela 15.6 sunt:

ˆ se insereaz  b
ˆ a se p streaz 
ˆ a se p streaz 
ˆ b se p streaz 
ˆ a se p streaz 
ˆ a se modic  în c
ˆ c se p streaz 

Desenul corespunz tor celor dou  tabele este:

Figura 15.3: aabaac  baabacc


Num rul minim de operaµii permise folosite, pentru transformarea unui ³ir de caractere în alt
23
https://ro.wikipedia.org/wiki/Tabel
CAPITOLUL 15. PROGRAMARE DINAMIC€ 253

24 25
³ir de caractere, se nume³te distanµ  Levenshtein ³i m soar  gradul de similitudine între cele
dou  ³iruri de caractere.
S  presupunem c  sunt date dou  cuvinte (³iruri de caractere) a ³i b.
S  presupunem c  a are na caractere (a = a1 a2 ...ana ) iar b are nb caractere (b = b1 b2 ...bnb ).
Pentru exemplicare vom considera a = f izic ³i b = matematic. Pentru acest exemplu avem:
na 5, nb 9, a1 f , ..., a5 c, b1 m, b2 a, ..., b9 c.
Vom nota Ai a1 ..ai (0 & i & na) prexul de lungime i al cuvântului a ³i prin Bj b1 ..bj
(0 & j & nb) prexul de lungime j al cuvântului b.
De exemplu: A3 a1 a2 a3 f iz ³i B4 b1 b2 b3 b4 mate.
Evident Ana =a ³i Bnb =b, deci d Ana , Bnb  = d a, b.
A0 ε ³i B0 ε reprezint  cuvinte vide (³iruri de caractere cu lungimea egal  cu 0).
26
ε este simbolul folosit pentru ³irul de caractere vid (Empty string ).
Vom nota prin d Ai , Bj  distanµa Levenshtein între cele dou  prexe Ai ³i Bj .
Ne gândim dac  exist  vechiul ³ablon din gura al turat !
Adic :
ˆ se poate ajunge în poziµia i, j  din poziµiile i  1, j  1 sau
i  1, j  sau i, j  1?
ˆ pentru d Ai , Bj  sunt utile d Ai1 , Bj 1  sau d Ai1 , Bj  sau
d Ai , Bj 1 ?
ˆ pentru AiÀ Bj sunt necesare Ai1 À
Bj 1 sau Ai1 Bj À
sau Ai À Bj 1 ?

Ni³te desene utile:

Figura 15.4: Ai1 ÀB j 1 ³i Ai Bj Figura 15.5: Ai1 ÀB j 1 ³i Ai j Bj

Figura 15.6: Ai1 ÀB j


Figura 15.7: Ai ÀB j 1

Algoritmul este:
1. Iniµializare (pentru i 0 sau j 0):

d Ai , Bj  min i, j  (15.2.1)
24
https://en.wikipedia.org/wiki/Levenshtein_distance
25
https://en.wikipedia.org/wiki/String_metric
26
https://en.wikipedia.org/wiki/Empty_string
CAPITOLUL 15. PROGRAMARE DINAMIC€ 254

2. Calcul (pentru 1 & i & na ³i 1 & j & nb):


~
„
„d Ai , Bj 1   1, inserare bj la sfâr³itul lui Ai
„
„
„
„d Ai1 , Bj   1, ³tergere ai de la sfâr³itul lui Ai
d Ai , Bj  min „
‚
„ (15.2.2)
„
„ 0 dac  ai bj , nimic!
„
„d Ai1 , Bj 1   w
„
„
€
1 dac  ai j bj , înlocuire

def
Folosim o matrice c0..na0..nb unde cij  d Ai , Bj .
Algoritmul în pseudocod este:

Listing 15.2.1: distanµa (de editare) Levenshtein


1 // initializare
2 c[0][0]=0;
3 for(i=1; i<=na; i++) c[i][0]=i;
4 for(j=1; j<=nb; j++) c[0][j]=j;
5
6 // calcul
7 for(i=1; i<=na; i++)
8 for(j=1;j<=nb; j++)
9 {
10 val=(a[i]==b[j]) ? 0 : 1;
11 c[i][j]=min( c[i-1][j-1]+val, c[i-1][j]+1, c[i][j-1]+1 );
12 }

Sunt necesare câteva explicaµii!


Când unul dintre cele dou  prexe este vid, avem dou  situaµii: :
ˆ A0 º Bj necesit  inserarea literelor (caracterelor) b1 , b2 , ..., bj .
Costul este în acest caz c[0][j]=j;
(iniµializarea liniei 0 din matricea de costuri) ³i este dat de cele j inser ri.
ˆ Ai º B0 necesit  ³tergerea literelor (caracterelor) a1 , a2 , ..., ai .
Costul este în acest caz c[i][0]=i;
(iniµializarea coloanei 0 din matricea de costuri) ³i este dat de cele i ³tergeri.

Când niciunul dintre cele dou  prexe nu este vid, avem tot dou  situaµii:
ˆ dac  ai bj , costul este cij  ci  1j  1; (trecerea de la transformarea Ai1 º Bj 1
la Ai º Bj nu aduce costuri suplimentare pentru c  nu sunt necesare nici inser ri, nici
³tergeri ³i nici inlocuiri).
ˆ dac  ai j bj , costul este ... puµin mai complicat de calculat! Trebuie s  ne gândim ... de
unde putem obµine transformarea Ai º Bj ? Sunt trei posibilit µi ³i o vom alege pe cea
mai bun !
 transform m Ai1 º Bj 1 ³i înlocuim ai cu bj ;
costul este ci  1j  1  1 (+1 este costul inlocuirii)
 transform m Ai1 º Bj ... ³i ³tergem ai de la sfâr³itul lui Ai ;
costul este ci  1j   1 (+1 este costul ³tergerii)
 transform m Ai º Bj 1 ³i inser m bj la sfâr³itul lui Ai ;
costul este cij  1  1 (+1 este costul ³tergerii)

Figura 15.8: transform m Ai1 ºB j 1 ³i ai bj


CAPITOLUL 15. PROGRAMARE DINAMIC€ 255

Figura 15.9: transform m Ai1 ºB j 1 ³i înlocuim ai cu bj

Figura 15.10: transform m Ai1 ºB j ... ³i ³tergem ai de la sfâr³itul lui Ai

Figura 15.11: transform m Ai ºB j 1 ³i inser m bj la sfâr³itul lui Ai

Dac  ni se cere ³i un set de operaµii, pornim de la coordonatele na, nb ³i mergem la stânga sau
în sus, astfel:
ˆ inserarea corespunde unei deplas ri la stânga;
ˆ ³tergerea corespunde unei deplas ri în sus;
ˆ modicarea corespunde unei deplas ri pe diagonal  (dac  ai j bj .

Dac  se cere doar distanµa, putem folosi doar memorie de ordinul O n (ultimele dou  linii
2
din matrice) în loc s  folosim memorie de ordinul O n  (întreaga matrice).
Programul surs :

Listing 15.2.2: distEdit.java


1 import java.io.*;
2 class DistEdit
3 {
4 static final char
5 inserez=’i’, // inserez
6 sterg=’s’,
7 modific=’m’,
8 nimic=’ ’; // caracter !!!
9
10 static final char
CAPITOLUL 15. PROGRAMARE DINAMIC€ 256

11 sus=’|’,
12 stanga=’-’,
13 diags=’x’;
14
15 static int na,nb;
16 static int [][] c; // c[i][j] = cost a1..ai --> b1..bj
17
18 static char [][] op; // op = operatia efectuata
19 static char [][] dir; // dir = directii pentru minim !!!
20
21 static String a,b; // a --> b (OBS: a1=a.char(0), ...)
22
23 public static void main(String[] args) throws IOException
24 {
25 int i,j,cmin=0;
26 BufferedReader br=new BufferedReader(
27 new FileReader("distedit5.in"));
28 PrintWriter out=new PrintWriter(
29 new BufferedWriter(new FileWriter("distedit5.out")));
30
31 a=br.readLine();
32 b=br.readLine();
33
34 na=a.length(); // a[0], a[1], ..., a[na-1]
35 nb=b.length(); // b[0], b[1], ..., b[nb-1]
36
37 c=new int[na+1][nb+1];
38 op=new char[na+1][nb+1];
39 dir=new char[na+1][nb+1];
40
41 //System.out.println(a+" --> "+na);
42 //System.out.println(b+" --> "+nb);
43
44 System.out.print("a : ");
45
46 for(i=1;i<=na;i++)
47 {
48 c[i][0]=i; // stergeri din a; b=vid !!!
49 op[i][0]=sterg; // a_i
50 dir[i][0]=sus;
51 System.out.print(a.charAt(i-1));
52 }
53 System.out.println();
54
55 System.out.print("b : ");
56 for(j=1;j<=nb;j++)
57 {
58 c[0][j]=j; // inserari in a; a=vid !!!
59 op[0][j]=inserez; //b_j
60 dir[0][j]=stanga;
61 System.out.print(b.charAt(j-1));
62 }
63 System.out.println("\n");
64
65 op[0][0]=nimic;
66 dir[0][0]=nimic;
67 for(i=1;i<=na;i++)
68 {
69 for(j=1;j<=nb;j++)
70 {
71 if(a.charAt(i-1)==b.charAt(j-1))
72 {
73 c[i][j]=c[i-1][j-1];
74 op[i][j]=nimic;
75 dir[i][j]=diags;
76 }
77 else
78 {
79 cmin=min(c[i-1][j-1],c[i-1][j],c[i][j-1]);
80 c[i][j]=1+cmin;
81 if(cmin==c[i][j-1]) // inserez b_j
82 {
83 op[i][j]=inserez;
84 dir[i][j]=stanga;
85 }
86 else
CAPITOLUL 15. PROGRAMARE DINAMIC€ 257

87 if(cmin==c[i-1][j]) // sterg a_i


88 {
89 op[i][j]=sterg;
90 dir[i][j]=sus;
91 }
92 else
93 if(cmin==c[i-1][j-1]) //inlocuire a_i <-- b_j
94 {
95 op[i][j]=modific;
96 dir[i][j]=diags;
97 }
98 }// else
99 }// for j
100 }// for i
101
102 out.println(a+" --> "+b+"\n");
103
104 afismi(c,out); // matrice de int
105 afismc(dir,out); // matrice de char
106 afismc(op,out); // matrice de char
107
108 afissol(na,nb,out);
109 afissol(na,nb);
110
111 out.println("\nCOST transformare = "+c[na][nb]);
112 out.close();
113 System.out.println("\nCOST transformare = "+c[na][nb]);
114 }// main
115
116 static void afismc(char[][] x, PrintWriter out)
117 {
118 int i,j;
119 out.print(" ");
120 for(j=1;j<=nb;j++) out.print(b.charAt(j-1));
121 for(i=0;i<=na;i++)
122 {
123 out.println();
124
125 if(i>0) out.print(a.charAt(i-1)); else out.print(" ");
126 for(j=0;j<=nb;j++) out.print(x[i][j]);
127 }
128
129 out.println("\n");
130 }// afismc(...)
131
132 static void afismi(int[][] x, PrintWriter out)
133 {
134 int i,j;
135 out.print(" ");
136 for(j=1;j<=nb;j++) out.print(b.charAt(j-1)+" ");
137
138 for(i=0;i<=na;i++)
139 {
140 out.println();
141 if(i>0)
142 out.print(a.charAt(i-1)+" ");
143 else
144 out.print(" ");
145
146 for(j=0;j<=nb;j++) out.print(x[i][j]+" ");
147 }
148 out.println("\n");
149 }// afismi(...)
150
151 static void afissol(int i,int j, PrintWriter out)
152 {
153 if(i==0&&j==0) return;
154
155 if(dir[i][j]==diags) afissol(i-1,j-1,out);
156 else
157 if(dir[i][j]==stanga) afissol(i,j-1,out);
158 else
159 if(dir[i][j]==sus) afissol(i-1,j,out);
160
161 if((i>0)&&(j>0))
162 {
CAPITOLUL 15. PROGRAMARE DINAMIC€ 258

163 if(op[i][j]==sterg)
164 out.println(i+" "+a.charAt(i-1)+" "+op[i][j]);
165 else
166 if(op[i][j]==inserez)
167 out.println(i+" "+" "+op[i][j]+" "+j+" "+b.charAt(j-1));
168 else
169 out.println(i+" "+a.charAt(i-1)+" "+op[i][j]+" "+j+" "+b.charAt(j-1));
170 }
171 else
172 if(i==0)
173 out.println(i+" "+a.charAt(i)+" "+op[i][j]+" "+j+" "+b.charAt(j-1));
174 else
175 if(j==0)
176 out.println(i+" "+a.charAt(i-1)+" "+op[i][j]+" "+j+" "+b.charAt(j));
177 }//afissol(...)
178
179 static void afissol(int i,int j)
180 {
181 if(i==0&&j==0) return;
182
183 if(dir[i][j]==diags) afissol(i-1,j-1);
184 else
185 if(dir[i][j]==stanga) afissol(i,j-1);
186 else
187 if(dir[i][j]==sus) afissol(i-1,j);
188
189 if((i>0)&&(j>0))
190 {
191 if(op[i][j]==sterg)
192 System.out.println(i+" "+a.charAt(i-1)+" "+op[i][j]);
193 else
194 if(op[i][j]==inserez)
195 System.out.println(i+" "+" "+op[i][j]+" "+j+" "+b.charAt(j-1));
196 else
197 System.out.println(i+" "+a.charAt(i-1)+" "+op[i][j]+" "+j+" "+b.charAt(j-1))
;
198 }
199 else
200 if(i==0)
201 //System.out.println(i+" "+a.charAt(i)+" "+op[i][j]+" "+j+" "+b.charAt(j-1));
202 System.out.println(i+" "+op[i][j]+" "+j+" "+b.charAt(j-1));
203 else
204 if(j==0)
205 //System.out.println(i+" "+a.charAt(i-1)+" "+op[i][j]+" "+j+" "+b.charAt(j));
206 System.out.println(i+" "+a.charAt(i-1)+" "+op[i][j]+" ");
207 }//afissolEcran(...)
208
209 static int min(int a, int b) { return a<b ? a:b; }
210
211 static int min(int a, int b, int c) { return min(a,min(b,c)); }
212 }// class
213 /*
214 a : fizic
215 b : matematic
216
217 1 f m 1 m
218 2 i m 2 a
219 3 z m 3 t
220 3 i 4 e
221 3 i 5 m
222 3 i 6 a
223 3 i 7 t
224 4 i 8 i
225 5 c 9 c
226
227 COST transformare = 7
228
229 Process completed.
230 */

Varianta C++ se obµine u³or:

Listing 15.2.3: distEdit.cpp


1 #include <iostream>
2 #include <fstream>
CAPITOLUL 15. PROGRAMARE DINAMIC€ 259

3 #include <string>
4
5 using namespace std;
6
7 const int NMAX = 2e1;
8
9 std::string a, b; // a --> b (OBS: a1=a.char(0), ...)
10
11 char
12 inserez=’i’, // inserez inaintea pozitiei
13 sterg=’s’,
14 modific=’m’,
15 nimic=’ ’; // caracter !!!
16
17 char
18 sus=’|’,
19 stanga=’-’,
20 diags=’x’;
21
22 int na,nb;
23 int c[NMAX+1][NMAX+1]; // c[i][j] = cost a1..ai --> b1..bj
24
25 char op[NMAX+1][NMAX+1]; // op = operatia efectuata
26 char dir[NMAX+1][NMAX+1]; // dir = directii pentru minim !!!
27
28 int min(int a, int b) { return a<b ? a:b; }
29
30 int min(int a, int b, int c) { return min(a,min(b,c)); }
31
32 void read()
33 {
34 std::ifstream cin("distedit5.in");
35
36 std::getline(cin, a);
37 na=a.length(); // a[0], a[1], ..., a[na-1]
38 cout<<"a = "<<a<<" --> na = "<<na<<"\n";
39
40 std::getline(cin, b);
41 nb=b.length(); // b[0], b[1], ..., b[nb-1]
42 cout<<"b = "<<b<<" --> nb = "<<nb<<"\n\n";
43
44 cin.close();
45 }
46
47 void afissol(int i,int j)
48 {
49 if(i==0&&j==0) return;
50
51 if(dir[i][j]==diags) afissol(i-1,j-1);
52 else
53 if(dir[i][j]==stanga) afissol(i,j-1);
54 else
55 if(dir[i][j]==sus) afissol(i-1,j);
56
57 if((i>0)&&(j>0))
58 {
59 if(op[i][j]==sterg)
60 cout<<i<<" "<<a[i-1]<<" "<<op[i][j]<<"\n";
61 else
62 if(op[i][j]==inserez)
63 cout<<i<<" "<<" "<<op[i][j]<<" "<<j<<" "<<b[j-1]<<"\n";
64 else
65 cout<<i<<" "<<a[i-1]<<" "<<op[i][j]<<" "<<j<<" "<<b[j-1]<<"\n";
66 }
67 else
68 if(i==0)
69 //cout<<i<<" "<<a[i]<<" "<<op[i][j]<<" "<<j<<" "<<b[j-1]<<"\n";
70 cout<<i<<" "<<op[i][j]<<" "<<j<<" "<<b[j-1]<<"\n";
71 else
72 if(j==0)
73 //cout<<i<<" "<<a[i-1]<<" "<<op[i][j]<<" "<<j<<" "<<b[j]<<"\n";
74 cout<<i<<" "<<a[i-1]<<" "<<op[i][j]<<"\n";
75 }//afissolEcran(...)
76
77 int main()
78 {
CAPITOLUL 15. PROGRAMARE DINAMIC€ 260

79 int i,j,cmin=0;
80
81 read();
82
83 for(i=1;i<=na;i++)
84 {
85 c[i][0]=i; // stergeri din a; b=vid !!!
86 op[i][0]=sterg; // a_i
87 dir[i][0]=sus;
88 }
89
90 for(j=1;j<=nb;j++)
91 {
92 c[0][j]=j; // inserari in a; a=vid !!!
93 op[0][j]=inserez; //b_j
94 dir[0][j]=stanga;
95 }
96
97 op[0][0]=nimic;
98 dir[0][0]=nimic;
99 for(i=1;i<=na;i++)
100 {
101 for(j=1;j<=nb;j++)
102 {
103 if(a[i-1]==b[j-1])
104 {
105 c[i][j]=c[i-1][j-1];
106 op[i][j]=nimic;
107 dir[i][j]=diags;
108 }
109 else
110 {
111 cmin=min(c[i-1][j-1],c[i-1][j],c[i][j-1]);
112 c[i][j]=1+cmin;
113 if(cmin==c[i][j-1]) // inserez b_j
114 {
115 op[i][j]=inserez;
116 dir[i][j]=stanga;
117 }
118 else
119 if(cmin==c[i-1][j]) // sterg a_i
120 {
121 op[i][j]=sterg;
122 dir[i][j]=sus;
123 }
124 else
125 if(cmin==c[i-1][j-1]) //inlocuire a_i <-- b_j
126 {
127 op[i][j]=modific;
128 dir[i][j]=diags;
129 }
130 }// else
131 }// for j
132 }// for i
133
134 cout<<a<<" --> "<<b<<"\n\n";
135
136 afissol(na,nb);
137
138 // in fisier
139 //std::ofstream cout("distedit5.out");
140 //cout <<c[na][nb]<< ’\n’;
141
142 // pe ecran
143 cout << "\ncost = "<<c[na][nb]<< ’\n’;
144
145 return 0;
146 }
147 /*
148 a = fizic --> na = 5
149 b = matematic --> nb = 9
150
151 fizic --> matematic
152
153 1 f m 1 m
154 2 i m 2 a
CAPITOLUL 15. PROGRAMARE DINAMIC€ 261

155 3 z m 3 t
156 3 i 4 e
157 3 i 5 m
158 3 i 6 a
159 3 i 7 t
160 4 i 8 i
161 5 c 9 c
162
163 cost = 7
164
165 Process returned 0 (0x0) execution time : 0.094 s
166 Press any key to continue.
167 */

15.2.6 Problema rucsacului 0  1

https://www.guru99.com/knapsack-problem-dynamic-programming.html
With the weight limit j, the optimal selections among packages 1, 2, ..., i – 1, i to have the
largest value will have two possibilities:
If package i is not selected, B[i][j] is the maximum possible value by selecting among packages
1, 2, ..., i – 1 with weight limit of j. You have:
B[i][j] = B[i – 1][j]
If package i is selected (of course only consider this case when W[i] ≤ j) then B[i][j] is equal to
the value V[i] of package i plus the maximum value can be obtained by selecting among packages
1, 2, ..., i – 1 with weight limit (j – W[i]). That is, in terms of the value you have:
B[i][j] = V[i] + B[i – 1][j – W[i]]
Due to the creation of B[i][j], which is the maximum possible value, B[i][j] will be the max of
the above 2 values. Basis of Dynamic Programming
So, you have to consider if it is better to choose package i or not. From there you have the
recursive formula as follows:
B[i][j]= max(B[i – 1][j], V[i]+B[i – 1][j – W[i]]
It is easy to see B[0][j] = maximum value possible by selecting from 0 package = 0. Calculate
the Table of Options
You build a table of options based on the above recursive formula. To check if the results are
correct (if not exactly, you rebuild the objective function B[i][j]). Through the creation of the
objective function B[i][j] and the table of options, you will orient the tracing.
Table of options B includes n + 1 lines, M + 1 columns,
Firstly, lled with the basis of dynamic programming: Line 0 includes all zeros. Using recursive
formulas, use line 0 to calculate line 1, use line 1 to calculate line 2, etc. ... until all lines are
calculated.
Trace
When calculating the table of options, you are interested in B[n][M] which is the maximum
value obtained when selecting in all n packages with the weight limit M.
If B[n][M] = B[n – 1][M] then package n is not selected, you trace B[n – 1][M]. If B[n][M]
≠ B[n – 1][M], you notice that the optimal selection has the package n and trace B[n –
1][M – W[n]].
Continue to trace until reaching row 0 of the table of options. Algorithm to Look Up the Table
of Options to Find the Selected Packages
Note: If B[i][j] = B[i – 1][j], the package i is not selected. B[n][W] is the optimal total value
of package put into the knapsack.
Steps for tracing:
Step 1: Starting from i = n, j = M. Step 2: Look in column j, up from bottom, you nd the
line i such that B[i][j] > B[i – 1][j]. Mark selected package i: Select [i] = true;
Daca B[i][j] == B[i – 1][j] înseamn  c  obiectul i nu a fost folosit (pus în rucsac).
Step 3: j = B[i][j] – W[i]. If j > 0, go to step 2, otherwise go to step 4 Step 4: Based on
the table of options to print the selected packages.
Explanation of Knapsack code:
Create table B[][]. Set default value for each cell is 0. Build table B[][] in bottom-up manner.
Calculate the table of options with the retrieval formula. Calculate B[i][j]. If you do not select
package i. Then evaluate: if you select package i, it will be more benecial then reset B[i][j]. Trace
CAPITOLUL 15. PROGRAMARE DINAMIC€ 262

the table from row n to row 0. If you choose package n. Once select package n, can only add
weight M - W[n - 1].
https://www.geeksforgeeks.org/0-1-knapsack-problem-dp-10/?ref=leftb
ar-rightbar
Method 1: Recursion. Approach: A simple solution is to consider all subsets of items and
calculate the total weight and value of all subsets. Consider the only subsets whose total weight is
smaller than W. From all such subsets, pick the maximum value subset. Optimal Sub-structure:
To consider all subsets of items, there can be two cases for every item.
***
Case 1: The item is included in the optimal subset.
Case 2: The item is not included in the optimal set.
***
Therefore, the maximum value that can be obtained from ‘n’ items is the max of the
following two values.
Maximum value obtained by n-1 items and W weight (excluding nth item). Value of nth item
plus maximum value obtained by n-1 items and W minus the weight of the nth item (including
nth item).
If the weight of ‘nth’ item is greater than ‘W’, then the nth item cannot be
included and Case 1 is the only possibility.
...

import java.io.*;
class Rucsac01
{
public static void main(String[] args) throws IOException
{
int n,m;
int i,j;
int[] g,v;
int[][] c;

PrintWriter out=new PrintWriter(


new BufferedWriter(new FileWriter("rucsac.out")));
StreamTokenizer st=new StreamTokenizer(
new BufferedReader(new FileReader("rucsac.in")));

st.nextToken(); n=(int)st.nval;
st.nextToken(); m=(int)st.nval;

g=new int[n+1];
v=new int[n+1];
c=new int[n+1][m+1];

for(i=1;i<=n;i++) { st.nextToken(); g[i]=(int)st.nval; }


for(i=1;i<=n;i++) { st.nextToken(); v[i]=(int)st.nval; }

for(i=1; i<=n; i++) c[i][0]=0;


for(j=0; j<=m; j++) c[0][j]=0;

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


for(j=1; j<=m; j++)
if(g[i]>j)
c[i][j]=c[i-1][j];
else
c[i][j]=max( c[i-1][j], c[i-1][j-g[i]] + v[i] );

out.println(c[n][m]);
out.close();
}// main(...)

static int max(int a, int b)


CAPITOLUL 15. PROGRAMARE DINAMIC€ 263

{
if(a>b) return a; else return b;
}// max(...)
}

15.2.7 Problema schimbului monetar

import java.io.*;
class Schimb
{
public static void main(String[] args) throws IOException
{
int v,n;
int i,j;
int[] b, ns;

PrintWriter out=new PrintWriter(


new BufferedWriter(new FileWriter("schimb.out")));
StreamTokenizer st=new StreamTokenizer(
new BufferedReader(new FileReader("schimb.in")));

st.nextToken(); v=(int)st.nval;
st.nextToken(); n=(int)st.nval;

b=new int[n+1];
ns=new int[v+1];

for(i=1;i<=n;i++) { st.nextToken(); b[i]=(int)st.nval; }

ns[0]=1;
for(i=1; i<=n; i++)
for(j=b[i]; j<=v; j++)
ns[j]+=ns[j-b[i]];
out.println(ns[v]);
out.close();
}// main
}// class

15.2.8 Problema travers rii matricei

Traversarea unei matrice de la Vest la Est; sunt permise deplas ri spre vecinii unei poziµii
(chiar ³i deplas ri prin "exteriorul" matricei).

import java.io.*;
class Traversare
{
public static void main(String[] args) throws IOException
{
int m,n;
int i,j,imin;
int[][] a,c,t;
int[] d;
int cmin,minc;

PrintWriter out=new PrintWriter(


new BufferedWriter(new FileWriter("traversare.out")));
StreamTokenizer st=new StreamTokenizer(
new BufferedReader(new FileReader("traversare.in")));
CAPITOLUL 15. PROGRAMARE DINAMIC€ 264

st.nextToken(); m=(int)st.nval;
st.nextToken(); n=(int)st.nval;

a=new int[m][n];
c=new int[m][n];
t=new int[m][n];
d=new int[n];

for(i=0;i<m;i++)
for(j=0;j<n;j++)
{
st.nextToken();
a[i][j]=(int)st.nval;
}

for(i=0;i<m;i++) c[i][0]=a[i][0];
for(j=1; j<n; j++)
for(i=0; i<m; i++)
{
minc=min(c[(i+m-1)%m][j-1], c[i][j-1], c[(i+1)%m][j-1]);
c[i][j]=a[i][j]+minc;
if(minc==c[(i+m-1)%m][j-1]) t[i][j]=((i+m-1)%m)*n+(j-1);
else
if(minc==c[i][j-1]) t[i][j]=i*n+(j-1);
else t[i][j]=((i+1)%m)*n+(j-1);
}

imin=0;
cmin=c[0][n-1];
for(i=1;i<m;i++)
if(c[i][n-1]<cmin)
{
cmin=c[i][n-1];
imin=i;
}
out.println(cmin);

d[n-1]=imin*n+(n-1);
j=n-1;
i=imin;
while(j>0)
{
i=t[i][j]/n;
j--;
d[j]=i*n+j;
}
for(j=0;j<n;j++) out.println((1+d[j]/n)+" "+(1+d[j]%n));
out.close();
}// main(...)

static int min(int a, int b)


{
if(a<b) return a; else return b;
}

static int min(int a, int b, int c)


{
return min(min(a,b),c);
}
CAPITOLUL 15. PROGRAMARE DINAMIC€ 265

}// class
/*
traversare.in traversare.out
3 4 6
2 1 3 2 2 1
1 3 5 4 1 2
3 4 2 7 3 3
1 4
*/

15.2.9 Problema segment rii vergelei

Scopul algoritmului este de a realiza n t ieturi, dea lungul unei vergele în locuri pre-specicate,
cu efort minim (sau cost). Costul ec rei operaµii de t iere este proporµional cu lungimea vergelei
care trebuie t iat . În viaµa real , ne putem imagina costul ca ind efortul depus pentru a plasa
vergeaua (sau un bu³tean!) în ma³ina de t iat.
Consider m ³irul de numere naturale 0 $ x1 $ x2 $ ... $ xn $ xn1 în care xn1 reprezint 
lungimea vergelei iar x1 , x2 , ..., xn reprezint  abscisele punctelor în care se vor realiza t ieturile
(distanµele faµ  de cap tul "din stânga" al vergelei).
Not m prin cij  (i $ j ) costul minim necesar realiz rii tuturor t ieturilor segmentului de
vergea xi ..xj .
Evident cii  1 0 pentru c  nu este necesar  nici o t ietur .
Pentru j % i s  presupunem c  realiz m prima t ietur  în xk (i $ k $ j ). Din vergeaua
xi ...xj  obµinem dou  bucaµi mai mici: xi ...xk  ³i xk ...xj . Costul pentru t ierea vergelei
xi ...xj  este format din costul transportului acesteia la ma³ina de t iat (xj  xi ) + costul t ierii
vergelei xi ...xk  (adic  cik ) ³i + costul t ierii vergelei xk ...xj  (adic  ck j ).
Dar, ce valoare are k ? Evident, k trebuie s  aib  acea valoare care s  minimizeze expresia
cik   ck j . Obµinem:

cij  xj  xi  min rcik   ck j x


i$k$j

import java.io.*;
class Vergea
{
static int n,nt=0;
static int x[];
static int c[][];
static int t[][];

static BufferedReader br; // pentru "stop" (pentru depanare!)

public static void main(String[]args) throws IOException


{
int i,j,h,k,min,kmin;
StreamTokenizer st=new StreamTokenizer(
new BufferedReader(new FileReader("vergea.in")));
PrintWriter out=new PrintWriter(
new BufferedWriter(new FileWriter("vergea.out")));
br=new BufferedReader(new InputStreamReader(System.in));

st.nextToken(); n=(int)st.nval;
x=new int[n+2];
c=new int[n+2][n+2];
t=new int[n+2][n+2];

for(i=1;i<=n+1;i++) { st.nextToken(); x[i]=(int)st.nval; }

System.out.println("n="+n);
System.out.print("x: ");
for(i=1;i<=n+1;i++) System.out.print(x[i]+" ");
CAPITOLUL 15. PROGRAMARE DINAMIC€ 266

System.out.println();

for(i=0;i<=n;i++) c[i][i+1]=0;

j=-1;
for(h=2;h<=n+1;h++) // lungimea vargelei
{
for(i=0;i<=n+1-h;i++) // inceputul vargelei
{
j=i+h; // sfarsitul vargelei
c[i][j]=x[j]-x[i];
min=Integer.MAX_VALUE;
kmin=-1;
for(k=i+1;k<=j-1;k++)
if(c[i][k]+c[k][j]<min)
{
min=c[i][k]+c[k][j];
kmin=k;
}
c[i][j]+=min;
t[i][j]=kmin;
}//for i
}// for h

out.println(c[0][n+1]);
out.close();

afism(c); afism(t);
System.out.println("Ordinea taieturilor: \n");
taieturi(0,n+1);
System.out.println();
}//main

public static void taieturi(int i,int j) throws IOException


{
if(i>=j-1) return;
int k;
k=t[i][j];
System.out.println((++nt)+" : "+i+".."+j+" --> "+k);
//br.readLine(); // "stop" pentru depanare !
if((i<k)&&(k<j)) { taieturi(i,k); taieturi(k,j); }
}//taieturi

static void afism(int[][] x) // pentru depanare !


{
int i,j;
for(i=0;i<=n+1;i++)
{
for(j=0;j<=n+1;j++) System.out.print(x[i][j]+" ");
System.out.println();
}
System.out.println();
}// afism(...)
}//class
/*
n=5
x: 2 4 5 8 12 15

0 0 4 8 16 27 38
0 0 0 3 9 19 29
CAPITOLUL 15. PROGRAMARE DINAMIC€ 267

0 0 0 0 4 12 22
0 0 0 0 0 7 17
0 0 0 0 0 0 7
0 0 0 0 0 0 0
0 0 0 0 0 0 0

0 0 1 1 2 3 4
0 0 0 2 3 4 4
0 0 0 0 3 4 4
0 0 0 0 0 4 4
0 0 0 0 0 0 5
0 0 0 0 0 0 0
0 0 0 0 0 0 0

Ordinea taieturilor:

1 : 0..6 --> 4
2 : 0..4 --> 2
3 : 0..2 --> 1
4 : 2..4 --> 3
5 : 4..6 --> 5
*/

15.2.10 Triangularizarea poligoanelor convexe

Consider m un poligon convex cu n vârfuri numerotate cu 1, 2, ..., n (în gur  n 9) ³i dorim


s  obµinem o triangularizare în care suma lungimilor diagonalelor trasate s  e minim  (ca ³i cum
am dori s  consum m cât mai puµin tu³ pentru trasarea acestora!).

1 9 1 9 9
1 11 9 9
8
2 8 2 8 8 8
2

7 7 7
7

3 3 3
6 5 6 6 6
4 5 4 5 5 4 555

Evident, orice latur  a poligonului face parte dintr-un triunghi al triangulaµiei. Consider m
la început latura 1, 9. S  presupunem c  într-o anumit  triangulaµie optim  latura 1, 9 face
parte din triunghiul 1, 5, 9. Diagonalele triangulaµiei optime vor genera o triangulaµie optim  a
poligoanelor convexe 1, 2, 3, 4, 5 ³i 5, 6, 7, 8, 9. Au ap rut astfel dou  subprobleme ale problemei
iniµiale.
S  not m prin p i, k, j  perimetrul triunghiului i, k, j  (i $ k $ j ) i³i prin cij  costul minim
al triagulaµiei poligonului convex i, i  1, ..., j  (unde i $ j ). Atunci:

cij  0, dac  j i1

³i
cij  min rp i, k, j   cik   ck j x, dac  i $ j &n
i&k$j

15.2.11 Algoritmul Roy-Floyd-Warshall


// Lungime drum minim intre oricare doua varfuri in graf orientat ponderat.
// OBS: daca avem un drum de lungime minima de la i la j atunci acest drum
// va trece numai prin varfuri distincte, iar daca varful k este varf intermediar,
// atunci drumul de la i la k si drumul de la k la j sunt si ele minime
// (altfel ar exista un drum mai scurt de la i la j); astfel, este indeplinit
// "principiul optimalitatii"
CAPITOLUL 15. PROGRAMARE DINAMIC€ 268

import java.io.*;
class RoyFloydWarshall // O(n^3)
{
static final int oo=0x7fffffff;
static int n,m;
static int[][] d;

public static void main(String[]args) throws IOException


{
int i,j,k;

StreamTokenizer st=new StreamTokenizer(


new BufferedReader(new FileReader("RoyFloydWarshall.in")));
PrintWriter out=new PrintWriter(
new BufferedWriter(new FileWriter("RoyFloydWarshall.out")));

st.nextToken(); n=(int)st.nval;
st.nextToken(); m=(int)st.nval;

d=new int[n+1][n+1];

for(i=1;i<=n;i++) for(j=1;j<=n;j++) d[i][j]=oo;


for(k=1;k<=m;k++)
{
st.nextToken(); i=(int)st.nval;
st.nextToken(); j=(int)st.nval;
st.nextToken(); d[i][j]=d[j][i]=(int)st.nval;
}

for(k=1;k<=n;k++)
for(i=1;i<=n;i++) // drumuri intre i si j care trec
for(j=1;j<=n;j++) // numai prin nodurile 1, 2, ..., k
if((d[i][k]<oo)&&(d[k][j]<oo))
if(d[i][j]>d[i][k]+d[k][j]) d[i][j]=d[i][k]+d[k][j];

for(i=1;i<=n;i++)
{
for(j=1;j<=n;j++)
if(d[i][j]<oo) System.out.print(d[i][j]+" ");
else System.out.print("*"+" ");
System.out.println();
}
out.close();
}//main
}//class

/*
6 6
1 2 3 2 3 1 * * *
1 3 1 3 4 2 * * *
2 3 2 1 2 2 * * *
4 5 1 * * * 2 1 2
5 6 3 * * * 1 2 3
4 6 2 * * * 2 3 4
*/
Capitolul 16

Potrivirea ³irurilor

Consider m un text (un ³ir de caractere) t t1 , t2 , ..., tn  ³i un ³ablon (tot un ³ir de caractere,
numit pattern în englez ) p p1 , p2 , ..., pm . Consider m m & n ³i dorim s  determin m dac 
textul t conµine ³ablonul p, adic , dac  exist  0 & d & n  m astfel încât tdi pi pentru orice
1 & i & m. Problema potrivirii ³irurilor const  în determinarea tuturor valorilor d (considerate
deplasamente) cu proprietatea menµionat .

16.1 Un algoritm inecient


Pentru ecare poziµie i cuprins  între 1 ³i n  m  1 vom verica dac  sub³irul
xi , xi1 , ..., xim1  coincide cu y .

import java.io.*;
class PotrivireSir
{
static char[] t,p;
static int n,m;

public static void main(String[] args) throws IOException


{
int i,j;
String s;

BufferedReader br=new BufferedReader(


new FileReader("potriviresir.in"));
PrintWriter out=new PrintWriter(
new BufferedWriter(new FileWriter("potriviresir.out")));

s=br.readLine();
n=s.length();
t=new char[n+1];
for(i=0;i<n;i++) t[i+1]=s.charAt(i);
System.out.print("t : ");
afisv(t,1,n);

s=br.readLine();
m=s.length();
p=new char[m+1];
for(i=0;i<m;i++) p[i+1]=s.charAt(i);
System.out.print("p : ");
afisv(p,1,m);
System.out.println();

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

269
CAPITOLUL 16. POTRIVIREA “IRURILOR 270

{
for(j=1;j<=m;j++) if(p[j]!=t[i+j-1]) break;
j--; // ultima pozi\c tie potrivita

if(j==m) { afisv(t,1,n); afisv(p,i,i+j-1); System.out.println(); }


}
out.close();
}//main()

static void afisv(char[] x, int i1, int i2)


{
int i;
for(i=1;i<i1;i++) System.out.print(" ");
for(i=i1;i<=i2;i++) System.out.print(x[i]);
System.out.println();
}// afisv(...)
}//class
/*
x : abababaababaababa
y : abaabab

abababaababaababa
abaabab

abababaababaababa
abaabab
*/

16.2 Un algoritm ecient - KMP


Algoritmul KMP (Knuth-Morris-Pratt) determin  potrivirea ³irurilor folosind informaµii refe-
ritoare la potrivirea sub³irului cu diferite deplasamente ale sale.
Pentru
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
t: a b a b a b a a b a b a a b a b a

1 2 3 4 5 6 7
³i exist  dou  potriviri:
p: a b a a b a b

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
t: a b a b a b a a b a b a a b a b a
p: a b a a b a b
1 2 3 4 5 6 7

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
t: a b a b a b a a b a b a a b a b a
p: a b a a b a b
1 2 3 4 5 6 7

“irul inecient de încerc ri este:


CAPITOLUL 16. POTRIVIREA “IRURILOR 271

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
t: a b a b a b a a b a b a a b a b a
p: a b a a b a b
p: a b a a b a b
p: a b a a b a b
p: a b a a b a b
p: a b a a b a b *
p: a b a a b a b
p: a b a a b a b
p: a b a a b a b
p: a b a a b a b
p: a b a a b a b *
p: a b a a b a b
1 2 3 4 5 6 7

Prima nepotrivire din ecare încercare este evidenµiat  prin caracter boldat iar soluµiile sunt
marcate cu *.
Dorim s  avans m cu mai mult de un pas la o nou  încercare, f r  s  risc m s  pierdem vreo
soluµie!

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
t: a b a b a b a a b a b a a b a b a
p: a b a a b a b
p: a b a a b a b
p: a b a a b a b *
p: a b a a b a b *
p: a b a

Not m prin ti..j  secvenµa de elemente consecutive ti , ..., tj  (cuprins  între poziµiile i ³i j )
din ³irul t t1 , t2 , ..., tn .
S  presupunem c  suntem la un pas al veric rii potrivirii cu un deplasament d ³i prima
nepotrivire a ap rut pe poziµia i din text ³i poziµia j  1 din ³ablon, deci ti  j..i  1 p1..j 
³i ti j pj  1.
¬
Care este cel mai bun deplasament d pe care trebuie s -l încercam?

xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
j j+1 m
d p: xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
1 ... j+1-k ...
xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
...
xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
xxxxxxxxxxxxxxxxxxxxxxxxxxxx
xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
xxxxxxxxxxxxxxxxxxxxxxxxxxxx
xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
xxxxxxxxxxxxxxxxxxxxxxxxxxxx
xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
xxxxxxxxxxxxxxxxxxxxxxxxxxxx
xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
xxxxxxxxxxxxxxxxxxxxxxxxxxxx
t: 1 ... i-j ... ...
xxxxxxxxxxxxxxxxxxxxxxxxxxxx
xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
i-k i-1
xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
xxxxxxxxxxxxxxxxxxxxxxxxxxxx i ... ... ... n
xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
xxxxxxxxxxxxxxxxxxxxxxxxxxxx
xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
xxxxxxxxxxxxxxxxxxxxxxxxxxxx
xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
xxxxxxxxxxxxxxxxxxxxxxxxxxxx
xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
xxxxxxxxxxxxxxxxxxxxxxxxxxxx
xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
xxxxxxxxxxxxxxxxxxxxxxxxxxxx
xxxxxxxxxxxxxxxxxxxxxxxxxxxx
xxxxxxxxxxxxxxxxxxxxxxxxxxxx
xxxxxxxxxxxxxxxxxxxxxxxxxxxx
1 k k+1 m
d' p:
xxxxxxxxxxxxxxxxxxxxxxxxxxxx
...
xxxxxxxxxxxxxxxxxxxxxxxxxxxx
xxxxxxxxxxxxxxxxxxxxxxxxxxxx
xxxxxxxxxxxxxxxxxxxxxxxxxxxx
... ...
xxxxxxxxxxxxxxxxxxxxxxxxxxxx

Figura 16.1: Deplasare optim 

Folosind Figura ??, dorim s  determin m cel mai mare indice k $ j astfel încât p1..k 
pj  1  k..j . Cu alte cuvinte, dorim s  determin m cel mai lung sux al secvenµei p1..j 
¬
iar noul deplasament d trebuie ales astfel încât s  realizeze acest lucru. Este astfel realizat  ³i
potrivirea textului t cu ³ablonul p, ti  k..i  1 p1..k .
R mâne s  veric m apoi dac  ti pk  1.
Observ m c  noul deplasament depinde numai de ³ablonul p ³i nu are nici o leg tur  cu textul
t.
Algoritmul KMP utilizeaz  pentru determinarea celor mai lungi suxe o funcµie numit  next.
Dat ind ³irul de caractere p1..m, funcµia

next  r1, 2, ..., mx r0, 1, ..., m  1x

este denit  astfel:

next j  maxrk ©k $ j ³i p1..k este sux pentru p1..j x.


Cum determin m practic valorile funcµiei next?
CAPITOLUL 16. POTRIVIREA “IRURILOR 272

Initializ m next1 0.
Presupunem c  au fost determinate valorile next1, next2, ..., nextj .
Cum determin m nextj  1?

1 k+1-k' k
xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
p: xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
... ...
xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
k+1 ... ...
xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
... ...
xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
p: 1 ... j+1-k ... j+1-k' ...... j j+1 ... ...
xxxxxxxxxxxxxxxxxxxxxxxxxxxxx
xxxxxxxxxxxxxxxxxxxxxxxxxxxxx
xxxxxxxxxxxxxxxxxxxxxxxxxxxxx
xxxxxxxxxxxxxxxxxxxxxxxxxxxxx
xxxxxxxxxxxxxxxxxxxxxxxxxxxxx
xxxxxxxxxxxxxxxxxxxxxxxxxxxxx
xxxxxxxxxxxxxxxxxxxxxxxxxxxxx
.. .. ..
xxxxxxxxxxxxxxxxxxxxxxxxxxxxx k'+1 ... ...
p: xxxxxxxxxxxxxxxxxxxxxxxxxxxxx
xxxxxxxxxxxxxxxxxxxxxxxxxxxxx
xxxxxxxxxxxxxxxxxxxxxxxxxxxxx
xxxxxxxxxxxxxxxxxxxxxxxxxxxxx
1 k'

Figura 16.2: Funcµia next

Ne ajut m de Figura ?? pentru a urm ri mai u³or raµionamentul! În aceast  gur  nextj  k
¬
³i nextk  k .
Dac  pj  1 pk  1 (folosind notaµiile din gur ) atunci nextj  1 k  1.
Obµinem:

dac  pj  1 pnext j   1 atunci nextj  1 next j   1.

Ce se întâmpl  dac  pj  1 j pk  1?


¬
C ut m un sux mai mic pentru p1..j ! Fie acesta p1..k . Dar acest sux mai mic este cel
mai lung sux pentru p1..k , cu alte cuvinte
¬
k next k  next next j .
¬ ¬
Astfel, dac  pj  1 pk  1 atunci next j  1 k  1.
Obµinem:

dac  pj  1 pnext next j   1 atunci nextj  1 next next j   1.

Dac  nici acum nu avem egalitate de caractere, vom continua acela³i raµionament pân  cand
g sim o egalitate de caractere sau lungimea prexului c utat este 0. Evident, acest algoritm se
termin  într-un num r nit de pa³i pentru c  j % k % k % ... ' 0. Dac  ajungem la 0, atunci vom
¬

avea next j  1 0.
Ordinul de complexitate al algoritmului KMP este O n  m.

import java.io.*;
class KMP
{
static int na=0; // nr aparitii
static char[] t,p; // t[1..n]=text, p[1..m]=pattern
static int[] next;

static void readData() throws IOException


{
String s;
char[] sc;
int i,n,m;
BufferedReader br=new BufferedReader(new FileReader("kmp.in"));
s=br.readLine();
sc=s.toCharArray();
n=sc.length;
t=new char[n+1];
for(i=1;i<=n;i++) t[i]=sc[i-1];

s=br.readLine();
sc=s.toCharArray();
m=sc.length;
CAPITOLUL 16. POTRIVIREA “IRURILOR 273

p=new char[m+1];
for(i=1;i<=m;i++) p[i]=sc[i-1];
}//readData()

static int[] calcNext(char[] p)


{
int m=p.length-1;
int[] next=new int[m+1]; // next[1..m] pentru p[1..m]
next[1]=0; // initializare
int k=0; // nr caractere potrivite
int j=2;
while(j<=m)
{
while(k>0&&p[k+1]!=p[j]) k=next[k];
if(p[k+1]==p[j]) k++;
next[j]=k; j++;
}
return next;
}// calcNext()

static void kmp(char[] t, char[] p) // t[1...n], p[1..m]


{
int n=t.length-1, m=p.length-1;
next=calcNext(p);
int j=0; // nr caractere potrivite deja
int i=1; // ultima pozitie a sufixului
while(i<=n) // t[1..n]
{
while(j>0&&p[j+1]!=t[i]) j=next[j];
if(p[j+1]==t[i]) j++;
if(j==m)
{
na++;
System.out.println("pattern cu deplasarea "+(i-m)+" : ");
afissol(t,p,i-m);
j=next[j];
}
i++;
}// while
}// kmp

static void afissol(char[] t, char[] p, int d)


{
int i, n=t.length-1, m=p.length-1;
for(i=1;i<=n;i++) System.out.print(t[i]);
System.out.println();
for(i=1;i<=d;i++) System.out.print(" ");
for(i=1;i<=m;i++) System.out.print(p[i]);
System.out.println();
}// afissol(...)

public static void main(String[] args) throws IOException


{
readData();
kmp(t,p);
PrintWriter out=new PrintWriter(
new BufferedWriter(new FileWriter("kmp.out")));
out.println(na);
out.close();
}//main()
CAPITOLUL 16. POTRIVIREA “IRURILOR 274

}//class
/*
pattern apare cu deplasarea 5 :
12312123412123412
1234
pattern apare cu deplasarea 11 :
12312123412123412
1234
*/

16.3 Probleme rezolvate


16.3.1 Circular - Campion 2003-2004 Runda 6
Autor: prof. Mot Nistor, Colegiul National "N.Balcescu" - Braila
Se spune c  ³irul y1 , y2 , ..., yn este o permutare circular  cu p poziµii a ³irului x1 , x2 , ..., xn dac 
y1 xp  1, y2 xp  2, ..., yn xp  n, unde indicii mai mari ca n se consider  modulo n, adic 
indicele k , cu k % n se refer  la elementul de indice k  n.
Cerinµ 
Pentru dou  ³iruri date determinaµi dac  al doilea este o permutare circular  a primului ³ir.
Date de intrare
Pe prima linie a ³ierului de intrare circular.in este scris numarul natural n. Pe liniile
urm toare sunt dou  ³iruri de caractere de lungime n, formate numai din litere mari ale alfabetului
latin.
Date de ie³ire
Pe prima linie a ³ierului circular.out se va scrie cel mai mic num r natural p pentru care ³irul
de pe linia a treia este o permutare circular  cu p poziµii a ³irului de pe linia a doua, sau num rul
1 dac  nu avem o permutare circular .

Restricµii ³i preciz ri
a 1 & n & 20000
Exemple
circular.in circular.out
10 7
ABCBAABBAB
BABABCBAAB
Timp maxim de execuµie/test: 0.1 secunde

Rezolvare (indicaµia autorului): O variant  cu dou  "for"-uri e foarte u³or de scris, dar nu
se încadreaz  în timp pentru n mare.
Folosim algoritmului KMP de c utare a unui sub³ir.
Concaten m primul ³ir cu el însu³i ³i c ut m prima apariµie a celui de-al doilea ³ir în ³irul nou
format. În realitate nu e nevoie de concatenarea efectiv  a ³irului, doar µinem cont c  indicii care
se refer  la ³irul "mai lung" trebuie luaµi modulo n.

import java.io.*;
class Circular
{
static int n,d=-1; // pozitia de potrivire
static char[] x,y; // x[1..n]=text, y[1..m]=pattern
static int[] next;

static void readData() throws IOException


{
String s;
char[] sc;
int i;
BufferedReader br=new BufferedReader(new FileReader("circular.in"));
CAPITOLUL 16. POTRIVIREA “IRURILOR 275

n=Integer.parseInt(br.readLine()); // System.out.println("n="+n);

x=new char[n+1];
y=new char[n+1];

s=br.readLine(); sc=s.toCharArray(); // System.out.println("x="+s);


for(i=1;i<=n;i++) x[i]=sc[i-1];

s=br.readLine(); sc=s.toCharArray(); // System.out.println("y="+s);

for(i=1;i<=n;i++) y[i]=sc[i-1];
}//readData()

static int[] calcNext(char[] p)


{
int m=n;
int[] next=new int[m+1]; // next[1..m] pentru p[1..m]
next[1]=0; // initializare
int k=0; // nr caractere potrivite
int j=2;
while(j<=m)
{
while(k>0&&p[k+1]!=p[j]) k=next[k];
if(p[k+1]==p[j]) k++;
next[j]=k; j++;
}
return next;
}// calcNext()

static void kmp(char[] t, char[] p) // t[1...n], p[1..m]


{
int m=p.length-1;
next=calcNext(p);
int j=0; // nr caractere potrivite deja
int i=1; // ultima pozitie a sufixului
while((i<=2*n)&&(d==-1)) // t[1..n]
{
while(j>0&&p[j+1]!=t[(i>n)?(i-n):i]) j=next[j];
if(p[j+1]==t[(i>n)?(i-n):i]) j++;
if(j==m) { d=i-n; break; }
i++;
}// while
}// kmp

public static void main(String[] args) throws IOException


{
readData();
kmp(x,y);
PrintWriter out=new PrintWriter(
new BufferedWriter(new FileWriter("circular.out")));
out.println(d);
out.close();
}//main()
}//class
/*
circular.in circular.out
----------- ------------
20 5
12312123412123412341
CAPITOLUL 16. POTRIVIREA “IRURILOR 276

12341212341234112312
*/

16.3.2 Cifru - ONI2006 baraj

Copiii solarieni se joac  adesea trimiµându-³i mesaje codicate. Pentru codicare ei folosesc
un cifru bazat pe o permutare p a literelor alfabetului solarian ³i un num r natural d.
Alfabetul solarian conµine m litere foarte complicate, a³a c  noi le vom reprezenta prin numere
de la 1 la m.
Dat ind un mesaj în limbaj solarian, reprezentat de noi ca o succesiune de n numere cuprinse
între 1 ³i m, c1 c2 ...cn , codicarea mesajului se realizeaz  astfel: se înlocuie³te ecare liter  ci cu
p ci , apoi ³irul obµinut p c1 p c2 ...p cn  se rote³te spre dreapta, f când o permutare circular 
cu d poziµii rezultând ³irul p cnd1 ...p cn1 p cn p c1 p c2 ...p cnd .
De exemplu, pentru mesajul 213321, permutarea p 312 (adic  p 1 3, p 2 1, p 3 2)
³i d 2. Aplicând permutarea p vom obµine ³irul 132213, apoi rotind spre dreapta ³irul cu dou 
poziµii obµinem codicarea 131322.
Cerinµ :
Date ind un mesaj necodicat ³i codicarea sa, determinaµi cifrul folosit (permutarea p ³i
num rul d).
Date de intrare:
Fi³ierul de intrare cifru.in conµine pe prima linie numele naturale n ³i m, separate prin spaµiu,
reprezentând lungimea mesajului ³i respectiv num rul de litere din alfabetul solarian. Pe cea de a
doua linie este scris mesajul necodicat ca o succesiune de n numere cuprinse între 1 ³i m separate
prin câte un spaµiu. Pe cea de a treia linie este scris mesajul codicat ca o succesiune de n numere
cuprinse între 1 ³i m separate prin câte un spaµiu.
Date de ie³ire:
Fi³ierul de ie³ire cifru.out va conµine pe prima linie num rul natural d, reprezentând num rul
de poziµii cu care s-a realizat permutarea circular  spre dreapta. Dac  pentru d exist  mai multe
posibilit µii se va alege valoarea minim . Pe urm toarea linie este descris  permutarea p. Mai
exact se vor scrie valorile p 1, p 2, ..., p m separate prin câte un spaµiu.
Restricµii:
n & 100000
m & 9999
Mesajul conµine ecare num r natural din intervalul 1, m cel puµin o dat .
Pentru teste cu m & 5 se acord  40 de puncte din care 20 pentru teste ³i cu n & 2000.
Exemplu:
cifru.in cifru.out
63 2
213321 312
131322
Timp maxim de execuµie/test: 0.2 secunde
Indicatii de rezolvare:
Soluµia comisiei

Fiecare apariµie a unui simbol din alfabet într-un ³ir se înlocuie³te cu distanµa faµ  de prece-
denta apariµie a aceluia³i simbol (considerând ³irul circular, deci pentru prima apariµie a simbolului
se ia distanµa faµ  de ultima apariµie a aceluia³i simbol).
F când aceast  recodicare pentru cele dou  ³iruri reducem problema la determinarea permu-
t rii circulare care duce primul ³ir în al doilea, care poate  rezolvat  cu un algoritm de pattern
matching, dac  concaten m primul ³ir cu el însu³i rezultând o complexitate O n.
Pentru m mic se pot genera toate permut rile mulµimii r1, 2, ..., mx f când pentru ecare
permutare o c utare (cu KMP de exemplu), iar pentru n mic se poate c uta permutarea pentru
ecare d 0, 1, ..., n.
Codul surs 
import java.io.*;
class kmp
{
static int[] t0; // text mesaj necodificat --> spatiu ... de eliberat !
static int[] t1; // text mesaj codificat --> spatiu ... de eliberat !
CAPITOLUL 16. POTRIVIREA “IRURILOR 277

static int[] d0; // distante ... mesaj necodificat


static int[] d1; // distante ... mesaj codificat

static int[] t; // text in KMP ... (d0,d0) ... d0 dublat ... spatiu !!!
static int[] s; // sablon in KMP ... (d1)
static int[] p; // prefix in KMP ... 1,2,...n

static int[] ua; // pozitia ultimei aparitii ... 1,2,...,m ... ==> d[] mai rapid
static int[] perm;// permutarea

static int n,m; // ... n=100.000, m=9.999 ... maxim !!! ==> 200K

public static void main(String[] args) throws IOException


{
StreamTokenizer st=new StreamTokenizer(
new BufferedReader(new FileReader("9-cifru.in")));
PrintWriter out=new PrintWriter(
new BufferedWriter(new FileWriter("cifru.out")));

int i,j,j0,j1,k,deplasarea=-1;

st.nextToken(); n=(int)st.nval;
st.nextToken(); m=(int)st.nval;

ua=new int[m+1];

t0=new int[n+1];
t1=new int[n+1];
d0=new int[n+1];
d1=new int[n+1];
p=new int[n+1];

for(i=1;i<=n;i++) { st.nextToken(); t0[i]=(int)st.nval; }


for(i=1;i<=n;i++) { st.nextToken(); t1[i]=(int)st.nval; }
distanta(t0,d0);
distanta(t1,d1);
//afisv(t0,1,n); afisv(d0,1,n); System.out.println();
//afisv(t1,1,n); afisv(d1,1,n); System.out.println();

s=d0;
prefix(s,p,n);
//afisv(s,1,n); afisv(p,1,n); System.out.println();

t=new int[2*n+1]; // ocupa spatiu prea mult; aici standard dar ...
for(i=1;i<=n;i++) t[i]=t[n+i]=d1[i];
//afisv(t,1,2*n);

deplasarea=kmp(t,2*n,s,n)-1; // d1 dublat si caut d0 ...


out.println(deplasarea);
System.out.println(deplasarea);

// permutarea ...
perm=ua; // economie de spatiu ...
for(i=1;i<=m;i++) perm[i]=0;
k=0; // nr elemente plasate deja in permutare ...
j1=0;
for(i=1;i<=n;i++)
{
j1++;
CAPITOLUL 16. POTRIVIREA “IRURILOR 278

j0=n-deplasarea+i;
if(j0>n) j0=j0-n;
//System.out.println(i+" : "+j0+" "+j1);
if(perm[t0[j0]]==0)
{
perm[t0[j0]]=t1[j1];
k++;
}
if(k==m) break;
}
//afisv(perm,1,m);

for(i=1;i<=m;i++) out.print(perm[i]+" ");


out.close();
}// main

static int kmp(int[] t, int n, int[] s, int m)// t1,...,tn si s1,...,sm


{
int k,i,pozi=-1;
k=0;
for (i=1;i<=n;i++)
{
while(k>0&&s[k+1]!=t[i]) k=p[k];
if (s[k+1]==t[i]) k++;
if(k==m)
{
pozi=i-m+1;
//System.out.println("incepe pe pozitia "+pozi);
break; // numai prima aparitie ... !!!
}
}// for
return pozi;
}// kmp(...)

static void distanta(int[] t,int[] d) // t=text, d=distante ...


{
int i,j,k;
for(i=1;i<=m;i++) ua[i]=0;

for(i=1;i<=n;i++)
{
if(ua[t[i]]!=0) // stiu pozitia spre stanga a lui t[i] ...
{
if(ua[t[i]]<i)
d[i]=i-ua[t[i]]; // e mai la stanga ...
else
d[i]=i-ua[t[i]]+n; // e mai la dreapta ...

ua[t[i]]=i; // noua pozitie a lui t[i] ...


continue;
}

// nu a aparut inca in 1..i-1 ==> de la n spre stanga


k=i; // distanta spre stanga ... pana la n inclusiv ...
j=n; // caut in zona n,n-1,n-2,...
while(t[i]!=t[j])
{
k++;
j--;
}
CAPITOLUL 16. POTRIVIREA “IRURILOR 279

d[i]=k;
ua[t[i]]=i;
}// for i
}// distanta(...)

static void prefix(int[] s,int[] p,int m) // s=sablon, p=prefix, m=dimensiune


{
int i,k;
p[1]=0;
for(i=2;i<=m;i++)
{
k=p[i-1];
while(k>0&&s[k+1]!=s[i]) k=p[k];
if(s[k+1]==s[i]) p[i]=k+1; else p[i]=0;
}
}// prefix()

static void afisv(int[] x, int i1, int i2)


{
int i;
for(i=i1;i<=i2;i++) System.out.print(x[i]+" ");
System.out.println();
}// afisv(...)
}// class
Capitolul 17

Geometrie computaµional 

17.1 Determinarea orient rii


Consider m trei puncte în plan P1 x1 , y1 , P2 x2 , y2  ³i P3 x3 , y3 .
Panta segmentului P1 P2 : m12 y2  y1 © x2  x1 
Panta segmentului P2 P3 : m23 y3  y2 © x3  x2 

P3

P3
P3
P2 P2
P2

P1 a) P1 b) P1 c)

Orientarea parcurgerii laturilor P1 P2 ³i P2 P3 (în aceast  ordine):

ˆ în sens trigonometric (spre stânga): m12 $ m23 , cazul a în gur 


ˆ în sensul acelor de ceas (spre dreapta): m12 % m23 , cazul c în gur 
ˆ vârfuri coliniare: m12 m23 , cazul b în gur 

Orientarea depinde de valoarea expresiei

o P1 x1 , y1 , P2 x2 , y2 , P3 x3 , y3  y2  y1  x3  x2   y3  y2  x 2  x1 

astfel
~
„
„$0  sens trigonometric
o P1 x1 , y1 , P2 x2 , y2 , P3
„
„
x3 , y3  ‚
„ 0  coliniare
„
„
„% 0
€  sensul acelor de ceas

17.2 Testarea convexit µii poligoanelor


Consider m un poligon cu n vârfuri P1 x1 , y1 P2 x2 , y2 ...Pn xn yn , n ' 3.
Poligonul este convex dac  ³i numai dac  perechile de segmente

P1 P2 , P2 P3 , P2 P3 , P3 P4 , ..., Pn2 Pn1 , Pn1 Pn  ³i Pn1 Pn , Pn P1 

au aceea³i orientare sau sunt colineare.

280
CAPITOLUL 17. GEOMETRIE COMPUTA•IONAL€ 281

P7 P6 P2 P3

P5 P4

P1 P1

P2 P3 P4 P7 P6 P5
a) b)

17.3 Aria poligoanelor convexe


Aria poligonului convex cu n vârfuri P1 x1 , y1 P2 x2 , y2 ...Pn xn yn , n ' 3 se poate determina
cu ajutorul urm toarei formule:
1
¶x y  x2 y3  ...  xn1 yn  xn y1  y1 x2  y2 x3  ...  yn1 xn  yn x1 ¶
2 1 2
Expresia de sub modul este pozitiv  dac  orientarea P1 P2 ...Pn P1 este în sens
trigonometric, este negativ  dac  orientarea P1 P2 ...Pn P1 este în sensul acelor de
ceasornic ³i este nul  dac  punctele P1 x1 , y1 , P2 x2 , y2 , ..., Pn xn yn  sunt colineare. Reciproca
acestei armaµii este deasemenea adev rat  în cazul poligoanelor convexe.

17.4 Poziµia unui punct faµ  de un poligon convex


Consider m un poligon convex cu n vârfuri P1 x1 , y1 P2 x2 , y2 ...Pn xn yn , n ' 3 ³i un punct
P0 x0 , y0 . Dorim s  determin m dac  punctul P0 x0 , y0  este în interiorul poligonului.
Pentru comoditatea prezent rii consider m ³i punctul Pn1 xn1 , yn1  unde x1 xn1 ³i
y1 yn1 , adic  punctul Pn1 este de fapt tot punctul P1 .
Consider m o latur  oarecare Pi Pi1  (1 & i & n) a poligonului.
Ecuaµia dreptei Pi Pi1  este
yi1  yi
Pi Pi1   y  yi xi1  xi x  xi 
Aducem la acela³i numitor ³i consider m funcµia

fi x, y  y  yi  xi1  xi   x  xi  yi1  yi 
Dreapta Pi Pi1  împarte planul în dou  semiplane. Funcµia fi x, y  are valori de acela³i
semn pentru toate punctele din acela³i semiplan, valori cu semn contrar pentru toate punctele din
cel lalt semiplan ³i valoarea 0 pentru doate punctele situate pe dreapt .
Pentru a  siguri c  punctul P0 x0 , y0  se a  în interiorul poligonului (acesta ind convex)
trebuie s  veric m dac  toate vârfurile poligonului împreun  cu punctul P0 x0 , y0  sunt de aceea³i
parte a dreptei Pi Pi1 , adic  toate valorile fi xj , yj  (1 & j & n, j j i ³i j j i  1) au acela³i semn
cu fi x0 , y0  (sau sunt nule dac  accept m prezenµa punctului P0 x0 , y0  pe frontiera poligonului).
Aceasta este o condiµie necesar  dar nu ³i sucient . Vom verica dac  pentru orice latur  Pi Pi1 
(1 & i & n) a poligonului toate celelalte vârfuri sunt în acela³i semiplan cu P0 x0 , y0  (din cele
dou  determinate de dreapta suport a laturii respective) iar dac  se întâmpl  acest lucru atunci
putem trage concluzia c  punctul P0 x0 , y0  se a  în interiorul poligonului convex.
O alt  modalitate de vericare dac  punctul P0 x0 , y0  este în interiorul sau pe frontiera
poligonului convex P1 x1 , y1 P2 x2 , y2 ...Pn xn yn  este vericarea urm toarei relaµii:

= arie
n
ariepoligon P1 P2 ...Pn  triunghi P0 Pk Pk1 
k 1

unde punctul P xn1 , yn1  este de fapt tot punctul P1 x1 , y1 .


CAPITOLUL 17. GEOMETRIE COMPUTA•IONAL€ 282

17.5 Poziµia unui punct faµ  de un poligon concav


Consider m un poligon concav cu n vârfuri P1 x1 , y1 P2 x2 , y2 ...Pn xn yn , n ' 3 ³i un punct
P0 x0 , y0 . Dorim s  determin m dac  punctul P0 x0 , y0  este în interiorul poligonului.
Poligonul concav se descompune în poligoane convexe cu ajutorul diagonalelor interne ³i se
folose³te un algoritm pentru poligoane convexe pentru ecare poligon convex astfel obµinut. Dac 
punctul este în interiorul unui poligon convex obµinut prin partiµionarea poligonului concav atunci
el se a  în interiorul acestuia. Dac  nu se a  în nici un poligon convex obµinut prin partiµionarea
poligonului concav atunci el nu se a  în interiorul acestuia.

P7 P6 P7 P6

P5 P5
P1 P1
P3 P3

P2 P4 P2 P4
a) b)

17.6 Înf ³ur toarea convex 

17.6.1 Împachetarea Jarvis

5 5

4 4

3 3

2 2

1 1

1 2 3 4 5 6 7 1 2 3 4 5 6 7

a) b)

Toate punctele de pe înf ³ur toarea convex  (cazul a în gur ):

import java.io.*; // infasuratoare convexa


class Jarvis1 // pe frontiera coliniare ==> le iau pe toate ... !!!
{
static int n,npic=0; // npic=nr puncte pe infasuratoarea convexa
static int [] x;
static int [] y;
static int [] p; // precedent
static int [] u; // urmator

static void afisv(int[] a, int k1, int k2)


{
int k;
for(k=k1;k<=k2;k++) System.out.print(a[k]+" ");
System.out.println();
}
CAPITOLUL 17. GEOMETRIE COMPUTA•IONAL€ 283

static int orient(int i1, int i2, int i3)


{
long s=(y[i1]-y[i2])*(x[i3]-x[i2])-(y[i3]-y[i2])*(x[i1]-x[i2]);
if(s<0) return -1; else
if(s>0) return 1; else return 0;
}

static void infasurareJarvis() throws IOException


{
BufferedReader br=new BufferedReader(new InputStreamReader(System.in));

int i0,i,i1,i2;
i0=1;
for(i=2;i<=n;i++) if((x[i]<x[i0])||((x[i]==x[i0])&&(y[i]<y[i0]))) i0=i;
System.out.println("Stanga_Jos ==> P"+i0+" : "+x[i0]+" "+y[i0]);

i1=i0;
npic++;
System.out.println(npic+" --> "+i1); //br.readLine();
do
{
i2=i1+1; if(i2>n) i2-=n;
for(i=1;i<=n;i++)
{
//System.out.println("orient("+i1+","+i+","+i2+")="+orient(i1,i,i2));
//br.readLine();
if(orient(i1,i,i2)>0) i2=i; else
if(orient(i1,i,i2)==0) // coliniare
if( // i intre i1 i2 ==> cel mai apropiat
((x[i]-x[i1])*(x[i]-x[i2])<0)||
((y[i]-y[i1])*(y[i]-y[i2])<0)
)
i2=i;
}
u[i1]=i2;
p[i2]=i1;
i1=i2;
npic++;
System.out.println(npic+" --> "+i1); //br.readLine();
} while(i2!=i0);
npic--; // apare de doua ori primul punct !
System.out.print("u : "); afisv(u,1,n);
System.out.print("p : "); afisv(p,1,n);
}// infasurareJarvis()

public static void main(String[] args) throws IOException


{
int k;
StreamTokenizer st= new StreamTokenizer(
new BufferedReader(new FileReader("jarvis.in")));
st.nextToken(); n=(int)st.nval;
x=new int[n+1];
y=new int[n+1];
p=new int[n+1];
u=new int[n+1];

for(k=1;k<=n;k++)
{
st.nextToken(); x[k]=(int)st.nval;
st.nextToken(); y[k]=(int)st.nval;
CAPITOLUL 17. GEOMETRIE COMPUTA•IONAL€ 284

}
infasurareJarvis();
}//main
}//class
F r  punctele coliniare de pe înf ³ur toarea convex  (cazul b în gur ):
import java.io.*; // infasuratoare convexa
class Jarvis2 // pe frontiera coliniare ==> iau numai capetele ... !!!
{
static int n,npic=0; // npic=nr puncte pe infasuratoarea convexa
static int [] x;
static int [] y;
static int [] p; // precedent
static int [] u; // urmator

static void afisv(int[] a, int k1, int k2)


{
int k;
for(k=k1;k<=k2;k++) System.out.print(a[k]+" ");
System.out.println();
}

static int orient(int i1, int i2, int i3)


{
long s=(y[i1]-y[i2])*(x[i3]-x[i2])-(y[i3]-y[i2])*(x[i1]-x[i2]);
if(s<0) return -1; else
if(s>0) return 1; else return 0;
}

static void infasurareJarvis() throws IOException


{
BufferedReader br=new BufferedReader(new InputStreamReader(System.in));

int i0,i,i1,i2;
i0=1;
for(i=2;i<=n;i++) if((x[i]<x[i0])||((x[i]==x[i0])&&(y[i]<y[i0]))) i0=i;
System.out.println("Stanga_Jos ==> P"+i0+" : "+x[i0]+" "+y[i0]);

i1=i0;
npic++;
System.out.println(npic+" --> "+i1); //br.readLine();
do
{
i2=i1+1; if(i2>n) i2-=n;
for(i=1;i<=n;i++)
{
//System.out.println("orient("+i1+","+i+","+i2+")="+orient(i1,i,i2));
//br.readLine();
if(orient(i1,i,i2)>0) i2=i; else
if(orient(i1,i,i2)==0) // coliniare
if( // i2 intre i1 i ==> cel mai departat
((x[i2]-x[i1])*(x[i2]-x[i])<0)||
((y[i2]-y[i1])*(y[i2]-y[i])<0)
)
i2=i;
}
u[i1]=i2;
p[i2]=i1;
i1=i2;
npic++;
System.out.println(npic+" --> "+i1); //br.readLine();
CAPITOLUL 17. GEOMETRIE COMPUTA•IONAL€ 285

} while(i2!=i0);
npic--; // apare de doua ori primul punct !
System.out.print("u : "); afisv(u,1,n);
System.out.print("p : "); afisv(p,1,n);
}// infasurareJarvis()

public static void main(String[] args) throws IOException


{
int k;
StreamTokenizer st= new StreamTokenizer(
new BufferedReader(new FileReader("jarvis.in")));
st.nextToken(); n=(int)st.nval;
x=new int[n+1];
y=new int[n+1];
p=new int[n+1];
u=new int[n+1];
for(k=1;k<=n;k++)
{
st.nextToken(); x[k]=(int)st.nval;
st.nextToken(); y[k]=(int)st.nval;
}
infasurareJarvis();
}//main
}//class

17.6.2 Scanarea Craham

5 5

4 4

3 3

2 2

1 1

1 2 3 4 5 6 7 1 2 3 4 5 6 7

a) b)

5 5

4 4

3 3

2 2

1 1

1 2 3 4 5 6 7 1 2 3 4 5 6 7

a) b)

Versiune cu mesaje pentru sortarea punctelor:


CAPITOLUL 17. GEOMETRIE COMPUTA•IONAL€ 286

import java.io.*; // numai pentru sortare si mesaje ...


class Graham0
{
static int n,npic=0; // npic=nr puncte pe infasuratoarea convexa
static int[] x;
static int[] y;
static int[] o; // o[k] = pozitia lui k inainte de sortare
static int[] of; // of[k] = pozitia lui k dupa sortare

// pentru depanare ... stop ! ...


static BufferedReader br=new BufferedReader(new InputStreamReader(System.in));

static void afisv(int[] a, int k1, int k2)


{
int k;
for(k=k1;k<=k2;k++) System.out.print(a[k]+" ");
System.out.print(" ");
}

static int orient(int i0,int i1, int i2)


{
long s=(y[i1]-y[i0])*(x[i2]-x[i0])-(y[i2]-y[i0])*(x[i1]-x[i0]);
if(s<0) return -1; else
if(s>0) return 1; else return 0;
}

static void qsort(int p, int u) throws IOException


{
// aleg un punct fix k
int k=(p+u)/2;

System.out.println("qsort: p="+p+" u="+u+" k="+k+" xk="+x[k]+" yk="+y[k]);


System.out.print("x : "); afisv(x,p,u); System.out.println();
System.out.print("y : "); afisv(y,p,u); System.out.println();

int i,j,aux;
i=p;j=u;
while(i<j)
{
while( (i<j)&&
( (orient(1,i,k)<0)||
((orient(1,i,k)==0)&&
(((x[i]-x[1])*(x[i]-x[k])<0)||((y[i]-y[1])*(y[i]-y[k])<0)))
)
)
{
i++;
}
while( (i<j)&&
( (orient(1,j,k)>0)||
((orient(1,j,k)==0)&&
(((x[j]-x[1])*(x[j]-x[k])>0)||((y[j]-y[1])*(y[j]-y[k])>0)))
)
)
{
j--;
}

if(i<j)
{
CAPITOLUL 17. GEOMETRIE COMPUTA•IONAL€ 287

if(k==i) k=j; else if(k==j) k=i;// k=fix dar se poate schimba pozitia
aux=x[i]; x[i]=x[j]; x[j]=aux;
aux=y[i]; y[i]=y[j]; y[j]=aux;
aux=o[i]; o[i]=o[j]; o[j]=aux;
}
}// while

System.out.println("Final while ... i="+i+" j="+j);


// i=j si P[i] este pe locul lui !!!
System.out.print("x : "); afisv(x,p,i-1); afisv(x,i,i); afisv(x,i+1,u);
System.out.println();
System.out.print("y : "); afisv(y,p,i-1); afisv(y,i,i); afisv(y,i+1,u);
System.out.println();
br.readLine();

if(p<i-1) qsort(p,i-1);
if(j+1<u) qsort(j+1,u);
}// qSort(...)

static void scanareGraham() throws IOException


{
int i0,i,i1,i2,aux;

System.out.print("x : "); afisv(x,1,n); System.out.println();


System.out.print("y : "); afisv(y,1,n); System.out.println();
System.out.println();

i0=1;
for(i=2;i<=n;i++) if((x[i]<x[i0])||((x[i]==x[i0])&&(y[i]<y[i0]))) i0=i;
System.out.println("Stanga_Jos ==> P"+i0+" : "+x[i0]+" "+y[i0]+"\n");

aux=x[1]; x[1]=x[i0]; x[i0]=aux;


aux=y[1]; y[1]=y[i0]; y[i0]=aux;
aux=o[1]; o[1]=o[i0]; o[i0]=aux;

System.out.print("x : "); afisv(x,1,n); System.out.println();


System.out.print("y : "); afisv(y,1,n); System.out.println();
System.out.print("o : "); afisv(o,1,n); System.out.println();
System.out.println();

qsort(2,n);

System.out.println();
System.out.print("x : "); afisv(x,1,n); System.out.println();
System.out.print("y : "); afisv(y,1,n); System.out.println();
System.out.print("o : "); afisv(o,1,n); System.out.println();
System.out.println();
}// scanareGraham()

public static void main(String[] args) throws IOException


{
int k;

StreamTokenizer st= new StreamTokenizer(


new BufferedReader(new FileReader("graham.in")));
st.nextToken(); n=(int)st.nval;
x=new int[n+1];
y=new int[n+1];
o=new int[n+1];
of=new int[n+1];
CAPITOLUL 17. GEOMETRIE COMPUTA•IONAL€ 288

for(k=1;k<=n;k++)
{
st.nextToken(); x[k]=(int)st.nval;
st.nextToken(); y[k]=(int)st.nval;
o[k]=k;
}

scanareGraham();

// ordinea finala (dupa sortare) a punctelor


for(k=1;k<=n;k++) of[o[k]]=k;
System.out.println();
System.out.print("of : "); afisv(of,1,n); System.out.println();
System.out.println();
}//main
}//class
Versiune cu toate punctele de pe înf ³ur toare:
import java.io.*; // NU prinde punctele coliniarele pe ultima latura !
class Graham1 // daca schimb ordinea pe "razele" din sortare, atunci ...
{ // NU prinde punctele coliniarele pe prima latura, asa ca ...
static int n;
static int np; // np=nr puncte pe infasuratoarea convexa
static int[] x;
static int[] y;
static int[] o; // pozitia inainte de sortare
static int[] p; // poligonul infasuratoare convexa

static int orient(int i0,int i1, int i2)


{
long s=(y[i1]-y[i0])*(x[i2]-x[i0])-(y[i2]-y[i0])*(x[i1]-x[i0]);
if(s<0) return -1; else
if(s>0) return 1; else return 0;
}

static void qsort(int p, int u)


{
int i,j,k,aux;
// aleg un punct fix k
k=(p+u)/2;
i=p;
j=u;
while(i<j)
{
while( (i<j)&&
( (orient(1,i,k)<0)||
((orient(1,i,k)==0)&&
(((x[i]-x[1])*(x[i]-x[k])<0)||((y[i]-y[1])*(y[i]-y[k])<0)))
)
) i++;
while( (i<j)&&
( (orient(1,j,k)>0)||
((orient(1,j,k)==0)&&
(((x[j]-x[1])*(x[j]-x[k])>0)||((y[j]-y[1])*(y[j]-y[k])>0)))
)
) j--;
if(i<j)
{
if(k==i) k=j; else if(k==j) k=i;// k=fix dar se poate schimba pozitia
aux=x[i]; x[i]=x[j]; x[j]=aux;
CAPITOLUL 17. GEOMETRIE COMPUTA•IONAL€ 289

aux=y[i]; y[i]=y[j]; y[j]=aux;


aux=o[i]; o[i]=o[j]; o[j]=aux;
}
}
if(p<i-1) qsort(p,i-1);
if(j+1<u) qsort(j+1,u);
}// qSort(...)

static void scanareGraham() throws IOException


{
int i0,i,i1,i2,i3,aux;

i0=1;
for(i=2;i<=n;i++) if((x[i]<x[i0])||((x[i]==x[i0])&&(y[i]<y[i0]))) i0=i;

aux=x[1]; x[1]=x[i0]; x[i0]=aux;


aux=y[1]; y[1]=y[i0]; y[i0]=aux;
aux=o[1]; o[1]=o[i0]; o[i0]=aux;

qsort(2,n);

i1=1; p[1]=i1;
i2=2; p[2]=i2;
np=2;

i3=3;
while(i3<=n)
{
while(orient(i1,i2,i3)>0)
{
i2=p[np-1];
i1=p[np-2];
np--;
}
np++;
p[np]=i3;
i2=p[np];
i1=p[np-1];
i3++;
}// while

// plasez si punctele coliniare de pe ultima latura a infasuratorii


i=n-1;
while(orient(1,p[np],i)==0) p[++np]=i--;

// afisez rezultatele
System.out.print("punctele initiale: ");
for(i=1;i<=np;i++) System.out.print(o[p[i]]+" ");
System.out.println();
System.out.print("infasuratoare x: ");
for(i=1;i<=np;i++) System.out.print(x[p[i]]+" ");
System.out.println();
System.out.print("infasuratoare y: ");
for(i=1;i<=np;i++) System.out.print(y[p[i]]+" ");
System.out.println();
}// scanareGraham()

public static void main(String[] args) throws IOException


{
int k;
CAPITOLUL 17. GEOMETRIE COMPUTA•IONAL€ 290

StreamTokenizer st= new StreamTokenizer(


new BufferedReader(new FileReader("graham1.in")));
st.nextToken(); n=(int)st.nval;
x=new int[n+1];
y=new int[n+1];
o=new int[n+1];
p=new int[n+1];

for(k=1;k<=n;k++)
{
st.nextToken(); x[k]=(int)st.nval;
st.nextToken(); y[k]=(int)st.nval;
o[k]=k;
}

scanareGraham();

}//main
}//class
Versiune f r  puncte coliniare pe înf ³ur toare:
import java.io.*; // aici ... infasuratoarea nu contine puncte coliniare ...
class Graham2 // este o eliminare din rezultatul final dar ...
{ // se pot elimina puncte la sortare si/sau scanare ...
static int n;
static int np; // np=nr puncte pe infasuratoarea convexa
static int[] x;
static int[] y;
static int[] o; // pozitia inainte de sortare
static int[] p; // poligonul infasuratoare convexa

static int orient(int i0,int i1, int i2)


{
long s=(y[i1]-y[i0])*(x[i2]-x[i0])-(y[i2]-y[i0])*(x[i1]-x[i0]);
if(s<0) return -1; else
if(s>0) return 1; else return 0;
}

static void qsort(int p, int u)// elimin si punctele coliniare (din interior)
{
int i,j,k,aux;
// aleg un punct fix k
k=(p+u)/2;
i=p;
j=u;
while(i<j)
{
while( (i<j)&&
( (orient(1,i,k)<0)||
((orient(1,i,k)==0)&&
(((x[i]-x[1])*(x[i]-x[k])<0)||((y[i]-y[1])*(y[i]-y[k])<0)))
)
) i++;
while( (i<j)&&
( (orient(1,j,k)>0)||
((orient(1,j,k)==0)&&
(((x[j]-x[1])*(x[j]-x[k])>0)||((y[j]-y[1])*(y[j]-y[k])>0)))
)
) j--;
if(i<j)
CAPITOLUL 17. GEOMETRIE COMPUTA•IONAL€ 291

{
if(k==i) k=j; else if(k==j) k=i;// k=fix dar se poate schimba pozitia
aux=x[i]; x[i]=x[j]; x[j]=aux;
aux=y[i]; y[i]=y[j]; y[j]=aux;
aux=o[i]; o[i]=o[j]; o[j]=aux;
}
}
if(p<i-1) qsort(p,i-1);
if(j+1<u) qsort(j+1,u);
}// qSort(...)

static void scanareGraham() throws IOException


{
int i0,i,i1,i2,i3,aux;

i0=1;
for(i=2;i<=n;i++) if((x[i]<x[i0])||((x[i]==x[i0])&&(y[i]<y[i0]))) i0=i;

aux=x[1]; x[1]=x[i0]; x[i0]=aux;


aux=y[1]; y[1]=y[i0]; y[i0]=aux;
aux=o[1]; o[1]=o[i0]; o[i0]=aux;

qsort(2,n);

i1=1; p[1]=i1;
i2=2; p[2]=i2;
np=2;

i3=3;
while(i3<=n)
{
while(orient(i1,i2,i3)>0) // elimin i2
{
i2=p[np-1];
i1=p[np-2];
np--;
}
np++;
p[np]=i3;
i2=p[np];
i1=p[np-1];
i3++;
}// while

// eliminarea punctelor coliniare de pe infasuratoare


p[np+1]=p[1];
for(i=1;i<=np-1;i++)
if(orient(p[i],p[i+1],p[i+2])==0) o[p[i+1]]=0;

// afisez rezultatele
System.out.print("punctele initiale: ");
for(i=1;i<=np;i++) if(o[p[i]]!=0) System.out.print(o[p[i]]+" ");
System.out.println();
System.out.print("infasuratoare x: ");
for(i=1;i<=np;i++) if(o[p[i]]!=0) System.out.print(x[p[i]]+" ");
System.out.println();
System.out.print("infasuratoare y: ");
for(i=1;i<=np;i++) if(o[p[i]]!=0) System.out.print(y[p[i]]+" ");
System.out.println();
}// scanareGraham()
CAPITOLUL 17. GEOMETRIE COMPUTA•IONAL€ 292

public static void main(String[] args) throws IOException


{
int k;

StreamTokenizer st= new StreamTokenizer(


new BufferedReader(new FileReader("graham2.in")));
st.nextToken(); n=(int)st.nval;
x=new int[n+2];
y=new int[n+2];
o=new int[n+2];
p=new int[n+2];

for(k=1;k<=n;k++)
{
st.nextToken(); x[k]=(int)st.nval;
st.nextToken(); y[k]=(int)st.nval;
o[k]=k;
}

scanareGraham();

}//main
}//class

17.7 Dreptunghi minim de acoperire a punctelor


Se poate determina dreptunghiul minim de acoperire pentru înf ³ur toarea convex  (gura
17.1) pentru a prelucra mai puµine puncte dar nu este obligatorie aceast  strategie.

D
M
ymax
P7 R
P6
A P8 P9 P5
P12
P1 P10
P11 P4 C
P2
P3 Q
N ymin

B
xmin xmax

Figura 17.1: Dreptunghi minim de acoperire

Putem s  presupunem c  punctele formeaz  un poligon convex. Determinarea dreptunghiului


de arie minim  care conµine în interiorul s u (inclusiv frontiera) toate punctele date se poate
face observând c  o latur  a sa conµine o latur  a poligonului convex. Pentru ecare latur  a
poligonului convex se determin  dreptunghiul minim de acoperire care conµine acea latur . Dintre
aceste dreptunghiuri se alege cel cu aria minim .

17.8 Cerc minim de acoperire a punctelor


Se poate determina cercul minim de acoperire pentru înf ³ur toarea convex  pentru a prelucra
mai puµine puncte dar nu este obligatorie aceast  strategie.
Ordon m punctele astfel încât pe primele poziµii s  e plasate punctele de extrem (cel mai din
stânga, urmat de cel mai din dreapta, urmat de cel mai de jos, urmat de cel mai de sus; dup 
CAPITOLUL 17. GEOMETRIE COMPUTA•IONAL€ 293

Ck = C k-1 P
k+1

Ck C
P k+1
k Ck

a) b)

acestea urmeaz  celelalte puncte într-o ordine oarecare). Presupunem c  punctele, dup  ordonare,
sunt: P1 x1 , y1 , P2 x2 , y2 , P3 x3 , y3 , ..., Pn xn , yn .
Not m cu Ci ai , bi ; ri  cercul de centru ai , bi  ³i raz  minim  ri care acoper  punctele
P1 , P2 , ..., Pn .
Ô Consider m cercul C2 a2 , b2 ; r2  unde a2 x1  x2 ©2, b2 y1  y2 ©2 ³i r2
1
2
x 2  x 1  2
 y2  y 1  2
, adic  cercul de diametru  P P
1 2  .
S  presupunem c  am determinat, pas cu pas, cercurile C2 , C3 , ..., Ci ³i trebuie s  determin m
cercul Ci1 .
Dac  punctul Pi1 se a  în interiorul cercului Ci atunci cercul Ci1 este identic cu Ci .
Dac  punctul Pi1 nu se a  în interiorul cercului Ci atunci cercul Ci1 se determin  reluînd
algoritmul pentru ³irul de puncte P1 , P2 , ...,Pi , Pi1 dar impunând condiµia ca acest cerc s  treac 
în mod obligatoriu prin punctul Pi1 xi1 , yi1 . Putem plasa acest punct pe prima poziµie în
³irul punctelor ³i astfel vom impune la ecare pas ca punctul P1 s  e pe cercul care trebuie
determinat!

17.9 Probleme rezolvate


17.9.1 Seceta - ONI2005 clasa a IX-a
lect. Ovidiu Dom³a
Gr dinile roditoare ale B r ganului sufer  anual pierderi imense din cauza secetei. C u-
t torii de ap  au g sit n fântâni din care doresc s  alimenteze n gr dini. Fie Gi , Fi , i 1, ..., n
puncte în plan reprezentând puncte de alimentare ale gr dinilor ³i respectiv punctele în care se
a  fântânile. Pentru ecare punct se dau coordonatele întregi x, y  în plan.
Pentru a economisi materiale, leg tura dintre o gr din  ³i o fântân  se realizeaz  printr-o
conduct  în linie dreapt . Fiecare fântân  alimenteaz  o singur  gr din . Consiliul Judeµean
Galaµi pl te³te investiµia cu condiµia ca lungimea total  a conductelor s  e minim .
Fiecare unitate de conduct  cost  100 lei noi (RON).
Cerinµ 
S  se determine m, costul minim total al conductelor ce leag  ecare gr din  cu exact o fântân .
Date de intrare
Fi³ierul de intrare seceta.in va conµine:
a Pe prima linie se a  num rul natural n, reprezentând num rul gr dinilor ³i al fântânilor.
a Pe urm toarele n linii se a  perechi de numere întregi Gx Gy , separate printr-un spaµiu,
reprezentând coordonatele punctelor de alimentare ale gr dinilor.
a Pe urm toarele n linii se a  perechi de numere întregi Fx Fy , separate printr-un spaµiu,
reprezentând coordonatele punctelor fântânilor.
Date de ie³ire
Fi³ierul de ie³ire seceta.out va conµine:
m  un num r natural reprezentând partea întreag  a costului minim total al conductelor.
Restricµii ³i preciz ri
a 1 $ n $ 13
a 0 & Gx, Gy, F x, F y & 200
CAPITOLUL 17. GEOMETRIE COMPUTA•IONAL€ 294

a Nu exist  trei puncte coliniare, indiferent dac  sunt gr dini sau fântâni
a Orice linie din ³ierele de intrare ³i ie³ire se termin  prin marcajul de sfâr³it de linie.
Exemplu
seceta.in seceta.out Explicaµie
3 624 Costul minim este [6.24264 * 100]=624
1 4 prin legarea perechilor:
3 3 Gradini Fantani
4 7 14 23
2 3 33 31
2 5 47 25
3 1
Timp maxim de execuµie/test: 1 sec sub Windows ³i 0.5 sec sub Linux.

Indicaµii de rezolvare *

Soluµia ocial , lect. Ovidiu Dom³a


Num rul mic al punctelor permite generarea tuturor posibilit µilor de a conecta o gr din  cu
o fântân  neconectat  la un moment dat.
Pentru ecare astfel de combinaµie g sit  se calculeaz  suma distanµelor Gi, F j , în li-
nie dreapta, folosindÔ formula distanµei dintre dou  puncte în plan, studiat  la geometrie.
(d A x, y , B z, t x  z 2  y  t2 ).
Acest  soluµie implementat  corect asigur  60  70 de puncte.
Pentru a obµine punctajul maxim se tine cont de urm toarele aspecte:
1. Se construie³te în prealabil matricea distanµelor d i, j  cu semnicaµia distanµei dintre
gr dina i ³i fântâna j . Aceasta va reduce timpul de calcul la variantele cu peste 9 perechi.
2. Pentru a elimina cazuri care nu pot constitui soluµii optime se folose³te proprietatea pa-
trulaterului c  suma a doua laturi opuse (condiµie care asigur  unicitatea conect rii unei singure
fântâni la o singur  gr din ) este mai mic  decât suma diagonalelor. De aceea nu se vor lua în
considerare acele segmente care se intersecteaz . Condiµia de intersecµie a dou  segmente care
au capetele în punctele de coordonate A a1, a2, B b1, b2, C c1, c2, D d1, d2 este ca luând
segmentul AB , punctele C ³i D s  se ae de aceea³i parte a segmentului AB ³i respectiv pentru
segmentul CD, punctele A ³i B s  se ae de aceea³i parte (se înlocuie³te în ecuaµia dreptei ce
trece prin dou  puncte, studiat  în clasa a 9-a).
Observaµie: Pentru cei interesaµi, problema are soluµie ³i la un nivel superior, folosind algorit-
mul de determinare a unui ux maxim de cost minim.

Variant  cu determinarea intesecµiei segmentelor.

import java.io.*; // cu determinarea intesectiei segmentelor


class Seceta1 // Java este "mai incet" decat Pascal si C/C++
{ // test 9 ==> 2.23 sec
static int nv=0;
static int n;
static int[] xg, yg, xf, yf, t, c;
static int[] a; // permutare: a[i]=fantana asociata gradinii i
static double costMin=200*1.42*12*100;
static double[][] d;
static PrintWriter out;
static StreamTokenizer st;

public static void main(String[] args) throws IOException


{
long t1,t2;
t1=System.currentTimeMillis();
citire();
rezolvare();
afisare();
t2=System.currentTimeMillis();
System.out.println("Timp = "+(t2-t1)+" ms");
CAPITOLUL 17. GEOMETRIE COMPUTA•IONAL€ 295

static void citire() throws IOException


{
int k;
st=new StreamTokenizer(new BufferedReader(new FileReader("seceta.in")));
st.nextToken(); n=(int)st.nval;

xg=new int[n+1];
yg=new int[n+1];
xf=new int[n+1];
yf=new int[n+1];
a=new int[n+1];
d=new double[n+1][n+1];

for(k=1;k<=n;k++)
{
st.nextToken(); xg[k]=(int)st.nval;
st.nextToken(); yg[k]=(int)st.nval;
}
for(k=1;k<=n;k++)
{
st.nextToken(); xf[k]=(int)st.nval;
st.nextToken(); yf[k]=(int)st.nval;
}
}

static void rezolvare() throws IOException


{
int i,j;
int s;
for(i=1;i<=n;i++) // gradina i
for(j=1;j<=n;j++) // fantana j
{
s=(xg[i]-xf[j])*(xg[i]-xf[j])+(yg[i]-yf[j])*(yg[i]-yf[j]);
d[i][j]=Math.sqrt(s);
}
f(1); // generez permutari
}

static void f(int k)


{
boolean ok;
int i,j;
for(i=1;i<=n;i++)
{
ok=true; // k=1 ==> nu am in stanga ... for nu se executa !
for(j=1;j<k;j++) if(i==a[j]) {ok=false; break;}
if(!ok) continue;
for(j=1;j<k;j++)
if(seIntersecteaza(xg[k],yg[k],xf[i], yf[i],
xg[j],yg[j],xf[a[j]],yf[a[j]]))
{
ok=false;
break;
}
if(!ok) continue;
a[k]=i;
if(k<n) f(k+1); else verificCostul();
}
CAPITOLUL 17. GEOMETRIE COMPUTA•IONAL€ 296

static void verificCostul()


{
int i;
double s=0;
for(i=1;i<=n;i++) s=s+d[i][a[i]];
if(s<costMin) costMin=s;
}

// de ce parte a dreptei [(xa,ya);(xb,yb)] se afla (xp,yp)


static int s(int xp,int yp,int xa,int ya,int xb,int yb)
{
double s=(double)yp*(xb-xa)-xp*(yb-ya)+xa*yb-xb*ya;
if(s<-0.001) return -1; // in zona "negativa"
else if(s>0.001) return 1; // in zona "pozitiva"
else return 0; // pe dreapta suport
}

// testeaza daca segmentul[P1,P1] se intersecteaza cu [P3,P4]


static boolean seIntersecteaza(int x1, int y1, int x2, int y2,
int x3, int y3, int x4, int y4)
{
double x,y;
if((x1==x2)&&(x3==x4)) // ambele segmente verticale
if(x1!=x3) return false;
else if(intre(y1,y3,y4)||intre(y2,y3,y4)) return true;
else return false;

if((y1==y2)&&(y3==y4)) // ambele segmente orizontale


if(y1!=y3) return false;
else if(intre(x1,x3,x4)||intre(x2,x3,x4)) return true;
else return false;

if((y2-y1)*(x4-x3)==(y4-y3)*(x2-x1)) // au aceeasi panta (oblica)


if((x2-x1)*(y3-y1)==(y2-y1)*(x3-x1)) // au aceeasi dreapta suport
if(intre(x1,x3,x4)||intre(x2,x3,x4)) return true;
else return false;
else return false;// nu au aceeasi dreapta suport
else // nu au aceeasi panta (macar unul este oblic)
{
x=(double)((x4-x3)*(x2-x1)*(y3-y1)-
x3*(y4-y3)*(x2-x1)+
x1*(y2-y1)*(x4-x3))/
((y2-y1)*(x4-x3)-(y4-y3)*(x2-x1));
if(x2!=x1) y=y1+(y2-y1)*(x-x1)/(x2-x1); else y=y3+(y4-y3)*(x-x3)/(x4-x3);
if(intre(x,x1,x2)&&intre(y,y1,y2)&&intre(x,x3,x4)&&intre(y,y3,y4))
return true; else return false;
}
}

static boolean intre(int c, int a, int b) // c este in [a,b] ?


{
int aux;
if(a>b) {aux=a; a=b; b=aux;}
if((a<=c)&&(c<=b)) return true; else return false;
}

static boolean intre(double c, int a, int b) // c este in [a,b] ?


{
CAPITOLUL 17. GEOMETRIE COMPUTA•IONAL€ 297

int aux;
if(a>b) {aux=a; a=b; b=aux;}
if((a<=c)&&(c<=b)) return true; else return false;
}

static void afisare() throws IOException


{
int k;
out=new PrintWriter(new BufferedWriter(new FileWriter("seceta.out")));
out.println((int)(costMin*100));
out.close();
}
}
Variant  cu cu determinarea pozitiei punctelor in semiplane ³i mesaje pentru depanare.
import java.io.*; // cu determinarea pozitiei punctelor in semiplane
class Seceta2 // cu mesaje pentru depanare !
{
static int nv=0;
static int n;
static int[] xg, yg, xf, yf, t, c;
static int[] a; // permutare: a[i]=fantana asociata gradinii i
static double costMin=200*1.42*12*100;
static double[][] d;
static PrintWriter out;
static StreamTokenizer st;

public static void main(String[] args) throws IOException


{
long t1,t2;
t1=System.currentTimeMillis();
citire();
rezolvare();
afisare();
t2=System.currentTimeMillis();
System.out.println("Timp = "+(t2-t1)+" ms");
}

static void citire() throws IOException


{
int k;
st=new StreamTokenizer(new BufferedReader(new FileReader("seceta.in")));
st.nextToken(); n=(int)st.nval;
xg=new int[n+1];
yg=new int[n+1];
xf=new int[n+1];
yf=new int[n+1];
a=new int[n+1];
d=new double[n+1][n+1];

for(k=1;k<=n;k++)
{
st.nextToken(); xg[k]=(int)st.nval;
st.nextToken(); yg[k]=(int)st.nval;
}
for(k=1;k<=n;k++)
{
st.nextToken(); xf[k]=(int)st.nval;
st.nextToken(); yf[k]=(int)st.nval;
}
}
CAPITOLUL 17. GEOMETRIE COMPUTA•IONAL€ 298

static void rezolvare() throws IOException


{
int i,j;
int s;
for(i=1;i<=n;i++) // gradina i
for(j=1;j<=n;j++) // fantana j
{
s=(xg[i]-xf[j])*(xg[i]-xf[j])+(yg[i]-yf[j])*(yg[i]-yf[j]);
d[i][j]=Math.sqrt(s);
}
f(1); // generez permutari
}

static void f(int k)


{
boolean ok;
int i,j;
for(i=1;i<=n;i++)
{
ok=true; // k=1 ==> nu am in stanga ... for nu se executa !
for(j=1;j<k;j++) if(i==a[j]) {ok=false; break;}
if(!ok) continue;

for(j=1;j<k;j++)
if((s(xg[k],yg[k],xg[j],yg[j],xf[a[j]],yf[a[j]])*
s(xf[i],yf[i],xg[j],yg[j],xf[a[j]],yf[a[j]])<0)&&
(s(xg[j], yg[j], xg[k],yg[k],xf[i],yf[i])*
s(xf[a[j]],yf[a[j]],xg[k],yg[k],xf[i],yf[i])<0))
{
afisv(k-1);// pe pozitia k(gradina) vreau sa pun i(fantana)
System.out.print(i+" ");// pe pozitia j(gradina) e pus a[j](fantana)
System.out.print(k+""+i+" "+j+""+a[j]);
System.out.print(" ("+xg[k]+","+yg[k]+") "+" ("+xf[i]+","+yf[i]+") ");
System.out.println(" ("+xg[j]+","+yg[j]+") "+" ("+xf[a[j]]+","+yf[a[j]]

ok=false;
break;
}
if(!ok) continue;

a[k]=i;
if(k<n) f(k+1); else verificCostul();
}
}

static void verificCostul()


{
int i;
double s=0;
for(i=1;i<=n;i++) s=s+d[i][a[i]];
if(s<costMin) costMin=s;
afisv(n); System.out.println(" "+s+" "+costMin+" "+(++nv));
}

static void afisv(int nn)


{
int i;
for(i=1;i<=nn;i++) System.out.print(a[i]);
}
CAPITOLUL 17. GEOMETRIE COMPUTA•IONAL€ 299

// de ce parte a dreptei [(xa,ya);(xb,yb)] se afla (xp,yp)


static int s(int xp,int yp,int xa,int ya,int xb,int yb)
{
double s=(double)yp*(xb-xa)-xp*(yb-ya)+xa*yb-xb*ya;
if(s<-0.001) return -1; // in zona "negativa"
else if(s>0.001) return 1; // in zona "pozitiva"
else return 0; // pe dreapta suport
}

static void afisare() throws IOException


{
int k;
out=new PrintWriter(new BufferedWriter(new FileWriter("seceta.out")));
out.println((int)(costMin*100));
out.close();
}
}
Variant  cu cu determinarea pozitiei punctelor in semiplane, f r  mesaje pentru depanare.
import java.io.*; // cu determinarea pozitiei punctelor in semiplane
class Seceta3 // Java este "mai incet" decat Pascal si C/C++
{ // test 9 ==> 2.18 sec
static int n;
static int[] xg, yg, xf, yf, t, c;
static int[] a; // permutare: a[i]=fantana asociata gradinii i
static double costMin=200*1.42*12*100;
static double[][] d;
static PrintWriter out;
static StreamTokenizer st;

public static void main(String[] args) throws IOException


{
long t1,t2;
t1=System.currentTimeMillis();
citire();
rezolvare();
afisare();
t2=System.currentTimeMillis();
System.out.println("Timp = "+(t2-t1)+" ms");
}

static void citire() throws IOException


{
int k;
st=new StreamTokenizer(new BufferedReader(
new FileReader("seceta.in")));
st.nextToken(); n=(int)st.nval;
xg=new int[n+1];
yg=new int[n+1];
xf=new int[n+1];
yf=new int[n+1];
a=new int[n+1];
d=new double[n+1][n+1];

for(k=1;k<=n;k++)
{
st.nextToken(); xg[k]=(int)st.nval;
st.nextToken(); yg[k]=(int)st.nval;
}
for(k=1;k<=n;k++)
CAPITOLUL 17. GEOMETRIE COMPUTA•IONAL€ 300

{
st.nextToken(); xf[k]=(int)st.nval;
st.nextToken(); yf[k]=(int)st.nval;
}
}

static void rezolvare() throws IOException


{
int i,j;
int s;
for(i=1;i<=n;i++) // gradina i
for(j=1;j<=n;j++) // fantana j
{
s=(xg[i]-xf[j])*(xg[i]-xf[j])+(yg[i]-yf[j])*(yg[i]-yf[j]);
d[i][j]=Math.sqrt(s);
}
f(1); // generez permutari
}

static void f(int k)


{
boolean ok;
int i,j;
for(i=1;i<=n;i++)
{
ok=true; // k=1 ==> nu am in stanga ... for nu se executa !
for(j=1;j<k;j++) if(i==a[j]) {ok=false; break;}
if(!ok) continue;
for(j=1;j<k;j++)
if((s(xg[k], yg[k], xg[j],yg[j],xf[a[j]],yf[a[j]])*
s(xf[i], yf[i], xg[j],yg[j],xf[a[j]],yf[a[j]])<0)&&
(s(xg[j], yg[j], xg[k],yg[k],xf[i], yf[i])*
s(xf[a[j]],yf[a[j]],xg[k],yg[k],xf[i], yf[i])<0))
{
ok=false;
break;
}
if(!ok) continue;
a[k]=i;
if(k<n) f(k+1); else verificCostul();
}
}

static void verificCostul()


{
int i;
double s=0;
for(i=1;i<=n;i++) s=s+d[i][a[i]];
if(s<costMin) costMin=s;
}

//de ce parte a dreptei [(xa,ya);(xb,yb)] se afla (xp,yp)


static int s(int xp,int yp,int xa,int ya,int xb,int yb)
{
double s=(double)yp*(xb-xa)-xp*(yb-ya)+xa*yb-xb*ya;
if(s<-0.001) return -1; // in zona "negativa"
else if(s>0.001) return 1; // in zona "pozitiva"
else return 0; // pe dreapta suport
}
CAPITOLUL 17. GEOMETRIE COMPUTA•IONAL€ 301

static void afisare() throws IOException


{
int k;
out=new PrintWriter(new BufferedWriter(new FileWriter("seceta.out")));
out.println((int)(costMin*100));
out.close();
}
}
Varianta 4:

import java.io.*; // gresit (!) dar ... obtine 100p ... !!!
class Seceta4 // test 9 : 2.18 sec --> 0.04 sec
{
static int n;
static int[] xg, yg, xf, yf, t, c;
static int[] a; // permutare: a[i]=fantana asociata gradinii i
static double costMin=200*1.42*12*100;
static double[][] d;
static boolean[] epus=new boolean[13];

static PrintWriter out;


static StreamTokenizer st;

public static void main(String[] args) throws IOException


{
long t1,t2;
t1=System.currentTimeMillis();

citire();
rezolvare();
afisare();

t2=System.currentTimeMillis();
System.out.println("Timp = "+(t2-t1)+" ms");
}// main(...)

static void citire() throws IOException


{
int k;
st=new StreamTokenizer(new BufferedReader(new FileReader("seceta.in")));
st.nextToken(); n=(int)st.nval;
xg=new int[n+1];
yg=new int[n+1];
xf=new int[n+1];
yf=new int[n+1];
a=new int[n+1];

d=new double[n+1][n+1];

for(k=1;k<=n;k++)
{
st.nextToken(); xg[k]=(int)st.nval;
st.nextToken(); yg[k]=(int)st.nval;
}
for(k=1;k<=n;k++)
{
st.nextToken(); xf[k]=(int)st.nval;
st.nextToken(); yf[k]=(int)st.nval;
}
}// citire(...)
CAPITOLUL 17. GEOMETRIE COMPUTA•IONAL€ 302

static void rezolvare() throws IOException


{
int i,j;
int s;
for(i=1;i<=n;i++) // gradina i
for(j=1;j<=n;j++) // fantana j
{
s=(xg[i]-xf[j])*(xg[i]-xf[j])+(yg[i]-yf[j])*(yg[i]-yf[j]);
d[i][j]=Math.sqrt(s);
}
f(1); // generez permutari
}// rezolvare(...)

static void f(int k)


{
int i,j;
boolean seIntersecteaza;
for(i=1;i<=n;i++)
{
if(epus[i]) continue;
seIntersecteaza=false;
for(j=1;j<=k-1;j++)
if(d[k][i]+d[j][a[j]]>d[j][i]+d[k][a[j]])
{
seIntersecteaza=true;
break;
}

if(seIntersecteaza) continue;

a[k]=i;

epus[i]=true;
if(k<n) f(k+1); else verificCostul();
epus[i]=false;
}// for i
}// f(...)

static void verificCostul()


{
int i;
double s=0;
for(i=1;i<=n;i++) s=s+d[i][a[i]];
if(s<costMin) costMin=s;
}// verificCostul(...)

static void afisare() throws IOException


{
int k;
out=new PrintWriter(new BufferedWriter(new FileWriter("seceta.out")));
out.println((int)(costMin*100));
out.close();
}// afisare(...)
}// class

17.9.2 Antena - ONI2005 clasa a X-a


prof. Osman Ay, Liceul International de Informatic  Bucure³ti

În Delta Dun rii exist  o zon  s lbatic , rupt  de bucuriile ³i necazurile civilizaµiei moderne.
CAPITOLUL 17. GEOMETRIE COMPUTA•IONAL€ 303

În aceast  zon  exist  doar n case, poziµiile acestora ind specicate prin coordonatele carte-
ziene de pe hart .
Postul de radio al ONI 2005 dore³te s  emit  pentru toµi locuitorii din zon  ³i, prin urmare,
va trebui s  instaleze o anten  de emisie special  pentru aceasta.
O anten  emite unde radio într-o zon  circular . Centrul zonei coincide cu punctul în care
este poziµionat  antena. Raza zonei este denumit  puterea antenei. Cu cât puterea antenei este
mai mare, cu atât antena este mai scump .
Prin urmare trebuie selectat  o poziµie optim  de amplasare a antenei, astfel încât ecare cas 
s  se ae în interiorul sau pe frontiera zonei circulare în care emite antena, iar puterea antenei s 
e minim .
Cerinµ 
Scrieµi un program care s  determine o poziµie optim  de amplasare a antenei, precum ³i
puterea minim  a acesteia.
Datele de intrare
Fi³ierul de intrare antena.in conµine pe prima linie un num r natural n, reprezentând num rul
de case din zon . Pe urm toarele n linii se a  poziµiile caselor. Mai exact, pe linia i  1 se a 
dou  numere întregi separate printr-un spaµiu x y , ce reprezint  abscisa ³i respectiv ordonata casei
i. Nu exist  dou  case în aceea³i locaµie.
Datele de ie³ire
Fi³ierul de ie³ire antena.out conµine pe prima linie dou  numere reale separate printr-un
spaµiu x y reprezentând abscisa ³i ordonata poziµiei optime de amplasare a antenei.
Pe cea de a doua linie se va scrie un num r real reprezentând puterea antenei.
Restricµii ³i preciz ri
a 2$N $ 15001
a 15000 $ x, y $ 15001
a Numerele reale din ³ierul de ie³ire trebuie scrise cu trei zecimale cu rotunjire.
a La evaluare, se veric  dac  diferenµa dintre soluµia a³at  ³i cea corect  (în valoare absolut )
este $ 0.01.
Exemplu
antena.in antena.out Explicaµie
7 3.250 2.875 Antena va  plasat  în punctul
5 0 3.366 de coordonate 3.250, 2.825 iar
2 6 puterea antenei este 3.366
4 5
2 2
0 2
3 6
5 2

Timp maxim de execuµie/test: 0.3 secunde pentru Windows ³i 0.1 secunde pentru Linux.
import java.io.*; // practic, trebuie sa determinam cele trei puncte
class Antena // prin care trece cercul care le acopera pe toate!!!
{
CAPITOLUL 17. GEOMETRIE COMPUTA•IONAL€ 304

static int n;
static int[] x,y;
static double x0, y0, r0;

public static void main(String[] args) throws IOException


{
int k;
long t1,t2;
t1=System.nanoTime();
StreamTokenizer st=new StreamTokenizer(
new BufferedReader(new FileReader("antena.in")));
PrintWriter out=new PrintWriter(
new BufferedWriter(new FileWriter("antena.out")));

st.nextToken(); n=(int)st.nval;
x=new int[n+1];
y=new int[n+1];

for(k=1;k<=n;k++)
{
st.nextToken(); x[k]=(int)st.nval;
st.nextToken(); y[k]=(int)st.nval;
}

if(n>3)
{
puncteExtreme();
cercDeDiametru(x[1],y[1],x[2],y[2]);
for(k=3;k<=n;k++)
if(!esteInCerc(k))
cercPrin(x[k],y[k],k-1); // trece prin Pk si acopera 1,2,...,k-1
}
else cercCircumscris(x[1],y[1],x[2],y[2],x[3],y[3]);

// scriere cu 3 zecimale rotunjite


out.print( (double)((int)((x0+0.0005)*1000))/1000+" ");
out.println((double)((int)((y0+0.0005)*1000))/1000);
out.println((double)((int)((r0+0.0005)*1000))/1000);
out.close();
t2=System.nanoTime();
System.out.println("Timp = "+((double)(t2-t1))/1000000000);
}// main(...)

// trece prin (xx,yy) si acopera punctele 1,2,...,k


static void cercPrin(int xx, int yy, int k)
{
int j;
cercDeDiametru(x[1],y[1],xx,yy); // trece prin P1 si (xx,yy)

for(j=2;j<=k;j++)
if(!esteInCerc(j))
cercPrin(xx,yy,x[j],y[j],j-1); // ... acopera 1,2,...,j-1
}// cercPrin(...)

// trece prin (xx,yy) si (xxx,yyy) si acopera 1,2,3,...,j


static void cercPrin(int xx,int yy,int xxx,int yyy,int j)
{
int i;
cercDeDiametru(xx,yy,xxx,yyy);
for(i=1;i<=j;i++) // acopera 1,2,...,j
CAPITOLUL 17. GEOMETRIE COMPUTA•IONAL€ 305

if(!esteInCerc(i))
cercCircumscris(xx,yy,xxx,yyy,x[i],y[i]);
}// cercPrin(...)

static boolean esteInCerc(int k)


{
if(d(x[k],y[k],x0,y0)<r0+0.0001) return true; else return false;
}

static void puncteExtreme()


{
int k,aux,min,max,kmin,kmax;

// caut cel mai din stanga punct (si mai jos) si-l pun pe pozitia 1
// (caut incepand cu pozitia 1)
kmin=1; min=x[1];
for(k=2;k<=n;k++)
if((x[k]<min)||(x[k]==min)&&(y[k]<y[kmin])) {min=x[k]; kmin=k;}
if(kmin!=1) swap(1,kmin);

// caut cel mai din dreapta (si mai sus) punct si-l pun pe pozitia 2
// (caut incepand cu pozitia 2)
kmax=2; max=x[2];
for(k=3;k<=n;k++)
if((x[k]>max)||(x[k]==max)&&(y[k]>y[kmax])) {max=x[k]; kmax=k;}
if(kmax!=2) swap(2,kmax);

// caut cel mai de jos (si mai la dreapta) punct si-l pun pe pozitia 3
// (caut incepand cu pozitia 3)
kmin=3; min=y[3];
for(k=4;k<=n;k++)
if((y[k]<min)||(y[k]==min)&&(x[k]>x[kmin])) {min=y[k]; kmin=k;}
if(kmin!=3) swap(3,kmin);

// caut cel mai de sus (si mai la stanga) punct si-l pun pe pozitia 4
// (caut incepand cu pozitia 4)
kmax=4; max=y[4];
for(k=5;k<=n;k++)
if((y[k]>max)||(y[k]==max)&&(x[k]<x[kmax])) {max=y[k]; kmax=k;}
if(kmax!=4) swap(4,kmax);

if(d(x[1],y[1],x[2],y[2])<d(x[3],y[3],x[4],y[4])) // puncte mai departate


{
swap(1,3);
swap(2,4);
}
}// puncteExtreme()

static void cercCircumscris(int x1,int y1,int x2,int y2,int x3,int y3)


{ // consider ca punctele nu sunt coliniare !
// (x-x0)^2+(y-y0)^2=r^2 ecuatia cercului verificata de punctele P1,P2,P3
// 3 ecuatii si 3 necunoscute: x0, y0, r

double a12, a13, b12, b13, c12, c13; // int ==> eroare !!!

a12=2*(x1-x2); b12=2*(y1-y2); c12=x1*x1+y1*y1-x2*x2-y2*y2;


a13=2*(x1-x3); b13=2*(y1-y3); c13=x1*x1+y1*y1-x3*x3-y3*y3;

// sistemul devine: a12*x0+b12*y0=c12;


// a13*x0+b13*y0=c13;
CAPITOLUL 17. GEOMETRIE COMPUTA•IONAL€ 306

if(a12*b13-a13*b12!=0)
{
x0=(c12*b13-c13*b12)/(a12*b13-a13*b12);
y0=(a12*c13-a13*c12)/(a12*b13-a13*b12);
r0=Math.sqrt((x1-x0)*(x1-x0)+(y1-y0)*(y1-y0));
}
else // consider cercul de diametru [(minx,maxx),(miny,maxy)]
{ // punctele sunt coliniare !
x0=(max(x1,x2,x3)+min(x1,x2,x3))/2;
y0=(max(y1,y2,y3)+min(y1,y2,y3))/2;
r0=d(x0,y0,x1,y1)/2;
}
}// cercCircumscris(...)

static void cercDeDiametru(int x1,int y1,int x2,int y2)


{
x0=((double)x1+x2)/2;
y0=((double)y1+y2)/2;
r0=d(x1,y1,x2,y2)/2;
}// cercDeDiametru(...)

static int min(int a,int b) { if(a<b) return a; else return b; }


static int max(int a,int b) { if(a>b) return a; else return b; }

static int min(int a,int b,int c) { return min(min(a,b),min(a,c)); }


static int max(int a,int b,int c) { return max(min(a,b),max(a,c)); }

static double d(int x1, int y1, int x2, int y2)
{
double dx,dy;
dx=x2-x1;
dy=y2-y1;
return Math.sqrt(dx*dx+dy*dy);
}

static double d(double x1, double y1, double x2, double y2)
{
double dx,dy;
dx=x2-x1;
dy=y2-y1;
return Math.sqrt(dx*dx+dy*dy);
}

//interschimb punctele i si j
static void swap(int i, int j)
{
int aux;
aux=x[i]; x[i]=x[j]; x[j]=aux;
aux=y[i]; y[i]=y[j]; y[j]=aux;
}// swap(...)
}// class

17.9.3 Mo³ia lui P cal  - OJI2004 clasa a XI-a

P cal  a primit, a³a cum era învoiala, un petec de teren de pe mo³ia boierului. Terenul este
împrejmuit complet cu segmente drepte de gard ce se sprijin  la ambele capete de câte un par
zdrav n. La o nou  prinsoare, P cal  iese iar în câ³tig ³i prime³te dreptul s  str mute ni³te pari,
unul câte unul, cum i-o  voia, astfel încât s -³i extind  suprafaµa de teren. Dar învoiala prevede
c  ecare par poate  mutat în orice direcµie, dar nu pe o distanµ  mai mare decât o valoare dat 
(scris  pe ecare par) ³i ecare segment de gard, ind cam ³uubred, poate  rotit ³i prelungit de
CAPITOLUL 17. GEOMETRIE COMPUTA•IONAL€ 307

la un singur cap t, cel lalt r mânând nemi³cat.


Cunoscând poziµiile iniµiale ale parilor ³i valoarea înscris  pe ecare par, se cere suprafaµa
maxim  cu care poate s -³i extind  P cal  proprietatea. Se ³tie c  parii sunt daµi într-o ordine
oarecare, poziµiile lor iniµiale sunt date prin numere întregi de cel mult 3 cifre, distanµele pe care
ecare par poate  deplasat sunt numere naturale strict pozitive ³i gura format  de terenul iniµial
este un poligon neconcav.
Date de intrare
Fi³ierul MOSIA.IN conµine n  1 linii cu urm toarele valori:
n - num rul de pari
x1 y1 d1 - coordonatele iniµiale ³i distanµa pe care poate  mutat parul 1
x2 y2 d2 - coordonatele iniµiale ³i distanµa pe care poate  mutat parul 2
. . .
xn yn dn - coordonatele iniµiale ³i distanµa pe care poate  mutat parul n
Date de ie³ire
În ³ierul MOSIA.OUT se scrie un num r real cu 4 zecimale ce reprezint  suprafaµa maxim 
cu care se poate m ri mo³ia.
Restricµii ³i observaµii:
3 $ N & 200 num r natural
1000 $ xi , yi $ 1000 numere întregi
0 $ di & 20 numere întregi
poligonul neconcav se dene³te ca un poligon convex cu unele vârfuri coliniare
poziµiile parilor sunt date într-o ordine oarecare
poligonul obµinut dup  mutarea parilor poate  concav
poziµiile nale ale parilor nu sunt în mod obligatoriu numere naturale
Exemplu
Pentru ³ierul de intrare
4
-3 0 2
3 0 3
0 6 2
0 -6 6
se va scrie în ³ierul de ie³ire valoarea 30.0000
Explicaµie: prin mutarea parilor 1 ³i 2 cu câte 2 ³i respectiv 3 unit µi, se obµine un teren
având suprafaµa cu 30 de unit µi mai mare decât terenul iniµial.
Timp limit  de executare: 1 sec./test

17.9.4 Partiµie - ONI2006 baraj

Ionic  a primit de ziua lui de la tat l s u un joc format din piese de form  triunghiular  de
dimensiuni diferite ³i o suprafaµu a magnetic  pe care acestea pot  a³ezate.
Pe suprafaµa magnetic  este desenat un triunghi dreptunghic cu lungimile catetelor a, respectiv
b ³i un sistem de coordonate xOy cu originea în unghiul drept al triunghiului, semiaxa Ox pe
cateta de lungime a, respectiv semiaxa Oy pe cateta de lungime b.
La un moment dat Ionic  a³eaz  pe tabla magnetic  n piese, pentru care se cunosc coordo-
natele vârfurilor lor. Tat l lui Ionic  vrea s  verice dac  pe tabl  piesele realizeaz  o partiµie a
triunghiului dreptunghic desenat, adic  dac  sunt îndeplinite condiµiile:
a nu exist  piese suprapuse;
a piesele acoper  toat  porµiunea desenat  (în form  de triunghi dreptunghic);
a nu exist  porµiuni din piese în afara triunghiului desenat.

Cerinµ 
Se cere s  se verice dac  piesele plasate pe tabla magnetic  formeaz  o partiµie a triunghiului
desenat pe tabla magnetic .
Date de intrare
Fi³ierul de intrare part.in conµine pe prima linie un num r natural k , reprezentând num rul
de seturi de date din ³ier. Urmeaz  k grupe de linii, câte o grup  pentru ecare set de date.
Grupa de linii corespunz toare unui set este format  dintr-o linie cu numerele a, b, n separate
între ele prin câte un spaµiu ³i n linii cu câte ³ase numere întregi separate prin spaµii reprezentând
coordonatele vârfurilor (abscis  ordonat ) celor n piese, câte o pies  pe o linie.
CAPITOLUL 17. GEOMETRIE COMPUTA•IONAL€ 308

Date de ie³ire
În ³ierul part.out se vor scrie k linii, câte o linie pentru ecare set de date. Pe linia i
(i 1, 2, ..., k ) se va scrie 1 dac  triunghiurile din setul de date i formeaz  o partiµie a triunghiului
desenat pe tabla magnetic  sau 0 în caz contrar.
Restricµii ³i preciz ri
a 1 & n & 150
a 1 & k & 10
a a, b sunt numere întregi din intervalul 0, 31000
a Coordonatele vârfurilor pieselor sunt numere întregi din intervalul [0, 31000].
Exemplu
part.in part.out
2 1
20 10 4 0
0 5 0 10 10 5
0 0 10 5 0 5
0 0 10 0 10 5
10 0 20 0 10 5
20 10 2
0 0 0 10 10 5
0 0 20 0 20 10

y y

10 10

T1
5 5 T1
T2 T2
T3 T4
x x
10 20 10 20
a) b)

Figura 17.2: a) pentru setul 1 de date ³i b) pentru setul 2 de date

Timp maxim de execuµie: 0.3 secunde/test

Prelucrare în Java dup  rezolvarea în C a autorului problemei


import java.io.*;
class part
{
static final int ON_EDGE=0;
static final int INSIDE=1;
static final int OUTSIDE=2;
static final int N_MAX=512;

static int N, A, B;
static int[][] X=new int[N_MAX][3];
static int[][] Y=new int[N_MAX][3];

static int sgn(int x)


{
return x>0 ? 1 : (x<0 ? -1 : 0);
}

static int point_sign (int x1, int y1, int x2, int y2, int _x, int _y)
{
int a, b, c;
a=y2-y1;
b=x1-x2;
c=y1*x2-x1*y2;
CAPITOLUL 17. GEOMETRIE COMPUTA•IONAL€ 309

return sgn(a*_x+b*_y+c);
}

static int point_inside (int n, int x, int y)


{
int i;
int[] sgn=new int[3];
for(i=0;i<3;i++)
sgn[i]=point_sign(X[n][i],Y[n][i],X[n][(i+1)%3],Y[n][(i+1)%3],x,y);
if(sgn[0]*sgn[1]<0 || sgn[0]*sgn[2]<0 || sgn[1]*sgn[2]<0) return OUTSIDE;
if(sgn[0]==0 || sgn[1]==0 || sgn[2]==0) return ON_EDGE;
return INSIDE;
}

static boolean segment_intersect(int x1,int y1,int x2,int y2,


int x3,int y3,int x4,int y4)
{
int a1,b1,c1,a2,b2,c2;
a1=y2-y1; b1=x1-x2; c1=y1*x2-x1*y2;
a2=y4-y3; b2=x3-x4; c2=y3*x4-x3*y4;
return sgn(a1*x3+b1*y3+c1)*sgn(a1*x4+b1*y4+c1)<0 &&
sgn(a2*x1+b2*y1+c2)*sgn(a2*x2+b2*y2+c2)<0;
}

static boolean triangle_intersect (int n1, int n2)


{
int i,j,x,t1=0,t2=0;
for(i=0;i<3;i++)
{
if((x=point_inside(n2,X[n1][i],Y[n1][i]))==ON_EDGE) t1++;
if(x==INSIDE) return true;
if((x=point_inside(n1,X[n2][i],Y[n2][i]))==ON_EDGE) t2++;
if(x==INSIDE) return true;
}
if(t1==3 || t2==3) return true;
for(i=0;i<3;i++)
for(j=0;j<3;j++)
if(segment_intersect(
X[n1][i],Y[n1][i],X[n1][(i+1)%3],Y[n1][(i+1)%3],
X[n2][j],Y[n2][j],X[n2][(j+1)%3],Y[n2][(j+1)%3]
)) { return true; }
return false;
}

static int solve()


{
int i,j,area=0;
for(i=0;i<N;i++)
{
for(j=0;j<3;j++)
if(point_inside(N,X[i][j],Y[i][j])==OUTSIDE) return 0;

area+=Math.abs((X[i][1]*Y[i][2]-X[i][2]*Y[i][1])-
(X[i][0]*Y[i][2]-X[i][2]*Y[i][0])+
(X[i][0]*Y[i][1]-X[i][1]*Y[i][0]));
}

if(area!=A*B) return 0;

for(i=0;i<N;i++)
CAPITOLUL 17. GEOMETRIE COMPUTA•IONAL€ 310

for(j=i+1;j<N;j++)
if(triangle_intersect(i,j)) return 0;

return 1;
}

public static void main(String[] args) throws IOException


{
int tests, i, j;
StreamTokenizer st=new StreamTokenizer(
new BufferedReader(new FileReader("part.in")));
PrintWriter out=new PrintWriter(
new BufferedWriter(new FileWriter("part.out")));

st.nextToken(); tests=(int)st.nval;

for(; tests-->0;)
{
st.nextToken(); A=(int)st.nval;
st.nextToken(); B=(int)st.nval;
st.nextToken(); N=(int)st.nval;

for(i=0;i<N;i++)
for(j=0;j<3;j++)
{
st.nextToken(); X[i][j]=(int)st.nval;
st.nextToken(); Y[i][j]=(int)st.nval;
}

X[N][0]=0; Y[N][0]=0;
X[N][1]=A; Y[N][1]=0;
X[N][2]=0; Y[N][2]=B;
out.println(solve());
}
out.close();
}// main(...)
}// class

17.9.5 Triunghi - ONI2007 cls 9

În comuna Triunghi din România sunt n µ rani codicaµi prin numerele 1, 2, ..., n. Dup 
anul 1990 a început retrocedarea suprafeµelor de p mânt deµinute înainte de colectivizare. Fiecare
µ ran are un document prin care dovede³te c  este proprietar pe o singur  suprafaµ  de teren de
form  triunghiular . Din p cate, documentele dau b taie de cap primarului (care se ocup  de
retrocedarea suprafeµelor de p mânt), pentru c  sunt porµiuni din suprafeµele de p mânt care se
reg sesc pe mai multe documente.
În aceast  comun  exist  o fântân  cu ap , ind posibil ca ea s  e revendicat  de mai mulµi
µ rani. O suprafaµ  de p mânt este dat  prin coordonatele celor trei colµuri, iar fântâna este
considerat  punctiform  ³i dat  prin coordonatele punctului.
Cerinµ 
S  se scrie un program care s  determine:
a Codurile µ ranilor care au documente cu suprafeµe de p mânt ce conµin în interior sau pe
frontier  fântâna.
b Codul µ ranului ce deµine un document cu suprafaµa de teren, care include toate celelalte
suprafeµe.
Date de intrare
Fi³ierul de intrare triunghi.in are pe prima linie num rul n de µ rani, pe urm toarele n linii
câte 6 valori numere întregi separate prin câte un spaµiu, în formatul: x1 y1 x2 y2 x3 y3, ce
CAPITOLUL 17. GEOMETRIE COMPUTA•IONAL€ 311

reprezint  coordonatele celor trei colµuri ale suprafeµei triunghiulare deµinute de un µ ran (x1, x2,
x3 abscise, iar y1, y2, y3 ordonate). Pe linia i  1 se a  coordonatele colµurilor suprafeµei de
teren triunghiulare deµinute de µ ranul i, i 1, 2, ..., n. Ultima linie a ³ierului (linia n  2) va
conµine coordonatele fântânii în formatul x y , cu un spaµiu între ele (x abscis , iar y ordonat ).
Date de ie³ire
Fi³ierul de ie³ire triunghi.out va conµine pe prima linie r spunsul de la punctul a, adic :
num rul de µ rani care îndeplinesc condiµia din cerinµ  ³i apoi codurile lor (în ordine cresc toare),
cu un spaµiu între ele. Dac  nu exist  µ rani cu condiµia din cerinµ , pe prima linie se va scrie cifra
0. Pe linia a doua se va scrie r spunsul de la punctul b, adic : codul µ ranului cu proprietatea
cerut , sau cifra 0, dac  nu exist  un astfel de µ ran.
Restricµii ³i preciz ri
a 2 & n & 65
a coordonatele colµurilor suprafeµelor de p mânt ³i ale fântânii sunt numere întregi din inter-
valul 3000, 3000
a cele trei colµuri ale ec rei suprafeµe de p mânt sunt distincte ³i necoliniare
a nu exist  doi µ rani care s  deµin  aceea³i suprafaµ  de p mânt
a nu se acord  punctaje parµiale.

Exemplu
secv.in secv.out
3 212
10 0 0 10 10 10 2
0 100 100 0 -100 0
0 0 10 0 0 10
10 5
Explicaµie:
La punctul a, sunt doi µ rani care deµin suprafeµe de p mânt ce au în interior sau pe frontier 
fântâna, cu codurile 1 ³i 2.
La punctul b, µ ranul cu codul 2 deµine o suprafaµ  de teren care include, suprafeµele de
p mânt deµinute de ceilalµi µ rani (cu codurile 1 ³i 3).
Timp maxim de execuµie/test: 0.1 secunde

Indicaµii de rezolvare - descriere soluµie

Descrierea soluµiei (Prof. Doru Popescu Anastasiu)


Not m cu T1 , T2 , ..., Tn triunghiurile corespunz toare suprafeµelor ³i cu I punctul unde se
g se³te fântâna.
Ti Ai Bi Ci , i 1, 2, ..., n.
a
nr 0
Pentru i 1, ..., n veric m dac  I este interior sau pe frontiera lui Ti , în caz armativ
nr nr  1 ³i solnr i. A³ m nr ³i vectorul sol.
Pentru a verica dac  I este interior sau pe frontiera unui triunghi Ti este sucient s  veric m
dac :
aria Ai Bi Ci  aria IAi Bi   aria IAi Ci   aria IBi Ci 
O alt  variant  ar  s  folosim poziµia unui punct faµ  de o dreapt .
b
Dac  exist  un asemenea triunghi atunci el este de arie maxim . Astfel determin m triunghiul
p de arie maxim . Pentru acest triunghi veric m dac  toate celelalte n  1 triunghiuri sunt
interioare sau pe frontiera lui Tp (adic  dac  au toate vârfurile în interiorul sau pe frontiera lui
Tp ). În caz armativ se a³eaz  p, altfel 0.

Codul surs 
import java.io.*;
class Pereti
{
CAPITOLUL 17. GEOMETRIE COMPUTA•IONAL€ 312

static int n,x0,y0;


static int smax=-1,imax=-1,nr=0;
static int[] x1=new int[66];
static int[] y1=new int[66];
static int[] x2=new int[66];
static int[] y2=new int[66];
static int[] x3=new int[66];
static int[] y3=new int[66];
static int[] sol=new int[66];

public static void main(String[] args) throws IOException


{
int i,j,s,s1,s2,s3;
boolean ok;

StreamTokenizer st=new StreamTokenizer(


new BufferedReader(new FileReader("14-triunghi.in")));
PrintWriter out=new PrintWriter(
new BufferedWriter(new FileWriter("triunghi.out")));
st.nextToken(); n=(int)st.nval;
for(i=1;i<=n;i++)
{
st.nextToken(); x1[i]=(int)st.nval;
st.nextToken(); y1[i]=(int)st.nval;
st.nextToken(); x2[i]=(int)st.nval;
st.nextToken(); y2[i]=(int)st.nval;
st.nextToken(); x3[i]=(int)st.nval;
st.nextToken(); y3[i]=(int)st.nval;
}
st.nextToken(); x0=(int)st.nval;
st.nextToken(); y0=(int)st.nval;
for(i=1;i<=n;i++)
{
s=aria(x1[i],y1[i],x2[i],y2[i],x3[i],y3[i]);
if(s>smax) {smax=s; imax=i;}
s1=aria(x1[i],y1[i],x2[i],y2[i],x0,y0);
s2=aria(x2[i],y2[i],x3[i],y3[i],x0,y0);
s3=aria(x1[i],y1[i],x3[i],y3[i],x0,y0);
if(s==s1+s2+s3) {nr++; sol[nr]=i;}
//System.out.println("i = "+i+" --> "+s+" "+s1+" "+s2+" "+s3);
}
if(nr>0)
{
out.print(nr+" ");
for(i=1;i<=nr;i++)
if(i!=nr) out.print(sol[i]+" "); else out.println(sol[i]);
}
else out.println(0);

//System.out.println("imax = "+imax);
ok=true;
for(i=1;i<=n;i++)
{
if(i==imax) continue;

s1=aria(x1[imax],y1[imax],x2[imax],y2[imax],x1[i],y1[i]);
s2=aria(x2[imax],y2[imax],x3[imax],y3[imax],x1[i],y1[i]);
s3=aria(x1[imax],y1[imax],x3[imax],y3[imax],x1[i],y1[i]);
if(smax!=s1+s2+s3) { ok=false; break; }
CAPITOLUL 17. GEOMETRIE COMPUTA•IONAL€ 313

s1=aria(x1[imax],y1[imax],x2[imax],y2[imax],x2[i],y2[i]);
s2=aria(x2[imax],y2[imax],x3[imax],y3[imax],x2[i],y2[i]);
s3=aria(x1[imax],y1[imax],x3[imax],y3[imax],x2[i],y2[i]);
if(smax!=s1+s2+s3) { ok=false; break; }

s1=aria(x1[imax],y1[imax],x2[imax],y2[imax],x3[i],y3[i]);
s2=aria(x2[imax],y2[imax],x3[imax],y3[imax],x3[i],y3[i]);
s3=aria(x1[imax],y1[imax],x3[imax],y3[imax],x3[i],y3[i]);
if(smax!=s1+s2+s3) { ok=false; break; }
}
if(ok) out.println(imax); else out.println(0);
out.close();
}// main(...)

static int aria(int x1, int y1, int x2, int y2, int x3, int y3) // dubla ...
{
int s=x1*y2+x2*y3+x3*y1-y1*x2-y2*x3-y3*x1;
if(s<0) s=-s;
return s;
}
}// class
Capitolul 18

Teoria jocurilor

18.1 Jocul NIM

18.1.1 Prezentare general 

18.1.2 Exemple

314
Capitolul 19

Alµi algoritmi

19.1 Secvenµ  de sum  maxim 

19.1.1 Prezentare general 

19.1.2 Exemple

19.2 Cuvinte ... a, b, c, d

Figura 19.1: abcd

315
CAPITOLUL 19. AL•I ALGORITMI 316

19.3 Algoritmul Belmann-Ford


Pentru grafuri cu costuri negative (dar fara cicluri negative!) se poate folosi algoritmul
Bellman-Ford.

19.3.1 Algoritmul Belmann-Ford pentru grafuri neorientate


// drum scurt in graf neorientat cu costuri negative (dar fara ciclu negativ!)
// Algoritm: 1. init
// 2. repeta de n-1 ori
// pentru fiecare arc (u,v)
// relax(u,v)
// 3. OK=true
// 4. pentru fiecare muchie (u,v)
// daca d[v]>d[u]+w[u][v]
// OK=false
// 5. return OK

import java.io.*;
class BellmanFord
{
static final int oo=0x7fffffff; // infinit
static int n,m; // varfuri, muchii

static int[][] w; // matricea costurilor


static int[] d; // d[u]=dist(nods,u)
static int[] p; // p[u]=predecesorul nodului u

public static void main(String[] args) throws IOException


{
int i,j,k;
int nods=1, cost; // nod sursa, costul arcului
int u,v;
StreamTokenizer st=new StreamTokenizer(
new BufferedReader(new FileReader("BellmanFordNeorientat.in"

st.nextToken(); n=(int)st.nval;
st.nextToken(); m=(int)st.nval;

w=new int[n+1][n+1];
d=new int[n+1];
p=new int[n+1];

for(i=1;i<=n;i++)
for(j=1;j<=n;j++)
w[i][j]=oo;// initializare !

for(k=1;k<=m;k++)
{
st.nextToken(); i=(int)st.nval;
st.nextToken(); j=(int)st.nval;
st.nextToken(); cost=(int)st.nval;
w[i][j]=cost;
w[j][i]=cost; // numai pentru graf neorientat
}

init(nods);

for(k=1;k<=n-1;k++) // de n-1 ori !!!


for(u=1;u<n;u++) // vectorii muchiilor erau mai buni !
CAPITOLUL 19. AL•I ALGORITMI 317

for(v=u+1;v<=n;v++) // lista de adiacenta era mai buna !


if(w[u][v]<oo) // (u,v)=muchie si u<v
relax(u,v);

boolean cicluNegativ=false;
for(u=1;u<n;u++)
for(v=u+1;v<=n;v++)
if(w[u][v]<oo) // (u,v)=muchie
if(d[u]<oo) // atentie !!! oo+ceva=???
if(d[v]>d[u]+w[u][v])
{
cicluNegativ=true;
break;
}

if(!cicluNegativ)
for(k=1;k<=n;k++)
{
System.out.print(nods+"-->"+k+" dist="+d[k]+" drum: ");
drum(k);
System.out.println();
}
}//main

static void init(int s)


{
int u;
for(u=1;u<=n;u++) { d[u]=oo; p[u]=-1; }
d[s]=0;
}// init(...)

static void relax(int u,int v) // (u,v)=arc(u-->v)


{
if(d[u]<oo) // oo+ceva ==> ???
if(d[u]+w[u][v]<d[v])
{
d[v]=d[u]+w[u][v];
p[v]=u;
}
}

static void drum(int k) // s --> ... --> k


{
if(p[k]!=-1) drum(p[k]);
System.out.print(k+" ");
}

static void afisv(int[] x)


{
int i;
for(i=1;i<=n;i++) System.out.print(x[i]+" ");
System.out.println();
}
}//class

/*
6 7
1 2 -3 1-->1 dist=0 drum: 1
1 3 1 1-->2 dist=-3 drum: 1 2
2 3 2 1-->3 dist=-1 drum: 1 2 3
CAPITOLUL 19. AL•I ALGORITMI 318

3 4 1 1-->4 dist=0 drum: 1 2 3 4


4 5 1 1-->5 dist=1 drum: 1 2 3 4 5
5 6 -3 1-->6 dist=-2 drum: 1 2 3 4 5 6
4 6 2
*/

19.3.2 Alg Belmann-Ford pentru grafuri orientate


// drumuri scurte in graf orientat cu costuri negative (dar fara ciclu negativ!)
// Dijkstra nu functioneaza daca apar costuri negative !
// Algoritm: 1. init
// 2. repeta de n-1 ori
// pentru fiecare arc (u,v)
// relax(u,v)
// 3. OK=true
// 4. pentru fiecare arc (u,v)
// daca d[v]>d[u]+w[u][v]
// OK=false
// 5. return OK

import java.io.*;
class BellmanFord
{
static final int oo=0x7fffffff;
static int n,m; // varfuri, muchii
static int[][] w; // matricea costurilor
static int[] d; // d[u]=dist(nods,u)
static int[] p; // p[u]=predecesorul nodului u

public static void main(String[] args) throws IOException


{
int i,j,k;
int nods=1, cost; // nod sursa, costul arcului
int u,v;
StreamTokenizer st=new StreamTokenizer(
new BufferedReader(new FileReader("BellmanFordNeorientat.in"
st.nextToken(); n=(int)st.nval;
st.nextToken(); m=(int)st.nval;

w=new int[n+1][n+1];
d=new int[n+1];
p=new int[n+1];
for(i=1;i<=n;i++) for(j=1;j<=n;j++) w[i][j]=oo; // initializare !
for(k=1;k<=m;k++)
{
st.nextToken(); i=(int)st.nval;
st.nextToken(); j=(int)st.nval;
st.nextToken(); cost=(int)st.nval;
w[i][j]=cost;
}

init(nods);
for(k=1;k<=n-1;k++) // de n-1 ori !!!
for(u=1;u<=n;u++) // vectorii arcelor erau mai buni !
for(v=1;v<=n;v++) // lista de adiacenta era mai buna !
if(w[u][v]<oo) // (u,v)=arc
relax(u,v);

boolean cicluNegativ=false;
for(u=1;u<=n;u++)
for(v=1;v<=n;v++)
CAPITOLUL 19. AL•I ALGORITMI 319

if(w[u][v]<oo) // (u,v)=arc
if(d[u]<oo) // atentie !!! oo+ceva=???
if(d[v]>d[u]+w[u][v])
{
cicluNegativ=true;
break;
}
System.out.println(cicluNegativ);
if(!cicluNegativ)
for(k=1;k<=n;k++)
{
System.out.print(nods+"-->"+k+" dist="+d[k]+" drum: ");
if(d[k]<oo) drum(k); else System.out.print("Nu exista drum!");
System.out.println();
}
}//main

static void init(int s)


{
int u;
for(u=1;u<=n;u++) { d[u]=oo; p[u]=-1; }
d[s]=0;
}// init()

static void relax(int u,int v) // (u,v)=arc(u-->v)


{
if(d[u]<oo) // oo+ceva ==> ???
if(d[u]+w[u][v]<d[v])
{
d[v]=d[u]+w[u][v];
p[v]=u;
}
}// relax(...)

static void drum(int k) // s --> ... --> k


{
if(p[k]!=-1) drum(p[k]);
System.out.print(k+" ");
}// drum(...)

static void afisv(int[] x)


{
int i;
for(i=1;i<=n;i++) System.out.print(x[i]+" ");
System.out.println();
}
}//class

/*
6 8 false
1 2 -3 1-->1 dist=0 drum: 1
1 3 1 1-->2 dist=-4 drum: 1 3 2
2 3 6 1-->3 dist=1 drum: 1 3
3 4 1 1-->4 dist=2 drum: 1 3 4
5 4 1 1-->5 dist=2147483647 drum: Nu exista drum!
5 6 -3 1-->6 dist=4 drum: 1 3 4 6
4 6 2
3 2 -5
*/
CAPITOLUL 19. AL•I ALGORITMI 320

19.3.3 Alg Belmann-Ford pentru grafuri orientate aciclice


// Cele mai scurte drumuri in digraf (graf orientat ACICLIC)
// Dijkstra nu functioneaza daca apar costuri negative !
// Algoritm: 1. sortare topologica O(n+m)
// 2. init(G,w,s)
// 3. pentru toate nodurile u in ordine topologica
// pentru toate nodurile v adiacente lui u
// relax(u,v)
// OBS: O(n+m)

import java.io.*;
class BellmanFordDAG
{
static final int oo=0x7fffffff;
static final int WHITE=0, BLACK=1; // color[u]=BLACK ==> u in lista
static int n,m,t,pozl; // varfuri, muchii, time, pozitie in lista
static int[] color; // culoare
static int[] lista; // lista
static int[] gi; // grad interior
static int[][] w; // matricea costurilor
static int[] d; // d[u]=dist(nods,u)
static int[] p; // p[u]=predecesorul nodului u

public static void main(String[] args) throws IOException


{
int i,j,k;
int nods=1, cost; // nod sursa, costul arcului
int u,v;
StreamTokenizer st=new StreamTokenizer(
new BufferedReader(new FileReader("BellmanFordDAG.in")));
st.nextToken(); n=(int)st.nval;
st.nextToken(); m=(int)st.nval;

w=new int[n+1][n+1];
color=new int[n+1];
lista=new int[n+1];
gi=new int[n+1];
d=new int[n+1];
p=new int[n+1];

for(i=1;i<=n;i++)
for(j=1;j<=n;j++) w[i][j]=oo; // initializare !

for(k=1;k<=m;k++)
{
st.nextToken(); i=(int)st.nval;
st.nextToken(); j=(int)st.nval;
st.nextToken(); cost=(int)st.nval;
w[i][j]=cost;
gi[j]++;
}

topoSort();
System.out.print("Lista : "); afisv(lista);

init(nods);
for(i=1;i<=n;i++)
{
u=lista[i];
for(v=1;v<=n;v++)
CAPITOLUL 19. AL•I ALGORITMI 321

if(w[u][v]<oo) // lista de adiacenta era mai buna !


relax(u,v);
}

System.out.print("Distante : ");
afisv(d);

for(k=1;k<=n;k++)
{
if(d[k]<oo) System.out.print(k+" : "+d[k]+" ... ");
else System.out.print(k+": oo ... ");
drum(k);
System.out.println();
}
}//main

static void init(int s)


{
int u;
for(u=1;u<=n;u++)
{
d[u]=oo;
p[u]=-1;
}
d[s]=0;
}// init(...)

static void relax(int u,int v) // (u,v)=arc(u-->v)


{
if(d[u]<oo) // oo+ceva ==> ???
if(d[u]+w[u][v]<d[v]) { d[v]=d[u]+w[u][v]; p[v]=u; }
}// relax(...)

static void drum(int k) // s --> ... --> k


{
if(p[k]!=-1) drum(p[k]);
if(d[k]<oo) System.out.print(k+" ");
}

static void topoSort()


{
int u,i,k,pozl;
for(i=1;i<=n;i++) // oricum era initializat implicit, dar ... !!!
color[i]=WHITE;
pozl=1;
for(k=1;k<=n;k++) // pun cate un nod in lista
{
u=nodgi0();
color[u]=BLACK;
micsorezGrade(u);
lista[pozl++]=u;
}
}// topoSort()

static int nodgi0() // nod cu gradul interior zero


{
int v,nod=-1;
for(v=1;v<=n;v++) // coada cu prioritati (heap) este mai buna !!!
if(color[v]==WHITE)
if(gi[v]==0) {nod=v; break;}
CAPITOLUL 19. AL•I ALGORITMI 322

return nod;
}// nodgi0()

static void micsorezGrade(int u)


{
int v;
for(v=1;v<=n;v++) // lista de adiacenta este mai buna !!!
if(color[v]==WHITE)
if(w[u][v]<oo) gi[v]--;
}// micsorezGrade(...)

static void afisv(int[] x)


{
int i;
for(i=1;i<=n;i++)
if(x[i]<oo) System.out.print(x[i]+" "); else System.out.print("oo ");
System.out.println();
}// afisv(...)
}//class

/*
6 7 Lista : 1 3 2 5 4 6
1 2 -3 Distante : 0 -4 1 2 oo 4
1 3 1 1 : 0 ... 1
3 4 1 2 : -4 ... 1 3 2
5 4 1 3 : 1 ... 1 3
5 6 -3 4 : 2 ... 1 3 4
4 6 2 5: oo ...
3 2 -5 6 : 4 ... 1 3 4 6
*/
323
Anexa A

Un pic de matematic !

A.1 ...

A.1.1 Prezentare general 

...

A.1.2 Exemple

...

324
Anexa B

Un pic de programare!

B.1 ...

B.1.1 Prezentare general 

...

B.1.2 Exemple

...

325
Glosar

algoritm, 1 Levenshtein, 250

distanµ  Levenshtein, 253


prex, 253
I.d.k.:
I don't know who the author is., v trage cu tunul, iv

326
Bibliograe
[1] Aho, A., Hopcroft, J., Ullman, J.D.; Data strutures and algorithms, Addison Wesley, 1983

[2] Andreica M.I.; Elemente de algoritmic  - probleme ³i soluµii, Cibernetica MC, 2011

[3] Andonie R., Gârbacea I.; Algoritmi fundamentali, o perspectiv  C++, Ed. Libris, 1995

[4] Atanasiu, A.; Concursuri de informatic . Editura Petrion, 1995

[5] Bell D., Perr M.; Java for Students, Second Edition, Prentice Hall, 1999

[6] Calude C.; Teoria algoritmilor, Ed. Universit µii Bucure³ti, 1987

[7] Cerchez, E., “erban, M.; Informatic  - manual pentru clasa a X-a., Ed. Polirom, 2000

[8] Cerchez, E.; Informatic  - Culegere de probleme pentru liceu, Ed. Polirom, 2002

[9] Cerchez, E., “erban, M.; Programarea în limbajul C/C++ pentru liceu, Ed. Polirom, 2005

[10] Cori, R.; Lévy, J.J.; Algorithmes et Programmation, Polycopié, version 1.6;
http://w3.edu.polytechnique.fr/informatique/
[11] Cormen, T.H., Leiserson C.E., Rivest, R.L.; Introducere în Algoritmi, Ed Agora, 2000

[12] Cormen, T.H., Leiserson C.E., Rivest, R.L.; Pseudo-Code Language, 1994

[13] Cristea, V.; Giumale, C.; Kalisz, E.; Paunoiu, Al.; Limbajul C standard, Ed. Teora, Bucure³ti,
1992

[14] Erickson J.; Combinatorial Algorithms; http://www.uiuc.edu/~jeffe/

[15] Flanagan, D.; Java in a Nutshell, O'Reilly, 1997.

[16] Giumale C., Negreanu L., C linoiu S.; Proiectarea ³i analiza algoritmilor. Algoritmi de sortare,
Ed. All, 1997

[17] Halim S., Halim F., Competitive programming, 2013

[18] Knuth, D.E.; Arta program rii calculatoarelor, vol. 1: Algoritmi fundamentali, Ed. Teora,
1999.

[19] Knuth, D.E.; Arta programarii calculatoarelor, vol. 2: Algoritmi seminumerici, Ed. Teora,
2000.

[20] Knuth, D.E.; Arta programarii calculatoarelor, vol. 3: Sortare ³i c utare, Ed. Teora, 2001.

[21] Knuth, D.E.; The art of computer programming, vol. 4A: Combinatorial algorithms, Part 1,
Addison Wesley, 2011.

[22] Lambert,K. A., Osborne,M.; Java. A Framework for Programming and Problem Solving,
PWS Publishing, 1999

[23] Laaksonen A.; Guide to competitive programming, Springer, 2017

[24] Livovschi, L.; Georgescu H.; Analiza ³i sinteza algoritmilor. Ed. Enciclopedic , Bucure³ti,
1986.

[25] Niemeyer, P., Peck J.; Exploring Java, O'Reilly, 1997.

327
BIBLIOGRAFIE 328

[26] Od gescu, I., Smeureanu, I., “tef nescu, I.; Programarea avansat  a calculatoarelor personale,
Ed. Militar , Bucure³ti 1993

[27] Od gescu, I.; Metode ³i tehnici de programare, Ed. Computer Lobris Agora, Cluj, 1998

[28] Popescu Anastasiu, D.; Puncte de articulaµie ³i punµi în grafuri, Gazeta de Informatic  nr.
5/1993

[29] R bâea, A.; https://math.univ-ovidius.ro/Doc/Admitere/CentruPregatire/


2007/Info/Lista_probleme_2000-2007.pdf
[30] R bâea, A.; https://math.univ-ovidius.ro/Doc/Admitere/CentruPregatire/
2007/Info/Rezolvari_C09.pdf

[31] R bâea, A.; https://math.univ-ovidius.ro/Doc/Admitere/CentruPregatire/


2007/Info/Rezolvari_C10.pdf
[32] R bâea, A.; https://math.univ-ovidius.ro/Doc/Admitere/CentruPregatire/
2007/Info/Rezolvari_C11.pdf

[33] R bâea, A.; https://math.univ-ovidius.ro/Doc/Admitere/CentruPregatire/


2007/Info/Rezolvari_Baraj.pdf
[34] Skiena S.S., Revilla M.A.; Programming challenges - The Programming Contest Training
Manual, Springer, 2003

[35] Tomescu, I.; Probleme de combinatoric  ³i teoria grafurilor, Editura Didactic  ³i Pedagogic ,
Bucure³ti, 1981

[36] Tomescu, I.; Leu, A.; Matematic  aplicat  în tehnica de calcul, Editura Didactic  ³i Pedago-
gic , Bucure³ti, 1982

[37] Tudor, S.; Informatic  - prolul real intensiv, varianta C++; Editura L&S, Bucure³ti, 2004

[38] Tudor, S.; Hutanu, V,; Informatic  intensiv; Editura L&S, Bucure³ti, 2006

[39] V duva, C.M.; Programarea in JAVA. Microinformatica, 1999

[40] Vi³inescu, R.; Vi³inescu, V.; Programare dinamic  - teorie ³i aplicaµii; GInfo nr. 15/4 2005

[41] Vlada, M.; Conceptul de algoritm - abordare modern , GInfo, 13/2,3 2003

[42] Vlada, M.; Grafuri neorientate ³i aplicaµii. Gazeta de Informatic , 1993

[43] Vlada, M.; Gândirea Algoritmic  - O Filosoe Modern  a Matematicii ³i Informaticii, CNIV-
2003, Editura Universit µii din Bucure³ti, 2003

[44] Weis, M.A.; Data structures and Algorithm Analysis, Ed. The Benjamin/Cummings Pu-
blishing Company. Inc., Redwoods City, California, 1995.

[45] Winston, P.H., Narasimhan, S.; On to JAVA, Addison-Wesley, 1996

[46] Wirth N.; Algorithms + Data Structures = Programs, Prentice Hall, Inc 1976

[47] *** - Gazeta de Informatic , Editura Libris, 1991-2005

[48] *** - https://github.com/DinuCr/CS/blob/master/Info/stuff%20stuff/Re


zolvari_C09.pdf
[49] *** - https://dokumen.tips/documents/rezolvaric09.html

[50] *** - https://www.scribd.com/doc/266218102/Rezolvari-C09

[51] *** - https://www.scribd.com/document/396362669/Rezolvari-C10

[52] *** - https://www.scribd.com/document/344769195/Rezolvari-C11

[53] *** - https://www.scribd.com/document/364077679/Rezolvari-C11-pdf

[54] *** - https://needoc.net/rezolvari-c11-pdf


BIBLIOGRAFIE 329

[55] *** - https://vdocumente.com/algoritmi-i-structuri-de-date.html

[56] *** - https://pdfslide.net/documents/algoritmi-si-structuri-de-date-


1-note-de-cuprins-1-oji-2002-clasa-a-ix-a-1-11.html
[57] *** - https://www.infoarena.ro/ciorna

[58] *** - https://infoarena.ro/olimpici

[59] *** - https://www.infogim.ro/

[60] *** - https://www.pbinfo.ro/

[61] *** - http://www.cplusplus.com/

[62] *** - http://www.cplusplus.com/doc/tutorial/operators/

[63] *** - http://www.info1cup.com/

[64] *** - http://www.olimpiada.info/

[65] *** - http://www.usaco.org/

[66] *** - http://algopedia.ro/

[67] *** - http://campion.edu.ro/

[68] *** - http://varena.ro/

[69] *** - http://rmi.lbi.ro/rmi_2019/

[70] *** - https://codeforces.com/

[71] *** - https://cpbook.net/

[72] *** - https://csacademy.com/

[73] *** - https://gazeta.info.ro/revigoram-ginfo/

[74] *** - https://oj.uz/problems/source/22

[75] *** - https://profs.info.uaic.ro/~infogim/2019/index.html

[76] *** - https://wandbox.org/

[77] *** - https://en.cppreference.com/w/cpp/language/operator_alternative

[78] *** - https://en.cppreference.com/w/cpp/algorithm

[79] *** - https://www.ejoi2019.si/

[80] *** - https://en.wikipedia.org/wiki/Algorithm

[81] *** - https://en.wikipedia.org/wiki/List_of_algorithms

[82] *** - https://en.wikipedia.org/wiki/List_of_data_structures


Lista autorilor
problemelor ³i indicaµiilor

Adi, 1

330

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