Sunteți pe pagina 1din 12

Programarea calculatoarelor și limbaje de programare – notițe de curs/ Curs 3

Programarea calculatoarelor și limbaje de programare – Curs 3

….II. Algoritmi. Metode de descriere a algoritmilor


II.3. Corectitudinea și complexitatea algoritmilor..…………………….1
III. Limbaje de programare. Elaborarea programelor
III. 1. Evoluția limbajelor de programare. Limbajul de asamblare.
Limbaje de nivel înalt…………………………………………………6
III.2. Etapele dezvoltării unui program……………………………….9
III. 3. Modalități de descriere a sintaxei unui limbaj………………..10

II.3. Corectitudinea și complexitatea algoritmilor

Pentru a rezolva o problemă folosind calculatorul se parcurg următoarele


etape:
• descrierea algoritmului
• demonstrarea corectitudinii algoritmului
• analiza complexității algoritmului
• implementarea algoritmului
În secțiunea precedentă s-au prezentat modalități de descriere a algoritmilor.
În ceea ce privește demonstrarea corectitudinii algoritmilor există metode mai
mult sau mai puțin formale. În termeni generali, verificare corectitudinii unui
algoritm presupune două etape:
 calculele efectuate de algoritm se încheie într-un număr finit de pași
 algoritmul conduce la rezultatul căutat atunci când este aplicat unor
date care pot fi prelucrate de algoritmul considerat, numite date
inițiale (sau de intrare) ale algoritmului
În evaluarea complexității unui algoritm se ține cont de două aspecte
 timpul necesar execuției algoritmului (dat de numărul de operații
elementare)
 spațiul de memorie necesitat de algoritm

1
Mădălina Roxana. Buneci

În general nu este posibil să obținem simultan un timp de execuție mai scurt precum
și un necesar de spațiu de memorare mai mic. Progresele tehnologice din ultima
vreme impun drept criteriu primordial criteriul timp.

Pentru a analiza teoretic algoritmul din punct de vedere al timpului de execuție


a programului care-l implementează, se utilizează un model matematic (care implică o
simplificare brută) pentru sistemul de calcul: modelul RAM (Random-access
machine). În acest model se presupune că:
• memoria este organizată ca un tablou infinit de celule
• fiecare celulă poate stoca cel mult o dată (bit, byte, înregistrare etc.)
• orice celulă poate fi accesată într-o unitate de timp
• instrucțiunile sunt executate secvențial
• toate instrucțiunile de bază necesită o unitate de timp
- citire/scriere (load/store)
- operații aritmetice (de exemplu, +, -, *, /)
- operații logice (de exemplu, <, >)
Timpul de execuție al programului care implementează algoritmul este direct
proporțional cu numărul de instrucțiuni de bază efectuate de algoritm. Pentru algoritm
timpul de execuție se definește ca numărul de instrucțiuni de bază efectuate.
Evident, modelul RAM nu este realist:
• Memoria este finită.
• Timpul pentru accesarea datelor depinde de locația acestora în
memorie: într-un mașină reală există o ierarhie complexă a
memoriei
- memorie externă (algoritmi special proiectați pentru
seturi de date mari care trebuie păstrate în memoria
externă)
- memorie rapidă (de dimensiune limitată)
- operații speciale de intrare/ieșire care transfera
informație între cele două tipuri de memorie
• Operațiile aritmetice se execută cu viteze diferite de execuție.

2
Programarea calculatoarelor și limbaje de programare – notițe de curs/ Curs 3

- de exemplu, înmulțirea este mai costisitoare decât


