Sunteți pe pagina 1din 164

Structuri de date şi algoritmi

_______________________________________________________________________________
CUPRINS

BAZELE ALGORITMILOR
Introducere
Descrierea Algoritmilor
-algoritm, program, programare
-scheme logice
-limbajul Pseudocod
-calculul efectuat de un algoritm
-rafinare in pasi succesivi
-probleme propuse
-test
Subalgoritmi
-conceptul de subalgoritm
-apelul unui subalgoritm
-elaborarea algoritmului
-proiectarea ascendenta si descendenta
-proiectarea modulara
-apel recursiv
-programare structurata
-probleme propuse
-test de verificare
Limbajul Pascal
-mediul de programare Turbo Pascal
-programe pascal simple
-subprograme
-tipuri de date
-probleme propuse
-test de verificare

STRUCTURI DE DATE
Introducere
Lista simplu inlantuita
Teorie generala
-definitia componentelor listei
-metoda statica
-metoda dinamica
-operatii specifice listei
Test de verificare
Lista dublu inlantuita
Teorie generala
-definitia componentelor listei

________________________________________________________________________________

1
Structuri de date şi algoritmi
_______________________________________________________________________________
-metoda statica
-metoda dinamica
-operatii specifice listei
Test de verificare
Stiva
Teorie generala
-definitia componentelor stivei
-metoda statica
-metoda dinamica
-operatii specifice stivei
Simularea stivei
Probleme propuse
Test de verificare
Probleme rezolvate
Coada
Teorie generala
-referitor la coada
-alocarea secventiala
alocarea dinamica
Simularea functionarii cozii
Probleme propuse
Probleme rezolvate
Arborele Binar
Teorie generala
-generalitati
-definitia elementelor arborelui
-implementarea in pascal
-operatii specifice
Simularea operatii
Test de verif
Probleme rezolvata
Graful
Teorie generala
Exemple

COMPLEXITATEA ALGORITMILOR
-etapele rezolvarii unei probleme
-notiune de algoritm si de program
-modelul abstract al masinii Turing
-teza Turing-Church
-analiza , proiectarea si implementarea algoritmilor
-operatia de baza cheia studiului complexitatii
-clase de algoritmi
-eficienata algoritmi

________________________________________________________________________________

2
Structuri de date şi algoritmi
_______________________________________________________________________________
-functii de complexitate
-apartenenta la o clasa de complexitate
-limita mimimala sau efort minimal
-algoritm optimal
-dificultatea problemelor
-complexitatea de timp
-probleme rezonabile si probleme nerezonabile\
-probleme de decizie si de optimizare
-clasa problemelor NP
-reductia polinamiala a problemelor
-echivalarea problemelor prin reductie polinopmiala
-teorema lui Cook
-clasa probleme NP
-marea intrebare a complexitatii algoritmilor
-algoritmi aproxiamtivi
-probleme insolvabile algoritmic
-complexiatea algoritmilor , calculatorul si viitorul
-test de verificare

TEHNICI DE CAUTARE
Cautare secventiala
Teorie generala
-descrierea generala a metodei
-descrierea algoritmului in pseudocod
-implementarea algoritmului in Pascal
-implementarea algoritmului in C
-performanta metodei de cautare
Probleme rezolvate
Probleme propuse
Simularea metodei de cautare

Cautare binara
Teorie generala
-descrierea generala a metodei
-descrierea algoritmului de cautare in Pseudocod
-implementarea algoritmului in Pascal
-implementarea algoritmului in C
-performanata metodei de cautare binara
Probleme rezolvate
Probleme propuse
Simularea metodei de cautare binara
Test de verificare

Arbori

________________________________________________________________________________

3
Structuri de date şi algoritmi
_______________________________________________________________________________
Arbore binar balansat in inaltime
-introducere
-descriere generala
-descrierea algoritmului in Pseudocod
-implementarea algoritmului in Pascal
-implementarea algoritmului in C
-performanata arbore binar balansat in inaltime
-simularea cautarii in arborele balansat in inaltime
-test de verificare
Arbore 2-3
-descriere generala
-descrierea algoritmului de cautare in Pseudocod
-implementarea alg in Pascal
-performanata arborelui 2-3
-simularea arborelui 2-3 in algoritmul de cautare
test de verificare

Arbore balansat in greutate


-descrierea generala a arborelui
-descrierea algoritmului de cautare in Pseudocod
-implementarera algoritmului in Pascal
-performanta arborelui balansat in greutate
-simularea arborelui balansat in greutate in algoritmul cautarii
-test de verificare
Probleme rezolvate
Probleme propuse

Tablele Hasing
Introducere
Functia Hash
-generalitati a functiilor hash
-metoda Impartirii
-metoda Patrat Mediu
-metoda Indoirii
-metoda Digit Analysis
-metoda dependenta de lungimea cheii
-metoda codarii algebrice
-metoda Hashing Multiplicativ
Performanta functiei hash
Simularea metodei Impartirii
Test de verificare

Tehnici de rezolvare a coleziunilor


Introducere
Probarea liniara
-descrierea generala a metodei

________________________________________________________________________________

4
Structuri de date şi algoritmi
_______________________________________________________________________________
-descrierea lagorotmului de cautare inserare in Pseudocod
-implementarea algoritmului in Pascal
-implementarea algoritmului in C
-performanta metodei
Probarea aleatoare
-descrierea generala a metodei
-descrierea algoritmului de cautare inserare in Pseuducod
-implementarea algoritmului in Pascal
Hashing multiplicativ
-descrierea generala a metodei
-descrierea algoritmului de cautare inserare in Pseuducod
-implementarea algoritmului in Pascal
-implementarea algoritmului in C
-performanta metodei
Inlantuire separata (Separate Chaining)
-descrierea generala a metodei
-descrierea algoritmului de cautare inserare in Pseuducod
-implementarea algoritmului in C
-performanta metodei
Buckets
-descrierea generala a metodei
-descrierea algoritmului de cautare inserare in Pseuducod
-implementarea algoritmului in C
Probleme propuse
Simularea tehnicii Probarii aleatoare
Test de verificare

TEHNICI DE SORTARE
Istoric
Prezentare generala

BubleSort
-descrierea generala metodei
-implementarea algoritmului in Pascal si C
-complexitatea algoritmului
-probleme rezolvate
-probleme propuse
-simulare
-test de verificare

SelectionSort
-descrierea generala metodei
-implementarea algoritmului in Pascal si C
-complexitatea algoritmului

________________________________________________________________________________

5
Structuri de date şi algoritmi
_______________________________________________________________________________
-probleme rezolvate
-probleme propuse
-simulare
-test de verificare

InsertSort
-descrierea generala metodei
-implementarea algoritmului in Pascal si C
-complexitatea algoritmului
-probleme rezolvate
-probleme propuse
-simulare
-test de verificare

ShellSort
-descrierea generala metodei
-implementarea algoritmului in Pascal si C
-complexitatea algoritmului
-probleme rezolvate
-probleme propuse
-simulare
-test de verificare

QuickSort
-descrierea generala metodei
-imbunatatiri
-probleme rezolvate
-probleme propuse
-simulare
-test de verificare

MergeSort
-descrierea generala metodei
-imbunatatiri
-probleme rezolvate
-probleme propuse
-simulare
-test de verificare

HeapSort
-descrierea generala metodei
-probleme rezolvate
-probleme propuse
-simulare
-test de verificare

________________________________________________________________________________

6
Structuri de date şi algoritmi
_______________________________________________________________________________
RadixSort
-descrierea generala metodei
-probleme rezolvate
-probleme propuse
-simulare
-test de verificare

TEHNICI DE PROGRAMARE
Backtracking
Greedy
Programarea dinamica
Branch&Bound
Test de verificare
Exemple

Introducere

________________________________________________________________________________

7
Structuri de date şi algoritmi
_______________________________________________________________________________
Progresele tehnologice în domeniul informaticii au condus la creşterea puterii de calcul şi a
memoriei calculatoarelor electronice, la ieftinirea acestor calculatoare, permiţând abordarea unor
probleme extrem de complexe, din toate sferele activităţii umane. Calculatorul a devenit un
instrument indispensabil pentru viaţa omului modern. Penetrarea Internetului şi a accesului la
resursele informatice mondiale, lărgirea considerabilă a spectrului de aplicaţii a contribuit şi
contribuie la impunerea calculatorului ca instrument de lucru vital în activitatea omului. În
consecinţă, cerinţa de noi programe în toate domeniile activităţii umane a crescut mereu.
  De aceea, se impune tot mai pregnant creşterea importanţei metodologilor de realizare a
produselor program, necesitatea unei instruiri corespunzătoare a noilor generaţii de specialişti şi de
utilizatori ai calculatoarelor. În formarea specialiştilor în informatică este importantă dobândirea
unor cunoştinţe şi deprinderi de a realiza produse program de o complexitate variată, care să
satisfacă cerinţele beneficiarului. Pentru aceasta este necesară respectarea unei discipline de lucru şi
cunoaşterea unei metodologii adecvate de proiectare şi realizare a acestor produse software. În
trecutul nu prea îndepărtat, în activitatea de instruire a specialiştilor în informatică s-a pus un mai
mare accent pe învăţarea unor limbaje de programare şi pe programarea propriu-zisă, acordându-se
o pondere mai mică analizei şi proiectării programelor. Specializarea în informatică nu înseamnă
doar cunoaşterea limbajelor de programare, ci în primul rând însuşirea metodelor moderne de
analiză şi proiectare a aplicaţiilor soft.
Scopul acestei cărţi este de a contribui la învăţarea programării ca o disciplină unitară având
o fundaţie ştiinţifică solidă, necesară programatorilor. Pentru aceasta este nevoie să încercăm să
definim mai clar noţiunea de programare.
Conform cu Sedgewitz [1990], programarea poate fi privită ca o activitate generală de a
extinde sau a de a modifica funcţionalităţile unui sistem. Este o activitate generală deoarece ea
poate fi efectuată atât de specialişti (programatori) cât şi de nespecialişti (setarea unui telefon
celular sau a unei alarme de acces într-o încăpere). Din acest punct de vedere activitatea de
programare constă din două elemente fundamentale: o componentă tehnologică şi o componentă
ştiinţifică. Partea ştiinţifică include elemente printre care se regăsesc structurile de date şi algoritmii
necesari descrierii operaţiilor de manipulare a acestor structuri de date. Această abordare ne permite
o independenţa a acestor structuri de date faţă de limbajele de programare şi faţă de tehnologiile de
proiectare utilizate in activitatea de programare.
Programele sunt făcute pentru a rezolva probleme din viaţa de zi cu zi. Informaţia prelucrată
de un program este stocată cu ajutorul structurilor de date. Aceste structuri de date grupează într-o
formă eficientă datele. O structură de date corect definită poate simplifica operaţia care prelucrează
acele date sau o poate complica. De aceea structurile de date au o importanţă majoră în activitatea
de proiectare si programare.
Programele prelucrează informaţii. Informaţiile sunt organizate sub formă de structuri de
calcul. Modul în care reprezentăm structurile de date afectează claritatea, conciziunea, viteza de
rulare şi capacitatea de stocare a programului. Dezvoltarea unui program complex este dificilă dacă
programatorul nu posedă cunoştinţe în domeniul structurilor de date, algoritmicii şi a tehnicilor de
programare.
Dacă nu se cunoaşte o soluţie pentru rezolvarea unei probleme nu există o soluţie magică de
a scrie un program care să rezolve acea problema. Un proverb afirmă că dacă nu ştii cum să ajungi
într-un anumit loc atunci nu are importanţă cum ajungi în acel loc. Mai mult chiar Murphi afirmă că

________________________________________________________________________________

8
Structuri de date şi algoritmi
_______________________________________________________________________________
orice problemă complexă are o soluţie simplă eronată. De aceea, înainte de a implementa un
program într-un anumit limbaj de programare, este necesară înţelegerea problemei, descrierea pas
cu pas a soluţiei, cu alte cuvinte este necesar a descrie problema prin intermediul unui algoritm.
Din păcate nu există un algoritm care să aleagă algoritmul care rezolvă orice problemă. Ceea
ce există sunt o serie de tehnici de dezvoltare a algoritmilor, numite în general tehnici de
programare.
Scopul principal al lucrării de faţă este familiarizarea celor care doresc să o înveţe, cu
activitatea de programare. Vom vedea în ce constă această activitate şi vom remarca accentul pus pe
gândirea omului, pe proiectarea algoritmilor şi corectitudinea lor, calculatorul fiind doar unealta
care execută ceea ce "dictează" programatorul. Pentru a putea dicta calculatorului avem nevoie de
un limbaj înţeles de ambele părţi; limbajul ales în acest scop este limbajul Pascal. De asemenea,
vom prezenta metodele de programare cunoscute în prezent şi discutate în literatura de specialitate.
Vom învăţa să analizăm algoritmii, dar vom fi mai puţin preocupaţi de măsurarea complexităţii unui
program, accentul fiind pus pe modul în care el poate fi obţinut. Noţiunile şi conceptele prezentate
sunt însoţite de exemple simple, în care s-au folosit: limbajul Pseudocod în descrierea algoritmilor,
un limbaj de specificare informal în descrierea tipurilor abstracte de date şi limbajul Pascal pentru
codificare.

I. BAZELE ALGORITMILOR

I.1. DESCRIEREA ALGORITMILOR

I.1.1. Algoritm, program, programare

Ce este un algoritm? O definiţie matematică, riguroasă, este greu de dat, chiar imposibilă fără
a introduce şi alte noţiuni (Giumale [2004]). Vom încerca în continuare o descriere a ceea ce se
înţelege prin algoritm.
Vom constata că un algoritm este un text finit, o secvenţă finită de propoziţii ale unui limbaj.
Din cauză că este inventat special în acest scop, un astfel de limbaj este numit limbaj de descriere a
algoritmilor. Fiecare propoziţie a limbajului precizează o anumită regulă de calcul, aşa cum se va
observa atunci când vom prezenta limbajul Pseudocod.
Algoritmii au următoarele caracteristici: generalitate, finitudine şi unicitate (Livovschi [1980])

În descrierea algoritmilor se folosesc mai multe limbaje de descriere, dintre care cele mai des
folosite sunt:
- limbajul schemelor logice;

________________________________________________________________________________

9
Structuri de date şi algoritmi
_______________________________________________________________________________
- limbajul Pseudocod.

I.1.2. Scheme logice

Schema logică este un mijloc de descriere a algoritmilor prin reprezentare grafică. Regulile
de calcul ale algoritmului sunt descrise prin blocuri (figuri geometrice) reprezentând operaţiile
(paşii) algoritmului, iar ordinea lor de aplicare (succesiunea operaţiilor) este indicată prin săgeţi.
Fiecărui tip de operaţie îi este consacrată o figură geometrică (un bloc tip) în interiorul căreia se va
înscrie operaţia din pasul respectiv (Livovschi [1980)..
Prin execuţia unui algoritm descris printr-o schemă logică se înţelege efectuarea tuturor
operaţiilor precizate prin blocurile schemei logice, în ordinea indicată de săgeţi.
În descrierea unui algoritm, deci şi într-o schemă logică, intervin variabile care marchează
atât datele cunoscute iniţial, cât şi rezultatele dorite, precum şi alte rezultate intermediare necesare
în rezolvarea problemei. Întrucât variabila joacă un rol central în programare este bine să definim
acest concept.
Variabila defineşte o mărime care îşi poate schimba valoarea în timp. Ea are un nume şi o
valoare. Este posibil ca variabila încă să nu aibă asignată o valoare, situaţie în care vom spune că ea
este neiniţializată.
Valorile pe care le poate lua variabila aparţin unei mulţimi D pe care o vom numi domeniul
variabilei. Variabila este folosită cu un anumit scop, ea notează o anumită mărime, cu o anumită
semnificaţie. În concluzie o variabilă este cunoscută prin (nume, domeniul D, valoare,
semnificaţie), unde valoarea aparţine mulţimii D {nedefinit}.
Pentru claritatea textului unui algoritm este important să se cunoască semnificaţia fiecărei
variabile, să fie menţionată expres această semnificaţie. În nici un caz nu se recomandă să se
folosească acelaşi nume pentru variabile cu semnificaţii diferite, caz care provocă confuzii şi chiar
erori grave. Vom enunţa acestea sub forma unor reguli cu care e bine să ne obişnuim şi să le
respectăm în activitatea de programare.

I.1.3. Limbajul PSEUDOCOD

Limbajul Pseudocod este un limbaj folosit în scopul proiectării algoritmilor şi este format
din propoziţii asemănătoare propoziţiilor limbii române, care corespund structurilor de calcul
folosite în construirea algoritmilor. Acesta va fi limbajul folosit de noi în proiectarea algoritmilor şi
va fi definit în cele ce urmează.
Ţinând seama că realizarea unui algoritm pentru rezolvarea unei probleme nu este
întotdeauna o sarcină simplă, că în acest scop sunt folosite anumite metode pe care le vom descrie
în capitolele următoare, în etapele intermediare pentru obţinerea algoritmului vom folosi propoziţii
curente din limba română (Frentiu, Groze [1986]).
Acestea sunt considerate elemente nefinisate din algoritm, asupra cărora trebuie să se revină
şi le vom numi propoziţii nestandard. Deci limbajul Pseudocod are două tipuri de propoziţii:
propoziţii standard, care vor fi prezentate fiecare cu sintaxa şi semnificaţia (semantica) ei şi

________________________________________________________________________________

10
Structuri de date şi algoritmi
_______________________________________________________________________________
propoziţii nestandard. Aşa cum se va arăta mai târziu, propoziţiile nestandard sunt texte care descriu
părţi ale algoritmului încă incomplet elaborate, nefinisate, asupra cărora urmează să se revină. Ele
se recunosc prin faptul că încep întotdeauna cu semnul '@' şi se termină cu punctul obişnuit
(caracterul '.').
Pe lângă aceste propoziţii standard şi nestandard, în textul algoritmului vom mai introduce
propoziţii explicative, numite comentarii. Pentru a le distinge de celelalte propoziţii, comentariile
vor fi închise între acolade. Rolul lor va fi explicat puţin mai târziu.
Prin execuţia unui algoritm descris în Pseudocod se înţelege efectuarea operaţiilor precizate
de propoziţiile algoritmului, în ordinea citirii lor (de sus în jos şi de la stânga spre dreapta).
Propoziţiile standard ale limbajului Pseudocod folosite în această lucrare, corespund
structurilor de calcul prezentate în Figura 1.1 şi vor fi prezentate în continuare.
În Figura I.1, prin A, B s-au notat subscheme logice, adică secvenţe de oricâte structuri
construite conform celor trei reguli menţionate în continuare.
Structura secvenţială (Figura I.1.a) este redată prin concatenarea propoziţiilor, simple sau
compuse, ale limbajului Pseudocod, care vor fi executate în ordinea întâlnirii lor în text.
Propoziţiile simple din limbajul Pseudocod sunt CITEŞTE, TIPAREŞTE, FIE şi apelul de
subprogram. Propoziţiile compuse corespund structurilor alternative şi repetitive.
Structura alternativă din Figura I.1.b este redată în Pseudocod prin propoziţia DACĂ,
prezentată în secţiunea 2.3.2, iar structura repetitivă din Figura I.1.c este redată în Pseudocod prin
propoziţia CÂTTIMP, prezentată în secţiunea 2.3.3.
Böhm şi Jacopini [1966] au demonstrat că orice algoritm poate fi descris folosind numai
aceste trei structuri de calcul.
Propoziţiile DATE şi REZULTATE sunt folosite în faza de specificare a problemelor, adică
enunţarea riguroasă a acestora.
 

Figura 1.1. Structuri elementare de calcul.

________________________________________________________________________________

11
Structuri de date şi algoritmi
_______________________________________________________________________________

Fiecare propoziţie standard începe cu un cuvânt cheie, aşa cum se va vedea în cele ce
urmează. Pentru a deosebi aceste cuvinte de celelalte denumiri, construite de programator, în acest
capitol vom scrie cuvintele cheie cu litere mari. Menţionăm că propoziţiile simple se termină cu
caracterul ';' în timp ce propoziţiile compuse, deci cele în interiorul cărora se află alte propoziţii, au
un marcaj de sfârşit propriu. De asemenea, menţionăm că propoziţiile limbajului Pseudocod vor fi
luate în seamă în ordinea întâlnirii lor în text, asemenea oricărui text al limbii române.
Propoziţia DATE se foloseşte pentru precizarea datelor iniţiale, deci a datelor considerate
cunoscute în problemă (numite şi date de intrare) şi are sintaxa:

DATE listă ;

unde listă conţine toate numele variabilelor a căror valoare iniţială este cunoscută.
În general, prin listă se înţelege o succesiune de elemente de acelaşi fel despărţite prin
virgulă. Deci în propoziţia DATE, în dreapta acestui cuvânt se vor scrie acele variabile care
marchează mărimile cunoscute în problemă.
 Pentru precizarea rezultatelor dorite se foloseşte propoziţia standard:

REZULTATE listă;

 în construcţia "listă" ce urmează după cuvântul REZULTATE fiind trecute numele variabilelor
care marchează (conţin) rezultatele cerute în problemă.
 Acum putem preciza mai exact ce înţelegem prin cunoaşterea completă a problemei de
rezolvat. Evident, o problemă este cunoscută atunci când se ştie care sunt datele de intrare ce
definesc problema dată şi rezultatele ce trebuiesc obţinute.
Deci pentru cunoaşterea unei probleme este necesară precizarea variabilelor care marchează
datele considerate cunoscute în problemă, care va fi reflectată printr-o propoziţie DATE şi
cunoaşterea exactă a rezultatelor problemei, care se va reflecta prin propoziţia REZULTATE.
Variabilele prezente în aceste propoziţii au anumite semnificaţii, presupuse cunoscute.
Cunoaşterea acestora, scrierea lor explicită, formează ceea ce vom numi în continuare specificarea
problemei. Specificarea unei probleme este o activitate foarte importantă dar nu şi simplă.
 De exemplu, pentru rezolvarea ecuaţiei de gradul al doilea, specificarea problemei, poate
fi:

 DATE a,b,c; { Coeficienţii ecuaţiei }


 REZULTATE x1,x2; { Rădăcinile ecuaţiei }

 Această specificaţie este însă incompletă. Nu întotdeauna ecuaţia are rădăcini reale. În cazul

________________________________________________________________________________

12
Structuri de date şi algoritmi
_______________________________________________________________________________
în care rădăcinile sunt complexe putem nota prin x1, x2 partea reală, respectiv partea imaginară a
rădăcinilor. Sau pur şi simplu, nu ne interesează valoarea rădăcinilor în acest caz, ci doar faptul că
ecuaţia nu are rădăcini reale.
Cu alte cuvinte avem nevoie de un mesaj care să ne indice această situaţie, sau de un
indicator, fie el numit ind. Acest indicator va lua valoarea 1 dacă rădăcinile sunt reale şi valoarea 0
în caz contrar. Deci specificaţia mai completă a problemei este:

DATE a,b,c; { Coeficienţii ecuaţiei }


REZULTATE ind, {ind = 1 pt. rădăcini reale, 0 pt complexe}
x1,x2; {Rădăcinile ecuaţiei, în cazul ind = 1, respectiv}
 {partea reală şi cea imaginară în cazul ind = 0}
 
Evident că specificarea problemei este o etapă importantă pentru găsirea unei metode de
rezolvare şi apoi în proiectarea algoritmului corespunzător. Nu se poate rezolva o problemă dacă
aceasta nu este bine cunoscută, adică nu avem scrisă specificarea problemei.
“Cunoaşte complet problema” este prima regulă ce trebuie respectată pentru a obţine cât mai
repede un algoritm corect pentru rezolvarea ei.

I.1.3.1. Algoritmi liniari

 Propoziţiile CITEŞTE şi TIPĂREŞTE sunt folosite pentru iniţializarea variabilelor de


intrare cu datele cunoscute în problemă, respectiv pentru tipărirea (aflarea) rezultatelor obţinute. În
etapa de programare propriu-zisă acestor propoziţii le corespund într-un limbaj de programare
instrucţiuni de intrare-ieşire.
 Propoziţia CITEŞTE se foloseşte pentru precizarea datelor iniţiale, deci a datelor
considerate cunoscute în problemă (numite şi date de intrare) şi are sintaxa:

CITEŞTE listă ;

unde listă conţine toate numele variabilelor a căror valoare iniţială este cunoscută.
 Deci în propoziţia CITEŞTE, în dreapta acestui cuvânt se vor scrie acele variabile care apar
în propoziţia DATE în specificarea problemei. Se subînţelege că aceste variabile sunt iniţializate cu
valorile cunoscute corespunzătoare.
 Pentru aflarea rezultatelor dorite, pe care calculatorul o va face prin tipărirea lor pe hârtie
sau afişarea pe ecran, se foloseşte propoziţia standard:

TIPĂREŞTE listă ;

________________________________________________________________________________

13
Structuri de date şi algoritmi
_______________________________________________________________________________

în construcţia listă ce urmează după cuvântul TIPĂREŞTE fiind trecute numele variabilelor a căror
valori dorim să le aflăm. Ele sunt de obicei rezultatele cerute în problemă, specificate şi în
propoziţia REZULTATE.
 Blocului de atribuire dintr-o schemă logică îi corespunde în Pseudocod propoziţia standard:

[FIE] var := expresie ;

 Această propoziţie este folosită pentru a indica un calcul algebric, al expresiei care urmează
după simbolul de atribuire ":=" şi de atribuire a rezultatului obţinut variabilei var. Expresia din
dreapta semnului de atribuire poate fi orice expresie algebrică simplă, cunoscută din manualele de
matematică din liceu şi construită cu cele patru operaţii: adunare, scădere, înmulţire şi împărţire
(notate prin caracterele +, -, *, respectiv /).
 Prin scrierea cuvântului FIE între paranteze drepte se indică posibilitatea omiterii acestui
cuvânt din propoziţie. El s-a folosit cu gândul ca fiecare propoziţie să înceapă cu un cuvânt al limbii
române care să reprezinte numele propoziţiei. De cele mai multe ori vom omite acest cuvânt. Atunci
când vom scrie succesiv mai multe propoziţii de atribuire vom folosi cuvântul FIE numai în prima
propoziţie, omiţându-l în celelalte.
 Din cele de mai sus rezultă că o variabilă poate fi iniţializată atât prin atribuire (deci dacă
este variabila din stânga semnului de atribuire :=) cât şi prin citire (când face parte din lista
propoziţiei CITEŞTE). O greşeală frecventă pe care o fac începătorii este folosirea variabilelor
neiniţializate. Evident că o expresie în care apar variabile care nu au valori nu poate fi calculată, ea
nu este definită. Deci nu folosiţi variabile neiniţializate.
 Pentru a marca începutul descrierii unui algoritm vom folosi propoziţia:
 
ALGORITMUL nume ESTE:

De asemenea, prin propoziţia:


SFALGORITM

vom marca sfârşitul unui algoritm.


 Algoritmii care pot fi descrişi folosind numai propoziţiile prezentate mai sus se numesc
algoritmi liniari.
 Ca exemplu de algoritm liniar prezentăm un algoritm ce determină viteza v cu care a mers
un autovehicul ce a parcurs distanţa D în timpul T.
Exemplul I.1.

 ALGORITMUL VITEZA ESTE: { Algoritmul 1: Calculează viteza }

________________________________________________________________________________

14
Structuri de date şi algoritmi
_______________________________________________________________________________
 { D = Distanţa (spaţiul) }
 { T = Timpul; V = Viteza }
 CITEŞTE D,T; { v:= spaţiu/timp }
 FIE V:=D/T;
 TIPĂREŞTE V
 SFALGORITM

I.1.3.2. Algoritmi cu ramificaţii

 Foarte mulţi algoritmi execută anumite calcule în funcţie de satisfacerea unor condiţii.
Aceste calcule sunt redate de structura alternativă prezentată în Figura I.1.b, căreia îi corespunde
propoziţia Pseudocod:
DACĂ cond
ATUNCI A
ALTFEL B
SFDACĂ

sau varianta redusă a ei:

DACĂ cond
ATUNCI A
SFDACĂ
 folosită în cazul în care grupul de propoziţii B este vid.
 Aceste propoziţii redau în Pseudocod structura alternativă de calcul. În primul rând este
necesară verificarea condiţiei scrise după cuvântul DACĂ. În cazul că această condiţie este
adevărată se va executa grupul de propoziţii A. În cazul în care această condiţie este falsă se va
executa grupul de propoziţii B, dacă este prezentă ramura ALTFEL. Indiferent care dintre
secvenţele A sau B a fost executată, se va continua cu propoziţia următoare propoziţiei DACĂ, ce
urmează după marcatorul de sfârşit SFDACĂ.
 O generalizare a structurii alternative realizată de propoziţia DACĂ este structura selectivă:

SELECTEAZĂ i DINTRE
v1: A1;
v2: A2;
...

________________________________________________________________________________

15
Structuri de date şi algoritmi
_______________________________________________________________________________
vn: An
SFSELECTEAZĂ

structură echivalentă cu următorul text Pseudocod:

DACĂ i = v1
ATUNCI A1
ALTFEL
DACĂ i = v2
ATUNCI A2
ALTFEL
...
DACĂ i = vn
ATUNCI An
SFDACĂ
...
SFDACĂ
SFDACĂ

Cu propoziţiile prezentate până acum putem descrie un număr însemnat de algoritmi.


Aceştia se numesc algoritmi cu ramificaţii.
De exemplu, vom descrie un algoritm pentru rezolvarea ecuaţiei de gradul al doilea. Am
prezentat în I.1.3 această problemă şi am precizat semnificaţia variabilelor respective. Pe lângă
aceste variabile, pentru rezolvarea problemei mai avem nevoie de două variabile auxiliare:
delta - pentru discriminantul ecuaţiei;
r - pentru valoarea radicalului folosit în calculul rădăcinilor.

Exemplul I.2.

ALGORITMUL ECGRDOI ESTE: { Algoritmul 2: Rezolvarea }


{ ecuaţiei de gradul doi }
CITEŞTE a,b,c; { a,b,c = coeficienţii ecuaţiei }
FIE delta:= b*b-4*a*c;
DACĂ delta < 0

________________________________________________________________________________

16
Structuri de date şi algoritmi
_______________________________________________________________________________
ATUNCI ind:= 0; {Cazul rădăcini complexe }
@r:=radical din (-delta);
x1:=-b/(a+a);
x2:= r/(a+a);
ALTFEL ind:=1; {cazul rădăcini reale }
@r:=radical din delta;
x1:=(-b-r)/(a+a);
x2:=(-b+r)/(a+a);
SFDACĂ
TIPĂREŞTE ind, x1,x2;
SFALGORITM
 

I.1.3.3 Algoritmi ciclici

În rezolvarea multor probleme trebuie să efectuăm aceleaşi calcule de mai multe ori, sau să
repetăm calcule asemănătoare. De exemplu, pentru a calcula suma a două matrice va trebui să
adunăm un element al primei matrice cu elementul de pe aceeaşi poziţie din a doua matrice, această
adunare repetându-se pentru fiecare poziţie în parte. Alte calcule trebuiesc repetate în funcţie de
satisfacerea unor condiţii.
În acest scop în limbajul Pseudocod există trei propoziţii standard: CÂTTIMP, REPETĂ şi
PENTRU. Propoziţia CÂTTIMP are sintaxa:

CÂTTIMP cond EXECUTĂ A SFCÂT

şi cere execuţia repetată a grupului de propoziţii A, în funcţie de condiţia "cond". Mai exact, se
evaluează condiţia "cond"; dacă aceasta este adevărată se execută grupul A şi se revine la evaluarea
condiţiei. Dacă ea este falsă execuţia propoziţiei se termină şi se continuă cu propoziţia care
urmează după SFCÂT.
Dacă de prima dată condiţia este falsă grupul A nu se va executa niciodată, altfel se va
repeta execuţia grupului de propoziţii A până când condiţia va deveni falsă. Din cauză că înainte de
execuţia grupului A are loc verificarea condiţiei, această structură se mai numeşte structură
repetitivă condiţionată anterior. Ea reprezintă structura repetitivă prezentată în Figura I.1.c.
Ca exemplu de algoritm în care se foloseşte această structură ciclică să rezolvăm algoritmul
lui Euclid pentru calculul celui mai mare divizor comun a două numere.

________________________________________________________________________________

17
Structuri de date şi algoritmi
_______________________________________________________________________________

Exemplul I.3.

ALGORITMUL Euclid ESTE: {Algoritmul 3: Cel mai mare divizor comun}


CITEŞTE n1,n2; {Cele două numere a căror divizor se cere}
FIE d:=n1; i:=n2;
CÂTTIMP i ≥0 EXECUTĂ
r:=d modulo i; d:=i; i:=r
SFCÂT
TIPĂREŞTE d; { d= cel mai mare divizor comun}
SFALGORITM {al numerelor n1 şi n2 }

În descrierea multor algoritmi se întâlneşte structura repetitivă condiţionată posterior:

REPETĂ A PÂNĂCÂND cond SFREP


 structură echivalentă cu:
 
A
CÂTTIMP not (cond) EXECUTĂ A SFCÂT

 Deci ea cere execuţia necondiţionată a lui A şi apoi verificarea condiţiei "cond". Va avea
loc repetarea execuţiei lui A până când condiţia devine adevărată. Deoarece condiţia se verifică
după prima execuţie a grupului A această structură este numită structura repetitivă condiţionată
posterior, prima execuţie a blocului A fiind necondiţionată.
 Ca exemplu de algoritm în care se foloseşte această propoziţie vom scrie un algoritm pentru
aproximarea numărului e cu o precizie dată de numărul eps pozitiv, folosindu-ne de dezvoltarea în
serie:
e = 1 + 1/1! + 1/2! + 1/3! + ... + 1/n! + ...
Specificaţia problemei în Pseudocod este:

 DATE eps ; { eps>0 }


 REZULTATE ve; { aproximarea seriei cu o eroare < eps }
 { deci r=˝e-ve˝<eps }

 Pentru rezolvarea problemei trebuie să ştim că restul rn din scrierea

________________________________________________________________________________

18
Structuri de date şi algoritmi
_______________________________________________________________________________
 e = 1 + 1/1! + 1/2! + 1/3! + ... + 1/n! + ...
 = sn + rn
este mai mic decât ultimul termen adunat la sn, deci rn<tn, unde tn=1/n!. Variabilele auxiliare
folosite în descrierea algoritmului sunt:
 n - precizează ultima fracţie adunată;
 t - este valoarea acestei fracţii.
Exemplul I.4.
 
ALGORITMUL NRE ESTE: { Algoritmul 4:Calculul lui e cu precizia eps }
 CITEŞTE eps; { eps > 0 }
 FIE ve:=2.5; t:=0.5; n:=2;
 REPETĂ
n:=n+1
  t:=t/n;
  ve:=ve+t;
 PÂNĂCÂND t < eps SFREP
 TIPĂREŞTE ve;
 SFALGORITM

 O altă propoziţie care cere execuţia repetată a unei secvenţe A este propoziţia

PENTRU c:=li; lf [;p] EXECUTĂ A SFPENTRU

 Ea defineşte o structura repetitivă predefinită, cu un număr determinat de execuţii ale


grupului de propoziţii A şi este echivalentă cu secvenţa:

 c:=li ; final:=lf ;
 REPETĂ
  A
  c:=c+p
 PÂNĂCÂND (c>final şi p>0) sau (c<final şi p<0) SFREP
 
Se observă că, în sintaxa propoziţiei PENTRU, pasul p este închis între paranteze drepte.
Prin aceasta indicăm faptul că el este opţional, putând să lipsească. În cazul în care nu este prezent,
valoarea lui implicită este 1.

________________________________________________________________________________

19
Structuri de date şi algoritmi
_______________________________________________________________________________
 Semnificaţia propoziţiei PENTRU este clară. Ea cere repetarea grupului de propoziţii A
pentru toate valorile contorului c cuprinse între valorile expresiilor li şi lf (calculate o singură dată
înainte de începerea ciclului), cu pasul p. Se subînţelege că nu trebuie să modificăm valorile
contorului în nici o propoziţie din grupul A. De multe ori aceste expresii sunt variabile simple, iar
unii programatori modifică în A valorile acestor variabile, încălcând semnificaţia propoziţiei
PENTRU. Deci,

Obs: Nu recalcula limitele şi nu modifica variabila de ciclare (contorul) în interiorul unei structuri
repetitive PENTRU.

 Să observăm, de asemenea, că prima execuţie a grupului A este obligatorie, abia după
modificarea contorului verificându-se condiţia de continuare a execuţiei lui A.
 Ca exemplu, să descriem un algoritm care găseşte minimul şi maximul componentelor unui
vector de numere reale.
Vom nota prin X acest vector, deci  X = (x1, x2, ... , xn).
 Specificaţia problemei este următoarea:
 DATE n,(x[i] ,i=1,n);
 REZULTATE valmin,valmax;

 iar semnificaţia acestor variabile se înţelege din cele scrise mai sus. Pentru rezolvarea problemei
vom examina pe rând cele n componente. Pentru a parcurge cele n componente avem nevoie de un
contor care să precizeze poziţia la care am ajuns. Fie i acest contor. Uşor se ajunge la următorul
algoritm:
Exemplul I.5.
 
ALGORITMUL MINMAX ESTE: { Algoritmul 5: Calculul }
{ valorii minime şi maxime }
CITEŞTE n,( x[i],i=1,n);
FIE valmin:=x[1]; valmax:=x[1];
PENTRU i:=2;n EXECUTĂ
DACĂ x[i]<valmin
ATUNCI valmin:=x[i ];
SFDACĂ
DACĂ x[i]>valmax
ATUNCI valmax:=x[i];
SFDACĂ

________________________________________________________________________________

20
Structuri de date şi algoritmi
_______________________________________________________________________________
SFPENTRU
TIPĂREŞTE valmin,valmax;
SFALGORITM
 
Un rol important în claritatea textului unui algoritm îl au denumirile alese pentru variabile.
Ele trebuie să reflecte semnificaţia variabilelor respective. Deci alege denumiri sugestive pentru
variabile, care să reflecte semnificaţia lor. Putem formula astfel regula de mai jos:

Obs:. Alege denumiri sugestive pentru variabile! În exemplul de mai sus denumirile valmin şi
valmax spun cititorului ce s-a notat prin aceste variabile.

I.1.4 Calculul efectuat de un algoritm

Fie X1, X2, ..., Xn, variabilele ce apar în algoritmul A. În orice moment al execuţiei
algoritmului, fiecare variabilă are o anumită valoare, sau este încă neiniţializată.
Vom numi stare a algoritmului A cu variabilele menţionate vectorul s = ( s1,s2,...,sn ) format
din valorile curente ale celor n variabile ale algoritmului.
Este posibil ca variabila Xj să fie încă neiniţializată, deci să nu aibă valoare curentă, caz în
care valoarea sj este nedefinită, lucru notat în continuare prin semnul întrebării '?'.
Prin executarea unei anumite instrucţiuni unele variabile îşi schimbă valoarea, deci
algoritmul îşi schimbă starea.
Se numeşte calcul efectuat de algoritmul A o secvenţă de stări s 0, s1, s2, ..., sm unde s0 este
starea iniţială cu toate variabilele neiniţializate, iar s m este starea în care se ajunge după execuţia
ultimei propoziţii din algoritm.

Exemplul I.6
Algoritmul Nrdivizori calculează numărul de divizori proprii ai unui număr dat X1.

P1 CITESTE X1;
P2 FIE X2:=1;
P3 FIE X3:=0;
P4 CÂTTIMP X1 ≠ X2 EXECUTĂ
P5 X2:=X2+1;
P6 DACĂ X1 modulo X2 = 0
ATUNCI X3:=X3+1

________________________________________________________________________________

21
Structuri de date şi algoritmi
_______________________________________________________________________________
SFDACĂ
SFCÂT
P7 TIPĂREŞTE X1,X3

Presupunând că X1 = 6, atunci stările prin care trece acest algoritm, deci calculul efectuat
de el, este redat mai jos.
s0 = ( ?, ?, ?)
P1(s0) = s1 = ( 6, ?, ?)
P2(s1) = s2 = ( 6, 1, ?)
P3(s2) = s3 = ( 6, 1, 0)
P5(s3) = s4 = ( 6, 2, 0)
P6(s4) = s5 = ( 6, 2, 1)
P5(s5) = s6 = ( 6, 3, 1)
P6(s6) = s7 = ( 6, 3, 2)
P5(s7) = s8 = ( 6, 4, 2)
P6(s8) = s9 = ( 6, 4, 2)
P5(s9) = s10= ( 6, 5, 2)
P6(s10)= s11= ( 6, 5, 2)
P6(s11)= s12= ( 6, 5, 2)
P5(s12)= s13= ( 6, 6, 2)
P7(s13)= s14= ( 6, 6, 2)

Execuţia (calculul) se va încheia cu tipărirea valorilor 6 şi 2 a celor două variabile din


propoziţia P7. Se poate observa că cele două valori tipărite reprezintă, prima (X1), numărul citit, iar
a doua (X3), rezultatul obţinut. Rezultatul X3 reprezintă numărul divizorilor proprii ai lui X1.
 
I.1.5 Rafinare în paşi succesivi

Adeseori, algoritmul de rezolvare a unei probleme este rezultatul unui proces complex, în
care se iau mai multe decizii. Observaţia este adevărată mai ales în cazul problemelor complicate,
dar şi pentru probleme mai simple din procesul de învăţământ. Este vorba de un proces de detaliere
pas cu pas a specificaţiei problemei, proces denumit şi proiectare descendentă, sau rafinare în paşi
succesivi. Algoritmul apare în mai multe versiuni succesive, fiecare versiune fiind o detaliere a
versiunii precedente.
În versiunile iniţiale apar propoziţii nestandard, clare pentru cititor, dar neprecizate prin
propoziţii standard. Urmează ca în versiunile următoare să se revină asupra lor. Algoritmul apare

________________________________________________________________________________

22
Structuri de date şi algoritmi
_______________________________________________________________________________
astfel în versiuni succesive, tot mai complet de la o versiune la alta.
Ca exemplu de algoritm obţinut prin rafinare succesivă vom proiecta un algoritm care să
rezolve următoarea problemă:
Exemplul I.7
 Se dau numerele întregi X = (x1, x2, ..., xn). Se cere să se reţină într-un vector Y toate
componentele distincte ale vectorului X (deci Y are numai componente distincte).
 Specificarea problemei:
 DATE n, (x[i], i=1,n);
 REZULTATE (y[j], j=1,k); {X=Y, unde X este mulţimea ce}
 {conţine toate numerele xi, iar Y = mulţimea}
 {ce conţine pe y[j], j=1,k şi y[l]<>y[j] pentru l≠j}

 Variabilele intermediare folosite şi semnificaţia lor, precum şi metoda folosită vor fi


rezultatul rafinărilor succesive şi vor fi înţelese din versiunilor respective.
Exemplul I.8

 ALGORITMUL DISTINCT ESTE: { Versiunea 1 }


 CITEŞTE n, (x[i], i=1,n);
 FIE y[1]:=x[1]; k:=1;
 @Examinează celelalte numere şi dacă trebuie adaugă-le în Y.
 TIPĂREŞTE (y[j], j=1,k);
 SFALGORITM
 Pentru a parcurge celelalte numere avem nevoie de un contor, fie el i, şi de folosirea
propoziţiei PENTRU. Ajungem la varianta:
Exemplul I.9

 ALGORITMUL DISTINCT ESTE: { Versiunea 2 }


 CITEŞTE n, (x[i], i=1,n);
 FIE y[1]:=x[1]; k:=1;
 PENTRU i:=2;n EXECUTĂ
  @Verifică dacă xi aparţine mulţimii Y.
  @Dacă nu aparţine atunci adaugă pe xi la Y.
 SFPENTRU
TIPĂREŞTE (y[j], j=1,k);
 SFALGORITM

________________________________________________________________________________

23
Structuri de date şi algoritmi
_______________________________________________________________________________

 Decizia ce trebuie luată este cum să verificăm apartenenţa unui elementul xi la mulţimea Y
formată din k elemente. Pentru aceasta calculăm indicatorul r, egal cu 0 dacă răspunsul este negativ
şi egal cu 1 în caz contrar. Aceasta se poate face cu secvenţa de propoziţii:

 FIE r:=0; j:=1;


 CÂTTIMP (r=0) şi (j<=k) EXECUTĂ
  DACĂ x[i]=y[j]
ATUNCI r:=1
ALTFEL j:=j+1
SFDACĂ
 SFCÂT

 Ajungem la versiunea finală a algoritmului dorit:

Exemplul I.10

 ALGORITMUL DISTINCT ESTE: { Versiunea 3 }


 CITEŞTE n, (x[i], i=1,n);
 FIE y[1]:=x[1]; k:=1;
 PENTRU i:=2;n EXECUTĂ
  FIE r:=0; j:=1;
  CÂTTIMP (r=0) şi (j<=k) EXECUTĂ
  DACĂ x[i]=y[j]
ATUNCI r:=1;
ALTFEL j:=j+1;
SFDACĂ
  SFCÂT
  DACĂ r=0
ATUNCI k:=k+1; y[k]:=x[i];
SFDACĂ

________________________________________________________________________________

24
Structuri de date şi algoritmi
_______________________________________________________________________________
 SFPENTRU
 TIPĂREŞTE (y[j], j=1,k);
 SFALGORITM

 Rezultă ca o consecinţă, o altă regulă importantă în proiectarea algoritmului:

Obs: Amână pe mai târziu detaliile nesemnificative; concentrează-ţi atenţia la deciziile importante
ale momentului.

I.1.6. Probleme propuse

Să se scrie algoritmi pentru rezolvarea următoarelor probleme:


1. Se dă numărul natural n. Să se determine primele n numere prime.
2. Se dă nN. Să se determine primele n triplete de numere pitagorice. (Tripletul (i,j,k) constituie
numere pitagorice dacă i<j<k şi i2+j2 = k2).
3. Tripletul (z,l,a) reprezintă o dată curentă zi, lună, an. Determinaţi a câta zi din an este această
dată.
4. Se cunoaşte data curentă (zc,lc,ac) şi ziua de naştere a unei persoane (zn,ln,an). Determinaţi
vârsta în zile a acestei persoane.
5. Cunoscând în ce zi din săptămână a fost 1 ianuarie, determinaţi ce zi din săptămână va fi în ziua
precizată prin tripletul (z,l,a).

I.2. SUBALGORITMI

I.2.1. Conceptul de subalgoritm

Orice problemă poate apare ca o subproblemă S a unei probleme mai complexe C.


Algoritmul de rezolvare a problemei S devine în acest caz o parte din algoritmul de rezolvare a
problemei C, parte numită subalgoritm.

Pentru a defini un subalgoritm vom folosi propoziţia standard:

SUBALGORITMUL nume(lpf) ESTE:

unde nume este numele subalgoritmului definit, iar lpf este lista parametrilor formali. Aceştia sunt
formaţi din variabilele care marchează datele de intrare (cele presupuse cunoscute) şi variabilele
care marchează datele de ieşire (rezultatele obţinute de subalgoritm).

________________________________________________________________________________

25
Structuri de date şi algoritmi
_______________________________________________________________________________
Această propoziţie este urmată de textul efectiv al subalgoritmului, text care precizează
calculele necesare rezolvării subproblemei corespunzătoare. Descrierea se va încheia cu cuvântul
SFSUBALGORITM sau SF-nume.

Exemplul I.11

 Să considerăm ca exemplu un subalgoritm cu numele MAXIM, care determină maximul


dintre componentele vectorului X = (x1, x2, ..., xn).
Datele cunoscute pentru acest subalgoritm sunt vectorul X şi numărul n al componentelor
vectorului X. Ca rezultat vom obţine maximul cerut, pe care-l vom nota cu max. În concluzie
specificarea subproblemei este:

 DATE n, X
 REZULTATE max

 Deci lista parametrilor formali conţine trei variabile, n, X şi max.  Pentru a găsi maximul
parcurgem toate componentele vectorului X, reţinând în variabila max valoarea cea mai mare
întâlnită. Evident, trebuie să începem cu max = x1. Subalgoritmul este prezentat în continuare.
 SUBALGORITMUL maxim(n,X,max) ESTE: {Calculează valoarea maximă}
 {dintre cele n componente ale lui X}
 FIE max:=x[1];
PENTRU i:=2;n EXECUTĂ
  DACĂ x[i]>max
ATUNCI max:=x[i];
SFDACĂ
 SFPENTRU
 SF-maxim
  
Una dintre greşelile frecvente ale începătorilor constă în introducerea în corpul
subalgoritmului a unor instrucţiuni de citire a datelor presupuse cunoscute, sau de tipărire a
rezultatelor obţinute. Acest lucru denotă o neînţelegere a conceptului de subalgoritm şi a rolului
subalgoritmilor în programare. Subalgoritmul rezolvă o subproblemă - care e o parte dintr-o
problema complexă de rezolvat.
De obicei, datele presupuse cunoscute pentru subproblemă, nu sunt datele iniţiale cunoscute
în problema iniţială; ele sunt rezultatul unor calcule efectuate înainte de apelul subalgoritmului. În
general, ele nu sunt cunoscute de programator, fiind rezultatul unor calcule făcute de algoritm.
Analog, rezultatele obţinute de subalgoritm sunt adesea doar valori intermediare, necesare în
continuare, fără a fi însă obligatoriu şi rezultate ale problemei iniţiale. De aceea nu este necesar să
le tipărim; este sarcina algoritmului apelant să trateze rezultatele obţinute de subalgoritm cum crede
de cuviinţă. 

Obs. Evită să citeşti şi să tipăreşti într-un subalgoritm.

Excepţie de la această regulă o fac subalgoritmii dedicaţi citirilor unor date, sau tipăririlor
unor rezultate. În acest caz, citirea, respectiv tipărirea unor date este cerută expres în enunţul

________________________________________________________________________________

26
Structuri de date şi algoritmi
_______________________________________________________________________________
subproblemelor corespunzătoare. Mai mult, un subalgoritm este scris pentru a rezolva subproblema
corespunzătoare indiferent de locul, timpul sau complexitatea problemei în care este folosit. De
aceea, trebuie să concepem subalgoritmii cu gândul la refolosirea lor.

Obs. Concepe un subalgoritm indiferent de contextul în care va fi folosit.

 Să considerăm în cele ce urmează, următorul exemplu. În cadrul multor algoritmi este
necesar calculul valorilor unei funcţii în diferite puncte. Este necesar să definim funcţia printr-un
subalgoritm de tip funcţie.
 Pentru definirea unui subalgoritm de tip funcţie se foloseşte un antet care precizează numele
funcţiei şi variabilele de care depinde ea. Subalgoritmul are forma:

FUNCŢIA nume(lpf) ESTE: {Antetul funcţiei}


ext {corpul funcţiei}
SF-nume {marca de sfârşit}

 În corpul funcţiei trebuie să existe cel puţin o instrucţiune de atribuire în care numele
funcţiei apare în membrul stâng, deci prin care funcţia primeşte o valoare.

 
Exemplul I.12

Să considerăm funcţia numar : R → {2,3,4,5}, definită matematic astfel:

 
În Pseudocod descrierea este următoarea:

FUNCŢIA numar(x) ESTE:


 DACĂ x<0.2
ATUNCI numar:=2
ALTFEL
  DACĂ x<0.5
ATUNCI numar:=3
ALTFEL
  DACĂ x<0.9
ATUNCI numar:=4
ALTFEL numar:=5
  SFDACĂ

________________________________________________________________________________

27
Structuri de date şi algoritmi
_______________________________________________________________________________
  SFDACĂ
 SFDACĂ
 SF-numar

 Am văzut că definiţia unei funcţii constă dintr-un antet şi dintr-un bloc care va defini
acţiunile prin care se calculează valoarea funcţiei. În antet se precizează numele funcţiei şi lista
parametrilor formali.
În concluzie, există două categorii de subalgoritmi: de tip funcţie şi subalgoritmi propriu-
zişi, cărora li se mai spune şi proceduri. Importanţa lor va fi subliniată prin toate exemplele care
urmează în acest curs. În încheiere menţionăm că subprogramele de tip funcţie se folosesc în scopul
definirii funcţiilor, aşa cum sunt cunoscute ele din matematică, în timp ce subalgoritmii de tip
procedură se referă la rezolvarea unor probleme ce apar ca subprobleme, fiind algoritmi de sine
stătători.
  

I.2.2. Apelul unui subalgoritm

Am văzut că un subalgoritm este dedicat rezolvării unei subprobleme S care apare într-o
problemă mai complexă C. Algoritmul corespunzător problemei C va folosi toate operaţiile
necesare rezolvării problemei S, deci va folosi ca parte întregul subalgoritm conceput pentru
rezolvarea subproblemei S. Spunem că el va apela acest subalgoritm.
În Pseudocod apelul unei funcţii se face scriind într-o expresie numele funcţiei urmat de lista
parametrilor actuali. Trebuie să existe o corespondenţă biunivocă între parametrii actuali şi cei
formali folosiţi în definiţia funcţiei. Deşi denumirile variabilelor din cele două liste pot să difere,
rolul variabilelor care se corespund este acelaşi. Mai exact, parametrul formal şi parametrul actual
corespunzător trebuie să se refere la aceeaşi entitate, trebuie să aibă aceeaşi semnificaţie, să
reprezinte aceeaşi structură de date. Putem considera că în timpul execuţiei algoritmului cei doi
parametri devin identici.
Folosirea unui subalgoritm în cadrul unui algoritm se face apelând acest subalgoritm prin
propoziţia standard:

CHEAMĂ nume(lpa);

unde nume este numele subalgoritmului apelat, iar lpa este lista parametrilor actuali. Această listă
conţine toate datele de intrare (cele cunoscute în subproblema corespunzătoare) şi toate rezultatele
obţinute în subalgoritm. Şi în acest caz între lista parametrilor formali din definiţia subalgoritmului
şi lista parametrilor actuali din propoziţia de apel trebuie să existe o corespondenţă biunivocă, ca şi
în cazul funcţiilor.

Ca o primă verificare a respectării acestei corespondenţe, subliniem că numărul parametrilor


actuali trebuie să coincidă cu numărul parametrilor formali.
Ca exemplu de apelare a funcţiilor, prezentăm în continuare un algoritm pentru a calcula a
câta zi din anul curent este ziua curentă (zi, luna, an). El foloseşte un subalgoritm de tip funcţie
pentru a obţine numărul zilelor lunii cu numărul de ordine i şi un altul pentru a verifica dacă un an

________________________________________________________________________________

28
Structuri de date şi algoritmi
_______________________________________________________________________________
este bisect sau nu. Aceste două funcţii sunt:
- NRZILE(i) furnizează numărul zilelor existente în luna i a unui an nebisect;
- BISECT(an) adevărată dacă anul dintre paranteze este bisect.

Exemplul I.13
 ALGORITMUL NUMĂR_ZILE ESTE: {A câta zi dintr-un an?}
 CITEŞTE zi, luna, an; {introduceţi data curentă}
 FIE nr:=zi; {nr va fi numărul care reprezintă a câta zi }
 {din an este data curentă}
 DACĂ luna>1 ATUNCI
  PENTRU i:=1, Luna-1 EXECUTĂ
nr:=nr+NRZILE(i)
SFPENTRU
 SFDACĂ
 DACĂ luna>2 ATUNCI
  DACĂ BISECT(an) ATUNCI
nr:=nr+1
SFDACĂ
SFDACĂ
 TIPĂREŞTE nr;
 SFALGORITM
 
Să observăm că în proiectarea acestui algoritm nu este necesar să cunoaştem detaliat
subalgoritmii folosiţi, ci doar specificarea acestor subalgoritmi, numele lor şi lista parametrilor
formali. La acest nivel accentul trebuie să cadă pe proiectarea algoritmului apelant, urmând să se
revină ulterior la proiectarea subalgoritmilor apelaţi, conform specificaţiei acestora. În cazul de faţă
este necesară descrierea funcţiilor NRZILE(i) şi BISECT(an). Lăsăm această descriere ca exerciţiu
pentru cititor.

Exemplul I.14

 Un alt exemplu de apelare a unei proceduri este algoritmul care efectuează suma a două
polinoame.
Un polinom P(X) este determinat prin gradul său, m, şi prin vectorul coeficienţilor P = (p 0,
p1, ..., pm) (prin pi s-a notat coeficientul lui Xi).
 Procedura SUMAPOL(m,P,n,Q,r,S) trebuie să efectueze suma S(X) = P(X)+Q(X), unde P
este un polinom de gradul m, iar Q este un polinom de gradul n, date. Suma lor, S, va fi un polinom
de gradul r calculat de subalgoritm. Pentru efectuarea acestui calcul este utilă folosirea unui alt
subalgoritm care adună la suma S(X) un alt polinom, T(X), de grad mai mic sau egal decât gradul
polinomului S(X). Un astfel de subalgoritm se prezintă în continuare.

 SUBALGORITMUL SUMAPOL1(n,T,r,S) ESTE: { n ≤ r}

________________________________________________________________________________

29
Structuri de date şi algoritmi
_______________________________________________________________________________
{S(X):=S(X)+T(X)}
 PENTRU i:=0;n EXECUTĂ
  s[i] := s[i] +t[i];
 SFPENTRU
 SF-SUMAPOL1

 Subalgoritmul SUMAPOL apelează acest subalgoritm, aşa cum se poate vedea în


continuare.

 SUBALGORITMUL SUMAPOL(m,P,n,Q,r,S) ESTE: {S(X):=P(X)+Q(X)}


 DACĂ m<n
  ATUNCI r:=n; S:=Q;
  CHEAMĂ SUMAPOL1(m,P,r,S)
  ALTFEL r:=m; S:=P;
  CHEAMĂ SUMAPOL1(n,Q,r,S)
 SFDACĂ
 SF-SUMAPOL
 
Să observăm că în textul acestui subalgoritm am extins semnificaţia propoziţiei de atribuire,
permiţând atribuirea S:=Q. Acest lucru este normal întrucât S corespunde unui polinom, iar Q este
un polinom cunoscut; prin atribuire S primeşte o valoare iniţială, cea dată de polinomul Q.
Subliniem că atribuirea  v := u  va fi corectă în cazul în care variabilele u şi v reprezintă
aceleaşi obiecte matematice, deci au aceeaşi semnificaţie.
 

I.2.3. Elaborarea algoritmilor. Propoziţii nestandard

Prin elaborarea (proiectarea) unui algoritm înţelegem întreaga activitate depusă de la enunţarea
problemei până la realizarea algoritmului corespunzător rezolvării acestei probleme.
În elaborarea unui algoritm deosebim următoarele activităţi importante (Andonie şi Gârbacea
[1995]):
 specificarea problemei;
 descrierea metodei alese pentru rezolvarea problemei;
 proiectarea propriu-zisă. Ea constă în descompunerea problemei în subprobleme, obţinerea
algoritmului principal şi a tuturor subalgoritmilor apelaţi, conform metodelor prezentate în
secţiunile următoare. Ea se termină cu descrierea algoritmului principal şi a subalgoritmilor
menţionaţi, dar şi cu precizarea denumirilor şi semnificaţiilor variabilelor folosite;
 verificarea algoritmului obţinut.

Astfel, să considerăm următorul exemplu, calculul radicalului de ordinul 2 din x.

a. În partea de specificare a problemei vom menţiona:

Se dă un număr real notat prin x, care trebuie să fie nenegativ. Se cere să găsim un alt număr

________________________________________________________________________________

30
Structuri de date şi algoritmi
_______________________________________________________________________________
pozitiv r astfel încât x = r.

Vom folosi un algoritm de aproximare a lui r. Deci specificarea făcută nu este completă,
neputând găsi un algoritm care să rezolve direct problema în forma enunţată. Vom modifica această
specificare, cerând să se calculeze r aproximativ, cu o eroare ce nu depăşeşte un număr real pozitiv
eps prestabilit. Ajungem astfel la următoarea specificare:

DATE eps,x; { eps, x  R , eps>0 şi x ≥ 0 }


REZULTATE r; { r  x < eps }

b. Urmează să precizăm metoda de rezolvare a problemei. Se ştie că există mai multe metode de
calcul al radicalului. Menţionăm următoarele două posibilităţi:
 - ca limită a unui şir convergent la r (definit printr-o relaţie de recurenţă);
 - prin aproximarea soluţiei ecuaţiei x = r.

Alegem pentru exemplificare metoda a doua, deci îl vom calcula pe r rezolvând ecuaţia x
= r.
Pentru rezolvarea ecuaţiei generale f (x) = 0 există mai multe metode. Alegem pentru
rezolvare metoda coardei şi a tangentei.
Această metodă constă în micşorarea repetată a intervalului [a,b] care conţine rădăcina r
căutată, la intervalul [a',b'], aşa cum se poate vedea în Figura 1.2. Variabilele folosite în descrierea
algoritmului sunt:
 a şi b, reprezentând capetele intervalului în care se află rădăcina;
 r mijlocul intervalului (a,b) în momentul în care b-a < eps, deci valoarea căutată.

 
 
Figura 1.2.: Grafic pentru metoda coardei şi a tangentei.

Algoritmul propriu-zis (secvenţa de propoziţii care obţine rezultatele dorite pornind de la

________________________________________________________________________________

31
Structuri de date şi algoritmi
_______________________________________________________________________________
datele iniţiale) este descris în continuare:
 
Exemplul I.15

{Algoritmul 3.3: Metoda coardei şi a tangentei}


 @Iniţializează pe a şi b.
 REPETĂ
@Atribuie lui a abscisa punctului de intersecţie a axei OX cu coarda ce uneşte punctele
(a,f(a)) şi (b,f(b)).
@Atribuie lui b abscisa punctului de intersecţie a axei OX cu tangenta în punctul (b,f(b))
dusă la graficul funcţiei f(t) = t - x .
PÂNĂCÂND b-a<eps SFREP
FIE r:=(a+b)/2;

În textul de mai sus apar trei propoziţii nestandard care sugerează însă foarte bine ce acţiuni
trebuiesc întreprinse. Prima stabileşte intervalul iniţial în care se află rădăcina, care depinde de
mărimea lui x: (x,1) când x este mai mic decât 1 sau (1,x) în caz contrar. Deci ea se va transcrie în
propoziţia standard:

DACĂ x<1
ATUNCI a:=x; b:=1;
ALTFEL a:=1; b:=x;
SFDACĂ

 Celelalte două propoziţii nestandard se vor transcrie în propoziţiile standard:

  FIE a := E1; b := E2 ;

unde  E1 este expresia care reprezintă abscisa punctului de intersecţie a axei OX cu coarda ce
uneşte punctele (a,f(a)) şi (b,f(b)), iar  E2 este expresia care reprezintă abscisa punctului de
intersecţie a axei OX cu tangenta în punctul (b,f(b)) dusă la graficul funcţiei f(t) = t - x.
Se ajunge la următoarea variantă finală:

Exemplul I.16
 
ALGORITMUL EXTRAGRADICAL ESTE: { Radical }
{ r := radical din x }
CITESTE eps,x; { eps, x  R , eps>0 şi x ≥ 0 }
DACĂ x<1
ATUNCI a:=x; b:=1; { Iniţializează }
ALTFEL a:=1; b:=x; { pe a şi b }
 SFDACĂ

________________________________________________________________________________

32
Structuri de date şi algoritmi
_______________________________________________________________________________
 REPETĂ
  a := E1; {abscisa punctului de intersecţie a axei OX cu}
  {coarda ce uneşte punctele (a,f(a)) şi (b,f(b))}
  b := E2; {abscisa punctului de intersecţie a axei OX cu tangenta}
  { în punctul (b,f(b)) dusă la graficul funcţiei f(t) = t2-x }
PÂNĂCÂND b-a<eps SFREP
FIE r:=(a+b)/2;
TIPĂREŞTE r; { ˝r-rad(x)˝<eps }
SF-EXTRAGRADICAL
  

I.2.4. Proiectarea ascendentă şi proiectarea descendentă

Există două metode generale de proiectare a algoritmilor, a căror denumire provine din
modul de abordare a rezolvării problemelor: metoda descendentă şi metoda ascendentă. Proiectarea
descendentă (top-down) porneşte de la problema de rezolvat, pe care o descompune în părţi
rezolvabile separat. De obicei aceste părţi sunt subprobleme independente, care la rândul lor pot fi
descompuse în subprobleme. La prima descompunere accentul trebuie pus pe algoritmul (modulul)
principal nu asupra subproblemelor. La acest nivel nu ne interesează amănunte legate de rezolvarea
subproblemelor, presupunem că le ştim rezolva, eventual că avem deja subalgoritmii pentru
rezolvarea lor. Urmează să considerăm pe rând, fiecare subproblemă în parte şi să proiectăm (în
acelaşi mod) un subalgoritm pentru rezolvarea ei. În final, se va descrie subalgoritmul de rezolvare
al fiecărei subprobleme, dar şi interacţiunile dintre aceşti subalgoritmi şi ordinea în care ei sunt
folosiţi.
Noţiunea de modul va fi definită în secţiunea următoare. Deocamdată înţelegem prin modul
orice subalgoritm sau algoritmul principal. Legătura dintre module se prezintă cel mai bine sub
forma unei diagrame numită arbore de programare (Blaga P. et al. [1978]). Fiecărui modul îi
corespunde în arborele de programare un nod, ai cărui descendenţi sunt toate modulele apelate
direct. Nodul corespunzător algoritmului principal este chiar nodul rădăcină.
Astfel, în arborele de programare din Figura.1.3 există un algoritm principal (modulul
PRINC), care apelează trei subalgoritmi (modulele CITDATE, CALCULE şi TIPREZ). La rândul
său, modulul CALCULE apelează trei subalgoritmi (modulele M1, M2 şi M3).
 

________________________________________________________________________________

33
Structuri de date şi algoritmi
_______________________________________________________________________________
Figura1.3. Arbore de programare.

 Din arborele de programare nu reiese însă de câte ori se apelează un subalgoritm, sau dacă
doi subalgoritmi se exclud unul pe celălalt. Este posibilă înlocuirea arborelui de programare cu o
diagramă de structură construită pe concepţia NESTED-LOGIC. În construirea unei astfel de
diagrame se folosesc structurile din Figura.1.4, bazate pe următoarele reguli (Livovschi L. [1980]):
 a) Operaţia de trecere de la un nivel superior la unul inferior se interpretează prin expresia
"CONSTĂ DIN" (fig.a);
 b) Operaţia de trecere de la un bloc la altul de pe acelaşi nivel se interpretează cu expresia
"URMAT DE" (fig.a);
 c) Caracterul 'o' prezent în bloc în colţul din dreapta sus este descendentul unui bloc de tip
selectiv (fig.b);
 d) Caracterul '*' prezent în bloc în colţul din dreapta sus identifică un bloc repetitiv şi se
interpretează cu expresia "MAI MULTE" (fig.c).
 

 
 Figura. 1.4. Diagrame de structură construită pe concepţia NESTED-LOGIC.
 
Teorema de structură NESTED-LOGIC afirmă că oricare ar fi schema logică S există o
schemă S' de structură NESTED-LOGIC echivalentă cu S (deci care rezolvă aceeaşi problemă).
 Ca exemplu de proiectare descendentă, descriem în continuare un algoritm pentru
rezolvarea unui sistem liniar de n ecuaţii cu n necunoscute:

A*X = B

 La primul nivel de descompunere deosebim trei activităţi distincte:


 - citirea datelor problemei;
 - rezolvarea sistemului menţionat;
 - tipărirea rezultatelor.
 Pentru rezolvarea sistemului vom proiecta un subalgoritm, întrucât această problemă poate
apare adesea ca subproblemă şi dorim să refolosim subalgoritmul obţinut. Pentru rezolvarea

________________________________________________________________________________

34
Structuri de date şi algoritmi
_______________________________________________________________________________
sistemului identificăm două subprobleme independente:
 - reducerea sistemului, prin metoda lui Gauss, la un sistem triunghiular echivalent;
 - rezolvarea sistemului triunghiular obţinut.
 Mai mult, subproblema reducerii sistemului conţine ca subprobleme determinarea ecuaţiei
în care rămâne xi şi care este folosită la reducerea acestei necunoscute din celelalte ecuaţii,
schimbarea a două ecuaţii între ele, deci interschimbarea a două linii din matricea extinsă şi
eliminarea propriu-zisă. Subalgoritmul PIVOT, corespunzător primei subprobleme, determină care
dintre ecuaţiile de rang i, i+1, ..., n are coeficientul lui xi maxim în valoare absolută, caz în care
erorile introduse din cauza aproximărilor în calcule cu numere reale sunt minime. Subalgoritmii
INTERSCHIMB şi ELIMIN corespund celorlalte două subprobleme. Arborele de programare
corespunzător se dă în Figura.1.5.
Exemplul I.17

 Algoritmul REZSISTEM este:


 Cheamă CITSISTEM(n, A, B)
 Cheamă SISTEM(n, A, B, kod)
 Dacă kod = 1 atunci Cheamă TIPSOL(n, B)
 altfel Tipăreşte "Sistemul nu este compatibil determinat."
 sfdacă
 sf-RezSistem
 
 

Figura 1.5. Arborele de programare pentru rezolvarea sistemului

Subalgoritmul SISTEM pentru rezolvarea unui sistem liniar de n ecuaţii cu n necunoscute


este prezentat în continuare.

________________________________________________________________________________

35
Structuri de date şi algoritmi
_______________________________________________________________________________
Subalgoritmul SISTEM(n,A,B,kod) este: {Rezolvă sistemul AX=B, de n ecuaţii cu n
necunoscute}
 {A este matricea sistemului, B vectorul termenilor liberi i}
 {Soluţia se depune în vectorul B, iar A se distruge}
 {kod = 1 pentru sistem compatibil, altfel kod = 0 }
 Cheamă REDUTRI(n,A,B,kod); {Reduce sistemul la un sistem triunghiular echivalent}
 Dacă kod = 1 atunci {kod = 1 când sistemul e compatibil}
 Cheamă REZOLV(n,A,B,kod); {Rezolvă sistemul triunghiular}
 sfdacă {Soluţia se depune în vectorul B}
 sf-SISTEM
 
Subalgoritmul REDUTRI(n,A,B,kod) este: {Reduce sistemul liniar A.X=B, de n ecuaţii cu
n necunoscute la un sistem triunghiular echivalent}
 { A'.X = B', matricea A' având sub diagonală numai zerouri}
 {A este matricea sistemului iar B - vectorul termenilor liberi}
 {kod = 0 pentru sistem nedeterminat sau incompatibil}
 Fie kod:=1; {Ipoteza sistem determinat}
 i:=1;
 Câttimp (i<n) şi (kod=1) execută {Se reduce necunoscuta xi din ecuaţiile i+1,...,n}
  Cheamă PIVOT(n,A,i,j); {j = numărul ecuaţieiîn care se păstrează necu-}
  Dacă a[i,j]=0 {noscuta x[j]}
atunci kod:=0 altfel
  Dacă j>i
atunci Cheamă INTERSCHIMB(i,j,n,A,B)
sfdacă
  Cheamă ELIMIN(n,A,B,i);
  sfdacă
  Fie i:=i+1;
 sfcât
 sf-REDUTRI
 

Subalgoritmul REZOLV(n,A,B,ind) este: {Rezolva sistemul triunghiular A.X=B}


 {deci matricea A are sub diagonala}

________________________________________________________________________________

36
Structuri de date şi algoritmi
_______________________________________________________________________________
 {numai zerouri. ind=1 pt.sistem determinat}
 {in care caz solutia se pune in vectorul B}
 {si ind=0 dacă e nedeterminat sau incompatibil}
  Fie r1:=b[n]; ind:=1;
  Dacă a[n,n]=0
atunci ind:=0
  altfel b[n]:=r1/a[n,n]
 sfdacă
 Fie i:=n-1;
 Câttimp (ind=1) şi (i>=1) execută
  Dacă a[i,i]=0
atunci ind:=0
altfel
  r1:=b[i];
  Pentru k:=i+1,n execută
  r:=a[i,k]*b[k]; r1:=r1-r;
  sfpentru
  b[i]:= r1/a[i,i]
  sfdacă
  Fie i:=I-1
 sfcât
 sf-REZOLV
 
Subalgoritmul PIVOT(n,A,i,j) este: {j primeşte o valoare >=i pentru care}
 Fie j:=i; { a[j,i] e maxim în valoare absolută}
 Pentru l:=i+1, n execută
  Dacă a[l,i] > a[j,i]
atunci j:=l
sfdacă
 sfpentru
 sf-PIVOT
   
Subalgoritmul INTERSCHIMB(i,j,n,A,B) este: {Schimbă între ele liniile i şi j din matricea
A}

________________________________________________________________________________

37
Structuri de date şi algoritmi
_______________________________________________________________________________
  { de ordinul n şi termenii liberi corespunzători}
 Pentru l:=1; n execută
  r:=a[i,l]; a[i,l]:=a[j,l]; a[j,l]:=r
sfpentru
 Fie r:=b[i]; b[i]:=b[j]; b[j]:=r
 sf-INTERSCHIMB;
  
Subalgoritmul ELIMIN(n,A,B,i) este: {Elimină necunoscuta x[i] din ecuaţiile i+1,...,n}
 Fie r:=a[i,i]; { x[i] din ecuaţiile în ipoteza a[i,i] ≠ 0}
 Pentru k:=i,n execută {Imparte ecuaţia nr i cu r}
  Fie a[i,k]:=a[i,k]/r
 sfpentru
 Fie b[i] := b[i]/r
 Pentru j:=i+1,n execută {Elimină necunoscuta}
  Fie r:=a[j,i]; {x[i] din ecuaţia nr.j}
  Pentru k:=1,n execută
  a[j,k]:=a[j,k]-r*a[i,k];
  sfpentru
  Fie b[j]:=b[j]-r*b[i]
 sfpentru
 sf-ELIMIN
  
În multe cărţi metoda top-down este întâlnită şi sub denumirea stepwise-refinement, adică
rafinare în paşi succesivi. Este vorba de un proces de detaliere pas cu pas a specificaţiei, denumit
proiectare descendentă. Algoritmul apare în diferite versiuni succesive, fiecare fiind o detaliere a
versiunii precedente. În aceste versiuni succesive apar multe enunţuri nestandard ce urmează a fi
precizate treptat prin propoziţii standard. Se recomandă ca ele să rămână comentarii în versiunea
finală. Algoritmul apare în versiuni succesive, tot mai complet de la o versiune la alta. În versiunea
finală în algoritm apar numai propoziţii standard. Un exemplu de rafinare succesivă a fost dat în
secţiunea 1.5.
 Diferenţa între metoda top-down şi metoda rafinării succesive este neesenţială, scopul
urmărit fiind acelaşi: concentrarea atenţiei asupra părţilor importante ale momentului şi amânarea
detaliilor pentru mai târziu. Dacă ar fi necesar să le deosebim am spune că metoda top-down se
referă la nivelul macro iar metoda rafinării succesive la nivel micro. La nivel macro se doreşte
descompunerea unei probleme complexe în subprobleme. La nivel micro se doreşte obţinerea unui
modul în versiune finală. Într-o versiune intermediară pot fi prezente numai părţile importante ale
acestuia, urmând să se revină asupra detaliilor în versiunile următoare, după ce aspectele importante

________________________________________________________________________________

38
Structuri de date şi algoritmi
_______________________________________________________________________________
au fost rezolvate.
Avantajele proiectării top-down (cunoscută şi sub denumirea "Divide et impera") sunt
multiple. Avantajul principal constă în faptul că ea permite programatorului să reducă
complexitatea problemei, subproblemele în care a fost descompusă fiind mai simple, şi să amâne
detaliile pentru mai târziu. În momentul în care descompunem problema în subprobleme nu ne
gândim cum se vor rezolva subproblemele ci care sunt ele şi conexiunile dintre ele.
 Proiectarea descendentă permite lucrul în echipe mari. Prin descompunerea problemei în
mai multe subprobleme, fiecare subproblemă poate fi dată spre rezolvare unei subechipe. Fiecare
subechipă nu cunoaşte decât subproblema pe care trebuie să o rezolve.
 Metoda "Divide et Impera" poate fi folosită nu numai la împărţirea problemei în
subprobleme ci şi la împărţirea datelor în grupe mai mici de date. În acest caz ea este cunoscută sub
numele de metoda divizării metodă prezentată în secţiunea 8.1. Un astfel de procedeu este folosit de
subalgoritmul Quicksort, care va fi prezentat în secţiunea 7.2.
 
Metoda ascendentă (bottom-up) porneşte de la propoziţiile limbajului şi de la subalgoritmi
existenţi, pe care îi asamblează în alţi subalgoritmi pentru a ajunge în final la algoritmul dorit. Cu
alte cuvinte, în cazul metodei ascendente va fi scris mai întâi subalgoritmul apelat şi apoi cel care
apelează. Ca rezultat al proiectării ascendente se ajunge la o mulţime de subalgoritmi care se
apelează între ei. Este important să se cunoască care subalgoritm apelează pe care, lucru redat
printr-o diagramă de structură, ca şi în cazul programării descendente.
 Această metodă are marele dezavantaj că erorile de integrare vor fi detectate târziu, abia în
faza de integrare. Se poate ajunge abia acum la concluzia că unii subalgoritmi, deşi corecţi, nu sunt
utili.
 De cele mai multe ori nu se practică o proiectare ascendentă sau descendentă pură ci o
combinare a lor, o proiectare mixtă.
   
I.2.5. Proiectarea modulară

Prin proiectare (programare) modulară înţelegem metoda de proiectare (programare) a unui


algoritm pentru rezolvarea unei probleme prin folosirea modulelor.
 Dar ce este un modul? Modulul este considerat (Frentiu M. et al. [1986]) o unitate
structurală de sine stătătoare, fie program, fie subprogram, fie o unitate de program. Un modul
poate conţine sau poate fi conţinut într-alt modul. Un modul poate fi format din mai multe
submodule. Astfel, în Pseudocod fiecare subalgoritm şi algoritmul principal sunt considerate
module. În limbajele de programare cu structură de bloc, de exemplu în Turbo Pascal UNIT-urile
pot fi considerate module. La compilarea separată un grup de subprograme compilate deodată
constituie un modul, dar acest modul poate fi considerat ca o mulţime de submodule din care este
compus.
 Este însă important ca fiecare modul să-şi aibă rolul său bine precizat, să realizeze o funcţie
în cadrul întregului program. El apare în mod natural în descompunerea top-down.

________________________________________________________________________________

39
Structuri de date şi algoritmi
_______________________________________________________________________________
 Indiferent că privim modulul ca un singur subalgoritm, un grup de subalgoritmi, sau un
algoritm de sine stătător ce apelează alţi subalgoritmi, considerăm modulele relativ independente,
dar cu posibilităţi de comunicare între ele. Astfel, un modul nu trebuie să fie influenţat de maniera
în care se lucrează în interiorul altui modul. Orice modificare ulterioară în structura unui program,
dacă funcţia pe care o realizează un modul M încă este necesară, acest modul trebuie să fie util şi
folosit în continuare fără modificări.
 Rezultă că programarea modulară se bazează pe descompunerea problemei în subprobleme
şi proiectarea şi programarea separată a subalgoritmilor corespunzători. De altfel, considerăm că
într-o programare serioasă nu se poate ajunge la implementare fără a avea în prealabil algoritmii
descrişi într-un limbaj de descriere (la noi Pseudocod). Deci programarea modulară se referă în
primul rând la proiectarea modulară a algoritmilor şi apoi la traducerea lor în limbajul de
programare ales, ţinând seama de specificul acestui limbaj. Programarea modulară este strâns legată
de programarea ascendentă şi de programarea descendentă, ambele presupunând folosirea
subalgoritmilor pentru toate subproblemele întâlnite.
 Avantajele programării modulare sunt multiple. Menţionăm în cele ce urmează câteva
dintre ele:
 Descompunerea unei probleme complexe în subprobleme este un mijloc convenabil
şi eficient de a reduce complexitatea (Principiul Divide et impera acţionează şi în
programare). Este evident că probabilitatea apariţiei erorilor în conceperea unui
program creşte cu mărimea programului, lucru confirmat şi de experienţa practică.
De asemenea, rezolvând o problemă mai simplă, testarea unui modul se poate face
mult mai uşor decât testarea întregului algoritm.
 Apoi, faptul că trebuiesc proiectate mai multe subprograme pentru subproblemele
întâlnite, permite munca mai multor programatori. S-a ajuns astfel la munca în
echipă, modalitate prin care se ajunge la scurtarea termenului de realizare a
produsului program.
 Modulele se pot refolosi ori de câte ori avem nevoie de ele. Astfel, s-a ajuns la
compilarea separată a subprogramelor şi la păstrarea subprogramelor obţinute în
biblioteci de subprograme, de unde ele se pot refolosi la nevoie. Sunt cunoscute
astăzi multe astfel de biblioteci de subprograme. Reutilizabilitatea acestor
subprograme este o proprietate foarte importantă în activitatea de programare. Ea
duce la mărirea productivităţii în programare, dar şi la creşterea siguranţei în
realizarea unui produs corect.
 Uneori, în timpul proiectării algoritmului sau a implementării lui, se ajunge la
concluzia că proiectarea a fost incompletă sau că unele module sunt ineficiente. Şi în
această situaţie programarea modulară este avantajoasă, ea permiţând înlocuirea
modulului în cauză cu altul mai performant.
 
Una din activităţile importante în realizarea unui program este verificarea corectitudinii
acestuia. Experienţa a arătat că modulele se pot verifica cu atât mai uşor cu cât sunt mai mici.
Abilitatea omului de a înţelege şi analiza corectitudinea unui subalgoritm este mult mai mare pentru
texte scurte. În unele cărţi chiar se recomandă a nu se folosi subalgoritmi mai mari decât 50 de

________________________________________________________________________________

40
Structuri de date şi algoritmi
_______________________________________________________________________________
propoziţii. Sigur că o astfel de limită nu există, dar se recomandă descompunerea unui subalgoritm
în alţi subalgoritmi oricând acest lucru este posibil în mod natural, deci aceşti noi subalgoritmi
rezolvă subprobleme de sine stătătoare, sau realizează funcţii bine definite.
 Ca exemplu de proiectare modulară ne propunem să dăm un algoritm pentru rezolvarea
următoarei probleme:

Exemplul I.18

Să se verifice dacă mulţimea finită K, pe care s-au definit două operaţii, este corp în raport
cu aceste operaţii.
 În vederea proiectării unui algoritm pentru rezolvarea ei avem nevoie de specificarea
problemei. Să observăm mai întâi că nu a fost precizată exact natura elementelor mulţimii K = { k 1,
k2, ..., kn }.
 Trebuie să specificăm modul de definire al unei operaţii O peste mulţimea K. Evident,
pentru a definii o operaţie pe mulţimea finită K, trebuie ca pentru oricare două elemente k i, kj  K
să cunoaştem O(k[i], k[j]) = k[s]  K.
Deci O ar fi o matrice pătratică de ordinul n cu elemente din K. Cum natura acestor
elemente nu este precizată, convenim să reţinem doar indicii acestor elemente, cu alte cuvinte să
lucrăm cu mulţimea  K' = { 1, 2, ... , n }.
 Ea este precizată prin numărul n. În acest caz definirea operaţiei O va fi simplă,
exprimându-se printr-o matrice pătrată de ordinul n cu elemente din K'. Deci specificarea problemei
este:

 DATE n, O1, O2; {O1, O2 = operaţii peste K'}


 REZULTATE kod {kod = 0 pentru corp, altfel kod > 0}
 {Prin valoarea lui kod se poate reţine care}
 {proprietate din definiţia corpului nu a fost îndeplinită}

 Trecând la proiectarea algoritmului, să observăm că la citirea datelor se întâlnesc două


operaţii, deci se repetă aceleaşi operaţii de două ori. Tipărirea rezultatului este mult mai simplă: se
tipăreşte valoarea lui kod. Decidem să folosim un subalgoritm CITOP(n,O) pentru citirea unei
operaţii, subalgoritm care va fi apelat în modulul principal de două ori: prima dată pentru citirea lui
O1, apoi pentru citirea lui O2.
Înaintea acestor apeluri în modulul principal se va citi valoarea lui n. Apoi se va apela
subalgoritmul CORP(n,O1,O2,kod) pentru verificarea propriu zisă. Ea constă din trei verificări:
a - este mulţimea grup în raport cu operaţia O1 ?
 b - scăzând elementul neutru pentru O1, este noua mulţime grup în raport cu O2;
 c - este O2 distributivă faţă de O1?

________________________________________________________________________________

41
Structuri de date şi algoritmi
_______________________________________________________________________________
 Putem realiza verificările a şi b cu acelaşi subalgoritm GRUP(p,n,O,e,kod) care verifică
axiomele grupului, reţinând rezultatul în kod, iar în e elementul neutru. Aceasta este posibil dacă
elementul neutru pentru O1 este 1, cele două apeluri făcându-se prima cu p=1, a doua cu p=2 (deci
elementul neutru pentru O1 este eliminat la a doua verificare). În cazul în care elementul neutru
pentru operaţia O1 este eą1 vom inter-schimba numerotarea celor două elemente, deci 1 va deveni
element neutru !
 Pentru verificarea axiomelor grupului se folosesc patru module: COMUTATIV, NEUTRU,
INVERS şi ASOCIATIV. Ele corespund subalgoritmilor:
 COMUTATIV(n,O,kod) verifică dacă operaţia O e comutativă;
 NEUTRU(n,O,e) găseşte elementul neutru e pentru O (sau e=-1);
 INVERS(n,O,e,kod) verifică existenţa inverselor;
 ASOCIATIV(n,O,kod) verifică dacă O este asociativă.

 Se ajunge la arborele de programare din Figura.1.6.


 

 
Figura 1.6. Arborele de programare pentru verificarea axiomelor corpului.

 Ca un al doilea exemplu de definire şi folosire a subalgoritmilor, să considerăm următoarea


problemă:
 Se dau trei mulţimi de numere:
 A = { a1, a2, ... , am }
 B = { b1, b2, ... , bn }
 C = { c1, c2, ... , cp }
 Se cere să se tipărească în ordine crescătoare elementele fiecărei mulţimi, precum şi a
mulţimilor A  B, B  C, C  A.
  În rezolvarea acestei probleme se întâlnesc următoarele subprobleme:

________________________________________________________________________________

42
Structuri de date şi algoritmi
_______________________________________________________________________________
 S1: Să se citească elementele unei mulţimi;
 S2: Să se efectueze reuniunea a două mulţimi;
 S3: Să se tipărească elementele unei mulţimi;
 S4: Să se ordoneze crescător elementele unei mulţimi.
 Presupunând că pentru rezolvarea acestor subprobleme am conceput subalgoritmii:
 
CITMUL(m,A);
 REUNIUNE(m,A,n,B,k,R);
 TIPMUL(m,A);
 ORDON(m,A);
care sunt specificaţi mai jos (la locul definirii lor) prin comentarii, algoritmul de rezolvare a
problemei de mai sus este dat în continuare. Întrucât operaţiile respective se folosesc de mai multe
ori (de 3 ori), am definit un subalgoritm TIPORDON(m,A) care ordonează mai întâi elementele
mulţimii A şi apoi le tipăreşte.

  ALGORITMUL OPER-MULTIMI ESTE: { Algoritmul 3.6: Subalgoritmi }


 CHEAMĂ CITMUL(m,A);
 CHEAMĂ CITMUL(n,B);
 CHEAMĂ CITMUL(p,C);
 CHEAMĂ TIPORDON(m,A);
 CHEAMĂ TIPORDON(n,B);
 CHEAMĂ TIPORDON(p,C);
 CHEAMĂ REUNIUNE(m,A,n,B,k,R);
 CHEAMĂ TIPORDON(k,R);
 CHEAMĂ REUNIUNE(n,B,p,C,k,R);
 CHEAMĂ TIPORDON(k,R);
 CHEAMĂ REUNIUNE(p,C,m,A,k,R);
 CHEAMĂ TIPORDON(k,R);
 SFALGORITM
  
Subalgoritmii apelaţi mai sus sunt definiţi în continuare.

SUBALGORITMUL CITMUL(n,M) ESTE: {Citeşte n şi M}


CITEŞTE n; {n=nr. elementelor mulţimii}

________________________________________________________________________________

43
Structuri de date şi algoritmi
_______________________________________________________________________________
CITEŞTE (m[i],i=1,n); {M=mulţimea cu elementele m1,m2,...,mn}
 SF-CITMUL
  
SUBALGORITMUL ORDON(n,M) ESTE: {Ordonează crescător cele n}
 REPETĂ {elemente ale mulţimii M}
  FIE ind:=0; {Cazul M este ordonată}
  PENTRU i:=1;n-1 EXECUTĂ
  DACĂ m[i]>m[i]+1 ATUNCI {schimbă ordinea celor}
  FIE t := m[i]; {două elemente}
  m[i]:= m[i]+1; m[i]+1:=t;
  ind:=1; {Cazul M nu era ordonată}
  SFDACĂ
  SFPENTRU
 PÂNĂCÂND ind=0 SFREP
 SF-ORDON
  
SUBALGORITMUL REUNIUNE(m,A,n,B,k,R) ESTE: { R := A U B }
 { k = numărul elementelor mulţimii R }
 FIE k:=m; R := A;
 PENTRU j:=1,n EXECUTĂ
  FIE ind:=0; {Ipoteza bj nu e in A}
  PENTRU i:=1;m EXECUTĂ
  DACĂ b[j]=a[i] ATUNCI
ind:=1 {bj este in A}
SFDACĂ
  SFPENTRU
  DACĂ ind=0 ATUNCI
k:=k+1; r[k]:=b[j];
SFDACĂ
 SFPENTRU
 SF-REUNIUNE
  
SUBALGORITMUL TIPMUL(n,M) ESTE: { Tipăreşte cele n elemente }
 PENTRU i:=1;n EXECUTĂ { ale mulţimii M }

________________________________________________________________________________

44
Structuri de date şi algoritmi
_______________________________________________________________________________
  TIPĂREŞTE mi
 SFPENTRU
 SF-TIPMUL
  
SUBALGORITMUL TIPORDON(n,M) ESTE: { Ordonează şi tipăreşte }
CHEAMĂ ORDON(n,M); { elementele mulţimii M }
CHEAMĂ TIPMUL(n,M);
 SF-TIPORDON
 
Tot ca exemplu de folosire a subalgoritmilor, vom scrie un algoritm pentru rezolvarea
următoarei probleme:
Exemplul I.19

Dirigintele unei clase de elevi doreşte să obţină un clasament al elevilor în funcţie de media
generală. În plus, pentru fiecare disciplină în parte doreşte lista primilor şase elevi.
  
În rezolvarea acestei probleme este necesară găsirea ordinii în care trebuiesc tipăriţi elevii în
funcţie de un anumit rezultat: nota la disciplina "j", sau media generală. Am identificat prin urmare
două subprobleme independente, referitoare la:
 (1) aflarea ordinii în care trebuie tipărite n numere pentru a le obţine ordonate;
 (2) tipărirea elevilor clasei într-o anumită ordine.
  
Prima subproblemă se poate specifica astfel:
  Dându-se numerele x1, x2, ... , xn, găsiţi ordinea o1, o2, ..., on, în care aceste numere devin
ordonate descrescător, adică
 x[o1] ≥ x[o2] ≥ ... ≥ x[on] .
  Pentru rezolvarea ei vom da un subalgoritm ORDINE în care intervin trei parametri
formali:
 - n, numărul valorilor existente;
 - X, vectorul acestor valori;
 - O, vectorul indicilor care dau ordinea dorită.
 Primii doi parametri marchează datele presupuse cunoscute, iar al treilea, rezultatele
calculate de subalgoritm.
  
SUBALGORITMUL ORDINE(n,X,O) ESTE: {n, numărul valorilor existente; X, vectorul
acestor }

________________________________________________________________________________

45
Structuri de date şi algoritmi
_______________________________________________________________________________
 { valor; O, vectorul indicilor care dau ordinea dorită }
 PENTRU i:=1; n EXECUTĂ
O[i] :=i ;
SFPENTRU
 REPETĂ ind:=0;
  PENTRU i:=1;n-1 EXECUTĂ
  DACĂ x[O[i]] < x[O[i+1]] ATUNCI
  FIE ind:=1; t:=O[i+1] ;
  O[i+1] :=O[i]; O[i] :=t;
  SFDACĂ
  SFPENTRU
 PANÂCÂND ind=0 SFREP
 SF-ORDINE
  
A doua subproblemă se poate specifica astfel:
 Dându-se ordinea o1,o2, ..., on, a elevilor clasei, numele şi mediile acestora, să se tipărească
numele şi mediile primilor k elevi în ordinea specificată.
  Subalgoritmul TIPAR, dat în continuare, rezolvă această problemă.

 SUBALGORITMUL TIPAR(k, NUME, O) ESTE:


 PENTRU i:=1;k EXECUTĂ
  @Tipăreşte datele elevului de rang oi.
 SFPENTRU
 SF-TIPAR
 
Variabilele folosite pentru problema dată sunt următoarele:
 - n reprezintă numărul elevilor clasei;
 - m este numărul disciplinelor la care elevii primesc note;
 - NUME este vectorul care reţine numele elevilor: NUMEi este numele elevului cu numărul
de ordine i;
 - NOTE este matricea notelor elevilor, având n linii şi m coloane;
NOTE[i,j] este nota elevului cu numele NUME[i] la disciplina cu numărul de ordine j;
NOTE[j] este coloana a j-a a matricei NOTE şi reprezintă notele elevilor la disciplina j;
 - MEDII este vectorul mediilor generale.

________________________________________________________________________________

46
Structuri de date şi algoritmi
_______________________________________________________________________________
  
Algoritmul este:

ALGORITMUL CLASAMENT ESTE: { Algoritmul 3.7: Ordonare }


CITEŞTE m, {numărul disciplinelor şi}
 n, {al elevilor}
 NUME[i], i=1,n, {numele elevilor}
 NOTE[i,j], j=1,m, i=1,n; {notele elevilor}
 PENTRU i:=1;n EXECUTĂ { calculează media generală}
  FIE S:=0; {a elevului i}
  PENTRU j:=1;m EXECUTĂ
S:=S+NOTE[i,j];
SFPENTRU
  FIE MEDII[i]:=S/m
 SFPENTRU
 CHEAMĂ ORDINE(n,MEDII,O);
 CHEAMĂ TIPAR(n,NUME,O)
 PENTRU j:=1;m EXECUTĂ
  CHEAMă ORDINE(n,NOTE.j,O);
  CHEAMĂ TIPAR(n,NUME,O);
 SFPENTRU
 SF-ALGORITM
  
Într-un algoritm, parametrii formali şi actuali pot fi funcţii sau proceduri. În continuare este
prezentat un astfel de exemplu, în care se calculează radicalii de ordinul doi şi trei din constanta mm
rezolvând ecuaţiile:
 x2 - mm = 0, notată g(x) = 0,
 respectiv
 x3 - mm = 0, notată h(x) = 0.
 
Pentru rezolvarea unei ecuaţii se pot folosi mai multe metode. În program am ales două:
metoda înjumătăţirii şi metoda coardei. Metoda coardei este descrisă amănunţit în secţiunea
următoare. Metoda înjumătăţirii constă în înjumătăţirea intervalului [a,b] care conţine rădăcina şi
reţinerea aceluia în care se află rădăcina, subinterval care va fi noua valoare a lui [a,b]. Calculul se
încheie în momentul în care lungimea intervalului [a,b] este mai mică decât ε, care ne dă eroarea cu

________________________________________________________________________________

47
Structuri de date şi algoritmi
_______________________________________________________________________________
care dorim să aflăm rădăcina.
 Întrucât metoda coardei foloseşte şi prima derivată, am notat prin f1, respectiv g1 derivatele
funcţiilor f şi g. Prin c şi t s-au notat cele două extremităţi ale intervalului care conţine rădăcina, t
fiind extremitatea în care se duce tangenta.
  SUBALGORITMUL coarda(c,t,r,f,f1) ESTE: {Se rezolvă ecuaţia f(x)=0 prin metoda
coardei}
 {c,t sunt extremităţile intervalului care conţine}
 {rădăcina, iar f1este derivata lui f }
 {r este rădăcina care se calculează}
 REPETĂ
  c:=c-f(c)*(t-c)/(f(t)-f(c));
  t:=t-f(t)/f1(t);
 PÂNĂCÂND t c < 0.00001;
 FIE r:=(c+t)/2
 SF-coarda
  
SUBALGORITMUL juma(a,b, r, f,f1) ESTE: {Se rezolvă prin metoda înjumătăţirii}
 {ecuaţia f(x)=0, care are o rădăcină în [a,b]}
 {r= rădăcina obţinută, iar f1 este derivata lui f}
 REPETĂ
  r:=(a+b)/2;
  DACĂ f(a)*f(r) <0 ATUNCI
b:=r
ALTFEL
a:=r
SFDACĂ
 PÂNĂCÂND ba < 0.00001 SFREPETĂ
 SF-juma
  
SUBALGORITMUL Rezec(a,b, r, f,f1, met) ESTE:
CHEAMĂ met(a,b,r,f,f1);
SF-Rezec
  
ALGORITMUL pffunctii ESTE: {Algoritmul 3.8}

________________________________________________________________________________

48
Structuri de date şi algoritmi
_______________________________________________________________________________
 TIPĂREŞTE 'Înjumătăţire Coarda ' ;
 CHEAMĂ Rezec(1,2,rj,g,g1,juma);
 CHEAMĂ Rezec(1,2,rc,g,g1,coarda);
 TIPĂREŞTE rj,rc ;
 CHEAMĂ Rezec(1,2,rj,h,h1,juma);
 CHEAMĂ Rezec(1,2,rc,h,h1,coarda);
 TIPĂREŞTE rj,rc ;
 SFALGORITM
   
Prin proiectare (programare) modulară înţelegem metoda de proiectare (programare) a
unui algoritm pentru rezolvarea unei probleme prin folosirea modulelor.
Dar ce este un modul? Modulul este considerat [Schach90, Frenţiu94] o unitate structurală
de sine stătătoare, fie program, fie subprogram, fie o unitate de program. Un modul poate conţine
sau poate fi conţinut într-alt modul. Un modul poate fi format din mai multe submodule. Astfel, în
Pseudocod fiecare subalgoritm şi algoritmul principal sunt considerate module. În limbajele de
programare cu structură de bloc (de exemplu în Turbo Pascal, prezentat în capitolul trei) UNIT-urile
pot fi considerate module. La compilarea separată un grup de subprograme compilate deodată
constituie un modul, dar acest modul poate fi considerat ca o mulţime de submodule din care este
compus.
Este însă important ca fiecare modul să-şi aibă rolul său bine precizat, să realizeze o funcţie
în cadrul întregului program. El apare în mod natural în descompunerea top-down.
Indiferent că privim modulul ca un singur subalgoritm, un grup de subalgoritmi, sau un
algoritm de sine stătător ce apelează alţi subalgoritmi, considerăm modulele relativ independente,
dar cu posibilităţi de comunicare între ele. Astfel, un modul nu trebuie să fie influenţat de maniera
în care se lucrează în interiorul altui modul. Orice modificare ulterioară în structura unui program,
dacă funcţia pe care o realizează un modul M încă este necesară, acest modul trebuie să fie util şi
folosit în continuare fără modificări.
Rezultă că programarea modulară se bazează pe descompunerea problemei în subprobleme
şi proiectarea şi programarea separată a subalgoritmilor corespunzători. De altfel, considerăm că
într-o programare serioasă nu se poate ajunge la implementare fără a avea în prealabil algoritmii
descrişi într-un limbaj de descriere (la noi Pseudocod). Deci programarea modulară se referă în
primul rând la proiectarea modulară a algoritmilor şi apoi la traducerea lor în limbajul de
programare ales, ţinând seama de specificul acestui limbaj. Programarea modulară este strâns legată
de programarea ascendentă şi de programarea descendentă, ambele presupunând folosirea
subalgoritmilor pentru toate subproblemele întâlnite.

I.2.5. Apel recursiv

În exemplele anterioare se observă că apelul unui subprogram se face după ce el a fost


definit. Este însă posibil ca un subalgoritm să se apeleze pe el însuşi. Într-un astfel de caz spunem

________________________________________________________________________________

49
Structuri de date şi algoritmi
_______________________________________________________________________________
că apelul este recursiv, iar subalgoritmul respectiv este definit recursiv.
Ca exemplu, definim în continuare o funcţie care calculează recursiv valoarea n!. Se va
folosi formula:

n   n  1!, n  0
n!  
 1, n  0

Recursivitatea constă în faptul că în definiţia funcţiei Factorial în argumentul n se foloseşte


aceeaşi funcţie Factorial dar de argument n-1. Deci funcţia Factorial se apelează pe ea însăşi. Este
important ca numărul apelurilor să fie finit, deci ca procedeul de calcul descris să se termine.

FUNCTIA Factorial(n) ESTE:


DACĂ n=0 ATUNCI
Factorial:=1
ALTFEL
Factorial:= n*Factorial(n-1)
SFDACĂ
SF-Factorial;

I.2.6. Programarea structurată

Programarea structurată este un stil de programare apărut în urma experienţei primilor ani de
activitate. Ea cere respectarea unei discipline de programare şi folosirea riguroasă a câtorva structuri
de calcul. Ca rezultat se va ajunge la un algoritm uşor de urmărit, clar şi corect.
Termenul programare, folosit în titlul acestei secţiuni şi consacrat în literatura de
specialitate, este folosit aici în sens larg şi nu este identic cu cel de programare propriu-zisă. Este
vorba de întreaga activitate depusă pentru obţinerea unui program, deci atât proiectarea algoritmului
cât şi traducerea acestuia în limbajul de programare ales.
Böhm şi Jacopini [1966] au demonstrat că orice algoritm poate fi compus din numai trei
structuri de calcul:
- structura secvenţială;
- structura alternativă;
- structura repetitivă.

I.3. Probleme propuse

I. Descrieţi în Pseudocod subalgoritmi care calculează următoarele funcţii:

 1. Pentru nN funcţia Prim(n) calculează al n-lea număr prim.

 2. Pentru z,l,aN daţi, 1≤z≤31, 1≤l≤12, 1900<a, funcţia NRZI(z,l,a) spune a câta zi din anul a este
data (z,l,a);

 3. Pentru nN funcţia UrmatorPrim(n) dă numărul prim imediat superior lui n.

________________________________________________________________________________

50
Structuri de date şi algoritmi
_______________________________________________________________________________
 4. Pentru polinomul P de gradul n cu coeficienţi reali dat şi xR funcţia VALPOL(x,n,P) dă
valoarea polinomului P în punctul x.

 5. Pentru k,nN, 0≤k≤n, funcţia C(n,k) calculează numărul combinărilor de n obiecte luate câte k.

 6. Pentru vectorii X şi Y cu n componente, funcţia E(n,X,Y) dă valoarea 0 dacă X=Y, respectiv i
dacă i este cel mai mic indice pentru care xi < yi.

 7. Cunoscând mulţimile A şi B funcţia INCLUS(A,B) verifică dacă mulţimea A este inclusă în
mulţimea B;

 8. Fie f o funcţie de forma  f : {1, 2, ..., m} → {1, 2, ..., n}.

Definim T(f) egal cu 1 dacă f este o funcţie injectivă, egal cu 2 dacă f este surjectivă, egal cu 3 dacă
f este bijectivă, şi 0 în caz contrar. Se dă funcţia f prin perechile de elemente (x,f(x)) care definesc
graficul său. Să se calculeze T(f).

 9. Se dă o relaţie binară R prin graficul său. Să se calculeze E(R) prin definiţie egală cu 0 dacă R
este o relaţie de recurenţă şi egală cu 1 în caz contrar.

 10. Şirul Fibonacci este definit astfel: f0=f1=1 şi fn=fn-1+fn-2, pentru n >1. Pentru nN dat calculaţi
F(n)=fn.

 11. Pentru nN dat calculaţi j(n), unde j este funcţia lui Euler, deci j(n) este numărul numerelor
mai mici decât n şi relativ prime cu n.

 12. Pentru nN dat calculaţi P(n), unde P(n) este 0 dacă numărul n este perfect şi 1 în caz contrar
(un număr n este perfect dacă este egal cu suma divizorilor săi mai mici decât el însuşi. Exemplu:
6=1+2+3)

  II. Scrieţi subalgoritmi pentru rezolvarea următoarelor probleme:

 1. Cunoscând mulţimile A şi B calculaţi C = A  B;

 2. Cunoscând mulţimile A şi B calculaţi C = A  B;

 3. Dându-se vectorul X cu n componente, ştergeţi toate componentele care se repetă;

 4. Dându-se vectorul X cu n componente numere întregi ordonate crescător şi aZ inseraţi pe a în
X astfel încât X să rămână ordonat crescător;

 5. Dându-se două polinoame calculaţi suma lor;

 6. Dându-se două polinoame calculaţi produsul lor;

________________________________________________________________________________

51
Structuri de date şi algoritmi
_______________________________________________________________________________
 7. Dându-se două matrice calculaţi suma lor;

 8. Dându-se două matrice pătrate de ordinul n calculaţi produsul lor;

 9. Dându-se două numere A şi B prin reprezentările lor în baza p găsiţi suma lor A+B prin
reprezentarea ei în baza p;

 10. Dându-se numerele reale x1, x2,... ,xn, determinaţi secvenţa de termeni consecutivi care are
suma maximă.

 11. Se dau p,qN şi numărul A prin reprezentarea sa în baza p. Determinaţi reprezentarea sa în


baza q.

 12. Se dă nN, n≥2. Să se formeze matricea pătrată A de ordinul n ale cărei coloane scrise una
după alta constituie primii n2 termeni ai şirului
1,2,3,4,5,6,7,8,9,1,0,1,1,1,2,1,3,1,4,1,5,1,6,1,7,1,8,1,9,2,0, ...  obţinut din scrierea cifrelor
semnificative ale numerelor naturale.

  III. Proiectaţi prin metoda top-down algoritmi pentru rezolvarea următoarelor probleme:

1. Se dă un şir de numere naturale x 1, x2, ..., xn. Spunem că două numere naturale sunt prietene dacă
scrierea unui număr (în baza 10) este obţinută prin scrierea cifrelor celuilalt în ordine inversă (de
exemplu 3678 şi 8763). Să se găsească toate perechile de numere consecutive prietene din şirul dat
şi frecvenţele cifrelor în scrierea numerelor date.

  2. Se dă un şir de numere naturale x 1, x2, ..., xn. Să se găsească toate subşirurile de elemente
consecutive de lungime maxima formate din numere prime şi toate numerele prime distincte
întâlnite.

  3. Se dă o matrice pătrată A de ordinul n şi numărul natural m>0. Se cere să se tipărească matricele
A, A2, ..., Am şi suma lor.

  4. Se dă polinomul P cu coeficienţi întregi, fie prin monoamele sale, fie prin grad şi coeficienţi. Să
se scrie un algoritm care determină rădăcinile raţionale ale polinomului P.

 5. Se dau numerele naturale n1 şi n2. Determinaţi mulţimea numerelor prime aflate între n1 şi n2 şi
mulţimea perechilor de gemeni (numerele prime p şi q se numesc gemeni dacă q-p=2).

  6. Fiind date mai multe polinoame cu coeficienţi reali să se determine suma lor şi polinomul de
grad maxim. Un polinom se dă fie prin monoamele sale, fie prin grad şi coeficienţi.

  7. Se citesc mai multe şiruri de numere naturale. Pentru fiecare şir citit, tipăriţi cea mai lungă scară
(secvenţa de termeni consecutivi strict crescători) şi depuneţi această scară într-un şir (rezultat) R.
La sfârşit tipăriţi cea mai lungă scară din R. Citirea unui şir se termină la întâlnirea unui număr
negativ, iar citirea se opreşte la şir de lungime 0 (deci fără nici un termen).

________________________________________________________________________________

52
Structuri de date şi algoritmi
_______________________________________________________________________________
  8. Se dau mai multe mulţimi de numere întregi pozitive. Să se determine reuniunea şi intersecţia
acestor mulţimi.

  9. Se dau mai multe mulţimi de numere naturale. Fie I(M) numărul mulţimilor diferite de M si care
conţin pe M. Să se determine mulţimea pentru care I(M) este maxim.

  10. O matrice rară A este o matrice în care majoritatea termenilor sunt nuli. O astfel de matrice se
poate reprezenta prin tripletele (linie, coloană, valoare) corespunzătoare valorilor nenule ale
matricei; deci A[linie,coloană] = valoare. Dându-se mai multe matrice rare determinaţi suma lor.

  11. Se dau mai multe numere naturale prin reprezentările lor în baza p. Găsiţi suma lor şi maximul
acestor numere (reprezentate în baza p şi toate operaţiile se fac în baza p).

  12. Se dau mai multe matrice cu coeficienţi întregi. Se cere suma matricelor care au determinantul
nenul, matricele care au determinantul nul şi matricea care are determinantul maxim.

Complexitatea algoritmilor

Etapele rezolvării unei probleme

Este cunoscut faptul că rezolvarea unei probleme presupune în principal trei etape:

I. Analiza problemei
II. Proiectarea soluţiei

III. Implementarea şi testarea soluţiei în practică.

În funcţie de gradul de generalitate a analizei efectuate, în a doua etapă se întâlnesc două


situaţii: proiectarea unei soluţii particulare, valabilă doar pentru acea problemă, şi proiectarea unei
soluţii generale, valabilă pentru orice instanţiere a acelei probleme, soluţia generalizată.
În timp ce soluţia particulară este valabilă doar pentru o instanţă a problemei, soluţia
generală este independentă de parametrii problemei şi oferă o metodă generală de rezolvare a
problemei.
Astfel, soluţionarea imediată a ecuaţiei x3+1=0 este particulară faţă de soluţionarea ecuaţiei
generalizate ax3+bx2+c=0.
 Noţiunea de algoritm şi cea de program
Atunci când metoda generală de rezolvare a unei probleme este prezentată precis, pe paşi ce se
efectuează într-o ordine bine precizată şi care conduc în timp finit la soluţia oricărei instanţieri a
problemei, vorbim de algoritmul de rezolvare a problemei.
De exemplu, algoritmul de determinare a unei soluţii reale a ecuaţiei polinomiale P(x) = 0,
cu o aproximare  dată, prin metoda tangentei.

________________________________________________________________________________

53
Structuri de date şi algoritmi
_______________________________________________________________________________
Avantajul major al proiectării unui algoritm de soluţionare a problemei este dat de faptul că
efortul de rezolvare poate fi transferat unei maşini automate (calculator) sub forma programului
executabil ce implementează algoritmul general de soluţionare.
Implementarea algoritmului general de soluţionare a problemei într-un program pe
calculator permite o importantă economie prin faptul că efortul major de analiză şi proiectare a
soluţiei a fost efectuat o singură dată iar rezolvarea problemei se reduce la efortul foarte redus de
executare (rulare) a programului cu ajutorul calculatorului pentru fiecare instanţă diferită a
problemei.
 Modelul abstract al Maşinii Turing
Primul model teoretic riguros de maşină automată de calcul este Modelul maşinii abstracte
Turing (1936). Acest model teoretic a stat la baza apariţiei efective a primei maşini electronice de
calcul, denumită mai târziu computer (calculator) după denumirea operatorului uman
(tehnicianului) care era angajat să facă toate calculele inginereşti sau contabile ale unui proiect sau
ale unei firme.
Teza Turing-Church
Rezultă că, în ultimă instanţă, puterea de rezolvare a unui calculator se reduce la puterea de
calcul a maşinii Turing. Iar puterea de calcul a acestei maşini abstracte riguroase este exprimată sub
forma Tezei Turing-Church: O maşină Turing poate face tot ceea ce poate face un computer uman
înzestrat cu creion, oricâte foi de hârtie şi reguli precise ( mecanice sau automate ) de calcul.
Există, pe lângă această formulare iniţială a lui Turing, o serie de alte formulări echivalente
ale acestei teze unanim acceptate de teoreticienii ce au pus bazele teoriei informatice şi a ştiinţei
calculatoarelor (computer science). Să observăm că această teză (propoziţie acceptată fără
demonstraţie ca având valoarea logică de adevărat) stabileşte o echivalenţă perfectă între puterea de
calcul a unui computer uman şi puterea de calcul a unui computer maşină.
Echivalarea subînţelesă (tacită) a noţiunii teoretice vagi de algoritm cu noţiunea matematică
riguroasă de Maşină Turing a însemnat acceptarea unanimă a Tezei Turing-Church de către
iniţiatorii informaticii. În consecinţă, studiul eficienţei unui algoritm în soluţionarea unei probleme
revine în studiul eficienţei în funcţionare a maşinii Turing şi implicit a eficienţei în funcţionare a
programului ce implementează acel algoritm de rezolvare.
Aceasta este consecinţa faptului că, în accepţiunea originală, algoritmul de soluţionare a unei
probleme este destinat unui computer uman, dar puterea de calcul a acestuia este aceeaşi cu puterea
de calcul a unei maşini Turing = computer maşină care este pusă în mişcare printr-un program
executabil.
 
Analiza, Proiectarea şi Implementarea algoritmilor şi Complexitatea algoritmilor

Reluând ideea iniţială, în rezolvarea automată a unei probleme (adică rezolvarea cu ajutorul
calculatorului a oricărei instanţe diferite problemei) se trece prin cele trei etape: Analiza teoretică a
problemei, Proiectarea algoritmului de soluţionare şi Implementarea algoritmului într-un program
executabil pe calculator.
În timp ce în prima etapă –analiza problemei - rezultatul cheie, pe care se concentrează
preponderent efortul de analiză, este demonstrarea corectitudinii soluţiei, în a doua etapă –
proiectarea algoritmului de soluţionare - cuvântul cheie este eficienţa de rezolvare. Studiul cu un
pronunţat caracter teoretic conţinut în această a doua etapă îşi propune să prevadă eficienţa în
funcţionare (necesarul de timp şi spaţiu calculator) a programului executabil ce va implementa
algoritmul, eventual prin compararea teoretică a algoritmilor de soluţionare diferiţi.

________________________________________________________________________________

54
Structuri de date şi algoritmi
_______________________________________________________________________________
De exemplu, în cazul problemei sortării unui şir de n chei prin comparaţii se poate estima
teoretic comparativ că algoritmul de sortare QuickSort este printre cele mai eficiente soluţii.
Disciplina informaticii care se ocupă cu studiul teoretic al eficienţei algoritmilor este numită
Complexitatea algoritmilor.

Operaţia de bază, cheia studiului complexităţii

La baza studierii complexităţii unui algoritm stă detectarea în descrierea algoritmului a


operaţiei sau a operaţiilor de bază, acea operaţie (acele operaţii) aflată (aflate) în cel mai interior
corp de ciclu repetitiv şi a cărei (a căror) contorizare permite estimarea în avans, înainte de lansarea
în execuţie, a timpului de execuţie a programului executabil corespunzător.
În majoritatea cazurilor există o legătură foarte strânsă între operaţia de bază (cea mai
repetată operaţie) şi operaţia care este dedusă în etapa de analiză a problemei ca fiind operaţia
inevitabilă pentru rezolvarea problemei.
De exemplu, în cazul problemei sortării unui şir de n chei prin comparaţii este limpede că
operaţia de comparare a “mărimii” cheilor este operaţia inevitabilă şi de asemenea ea va fi şi
operaţia de bază a cărei contorizare va permite estimarea, înainte de execuţie, a duratei de
funcţionare a programului ce implementează algoritmul de sortare respectiv.
 

Clase de algoritmi
În această situaţie, toţi algoritmii diferiţi care soluţionează aceeaşi problemă şi care se
bazează pe aceeaşi operaţie de bază (inevitabilă) formează clasa algoritmilor de soluţionare a
problemei. De obicei numele clasei va fi dat chiar de numele acelei operaţii de bază ce este
conţinută de fiecare algoritm al clasei în mod inevitabil.
De exemplu, în cazul problemei sortării unui şir de n chei prin comparaţii, algoritmii diferiţi
ce soluţionează problema sunt grupaţi în clasa algoritmilor de sortare prin comparaţii, spre
deosebire de clasa algoritmilor de sortare prin distribuire ce soluţionează aceeaşi problemă
bazându-se însă pe altă operaţie de bază: distribuirea cheilor de sortat.
Operaţia de bază a algoritmilor dintr-o clasă de algoritmi poate fi comparată cu o vergea
verticală pe care sunt înşiraţi toţi algoritmii clasei. Ea este cea care permite studiul comparativ a
eficienţei algoritmilor din acea clasă (ea fiind o veritabilă coloană vertebrală a clasei respective).
Eficienţa algoritmilor
Compararea eficienţei a doi algoritmi diferiţi ce soluţionează aceeaşi problemă se face prin
determinarea teoretică a numărului de repetiţii a operaţiei de bază în fiecare algoritm şi
compararea celor două valori. În final rezultatul comparării va permite estimarea comparativă a
duratei de funcţionare a programelor şi alegerea celui mai bun.
Compararea eficienţei a doi algoritmi, din punct de vedere practic, se face prin compararea
timpilor medii de execuţie a celor două programe ce îi implementează. Această metodă pragmatică
are dezavantajul că necesită rularea programelor care, în anumite situaţii, poate dura un timp
surprinzător de mare.

Funcţiile de complexitate
Numărul de repetiţii al operaţiei de bază se exprimă matematic printr-o funcţie de
complexitate individuală asociată algoritmului, funcţie ce depinde în mod inevitabil de dimensiunea
(mărimea) vectorului datelor de intrare.

________________________________________________________________________________

55
Structuri de date şi algoritmi
_______________________________________________________________________________
De exemplu, în cazul algoritmului de determinare a maximului dintr-un şir de n elemente
prin comparaţii, este evident că numărul de comparaţii efectuat de orice algoritm de maxim va fi o
funcţie de n. Iată descrierea în Pseudocod a algoritmului:
Citeşte n, a1, a2,…, an; După cum se observă vectorul de intrare (şirul a) are dimensiunea n.
Max := a1;
Pentru i := 2 pînă la n execută Este evident că operaţia de bază – comparaţia “<” – se execută de
Dacă Max < ai atunci Max := ai; n-1 ori, adică pentru valorile contorului i cuprinse între 2 şi n.
Scrie Max; Deci, funcţia care descrie numărul operaţiilor de bază este: f(n)=n-1
În studiul complexităţii algoritmilor, pentru a permite realizarea comparaţilor necesare
pentru clasificarea algoritmului din punct de vedere al eficienţei, dimensiunea vectorului de intrare
este “împinsă” spre infinit.
Compararea complexităţii algoritmilor dintr-o clasă de algoritmi cere în mod inevitabil
gruparea funcţiilor de complexitate în clase de funcţii de complexitate şi studiul comportamentului
asimptotic al acestora (când n –dimensiunea vectorului de intrare tinde către infinit).
Apartenenţa la o clasă de complexitate
În acest fel, complexitatea unui algoritm se poate exprima prin precizarea clasei de
apartenenţă a funcţiei de complexitate a respectivului algoritm. Notaţiile clasice care exprimă felul
apartenenţei funcţiei de complexitate f(n) a unui algoritm la diverse clase de funcţii sunt ,  şi .
În această introducere sînt prezentate noţiuni de bază folosite în capitolele următoare ale
lucrării. O atenţie specială este acordată structurilor de tip graf, care intervin foarte frecvent în
elaborarea algoritmilor geometrici.
Definiţia 1.1. Un algoritm este un set de instrucţiuni care trebuie executate pentru a se obţine un
răspuns la o problemă dată.
Un algoritm are următoarele proprietăţi (Knuth [1976], Burdescu [1998]):

 finitudine: trebuie să se termine întotdeauna după un număr finit de paşi (instrucţiuni);


 determinism: fiecare pas trebuie să fie exact precizat, în mod riguros şi neambiguu;

 generalitate: trebuie să rezolve problema pentru orice date de intrare din domeniul
precizat;

 efectivitate: fiecare instrucţiune trebuie să fie exactă şi de durată finită.

Ultima proprietate trebuie nuanţată, având în vedere faptul că memoria oricărui calculator
este limitată. Nu întotdeauna operaţiile aritmetice se efectuează exact, în unele cazuri obţinându-se
o aproximare a rezultatelor. Există şi soluţii propuse pentru respectarea acestei proprietăţi, care
presupun implementarea software a operaţiilor elementare cu numere întregi (Fortune. et al.
[1993]), dar acestea duc la scăderea vitezei de prelucrare. De aceea, abordări recente iau în
considerare modelul bazat pe aritmetica în virgulă mobilă, implementat pe toate calculatoarele
actuale (Shewchuk [1997]).
Având un algoritm care rezolvă o problemă dată, urmează să determinăm resursele acestuia
(Livovschi şi Georgescu, [1986]). Concret, de câtă memorie şi timp avem nevoie ca să obţinem
soluţia problemei? În acest scop facem următoarele simplificări: fiecare operaţie elementară a
algoritmului se execută într-o unitate de timp, informaţiile despre un obiect elementar se
memorează într-o locaţie de memorie.

________________________________________________________________________________

56
Structuri de date şi algoritmi
_______________________________________________________________________________
Fie f  N  N o funcţie care indică relaţia dintre numărul de valori (date de intrare)
prelucrate de un algoritm, şi numărul de operaţii elementare efectuate de acesta pentru obţinerea
rezultatelor. Funcţia f poate avea o expresie analitică destul de complicată, de aceea considerăm
încă o funcţie g  N  N cu o expresie analitică simplificată.
Definiţia 1.2. Funcţia f are ordinul de mărime cel mult g(n), notaţie: f  O(g(n)), dacă şi numai
dacă există valori c  0 şi n0  N astfel încât pentru orice n  n0 să avem f(n)  c g(n).
O(g)  { h  N  N |  c  0, n0  N a. î.  n  n0, h(n)  c g(n) }.
(1.1)
Definiţia 1.3. Funcţia f are ordinul de mărime cel puţin g(n), notaţie: f  (g(n)), dacă şi numai
dacă există valori c  0 şi n0  N astfel încât pentru orice n  n0 să avem f(n)  c g(n).
(g)  { h  N  N |  c  0, n0  N a. î.  n  n0, h(n)  c g(n) }.
(1.2)
Definiţia 1.4. Funcţia f are ordinul de mărime g(n), notaţie: f  (g(n)), dacă şi numai dacă există
valori c1, c2  0 şi n0  N astfel încât pentru orice n  n0 să avem c1 g(n)  f(n)  c2 g(n).
(g)  { h  N  N |  c1, c2  0, n0  N a. î.  n  n0, c1 g(n)  h(n)  c2 g(n) }.(1.3)
Prezentăm două rezultate remarcabile care vor fi folosite foarte frecvent pe parcursul lucrării
Knuth [1976]:
(1) Se dă un şir de n valori dintr-un domeniu pe care este definită o relaţie de ordine totală.
Cel mai eficient algoritm de ordonare a şirului dat, care se bazează pe comparaţii, are complexitate
de ordin (n log n).
(2) Se dă un şir de n valori ordonate. Căutarea unei valori (localizarea poziţiei acesteia sau
obţinerea unui răspuns negativ) în şirul dat necesită un timp de ordin O(log n).

O categorie specială de probleme, numite NP-complete, se caracterizează prin următoarele:

 nu se cunosc algoritmi eficienţi (de complexitate polinomială), se cunosc în schimb


algoritmi de complexitate exponenţială pentru rezolvarea acestora;
 problemă NP-completă este polinomial transformabilă într-o altă problemă tot NP-
completă: dacă se rezolvă o problemă A, soluţia unei alte probleme B se poate obţine
printr-o transformare în timp polinomial din soluţia problemei A.

Cea mai generală problemă NP-completă este problema satisfiabilităţii: fiind dată o expresie
logică în forma normală conjunctivă cu n variabile, să se determine dacă pot fi atribuite valori
logice variabilelor astfel încât expresia să fie adevărată Cook [1970].

Complexitatea medie. Considerăm un algoritm care procesează n valori date la intrare.


Pentru o anumită configuraţie a valorilor, probabilitatea configuraţiei fiind pi, sunt necesare fi(n)
operaţii. Complexitatea medie a algoritmului este o sumă ponderată:  pi f i  n .
i

Exemplul 1.1. Algoritmul Quick-sort necesită un timp de ordin O(n log n) în majoritatea cazurilor
pentru a ordona un şir de n valori. Există însă câteva configuraţii (foarte puţine) care au nevoie de
un timp de ordin O(n2) pentru a fi procesate. Complexitatea medie a acestui algoritm este de ordin
O(n log n) Knuth [1976]:

________________________________________________________________________________

57
Structuri de date şi algoritmi
_______________________________________________________________________________
Într-un mod similar se poate defini noţiunea de complexitate pentru necesarul de memorie.
Aceasta arată câtă memorie este necesară pentru rezultatele intermediare şi cele de ieşire.
Definiţiile date mai sus asigură o estimare a eficienţei unui algoritm independentă de o
implementare practică a acestuia folosind un anumit limbaj de programare, urmărindu-se
„ascunderea” factorului multiplicativ. Totuşi acest factor nu poate fi întotdeauna ignorat, deoarece
există probleme care admit mai mulţi algoritmi cu acelaşi ordin de complexitate, şi atunci trebuie să
se efectueze o „rafinare” a studiului complexităţii. Rezultate recente arată că această constantă poate
influenţa eficienţa unor implementări, de aceea abordările clasice ale acestui subiect trebuie privite
cu rezerve.
Unele probleme de optimizare se rezolvă cu ajutorul unor algoritmi de complexitate mare,
exemplu: probleme NP-complete sau de complexitate O(nd). În asemenea situaţii ne mulţumim cu
soluţii aproximative obţinute cu ajutorul unor algoritmi euristici, mult mai eficienţi şi de cele mai
multe ori mai simpli.
O categorie specială, frecvent întâlnită mai ales în probleme de geometrie, este aceea a
algoritmilor incrementali. Un algoritm din această categorie procesează valorile de intrare una câte
una, la fiecare pas obţinându-se o soluţie parţială pentru datele deja procesate.
Dacă algoritmul de determinare a maximului din n chei prin comparaţii efectuează un
număr liniar de comparaţii (adică proporţional cu n) atunci se spune că el este un algoritm  (n)
(adică are funcţia de complexitate din  (n) = mulţimea funcţiilor lineare de forma c n), iar dacă
efectuează cel puţin, respectiv cel mult, tot atâtea comparaţii (adică proporţional cu n) se spune că
algoritmul este  (n), respectiv  (n).
Clasele de funcţii de complexitate, şi implicit clasele de complexitate a algoritmilor, cele
mai des întâlnite, în ordine crescătoare a complexităţii sunt:  (ln n) - clasa algoritmilor cu
complexitate logaritmică (timpul de execuţie va fi egal cu c ln n),  (n) - liniară (timpul de
execuţie va fi egal cu c n),  (n ln n) - liniar-logaritmică (timpul de execuţie va fi egal cu c n ln
n),  (P(n)) - polinomială (timpul de execuţie va fi egal cu c P(n)),  (ex) - exponenţială (timpul de
execuţie va fi egal cu c Exp(n)) sau  (n!) - factorială (timpul de execuţie va fi egal cu c n!). Peste
tot c este o constantă de proporţionalitate care depinde de calculatorul pe care se rulează
programele.
Iată cîte un exemplu de algoritm din literatura de specialitate, corespunzător fiecărei clase:
algoritmul de căutare binară BinarynSearch -  (log n), algoritmul de determinare a maximului
Max -  (n), algoritmul de sortare prin comparaţii QuickSort -  (n log n), algoritmul comun de
înmulţire a matricelor -  (n3), algoritmul de colorare optimă cu k culori a unui graf cu n noduri prin
încercări repetate -  (kn), respectiv, algoritmul de generare a permutărilor -  (n!), toţi algoritmii
având vectorul de intrare cu n elemente (de dimensiune n).

Limita inferioară sau efortul minimal


Atunci când într-o clasă de algoritmi, toţi bazaţi pe aceeaşi operaţie de bază, se poate stabili
o limită inferioară a numărului de operaţii de bază inevitabile ce trebuiesc efectuate pentru
soluţionarea problemei, putem vorbi despre efortul minimal de rezolvare şi despre algoritmul
optimal de rezolvare a problemei respective.
Algoritm optimal
Algoritmul optimal este acel algoritm care efectuează cel mai mic număr de operaţii de bază
dintre toţi algoritmii clasei sale, cunoscuţi sau neinventaţi încă (o clasă de algoritmi prin criteriul
său de apartenenţă - prezenţa operaţiei de bază inevitabile - include obligatoriu în acea clasă toţi

________________________________________________________________________________

58
Structuri de date şi algoritmi
_______________________________________________________________________________
algoritmii ce rezolvă problema bazându-se pe operaţia respectivă, chiar şi algoritmii nedescoperiţi
încă !).
De exemplu, în clasa algoritmilor de sortare prin comparaţii se înţelege că este aparţinător
orice algoritm de sortare bazat pe comparaţii cunoscut sau necunoscut încă. În plus, limita
inferioară a numărului de comparaţii necesar pentru sortarea listei de n elemente (efortul minimal)
prin comparaţii este demonstrat riguros ca fiind log2 n! dar un algoritm optimal care să efectueze
exact atâtea operaţii de bază (comparaţii) nu se cunoaşte.
În clasa algoritmilor de determinare a maximului prin comparaţii, algoritmul clasic de
determinare a maximului este un algoritm optimal întrucât el efectuează un număr minimal de
comparaţii inevitabile: n-1.
Complexitatea algoritmilor şi dificultatea problemelor
Din perspectiva rezolvării problemelor cu ajutorul calculatorului putem studia şi compara
dificultatea problemelor studiind şi comparând complexitatea algoritmilor optimali de soluţionare a
acestora.
Astfel, problema sortării prin comparaţii cere un efort minimal liniar-logaritmic. Ea nu este
totuşi o problemă de efort liniar-logaritmic ( (n log n)) întrucât ea poate fi soluţionată cu efort
liniar ( (n) operaţii) atunci când cheile de sortare pot fi distribuite folosind un algoritm de sortare
prin distribuire.

Complexitatea de timp
Măsurarea în practică a duratei de execuţie (adică a complexităţii de timp) a implementării
algoritmilor a condus la următoarea concluzie categorică: singurii algoritmi ce soluţionează în mod
eficient o problemă oarecare sunt acei algoritmi care au complexitatea polinomială (sau mai mică
decât exponenţială). O astfel de soluţie se numeşte soluţie rezonabilă, faţă de cealaltă situaţie cu
soluţie nerezonabilă având o complexitate de timp exponenţială sau factorială (total ineficientă ca
durată de execuţie).
De exemplu, dacă pentru colorarea unui graf cu n vârfuri cu doar trei culori, se încearcă
toate cele 3n variante de colorare (printr-un algoritm de tip backtracking), timpul necesar unui
program pentru epuizarea tuturor cazurilor, în cazul în care n=100, este direct proporţional cu
uriaşa valoare de 3100 = 38742048910 ceea ce face acel program cu totul inutilizabil (chiar dacă am
presupune că o instrucţiune s-ar executa într-o pico-secundă = 10-12s).
 
Probleme rezonabile şi probleme nerezonabile. Clasa problemelor P-polinomiale
După efortul de rezolvare necesar, reductibil în ultimă instanţă la durată de execuţie a
programului, problemele se pot împărţi în probleme rezonabile şi nerezonabile. Ţinând cont de cele
de mai sus, problemele rezonabile sunt problemele de dificultate polinomială şi ele sunt grupate în
clasa problemelor P (Clasa problemelor Polinomiale). Pentru fiecare problemă din această clasă se
cunoaşte un algoritm de soluţionare cu complexitate polinomială, adică programul ce
implementează acest algoritm are o durată de execuţie proporţională cu o funcţie polinomială de n,
unde n este dimensiunea datelor de intrare.
Există cu siguranţă probleme ce nu fac parte din această clasă P: de exemplu, problema
generării tuturor celor n! permutări ale unui şir de n elemente distincte, care are inevitabil
dificultatea (durata de execuţie) factorială n!.

Probleme de decizie şi probleme de optimizare

________________________________________________________________________________

59
Structuri de date şi algoritmi
_______________________________________________________________________________
În încercarea de a studia şi clasifica dificultatea problemelor ne-polinomiale (nerezonabile ca
durată de rezolvare cu ajutorul calculatorului) ele au fost grupate în două categorii: probleme în
varianta de decizie, în care se cere doar să se decidă prin DA sau NU dacă există pentru problema
respectivă o soluţie bine-precizată, şi probleme în varianta de optimizare, în care se cere
determinarea soluţiei optime pentru problema respectivă.
De exemplu, problema colorării grafului în varianta de decizie cere să se decidă dacă un graf
poate fi colorat cu k culori, unde k este precizat dinainte. Aceeaşi problemă, în varianta de
optimizare, cere să se determine  - numărul minim de culori necesar (numărul cromatic) pentru
colorarea unui graf (astfel încât oricare două vârfuri adiacente să fie colorate diferit),  fiind
evident un număr ce nu poate fi precizat dinainte.
 
Clasa problemelor NP (non - deterministic polinomiale)

Există o clasă largă de probleme de decizie cu extrem de numeroase aplicaţii practice numită
clasa problemelor NP (non-deterministic polinomiale). Această clasă include pe lângă toate
problemele de decizie din P şi o mulţime foarte mare de probleme de decizie (toate inspirate din
practică) cărora nu li se cunoaşte o soluţie polinomială.
Pentru a putea explica de ce sunt numite NP (non-deterministice), este necesar să revenim la
echivalarea unanim acceptată de teoreticienii informaticii între noţiunea de algoritm şi noţiunea de
maşină Turing. Astfel, pentru fiecare din problemele NP se cunoaşte o soluţie sub forma unei
maşini Turing nedeterministice(!) (care la o tranziţie de stare poate trece dintr-o stare într-una din
oricare alte k stări posibile, fără a se putea preciza exact în care) care se opreşte furnizând soluţia
după un număr polinomial de paşi.
În termenii algoritmici, aceeaşi non-determinarea este exprimată astfel: o problemă NP este o
problemă ce are un algoritm de soluţionare care decide în timp polinomial dacă o soluţie propusă
(prin "ghicire" sau "inventare" ) este sau nu validă.
În aceşti termeni, nedeterminarea constă mai exact în faptul că sub-algoritmul de “ghicire”
sau “inventare” de soluţii, din cauză că nu poate parcurge întreg spaţiul soluţiilor posibile pentru că
nu ar mai rămâne un algoritm polinomial, nu oferă nici o garanţie că găseşte sau că se “apropie”
măcar în vreun fel de soluţia problemei prin simplă “ghicire”.

Reducţia polinomială a problemelor

În intenţia de a compara dificultatea problemelor de decizie între ele s-a sesizat că dacă
există un algoritm de transformare T a datelor de intrare D’ ale unei probleme de decizie P’ astfel
încât noile date de intrare D=T(D’) să se potrivească ca date de intrare unei probleme de decizie P,
ce are ca soluţie un algoritm de soluţionare cunoscut A, atunci un algoritm de soluţionare A’ a
problemei P’ se obţine imediat prin compunerea algoritmului A cu transformarea T astfel încît A’=
A T.
Dacă complexitatea algoritmului de transformare T este rezonabilă (polinomială) atunci
înseamnă că complexitatea algoritmului A’ obţinut prin compunere este tot atât de rezonabilă cu cea
a algoritmului A care este cunoscut (adică, dacă A are complexitate polinomială şi A’ va avea tot o
complexitatea polinomială obţinută prin compunerea celor două funcţii de complexitate polinomiale
corespunzătoare lui A şi T). În acest caz, când algoritmul T de transformare a intrării unei probleme
în intrarea alteia este de complexitate polinomială, atunci întreagă această metodă ce transformă

________________________________________________________________________________

60
Structuri de date şi algoritmi
_______________________________________________________________________________
soluţionarea problemei P’ în soluţionarea problemei P cunoscută deja, se numeşte reducţie
polinomială iar cele două probleme se spune că se reduc una la cealaltă: P’  P.
De exemplu, se poate arăta că Problema Orarului (unui institut de învăţământ) este
reductibilă la Problema Colorării Grafului prin faptul că sub-algoritmul de transformare a orelor de
predare/zile/clase/profesori în nodurile unui graf este liniară iar aşezarea orelor pe orar este
echivalentă cu colorarea grafului obţinut prin această transformare liniară.
 
Echivalenţa problemelor prin reducţie polinomială
Atunci când două probleme P şi P’ se reduc polinomial reciproc una la cealaltă, adică P’ 
P şi P  P’, se spune că cele două probleme sunt echivalente.
Desigur, ţinând cont de cele zise mai sus, se poate arăta că Problema Orarului şi Problema
Colorării Grafului sunt echivalente.
 
Teorema lui Cook. Probleme NP-Complete

Bazîndu-se pe Teza Turing-Church şi folosind câteva elemente de calcul logic


propoziţional, în 1971 Cook a demonstrat o teoremă fundamentală pentru domeniul complexităţii
algoritmilor. Teorema lui Cook afirmă că: Toate problemele NP sunt reductibile polinomial la
Problema FNC, deci problema FNC este o problemă NP-completă.
Problema FNC (Problema Formei Normale Conjuctive) cere să se decidă dacă este posibil să existe
o alegere de valori logice Adevărat sau Fals pentru n variabile logice x1, x2, …, xn ce apar într-o
formă normală conjunctivă (formată dintr-un şir de clauze conjugate, fiecare clauză fiind obţinută
doar prin compunerea – sau logic – a variabilelor xi sau negaţiei lor  xi) astfel încât valoarea logică
a propoziţiei logice finale să fie Adevărat.
De exemplu, fie x, y, z cele trei variabile logice, o formă normală conjuctivă posibilă cu trei
clauze conjugate este: ( x  y )  ( x   y   z)  (  x  z ) ce admite ca soluţie x=Adevărat,
y=Adevărat sau Fals şi z=Adevărat.
 
Clasa problemelor NP-Complete

După descoperirea şi demonstrarea existenţei primei probleme NP-complete a urmat


demonstrarea NP-completitudinii, prin echivalarea cu problema FNC, a unei serii întregi de
probleme clasice din informatică şi cunoscute ca fiind probleme extrem de dificil de soluţionat
eficient în timp: Problema Colorării Grafului, Problema Partiţionării Sumei, Problema Comis-
Voiajorului, Problema Rucsacului, Problema Planificării Sarcinilor cu Penalizări, etc.
S-a obţinut astfel clasa problemelor NP-complete ce au neobişnuita proprietate că prin
soluţionarea în timp polinomial doar a uneia dintre ele se va obţine automat în consecinţă soluţia
polinomială pentru toate celelalte.
Din păcate, pentru nici una din sutele de probleme NP-complete inventariate până acum nu
se cunoaşte o soluţie polinomială (rezonabilă în timp de execuţie). Însă nu s-a putut demonstra că
vreuna dintre ele nu poate admite o soluţie polinomială.
 
Marea Întrebare a Complexităţii algoritmilor

________________________________________________________________________________

61
Structuri de date şi algoritmi
_______________________________________________________________________________
Aceasta este Marea Întrebare a complexităţii algoritmilor dar şi a informaticii: P=NP ? Sînt
problemele NP reductibile la cele din P ? Pot exista soluţii polinomiale pentru problemele NP ?
Răspunsul la Marea Întrebare este de importanţă capitală pentru majoritatea domeniilor
practice în care calculatorul are un rol tot mai important (strategic, am putea zice): reţele de
transport şi reţele de comunicaţii, astronautică şi aeronautică, domeniile militare şi a serviciilor de
informaţii, proiectarea automată, urmărirea automată şi controlul proceselor industriale, etc. Un
răspuns afirmativ, P=NP, ar avea ca şi consecinţă imediată posibilitatea introducerii calculatorului
în deplină siguranţă în poziţiile de decizie şi control automat a proceselor cheie a fiecăruia din
domeniile amintite, datorită capacităţii algoritmilor implementaţi de a lua decizii optime în timp real
(rezonabil) în orice situaţie.
Atunci, actualul nume: Era informatizării, prematur acordat perioadei ultimilor ani, s-ar
potrivii cu adevărat întru-totul, iar noţiunile de pilot automat sau robot ar face parte din viaţa
cotidiană a fiecăruia. Merită aici să atragem atenţia asupra puterii extraordinare pe care o vor
dobândi în acea situaţie cei ce supervizează şi programează calculatoarele aflate în puncte strategice
de decizie, putere ce fără exagerare ar putea fi numită planetară, şi de aceea considerăm că:
din păcate şansele de a afla cu toţii despre existenţa răspunsului afirmativ la Marea Întrebare,
înainte ca consecinţele strategice ale acestuia să fi fost deja puse în practică la scară planetară,
sunt minime.
Pentru a răspunde însă negativ la Marea Întrebare este suficient să se demonstreze riguros că
una din problemele NP-complete are o complexitate exponenţială, deci cu siguranţă nu admite o
soluţie cu timp de execuţie polinomial. Sau, altfel spus, ar fi suficient să se arate riguros că într-o
anumită problemă NP-completă, numărul de operaţii de bază inevitabile nu poate fi mai mic decât
exponenţial.
În situaţia unui răspuns negativ la Marea Întrebare, toate punctele de decizie în timp real vor
fi ocupate ca şi până acum de operatori umani ce nu trebuie şi nu pot fi priviţi ca computere umane,
ei comportându-se uneori omeneşte, deci într-un mod nedeterminist. Să nu uităm că avem cu toţii
experienţa momentelor cruciale din viaţa personală ce au cerut stringent luarea unei decizii şi în
majoritatea cazurilor decizia luată de noi n-a fost una de tip raţional, “algoritmic” sau după o
“reţetă” anume, ci soluţia aleasă a avut o componentă subiectivă intuitivă sau chiar ilogică.
 
Algoritmi aproximativi

Întrucât nu sunt încă semne clare că se va putea găsi în viitorul imediat răspunsul la Marea
Întrebare, deşi se fac eforturi mari în această direcţie, disciplina Complexitatea algoritmilor s-a
îmbogăţit prin introducerea unui capitol consistent: Proiectarea şi studiul complexităţii algoritmilor
aproximativi. În primul rând trebuie precizat că nu este necesară proiectarea unor astfel de algoritmi
decât în cazul problemelor cărora nu li se cunoaşte o soluţie rezonabilă (alta decât exponenţială).
Prin algoritm aproximativ se înţelege un algoritm de complexitate polinomială (o soluţie
rezonabilă, deci) care însă nu este capabil să determine soluţia optimă a problemei ci doar una
aproximativă. Studiul complexităţii algoritmilor aproximativi se ocupă cu estimarea teoretică a
gradului de aproximare pe care soluţia algoritmului aproximativ o oferă, deci implicit studiul
eficienţei acestuia. Este evident că, atunci când soluţia aproximativă este foarte apropiată de soluţia
optimă iar timpul de obţinere al ei este unul polinomial şi nu exponenţial, este avantajoasă folosirea
în practică a unui algoritm aproximativ performant. Desigur, soluţiile oferite de algoritmii
aproximativi, chiar dacă se apropie de soluţia optimă a problemei, nu oferă însă nici o informaţie în
plus despre soluţia optimă, aceasta rămânând în continuare nedeterminată.

________________________________________________________________________________

62
Structuri de date şi algoritmi
_______________________________________________________________________________
De exemplu, în cazul Problemei Colorării Grafului, soluţia optimă cere determinarea
numărului cromatic, adică a numărului minim de culori necesare pentru colorarea nodurilor grafului
astfel încât oricare două vârfuri adiacente să fie colorate diferit.
Un exemplu de algoritm aproximativ de colorare a grafului ar fi următorul: fie c 1, c2, …, ck,
… şirul culorilor; parcurgând într-o anumită ordine mulţimea celor n vârfuri ale grafului v1, v2, …,
vn se colorează fiecare vârf cu culoarea de indice minimal care îi este permisă (ţinând cont de
culorile nodurilor adiacente lui). Parcurgerea celor n vârfuri precum şi selectarea culorii de indice
minim necesită un timp polinomial, deci acest algoritm aproximativ este eficient în practică. Ceea
ce este mai dificil este estimarea gradului de aproximare a soluţiei optime. Aceasta se face prin
calcularea mediei rapoartelor între numărul de culori obţinut cu ajutorul acestui algoritm
aproximativ şi numărul cromatic (care va trebui aflat apelând la un algoritm exponenţial, gen
backtracking). Eficienţa acestui algoritm aproximativ este dată de măsura în care valoarea medie a
raportului se apropie de valoarea 1.
 
Probleme insolvabile algoritmic

Chiar dacă nu se cunoaşte încă răspunsul la Marea Întrebare, se cunoaşte în schimb


răspunsul la o altă întrebare importantă:
există probleme ce nu pot fi soluţionate cu ajutorul calculatorului ?
Răspunsul este afirmativ şi el a fost pentru prima dată afirmat în 1936 chiar de Alan Turing,
“părintele” calculatoarelor actuale. Eforturile principale ale lui Turing la acea vreme erau
concentrate tocmai pentru căutarea răspunsului la această întrebare. Lipsa de rigurozitate a
noţiunilor: problemă, soluţie şi calculator l-au obligat pe Turing să imagineze modelul matematic
riguros al maşinii Turing şi să formuleze Teza sa ce permite echivalarea maşinii Turing cu noţiunea
vagă de algoritm, noţiune care ţine locul noţiunii şi mai vagi de soluţie generală a unei probleme.
Enunţul simplificat al Problemei Stopului care l-a condus pe Turing la răspunsul întrebării
există probleme insolvabile algoritmic? în mod afirmativ (printr-un exemplu concret de problemă)
este următorul:
Există vreo metodă generală şi finită care să decidă dacă un algoritm (sau o procedură automată)
oferă răspuns în timp finit pentru un set de date de intrare oarecare ?
Ea este foarte asemănătoare cu o altă problemă insolvabilă algoritmic, Problema a zecea a
lui Hilbert:
există o metodă generală care să decidă în timp finit dacă o ecuaţie diofantică oarecare are sau nu
soluţie în numere întregi ?
Ideea cheie care stă la baza demonstraţiei riguroase a insolvabilităţii acestor probleme nu
este nouă, ci ea a apărut pentru întâia oară în demonstraţia teoremelor de incompletitudine ale
matematicii ale lui Gödel de la începutul anilor ’30. Această idee este inspirată din faimosul
paradox antic al mincinosului. Astfel, propoziţiile de genul “Eu sunt mincinos” sau “Eu spun în
fiecare moment o minciună” au un conţinut paradoxal. În analizarea conţinutului logic al acestor
propoziţii ce din punct de vedere semantic sunt de fapt nişte negaţii se ajunge rapid la paradoxul
semantic de negare a negaţiei şi, în consecinţă, nu li se poate determina valoare logică de adevărat
sau fals. Adică, este inacceptabil ca ele să fie adevărate, şi este inacceptabil ca ele să fie false!
Godel şi Turing au arătat în mod asemănător că presupunând, prin reducere la absurd, că
matematica este completă respectiv că problema stopului este solvabilă se va ajunge inevitabil la
propoziţii paradoxale din punct de vedere matematic riguros al conţinutului logic.
 

________________________________________________________________________________

63
Structuri de date şi algoritmi
_______________________________________________________________________________
Complexitatea algoritmilor, calculatorul şi viitorul
De la Gödel şi Turing încoace (anii ’30) au apărut o mulţime de alte probleme insolvabile
algoritmic (fiecare surprinzând oarecum prin simplicitatea enunţului) şi despre care putem spune că
vor rămâne să fie rezolvate exclusiv prin puterea minţii umane (fără ajutorul maşinilor automate
actuale), cel puţin câtă vreme va fi păstrat modelul abstract ce stă la baza calculatoarelor actuale.
Momentan aceste probleme au o dificultate ce depăşeşte puterea noastră şi nu li se pot întrezării
soluţii. Vor fi aceste probleme soluţionate vreodată ? Nu putem afirma, nici că vor fi, nici că nu vor
putea fi soluţionate, dar că sunt cele mai complexe şi mai dificile probleme ce au apărut în
matematică şi informatică putem afirma cu siguranţă.
 

________________________________________________________________________________

64
Structuri de date şi algoritmi
_______________________________________________________________________________
I.3. PROGRAMARE ÎN TURBO-PASCAL
 
 Pentru a putea utiliza algoritmii la rezolvarea problemelor, este necesar ca ei să fie
"implementaţi" într-un limbaj "înţeles" de calculator, iar acesta să execute acţiunile cerute de
algoritm. Iar pentru a învăţa corect programarea este necesar să se înţeleagă întreaga activitate de
programare. In plus, testarea algoritmilor implementaţi se poate face doar prin traducerea acestora
într-un limbaj de programare. În acest scop vom prezenta un minim de cunoştinţe despre limbajul
Pascal, suficiente însă pentru a scrie programe pentru a rezolva o varietate mare de probleme de
matematică (Boian şi Frentiu [1992]).
   
1.3.1. Mediul de programare Turbo Pascal
  Mediul de programare Turbo Pascal este un ansamblu de programe complex, conceput
pentru programatorii care utilizează limbajul Pascal. Mediul Turbo Pascal permite editarea
(introducerea şi corectarea) programelor Pascal, compilarea programului sursă introdus, execuţia
acestuia dacă el este corect sintactic şi lexical. Programarea în limbajul Pascal este mult uşurată de
acest mediu de programare interactiv cu ajutorul căruia programatorul poate corecta uşor erorile de
compilare semnalate direct pe ecran în programul sursă unde a fost indicată eroarea (prima eroare
dacă sunt mai multe). După corectarea erorii programul se poate compila din nou până când se
ajunge la o variantă corectă. În continuare se poate testa programul introdus prin execuţia acestuia.
Toate aceste etape prezentate anterior (editare, compilare, execuţie, precum şi altele) se pot
parcurge uşor din acest mediu, fără a mai apela la alte programe utilitare. Odată apelat mediul
Turbo Pascal putem efectua toate operaţiile necesare parcurgerii etapelor programului Pascal fără a
părăsi mediul. Părăsirea mediului se va face doar la sfârşit când programul este terminat.
 Mediul de programare oferă o interfaţă prietenoasă printr-un sistem de meniuri şi opţiuni ce
permit editarea fişierelor sursă Pascal, compilarea acestora, execuţia programelor obţinute,
depanarea lor şi multe altele.
 Pornirea mediului de programare Turbo Pascal se face prin comanda Turbo<Enter> într-un
mediu MS-DOS, sau prin accesarea corespunzătoare a unei icoane care corespunde mediului de
programare ân cazul mediului Windows. Lansarea în execuţie a mediului de programare Turbo
Pascal este semnalată de deschiderea unei fereastre de editare care permite editarea textelor sursă
Pascal (programele scrise în limbajul Pascal). În partea de superioară a ferestrei este afişat meniul
principal iar în partea inferioară sunt afişate câteva comenzi importante ce se pot efectua direct fără
a apela sistemul de meniuri.
 Pentru a accesa un fişier ce conţine cod sursă Pascal vom putea tasta direct F3 sau putem
accesa fişierul prin intermediul meniului principal (F10 - submeniul File opţiunea Open), după care
se va tasta numele fişierului (de exemplu Test<Enter>).
După aceasta, fişierul cu numele specificat şi extensia implicită PAS va fi deschis, iar
fereastra de editare este pregătită pentru editarea programului (exemplu: Test.Pas).
Programul se va introduce rând cu rând, fiecare rând fiind terminat cu <Enter>. În editarea
programului se pot utiliza comenzile de editare descrise în anexa A. După ce programul a fost
introdus se poate compila pentru a depista eventualele erori sintactice şi lexicale. Aceasta se poate

________________________________________________________________________________

65
Structuri de date şi algoritmi
_______________________________________________________________________________
realiza fie direct prin tastarea combinaţiei de taste Alt+F9 sau F9, fie prin meniul principal (F10 -
submeniul Compile opţiunea Compile sau Make). Dacă au fost semnalate erori, atunci acestea se
vor corecta, apoi se va relua compilarea, şi tot aşa, până când compilarea se termină cu succes.
 În continuare, se poate trece la etapa de execuţie a programului scris (editat) şi compilat.
Execuţia programului se poate realiza fie direct prin tastarea combinaţiei de taste Ctrl+F9, fie prin
meniul principal (F10 - submeniul Run opţiunea Run). Eventualele rezultate în timpul execuţiei
programului sunt afişate într-o nouă fereastră. La terminarea execuţiei programului se va redeschide
fereastra de editare. Dacă sunt depistate erori de execuţie atunci programul se corectează la nivelul
codului sursă şi se va relua compilarea şi execuţia programului.
 Dacă se doreşte terminarea sesiunii de lucru cu mediul Turbo Pascal atunci se părăseşte
acest mediu direct prin combinaţia Alt+X sau prin meniul principal (F10, File şi Exit). Dacă fişierul
nu a fost salvat niciodată (comanda Save = F2 din submeniul File) atunci se cere confirmarea
salvării dacă programatorul doreşte acest lucru (prin Yes).
 Mediul Turbo Pascal împarte ecranul în trei zone astfel: 

Fig. 4. Structura generală a ferestrei de editare a mediului de programare Turbo


Pascal.

Fereastra de editare este utilizată pentru introducerea şi corectarea programelor scrise în


limbajul Pascal. Din această fereastră se poate accesa meniul principal descris mai jos prin apăsarea
tastei F10, apoi prin navigare cu ajutorul tastelor reprezentând săgeţile stânga sau dreapta alegem

________________________________________________________________________________

66
Structuri de date şi algoritmi
_______________________________________________________________________________
opţiunea dorită. Comenzile de editare sunt descrise şi sunt aceleaşi ca şi cele ale editorului WS (vezi
anexa A ). Se poate selecta şi direct o anumită opţiune din meniul principal prin tastarea simultană
Alt+x, unde x reprezintă litera caracteristică din opţiunea aleasă descrisă mai sus cu caracter
îngroşat ( File = F, Edit = E, ... Help = H ) .

 Fig. 5. Structura meniului principal a ferestrei de editare a mediului de programare


Turbo Pascal.

În partea de jos a ecranului se află afişată linia de memento care arată cu ce taste
(combinaţie de taste) putem realiza un anumit lucru direct fără a mai trece prin meniul principal
astfel:
 

________________________________________________________________________________

67
Structuri de date şi algoritmi
_______________________________________________________________________________

Fig. 6. Structura generală a meniului memento a ferestrei de editare a mediului de


programare Turbo Pascal.

În continuare vom descrie posibilităţile (facilităţile) oferite de mediul Turbo Pascal prin
meniul principal:
  
- File: permite încărcarea şi salvarea fişierelor text ce conţin programe sursă Pascal (New,
Open=F3, Save=F2, ...), schimbarea directorului curent (Change Dir), tipărirea la imprimantă a
programului curent (Print), executarea de comenzi Dos (Dos Shell) şi terminarea sesiunii şi
părăsirea mediului (Exit=Alt+X).
 - Edit: permite introducerea şi corectarea fişierului Pascal, recuperarea textelor şterse
(Undo=Alt+BkSp), mutarea unui bloc de text marcat într-o zonă tampon numită clipboard
(Cut=Shift + Del), copierea unui bloc în zona tampon (Copy=Ctrl+Ins), inserarea în poziţia
cursorului a unui bloc din zona tampon (Paste=Shift+Ins), ştergerea unui bloc (Clear=Ctrl+Del) şi
vizualizarea zonei tampon (Show clipboard). Pentru a marca un bloc (o secvenţă de program sursă)
se utilizează simultan tasta Shift şi săgeţile direcţionale de pe tastatură.
  - Search: realizează funcţiile de căutare a unui şir de caractere (Find), de înlocuire
(Replace), precum şi repetarea ultimei comenzi de căutare/înlocuire (Search again), poziţionarea
directă pe o linie din programul sursă precizată prin numărul ei (Go to line number), şi altele.

________________________________________________________________________________

68
Structuri de date şi algoritmi
_______________________________________________________________________________
- Run: permite execuţia unui program astfel:
 - execuţia întregului program (Run=Ctrl+F9);
 - execuţia programului până în poziţia cursorului din programul sursă (Go to
cursor=F4);
 - execuţia pas cu pas iar a subprogramelor într-un pas (Step over=F8);
 - execuţia pas cu pas, inclusiv a subprogramelor (Trace into=F7);
  
- Compile: realizarea compilarea unui program (Compile =Alt+F9), iar destinaţia
programului obiect rezultat poate fi în memoria internă sau pe disc (Destination Memory/Disk),
compilarea automată a uniturilor dacă acestea au fost modificate (Make=F9), sau necondiţionată
(Build) şi altele;
  - Debug: permite depanarea programelor prin vizualizarea punctelor de întrerupere
(Breakpoints), afişarea ferestrei de vizualizare a valorilor variabilelor (Watch), afişarea ferestrei de
rezultate (User screen=Alt+F5), vizualizarea şi modificarea valorilor variabilelor
(Evaluate/modify=Ctrl+F4), adaugă expresii în fereastra de vizualizare (Add watch = Ctrl+F7),
marcarea punctelor de întrerupere (Add breakpoint şi altele.
- Tools: deschide fereastră de mesaje (Messages) şi altele.
 - Option: setează opţiuni de compilare (Compiler), directoarele de lucru utile (Directories),
preferinţe de editare, mouse, startare şi culori (Environment), salvare configuraţie (Save) într-un
fişier (Save a.).
- Window: permite operaţii asupra ferestrelor de lucru astfel:
 - Tile - toate ferestrele se văd simultan prin împărţirea egală a ecranului,
 - Cascade - ferestrele sunt aranjate una peste cealaltă;
 - Size/Move=Ctr+F5 - permite deplasarea şi redimensionarea ferestrelor utilizând
săgeţile şi tasta shift;
 - Zoom=F5 - măreşte fereastra activă;
 - Next=F6 - afişează (activează) următoarea fereastră;
 - Close=Alt+F3 - închide fereastra activă;
 - List = Alt+0 - tipăreşte lista ferestrelor de lucru.
  - Help: permite îndrumarea operatorului prin submeniuri şi opţiuni care conduc la
explicaţii şi exemple.
  

1.3.1.1. Programe simple Pascal

________________________________________________________________________________

69
Structuri de date şi algoritmi
_______________________________________________________________________________
  
Limbajul Pascal, definit de Wirth, a apărut în anul 1970, şi s-a răspândit rapid datorită
calităţilor sale. Wirth a urmărit să prezinte conceptele de bază din domeniul programării, în scopul
însuşirii acestei activităţi. Deşi iniţial limbajul a fost gândit în scop universitar, el este folosit astăzi
la programarea celor mai diverse probleme, pe toate sistemele de calcul existente (Cristea et al.
[1992]).
 Ca orice limbaj de programare limbajul Pascal este construit folosind un alfabet ce conţine
caracterele întâlnite în scrierea obişnuită şi cea matematică:
 - literele (mari şi mici) ale alfabetului latin:
 A B C D E F G H I J K L M N O P Q R S T U V W X Y Z
 a b c d e f g h i j k l m n o p q r s t u v w x y z
 - cifrele zecimale: 0 1 2 3 4 5 6 7 8 9
 - caractere speciale: + - * / . , ; : ( ) [ ] { } etc.
 Cu ajutorul lor se vor construi toate instrucţiunile limbajului.
 Menţionăm că literele mici se consideră identice cu literele mari, exceptând folosirea lor în
valori de tip string (texte). De asemenea, cel puţin în constante de tip string, pe lângă caracterele
prezentate mai sus, sunt permise toate caracterele existente la tastatura calculatorului. De fapt multe
particularităţi ale limbajului depind de implementarea lui pe calculatorul folosit.
 În prezentarea care urmează vom folosi notaţia BNF (Backus-Naur Form) pentru a defini
elementele limbajului Pascal. De exemplu prin notaţia:
  s ::= e1 | e2 | { e3 [, e4] + } e5
  vom înţelege că prin definiţie elementul s este e1, sau e2, sau construcţia { e3 [, e4]+} e5,
care reuneşte expresiile:
 e5
 e3 + e5
 e3 , e4 + e5
 e3 , e4 + e3 + e5
 etc.
  Prin scrierea unei construcţii între acoladele { } se indică faptul că acea construcţie poate
să se repete de ori câte ori (inclusiv de zero ori). Parantezele drepte îngroşate sunt folosite pentru
scrierea construcţiilor opţionale; ceea ce se află închis între aceste paranteze poate lipsi.
 Se observă că în sintaxa de mai sus se folosesc metasimbolurile [, ], { şi }. A nu se
confunda cu caracterele [, ], { şi } permise şi folosite în anumite construcţii Pascal.
 Pentru alte metasimboluri vom alege anumite cuvinte, scrise cu litere mici între caracterele
< şi > şi scrise cursiv. 
Ca în toate limbajele de programare şi în Pascal se folosesc frecvent identificatorii. Prin
identificator, notat în definiţiile sintactice care urmează prin <id>, se înţelege o secvenţă de litere
(mari sau mici) şi cifre, primul caracter fiind obligatoriu o literă. De asemenea, în construcţia

________________________________________________________________________________

70
Structuri de date şi algoritmi
_______________________________________________________________________________
identificatorilor este permis şi caracterul '_'. Acesta se recomandă să se folosească în scrierea
identificatorilor compuşi din două cuvinte unite prin acest caracter.
 O parte dintre identificatori au un rol special în definirea instrucţiunilor limbajului Pascal.
Aceştia se numesc cuvinte rezervate şi sunt:
  
AND DOWNTO IF OR THEN ARRAY ELSE
IN PACKED TO BEGIN END LABEL PROCEDURE
TYPE CASE FILE MOD PROGRAM UNTIL CONST
FOR NIL RECORD VAR DIV FUNCTION NOT
REPEAT WHILE DO GOTO OF SET WITH
 
Cuvintele rezervate nu pot fi folosite în program în alt scop decât cel fixat, acela de a defini
sintaxa instrucţiunilor Pascal.
 Pentru a sublinia care sunt cuvintele rezervate, în definiţiile care urmează cât şi în primele
programe date ca exemple, vom scrie cuvintele rezervate cu litere mari. De asemenea, aceste
cuvinte vor fi scrise îngroşat, pentru a le diferenţia clar de celelalte construcţii datorate
programatorului.

1.3.1.2. Structura unui program Pascal

  Un program Pascal constă dintr-un titlu, o parte de declaraţii şi instrucţiunile care formează
programul principal. El are următoarea structură generală:

  <program> ::= <antet_program> ; <bloc> .


 unde
 <antet_program> ::= PROGRAM <id> [ ( <lista_id> ) ]
 iar
 <bloc> ::= <lista_decl> ; <ins_compusă>
  
Construcţiile <lista_decl> şi <ins_compusă> vor fi complet înţelese puţin mai târziu, după
ce se vor prezenta declaraţiile şi instrucţiunile limbajului Pascal. În general prin lista de elemente
<lista_e> se înţelege un simplu element <e>, sau o succesiune de elemente
  <lista_e> ::= <e> { s <e> }
  unde separatorul s este virgula sau uneori caracterul ';', caz în care vom menţiona acest
lucru.
 În definiţia blocului elementul <decl> este metasimbolul folosit pentru declaraţia Pascal

________________________________________________________________________________

71
Structuri de date şi algoritmi
_______________________________________________________________________________
care va fi definită în secţiunea 4.2.5, iar <ins> este metasimbolul folosit pentru a nota o instrucţiune
Pascal şi va fi definit în secţiunea 4.2.6.
 Aşa cum se va vedea mai târziu, în definiţia declaraţiilor de procedură se foloseşte
metasimbolul <bloc>. Deci un bloc care conţine o procedură conţine un alt bloc care, la rândul lui,
poate conţine alt bloc.
 Oriunde în textul programului pot fi incluse comentarii. Acestea sunt folosite de către
utilizatori în scopul îmbunătăţirii clarităţii programului şi a explicării semnificaţiei unor notaţii sau
părţi de program. Ele nu sunt luate în seamă de calculator, singurul lor scop fiind acela de a oferi
programatorului posibilitatea de a insera în program explicaţii utile programatorului. Un comentariu
este orice text închis între acolade
  <comentariu> ::= { text } ˝ (* text *)
  Prezentăm în continuare un exemplu de program Pascal:
Exemplul I.20
 PROGRAM RADICALI; { Programul 1 }
{ Exemplu de program Pascal }
 VAR x, r: REAL; { Declaraţii }
 BEGIN { Instrucţiunea compusă }
 REPEAT
  READLN(x);
  r := SQRT(x); { Functia SQRT este o funcţie }
  { standard pentru extragerea radicalului }
  WRITELN('Radical din ', x, ' este ', r)
 UNTIL x = 0
 END.
  
     1.3.3.3. Constante şi variabile Pascal

  Datele şi rezultatele dintr-un program sunt reprezentate prin constante şi variabile.


Constantele se caracterizează prin faptul că nu-şi modifică valoarea în timpul execuţiei unui
program. Orice constantă este precizată prin sintaxa ei şi are o valoare bine definită.
 Constantele pot fi: numerice, şir de caractere şi booleene. La rândul lor constantele
numerice pot fi întregi sau reale.
  Constantele întregi sunt cele care reprezintă numerele întregi din matematică şi au sintaxa
obişnuită de scriere a numerelor. De exemplu, 15, 1989, -314 sunt constante întregi.
  Constantele reale sunt cele care reprezintă numerele reale. Scrierea unui număr real poate fi
în forma normală şi în forma exponenţială.
 În forma normală este prezent atât punctul zecimal "." cât şi partea întreagă şi partea

________________________________________________________________________________

72
Structuri de date şi algoritmi
_______________________________________________________________________________
fracţionară a numărului real. De exemplu 3.14159 1.72 -5.749 103.0 0.23  sunt constante reale
corecte. Notaţiile  .23A 103.+ 37.5. -123.+44  sunt greşite.
 În forma exponenţială, un număr întreg sau un număr real în forma normală este urmat de
litera E sau e şi de un număr întreg numit exponent. Valoarea numărului real scris în această formă
este egală cu numărul scris în faţa literei E înmulţită cu 10 la puterea egală cu exponentul scris după
litera E. De exemplu:
 12345E-6 este egal cu 0.012345 ;
 3.123456E5 este egal cu 312345.6 .
  Constanta şir de caractere este o secvenţă de caractere (un text) închisă între apostrofuri.
Valoarea constantei este chiar textul închis între apostrofuri. Dacă este necesar ca în text să apară şi
apostroful el trebuie dublat. De exemplu:
 '22 Decembrie 1989'
 'L''Hospital'
 '' { şirul vid }
 În cazul în care un singur caracter este închis între apostrofuri avem o constantă de tip
caracter. O constantă şir de caractere este considerată ca fiind rezultatul concatenării mai multor
constante de tip caracter.
 Deşi s-a afirmat la început că literele mari se consideră identice cu cele mici, singura
excepţie este folosirea lor în constanta şir de caractere. Într-o asemenea constantă fiecare literă mare
diferă de litera mică corespunzătoare.
  Constanta booleană reprezintă o valoare logică şi se reprezintă prin identificatorii TRUE
pentru valoarea logică "adevărat", respectiv FALSE pentru valoarea logică "fals".
  Conceptul de variabilă a fost deja prezentat în secţiunea 1.2. Ea corespunde unei date care
îşi  poate schimba valoarea în timpul execuţiei programului. Variabila are un nume şi poate primi o
valoare dintr-un domeniu bine precizat de valori. In Pascal numele unei variabile este un
identificator. In limbajul Pascal fiecare variabilă are un tip care trebuie să fie declarat în program
înainte ca variabila să fie folosită.
   
     1.3.3.4. Tipuri de date
  
Prin tip de date se înţelege o mulţime de valori şi o mulţime de operaţii ce pot fi efectuate cu
valori de acest tip. Mulţimea valorilor se mai numeşte şi domeniul tipului.
 Există mai multe posibilităţi de definire a unui tip de date, unele dintre ele vor fi prezentate
mai târziu. Menţionăm că unele tipuri de date sunt predefinite, iar altele sunt definite de către
programator în timpul scrierii programului. După elementele care formează domeniul tipului
deosebim tipuri de date simple, respectiv compuse cu ajutorul altor tipuri. De asemenea, putem avea
ca domeniu mulţimea adreselor memoriei calculatorului. Deci un tip este:
  <tip> ::= <tip_simplu> | <tip_structurat> | <tip_referinţă>
  Tipul de date <tip_simplu> se defineşte în continuare, iar alte două tipuri vor fi definite în

________________________________________________________________________________

73
Structuri de date şi algoritmi
_______________________________________________________________________________
secţiunile următoare (ARRAY şi RECORD).
 Tipurile simple de date conţin tipurile numerice INTEGER şi REAL, tipul BOOLEAN,
tipul CHAR, tipul enumerare şi tipul subdomeniu. Avem:
  <tip_simplu> ::= <tip_real> | <tip_ordinal>
 unde
 <tip_ordinal> ::= <tip_întreg> | <tip_boolean> | <tip_caracter> |  <tip_enumerare> |
<tip_subdomeniu>
  
Tipurile întreg, real, boolean şi caracter sunt predefinite şi sunt marcate prin cuvintele
INTEGER, REAL, BOOLEAN, respectiv CHAR. Deci
 <tip_real> ::= REAL 
<tip_intreg> ::= INTEGER
 <tip_boolean> ::= BOOLEAN
 <tip_caracter> ::= CHAR
  
Tipurile INTEGER şi REAL se referă la mulţimile de numere întregi şi reale, dar mulţimea
valorilor fiecărui tip este finită şi depinde de calculatorul folosit.
 Deşi funcţiile Pascal vor fi indicate în secţiunea următoare prezentăm aici câteva funcţii
definite asupra valorilor de tip ordinal sau de tip caracter.
 Pentru fiecare tip ordinal, deci şi pentru tipul caracter, mulţimea valorilor este finită şi
ordonată. Pentru obţinerea rangului elementului x în această mulţime ordonată se poate folosi
funcţia ORD(x). Pentru primul element p din această mulţime avem ORD(p) = 0. Excepţie este
ORD(i) pentru i întreg, când ORD(i)=i. Funcţia SUCC furnizează succesorul unui element în
această mulţime, deci SUCC(x) reprezintă succesorul elementului x în domeniul valorilor şi este
nedefinit pentru ultimul element din domeniu. Prin PRED(x) se notează predecesorul elementului x
în domeniul tipului şi este nedefinit pentru primul element din domeniu.
 Din definiţiile de mai sus se deduc uşor următoarele proprietăţi:

 ORD( SUCC(x) ) = ORD(x) + 1;


 ORD( PRED(x) ) = ORD(x) - 1.
 
SUCC(x) şi PRED(x) trebuie să fie definite.
     1.3.3.4. Tipul întreg
  
 Domeniul tipului întreg este submulţimea numerelor întregi cuprinse în intervalul [-
MAXINT-1, MAXINT], unde MAXINT este o constantă întreagă predefinită a cărei valoare
depinde de calculatorul folosit şi este determinată de mărimea locaţiei pe care se reprezintă un

________________________________________________________________________________

74
Structuri de date şi algoritmi
_______________________________________________________________________________
număr întreg în calculatorul respectiv (de exemplu MAXINT = 32767).
  o
Semnificaţia lui x o y
Operaţia o Semnificaţia lui x o y

+ adunare

- scădere

* înmulţire

DIV Împărţire întreagă

/ Împărţire reală

MOD Restul împărţirii întregi a lui x la y

Tab.1.3. Operaţiile tipului INTEGER


 
Operaţiile definite între valori de tip întreg sunt +, -, *, DIV, MOD şi /, iar semnificaţia lor
se dă în Tabelul 1.3. Uneori, - se notează şi o operaţie unară. Prin -x se înţelege opusul lui x faţă de
operaţia de adunare.
   
     1.3.3.5. Tipul real

  Tipul real reprezintă o submulţime finită de numere reale aflate în intervalul [-vmax,
vmax], submulţime care depinde de modul de reprezentare a numerelor reale în calculatorul folosit.
Deci vmax este o constantă reală care diferă de la un calculator la altul, dar nu e o constantă
predefinită ca MAXINT în cazul tipului INTEGER.
 Operaţiile definite între valori de tip real sunt adunarea, scăderea, înmulţirea şi împărţirea,
notate prin +, -, *, respectiv / . De remarcat că aceste operaţii sunt definite şi când un operand este
întreg iar celălalt real, rezultatul fiind real, datorită conversiei implicite a tipului întreg la real.
 Menţionăm că în reprezentarea oricărui număr real în calculator se reţin un număr finit de
cifre semnificative. Din această cauză rezultatul unei operaţii cu numere reale este aproximativ.
Pentru a înţelege exact aceste afirmaţii este necesară cunoaşterea reprezentării numerelor în
calculator.
   
     1.3.3.6. Tipul boolean
  Domeniul tipului boolean constă din mulţimea valorilor logice "fals" şi "adevărat", marcate
prin constantele FALSE, respectiv TRUE. Ordinea este FALSE < TRUE.

________________________________________________________________________________

75
Structuri de date şi algoritmi
_______________________________________________________________________________
 Operaţiile logice binare sunt marcate prin AND (conjuncţia logică) şi OR (disjuncţia
logică), iar negaţia (operaţie logică unară) prin NOT. Operatorii relaţionali au în acest caz
semnificaţii logice. Deci prin =, <>, <=, >=, sunt notate echivalenţa, neechivalenţa (SAU exclusiv),
implicaţia, respectiv implicaţia inversă.
   1.3.3.7. Tipul caracter
  Tipul caracter este marcat prin identificatorul CHAR şi reprezintă mulţimea caracterelor cu
care lucrează calculatorul respectiv. Principial, ordinea acestor caractere poate să difere de la
calculator la calculator. Însă indiferent de calculator, limbajul Pascal cere ca mulţimea cifrelor
zecimale să fie codificată compact (diferenţa codurilor a două cifre consecutive să fie 1), iar
submulţimile literelor mari şi a literelor mici să fie ordonate alfabetic.
 Nu există operaţii definite asupra valorilor de tip CHAR. Pe lângă funcţiile definite pentru
argument de orice tip ordinal, pentru tipul caracter mai întâlnim şi funcţia CHR. Pentru i întreg,
CHR(i) furnizează caracterul de rang i din domeniul tipului CHAR. Să observăm că
   ORD( CHR(i) ) = i
 şi
  CHR( ORD(c) ) = c .
   
   1.3.3.8 Tipul enumerare
  Tipul enumerare se specifică prin scrierea valorilor acestui tip între paranteze:
  <tip-enumerare> ::= ( <lista-id> )
  identificatorii din paranteze constituind valorile tipului enumerare definit. Ordinea lor este
cea dată de ordinea identificatorilor în listă. Nu este permis ca o valoare a acestui tip (deci un
identificator) să fie reutilizat în definiţia altui tip.
 Exemple de tip enumerare:
  Exemplul 1: (LUNI, MARTI, MIERCURI, JOI, VINERI, SAMBATA, DUMINICA)
  Exemplul 2: (PRIMAVARA, VARA, TOAMNA, IARNA)
  Pentru aceste exemple avem:
 PRED(TOAMNA) = VARA
 SUCC(JOI) = VINERI
 ORD(LUNI) = 0
 ORD(IARNA) = 3 .
  Menţionăm că valorile variabilelor de tip enumerare nu pot fi citite şi nici tipărite.
  
   1.3.3.9. Tipul subdomeniu
 
 Tipul subdomeniu poate fi definit din orice tip ordinal prin precizarea limitelor inferioară şi
superioară care definesc valorile subdomeniului. Tipul ordinal pentru care se defineşte subdomeniul

________________________________________________________________________________

76
Structuri de date şi algoritmi
_______________________________________________________________________________
se numeşte tip de bază al subdomeniului. Avem:
  <tip-subdomeniu> ::= constanta1 .. constanta2
  unde constanta1 şi constanta2 sunt valori ale tipului ordinal de bază care satisfac
inegalitatea
  Ord(constanta1) <= Ord(constanta2).
 
 De exemplu:
 - subdomeniul întregilor de la 1 până la 20: 1..20
 - subdomeniul zilelor lucrătoare: LUNI .. VINERI
 - caracterele cifre zecimale: '0' .. '9'.
   
   1.3.3.9. Expresii Pascal
  O expresie Pascal este formată din operanzi şi operatori. Deci o expresie Pascal are forma:
  a[1] o[1] a[2] o[2] ... a[n] o[n] a[n+1]
  unde a[i], i = 1, 2, ..., n+1, sunt operanzii expresiei, iar o[i], i = 1, 2, ..., n, sunt operatorii
expresiei. Deoarece n >= 0, pot exista expresii formate dintr-un singur operand. Acest operand
poate avea orice tip Pascal, el fiind şi tipul expresiei. În funcţie de tipurile operanzilor deosebim
expresii aritmetice, expresii relaţionale, expresii logice şi expresii ordinale.
 Întrucât în definirea expresiilor se folosesc şi funcţiile, prezentăm câteva funcţii Pascal.
  
1.3.3.9.1. Funcţii predefinite
  
Pe lângă operaţiile algebrice prezentate deja, există şi funcţii frecvent folosite în rezolvarea
unor probleme, cum ar fi extragerea rădăcinii pătrate, calculul valorii unei funcţii trigonometrice,
calculul logaritmilor etc. Pentru efectuarea acestor calcule în limbajul Pascal există funcţii
predefinite, funcţii pe care le dăm în tabelul 4.2.1.
  
1.3.3.9.2. Expresii aritmetice
  Expresiile aritmetice sunt expresii în care operanzii sunt valori numerice întregi sau reale.
Operatorii sunt cei permişi de tipurile întreg şi real; se cere ca operaţia care leagă doi operanzi să fie
definită în tipul acestor operanzi. Operanzii unei expresii aritmetice pot fi:
 - constante numerice;
 - variabile simple de tip numeric;
 - elemente de tablou de tip numeric;
 - funcţii numerice;
 - subexpresii aritmetice, adică o expresie aritmetică, eventual precedată de semnul '-',

________________________________________________________________________________

77
Structuri de date şi algoritmi
_______________________________________________________________________________
închisă între paranteze.

Notaţia Semnificaţia

abs(i) Valoarea (întreagă) absolută a întregului i

abs(x) Valoarea (reală) absolută a realului x

sqr(i) Pătratul întregului i

sqr(x) Pătratul numărului real x

int(x) Partea întreagă a numărului real x dacă x>0, respectiv –


int(abs(x)) dacă x<0

trunc(x) Întregul obţinut din numărul real x prin eliminarea părţii


fracţionare

round(x) Cel mai apropiat întreg de numărul real x

frac(x) Valoarea x - int(x), x real

exp(x) e la puterea x, pentru x întreg sau real

sin(x) sinusul lui x

cos(x) cosinusul lui x

arctan(x) arctangenta din x, unde x este masura unghiului in radiani

ln(x) logaritm natural din x, x întreg sau real > 0

sqrt(x) radical din x, x>=0

succ(i) succesorul valorii ordinale i

pred(i) predecesorul valorii ordinale

ord(e) numărul de ordine al valorii e în tipul expresiei ordinale

chr(i) caracterul de ordin i în tipul CHAR

odd(i) funcţie logică cu valoarea TRUE dacă i este impar şi


FALSE dacă i este par

Tab.1.4. Funcţii Pascal predefinite.


  
Deci o expresie aritmetică, notată <exp-a>, se poate defini astfel:
  <exp-a> ::= <term> ˝ <exp-a> + <term> ˝ <exp-a> - <term>

________________________________________________________________________________

78
Structuri de date şi algoritmi
_______________________________________________________________________________
 unde
  <term> ::= <factor> ˝ <term> * <factor> ˝ <term> / <factor> ˝ <term> DIV <factor> ˝
 <term> MOD <factor>
  iar
  <factor> ::= <constantă numerică fără semn> ˝
 <variabilă de tip numeric> ˝
 <element de tablou de tip numeric> ˝
 <funcţie de tip numeric> ˝
 ( <exp-a> ) ˝
( -<exp-a> )
  Menţionăm că prioritatea operaţiilor este cea cunoscută şi folosită în matematică. Pentru a
indica o altă ordine se vor folosi parantezele. De asemenea, menţionăm că expresiile aritmetice
definite mai sus pot fi precedate de operaţia unară -, cu semnificaţia cunoscută din matematică.
 Tipul unei expresii aritmetice este întreg dacă toţi operanzii sunt întregi şi expresia nu
conţine operatorul /. Dacă însă expresia conţine acest operator, sau dacă există cel puţin un operand
de tip real, atunci tipul expresiei este real.
 În evaluarea unei expresii aritmetice se calculează mai întâi toţi operanzii. O subexpresie
aritmetică este tot un operand. În evaluarea ei se procedează ca la o expresie aritmetică; deci dacă
toţi operanzii sunt întregi şi operaţiile sunt definite în tipul întreg atunci rezultatul este întreg.
 Astfel, valoarea expresiei 5 div 2 + 1.0 este numărul real 3.0 şi nu 3.5, întrucât primul
operand al adunării are valoarea întreagă 2 deoarece 5 div 2 este o expresie de tip întreg.
  
1.3.3.9.3.. Expresii relaţionale
  Expresia relaţională are scopul de a permite scrierea în Pascal a unor relaţii matematice
care pot fi adevărate sau false. Ea are forma
  <e1> <op.rel.> <e2>
  unde <e1> şi <e2> sunt expresii care au acelaşi tip ordinal, sau au tipul real, sau sunt
elemente de acelaşi tip în care s-a definit operaţia relaţională <op.rel.>, iar
  <op.rel.> ::= < | <= | = | > | >= | <>
  Valoarea expresiei relaţionale de mai sus este TRUE dacă între valorile expresiilor <e1> şi
<e2> are loc relaţia indicată şi este FALSE în caz contrar. De fapt aceste simboluri notează relaţiile
cunoscute din matematică.
 Atenţie însă la relaţia de egalitate între două numere reale. Din cauza unor aproximări
calculele cu numere reale nu sunt exacte. De exemplu, dacă x=i/3, expresia relaţională x = x*3
poate fi falsă întrucât, din cauza reprezentării interne calculele cu numere reale nu sunt exacte, iar
x*3 nu este o valoare întreagă ci o valoare reală foarte apropiată de i dar, totuşi, diferită de i.
 Tot ca exemplu, menţionăm că dacă A şi B sunt două mulţimi de acelaşi tip atunci expresia
A = B va avea valoarea logică TRUE în cazul când mulţimile A şi B sunt egale şi FALSE în caz

________________________________________________________________________________

79
Structuri de date şi algoritmi
_______________________________________________________________________________
contrar.
  
1.3.3.9.4.. Expresii logice
  
Expresia logică este o expresie Pascal în care operatorii pot fi operatori logici binari:
  AND pentru conjuncţia logică;
  OR pentru disjuncţia logică,
iar operanzii pot fi:
 - constante logice;
 - variabile de tip logic;
 - elemente de tablou de tip logic;
 - funcţii logice;
 - expresii relaţionale;
 - subexpresii logice, adică expresii logice închise între paranteze;
 - negaţia unui operand logic din cei de mai sus, deci NOT <operand logic>;
 - expresia x in M unde M este o mulţime şi x o variabilă având tipul de bază al mulţimii M.

În determinarea valorii unei expresii logice se evaluează mai întâi operanzii şi apoi
operaţiile logice binare în ordinea priorităţii lor: mai întâi conjuncţiile logice (operaţiile AND) şi
apoi disjuncţiile logice (operaţiile logice OR). Operaţiile logice cu aceeaşi prioritate se evaluează de
la stânga spre dreapta în ordinea scrierii lor.
 Menţionăm că este necesar ca toţi operanzii să fie definiţi, altfel este posibil ca evaluarea
unei expresii logice să se termine cu eroare. Astfel, în expresia logică   (i<=10) AND (x[i]>0)
  este posibil să ajungem la o eroare dacă x are doar 10 componente şi i are valoarea 11, întrucât
x[11] nu este definit, deci al doilea operand nu are sens. Unele implementări nu mai evaluează al
doilea operand în cazul în care primul operand este FALSE, valoarea expresiei fiind în acest caz
FALSE.
 De asemenea, menţionăm că într-o expresie logică în care apar operatorii + şi AND, ultimul
considerat mai prioritar decât primul, este posibil să se scrie expresii fără sens. Astfel, expresia  0 <
A+B AND C < D  este interpretată ca şi expresia     0 < A+(B AND C) < D   care nu are sens. De
aceea se cere ca, în astfel de cazuri, să se folosească parantezele pentru precizarea priorităţii
operaţiilor.
   

1.3.3.9.5.. Declaraţii Pascal


  

________________________________________________________________________________

80
Structuri de date şi algoritmi
_______________________________________________________________________________
Într-un program Pascal trebuie precizate toate variabilele şi denumirile simbolice înainte de
a le folosi. Spunem că o entitate este declarată atunci când ea apare într-o declaraţie Pascal, în care
se precizează tipul ei, eventual alte informaţii despre ea, utile atât programatorului cât şi
compilatorului. Deci, orice obiect pe care dorim să-l folosim într-un program Pascal trebuie mai
întâi declarat în partea de declaraţii a programului.
 După obiectele pe care le reprezintă deosebim următoarele declaraţii Pascal:
 - declaraţii de etichete;
 - declaraţii (definiri) de constante;
 - declaraţii (definiri) de tipuri;
 - declaraţii de variabile;
 - declaraţii de subprograme.
 Deci
  <decl> ::= <def-et> | <def-const> | <def-tip> | <def-var> |<def-subp>
  Deşi nu este obligatoriu să le întâlnim pe toate în aceeaşi unitate de program, atunci când
sunt prezente ele trebuie scrise respectând ordinea enumerării de mai sus. De asemenea, în limbajul
Pascal standard într-o unitate de program fiecare declaraţie poate apare cel mult odată. Limbajul
Turbo Pascal a renunţat la aceste restricţii.
 În definiţia sintactică a unui program Pascal s-a folosit metasimbolul <lista-decl>. Evident,
elementele acestei liste sunt declaraţii, separatorul folosit fiind caracterul ';' .
 Deşi există, nu vom folosi declaraţia de etichete. Programarea structurată pretinde
renunţarea la instrucţiunea GOTO, instrucţiune care cere folosirea etichetelor. Prin exemplele care
le dăm arătăm că acest lucru, de altfel demonstrat, este posibil. În consecinţă, nici nu o vom
prezenta în acest text. 

 OBS 3.: Evită să foloseşti instrucţiunea GOTO.


  
 1.3.3.9.6.. Definirea constantelor
  
Prin metasimbolul <def_const> am notat o definire de constante cu nume, care are sintaxa
 CONST <id> = <constanta> { ; <id> = <constanta> }
 unde <id> este un identificator care va nota numele constantei definite, iar constanta care urmează
va preciza valoarea constantei definite. Ea poate fi numerică, booleană, sau un şir de caractere. De
exemplu:
  CONST
  PI = 3.141592653;
  ORDIN = 5; N = 4;
  MARCA = '...'

________________________________________________________________________________

81
Structuri de date şi algoritmi
_______________________________________________________________________________
 defineşte o constantă cu numele PI şi valoarea precizată după semnul egal, alte două constante cu
numele ORDIN şi valoarea 5, respectiv cu numele N şi valoarea 4 şi constanta cu numele MARCA
cu valoarea egală cu stringul ‘ ‘, compus din trei puncte.
 Definirea unei constante este necesară atunci când ne referim la ea în mai multe părţi ale
programului, pentru a prescurta scrierea, sau pentru a permite modificarea ulterioară uşoară a
programului. Deci

  OBS. 4: Foloseşte constante cu nume în locul celor anonime.


 
  
1.3.3.9.7. Definirea tipurilor

  Declaraţia <def-tip> de definire a unor tipuri noi are sintaxa


  
TYPE <id> = <tip> { ; <id> = <tip> }
 unde <id> este un identificator ce reprezintă numele tipului definit, iar <tip> este un tip definit cu
ajutorul tipurilor deja cunoscute (predefinite, sau definite anterior de utilizator).  De exemplu, prin
declaraţia:
TYPE domindici = 1..12
se defineşte un tip cu numele domindici care este un subdomeniu al lui INTEGER, format din toate
numerele întregi cuprinse intre cele două limite menţionate, 1, respectiv 12.
  
1.3.3.9.7. Declararea variabilelor
  
Toate variabilele folosite într-un program Pascal trebuie declarate înainte de a fi folosite.
Declaraţia de variabile, notată prin metasimbolul <def-var>, are sintaxa
  VAR <lista-id> : <tip> { ; <lista-id> : <tip> }
  unde <lista-id> este o listă de identificatori, iar <tip> este un tip predefinit sau definit de
utilizator. Prin ea se declară toate variabilele din lista de identificatori <lista-id> ca având tipul
precizat după caracterul ':'.  De exemplu, prin
 VAR M, N, I, J, K: INTEGER;
 X, Y, Z: REAL
se declară cinci variabile de tip întreg, având numele M, N, I, J şi K şi trei variabile de tip real cu
numele X, Y, şi Z. Prin declaraţia
 VAR A: ARRAY[1..9] OF REAL
se declară un vector A cu nouă componente. Tipul variabilei A, cel de "vector cu 9 componente
reale", este un tip anonim şi nu mai poate fi folosit în altă parte a programului Pascal. Chiar şi în

________________________________________________________________________________

82
Structuri de date şi algoritmi
_______________________________________________________________________________
aceeaşi declaraţie de variabile folosirea aceleaşi construcţii pentru a defini variabila B tot "vector cu
9 componente reale" se consideră diferită de prima. De exemplu prin declaraţia
  VAR A: ARRAY[1..9] OF REAL;
  B: ARRAY[1..9] OF REAL
 s-au declarat doi vectori, dar care se consideră că nu au acelaşi tip, spre deosebire de declaraţia
  VAR C, D : ARRAY[1..9] OF REAL
prin care s-au definit două variabile de acelaşi tip.
 Astfel se impune o regulă importantă în programare:
OBS. 1.5: Foloseşte tipuri de date cu nume în locul celor anonime.
  Respectarea acestei reguli conduce, pe lângă cele arătate mai sus, la creşterea clarităţii
textului şi a posibilităţii de modificare a textului sursă (schimbarea structurii marcată prin acest
nume se poate face într-un singur loc, cel al definirii ei).
 Variabilele şi celelalte elemente declarate într-o procedură sunt locale în această procedură,
deci pot fi folosite în orice instrucţiune a procedurii, dar nu şi în afara ei. De asemenea, ele pot fi
folosite în orice altă procedură declarată în interiorul acestei proceduri, ele fiind variabile globale
pentru acestea, aşa cum se va arăta în secţiunea 4.3.3.
   
1.3.3.9.8. Instrucţiuni Pascal
  Există mai multe instrucţiuni Pascal, toate notate prin metasimbolul <ins>. Există
instrucţiuni Pascal simple în interiorul cărora nu se află alte instrucţiuni şi instrucţiuni structurate,
compuse din alte instrucţiuni. Deci
  <ins> ::= <ins_simple> | <ins_structurate>
  unde
  <ins_simple> ::= <ins_atribuire> | <ins_vidă> | <apel_procedură>
  iar
  <ins_structurate> ::= <ins_compusă> | <ins_condiţionale> | <ins_iterative>
  Aceste instrucţiuni vor fi prezentate în continuare.
   
1.3.3.9.9. Instrucţiunea de atribuire
  Instrucţiunea de atribuire are scopul de a atribui valori unor variabile. Ea are sintaxa
  <ins_atribuire> ::= <variabila> := <expresie>
unde <variabila> şi <expresie> sunt de acelaşi tip, exceptând câteva cazuri menţionate mai jos.
Tipurile tv şi te ale elementelor <variabila> şi <expresie> pot fi întreg, real, boolean, enumerare sau
orice tip structurat cunoscut în programul în care apare instrucţiunea, cu excepţia tipului fişier.
 Instrucţiunea cere mai întâi evaluarea expresiei din partea dreaptă a semnului de atribuire
":=". Dacă tv şi te sunt identice atunci valoarea expresiei este atribuită variabilei din stânga
semnului de atribuire. Dacă aceste tipuri diferă atunci se semnalează eroare, exceptând patru cazuri

________________________________________________________________________________

83
Structuri de date şi algoritmi
_______________________________________________________________________________
care vor fi prezentate în continuare:
 1) Astfel, dacă te este INTEGER iar tv este REAL atunci se converteşte valoarea întreagă a
expresiei într-o valoare reală care se atribuie variabilei.
 2) Dacă tv şi te sunt tipuri enumerare sau subdomeniu cu acelaşi tip de bază şi dacă valoarea
expresiei aparţine tipului tv, această valoare se atribuie variabilei.
 3) Dacă tv şi te sunt mulţimi atunci este posibil ca unul sau ambele tipuri să fie subdomenii
ale aceluiaşi tip ordinal. Atribuirea este permisă dacă valorile din mulţimea rezultat sunt incluse în
tipul de bază al variabilei din stânga semnului de atribuire.
 4) A patra excepţie se referă la tipul STRING; tv şi te putând fi tipuri STRING diferite.
Atribuirea este corectă dacă valoarea ei se poate atribui variabilei din stânga atribuirii.
  Sintactic, în partea stângă a semnului de atribuire, <variabila> poate fi o variabilă simplă
sau o componentă a unui tablou sau alt tip de dată structurată. De asemenea, ea poate fi un tablou
atunci când şi în partea dreaptă este un tablou de acelaşi tip. În acest caz prin A := B, se înţelege o
scriere condensată a atribuirilor A[i]:=B[i] pentru toate valorile indicelui i aflate în tipul de index
folosit în definirea tablourilor A şi B.
 În general, este posibilă atribuirea A := B când şi A şi B sunt variabile de acelaşi tip. În
toate cazurile se cere ca partea dreaptă a atribuirii să aibă o valoare anterioară, altfel vom spune că
variabila B este neiniţializată, iar atribuirea va conduce la erori logice în execuţia programului.
   
1.3.3.9.10. Instrucţiunea compusă şi instrucţiunea vidă
  
În definirea unor instrucţiuni structurate sau a construcţiei <bloc> din secţiunea 2.1.2, se
cere folosirea unei singure instrucţiuni, aşa cum se va vedea în continuare. Pentru a putea include în
aceste locuri grupuri de instrucţiuni, în Pascal este posibil să definim o succesiune de instrucţiuni ca
o singură entitate. Această entitate se numeşte instrucţiune compusă şi are sintaxa
  BEGIN
  <ins>;
  {<ins> }
 END
  În limbajul Pascal caracterul ';' separă două instrucţiuni; el nu are rolul de a marca sfârşitul
acestora. Adesea însă întâlnim caracterul ';' în faţa cuvântului END. În acest caz se consideră că
între ';' şi END se află o instrucţiune vidă:
  <ins-vidă> ::=
  Instrucţiunea vidă are efect nul şi în cazul menţionat mai sus ea nu este necesară. Este
posibil să întâlnim situaţii în care avem nevoie de această instrucţiune. De exemplu, dacă la
îndeplinirea unei condiţii dorim să mergem la sfârşitul programului fără a mai executa nimic, vom
face un salt la o instrucţiune vidă, ultima instrucţiune din programul respectiv.
 Subliniem însă că este interzisă folosirea instrucţiunii vide, deci şi a caracterului ';' în faţa
cuvântului ELSE.

________________________________________________________________________________

84
Structuri de date şi algoritmi
_______________________________________________________________________________
  
1.3.3.9.11. Citirea şi scrierea datelor
 
 Pentru transferul datelor între memoria internă şi suportul extern de date se folosesc
procedurile READ şi WRITE. Deşi procedurile vor fi prezentate mai târziu, precizăm semnificaţia
acestor două proceduri pentru a putea da în continuare exemple complete de programe Pascal.
 Procedura READ se foloseşte pentru transferul datelor în memoria internă. Sintaxa apelului
acestei proceduri este:
READ ( <lista-var> )
 unde <lista-var> este o listă de variabile care pot avea unul din tipurile întreg, real, sau caracter.
 Efectul acestui apel este de atribuire a unei valori fiecărei variabile prezente în lista de
variabile. Valorile se iau de pe suportul extern începând din punctul în care s-a ajuns după citirea
anterioară. Valorile sunt astfel luate încât să corespundă tipului variabilei corespunzătoare. Dacă
acest lucru nu este posibil atunci se va semnala o eroare în execuţie.
 Ca exemplu, dacă X este o variabilă de tip întreg iar U şi V sunt variabile de tip caracter,
instrucţiunea
  READ(X, U, V)
  cere atribuirea unei valori întregi variabilei X şi a câte o valoare caracter variabilelor U şi
V. Dacă pe suportul extern se află   125 A atunci X va primi valoarea 125, variabila U va primi
valoarea ' ' (caracterul blanc), iar V va primi valoarea 'A'. Dacă însă suportul extern conţine 12.4 A
execuţia se va termina cu eroare întrucât valoarea 12.4 nu are tipul întreg.
Tot cu eroare se va termina execuţia când pe suportul extern avem TEXT 25  întrucât prima
valoare întâlnită nu este un număr întreg.
 Procedura WRITE asigură transferul informaţiei din memoria internă pe suportul extern.
Apelul procedurii are forma
WRITE ( <lista-write> )
unde <lista-write> este o listă de expresii Pascal de orice tip.
 Efectul acestui apel este tipărirea valorilor expresiilor din <lista-write>. Bineînţeles că
pentru a tipări aceste valori se vor evalua mai întâi expresiile prezente în listă. Valorile expresiilor
sunt tipărite dar nu sunt reţinute în memorie şi nu pot fi folosite în alte instrucţiuni din program.
Dacă dorim să folosim valoarea unei expresii şi în alte instrucţiuni atunci o vom atribui mai întâi
unei variabile şi vom folosi această variabilă atât în <lista-write> cât şi în celelalte instrucţiuni.
 Pentru a înţelege mai bine procesul de introducere şi extragere a datelor vom folosi
noţiunea de înregistrare. Prin înregistrare vom înţelege informaţia pe care o transferă la un moment
dat o unitate periferică. Astfel, pentru un cititor de cartele o înregistrare era o cartelă perforată.
Pentru imprimantă o înregistrare va fi un rând al hârtiei de scris (listingului), a cărei lungime
depinde de imprimanta folosită (de obicei 80 sau 132 de caractere). Pentru un calculator personal o
înregistrare va fi un rând al ecranului, sau o succesiune de caractere de lungime variabilă, terminată
cu un caracter special EOLN (de la End Of Line).

________________________________________________________________________________

85
Structuri de date şi algoritmi
_______________________________________________________________________________
 Pe suportul extern datele de transferat se află scrise, sau vor fi tipărite pe o succesiune de
înregistrări. La procedurile READ şi WRITE atunci când se epuizează o înregistrare se începe o altă
înregistrare. Adeseori este însă necesar să se treacă la o altă înregistrare înainte de a fi epuizată
precedenta. Pentru aceasta se pot folosi procedurile READLN şi WRITELN, care au sintaxa şi
semnificaţia procedurilor READ, respectiv WRITE, dar cer în plus ca la terminarea execuţiei lor să
se treacă la începutul unei noi înregistrări.
  
1.3.3.9.12. Instrucţiuni condiţionale
  
Există două instrucţiuni Pascal care permit execuţia unor instrucţiuni în funcţie de
îndeplinirea unor condiţii: instrucţiunile IF şi CASE.
  
1.3.3.9.12.1. Instrucţiunea IF

Instrucţiunea IF are sintaxa


IF <cond> THEN <ins1>
 sau
IF <cond> THEN <ins1> ELSE <ins2>
unde <cond> este o expresie logică iar <ins1> şi <ins2> sunt instrucţiuni Pascal. Subliniem că după
cuvântul THEN şi în faţa cuvântului ELSE nu poate fi scris caracterul ';', deci nu poate apare o
instrucţiune vidă.
 În ambele variante instrucţiunea cere mai întâi evaluarea expresiei logice <cond>. Dacă
valoarea obţinută este TRUE atunci se execută instrucţiunea <ins1> cu care se încheie execuţia
instrucţiunii IF. Dacă valoarea obţinută este FALSE atunci, în cazul variantei a doua se execută
instrucţiunea <ins2>, iar în cazul primei variante nu se execută nimic.
 Această instrucţiune permite scrierea in Pascal a structurilor alternative şi este echivalentă
ca semnificaţie cu propoziţia Pseudocod:
 
Dacă <cond> atunci <ins1>
 altfel <ins2>
 Sfdacă
  Ca un prim exemplu, pentru a efectua atribuirea V:= X în Pascal putem scrie:
  IF X < 0 THEN V := -X ELSE V := X

Exemplul 21:  Exemplu de rezolvare a ecuaţiei de gradul 2.


Algoritmul Ecgr2 este:
Citeşte a, b, c;

________________________________________________________________________________

86
Structuri de date şi algoritmi
_______________________________________________________________________________
 Fie d := b*b - 4*a*c
 Dacă d < 0 atunci kod := 0
 altfel kod := 1; r := radical din d
 x1 := (-b-r)/(a+a); x2 := (-b+r)/(a+a)
 sfdacă
 Dacă kod = 0 atunci Tipareste('Ec. nu are rad. reale')
 altfel Tipareste('Radacinile ec. sunt:', x1, x2)
 sfdacă
 sf-Ecgr2
 
  Programul Pascal corespunzător este următorul:
  PROGRAM Ecgr2; { Programul 4.2: Rezolvarea}
 {ecuatiei de gradul 2}
 VAR a, b, c, {Coeficienţii ecuaţiei}
 d, {Discriminantul ecuaţiei}
 r, {variabilă de lucru}
 x1, x2: REAL; {Rădăcinile}
 kod: INTEGER; {kod =0 pt. răd.reale, 1_complexe}
 BEGIN
  WRITELN('Se rezolva ecuatia de gradul doi');
  WRITELN('Dati coeficientii a, b, c'); READLN(a, b, c);
  d := b*b - 4*a*c;
  IF d < 0 THEN
kod := 0
ELSE
  BEGIN
  kod := 1; r := sqrt(d);
  x1 := (-b-r)/(a+a);
  x2 := (-b+r)/(a+a)
  END;
  IF kod = 0
  THEN WRITE('Ec. nu are radacini reale')
  ELSE
WRITE('Radacinile ecuatiei sunt:', x1, x2);

________________________________________________________________________________

87
Structuri de date şi algoritmi
_______________________________________________________________________________
 END.
  
 
1.3.3.9.12.2.. Instrucţiunea CASE
  Instrucţiunea CASE permite selectarea unei instrucţiuni dintr-o mulţime de instrucţiuni
marcate, în funcţie de valoarea unui selector. Sintaxa instrucţiunii este
  CASE <eo> OF
 <lista-c> : <ins> { ;
 <lista-c> : <ins> }
 [ ELSE <ins> ]
 END
  unde <eo> este o expresie de tip ordinal, <lista-c> este o listă de constante-case, fiecare
constantă-case având tipul ordinal al expresiei <eo>. În <lista-c> în locul unei constante poate apare
şi un subdomeniu c1..c2 pentru o scriere mai condensată în cazul când constantele sunt consecutive.
 Execuţia instrucţiunii CASE cere mai întâi evaluarea expresiei <eo>, obţinându-se o
valoare v, care constituie valoarea selectorului. Apoi se caută în listele de constante-case constanta
egală cu v şi se execută instrucţiunea a cărei etichetă este chiar v.
 Dacă nu există nici o instrucţiune cu eticheta v atunci, în cazul că este prezent cuvântul
ELSE se execută instrucţiunea ce urmează după acest cuvânt, altfel nu se execută nici o
instrucţiune.
Exemplul 22:    Ca exemplu vom scrie un program Pascal care calculează valoarea unei funcţii

  PROGRAM Valf; { Programul 2: Valoarea unei funcţii }


 { Instrucţiunea CASE }
 VAR x, f : REAL;
 a : CHAR;
 BEGIN
  WRITELN('Se calculeaza f(x,a). Introduceţi x si a');
  READ(a, x);
  CASE a OF
  'A': f := 5*x-8; { cazul a = 'A' }
  'B': f := sin(x); { cazul a = 'B' }
  'C': f := 3*x+1 { cazul a = 'C' }
  END {CASE};
  WRITE('f(', x, ',', a, ')=', f)

________________________________________________________________________________

88
Structuri de date şi algoritmi
_______________________________________________________________________________
 END.
  
1.3.3.9.13.. Instrucţiuni repetitive
  În Pascal există trei instrucţiuni care permit execuţia repetată a unui grup de instrucţiuni şi
anume instrucţiunile FOR, WHILE şi REPEAT.

1.3.3.9.13.1. Instrucţiunea FOR


  Această instrucţiune permite execuţia repetată a unei instrucţiuni în funcţie de valoarea
unui contor. Ea are sintaxa
FOR <v> := <e1> TO <e2> DO <ins>
 sau  
FOR <v> := <e1> DOWNTO <e2> DO <ins>
  Aici <v> este un identificator de variabilă numită contor, iar <e1> şi <e2> sunt expresii,
toate trei având acelaşi tip ordinal (deci nu pot avea tipul real). Valorile vi şi vf ale expresiilor <e1>
şi <e2> se calculează o singură dată la începutul execuţiei instrucţiunii FOR. Variabila contor <v>
va lua valori între limitele vi şi vf, crescător dacă în instrucţiune figurează cuvântul TO şi
descrescător dacă în instrucţiune figurează cuvântul DOWNTO. Semnificaţia acestei instrucţiuni
este dată prin următorul algoritm:
 @Calculează valorile vi şi vf ale expresiilor e1, respectiv e2;
 DACĂ c1 ATUNCI
  FIE v := vi;
  REPETĂ
  @Execută instrucţiunea ins;
  DACĂ c2 ATUNCI
v:=e3
SFDACĂ
  PÂNĂCÂND c3 SFREP
 SFDACĂ
unde condiţiile c1, c2 şi c3 şi expresia e3 sunt definite în continuare:
  

Condiţii TO DOWNTO

c1 vi ≤= vf vi >= vf

c2 v < vf v > vf

c3 v >=vf v <= vf

________________________________________________________________________________

89
Structuri de date şi algoritmi
_______________________________________________________________________________
e3 este SUCC(v) PRED(v)

Tabelul 6: Definirea condiţiilor pentru execuţia instrucţiunii FOR.


 
Contorul v poate fi folosit în instrucţiunea <ins> dar nu este permisă modificarea valorii
sale. Deşi unele implementări permit acest lucru, recomandăm să nu se modifice valoarea variabilei
contor v în instrucţiunea <ins>.
 Astfel, în mediul Turbo-Pascal nu se consideră eroare modificarea variabilei contor, dar
execuţia unui astfel de program devine imprevizibilă. De exemplu, execuţia următoarelor
instrucţiuni intră într-un ciclu infinit:
 For i:=1 to 5 do {La execuţia acestui grup se vor tipari}
 begin { int=1, apoi 3, 5, 7, 9, ... }
  writeln('int=',i); { deci se intra in ciclu infinit}
  readln(j); { in Turbo 7.0 ! }
  i:=i+1; 
end
  Unii programatori folosesc intenţionat, atunci când e posibil acest lucru, modificarea
valorii contorului pentru a forţa ieşirea din ciclu. O astfel de programare încalcă semnificaţia
instrucţiunii FOR şi nu este recomandabilă.
 Ca un prim exemplu de folosire a instrucţiunii FOR dăm un program care tipăreşte toţi
divizorii proprii ai numărului natural n>2.
Exemplul 23:      
PROGRAM Divizori; { Programul 4.4: Divizorii lui n }
VAR n,i : INTEGER;
BEGIN
WRITELN('Se tiparesc divizorii lui n');
WRITE('Dati n='); READLN(n);
FOR i:=2 TO n-1 DO
IF n MOD i = 0 THEN
WRITELN(i);
END.
  
1.3.3.9.13.2. Instrucţiunea WHILE
 Această instrucţiune are sintaxa
WHILE <cond> DO <ins>
unde <cond> este o expresie logică iar <ins> este o instrucţiune Pascal. Semnificaţia instrucţiunii

________________________________________________________________________________

90
Structuri de date şi algoritmi
_______________________________________________________________________________
WHILE este aceeaşi cu a propoziţiei Pseudocod:
  CÎTTIMP <cond> EXECUTĂ
<ins> SFCÂT
 Deci execuţia instrucţiunii WHILE cere următoarele:
 1. evaluarea expresiei logice <cond>;
 2. dacă valoarea expresiei este TRUE atunci se execută instrucţiunea <ins> şi se revine la
pasul 1, altfel execuţia se termină.
 Cu alte cuvinte, ea cere execuţia repetată a unei instrucţiuni Pascal în funcţie de valoarea de
adevăr a unei expresii logice. Dacă iniţial expresia logică este falsă execuţia instrucţiunii respective
nu va avea loc niciodată.
  Ca exemplu, vom transcrie în Pascal următorul algoritm care găseşte primele n numere
prime (Mocanu, et al. [1993]).
Exemplul 23:      
Pseudocod:
 Algoritmul PRIME este: {Determina primele n numere prime}
 Citeşte n;
 Fie k:=2; p[1]:=2; p[2]:=3; i:=5;
 Câttimp k<n execută
  Fie j:=1;
  Câttimp j<=k şi (i mod p[j] <> 0) execută
j:=j+1
sfcât
  Dacă j>k atunci
k:=k+1;
p[k]:=i
sfdacă
 i:=i+2;
 sfcât
 Tipăreşte p[j], j=1,n;
 sf-Prime

Programul Pascal:

 PROGRAM PRIME; { Programul 4.5: Calculează


  primele n numere prime }

________________________________________________________________________________

91
Structuri de date şi algoritmi
_______________________________________________________________________________
 VAR n,i,j,k : INTEGER;
 p : ARRAY [1..200] OF INTEGER;
 BEGIN
  WRITELN('Se tiparesc primele n numere prime!');
  WRITE('Dati n='); READLN(n);
  k:=2; p[1]:=2; p[2]:=3; i:=5;
  WHILE k<n DO
  BEGIN
j:=1;
  WHILE (j<=k) AND (i mod p[j] <> 0) DO
j:=j+1;
  IF j>k THEN
BEGIN
k:=k+1;
p[k]:=i
END;
  i:=i+2
 
END;
  WRITELN('Primele ',n,' numere prime sunt:');
  FOR j:=1 TO n DO WRITELN(' p(',j,')=',p[j])
 END.

    
1.3.3.9.13.3. Instrucţiunea REPEAT
  Această instrucţiune permite execuţia repetată a unui grup de instrucţiuni. Ea are sintaxa
REPEAT <ins> { ; <ins> } UNTIL <cond>
unde <ins> este o instrucţiune Pascal, iar <cond> este o expresie logică. Instrucţiunea este
echivalentă cu următoarea propoziţie Pseudocod:
  REPETĂ
<ins> { <ins> }
PÂNĂCÂND <cond> SFREP
Deci semnificaţia instrucţiunii este următoarea:
 1. se execută instrucţiunile scrise între cuvintele REPEAT şi UNTIL;

________________________________________________________________________________

92
Structuri de date şi algoritmi
_______________________________________________________________________________
 2. se evaluează expresia logică <cond>. Dacă valoarea expresiei este TRUE atunci execuţia
se termină, altfel se revine la pasul 1.
  Ca exemplu de folosire a instrucţiunii REPEAT vom transcrie în Pascal următorul algoritm
pentru ordonarea descrescătoare a unui şir de numere reale.
Exemplul 24:    
Pseudocod:  
   Algoritmul Ordon este: {Ordonează descrescător}
  {numerele x[1], ... , x[n]}
 Citeşte n, (x[i],i=1,n);
 Repetă k:=0;
  Pentru i:=1,n-1 execută
  Dacă x[i]<x[i+1] atunci
  k:=1;
  t:=x[i];
  x[i]:=x[i+1];
  x[i+1]:=t;
  sfdacă
  sfpentru
 pânăcând k=0 sfrep
 Tipăreşte x[i],i=1,n;
 sfalgoritm
 
Programul Pascal corespunzător este:
 PROGRAM Ordon; {Programul 4.6: Ordonarea unui sir de numere}
 VAR t : REAL;
 n,i,k : INTEGER;
 x : ARRAY [1..100] OF REAL;
 BEGIN
  WRITELN('Se ordonează o secventa de numere reale');
  WRITE('Nr.termenilor='); READLN(n);
  WRITELN('Dati termenii');
  FOR i:=1 TO n DO
READ(x[i]);
  WRITELN;

________________________________________________________________________________

93
Structuri de date şi algoritmi
_______________________________________________________________________________
WRITELN('Sirul initial este:');
  FOR i:=1 TO n DO
WRITE(x[i]:8:1);
  REPEAT
k:=0;
  FOR i:=1 TO n-1 DO
  IF x[i] < x[i+1] THEN
  BEGIN
k:=1;
t:=x[i];
  x[i]:=x[i+1];
  x[i+1]:=t
  END;
  UNTIL k=0;
  WRITELN(' Sirul ordonat este:');
 FOR i:=1 TO n DO
WRITE(x[i]:8:2);
 END.
   
1.3.3.10. Subprograme Pascal
 
Subprogramele în Pascal corespund subprogramelor din Pseudocod. Rolul şi importanţa lor
sunt cele deja menţionate în capitolul doi. Importanţa subprogramelor în programare a fost deja
subliniată în capitolul trei. În scopul refolosirii subalgoritmilor şi subprogramelor e bine ca pentru
orice problemă să descriem un subalgoritm pentru rezolvarea ei.

Obs. Xx: Concepe subalgoritm şi scrie subprogram pentru orice problemă care-ar putea fi reîntâlnită
în viitor.

  1.3.3.10.1. Sintaxa subprogramelor Pascal

  Ca şi în alte limbaje de programare şi în limbajul Pascal există două tipuri de subprograme:
funcţii şi proceduri. Definirea acestor subprograme în cadrul unui program Pascal se face în partea
de declaraţii. Avem
  <def-subprogram> ::= <def-funcţie> ˝ <def-procedură>

________________________________________________________________________________

94
Structuri de date şi algoritmi
_______________________________________________________________________________
unde  <def-funcţie> ::= <antet-funcţie> ; <bloc>
 <def-procedură> ::= <antet-procedură> ; <bloc>
 iar
 <antet-funcţie> ::= FUNCTION <id> [ (l.p.f.) ] : <tipf>
 <antet-procedură> ::= PROCEDURE <id> [ (l.p.f.) ]
  În această sintaxă <id> este un identificator care constituie numele subprogramului definit.
Lista parametrilor formali <l.p.f.> este opţională şi ea precizează variabilele de care depinde
subprogramul şi tipul acestor variabile.
  
1.3.3.10.2. Lista parametrilor formali
 
 Lista parametrilor formali este formată din mai multe secţiuni de parametri separate între
ele de caracterul ';'. Sintaxa acestei liste este:
  <l.p.f.> ::= <spf> { ; <spf> }
 unde prin <spf> s-a notat o secţiune de parametri formali, secţiune care are sintaxa
  <spf> ::= <sp-val> ˝ <sp-var> ˝ <pf-funcţie> ˝ <pf-procedură>
  Din punct de vedere sintactic secţiunea de parametri valoare <sp-val> este orice listă de
identificatori urmată de caracterul ':' şi de un identificator de tip care va fi tipul tuturor
identificatorilor din lista de identificatori. Deci
  <sp-val> ::= <lista-id> : <id-tip>
  Secţiunea de parametri variabilă <sp-var> are sintaxa
  <sp-var> ::= VAR <lista-id> : <id-tip>
unde <id-tip> este un identificator de tip, definit anterior, deci asemănătoare secţiunii parametrilor
valoare, singura diferenţă fiind prezenţa cuvântului VAR în faţa listei.
 Menţionăm că identificatorii care notează parametrii formali nu pot fi declaraţi din nou în
partea de declaraţii a subprogramului. Ei se consideră declaraţi şi pot fi folosiţi în tot subprogramul,
ca orice altă variabilă locală definită în subprogram.
 Ca semnificaţie, parametri formali notează datele de intrare (cele presupuse cunoscute) şi
datele de ieşire (rezultate) ale subprogramului. Ei sunt chiar parametrii formali din definiţia
subalgoritmilor corespunzători.
 
 1.3.3.10.3. Variabile locale şi variabile globale
  
În definiţia unui subprogram apare la început un antet, după care urmează un bloc. Deci
subprogramul are o structură similară unui program. Să ne reamintim că un bloc constă din două
părţi: o listă de declaraţii şi o instrucţiune compusă. Elementele definite în lista de declaraţii sunt
locale pentru blocul în care sunt definite. Acesta constituie domeniul de vizibilitate al acestor

________________________________________________________________________________

95
Structuri de date şi algoritmi
_______________________________________________________________________________
elemente; ele pot fi folosite numai în interiorul subprogramului în care au fost declarate, nu şi în
afara acestuia.
 Fie S un subprogram al programului P. Pe lângă variabilele locale ale subprogramului S
toate elementele declarate în lista de declaraţii ale programului P sunt considerate globale pentru
subprogramul S şi pot fi folosite în acest subprogram. Deci elementele declarate în S pot fi folosite
numai în S, nu şi în restul programului P. Ele sunt locale pentru S, dar sunt globale şi pot fi folosite
în subprogramele S1 şi S2 incluse în S. Elementele definite în P sunt globale şi pot fi folosite în S,
S1 şi S2.
Considerând programul principal ca un bloc de nivel 0, vom considera subprogramele
definite în el ca blocuri de nivel 1. În general, un bloc definit într-un bloc de nivel i are nivelul i+1.
 Dacă într-un bloc de nivel i se foloseşte o variabilă v şi acelaşi identificator v notează o
variabilă într-un bloc de nivel i+1, cele două variabile se consideră distincte deşi au acelaşi nume. În
acest caz variabila din blocul interior este cea considerată existentă în acest bloc, iar cea exterioară
nu există decât în partea blocului de nivel i exterioară blocului de nivel i+1.
 Ca exemplu, să considerăm programul 4.9 din secţiunea 4.3.6. În procedura S1 se foloseşte
variabila locală x. Dar şi în programul principal se foloseşte o variabilă globală cu numele x.
Întrucât procedura S1 foloseşte variabila locală cu numele x, variabila globală x îşi pierde
semnificaţia în interiorul acestei proceduri (în care există o variabilă locală cu numele x). Cele două
variabile (cu acelaşi nume x) au rezervate locaţii de memorie distincte şi, în consecinţă, sunt
distincte. Cât priveşte folosirea variabilelor globale recomandăm
  
Obs. Xx: Nu folosiţi variabile globale decât în cazuri speciale.
   
1.3.3.11. Funcţii Pascal

  Am văzut că definiţia unei funcţii constă dintr-un antet şi dintr-un bloc care va defini
acţiunile prin care se calculează valoarea funcţiei. În antet se precizează numele funcţiei, lista
parametrilor formali şi tipul funcţiei, <tipf>. Acesta trebuie să fie un nume de tip referinţă, sau un
tip simplu.
 Este necesar ca în instrucţiunile din corpul <bloc> al funcţiei să existe cel puţin o atribuire
prin care identificatorului <id> să i se atribuie o valoare.
 Apelul unei funcţii se face scriind într-o expresie numele funcţiei urmat de lista
parametrilor actuali. Trebuie să existe o corespondenţă biunivocă între parametrii actuali şi cei
formali folosiţi în definiţia funcţiei. Dacă nu au existat parametri formali în definiţie atunci apelul se
face scriind doar numele funcţiei. Despre corespondenţa dintre parametrii formali şi parametrii
actuali vom vorbi în secţiunea 4.3.6.
 Ca exemplu dăm în continuare un program care foloseşte un subprogram de tip funcţie
pentru a obţine numărul zilelor lunii cu numărul de ordine i pentru a calcula a câta zi din anul curent
este ziua curentă (zi, luna, an).
 Se folosesc două funcţii:

________________________________________________________________________________

96
Structuri de date şi algoritmi
_______________________________________________________________________________
 - NRZILE(i) furnizează numărul zilelor existente în luna i;
- BISECT(an) adevărată dacă anul dintre paranteze este bisect.
Exemplul 25:    
Algoritmul în Pseudocod este următorul:
 
 ALGORITMUL NUMĂR_ZILE ESTE:
 CITEŞTE zi, luna, an;
 FIE nr := zi;
 DACĂ luna > 1 ATUNCI
  PENTRU i := 1, luna-1 EXECUTĂ
nr := nr + NRZILE(i)
SFPENTRU
 SFDACĂ
 DACĂ luna > 2 ATUNCI
  DACĂ BISECT(an) ATUNCI
nr := nr + 1
SFDACĂ
 SFDACĂ
 TIPĂREŞTE nr;
 SFALGORITM
  
Programul Pascal corespunzător este următorul:
 PROGRAM APELFUNCTIE; { Programul 4.7 Exemplu de funcţii Pascal}
 VAR zi, luna, an, { Aceste variabile conţin data curentă }
 nr: INTEGER; { nr va fi rezultatul cerut }
 FUNCTION NRZILE(i: INTEGER): INTEGER; {Furnizeaza numarul zilelor}
  {lunii cu numarul i}
 BEGIN
  CASE i OF
  2: NRZILE := 28;
  4, 6, 9, 11: NRZILE := 30;
  1, 3, 5, 7, 8, 10, 12: NRZILE := 31
  END {CASE}
 END; {Nrzile}

________________________________________________________________________________

97
Structuri de date şi algoritmi
_______________________________________________________________________________
  
FUNCTION BISECT(a: INTEGER): BOOLEAN; {Adevarata daca a este an bisect}
BEGIN
IF a mod 4 = 0 {and alte cerinte} {verivica daca a este an bisect}
THEN BISECT := TRUE
ELSE BISECT := FALSE
END; {Bisect}
  
BEGIN {programul principal}
WRITELN('Dati data curenta.');
WRITE('ziua = '); READLN(zi);
WRITE('luna = '); READLN(luna);
WRITE('anul = '); READLN(an);
  nr := zi;
  IF luna > 1 THEN
  FOR i := 1 TO luna - 1 DO
nr := nr + NRZILE(i);
  IF luna > 2 THEN
IF BISECT(an)
THEN nr := nr + 1;
  WRITE('Data(', zi, ',', luna, ',', an, ') este a ');
  WRITELN(nr, '-a zi din an');
 END.
  
Funcţia NRZILE definită mai sus este apelată în partea dreaptă a unei atribuiri prin scrierea
ei în locul unei expresii. La fel, funcţia booleană BISECT este apelată prin scrierea ei într-o
expresie logică.
 

 1.3.3.12. Instrucţiunea apel de procedură


  
Apelul unei proceduri se face scriind numele procedurii urmat de lista parametrilor actuali
pe locul unei instrucţiuni, ceea ce echivalează cu execuţia tuturor instrucţiunilor din bloc.
<apel-procedură> ::= <id> [ (<lista-p.a.> ]

________________________________________________________________________________

98
Structuri de date şi algoritmi
_______________________________________________________________________________
  Apelul unei proceduri are ca efect execuţia tuturor instrucţiunilor procedurii apelate,
considerând că fiecare parametru formal este egal cu parametrul actual corespunzător (a se vedea
secţiunea 4.3.6).
 Dăm un exemplu de program în care se definesc şi se apelează proceduri. În plus,
programul ilustrează modul de lucru cu cifrele privite drept caractere, precum şi folosirea funcţiei
ORD.
  Se cere să se tipărească toate numerele întregi întâlnite într-un şir de caractere care se
termină prin caracterul '$'.
  Algoritmul de rezolvare se dă în continuare. El foloseşte un subalgoritm, CITCIFRA(b,c),
care citeşte un caracter c şi atribuie lui b valoarea TRUE dacă c este o cifră, respectiv FALSE în caz
contrar.
 Exemplul 26:    
Algoritmul în Pseudocod este următorul:
  
ALGORITMUL CONVERSII ESTE:
 REPETĂ
  @Caută o cifră {cu care începe un întreg}
  @Calculează numărul întreg format din cifrele consecutive  întâlnite.
  @Tipăreşte numărul găsit.
 PÂNĂCÂND c='$' SFREP
 SFALGORITM
 
Programul Pascal corespunzător se dă în continuare.
 PROGRAM CONVERSII; { Programul 4.8: Conversii
  {de secvente de cifre in intregi}
 VAR c: CHAR;
 nr: INTEGER;
 b: BOOLEAN;
  
PROCEDURE CITCIFRA(Var b: BOOLEAN; {b=TRUE daca c este}
 Var c: CHAR); {cifra; c=caracterul citit}
 BEGIN
  READ(c); b:= TRUE;
  IF (c<'0') OR (c>'9') THEN
b := FALSE;

________________________________________________________________________________

99
Structuri de date şi algoritmi
_______________________________________________________________________________
 END;
 
BEGIN {Programul principal}
REPEAT
CITCIFRA(b, c);
  WHILE NOT(b) AND (c<>'$') DO
CITCIFRA(b, c);
  IF c <> '$' THEN
  BEGIN
nr := 0;
  REPEAT nr := nr*10 + Ord(c) - Ord('0');
  CITCIFRA(b, c);
  UNTIL NOT(b);
  WRITELN('nr. citit = ', nr)
  END
  UNTIL c='$'
 END.
   
1.3.3.13. Corespondenţa dintre parametri actuali şi formali
  
Apelul unui subprogram se face scriind numele acestuia urmat de lista parametrilor actuali.
Între parametrii din lista parametrilor actuali şi parametrii din lista parametrilor formali trebuie să
existe o corespondenţă biunivocă, iar parametrii care sunt în corespondenţă trebuie să aibă acelaşi
tip. De fapt, ei trebuie să aibă aceeaşi semnificaţie, să se refere la acelaşi obiect, să reprezinte
aceeaşi structură de date.
 Sintactic un parametru actual poate fi o expresie Pascal, o variabilă, un identificator de
procedură sau un identificator de funcţie.
    
1.3.3.13.1. Parametri valoare
  Parametrul actual corespunzător unui parametru formal valoare f poate fi orice expresie
Pascal e de acelaşi tip cu parametrul f. La apelul subprogramului se calculează valoarea expresiei e
şi se atribuie valoarea obţinută parametrului formal f. Mai exact, parametrul formal f de tip valoare
are rezervată o locaţie de memorie în care se depune valoarea expresiei e înainte de a începe
execuţia instrucţiunilor subprogramului. În continuare această variabilă f este considerată variabilă
locală în subprogram. Ea îşi poate schimba valoarea dar această valoare nu poate fi transmisă în
unitatea de program în care s-a făcut apelul.

________________________________________________________________________________

100
Structuri de date şi algoritmi
_______________________________________________________________________________
 Menţionăm că este posibil ca elementele f şi e să nu aibă acelaşi tip. Excepţiile sunt cele
permise de atribuirea f := e.
 De asemenea, menţionăm că parametrii formali de tip valoare corespund variabilelor care se
consideră cunoscute la intrarea în subalgoritm (datelor de intrare).
  Prezentăm în continuare un program în care se poate vedea un exemplu de apel al unui
subprogram cu parametri valoare.
 Exemplul 27:    

 PROGRAM Parval; { Programul 4.9: Parametri formali }


 VAR x, z : INTEGER; { de tip valoare şi variabila }
 
PROCEDURE S1(y: INTEGER);
 VAR x: INTEGER;
 BEGIN
  x := y*y;
  y := x;
  z := y-4;
  writeln('In procedura y, z = ', y:5, z:5);
 END; {S1}
 
BEGIN
  FOR x := 2 TO 4 DO
  BEGIN
  S1(x);
  writeln('In program x, z = ', x:5, z:5);
  END
 END.
  La execuţia acestui program se obţin următoarele rezultate:
In procedura y, z = 4 0
 In program x, z = 2 0
 In procedura y, z = 9 5
 In program x, z = 3 5
 In procedura y, z = 16 11
 In program x, z = 4 11

________________________________________________________________________________

101
Structuri de date şi algoritmi
_______________________________________________________________________________
  
Se poate observa că valorile variabilei locale y nu au fost retransmise în programul
principal, parametrul actual x nemodificându-şi valorile prin execuţia procedurii S1.
  
1.3.3.13.1.2. Parametrii de tip referinţă
  
Parametri de tip referinţă se folosesc pentru a transmite valori între unitatea apelantă şi
subprogramul apelat, în ambele sensuri. De obicei sunt folosiţi pentru a nota rezultatele obţinute în
subprogram. Parametrul actual a corespunzător unui parametru formal de tip referinţă f este
obligatoriu o variabilă de acelaşi tip cu parametrul formal f. La apelul unui subprogram parametrul
formal se consideră identic cu parametrul actual corespunzător. Practic acest lucru se realizează prin
faptul că amândoi au aceeaşi adresă în memorie. Astfel, parametrul actual a devine parametru
global; orice modificare a parametrului formal f este şi o modificare a parametrului actual a.
 Se recomandă ca parametrii actuali corespunzători parametrilor formali variabilă să fie
distincţi. Unele implementări consideră eroare când la doi parametri formali corespunde acelaşi
parametru actual, altele acceptă o astfel de corespondenţă. Atenţie însă la o asemenea situaţie
întrucât fiind identici cu acelaşi parametru actual, cei doi parametri formali devin identici între ei,
ceea ce poate duce la efecte neaşteptate. De aceea recomandăm să se evite o asemenea utilizare,
chiar dacă ea este permisă.
   
1.3.3.13.1.3. Parametrii funcţie şi parametrii procedură
  
Din definiţia sintactică a listei parametrilor formali se observă că un parametru formal poate
fi şi o funcţie sau o procedură. Parametrul actual trebuie să fie o funcţie, respectiv o procedură
similară parametrului formal corespunzător.
 Un exemplu de program în care se folosesc parametrii formali şi actuali funcţii şi proceduri
este cel de mai jos, în care se calculează radicalii de ordinul doi şi trei din constanta mm (în
program egală cu 2) rezolvând ecuaţiile:
 x2 - mm = 0, notată g(x) = 0,
 respectiv
 x3 - mm = 0, notată h(x) = 0.
  Pentru rezolvarea unei ecuaţii se pot folosi mai multe metode. În program am ales două:
metoda înjumătăţirii şi metoda coardei. Întrucât metoda coardei foloseşte şi prima derivată, am
notat prin f1, respectiv g1 derivatele funcţiilor f şi g.
 Exemplul 28:    

 Program lpf; { Program 4.10: P.F. funcţii si proceduri}


 Const mm = 2;

________________________________________________________________________________

102
Structuri de date şi algoritmi
_______________________________________________________________________________
 Type
  fct = function (x: real): real;
  sub1 = procedure (a, b: real; var r:real; f,f1:fct);
 Var r: real;
 i: integer;
 
Function g(s: real): real;
 Begin
  g := s*s - mm;
 end;
  
Function h(s: real): real;
Begin
 h:= s*s*s - mm;
 end;
  
Function g1(s: real): real;
 Begin
  g1 := s + s;
 end;
  
Function h1(s: real): real;
 Begin
  h1 := 3*s*s;
 end;
  
Procedure coarda(a, b: real; var r: real; f, f1: fct); {Se rezolva ecuatia f(x) = 0 prin metoda
coardei}
 Var c, t: real;
 Begin
  c := a; t := b;
  Repeat
  c := c - f(c)*(t-c)/(f(t)-f(c));
  t := t - f(t)/f1(t);

________________________________________________________________________________

103
Structuri de date şi algoritmi
_______________________________________________________________________________
  until t - c < 0.00001;
  r := (c+t)/2
 end;
  
Procedure juma(a, b: real; var r: real; f, f1: fct); {Se rezolva ecuatia f(x)=0 prin metoda
injumatatirii}
 Begin
  Repeat
  r := (a+b)/2;
  If f(a)*f(r) < 0 then
b := r
else
a := r;
 until b - a < 0.00001
 end;
 
 Procedure Rezec(a, b: real; var r: real; f, f1: fct; met: sub1);
 Begin
  met(a, b, r, f, f1);
 end;
 
Begin
writeln('injumatatire coarda: ');
writeln('radical din 2');
  Rezec(1, 2, r, g, g1, juma);
  write('r = ', r:9:5);
  Rezec(1, 2, r, g, g1, coarda);
  writeln(' ', r:9:5);
  writeln('radical din 3');
  Rezec(1, 2, r, h, h1, juma);
  write('r = ', r:9:5);
  Rezec(1, 2, r, h, h1, coarda);
  writeln(' ', r:9:5);
 end.

________________________________________________________________________________

104
Structuri de date şi algoritmi
_______________________________________________________________________________
   
1.3.3.14. Apel recursiv
 
 În exemplele date se observă că apelul unui subprogram se face după ce el a fost definit.
Este însă permis ca în interiorul unei proceduri să fie definită o altă procedură. Această procedură
poate fi utilizată numai în interiorul procedurii în care a fost declarată şi numai după ce a fost
definită.
 Este posibil ca un subprogram să se apeleze pe el însuşi, recursiv. Ca exemplu, prezentăm
în continuare un program care foloseşte o funcţie care calculează recursiv valoarea n! cu ajutorul
formulei  n! = n.(n-1)! .
 Exemplul 29:    

Program ApelRecursiv; { Programul 4.11: n Factorial }


Type nat = 0..maxint;
Var i: nat;
 Function Fact(n: nat): nat;
 Begin
  If n = 0 then
Fact := 1
  else
Fact := n * Fact(n-1)
 end;
 
Begin
  For i:=3 to 7 do
  Writeln(i,'! = ', Fact(i))
 end.
  Să urmărim execuţia apelului FACT(n) pentru n = 3. Să observăm că funcţia Fact nu are
nici o variabilă locală în afară de parametrul formal valoare n. Deci la apelul acestei funcţii se vor
rezerva două locaţii de memorie, una pentru n şi una pentru valoarea funcţiei FACT, fiecare apel
însemnând o astfel de rezervare.
 Deci, după primul apel, zona de memorie alocată este cea situaţia a) de mai jos. Pentru a
efectua atribuirea Fact(1) := 3*Fact(2) se apelează Fact(2) şi se rezervă din nou două locaţii de
memorie, ajungând la situaţia din situaţia b) de mai jos. La execuţia acestui apel trebuie efectuată
atribuirea Fact(2) := 2*Fact(1). Deci are loc un al treilea apel, Fact(1), pentru care se rezervă din
nou memorie, ajungându-se la situaţia c) de mai jos. De această dată se cere efectuarea atribuirii
Fact(3) := 1*Fact(0) şi are loc un al patrulea apel, Fact(0), ajungându-se la situaţia d) de mai jos.

________________________________________________________________________________

105
Structuri de date şi algoritmi
_______________________________________________________________________________
Când ultimul apel s-a executat, el întoarce valoarea Fact(4) := 1 şi se eliberează zona de memorie
corespunzătoare ajungându-se la situaţia situaţia e) de mai jos. Întrucât şi apelul Fact(1) a fost
executat, se ajunge la situaţia f) de mai jos. Se efectuează şi atribuirea Fact(2) := 2*Fact(1) = 2 cu
care se termină execuţia apelului Fact(2), ajungându-se la situaţia g) de mai jos. În această situaţie
se încheie şi execuţia primului apel cu valoarea Fact(1) := 6.
 3 ?
n Fact1
 a) 3 ? 2 ?
n Fact1 n’ Fact2
 b) 3 ? 2 ? 1 ?
n Fact1 n' Fact2 n" Fact3
 c) 3 ? 2 ? 1 ? 0 1
n Fact1 n' Fact2 n" Fact3 n"' Fact4
 d) 3 ? 2 ? 1 1
n Fact1 n' Fact2 n" Fact3
 e) 3 ? 2 2
n Fact1 n’ Fact2
 f) 3 6
n Fact1
 g)
  
 Să observăm atât modul de execuţie al unui apel recursiv cât şi faptul că necesarul de
memorie creşte la fiecare apel. Iar în cazul în care zona de date locale ale procedurii este mare,
volumul de memorie utilizat poate creşte foarte mult.
 Din această cauză apelul recursiv nu este indicat. Consumul de memorie şi timpul necesar
execuţiei sunt mult mai mari decât în cazul unui program nerecursiv corespunzător aceleaşi
probleme. Totuşi, folosirea procedurilor recursive are avantajul unei descrieri mai clare şi mai
apropiată de limbajul matematic.
 În programul următor sunt date câteva exemple de funcţii recursive cu argumente numere
întregi pentru determinarea cifrei maxime a unui număr întreg, sumei cifrelor unui număr întreg şi a
celui mai mare divizor comun a două numere întregi.
 Exemplul 30:    
 Program ExempleRecursvitate; {Programul 4.12: Cifre}
 Const Nm = 1000;
 Var a, b: Integer;
  Function Max2(a, b: Integer): Integer; {Determina maximul a doua numere intregi a, b}

________________________________________________________________________________

106
Structuri de date şi algoritmi
_______________________________________________________________________________
 Begin
  If a>b then Max2:= a else Max2:= b
 end;
 
 Function CifMax(a: Integer): byte; {Calculeaza cifra maxima}
 Begin { a numarului intreg a}
  If a < 10 then
CifMax := a
  else
CifMax := Max2(CifMax(a div 10), a mod 10)
 end;
  
Function SumCif(a: Integer): Integer; {Calculează }
 Begin { suma cifrelor numarului a}
  If a < 10 then
SumCif := a
  else
SumCif := SumCif(a div 10) + a mod 10
 end;
  
Function Cmmdc(a, b: Integer): Integer; {Cel mai mare }
 Begin { divizor comun al numerelor a, b}
  If b = 0 then Cmmdc := a
  else
Cmmdc := Cmmdc(b, a mod b)
 end;
 
Begin
  Randomize; a := Random(Nm); b := Random(Nm);
  Writeln ('Cifra maxima a nr. ', a, ' este ', CifMax(a));
  Writeln ('Suma cifrelor nr. ', a, ' este ', SumCif(a));
  Writeln ('C.m.m.d.c.(', a, ',', b, ') = ', Cmmdc(a,b));
 end. 

________________________________________________________________________________

107
Structuri de date şi algoritmi
_______________________________________________________________________________
Următorul program Pascal utilizează câteva funcţii şi proceduri recursive corespunzătoare
unor operaţii elementare asupra şirurilor de numere (generarea şi tiparirea elementelor unui şir,
valoarea şi pozitia elementului maxim respectiv minim dintr-un şir, suma elementelor unui şir,
căutarea unei valori într-un şir şi precizarea dacă un şir este sau nu ordonat crecător respectiv
descrescător).
 Exemplul 31:    

 Program ExRecSir; {Programul 4.13: Exemple de }


{ funcţii şi proceduri recursive}
 Const Dm = 30;
 Type Telem = Integer; {Poate fi orice alt tip}
  {Orice schimbare înseamnă doar o redefinire}
 Sir = Array [1..Dm] of Telem;
 Var A:Sir; n:byte; x:Telem;
   
Procedure Genereaza(Var A:Sir; n:byte); {Generează şirul A cu n valori aleatoare }
 Begin
  If n > 0 then
  Begin
  a[n] := Random(100); { Dă valoare ultimului element}
  Genereaza(A, n-1) { apoi primelor n-1 elemente}
  end
 end {Genereaza};
  
Procedure Tipareste (A:Sir; n:byte); { Tipăreşte din }
 Begin { sirul A primele n elemente}
  If n > 0 then
Begin
  Tipareste (A, n-1); { Tipăreşte primele n-1 elemente }
  Write (a[n], ',') { apoi elementul n }
end
else
Writeln
 end;
  

________________________________________________________________________________

108
Structuri de date şi algoritmi
_______________________________________________________________________________
Procedure TipInvers (A:Sir; n:byte); {Tipăreşte în ordine inversa}
 Begin {primele n elemente ale şirului A}
  If n > 0 then
Begin
  Write (a[n], ','); {Tipăreşte elementul n,}
  TipInvers(A, n-1) {apoi în ordine inversă primele}
  end {n-1 elemente}
 end;
 
Function Max2l(a, b: Telem): Telem; {Max2(a,b) = cel mai mare dintre a si b }
 Begin
  If a > b then Max2 := a else Max2 := b
 end;
  
Function Maxim(A:Sir; n:byte): Telem; {Elementul maxim }
 Begin {din sirul A cu n elemente este egal cu elementul
  maxim din primele n elemente}
  If n=1 then
Maxim := a[1]
  else
Maxim := Max2(a[n], Maxim(A,n-1)) {Maximul dintre primele n
este cel mai mare dintre}
  end; {ultimul element şi maximul dintre primele n-1 elemente}
  
Function Min2(a,b: Telem): Telem; {Min2(a, b) = cel mai mic dintre a şi b}
 Begin
  If a<b then Min2:= a else Min2:= b
 end; 
 
Function Minim(A: Sir; n: byte): Telem; {Elementul minim }
 Begin { din sirul A cu n elemente}
  If n = 1
then Minim := a[1]
  else

________________________________________________________________________________

109
Structuri de date şi algoritmi
_______________________________________________________________________________
Minim := Min2(a[n], Minim(A,n-1))
 end;
  
 Function PozMax(A: Sir; n: byte): Telem; {Poziţia elementului maxim din şirul A de n
elemente}
  Function PozMax2(i, j: byte): byte; {Poziţia elementului }
 Begin { mai mare dintre Ai si Aj}
  If a[i] > a[j]
then
PozMax2 := i
  else
PozMax2 := j
 end;
 Begin {Pozitia maximului din primele n elemente}
  If n = 1 {este pozitia celui mai mare}
  then PozMax := 1 {dintre ultimul si elementul}
  else
PozMax:= PozMax2(n, PozMax(A, n-1)) {maxim}
 end; din primele n-1}
  
Function PozMin(A: Sir; n: byte): Tel; {Poziţia elementului minim din şirul A cu n
elemente}
 Function PozMin2(i, j: byte): byte;
 Begin
  If a[i] < a[j] then PozMin2 := i else PozMin2 := j
 end;
 Begin {PozMin}
  If n = 1 then PozMin := 1
  else PozMin := PozMin2(n, PozMin(A, n-1))
 end;
  
Function Exista(A: Sir; n: byte; x: Telem): Boolean;
Begin {x apartine A?: x=A[n] sau x există în primele n-1}
Exista := (n > 0) and ((x = a[n]) or Exista(A, n-1, x))

________________________________________________________________________________

110
Structuri de date şi algoritmi
_______________________________________________________________________________
 end;
  
Function Cresc(A: Sir; n: byte): Boolean; {Este şirul A ordonat crescător ?}
 Begin {Primele n-1 sunt ordonate crescator şi A[n-1]<A[n]}
  If n=1 then Cresc:= True
  else
Cresc:= Cresc(A, n-1) and (a[n-1] <= a[n])
 end;
  
Function Descr(A: Sir; n: byte): Boolean; {Este sirul A ordonat descrescător ?}
 Begin
  If n=1 then Descr:= True
  else
Descr:= Descr(A, n-1) and (a[n-1] >= a[n])
 end;
  
Function Suma(A: Sir; n: byte): Telem; {Suma elementelor şirului A. Suma primelor n }
 Begin { elemente = suma primelor n-1 el.+ A[n]}
  If n = 0 then Suma := 0
  else
Suma := Suma(A, n-1) + a[n]
 end;
 Begin
  Randomize;
  n := Random(20) + 10; Genereaza(A, n);
  Tipareste(A, n); Writeln;
  TipInvers(A, n);
  Writeln('Maximul = ', Maxim(A, n));
  Writeln ('Minimul = ', Minim(A, n));
  Writeln ('Poz_Max = ', PozMax(A, n));
  Writeln ('Poz_Min = ',PozMin(A,n));
  Randomize; x := Random(100);
  If Exista(A, n, x)
  then Writeln('In sir exista el. ', x)

________________________________________________________________________________

111
Structuri de date şi algoritmi
_______________________________________________________________________________
  else Writeln('In sir nu exista el. ', x);
  If Cresc(A, n)
  then Writeln('Sirul este ordonat crescator.')
  else Writeln('Sirul nu este ordonat crescator.');
  If Descr(A, n)
  then Writeln('Sirul este ordonat descrescator.')
  else Writeln('Sirul nu este ordonat descresc.');
  Writeln('Suma elementelor sirului = ',Suma(A,3));
  Readln
 end.
  
Este posibil ca două proceduri să se apeleze una pe cealaltă. Însă s-a afirmat mai sus că o
procedură nu poate fi apelată înainte de a fi definită. Ori procedurile P1 şi P2 se apelează reciproc:
P1 apelează pe P2 şi P2 apelează pe P1. În acest caz se permite ca înainte de a apela pe P2, care încă
nu a fost declarată înaintea apelului, să se specifice că ea va fi definită ulterior. Pentru aceasta este
nevoie să se folosească directiva FORWARD. Cu ajutorul acestei directive se precizează antetul
procedurii P2 înaintea procedurii P1, iar definiţia procedurii P2 va fi făcută după procedura P1,
conform schemei de mai jos.
Procedure P2(<lista p.f.2>); Forward;
Procedure P1(<lista p.f.1>);
textul procedurii P1 în care apare apelul
  P2(<lista p.a.2>)
 end; {Sfârsitul procedurii P1}
 Procedure P2;
  textul procedurii P2  în care apare apelul
  P1(<lista p.a.1>)
 end;
  Aici, în textul procedurii P2 se folosesc parametri formali ai procedurii P2 declaraţi în
<lista p.f.2>, listă care nu mai este prezentă în antetul procedurii P2, declarată după procedura P1.
 Dăm în continuare un exemplu de program în care subprogramele care se apelează reciproc
calculează valorile funcţiilor f şi g definite prin
 f(0) = 1; g(0) = 1 ;
 g(x) = x*x*f(x-1) , pentru x >= 1;
 f(x) = (2*x-1)*g(x), pentru x >= 1.
 Programul tabelează valorile acestor funcţii pentru x = 1, 2, ..., 6.
 Exemplul 32:    

________________________________________________________________________________

112
Structuri de date şi algoritmi
_______________________________________________________________________________
 Program ApelRec2; { Programul 4.14: Proceduri recursive }
 Type nat = 0..maxint;
 Var n: nat;
  Function G(x: nat): nat; Forward ;
  Function F(x: nat): nat ;
  Begin
  If x = 0 then F := 1
else
F := (x+x-1)*G(x)
 end;
 Function G;
 Begin
  If x = 0 then G := 1
  else
G := x*x*F(x-1)
 end;
 Begin
  For n:=1 to 6 do
  Writeln('F(', n, ')=', F(n), ' G(', n, ')=', G(n))
 end.
   
  
 1.3.3.15. Tipuri de date structurate
  
Am văzut că prin termenul de dată înţelegem o valoare a unei informaţii folosită într-un
algoritm, cum ar fi numărul 1394, sau caracterul 'A', sau (1, 'Martie', 1996). O dată atomică este o
dată pe care o putem considera, cel puţin pentru un moment, o entitate nedecompozabilă. De
exemplu, numărul 1394 poate fi considerat o dată atomică memorată, scrisă pe această hârtie. Dar,
putem vedea acest întreg şi ca o dată compusă, ca o secvenţă de cifre scrise aici de la stânga spre
dreapta; în acest caz fiecare cifră este considerată atomică, însă întregul va fi o dată compusă. O
cifră la rândul ei ar putea fi considerată compusă, de exemplu, din puncte. Astfel putem alege orice
nivel la care să oprim descompunerea unei date, considerând-o apoi atomică.
 Dacă descompunem o dată, ca şi mai sus întregul 1394, atunci valorile obţinute le vom
numi elemente componente, sau simplu elemente. O dată care se compune din mai multe elemente
o numim dată structurată. O dată structurată are elemente componente şi o structură, care defineşte
modul de aranjare sau relaţiile ce se pot stabili între elemente.

________________________________________________________________________________

113
Structuri de date şi algoritmi
_______________________________________________________________________________
 În încercarea de a grupa datele după caracteristici comune a fost introdusă noţiunea de tip.
Printr-un tip de date înţelegem o mulţime de valori (de date) şi o mulţime de operaţii între aceste
valori. Dacă valorile din mulţime sunt atomice atunci avem un tip de date atomic, iar dacă valorile
sunt structurate atunci avem un tip de date structurat, sau o structură de date. Deci o structură de
date este un tip de date ale cărui valori:
 (1) pot fi descompuse într-o mulţime de elemente de date, fiecare element putând fi atomic
sau o altă structură de date;
 (2) includ o mulţime de asocieri, sau relaţii (structura) între elementele componente.
 Operaţiile unui tip de date structurat pot să acţioneze nu numai asupra valorilor tipului de
date ci şi asupra elementelor componente ale structurii de date.
 Alegerea unei reprezentări potrivite pentru obiectele de date pe care le folosim într-un
program este de importanţă majoră. Dar să definim întâi ce înţelegem prin reprezentarea datelor.
Termenul reprezentarea datelor înseamnă o alegere particulară, din mai multe care sunt posibile, a
componentelor, a tipului lor, şi a organizării acestor valori memorate într-un obiect.
 În acest capitol sunt prezentate câteva alternative disponibile în limbajul Pascal pentru
reprezentarea datelor structurate.
 
  1.3.3.15.1. Structura de tablou
  
Una din structurile de date esenţiale în programare sunt cele de tip tablou. Cel mai simplu
mod de a folosi tablourile este de a reprezenta date organizate ca şi un şir elemente. La fel, cu
ajutorul lor se pot reprezenta diferite tipuri de tabele. De exemplu, un tabel al notelor obţinute la un
examen de studenţii dintr-o grupă, ar putea arăta astfel
 Student Nota
 1 9
 2 7
 3 10
 ..........................
 25 8
  
Un astfel de tabel ar putea fi reprezentat într-un program printr-un tablou.
Un tablou constă dintr-un şir elemente, fiecare element fiind identificat printr-un indice.
Elementele sunt toate de acelaşi tip de date, care poate fi orice tip. Indicii sunt dintr-un subdomeniu
al unui tip ordinal. Caracteristicile esenţiale ale unui tablou sunt două: elementele sunt ordonate prin
şirul valorilor indicilor, respectiv elementele sunt de acelaşi tip.
 Pentru a defini reprezentarea unui tablou trebuie să precizăm două lucruri: subdomeniul
indicilor şi tipul de date al elementelor componente. Sintaxa specificării în Pascal a unei structuri de
tablou este

________________________________________________________________________________

114
Structuri de date şi algoritmi
_______________________________________________________________________________
<tablou> ::= array [<tip1>] of <tip2>
  unde parantezele drepte şi tipurile <tip1> şi <tip2> sunt obligatorii. Tipul <tip1> se
numeşte tip de indexare şi precizează valorile pe care le poate lua indicele tabloului. El poate fi
orice tip ordinal diferit de Integer, putând însă fi un subdomeniu al tipului Integer. Tipul
elementelor tabloului este <tip2>, care poate fi orice tip definit anterior: predefinit sau definit de
către utilizator, sau orice tip anonim.
 Astfel, prin  array[1..25] of integer
 se specifică un tip de tablou cu componente întregi, ale cărui elemente sunt indexate prin numere
întregi de la 1 la 25.
Prin  array['A'..'Z'] of real
 se specifică o structură de tablou cu componente reale ale cărui elemente sunt indexate prin
caractere aflate în subdomeniul 'A'..'Z' . Dacă tabloul Tab are acest tip atunci elementele sale sunt
Tab[c] pentru c în subdomeniul 'A'..'Z'.
 Un element al tabloului se referă prin scrierea numelui tabloului urmat de rangul
elementului în acest tablou (indicele) scris între parantezele drepte [ şi ]. Un element selectat al
tabloului poate fi folosit ca şi orice variabilă de tipul elementelor tabloului.
 Singura operaţie predefinită asupra tablourilor este atribuirea, A := B, care are ca efect
copierea valorilor elementelor tabloului B în elementele corespunzătoare tabloului A. Cele două
tablouri trebuie să fie de acelaşi tip.
 Un tablou este reprezentat în memoria calculatorului ca şi un şir de locaţii de memorare,
fiecărui element corespunzându-i o locaţie. Tabloul Pascal este o abstractizare care ascunde detaliile
despre cum este organizat un tablou în memoria calculatorului. În figura următoare este sugerată
reprezentarea unui tablou în memorie, fiecărei locaţii de memorare corespunzându-i un dreptunghi,
valoarea unui element fiind înscrisă în dreptunghiul corespunzător. Să notăm că indicii nu sunt
memoraţi ca şi parte a tabloului.
  Nota[1] Nota[2] Nota[3] ... Nota[25]
  9 7 10 ... 8
  Elementele unui tablou pot fi la rândul lor tablouri. Astfel, elementele unui tablou de tipul
  array[1..10] of array[1..6] of real
 sunt vectori cu 6 componente reale. Dacă T este un tablou de acest tip atunci T are 10 componente,
fiecare componentă fiind un vector cu 6 componente. De fapt este vorba de o matrice cu 10 linii şi 6
coloane. Componenta T[i] a acestui tablou va fi un vector cu 6 componente numere reale.
Selectarea elementului cu indicele j al tabloului T[i] poate fi făcută prin T[i][j].
 Menţionăm că se acceptă o prescurtare a declaraţiei de mai sus sub forma
  array[1..10, 1..6] of real
  Deşi nu este o declaraţie echivalentă cu cea de mai înainte ea permite totuşi declararea unei
matrice cu 10 linii şi 6 coloane. Diferenţa constă în faptul că folosind această declaraţie o linie a
matricei nu mai este considerată ca o entitate de sine stătătoare. Referirea la linia i prin T[i] este
greşită în acest caz, iar prin T[i, j] se referă elementul din linia i coloana j.

________________________________________________________________________________

115
Structuri de date şi algoritmi
_______________________________________________________________________________
 În continuare vom scrie un program în care se foloseşte un subprogram pentru ordonarea
crescătoare a unui şir de numere reale, pe care-l vom apela pentru a ordona mai întâi un şir dat de
numere reale x[1], x[2], ..., x[n], iar apoi pentru a ordona pătratele acestor numere. Subalgoritmul
de ordonare a acestor numere este dat în capitolul şapte, secţiunea 7.1.
 Exemplul 33:    
 
Program Tablou1; {Programul 4.14: Tablouri}
type Vector = array[1..100] of real;
var i, n: Integer;
 x : Vector;
  
procedure Tipar(n:integer; x:vector); {Tipareste, vectorul X cu n elemente}
begin
  writeln;
for i:=1 to n do Write(x[i])
 end; {Tipar}
  procedure Ordonare(n: Integer; var X: Vector); {Ordonează nedescrescator primele }
{ n componente ale vectorului X}
 var i, kod: Integer;
 t: Real;
 begin
  repeat kod := 0;
  for i:=1 to n-1 do
  if x[i] > x[i+1] then
begin
  kod := 1; t := x[i];
  x[i] := x[i+1];
  x[i+1] := t
  end {if}
  until kod = 0
 end; {Ordonare}
 begin
  Write('n = '); Readln(n);
for i:=1 to n do

________________________________________________________________________________

116
Structuri de date şi algoritmi
_______________________________________________________________________________
Read(x[i]);
  Writeln('Sirul ordonat este:');
  Ordonare(n, X);
  Tipar;
  for i:=1 to n do
x[i] := x[i]*x[i];
  Writeln('Sirul patratelor dupa ordonare:');
  Ordonare(n, X);
  Tipar(n,X)
 end.
  Dăm şi un exemplu de program în care apar matrice. Programul rezolvă următoarea
problemă:
 Exemplul 34:    
 
 Se dă o matrice patrată A şi se cere să se tipărească matricele A, A 2 , A3 şi A4.   Pentru
tipărirea unei matrice şi pentru înmulţirea a două matrice se folosesc procedurile TipMat, respectiv
ProdMat. Programul Pascal este următorul:
  
Program PuteriMat; {Programul 4.15: Puterile unei matrice}
 const n = 5;
 type Mat = array[1..n, 1..n] of Real;
 var A, B, C: Mat;
   
Procedure CitMat(n: Byte; var A: Mat); {Citeste matricea A}
 var i, j: byte; {patrata de ordinul n}
 begin
  for i:=1 to n do
  for j:=1 to n do
  begin
  Write('A(', i, ',', j, ')=');
  Readln(A[i,j])
  end;
 end;
   

________________________________________________________________________________

117
Structuri de date şi algoritmi
_______________________________________________________________________________
procedure TipMat(n: Integer; {Tipareşte mesajul s si}
 A: Mat; s: String); {matricea A de ordinul n}
 var i, j: Integer;
 begin
  writeln; writeln;
  writeln(s);
  for i:=1 to n do
begin
  Writeln;
  for j:=1 to n do
Write(A[i,j]:8:1);
  end
 end; {TipMat}
  
 Procedure ProdMat(n: Byte; A, B: Mat; var C: Mat); {n = ordinul matricelor}
  {C := A * B}
 var i, j, k: Integer;
 s : Real;
 begin
  for i:=1 to n do
  for j:=1 to n do
  begin
  s:=0;
  for k:=1 to n do
s := s+A[i,k]*B[k,j];
  C[i,j] := s
  end
 end; {ProdMat}
 
begin
  Writeln('Se tiparesc puterile unei matrice');
  Writeln('Dati matricea !);
  CitMat(n, A);
  TipMat(n, A, 'Matricea A este: ');

________________________________________________________________________________

118
Structuri de date şi algoritmi
_______________________________________________________________________________
  ProdMat(n, A, A, B);
  TipMat(n, B, 'Matricea A*A este: ');
  ProdMat(n, A, B, C);
  TipMat(n, C, 'Matricea A*A*A este: ');
  ProdMat(n, B, B, C);
  TipMat(n, C, 'Matricea A*A*A*A este:');
 end.
 
 1.3.3.15.2. Structura de date înregistrare
  
Multe obiecte din lumea reală le descriem de obicei prin enumerarea unor atribute
importante pe care le au. Atributele servesc la descrierea clasei generale de obiecte - tipul
obiectului. Un obiect particular de acel tip este definit prin asocierea unei valori particulare fiecărui
atribut. De exemplu, o notiţă dintr-o agendă telefonică personală poate referi o persoană prin
atributele Nume, Telefon şi Adresă, iar o persoană notată în agendă ar putea fi:
  Ioana Pop; 174563; G. Enescu, 25/A.
  În programare putem descrie date compuse (deci şi tipul de date corespunzător) într-un
mod asemănător. Precizarea (definirea) tipului de date se va face prin enumerarea numelor unor
atribute şi precizarea unui tip de date asociat fiecărui atribut. Un obiect particular de acest tip nou va
conţine o valoare particulară pentru fiecare atribut specificat. Un astfel de tip de date îl vom numi
tip de date înregistrare. Fiecare atribut îl numim câmp al înregistrării. Un obiect de date de tip
înregistrare va fi numit o înregistrare.
 Observăm că structura de date înregistrare modelează produsul cartezian din matematică. O
înregistrare constă dintr-un număr fix de elemente componente de diferite tipuri. Caracteristica
esenţială a unei înregistrări este că ea grupează elemente de dată de diferite tipuri într-o singură
entitate. Acest aspect contribuie la o exprimare concisă şi uşor de urmărit în programare.
 Sintaxa specificării unui tip înregistrare în limbajul Pascal este:
record <selector> : <tip> { ; <selector> : <tip> } end
  unde <selector> este un identificator sau o listă de identificatori iar <tip> este un tip
predefinit sau definit anterior şi specifică tipul selectorului din stânga sa. Selectorii care apar după
cuvântul record trebuie să fie distincţi între ei şi constituie numele dat componentelor
corespunzătoare ale produsului cartezian.
 Un astfel de tip înregistrare poate primi un nume folosind declaraţia type, prezentată în
secţiunea 4.2.5.2.  De exemplu, prin
 type Data = record
  zi : 1..31;
  luna: 1..12;
  an : 1900..1999

________________________________________________________________________________

119
Structuri de date şi algoritmi
_______________________________________________________________________________
 end;
 se defineşte un tip înregistrare cu numele Data şi care are trei componente: zi, luna, an. Se observă
că este un tip ce conţine toate datele din secolul nostru, sub forma unor triplete (z,l,a).
 Operaţiile asupra înregistrărilor sunt atribuirea înregistrărilor şi selectarea unui cîmp al unei
înregistrări. Dacă A şi B sunt două înregistrări de acelaşi tip, atunci atribuirea A := B, va avea ca
efect copierea valorilor câmpurilor înregistrării B în câmpurile înregistrării A. Pentru selectarea
unui câmp particular al unei înregistrări vom folosi notaţia
<variabila-înregistrare> . <selector>
  Astfel, pentru variabila V de tipul Data vom avea următoarele trei componente (variabile
selectate):
  V.zi, V.luna, V.an.
  Vom scrie o procedură care rezolvă problema de mai jos şi care foloseşte tipul Data, definit
mai sus. Problema este următoarea:
 Exemplul 35:    

  Să se determine vârsta unei persoane în număr de zile dacă se cunoaşte data naşterii
persoanei şi data curentă. Subalgoritmul pentru determinarea vârstei, dat sub formă de funcţie, este
următorul:
Pseudocod:
 Funcţia VARSTA(Dnas, Dazi) este:
 Fie nr := NRZIAN(Dazi);
 @Pentru (fiecare an întreg din viaţă) adună la nr
 numărul zilelor anului respectiv.
 Fie VARSTA := nr - NRZIAN(Dnas);
 sf-VARSTA
  Acest subalgoritm foloseşte funcţia NRZIAN(zi) pentru a determina a câta zi din an este o
dată curentă. Funcţia menţionată este descrisă în continuare.
 Funcţia NRZIAN(zian) este:
 Fie nr := ziua curentă a lunii;
 Dacă (nu suntem în ianuarie) atunci
  Pentru fiecare lună întreagă execută
  Fie nr := nr + numărul zilelor lunii i;
  sfpentru
 Dacă (a trecut februarie) atunci
  Dacă anuI este bisect atunci
nr:=nr+1

________________________________________________________________________________

120
Structuri de date şi algoritmi
_______________________________________________________________________________
sfdacă
 sfdacă
 sfdacă
 Fie NRZIAN:=nr;
 sf-NRZIAN
Procedura Pascal corespunzătoare se defineşte în programul următor:
 Program ApelFunctie; { Programul 4.16: Tipul inregistrare}
 const NZile: array[1..12] of Integer = (31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31);
  type Data = record
  zi : 1..31;
  luna: 1..12;
  an : 1900..1999
 end;
 var Dnas, { Data nasterii }
 Dazi: Data; { Data curenta }
  
function Bisect(a: Integer): Boolean; { Adevarata daca }
 begin { a e bisect }
  if a mod 4 = 0 then
Bisect := True
  else
Bisect := False
 end;
  
Procedure CitData(var D: Data); { Citeşte un triplet }
 begin { zi, lună, an care }
  Write('ziua='); Readln(D.zi); { reprezintă o dată }
  Write('luna='); Readln(D.luna);
  Write('anul='); Readln(D.an);
 end;
 
Function NrZiAn(V: Data): Integer; { NRZIAN := a cata zi }
 var i, nr: Integer; { in an este data V }
 begin

________________________________________________________________________________

121
Structuri de date şi algoritmi
_______________________________________________________________________________
  nr := V.zi;
  if V.luna > 1 then
  for i:=1 to V.Luna-1 do
nr := nr + NZile[i];
  if V.luna > 2 then
  if Bisect(V.an) then
nr := nr + 1;
  NrZiAn := nr
 end; {NRZIAN}
  
Function Varsta(Dnas, Dazi: Data): Integer; { Varsta := }
 var i, nr: Integer; {numarul de zile aflate}
 begin {intre datele Dnas si Dazi}
  nr := NrZiAn(Dazi);
  for i := Dnas.an to Dazi.an-1 do
begin
nr:=nr+365;
if Bisect(i) then
nr:=nr+1
  end; {for}
  Varsta := nr - NrZiAn(Dnas);
 End; {Varsta}

 Begin {Programul principal}


  Writeln('Dati data nasterii: '); CitData(Dnas);
  Writeln('Dati data curenta: '); CitData(Dazi);
  Writeln('Persoana nascuta in: ');
  Writeln('(', Dnas.zi, ',', Dnas.luna, ',', Dnas.an, ')');
  Writeln('are varsta ', VARSTA(Dnas,Dazi), ' zile.');
 End.
  
În definirea unui tip înregistrare o componentă poate fi la rândul ei de tipul înregistrare. Un
astfel de exemplu se dă în continuare:
 type Student = record

________________________________________________________________________________

122
Structuri de date şi algoritmi
_______________________________________________________________________________
  nume, prenume: array[1..8] of char ;
  datan: record
  zi : 1..31 ;
  luna: 1..12 ;
  an : 1900..1989
  end;
  note: array[1..12] of 1..10
 end;
  
În acest caz, dacă X are tipul Student atunci prin X.datan se notează tripletul
  X.datan.zi X.datan.luna X.datan.an
care reprezintă data naşterii studentului X, iar X.nume este numele acestui student.

 xxxxxxx Tipul referinţă

În multe aplicaţii practice care folosesc prelucrări de date, nu este posibil să cunoaştem a
priori dimensionalitatea datelor. Din această cauză, utilizarea unui tablou pentru a stoca aceste date
este ineficientă, deoarece fie facem risipă de memorie printr-o alocare statică acoperitoare, fie sub
dimensionăm tabloul pentru stocarea datelor, ceea ce poate duce la fenomene de depăşire a zonei de
memorie alocată datelor, acest fenomen fiind un fenomen sever care poate duce la blocarea
execuţiei programului şi chiar la blocarea sistemului de calcul. Limbajul de programare Turbo
Pascal stabileşte dimensiunea de memorie alocată datelor de la nivelul codului sursă, care, compilat
şi link-editat se transformă în cod executabil. Pe parcursul execuţiei nu se pot modifica dinamic
alocările de date declarate la nivelul codului sursă.
De aceea, pentru a rezolva problema unei alocări dinamice a memoriei trebuie să utilizăm un
tip de dată special, numit tip referinţă sau pointer şi o serie de proceduri speciale care să aloce
memorie sau să elibereze zona de memorie folosită şi care nu mai este necesară.
Tipul de date referinţă (pointer) va conţine adresa unui anumit tip de dată. Declaraţia de tip
este:
nume_dată = ^tip;
unde nume_dată este numele noului tip de dată referinţă, iar tip reprezintă tipul datelor referite de
către datele de tip nume_dată.
Declaraţia de mai sus este o declaraţie generică de tip, neavând ca efect rezervarea propriu-
zisă de memorie. Pentru a realiza efectiv alocarea unei zone de memorie de dimensiunea dată de
tipul de dată tip, va trebui să folosim procedura NEW. Apelul procedurii se face astfel:
NEW(variabilă _referinţă);
Unde variabilă_referinţă este o variabilă numită variabilă dinamică (Cristea, et al. [1992]).
Procedura NEW realizează alocarea dinamică a memoriei heap pe parcursul execuţiei programului.

________________________________________________________________________________

123
Structuri de date şi algoritmi
_______________________________________________________________________________
În urma apelării procedurii NEW, variabilei dinamice variabilă_referinţă i se va atribui o adresă de
memorie de dimensiune egală cu zona de memorie ocupată da o dată de tipul referit.
Asupra variabilelor dinamice pot fi efectuate orice operaţii permise de tipul acestora.
Variabilele dinamice pot primi valori şi prin operaţii de atribuire, pot apare în expresii relaţionale
care utilizează operatorii relaţionali = şi <>.
Limbajul Turbo Pascal permite utilizarea tipului pre-definit pointer. Acest tip de dată este
compatibil cu orice tip referinţă definit de utilizator. Unei variabile de tip pointer îi poate fi atribuită
valoarea adresei oricărei variabile, proceduri sau funcţii definite de utilizator. O astfel de adresă
poate fi obţinută prin utilizarea operatorului @, astfel:
@variabilă;
Dacă o variabilă de referinţă are valoarea NIL înseamnă că variabila respectivă conţine o
adresă imposibilă, care nu există în zona de memorie.
Eliberarea efectivă a spaţiului de memorie ocupat se face cu ajutorul procedurii DISPOSE,
după următoarea regulă:
DISPOSE(variabilă _referinţă);
Unde adresa zonei de memorie ce se va elibera este conţinută în variabilă_referinţă. Dimensiunea
zonei de memorie eliberată este dată de dimensiunea tipului de dată referit de variabilă_referinţă.
Dacă dorim să alocăm sau să eliberăm în mod explicit un anumit număr de octeţi putem
folosi procedurile GetMeM şi FreeMem.
   

1.3.3.16. Probleme propuse


  
1. Se dă un şir de numere naturale al cărui sfârşit este marcat prin valoarea 0. Spunem că
două numere naturale sunt asemenea dacă scrierile lor în baza 10 se face cu aceleaşi cifre (Ex.
36668 şi 8363). Să se găsească secvenţa de numere consecutive asemenea de lungime maximă din
şirul dat şi frecvenţele cifrelor în scrierea numerelor date.
  2. Se citesc mai multe şiruri de numere naturale nenule fiecare şir terminându-se la citirea
valorii zero. Pentru fiecare şir să se tipărească secvenţa de elemente consecutive de lungime
maximă formată din numere prime. La sfârşit se vor tipări şi toate numerele prime întâlnite. Citirea
se va încheia când şirul citit are 0 termeni (altfel spus la citirea a doi de zero consecutivi)
  3. Să se scrie un program pentru determinarea reuniunii unor mulţimi de numere întregi
pozitive. O mulţime se dă întotdeauna începând pe un rând nou, iar sfârşitul elementelor sale este
marcat prin numărul 0. Mulţimile se termină la întâlnirea mulţimii vide. Se va tipări reuniunea
acestor mulţimi şi mulţimea cu cele mai multe elemente, tipărirea elementelor unei mulţimi se va
face în ordinea crescătoare a valorilor elementelor.
  4. Fiind date mai multe polinoame cu coeficienţi reali să se determine suma lor şi
polinomul de grad maxim. Un polinom se dă fie prin monoamele sale, fie prin grad şi coeficienţi.

________________________________________________________________________________

124
Structuri de date şi algoritmi
_______________________________________________________________________________
Citirea polinoamelor se va încheia la introducerea “gradului” negativ.
  5. Participanţii la un concurs sunt identificaţi printr-un număr strict pozitiv (număr
legitimaţie), şi obţin un punctaj (număr întreg). Să se scrie un program Pascal care citeşte rezultatele
obţinute la acest examen şi tipăreşte toţi candidaţii ordonaţi crescător după numărul legitimaţiilor,
cu punctajul corespunzător şi primii m (m > 0) candidaţi în ordinea descrescătoare a punctajelor
(dacă doi dintre aceştia au punctaje egale atunci ei se diferenţiază în ordine crescătoare după
numărul legitimaţiei).
  6. Să se scrie un program care citeşte câte un polinom cu coeficienţi întregi (introdus fie
prin grad şi coeficienţi fie prin monoamele sale), până la întâlnirea unui polinom de grad 0. Pentru
fiecare polinom P citit, determină rădăcinile naturale ale lui P şi le tipăreşte sub forma:
  Rădăcinile naturale ale polinomului P cu coeficienţii
 . . . 
sunt ...
  7. Se dau un polinom P (grad + şirul coeficienţilor), şi un şir de triplete (a, b, e) pentru care
P(a)*P(b) <= 0 si e>0, şirul terminându-se cu un triplet în care e<0. Să se găsească rădăcina ecuaţiei
P(x)=0 din intervalul [a,b], prin metoda înjumătăţirii, cu eroarea e, şi să se tipărească:
  "În intervalul [a,b] polinomul are rădăcina ..., aproximată cu precizia ...(valoarea lui e)".
  pentru fiecare triplet, iar la sfârşit să se tipărească toate rădăcinile pozitive găsite.
 
8. Să se scrie un program care creează matricea pătratică de ordinul n (dat), formată din
scrierea cifrelor semnificative ale numerelor naturale în ordinea liniilor şi tipăreşte matricele A,
A2, ... , Am.
  9. Fiind date două numere naturale să se scrie un program Pascal pentru a determina cel
mai mic şi cel mai mare dintre ele. Un număr poate avea cel mult 100 cifre. Numerele se introduc
de la tastatură prin cifrele lor (reprezentare în baza 10).
  10. Să se scrie un program Pascal care citeşte mai multe matrice şi tipăreşte matricea care
are determinantul cel mai mare. Pentru fiecare matrice programul citeşte ordinul matricei şi
elementele în ordinea liniilor. Execuţia programului se încheie când se introduce 0 pentru ordinul
unei matrice.
  11. Să se scrie un program care citeşte două numere naturale nenule a şi b si tipăreşte restul
împărţirii lui a la b. Numerele pot avea cel mult 50 cifre şi sunt reprezentate în baza 10.
  12. Să se scrie un program care tipăreşte rădăcinile raţionale ale unui polinom cu
coeficienţi întregi (dacă există), polinom dat prin grad şi coeficienţii săi.
  

________________________________________________________________________________

125
Structuri de date şi algoritmi
_______________________________________________________________________________

II. Structuri de date

II.1. Lista liniară simplu înlănţuită


Multe din informaţiile prelucrate de calculator sunt organizate ca liste: elementele unui
vector, şirul format de numele din cartea de telefon (lista abonaţilor), lista candidaţilor la admitere
într-o facultate, lista materialelor necesare dintr-o secţie industrială, lista locatarilor unui imobil . De
aceea lista ocupă un loc important între structurile de date uzuale. În cele ce urmează, prezentăm
câteva elemente de bază referitoare la liste, modalităţile de reprezentare a listelor în calculator şi
unele aplicaţii semnificative.

________________________________________________________________________________

126
Structuri de date şi algoritmi
_______________________________________________________________________________

II.1.1. Definiţii

Prin listă înţelegem un şir (eventual vid) de elemente aparţinând unei mulţimi de date (Tomescu
[1997]). Memorarea listelor liniare se poate face în mai multe moduri, în continuare prezentându-se
unele dintre ele.
Alocarea secvenţială (statică) constă în a memora elementele listei în locaţii succesive de
memorie, conform ordinei acestor elemente în listă; în acelaşi timp se memorează adresa de bază
(adresa primului element al listei). Un exemplu tipic este cel în care elementele listei sunt memorate
într-un vector, adresa de bază fiind adresa primului element al vectorului.

Alocarea înlănţuită (dinamică) presupune că fiecare element al listei liniare este înlocuit cu o
celulă formată din două părţi: o parte conţine o informaţie specifică al elementului şi o parte cu
informaţia de legătură, care conţine adresa celulei corespunzător următorului element.

info successor

nod
Fig.2.1. Structura unui nod al listei simplu înlănţuite.

Ca şi la alocarea secvenţială mai trebuie memorată o adresă de bază; în plus, partea de


legătură a ultimei celule primeşte o valoare ce nu poate desemna o legătură.
Utilizăm forma de alocare dinamică, ceea ce înseamnă că în timpul rulării programului, în
funcţie de necesităţi, se alocă memorie suplimentară sau se renunţă la ea. Pentru alocarea dinamică
utilizăm tipul de date referinţă. Se consideră secvenţa de program cu implementarea elementelor,
element oarecare al listei care este format din informaţia utilă (info) şi un pointer care se referă la
următorul element al listei :

O listă liniară simplu înlănţuită este o structură de forma:

Fig.2.2. Structura unei liste simplu înlănţuite.

 unde semnificaţia notaţiilor folosite este următoarea:

 adr1, adr2, ..., adrn reprezintă adresele din memorie ale celor n înregistrări;
 inf1, inf2,  ..., infn reprezintă informaţiile utile din cele n înregistrări

________________________________________________________________________________

127
Structuri de date şi algoritmi
_______________________________________________________________________________
Implementarea unui element al acestei structuri se face în felul următor în limbajul Pascal:

Type
adresa = ^ nod ;
nod=record
info : tip ;
succesor : adresa ;
end;

Var
List : adresa ;

Numele de "simplu înlănţuită " provine din faptul că fiecare element al listei conţine o
singură adresă şi anume adresa elementului următor din listă. Aici avem o excepţie pentru ultimul
element, care are câmpul de adresă cuvântul cheie NIL ( semnificând "nici o adresa" ).

II.1.2. Operaţii specifice listei

Conţinutul listei se poate modifica prin următoarele operaţii:


a. iniţializarea unei liste ca o listă vidă;
b. inserarea de noi elemente în orice loc din listă;
c. căutarea unor elemente în orice poziţie din listă;
d. ştergerea de elemente din orice poziţie a listei;
e. modificarea unui element dintr-o poziţie dată;

Alte operaţii sunt cele de caracterizare. Acestea nu modifică structura listelor, ci furnizeză
informaţii despre ele . Dintre operaţiile de caracterizare vom considera în continuare:
 determinarea lungimii listei ( numărul de elemente ) ;
 localizarea elementului din listă care îndeplineşte o anumită condiţie ;

a. Algoritmul Creare (Iniţializare)

Operaţia realizează iniţializarea listei ca o listă vidă, adică o listă fără elemente:
.
Procedura Creare
List = NIL
End;

b. Algoritmul Inserare

b1. Inserare_Început : inserăm un element la începutul listei;

- alocarea de spaţiu elementului nou;


- modificare de legături ( a şi b );

________________________________________________________________________________

128
Structuri de date şi algoritmi
_______________________________________________________________________________

Fig.2.3. Operaţia de inserare la începutul unei liste simplu înlănţuite.

Procedure Inserare_Început ( List , info_nou )


Begin
new(adr);
adr^.succesor = List;
List = adr;
adr^.info = info_nou;
end.

b2. Inserare_Sfârşit : inserăm un element la sfârşitul listei ;

 Fig.2.4. Operaţia de inserare la sfârşitul unei liste simplu înlănţuite.

Secvenţa de operaţii necesară :


- găsirea adresei ultimului element ;
 - alocarea spaţiului elementului nou ;
 - a ( ultimul devine penultimul ) ;
 - b ( noul element devine ultimul ) ;
 - introducerea de informaţii ;

 Procedure Inserare_Sfârşit ( List , info_nou )


 Var
  ultim : adresa;
  Begin
  ultim = List;

________________________________________________________________________________

129
Structuri de date şi algoritmi
_______________________________________________________________________________
   while ultim^.succesor <> NIL do
   ultim = ultim^.succesor;
   nou (adr);
   ultim^. succesor = adr;
   adr^.succesor = NIL;
   adr^.info = info_nou;
  end;

b3. Inserare_Mijloc : inserăm un element nou după un element dat din listă, a cărui adresă este
numită după;

 Fig.2.5. Operaţia de inserare în interiorul unei liste simplu înlănţuite.


Secvenţa de operaţii necesară:

- alocare de spaţiu ;
- succesorul noului element devine elementul listei ( pas a. ) ;
- succesorul elementului ' după ' devine noul element (pas b. ) ;
- introducem informaţia nouă ;

Procedura Inserare_Interior ( List , după , info_nou )


Begin
           new(adr);
               adr^.succesor = după^.succesor;
               după^.succesor = adr;
               adr^.info = info_nou;
 End;

c. Algoritmul căutare

Algoritmul caută în lista simplu înlănţuită nodul care conţine informaţia info_căutat. Dacă
ea există, funcţia de căutare va returna adresa nodului ce conţine informaţia căutată. Dacă
informaţia nu există, atunci funcţia va returna o adresă vidă (NIL).

Function Căutare ( List , info_căutat )


Var

________________________________________________________________________________

130
Structuri de date şi algoritmi
_______________________________________________________________________________
adr: adresa;
Begin
adr = List;
while adr <> NIL do
if adr^.info = info_căutat
then begin
căutare = adr;
Exit;
end
else
adr = adr^.succesor;
Căutare = NIL;
End.

d. Algoritmul Ştergere

d1. Ştergere_Început : ştergerea elementului de la începutul listei;

Secvenţa de operaţii necesară:


- salvarea adresei de eliberare;
- primul element devine succesorul fostului prim element;
- eliberarea memoriei;

 Fig.2.6. Operaţia de ştergere la începutul unei liste simplu înlănţuite.

Procedure Ştergere_Început ( Var List )


Var liber : adresa;
Begin
liber = List;
List = List^.succesor;
DISPOSE ( liber );
End;

d2. Ştergere_Sfârşit: ştergerea ultimului element din listă;


 

________________________________________________________________________________

131
Structuri de date şi algoritmi
_______________________________________________________________________________

 Fig.2.7. Operaţia de ştergere la sfârşitul unei liste simplu înlănţuite.

Secvenţa de operaţii necesară:


- găsirea adresei penultimului element din listă ;
- salvarea adresei elementului de şters ;
- succesorul penultimului element devine NIL;
- eliberarea memoriei ;

 Procedure Ştergerea_Sfârşit ( [ Var ] List )


 Var penultim, liber : adresa;
  Begin
                penultim = List;
            while penultim^.succesor^.succesor <> NIL do
                  penultim = penultim^.succesor;
                  liber = penultim^. succesor;
                  penultim^.succesor = NIL;
             DISPOSE ( liber );
   End;

d3. Ştergere_Mijloc: ştergerea unui element după un element dat din listă, a cărui adresă este
numită după;

 Fig.2.8. Operaţia de ştergere în interiorul unei liste simplu înlănţuite.

Secvenţa de operaţii necesară:

________________________________________________________________________________

132
Structuri de date şi algoritmi
_______________________________________________________________________________
 - salvarea adresei de eliberat;
 - succesorul lui 'după' devine succesorul succesorului acestuia ( pas a.);
 - eliberarea memoriei;

Procedure Ştergere_Mijloc ( List , după )


VAR liber : adresa;
 Begin
         liber = după^.succesor;
         după^.succesor = după^.succesor^.succesor;
         DISPOSE ( liber );
  End;

e. Algoritmul modificare

Algoritmul apelează funcţia de căutare în lista simplu înlănţuită, căutând nodul care conţine
informaţia nrcăutat. Dacă ea există, se modifică informaţia existentă în acel nod cu informaţia
nrmodificat. Dacă informaţia nu există, atunci procedura va returna o adresă vidă (NIL).

Procedure MODIFICARE ( List , nrcăutat , nrmodificat )


Begin
         adr:= CĂUTARE ( List , nrcăutat );
if adr = NIL then
            write ( ' Nu exista informaţia căutată !!! ' )
         else
begin
            write (' Exista informaţia căutată !!! ')
            adr^.info:=nrmodificat
      end;
End;

II.1.3. Probleme propuse

1.) Să se creeze o listă liniară simplu înlănţuită cu trei noduri, să se iniţializeze elementele listei.

2.) Definiţi şi iniţializaţi o listă simplu înlănţuită. Efectuaţi ştergerea temporară a unui element.
Afişaţi lista modificată, reactivaţi elementul.

3.) Se consideră o listă ce conţine elemente care au ca informaţie utilă: cod produs, cantitate, preţ.
Scrieţi şi apelaţi funcţia care calculează total valoare pentru materialele existente în listă.

4.) Să se scrie procedurile care realizează diferite modalităţi de tipărire a unei liste simplu înlănţuite.

________________________________________________________________________________

133
Structuri de date şi algoritmi
_______________________________________________________________________________
5.) Să se scrie programul pentru adunarea a două matrice rare, memorate sub forma de liste liniare
simplu înlănţuite.

6.) Să se construiască o listă liniară simplu înlănţuită şi apoi să se scrie funcţiile de inserare a unui
element în listă la început de listă, la sfârşitul listei şi în interiorul listei.

7.) Scrieţi funcţia care efectuează copierea unei liste nerecursiv, cu eliminarea elementelor ce au ca
informaţie utilă o valoare mai mică decât un nivel specificat de un parametru.

8.) Modelaţi şi programaţi servirea " peste rând " la distribuirea unor produse deficitare, folosind o
structură de date de tip lista liniară simplu înlănţuită.

9.) Număraţi elementele pozitive, negative şi nule ale unei liste într-o funcţie care după apelare,
returnează un pointer la un vector cu trei componente, se stochează rezultatele parcurgerii listei.

10.) Scrieţi programul pascal pentru inserarea unui element într-o listă simplu înlănţuită ale cărei
elemente sunt deja sortate crescător, pe poziţia care păstrează lista ordonată.

11.) Să se genereze o listă simplă înlănţuită care să conţină culoarea, marca şi numărul de
înmatriculare al unor autoturisme aflate la o coadă la benzină. Să se mute toate maşinile de culoare
albă în faţă, iar cele roşii în spate. Să se elimine din coadă maşinile cu număr < 1000. Să se
determine numărul maşinilor cu marca "DACIA".

12.) Fiind dată o matrice rară, să se alcătuiască o listă înlănţuită în care elementele au forma ( linia,
coloana, valoarea) şi valoarea diferit de 0.

 Să se listeze elementele alate pe diagonala principală (toate).


 Să se determine elementul minim de pe diagonala principală (toate).
 Să se elimine elementul de valoare maximă.

 13.) Să se memoreze o matrice rară sub forma unei liste înlănţuite, în care elementele matricei vor
fi reţinute în ordinea lor naturală (pe linii), indiferent de ordinea introducerii lor. Să se tipărească
această matrice în forma normală (toate elementele, inclusiv cele nule).

II.2. Lista dublu înlănţuită


II.2.1. Definiţii

Observăm că odată pornită căutarea într-o listă, nu ne mai putem întoarce înapoi doar dacă o
luăm de la început. Acest lucru nu este deloc convenabil, în afară de situaţia în care nu avem de ce
să ne întoarcem înapoi. Există operaţii precum: inserarea înaintea elementului curent din listă sau

________________________________________________________________________________

134
Structuri de date şi algoritmi
_______________________________________________________________________________
ştergerea elementului curent, care presupun referirea la elementul dinaintea elementului curent -
operaţie care se realizează cu dificultate. Mul mai comode sunt listele de forma prezentată în figura
următoare, liste dublu înlănţuite cu posibilitatea de navigare dublă (Tomescu, I., [1997]).
Memorarea listelor liniare se poate face în mai multe moduri, în continuare prezentându-se
unele dintre ele.
Alocarea secvenţială (statică) constă în a memora elementele listei în locaţii succesive de
memorie, conform ordinei acestor elemente în listă; în acelaşi timp se memorează adresa de bază
(adresa primului element al listei). Un exemplu tipic este cel în care elementele listei sunt memorate
într-un vector, adresa de bază fiind adresa primului element al vectorului.
Alocarea înlănţuită (dinamică) presupune că fiecare element al listei liniare este înlocuit cu o
celulă formată din două părţi: o parte conţine o informaţie specifică al elementului şi o parte cu
informaţia de legătură, care conţine adresa celulei corespunzător următorului element.
Ca şi la alocarea secvenţială mai trebuie memorată o adresă de bază; în plus, partea de
legătură a ultimei celule primeşte o valoare ce nu poate desemna o legătură.

     O listă liniară dublu înlănţuită este o structură de forma:

 Fig.2.9. Operaţia de ştergere în interiorul unei liste simplu înlănţuite.

Lista dublu înlănţuită este un tip special de listă, care necesită un spaţiu mare de memorie
mai mare, în care însă în afară de informaţia utilă, mai conţine şi informaţia de legătură a fiecărui
element, care cuprinde atât adresa elementului precedent în listă (predecesor), cât şi adresa
elementului următor (succesor). Dacă elementul succesor sau cel predecesor nu există, pointerii
respectivi au valoarea NIL.
Elementul care are adresa succesorului NIL se va numi primul element al listei în raport cu
legătura succesor şi ultim element în raport cu legătura predecesor. Elementul care are adresa
predecesorului NIL se va numi ultim element al listei în raport cu legătura succesor şi primul
element în raport cu legătura predecesor.
Utilizăm forma de alocare dinamică, ceea ce înseamnă că în timpul rulării programului, în
funcţie de necesităţi, se alocă memorie suplimentară sau se renunţă la ea. Pentru alocarea dinamică
utilizăm tipul de date referinţă. Se consideră secvenţa de program cu implementarea elementelor,
element oarecare al listei care este format din informaţia utilă (info)
şi un pointer care se referă la următorul element al listei:
 
Type

________________________________________________________________________________

135
Structuri de date şi algoritmi
_______________________________________________________________________________
adresa = ^ nod;
  nod=record
  info : tip;
  succesor : adresa;
  predecesor : adresa;
end;
Var
  List : adresa;
Observaţie 2.1.: din păcate cei doi pointeri din fiecare element al listei dublu înlănţuite consumă
destul de multă memorie, şi de aceea listele simplu înlănţuite sunt mai des folosite.

II.2.2. Operaţii specifice listei

Structura şi conţinutul listei se poate modifica prin următoarele operaţii:


a. iniţializarea unei liste ca o listă vidă;
b. inserarea de noi elemente în orice loc din listă;
c. căutarea unor elemente în orice poziţie din listă;
d. ştergerea de elemente din orice poziţie a listei;
e. modificarea unui element dintr-o poziţie dată ;
    Alte operaţii sunt cele de caracterizare. Acestea nu modifică structura listelor, ci
furnizează informaţii despre ele. Dintre operaţiile de caracterizare vom considera în continuare:
- determinarea lungimii listei ( numărul de elemente );
- localizarea elementului din listă care îndeplineşte o anumită condiţie;
Setul de operaţii, considerat în cele ce urmează, utilizează poziţia curentă în listă, definită ca
poziţia elementului accesibil la un moment dat. Operaţiile de prelucrare se referă la elementul din
poziţia curentă. În afara acestora, sunt definite operaţii care asigură modificarea poziţiei curente.

   Să analizăm fiecare operaţie în parte:


a. Algoritmul creare: are loc iniţializarea listei ca o listă vidă, adică o listă fără elemente;

 
Procedura Creare
    List = NIL;
End;

b. Algoritmul Inserare

b1. Inserare_Început: inserăm un element la începutul listei ;

________________________________________________________________________________

136
Structuri de date şi algoritmi
_______________________________________________________________________________

 
 Fig.2.10. Operaţia de inserare la începutul unei liste dublu înlănţuite.

Secvenţa de operaţii necesară:


- alocarea de spaţiu elementului nou ;
 - predecesorul elementului nou devine NIL ;
 - se face legătura dintre succesorul noului element şi List ;
 - predecesorul lui List devine noul element ;
 - List preia adresa noului element ;
 - introducerea informaţiei ;
 
Procedure Inserare_Început ( Var List , info_nou )
    new(adr );
  adr^.predecesor = NIL;
    adr^.succesor = List ;
    List^.predecesor = adr ;
    List = adr ;
    adr^.info = info_nou ;
End;

b2. Inserare_Sfârşit : inserăm un element la sfărşitul listei ;

 Fig.2.11. Operaţia de inserare la sfârşitul unei liste dublu înlănţuite.

________________________________________________________________________________

137
Structuri de date şi algoritmi
_______________________________________________________________________________
Secvenţa de operaţii necesară:
 - găsirea adresei ultimului element ;
 - alocarea spaţiului elementului nou ;
 - predecesorul noului element devine ultimul ;
 - succesorul ultimului element devine noul element ;
 - introducerea de informaţii ;
 - modificarea adresei ultimului element ;
 
Procedure Inserare_Sfârşit ( Var List , info_nou )
Var
   ultim : adresa ;
Begin
  ultim = List;
  while ultim^.succesor <> NIL do
  ultim = ultim^.succesor
  nou (adr);
  adr^.predecesor = ultim;
  ultim^. succesor = adr;
  adr^.succesor = NIL;
  adr^.info = info_nou;
  ultim = adr;
End;
b3. Inserare_Mijloc: inserăm un element nou după un element dat din listă, a cărui adresă este
numită după;

 
 Fig.2.11. Operaţia de inserare în interiorul unei liste dublu înlănţuite.

Secvenţa de operaţii necesară:


- alocare de spaţiu ;
 - predecesorul noului element devine elementul listei ;
 - predecesorul succesorului lui 'după' devine noul element ;
 - succesorul elementului nou devine succesorul elementului 'după';
 - introducem informaţia nouă ;
 

________________________________________________________________________________

138
Structuri de date şi algoritmi
_______________________________________________________________________________
Procedura Inserare_Mijloc ( Var List , după , info_nou )
     new (adr) ;
     adr^.predecesor = după ;
     după^.succesor^.predecesor = adr ;
     adr^.succesor = după^.succesor ;
     adr^.info = info_nou ;
End;

c. Algoritmul Căutare:
Algoritmul caută în lista dublu înlănţuită nodul care conţine informaţia info_căutat. Dacă ea
există, funcţia de căutare va returna adresa nodului ce conţine informaţia căutată. Dacă informaţia
nu există, atunci funcţia va returna o adresă vidă (NIL). Căutarea se poate face bidirecţional, pe
legătura succesor sau pe legătura predecesor.
Function Căutare ( List , info_căutat ): adresa
Var
     adr : adresa ;
Begin
adr:=List;
while adr < > NIL do
    if adr^.info = nrcautat then
     begin
       Căutare = adr ;
       exit;
     end
else
adr = adr^.succesor ;
Căutare = NIL ;
 End;

d. Algoritmul Ştergere

d.1 Ştergere_Început : ştergerea elementului de la începutul listei ;

 Fig.2.12. Operaţia de ştergere la începutul unei liste dublu înlănţuite.

________________________________________________________________________________

139
Structuri de date şi algoritmi
_______________________________________________________________________________

 Secvenţa de operaţii necesară:


-  salvarea adresei de eliberare ;
- predecesorul succesorului lui List să devină NIL ;
 - List devine succesorul primului element ;
 - predecesorul lui List va devine NIL ;
 - eliberarea memoriei ;
 
Procedura Ştergere_Început ( Var List )
Var
    liber : adresa ;
 Begin
if List = NIL then
        write(" Lista este vidă ")
   else
     liber = List ;
     List = List^.succesor ;
      List^.predecesor = NIL ;
      DISPOSE ( liber ) ;
End;

d.2 Ştergere_Sfârşit : ştergerea ultimului element din listă ;

 
 Fig.2.13. Operaţia de ştergere la sfârşitul unei liste dublu înlănţuite.
 
Secvenţa de operaţii necesară:
 - găsirea adresei ultimului element din listă ;
 - salvarea adresei elementului de şters ;
 - succesorul predecesorului lui ultim devine NIL;
 - eliberarea memoriei ;
 
Procedure Ştergerea_Sfârşit ( Var List )
Var
    ultim , liber : adresa ;

________________________________________________________________________________

140
Structuri de date şi algoritmi
_______________________________________________________________________________
  Begin
    ultim = List ;
    while ultim^.succesor < > NIL do
       ultim = ultim^.succesor ;
     liber = ultim ;
     ultim^.predecesor^.succesor = NIL ;
    DISPOSE( liber ) ;
End;

d.3 Ştergere_Mijloc : ştergerea unui element după un element dat din listă, a cărui adresă este
numită după;
;

  Fig.2.14. Operaţia de ştergere la sfârşitul unei liste dublu înlănţuite.

Secvenţa de operaţii necesară:


- salvarea adresei de eliberat ;
 - predecesorul succesorului 'după' devine predecesorul lui 'după' ;
 - succesorul predecesorul 'după' devine succesorul lui 'după' ;
 - eliberarea memoriei ;

Procedure Ştergere_Mijloc ( Var List, după )


Var
    liber : adresa ;
Begin
    liber = după ;
     după^.succesor^.predecesor = după^.succesor ;
     după^.predecesor^.succesor = după^.succesor ;
    DISPOSE ( liber );
 End;
e. Modificare (Actualizare) :
Algoritmul apelează funcţia de căutare în lista dublu înlănţuită, căutând nodul care conţine
informaţia nrcăutat. Dacă ea există, se modifică informaţia existentă în acel nod cu informaţia

________________________________________________________________________________

141
Structuri de date şi algoritmi
_______________________________________________________________________________
nrmodificat. Dacă informaţia nu există, atunci procedura va returna o adresă vidă (NIL). Căutarea
informaţiei de modificat se poate face pe ambele legături, predecesor şi succesor.

Procedure MODIFICARE ( List , nrcăutat , nrmodificat )


Begin
adr := CĂUTARE ( List , nrcăutat );
if adr = NIL then
    write ( ' Nu exista informaţia căutată !!! ' ) ;
  else
begin
    write (' Exista informaţia căutată !!! ') ;
    adr^.info := nrmodificat ;
end;
End;

Probleme propuse
1.) Să se construiască o listă dublu înlănţuită şi să se scrie elementele acesteia în ambele sensuri în
funcţie de opţiune. Să se şteargă apoi un element al listei şi să tipărească lista rezultată.

2.) Să se construiască o listă dublu înlănţuită cu 5 elemente citite dintr-un vector. Să se scrie funcţia
recursivă de tipărire a listei în ambele sensuri. Să se scrie funcţia de ştergere a unui element din
listă.

3.) Scrieţi şi apelaţi funcţia care verifică dacă matricea rară pătratică ale cărei elemente sunt stocate
într-o listă conţine numai elemente ale diagonalei principale.

4.) Să se construiască un arbore genealogic (folosind o listă înlănţuită) utilizând pentru fiecare
membru introdus legăturile de tip : părinte, copil (primul), frate, soţ_soţie. Să se găsească, pentru
membru dat, toţi verii de grad I (copiii fraţilor părinţilor).

5.) Să se creeze o listă înlănţuită cu articole cu structura : Nume_student, An, Grupă, Medie. Să se
listeze toţi studenţii cu media > 9 iar apoi să se elimine din listă toţi studenţii din anul 5.

6.) Fiind dată o matrice ale cărei elementele reprezintă localităţi, să se construiască o listă, în care
pentru fiecare localitate se vor memora legăturile cu localităţile învecinate în cele patru direcţii (N,
E, S, V). Să se parcurgă un traseu, plecând de la o anumită localitate de start, urmând un şir de
direcţii ( Se vor ignora direcţiile inaccesibile).

7.) O matrice rară poate fi memorată printr-o listă dublu înlănţuită având ca elemente elementele
nenule ale matricei respective. Fiecare linie sau coloană va fi reprezentată printr-o listă dublu
înlănţuita Se cere un program care să permită memorarea unei astfel de matrice rare şi efectuarea
de operaţii de citire, adunare, scădere sau înmulţire şi de asemenea tipărirea unei matrice.

________________________________________________________________________________

142
Structuri de date şi algoritmi
_______________________________________________________________________________

8.) Să se construiască o listă înlănţuită cu componentele de forma : X i, Leg_Urm, Leg_Prim, unde


Xi   N şi Xi < Xj pentru i < j, Leg_Urm este adresa următorului element din listă, iar Leg_Prim
este adresa ultimului număr prim dinaintea lui Xi. Să se listeze toate numerele Xi în ordine
crescătoare, apoi doar numerele prime din listă în ordine descrescătoare.
 
9.) Se dă o listă înlănţuită. Se cere o procedură care să permită adăugarea unui element pe poziţia n;
(dacă lista conţine mai puţine de n-1 elemente, adăugarea se va efectua în coada listei) şi o
procedură care să efectueze ştergerea elementului de pe poziţia n (dacă lista conţine mai puţin de n
elemente, se va şterge ultimul element).

10.) Se citeşte un şir X1, X2, ..., XN de numere naturale distincte şi un număr natural b > 1, în funcţie
de care şirul dat se poate împărţi în b subşiruri astfel: Xi  Sk dacă restul împărţirii lui Xi la b este k.
Să se construiască o listă de forma: X i, Leg_Sir, Leg_Elem, unde Leg_Sir este adresa primului
element din următorul subşir, iar Leg_Elem este adresa următorului element din acelaşi subşir.
Pentru un număr k dat, să se listeze subşirul Sk.

11.) Să se depună numerele reale X1, X2, ..., XN  într-o listă, apoi să se ordoneze crescător această
listă folosind o procedură care aduce elementul minim pe prima poziţie şi o procedură care mută
elementul  pe ultima poziţie.

12.) Se dă o mulţime de maşini caracterizate prin culoare, marcă, număr. Să se construiască o listă
cu toate maşinile având acea culoare. Să se listeze numerele de înmatriculare ale maşinilor care au o
culoare şi o marcă dată.

13.) Fiind dată o listă cu numerele naturale X1, X2, ..., Xn, să se elimine elementele care se repetă,
apoi după fiecare element să se adauge divizorii săi.

14.) Se dau numerele reale a, X1, X2, ..., Xn. Să se construiască o listă care să poată fi parcursă în
ambele sensuri, apoi să se elimine din listă primul şi ultimul element egal cu a.

II.3. Stiva (stack)


II.3.1. Generalităţi
O stivă (în engleză stack), este o listă lineară de un tip special, în care adăugarea sau
scoaterea unui element se face la un singur capăt al listei, numit vârful stivei (Aho et al. [1987]).
Elementul introdus primul în stivă poartă numele de bază a stivei. O stivă fără nici un element se
numeşte stivă vidă sau stivă goală. Ca orice listă liniară, o stivă poate fi realizată practic
(implementată) fie folosind o reprezentare statică (secvenţială), fie prin reprezentare dinamică
(înlănţuită). Indiferent de reprezentare, implementarea presupune alocarea unui spaţiu limitat, dacă
el este în întregime ocupat, spunem că avem stiva este plină.
Stiva se poate asemăna unui vraf de farfurii aşezat pe o masă, modalitatea cea mai comodă

________________________________________________________________________________

143
Structuri de date şi algoritmi
_______________________________________________________________________________
de a pune o farfurie fiind în vârful stivei, tot de aici fiind cel mai simplu să se ia o farfurie. Datorită
locului unde se acţionează asupra stivei, operaţiile de inserare şi extragere, aceste structuri se mai
numesc de tip LIFO (Last In - First Out), adică ultimul sosit - primul ieşit.

II.3.2. Stiva – alocare statică

În reprezentarea secvenţială, elementele stivei vor fi memorate într-un vector (tabel) numit
Stiva cu n componente, n fiind capacitatea maximă a stivei. Vom nota cu TOP numărul efectiv de
elemente din stivă, şi anume:
TOP = 0 – va reprezenta o stivă goală (deci nu vom mai putea extrage elemente din stivă)
TOP >= n - va reprezenta o stivă plină (deci nu vom mai putea insera elemente în stivă)

 
TOP
 
Fig.2.15. Reprezentare unei stive secvenţiale.

Operaţiile efectuate asupra stiva sunt:


a. testare capacitate superioară (stivă plină);
b. testare capacitate inferioară (stivă goală);
c. inserare de noi elemente, operaţie numită PUSH
d. extragere (ştergere) din stivă, operaţie numită POP.

a.Testare capacitate superioară (stivă plină);

Function Full_Stack(TOP:INTEGER): boolean


Begin
If TOP = n then
Full_Stack:=TRUE
else
Full_Stack:=FALSE;
End;

a.Testare capacitate inferioară (stivă goală);

________________________________________________________________________________

144
Structuri de date şi algoritmi
_______________________________________________________________________________

Function Empty_Stack(TOP:INTEGER): boolean


Begin
If TOP = 0 then
Empty_Stack:=TRUE
else
Empty_Stack:=FALSE;
End;

c.Inserare - PUSH;

Procedure PUSH (VAR TOP:INTEGER, info_nou:tip)


Begin
If NOT FULL_Stack(TOP) then
Begin
Stiva[TOP]:=info_nou;
TOP=TOP+1;
else
writeln(‘OVERFLOW’);
End;

d.Extragere - POP;

Function Pop (VAR TOP:INTEGER):tip


Begin
If NOT Empty_Stack(TOP) then
Begin
TOP=TOP-1;
Pop:=Stiva[TOP];
else
writeln(‘UNDERFLOW’);
End;

II.3.3. Stiva – alocare dinamică

În reprezentarea dinamică, stiva va fi implementată cu ajutorul unei liste simplu înlănţuite.


Adresa ultimului element din stivă va fi numită TOP, având semnificaţia de a indica spre elementul
din vârful stivei. Structura va fi manipulată ca şi orice listă simplu înlănţuită prin adresa primului
element, notat LIST, care va semnifica baza stivei.

________________________________________________________________________________

145
Structuri de date şi algoritmi
_______________________________________________________________________________

LIST TOP

Fig.2.16. Reprezentare unei stive dinamice.


Operaţiile efectuate asupra stiva sunt:
a. testare capacitate inferioară (stivă goală);
b. inserare de noi elemente, operaţie numită PUSH
c. extragere (ştergere) din stivă, operaţie numită POP.

Obs. 2.2.: Memoria fiind alocată dinamic nu este necesar testul de capacitate superioară.

a.Testare capacitate inferioară (stivă goală);

Function Empty_Stack(TOP:adresa): boolean


Begin
If TOP = NIL then
Empty_Stack:=TRUE
else
Empty_Stack:=FALSE;
End;

c.Inserare - PUSH;

Procedure PUSH (VAR TOP:adresa, info_nou:tip)


Begin
Inserare_sfârsit(LIST,TOP,info_nou)
End;

d.Extragere - POP;

Function POP (VAR TOP:adresa):tip


Begin
If NOT Empty_Stack(TOP) then

________________________________________________________________________________

146
Structuri de date şi algoritmi
_______________________________________________________________________________
Begin
Ştergere_sfârsit(LIST,TOP)
else
writeln(‘UNDERFLOW’);
End;

II.3.4. Probleme propuse

1.) Să se realizeze evidenţa materialelor existente,într-o magazie. La intrarea în stoc a materialelor


pe baza de factură, se adaugă elemente unei stive. La eliberarea spre consum productiv, se şterge
vârful stivei. Procedeul continuă după cum avem intrări sau consumuri de materiale. Se va scrie şi
funcţia pentru numărarea facturilor.

 2.) Se consideră o stivă nevidă de lungime mai mare ca 5. Scrieţi funcţia pentru dealocarea
memoriei ocupate de această stivă .

 3.) O echipă realizează demontarea unei instalaţii cu dispunerea reperelor şi sub-ansamblelor într-
un mod corespunzător reparării şi mai apoi asamblări. O altă echipă efectuează asamblarea. Scrieţi
programul care afişează lista operaţiilor de asamblare ştiind ca aceste operaţii sunt specificate prin
numele reperelor sau sub-ansamblelor.

 4.) Se consideră mulţimea literelor mari şi mulţimea literelor mici. Să se construiască pornind de la
un şir oarecare de litere, două stive ce conţin literele mari, respectiv, literele mici.
Să se afişeze literele mici şi mari în ordinea în care au fost introduse.

 5.) Scrieţi şi apelaţi funcţia de ştergere a unei stive până la elementul a cărui informaţie utilă
coincide cu un parametru dat.

 6.) Dându-se stivele A şi B care conţin cod produs, cantitate şi, respectiv, cod produs şi preţ, creaţi
stiva ale cărei elemente conţine cod produs, cantitate şi preţ. La apelarea funcţiei, elementele sunt
presupuse sortate după cod.

 7.) Descompuneţi folosind o funcţie pe care o apelaţi, o stivă în două stive cu număr egal de
elemente, numai dacă acest lucru este posibil.

 8.) Găsiţi o formulă simplă pentru numărul de permutări de n elemente, ce pot fi obţinute cu 
ajutorul unei stive.

 9.) Arătaţi că permutarea p1, p2, ..., pn se poate obţine pornind de la 1, 2, ..., n utilizând o stivă dacă
şi numai dacă nu există indici i < j < k astfel încât pj < pk < pi.

________________________________________________________________________________

147
Structuri de date şi algoritmi
_______________________________________________________________________________
II.4. Coada (queue)
II.4.1. Generalităţi

În prelucrarea unei mulţimi de elemente pot să fie utilizate diferite metode de alegere a
următorului element supus prelucrării (Dale şi Lilly [1988]). Această alegere poate să fie aleatoare
sau să respecte un anumit criteriu. De exemplu, pentru o mulţime la care este semnificativă ordinea
în care elementele au fost introduse în mulţime se poate considera un criteriu care să se bazeze pe
această ordine. Modelul intuitiv al acestei structuri este coada care se formează la un magazin,
lumea se aşează la coadă la sfârşitul ei, cei care se găsesc la începutul cozii sunt serviţi, şi părăsesc
apoi coada.
Coada reprezintă o altă categorie specială de listă liniară, în care toate elementele se
inserează la un capăt (sfârşit) şi se extrag (şterg) la celălalt capăt (început).
Preluând o terminologie din domeniul sistemelor de operare, coada mai poartă numele de
listă FIFO (First In - First Out), adică „primul intrat în coadă va fi primul care va ieşi din coadă”.
 
II.4.2. Coada – alocare statică

În reprezentarea secvenţială, elementele cozii vor fi memorate într-un vector (tabel) numit
coada cu n componente, n fiind capacitatea maximă a stivei. Vom nota cu Front indexul
elementului ce urmează a fi servit, iar cu Rear indexul primului element liber din coadă. Dacă:
Front = Rear – va reprezenta o coadă goală (deci nu vom mai putea extrage elemente din coadă)
Rear >= n - va reprezenta o coadă plină (deci nu vom mai putea insera elemente în coadă)

extragere inserare
Front Rear
 
Fig.2.17. Reprezentare unei cozi secvenţiale.

Operaţiile efectuate asupra cozii sunt:


e. testare capacitate superioară (stivă plină);
f. testare capacitate inferioară (stivă goală);
g. inserare de noi elemente, operaţie numită ENQUEUE;
h. extragere (ştergere) din stivă, operaţie numită DEQUEUE.

________________________________________________________________________________

148
Structuri de date şi algoritmi
_______________________________________________________________________________
a. Testare capacitate superioară (coadă plină);

Function Full_Queue(Rear:INTEGER): boolean


Begin
If Rear = n then
Full_Queue:=TRUE
else
Full_ Queue:=FALSE;
End;

b. Testare capacitate inferioară (coadă goală);

Function Empty_ Queue (Front, Rear:INTEGER): boolean


Begin
If Front = Rear then
Empty_ Queue:=TRUE
else
Empty_ Queue:=FALSE;
End;

c.Inserare - ENQUEUE;

Procedure ENQUEUE (VAR Rear:INTEGER, info_nou:tip)


Begin
If NOT FULL_Queue(Rear) then
Begin
Coada[Rear]:=info_nou;
Rear=Rear+1;
else
writeln(‘OVERFLOW’);
End;

d.Extragere - DEQUEUE;

Function DEQUEUE(VAR Front:INTEGER):tip


Begin
If NOT Empty_Queue(Front, Rear) then
Begin
Dequeue:=coada[Front];
Front=Front+1;

else

________________________________________________________________________________

149
Structuri de date şi algoritmi
_______________________________________________________________________________
writeln(‘UNDERFLOW’);
End;

II.4.3. Coada – alocare dinamică

În reprezentarea dinamică, coada va fi implementată cu ajutorul unei liste simplu


înlănţuite. Adresa ultimului element din stivă va fi numită Rear, având semnificaţia de a indica
adresa ultimul element inserat în coadă. Adresa primului element din stivă va fi numită Front,
având semnificaţia de a indica adresa primului element inserat în coadă, deci a elementului care
urmează a fi extras.

Front Rear

Fig.2.16. Reprezentare unei stive dinamice.


Operaţiile efectuate asupra stiva sunt:
a. testare capacitate inferioară (coadă goală);
b. inserare de noi elemente în coadă, operaţie numită ENQUEUE;
c. extragere (ştergere) din coadă, operaţie numită DEQUEUE.

Obs. 2.2.: Memoria fiind alocată dinamic nu este necesar testul de capacitate superioară.

a.Testare capacitate inferioară (coadă goală);

Function Empty_Queue(Front:adresa): boolean


Begin
If Front = NIL then
Empty_Stack:=TRUE
else
Empty_Stack:=FALSE;
End;

________________________________________________________________________________

150
Structuri de date şi algoritmi
_______________________________________________________________________________
b.Inserare - ENQUEUE;

Procedure ENQUEUE (VAR Rear:adresa, info_nou:tip)


Begin
Inserare_sfârsit(Front,Rear,info_nou)
End;

c.Extragere - DEQUEUE;

Function DEQUEUE(VAR Front:adresa):tip


Begin
If NOT Empty_Stack(Front) then
Begin
Ştergere_sfârsit(LIST,TOP)
else
writeln(‘UNDERFLOW’);
End;

II.4.4. Probleme propuse

1.) O coadă biterminală cu restricţii la intrare este o listă lineară în care elementele nu pot fi inserate
decât la un capăt, dar pot fi şterse de la ambele capete; în mod clar o astfel de coadă biterminală
poate opera atât ca o stivă cât şi ca o coadă, dacă nu ştergem elemente decât da la un singur capăt.
Se poate ca şi o coadă biterminală cu restricţii la ieşire să opereze fie ca o coadă ?

2.) Există permutări ale elementelor 1, 2, ..., n care nu pot fi obţinute cu ajutorul unei cozi care nu
este cu restricţii nici la intrare, nici la ieşire ?

3.) Folosind o structură de tip stivă şi coadă să se decidă dacă un cuvânt este palindrom. Un
palindrom este un cuvânt care coincide cu inversul său. Exemple: lupul – este palindrom, acesta –
nu este palindrom.

4.) Se introduc n numere reale într-o stivă şi într-o coadă. Se extrage un număr din stivă şi se
introduce în coadă. Se extrage un număr din coadă şi se introduce în stivă. Să se tipărească
conţinutul stivei şi al cozii după k paşi.

II.5. Arborele binar

Când am discutat despre structurile de date înlănţuite, liste simplu sau dublu înlănţuite, am
văzut avantajele evidente ale stocării de date ordonate. Folosirea eficientă a spaţiului de memorare,

________________________________________________________________________________

151
Structuri de date şi algoritmi
_______________________________________________________________________________
manipularea întregii structuri de date doar prin intermediul adresei de început, etc. sunt doar câteva
din argumentele folosirii acestor structuri de date dinamice. Dezavantajul evident al structurilor de
date de tip listă înlănţuită este însă provocat de procedurile de căutare secvenţială care au o
performanţă de ordinul O(n). De aceea, s-a construit o nouă structură de date dinamică, de tip
înlănţuit, care să păstreze avantajele flexibilităţii structurii de date tip listă înlănţuită şi să se bazeze
pe o performanţă mult îmbunătăţită a algoritmului de căutare, de ordinul O(log2n).

II.5.1. Definiţii

Un arbore este o mulţime de elemente numite noduri, un singur nod având rolul de nod
rădăcină, împreună cu o relaţie de tip părinte-fiu care defineşte o structură ierarhică pe mulţimea
nodurilor. În mod formal, un arbore poate fi definit recursiv astfel (Aho et al. [1987]):

1. Un arbore poate conţine un singur nod. Acest nod este şi nodul rădăcină al arborelui.
2. Fie n un nod şi T1, T2, …, Tk arbori cu nodurile rădăcină n1, n2, …, nk. Putem construi un nou
arbore, făcând ca nodul n să devină nodul părinte al nodurilor n 1, n2, …, nk. În acest arbore, n
este nodul rădăcină, iar T1, T2, …, Tk se numesc subarbori. Nodurile n1, n2, …, nk se numesc
noduri fiu ale nodului n.

Un arbore binar este o structură arborescentă în care fiecare vârf are cel mult doi
descendenţi, făcându-se însă distincţie clară între descendentul drept şi descendentul stâng al
fiecărui vârf. Se acceptă şi arborele binar cu 0 vârfuri, numit arbore binar vid.
Arborii binari nu reprezintă cazuri particulare de arbori orientaţi, decât dacă se face
abstracţie de distincţia menţionată între descendentul drept şi cel stâng al fiecărui vârf. Într-adevăr
dacă un vârf are un singur descendent, această informaţie este suficientă în cazul unui arbore, dar
insuficientă în cazul unui arbore binar, când trebuie precizat dacă acest descendent este descendent
stâng sau descendent drept.
Într-o structură de tip arbore, elementele sunt structurate pe nivele; pe primul nivel, numit
nivel 0, există un singur nod numit rădăcină, de care sunt legate mai multe noduri, numite fii care
formează nivelul 1; de acestea sunt legate elementele de pe nivelul 2 ş.a.m.d.

Fig. 2.x. Exemplu de arbore şi organizarea sa pe nivele.

________________________________________________________________________________

152
Structuri de date şi algoritmi
_______________________________________________________________________________
Un arbore este compus din elementele numite noduri sau vârfuri şi legăturile dintre acestea.
Un nod situat pe un anumit nivel este nod tată pentru nodurile legate de el, situate pe nivelul
următor, acestea reprezentând fiii săi. Fiecare nod are un singur tată, cu excepţia rădăcinii care nu
are tată. Nodurile fără fii se numesc noduri terminale sau noduri frunză.
Termenii 'nod tată', 'nod fiu', 'nod bunic', 'nod nepot' sau 'nod frate' sunt preluaţi de la arborii
genealogici, cu care arborii se aseamănă foarte mult.
Arborii, ca structuri dinamice de date, au extrem de multe aplicaţii în informatică. Deosebit
de utilizată în aplicaţii este structura de tip arbore binar. Un arbore binar este un arbore în care
fiecare nod are cel mult doi fii, fiul stâng şi fiul drept (fiul stâng este legat în stânga tatălui şi cel
drept în dreapta). Dacă se elimină rădăcina şi legăturile ei, se obţin doi arbori binari care se numesc
subarborele stâng şi subarborele drept ai arborelui iniţial.
Arborele binar este, deci, o structură recursivă de date. Un arbore binar nevid fie se reduce
la rădăcină, fie cuprinde rădăcina şi cel mult doi subarbori binari. Un arbore binar se poate
implementa foarte uşor cu ajutorul adreselor de înlănţuire, fiecare element cuprinzând, în afară de
informaţia proriu-zisă asociată nodului, adresa fiului stâng şi adresa fiului drept, acestea exprimând
legăturile existente între noduri.

Implementarea arborilor binari

În majoritatea implementărilor şi cei doi subarbori sunt adresaţi indirect; în funcţie de


varianta de implementare - dinamică sau statică, adresarea se realizează fie prin intermediul
pointerilor, fie prin intermediul indicilor de tablou.

Alegem implementarea dinamică cea mai simplă formă de reprezentare a nodurilor. Un nod
al unui arbore binar AB are următoarea structură:

Fiu_st Info Fiu_dr

Figura V.14. Structura nodului unui arbore binar.

unde:
fiu_st : reprezintă adresa fiului stânga;
Info: informaţia ataşătă nodului.;
fiu_dr: reprezintă adresa fiului dreapta;

Structura nodului în implementarea Pascal este următoarea (Albeanu [1994]):


Type
Adresa = ^Nod
Nod = RECORD
fiu_st : Adresa;
Info : tipinfo;

________________________________________________________________________________

153
Structuri de date şi algoritmi
_______________________________________________________________________________
fiu_dr : Adresa;
end

V.3.3. Operaţii într-un arbore binar AB

Operaţiile în cazul arborilor binari sunt:


a. creare-iniţializare;
b. inserare-balansare;
c. căutare;
d. ştergere;
e. traversarea:

Ne vom ocupa doar de operaţia de traversare care nu este dependentă de organizarea


particulară a arborelui, fiind aceeaşi pentru toţi arborii binari. Construirea unui arbore binar se face
în funcţie de relaţia definită pe datele conţinute în nodurile arborelui binar. În general, se alege o
parte a datei stocată în nodurile arborelui binar, după care se va ordona noua structură de date
arborescentă. Informaţia după care se ordonează arborele binar se numeşte cheie.

Cea mai des utilizată formă de organizare a unui arbore binar este următoarea:

Cheia nodului fiu stânga < Cheia nodului fiu stânga < Cheia nodului fiu stânga (2.x)

Utilizând formula de mai sus se obţin arbori binari speciali, utilizaţi pentru organizarea şi
căutarea datelor, numiţi arbori binari de căutare ABC.

e. Traversarea arborelui binar. Constă în parcurgerea pe rând a nodurilor în vederea prelucrării


informaţiilor ataşate acestora. Traversarea arborelui presupune vizitarea fiecărui nod o singură dată,
operaţie echivalentă cu o liniarizare a arborelui. Există trei modalităţi importante de traversare a
unui arbore binar, exprimate prin regulile recursive:

1.) în preordine: se vizitează rădăcina, apoi tot în preordine se vizitează nodurile subarborelui stâng
şi apoi acelea ale subarborelui drept.

2.) în inordine: se vizitează în inordine nodurile subarborelui stâng, apoi rădăcina şi apoi tot în
inordine, nodurile subarborelui drept.

3.) în postordine: se vizitează în postordine nodurile subarborelui stâng, apoi tot în postordine,
nodurile subarborelui drept şi, la sfârşit, rădăcina.

Obs.2.x:
1. evident, dacă arborele se reduce la un singur nod, vizitarea acestuia în preordine, inordine

________________________________________________________________________________

154
Structuri de date şi algoritmi
_______________________________________________________________________________
sau postordine presupune parcurgerea acestuia, deci prelucrarea informaţiilor asociate lui;
2. cele trei modalităţi de traversare diferă prin, momentul în care se vizitează rădăcina şi
anume, în cazul:
 preordine: se vizitează întâi rădăcina, apoi subarborele stâng şi după aceea cel drept
(RSD – Rădăcină, Stânga, Dreapta);
 inordine: se vizitează subarborele stâng, se vizitează rădăcina şi subarborele drept
(SRD - Stânga, Rădăcină, Dreapta);
 postordine: se vizitează subarborele stâng, se vizitează subarborele drept şi rădăcina
(SDR - Stânga, Dreapta, Rădăcină);

Descrierea algoritmilor de traversare, în limbajul Pascal este:

Procedure inordine ( def : adresa ) ; { traversare inordine }


begin
if def <> nil then
begin
inordine ( def^.fiu_st ) ;
write ( def^.info : 3 ) ;
inordine ( def^.fiu_dr ) ;
end ;
end ;

Procedure preordine ( def : adresa ) ; { traversare preordine }


begin
if def <> nil then
begin
write ( def^.info : 3 ) ;
preordine ( def^.fiu_st ) ;
preordine ( def^.fiu_dr ) ;
end ;
end ;

Procedure postordine ( def : adresa ) ; { traversare postordine }


begin
if def <> nil then
begin
postordine ( def^.fiu_st ) ;
postordine(def^.fiu_dr);
write ( def^.info : 3 ) ;
end ;
end ;

Probleme propuse

________________________________________________________________________________

155
Structuri de date şi algoritmi
_______________________________________________________________________________
1.) Câte structuri arborescente diferite există pentru trei noduri A,B şi C ?

2.) Dacă nodul A are trei fraţi iar B este tatăl lui A, care este gradul lui B ?

3.) Există arbore binar care să nu fie arbore ?

4.) Să se creeze arborele genealogic propriu pe parcursul a trei sau patru generaţii, punându-se în
nodul rădăcină prenumele iar în nodurile descendente ale fiecărui nod, numele părinţilor.

5.) Pentru arborele binar din figura 32 să se scrie listele de vârfuri obţinute în urma celor trei
metode de parcurgere.

6.) Să se scrie un program pentru determinarea adâncimii maxime a unui arbore binar, adică al
numărului de nivel maxim asociat nodurilor terminale.

7.) Să se scrie un program pentru listarea tuturor nodurilor aflate pe un nivel dat într-un arbore
binar.

8.) Aplicând cele trei metode de parcurgere a unui arbore binar, să se extragă, în ordinea în care
apar, nodurile descendente, în ordinea de la stânga la dreapta.

9.) Să se conceapă algoritmi de prelucrare (creare, parcurgere etc. ) pentru arborescenţe, utilizând
pentru fiecare nod câte o listă cu toate nodurile descendente, în ordinea de la stânga la dreapta.

10.) Să se scrie un algoritm care, pe baza succesiunii nodurilor în inordine şi postordine ale unui
arbore binar, să producă succesiunea în preordine a acestora. Analog, pe baza succesiunilor în
ordine şi preordine, să se obţină lista nodurilor în postordine.

II.5. Grafuri

II.5.1. Grafuri orientate

II.5.1.1. Definiţii şi generalităţi

Definiţia 1.5. Un graf orientat este un cuplu G  (X, U), unde X este o mulţime finită şi nevidă de
elemente numite vârfuri, şi U  XX este o mulţime de elemente numite arce.

Fie un arc u = (x,y)  U. Spunem că arcul u este incident exterior cu vârful x, este incident

________________________________________________________________________________

156
Structuri de date şi algoritmi
_______________________________________________________________________________
interior cu vârful y, şi are sensul (orientarea) de la x la y. Vârful x este extremitate iniţială şi
predecesorul lui y, şi vârful y este extremitate finală şi succesorul lui x. Deci în precizarea unui arc
contează ordinea vârfurilor.

Două vârfuri sunt adiacente dacă între ele există un arc. Două arce distincte sunt adiacente
dacă au o extremitate comună.

Definiţia 1.6. Un graf orientat este un cuplu G  (X,  ), unde X este o mulţime de elemente numite
vârfuri, şi  este o aplicaţie multivocă  : X  X (adică o funcţie definită pe X cu valori în familia
P(X) a părţilor lui X).

Dacă x  X atunci y   (x) precizează că de la vârful x la vârful y există un arc şi spunem că


y este succesor al lui x. Spunem că x este predecesor al lui y şi notăm x   1(y).

Se dă un graf G  (X,  ) şi fie x  X un vârf. Notăm cu g+(x) numărul succesorilor săi, adică
| (x)|; acest număr se numeşte semigradul exterior al lui x. Notăm cu g(x) numărul predecesorilor
săi, adică | 1(x)|; acest număr se numeşte semigradul interior al lui x. Suma g(x)  g+(x) + g(x) se
numeşte gradul lui x. Dacă g(x)  0 spunem că vârful x este izolat.

Cele două definiţii sunt analoge:

(x)  {y  X | (x, y)  U}; (x, y)  U  y  (x).

Definiţia 1.7 Un graf orientat este simetric dacă pentru orice pereche de vârfuri x, y  X avem: (x,y)
 U  (y,x)  U.

Un graf este bipartit dacă există o partiţie X  X1  X2 astfel încât pentru orice (x,y)  U
avem x  X1 şi y  X2.

Un graf G’  (A, V) este subgraf al grafului G = (X, U) dacă A  X şi V  (AA)  U.

Un graf G’  (X, V) este graf parţial al grafului G  (X, U) dacă V  U.

Un graf G’  (A, V) este subgraf parţial al grafului G  (X, U) dacă A  X şi V  (AA)  U.

Definiţia 1.8 Fiind dat un graf orientat G  (X, U), un drum în graful G este o succesiune de arce cu
proprietatea că extremitatea terminală a unui arc coincide cu extremitatea iniţială a arcului următor
din drum. Un drum se poate defini şi prin succesiunea de vârfuri care sunt extremităţi ale arcelor ce
compun drumul: o succesiune de vârfuri cu proprietatea că orice două vârfuri consecutive sunt unite

________________________________________________________________________________

157
Structuri de date şi algoritmi
_______________________________________________________________________________
printr-un arc.

Un drum într-un graf este:

– simplu, dacă nu foloseşte de două ori un acelaşi arc;

– compus, dacă nu e simplu;

– elementar, dacă nu trece de două ori prin acelaşi vârf;

– eulerian, dacă este simplu şi foloseşte toate arcele grafului;

– hamiltonian, dacă este elementar şi trece prin toate vârfurile grafului;

– circuit, dacă extremitatea iniţială a drumului coincide cu cea finală.

II.5.1.2. Reprezentarea grafurilor orientate

a. Reprezentarea cu ajutorul matricelor de adiacenţă

Fie un graf orientat G  (X, U), unde presupunem că G  1,2,  , n . Matricea de adiacenţă
A asociată grafului este o matrice pătratică de ordinul n, definită astfel:

1, dacă  i , j   U
A i, j    (5.x)
0, dacă  i, j   U

Exemplu 5.1: Să considerăm graful reprezentat în figura de mai jos:

Fig. 5.x.: Exemplu de graf orientat.

________________________________________________________________________________

158
Structuri de date şi algoritmi
_______________________________________________________________________________
Atunci matricea de adiacenţă ataşată grafului este:

0 1 1 0
 
0 0 0 1
A
0 1 0 0
 
0 0 1 0 

Dezavantajul utilizării reprezentării grafurilor orientate cu ajutorul matricelor de adiacenţă


este dată de faptul că spaţiul ocupat este de ordinul Ω(n2). Orice operaţie efectuată asupra grafului
orientat necesită parcurgerea matricei de adiacenţă asociată, care este o operaţie de ordinul O(n2).

b. Reprezentarea cu ajutorul listelor de adiacenţă

Pentru a elimina dezavantajele reprezentării grafurilor orientate cu ajutorul matricelor de


adiacenţă putem folosi o metodă bazată pe reprezentarea cu liste înlănţuite, numită reprezentare cu
liste de adiacenţă.

Lista de adiacenţă pentru un vârf i este o listă simplu înlănţuită a tuturor nodurilor adiacente
cu i. În acest fel reprezentarea grafului orientat G  (X, U), se face cu o tabelă n dimensională
numită CAP, elementul CAP[i] reprezentând un pointer către capul listei de adiacenţă al vârfului i.

Exemplu 5.2: Reprezentarea grafului din Figura 5.x cu ajutorul listelor de adiacenţă:

Fig. 5.x:Reprezentarea grafului orientat din Figura 5.x cu ajutorul listelor de adiacenţă.

Reprezentării grafurilor orientate cu ajutorul listelor de adiacenţă necesită un spaţiu de


memorare ocupat este de ordinul Ω(n+nr.de_arce). De aceea, această formă de reprezentare este
preferabilă atunci când expresia n+nr.de_arce este mai mică decât n2. De asemenea constatarea

________________________________________________________________________________

159
Structuri de date şi algoritmi
_______________________________________________________________________________
faptului dacă există sau nu un arc de la vârful i la vârful j necesită un timp proporţional cu O(n).

II.5.2. Grafuri neorientate

II.5.2.1. Definiţii şi generalităţi

Definiţia 1.9. Un graf neorientat este un cuplu G = (X, U), unde X este o mulţime de elemente
numite vârfuri, şi U este o mulţime de perechi neordonate de vârfuri numite muchii. O muchie (x, y)
nu are orientare, astfel că (x, y)  (y, x).

O muchie este incidentă vârfurilor care sunt extremităţi ale sale. Două vârfuri sunt adiacente
dacă între ele există o muchie. Două muchii sunt adiacente dacă au o extremitate comună. Gradul
unui vârf x este numărul de muchii incidente cu acesta şi se notează g(x).

Definiţia 1.10. Un lanţ este o succesiune de muchii cu proprietatea că orice muchie are un vârf
comun cu muchia precedentă şi celălalt vârf comun cu muchia succesoare.

În mod analog definim noţiunea de ciclu corespunzătoare noţiunii de circuit de la grafuri


orientate.

Definiţia 1.11. Un graf neorientat este conex dacă între orice două vârfuri există un lanţ.

O componentă conexă a unui graf neorientat este un subgraf conex maximal în raport cu operaţia
de incluziune. Cu alte cuvinte, nu există o submulţime de vârfuri mai numeroasă care să inducă un
subgraf conex.

Definiţia 1.12. Un arbore este un graf conex fără cicluri.

Lema 1.1. Un arbore cu n vârfuri are n–1 muchii.

Demonstraţie (Livovschi şi Georgescu [1986]). Considerăm pentru început un graf cu două


vârfuri unite printr-o muchie, care este evident un arbore. La fiecare pas adăugăm un vârf nou pe
care îl unim printr-o muchie cu un vârf deja existent. Presupunem că după un număr de paşi avem
un arbore cu k vârfuri şi k–1 muchii. La următorul pas obţinem un arbore parţial cu k1 vârfuri şi k
muchii. Nu se obţine nici un ciclu: pentru aceasta ar fi trebuit ca noul vârf ales să fi fost deja

________________________________________________________________________________

160
Structuri de date şi algoritmi
_______________________________________________________________________________
conectat cu unul din cele k vârfuri, fapt imposibil.

II.5.2.2. Reprezentarea grafurilor neorientate

a. Reprezentarea cu ajutorul matricelor de adiacenţă

Fie un graf neorientat G  (X, U), unde presupunem că G  1,2,  , n . Matricea de


adiacenţă A asociată grafului este o matrice pătratică de ordinul n, definită astfel:

1, dacă  i , j   U
A i, j    (5.x)
0, dacă  i, j   U

Obs. 5.x: Se observă că matricea de adiacenţă ataşată grafului este simetrică în raport cu diagonala
principală.

Exemplu 5.1: Să considerăm graful reprezentat în figura de mai jos:

Fig. 5.x.: Exemplu de graf neorientat.

Atunci matricea de adiacenţă ataşată grafului este:

0 1 0 1
 
1 0 1 1
A
0 1 0 1
 
1 1 1 0 

b. Reprezentarea cu ajutorul listelor de adiacenţă

Pentru a elimina dezavantajele reprezentării grafurilor neorientate cu ajutorul matricelor de


adiacenţă putem folosi o metodă bazată pe reprezentarea cu liste înlănţuite, numită reprezentare cu

________________________________________________________________________________

161
Structuri de date şi algoritmi
_______________________________________________________________________________
liste de adiacenţă.

Lista de adiacenţă pentru un vârf i este o listă simplu înlănţuită a tuturor nodurilor adiacente
cu i. În acest fel reprezentarea grafului orientat G  (X, U), se face cu o tabelă n dimensională
numită CAP, elementul CAP[i] reprezentând un pointer către capul listei de adiacenţă al vârfului i.

Exemplu 5.2: Reprezentarea grafului din Figura 5.x cu ajutorul listelor de adiacenţă:

Fig. 5.x:Reprezentarea grafului orientat din Figura 5.x cu ajutorul listelor de adiacenţă.

II.5.4. Grafuri planare

Pentru a reprezenta unele structuri geometrice în plan sau în spaţiu se foloseşte o categorie
specială de structuri de date: grafuri planare.

Definiţia 1.13. Un graf G este planar dacă este posibil să fie reprezentat pe un plan astfel încât
vârfurile să fie distincte, muchiile curbe simple şi două muchii să nu se intersecteze decât la
extremităţile lor (dacă sunt adiacente).

Reprezentarea grafului conform cu condiţiile impuse se numeşte graf planar topologic şi se


notează tot prin G. Nu se consideră distincte două grafuri planare topologice dacă le putem face să
coincidă prin deformarea elastică a planului.

Definiţia 1.14. O faţă a unui graf planar este o regiune a planului delimitată de muchii, care are
proprietatea că orice două puncte din această regiune pot fi unite printr-o curbă simplă care nu

________________________________________________________________________________

162
Structuri de date şi algoritmi
_______________________________________________________________________________
întâlneşte nici muchii şi nici vârfuri.

Definiţia 1.15. Frontiera unei feţe este mulţimea muchiilor care ating faţa respectivă. Două feţe
sunt adiacente dacă frontierele lor au cel puţin o muchie comună.

Într-un graf topologic, frontiera unei feţe este formată din unul sau mai multe cicluri
elementare disjuncte, din muchii suspendate sau care unesc două cicluri disjuncte (istmuri).

Definiţia 1.16. Conturul unei feţe este conturul ciclurilor elementare care conţin în interiorul lor
toate celelalte muchii ale frontierei.

Există întotdeauna o faţă infinită şi care are frontieră, dar nu are contur; toate celelalte feţe
sunt finite şi admit un contur.

Teorema 1.2 (Euler). Într-un graf planar topologic conex cu n vârfuri, m muchii şi f  feţe avem
relaţia:

n–mf 2

Demonstraţie (Tomescu, I. [1981]).  Dacă f  1 atunci m  n–1 pentru un arbore (lema 1.1),


deci relaţia este adevărată.

Presupunem afirmaţia adevărată pentru orice graf planar conex cu f –1 feţe, şi să considerăm un
graf planar conex cu f feţe. Fie (x,y) o muchie a unui ciclu; aceasta se află pe frontiera a două feţe S
şi T. Dacă eliminăm muchia (x,y) se obţine un graf planar conex cu n’  n vârfuri, m’  m–1 muchii
şi f’  f –1 feţe. Conform ipotezei de inducţie n’–m’f’  n–1–m1f  2, de unde obţinem că n–
mf  2.

Corolar 1.3. În orice graf planar topologic conex cu n vârfuri, m muchii şi f feţe avem:

(i) n  2m/3 ; m  3f–6 ; n  2f–4 (dacă fiecare vârf are gradul cel puţin trei)

(ii) f  2m/3 ; m  3n–6 ; f  2n–4 (dacă graful are cel puţin două muchii)

Demonstraţie. (i) Din fiecare vârf pleacă cel puţin trei muchii, şi fiecare muchie uneşte două
vârfuri; rezultă 3n  2m. Celelalte două inegalităţi rezultă prin simplă înlocuire.

(ii) Dacă m  2 atunci f  1 şi n  3. Dacă f  1 şi m  2 atunci n  m1. Dacă f  2 atunci


fiecare faţă are cel puţin trei laturi (muchii), şi o muchie este comună la cel mult două feţe; rezultă
3f  2m. Celelalte două inegalităţi rezultă prin simplă înlocuire.

________________________________________________________________________________

163
Structuri de date şi algoritmi
_______________________________________________________________________________

Să considerăm în spaţiul tridimensional un poliedru convex cu n vârfuri, m muchii şi f feţe.


Putem să-l reprezentăm pe o sferă astfel încât două muchii să nu se intersecteze decât la extremităţi.
Efectuând o proiecţie stereografică al cărei centru să fie în mijlocul unei feţe, poliedrul se poate
reprezenta pe un plan. Graful fiind planar, se obţine relaţia lui Euler nmf  2.

________________________________________________________________________________

164

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