Documente Academic
Documente Profesional
Documente Cultură
INTRODUCERE
2
păcate, lipsa talentului va apărea la început sub forma unei insatisfacţii interioare şi
ca o impresie acută că lipsesc rezultatele. Reamintim că însăşi cuvîntul facultate
are la origine sensul de capacitate, potenţialitate, înzestrare. Deci, normal ar fi ca
alegerea unui student pentru frecventarea cursurilor unei Facultăţi să fi fost făcută
ţinînd cont de aptitudinile şi abilităţile celui în cauză, descoperite în prelabil, adică
să se dovedească talentat pentru domeniul ales. Acest lucru este cu atît mai
important în cazul optării pentru învăţarea programării, cunoscută fiind ca o
specializare complexă şi solicitantă.
Încheiem răspunzînd într-o singură frază întrebării din titlu Ce şanse am să devin un bun
programator ? :
dacă mă simt înzestrat cu talent pentru programare (adică nu mă simt inconfortabil
la acest subiect) atunci, mobilizîndu-mi voinţa (motivaţia) şi amplificîndu-mi capacitatea
de abstractizare, logica, inteligenţa şi creativitatea (ce există în mine într-o formă
potenţială), prin practică de programare voi acumula în timp tehnica şi experienţa
necesare pentru a deveni cu siguranţă un bun programator , însă nu fără efort, răbdare
şi perseverenţă.
3
Probleme de logică
Probleme de perspicacitate
3. Fumat Lui Popescu nici prin gînd nu-i trecea să folosească toate mijloacele pe care le
avea la îndemînă ca să lupte împotriva adversarilor tendinţei contra neintroducerii mişcării
anti-fumat. Care este poziţia lui Popescu: este pentru sau contra fumatului ?
4. Trei cutii. În trei cutii identice sînt închise trei perechi de fructe: fie o pereche de mere,
fie o pereche de pere, fie o pereche formată dintr-un măr şi o pară. Pe cele trei cutii sînt
lipite trei etichete: "două mere", "două pere" şi, respectiv, "un măr şi o pară". Ştiind că
nici una din etichete nu corespunde cu conţinutul cuitei închise pe care se află, să se afle
care este numărul minim de extrageri a cîte un fruct pentru a se stabili conţinutul fiecărei
cutii.
5. Întrerupătoarele. Pe peretele alăturat uşei încuiate de la intrarea unei încăperi, se află trei
întrerupătoare ce corespund cu cele trei becuri de pe plafonul încăperii în care nu putem
intra. Acţionînd oricare din întrerupătoare, dunga de lumină care apare pe sub uşă ne
asigură că niciunul din cele trei becuri nu este ars. Cum putem afla, intrând o singură dată
în încăpere, care întrerupător corespunde cu care bec ?
4
6. Cîntarul defect. Avînd la dispoziţie un cîntar gradat defect care greşeşte constant cu
aceeaşi valoare (cantitate necunoscută de grame), putem să cîntărim ceva determinîndu-i
corect greutatea ?
7. Împăturirea celor 8 pătrate. Împăturiţi iniţial în opt o foaie dreptunghiulară după care
desfaceţi-o şi însemnaţi fiecare din cele opt zone dreptunghiulare obţinute (marcate de
pliurile de îndoire) cu o cifră de la 1 la 8. Puteţi împături foaia astfel obţinută reducînd-o
de opt ori (la un singur dreptunghi sau pătrat) astfel încît trecînd cu un ac prin cele opt
pliuri suprapuse acesta să le perforeze exact în ordinea 1, 2, 3, …, 8 ? Încercaţi aceste
două configuraţii:
1 8 7 4
2 3 6 5
1 8 2 7
4 5 3 6
8. Problemă pentru cei puternici. Încercaţi să împăturiţi de 8 ori, pur şi simplu, o coală de
hîrtie (de fiecare dată linia de îndoire este "în cruce" peste cea dinainte). Este posibil ?
9. Piese defecte Într-un atelier există 10 lădiţe ce conţin fiecare piese cu greutatea de 100
grame, cu excepţia uneia din lădiţe ce conţine piese defecte având greutatea de 90 grame.
Puteţi preciza care este lădiţa cu pricina, folosind un cîntar doar pentru o singură dată ?
5
Probleme cu chibrituri
2. Folosind doar sase bete de chibrit realizati in plan patru triunghiuri echilaterale egale.
3. Camarute. Realizati o constructie in care sa apara zece "camere" cu ajutorul a sapte chibrituri.Acestea nu pot
avea capete libere.
4. Triunghiuri. Folosind sase bete de chibrit, realizati o constructie care sa cuprinda patru triunghiuri echilaterale,
toate avand lungimea unui bat .
6. Doar 6 chibrituri. Realizati o constructie in care sa apara douasprezece triunghiuri dreptungice cu ajutorul a ...
sase chibrituri.
7. Cateva figuri geometrice simple. Utilizand noua chibrituri, realizati o constructie in care sa existe trei patrate
si doua triunghiuri echilaterale, toate cu laturile egale cu lungimea unui chibrit.
8. Demonstratie. Aratati ca folosind zece bete de chibrit, asezate doar in pozitie verticala sau orizontala se poate
construi un unic poligon cu aria de cinci unitati.
9. Un poligon. Folosind douasprezece bete de chibrit, desenati un poligon avand aria egala cu patru unitati.
10. Problema se complica. Impartiti optsprezece bete de chibrit in doua grupuri : unul de cinci si celalalt de
treisprezece.Realizati astfel, doua poligoane, unul cu aria de cinci ori mai mare decat a celuilalt.
12. Numarul maxim. Cate constructii diferite se pot obtine prin folosirea a numai batru bete de chibrit? (Doua
figuri sunt diferite daca nu se poate obtine una din cealalta prin deformare, oglindire sau rasturnare)
13. Un hectar. Lungimea unui bat de chibrit este de aproximativ 4,5 cm, aria unui patrat format din patru bete
fiind deci de aproximativ 20 cm 2 . Care este numarul minim de bate necesare pentru a obtine un hectar?
14. Patrate. Daca admitem patratul cu aria nula, observam ca are perimetrul egal ca valoare numerica cu aria
sa.Exista si alte asemenea patrate?De cate chibrituri avem nevoie pentru a le construi?
15. Fractii. Cu ajutorul a sase bete de chibrit a fost realizata o fractie subunitara. Deplasati un singur bat
pentru a obtine o fractie cu valoarea 1.
16. Un cub. Intr-un punct de intalnire a trei bete de chibrit putem avea zero, unul, doua sau trei capete cu fosfor,
deci patru posibilitati. Din douasprezece bete realizati un cub in care pe fiecare fata sa se gaseasca toate cele
patru combinatii.
17. "Intalnirea". Realizati o constructie in care sa apara exact o data fiecare combinatie posibila de puncte de
intalnire a unul, doua si trei chirituri, distingand intre capatul cu fosfor si cel fara. (Punctul de intalnire pentru un
chibrit este un capat liber)
18. 4-5 patrate. Plecand de la urmatoarea figura,adaugati patru bete pentru a obtine o constructie cu numai patru,
respectiv cinci patrate.
6
19. Deplasari. In figura apar trei patrate realizate cu jutorul a douasprezece chibrituri.
Deplasati trei bete pentru a obtine sapte patrate sau patru chibrituri pentru a obtine patru
patrate.
20. Hexagonul. In figura apare un hexagon format cu ajutorul a sase chibrituri. Formati doua
romburi prin deplasarea a doua bete si adaugarea unui al treilea chibrit.
21. Spirala. Deplasati numai patru bete de chibrit din spirala pentru a obtine trei patrate.
22. Numarul minim. Care este cel mai mic numar care poate fi scris cu ajutorul a trei bete de chibrit?
23. Fereastra. Sa ne imaginam ca figura reprezinta o fereastra cu patru ochiuri de geam. Construiti o
alta fereastra, cu laturile formate tot din doua chibrituri, care sa aiba opt ochiuri de geam de aceeasi
forma, toate cu latura egala cu un bat de chibrit.
24. Impartiri. In figura apare un patrat 5 X 5 si patru chibrituri in interiorul acestuia. Folosind inca cincisprezece
bete de chibrit, impartiti suprafata patatului in cinci zone de forme diferite, dar de arii egale.
7
25. Posibilitati multiple. Aflati toate posibilitatile de orientare a trei chibrituri care se intalnesc
intr-un punct, chibrituri asezate dupa directiile indicate in figura.
26. O adunare. Deplasati un chibrit, astfel incat din operatia incorecta sa se obtina o egalitate corecta.
27. Unghiul drept. Folosind numai trei bete de chibrit, realizati de patru ori mai mule unghiuri drepte.
28. O constructie continua. Gasiti un drum continuu de-a lungul constructiei din figura cu conditia ca acesta sa
treaca peste fiecare bat o singura data.
29. Casuta din chibrituri. Casuta din figura are usa pe peretele din stanga. Deplasati doua chibrituri pentru a
obtine o casuta cu usa pe peretele din dreapta.
1. Substituirea literelor. Subtituiţi literele cu cifre astfel încît următoarele adunări să fie
corecte: GERALD + DONALD = ROBERT ; FORTY + TEN + TEN = SIXTY ; BALON
+ OVAL = RUGBY.
2. Test de angajare la Microsoft. Patru excursionişti ajung pe malul unui rîu pe care doresc
să-l traverseze. Întrucît s-a înoptat şi ei dispun doar de o singură lanternă, ei pot să treacă
rîul cel mult cîte doi laolaltă. Ştiind că, datorită diferenţelor de vîrstă şi datorită oboselii,
ei ar avea individual nevoie pentru a traversa rîul de 1, 2, 8 şi 10 minute, se cere să se
decidă dacă este posibilă traversarea rîului în aceste conditţii în doar 17 minute ?
8
3. (!) Imposibilă. Să se taie toate cele 16 segmente ale figurii următoare cu o singură linie
curbă continuă şi care nu se intersectează cu ea însăşi.
4. (!) Problema "ochilor albaştri". Sîntem martorii următorului dialog între două persoane
X şi Y. << X: Eu am trei copii. Produsul vîrstei lor este 36 iar suma vîrstei lor este egală
cu numărul de etaje al blocului din vecini de mine. Îl ştii, nu-i aşa ? Y: Desigur. Dar
numai din cît mi-ai spsus nu pot să deduc care este vîrsta copiilor tăi. X: Bine, atunci află
că cel mare are ochi albaştrii.>> Puteţi afla care este vîrsta celor trei copii ?
6. Vinul în apă şi apa în vin. Dintr-o sticlă ce conţine un litru de apă este luat un pahar (un
decilitru) ce este turnat pest un litru de vin. Vinul cu apa se amestecă bine după care se ia
cu acelaşi pahar o cantitate egală de "vin cu apă" ce se toarnă înapoi peste apa din sticlă.
Avem acum mai multă apă în vin decît vin în apă, sau invers ?
7. (!!!!) Cuiele în echilibru. Avem la dispoziţie 7 cuie normale, cu capul obişnuit. Înfigem
unul vertical în podea (sau într-o placă de lemn). Se cere să se aşeze cele 6 cuie rămase în
echilibru stabil pe capul cuiului vertical, fără ca niciunul din cele şase cuie să atingă
podeaua.
8. (!!) Ţigările tangente. Este posibil să aşezăm pe masă şase ţigări astfel încît fiecare să se
atingă cu fiecare (oricare două să fie tangente) ? (!!!) Dar şapte ţigări ?
9. (!) Problema celor 12 înţelepţi (în variantă modernă). Managerul unei mari companii
doreşte să pună la încercare inteligenţa şi puterea de judecată a celor 12 membrii ai
consiliului său de conducere. Luînd 12 cărţi de joc, unele de pică şi altele de caro, el le
aşează cîte una pe fruntea fiecărui consilier astfel încît fiecare să poată vedea cărţile de pe
frunţile celorlalţi dar nu şi pe a sa. Managerul le cere celor care consideră că au pe frunte o
carte de caro (diamond) să facă un pas în faţă, altfel ei nu vor mai putea face parte din
consiliu. După ce îşi repetă cererea de şapte ori, timp în care niciunul din cei 12 consilieri
nu face nici o mişcare (ci doar se privesc unii pe alţii), toţi consilierii care au într-adevăr
pe frunte o carte de caro ies deodată în faţă. Puteţi deduce cîţi au ieşit şi cum şi-au dat ei
seama ce carte este aşezată pe fruntea lor ?
9
11. Rifi şi Ruf. Cei doi iubiţi Rifi şi Ruf, din nordica ţară Ufu-Rufu, locuiesc în sate diferite
aflate la distanţa de 20 km unul de altul. În fiecare dimineaţă ei pornesc exact deodată (la
răsărit) unul spre celălalt spre a se întîlni şi a se săruta confrom obiceiului nordic: nas în
nas. Într-o dimineaţă o muscă rătăcită porneşte exact la răsăritul soarelui de pe nasul lui
Rifi direct spre nasul lui Ruf, care o alungă trimiţînd-o din nou spre nasul lui Rifi, ş.a.m.d.
..., pînă cînd ea sfîrşeşte tragic în momentul "sărutului" celor doi. Ştiind că Rifi se
deplasează cu 4 km/oră, Ruf cu 6 km/oră iar musca zboară cu 10 km/oră, se cere să se afle
ce distanţă a parcurs musca în zbor de la răsărit şi pînă în momentul tragicului ei sfîrşit.
Na Ra Ta
Tn Na
Ta
Nn Pn Pn
Pa Rn Pa
Pn Pa Pn
Pa Pa Pa
Ca Ca
13. Bronx contra Brooklyn. Un tînăr, ce locuieşte în Manhattan în imediata apropiere a unei
staţii de metrou, are două prietene, una în Brooklyn şi cealaltă în Bronx. Pentru a o vizita
pe cea din Brooklyn el ia metroul ce merge spre partea de jos a oraşului, în timp ce, pentru
a o vizita pe cea din Bronx, el ia din acelaşi loc metroul care merge în direcţie opusă.
Metrourile spre Brooklyn şi spre Bronx intră în staţie cu aceeşi frecvenţă: din 10 în 10
minute fiecare. Dar, deşi el coboară în staţia de metrou în fiecare sîmbătă la întîmplare şi
ia primul metrou care vine (nedorind să "favorizeze" pe nici una din prietenele sale), el a
constatat că, în medie, el merge în Brooklyn de 9 ori din 10. Puteţi găsi o explicaţie logică
a fenomenul ?
14. (!!) Problema celor 12 bile. În faţa noastră se află 12 bile identice ca formă, vopsite la
fel, dar una este cu siguranţă falsă, ea fiind fie mai grea, fie mai uşoară, fiind făcută dintr-
un alt material. Avem la dispoziţie o balanţă şi se cere să determinăm doar prin 3 cîntăriri
care din cele 12 bile este falsă precizînd şi cum este ea: mai grea sau mai uşoară. (!!!) Mai
mult, puteţi determina care este numărul maxim de bile din care prin 4 cîntăriri cu balanţa
se poate afla exact bila falsă şi cum este ea ?
10
15. (!) Problema celor 2 perechi de mănuşi. Aflat într-o situaţie ce implică intervenţia de
urgenţă, un medic chirurg constată că are la dispoziţie doar 2 perechi de mănuşi sterile
deşi el trebuie să intervină rapid şi să opereze succesiv 3 bolnavi. Este posibil ca cele trei
operaţii de urgenţă să se desfăşoare în condiţii de protecţie normale cu numai cele 2
perechi de mănuşi ? (Sîngele fiecăruia din cei 3 pacienţi, precum şi mîna doctorului nu
trebuie să conducă la un contact infecţios.)
16. (!!) Problema frînghiei prea scurte. O persoană ce are asupra ei doar un briceag şi o
frînghie lungă de 30 metri se află pe marginea unei stînci, privind în jos la peretele vertical
de 40 metri aflat sub ea. Frînghia poate fi legată doar în vîrf sau la jumătatea peretelui (la
o înălţime de 20 metri de sol) unde se află o mică platformă de sprijin. Cum este posibil ca
persoana aflată în această situaţie să ajungă teafără jos coborînd numai pe frînghie, fără a
fi nevoită să sară deloc punîndu-se astfel în pericol ?
17. (!!) O jumătate de litru. Avem în faţa noastră un vas cilindric cu capacitatea de 1 litru,
plin ochi cu apă. Se cere să măsurăm cu ajutorul lui ½ litru de apă, fără a ne ajuta de nimic
altceva decît de mîinile noastre.
18. (!) Să v ezi şi să nu crezi. Priviţi următoarele două figuri: prin reaşezarea decupajelor
interioare ale primeia se obţine din nou aceeaşi figură dar avînd un pătrăţel lipsă ! Cum
explicaţi "minunea" ?
11
Care este regula de încifrare? Ce numere reprezintă următoarele coduri cifrate:
nagevonagevogedunanabivobiduvogedu;
nagevonaduvogedunanabivobiduvogedu;
naduvogenanabivobiduvogedu;
nanabivogeduvogedu;
nabivonabivonaduvogedunagevonagevogedunanabivobiduvogedu;
nanagevobiduvogedu?
Încifraţi numerele 256 şi 1024 prin acestă metodă.
1 1 20 101010
2 10 25 1000101
3 11 30 1010001
5 110 40 10001001
10 1110 50 10100100
15 10010 60 100001000
Puteţi spune ce numere sînt codificate prin 100, 101, 1000, 1111, 10000 şi 11111 ?
Puteţi codifica numerele 70, 80, 90, 100, 120, 150 şi 1000 ?
3. (!!!) Problema dialogului perplex. Există două numere m şi n din intervalul [2..99] şi
două persoane P şi S astfel încît persoana P ştie produsul lor, iar S ştie suma lor. Ştiind că
între P şi S a avut loc următorul dialog:
"Nu ştiu numerele" spune P.
"Ştiam ca nu ştii" răspunde S, "nici eu nu ştiu."
"Acuma ştiu !" zice P strălucind de bucurie.
"Acum ştiu şi eu…" şopteşte satisfăcut S.
să se determine toate perechile de numere m şi n ce "satisfac" acest dialog (sînt soluţii ale
problemei).
4. (!!!!) Împăturirea celor 8 pătrate. Împăturiţi iniţial în opt o foaie dreptunghiulară după
care desfaceţi-o şi însemnaţi fiecare pătrăţel obţinut cu o cifră de la 1 la 8. Proiectaţi un
algoritm şi realizaţi un program care, primind configuraţia (numerotarea) celor 8 pătrăţele,
să poată decide dacă se poate împături foaia astfel obţinută reducînd-o de opt ori (la un
singur pătrat) astfel încît trecînd cu un ac prin cele opt foi suprapuse acesta să le perforeze
exact în ordinea 1, 2, 3, …, 8.
5. (!!!!) Problema fetelor de la pension. Problema a apărut pe vremea cînd fetele învăţau la
pension fără ca prin prezenţa lor băieţii să le tulbure educaţia. Pedagoaga fetelor unui
pension de 15 fete a hotarît ca în fiecare dupa-amiază, la ora de plimbare, fetele să se
plimbe în cinci grupuri de cîte trei. Se cere să se stabilească o programare a plimbărilor pe
durata unei săptămîni (şapte zile) astfel încît fiecare fată să ajungă să se plimbe numai o
singură dată cu oricare din celelalte paisprezece (oricare două fete să nu se plimbe de două
ori împreună în decursul unei săptămîni).
12
CAPITOLUL 2
Noţiuni fundamentale de programare
2
fapt permite prognosticarea încă din această fază – faza de proiectare a algoritmului de
soluţionare – a eficienţei practice, măsurabilă ca durată de execuţie, a programului.
Algoritmul
Se ştie că la baza oricărui program stă un algoritm (care, uneori, este numit metodă de
rezolvare). Noţiunea de algoritm este o noţiune fundamentală în informatică şi înţelegerea ei,
alături de înţelegerea modului de funcţionare a unui calculator, permite înţelegerea noţiunii de
program executabil. Vom oferi în continuare o definiţie unanim acceptată pentru noţiunea de
algoritm:
3
Să observăm şi diferenţa: în timp ce metoda matematică este corectă chiar dacă ea
converge către soluţie doar la infinit, un algoritm trebuie să întoarcă rezultatul după un
număr finit de paşi. Să observăm de asemenea că, acolo unde matematica nu oferă
dovada, algoritmul nu va fi capabil să o ofere nici el. De exemplu, nu este greu de scris un
algoritm care să verifice corectitudinea Conjecturii lui Goldbach: “Orice număr par se
scrie ca sumă de două numere prime”, dar, deşi programul rezultat poate fi lăsat să ruleze
pînă la valori extrem de mari, fără să apară nici un contra-exemplu, totuşi conjectura nu
poate fi astfel infirmată, dar nici afirmată.
Descrierea algoritmilor
Două dintre metodele clasice de descriere a algoritmilor sînt denumite Schemele logice şi
Pseudo-Codul. Ambele metode de descriere conţin doar patru instrucţiuni elementare care au
fiecare un corespondent atît schemă logică cît şi în pseudo-cod.
În cele ce urmează vom prezenta amândouă variantele, dar în ultimul timp se foloseşte
numai varianta pseudo-cod, folosirea schemelor logice s-a redus drastic în ultimii ani.
Avantajul descrierii algoritmilor prin scheme logice este dat de libertatea totală de înlănţuire a
operaţiilor (practic, săgeata care descrie ordinea de execuţie, pleacă de la o operaţie şi poate fi
trasată înspre orice altă operaţie). Este demonstrat matematic riguros că descrierea prin
pseudo-cod, deşi pare mult mai restrictivă (operaţiile nu pot fi înlănţuite oricum, ci trebuie
executate în ordinea citirii: de sus în jos şi de la stînga la dreapta), este totuşi perfect
echivalentă. Deci, este dovedit că plusul de ordine, rigoare şi simplitate pe care îl oferă
descrierea prin pseudo-cod nu îngrădeşte prin nimic libertatea programării. Totuşi,
programele scrise în limbajele de asamblare, care sînt mult mai compacte şi au dimensiunile
mult reduse, nu ar putea fi descrise altfel decît prin scheme logice.
Varianta descrierii prin schemă logică este caracterizată prin folosirea următoarelor
blocuri elementare:
1. Atribuirea –
2. Intrare/Ieşire –
3. Condiţionala -
4. Ciclurile – cu ajutorul blocurilor elementare de mai sus se pot realiza orice fel de cicluri,
atât cele cu repetare condiţionată (de tip Repeat-Until şi Do-While) cât şi cele cu repetare
de un număr cunoscut de ori (de tip For-Next).
4
Varianta descrierii prin pseudo cod este caracterizată prin folosirea următoarelor
instrucţiuni elementare:
1. Atribuirea – var:=expresie;
5
Forma de programare care se bazează doar pe cele patru structuri se numeşte
programare structurată (spre deosebire de programarea nestructurată bazată pe descrierea
prin scheme logice). Teorema de echivalenţă a puterii de descriere prin pseudo-cod cu
puterea de descriere prin schemă logică afirmă că programarea structurată (aparent limitată
de cele patru structuri) este echivalentă cu programarea nestructurată (liberă de structuri
impuse). Evident, prin ordinea, lizibilitatea şi fiabilitatea oferită de cele patru structuri
elementare (şi asta fără a îngrădi libertatea de exprimare) programarea structurată este net
avantajoasă. În fapt, limbajele de nivel înalt de programare nestructurată (Fortran, Basic) au
fost de mult scoase din uz. În schimb, cele de nivel scăzut (limbajele de asamblare) sînt
necesare a fi folosite în continuare în cazurile necesităţii unor viteze mari de execuţie la
programarea în timp-real, mai ales pentru software de conducere a proceselor (în
automatizări).
Programul
6
Exemple de probleme rezolvate
1. Se citesc a,b,c coeficienţii reali a unei ecuaţii de gradul II. Să se afişeze soluţile
ecuaţiei.
Descrierea algoritmului:
- ecuaţia de gradul II este de forma ax2+bx+c=0
- presupunînd că a ≠ 0 calculăm determinantul ecuatiei delta = b 2 − 4 ⋅ a ⋅ c
− b ± delta
- dacă delta >= 0 atunci ecuaţia are soluţiile reale x1, 2 =
2⋅a
b delta
- dacă delta < 0 atunci ecuaţia are soluţiile complexe z1, 2 = − ± j⋅
2⋅a 2⋅a
Ecuatie_grad_2;
Var a,b,c,delta,x1,x2,Rex1,Rex2,Imx1,Imx2:real;
BEGIN
Read a,b,c;
If a=0 then
Begin
x1:=-c/b;
Write x1;
End
else
Begin
delta:=b*b-4*a*c;
If delta>=0 then
Begin
x1:=(-b-sqrt(delta))/(2*a);
x2:=(-b+sqrt(delta))/(2*a);
Write x1,x2;
End
else
Begin
Rex1:=-b/(2*a);
Rex2:=-b/(2*a);
Imx1:=-sqrt(-delta))/(2*a);
Imx2:=sqrt(-delta))/(2*a);
Write Rex1,Imx1;
Write Rex2,Imx2;
End
End
END.
7
2. Să se determine dacă trei numere a,b,c reale pot reprezenta laturile unui triunghi.
Dacă da, să se calculeze perimetrul şi aria sa.
Descrierea algoritmului:
- condiţia necesară pentru ca trei numere să poată fi lungimile laturilor unui triunghi este ca
cele trei numere să fie pozitive (condiţie implicită) şi suma a oricăror două dintre ele să fie
mai mare decît cel de-al treilea număr
- după condiţia este îndeplinită vom calcula perimetrul triunghiului cu formula p=a+b+c şi
aria folosind formula lui Heron s=sqrt(2*p(2*p-a)( 2*p-b)( 2*p-c)).
Laturile_Unui_Triunghi;
Var a,b,c,s,p:real;
function laturi_ok:boolean;
begin
laturi_ok:= (a>0) and (b>0) and (c>0) and (a+b>c) and (a+c>b) and (b+c>a);
end;
BEGIN
Read a,b,c;
IF laturi_ok then
begin
p:=a+b+c;
s:=sqrt(2*p*(2*p-a)*( 2*p-b)*( 2*p-c));
write s;
write p;
end
else write 'Nu formeaza triunghi';
END.
Descrierea algoritmului:
- vom oferi varianta în care suma primelor n numere naturale va fi calculata cu una dintre
instructiunile repetitive cunoscute (for, while, repeat) fără a apela la formula matematică
cunoscută S(n)=n*(n+1)/2
Suma_n;
Var n,s,i:integer;
BEGIN
Read n;
s:=0;
For i:=1 to n do s:=s+i;
Write s;
END.
Descrierea algoritmului:
- un număr p este prim dacă nu are nici un divizor în afară de 1 şi p cu ajutorul unei variabile
contor d vom parcurge toate valorile intervalului [2.. p ]; acest interval este suficient pentru
8
depistarea unui divizor, deoarece dacă d1 | p ⇒ p = d1 ⋅ d 2 (unde d1 < d2) ⇒ d1 ≤ d1 ⋅ d 2 =
p iar d2 ≥ d1 ⋅ d 2 = p
Nr_prim;
Var p,i:integer;
prim:boolean;
BEGIN
Read p;
prim:=true;
for i:=2 to trunc(sqrt(p)) do
if n mod i=0 then prim:=false;
if prim then
write 'este nr prim'
else
write 'nu e nr prim';
END.
Vocale;
Var sir:string[80];
Vocale,Consoane,i:integer;
BEGIN
Read sir;
i:=1; Vocale:=0; Consoane:=0;
While sir[i]<>’.’ do
begin
If Upcase(sir[i]) in [‘A’,’E’,’I’,’O’,’U’] then Inc(Vocale)
else If Upcase(sir[i]) in [‘A’..’Z’] then Inc(Consoane);
Inc(i);
end;
Write Vocale, Consoane, i-Vocale-Consoane;
END.
9
În continuare se prezintă o listă de probleme propuse spre rezolvare.
5. Se citeşte n gradul unui polinom şi şirul an, an-1, … , a1, a0 coeficienţilor unui polinom P.
Se citeşte x, să se determine P(x).
Se citesc x şi y, să se determine dacă polinomul P schimbă de semn de la x la y.
Se citeşte a, să se determine restul împărţirii lui P la x-a.
10
Să se afişeze propoziţia rezultată prin inserarea în spatele fiecărei vocale ‘v’ a şirului
“pv” (“vorbirea găinească”).
11
18. Se citeşte n şi k, să se afişeze n ca sumă de k numere distincte.
19. Se citeşte n, să se determine o alegere a semnelor + şi – astfel încît să avem relaţia
1±2±…±(n+1) ±n=0, dacă ea este posibilă.
20. Se citeşte n şi şirul de valori reale x1, x2, … , x n-1, xn ordonat crescător. Să se determine
distanţa maximă între două elemente consecutive din şir.
21. Se citeşte n gradul unui polinom şi şirul xn, xn-1, … , x1 soluţiilor reale a unui polinom P.
Să se determine şirul an, an-1, … , a1, a0 coeficienţilor polinomului P.
22. Se citesc două şiruri de valori reale x1, x2, … , x n-1, xn şi y1, y2, … , y m-1, ym ordonate
crescător. Să se afişeze şirul z1, z2, … , z n+m-1, zn+m rezultat prin interclasarea celor două
şiruri.
23. Un şir de fracţii ireductibile din intervalul [0,1] cu numitorul mai mic sau egal cu n se
numeşte şir Farey de ordinul n. De exemplu, şirul Farey de ordinul 5 (ordonat crescător)
este: 0/1, 1/5, ¼, 1/3, 2/5, ½, 3/5, 2/3, ¾, 4/5, 1/1. Să se determineşirul Farey de ordinul
n, cu n citit.
24. Se citeşte n şi S o permutare a mulţimii {1, 2, …, n}. Să se determine numărul de
inversiuni şi signatura permutării S.
25. Se citeşte n şi S o permutare a mulţimii {1, 2, …, n}. Să se determine cel mai mic număr k
pentru care Sk={1, 2, …, n}.
26. Fie M={1, 3, 4, …} mulţimea numerelor obţinute pe baza regulii R1, şi a regulii R2
aplicate de un număr finit de ori: R1) 1∈M R2 ) Dacă x∈M atunci y=2x+1 şi z=3x+1
aparţin lui M. Se citeşte n, să se determine dacă n aparţine mulţimii M fără a genera toate
elementele acesteia mai mici decît n.
27. Se citeşte n, k şi o matrice A=(ai,j) nxn pătratică. Să se determine Ak.
28. Se citeşte n şi o matrice A=(ai,j) nxn pătratică. Să se determine d determinantul matricii A.
29. Se citeşte n şi cele n perechi (xi, yi) de coordonate a n puncte Pi în plan. Să se determine
care dintre cele n puncte poate fi centrul unui cerc acoperitor de rază minimă.
30. Să se determine, cu 5 zecimale exacte, rădăcina ecuaţiei x3+x+1=0 care există şi este unică
în intervalul [-1,1].
31. Se citeşte n şi şirul de valori reale x1, x2, … , x n-1, xn. Să se determine poziţia de început şi
lungimea celui mai mare subşir de numere pozitive.
32. Se citeşte n, să se afişeze binomul lui Newton: (x+y)n.
33. Se citeşte n, să se afişeze binomul lui Newton generalizat:
(x1+x2+…+xp) =Σn!/(n1!n2!…np!) x1 1x2 2…xp p pentru n1+n2+…+np=n şi ni>0, i=1,p.
n n n n
12
41. Scrieţi o funcţie recursivă pentru a determina o aşezare a 8 turnuri pe o tablă de şah astfel
încît să nu se atace între ele. (Tabla de şah va fi reprezentată printr-o matrice pătratică de
8x8).
42. Să se determine peste cîţi ani data de azi va cădea în aceeaşi zi a săptămînii.
43. Avem la dispoziţie un fişier ce conţine numele, prenumele şi media tuturor studenţilor din
grupă.
Să se afişeze studentul cu cea mai mare medie.
Să se afişeze toţi studenţii bursieri.
Să se afişeze studentul care are media cea mai apropiată de media aritmetică a
mediilor pe grupă.
Să se afişeze toţi studenţii din prima jumătate a alfabetului.
Să se afişeze toţi studenţii în ordine inversă decît cea din fişier.
Să se creeze un fişier catalog care să conţină aceleaşi informaţii în ordinea alfabetică a
numelui.
44. Avem la dispoziţie două fişiere ce conţin numele, prenumele şi media tuturor studenţilor
din cele două grupe ale anului în ordinea descrescătoare a mediilor.
Să se afişeze toţi studenţii din ambele grupe care au media mai mare decît media
anului.
Să se creeze prin interclasare un fişier totalizator care conţine toţi studenţii anului în
ordinea descrescătoare a mediilor.
13
Probleme dificile
La fel ca matematica şi informatica are o mulţime de probleme foarte dificile care îşi
aşteaptă încă rezolvarea. Asemănarea cu matematica ne interesează mai ales în privinţa unui
aspect "capcană" asupra căruia dorim să atragem atenţia aici.
Enunţurile problemelor dificile sau foarte dificile de informatică este, în 99% din
cazuri, foarte simplu şi poate fi citit şi înţeles de oricine. Acest fapt consituie o capcană sigură
pentru începători. Dacă în matematică lucrurile nu stau aşa, asta se datorează numai faptului
că studiul matematicii are vechime şi problemele, împreună cu dificultăţile lor, sînt ceva mai
bine cunoscute. În informatică nu avem însă aceeaşi situaţie. Ba chiar se întîmplă că probleme
foarte dificile sînt amestecate în culegerile de probleme de informatică printre probleme
uşoare.
Acest paragraf îşi propune să pună în gardă în privinţa dificultăţii problemelor oferind
o mică iniţiere în acest domeniu şi să umple lacuna ce mai există încă la ora actuală în cultura
de specialitate.
Dificultatea problemelor de programare a căror enunţuri urmează este considerată
maximă de teoreticienii informaticii (ele se numesc probleme NP-complete). Este foarte
posibil să existe aceste probleme în unele culegeri de programare. Ele sînt depăşite în
dificultate doar de problemele insolvabile algoritmic.
Spre deosebire de matematică, dificultatea problemelor de informatică nu este dată de
faptul că nu se cunoaşte un algoritm de rezolvare a lor, ci datorită faptului că nu se cunoaşte
un algoritm eficient de rezolvare a lor. Existenţa unei metode de proiectare a algoritmilor atît
de general valabilă, cum este metoda back-tracking, face ca prea puţine probleme cu care ne
putem întîlni să nu aibă o soluţie. Dar, întrucît în cazul metodei back-tracking, căutarea
soluţiei se face într-un mod exhaustiv (se caută peste tot, pentru ca să fim siguri că nu lăsăm
nici o posibilitate neexplorată), durata căutării are o creştere exponenţial-proporţională cu
dimesiunea datelor de intrare. De exemplu, timpul de căutare care depinde de valoarea de
intrare n poate avea o expresie de forma T(n)=c⋅2n secunde, unde c este un factor de
proporţionalitate ce poate varia, să zicem, de la c=62.8 cînd algoritmul este executat pe un
calculator sau c=12.5 cînd el este rulat pe un calculator de cinci ori mai performant. Dar,
indiferent de calculator, pentru n=100 avem 2100=(210)10≈(103)10=1030, deci timpul măsurat în
secunde are ordinul de mărime mai mare de 30. Cea mai largă estimare pentru vîrsta
Universului nostru nu depăşeşte 20 mildiarde de ani ceea ce transformat în secunde conduce
la un ordin de mărime mai mic de 20. Deci, chiar şi pentru valori mici ale lui n (de ordinul
sutelor) am avea de aşteptat pentru găsirea soluţiei de 10 miliarde de ori mai mult decît a
trecut de la Big Bang încoace. Se pune problema dacă în această situaţie pot fi considerate
astfel de programe ca rezolvări rezonabile, doar pentru că ele găsesc soluţia în cazurile în care
n=2, 3, 4, …, 10 ?
Exemplele următoare sînt doar cîteva, cele mai uzuale, dintr-o listă cunoscută ce
conţine la ora actuală peste şase sute de astfel de probleme. Pentru fiecare dintre aceste
probleme nu se cunosc alte soluţii decît inutilii algoritmi de gen back-tracking. În listă apare
des noţiunea de graf, aşa că o vom introduce în continuare cît mai simplu cu putinţă: printr-un
graf se înţelege o mulţime de vîrfuri şi o mulţime de muchii care unesc unele vîrfuri între ele.
Orice hartă rutieră, feroviară sau de trafic aerian schematizată reprezintă desenul unui graf.
14
2. Problema rucsacului. Avem un rucsac de capacitate întreagă pozitivă C şi n obiecte cu
dimensiunile d1, d2, …, dn şi avînd asociate profiturile p1, p2, …, pn (în caz că ajung în
rucsac). Se cere să se determine profitul maxim ce se poate obţine prin încărcarea
rucsacului (fără ai depăşi capacitatea).
3. Problema colorării grafului. Să se determine numărul minim de culori (numărul
cromatic) necesar pentru colorarea unui graf astfel încît oricare două vîrfuri unite printr-o
muchie (adiacente) să aibă culori diferite.
4. Problema împachetării. Presupunînd că dispunem de un număr suficient de mare de cutii
fiecare avînd capacitatea 1 şi n obiecte cu dimensiunile d1, d2, …, dn, cu 0<di<1, se cere să
se determine numărul optim (cel mai mic) de cutii necesar pentru împachetarea tutror
celor n obiecte.
5. Problema comisului voiajor. (varianta simplificată) Dîndu-se o hartă (un graf), se cere să
se găsească un circuit (un şir de muchii înlănţuite) care trece prin fiecare vîrf o singură
dată.
15
Probleme nesoluţionate încă
Aşa cum s-a putut constata în paragraful anterior, există multe probleme în informatică
pentru care încă nu se cunosc soluţii eficiente. În continuare vom oferi o listă de probleme
nesoluţionate încă. De fapt, ele apar mai ales în matematică, fiind cunoscute sub numele de
conjecturi, şi au toate ca specific un fapt care este de mare interes pentru programatori.
Incertitudinea asupra lor ar putea fi definitiv înlăturată nu numai prin demonstraţie
matematică ci şi cu ajutorul formidabilei puteri de calcul a computerelor. Astfel, fiecare din
aceste conjecturi numerice ar putea fi infirmată (concluzia ar fi atunci că respectiva
conjectură este falsă) dacă i s-ar găsi un contraexemplu. Este necesar doar să se găsească un
set de numere pentru care propoziţia respectivă să fie falsă. Ori, acest efort nu este la
îndemâna niciunui matematician dar este posibil pentru un programator. El nu are decît să
scrie un program eficient şi să pună calculatorul să caute un contra-exemplu.
Fiecare problemă conţine aceeaşi capcană ca şi în problemele paragrafului anterior:
algoritmii de căutare a contra-exemplelor pot fi concepuţi rapid, relativ simpli şi cu efort de
programare redus (de exemplu, prin trei-patru cicluri for imbricate sau printr-o soluţie gen
back-tracking) dar ei vor deveni în scurt timp total ineficienţi şi vor conduce la programe mari
consumatoare de timp. De aceea, problemele din acest paragraf trebuie tratate cu multă
atenţie. Abordarea acestui tip de probleme cere din partea programatorului un anumit grad de
măiestrie.
Rezolvînd numai una dintre ele veţi fi recompensaţi pe măsură: riscaţi să deveniţi celebri!
1. Conjectura lui Catalan. Singurele puteri naturale succesive sînt 8=23 şi 9=32.
16
3. Problema umplerii pătratului unitate. Întrebare: este posibil ca mulţimea
dreptunghiurilor de forma 1/k x 1/(k+1), pentru fiecare k întreg pozitiv, să umple în
întregime şi fără suprapuneri pătratul unitate, de latură 1x1 ?
Observaţie: este evident că suma infinită a ariilor dreptunghiurilor este egală cu aria pătratului
unitate. Avem Σk>01/(k(k+1))=Σk>0(1/k-1/(k+1))=1.
Comentariu: aparent, descoperirea dezvoltărilor în serie pare să fi plecat de la unele evidente
propietăţi geometrice, uşor de sesizat chiar din desene simple în care valorilor numerice li se
asociază segmente de lungimi corespunzătoare. Iată însă o surpriză în această situaţie: suma
seriei numerice este evidentă analitic însă reprezentarea geometrică a "fenomenului" este
"imposibilă".
4. Conjectura fracţiilor egiptene (atribuită lui Erdös şi Graham). Orice fracţie de forma
4/n se descompune ca sumă de trei fracţii egiptene (de forma 1/x).
Observaţie: într-o exprimare matematic riguroasă, pentru orice n natural există trei valori
naturale, nu neapărat distincte, x, y, şi z astfel încît 4/n=1/x+1/y+1/z.
Comentariu: este încă un mister motivul pentru care egiptenii preferau descompunerea
facţiilor numai ca sumă de fracţii egiptene. Descoperiseră ei această descompunere minimală
a fracţiilor de forma 4/n ? Dar mai ales, ce procese fizice reale erau astfel mai bine modelate ?
Înclinăm să credem că există o legătură între fenomenele fizice ondulatorii, transformata
Fourier şi fracţiile egiptene.
Observaţie: dacă considerăm un pătrat unitate avînd vîrfurile de coordonate (0,0), (1,0), (0,1)
şi (1,1) atunci se cere găsirea unui punct (x,y) astfel încît x2+y2, (x-1)2+y2, x2+(y-1)2 şi (x-
1)2+(y-1)2 să fie toate patru pătrate perfecte. Atenţie, x şi y nu este obligatoriu să fie întregi.
Acest fapt ridică foarte serioase probleme la proiectarea unui algoritm de căutare a unui astfel
de punct (x,y).
Comentariu: la fel ca şi în cazul cutiei raţionale, se pare că există limitări serioase şi
neaşteptate în încercarea de optimizare a numărului de subansamble necesare pentru
construierea scheletelor sau cadrelor de susţinere. Se pare că cele două dimensiuni pe care
geometria plană se bazează conduce la o complexitate inerentă neaşteptat de mare.
Observaţie: se cere să se spună către ce valoare converge seria Σk>01/k3 sau Σk>0k-3. Se ştie că
în cazul în care în locul puterii a 3-a punem puterea a 2-a seria converge la π 2/6, în cazul în
care în locul puterii a 3-a punem puterea a 4-a seria converge la π 4/90.
Comentariu: deşi pare a fi o problemă de analiză matematică pură deoarece ni se cere să
găsim expresia sintetică şi nu cea numerică aproximativă a sumei seriei, există însă uluitoare
descoperiri asemănătoare ale unor formule de analiză numerică sau chiar dezvoltări în serie
(cea mai celebră fiind cea a cifrelor hexazecimale ale lui π) făcute cu ajutorul calculatorului
prin calcul simbolic.
17
Observaţie: Se cunoaşte că în cazul în care puterea este 3 avem soluţia: 13+123=93+103 iar în
cazul în care puterea este 4 avem soluţia: 1334+1344=594+1584 .
Comentariu: căutarea unor algoritmi generali de rezolvare a ecuaţiilor diofantice a condus la
importante descoperiri în matematică dar şi în informatică. De exemplu, celebrul
matematician Pierre Fermat, “stîrnit” fiind de problemele conţinute în lucrarea Arithmetika a
matematicianului antic Diofant din Alexandria (de unde şi numele ecuaţiilor diofantice), a
descoperit în 1637 faimoasa sa teoremă: Ecuaţia diofantică xn+yn=zn nu admite soluţie pentru
n>2. Dar tot în aceeaşi perioadă a descoperit şi faptul că cea mai mică soluţie a ecuaţiei
diofantice x2 - 109*y2 = 1 este perechea x=158 070 671 986 249 şi y= 15 140 424 455 100.
Dacă încercăm doar să verificăm această soluţie fără ajutorul calculatorului ne putem da
seama de performanţele pe care le-a realizat Fermat. În informatică este acum cunoscut şi
demonstrat că este imposibil să se construiască un algoritm general pentru rezolvarea
ecuaţiilor diofantice.
8. Problema celor 13 oraşe. Puteţi localiza 13 oraşe pe o planetă sferică astfel încît distanţa
minimă dintre oricare două dintre ele să fie cît mai mare cu putinţă ?
9. Conjectura lui Collatz. Se pleacă de la un n întreg pozitiv. Dacă n este par se împarte la
doi; dacă n este impar se înmulţeşte cu trei şi i se adună unu. Repetînd în mod
corespunzător doar aceşti doi paşi se va ajunge întotdeauna la 1 indiferent de la ce valoare
n se porneşte ?
Observaţie: de exemplu, pornind de la n=6 obţinem în 8 paşi şirul valorilor: 6, 3, 10, 5, 16, 8,
4, 2, 1.
Comentariu: valoarea finală 1 este ca o "gaură neagră" care absoarbe în final şirul obţinut.
"Raza" de-a lungul căreia are loc "căderea" în gaura neagră 1 este dată mereu de şirul
puterilor lui 2: 2, 4, 8, 16, 32, 64, … cu ultima valoare de forma 3k+1, adică 4, 16, 64, 256,
…. Se pare că valorile obţinute prin cele două operaţii nu pot "să nu dea" nicicum peste acest
şir care le va face apoi să "cadă în gaura neagră" 1.
10. Problema înscrierii pătratului. Dîndu-se o curbă simplă închisă în plan, vom putea
întotdeauna găsi patru puncte pe curbă care pot să constituie vîrfurile unui pătrat ?
18
Observaţie: în cazul curbelor închise regulate (ce au axe de simetrie: cerc, elipsă, ovoid) nu
este greu de arătat prin construire efectivă că există un pătrat ce se "sprijină" pe curbă. Pare
însă de nedovedit acelaşi fapt în cazul unor curbe închise foarte neregulate. Găsirea celor
patru puncte, într-o astfel de situaţie, este de neimaginat fără ajutorul calculatorului.
Comentariu: o consecinţă surprinzătoare a acestei conjecturi este faptul că pe orice curbă de
nivel (curbă din teren care uneşte punctele aflate toate la aceaşi altitudine) am putea găsi patru
puncte de sprijin pentru o platformă pătrată (un fel de masă) perfect orizontală, de mărime
corespunzătoare. Acest fapt ar putea să explice ampla răspîndire a meselor cu patru picioare în
detrimentul celor cu trei: dacă îi cauţi poziţia, cu siguranţă o vei găsi şi o vei putea aşeza pe
toate cele patru picioare, astfel masa cu patru picioare va oferi o perfectă stabilitate şi va sta
perfect orizontală, pe cînd cea cu trei picioare deşi stă acolo unde o pui din prima (chiar şi
înclinată) nu oferă aceeaşi stabilitate.
19
Probleme insolvabile algoritmic
Acest paragraf special are ca scop trezirea interesului şi pasiunii pentru informatică
celor care pot acum să vadă cît de deosebite sînt descoperirile şi rezultatele din acest domeniu,
precum şi punerea în gardă a celor care, în entuziasmul lor exagerat, îşi închipuie că pot
programa calculatorul să facă orice treabă sau să rezolve orice problemă. Aşa cum am văzut şi
în paragraful ce prezintă problemele dificile ale informaticii, enunţurile problemelor foarte
dificile sau chiar insolvabile sînt foarte simple şi pot uşor constitui o capcană pentru
necunoscători.
Comentariu: acesta este cel dintîi şi cel mai celebru exemplu de problemă insolvabilă.
Demonstraţia riguroasă a acestui fapt a fost dată pentru prima dată în 1936 de inventatorul
calculatorului actual matematicianul englez Alan Mathison Turing. Odată existînd această
demonstraţie, multe din următoarele probleme insolvabile algoritmic s-au redus la aceasta.
Implicaţiile practice, teoretice şi filozofice ale problemei Stopului sînt foarte importante atît
pentru informatică cît şi pentru matematică. Astfel, două consecinţe strategice ale problemei
Stopului sînt:
1. nu poate exista un calculator oricît de puternic cu ajutorul căruia să se poată decide asupra
comportamentului viitor al oricărui alt calculator de pe glob;
2. nu poate să existe în matematică o metodă generală de demonstrare inductivă-logică a
propoziţiilor matematice (se închide în acest fel o mai veche căutare a matematicienilor şi
logicienilor cunoscută sub numele de Entscheidungs Problem sau Problema deciziei).
Comentariu: sistemele de ecuaţii diofantice sînt sistemele de ecuaţii algebrice de mai multe
variabile cu coeficienţi întregi şi cărora li se caută soluţii întregi. De exemplu, a fost nevoie de
ajutorul calculatorului pentru a se descoperi cea mai mică soluţie a ecuaţiei diofantice
p4+q4+r4=s4 şi care este cvadrupletul p=95600, q=217519, r=414560, s=422461
(infirmîndu-se în acest fel "conjectura" lui Leonard Euler care în 1796 a presupus că această
ecuaţie diofantică nu are soluţii întregi). Această problemă ce cere o metodă generală de
rezolvare a ecuaţiilor diofantice este cunoscută sub denumirea de Problema a 10-a a lui
Hilbert.
3. Problema acoperirii planului (Problema pavajului sau Problema croirii). Fiind dată o
mulţime de forme poligonale, nu există o metodă generală (un algoritm) care să decidă
dacă utilizând aceste forme este posibilă acoperirea completă a planului (fără suprapuneri
şi goluri).
Comentariu: în practică este mult mai importantă problema croirii care cere să se decupeze
fără pierderi un set cît mai mare de forme date (croiuri) dintr-o bucată iniţială de material
oricît de mare. Este, de asemenea, demonstrat că problema rămîne insolvabilă algoritmic chiar
şi atunci cînd formele poligonale sînt reduse la poliomine (un fel de "mozaicuri") care se
formează doar pe o reţea rectangulară caroiată. Iată cîteva exemple de mulţimi formate dintr-o
20
singură poliomină şi, alăturat, răspunsul la întrebarea dacă cu ele se poate acoperi planul sau
nu:
DA NU DA
4. Problema şirurilor lui Post. Se dau două mulţimi egale de şiruri finite de simboluri ce
sînt împerecheate astfel: un şir dintr-o mulţime cu şirul corespunzător din a doua mulţime.
Nu există un algoritm general prin care să se decidă dacă există o ordine de concatenare
a şirurilor (simultan din cele două mulţimi) astfel încît cele două şiruri lungi pereche
rezultate să fie identice.
Comentariu: de exemplu, fie A={ 101, 010, 00 } şi B={ 010, 10, 001 } cele două mulţimi de
şiruri de simboluri (pentru uşurinţă au fost alese simbolurile binare 1 şi 0). Perechile
corespunzătoare de şiruri sînt 1.(101,010), 2.(010,10) şi 3.(00,001). Observăm că şirurile
pereche pot avea lungimi diferite (ca în perechile 2 şi 3). În continuare, pentru a vedea cum se
procedează, cele două şiruri pereche rezultante prin concatenare le vom scrie unul deasupra
celuilalt sesizînd cum avansează procesul de egalizare a lor. Punctele sînt intercalate doar
pentru a evidenţia perechile, ele nu contribuie la egalitate:
00. Concatenarea poate începe doar cu 00.101. Obligatoriu urmează perechea 1-a
001. perechea a 3-a,00 de "sus" ⊂ 001 de "jos" 001.010. singura care începe cu 1 "sus".
00.101.00. Dacă am continua cu perechea 00.101.010 … nu s-ar obţine rezultatul
001.010.001. a 3-a … 001.010.10 final oferit de perechea 2-a !
Comentariu: de exemplu, fie următoarele cinci egalităţi (citiţi-le în limba engleză) EAT=AT,
ATE=A, LATER=LOW, PAN=PILLOW şi CARP=ME. Este CATERPILLAR egal cu
MAN? Iată şirul egalităţilor iterate care ne poate oferi răspunsul: CATERPILLAR =
CARPILLAR =CARPILLATER =CARPILLOW= CARPAN= MEAN= MEATEN=
MATEN= MAN.
Dar de la CARPET putem ajunge la MEAT? Întrucît se vede că numărul total de A-uri plus
W-u ri şi M-uri nu se poate modifica prin nici o substituţie şi întrucît CARPET are un A
(adică numărul asociat este 1) iar MEAT are un A şi un M (deci 2), rezultă că această egalitate
nu este permisă.
Mai mult, se ştie că există liste particulare de cuvinte pentru care nu poate exista un algoritm
ce decide dacă două cuvinte sînt egale sau nu. Iată o astfel de listă de şapte egalităţi: AH=HA,
OH=HO, AT=TA, OT=TO, TAI=IT, HOI=IH şi THAT=ITHT.
21
numele de probleme nedecidabile sau probleme nerecursive, în enunţul lor cuvîntul algoritm
fiind înlocuit mai ales cu cuvintele metodă generală.
Studierea acestui domeniu a creat condiţii pentru apariţia de noi direcţii de cercetare
prin care se încearcă explicarea raţionamentelor matematice ba chiar se încearcă descoperirea
limitelor raţiunii umane în general. Unii oameni de ştiinţă contemporani, cum este celebrul
matematician-fizician englez Roger Penrose, depun eforturi mari pentru a oferi o
demonstraţie matematică riguroasă pentru ipoteza că, în cele din urmă şi în esenţă,
raţionamentele umane nu sînt algoritmice, nici măcar cele matematice. După părerea lui
Penrose mintea umană nu poate fi asimilată cu un calculator ci este mai mult decît atît şi nu
vor putea exista vreodată calculatoare sau roboţi mai inteligenţi decît oamenii.
22
Capitolul 3
DIVIDE ET IMPERA
Căutare binară
Problema căutării unei valori particulare într-un şir de elemente este o
problemă fundamentală în proiectarea algoritmilor. Această problemă în cazul general
poate fi formulată după cum urmează: „Să se determine dacă numărul x este în şirul S
de n elemente. Dacă da să se specifice poziţia în şir, dacă nu poziţia va fi 0.”
Pentru rezolvarea acestei probleme cea mai simplă abordare este căutarea
secvenţială, care începând de la primul element din şirul S, îl compară succesiv pe x
cu fiecare element al şirului S până se găseşte un element egal cu x, sau până când se
termină elementele din S. Daca x este găsit se memorează poziţia iar dacă nu este
găsit se iniţializează poziţia egală cu 0. Acest algoritm nu necesită ca elementele
şirului S să fie ordonate. În continuare se prezintă algoritmul descris mai sus.
cautare_secventiala;
BEGIN
var n,S(1..n),x,pozitie:int;
read n, S(1..n), x;
pozitie:=1;
while pozitie<=n AND S(pozitie)<>x
pozitie:=pozitie+1;
if pozitie>n then pozitie:=0;
write pozitie;
END
O abordare mult mai eficientă, mai ales ca timp de execuţie în cazul şirurilor
cu număr foarte mare de elemente, este căutarea binară. Aceasta se poate aplica pentru
şiruri S ordonate, să presupunem în ordine crescătoare. Cu modificări minore se poate
realiza algoritmul pentru cazul şirului S ordonat în ordine descrescătoare.
Prezentat în cuvinte, acest algoritm de căutare binară la început compară pe x
cu elementul din mijlocul şirului. Dacă sunt egale, atunci am găsit poziţia lui x şi
algoritmul se termină. Dacă x este mai mic decât elementul din mijlocul şirului S
atunci înseamnă că x ar putea să fie în prima jumătate (din stânga) a şirului şi
algoritmul continuă căutarea în acelaşi mod în prima jumătate a şirului (adică, x este
comparat cu elementul de la mijlocul subşirului. Dacă este egal, poziţia lui x a fost
găsită, dacă nu se continuă.). Dacă x este mai mare decât elementul din mijlocul
şirului, atunci x ar putea fi în a doua jumătate (din dreapta) a şirului. Aceşti paşi sunt
repetaţi, înjumătăţind treptat şirul S, până când se găseşte poziţia lui x în şir sau până
când se determină că x nu face parte din S. În continuare se prezintă scris în
pseudocod algoritmul descris mai sus.
cautare_binara;
BEGIN
var n,S(1..n),x,pozitie,low,high,mid:int;
read n, S(1..n), x;
low:=1; high:=n;
pozitie:=0;
while low<=high AND pozitie=0
begin
mid:=int((low+high)/2);
if x=S(mid) then pozitie:=mid
else if x<S(mid) then high:=mid-1
else low:=mid+1;
end;
write pozitie;
END
2
comparaţii pentru a afla că x nu este într-un şir de dimensiune n. Pe de altă parte, dacă
x este în şirul S, numărul de comparaţii este mai mic sau egal cu n.
În algoritmul de căutare binară există două comparaţii între x şi S(mid) în
fiecare pas al buclei while (cu excepţia cazului în care se găseşte x egal cu S(mid)).
Dacă se implementează acest algoritm în limbaj de asamblare, x va fi comparat o
singură dată cu S(mid) în fiecare pas, deoarece această comparaţie setează indicatorii
de condiţie şi pe baza valorii acestora se va realiza ramificarea în program (pentru
egal, mai mic şi mai mare). Vom presupune în continuare că algoritmul este
implementat astfel încât în fiecare pas al buclei să se realizeze o singură comparaţie
între x şi S(mid). În fig.3.1 se vede că algoritmul realizează 6 ( 6 = log 2 32 + 1 )
comparaţii pentru şirul S de 32 de elemente şi x mai mare decât toate elementele
şirului. Acesta este numărul maxim de comparaţii pe care le realizează algoritmul.
Indiferent dacă x este în şir, dacă x este mai mic decât toate elementele şirului sau
dacă x este între două elemente ale şirului nu se vor realiza mai mult de 6 comparaţii.
3
Căutarea binară este cel mai simplu algoritm de tip divide-et-impera deoarece
problema se împarte într-o singură problemă mai mică, deci nu trebuie realizată
combinarea soluţiilor. Soluţia problemei iniţiale este aceeaşi cu soluţia problemei mai
mici. De exemplu, să considerăm următoarele valori:
n=13
x=18
S=(10 12 13 14 18 20 25 27 30 35 40 45 47)
În fig.3.2 se prezintă schematic paşii căutării numărului x în şirul S prin
algoritmul de căutare binară.
Cautare_binara;
Var n,x,S(1..n):int;
Function pozitie(low,high:int):int;
begin
if low>high then return 0
else
begin
mid:=int((low+high)/2);
if x=S(mid) then return mid
else if x<S(mid) then return pozitie(low,mid-1)
else return pozitie(mid+1,high);
end;
end;
BEGIN
Read n,x,S(n);
Write pozitie(1,n);
END
Între cele două implementări ale căutării binare, şi anume iterativă respectiv
recursivă, nu există nici o diferenţă de conţinut. Algoritmul este identic.
Implementarea recursivă ilustrează foarte bine principiul metodei divide-et-impera. În
4
schimb, implementarea iterativă este mai economică din punctul de vedere al
memoriei necesare. Aceasta deoarece implementarea recursivă necesită un spaţiu de
memorie suplimentar alocat stivei în care se salvează rezultatele parţiale la fiecare
reapelare a funcţiei. Acest spaţiu de memorie poate deveni destul mare, atunci când
avea de-a face cu şiruri cu foarte multe elemente.
5
Intrări: numărul întreg pozitiv n, şirul S cu n elemente.
Ieşiri: şirul S cu elementele ordonate crescător.
Ordonare_Mergesort;
Var n,S(1..n):int;
procedure mergesort(n:int,S(1..n):int);
begin
if n>1 then
begin
h:=int(n/2);
m:=n-h;
U(1..h), V(1..m):int;
Copy S(1..h) in U(1..h);
Copy S(h+1..n) in V(1..m);
mergesort(h,U);
mergesort(m,V);
merge(h,m,U,V,S);
end;
end;
procedure merge(h,m:int,U(),V(),S():int);
begin
var i,j,k:int;
i:=1;
j:=1;
k:=1;
while i=h AND j<=m
begin
if U(i)<V(j) then
begin
S(k):=U(i);
i:=i+1;
end;
else
begin
S(k):=V(j);
j:=j+1;
end;
k:=k+1;
end;
if i>h then Copy V(j..m) in S(k..h+m)
else Copy U(i..h) in S(k..h+m)
end;
BEGIN
Read n,S(1..n);
Mergesort(n,S(1..n));
Write S(1..n);
END
6
Acest algoritm de sortare se poate realiza şi în varianta numită in-place sort.
Această variantă nu utilizează spaţiu suplimentar de memorie faţă de cel necesar
pentru stocarea şirului de intrare. Algoritmul de mai sus nu este de tipul in-place sort
deoarece foloseşte şirurile U şi V pe lângă şirul de intrare S.
Algoritmul de sortare de tip in-place sort realizează majoritatea manipulărilor
de date în şirul de intrare S. În continuare se prezintă acest algoritm care foloseşte o
abordare similară algoritmului recursiv de căutare binară.
Ordonare_Mergesort2;
Var n,S(1..n):int;
procedure mergesort2(low,high:int);
begin
var mid:int;
if low<high then
begin
mid:=int((low+high)/2);
mergesort2(low,mid);
mergesort2(mid+2,high);
merge2(low,mid,high);
end;
end;
procedure merge2(low,mid,high:int);
begin
var i,j,kU(low..high):int; şirul U este necesar pentru concatenare
i:=low;
j:=mid+1;
k:=low;
while i=mid AND j=high
begin
if S(i)<S(j) then
begin
U(k):=S(i);
i:=i+1;
end;
else
begin
U(k):=S(j);
j:=j+1;
end;
k:=k+1;
end;
if i>mid then Copy S(j..high) in U(k..high)
else Copy S(i..mid) in U(k..high);
Copy U(low..high) in S(low..high);
end;
7
BEGIN
Read n,S(1..n);
Mergesort2(1,n);
Write S(1..n);
END
După împărţire avem în stânga elementului pivor toate elementelor din şir mai
mici decât acesta, iar în dreapta lui toate elementele mai mari. Quicksort este apoi
apelat în mod recursiv pentru sortarea fiecărei părţi. Acest procedeu continuă până ce
se obţine un şir cu un singur element. Acest şir este deja sortat.
În continuare se prezintă algoritmul Quicksort scris în pseudocod:
Ordonare_Quicksort;
Var n,S(1..n):int;
procedure quicksort(low,high:int);
begin
var pivotpoint:int;
if high>low then
begin
partition(low,high,pivotpoint);
quicksort(low,pivotpoint-1);
quicksort(pivotpoint+1,high);
end;
end;
8
procedure partition(low,high,pivotpoint:int);
begin
var i,j:int, pivotitem:int;
pivotitem:=S(low); Alege primul element din S ca pivot
j:=low;
for i=low+1 to high
if S(i)<pivotitem then
begin
j:=j+1;
Exchange S(i) and S(j);
end;
pivotpoint:=j;
Exchange S(low) and S(pivotpoint); Pune pivotitem în pivotpoint
end;
BEGIN
Read n,S(1..n);
Quicksort(1,n);
Write S(1..n);
END
9
PROBLEME PROPUSE
10
Capitolul 4
PROGRAMARE DINAMICĂ
sirul_Fibonacci;
function fib(n:int):int;
begin
if n<=1 then return n
else return fib(n-1)+fib(n-2);
end;
BEGIN
var n:int;
read n;
write fib(n);
END
Programarea dinamică are o abordare complet diferită. Singura asemănare dintre
programarea dinamică şi metoda divide-et-impera este faptul că o problemă este împărţită în
probleme mai mici. Însă, în cazul programării dinamice se rezolvă problemele mai mici, se
stochează rezultatele şi, mai târziu, ori de câte ori este necesar un rezultat, acesta se preia şi nu
se recalculează. Termenul de programare dinamică vine din teoria sistemelor şi aici prin
programare se înţelege utilizarea unui tablou în care se construieşte soluţia.
Un exemplu de calcul a celui de al n-lea termen al şirului lui Fibonacci prin metoda
programării dinamice este prezentat în continuare. Pentru calculul termenului al n-lea al
şirului se construieşte un şir f cu primii n+1 termeni ai şirului indexaţi de la 0 la n.
sirul_Fibonacci;
function fib2(n:int):int;
begin
var i,f(0..n):int;
f(0):=0;
if n>0 then
begin
f(1):=1;
for i:=2 to n
f(i):=f(i-1)+f(i-2);
end;
return f(n)
end;
BEGIN
var n:int;
read n;
write fib2(n);
END
Într-un algoritm realizat prin programare dinamică, construim într-o matrice (sau
succesiune de matrici) o soluţie de jos în sus. Programarea dinamică realizează aşadar o
abordare bottom-up. Uneori, după ce realizăm algoritmul utilizând o matrice (sau secvenţă de
matrici) îl putem optimiza deoarece mare parte din spaţiul de memorie alocat iniţial nu este
necesar.
Paşii în dezvoltarea unui algoritm prin programare dinamică sunt următorii:
1. Stabilirea unei proprietăţi recursive care dă soluţia pentru problemă.
2. Rezolvarea problemei prin metoda bottom-up, rezolvând mai întâi
subproblemele mai mici.
4.1. Algoritmul lui Floyd pentru determinarea celui mai scurt drum
O problemă foarte des întâlnită de cei care călătoresc mult cu avionul este
determinarea celui mai scurt traseu pentru a ajunge dintr-un oraş în altul atunci când nu există
un zbor direct. Vom prezenta în continuare un algoritm care rezolvă această problemă precum
şi altele similare.
Pentru început vom aminti câteva noţiuni din teoria grafurilor. În fig.4.1 se prezintă un
graf orientat şi marcat.
2
Fig.4.1. Un graf orientat şi marcat
O problemă care are multe aplicaţii este găsirea celor mai scurte căi de la fiecare nod
la toate celelalte. Evident, cea mai scurtă cale trebuie să fie o cale simplă. În fig.4.1 există trei
căi simple de la v1 la v3, şi anume: [v1, v2, v3], [v1, v4, v3] şi [v1, v2, v4, v3]. Deoarece:
lungime[v1, v2, v3]=1+3=4
lungime[v1, v4, v3]=1+2=3
lungime[v1, v2, v4, v3]=1+2+2=5
[v1, v4, v3] este cea mai scurtă cale de la v1 la v3. Aşa cum am mai spus, o aplicaţie uzuală a
celor mai scurte căi este găsirea celor mai scurte drumuri între două oraşe.
Problema celor mai scurte căi este o problemă de optimizare. Într-o problemă de
optimizare pot exista mai multe soluţii candidat. Fiecare soluţie candidat are asociată o
valoare şi o soluţie a problemei este orice soluţie candidat care are o valoare optimă. În
funcţie de problemă, valoarea optimă este fie cea minimă fie cea maximă. În cazul problemi
celor mai scurte căi, o soluţie candidat este o cale de la un nod la altul, valoarea este lungimea
căii şi valoarea optimă este minimul acestor lungimi.
Deoarece pot exista mai multe căi de la un nod la altul care să fie cele mai scurte,
problema este să se găsească oricare dintre cele mai scurte căi. Un algoritm evident pentru
această problemă ar fi să se determine, pentru fiecare nod, lungimile tuturor căilor de la acel
nod la toate celelalte şi să se calculeze apoi minimul acestor lungimi. Dar acest algoritm este
foarte mare consumator de timp. Daca ar exista câte un arc de la fiecare nod la toate celelalte,
am obţine un număr foarte mare de căi posibile. Această metodă de soluţionare nu este
acceptabilă. Trebuie să găsim un algoritm mai eficient.
3
Folosind programarea dinamică, vom crea un algoritm pentru problema celor mai
scurte drumuri. Mai întâi dezvoltăm un algoritm care determină numai lungimile celor mai
scurte drumuri. După aceasta îl modificăm pentru a produce şi cele mai scurte căi. Vom
reprezenta un graf marcat având n noduri printr-o matrice W după cum urmează:
marcajul de pe arc, daca exista un arc de la v i la v j
W (i, j) = ∞, daca nu exista un arc de la v i la v j
0, daca i = j
Deoarece se spune că nodul vi este adiacent nodului vj dacă există un arc de la vi la vj,
această matrice se numeşte matricea adiacenţelor. Graful din fig.4.1 este reprezentat prin
matricea adiacenţelor după cum urmează:
0 1 ∞ 1 5
9 0 3 2 ∞
W = ∞ ∞ 0 4 ∞
∞ ∞ 2 0 3
3 ∞ ∞ ∞ 0
Vom defini acum o matrice D, de forma de mai jos, care conţine lungimile celor mai
scurte căi din graf.
0 1 3 1 4
8 0 3 2 5
D = 10 11 0 4 7
6 7 2 0 3
3 4 6 4 0
De exemplu, D(3,5)=7 deoarece 7 este lungimea unei celei mai scurte căi de la v3 la
v5. Dacă putem găsi o modalitate de calcul a valorilor din D pe baza celor din W, vom avea un
algoritm pentru probleme celor mai scurte drumuri. Vom realiza aceasta prin calculul unei
serii de n+1 matrici D(k), unde 0<k<n şi unde:
D (k ) (i, j) = lungimea unei celei mai scurte cai de la v i la v j utilizand numai nodurile din
multimea {v1 , v 2 ,..., v k }ca noduri int ermediare
Înainte de a demonstra corectitudinea acestui algoritm, vom determina câteva valori
(k)
prin D (i,j) pentru graful din fig.4.1.
D (0 ) (2,5) = lungimea[v 2 , v 5 ] = ∞
D (1) (2,5) = min (lungimea[v 2 , v 5 ], lungimea[v 2 , v1 , v 5 ]) = min (∞,14 ) = 14
D (2 ) (2,5) = D (1) (2,5) = 14 Pentru orice graf acestea sunt egale deoarece o cea mai
scurtă cale care porneşte din v2 nu poate trece tot prin
v2.
D (2,5) = D (2,5) = 14
(3 ) (2 )
Pentru acest graf acestea sunt egale deoarece
incluzându-l pe v3 nu se produce nici o cale nouă de la
v2 la v5.
Ultima valoare care se calculează, D(5)(2,5) este lungimea celui mai scurt drum de la
v2 la v5 care poate terce prin oricare alte noduri. Adică aceasta este cea mai scurtă cale.
Deoarece D(n)(i,j) este lungimea celei mai scurte căi de la vi la vj care poate trece prin
oricare alte noduri, aceasta va fi lungimea celei mai scurte căi de la vi la vj. Deoarece D(0)(i,j)
4
este lungimea unei cele mai scurte căi de la vi la vj care nu poate trece prin nici un alt nod,
aceasta este de fapt lungimea arcului de la vi la vj. Prin aceste două afirmaţii am stabilit că:
D (0 ) = W şi D (n ) = D .
Aşadar, pentru a determina pe D din W trebuie doar să obţinem D(n) din D(0). Paşii
necesari în programarea dinamică pentru aceasta sunt:
1. Stabilirea unei prorietăţi (proces) recursive cu care putem calcula D(k) din D(k-1).
2. Rezolvarea unei subprobleme în stilul bottom-up (de jos în sus) prin repetarea
procesului (stabilit în pasul 1) pentru k=1 .. n. Aceasta va conduce la secvenţa:
D , D1 , D 2 ,..., D n .
0
↑ ↑
W D
5
Deoarece singurele cazuri posibile sunt cele două descrise mai sus, înseamnă că
valoarea lui D (k ) (i, j) este minimul valorilor din cele două cazuri. Aceasta înseamnă că putem
determina pe D(k) din D(k-1) după cum urmează:
[ ]
D (k ) (i, j) = min D (k −1) (i, j), D (k −1) (i, k ) + D (k −1) (k , j)
cazul1 cazul 2
Am realizat astfel pasul 1 din algoritmul dezvoltat prin programare dinamică. Pentru
pasul 2 folosim recursivitarea din pasul 1 pentru a crea secvenţa de matrici. În continuare vom
prezenta cum se calculează fiecare din aceste matrici din precedentele pentru cazul grafului
din fig.4.1.
D (0 ) = W
[ ]
D (1) (2,4) = min D (0 ) (2,4 ), D (0 ) (2,1) + D (0 ) (1,4 ) = min[2,9 + 1] = 2
[ ]
D (1) (5,2 ) = min D (0 ) (5,2 ), D (0 ) (5,1) + D (0 ) (1,2 ) = min[∞,3 + 1] = 4
[ ]
D (1) (5,4 ) = min D (0 ) (5,4 ), D (0 ) (5,1) + D (0 ) (1,4 ) = min[∞,3 + 1] = 4
După ce se calculează întreaga matrice D(1), se poate calcula şi matricea D(2). De
exemplu, pentru matricea D(2) un element este:
[ ]
D (2 ) (5,4 ) = min D (1) (5,4 ), D (1) (5,2 ) + D (1) (2,4 ) = min[4,4 + 2] = 4
După calcularea tuturor elementelor din D(2) vom continua pe acelaşi principiu până
la D(5). Această matrice este matricea D, care conţine lungimile celor mai scurte căi.
În continuare vom prezenta algoritmul lui Floyd, care datează din anul 1962.
Problema: Să se calculeze cele mai scurte căi între oricare două noduri dintr-un graf
marcat. Marcajele sunt numere pozitive.
Intrări: Un graf orientat şi marcat şi n=numărul de noduri din graf. Graful este
reprezentat prin matricea adiacenţelor W, pătratică bidimensională de dimensiune n.
Ieşiri: O matrice pătratică bidimensională D, de dimensiunea n, în care D(i,j)
reprezintă cea mai scurtă cale de la nodul i la nodul j.
Floyd;
procedure Floyd(n:int, W(n,n):const, D(n,n):real);
begin
var i,j,k:int;
D=W;
for k=1 to n
for i=1 to n
for j=1 to n
D(i,j):=min(D(i,j),D(i,k)+D(k,j));
end;
BEGIN
var n:int, W(n,n),D(n,n):real;
read n,W(n,n);
Floyd(n,W,D);
write D(n,n);
END
Putem realiza calculele utilizând numai o matrice D deoarece valorile din linia k şi
cele din coloana k nu se modifică în timpul celei de a k-a iteraţie din buclă. Adică, în iteraţia k
din algoritm se atribuie valorile:
6
D(i, k ) = min[D(i, k ), D(i, k ) + D(k , k )]
care este egală cu D(i,k) şi:
D(k , j) = min[D(k , j), D(k , j) + D(k , k )]
care este egală cu D(k,j). În timpul celei de a k-a iteraţie, D(i,j) se calculează numai din
propria sa valoare şi din valorile din linia k şi din coloana k. Deoarece acestea şi-au păstrat
aceleaşi valori ca în iteraţia k-1, acestea pot fi folosite fără griji. Aşa cum am mai spus, după
dezvoltarea unui algoritm prin programarea dinamică, este posibil să se revizuiască acesta
pentru a-l face mai eficient.
Floyd_2;
procedure Floyd2(n:int, W(n,n):const, D(n,n):real, P(n,n):int);
begin
var i,j,k:int;
for i=1 to n
for j=1 to n
P(i,j):=0;
D=W;
for k=1 to n
for i=1 to n
for j=1 to n
if D(i,k)+D(k,j)<D(i,j) then
begin
P(i,j):=k;
D(i,j):=D(i,k)+D(k,j);
end
end;
BEGIN
var n:int, P(n,n):int, W(n,n),D(n,n):real;
read n,W(n,n);
Floyd(n,W,D,P);
write D(n,n), P(n,n);
END
Pentru graful din fig.4.1 matricea P rezultată prin algoritmul de mai sus este:
7
0 0 4 0 4
5
0 0 0 4
P = 5 5 0 0 4
5 5 0 0 0
0 1 4 1 0
Pentru a găsi cel mai scurt drum de la nodul vq la nodul vr folosind matricea P,
utilizăm algoritmul următor:
Problema: să se afişeze cel mai scurt drum de la un nod la alt nod dintr-un graf marcat.
Intrări: matricea P produsă prin algoritmul Floyd2 şi cei doi indici q şi r ai nodurilor
din graf între care se va afişa cel mai scurt drum.
Ieşiri: nodurile intermediare de pe cel mai scurt drum între vq şi vr.
procedure cale(q,r:int);
begin
if P(q,r)!=0 then
begin
cale(q,P(q,r));
write ”v”&P(q,r);
cale(P(q,r),r);
end;
end
Având exemplul din fig.4.1, care are matricea P calculată mai sus, pentru valorile q=5
şi r=3 ieşirea va fi:
v1 v4
Acestea sunt nodurile intermediare de pe cel mai scurt drum de la v5 la v3.
8
cale optimă de la vk la vj, atunci subcăile de la ui la uk şi de la vk la vj trebuie să fie şi ele
optime. Aşadar, soluţia optimă a unei probleme conţine soluţii optime pentru toate
subproblemele şi se aplică principiul de optim.
Dacă într-o anumită problemă se aplică principiul de optim, putem dezvolta o
recursivitate care dă o soluţie optimă pentru un caz al problemei prin soluţii optime ale
subproblemelor. Motivul cel mai important pentru care putem folosi programarea dinamică în
construirea unei soluţii optime pentru un caz al problemei este că soluţiile optime la
subprobleme pot fi printre soluţiile optime. De exemplu, în cazul problemei celui mai scurt
drum, dacă subcăile sunt cele mai scurte, atunci calea obţinută prin combinarea acestora va fi
optimă. Vom putea aşadar folosi recursivitatea pentru a construi soluţii optime la probleme tot
mai complexe pe principiul de jos în sus. Fiecare soluţie determinată pe această cale va fi
întotdeauna optimă.
Deşi principiul optimului ar putea părea evident, în practică este necesar să
demonstrăm că acest principiu se poate aplica, înainte de a presupune că prin programare
dinamică vom obţine soluţia optimă. Următorul exemplu arată că acest principiu nu se aplică
în toate problemele de optimizare.
Exemplu:
Să considerăm problema celui mai lung drum care presupune găsirea celei mai lungi
căi simple de la orice vârf la toate celelalte ale unui graf. Restricţionăm problema la căi
simple deoarece utilizând un ciclu putem crea o cale oricât de lungă, trecând repetat prin
ciclul respectiv. În fig.4.3 calea simplă cea mai lungă de la v1 la v4 este [v1, v3, v2, v4]. Însă
subcalea [v1, v3] nu este calea optimă (cea mai lungă) de la v1 la v3 deoarece:
lungime[v1, v3]=1 şi lungime[v1, v2, v3]=4.
9
Un arbore de căutare binară este un arbore binar de articole (denumite uzual chei),
care provin dintr-o mulţime ordonată, astfel încât:
1. Fiecare nod conţine o cheie.
2. Cheile din subarborele stâng al unui nod dat sunt mai mici sau egale cu cheia
din acel nod.
3. Cheile din subarborele drept al unui nod dat sunt mai mari sau egale cu cheia
din acel nod.
În fig.4.4 se prezintă doi arbori de căutare binară, fiecare cu aceleaşi chei. În arborele
din fig.4.4.a, dacă analizăm subarborele drept al nodului conţinând articolul „Radu” vedem că
acesta conţine articolele „Toma”, „Ursu” şi „Willy” şi toate aceste articole sunt mai mari
decât „Radu” atunci când sunt ordonate alfabetic. Deşi, în general, o cheie poate apare de mai
multe ori într-un arbore de căutare binară, pentru simplificare vom presupune că toate cheile
sunt distincte.
Adâncimea unui nod dintr-un arbore este numărul de arcuri din calea unică de la
rădăcină la nod. Acesta se mai numeşte şi nivelul nodului în arbore. De obicei spunem că un
nod are o adâncime şi că nodul este la un nivel. De exemplu, în arborele din fig.4.4.a, nodul
cu cheia „Ursu” are o adâncime de 2. Am putea spune şi că acel nod este la nivelul 2.
Rădăcina are o adâncime de 0 şi este la nivelul 0. Adâncimea unui arbore este maximul
adâncimilor tuturor nodurilor acelui arbore. Arborele din fig.4.4.a are adâncimea 3, în timp ce
cel din fig.4.4.b are adâncimea 2. Un arbore binar se spune că este echilibrat dacă adâncimile
celor doi subarbori ai fiecărui nod nu diferă niciodată prin mai mult de 1. Arborele din
fig.4.4.a nu este echilibrat deoarece subarborele stâng al rădăcinii are adâncimea 0 şi
subarborele drept are adâncimea 2. Arborele din fig.4.4.b este echilibrat.
Un arbore binar de căutare conţine înregistrări care sunt preluate în funcţie de valorile
cheilor. Obiectivul nostru este să organizăm cheile dintr-un arbore binar astfel încât timpul
mediu necesar localizării unei chei să fie minimizat. Un arbore care este organizat în acest
mod este denumit optimal. Nu este greu de observat că dacă toate cheile au aceeaşi
probabilitate de a fi cheia de căutare, arborele din fig.4.4.b este optimal. Ne interesează, însă,
cazul în care cheile nu au aceeaşi probabilitate. Un exemplu de asemenea caz ar fi căutarea în
unul din arborii din fig.4.4 după un nume ales aleator. Deoarece „Radu” este un nume mai
comun decât „Willy”, îi vom atribui o probabilitate mai mare lui „Radu”.
10
Vom analiza cazul în care se ştie că cheia de căutare este în arbore. Pentru a minimiza
timpul mediu de căutare, trebuie să ştim complexitatea în timp a localizării unei chei. Aşadar,
mai întâi, vom scrie şi analiza un algoritm care caută o cheie într-un arbore binar de căutare.
Tipul de dată utilizat de acest algoritm este:
struct nodetype
{
keytype key;
nodetype* left;
nodetype* right;
};
typedef nodetype* node_pointer;
Problema:
Să se determine nodul care conţine o cheie într-un arbore binar de căutare. Se
presupune că această cheie se află în arbore.
Intrări: un pointer tree la un arbore binar de căutare şi o cheie key.
Ieşiri: un pointer p la nodul care conţine cheia.
∑c
i =1
i ⋅ pi
11
Exemplu:
În fig.4.5 se prezintă cei cinci arbori diferiţi pentru cazul n=3.
Valorile cheilor nu sunt importante. Este necesar numai ca ele să fie ordonate. Dacă
avem: p1=0.7, p2=0.2 şi p3=0.1, atunci timpii medii de căutare pentru arborii din fig.4.5 sunt:
1. 3 ⋅ 0.7 + 2 ⋅ 0.2 + 1 ⋅ 0.1 = 2.6
2. 2 ⋅ 0.7 + 3 ⋅ 0.2 + 1 ⋅ 0.1 = 2.1
3. 2 ⋅ 0.7 + 1 ⋅ 0.2 + 3 ⋅ 0.1 = 1.8
4. 1 ⋅ 0.7 + 3 ⋅ 0.2 + 2 ⋅ 0.1 = 1.5
5. 1 ⋅ 0.7 + 2 ⋅ 0.2 + 3 ⋅ 0.1 = 1.4
Cel de al cincilea arbore este cel optim.
În general, nu putem găsi arborele binar de căutare optim lând în considerare toţi
arborii binari de căutare deoarece numărul acestor arbori este cel puţin exponenţial în n.
Vom folosi programarea dinamică pentru a dezvolta un algoritm mai eficient. Pentru
aceasta vom presupune că avem cheile de la keyi până la keyj aranjate într-un arbore care
minimizează expresia:
j
∑c
m =i
m ⋅ pm
unde cm este numărul de comparaţii necesare pentru a localiza keym în arbore. Vom numi
acest arbore optim pentru acele chei şi vom nota valoarea optimă prin A(i,j). Deoarece este
necesară o comparaţie pentru a localiza o cheie într-un arbore cu o singură cheie, A(i,i)=pi.
Exemplu:
Să presupunem că avem trei chei şi probabilităţile din exemplul anterior. Adică:
p1=0.7, p2=0.2 şi p3=0.1.
Pentru a determina A(2,3) trebuie să considerăm cei doi arbori din fig.4.6. Pentru acei
doi arbori avem următoarele:
1. 1 ⋅ p 2 + 2 ⋅ p 3 = 1 ⋅ 0.2 + 2 ⋅ 0.1 = 0.4
2. 2 ⋅ p 2 + 1 ⋅ p 3 = 2 ⋅ 0.2 + 1 ⋅ 0.1 = 0.5
Primul arbore este optim şi A(2,3)=0.4.
12
Fig.4.6. Arborii binari de căutare compuşi din key2 şi key3
Se observă că arborele optim obţinut în exemplul de mai sus este subarborele drept al
rădăcinii arborelui optim obţinut în exemplul anterior. Chiar dacă acest subarbore nu ar fi fost
exact acelaşi cu subarborele drept, timpul mediu de căutare în el ar fi trebuit să fie acelaşi.
Altfel, l-am fi putut înlocui în acel subarbore, obţinând un arbore cu un timp mediu de căutare
mai mic. În general, orice subarbore al unui arbore optim trebuie să fie optim pentru cheile
din acel subarbore. Aşadar, se aplică principiul optimului.
Acum, să presupunem că arborele 1 este un arbore optim pentru resticţia ca key1 să fie
rădăcina, arborele 2 este un arbore optim pentru restricţia ca key2 să fie rădăcina, ..., arborele
n este un arbore optim pentru restricţia ca keyn să fie rădăcina. Pentru 1 ≤ k ≤ n subarborii
arborelui k trebuie să fie optimi şi aşadar timpul mediu de căutare în aceşti subarbori sunt aşa
cum se vede în fig.4.7. Această figură prezintă şi că pentru a localiza keym în arborele k
pentru fiecare m ≠ k este necesară exact o comparaţie în plus (cea din rădăcină) faţă de cele
necesare localizării acelei chei în subarborele care o conţine. Această comparaţie adaugă 1xpm
la timpul mediu de căutare pentru keym în arborele k. Am stabilit că timpul mediu de căutare
pentru arborele k este dat de:
13
n
A(1, n ) = min (A(1, k − 1) + A(k + 1, n )) + ∑ p m ,
1≤ k ≤ n
m =1
unde A(1,0) şi A(n+1,n) sunt definite ca fiind 0. Deşi suma probabilităţilor din această ultimă
expresie este evident 1, am scris-o ca o sumă pentru că dorim să generalizăm rezultatul. Până
acum nu a existat nici o necesitate în demonstraţia de mai sus să avem cheile de la key1 la
keyn. Deci, în general, expresiile de mai sus se păstrează şi pentru domeniul keyi..keyj, unde
i<j. Am obţinut aşadar următoarele:
j
A(i, j) = min(A(i, k − 1) + A(k + 1, j)) + ∑ p m , i < j
i≤ k ≤ j
m =i
A(i, i ) = p i
A(i, i − 1) = A( j + 1, j) = 0 .
def
Utilizând egalităţile de mai sus putem scrie un algoritm care determină un arbore binar
de căutare optim. Deoarece A(i,j) este calculat din intrările din rândul i de la dreapta lui A(i,j)
şi din intrările din coloana j de sub A(i,j), vom calcula secvenţial valorile de pe fiecare
diagonală. Deoarece paşii algoritmului sunt destul de simpli, vom prezenta mai întâi
algoritmul şi apoi un exemplu sugestiv. Matricea R produsă de algoritm conţine indicii cheilor
alese ca rădăcini la fiecare pas. De exemplu, R(1,2) este indexul cheii din rădăcina unui
arbore optim conţinând primele două chei şi R(2,4) este indexul cheii din rădăcina unui arbore
optim conţinând a doua, a treia şi a patra cheie. După analizarea algoritmului, vom prezenta
modul de construire a unui arbore optim din matricea R.
14
R(i,j):=o valoare a lui k care a dat minimul;
end;
minavg:=A(1,n);
end
function tree(i,j:int):node_pointer;
begin
var k:int, p:node_pointer;
k:=R(i,j);
if k=0 then return NULL
else
begin
p:=new nodetype;
p->key:=key(k);
p->left:=tree(i,k-1);
p->right:=tree(k+1,j);
return p;
end;
end
Exemplu:
Presupunem că avem următoarele valori pentru matricea key:
Şi
3 3 1 1
p1 = p2 = p3 = p4 = .
8 8 8 8
Matricile A şi R produse de algoritmii de mai sus sunt:
15
3 9 11 7
0 8 8 8 8
0 1 1 2 2
3 5 0 2 2 2
0 1
8 8
A= 1 3 R= 0 3 3
0
8 8
0 4
1
0 0
8
0
7
Arborele creat pe baza matricei R este dat în fig.4.8. Timpul mediu de căutare este .
4
Se observă că R(1,2) ar putea fi 1 sau 2. Motivul este că oricare din aceşti indexi pot fi
rădăcina unui arbore optim care să conţină numai primele două chei. Aşadar, ambii indexi dau
valoarea minimă a lui A(1,2) din algoritmul de mai sus, ceea ce înseamnă că oricare ar putea
fi ales pentru R(1,2).
16
Fig.4.9. Turul optimal este (v1, v3, v4, v2, v1)
Ultimul tur este cel optim. Am rezolvat problema luând în considerare toate tururile
posibile. În general, poate exista un arc de la fiecare vârf la fiecare alt vârf. Dacă luăm în
considerare toate tururile posibile pentru grafuri cu multe vârfuri şi arce timpul necesar
rezolvării problemei devine foarte mare.
Se observă că dacă vk este primul nod după v1 în turul optim, subcalea acelui tur de la
vk la v1 trebuie să fie cea mai scură cale de la vk la v1 care trece prin fiecare din celelalte
vârfuri exact o dată. Aceasta înseamnă că se aplică principiul optimului şi putem, deci, folosi
programarea dinamică. Pentru aceasta, reprezentăm graful printr-o matrice de adiacenţă W, ca
în secţiunea precedentă. Matricea de adiacenţă pentru graful din fig.4.9 este:
0 2 9 ∞
1 0 6 4
W=
∞ 7 0 8
6 3 ∞ 0
Notăm cu V=mulţimea tuturor vârdurilor
A=o submulţime a lui V
D[v i , A ] =lungimea celei mai scurte căi de la vi la v1 care trece prin fiecare vârf din A
exact o dată.
De exemplu, pentru graful fin fig.4.9 avem: V = {v1 , v 2 , v 3 , v 4 }. Se observă că se
folosesc acoladele pentru a reprezenta o mulţime si parantezele drepte pentru a reprezenta o
cale. Dacă A = {v 3 } , atunci avem:
D[v 2 , A ] = length[v 2 , v 3 , v1 ] = ∞
Dacă A = {v 3 , v 4 }, atunci avem:
D[v 2 , A ] = min (length[v 2 , v 3 , v 4 , v1 ], length[v 2 , v 4 , v 3 , v1 ]) = min(20, ∞ ) = 20
Deoarece V − {v1 , v j } conţine toate arcele cu excepţia lui v1 şi vj şi se aplică principiul
de optim, vom avea:
[
Lungimea unui tur optim= min (W[1, j] + D v j , V − {v1 , v j } ) ]
2≤ j≤ n
D[v i , Φ ] = W[i,1]
17
Folosind formula de mai sus putem realiza un algoritm bazat pe programarea dinamică
pentru problema comisului voiajor. Înainte vom prezenta pe scurt modul de operare al
algoritmului propus.
Ne propunem să determinăm un tur optim pentru graful din fig.4.9. Mai întâi
considerăm mulţimea vidă şi obţinem:
D[v 2 , Φ ] = 1
D[v 3 , Φ ] = ∞
D[v 4 , Φ ] = 6
Apoi considerăm toate mulţimile care conţin un singur element:
[ ]
D[v 3 , {v 2 }] = min (W[3, j] + D v j , {v 2 } − {v j } ) = W[3,2] + D[v 2 , Φ ] = 7 + 1 = 8
j:v j∈{v 2 }
Similar:
D[v 4 , {v 2 }] = 3 + 1 = 4
D[v 2 , {v 3 }] = 6 + ∞ = ∞
D[v 4 , {v 2 }] = ∞ + ∞ = ∞
D[v 2 , {v 4 }] = 4 + 6 = 10
D[v 3 , {v 4 }] = 8 + 6 = 14
Apoi, să considerăm toate mulţimile cu două elemente:
[
D[v 4 , {v 2 , v 3 }] = min (W[4, j] + D v j , {v 2 , v 3 } − {v j } ) = ]
j:v j∈{v 2 , v3 }
= min (W[1,2] + D[v 2 , {v 3 , v 4 }], W[1,3] + D[v 3 , {v 2 , v 4 }], W[1,4] + D[v 4 , {v 2 , v 3 }]) =
= min(2 + 20,9 + 12, ∞ + ∞ ) = 21
Algoritmul prin programare dinamică pentru problema comisului voiajor este:
Problema: Să se determine un tur optim într-un graf orientat şi marcat. Marcajele sunt
numere întregi pozitive.
Intrări: un graf orientat şi marcat; n=numărul de noduri din graf. Graful este
reprezentat printr-o matrice bidimensională W, pătratică de ordinul n, unde W[i,j] este
marcajul de pe arcul de la nodul i la nodul j.
Ieşiri: o variabilă minlength, a cărei valoare este lungimea unui tur optim şi o matrice
bidimensională P din care se poate constui un tur optim. P are n linii şi ca număr de coloane
numărul tuturor submulţimilor lui V-{v1}. P[i,A] este indexul primului nod după vi pe o cea
mai scurtă cale de la vi la v1 care trece prin toate vârfurile din A o singură dată.
Comis_voiajor;
procedure Travel(n:int, W(n,n):const, P(n, ):int, minlenght:int);
begin
var i,j,k:int;
var D(n, [submulţimile lui V-{v1}]):int;
18
for i=2 to n
D(i,?)=W(i,1);
for k=1 to n-2
for (toate submulţimile lui A ⊆ V-{v1} conţinând k noduri)
for (i astfel încât i ≠ 1 şi vi nu este în A)
begin
D(i,A):= min (W(i,j)+D(j,A-{vj}));
[
j: v j ∈ A ]
P(i,A):=valoarea lui j care a dat minimul de mai sus;
end;
D(1,V-{v1}):= min (W(1,j)+D(j,V-{v1,vj}));
2≤ j≤ n
P(1,V-{v1}):=valoarea lui j care a dat minimul de mai sus;
minlenght:=D(1,V-{v1});
end;
BEGIN
var n:int, P(n, ):int, W(n,n):int,lungime:int;
read n,W(n,n);
Floyd(n,W,P,lungime);
write lungime;
END
În continuare vom prezenta modalitatea de extragere a unui tur optim din matricea P.
Elementele din matricea P necesare pentru a determina un tur optim pentru graful din fig.4.9
sunt date mai jos:
19
PROBLEME PROPUSE
20
Capitolul 5
METODA GREEDY
În povestirile lui Charles Dickens există un personaj deja clasic şi anume Ebenezer
Scrooge. Acesta este probabil cea mai lacomă persoană care a existat vreodată, în ficţiune sau
în realitate. Acest Scrooge nu ţinea niciodată seama de trecut sau de viitor. Singurul său
obiectiv zilnic era să strângă cu lăcomie cât mai mult aur. După ce Spiritul Crăciunurilor
Trecute i-a amintit de trecut şi l-a avertizat în privinţa viitorului, el şi-a schimbat modul lacom
de a se purta.
Un algoritm Greedy procedează la fel ca personajul de mai sus. Adică, culege succesiv
articole de date, de fiecare dată luând cel care este considerat „cel mai bun” pe baza unor
criterii, fără a ţine seama de alegerile pe care le-a făcut înainte sau de cele pe care le va face în
viitor. Nu trebuie să credem că algoritmii Greedy nu sunt buni, datorită analogiei cu
personajul negativ Scrooge sau cu cuvântul „Greedy” (lacom). Aceşti algoritmi adesea
conduc la soluţii foarte eficiente şi simple.
La fel ca programarea dinamică, algoritmii Greedy sunt utilizaţi adesea pentru a
rezolva probleme de optimizare. Metoda Greedy are avantajul că este mai directă.
La programarea dinamică se foloseşte recursivitatea pentru a împărţi o problemă în
probleme mai mici. La metoda Greedy nu apare împărţirea în probleme mai mici. Un algoritm
Greedy găseşte o soluţie făcând o succesiune de alegeri, fiecare dintre acestea pare cea mai
bună în momentul alegerii. Adică, fiecare alegere este un optim local. Prin aceasta se speră să
se obţină o soluţie care să fie un optim global, dar nu este întotdeauna aşa. Pentru un anumit
algoritm, trebuie să determinăm dacă soluţia este întotdeauna optimă.
În continuare vom prezenta un exemplu deja clasic pentru metoda Greedy.
George, un vânzător, trebuie să dea restul la cumpărături. Clienţii de obicei nu doresc
să primească multe monede. De exemplu, majoritatea clienţilor ar fi nervoşi dacă ar primi 87
de monezi de un ban atunci când restul ar fi 87 de bani. Aşadar, obiectivul este nu numai să se
dea restul corect, ci şi cu cât mai puţine monede posibil. O soluţie la o asemenea problemă a
restului este un set de monede a căror valoare totală este egală cu suma datorată
cumpărătorului, iar o soluţie optimă este un asemenea set cu număr minim de monede.
O abordare Greedy la această problemă ar putea fi după cum urmează:
• George începe să caute cea mai mare monedă (ca valoare) pe care o are. Adică,
criteriul său de decidere a celei mai bune monede (optim local) este valoarea
monedei. Aceasta, într-un algoritm Greedy, se numeşte procedura de selecţie.
• Apoi, verifică dacă adunând această monedă la rest valoarea rezultată nu
depăşeşte valoarea datorată. Aceasta, într-un algoritm Greedy, se numeşte
procedura de fezabilitate. Dacă adăugarea monedei nu depăşeşte suma
datorată, atunci adaugă moneda la rest.
• Apoi, verifică dacă valoarea totală a monedelor strânse până acum este egală
cu suma datorată. Aceasta, într-un algoritm Greedy, se numeşte verificarea
soluţiei. Dacă nu, George alege o nouă monedă după acelaşi criteriu şi repetă
întregul proces până când valoarea restului este egală cu suma datorată sau nu
mai are monede din care să aleagă. În cazul în care i se termină monedele el nu
va putea returna exact suma datorată.
În continuare se prezintă algoritmul Greedy scris în pseudocod pentru etapele descrise
mai sus.
While mai există monede şi problema nu este soluţionată
Begin
Ia cea mai mare monedă dintre cele rămase; procedura de selecţie
If adăugarea monedei face restul să depăşească suma datorată
Respinge moneda; testul de fezabilitate
Else
Adaugă moneda la rest;
If valoarea totală a restului este egală cu suma datorată
Problema este rezolvată;
End
monede(v[1..n],C)
v[1..n] sortare descrescătoare(v[1..n])
FOR i ←1, n DO S[i] ← 0
i←1
WHILE C > 0 AND i ≤ n DO
S[i]← C DIV v[i]
C ← C MOD v[i]
i←i+1
IF C = 0 THEN RETURN S[1..n]
ELSE "nu s-a gasit solutie"
2
mulţimea candidaţilor selectaţi, verificăm dacă această mulţime nu constituie o soluţie
posibilă a problemei noastre. Daca algoritmul greedy funcţionează corect, prima soluţie găsită
va fi totodată o soluţie optimă a problemei. Soluţia optimă nu este in mod necesar unică: se
poate ca funcţia obiectiv sa aibă aceeaşi valoare optimă pentru mai multe soluţii posibile.
function greedy(C)
{C este multimea candidatilor}
S ← ∅ {S este multimea in care construim solutia}
while not solutie(S) and C ≠ ∅ do
x ← un element din C care maximizeaza/minimizeaza select(x)
C ← C \ {x}
if fezabil(S U {x}) then
S ← S U {x}
if solutie(S) then
return S
else
return “nu exista solutie”
5.1. Aplicaţii
O clasa frecvent întâlnită în practică este cea a problemelor de optimizare de tipul:
Să se determine x care aparţine lui X astfel încât :
i) x satisface anumite condiţii (restricţii);
ii) x optimizează (minimizează sau maximizează) un criteriu.
O subclasa importanta în informatică o reprezintă cea în care X este o mulţime finita,
problema de optimizare fiind numită în acest caz discretă sau combinatoria. La o primă
vedere problema pare foarte simplă întrucât este suficient să se parcurgă X şi să se aleagă
elementul care satisface restricţiile şi optimizează criteriul. O astfel de abordare (numită şi
metoda forţei brute) devine total ineficientă (sau chiar impracticabilă) atunci când numărul de
elemente din X este foarte mare.
submulţime(A[1..n],m)
A[1..n] sortare_descrescătoare(A[1..n])
FOR i←1,m DO S[i] ←A[i]
RETURN S[1..m]
3
Două dintre cele mai cunoscute variante ale problemei sunt:
- Varianta discretă (0-1). Obiectele nu pot fi divizate: un obiect este fie preluat în
întregime fie nu este preluat.
- Varianta continuă (fracţionară). Este posibil să fie transferate şi fracţiuni din
obiecte, profitul asigurat fiind proporţional cu fracţiunea.
Doar pentru varianta fracţionară se poate obţine o soluţie optimă aplicând strategia
greedy. În acest caz alegerea local optimală este cea care corespunde profitului relativ maxim
(profitul relativ al obiectului i este pi / di). Aplicând strategia greedy se ajunge la a efectua
următoarele prelucrări:
(i) se ordonează A descrescător după profitul relativ;
(ii) se transferă obiectele în ordinea în care apar în A, în întregime cu excepţia
ultimului obiect din care se ia doar o fracţiune, atât cât să se umple rucsacul.
Pentru a descrie algoritmul facem următoarele convenţii: A[i].p, A[i].d reprezintă
profitul respectiv dimensiunea obiectului i. Soluţia va fi de forma: S = (S[1] , … , S[n]) cu S[i]
aparţine lui [0; 1], iar elementele sale vor fi interpretate astfel: S[i] = 0 - obiectul i nu este
selectat, S[i] = 1 – obiectul i este selectat în întregime, S[i] aparţine lui (0; 1) - este selectată
doar o fracţiune din obiectul i. Cu aceste convenţii algoritmul poate fi descris astfel:
rucsac fractionar(A[1::n],C)
A[1 .. n] sortare descrescătoare(A[1..n]) //sortare după profitul relativ
FOR i ← 1, n DO S[i] ← 0
i ←1
WHILE C > 0 AND i ≤ n DO
IF S[i].d ≤ C
THEN S[i]←1; C ← C - A[i].d
ELSE S[i]←C/A[i].d; C←0
i← i + 1
RETURN S[1..n]
4
selecţie activităţi(A[1..n])
A[1..n] ← sortare crescătoare(A[1..n]) // sortare după timpul de finalizare
S[1]← A[1]
i←2; k←1
WHILE i≤ n DO
IF S[k].t ≤ A[i].p
THEN k←k + 1; S[k] ← A[i];
i←i + 1
RETURN S[1..k]
Ordinea T
123 5+(5+10)+(5+10+3) = 38
132 5+(5+3)+(5+3+10) = 31
213 10+(10+5)+(10+5+3) = 43
231 10+(10+3)+(10+3+5) = 41
312 3+(3+5)+(3+5+10) = 29 ← optim
321 3+(3+10)+(3+10+5) = 34
Algoritmul greedy este foarte simplu: la fiecare pas se selectează clientul cu timpul
minim de servire din mulţimea de clienţi rămasă.
Prin metoda greedy obţinem deci întotdeauna planificarea optima a clienţilor.
Problema poate fi generalizata pentru un sistem cu mai multe staţii de servire.
5
Conform metodei greedy, construim un arbore binar fuzionând cele doua litere cu
frecventele cele mai mici. Valoarea fiecărui vârf este data de frecventa pe care o reprezintă.
In final, ajungem la arborele din Figura 5.1, in care fiecare vârf terminal corespunde
unei litere din text.
Pentru a obţine codificarea binara a literei P, nu avem decât sa scriem secvenţa de 0-
uri si 1-uri in ordinea apariţiei lor pe drumul de la rădăcina către vârful corespunzător lui P:
1011. Procedam similar si pentru restul literelor:
S (11), I (0), P (1011), O (100), T (1010)
Pentru un text format din n litere care apar cu frecventele f1, f2, …, fn, un arbore de
codificare este un arbore binar cu vârfurile terminale având valorile f1, f2, …, fn, prin care se
obţine o codificare binara a textului. Un arbore de codificare nu trebuie in mod necesar sa fie
construit după metoda greedy a lui Huffman, alegerea vârfurilor care sunt fuzionate la fiecare
pas putându-se face după diverse criterii. Lungimea externa ponderata a unui arbore de
codificare este:
n
∑a i =1
i fi
6
Arborii de codificare pe care i-am considerat in acesta secţiune corespund unei
codificări de tip special: codificarea unei litere nu este prefixul codificării nici unei alte litere.
O astfel de codificare este de tip prefix. Codul Morse nu face parte din aceasta categorie.
Codificarea cea mai compacta a unui sir de caractere poate fi întotdeauna obtinută printr-un
cod de tip prefix. Deci, concentrându-ne atenţia asupra acestei categorii de coduri, nu am
pierdut nimic din generalitate.
7
Ordonam crescător (in funcţie de cost) muchiile grafului: {1, 2}, {2, 3},{4, 5}, {6, 7},
{1, 4}, {2, 5}, {4, 7}, {3, 5}, {2, 4}, {3, 6}, {5, 7}, {5, 6} si apoi aplicam algoritmul.
Structura componentelor conexe este ilustrata, pentru fiecare pas, in Tabelul 6.1.
Multimea A este iniţial vida si se completează pe parcurs cu muchii acceptate (care nu
formează un ciclu cu muchiile deja existente in A). In final, multimea A va conţine muchiile
{1, 2}, {2, 3}, {4, 5}, {6, 7}, {1, 4}, {4, 7}. La fiecare pas, graful parţial <V, A> formează o
pădure de componente conexe, obţinuta din pădurea precedenta unind doua componente.
Fiecare componenta conexa este la rândul ei un arbore parţial de cost minim pentru
vârfurile pe care le conectează.
Iniţial, fiecare vârf formează o componenta conexa. La sfârşit, vom avea o singura
componenta conexa, care este arborele parţial de cost minim căutat (Figura 5.2b).
Ceea ce am observat in acest caz particular este valabil si pentru cazul general, din
Proprietatea 5.1 rezultând:
Proprietatea 5.2 In algoritmul lui Kruskal, la fiecare pas, graful parţial <V, A>
formează o pădure de componente conexe, in care fiecare componenta conexa este la rândul ei
un arbore parţial de cost minim pentru vârfurile pe care le conectează.
In final, se obţine arborele parţial de cost minim al grafului G. Pentru a implementa
algoritmul, trebuie sa putem manipula submulţimile formate din vârfurile componentelor
conexe. Folosim pentru aceasta o structura de mulţimi disjuncte si procedurile de tip find si
merge In acest caz, este preferabil sa reprezentam graful ca o lista de muchii cu costul asociat
lor, astfel încât sa putem ordona aceasta lista in funcţie de cost. Iată algoritmul:
8
A ← A U {{u, v}}
until #A = n–1
return A
Proprietatea 5.3 In algoritmul lui Prim, la fiecare pas, <U, A> formează un arbore
parţial de cost minim pentru subgraful <U, A> al lui G. In final, se obţine arborele parţial de
cost minim al grafului G.
Descrierea formala a algoritmului este data in continuare.
9
U ← {un vârf oarecare din V}
{bucla greedy}
while U ≠ V do
gaseste {u, v} de cost minim astfel ca u ∈ V \ U si v ∈ U
A ← A U {{u, v}}
U ← U U {u}
return A
A.5.3 Cele mai scurte drumuri care pleacă din acelaşi punct
Fie G = <V, M> un graf orientat, unde V este multimea vârfurilor si M este multimea
muchiilor. Fiecare muchie are o lungime nenegativa. Unul din vârfuri este desemnat ca vârf
sursa. Problema este sa determinam lungimea celui mai scurt drum de la sursa către fiecare
vârf din graf.
Vom folosi un algoritm greedy, datorat lui Dijkstra (1959). Notam cu C multimea
vârfurilor disponibile (candidaţii) si cu S multimea vârfurilor deja selectate. In fiecare
moment, S conţine acele vârfuri a căror distanta minima de la sursa este deja cunoscuta, in
timp ce multimea C conţine toate celelalte vârfuri. La început, S conţine doar vârful sursa, iar
in final S conţine toate vârfurile grafului. La fiecare pas, adăugam in S acel vârf din C a cărui
distanta de la sursa este cea mai mica.
Spunem ca un drum de la sursa către un alt vârf este special, daca toate vârfurile
intermediare de-a lungul drumului aparţin lui S. Algoritmul lui Dijkstra lucrează in felul
următor. La fiecare pas al algoritmului, un tablou D conţine lungimea celui mai scurt drum
special către fiecare vârf al grafului. După ce adăugam un nou vârf v la S, cel mai scurt drum
special către v va fi, de asemenea, cel mai scurt dintre toate drumurile către v. Când algoritmul
10
se termina, toate vârfurile din graf sunt in S, deci toate drumurile de la sursa către celelalte
vârfuri sunt speciale si valorile din D reprezintă soluţia problemei. Presupunem, pentru
simplificare, ca vârfurile sunt numerotate, V = {1, 2, …, n}, vârful 1 fiind sursa, si ca matricea
L da lungimea fiecărei muchii, cu L[i, j] = +∞, daca muchia (i, j) nu exista. Soluţia se va
construi in tabloul D[2 .. n].
Algoritmul este:
Pentru graful din Figura 5.3, paşii algoritmului sunt prezentaţi in Tabelul 5.4.
Observam ca D nu se schimba daca mai efectuam o iteraţie pentru a-l scoate si pe {2} din C.
De aceea, bucla greedy se repeta de doar n−2 ori. Se poate demonstra următoarea proprietate:
Proprietatea 5.4. In algoritmul lui Dijkstra, daca un vârf i
i) este in S, atunci D[i] da lungimea celui mai scurt drum de la sursa către i;
ii) nu este in S, atunci D[i] da lungimea celui mai scurt drum special de la sursa către i.
La terminarea algoritmului, toate vârfurile grafului, cu excepţia unuia, sunt in S. Din
proprietatea precedenta, rezulta ca algoritmul lui Dijkstra funcţionează corect. Daca dorim sa
aflam nu numai lungimea celor mai scurte drumuri, dar si pe unde trec ele, este suficient sa
adăugam un tablou P[2 .. n], unde P[v] conţine numărul nodului care îl precede pe v in cel mai
scurt drum.
Pasul v C D
iniţializare — {2, 3, 4, 5} [50, 30, 100, 10]
1 5 {2, 3, 4} [50, 30, 20, 10]
2 4 {2, 3} [40, 30, 20, 10]
3 3 {2} [35, 30, 20, 10]
Tabelul 5.4 Algoritmul lui Dijkstra aplicat grafului din Figura 5.3.
Pentru a găsi drumul complet,nu avem decât sa urmărim, in tabloul P, vârfurile prin
care trece acest drum, de la destinaţie la sursa.
11
Modificările in algoritm sunt simple:
• iniţializează P[i] cu 1, pentru 2 ≤ i ≤ n
• conţinutul buclei for cea mai interioara se înlocuieşte cu
if D[w] > D[v]+L[v, w] then D[w] ← D[v]+L[v, w]
P[w] ← v
• bucla repeat se executa de n−1 ori
Euristica greedy
Pentru anumite probleme, se poate accepta utilizarea unor algoritmi despre care nu se
ştie daca furnizează soluţia optima, dar care furnizează rezultate “acceptabile”, sunt mai uşor
de implementat si mai eficienţi decât algoritmii care dau soluţia optima. Un astfel de algoritm
se numeşte euristic.
Una din ideile frecvent utilizate in elaborarea algoritmilor euristici consta in
descompunerea procesului de căutare a soluţiei optime in mai multe subprocese succesive,
fiecare din aceste subprocese constând dintr-o optimizare. O astfel de strategie nu poate
conduce întotdeauna la o soluţie optima, deoarece alegerea unei soluţii optime la o anumita
etapa poate împiedica atingerea in final a unei soluţii optime a întregii probleme; cu alte
cuvinte, optimizarea locala nu implica, in general, optimizarea globala. Regăsim, de fapt,
principiul care sta la baza metodei greedy. Un algoritm greedy, despre care nu se poate
demonstra ca furnizează soluţia optima, este un algoritm euristic.
Vom da doua exemple de utilizare a algoritmilor greedy euristici.
Mai putem colora cu albastru si vârful 5. Deci, ne-au fost suficiente doua culori. Daca
coloram vârfurile in ordinea 1, 5, 2, 3, 4, atunci se obţine o colorare cu trei culori. Rezulta ca,
prin metoda greedy, nu obţinem decât o soluţie euristica, care nu este in mod necesar soluţia
optima a problemei. De ce suntem atunci interesaţi intr-o astfel de rezolvare? Toţi algoritmii
cunoscuţi, care rezolva optim aceasta problema, sunt exponenţiali, deci, practic, nu pot fi
folosiţi pentru cazuri mari. Algoritmul greedy euristic propus furnizează doar o soluţie
“acceptabila”, dar este simplu si eficient.
Un caz particular al problemei colorării unui graf corespunde celebrei probleme a
12
colorării harţilor: o harta oarecare trebuie colorata cu un număr minim de culori, astfel încât
doua tari cu frontiera comuna sa fie colorate diferit. Daca fiecărui vârf ii corespunde o tara, iar
doua vârfuri adiacente reprezintă tari cu frontiera comuna, atunci harţii ii corespunde un graf
planar, adică un graf care poate fi desenat in plan fără ca doua muchii sa se intersecteze.
Celebritatea problemei consta in faptul ca, in toate exemplele întâlnite, colorarea s-a putut
face cu cel mult 4 culori. Aceasta in timp ce, teoretic, se putea demonstra ca pentru o harta
oarecare este nevoie de cel mult 5 culori. Recent s-a demonstrat pe calculator faptul ca orice
harta poate fi colorata cu cel mult 4 culori. Este prima demonstrare pe calculator a unei
teoreme importante.
Problema colorării unui graf poate fi interpretata si in contextul planificării unor
activitatea. De exemplu, sa presupunem ca dorim sa executam simultan o mulţime de
activitatea, in cadrul unor săli de clasa. In acest caz, vârfurile grafului reprezintă activitatea,
iar muchiile unesc activităţile incompatibile. Numărul minim de culori necesare pentru a
colora graful corespunde numărului minim de săli necesare.
PROBLEME PROPUSE
2. Scrieţi algoritmul greedy pentru colorarea unui graf si analizaţi eficienta lui.
13
să rezolve problema. Se obţine soluţia optimă ? Aceeaşi problemă pentru cazul în care toate
profiturile sunt identice (dar dimensiunile sunt diferite).
Pentru un text format din n litere care apar cu frecventele f1, f2, …, fn,
demonstraţi ca arborele de codificare Huffman minimizează lungimea externa
ponderata pentru toţi arborii de codificare cu vârfurile terminale având valorile
f1, f2, …, fn.
14
Capitolul 6
METODA BACKTRACKING
Dacă am încerca să căutăm drumul prin bine cunoscutul labirint făcut din gard viu
aflat în Hampton Court Palace în Anglia, nu am avea altă soluţie decât să urmăm orbeşte o
cale până când găsim un capăt de drum. Când se întâmplă aceasta, trebuie să ne întoarcem la o
bifurcaţie şi să încercăm o altă cale. Oricine a încercat vreodată să rezolve un labirint a trăit
frustrarea ajungerii la capete de drum. Ce bine ar fi dacă ar exista semne care să indice că un
anumit drum nu conducere la nimic altceva decât un capăt. Dacă semnul ar fi poziţionat
aproape de începutul unei căi, timpul economisit ar fi foarte mare, deoarece toate bifurcaţiile
de după acel semn nu ar mai fi luate în considerare. Aceasta înseamnă că ar fi evitate nu unul
ci mai multe capete de drum. Dar în celebrul labirint din gard viu, ca şi în majoritatea
labirinturilor, nu există asemenea semne. Însă, aşa cum vom vedea, în algoritmii de tip
backtracking aceste semne există.
2
[< 1, 1 >, < 2, 1 >, < 3, 1 >, < 4, 4 >]
[< 1, 1 >, < 2, 1 >, < 3, 2 >, < 4, 1 >]
Se observă că nodurile sunt vizitate conform unei căutări Depth-First-Search în care fii
unui nod sunt vizitaţi de la stânga la dreapta. O căutare Depth-First-Search simplă într-un
arbore al spaţiului de stare este asemenea urmării fiecărei căi dintr-un labirint până la
atingerea unui capăt de drum. Nu profită de nici un semn aflat pe drum. Putem face căutarea
mai eficientă prin căutarea acestor semne. De exemplu, aşa cum se vede în fig.6.3.a, nu pot fi
puse două regine în aceeaşi coloană. Aşadar, nu are sens construirea şi verificarea căilor din
ramura care porneşte din nodul care conţine perechea < 2, 1 > în fig.6.2 (deoarece am pus deja
regina 1 în coloana 1, nu mai putem pune şi regina 2 în aceeaşi coloană). Acest semn ne spune
că acest nod nu poate conduce decât la capete de drum. Similar, aşa cum se vede în fig.6.3.b,
nu pot exista două regine pe aceeaşi diagonală. Aşadar, nu are sens construirea şi verificarea
ramurii care porneşte din nodul care conţine perechea < 2, 2 > în fig.6.2.
Fig.6.3. Diagrama care arată faptul că dacă prima regină este plasată în coloana 1, a
doua regină nu poate fi plasată în coloana 1 (a) sau în coloana 2(b)
PROBLEME PROPUSE
3
4