adunarea,
- tipul operanzilor influențează de asemenea timpul de
execuție, operațiile în virgulă mobilă fiind mai
costisitoare decât cele cu numere întregi
• Procesare paralelă
• Multithreading - rularea mai multor fire de execuție pe un
procesor
• Procesoare multiple, procesoare multicore, etc.
• Sisteme distribuite
În general nu este posibil să obținem simultan un timp de execuție mai scurt
precum și un necesar de spațiu de memorare mai mic. De obicei, criteriul care primează
este timpul de execuție.
Cuantificând caracteristicile relevante (de exemplu, dimensiunea) ale datelor de
intrare dintr-un set S, pentru un algoritm stabilit, se poate defini o funcție S: N→
R+\{0} care dă timpul de execuție, iar pentru analiza complexității algoritmului se pot
utiliza:
• (n) timpul de execuție în cazul cel mai defavorabil, i.e.:
(n) = sup {TS(n) : S este un set de date de intrare de dimensiune n}
• (n) timpul de execuție în cazul cel mai favorabil, i.e.:
(n) = inf{TS(n) : S este un set de date de intrare de dimensiune n}
• (n) timpul mediu de execuție: presupune cunoașterea repartiției probabilistice
a datelor de intrare
În general ne interesează comportarea asimptotică a lui (n) (i.e. pentru valori mari
ale lui n). În acest sens introducem următoarele notații, unde f: N→ R+\{0} este o
funcție:
➢ (n) = O(f(n)) dacă  C > 0, n0  N cu (n)  C f(n)  n  n0
Interpretare:  are o creștere mai lentă decât f
τ(n )
➢ (n) = o(f(n)) dacă lim =0
n →  f (n )

3
Mădălina Roxana. Buneci

Interpretare:  are o creștere mai strict mai lentă decât f


➢ (n) = (f(n)) dacă  C1, C2 > 0, n0  N cu C1 f(n)  (n)  C2 f(n)  n 
n0
Interpretare:  are o creștere la fel de lentă ca f
➢ (n) = (f(n)) dacă f(n) = O((n))
Interpretare: f are o creștere mai lentă decât 
(n )
➢ (n) = (f(n)) dacă lim =
n → f (n)

Interpretare: f are o creștere strict mai lentă decât 

O și o se folosesc pentru a stabili marginile superioare ale timpului de execuție,


iar  și  pentru limita inferioară a timpului de execuție.
Un algoritm se numește algoritm polinomial dacă (n) = O(P(n)), unde  este
timpul execuție al algoritmului, iar P este o funcție polinomială. Un algoritm se
numește algoritm exponențial dacă (n) =  (an), unde a>1 și  este timpul cerut de
algoritm.

Exemplul 1. Se dă un șir de n numere reale introduse de la tastatură. Se cere


să se determine elementul minim al șirului.

întreg n,i;
real x[1..n], min;
citește n;

pentru i=1,n execută


citește x[i]
sfârșit pentru;

minx[1];

4
Programarea calculatoarelor și limbaje de programare – notițe de curs/ Curs 3

pentru i=2,n execută


dacă min>x[i] atunci
minx[i];
sfârșit dacă;
sfârșit pentru;
scrie min;
sfârșit

Ne îndreptăm atenția asupra numărului de comparații de numerele reale


necesare pentru determinarea minimului. Se observă că algoritmul de mai sus necesită
n-1 comparații, deci (n) = n-1. În consecință algoritmul este polinomial. Se poate arăta
(prin inducție după n) că acest algoritm este optimal, în sensul că: pentru determinarea
minimului a n numere date sunt necesare cel puțin n-1 comparări.

Exemplul 2. Se dă un șir de n numere reale introduse de la tastatură. Se cere să se


afișeze șirul precum și elementele minim și maxim ale șirului.

întreg n,i;
real x[1..n], min, max;
citește n;
pentru i=1,n execută
citește x[i]
sfârșit pentru;
minx[1]; maxx[1];
pentru i=2,n execută
dacă min>x[i] atunci minx[i]
altfel dacă max<x[i] atunci max x[i]
sfârșit dacă;
sfârșit dacă;
sfârșit pentru;

5
Mădălina Roxana. Buneci

