Documente Academic
Documente Profesional
Documente Cultură
2. Algoritmi
După cum s-a specificat în paragraful 1.3, etapa preliminară scrierii unui algoritm de
rezolvare a unei probleme este analiza acesteia, în cadrul căreia trebuie determinate mărimile
de intrare, cele de ieşire, precum si relaţiile ce conduc la obţinerea marimilor de ieşire. În
cadrul etapei de descriere a algoritmului de rezolvare a problemei, trebuie descrisă de fapt
succesiunea operaţiilor care realizează relaţiile determinate anterior. Un algoritm care nu este
precedat de o analiză corectă a problemei de rezolvat este de cele mai multe ori un algoritm
eronat sau neperformant.
Exemplul 2.1. Să se determine cel mai mare divior comun a două mumere întregi folosind
algoritmul lui Euclid.
Analiza problemei.
Vom nota cu m şi n numerele pentru care trebuie determinat cel mai mare divizor
comun. Astfel, variabilele m şi n reprezintă variabile de intrare pentru problemă. Algoritmul
lui Euclid presupune efectuarea repetată a unor operaţii de împărţire cu rest, până când se
obţine un rest zero. Fie de exemplu: m=8, n=12:
8=0*12+8 (r1=8)
12=1*8+4 (r2=4)
8=2*4+0 (r3=0)
Ultimul rest nenul (r2=4) va fi solutia problemei.
Pe baza examinării algoritmului precedent, se pot descrie principalele caracteristici ale unui
algoritm:
1. Generalitatea unui algoritm, se referă la faptul că în mod uzual algoritmii descriu
rezolvarea unor categorii de probleme, nu a unor probleme singulare. În exemplul
precedent, algoritmul descrie determinarea celui mai mare divizor comun a oricăror
numere întregi, nu numai a numerelor 8 şi 12.
2. Intrarea în algoritm, este reprezentată de zero sau mai multe mărimi care trebuie
specificate algoritmului, pantru ca el să poată fi executat. În mod uzual intrarea în
algoritm se face prin intarmediul unor varabile de intrare, care la începutul execuţiei
algoritmului vor fi iniţializate cu valorile de intrare curente.
3. Ieşirea din algoritm, constă în una sau mai multe mărimi care sunt determinate de
către algoritm ce reprezintă rezultatele problemei aferente algoritmului. Ieşirea se
realizează în mod uzual prin intermediul unor variabile de ieşire. Dacă este posibil
algoritmii să nu aibe mărimi de intrare, ei trebuie obligatoriu să aibe mărimi de ieşire,
pentru că acestea reprezintă soluţiile problemelor aferente.
4. Cracterul finit al algoritmului, reprezintă cerinţa ca numărul de operaţii efectuate
pentru determinarea mărimilor de ieşire să fie finit, altfel algoritmul nu poate să
determine soluţiile problemei aferente.
5. Claritatea algoritmului, se referă la cerinţa ca operaţiile din algoritm să fie clar şi
concis specificate, fiind astfel înlăturate nedeterminismul şi ambiguităţile anumitor
operaţii.
Este necesară prezentarea unui set minim de reguli pentru a putea defini sintaxa acestui
limbaj.
Majoritatea propoziţiilor din limbaj sunt propoziţii imperative (instrucţiuni), fiecare
având o semnificaţie foarte clară şi desemnând operaţii precise. Puţinele propoziţii
declarative au doar rolul de a delimita din punct de vedere sintactic algoritmii, sau de a
specifica în cazul funcţiilor şi procedurilor modul în care se face transferul de informaţie între
funcţia/procedura apelată şi algoritmul apelant.
În afara acestor propoziţii, în cadrul algoritmilor pot apare propoziţii dintr-o categorie
aparte, numite comentarii. Comentariile nu sunt luate în considerare la execuţia algoritmilor,
ele având doar un rol de explicare a anumitor operaţii din cadrul acestora. Un comentariu
începe cu şirul de caractere // şi se termină la sfârşitul liniei curente.
Desemnarea acţiunilor din cadrul instrucţiunilor se face prin intermediul unuia sau a
mai multor cuvinte cheie. Acestea sunt cuvinte rezervate şi nu pot fi utilizate de către
programator în alte scopuri. A doua categorie importantă de cuvinte sunt cuvintele utilizator,
numite şi identificatori, care sunt nume date de programatori anumitor obiecte din algoritm.
Pentru a deosebi cuvintele din cele două categorii, cuvintele cheie sunt subliniate.
Identificatorii sunt şiruri de caractere alfabetice, care încep întotdeauna cu o literă.
În mod uzual, identificatorii desemnează nume de variabile. Întrucât o mare parte a
problemelor a căror rezolvare poate fi descrisă algoritmic sunt de natură ştiinţifică, o parte a
noţiunilor şi conceptelor din matematică au fost preluate în limbajul algoritmic. Astfel,
noţiunea de variabilă reprezintă o entitate cu două atribute: numele variabilei (care este un
identificator) şi valoarea sa curentă sau instantanee. În timpul execuţiei unui algoritm,
valoarea curentă a unei variabilă se poate modifica. Acţiunea prin care unei variabile i se
asociază o valoare curentă se numeşte asignarea variabilei respective.
O altă noţiune utilizată este cea de expresie. O expresie este formată din operanzi,
operatori şi perechi de paranteze rotunde. Operanzii pot fi constante, variabile şi apeluri de
funcţii. Operatorii reprezintă operaţiile elementare ce se pot efectua asupra operanzilor.
Evaluarea unei expresii presupune aplicarea operatorilor asupra operanzilor într-o anumită
ordine, pentru obţinerea rezultatului final. Schimbarea ordinii de evaluare se realizează prin
utilizarea parantezelor rotunde.
Pentru a nu formaliza excesiv acest limbaj, anumite noţiuni din limbajele de
programare de nivel înalt nu sunt luate în considerare. De exemplu, noţiunea de tip de date
este utilizată în mod implicit. Tipul de date al unei variabile reprezintă mulţimea valorilor pe
care variabila le poate lua în timpul execuţiei unui algoritm. Se subânţelege faptul că o
variabilă nu poate lua valori diferite în timpul execuţiei unui algoritm (valori întregi şi valori
logice de exemplu).
Descrierea unui algoritm se face între două propoziţii declarative, formate fiecare dintr-un
singur cuvânt cheie:
start
sfarsit
Între cele două cuvinte se specifică secvenţa de instrucţiuni ce formează descrierea rezolvării
problemei asociate. O secvenţă de instrucţiuni reprezintă un şir de instrucţiuni, scrise în mod
uzual unele sub altele, care se execută secvenţial (una după alta).
Cele mai utilizate instrucţiuni sunt cele de citire şi scriere a datelor, de atribuire, de
selecţie şi instrucţiunile repetitive.
Corespunzător intrării şi ieşirii unui algoritm, limbajul algoritmic posedă două
instrucţiuni, numite instrucţiunea de citire, respectiv instrucţiunea de scriere. Prima este
folosită pentru citirea din afara algoritmului a datelor de intrare în algoritm, iar a doua pentru
scrierea în afara algoritmului a datelor de ieşire (a rezultatelor).
unde lista variabile reprezintă o listă de una sau mai multe variabile de intrare ale
algoritmului, separate prin virgulă dacă sunt mai multe.
Observaţie. Cracterele ““ şi ““ nu fac parte din sintaxa limbajului algoritmic, ci din cea a
limbajului de descriere. Între cele două caractere se specifică o categorie sintactică, adică o
mulţime de construcţii sintactice grupate împreună.
Observaţie. Instrucţiunea de citire este o modalitate prin care se pot asigna valori
variabilelor. După ce o constantă se asignează la o variabilă în acest fel, variabila îşi va păstra
valoarea curentă până la prima instrucţiune care o va modifica.
unde lista parametri reprezintă o listă de unul sau mai mulţi parametri separaţi prin
virgulă.
Există două categorii de parametri: de tip mesaj şi de tip expresie. Parametrii de tip
mesaj sunt şiruri de caractere cuprinse între două apostrofuri şi sunt utilizaţi pentru
tranmiterea unor mesaje în exteriorul algoritmilor. Valoarea unui astfel de parametru este
chiar şirul de caractere. Prametrii de tip expreise sunt expresii care se pot evalua. Valoarea
unui astfel de parametru este cea rezultată în urma evaluării.
Exemplul 2.2. Să se scrie un algoritm care determină suma şi produsul a două numere date.
Descrierea algoritmului
Notând cu x şi y variabilele de intrare corespunzătoare valorilor de intrare, algoritmul
se poate descrie astfe:
start
citeste x, y
scrie ‘suma=‘, x+y, ‘produsul=‘, x*y
sfarsit
Una dintre cele mai utilizate instrucţiuni, atât în limbajul algoritmic, cât şi în limbajele de
programare, este instrucţiunea de atribuire. Aceasta reprezintă cel de-al doilea mod prin care
se pot asigna valori variabilelor. Definiţia sintactică a instrucţiunii este:
atribuie variabila expresie
unde variabila este numele unei variabile a algoritmului, iar expresie este o
expresie compatibilă cu variabila. Cracterul ““ reprezintă operatorul de atribuire şi are
rolul de a indica sensul de transfer al informaţiei. Întrucât operatorul de atribuire identifică
exact instrucţiunea de atribuire, cuvântul cheie atribuie se poate omite, rezultând o formă
simplificată de scriere:
variabila expresie
În mod uzual, operatorii care apar în cadrul expresiilor sunt preluaţi din matematică,
reprezentând principalele operaţii, dar mulţimea acestora poate fi extinsă pentru a cuprinde
operaţii din diferite domenii. Deşi nu se specifică în mod formal, următoarele categorii de
operatori sunt mai des utilizaţi:
- operatori aritmetici:
- +, -, *, /: reprezintă operaţiile algebrice elementare;
În acest fel, numele unei variabile poate apare atât în stânga, cât şi în dreapta operatorului de
atribuire. Cele două valori ale variabilei se numesc: valoare veche (cea din stânga) şi valoare
nouă (cea din dreapta).
Instrucţiunea de selecţie cea mai utilizată este instrucţiunea de decizie. Ea este o
instrucţiune compusă, a cărei execuţie presupune execuţia unei secvenţe de instrucţiuni din
maxim două posibile, în funcţie de o anumită condiţie. Definiţia sintactică a instrucţiunii este
următoarea:
Principalii operatori care apar în cadrul expresiilor logice sunt operatorii logici şi
operatorii de comparare (de relaţie). Operatorii logici corespund principalelor operaţii
logice: sau logic (), şi logic () şi negaţia logică (). Operatorii de relaţie (<, , >, , =,
) compară valoarea a doi operanzi, generând tot un rezultat logic.
Sunt cazuri în care este utilă selecţia unei secvenţe de instrucţiuni din mai multe
alternative posibile. În aceste cazuri se poate utiliza o formă extinsă a instrucţiunii de decizie,
care devine o instrucţiune de selecţie. Forma sintactică a acesteia este următoarea:
altfel daca expresie n atunci
secventa n
altfel
secventa n+1
Descrierea algoritmului.
Pentru că instrucţiunea de decizie selectează pentru execuţie o secvenţă din maxim
două posibile, în cadrul algoritmului se vor folosi o instructiune de decizie extinsă, câte o
alternativă pentru fiecare ramură a funcţiei; prima decizie determină complet doar intervalul
(-, -2), urmând ca în cazul intervalului [-2, ) să se considere următorul punct de decizie, -
1, ş.a.m.d.
start
citeste x
daca x<-2 atunci
y -2*x2-x+2
altfel
daca x<-1 atunci
y x-1
altfel
daca x<1 atunci
y (2-x)/(x+2)
altfel
y (2*x2+x-2)/(2*x+1)
scrie x,y
sfarsit
Există cazuri în care a doua alternativă dintr-o decizie nu apare. În acest caz se poate utiliza
forma scurtă a instrucţiunii de decizie:
În cazul în care expresia logică este adevărată, se execută secvenţa de instrucţiuni internă; în
caz contrar, instrucţiunea de decizie este inefectivă.
Exemplul 2.4. Să reluăm exemplul precedent, dar folosind doar instrucţiuni de decizie cu
formă scurtă: pentru fiecare ramură a funcţiei se va considera o singură instrucţiune de
decizie.
start
citeste x
daca x<-2 atunci
y -2*x2-x+2
daca (x-2) (x<-1) atunci
y x-1
daca (x-1) (x<1) atunci
y (2-x)/(x+2)
daca x1 atunci
y (2*x2+x-2)/(2*x+1)
scrie x,y
sfarsit
x 5
cat timp x<10 executa
scrie ‘o iteratie’
x x+1
Exemplul 2.5. Să se determine cel mai mare divizor comun a două numere întregi folosind
algoritmul lui Euclid.
Aanaliza problemei.
Algoritmul lui Euclid a fost prezentat anterior. Se vor utiliza două variabile de intrare,
m şi n, reprezentând iniţial cele două numere. În timpul operaţiilor repetate de determinare a
câtului, m reprezintă deîmpârţitul, n împărţitorul, iar r restul împărţirii. După fiecare iteraţie,
se vor efectua atribuirrile: mn, nr, pentru reiniţializarea variabilelor la următoarea
iteraţie. Rezultatul problemei va fi memorat în variabila n, care stochează ultimul rest nenul.
Descrierea algoritmului.
start
citeste m, n
cat timp m mod n 0 executa
r m mod n //se determina restul
m n
n r
scrie n
sfarsit
repeta
secventa
cat timp expresie
Exemplul 2.6. Se consideră un şir de numere terminat cu numărul zero. Să se determine câte
numere pozitive sunt în şir.
Analiza problemei.
Se va utiliza o variabilă de intrare, notată cu x, prin intermediul căreia se vor citi
valorile din şir. Variabila de ieşire, notată cu np, va memora numărul de numere pozitive din
şir. Iniţial np va fi iniţializată la zero; de fiecare dată când se citeşte un număr, se compară
acesta cu zero: dacă numărul curent este pozitiv, variabila np va fi incrementată cu 1.
Iteraţiile se termină când se citeşte o valoare zero.
Descrierea algoritmului.
start
np 0
repeta
citeste x
daca x>0 atunci
np np+1
cat timp x0
scrie np
sfarsit
În cazul precedentelor două instrucţiuni repetitive, numărul de iteraţii este greu de estimat în
faza de proiectare a algoritmului. Sunt însă situaţii în care acesta poate fi estimat relativ uşor.
În aceste cazuri este probabil preferabilă utilizarea instrucţiunii repetitive cu număr
cunoscut de iteraţii.
De data aceasta gestionarea numărului de iteraţii se realizează cu ajutorul unei
variabile numite variabila de control a instrucţiunii. Aceasta va primi în timpul execuţiei
instrucţiunii repetitive valori succesive dintr-un şir de valori monoton, mărginit de două
valori distincte numite valoare iniţială, respectiv valoare finală. Valoarea iniţială este chiar
prima valoare din sir, pe când valoarea finală este doar o margine a şirului de valori (margine
superioară în cazul unui şir crescător, sau margine inferioară în cazul unui şir descrescător).
Şirul valorilor variabilei de control are proprietatea că diferenţa între oricare două
valori consecutive este constantă. Această constantă se numeşte pas de incrementare. Astfel,
şirul valorilor unei variabile de control se poate determina dacă se cunosc valoarea iniţială,
limita finală şi pasul de incrementare.
Notând cu vi, vf şi p valoarea iniţială, limita finală, respectiv pasul de incrementare, iată
câteva exemple de şiruri de valori pentru variabila v:
a) vi=2, vf=9, p=2 v{2, 4, 6, 8}
b) vi=9, vf=2, p=-2 v{9, 7, 5, 3}
c) vi=2, vf=2, p=3 v{2}
d) vi=2, vf=9, p=0 v{2, 2, 2, ...}
Se observă faptul că între valoarea iniţială vi, limita finală vf şi pasul de incrementare p
trebuie să existe anumite relaţii:
unde:
- variabila reprezintă numele unei unei variabile din algoritm, care este variabila
de control a instrucţiunii repetitive;
- expresie1, expresie2 şi expresie3 sunt trei expresii compatibile cu
tipul variabilei de control a căror valoare reprezintă: valoarea iniţială, valoarea finală,
respectiv pasul de incrementare.
Observaţie. Parantezele drepte nu fac parte din sintaxa instrucţiunii, ci din cea a limbajului
de descriere: între paranteze drepte se specifică acele construcţii sintactice opţionale. În cazul
în care într-o instrucţiune, pasul de incrementare nu este specificat, el are valoarea implicită
1.
Analiza problemei.
Va trebui calculată suma:
n
S=13+23+ ... +n3= k 3
k 1
Pentru calculul lui S se va utiliza o relaţie de recurenţă. Vom nota cu Si suma cuburilor
primelor i numere naturale:
Si=13+23+ ... +i3.
Definim relaţia astfel:
(1) Si=Si-1+i3, pentru i>0,
S0=0.
Se observă astfel că Sn=S, suma ce trebuie determinată.
Se va utiliza proprietatea instrucţiunii de atribuire, în care o variabilă poate apare în ambele
părţi ale operatorului de atribuire, astfel încât instrucţiunea:
S S + i3,
reprezintă chiar relaţia (1) la iteraţia I.
Folosind această observaţie, se va utiliza variabila S pentru stocarea valorilor Si la diferitele
iteraţii (adică va memora pe rând valorile S0, S1, ..., Sn). Variabila va fi iniţializată cu
valoarea 0, iar după ultima iteraţie va memora chiar valoarea dorită.
Descrierea algoritmului.
start
citeste n
s 0
pentru k1 la n executa
s s+k3
scrie s
sfarsit
În cazul problememor complexe este dificilă descrierea rezolvărilor acestora direct, folosind
doar instrucţiuni executabile ale limbajului algoritmic. În mod uzual, problema iniţială va
trebui descompusă într-un amumit număr de subprobleme, a căror rezolvare trebuie descrisă
separat.
O metodă des utilizată în acest caz este metoda rafinărilor succesive. În cadrul
acesteia se face distincţie între enunţarea unei subprobleme şi descrierea rezolvării ei. Pentru
a specifica o anumită subproblemă în cadrul unui algoritm, limbajul algoritmic trebuie extins
cu o nouă clasă de propoziţii, numite propoziţii nestandard. Acestea sunt specificate în
limbajul natural şi încadrate între două caractere ‘*’. Descrierea rezolvării unei subprobleme
asociate unei propoziţii nestandard se numeşte rafinarea propoziţiei; rafinarea propoziţiilor
nestandard se va face după descrierea algoritmului principal al problemei iniţiale.
Analiza problemei.
În cadrul acestei probleme (simple şi a cărei rezolvare nu necesită descompunerea în
subprobleme) se pot pune în evidenţă două subprobleme: citirea şirului de numere şi
determinarea mediei aritmetice. Principalele variabile folosite în algoritm sunt:
- n, care specifică numărul de elemente din şir,
- x, care reprezintă un vector de numere cu componentele: x1, x2, ..., xn,
- medie, care reprezintă madia aritmetică a sirului de numere.
Descrierea algoritmului.
Algoritmul principal se poate descrie astfel:
start
citeste n
* citirea sirului de numere x1, x2, ..., xn *
* calculul mediei aritmetice:medie=(x1+ x2+ ...+ xn)/n *
scrie med
sfarsit
Se observă faptul că rafinarea unei propoziţii nestandard conţine trei elemente: enunţul
propoziţiei, secvenţa de instrucţiuni ce formează descrierea rezolvării problemei aferente si
cuvăntul cheie sfarsit, care are rolul de a delimita rafinarea propoziţiei curente.
În cazul în care anumite subprobleme sunt de asemenea complexe, procesul de
împărţire în subprobleme poate continua. Rezultă astfel o descriere a algoritmului pe nivele:
- pe nivelul 0 se află algoritmul principal al problemei;
- pe nivelul 1 se află refinările propoziţiilor nestandard de pe nivelul 0;
- în general, pe nivelul k se află refinările propoziţiilor nestandard de pe nivelul k-1.
Operaţia de rafinare succesivă continuă până la un nivel în care rafinările tuturor propoziţiilor
conţin doar instrucţiuni standard ale limbajului algoritmic.
Analiza problemei.
Conform definiţiei puterii unei matrici:
A0=I, Ak=Ak-1*A, pentru k>0,
se poate descrie algoritmul de rezolvare după şablonul problemei redusă la numere reale:
Dându-se un număr natural k şi un număr real a, să se determine numărul b=ak.
Un algoritm pentru acesta este:
start
citeste k
citeste a
b 1
pentru p 1 la k executa
c b*a
b c
scrie b
sfarsit
Descrierea algoritmului.
Subproblemele puse în evidenţă corespund celor citirii matricii A şi scrierii matricii B,
instrucţiunii de atribuire, precum şi intrucţiunii repetitive. Nivelul 0 este:
start
citeste k
* citeste matricea A *
* atribuie mtricial B I *
* determina matricea: B=Ak *
* scrie matricea B *
sfarsit
* citeste matricea A *
pentru i 1 la n executa
pentru j 1 la n executa
citeste aij
sfarsit
* atribuie mtricial B I *
pentru i 1 la n executa
pentru j 1 la n executa
daca i=j atunci
bij 1
altfel
bij 0
sfarsit
* scrie matricea B *
pentru i 1 la n executa
pentru j 1 la n executa
scrie bij
sfarsit
* atribuie matricial: B C *
pentru i 1 la n executa
pentru j 1 la n executa
scrie bij cij
sfarsit
Execuţia algoritmilor descrişi prin metoda rafinarilor succesive nu este directă. Înainte de
execuţia propriu-zisă, va trebui ca un asemenea algoritm să fie transformat într-un algoritm
echivalent din punct de vedere paragmatic, dar care nu contine decât instrucţiuni standard ale
limbajului. Notând cu n numărul nivelulu maxim, operaţia de transformare a algoritmului se
poate descrie astfel:
Cu alte cuvinte, începând de la penultimul nivel, se vor substitui toate propoziţiile nestandard
cu rafinările lor aflate pe nivelul imediat următor. Se asigură în acest mod apariţia doar a
instrucţiunilor standard ale limbajului.
În exemplul anterior, se vor substitui întâi propoziţiile nestandard: *determina
matricea:C=B*A* şi *atribuie matricial:BC* în propoziţia *determina matricea:B=Ak *.
La pasul următor se vor substitui cele patru propoziţii nestandard din algoritmul principal cu
rafinările lor, rezultând un algoritm direct executabil:
start
citeste k
pentru i 1 la n executa
pentru j 1 la n executa
citeste aij
pentru i 1 la n executa
pentru j 1 la n executa
daca i=j atunci
bij 1
altfel
bij 0
pentru p 1 la k executa
pentru i 1 la n executa
pentru j 1 la n executa
cij 0
pentru s 1 la n executa
cij cij+aik*bkj
pentru i 1 la n executa
pentru j 1 la n executa
cij bij
pentru i 1 la n executa
pentru j 1 la n executa
scrie bij
sfarsit
start
citeste n
CitireElementeSir(n,x)
medie CalculMedieAritmetica(n,x)
scrie medie
sfarsit
Descrierea algortimului în forma prezentată mai sus are avantajul execuţiei directe a acestuia
prin apelul procedurii CitireElementeSir şi a funcţiei CalculMedieAritmetica.
Deosebirea între funcţii şi proceduri se referă la rezultatul determinat de acestea.
Funcţiile sunt folosite în cazul în care subprobleme aferente au drept scop principal
determinarea unei valori simple, pe când procedurile au drept scop principal efectuarea
anumitor operaţii. Distincţia între acestea nu este însă foarte clară, pentru că şi funcţiile pot
avea mai multe mărimi de ieşire transmise prin parametri.
Se observă din exemplul anterior că definirea unei funcţii sau a unei proceduri presupune
specificarea a două elemente distincte: antetul şi corpul funcţiei sau procedurii. Antetul
defineşte elementele de interacţiune ale funcţiei/procedurii cu exteriorul, pe când corpul
specifică acţiunile care trebuie îndeplinite pentru rezolvarea subproblemei asociate.
Specificarea terminării descrierii funcţiei/procedurii se face prin intermediul cuvântului cheie
sfârşit.
Observaţie. Caracterele ‘[‘ şi ‘]’ nu fac parte din sintaxa limbajului algoritmic, ci din cea a
limbajului de descriere. Între două paranteze drepte se specifică în mod uzual o categorie
sintactică opţională (care poate sau nu să apară îmtr-un algoritm).
Antetul unei funcţii sau proceduri conţine un cuvânt cheie specificând tipul acesteia (funcţie
sau procedură), un identificator reprezentând numele asociat ei, precum şi parametrii formali
ai acesteia.
Parametri formali ai unei funcţii sau proceduri sunt opţionali şi reprezintă modul prin
care funcţia/procedura interacţionează cu exteriorul, specificând mărimile de intrare şi de
ieşire. O funcţie sau o procedură fiind de fapt un algoritm ce descrie modul de rezolvare al
unei subprobleme, ea poate primi la intrare valorile care sunt necesare pentru execuţia
algoritmului (care sunt parametri formali de intrare ai funcţiei/procedurii respective) şi
trebuie să determine mărimile de ieşire ale acesteia (care sunt parametri formali de ieşire).
Aceşti parametri pot fi consideraţi drept variabile locale funcţiei/procedurii, care nu au
semnificaţie în afara corpului acesteia.
Specificarea parametrilor de intrare se face cu ajutorul cuvântului cheie intrare,
iar a celor de ieşire cu ajutorul cuvântului cheie ieşire. În exemplul anterior parametrul nr
(specificând numărul de elemente al şirului) reprezintă un parametru de intrare atât în cazul
funcţiei cât şi al procedurii, iar v (specificând vectorul ce conţine elementele şirului) este un
parametru de ieşire în cazul procedurii şi unul de intrare în cazul funcţiei.
returneaza [expresie]
unde returnează este un cuvânt cheie, specificând locul unde execuţia procedurii sau a
funcţiei se încheie, revenindu-se în algoritmul care a apelat-o. Categoria sintactică
expresie reprezintă o expresie opţională, folosită doar în cadrul funcţiilor. În acest caz,
valoarea expresisei reprezintă chiar valoarea pe care funcţia respectivă trebuie să o determine
şi care trebuie returnată algoritmului apelant. În cazul în care expresia lipseşte, instrucţiunea
se utilizează în corpul procedurilor, în caz contrar ea utilizându-se în corpul funcţiilor. În
exemplul anterior, instrucţiunea de întoarcere este folosită doar în cadrul funcţiei
CalculMedieAritmetica, expresia s/nr reprezentând media aritmetică pe care funcţia trebuie
să o calculeze.
În cazul în care se folosesc funcţii sau proceduri pentru descrierea unui algoritm, trebuie
realizate două operaţii distincte: definirea funcţiilor şi/sau procedurilor folosite, precum şi
apelul acestora în cadrul algoritmului. Din punct de vedere sintactic, apelul indică numele
funcţiei/procedurii apelate, precum şi parametrii de apel, specificaţi între paranteze rotunde:
Observaţie. Există o deosebire importantă între apelul unei funcţii şi apelul unei proceduri:
apelul unei funcţii se poate face doar în cadrul unor expresii, în acele locuri din algoritm unde
este permisă utilizarea unor expresii (de exemplu, în cadrul instrucţiunilor de atribuire), pe
când apelul unei proceduri este de sine stătător, fiind considerat ca o instrucţiune distinctă,
numită instrucţiunea de apel de procedură.
Parametri de apel se mai numesc parametri actuali, iar împreună cu parametrii formali ai
funcţiei/procedurii apelate formează suportul pentru mecanismul de transfer a informaţiei
între procedura/funcţia apelată şi algoritmul apelant. Între parametri formali şi parametri
actuali trebuie să existe anumite concordanţe:
a) numărul parametrilor acuali şi al parametrilor formali trebuie să fie acelaşi;
b) tipul de date al fiecărui parametru actual trebuie să corespundă tipului de date al
parametrului formal de pe aceiaşi poziţie (în exempul precedent, parametrul actual n
este un întreg şi corespunde parametrului formal nr, iar parametrul actual x este un
vector de numere reale care corespunde parametrului formal v).
Apelul unei funcţii sau proceduri presupune efectuarea mai multor etape:
1) suspendarea execuţiei elgoritmului apelant în locul apelului respectiv;
2) transferul informaţiei dinspre algoritmul apelant spre procedura/funcţia apelată;
3) execuţia funcţiei/prodedurii apenate;
4) transferul informaţiei dinspre funcţia/procedura apelată spre algoritmul apelant;
5) reluarea execuţiei algoritmului apelant din locul imediat următor apelului.
start
citeste n
CitireElementeSir(n,x)
k DeterminaPozitia(n,x)
daca k > 0 atunci
scrie k
altfel
scrie ‘Nu sunt elemete negative’
sfarsit
În cadrul procedurii de citire a elementelor unui şir de numere din exemplul 1, după execuţia
procedurii, valoarea parametrului de ieşire v (reprezentând un vector de numere) este
transmisă variabilei x din algoritmului principal.
În cazul apelului unei funcţii există o etapă suplimentară la execuţia acesteia. În acest
caz, numele unei funcţii reprezintă el însuşi un parametru de ieşire, iar după execuţia funcţiei
această valoare trebuie transmisă spre exterior. Modalitatea de transfer este asemănătoare
evaluării expresiilor algebrice: după transferul parametrilor de ieşire spre algoritmul apelant,
apelul funcţiei din cadrul expresiei apelante se înlocuieşte cu valoarea returnată de aceasta.
Există cazuri în care pentru o anumită funcţie sau procedură, anumiţi pararmetri pot fi atât de
intrare cât şi de ieşire, în sensul că la începutul execuţiei funcţiei/procedurii intră cu anumite
valori, iar la sfârşitul execuţiei se întorc în algoritmul apelant cu valorile modificate. Aceştia
formează o categorie aparte: parametri de intrare şi ieşire, specificaţi de cuvântul cheie
intrare-iesire. Ei se comportă la fel cu parametrii de intrare la etapa a doua a apelului
şi la fel cu parametrii de ieşire la etapa a patra a apelului. Exemplul următor utilizează
parametri de intrare-ieşire.
Analiza problemei.
Fie polinomul:
P = a0 + a1X + ... + anXn,
care va fi specificat prin gradul său şi vectorul coeficienţilor.
Descrierea algoritmului.
Algoritmul următor apelează de k ori procedura DerivataPolinom; ambii parametri
formali, dim şi a sunt parametri de intrare-ieşire.
start
citeste n, k
CitesteCoeficienti(n,a)
pentru i 1 la k executa
DerivataPolinom(n,a)
ScrieCoeficienti(n,a)
sfarsit
În cazul în care o funcţie sau o procedură are mai multe tipuri de parametri formali, în mod
uzual se declară întâi parametrii de intrare, apoi parametrii de intrare-ieşire şi parametrii de
ieşire.
Estimarea timpului de execuție al unui algoritm se face pe baza unor presupuneri, care
definesc un model de calcul și o unitate de măsură:
Prelucrările din algoritmi se efectuează în mod secvențial (mașina de tip von Newman
este baza sistemelor de calcul actuale)
Operațiile elementare sunt efectuate în timp constant, indiferent de valoarea
operanzilor
Timpul de acces la informațiile din algoritm nu depind de poziția acestora (nu există
diferențe între prelucrarea diferitelor elemente dintr-un tablou)
Evaluarea expresiilor logice sau de relatie din cadrul instrucțiunilor de decizie sau
repetitive
Evaluarea expresiilor aritmetice din cadrul instrucțiunii repetitive cu număr cunoscut
de pași
Considerând că toate operațiile elementare au același cost de execuție (presupus, în general,
egal cu 1), timpul de execuție se obține prin însumarea costurilor prelucrărilor din algoritm.
Exemplul 2.13 Să se determine valoarea maximă a unui şir de numere, precum şi poziţia din
şir a valorii maxime. În cazul în care sunt mai multe valori maxime, să se determine ultima
valoare. Descrierea formală este următoarea:
Dându-se n elemente, x1, x2, ..., xn, să se determine m şi j astfel încât:
m=xj=max(x1, x2, ..., xn),
pentru care j este cel mai mare posibil.
Analiza problemei.
Se vor utiliza următoarele variabile de ieşire:
- m, pentru memorarea valorii maxime până la valoarea curentă care se testează;
- j, pentru memorarea indicelui valorii maxime.
Iniţial, valoarea maximă se consideră x1. Se vor parcurge pe rând elementele şirului de
numere; dacă elementul curent xi este mai mare decât valoarea maximă curentă, acesta va
deveni noua valoare maximă.
Descrierea algoritmului.
start
1 citeste n
2 pentru k 1 la n executa
2.1 citeste xk
3 m x1
4 j 1
5 pentru k 2 la n executa
5.1 daca xk>m atunci
5.1.1 m xk
5.1.2 j k
6 scrie m,j
sfarsit
Pentru a putea fi identificate, s-au notat instrucţiunile cu numere. Instrucţiunile interne unor
instrucţiuni compuse s-au notat cu mai multe numere. De exemplu, instrucţiunea 5.1 este
instrucţiunea de decizie internă instrucţiunii repetitive 5. În general, numărul de numere cu
care este etichetată o instrucţiune indică nivelul de indentare al acesteia (nivelul de
includere).
Analiza algoritmului.
A. Timpul de execuţie
Pentru analiza algoritmului, trebuie precizate câteva elemente privind timpul de execuţie al
instrucţiunilor limbajului algoritmic:
a) toate instrucţiunile simple au un timp egal de execuţie (presupus egal cu 1);
b) timpul de execuţie al instrucţiunii de decizie este egal cu timpul de execuţie al uneia
dintre secvenţele interne de instrucţiuni (acea care se va executa) plus timpul de
evaluare al expresiei logice (presupus egal cu 1);
c) timpul de execuţie al instrucţiunilor repetitive cu test iniţial şi final se calculează după
următoarea formulă:
t = n*(t1+1) ,
unde t1 reprezintă timpul de execuţie al instrucţiunilor din secvenţa internă, n numărul
de iteraţii al instrucţiunii; constanta 1 provine de la evaluarea expresiei logice la
fiecare iteraţie;
d) timpul de execuţie al instrucţiunii repetitive cu număr cunoscut de iteraţii se
calculează după următoarea formulă:
t = 1+n*(t1+2) ,
unde t1 reprezintă timpul de execuţie al instrucţiunilor din secvenţa internă, n numărul
de iteraţii al instrucţiunii; constanta 2 provine de la operaţia de determinare a valorii
curente a variabilei de control a instrucţiunii şi de la evaluarea expresiei logice la
fiecare iteraţie, iar constanta 1 de la operaţia de iniţializare a variabilei de control.
În cazul algoritmului precedent, vor trebui determinate: valoarea minimă, valoarea maximă şi
cea media a lui a.
Valoarea minimă a lui a este zero. Aceasta se întâmplă când şirul este ordonat crescător:
x1<x2< ... <xn. În acest caz, timpul minim al algoritmului este:
tmin = 1+6*n.
Valoarea maximă este n-1, în czul în care sirul este ordonat descrecător: x1>x2> ... >xn. În
acest caz, timpul maxim al algoritmului este:
tmin = 1+8*n.
Valoarea medie a lui a este cuprinsă între 0 şi n-1, dar este mai greu de estimat. Pentru
determinarea valorii medii, vor trebui făcute anumite presupuneri asupra caracteristicilor
probabile pentru datele de intrare.
Pentru algoritmul precedent vom considera că elementele xk sunt distincte şi că
fiecare din cele n! permutări ale acestora sunt echiprobabile. Această presupunere este uzuală
pentru cele mai multe situaţii, dar analiza poate fi efectuată si pe baza altor presupuneri.
Pentru cazul particular n=3, de exemplu, presupunem că următoarele şase situaţii sunt
la fel de probabile (alăturat sunt specificate valorile lui a pentru cazul respectiv):
- x1<x2<x3, a=2;
- x1<x3<x2, a=1;
- x2<x1<x3, a=1;
- x2<x3<x1, a=1
- x3<x1<x2, a=0
- x3<x2<x1, a=0.
Valoarea medie a lui a pentru cazul n=3 este: (2+1+1+1+0+0)/6=5/6.
Se observă faptul că în cele şase situaţii contează doar ordinea relativă a numerelor,
ceea ce conduce la considerarea mulţimii indicilor elementelor în locul valorilor: 123, 132,
213, 231, 312, 321.
Folosind această observaţie, se poate defini probabilitatea ca a să aibă valoarea k
(notată cu pnk):
pnk = (numărul de permutări a n obiecte pentru care a=k) / (n!)
Pentru determinarea lui an ar trebui determinate valorile probabilităţilor pnk, operaţie destul de
dificilă. O variantă constă în determinarea unei relaţii de recurenţă pentru probabilităţile pnk
şi utilizarea unei funcţii generatoare pentru aceste probabilităţi.
Funcţiile generatoare sunt utilizate pentru a obţine informaţii despre cantităţile pnk.
Considerând prin extensie pnk=0 pentru k>n, conform relației (3b), se poate considera funcţia
generatoare ca o serie infinită:
(4) Gn(z) = pn0+pn1*z+pn2*z2+ ... = k(pnk*zk)
Folosind condiţiile (3a), se poate calcula G1(z):
G1(z) = p10+p11*z+ ... = p10 + 0 = 1.
Utilizând relaţiile de recurenţă (2), se poate determina şi o relaţie de recurenţă pentru funcţia
generatoare Gn:
(5) Gn(z) = k0(pnk*zk) = k0((1/n*p(n-1)k+(n-1)/n*p(n-1)(k-1)) * zk) =
= (1/n)*k0(p(n-1)k*zk) + (z*(n-1)/n)*k0(p(n-1)(k-1)*zk-1) =
= (1/n)*Gn-1(z) + (z*(n-1)/n)*Gn-1(z)
Prima derivată a funcţiei Gn se poate determina uşor din relația (4):
(6) Gn1(z) = k(k*pnk*zk-1)
Din relaţiile (1), (6) şi (3b) rezultă:
n 1
an = Gn1(1) = k * p
k 0
nk
B. Notaţia asimptotică
Primele două proprietăți permit notației O să definească clase de echivalență: f(n) și g(n)
sunt echivalente dacă f(n)O(g(n)). Clasele de echivalență corespunzătoare se numesc clase
de complexitate. Principalele clase de complexitate sunt următoarele:
Complexitate logaritmică, O(lg(n)); de exemplu: căutarea binară
Complexitate liniară, O(n); de exemplu: căutarea secvențială
Complexitate pătraticică, O(lg(n2)); de exemplu: sortarea prin inserție
Complexitate cubică, O(lg(n3)); de exemplu: produsul a două matrici
Complexitate exponențială, O(2n); de exemplu: prelucrarea tuturor submulțimilor unei
mulțimi de n elemente
Complexitate factorială, O(n!); de exemplu: prelucrarea tuturor permutărilor unei
mulțimi de n elemente
2.6 Probleme
2.1. Să se demonstreze matematic caracterul finit al algoritmului lui Euclid din exemplul
2.1.
2.3. Pentru o valoare întragă n dată, să se scrie un algoritm care determină valorile
următoarelor expresii:
e1 = 2n+2-n
e2 = 1/2+2/3+ ... +n/(n+1)
e3 = (sin(1))/(n2+1)+(sin(2))/(n2+2)+ ... +(sin(n))/(n2+n)
2.4. Se consideră un punct în plan P(x0, y0) şi o dreaptă de ecuaţie: f(x, y) = a*x+b*y+c =
0. Să se scrie un algoritm care determină poziţia punctului P faţă de dreaptă:
a) pe dreaptă,
b) în semiplanul ce conţine originea O(0, 0),
c) în semiplanul ce nu conţine originea O(0, 0).
2.5. Să se scrie un algoritm care determină dacă un număr întreg n este prim.
2.6. Să se scrie un algoritm care determină valoarea y = n x , pentru un număr real pozitiv x
şi un număr întreg pozitiv n, efectuând un număr minim de operaţii de înmulţire şi
extragere a rădăcinii pătrate.
Indicaţie. Se vor determina cifrele numărului fracţionar 1/n în baza 2 şi se va utiliza
forma polinomială a numărului 1/n.
2.7. Se consideră o mulţime finită A = {a1, a2, …, an} şi relaţie binară R A A. Relaţia
se poate reprezenta printr-o matrice M, astfel: mij = 1, dacă (aI, aj) R şi mij = 0 altfel.
Pentru o asemenea relaţie binară, să se scrie un algoritm care determină dacă ea este:
- simetrică,
- reflexivă,
- tranzitivă.
2.8. Să se scrie un algoritm care determină polinomul sumă şi polinomul produs a două
polinoame cu coeficienţi reali. Polinoamele sunt specificate prin gradele lor şi vectorii
coeficienţilor.
2.9. Se consideră două şiruri de numere: x1, …, xn şi y1, …, ym. Să se scrie un algoritm care
determină dacă şirul al doilea este un subşir al primului (adică este format din elemente
consecutive ale primului şir: există un indice k astfel încât xk+i = yi, pentru i=1, 2, …,
m).
2.10. Se consideră sistemul bielă-manivelă din figura alăturată. În lungul axei Ox se mişcă o
culisă, iar manivela OA se mişcă în sens trigonometric cu viteza unghiulară constantă .
Să se scrie un algoritm care determină poziţia şi viteza punctului C situat la jumătatea
distanţei AB (adică valorile x, y, vx, vy ale acestuia), în puncte succesive pentru intervalul
de timp [0, tmax], cu un pas de incrementare dt pentru variabila t. Se cunoaşte: tmax, , dt,
|OA| = r, |AB| = l.
y
A
O B
2.11. Se consideră în plan n puncte de coordonate: (x1, y1), (x2, y2), ..., (xn, yn). Să se scrie un
algoritm care determină dacă poligonul cu vârfurile în punctele date este convex sau
nu;