Sunteți pe pagina 1din 519

ALGORITMI S I STRUCTURI DE DATE Note de curs

(uz intern - draft v2.3)

Prefat a
C and dorim s a reprezent am obiectele din lumea real a ntr-un program pe calculator, trebuie s a avem n vedere: modelarea obiectelor din lumea real a sub forma unor entit a ti matematice abstracte si tipuri de date, operat iile pentru nregistrarea, accesul si utilizarea acestor entit a ti, reprezentarea acestor entit a ti n memoria calculatorului, si algoritmii pentru efectuarea acestor operat ii. Primele dou a elemente sunt n esent a de natur a matematic a si se refer a la ce structuri de date si operat ii trebuie s a folosim, iar ultimile dou a elemente implic a faza de implementare si se refer a la cum s a realiz am structurile de date si operat iile. Algoritmica si structurile de date nu pot separate. De si algoritmica si programarea pot separate, noi nu vom face acest lucru, ci vom implementa algoritmii ntr-un limbaj de programare (Pascal, C/C++, Java). Din aceast a cauz a acest curs este si o init iere n algoritmic a si programare. Scopul cursului este subordonat scopului specializ arii (informatic a, n cazul nostru) care este s a preg ateasc a speciali sti competent i, cu nalt a calicare n domeniul informaticii, cadre didactice competente n acest domeniu (profesor de informatic a n gimnaziu si liceu), informaticieni n diverse domenii cu prol tehnic, economic, etc. ce pot ncepe lucrul imediat dup a absolvirea facult a tii.Dezideratul nal este deci competent a. Competent a ntr-un domeniu de activitate implic a experient a n rezolvarea problemelor din acel domeniu de activitate. At at competent a c at si experient a n rezolvarea problemelor se pot obt ine numai dac a permanent se ntreprind eforturi pentru nsu sirea de noi cuno stint e. De exemplu, orice informatician (programator sau profesor) care elaboreaz a programe pentru rezolvarea unor probleme diverse, trebuie s a aib a competent e conform schemei1 :
PROBLEMA (model fizic)

ALGORITMICA (model virtual)

PROGRAMARE

Gandire algoritmica

Experienta (rezolvarea de probleme)

Cursul de Algoritmi si structuri de date este util ( si chiar necesar) pentru formarea competent elor si abilit a tilor unui bun programator sau profesor de informatic a. Pentru a vedea care sunt aceste competent e si abilit a ti putem, de
1 M. Vlada; E-Learning si Software educat ional; Conferint a Nat ional a de Inv a ta m ant Virtual, Bucure sti, 2003

exemplu, s a citim Programa pentru informatic a - Concursul nat ional unic pentru ocuparea posturilor didactice declarate vacante n nv a ta m antul preuniversitar.2 Intr-un fel, primul semestru al cursului Algoritmi si structuri de date este echivalent cu ceea ce se pred a la informatic a n clasa a IX-a iar al doilea semestru cu clasa a X-a (specializarea: matematic a-informatic a, intensiv informatic a). Diferent a este dat a n primul r and de dicultatea problemelor abordate de c atre noi n cadrul acestui curs. Din aceast a cauz a vom avea n vedere si ce prevede Pograma solar a pentru clasa a IX-a, Prol real, Specializarea: Matematic a-informatic a, intensiv informatic a. De asemenea, merit a s a vedem ce p areri au cei care au terminat de cur and o facultate cu un prol de informatic a si care au un nceput de carier a reu sit. Vom nt elege de ce acest curs este orientat pe rezolvarea de probleme. Alegerea limbajului Java pentru prezentarea implement arilor algoritmilor a fost f acut a din c ateva considerente. Java veric a validitatea indicilor tablourilor (programele nu se pot termina printr-o violare de memorie sau eroare de sistem). Java realizeaz a gestiunea automat a a memoriei (recupereaz a automat memoria care nu mai este necesar a programului) ceea ce simplic a scrierea programelor si permite programatorului s a se concentreze asupra esent ei algoritmului. Exist a documentat ie pe internet. Compilatorul de Java este gratuit. Un program scris n Java poate executat pe orice calculator (indiferent de arhitectur a sau sistem de operare). Student ii nu sunt obligat i s a realizeze implement arile algoritmilor n Java; ei pot folosi Pascal sau C/C++. Algoritmii prezentat i n curs sunt descri si n limbaj natural sau n limbaj algoritmic iar implement arile sunt n limbajul de programare Java. Java este un limbaj orientat-obiect, dar noi vom utiliza foarte put in aceast a particularitate. Sunt prezentate toate elementele limbajului de programare Java necesare pentru acest curs dar ecesta nu este un curs de programare n Java. Cuno stint ele minimale acceptate la sf ar situl cursului rezult a din Legea nr. 288 din 24 iunie 2004 privind organizarea studiilor universitare si, de exemplu, din Ghidul calit a tii n nv a ta m antul superior3 . Aici se precizeaz a faptul c a diploma de licent a se acord a unui absolvent al programului de studii care: demonstreaz a acumulare de cuno stint e si capacitatea de a nt elege aspecte din domeniul de studii n care s-a format, poate folosi at at cuno stint ele acumulate precum si capacitatea lui de nt elegere a fenomenelor printr-o abordare profesional a n domeniul de activitate, a acumulat competent e necesare demonstr arii, argument arii si rezolv arii problemelor din domeniul de studii considerat, si-a dezvoltat deprinderi de nv a tare necesare procesului de educat ie continu a.

prin O.M:Ed.C. nr.5287/15.11.2004 Universit a tii din Bucure sti, 2004; Capitolul 4, Calitatea programelor de studii universitare, Prof.univ.dr. Gabriela M. Atanasiu - Universitatea Tehnic a Gh.Asachi din Ia si
3 Editura

2 Aprobat a

Cuprins
1 Not iuni fundamentale 1.1 Programe ciudate . . . . . . . . . . . . . 1.1.1 Un program ciudat n Pascal . . 1.1.2 Un program ciudat n C++ . . . 1.1.3 Un program ciudat n Java . . . 1.1.4 Structura unui program Java . . 1.2 Conversii ale datelor numerice . . . . . 1.2.1 Conversia din baza 10 n baza 2 . 1.2.2 Conversia din baza 2 n baza 10 . 1.2.3 Conversii ntre bazele 2 si 2r . . 2 Structuri de date 2.1 Date si structuri de date . . . . . . . . 2.1.1 Date . . . . . . . . . . . . . . . 2.1.2 Structuri de date . . . . . . . . 2.2 Structuri si tipuri de date abstracte . . 2.2.1 Structuri de date abstracte . . 2.2.2 Tipuri de date abstracte . . . . 2.3 Structuri de date elementare . . . . . 2.3.1 Liste . . . . . . . . . . . . . . . 2.3.2 Stive si cozi . . . . . . . . . . . 2.3.3 Grafuri . . . . . . . . . . . . . 2.3.4 Arbori binari . . . . . . . . . . 2.3.5 Heap-uri . . . . . . . . . . . . . 2.3.6 Structuri de mult imi disjuncte 3 Algoritmi 3.1 Etape n rezolvarea problemelor . 3.2 Algoritmi . . . . . . . . . . . . . 3.2.1 Ce este un algoritm? . . . 3.2.2 Propriet a tile algoritmilor 3.2.3 Tipuri de prelucr ari . . . v . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 1 1 1 2 3 4 5 5 6 6 7 7 7 9 10 10 10 11 11 12 13 14 15 16 17 17 18 18 20 20

3.3

3.4

3.5

Descrierea algoritmilor . . . . . . . . . . . . . . . 3.3.1 Limbaj natural . . . . . . . . . . . . . . . 3.3.2 Scheme logice . . . . . . . . . . . . . . . . 3.3.3 Pseudocod . . . . . . . . . . . . . . . . . Limbaj algoritmic . . . . . . . . . . . . . . . . . . 3.4.1 Declararea datelor . . . . . . . . . . . . . 3.4.2 Operat ii de intrare/ie sire . . . . . . . . . 3.4.3 Prelucr ari liniare . . . . . . . . . . . . . . 3.4.4 Prelucr ari alternative . . . . . . . . . . . 3.4.5 Prelucr ari repetitive . . . . . . . . . . . . 3.4.6 Subalgoritm . . . . . . . . . . . . . . . . . 3.4.7 Probleme rezolvate . . . . . . . . . . . . . 3.4.8 Probleme propuse . . . . . . . . . . . . . Instruct iuni corespondente limbajului algoritmic 3.5.1 Declararea datelor . . . . . . . . . . . . . 3.5.2 Operat ii de intrare/ie sire . . . . . . . . . 3.5.3 Prelucr ari liniare . . . . . . . . . . . . . . 3.5.4 Prelucr ari alternative . . . . . . . . . . . 3.5.5 Prelucr ari repetitive . . . . . . . . . . . . 3.5.6 Subprograme . . . . . . . . . . . . . . . . 3.5.7 Probleme rezolvate . . . . . . . . . . . . . 3.5.8 Probleme propuse . . . . . . . . . . . . .

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

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

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

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

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

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

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

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

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

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

20 21 22 22 23 23 23 24 24 25 26 27 30 32 32 34 35 35 35 36 37 52 55 55 57 57 58 58 60 61 62 62 62 62 63 64 66 67 67 69

4 Analiza complexit a tii algoritmilor 4.1 Scopul analizei complexit a tii . . . . . . . . . . . . . . . . 4.1.1 Complexitatea spat iu . . . . . . . . . . . . . . . 4.1.2 Complexitatea timp . . . . . . . . . . . . . . . . 4.2 Notat ia asimptotic a . . . . . . . . . . . . . . . . . . . . 4.2.1 Denire si propriet a ti . . . . . . . . . . . . . . . 4.2.2 Clase de complexitate . . . . . . . . . . . . . . . 4.2.3 Cazul mediu si cazul cel mai defavorabil . . . . . 4.2.4 Analiza asimptotic a a structurilor fundamentale 4.3 Exemple . . . . . . . . . . . . . . . . . . . . . . . . . . . 4.3.1 Calcularea maximului . . . . . . . . . . . . . . . 4.3.2 Sortarea prin select ia maximului . . . . . . . . . 4.3.3 Sortarea prin insert ie . . . . . . . . . . . . . . . . 4.3.4 Sortarea rapid a (quicksort) . . . . . . . . . . . . 4.3.5 Problema celebrit a tii . . . . . . . . . . . . . . . . 4.4 Probleme . . . . . . . . . . . . . . . . . . . . . . . . . . 4.4.1 Probleme rezolvate . . . . . . . . . . . . . . . . . 4.4.2 Probleme propuse . . . . . . . . . . . . . . . . .

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

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

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

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

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

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

5 Recursivitate 5.1 Funct ii recursive . . . . . . . 5.1.1 Funct ii numerice . . . 5.1.2 Funct ia lui Ackerman 5.1.3 Recursii imbricate . . 5.2 Proceduri recursive . . . . . .

. . . . .

. . . . .

. . . . .

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

71 71 71 74 74 75 77 77 78 78 80 80 81 82 84 87 93 93 93 94 95 95 95 96 97 97 98 98 98 100 100 100 101 101 102 104 104 105 106 107 107 107

6 Analiza algoritmilor recursivi 6.1 Relat ii de recurent a . . . . . . . . 6.1.1 Ecuat ia caracteristic a . . . 6.1.2 Solut ia general a . . . . . . 6.2 Ecuat ii recurente neomogene . . . 6.2.1 O form a simpl a . . . . . . . 6.2.2 O form a mai general a . . . 6.2.3 Teorema master . . . . . . 6.2.4 Transformarea recurent elor 6.3 Probleme rezolvate . . . . . . . . .

7 Algoritmi elementari 7.1 Operat ii cu numere . . . . . . . . . . . . . . . . 7.1.1 Minim si maxim . . . . . . . . . . . . . 7.1.2 Divizori . . . . . . . . . . . . . . . . . . 7.1.3 Numere prime . . . . . . . . . . . . . . 7.2 Algoritmul lui Euclid . . . . . . . . . . . . . . . 7.2.1 Algoritmul clasic . . . . . . . . . . . . . 7.2.2 Algoritmul lui Euclid extins . . . . . . . 7.3 Operat ii cu polinoame . . . . . . . . . . . . . . 7.3.1 Adunarea a dou a polinoame . . . . . . . 7.3.2 Inmult irea a dou a polinoame . . . . . . 7.3.3 Calculul valorii unui polinom . . . . . . 7.3.4 Calculul derivatelor unui polinom . . . . 7.4 Operat ii cu mult imi . . . . . . . . . . . . . . . 7.4.1 Apartenent a la mult ime . . . . . . . . . 7.4.2 Diferent a a dou a mult imi . . . . . . . . 7.4.3 Reuniunea si intersect ia a dou a mult imi 7.4.4 Produsul cartezian a dou a mult imi . . . 7.4.5 Generarea submult imilor unei mult imi . 7.5 Operat ii cu numere ntregi mari . . . . . . . . . 7.5.1 Adunarea si sc aderea . . . . . . . . . . . 7.5.2 Inmult irea si mp artirea . . . . . . . . . 7.5.3 Puterea . . . . . . . . . . . . . . . . . . 7.6 Operat ii cu matrice . . . . . . . . . . . . . . . . 7.6.1 Inmult irea . . . . . . . . . . . . . . . . . 7.6.2 Inversa unei matrice . . . . . . . . . . .

8 Algoritmi combinatoriali 8.1 Principiul includerii si al excluderii si aplicat ii 8.1.1 Principiul includerii si al excluderii . . 8.1.2 Num arul funct iilor surjective . . . . . 8.1.3 Num arul permut arilor f ar a puncte xe 8.2 Principiul cutiei lui Dirichlet si aplicat ii . . . 8.2.1 Problema subsecvent ei . . . . . . . . . 8.2.2 Problema sub sirurilor strict monotone 8.3 Numere remarcabile . . . . . . . . . . . . . . 8.3.1 Numerele lui Fibonacci . . . . . . . . 8.3.2 Numerele lui Catalan . . . . . . . . . 8.4 Descompunerea n factori primi . . . . . . . . 8.4.1 Funct ia lui Euler . . . . . . . . . . . . 8.4.2 Num arul divizorilor . . . . . . . . . . 8.4.3 Suma divizorilor . . . . . . . . . . . . 8.5 Partit ia numerelor . . . . . . . . . . . . . . . 8.5.1 Partit ia lui n n exact k termeni . . . 8.5.2 Partit ia lui n n cel mult k termeni . . 8.5.3 Partit ii multiplicative . . . . . . . . . 8.6 Partit ia mult imilor . . . . . . . . . . . . . . . 8.7 Probleme rezolvate . . . . . . . . . . . . . . . 9 Algoritmi de c autare 9.1 Problema c aut arii . . 9.2 C autarea secvent ial a 9.3 C autare binar a . . . 9.4 Inserare n tabel a . . 9.5 Dispersia . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .

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

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

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

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

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

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

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

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

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

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

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

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

109 109 109 110 112 113 113 114 114 115 116 119 119 121 121 122 122 123 123 123 124 127 127 127 129 130 131 133 133 134 139 139 141 142 143 145 145 151 155 157 159

10 Algoritmi elementari de sortare 10.1 Introducere . . . . . . . . . . . . . . . . . . . 10.2 Sortare prin select ie . . . . . . . . . . . . . . 10.3 Sortare prin insert ie . . . . . . . . . . . . . . 10.3.1 Insert ie direct a . . . . . . . . . . . . . 10.3.2 Insert ie binar a . . . . . . . . . . . . . 10.4 Sortare prin interschimbare . . . . . . . . . . 10.5 Sortare prin mic sorarea incrementului - shell 11 Liste 11.1 Liste liniare . . . . . . . . . . . 11.2 Cozi . . . . . . . . . . . . . . . 11.3 Stive . . . . . . . . . . . . . . . 11.4 Evaluarea expresiilor aritmetice 11.5 Operat ii asupra listelor . . . . . . . . . . . . . . . . . . . . . . . prexate . . . . . . . . . . . . . . . .

12 Algoritmi divide et impera 12.1 Tehnica divide et impera . . . . . . . . . . . 12.2 Ordinul de complexitate . . . . . . . . . . . 12.3 Exemple . . . . . . . . . . . . . . . . . . . . 12.3.1 Sortare prin partitionare - quicksort 12.3.2 Sortare prin interclasare - MergeSort 12.3.3 Placa cu g auri . . . . . . . . . . . . 12.3.4 Turnurile din Hanoi . . . . . . . . . 12.3.5 Injum at a tire repetat a . . . . . . . .

. . . . . . . .

. . . . . . . .

. . . . . . . .

. . . . . . . .

. . . . . . . .

. . . . . . . .

. . . . . . . .

. . . . . . . .

. . . . . . . .

. . . . . . . .

. . . . . . . .

. . . . . . . .

. . . . . . . .

163 163 164 165 165 166 168 169 173 177 177 180 180 185 191 197 204 208 213 213 214 215 215 216 217 217 217 218 219 219 220 220 220 228 230 241 246 250 255 260

13 Algoritmi BFS-Lee 13.1 Prezentare general a. . . . . . . . . . . . . . . . 13.2 Probleme rezolvate . . . . . . . . . . . . . . . . 13.2.1 Romeo si Julieta - OJI2004 clasa a X-a 13.2.2 Sudest - OJI2006 clasa a X-a . . . . . . 13.2.3 Muzeu - ONI2003 clasa a X-a . . . . . . 13.2.4 P aianjen ONI2005 clasa a X-a . . . . . 13.2.5 Algoritmul Edmonds-Karp . . . . . . . 13.2.6 Cuplaj maxim . . . . . . . . . . . . . . 14 Metoda optimului local - greedy 14.1 Metoda greedy . . . . . . . . . . . . . . . . . 14.2 Algoritmi greedy . . . . . . . . . . . . . . . . 14.3 Exemple . . . . . . . . . . . . . . . . . . . . . 14.3.1 Problema continu a a rucsacului . . . . 14.3.2 Problema plas arii textelor pe o band a 14.3.3 Problema plas arii textelor pe m benzi 14.3.4 Maximizarea unei sume de produse . . 14.3.5 Problema stat iilor . . . . . . . . . . . 14.3.6 Problema cutiilor . . . . . . . . . . . . 14.3.7 Problema sub sirurilor . . . . . . . . . 14.3.8 Problema intervalelor disjuncte . . . . 14.3.9 Problema alegerii taxelor . . . . . . . 14.3.10 Problema acoperirii intervalelor . . . . 14.3.11 Algoritmul lui Prim . . . . . . . . . . 14.3.12 Algoritmul lui Kruskal . . . . . . . . . 14.3.13 Algoritmul lui Dijkstra . . . . . . . . . 14.3.14 Urgent a - OJI2002 cls 11 . . . . . . . 14.3.15 Reactivi - OJI2004 cls 9 . . . . . . . . 14.3.16 Pal - ONI2005 cls 9 . . . . . . . . . . 14.3.17 S ant - ONI2006 cls 9 . . . . . . . . . . 14.3.18 Cezar - OJI2007 cls 11 . . . . . . . . .

. . . . . . . .

. . . . . . . .

. . . . . . . .

. . . . . . . .

. . . . . . . .

. . . . . . . .

. . . . . . . .

. . . . . . . .

. . . . . . . .

. . . . . . . .

. . . . . . . .

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

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

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

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

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

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

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

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

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

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

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

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

15 Metoda backtracking 15.1 Generarea produsului cartezian . . . . . . . . . . . 15.1.1 Generarea iterativ a a produsului cartezian . 15.1.2 Generarea recursiv a a produsului cartezian 15.2 Metoda bactracking . . . . . . . . . . . . . . . . . 15.2.1 Bactracking iterativ . . . . . . . . . . . . . 15.2.2 Backtracking recursiv . . . . . . . . . . . . 15.3 Probleme rezolvate . . . . . . . . . . . . . . . . . . 15.3.1 Generarea aranjamentelor . . . . . . . . . . 15.3.2 Generarea combin arilor . . . . . . . . . . . 15.3.3 Problema reginelor pe tabla de sah . . . . . 15.3.4 Turneul calului pe tabla de sah . . . . . . . 15.3.5 Problema color arii h art ilor . . . . . . . . . 15.3.6 Problema vecinilor . . . . . . . . . . . . . . 15.3.7 Problema labirintului . . . . . . . . . . . . 15.3.8 Generarea partit iilor unui num ar natural . 15.3.9 Problema parantezelor . . . . . . . . . . . . 15.3.10 Algoritmul DFS de parcurgere a grafurilor . 15.3.11 Determinarea componentelor conexe . . . . 15.3.12 Determinarea componentelor tare conexe . 15.3.13 Sortare topologic a . . . . . . . . . . . . . . 15.3.14 Determinarea nodurilor de separare . . . . 15.3.15 Determinarea muchiilor de rupere . . . . . 15.3.16 Determinarea componentelor biconexe . . . 15.3.17 Triangulat ii - OJI2002 clasa a X-a . . . . . 15.3.18 Partit ie - ONI2003 clasa a X-a . . . . . . . 15.3.19 Scut a - ONI2003 clasa a X-a . . . . . . . . 16 Programare dinamic a 16.1 Prezentare general a. . . . . . . . . . . . . . . 16.2 Probleme rezolvate . . . . . . . . . . . . . . . 16.2.1 Inmult irea optimal a a matricelor . . . 16.2.2 Sub sir cresc ator maximal . . . . . . . 16.2.3 Sum a maxim a n triunghi de numere . 16.2.4 Sub sir comun maximal . . . . . . . . . 16.2.5 Distant a minim a de editare . . . . . . 16.2.6 Problema rucsacului (0 1) . . . . . . 16.2.7 Problema schimbului monetar . . . . . 16.2.8 Problema travers arii matricei . . . . . 16.2.9 Problema segment arii vergelei . . . . . 16.2.10 Triangularizarea poligoanelor convexe 16.2.11 Algoritmul Roy-Floyd-Warshall . . . . 16.2.12 Oracolul decide - ONI2001 cls 10 . . . 16.2.13 Pav ari - ONI2001 clasa a X-a . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .

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

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

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

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

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

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

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

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

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

269 269 269 274 277 279 279 280 280 284 294 296 298 301 303 306 310 311 313 314 316 320 321 323 326 330 336 343 343 345 345 348 352 353 360 366 367 368 370 373 374 375 381

xi 16.2.14 Balant a ONI2002 clasa a X-a . 16.2.15 Aliniere ONI2002 clasa a X-a . 16.2.16 Munte - ONI2003 cls 10 . . . . 16.2.17 L acusta - OJI2005 clasa a X-a 16.2.18 Avere ONI2005 cls 10 . . . . . 16.2.19 Suma - ONI2005 cls 10 . . . . 17 Potrivirea sirurilor 17.1 Un algoritm inecient . . . . . . . . 17.2 Un algoritm ecient - KMP . . . . . 17.3 Probleme rezolvate . . . . . . . . . . 17.3.1 Circular - Campion 2003-2004 17.3.2 Cifru - ONI2006 baraj . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 383 387 393 401 412 416 429 429 431 436 436 438 445 445 446 446 446 447 448 448 452 462 463 463 463 477 482 483 487

. . . . . . . . . . . . . . . . . . Runda 6 . . . . . .

18 Geometrie computat ional a 18.1 Determinarea orient arii . . . . . . . . . . . . . 18.2 Testarea convexit a tii poligoanelor . . . . . . . . 18.3 Aria poligoanelor convexe . . . . . . . . . . . . 18.4 Pozit ia unui punct fat a de un poligon convex . 18.5 Pozit ia unui punct fat a de un poligon concav . 18.6 Inf a sur atoarea convex a. . . . . . . . . . . . . . 18.6.1 Impachetarea Jarvis . . . . . . . . . . . 18.6.2 Scanarea Craham . . . . . . . . . . . . . 18.7 Dreptunghi minim de acoperire a punctelor . . 18.8 Cerc minim de acoperire a punctelor . . . . . . 18.9 Probleme rezolvate . . . . . . . . . . . . . . . . 18.9.1 Seceta - ONI2005 clasa a IX-a . . . . . 18.9.2 Antena - ONI2005 clasa a X-a . . . . . 18.9.3 Mo sia lui P acal a - OJI2004 clasa a XI-a 18.9.4 Partit ie - ONI2006 baraj . . . . . . . . . 18.9.5 Triunghi - ONI2007 cls 9 . . . . . . . .

19 Teoria jocurilor 493 19.1 Jocul NIM . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 493 19.1.1 Prezentare general a . . . . . . . . . . . . . . . . . . . . . . 493 19.1.2 Exemple . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 493 20 Alt i algoritmi 20.1 Secvent a de sum a maxim a . . . . . . . . . . . . . . . . . . . 20.1.1 Prezentare general a . . . . . . . . . . . . . . . . . . 20.1.2 Exemple . . . . . . . . . . . . . . . . . . . . . . . . . 20.2 Algoritmul Belmann-Ford . . . . . . . . . . . . . . . . . . . 20.2.1 Algoritmul Belmann-Ford pentru grafuri neorientate 20.2.2 Alg Belmann-Ford pentru grafuri orientate . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 495 495 495 495 495 495 498

xii 20.2.3 Alg Belmann-Ford pentru grafuri orientate aciclice . . . . . 501

Capitolul 1

Not iuni fundamentale


In general, student ii din anul I au cuno stint e de programare n Pascal sau C/C++. Noi vom prezenta implement arile algoritmilor n Java. Nu are prea mare important a dac a este Java, C/C++, Pascal sau alt limbaj de programare. Oricare ar limbajul de programare, trebuie s a stim n primul r and cum se reprezint a numerele n memoria calculatorului. Altfel putem avea surprize ciudate.

1.1

Programe ciudate

Dac a nu suntem atent i la valorile pe care le pot lua variabilele cu care lucr am, putem obt ine rezultate gre site chiar dac a modalitatea de rezolvare a problemei este corect a. Prezent am astfel de situat ii n Pascal, C/C++ si Java.

1.1.1

Un program ciudat n Pascal

Iat a un program Pascal n care dorim s a calcul am suma 20.000 + 30.000. var x,y,z:integer; BEGIN x:=20000; y:=30000; z:=x+y; write(x,+,y,=,z); END. De si ne a steptam s a apar a ca rezultat 50.000, surpriza este c a pe ecran apare 20000+30000=-15536 1

CAPITOLUL 1. NOT IUNI FUNDAMENTALE

Figura 1.1: Un program ciudat n Pascal

1.1.2

Un program ciudat n C++


#include<iostream.h> int main() { int x,y,z; x=20000; y=30000; z=x+y; cout << x <<"+"<<y<<"="<<z; return 0; }

Iat a un program n C++ n care dorim s a calcul am suma 20.000 + 30.000.

De si ne a steptam s a apar a ca rezultat 50.000, surpriza este c a pe ecran apare 20000+30000=-15536

Figura 1.2: Un program ciudat n C++

1.1. PROGRAME CIUDATE

1.1.3

Un program ciudat n Java

class Ciudat { public static void main(String args[]) { int x,y,z; x=200000; y=300000; z=x*y; System.out.println(x+"*"+y+"="+z); } }

Iat a un program n C++ n care dorim s a calcul am suma 200.000 300.000.

De si ne a steptam s a apar a ca rezultat 60.000.000.000, surpriza este c a pe ecran apare 200000*300000=-129542144

Figura 1.3: Un program ciudat n Java Calculul cu numerele ntregi este relativ simplu. Calculele sunt f acute ntr-o aritmetic a modulo N = 2n unde n este num arul de bit i ai cuv antului ma sin a. Exist a ma sini pe 16, 32 si 64 bit i pentru care N este aproximativ egal cu 6 104 , 4 109 si respectiv 2 1019 . Se pot reprezenta si numerele ntregi negative. Modul curent de reprezentare este n complement fat a de 2. In notat ie binar a, bitul cel mai semnicativ este bitul de semn. Numerele negative sunt cuprinse ntre 2n1 si 2n1 1. Atunci c and valorile obt inute din calcule dep a sesc marginile permise de tipul variabilelor implicate n respectivele calcule, se pot obt ine rezultate eronate.

CAPITOLUL 1. NOT IUNI FUNDAMENTALE

1.1.4

Structura unui program Java

Un program simplu n Java are urm atoarea structur a: class numeClasa { public static void main(String args[]) { // declar ari de variabile // instruct iuni } } Programul prezentat n sect iunea anterioar a se poate scrie sub forma: class Ciudat { public static void main(String args[]) { // declar ari de variabile int x,y,z; // instruct iuni x=200000; y=300000; z=x*y; System.out.println(x+*+y+=+z); } } Clasa este elementul de baz a n Java. Cel mai simplu program n Java este format dintr-o clas a (numele clasei este la latitudinea programatorului; singura recomandare este s a nceap a cu liter a mare) si funct ia main. In exemplul de mai sus sunt declarate trei variabile (x, y si z) de tip int (adic a de tip ntreg cu semn). Spat iul alocat variabilelor de tip int este de 4 octet i (32 bit i). Aceasta nseamn a c a o astfel de variabil a poate avea valori ntre 263 si 263 1. Valoarea maxim a este de aproximativ 2 miliarde. In programul anterior x are valoarea 200.000 iar y are valoarea 300.000, deci produsul are valoarea 60 miliarde care dep a se ste cu mult valoarea maxim a de 2 miliarde. In binar, 60 miliarde se scrie (folosint 36 bit i) sub forma 110111111000010001110101100000000000 dar sunt ret inut i numai 32 bit i din partea dreapt a, adic a 11111000010001110101100000000000 Primul bit reprezint a bitul de semn (1 reprezint a semnul - iar 0 reprezint a semnul +). Aceast a reprezentare trebuie g andit a ca ind o reprezentare n cod

1.2. CONVERSII ALE DATELOR NUMERICE

complementar (ea este n memoria calculatorului si toate numerele ntregi cu semn sunt reprezentate n acest cod). Reprezentarea n cod direct se obt ine din reprezentarea n cod complementar (mai precis, trec and prin reprezentarea n cod invers si adun and, n binar, 1): 11111000010001110101100000000000 (cod complementar) 10000111101110001010011111111111 (cod invers) 10000111101110001010100000000000 (cod direct) Din codul direct se obt ine -129542144 n baza 10. Aceasta este explicat ia acelui rezultat ciudat!

1.2

Conversii ale datelor numerice

1.2.1

Conversia din baza 10 n baza 2

Fie x = an ...a0 num arul scris n baza 10. Conversia n baza 2 a num arului x se efectueaz a dup a urm atoarele reguli: Se mparte num arul x la 2 iar restul va reprezenta cifra de ordin 0 a num arului scris n noua baz a (b0 ). C atul obt inut la mp art irea anterioar a se mparte la 2 si se obt ine cifra de ordin imediat superior a num arului scris n noua baz a. Secvent a de mp art iri se repet a p an a c and se ajunge la c atul 0. Restul de la a k -a mp art ire va reprezenta cifra bk1 . Restul de la ultima mp art ire reprezint a cifra de ordin maxim n reprezentarea num arului n baza 2. Metoda conduce la obt inerea rezultatului dup a un num ar nit de mp artiri, ntruc at n mod inevitabil se ajunge la un c at nul. In plus, toate resturile obt inute apart in mult imii {0, 1}. Exemplu. Fie x = 13 num arul n baza 10. Secvent a de mp art iri este: (1) se mparte 13 la 2 si se obt ine c atul 6 si restul 1 (deci b0 = 1) (2) se mparte 6 la 2 si se obt ine c atul 3 si restul 0 (deci b1 = 0) (3) se mparte 3 la 2 si se obt ine c atul 1 si restul 1 (deci b2 = 1) (4) se mparte 1 la 2 si se obt ine c atul 0 si restul 1 (deci b3 = 1). Prin urmare (13)10 = (1101)2 .

CAPITOLUL 1. NOT IUNI FUNDAMENTALE

1.2.2

Conversia din baza 2 n baza 10

Dac a y = bn ...b1 b0 este un num ar n baza 2 , atunci reprezentarea n baza 10 se obt ine efectu and calculul ( n baza 10): x = bn 2n + ... + b1 2 + b0 . Exemplu. Fie y = 1100. Atunci reprezentarea n baza 10 va x = 1 23 + 1 22 + 0 21 + 0 20 = 12.

1.2.3

Conversii ntre bazele 2 si 2r

Pentru conversia unui num ar din baza p n baza q se poate converti num arul din baza p n baza 10, iar acesta se converte ste n baza q . In cazul conversiei unui num ar din baza p = 2 n baza q = 2r se poate evita trecerea prin baza 10 proced andu-se n modul urm ator: se formeaz a grupuri de c ate r cifre pornind de la ultima cifr a din dreapta, nspre st anga. Fiecare grup de r cifre va convertit ntr-o cifr a a bazei q . Fie, spre exemplu: p = 2, q = 16 = p4 si x = (1011010)2 . Se obt in urm atoarele grupuri de c ate 4 cifre binare: (1010)2 = A16 si (0101)2 = 516 . Deci scrierea num arului x n baza 16 este: (5A)16 . Se observ a c a a fost completat a cu 0, spre st anga, cea mai din st anga grup a, p an a la formarea grupei complete de 4 cifre binare. In cazul conversiei unui num ar din baza p = 2r n baza q = 2 se poate de asemenea evita trecerea prin baza 10 proced andu-se n modul urm ator: ecare cifr a din reprezentarea n baza p = 2r se nlocuie ste cu r cifre binare care reprezint a scrierea respectivei cifre n baza 2. Fie, spre exemplu: p = 16 = 24 , q = 2 si x = (3A)16 . Se fac urm atoarele nlocuiri de cifre: 3 0011, A 1010. Deci scrierea num arului x n baza 2 este: (111010)2 . Se observ a c a nu apar cifrele 0 din st anga scrierii brute (00111010)2 obt inute prin nlocuiri.

Capitolul 2

Structuri de date
Inainte de a elabora un algoritm, trebuie s a ne g andim la modul n care reprezent am datele.

2.1

Date si structuri de date

2.1.1

Date

Datele sunt entit a ti purt atoare de informat ie. In informatic a, o dat a este un model de reprezentare a informat iei, accesibil unui anumit procesor (om, unitate central a, program), model cu care se poate opera pentru a obt ine noi informat ii despre fenomenele, procesele si obiectele lumii reale. In functie de modul lor de organizare, datele pot : elementare (simple) sau structurate. Datele elementare au caracter atomic, n sensul c a nu pot descompuse n alte date mai simple. Astfel de date sunt cele care iau ca valori numere sau siruri de caractere. O dat a elementar a apare ca o entitate indivizibil a at at din punct de vedere al informat iei pe care o reprezint a c at si din punct de vedere al procesorului care o prelucreaz a. O dat a elementar a poate privit a la nivel logic (la nivelul procesorului uman) sau la nivel zic (la nivelul calculatorului). Din punct de vedere logic, o dat a poate denit a ca un triplet de forma (identif icator, atribute, valori). Din punct de vedere zic, o dat a poate denit a ca o zon a de memorie de o anumit a lungime, situat a la o anumit a adres a absolut a, n care sunt memorate n timp si ntr-o form a specic a valorile datei. 7

CAPITOLUL 2. STRUCTURI DE DATE

Identicatorul este un simbol asociat datei pentru a o distinge de alte date si pentru a o putea referi n cadrul programului. Atributele sunt propriet a tii ale datei si precizeaz a modul n care aceasta va tratat a n cadrul procesului de prelucrare. Dintre atribute, cel mai important este atributul de tip care dene stete apartenent a datei la o anumit a clas a de date. O clas a de date este denit a de natura si domeniul valorilor datelor care fac parte din clasa respectiv a, de operat iile specice care se pot efectua asupra datelor si de modelul de reprezentare intern a a datelor. Astfel, exist a date de tip ntreg, de tip real, de tip logic, de tip sir de caractere, etc. O mult ime de date care au acelea si caracteristici se nume ste tip de date. Evident, un tip de date este o clas a de date cu acela si mod de interpretare logic a si reprezentare zic a si se caracterizeaz a prin valorile pe care le pot lua datele si prin operat iile care pot efectuate cu datele de tipul respectiv. De exemplu, tipul ntreg se caracterizeaz a prin faptul c a datele care i apart in pot lua doar valori ntregi, si asupra lor pot efectuate operat ii aritmetice clasice (adunare, sc adere, nmult ire, mp art ire n mult imea numerelor ntregi, comparat ii). Se poate considera c a datele organizate sub forma tablourilor unidimensionale formeaz a tipul vector iar datele organizate sub forma tablourilor bidimensionale formeaz a tipul matrice. In funct ie de natura elementelor care o compun, o structur a de date poate : omogen a, atunci c and toate elementele au acela si tip; neomogen a, atunci c and elementele componente au tipuri diferite. static a, atunci c and num arul de componente este xat;

In functie de num arul datelor care o compun, o structur a de date poate : dinamic a, atunci c and num arul de componente este variabil.

Din punct de vedere al modului n care sunt utilizate datele pot : Constante. Valoarea lor nu este si nu poate modicat a n cadrul algoritmului, ind xat a de la nceputul acestuia. O constant a este o dat a care p astreaz a aceea si valoare pe tot parcursul procesului de prelucrare. Pentru constantele care nu au nume, ns a si valoarea lor este cea prin care se identic a. Constante care au nume (identicator) sunt init ializate cu o valoare n momentul declar arii. Variabile. Valoarea lor poate modicat a n cadrul algoritmului. In momentrul declar arii lor, variabilele pot init ializate (li se atribuie o valoare) sau pot neinit ializate (nu li se atribuie nici o valoare). O variabil a este o dat a care nu p astreaz a neap arat aceea si valoare pe parcursul procesului de prelucrare. Tipul unei date trebuie s a e precizat, n cadrul programului de prelucrare, printr-o declarat ie de tip ce precede utilizarea respectivei constante sau variabile. Valorile datei pot numere, sau valori de adev ar, sau siruri de caractere, etc.

2.1. DATE S I STRUCTURI DE DATE

2.1.2

Structuri de date

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

poate reprezentat a ca o dat a structurat a (tablou bidimensional) specic nd ecare element prin doi indici (de linie si de coloan a). O structur a de date este deci o colect ie de date, eventual de tipuri diferite, pe care s-a denit o anumit a organizare si c areia i este specic un anumit mod de identicare a elementelor componente. Componetele unei structuri de date pot identicate prin nume sau prin ordinea pe care o ocup a n cadrul structurii. Dac a accesul la o anumit a component a a structurii de date se poate face f ar a s a tinem seama de celelalte componente, vom spune c a structura de date este cu acces direct. In schimb, dac a accesul la o component a a structurii de date se poate face numai tin and cont de alte c ampuri ale structurii ( n conformitate cu ordinea structurii, printr-un proces de traversare) atunci vom spune c a structura este cu acces secvent ial. Structurile de date pot create pentru a depozitate n memoria intern a (aceste structuri de date se numesc structuri interne) sau n memoria extern a (se numesc structuri externe, sau siere). Structurile interne au un caracter de date temporare (ele dispar odat a cu ncetarea activit a tii de prelucrare) iar cele externe au un caracter de date permanente (mai bine spus, de lung a durat a). Dac a pe l ang a componentele structurii se nregistreaz a pe suport si alte date suplimentare care s a materializeze relat ia de ordonare, atunci structura de date respectiv a este explicit a, n caz contrar este implicit a. De exemplu, structura de date de tip tablou este o structur a implicit a de date iar structura de date de tip list a liniar a este o structur a explicit a de date. Asupra structurilor de date se pot efectua operat ii care se refer a structura respectiv a sau la valorile datelor componente. Cele mai importante operat ii sunt: operat ia de creare, care const a n memorarea pe suportul de memorie a structurii de date n forma sa init ial a,

10

CAPITOLUL 2. STRUCTURI DE DATE

operat ia de consultare, care const a n accesul la elementele structurii n vederea prelucr arii valorilor acestora, si operat ia de actualizare, care const a n ad augarea 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 si pe care s-au denit acelea si operat ii, poart a numele de tip de structur a de date. Dac a analiz am ns a operat iile care se efectueaz a asupra unei structuri de date, vom putea vedea c a toate acestea se reduc la executarea, eventual repetat a, a unui grup de operat ii specice numite operat ii de baz a.

2.2

Structuri si tipuri de date abstracte

2.2.1

Structuri de date abstracte

Abstractizarea datelor reprezint a de fapt concentrarea asupra esent ialului, ignor and detaliile (sau altfel spus, conteaz a ce nu cum). St ap anirea aplicat iilor complexe se obt ine prin descompunerea n module. Un modul trebuie s a e simplu, cu complexitatea ascuns a n interiorul lui, si s a aib a o interfat a simpl a care s a permit a folosirea lui f ar a a cunoa ste implementarea. O structur a de date abstract a este un modul const and din date si operat ii. Datele sunt ascunse n interiorul modulului si pot accesate prin intermediul operat iilor. Structura de date este abstract a deoarece este cunoscut a numai interfat a structurii, nu si implementarea (operat iile sunt date explicit, valorile sunt denite implicit, prin intermediul operat iilor).

2.2.2

Tipuri de date abstracte

Procesul de abstractizare se refer a la dou a aspecte: abstractizarea procedural a, care separ a propriet a tile logice ale unei act iuni de detaliile implement arii acesteia abstractizarea datelor, care separ a propriet a tile logice ale datelor de detaliile reprezent arii lor O structur a de date abstracte are un singur exemplar (o singur a instant a ). Pentru a crea mai multe exemplare ale structurii de date abstracte se dene ste un tip de date abstract. In Java, de exemplu, clasa asigur a un mod direct de denire a oric arui tip de date abstract.

2.3. STRUCTURI DE DATE ELEMENTARE

11

2.3

Structuri de date elementare

2.3.1

Liste

O list a este o colect ie de elemente de informat ie (noduri) aranjate ntr-o anumit a ordine. Lungimea unei liste este num arul de noduri din list a. Structura corespunzatoare de date trebuie s a ne permit a s a determin am ecient care este primul/ultimul nod n structur a si care este predecesorul/succesorul unui nod dat (dac a exist a). Iat a cum arat a cea mai simpl a list a, lista liniar a:
capul listei coada listei

Figura 2.1: List a liniar a O list a circular a este o list a n care, dup a ultimul nod, urmeaz a primul nod, deci ecare nod are succesor si predecesor. C ateva dintre operat iile care se efectueaz a asupra listelor sunt: inserarea (ad augarea) unui nod, extragerea ( stergerea) unui nod, concatenarea unor liste, num ararea elementelor unei liste etc. Implementarea unei liste se realizeaz a n dou a moduri: secvent ial si n ant uit. Implementarea secvential a se caracterizeaz a prin plasarea nodurilor n locat ii succesive de memorie, n conformitate cu ordinea lor n list a. Avantajele acestui mod de implementare sunt accesul rapid la predecesorul/succesorul unui nod si g asirea rapid a a primului/ultimului nod. Dezavantajele sunt modalit a tile relativ complicate de inserarea/ stergere a unui nod si faptul c a, n general, nu se folose ste ntreaga memorie alocat a listei. Implementarea nl ant uit a se caracterizeaz a prin faptul c a ecare nod cont ine dou a p art i: informat ia propriu-zis a si adresa nodului succesor. Alocarea memoriei pentru ecare nod se poate face n mod dinamic, n timpul rul arii programului. Accesul la un nod necesit a parcurgerea tuturor predecesorilor s ai, ceea ce conduce la un consum mai mare de timp pentru aceast a operat ie. In schimb, operat iile de inserare/ stergere sunt foarte rapide. Se consum a exact at at spat iu de memorie c at este necesar dar, evident, apare un consum suplimentar de memorie pentru nregistrarea leg aturii c atre nodul succesor. Se pot folosi dou a adrese n loc de una, astfel nc at un nod s a cont in a pe lang a adresa nodului succesor si adresa nodului predecesor. Obt inem astfel o list a dublu inl ant uit a, care poate traversat a n ambele direct ii. Listele nl ant uite pot reprezentate prin tablouri. In acest caz, adresele nodurilor sunt de fapt indici ai tabloului.

12

CAPITOLUL 2. STRUCTURI DE DATE

O alternativ a este s a folosim dou a tablouri val si next astfel: s a memor am informat ia ecarui nod i n locat ia val[i], iar adresa nodului s au succesor n locat ia next[i]. Indicele locat iei primului nod este memorat n variabila p. Vom conveni ca, pentru cazul listei vide, s a avem p = 0 si next[u] = 0 unde u reprezint a ultimul nod din list a. Atunci, val[p] va cont ine informat ia primului nod al listei, next[p] adresa celui de-al doilea nod, val[next[p]] informat ia din al doilea nod, next[next[p]] adresa celui de-al treilea nod, etc. Acest mod de reprezentare este simplu dar apare problema gestion arii locat iilor libere. O solut ie este s a reprezent am locat iile libere tot sub forma unei liste nlant uite. Atunci, stergerea unui nod din lista init ial a implic a inserarea sa n lista cu locat ii libere, iar inserarea unui nod n lista init ial a implic a stergerea sa din lista cu locat ii libere. Pentru implementarea listei de locat ii libere, putem folosi acelea si tablouri dar avem nevoie de o alt a variabil a, f reehead, care s a cont in a indicele primei locat ii libere din val si next. Folosim acelea si convent ii: dac a f reehead = 0 nseamn a c a nu mai avem locat ii libere, iar next[ul] = 0 unde ul reprezint a ultima locat ie liber a. Vom descrie in continuare dou a tipuri de liste particulare foarte des folosite.

2.3.2

Stive si cozi

O stiv a este o list a liniar a cu proprietatea c a operat iile de inserare/extragere a nodurilor se fac n/din coada listei. Dac a nodurile A, B, C sunt inserate ntr-o stiv a n aceast a ordine, atunci primul nod care poate sters/extras este C. In mod echivalent, spunem c a ultimul nod inserat este singurul care poate sters/extras. Din acest motiv, stivele se mai numesc si liste LIFO (Last In First Out). Cel mai natural mod de reprezentare pentru o stiv a este implementarea secvent ial a ntr-un tablou S [1..n], unde n este num arul 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 a care cont ine adresa (indicele) ultimului nod inserat. Init ial, c and stiva este vid a, avem (prin convent ie) top = 0. O coad a este o list a liniar a n care inser arile se fac doar n capul listei, iar stergerile/extragerile se fac doar din coada listei. Din acest motiv, cozile se mai numesc si liste FIFO (First In First Out). O reprezentare secvent ial a pentru o coad a se obt ine prin utilizarea unui tablou C [0..n 1], pe care l trat am ca si cum ar circular: dup a locat ia C [n 1] urmeaz a locat ia C [0]. Fie tail variabila care cont ine indicele locat iei predecesoare primei locat ii ocupate si e head variabila care cont ine indicele locat iei ocupate ultima oar a. Variabilele head si tail au aceea si valoare atunci si numai atunci c and coada este vid a. Init ial, avem head = tail = 0. Trebuie s a observ am faptul c a testul de coad a vid a este acela si cu testul de coad a plin a. Dac a am folosi toate cele n locat ii la un moment dat, atunci nu am putea distinge ntre situat ia de coad a plin a si cea de coad a vid a, deoarece n ambele situat ii am avea head = tail. In consecint a, vom folosi efectiv, n orice moment, cel mult n 1 locat ii din cele n ale tabloului C .

2.3. STRUCTURI DE DATE ELEMENTARE

13

2.3.3

Grafuri

Un graf este o pereche G =< V, M >, unde V este o mult ime de v arfuri, iar M V V este o mult ime de muchii. O muchie de la v arful a la v arful b este notat a cu perechea ordonat a (a, b), dac a graful este orientat, si cu multimea {a, b}, dac a graful este neorientat. Dou a v arfuri unite printr-o muchie se numesc adiacente. Un v arf care este extremitatea unei singure muchii se nume ste v arf terminal. Un drum este o succesiune de muchii de forma (a1 , a2 ), (a2 , a3 ), ..., (an1 , an ) sau de forma {a1 , a2 }, {a2 , a3 }, ..., {an1 , an } dup a cum graful este orientat sau neorientat. Lungimea drumului este egal a cu num arul muchiilor care l constituie. Un drum simplu este un drum n care nici un v arf nu se repet a. Un ciclu este un drum care este simplu, cu except ia primului si ultimului v arf, care coincid. Un graf aciclic este un graf f ar a cicluri. Un graf neorientat este conex, dac a ntre oricare dou a v arfuri exist a un drum. Pentru grafuri orientate, aceast a notiune este nt arit a: un graf orientat este tare conex, dac a ntre oricare dou a v arfuri i si j exist a un drum de la i la j si un drum de la j la i. V arfurilor unui graf li se pot ata sa informat ii (numite valori), iar muchiilor li se pot ata sa informat ii numite uneori lungimi sau costuri. Exist a cel put in trei moduri de reprezentare ale unui graf: Printr-o matrice de adiacent a A, n care A[i, j ] = true dac a v arfurile i si j sunt adiacente, iar A[i, j ] = f alse n caz contrar. O alt a variant a este s a-i d am lui A[i, j ] valoarea lungimii muchiei dintre v arfurile i si j , considerand A[i, j ] = + atunci c and cele dou a v arfuri nu sunt adiacente. Cu aceast a reprezentare, putem verica u sor dac a dou a v arfuri sunt adiacente. Pe de alt a parte, dac a dorim s a a am toate v arfurile adiacente unui v arf dat, trebuie s a analiz am o ntreag a linie din matrice. Aceasta necesit a n operat ii (unde n este num arul de v arfuri n graf), independent de num arul de muchii care conecteaz a v arful respectiv. Prin liste de adiacent a , adic a prin ata sarea la ecare v arf i a listei de v arfuri adiacente (pentru grafuri orientate, este necesar ca muchia s a plece din i). Intrun graf cu m muchii, suma lungimilor listelor de adiacent a este 2m, dac a graful este neorientat, respectiv m, dac a graful este orientat. Dac a num arul muchiilor n graf este mic, aceast a reprezentare este preferabil a din punct de vedere al memoriei necesare. Totu si, pentru a determina dac a dou a v arfuri i si j sunt adiacente, trebuie s a analiz am lista de adiacent a a lui i ( si, posibil, lista de adiacent a a lui j ), ceea ce este mai put in ecient dec at consultarea unei valori logice n matricea de adiacent a . Printr-o list a de muchii. Aceast a reprezentare este ecient a atunci c and avem de examinat toate muchiile grafului.

14

CAPITOLUL 2. STRUCTURI DE DATE

2.3.4

Arbori binari

Un arbore este un graf neorientat, aciclic si conex. Sau, echivalent, un arbore este un graf neorientat n care exist a exact un drum ntre oricare dou a v arfuri. Un arbore reprezentat pe niveluri se nume ste arbore cu r ad acin a. V arful plasat pe nivelul 0 se nume ste r ad acina arborelui. Pe ecare nivel i > 0 sunt plasate v arfurile pentru care lungimea drumurilor care le leag a de r ad acin a este i. V arfurile de pe un nivel i > 0 legate de acela si v arf j de pe nivelul i 1 se numesc descendent ii direct i (ii) v arfului j iar v arful j se nume ste ascendent direct (tat a) al acestor v arfuri. Dac a exist a un drum de la un v arf i de pe nivelul ni la un v arf j de pe nivelul nj > ni, atunci v arful i se nume ste ascendent al lui j , iar v arful j se nume ste descendent al lui i. Un v arf terminal (sau frunz a) este un v arf f ar a descendent i. V arfurile care nu sunt terminale se numesc neterminale. Un arbore n care orice v arf are cel mult doi descendent i se nume ste arbore binar. Intr-un arbore cu r ad acin a (reprezentat pe niveluri), ad ancimea unui v arf este lungimea drumului dintre r ad acin a si acest v arf iar n alt imea unui v arf este lungimea celui mai lung drum dintre acest v arf si un v arf terminal. In alt imea arborelui este n alt imea r ad acinii. Intr-un arbore binar, num arul maxim de v arfuri aate pe nivelul k este 2k . k+1 Un arbore binar de n alt ime k are cel mult 2 1 v arfuri, iar dac a are exact 2k+1 1 v arfuri, se nume ste arbore plin. Varfurile unui arbore plin se numeroteaza n ordinea nivelurilor. Pentru acela si nivel, numerotarea se face n arbore de la st anga la dreapta.
1
nivelul 0

nivelul 1

nivelul 2

10

11

12

13

14

15

nivelul 3

Figura 2.2: Arbore binar plin Un arbore binar cu n v arfuri si de n alt ime k este complet, dac a se obt ine din arborele binar plin de n alt ime k , prin eliminarea, dac a este cazul, a v arfurilor numerotate cu n + 1, n + 2, ..., 2k+1 1.

2.3. STRUCTURI DE DATE ELEMENTARE

15

Acest tip de arbore se poate reprezenta secvent ial folosind un tablou T , pun and v arfurile de ad ancime k , de la st anga la dreapta, n pozit iile T [2k ], T [2k +1], k+1 ..., T [2 1] (cu posibila except ie a ultimului nivel care poate incomplet).
1
nivelul 0

nivelul 1

nivelul 2

10

11

12

nivelul 3

Figura 2.3: Arbore binar complet

Tat al unui v arf reprezentat n T [i], i > 0, se a a n T [i/2]. Fiii unui v arf reprezentat n T [i] se a a, dac a exist a, n T [2i] si T [2i + 1].

2.3.5

Heap-uri

Un max-heap (heap=gramad a ordonat a, n traducere aproximativ a) este un arbore binar complet, cu urm atoarea proprietate: valoarea ecarui v arf este mai mare sau egal a cu valoarea ec arui u al s au. Un min-heap este un arbore binar complet n care valoarea ecarui v arf este mai mic a sau egal a cu valoarea ec arui u al s au.
11
nivelul 0

10

nivelul 1

nivelul 2

nivelul 3

Figura 2.4: Max-heap

16

CAPITOLUL 2. STRUCTURI DE DATE Acela si heap poate reprezentat secvent ial prin urm atorul tablou: 11 7 10 7 7 9 2 4 6 5 7 3

Caracteristica de baz a a acestei structuri de date este c a modicarea valorii unui v arf se face foarte ecient, p astr andu-se proprietatea de heap. De exemplu, ntr-un max-heap, dac a valoarea unui v arf cre ste, astfel nc at dep a se ste valoarea tat alui, este sucient s a schimb am ntre ele aceste dou a valori si s a continu am procedeul n mod ascendent, p an a c and proprietatea de heap este restabilit a. Dac a, dimpotriv a, valoarea v arfului scade, astfel nc at devine mai mic a dec at valoarea cel put in a unui u, este sucient s a schimb am intre ele valoarea modicat a cu cea mai mare valoare a iilor, apoi s a continu am procesul n mod descendent, p an a c and proprietatea de heap este restabilit a. Heap-ul este structura de date ideal a pentru extragerea maximului/minimului dintr-o mult ime, pentru inserarea unui v arf, pentru modicarea valorii unui v arf. Sunt exact operat iile de care avem nevoie pentru a implementa o list a dinamic a de priorit a ti: valoarea unui v arf va da prioritatea evenimentului corespunzator. Evenimentul cu prioritatea cea mai mare/mic a se va aa mereu la radacina heap-ului, iar prioritatea unui eveniment poate modicat a n mod dinamic.

2.3.6

Structuri de mult imi disjuncte

S a presupunem c a avem N elemente, numerotate de la 1 la N . Numerele care identic a elementele pot , de exemplu, indici intr-un tablou unde sunt memorate valorile elementelor. Fie o partitie a acestor N elemente, format a din submult imi dou a c ate dou a disjuncte: S 1, S 2, ... . Presupunem c a ne intereseaz a reuniunea a dou a submult imi, Si Sj . Deoarece submult imile sunt dou a c ate dou a disjuncte, putem alege ca etichet a pentru o submult ime oricare element al ei. Vom conveni ca elementul minim al unei mult imi s a e eticheta mult imii respective. Astfel, multimea {3, 5, 2, 8} va numit a multimea 2. Vom aloca tabloul set[1..N ], n care ec arei locatii set[i] i se atribuie eticheta submult imii care cont ine elementul i. Avem atunci proprietatea: set[i] i, pentru 1 i N . Reuniunea submult imilor etichetate cu a si b se poate realiza astfel: procedure reuniune(a, b) i a; jb if i > j then interschimb ai si j for k j to N do if set[k ] = j then set[k ] i

Capitolul 3

Algoritmi

3.1

Etape n rezolvarea problemelor


Principalele etape care se parcurg n rezolvarea unei probleme sunt: (a) Stabilirea datelor init iale si a obiectivului (ce trebuie determinat). (b) Alegerea metodei de rezolvare. (c) Aplicarea metodei pentru date concrete. Exemplu. S a presupunem c a problema este rezolvarea, n R, a ecuat iei x2 3x + 2 = 0. (a) Datele init iale sunt reprezentate de c atre coecient ii ecuat iei iar obiectivul este determinarea r ad acinilor reale ale ecuat iei. (b) Vom folosi metoda de rezolvare a ecuat iei de gradul al doilea av and forma general a ax2 + bx + c = 0. Aceast a metod a poate descris a astfel: Pasul 1. Se calculeaz a discriminantul: = b2 4ac. Pasul 2. Dac a>0 atunci ecuat ia are dou a r ad acini reale distincte: x1,2 = altfel, dac a=0 atunci ecuat ia are o r ad acina real a dubl a: x1,2 = altfel ecuat ia nu are r ad acini reale. (c) Aplicarea metodei pentru datele problemei (a = 1, b = 3, c = 2) conduce la rezultatul: x1 = 1, x2 = 2. 17
b 2a b 2a

18

CAPITOLUL 3. ALGORITMI

3.2

Algoritmi

3.2.1

Ce este un algoritm?

Un algoritm este o succesiune de operat ii aritmetice si/sau logice care, aplicate asupra unor date, permit obt inerea rezultatului unei probleme din clasa celor pentru care a fost conceput. S a observ am c a nu apare n denit ie cuv antul calculator; algoritmii nu au neap arat leg atur a cu calculatorul. Totu si, n acest curs ne vom concentra aproape exclusiv pe algoritmi care pot implementat i rezonabil pe calculator. Altfel spus, ecare pas din algoritm trebuie astfel g andit nc at ori este suportat direct de c atre limbajul de programare favorit (operat ii aritmetice, cicluri, recursivitate, etc) ori este asem an ator cu ceva nv a tat mai nainte (sortare, c autare binar a, parcurgere n ad ancime, etc). Secvent a de pa si prin care este descris a metoda de rezolvare a ecuat iei de gradul al doilea (prezentat a n sect iunea anterioar a) este un exemplu de algoritm. Calculul efectuat la Pasul 1 este un exemplu de operat ie aritmetic a, iar analiza semnului discriminantului (Pasul 2) este un exemplu de operat ie logic a. Descrierea unui algoritm presupune precizarea datelor init iale si descrierea prelucr arilor efectuate asupra acestora. Astfel, se poate spune c a: algoritm = date + prelucr ari Al-Khwarizmi a fost cel care a folosit pentru prima dat a reguli precise si clare pentru a descrie procese de calcul (operat ii aritmetice fundamentale) n lucrarea sa Scurt a carte despre calcul algebric. Mai t arziu, aceast a descriere apare sub denumirea de algoritm n Elementele lui Euclid. Algoritmul lui Euclid pentru calculul celui mai mare divizor comun a dou a numere naturale este, se pare, primul algoritm cunoscut n matematic a. In matematic a not iunea de algoritm a primit mai multe denit ii: algoritmul normal al lui A. A. Markov, algoritmul operat ional al lui A. A. Leapunov, ma sina Turing, funct ii recursive, sisteme POST. S-a demonstrat c a aceste denit ii sunt echivalente din punct de vedere matematic. In informatic a exist a de asemenea mai multe denit ii pentru not iunea de algoritm. De exemplu, n [35] not iunea de algoritm se dene ste astfel: Un algoritm este sistemul virtual A = (M, V, P, R, Di, De, M i, M e) constituit din urm atoarele elemente: M - memorie intern a format a din locat ii de memorie si utilizat a pentru stocarea temporar a a valorilor variabilelor;

3.2. ALGORITMI V - mult ime de variabile denite n conformitate cu rat ionamentul R, care utilizeaz a memoria M pentru stocarea valorilor din V ; P - proces de calcul reprezentat de o colect ie de instruct iuni/comenzi exprimate ntr-un limbaj de reprezentare (de exemplu, limbajul pseudocod); folosind memoria virtual aM si mult imea de variabile V , instruct iunile implementeaz a/codic a tehnicile si metodele care constituie rat ionamentul R; execut ia instruct iunilor procesului de calcul determin a o dinamic a a valorilor variabilelor; dup a execut ia tuturor instruct iunilor din P , solut ia problemei se a a n anumite locat ii de memorie corespunz atoare datelelor de ie sire De; R - rat ionament de rezolvare exprimat prin diverse tehnici si metode specice domeniului din care face parte clasa de probleme supuse rezolv arii (matematic a, zic a, chimie etc.), care mbinate cu tehnici de programare corespunz atoare realizeaz a act iuni/procese logice, utiliz and memoria virtual aM si mult imea de variabile V ; Di - date de intrare care reprezint a valori ale unor parametri care caracterizeaz a ipotezele de lucru/st arile init iale ale problemei si care sunt stocate n memoria M prin intermediul instruct iunilor de citire/intrare care utilizeaz a mediul de intrare M i; De - date de ie sire care reprezint a valori ale unor parametri care caracterizeaz a solut ia problemei/st arile nale; valorile datelor de ie sire sunt obt inute din valorile unor variabile generate de execut ia instruct iunilor din procesul de calcul P , sunt stocate n memoria M , si nregistrate pe un suport virtual prin intermediul instruct iunilor de scriere/ie sire care utilizeaz a mediul de ie sire M e; ; M i - mediu de intrare care este un dispozitiv virtual de intrare/citire pentru preluarea valorilor datelor de intrare si stocarea acestora n memoria virtual a M; M e - mediu de ie sire care este un dispozitiv virtual de ie sire/scriere pentru preluarea datelor din memoria virtual a M si nregistrarea acestora pe un suport virtual (ecran, h artie, disc magnetic, etc.).

19

Un limbaj este un mijloc de transmitere a informat iei. Exist a mai multe tipuri de limbaje: limbaje naturale (englez a, rom an a, etc), limbaje stiint 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 at s a poat a transmi si calculatorului cu scopul ca acesta s a efectueze operat iile specicate. Un program este un algoritm tradus ntr-un limbaj de programare.

20

CAPITOLUL 3. ALGORITMI

3.2.2

Propriet a tile algoritmilor

Principalele propriet a ti pe care trebuie s a le aib a un algoritm sunt: Generalitate. Un algoritm trebuie s a poat a utilizat pentru o clas a ntreag a de probleme, nu numai pentru o problem a particular a. Din aceast a cauz a, o metod a de rezolvare a unei ecuat ii particulare nu poate considerat a algoritm. Finitudine. Orice algoritm trebuie s a permit a obt inerea rezultatului dup a un num ar nit de prelucr ari (pa si). Din aceast a cauz a, o metod a care nu asigur a obt inerea rezultatului dup a un num ar nit de pa si nu poate considerat a algoritm. Determinism. Un algoritm trebuie s a prevad a, f ar a ambiguit a ti si f ar a neclarit a ti, modul de solut ionare a tuturor situat iilor care pot s a apar a n rezolvarea problemei. Dac a n cadrul algoritmului nu intervin elemente aleatoare, atunci ori de c ate ori se aplic a algoritmul aceluia si set de date de intrare trebuie s a se obt in a acela si rezultat.

3.2.3

Tipuri de prelucr ari

Prelucr arile care intervin ntr-un algoritm pot simple sau structurate. Prelucr arile simple sunt atribuiri de valori variabilelor, eventual prin evaluarea unor expresii; Prelucr arile structurate pot de unul dintre tipurile: Liniare. Sunt secvent e de prelucr ari simple sau structurate care sunt efectuate n ordinea n care sunt specicate; Alternative. Sunt prelucr ari caracterizate prin faptul c a n funct ie de realizarea sau nerealizarea unei condit ii se alege una din dou a sau mai multe variante de prelucrare; Repetitive. Sunt prelucr ari caracterizate prin faptul c a aceea si prelucrare (simpl a sau structurat a) este repetat a c at timp este ndeplinit a o anumit a condit ie.

3.3

Descrierea algoritmilor

Algoritmii nu sunt programe, deci ei nu trebuie specicat i ntr-un limbaj de programare. Detaliile sintactice, de exemplu din Pascal, C/C++ sau Java, nu au nici o important a n elaborarea/proiectarea algoritmilor. Pe de alt a parte, descrierea n limba rom an a (ca si n limba englez a [15]) n mod uzual nu este o idee mai bun a. Algoritmii au o serie de structuri - n special

3.3. DESCRIEREA ALGORITMILOR

21

condit ionale, repetitive, si recursivitatea - care sunt departe de a putea descrise prea u sor n limbaj natural. La fel ca orice limb a vorbit a, limba rom ana este plin a de ambiguit a ti, sub nt elesuri si nuant e de semnicat ie, iar algoritmii trebuie s a e descri si cu o acuratet e maxim posibil a. Cea mai bun a metod a de a descrie un algoritm este utilizarea limbajului pseudocod. Acesta folose ste structuri ale limbajelor de programare si matematicii pentru a descompune algoritmul n pa si elementari (propozit ii simple), dar care pot scrise folosind matematica, rom ana curat a, sau un amestec al celor dou a. Modul exact de structurare a pseudocodului este o alegere personal a. O descriere foarte bun a a algoritmului arat a structura intern a a acestuia, ascunde detaliile care nu sunt semnicative, si poate implementat a u sor de c atre orice programator competent n orice limbaj de programare, chiar dac a el nu nt elege ce face acel algoritm. Un pseudocod bun, la fel ca si un cod bun, face algoritmul mult mai u sor de nt eles si analizat; el permite de asemenea, mult mai u sor, descoperirea gre selilor. Pe de alt a parte, proba clar a se poate face numai pe baza unui program care s a dea rezultatele corecte! Oamenii sunt oameni! Cineva poate s a insiste c a algoritmul lui este bun de si ... nu este! S i atunci ... program am!

3.3.1

Limbaj natural

Exemple. 1. Algoritmul lui Euclid. Permite determinarea celui mai mare divizor comun (cmmdc) a dou a numere naturale a si b. Metoda de determinare a cmmdc poate descris a n limbaj natural dup a cum urmeaz a. Se mparte a la b si se ret ine restul r. Se consider a ca nou de mp art it vechiul mp art itor si ca nou mpart itor restul obt inut la mp art irea anterioar a. Operat ia de mp art ire continu a p an a se obt ine un rest nul. Ultimul rest nenul (care a fost si ultimul mp art itor) reprezint a rezultatul. Se observ a c a metoda descris a ndepline ste propriet a tile unui algoritm: poate aplicat a oric arei perechi de numere naturale iar num arul de prelucr ari este nit (dup a un num ar nit de mp art iri se ajunge la un rest nul). De asemenea se observ a c a prelucrarea principal a a algoritmului este una repetitiv a, condit ia utilizat a pentru a analiza dac a s-a terminat prelucrarea ind egalitatea cu zero a restului. 2. Schema lui Horner. Permite determinarea c atului si restului mp art irii unui polinom P [X ] = an X n + an1 X n1 + ... + a1 X + a0 = 0 la un binom de forma X b. O modalitate simpl a de a descrie metoda de rezolvare este schema urm atoare: 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]

22

CAPITOLUL 3. ALGORITMI

Valorile cn1 , cn2 , ..., c1 , c0 reprezint a coecient ii c atului, iar ultima valoare calculat a reprezint a valoarea restului (valoarea polinomului calculat a n b). S i n acest caz prelucrarea principal a este una repetitiv a const and n evaluarea expresiei bck + ak pentru k lu and, n aceast a ordine, valorile n 1, n 2, ..., 2, 1, 0.

3.3.2

Scheme logice

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

3.3.3

Pseudocod

Un limbaj algoritmic este o notat ie care permite exprimarea logicii algoritmilor ntr-un mod formalizat f ar a a necesare reguli de sintax a riguroase, ca n cazul limbajelor de programare. Un limbaj algoritmic mai este denumit si pseudocod. Un algoritm descris n pseudocod cont ine at at enunt uri care descriu operat ii ce pot traduse direct ntr-un limbaj de programare (unui enunt n limbaj algoritmic i corespunde o instruct iune n program) c at si enunt uri ce descriu prelucr ari ce urmeaz a a detaliate abia n momentul scrierii programului. Nu exist a un anumit standard n elaborarea limbajelor algoritmice, ecare programator put and s a conceap a propriul pseudocod, cu condit ia ca acesta s a permit a o descriere clar a si neambigu a a algoritmilor. Se poate folosi sintaxa limbajului de programare preferat, n care apar enunt uri de prelucr ari. De exemplu: for ecare v arf v din V { culoare[v] = alb; distanta[v] = innit; predecesor[v]=-1; }

3.4. LIMBAJ ALGORITMIC

23

3.4

Limbaj algoritmic
In continuare prezent am un exemplu de limbaj algoritmic.

3.4.1

Declararea datelor

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

3.4.2

Operat ii de intrare/ie sire

Preluarea valorilor pentru datele de intrare este descris a sub forma: read v1 , v2 , ...; unde v1 , v2 , ... sunt nume de variabile. A sarea rezultatelor este descris a sub forma: write e1 , e2 , ...;

24

CAPITOLUL 3. ALGORITMI

unde e1 , e2 , ... sunt expresii ( n particular pot constante sau variabile). Operat ia de atribuire. Operat ia de atribuire a unei valori c atre o variabil a se descrie prin: v = <expresie>; unde v este un nume de variabil a, <expresie> desemneaz a o expresie aritmetic a sau logic a, iar = este operatorul de atribuire. Pentru acesta din urm a pot folosite si alte simboluri, ca de exemplu := sau . Expresiile pot descrise conform regulilor utilizate n matematic a.

3.4.3

Prelucr ari liniare

O secvent a de prelucr ari se descrie n modul urm ator: <prel 1>; <prel 2>; ... <prel n>; sau <prel 1>; <prel 2>; ... <prel n>; O astfel de scriere indic a faptul c a n momentul execut iei prelucr arile se efectueaz a n ordinea n care sunt specicate.

3.4.4

Prelucr ari alternative

O prelucrare alternativ a complet a (cu dou a ramuri) este descris a prin: if <condit ie> sau sub forma if <condit ie> then <prel 1> else <prel 2>; unde <condit ie> este o expresie relat ional a. Aceast a prelucrare trebuie nt eleas a n modul urm ator: dac a condit ia este adev arat a atunci se efectueaz a prelucrarea <prel 1>, altfel se efectueaz a <prel 2>. O prelucrare alternativ a cu o singur a ramur a se descrie prin: if <condit ie> <prel>; sau if <condit ie> then <prel>; iar execut ia ei are urm atorul efect: dac a condit ia este satisfacut a atunci se efectueaz a prelucrarea specicat a, altfel nu se efectueaz a nici o prelucrare ci se trece la urm atoarea prelucrare a algoritmului. <prel 1> else <prel 2>;

3.4. LIMBAJ ALGORITMIC

25

3.4.5

Prelucr ari repetitive

Prelucr arile repetitive pot de trei tipuri: cu test init ial, cu test nal si cu contor. Prelucrarea repetitiv a cu test init ial se descrie prin: Prelucrarea repetitiv a cu test init ial se descrie prin: while <condit ie> <prel>; sau while <condit ie> do <prel>; In momentul execut iei, at at timp c at condit ia este adevarat a, se va executa instruct iunea. Dac a condit ia nu este la nceput satisf acut a, atunci instruct iunea nu se efectueaz a niciodat a. Prelucrarea repetitiv a cu test nal se descrie prin: do <prel> while <condit ie>; Prelucrarea se repet a p an a c and condit ia specicat a devine fals a. In acest caz prelucrarea se efectueaz a cel put in o dat a, chiar dac a condit ia nu este satisfacut a la nceput. Prelucrarea repetitiv a cu contor se caracterizeaz a prin repetarea prelucr arii de un num ar prestabilit de ori si este descris a prin: for i = i1 , i2 , ..., in <prel>; sau for i = i1 , i2 , ..., in do <prel>; unde i este variabila contor care ia, pe r and, valorile i1 , i2 , ..., in n aceast a ordine, prelucrarea ind efectuat a pentru ecare valoare a contorului. Alte forme utilizate sunt: for i = vi to vf do <prel>;

n care contorul ia valori consecutive cresc atoare ntre vi s i vf , si for i = vi downto vf do <prel>;

n care contorul ia valori consecutive descresc atoare ntre vi s i vf .

26

CAPITOLUL 3. ALGORITMI

3.4.6

Subalgoritm

In cadrul unui algoritm poate s a apar a necesitatea de a specica de mai multe ori si n diferite locuri un grup de prelucr ari. Pentru a nu le descrie n mod repetat ele pot constitui o unitate distinct a, identicabil a printr-un nume, care este numit a subalgoritm. Ori de c ate ori este necesar a efectuarea grupului de prelucr ari din cadrul subalgoritmului se specic a numele acestuia si, eventual, datele curente asupra c arora se vor efectua prelucrarile. Aceast a act iune se nume ste apel al subalgoritmului, iar datele specicate al aturi de numele acestuia si asupra c arora se efectueaz a prelucrarile se numesc parametri. In urma traducerii ntr-un limbaj de programare un subalgoritm devine un subprogram. Un subalgoritm poate descris n felul urm ator: <nume subalg> (<tip> <nume p1>, <tip> <nume p2>, ... ) { ... /* prelucr ari specice subalgoritmului */ ... return <nume rezultat>; } unde <nume subalg> reprezint a numele subalgoritmului iar nume p1, nume p2, ... reprezint a numele parametrilor. Ultimul enunt , prin care se returneaz a rezultatul calculat n cadrul subalgoritmului, este optional. Modul de apel depinde de modul n care subalgoritmul returneaz a rezultatele sale. Dac a subalgoritmul returneaz a efectiv un rezultat, printr-un enunt de forma return <nume rezultat>; atunci subalgoritmul se va apela n felul urm ator: v=<nume subalg>(nume p1, nume p2, ...); Ace sti subalgoritmi corespund subprogramelor de tip funct ie. Dac a n subalgoritm nu apare un astfel de enunt , atunci el se va apela prin: <nume subalg>(nume p1, nume p2, ...); variant a care corespunde subprogramelor de tip procedur a. Observat ie. Prelucr arile care nu sunt detaliate n cadrul algoritmului sunt descrise n limbaj natural sau limbaj matematic. Comentariile suplimentare vor cuprinse ntre /* si */. Dac a pe o linie a descrierii algoritmului apare simbolul // atunci tot ce urmeaz a dup a acest simbol, pe aceea si linie cu el, este interpretat ca ind un comentariu (deci, nu reprezint a o prelucrare a algoritmului).

3.4. LIMBAJ ALGORITMIC

27

3.4.7

Probleme rezolvate

1. Algoritmului lui Euclid. Descrierea n pseudocod a algoritmului lui Euclid este urm atoarea: 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 atoarea: 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 ar natural din baza 10 n baza 2. Fie n un num ar ntreg pozitiv. Pentru a determina cifrele reprezentarii n baza doi a acestui num ar se poate folosi urm atoarea metod a: Se mparte n la 2, iar restul va reprezenta cifra de rang 0. C atul obt inut la mpartirea anterioar a se mparte din nou la 2, iar restul obt inut va reprezenta cifra de ordin 1 s.a.m.d. Secvent a de mp art iri continu a p n a la obt inerea unui c at nul. Descrierea n pseudocod a acestui algoritm este: int n, d, c, r; read n; d = n; c = d / 2; /* c atul mp art irii ntregi a lui d la 2 */ r = d % 2; /* restul mp art irii ntregi a lui d la 2 */ write r; while (c != 0) { d = c; c = d / 2; /* c atul mp art irii ntregi a lui d la 2 */ r = d % 2; /* restul mp art irii ntregi a lui d la 2 */ write r; } 4. Conversia unui num ar ntreg din baza 2 n baza 10.

28

CAPITOLUL 3. ALGORITMI

Dac a bk bk1 ...b1 b0 reprezint a cifrele num arului n baza 2, atunci valoarea n baza 10 se obt ine efectu nd calculul: (bk bk1 ...b1 b0 )10 = bk 2k + bk1 2k1 + ... + b1 2 + b0 De si calculul de mai sus este similar cu evaluarea pentru X = 2 a polinomului P [X ] = bk X k + bk1 X k1 + ... + b1 X + b0 prelucrare pentru care ar putea folosit algoritmul corespunz ator schemei lui Horner, n continuare prezent am o alt a variant a de rezolvare a acestei probleme, care folose ste un subalgoritm pentru calculul puterilor unui num ar 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 a se scrie un algoritm pentru determinarea tuturor divizorilor naturali ai unui num ar ntreg. Rezolvare. Fie n num arul ai c arui divizori trebuie determinat i. Evident 1 si |n| sunt divizori ai lui n. Pentru a determina restul divizorilor este sucient ca ace stia s a e c autat i printre elementele mult imii {2, 3, ..., [|n|]} cu [x] desemn and partea ntreag a a lui x. Algoritmul poate descris n modul urm ator: int n, d; read n; write 1; /* a sarea primului divizor */ for d = 2, [|n|/2] if (d divide pe n) then write d; write |n| /* a sarea ultimului divizor */ 6. S a se scrie un algoritm pentru determinarea celui mai mare element dintrun sir de numere reale. Rezolvare. Fie x1 , x2 , ..., xn sirul analizat. Determinarea celui mai mare element const a n init ializarea unei variabile de lucru max (care va cont ine valoarea maximului) cu x1 si compararea acesteia cu ecare dintre celelalte elemente ale sirului. Dac a valoarea curent aa sirului, xk , este mai mare dec at valoarea variaabilei max atunci acesteia din urm a i se va da valoarea xk . Astfel, dup a a k1 comparat ie variabila max va cont ine valoarea maxim a din sub sirul x1 , x2 , ..., xk . Algoritmul poate descris n modul urm ator:

3.4. LIMBAJ ALGORITMIC int k, n; read n; double x[1..n], max; /* vectorul si variabila de lucru */ read x; /* preluarea elementelor sirului */ max = x[1]; for k = 2, n if (max < x[k]) then max = x[k]; write max; 7. S a se aproximeze, cu precizia , limita sirului
n

29

sn =
k=0

1 . k!

Rezolvare. Calculul aproximativ (cu precizia ) al limitei sirului sn const a n 1 , are proprietatea calculul sumei nite sk , unde ultimul termen al sumei, tk = k ! k a relat ie va folosit a pentru calculul valorii tk < . Intruc at tk+1 = kt +1 , aceast termenului curent (permit and mic sorarea num arului de calcule). double eps, t, s; int k; k=1; /* init ializare indice */ t=1; /* init ializare termen */ s=1; /* init ializare suma */ do { s=s+t; /* ad augarea termenului curent */ k=k+1; t=t/k; /* calculul urm atorului termen */ } while (t eps); s=s+t; (* ad augarea ultimului termen *) write s; 8. Fie A o matrice cu m linii si n coloane, iar B o matrice cu n linii si p coloane, ambele av and elemente reale. S a se determine matricea produs C = A B . Rezolvare. Matricea C va avea m linii si p coloane, iar ecare element se determin a efectu and suma:
n

ci,j =
k=1

ai,k bk,j ,

1 i m, 1 j p.

In felul acesta calculul elementelor matricei C se efectueaz a prin trei cicluri imbricate (unul pentru parcurgerea liniilor matricei C , unul pentru parcurgerea coloanelor matricei C , iar unul pentru efectuarea sumei specicate mai sus).

30

CAPITOLUL 3. ALGORITMI 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]; 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;

/* matrice */

3.4.8

Probleme propuse

1. Fie D o dreapt a de ecuat ie ax + by + c = 0 si (C) un cerc de centru O(x0 , y0 ) si raz a r. S a se stabileasc a pozit ia dreptei fat a de cerc. Indicat ie. Se calculeaz a distant a de la centrul cercului la dreapta D utiliz and formula: |ax0 + by0 + c| . d= a2 + b2 Dac a d r + atunci dreapta este exterioar a cercului, dac a d r atunci dreapta este secant a, iar dac a r < d < r + atunci este tangent a (la implementarea egalitatea ntre dou a numere reale ...). 2. S a se genereze primele n elemente ale sirurilor ak si bk date prin relat iile de recurent a: ak+1 = 5ak + 3 , ak + 3 bk = ak + 3 , ak + 1 k 0, a0 = 1.

3. S a se determine r ad acina p atrat a a unui num ur real pozitiv a cu precizia = 0.001, folosind relat ia de recurent a: xn+1 = 1 2 xn + a xn , x1 = a.

Precizia se consider a atins a c and |xn+1 xn | < . 4. Fie A o matrice p atratic a de dimensiune n. S a se transforme matricea A, prin interrschimb ari de linii si de coloane, astfel nc at elementele de pe diagonala principal a s a e ordonate cresc ator.

3.4. LIMBAJ ALGORITMIC

31

5. S a se determine cel mai mare divizor comun al unui sir de numere ntregi. 6. S a se calculeze coecient ii polinomului P [X ] = (aX + b)n , a, b Z, n N.

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

a.

b.

c.

Figura 3.1: Zone n matrice p atratic a

8. Fie x1 , x2 , ..., xn Z r ad acinile unui polinom cu coecient i ntregi: P [X ] = an X n + an1 X n1 + ... + a1 X + a0 . S a se determine coecient ii polinomului. 9. S a se determine toate r ad acinile rat ionale ale polinomului P [X ] care are coecient i ntregi. 140. Fie [P1 , P2 , ..., Pn ] un poligon convex dat prin coordonatele carteziene ale v arfurilor sale ( n ordine trigonometric a). S a se calculeze aria poligonului. 11. Fie f : [a, b] R o funct ie continu a cu proprietatea c a exist a un unic (a, b) care are proprietatea c a f ( ) = 0. S a se aproximeze cu precizia = 0.001 utiliz and metoda bisect iei. 12. Fie P si Q polinoame cu coecient i ntregi. S a se determine toate r ad acinile rat ionale comune celor dou a polinoame. 13. S a se determine toate numerele prime cu maxim 6 cifre care r am an prime si dup a r asturnarea lor (r asturnatul num arului abcdef este f edcba).

32

CAPITOLUL 3. ALGORITMI

3.5

Instruct iuni corespondente limbajului algoritmic

3.5.1

Declararea datelor

Datele simple se declar a sub forma: <tip> <nume>; sau <tip> <nume>= literal; unde <tip> poate lua una dintre urm atoarele valori: byte, short, int, long, oat, double, boolean, char. In exemplul urm ator sunt prezentate c ateva modalit a ti 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; } }

3.5. INSTRUCT IUNI CORESPONDENTE LIMBAJULUI ALGORITMIC

33

Java dene ste mai multe tipuri primitive de date. Fiecare tip are o anumit a dimensiune, care este independent a de caracteristicile ma sinii gazd a. Astfel, spre deosebire de C/C++, unde un ntreg poate reprezentat pe 16, 32 sau 64 de bit i, n funct ie de arhitectura ma sinii, o valoare de tip ntreg n Java va ocupa ntotdeauna 32 de bit i, indiferent de ma sina pe care ruleaz a. Aceast a consecvent a este esent ial a deoarece o aceea si aplicat ie va trebui s a ruleze pe ma sini cu arhitectur a pe 16, 32 sau 64 de bit i si s a produc a acela si rezultat pe ecare ma sin a n parte. Tip byte short int long oat double boolean char Dimensiune (octet i) 1 2 4 8 4 8 1 2 Valoare minima 27 215 231 263 +-1.4E-45 +-4.94E-324 Valoare maxima 27 1 215 1 231 1 263 1 +-3.4E+38 +-1.79E+308 Valoare initiala 0 0 0 0 0 0 f alse null Cifre semnicative

6-7 14-15

Tabelul 3.1: Tipurile primitive de date n Java Variabilele pot init ializate la declararea lor sau n momentul utiliz arii lor efective. Dac a valoarea nu este specicat a explicit atunci variabila se init ializeaz a cu o valoare init ial a implicit a. Tabelul anterior prezint a c ateva exemple n acest sens. Conversiile ntre diferitele tipuri sunt permise (acolo unde au semnicat ie). Se vede din tabel c a unele tipuri de variabile au posibilitatea s a reprezinte un spectru mai mare de numere dec at altele. In afara tipurilor de baz a, limbajul Java suport a si tipuri de date create de utilizator, de pild a variabile de tip clas a, interfat a sau tablou. Ca si celelalte variabile, dac a nu sunt explicit init ializate, valoarea atribuit a implicit este null. Modicatorul static este folosit pentru a specica faptul c a variabila are o singur a valoare, comun a tuturor instant elor clasei n care ea este declarat a. Modicarea valorii acestei variabile din interiorul unui obiect face ca modicarea s a e vizibil a din celelalte obiecte. Variabilele statice sunt init ializate la nc arcarea codului specic unei clase si exist a chiar si dac a nu exist a nici o instant a a clasei respective. Din aceast a cauz a, ele pot folosite de metodele statice. Tablourile unidimensionale se declar a sub forma: <tip>[ ] <nume> =new <tip>[n]; sau <tip> <nume>[ ] =new <tip>[n];

34

CAPITOLUL 3. ALGORITMI Elementele vectorului pot accesate cu ajutorul unui indice, sub forma: <nume>[i]

unde i poate lua orice valoare ntre 0 si n 1. In cazul tablourilor bidimensionale, o declarat ie de forma: <tip>[ ] [ ] <nume> = new <tip>[m][n]; sau <tip> <nume> [ ] [ ] = new <tip>[m][n]; specic a o matrice cu m linii si n coloane. Fiecare element se specic a prin doi indici: <nume>[i][j] unde i reprezint a indicele liniei si poate avea orice valoare ntre 0 si m 1 iar j reprezint a indicele coloanei si poate avea orice valoare ntre 0 si n 1.

3.5.2

Operat ii de intrare/ie sire

Preluarea unei valori de tip int de la tastatur a se poate face sub forma: BufferedReader br = new BufferedReader( new InputStreamReader(System.in)); int vi=Integer.parseInt(br.readLine()); iar dintr-un sier (de exemplu fis.in), sub forma: StreamTokenizer st = new new new st.nextToken(); int vi = StreamTokenizer( BufferedReader( FileReader("fis.in"))); (int) st.nval;

Scrierea valorii unei variabile v pe ecran se poate face sub forma: System.out.print(v); iar ntr-un sier (de exemplu fis.out), sub forma: PrintWriter out = new PrintWriter( new BufferedWriter( new FileWriter("fis.out"))); out.print(v); out.close();

3.5. INSTRUCT IUNI CORESPONDENTE LIMBAJULUI ALGORITMIC

35

3.5.3

Prelucr ari liniare

O secvent a de prelucr ari se descrie n modul urm ator: <instr 1>; <instr 2>; ... <instr n>; sau <instr 1>; <instr 2>; ... <instr n>; O astfel de scriere indic a faptul c a n momentul execut iei instruct iunile se efectueaz a n ordinea n care sunt specicate.

3.5.4

Prelucr ari alternative

O prelucrare alternativ a complet a (cu dou a ramuri) este descris a prin: if (<condit ie>) <instr 1> else <instr 2>;

unde <condit ie> este o expresie relat ional a. Aceast a prelucrare trebuie nt eleas a n modul urm ator: dac a condit ia este adev arat a atunci se efectueaz a prelucrarea a <instr 2>. <instr 1>, altfel se efectueaz O prelucrare alternativ a cu o singur a ramur a se descrie prin: if (<condit ie>) <instr>; iar execut ia ei are urm atorul efect: dac a condit ia este satisfacut a atunci se efectueaz a instruct iunea specicat a, altfel nu se efectueaz a nici o prelucrare ci se trece la urm atoarea prelucrare a algoritmului.

3.5.5

Prelucr ari repetitive

Prelucr arile repetitive pot de trei tipuri: cu test init ial, cu test nal si cu contor. Prelucrarea repetitiv a cu test init ial se descrie prin: while (<condit ie>) <instr>; In momentul execut iei, at at timp c at condit ia este adevarat a, se va executa prelucrarea. Dac a condit ia nu este la nceput satisf acut a, atunci prelucrarea nu se efectueaz a niciodat a. Prelucrarea repetitiv a cu test nal se descrie prin:

36

CAPITOLUL 3. ALGORITMI do <instr> while (<condit ie>);

Instruct iunea se repet a p an a c and condit ia specicat a devine fals a. In acest caz prelucrarea se efectueaz a cel put in o dat a, chiar dac a condit ia nu este satisfacut a la nceput. Prelucrarea repetitiv a cu contor se caracterizeaz a prin repetarea prelucr arii de un num ar prestabilit de ori si este descris a prin: for(<instr1> ; <conditie>; <instr2>) <instr3>; In general <instr1> reprezint a etapa de init ializare a contorului, <instr2> reprezint a etapa de incrementare a contorului, <instr3> reprezint a instruct iunea care se execut a n mod repetat c at timp condit ia <conditie> are valoarea true.

3.5.6

Subprograme

In cadrul unui program poate s a apar a necesitatea de a specica de mai multe ori si n diferite locuri un grup de prelucr ari. Pentru a nu le descrie n mod repetat ele pot constitui o unitate distinct a, identicabil a printr-un nume, care este numit a subprogram sau, mai precis, funct ie (dac a returneaz a un rezultat) sau procedur a (dac a nu returneaz a nici un rezultat). In Java funct iile si procedurile se numesc metode. Ori de c ate ori este necesar a efectuarea grupului de prelucr ari din cadrul programului se specic a numele acestuia si, eventual, datele curente asupra c arora se vor efectua prelucrarile. Aceast a act iune se nume ste apel al subprogramului, iar datele specicate al aturi de numele acestuia si asupra c arora se efectueaz a prelucrarile se numesc parametri. Un subprogram poate descris n felul urm ator: <tipr> <nume sp> (<tipp1> <numep1>, <tipp2> <numep2>, ... ) { ... /* prelucr ari specice subprogramului */ ... return <nume rezultat>; } unde <tipr> reprezint a tipul rezultatului returnat (void dac a subprogramul nu returneaz a nici un rezultat), <nume sp> reprezint a numele subprogramului, iar numep1, numep2, ... reprezint a numele parametrilor. Ultimul enunt , prin care se returneaz a rezultatul calculat n cadrul subprogramului, trebuie pus numai dac a <tipr> nu este void. Modul de apel depinde de modul n care subprogramul returneaz a rezultatele sale. Dac a subprogramul returneaz a efectiv un rezultat, printr-un enunt de forma return <nume rezultat>;

3.5. INSTRUCT IUNI CORESPONDENTE LIMBAJULUI ALGORITMIC atunci subprogramul se va apela n felul urm ator: v=<nume sp>(nume p1, nume p2, ...);

37

Aceste subprograme corespund subprogramelor de tip funct ie. Dac a n subprogram nu apare un astfel de enunt , atunci el se va apela prin: <nume sp>(nume p1, nume p2, ...); variant a care corespunde subprogramelor de tip procedur a. Observat ie. Prelucr arile care nu sunt detaliate n cadrul algoritmului sunt descrise n limbaj natural sau limbaj matematic. Comentariile suplimentare vor cuprinse ntre /* si */. Dac a pe o linie a descrierii algoritmului apare simbolul // atunci tot ce urmeaz a dup a acest simbol, pe aceea si linie cu el, este interpretat ca ind un comentariu (deci, nu reprezint a o prelucrare a programului).

3.5.7

Probleme rezolvate

1. Descompunere Fibonacci. S a se descompun a un num ar natural, de cel mult 18-19 cifre, n sum a de c at mai put ini termeni Fibonacci. Rezolvare: Programul urm ator calculeaz a si a seaz a primii 92 de termeni din sirul Fibonacci (mai mult nu este posibil f ar a numere mari!), si descompune num arul x introdus de la tastatur a. Metoda static int maxFibo ( long nr ) returneaz a indicele celui mai mare element din sirul lui Fibonacci care este mai mic sau egal cu parametrul nr. import java.io.*; class DescFibo { static int n=92; static long[] f=new long[n+1]; public static void main (String[]args) throws IOException { long x,y; int iy, k, nrt=0; BufferedReader br = new BufferedReader( new InputStreamReader(System.in)); System.out.print("x = "); x=Long.parseLong(br.readLine()); f[0]=0; f[1]=1; 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");

38

CAPITOLUL 3. ALGORITMI 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; }

} De exemplu, pentru x = 5678 pe ecran apare: 1 2 3 4 5 6 7 : : : : : : : 5678 f[19] = 418 1497 f[16] = 987 510 f[14] = 377 133 f[11] = 89 44 f[9] = 34 10 f[6] = 8 2 f[3] = 2

n si x2 sunt r ad acinile ecuat iei cu coecient i ntregi 2. Fie Sn = xn 1 + x2 unde x1 ax + bx + c = 0 (vom considera a = 1!). S a se a seze primii 10 termeni ai sirului Sn si s a se precizeze n dreptul ec arui termen dac a este num ar prim, iar dac a nu este num ar prim s a se a seze descompunerea n factori. Rezolvare: 2

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;

3.5. INSTRUCT IUNI CORESPONDENTE LIMBAJULUI ALGORITMIC p=c/a; 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 atoarele rezultate: 1 : -1 = 2 : -3 PRIM 1

39

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

CAPITOLUL 3. ALGORITMI

3. Se consider a funct ia f (x) = P (x)ex unde P (x) este un polinom de grad n cu coecient i ntregi. S a se a seze toate derivatele p an a la ordinul m ale funct iei f , si, n dreptul coecient ilor polinoamelor care apar n aceste derivate, s a se precizeze dac a respectivul coecient este num ar prim, iar dac a nu este num ar prim s a se a seze descompunerea n factori. De asemenea, s a se a seze care este cel mai mare num ar prim care apare, si care este ordinul derivatei n care apare acest cel mai mare num ar prim. Rezolvare: Derivata funct iei f are forma Q(x)ex unde Q este un polinom de acela si grad cu polinomul P . Toat a rezolvarea problemei se reduce la determinarea coecient ilor polinomului Q n funct ie de coecient 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)

3.5. INSTRUCT IUNI CORESPONDENTE LIMBAJULUI ALGORITMIC { 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)) { System.out.println(); return; } while(nr%d==0) { System.out.print(d+" "); nr=nr/d; }

41

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

CAPITOLUL 3. ALGORITMI

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 ad acini rat ionale. S a se determine toate r ad acinile rat ionale ale unei ecuat ii cu coecient i ntregi. Rezolvare: Se caut a r ad acini rat ionale formate din fract ii n care num ar atorul este divizor al termenului liber iar numitorul este divizor al termenului dominant. Programul care urmeaz a genereaz a coecient ii ecuat iei, plec and de la fract ii date (ca r ad acini), si apoi determin a r ad acinile rat 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++)

3.5. INSTRUCT IUNI CORESPONDENTE LIMBAJULUI ALGORITMIC {

43

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

44

CAPITOLUL 3. ALGORITMI 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(); }// afisv() }// class 5. S a se a seze frecvent a cifrelor care apar n
n

f (n) =
k=0

1 n C 2k n+k

net in and cont de faptul c a f (n) are o expresie mult mai simpl a, si anume 2n . Suma trebuie calculat a simul and operat iile de adunare, nmult ire si mp art ire la 2, cu numere mari. Rezolvare: Funct ia se pune sub forma: 1 f (n) = n 2
n n 2nk Cn +k k=0

Se calculeaz a suma, si apoi se fac n mp art iri succesive la 2. class e05

3.5. INSTRUCT IUNI CORESPONDENTE LIMBAJULUI ALGORITMIC { 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; int[] f=new int[10]; for(i=0;i<x.length;i++) f[x[i]]++;

45

46

CAPITOLUL 3. ALGORITMI 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++) {

3.5. INSTRUCT IUNI CORESPONDENTE LIMBAJULUI ALGORITMIC 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; } } 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; }

47

48

CAPITOLUL 3. ALGORITMI 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; } }// class 6. S a se a seze S (n, 1), S (n, 2), ..., S (n, m) (inclusiv suma cifrelor si num arul cifrelor pentru ecare num a) stiind c a S (n + 1, m) = S (n, m 1) + mS (n, m) si S (n, 1) = S (n, n) = 1, n m. Se vor implementa operat iile cu numere mari. Rezolvare: Matricea de calcul este subdiagonal a. Se completeaz a cu 1 prima coloan a si diagonala principal a, iar apoi se determin a celelalte elemente ale matricei folosind relat ia dat a (aranjat a put in altfel!). Matricea de calcul va avea de fapt trei

3.5. INSTRUCT IUNI CORESPONDENTE LIMBAJULUI ALGORITMIC

49

dimensiuni (numerele devin foarte mari, a sa c a elementul Si,j trebuie s a cont in a 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 static static static int[] suma(int[] x,int[] y){...} int[] nr2v(int nr){...} int[] inm(int[]x, int[]y){...} 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 atoarele valori (numerele devin foarte mari!): 1 : 1 1 1 2 : 15 64 562949953421311

50

CAPITOLUL 3. ALGORITMI

3 : 24 102 119649664052358811373730 4 : 29 138 52818655359845224561907882505 5 : 33 150 740095864368253016271188139587625 6 : 37 170 1121872763094011987454778237712816687 7 : 39 172 355716059292752464797065038013137686280 8 : 41 163 35041731132610098771332691525663865902850 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 a se a seze B1 , B2 , ..., Bn stiind c a
n

Bn+1 =
k=0

k Bk , B0 = 1. Cn

3.5. INSTRUCT IUNI CORESPONDENTE LIMBAJULUI ALGORITMIC

51

Se vor implementa operat iile cu numere mari. Rezolvare: Vectorul de calcul va avea de fapt dou a dimensiuni (numerele devin foarte mari, a sa c a elementul Bi trebuie s a cont in a 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); } System.out.print(i+" : "); afisv(b[i]); } System.out.println(" "+Long.MAX_VALUE); System.out.println("... Gata ..."); } static static static static int[] suma(int[] x,int[] y){...} int[] nr2v(int nr){...} int[] inm(int[]x, int[]y){...} 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++)

52 { d=cmmdc(y[j],x[i]); y[j]=y[j]/d; x[i]=x[i]/d; if(y[j]==1) break;

CAPITOLUL 3. ALGORITMI

} rez=nr2v(1); for(i=1;i<=k;i++) rez=inm(rez,nr2v(x[i])); return rez; } static int cmmdc(int a,int b) {...} }

3.5.8

Probleme propuse

n n si x3 sunt r ad acinile ecuat iei cu coecient i 1. Fie Sn = xn 1 +x2 +x3 unde x1 , x2 ntregi ax3 + bx2 + cx + d = 0 (vom considera a = 1!). S a se a seze primii 10 termeni ai sirului Sn si s a se precizeze n dreptul ec arui termen dac a este num ar prim, iar dac a nu este num ar prim s a se a seze descompunerea n factori.

2. S a se a seze frecvent a cifrelor care apar n f (n) =


n1 k=0 k n1k Cn (k + 1)! 1 n

net in and cont de faptul c a f (n) are o expresie mult mai simpl a, si anume nn . Suma trebuie calculat a simul and operat iile cu numere mari. 3. S a se a seze frecvent a cifrelor care apar n f (n) = nn1 +
n1 k=1 k k 1 Cn k (n k )nk

net in and cont de faptul c a f (n) are o expresie mult mai simpl a, si anume nn . Suma trebuie calculat a simul and operat iile cu numere mari. 4. S a se calculeze f (n) = n 1 1 p1 1 1 p2 ... 1 1 pm

3.5. INSTRUCT IUNI CORESPONDENTE LIMBAJULUI ALGORITMIC


im 1 i2 a descompunerea n factori primi a lui n. unde n = pi 1 p2 ...pm reprezint

53

5. S a se calculeze (n) = card {k N/1 k n, cmmdc(k, n) = 1} . 6. S a se calculeze f (n) =


d|n

(n)

unde este funct ia de la exercit iul anterior, net in and cont de faptul c a f (n) are o expresie mult mai simpl a, si anume n. 7. S a se calculeze f (n) = n! 1 8. S a se calculeze
m

1 1 (1)n + ... + 1! 2! n!

f (m, n, 1 , 2 , ..., n ) =

k=1

1 k Ck (1)mk Cm

2 Ck +1

n ... Ck +n1

9. S a se calculeze
1 g (m, n, 1 , 2 , ..., n ) = Cm 1 2 Cm +1 2 n ... Cm +n1 n

implement and operat iile cu numere mari. 10. S a se calculeze f (n) = 1 1 2 2 (2n)! Cn 2(2n 1)! + Cn 2 (2n 2)! ... + (1)n 2n n! . 2n

1 Cn n + 1 2n implement and operat iile cu numere mari. Cn = 12. S a se a seze P (100, 50) (inclusiv suma cifrelor si num arul cifrelor) stiind c a P (n + k, k ) = P (n, 1) + P (n, 2) + ... + P (n, k )

11. S a se calculeze

54 si

CAPITOLUL 3. ALGORITMI

P (n, 1) = P (n, n) = 1, n k 1. Se vor implementa operat iile cu numere mari. 13. S a se determine cel mai mic num ar natural r, astfel nc at pr = e, unde p este o permutare dat a si e este permutarea identic a. 14. S a se a seze C100 stiind c a
n

Cn =
k=1

Ck1 Cnk , C0 = 1.

Se vor implementa operat iile cu numere mari. 15. S a se a seze E100 stiind c a En = E2 En1 + E3 En2 + ... + En1 E2 , E1 = E2 = 1. Se vor implementa operat iile cu numere mari. 16. S a se calculeze S (n, m) = 1 m!
m 1 k=0 k (1)k Cm (m k )n

17. S a se a seze C100 stiind c a


n

Cn =
k=1

k Cn Fk .

unde Fk este termen Fibonacci. Se vor implementa operat iile cu numere mari. 18. S a se a seze C100 stiind c a
n

Cn =
k=1

k k Cn 2 Fk .

unde Fk este termen Fibonacci. Se vor implementa operat iile cu numere mari. 19. S a se determine puterea a zecea a unui polinom dat.

Capitolul 4

Analiza complexit a tii algoritmilor


4.1 Scopul analizei complexit a tii

In general exist a mai multi algoritmi care rezolv a aceea si problem a. Dorim s a exprim am ecient a algoritmilor sub forma unui criteriu care s a ne permit a s a alegem din mai mult i algoritmi pe cel optim. Exist a mai multe moduri n care putem exprima ecient a: prin timpul necesar pentru execut ia algoritmului sau prin alte resurse necesare (de exemplu memoria). In ambele cazuri ns a, avem o dependent a de dimensiunea cazului studiat. Se pune problema de alegere a unei unit a ti de m asur a pentru a exprima ecient a teoretic a a unui algoritm. O important a deosebit a n rezolvarea acestei probleme o are principiul invariant ei. Acesta ne arat a c a nu este necesar s a folosim o astfel de unitate. Principiul invariant ei: dou a implement ari diferite ale aceluia si algoritm nu difer a n ecient a cu mai mult de o constant a multiplicativ a. Implementarea unui algoritm presupune elementele legate de calculatorul folosit, de limbajul de programare si ndem anarea programatorului (cu condit ia ca acesta s a nu modice algoritmul). Datorit a principiului invariant ei vom exprima ecient a unui algoritm n limitele unei constante multiplicative. Un algoritm este compus din mai multe instruct iuni, care la r andul lor sunt compuse din mai multe operat ii elementare. Datorit a principiului invariant ei nu ne intereseaz a timpul de execut ie a unei operat ii elementare, ci numai num arul lor, dar ne intereseaz a care si ce sunt operat iile elementare. Denit ia 1 O operat ie elementar a este o operat ie al c arui timp de execut ie poate m arginit superior de o constant a care depinde numai de particularitatea implement arii (calculator, limbaj de programare etc). 55

56

II ALGORITMILOR CAPITOLUL 4. ANALIZA COMPLEXITAT

Deoarece ne intereseaz a timpul de executie n limita unei constante multiplicative, vom considera doar num arul operat iilor elementare executate ntr-un algoritm, nu si timpul exact de execut ie al operat iilor respective. Este foarte important ce anume denim ca operat ie elementar a. Este adunarea o operat ie elementara? Teoretic nu este, pentru c a depinde de lungimea celor doi operanzi. Practic, pentru operanzi de lungime rezonabil a putem s a consider am c a adunarea este o operat ie elementar a. Vom considera n continuare c a adun arile, sc aderile, nmult irile, mp art irile, operat iile modulo (restul mp aart irii ntregi), operat iile booleene, comparat iile si atribuirile sunt operat ii elementare. Uneori ecient a difer a dac a tinem cont numai de unele operat ii elementare si le ignor am pe celelalte (de exemplu la sortare: comparat ia si interschimbarea). De aceea n analiza unor algoritmi vom considera o anumit a operat ie elementar a, care este caracteristic a algoritmului, ca operat ie barometru, neglij andu-le pe celelalte. De multe ori, timpul de executie al unui algoritm poate varia pentru cazuri de m arime identic a. De exemplu la sortare, dac a introducem un sir de n numere gata sortat, timpul necesar va cel mai mic dintre timpii necesari pentru sortarea oricarui alt sir format din n numere. Spunem c a avem de-a face cu cazul cel mai favorabil. Dac a sirul este introdus n ordine invers a, avem cazul cel mai defavorabil si timpul va cel mai mare dintre timpii de sortare a sirului de n numere. Exist a algoritmi n care timpul de execut ie nu depinde de cazul considerat. Dac a dimensiunea problemei este mare, mbun at a tirea ordinului algoritmului este esent ial a, n timp ce pentru timpi mici este sufcient a performant a hardware. Elaborarea unor algoritmi ecient i presupune cuno stint e din diverse domenii (informatic a, matematic a si cuno stiint e din domeniul c aruia i apart ine problema practic a a c arui model este studiat, atunci c and este cazul). Exemplul 1 Elaborat i un algoritm care returneaz a cel mai mare divizor comun (cmmdc) a doi termeni de rang oarecare din sirul lui Fibonacci. S irul lui Fibonacci, fn = fn1 + fn2 , este un exemplu de recursivitate n cascad a si calcularea efectiv a a celor doi termeni fm fn , urmat a de calculul celui mai mare divizor al lor, este total neindicat a. Un algoritm mai bun poate obt inut dac a tinem seama de rezultatul descoperit de Lucas n 1876: cmmdc(fm , fn ) = fcmmdc(m,n) Deci putem rezolva problema calcul and un singur termen al sirului lui Fibonacci. Exist a mai mult i algoritmi de rezolvare a unei probleme date. Prin urmare, se impune o analiz a a acestora, n scopul determin arii ecient ei algoritmilor de rezolvare a problemei si pe c at posibil a optimalit a tii lor. Criteriile n funct ie de care vom stabili ecient a unui algoritm sunt complexitatea spat iu (memorie utilizat a) si complexitatea timp (num arul de operat iilor elementare).

II 4.1. SCOPUL ANALIZEI COMPLEXITAT

57

4.1.1

Complexitatea spat iu

Prin complexitate spat iu nt elegem dimensiunea spat iului de memorie utilizat de program. Un program necesit a un spat iu de memorie constant, independent de datele de intrare, pentru memorarea codului, a constantelor, a variabilelor si a structurilor de date de dimensiune constant a alocate static si un spat iu de memorie variabil, a c arui dimensiune depinde de datele de intrare, const and din spat iul necesar pentru structurile de date alocate dinamic, a c aror dimensiune depinde de instant a problemei de rezolvat si din spat iul de memorie necesar apelurilor de proceduri si funct ii. Progresele tehnologice fac ca important a criteriului spat iu de memorie utilizat s a scad a, prioritar devenind criteriul timp.

4.1.2

Complexitatea timp

Prin complexitate timp nt elegem timpul necesar execut iei programului. Inainte de a evalua timpul necesar execut iei programului ar trebui s a avem informat ii detaliate despre sistemul de calcul folosit. Pentru a analiza teoretic algoritmul, vom presupune c a se lucreaz a pe un calculator clasic, n sensul c a o singur a instruct iune este executat a la un moment dat. Astfel, timpul necesar execut iei programului depinde numai de num arul de operat ii elementare efectuate de algoritm. Primul pas n analiza complexit a tii timp a unui algoritm este determinarea operat iilor elementare efectuate de algoritm si a costurilor acestora. Consider am operat ie elementar a orice operat ie al c arei timp de execut ie este independent de datele de intrare ale problemei. Timpul necesar execut iei unei operat ii elementare poate diferit de la o operat ie la alta, dar este xat, deci putem spune c a operat iile elementare au timpul m aginit superior de o constant a. F ar a a restr ange generalitatea, vom presupune c a toate operat iile elementare au acela si timp de execut ie, ind astfel necesar a doar evaluarea num arului de operat ii elementare, nu si a timpului total de execut ie a acestora. Analiza teoretic a ignor a factorii care depind de calculator sau de limbajul de programare ales si se axeaz a doar pe determinarea ordinului de m arime a num arului de operat i elementare. Pentru a analiza timpul de execut ie se folose ste deseori modelul Random Access Machine (RAM), care presupune: memoria const a ntr-un sir innit de celule, ecare celul a poate stoca cel mult o dat a, ecare celul a de memorie poate accesat a ntr-o unitate de timp, instruct iunile sunt executate secvent ial si toate instruct iunile de baz a se execut a ntr-o unitate de timp. Scopul analizei teoretice a algoritmilor este de fapt determinarea unor funct ii care s a limiteze superior, respectiv inferior comportarea n timp a algoritmului. Funct iile depind de caracteristicile relevante ale datelor de intrare.

58

II ALGORITMILOR CAPITOLUL 4. ANALIZA COMPLEXITAT

4.2

Notat ia asimptotic a

4.2.1

Denire si propriet a ti

Denit ia 2 Numim ordinul lui f , mult imea de funct ii O(f ) = {t : N R+ |c > 0, n0 N a. . t(n) cf (n), n > n0 } (4.2.1)

Rezult a c a O(f ) este mult imea tuturor funct iilor m arginite superior de un multiplu real pozitiv al lui f , pentru valori sucient de mari ale argumentului. Dac a t(n) O(f ) vom spune c a t este de ordinul lui f sau n ordinul lui f . Fie un algoritm dat si o funct ie t : N R+ , astfel nc at o anumit a implementare a algoritmului s a necesite cel mult t(n) unit a ti de timp pentru a rezolva un caz de marime n. Principiul invariant ei ne asigur a c a orice implementare a algoritmului necesit a un timp n ordinul lui t. Mai mult, acest algoritm necesit a un timp n ordinul lui f pentru orice functie f : N R+ pentru care t O(f ). In particular t O(t). Vom c auta s a g asim cea mai simpl a funct ie astfel nc at t O(f ). Pentru calculul ordinului unei funct ii sunt utile urm atoarele propriet a ti: Proprietatea 1 O(f ) = O(g ) f O(g ) si g O(f ) Proprietatea 2 O(f ) O(g ) f O(g ) si g / O(f ) Proprietatea 3 O(f + g ) = O(max(f, g )) Pentru calculul mult imilor O(f ) si O(g ) este util a proprietatea urm atoare: Proprietatea 4 Fie f, g : N R+ . Atunci
n

lim

f (n) R+ O(f ) = O(g ) g (n) f (n) = 0 O(f ) O(g ). g (n)

lim

Reciproca nu este n general valabil a. Fie de exemplu, t(n) = n2 + 3n + 2, atunci n2 + 3n + 2 = 1 = O(n2 + 3n + 2) = O(n2 ) n n2 lim n2 + 3n + 2 = 0 = O(n2 + 3n + 2) O(n3 ) n n3 lim

4.2. NOTAT IA ASIMPTOTICA ln(n) = lim n n n lim


1 n 1

59

2 n

2 = lim = 0 = O(ln(n)) O( n) n n

dar O( n) O(ln(n) Dac a p este un polinom de gradul m n variabila n, atunci O(p) = O(nm ). Notat ia asimptotic a dene ste o relat ie de ordine part ial a ntre funct ii. Pentru f, g : N R not am f g dac a O(f ) O(g ). Aceast a relat ie are propriet a tile corespunz atoare unei relat ii de ordine, adic a: a) reexivitate: f f b) antisimetrie: dac af g si g f atunci f = g c) tranzitivitate: f g si g h, implic a f h. Dar nu este o relat ie de ordine! Exist a si funct ii astfel nc at f g (f / O(g )) si g f (g / O(f )). De exemplu f (n) = n, g (n) = n1+sin(n) . Putem deni si o relat ie de echivalent a: f g , dac a O(f ) = O(g ). In mult imea O(f ) putem nlocui orice funct ie cu o funct ie echivalent a cu ea. De exemplu: ln(n) log(n) log2 (n). Not and cu O(1) mult imea funct iilor m arginite superior de o constant a si consider and m N , m 2, obt inem ierarhia: O(1) O(log(n)) O( n) O(n) O(n log (n)) O(nm ) O(2n ) O(n!) si evident O(n2 ) O(n3 ) ... O(nm ) pentru m 4. Aceast a ierarhie corespunde ierarhiei algoritmilor dup a criteriul performant ei. Pentru o problem a dat a, dorim sa realiz am un algoritm cu un ordin situat c at mai n st anga n aceast a ierarhie. Notatia O(f ) este pentru a delimita superior timpul necesar unui algoritm. Not am TA (n) timpul necesar execut iei algoritmului A. o funct ie arbitrar a. Spunem c a algoritmul este de ordinul Fie f : N R + lui f (n) ( si not am TA (n) O(f (n))), dac a si numai dac a exist ac>0 si n0 N, astfel nc at TA (n) c f (n), n n0 . De exemplu: a) Dac a TA (n) = 3n +2, atunci TA (n) O(n), pentru c a 3n +2 4n, n 2. Mai general, dac a TA (n) = a n + b, a > 0, atunci TA (n) O(n) pentru c a exist a c=a+1>0 si n0 = b N, astfel nc at a n + b (a + 1) n, n b. b) Dac a TA (n) = 10n2 + 4n + 2, atunci TA (n) O(n2 ), pentru c a 10n2 + 4n + 2 11n2 , n 5. Mai general, dac a T A(n) = an2 + bn + c, a > 0, atunci T A(n) O(n2 ), 2 pentru c a an + bn + c (a + 1)n2 , n max(b, c) + 1. c) Dac a TA (n) = 6 2n + n2 , atunci T A(n) O(2n ), pentru c a TA (n) n 7 2 ,n 4. Dac a TA (n) = ak nk + ak1 nk1 + ... + a1 n + a0 , atunci T A(n) O(nk ). Aceasta rezult a din: TA (n) = |TA (n)| = |ak nk + ak1 nk1 + ... + a1 n + a0 | k |ak |n + |ak1 |nk1 + ... + |a1 |n + |a0 | (|ak | + |ak1 | + ... + |a1 | + |a0 |)nk , n 1 si aleg and c = |ak | + |ak1 | + ... + |a1 | + |a0 | si n = 1 rezult a TA (n) O(nk ).

60

II ALGORITMILOR CAPITOLUL 4. ANALIZA COMPLEXITAT

4.2.2

Clase de complexitate

Notat ia O ofer a o limit a superioar a a timpului de execut ie a unui algoritm. Un algoritm cu TA (n) O(1) necesit a un timp de execut ie constant. Un algoritm cu TA (n) O(n) se nume ste liniar. Dac a TA (n) O(n2 ) algoritmul se nume ste p atratic, iar dac a TA (n) O(n3 ), cubic. Un algoritm cu TA (n) O(nk ) se nume ste polinomial, iar dac a TA (n) O(2n ) algoritmul se nume ste exponent ial. Tabelul urm ator ilustreaz a comportarea a cinci din cele mai importante funct ii de complexitate. O(log (n)) (logaritmic) 0 1 2 3 4 5 O(n) (liniar) 1 2 4 8 16 32 O(n.log (n)) (log-liniar) 0 2 8 24 64 160 O(n2 ) (p atratic) 1 4 16 64 256 1024 O(n3 ) cubic 1 8 64 512 4096 32768 O(2n ) (exponent ial) 2 4 16 256 65536 4294967296

Tabelul 4.1: Funct ii de complexitate Dac a TA (n) O(2n ), pentru n = 40, pe un calculator care face 109 de operat ii pe secund a, sunt necesare aproximativ 18 minute. Pentru n = 50, acela si program va rula 13 zile pe acest calculator, pentru n = 60, vor necesari peste 310 ani, iar pentru n = 100 aproximativ 4.1013 ani. Utilitatea algoritmilor polinomiali de grad mare este de asemenea limitat a. De exemplu, pentru O(n10 ), pe un calculator care execut a 109 operat ii pe secund a sunt necesare 10 secunde pentru n = 10, aproximativ 3 ani pentru n = 100 si circa 3.1013 ani pentru n = 1000. Uneori este util s a determin am si o limit a inferioar a pentru timpul de execut ie a unui algoritm. Notat ia matematic a este . Denit ie: Spunem c a TA (n) (f (n)) dac a si numai dac a c > 0 si n0 N astfel nc at TA (n) c f (n), n n0 . De exemplu: a) dac a TA (n) = 3n + 2, atunci TA (n) (n), pentru c a 3n + 2 3n, n 1; b) dac a TA (n) = 10n2 +4n+2, atunci TA (n) (n), pentru c a 10n2 +4n+2 n2, n 1; c) dac a TA (n) = 6 2n + n2 , atunci TA (n) (2n ), pentru c a 6 2n + n2 2n , n 1. Exist a funct ii f care constituie at at o limit a superioar a c at si o limit a inferioar a a timpului de execut ie a algoritmului. De exemplu, dac a TA (n) = ak nk + ak1 nk1 + ... + a1 n + a0 , ak > 0 atunci TA (n) (nk ). Denit ie : Spunem c a TA (n) (f (n)) dac a si numai dac a c1 , c2 > 0 si n0 N astfel nc at c1 f (n) TA (n) c2 f (n), n n0 .

4.2. NOTAT IA ASIMPTOTICA

61

In acest caz f (n) constituie at at o limit a inferioar a c at si o limit a superioar a pentru timpul de execut ie a algoritmului. Din acest motiv se poate numi ordin exact. Se poate ar ata u sor c a (f (n)) = O(f (n)) (f (n)). De asemenea, dac a TA (n) = ak nk + ak1 nk1 + ... + a1 n + a0 , ak > 0 atunci TA (n) (nk ).

4.2.3

Cazul mediu si cazul cel mai defavorabil

Am ar atat c a timpul de execut ie al unui algoritm este direct proport ional cu num arul de operat ii elementare si am stabilit o notat ie asimptotic a pentru timpul de execut ie. Totu si, num arul de operat ii elementare efectuate de algoritm poate varia considerabil pentru diferite seturi de date de intrare. Determinarea complexit a tii timp a algoritmului ca o funct ie de caracteristicile datelor de intrare este o sarcin a u soar a doar pentru algoritmi relativ simpli, dar n general problema este dicil a si din aceast a cauz a analiz am complexitatea algoritmilor n medie sau n cazul cel mai defavorabil. Complexitatea n cazul cel mai defavorabil este num arul maxim de operat ii elementare efectuate de algoritm. Dar chiar dac a este cunoscut cazul cel mai defavorabil, datele utilizate efectiv n practic a pot conduce la timpi de execut ie mult mai mici. Numero si algoritmi foarte utili au o comportare convenabil a n practic a, dar foarte proast a n cazul cel mai defavorabil. Cel mai cunoscut exemplu este algoritmul de sortare rapid a (quicksort) care are complexitatea n cazul cel mai defavorabil de O(n2 ), dar pentru datele nt alnite n practic a funct ioneaz a n O(n log n). Determinarea complexit a tii n medie necesit a cunoa sterea repartit iei probabilistice a datelor de intrare si din acest motiv analiza complexit a tii n medie este mai dicil de realizat. Pentru cazuri simple, de exemplu un algoritm de sortare care act ioneaza asupra unui tablou cu n componente ntregi aleatoare sau un algoritm geometric pe o mult ime de N puncte n plan de coordonate aleatoare cuprinse n intervalul [0, 1], putem caracteriza exact datele de intrare. Dac a not am: D - spat iul datelor de intrare p(d) - probabilitatea aparit iei datei d D la intrarea algoritmului TA (d) - num arul de operat ii elementare efectuate de algoritm pentru d D atunci complexitatea medie este p(d) TA (d).

dD

62

II ALGORITMILOR CAPITOLUL 4. ANALIZA COMPLEXITAT

4.2.4

Analiza asimptotic a a structurilor fundamentale

Consider am problema determin arii ordinului de complexitate n cazul cel mai defavorabil pentru structurile algoritmice: secvent ial a, alternativ a si repetitiv a. Presupunem c a structura secvent ial a este constituit a din prelucr arile A1 , A2 , ..., Ak si ecare dintre acestea are ordinul de complexitate O(gi (n)), 1 i n. Atunci structura va avea ordinul de complexitate O(max{g1 (n), ..., gk (n)}). Dac a condit ia unei structuri alternative are cost constant iar prelucr arile celor dou a variante au ordinele de complexitate O(g1 (n)) respectiv O(g2 (n)) atunci costul structurii alternative va O(max{g1 (n), g2 (n)}). In cazul unei structuri repetitive pentru a determina ordinul de complexitate n cazul cel mai defavorabil se consider a num arul maxim de iterat ii. Dac a acesta este n iar n corpul ciclului prelucr arile sunt de cost constant atunci se obt ine ordinul O(n).

4.3

Exemple

4.3.1

Calcularea maximului

Fiind date n elemente a1 , a2 , ..., an , s a se calculeze max{a1 , a2 , ..., an }. max = a[1]; for i = 2 to n do if a[i] > max then max = a[i]; Vom estima timpul de execut ie al algoritmului n funct ie de n, num arul de date de intrare. Fiecare iterat ie a ciclului for o vom considera operat ie elementar a. Deci complexitatea algoritmului este O(n), at at n medie c at si n cazul cel mai defavorabil.

4.3.2

Sortarea prin select ia maximului

Sort am cresc ator 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; }

4.3. EXEMPLE a[pozmax]=a[j]; a[j]=max; } }

63

Estim am complexitatea algoritmului n funct ie de n, dimensiunea vectorului. La ecare iterat ie a ciclului for exterior este calculat max{a1 , a2 , ..., aj } si plasat pe pozit ia j , elementele de la j + 1 la n ind deja plasate pe pozit iile lor denitive. Conform exemplului anterior, pentru a calcula max{a1 , a2 , ..., aj } sunt necesare j 1 operat ii elementare, n total 1 + 2 + ... + (n 1) = n(n 1)/2. Deci complexitatea algoritmului este de O(n2 ). S a observ am c a timpul de execut ie este independent de ordinea init ial a a elementelor vectorului.

4.3.3

Sortarea prin insert ie

Este o metod a de asemenea simpl a, pe care o utiliz am adesea c and ordon am c art ile la jocuri de c art 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 am algoritmul n funct ie de n, dimensiunea vectorului ce urmeaz aa sortat. La ecare iterat ie a ciclului for elementele a1 , a2 , ..., ai1 sunt deja ordonate si trebuie s a inser am valorea a[i] pe pozit ia corect a n sirul ordonat. In cazul cel mai defavorabil, c and vectorul este init ial ordonat descresc ator, ecare element a[i] va plasat pe prima pozit ie, deci ciclul while se execut a de i 1 ori. Consider and drept operat ie elementar a comparat ia a[poz 1] > val urmat a de deplasarea elementului de pe pozit ia poz 1, vom avea n cazul cel mai defavorabil 1 + 2 + ... + (n 1) = n(n 1)/2 operat ii elementare, deci complexitatea algoritmului este de O(n2 ). S a analiz am comportarea algoritmului n medie. Consider am c a elementele vectorului sunt distincte si c a orice permutare a lor are aceea si probabilitate de aparit ie. Atunci probabilitatea ca valoarea ai s a e plasat a pe pozit ia k n sirul a1 , a2 , ..., ai , k {1, 2, ...., i} este 1/i. Pentru i xat, num arul mediu de operat ii elementare este:
i

k=1

1 1 (k 1) = i i

k=1

(k 1) =

1 i

i(i + 1) i 2

i+1 i1 1= 2 2

64

II ALGORITMILOR CAPITOLUL 4. ANALIZA COMPLEXITAT Pentru a sorta cele n elemente sunt necesare
n i=2

i1 1 = 2 2

n(n + 1) 1 (n 1) 2

n 2

(n + 1) 1 2

n(n 1) 4

operat ii elementare. Deci complexitatea algoritmului n medie este tot O(n2 ).

4.3.4

Sortarea rapid a (quicksort)

Acest algoritm a fost elaborat de C.A.R. Hoare n 1960 si este unul dintre cei mai utilizat 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); } } Init ial apel am quicksort(1,n). Funct ia divide are rolul de aplasa primul element (a[st]) pe pozit ia sa corect a n sirul ordonat. In st anga sa se vor g asi numai elemente mai mici, iar n dreapta numai elemente mai mari dec at 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; } Observat ie : Vectorul a este considerat variabil a global a.

4.3. EXEMPLE

65

In cazul cel mai defavorabil, c and vectorul a era init ial ordonat, se fac n 1 apeluri succesive ale procedurii quicksort, cu parametrii (1, n), (1, n 1), ..., (1, 2) (dac a vectorul a era init ial ordonat descresc ator) sau (1, n), (2, n), ..., (n 1, n) (dac a vectorul a era ordonat cresc ator). La ecare apel al procedurii quicksort este apelat a funct ia divide(1,i) (respectiv divide(i, n)) care efectueaz a i 1, (respectiv n i 1) operat ii elementare. In total num arul de operat ii elementare este (n 1) + (n 2) + ... + 1 = n(n 1)/2. Complexitatea algoritmului n cazul cel mai defavorabil este de O(n2 ). S a analiz am comportarea algoritmului n medie. Vom consider am c a orice permutare a elementelor vectorului are aceea si probabilitate de aparit ie si not am cu Tn num arul de operat ii elementare efectuate pentru a sorta n elemente. Probabilitatea ca un element al vectorului s a e plasat pe pozit ia k n vectorul ordonat, este de 1/n. 0,
1 n n k=1

Tn =

(Tk1 + Tnk ) + (n 1),

dac a n = 0 sau n = 1 dac an>1

(pentru a ordona cresc ator n elemente, determin am pozit ia k n vectorul ordonat a primului element, ceea ce necesit a n 1 operat ii elementare, sort am elementele din st anga, ceea ce necesit a Tk1 operat ii elementare, apoi cele din dreapta, necesit and Tnk operat ii elementare). Problema se reduce la a rezolva relat ia de recurent a de mai sus. Mai nt ai observ am c a T0 + T1 + ... + Tn1 = T n 1 + ... + T1 + T0 . Deci, Tn = n 1 + 2 n
n

k=1

T k 1

Inmult im ambii membri ai acestei relat ii cu n. Obt inem:


n

nTn = n(n 1) + 2

k=1

T k 1

Sc az and din aceast a relat ie, relat ia obt inut a pentru n 1, adic a (n 1)Tn1 = (n 1)(n 2) + 2 obt inem nTn (n 1)Tn1 = n(n 1) (n 1)(n 2) + 2Tn1 de unde rezult a nTn = 2(n 1) + (n + 1)Tn1
n1 k=1

T k 1

66

II ALGORITMILOR CAPITOLUL 4. ANALIZA COMPLEXITAT

Imp art ind ambii membri cu n(n + 1) obt inem Tn Tn1 2(n 1) Tn2 2(n 1) 2(n 2) T2 = + = + + = ... = +2 n+1 n n(n + 1) n 1 n(n + 1) (n 1)n 3 Deci Tn T2 = +2 n+1 3
n n

k=3

k1 k (k + 1)

k=3

1 1 1 + k+1 k k+1

T2 2 + +2 3 n+1

k=3

1 2 k

k=1

1 2 ln n k

Deci, n medie, complexitatea algoritmului este de O(n log n).

4.3.5

Problema celebrit a tii

Numim celebritate o persoan a care este cunoscut a de toat a lumea, dar nu cunoa ste pe nimeni. Se pune problema de a identica o celebritate, dac a exist a, ntr-un grup de n persoane pentru care relat iile dintre persoane sunt cunoscute. Putem reformula problema n limbaj de grafuri astfel: ind dat un digraf cu n v arfuri, vericat i dac a exist a un v arf cu gradul exterior 0 si gradul interior n 1. Reprezent am graful asociat problemei prin matricea de adiacent a ann ai,j = 1, 0, dac a persoana i cunoaste persoana j ; altfel.

O prim a solut ie ar s a calcul am pentru ecare persoan a p din grup num arul de persoane pe care p le cunoa ste (out) si num arul de persoane care cunosc persoana p (in). Cu alte cuvinte, pentru ecare v arf din digraf calcul am gradul interior si gradul exterior. Dac a g asim o persoan a pentru care out = 0 si in = n 1, aceasta va celebritatea c autat a. celebritate=0; for p=1,2,...,n { in=0; out=0; for j=1,2,...,n { in=in+a[j][p]; 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.);

4.4. PROBLEME

67

Se poate observa cu u surint a c a algoritmul este de O(n2 ). Putem mbun at a ti algoritmul f ac and observat ia c a atunci c and test am relat iile dintre persoanele x si y apar urm atoarele posibilit a tii: a[x, y ] = 0 si n acest caz y nu are nici o sans a s a e celebritate, sau a[x, y ] = 1 si n acest caz x nu poate celebritate. Deci la un test elimin am o persoan a care nu are sanse s a e celebritate. F ac and succesiv n 1 teste, n nal vom avea o singur a persoan a candidat la celebritate. R am ane s a calcul am num arul de persoane cunoscute si num arul de persoane care l cunosc pe acest candidat, singura celebritate posibil a. 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.); In acest caz algoritmul a devenit liniar.

4.4

Probleme

4.4.1

Probleme rezolvate

Problema 1 Care armat ii sunt adevarate: a) n2 O(n3 ) b) n3 O(n2 ) c) 2n+1 O(2n ) d) (n + 1)! O(n!) e) f : N R , f O(n) = f 2 O(n2 ) f ) f : N R , f O(n) = 2f O(2n ) Rezolvare: n2 a) Armat ia este adevarat a pentru c a: limn n n2 O(n3 ). 3 = 0 = 3 n b) Armat ia este fals a pentru c a: limn n 2 =

68

II ALGORITMILOR CAPITOLUL 4. ANALIZA COMPLEXITAT


n+1

c) Armat ia este adevarat a pentru c a: limn 22n = 2 = O(2n+1 ) = n O(2 ). = limn n+1 d) Armat ia este fals a pentru c a: limn (n+1)! n! 1 = e) Armat ia este adevarat a pentru c a: f O(n) = c > 0 si n0 N astfel nc at f (n) < c n, n > n0 . Rezult a c a c1 = c2 astfel nc a f 2 (n) < c1 n2 , n > n0 , deci f 2 O(n2 ). e) Armat ia este adevarat a pentru c a: f O(n) = c > 0 si n0 N astfel nc at f (n) < c n, n > n0 . Rezult a c a c1 = 2c astfel nc a 2f (n) < 2cn = 2c 2n = c1 2n , n > n0 , deci 2f O(2n ). / O(log n). Problema 2 Ar atat i c a log n O( n) dar n Indicat ie: Prelungim domeniile funct iilor pe R+ , pe care sunt derivabile, si aplic am relula lui LH ospital pentru log n/ n. Problema 3 Demonstrat i urm atoarele armat ii: i) ii)
i=1 n

loga (logb n), pentru oricare a, b > 1


n

ik (nk+1 ), pentru oricare k N 1 (n log n) i

iii)
i=1

iv )

log n! (n log n)
i=1

Indicat ii: La punctul iii) se tine cont de relat ia 1 + ln n i

unde 0.5772 este constanta lui Euler. La punctul iv ) din n! < nn , rezult a log n! < n log n, deci log n! O(n log n). Trebuie s a g asim si o margine inferioar a. Pentru 0 i n 1 este adev arat a relat ia (n i)(i + 1) n Deoarece (n!)2 = (n 1)((n 1) 2)((n 2) 3)...(2 (n 1))(1 n) nn rezult a 2 log n! n log n, adic a log n! 0.5n log n, deci log n! (n log n). Relat ia se poate demonstra si folosind aproximarea lui Stirling n! 2n n e
n

(1 + (1/n))

4.4. PROBLEME

69

4.4.2

Probleme propuse

1. Ar atat i c a: a) n3 + 106 n (n3 ) n n b) n2 + 6 2n ()(n2 ) c) 2n2 + n log n (n2 ) d) nk + n + nk log n (nk log n), k 1 e) loga n (logb n), a, b > 0, a = 1, b = 1. 2. Pentru oricare doua functii f, g : N R demonstrat i c a O(f + g ) = O(max(f, g )) unde suma si maximul se iau punctual. 3. Fie f, g : N R+ Demonstrat i c a: i) lim
n

(4.4.1)

f (n) R+ O(f ) = O(g ), g (n)

ii) lim

f (n) = 0 O(f ) O(g ) g (n)

Observat ie: Implicat iile inverse nu sunt n general adev arate, deoarece se poate ntampla ca limitele s a nu existe. 4. Demonstrat i prin induct ie c a pentru a determina maximul a n numere sunt necesare n 1 comparat ii. 5. Care este timpul de execut ie a algoritmului quicksort pentru un vector cu n componente egale? 6. S a consider am urm atorul 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; Analizat i algoritmul n medie si n cazul cel mai defavorabil. 7. Analizat i complexitatea algoritmului de interclasare a doi vectori ordonat i, a cu n componente, 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; }

70

II ALGORITMILOR CAPITOLUL 4. ANALIZA COMPLEXITAT

} 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, vericat i dac a o valoare dat a x se g ase ste sau nu n vector. Evaluat i complexitatea algoritmului n cazul cel mai defavorabil si n medie. 9. Se d a a un vector cu n componente. Scriet i un algoritm liniar care s a determine cea mai lung a secvent a de elemente consecutive de valori egale. 10. Fie T un text. Vericat i n timp liniar dac a un text dat T este o permutare circular a a lui T . 11. Fie X = (x1 , x2 , ..., xn ) o secvent a de numere ntregi. Fiind dat x, vom numi multiplicitate a lui x n X num arul de aparit ii ale lui x n X . Un element se nume ste majoritar dac a multiplicitatea sa este mai mare dec at n/2. Descriet i un algoritm liniar care s a determine elementul majoritar dintr-un sir, dac a un astfel de element exist a. 12. Fie {a1 , a2 , ..., an } si {b1 , b2 , ..., bm }, dou a mult imi de numere ntregi, nenule (m < n). S a se determine {x1 , x2 , ..., xm }, o submult ime a mult imii {a1 , a2 , ..., an } pentru care funct ia f (x1 , x2 , ..., xm ) = a1 x1 + a2 x2 + ... + an xm ia valoare maxim a, prin doi algoritmi de complexitate diferit a.

Capitolul 5

Recursivitate
Denit iile prin recurent a sunt destul de curente n matematic a: progresia aritmetic a, progresia geometric a, sirul lui Fibonacci, limite de siruri, etc.

5.1

Funct ii recursive

5.1.1

Funct ii numerice

Pentru calculul termenilor sirului lui Fibonacci, a transcriere literal a a formulei este urm atoarea: static int if (n <= return else return } fib(int n) { 1) 1; fib(n-1) + fib(n-2);

fib este o funct ie care utilizeaz a propriul nume n denit ia proprie. De asemenea, dac a argumentul n este mai mic dec at 1 returneaz a valoarea 1 iar n caz contrar returneaz a f ib(n 1) + f ib(n 2). In Java este posibil, ca de altfel n multe alte limbaje de programare (Fortran, Pascal, C, etc), s a denim astfel de funct ii recursive. Dealtfel, toate sirurile denite prin recurent a se scriu n aceast a manier a n Java, cum se poate observa din urm atoarele dou a exemple numerice: factorialul si triunghiul lui Pascal. 71

72 static int if (n != return else return } fact(int n) { 1) 1; n * fact (n1);

CAPITOLUL 5. RECURSIVITATE

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)

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 a Java calculul funct iilor recursive. Putem s a r aspundem prin urm arirea calculelor n cazul calculului lui f ibo(4). Reamintim c a argumentele sunt transmise prin valoare n acest caz, iar un apel de funct ie const a n evaluarea argumentului, apoi lansarea n execut ie a funct iei cu valoarea

5.1. FUNCT II RECURSIVE 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) 3+2 5

73

Exist a deci un num ar semnicativ de apeluri succesive ale funct iei f ib (9 apeluri pentru calculul lui f ibo(4)). S a not am prin Rn num arul apelurilor funct iei f ibo pentru calculul lui f ibo(n). Evident R0 = R1 = 1, si Rn = 1 + Rn1 + Rn2 pentru n > 1. Pun and Rn = Rn + 1, obt inem c a Rn = Rn 1 + Rn2 pentru n > 1, a Rn = 2 f ibo(n) si de aici obt inem c a Rn = 2 f ibo(n) 1. si R1 = R0 = 2. Rezult Num arul de apeluri recursive este foarte mare! Exist a o metod a iterativ a simpl a care permite calculul lui f ibo(n) mult mai repede. f ibo(n) f ibo(n 1) = 1 1 1 0 u v = f ibo(n 1) f ibo(n 2) 1 1 1 0 = ... = u0 v0 1 1 1 0
n

1 0

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

74

CAPITOLUL 5. RECURSIVITATE

Se poate calcula si mai repede folosind ultima form a si calcul and puterea matricei ... Pentru a rezuma, o regul a bun a este s a nu ncerc am s a intr am n meandrele detaliilor apelurilor recursive pentru a nt elege sensul unei funct ii recursive. In general este sufucient s a nt elegem sintetic funct ia. Funct ia lui Fibonacci este un caz particular n care calculul recursiv este foarte lung. Cam la fel se nt ampl a (dac a nu chiar mai r au!) si cu triunghiul lui Pascal. Dar nu aceasta este situat ia n general. Nu numai c a scrierea recursiv a se poate dovedi ecace, dar ea este totdeauna natural a si deci cea mai estetic a. Ea nu face dec at s a respecte denit ia matematic a prin recurent a. Este o metod a de programare foarte puternic a.

5.1.2

Funct ia lui Ackerman

S irul lui Fibonacci are o cre stere exponent ial a. Exist a funct ii recursive care au o cre stere mult mai rapid a. Prototipul este funct ia lui Ackerman. In loc s a denim matematic aceast a funct ie, este de asemenea simplu s a d am denit ia recursiv a 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)); } Se poate verica c a ack (0, n) = n + 1, ack (1, n) = n + 2, ack (2, n) 2n, ack (3, n) 2n , ack (5, 1) ack (4, 4) 265536 > 1080 , adic a num arul atomilor din univers [11].

5.1.3

Recursii imbricate

Funct ia lui Ackerman cont ine dou a apeluri recursive imbricate ceea ce determin a o cre stere rapid a. Un alt exemplu este funct ia 91 a lui MacCarty [11]: static int f(int n) { if (n > 100) return n-10; else return f(f(n+11)); }

5.2. PROCEDURI RECURSIVE Pentru aceast a funct ie, calculul lui f (96) d a f (96) = f (f (107)) = f (97) = ... = f (100) = f (f (111)) = f (101) = 91.

75

Se poate ar ata c a aceast a funct ie va returna 91 dac a n 100 si n 10 dac a n > 100. Aceast a funct ie anecdotic a, care folose ste recursivitatea imbricat a, este interesant a pentru c nu este evident c a o astfel de denit ie d a d a acela si rezultat. Un alt exemplu este funct ia lui Morris [11] care are urm atoarea form a: static int if (m == return else return } g(int m, int n) { 0) 1; g(m-1, g(m, n));

Ce valoare are g (1, 0)? Efectul acestui apel de funct ie se poate observa din denit ia ei: g (1, 0) = g (0, g (1, 0)). Se declan seaz a la nesf ar sit apelul g (1, 0). Deci, calculul nu se va termina niciodat a!

5.2

Proceduri recursive

Procedurile, la fel ca si funct iile, pot recursive si pot suporta apeluri recursive. Exemplul clasic este cel al turnurilor din Hanoi. Pe 3 tije din fat a noastr a, numerotate 1, 2 si 3 de la st anga la dreapta, sunt n discuri de dimensiuni diferite plasate pe tija 1 form and un con cu discul cel mai mare la baz a si cel mai mic n v arf. Se dore ste mutarea discurilor pe tija 3, mut and numai c ate un singur disc si neplas and niciodat a un disc mai mare peste unul mai mic. Un rat ionament recursiv permite scrierea solut iei n c ateva r anduri. Dac a n 1, problema este trivial a. Presupunem problema rezolvat a pentru mutarea a n 1 discuri de pe tija i pe tija j (1 i, j 3). Atunci, exist a o solut ie foarte simpl a pentru mutarea celor n discuri de pe tija i pe tija j : 1. se mut a primele n 1 discuri (cele mai mici) de pe tija i pe tija k = 6 i j , 2. se mut a cel mai mare disc de pe tija i pe tija j , 3. se mut a 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); } }

76

CAPITOLUL 5. RECURSIVITATE

Aceste c ateva linii de program arat a foarte bine cum generaliz and problema, adic a mutarea de pe oricare tij a i pe oricare tij aj , un program recursiv de c ateva linii poate rezolva o problem a apriori complicat a. Aceasta este fort a recursivit a tii si a rat ionamentului prin recurent a.
pasul 1

a)

b) pasul 2

pasul 3

B d)

B c)

Capitolul 6

Analiza algoritmilor recursivi


Am v azut n capitolul precedent c at de puternic a si util a este recursivitatea n elaborarea unui algoritm. Cel mai important c a stig al exprim arii recursive este faptul c a ea este natural a si compact a. Pe de alt a parte, apelurile recursive trebuie folosite cu discern am ant, deoarece solicit a si ele resursele calculatorului (timp si memorie). Analiza unui algoritm recursiv implic a rezolvarea unui sistem de recurent e. Vom vedea n continuare cum pot rezolvate astfel de recurent e.

6.1

Relat ii de recurent a

O ecuat ie n care necunoscutele sunt termenii xn , xn+1 , ...xn+k ai unui sir de numere se nume ste relat ie de recurent a de ordinul k . Aceast a ecuat ie poate satisf acut a de o innitate de siruri. Ca s a putem rezolva ecuat ia (relat ia de recurent a) mai avem nevoie si de condit ii init iale, adic a de valorile termenilor x0 , x1 , ..., xk1 . De exemplu relat ia de recurent a (n + 2)Cn+1 = (4n + 2)Cn , pentru n 0, C0 = 1 este de ordinul 1. Dac a un sir xn de numere satisface o formul a de forma a0 xn + a1 xn+1 + ... + ak xn+k = 0, k 1, ai R, a0 , ak = 0 (6.1.1)

atunci ea se nume ste relat ie de recurent a de ordinul k cu coecient i constant i. Coecient ii sunt constant i n sensul c a nu depind de valorile sirului xn . 77

78

CAPITOLUL 6. ANALIZA ALGORITMILOR RECURSIVI

O astfel de formul a este de exemplu Fn+2 = Fn+1 + Fn , F0 = 0, F1 = 1, adic a relat ia de recurent a care dene ste sirul numerelor lui Fibonacci. Ea este o relat ie de recurent a de ordinul 2 cu coecient i constant i.

6.1.1

Ecuat ia caracteristic a

G asirea expresiei lui xn care s a satisfac a relat ia de recurent a se nume ste rezolvarea relat iei de recurent a . F ac and substitut ia xn = rn obt inem urm atoarea ecuat ie, numit a ecuat ie caracteristic a: a0 + a1 r + a2 r2 + ... + ak rk = 0 (6.1.2)

6.1.2

Solut ia general a

Solut ia general a a relat iei de recurent a omogen a de ordinul k cu coecient i constant i este de forma
k

xn =
i=1 (i

i) ci x( n

(6.1.3)

(se mai numesc si sistem fundamental de solut ii). Pentru determinarea acestor solut ii distingem urm atoarele cazuri: Ecuat ia caracteristic a admite r ad acini reale si distincte n sunt Dac a r1 , r2 , ..., rk sunt r ad acini reale ale ecuat iei caracteristice, atunci ri solut ii ale relat iei de recurent a. n n relat ia de recurent a, obt inem: Intr-adev ar, introduc and expresiile ri
n+k n+2 n+1 2 k n n =0 a0 + a1 ri + a2 ri + ... + ak ri = ri + ... + ak ri + a2 ri a0 ri + a1 ri

unde xn )|i {1, 2, ..., k} sunt solut ii liniar independente ale relat iei de recurent a

Dac a r ad acinile ri (i = 1, 2, ..., k) sunt distincte, atunci relat ia de recurent a are solut ia general a n n n xn = c1 r1 + c2 r2 + ... + ck rk (6.1.4) unde coecient ii c1 , c2 , ..., ck se pot determina din condit iile init iale. Ecuat ia caracteristic a admite r ad acini reale multiple Fie r o r ad acin a multipl a de ordinul p a ecuat iei caracteristice. Atunci rn , nrn , n2 rn , ..., np1 rn sunt solut ii liniar independente ale relat iei de recurent a si xn = c1 + c2 n + ... + cp1 np1 rn (6.1.5)

6.1. RELAT II DE RECURENT A

79

este o solut ie a relat iei de recurent a. Acest lucru se mai poate demonstra u sor dac a tinem cont de faptul c a o r ad acin a multipl a de ordinul p a unui polinom P (x) este r ad acin a si a polinoamelor derivate P (x), P (x), ..., P (p1) (x). Solut ia general a este suma dintre solut ia general a corespunz atoare r ad acinilor simple ale ecuat iei caracteristice si solut ia general a corespunz atoare r ad acinilor multiple. Dac a ecuat ia caracteristic a are r ad acinile simple r1 , r2 , ..., rs si r ad acinile multiple rs1 , rs+2 , ..., rs+t de multiplicitate p1 , p2 , ..., pt (s + p1 + p2 + ... + pt = k ), atunci solut ia general a a relat iei de recurent a este xn
n n n = c1 r1 + c2 r2 + ... + cs rs + (1) (1) (1)

c1 + c2 n + ... + cp1 1 np1 1 + ... (t) (t) (1) c1 + c2 n + ... + cpt 1 npt 1 + unde c1 , ..., cs , c1 , ..., cp1 1 , ..., c1 , ..., cpt 1 sunt constante, care se pot determina din condit iile init iale. Ecuat ia caracteristic a admite r ad acini complexe simple Fie r = aeib = a(cos b + i sin b) o r ad acin a complex a. Ecuat ia caracteristic a are coecient i reali, deci si conjugata r = aeib = a(cos b i sin b) este r ad acin a pentru ecuat ia caracteristic a. Atunci solut iile corespunz atoare acestora n sistemul fundamental de solut ii pentru recurent a liniar a si omogen a sunt
n (2) n x(1) n = a cos bn, xn = a sin bn. (1) (1) (t) (t)

Ecuat ia caracteristic a admite r ad acini complexe multiple Dac a ecuat ia caracteristic a admite perechea de r ad acini complexe r = aeib , r = aeib b = 0 de ordin de multiplicitate k , atunci solut iile corespunz atoare acestora n sistemul fundamental de solut ii sunt
n (2) n (k ) k 1 n x(1) a cos bn, n = a cos bn, xn = na cos bn, ..., xn = n k+1) (k+2) (2k) x( = an sin bn, xn = nan sin bn, ..., xn = nk1 an sin bn, n

Pentru a obt ine solut ia general a a recurent ei omogene de ordinul n cu coecient i constant i se procedeaz a astfel: 1. Se determin a r ad acinile ecuat iei caracteristice 2. Se scrie contribut ia ec arei r ad acini la solut ia general a. 3. Se nsumeaz a si se obt ine solut ia general a n funct ie de n constante arbitrare. 4. Dac a sunt precizate condit iile init iale atunci se determin a constantele si se obt ine o solut ie unic a.

80

CAPITOLUL 6. ANALIZA ALGORITMILOR RECURSIVI

6.2

Ecuat ii recurente neomogene

6.2.1

O form a simpl a

Consider am acum recurent e de urm atoarea form a mai general a a0 tn + a1 tn1 + ... + ak tnk = bn p(n) unde b este o constant a, iar p(n) este un polinom n n de grad d. Ideea general a este s a reducem un astfel de caz la o form a omogen a. De exemplu, o astfel de recurent a poate : tn 2tn1 = 3n In acest caz, b = 3 si p(n) = 1. Inmult im recurent a cu 3, si obt inem 3tn 6tn1 = 3n+1 Inlocuind pe n cu n + 1 n recurent a init ial a, avem tn+1 2tn = 3n+1 Sc adem aceste dou a ecuat ii tn+1 5tn + 6tn1 = 0 Am obt inut o recurent a omogen a. Ecuat ia caracteristic a este: x2 5x + 6 = 0 adic a (x 2)(x 3) = 0. Intuitiv, observ am c a factorul (x 2) corespunde p art ii st angi a recurent ei init iale, n timp ce factorul (x 3) a ap arut ca rezultat al calculelor efectuate pentru a sc apa de partea dreapt a. Generaliz and acest procedeu, se poate ar ata c a, pentru a rezolva ecuat ia init ial a, este sucient s a lu am urm atoarea ecuat ie caracteristic a: (a0 xk + a1 xk1 + + ak )(x b)d+1 = 0 Odat a ce s-a obt inut aceast a ecuat ie, se procedeaz a ca n cazul omogen. Vom rezolva acum recurent a corespunzatoare problemei turnurilor din Hanoi: tn = 2tn1 + 1, n = 1 iar t0 = 0. Rescriem recurent a astfel tn 2tn1 = 1

6.2. ECUAT II RECURENTE NEOMOGENE

81

care este de forma general a prezentat a la nceput, cu b = 1 si p(n) = 1. Ecuat ia caracteristic a este atunci (x 2)(x 1) = 0, cu solut iile 1 si 2. Solut ia general aa recurent ei este: tn = c1 1n + c2 2n Avem nevoie de dou a condit ii init iale. S tim c a t0 = 0; pentru a g asi cea de-a doua condit ie calcul am t1 = 2t0 + 1 = 1. Din condit iile init iale, obt inem tn = 2n 1. Dac a ne intereseaz a doar ordinul lui tn , nu este necesar s a calcul am efectiv constantele n solut ia general a. Dac a stim c a tn = c1 1n + c2 2n , rezult a tn O(2n ). Din faptul c a num arul de mut ari a unor discuri nu poate negativ sau constant, deoarece avem n mod evident tn n, deducem c a c2 > 0. Avem atunci tn (2n ) si deci, tn (2n ). Putem obt ine chiar ceva mai mult. Substituind solut ia general a napoi n recurent a init ial a, g asim 1 = tn 2tn1 = c1 + c2 2n 2(c1 + c2 2n1 ) = c1 Indiferent de condit ia init ial a, c1 este deci 1.

6.2.2

O form a mai general a

O ecuat ie recurent a neomogen a de form a mai general a este:


k j =0 n aj T n j = bn 1 pd1 (n) + b2 pd2 (n) + ...

n care pd (n) = nd + c1 nd1 + ... + cd Ecuat ia caracteristic a complet a este:


k

Exemplul 3: Tn = 2T (n 1) + n + 2n , n 1, T0 = 0. Acestui caz i corespund b1 = 1, p1 (n) = n, d1 = 1 si b2 = 2, p2 (n) = 1, d2 = 0, iar ecuat ia caracteristic a complet a este: (r 2)(r 1)2 (r 2) = 0

j =0

aj rkj (r b1 )

d1 +1

(r b2 )

d2 +1

... = 0

82 cu solut ia:

CAPITOLUL 6. ANALIZA ALGORITMILOR RECURSIVI

T (n) = c1 1n + c2 n 2n + c3 2n + c4 n 2n

T (0) = 0 T (1) = 2T (0) + 1 + 21 = 3 T (2) = 2T (1) + 2 + 22 = 12 T (3) = 2T (2) + 3 + 23 = 35 Deci

c1 + c3 c + c + 2c + 2c 1 2 3 4 c + 2 c + 4 c + 8 c4 1 2 3 c1 + 3c2 + 8c3 + 24c4

=0 c1 =3 c2 = 12 c 3 = 35 c4

= 2 = 1 =2 =1

T (n) = 2 n + 2n+1 + n 2n = O(n 2n ).

6.2.3

Teorema master

De multe ori apare relat ia de recurent a de forma T (n) = aT (n/b) + f (n) (6.2.6)

unde a si b sunt constante iar f (n) este o funct ie (aplicarea metodei Divide et Impera conduce de obicei la o astfel de ecuat ie recurent a). A sa numita teorem a Master d a o metod a general a pentru rezolvarea unor astfel de recurent e c and f (n) este un simplu polinom. Solut ia dat a de teorema master este: 1. dac a f (n) = O nlogb (a) cu > 0 atunci T (n) = nlogb a 2. dac a f (n) = nlogb a atunci T (n) = nlogb a lg n 3. dac a f (n) = nlogb (a+) si a f
n b

Din p acate, teorema Master nu funct ioneaz a pentru toate funct iile f (n), si multe recurent e utile nu sunt de forma (6.2.6). Din fericire ns a, aceasta este o tehnic a de rezolvare a celor mai multe relat ii de recurent a provenite din metoda Divide et Impera.

c f (n) cu c < 1 atunci T (n) = (f (n)).

Pentru a rezolva astfel de ecuat ii recurente vom reprezenta arborele generat de ecuat ia recursiv a. R ad acina arborelui cont ine valoarea f (n), si ea are noduri descendente care sunt noduri r ad acin a pentru arborele provenit din T (n/b).

6.2. ECUAT II RECURENTE NEOMOGENE

83

Pe nivelul i se a a nodurile care cont in valoarea ai f (n/bi ). Recursivitatea se opre ste c and se obt ine un caz de baz a pentru recurent a. Presupunem c a T (1) = f (1). Cu aceast a reprezentare este foarte clar c a T (n) este suma valorilor din nodurile arborelui. Presupun and c a ecare nivel este plin, obt inem T (n) = f (n) + af (n/b) + a2 f (n/b2 ) + a3 f (n/b3 ) + ... + ak f (n/bk ) unde k este ad ancimea arborelui de recursivitate. Din n/bk = 1 rezult a k = logb n. Ultimul termen diferit de zero n sum a este de forma ak = alogb n = nlogb a (ultima egalitate ind nt alnit a n liceu!). Acum putem u sor enunt a si demonstra teorema Master. Teorema 1 (Teorema Master) Relat ia de recurent a T (n) = aT (n/b)+ f (n) are urm atoarea solut ie: dac a af (n/b) = f (n) unde < 1 atunci T (n) = (f (n)); dac a af (n/b) = f (n) unde > 1 atunci T (n) = (nlogb a ); dac a af (n/b) = f (n) atunci T (n) = (f (n) logb n); Demonstrat ie: Dac a f (n) este un factor constant mai mare dec at f (b/n), atunci prin induct ie se poate ar ata c a suma este a unei progresii geometrice descresc atoare. Suma n acest caz este o constant a nmult it a cu primul termen care este f (n). Dac a f (n) este un factor constant mai mic dec at f (b/n), atunci prin induct ie se poate ar ata c a suma este a unei progresii geometrice cresc atoare. Suma n acest caz este o constant a nmult it a cu ultimul termen care este nlogb a .

84

CAPITOLUL 6. ANALIZA ALGORITMILOR RECURSIVI

Dac a af (b/n) = f (n), atunci prin induct ie se poate ar ata c a ecare din cei k + 1 termeni din sum a sunt egali cu f (n). Exemple. 1. Select ia aleatoare: T (n) = T (3n/4) + n. Aici af (n/b) = 3n/4 iar f (n) = n, rezult a = 3/4, deci T (n) = (n). 2. Algoritmul de multiplicare al lui Karatsuba: T (n) = 3T (n/2) + n. Aici af (n/b) = 3n/2 iar f (n) = n, rezult a = 3/2, deci T (n) = (nlog2 3 ). 3. Mergesort: T (n) = 2T (n/2) + n. Aici af (n/b) = n, iar f (n) = n, rezult a = 1 deci T (n) = (n log2 n). Folosind accea si tehnic a a arborelui recursiv, putem rezolva recurent e pentru care nu se poate aplica teorema Master.

6.2.4

Transformarea recurent elor

La Mergesort am avut o relai e de recurent a de forma T (n) = 2T (n/2) + n si am obt inut solut ia T (n) = O(n log2 n) folosind teorema Master (metoda arborelui de recursivitate). Aceast a modalitate este corect a dac a n este o putere a lui 2, dar pentru alte valori ale lui n aceast a recurent a nu este corect a. C and n este impar, recurent a ne cere s a sort am un num ar elemente care nu este ntreg! Mai r au chiar, dac a n nu este o putere a lui 2, nu vom atinge niciodat a cazul de baz a T (1) = 0. Pentru a obt ine o recurent a care s a e valid a pentru orice valori ntregi ale lui n, trebuie s a determin am cu atent ie marginile inferioar a si superioar a: T (n) = T (n/2) + T (n/2) + n. Metoda transform arii domeniului rescrie funct ia T (n) sub forma S (f (n)), unde f (n) este o funct ie simpl a si S () are o recurent a mai u soar a. Urm atoarele inegalit a ti sunt evidente: T (n) 2T (n/2) + n 2T (n/2 + 1) + n. Acum denim o nou a funct ie S (n) = T (n + ), unde este o constant a necunoscut a, aleas a astfel nc at s a e satisf acut a recurent a din teorema Master S (n) S (n/2) + O(n). Pentru a obt ine valoarea corect a a lui , vom compara dou a versiuni ale recurent ei pentru funct ia S (n + ): S (n) 2S (n/2) + O(n) T (n) 2T (n/2 + 1) + n T (n + ) 2T (n/2 + ) + O(n) T (n + ) 2T ((n + )/2 + 1) + n +

Pentru ca aceste dou a recurent e s a e egale, trebuie ca n/2+ = (n+)/2+1, care implic a = 2. Teorema Master ne spune acum c a S (n) = O(n log n), deci T (n) = S (n 2) = O((n 2) log(n 2) = O(n log n).

6.2. ECUAT II RECURENTE NEOMOGENE

85

Un argument similar d a o ajustare a marginii inferioare T (n) = (n log n). Deci, T (n) = (n log n) este un rezultat ntemeiat de si am ignorat marginile inferioar a si superioar a de la nceput! Transformarea domeniului este util a pentru nl aturarea marginilor inferioar a si superioar a, si a termenilor de ordin mic din argumentele oric arei recurent e care se potrive ste un pic cu teorema master sau metoda arborelui de recursivitate. Exist a n geometria computat ional a o structur a de date numit a arbore pliat, pentru care costul operatiei de c autare ndepline ste relat ia de recurent a T (n) = T (n/2) + T (n/4) + 1. Aceasta nu se potrive ste cu teorema master, pentru c a cele dou a subprobleme au dimensiuni diferite, si utiliz and metoda arborelui de recursivitate nu obt inem dec at ni ste margini slabe n << T (n) << n. Dac a nu au forma standard, ecuat iile recurente pot aduse la aceast a form a printr-o schimbare de variabil a. O schimbare de variabil a aplicabil a pentru ecuat ii de recurent a de tip multiplicativ este: n = 2k k = log n De exemplu, e T (n) = 2 T (n/2) + n log n, n > 1 Facem schimbarea de variabil a t(k ) = T (2k ) si obt inem: t(k ) 2 t(k 1) = k 2k , deci b = 2, p(k ) = k, d = 1 Ecuat ia caracteristic a complet a este: (r 2)3 = 0 cu solut ia t(k ) = c1 2k + c2 k 2k + c3 k 2 2k Deci T (n) = c1 n + c2 n log n + c3 n log2 n O(n log2 n|n = 2k ) Uneori, printr-o schimbare de variabil a, putem rezolva recurent e mult mai complicate. In exemplele care urmeaz a, vom nota cu T (n) termenul general al recurent ei si cu tk termenul noii recurent e obt inute printr-o schimbare de variabil a. Presupunem pentru nceput c a n este o putere a lui 2. Un prim exemplu este recurenta T (n) = 4T (n/2) + n, n > 1

86

CAPITOLUL 6. ANALIZA ALGORITMILOR RECURSIVI

n care nlocuim pe n cu 2k , notam tk = T (2k ) = T (n) si obt inem tk = 4tk1 + 2k Ecuat ia caracteristic a a acestei recurent e liniare este (x 4)(x 2) = 0 si deci, tk = c1 4k + c2 2k . Inlocuim la loc pe k cu log2 n T (n) = c1 n2 + c2 n Rezult a T (n) O(n2 |n este o putere a lui 2) Un al doilea exemplu l reprezint a ecuat ia T (n) = 4T (n/2) + n2 , n > 1 Proced and la fel, ajungem la recurenta tk = 4tk1 + 4k cu ecuat ia caracteristic a si solut ia general a tk = c1 42 + c2 k 42 . Atunci, T (n) = c1 n2 + c2 n2 lg n si obt inem In sf ar sit, s a consider am si exemplul T (n) O(n2 log n|n este o putere a lui 2) T (n) = 3T (n/2) + cn, n > 1 c ind o constant a. Obt inem succesiv T (2k ) = 3T (2k1 ) + c2k tk = 3tk1 + c2k cu ecuat ia caracteristic a tk = c1 3k + c2 2k T (n) = c1 3lg n + c2 n (x 3)(x 2) = 0 (x 4)2 = 0

6.3. PROBLEME REZOLVATE si, deoarece alg b = blg a obt inem T (n) = c1 nlg 3 + c2 n deci, T (n) O(nlg 3 |n este o putere a lui 2)

87

Putem enunt a acum o proprietate care este util a ca ret et a pentru analiza algoritmilor cu recursivit a ti de forma celor din exemplele precedente. Fie T : N R+ o funct ie eventual nedescresc atoare T (n) = aT (n/b) + cnk , n > n0 unde: n0 1, b 2 si k 0 sunt ntregi; a este o putere a lui b. Atunci avem k (n ), T (n) (nk log n), (nlogb a ), si c sunt numere reale pozitive; n/n0 pentru a < bk ; pentru a = bk ; pentru a > bk ;

6.3

Probleme rezolvate

1. S a se rezolve ecuat ia: Fn+2 = Fn+1 + Fn , F0 = 0, F1 = 1. Ecuat aia caracteristic a corespunz atoare r2 r 1 = 0 are solut iile 1 5 1+ 5 , r2 = . r1 = 2 2 1+ 5 2
n

Solut ia general a este Fn = c1 + c2 1 5 2


n

Determin am constantele c1 si c2 din condit iile init iale F0 = 0 si F1 = 1. Rezolv and sistemul c1 + c2 = 0 c1
1+ 5 2

+ c2

1 5 2

=1

88

CAPITOLUL 6. ANALIZA ALGORITMILOR RECURSIVI

1 1 obt inem c1 = si c1 = . 5 5 Deci, solut ia relat iei de recurent a care dene ste numerele lui Fibonacci este: n n 1 1+ 5 1 5 1 Fn = 2 2 5 5

2. S a se rezolve relat ia de recurent a: xn+3 = xn+2 + 8xn+1 12xn , Ecuat ia caracteristic a corespunz atoare este: r3 r2 8r + 12 = 0 si are solut iile: r1 = r2 = 2 si r3 = 3. Solut ia general a este de forma: xn = (c1 + nc2 )2n + c3 (3)n . Din condit iile init iale rezult a constantele: c1 = 1 5 , c2 = Solut ia general a este: xn = n 1 + 2 5 1 2n (3)n . 5
1 2

x0 = 0, x1 = 2, x2 = 3.

si c3 = 1 5.

3. S a se rezolve relat ia de recurent a: xn+3 = 6xn+2 12xn+1 + 8xn , Ecuat ia caracteristic a corespunz atoare este: r3 6r2 + 12r 8 = 0 si are solut iile: r1 = r2 = r3 = 2. Solut ia general a este de forma: xn = (c1 + c2 n + c3 n2 )2n . Din condit iile init iale rezult a constantele: c1 = 0, c2 = Solut ia general a este: xn =
3 2

x0 = 0, x1 = 2, x2 = 4.

si c3 = 1 2.

3 1 n n2 2n = (3n n2 )2n1 . 2 2

4. S a se rezolve relat ia de recurent a: xn+2 = 2xn+1 2xn , x0 = 0, x1 = 1.

6.3. PROBLEME REZOLVATE Ecuat ia caracteristic a corespunz atoare este: r2 2r + 2 = 0

89

si are solut iile: r1 = 1 + i si r2 = 1 i care se pot scrie sub form a trigonometric a astfel: r1 = 2 cos + i sin , r2 = 2 cos i sin . 4 4 4 4 Solut iile fundamentale sunt: x(1) n = 2
n

cos

n (2) 2 , xn = 4

sin

n . 4

Solut ia general a este de forma: xn = 2


n

c1 cos

n n . + c2 sin 4 4

Din condit iile init iale rezult a constantele: c1 = 0 si c2 = 1. Solut ia general a este: xn = 2
n

sin

n . 4

5. S a se rezolve relat ia de recurent a: xn+3 = 4xn+2 6xn+1 + 4xn , Ecuat ia caracteristic a corespunz atoare este: r3 4r2 + 6r 4 = 0 si are solut iile: r1 = 2, r2 = 1 + i si r3 = 1 i. Solut ia general a este de forma: xn = c1 2n + c2 2
n

x0 = 0, x1 = 1, x2 = 1.

cos

n 2 + c3 4

sin

n . 4

3 1 , c2 = 1 Din condit iile init iale rezult a constantele: c1 = 2 2 si c3 = 2 . Solut ia general a este: n 2 n n n1 xn = 2 + cos . + 3 sin 2 4 4

6. S a se rezolve relat ia de recurent a: T (n) 3T (n 1) + 4T (n 2) = 0, n 2, T (0) = 0, T (1) = 1.

90

CAPITOLUL 6. ANALIZA ALGORITMILOR RECURSIVI Ecuat ia caracteristic a r2 3r + 4 = 0 are solut iile r1 = 1, r2 = 4, deci T (n) = c1 (1)n + c2 4n Constantele se determin a din condit iile init iale: c1 + c2 = 0 c1 + 4c2 = 1
1 c1 = 5 c2 = 1 5

Solut ia este: T (n) =

1 n [4 (1)n ] . 5

7. S a se rezolve relat ia de recurent a: T (n) = 5T (n 1) 8T (n 2) + 4T (n 3), n 3, cu T (0) = 0, T (1) = 1, T (2) = 2. Ecuat ia caracteristic a: r3 5r2 + 8r 4 = 0 r1 = 1, r2 = r3 = 2 deci T (n) = c1 1n + c2 2n + c3 n2n Determinarea constantelor c1 + c2 c1 + 2c2 + 2c3 c1 + 4c2 + 8c3 Deci T (n) = 2 + 2n+1

n n 2 = 2n+1 n2n1 2. 2

=0 c1 = 2 = 1 c2 = 2 1 =2 c3 = 2

8. S a se rezolve relat ia de recurent a: T (n) = 4T (n/2) + n lg n. In 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 arginit a si inferior si superior de c atre serii geometrice cresc atoare, deci solut ia este T (n) = (nlog2 4 ) = (n2 ). Acest truc nu merge n cazurile doi si trei ale teoremei Master. 9. S a se rezolve relat ia de recurent a: T (n) = 2T (n/2) + n lg n.

6.3. PROBLEME REZOLVATE

91

Nu putem aplica teorema Master pentru c a af (n/b) = n/(lg n 1) nu este egal a cu f (n) = n/ lg n, iar diferent a nu este un factor constant. Trebuie s a calcul am suma pe ecare nivel si suma total a n alt mod. Suma tuturor nodurilor de pe nivelul i este n/(lg n i). In particular, aceasta nseamn a c a ad ancimea arborelui este cel mult lg n 1. T (n) =
lg n1 i=0

n = lg n i

lg n

j =1

n = nHlg n = (n lg lg n). j

10. (Quicksort aleator). S a se rezolve relat ia de recurent a: T (n) = T (3n/4) + T (n/4) + n. In acest caz nodurile de pe acela si nivel al arborelui de recursivitate au diferite valori. Nodurile din orice nivel complet (adic a, deasupra oric arei frunze) au suma n, deci este la fel ca n ultimul caz al teoremei Master si orice frunz a are nivelul ntre log4 n si log4/3 n. Pentru a obt ine o margine superioar a, vom supraevalua T (n) ignor and cazurile de baz a si extinz and arborele n jos c atre nivelul celei mai ad anci frunze. Similar, pentru a obt ine o margine inferioar a pentru T (n), vom subevalua T (n) contoriz and numai nodurile din arbore p an a la nivelul frunzei care este cea mai put in ad anc a. Aceste observat ii ne dau marginile inferioar a si superioar a: n log4 n T (n) n log4/3 n. Deoarece aceste margini difer a numai printr-un factor constant, avem c a T (n) = (n log n). 11. (Select ie determinist a). S a se rezolve relat ia de recurent a: T (n) = T (n/5) + T (7n/10) + n. Din nou, avem un arbore recursiv trunchiat. Dac a ne uit am numai la nivelurile complete ale arborelui, observ am c a suma pe nivel formeaz a o serie geometric a descresc atoare T (n) = n + 9n/10 + 81n/100 + ..., deci este ca n primul caz al teoremei Master. Putem s a obt inem o margine superioar a ignor and cazurile de baz a n totalitate si cresc and arborele spre innit, si putem obt ine o margine inferioar a contoriz and numai nodurile din nivelurile complete. In ambele situat ii, seriile geometrice sunt majorate de termenul cel mai mare, deci T (n) = (n). 12. S a se rezolve relat ia de recurent a: T (n) = 2 n T ( n) + n. Avem cel mult lg lg n niveluri dar acum avem nodurile de pe nivelul i care au suma 2i n. Avem o serie geometric a cresc atoare a sumelor nivelurilor, la fel ca

92

CAPITOLUL 6. ANALIZA ALGORITMILOR RECURSIVI

n cazul doi din teorema Master, deci T (n) este majorat a de suma nivelurilor cele mai ad anci. Se obt ine: T (n) = (2lg lg n n) = (n log n). 13. S a se rezolve relat ia de recurent a: T (n) = 4 n T ( n) + n. Suma nodurilor de pe nivelul i este 4i n. Avem o serie geometric a cresc atoare, la fel ca n cazul doi din teorema master, deci nu trebuie dec at s a avem grij a de aceste niveluri. Se obt ine T (n) = (4lg lg n n) = (n log2 n).

Capitolul 7

Algoritmi elementari
7.1 Operat ii cu numere

7.1.1

Minim si maxim

S a presupunem c a dorim s a determin am valorile minim a si maxim a dintru-un vector x[1..n. Proced am 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 comparat ii. Se poate mai repede? Da! Imp art im sirul n dou a si determin am vmin si vmax n cele dou a zone. Compar am vmin1 cu vmin2 si stabilim vminm. La fel pentru vmax. Prelucrarea se repet a pentru cele dou a zone (deci se folose ste recursivitatea). Apar c ate dou a comparat ii n plus de ecare dat a. Dar c ate sunt n minus? Presupunem c a n este o putere a lui 2 si T (n) este num arul de comparat ii. Atunci T (n) = 2T (n/2) + 2 si T (2) = 1. Cum rezolv am aceast a relat ie de recurent a? B anuim c a solut ia este de forma T (n) = an + b. Atunci a si b trebuie s a satisfac a sistemul de ecuat ii 2a + b = 1 an + b = 2(an/2 + b) + 2 93

94

CAPITOLUL 7. ALGORITMI ELEMENTARI

care are solut ia b = 2 si a = 3/2, deci (pentru n putere a lui 2), T (n) = 3n/2 2, adic a 75% din algoritmul anterior. Se poate demonstra c a num arul de comparat ii este 3 n/2 2 pentru a aa minimum si maximum. O idee similar a poate aplicat a pentru varianta secvent ial a. Presupunem c a sirul are un num ar 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 iterat ie necesit a trei comparat ii, iar init ializarea variabilelor necesit a o comparat ie. Ciclul se repet a de (n 2)/2 ori, deci avem un total de 3n/2 2 comparat ii pentru n par.

7.1.2

Divizori

Fie n un num ar natural. Descompunerea n facori primi


k 2 1 n = p 1 p2 ... pk

(7.1.1)

se nume ste descompunere canonic a. Dac a not am prin d(n) num arul divizorilor lui n N, atunci: d(n) = (1 + 1 )(1 + 2 )...(1 + k ) (7.1.2) Pentru calculul lui d(n) putem folosi urm atorul 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;}

7.2. ALGORITMUL LUI EUCLID p=p*(1+nd); d=d+2; } if(n!=1) p=p*2; return p; }

95

7.1.3

Numere prime

Pentru testarea primalit a ti unui num ar putem folosi urm atorul 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; }

7.2

Algoritmul lui Euclid

7.2.1

Algoritmul clasic

Un algoritm pentru calculul celui mai mare divizor comun (cmmdc) a dou a numere naturale poate descompunerea lor n factori si calculul produsului tuturor divizorilor comuni. De exemplu dac a a = 1134 = 2 3 3 3 3 7 si b = 308 = 2 2 7 11 atunci cmmdc(a, b) = 2 7 = 14. Descompunerea n factori a unui num ar natural n poate necesita ncercarea tuturor numerelor naturale din intervalul [2, n]. Un algoritm ecient pentru calculul cmmdc(a, b) este algoritmul lui Euclid. 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; }

96 Pentru a = 1134 si a0 = 1134, a1 = 308, a2 = 210, a3 = 98,

CAPITOLUL 7. ALGORITMI ELEMENTARI b = 308 se obt ine: b0 = 308; b1 = 210; b2 = 98; b3 = 14.

Lema 1 cmmdc(a x b, b) = cmmdc(a, b). Demonstrat ie: Pentru nceput ar at am c a cmmdc(a x b, b) >= cmmdc(a, b). Presupunem c a d divide a s b, deci a = c1 d si b = c2 d. Atunci d divide a x b pentru c a a x b = (c1 x c2 ) d. Demonstr am si inegalitatea contrar a cmmdc(a x b, b) <= cmmdc(a, b). Presupunem c a d divide a x b si b, deci a x b = c3 d si b = c2 d. Atunci d divide a pentru c a a = (a x b) + x b = (c3 + x c2 ) d. De aici rezult a c a cmmdc(b, c) = cmmdc(c, b) = cmmdc(a mod b, b) = gcd(a, b). Prin induct ie rezult a c a cel mai mare divizor comun al ultimelor dou a numere este egal cu cel mai mare divizor comun al primelor dou a numere. Dar pentru cele dou a numere a si b din nal, cmmdc(a, b) = b, pentru c a b divide a.

7.2.2

Algoritmul lui Euclid extins

Pentru orice dou a numere intregi pozitive, exist ax si y (unul negativ) astfel nc at x a + y b = cmmdc(a, b). Aceste numere pot calculate parcurg and napoi algoritmul clasic al lui Euclid. Fie ak si bk valorile lui a si b dup a k iterat ii ale buclei din algoritm. Fie xk si y k numerele care indeplinesc relat ia xk ak + yk bk = cmmdc(ak , bk ) = cmmdc(a, b). Prin inductie presupunem c a xk s yk exist a, pentru c a la sf ar sit, c and bk divide ak , putem lua xk = 0 s yk = 1. Presupun and c a xk si yk sunt cunoscute, putem calcula xk1 s i y k 1 . ak = bk1 si bk = ak1 mod bk1 = ak1 dk1 bk1 , unde dk1 = ak1 /bk1 ( mp art ire ntreag a). Substituind aceste expresii pentru ak si bk obt inem cmmdc(a, b) = xk ak + yk bk = xk bk1 + yk (ak1 dk1 bk1 ) = yk ak1 + (xk yk dk1 ) bk1 . Astfel, tin and cont de relat ia xk1 ak1 + yk1 bk1 = cmmdc(a, b), obt inem xk1 = yk , yk1 = xk yk dk1 . Pentru 1134 si 308, obt 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.

7.3. OPERAT II CU POLINOAME

97

si de asemenea, valorile pentru xk si y k : x3 = 0, y3 = 1; x2 = 1, y2 = 012 = 2; x1 = 2, y1 = 1+21 = 3; x0 = 3, y1 = 2 3 3 = 11. Desigur relat ia 3 1134 11 308 = 14 este corect a. Solut ia nu este unic a. S a observ am c a (3 + k 308) 1134 (11 + k 1134) 308 = 14, pentru orice k , ceea ce arat a c a valorile calculate pentru x = x0 si y = y0 nu sunt unice.

7.3

Operat ii cu polinoame

Toate operat iile cu polinoame obi snuite se fac utiliz and siruri de numere care reprezint a coecient ii polinomului. Not am cu a si b vectorii coecient ilor polinoamelor cu care se opereaz a si cu m si n gradele lor. Deci a(X ) = am X m + ... + a1 X + a0 si b(X ) = bn X n + ... + b1 X + b0 .

7.3.1

Adunarea a dou a polinoame

Este asem an atoare 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; } }

98

CAPITOLUL 7. ALGORITMI ELEMENTARI

7.3.2

Inmult irea a dou a 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 si 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; }

7.3.3

Calculul valorii unui polinom

Valoarea unui polinom se calculeaz a 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; m=a.length-1; val=a[m]; for(i=m-1;i>=0;i--) val=val*x+a[i]; return val; }

7.3.4
Fie

Calculul derivatelor unui polinom

b(X ) = bn X n + bn1 X n1 + ... + b1 X + b0 derivata de ordinul 1 a polinomului a(X ) = am X m + am1 X m1 + ... + a1 X + a0 .

7.3. OPERAT II CU POLINOAME Dar a (X ) = m am X m1 + (m 1) am1 X m2 + ... + 2 a2 X + a1 . Rezult a c a n=m1 si bi = (i + 1) ai+1 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; }

99

Pentru calculul valorii v = a (x) a derivatei polinomului a n x este sucient apelul v=valp(derivp(a),x);. Dac a vrem s a calcul am 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; } Pentru calculul valorii v = a(k) (x) a derivatei de ordinul k a polinomului a n x este sucient apelul v=valp(derivpk(a,k),x);.

100

CAPITOLUL 7. ALGORITMI ELEMENTARI

7.4

Operat ii cu mult imi

O mult ime A se poate memora ntr-un vector a, ale c arui elemente sunt distincte. Folosind vectorii putem descrie operat iile cu mult imi.

7.4.1

Apartenent a la mult ime

Testul de apartenent a a unui element x la o multime A, este prezentat n algoritmul urm ator: 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; }

7.4.2

Diferent a a dou a mult imi

Diferent a a dou a mult imi este dat a de mult imea C = A B = {x|x A, x / B} Not am 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; } }

7.4. OPERAT II CU MULT IMI

101

7.4.3

Reuniunea si intersect ia a dou a mult imi

Reuniunea a dou a mult imi este multimea: C = A B = A (B A). Introducem n C toate elementele lui A si 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; } } Intersect ia a dou a mult imi este multimea: C = A B = {x|x A si x B } 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; } }

7.4.4

Produsul cartezian a dou a mult imi

102

CAPITOLUL 7. ALGORITMI ELEMENTARI Produs cartezian a doua multimi este multimea: A B = {(x, y )|x A si y B }

Putem stoca produsul cartezian sub forma unei matrice C cu dou a linii si m n coloane. Fiecare coloan a a matricei cont ine c ate 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 = {1, 2, 3, 4} si B = {1, 2, 3}, matricea C este linia 0 linia 1 0 1 1 1 1 2 2 1 3 3 2 1 4 2 2 5 2 3 6 3 1 7 3 2 8 3 3 9 4 1 10 4 2 11 4 3

7.4.5

Generarea submult imilor unei mult imi

Generarea submult imilor unei multimi A = {a1 , a2 , ..., an }, este identic a cu generarea submult imilor mult imii de indici {1, 2, ..., n}. O submult ime se poate memora sub forma unui vector cu n componente, unde ecare component a poate avea valori 0 sau 1. Componenta i are valoarea 1 dac a elementul ai apart ine submult imii si 0 n caz contrar. O astfel de reprezentare se nume ste reprezentare prin vector caracteristic. Generarea tuturor submult imilor nseamn a generarea tuturor combinat iilor de 0 si 1 care pot ret inute de vectorul caracteristic V , adic a a tuturor numerelor n baza 2 care se pot reprezenta folosind n cifre. Pentru a genera adunarea n binar, tinem cont c a trecerea de la un ordin la urm atorul se face c and se obt ine suma egal a cu 2, adic a 1 + 1 = (10)2 . pozit ia 1 2 3 4 De exemplu, pentru n = 4, vom folosi un vector v valoarea

7.4. OPERAT II CU MULT IMI init ial 0 0 0 0 0 0 0 0 1 1 0 0 0 1 1 1 2 0 1 0 si adun am 1 1 si adun am 1

103

obt inem 0 obt inem 0 obt inem 0 obt inem 0 obt inem 0 obt inem 0 obt inem 0 obt inem obt inem 1

2 care nu este permis, si trecem la ordinul urm ator 0 si adun am 1 1 si adun am 1 2 care nu este permis, si trecem la ordinul urm ator 0 care nu este permis, si trecem la ordinul urm ator 0 si a sa mai departe p an a c and 1

Aceste rezultate se pot ret ine ntr-o matrice cu n linii si 2n coloane. a1 a2 a3 a4 0 0 0 0 0 1 0 0 0 1 2 0 0 1 0 3 0 0 1 1 4 0 1 0 0 5 0 1 0 1 6 0 1 1 0 7 0 1 1 1 8 1 0 0 0 9 1 0 0 1 A 1 0 1 0 B 1 0 1 1 C 1 1 0 0 D 1 1 0 1 E 1 1 1 0 F 1 1 1 1 0 1 2 3

Ultima coloan a cont ine num arul liniei din matrice. Coloana 0 reprezint a mult imea vid a, coloana F reprezint a ntreaga mult ime, si, de exemplu, coloana 5 reprezint a submultimea {a2 , a4 } iar coloana 7 reprezint a submultimea {a2 , a3 , a4 }. 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; }

104

CAPITOLUL 7. ALGORITMI ELEMENTARI

7.5

Operat ii cu numere ntregi mari

Operat iile aritmetice sunt denite numai pentru numere reprezentate pe 16, 32 sau 64 bit i. Dac a numerele sunt mai mari, operat iile trebuie implementate de utilizator.

7.5.1

Adunarea si sc aderea

Adunarea si sc aderea sunt directe: aplic and metodele din scoala elementar a. static int[] suma(int[] x, int[] y) { int nx=x.length; 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; } }

7.5. OPERAT II CU NUMERE INTREGI MARI

105

7.5.2

Inmult irea si mp artirea

Metoda nv a tat a n soal a este corect a. 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; 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; } }

106

CAPITOLUL 7. ALGORITMI ELEMENTARI

7.5.3

Puterea

Presupunem c a vrem s a calcul am xn . Cum facem acest lucru? Este evident c a urm atoarea secvent a funct ioneaz a: for (p = 1, i = 0; i < n; i++) p *= x; Presupun and c a toate nmult irile sunt efectuate ntr-o unitate de timp, acest algoritm are complexitatea O(n). Totu si, putem s a facem acest lucru mai repede! Presupun and, pentru nceput, c a n = 2k , urm atorul algoritm este corect: for (p = x, i = 1; i < n; i *= 2) p *= p; Aici num arul de treceri prin ciclu este egal cu k = log2 n. Acum, s a consider am cazul general. Presupunem c a n are expresia binar a (bk , bk1 , ..., b1 , b0 ). Atunci putem scrie
k

n=
i=0,bi =1

2i .

Deci,
k

xn =
i=0,bi =1

x2 .

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

7.6. OPERAT II CU MATRICE int exponent_3(int x, int n) { int y; if (n == 0) return 1; if (n & 1) /* n este impar */ return x * exponent_3(x, n - 1); y = exponent_3(x, n / 2); return y * y; }

107

7.6

Operat ii cu matrice

7.6.1

Inmult irea

O funct ie scris a 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]; } }

7.6.2

Inversa unei matrice

O posibilitate este cea din scoal a. Aceasta presupune calculul unor determinant i. Determinantul det(A) se dene ste recursiv astfel: det(A) =
n1 i=0

(1)i+j ai,j det(Ai,j ).

unde ai,j este element al matricei iar Ai,j este submatricea obt inut a prin eliminarea liniei i si a coloanei j .

108

CAPITOLUL 7. ALGORITMI ELEMENTARI

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; } } Folosind determinant i, inversa matricei se poate calcula folosind regula lui Cramer. Presupunem c a A este inversabil a si e B = (bi,j ) matricea denit a prin bi,j = (1)i+j det(Ai,j )/ det(A). Atunci A1 = B T , unde B T este transpusa matricei B .

Capitolul 8

Algoritmi combinatoriali
8.1 Principiul includerii si al excluderii si aplicat ii

8.1.1

Principiul includerii si al excluderii

Fie A si B dou a mult imi nite. Not am prin |A| cardinalul mult imii A. Se deduce u sor c a: |A B | = |A| + |B | |A B |. Fie A o mult ime nit a si A1 , A2 , ..., An submult imi ale sale. Atunci num arul elementelor lui A care nu apar n nici una din submult imile Ai (i = 1, 2, ..., n) este egal cu:
n

|A|

i=1

|Ai |+

1i<j n

|Ai Aj |

1i<j<kn

|Ai Aj Ak |+...+(1)n |A1 A2 ...An |

Se pot demonstra prin induct ie matematic a urm atoarele formule:


n n n

i=1 n

Ai | =

i=1 n

|Ai |

1i<j n

|Ai Aj |+

1i<j<kn

|Ai Aj Aj |...+(1)n+1 |

i=1 n

Ai |

i=1

Ai | =

i=1

|Ai |

1i<j n

|Ai Aj |+

1i<j<kn

|Ai Aj Aj |...+(1)n+1 |

i=1

Ai |

109

110

CAPITOLUL 8. ALGORITMI COMBINATORIALI

8.1.2

Num arul funct iilor surjective

Se dau mult imile X = {x1 , x2 , ..., xm } si Y = {y1 , y2 , ..., yn }. Fie Sm,n num arul funct iilor surjective f : X Y . Fie A = {f |f : X Y } (mult imea tuturor funct iilor denite pe X cu valori n Y ) si Ai = {f |f : X Y, yi / f (X )} (mult imea funct iilor pentru care yi nu este imaginea nici unui element din X ). Atunci
n

Sm,n = |A| |

i=1

Ai |

Folosind principiul includerii si al excluderii, obt inem


n

Sm,n = |A|

i=1

|Ai | +

1i<j n

|Ai Aj | ... + (1)n |A1 A2 ... An |

etc.

Se poate observa u sor c a |A| = nm , |Ai | = (n 1)m , |Ai Aj | = (n 2)m ,


k moduri, deci Din Y putem elimina k elemente n Cn k 1i1 <i2 <...<ik n j =1 k (n k )m Aij | = Cn

Rezult a:
1 2 n1 Sm,n = nm Cn (n 1)m + Cn (n 2)m + ... + (1)n1 Cn

Observat ii: 1. Deoarece A1 A2 ... An = si pentru c a nu poate exista o funct ie care s a nu ia nici o valoare, ultimul termen lipse ste. 2. Dac a n = m atunci num arul funct iilor surjective este egal cu cel al funct iilor injective, deci Sm,n = n! si se obt ine o formul a interesant a: n! =
n1 k=0 k (1)k Cn (n k )n

class Surjectii { public static void main (String[]args) { int m, n=5, k, s; for(m=2;m<=10;m++) { s=0;

8.1. PRINCIPIUL INCLUDERII S I AL EXCLUDERII S I APLICAT II 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; } }

111

112

CAPITOLUL 8. ALGORITMI COMBINATORIALI

8.1.3

Num arul permut arilor f ar a puncte xe

Fie X = {1, 2, ..., n}. Dac a p este o permutare a elementelor mult imii X , spunem c a num arul i este un punct x al permut arii p, dac a p(i) = i (1 i n). Se cere s a se determine num arul D(n) al permut arilor f ar a puncte xe, ale mult imii X . S a not am cu Ai mult imea celor (n 1)! permut ari care admit un punct x n i (dar nu obligatoriu numai acest punct x!). Folosind principiul includerii si al excluderii, num arul permut arilor care admit cel put in un punct x este egal cu:
n n

|A1 A2 ... An | = Dar

i=1

|Ai |

1i<j n

|Ai Aj | + ... + (1)n1 |

i=1

Ai |.

|Ai1 Ai2 ... Aik | = (n k )! n pozit iile deoarece o permutare din mult imea Ai1 Ai2 ... Aik are puncte xe i1 , i2 , ..., ik , celelalte pozit ii cont in and o permutare a celor n k elemente r amase k (care pot avea sau nu puncte xe!). Cele k pozit ii i1 , i2 , ..., ik pot alese n Cn moduri, deci
1 2 n |A1 A2 ... An | = Cn (n 1)! Cn (n 2)! + ... + (1)n1 Cn .

Atunci D(n) = n! |A1 A2 ... An | = 1 2 n = n! Cn (n 1)! + Cn (n 2)! +... + (1)n Cn 1 1 (1)n 1 . = n! 1 + + ... + 1! 2! 3! n!

De aici rezult a c a

D(n) = e1 , n! deci, pentru n mare, probabilitatea ca o permutare a n elemente, aleas a aleator, s a nu aib a puncte xe, este de e1 0.3678. Se poate demonstra u sor c a:
n

lim

D(n + 1) D(n + 1)

= n (D(n) + D(n 1)) .

(n + 1)D(n) + (1)n+1

class PermutariFixe { public static void main(String [] args) {

8.2. PRINCIPIUL CUTIEI LUI DIRICHLET S I APLICAT II 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); } }

113

8.2

Principiul cutiei lui Dirichlet si aplicat ii


Acest principiu a fost formulat prima dat a de Dirichle (1805-1859). In forma cea mai simpl a acest principiu se enunt a astfel: Dac a n obiecte trebuie mp art ite n mai put in de n mult imi, atunci exist a cel put in o mult ime n care vor cel put in dou a obiecte. Mai general, principiul lui Dirichlet se poate enunt a astfel: Fiind date m obiecte, care trebuie mp art ite n n mult imi, si un num ar natural k astfel nc at m > kn, atunci, n cazul oric arei mp art iri, va exista cel put in o mult ime cu cel put in k + 1 obiecte. Pentru k = 1 se obt ine formularea anterioar a. Cu ajutorul funct iilor, principiul cutiei se poate formula astfel: Fie A si B dou a mult imi nite cu |A| > |B | si funct ia f : A B . Atunci, exist a b B cu proprietatea c a |f 1 (b)| 2. Dac a not am |A| = n si |B | = r atunci |f 1 (b)| n . r Demonstr am ultima inegalitate. Dac a aceasta nu ar adev arat a, atunci |f 1 (b)| < n , b B. r n =n r

Dar mult imea B are r elemente, deci n=


b B

|f 1 (b)| < r

ceea ce este o contradict ie.

8.2.1

Problema subsecvent ei

Se d a un sir nit a1 , a2 , ..., an de numere ntregi. Exist a o subsecvent a ai , ai+1 , ..., aj cu proprietatea c a ai + ai+1 + ... + aj este un multiplu de n.

114

CAPITOLUL 8. ALGORITMI COMBINATORIALI S a consider am urm atoarele sume: s1 s2 = a1 , = a1 + a2 ,

... sn = a1 + a2 + ... + an . Dac a exist a un k astfel sk este multiplu de n atunci i = 1 si j = k . Dac a nici o sum a part ial a sk nu este multiplu de n, atunci resturile mp art irii acestor sume part iale la n nu pot dec at n mult imea {1, 2, ..., n 1}. Pentru c a avem n sume part iale si numai n 1 resturi, nseamn a c a exist a cel put in dou a si rest. Atunci subsecvent a c autat a si sk2 , unde k1 < k2 ) cu acela sume part iale (sk1 se obt ine lu and i = k1 + 1 si j = k2 .

8.2.2

Problema sub sirurilor strict monotone

Se d a sirul de numere reale distincte a1 , a2 , ..., amn+1 . Atunci, sirul cont ine un sub sir cresc ator de m + 1 elemente: ai1 < ai2 < ... < aim+1 unde 1 i1 < i2 < ... < im+1 mn + 1, sau un sub sir descresc ator de n + 1 elemente aj1 < aj2 < ... < ajn+1 unde 1 j1 < j2 < ... < jn+1 mn + 1, sau ambele tipuri de sub siruri. Fiec arui element al sirului i asociem perechea de numere naturale (xi , yi ) unde xi este lungimea maxim a a sub sirurilor cresc atoare care ncep cu ai iar yi este lungimea maxim a a sub sirurilor descresc atoare care ncep n ai . Presupunem c a armat ia problemei nu este adev arat a, adic a: pentru toate numerele naturale xi si yi avem 1 xi m si 1 yi n. Atunci perechile de numere (xi , yi ) pot avea mn elemente distincte. Deoarece sirul are mn + 1 termeni, exist a un ai si un aj pentru care perechile de numere (xi , yi ) si (xj , yj ) sunt identice (xi = xj , yi = yj ), dar acest lucru este imposibil (cei doi termeni ai si aj ar trebui s a coincid a), ceea ce este o contradit ie. Deci exist a un sub sir cresc ator cu m + 1 termeni sau un sub sir descresc ator cu n + 1 termeni.

8.3

Numere remarcabile

8.3. NUMERE REMARCABILE

115

8.3.1

Numerele lui Fibonacci

Numerele lui Fibonacci se pot deni recursiv prin: F0 = 0, F1 = 1, Fn = Fn1 + Fn2 pentru n 2. 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 ata c a Fn = 2n 1 5 1+ 5
n

(8.3.1)

Numerele lui Fibonacci satisfac multe identit a ti interesante, ca de exemplu: 1 1 1 0


n

= =

Fn+1 Fn (1)n

2 Fn+1 Fn1 Fn

Fn Fn1

(8.3.2) (8.3.3) (8.3.4) (8.3.5) (8.3.6)

Fn+m Fnk

= Fm Fn+1 + Fm1 Fn = multiplu de Fk

si F2 + F4 + ... + F2n F1 + F3 + ... + F2n1 = F2n+1 1 = F 2n = Fn Fn+1 2 = F2 n


2 = F2 n+1 1

(8.3.7) (8.3.8) (8.3.9) (8.3.10) (8.3.11)

2 2 2 F1 + F2 + ... + Fn F1 F2 + F2 F3 + ... + F2n1 F2n

F1 F2 + F2 F3 + ... + F2n F2n+1

Teorema 2 Orice num ar natural n se poate descompune ntr-o sum a de numere Fibonacci. Dac a nu se folosesc n descompunere numerele F0 s i F1 si nici dou a numere Fibonacci consecutive, atunci aceast a descompunere este unic a abstract ie f ac and de ordinea termenilor. Folosind aceast a descompunere, numerele naturale pot reprezentate asem an ator reprezent arii n baza 2. De exemplu 19 = 1 13 + 0 8 + 1 5 + 0 3 + 0 2 + 1 1 = (101001)F In aceast a scriere nu pot exista dou a cifre 1 al aturate.

116

CAPITOLUL 8. ALGORITMI COMBINATORIALI

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

8.3.2

Numerele lui Catalan

Numerele Cn =

1 Cn n + 1 2n

8.3. NUMERE REMARCABILE

117

se numesc numerele lui Catalan. Ele apar n multe probleme, ca de exemplu: num arul arborilor binari, num arul de parantez ari corecte, num arul drumurilor sub diagonal a care unesc punctele (0, 0) si (n, n) formate din segmente orizontale si verticale, num arul secvent elor cu n bit i n care num arul cifrelor 1 nu dep a se ste num arul cifrelor 0 n nici o pozit ie plec and de la st anga spre dreapta, num arul segmentelor care unesc 2n puncte n plan f ar a s a se intersecteze, num arul sirurilor (x1 , x2 , ..., x2n ) n care xi {1, 1} si x1 + x2 + ... + x2n = 0 cu proprietatea x1 + x2 + ... + xi 0 pentru orice i = 1, 2, ..., 2n 1, num arul modurilor de a triangulariza un poligon, si multe altele. Numerele lui Catalan sunt solut ie a urm atoarei ecuat ii de recurent a: Cn+1 = C0 Cn + C1 Cn1 + ... + Cn C0 , pentru n 0 si C0 = 1. Numerele lui Catalan veric a si relat ia: Cn+1 = 4n + 2 Cn n+2

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

118

CAPITOLUL 8. ALGORITMI COMBINATORIALI 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;

8.4. DESCOMPUNEREA IN FACTORI PRIMI 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++) 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; } }

119

8.4

Descompunerea n factori primi

8.4.1

Funct ia lui Euler

Funct ia (n) a lui Euler ne d a num arul numerelor naturale mai mici ca n si prime cu n. Num arul n poate descompus n factori primi sub forma:
m 1 2 n = p 1 p2 ...pm

Not am cu Ai mult imea numerelor naturale mai mici ca n care sunt multipli de pi . Atunci avem: n n n |Ai | = , |Ai Aj | = , |Ai Aj Ak | = , ... pi pi pj pi pj pk

120 Rezult a:
m

CAPITOLUL 8. ALGORITMI COMBINATORIALI

(n) = n

i=1

n + pi

1i<j m

n pi pj

1i<j<km

n n + ... + (1)m pi pj pk p1 p2 ...pm

care este tocmai dezvoltarea produsului (n) = n 1 1 p1 1 1 p2 ... 1 1 pm

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

8.4. DESCOMPUNEREA IN FACTORI PRIMI 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(); } }

121

8.4.2
Fie

Num arul divizorilor


ek e1 e2 n = f1 f2 ... fk

descompunerea lui n n factori primi si ndiv (n) num arul divizorilor lui n. Atunci ndiv (n) = (1 + e1 ) (1 + e2 ) ... (1 + ek ).

8.4.3
Fie

Suma divizorilor
ek e2 e1 ... fk f2 n = f1

descompunerea lui n n factori primi si sdiv (n) suma divizorilor lui n. Atunci sdiv (n) =
d|n

d=

f1

(1+e1 )

k f 1 1 f2 2 1 ... k . f1 1 f2 1 fk 1

(1+e )

(1+e )

Demonstrat ie: Fie n = ab cu a = b si 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.

122

CAPITOLUL 8. ALGORITMI COMBINATORIALI Sumele divizorilor lui a si b sunt: sdiv (a) = 1 + a1 + a2 + ... + a sdiv (b) = 1 + b1 + b2 + ... + b. Dar sdiv (ab) =
d|ab

d=
i,j

ai bj = (
i

ai ) (

bj ) = sdiv (a) sdiv (b)

De aici rezult a c a:
ek ek e1 e2 e1 e2 sdiv (f1 f2 ... fk ) = sdiv (f1 ) sdiv (f2 ) ... sdiv (fk )

si mai departe rezult a relat ia dat a init ial!

8.5

Partit ia numerelor

8.5.1

Partit ia lui n n exact k termeni

Fie P (n, k ) num arul modalit a tilor de a descompune num arul natural n ca sum a de exact k termeni nenuli, consider and dou a descompuneri ca ind distincte dac a difer a prin cel put in un termen (deci, f ar aa tine 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 si n = a1 + a2 + ... + ak ; a1 a2 ... ak 1. Demonstrat ie: Ultimul termen ak poate 1 sau 2. Pentru ak = 1 avem P (n 1, k 1) posibilit a ti de alegere a factorilor a1 , a2 , ..., ak1 astfel nc at n 1 = a1 + a2 + ... + ak1 si a1 a2 ... ak1 1 Pentru ak 2 putem s a sc adem 1 din tot i factorii descompunerii lui n si ... a 1, deci num a rul a + ... + a unde a + a obt inem n k = a 2 1 2 1 k k descompunerilor lui n cu ak 2 este P (n k, k ). Obt inem relat ia P (n, k ) = P (n 1, k 1) + P (n k, k )

8.6. PARTIT IA MULT IMILOR care poate transformat a 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)

123

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 si tin and cont c a P (n k + 1, 1) = 1 = P (n k, 1) obt inem relat ia dat a la nceput.

8.5.2

Partit ia lui n n cel mult k termeni

Fie A(n, k ) num arul modalit a tilor de a descompune num arul natural n ca sum a de cel mult k termeni nenuli, consider and dou a descompuneri ca ind distincte dac a difer a prin cel put in un termen (deci, f ar a a tine 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 a num arul partit iilor lui n. (1) Num arul partit iilor n cel mult k factori este egal cu num arul partit iilor n exact k factori plus num arul partit iilor n cel mult k 1 termeni. (2) Dat a ind o partit ie a lui n n exact k termeni nenuli, putem sc adea 1 din ecare termeni, obt in and o partit ie a lui n k n cel mult k termeni nenuli. Astfel, exist a o corespondent a bijectiv a ntre partit iile lui n n exact k termeni si partit iile lui n k n cel mult k factori.

8.5.3

Partit ii multiplicative

Fie A(n, k ) num arul modalit a tilor de a descompune num arul natural n ca sum a de cel mult k termeni nenuli, consider and dou a descompuneri ca ind distincte dac a difer a prin cel put in un termen (deci, f ar a a tine cont de ordinea termenilor).

8.6

Partit ia mult imilor

124

CAPITOLUL 8. ALGORITMI COMBINATORIALI

Fie S (n, k ) num arul modalit a tilor de a partit iona o mult ime A cu n elemente n k submult imi nevide, consider and dou a partit ii ca ind distincte dac a difer a prin cel put in o submult ime (deci, f ar aa tine cont de ordinea submult 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 si A = A1 A2 ... Ak ; Demonstrat ie: Fie A = {a1 , a2 , ..., an } o mult ime cu n elemente si A1 , A2 , ..., Ak o partit ie oarecare. Elementul an poate (1) ntr-o submult ime cu un singur element (chiar el!), sau (2) ntr-o submult ime cu cel put in 2 elemente (printre care se g a se ste si el!). Num arul partit iilor de tipul (1) este S (n 1, k 1) (f ar a elementul an r am an n 1 elemente si k 1 submult imi n partit ie). Num arul partit iilor de tipul (2) este k S (n 1, k ) (elimin and elementul an din submult imea n care se a a, acea submult ime r am ane nevid a!). Se pare c a nu este prea clar (sau evident!) de ce apare totu si factorul k n expresia k S (n 1, k )! S a privim put in altfel lucrurile: consider am toate partit iile mult imii {a1 , a2 , ..., an1 } care au k submult imi; num arul acestora este S (n 1, k ); introducerea elementului an n aceste partit ii se poate face n oricare din cele k submult imi; deci k S (n 1, k ) reprezint a num arul partit iilor din cazul (2). Ai Aj = pentru i = j.

8.7

Probleme rezolvate

1. S a se determine num arul arborilor binari cu n v arfuri. Rezolvare: Fie b(n) num arul arborilor binari cu n v arfuri. Prin convent ie b0 = 1. Prin desene b1 = 1, b2 = 2, b3 = 5, si: dac a x am r ad acina arborelui, ne mai r am an n 1 v arfuri care pot ap area n subarborele st ang sau drept; dac a n subarborele st ang sunt k v arfuri, n subarborele drept trebuie s a e n 1 k

8.7. PROBLEME REZOLVATE

125

v arfuri; cu ace sti subarbori se pot forma n total bk bn1k arbori; adun and aceste valori pentru k = 0, 1, ..., n 1 vom obt ine valoarea lui bn . Deci, pentru n 1 bn = b0 bn1 + b1 bn2 + ... + bn1 b0 = Se obt ine
n1 k=0

bk bn1k

1 Cn n + 1 2n 2. Care este num arul permut arilor a n obiecte cu p puncte xe? p moduri, si cele n p Rezolvare: Deoarece cele p puncte xe pot alese n Cn puncte r amase nu mai sunt puncte xe pentru permutare, rezult a c a num arul permut arilor cu p puncte xe este egal cu bn =
p Cn D(n p)

deoarece pentru ecare alegere a celor p puncte xe exist a D(n p) permut ari ale obiectelor r amase, f ar a puncte xe. 3. Fie X = {1, 2, ..., n}. Dac a E (n) reprezint a num arul permut arilor pare1 ale mult imii X f ar a puncte xe, atunci E (n) = 1 D(n) + (1)n1 (n 1) . 2

Rezolvare: Fie Ai mult imea permut arilor pare p astfel nc at p(i) = i. Deoarece n !, rezult a c a num arul permut arilor pare este 1 2 E (n) = = T in and cont c a |Ai1 Ai2 ... Aik | = (n k )! rezult a formula cerut a. 1 n! |A1 ... An | = 2 1 1 2 n n! Cn (n 1)! + Cn (n 2)! + ... + (1)n Cn . 2

1 Dac a i < j, si n permutare i apare dup a j , spunem c a avem o inversiune. O permutare este par a dac a are un num ar par de inversiuni

126

CAPITOLUL 8. ALGORITMI COMBINATORIALI

Capitolul 9

Algoritmi de c autare
9.1 Problema c aut arii

Problema c aut arii este: se d a un vector a cu n elemente si o valoare x de acela si tip cu elementele din a. S a se determine p astfel nc at x = a[p] sau 1 dac a nu exist a un element cu valoarea v n a. O tabel a cu c ampuri care nu sunt de acela si tip se poate organiza cu ajutorul vectorilor dac a num arul de coloane este sucient de mic. De exemplu, o tabel a cu trei informat ii: num ar curent, nume, telefon poate organizat a cu ajutorul a doi vectori (nume si telefon) iar num arul curent este indicele din vector.

9.2

C autarea secvent ial a

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 si 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; } 127

128

CAPITOLUL 9. ALGORITMI DE CAUTARE O alt a posibilitate este de a pune o santinel a n cap atul 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 autare ntr-un tabel de nume este n acest caz mai ecient a, pentru c a nu se face dec at un singur test n plus aici ( n loc de dou a teste). C autarea secvent ial a se mai nume ste si c autare linear a, pentru c a se execut a N/2 operat ii n medie, si N operat ii n cazul cel mai defavorabil. Intr-un tablou cu 10.000 elemente, c autarea execut a 5.000 operat ii n medie, ceea ce nseamn a un consum de timp de aproximativ 0.005 ms (milisecunde). Iat a un program complet care utilizeaz a c autarea linear a ntr-o tabel a. class Tabela { final static int N = 6; static String nume[] = new String[N+1]; static int telefon[] = new int[N+1]; static void nume[0] = nume[1] = nume[2] = nume[3] = nume[4] = nume[5] = } initializare() { "Paul"; telefon[0] "Robert"; telefon[1] "Laura"; telefon[2] "Ana"; telefon[3] "Tudor"; telefon[4] "Marius"; telefon[5]

= = = = = =

2811; 4501; 2701; 2702; 2805; 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])); } }

9.3. CAUTARE BINARA

129

Cel mai simplu algoritm care rezolv a aceast a problem a este c autarea liniar a, care testeaz a elementele vectorului unul dupa altul, ncep and cu primul. p=-1; for(i=0;i<n;i++) if(x==a[i]) { p=i; break; } S a analiz am complexitatea acestui algoritm pe cazul cel mai defavorabil, acela n care v nu se g ase ste n a. In acest caz se va face o parcurgere complet a a lui a. Consider and ca operat ie elementar a testul v == a[i], num arul de astfel de operat ii elementare efectuate va egal cu numarul de elemente al lui a, adic a n. Deci complexitatea algoritmului pentru cazul cel mai defavorabil este O(n).

9.3

C autare binar a

O alt a tehnic a de c autare n tabele este c autarea binar a. Presupunem c a tabela de nume este sortat a n ordine alfabetic a (cum este cartea de telefoane). In loc de a c auta secvent ial, se compar a cheia de c autare cu numele care se af a la mijlocul tabelei de nume. Dac a acesta este acela si, se returneaz a num arul de telefon din mijloc, altfel se re ncepe c autarea n prima jum atate (sau n a doua) dac a numele c autat este mai mic (respectiv, mai mare) dec at numele din mijlocul tabelei. static int cautareBinara(String x) { int i, s, d, cmp; s = 0; d = N1; do { i = (s + d) / 2; cmp = x.compareTo(nume[i]); if (cmp == 0) return telefon[i]; if (cmp < 0) d = i - 1; else s = i + 1; } while (s <= d); return -1; } Num arul CN de comparat ii efectuate pentru o tabel a 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 a tabela are 10.000 elemente, atunci CN 14. Acesta reprezint a un beneciu considerabil n raport cu 5.000 de operat ii necesare la c autarea linear a.

130

CAPITOLUL 9. ALGORITMI DE CAUTARE

Desigur c autarea secvent ial a este foarte u sor de programat, si ea se poate folosi pentru tabele de dimensiuni mici. Pentru tabele de dimensiuni mari, c autarea binar a este mult mai interesant a. Se poate obt ine un timp sub-logaritmic dac a se cunoa ste distribut ia obiectelor. De exemplu, n cartea de telefon, sau n dict ionar, se stie apriori c a un nume care ncepe cu litera V se a a c atre sf ar sit. Presupun and distribut ia uniform a, putem face o regul a de trei simpl a pentru g asirea indicelui elementului de referint a pentru comparare, n loc s a alegem mijlocul, si s a urm am restul algoritmului de c autare binar a. Aceast a metod a se nume ste c autare prin interpolare. Timpul de c autare este O(log log N ), ceea ce nseamn a cam 4 operat ii pentru o tabel a de 10.000 elemente si 5 operat ii pentru 109 elemente n tabel a. Este spectaculos! O implementare iterativ a a c aut arii binare ntr-un vector ordonat (cresc ator sau descresc ator) 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.

9.4

Inserare n tabel a

La c autarea secvent ial a sau binar a nu am fost preocupat i de inserarea n tabel a a unui nou element. Aceasta este destul de rar a n cazul c art ii de telefon dar n alte aplicat ii, de exemplu lista utilizatorilor unui sistem informatic, este frecvent utilizat a. Vom vedea cum se realizeaz a inserarea unui element ntr-o tabel a, n cazul c aut arii secvent iale si binare. Pentru cazul secvent ial este sucient s a ad aug am la cap atul tabelei noul element, dac a are loc. Dac a nu are loc, se apeleaz a o procedur a error care a seaz a un mesaj de eroare.

9.5. DISPERSIA void inserare(String x, int val) { ++n; if (n >= N) error ("Depasire tabela"); numem[n] = x; telefon[n] = val; }

131

Inserarea se face deci n timp constant, de ordinul O(1). In cazul c aut arii binare, trebuie ment inut tabelul ordonat. Pentru inserarea unui element nou n tabel a, trebuie g asit a pozit ia sa printr-o c autare binar a (sau secvent ial a), apoi se deplaseaz a toate elementele din spatele ei spre dreapta cu o pozit ie pentru a putea insera noul element n locul corect. Aceasta necesit a log n+n operet ii. Inserarea ntr-o tabel a ordonat a cu n elemente este deci de ordinul O(n).

9.5

Dispersia

O alt a metod a de c autare n tabele este dispersia. Se utilizeaz a o funct ie h de grupare a cheilor (adesea siruri de caractere) ntr-un interval de numere ntregi. Pentru o cheie x, h(x) este locul unde se af a x n tabel a. Totul este n ordine dac a h este o aplicat ie injectiv a. De asemenea, este bine ca funct ia aleas a s a permit a evaluarea cu un num ar mic de operat ii. O astfel de funct ie este h(x) = x1 B m1 + x2 B m2 + ... + xm1 B + xm mod N.

De obicei se ia B = 128 sau B = 256 si se presupune c a dimensiunea tabelei N este un num ar prim. De ce? Pentru c a nmult irea puterilor lui 2 se poate face foarte u sor prin operat ii de deplasare pe bit i, numerele ind reprezentate n binar. In general, la ma sinile (calculatoarele) moderne, aceste operat ii sunt net mai rapide dec at nmult irea numerelor oarecare. C at despre alegerea lui N , aceasta se face pentru a evita orice interferent a ntre ntre nmult irile prin B si mp art irile prin N. Intr-adev ar, dac a de exemplu B = N = 256, atunci h(x) = x(m) este funct ia h care nu depinde dec at de ultimul caracter al lui x. Scopul este de a avea o funct ie h, de dispersie, simplu de calculat si av and o bun a distribut ie pe intervalul [0, N 1]. Calculul funct iei h se face prin funct ia h(x,m), unde m este lungimea sirului 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; }

132

CAPITOLUL 9. ALGORITMI DE CAUTARE

Deci funct ia h d a pentru toate cheile x o intrare posibil a n tabel a. Se poate apoi verica dac a x = nume[h(x)]. Dac a da, c autarea este terminat a. Dac a nu, nseamn a c a tabela cont ine o alt a cheie astfel nc at h(x ) = h(x). Se spune atunci c a exist a o coliziune, si tabela trebuie s a gestioneze coliziunile. O metod a simpl a este de a lista coliziunile ntr-un tabel col paralel cu tabelul nume. Tabela de coliziuni d a o alt a intrare i n tabela de nume unde se poate g asi cheia c autat a. Dac a nu se g ase ste valoarea x n aceast a nou a intrare i, se continu a cu intrarea i dat a de i = col[i]. Se continu a astfel c at timp col[i] = 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 autare consum a un timp mai mare sau egal ca lungimea medie a claselor de echivalent a denite pe tabel a de valorile h(x), adic a de lungimea medie a listei de coliziuni. Dac a funct ia de dispersie este perfect uniform a, nu apar coliziuni si determin a toate elementele printr-o singur a comparat ie. Acest caz este foarte put in probabil. Dac a num arul mediu de elemente av and aceea si valoare de dispersie este k = N/M , unde M este num arul claselor de echivalent a denite de h, c autarea ia un timp de N/M . Dispersia nu face dec at s a reduc a printr-un factor constant timpul c aut arii secvent iale. Interesul fat a de dispersie este pentru c a adesea este foarte ecace, si u sor de programat.

Capitolul 10

Algoritmi elementari de sortare


Tablourile sunt structuri de baz a n informatic a. Un tablou reprezint a, n funct ie de dimensiunile sale, un vector sau o matrice cu elemente de acela si tip. Un tablou permite accesul direct la un element, si noi vom utiliza intens aceast a proprietate n algoritmii de sortare pe care i vom considera.

10.1

Introducere

Ce este sortarea? Presupunem c a se d a un sir de N numere ntregi ai , si se dore ste aranjarea lor n ordine cresc atoare, n sens larg. De exemplu, pentru N = 10, sirul 18, 3, 10, 25, 9, 3, 11, 13, 23, 8 va deveni 3, 3, 8, 9, 10, 11, 13, 18, 23, 25. Aceast a problem a este clasic a n informatic a si a fost studiat a n detaliu, de exemplu, n [23]. In proctic a se nt alne ste adesea aceast a problem a. De exemplu, stabilirea clasamentului ntre student i, construirea unui dict ionar, etc. Trebuie f acut a o distint ie ntre sortarea unui num ar mare de elemente si a unui num ar mic de elemente. In acest al doilea caz, metoda de sortare este put in important a. Un algoritm amuzant const a n a vedea dac a setul de c art i de joc din m an a este deja ordonat. Dac a nu este, se d a cu ele de p am ant si se re ncepe. Dup a un anumit timp, exist a riscul de a avea c art ile ordonate. Desigur, poate s a nu se termine niciodat a, pentru noi, aceast a sortare. 133

134

CAPITOLUL 10. ALGORITMI ELEMENTARI DE SORTARE

O alt a tehnic a (discut and serios de data aceasta), frecvent utilizat a la un joc de c art i, const a n a vedea dac a exist a o transpozit ie de efectuat. Dac a exist a, se face interschimbarea c art ilor de joc si se caut a o alt a transpozit ie. Procedeul se repet a p an a c and nu mai exist a transpozit ii. Aceast a metod a funct ioneaz a foarte bine pentru o bun a distribut ie a c art ilor. Este u sor de intuit c a num arul obiectelor de sortat este important. Nu este cazul s a se caute o metod a sosticat a pentru a sorta 10 elemente. Cu alte cuvinte, nu se trage cu tunul ntr-o musc a! Exemplele tratate ntr-un curs sunt ntotdeauna de dimensiuni limitate, din considerente pedagogice nu este posibil de a prezenta o sortare a mii de elemente.

10.2

Sortare prin select ie

In cele ce urmeaz a, presupunem c a trebuie s a sort am un num ar de ntregi care se g asesc ntr-un tablou (vector) a. Algoritmul de sortare cel mai simplu este prin select ie. El const a n g asirea pozit iei n tablou a elementului cu valoarea cea mai mic a, adic a ntregul m pentru care ai am pentru orice i. Odat a g asit a aceast a pozit ie m, se schimb a ntre ele elementele a1 si am . Apoi se re ncepe aceast a operat ie pentru sirul (a2 , a3 , ..., aN ), tot la fel, c aut andu-se elementul cel mai mic din acest sir si interschimb andu-l cu a2 . S i a sa mai departe p an a la un moment dat c and sirul va cont ine un singur element. C autarea celui mai mic element ntr-un tablou este un prim exercit iu de programare. Determinarea pozit iei acestui element este foarte simpl a, ea se poate efectua cu ajutorul instruct iunilor urm atoare: m = 0; for (int j = 1; j < N; ++j) if (a[j] < a[m]) m = i; Schimbarea ntre ele a celor dou a elemente necesit a o variabil a temporar at si se efectueaz a prin: t = a[m]; a[m] = a[1]; a[1] = t; Acest set de operat ii trebuie reluat prin nlocuirea lui 1 cu 2, apoi cu 3 si a sa mai departe p an a la N . Aceasta se realizeaz a prin introducerea unei variabile i care ia toate valorile ntre 1 si N . Toate acestea sunt ar atate n programul care urmeaz a. De aceast a dat a vom prezenta programul complet. Procedurile de achizit ionare a datelor si de returnare a rezultatelor sunt deasemenea prezentate. Pentru alt i algoritmi, ne vom limita la descrierea efectiv a a sort arii.

10.2. SORTARE PRIN SELECT IE 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(); afisare(); } }

135

136

CAPITOLUL 10. ALGORITMI ELEMENTARI DE SORTARE Un exemplu de execut ii este: 1 2 3 4 5 6 7 12 4 30 3 4 23 14 m 1 2 3 4 5 6 7 12 4 30 7 4 23 14 i m 1 2 3 4 5 6 7 4 12 30 7 4 23 14 i m 1 2 3 4 5 6 7 4 4 30 7 12 23 14 i m 1 2 3 4 5 6 7 4 4 7 30 12 23 14 i m 1 2 3 4 5 6 7 4 4 7 12 30 23 14 i m 1 2 3 4 5 6 7 4 4 7 12 14 23 30 im 1 2 3 4 5 6 7 4 4 7 12 14 23 30

0 7 i 0 3

0 3 i 0 3

1 12

2 4

3 30

0 3

0 3

1 4 i 1 4

0 3

0 3

1 4

2 12 m 2 4 i 2 4

4 7 m 3 4 30 7

5 4

6 23

7 14

5 4

6 23

7 14

3 30

0 3

0 3

1 4

2 4

3 7 i 3 7

0 3

0 3

1 4

2 4

3 7

0 3

0 3

1 4

2 4

3 7

0 3

0 3

1 4

2 4

3 7

5 6 12 23 m 4 5 6 30 12 23 m 4 5 6 12 30 23 i m 4 5 6 12 14 23 i 4 5 6 12 14 23 im 4 5 6 12 14 23

4 7

7 14

7 14

7 14

7 30 m 7 30

7 30

Este u sor de calculat num arul de operat ii necesare. La ecare iterat ie, se pleac a de la elementul ai si se compar a succesiv cu ai+1 , ai+2 , ..., aN . Se fac deci N i comparat ii. Se ncepe cu i = 1 si se termin a cu i = N 1. Deci, se fac (N 1) + (N 2) + ... + 2 + 1 = N (N 1)/2 comparat ii si N 1 interschimb ari. Sortarea prin select ie execut a un num ar de comparat ii de ordinul N 2 . O variant a a sort arii prin select ie este metoda bulelor. Principiul ei este de a parcurge sirul (a1 , a2 , ..., aN ) invers and toate perechile de elemente consecutive (aj 1, aj ) neordonate. Dup a prima parcurgere, elementul maxim se va aa pe pozit ia N . Se re ncepe cu prexul (a, a2 , ..., aN 1 ), ..., (a1 , a2 ). Procedura corespunz atoare utilizeaz a un indice i care marcheaz a sf ar situl prexului n sortare, si un indice j care permite deplasarea c atre marginea i.

10.2. SORTARE PRIN SELECT IE

137

Se poate calcula de asemenea foarte u sor num arul de operat ii si se obt ine un num ar de ordinul O(N 2 ) comparat ii si, eventual interschimb ari (dac a, de exemplu, tabloul este init ial n ordine descresc atoare). static void sortBule() { int t; for (int i = N1; i >= 0; i) for (int j = 1; j <= i; ++j) if (a[j1] > a[j]) { t = a[j1]; a[j1] = a[j]; a[j] = t; } } 0 7 1 12 2 4 j 2 12 3 30 4 3 5 4 6 23 7 14 i 7 14 i 7 14 i 7 14 i 7 14 ji a ajuns pe 7 30 0 7 1 4 2 3 4 5 12 30 3 4 j 2 3 4 5 12 3 30 4 j 2 3 4 5 12 3 4 30 j 2 3 4 5 12 3 4 23 7 14 i 6 7 23 14 i 6 7 23 14 i 6 7 30 14 j i 6 7 14 30 ji vector). 6 7 14 30 i 6 7 14 30 i 6 23

0 7

1 4

3 30

0 7

1 4

2 12

3 3

4 3 j 4 30

5 4

6 23

0 7

1 4

0 7

1 4

2 12

3 3

4 4

5 4 j 5 30

6 23

0 7

1 4

0 7

1 4

2 12

3 3

4 4

5 23

6 23 j 6 30

0 7

1 4

0 7

1 4

2 12

3 3

4 4

5 23

0 7

0 4

Dup a prima parcurgere 30 1 2 3 4 5 6 4 12 3 4 23 14 j i 1 2 3 4 5 6 7 7 12 3 4 23 14 30 j i

locul s au (ultimul loc n 0 1 2 3 4 5 4 7 12 3 4 23 j 0 1 2 3 4 5 4 7 3 12 4 23 j

138 0 4 1 7 2 3

CAPITOLUL 10. ALGORITMI ELEMENTARI DE SORTARE 3 12 4 4 j 4 12 5 23

6 7 0 1 2 3 4 5 6 7 14 30 4 7 3 4 12 23 14 30 i j i 0 1 2 3 5 6 7 0 1 2 3 4 5 6 7 4 7 3 4 23 14 30 4 7 3 4 12 14 23 30 ji ji Dup a a doua parcurgere 23 a ajuns pe locul s au (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 a treia parcurgere 14 a ajuns pe locul s au (de fapt era deja pe locul s au de la nceputul acestei parcurgeri; s-au mai aranjat totu si c ateva 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 a patra parcurgere 12 a ajuns pe locul s au (de fapt era deja pe locul s au de la nceputul acestei parcurgeri; oricum, la aceast a parcurgere s-au mai aranjat c ateva elemente!). La urm atoarea parcurgere nu se efectueaz a nici o interschimbare de elemente. Vectorul este deja sortat, a sa c a urm atoarele parcurgeri se fac, de asemenea, f ar a s a se execute nici o interschimbare de elemente. O idee bun a este introducerea unei variabile care s a contorizeze num arul de interschimb ari din cadrul unei parcurgeri. Dac a nu s-a efectuat nici o interschimbare atunci vectorul este deja sortat a sa c a se poate ntrerupe execut ia urm atoarelor parcurgeri. Programul modicat este: static void sortBule() { int t, k; for (int i = N1; i >= 0; i) { k=0; for (int j = 1; j <= i; ++j) if (a[j1] > a[j]) {t = a[j1]; a[j1] = a[j]; a[j] = t; k++;} if(k==0) break; } }

10.3. SORTARE PRIN INSERT IE

139

10.3

Sortare prin insert ie

O metod a complet diferit a este sortarea prin insert ie. Aceasta este metoda utilizat a pentru sortarea unui pachet de c art i de joc. Se ia prima carte, apoi a doua si se aranjeaz a n ordine cresc atoare. Se ia a treia carte si se plaseaz a pe pozit ia corespunz atoare fat a de primele dou a c art i, si a sa mai departe. Pentru cazul general, s a presupunem c a primele i 1 c art i sun deja sortate cresc ator. Se ia a i-a carte si se plaseaz a pe pozit ia corespunz atoare relativ la primele i 1 c art i deja sortate. Se continu a p an a la i = N .

10.3.1

Insert ie direct a

Dac a determinarea pozit iei corespunz atoare, n sub sirul format de primele i 1 elemente, se face secvent ial, atunci sortarea se nume ste sortare prin insert ie direct a. Procedura care realizeaz a aceast sortare 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 pozit iile din st anga ind deja sortate) se parcurge vectorul spre st anga plec and de la pozit ia i 1. Elementele vizitate se deplaseaz a cu o pozit ie spre dreapta pentru a permite plasarea elementului ai (a c arui valoare a fost salvat an variabila temporar a v ) pe pozit ia corespunz atoare. Procedura cont ine o mic a eroare, dac a ai este cel mai mic element din tablou, c aci va ie si din tablou spre st anga. Se poate remedia aceast a situat ie plas and un element a0 de valoare max int. Se spune c a a fost plasat a o santinel a la st anga tabloului a. Aceasta nu este ntotdeauna posibil, si atunci trebuie ad augat un test asupra indicelui j n bucla while. Un exemplu numeric este cel care urmeaz a: Inserarea lui 12 nu necesit a deplas ari 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

140

CAPITOLUL 10. ALGORITMI ELEMENTARI DE SORTARE Inserarea lui 30 nu necesit a deplas ari de elemente.

4 5 6 7 30 4 23 14 i 1 2 3 5 6 7 1 2 3 4 5 6 7 4 7 12 4 23 14 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 arul de comparat ii pentru inserarea unui element n secvent a deja sortat a este egal cu num arul de inversiuni plus 1. Fie ci num arul de comparat ii. Atunci ci = 1 + card{aj |aj > ai , j < i} Pentru o permutare corespunz atoare unui sir de sort ari, unde num arul de inversiuni este inv ( ), num arul total de comparat ii pentru sortarea prin insert ie este
N

0 4 j 0 3

1 7

2 12

3 30

4 3 i 4 30

5 4

6 23

7 14

0 3 j 0 3

1 4

2 7

3 12

C =
i=2

ci = N 1 + inv ( ).

Deci, num arul mediu de comparat ii este CN = 1 N! C = N 1 + N (N 1) N (N + 3) = 1 4 4

SN

unde Sn reprezint a grupul permut arilor de n elemente. De si ordinul de cre stere este tot N 2 , aceast a metod a de sortare este mai ecient a dec at sortarea prin select ie. Cea mai bun a metod a de sortare necesit a n log2 n comparat ii. In plus, ea are o proprietate foarte bun a: num arul de operat ii depinde puternic de ordinea init ial a din tablou. In cazul n care tabloul este aproape ordonat, si drept urmare exist a put ine inversiuni si sunt necesare put ine operat ii, spre deosebire de primele dou a metode de sortare. Sortarea prin inserare este deci o metod a de sortare bun a dac a tabloul de sortat are sansa de a aproape ordonat.

10.3. SORTARE PRIN INSERT IE

141

10.3.2

Insert ie binar a

Metoda anterioar a se poate mbunat a ti dac a tinem cont de faptul c a secvent a n care se face inserarea este deja ordonat a, iar n loc s a se fac a insert ia direct a n aceast a secvent a, c autarea pozit iei pe care se face inserarea se face prin c autare binar a. 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; 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; } }

142

CAPITOLUL 10. ALGORITMI ELEMENTARI DE SORTARE

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

10.4

Sortare prin interschimbare

Aceast a metod a folose ste interschimbarea ca si caracteristic a principal a a metodei de sortare. In cadrul acestei metode se compar a si se interschimb a perechi adiacente de chei pan a c and 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; schimb=true; } } }

10.5. SORTARE PRIN MICS ORAREA INCREMENTULUI - SHELL

143

10.5

Sortare prin mic sorarea incrementului - shell

Prezent am metoda pe urm atorul sir: 44, 55, 12, 42, 94, 18, 6, 67. Se grupeaz a elementele aate la 4 pozit ii distant a, si se sorteaz a separat. Acest proces este numit numit 4 sort. Rezult a sirul: 44, 18, 06, 42, 94, 55, 12, 67 Apoi se sorteaz a elementele aate la 2 pozit ii distant a. Rezult a: 6, 18, 12, 42, 44, 55, 94, 97 Apoi se sorteaz a sirul rezultat ntr-o singur a trecere: 1 - sort 6, 12, 18, 42, 44, 55, 94, 97 Se observ a urm atoarele: un proces de sortare i sort combin a 2 grupuri sortate n procesul 2i sort anterior n exemplul anterior s-a folosit secventa de increment i 4, 2, 1 dar orice secvent a, cu condit ia ca cea mai n a sortare s a e 1 sort. In cazul cel mai defavorabil, n ultimul pas se face totul, dar cu multe comparat ii si interschimb ari. dac a cei t incrementi sunt h1 , h2 , .. ht , ht = 1 si hi+1 < hi , ecare hi -sort se poate implementa ca si o sortate prin insertie direct a. 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; } } }

144

CAPITOLUL 10. ALGORITMI ELEMENTARI DE SORTARE

Capitolul 11

Liste
Scopul listelor este de a genera un ansamblu nit de elemente al c arui num ar nu este xat apriori. Elementele acestui ansamblu pot numere ntregi sau reale, siruri de caractere, obiecte informatice complexe, etc. Nu suntem acum interesat i de elementele acestui ansamblu ci de operat iile care se efectueaz a asupra acestuia, independent de natura elementelor sale. Listele sunt obiecte dinamice, n sensul c a num arul elementelor variaz a n cursul execut iei programului, prin ad aug ari sau stergeri de elemente pe parcursul prelucr arii. Mai precis, operat iile permise sunt: testarea dac a ansamblul este vid ad augarea de elemente vericarea dac a un element este n ansamblu stergerea unui element

11.1

Liste liniare

Fiecare element al listei este cont inut ntr-o celul a care cont ine n plus adresa elementului urm ator, numit si pointer. Java permite realizarea listelor cu ajutorul claselor si obiectelor: celulele sunt obiecte (adic a instant e ale unei clase) n care un c amp cont ine o referint a c atre celula urm atoare. Referint a c atre prima celul a este cont inut a ntr-o variabil a. class Lista { int continut; Lista urmator; 145

146 Lista (int x, Lista a) { continut = x; urmator = a; } }

CAPITOLUL 11. LISTE

Instruct iunea new Lista(x,a) construie ste o nou a celul a cu c ampurile x si a. Func tia Lista(x,a) este un constructor al clasei Lista (un constructor este o funct ie nestatic a care se distinge prin tipul rezultatului s au care este cel al clasei curente, si prin absent a numelui de identicare). Obiectul null apart ine tuturor claselor si reprezint a n cazul listelor marcajul de sf ar sit de list a. De asemenea new Lista(2, new Lista(7, new Lista(11,null))) reprezint a lista 2, 7, 11. static boolean esteVida (Lista a) { return a == null; } Procedura adauga insereaz a un element n capul listei. Aceast a modalitate de a introduce elemente n capul listei este util a pentru ca num arul de operat ii necesare ad aug arii de elemente s a e independent de m arimea listei; este sucient a modicarea valorii capului listei, ceea ce se face simplu prin: 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 11.1: Ad augarea unui element n list a Funct ia cauta, care veric a dac a elementul x este n lista a, efectueaz a o parcurgere a listei. Variabila a este modicat a iterativ prin a=a.urmator pentru a parcurge elementele listei p an a se g ase ste x sau p an a se g ase ste sf ar situl listei (a = null). static boolean cauta (int x, Lista a) { while (a != null) { if (a.continut == x) return true; a = a.urmator; } return false; }

11.1. LISTE LINIARE Funct ia cauta poate scris a 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)); }

147

Aceast a sciere recursiv a este sistematic a pentru funct iile care opereaz a asupra listelor. Tipul list a veric a ecuat ia urm atoare: Lista = {Lista vida} Element Lista unde este sau exclusiv iar este produsul cartezian. Toate procedurile sau funct iile care opereaz a asupra listelor se pot scrie recursiv n aceast a manier a. De exemplu, lungimea listei se poate determina prin: static int lungime(Lista a) { if (a == null) return 0; else return 1 + longime(a.urmator); } care este mai elegant a dec at scrierea iterativ a: static int lungime(Lista a) { int lg = 0; while (a != null) { ++lg; a = a.urmator; } return lg; }

148

CAPITOLUL 11. LISTE

Alegerea ntre maniera recursiv a sau iterativ a este o problem a subiectiv a n general. Pe de alt a parte se spune c a scrierea iterativ a este mai ecient a pentru c a folose ste mai put in a memorie. Grat ie noilor tehnici de compilare, acest lucru este mai put in adev arat; ocuparea de memorie suplimentar a, pentru calculatoarele de ast azi, este o problem a foarte put in critic a. Eliminarea unei celule care cont ine x se face modic and valoarea c ampului urmator cont inut n predecesorul lui x: succesorul predecesorului lui x devine succesorul lui x. Un tratament particular trebuie f acut dac a elementul care trebuie eliminat este primul element din list a. Procedura recursiv a de eliminare este foarte compact a n denit 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 11.2: Eliminarea unui element din list a

O procedur a iterativ a solicit a un plus de atent 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; }

11.1. LISTE LINIARE

149

In cadrul funct iilor anterioare, care modic a lista a, nu se dispune de dou a liste distincte, una care cont ine x si alta identic a cu precedenta dar care nu mai cont ine x. Pentru a face acest lucru, trebuie recopiat a o parte a listei a ntr-o nou a list a, cum face programul urm ator, unde exist a o utilizare put in important a de memorie suplimentar a. Oricum, spat iul de memorie pierdut este recuperat de culeg atorul de spat iu de memorie GC (Garbage Colection) din Java, dac a nu mai este folosit a lista a. Cu tehnicile actuale ale noilor versiuni ale GC, recuperarea se efectueaz a foarte rapid. Aceasta este o diferent a important a fat a de limbajele de programare precum Pascal sau C, unde trebuie s a m preocupat i de spat iul de memorie pierdut, dac a trebuie s a- l reutiliz am. 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 a o tehnic a, utilizat a adesea, care permite evitarea unor teste pentru eliminarea unui element dintr-o list a si, n general, pentru simplicarea program arii operat iilor asupra listelor. Aceasta const a n utilizarea unui fanion / santinel a care permite tratarea omogen a a listelor indiferent dac a sunt vide sau nu. In reprezentarea anterioar a lista vid a nu a avut aceea si structur a ca listele nevide. Se utilizeaz a o celul a plasat a la nceputul listei, care nu are informat ie semnicativ a n c ampul continut; adresa adev aratei prime celule se a a n c ampul urmator al acestei celule. Astfel obt inem o list a p azit a, avantajul ind c a lista vid a cont ine numai garda si prin urmare un num ar de programe, care f aceau un caz special din lista vid a sau din primul element din list a, devin mai simple. Aceast a not iune este un pic echivalent a cu not iunea de santinel a pentru tablouri. Se utilizeaz a aceast a tehnic a n proceduri asupra sirurilor prea lungi. De asemenea, se pot deni liste circulare cu gard a / paznic n care n prima celul a n c ampul urmator este ultima celul a din list a. Pentru implementarea listelor se pot folosi perechi de tablouri : primul tablou, numit continut, cont ine elementele de informat ii, iar al doilea, numit urmator, cont ine adresa elementului urm ator din tabloul continut. Procedura adauga efectueaz a numai 3 operat ii elementare. Este deci foarte ecient a. Procedurile cauta si elimina sunt mai lungi pentru c a trebuie s a parcurg a ntreaga list a. Se poate estima c a num arul mediu de operat ii este jum atate din lungimea listei. C autarea binar a efectueaz a un num ar logaritmic iar c autarea cu tabele hash (de dispersie) este si mai rapid a. Exemplu 1. Consider am construirea unei liste de numere prime mai mici dec at un num ar natural n dat. Pentru construirea acestei liste vom ncepe, n

150

CAPITOLUL 11. LISTE

prima faz a, prin ad augarea numerelor de la 2 la n ncep and cu n si termin and cu 2. Astfel numerele vor n list a n ordine cresc atoare. Vom utiliza metoda clasic aa ciurului lui Eratostene: consider am succesiv elementele listei n ordine cresc atoare si suprim am tot i multiplii lor. Aceasta se realizeaz a prin procedura urm atoare: 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 ste o list a simplu nl ant uit a format a din toate cheile care au h(x) = i. Elementele listei sunt intr ari care permit accesul la tabloul de nume si numere de telefon. Procedurile de c autare si inserare a unui element x ntr-un tablou devin proceduri de c autare si ad augare ntr-o list a. Tot astfel, dac a funct ia h este r au aleas a, num arul de coliziuni devine important, multe liste devin de dimensiuni enorme si f ar amit area risc a s a devin a la fel de costisitoare ca si c autarea ntr-o list a simplu nl ant uit a obi snuit a. Dac a h este bine aleas a, c autarea este rapid a. Lista al[] = new Lista[N1]; static void insereaza (String x, int val) { int i = h(x); al[i] = adauga (x, al[i]); } 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; }

11.2. COZI

151

11.2

Cozi

Cozile sunt liste liniare particulare utilizate n programare pentru gestionarea obiectelor care sunt n a steptarea unei prelucr ari ulerioare, de exemplu procesele de a steptare a resurselor unui sistem, nodurile unui graf, etc. Elementele sunt ad augate sistematic la coad a si sunt eliminate din capul listei. In englez a se spune strategie FIFO (First In First Out), n opozit ie cu strategia LIFO (Last In Firs Out) utilizat a la stive. Mai formalizat, consider am o mult ime de elemente E , mult imea cozilor cu elemente din E este notat a Coada(E ), coada vid a (care nu cont ine nici un element) este C0 , iar operat iile asupra cozilor sunt: esteV ida, adauga, valoare, elimina: esteV ida este o aplicat ie denit a pe Coada(E ) cu valori n {true, f alse}, esteV ida(C ) aste egal a cu true dac a si numai dac a coada C este vid a. adauga este o aplicat ie denit a pe E Coada(E ) cu valori n Coada(E ), adauga(x, C ) este coada obt inut a plec and de la coada C si inser and elementul x la sf ar situl ei. valoare este o aplicat ie denit a pe Coada(E )C0 cu valori n E care asociaz a unei cozi C , nevid a, elementul aat n cap. elimina este o aplicat ie denit a pe Coada(E )C0 cu valori n Coada(E ) care asociaz a unei cozi nevide C o coad a obt inut a plec and de la C si elimin and primul element. Operat iile asupra cozilor satisfac urm atoarele relat ii: Pentru C = 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 a idee de realizare sub form a de programe a operat iilor asupra cozilor este de a mprumuta tehnica folosit a n locurile unde client ii stau la coad a pentru a servit i, de exemplu la gar a pentru a lua bilete, sau la cas a ntr-un magazin.

152

CAPITOLUL 11. LISTE

Fiecare client care se prezint a obt ine un num ar si client ii sunt apoi chemat i de c atre funct ionarul de la ghi seu n ordinea cresc atoare a numerelor de ordine primite la sosire. Pentru gestionarea acestui sistem, gestionarul trebuie s a cunoasc a dou a numere: num arul obt inut de c atre ultimul client sosit si num arul obt inut de c atre ultimul client servit. Not am aceste dou a numere inceput si sf arsit si gestion am sistemul n modul urm ator: coada de a steptare este vid a dac a si numai dac a inceput = sf arsit, atunci c and sose ste un nou client, se incrementeaz a sf arsit si se d a acest num ar clientului respectiv, atunci c and funct ionarul este liber el poate servi un alt client, dac a coada nu este vid a, incrementeaz a inceput si cheam a posesorul acestui num ar. In cele ce urmeaz a sunt prezentate toate operat iile n Java utiliz and tehnicile urm atoare: se reatribuie num arul 0 unui nou client atunci c and se dep a se ste un anumit prag pentru valoarea sf arsit. Se spune c a este un tablou (sau tampon) circular, sau coad a circular a. 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; }

11.2. COZI

153

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; c.plina = false; } }

inceput

sfarsit

e1

e2

...

en

Figura 11.3: Coad a de a steptare implementat a ca list a

O alt a modalitate de gestionare a cozilor const a n utilizarea listelor nl ant uite cu

154

CAPITOLUL 11. LISTE

gard a n care se cunosc adresele primului si ultimului element. Aceasta d a operat iile urm atoare: 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 sim-

11.3. STIVE

155

plu nl ant uite. Scrierea programelor const a n primul r and n alegerea structurilor de date pentru reprezentarea cozilor. Ansamblul funct iilor care opereaz a asupra cozilor se pot plasa ntr-un modul care manipuleaz a tablouri sau liste. Utilizarea cozilor se face cu ajutorul funct iilor vida, adauga, valoare, elimina. Aceasta este deci interfat a cozilor care are important a n programe complexe.

11.3

Stive

Not iunea de stiv a intervine n mod curent n programare, rolul s au principal ind la implementarea apelurilor de proceduri. O stiv a se poate imagina ca o cutie n care sunt plasate obiecte si din care se scot n ordinea invers a fat a de cum au fost introduse: obiectele sunt puse unul peste altul n cutie si se poate avea acces numai la obiectul situat n v arful stivei. In mod formalizat, se consider a o mult ime E , mult imea stivelor cu elemente din E este notat a Stiva(E ), stiva vid a (care nu cont ine nici un element) este S0 , operat iile efectuate asupra stivelor sunt vida, adauga, valoare, elimina, ca si la re. De aceast a dat a, relat iile satisf acute sunt urm atoarele: elimina(adauga(x, S )) = S esteV ida(adauga(x, S )) = f alse valoare(adauga(x, S )) = x esteV ida(S0 ) = true Cu ajutorul acestor relat ii se pot exprima toate operat iile relative la stive. Realizarea operat iilor asupra stivelor se poate face utiliz and un tablou care cont ine elementele si un indice care indic a pozit ia v arfului 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(); }

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

CAPITOLUL 11. LISTE

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"); 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 at cu ajutorul tablourilor (vectorilor) c at si cu ajutorul listelor simplu nl ant uite.

11.4. EVALUAREA EXPRESIILOR ARITMETICE PREFIXATE

157

11.4

Evaluarea expresiilor aritmetice prexate

Vom ilustra utilizarea stivelor printr-un program de evaluare a expresiilor aritmetice scrise sub o form a particular a. In programare o expresie aritmetic a poate cont ine numere, variabile si operat ii aritmetice (ne vom limita numai la + si *). Vom considera ca intr ari numai numere naturale. Expresiile prexate cont in simbolurile: numere naturale, +, *, ( si ). Dac a e1 si e2 sunt expresii prexate atunci (+e1 e2 ) si (e1 e2 ) sunt expresii prexate. Pentru reprezentarea unei expresii prexate n Java, vom utiliza un tablou ale c arui elemente sunt entit a tile expresiei. Elementele tabloului sunt obiecte cu trei c ampuri: primul reprezint a natura entit a tii (simbol sau num ar), al doilea reprezint a valoarea entit a tii dac a aceasta este num ar, iar al treilea este este un simbol dac a entitatea este simbol. class Element { boolean esteOperator; int valoare; char simbol; } Vom utiliza funct iile denite pentru stiv a si procedurile denite n cele ce urmeaz a. 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 a n stivuirea rezultatelor intermediare, stiva cont in and operatori si numere, dar niciodat a nu va cont ine consecutiv numere. Se examineaz a succesiv entit a tile expresiei dac a entitatea este un operator sau un num ar si dac a v arful stivei este un operator, atunci se plaseaz a n stiv a. Dac a este un num ar si v arful stivei este de asemenea un num ar, act ioneaz a operatorul care precede v arful stivei asupra celor dou a numere si se repet a operat ia asupra rezultatului g asit. De exemplu, pentru expresia (+ (* (+ 35 36) (+ 5 6)) (* (+ 7 8) (*9 9 ))) evaluarea decurge astfel:

158

CAPITOLUL 11. LISTE 9 * 15 * 781 +

* +

+ * +

35 + * +

71 * +

+ 71 * +

5 + 71 * +

781 +

* 781 +

+ * 781 +

7 + * 781 +

* 15 * 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; } In acest caz, este util a prezentarea unui program principal care utilizeaz a aceste funct 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); } }

11.5. OPERAT II ASUPRA LISTELOR

159

11.5

Operat ii asupra listelor

In aceast a sect iune sunt prezentat i c a tiva algoritmi de manipulare a listelor. Ace stia sunt utilizat i n limbajele de programare care au listele ca structuri de baz a. Funct ia Tail este o primitiv a clasic a; ea suprim a primul element al listei.
a Tail(a)

e1

e2

e3

e4

e1

e2

e3

e4

Figura 11.4: Suprimarea primului element din list a

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 dune liste vide."); return a.info; } static Lista tail (Lista a) { if (a == null) erreur ("Tail dune liste vide."); return a.next; } Procedurile asupra listelor construiesc o list a plec and de la alte dou a liste, pun o list a la cap atul celeilalte liste. In prima procedur a append, cele dou a liste nu sunt modicate; n a doua procedur a, concat, prima list a este transformat a pentru a ret ine rezultatul. Totusi, se poate remarca faptul c a, dac a append copiaz a primul s au argument, partajeaz a nalul listei rezultat cu al doilea argument.

160
a b

CAPITOLUL 11. LISTE

a1

a2 append(a,b}

a3

b1

b2

b3

b4

a1

a2

a3

Figura 11.5: Concatenarea a dou a 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 concat(a,b}

a3

b1

b2

b3

b4

Figura 11.6: Concatenarea a dou a liste prin concat

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

11.5. OPERAT II ASUPRA LISTELOR Aceast a ultim a procedur a 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; } }

161

Procedura de calcul de oglindire a unei liste a const a n construirea unei liste n care elementele listei a sunt n ordine invers a. Realizarea acestei proceduri este un exercit iu clasic de programare asupra listelor. D am aici dou a solut i, una iterativ a, cealalt a recursiv a, complexitatea este O(n2 ) deci p atratic a, dar clasic a. Noua metod a nReverse modic a argumentul s au, n timp ce Reverse nu-l modic a ci construie ste o alt a list a 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; }
c

e1

e2

e3

e4 a

e5

nill

Figura 11.7: Transformarea listei prin inversarea leg aturilor

static Lista Reverse (Lista a) { if (a == null) return a; else return append(Reverse (a.next), adauga(a.info, null)); }

162

CAPITOLUL 11. LISTE

Se poate scrie o versiune iterativ a a versiunii recursive, grat ie unei funct ii auxiliare care acumuleaz a rezultatul ntr-unul din argumentele sale: 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 exercit iu formator const a n a administra liste n care elementele sunt aranjate n ordine cresc atoare. Procedura de ad augare devine ceva mai complex a pentru c a trebuie g asit a pozit ia celulei care trebuie ad augat a dup a parcurgerea unei p art i a listei. Nu trat am acest exercit iu dec at n cazul listelor circulare cu gard a. Pentru o astfel de list a, valoarea c ampului inf o din prima celul a nu are nici o semnicat ie (este celula de gard a). C ampul next din ultima celul a cont ine adresa primei celule.

a1 garda

a2

a3

a4

a5

a6

Figura 11.8: List a circular a cu gard a

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 12

Algoritmi divide et impera


12.1 Tehnica divide et impera

Divide et impera1 este o tehnic a de elaborare a algoritmilor care const a n: Descompunerea repetat a a problemei (subproblemei) ce trebuie rezolvat a n subprobleme mai mici. Rezolvarea n acela si mod (recursiv) a tuturor subproblemelor. Compunerea subsolut iilor pentru a obt ine solut ia problemei (subproblemei) init iale. Descompunerea problemei (subproblemelor) se face p an an momentul n care se obt in subprobleme de dimensiuni at at de mici nc at au solut ie cunoscut a sau pot rezolvate prin tehnici elementare. Metoda poate descris a astfel: procedure divideEtImpera(P, n, S ) if (n n0 ) then rezolv a subproblema P prin tehnici elementare else mparte P n P1 , ..., Pk de dimensiuni n1 , ..., nk divideEtImpera(P1 , n1 , S1 ) ... divideEtImpera(Pa , nk , Sk ) combin a S1 , ..., Sk pentru a obt ine S Exemplele tipice de aplicare a acestei metode sunt algoritmii de parcurgere a arborilor binari si algoritmul de c autare binar a.
1 divide-and-conquer,

n englez a

163

164

CAPITOLUL 12. ALGORITMI DIVIDE ET IMPERA

12.2

Ordinul de complexitate

Vom presupune c a dimensiunea ni a subproblemei i satisface relat ia ni n b, unde b > 1. Astfel, pasul de divizare reduce o subproblem a la altele de dimensiuni mai mici, ceea ce asigur a terminarea subprogramului recursiv. Presupunem c a divizarea problemei n subprobleme si compunerea solut iilor subproblemelor necesit a un timp de ordinul O(nk ). Atunci, complexitatea timp T (n) a algoritmului divideEtImpera este dat a de relat ia de recurent a: T (n) = O(1) k a T(n b ) + O (n ) , dac a n n0 , dac a n > n0 (12.2.1)

Teorema 3 Dac a n > n0 atunci: log a O(n b ) T (n) = O(nk logb n) O(nk )

, dac a a > bk , dac a a = bk , dac a a < bk

(12.2.2)

Demonstrat ie: Putem presupune, f ar a a restr ange generalitatea, c a n = bm n0 . De k k asemenea, presupunem c a T (n) = c n0 dac a n n0 si T (n) = a T ( n b)+cn dac a n > n0 . Pentru n > n0 avem: n T (n) = aT + cnk b = aT bm1 n0 + cnk = a aT bm2 n0 + c = a2 T bm2 n0 = ... = am T (n0 ) + c am1 n b m 1
m k

n b n +c a b

+ cnk + nk n b
k

+ ... + a
k

+ nk

m 1 k k = am cnk b n0 + ... + a bm1 0 +c a m 1+ = cnk 0a m

m k k nk 0 + (b ) n0

bk + ... + a
i

bk a

= cam
i=0

bk a

unde am notat cnk 0 prin c. Distingem cazurile: 1. a > bk . Seria


m i=0 bk a i

este convergent a si deci sirul sumelor part iale este

convergent. De aici rezult a c a T (n) = O(am ) = O(alogb n ) = O(nlogb a )

12.3. EXEMPLE

165

2. a = bk . Rezult a c a am = bkm = cnk si de aici T (n) = O(nk m) = O(nk logb n). 3. a < bk . Avem T (n) = O(am ( ba )m ) = O(bkm ) = O(nk ).
k

12.3

Exemple

Dintre problemele clasice care se pot rezolva prin metoda divide et impera ment ion am: c autare binar a sortare rapid a (quickSort) problema turnurilor din Hanoi sortare prin interclasare - Merge sort dreptunghi de arie maxim a n placa cu g auri, etc

12.3.1

Sortare prin partitionare - quicksort

Se bazeaz a pe metoda interschimb arii, ns a din nou, interschimbarea se face pe distant e mai mari. Astfel, av and tabloul a[], se aplic a urm atorul algoritm: 1. se alege la nt amplare un element x al tabloului 2. se scaneaz a tabloul a[] la st anga lui x p an a c and se g ase ste un element ai > x 3. se scaneaz a tabloul la dreapta lui x p an a c and se g ase ste un element aj < x 4. se interschimb a ai cu aj 5. se repet a pa sii 2, 3, 4 p an a c and scan arile se vor nt alni pe undeva la mijlocul tabloului. In acel moment, tabloul a[] va partitionat n 2 astfel, la st anga lui x se vor g asi elemente mai mici ca si x, la dreapta, elemente mai mari ca si x. Dup a aceasta, se aplic a acela si proces sub sirurilor de la st anga si de la dreapta lui x, p an a c and aceste sub siruri 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) {

166

CAPITOLUL 12. ALGORITMI DIVIDE ET IMPERA

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 a ( n medie)!

12.3.2

Sortare prin interclasare - MergeSort

Ideea este de a utiliza interclasarea n etapa de asamblare a solut iilor. In urma rezolv arii recursive a subproblemelor rezult a vectori ordonat i si prin interclasarea lor obt inem vectorul init ial sortat. Vectorii de lungime 1 sunt evident considerat i sortat i. Pasul de divizare se face n timpul O(1). Faza de asamblare se face n timpul O(m1 + m2 ) unde n1 si n2 sunt lungimile celor doi vectori care se interclaseaz a. Din Teorema 3 pentru a = 2, b = 2 si k = 1 rezult a c a ordinul de complexitate al algoritmului MergeSort este O(n log2 n) unde n este dimensiunea vectorului init ial supus sort arii. class MergeSort { static int x[]={3,5,2,6,4,1,8,2,4,3,5,3}; public static void main(String[]args)

12.3. EXEMPLE { 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[]) { 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]; return a; }//interclasare(...) }//class

167

k++; }

168

CAPITOLUL 12. ALGORITMI DIVIDE ET IMPERA

12.3.3

Placa cu g auri

Se consider a o plac a dreptunghiular a n care exist a n g auri punctiforme de coordonate cunoscute. S a se determine pe aceast a plac a un dreptunghi de arie maxim a, cu laturile paralele cu laturile pl acii, care s a nu cont in a g auri n interior ci numai eventual pe laturi. Vom considera placa ntr-un sistem cartezian. Consider am o subproblem a de forma urm atoare: dreptunghiul determinat de diagonala (x1, y 1) (din st anga-jos) si (x2, y 2) din dreapta-sus este analizat pentru a vedea dac a n interior cont ine vreo gaur a; dac a nu cont ine, este o solut ie posibil a (se va alege cea de arie maxim a); dac a exist a o gaur a n interiorul s au, atunci se consider a patru subprobleme generate de cele 4 dreptunghiuri obt inute prin descompunerea pe orizontal a si pe vertical a de cele dou a drepte paralele cu axele care trec prin punctul care reprezint a gaura.
1 2 3

Figura 12.1: Dreptunghi de arie maxim a n placa cu g auri

import java.io.*; 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(); st.nextToken(); st.nextToken(); st.nextToken(); st.nextToken(); x1=(int) st.nval; y1=(int) st.nval; x2=(int) st.nval; y2=(int) st.nval; n=(int) st.nval;

12.3. EXEMPLE

169

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

12.3.4

Turnurile din Hanoi

Se dau trei tije verticale A, B2 si C. Pe tija A se g asesc n discuri de diametre diferite, aranjate n ordine descresc atoare a diametrelor de la baz a spre v arf. Se cere s a se g aseasc a o strategie de mutare a discurilor de pe tija A pe tija C respect and urm atoarele reguli:

170

CAPITOLUL 12. ALGORITMI DIVIDE ET IMPERA la un moment dat se va muta un singur disc (cel care se a a deasupra celorlalte discuri pe o tij a); un disc poate a sezat doar peste un disc de diametru mai mare dec at al s a sau pe o tij a goal a. Imp art im problema astfel: se mut a primele n 1 discuri de pe tija surs a A pe tija intermediar aB se mut a ultimul disc de pe tija surs a A pe tija destinat ie C se mut a cele n 1 discuri de pe tija intermediar a B pe tija destinat ie C
pasul 1

a)

b) pasul 2

pasul 3

B d)

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

12.3. EXEMPLE

171

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 1 Disc 1 A 2 Disk 2 A 3 Disc 1 B 4 Disk 3 A 5 Disc 1 C 6 Disk 2 C 7 Disc 1 A 8 Disk 4 A 9 Disc 1 B 10 Disk 2 B 11 Disc 1 C 12 Disk 3 B 13 Disc 1 A 14 Disk 2 A 15 Disc 1 B numarul discurilor: 4 ==> B --> C ==> C --> B ==> A --> B ==> B --> C ==> C --> A ==> A --> C ==> B --> C ==> 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));

172

CAPITOLUL 12. ALGORITMI DIVIDE ET IMPERA

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

12.3. EXEMPLE

173

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) 1 Disc 1 A ==> B A(432) 2 Disc 2 A --> C A(43) 3 Disc 1 B ==> C A(43) 4 Disc 3 A --> B A(4) 5 Disc 1 C ==> A A(41) 6 Disc 2 C --> B A(41) 7 Disc 1 A ==> B A(4) 8 Disc 4 A --> C A() 9 Disc 1 B ==> C A() 10 Disc 2 B --> A A(2) 11 Disc 1 C ==> A A(21) 12 Disc 3 B --> C A(21) 13 Disc 1 A ==> B A(2) 14 Disc 2 A --> C A() 15 Disc 1 B ==> C A()

B() B(1) B(1) B() B(3) B(3) B(32) B(321) B(321) B(32) B(3) B(3) B() B(1) B(1) B()

C() C() C(2) C(21) C(21) C(2) C() C() C(4) C(41) C(41) C(4) C(43) C(43) C(432) C(4321)

12.3.5

Injum at a tire repetat a

174

CAPITOLUL 12. ALGORITMI DIVIDE ET IMPERA

Se consider a un sir de numere naturale x1 , x2 , ..., xn si o succesiune de decizii d1 , d2 , ... unde di {S, D} au urm atoarea semnicat ie: la pasul i, sirul r amas n acel moment se mparte n dou a sub siruri cu num ar egal elemente (dac a num arul de elemente este impar, elementul situat la mijloc se elimin a) si se elimin a jum atatea din partea st ang aa sirului dac a di = S (dac a di = D se elimin a jum atatea din partea drept a). Deciziile se aplic a n ordine, una dup a alta, p an a c and r am ane 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

12.3. EXEMPLE 6 ... 7 a ramas! 6 ... 7 elimin S 7 ... 7 a ramas! Toate elementele care pot r am ane: 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) { 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

175

176 SSS SSD SDS SDD DSS DSD DDS DDD --> --> --> --> --> --> --> --> 10 9 7 6 5 4 2 1

CAPITOLUL 12. ALGORITMI DIVIDE ET IMPERA

Capitolul 13

Algoritmi BFS-Lee

13.1

Prezentare general a

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

177

178 st.nextToken(); st.nextToken(); st.nextToken(); st.nextToken();

CAPITOLUL 13. ALGORITMI BFS-LEE n=(int)st.nval; m=(int)st.nval; nods=(int)st.nval; 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; 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()

13.1. PREZENTARE GENERALA { 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();

179

// 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; p[v]=u; if(color[fin]!=WHITE) break; // optimizare; ies din for incoada(v); } } }//for color[u]=BLACK; if(color[fin]!=WHITE) break; }//while }//bfs

// am ajuns la nod_destinatie

180

CAPITOLUL 13. ALGORITMI BFS-LEE

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 2 5 1 2 2 3 3 1 3 4 4 5 5 6 6 4 7 8 8 9 9 7 7 4 */

Distanta(2,8) = 4 drum : 2 3 4 7 8

13.2

Probleme rezolvate

13.2.1

Romeo si Julieta - OJI2004 clasa a X-a

In ultima ecranizare a celebrei piese shakespeariene Romeo si Julieta tr aiesc ntr-un ora s modern, comunic a prin e-mail si chiar nvat a s a programeze. Intro secvent a tulbur atoare sunt prezentate fr am at arile interioare ale celor doi eroi ncerc and f ar a succes s a scrie un program care s a determine un punct optim de nt alnire. Ei au analizat harta ora sului si au reprezentat-o sub forma unei matrice cu n linii si m coloane, n matrice ind marcate cu spat iu zonele prin care se poate trece (str azi lipsite de pericole) si cu X zonele prin care nu se poate trece. De asemenea, n matrice au marcat cu R locul n care se a a locuint a lui Romeo, iar cu J locul n care se a a locuint a Julietei. Ei se pot deplasa numai prin zonele care sunt marcate cu spat iu, din pozit ia curent a n oricare dintre cele 8 pozit ii nvecinate (pe orizontal a, vertical a sau diagonale).

13.2. PROBLEME REZOLVATE

181

Cum lui Romeo nu i place s a a stepte si nici s a se lase a steptat n-ar tocmai bine, ei au hot ar at c a trebuie s a aleag a un punct de nt alnire n care at at Romeo, c at si Julieta s a poat a ajunge n acela si timp, plec and de acas a. Fiindc a la nt alniri am andoi vin ntr-un suet, ei estimeaz a timpul necesar pentru a ajunge la nt alnire prin num arul de elemente din matrice care constituie drumul cel mai scurt de acas a p an a la punctul de nt alnire. S i cum probabil exist a mai multe puncte de nt alnire posibile, ei vor s a l aleag a pe cel n care timpul necesar pentru a ajunge la punctul de nt alnire este minim. Cerint a Scriet i un program care s a determine o pozit ie pe hart a la care Romeo si Julieta pot s a ajung a n acela si timp. Dac a exist a mai multe solut ii, programul trebuie s a determine o solut ie pentru care timpul este minim. Datele de intrare Fi sierul de intrare rj.in cont ine: pe prima linie numerele naturale N M , care reprezint a num arul de linii si respectiv de coloane ale matricei, separate prin spat iu; pe ecare dintre urm atoarele N linii se a a M caractere (care pot doar R, J , X sau spat iu) reprezent and matricea. Datele de ie sire Fi sierul de ie sire rj.out va cont ine o singur a linie pe care sunt scrise trei numere naturale separate prin c ate un spat iu tmin x y , av and semnicat ia: x y reprezint a punctul de nt alnire (x - num arul liniei, y - num arul coloanei); tmin este timpul minim n care Romeo (respectiv Julieta) ajunge la punctul de nt alnire. Restrict ii si preciz ari 1 < N, M < 101 Liniile si coloanele matricei sunt numerotate ncep and cu 1. Pentru datele de test exist a ntotdeauna solut ie. Exemple rj.in 5 8 XXR XXX X X X J X X X XX XXX XXXX rj.out 4 4 4

182

CAPITOLUL 13. ALGORITMI BFS-LEE

Explicat ie: Traseul lui Romeo poate : (1,3), (2,4), (3,4), (4,4). Timpul necesar lui Romeo pentru a ajunge de acas a la punctul de nt alnire este 4. Traseul Julietei poate : (3,1), (4,2), (4,3), (4,5). Timpul necesar Julietei pentru a ajunge de acas a la punctul de nt alnire este deasemenea 4. In plus 4, este punctul cel mai apropiat de ei cu aceast a proprietate. Timp maxim de executare: 1 secund a/test Indicat ii de rezolvare * Mihai Stroe, Ginfo nr. 14/4 aprilie 2004 Problema se rezolv a folosind algoritmul lui Lee. Se aplic a acest algoritm folosind ca puncte de start pozit ia lui Romeo si pozit ia Julietei. Vom folosi o matrice D n care vom pune valoarea 1 peste tot pe unde nu se poate trece, valoarea 2 n pozit ia n care se a a Romeo init ial, valoarea 3 n pozit ia n care se a a Julieta init ial si valoarea 0 n rest. La ecare pas k vom parcurge aceast a matrice si n pozit iile vecine celor care au valoarea 2 vom pune valoarea 2, dac a acestea au valoarea 0 sau 3. Dac a o pozit ie are valoare 3, nseamn a c a la un moment de timp anterior Julieta se putea aa n pozit ia respectiv a. La acela si pas k vom mai parcurge matricea o dat a si n pozit iile vecine celor care au valoarea 3 vom pune valoarea 3, dac a acestea au valoarea 0. Dac a la pasul k pozit ia vecin a uneia care are valoarea 3, are valoarea 2, atunci ne vom opri si k reprezint a momentul minim de timp dup a care cei doi se nt alnesc, iar pozit ia care are valoare 2 reprezint a locul nt alnirii. La prima vedere s-ar p area c a num arul k nu reprezint a momentul de timp minim la care cei doi se nt alnesc. Vom demonstra c a algoritmul este corect prin metoda reducerii la absurd. Pentru aceasta avem n vedere c a pozit iile marcate cu 2 reprezint a toate locurile n care se poate aa Romeo dup a cel mult k pa si, iar cele marcate cu 2 reprezint a toate locurile n care se poate aa Julieta dup a cel mult k pa si. Dac a k nu reprezint a momentul de timp minim la care cei doi se nt alnesc nseamn a c a acesta a fost determinat mai devreme si algoritmul s-a oprit deja. Analiza complexit a tii Ordinul de complexitate al operat iei de citire a datelor de intrare este O(M N ). Ordinul de complexitate al acestui algoritm este O(kM N ), unde k reprezint a momentul n care cei doi se nt alnesc. Ordinul de complexitate al operat iei de scriere a rezultatului este O(1). In concluzie, ordinul de complexitate al algoritmului de rezolvare a acestei probleme este O(kM N ).

13.2. PROBLEME REZOLVATE Rezolvare detaliat a

183

Codul surs a*

import java.io.*; class RJ { static final int zid=10000; static int m,n; static int[][] xr,xj; static int[] qi=new int[5000]; static int[] qj=new int[5000]; static int ic, sc;

// coada sau coada circulara mai bine ! // coada // 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]; xj=new int[m+2][n+2]; // matrice bordata cu zid ! // 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; else if(s.charAt(j-1)==R) {ir=i; jr=j; xr[i][j]=1;}

// zid!

184

CAPITOLUL 13. ALGORITMI BFS-LEE 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 ic=sc=0; qi[sc]=ir; qj[sc]=jr; sc++; while(ic!=sc) { i=qi[ic]; j=qj[ic]; ic++; fill(xr,i,j); } // coada vida; // (ir,jr) --> coada

// scot din coada

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

13.2. PROBLEME REZOLVATE if(x[i+1][j+1]==0) if(x[i+1][j-1]==0) if(x[i][j+1]==0) if(x[i][j-1]==0) }// fil(...) }// class

185

{x[i+1][j+1]=t+1; qi[sc]=i+1; qj[sc]=j+1; sc++;} // SE {x[i+1][j-1]=t+1; qi[sc]=i+1; qj[sc]=j-1; sc++;} // SV {x[i][j+1]=t+1; {x[i][j-1]=t+1; qi[sc]=i; qi[sc]=i; qj[sc]=j+1; sc++;} // E qj[sc]=j-1; sc++;} // V

13.2.2

Sudest - OJI2006 clasa a X-a

Fermierul Ion det ine un teren de form a p atrat a, mp art it n N N p atrate de latur a unitate, pe care cultiv a carto. Pentru recoltarea cartolor fermierul folose ste un robot special proiectat n acest scop. Robotul porne ste din p atratul din st anga sus, de coordonate (1, 1) si trebuie s a ajung a n p atratul din dreapta jos, de coordonate (N, N ). Traseul robotului este programat prin memorarea unor comenzi pe o cartel a magnetic a. Fiecare comand a specic a direct ia de deplasare (sud sau est) si num arul de p atrate pe care le parcurge n direct ia respectiv a. Robotul str ange recolta doar din p atratele n care se opre ste ntre dou a comenzi. Din p acate, cartela pe care se a a programul s-a deteriorat si unitatea de citire a robotului nu mai poate distinge direct ia de deplasare, ci numai num arul de pa si pe care trebuie s a-i fac a robotul la ecare comand a. Fermierul Ion trebuie s a introduc a manual, pentru ecare comand a, direct ia de deplasare. Cerint a Scriet i un program care s a determine cantitatea maxim a de carto pe care o poate culege robotul, n ipoteza n care Ion specic a manual, pentru ecare comand a, direct ia urmat a de robot. Se va determina si traseul pe care se obt ine recolta maxim a. Datele de intrare Fi sierul de intrare sudest.in are urm atoarea structur a: Pe linia 1 se a a num arul natural N , reprezent and dimensiunea parcelei de teren. Pe urm atoarele N linii se a a c ate N numere naturale, separate prin spat ii, reprezent and cantitatea de carto din ecare p atrat unitate. Pe linia N + 2 se a a un num ar natural K reprezent and num arul de comenzi aate pe cartela magnetic a. Pe linia N + 3 se a a K numerele naturale C1 , ...,CK , separate prin spat ii, reprezent and num arul de pa si pe care trebuie s a-i efectueze robotul la ecare comand a.

186

CAPITOLUL 13. ALGORITMI BFS-LEE

Datele de ie sire Fi sierul de iesire sudest.out va cont ine pe prima linie cantitatea maxim a de carto recoltat a de robot. Pe urm atoarele K + 1 linii vor scrise, n ordine, coordonatele p atratelor unitate ce constituie traseul pentru care se obt ine cantitate maxim a de carto, c ate un p atrat unitate pe o linie. Coordonatele scrise pe aceea si linie vor separate printr-un spat iu. Primul p atrat de pe traseu va avea coordonatele 11, iar ultimul va avea coordonatele N N . Dac a sunt mai multe trasee pe care se obt ine o cantitate maxim a de carto recoltat a se va a sa unul dintre acestea. Restrict ii si preciz ari 5 N 100 2K 2N 2 1 C1 , ..., CK 10 Cantitatea de carto dintr-un p atrat de teren este num ar natural ntre 0 si 100. Pentru ecare set de date de intrare se garanteaz a c a exist a cel put in un traseu. Se consider a c a robotul str ange recolta si din p atratul de plecare (1, 1) si din cel de sosire (N, N ). Pentru determinarea corect a a cantit a tii maxime recoltate se acord a 50% din punctajul alocat testului respectiv; pentru cantitate maxim a recoltat a si traseu corect se acord a 100%. Exemplu sudest.in 6 121041 133511 2 2 1 2 1 10 453926 113201 10 2 4 6 5 10 5 22141

sudest.out 29 11 31 51 61 65 66

Explicat ii Un alt traseu posibil este: 11 13 15 25 65 66 dar costul s au este 1 + 1 + 4 + 1 + 5 + 10 = 22

Timp maxim de execut ie/test: 1 secund a

13.2. PROBLEME REZOLVATE Indicat ii de rezolvare * solut ia comisiei Reprezentarea informat iilor N - num arul de linii K - num arul de comenzi A[N max][N max]; - memoreaz a cantitatea de produs

187

C [N max][N max]; - C [i][j ] = cantitatea maxim a de carto culeas a pe un traseu ce porne ste din (1, 1) si se termin a n (i, j ), respect and condit iile problemei P [N max][N max]; - P [i][j ] = pasul la care am ajuns n pozit ia i, j culeg and o cantitate maxim a de carto M ove[2 N max]; - memoreaz a cele K comenzi Parcurg sirul celor k mut ari. La ecare mutare marchez pozit iile n care pot ajunge la mutarea respectiv a. Mai exact, parcurg toate pozit iile n care am putut ajunge la pasul precedent (cele marcate n matricea P corespunz ator cu num arul pasului precedent) si pentru ecare pozit ie veric dac a la pasul curent pot s a execut mutarea la sud. In caz armativ, veric dac a n acest caz obt in o cantitate de carto mai mare dec at cea obt inut a p an a la momentul curent (dac a da, ret in noua cantitate, si marchez n matricea P pozit ia n care am ajuns cu indicele mut arii curente). In mod similar procedez pentru o mutare spre est. Codul surs a* Variant a dup a solut ia ocial a: 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]; static int[][] C=new int[Nmax][Nmax]; static int[][] P=new int[Nmax][Nmax]; static int[] Move=new int[2*Nmax];

// // // //

A[i][j]=cantitatea de cartofi C[i][j]=cantitatea maxima ... pas comenzile

188

CAPITOLUL 13. ALGORITMI BFS-LEE

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

13.2. PROBLEME REZOLVATE 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()

189

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(...) }// class Variant a folosind coad a: 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;

190 static static static static static static

CAPITOLUL 13. ALGORITMI BFS-LEE int[][] a=new int[nmax][nmax]; // int[][] c=new int[nmax][nmax]; // int[][] p=new int[nmax][nmax]; // int[] move=new int[2*nmax]; // int ic,sc,scv,qmax=100; int[] q=new int[qmax]; // coada a[i][j]=cantitatea de cartofi c[i][j]=cantitatea maxima ... pozitia anterioara optima 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;} }// 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

13.2. PROBLEME REZOLVATE 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]; 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

191

13.2.3

Muzeu - ONI2003 clasa a X-a

Suntet i un participant la Olimpiada Nat ional a de Informatic a. In programul olimpiadei intr a si c ateva activit a tii de divertisment. Una dintre ele este vizitarea unui muzeu. Acesta are o structur a de matrice dreptunghiular a cu M linii si N coloane; din orice camer a se poate ajunge n camerele vecine pe direct iile nord, est, sud si vest (dac a aceste camere exist a). Pentru pozit ia (i, j ) deplasarea spre nord presupune trecerea n pozit ia (i 1, j ), spre est n (i, j + 1), spre sud n

192 (i + 1, j ) si spre vest n (i, j 1).

CAPITOLUL 13. ALGORITMI BFS-LEE

Acest muzeu are c ateva reguli speciale. Fiecare camer a este marcat a cu un num ar ntre 0 si 10 inclusiv. Mai multe camere pot marcate cu acela si num ar. Camerele marcate cu num arul 0 pot vizitate gratuit. Intr-o camer a marcat a cu num arul i (i > 0) se poate intra gratuit, dar nu se poate ie si din ea dec at dac a ar atat i supraveghetorului un bilet cu num arul i. Din fericire, orice camer a cu num arul i (i > 0) ofer a spre v anzare un bilet cu num arul i; o dat a cump arat acest bilet, el este valabil n toate camerele marcate cu num arul respectiv. Biletele pot avea pret uri diferite, dar un bilet cu num arul i va avea acela si pret n toate camerele n care este oferit spre v anzare. Dumneavoastr a intrat i n muzeu prin colt ul de Nord-Vest (pozit ia (1, 1) a matricei) si dorit i s a ajunget i la ie sirea situat a n colt ul de Sud-Est (pozit ia (M, N ) a matricei). O dat a ajuns acolo primit i un bilet gratuit care v a permite s a vizitat i tot muzeul. Pozit iile (1, 1) si (M, N ) sunt marcate cu num arul 0. Cerint a Cunosc andu-se structura muzeului, determinat i o strategie de parcurgere a camerelor, astfel nc at s a ajunget i n camera (M, N ) pl atind c at mai put in. Dac a exist a mai multe variante, aleget i una n care este parcurs un num ar minim de camere (pentru a c a?tiga timp si pentru a avea mai mult timp pentru vizitarea integral a a muzeului). Date de intrare Prima linie a sierului de intrare muzeu.in cont ine dou a numere ntregi M si N , separate printr-un spat iu, num arul de linii, respectiv de coloane, al matricei care reprezint a muzeul. Urm atoarele M linii cont in structura muzeului; ecare cont ine N numere ntregi ntre 0 si 10 inclusiv, separate prin spat ii. Linia M + 2 cont ine 10 numere ntregi ntre 0 si 10000 inclusiv, reprezent and costurile biletelor 1, 2, 3, ...10 n aceast a ordine. Date de ie sire In sierul muzeu.out vet i a sa: pe prima linie suma minim a necesar a pentru a ajunge din (1, 1) n (M, N ); pe a doua linie num arul minim de mut ari L efectuate dintr-o camer a ntr-o camer a vecin a, pentru a ajunge din (1, 1) n (M, N ); pe a treia linie L caractere din multimea N , E , S , V reprezent and deplas ari spre Nord, Est, Sud sau Vest. Restrict ii si preciz ari Exemplu: 2 N 50

13.2. PROBLEME REZOLVATE 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 a/test. Indicat ii de rezolvare *

193

Mihai Stroe, GInfo nr. 13/6 - octombrie 2003 Se observ a c a num arul variantelor de cump arare a biletelor (cump ar / nu cump ar biletul 1, biletul 2, ..., biletul 10) este 210 = 1024. Se genereaz a ecare din aceste variante. Pentru o astfel de variant a, se calculeaz a costul si se transform a matricea init ial a ntr-o matrice cu 0 si 1, n care 0 reprezint a o camer a pentru care se pl ate ste biletul (sau nu e nevoie de bilet) si 1 reprezint a o camer a pentru care nu se cump ara bilet (deci n care nu se intr a). Pentru o astfel de matrice, problema determin arii celui mai scurt drum de la (1, 1) la (M, N ) se rezolv a cu algoritmul lui Lee. Se rezolv a aceast a problem a pentru toate cele 1024 variante. Eventual, dac a la un moment dat exist a o solut ie cu un cost mai bun dec at al variantei curente, aceasta nu mai este abordat a. Evident, problema determin arii celui mai scurt drum se poate rezolva si pe matricea init ial a, tin and cont la ecare pas de biletele cump arate; cum algoritmul lui Lee este folosit de obicei pentru o matrice cu 0 si 1, am folosit init ial convent ia respectiv a pentru a u sura nelegerea. Se alege solut ia de cost minim si, n caz de egalitate, cea cu num ar minim de camere. Analiza complexit a tii Operat ia de citire a datelor are ordinul de complexitate O(M N ). Fie C num arul de numere de marcaj diferite din matrice. Pentru ecare dintre cele 2C variante, g asirea drumului minim cu algoritmul lui Lee are complexitatea O(M N ). Operat ia de scriere a solut iei are ordinul de complexitate O(M N ) pentru cazul cel mai defavorabil. Ordinul de complexitate al algoritmului de rezolvare a acestei probleme este O(M N 2C ). Algoritmul funct ioneaz a datorit a restrict iei impuse pentru C (cel mult 10 numere de marcaj). Consider and 2C o constant a, ordinul de complexitate devine O(M N ).

194 Codul surs a

CAPITOLUL 13. ALGORITMI BFS-LEE

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 static static static static static static static int m,n; // dimensiuni muzeu int ncmin; // nr camere minim int cc,cmin=50*50*10000/2; // cost curent/minim boolean amAjuns; int[][] int[][] int[][] int[][] x=new int[51][51]; // muzeu c=new int[51][51]; // costul (1,1) --> (i,j) d=new int[51][51]; // directii pentru "intoarcere" 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]; static int[] qj=new int[qdim]; static int ic, sc; // coada pentru i din pozitia (i,j) // coada pentru j din pozitia (i,j) // 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);

13.2. PROBLEME REZOLVATE 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(...)

195

static void matriceCosturi() { 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()

196 {

CAPITOLUL 13. ALGORITMI BFS-LEE

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

13.2. PROBLEME REZOLVATE out.println(cmin); out.println(ncmin-1);

197

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

13.2.4

P aianjen ONI2005 clasa a X-a

Un p aianjen a tesut o plas a, n care nodurile sunt dispuse sub forma unui caroiaj cu m linii (numerotate de la 0 la m 1) si n coloane (numerotate de la 0 la n 1) ca n gur a. Init ial, oricare dou a noduri vecine (pe orizontal a sau vertical a) erau unite prin segmente de plas a de lungime 1. In timp unele port iuni ale plasei sau deteriorat, devenind nesigure. Pe plas a, la un moment dat, se g asesc p aianjenul si o musc a, n noduri de coordonate cunoscute.

198
0 1 2 3 4 5 6

CAPITOLUL 13. ALGORITMI BFS-LEE

0 1 2 3 4 5 6 7 8

pozitie paianzen

pozitie musca

Cerint a S a se determine lungimea celui mai scurt traseu pe care trebuie s a-l parcurg a p aianjenul, folosind doar port iunile sigure ale plasei, pentru a ajunge la musc a. De asemenea, se cere un astfel de traseu. Datele de intrare Fi sierul de intrare paianjen.in cont ine: pe prima linie dou a numere naturale m n, separate printr-un spat iu, reprezent and num arul de linii si respectiv num arul de coloane ale plasei; pe a doua linie dou a numere naturale lp cp, separate printr-un spat u, reprezentnd linia si respectiv coloana nodului n care se a a init ial p aianjenul; pe linia a treia dou a numere naturale lm cm separate printr-un spat iu, reprezent and linia si respectiv coloana pe care se a a init ial musca; pe linia a patra, un num ar natural k , reprezent and num arul de port iuni de plas a deteriorate; pe ecare dintre urm atoarele k linii, c ate patru valori naturale l1 c1 l2 c2, separate prin c ate un spat iu, reprezent and coordonatele capetelor celor k port iuni de plas a deteriorate (linia si apoi coloana pentru ecare cap at). Datele de ie sire Fi sierul de ie sire paianjen.out va cont ine pe prima linie un num ar natural min reprezent and lungimea drumului minim parcurs de p aianjen, exprimat n num ar de segmente de lungime 1. Pe urm atoarele min + 1 linii sunt scrise nodurile prin care trece p aianjenul, c ate un nod pe o linie. Pentru ecare nod sunt scrise linia si coloana pe care se a a, separate printr-un spat iu. Restrict ii si preciz ari 1 m, n 140 1 k 2 (m n m n + 1) Lungimea drumului minim este cel mult 15000 Pentru datele de test exist a ntotdeauna solut ie. Dac a problema are mai multe solut ii, se va a sa una singur a.

13.2. PROBLEME REZOLVATE

199

Port iunile nesigure sunt specicate n sierul de intrare ntr-o ordine oarecare. Oricare dou a port iuni nesigure orizontale se pot intersecta cel mult ntr-un cap at. De asemenea, oricare dou a port iuni nesigure verticale se pot intersecta cel mult ntr-un cap at. Se acord a 30% din punctaj pentru determinarea lungimii drumului minim si 100% pentru rezolvarea ambelor cerint e. Exemplu paianjen.in 97 23 74 8 2425 2333 3031 3335 4454 6465 6575 7273 paianjen.out 8 23 22 32 42 52 62 63 73 74 Explicat ie Problema corespunde gurii de mai sus. Traseul optim este desenat cu linie groas a, iar port iunile nesigure sunt desenate punctat.

Timp maxim de execut ie/test: 1 secund a pentru Windows si 0.1 secunde pentru Linux. Indicat ii de rezolvare * prof. Carmen Popescu, C. N. Gh. Laz ar Sibiu Plasa de p aianjen se memoreaz a ntr-o matrice A cu M linii si N coloane, ecare element reprezent and un nod al plasei. A[i, j ] va codica pe patru bit i direct iile n care se poate face deplasarea din punctul (i, j ): bitul 0 este 1 dac a p aianjenul se poate deplasa n sus, bitul 1 este 1 dac a se poate deplasa la dreapta, bitul 2 - n jos, bitul 3 - la st anga. Rezolvarea se bazeaz a pe parcurgerea matriciei si ret inerea nodurilor parcurse ntr-o structur a de date de tip coad a (parcurgere BF - algoritm Lee). Drumul minim al p aianjenului se ret ine ntr-o alt a matrice B , unde B [i, j ] este 0 dac a nodul (i, j ) nu este atins, respectiv o valoare pozitiv a reprezent and pasul la care a ajuns paianjenul n drumul lui spre musc a. Deci elementul B [lm, cm] va cont ine lungimea drumului minim. Reconstituirea drumului minim se face pornind de la pozit ia mu stei, utiliz and, de asemenea un algoritm de tip BF, cu oprirea c aut arii n jurul nodului curent n momentul detect arii unui nod de pas mai mic cu o unitate dec at cel al nodului curent.

200

CAPITOLUL 13. ALGORITMI BFS-LEE TESTE Obs dimensiune mic a, re put ine rupte solut ie unic a, foarte multe re rupte caz particular, nici un r rupt re put ine rupte, dimensiune mare multe re rupte, dimensiune mare traseu n spiral a solutie unic a traseu scurt, greu de g asit cu backtracking multe re rupte, drum n serpentine rele interioare rupte, traseul pe frontier a o barier a pe mijlocul tablei, cu o fant a

# 0 1 2 3 4 5 6 7 8 9

m 10 10 2 140 131 100 100 138 140 138

n 8 8 2 140 131 130 7 138 140 138

k 11 46 0 20 2000 12771 23 9381 38365 273

min 16 74 2 278 103 12999 15 9050 555 274

O solut ie backtracking simplu obt ine maxim 20 de puncte, iar mbun at a tit maxim 30 de puncte. Codul surs a

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 static static static static static int m,n; // dimensiunile plasei int lp,cp; // pozitie paianjen(lin,col) int lm,cm; // pozitie musca(lin,col) int[] qi=new int[qdim]; // coada pentru i din pozitia (i,j) int[] qj=new int[qdim]; // coada pentru j din pozitia (i,j) 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();

13.2. PROBLEME REZOLVATE 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; for(i=0;i<m;i++) for(j=0;j<n;j++) p[i][j]=0xF; 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);

201

// 1111=toate firele !

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

202 { p[i1][j]^=dreapta; p[i1][j]^=stanga;

CAPITOLUL 13. ALGORITMI BFS-LEE

// 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)) { c[i-1][j]=t+1; qi[sc]=i-1; qj[sc]=j; sc=(sc+1)%qdim; d[i-1][j]=jos; } // N

} p[i1][j2]^=stanga;

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;

13.2. PROBLEME REZOLVATE } 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;

203

204

CAPITOLUL 13. ALGORITMI BFS-LEE j=cm; while((i!=lp)||(j!=cp)) // folosesc matricea 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; else if(d[i][j]==stanga) { c[i][j-1]=dreapta; else System.out.println("Eroare la traseu ...

c care nu mai e necesara !

j++; } j--; } !");

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

13.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(

13.2. PROBLEME REZOLVATE

205

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

206 }

CAPITOLUL 13. ALGORITMI BFS-LEE

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;} } if(gasitt) break; }//while return gasitt; }// bfs(...)

13.2. PROBLEME REZOLVATE

207

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 1 2 16 1 3 13 2 3 4 2 4 12 3 2 10 3 5 14

drum : 1 2 4 6 min=12 maxf=12 drum : 1 3 5 6 min= 4 maxf=16 drum : 1 3 5 4 6 min= 7 maxf=23 Nu mai exista drum de crestere a fluxului !!! fluxMax(1,6) = 23 : 0 12 11 0 0 0 0 0 0 12 0 0

208 4 3 9 4 6 20 5 4 7 5 6 4 */ 0 0 0 0 0 0 0 0 0 0 0 0 0 0 7 0

CAPITOLUL 13. ALGORITMI BFS-LEE 11 0 0 0 0 19 4 0

13.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 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 cte 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 / . \

13.2. PROBLEME REZOLVATE 1 4 2 1 2 1 3 1 2 3 3 4 4 . 2 . . 3 . . 4 Timp de executie: 0,5 s=0 - 2 \ 3 --- n+m+1=t / / / / / / secunde/test

209

*/

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( 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++)

210 {

CAPITOLUL 13. ALGORITMI BFS-LEE

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

13.2. PROBLEME REZOLVATE 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;} }

211

212

CAPITOLUL 13. ALGORITMI BFS-LEE 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( 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 14

Metoda optimului local greedy


14.1 Metoda greedy

Metoda Greedy are n vedere rezolvarea unor probleme de optim n care optimul global se determin a din estim ari succesive ale optimului local. Metoda Greedy se aplic a urm atorului tip de problem a: dintr-o mult ime de elemente C (candidat i la construirea solut iei problemei), se cere s a se determine o submult ime S , (solut ia problemei) care ndepline ste anumite condit ii. Deoarece este posibil s a existe mai multe solut ii se va alege solut ia care maximizeaz a sau minimizeaz a o anumit a funct ie obiectiv. O problem a poate rezolvat a prin tehnica (metoda) Greedy dac a ndepline ste proprietatea: dac a S este o solut ie, iar S este inclus a n S , atunci si S este o solut ie. Pornind de la aceast a condit ie, init ial se presupune c a S este mult imea vid a si se adaug a succesiv elemente din C n S , ajung and la un optim local. Succesiunea de optimuri locale nu asigur a, n general, optimul global. Dac a se demonstreaz a c a succesiunea de optimuri locale conduce la optimul global, atunci metoda Greedy este aplicabil a cu succes. Exist a urm atoarele variante ale metodei Greedy: 1. Se pleac a de la solut ia vid a pentru multimea S si se ia pe r and c ate un element din mult imea C . Dac a elementul ales ndepline ste condit ia de optim local, el este introdus n mult imea S . 2. Se ordoneaz a elementele mult imii C si se veric a dac a un element ndepline ste condit ia de apartenent a la mult imea S . 213

214

CAPITOLUL 14. METODA OPTIMULUI LOCAL - GREEDY

14.2

Algoritmi greedy

Algoritmii greedy sunt n general simpli si sunt folosit i la rezolvarea unor probleme de optimizare. In cele mai multe situat ii de acest fel avem: o mult ime de candidat i (lucr ari de executat, v arfuri ale grafului, etc.) o funct ie care veric a dac a o anumit a mult ime de candidat i constituie o solut ie posibil a, nu neap arat optim a, a problemei o funct ie care veric a dac a o mult ime de candidat i este fezabil a, adic a dac a este posibil s a complet am aceast a mult ime astfel nc at s a obt inem o solut ie posibil a, nu neap arat optim a, a problemei o funct ie de select ie care indic a la orice moment care este cel mai promit ator dintre candidat ii nc a nefolosit i o funct ie obiectiv care d a valoarea unei solut ii (timpul necesar execut arii tuturor lucr arilor ntr-o anumit a ordine, lungimea drumului pe care l-am g asit, etc) si pe care urm arim s a o optimiz am (minimiz am/maximiz am) Pentru a rezolva problema de optimizare, c aut am o solut ie posibil a care s a optimizeze valoarea funct iei obiectiv. Un algoritm greedy construie ste solut ia pas cu pas. Init ial, mult imea candidat ilor selectat i este vid a. La ecare pas, ncerc am s a ad aug am la aceast a mult ime pe cel mai promit a tor candidat, conform funct iei de select ie. Dac a, dup a o astfel de ad augare, mult imea de candidat i selectat i nu mai este fezabil a, elimin am ultimul candidat ad augat; acesta nu va mai niciodat a considerat. Dac a, dup a ad augare, mult imea de candidat i selectat i este fezabil a, ultimul candidat ad augat va r am ane de acum ncolo n ea. De ecare dat a c and l argim mult imea candidat ilor selectat i, veric am dac a aceast a mult ime constituie o solut ie posibil a a problemei. Dac a algoritmul greedy funct ioneaz a corect, prima solut ie g asit a va considerat a solut ie optim aa problemei. Solut ia optim a nu este n mod necesar unic a: se poate ca funct ia obiectiv s a aib a aceea si valoare optim a pentru mai multe solut ii posibile. Descrierea formal a a unui algoritm greedy general este: function greedy (C ) // C este mult imea candidat ilor S // S este mult imea n care construim solut ia while not solutie(S ) and C = do x un element din C care maximizeaz a/minimizeaz a select(x) C C {x} if f ezabil(S {x}) then S S {x} if solutie(S ) then return S else return nu exist a solut ie

14.3. EXEMPLE

215

14.3

Exemple

Dintre problemele clasice care se pot rezolva prin metoda greedy ment ion am: plata restului cu num ar minim de monezi, problema rucsacului, sortare prin select ie, determinarea celor mai scurte drumuri care pleac a din acela si punct (algoritmul lui Dijkstra), determinarea arborelui de cost minim (algoritmii lui Prim si Kruskal), determinarea mult imii dominante, problema color arii intervalelor, codicarea Human, etc.

14.3.1

Problema continu a a rucsacului

Se consider a n obiecte. Obiectul i are greutatea gi si valoarea vi (1 i n). O persoan a are un rucsac. Fie G greutatea maxim a suportat a de rucsac. Persoana n cauz a dore ste s a pun a n rucsac obiecte astfel nc at valoarea celor din rucsac s a e c at mai mare. Se permite fract ionarea obiectelor (valoarea p art ii din obiectul fract ionat ind direct proport ional a cu greutatea ei!). Not am cu xi [0, 1] partea din obiectul i care a fost pus a n rucsac. Practic, trebuie s a maximiz am funct ia
n

f (x) =
i=1

xi ci .

Pentru rezolvare vom folosi metoda greedy. O modalitate de a ajunge la solut ia optim a este de a considera obiectele n ordinea descresc atoare a valorilor i utilit a tilor lor date de raportul v ( i = 1 , ..., n ) s i de a le nc a rca ntregi n rucsac gi p an a c and acesta se umple. Din aceast a cauz a presupunem n continuare c a v2 vn v1 ... . g1 g2 gn Vectorul x = (x1 , x2 , ..., xn ) se nume ste solut ie posibil a dac a xi [0, 1], i = 1, 2, ..., n n i=1 xi gi G iar o solut ie posibil a este solut ie optim a dac a maximizeaz a funct ia f . Vom nota Gp greutatea permis a de a se nc arca n rucsac la un moment dat. Conform strategiei greedy, procedura de rezolvare a problemei este urm atoarea: procedure rucsac() Gp G for i = 1, n if Gp > gi then Gp Gp gi

216

CAPITOLUL 14. METODA OPTIMULUI LOCAL - GREEDY else Gp xi gi for j = i + 1, n xj 0 Vectorul x are forma x = (1, ..., 1, xi , 0, ..., 0) cu xi (0, 1] si 1 i n.

Propozit ia 1 Procedura rucsac() furnizeaz a o solut ie optim a. Demonstrat ia se g ase ste, de exemplu, n [25] la pagina 226.

14.3.2

Problema plas arii textelor pe o band a

S a presupunem c a trebuie s a plas am n texte T1 , T2 , ..., Tn , de lungimi date L1 , L2 , ..., Ln , pe o singur a band a sucient de lung a. Atunci c and este necesar a citirea unui text sunt citite toate textele situate naintea lui pe band a. Modalitatea de plasare a celor n texte pe band a corespunde unei permut ari p a mult imii {1, 2, ..., n}, textele ind a sezate pe band a n ordinea Tp(1) Tp(2) ...Tp(n) . Intr-o astfel de aranjare a textelor pe band a, timpul mediu de citire a unui text este: k n 1 f (p) = Lp(i) n i=1
k=1

Se dore ste determinarea unei permut ari care s a asigure o valoare minim aa timpului mediu de citire. Rezolvarea problemei este foarte simpl a: se sorteaz a cresc ator textele n funct ie de lungimea lor si se plaseaz a pe band a n ordinea dat a de sortare. Urm atoarea propozit ie ([25] pagina 99) ne permite s a m siguri c a n acest mod ajungem la o plasare optim a. Propozit ia 2 Dac a L1 L2 ... Ln atunci plasarea textelor corespunz atoare permutarii identice este optim a. Demonstrat ie: Fie p = (p1 , p2 , ..., pn ) o plasare optim a. Dac a exist a i < j cu Lp(i) Lp(j ) atunci consider am permutarea p obt inut a din p prin permutarea elementelor de pe pozit iile i si j . Atunci f (p ) f (p) = (n j + 1)(Lp(i) Lp(j ) ) + (n i + 1)(Lp(j ) Lp(i) ) deci Cum p este o plasare optim a si f (p ) f (p) rezult a c a f (p ) = f (p) deci s i p este o plasare optim a. Aplic and de un num ar nit de ori acest rat ionament, trecem de la o permutare optim a la alt a permutare optim a p an a ajungem la permutarea identic a. Rezult a c a permutarea identic a corespunde unei plas ari optime. f (p ) f (p) = (Lp(j ) Lp(i) )(j i) 0.

14.3. EXEMPLE

217

14.3.3

Problema plas arii textelor pe m benzi

S a presupunem c a trebuie s a plas am n texte T1 , T2 , ..., Tn , de lungimi date L1 , L2 , ..., Ln , pe m benzi sucient de lungi. Atunci c and este necesar a citirea unui text sunt citite toate textele situate naintea lui pe banda respectiv a. Se dore ste determinarea unei plas ari a celor n texte pe cele m benzi care s a asigure o valoare minim a a timpului mediu de citire. Rezolvarea problemei este foarte simpl a: se sorteaz a cresc ator textele n funct ie de lungimea lor si

plasarea textelor pe benzi se face n ordinea dat a de sortare, ncep and cu primul text (cu cea mai mic a lungime) care se plaseaz a pe prima band a, iar mai departe ecare text se plaseaz a pe banda urm atoare celei pe care a fost plasat textul anterior. Presupun and c a L1 L2 ... Ln , se poate observa c a textul Ti va plasat 1 m n continuarea celor aate deja pe aceast a band a iar dup a pe banda i i m textul Ti , pe aceea si band a cu el, vor plasate textele i + m, i + 2m, ..., deci nc a ni texte (demonstrat iile se pot consulta, de exemplu, n [25]). m

14.3.4

Maximizarea unei sume de produse

Se dau mult imile de numere ntregi A = {a1 , a2 , ..., an } si B = {b1 , b2 , ..., bm }, unde n m. S a se determine o submult ime B = {x1 , x2 , ..., xn } a lui B astfel nc at valoarea expresiei E = a1 x1 + a2 x2 + an xn s a e maxim a. Vom sorta cresc ator elementele celor dou a mult imi. Putem presupune acum c a a1 a2 ... an si b1 b2 ... bm . Dac a toate elementele din A sunt pozitive vom lua xn = bm , xn1 = bm1 , si a sa mai departe. Dac a toate elementele din A sunt negative vom lua x1 = b1 , x2 = b2 , si a sa mai departe. Dac a primele n1 elemente din A sunt strict negative si ultimele n2 elemente din A sunt pozitive sau nule (n1 + n2 = n) atunci vom lua x1 = b1 , x2 = b2 , ..., si xn = bm , xn1 = bm1 , ..., xnn2 +1 = bmn2 +1 . xn1 = bn1

14.3.5

Problema stat iilor

Se consider a o mult ime de numere naturale A = {a1 , a2 , ..., an } care reprezint a coordonatele a n stat ii pe axa real a. Vom presupune a1 < a2 < ... < an . S a se

218

CAPITOLUL 14. METODA OPTIMULUI LOCAL - GREEDY

determine o submult ime cu num ar maxim de stat ii cu proprietatea c a distant a dintre dou a stat ii al aturate este cel putin d (o distant a dat a). Rezolvarea problemei este urm atoarea: se alege stat ia 1,

se parcurge sirul stat iilor si se alege prima stat ie nt alnit a care este la distant a cel put in d fat a de stat ia aleas a anterior; se repet a acest pas p an a c and s-au vericat toate stat iile. Propozit ia 3 Exist a o solut ie optim a care cont ine stat ia 1. ie a problemei care nu cont ine Demonstrat ie: Fie B = {ai1 , ai2 , ..., aim } o solut ia i1 se poate nlocui cu stat ia stat ia 1 (deci ai1 > a1 ). Evident |ai2 ai1 | d. Stat 1 pentru c a |ai2 a1 | = |ai2 ai1 + ai1 a1 | = |ai2 ai1 | + |ai1 a1 | > d. Dup a selectarea stat ie 1 se pot selecta (pentru obt inerea unei solut ii optime) numai stat ii situate la distant e cel put in d fat a de stat ia 1. Pentru aceste stat ii repet am strategia sugerat a de propozit ia anterioar a.

14.3.6

Problema cutiilor

Se dore ste obt inerea unei congurat ii de n numere plasate pe n + 1 pozit ii (o pozit ie ind liber a) dintr-o congurat ie init ial a dat a n care exist a o pozit ie liber a si cele n numere plasate n alt a ordine. O mutare se poate face dintr-o anumit a pozit ie numai n pozit ia liber a. Prezent am algoritmul de rezolvare pe baza urm atorului exemplu: 1 2 3 4 5 6 7 init ial 3 1 2 6 5 4 nal 1 2 3 4 5 6 rezolvat Vom proceda astfel: cutia goal a din congurat ia init ial a se a a pe pozit ia 3 dar pe aceast a pozit ie, n congurat ia nal a, se a a num arul 3; c aut am num arul 3 din congurat ia init ial a ( l g asim pe pozit ia 1) si l mut am pe pozit ia cutiei goale; acum, cutia goal a se a a pe pozit ia 1; vom repeta acest rat ionament p an a c and pozit iile cutiilor goale, n cele dou a congurat ii, coincid. 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 3 2 6 5 4 nal 1 2 3 4 5 6 rezolvat

14.3. EXEMPLE 1 2 modicat 1 2 nal 1 2 rezolvat Acum vom c auta o cutie cutia goal a. 1 2 modicat 1 2 nal 1 2 rezolvat 3 4 5 3 6 3 4 nerezolvat a 3 3 3 4 6 5 4

219 6 7 5 4 5 6 si vom muta num arul din acea cutie n 6 5 5 7 4 6

Repet am rat ionamentul prezentat la nceput si vom continua p an a c and toate numerele ajung pe pozit iile din congurat ia nal a. 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

14.3.7

Problema sub sirurilor

S a se descompun a un sir de n numere ntregi n sub siruri strict cresc atoare astfel nc at numerele lor de ordine din sirul init ial s a e ordonate cresc ator n sub sirurile formate si num arul sub sirurilor s a e minim. Metota de rezolvare este urm atoarea: se parcurg elementele sirului init ial unul dup a altul si pentru ecare element xi se caut a un sub sir existent la care xi se poate ad auga la sf ar sit; dac a nu exist a un astfel de sub sir se creeaz a un sub sir nou care va cont ine ca prim element pe xi ; dac a exist a mai multe sub siruri la care se poate ad auga xi , se va alege acela care are cel mai mare ultim element. Observat ie: Ultimele elemente din ecare sub sir (care sunt si elemente maxime din aceste sub siruri) formeaz a un sir descresc ator. Acest fapt permite utilizarea c aut arii binare pentru determinarea sub sirului potrivit pentru elementul xi atunci c and prelucr am acest element.

14.3.8

Problema intervalelor disjuncte

Se consider a n intervale nchise pe axa real a [a1 , b1 ], [a2 , b2 ], ..., [an , bn ]. Se cere selectarea unor intervale disjuncte astfel nc at num arul acestora s a e maxim. Metoda de rezolvare este urm atoarea: se sorteaz a intervalele cresc ator dup a cap atul din dreapta; se selecteaz a primul interval; se parcurg intervalele, unul

220

CAPITOLUL 14. METODA OPTIMULUI LOCAL - GREEDY

dup a altul, p an a se g ase ste un interval [ai , bi ] disjunct cu ultimul interval ales si se adaug a acest interval la solut ie, el devenind astfel ultimul interval ales; acest procedeu continu a p an a c and nu mai r am an intervale de analizat. Propozit ia 4 Exist a o solut ie optim a care cont ine primul interval dup a sortare.

14.3.9

Problema alegerii taxelor

Se dau dou a siruri cu c ate 2n numere ntregi ecare, x = (x1 , x2 , ..., x2n ) si y = (y1 , y2 , ..., y2n ) reprezent and taxe. S a se determine sirul z = (z1 , z2 , ..., z2n ), unde zi {xi , yi } (1 i 2n), astfel nc at suma tuturor elementelor din sirul z s a e minim a si acest sir s a cont in a exact n elemente din sirul x si n elemente din sirul y . Metoda de rezolvare este urm atoarea: se construie ste sirul t = x y ( n care ti = xi yi ) si se sorteaz a cresc ator; se aleg din sirul x elementele corespunz atoare primelor n elemente din sirul t sortat iar celelalte n elemente se iau din sirul y .

14.3.10

Problema acoperirii intervalelor

Se consider a n intervale nchise [a1 , b1 ], [a2 , b2 ], ..., [an , bn ]. S a se determine o mult ime cu num ar minim de alemente C = {c1 , c2 , ..., cm } care s a acopere toate cele n intervale date (spunem c a ci acoper a intervalul [ak , bk ] dac a ci [ak , bk ]). Metoda de rezolvare este urm atoarea: se sorteaz a intervalele cresc ator dup a cap atul din dreapta. Presupunem c a b1 b2 ... bn . Primul punct ales este c1 = b1 . Parcurgem intervalele p an a cand g asim un interval [ai , bi ] cu ai > c1 si alegem c2 = bi . Parcurgem mai departe intervalele p an a cand g asim un interval [aj , bj ] cu aj > c2 si alegem c3 = bj . Repet am acest procedeu p an a c and termin am de parcurs toate intervalele.

14.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;

14.3. EXEMPLE static int[] p; // predecesor in arbore

221

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(k=1;k<=m;k++) { st.nextToken(); st.nextToken(); st.nextToken(); } for(j=1;j<=n;j++) cost[i][j]=oo;

i=(int)st.nval; j=(int)st.nval; 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) { 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;

222 }//for k

CAPITOLUL 14. METODA OPTIMULUI LOCAL - GREEDY

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 2 1 3 2 3 3 4 4 5 5 6 4 6 */

3 1 2 1 1 3 2

1 3 2 3 4 3 5 4 6 4 cost=7

Alg Prim cu distant 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;

14.3. EXEMPLE cost=new int[n+1][n+1]; esteInArbore=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[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;

223

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

224

CAPITOLUL 14. METODA OPTIMULUI LOCAL - GREEDY

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 2 1 3 2 3 3 4 4 5 5 6 4 6 */

3 1 2 1 1 3 2

1 3 2 3 4 3 5 4 6 4 cost=7

Alg Prim cu heap import java.io.*; // arbore minim de acoperire: algoritmul lui Prim 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];

14.3. EXEMPLE 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; }

225

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

226

CAPITOLUL 14. METODA OPTIMULUI LOCAL - GREEDY q--; for(v=1;v<=q;v++) if(w[u][hnod[v]]<oo) relax(u,hnod[v]);

// noduri nefinalizate = in heap 1..q // cost finit ==> exista arc (u,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; 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]) // 1 <---> q

14.3. EXEMPLE 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]; }// extragMin(...) // hnod[1] a ajuns deja (!) in hnod[q]

227

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

3 1 3 2

228 2 3 3 4 4 5 5 6 4 6 */ 2 1 1 3 2

CAPITOLUL 14. METODA OPTIMULUI LOCAL - GREEDY 3 4 4 5 4 6 costa=7

14.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; 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

14.3. EXEMPLE

229

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; i=p; j=u; while(i<j) {

230

CAPITOLUL 14. METODA OPTIMULUI LOCAL - GREEDY 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

14.3.13

Algoritmul lui Dijkstra

Alg Dijkstra cu distant 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++)

14.3. EXEMPLE { 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;

231

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: "); 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

232 1 2 1 3 2 3 3 4 5 4 5 6 4 6 */ 4 1 2 1 1 3 2

CAPITOLUL 14. METODA OPTIMULUI LOCAL - GREEDY 1-->2 1-->3 1-->4 1-->5 1-->6 dist=3 dist=1 dist=2 dist=3 dist=4 drum: drum: drum: drum: drum: 1 1 1 1 1 3 3 3 3 3 2 4 4 5 4 6

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

14.3. EXEMPLE

233

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)

234 {

CAPITOLUL 14. METODA OPTIMULUI LOCAL - GREEDY

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

14.3. EXEMPLE

235

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 2 1 3 2 3 3 4 5 4 5 6 4 6 */

4 1 2 1 1 3 2

1-->1 1-->2 1-->3 1-->4 1-->5 1-->6

dist=0 dist=3 dist=1 dist=2 dist=3 dist=4

drum: drum: drum: drum: drum: drum:

1 1 1 1 1 1

3 3 3 3 3

2 4 4 5 4 6

Alg Dijkstra cu distant e, n graf orientat import java.io.*; // drumuri minime de la nodSursa class Dijkstra // O(n^2) {

236 static static static static static static

CAPITOLUL 14. METODA OPTIMULUI LOCAL - GREEDY final int oo=0x7fffffff; int n,m; int[][] cost; boolean[] esteFinalizat; int[] p; // predecesor in drum 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; } d[nodSursa]=0; for(k=1;k<=n;k++) // O(n) { min=oo; for(j=1;j<=n;j++) // O(n) {

14.3. EXEMPLE

237

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.*; class DijkstraOrientatHeap // distante minime de la nodSursa // pastrate in MinHeap ==> O(n log n) ==> OK !!!

238 {

CAPITOLUL 14. METODA OPTIMULUI LOCAL - GREEDY

static final int oo=0x7fffffff; static int n,m; static int[][]w; static int[]d; static int[]p; static int[] hd; static int[] hnod; static int[] pozh; // matricea costurilor // distante de la nod catre arbore // predecesorul nodului in arbore // hd[i]=distanta din pozitia i din heap // hnod[i]= nodul din pozitia i din heap // 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) {

14.3. EXEMPLE

239

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; 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++) if(w[u][hnod[v]]<oo) relax(u,hnod[v]); } }//dijkstra(...) static void relax(int u,int v) { if(d[u]+w[u][v]<d[v]) {

// noduri nefinalizate = in heap 1..q // cost finit ==> exista arc (u,v) // relax si refac heap

240

CAPITOLUL 14. METODA OPTIMULUI LOCAL - GREEDY 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;

14.3. EXEMPLE

241

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 2 1 3 2 3 3 4 5 4 5 6 4 6 */

4 1 2 1 1 3 2

1-->1 1-->2 1-->3 1-->4 1-->5 1-->6

dist=0 drum: 1 dist=4 drum: 1 2 dist=1 drum: 1 3 dist=2 drum: 1 3 4 Nu exista drum! dist=4 drum: 1 3 4 6

14.3.14

Urgent a - OJI2002 cls 11

Autorit a tile dintr-o zon a de munte intent ioneaz a s a stabileasc a un plan de urgent a pentru a react iona mai ecient la frecventele calamit a ti naturale din zon a. In acest scop au identicat N puncte de interes strategic si le-au numerotat distinct de la 1 la N . Punctele de interes strategic sunt conectate prin M c ai de acces av and

242

CAPITOLUL 14. METODA OPTIMULUI LOCAL - GREEDY

priorit a ti n funct ie de important a. Intre oricare dou a puncte de interes strategic exist a cel mult o cale de acces ce poate parcurs a n ambele sensuri si cel put in un drum (format din una sau mai multe c ai de acces) ce le conecteaz a. In cazul unei calamit a ti unele c ai de acces pot temporar ntrerupte si astfel ntre anumite puncte de interes nu mai exist a leg atur a. Ca urmare pot rezulta mai multe grupuri de puncte n a sa fel nc at ntre oricare dou a puncte din acela si grup s a existe m acar un drum si ntre oricare dou a puncte din grupuri diferite s a nu existe drum. Autorit a tile estimeaz a gravitatea unei calamit a ti ca ind suma priorit a tilor c ailor de acces distruse de aceasta si doresc s a determine un scenariu de gravitate maxim a, n care punctele de interes strategic s a e mp art ite ntr-un num ar de K grupuri. Date de intrare Fi sierul de intrare URGENTA.IN are urm atorul format: N M K i1 j1 p1 - ntre punctele i1 si j1 exist a o cale de acces de prioritate p1 i2 j2 p2 - ntre punctele i2 si j2 exist a o cale de acces de prioritate p2 ... iM jM pM - ntre punctele iM si jM exist a o cale de acces de prioritate pM

Date de ie sire Fi sierul de ie sire URGENTA.OUT va avea urm atorul format: gravmax - gravitatea maxim a C - num arul de c ai de acces ntrerupte de calamitate k1 h1 - ntre punctele k1 si h1 a fost ntrerupt a calea de acces k2 h2 - ntre punctele k2 si h2 a fost ntrerupt a calea de acces ... kC hC - ntre punctele kC si hC a fost ntrerupt a calea de acces Restrict ii si preciz ari 0 < N < 256 N 2 < M < 32385 0<K <N +1 Priorit a tile c ailor de acces sunt ntregi strict pozitivi mai mici dec at 256. Un grup de puncte poate cont ine ntre 1 si N puncte inclusiv. Dac a exist a mai multe solut ii, programul va determina una singur a. Exemplu

14.3. EXEMPLE URGENTA.IN 7 11 4 121 132 173 243 342 351 361 375 455 564 673 URGENTA.OUT 27 8 13 17 24 34 37 45 56 67

243

Timp maxim de executare: 1 secund a / test Codul surs a 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( new BufferedWriter(new FileWriter("urgenta.out"))); st.nextToken(); n=(int)st.nval; st.nextToken(); m=(int)st.nval; st.nextToken(); ncc=(int)st.nval;

244

CAPITOLUL 14. METODA OPTIMULUI LOCAL - GREEDY

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 // { 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]; O(n)

14.3. EXEMPLE 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++; 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);

245

// 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

246 }

CAPITOLUL 14. METODA OPTIMULUI LOCAL - GREEDY

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

14.3.15

Reactivi - OJI2004 cls 9

Intr-un laborator de analize chimice se utilizeaz a N reactivi. Se stie c a, pentru a evita accidentele sau deprecierea reactivilor, ace stia trebuie s a e stocat i n condit ii de mediu speciale. Mai exact, pentru ecare reactiv x, se precizeaz a intervalul de temperatur a [minx , maxx ] n care trebuie s a se ncadreze temperatura de stocare a acestuia. Reactivii vor plasat i n frigidere. Orice frigider are un dispozitiv cu ajutorul c aruia putem stabili temperatura (constant a) care va n interiorul acelui frigider (exprimat a ntr-un num ar ntreg de grade Celsius). Cerint a Scriet i un program care s a determine num arul minim de frigidere necesare pentru stocarea reactivilor chimici. Date de intrare Fi sierul de intrare react.in cont ine: pe prima linie num arul natural N , care reprezint a num arul de reactivi; pe ecare dintre urm atoarele N linii se a a min max (dou a numere ntregi separate printr-un spat iu); numerele de pe linia x + 1 reprezint a temperatura minim a, respectiv temperatura maxim a de stocare a reactivului x.

14.3. EXEMPLE

247

Date de ie sire Fi sierul de ie sire react.out va cont ine o singur a linie pe care este scris num arul minim de frigidere necesar. Restrict ii si preciz ari 1 N 8000 100 minx maxx 100 (numere ntregi, reprezent and grade Celsius), pentru orice x de la 1 la N un frigider poate cont ine un num ar nelimitat de reactivi Exemple react.in 3 -10 10 -25 20 50 react.out 2 react.in 4 25 57 10 20 30 40 react.out 3 react.in 5 -10 10 10 12 -20 10 7 10 78 react.out 2

Timp maxim de execut ie: 1 secund a/test Indicat ii de rezolvare - descriere solut ie * Solut ie prezentat a de Mihai Stroe, GInfo nr. 14/4 Problema se poate rezolva prin metoda greedy. O variant a mai explicit a a enunt ului este urm atoarea: Se consider a N intervale pe o ax a. S a se aleag a un num ar minim de puncte astfel nc at ecare interval s a cont in a cel put in unul dintre punctele alese. Facem o prim a observat ie: pentru orice solut ie optim a exist a o solut ie cu acela si num ar de puncte (frigidere), n care ecare punct s a e sf ar situl unui interval. Aceasta se poate obt ine mut and ecare punct spre dreapta, p an a c and ar ajunge la sf ar situl intervalului care se termin a cel mai repede, dintre intervalele care l cont in. Se observ a c a noua solut ie respect a restrict iile din enunt . In continuare ne vom concentra pe g asirea unei solut ii de acest tip. Sort am reactivii dup a sf ar situl intervalului. Pentru intervalul care se termin a cel mai repede, alegem ultimul punct al s au ca temperatur a a unui frigider. Se observ a c a aceast a alegere este cea mai bun a, dintre toate alegerile unui punct n intervalul respectiv, n sensul c a mult imea intervalelor care cont in punctul este mai mare (conform relat iei de incluziune), dec at mult imea corespunz atoare oric arei alte alegeri. Acest fapt este adev arat, deoarece mutarea punctului mai la st anga nu duce la selectarea unor noi intervale. Intervalele care cont in punctul respectiv sunt eliminate (reactivii corespunz atori pot plasat i ntr-un frigider), iar procesul continu a cu intervalele r amase, n acela si

248 mod.

CAPITOLUL 14. METODA OPTIMULUI LOCAL - GREEDY

Analiza complexit a tii Not am cu D num arul de temperaturi ntregi din intervalul care cont ine temperaturile din enunt . Se observ a c a D este cel mult 201. Citirea datelor de intrare are ordinul de complexitate O(N ). Sortarea intervalelor dup a cap atul din dreapta are ordinul de complexitate O(N logN ). Urmeaz a F pa si, unde F este num arul de frigidere selectate. Deoarece ecare frigider este setat la o temperatur a ntreag a, F D. In cadrul unui pas, determinarea intervalului care se termin a cel mai repede, pe baza vectorului sortat, are ordinul de complexitate O(1). Eliminarea intervalelor care cont in un anumit punct (sf ar situl intervalului care se termin a cel mai repede) are ordinul de complexitate O(N ). A sarea rezultatului are ordinul de complexitate O(1). In concluzie, ordinul de complexitate al algoritmului de rezolvare a acestei probleme este O(N D + N logN ); deoarece n general D > logN , consider am ordinul de complexitate ca ind O(N D). Codul surs a

import java.io.*; class Reactivi { static int n; static int ni=0; static int nf=0; static int[] ngf; static int[] x1,x2;

// // // // //

n=nr reactivi ni=nr interschimbari in quickSort n=nr frigidere ng=nr grade in frigider 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++) {

14.3. EXEMPLE 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; 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);

249

250 } }

CAPITOLUL 14. METODA OPTIMULUI LOCAL - GREEDY

14.3.16

Pal - ONI2005 cls 9

Autor: Silviu G anceanu Print ul Algorel este n ncurc atur a din nou: a fost prins de Sp anul cel Negru n ncercarea sa de a o salva pe print es a si acum este nchis n Turnul cel Mare. Algorel poate evada dac a g ase ste combinat ia magic a cu care poate deschide poarta turnului. Print ul stie cum se formeaz a aceast a combinat ie magic a: trebuie s a utilizeze toate cifrele scrise pe u sa turnului pentru a obt ine dou a numere palindroame, astfel nc at suma lor s a e minim a, iar aceast a sum a este combinat ia magic a ce va deschide u sa. Primul num ar palindrom trebuie s a aib a cel put in L cifre, iar cel de-al doilea poate avea orice lungime diferit a de 0. Numerele palindroame formate nu pot ncepe cu cifra 0. Acum intervenit i dumneavoastr a n poveste, ind prietenul s au cel mai priceput n algoritmi. Prin noul super-telefon al s au, print ul transmite num arul de aparit ii a ec arei cifre de pe u sa turnului precum si lungimea minim a L a primului num ar, iar dumneavoastr a trebuie s a-i trimitet i c at mai repede numerele cu care poate obt ine combinat ia magic a. Cerint a Av and datele necesare, aat i dou a numere palindroame cu care se poate obt ine combinat ia magic a. Date de intrare Prima linie a sierului pal.in cont ine un num ar ntreg L reprezent and lungimea minim a a primului num ar. Urmeaz a 10 linii: pe linia i + 2 se va aa un num ar ntreg reprezent and num arul de aparit ii ale cifrei i, pentru i cu valori de la 0 la 9. Date de ie sire Prima linie a sierului de ie sire pal.out cont ine primul num ar palidrom, iar cea de-a doua linie cont ine cel de-al doilea num ar palindrom. Dac a exist a mai multe solut ii se va scrie doar una dintre ele. Restrict ii si preciz ari In total vor cel mult 100 de cifre 1 L < 100 si L va mai mic dec at num arul total de cifre Pentru datele de test va exista ntotdeauna solut ie: se vor putea forma din cifrele scrise pe u sa turnului dou a numere care ncep cu o cifr a diferit a de 0, iar primul num ar s a aib a cel put in L cifre

14.3. EXEMPLE

251

Un num ar este palindrom dac a el coincide cu r asturnatul s au. De exemplu 12321 si 7007 sunt numere palindroame, n timp ce 109 si 35672 nu sunt. Pentru 30% dintre teste, num arul total de cifre va cel mult 7; pentru alte 40% din teste num arul total de cifre va cel mult 18, iar pentru restul de 30% din teste num arul total de cifre va mai mare sau egal cu 30. Fiecare linie din sierul de intrare si din sierul de ie sire se termin a cu marcaj de sf ar sit de linie. Exemplu pal.in pal.out 5 10001 3 222 2 3 0 0 0 0 0 0 0 Explicat ie Pentru acest exemplu avem L = 5, 3 cifre de 0, 2 cifre de 1 si 3 cifre de 2. Cifrele de la 3 la 9 lipsesc de pe u sa turnului. Cele dou a palindroame cu care se genereaz a combinat ia magic a sunt 10001 si 222. Combinat ia magic a va suma acestora si anume 10223 (care este suma minim a pe care o putem obt ine).

Timp maxim de execut ie/test: 1 sec sub Windows si 1 sec sub Linux Indicat ii de rezolvare - descriere solut ie Solut ia ocial a, Silviu G anceanu Problema se rezolv a utiliz and tehnica greedy. Not am num arul total de cifre cu N C . Se observ a c a, pentru ca suma celor dou a numere palindroame s a e minim a, trebuie ca lungimea maxim a a celor dou a numere s a e c at mai mic a. A sadar, pentru nceput, se stabile ste lungimea exact a a primului num ar (care va cel mai lung), n funct ie de L n modul urm ator: 1. dac a L < N C/2, atunci lungimea primului palindrom va aleas a astfel nc at cele dou a numere s a e c at mai apropiate ca lungime 2. dac a L >= N C/2 apar cazuri particulare si se stabile ste dac a lungimea primului palindrom cre ste sau nu cu o unitate. Av and lungimile numerelor stabilite putem merge mai departe utilz and strategia greedy, observ and c a cifrele mai mici trebuie s a ocupe pozit ii c at mai semnicative. Pentru a realiza acest lucru se vor completa n paralel cele dou a numere cu cifrele parcurse n ordine cresc atoare stabilind n care din cele dou a numere se vor pozit iona. De asemenea, trebuie avut n vedere ca niciunul din cele dou a numere s a nu nceap a cu cifra 0. Datele de test au fost construite astfel nc at si solut ii neoptime s a obt in a puncte, gradat, n funct ie de optimiz arile efectuate asupra implement arii.

252 Codul surs a

CAPITOLUL 14. METODA OPTIMULUI LOCAL - GREEDY

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

14.3. EXEMPLE

253

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

254

CAPITOLUL 14. METODA OPTIMULUI LOCAL - GREEDY 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)

14.3. EXEMPLE { int i; int n=x.length; for(i=0;i<=9;i++) if(fc[i]>0) { x[k]=i; fc[i]--; x[n-k-1]=i; fc[i]--; break; } }

255

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

14.3.17

S ant - ONI2006 cls 9

Cei n det inut i ai unei nchisori, numerotat i de la 1 la n, trebuie s a sape un sant dispus n linie dreapt a ntre dou a puncte A si B , situate la distant a de 250 km unul de cel alalt, pe care exist a 251 borne kilometrice numerotate de la 0 la 250. Fiecare det inut are ns a o pretent ie, e doar democrat ie, nu?: el dore ste s a sape doar undeva n zona dintre borna x si borna y . Pe l ang a aceste pretent ii nchisoarea se confrunt a si cu o alt a problem a: nu are sucient i paznici angajat i.

256

CAPITOLUL 14. METODA OPTIMULUI LOCAL - GREEDY

Cerint a Cunosc andu-se num arul n de det inut i si pretent iile lor, s a se determine locul (locurile) unde vor pu si det inut ii s a sape ntr-o zi de munc a, respect andu-se pretent iile lor, astfel nc at num arul de paznici necesari pentru a p azi cei n det inut i s a e minim. Intervalul n care poate p azi un paznic nu poate cont ine dou a sau mai multe zone disjuncte dintre cele exprimate de det inut i n preferint ele lor. Date de intrare Fi sierul de intrare sant.in are pe prima linie num arul n de det inut i. Pe ecare dintre urm atoarele n linii exist a c ate dou a numere naturale, ai bi , separate printrun spat iu (ai bi ), care reprezint a pretent ia det inutului. Mai exact pe linia i + 1 (1 i n) este descris a pretent ia det inutului cu num arul de ordine i. Date de ie sire Fi sierul de ie sire sant.out va cont ine pe prima linie num arul natural k reprezent and num arul minim de paznici necesari pentru paza celor n det inut i sco si la lucru. Pe urm atoarele 2k linii vor descrise locurile unde vor pu si s a sape det inut ii, astfel: ecare pereche de linii (2j, 2j +1) va cont ine pe linia 2j trei numere naturale p xj yj , separate prin c ate un spat iu reprezent and num arul de ordine al paznicului si bornele kilometrice xj c si yj unde va p azi paznicul p, iar pe linia 2j + 1 vor scrise numerele de ordine ale det inut ilor care sap a n aceast a zon a, separate prin c ate un spat iu, ordonate cresc ator. Restrict ii si preciz ari 1 n 10000 0 ai bi 250, pentru orice i, 1 i n 0 xj yj 250, pentru orice j , 1 j k un det inut poate s a sape si ntr-un singur punct ( n dreptul bornei kilometrice numerotat a cu x) n cazul n care exist a mai multe solut ii se va a sa una singur a numerele de ordine ale paznicilor vor scrise n sier n ordine cresc atoare numerotarea paznicilor ncepe de la 1 Exemplu

14.3. EXEMPLE .in 3 0 20 8 13 30 60 4 10 203 2 53 30 403 5 7 33 .out 2 1 8 13 12 2 30 60 3 3 1 10 20 1 255 24 3 30 40 3 2 1 30 30 1234 2 27 28 5 Explicat ie sunt necesari 2 paznici: paznicul 1 va p azi ntre borna 8 si borna 13, iar det inut ii p azit i sunt 1 si 2; paznicul 2 va p azi ntre borna 30 si borna 60, iar det inutul p azit este 3 sunt necesari 3 paznici: paznicul 1 va p azi ntre borna 10 si borna 20, iar det inutul p azit este 1; paznicul 2 va p azi la borna 5, iar det inut ii p azit i sunt 2 si 4; paznicul 3 va p azi ntre borna 30 si borna 40, iar det inutul p azit este 3

257

5 10 30 30 32 0 30 27 30 27 28

sunt necesari 2 paznici: paznicul 1 va p azi la borna 30, iar det inut ii p azit i sunt 1, 2, 3 si 4; paznicul 2 va p azi ntre borna 27 si borna 28, iar det inutul p azit este 5 !Solut ia nu este unic a!

Timp maxim de execut ie/test: 1 secund a (Windows), 0.5 secunde (Linux) Indicat ii de rezolvare - descriere solut ie Solut ia comisiei Problema cere, de fapt, determinarea num arului minim de intersect ii ntre segmentele determinate de kilometrul minim si maxim ntre care sap a un det inut. Pentru a le determina procedez astfel: ordonez intervalele cresc ator dup a kilometrul minim si descresc ator dup a kilometrul maxim pun primul det inut (deci cel cu intervalul de s apare cel mai mare) n grija primului paznic pentru toate celelalte caut un paznic care mai p aze ste det inut i si care poate p azi si acest det inut (adic a intersect ia segmentelor s a e nevid a) dac a g asesc ajustez intervalul de s apare ca s a poat a s apa si acest det inut altfel (dac a nu g asesc) mai am nevoie de un paznic si l dau n grija acestuia

258

CAPITOLUL 14. METODA OPTIMULUI LOCAL - GREEDY

Datorit a faptului c a intervalele sunt sortate, cresc ator dup a cap atul inferior, n momentul c and un interval nu se mai intersecteaz a cu cel deja determinat, este sigur c a nici un alt interval ce l urmeaz a nu se mai intersecteaz a, lucru ce asigur a determinarea num arului minim de paznici. Codul surs a

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

14.3. EXEMPLE { 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); // 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

259

260

CAPITOLUL 14. METODA OPTIMULUI LOCAL - GREEDY

14.3.18

Cezar - OJI2007 cls 11

In Roma antic a exist a n a sez ari senatoriale distincte, c ate una pentru ecare dintre cei n senatori ai Republicii. A sez arile senatoriale sunt numerotate de la 1 la n, ntre oricare dou a a sez ari exist and leg aturi directe sau indirecte. O leg atur a este direct a dac a ea nu mai trece prin alte a sez ari senatoriale intermediare. Edilii au pavat unele dintre leg aturile directe dintre dou a a sez ari (numind o astfel de leg atur a pavat a strad a), astfel nc at ntre oricare dou a a sez ari senatoriale s a existe o singur a succesiune de str azi prin care se poate ajunge de la o a sezare senatorial a la cealalt a. Tot i senatorii trebuie s a participe la sedint ele Senatului. In acest scop, ei se deplaseaz a cu lectica. Orice senator care se deplaseaz a pe o strad a pl ate ste 1 ban pentru c a a fost transportat cu lectica pe acea strad a. La alegerea sa ca prim consul, Cezar a promis c a va dota Roma cu o lectic a gratuit a care s a circule pe un num ar de k str azi ale Romei astfel nc at orice senator care va circula pe str azile respective, s a poat a folosi lectica gratuit a f ar a a pl ati. Str azile pe care se deplaseaz a lectica gratuit a trebuie s a e legate ntre ele (zborul, metroul sau teleportarea neind posibile la acea vreme). In plus, Cezar a promis s a stabileasc a sediul s alii de sedint e a Senatului ntruna dintre a sez arile senatoriale aate pe traseul lecticii gratuite. Problema este de a alege cele k str azi si amplasarea sediului s alii de sedint e a Senatului astfel nc at, prin folosirea transportului gratuit, senatorii, n drumul lor spre sala de sedint e, s a fac a economii c at mai nsemnate. In calculul costului total de transport, pentru tot i senatorii, Cezar a considerat c a ecare senator va c al atori exact o dat a de la a sezarea sa p an a la sala de sedint e a Senatului. Cerint a Scriet i un program care determin a costul minim care se poate obt ine prin alegerea adecvat a a celor k str azi pe care va circula lectica gratuit a si a locului de amplasare a s alii de sedint a a Senatului. Date de intrare Fi sierul cezar.in cont ine pe prima linie dou a valori n k separate printr-un spat iu reprezent and num arul total de senatori si num arul de strazi pe care circul a lectica gratuit a pe urm atorele n 1 linii se a a c ate dou a valori i j separate printr-un spat iu, reprezent and numerele de ordine a dou a a sez ari senatoriale ntre care exist a strad a. Date de ie sire Pe prima linie a sierului cezar.out se va scrie costul total minim al transport arii tuturor senatorilor pentru o alegere optim a a celor k str azi pe care va circula lectica gratuit a si a locului unde va amplasat a sala de sedint e a Senatului. Restrict ii si preciz ari

14.3. EXEMPLE

261

1 < n 10000, 0 < k < n 1 i, j n , i = j Oricare dou a perechi de valori de pe liniile 2, 3, ..., n din sierul de intrare reprezint a dou a str azi distincte. Perechile din sierul de intrare sunt date astfel nc at respect a condit 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 13 3 12 23 28 78 75 54 56 89 8 10 10 11 10 12 10 13

cezar.out 11

Explicat ie Costul minim se obt ine, de exemplu, pentru alegerea celor 3 str azi ntre a sez arile 5-7, 7-8, 8-10 si a s alii de sedint e a Senatului n a sezarea 8 (dup a cum este evident iat n desen). Exist a si alte alegeri pentru care se obt ine solut ia 11.

Timp maxim de execut ie/test: 0.5 secunde Indicat ii de rezolvare - descriere solut ie * O implementare posibil a utilizeaz a metoda GREEDY, O(n (n k )) sau O((n k ) log (n)).

262

CAPITOLUL 14. METODA OPTIMULUI LOCAL - GREEDY

Se elimin a succesiv, dintre frunzele existente la un moment dat, frunza de cost minim. Toate nodurile au costul init ial 1. La eliminarea unei frunze, se incrementeaz a cu 1 costul tat alui acesteia. Validitatea metodei rezult a din observat ia c a, la eliminarea unei frunze oarecare, tat al acesteia poate deveni frunz a la r andul lui, dar cu un cost strict mai mare dec at al frunzei eliminate. Se poate ret ine arborele cu ajutorul listelor de adiacent a (liniare sau organizate ca arbori de c autare), iar frunzele se pot memora ntr-un minheap de costuri, structur a care se actualizeaz a n timp logaritmic. Codul surs a

Varianta 1: import java.io.*; class cezar { static StreamTokenizer st; static PrintWriter out; static int n, ns; static static static static static int[] int[] int[] int[] int[] v1 = new int[10001]; v2 = new int[10001]; g = new int[10001]; ca = new int[10001]; nde = new int[10001];

// gradele // ca[k]=costul acumulat in nodul k !!! // nde[k]=nr "descendenti" eliminati pana in k

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; 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; // curatenie ...

14.3. EXEMPLE 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() { int i, imin, timin; int min; // frunze(g=1) cu cost=min

263

min=Integer.MAX_VALUE; imin=-1; // for(i=1;i<=n;i++) // if(g[i]==1) // if(nde[i]+1<min) // { min=nde[i]+1; imin=i; }

initializare aiurea ... mai bine cu heapMIN ... i=frunza ... aici este testul CORECT ... !!!

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() 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")));

264 citire();

CAPITOLUL 14. METODA OPTIMULUI LOCAL - GREEDY

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 static static static int[] v1 = new int[10001]; int[] v2 = new int[10001]; int[] g = new int[10001]; int[] ca = new int[10001];

// gradele // 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(); }

14.3. EXEMPLE

265

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]=0; ca[i]=1; } // curatenie ...

// 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

266

CAPITOLUL 14. METODA OPTIMULUI LOCAL - GREEDY int i, imin, timin; int min; min=Integer.MAX_VALUE; imin=-1; for(i=1;i<=n;i++) if(g[i]==1) if(ca[i]<min) { min=ca[i]; imin=i; } timin=tata(imin); g[imin]--; g[timin]--; ca[timin]=ca[timin]+min; s+=min;

// // // //

initializare aiurea ... mai bine cu heapMIN ... i=frunza cost acumulat

//System.out.println(" Elimin nodul "+imin+ // " timin = "+timin+" ca = "+ca[timin]); }// elimin() public static void main(String[] args) throws IOException { 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;

14.3. EXEMPLE } System.out.println("\n"+s+" "+senat); out.println(s+" "+senat); out.close(); }// main }// class

267

268

CAPITOLUL 14. METODA OPTIMULUI LOCAL - GREEDY

Capitolul 15

Metoda backtracking
Metoda backtracking se utilizeaz a pentru determinarea unei submult imi a unui produs cartezian de forma S1 S2 ... Sn (cu mult imile Sk nite) care are anumite propriet a ti. Fiecare element (s1 , s2 , ..., sn ) al submult imii produsului cartezian poate interpretat ca solut ie a unei probleme concrete. In majoritatea cazurilor nu oricare element al produsului cartezian este solut ie ci numai cele care satisfac anumite restrict ii. De exemplu, problema determin arii tuturor combin arilor de m elemente luate c ate n (unde 1 n m) din mult imea {1, 2, ..., m} poate reformulat a ca problema determin arii submult imii produsului cartezian {1, 2, ..., m}n denit a astfel: {(s1 , s2 , ..., sn ) {1, 2, ..., m}n |si = sj , i = j, 1 i, j n}. Metoda se aplic a numai atunci c and nu exist a nici o alt a cale de rezolvare a problemei propuse, deoarece timpul de execut ie este de ordin exponent ial.

15.1

Generarea produsului cartezian

Pentru ncep atori, nu metoda backtracking n sine este dicil de nt eles ci modalitatea de generare a produsului cartezian.

15.1.1

Generarea iterativ a a produsului cartezian

Presupunem c a mult imile Si sunt formate din numere ntregi consecutive cuprinse ntre o valoare min si o valoare max. De exemplu, dac a min = 0, max = 9 atunci Si = {0, 1, ..., 9}. Dorim s a gener am produsul cartezian S1 S2 ... Sn 269

270

CAPITOLUL 15. METODA BACKTRACKING

(de exemplu, pentru n = 4). Folosim un vector cu n elemente a = (a1 , a2 , ..., an ) si vom atribui ec arei componente ai valori cuprinse ntre min si max. 0 1 2 3 4 5 0 1 ... ... n n+1 Ne trebuie un marcaj pentru a preciza faptul c a o anumit a component a este goal a (nu are plasat a n ea o valoare valid a). In acest scop folosim variabila gol care poate init ializat a cu orice valoare ntreag a care nu este n intervalul [min, max]. Este totu si util a init ializarea gol=min-1;. Cum ncepem generarea produsului cartezian? O prim a variant a este s a atribuim tuturor componentelor ai valoarea min si avem astfel un prim element al produsului cartezian, pe care putem s a-l a sa m. 0 1 2 3 4 5 0 0 0 0 0 1 ... ... n n+1 Trebuie s a construim urm atoarele elemente ale produsului cartezian. Este util a, n acest moment, imaginea kilometrajelor de ma sini sau a contoarelor de energie electic a, de ap a, de gaze, etc. Urm atoarea congurat ie a acestora este 0 1 2 3 4 5 0 0 0 1 dup a care urmeaz a 0 1 ... ... n n+1 0 1 2 3 4 5 0 0 0 2 0 1 ... ... n n+1 Folosim o variabil a k pentru a urm ari pozit ia din vector pe care se schimb a valorile. La nceput k = n si, pe aceast a pozit ie k , valorile se schimb a cresc and de la min c atre max. Se ajunge astfel la congurat ia (k = 4 = n aici!) 0 1 2 3 4 5 0 0 0 9 0 1 ... ... n n+1 Ce se nt ampl a mai departe? Apare congurat 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 a aici este cheia metodei backtraching! Dac a reu sim s a nt elegem acest pas de trecere de la o congurat ie la alta si s a formaliz am ce am observat, atunci am descoperit singuri aceast a metod a! Pe pozit ia k = n, odat a cu aparit ia valorii 9 = max, s-au epuizat toate valorile posibile, a sa c a vom considera c a pozit ia k este goal a si vom trece pe o pozit ie mai la st anga, deci k = k 1 (acum k = 3). 0 1 2 3 4 5 0 0 0 0 1 ... ... n n+1

15.1. GENERAREA PRODUSULUI CARTEZIAN

271

Pe pozit ia k = 3 se af a valoarea 0 care se va schimba cu urm atoarea valoare (dac a exist a 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 a plasarea unei valori pe pozit 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 a nu am ie sit n afara vectorului, n urma pasului f acut spre dreapta!), n cazul nostru k = 4 si pozit ia este goal a, se plaseaz a prima valoare valid a (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 sterea cu o unitate a valorii vechi iar acum facem trecerea gol min, nt elegem de ce este util s a init ializ am variabila gol cu valoarea min 1. S a consider am c a s-a ajuns la congurat ia 0 1 2 3 4 5 0 0 9 9 0 1 ... ... n n+1 Aici k = 4 = n si 9 = max. Pe pozit ia k nu se mai poate pune o nou a valoare si atunci: se pune gol pe pozit ia k se face pasul spre st anga, deci k = k 1 0 1 2 3 4 5 0 0 9 0 1 ... ... n n+1 Aici k = 3 si 9 = max. Pe pozit ia k nu se mai poate pune o nou a valoare si atunci: se pune gol pe pozit ia k se face pasul spre st anga, deci k = k 1 0 1 2 3 4 5 0 0 0 1 ... ... n n+1 Aici k = 2 si 0 < max. Pe pozit ia k se poate pune o nou a valoare si atunci: se pune 0 + 1 = 1 = urm atoarea valoare pe pozit ia k se face pasul spre dreapta, deci k = k + 1 0 1 2 3 4 5 0 1 0 1 ... ... n n+1 Aici k = 3 si gol=-1< max. Pe pozit ia k se poate pune o nou a valoare: se pune gol+1= 1 + 1 = 0 = urm atoarea valoare pe pozit ia k

272

CAPITOLUL 15. METODA BACKTRACKING

se face pasul spre dreapta, deci k = k + 1 0 1 2 3 4 5 0 1 0 0 1 ... ... n n+1 Aici k = 4 si gol=-1< max. Pe pozit ia k se poate pune o nou a valoare: se pune gol+1= 1 + 1 = 0 = urm atoarea valoare pe pozit ia k se face pasul spre dreapta, deci k = k + 1 0 1 2 3 4 5 0 1 0 0 0 1 ... ... n n+1 iar aici k = n + 1 si am ajuns n afara vectorului! Nu-i nimic! Se a seaz a solut ia 0100 si se face pasul la st anga. Aici k = 4 si 0 < max. Pe pozit ia k se poate pune o nou a valoare: se pune 0 + 1 = 1 = urm atoarea valoare pe pozit ia k se face pasul spre dreapta, deci k = k + 1 0 1 2 3 4 5 0 1 0 1 0 1 ... ... n n+1 A ap arut n mod evident urm atoarea regul a: ajung and pe pozit ia k n, ncerc am s a punem urm atoarea valoare (gol are ca urm atoarea valoare pe min) pe aceast a pozit ie si dac a se poate: se pune urm atoarea valoare si se face pasul spre dreapta; dac a nu se poate: se pune gol=min 1 si se face pasul spre st anga. Presupunem c a s-a ajuns la congurat ia 0 1 2 3 4 5 9 9 9 9 care se a seaz a ca solut ie. Ajungem la k = 4 0 1 ... ... n n+1 0 1 2 3 4 5 9 9 9 9 se gole ste pozit ia si ajungem la k = 3 0 1 ... ... n n+1 0 1 2 3 4 5 9 9 9 se gole ste pozit ia si ajungem la k = 2 0 1 ... ... n n+1 0 1 2 3 4 5 9 9 se gole ste pozit ia si ajungem la k = 1 0 1 ... ... n n+1 0 1 2 3 4 5 9 se gole ste pozit ia si ajungem la k = 0 0 1 ... ... n n+1 0 1 2 3 4 5 iar aici k = 0 si am ajuns n afara vectorului! 0 1 ... ... n n+1 Nu-i nimic! Aici s-a terminat generarea produsului cartezian!

15.1. GENERAREA PRODUSULUI CARTEZIAN

273

Putem descrie acum algoritmul pentru generarea produsului cartezian S n , unde S = {min, min + 1, ..., max}: structuri de date: a[1..n] vectorul solut ie k pozit ia n vectorul solut ie, cu convent iile k = 0 precizeaz a terminarea gener arii, iar k = n + 1 precizeaz a faptul c a s-a construit o solut ie care poate a sat a; proces de calcul: 1. se construie ste prima solut ie 2. se plaseaz a pozit ia n afara vectorului ( n dreapta) 3. c at timp nu s-a terminat generarea 4. dac a este deja construit a o solut ie 5. se a seaz a solut ia si se face pasul spre st anga 6. altfel, dac a se poate m ari valoarea pe pozit ia curent a 7. se m are ste cu 1 valoarea si se face pasul spre dreapta 8. altfel, se gole ste pozit ia curent a si se face pasul spre st anga 3. revenire la 3. Implementarea acestui algoritm n Java este urm atoarea: 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"); } }

274

CAPITOLUL 15. METODA BACKTRACKING

O alt a variant a ar putea init ializarea vectorului solut ie cu gol si plasarea pe prima pozit ie n vector. Nu mai avem n acest caz o solut ie gata construit a dar algoritmul este acela si. Implementarea n acest caz este urm atoarea: 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"); } }

15.1.2

Generarea recursiv a 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(); }

15.1. GENERAREA PRODUSULUI CARTEZIAN

275

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

276

CAPITOLUL 15. METODA BACKTRACKING

class ProdCartCar1 { static char[] x; 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} };

15.2. METODA BACTRACKING 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(); } } static void afisv() { int i; for(i=1; i<=n; i++) System.out.print(x[i]); System.out.println(); } }// class

277

15.2

Metoda bactracking

Se folose ste pentru rezolvarea problemelor care ndeplinesc urm atoarele condit ii: 1. nu se cunoa ste o alt a metod a mai rapid a de rezolvare; 2. solut ia poate pus a sub forma unui vector x = (x1 , x2 , ..., xn ) cu xi Ai , i = 1, ..., n; 3. mult imile Ai sunt nite. Tehnica backtracking pleac a de la urm atoarea premis a: dac a la un moment dat nu mai am nici o sans a s a ajung la solut ia c autat a, renunt s a continui pentru o valoare pentru care stiu c a nu ajung la nici un rezultat.

278

CAPITOLUL 15. METODA BACKTRACKING

Specicul metodei const a n maniera de parcurgere a spat iului solut iilor. solut iile sunt construite succesiv, la ecare etap a ind completat a c ate o component a; alegerea unei valori pentru o component a se face ntr-o anumit a ordine astfel nc at s a e asigurat a o parcurgere sistematic a a spat iului A1 A2 ... An ; la completarea componentei k se veric a dac a solut ia part ial a (x1 , x2 , ..., xk ) veric a condit iile induse de restrict iile problemei (acestea sunt numite condit ii de continuare); dac a au fost ncercate toate valorile corespunz atoare componentei k si nc a nu a fost g a sit a o solut ie, sau dac se dore ste determinarea unei noi solut ii, se revine la componenta anterioar a (k 1) si se ncearc a urm atoarea valoare corespunz a toare acesteia, s.a.m.d. procesul de c autare si revenire este continuat p an a c and este g asit a o solut ie (dac a este sucient a o solut ie) sau p an a c and au foste testate toate congurat iile posibile (dac a sunt necesare toate solut iile). In gur a este ilustrat modul de parcurgere a spat iului solut iilor n cazul gener arii produsului cartezian{0, 1}4 . In cazul n care sunt specicate restrict ii (ca de exemplu, s a nu e dou a componente al aturate egale) anumite ramuri ale structurii arborescente asociate sunt abandonate nainte de a atinge lungimea n.

x1

x2

x3

x4

x1

x2

x3

x4

15.2. METODA BACTRACKING

279

In aplicarea metodei pentru o problem a concret a se parcurg urm atoarele etape: se alege o reprezentare a solut iei sub forma unui vector cu n componente; se identic a mult imile A1 , A2 , ..., An si se stabile ste o ordine n tre elemente care s a indice modul de parcurgere a ec arei mult imi; pornind de la restrict iile problemei se stabilesc condit iile de validitate ale solut iilor part iale (condit iile de continuare). In aplicat iile concrete solut ia nu este obt inut a numai n cazul n care k = n ci este posibil s a e satisf acut a o condit ie de g asire a solut iei pentru k < n (pentru anumite probleme nu toate solut iile cont in acela si num ar de elemente).

15.2.1

Bactracking iterativ

Structura general a a algoritmului este: for(k=1;k<=n;k++) x[k]=gol; init iarizarea vectorului solut ie k=1; pozit ionare pe prima component a while (k>0) c at timp exist a componente de analizat if (k==n+1) dac a x este solut ie {as(x); k;} atunci: a sare solut ie si pas st anga else altfel: { if(x[k]<max[k]) dac a exist a elemente de ncercat if(posibil(1+x[k])) dac a 1+x[k] este valid a ++x[k++]; atunci m aresc si fac pas dreapta else ++x[k]; altfel m aresc si r am an pe pozitie else x[k]=gol; altfel golesc si fac pas dreapta } Vectorul solut ie x cont ine indicii din mult imile A1 , A2 , ..., An . Mai precis, x[k ] = i nseamn a c a pe pozit ia k din solut ia x se a a ai din Ak .

15.2.2

Backtracking recursiv

Varianta recursiv a a algoritmului backtracking poate realizat a dup a o schem a asem an atoare cu varianta recursiv a. Principiul de funct ionare al functiei f (primul apel este f (1)) corespunz ator unui nivel k este urm atorul: n situat ia n care avem o solut ie, o a sa m si revenim pe nivelul anterior; n caz contrar, se init ializeaz a nivelul si se caut a un succesor; c and am g asit un succesor, veric am dac a este valid. In caz armativ, procedura se autoapeleaz a pentru k + 1; n caz contrar urm and a se continua c autarea succesorului; dac a nu avem succesor, se trece la nivelul inferior k 1 prin ie sirea din procedura recursiv a f.

280

CAPITOLUL 15. METODA BACKTRACKING

15.3

Probleme rezolvate

15.3.1

Generarea aranjamentelor

Reprezentarea solut iilor: un vector cu n componente. Mult imile Ak : {1, 2, ..., m}. Restrict iile si condit iile de continuare: Dac a x = (x1 , x2 , ..., xn ) este o solut ie ea trebuie s a respecte restrict iile: xi = xj pentru oricare i = j . Un vector cu k elemente (x1 , x2 , ..., xk ) poate conduce la o solut ie numai dac a satisface condit iile de continuare xi = xj pentru orice i = j , unde i, j {1, 2, ..., k}. Condit ia de g asire a unei solut ii: Orice vector cu n componente care respect a restrict iile este o solut ie. C and k = n + 1 a fost g asit a o solut ie. Prelucrarea solut iilor: Fiecare solut ie obt inut a este a sat a. 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

15.3. PROBLEME REZOLVATE {

281

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

282 else a[++k]=0; } } }

CAPITOLUL 15. METODA BACKTRACKING

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

15.3. PROBLEME REZOLVATE 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); }

283

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

284

CAPITOLUL 15. METODA BACKTRACKING

15.3.2

Generarea combin arilor

Sunt prezentate mai multe variante (iterative si recursive) cu scopul de a vedea diferite modalit a ti de a c a stiga timp n execut ie (mai mult sau mai put in semnicativ!). class GenCombI1a // 4550 ms cu 12 22 // { // 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 40 ms cu 8 14 fara afis

15.3. PROBLEME REZOLVATE 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(); }

285

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

286

CAPITOLUL 15. METODA BACKTRACKING

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

15.3. PROBLEME REZOLVATE {

287

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

288

CAPITOLUL 15. METODA BACKTRACKING

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

15.3. PROBLEME REZOLVATE 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(); } }

289

class GenCombR1b // 2100 ms 12 22 { 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); */ } }

290

CAPITOLUL 15. METODA BACKTRACKING

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

15.3. PROBLEME REZOLVATE

291

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

292

CAPITOLUL 15. METODA BACKTRACKING 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() { 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(); */ } }

15.3. PROBLEME REZOLVATE 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(); */ } } 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

293

294

CAPITOLUL 15. METODA BACKTRACKING

15.3.3

Problema reginelor pe tabla de sah

O problem a clasic a de generare a congurat iilor ce respect a anumite restrict ii este cea a amplas arii damelor pe o tabl a de sah astfel nc at s a nu se atace reciproc. Reprezentarea solut iei: un vector x unde xi reprezint a coloana pe care se a a regina dac a linia este i. Restrict ii si condit ii de continuare: xi = xj pentru oricare i = j (reginele nu se atac a pe coloan a) si |xi xj | = |i j | (reginele nu se atac a pe diagonal a). Sunt prezentate o variant a iterativ a si una recursiv a. 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()

15.3. PROBLEME REZOLVATE

295

static boolean posibil(int k) { for(int i=1;i<=k-1;i++) 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(); } }

296

CAPITOLUL 15. METODA BACKTRACKING

static void afisv() { int i; System.out.print(++nv+" : "); for(i=1; i<=n; i++) System.out.print(x[i]+" "); System.out.println(); } }

15.3.4

Turneul calului pe tabla de sah

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

15.3. PROBLEME REZOLVATE 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()

297

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 atoarele rezultate: Dimensiunea tablei de sah [3..8] : 5 ---------------------------------1 14 19 8 25 6 9 2 13 18 15 20 7 24 3 10 5 22 17 12 21 16 11 4 23

Numar de incercari = 8839

298

CAPITOLUL 15. METODA BACKTRACKING

15.3.5

Problema color arii h art ilor

Se consider a o hart a cu n t ari care trebuie colorat a folosind m < n culori, astfel nc at oricare dou a t ari vecine s a e colorate diferit. Relat ia de vecin atate dintre t ari este ret inut a ntr-o matrice n n ale c arei elemente sunt: vi,j = 1 dac a i este vecin a cu j 0 dac a i nu este vecin a cu j

Reprezentarea solut iilor: O solut ie a problemei este o modalitate de colorare a t a rilor si poate reprezentat a printru-un vector x = (x1 , x2 , ..., xn ) cu xi {1, 2, ..., m} reprezent and culoarea asociat a t arii i. Mult imile de valor ale elementelor sint A1 = A2 = ... = An = {1, 2, ..., m}. Restrict ii si condit ii de continuare: Restrict ia ca dou a t ari vecine s a e colorate diferit se specic a prin: xi = xj pentru orice i si j av and proprietatea vi,j = 1. Condit ia de continuare pe care trebuie s a o satisfac a solut ia part ial a (x1 , x2 , ..., xk ) este: xk = xi pentru orice i < k cu proprietatea c a 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;

15.3. PROBLEME REZOLVATE 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); 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 2 3 4 5 6 1 1 2 2 3 3 2 3 1 3 1 2 3 2 3 1 2 1 2 3 1 3 1 2 3 2 3 1 2 1

299

300

CAPITOLUL 15. METODA BACKTRACKING

class ColorareHartiR1 { static int[] x; static int[][] a= { {0,0,0,0,0,0}, {0,2,1,1,1,1},// {0,1,2,1,0,0},// {0,1,1,2,1,0},// {0,1,0,1,2,1},// {0,1,0,0,1,2} // }; static int n,m,nv=0;

Constanta (1) Calarasi (2) Ialomita (3) Braila (4) Tulcea (5)

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

15.3. PROBLEME REZOLVATE

301

15.3.6

Problema vecinilor

Un grup de n persoane sunt a sezate pe un r and de scaune. Intre oricare doi vecini izbucnesc conicte. Rearanjat i persoanele pe scaune astfel nc at ntre oricare doi vecini certat i s a existe una sau cel mult dou a persoane cu care nu au apucat s a se certe! A sat i toate variantele de rea sezare posibile. Vom rezolva problema prin metada backtracking. Presupunem c a persoanele sunt numerotate la nceput, de la stanga la dreapta cu 1, 2, ..., n. Consider am ca solut ie un vector x cu n componente pentru care xi reprezint a pozit ia pe care se va aa persoana i dup a rea sezare. Pentru k > 1 dat, condit iile de continuare sunt: a) xj = xk , pentru j = 1, ..., k 1 (x trebuie s a e permutare) b) |xk xk1 | = 2 sau 3 ( ntre persoana k si vecinul s au anterior trebuie s a se ae una sau dou a persoane) In locul solutiei x vom lista permutarea y = x1 unde yi reprezint a persoana care se aseaz a 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; x[k]=i; if(k<n) f(k+1); else afis(); }

302 }

CAPITOLUL 15. METODA BACKTRACKING

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)

15.3. PROBLEME REZOLVATE

303

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

15.3.7

Problema labirintului

Se d a un labirint sub form a de matrice cu m linii si n coloane. Fiecare element al matricei reprezint a o camer a a labirintului. Intr-una din camere, de coordonate x0 si y0 se g a se ste un om. Se cere s a se g a seasc a toate ie sirile din labirint. O prim a problem a care se pune este precizarea modului de codicare a ie sirilor din ecare camer a a labirintului. Dintr-o pozit ie oarecare (i, j ) din labirint, deplasarea se poate face n patru direct ii: Nord, Est, Sud si Vest considerate n aceast a ordine (putem alege oricare alt a ordine a celor patru direct ii, dar odat a aleas a aceasta se p astreaz a p an a la sf ar situl problemei). Fiecare camer a din labirint poate caracterizat a printr-un sir de patru cifre binare asociate celor patru direct ii, av and semnicat ie faptul c a acea camer a are sau nu ie siri pe direct iile considerate. De exemplu, dac a pentru camera cu pozit ia (2, 4) exist a ie siri la N si S, ei i va corespunde sirul 1010 care reprezint a num arul 10 n baza zece. Prin urmare, codic am labirintul printr-o matrice a[i][j ] cu elemente ntre 1 sai 15. Pentru a testa u sor ie sirea din labirint, matricea se bordeaz a cu dou a linii si dou a coloane de valoare egal a cu 16. Ne punem problema determin arii ie sirilor pe care le are o camer a. O camer a are ie sirea numai spre N dac a si numai dac a a[i][j ]&&8 = 0. Drumul parcurs la un moment dat se ret ine ntr-o matrice cu dou a linii, d, n care: d[1][k ] reprezint a linia camerei la care s-a ajuns la pasul k ; d[2][k ] reprezint a coloana camerei respective. La g asirea unei ie siri din labirint, drumul este a sat. Principiul algoritmului este urm atorul:

304

CAPITOLUL 15. METODA BACKTRACKING

se testeaz a dac a s-a ie sit din labiritn (adic a a[i][j ] = 16); n caz armativ se a seaz a drumul g asit; n caz contrar se procedeaz a astfel: se ret in n matricea d coordonatele camerei vizitate; se veric a dac a drumul arcurs a mai trecut prin aceast a camer a, caz n care se iese din procedur a; se testeaz a pe r and ie sirile spre N, E, S, V si acolo unde este g asit a o astfel de ie sire se reapeleaz a procedura cu noile coordonate; naintea ie sirii din procedur a se decrementeaz a 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; 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();

15.3. PROBLEME REZOLVATE for(j=0;j<n;j++) out.print(l[i][j]); } if (!ok) out.println("NU exista iesire !"); out.close(); }// main()

305

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 obt ine sierul de ie sire: labirint.out HHHHHHEH H....H.H H*xHHH.H H*HHHH.H H*...H.H H*HHHH.H H******H HHHHHH*H

306

CAPITOLUL 15. METODA BACKTRACKING

15.3.8

Generarea partit iilor unui num ar natural

S a se a seze toate modurile de descompunere a unui num ar natural n ca sum a de numere naturale. Vom folosi o procedur a f care are doi parametri: componenta la care s-a ajuns (k ) si o valoare v (care cont ine diferent a care a mai r amas p an a la n). Init ial, procedura este apelat a pentru nivelul 1 si valoarea n. Imediat ce este apelat a, procedura va apela o alta pentru a sarea vectorului (init ial a seaz a n). Din valoarea care se g ase ste pe un nivel, S [k ], se scad pe r and valorile 1, 2, ..., S [k ] 1, valori cu care se apeleaz a procedura pentru nivelul urm ator. La revenire se reface valoarea existent a. 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]+" "); }

15.3. PROBLEME REZOLVATE

307

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

308

CAPITOLUL 15. METODA BACKTRACKING 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 a este: class PartitieNrImpare2 // n = suma de numere impare ; { // optimizat timp la jumatate !!! static int dim=0,nsol=0; // nsol = 38328320 2Gata!!! static int[] x=new int[161];

timp = 4787

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(); System.out.println("nsol = "+nsol+" timp= "+(t2-t1)); }

15.3. PROBLEME REZOLVATE 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

309

310

CAPITOLUL 15. METODA BACKTRACKING

15.3.9

Problema parantezelor

Problema cere generarea tuturor combinat iilor de 2n paranteze (n paranteze de deschidere si 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) { 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

15.3. PROBLEME REZOLVATE

311

15.3.10

Algoritmul DFS de parcurgere a grafurilor

Algoritmul DFS de parcurgerea in ad ancime a grafurilor neorientate foloseste tehnica backtrackink. 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(); st.nextToken(); st.nextToken(); st.nextToken(); n=(int)st.nval; m=(int)st.nval; nods=(int)st.nval; 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]; 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; }

312

CAPITOLUL 15. METODA BACKTRACKING

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++) if(a[u][v]==1) if(color[v]==WHITE) { p[v]=u; dfs(v); } color[u]=BLACK; f[u]=++t; }//dfs

// listele de adiacenta ... !!! // v in Ad(u) !!!

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

drum : 3 1 4 Descoperit : Finalizat :

2 11

5 6

1 12

3 10

4 7

8 9

15.3. PROBLEME REZOLVATE 5 3 2 5 1 3 4 5 */

313

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

314 {

CAPITOLUL 15. METODA BACKTRACKING

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 2 2 3 3 1 4 5 6 7 7 8 8 9 */

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

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

15.3. PROBLEME REZOLVATE static int[][] at;

315

// 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]; f=new int[n+1]; at=new int[n+1][n+1]; lista=new int[n+1]; ctc=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(); } }//main static void dfsa(int u) { int v; color[u]=GRAY;

316

CAPITOLUL 15. METODA BACKTRACKING 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 2 2 3 3 1 4 5 6 7 7 8 8 9 5 4 7 6 8 7 */

1 2 3 4

: : : :

6 7 8 9 4 5 1 2 3

15.3.13

Sortare topologic a

Folosind parcurgerea n ad ancime // // // // // // // // 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.*;

15.3. PROBLEME REZOLVATE

317

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

318

CAPITOLUL 15. METODA BACKTRACKING

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 6 3 1 2 3 4 5 6 */

5 6 3 4 1 2

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!) 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")));

15.3. PROBLEME REZOLVATE

319

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; for(v=1;v<=n;v++) // lista de adiacenta este mai buna !!!

320

CAPITOLUL 15. METODA BACKTRACKING if(color[v]==WHITE) if(a[u][v]==1) gi[v]--;

} }//class /* 6 4 6 3 1 2 3 4 5 6 */

1 2 5 6 3 4

15.3.14

Determinarea nodurilor de separare


// determinarea nodurilor care strica conexitatea // in graf neorientat conex

import java.io.*; class NoduriSeparare { 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) {

15.3. PROBLEME REZOLVATE 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) { 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

321

15.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;

322

CAPITOLUL 15. METODA BACKTRACKING

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; for(int v=1;v<=n;v++) if((a[u][v]==1)&&(cc[v]==0)) conex(v,et); }//conex }//class /* 9 10 7 2 5 1 1 8

1 2 3 7

8 7 9 9

15.3. PROBLEME REZOLVATE 9 4 6 9 6 4 4 1 9 5 9 7 9 3 */

323

15.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 static static static static static static static final int WHITE=0, GRAY=1,BLACK=2; int n,ncb,t,ndr,vs,m=0,root; // root=radacina arborelui DFS int[][] G; // liste de adiacenta int[] grad,low,d; // grad nod, low[], d[u]=moment descoperire nod u int[][] B; // componente biconexe int[] A; // puncte de articulare int[] color; // culoarea nodului 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);

324

CAPITOLUL 15. METODA BACKTRACKING System.out.print("Numar componente Biconexe : "); System.out.println(ncb); for(int i=1;i<=ncb;i++) { System.out.print("Componenta Biconexa "+i+" : "); 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]; low=new int[n+1]; A=new int[n+1]; fs=new int[m+1]; // vectorii sunt initializati cu zero grad=new int[n+1]; color=new int[n+1]; // 0=WHITE G=new int[n+1][n+1]; B=new int[n+1][n+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) { int fiuu,i; d[u]=++t; color[u]=GRAY; low[u]=d[u]; for(i=1;i<=grad[u];i++) { fiuu=G[u][i]; /* calculeaza d si low */

// fiuu = un descendent al lui u

15.3. PROBLEME REZOLVATE

325

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 !!! { /* 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;

326

CAPITOLUL 15. METODA BACKTRACKING

} 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 1 8 1 2 1 3 6 3 4 | \ 2 4 | 5 --3 5 | / 5 7 7 5 6 6 7 */

8 | 1 / 3 \ 4 \ 2 /

Bridge: 8 1 Bridge: 5 3 Graful NU este Biconex Puncte de articulare : 1 3 5 Numar componente Biconexe : 4 Componenta Biconexa 1 : 1 8 Componenta Biconexa 2 : 1 2 3 4 Componenta Biconexa 3 : 5 6 7 Componenta Biconexa 4 : 3 5

15.3.17

Triangulat ii - OJI2002 clasa a X-a

O triangulat ie a unui poligon convex este o mult ime format a din diagonale ale poligonului care nu se intersecteaz a n interiorul poligonului ci numai n v arfuri si care mpart toat a suprafat a poligonului n triunghiuri. Fiind dat un poligon cu n v arfuri notate 1, 2, ..., n s a se genereze toate triangulat iile distincte ale poligonului. Dou a triangulat ii sunt distincte dac a difer a prin cel put in o diagonal a. Datele de intrare: n sierul text triang.in se a a pe prima linie un singur num ar natural reprezent and valoarea lui n (n 11). Datele de ie sire: n sierul text triang.out se vor scrie: - pe prima linie, num arul de triangulat ii distincte; - pe ecare din urm atoarele linii c ate o triangulat ie descris a prin diagonalele ce o compun. O diagonal a va precizat a prin dou a numere reprezent and cele dou a v arfuri care o denesc; cele dou a numere ce denesc o diagonal a se despart prin cel put in un spat iu, iar ntre perechile de numere ce reprezint a diagonalele dintr-o triangulat ie se va l asa de asemenea minimum un spat iu. Exemplu

15.3. PROBLEME REZOLVATE triang.in 5 triang.out 5 1314 2425 5253 3531 4214

327

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 a Se genereaza toate combinatiile de diagonale care formeaza o triangulat 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];

328 v2=new int[nd+1];

CAPITOLUL 15. METODA BACKTRACKING

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

15.3. PROBLEME REZOLVATE 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; 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

329

330

CAPITOLUL 15. METODA BACKTRACKING

15.3.18

Partit ie - ONI2003 clasa a X-a

Se dene ste o partit ie a unui num ar 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 a urm atoarea relat ie: n1 n2 ... ni ... nk 1 Cerint a Fiind dat un num ar natural n, s a se determine c ate partit ii ale lui se pot scrie, conform cerint elor de mai sus, stiind c a oricare num ar ni dintr-o partit ie trebuie s a e un num ar impar. Datele de intrare Fi sierul partitie.in cont ine pe prima linie num arul n Datele de ie sire Fi serul partitie.out va cont ine pe prima linie num arul de partit ii ale lui n conform cerint elor problemei. Restrict ii si preciz ari 1 N 160 Exemplu partitie.in partitie.out 7 5 Explicat ii: Cele cinci partit ii sunt: 1+1+1+1+1+1+1 1+1+1+1+3 1+1+5 1+3+3 7 Timp maxim de executare: 3 secunde/test Indicat ii de rezolvare * Stelian Ciurea Problema se poate rezolva n mai multe moduri: Solut ia comisiei se bazeaz a pe urm atoarele formule si teoreme de combinatoric a:

15.3. PROBLEME REZOLVATE

331

este

num arul de partit ii ale unui num ar n n k p art i (nu neap arat distincte) P (n, k ) = P (n k, 1) + P (n k, 2) + ... + P (n k, k )

cu P (n, 1) = P (n, n) = 1 si P (n, k ) = 0 dac a n < k. num arul de partit ii ale unui num a n n k p art i distincte este P (n, k ) = P (n k (k 1)/2, k ) num arul de partit ii ale unui num ar n n k p art i impare care se pot si repeta este egal cu num arul de partit ii ale unui num ar n n k p art i distincte. Problema se poate rezolva si prin backtracking; f ar a prea mari optimiz ari se poate obt ine rezultatul n mai put in de 3 secunde pentru n < 120. Pentru valori mai mari ale lui n, se poate l asa programul s a ruleze si se ret in rezultatele ntr-un vector cu valori init iale. Rezultatul pentru n = 160 are 8 cifre, deci nu este necesar a implementarea operat iilor cu numere mari! Mihai Stroe, GInfo nr. 13/6 - octombrie 2003 Problema se poate rezolva n mai multe moduri. O idee bun a de rezolvare a problemei const n folosirea metodei program arii dinamice. Se poate construi o matrice A, unde Ai,k reprezint a num arul de partit ii ale num arului i cu numere impare, din care ultimul este cel mult egal cu k . Un element din A se calculeaz a observ and c a o partit ie a lui i cu numere impare, cel mult egale cu k , este format a dintr-un un num ar M , mai mic sau egal cu k , si alte numere, mai mici sau egale cu M . De aici rezult a relat ia: Ai,k =
M =1,...,k;M i;

AiM,M
M =impar

Init ial A0,0 este 1. La implementare, pentru a economisi spat iu, se poate alege o variant a n care Ai,k s a reprezinte num arul de partit ii ale lui i cu numere impare, din care ultimul este cel mult egal cu al k -lea num ar impar, adic a 2 k 1. Dup a calcularea elementelor matricei, solut ia pentru num aul i se g ase ste n Ai,i . Aceste valori pot salvate ntrun vector de constante, care este transformat ntr-un nou program, n acela si mod ca la problema Circular de la clasa a IX-a. Aceast a metod a conduce la o rezolvare instantanee a testelor date n concurs. O alt a metod a, bazat a pe vectorul de constante, ar nsemnat generarea solut iilor folosind metoda backtracking. Un backtracking l asat s a se execute n timpul concursului ar putut genera solut iile pentru toate valorile lui N , dup a care se putea folosi metoda vectorului de constante, n timp ce un backtracking folosit ca solut ie a problemei ar obinut punctajul maxim doar pentru testele mici si medii (pentru valori ale lui N mai mici dec at 130). Limitele problemei si timpul de execuie de 3 secunde permit rezolv arii prin backtracking s bt inerea unui punctaj mult umitor.

332

CAPITOLUL 15. METODA BACKTRACKING

Analiza complexit a tii Pentru o rezolvare care se bazeaz a pe metoda vectorului de constante, ordinul de complexitate al solut iei nale ar fost O(1); solut ia const a nn citirea valorii lui N si a sarea rezultatului memorat. Solut ia descris a anterior, bazat a pe metoda program arii dinamice, are ordinul de complexitate O(n3 ). Invit am cititorul s a caute metode mai eciente. Ordinul de complexitate al unei solut ii care se bazeaz a pe metoda backtracking este exponent ial. Codul surs a

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

15.3. PROBLEME REZOLVATE

333

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

334

CAPITOLUL 15. METODA BACKTRACKING

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

15.3. PROBLEME REZOLVATE 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();

335

int n,i; StreamTokenizer st=new StreamTokenizer( new BufferedReader(new FileReader("partitie.in"))); PrintWriter out=new PrintWriter(new BufferedWriter( 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;

336 //afis1(); return; }

CAPITOLUL 15. METODA BACKTRACKING

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; } static int min(int a,int b) { return (a<b)?a:b; } }// class

15.3.19

Scut a - ONI2003 clasa a X-a

Majoritatea participant ilor la ONI2003 au auzit, n copil arie, povestea Scut ei Ro sii. Pentru cei care o stiu, urmeaz a partea a doua; pentru cei care nu o stiu, nu v a facet i griji, cunoa sterea ei nu este necesar a pentru a rezolva aceast a problem a. Povestea nu spune ce s-a nt amplat pe drumul pe care Scut a Ro sie s-a ntors de

15.3. PROBLEME REZOLVATE

337

la bunicut a. Vet i aa am anunte n continuare. Pe drum, ea s-a nt alnit cu Lupul (fratele lupului care a p ar asit povestea n prima parte). Acesta dorea s a o m an ance, dar a decis s a-i acorde o sans a de sc apare, provoc and-o la un concurs de cules ciupercut e. Scut a Ro sie se a a n pozit ia (1, 1) a unei matrice cu N linii si N coloane, n ecare pozit ie a matricei ind amplasate ciupercut e. Lupul se a a n pozit ia (1, 1) a unei alte matrice similare. Pe parcursul unui minut, at at Scut a, c at si Lupul se deplaseaz a ntr-una din pozit iile vecine (pe linie sau pe coloan a) si culeg ciupercut ele din pozit ia respectiv a. Dac a Scut a Ro sie ajunge ntr-o pozit ie n care nu sunt ciupercut e, va pierde jocul. Dac a la sf ar situl unui minut ea are mai put ine ciupercut e dec at Lupul, ea va pierde jocul de asemenea. Jocul ncepe dup a ce am andoi participant ii au cules ciupercut ele din pozit iile lor init iale (nu conteaz a cine are mai multe la nceputul jocului, ci doar dup a un num ar ntreg strict pozitiv de minute de la nceput). Dac a Scut a Ro sie pierde jocul, Lupul o va m anca. Inainte de nceperea jocului, Scut a Ro sie l-a sunat pe V an ator, care i-a promis c a va veni ntr-un sfert de ora (15 minute) pentru a o salva. Deci Scut a Ro sie va liber a s a plece dac a nu va pierde jocul dup a 15 minute. Din acest moment, scopul ei este nu numai s a r am an a n viat a, ci si s a culeag a c at mai multe ciupercut e, pentru a le duce acas a (dup a ce va veni, v an atorul nu o va mai l asa s a culeag a). Lupul, cunoscut pentru l acomia sa proverbial a, va alege la ecare minut mutarea n c ampul vecin cu cele mai multe ciupercut e (matricea sa este dat a astfel nct s a nu existe mai multe posibilit a ti de alegere la un moment dat). Povestea spune c a Scut a Ro sie a plecat acas a cu co sulet ul plin de ciupercut e, folosind indicat iile date de un program scris de un concurent la ONI 2003 (nu vom da detalii suplimentare despre alte aspecte, cum ar c al atoria n timp, pentru a nu complica inutil enunt ul problemei). S a fost acest program scris de c atre dumneavoastr a? Vom vedea... Cerint a Scriet i un program care s a o ajute pe Scut a Ro sie s a r am an a n joc si s a culeag a c at mai multe ciupercut e la sf ar situl celor 15 minute! Date de intrare Fi sierul scuta.in are urm atoarea structur a: N - dimensiunea celor dou a matrice a11 a12 ...a1n -matricea din care culege Scut a Ro sie a21 a22 ...a2n ... an1 an2 ...ann b11 b12 ...b1n - matricea din care culege Lupul b21 b22 ...b2n ... bn1 bn2 ...bnn Date de ie sire Fi sierul scuta.out are urm atoarea structur a:

338

CAPITOLUL 15. METODA BACKTRACKING

N R - num arul total de ciupercut e culese d1 d2 ...d15 - direct iile pe care s-a deplasat Scut a Ro sie, separate prin c ate un spat iu (direct iile pot N, E, S, V indic and deplas ari spre Nord, Est, Sud, Vest; pozit ia (1, 1) este situat a n colt ul de Nord-Vest al matricei) Restrict ii 4 N 10; valorile din ambele matrice sunt numere naturale mai mici dec at 256; nici Scut a Ro sie si nici Lupul nu vor p ar asi matricele corespunz atoare; dup a ce unul din juc atori culege ciupercut ele dintr-o pozit ie, n pozit ia respectiv a r am an 0 ciupercut e; pe testele date, Scut a Ro sie va avea ntotdeauna posibilitatea de a rezista 15 minute. 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 Explicat ie: Scut a Ro sie a efectuat acelea si mut ari cu cele efectuate de Lup si a avut tot timpul o ciupercut a n plus. In nal ea a cules toate ciupercut ele din matrice. Timp maxim de executare: 1 secund a/test Indicat ii de rezolvare * Mihai Stroe, GInfo nr. 13/6 - octombrie 2003 Problema se rezolv a folosind metoda backtracking n plan. Se observ a c a mut arile Lupului sunt independente de mut arile Scut ei, astfel ele pot determinate imediat dup a citirea datelor. In acest punct al rezolv arii, Scut a trebuie s a mute la ecare moment, astfel nc at s a r am an a n joc, iar n nal s a str ang a c at mai multe ciupercut e. Restrict iile sunt date de enunt ul problemei si de num arul de ciupercut e culese de Lup, care este cunoscut la ecare moment. Rezolvarea prin metoda backtracking ncearc a la ecare pas, pe r and, ecare mutare din cele cel mult patru posibile (teoretic; de fapt, cel mult trei mut ari sunt posibile n ecare moment, deoarece Scut a nu se va ntoarce n pozit ia din care tocmai a venit).

15.3. PROBLEME REZOLVATE

339

Se urm are ste respectarea restrict iilor impuse. In momentul g asirii unei solut ii, aceasta este comparat a cu solut ia optim a g asit a p an a atunci si dac a este mai bun a este ret inut a. Inc a nu a fost g asit a o rezolvare polinomial a pentru aceast a problem a ( si este improbabil ca o astfel de rezolvare s a existe), dar limitele mici si faptul c a num arul de mut ari disponibile ncepe s a scad a destul de repede, n multe cazuri permit un timp de execut ie foarte bun. Analiza complexit a tii Fie M num arul de mut ari pe care le are de efectuat Scut a. Operat iile de citire si scriere a datelor au ordinul de complexitate O(N 2 ), respectiv O(M ), deci nu vor luate n calcul. Ordinul de complexitate este dat de rezolvarea prin metoda backtracking. Av and n vedere c a la ecare pas Scut a poate alege dintre cel mult 3 mut ari (deoarece nu se poate ntoarce n pozit ia din care a venit), ordinul de complexitate ar O(3M ). De fapt, num arul maxim de st ari examinate este mult mai mic; de exemplu, primele dou a mut ari ofer a dou a variante de alegere n loc de trei. Alte restrict ii apar datorit a limit arii la o matrice de 10 10 elemente. Cum num arul M este cunoscut si mic, s-ar putea considera c a ordinul de complexitate este limitat superior, deci constant (constanta introdus a ind totu si destul de mare). Codul surs a

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

340 a=new b=new x=new y=new int[n+2][n+2]; int[n+2][n+2]; int[17]; int[17];

CAPITOLUL 15. METODA BACKTRACKING

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])) if((a[i+1][j]>0)&&(a[i+1][j]+x[k]>=y[k+1])) if((a[i][j-1]>0)&&(a[i][j-1]+x[k]>=y[k+1])) if((a[i][j+1]>0)&&(a[i][j+1]+x[k]>=y[k+1])) a[i][j]=aij; }//f(...) { { { { tc[k]=N; tc[k]=S; tc[k]=V; tc[k]=E; f(i-1,j,k+1); f(i+1,j,k+1); f(i,j-1,k+1); f(i,j+1,k+1); } } } }

15.3. PROBLEME REZOLVATE

341

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

342

CAPITOLUL 15. METODA BACKTRACKING

Capitolul 16

Programare dinamic a

16.1

Prezentare general a

Folosirea tehnicii program arii dinamice solicit a experient a, intuit ie si abilit a ti matematice. De foarte multe ori rezolv arile date prin aceast a tehnic a au ordin de complexitate polinomial. In literatura de specialitate exist a dou a variante de prezentare a acestei tehnici. Prima dintre ele este mai degrab a de natur a deductiv a pe c and a doua folose ste g andirea inductiv a. Prima variant a se bazeaz a pe conceptul de subproblem a. Sunt considerate urm atoarele aspecte care caracterizeaz a o rezolvare prin programare dinamic a: Problema se poate descompune recursiv n mai multe subprobleme care sunt caracterizate de optime part iale, iar optimul global se obt ine prin combinarea acestor optime part iale. Subproblemele respective se suprapun. La un anumit nivel, dou a sau mai multe subprobleme necesit a rezolvarea unei aceea si subprobleme. Pentru a evita risipa de timp rezultat a n urma unei implement ari recursive, optimele part iale se vor ret ine treptat, n maniera bottom-up, n anumite structuri de date (tabele). A doua variant a de prezentare face apel la conceptele intuitive de sistem, stare si decizie. O problem a este abordabil a folosind tehnica program arii dinamice dac a satisface principiul de optimalitate sub una din formele prezentate n continuare. Fie secvent a de st ari S0 , S1 , ..., Sn ale sistemului. 343

344

CAPITOLUL 16. PROGRAMARE DINAMICA Dac a d1 , d2 , ..., dn este un sir optim de decizii care duc la trecerea sistemului din starea S0 n starea Sn , atunci pentru orice i (1 i n) di+1 , di+2 , ..., dn este un sir optim de decizii care duc la trecerea sistemului din starea Si n starea Sn . Astfel, decizia di depinde de deciziile anterioare di+1 , ..., dn . Spunem c a se aplic a metoda nainte. Dac a d1 , d2 , ..., dn este un sir 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 sir optim de decizii care duc la trecerea sistemului din starea S0 n starea Si . Astfel, decizia di+1 depinde de deciziile anterioare d1 , ..., di . Spunem c a se aplic a metoda napoi. Dac a d1 , d2 , ..., dn este un sir optim de decizii care duc la trecerea sistemului din starea S0 n starea Sn , atunci pentru orice i (1 i n) di+1 , di+2 , ..., dn este un sir optim de decizii care duc la trecerea sistemului din starea Si n starea Sn si d1 , d2 , ..., di este un sir optim de decizii care duc la trecerea sistemului din starea S0 n starea Si . Spunem c a se aplic a metoda mixt a.

Indiferent de varianta de prezentare, rezolvarea prin programare dinamic a presupune g asirea si rezolvarea unui sistem de recurent e. In prima variant a avem recurent e ntre subprobleme, n a doua variant a avem recurent e n sirul de decizii. In afara cazurilor n care recurent ele sunt evidente, este necesar a si o justicare sau o demonstrat ie a faptului c a aceste recurent e sunt cele corecte. Deoarece rezolvarea prin recursivitate duce de cele mai multe ori la ordin de complexitate exponent ial, se folosesc tabele auxiliare pentru ret inerea optimelor part iale iar spat iul de solut ii se parcurge n ordinea cresc atoare a dimensiunilor subproblemelor. Folosirea acestor tabele d a si numele tehnicii respective. Asem an ator cu metoda divide et impera, programarea dinamic a rezolv a problemele combin and solut iile unor subprobleme. Deosebirea const a n faptul c a divide et impera partit ioneaz a problema n subprobleme independente, le rezolv a (de obicei recursiv) si apoi combin a solut iile lor pentru a rezolva problema initial a, n timp ce programarea dinamic a se aplic a problemelor ale c aror subprobleme nu sunt independente, ele av and sub-subprobleme comune. In aceast a situat ie, se rezolv a ecare sub-subproblem a o singur a dat a si se folose ste un tablou pentru a memora solut ia ei, evit andu-se recalcularea ei de c ate ori subproblema reapare. Algoritmul pentru rezolvarea unei probleme folosind programarea dinamic a se dezvolta n 4 etape: 1. caracterizarea unei solut ii optime (identicarea unei modalit ati optime de rezolvare, care satisface una dintre formele principiului optimalit a tii), 2. denirea recursiv a a valorii unei solut ii optime, 3. calculul efectiv al valorii unei solut ii optime, 4. reconstituirea unei solut ii pe baza informat iei calculate.

16.2. PROBLEME REZOLVATE

345

16.2

Probleme rezolvate

16.2.1

Inmult irea optimal a a matricelor

Consider am n matrice A1 , A2 , ..., An , de dimensiuni d0 d1 , d1 d2 , ..., dn1 dn . Produsul A1 A2 ... An se poate calcula n diverse moduri, aplic and asociativitatea operat iei de nmultire a matricelor. Numim nmult ire elementar a nmult irea a dou a elemente. In funct ie de modul de parantezare difer a num arul de nmult iri elementare necesare pentru calculul produsului A1 A2 ... An . Se cere parantezare optimal a a produsului A1 A2 ... An (pentru care costul, adic a num arul total de nmult iri elementare, s a e minim). Exemplu: Pentru 3 matrice de dimensiuni (10, 1000), (1000, 10) si (10, 100), produsul A1 A2 A3 se poate calcula n dou a moduri: 1. (A1 A2 ) A3 necesit and 1000000+10000=1010000 nmult iri elementare 2. A1 (A2 A3 ), necesit and 1000000+1000000=2000000 nmult iri. Reamintim c a num arul de nmult iri elementare necesare pentru a nmult i o matrice A, av and n linii si m coloane, cu o matrice B , av and m linii si p coloane, este n m p. Solutie 1. Pentru a calcula A1 A2 ... An , n nal trebuie s a nmult im dou a matrice, deci vom paranteza produsul astfel: (A1 A2 ... Ak ) (Ak+1 ... An ). Aceast a observat ie se aplic a si produselor dintre paranteze. Prin urmare, subproblemele problemei init iale constau n determinarea parantez arii optimale a produselor de matrice de forma Ai Ai+1 ... Aj , 1 i j n. Observ am c a subproblemele nu sunt independente. De exemplu, calcularea produsului Ai Ai+1 ...Aj si calcularea produsului Ai+1 Ai+2 ...Aj +1 , au ca subproblem a comun a calcularea produsului Ai+1 ... Aj . 2. Pentru a ret ine solut iile subproblemelor, vom utiliza o matrice M , cu n linii si n coloane, cu semnicatia: M [i][j ] = num arul minim de nmult iri elementare necesare pentru a calcula produsul Ai Ai+1 ... Aj , 1 i j n. Evident, num arul minim de nmult iri necesare pentru a calcula A1 A2 ... An este M [1][n].

346

CAPITOLUL 16. PROGRAMARE DINAMICA

3. Pentru ca parantezarea s a e optimal a, parantezarea produselor A1 A2 ... Ak si Ak+1 ... An trebuie s a e de asemenea optimal a. Prin urmare elementele matricei M trebuie s a satisfac a urmatoarea relat ie de recurent a: M [i][i] = 0, i = 1, 2, ..., n. M [i][j ] = min {M [i][k ] + M [k + 1][j ] + d[i 1] d[k ] d[j ]}
ik<j

Cum interpret am aceast a relat ie de recurent a? Pentru a determina num arul minim de nmult iri elementare pentru calculul produsului Ai Ai+1 ... Aj , x am pozit ia de parantezare k n toate modurile posibile ( ntre i si j 1), si alegem varianta care ne conduce la minim. Pentru o pozit ie k xata, costul parantez arii este egal cu numarul de nmult iri elementare necesare pentru calculul produsului Ai Ai+1 ...Ak , la care se adaug a num arul de nmult iri elementare necesare pentru calculul produsului Ak+1 ... Aj si costul nmult irii celor dou a matrice rezultate (di1 dk dj ).

Observ am c a numai jum atatea de deasupra diagonalei principale din M este utilizat a. Pentru a construi solut ia optim a este util a si ret inerea indicelui k , pentru care se obtine minimul. Nu vom considera un alt tablou, ci-l vom ret ine, pe pozit ia simetric a fat a de diagonala principala (M [j ][i]).

4. Rezolvarea recursiv a a relat iei de recurent a este inecient a, datorit a faptului c a subproblemele se suprapun, deci o abordare recursiv a ar conduce la rezolvarea aceleia si subprobleme de mai multe ori. Prin urmare vom rezolva relat ia de recurent a n mod bottom-up: (determin am parantezarea optimal a a produselor de dou a 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);

16.2. PROBLEME REZOLVATE 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

347

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

348

CAPITOLUL 16. PROGRAMARE DINAMICA

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

16.2.2

Sub sir cresc ator maximal

Fie un sir A = (a1 , a2 , ..., an ). Numim sub sir al sirului 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 sir cresc ator al sirului A, de lungime maxim a. De exemplu, pentru A = (8, 3, 6, 50, 10, 8, 100, 30, 60, 40, 80) o solut ie poate (3, 6, 10, 30, 60, 80). Rezolvare sir cresc ator al 1. Fie Ai1 = (ai1 ai2 ... aik ) cel mai lung sub sirului A. Observ am c a el coincide cu cel mai lung sub sir cresc ator al sirului sir (ai1 , ai1 +1 , ..., an ). Evident Ai2 = (ai2 ai3 ... aik ) este cel mai lung sub a a problemei cresc ator al lui (ai2 , ai2 +1 , ..., an ), etc. Prin urmare, o subproblem init iale const a n determinarea celui mai lung sub sir cresc ator care ncepe cu ai , i = {1, .., n}.

16.2. PROBLEME REZOLVATE

349

Subproblemele nu sunt independente: pentru a determina cel mai lung sub sir cresc ator care ncepe cu ai , este necesar s a determin am cele mai lungi sub siruri cresc atoare care ncep cu aj , ai aj , j = {i + 1, .., n}. 2. Pentru a ret ine solut iile subproblemelor vom considera doi vectori l si poz , ecare cu n componente, av and semnicat ia: l[i] =lungimea celui mai lung sub sir cresc ator care ncepe cu a[i]; poz [i] =pozit ia elementului care urmeaz a dup a a[i] n cel mai lung sub sir cresc ator care ncepe cu a[i], dac a un astfel de element exist a, sau 1 dac a un astfel de element nu exist a. 3. Relat ia de recurent a care caracterizeaz a substructura optimal a a problemei este: l[n] = 1; poz [n] = 1; l[i] = max {1 + l[j ]|a[i] a[j ]}
j =i+1,n

unde poz [i] = indicele j pentru care se obt ine maximul l[i]. Rezolv am relat ia de recurent a 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 solut ia optim a a problemei, determin am valoarea maxim a din vectorul l, apoi a sa m solut ia, ncep and cu pozit ia maximului si utiliz and informat 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]<< ; Secvent ele de program de mai sus sunt scrise n c/C++.

350

CAPITOLUL 16. PROGRAMARE DINAMICA Programele urm atoare sunt scrise n Java:

import java.io.*; class SubsirCrescatorNumere { static int n; static int[] a; static int[] lg; 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

16.2. PROBLEME REZOLVATE import java.io.*; class SubsirCrescatorLitere {

351

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

352

CAPITOLUL 16. PROGRAMARE DINAMICA

16.2.3

Sum a maxim a n triunghi de numere

S a consider am un triunghi format din n linii (1 < n 100), ecare linie cont in and numere ntregi din domeniul [1, 99], ca n exemplul urm ator: 7 3 8 2 4 5 7 2 1 4 6 8 0 4 5

Tabelul 16.1: Triunghi de numere Problema const a n scrierea unui program care s a determine cea mai mare sum a de numere aate pe un drum ntre num arul de pe prima linie si un num ar de pe ultima linie. Fiecare num ar din acest drum este situat sub precedentul, la st anga sau la dreapta acestuia. (IOI, Suedia 1994) Rezolvare 1. Vom ret ine triunghiul ntr-o matrice p atratic a T , de ordin n, sub diagonala principal a. Subproblemele problemei date constau n determinarea sumei maxime care se poate obt ine din numere aate pe un drum ntre numarul T [i][j ], p an a la un num ar de pe ultima linie, ecare num ar din acest drum ind situat sub precedentul, la st anga sau la dreapta sa. Evident, subproblemele nu sunt independente: pentru a calcula suma maxim a a numerelor de pe un drum de la T [i][j ] la ultima linie, trebuie s a calcul am suma maxim a a numerelor de pe un drum de la T [i + 1][j ] la ultima linie si suma maxim a a numerelor de pe un drum de la T [i + 1][j + 1] la ultima linie. 2. Pentru a ret ine solut iile subproblemelor, vom utiliza o matrice S , p atratic a de ordin n, cu semnicat ia S [i][j ] = suma maxim a ce se poate obt ine pe un drum de la T [i][j ] la un element de pe ultima linie, respect and condit iile problemei. Evident, solut ia problemei va S [1][1]. 3. Relat ia de recurent a care caracterizeaz a substructura optimal a a problemei este: S [n][i] = T [n][i], i = {1, 2, ..., n} S [i][j ] = T [i][j ] + max{S [i + 1][j ], S [i + 1][j + 1]} 4. Rezolv am relat ia de recurent a 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--) for (j=1; j<=i; j++)

16.2. PROBLEME REZOLVATE { 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]); }

353

Exercit iu: A sat i si drumul n triunghi pentru care se obt ine solut ia optim a.

16.2.4

Sub sir comun maximal

Fie X = (x1 , x2 , ..., xn ) si Y = (y1 , y2 , ..., ym ) dou a siruri de n, respectiv m numere ntregi. Determinat i un sub sir comun de lungime maxim a. Exemplu Pentru X = (2, 5, 5, 6, 2, 8, 4, 0, 1, 3, 5, 8) si Y = (6, 2, 5, 6, 5, 5, 4, 3, 5, 8) o solut ie posibil a este: Z = (2, 5, 5, 4, 3, 5, 8). Solutie 1. Not am cu Xk = (x1 , x2 , ..., xk ) (prexul lui X de lungime k ) si cu Yh = (y1 , y2 , ..., yh ) prexul lui Y de lungime h. O subproblem a a problemei date const a n determinarea celui mai lung sub sir comun al lui Xk , Yh . Notam cu LCS (Xk , Yh ) lungimea celui mai lung sub sir comun al lui Xk , Yh . Utiliz and aceste notat ii, problema cere determinarea LCS (Xn , Ym ), precum si un astfel de sub sir. Observat ie 1. Dac a Xk = Yh atunci LCS (Xk , Yh ) = 1 + LCS (Xk1 , Yh1 ). 2. Dac a Xk = Y h atunci LCS (Xk, Y h) = max(LCS (Xk1 , Yh ), LCS (Xk , Yh1 )). Din observat ia precedent a deducem c a subproblemele problemei date nu sunt independente si c a problema are substructur a optimal a. 2. Pentru a ret ine solut iile subproblemelor vom utiliza o matrice cu n + 1 linii si m + 1 coloane, denumit a lcs. Linia si coloana 0 sunt utilizate pentru init ializare cu 0, iar elementul lcs[k ][h] va lungimea celui mai lung sub sir comun al sirurilor Xk si Yh . 3. Vom caracteriza substructura optimal a a problemei prin urm atoarea relat ie de recurent a: lcs[k ][0] = lcs[0][h] = 0, k = {1, 2, .., n}, h = {1, 2, .., m} max lcs[k ][h 1], lcs[k 1][h], dac a x[k ] <> y [h] lcs[k ][h] = 1 + lcs[k 1][h 1], dac a x[k ] = y [h]

Rezolv am relat ia de recurent a n mod bottom-up:

354

CAPITOLUL 16. PROGRAMARE DINAMICA

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 a de date suplimentar a cu ajutorul c areia s a memor am solut ia optim a, vom reconstitui solut ia optim a pe baza rezultatelor memorate n matricea lcs. Prin reconstituire vom obt ine solut ia n ordine invers a, din acest motiv vom memora solutia ntr-un vector, pe care l vom a sa de la sf ar sit c atre nceput: 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] << ; Secvent ele de cod de mai sus sunt scrise n C/C++. Programul urm ator este scris n Java si determin a toate solut iile. Sunt determinate cea mai mic a si cea mai mare solut ie, n ordine lexicograc a. De asemenea sunt a sate matricele auxiliare de lucru pentru a se putea urm arii mai u sor modalitatea de determinare recursiv a a tuturor solut iilor. import java.io.*; // SubsirComunMaximal class scm { static PrintWriter out; static int [][] a;

16.2. PROBLEME REZOLVATE static char [][] d; static static static static String x,y; char[] z,zmin,zmax; int nsol=0,n,m; final char sus=|, stanga=-, diag=*;

355

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 = "); afisv(zmax); out.close(); } static void citire() throws IOException { BufferedReader br=new BufferedReader(new FileReader("scm.in")); x=br.readLine(); y=br.readLine(); }

356

CAPITOLUL 16. PROGRAMARE DINAMICA

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

16.2. PROBLEME REZOLVATE {

357

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

358

CAPITOLUL 16. PROGRAMARE DINAMICA

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++) { 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();

16.2. PROBLEME REZOLVATE 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 atoarele rezultate: 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 3 0 1 1 2 2 2 2 2 2 2 2 2 2 2 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 6 0 1 2 2 3 3 4 4 4 4 4 4 4 5 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 c 0 1 2 2 3 4 4 5 5 6 6 6 6 b 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 f 0 1 2 2 3 4 4 5 6 6 7 7 8 e 0 1 2 2 3 4 4 5 6 6 7 8 8

359

1 2 3 4 5 6 a b c d e f

1 3 2 4 6 5 a c b d f e 1 2 3 4 * | | | * | * * -

360 5 6 a b c d e f | | | | | | | | | | | | | | | | | | | | | | | | * | | | | | | * * | | | | | * | | | * * | | *

CAPITOLUL 16. PROGRAMARE DINAMICA * -

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

16.2.5

Distant a minim a de editare

Fie d(s1, s2) distant a de editare (denit a ca ind num arul minim de operat ii de stergere, inserare si modicare) dintre sirurile de caractere s1 si s2. Atunci: d(, ) = 0 d(s, ) = d(, s) = |s|, (16.2.1) (16.2.2)

Av and n vedere ultima operat ie efectuat a asupra primului sir de caractere, la sf ar situl acestuia (dar dup a modic arile efectuate deja), obt inem:

16.2. PROBLEME REZOLVATE

361

d(s1 + ch1 , s2 ) + 1, inserare ch2 d(s , s + ch ) + 1, stergere ch1 1 2 2 d(s1 + ch1 , s2 + ch2 ) = min (16.2.3) 0 dac a ch1 = ch2 , nimic! d ( s , s ) + 1 2 1 dac a ch1 = ch2 , nlocuire Folosim o matrice c[0..|s1|][0..|s2|] unde c[i][j ] = d(s1 [1..i], s2 [1..j ]). Algoritmul n pseudocod este:
def

m[0][0]=0; for(i=1; i<=length(s1); i++) m[i][0]=i; for(j=1; j<=length(s2); j++) m[0][j]=j; for(i=1; i<=length(s1); i++) for(j=1;j<=length(s2); j++) { val=(s1[i]==s2[j]) ? 0 : 1; m[i][j]=min( m[i-1][j-1]+val, m[i-1][j]+1, m[i][j-1]+1 ); } Programul surs a: import java.io.*; class DistEdit { static final char inserez=i, // inserez inaintea pozitiei sterg=s, modific=m, nimic= ; // caracter !!! static final char sus=|, stanga=-, diags=x; static static static static int na,nb; int [][] c; // c[i][j] = cost a1..ai --> b1..bj char [][] op; // op = operatia efectuata char [][] dir; // dir = directii pentru minim !!! // a--> b

static String a,b;

public static void main(String[] args) throws IOException { int i,j,cmin=0;

362

CAPITOLUL 16. PROGRAMARE DINAMICA BufferedReader br=new BufferedReader( new FileReader("distedit.in")); PrintWriter out=new PrintWriter( new BufferedWriter(new FileWriter("distedit.out"))); a=br.readLine(); b=br.readLine(); na=a.length(); nb=b.length(); c=new int[na+1][nb+1]; op=new char[na+1][nb+1]; dir=new char[na+1][nb+1]; System.out.println(a+" --> "+na); System.out.println(b+" --> "+nb); for(i=1;i<=na;i++) { c[i][0]=i; // stergeri din a; b=vid !!! op[i][0]=sterg; // s_i dir[i][0]=sus; } for(j=1;j<=nb;j++) { c[0][j]=j; // inserari in a; op[0][j]=inserez; //t_j dir[0][j]=stanga; } op[0][0]=nimic; dir[0][0]=nimic; for(i=1;i<=na;i++) { for(j=1;j<=nb;j++) { if(a.charAt(i-1)==b.charAt(j-1)) { c[i][j]=c[i-1][j-1]; op[i][j]=nimic; dir[i][j]=diags; }

a=vid !!!

16.2. PROBLEME REZOLVATE else { cmin=min(c[i-1][j-1],c[i-1][j],c[i][j-1]); c[i][j]=1+cmin; if(cmin==c[i][j-1]) // inserez t_j { op[i][j]=inserez; dir[i][j]=stanga; } else if(cmin==c[i-1][j]) // sterg s_i { op[i][j]=sterg; dir[i][j]=sus; } else if(cmin==c[i-1][j-1]) //s_i-->t_j { op[i][j]=modific; dir[i][j]=diags; } }// else }// for j }// for i afismi(c,out); afismc(dir,out); afismc(op,out); afissol(na,nb,out); out.println("\nCOST transformare = "+c[na][nb]); out.close(); System.out.println("COST transformare = "+c[na][nb]); }// main static void afismc(char[][] x, PrintWriter out) { int i,j; out.print(" "); for(j=1;j<=nb;j++) out.print(b.charAt(j-1)); for(i=0;i<=na;i++) { out.println(); if(i>0) out.print(a.charAt(i-1)); else out.print(" ");

363

364

CAPITOLUL 16. PROGRAMARE DINAMICA

for(j=0;j<=nb;j++) out.print(x[i][j]); } out.println("\n"); }// afismc(...) static void afismi(int[][] x, PrintWriter out) { int i,j; out.print(" "); for(j=1;j<=nb;j++) out.print(b.charAt(j-1)); for(i=0;i<=na;i++) { out.println(); if(i>0) out.print(a.charAt(i-1)); else out.print(" "); for(j=0;j<=nb;j++) out.print(x[i][j]); } out.println("\n"); }// afismi(...) static void afissol(int i,int j, PrintWriter out) { if(i==0&&j==0) return; if(dir[i][j]==diags) afissol(i-1,j-1,out); else if(dir[i][j]==stanga) afissol(i,j-1,out); else if(dir[i][j]==sus) afissol(i-1,j,out); if((i>0)&&(j>0)) { if(op[i][j]==sterg) out.println(i+" "+a.charAt(i-1)+" "+op[i][j]); else if(op[i][j]==inserez) out.println(" "+op[i][j]+" "+j+" "+b.charAt(j-1)); else out.println(i+" "+a.charAt(i-1)+" "+op[i][j]+" "+j+" "+b.charAt(j-1)); } else if(i==0) out.println(i+" "+a.charAt(i)+" "+op[i][j]+" "+j+" "+b.charAt(j-1)); else if(j==0) out.println(i+" "+a.charAt(i-1)+" "+op[i][j]+" "+j+" "+b.charAt(j));

16.2. PROBLEME REZOLVATE }//afissol(...)

365

static int min(int a, int b) { return a<b ? a:b; } static int min(int a, int b, int c) { return min(a,min(b,c)); } }// class Rezultate a sate n sierul de ie sire: altruisti 0123456789 a1012345678 l2101234567 g3211234567 o4322234567 r5433234567 i6544333456 t7654444445 m8765555555 i9876665665 altruisti --------a|x-------l||x------g|||x-----o||||x----r||||x----i|||||xx--x t|||x|||xxm|||||||||x i||||||x-|x altruisti iiiiiiiii as iiiiiiii lss iiiiiii gsssmiiiiii ossssmiiiii rssss iiiii isssssm ii tsss sssm i msssssssssm issssss is

366 1 2 3 4 5 6 7 8 9 a 1 l 2 g m 3 o s r 4 i 5 i 6 i 7 t 8 m s i 9 a l t r u i s t i

CAPITOLUL 16. PROGRAMARE DINAMICA

COST transformare = 5

16.2.6

Problema rucsacului (0 1)

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;

16.2. PROBLEME REZOLVATE 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) { if(a>b) return a; else return b; }// max(...) }

367

16.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; }

368

CAPITOLUL 16. PROGRAMARE DINAMICA

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

16.2.8

Problema travers arii matricei

Traversarea unei matrice de la Vest la Est; sunt permise deplas ari spre vecinii unei pozit ii (chiar si deplas ari 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"))); st.nextToken(); m=(int)st.nval; st.nextToken(); n=(int)st.nval; a=new c=new t=new d=new int[m][n]; int[m][n]; int[m][n]; int[n];

for(i=0;i<m;i++) for(j=0;j<n;j++) { st.nextToken();

16.2. PROBLEME REZOLVATE 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)

369

370 {

CAPITOLUL 16. PROGRAMARE DINAMICA

return min(min(a,b),c); } }// 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 */

16.2.9

Problema segment arii vergelei

Scopul algoritmului este de a realiza n t aieturi, dea lungul unei vergele n locuri pre-specicate, cu efort minim (sau cost). Costul ec arei operat ii de t aiere este proport ional cu lungimea vergelei care trebuie t aiat a. In viat a real a, ne putem imagina costul ca ind efortul depus pentru a plasa vergeaua (sau un bu stean!) n ma sina de t aiat. Consider am sirul de numere naturale 0 < x1 < x2 < ... < xn < xn+1 n care xn+1 reprezint a lungimea vergelei iar x1 , x2 , ..., xn reprezint a abscisele punctelor n care se vor realiza t aieturile (distant ele fat a de cap atul din st anga al vergelei). Not am prin c[i][j ] (i < j ) costul minim necesar realiz arii tuturor t aieturilor segmentului de vergea [xi ..xj ]. Evident c[i][i + 1] = 0 pentru c a nu este necesar a nici o t aietur a. Pentru j > i s a presupunem c a realiz am prima t aietur a n xk (i < k < j ). Din vergeaua [xi ...xj ] obt inem dou a bucat i mai mici: [xi ...xk ] si [xk ...xj ]. Costul pentru t aierea vergelei [xi ...xj ] este format din costul transportului acesteia la ma sina de t aiat (xj xi ) + costul t aierii vergelei [xi ...xk ] (adic a c[i][k ]) si + costul t aierii vergelei [xk ...xj ] (adic a c[k ][j ]). Dar, ce valoare are k ? Evident, k trebuie s a aib a acea valoare care s a minimizeze expresia c[i][k ] + c[k ][j ]. Obt inem: c[i][j ] = xj xi + min {c[i][k ] + c[k ][j ]}
i<k<j

import java.io.*; class Vergea { static int n,nt=0; static int x[]; static int c[][]; static int t[][];

16.2. PROBLEME REZOLVATE

371

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]+" "); 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

372 }// for h

CAPITOLUL 16. PROGRAMARE DINAMICA

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 0 0 0 0 0 0 0 0 0 0 0 0 4 0 0 0 0 0 0 8 16 27 38 3 9 19 29 0 4 12 22 0 0 7 17 0 0 0 7 0 0 0 0 0 0 0 0

16.2. PROBLEME REZOLVATE 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 0 0 0 0 0 0 1 2 0 0 0 0 0 2 3 3 0 0 0 0 3 4 4 4 0 0 0 4 4 4 4 5 0 0

373

Ordinea taieturilor: 1 : 2 : 3 : 4 : 5 : */ 0..6 0..4 0..2 2..4 4..6 --> --> --> --> --> 4 2 1 3 5

16.2.10

Triangularizarea poligoanelor convexe

Consider am un poligon convex cu n v arfuri numerotate cu 1, 2, ..., n ( n gur a n = 9) si dorim s a obt inem o triangularizare n care suma lungimilor diagonalelor trasate s a e minim a (ca si cum am dori s a consum am c at mai put in tu s pentru trasarea acestora!).
1 2 9 8 7 3 4 6 5 3 4 5 5 5 6 4 555 2 1 9 9 8 7 3 6 6 2 11 9 9

8 8

8 7

Evident, orice latur a a poligonului face parte dintr-un triunghi al triangulat iei. Consider am la nceput latura [1, 9]. S a presupunem c a ntr-o anumit a triangulat ie optim a latura [1, 9] face parte din triunghiul [1, 5, 9]. Diagonalele triangulat iei optime vor genera o triangulat ie optim a a poligoanelor convexe [1, 2, 3, 4, 5] si [5, 6, 7, 8, 9]. Au ap arut astfel dou a subprobleme ale problemei init iale. S a not am prin p(i, k, j ) perimetrul triunghiului [i, k, j ] (i < k < j ) i si prin c[i][j ] costul minim al triagulat iei poligonului convex [i, i + 1, ..., j ] (unde i < j ). Atunci: c[i][j ] = 0, dac a j =i+1

374 si

CAPITOLUL 16. PROGRAMARE DINAMICA

c[i][j ] = min {p(i, k, j ) + c[i][k ] + c[k ][j ]}, dac ai<jn


ik<j

16.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" 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(k=1;k<=m;k++) { st.nextToken(); st.nextToken(); st.nextToken(); } for(j=1;j<=n;j++) d[i][j]=oo;

i=(int)st.nval; j=(int)st.nval; 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

16.2. PROBLEME REZOLVATE 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 1 3 2 3 4 5 5 6 4 6 */

375

3 1 2 1 3 2

2 3 1 * * *

3 4 2 * * *

1 2 2 * * *

* * * 2 1 2

* * * 1 2 3

* * * 2 3 4

16.2.12

Oracolul decide - ONI2001 cls 10

prof. Doru Popescu Anastasiu, Slatina La un concurs particip a N concurent i. Fiecare concurent prime ste o foaie de h artie pe care va scrie un cuv ant av and cel mult 100 de caractere (litere mici ale alfabetului englez). Cuvintele vor distincte. Pentru departajare, concurent ii apeleaz a la un oracol. Acesta produce si el un cuvnt. Va c a stiga concurentul care a scris cuv antul cel mai apropiat de al oracolului. Gradul de apropiere dintre dou a cuvinte este lungimea subcuv antului comun de lungime maxim a. Prin subcuv ant al unui cuv ant dat se nt elege un cuv ant care se poate obt ine din cuv antul dat, elimin and 0 sau mai multe litere si p astr and ordinea literelor r amase. Cerint a Se cunosc cuv antul c0 produs de oracol si cuvintele ci , i = 1, ..., N scrise de concurent i. Pentru a ajuta comisia s a desemneze c a stig atorul, se cere ca pentru ecare i s a identicat i pozit iile literelor ce trebuie sterse din c0 si din ci astfel nc at prin stergere s a se obt in a unul dintre subcuvintele comune de lungime maxim a.

376

CAPITOLUL 16. PROGRAMARE DINAMICA

Date de intrare Fi sier de intrare: ORACOL.IN Linia 1: N num ar natural nenul, reprezent and num arul concurent ilor; Linia 2: c0 cuv antul produs de oracol; Liniile 3..N+2: cuv ant pe aceste N linii se a a cuvintele scrise de cei N concurent i, un cuv ant pe o linie; Date de ie sire Fi sier de ie sire: ORACOL.OUT Liniile 1..2*N: pozit iile literelor ce trebuie sterse pe ecare linie i (i = 1, 3, ..., 2 N 1) se vor scrie numere naturale nenule, separate prin c ate un spat iu, reprezent and pozit iile de pe care se vor sterge litere din cuv antul produs de oracol; pe ecare linie j (j = 2, 4, ..., 2 N ) se vor scrie numere naturale nenule, separate prin c ate un spat iu, reprezent and pozit iile de pe care se vor sterge litere din cuv antul concurentului cu num arul j/2. Restrict ii 2 N 100 Dac a exist a mai multe solut ii, n sier se va scrie una singur a. Dac a dintr-un cuv ant nu se va t aia nici o liter a, linia respectiv a din sierul de intrare va r am ane vid a. Exemplu ORACOL.IN ORACOL.OUT poate cont ine solut ia: 3 3 abc 34 abxd aabxyc 145 acb 3 2 Timp maxim de executare/test: 1 secund a Codul surs a Varianta 1: import java.io.*; // subsir comun maximal - problema clasica ... class Oracol // varianta cu mesaje pentru depanare ... { static final char sus=|, stanga=-, diag=*; static int[][] a; static char[][] d; static String x,y; static boolean[] xx=new boolean[101]; static boolean[] yy=new boolean[101];

16.2. PROBLEME REZOLVATE static char[] z; static int m,n,nc;

377

public static void main(String[] args) throws IOException { int i,j,k; BufferedReader br=new BufferedReader(new FileReader("oracol.in")); PrintWriter out=new PrintWriter( new BufferedWriter(new FileWriter("oracol.out"))); nc=Integer.parseInt(br.readLine()); x=br.readLine().replace(" ",""); // elimina spatiile ... de la sfarsit ! m=x.length(); for(k=1;k<=nc;k++) { y=br.readLine().replaceAll(" ",""); // elimina spatiile ... daca sunt! n=y.length(); matrad(); afism(a); afism(d); System.out.print("O solutie oarecare: "); z=new char[a[m][n]+1]; for(i=1;i<=m;i++) xx[i]=false; for(j=1;j<=n;j++) yy[j]=false; osol(m,n); System.out.println("\n"); for(i=1;i<=m;i++) if(!xx[i]) out.print(i+" "); out.println(); for(j=1;j<=n;j++) if(!yy[j]) out.print(j+" "); out.println(); } out.close(); System.out.println("\n"); }// main(...) static void matrad() { int i,j; a=new int[m+1][n+1];

378

CAPITOLUL 16. PROGRAMARE DINAMICA

d=new char[m+1][n+1]; for(i=1;i<=m;i++) for(j=1;j<=n;j++) if(x.charAt(i-1)==y.charAt(j-1)) { 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; } }// matrad() 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)); xx[lin]=yy[col]=true; } }// osol(...) static int max(int a, int b) { if(a>b) return a; else return b; }// max(...) static void afism(int[][] a) { int i,j; System.out.print(" "); for(j=0;j<n;j++) System.out.print(y.charAt(j)+" "); System.out.println();

16.2. PROBLEME REZOLVATE System.out.print(" "); for(j=0;j<=n;j++) System.out.print(a[0][j]+" "); System.out.println(); for(i=1;i<=m;i++) { System.out.print(x.charAt(i-1)+" "); for(j=0;j<=n;j++) System.out.print(a[i][j]+" "); System.out.println(); } System.out.println("\n"); }// afism(int[][]...) static void afism(char[][] d) // difera tipul parametrului { int i,j; System.out.print(" "); for(j=0;j<n;j++) System.out.print(y.charAt(j)+" "); System.out.println(); System.out.print(" "); for(j=0;j<=n;j++) System.out.print(d[0][j]+" "); System.out.println(); for(i=1;i<=m;i++) { System.out.print(x.charAt(i-1)+" "); for(j=0;j<=n;j++) System.out.print(d[i][j]+" "); System.out.println(); } System.out.println("\n"); }// afism(char[][]...) }// class Varianta 2: import java.io.*; // problema reala ... class Oracol { static final char sus=|, stanga=-, diag=*; static int[][] a; static char[][] d; static String x,y; static boolean[] xx=new boolean[101];

379

380

CAPITOLUL 16. PROGRAMARE DINAMICA

static boolean[] yy=new boolean[101]; static int m,n,nc; public static void main(String[] args) throws IOException { int i,j,k; BufferedReader br=new BufferedReader(new FileReader("oracol.in")); PrintWriter out=new PrintWriter( new BufferedWriter( new FileWriter("oracol.out"))); nc=Integer.parseInt(br.readLine()); x=br.readLine().replace(" ",""); // elimina spatiile ... de la sfarsit ! m=x.length(); for(k=1;k<=nc;k++) { y=br.readLine().replaceAll(" ",""); // elimina spatiile ... daca sunt! n=y.length(); matrad(); for(i=1;i<=m;i++) xx[i]=false; for(j=1;j<=n;j++) yy[j]=false; osol(m,n); for(i=1;i<=m;i++) if(!xx[i]) out.print(i+" "); out.println(); for(j=1;j<=n;j++) if(!yy[j]) out.print(j+" "); out.println(); } out.close(); }// main(...) static void matrad() { int i,j; a=new int[m+1][n+1]; d=new char[m+1][n+1]; for(i=1;i<=m;i++) for(j=1;j<=n;j++) if(x.charAt(i-1)==y.charAt(j-1)) { a[i][j]=1+a[i-1][j-1]; d[i][j]=diag;

16.2. PROBLEME REZOLVATE } 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; } }// matrad() 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) xx[lin]=yy[col]=true; }// osol(...) static int max(int a, int b) { if(a>b) return a; else return b; }// max(...) }// class

381

16.2.13

Pav ari - ONI2001 clasa a X-a

prof. Doru Popescu Anastasiu, Slatina Se d a un dreptunghi cu lungimea egal a cu 2N centimetri si l a timea egal a cu 3 centimetri . Cerint a S a se determine num arul M al pav arilor distincte cu dale dreptunghiulare care au lungimea egal a cu un centimetru si l a timea egal a cu 2 centimetri. Datele de intrare Fi sier de intrare: pavari.in Linia 1: N - num ar natural nenul, reprezent annd jum atatea lungimii dreptunghiului. Datele de ie sire Fi sier de ie sire: pavari.out Linia 1: M - num ar natural nenul, reprezent and num arul modalit a tilor de a pava dreptunghiul. Restrict ii si preciz ari 1 N 100

382 Exemplu pavari.in 2

CAPITOLUL 16. PROGRAMARE DINAMICA

pavari.out 11

Timp maxim de executare: 1 secund a/test Codul surs a*

import java.io.*; // x[n]=x[n-1]+2*y[n-1]; x[1]=3; class Pavari // y[n]=y[n-1]+x[n]; y[1]=4; { public static void main(String[] args) throws IOException { int k,kk,n; StreamTokenizer st=new StreamTokenizer( new BufferedReader(new FileReader("pavari9.in"))); st.nextToken(); n=(int)st.nval; int[] xv,xn,yv,yn; xv=new int[1]; yv=new int[1]; xv[0]=3; yv[0]=4; xn=xv; for(k=2;k<=n;k++) { xn=suma(xv,suma(yv,yv)); yn=suma(yv,xn); xv=xn; yv=yn; } PrintWriter out=new PrintWriter( new BufferedWriter(new FileWriter("pavari.out"))); for(kk=xn.length-1;kk>=0;kk--) out.print(xn[kk]); out.close(); }// main(...) static int[] suma(int[] x, int[] y) { int nx=x.length,ny=y.length,i,t; int nz; if(nx>ny) nz=nx+1; else nz=ny+1;

16.2. PROBLEME REZOLVATE int[] z=new int[nz]; t=0; for(i=0;i<nz;i++) { z[i]=t; if(i<nx) z[i]+=x[i]; if(i<ny) z[i]+=y[i]; t=z[i]/10; z[i]=z[i]%10; } if(z[nz-1]!=0) return z; else { int[] zz=new int[nz-1]; for(i=0;i<nz-1;i++) zz[i]=z[i]; return zz; } }//suma }

383

16.2.14

Balant a ONI2002 clasa a X-a

Gigel are o balant a mai ciudat a pe care vrea s a o echilibreze. De fapt, aparatul este diferit de orice balant a pe care at i vazut-o p an a acum. Balant a lui Gigel dispune de dou a brat e de greutate neglijabil a si lungime 15 ecare. Din loc n loc, la aceste brat e sunt ata sate c arlige, pe care Gigel poate at arna greut a ti distincte din colect ia sa de G greut a ti (numere naturale ntre 1 si 25). Gigel poate at arna oric ate greut a ti de orice c arlig, dar trebuie s a foloseasc a toate greut a tile de care dispune. Folosindu-se de experient a particip arii la Olimpiada Nat ional a de Informatic a, Gigel a reu sit s a echilibreze balant a relativ repede, dar acum dore ste s a stie n c ate moduri poate ea echilibrat a. Cerint a Cunosc and amplasamentul c arligelor si setul de greut a ti pe care Gigel l are la dispozit ie, scriet i un program care calculeaz a n c ate moduri se poate echilibra balant a. Se presupune c a este posibil s a se echilibreze balant a (va posibil pe toate testele date la evaluare). Datele de intrare Fi sierul de intrare balanta.in are urm atoarea structur a:

384

CAPITOLUL 16. PROGRAMARE DINAMICA

pe prima linie, num arul C de c arlige si num arul G de greut a ti, valori separate prin spat iu; pe urm atoarea linie, C numere ntregi, distincte, separate prin spat iu, cu valori cuprinse ntre 15 si 15 inclusiv, reprezent and amplasamentele c arligelor fat a de centrul balant ei; valoarea absolut a a numerelor reprezint a distant a fat a de centrul balant ei, iar semnul precizeaz a brat ul balant ei la care este ata sat crligul, - pentru brat ul st ang si + pentru brat ul drept; pe urm atoarea linie, G numere naturale distincte, cuprinse ntre 1 si 25 inclusiv, reprezent and valorile greut a tilor pe care Gigel le va folosi pentru a echilibra balant a. Datele de ie sire Fi sierul de ie sire balanta.out cont ine o singur a linie, pe care se a a un num ar natural M , num arul de variante de plasare a greut a tilor care duc la echilibrarea balant ei. Restrict ii si preciz ari 2 C 20, 2 G 20; greut a tile folosite au valori naturale ntre 1 si 25; num arul M cerut este ntre 1 si 100.000.000; celelalte restrict ii (lungimea brat elor balant ei etc.) au fost prezentate ante-

rior.

balant a se echilibreaz a dac a suma produselor dintre greut a ti si coordonatele unde ele sunt plasate este 0 (suma momentelor greut a tilor fat a de centrul balant ei este 0). Exemplu balanta.in 24 -2 3 3458 balanta.out 2

Timp maxim de executare: 1 secund a/test Indicat ii de rezolvare * Solut ia comisiei Problema se rezolva prin metoda program arii dinamice. Se calculeaz a n c ate moduri se poate scrie ecare sum a j , folosind primele i greut a ti. Init ial i = 0 si suma 0 se poate obt ine ntr-un singur mod, restul sumelor n 0 moduri. Urmeaza G pa si. La ecare astfel de pas i se calculeaz a n c ate moduri putem obt ine ecare sum a introduc and o nou a greutate - a i-a - n toate congurat iile precedente. Practic, dac a suma S s-a obtinut cu primele i 1 greut a ti n M moduri, pun and greutatea i pe c arligul k se va obt ine suma S +(greutate[i] coordonata[k ])

16.2. PROBLEME REZOLVATE

385

n M moduri (la care, evident, se pot adauga alte moduri de obt inere plas and greutatea i pe un alt c arlig si folosind suma respectiv a). In acest mod s-ar construi o matrice cu G linii si 2 (sumamaxima) + 1 coloane, cu elemente numere ntregi pe 32 de bit i; de fapt se memoreaza doar ultimele dou a linii (ecare linie se obt ine din precedenta). Suma maxim a este 15 25 20 = 7500, deci o linie se incadreaz a n mai put in de 64K . Rezultatul nal se obt ine pe ultima linie, n coloana asociat a sumei 0. GInfo 12/6 octombrie 2002 Se observ a c a suma momentelor greut a tilor este cuprins a ntre 6000 si 6000 (dac a avem 20 de greut a ti cu valoarea 20 si acestea sunt amplasate pe cel mai n dep artat c arlig fat a de centrul balant ei, atunci modulul sumei momentelor fort elor este 152020 = 6000). Ca urmare, putem p astra un sir a ale c arui valori ai vor cont ine num arul posibilit a tilor ca suma momentelor greut a tilor s a e i. Indicii sirului vor varia ntre 6000 si 6000. Pentru a mbun at a ti viteza de execut ie a programului, indicii vor varia ntre 300g si 300g , unde g este num arul greut a tilor. Pot realizate mbun at a tiri suplimentare dac a se determin a distant ele maxime fat a de mijlocul balant ei ale celor mai ndep artate c arlige de pe cele dou a talere si suma total a a greut a tilor. Dac a distant ele sunt d1 si d2 , iar suma este s, atunci indicii vor varia ntre d1 s si d2 s. Init ial, pe balant a nu este ag a tat a nici o greutate, a sadar suma momentelor greut a tilor este 0. Ca urmare, init ial valorile ai vor 0 pentru orice indice nenul si a0 = 1 (exist a o posibilitate ca init ial suma momentelor greut ailor s a e 0 si nu exist a nici o posibilitate ca ea s a e diferit a de 0). In continuare, vom ncerca s a amplas am greut a tile pe c arlige. Fiecare greutate poate amplasat a pe oricare dintre c arlige. S a presupunem c a la un moment dat exist a ai posibilit a ti de a obt ine suma i. Dac a vom amplasa o greutate de valoare g pe un c arlig aat la distant a d fat a de centrul balant ei, suma momentelor greut a tilor va cre ste sau va sc adea cu gd ( n funct ie de brat ul pe care se a a c arligul). Ca urmare, dup a amplasarea noii greut a ti exist a ai posibilit a ti de a obt ine suma i + gd. Consider am c a sirul b va cont ine valori care reprezint a num arul posibilit a tilor de a obt ine sume ale momentelor fort elor dup a amplasarea greut a tii curente. Inainte de a testa posibilit aile de plasare a greut a tii, sirul b va cont ine doar zerouri. Pentru ecare pereche (i, d), valoarea bi+gd va cre ste cu ai . Dup a considerarea tuturor perechilor, vom putea trece la o nou a greutate. Valorile din sirul b vor salvate n sirul a, iar sirul b va reinit ializat cu 0. Rezultatul nal va dat de valoarea a0 obt inut a dup a considerarea tuturor greut a tilor disponibile. Analiza complexit a tii Pentru studiul complexit a tii vom nota num arul greut a tilor cu g , iar cel al c arligelor cu c. Citirea datelor de intrare corespunz atoare c arligelor si greutat ilor se realizeaz a n timp liniar, deci ordinul de complexitate al acestor operat ii este O(g ), respectiv O(c).

386

CAPITOLUL 16. PROGRAMARE DINAMICA

Singura m arime care nu este considerat a constant a si de care depinde num arul de posibilit a ti de a obt ine sumele este num arul greut a tilor. A sadar, vom considera c a ordinul de complexitate al travers arii sirului a este O(g ). Num arul de travers ari este dat tot de num arul greut a tilor disponibile, deci vom avea O(g ) travers ari. In timpul travers arii vom considera toate c arligele pentru ecare element al sirului. Ca urmare, ordinul de complexitate al operat iilor efectuate asupra unui element ntr-o parcurgere este O(c). Rezult a c a ordinul de complexitate al unei parcurgeri este O(g )O(c) = O(gc), n timp ce ordinul de complexitate al ntregii operat ii care duce la obt inerea rezultatului este O(g )O(gc) = O(g 2c). A sarea num arului de posibilit a ti de a echilibra balant a se realizeaz a n timp constant. In concluzie, ordinul de complexitate al algoritmului de rezolvare a acestei probleme este O(c) + O(g ) + O(g 2 c) + O(1) = O(g 2 c). Codul surs a

import java.io.*; class Balanta { static long[] a=new long[15001]; static long[] b=new long[15001]; static int[] carlig=new int[20]; static int[] greutate=new int[20]; static int nrCarlige, nrGreutati;

// // // //

a[i] i = -7500, 7500 sir auxiliar (a+7500)!!! coordonatele carligelor valorile greutatilor

public static void main(String[] args) throws IOException { long t1,t2; t1=System.currentTimeMillis(); citesteDatele(); determinaSolutia(); scrieSolutia(); t2=System.currentTimeMillis(); System.out.println("TIMP = "+(t2-t1)+" milisecunde"); }// main() static void citesteDatele() throws IOException { StreamTokenizer st=new StreamTokenizer( new BufferedReader(new FileReader("balanta9.in"))); st.nextToken(); nrCarlige=(int)st.nval;

16.2. PROBLEME REZOLVATE st.nextToken(); nrGreutati=(int)st.nval; for(int i=0;i<nrCarlige;i++) { st.nextToken(); carlig[i]=(int)st.nval; } for(int i=0;i<nrGreutati;i++) { st.nextToken(); greutate[i]=(int)st.nval; } }// citesteDate()

387

static void scrieSolutia() throws IOException { PrintWriter out=new PrintWriter( new BufferedWriter(new FileWriter("balanta9.out"))); out.print(a[7500+0]); out.close(); }// scrieSolutia() static void determinaSolutia() { int i,j,k; a[7500+0]=1; // initial balanta este echilibrata for(i=0;i<nrGreutati;i++) { for(j=-7500;j<=7500;j++) if(a[7500+j]!=0) for(k=0;k<nrCarlige;k++) b[7500+j+carlig[k]*greutate[i]]+=a[7500+j]; for (j=-7500;j<=7500;j++) { a[7500+j]=b[7500+j]; b[7500+j]=0; } } }// determinaSolutia() }// class

16.2.15

Aliniere ONI2002 clasa a X-a

In armat a, o companie este alc atuit a din n soldat i. La inspect ia de dimineat a soldat ii stau aliniat i n linie dreapt a n fat a c apitanului. Acesta nu e mult umit de

388

CAPITOLUL 16. PROGRAMARE DINAMICA

ceea ce vede; e drept c a soldat ii sunt a sezat i n ordinea numerelor de cod 1, 2, ..., n din registru, dar nu n ordinea n alt imii. C apitanul cere c atorva soldat i s a ias a din r and, astfel ca cei r ama si, f ar a a- si schimba locurile, doar apropiindu-se unul de altul (pentru a nu r am ane spat ii mari ntre ei) s a formeze un sir n care ecare soldat vede privind de-a lungul sirului, cel put in una din extremit a ti (st anga sau dreapta). Un soldat vede o extremitate dac a ntre el si cap atul respectiv nu exist a un alt soldat cu n alt imea mai mare sau egal a ca a lui. Cerint a Scriet i un program care determin a, cunosc and n alt imea ec arui soldat, num arul minim de soldat i care trebuie s a p ar aseasc a format ia astfel ca sirul r amas s a ndeplineasc a condit ia din enunt . Datele de intrare Pe prima linie a sierului de intrare aliniere.in este scris num arul n al soldat ilor din sir, iar pe linia urm atoare un sir de n numere reale, cu maximum 5 zecimale ecare si separate prin spat ii. Al k -lea num ar de pe aceast a linie reprezint a n alt imea soldatului cu codul k (1 k n). Datele de ie sire Fi sierul aliniere.out va cont ine pe prima linie num arul soldat ilor care trebuie s a p ar aseasc a format ia, iar pe linia urm atoare codurile acestora n ordine cresc atoare, separate dou a c ate dou a printr-un spat iu. Dac a exist a mai multe solut ii posibile, se va scrie una singur a. Restrict ii si preciz ari 2 n 1000 n alt imile sunt numere reale n intervalul [0.5; 2.5]. Exemplu aliniere.in aliniere.out 8 4 1.86 1.86 1.30621 2 1.4 1 1.97 2.2 1 3 7 8 Explicat ie R am an soldat ii cu codurile 2, 4, 5, 6 av and n alt imile 1.86, 2, 1.4 si 1. Soldatul cu codul 2 vede extremitatea st ang a. Soldatul cu codul 4 vede ambele extremit a ti. Soldat ii cu codurile 5 si 6 v ad extremitatea dreapt a. Timp maxim de executare: 1 secund a/test Indicat ii de rezolvare Solut ia comisiei Problema se rezolv a prin metoda program arii dinamice.

16.2. PROBLEME REZOLVATE

389

Se calculeaz a, pentru ecare element, lungimea celui mai lung sub sir strict cresc ator care se termin a cu el si lungimea celui mai lung sub sir strict descresc ator care ncepe cu el. Solutia const a n p astrarea a dou a astfel de sub siruri de soldat i (unul cresc ator si unul descresc ator) pentru DOI soldat i de aceea si n alt ime (eventual identici) si eliminarea celorlalt i. Soldat ii din primul sub sir privesc spre stanga, ceilalt i spre dreapta. Primul sub sir se termin a inainte de a ncepe al doilea. Se au in vedere cazurile particulare. Deoarece s-a considerat c a o parte din concurent i vor rezolva problema pentru un singur soldat central (tot i ceilalti soldat i p astrati av and naltimea mai mic a) si nu vor observa cazul n care se pot p astra doi soldat i de aceea si nalt ime, majoritatea testelor se ncadreaz a n acest caz. GInfo 12/6 octombrie 2002 Pentru ecare soldat vom determina cel mai lung sub sir strict cresc ator (din punct de vedere al n alt imii) de soldat i care se termin a cu el, respectiv cel mai lung sub sir strict descresc ator de soldat i care urmeaz a dup a el. Dup a aceast a operat ie, vom determina soldatul pentru care suma lungimilor celor dou a siruri este maxim a. Chiar dac a s-ar p area c a n acest mod am g asit solut ia problemei, mai exist a o posibilitate de a m ari num arul soldat ilor care r am an n sir. S a consider am soldatul cel mai nalt n sirul r amas (cel c aruia i corespunde suma maxim a). Acesta poate privi e spre st anga, e spre dreapta sirului. Din aceste motive, la st anga sau la dreapta sa poate s a se ae un soldat de aceea si n alt ime; unul dintre cei doi va privi spre dreapta, iar cel alalt spre st anga. Totu si, nu putem alege orice soldat cu aceea si n alt ime, ci doar unul pentru care lungimea sirului strict cresc ator (dac a se a a spre st anga) sau a celui strict descresc ator (dac a se a a spre dreapta) este aceea si cu lungimea corespunz atoare sirului strict cresc ator, respectiv strict descresc ator, corespunz atoare celui mai nalt soldat dintre cei r ama si n sir. Dup a identicarea celor doi soldat i de n alt imi egale (sau demonstrarea faptului c a nu exist a o pereche de acest gen care s a respecte condit iile date) se marcheaz a tot i soldat ii din cele dou a sub siruri. Ceilalt i soldat i vor trebui s a p ar aseasc a format ia. Analiza complexit a tii Citirea datelor de intrare se realizeaz a n timp liniar, deci ordinul de complexitate al acestei operat ii este O(n). Chiar dac a exist algoritmi ecient i (care ruleaz a n timp liniar-logaritmic) de determinare a celui mai lung sub sir ordonat, timpul de execut ie admis ne permite folosirea unui algoritm simplu, cu ordinul de complexitate O(n2 ). Acesta va aplicat de dou a ori, dup a care se va c auta valoarea maxim a a sumei lungimilor sirurilor corespunz atoare unui soldat; a sadar identicarea soldatului care poate privi n ambele direct ii este o operat ie cu ordinul de complexitate O(n2 ) + O(n2 ) + 2 O(n) = O(n ). Urmeaz a eventuala identicare a unui alt soldat de aceea si n alt ime care respect a condit iile referitioare la lungimile sub sirurilor. Pentru aceasta se parcurge

390

CAPITOLUL 16. PROGRAMARE DINAMICA

sirul soldat ilor de la soldatul identicat anterior spre extremit a ti; operat ia necesit a un timp liniar. Deteminarea celor dou a sub siruri se realizeaz a n timp liniar dac a, n momentul construirii celor dou a sub siruri, se p astreaz a predecesorul, respectiv succesorul ec arui soldat. In timpul parcurgerii sub sirurilor sunt marcat i soldat ii care r am an n format ie. Pentru scrierea datelor de ie sire se parcurge sirul marcajelor si sunt identicat i soldat ii care p ar asesc format ia. Ordinul de complexitate al acestei operat ii este O(n). In concluzie, ordinul de complexitate al algoritmului de rezolvare a acestei probleme este O(n) + O(n2 ) + O(n) + O(n) + O(n) = O(n2 ). Codul surs a

import java.io.*; class Aliniere { static final String fisi="aliniere.in"; static float[] inalt; // inaltimile soldatilor static int ns, nsr; // nr soldati, nr soldati ramasi static int[] predCresc; // predecesor in subsirul crescator static int[] lgCresc; // lungimea sirului crescator care se termina cu i static int[] succDesc; // succesor in subsirul descrescator static int[] lgDesc; // lungimea sirului descrescator care urmeaza dupa i static boolean[] ramas; // ramas in sir public static void main(String[] args) throws IOException { long t1,t2; t1=System.currentTimeMillis(); citescDate(); subsirCresc(); subsirDesc(); alegeSoldati(); scrieRezultate(); t2=System.currentTimeMillis(); System.out.println("TIME = "+(t2-t1)+" millisec "); }// main() static void citescDate() throws IOException { StreamTokenizer st=new StreamTokenizer(

16.2. PROBLEME REZOLVATE

391

new BufferedReader(new FileReader(fisi))); st.nextToken(); ns=(int)st.nval; predCresc=new int[ns]; lgCresc=new int[ns]; succDesc=new int[ns]; lgDesc=new int[ns]; ramas=new boolean[ns]; inalt=new float[ns]; for(int i=0;i<ns;i++) {st.nextToken(); inalt[i]=(float)st.nval;} }//citescDate() static void subsirCresc() { int i,j; lgCresc[0]=1; predCresc[0]=-1; for(i=1;i<ns;i++) { lgCresc[i]=1; // subsirul formar doar din i predCresc[i]=-1; // nu are predecesor for (int j=0;j<i;j++) if(inalt[j]<inalt[i]) if(lgCresc[i]<lgCresc[j]+1) // sir mai lung { lgCresc[i]=lgCresc[j]+1; predCresc[i] = j; } } }//subsirCresc() static void subsirDesc() { int i,j; lgDesc[ns-1]=0; // nu exista nici un soldat mai mic dupa ns-1 succDesc[ns-1]=-1; // ns-1 nu are succesor for(i=ns-2;i>=0;i--) { lgDesc[i]=0; // nu exista nici un soldat mai mic dupa i succDesc[i]=-1; // i nu are succesor for(j=ns-1;j>i;j--) if(inalt[j]<inalt[i]) // soldat mai mic if(lgDesc[i]<lgDesc[j]+1) // sir mai lung

392 {

CAPITOLUL 16. PROGRAMARE DINAMICA

lgDesc[i]=lgDesc[j]+1; succDesc[i]=j; } } }// subsirDesc()

// actualizarea lg subsir // actualizare succesor

static void alegeSoldati() { int i; // este posibil ca in mijloc sa fie doi soldati cu inaltimi egale int im=-1; // indicele soldatului din mijloc int ic, id; // indicii care delimiteaza in interior cele doua subsiruri for(i=0;i<ns;i++) if(lgCresc[i]+lgDesc[i]>nsr) { nsr=lgCresc[i]+lgDesc[i]; im=i; } // in "mijlocul" sirului se pot afla doi soldati cu aceeasi inaltime ic=im; id=im; // caut in stanga un subsir cu aceeasi lungime --> soldat cu aceeasi inaltime for(i=im-1;i>=0;i--) if(lgCresc[ic]==lgCresc[i]) ic=i; // caut in dreapta un subsir cu aceeasi lungime --> soldat cu aceeasi inaltime for(i=im+1;i<ns;i++) if(lgDesc[id]==lgDesc[i]) id=i; if(ic!=id) // in "mijloc" sunt doi soldati cu aceeasi inaltime nsr++; while(id!=-1) // indice descrescator { ramas[id]=true; id=succDesc[id]; } while(ic!=-1) // indice crescator { ramas[ic] = true; ic=predCresc[ic]; } }// alegeSoldati()

16.2. PROBLEME REZOLVATE

393

static void scrieRezultate() throws IOException { PrintWriter out=new PrintWriter( new BufferedWriter(new FileWriter("aliniere.out"))); out.println(ns- nsr); for(int i=0;i<ns;i++) if(!ramas[i]) out.print((i+1)+" "); out.close(); }// scriuRezultate() }// class

16.2.16

Munte - ONI2003 cls 10

Intr-o zon a montan a se dore ste deschiderea unui lant de telecabine. Stat iile de telecabine pot nint ate pe oricare din cele N v arfuri ale zonei montane. V arfurile sunt date n ordine de la st anga la dreapta si numerotate de la 1 la N , ecare v arf i ind precizat prin coordonata X [i] pe axa OX si prin n alt imea H [i]. Se vor nint a exact K stat ii de telecabine. Stat ia de telecabine i (2 i K ) va conectat a cu stat iile i 1 si i + 1; stat ia 1 va conectat a doar cu stat ia 2, iar stat ia K , doar cu stat ia K 1. Stat ia 1 va obligatoriu amplasat a n v arful 1, iar stat ia K n v arful N . Se dore ste ca lant ul de telecabine s a asigure leg atura ntre v arful 1 si v arful N . Mai mult, se dore ste ca lungimea total a a cablurilor folosite pentru conectare s a e minim a. Lungimea cablului folosit pentru a conecta dou a stat ii este egal a cu distant a dintre ele. In plus, un cablu care une ste dou a stat ii consecutive nu poate avea lungimea mai mare dec at o lungime xat a L. O restrict ie suplimentar aeste introdus a de formele de relief. Astfel, v arfurile i si j (i < j ) nu pot conectate direct dac a exist a un v arf v (i < v < j ) astfel nc at segmentul de dreapta care ar uni v afurile i si j nu ar trece pe deasupra v arfului v. In cazul n care cele trei v arfuri sunt coliniare, se consider a toate trei ca ind stat ii, chiar dac a distan c dintre vrfurile i si j este mai mic a dec at L. Cerint a D andu-se amplasarea celor N vrfuri ale lant ului muntos, stabilit i o modalitate de dispunere a celor K stat ii de telecabine astfel nc at lungimea total a a cablurilor folosite pentru conectare s a e minim a, cu restrict iile de mai sus. Se garanteaz a c a, pe toate testele date la evaluare, conectarea va posibil a. Date de intrare Prima linie a sierului de intrare munte.in cont ine trei numere ntregi N , K si L, separate prin spat ii, cu semnicat iile de mai sus. Urm atoarele N linii cont in coordonatele v arfurilor; linia i + 1 cont ine coordonatele v arfului i, X [i] si H [i], separate printr-un spat iu. Date de ie sire In sierul munte.out vet i a sa:

394

CAPITOLUL 16. PROGRAMARE DINAMICA

- pe prima linie lungimea total a minim a a cablurilor, rotunjit a la cel mai apropiat numar ntreg (pentru orice ntreg Q, Q.5 se rotunjeste la Q + 1); - pe a doua linie K numere distincte ntre 1 si N , ordonate cresc ator, numerele v arfurilor n care se vor nin a stat ii de telecabine. Dac a exist a mai multe variante, a sat i una oarecare. Restrict ii si preciz ari 2 N 100 2 K 30 si K N 0 L, X [i], H [i] 100.000 si X [i] < X [i + 1] Exemplu munte.in munte.out 7 5 11 22 0 16 13567 43 68 74 12 16 13 16 14 16 Explicat ii - trasarea unui cablu direct ntre v arfurile 1 si 5 ar contravenit restrict iei referitoare la lungimea maxim a a unui cablu; n plus, s-ar obt inut o solut ie cu 2 stat ii de telecabine n loc de 3 (deci solut ia ar invalid a si pentru valori mari ale lui L); - pentru a ilustra restrict ia introdus a de formele de relief, preciz am c a v arfurile 1 si 4 nu au putut conectate direct datorit a n alt imii v arfului 3. De asemenea, v arfurile 5 si 7 nu au putut conectate direct datorit a n alt imii v arfului 6. Timp maxim de executare: 1 secund a/test. Indicat ii de rezolvare - descriere solut ie * Mihai Stroe, GInfo nr. 13/6 - octombrie 2003 Problema se rezolv a prin metoda program arii dinamice. Practic, problema se poate mp art i n dou a subprobleme. Primul pas const a n determinarea perechilor de v arfuri care pot unite printr-un cablu. Acest pas se rezolv a folosind cuno stint e elementare de geometrie plan a (ecuat ia dreptei, y = a x + b). Se va obt ine o matrice OK , unde OKi,j are valoarea 1 dac a v arfurile i si j pot unite si 0 n caz contrar. Folosind aceast a matrice, se vor conecta v arfurile 1 si N cu un lant de K stat ii, astfel nc at oricare dou a stat ii consecutive s a aib a OK -ul corespunz ator egal cu 1. Aceast a subproblema se rezolv a prin metoda program arii dinamice dup a cum urmeaz a: se construie ste o matrice A cu K linii si N coloane, unde Ai,j reprezint a lungimea total a minim a a unui lant cu i stat ii care conecteaz a v arfurile 1 si j .

16.2. PROBLEME REZOLVATE

395

Init ial A1,1 = 0, A1,i = + si Ai,1 = + pentru i > 1. Pentru i cuprins ntre 2 si N , componentele matricei se calculeaz a astfel: Ai,j = min{Ai1,v + dist(v, j )}, unde v < j si OKv,j = 1 Concomitent cu calculul elementelor Ai,j se construie ste o matrice T , unde Ti,j reprezint a v -ul care minimizeaz a expresia de mai sus. Matricea T este folosit a pentru reconstituirea solut iei. Lungimea total a minim a este regasit a n AK,N . Subproblemele puteau tratate si simultan, ceea ce complica implementarea. Analiza complexit a tii Operat ia de citire a datelor are ordinul de complexitate O(N ). Calculul matricei OK are ordinul de complexitate O(N 3 ). Algoritmul bazat pe metoda program arii dinamice are ordinul de complexitate O(N 2 K ). Reconstituirea solut iei si a sarea au ordinul de complexitate O(N ). Deoarece K N , ordinul de complexitate al algoritmului de rezolvare a acestei probleme este O(N 3 ). Codul surs a Prima variant a: import java.io.*; // cu mesaje pt depanare dar ... fara traseu class Munte1 { static final int oo=Integer.MAX_VALUE; static int n,m,L; // m=nr statii (in loc de K din enunt) static static static static int[] x,h; double[][] a; int[][] t; boolean[][] ok;

public static void main(String[] args) throws IOException { long t1,t2; t1=System.currentTimeMillis(); int i; BufferedReader br=new BufferedReader(new FileReader("munte.in")); StreamTokenizer st=new StreamTokenizer(br); st.nextToken(); n=(int)st.nval;

396

CAPITOLUL 16. PROGRAMARE DINAMICA st.nextToken(); m=(int)st.nval; st.nextToken(); L=(int)st.nval; x=new int[n+1]; h=new int[n+1]; ok=new boolean[n+1][n+1]; a=new double[m+1][n+1]; t=new int[m+1][n+1];

// implicit este false

for(i=1;i<=n;i++) { st.nextToken(); x[i]=(int)st.nval; st.nextToken(); h[i]=(int)st.nval; } matriceaOK(); afism(ok); matriceaA(); afisSolutia(); t2=System.currentTimeMillis(); System.out.println("Timp = "+(t2-t1)); }// main() static void matriceaA() { int i,j,k,kmin; double d,dkj,min; a[1][1]=0; for(i=2;i<=m;i++) a[i][1]=oo; for(j=2;j<=n;j++) a[1][j]=oo; afism(a); for(i=2;i<=m;i++) { for(j=2;j<=n;j++) { min=oo; kmin=-1; for(k=1;k<j;k++) { System.out.println(i+" "+j+" "+k+" "+ok[k][j]);

16.2. PROBLEME REZOLVATE

397

if(ok[k][j]) { dkj=dist(k,j); d=a[i-1][k]+dkj; System.out.println(i+" "+j+" "+k+" dkj="+dkj+" d="+d+" min="+min); if(d<min) { min=d;kmin=k; } } }// for k a[i][j]=min; }// for j System.out.println("Linia: "+i); afism(a); }// for i }// matriceaA() static double dist(int i, int j) { double d; d=(double)(x[i]-x[j])*(x[i]-x[j])+(double)(h[i]-h[j])*(h[i]-h[j]); return Math.sqrt(d); }// dist(...) static void matriceaOK() { int i,j,ij,x1,y1,x2,y2,sp1,sp2; for(i=1;i<=n-1;i++) { x1=x[i]; y1=h[i]; for(j=i+1;j<=n;j++) { x2=x[j]; y2=h[j]; ok[i][j]=ok[j][i]=true; for(ij=i+1;ij<=j-1;ij++) // i .. ij .. j { sp1=(0 -y1)*(x2-x1)-(y2-y1)*(x[ij]-x1); sp2=(h[ij]-y1)*(x2-x1)-(y2-y1)*(x[ij]-x1); if(sp1*sp2<=0) { ok[i][j]=ok[j][i]=false; System.out.println(i+" "+j+" ("+ij+")\t"+sp1+"\t"+sp2); break; } if(!ok[i][j]) break; }//for ij

398

CAPITOLUL 16. PROGRAMARE DINAMICA

if(ok[i][j]) if(dist(i,j)>L+0.0000001) ok[i][j]=ok[j][i]=false; }//for j }// for i }// matriceaOK() static void afisSolutia() throws IOException { int i,j; PrintWriter out = new PrintWriter( new BufferedWriter(new FileWriter("munte.out"))); out.println(a[m][n]); out.close(); }//afisSolutia() static void afism(int[][] a) { int i,j; for(i=0;i<a.length;i++) { for(j=0;j<a[i].length;j++) System.out.print(a[i][j]+" "); System.out.println(); } System.out.println(); }// afism(...) static void afism(boolean[][] a) { int i,j; for(i=1;i<a.length;i++) { for(j=1;j<a[i].length;j++) System.out.print(a[i][j]+" "); System.out.println(); } System.out.println(); }// afism(...) static void afism(double[][] a) { int i,j; for(i=1;i<a.length;i++) { for(j=1;j<a[i].length;j++) System.out.print(a[i][j]+" "); System.out.println(); }

16.2. PROBLEME REZOLVATE System.out.println(); }// afism(...) }// class A doua variant a:

399

import java.io.*; // FINAL: fara mesaje si cu traseu ... dar class Munte2 // test 8 : P1(99,59) P2(171,96) P3(239,81) P4(300,78) { // solutia: 1 4 5 ... este gresita (1 4 nu trece de P2 si P3!) static final int oo=Integer.MAX_VALUE; static int n,m,L; // m=nr statii (in loc de K din enunt) static static static static static int[] x,h; double[][] a; int[][] t; boolean[][] ok; int[] statia;

public static void main(String[] args) throws IOException { long t1,t2; t1=System.currentTimeMillis(); int i,j; BufferedReader br=new BufferedReader(new FileReader("munte.in")); StreamTokenizer st=new StreamTokenizer(br); st.nextToken(); n=(int)st.nval; st.nextToken(); m=(int)st.nval; st.nextToken(); L=(int)st.nval; x=new int[n+1]; h=new int[n+1]; ok=new boolean[n+1][n+1]; a=new double[m+1][n+1]; t=new int[m+1][n+1]; statia=new int[m+1]; for(i=1;i<=n;i++) { st.nextToken(); x[i]=(int)st.nval; st.nextToken(); h[i]=(int)st.nval; } matriceaOK();

400

CAPITOLUL 16. PROGRAMARE DINAMICA matriceaA(); j=n; for(i=m;i>=1;i--) { statia[i]=j; j=t[i][j]; } afisSolutia();

t2=System.currentTimeMillis(); System.out.println("Timp = "+(t2-t1)); }// main() static void matriceaA() { int i,j,k,kmin; double d,dkj,min; a[1][1]=0; for(i=2;i<=m;i++) a[i][1]=oo; for(j=2;j<=n;j++) a[1][j]=oo; for(i=2;i<=m;i++) { for(j=2;j<=n;j++) { min=oo; kmin=0; for(k=1;k<j;k++) { if(ok[k][j]) { dkj=dist(k,j); d=a[i-1][k]+dkj; if(d<min) { min=d;kmin=k; } } }// for k a[i][j]=min; t[i][j]=kmin; }// for j }// for i }// matriceaA() static double dist(int i, int j) { double d; d=(double)(x[i]-x[j])*(x[i]-x[j])+(double)(h[i]-h[j])*(h[i]-h[j]); return Math.sqrt(d); }// dist(...)

16.2. PROBLEME REZOLVATE

401

static void matriceaOK() { int i,j,ij,x1,y1,x2,y2; long sp1,sp2; // 100.000*100.000=10.000.000.000 depaseste int !!! for(i=1;i<=n-1;i++) { x1=x[i]; y1=h[i]; for(j=i+1;j<=n;j++) { x2=x[j]; y2=h[j]; ok[i][j]=ok[j][i]=true; for(ij=i+1;ij<=j-1;ij++) // i .. ij .. j { sp1=(0 -y1)*(x2-x1)-(y2-y1)*(x[ij]-x1); sp2=(h[ij]-y1)*(x2-x1)-(y2-y1)*(x[ij]-x1); if(sp1*sp2<=0) { ok[i][j]=ok[j][i]=false; break; } if(!ok[i][j]) break; }//for ij if(ok[i][j]) if(dist(i,j)>L) ok[i][j]=ok[j][i]=false; }//for j }// for i }// matriceaOK() static void afisSolutia() throws IOException { int i; PrintWriter out = new PrintWriter( new BufferedWriter(new FileWriter("munte.out"))); out.println((int)(a[m][n]+0.5)); for(i=1;i<=m;i++) out.print(statia[i]+" "); out.println(); out.close(); }//afisSolutia() }// class

16.2.17

L acusta - OJI2005 clasa a X-a

Se consider a o matrice dreptunghiular a cu m linii si n coloane, cu valori naturale. Travers am matricea pornind de la colt ul st anga-sus la colt ul dreapta-jos.

402

CAPITOLUL 16. PROGRAMARE DINAMICA

O traversare const a din mai multe deplas ari. La ecare deplasare se execut a un salt pe orizontal a si un pas pe vertical a. Un salt nseamn a c a putem trece de la o celul a la oricare alta aat a pe aceea si linie, iar un pas nseamn a c a putem trece de la o celul a la celula aat a imediat sub ea. Except ie face ultima deplasare (cea n care ne a am pe ultima linie), c and vom face doar un salt pentru a ajunge n colt ul dreapta-jos, dar nu vom mai face si pasul corespunz ator. Astfel traversarea va consta din vizitarea a 2m celule. Cerint a Scriet i un program care s a determine suma minim a care se poate obt ine pentru o astfel de traversare. Datele de intrare Fi sierul de intrare lacusta.in cont ine pe prima linie dou a numere naturale separate printr-un spat iu m n, reprezent and num arul de linii si respectiv num arul de coloane ale matricei. Pe urm atoarele m linii este descris a matricea, c ate n numere pe ecare linie, separate prin c ate un spat iu. Datele de ie sire Fi sierul de ie sire lacusta.out va cont ine o singur a linie pe care va scris a suma minim a g asit a. Restrict ii si preciz ari 1 m, n 100 Valorile elementelor matricei sunt numere ntregi din intervalul [1, 255]. Exemple lacusta.in 45 34579 66344 63396 65382 lacusta.out 28 Explicatie Drumul este: (1, 1) (1, 3) (2, 3) (2, 2) (3, 2) (3, 3) (4, 3) (4, 5)

Timp maxim de executare: 1 secund a/test Indicat ii de rezolvare * Ginfo nr. 15/3 martie 2005 Pentru rezolvarea acestei probleme vom utiliza metoda program arii dinamice. Vom nota prin A matricea dat a si vom construi o matrice B ale c arei elemente bij vor cont ine sumele minime necesare pentru a ajunge n celula (i, j ) pornind din celula (i 1, j ). Vom completa init ial elementele de pe a doua linie a matricei B. Valoarea b2,1 va deoarece n aceast a celul a nu se poate ajunge. Valorile celorlalte elemente b2i vor calculate pe baza formulei: b2,i = a1,1 + a1,i + a2,i .

16.2. PROBLEME REZOLVATE Pentru celelalte linii, valorile bij vor calculate pe baza formulei: bi,j = ai,j + ai1,j + min(bi1,k ),

403

unde k variaz a ntre 1 si n. Evident, relat ia nu este valabil a pentru elementul de pe coloana k care corespunde minimului, deoarece nu se poate cobor direct, ci trebuie efectuat un salt orizontal. In aceast a situat ie vom alege al doilea minim de pe linia anterioar a. In nal alegem minimul valorilor de pe ultima linie a matricei B (f ar a a lua n considerare elementul de pe ultima coloan a a acestei linii) la care adaug am valoarea amn . Coduri surs a* Prima variant a: import java.io.*; class Lacusta1 { static final int oo=100000; static int m,n; static int[][] a,b; // 0 <= i <= m-1;

0 <= j <= n-1

public static void main(String[] args) throws IOException { long t1,t2; t1=System.currentTimeMillis(); int i,j; StreamTokenizer st=new StreamTokenizer( new BufferedReader(new FileReader("lacusta.in"))); PrintWriter out=new PrintWriter( new BufferedWriter(new FileWriter("lacusta.out"))); st.nextToken(); m=(int)st.nval; st.nextToken(); n=(int)st.nval; a=new int[m][n]; b=new int[m][n]; for(i=0;i<m;i++) for(j=0;j<n;j++) {

404

CAPITOLUL 16. PROGRAMARE DINAMICA st.nextToken(); a[i][j]=(int)st.nval; } for(i=0;i<m;i++) for(j=0;j<n;j++) b[i][j]=oo; // prima linie (i=0) din b este oo // a doua linie (i=1) din b for(j=1;j<n;j++) b[1][j]=a[0][0]+a[0][j]+a[1][j]; // urmatoarele linii din b for(i=2;i<m;i++) for(j=0;j<n;j++) b[i][j]=a[i][j]+a[i-1][j]+minLinia(i-1,j); // "obligatoriu" (!) si ultima linie (i=n-1) dar ... fara coborare b[m-1][n-1]=minLinia(m-1,n-1)+a[m-1][n-1];

out.println(b[m-1][n-1]); out.close(); t2=System.currentTimeMillis(); System.out.println("Timp = "+(t2-t1)); }// main() static int minLinia(int ii, int jj) // min pe linia=ii fara pozitia jj==col { int j,min=oo; for(j=0;j<n;j++) if(j!=jj) if(b[ii][j]<min) min=b[ii][j]; return min; }// minLinia(...) }// class A doua variant a: import java.io.*; // suplimentar ... si traseul ! class Lacusta2 { static final int oo=100000; static int m,n; static int[][] a,b; // 1 <= i <= m; 1 <= j <= n public static void main(String[] args) throws IOException { long t1,t2;

16.2. PROBLEME REZOLVATE t1=System.currentTimeMillis(); int i,j,min,jmin,j0; StreamTokenizer st=new StreamTokenizer( new BufferedReader(new FileReader("lacusta.in"))); PrintWriter out=new PrintWriter( new BufferedWriter(new FileWriter("lacusta.out"))); st.nextToken(); m=(int)st.nval; st.nextToken(); n=(int)st.nval; a=new int[m+1][n+1]; b=new int[m+1][n+1]; for(i=1;i<=m;i++) for(j=1;j<=n;j++) { st.nextToken(); a[i][j]=(int)st.nval; } for(i=1;i<=m;i++) for(j=1;j<=n;j++) b[i][j]=oo; // prima linie (i=1) din b este oo // a doua linie (i=2) din b for(j=2;j<=n;j++) b[2][j]=a[1][1]+a[1][j]+a[2][j]; // urmatoarele linii din b for(i=3;i<=m;i++) for(j=1;j<=n;j++) b[i][j]=a[i][j]+a[i-1][j]+minLinia(i-1,j);

405

// "obligatoriu" (!) si ultima linie (i=n) dar ... fara coborare b[m][n]=minLinia(m,n)+a[m][n]; out.println(b[m][n]); out.close(); jmin=-1; // initializare aiurea ! j0=1; // pentru linia 2 System.out.print(1+" "+1+" --> "); for(i=2;i<=m-1;i++) // liniile 2 .. m-1 { min=oo; for(j=1;j<=n;j++) if(j!=j0) if(b[i][j]<min) { min=b[i][j]; jmin=j;} System.out.print((i-1)+" "+jmin+" --> ");

406

CAPITOLUL 16. PROGRAMARE DINAMICA System.out.print(i+" "+jmin+" --> "); j0=jmin; } j0=n; min=oo; for(j=1;j<n;j++) if(j!=j0) if(b[i][j]<min) { min=b[i][j]; jmin=j;} System.out.print((i-1)+" "+jmin+" --> "); System.out.print(i+" "+jmin+" --> "); System.out.println(m+" "+n);

t2=System.currentTimeMillis(); System.out.println("Timp = "+(t2-t1)); }// main() static int minLinia(int ii, int jj) // min pe linia=ii fara pozitia jj==col { int j,min=oo; for(j=1;j<=n;j++) if(j!=jj) if(b[ii][j]<min) min=b[ii][j]; return min; }// minLinia(...) }// class Varianta 3: import java.io.*; // fara matricea de costuri (economie de "spatiu") class Lacusta3 // traseul este ... pentru depanare { // daca se cere ... se poate inregistra si apoi ... static final int oo=100000; static int m,n; static int[][] a; // 1 <= i <= m; 1 <= j <= n public static void main(String[] args) throws IOException { long t1,t2; t1=System.currentTimeMillis(); int i,j; int minc,minsol,jmin,j0;

16.2. PROBLEME REZOLVATE StreamTokenizer st=new StreamTokenizer( new BufferedReader(new FileReader("lacusta.in"))); PrintWriter out=new PrintWriter( new BufferedWriter(new FileWriter("lacusta.out"))); st.nextToken(); m=(int)st.nval; st.nextToken(); n=(int)st.nval; a=new int[m+1][n+1]; for(i=1;i<=m;i++) for(j=1;j<=n;j++) { st.nextToken(); a[i][j]=(int)st.nval; } minsol=oo; System.out.print(1+" "+1+" --> ");

407

// a doua linie (i=2) minc=oo; jmin=-1; for(j=2;j<=n;j++) if(a[1][1]+a[1][j]+a[2][j]<minc) {minc=a[1][1]+a[1][j]+a[2][j]; jmin=j;} System.out.print(1+" "+jmin+" --> "); System.out.print(2+" "+jmin+" --> "); minsol=minc; j0=jmin; jmin=-1; // initializare aiurea ! for(i=3;i<=m-1;i++) { minc=oo; for(j=1;j<=n;j++) if(j!=j0) if(a[i-1][j]+a[i][j]<minc) {minc=a[i-1][j]+a[i][j]; jmin=j;} System.out.print((i-1)+" "+jmin+" --> "); System.out.print(i+" "+jmin+" --> "); minsol+=minc; j0=jmin; } j0=n; minc=oo; for(j=1;j<=n;j++)

408

CAPITOLUL 16. PROGRAMARE DINAMICA if(j!=j0) if(a[m-1][j]+a[m][j]<minc) {minc=a[m-1][j]+a[m][j]; jmin=j;} System.out.print((m-1)+" "+jmin+" --> "); System.out.print(m+" "+jmin+" --> "); minsol+=minc+a[m][n]; System.out.println(m+" "+n); out.println(minsol); out.close();

t2=System.currentTimeMillis(); System.out.println("Timp = "+(t2-t1)); }// main() }// class Varianta 4: import java.io.*; // fara matricea de costuri (economie de "spatiu") class Lacusta4 // si ... fara matricea initiala (numai doua linii din ea !) { // calculez pe masura ce citesc cate o linie ! static final int oo=100000; static int m,n; static int[][] a; // 1 <= i <= m; 1 <= j <= n public static void main(String[] args) throws IOException { long t1,t2; t1=System.currentTimeMillis(); int i,j,ii,jj; int minc,minsol,jmin,j0; StreamTokenizer st=new StreamTokenizer( new BufferedReader(new FileReader("lacusta.in"))); PrintWriter out=new PrintWriter( new BufferedWriter(new FileWriter("lacusta.out"))); st.nextToken(); m=(int)st.nval; st.nextToken(); n=(int)st.nval; a=new int[2][n+1]; for(i=1;i<=2;i++) // citesc numai primele doua linii for(j=1;j<=n;j++) {

16.2. PROBLEME REZOLVATE st.nextToken(); a[i%2][j]=(int)st.nval; } minsol=oo; System.out.print(1+" "+1+" --> "); // a doua linie (i=2) minc=oo; jmin=-1; for(j=2;j<=n;j++) if(a[1%2][1]+a[1][j]+a[2%2][j]<minc) {minc=a[1%2][1]+a[1%2][j]+a[2%2][j]; jmin=j;} System.out.print(1+" "+jmin+" --> "); System.out.print(2+" "+jmin+" --> "); minsol=minc; j0=jmin;

409

jmin=-1; // initializare aiurea ! for(i=3;i<=m-1;i++) // citesc mai departe cate o linie { for(j=1;j<=n;j++) { st.nextToken(); a[i%2][j]=(int)st.nval; } minc=oo; for(j=1;j<=n;j++) if(j!=j0) if(a[(i-1+2)%2][j]+a[i%2][j]<minc) {minc=a[(i-1+2)%2][j]+a[i%2][j]; jmin=j;} System.out.print((i-1)+" "+jmin+" --> "); System.out.print(i+" "+jmin+" --> "); minsol+=minc; j0=jmin; } //citesc linia m for(j=1;j<=n;j++) { st.nextToken(); a[m%2][j]=(int)st.nval; } j0=n; minc=oo; for(j=1;j<=n;j++) if(j!=j0) if(a[(m-1+2)%2][j]+a[m%2][j]<minc) {minc=a[(i-1+2)%2][j]+a[i%2][j]; jmin=j;} System.out.print((i-1)+" "+jmin+" --> "); System.out.print(i+" "+jmin+" --> "); minsol+=minc+a[m%2][n];

410

CAPITOLUL 16. PROGRAMARE DINAMICA System.out.println(m+" "+n); out.println(minsol); out.close();

t2=System.currentTimeMillis(); System.out.println("Timp = "+(t2-t1)); }// main() }// class Varianta 5: import java.io.*; // numai o linie din matricea initiala ! class Lacusta5 { static final int oo=100000; static int m,n; static int[] a; // 1 <= j <= n public static void main(String[] args) throws IOException { long t1,t2; t1=System.currentTimeMillis(); int i,j,ii,jj,a11,aa; int minc,minsol,jmin,j0; StreamTokenizer st=new StreamTokenizer( new BufferedReader(new FileReader("lacusta.in"))); PrintWriter out=new PrintWriter( new BufferedWriter(new FileWriter("lacusta.out"))); st.nextToken(); m=(int)st.nval; st.nextToken(); n=(int)st.nval; a=new int[n+1]; // citesc numai prima linie for(j=1;j<=n;j++) { st.nextToken(); a[j]=(int)st.nval; } System.out.print(1+" "+1+" --> "); a11=a[1]; // citesc a doua linie st.nextToken(); a[1]=(int)st.nval; minc=oo; jmin=-1;

16.2. PROBLEME REZOLVATE for(j=2;j<=n;j++) { aa=a[j]; st.nextToken(); a[j]=(int)st.nval; if(aa+a[j]<minc) {minc=aa+a[j]; jmin=j;} } System.out.print(1+" "+jmin+" --> "+2+" "+jmin+" --> "); minsol=a11+minc; j0=jmin;

411

// citesc mai departe cate o linie si ... for(i=3;i<=m-1;i++) { minc=oo; jmin=-1; for(j=1;j<=n;j++) { aa=a[j]; st.nextToken(); a[j]=(int)st.nval; if(j!=j0) if(aa+a[j]<minc) {minc=aa+a[j]; jmin=j;} } System.out.print((i-1)+" "+jmin+" --> "+i+" "+jmin+" --> "); minsol+=minc; j0=jmin; } //citesc linia m (primele n-1 componente) minc=oo; jmin=-1; for(j=1;j<=n-1;j++) { aa=a[j]; st.nextToken(); a[j]=(int)st.nval; if(aa+a[j]<minc) {minc=aa+a[j]; jmin=j;} } System.out.print((m-1)+" "+jmin+" --> "+m+" "+jmin+" --> "); minsol+=minc; j0=jmin; // citesc ultimul element st.nextToken(); a[n]=(int)st.nval; minsol+=a[n]; System.out.println(m+" "+n);

412 out.println(minsol); out.close();

CAPITOLUL 16. PROGRAMARE DINAMICA

t2=System.currentTimeMillis(); System.out.println("Timp = "+(t2-t1)); }// main() }// class

16.2.18

Avere ONI2005 cls 10

Italag a fost toat a viat a pasionat de speculat ii bursiere reu sind s a adune o avere considerabil a. Fiind un tip original si pasionat de matematic a a scris un testament inedit. Testamentul cont ine dou a numere naturale: S reprezent and averea ce trebuie mp art it a mo stenitorilor si N reprezent and alegerea sa pentru mp art irea averii. Italag decide s a- si mpart a toat a averea, iar sumele pe care le acord a mo stenitorilor s a e n ordine strict descresc atoare. De exemplu dac a averea ar 7 unit a ti monetare, ar putea mp art it a astfel: 4 (unit a ti primului mo stenitor) 3 (unit a ti celui de-al doilea), sau 6 (unit a ti primului mo stenitor) 1 (unitate celui de-al doilea), sau 7 (unit a ti doar primului mo stenitor), sau 5 (unit a ti primului mo stenitor) 2 (unit a ti celui de-al doilea), sau 4 (unit a ti primului mo stenitor) 2 (unit a ti celui de-al doilea) 1 (unitate celui de-al treilea). V az and c a i este foarte greu s a verice dac a nu cumva a omis vreo variant a de mp art ire, Italag le-a scris n ordine lexicograc a. Pentru exemplul de mai sus: 4 2 1; 4 3; 5 2; 6 1; 7. A hot ar at ca banii s a e distribuit i conform celei de-a N -a posibilit a ti din ordinea lexicograc a. Cerint a Scriet i un program care pentru numerele S , N date s a calculeze si s a a seze num arul total de posibilit a ti de mp art ire a averii, precum si modul n care se face aceast a mp art ire conform cu a N -a posibilitate din ordinea lexicograc a. Datele de intrare Fi sierul de intrare avere.in cont ine o singur a linie pe care se a a dou a numere naturale separate printr-un singur spat iu: primul num ar (S ) reprezint a suma total a cel de-al doilea (N ) reprezint a num arul de ordine al pozit iei c autate. Datele de ie sire Fi sierul de ie sire avere.out va cont ine dou a linii: pe prima linie va a sat num arul total de modalit a ti de mp art ire a averii;

16.2. PROBLEME REZOLVATE

413

pe cea de-a doua linie va a sat a a N -a posibilitate de mp art ire a lui S conform cerint ei n ordine lexicograc a. Elementele sale vor separate prin c ate un spat iu. Restrict ii si preciz ari 1 < S < 701 0 < N < num arul total de posibilit a ti cu suma S Se acord a punctaj part ial pentru ecare test: 5 puncte pentru determinarea corect a a num arului de posibilit a ti de mp art ire a lui S si 5 puncte pentru determinarea corect a a posibilit a tii N , din ordinea lexicograc a. Posibilit a tile de mp art ire a averii sunt numerotate ncep and cu 1. Fie x = (x1 , x2 ..., xm ) si y = (y1 , y2 , ..., yp ) dou a siruri. Spunem c a x preced a pe y din punct de vedere lexicograc, dac a exist a k 1, astfel nc at xi = yi , pentru orice i = 1, ..., k 1 si xk < yk . Exemple: 4 2 1 preced a secvent a 4 3 deoarece (4 = 4, 2 < 3), iar 6 1 preced a 7 deoarece 6 < 7. Exemple: avere.in 72 avere.in 12 5 avere.out 5 43 avere.out 15 651 avere.out
962056220379782044 175 68 63 58 54 45 40 36 34 32 20 18 17 14 11 9 3 2 1

avere.in
700 912345678912345678

Timp maxim de execut ie/test: 1 secund a pentru Windows si 0.1 secunde pentru Linux. Limita total a de memorie sub Linux este 3Mb din care 1Mb pentru stiv a. Indicat ii de rezolvare - descriere solut ie * stud. Emilian Miron, Universitatea Bucure sti Problema se rezolv a prin programare dinamic a dup a valoarea maxim a pe care poate s a o ia primul num ar n cadrul descompunerii si dup a suma total a. Obt inem recurent a:

414 c[v ][s] =

CAPITOLUL 16. PROGRAMARE DINAMICA

c[v 1][s] //pun and pe prima pozit ie 1, 2, ...,v-1 +c[v 1][s v ] //punnd pe prima pozit ie v c[0][0] = 1, c[0][s] = 0 pentru s > 0 Num arul total de posibilit a ti va egal cu c[S ][S ]. Reconstituirea solut iei se face stabilind primul num ar ca ind cel mai mic i astfel nc at c[i][S ] N si c[i 1][S ] < N . Procesul continu a pentru S = S i si N = N c[i 1][S ] p an a c and N = 0. Observ am c a recurent a depinde doar de linia anterioar a, a sa c a ea se poate calcula folosind un singur vector. Aceasta ne ajut a pentru a respecta limita de memorie. Astfel calcul am toate valorile folosind un singur vector si p astr am la ecare pas c[i][S ]. Reconstruct ia se face recalcul and valorile la ecare pas pentru S -ul curent. Solut ia descris a efectueaz a O(S 2 L) operat ii, unde L este lungimea solut iei. Observ and L = maximO(S 1/2 ) timpul total este O(S 3/2 ). O solut ie backtracking obt ine 20 puncte, iar una cu memorie O(N 2 ) 50 puncte. Codul surs a

import java.io.*; class Avere1 { static int S; static long n; static long c[][];

// // // // // //

c[0][0]=1; c[0][s]=0; pentru s=1,2,...,S c[v][s]=c[v-1][s]; pentru v>=1 si s<v c[v][s]=c[v-1][s]+c[v-1][s-v]; pentru v>=1 si s>=v test 4 ??? test 5 = gresit N (>...) in fisier intrare toate celelalte: OK rezultat si timp !!! dar fara economie de memorie !!!!!!!

public static void main(String[] args) throws IOException { long t1,t2; t1=System.nanoTime(); int s,v; StreamTokenizer st=new StreamTokenizer( new BufferedReader(new FileReader("10-avere.in"))); PrintWriter out=new PrintWriter( new BufferedWriter(new FileWriter("avere.out"))); st.nextToken(); S=(int)st.nval; st.nextToken(); n=(int)st.nval; c=new long[S+1][S+1]; for(v=0;v<=S;v++) c[v][0]=(long)1;

16.2. PROBLEME REZOLVATE

415

for(v=1;v<=S;v++) for(s=0;s<=S;s++) if(s<v) c[v][s]=c[v-1][s]; else c[v][s]=c[v-1][s]+c[v-1][s-v]; //afism(); out.println(c[S][S]); while((n>0)&&(S>0)) { v=0; while(c[v][S]<n) v++; out.print(v+" "); n=n-c[v-1][S]; S=S-v; } out.close(); t2=System.nanoTime(); System.out.println("Timp = "+((double)(t2-t1))/1000000000); }// main(...) static void afism() { int i,j; for(i=0;i<=S;i++) { for(j=0;j<=S;j++) System.out.print(c[i][j]+" "); System.out.println(); } }// afism() }// class /* 1 0 0 0 0 0 0 0 0 0 0 0 0 1 1 0 0 0 0 0 0 0 0 0 0 0 1 1 1 1 0 0 0 0 0 0 0 0 0 1 1 1 2 1 1 1 0 0 0 0 0 0 1 1 1 2 2 2 2 2 1 1 1 0 0 1 1 1 2 2 3 3 3 3 3 3 2 2 1 1 1 2 2 3 4 4 4 5 5 5 5 1 1 1 2 2 3 4 5 5 6 7 7 8 1 1 1 2 2 3 4 5 6 7 8 9 10 1 1 1 2 2 3 4 5 6 8 9 10 12 1 1 1 2 2 3 4 5 6 8 10 11 13 1 1 1 2 2 3 4 5 6 8 10 12 14 1 1 1 2 2 3 4 5 6 8 10 12 15 Press any key to continue...

416 */

CAPITOLUL 16. PROGRAMARE DINAMICA

16.2.19

Suma - ONI2005 cls 10

Tradit ia este ca, la ie sirea la pensie, pentru ecare zi de activitate n slujba sultanului, marele vizir s a primeasc a o prim a stabilit a de marele sfat al t arii. Astfel, vizirul Magir a primit pentru doar 5 zile de activitate prima total a de 411 galbeni, deoarece sfatul t arii a hot ar at pentru ziua nt ai o sum a de 53 de galbeni, pentru ziua a doua 200 de galbeni, pentru ziua a treia 12 galbeni, pentru ziua a patra 144 de galbeni, iar pentru ziua a cincea doar 2 galbeni. Vizirul Jibal, celebru pentru contribut ia adus a la rezolvarea conictului din zon a, prime ste dreptul ca, la ie sirea la pensie, s a modice sumele stabilite de sfatul t arii, dar nu foarte mult. El poate uni cifrele sumelor stabilite si le poate desp art i apoi, dup a dorint a, astfel nc at, suma primit a pe ecare zi s a nu dep a seasc a 999 de galbeni si s a primeasc a cel put in un galben pentru ecare dintre zilele de activitate. Astfel, dac a are doar 5 zile de activitate, pl atite cu 23, 417, 205, 5 si respectiv 40 de galbeni, n total 680 de galbeni, el poate opta pentru o nou a distribut ie a cifrelor numerelor stabilite de marele sfat astfel: pentru prima zi cere 2 galbeni, pentru a doua 3, pentru a treia 417, pentru a patra 205 si pentru a cincea 540 de galbeni, primind astfel 1167 de galbeni n total. Cerint a Pentru num arul de zile n si cele n sume stabilite de sfatul t arii pentru Jibal, scriet i un program care s a determine cea mai mare prim a total a care se poate obt ine prin unirea si desp art irea cifrelor sumelor date. Datele de intrare Fi sierul de intrare suma.in cont ine: pe prima linie un num ar natural n reprezent and num arul de zile de activ-

itate

pe linia urm atoare, n numere naturale separate prin spat ii s1 , s2 , ..., sn reprezent and sumele atribuite de sfatul t arii. Datele de ie sire Fi sierul de ie sire suma.out va cont ine o singur a linie pe care va a sat un singur num ar natural reprezent and prima total a maxim a care se poate obt ine. Restrict ii si preciz ari 1 < n < 501 0 < si < 1000, pentru orice 1 i n In orice distribut ie, ecare sum a trebuie s a e o valoare proprie (s a nu nceap a cu 0). Orice sum a dintr-o distribut ie trebuie s a e nenul a. Pentru 20% din teste, n 10, pentru 50% din teste n 50. Exemple

16.2. PROBLEME REZOLVATE suma.in 3 58 300 4 suma.out 362 Explicat ie Prima maxim a (362) se obt ine chiar pentru distribut ia 58 300 4 Explicat ie Prima maxim a (1608) se obt ine pentru distribut ia 2 341 720 5 540

417

suma.in 5 23 417 205 5 40

suma.out 1608

Timp maxim de execut ie/test: 1 secund a pentru Windows si 0.1 secunde pentru Linux. Indicat ii de rezolvare - descriere solut ie Solut ia ocial a Solut ia I Solut ia propus a utilizeaz a metoda program arii dinamice. Este implementat un algoritm de expandare tip Lee realizat cu o coad a alocat a dinamic. Astfel, ecare cifr a contribuie la expandarea solut iilor precedente care au sans a de dezvoltare ulterioar a. Vectorul best memoreaz a la ecare moment suma cea mai mare format a dintr-un num ar dat de termeni. Condit ia nc-nr<=(n-p^.t-1)*3+2 (unde nc este num arul total de cifre care se distribuie, nr este num arul de ordine al cifrei curente, n este num arul total de termeni si p^.t este num arul de termeni ai solut iei curente) testeaz a ca, prin crearea unui nou termen cu ajutorul cifrei curente, s a mai existe sansa construirii cu cifrele r amase a unei solut i cu n termeni. Condit ia nc-nr>=n-p^.t testeaz a ca, prin lipirea cifrei curente la ultimul termen al solut iei curente, s a mai existe sansa construirii cu cifrele r amase a unei solut ii cu n termeni. type pnod=^nod; nod=record s:longint; {suma} t,last:word; {nr. de termeni si ultimul termen} next:pnod end; var n,nc,i,k:longint; f:text; best:array[1..1000]of longint; p,u:pnod; c:char; procedure citire; {determina numarul total de cifre} var i,x:longint; begin

418

CAPITOLUL 16. PROGRAMARE DINAMICA

assign(f,suma.in);reset(f); readln(f,n); for i:=1 to n do begin read(f,x); repeat inc(nc);x:=x div 10 until x=0 end; close(f) end; {expandarea corespunzatoare cifrei curente} procedure calc(nr:longint;cif:byte); var c,q:pnod; gata:boolean; begin c:=u;gata:=false; repeat if (cif>0) and (nc-nr<=(n-p^.t-1)*3+2) and (best[p^.t]=p^.s) then begin new(u^.next);u:=u^.next; u^.s:=p^.s+cif; u^.t:=p^.t+1; u^.last:=cif end; if (p^.last<100)and(nc-nr>=n-p^.t) then begin new(u^.next);u:=u^.next; u^.s:=p^.s+p^.last*9+cif; u^.t:=p^.t; u^.last:=p^.last*10+cif; end; if p=c then gata:=true; q:=p;p:=p^.next;dispose(q) until gata; end; {recalcularea valorilor maxime memorate in vectorul best} procedure optim; var i:longint; q:pnod; gata:boolean; begin for i:=1 to n do best[i]:=0; q:=p;gata:=false; repeat if q^.s>best[q^.t] then best[q^.t]:=q^.s; if q=u then gata:=true;

16.2. PROBLEME REZOLVATE q:=q^.next until gata; end; BEGIN citire; {reluarea citirii cifrelor, ignorand spatiile} reset(f); readln(f); repeat read(f,c) until c<> ; new(p); p^.s:=ord(c)-48;p^.t:=1; p^.last:=p^.s; best[1]:=p^.s; u:=p; for i:=2 to nc do begin repeat read(f,c) until c<> ; calc(i,ord(c)-48); optim end; close(f); assign(f,suma.out);rewrite(f);writeln(f,best[n]);close(f) END.

419

Solut ia II Problema se rezolv a prin metoda programare dinamic a. Concaten am numerele si obt inem un sir (s a l not am a) de cifre (de lungime L maxim N 3). Pentru ecare pozit ie p (p = 1..L) calcul am prima maxim a pe care o putem obt ine desp art ind sub sirul de p an a la p inclusiv n n sume (n = 1..N ). Obt inem relat ia: smax[p][n] = min( smax[p-1][n-1]+a[p], // pun and ultima sum a format a doar din cifra a[i] smax[p-2][n-1]+a[p-1]*10+a[p], // pun and ultima sum a din 2 cifre smax[p-3][n-1]+a[p-2]*100+a[p-1]*10+a[p] // pun and ultima sum a din 3 cifre ) Trebuie avute n vedere cazurile limit a c and p = 1, p = 2 sau p = 3 si cazurile n care a[p], a[p 1] sau a[p 2] sunt zero, moment n care nu putem forma o sum a de lungimea respectiv a, a sa c a excludem termenii din expresia de minim. Pentru u surint a n implementare stoc am smax[p][n] = inf init pentru cazul n care sub sirul de p an a la p nu poate mp art it n mod corect n n sume, iar observ and c a recurent a depinde doar de ultimele 3 linii, nu p astr am dec at pe acestea si linia curent a pentru a nu avea probleme cu memoria. Obt inem memorie O(N ) si timp de execut ie O(L N ) = O(N 2 );

420 Codul surs a Varianta 1:

CAPITOLUL 16. PROGRAMARE DINAMICA

import java.io.*; // fara economie de spatiu class Suma1 { static int n,nc; // nc=nr cifre din sir static int[] c=new int[1501]; // sirul cifrelor static int[][] s; public static void main (String[] args) throws IOException { int i,j,x; StreamTokenizer st=new StreamTokenizer( new BufferedReader(new FileReader("suma.in"))); PrintWriter out=new PrintWriter(new BufferedWriter( new FileWriter("suma.out"))); st.nextToken(); n=(int)st.nval; nc=0; j=0; for(i=1;i<=n;i++) { st.nextToken(); x=(int)st.nval; if(x<10) {c[++j]=x; nc+=1;} else if(x<100) {c[++j]=x/10; c[++j]=x%10; nc+=2;} else if(x<1000) {c[++j]=x/100; c[++j]=(x/10)%10; c[++j]=x%10; nc+=3;} else System.out.println("Eroare date !"); } s=new int[nc+1][n+1]; calcul(); afism(); out.print(s[nc][n]); out.close(); }// main(...) static void calcul() { // s[i][j]=max(s[i-1][j-1]+c[i], // xj are 1: cifra c[i] // s[i-2][j-1]+c[i-1]*10+c[i], // xj are 2 cifre: c[i-1]c[i] // s[i-3][j-1]+c[i-2]*100+c[i-1]*10+c[i]); // xj are 3 cifre

16.2. PROBLEME REZOLVATE int i,j,smax; s[1][1]=c[1]; s[2][1]=c[1]*10+c[2]; s[3][1]=c[1]*100+c[2]*10+c[3]; if(c[2]!=0) s[2][2]=c[1]+c[2]; if((c[2]!=0)&&(c[3]!=0)) s[3][3]=c[1]+c[2]+c[3]; if(c[3]==0) { if(c[2]!=0) s[3][2]=c[1]+c[2]; } else // c[3]!=0 { if(c[2]==0) s[3][2]=c[1]*10+c[3]; else // c[2]!=0 && c[3]!=0 s[3][2]=max(c[1]+c[2]*10+c[3],c[1]*10+c[2]+c[3]); } for(i=4;i<=nc;i++) for(j=1;j<=n;j++) { smax=0;

421

// i = pozitie cifra in sirul cifrelor // j = pozitie numar in sirul final al numerelor

if(j<=i) { if((c[i]!=0)&&(s[i-1][j-1]!=0)) smax=max(smax,s[i-1][j-1]+c[i]); if((c[i-1]!=0)&&(s[i-2][j-1]!=0)) smax=max(smax,s[i-2][j-1]+c[i-1]*10+c[i]); if((c[i-2]!=0)&&(s[i-3][j-1]!=0)) smax=max(smax,s[i-3][j-1]+c[i-2]*100+c[i-1]*10+c[i]); } s[i][j]=smax; }// for }// calcul() static int max(int a, int b) { if(a>b) return a; else return b; }

422 static void afism() { int i,j;

CAPITOLUL 16. PROGRAMARE DINAMICA

System.out.print(" \t"); for(j=1;j<=n;j++) System.out.print(j+"\t"); System.out.println(); for(i=1;i<=nc;i++) { System.out.print(i+" "+c[i]+" :\t"); for(j=1;j<=n;j++) System.out.print(s[i][j]+"\t"); System.out.println(); } }// afism() }// class Varianta 2: import java.io.*; // cu economie de spatiu !!! class Suma2 { static int n,nc; // nc=nr cifre din sir static int[] c=new int[1501]; // sirul cifrelor static int[][] s; public static void main (String[] args) throws IOException { long t1,t2; t1=System.currentTimeMillis(); int i,j,x; StreamTokenizer st=new StreamTokenizer( new BufferedReader(new FileReader("suma.in"))); PrintWriter out=new PrintWriter(new BufferedWriter( new FileWriter("suma.out"))); st.nextToken(); n=(int)st.nval; nc=0; j=0; for(i=1;i<=n;i++) { st.nextToken(); x=(int)st.nval; if(x<10) {c[++j]=x; nc+=1;} else

16.2. PROBLEME REZOLVATE

423

if(x<100) {c[++j]=x/10; c[++j]=x%10; nc+=2;} else if(x<1000) {c[++j]=x/100; c[++j]=(x/10)%10; c[++j]=x%10; nc+=3;} else System.out.println("Eroare date !"); } s=new int[4][n+1]; // cu economie de spatiu !!! calcul(); out.print(s[nc%4][n]); out.close(); t2=System.currentTimeMillis(); System.out.println("Timp = "+(t2-t1)); }// main(...) static void calcul() { // s[i][j]=max(s[i-1][j-1]+c[i],// xj are 1: cifra c[i] // s[i-2][j-1]+c[i-1]*10+c[i], // xj are 2 cifre: c[i-1]c[i] // s[i-3][j-1]+c[i-2]*100+c[i-1]*10+c[i]); // xj are 3 cifre int i,j,smax; s[1][1]=c[1]; s[2][1]=c[1]*10+c[2]; s[3][1]=c[1]*100+c[2]*10+c[3]; if(c[2]!=0) s[2][2]=c[1]+c[2]; if((c[2]!=0)&&(c[3]!=0)) s[3][3]=c[1]+c[2]+c[3]; if(c[3]==0) { if(c[2]!=0) s[3][2]=c[1]+c[2]; } else // c[3]!=0 { if(c[2]==0) s[3][2]=c[1]*10+c[3]; else // c[2]!=0 && c[3]!=0 s[3][2]=max(c[1]+c[2]*10+c[3],c[1]*10+c[2]+c[3]); } for(i=4;i<=nc;i++) // i = pozitie cifra in sirul cifrelor for(j=1;j<=n;j++) // j = pozitie numar in sirul final al numerelor { smax=0; if(j<=i)

424 {

CAPITOLUL 16. PROGRAMARE DINAMICA

if((c[i]!=0)&&(s[(i-1+4)%4][j-1]!=0)) smax=max(smax,s[(i-1+4)%4][j-1]+c[i]); if((c[i-1]!=0)&&(s[(i-2+4)%4][j-1]!=0)) smax=max(smax,s[(i-2+4)%4][j-1]+c[i-1]*10+c[i]); if((c[i-2]!=0)&&(s[(i-3+4)%4][j-1]!=0)) smax=max(smax,s[(i-3+4)%4][j-1]+c[i-2]*100+c[i-1]*10+c[i]); } s[i%4][j]=smax; } }// calcul() static int max(int a, int b) { if(a>b) return a; else return b; } }// class Varianta 3: import java.io.*; // fara economie de spatiu dar cu afisare o solutie ! class Suma3 { static int n,nc; // nc=nr cifre din sir static int[] c=new int[1501]; // sirul cifrelor static int[][] s; static int[][] p; // predecesori static int[] sol; // o solutie public static void main (String[] args) throws IOException { int i,j,x; StreamTokenizer st=new StreamTokenizer( new BufferedReader(new FileReader("suma.in"))); PrintWriter out=new PrintWriter(new BufferedWriter( new FileWriter("suma.out"))); st.nextToken(); n=(int)st.nval; nc=0; j=0; for(i=1;i<=n;i++)

16.2. PROBLEME REZOLVATE {

425

st.nextToken(); x=(int)st.nval; if(x<10) {c[++j]=x; nc+=1;} else if(x<100) {c[++j]=x/10; c[++j]=x%10; nc+=2;} else if(x<1000) {c[++j]=x/100; c[++j]=(x/10)%10; c[++j]=x%10; nc+=3;} else System.out.println("Eroare date !"); } s=new int[nc+1][n+1]; p=new int[nc+1][n+1]; calcul(); afism(s); System.out.println(); afism(p); System.out.println(); sol=new int[n+1]; solutia(); afisv(sol); out.print(s[nc][n]); out.close(); }// main(...) static void solutia() { int i,i1,i2,k; i2=nc; for(k=n;k>=1;k--) { i1=p[i2][k]; System.out.print(k+" : "+i1+"->"+i2+" ==> "); for(i=i1;i<=i2;i++) sol[k]=sol[k]*10+c[i]; System.out.println(sol[k]); i2=i1-1; } }// solutia() static void calcul() { // s[i][j]=max(s[i-1][j-1]+c[i],// xj are 1: cifra c[i] // s[i-2][j-1]+c[i-1]*10+c[i], // xj are 2 cifre: c[i-1]c[i] // s[i-3][j-1]+c[i-2]*100+c[i-1]*10+c[i]); // xj are 3 cifre int i,j,smax;

426

CAPITOLUL 16. PROGRAMARE DINAMICA s[1][1]=c[1]; s[2][1]=c[1]*10+c[2]; s[3][1]=c[1]*100+c[2]*10+c[3]; p[1][1]=p[2][1]=p[3][1]=1; if(c[2]!=0) s[2][2]=c[1]+c[2]; if((c[2]!=0)&&(c[3]!=0)) s[3][3]=c[1]+c[2]+c[3]; if(c[3]==0) { if(c[2]!=0) s[3][2]=c[1]+c[2]; } else // c[3]!=0 { if(c[2]==0) { s[3][2]=c[1]*10+0+c[3]; p[3][2]=3;} else // c[2]!=0 && c[3]!=0 { s[3][2]=max(c[1]+c[2]*10+c[3],c[1]*10+c[2]+c[3]); if(s[3][2]==c[1]+c[2]*10+c[3]) p[3][2]=2; else p[3][2]=3; } } for(i=4;i<=nc;i++) // i = pozitie cifra in sirul cifrelor for(j=1;j<=n;j++) // j = pozitie numar in sirul final al numerelor { smax=0; if(j<=i) { if((c[i]!=0)&&(s[i-1][j-1]!=0)) { smax=max(smax,s[i-1][j-1]+c[i]); if(smax==s[i-1][j-1]+c[i]) p[i][j]=i; } if((c[i-1]!=0)&&(s[i-2][j-1]!=0)) { smax=max(smax,s[i-2][j-1]+c[i-1]*10+c[i]); if(smax==s[i-2][j-1]+c[i-1]*10+c[i]) p[i][j]=i-1; } if((c[i-2]!=0)&&(s[i-3][j-1]!=0)) { smax=max(smax,s[i-3][j-1]+c[i-2]*100+c[i-1]*10+c[i]); if(smax==s[i-3][j-1]+c[i-2]*100+c[i-1]*10+c[i]) p[i][j]=i-2; }

16.2. PROBLEME REZOLVATE }// if s[i][j]=smax; }// for }// calcul() static int max(int a, int b) { if(a>b) return a; else return b; } static void afism(int[][] x) { int i,j; System.out.print(" \t"); for(j=1;j<=n;j++) System.out.print(j+"\t"); System.out.println(); for(i=1;i<=nc;i++) { System.out.print(i+" "+c[i]+" :\t"); for(j=1;j<=n;j++) System.out.print(x[i][j]+"\t"); System.out.println(); } }// afism() static void afisv(int[] sol) { int i,sum=0; System.out.println(); for(i=1;i<=n;i++) { System.out.print(sol[i]+" "); sum+=sol[i]; } System.out.println(" ==> "+sum); }// afisv() }// class

427

428

CAPITOLUL 16. PROGRAMARE DINAMICA

Capitolul 17

Potrivirea sirurilor
Consider am un text (un sir de caractere) t = (t1 , t2 , ..., tn ) si un sablon (tot un sir de caractere, numit pattern n englez a) p = (p1 , p2 , ..., pm ). Consider am mn si dorim s a determin am dac a textul t cont ine sablonul p, adic a, dac a exist a 0 d n m astfel nc at td+i = pi pentru orice 1 i m. Problema potrivirii sirurilor const a n determinarea tuturor valorilor d (considerate deplasamente) cu proprietatea ment ionat a.

17.1

Un algoritm inecient

Pentru ecare pozit ie i cuprins a ntre 1 si n m +1 vom verica dac a sub sirul (xi , xi+1 , ..., xi+m1 ) 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"))); 429

430

CAPITOLUL 17. POTRIVIREA S IRURILOR

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++) { 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 */

17.2. UN ALGORITM EFICIENT - KMP

431

17.2

Un algoritm ecient - KMP

Algoritmul KMP (Knuth-Morris-Pratt) determin a potrivirea sirurilor folosind informat ii referitoare la potrivirea sub sirului cu diferite deplasamente ale sale. Pentru t: 1 a si 1 a 2 b 3 a 1 a 3 a 4 b 2 b 4 b 5 a 3 a 5 a a 1 5 a 6 b 4 a 6 b b 2 6 b 7 a 5 b 7 a a 3 7 a 8 a 6 a 8 a a 4 8 a 9 b 7 b 9 b b 5 9 b 10 a 11 b 12 a 13 a 14 b 15 a 16 b 17 a

p: 2 b

exist a dou a potriviri: 10 a a 6 10 a a 1 11 b b 7 11 b b 2 12 a 13 a 14 b 15 a 16 b 17 a

t: p:

t: p:

1 a

2 b

3 a

4 b

12 a a 3

13 a a 4

14 b b 5

15 a a 6

16 b b 7

17 a

S irul inecient de ncerc ari este: 1 a a 2 b b a 3 a a b a 4 b a a b a 5 a b a a b a 6 b a b a a b a 7 a b a b a a b a 8 a b a b a a b a 9 b 10 a 11 b 12 a 13 a 14 b 15 a 16 b 17 a

t: p: p: p: p: p: p: p: p: p: p: p:

b a b a a b a

b a b a a b a

b a b a a b a 1

* b a b a a b 2

b a b a a 3

b a b a 4

b a b 5

b a 6

* b 7

Prima nepotrivire din ecare ncercare este evident iat a prin caracter boldat iar solut iile sunt marcate cu *. Dorim s a avans am cu mai mult de un pas la o nou a ncercare, f ar a s a risc am s a pierdem vreo solut ie!

432 1 a a 2 b b 3 a a a 4 b a b 5 a b a a 6 b a a b 7 a b b a

CAPITOLUL 17. POTRIVIREA S IRURILOR 8 a a a 9 b b b 10 a 11 b 12 a 13 a 14 b 15 a 16 b 17 a

t: p: p: p: p: p:

a a

b b

* a

a a

b b

* a

Not am prin t[i..j ] secvent a de elemente consecutive (ti , ..., tj ) (cuprins a ntre pozit iile i si j ) din sirul t = (t1 , t2 , ..., tn ). S a presupunem c a suntem la un pas al veric arii potrivirii cu un deplasament d si prima nepotrivire a ap arut pe pozit ia i din text si pozit ia j + 1 din sablon, deci t[i j..i 1] = p[1..j ] si t[i] = p[j + 1]. Care este cel mai bun deplasament d pe care trebuie s a-l ncercam?

p:
1 ...

t:
d'

xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx xxxxxxxxxxxxxxxxxxxxxxxxxxx xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx xxxxxxxxxxxxxxxxxxxxxxxxxxx xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx xxxxxxxxxxxxxxxxxxxxxxxxxxx xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx xxxxxxxxxxxxxxxxxxxxxxxxxxx xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx xxxxxxxxxxxxxxxxxxxxxxxxxxx xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx xxxxxxxxxxxxxxxxxxxxxxxxxxx xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx xxxxxxxxxxxxxxxxxxxxxxxxxxx xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx xxxxxxxxxxxxxxxxxxxxxxxxxxx xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx xxxxxxxxxxxxxxxxxxxxxxxxxxx xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx xxxxxxxxxxxxxxxxxxxxxxxxxxx xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx xxxxxxxxxxxxxxxxxxxxxxxxxxx xxxxxxxxxxxxxxxxxxxxxxxxxxx xxxxxxxxxxxxxxxxxxxxxxxxxxx xxxxxxxxxxxxxxxxxxxxxxxxxxx xxxxxxxxxxxxxxxxxxxxxxxxxxx xxxxxxxxxxxxxxxxxxxxxxxxxxx xxxxxxxxxxxxxxxxxxxxxxxxxxx xxxxxxxxxxxxxxxxxxxxxxxxxxx xxxxxxxxxxxxxxxxxxxxxxxxxxx xxxxxxxxxxxxxxxxxxxxxxxxxxx

...

j+1-k i-k 1

...

j+1

... ... ...

i-j

...

...

i-1 k

i k+1

... ... m

...

p:

...

Figura 17.1: Deplasare optim a Folosind Figura 17.2, dorim s a determin am cel mai mare indice k < j astfel nc at p[1..k ] = p[j + 1 k..j ]. Cu alte cuvinte, dorim s a determin am cel mai lung sux al secvent ei p[1..j ] iar noul deplasament d trebuie ales astfel nc at s a realizeze acest lucru. Este astfel realizat a si potrivirea textului t cu sablonul p, t[i k..i 1] = p[1..k ]. R am ane s a veric am apoi dac a t[i] = p[k + 1]. Observ am c a noul deplasament depinde numai de sablonul p si nu are nici o leg atur a cu textul t. Algoritmul KMP utilizeaz a pentru determinarea celor mai lungi suxe o funct ie numit a next. Dat ind sirul de caractere p[1..m], funct ia next : {1, 2, ..., m} {0, 1, ..., m 1} este denit a astfel: next(j ) = max{k/k < j si p[1..k ] este sux pentru p[1..j ]}. Cum determin am practic valorile funct iei next?

17.2. UN ALGORITM EFICIENT - KMP Initializ am next[1] = 0. Presupunem c a au fost determinate valorile next[1], next[2], ..., next[j ]. Cum determin am next[j + 1]?
xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx

433

k+1-k'

p: p:
1 ...

...

...

k+1 j+1 k'+1

... ... ...

... ... ...

j+1-k

... ...

p:

xxxxxxxxxxxxxxxxxxxxxxxxxxxxxx xxxxxxxxxxxxxxxxxxxxxxxxxxxxxx xxxxxxxxxxxxxxxxxxxxxxxxxxxxxx xxxxxxxxxxxxxxxxxxxxxxxxxxxxxx xxxxxxxxxxxxxxxxxxxxxxxxxxxxxx xxxxxxxxxxxxxxxxxxxxxxxxxxxxxx xxxxxxxxxxxxxxxxxxxxxxxxxxxxxx xxxxxxxxxxxxxxxxxxxxxxxxxxxxxx xxxxxxxxxxxxxxxxxxxxxxxxxxxxxx xxxxxxxxxxxxxxxxxxxxxxxxxxxxxx xxxxxxxxxxxxxxxxxxxxxxxxxxxxxx xxxxxxxxxxxxxxxxxxxxxxxxxxxxxx

j+1-k'

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

k'

Figura 17.2: Funct ia next Ne ajut am de Figura 17.2 pentru a urm ari mai u sor rat ionamentul! In aceast a gur a next[j ] = k si next[k ] = k . Dac a p[j +1] = p[k +1] (folosind notat iile din gur a) atunci next[j +1] = k +1. Obt inem: dac a p[j + 1] = p[next(j ) + 1] atunci next[j + 1] = next(j ) + 1. Ce se nt ampl a dac a p[j + 1] = p[k + 1]? C aut am un sux mai mic pentru p[1..j ]! Fie acesta p[1..k ]. Dar acest sux mai mic este cel mai lung sux pentru p[1..k ], cu alte cuvinte k = next(k ) = next(next(j )). Astfel, dac a p[j + 1] = p[k + 1] atunci next(j + 1) = k + 1. Obt inem: dac a p[j + 1] = p[next(next(j )) + 1] atunci next[j + 1] = next(next(j )) + 1. Dac a nici acum nu avem egalitate de caractere, vom continua acela si rat ionament p an a cand g asim o egalitate de caractere sau lungimea prexului c autat este 0. Evident, acest algoritm se termin a ntr-un num ar nit de pa si pentru c a j > k > k > ... 0. Dac a 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;

434

CAPITOLUL 17. POTRIVIREA S IRURILOR

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; 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] {

17.2. UN ALGORITM EFICIENT - KMP

435

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() }//class /* pattern apare cu deplasarea 5 : 12312123412123412 1234 pattern apare cu deplasarea 11 : 12312123412123412 1234 */

436

CAPITOLUL 17. POTRIVIREA S IRURILOR

17.3
17.3.1

Probleme rezolvate
Circular - Campion 2003-2004 Runda 6

Autor: prof. Mot Nistor, Colegiul National N.Balcescu - Braila Se spune c a sirul y1 , y2 , ..., yn este o permutare circular a cu p pozit ii a sirului x1 , x2 , ..., xn dac a y1 = xp + 1, y2 = xp + 2, ..., yn = xp + n, unde indicii mai mari ca n se consider a modulo n, adic a indicele k , cu k > n se refer a la elementul de indice k n. Cerint a Pentru dou a siruri date determinat i dac a al doilea este o permutare circular a a primului sir. Date de intrare Pe prima linie a sierului de intrare circular.in este scris numarul natural n. Pe liniile urm atoare sunt dou a siruri de caractere de lungime n, formate numai din litere mari ale alfabetului latin. Date de ie sire Pe prima linie a sierului circular.out se va scrie cel mai mic num ar natural p pentru care sirul de pe linia a treia este o permutare circular a cu p pozit ii a sirului de pe linia a doua, sau num arul 1 dac a nu avem o permutare circular a. Restrict ii si preciz ari 1 n 20000 Exemple circular.in 10 ABCBAABBAB BABABCBAAB circular.out 7

Timp maxim de execut ie/test: 0.1 secunde Rezolvare (indicat ia autorului): O variant a cu dou a for-uri e foarte u sor de scris, dar nu se ncadreaz a n timp pentru n mare. Folosim algoritmului KMP de c autare a unui sub sir. Concaten am primul sir cu el nsu si si c aut am prima aparit ie a celui de-al doilea sir n sirul nou format. In realitate nu e nevoie de concatenarea efectiv aa sirului, doar tinem cont c a indicii care se refer a la sirul mai lung trebuie luat i modulo n. import java.io.*; class Circular { static int n,d=-1; // pozitia de potrivire

17.3. PROBLEME REZOLVATE static char[] x,y; // x[1..n]=text, y[1..m]=pattern static int[] next;

437

static void readData() throws IOException { String s; char[] sc; int i; BufferedReader br=new BufferedReader(new FileReader("circular.in")); 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

438

CAPITOLUL 17. POTRIVIREA S IRURILOR

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 12341212341234112312 */

17.3.2

Cifru - ONI2006 baraj

Copiii solarieni se joac a adesea trimit andu- si mesaje codicate. Pentru codicare ei folosesc un cifru bazat pe o permutare p a literelor alfabetului solarian si un num ar natural d. Alfabetul solarian cont ine m litere foarte complicate, a sa c a 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 si m, c1 c2 ...cn , codicarea mesajului se realizeaz a astfel: se nlocuie ste ecare liter a ci cu p(ci ), apoi sirul obt inut p(c1 )p(c2 )...p(cn ) se rote ste spre dreapta, f ac and o permutare circular a cu d pozit ii rezult and sirul p(cnd+1 )...p(cn1 )p(cn )p(c1 )p(c2 )...p(cnd ). De exemplu, pentru mesajul 213321, permutarea p = (312) (adic a p(1) = 3, p(2) = 1, p(3) = 2) si d = 2. Aplic and permutarea p vom obt ine sirul 132213, apoi rotind spre dreapta sirul cu dou a pozit ii obt inem codicarea 131322. Cerint a:

17.3. PROBLEME REZOLVATE

439

Date ind un mesaj necodicat si codicarea sa, determinat i cifrul folosit (permutarea p si num arul d). Date de intrare: Fi sierul de intrare cifru.in cont ine pe prima linie numele naturale n s i m, separate prin spat iu, reprezent and lungimea mesajului si respectiv num arul de litere din alfabetul solarian. Pe cea de a doua linie este scris mesajul necodicat ca o succesiune de n numere cuprinse ntre 1 si m separate prin c ate un spat iu. Pe cea de a treia linie este scris mesajul codicat ca o succesiune de n numere cuprinse ntre 1 si m separate prin c ate un spat iu. Date de ie sire: Fi sierul de ie sire cifru.out va cont ine pe prima linie num arul natural d, reprezent and num arul de pozit ii cu care s-a realizat permutarea circular a spre dreapta. Dac a pentru d exist a mai multe posibilit a tii se va alege valoarea minim a. Pe urm atoarea linie este descris a permutarea p. Mai exact se vor scrie valorile p(1), p(2), ..., p(m) separate prin c ate un spat iu. Restrict ii: n 100000 m 9999 Mesajul cont ine ecare num ar natural din intervalul [1, m] cel put in o dat a. Pentru teste cu m 5 se acord a 40 de puncte din care 20 pentru teste si cu n 2000. Exemplu: cifru.in cifru.out 63 2 213321 312 131322 Timp maxim de execut ie/test: 0.2 secunde Indicatii de rezolvare: Solut ia comisiei Fiecare aparit ie a unui simbol din alfabet ntr-un sir se nlocuie ste cu distant a fat a de precedenta aparit ie a aceluia si simbol (consider and sirul circular, deci pentru prima aparit ie a simbolului se ia distant a fat a de ultima aparit ie a aceluia si simbol). F ac and aceast a recodicare pentru cele dou a siruri reducem problema la determinarea permut arii circulare care duce primul sir n al doilea, care poate rezolvat a cu un algoritm de pattern matching, dac a concaten am primul sir cu el nsu si rezult and o complexitate O(n). Pentru m mic se pot genera toate permut arile mult imii {1, 2, ..., m} f ac and pentru ecare permutare o c autare (cu KMP de exemplu), iar pentru n mic se poate c auta permutarea pentru ecare d = 0, 1, ..., n. Codul surs a import java.io.*;

440

CAPITOLUL 17. POTRIVIREA S IRURILOR

class kmp { static int[] t0; // text mesaj necodificat --> spatiu ... de eliberat ! static int[] t1; // text mesaj codificat --> spatiu ... de eliberat ! 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();

17.3. PROBLEME REZOLVATE s=d0; prefix(s,p,n); //afisv(s,1,n); afisv(p,1,n); System.out.println();

441

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

442

CAPITOLUL 17. POTRIVIREA S IRURILOR

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

17.3. PROBLEME REZOLVATE 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

443

444

CAPITOLUL 17. POTRIVIREA S IRURILOR

Capitolul 18

Geometrie computat ional a

18.1

Determinarea orient arii

Consider am trei puncte n plan P1 (x1 , y1 ), P2 (x2 , y2 ) si P3 (x3 , y3 ). Panta segmentului P1 P2 : m12 = (y2 y1 )/(x2 x1 ) Panta segmentului P2 P3 : m23 = (y3 y2 )/(x3 x2 )
P3 P3 P2 P2 P1 a) P1 b) P1 c) P2 P3

Orientarea parcurgerii laturilor P1 P2 si P2 P3 ( n aceast a ordine): n sens trigonometric (spre st anga): m12 < m23 , cazul a) n gur a n sensul acelor de ceas (spre dreapta): m12 > m23 , cazul c) n gur a v arfuri coliniare: m12 = m23 , cazul b) n gur a Orientarea depinde de valoarea expresiei o(P1 (x1 , y1 ), P2 (x2 , y2 ), P3 (x3 , y3 )) = (y2 y1 ) (x3 x2 ) (y3 y2 ) (x2 x1 ) 445

446 astfel

CAPITOLUL 18. GEOMETRIE COMPUTAT IONALA

18.2

< 0 sens trigonometric o(P1 (x1 , y1 ), P2 (x2 , y2 ), P3 (x3 , y3 )) = 0 coliniare > 0 sensul acelor de ceas

Testarea convexit a tii poligoanelor

Consider am un poligon cu n v arfuri P1 (x1 , y1 )P2 (x2 , y2 )...Pn (xn yn ), n 3. Poligonul este convex dac a si numai dac a perechile de segmente (P1 P2 , P2 P3 ), (P2 P3 , P3 P4 ), ..., (Pn2 Pn1 , Pn1 Pn ) si (Pn1 Pn , Pn P1 ) au aceea si orientare sau sunt colineare.
P7 P6 P5 P1 P2 P3 P4 P1 P7 P6 P5 P2 P3 P4

a)

b)

18.3

Aria poligoanelor convexe

Aria poligonului convex cu n v arfuri P1 (x1 , y1 )P2 (x2 , y2 )...Pn (xn yn ), n 3 se poate determina cu ajutorul urm atoarei formule: 1 |x1 y2 + x2 y3 + ... + xn1 yn + xn y1 y1 x2 + y2 x3 + ... + yn1 xn + yn x1 | 2 Expresia de sub modul este pozitiv a dac a orientarea P1 P2 ...Pn P1 este n sens trigonometric, este negativ a dac a orientarea P1 P2 ...Pn P1 este n sensul acelor de ceasornic si este nul a dac a punctele P1 (x1 , y1 ), P2 (x2 , y2 ), ..., Pn (xn yn ) sunt colineare. Reciproca acestei armat ii este deasemenea adev arat a n cazul poligoanelor convexe.

18.4

Pozit ia unui punct fat a de un poligon convex

DE UN POLIGON CONCAV 18.5. POZIT IA UNUI PUNCT FAT A

447

Consider am un poligon convex cu n v arfuri P1 (x1 , y1 )P2 (x2 , y2 )...Pn (xn yn ), n3 si un punct P0 (x0 , y0 ). Dorim s a determin am dac a punctul P0 (x0 , y0 ) este n interiorul poligonului. Pentru comoditatea prezent arii consider am si punctul Pn+1 (xn+1 , yn+1 ) unde x1 = xn+1 si y1 = yn+1 , adic a punctul Pn+1 este de fapt tot punctul P1 . Consider am o latur a oarecare [Pi Pi+1 ] (1 i n) a poligonului. Ecuat ia dreptei (Pi Pi+1 ) este (Pi Pi+1 ) : y yi = yi+1 yi (x xi ) xi+1 xi

Aducem la acela si numitor si consider am funct ia fi (x, y ) = (y yi ) (xi+1 xi ) (x xi ) (yi+1 yi ) Dreapta (Pi Pi+1 ) mparte planul n dou a semiplane. Funct ia fi (x, y ) are valori de acela si semn pentru toate punctele din acela si semiplan, valori cu semn contrar pentru toate punctele din cel alalt semiplan si valoarea 0 pentru doate punctele situate pe dreapt a. Pentru a siguri c a punctul P0 (x0 , y0 ) se a a n interiorul poligonului (acesta ind convex) trebuie s a veric am dac a toate v arfurile poligonului mpreun a cu punctul P0 (x0 , y0 ) sunt de aceea si parte a dreptei (Pi Pi+1 ), adic a toate valorile fi (xj , yj ) (1 j n, j = i si j = i + 1) au acela si semn cu fi (x0 , y0 ) (sau sunt nule dac a accept am prezent a punctului P0 (x0 , y0 ) pe frontiera poligonului). Aceasta este o condit ie necesar a dar nu si sucient a. Vom verica dac a pentru orice latur a [Pi Pi+1 ] (1 i n) a poligonului toate celelalte v arfuri sunt n acela si semiplan cu P0 (x0 , y0 ) (din cele dou a determinate de dreapta suport a laturii respective) iar dac a se nt ampl a acest lucru atunci putem trage concluzia c a punctul P0 (x0 , y0 ) se a a n interiorul poligonului convex. O alt a modalitate de vericare dac a punctul P0 (x0 , y0 ) este n interiorul sau pe frontiera poligonului convex P1 (x1 , y1 )P2 (x2 , y2 )...Pn (xn yn ) este vericarea urm atoarei relat ii:
n

ariepoligon (P1 P2 ...Pn ) =


k=1

arietriunghi (P0 Pk Pk+1 )

unde punctul P (xn+1 , yn+1 ) este de fapt tot punctul P1 (x1 , y1 ).

18.5

Pozit ia unui punct fat a de un poligon concav

Consider am un poligon concav cu n v arfuri P1 (x1 , y1 )P2 (x2 , y2 )...Pn (xn yn ), n3 si un punct P0 (x0 , y0 ). Dorim s a determin am dac a punctul P0 (x0 , y0 ) este n interiorul poligonului.

448

CAPITOLUL 18. GEOMETRIE COMPUTAT IONALA

Poligonul concav se descompune n poligoane convexe cu ajutorul diagonalelor interne si se folose ste un algoritm pentru poligoane convexe pentru ecare poligon convex astfel obt inut. Dac a punctul este n interiorul unui poligon convex obt inut prin partit ionarea poligonului concav atunci el se a a n interiorul acestuia. Dac a nu se a a n nici un poligon convex obt inut prin partit ionarea poligonului concav atunci el nu se a a n interiorul acestuia.
P7 P6 P5 P1 P2 P3 P4 P1 P2 P3 P4 P7 P6 P5

a)

b)

18.6

Inf a sur atoarea convex a


Impachetarea Jarvis

18.6.1

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

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

a)

b)

Toate punctele de pe nf a sur atoarea convex a (cazul a) n gur a): import java.io.*; // infasuratoare convexa class Jarvis1 // pe frontiera coliniare ==> le iau pe toate ... !!! {

URATOAREA 18.6. INFAS CONVEXA static static static static static int int int int int n,npic=0; [] x; [] y; [] p; [] u;

449

// npic=nr puncte pe infasuratoarea convexa

// precedent // 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( // 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)

450 ) i2=i;

CAPITOLUL 18. GEOMETRIE COMPUTAT IONALA

} 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; } infasurareJarvis(); }//main }//class F ar a punctele coliniare de pe nf a sur atoarea convex a (cazul b) n gur a): 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

URATOAREA 18.6. INFAS CONVEXA 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(); }

451

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;

452

CAPITOLUL 18. GEOMETRIE COMPUTAT IONALA

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; } infasurareJarvis(); }//main }//class

18.6.2

Scanarea Craham

Versiune cu mesaje pentru sortarea punctelor: 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));

URATOAREA 18.6. INFAS CONVEXA

453

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

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

a)

b)

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

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

a)

b)

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

454

CAPITOLUL 18. GEOMETRIE COMPUTAT IONALA

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) { 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+" // i=j si P[i] este pe locul lui !!! j="+j);

URATOAREA 18.6. INFAS CONVEXA

455

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;

456

CAPITOLUL 18. GEOMETRIE COMPUTAT IONALA 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]; 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 a sur atoare: 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)

URATOAREA 18.6. INFAS CONVEXA {

457

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

458

CAPITOLUL 18. GEOMETRIE COMPUTAT IONALA

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; StreamTokenizer st= new StreamTokenizer( new BufferedReader(new FileReader("graham1.in"))); st.nextToken(); n=(int)st.nval;

URATOAREA 18.6. INFAS CONVEXA x=new y=new o=new p=new int[n+1]; int[n+1]; int[n+1]; int[n+1];

459

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 ar a puncte coliniare pe nf a sur atoare: 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) {

460 while(

CAPITOLUL 18. GEOMETRIE COMPUTAT IONALA (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; 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) {

URATOAREA 18.6. INFAS CONVEXA 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;

461

// 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() 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++) {

462

CAPITOLUL 18. GEOMETRIE COMPUTAT IONALA st.nextToken(); x[k]=(int)st.nval; st.nextToken(); y[k]=(int)st.nval; o[k]=k; } scanareGraham();

}//main }//class

18.7

Dreptunghi minim de acoperire a punctelor

Se poate determina dreptunghiul minim de acoperire pentru nf a sur atoarea convex a (gura 18.1) pentru a prelucra mai put ine puncte dar nu este obligatorie aceast a strategie.

M P7 A P1 P2 N
xmin

D P6 P12 P10 P3 P11 P4 P5 C Q


xmax ymin

ymax

P8

P9

Figura 18.1: Dreptunghi minim de acoperire

Putem s a presupunem c a punctele formeaz a un poligon convex. Determinarea dreptunghiului de arie minim a care cont ine n interiorul s au (inclusiv frontiera) toate punctele date se poate face observ and c a o latur a a sa cont ine o latur a a poligonului convex. Pentru ecare latur a a poligonului convex se determin a dreptunghiul minim de acoperire care cont ine acea latur a. Dintre aceste dreptunghiuri se alege cel cu aria minim a.

18.8. CERC MINIM DE ACOPERIRE A PUNCTELOR

463

18.8

Cerc minim de acoperire a punctelor

Se poate determina cercul minim de acoperire pentru nf a sur atoarea convex a pentru a prelucra mai put ine puncte dar nu este obligatorie aceast a strategie.

Ck = C k-1
P
k x

k+1

Ck

Ck

k+1

a)

b)

Ordon am punctele astfel nc at pe primele pozit ii s a e plasate punctele de extrem (cel mai din st anga, urmat de cel mai din dreapta, urmat de cel mai de jos, urmat de cel mai de sus; dup a acestea urmeaz a celelalte puncte ntr-o ordine oarecare). Presupunem c a punctele, dup a ordonare, sunt: P1 (x1 , y1 ), P2 (x2 , y2 ), P3 (x3 , y3 ), ..., Pn (xn , yn ). Not am cu Ci (ai , bi ; ri ) cercul de centru (ai , bi ) si raz a minim a ri care acoper a punctele P1 , P2 , ..., Pn . Consider am cercul C2 (a2 , b2 ; r2 ) unde a2 = (x1 + x2 )/2, b2 = (y1 + y2 )/2 si 2 + (y y )2 , adic ( x x ) a cercul de diametru [ P P ]. r2 = 1 2 1 2 1 1 2 2 S a presupunem c a am determinat, pas cu pas, cercurile C2 , C3 , ..., Ci si trebuie s a determin am cercul Ci+1 . Dac a punctul Pi+1 se a a n interiorul cercului Ci atunci cercul Ci+1 este identic cu Ci . Dac a punctul Pi+1 nu se a a n interiorul cercului Ci atunci cercul Ci+1 se determin a relu nd algoritmul pentru sirul de puncte P1 , P2 , ...,Pi , Pi+1 dar impun and condit ia ca acest cerc s a treac a n mod obligatoriu prin punctul Pi+1 (xi+1 , yi+1 ). Putem plasa acest punct pe prima pozit ie n sirul punctelor si astfel vom impune la ecare pas ca punctul P1 s a e pe cercul care trebuie determinat!

18.9
18.9.1

Probleme rezolvate
Seceta - ONI2005 clasa a IX-a
lect. Ovidiu Dom sa

464

CAPITOLUL 18. GEOMETRIE COMPUTAT IONALA

Gr adinile roditoare ale B ar aganului sufer a anual pierderi imense din cauza secetei. C aut atorii de ap a au g asit n f ant ani din care doresc s a alimenteze n gr adini. Fie Gi , Fi , i = 1, ..., n puncte n plan reprezent and puncte de alimentare ale gr adinilor si respectiv punctele n care se a a f ant anile. Pentru ecare punct se dau coordonatele ntregi (x, y ) n plan. Pentru a economisi materiale, leg atura dintre o gr adin a si o f ant an a se realizeaz a printr-o conduct a n linie dreapt a. Fiecare f ant an a alimenteaz a o singur a gr adin a. Consiliul Judet ean Galat i pl ate ste investit ia cu condit ia ca lungimea total a a conductelor s a e minim a. Fiecare unitate de conduct a cost a 100 lei noi (RON). Cerint a S a se determine m, costul minim total al conductelor ce leag a ecare gr adin a cu exact o f ant an a. Date de intrare Fi sierul de intrare seceta.in va cont ine: Pe prima linie se a a num arul natural n, reprezent and num arul gr adinilor si al f ant anilor. Pe urm atoarele n linii se a a perechi de numere ntregi Gx Gy , separate printr-un spat iu, reprezent and coordonatele punctelor de alimentare ale gr adinilor. Pe urm atoarele n linii se a a perechi de numere ntregi Fx Fy , separate printr-un spat iu, reprezent and coordonatele punctelor f ant anilor. Date de ie sire Fi sierul de ie sire seceta.out va cont ine: m un num ar natural reprezent and partea ntreag a a costului minim total al conductelor. Restrict ii si preciz ari 1 < n < 13 0 Gx, Gy, F x, F y 200 Nu exist a trei puncte coliniare, indiferent dac a sunt gr adini sau f ant ani Orice linie din sierele de intrare si ie sire se termin a prin marcajul de sf ar sit de linie. Exemplu seceta.in 3 14 33 47 23 25 31 seceta.out 624 Explicat ie Costul minim este [6.24264 * 100]=624 prin legarea perechilor: Gradini Fantani 14 23 33 31 47 25

Timp maxim de execut ie/test: 1 sec sub Windows si 0.5 sec sub Linux.

18.9. PROBLEME REZOLVATE Indicat ii de rezolvare *

465

Solut ia ocial a, lect. Ovidiu Dom sa Num arul mic al punctelor permite generarea tuturor posibilit a tilor de a conecta o gr adin a cu o f ant an a neconectat a la un moment dat. Pentru ecare astfel de combinat ie g asit a se calculeaz a suma distant elor (Gi, F j ), n linie dreapta, folosind formula distant ei dintre dou a puncte n plan, studiat a la geometrie. (d(A(x, y ), B (z, t) = (x z )2 + (y t)2 ). Acest a solut ie implementat a corect asigur a 60 70 de puncte. Pentru a obt ine punctajul maxim se tine cont de urm atoarele aspecte: 1. Se construie ste n prealabil matricea distant elor d(i, j ) cu semnicat ia distant ei dintre gr adina i si f ant ana j . Aceasta va reduce timpul de calcul la variantele cu peste 9 perechi. 2. Pentru a elimina cazuri care nu pot constitui solut ii optime se folose ste proprietatea patrulaterului c a suma a doua laturi opuse (condit ie care asigur a unicitatea conect arii unei singure f ant ani la o singur a gr adin a) este mai mic a dec at suma diagonalelor. De aceea nu se vor lua n considerare acele segmente care se intersecteaz a. Condit ia de intersect ie a dou a segmente care au capetele n punctele de coordonate A(a1, a2), B (b1, b2), C (c1, c2), D(d1, d2) este ca lu and segmentul AB , punctele C si D s a se ae de aceea si parte a segmentului AB si respectiv pentru segmentul CD, punctele A si B s a se ae de aceea si parte (se nlocuie ste n ecuat ia dreptei ce trece prin dou a puncte, studiat a n clasa a 9-a). Observat ie: Pentru cei interesat i, problema are solut ie si la un nivel superior, folosind algoritmul de determinare a unui ux maxim de cost minim. Variant a cu determinarea intesect 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();

466

CAPITOLUL 18. GEOMETRIE COMPUTAT IONALA 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(); st.nextToken(); } for(k=1;k<=n;k++) { st.nextToken(); st.nextToken(); } } 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 }

xg[k]=(int)st.nval; yg[k]=(int)st.nval;

xf[k]=(int)st.nval; yf[k]=(int)st.nval;

18.9. PROBLEME REZOLVATE

467

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

468

CAPITOLUL 18. GEOMETRIE COMPUTAT IONALA 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] ? { 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;

18.9. PROBLEME REZOLVATE

469

out=new PrintWriter(new BufferedWriter(new FileWriter("seceta.out"))); out.println((int)(costMin*100)); out.close(); } } Variant a cu cu determinarea pozitiei punctelor in semiplane si 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++)

470 {

CAPITOLUL 18. GEOMETRIE COMPUTAT IONALA

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((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]+") ");

18.9. PROBLEME REZOLVATE System.out.println(" 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]); } // 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 } ("+xg[j]+","+yg[j]+") "+"

471 ("+xf[a[j]]+","+yf[a[j]]+") ");

static void afisare() throws IOException { int k; out=new PrintWriter(new BufferedWriter(new FileWriter("seceta.out"))); out.println((int)(costMin*100)); out.close(); } }

472

CAPITOLUL 18. GEOMETRIE COMPUTAT IONALA

Variant a cu cu determinarea pozitiei punctelor in semiplane, f ar a 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++)

18.9. PROBLEME REZOLVATE { 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 }

473

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

474 {

CAPITOLUL 18. GEOMETRIE COMPUTAT IONALA

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

18.9. PROBLEME REZOLVATE citire(); rezolvare(); afisare(); t2=System.currentTimeMillis(); System.out.println("Timp = "+(t2-t1)+" ms"); }// main(...)

475

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(); st.nextToken(); } for(k=1;k<=n;k++) { st.nextToken(); st.nextToken(); } }// citire(...)

xg[k]=(int)st.nval; yg[k]=(int)st.nval;

xf[k]=(int)st.nval; 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

476 }// rezolvare(...)

CAPITOLUL 18. GEOMETRIE COMPUTAT IONALA

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

18.9. PROBLEME REZOLVATE

477

18.9.2

Antena - ONI2005 clasa a X-a

prof. Osman Ay, Liceul International de Informatic a Bucure sti In Delta Dun arii exist a o zon a s albatic a, rupt a de bucuriile si necazurile civilizat iei moderne. In aceast a zon a exist a doar n case, pozit iile acestora ind specicate prin coordonatele carteziene de pe hart a. Postul de radio al ONI 2005 dore ste s a emit a pentru tot i locuitorii din zon a si, prin urmare, va trebui s a instaleze o anten a de emisie special a pentru aceasta. O anten a emite unde radio ntr-o zon a circular a. Centrul zonei coincide cu punctul n care este pozit ionat a antena. Raza zonei este denumit a puterea antenei. Cu c at puterea antenei este mai mare, cu at at antena este mai scump a. Prin urmare trebuie selectat a o pozit ie optim a de amplasare a antenei, astfel nc at ecare cas a s a se ae n interiorul sau pe frontiera zonei circulare n care emite antena, iar puterea antenei s a e minim a. Cerint a Scriet i un program care s a determine o pozit ie optim a de amplasare a antenei, precum si puterea minim a a acesteia. Datele de intrare Fi sierul de intrare antena.in cont ine pe prima linie un num ar natural n, reprezent and num arul de case din zon a. Pe urm atoarele n linii se a a pozit iile caselor. Mai exact, pe linia i + 1 se a a dou a numere ntregi separate printr-un spat iu x y , ce reprezint a abscisa si respectiv ordonata casei i. Nu exist a dou a case n aceea si locat ie. Datele de ie sire Fi sierul de ie sire antena.out cont ine pe prima linie dou a numere reale separate printr-un spat iu x y reprezentnd abscisa si ordonata pozit iei optime de amplasare a antenei. Pe cea de a doua linie se va scrie un num ar real reprezent and puterea antenei. Restrict ii si preciz ari 2 < N < 15001 15000 < x, y < 15001 Numerele reale din sierul de ie sire trebuie scrise cu trei zecimale cu rotun-

jire.

La evaluare, se veric a dac a diferent a dintre solut ia a sat a si cea corect a ( n valoare absolut a) este < 0.01. Exemplu

478 antena.in 7 50 26 45 22 02 36 52

CAPITOLUL 18. GEOMETRIE COMPUTAT IONALA antena.out 3.250 2.875 3.366 Explicat ie Antena va plasat a n punctul de coordonate (3.250, 2.825) iar puterea antenei este 3.366

Timp maxim de execut ie/test: 0.3 secunde pentru Windows si 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!!! { 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")));

18.9. PROBLEME REZOLVATE 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; }

479

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;

480

CAPITOLUL 18. GEOMETRIE COMPUTAT IONALA

cercDeDiametru(xx,yy,xxx,yyy); for(i=1;i<=j;i++) // acopera 1,2,...,j 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

18.9. PROBLEME REZOLVATE { swap(1,3); swap(2,4); } }// puncteExtreme()

481

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

482

CAPITOLUL 18. GEOMETRIE COMPUTAT IONALA

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

18.9.3

Mo sia lui P acal a - OJI2004 clasa a XI-a

P acal a a primit, a sa cum era nvoiala, un petec de teren de pe mo sia boierului. Terenul este mprejmuit complet cu segmente drepte de gard ce se sprijin a la ambele capete de c ate un par zdrav an. La o nou a prinsoare, P acal a iese iar n c a stig si prime ste dreptul s a str amute ni ste pari, unul c ate unul, cum i-o voia, astfel nc at s a- si extind a suprafat a de teren. Dar nvoiala prevede c a ecare par poate mutat n orice direct ie, dar nu pe o distant a mai mare dec at o valoare dat a (scris a pe ecare par) si ecare segment de gard, ind cam suubred, poate rotit si prelungit de la un singur cap at, cel alalt r am an and nemi scat. Cunoscnd pozit iile init iale ale parilor si valoarea nscris a pe ecare par, se cere suprafat a maxim a cu care poate s a- si extind a P acal a proprietatea. Se stie c a parii sunt dat i ntr-o ordine oarecare, pozit iile lor init iale sunt date prin numere ntregi de cel mult 3 cifre, distant ele pe care ecare par poate deplasat sunt numere naturale strict pozitive si gura format a de terenul init ial este un poligon neconcav. Date de intrare

18.9. PROBLEME REZOLVATE

483

Fi sierul MOSIA.IN cont ine n + 1 linii cu urm atoarele valori: n - num arul de pari x1 y1 d1 - coordonatele init iale si distant a pe care poate mutat parul 1 x2 y2 d2 - coordonatele init iale si distant a pe care poate mutat parul 2 ... xn yn dn - coordonatele init iale si distant a pe care poate mutat parul n Date de ie sire In sierul MOSIA.OUT se scrie un num ar real cu 4 zecimale ce reprezint a suprafat a maxim a cu care se poate m ari mo sia. Restrict ii si observat ii: 3 < N 200 num ar natural 1000 < xi , yi < 1000 numere ntregi 0 < di 20 numere ntregi poligonul neconcav se dene ste ca un poligon convex cu unele v arfuri coliniare pozit iile parilor sunt date ntr-o ordine oarecare poligonul obt inut dup a mutarea parilor poate concav pozit iile nale ale parilor nu sunt n mod obligatoriu numere naturale Exemplu Pentru sierul de intrare 4 -3 0 2 3 0 3 0 6 2 0 -6 6 se va scrie n sierul de ie sire valoarea 30.0000 Explicat ie: prin mutarea parilor 1 si 2 cu c ate 2 si respectiv 3 unit a ti, se obt ine un teren av and suprafat a cu 30 de unit a ti mai mare dec at terenul init ial. Timp limit a de executare: 1 sec./test

18.9.4

Partit ie - ONI2006 baraj

Ionic a a primit de ziua lui de la tat al s au un joc format din piese de form a triunghiular a de dimensiuni diferite si o suprafat u a magnetic a pe care acestea pot a sezate. Pe suprafat a magnetic a este desenat un triunghi dreptunghic cu lungimile catetelor a, respectiv b si 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 a seaz a pe tabla magnetic a n piese, pentru care se cunosc coordonatele v arfurilor lor. Tat al lui Ionic a vrea s a verice dac a pe tabl a piesele realizeaz a o partit ie a triunghiului dreptunghic desenat, adic a dac a sunt ndeplinite condit iile: nu exist a piese suprapuse;

484

CAPITOLUL 18. GEOMETRIE COMPUTAT IONALA piesele acoper a toat a port iunea desenat a ( n form a de triunghi dreptunghic); nu exist a port iuni din piese n afara triunghiului desenat.

Cerint a Se cere s a se verice dac a piesele plasate pe tabla magnetic a formeaz a o partit ie a triunghiului desenat pe tabla magnetic a. Date de intrare Fi sierul de intrare part.in cont ine pe prima linie un num ar natural k , reprezent and num arul de seturi de date din sier. Urmeaz a k grupe de linii, c ate o grup a pentru ecare set de date. Grupa de linii corespunz atoare unui set este format a dintr-o linie cu numerele a, b, n separate ntre ele prin c ate un spat iu si n linii cu c ate sase numere ntregi separate prin spat ii reprezent and coordonatele v arfurilor (abscis a ordonat a) celor n piese, c ate o pies a pe o linie. Date de ie sire In sierul part.out se vor scrie k linii, c ate o linie pentru ecare set de date. Pe linia i (i = 1, 2, ..., k) se va scrie 1 dac a triunghiurile din setul de date i formeaz a o partit ie a triunghiului desenat pe tabla magnetic a sau 0 n caz contrar. Restrict ii si preciz ari 1 n 150 1 k 10 a, b sunt numere ntregi din intervalul [0, 31000] Coordonatele vrfurilor pieselor sunt numere ntregi din intervalul [0, 31000]. Exemplu part.in 2 20 10 4 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 part.out 1 0

Timp maxim de execut ie: 0.3 secunde/test Prelucrare n Java dup a rezolvarea n C a autorului problemei import java.io.*; class part { static final int ON_EDGE=0; static final int INSIDE=1;

18.9. PROBLEME REZOLVATE


y 10 T1 5 T2 T3 a) 10 T4 20 x b) 10 5 T1 T2 20 x 10 y

485

Figura 18.2: a) pentru setul 1 de date si b) pentru setul 2 de date

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

486

CAPITOLUL 18. GEOMETRIE COMPUTAT IONALA

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;

18.9. PROBLEME REZOLVATE

487

for(i=0;i<N;i++) 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

18.9.5

Triunghi - ONI2007 cls 9

In comuna Triunghi din Rom ania sunt n t arani codicat i prin numerele

488

CAPITOLUL 18. GEOMETRIE COMPUTAT IONALA

1, 2, ..., n. Dup a anul 1990 a nceput retrocedarea suprafet elor de p am ant det inute nainte de colectivizare. Fiecare t aran are un document prin care dovede ste c a este proprietar pe o singur a suprafat a de teren de form a triunghiular a. Din p acate, documentele dau b ataie de cap primarului (care se ocup a de retrocedarea suprafet elor de p am ant), pentru c a sunt port iuni din suprafet ele de p am ant care se reg asesc pe mai multe documente. In aceast a comun a exist a o f ant an a cu ap a, ind posibil ca ea s a e revendicat a de mai mult i t arani. O suprafat a de p am ant este dat a prin coordonatele celor trei colt uri, iar f ant ana este considerat a punctiform a si dat a prin coordonatele punctului. Cerint a S a se scrie un program care s a determine: a) Codurile t aranilor care au documente cu suprafet e de p am ant ce cont in n interior sau pe frontier a f ant ana. b) Codul t aranului ce det ine un document cu suprafat a de teren, care include toate celelalte suprafet e. Date de intrare Fi sierul de intrare triunghi.in are pe prima linie num arul n de t arani, pe urm atoarele n linii c ate 6 valori numere ntregi separate prin c ate un spat iu, n formatul: x1 y 1 x2 y 2 x3 y 3, ce reprezint a coordonatele celor trei colt uri ale suprafet ei triunghiulare det inute de un t aran (x1, x2, x3 abscise, iar y 1, y 2, y 3 ordonate). Pe linia i + 1 se a a coordonatele colt urilor suprafet ei de teren triunghiulare det inute de t aranul i, i = 1, 2, ..., n. Ultima linie a sierului (linia n + 2) va cont ine coordonatele f ant anii n formatul x y , cu un spat iu ntre ele (x abscis a, iar y ordonat a). Date de ie sire Fi sierul de ie sire triunghi.out va cont ine pe prima linie r aspunsul de la punctul a), adic a: num arul de t arani care ndeplinesc condit ia din cerint a si apoi codurile lor ( n ordine cresc atoare), cu un spat iu ntre ele. Dac a nu exist a t arani cu condit ia din cerint a, pe prima linie se va scrie cifra 0. Pe linia a doua se va scrie r aspunsul de la punctul b), adic a: codul t aranului cu proprietatea cerut a, sau cifra 0, dac a nu exist a un astfel de t aran. Restrict ii si preciz ari 2 n 65 coordonatele colt urilor suprafet elor de p am ant si ale f ant anii sunt numere ntregi din intervalul [3000, 3000] cele trei colt uri ale ec arei suprafet e de p am ant sunt distincte si necoliniare nu exist a doi t arani care s a det in a aceea si suprafat a de p am ant nu se acord a punctaje part iale. Exemplu

18.9. PROBLEME REZOLVATE secv.in 3 10 0 0 10 10 10 0 100 100 0 -100 0 0 0 10 0 0 10 10 5 secv.out 212 2

489

Explicat ie: La punctul a), sunt doi t arani care det in suprafet e de p am ant ce au n interior sau pe frontier a f ant ana, cu codurile 1 si 2. La punctul b), t aranul cu codul 2 det ine o suprafat a de teren care include, suprafet ele de p am ant det inute de ceilalt i t arani (cu codurile 1 si 3). Timp maxim de execut ie/test: 0.1 secunde Indicat ii de rezolvare - descriere solut ie Descrierea solut iei (Prof. Doru Popescu Anastasiu) Not am cu T1 , T2 , ..., Tn triunghiurile corespunz atoare suprafet elor si cu I punctul unde se g ase ste f ant ana. Ti = Ai Bi Ci , i = 1, 2, ..., n. a) nr = 0 Pentru i = 1, ..., n veric am dac a I este interior sau pe frontiera lui Ti , n caz armativ nr = nr + 1 si sol[nr] = i. A sa m nr si vectorul sol. Pentru a verica dac a I este interior sau pe frontiera unui triunghi Ti este sucient s a veric am dac a: aria(Ai Bi Ci ) = aria(IAi Bi ) + aria(IAi Ci ) + aria(IBi Ci ) O alt a variant a ar s a folosim pozit ia unui punct fat a de o dreapt a. b) Dac a exist a un asemenea triunghi atunci el este de arie maxim a. Astfel determin am triunghiul p de arie maxim a. Pentru acest triunghi veric am dac a toate celelalte n 1 triunghiuri sunt interioare sau pe frontiera lui Tp (adic a dac a au toate v arfurile n interiorul sau pe frontiera lui Tp ). In caz armativ se a seaz a p, altfel 0. Codul surs a import java.io.*; class Pereti {

490 static static static static static static static static static

CAPITOLUL 18. GEOMETRIE COMPUTAT IONALA int n,x0,y0; int smax=-1,imax=-1,nr=0; int[] x1=new int[66]; int[] y1=new int[66]; int[] x2=new int[66]; int[] y2=new int[66]; int[] x3=new int[66]; int[] y3=new int[66]; 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+" ");

18.9. PROBLEME REZOLVATE

491

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

492

CAPITOLUL 18. GEOMETRIE COMPUTAT IONALA

Capitolul 19

Teoria jocurilor

19.1

Jocul NIM

19.1.1

Prezentare general a

19.1.2

Exemple

493

494

CAPITOLUL 19. TEORIA JOCURILOR

Capitolul 20

Alt i algoritmi

20.1

Secvent a de sum a maxim a

20.1.1

Prezentare general a

20.1.2

Exemple

20.2

Algoritmul Belmann-Ford

Pentru grafuri cu costuri negative (dar fara cicluri negative!) se poate folosi algoritmul Bellman-Ford.

20.2.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 495

496 // // // //

CAPITOLUL 20. ALT I ALGORITMI 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; static int[] d; static int[] p; // matricea costurilor // d[u]=dist(nods,u) // 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);

20.2. ALGORITMUL BELMANN-FORD

497

for(k=1;k<=n-1;k++) for(u=1;u<n;u++) for(v=u+1;v<=n;v++) if(w[u][v]<oo) relax(u,v);

// // // //

de n-1 ori !!! vectorii muchiilor erau mai buni ! lista de adiacenta era mai buna ! (u,v)=muchie si 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; d[s]=0; }// init(...)

p[u]=-1;

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

498

CAPITOLUL 20. ALT I ALGORITMI

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 1 3 2 3 3 4 4 5 5 6 4 6 */

-3 1 2 1 1 -3 2

1-->1 1-->2 1-->3 1-->4 1-->5 1-->6

dist=0 drum: 1 dist=-3 drum: 1 2 dist=-1 drum: 1 2 3 dist=0 drum: 1 2 3 4 dist=1 drum: 1 2 3 4 5 dist=-2 drum: 1 2 3 4 5 6

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

20.2. ALGORITMUL BELMANN-FORD static int[][] w; // matricea costurilor static int[] d; // d[u]=dist(nods,u) static int[] p; // p[u]=predecesorul nodului u

499

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++) for(u=1;u<=n;u++) for(v=1;v<=n;v++) if(w[u][v]<oo) relax(u,v);

// // // //

de n-1 ori !!! vectorii arcelor erau mai buni ! lista de adiacenta era mai buna ! (u,v)=arc

boolean cicluNegativ=false; for(u=1;u<=n;u++) for(v=1;v<=n;v++) 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);

500

CAPITOLUL 20. ALT I ALGORITMI

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 1 2 -3 1 3 1

false 1-->1 dist=0 drum: 1 1-->2 dist=-4 drum: 1 3 2

20.2. ALGORITMUL BELMANN-FORD 2 3 3 4 5 4 5 6 4 6 3 2 */ 6 1 1 -3 2 -5 1-->3 1-->4 1-->5 1-->6 dist=1 drum: 1 3 dist=2 drum: 1 3 4 dist=2147483647 drum: Nu exista drum! dist=4 drum: 1 3 4 6

501

20.2.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];

502 lista=new int[n+1]; gi=new int[n+1]; d=new int[n+1]; p=new int[n+1];

CAPITOLUL 20. ALT I ALGORITMI

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 init(nods); for(i=1;i<=n;i++) { u=lista[i]; for(v=1;v<=n;v++) if(w[u][v]<oo) relax(u,v); }

: "); afisv(lista);

// lista de adiacenta era mai buna !

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;

20.2. ALGORITMUL BELMANN-FORD 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+" "); }

503

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;} return nod; }// nodgi0()

504

CAPITOLUL 20. ALT I ALGORITMI

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 1 2 1 3 3 4 5 4 5 6 4 6 3 2 */

-3 1 1 1 -3 2 -5

Lista : 1 3 2 5 4 6 Distante : 0 -4 1 2 oo 4 1 : 0 ... 1 2 : -4 ... 1 3 2 3 : 1 ... 1 3 4 : 2 ... 1 3 4 5: oo ... 6 : 4 ... 1 3 4 6

Bibliograe
[1] Aho, A.; Hopcroft, J.; Ullman, J.D.; Data strutures and algorithms, Addison Wesley, 1983 [2] Aho, A.; Hopcroft, J.; Ullman, J.D.; The Random Access Machine, 1974 [3] Andonie R., G arbacea I.; Algoritmi fundamentali, o perspectiv a C++, Ed. Libris, 1995 [4] Apostol C., Ro sca I. Gh., Ro sca V., Ghilic-Micu B., Introducere n programare. Teorie si aplicatii, Editura ... Bucure sti, 1993 [5] Atanasiu, A.; Concursuri de informatic a. Editura Petrion, 1995 [6] Atanasiu, A.; Ordinul de complexitate al unui algoritm. Gazeta de Informatic a nr.1/1993 [7] - Bell D., Perr M.: Java for Students, Second Edition, Prentice Hall, 1999 [8] Calude C.; Teoria algoritmilor, Ed. Universit a tii Bucure sti, 1987 [9] Cerchez, E.; Informatic a - Culegere de probleme pentru liceu, Ed. Polirom, Ia si, 2002 [10] Cerchez, E., S erban, M.; Informatic a - manual pentru clasa a X-a., Ed. Polirom, Ia si, 2000 [11] Cori, R.; L evy, J.J.; Algorithmes et Programmation, Polycopi e, version 1.6; http://w3.edu.polytechnique.fr/informatique/ [12] Cormen, T.H., Leiserson C.E., Rivest, R.L.; Introducere n Algoritmi, Ed Agora, 2000 [13] Cormen, T.H., Leiserson C.E., Rivest, R.L.; Pseudo-Code Language, 1994 [14] Cristea, V.; Giumale, C.; Kalisz, E.; Paunoiu, Al.; Limbajul C standard, Ed. Teora, Bucure sti, 1992 [15] Erickson J.; Combinatorial Algorithms; http://www.uiuc.edu/~jeffe/ 505

506 [16] Flanagan, D.; Java in a Nutshell, OReilly, 1997. [17] Flanagan, D.; Java examples in a Nutshell, OReilly, 1997.

BIBLIOGRAFIE

[18] Giumale, C.; Introducere n Analiza Algoritmilor, Ed.Polirom, 2004 [19] Giumale C., Negreanu L., C alinoiu S.; Proiectarea si analiza algoritmilor. Algoritmi de sortare, Ed. All, 1997 [20] Gosling, J.; Joy, B.; Steele, G.; The Java Language Specication, Addison Wesley, 1996. [21] Knuth, D.E.; Arta program arii calculatoarelor, vol. 1: Algoritmi fundamentali, Ed. Teora, 1999. [22] Knuth, D.E.; Arta programarii calculatoarelor, vol. 2: Algoritmi seminumerici, Ed. Teora, 2000. [23] Knuth, D.E.; Arta programarii calculatoarelor, vol. 3: Sortare si c autare, Ed. Teora, 2001. [24] Lambert,K. A., Osborne,M.; Java. A Framework for Programming and Problem Solving, PWS Publishing, 1999 [25] Livovschi, L.; Georgescu H.; Analiza si sinteza algoritmilor. Ed. Enciclopedic a, Bucure sti, 1986. [26] Niemeyer, P.; Peck J.; Exploring Java, OReilly, 1997. [27] Od agescu, I.; Smeureanu, I.; S tef anescu, I.; Programarea avansat a a calculatoarelor personale, Ed. Militar a, Bucure sti 1993 [28] Od agescu, I.; Metode si tehnici de programare, Ed. Computer Lobris Agora, Cluj, 1998 [29] Popescu Anastasiu, D.; Puncte de articulat ie si punt i n grafuri, Gazeta de Informatic a nr. 5/1993 [30] Rotariu E.; Limbajul Java, Computer Press Agora, Tg. Mures, 1996 [31] Tomescu, I.; Probleme de combinatoric a si teoria grafurilor, Editura Didactic a si Pedagogic a, Bucure sti, 1981 [32] Tomescu, I.; Leu, A.; Matematic a aplicat a n tehnica de calcul, Editura Didactic a si Pedagogic a, Bucure sti, 1982 [33] Vaduva, C.M.; Programarea in JAVA. Microinformatica, 1999 [34] ii nescu, R.;; Vii nescu, V.; Programare dinamic a - teorie si aplicat ii; GInfo nr. 15/4 2005

BIBLIOGRAFIE

507

[35] Vlada, M.; Conceptul de algoritm - abordare modern a, GInfo, 13/2,3 2003 [36] Vlada, M.; Grafuri neorientate si aplicat ii. Gazeta de Informatic a, 1993 [37] Weis, M.A.; Data structures and Algorithm Analysis, Ed. The Benjamin/Cummings Publishing Company. Inc., Redwoods City, California, 1995. [38] Winston, P.H., Narasimhan, S.: On to JAVA, Addison-Wesley, 1996 [39] Wirth N., Algorithms + Data Structures = Programs, Prentice Hall, Inc 1976 [40] *** - Gazeta de Informatic a, Editura Libris, 1991-2005