pentru i=1,n execută


scrie x[i]
sfârșit pentru;
scrie min, max;
sfârșit program

III. Limbaje de programare. Elaborarea programelor

Un limbaj de programare este un limbaj artificial care poate fi utilizat pentru a


defini un șir de instrucțiuni ce pot fi în final prelucrate și executate de calculator.

III. 1. Evoluția limbajelor de programare.


Limbajul de asamblare. Limbaje de nivel înalt.

Programarea primelor calculatoare se făcea în limbaj mașină (cod mașină) : un


program scris în cod mașină poate fi reprezentat ca o secvență de coduri binare, care
corespunde exact formei în care programele sunt memorate înainte de a fi executate.
Această reprezentare se numește program executabil.
Primul pas în evoluția limbajelor îl reprezintă limbajul de asamblare. Limbajul
de asamblare este în esență același lucru cu limbajul mașină al calculatorului, doar că
este exprimat într-o formă inteligibilă pentru oameni. Specific limbajului de asamblare
este faptul că fiecare instrucțiune a lui este corespondenta unei operații elementare a
calculatorului. Limbajul de asamblare are trei părți esențiale [pg. 471, P. Norton
Secrete PC, Editura Teora, 1996]:
• Prima parte este limbajul propriu-zis, fiind formată din instrucțiunile
individuale în limbaj mașină, reprezentate într-o formă textuală, ceea ce
face mai ușoară scrierea programelor.
• A doua parte constă din comenzile care controlează procesul, stabilind în
primul rând condițiile de lucru pentru program.
• A treia parte a limbajului de asamblare este un mecanism care ușurează
munca programatorului, în situația în care în program apar secvențe de

6
Programarea calculatoarelor și limbaje de programare – notițe de curs/ Curs 3

instrucțiuni care se repetă. Limbajul de asamblare permite programatorului


să abrevieze o secvență de instrucțiuni în cadrul unei macroinstrucțiuni
numită pe scurt macro. Secvența de instrucțiuni se înlocuiește cu linia de
cod reprezentând macroinstrucțiunea asociată.
Scrierea unui program în limbaj de asamblare este un proces lung și plictisitor.
Un program mare și complex poate fi alcătuit din sute de mii de instrucțiuni în limbaj
mașină. Dacă un astfel de program este scris în limbaj de asamblare, programatorul
trebuie să scrie separat toate aceste comenzi, deci posibilitățile de apariție a erorilor
sunt mari.
Limbajele de nivel înalt (orice limbaj de calculator cu excepția limbajului de
asamblare) sunt proiectate pentru a facilita scrierea unui program și pentru a elimina
probabilitatea mare de apariție a erorilor specifică limbajului de asamblare. Limbajele
de nivel înalt au la bază ideea de concentra mai multe instrucțiuni din limbajul mașină
într-o singură comandă de program. Această idee este similară cu cea a macro-urilor
din limbajul de asamblare, numai că este aplicată într-un sens mai larg.
Limbajul de asamblare și limbajele de nivel înalt au atât avantaje, cât și
dezavantaje. În comparație cu limbajele de nivel înalt limbajul de asamblare are
următoarele dezavantajele:
• solicită mai multă muncă de scriere a programelor decât limbajele de nivel
înalt, deoarece sunt necesare mai multe linii de cod de program
• probabilitatea de apariție a erorilor este mare deoarece implică mai multe
detalii
• necesită mai multă experiență din partea programatorului decât limbajele
de nivel înalt
Evident că limbajul de asamblare are și avantaje în raport cu limbajele de nivel înalt.
Astfel
• programele (executabile) sunt mai mici și rulează mai rapid, deoarece
programatorii în limbaj de asamblare își folosesc experiența pentru a găsi
modalități mai eficiente de executare a fiecărui pas
• cu ajutorul limbajului de programare se poate ordona calculatorului să execute orice
instrucțiune (din cele care pot fi executate de calculatorul respectiv), în timp ce

7
Mădălina Roxana. Buneci

limbajele de nivel înalt nu oferă această posibilitate; vorbind într-un sens mai larg,
putem spune că limbajele de nivel înalt permit calculatorului să beneficieze de 90%
din “aptitudinile” calculatorului, în timp ce limbajul de asamblare permite o
utilizare 100% a calculatorului (bineînțeles, dacă programatorul este suficient de
experimentat). [P. Norton, Secrete PC, Editura Teora, București, 1996. pg. 473]
Până acum am vorbit despre limbajele de nivel înalt ca fiind o categorie
omogenă, dar ele nu sunt chiar asemănătoare. Aceste limbaje au multe elemente
comune, mai ales prin contrast cu limbajul de asamblare, dar există și multe diferențe
importante între ele. Elaborarea limbajelor de nivel înalt a vizat încă de la început trei
direcții importante, și s-a concretizat prin trei categorii de limbaje:
• limbaje bazate pe conceptele de algoritm (Fortran, Algol) și prelucrarea datelor cu
caracter științific, care au evoluat spre limbaje bazate pe conceptele de programare
structurată, abstractizare, modularizare, programare orientată pe obiecte (Algol,
Fortran, Pascal, C, Modula-2, Ada, C++, Java, etc.)
• limbaje bazate pe prelucrarea datelor (Cobol) care au evoluat spre sisteme de
gestiune de baze de date - sisteme care permit rezolvarea problemelor specifice
bazelor de date - (FoxPro, SQL, etc.)
• limbaje bazate pe prelucrarea listelor (Lisp) care au evoluat în limbaje specifice
inteligenței artificiale - domeniu al informaticii care-și propune simularea pe
sistemele de calcul a comportamentelor inteligente ale ființelor umane- (Prolog,
Mercury).

Se pot evidenția astfel cinci “generații” de limbaje de programare. Pe parcursul


fiecărei generații, sintaxa limbajului a devenit mai ușor de înțeles și citit (human-
readable).
• 1GL – First generation languages – sunt limbajele de programare care sunt
specifice calculatorului (cod-mașină). Acest prim tip de programare este
dificil, iar programatorul trebuie să dețină cunoștințe detaliate despre
procesorul respectiv.

8
Programarea calculatoarelor și limbaje de programare – notițe de curs/ Curs 3

• 2GL – Second generation languages – sunt cunoscute ca limbaje de asamblare,


iar codul scris în aceste limbaje este convertit in cod mașina (1GL). Acestea
sunt considerate limbaje low-level.
• 3GL – Third generation languages – Conceptele/instrucțiunile prezente in
aceste limbaje preiau mult din responsabilitățile programatorului. Limbajele
din aceasta generație sunt cunoscute ca limbaje “high-level”: Java, C, C++,
C#. (Limbajul C este uneori încadrat în categoria limbajelor de “nivel mediu”
deoarece permite scrierea de programe destinate unei palete diverse de
domenii, începând cu aplicații scrise în mod tradițional în limbaje de
asamblare, continuând cu compilatoare, editoare de text, și până la aplicații
numerice complexe, baze de date, etc).
• 4GL – Fourth generation languages – sunt apropiate de limbajul uman, fiind
îmbunătățite față de generația precedentă. Aceste limbaje sunt folosite în mod
special pentru accesarea bazelor de date. Au fost construite cu scopul de a
reduce costurile dezvoltării software, de a reduce eforturile programatorului.
• 5GL – Fifth generation languages – au fost concepute cu scopul de a rezolva
probleme fără intervenția programatorului. Sunt folosite în domeniul
inteligenței artificiale. Exemplu: Prolog, Mercury.

III.2. Etapele dezvoltării unui program

Această secțiune a fost discutată în lucrarea de laborator 1.


Odată cu creșterea complexității programelor și a numărului de utilizatori crește
și costul programelor. A apărut astfel necesitatea elaborării unei metodologii a
programării (pentru a obține în mod economic programe sigure și care funcționează
eficient). Există patru faze fundamentale ale metodologiilor ingineriei programării:
- analiza (ce dorim să construim);
- proiectarea (cum vom construi);
- implementarea (construirea propriu-zisă);
- testarea (asigurarea calității).

9
Mădălina Roxana. Buneci

Deși aceste faze se referă în mod special la ciclul de viață al produsului software,
ele pot fi aplicate și altor stadii de existenta prin care trece un program de la creare până
la ieșire din uz (lansare, întreținere, ieșire din uz).

III.3. Modalități de descriere a sintaxei unui limbaj

Vocabularul unui limbaj de programare este format din cele mai simple
elemente cu semnificație lingvistică, numite unități lexicale. Sintaxa unui limbaj de
programare este definită de un ansamblu de reguli pe baza cărora se combină
elementele vocabularului pentru a obține fraze corecte (instrucțiuni, secvențe de
instrucțiuni, declarații de variabile, constante, subprograme, etc.). O unitate sintactică
este o mulțime de fraze alcătuite din elemente ale vocabularului. Dintre modalitățile de
descriere a sintaxei unui limbaj prezentăm:
➢ formalismul BNF (acronim pentru Backus Naur Form);
➢ diagramele sintactice.
Varianta de BNF pe care o prezentăm mai departe utilizează următoarele
simboluri:
  :: = | { } [ ]
cu următoarele semnificații:
•  și  sunt folosite pentru delimitarea numelui unității sintactice
• :: = apare după o unitate sintactică și are sensul “unitatea sintactică este
definită prin”
• | este utilizat pentru a separa între ele mai multe variante de definire ale unei
unități sintactice. De exemplu, putem defini
<cifra zecimală> :: = 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 |9
• { și } denotă repetarea posibilă (de zero sau de mai multe ori) a
simbolurilor pe care le delimitează. În general, u :: = {<v>} se folosește
ca prescurtare pentru definiția recursivă <u> ::=<vid> |<u><v>, unde <vid>
este elementul neutru la concatenarea unităților sintactice. Următoarele
două definiții sunt echivalente:

10
Programarea calculatoarelor și limbaje de programare – notițe de curs/ Curs 3

< întreg pozitiv> :: = <cifra zecimală>{<cifra zecimală>}


< întreg pozitiv> :: = <cifra zecimală> | < întreg pozitiv> <cifra
zecimală>
• [ și ] denotă prezența opțională a simbolurilor pe care le delimitează. Spre
exemplu,
<întreg> :: = [+ | -] < întreg pozitiv>
definește unitatea sintactică ale cărei elemente sunt de forma +x, -x, x cu x
din unitatea sintactică < întreg pozitiv>.
O definiție în notația BNF poate fi privită ca o expresie în care operanzii sunt
numele de unități sintactice, iar operatorii sunt: concatenare, |, ::= (enumerați în ordinea
descrescătoare a priorităților). Expresia se evaluează ținând seama de prioritatea
operatorilor, cu convenția uzuală în ceea ce privește parantezele { } și [].

Diagramele sintactice realizează o reprezentare mai intuitivă decât cea dată de


notația BNF. Prezentăm în continuare echivalentul în notație prin diagrame a unor
definiții în notație BNF.
• Definiția <u> :: = a1a2…an se reprezintă prin diagrama:
<u>
a1 … an

• Definiția <u> :: = a1 | a2 | … | an se reprezintă prin diagrama:


<u>
a1
a2

an

• Definiția <u> :: = x{yx} se reprezintă prin diagrama:


<u>
x
y

11
Mădălina Roxana. Buneci

• Definiția <u> :: = [x] se reprezintă prin diagrama:


<u>
x

12

S-ar putea să vă placă și