Sunteți pe pagina 1din 165

MINISTERUL EDUCAIEI I CERCETRII

UNIVERSITATEA 1 DECEMBRIE 1918

Lect. dr. Corina Rotar

ALGORITMI I STRUCTURI DE DATE

Alba Iulia
2008

CUPRINS

I. Algoritmi i programare. Descrierea algoritmilor.......................................2


II. Elementele limbajului de programare C..................................................15
III. Functii. Transmiterea parametrilor. Recursivitate..................................33
IV. Tablouri. Tehnici de sortare...................................................................42
V. Structuri. Tipuri de date definite de utilizator.........................................59
VI. Lucrul cu fiiere......................................................................................69
VII. Alocarea dinamica a memoriei.............................................................77
VIII. List simplu nlnuit.........................................................................80
IX. Lista dublu nlnuit.............................................................................90
X. Liste circulare. Stive. Cozi.......................................................................99
XI. Arbori...................................................................................................109
XII. Elemente de Grafuri. Algoritmi..........................................................126
XIII. Metode de elaborare a algoritmilor. Divide et Impera......................131
XIV. Metode de elaborare a algoritmilor.Greedy.......................................142
XV. Metode de elaborare a algoritmilor. Backtracking. ...........................147
XVI. Metode de elaborare a algoritmilor.Programare dinamica................157

Algoritmi i structuri de date

I.

ALGORITMI I PROGRAMARE. DESCRIEREA


ALGORITMILOR

1. Programare. Etapele programrii.


Programarea reprezint activitatea complex de elaborare a programelor.
Programarea nu se refer strict la scrierea codului surs (descrierea n limbaj de
programare a rezolvrii problemei); aceast activitate implic parcurgerea mai
multor etape:
a. Analiza problemei. Problemele reale cu care ne confruntm nu sunt
formulate ntotdeauna ntr-un stil clar, precis. De cele mai multe ori, problemele
sunt formulate incomplet, ambiguu, lsnd programatorului sarcina de a determina
corect: ce se cunoate din problem i rezultatele cerute. Etapa de analiz a
problemei se finalizeaz prin identificarea celor dou elemente eseniale ale
formulrii unei probleme: datele de intrare i datele de ieire.
b. Proiectarea algoritmului poate fi considerat etapa de creativitate a
programrii, n care folosindu-se de cunotinele i experiena dobndite,
programatorul va identifica metoda de rezolvare a problemei date i va dezvolta
algoritmul corespunztor. Finalitatea acestei etape o constituie un algoritm descris
clar ntr-una dintre variantele de reprezentare algoritmic (scheme logice,
pseudocod, sau chiar limbaj de programare n cazul n care problema este de
dificultate mic).
c. Traducerea n limbaj de programare (implementarea): este etapa n care se
va efectua o traducere n limbaj de programare a descrierii algoritmului rezultat
n etapa de proiectare. Cunoaterea n detaliu a regulilor sintactice ale limbajului
ales, experiena i stilul programatorului sunt ingredientele necesare i suficiente
ale acestei etape.
d. Traducerea n cod main, execuia i testarea. Traducerea n cod main
se realizeaz automat cu ajutorul a dou componente ale mediului de programare:
compilatorului i a editorul de legturi. Testarea programului const n execuia sa
repetat pentru o mulime de date de intrare (date de test) i verificarea
corectitudinii rezultatelor oferite. Rezultatele incorecte ne semnaleaz o eroare
logic de proiectare i necesit o revizuire a algoritmului i re-parcurgerea etapelor
programrii.
e. ntreinerea este ultima etap a programrii i const din aciuni de
actualizare a produsului final i de asisten oferit beneficiarului.
Dintre toate aceste etape, cea care este mai solicitant este etapa de proiectare.
Practic, parcurgerea corect a acestei etape va face diferena ntre programatori i
amatori. Esena programrii const n capacitatea programatorului de a elabora i
descrie clar metoda de rezolvare a problemei. Orice eroare aprut la nivelul etapei
2

Algoritmi i structuri de date

de proiectare a algoritmului va avea repercusiuni asupra produsului final, genernd


erori logice.
2. Definirea algoritmilor
Aa cum am subliniat mai sus, etapa de proiectare a algoritmilor este cea mai
complex dintre cele etapele enumerate ale programrii. Noiunea de algoritm,
proprietile pe care trebuie s le ndeplineasc acesta i modalitile de
reprezentare standardizat a algoritmilor fac subiectul paragrafelor urmtoare.
Cuvntul algoritm provine din pronunia fonetica a numelui matematicianului
arab Al-Khwarizmi Muhammed ibs Musa (780-850), care se refer la reguli
precise pentru descrierea proceselor de calcul din aritmetic. Aceiai descriere se
regsete n lucrarea Elementele lui Euclid, cca. 300 .Hr., cnd pentru prima dat
se descrie o secven ordonat de reguli clare pentru descrierea rezolvrii unor
probleme. De altfel, algoritmul de determinare a celui mai mare divizor comun a
dou numere (algoritmul lui Euclid) este considerat ca primul algoritm din istorie.
n limbajul uzual, prin algoritm se nelege o metod de rezolvare a unei
probleme, alctuit dintr-o mulime de pai, dispui ntr-o ordine stabilit, ale cror
parcurgere ne conduce la rezultatul dorit. Algoritmii informatici, despre care vom
discuta n acest capitol, sunt definii mult mai riguros, surprinznd proprietile
obligatorii pe care acetia trebuie s le ndeplineasc.
Definiie: Un Algoritm (informatic) reprezint o secven
finit i ordonat de reguli clare, a cror parcurgere ne permite ca,
pornind de la o mulime de date de intrare, s obinem n mod eficient
rezultatele corecte ale problemei.
3. Proprietile algoritmilor:
Orice algoritm informatic verific urmtoarele proprieti:
1. Generalitate - un algoritm nu rezolv o singur problem, ci o clas de
probleme de acelai tip.
2. Finitudine - aciunile algoritmului trebuie s se termine dup un numr
finit de operaii, aceasta pentru orice set de date valide
3. Claritate - aciunile algoritmului trebuie s fie clare, simple i riguros
specificate
4. Corectitudine - algoritmul trebuie s produc un rezultat corect (date de
ieire) pentru orice set de date de intrare valide
5. Eficien - algoritmul trebuie s fie eficient privind resursele utilizate, i
anume s utilizeze memorie minim i s se execute ntr-un timp minim.
Regulile prin care algoritmul exprim maniera de rezolvare a problemei sunt de
4 tipuri:
1. reguli de intrare prin care se realizeaz introducerea datelor de intrare n
memoria unui sistem de calcul virtual sau real
2. reguli de calcul care permit efectuarea operaiilor elementare aritmetice
3

Algoritmi i structuri de date

3. reguli condiionale prin care se va decide continuarea sau nu a


algoritmului printr-o secven de reguli
4. reguli de ieire prin care se permite furnizarea rezultatelor finale ale
algoritmului
n funcie de tipurile de reguli pe care le conine, un algoritm poate fi:
- Algoritm liniar conine doar reguli de tipul 1,2,4
- Algoritm ramificat conine cel puin o regul de tipul 3
- Algoritmi repetitivi (ciclici) o secven de reguli se repet de
un numr finit de ori.
4. Obiecte si operaii ale algoritmilor
Algoritmii informatici opereaz cu urmtoarele obiecte fundamentale:
- constante
- variabile
Constanta reprezint o mrime a crei valoare nu se modific n timpul
execuiei (parcurgerii) algoritmului.
Variabila reprezint o mrime a crei valoare este modificabil n timpul
execuiei algoritmului.
Cuvntul variabil este unul fundamental n programare. Prin aceast noiune se
denumesc date de tipuri diferite. Tipurile de date sunt clasificate ca tipuri
elementare (ntreg, real, caracter) i tipuri structurate. Datele elementare sunt
uniti indivizibile de informaie, iar datele de tip structural sunt alctuite prin
asamblarea datelor elementare. Printr-un tip de dat se nelege n general att
domeniul de valori posibile ale datelor, ct i operaiile specifice datelor de acel tip.
Introducerea i utilizarea unei variabile n descrierea algoritmului corespunde
identificrii i manipulrii unei date de un tip de dat presupus. Variabila are ataat
un nume, o valoare curent care aparine domeniului tipului specificat i implic
cunoaterea mulimii de operaii posibile la care ia parte. n algoritmii informatici,
variabilele sunt toate datele de intrare, datele de ieire (rezultatele finale ale
problemei) i datele intermediare (acele date care corespund unor rezultate pariale
ale problemei ce intervin n procese ulterioare pentru determinarea rezultatelor
finale).
n anumite situaii, aciunea algoritmilor este ndreptat asupra: fiierelor i a
articolelor. Fiierul reprezint o mulime structurat i omogen de date i articolul
este unitatea atomic a fiierului.
Obiectele pe care le utilizeaz un algoritm sunt supuse unor operaii de
manipulare. Secvena de pai descris de algoritm este parcurs ntr-o anumit
ordine dictat de operaiile de control. Cele dou categorii de operaii formeaz
operaiile principale ale algoritmilor informatici:
- Operaii de intrare-ieire permit introducerea n memoria
sistemului de calcul (real sau virtual) a datelor de intrare i respectiv,
redarea rezultatelor obinute
- Operaii de atribuire prin intermediul acestor operaii, unei
variabile i se atribuie valoarea unei alte variabile, a unei constante sau
rezultatul evalurii unei expresii
- Operaii de calcul sunt operaiile executate cu ajutorul expresiilor
4

Algoritmi i structuri de date

Operaii de decizie condiioneaz execuia unui operaii sau grupe


de operaii n urma evalurii unor expresii la valorile logice
adevrat, fals.
Operaii de apel permit apelul unei instruciuni sau a unui grup de
instruciuni n vederea executrii acesteia de un numr finit de ori
Operaii de salt permit continuarea algoritmului dintr-un anumit
punct al acestuia
Operaii auxiliare operaii specifice lucrului cu baze de date sau
fiiere.

5. Descrierea algoritmilor. Scheme logice


Reprezentarea algoritmilor poate fi fcut n dou maniere:
- grafic prin intermediul schemelor logice
- textual prin intermediul unor limbaje standardizate (limbaj
pseudocod, limbaj de programare)
Schemele logice sunt reprezentri grafice ale algoritmilor informatici construite
pe baza unei mulimi de simboluri grafice conectate prin intermediul sgeilor.
Mulimea de simboluri grafice i semnificaia acestora este prezentat n
continuare:
a. Simbolurile delimitatoare: avnd rolul de a marca nceputul i sfritul
schemei logice, corespunde momentelor de start i stop a algoritmului
descris corespunztor.

b. Simboluri de intrare-ieire corespunztoare operaiilor de intrare-ieire


prin care se realizeaz comunicarea cu exteriorul sistemului de calcul
virtual.

c. Simboluri de atribuire - calcul pentru reprezentarea operaiilor de


calcul i atribuire. Detaliile referitoare la operaii sunt nscrise n interiorul
blocului.

Algoritmi i structuri de date

d. Simbolul de decizie utilizate pentru reprezentarea operaiilor de decizie.


Sunt simboluri cu o singur intrare i cel puin dou ieiri.

e. Simbolul de procedur se folosesc pentru reprezentarea operaiilor de


apel. Sunt apelate proceduri sau module externe (care sunt descrise n alt
parte).

f.

Simboluri auxiliare: Conectorii i sgeile asigur consistena schemei


logice.

Utilizarea corect a simbolurilor grafice permite descrierea oricrui algoritm


informatic. Se evideniaz trei structuri fundamentale corespunztoare: parcurgerii
secveniale a unei succesiuni de pai, ramificrii algoritmilor i execuiei repetate a
unei secvene de operaii. Cele trei structuri fundamentale sunt: structura
secvenial, structura alternativ, structura repetitiv, i sunt descrise prin
blocuri de simboluri logice astfel:
6

Algoritmi i structuri de date

Denumire
structur
Structura
secvenial

Reprezentare grafic

Semnificaie
Execuia
secvenial
a
operaiilor
descrise
n
simbolurile de calcul:
S1, Sn
Execut secvena S1 dac
condiia Cond este adevrat
SAU execut secvena S2 dac
Cond este fals

Structura
alternativ

Execut secvena S dac


condiia Cond este adevrat
SAU nu execut secvena S
dac Cond este fals.
Structura
repetitiv

Structura repetitiv
precondiionat:
Ct timp condiia Cond rmne
adevrat
repet execuia
secvenei S
Structura repetitiv
postcondiionat:
Repet execuia secvenei S ct
timp condiia Cond rmne
adevrat

Observaie: Diferena dintre structurile repetitive precondiionat i


postcondiionat const n poziia simbolului de decizie corespunztor etapei de
verificare a condiiei de continuare a execuiei blocului:
- structura precondiionat presupune iniial verificarea condiiei i apoi
execuia secvenei
- structura postcondiionat presupune execuia secvenei i apoi
verificarea condiiei
Aceast diferen va genera un numr minim de repetri a secvenei S diferit
pentru cele dou structuri: minim 0 execuii pentru structura precondiionat i
minim 1 execuie a secvenei n structura postcondiionat, n situaia n care de la
prima evaluare a condiiei aceasta este fals..
6. Exemple de scheme logice
Exemplul 1. Algoritmul de determinare a soluiilor ecuaiei de gradul 2:
ax 2 + bx + c = 0 . Rezolvarea problemei presupune tratarea celor 4 cazurilor
identificate n funcie de datele de intrare furnizate:
7

Algoritmi i structuri de date

1. rdcinile ecuaiei sunt reale


2. ecuaia de gradul II nu are rdcini reale
3. ecuaia este de gradul I
4. ecuaia este imposibil

Exemplul 2. Algoritmul de determinare a produsului primelor n numere naturale.

Algoritmi i structuri de date

Exemplul 3. Algoritmul de determinare a celui mai mare divizor comun a dou


numere:

7. Pseudocod
Pseudocodul este un limbaj standardizat intermediar ntre limbajul natural i
limbajul de programare. Descrierea algoritmilor cu ajutorul limbajului pseudocod
nltur din neajunsurile utilizrii schemelor logice, fiind mult mai flexibil i
natural dect acestea. n plus, descrierea n pseudocod permite convertirea cu
uurin a algoritmilor astfel exprimai ntr-un limbaj de programare.
Limbajul pseudocod folosete dou tipuri de propoziii:
1. Propoziiile standard: proprii limbajului pseudocod
2. Propoziiile ne-standard: acele propoziii ce descriu n limbaj uzual
operaii ce urmeaz a fi detaliate, rafinate ulterior. Aceste propoziii sunt marcate
prin simbolul #.
Exist o concordan ntre simbolurile grafice ale schemelor logice i
propoziiile limbajului pseudocod. De asemenea, structurile fundamentale pot fi
descrise prin propoziii proprii limbajului pseudocod.
Tabelul urmtor prezint concordana dintre descrierea grafic i propoziiile
pseudocod pentru operaii specifice algoritmilor:
Schema logic

Pseudocod
Algoritmul NUME-ALGORITM este:

Algoritmi i structuri de date

Sfrit NUME-ALGORITM
sau SfAlgoritm
Citete a1,a2,,an
Tiprete a1,a2,,an
Limbajul pseudocod, pentru a uura traducerea ulterioar a algoritmului n
limbaj de programare, permite folosirea unei propoziii standard echivalent uneia
dintre instruciunile populare ale limbajelor de programare. Aceasta propoziie
corespunde unei structuri repetitive cu numr cunoscut de repetri i se descrie
astfel:
Schema logic

Pseudocod
Pentru var de la I la F cu pasul pas
Execut S
Sfrit Pentru
- structura ciclic cu numr cunoscut
de repetri

Tabelul urmtor prezint modul de descriere a structurilor fundamentale n


pseudocod:
Structurile
fundamentale
Structura
secvenial
Structuri
alternative

Schema logic

Pseudocod

Limbaj natural

[Execut] S1
...
[Execut] Sn
Dac Cond atunci
Execut S1
Altfel
Execut S2
SfDac
Dac Cond atunci
Execut S
Sf Dac

Execut secvenial
blocurile de calcul
S1, S2,Sn
Dac condiia este
adevrat atunci se execut
blocul S1, altfel se execut
blocul S2.

10

Dac condiia este


adevrat atunci se execut
blocul S1.

Algoritmi i structuri de date


Cttimp Cond
Execut S
SfCttimp
Repet
Execut S
Pncnd Cond

Structuri
repetitive

Cttimp condiia Cond este


adevrat se repet blocul
S.
Se repet blocul S pn
cnd condiia Cond devine
adevrat.

8. Probleme propuse spre rezolvare


S se descrie grafic i textual (pseudocod) algoritmul de rezolvare a
problemei urmtoare:
Problema 1. S se citeasc un numr de n valori i s se determine cea mai mic
valoare citit.
Problema 2. S se rezolve sistemul de 2 ecuaii liniare cu 2 necunoscute.
Problema 3. S se citeasc n valori i s se calculeze media aritmetic a acestora.
9. Exemple de algoritmi descrii n pseudocod
Exemplul 1. Algoritmul de determinare a soluiilor ecuaiei de gradul 2:
ax 2 + bx + c = 0 .
Algoritm EcuaieGradII este:
Citete a, b, c
Dac a 0 atunci
Dac b 0 atunci
x = -c / b
Tiprete Sol. Ec. grad I,x
Altfel
Structuri
Tiprete
Imposibil
de
decizionale
rezolvat
imbricate
SfDac
Altfel
= b2-4ac
Dac 0 atunci
x1 = ( b ) 2a
x 2 = ( b + ) 2a
Tiprete x1,x2

Altfel
Tiprete Rdcini complexe
SfDac
SfDac
Sfrit EcuaieGradII

11

Algoritmi i structuri de date

Exemplul 2. Algoritmul de determinare a produsului primelor n numere naturale.


Algoritm Factorial este:
Citete n
P:=1
Structur
repetitiv cu
numr
cunoscut de
repetri

Pentru i de la 1 la n cu pasul 1
Execut P:=P * i
Sfrit Pentru
Tiprete P
Sfrit Factorial

Exemplul 3. Algoritmul de determinare a celui mai mare divizor comun a dou


numere.

Structur
repetitiv
precondiionat

Algoritm Euclid este:


Citete a,b
rest = a modulo b
Cttimp (rest 0)
a=b
b = rest
rest = a modulo b
Sfrit cttimp
Tiprete b
Sfrit Euclid

10. Subalgoritmi
O problem de dificultate sporit necesit uneori o mprire n subprobleme de
dificultate mai mic n baza principiului divide et impera. Algoritmii de rezolvare a
subproblemelor rezultate sunt mai uor de descris i vor constitui subalgoritmi ai
algoritmului global de rezolvare a problemei considerate. Deseori se poate ntlni
situaia ca o anumit secven de operaii s fie executat de mai multe ori pentru
date de intrare diferite. n aceste cazuri este util construirea unui subalgoritm
corespunztor secvenei de operaii i apelarea acestuia n algoritmul global ori de
cte ori este nevoie.
Subalgorimii sunt practic algoritmi care rezolv subprobleme ale unei probleme
date. Acest lucru indic faptul c orice subalgoritm verific proprietile de
generalitate, finitudine, claritate, corectitudine ale algoritmilor informatici.
Caracterul de generalitate va fi asigurat prin parametrizarea subalgoritmilor.
12

Algoritmi i structuri de date

Acetia vor primi la intrare nu datele de intrare ale problemei ci date intermediare
ale problemei globale sau rezultate pariale din procesele de calcul. Ieirile
subalgoritmilor sunt date pe care algoritmul general le va prelucra ulterior.
Comunicarea dintre subalgoritm i algoritmul printe se realizeaz prin
intermediul parametrilor. n pseudocod sintaxa general a unui subalgoritm este
urmtoarea:
Subalgoritm NumeSubalgoritm ( lista parametrii formali)
. // operaii asupra parametrilor din lista
Sfrit NumeSubalgoritm
Apelarea unui subalgoritm din algoritmul apelant se face prin propoziia:
Cheam NumeSubalgoritm (list parametrii actuali)
Lista parametrilor formali va conine nume generice ale datelor de intrare i
datelor de ieire ale subalgoritmului. Lista parametrilor actuali conine numele
datelor concrete care se doresc a fi transmise subalgoritmului i numele sub care se
rein rezultatelor pariale ale subalgoritmului.
La apelul unui subalgoritm parametrii formali (generici) din list se vor nlocui
cu parametrii concrei (actuali).
Exemplu: S se descrie un algoritm de determinare a maximului dintre 4 valori
date, evideniind subalgoritmul de determinare a maximului dintre oricare dou
valori.
Subalgoritm Maxim2(DI: x ,y; DE: Max)
Dac x>y atunci
Max=x
Altfel
Max=y
SfDac
Sfrit Maxim2

Apelul repetat al
subalgoritmului, pentru
diferii parametri
actuali

Algoritm Maxim4 este:


Citete a,b,c,d
Cheam(a,b; max1)
Cheam(c,d; max2)
Cheam(max1,max2 ; maxim)
Tiprete maxim
Sfrit Maxim4

Rezolvarea problemelor cu ajutorul calculatorului presupune:


1. Modelarea matematic a problemei
2. Alegerea unei metode de rezolvare
Modelarea matematic i alegerea metodei de rezolvare se mbin cu
conceperea algoritmului rezultnd ceea ce numim proiectarea programului. n faza
de elaborare a algoritmului trebuie s se in seama de proprietile fundamentale
ale algoritmilor: generalitate, claritate, finitudine, corectitudine i eficien.
Calitatea implementrii software este dependent de calitatea algoritmului elaborat.

13

Algoritmi i structuri de date

De aceea, faza de dezvoltare a algoritmului de rezolvare a unei clase de probleme


trebuie abordat cu o deosebit atenie.

14

Algoritmi i structuri de date

II.

ELEMENTELE LIMBAJULUI DE PROGRAMARE C

Limbajul de programare C a fost dezvoltat la nceputul anilor 70 de ctre Ken


Thompson i Dennis M. Ritchie. Popularitatea acestui limbaj a fost ctigat
datorit avantajelor pe care le prezint:
- programele scrise n C sunt portabile eficiente
- permite utilizarea principiilor programrii structurate
- ofer faciliti de manevrare a biilor, octeilor i adreselor de memorie
- este un limbaj dedicat paradigmei programrii procedurale
Prin limbaj de programare nelegem totalitatea regulilor sintactice i semantice
prin care se pot comunica unui computer algoritmii de rezolvare a problemelor.
Elementele unui limbaj de programare sunt: alfabetul, unitile lexicale,
unitile sintactice i comentarii.
A. Alfabetul limbajului C este format din:
1.
<liter>::=A|B|C|D|E|F|G|H|I|J|K|L|M|N|O|P|Q|R|S|T|U|V|W|X|Z|Y|a|b|
c|d|e|f|g|h|i|j|k|l|m|n|o|p|q|r|s|t|u|v|w|x|z|y|_
2.
<cifr> ::= 0|1|2|3|4|5|6|7|8|9
3.
<simboluriSpeciale>::=<simboluriSpecialeAfiabile>|
<simboluriSpecialeNeafiabile>
<simboluriSpecialeAfiabile>::=!|,||%|#|&|(|)|*|/|+|-|:|
;|<|=|>|?|[|]|\|~|{|}|||.||spaiu|
<simboluriSpecialeNeafiabile>::=\n |\t |\b |\r |\f |\v |\a
B. Unitile lexicale:
1.
2.
3.
4.

Identificatori
Cuvinte cheie (cuvinte rezervate)
Constante
Operatori
C. Unitile sintactice:

1. Expresii
2. Instruciuni
D. Comentarii: au rolul de a justifica textul surs redactat i de a oferi o
mai bun nelegere a acestuia. Comentariile sunt marcate n programul
surs prin // sau /**/
Exemple: //variabila i are semnificaia unui contor
/* acest program determin
soluiile ecuaiei de gradul II*/

15

Algoritmi i structuri de date

B. Unitile lexicale ale limbajului C


Unitile lexicale sunt elementele atomice ale unui program, formate cu ajutorul
simbolurilor precizate de alfabet.
B.1. Identificatorii: sunt secvene de litere i cifre ale limbajului definit care
ncep obligatoriu cu o liter sau caracterul _, avnd rolul de a identifica
conceptele utilizate n program. Identificatorii corespund unor nume de variabile,
nume de constante simbolice, nume de funcii.
Variabile, tip de date
Variabil:
- are un nume care o identific (acest nume este exprimat printr-un
identificator)
- are asociat o locaie de memorie n care se regsete valoare curent,
coninutul acestei locaii de memorie se poate schimba
- are un anumit tip de date, care exprim natura i dimensiunea valorilor pe
care le ia
Regul: ntr-un program C, toate datele (variabile) trebuie declarate pentru a
putea fi folosite. Prin declararea unei date se nelege precizarea tipului datei
respective i stabilirea identificatorului. Tipul datei ne spune domeniul valorilor
datei ct i lungimea de reprezentare intern. Declararea datelor se face prin
instruciuni de declarare de tip de date:
<instruciune de declarare de tip>::= <tip><list de identificatori>;
unde: <tip> poate fi un tip predefinit (vezi lista tipurilor predefinite n C), un tip
definit de programator sau tipul special void (tip nedefinit).
Exemple:
int a,b,c; // declararea a trei variabile simple de tip ntreg
float var; //declararea unei variabile simple de tipul real, virgul flotant n
simpl precizie
char ch ; // declararea unei variabile de tip caracter
B.2. Cuvintele rezervate (cuvinte cheie) sunt identificatori care au o
semnificaie special. Orice cuvnt rezervat nu poate avea alt utilizare ntr-un
program dect cea predefinit. Mulimea cuvintelor cheie ale limbajului C este
prezentat n tabelul urmtor:
Cuvintele cheie ale limbajului C
auto
defaul float registe switch
t
r
break
do
for
return
typedef
case
double go to short
union
char
else
if
signed unsigned
const
enum
int
sizeof
void
continue extern long static
volatile
16

Algoritmi i structuri de date

struct

while

Lista cuvintelor rezervate conine i setul de tipuri predefinite ale limbajului C.


Tipurile predefinite ale limbajului corespund tipurilor elementare de date: ntreg,
real, caracter. Aceste tipuri sunt introduse prin cuvintele rezervate:
Tipul
predefinit
int
short
long
unsigned
unsigned
long
char

Domeniul de valori

Semnificaie
ntreg reprezentat intern pe 16 bii,
n cod complementar fa de 2
ntreg reprezentat intern pe 16 bii,
n cod complementar fa de 2
ntreg reprezentat intern pe 32 bii,
n cod complementar fa de 2
ntreg fr semn reprezentat pe 16
bii
ntreg fr semn reprezentat pe 32
bii
caracter reprezentat intern prin
codul ASCII corespunztor pe 8
bii

float

Real, reprezentat n virgul flotant,


simpl precizie pe 32 bii

double

Real, reprezentat n virgul flotant,


dubl precizie pe 64 bii

unsigned
char
signed
char
long
double

Caracter, reprezentare intern fr


semn, pe 8 bii, echivalent implicit
pentru char
Caracter, reprezentare intern cu
semn, pe 8 bii
Real, reprezentat n virgul flotant,
dubl precizie pe 80 bii

[-32768,32767]
[-32768,32767]
[-2147483648,2147483647]
[0,65535]
[0,4294967295]
[0.255]
[- 3,4 10 38 , 3,4 10 38 ]
- valorile minime cuprinse n
intervalul [- 3,4 10 38 ,
3,4 10 38 ] se confund cu 0
[- 1.7 10 308 , 1.7 10 308 ]
- valorile minime cuprinse n
intervalul
[- 1.7 10 308 ,
1.7 10 308 ] se confund cu
0
[0,255]
[-128,127]
- valoarea minim absolut
reprezentabil: 3,4 10 4932
- valoarea maxim absolut
reprezentabil: 1.1 10 +4932

B.3. Constante
Constantele sunt mrimi ale cror valoare nu se modific pe parcursul execuiei
unui program. Constantele se clasific n patru categorii:
17

Algoritmi i structuri de date

Constante caracter
Constante ir de caracter
Constante numerice
Constante simbolice

Constante caracter
Constanta caracter este un simbol care are ataat valoarea codului ASCII al
caracterului corespondent.. Un caracter este reprezentat n memoria calculatorului
pe un octet (8 bii).
Exist dou categorii de caractere: caractere afiabile (imprimabile) i caractere
neafiabile:
<constanta caracter>::= <caracter>
<caracter>::=<caracter afiabil (ASCII)> |<caracter neafiabil>
O constant caracter grafic (afiabil) se specific incluznd caracterul
ntre simbolurile apostrof: .
Exemple:
A codul ASCII 65
a codul ASCII 97
+ codul ASCII 77
2 codul ASCII 50
O constant caracter non-grafic (neafiabil) au notaii speciale, se specific
utiliznd caracterul backslash (\) i apostroful ().
Exemple:
ntoarcerea cu un spaiu (Backspace) \b codul ASCII 8
Retur car
\r codul ASCII 13
Linie nou \n codul ASCII 10
Constante ir de caractere
Sunt formate dintr-o succesiune de caractere care se delimiteaz prin ghilimele:
exemplu. Fiecare caracter al irului se reprezint n memoria calculatorului pe un
octet, prin codul ASCII corespunztor. Sfritul secvenei de coduri ale caracterelor
dintr-un ir este marcat n memorie de un octet special cu valoarea NULL,
corespunztor caracterului \0.
Observaie: reprezentarea n memorie a lui k este diferit de reprezentarea lui
k. Astfel, irului k i este alocat un spaiu de 2 octei, ultimul fiind cel ce
marcheaz sfritul NULL.
Constante numerice
Constantele numerice sunt n fapt numerele. n limbajul C se face o distincie
ntre numere, n funcie de tipul (ntreg sau real), baza de numeraie i precizia
codificrii.
Numerele ntregi zecimale, exemple: -123, 5, +5.
Numerele octale ntregi, ncep cu cifra 0 i sunt de forma: 0cc, unde c
este cifr octal. Exemple: 0452 , unde 452 este numrul n baza 8
Numerele hexazecimale ntregi: ncep cu 0x sau 0X i sunt de forma:
0xcc unde c sunt cifre hexazecimale. Exemplu: 0xabbd.
18

Algoritmi i structuri de date

Numerele flotante de tip real n simpl precizie


[{+/-}]ddd . ddd
Pot s conin semnul (+ sau -) specificat la nceputul scrierii urmat de
partea ntreag, punctul zecimal i partea fracionar.
Exemple: +23.567 , -.667 , .4567
Numerele flotante de tip real n dubl precizie
[{+/-}]ddd . ddd {E / e} [{+/-}] dd sau:
[{+/-}] <parte ntreag>.<parte fracionar> {E/e} [{+/-}] <exponent>
Exemple:
0.345E2 (corespunztor valorii 0.345*102)
-0. 012e-3 (-0.012*10-3)
22.0e12 (=22*1012)
Constante simbolice
Definirea constantelor simbolice se face prin directiva de preprocesare define,
prin construcia:
#define <NumeConstant> <ValoareAsociat>
Ex:
#define PI 3.1415
#define raza 124
B.4. Operatorii
Operatorii sunt reprezentai de simboluri sau secvene de simboluri care intr n
alctuirea expresiilor.
n funcie de tipul operaiilor pe care le induc, operatorii limbajului C sunt
clasificai:
a. Operatori aritmetici
b. Operatori de incrementare i decrementare
c. Operatori logici
d. Operatori relaionali
e. Operatori de deplasare
f. Operatori la nivel de bii
g. Operatori de asignare (atribuire)
h. Operatorul condiional
i. Ali operatori
Dup numrul operanzilor pe care i implic, operatorii pot fi:
- unari
- binari
- ternari
Operatorii aritmetici
Operatori aritmetici unari:
<OperatoriAritmeticiUnari>::= - | +
Au rolul de a specifica semnul unei expresii. Se folosesc n construcii de
urmtoarea form:
{+/-}<expresie>
Dac o expresie nu este precedat de semn se consider implicit semnul +
(pozitiv).
19

Algoritmi i structuri de date

Exemple: +12.34, -0.56, +(a-2), etc.


Operatori aritmetici binari:
<OperatorAritmeticBinar>::= +| - | * | / | %
Au semnificaia operaiilor aritmetice: adunare, scdere, mprire, inmulire,
modulo i se utilizeaz n structuri de forma:
<expresie1><OperatorAritmeticBinar><expresie2>
Expresiile 1, 2 pot fi reprezentate de constante, variabile, nume de funcii sau
alte expresii aritmetice.
Exemple: a+b, (a+b) / 2, suma(a,b) % c , etc.
Operatorii de incrementare-decrementare
<OperatorulDeIncrementare>::= ++
Se folosete n construcii de forma:
++<expresie>
(preincrementare)
<expresie>++
(postincrementare)
Semnificaia acestuia este urmtoarea:
++<expresie> este echivalent cu <expresie> := <expresie>+1, unde
prin simbolul := se nelege i se atribuie
<OperatorulDeDecrementare>::= -Se folosete n construcii de forma:
--<expresie>
(predecrementare)
<expresie>-(postdecrementare)
Semnificaia acestuia este urmtoarea:
--<expresie> este echivalent cu <expresie> := <expresie>-1
Diferena dintre preincrementare i postincrementare:
Fie urmtoarele dou expresii:
1. A:=++B
2. A:=B++
si B are valoarea iniial 4.
Pentru prima expresie, n urma efecturii calculelor B va avea valoarea 5 i A
va avea valoarea 5 dat fiind faptul c prima aciune este aceea de incrementare
urmat de atribuire. n al doilea caz, B are valoarea 5 iar A=4, dat fiind faptul c
atribuirea se face naintea incrementrii.
Operatorii relaionali:
<OperatorRelaional>::=
= = | != | < | <= | > | >=
Cu semnificaia:
= = egal
!= diferit
<= mai mic sau egal
>= mai mare sau egal, etc.
Se folosesc n construcii de forma:
<expresie1><OperatorRelaional><expresie2>
20

Algoritmi i structuri de date

O structur de forma anterioar este denumit expresie relaional i este


evaluat la una dintre valorile logice de adevr True (1) sau False (0)
Exemplu: Fie expresia 2<3 ; n urma comparrii operanzilor (constante
numerice n acest caz) se constat c valoarea logic este adevr ceea ce
conduce la evaluarea expresiei 2<3 la valoarea 1 (Adevrat).
Operatorii logici:
<OperatorLogic> ::= && | || | !
Cu semnificaia:
&& - operatorul AND (I) conjuncia
|| - operatorul OR (SAU) disjuncia
! - operatorul NOT (negaia)
Exemple:
(a<b) && (b !=c) , a || (!a)
Operatori de deplasare:
<OperatorDepalsare>::= << | >>
Sunt operatori binari utilizai n construcii de forma:
<expresie1><OperatorDeplasare><expresie2>
unde: expresiile sunt evaluate n prealabil la valori ntregi;
Operatorii de deplasare corespund operaiilor de deplasare pe bii, efectul
acestora fiind acela de a provoca deplasarea biilor din reprezentarea binar a
ntregului dat de expresia 1 la stnga (<<) sau la dreapta (>>) cu un numr de
poziii specificat de valoarea ntreag a expresiei 2.
Regul: Prin aceste operaii de deplasare, biii deplasai n afara limitei de
reprezentare se pierd, iar biii eliberai sunt completai cu 0 sau 1 n funcie de
semnul ntregului respectiv: pozitiv sau negativ:
A<<B are semnificaia de deplasare a biilor din reprezentarea binar a lui
A cu B poziii la stnga
A>>B are semnificaia de deplasare a biilor din reprezentarea binar a lui
A cu B poziii la dreapta
Operatorii la nivel de bii:
Acetia corespund operatorilor logici (boolean): I, SAU, Negaie, SAU
EXCLUSIV
<OperatoriBoolean>::= & | | | ~ | ^
& - I
| - SAU
~ - Negaia
^ - SAU EXCLUSIV

21

Algoritmi i structuri de date

Operatorii la nivel de bii se utilizeaz n manevrarea biilor din reprezentrile


binare ale operanzilor ntregi sau caracter.
Exemplu:
Fie A i B dou numere ntregi ale cror reprezentri pe 16 bii sunt:
A= 0001 0010 1010 1011 i
B= 1101 1111 0000 0101
Atunci:
A&B= 0001 0010 0000 0001
A|B = 1101 1111 1010 1111
Operatori de asignare (atribuire):
<OperatorAsignare>::= = | += | -= | *= | /= | %= | >>= | <<= | &= | != | ^=
Cea mai simpl operaie de asignare este cea format prin construcia:
<variabil> = <expresie>
avnd semnificaia urmtoare: expresia din partea dreapt a operatorului de
asignare este evaluat iar rezultatul evalurii (o valoare) este atribuit variabilei din
partea stng a operatorului =.
n cazul general, cnd avem asignri de forma:
<variabil> <operator> = <expresie>
unde: <operator>::=+|-|*|/|%|>>|<<|&|!|^ , structur este echivalent cu:
<variabil>=<variabil> <operator> <expresie>
Operatorul condiional:
Este un operator ternar format din dou simboluri ? i : i folosit n construcii
de forma:
<expresie1>? <expresie2>:<expresie3>
Semnificaie: expresiile 1,2,3 sunt evaluate i valoarea de evaluare a expresiei
globale este valoarea expresiei 2 dac expresia 1 este adevrat altfel valoarea
expresiei 3 dac expresia 1 este fals.
Exemple: (A<B) ? A : B
(A= =0) ? egal cu zero : diferit de zero
Operatorul secvenial ,:
Se folosete n construcii de forma:
<expr1> , <expr2> , <expr3> , . , <exprn>
Expresia global de mai sus se evaluaz de la stnga la dreapta, valoarea final este
cea rezultat n urma evalurii ultimei sub-expresii exprn.
Operatorul de conversie explicit ( cast)
Efectul operatorului cast este conversia explicit a unei expresii la un alt tip de
dat dect cel implicit. Utilizarea operatorului se face prin construcii: ( tip)
expresie, unde tip - reprezint tipul de date spre care se face conversia.
Expresiile de forma var=expresie semnific evaluarea prima dat a expresiei, i
dac tipul rezultatului obinut este diferit de cel al variabilei, se realizeaz
conversia implicita la tipul variabilei. n conversiile explicite, operatorul cast apare
22

Algoritmi i structuri de date

n expresii de forma: var = (tip) expresie i prin aceast operaie, variabila


primete valoarea la care a fost evaluat expresia convertita la tipul explicit tip.
C. Unitile sintactice
Unitile sintactice sunt elemente complexe alctuite prin combinarea a dou
sau mai multe uniti lexicale.
C.1. Expresii
Expresiile sunt secvene de operatori i operanzi. n funcie de tipul operatorilor
utilizai, expresiile pot fi: expresii aritmetice, expresii logice, relaionale, etc.
Operanzii care intr n alctuirea expresiilor pot fi:
- variabile
- constante
- apel de funcii
- alte expresii
C.2. Instruciuni
Instruciunile sunt propoziii care respect regulile limbajului de programare n
care sunt redactate. Instruciunile pot fi simple sau compuse.
A. Instruciunea expresie: orice expresie urmat de caracterul ; devine
instruciune.
Sintaxa instruciuni expresie este urmtoarea:
<expresie>;
Exemplu: a=b+c;
B. Instruciunea bloc: este format din simbolurile { } n interiorul crora
sunt scrise alte instruciuni. Ordinea de execuie a instruciunilor grupate
este ordinea apariiei acestora n bloc (instruciunile din bloc se execut
secvenial):
Sintaxa:
{
<instruciune_1>
<instruciune_2>

<instruciune_n>
}
Rolul acestei instruciuni este de a grupa mai multe instruciuni simple,
compuse sau alte instruciuni bloc.
C. Instruciunile de ramificare
- corespund structurilor alternative: decizie simpl i decizie multipl.
Instruciunea if
Sintaxa instruciunii:
if ( <expresie> )
<instruciune_1>
23

Algoritmi i structuri de date

else
<instruciune_2>
sau:
if ( <expresie> )
<instruciune_1>
Unde, instruciunile 1 sau 2 pot fi de asemenea instruciuni compuse sau bloc
de instruciuni.
Efectul instruciunii:
<expresia> este evaluat i dac valoarea obinut n urma evalurii este
diferit de 0 se execut instruciunea 1, altfel se execut instruciunea 2.
Cele dou instruciuni 1 i 2 se exclud reciproc.
Instruciunea IF simpl nu conine partea else <instruciune 2>
Instruciunea IF complet conine ambele ramuri.
Exemple:
if

(a!=0)
b=1/a;

If

(a>=b)
maxim=a;

if

else

maxim=b;

(delta>=0)
{
x1=(-b-sqrt(delta))/2*a;
x2=(-b+sqrt(delta))/2*a;
}

Sintaxa limbajului C permite ca n cadrul instruciunilor compuse ce constituie


cele dou ramuri ale structurii alternative IF s existe alte structuri alternative IF. O
astfel de construcie se denumete structur alternativ imbricat sau instruciuni IF
imbricate.
Instruciunea switch
- reprezint o generalizare a structurii alternative (if).
- se poate nlocui printr-o secven de structuri alternative imbricate.
n structura instruciunii switch este regsit o instruciune simpl de a crei
prezen depinde execuia corect a instruciunii alternative switch. Aceasta
instruciune are sintaxa:
break;
Rolul instruciunii break este de a ntrerupe execuia instruciunii curente i de a
trece la execuia urmtoarei instruciuni ce urmeaz instruciunii switch.
Sintaxa general:
switch (<expresie>)
{
case <val1> : <instruciune_1>; break;
case <val2> : <instruciune_2>; break;

case <valn> : <instruciune_n>; break;


[default : <instruciune_default>;] //opional
24

Algoritmi i structuri de date

};
Efectul instruciunii:
- Se evalueaz expresia dintre paranteze la o valoare.
- Se caut secvenial valoarea la care a fost evaluat expresia n lista
de valori val1, val2, valn.
- Dac nu se gsete nici o valoare val1, val2,valn egal cu valoarea la
care a fost evaluat expresia se va executa, dac este precizat,
instruciunea default sau efectul instruciunii switch este nul
- Dac s-a gsit o valoare egal cu valoarea expresiei se va executa
instruciunea corespunztoare i se va prsi structura alternativ.
Exemplu: Afiarea numelui zilei din sptmn creia i corespunde un numr
1,2,...,7:
switch (numar)
{
case 1: printf(\n Luni);break;
case 2: printf(\n Marti); break;
case 3: printf(\n Miercuri); break;
case 4: printf(\n Joi); break;
case 5: printf(\n Vineri); break;
case 6: printf(\n Sambata); break;
case 7: printf(\n Duminica); break;
default:printf(\n nu este numar 1,2,3,4,5,6,7);
}

D. Instruciunile de ciclare
n pseudocod am evideniat cele trei tipuri de structuri repetitive. Acestora le
corespund trei instruciuni de ciclare n limbajului C:
Structura repetitiv precondiionat -> instruciunea while
Structura repetitiv postcondiionat -> instruciunea do while
Structura repetitiv cu numr prefixat de repetri -> instruciunea for
Instruciunea while
Sintaxa:
while (<expresie>) //antetul instruciunii
<instruciune>
//corpul instruciunii
unde: <instruciune> poate fi o instruciune simpl, compus, i poate conine
alte instruciuni while, caz n care spunem c avem o instruciune while imbricat
Efectul instruciunii:
n primul pas, se evalueaz expresia dintre paranteze. Dac rezultatul evalurii
este o valoare non-nul (adevrat) se execut instruciunea din corpul while i
procesul se reia prin reevaluarea expresiei. Dac rezultatul unei evaluri este nul, se

25

Algoritmi i structuri de date

ntrerupe execuia instruciunii while, i programul se continu cu urmtoarea


instruciune ce urmeaz instruciunii while.
Simplificat, o instruciune while are semnificaie:
Ct timp expresia este adevrat repet corpul de instruciuni
Observaie: corpul instruciunii while este posibil s nu se execute nici o dat, n
situaia n care expresia evaluat este de la nceput fals.
Exemplu 1:Se tiprete valoarea unui contor pn cnd acesta devine mai mare
dect 10. Valoarea iniial a contorului este 0.
i=0;
while (i<=10)
{
printf (%d, i);
i++;
}

Exemplu 2: Se calculeaz suma primelor 10 numere naturale, respectiv: 1+2+3+


+10
i=1;s=0; //iniializarea
while (i<=10)
{
s=s+i;
i++;
}

Instruciunea dowhile
Sintaxa:
do
<instruciune>
while (<expresie>);

//corpul

<instruciune>: poate fi o instruciune simpl, compus, i poate conine alte


instruciuni dowhile, caz n care spunem c avem o instruciune repetitiv
imbricat.
Efectul instruciunii dowhile:
- n primul pas se execut corpul instruciunii dowhile
- Se evalueaz expresia dintre paranteze
- dac rezultatul evalurii expresiei este adevrat se reia procesul prin
execuia corpului instruciunii, n caz contrar se ncheie execuia
instruciunii dowhile
Semnificaie:
Execut blocul de instruciuni pn cnd expresia devine fals
Observaie: corpul unei instruciuni repetitive postcondiionate se execut cel puin
o dat. (chiar dac expresia este fals de la prima evaluare).

26

Algoritmi i structuri de date

Exemplu 1. Citete un caracter de la tastatur pn cnd se introduce caracterul


0.
do

scanf(%c,&caracter);
}
while (caracter<>0);

Exemplu 2. Calculeaz suma unor numere ntregi citite de la tastatur pn la


introducerea unui numr negativ sau zero:
suma=0;
do
{
scanf(%d, &nr);
suma=suma+nr;
} while (nr>0) ;

//citeste numrul curent

Exemplu 3: Programul urmtor citete un text (terminat prin Enter) i afieaz


numrul de vocale din compunerea textului.
#include <stdio.h>
void main()
{
char t; //caracterul curent
int contor=0;
printf("Introduceti textul: ");
do
{
scanf("%c",&t); //citire caracter cu caracter textul
if ((t=='a')||(t=='e')||(t=='i')||(t=='o')||(t=='u'))
contor++;
} while (t!=10); //10 este codul ASCII pentru Enter
printf("Numarul de vocale: %d",contor);
}

Instruciunea for
Sintaxa:
for ( <expresie1> ; <expresie2> ; <expresie3> )
<instruciune>
Unde:
-

cele trei expresii din antetul instruciunii sunt separate prin


caracterul ;
<instruciune> poate fi simpl, compus sau poate conine o
alt instruciune repetitiv for.
<expresie1> - reprezint expresia de iniializare. (iniializarea
contorului)
27

Algoritmi i structuri de date

<expresie2> - reprezint o expresia ce este evaluat i de a


crei valori de evaluare depinde repetarea corpului
instruciunii.
<expresie3> - reprezint expresia de actualizare (actualizarea
contorului)

Efect:
- n primul pas, se evalueaz expresia1, respectiv se execut secvena de
iniializare
- Se evalueaz expresia2 (aceast expresie are acelai rol ca <expresie> din
structura while sau dowhile )
- Dac rezultatul evalurii anterioare este adevrat se execut corpul
instruciunii for i apoi se execut secvena de actualizare specificat prin
expresie3
- Dac rezultatul este fals, se ncheie execuia for.
Exemplu 1: Afiarea primelor 10 numere naturale.
for ( i=1; i<=10; i++)
printf(\n %d, i);

Exemplu 2: Calcularea lui n!


fact=1;
for (i=1;i<=n;i++)
{
fact=fact*i;
}

E. Apelul funciilor de intrare - ieire


Operaiile de intrare-ieire (citirea datelor de intrare i afiarea rezultatelor) nu
se realizeaz prin instruciuni ale limbajului C, ci prin apelarea unor funcii
speciale, definite n bibliotecile de funcii ale limbajului C.
Funciile printf, scanf
- printf i scanf sunt funcii predefinite de intrareieire definite n biblioteca stdio
Pentru a putea folosi aceste funcii ntr-un program C este necesar n prealabil
specificarea prin directiv de preprocesare a fiierului antet (header) ce conine
prototipurile acestora:
#include <stdio.h> //la nceputul programului surs
Prin apelul funciei printf se realizeaz afiarea pe ecran a mesajelor, datelor,
valorilor unor variabile sau rezultate finale sau intermediare ale programului.
Sintaxa apelului funciei printf:
printf(sir de formatare, expr1, expr2, )
unde:

28

Algoritmi i structuri de date

expr1, expr2, sunt expresii separate prin virgul i pot reprezenta


variabile, constante sau orice expresie evaluabil (aritmetic, logic,
relaional, etc.)
- sir de formatare este ncadrat de caracterele ghilimele i reprezint
forma sub care vor fi tiprite pe ecran valorile expresiilor.
Efect: prin apelul funciei printf se realizeaz afiarea pe ecran a datelor ntr-un
anumit format.
Sir de formatare poate conine doar text i lista de expresii poate fi vid, caz n
care prin apelul funciei printf se tiprete pe ecran textul specificat n acest cmp.
Exemple:
printf(exemplu de program!)
printf(\n acest text este afisat pe ecran)
n general sir de formatare poate conine:
- Text
- Specificatori de format
- Secvene escape
Secvenele escape sau comenzi de control pot fi de dou tipuri: secvene
negrafice i secvene grafice sau afiabile. Numrul specificatorilor de format este
egal cu numrul de expresii separate prin virgul. Fiecare specificator de format se
refer la modul de redare a valorii expresiei de pe poziia corespondent:
Secvenele escape negrafice:
\n trecere la linie nou
\t deplasare la dreapta cu un tab
\b deplasare la stnga cu o poziie
\r revenire la nceputul rndului
\f trecere la pagina urmtoare
\a semnal sonor
Secvenele escape grafice:
\ afiare apostrof
\ afiare ghilimele
\\ - afiare backslash
\xCC afiare caracterul al crui cod ASCII n hexazecimal este dat prin
secvena CC de cifre n baza 16.
Specificatorii de format
<specificator de format>::= % [-] [m.[n]] [l] <conversie>
<conversie>::= d|o|x|X|u|s|c|f|e|E|g|G
-

caracterul dac apare semnific cadrarea la stnga a rezultatului


corespunztor specificatorului curent
m - specific numrul minim de caractere ale datei ce va fi afiate
.n - semnific n cazul datelor numerice numrul de cifre dup punctul
zecimal
l - semnific conversie din formatul intern long n formatul extern.
29

Algoritmi i structuri de date

Conversie specific tipul de conversie din formatul intern (reprezentarea


intern) n cel extern (cum va fi afiat):
d ntreg int cu semn
o ntreg n octal
x, X ntreg n hexazecimal (litere mici sau MARI pentru cifrele a,b,f
respectiv A,B,F)
u ntreg fr semn (unsigned)
c din codul ASCII al reprezentrii binare interne -> caracterul
s din irul de coduri ASCII -> irul de caractere
f conversie din float sau double n formatul extern zecimal
e,E - conversie din float sau double n formatul cu mantis i exponent
g,G conversiile de la f i E sau f i e dar alegerea se face pentru numrul
minim de poziii
Exemple:
printf(valoarea intreaga a= %d,a);
printf(realul a= %5.2f,a);
printf(%d + %d = %d,a,b,a+b);
printf(suma este %4.4f , suma);
Sintaxa apelului funciei scanf :
printf(sir de formatare, adr1, adr2, )
Efect: prin apelul funciei scanf se realizeaz citirea de la tastatur datelor ntrun anumit format.
irul de formatare are aceeai semnificaie ca i la funcia printf.
irul de adrese: adr1, adr2,adrn se asociaz specificatorilor de format
pstrnd ordinea. O adres se specific prin precedarea numelui variabilei de
operatorul unar adres notat &. Astfel var este numele unei variabile, iar &var este
adresa de memorie la care se stocheaz valoarea variabilei.
Excepie: nu se utilizeaz operatorul adres n cazul n care variabila este o
variabil special pointer, respectiv cnd numele variabilei este o adres de
memorie.
Exemple:
scanf (%d, &x);
scanf (%d %d %f,&a,&b,&x);
scanf(%d %d %d, &x[0], &x[1], &x[2]);
scanf(%s %s %d, nume, prenume, &varsta);
30

Algoritmi i structuri de date

Structura unui program C


Redactarea unui program n limbajul C este liber atta timp ct se respect
regulile sintactice, semnele de punctuaie i se folosesc construciile specifice n
mod corect. Programul C este constituit dintr-o secven de instruciuni, ntr-o
ordine determinat, corespunztor algoritmului de rezolvare a problemei
considerate. Instruciunile programului pot fi instruciuni de declarare sau definire
a unor variabile, funcii i instruciuni care se regsesc n corpul funciilor.
Structura unui program C este urmtoarea:
<program>::= [<directive de procesare>]
[<instruciuni de declarare a variabilelor globale>]
<funcii>
Directivele de preprocesare: sunt numite i comenzi de preprocesare i apariie
lor n program nu este obligatorie. Sunt marcate prin simbolul # (diez) care le
precede.
Exemplu:
/* includerea bibliotecii standard de funcii de intrare-ieire */
#include <stdio.h>
Instruciuni de declarare a variabilelor globale: opional; Anumite variabile
sunt folosite n cadrul mai multor funcii, avnd o utilizare global., fapt pentru
care aceste variabile sunt declarate n secvena de declarare a variabilelor cu
caracter global (n afara oricror funcii ale programului).
Funcii: sunt uniti independente ale unui program prin care se reprezint un
subalgoritm (procedura, modul) al unui algoritm de rezolvare a unei probleme date.
Clasificare funcii:
1. Funcii standard au nume prestabilit, sunt constituite n biblioteci de
funcii standard
2. Funcii utilizator au nume ales de ctre utilizator i sunt construite de
ctre acesta
3. Funcia principal (main)
Un program C conine obligatoriu funcia principal main. Putem avea mai
multe funcii utilizator declarate i definite sau alte funcii standard ce sunt apelate
dar n prealabil au fost incluse, prin directive de preprocesare, bibliotecile din care
fac parte.
Sintaxa funciei main:
void main (void)
{
//declaraii locale
//instruciuni

}
31

Algoritmi i structuri de date

Exemplu de program C:
#include <stdio.h> //rezolvarea ec. de gradul II
#include <math.h>
void main(void)
{
double a,b,c,x1,x2,delta;
printf("\nDati coeficientii ecuatiei de gradul II: ");
scanf("%lf,%lf,%lf",&a,&b,&c);
if (a==0)
{
if (b==0)
printf("ecuatie imposibila!");
else
{
x1=-c/b;
printf("Solutia ecuatiei de grad I: %lf",x1);
}
}
else
{
delta=b*b-4*a*c;
if (delta>=0)
{
x1=(-b-sqrt(delta))/2*a;
x2=(-b+sqrt(delta))/2*a;
printf("Solutiile ec. gr. II: %lf, %lf",x1,x2);
}
else
printf("Ecuatia de gr. II nu are solutii reale!");
}
}

Probleme:
1. S se citeasc un sir de n numere ntregi de la tastatur i s se calculeze suma
acestora.
2. S se citeasc n numere ntregi i s se numere cte elemente sunt pozitive.
3. S se scrie un program C pentru afiarea relaiei dintre dou numere: < , > sau =.
4. Scriei un program C pentru rezolvarea ecuaiei de gradul II
Obs. Pentru scrierea unui program care rezolv ecuaia de gradul 2 este necesar
cunoaterea funciei de extragere a radicalului: sqr(<parametru>) al crei prototip
se afl n biblioteca math.h
5. S se scrie un program C care citete dou numere reale i un caracter: + , - , /
sau * i afieaz rezultatul operaiei aritmetice corespunztoare.
6. S se scrie un program C care afieaz toi multiplii de k mai mici dect o
valoare dat n.
7. S se scrie un program C care calculeaz i afieaz puterile lui 2 pn la n,
adic 2, 22,23, , 2n .
32

Algoritmi i structuri de date

III.

FUNCTII. TRANSMITEREA PARAMETRILOR.


RECURSIVITATE.

Funcia reprezint o unitate de sine stttoare a unui programului C, prin


intermediul creia se descrie un subalgoritm de rezolvare a unei subprobleme.
ntr-un capitol precedent am subliniat importana subalgoritmilor n descrierea
unor metode de rezolvare a problemelor. Programele C permit o traducere a
descrierilor algoritmice bazate pe subalgoritmi prin posibilitatea definirii unor
funcii utilizator care trateaz aspecte pariale ale rezolvrii. Un program C poate
conine funcia principal main dar i alte funcii definite de ctre programator sau
funcii predefinite din bibliotecile standard.
Limbajul C este suplimentat prin biblioteci care conin funcii nrudite prin
intermediul crora se realizeaz diferite operaii. Aceste funcii de biblioteci pot fi
apelate n programele dezvoltate dac n prealabil s-a inclus biblioteca
corespunztoare prin directive de preprocesare #include. (spre exemplu funciile
standard scanf i printf ale bibliotecii stdio)
Funciile definite de utilizator au urmtoarea sintax:
<tip>NumeFuncie(<tip1><arg1>,,<tipn><argn>)
{
<instruciuni de declarare de tip a variabilelor locale>

<instruciuni>

}
- Corpul funciei cuprinde ntre acolade {} conine partea de declarare a
variabilele locale funciei respective i partea de prelucrare a datelor:
secven de instruciuni prin care se prelucreaz variabilele locale sau
altele cu caracter global.
- n antetul unei funcii este precizat tipul de dat pe care l returneaz
<tip>. Acesta poate fi void, caz n care funcia nu returneaz nimic.
- Parantezele rotunde (.) ce urmeaz numelui funciei delimiteaz lista
argumentelor funciei.
- Fiecrui argument i este precizat tipul i numele sub care este referit n
cadrul funciei curente.
Prototipul funciei: este format de antetul funciei din care poate s lipseasc
numele parametrilor formali:
<tip>NumeFuncie(<tip1>,,<tipn>) ; //declararea unei funcii
La definirea unei funcii, argumentele precizate n antetul acesteia se numesc
parametrii formali. La apelul funciei, parametrii formali sunt nlocuii de
parametrii actuali:
Apelul unei funcii se face prin construcia:

33

Algoritmi i structuri de date

NumeFuncie(pa1, pa2, , pan);


Categorii de funcii:
1. Funcie fr tip i fr parametrii
Exist posibilitatea definirii de ctre utilizator a unor funcii care nu
returneaz nimic i lista parametriolor acestora este vid:
void NumeFuncie ()
{
//corpul functiei
}
Apelul funciei se face printr-o construcie de forma:
NumeFuncie();
Exemplu: Se definete o funcie Cifre care tiprete cifrele arabe. Funcia
va fi apelat n funcia main.
#include <stdio.h>
void Cifre()
{
int i; //declaraie de variabil local
for(i=0;i<=9;i++) printf(%d,i);
}
void main(void)
{
Cifre();
//apelul functiei
}

Cifre

2. Funcii cu tip
Dac unei funcii i este precizat tipul (diferit de void) acest lucru ne spune
c funcia returneaz codului apelant o valoare de acest tip. n caz contrar, funcia
nu returneaz valoare. Tipul funciei reprezint n fapt tipul de dat al valorii pe
care o returneaz. Instruciunea return, folosit n cadrul funciei cu tip definite,
are sintaxa:
return <expresie>;
i are rolul de a reveni n programul apelant de a returna acestuia o valoare de
tipul precizat.
Funcia poate fi apelat printr-o instruciune de asignare:
ValoareReturnat= NumeFuncie(); //membrul stng al instruciunii este o
variabil de tipul returnat de funcie
34

Algoritmi i structuri de date

Exemplu: Programul C conine o funcie CitireValoare, care citete o valoare


numeric ntreag de la tastatur i returneaz aceast valoare funciei main
apelant, pe care aceasta o va afia pe ecran.
#include <stdio.h>
int CitesteValoare()
{
int numar; //declaraie de variabil local
scanf(%d,&numar);
return numar;
}
void main(void)
{
int valoare;
valoare=CitesteValoare();//apel
printf(valoarea este: %d,
valoare);
}

void main(void)
//varianta compacta
{
printf(valoarea este: %d,
CitesteValoare());
}

3. Funcii parametrizate
Funciile cu parametri corespund acelor subalgoritmi care prelucreaz date de
intrare reprezentnd rezultate intermediare ale algoritmului general. Intrrile
funciei sunt descrise prin lista parametrilor formali ce conine nume generice ale
datelor prelucrate n corpul funciilor. Parametrii efectivi sunt transmii la apelul
funciilor, acetia nlocuind corespunztor parametrii generici specificai. n
limbajul C transmiterea parametrilor actuali funciilor apelate se face prin valoare:
nelegnd prin aceasta c valorile curente ale parametrilor actuali sunt atribuite
parametrilor generici ai funciilor.
Exemplu:
int minim(int a, int b)
{ return ( (a>b)?a:b) }

//apelul din functia main


int nr1,nr2, min;
nr1=7; nr2=6;
min=minim(nr1,nr2); // la apel a va primi valoarea 7 i b valoarea 6
min=minim(nr2,nr1); // la apel a va primi valoarea 6 i b valoarea 7

Fie funcia:
tip NumeFuncie(tip1 pf1, tip2 pf2, , tipn pfn)
{};
La apelul:
NumeFuncie(pa1, pa2, , pan);
se vor transmite prin valoare parametrii actuali i fiecare parametru formal din
antetul funciei este nlocuit cu valoarea parametrului actual. Dac parametrul
35

Algoritmi i structuri de date

actual este o expresie, aceasta este evaluat la o valoare, ulterior este copiat n
parametrul formal corespunztor.
Observaie: modificrile aduse parametrilor formali n cadrul funciei
apelate nu afecteaz valorile parametrilor actuali. Exempul urmtor
evideniaz acest aspect:
#include <stdio.h>
void putere(int n) //ridic la ptrat valoarea n i afieaz
rezultatul
{
n=n*n;
printf( valoarea lui n in functie este %d,n); //n este 25
}
void main(void)
{
int n=5;
printf( valoarea lui n inainte de apel este %d,n);
// efectul: valoarea lui n inainte de apel este 5
putere(n);
// efectul: valoarea lui n in functie este 25
printf( valoarea lui n dupa de apel este %d,n);
//efectul: valoarea lui n dupa de apel este 5 (!nu s-a
modificat parametrul actual)
}

n multe situaii este necesar ca efectul operaiilor din corpul unei funcii apelate
asupra parametrilor de intrare s fie vizibil i n corpul apelant. n exemplul
anterior efectul dorit este acela ca variabila n dup apel s pstreze rezultatul
ridicrii la ptrat. Transmiterea prin valoare nu permite acest lucru. n schimb,
transmiterea prin adres realizat prin intermediul variabilelor de tip pointer
asigur modificarea valorilor parametrilor actuali.
Pentru a nelege mecanismul transmiterii parametrilor prin adres n limbajul
C, vom defini n continuare noiunea de pointer.
Pointeri
Pointer = variabil care are ca valori adrese de memorie.
Sintaxa de declarare a unei variabile pointer:
tip * p; // variabil pointer spre tip
Prin aceast declaraie s-a introdus o variabil ale crei valoare este adresa unei
zone de memorie ce poate reine o dat de tipul tip.
Fie x o variabil simpl de tipul tip:
tip x;
i p un pointer (variabil de tip pointer) care are ca valoare adresa variabilei x.
Pentru a face o atribuire unei variabile de tip pointer p se folosete construcia:
p=&x; // semnificaia: lui p i se atribuie adresa variabilei x.

36

Algoritmi i structuri de date


Variabila x:
Variabila pointer p:

Valoarea lui x

Adresa lui x

Prin construcia &x ne referim la adresa variabilei x.


Prin construcia *p ne referim la valoarea variabilei x.
- Operatorul * se numete operator de indirectare
- Operatorul & se numete operator adres
Expresia *&x are aceiai semnificaie cu expresia x.
Transmiterea parametrilor prin adres
Transmiterea prin adres se realizeaz prin variabile de tip pointer i ne
asigur de modificarea valorii parametrilor actuali.
Transmiterea prin prin adresa este realizat astfel:
1. Parametrii formali ai funciei se declar ca fiind de tip pointer;
tip NumeFuncie(tip1 *p1, tip2 *p2, , tipn *pn)
{};
2. La apel, argumentele funciei sunt adresele parametrilor actuali;
NumeFuncie(&p1, &p2, , &pn)
Exemplu:
#include <stdio.h>
void putere(int *p) //ridic la ptrat valoarea n i afieaz
rezultatul
{
*p=*p* *p;//ridic la ptrat valoarea referit de p: *p
printf( valoarea lui n in functie este %d,*p); //n este 25
}
void main(void)
{
int n=5;
printf( valoarea lui n inainte de apel este %d,n);
// efectul: valoarea lui n inainte de apel este 5
putere(&n);
// efectul: valoarea lui n in functie este 25
printf( valoarea lui n dupa de apel este %d,n);
//efectul: valoarea lui n dupa de apel este 25
modificat parametrul actual)
}

37

(!s-a

Algoritmi i structuri de date

Exerciiu: S se scrie o funcie care interschimb valorile a dou variabile


transmise ca parametrii.
Pentru construirea funciei cerute este util transmiterea prin adres, deoarece,
efectul interschimbrii trebuie s fie vizibil i n codul apelant. Prezentm n
continuare dou variante de implementare, prima dintre acestea folosete
transmiterea implicit prin valoare, fapt pentru care nu corespunde rezultatului
dorit. Cea de-a doua este varianta corect, folosindu-ne de transmiterea
parametrilor prin adres (prin pointeri).
void interschimbare(int
b)
{
int auxiliar;
auxiliar=a;
a=b;
b=auxiliar;
}

a,

int

//Apelul:
int x,y;
x=1;
x=2;
interschimbare(x,y)
//Efectul
Inainte de apel:
x=1
y=2
Dup apel:
x=1
y=2

void interschimbare(int *a,


int *b)
{
int auxiliar;
auxiliar=*a;
*a=*b;
*b=auxiliar;
}

//Apelul:
int x,y;
x=1;
x=2;
interschimbare(&x,&y)
//Efectul
Inainte de apel:
x=1
y=2
Dup apel:
x=2
y=1

Probleme:
1. Dezvoltai un program C care determin minimul dintr-un ir de numere
citite de la tastatur (fr a utiliza tablouri), punnd n eviden funcia care
determin minimul dintre dou valori.
2. Scriei o funcie care rezolv ecuaia de gradul II. Funcia va avea 5
argumente: coeficienii ecuaiei (a,b,c) i posibilele soluii (x1,x2). Funcia
returneaz un numr ntreg cu semnificaia:
- -1, ecuaia nu este de gradul II
- 0 , ecuaia are soluii complexe
- 1, ecuaia are soluii reale
Primii trei parametrii se transmit prin valoare, ultimii doi - prin adres.

38

Algoritmi i structuri de date

RECURSIVITATE
Recursivitatea se obine prin instruciunea de apel a unei funcii n corpul
definirii funciei respective:
<tip>NumeFuncie(<tip1><arg1>,,<tipn><argn>)
{
<instruciuni de declarare de tip a variabilelor locale>

<instruciuni>

NumeFuncie(pa1, pa2, , pan); //auto apelul funciei

}
La fiecare apel al funciei recursive, pe stiva programului sunt depuse noul set
de variabile locale (parametrii). Chiar dac variabile locale au acelai nume cu cele
existente nainte de apelarea funciei, valorile lor sunt distincte, i nu exist
conflicte de nume. Practic, ultimele variabile create sunt luate n considerare n
operaiile coninute de funcie.
Problem 1: S se calculeze P(n)=n! printr-o funcie recursiv.
Analiza problemei: Pornind de la observaia c produsul P(n)=1*2*(n-1)*n se
mai poate formula ca:
P(n)=P(n-1) * n, vom defini o funcie factorial care trateaz problema
determinrii lui P(k) pe baza formulei anterioare, presupunnd c P(k-1) este
calculabil dup acelai procedeu. P(k-1)=P(k-2)*(k-1).
Funcia autoapelant trebuie s conin o condiie de terminare a recursivitii.
n problema calculului n!, condiia de terminare a recursivitii se deduce din
observaia c 1! are valoarea 1, ceea ce nu mai necesit un calcul suplimentar.
Apelul iniial al funciei factorial se va face pentru parametrul actual nreprezentnd data de intrare a programului. Funcia returneaz la ieirea din apel
rezultatul dorit n!.
int factorial(int k)
{
if (k>1)
return (k*factorial(k-1));
else
return 1;
}

//apelul functiei
int p;
p=factorial(n);

Execuia pas cu pas:


39

Algoritmi i structuri de date

Considerm n=3
1. La apelul iniial factorial(n) se transmite valoarea 3 parametrului formal k i
se pred controlul funciei apelate
2. Din condiia adevrat 3>=1 rezult amnarea revenirii din funcie i un
nou apel factorial(2); la ieirea din primul apel se va return 3*factorial(2).
3. Apelul factorial(2) va produce transmiterea valorii 2 la parametrul formal
k i predarea controlului funciei apelate pentru valoarea curent a
parametrului
4. Condiia adevrat 2>=1 produce o nou apelare factorial(1) cu amnarea
revenirii din apelul curent; la ieirea din acest al doilea apel se va return
2*factorial(1) .
5. Apelul factorial(1) va produce transmiterea valorii 1 la parametrul formal
k i predarea controlului funciei apelate pentru valoarea curent a
parametrului
6. Din condiia fals 1>1 rezult c la ieirea din acest apel se va return 1 i
funcia nu se mai apeleaz.
7. Revenirea din ultimul apel este urmat de revenirile n cascad din apelurile
precedente n ordinea invers, ceea ce conduce la rezultatul 3*2*1 = 6.
factorial(n)

n* factorial(n-1)
n* (n-1)*factorial(n-2)
n* (n-1)* (n-2)*factorial(n-3)
...
n* (n-1)* (n-2)*...*factorial(1)
n* (n-1)* (n-2)*...* (1)

Recursivitatea poate fi de mai multe feluri, n funcie de numrul de apeluri


coninute de funcia (subalgoritmul) recursiv:
1. recursivitate liniar: funcia conine cel mult un autoapel exemplu funcia factorial descris anterior
2. recursivitate neliniar: funcia conine dou sau mai multe apeluri
recursive exemplu fibonacci descris n continuare
Problema 2: Se cere determinarea celui de-al n-lea termen din irul lui
Fibonacci.
Analiza problemei:
Se cunoate c:
- primii doi termeni ai irului sunt 0,1:
o Fibonacci(0)=0
o Fibonacci(1)=1
- oricare termen este format prin nsumarea celor doi termeni
precedeni:
40

Algoritmi i structuri de date

Fibonacci(k)=Fibonacci(k-1)+ Fibonacci(k-2)

Funcia recursiv este:


int Fibonacci(int k)
{
if (k<=1)
return k;
else
return Fibonacci(k-2) + Fibonacci(k-1); //dou
autoapeluri
}

Problema 3. Determinarea celui mai mare divizor comun a dou numere a i b.


(prin funcie recursiv)
Analiza problemei:
cmmdc(a,b) poate fi:
1. a, dac b = 0
2. cmmdc(b, a mod b), dac b0
Prin a mod b se nelege restul mpririi ntregi a lui a la b
Pe baza observaiei precedente, funcia recursiv cmmdc se construiete astfel:
int cmmdc(int m, int n)
{
if(n==0)
return m;
return
cmmdc(n,m%n);
}

//autoapel

Recursivitatea este o tehnic costisitoare datorit spaiului de memorie blocat


pentru reinerea variabilelor locale create la fiecare apel. Exist diferite metode de
eliminare a recursivitii din programele C, pentru a prentmpina acest neajuns.
Descriem n continuare procedura de eliminare a recursivitii liniare:
1. Se utilizeaz o list care se iniializeaz ca fiind vid. n aceast list vor fi
salvai parametrii formali i variabilele locale ale funciei recursive. Operaiile
posibile cu lista folosit sunt: adugarea unui nou element n captul listei i
extragerea unui element din captul listei(vezi conceptul de stiv)
2. Cttimp condiia de continuare a recursivitii este adevrat execut:
- Adaug (salveaz) n list valorile actuale pentru parametrii
funciei recursive si variabilele locale
- Execut instruciunile funciei recursive
- Modifica valorile argumentelor funciei recursive (actualizarea
parametrilor)
3. Dac la terminarea pasului 2 lista nu este vid, se extrage mulimea de
variabile din captul acesteia, se calculeaz valoarea dorit i se executa
instruciunile ce apar dup apelul funciei recursive, apoi se trece la pasul 2.
Dac lista este vid se termin execuia algoritmului.
41

Algoritmi i structuri de date

IV.

TABLOURI. TEHNICI DE SORTARE.

Tablourile n limbajul C sunt variabile cu indici. Pot fi de dou categorii,


tablouri unidimensionale (vectori) sau multidimensionale. La nivel abstract, un
tablou unidimensional se poate defini ca o list ordonat de n valori de acelai tip:
x1,x2, ,xn. Fiecare poziie i din cadrul tabloului conine elementul xi, respectiv o
valoare de tipul specificat.
Declararea unui tablou unidimensional n limbajul C se face astfel:
<tip> NumeTablou [NumarElemente];
Unde <tip> este tipul de date al elementelor tabloului, NumarElemente este un
numr natural sau o constant cu valoare ntreag pozitiv prin care se precizeaz
numrul elementelor din tablou, NumeTablou este un identificator prin care este
denumit vectorul.
- Referirea unui element al tabloului se folosete numele tabloului i indicele
corespunztor. Spre exemplu: int x[100];
- Pentru a referi elementul de pe poziia i vom scrie x[i-1], dat fiind faptul c
primul element al irului este x[0].
- Tablourilor li se asociaz locaii de memorie consecutive, de aceeai lungime
n care sunt stocate valorile fiecrui element.
- Observaie: numele tabloului (spre ex. x) este o variabil a crei valoare este
adresa de memorie a primului element din ir (pointer).
Declararea tablourilor n-dimensionale:
<tip> NumeTablou [N1] [N2] [Nn];
Unde N1, N2 Nn reprezint numrul de elemente pe fiecare dimensiune.
- Referirea unui element al tabloului multidimensional se face prin
identificatorul tabloului (numele) i specificarea poziiei pe fiecare
dimensiune:
- NumeTablou [i1] [i2] [in].
Exemplu:
int A[20] [30];
//declararea unei matrice (tablou bi-dimensional) de 20 linii si 30 coloane.
- Primul element al tabloului bidimensional declarat mai sus este A[0][0], iar
numele tabloului conine adresa primului element memorat.
Observaie: Numele unui tablou n C este de fapt un pointer, avnd ca valoare
adresa primului sau element. La transmiterea vectorilor ca parametrii ai unor
funcii se va ine cont de acest aspect.
Exemplu:
int T[100]; //tablou de 100 intregi
Numele tabloului, T - reprezint adresa primului element al sau, respectiv,
adresa lui T[0].

42

Algoritmi i structuri de date

Tablourile sunt structuri de date:


- compuse: sunt formate din mai multe elemente (de acelai fel)
- ordonate: exist o relaie de ordine dat de poziia elementelor n
tablou
- omogene: toate elementele unui tablou au acelai tip
Tablourile unidimensionale vor fi denumite n continuare vectori.
Pointeri i tablouri. Operaii cu pointeri
ntre tablouri i variabilele pointer exist o strns legtur: numele unui tablou
este un pointer, fiind de fapt adresa primului element al tabloului respectiv:
tab[0]

tab[1]

tab[2]

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

tab[n]

tab
Exemplu:
int tab[100];
//tab este adresa elementului t[0]
Observaie: Deoarece memoria alocat unui tablou este o zon contigu, respectiv,
elementele tabloului sunt grupate, cunoscnd adresa primului element, printr-o
operaie elementar se poate accesa oricare alt element al tabloului.
Operaii cu pointeri:
Asupra pointerilor se pot efectua urmtoarele operaii:
- incrementare/decrementare
- adunarea/scdere cu un ntreg
- diferena a doi pointeri
Fie declaraia urmtoare:
<tip> *p; //p este un pointer la tipul <tip>
Efectul operaiilor de incrementare/decrementare este urmtorul:
p++ i ++p echivalent cu: p=p+dim
p-- i --p echivalent cu: p=p-dim
unde: dim reprezint dimensiunea exprimat n octei a unei variabile de tipul
<tip>
Exemplu.
int *p;
p++;
//p se mrete cu 2, deoarece o variabil de tipul int se memoreaz pe 2 octei
Adunarea i scderea unui ntreg
Fie: <tip> *p;
int k;
Expresiile: p+k, p-k sunt expresii valide n limbajul C, avnd semnificaiile
urmtoare:
43

Algoritmi i structuri de date

p+k - reprezint o adres de memorie; valoarea p+k este egal cu valoarea


lui p mrit cu k*dim , unde dim- dimensiunea exprimat n octei a tipului <tip>
p-k - are valoarea p micorat cu k*dim , unde dim- dimensiunea exprimat
n octei a tipului <tip>
Accesarea elementelor unui tablou poate fi fcut i prin operaii cu variabile
pointer:
dim
dim
dim
dim octeti
tab[0]

tab

tab+1

tab[1]

tab[2]

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

tab+2

tab[n]

tab+n

Exemplu:
int tab[100]; //tablou de 100 ntregi
int i;
(tab) adresa primului element tab[0]
(tab+1) adresa celui de-al doilea element tab[1]
(tab+i) reprezint adresa celui de-al (i-1) -lea element al tabloului, respectiv este
adresa elementului tab[i]
*(tab+i) reprezint valoarea elementului tab[i]
Diferena a doi pointeri:
Dou variabile de tip pointer prin care se refer elementele aceluiai tablou pot
fi sczute. Fie tabloul tab i doi pointeri p i q care adreseaz elementele tab[i] i
tab[j]. Diferena p-q este un numr ntreg k, reprezintnd numrul de elemente
care desparte cele dou adrese. Aceast dimensiune se poate determina prin
calculul elementar: k=(j-i).
ntre adresele p i q se afl un numr de (j-i) elemente, respectiv, (j-i)* dim
octei, unde dim - dimensiunea necesar reprezentrii unui element al tabloului.
dim
dim
tab[i]

tab+i

......................
dim *k

tab[i+k]

tab+(i+k)

iruri de caractere
Operaiile cu iruri de caractere se efectueaz prin apelul unor funcii de
bibliotec specifice. Prototipurile funciilor care manipuleaz iruri de caractere se
afl n fiierul antet string.h.
44

Algoritmi i structuri de date

Orice ir de caractere se va pstra ntr-o zon de memorie contigu. Ne


reamintim c unei variabile de tipul char i este necesar o zon de memorie de 1
octet, pentru reinerea codului numeric corespunztor (reprezentarea intern). Un
ir de caractere, declarat ca tablou unidimensional de tip char va ocupa o zon de n
octei terminat printr-un octet suplimentar cu valoarea '\0 caracterul nul, prin
care s-a marcat terminarea irului.
Ex: char sir[20];
Folosind relaia dintre tablouri i pointeri, putem utiliza n programe declaraii
de forma:
char *psir;
Accesarea caracterelor din compunerea irului va fi posibil prin variabile
indexate sau prin operaii cu pointeri:
sir[0] sau *psir codul numeric (ASCII) al primului caracter din sir
sir[1] sau *(psir) - codul numeric al celui de-al doilea caracter din sir
sir sau psir adresa primului caracter
sir+1 sau psir+1 adresa celui de-al doilea caracter din sir
Pentru manipularea irurilor de caractere, biblioteca standard string.h pune la
dispoziie funcii speciale dedicate operaiilor specifice.
Lungime unui ir de caractere
Lungimea unui ir de caractere este definit prin numrul caracterelor care intr
n compunerea irului. Funcia standard strlen este util n determinarea lungimii
unui ir de caractere:
unsigned strlen(const char *s)
Exemplu:
char const *psir = text
unsigned l;
l= strlen(psir); // lui l i se va atribui 4
//numrul de caractere, excluznd marcatorul NULL)
Copierea unui ir de caractere
-

este operaia prin care un ir de caractere surs este copiat ntr-o alt
zon de memorie ataat unui alt ir de caractere destinaie
funcia specific este: strcpy
char * strcpy(char *destinatie, const char *sursa)

Funcia are ca efect copierea tuturor caracterelor n zona de memorie referit de


destinatie din zona de memorie referit de sursa. (inclusiv caracterul NULL)
Funia strcpy returneaz la revenire adresa de nceput a zonei n care s-a copiat
irul (pointer-ul destinatie)
Exemplu: Interschimbarea a dou iruri de caractere:
void schimb(char a[20],char b[20])
{
char aux[20];
strcpy(aux,a);

45

Algoritmi i structuri de date


strcpy(a,b);
strcpy(b,aux);
}

Concatenarea a dou iruri de caractere


Se realizeaz prin apelul funciei strcat:
char * strcat(char *destinatie,const char *sursa)
Apelul funciei are ca efect copierea irului de la adresa sursa n zona de
memorie imediat urmtoare irului de la adresa destinatie. La revenire, funcia
returneaz adresa destinaie.
Compararea a dou iruri de caractere
Operaia de comparare a dou iruri presupune verificarea codurilor ASCII ale
caracterelor din compunerea irurilor. Compararea irurilor de caractere este o
operaie util n probleme care cer ordonarea lexicografic a unor secvene de text.
char * strcmp(const char *sir1,const char *sir2)
Funcia strcmp returneaz:
- valoare negativ, dac sir1<sir2
- 0, dac sir1=sir2
- valoare pozitiv, dac sir1>sir2
Exemplu: Program de generare a parolelor. Pentru un cuvnt dat, parola va fi
obinut prin scrierea de la dreapta la stnga a caracterelor de pe poziiile impare.
#include <stdio.h>
#include <string.h>
void main()
{
char pass[20],cuv[20];
int i,n;

pass[0]=NULL;
printf("dati cuv ");scanf("%s",&cuv);
i=(strlen(cuv)-1);
if (i%2) i--;
n=0;
while (i>=0)
{
pass[n]=cuv[i];
i-=2;
n++;
}
pass[n]=NULL;
printf("parola este %s",pass);

46

Algoritmi i structuri de date

Operaiile specifice cu vectori sunt:


1. parcurgerea
a. pentru accesarea i/sau modificarea elementelor (citirea i
afiarea)
b. pentru numrarea elementelor ce verific o condiie
2. cutarea secvenial
3. minimul/maximul dintr-un vector
4. inserarea i tergerea unui element pe o poziie dat
5. concatenarea a doi vectori
6. interclasarea a doi vectori
7. sortarea
a. sortarea prin selecie
b. sortarea prin numrare
c. sortarea prin metoda bulelor (prin interschimbare)
Alte variante de sortare vor fi descrise n capitolele dedicate metodelor de
programare (ex. sortarea rapid i sortarea prin interclasare)
1. Parcurgerea tablourilor
//Parcurgerea tablourilor citirea i afiarea unui vector
void citire_tablou(int t[], int *n)
{
int i;
printf(dati dimensiunea tabloului:); scanf(%d,n);
for(i=0;i<*n;i++)
scanf(%d, &t[i]);
}
void afisare_tablou(int t[], int n)
{
int i;
for(i=0;i<n;i++)
printf(%d, t[i]);
}

int tablou[30];
int dim_tablou10;
citire_tablou(tablou, &dim_tablou);
//apelul funciei de citire tablou
afisare_tablou(tablou, dim_tablou);
//apelul funciei de afiare tablou

Legtura dintre pointeri i tablouri influeneaz maniera de transmitere a


tablourilor ca parametri unei funcii. Oferim o alt variant de funcie care citete
un tablou unidimensional, n care se folosesc expresii cu pointeri, iar parametrul
formal este declarat ca pointer:
47

Algoritmi i structuri de date


void citire_tablou_II(int *pt, int *n)
{
int i, valoare;
printf(dati dimensiunea tabloului:); scanf(%d,n);
for(i=0;i<*n;i++)
{
scanf(%d, &valoare);
*(p+i)=valoare;
}
}

//Parcurgerea tablourilor numrarea elementelor ce verific o condiie dat


int

numara_pozitive(int t[], int n)


{
int i, contor;
contor=0;
for(i=0;i<n;i++)
if (t[i]>0)
contor++;
return contor;
}

2. Cutarea secvenial
- operaia de cutare presupune parcurgerea element cu element a vectorului, n
ordinea dat de indeci
//Cutarea : determinarea poziiei pe care se afl valoarea cutat
int

cauta_cheie(int t[], int n, int cheie)


{
int i;
for(i=0;i<n;i++)
if (t[i]==cheie)
return i; //poziia valorii cutate
return -1;//dac nu s-a gsit valoarea se ntoarce -1
}

3. Determinarea minimului/maximului dintr-un vector


Principiul este urmtorul:
- se presupune c elementul de pe prima poziie din vector este minimul
- se parcurge vectorul element cu element (de la a 2-a poziie, pn la
ultima), comparndu-se minimul presupus cu valoarea curent din
vector
- dac elementul curent este mai mic dect minimul presupus, se va
actualiza valoarea minimului, n caz contrar se trece la urmtorul
element fr a altera minimul presupus
Pentru determinarea maximului procedeul este similar, modificndu-se doar
operatorul logic utilizat n condiia de actualizare a maximului presupus.
48

Algoritmi i structuri de date

Algoritmul descris n pseudocod pentru determinarea minimului dintr-un vector


x1,x2,...,xn este urmtorul:
Algoritm Minim este
Citete: x1,x2,...,xn, n //vectorul x de n elemente; dimensiunea n
Fie min=x1
Pentru i de la 2 la n //se parcurge vectorul
Dac xi<min atunci
min=xi
SfDac
SfPentru
Tiprete min
SfAlgoritm
// codul surs C corespunztor algoritmului descris
//se citesc n prealabil dimensiunea i elementele vectorului x
int x[100];
int i, min, n;
citire_tablou(x,&n);
min=t[0];
for(i=0;i<n;i++)
if (x[i]<min0)
min=x[i];
printf(Minimul este:%d, min);

4. Inserarea/tergerea unui element


Inserarea unui element nou pe o poziie k n vector presupune efectuarea
urmtoarelor operaii:
- mrirea dimensiunii vectorului cu 1.
- mutarea cu o poziie la dreapta a tuturor elementelor situate pe poziiile
mai mari dect k
- inserarea propriu-zis a noului element pe poziia k.
tergerea elementului de pe poziia k presupune operaiile urmtoare:
- mutarea la stnga cu o poziie a tuturor elementelor situate la dreapta
poziiei k
- micorarea dimensiunii vectorului cu 1
5. Concatenarea a doi vectori
Rezultatul concatenrii a doi vectori de dimensiune n1, respectiv n2, este un al
treilea vector de dimensiune n1+n2, format prin copierea elementelor primului
vector urmat de copierea elementelor celui de-al doilea vector. Concatenarea este
o operaie simpl fapt pentru care vor lsa ca exerciiu definirea unei funcii C care
realizeaz aceast operaie.

49

Algoritmi i structuri de date

Observaie: operaia de concatenarea difer semnificativ de operaia de


interclasare.
6. Interclasarea a doi vectori
Interclasarea este procedeul prin care, pornind de la dou secvene ordonate de
elemente se formeaz o secven care conine toate elementele primelor dou i
este ordonat.
Operaia de interclasare se aplic asupra a doi vectori sortai rezultnd un al
treilea vector cu proprietile:
- conine toate elementele vectorilor iniiali
- este ordonat
Fie X i Y doi vectori ordonai cresctor, de dimensiune n, respectiv m:
x[1],x[2],...,x[n]
y[1],y[2],...,y[m]
Se dorete obinerea vectorului ordonat z: z[1],z[2],...,z[p], format din toate
elementele celor doi vectori de intrare.
Principiul este de a completa element cu element vectorul rezultat Z, prin
copierea elementelor vectorilor X i Y pstrnd relaia de ordine. Interclasarea se
realizeaz prin executarea urmtoarelor etape:
1. Ct timp mai exist elemente de parcurs n ambii vectori: X,Y, acetia
se parcurg secvenial:
a. Se compar elementele curente ale celor doi vectori X i Y, i
cel mai mic dintre acestea se copiaz n vectorul Z
2. Dac au mai rmas elemente neparcurse n vectorul X, se vor copia n
Z
3. Dac au mai rmas elemente neparcurse n vectorul Y, se vor copia n
Z
Parcurgerea vectorilor X,Y,Z presupune utilizarea a trei variabile cursor cu
semnificaia poziiei curente n vectorul corespunztor:
Fie i cursorul care indic poziia curent n vectorul X
Fie j cursorul care indic poziia curent n vectorul Y
Fie k cursorul care indic poziia curent n vectorul Z
Algoritmul Interclasare este:
Citete n, x[1],x[2],...,x[n]
Citete m, y[1],y[2],...,y[m]
Fie i=1,j=1 //poziiile de start n vectorii de intrare
Fie k=1 //poziia de start n vectorul rezultat
Cttimp(i<=n i j<=m)
Dac x[i]<y[j] atunci
z[k]=x[i]
k=k+1 //trecere la urmtoarea poziie n vectorul Z
i=i+1//trecere la urmtoarea poziie n vectorul X
Altfel
50

Algoritmi i structuri de date

z[k]=y[j]
k=k+1 //trecere la urmtoarea poziie n vectorul Z
j=j+1//trecere la urmtoarea poziie n vectorul Y
SfDac
SfCttimp
Dac i<n atunci //au mai rmas elemente n vectorul X
Pentru w de la i la n //se copiaz elementele rmase n X
z[k]=x[w]
k=k+1
SfPentru
SfDac
Dac j<m atunci //au mai rmas elemente n vectorul Y
Pentru w de la j la m //se copiaz elementele rmase n Y
z[k]=y[w]
k=k+1
SfPentru
SfDac
p=k-1 // sau p=n+m
Tiprete z[1],z[2],...,z[p]
SfAlgoritm //Interclasare
TABLOURI n-DIMENSIONALE
Adesea, este necesar prelucrarea datelor structurate n tablouri
multidimensionale. Un caz particular este cel al tablourilor bidimensionale,
cunoscute sub denumirea de matrice. Structura de matrice este reprezentat n
matematic prin:

x11

x
X = 21
...

x
m1

x12

...

x 22

...

...
xm 2

...
...

x1n

x2n
...

x mn

Toate elementele unei matrice sunt de acelai tip i dimensiunile matricei se


refer la numrul de linii (m) i de coloane (n). n limbajul C, declararea unei
structuri de tablou bi-dimensional se face prin construcia:
tipElement NumeTablou [linii] [coloane], unde:
- tipElement este tipul de dat[ al elementelor
- linii i coloane specific dimensiunea memoriei alocate tabloului
identificat prin NumeTablou
Un tablou bidimensional este reprezentat ntr-o succesiune de locaii de
memorie referite prin acelai identificator (NumeTablou). Fiecare element al
tabloului este referit prin poziia sa n cadrul irului. Poziia este precizat prin
dou numere pozitive (indeci), care reprezint cele dou dimensiuni (linie i
coloan).
51

Algoritmi i structuri de date

Prin declaraia tipElement NumeTablou [linii] [coloane] s-au alocat n memorie


un numr de octei egal cu linii*coloane*sizeof(tipElement), necesari memorrii
elementelor acestei structuri de date. Zon de memorie rezervat tabloului este
contigu.
Numele tabloului este adresa primului element memorat, accesat prin
construcia: NumeTablou[0][0], astfel nct n prelucrrile tablourilor, n mod uzual
se consider numerotarea liniilor i a coloanelor ncepnd de la 0.
Accesarea elementului de pe linia i, coloana j se face prin construcia
NumeTablou[i][j]. Ne reamintim c sintaxa NumeTablou[i] este echivalent unei
operaii cu pointeri exprimat prin expresia: NumeTablou+i. ntr-o manier
similar, expresia NumeTablou[i][j] poate fi exprimat printr-o operaie cu
pointeri: NumeTablou+i+j.
Citirea elementelor unei matrice este posibil prin citirea pe rnd a fiecrui
element din compunerea sa. n acest sens se vor parcurge mulimile indecilor de
linie i coloan prin dou instruciuni repetitive (ciclice) imbricate. Codul C de mai
jos are ca efect citirea unei matrice de numere ntregi:
int mat[10][10];
int i,j;
printf(\nIntroduceti nr. de linii );scanf(%d,&m);
printf(\nIntroduceti nr. de coloane );scanf(%d,&n);
for(i=0; i<m; i++)
for(j=0; j<n; j++)
{
printf(\n Matrice[%d][%d] = : ,i,j);
scanf(%d,&mat[i][j]);
}

Afiarea valorilor unei matrici (n formatul uzual) este descris prin secvena
urmtoare :
printf(\n Matricea este: );
for(i=0; i<m; i++)
{
for(j=0; j<n; j++)
printf(%d, mat[i][j]);
printf(\n);
}

n programele de prelucrare a matricelor este util implementarea funciilor


corespunztoare operaiilor de intrare ieire cu aceste structuri: citirea unei
matrice, afiarea unei matrice. Funciile respective vor avea ca parametri att
dimensiunile tabloului (numrul de linii i coloane) dar i adresa primului element
(numele tabloului). Parcurgerea n cadrul funciilor a tablourilor cu ajutorul
indecilor ascunde n fapt un calcul de adrese: cunoaterea adresei de nceput a
zonei de memorie alocate tabloului permite accesarea tuturor elementelor sale.
n privina parametrilor formali, antetul funciei de citire poate fi exprimat:
52

Algoritmi i structuri de date

void citire (tipElement NumeTablou[Mmaxim][Nmaxim], int linii, int coloane)


Exemplu: Programul urmtor determin suma i produsul a dou matrici:
#include <stdio.h>
#include <conio.h>
#define DimMax 10 //numrul maxim de elemente
//pe linii si coloane
void afisare_matrice(int n,int m,int a[DimMax][DimMax])
{
int i,j;
for(i=0;i<n;i++)
{ for(j=0;j<m;j++)
printf("%d ",a[i][j]);
printf("\n");
}
}
void citire_matrice(int *n,int *m,int a[DimMax][DimMax])
{
int i,j;
printf("\nIntroduceti nr. de linii n=");scanf("%d",n);
printf("\nIntroduceti nr. de coloane m=");
scanf("%d",m);
printf("\nIntroduceti elementele matricei\n");
for (i=0;i<*n;i++)
for(j=0;j<*m;j++)
{
printf("a[%d,%d]=",i,j);scanf("%d",&a[i][j]);
}
printf("\n");
}
void suma(int n,int m, int a[DimMax][DimMax],
int b[DimMax][DimMax], int c[DimMax][DimMax])
{
int i,j;
for(i=0;i<n;i++)
for(j=0;j<m;j++)
c[i][j]=a[i][j]+b[i][j];
}
void produs(int n,int m,int p, int a[DimMax][DimMax],
int b[DimMax][DimMax],int c[DimMax][DimMax])
{
int i,j,k;
int s;
for(i=0;i<n;i++)
for(j=0;j<p;j++)
{
s=0;

53

Algoritmi i structuri de date


for(k=0;k<m;k++)
s=s+a[i][k]*b[k][j];
c[i][j]=s;

}
}
void main()
{
int n,m,p;
int a[DimMax][DimMax];
int b[DimMax][DimMax];
int c[DimMax][DimMax];
citire_matrice(&n,&m,a);
citire_matrice(&m,&p,b);
produs(n,m,p,a,b,c);
afisare_matrice(n,p,c);
suma(n,m,a,b,c);
afisare_matrice(n,m,c);
}

ALGORITMI DE SORTARE
Sortarea reprezint procedeul prin care o mulime de elemente este aranjat
dup o relaie de ordine dat.
Sortarea prin selecie
Fie x1,x2,...,xn un vector de n elemente.
Principiul este acela de a considera subvectorul x i,,xn i de a determina
minimul din aceast secven, urmnd ca minimul rezultat s se interschimbe cu
elementul xi. Procedeul se va repeta pentru oricare i=1,,n-1.
Algoritm SortareSelecie este:
Citete n, x1,x2,...,xn
Pentru i de la 1 la n-1
//determin poziia i valoarea minimului subvectorului xi,,xn
pozmin=i
min=xi
Pentru j de la i+1 la n
Dac xj <min atunci
pozmin=j
min=xj
SfDac
sfPentru
//interschimbare xi cu min
xpozmin=xi
xi=min
SfPentru
54

Algoritmi i structuri de date

SfAlgoritm
Exemplu:
/*program care citeste un vector de numere intregi,
ordoneaza elementele vectorului prin metoda sortarii prin selectie
si tipareste vectorul ordonat */
#include <stdio.h>
void citireSir(int x[],int *n)
{
int i;
printf("\ndati n=");
scanf("%d",n);
for(i=0;i<*n;i++)
{printf("\nX[%d]=",i+1);
scanf("%d",&x[i]);
}
}
void tiparireSir(int x[],int n)
{
int i;
for(i=0;i<n;i++)
printf("%d ",x[i]);
}
void SSort(int x[],int n)
{
int i,j,aux,poz;
for(i=0;i<n;i++)
{
//caut in sirul i .... n elementul minim
aux=x[i];poz=i;
for(j=i+1;j<n;j++)
if (x[j]<aux)
{aux=x[j];
poz=j;}
//interschimb cu x[i]
x[poz]=x[i];
x[i]=aux;
}
}
void main()
{
int a[100],n;
citireSir(a,&n);
SSort(a,n);
tiparireSir(a,n);
}

55

Algoritmi i structuri de date

Sortarea prin inserie


Fie x1,x2,...,xn un vector de n elemente
Principiul acestei tehnici este acela de a privi tabloul ca fiind mprit n dou
subtablouri: x1,...,xi-1 i xi,...,xn. Se presupune c primul subtablou este deja ordonat
i se urmrete inserarea elementului x i n subtabloul ordonat astfel nct dup
efectuarea inseriei subtabloul rezultat s rmn ordonat. Acest procedeu se va
continua privind tabloul iniial ca dou subtablouri : x 1,...,xi i xi+1,...,xn. Cunoscnd
c primul subtablou este deja ordonat (ne-am asigurat de acest lucru la operaia de
inserare precedent), se va continua cu inserarea elementului x i+1 n x1,...,xi i
obinerea subtabloului ordonat x1,...,xi+2. Procedura se repet pn cnd nu mai sunt
elemente de inserat n subtabloul stng.
Inserarea unui element oarecare x i presupune determinarea poziiei n care va fi
depus. Aceasta se rezum la parcurgerea subtabloului n care se va face inserarea,
de la stnga la dreapta, i determinarea primul element x k care este mai mare dect
xi. n acel moment k devine poziia pe care va fi depus x i. Parcurgerea subtabloului
se poate face i n sens invers, de la dreapta la stnga, ns n acest caz se va
determina k poziia primului element xk care verific condiia c este mai mic
dect xi.
Prima mprire a tabloului este n punctul i=2, ceea ce produce un subvector
format din doar elementul x1 i subvectorul x2,...,xn. Se poate observa c primul
subtablou este deja ordonat.
Algoritm SortareInserie este:
Citete n, x1,x2,...,xn
Pentru i de la 2 la n
//inserez elementul xi n subvectorul stng
x0=xi
j=i-1
Cttimp (xj>x0)
xj+1=xj
j=j-1
SfCttimp
xj+1=x0
SfPentru
SfAlgoritm
Exemplu: Funcia urmtoare realizeaz sortarea prin inserie a unui vector.
void SInsert(int x[],int n)
{
int i,j,aux,k;
for(i=1;i<n;i++)
{
//caut pentru x[i] pozitia buna in subsirul din stanga
//1.....i-1
k=i-1; aux=x[i];//salvez valoarea curenta de inserat n aux

56

Algoritmi i structuri de date


while ((aux<x[k])&& (k>=0))
//primul element mai mic decat aux
{x[k+1]=x[k];
k--;
}
// k+1 reprezinta pozitia pe care se insereaza aux
x[k+1]=aux;
}
}

Sortarea prin interschimbare (BubbleSort )


Metoda sortrii prin interschimbare const n parcurgerea repetat a vectorului
i compararea pe rnd a perechilor de elemente consecutive urmat de
interschimbarea valorilor acestora n situaia n care relaia de ordine dorit nu este
verificat.
La fiecare parcurgere se va presupune c vectorul este ordonat (marcarea acestei
presupuneri se face printr-un cod), ns la determinarea unei perechi de elemente
consecutive care necesit interschimbare, presupunerea este anulat.
Algoritmul se va termina n condiiile n care la o parcurgere anterioar,
complet a vectorului, presupunerea s-a dovedit adevrat.
Algoritm SortareInterschimbare este
//ordonare cresctoare dup valorile elementelor
Citete n, x1,x2,...,xn
Repet
Ordonat=Adevrat
Pentru i de la 1 la n-1
Dac xi>xi+1 atunci
Cheam Interschimbare(xi,xi+1)
Ordonat=fals
SfDac
SfPentru
Pncnd (Ordonat=Adevrat)
SfAlgoritm
Exemplu: Funcia urmtoare realizeaz sortarea prin interschimbare a unui vector.
void SBubble(int x[],int n)
{
int cod; //false sau adevarat
int i,j,aux,k;
do
{
cod=0; //presupun vectorul ordonat
for(i=0;i<n-1;i++)
{
if (x[i]>x[i+1])

57

Algoritmi i structuri de date


{
//Interschimb vecinii
aux=x[i];
x[i]=x[i+1];
x[i+1]=aux;
cod=1;
//am gasit vecini care nu respecta
//relatia de ordine
}

}
}while (cod!=0);
}

Probleme propuse spre rezolvare:


1.
2.
3.

4.
5.

Se d o matrice cu numere ntregi, de n linii i m coloane. S se determine


numrul elementelor pozitive din compunerea matricei.
Se d un tablou bidimensional de numere ntregi organizat n n linii i n
coloane (matrice ptratic). S se ordoneze cresctor elementele de pe
diagonala principal a matricei.
Fiind dat o matrice A de n linii i coloane, format din numere reale, s
se construiasc o matrice B de n linii i m+1 coloane, ale crei prime m
coloane sunt copiate din matricea A i ultima coloan este format din
valorile sumelor elementelor de pe fiecare linie a matricei A.
S se determine inversa unei matrice.
Determinai cel mai mic numr pozitiv al unui vector de n numere ntregi.

58

Algoritmi i structuri de date

V.

STRUCTURI. TIPURI DE DATE DEFINITE DE UTILIZATOR.

Datele pot fi clasificate n dou categorii:


- Date simple, izolate: tipurile acestora sunt predefinite n limbajul C (ex.
int. double, float, char)
- Date grupate, sau structurate: tablourile i structurile, care permit
prelucrarea global dar i individual a fiecrui element al datei respective.
Structurile: reprezint o modalitate de grupare a datelor care nu sunt neaprat
de acelai tip (spre deosebire de tablouri). Este necesar uneori referirea la o dat
care cuprinde mai multe elemente, exemplu data calendaristic , avnd
componentele: zi, luna, an.
Prin structur n limbajul de programare, nelegem o colecie de date
neomogene (de tipuri diferite), grupate sub un singur nume. Exista posibilitatea
accesului la fiecare component a structurii , ct i posibilitatea referirii n
ansamblu a structurii definite.
Sintaxa declarrii unei structuri:
struct NumeStructur
{
<instructiuni de declarare de tip>
} nume1, nume2, , numeN;
unde:
<instructiune de declarare de tip>::= <tip> <lista identificatori>
Observaie: Identificatorii: NumeStructur, nume1, , numeN pot sa lipseasc
dar nu toi odat. Astfel:
- Dac NumeStructur lipsete, cel puin nume1 trebuie s apar n construcie
- Dac nume1, , numeN lipsesc, atunci este obligatoriu ca NumeStructur se
fie prezent.
- n situaia n care NumeStructur este prezent, prin acest lucru se definete
un nou tip de dat denumit NumeStructur.
-Dac nume1, numeN sunt prezente ele reprezint date de tipul nou introdus
NumeStructur.
- Dac una dintre datele din compunerea unei structuri este de tipul pointer la
tipul de date introdus prin structura respectiv, numim structura recursiv:
struct NumeStructur //structura recursiv
{
struct NumeStruct * data; //pointer la tipul de date struct NumeStructur
<instructiuni de declarare de tip>
} nume1, nume2, , numeN;
Exemple:
59

Algoritmi i structuri de date


struct DataCalendaristic
{
int zi;
int luna;
int an;
} DataNasterii, DataAngajarii;

-prin aceast construcie am introdus un tip nou (DataCalendaristic) i am definit


dou variabile de tipul nou introdus (DataNasterii i DataAngajarii)
struct

{
int zi;
int luna;
int an;
} DataNasterii, DataAngajarii;

-prin aceast construcie NU am introdus un tip nou ci doar am definit dou


variabile structurate (DataNasterii i DataAngajarii).
struct DataCalendaristic{
int zi;
int luna;
int an;
}

struct DataCalendaristic DataNasterii, DataAngajarii;

- prin aceast construcie am definit un tip nou (DataCalendaristic) i ulterior s-au


declarat dou variabile structurate (DataNasterii i DataAngajarii) de tipul
DataCalendaristic.
Exist posibilitatea declarrii unor tablouri de date structurate. Spre exemplu,
avem nevoie ntr-un program de un ir de 100 date calendaristice, pe care dorim
ulterior s le prelucrm. Cum se declar un tablou de date structurate?
struct NumeStructur
{
<instruciuni de declarare de tip>
}
struct NumeStructur TablouDeStructuri[N];
- prin aceast construcie am declarat un tablou unidimensional de N date
structurate. Fiecare element al tabloului este reprezentat de o grupare de date.
Exemplu:
struct DataCalendaristic
{
int zi;
int luna;int an;
}

60

Algoritmi i structuri de date


struct DatCalendaristic SirDate[100];

fiecare element al tabloului SirDate are trei componente: zi, luna, an i


reprezint o dat calendaristic.

Exist posibilitatea ca n cadrul definirii unei structuri de date s apar o


declaraie a unei date de tip structurat. Spre exemplu, dorim s prelucram date
grupate de tipul Persoan. Pentru fiecare dat de acest tip avem mai multe
informaii de identificare:nume, prenume, adres, data naterii. Se observ c data
naterii la rndul su este o dat structurat (zi, lun, an).
Exemplu de structuri imbricate:
struct DataCalendaristic {
int zi; int luna; int an;
};
struct Persoana {
char nume[10]; char prenume[10]; char adresa[25];
struct DataCalendaristica dataNasterii;
};
//declaraia unei date (Pers1) de tipul Persoana
struct Persoana Pers1;

Accesul la componentele unei structuri


Pentru a putea accesa componente ale unei date structurate se va utiliza
operatorul punct .. Dac avem o structur cu numele NumeStructur i o
component a sa cu numele NumeComponent, referirea componentei respective
se face prin construcia:
NumeStructur.NumeComponent
Pentru structuri imbricate se folosete operatorul . de cte ori este
nevoie:Dac dorim s accesm anul naterii persoanei Pers1 din exemplul
precedent, acest lucru se face printr-o construcie de forma:
Pers1.dataNasterii.an
Fie structura:
struct DataCalendaristic
{
int zi, luna, an;
}
struct DatCalendaristic SirDate[100];

Pentru a accesa o component a unui element al unui tablou de date structurate


se folosesc construcii de forma :
SirDate[24].zi , SirDate[1].luna , SirDate[k].an.
Pointeri la structuri
Declararea unui pointer la o structur NumeStructur se face prin construcia:
struct NumeStructur *p;
n acest caz, p este o variabil pointer, i are ca valoare adresa unei date de tipul
NumeStructur.
61

Algoritmi i structuri de date

Exemplu. struct DataCalendaristic *p;


Avem nevoie de pointeri la structuri n urmtoarea situaie:
- n cazul n care avem o funcie ce prelucreaz o variabil de tipul unei
structuri, transmis ca parametru, i dorim ca efectul modificrilor din
corpul funciei apelate s se regseasc i n functia apelanta. Spre
exemplu, dorim s citim o dat de tip structur printr-o funcie, iar
valorile citite n corpul funciei trebuie s fie vizibile n programul
apelant.
Exemplu:
void Citire(struct DataCalendaristic *p)
{ int ziuaN,lunaN,anulN;
scanf(%d,&ziuaN);
(*p).zi=ziuaN;
scanf(%d,&lunaN);
(*p).luna=lunaN;
scanf(%d,&anulN);
(*p).an=anulN;
}
Struct DataCalendaristica DC;
//Apelul functiei:
Citire(&DC);

Pentru simplificarea sintaxei codului surs, construcia (*p).NumeComponent


se poate nlocui cu contrucia: p->NumeComponent.
Tipuri de date utilizator
n foarte multe situaii este nevoie ca utilizatorul s-i defineasc noi tipuri de
date pe care le prelucreaz n program. Introducerea unui nou tip de date utilizator
se face prin construciile struct. Noul tip de date este referit prin: struct
NumeStructur iar datele se vor declara prin instruciunile de declarare:
struct NumeStructur data1, data2, datan;
Pentru a utiliza n mod mai flexibil numele noului tip de date introdus,
respectiv, de a renuna la cuvntul cheie struct n declaraii de date se va utiliza
declaraia de tip.
Limbajul C permite atribuirea unui nume pentru un tip predefinit sau pentru nou
tip definit de utilizator prin construcia:
typedef TIP NumeTip;
n continuare cuvntul NumeTip poate fi utilizat n declaraii de date ca i
cuvintele cheie (rezervate limbajului C) care denumesc tipurile (int, float, etc.)
Exemplu: Redenumirea tipului int al limbajului C:
typedef int INTREG;
Declararea unei variabile X de tipul int se poate face astfel:
INTREG X; //identic cu declaraia:
int X;

62

Algoritmi i structuri de date

n cazul tipurilor definite de utilizator, introduse prin declaraii de structur,


putem asigna un nume noului tip printr-o construcie de forma:
typedef struct NumeStructur
{
<instructiuni de declarare de tip>
} NumeTip;
Declararea unor variabile de tipul anterior se face astfel:
NumeTip data1, data2, datan;
Exemplu1: Program care citete un ir de structuri de tipul Persoana i afieaz
acest ir ordonat dup cmpul nume.
#include <stdio.h>
#include <string.h>
typedef struct{
int zi;
int luna;
int an;
}TData;
typedef struct {
char cnp[13];
char nume[20];
char prenume[40];
TData datanasterii;
char adresa[40];
}TPersoana;
void citirePers(TPersoana *p)
{
char aux[40];
printf("\n Introduceti CNP="); scanf("%s",aux);
strcpy(p->cnp,aux);
printf("\n Introduceti Nume="); scanf("%s",aux);
strcpy(p->nume,aux);
printf("\n Introduceti Prenume="); scanf("%s",aux);
strcpy(p->prenume,aux);
printf("\n Introduceti zi nastere:");
scanf("%d",&p->datanasterii.zi);
printf("\n Introduceti luna nastere:");
scanf("%d",&p->datanasterii.luna);
printf("\n Introduceti an nastere:");
scanf("%d",&p->datanasterii.an);
}
void tiparirePers(TPersoana p)
{
printf("\n%s %s %s %d.%d.%d ", p.cnp,
p.nume,p.prenume,p.datanasterii.zi,p.datanasterii.luna,
p.datanasterii.an);

63

Algoritmi i structuri de date


}
void sortNUME(TPersoana per[],int nr)
{
int cod,i;
TPersoana aux;
do{
cod=1;
for(i=0;i<=nr-2;i++)
if(strcmp(per[i].nume,per[i+1].prenume)>0)
{
//interschimb per[i] cu per[i+1]
aux=per[i];
per[i]=per[i+1];
per[i+1]=aux;
cod=0;
}
}while (cod==0);
}
void main()
{
TPersoana per[20]; //tablou de structuri
int nr;
printf("\n
dati
numarul
de
scanf("%d",&nr);
for(int i=0;i<nr;i++)
citirePers(&per[i]);
sortNUME(per,nr);
for(i=0;i<nr;i++)
tiparirePers(per[i]);
}

persoana:");

Probleme propuse spre rezolvare:


1. S se defineasc un nou tip de dat pentru entitatea Punct (3D), s
se denumeasc acest tip TipPunct i s se declare 3 date de acest
tip utilizator.
2. Scriei un program care citete ntr-un tablou un numr de k
structuri de tipul utilizator Marfa. Cmpurile structurii sunt:
codMarfa, DenumireMarfa, PretMarfa, CantMarfa, Valoare. Se
vor calcula valorile mrfurilor (valoare=pret*cantitate) i tabloul
se va ordona descresctor dup valorile obinute i se va afia pe
ecran. (valoarea produselor nu se va citi, aceasta urmnd a fi
calculat dup formula dat)
3. S se scrie un program care:
- Definete un nou tip de date Punct2D (puncte n plan)
- Definete un tip de date Triunghi
64

Algoritmi i structuri de date

Citete de la tastatur un tablou unidimensional (ir) de date de


tipul triunghi.

65

Algoritmi i structuri de date

TIP ABSTRACT DE DATE


Prin tip de date se nelege domeniul valorilor i operaiile specificate datelor de
acel tip. Tipurile de date se clasific n tipuri elementare (ex. numeric real, numeric
ntreg, caracter) i structurate (omogene tablouri, neomogene - structuri).
Majoritatea limbajelor de programare ofer posibilitatea manipulrii datelor de tip
elementar dar i introducerea de noi tipuri de date. Pentru tipurile de date definite
de utilizator, programatorul i va construi module specifice de prelucrare a datelor
de acel tip.
Prezentm ca exemplu un modul C care conine definirea tipului de date
abstract Rational.
Reamintim c un numr raional este un numr care poate fi exprimat prin
fracia m/n, unde m i n sunt numere ntregi. Reprezentarea unui numr raional se
poate face printr-o structur cu dou componente de tip ntreg, semnificnd ntregii
m i n ai fraciei m/n. Identificm urmtoarele operaii cu numere raionale:
- Iniializare: stabilete valorile implicite pentru m=0,n=1.
- Definire: stabilete valorile pentru componentele m i n.
- Modificare unei componente m sau n (setm, setn)
- Accesarea unei componente (getm, getn)
- Adunarea a dou numere raionale
- nmulirea a dou numere raionale
- Reducerea unui raional (prin mprirea lui m i n la cel mai mare divizor
comun al celor doi ntregi
- Testarea egalitii a dou numere raionale
- Testarea relaiei de ordine dintre dou numere raionale
- Inversul unui raional
Fiecare operaie din cele enumerate va fi definit prin intermediul unei funcii C
corespunztoare n modulul prezentat mai jos.
TAD RATIONAL
#include <stdio.h>
typedef struct{
int m;
int n;
}Rational;
void initializare(Rational *a)
{
a->m = 0;
a->n = 1;
}
void definire(Rational *a, int m_nou, int n_nou)
{ a->n = n_nou;

66

Algoritmi i structuri de date


}

a->m = m_nou;

void setm(Rational *a, int m_nou)


{
a->m = m_nou;
}
void setn(Rational *a, int n_nou)
{
a->n = n_nou;
}
int getm(Rational a)
{ return a.m;
}
int getn(Rational a)
{ return a.n;
}
int cmmdc(int x, int y)
{ int aux;
if (x < y)
{
aux = x;
x = y;
y = aux;
}
while ( x != y)
if ( x > y)
x = x -y;
else
y = y -x;
return x;
}
void reduce(Rational *a)
{ int x; //cel mai mare divizor comun
x = cmmdc(a->m, a->n);
a->m = a->m / x;
a->n = a->n / x;
}
void print(Rational a)
{ printf("\n %d/%d ", a.m, a.n);
}
void inmultire(Rational a, Rational b, Rational *c)
{ c->m = a.m * b.m ;
c->n = a.n * b.n;

67

Algoritmi i structuri de date


}

reduce(c);

void adunare(Rational a, Rational b, Rational *c)


{ c->m = (a.m * b.n + b.m * a.n);
c->n = a.n * b.n;
reduce(c);
}
int egalitate(Rational a, Rational b)
{if ((a.m*b.n)==(a.n*b.m))
return 1;
return 0;
}
Rational invers(Rational a)
{
Rational aux;
aux.m=a.n;
aux.n=a.m;
return aux;
}
int maiMic(Rational a, Rational b)
//verifica relatia de ordine a<b
{if (((a.n*b.n>0) &&(a.m*b.n<=a.n*b.m))||
((a.n*b.n<0) &&(a.m*b.n>=a.n*b.m)))
return 1;
return 0;
}

Problem: Urmnd exemplul prezentat anterior (TAD Rational) s se conceap


tipul abstract de date Complex, dezvoltnd o bibliotec de funcii specifice
operaiilor cu numere complexe.

68

Algoritmi i structuri de date

VI.

LUCRUL CU FIIERE

Prin fiier se nelege colecie ordonat de elemente pe care le numim


nregistrri. Fiierele sunt pstrate pe suporturi externe reutilizabile: hard disk,
floppy disk, etc. Prelucrarea fiierelor implic un numr de operaii specifice
acestora:
1.
2.
3.
4.
5.
6.
7.

Creare fiier
Deschidere fiier
Citire din fiier
Adaugare scriere n fiier
Actualizare fiier
Poziionare n fiier
tergere fiier

Operaiile specifice fiierelor se realizeaz prin intermediul unor funcii


standard existente n biblioteca limbajului C. Exist dou nivele de tratare a
fiierelor:
1. Nivelul inferior face apel direct la sistemul de operare
2. Nivelul superior face apel la proceduri specializate de prelucrare a
fiierelor, care utilizeaz structuri speciale de tipul FILE.
Nivelul inferior de prelucrare a fiierelor
Pentru a putea prelucra un fiier la nivel inferior acesta trebuie s fie creat n
prealabil.
1. Crearea unui fiier nou se realizeaz prin apelul funciei creat care are
prototipul:
int creat (const char* cale, int mod );
Unde:
- cale este un pointer spre un ir de caractere prin care este specificat
numele complet al fiierului (calea)
- mod un numr ntreg prin care se specific modul de acces al
proprietarului la fiier. Acest mod poate fi definit folosind constante
simbolice:
S_IREAD proprietarul poate citi fisierul
S_IWRITE proprietarul poate scrie fisierul
S_IEXE proprietarul poate executa programul coninut
de fiierul respectiv
Utilizarea acestei funcii implic includerea fiierelor: io.h i stat.h.
Observaie: Indicatorii de mod pot fi combinai. Astfel pentru a crea un fiier n
care avem drepturi de scriere i citire vom utiliza construcia: S_IREAD|
S_IWRITE.
69

Algoritmi i structuri de date

Funcia creat returneaz un numr ntreg care are semnificaia descriptorului de


fiier (dac este pozitiv) sau eroare (dac este -1).
Descriptorul de fiier este reprezint un numr ntreg pozitiv ataat fiierului
prelucrat, prin intermediul cruia se identific un fiier n operaiile specifice
realizate asupra acestuia.
2. Deschiderea unui fiier
Deschiderea fiierului se realizeaz prin apelul funciei open al crei prototip
este:
int open (const char* cale, int acces );
Utilizarea acestei funcii presupune includerea fiierelor io.h i fcntl.h:
#include <io.h>
#include <fcntl.h>
-

cale pointer la un ir de caractere prin care se specific calea complet a


fiierului ce va fi deschis.
acces ntreg prin care se specific modul de acces la fiier:
O_RDONLY numai n citire
O_WRONLY numai n scriere
O_RDWR citire-scriere
O_APPEND adugare la sfritul fiierului
O_BINARY prelucrare binar
O_TEXT prelucrare text

Valorile de acces pot fi combinate prin caracterul | , spre exemplu:


O_RDONLY|O_BINARY
Funcia returneaz un ntreg nenegativ (descriptorul de fiier) sau 1 n caz de
eroare.
Exemplu:
int df;
df=open(nume.txt,O_APPEND);

Observaii:
Dac nu este specificat complet calea fiierului, se consider implicit calea
curent.
Calea complet a unui fiier este specificat printr-un ir de caractere de forma:
Litera:\Dir1 \ Dir2 . \Dir
Unde:
- Litera poate fi A,B,C reprezint litera de identificare a discului (ex:
C harddisk, A floppy disk)
- Dir1, DIr2, sunt nume de subdirectoare
Dac specificm calea fiierului n apelul funciei open, toate caracterele \
trebuie dublate
Exemplu:
int df;
df = open (C:\\Borlandc\\bin\\text.cpp, O_RDWR);

70

Algoritmi i structuri de date

3. Citirea din fiier


Se realizeaz prin funcia read, prototipul acesteia se afl n fiierul io.h:
int read(int df, void *mem, unsigned lung);
unde:
- df descriptorul ataat fiierului (returnat de funcia open)
- mem pointer spre zona de memorie n care se va pstra nregistrarea
citit din fiier
- lung lungimea nregistrrii exprimat n numr de octei
Funcia read returneaz un ntreg reprezentnd numrul de octei citii din fiier
sau: 1 n caz de eroare, 0 la terminarea fiierului
Efectul: Prin apelul funciei read se citete din fiier nregistrarea curent; la un
apel urmtor se va citi urmtoarea nregistrare, .a.m.d pn la sfritul fiierului.
Exemplu: citirea din fiier caracter cu caracter pn la terminarea fiierului i
afiarea pe monitor a coninutul acestuia:
int df, i;
char c;
df=open(proba.txt,O_RDONLY);
do{
i=read(df, c, sizeof(char) );
//sizeof(char) returneaz numrul de octei
//necesari tipului specificat ntre paranteze.
printf(%c, c);
}while (i>0);

4. Scrierea n fiier
Funcia write , prototipul acesteia se afl n fiierul io.h:
int write(int df, void *mem, unsigned lung);
unde:
- df descriptorul ataat fiierului (returnat de funcia open)
- mem pointer spre zona de memorie din care se preia informaia ce se va
scrie n fiier
- lung lungimea nregistrrii exprimat n numr de octei
Funcia write returneaz un ntreg reprezentnd numrul de octei scrii din
fiier, n general numrul specificat de parametrul lung. Dac cele dou valori sunt
diferite, este semnalat prin acesta un caz de eroare.
Exemplu: scrierea ntr-un fiier a unei secvene de caractere

int df, i;
char text[30] = Acest text se scrie in fisier!;
df=open(proba.txt,O_APPEND);
write(df, text, 30);

5. Poziionarea n fiier
Citirea i scrierea n fiiere se face n mod secvenial, ceea ce nseamn c
nregistrrile se scriu una dup alta pe suportul fiierului i citirea se face n
aceeiai ordine n care au fost scrise. Dac dorim ca accesul la nregistrri s se
71

Algoritmi i structuri de date

fac ntr-o ordine diferit dect cea implicit se poate utiliza funcia lseek pentru
poziionarea n fiier:
long lseek(int df, long depl, int orig);
unde:
- df descriptorul de fiier
- depl deplasamentul exprimat n numr de octei
- orig este un numr ntreg ale crui valori semnific:
0 deplasamentul se consider de la inceputul fiierului
1 deplasamentul se consider din poziia curent
2 deplasamentul se consider de la sfritul fiierului
Funcia returneaz poziia fa de nceputul fiierului.
6. nchiderea fiierului
Funcia close avnd prototipul:
int close (int df); //df- descriptorul de fiier
Utilizarea funciei close implic includerea fiierului bibliotec io.h
Returneaz:
0 la nchiderea normal a fiierului
-1 n caz de eroare
Nivelul superior de prelucrare a fiierelor
Funcii de manipulare a fiierelor sunt definite n bibliotecile standard ale
limbajului. Prototipurile funciilor se gsesc n fiierul antet stdio.h. Fiierul care
se prelucreaz este identificat n mod unic printr-un pointer de fiier (uzual
denumim acest pointer de fiier pf).
n limbajul C exist tipul de date FILE (fiier) prin care se definete structura
datelor dintr-un fiier. n declaraiile de tip vom avea nevoie de o secven:
FILE *pf;
1.
Deschiderea unui fiier
Funcia de deschidere a unui fiier se numete: fopen i prototipul funciei este
urmtorul:
FILE *fopen(const char *nume, const char *mod);
n declaraiile de tip vom avea nevoie de o secven:
FILE *pf, fopen();
Argumentele funciei:
nume ir de caractere prin care se identific numele fiierului de pe disc
pe care dorim s-l accesm.
mod ir de caractere prin care se specific modul de acces la fiier.
Modurile valide de deschidere a fiierului sunt:
r - deschidere pentru citire
w - deschidere pentru scriere (informaia din fiier se pierde la
deschiderea n acest mod)
a - deschide pentru scriere (fr pierderea informaiei din fiier) sau
creeaz fiier
r+ - deschide pentru actualizare
72

Algoritmi i structuri de date

w+ - deschide sau creeaz fiier pentru actualizare (pierderea


informaiei)
a+ - deschide sau creeaz fiier pentru actualizare (fr pierderea
informaiei anterioare)
Efectul apelrii funciei: deschide fiierul specificat prin nume n modul de
acces dorit i returneaz un pointer spre structura FILE (FILE *pf) care ulterior se
utilizeaz pentru a identifica fiierul n operaiile ce se efectueaz asupra sa. n caz
de insucces, funcia returneaz NULL.
Exemplu:
FILE *pf; //pointer spre FILE

pf=fopen(fis1.dat, r);
// deschid fiierul fis1.dat pentru a putea citi informaii
if (pf==NULL)
{
printf(\n Nu s-a putut deschide fisierul);
exit(1); //iesire din program
}

2.

nchiderea fiierului

Funcia de nchidere a unui fiier se numete fclose i are prototipul:


int fclose(FILE *pf);
Argumentul funciei:
pf pointer spre FILE, identific fiierul ce se nchide
Funcia returneaz 0 n caz de succes sau EOF (end of file) n caz de insucces.
n declaraiile de tip vom avea nevoie de o secven:
int fclose();
3.

Scrierea n fiier

Funcia de scriere n fiier: fprintf are prototipul:


int fprintf(FILE *pf, const char* sir_format, lista_arg);
Argumentul funciei:
pf pointer spre FILE, identific fiierul ]n care se efectueaz scrierea
sir_format, lista_arg - au aceiai semnificaie ca i n cazul funciei
printf
Funcia returneaz n caz de succes numrul de caractere scrise sau un numr
negativ n caz de insucces.
Exemplu:
fprintf(pf, Rezultatul este: %d, rezultat);

4.

Citirea din fiier

Funcia de citire din fiier: fscanf are prototipul:


int fscanf(FILE *pf, const char* sir_format, lista_arg);
Argumentul funciei:
pf pointer spre FILE, identific fiierul din care se efectueaz citirea
sir_format, lista_arg - au aceiai semnificaie ca i n cazul funciei scanf
73

Algoritmi i structuri de date

Funcia returneaz n caz de succes numrul de caractere citite sau un numr


negativ n caz de insucces.
Exemplu:
fscanf(pf, %d, &x);
5.

Poziionarea n fiier

n mod firesc accesul la nregistrrile unui fiier se produce n mod secvenial,


prin parcurgerea nregistrrilor de la nceputul fiierului pn la nregistrarea
dorit. Funcia de bibliotec fseek este util deoarece are ca efect poziionarea n
fiier fr a fi necesar parcurgerea explicit a nregistrrilor anterioare celei
cutate.
Prototipul funciei este:
int fseek (FILE *pf, long increment, int mod)
mod poate fi:
0 dac increment indic numrul de octei fa de nceputul fiierului
1 dac increment indic numrul de octei fa de poziia curent
2 dac increment indic numrul de octei fa de sfritul fiierului
n parcurgerea nregistrrilor dintr-un fiier, n mod frecvent este necesar
verificarea dac s-a ajuns sau nu la sfritul fiierului. n acest scop se va folosi
funcia: feof
int feof(FILE *stream);
Funcia returneaz 1 dac s-a ajuns la sfritul fiierului, 0 dac nu s-a ajuns
la sfritul fiierului i -1 n caz de eroare.
Alte funcii de prelucrare a fiierelor
Funciile fgetc i fputc au prototipueile
int fgetc(FILE *pf);
int fputc(int c, FILE *pf);
Efectul: citirea/scrierea unui caracter din/n fiierul identificat prin pointerul pf.
Funcia fgetc returneaz caracterul citit.
Exemplu: Programul citete caracter cu caracter un fiier i afieaz coninutul
acestuia pe ecran:
#include <stdio.h>
void main(void)
{
int c;
FILE *ptr;
ptr = fopen("c:\\test.txt","r");

while ((c = fgetc(ptr)) != EOF)


{
printf("%c",c);
}
fclose(ptr);

74

Algoritmi i structuri de date

Funciile fgets i fputs au prototipurile


int *fgets(char *sir, int n, FILE *stream);
int fputs(const char *sir, FILE *stream);
Efectul: citirea/scrierea unui ir de caractere din-n fiierul identificat prin
pointerul pf.
Operaiile de inserare sau tergere a unei nregistrri din fiier sunt operaii
complexe realizate n mai multe etape. Se folosesc funciile de bibliotec standard
enumerate (deschidere fiier, creare fiier, scriere, citire, etc.) n implementarea
operaiilor de inserare-tergere, ns nu exist funcii standard corespunztoare
celor dou operaii ca atare.
Inserarea pe o poziie k a unei noi nregistrri presupune parcurgerea
urmtoarelor etape:
1. deschiderea fiierului iniial n citire
2. crearea unu fiier auxiliar i deschiderea sa n modul scriere
3. parcurgerea secvenial fiierului iniial i copierea nregistrrilor n
fiierul auxiliar pn la nregistrarea k
4. scrierea noii nregistrri n fiierul auxiliar
5. continuarea parcurgerii fiierului iniial i continuarea copierii
nregistrrilor n fiierul auxiliar
6. redenumirea fiierului auxiliar cu numele fiierului iniial
tergerea nregistrrii k dintr-un fiier presupune de asemenea folosirea unui
fiier auxiliar i parcurgerea etapelor urmtoare:
1. crearea fiierului auxiliar i deschidere sa n modul scriere
2. parcurgerea i copierea nregistrrilor fiierului iniial n fiierul auxiliar
pn la nregistrarea de pe poziia k-1
3. citirea nregistrrii k fr a fi copiat n auxiliar
4. parcurgerea i copierea nregistrrilor fiierului iniial n fiierul auxiliar
de la nregistrarea k+1 pn la sfrit.
O variant de inserare sau tergere a unei nregistrri este aceea prin care se
renun la folosirea unui fiier auxiliar i se construiete o structur liniar de tip
tablou n care sunt salvate temporar nregistrrile fiierului iniial.
Afiarea coninutului unui fiier. Etape:
Deschide fiierul
Cttimp nu s-a ajuns la sfritul fiierului
Citete linia curenta
Afiare pe ecran linia citita
Sfcttimp
nchidere fiier
Copierea coninutului unui fiier (fis1.dat) ntr-un alt fiier (fis2.dat). Etape:
Creeaz fiierul fis2.dat
75

Algoritmi i structuri de date

Deschide fiierul fis1.dat


Cttimp (nu s-a ajuns la sfritul fiierului fis1.dat)
Citete din fiierul fis1.dat
Scrie n fiierul fis2.dat
Sfcttimp
nchidere fiierele.
Probleme:
1. S se scrie un program C care citete de la tastatur un vector de
structuri Punct3D i le salveaz ntr-un fiier puncte.dat.
2. S se scrie un program C care citete din fiierul puncte.dat un
vector de structuri Punct3D i afieaz coordonatele punctelor pe
ecran.
3. Scriei un program care gestioneaz ntr-un fiier structuri de tipul
Persoana (CNP, nume, prenume, data nasterii, etc.). Operaiile
evideniate sunt cele de adugare nregistrare, tergere nregistrare,
cutare persoan dup valoarea cmpului CNP, ordonarea
alfabetic a persoanelor memorate n fiier.

76

Algoritmi i structuri de date

VII.

ALOCAREA DINAMICA A MEMORIEI

Una dintre cele mai costisitoare resurse ale unui program este este memoria
utilizat. Programarea este o activitate n care, n afara corectitudinii programelor
elaborate, se urmrete i eficiena acestora, msurat prin timpul de execuie i
memoria utilizat. n scopul economiei resurselor, limbajele de programare ofer
programatorului instrumentele necesare unei bune gestionri a memoriei.
Alocarea memoriei pentru variabilele locale declarate ntr-o funcie se face n
etapa de execuie a programului, pe stiv, i nu este permanent. La ieirea din
funcie, stiva se cur, fapt pentru care datele alocate sunt distruse. Acest
mecanism de alocare este eficient i se denumete alocare dinamic. n schimb,
datele declarate global au un comportament diferit. Pentru variabilele globale,
memoria este alocat n faza premergtoare execuiei i zona de memorie
respectiv rmne alocat acestora pn la terminarea programului. Spre exemplu,
declararea global a unui tablou unidimensional de lungime maxim 2000
(<TipElement> tablou[2000]; ), face ca o zon de memorie considerabil s fie
blocat pentru aceast structur, indiferent de numrul elementelor folosite efectiv,
numr care n multe cazuri poate fi cu mult mai mic dect dimensiunea maxim
declarat. n astfel de situaii este preferabil o manier de alocare dinamic n
vederea unei gestionri economice a memoriei. Alocarea dinamic a memoriei
poate fi simplificat definit prin totalitatea mecanismelor prin care datelor le sunt
asignate zone specifice de memorie n faza de execuie a programelor.
Dezavantajele alocrii dinamice a memoriei constau n:
1. sarcina suplimentar a programatorului de a asigura i eliberarea
memoriei cnd nu mai are nevoie de datele respective
2. efectul fragmentrii memoriei care poate produce imposibilitatea
alocrii ulterioare a unei zone de memorie de dimensiune dorit.
Limbajul C/C++ ofer programatorului funcii standard prin care se realizeaz
alocarea, respectiv, dealocarea (eliberarea) memoriei. Funciile malloc i free au
prototipurile n fiierul antet: alloc.h i gestioneaz o zon de memorie special,
denumit memoria heap .
Funcia malloc:
void *malloc(size_t dimensiune)
Argumentul funciei este dimensiunea exprimat n octei, semnificnd
dimensiunea zonei alocate. Funcia returneaz n caz de succes adresa de memorie
a zonei alocate. Este posibil ca cererea de alocare s nu poat fi satisfcut dac nu
mai exist suficient memorie n zona de heap sau nu exist o zon compact de
dimensiune cel puin egal cu dimensiune. n caz de insucces, funcia malloc
returneaz NULL.
Funcia calloc:
void *calloc(size_t nrElemente, size_t dimensiune)
Efectul funciei: se aloc un bloc de memorie de mrime nrElemente *
dimensiune (aceast zon nu trebuie s depeasc 64 Ko octei din heap) i
coninutul blocului alocat este ters. n caz de succes, funcia returneaz adresa

77

Algoritmi i structuri de date

blocului de memorie alocat; returneaz 0 dac nu exist spaiu liber de mrimea


solicitat.
Funcia realloc:
void *realloc(void* adrBloc, size_t Dimensiune)
Funcia ncearc s mreasc sau s micoreze un bloc (alocat dinamic)
la numrul de octei specificai de parametrul Dimensiune. Parametrul adrBloc
indic un bloc de memorie obinut prin apelarea anterioar a funciei malloc,
calloc sau realloc. Dac parametrul adrBloc este 0, efectul este identic cu al
funciei malloc. Dac Dimensiune este 0 efectul apelului este identic cu cel al
funciei free.
Funcia ajusteaz mrimea blocului alocat la Dimensiune i returneaz adresa
blocului realocat sau 0 - dac nu se poate face realocarea.
Funcia free:
void free(void * p)
Argumentul funciei este adresa zonei ce va fi eliberat. Efectul apelului
funciei free este de a elibera memoria a crei adres este p.
Exemplu:
tip *p;
p= (tip*)malloc(sizeof(tip)) ;
free(p);
Observaie: operatorul sizeof returneaz dimensiunea n octei a expresiei:
sizeof(expresie). Construcia (Tip*) realizeaz o conversie explicit de tip: din tipul
pointer nedefinit (void *) spre tipul pointer la Tip.
Utilizarea celor dou funcii standard malloc i free devine util i n programe
n care se vor manipula tablouri de elemente: iruri de caractere, vectori de date
simple, vectori de structuri, etc.
Programele urmtoare sunt exemple simple de gestionare eficient a memoriei
prin alocarea dinamic a structurilor de date:
Program Exemplu de alocarea dinamic a tablourilor.
#include <stdio.h>
#include <stdlib.h>
int *citire_vector(int *pDim)
{
int * vector;
int i;
printf("\nDati n:");scanf("%d", pDim);
if ((vector=(int *)calloc(*pDim,sizeof(int)))==NULL )
{
printf("Insucces la alocarea dinamica");
exit(1);
}
for (i=0; i<*pDim; i++)

78

Algoritmi i structuri de date


{
printf("vector[%d]=", i+1);
scanf("%d", &vector[i]);// sau scanf("%d", vector+i);
}
return vector;
//se returneaza adresa vectorului
}
void afisare_vector(int *vector, int n)
{
int i;
for (i=0; i<n; i++)
{
printf("\nvector[%d]=%d", i+1, vector[i]);
}
}
void main(void)
{
int n;
int *vector;
vector=citire_vector(&n);
afisare_vector(vector, n);
}

Program 2 - operaii cu iruri de caractere. Programul ca aloca dinamic memorie


pentru un ir de caractere sir2 i va efectua o copiere a irului sir1 citit de la
tastatur n zona de memorie rezervat noului ir.
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
void main(void)
{
char sir1[10];
char *sir2;
printf("introduceti un sir de caractere: \n");
scanf("%9s", sir1);
// se retin primele 10 caractere introduse
int dimCh=sizeof(char);
if ((sir2=(char *)malloc( (strlen(sir1)+1)* dimCh))==NULL)
{
printf("Insucces la alocarea dinamica");
exit(1);
}
strcpy(sir2, sir1);
printf("Sirul sursa: %s \n", sir1);
printf("Sirul destinatie: %s \n", sir2);
}

79

Algoritmi i structuri de date

VIII. LIST SIMPLU NLNUIT


Lista reprezint o mulime de dimensiune variabil, format din elemente de
acelai tip. nelegem prin list o mulime dinamic i omogen, a crei
dimensiune se modific n timpul rulrii programului.
Memorarea structurilor de date de tip list se poate face n dou maniere:
1. secvenial - elementele listei sunt memorate la adrese consecutive
2. nlnuit elementele listei nu ocup adrese consecutive, fiecare
element conine pe lng informaia propriu-zis i o legtur spre
urmtorul element.
Memorarea secvenial se poate produce prin folosirea structurilor de date de tip
tablou. Dinamica listei secveniale se realizeaz n aceast situaie prin reinerea
unui parametru suplimentar cu semnificaia dimensiunii curente a tabloului. Pentru
listele organizate static, sub form de tablou, ordinea elementelor este cea implicit
n tablou.
n ciuda simplitii acestei abordri se prefer varianta elementelor nlnuite i
alocate dinamice. Argumentul folosirii listelor nlnuite rezid din necesitatea
economiei de memorie. Folosirea tablourilor foreaz o declararea a numrului
maxim de elemente ceea ce rareori este cunoscut la momentul dezvoltrii
programului. n consecin, folosirea unui tablou de o dimensiune mult mai mic
dect maximul declarat, permite manevrarea elementelor sale dar n acelai timp
produce o risip inutil a unui bloc memorie alocat i neutilizat.
n spiritul economiei de memorie, abordarea structurilor de date de tip list prin
liste nlnuite alocate dinamic este avantajoas. Organizarea acestor structuri de
date n C se face prin folosirea unor legturi care nu sunt altceva dect pointeri
(adrese) spre urmtoarele elemente din list. Pentru listele organizate dinamic, sub
form nlnuit, ordinea elementelor este dat de pointeri.Pointerii legtur din
compunerea unui element al listei indic adresele unor alte elemente de acelai tip.
Elementele unei liste se denumesc n mod uzual noduri. Prin folosirea pointer-ilor
(legturi), structura unui nod al listei devine recursiv.
Liste nlnuite pot fi: simplu, dublu sau multiplu nlnuite. Clasificarea listelor
se face n funcie de numrul legturilor din compunerea unui element.
Operaiile principale cu liste sunt urmtoarele:
1. parcurgere
2. creare
3. distrugere (tergere)
4. adugare nou element
5. tergere element
Lista simplu nlnuit
Structura nodului listei simplu nlnuite:
Figura urmtoare prezint n mod grafic structura unui nod al listei simplu
nlnuite, punndu-se n eviden cele dou pri ale nodului:
1. zona de informaii, format din unul sau mai multe cmpuri
80

Algoritmi i structuri de date

2. legtura spre alt nod de acelai fel

info

adr

O structur care descrie compunerea unui element de acest gen este urmtoarea:
struct nod
{
//declaraii de date cmpuri ale informaiei
struct nod *adr; //adresa urmtorului nod
}
Convenim c informaia din noduri conine un cmp special (cheie) ale crui
valori sunt distincte pentru elementele aceleiai liste (cheie nu este un cmp
obligatoriu). Pentru a simplifica exemplele urmtoare, vom introduce un nou tip de
dat, tipul nodurilor, denumit TNod.
typedef struct nod
{
int cheie; //cmp cu valori unice pentru nodurile listei
//alte cmpuri
struct nod *urm; //adresa urmtorului nod
}Tnod;
Gestionarea unei liste de astfel de noduri necesit cunaterea adresei primului i
eventual al ultimului nod din list. Reinndu-se doar adresa primului nod, celelalte
noduri pot fi parcurse, accesate, prin urmrirea legturilor urm coninute n
nodurile curente.
Adresa fiecrui nod este coninut de nodul precedent, cu excepia primului nod
al listei, pentru care nu exist un nod precedent. Ultimul nod nu va referi niciun alt
nod urmtor, fapt care se marcheaz prin pointerul urm care devine Null. Figura
urmtoare sugereaz organizarea unei liste simplu nlnuite:
.

prim

ultim

NULL

Pentru implementarea operaiilor specifice listelor nlnuite este util


declararea a doi pointeri la tipul Tnod, cu semnificaia adreselor primului i
ultimului element din list:
Tnod *prim, *ultim;
Parcurgerea listei
Parcurgerea listei presupune accesarea sau prelucrarea elementelor listei n
ordinarea stabilit de legturile coninute de noduri. Cunoscnd primul element
prim , acesta conine adresa urmtorului element, care la rndul su conine adresa
urmtorului, etc. n acest mod, prin urmrirea adreselor de legtur pot fi accesai
toi membrii listei. Terminarea operaiei de parcurgere a listei const n accesarea
ultimului element, marcat prin adres nul a pointerului urm.
81

Algoritmi i structuri de date

Considernd p adresa nodului curent din list, trecerea la nodul urmtor se


obine prin asignarea: p=p->urm;
Dac nodul curent p este Null (p==0), se ncheie parcurgerea.
Paii parcurgerii unei liste sunt urmtorii:
1. iniializeaz pointer-ul curent cu adresa primului nod : p=prim
2. cttimp (pointerul curent este diferit de 0: p!=0 ) execut
a. prelucrare nodul referit de p (accesare, modificare coninut)
b. trecerere la urmtorul nod p=p->urm
Oferim n continuare o funcie C prin care se parcurge lista simplu nlnuit
gestionat de prim i ultim i sunt afiate informaiile nodurilor traversate.
void tiparire_lista()
{
tnod *p; //p semnific nodul curent
p=prim; //se pornete traversarea listei de la primul nod
while(p!=0) //cttimp nu s-a ajuns la sfritul listei
{
printf("\n%d ",p->cheie);
// afiarea celorlalte cmpuri din nodul curent
p=p->urm; //trecere la urmtorul element
}
}

Crearea listei vide


Crearea unei liste nlnuite care nu conine niciun element presupune
iniializarea celor doi pointeri prim i ultim cu valoarea 0:
prim=ultim=0;
Crearea unei liste cu mai mult de 0 noduri presupune adugarea n mod repetat
a noilor noduri pn la ntlnirea unei condiii de terminare a procedeului. Noile
noduri pot fi adugate sistematic dup ultimul element al listei, sau naintea
primului nod. n procesul de adugarea a unui nou nod se ine cont de dou
aspecte:
1. nodul nou trebuie alocat n memorie i ncrcat cu informaie
2. anumite legturi din list trebuie refcute pentru a asigura consistena
organizrii
Prezentm n continuare o funcie C care are efectul alocrii i ncrcrii unui
nou nod de tipul Tnod. Utilitatea acestei funcii o vom nelege n construirea
funciilor de inserare - adugare noduri la list:
tnod * incarca_nod()
{
tnod *p;
p=(tnod*)malloc(sizeof(tnod)); //alocare memorie
printf("\n dati cheia"); scanf("%d",&p->cheie);
//citire cheie
//citire alte informaii coninute de nod
return p; //returnarea adresei nodului ncrcat
}

82

Algoritmi i structuri de date

Inserarea unui nou element:


1. naintea primului nod
Etapele introducerii unui nou nod naintea primului nod al listei gestionate prin
pointerii prim i ultim sunt urmtoarele:
1. alocarea i ncrcarea noului nod:
2. stabilirea faptului c acest nou nod va adresa ca urmtor element
chiar pe nodul prim: nou->urm=prim
3. stabilirea noului nod ca prim element al listei: prim=nou
Observaie: dac lista este vid n momentul ncercrii de a aduga un nod nou,
efectul operaiei const n obinerea unei liste cu un singur element, fapt pentru
care capetelor listei prim i ultim sunt confundate i este necesar tratarea acestui
caz particular.

1
prim
nou

void adaugare_prim()
{
tnod *p;
p=incarca_nod();
if (prim= =0) //lista vida
{prim=p;
ultim=p;
ultim->urm=0;
return;
}
p->urm=prim;
prim=p;
}

2. Dup ultimul nod


Etapele introducerii unui nou nod dup ultimul nod al listei gestionate prin
pointerii prim i ultim sunt urmtoarele:
1. alocarea i ncrcarea noului nod:
2. stabilirea faptului c acest ultimul nod va adresa ca urmtor
element pe noul nod: ultim->urm=nou (1)
3. stabilirea noului nod ca ultim element al listei, i marcarea acestuia
ca ultim nod din list: ultim=nou; nou->urm =0; (2)
4. dac lista este vid n momentul ncercrii de a aduga un nod nou,
se va trata distinct acest caz

83

Algoritmi i structuri de date

1
2

ultim
nou

void adaugare_ultim()
{ tnod *nou; nou=incarca_nod();
if (prim==0) //lista vida
{prim=nou;
ultim=nou;
ultim->urm=0;
return;
}
ultim->urm=nou;
ultim=nou; ultim->urm=0;
}

3. Inserarea unui nod naintea unui nod specificat


Acest tip de operaie se realizeaz n dou etape:
1. cutarea nodului naintea cruia se va insera un nou nod
2. inserarea propriu-zis a noului nod
Cutarea unui nod specificat prin cheie se realizeaz printr-un algoritm
elementar de parcurgere sistematic a listei pn la ntlnirea nodului ce conine
cheia cutat. n acest scop se poate construi o funcie care returneaz adresa
nodului cutat, i 0 n caz de insucces (dac nodul de cheie dat nu exist n list).
Argumentul funciei este valoarea cheii cutate.
Cutarea nodului naintea cruia se va insera noul nod poate fi realizat n
aceiai funcie n care se face inserarea. Procedura de inserare ine cont de cazul
particular n care nodul specificat prin cheie este primul nod.
Stabilirea legturilor dintre noduri trebuie fcut corect pentru a nu genera
pierderea unei sub-liste de noduri. n acest sens este necesar reinerea n
permanen a adresei nodului precedent nodului curent, pentru ca ulterior noul nod
s poate fi nlnuit ntre nodurile precedent i curent. Imaginea urmtoare
sugereaz etapele inserrii noului nod, cunoscndu-se adresa prev (nodul
precedent= i adresa nodului curent p:
1. nodul nou va conine adresa nodul curent: nou->urm=p;
2. precedentul nod va conine adresa noului nod prev->urm=nou; atribuire prin care vechea nlnuirea a nodurilor prev i p se
pierde.

84

Algoritmi i structuri de date

.
prev

curent

nou

Funcia urmtoare descrie operaia de inserare naintea unui nod cutat dup
valoarea cheii:
void adaugare_inainte()
{
int valoare;
printf("\ndati cheia de cautat:");scanf("%d",&valoare);
tnod *p,*prec, *nou;
p=prim; //iniializare nodul curent cu prim
while (p!=0)
{
if (p->numar!=valoare)
{//NU s-a gasit nc nodul
prec=p; //salveaz adresa precedentului n prev
p=p->urm; //trece la urmtorul element
}
else
{ //s-a gsit nodul de cheie dat
if (p==prim)
{//caz particular, nodul cutat este primul
adaugare_prim();return;
}
else
{
nou=incarca_nod();
nou->urm=p; //reface legturile
prec->urm=nou;
return;
}
}
}
}//sfrit funcie

4. Dup un nod stabilit de o cheie:


Operaia de inserare dup un nod specificat este similar celei anterioare. Cazul
particular al procedeului const n gsirea ca nod de referin chiar ultimul nod al
listei, fapt pentru care operaia se reduce la adugarea dup ultim (funcie deja
construit).

85

Algoritmi i structuri de date

n plus, reinerea unei adresa a precedentului nodului curent nu mai este


necesar. n fapt, nodul nou se va insera ntre nodul gsit (curent) i urmtorul nod
al nodului curent. ns, prin maniera de nlnuire, adresa urmtorului nod al
nodului gsit este cunoscut, fiind memorat ca legtur chiar n nodul gsit: p>urm .

.
p

p->urm

nou

Considernd nodul de cheie dat gsit: p, etapele inserrii noului nod sunt:
1. stabilete adresa urm a nodului nou ca fiind adresa nodul urmtor al lui p:
nou->urm=p->urm (1)
2. stabile;te adresa urm a lui p ca fiind adresa lui nou: p->urm=nou. Prin
aceast asignare s-a pierdut automat nlnuirea veche ntre p i p->urm
void adaugare_dupa()
{
int valoare; printf("\ndati cheia de
cautat:");scanf("%d",&valoare);
tnod *p,*nou;
p=prim;
while (p!=0)
{
if (p->numar!=valoare)
{//NU am gasit
p=p->urm;
}
else
{ //caz particular
if (p==ultim)
{
adaugare_ultim();return;
}
else
{
//alocare memorie i ncrcare nod cu inf.
nou=incarca_nod();
nou->urm=p->urm; //stabilirea legturilor
p->urm=nou;
return;
}
}
}
}

86

Algoritmi i structuri de date

tergerea unui element


1. tergerea primului element al listei necesit eliberarea memoriei
dar i actualizarea pointer-ului prim necesar n gestionarea
ulterioar a listei. Actualizarea prim-ului nod al listei se face prin
asignarea prim=prim->urm, prin care urmtorul nodului prim
(secundul) devine prim al listei
void stergere_prim()
{
tnod *primvechi;
primvechi=prim; //salvare adresa primului element
if (primvechi==0)
{//lista este vid, nu se va terge nimic
printf("\nlista este vida");
return;
}
prim=prim->urm; //actualizare prim
free(primvechi);//eliberarea memoriei adresata de prim
}

2. tergerea ultimului presupune refacerea legturilor, eliberarea


unei zone de memorie i actualizarea pointer-ului ultim (util n
gestionarea listei). n contradicie cu operaia de tergere a
primului nod, tergerea ultimului nod al listei necesit o parcurgere
n totalitate a listei, pentru a determina adresa precedentului
nodului ultim. Acest lucru este necesar pentru a realiza operaia de
actualizare a pointer-ului ultim. Adresa urmtorului nod dup prim
se poate determina n mod elementar prin cmpul urm, ns, adresa
nodului anterior ultimului se determin printr-un procedeu
suplimentar de traversare a listei, genernd un cost suplimentar al
algoritmului.
Etapele tergerii ultimului element al unei liste sunt:
a. traversarea listei pentru a determina adresa penultimului element
b. actualizarea ultimului nod
c. eliberarea memoriei
void stergere_ultim()
{
tnod *ultimvechi,*prec,*p;
p=prim;
if (p==0)
{
printf("\nlista este vida");return;
}
while (p!=ultim)
//traversarea listei i reinerea precedentului
//curent
{
prec=p; //salvare precedent

87

nodului

Algoritmi i structuri de date


p=p->urm; //trecere la urmtorul nod
}
//n acest punct prec este adresa penultimului nod
ultimvechi=p; //salvare vechiul nod ultim
ultim=prec; ultim->urm=0; //actualizare i marcare ultim
free (ultimvechi); //eliberare memorie
}

3. tergerea unui element precizat


tergerea unui element precizat prin valoarea cheii se execut prin urmtorii
pai:
- cutarea nodului p ce va fi ters i reinerea adresei precedentului
acestuia: prev
- refacerea legturilor: prev->urm= p->urm (1)
- eliberarea memoriei
Cazurile particulare ale procedurii sunt tratate distinct prin procedurile specifice
de tergere a primului sau ultimului nod.

prev

void stergere_oarecare()
{
int valoare; printf("\ndati cheia de
cautat:");scanf("%d",&valoare);
tnod *p,*prev,*pvechi;
p=prim;
while(p!=0)
{
if (p->cheie==valoare)
{//s-a gsit nodul i acesta va fi ters
if (p==prim)
{
stergere_prim();
return;
} //caz particular
if (p==ultim)
{
stergere_ultim();
return;
} //caz particular
//cazul general
pvechi=p; //salvare adres nod curent
prev->urm=p->urm; //refacere legturi
free(pvechi); //eliberare memorie
return;

88

Algoritmi i structuri de date


else

}
{//nu s-a gsit nc
prev=p; //salvarea adresei precedentului
p=p->urm; //trecere la urmtorul nod
}

}
}

4. tergerea listei
tergerea complet a listei se poate realiza prin apelul repetat al funciilor deja
construit de tergere a primului, respectiv, a ultimului nod pn cnd lista devine
vid (pointer-ul prim devine Null). Din punct de vedere al eficienei, variante
tergerii listei prin apelul funciei tergere_prim este preferat deoarece nu necesit
traversarea listei pentru fiecare operaie de tergere a unui nod.
O variant simpl dar greit de tergere a listei const n distrugerea capetelor
listei prim i ultim, fapt pentru care gestionarea ulterioar a listei ( accesarea i
prelucrarea nodurilor sale ) devine imposibil. Cu toate acestea, memoria rmne
alocat nodurilor intermediare. Prin aceasta s-a distrus doar mecanismul de
accesare a nodurilor, nu s-au distrus efectiv nodurile listei.
Funcia urmtoare este o variant de tergere a listei, prin tergerea repetat a
nodurilor din captul listei.
void stergere_lista()
{
tnod*p,*primvechi;
p=prim;
while(p!=0) //ct timp mai sunt noduri n list
{
if (prim==0)
{
printf("\nlista e complet stearsa");
return;
}
else
{
//sterg primul nod i actualizez prim
primvechi=prim;
prim=prim->urm;
free(primvechi);
}
}
}

89

Algoritmi i structuri de date

IX.

LISTA DUBLU NLNUIT

Lista dublu nlnuit este format din noduri care conin:


- informaie
- adresa urmtorului nod
- adresa precedentului nod

info
Avantajul utilizrii listelor dublu nlnuite rezult din posibilitatea parcurgerii
(traversrii) listei n ambele sensuri: de la primul la ultimul, respectiv, de la ultimul
la primul nod. Acest lucru permite o manipulare mai flexibil a nodurilor listei
Structura unui nod al listei dublu nlnuite este urmtoarea:
struct nod
{
//declaraii de date cmpuri ale informaiei
struct nod *urm; //adresa urmtorului nod
struct nod *prec; //adresa nodului precedent
}
n exemplele urmtoare vom utiliza un tip de date utilizator prin care specificm
tipul nodurilor listei:
typedef struct nod
{int cheie;
..//alte cmpuri
struct nod *pre;
struct nod* urm;
}tnod;
Ca i la lista simplu nlnuit, principalele operaii sunt:
- crearea;
- accesul la un nod; parcurgerea listei
- adugarea unui nod;
- tergerea unui nod,
- tergerea listei.
Gestionarea unei liste dublu nlnuite se face n maniera similar listelor simplu
nlnuite prin adresele nodurilor prim i ultim. n plus, nodul prim se marcheaz
prin stabilirea adresei precedentului su la Null: prim->prec=0.
tnod *prim,*ultim;
Figura urmtoare sugereaz maniera de organizare a unei liste dublu nlnuite
(alocate dinamic):

ultim

prim

90

Algoritmi i structuri de date

Crearea listei dublu nlnuit


Crearea listei vide presupune iniializarea celor doi pointer de control prim
i ultim cu valoarea 0 (Null): prim=ultim=0. Crearea unei liste cu mai mult de un
nod se rezum la apelul repetat al subrutinelor de adugare a unui nou nod (naintea
primului sau dup ultimul nod).
Funcia urmtoare este un exemplu prin care se poate crea o list dublu
nlnuit prin adugarea noilor noduri dup ultimul nod. Un caz particular al
procedurii de adugare a noului nod este tratat distinct: n situaia n care lista este
vid, dup adugarea unui nod trebuie marcate nodurile prim i ultim. Funcia
incarca_nod este cea definit n capitolul dedicat listelor simplu nlnuite.
void creare()
{
tnod *p;
int rasp;
//creare lista vida
prim=ultim=0;
printf("\nIntroduceti? (1/0)");scanf("%d",&rasp);
while (rasp==1)
{
p=incarca_nod(); //alocare memorie i ncrcare nod
if (prim==0)
{//creare primul nod
prim=p;ultim=p;
prim->pre=0; ultim->urm=0; //marcare capete list
}
else
{
ultim->urm=p;
p->pre=ultim;
ultim=p;
ultim->urm=0;
}
printf("\nIntroduceti? (1/0)");scanf("%d",&rasp);
}
}

Parcurgerea listei dublu nlnuit


Spre deosebire de listele simplu nlnuite, listele dublu nlnuite pot fi
parcurse n ambele sensuri. Prezentm n continuare funcii de afiare a informaiei
nodurilor parcurse n dou variante: prin folosirea legturii urmtor (urm),
respectiv, succesor (prec).
void tiparireDirecta()
{
tnod *p;
if (prim==0)
{printf("\nLista e vida!");
return;

void tiparireInversa()
{
tnod *p;
if (prim==0)
{printf("\nLista e vida!");
return;
91

Algoritmi i structuri de date

}
p=prim; //iniializare adres nod curent
while(p!=0)
{
printf("\n %d",p->cheie);
p=p->urm; //trece la urmtorul nod
}
}

}
p=ultim; //iniializare adres nod curent
while(p!=0)
{
printf("\n %d",p->cheie);
p=p->prec; //trece la precedentul nod
}
}

ADUGAREA UNUI NOU NOD


Sunt tratate n continuare diferite modaliti de adugare a unui nou nod ntr-o
list dublu nlnuit:
1. Adugare naintea primului nod
Adugarea unui nou nod naintea primului nod ale listei presupune efectuarea
urmtoarelor operaii:
1. alocarea i ncrcarea noului nod:
2. stabilirea faptului c acest nou nod va adresa ca urmtor element
chiar pe nodul prim: nou->urm=prim (1)
3. stabilirea faptului c nodul prim va referi ca precedent element pe
nodul nou: prim->prec=nou (2)
4. stabilirea noului nod ca prim element al listei: prim=nou (3)
5. marcarea nodului prim: prim->prec=0 (4)
1
.
prim
3
4

nou

Observaie: n cazul n care lista este naintea adugrii unui nod nou, efectul
operaiei const n obinerea unei liste cu un singur element, fapt pentru care
capetelor listei prim i ultim sunt confundate i este necesar tratarea acestui caz
particular.
void adaugare_prim()
{
tnod *nou; p=incarca_nod();
if (prim==0)
{
prim=nou;ultim=nou;
prim->prec=0;ultim->urm=0;
}
else
{
nou->urm=prim; //pasul 1

92

Algoritmi i structuri de date


prim->prec=nou; //pasul 2
prim=nou; //pasul 3
prim->prec=0; //pasul 4
}
}

2. Adugare dup ultimul nod


Adugarea unui nou nod dup ultimul nod al listei presupune efectuarea
urmtoarelor operaii:
- alocarea i ncrcarea noului nod:
- stabilirea faptului c acest nou nod va adresa ca precedent element
chiar pe nodul ultim: nou->prec=ultim (1)
- stabilirea faptului c nodul ultim va referi ca urmtor element pe nodul
nou: ultim->urm=nou (2)
- stabilirea noului nod ca ultim element al listei: ultim=nou (3)
- marcarea nodului ultim: ultim->urm=0 (4)
Cazul special al procedurii (lista este vid) se trateaz n mod diferit.
1
.
4

ultim
2
3

void adaugare_ultim()
{
tnod *nou;nou=incarca_nod();
if (prim==0)
{
prim=nou;ultim=nou;
prim->prec=0;ultim->urm=0;
}
else
{
nou->prec=ultim; //(1)
ultim->urm=nou; //(2)
ultim=nou; //(3)
ultim->urm=0; //(4)
}
}

93

nou

Algoritmi i structuri de date

3. Adugare naintea uni nod specificat prin cheie


Adugarea unui nod naintea unui nod specificat prin valoarea cheii, se
realizeaz prin dou etape:
- cutarea nodului naintea cruia se va face inserarea
- inserarea propriu-zis
Reamintim c la operaia similar pentru liste simplu nlnuite era necesar
determinarea nodului precedent nodului precizat de cheie li acest lucru se realiza
printr-un pointer auxiliar n care se reinea acea adres. n cazul listelor dublu
nlnuite lucrurile sunt simplificate datorit adreselor precedent coninute de
fiecare nod, adrese utile n accesarea vecinilor (nodurile anterioare).
Dac nodul cutat este chiar primul, problema se reduce la un caz particular
tratat prin funcia adaugare_prim. Dac lista este vid sau nodul cutat nu s-a gsit,
nu se va aduga un nou nod.
4

p->prec

nou

void adaugare_inainte(int valoare)


{
tnod *p,*nou; //p contine
adresa nodului curent in
parcurgerea listei
p=prim;
while(p!=0)
{ //se parcurge direct lista, de la primul spre ultimul nod
if (p->cheie!=valoare)
{//nu s-a gasit inca nodul de cheie data
p=p->urm; //trecere la urmatorul nod
}
else
{
//am gasit nodul p inaintea caruia se insereaza nou;
if (p!=prim)
{
nou=incarca_nod();
nou->urm=p; //(1)
(p->pre)->urm=nou; //(2)
nou->pre=p->pre; //(3)
p->pre=nou; //(4)
return;
}

94

Algoritmi i structuri de date


else

{
adaugare_prim();
return;
}

}
}
}//sfarsit functie

Observaie: Pentru manevrarea legturii urmtoare a nodului precedent celui


curent (notm nodul curent p) este valabil construcia: (p->pre)->urm, unde (p>pre) este adresa precedentului nodului p.
4. Adugare dup un nod specificat prin cheie
Procedura de adugare a unui nou nod dup un nod precizat prin valoarea cheii
este simalr celei prezentate la punctul 3. Cazul particular este cel n care nodul
cutat este ultimul nod al listei, n aceast situaie se va apela funcia
adugare_ultim. Dac lista este vid sau nu s-a gsit nodul cutat, nu are sens s
se opereze adugarea unui nou nod.
Inserarea propriu-zis a nodului nou naintea nodului p presupune refacerea
legturilor n aa fel nct consistena listei s fie asigurat (s nu se piard
secvene de noduri prin distrugerea accesului la ele ):
- nodul nou va avea ca legtur urm pe urmtorul nod al nodului p:
nou->urm=p->urm; (1)
- nodul p va fi urmat de nou
p->urm=nou; (2)
- precedentul nodului nou va fi nodul p
nou->pre=p; (3)
- nodul precedent al nodului urmtorul lui p devine nou:
(p->urm)->pre=nou; (4)
void adaugare_dupa(int valoare)
{
tnod *p,*nou;
//caut p si inserez nod
p=prim;
while(p!=0)
{
if (p->cheie!=valoare)
{
p=p->urm;
}
else
{
if (p==ultim)
{adaugare_ultim();return;}
else

95

Algoritmi i structuri de date


{
nou=incarca_nod();
nou->urm=p->urm;
p->urm=nou;
nou->pre=p;
(p->urm)->pre=nou;
return;
}
}

TERGEREA UNUI NOD


tergerea capetelor prim i ultim ale unei liste dublu nlnuite nu difer prin
costul de calcul precum la listele simplu nlnuite. Am vzul c n cazul listelor
simplu nlnuite tergerea ultimului nod necesita un efort computaional mai mare.
Prin legtura prec a nodurilor unei liste dublu nlnuite putem accesa nodurile
precedent, fapt pentru care, la tergerea ultimului nod nu este necesar traversarea
complet a listei.
tergerea unui capt al listei presupune:
- salvarea temporar a adresei captului respectiv ntr-un pointer auxiliar
- actualizarea i marcarea noului capt
- eliberarea memoriei
Oferim n continuare dou variante pentru funciile de tergere a capetelor prim
i ultim:
/*tergere prim nod*/
void stergere_prim()
{
tnod*primvechi;
if (prim==0) //lista vid
return;
else
{//mai sunt noduri
if(prim!=ultim)
{//salvare prim
primvechi=prim;
//actualizare prim
prim=prim->urm;
//marcare prim
prim->pre=0;
//eliberare memorie
free(primvechi);
}
else
prim=ultim=0;
}
}//sfarsit functie

/*tergere ultim nod*/


void stergere_ultim()
{
tnod*ultimvechi;
if (ultim==0) //lista vid
return;
else
{
if (prim!=ultim)
{ //salvare ultim
ultimvechi=ultim;
//actualizare ultim
ultim=ultim->pre;
//marcare ultim
ultim->urm=0;
//eliberare memorie
free(ultimvechi);
}
else
prim=ultim=0;
}
}//sfarsit functie

96

Algoritmi i structuri de date

tergerea unui nod oarecare


tergerea unui nod oarecare precizat prin valoarea cheii presupune:
- cutarea nodului
- tergerea propriu-zis a nodului
Cutarea nodului se face prin parcurgerea ntr-un sens a listei i compararea
valorii cheii nodurilor curente cu valoarea dat. Dac se gsete un nod care
verific condiie, se va opera etapa de tergere propriu-zis a nodului prin:
o salvarea adresei nodului de ters
o refacerea legturilor pentru a asigura consistena listei i
posibilitatea parcurgerii ulterioare n ambele sensuri
o eliberarea memoriei alocate nodului de ters
1

2
p->urm

p->prec

Refacerea legturilor const din urmtoarele asignri:


- precedentul urmtorului lui p devine precedentul lui p
p->urm->pre=p->pre; (1)
- urmtorul precedentului lui p devine urmtorul lui p:
p->pre->urm=p->urm; (2)
Cazurile particulare se trateaz separat: lista este deja vid sau lista devine vid
dup tergerea nodului.
void stergere_nod(int valoare)
{
tnod *p,*pvechi;
if (prim==0) return; //lista este deja vida
if (prim==ultim && prim->cheie==valoare)
{//lista devine vida
prim=ultim=0;
return;
}
p=prim;
while(p!=0)
{
if (p->cheie==valoare) //gasit
{
if (p==prim)
{stergere_prim();return;}
if (p==ultim)
{stergere_ultim();return;}

97

Algoritmi i structuri de date

pvechi=p; //salvare adres nod curent


p->urm->pre=p->pre; (1)
p->pre->urm=p->urm; (2)
free(pvechi); //eliberare memoriei- adresa pvechi
return;
}
else //nu s-a gasit nc
p=p->urm; //trecere la urmtorul nod
}

tergerea listei
tergerea complet listei dublu nlnuit se poate face cu acelai efort de calcul
prin apelarea repetat a funciei stergere_prim sau stergere_ultim. tergerea
capetelor listei nu asigur eliberarea memoriei ocupate de nodurile intermediare.
Exemplu:tergerea listei prin apelul repetat al funciei de tergere a primului nod.
void stergere_lista()
{
while(prim!=0)
stergere_prim();
}

98

Algoritmi i structuri de date

X.

LISTE CIRCULARE. STIVE. COZI

Lista circular este o list (simplu sau dublu) nlnuit cu proprietatea c toate
nodurile sunt echivalente, respectiv, nu exist noduri speciale care nu conin adresa
nodurilor succesoare sau predecesoare. Aceste noduri speciale - denumite capetele
listei au fost utilizate n gestionarea listelor simplu i dublu nlnuite. O list
circular va fi gestionat prin alte mecanisme dect cele bazate pe meninerea
adreselor speciale prim i ultim.
LISTA SIMPLU NLNUIT CIRCULAR
ntr-o list circular simplu nlnuit toate nodurile conin adresa urmtorului
nod. Structura nodului este similar celei prezentate la capitolul dedicat listelor
simplu nlnuite:
typedef struct nod
{
int cheie; //cmp cu valori unice pentru nodurile listei
//alte cmpuri
struct nod *urm; //adresa urmtorului nod
}Tnod;
Organizarea unei liste circulare cu noduri de acest tip este sugerat de figura
alturat.

Orice list simplu nlnuit gestionat prin pointer-ii prim i ultim se poate
transforma n list circular printr-o operaie elementar de asignare:
ultim->urm=prim
Prin operaia anterioar s-a stabilit faptul c ultimul nod al listei iniiale va
conine adresa primului nod al listei, ceea ce conduce la o structur de list
circular a crei gestionare poate fi efectuat prin adresa pointer-ului prim, ns
fr ca acesta s semnifice adresa unui capt al listei, ci doar adresa unui nod
oarecare.
Spre deosebire de listele simplu nlnuite la care este suficient cunoaterea
adresei primului nod i, eventual, pentru simplificarea prelucrrilor, i a adresei
ultimului nod, ntr-o list circular, cunoaterea adresei oricrui nod din
compunerea listei este suficient pentru a putea gestiona aceast structur. Astfel,
gestionarea unei liste circulare se face prin unui pointer care refer oricare nod al
listei:
Tnod *pLC; //pointer la lista circular

99

Algoritmi i structuri de date


pLC

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

Operaiile posibile cu listele circulare sunt aceleai ca cele specifice listelor


simplu nlnuite:
- parcurgere
- creare
- distrugere (tergere)
- adugare nou element
- tergere element
Crearea listei circulare simplu nlnuite
Crearea listei vide se realizeaz prin iniializarea pointer-ului pLC cu valoarea
Null:
pLC=0;
Crearea unei liste circulare care conine cel puin un element presupune o
operaie repetitiv de adugare a unui nou nod. Pentru nodul care se adaug se va
aloca memorie n prealabil i se acesta va ncrca cu informaii. Funcia
incarca_nod prezentat n capitolele precedente poate fi utilizat n acest sens.
Adugarea nodului nou se poate efectua n dou maniere:
1. adugarea naintea nodului referit de pLC
2. adugarea dup nodul referit de pLC
Adugarea unui nou nod naintea nodului pLC necesit un efort computaional
suplimentar prin parcurgerea listei circulare. Aceast parcurgere este necesar
pentru a determina adresa nodului precedent al nodului pLC n vederea refacerii
legturilor i asigurrii consistenei listei.
Acest aspect a fost evideniat n cazul listelor simplu nlnuite, pentru care
operaiile de inserare naintea unui nod oarecare, respectiv, inserare dup un nod
oarecare difer semnificativ prin necesitatea parcurgerii complete a listei n primul
caz.
Funcia urmtoare adaug noi noduri la lista circular gestionat prin pLC n
varianta 2.
void creare_LCSI()
{
Tnod *nou; int rasp;
pLC=0; //lista este initial vida
printf("\nIntroduceti? (1/0)");scanf("%d",&rasp);
while (rasp==1)
{
nou=incarca_nod();//alocare memorie i ncrcare nod
if (pLC==0)

100

Algoritmi i structuri de date

{//creare primul nod


// nodul pLC contine adresa sa;
pLC=nou;
//lista devine circulara
pLC->urm=pLC;
}
else
{ //adaugare nou dupa pLC
nou->urm=pLC->urm;
pLC->urm=nou;
pLC=nou; //pLC va contine adresa noului nod
}
printf("\nIntroduceti? (1/0)");scanf("%d",&rasp);

Parcurgerea listei circulare simplu nlnuite


Parcurgerea listei circulare se va face prin urmrirea legturilor (adreselor)
coninute de noduri, n aceiai manier ca la listele simplu nlnuite, printr-un
pointer auxiliar. Specificul listelor circulare implic o alt condiie de oprire a
traversrii listelor. Dac pentru listele gestionate prin prim i ultim aceast condiie
era evident (pointer-ul auxiliar prin care se parcurge lista a ajuns la ultim), n
cazul listelor circulare condiia se refer la revenirea n punctul de plecare. Iniial,
pointer-ul conine adresa cunoscut a unui nod oarecare: pLC. Nodurile urmtoare
se parcurg ct timp pointer-ul auxiliar nu va avea aceiai adres de nceput: pLC
(nu a revenit la poziia iniial):
p=pLC;
do
{
//prelucrare nod referit de p
p=p->urm; //trecere la urmatorul nod
}while (p!=pLC);

Funcia urmtoare afieaz cheile nodurilor unei liste circulare:


void Tiparire()
{
tnod *p; //pointer-ul auxiliar
p=pLC; //iniializare p
if (p==0) //lista este vida
return; // nu are sens continuarea parcurgerii
do
{
printf(\n %d,p->cheie);
p=p->urm; //trecere la urmatorul nod
}while (p!=pLC);
}

101

Algoritmi i structuri de date

Operaia de cutare a unui nod specificat prin valoarea cheii presupune


parcurgerea listei circulare i verificarea dac nodurile conin pentru cmpul cheie
valoarea dat. Funcia prin care se realizeaz aceast operaie va returna adresa
nodului gsit sau 0 n caz de insucces:
tnod* cauta(int valoare)
{
tnod *p; //pointer-ul auxiliar
p=pLC; //iniializare p
if (p==0) return 0; //lista este vida
do
{
if (p->cheie==valoare)
return p; //s-a gasit nodul cautat
p=p->urm; //trecere la urmatorul nod
}while (p!=pLC);
return 0; //s-a incheiat cautarea si nodul nu s-a gasit
}

Inserarea nodurilor ntr-o list circular simplu nlnuit


1. Inserarea naintea unui nod specificat prin valoarea cheii
2. Inserarea dup un nod specificat prin valoarea cheii
1. Inserarea unui nou nod naintea unui nod specificat prin valoarea cheii
presupune parcurgerea urmtoarelor etape:
- cutarea nodului de cheie dat
- inserarea propriu-zis dac etapa anterioar s-a ncheiat cu succes
Observaie: n etapa de cutare a nodului de cheie dat se va reine adresa
nodului precedent a nodului curent, pentru a face posibil refacerea legturilor n
faza de inserare. n caz contrar ar fi necesar o reparcurgere a listei circulare pentru
a determina precedentul nodului naintea cruia va fi inserat noul nod.
Cunoscnd adresa nodului de cheie dat i adresa precedentului su, inserarea
propriu-zis a unui nou nod se reduce la: alocarea memoriei, ncrcarea nodului
nou cu informaie i refacerea legturilor ntr-o manier similar celei prezentate n
operaia omonim pentru liste simplu nlnuite:
void inserareInainte(int valoare)
{
tnod *nou;
tnod *prev; //precedentul nodului curent
tnod *p; //pointer care refera nodul curent
if (pLC==0)
return; //lista este vida, nu are sens continuare operatiei
p=pLC;
do
//cautarea nodului p
{
prev=p; //retine precedentul nodului curent
p=p->urm; //trece la urmatorul nod

102

Algoritmi i structuri de date

if (p->cheie==valoare) //s-a gasit nodul


break;
//iesire din instructuinea repetitiva
//p este adresa nodului gasit
}while(p!=pLC);
if (p->cheie!=valoare) //cautarea s-a incheiat cu Insucces
return;
if (p->cheie==valoare) //cautarea s-a incheiat cu Succes
{
//etapa de inserare
nou=incarca_nod(); //alocare memorie si incarcare nou
nou->urm=p;
prev->urm=nou;
}

Observaii:
- nodul referit de pLC este ultimul nod verificat n etapa de cutare
- instruciunea decizional if (p->cheie==valoare) este redundant, dat
fiind faptul c o condiie precedent verificat situaia opus i provoac
revenirea din funcie. Din motive de lizibilitate i nu de optimizare a
codului am convenit s furnizm o variant explicit a funciei pentru o
urmrire uoar a etapelor descrise.
2. Inserarea unui nod nou dup un nod precizat de cheie presupune:
- cutarea nodului de cheie dat
- inserarea propriu-zis
Dac prima etap s-a ncheiat cu succes, se cunoate adresa nodului de cheie
dat p, dar i adresa urmtorului nod (datorit legturii urm) p->urm. Nodul nou
va fi inserat ntre cele dou noduri de adrese cunoscute. Nu mai este necesar
determinare altei adrese dect cea a nodului cutat dup valoarea cheii.
Funcia urmtoare este o posibil implementare a operaiei discutate:
void inserareDupa(int valoare)
{
tnod *nou;
tnod *p; //pointer care refera nodul curent
if (pLC==0) return; //lista este vida, nu are sens continuare
operatiei
p=pLC;
do
//cautarea nodului p
{
if (p->cheie==valoare) //s-a gasit nodul
break; //iesire din instructuinea repetitiva
//p este adresa nodului gasit
p=p->urm; //trece la urmatorul nod
}while(p!=pLC);
if (p->cheie!=valoare) //cautarea s-a incheiat cu Insucces
return;
//dac s-a ajuns n acest punct, cautarea s-a incheiat cu
//Succes

103

Algoritmi i structuri de date


//etapa de inserare
nou=incarca_nod(); //alocare memorie si incarcare nou
nou->urm=p->urm;
p->urm=nou;
}

Observaie: nodul referit de pLC este primul nod verificat n etapa de cutare.
tergerea unui nod precizat de valoarea cheii
Operaia de tergere a nodului precizat printr-o cheie presupune:
- cutarea nodului i reinerea adresei precedentului su ()
- tergerea nodului: refacerea legturilor i eliberarea memoriei
Cazurile particulare ale operaiei se trateaz diferit:
a. lista este vid nainte tergerii
b. lista devine vid dup tergere
c. nodul de ters este chiar pLC
Convenim c n cazul particular c. (nodul ce se va terge este chiar nodul referit
de pointer-ul pLC i lista nu devine vid), pLC va referi nodul precedent celui
ters.
O funcie C care descrie operaia de tergere este urmtoarea:
void steregereNod(int valoare)
{
tnod *p,*prev;
//p - adresa nodului curent
//prev - adresa precedentului nodului curent
if (pLC==0) return; //lista este vida, cazul particular (a.)
p=pLC;
do
//cautarea nodului p
{
prev=p; //retine precedentul nodului curent
p=p->urm; //trece la urmatorul nod
if (p->cheie==valoare) //s-a gasit nodul
break; //iesire din instructuinea repetitiva, p este adresa

nodului gasit
}while(p!=pLC);
if (p->cheie!=valoare) return; //nu s-a gasit nodul

//nodul gasit este referit de p, urmeaza etapa de stergere


if (p->urm==p) //lista are un singur nod - nodul care se va sterge (b.)
{
pLC=0; //lista devine vida
free(p); //eliberare memorie
}
else

104

Algoritmi i structuri de date


{
if (p==pLC) //nodul de sters este referit de pLC, cazul (c.)
{
pLC=prev; //actualizare pLC
free(p); //eliberare memorie
}
else //cazul general
{
prev->urm=p->urm; //refacere legaturi
free(p); //eliberare memorie
}
}

}//sfarsit functie steregereNod


Observaie: n situaia n care informaia din noduri conine adrese alocate
dinamic (prin apelul funciei malloc), eliberarea memoriei alocate unui nod p
trebuie s in cont i de acest aspect, fapt pentru care, apelul funciei free(p) nu
este suficient. Din aceste considerente, o funcie special de eliberare a memoriei
alocate unui nod poate fi conceput. Spre exemplu:
typedef struct nod
{
int CNP; //cmp cu valori unice pentru nodurile listei
char *nume
struct nod *urm; //adresa urmtorului nod
}Persoana;
Alocarea memoriei pentru un nod de tipul Persoana (Persoana *p) necesit un
apel malloc pentru cmpul nume. Eliberarea memoriei alocate nodului p se va
executa corect prin funcia urmtoare:
void eliberare_nod(Persoana *p)
{
free(p->nume);
free(p);
}

tergerea liste circulare simplu nlnuite


Operaia de distrugere a unei liste circulare se realizeaz prin tergerea tuturor
nodurilor sale i nu prin distrugerea adresei speciale pLC prin care se gestioneaz
lista.
Dac nu este deja vid, lista se parcurge i noduri precedente nodului curent se
terg pn cnd lista devine vid. O funcie de tergere a listei circulare gestionate
prin pLC este urmtoarea:
void stergere()
{
tnod *p; //nodul curent
tnod *prev; //precedentul nodului curent
if (pLC==0) return; //lista este deja vida

105

Algoritmi i structuri de date


p=pLC;
do
{
prev=p;
p=p->urm;
eliberare_nod(prev);
}while(p!=pLC)
pLC=0; //marcare list vid
}

Observaie: primul nod eliberat este cel referit de pLC, fapt pentru care cnd
condiia p==pLC devine adevrat se indic revenirea n punctul de plecare a
pointer-ului p ceea ce semnific faptul c toate nodurile au fost terse (inclusiv
nodul referit de pointer-ul special pLC) i lista este vid.
LISTA DUBLU NLNUIT CIRCULAR
Lista circular dublu nlnuit este gestionat printr-un pointer la un nod
oarecare. Structura nodului este cea prezentat la listele dublu nlnuite i conine:
zona de informaii, adresa precedentului i adresa nodului urmtor.
Operaiile specifice: creare, inserare nod, tergere nod, tergere list,
parcurgere, cutare, sunt similare operaiilor descrise cu liste circulare simplu
nlnuite. Diferenele semnificative apar la procedurile de inserare naintea unui
nod precizat i tergerea unui nod oarecare, care se simplific prin existena unei
legturi spre nodurile precedente.
Transformarea unei liste dublu nlnuite n list circular se realizeaz prin
legarea capetelor prim i ultim, n ambele sensuri:
ultim->urm=prim;
prim->prec=ultim;
STIVE. COZI.
Stiva reprezint un caz special de lista liniara n care intrrile si ieirile se fac
la un singur capt al ei. Organizarea structurilor de date de tip stiv se poate face
n dou maniere:
- secvenial - elementele stivei sunt memorate la adrese
consecutive
- nlnuit elementele stivei nu ocup adrese consecutive,
fiecare element conine o legtur spre urmtorul element.
Prin organizarea secvenial nu se poate face economie de memorie, fapt pentru
care n general se practic organizarea nlnuit cu alocare dinamic a stivelor.
Structura de stiv se remarc prin operaiile specifice: push i pop,
corespunztoare adugrii unui element, respectiv, tergerii unui element n/din
vrful stivei. Principiul de funcionare al stivei este cel cunoscut sub denumirea de
LIFO (Last In First Out ultimul intrat, primul ieit).

106

Algoritmi i structuri de date


vrful

vrful

info

info

info

info

adr

info

adr

info

adr

info

baza

info

I Structura de date STIV cu


alocare static

baza

II. Structura de date STIV cu alocare


dinamic

Practic, stiva este o list simplu nlnuit pentru care operaiile specifice se
limiteaz la urmtoarele:
- creare stiv vid
- adugare element (push)
- tergere element (pop)
- terge lista (clear)
- accesare fr eliminare - a elementului din vrful stivei
n plus fa de operaiile enumerate anterior sunt posibile implementate operaii
de verificare:
- verific dac stiva este plin
- verific dac stiva este goal
Gestionarea stivei se face n mod similar listei nlnuite prin capetele prim i
ultim. La nivel abstract, o stiv are o baz a sa i un vrf, ceea ce convine unei
asocieri a nodurilor referite de prim i ultimi cu cele dou elemente specifice:
- baza stivei corespunde nodului prim i vrful stivei corespunde
nodului ultim
n aceast abordare, operaiile push i pop se traduc prin operaiile de:
- adugare a unui nou nod dup ultim (adugare n vrful stivei)
- tergere ultim (tergere din vrful stivei)
Privitor la eficiena operaiilor descrise ntr-un capitol anterior, ne reamintim c
operaia de adugare a unui nou element dup cel referit de pointer-ul ultim
necesita o parcurgere prealabil a listei. n schimb, adugarea unui nou nod
naintea celui referit de prim este mai puin costisitoare. Din aceste considerente, se
practic o inversare a rolurilor celor dou capete ale stivei, pentru a obine operaii
mai eficiente:
- baza stivei corespunde nodului ultim i vrful stivei
corespunde nodului prim
Astfel, operaiile push i pop se vor traduce prin:
- adugare a unui nou nod nainte de prim (adugare n vrful
stivei)
- tergere prim (tergere din vrful stivei)

107

Algoritmi i structuri de date

Coada este un alt caz special de list nlnuit bazat pe principiul FIFO (First
In First Out primul intrat, primul ieit). Acest principiu arat c primul element
introdus n list este i primul care va fi ters. O structur de acest gen are dou
capete, denumite sugestiv: cap i coad.
Operaiile primare cu cozi sunt:
- creare stiv vid
- adugare element n coad
- tergere element din cap
- terge lista (clear)
Spre deosebire de stiv, adugarea i tergerea unui element se execut n
capetele diferite ale cozii.
Ca i n cazul stivelor, organizarea unei cozi poate fi fcut n mod secvenial
(static) prin intermediul tablourilor unidimensionale sau dinamic prin liste
simplu nlnuite. Cea de-a doua variant este de preferat din raiuni economice.
prim

ultim
0

Coada este astfel o list nlnuit ale crei capete referite prin prim i ultim
semnific capul i coada structurii, ceea ce permite organizarea n dou maniere:
- prim refer capul listei i ultim refer coada listei
- ultim refer capul listei i prim refer coada listei
Conform celor dou abordri enumerate anterior, operaiile de adugare i
scoatere elemente n/din lista FIFO se traduc prin:
- adugare dup nodul ultim i tergere nod prim
- adugare nainte de prim i tergere nod ultim
Constatm c spre deosebire de stive, ambele abordri sunt eficiente, astfel nct
alegerea oricrei variante este posibil. Printr-o convenie, adugarea unui nod se
face dup ultimul nod (coada) al listei, iar scoaterea din list a unui nod este
implementat prin tergerea nodului prim (cap).

108

Algoritmi i structuri de date

XI.

ARBORI

Un arbore reprezint o mulime nevid i finit de elemente de acelai fel, pe


care le denumim noduri:
A = {a 1 ,a 2 ,..., a n } , n > 0
Proprietile arborelui sunt:
1. exist un singur nod numit rdcin
2. nodurile diferite de rdcin formeaz submulimi disjuncte
denumite subarbori.
3. Fiecare subarbore respect proprietile 1 i 2.
Arborii sunt structuri de date de natur dinamic i recursiv (ca i listele).
Reprezentarea grafic a unui arbore oarecare este ilustrat mai jos:
Radacina

Nod intern

Nod intern

Terminal

Nod intern

Nod intern

Terminal

Terminal
Terminal

Terminal

Nodurile arborelui pot fi:


- nodul rdcin (fiecare arbore are un singur nod de acest tip)
- noduri interne
- noduri terminale (frunze)
Nodul rdcin se distinge de celelalte noduri prin faptul c nu acesta nu are
succesori (noduri care l preced ).
Nodurile interne sunt precedate de 1 singur nod i pot fi urmate de 1 sau mai
multe noduri.
Nodurile terminale sunt precedate de 1 singur nod i sunt urmate de 0 noduri.
Terminologia specific ncorporeaz denumirile de printe pentru nodul care
preced alte noduri, fii, pentru nodurile care urmeaz un nod printe i frai pentru
nodurile care au acelai printe.
Nodurile unui arbore se caracterizeaz prin dou mrimi:
109

Algoritmi i structuri de date

Nivelul nodului: reprezint o valoarea natural prin care se


identific numrul de strmoi pn la nodul rdcin.
o Nodul rdcin este considerat pe nivelul 1
o Fiii nodului rdcin sunt pe nivelul 2
o Fiii fiilor nodului rdcin sunt pe nivelul 3
o etc.
Ordinul nodului: este un numr natural i reprezint numrul
de descendeni direci (fii) pe care i are nodul respectiv. Nodurile
terminale au ordinul 0.
Nivelul 1

Nivelul 2

Nivelul 3

Nivelul 4

Nivelele nodurilor n arbore


Nodul Ordinul nodului
1
O(1)=2
2
O(2)=3
3
O(3)=0
4
O(4)=2
5
O(5)=0
6
O(6)=1
7
O(7)=0
8
O(8)=0
9
O(9)=0
Ordinul fiecrui nod din arborele prezentat ca exemplu
Fiecare subarbore al arborelui este caracterizat prin nlimea sa, mrime care
reprezint numrul de nivele pe care le conine subarborele respectiv.
Un nod al arborelui conine:
a.
zona de date
b.
1 sau mai multe legturi spre fii si
Dac ntre fiii oricrui nod exist o relaie de ordine, spunem c arborele
respectiv este arbore ordonat.
110

Algoritmi i structuri de date

Dac numrul de fii ai fiecrui nod din compunerea unui arbore este 0,1 sau 2,
atunci arborele respectiv este numit arbore binar. Structura unui nod al arborelui
binare conine:
- zona de informaii
- legtur spre fiul stng
- legtur spre fiul drept
ntr-un arbore binar, exist posibilitatea ca una sau ambele legturi ale unui
printe spre fiii si s fie nule. Nodurile terminale au ambele legturile nule.
Anumite noduri interne pot s aib doar un fiu, astfel nct legtura spre cellalt fiu
este nul.
Importana studierii arborilor binari este dat de multitudinea de aplicaii
practice n care se face uz de aceast structur de date. n plus, un arbore poate fi
transformat i reprezentat prin arbore binar. Transformarea presupune etapele
urmtoare:
1. se stabilete legtur ntre fraii de acelai printe
2. se suprim legturile dinspre printe, cu excepia legturii cu
primului fiu
Exemplu:
Fie arborele oarecare din figura urmtoare:
1

7
Figura 1 Arbore oarecare

Dup transformare, arborele devine binar:

111

Algoritmi i structuri de date


1

Figura 2 Arbore binar

Parcurgerea arborilor
Parcurgerea (traversarea) arborilor presupune obinerea unei liste a nodurilor
arborelui. n funcie de ordinea n care sunt considerate nodurile arborelui, avem
mai multe tipuri de traversare a arborilor.
-

Parcurgerea n adncime (in depth): se viziteaz subarborii descendeni


ai nodului rdcin, n aceiai manier, apoi se viziteaz nodul rdcin.
Parcurgerea n adncime a arborelui considerat ca i exemplu n figura 1
produce urmtoarea list a nodurilor: 7 5 6 2 3 4 1.
Parcurgerea n lime (in breadth): se viziteaz nodurile pe nivele,
pornindu-se de la nivelele cele mai de sus (nivelul 1 rdcina) spre
nivelele mai mari, i pe fiecare nivel se viziteaz nodurile ntr-o anumit
ordine, spre ex. de la stnga la dreapta. Parcurgerea n lime a arborelui
din figura 1 produce lista: 1 2 3 4 5 6 7.
ARBORI BINARI

Structura unui nod al arborelui binar este urmtoarea:


typedef struct nod
{
//informaii
struct nod * st; //legtura spre subarborele stng
struct nod * dr; //legtura spre subarborele drept
}Tnod;
unde:
st este un pointer, a crei valoare este adresa fiului stng a nodului curent
dr este un pointer, a crei valoare este adresa fiului drept a nodului
curent

112

Algoritmi i structuri de date

Absena unui fiu (stng sau drept) se marcheaz prin valoarea Null (0) a
pointer-ului corespunztor.
Gestionarea unui arbore binare este posibil prin adresa nodului rdcin:
Tnod *rad;
Operaiile specifice arborilor binari sunt:
1. crearea arborelui
2. inserarea unui nod
3. tergerea unui nod
4. tergerea arborelui
5. accesarea unui nod (cutarea)
6. parcurgerea unui arbore
Operaia de creare a arborelui binar necesit iniializarea nodului rdcin:
rad=0 i adugarea (inserarea) ulterioar a noilor noduri dup o anumit regul.
Operaiile de inserare i accesare a nodurilor dintr-un arbore binar presupun
existena unui criteriu care se desprinde din specificaiile problemei concrete de
rezolvat. Aceste criterii fiind variate, rezolvarea problemelor cu arbori binare se
poate simplifica prin considerarea unei funcii verific care are doi parametri:
pnod1 i pnod2 adresele a dou noduri i returneaz valoare -1,0 sau 1 cu
semnificaia:
A) -1 dac pnod2 este adresa unui nod care poate fi accesat sau inserat n
stnga nodului adresat de pnod1
B) +1 dac pnod2 este adresa unui nod care poate fi accesat sau inserat n
dreapta nodului adresat de pnod1
C) 0 dac pnod2 refer un nod care NU poate fi accesat sau inserat n
subarborii stng sau drept ai nodului referit de pnod2.
Prototipul acestei funcii ajuttoare este:
int verifica(Tnod *pnod1 , Tnod * pnod2);
Inserarea unui nou nod presupune:
- determinarea locului n care va fi inserat noul nod
- alocarea de memorie i ncrcarea cu informaii a nodului nou
- stabilirea legturilor n arbore
n privina locaiei n care poate fi inserat nodul, aceasta este dat de criteriul
specificat de problema concret i se disting urmtoarele cazuri:
- n poziia rdcinii
- ca nod terminal
- ca nod intern
tergerea unui nod oarecare presupune:
- determinarea locaiei sale
- refacerea legturilor n arbore
- eliberarea memoriei alocate nodului ters
tergerea ntregului arbore necesit tergeri repetate ale nodurilor sale pn
cnd rdcina devine vid.
Parcurgerea unui arbore binare poate fi fcut n trei moduri:
113

Algoritmi i structuri de date

1. parcurgerea n preordine: pentru fiecare nod curent se va


accesa/prelucra informaia coninut, subarborele stng i n final
subarborele drept. (fiecare subarbore va fi parcurs n aceiai manier).
- Simplificat, parcurgerea n preordine a unui arbore
presupune parcurgerea n ordinea: R (rdcin), S
(subarbore stng), S (subarbore stng). R S D
2. parcurgerea n postordine: pentru fiecare nod curent se va parcurge
subarborele stng, subarborele drept i n final se va accesa/prelucra
informaia coninut n nodul curent (fiecare subarbore va fi parcurs n
aceiai manier).
- Simplificat parcurgerea n postordine nseamn
traversarea arborelui n ordinea: S (stng) D(drept)
R (rdcin)
3. parcurgerea n inordine: pentru fiecare nod curent se va parcurge
subarborele stng, nodul curent i n final subarborele drept (fiecare
subarbore va fi parcurs n aceiai manier).
- Simplificat: ordinea de traversare a arborelui este S
(stnga) R (rdcin) D (dreapta).
Exemplu:
J

La parcurgerea n preordine a arborelui din figura anterioar se vor accesa


nodurile n ordinea: J H E A B F I G C D
La parcurgerea n postordine se vor accesa nodurile n ordinea:
ABEFHCGDIJ
La parcurgerea n inordine se vor accesa nodurile n ordinea:
AEBHFJICGD
Funciile de parcurgere a unui arbore binar n cele trei variante (preordine,
postordine, inordine) sunt date mai jos:
void preordine(tnod *p)
{
if p!=0
{
//vizitarea nodului curent (tiprirea cheii)
printf(\t%d, p->cheie);
//parcurgere subarbore stng

114

Algoritmi i structuri de date


//autoapelare funcie preordine
preordine(p->st);
//parcurgere subarbore stng
//autoapelare funcie preordine
preordine(p->dr);
}
}
void inordine(tnod *p)
{
if (p!=0)
{
inordine(p->st);
printf("
%d",p->cheie);//vizitare nod
inordine(p->dr);
}
}
void postordine(tnod *p)
{
if (p!=0)
{
postordine(p->st);
postordine(p->dr);
printf("
%d",p->cheie);
}
}

Cutarea unui nod


Fiind dat un criteriu de cutare, cutarea ntr-un arbore binar presupune
parcurgerea nodurilor i verificarea criteriului pentru fiecare nod vizitat. n cazul
n care s-a gsit un nod care respect criteriul, procesul de parcurgere a arborelui se
va ncheia. Dac nu se gsete niciun nod care verific criteriul, acest lucru trebuie
semnalat.
n scopul definirii unei funcii de cutare n arborele binar, se consider funcia
auxiliar prin care se verific ndeplinirea sau nendeplinirea criteriului de cutare
i continuare cutrii ntr-unul din subarborii stng sau drept ai nodului curent. n
mod frecvent, criteriul se refer la ndeplinirea condiiei de egalitate ntre o valoare
dat i valoarea unui cmp din informaia memorat n noduri. Pentru a generaliza,
considerm funcia verificare descris anterior prin care se decide dac un nod
cutat pnod2 este gsit n arbore la adresa pnod1, sau se va continua cutarea n
subarborele stng sau drept a nodului adresat de pnod1.
O funcie de cutare se poate descrie ca mai jos. Funcia returneaz adresa
nodului gsit sau 0 n cazul n care nu se gsete un nod care verific criteriul
stabilit.
tnod *rad; //variabil global
tnod *cautare(tnod *pnod2)

115

Algoritmi i structuri de date


{ // caut nodul din arbore care este echivalent cu pnod2
tnod *p;
if rad==0 return 0; //arborele este vid
while (p!=0)
{
if (verificare(p,pnod2)==0)
return p; //s-a gsit nodul
else
if (verificare(p,pnod2)==-1)
p= p->st; //continuare cautare la stanga
else
p= p->dr; //continuare cautare la stanga
}
return 0; //nu s-a gasit nodul cautat
}

Crearea unui arbore binar


Operaia de creare a unui arbore binar presupune operaii repetate de inserare a
unui nou nod ca nod terminat. Pentru fiecare nod care urmeaz s fie inserat este
necesar n prealabil determinarea poziiei n care acesta va fi adugat, respectiv,
printele de care acesta se va lega. Nodul printe se va determina printr-o
parcurgere parial a arborelui. n realizarea acestei operaii ne putem folosi de
funcia auxiliar verifica sau o alt funcie prin care se decide n care din subarbore
va fi continuat cutarea posibilului printe.
ARBORI BINARI DE CUTARE
Un caz particular de arbori binari sunt arborii binari de cutare. n plus fa de
aspectele menionate la prezentarea arborilor binari, un arbore de cutare prezint o
caracteristic suplimentar: fiecare nod al su conine o cheie (cmp cu valori unice
pentru nodurile arborelui) i:
- toate nodurile din stnga nodului respectiv au valorile cheilor
mai mici dect cheia nodului curent
- toate nodurile din dreapta nodului respectiv au valorile cheilor
mai mari dect cheia nodului curent
Exemplu: Figura alturat ilustreaz reprezentarea grafic a unui arbore binar de
cutare (valoarea cheii este precizat n fiecare nod):

116

Algoritmi i structuri de date


7

Se poate observa o proprietate important a arborilor binari de cutare: la


parcurgerea n inordine se obine lista nodurilor ordonate dup valoarea cheii.
n plus, operaia de cutare a unui nod specificat de valoarea cheii este simplificat
(de aici i denumirea arborilor de cutare).
Cutarea unui nod dup o cheie dat nu necesit parcurgerea ntregului arbore.
Datorit specificului acestor arbori, valoarea cheii este comparat cu cheia
coninut n rdcin i n funcie de rezultatul comparaiei, cutarea se va continua
doar ntr-unul dintre cei doi subarbori, cellalt subarbore fiind exclus din cutare.
Procedeul se va continua n mod similar pentru subarborele curent: se compar
valoarea cheii cutate cu cheia rdcinii subarborelui i n funcie de rezultatul
comparaiei se va continua cutarea doar ntr-unul dintre subarborii subarborelui
curent. Cutarea se va ncheia n momentul n care un nod rdcin a subarborelui
curent conine cheia cutat sau dac nu mai sunt noduri de parcurs i cheia nu a
fost gsit.
Cutarea unei informaii ntr-o structur de arbore binar de cutare este
eficient, deoarece numrul nodurilor accesate se reduce prin excluderea acelor
subarbori a cror parcurgere ar fi inutil.
Operaiile de inserare i tergere executate asupra unui arbore binar de cutare
vor produce de asemenea un arbore binar de cutare. Astfel, n definirea unor
funcii specifice care opereaz asupra arborilor de cutare se va ine cont de
criteriul de ordine ntre cheile nodurilor ce formeaz arborele.
1. Creare arbore binar de cutare. Inserarea nodurilor.
Fiind dat o mulime de informaii ce trebuie organizate sub forma unui arbore
binar de cutare, crearea arborelui se realizeaz n urmtoarele etape:
-creare arbore vid
-creare arbore cu un singur nod (rdcin)
-inserare noduri ct timp mai exist informaie de organizat
La fiecare pas de inserare unui nod n arborele deja format se va ine cont de
criteriul de ordonare i arborele rezultat va fi tot un arbore binar de cutare.
Dac rdcina este vid nainte de inserare, nodul de inserat va deveni rdcina
arborelui:
rad=nou; rad->st=rad->dr=0;

117

Algoritmi i structuri de date

Dac exist cel puin un nod n arbore, se va cuta un printe al nodului de


inserat. Acest printe ndeplineasc condiiile:
- dac cheia coninut de printe este mai mare dect cheia nodului
nou, atunci, printele trebuie s aib legtura stng vid, pentru a reui
legarea nodului nou n stnga sa (prin aceasta ne asigurm c
subarborele construit, al crei rdcin este nodul printe, este ordonat)
- dac cheia coninut de printe este mai mic dect cheia nodului
nou, atunci, printele trebuie s aib legtura dreap vid
int inserare(tnod *nou)
{ tnod *p;
nou->st=nou->dr=0;
if (rad==0)
{//creare radacina
rad=nou;
rad->st=rad->dr=0;
return 1;
}
p=rad;
while(1)
{
if (nou->cheie<p->cheie)
if (p->st!=0)
p=p->st;
else
{p->st=nou;nou->st=nou->dr=0;return 1;}
else
if (p->dr!=0)
p=p->dr;
else
{p->dr=nou;nou->st=nou->dr=0;return 1;}
}
return 0; //nu s-a realizat operaia, cod de eroare
}

Arborele binar de cutare se poate construi printr-o secven:


printf("\ncate noduri introducei?"); scanf("%d",&n);
tnod *nou;
rad=0; //arbore vid
for (int i=0;i<n-1;i++)
{
nou=incarca_nod();
if (nou!=0) //s-a reuit ncrcarea
{
j=inserare(nou);
if (j==0) //nu s-a reuit inserarea
eliberare_nod(nou);//eliberare memorie alocat pt. nou
}
}

118

Algoritmi i structuri de date

2. Cutarea unui nod de cheie precizat


Cutarea dup valoarea cheii este operaia specific n arborele binar de cutare
prin care se determin adresa nodului a crei cheie este egal cu o valoare dat.
Dac nu se gsete un astfel de nod, operaia se ncheie cu insucces.
Construim mai jos o funcie care primete ca parametru valoarea cheii cutate
i returneaz:
- adresa nodului gsit, sau
- 0, n caz de insucces
tnod * cautare(int val)
{
tnod *p;
p=rad;
while(p!=0) //cttimp mai sunt noduri de vizitat
{
if (p->cheie==val)
return p; //s-a gasit nodul
if (val<p->cheie)
{ //continuare cutare n subarbore stng
p=p->st; }
if (p->cheie<val)
{//continuare cutare n subarbore drept
p=p->dr; }
}
return p; //nu s-a gsit nodul, p este Null
}

3. tergere nod de cheie precizat


tergerea unui nod de cheie precizat, precum i celelalte operaii specifice
arborilor de cutare, va produce un arbore cu aceleai caracteristici (cheile
nodurilor rmn ordonate dup tergere).
Prima etap a operaiei de tergere const n determinarea nodului de cheie
precizat (nodul ce se va terge). n acest scop este util o funcie care caut cheia
n arbore, ns fa de operaia de cutare descris anterior, este nevoie
determinarea unui element suplimentar: adresa printelui nodului ce va fi ters.
n funcie de poziia nodului de ters: p i a printelui su n arbore: parinte,
ntlnim urmtoarele cazuri:
a. nodul gsit este nod frunz (nod terminal) i este situat n dreapta
printelui su:
- se elibereaz nodul
- se stabilete legtura dreapt a printelui ca fiind nul:
parinte->dr=0
b. nodul gsit este nod frunz (nod terminal) i este situat n stnga printelui
su:
- se elibereaz nodul
- se stabilete legtura stng a printelui ca fiind nul:
parinte->st=0
119

Algoritmi i structuri de date

c. nodul cutat nu este nod terminal, dar are doar un subarbore stng
- leag printele de subarborele stng al nodului de ters:
o dac nodul este legat n stnga printelui, atunci
parinte->st=p->st cazul c.1
o dac nodul este legat n dreapta parintelui, atunci
parinte->dr = p->st cazul c.2
- elibereaz nodul
d. nodul cutat p nu este nod terminal, dar are doar un subarbore drept
- leag printele de subarborele drept al nodului p:
o dac nodul este legat n stnga printelui, atunci
parinte->st=p->dr cazul d.1
o dac nodul este legat n dreapta parintelui, atunci
parinte->dr = p->dr cazul d.2
- elibereaz nodul
e. nodul cutat p nu este terminal i are ambii subarbori (legturile stnga i
dreapta sunt nenule).
O alt manier de stabilire a cazurilor de tergere n arborele binar este dat de
ordinul nodului ce va fi ters:
Dac nodul are ordinul 0 sau 1 (are maxim un subarbore) este util s grupm
cazurile descrise mai sus a,b,c,d ntr-o singur tratare: printele nodului p va
conine adresa subarborelui lui p (stng sau drept), chiar dac acest subarbore este
vid (nodul p este terminal).
Dac ordinul nodului p este 2, se va trata cazul e.
Cazul e este cel mai complex caz de tergere a unui nod. Numim predecesor al
nodului p, cel mai din dreapta nod din subarborelui stng al lui p. Numim succesor
al nodului p, cel mai din stnga nod din subarborelui drept al lui p. Att
predecesorul ct i succesorul unui nod oarecare, sunt noduri terminale n arborele
dat.
Exemplu:

Nodul
de sters

Predecesor

Nodul
de sters

5
Succesor

120

Algoritmi i structuri de date

Nodurile predecesor i succesor au proprietate c sunt nodurile de cheie


imediat mai mic, respectiv, imediat mai mare dect cheia nodului p, i sunt noduri
cu un singur subarbore, fapt pentru care tergerea nodului p se realizeaz prin:
- copierea informaiei din predecesor/succesor n nodul p
- tergerea predecesorului/succesorului nodului p- cazul tergerii unui
nod cu un subarbore
Practic, tergerea unui nod cu doi subarbori se transform ntr-o cutare a altui
nod care are maxim un subarbore, urmat de tergerea acestuia.
Exemplu:
7

Nodul
de sters
2

Predecesor

Figura 3 nainte de tergerea nodului

Figura 4 Dup tergerea nodului

Observaii: Dac nodul p ce va fi ters este printele direct al predecesorului


su, atunci predecesorul este n stnga printelui su (n stnga nodului p).
Dac nodul p ce va fi ters nu este printele direct al predecesorului su, atunci,
predecesorul este legat de printele su direct prin legtura dreapt.
Exemplu: n figura anterioar, nodul 4 nu este printele direct al predecesorului
(nodul 3), astfel nct, predecesorul este legat n dreapta printelui su direct
nodul 2.
Ne imaginm urmtoarea situaie:
6

Nodul
de sters
2

Predecesor

n acest caz, pentru nodul de ters se va actualiza legtura stng. Dup operaia
de tergere, arborele devine:

121

Algoritmi i structuri de date


6

Funcia de mai jos este o variant de implementare a algoritmului de tergere a


unui nod dintr-un arbore de cutare. Se acord atenie nodului rdcin, acesta fiind
un nod important prin care se gestioneaz arborele. Dac acest nod trebuie ters,
tratarea cazului se face n mod diferit pentru protejarea adresei de acces la arbore.
void stergere()//stergere nod de cheie data
{
tnod *p,*tatap,*predecesor,*tatapredecesor;
int cheie;
printf("\ndati cheia");scanf("%d",&cheie);
p=rad;
//caut nodul p;
while(p!=0)
{
if (p->cheie==cheie)
break;
if (cheie<p->cheie) {tatap=p;p=p->st;}
if (cheie>p->cheie) {tatap=p;p=p->dr;}
}//caut p si retine parintele sau
if (p==0) {printf("\n Nu exista cheia cautata!!!");return;}
//cazul I________________________nod frunza
if ((p->st==0)&&(p->dr==0))
{
if (p==rad) rad=0; //arborele devine vid
if (tatap->st==p) //cazul a
{tatap->st=0;
eliberare_nod(p);
return;}
if (tatap->dr==p) //cazul b
{tatap->dr=0;
eliberare_nod(p);
return;}
}
//cazul II________________________nod cu un subarbore
//cazul c, p are doar subarbore stang
if (p->dr==0)
{

122

Algoritmi i structuri de date


if (p==rad) {rad=p->st;eliberare_nod(p);return;}
if (tatap->st==p) // cazul c.1
{
tatap->st=p->st;
eliberare_nod(p);
return;}
if (tatap->dr==p)// cazul c.2
{
tatap->dr=p->st;
eliberare_nod(p);
return;}
}//sfarsit caz c
//cazul d, p are doar subarbore drept
if (p->st==0)
{
if (p==rad) {rad=p->dr;eliberare_nod(p);return;}
if (tatap->st==p) // cazul d.1
{
tatap->st=p->dr;
eliberare_nod(p);
return;}
if (tatap->dr==p) // cazul d.1
{
tatap->dr=p->dr;
eliberare_nod(p);
return;}
}//sfarsit caz d
//cazul e________________________nod cu 2 subarbori
//pas 1 caut predecesor si retin parintele predecesorului
tatapredecesor=p;
predecesor=p->st;
while(predecesor->dr!=0)
{
tatapredecesor=predecesor;
predecesor=predecesor->dr;
}//retin parintele predecesorului
if (tatapredecesor==p)
{//nodul de sters este parintele predecesorului
tatapredecesor->st=predecesor->st;
p->cheie=predecesor->cheie;
eliberare_nod(predecesor);
return;
}
if (tatapredecesor!=p)
{
tatapredecesor->dr=predecesor->st;
p->cheie=predecesor->cheie;
eliberare_nod(predecesor);
return;
}

123

Algoritmi i structuri de date


}//sfrit functie stergere

4. tergere arbore binar de cutare


tergerea complet a arborelui binar const n tergerea tuturor nodurilor sale.
n acest scop se poate defini o funcie recursiv prin care se parcurge arborele n
post ordine i vizitarea nodului const n eliberarea sa. Ultimul nod eliberat este
nodul rdcin.
void stergere_arbore(tnod *p)
{
if (p!=0)
{
stergere_arbore(p->st);
stergere_arbore(p->dt);
eliberare_nod(p);
}
}

Apelul funciei este:


stergere_arbore(rad);

ARBORI BINARI ECHILIBRAI (AVL)


Un caz special de arbori binari ordonai (arbori de cutare) sunt arborii AVL
(descrii de Adelson, Velski, Landis). Acetia au n plus proprietatea de echilibru,
formulat prin:
Pentru orice nod al arborelui diferena dintre nlimea subarborelui stng
al nodului i nlimea subarborelui drept al nodului este maxim 1.
Fiecrui nod ntr-un arbore AVL i este asociat o mrime denumit factor de
echilibru. Factorul de echilibru se definete prin diferena dintre nlimile
subarborelui drept i nlimea subarborelui stng, i pentru arborii echilibrai poate
fi una dintre valorile -1, 0, +1. Dac factorul de echilibru pentru un nod are alt
valoare dect cele enumerate, arborele nu este arbore AVL.
Operaiile posibile asupra arborilor AVL sunt aceleai operaii ca n cazul
arborilor binari ordonai simpli: creare, inserare, tergere, parcurgere, cutare. n
schimb, n operaiile asupra arborilor AVL trebuie inut cont de proprietatea de
echilibru din moment ce o operaie de inserare a unui nod nou sau de tergere a
unui nod poate conduce la arbori binari dezechilibrai.
Practica este urmtoarea: operaiile definite pentru arborii de cutare se aplic i
asupra arborilor AVL, ns, ulterior, este verificat ndeplinirea proprietii de
echilibrare. n cazul n care arborele a devenit dezechilibrat, prin operaii
suplimentare se va reorganiza arborele pentru obine un arbore echilibrat.
Probleme propuse spre rezolvare

124

Algoritmi i structuri de date

1. Se citete de la tastatur o expresie matematic n form prefixata, sub forma


unui ir de caractere (operatorul este plasat n faa operanzilor). S se
construiasc arborele corespunztor acestei expresii. Fiecare nod conine un
operator sau un operand. S se evalueze expresia.
2. S se scrie o funcie care numr ntr-un arbore binar ordonat cte noduri
conin chei cu valori cuprinse n intervalul [a,b].
3. Scriei o funcie care calculeaz nivelul i factorul de echilibrare pentru oricare
nod al unui arbore binar.

125

Algoritmi i structuri de date

XII.

ELEMENTE DE GRAFURI. ALGORITMI.

Definiie: Graf este o pereche ordonat de mulimi G =(X, ), unde X este o


mulime de vrfuri, iar = XX este o mulime de muchii sau arce (pentru grafuri
orientate).
Exemplu:
1
2

6
4

3
7

O muchie de la vrful x la vrful y este notata cu perechea ordonata (x, y), dac
graful este orientat i n mod uzual este folosit termenul de arc, si cu mulimea
{x, y}, dac graful este neorientat. n reprezentarea grafic, arcele (x,y) sunt
marcate prin sgei de la extremitatea iniial x la cea final y, iar muchiile prin
segmente.
ntr-un graf orientat, existena unui arc de la vrful x la vrful y nu presupune i
existena arcului de la y la x. n grafurile neorientate, dac exist muchie ntre x i
y, atunci aceasta este i muchie ntre vrfurile y i x. Vrfurilor unui graf li se pot
ataa informaii numite uneori valori, iar muchiilor li se pot ataa informaii numite
costuri.
Urmtoarele noiuni sunt specifice grafurilor:
Dou vrfuri unite printr-o muchie se numesc adiacente.
Un drum este o succesiune de muchii de forma:
(x1, x2), (x2, x3), ..., (xn-1, xn) n graf neorientat
sau de forma
{x1, x2}, {x2, x3}, ..., {xn-1, xn} n graf neorientat
Un lan se definete ca o succesiune de vrfuri x 1, x2, x3, xn n care oricare
dou vrfuri sunt adiacente.
ntr-un drum simplu muchiile care l compun sunt distincte.
ntr-un drum elementar vrfurile care l compun sunt distincte.
Lungimea drumului este egala cu numrul muchiilor care l constituie.
Un lan elementar al grafului G care conine toate vrfurile grafului se numete
lan hamiltonian. Determinarea unui lan hamiltonian al grafului este o problem
foarte popular cunoscut ca Problema Comis Voiajorului rezolvat prin metoda
Greedy.
Un ciclu este un drum care este simplu i care are drept capete un acelai vrf.
Un graf fr cicluri se numete graf aciclic.
Un subgraf al lui G este un graf G=(X', '), unde X' X, iar ' este formata din
muchiile din care unesc vrfuri din X'.
126

Algoritmi i structuri de date

Un graf parial este un graf (X, "), unde " .


Un graf neorientat este conex, dac ntre oricare doua vrfuri exista un drum.
Pentru grafuri orientate, aceasta noiune este ntrit: un graf orientat este tare
conex, dac ntre oricare dou vrfuri x si y exista un drum de la x la y si un drum
de la y la x.
Reprezentarea grafurilor
Prin matricea de adiacenta A, n care A[i, j] = 1 dac vrfurile i si j sunt
adiacente, iar A[i, j] = 0 n caz contrar.
2. Prin liste de adiacenta: fiecare vrf i are ataat lista de vrfuri adiacente
lui (pentru grafuri orientate, este necesar ca muchia sa plece din i).
3. Prin lista de muchii. Aceasta reprezentare este eficienta atunci cnd se
dorete examinarea tuturor muchiilor grafului.
4. Prin matricea costurilor (grafuri neorientate etichetate) C n care C[i, j] 0
este costul asociat muchiei, iar C[i, j] = 0 semnific faptul c nu exist
muchie {i,j}.
1.

Parcurgerea grafurilor
Parcurgerea unui graf presupune vizitarea intr-o anumita ordine nodurilor
grafului, o singura dat fiecare. n functie de ordinea de parcurgere a vrfurilor
exista 2 metode de parcurgere:
1.
2.

Metoda parcurgerii n lime - Breadth First


Metoda parcurgerii n adncime - Depth First

1. Parcurgerea n lime Breadth First: se viziteaz vrful stabilit iniial xs,


apoi vecinii acestuia (vrfurile adiacente) apoi vecinii vecinilor lui xs, etc.
Procedura de parcurgere n lime funcioneaz dup urmtorul principiu: atunci
cnd s-a ajuns ntr-un vrf oarecare x nevizitat, l marcm si vizitm apoi toate
vrfurile adiacente lui x rmase nevizitate, apoi toate vrfurile nevizitate adiacente
vrfurilor adiacente lui x, etc. La parcurgerea n lime se folosete o structur de
coad pentru a putea vizita toi vecinii unui nod dat nainte de a-l marca drept
vizitat.
Subalgoritm BreadthFirst (x) este //x reprezint vrful curent
C= // iniializare coada vid
*Marcheaz x ca vizitat
Inserare(C,x) //inserare vrfului x n coada C
Cttimp (C)
Scoate(C,y) //scoate din coad vrful y
Pentru fiecare vrf z, adiacent lui y
Dac z este nevizitat atunci
*Marcheaz z ca vizitat
127

Algoritmi i structuri de date

Inserare(C,z)
SfDac
SfPentru
SfCttimp
SfSubalgoritm
Observaie: marcarea unui vrf ca fiind vizitat sau nevizitat se poate realiza prin
folosirea unui tablou tab[1n] ale crui elemente sunt asociate vrfurilor
grafului i au valori binare cu semnificaia: tab[i]=1 vrful i este vizitat, respectiv,
tab[i]=0 vrful i este nevizitat
2. Parcurgerea n adncime presupune vizitarea vrfului iniial x S i marcarea sa
ca fiind vizitat, apoi se alege un vrf x, adiacent lui x S i se aplic aceiai procedur
recursiv, avnd ca punct de plecare vrful x. Procedura de parcurgere n
adncime a vrfurilor unui graf se preteaz la o implementare recursiv. La
terminarea procedurii curente (la revenirea din apelul recursiv), dac exista un alt
vrf adiacent vrfului curent x, care nu a fost vizitat, apelam din nou procedura etc.
Dac toate vrfurile adiacente lui x au fost marcate ca vizitate se termin vizitarea
vrfului x.
Subalgoritm DepthFirst (x) este:
*Marcheaz x ca vizitat
Pentru fiecare vrf y adiacent lui x
Dac y este nevizitat atunci
Cheam DepthFirst (y)
SfDac
SfPentru
SfSubalgoritm
Observaie: Varianta nerecursiv a subalgoritmului DepthFirst se realizeaz prin
utilizarea unei structuri de date de tip stiv.
Subalgoritmii BreadthFirst i DepthFirst sunt apelai din algoritmul Parcurgere:
Algoritm Parcurgere(G) este
Pentru fiecare x din X
*Marcheaz x ca nevizitat
SfPentru
Pentru fiecare x din X
Dac x este nevizitat atunci
Cheam BreadthFirst (x) sau Cheam
DepthFirst (x)
SfDac
SfPentru
SfAlgoritm
128

Algoritmi i structuri de date

n cazul unui graf neconex, se pune problema determinrii componentelor sale


conexe:
O componenta conexa a grafului G=(X, M), este un subgraf G=(X', M'), conex
i maximal. Maximalitatea se refer la faptul c nu exista lan n graful G care sa
aib o extremitate n X i pe cealalt n X\X.
Un arbore este un graf neorientat, aciclic i conex. ntr-un arbore exista exact
un drum ntre oricare doua vrfuri.
Un graf parial care este arbore se numeste arbore partial. Un arbore parial
este un graf parial fr cicluri.
Arborele parial de cost minim (suma costurilor muchiilor este minim) se
determin printr-un algoritm de tip Greedy: Algoritmul lui Kruskal.
Algoritmul de determinare a Arborele parial de cost minim (APM).
Problema APM: Se d un graf G=(X,) cu muchiile etichetate prin costuri
(datele de intarre sunt reprezentate prin matricea costurilor).Se cere determinarea
arborelui parial de cost minim a grafului dat. (datele de ieire sunt muchiile care
formeaz arborele parial de cost minim)
Rezolvare: Algoritmul lui Kruskal este un algoritm cunoscut de rezolvare a
problemei enunate i este un algoritm de tip Greedy. Principiul acestui algoritm
este urmtorul:
Considernd graful G=(X,), A=(X, ) arborele ce se determin (reprezentat
prin lista de muchii) i n numrul de vrfuri n=|X|:
- se pornete cu arborele vid: =
- n mod repetat se alege muchia de cost minim a grafului G care nu
formeaz ciclu n arborele A i se adaug la
- algoritmul se termin cnd au fost alese n-1 muchii
Algoritm Kruskal este:
Date de intrare: G=(X,)
Fie =
Cttimp (||<n-1)
*Alege {i,j} de cost minim din
Dac {i,j} nu conine cicluri
= {i,j}
= \ {i,j}
SfDac
SfCttimp
Date de ieire: A=(X,)
SfAlgoritm

129

Algoritmi i structuri de date

Observaie: Mulimea muchiilor grafului dat se poate ordona descresctor dup


costuri i se va parcurge n mod secvenial, fr a mai fi necesar procedura de
alegere a muchiei de cost minim din graful G.

130

Algoritmi i structuri de date

XIII. METODE DE ELABORARE A ALGORITMILOR. DIVIDE ET


IMPERA.
Metode Divide et Impera este o metod de rezolvare a unor probleme i este
inspirat de principiul mparte i stpnete. n conformitate cu principiul
amintit, o problem de complexitate mare se va mpri n subprobleme de acelai
tip ns de dimensiuni mai mici ale cror rezolvare se va dovedi mai simpl. De
asemenea, subproblemele obinute prin descompunerea problemei pot fi la rndul
lor mprite n subprobleme mai mici. Etapa de mprirea se consider ncheiat
cnd subproblemele
devin elementare - rezolvabile imediat. Soluiile
subproblemelor se vor combina pentru a alctui soluia global a problemei.
Metoda descris poate fi aplicat n condiiile n care problemele admit o
mprire n subprobleme de acelai fel. Putem vorbi de un specific al problemelor
rezolvabile cu Divide et Impera. Acest specific este dat de urmtoarele afirmaii:
-

problema global se poate mpri n 2 sau mai multe subprobleme


asemntoare de dimensiuni mai mici
subproblemele obinute prin mprire sunt independente, ceea ce permite
ca soluiile pariale ale acestora s nu depind unele de altele
contextul problemei ne permite s identificm condiia ca o subproblem
s fie considerat elementar i s nu mai fie supus unei mpriri
ulterioare
subproblemele obinute prin mprire admit aceiai metod de rezolvare
fiind probleme de acelai fel.

Exemple de probleme celebre rezolvabile cu metoda Divide et Impera sunt:


problema Turnurilor din Hanoi, Sortarea rapid, etc.
Fie P problema global, n dimensiunea ei i S soluia dorit. Fie n0
dimensiunea minim a unei probleme pentru care aceasta devine elementar i
admite o rezolvare imediat.
Deoarece toate subproblemele sunt tratate prin aplicarea aceluiai algoritm,
diferena fcndu-se doar prin datele de intrare-ieire i dimensiunea
subproblemelor, precum i datorit faptului c soluiile obinute sunt soluii pariale
ale problemei globale, descrierea potrivit a metodei este dat printr-un
subalgoritm autoapelabil. De altfel, implementarea n limbaj de programare se face
prin proceduri (funcii) recursive.
Subalgoritm DivideEtImpera (P_formal, n_formal; S_formal) este:
Dac n_formal< n0 atunci
#rezolv imediat subproblema P_formal i obine soluia S_formal
Altfel
#mparte problema P_formal de dimensiune n_formal n:
131

Algoritmi i structuri de date

# P1 de dimensiunea n1,
# P2 de dimensiune n2,
...
# Pk de dimensiune nk
Cheam DivideEtImpera(P1,n1,S1)
Cheam DivideEtImpera(P2,n2,S2)
...
Cheam DivideEtImpera(Pk,nk,Sk)
#combin soluiile pariale S1,S2,...,Sk i obine S_formal
SfDac
SfSubalgoritm
Propoziiile nestandard din descrierea subalgoritmului nu pot fi detaliate dect n
funcie de problema rezolvat.
Pentru a rezolva problema global P, apelul subalgoritmului se va face pentru
parametrii actuali P, n i S:
Cheam DivideEtImpera(P,n;S)
Problema 1: Se cere determinarea maximului dintr-un vector de n valori numerice.
Analiza problemei:
Determinarea celui mai mare element dintr-un vector poate fi privit ca o
problem de determinare a maximului dintre dou valori intermediare,
reprezentnd maximele celor dou subiruri obinute prin mprirea irului iniial
n dou pri egale:
x1 ,x2 ,x3 , , xk+1 , xk , xk+1 ,,xn-2 ,xn-1 ,xn
Maxim
x1 ,x2 ,x3 , , xk+1 , xk

xk+1 ,,xn-2 ,xn-1 ,xn

Maxim1

Maxim2

Maxim= maxim(Maxim1,Maxim2)
Fiecare subir din mprirea anterioar se va putea mpri din nou n dou pri
de dimensiuni apropiate. Acest proces de mprire se va ncheia cnd un subir de
elemente nu mai poate fi mprit, respectiv cnd dimensiunea acestuia s-a redus la
1. n acest moment, subproblema devine elementar, deoarece maximul
elementelor unui vector format dintr-un singur element este nsi elementul
respectiv.
132

Algoritmi i structuri de date

Am identificat n problema dat iniial o problem rezolvabil prin Divide Et


Impera. Dimensiunea unei subprobleme este dat de numrul elementelor din
subirul manevrat i dimensiunea unei subprobleme elementare este 1. Combinarea
soluiilor pariale se face printr-o comparaie simpl.
Subalgoritmul DivideEtImpera devine n acest caz:
Subalgoritm DivideEtImpera _Maxim(xinf , , xsup ;Maxim_formal) este:
Dac sup-inf <= 0 atunci
Maxim_formal= xinf
Altfel
mij= (sup+inf)/2
Cheam DivideEtImpera_Maxim(xinf , , xmij;Maxim1)
Cheam DivideEtImpera_Maxim(xmij+1 , , xsup,Maxim2)
Dac Maxim1>Maxim2 atunci
Maxim_formal=Maxim1
Altfel
Maxim_formal=Maxim2
SfDac
SfDac
SfSubalgoritm
Pentru rezolvarea problemei complete apelul subalgoritmului devine:
Cheam DivideEtImpera(x1 , ,xn ;Maxim)
Program:
#include <stdio.h>
#define NMAX 20
void citire (int t[NMAX],int *n)
{int i;
printf("dati dimensiunea tabloului;");
scanf("%d",n);
for(i=0;i<*n;i++)
{
printf("\nT[%d]=",i);
scanf("%d",&t[i]);
}
}
int maxim(int t[NMAX], int inf,int sup)
{
int mij, max1,max2;
if ((sup-inf)<=0)
return t[inf];
mij=(sup+inf)/2;
max1=maxim(t,inf,mij);
max2=maxim(t,mij+1,sup);
if (max1>max2)
return max1;

133

Algoritmi i structuri de date


else

return max2;

}
void main()
{
int a[NMAX],dim;
citire(a,&dim);
int max;
max=maxim(a,0,dim-1);
printf("Maximul este: %d",max);
}

Problema 2: Problema turnurilor din Hanoi. Se dau 3 tije simbolizate prin A,B,C.
Pe tija A se gsesc n discuri de diametre diferite, aezate de jos n sus n ordine
cresctoare a diametrelor. Se cere sa se mute toate discurile de pe tija A pe tija B,
utiliznd ca tija intermediara tija C, respectnd urmtoarele reguli:
- la fiecare pas se muta un singur disc ;
- nu este permis sa se aeze un disc cu diametrul mai mare peste un disc cu
diametrul mai mic.

Analiza problemei:
Cazul elementar al problemei const n existena unui singur disc pe tija A,
ceea ce reduce rezolvarea la o mutare a discului de pe tija A pe tija B.
Pentru n=2 (dou discuri), rezolvarea problemei const n 3 mutri,
folosind tija intermediar C:
- mut discul mai mic de pe tija A pe tija C
- mut discul mai mare de pe tija A pe tija B
- mut discul mic de pe tija C pe tija B
Generaliznd, pentru un numr oarecare n de discuri situate pe tija A,
rezolvarea problemei se reduce la parcurgerea urmtoarelor etape:
- mut primele n-1 discuri de pe A pe C, utiliznd tija B ca intermediar
- mut discul rmas de pe tija A pe tija B
134

Algoritmi i structuri de date

mut cele n-1 discuri de pe C pe B, utiliznd tija A ca intermediar

Observm c problema s-a redus la dou probleme de dimensiuni mai mici,


respectiv, de a efectua mutarea a n-1 discuri. Aceiai manier de abordare a
problemei de dimensiune n-1 va reduce problema la mutarea a n-2 discuri, .a.m.d.
n cele din urm, pentru cazul elementar al unui singur disc ce trebuie mutat, se va
aplica rezolvarea direct.
Subalgoritmul Hanoi va trata problema transferului a k discuri de pe tija X, pe
tija Y, folosind tija rmas Z ca intermediar:
Subalgoritm Hanoi (k,X,Y,Z) este:
Dac k=1 atunci
Mut discul de pe tija X pe tija Y //rezolvare direct, problem
elementar
Altfel
Cheam Hanoi(k-1,X,Z,Y)
Mut discul rmas de pe X pe Y //problem elementar
Cheam Hanoi(k-1,Z,Y,X)
SfDac
SfSubalgoritm
Apelul subalgoritmului pentru rezolvarea problemei globale este:
Cheam Hanoi(n,A,B,C)
Program:
#include <stdio.h>
void Hanoi (int n, int A, int B, int C)
{if (n>0)
{
Hanoi (n - 1, A, C, B);
printf("\nMuta discul de pe tija %c pe tija %c",A,C);
Hanoi( n-1, B, A, C);
}
}//sfarsit Hanoi
void main ()
{
int n;
printf("\nDati numarul de discuri:"); scanf("%d",&n);
Hanoi (n, A, B, C); //apel hanoi
}

Problema 3: Cutare Binar. Fiind dat un vector ordonat de n valori numerice, s


se determine poziia pe care se regsete o valoare dat cheie.
Analiza problemei:
Cutarea unei valori cheie ntr-un vector ordonat de n valori se poate reduce la o
problem mai simpl (de dimensiune mai mic) dac ne folosim de informaia c
vectorul este ordonat. Acest lucru ne permite s mprim vectorul n dou pri
135

Algoritmi i structuri de date

relativ egale, s comparm cheia cu valoarea elementului de pe poziia tieturii i


s decidem continuarea cutrii ntr-unul dintre subvectorii rezultai, ignornd acea
parte care nu poate conine cheia cutat. Subvectorul n care se continu cutarea
poate fi la rndul su mprit n dou pri pentru a obine probleme mai mici.
mprirea se termin n condiiile n care cheia a fost gsit sau subvectorul pe
care l manevrm nu poate fi mprit (este format din maxim un element).
Decizia continurii cutrii ntr-unui dintre subvectorii rezultai printr-o
mprire precedent se face pe baza unei comparaii simple a elementului de pe
poziia tieturii, notat xmij, cu cheia cutat. Rezultatul comparaiei poate fi
urmtorul:
- cheie egal cu valoarea elementului xmij rezult c am gsit poziia mij a
cheii
- cheie mai mic dect xmij - rezult c vom continua cutarea n prima parte
a vectorului, ntre elementele situate la stnga poziiei mij
- cheie mai mare dect xmij rezult c vom continua cutarea ntre
elementele din dreapta poziiei mij
Subalgoritmul de rezolvare a problemei de cutare binar se descrie astfel:
Subalgoritm CutareBinar(xinf , , xsup ,cheie;poz) este:
Dac sup-inf <= 0 atunci
// vectorul curent conine un singur element
Dac xinf= cheie atunci
poz=inf
Altfel
poz= -1 // semnificaia faptului c nu s-a gsit cheia
SfDac
Altfel
// vectorul curent conine mai mult de un element
mij= (sup+inf)/2
Dac cheie=xmij atunci
poz=mij //am gsit poziia cheii
Altfel
Dac cheie<xmij atunci
Cheam CutareBinar(xinf , , xmij ,cheie;poz)
Altfel
Cheam CutareBinar(xmij+1 , , xsup ,cheie;poz)
SfDac
SfDac
SfSubalgoritm
Apelul Cheam CutareBinar(x1 , , xn ,cheie;poz) rezolv problema
global. Dac valoarea poz este egal cu -1 la terminarea apelului, cheia nu a fost
gsit n vectorul dat, n caz contrar, valoarea poz reprezint poziia pe care a fost
gsit cheia cutat.

136

Algoritmi i structuri de date

Problema 4. Sortare rapid (QuickSort). Algoritmul de sortare rapid este


un exemplu tipic de algoritm Divide et Impera. Fiind dat un vector oarecare de n
elemente se cere ordonarea cresctoare a vectorului dup valorile elementelor.
Ideea de baz a sortrii rapide const n mprirea vectorului n doi
subvectori cu proprietatea c toate elementele primului sunt mai mici dect un
element pivot i elementele celui de-al doilea subvector sunt mai mari dect
pivotul. Alegerea pivotului rmne n sarcina programatorului, existnd trei
variante plauzibile: pivotul este elementul median al subvectorului manevrat,
pivotul este elementul de pe prima poziie, respectiv, pivotul este ales ca elementul
de pe ultima poziie din subvector. n descrierea urmtoare, pivotul este ales ca
fiind elementul de pe poziia median.
mprirea vectorului n dou pri care verific proprietatea enunat
necesit operaii de interschimbare a valorilor elementelor de pe poziii diferite.
Odat ce etapa de mprire a fost parcurs, cei doi subvectori rezultai vor fi supui
aceluiai procedeu de mprire. Problema devine elementar implicit dac
subvectorul corespunztor are dimensiunea 1, fiind considerat ordonat. Combinarea
soluiilor nu necesit o etap distinct, fcndu-se n mod implicit, prin
interschimbrile elementelor vectorului dat n apelurile recursive ale
subalgoritmului.
mprirea unui ir de elemente xinf, ,xsup se face parcurgnd paii:
1. iniializeaz doi indici de parcurgere a subirului n dou sensuri: i
indicele de parcurgere a irului de la stnga la dreapta, respectiv, j
indicele de parcurgere de la dreapta la stnga; iniial, i=inf, j=sup.
2. stabilete pivotul (elementul de pe poziie median din ir)
3. cttimp pivotul este mai mare dect elementele de pe poziiile parcurse de
la stnga la dreapta, efectueaz avans la dreapta al indicelui i
4. ct timp pivotul este mai mare dect elementele de pe poziiile parcurse de
la stnga la dreapta, efectueaz avans la dreapta al indicelui i
5. dac i<j interschimb valorile de pe poziiile i i j i actualizeaz indicii
prin avans la dreapta, respectiv la stnga
6. repet paii 3, 4 i 5 pn cnd valoarea indicelui i devine mai mare dect
valoarea lui j
Dup parcurgerea pailor 1-6 se consider subirul x inf, ,xsup mprit n dou
subiruri: xi, ,xsup i xinf, ,xj. Dac subirurile obinute conin mai mult de 1
element, acestea se vor supune aceluiai procedeu descris.
Subalgoritmul corespunztor metodei descrise anterior este urmtorul:
Subalgoritm QuickSort(xinf, ,xsup)este:
Fie i=inf i j=sup
mij=(inf+sup)/2 //mij reprezint poziia pivotului xmij
Repet
Cttimp ( i<sup i xi<xmij)
//avans la dreapta
i=i+1
SfCttimp
137

Algoritmi i structuri de date

Cttimp ( j>inf i xj>xmij)


//avans la stnga
j=j-1
SfCttimp
Dac i<j atunci
Cheam Interschimbare(xi,xj)
SfDac
Pncnd (i>j)
Dac (i<sup) atunci
Cheam QuickSort(xi, ,xsup)
SfDac
Dac (j>inf)
Cheam QuickSort(xinf, ,xj)
SfDac
SfSubalgoritm
Pentru rezolvare problemei globale se va efectua apelul:
Cheam QuickSort (x1, ,xn)
Program C:
#include <stdio.h>
void citireSir(int x[10],int *n)
{
int i;
printf("\ndati n=");
scanf("%d",n);
for(i=0;i<*n;i++)
{printf("\nX[%d]=",i+1);
scanf("%d",&x[i]);
}
}
void tiparireSir(int x[10],int n)
{
int i;
for(i=0;i<n;i++)
printf("%d ",x[i]);
}
void SQuick(int x[10],int st, int dr)
{
int i,j,k,M;
i=st; j=dr;M=x[(st+dr)/2];
do{

//avans la stanga
while ((i<dr) && (x[i]<M)) i++;
//avans spre dreapta
while ((j>st) && (x[j]>M)) j--;
if (i<j)
{//interschimb x[i] cu x[j]

138

Algoritmi i structuri de date


k=x[i];
x[i]=x[j];
x[j]=k;
}
if (i<=j) {i++; j--;}
}while(i<=j);
if (i<dr) SQuick(x,i,dr); //daca subsirul 1 mai are cel putin
1 elem ..
if (j>st) SQuick(x,st,j); //daca subsirul 2 mai are cel putin
1 elem ..
}
void main()
{
int a[10],n;
citireSir(a,&n);
SQuick(a,0,n-1);
tiparireSir(a,n);
}

Problema 4. Sortarea prin interclasare este o tehnic de ordonare a unui


vector. Principiul de baz este acela al mpririi vectorului iniial n dou pri
egale (subvectori), prin sortarea prilor rezultate i ulterior interclasarea celor doi
subvectori ordonai, rezultnd vectorul global ordonat. Fiecare dintre cei doi
subvectori obinui la o mprire precedent, la rndul lor pot fi mprii fiecare n
dou pri, urmnd ca subvectorii de dimensiune mai mic s fie supui aceluiai
mecanism de ordonare prin interclasarea i s obinem subvectori ordonai, de
dimensiuni mai mari.
Operaia de interclasare a doi vectori a fost discutat ntr-un capitol precedent.
Aceast operaie presupune parcurgerea secvenial a celor doi vectori ordonai i
construirea celui de-al treilea vector prin copierea elementelor din cei doi vectori
cu restricia de a pstra relaia de ordine ntre elementele vectorului rezultat. n
algoritmul de sortare prin interclasare, vectorii ce vor fi interclasai sunt de fapt
subvectori ai aceluiai vector, ceea ce conduce la o procedur de interclasare uor
diferit fa de cea prezentat n capitolul .
Observm c problema general permite o mprire n subprobleme
independente mai mici rezolvabile prin aceiai tehnic, ceea ce ne permite o
abordare prin metoda Divide et Impera. Subproblemele se consider elementare
dac dimensiunea subvectorilor devine 1, ceea ce este echivalent afirmaiei
subvector ordonat.
Subalgoritmul prin care se realizeaz sortarea prin interclasare este descris n
continuare:

139

Algoritmi i structuri de date

Subalgoritm MergeSort(xinf , , xsup) este:


Dac sup-inf<1 atunci
// nu se efectueaz nimic, se revine din apelul subalgoritmului (condiia
// de terminare a recursivitii), considernd c subvectorul curent de
// dimensiune 1 este ordonat
Altfel
Fie mij=(inf+sup)/2
Cheam MergeSort(xinf , , xmij)
Cheam MergeSort(xmij+1 , , xsup)
//interclasarea subvectorilor xinf , , xmij i xmij+1 , , xsup
i=inf
// i indicele de parcurgere a subvectorului x inf , , xmij
j=mij+1 // j indicele de parcurgere a subvectorului x mij+1 , , xsup
k=1
// k parcurgerea vectorului rezultat y 1 ,, ydim
Cttimp (i<=mij i j<=sup)
Dac (xi<xj) atunci
yk=xi // se copiaz din primul subvector
i=i+1
k=k+1
Altfel
yk=xj // se copiaz din al doilea subvector
Interclasare
j=j+1
k=k+1
SfDac
SfCttimp
Cttimp (i<=mij) //au mai rmas elemente n primul subcvector
yk=xi // se copiaz din primul subvector
i=i+1
k=k+1
SfCttimp
Cttimp (j<=sup) //au mai rmas elemente n primul subcvector
yk=xj // se copiaz din al doilea subvector
j=j+1
k=k+1
SfCttimp
// copierea vectorului y n vectorul xinf , , xsup
Pentru i de la 1 la k-1
xi+inf-1=yi
SfPentru
SfDac
SfSubalgoritm
Apelul Cheam MergeSort(x1 , , xn) va produce ordonarea vectorului global

140

Algoritmi i structuri de date

Program C: Ordonarea unui vector prin metoda sortrii prin interclasare.


#include <stdio.h>
void citireSir(int x[10],int *n)
{int i;
printf("\ndati n=");scanf("%d",n);
for(i=1;i<=*n;i++)
{printf("\nX[%d]=",i+1);scanf("%d",&x[i]);}
}
void tiparireSir(int x[10],int n)
{int i;
for(i=1;i<=n;i++)
printf("%d ",x[i]);
}
void MergeSort(int x[], int inf, int sup)
{int mij,k,i,j;
int y[20];
if(inf<sup)
{
mij=(inf+sup)/2;
mergesort(x,inf,mij);
mergesort(x,mij+1,sup);
//merge(x,inf,sup,mij);
i=inf;
j=mij+1;
k=inf;
while((i<=mij)&&(j<=sup))
{
if(x[i]<x[j])
{
y[k]=x[i];
k++;
i++;}
else
{
y[k]=x[j];
k++;
j++;}
}
while(i<=mij)
{
y[k]=x[i]; k++; i++; }
while(j<=sup)
{
y[k]=x[j]; k++; j++; }
for(i=inf;i<k;i++)
x[i]=y[i];
}}
void main(void)
{int a[10],n;
citireSir( a,&n);
MergeSort(a,1,n);
tiparireSir(a,n);
}

141

Algoritmi i structuri de date

XIV. METODE DE ELABORARE A ALGORITMILOR.GREEDY


Metoda Greedy (greedy = (en.)lacom) este o tehnic de rezolvare problemelor
de optimizare bazat pe principiul alegerii lacome a optimului local n vederea
determinrii optimului global. Soluia unei probleme abordat cu metoda Greedy se
construiete treptat, prin alegeri corecte, optimale i irevocabile ale soluiilor
pariale. Dezavantajul major al metodei const n incapacitatea determinrii soluiei
optime globale pentru orice problem.
Pentru a aplica metoda Greedy, problema abordat trebuie reformulat ntr-o
maniera ablon prin care se pot identifica datele de intrare sub forma unei mulimi
A, datele de ieire sub forma unei submulimi B a mulimii de intrare A i anumite
condiii specifice problemei care ne permit alegerea unei soluii pariale optimale:
Fie A o mulime de n elemente: A={a1,a2,,an}.
Se cere determinarea submulimii B, B A astfel nct B este maximal i
verific anumite condiii.
Principiul de rezolvare:
- Se pornete cu mulimea vid B={}, care ulterior va fi completat cu
elemente ale mulimii A
- Se parcurge mulimea A, element cu element, i se verific pentru fiecare
membru al mulimii A condiiile identificate din contextul problemei.
Dac aceste condiii sunt verificate, elementul respectiv va fi nghiit de
mulimea B
- Procedeul se ncheie n dou situaii:
o S-a determinat mulimea maximal B
o Nu mai sunt elemente de parcurs n mulimea A
Exist dou variante ale algoritmului Greedy:
- cu ordonarea n prealabil a mulimii A
- fr ordonarea mulimii A
Descrierea algoritmic a metodei generale este prezentat n continuare, cu
meniunea c propoziiile nestandard (introduse prin prefixarea cu simbolul*) nu
pot fi rafinate dect pentru problema concret de rezolvat:
Algoritm Greedy este: //varianta fr ordonarea mulimii A
Date de intrare: A
Date de ieire: B
Fie B={}
Cttimp (A) i (*B nu este optim)
*Alege ai din A
Dac B {ai} *este soluie posibil atunci
B = B {ai} sau * ai nlocuiete un element din B
A=A \ { ai }
SfDac
SfCttimp
SfAlgoritm
142

Algoritmi i structuri de date

Algoritm Greedy2 este: //varianta cu ordonarea mulimii A dup un criteriu


Date de intrare: A
Date de ieire: B
Fie B={}
*Ordoneaz mulimea A
Pentru i de la 1 la n
Dac B {ai} este soluie posibil atunci
B = B {ai} sau ai nlocuiete un element din B
SfDac
SfPentru
SfAlgoritm
Problema 1. Problema monedelor.
Se consider o mulime infinit de monede de valori 1,k1,k2,...,kn-1 i S o sum
exprimabil prin aceste monede. Se cere determinarea unei scheme de exprimare a
sumei S utiliznd un numr optim (minim) de monede.
Analiza problemei:
Din problema formulat mai sus putem deduce mulimea de intrare A ca fiind
mulimea monedelor de valori 1,k 1,k2,...,kn-1, considernd c numrul de monede de
aceiai valoare este nelimitat. Soluia problemei este reprezentat dintr-o
submulime B de monede, nu neaprat de valori diferite, astfel nct dimensiunea
submulimii este minim i suma valorilor monedelor coninute de B nu depete
suma dat S.
Rezolvarea logic a problemei const n parcurgerea pailor:
- iniializeaz mulimea B cu mulimea vid
- se alege cea mai mare unitate monetar k j care este mai mic valoric dect
suma S
- Ct timp suma S rmne pozitiv adaug moneda k j la mulimea B i scade
valoarea monedei din suma S
- alege monede de valoare imediat mai mic dect k j, respectiv, kj-1
- Ct timp suma S rmne pozitiv adaug moneda k j-1 la mulimea B i
scade valoarea monedei kj-1din suma S
- alege monede de valoare imediat mai mic dect k j-1, respectiv, kj-2
- ...
- Procesul se continu pn cnd nu mai exist valori monetare mai mici
care ar putea fi adugate mulimii B
Procedeul descris anterior corespunde unui algoritm Greedy: soluia global se
construiete treptat prin alegeri succesive i irevocabile ale unitilor monetare ce
intr n alctuirea schemei de exprimare a sumei S. Lcomia procedeului este dat
de maniera de parcurgere a valorilor monetare, de la cele mai mari nspre cele mai
mici, ceea ce permite formarea unei mulimi B de dimensiune minim (cu ct
valorile monetare sunt mai mari, cu att numrul monedelor prin care se exprima S
este mai mic).
143

Algoritmi i structuri de date

Algoritmul GreedyUnitiMonetare este:


Date de intrare: S,k,n
Date de ieire: B
Fie B={}
*Caut cel mai mare j pentru care kj<=S
Cttimp (j0)
Cttimp (S- kj 0)
S=S- kj
B=B { kj }
SfCttimp
j=j-1 //alege urmtoarea valoare monetar, mai mic
SfCttimp
Datele de intrare ale algoritmului descris sunt suficiente pentru a defini
mulimea A: cunoscnd k, n i faptul c numrul de monede de acelai tip este
nelimitat, mulimea A se subnelege ca fiind: A= {1,1, , k,k,,k2, k2, kn1 n-1
,k ,..}
Problema 2. Problema Rucsacului. Se consider o mulime A de n obiecte
caracterizate fiecare prin capacitate i importan: A= {a1,a2,,an}:

i =1,..., n , wi importana obiectului ai

i =1,..., n , ci importana obiectului ai


Se cere determinarea unei scheme de ncrcare optim a unui rucsac de
capacitate maxim permis C cu obiecte din mulimea A: suma importanelor
obiectelor selectate trebuie s fie maxim i suma capacitilor obiectelor selectate
nu trebuie s depeasc capacitatea maxim C.
Observaie: Varianta fracionar a problemei rucsacului permite ca anumite
obiecte s fie ncrcate n fraciuni, soluia final oferit de metoda Greedy fiind
optimul global. Pentru problema discret a rucsacului (obiectele nu pot fi mprite
pe fraciuni), metoda Greedy nu determin ntotdeauna optimul global.
Analiza problemei (varianta discret): Strategia de ncrcare a rucsacului
(iniial gol) este de a introduce treptat n rucsac acel obiect din mulimea obiectelor
disponibile, pentru care raportul capacitate importan este minim, ceea ce
corespunde construirii unei soluii pariale optime. Practic, la fiecare pas de
construire a soluiei vom prefera un obiect de importan mare i capacitate mic.
Alegerea lacom a obiectelor garanteaz obinerea unei soluii finale bune.
Faptul c asupra alegerilor efectuate nu se revine determin ca n multe situaii
soluia optim global s nu poat fi gsit.
Se observ c mulimea obiectelor A se parcurge n ordine cresctoare dup
raportul capacitate importan, ceea ce ne conduce la construirea unui algoritm
Greedy n varianta a doua, cu ordonarea elementelor mulimii A.
Algoritmul de rezolvare a problemei rucsacului, varianta discret, este descris n
continuare:
144

Algoritmi i structuri de date

Algoritm Rucsac este:


Date de intrare: A= {a1,a2,,an}
//pentru care se cunosc wi i ci , i =1,..., n
Date de intrare: C capacitatea rucsacului
Date de ieire B //mulimea obiectelor selectate n rucsac
Ordoneaz A , cresctor dup valorile rapoartelor wi/ci
Fie CB=0 //suma capacitilor obiectelor selectate
Fie B={}
Pentru i de la 1 la n
Dac ( CB + ci C ) atunci
B=B {ai}
CB =CB + ci
SfDac
SfPentru
SfAlgoritm
Problema 3. Planificarea spectacolelor. Se consider o mulime de n activiti
(spectacole) A= {a1,a2,,an}, fiecare activitate ai fiind caracterizat prin ora de
debut si i ora de terminare t i. ntr-o sal de spectacole, se dorete o selecie a
activitilor disponibile astfel nct s fie derulate, ntr-o singur zi ct mai multe
spectacole i acestea s nu se intercaleze.
Se cere un orar al spectacolelor astfel nct numrul lor s fie maxim posibil i
s nu existe suprapuneri.
Analiza problemei: Din analiza specificaiilor problemei se deduce imediat
formularea specific unei probleme rezolvabile prin metoda Greedy:
Se d A= {a1,a2,,an}
Se cere B A astfel nct B este maximal i verific anumite condiii.
Condiiile se refer la ne-suprapunerea oricror dou activiti planificate.
Ceea ce este dificil de stabilit, este maniera de alegere, la fiecare pas, a
urmtoarei activiti din mulimea activitilor disponibile (neplanificate deja),
pentru a obine n final o planificare optim.
Identificm trei variante de alegere a urmtoarei activiti pe care o planificm :
- n ordine cresctoare, dup timpul de start si
- n ordine cresctoare, dup durata activitii: d i = ti-si
- n ordine cresctoare, dup timpul de terminare t i
Dintre cele trei variante enunate, doar cea de-a treia se dovedete plauzibil.
Pentru primele dou cazuri vom demonstra ineficiena prin contraexemple:
Contraexemplu 1.
t4

s4
s3
s2

t3

t2
t1

s1

145

Algoritmi i structuri de date

Considerm cazul particular descris n imaginea alturat. Fiecare activitate este


reprezentat printr-un segment ale crui capete marcheaz timpul de start i de
terminare. Prin alegerea spectacolelor n ordinea cresctoare a timpului de debut, se
va alege ca prim activitate planificat: a 1 i se constat imposibilitatea selectrii
unei alte activiti fr s existe suprapuneri. Algoritmul ar furniza n acest caz
soluia B={a1} cu toate c exist o soluie mai bun: B={a2,a3}.
Contraexemplu 2.
t4

s4
s3

t3

t2

s2
s1

t1

Prin alegerea n ordinea cresctoare a duratei activitilor disponibile, n cazul


particular figurat n imaginea alturat, prima activitate selectat este a 1. Aceast
alegere se dovedete neinspirat deoarece nicio alt activitate nu va mai putea fi
planificat ulterior, datorit suprapunerilor. Soluia ar fi n acest caz B={a1} cu
toate c exist o soluie mai bun: B={a2,a3}.
Algoritmul de rezolvare a problemei planificrii spectacolelor implic
ordonarea mulimii de intrare, cresctor, dup timpul de terminare a activitilor:
Algoritm GreedySpectacole este:
Date de intrare: A= {a1,a2,,an}// pentru care se cunosc

i =1,..., n

s i i ti ,

Date de ieire B
Fie B={}
*Ordoneaz cresctor A dup valorile ti
Pentru i de la 1 la n
Dac *(nu exist aj B, astfel nct ai se suprapune lui aj) atunci
B=B {ai}
SfDac
SfPentru
SfAlgoritm
Observaie: Dou activiti ai i aj se consider c nu se suprapun ddac este
verificat condiia:si>tj sau sj>ti.

146

Algoritmi i structuri de date

XV.

METODE DE ELABORARE A ALGORITMILOR.


BACKTRACKING.

Metoda Backtracking (cutare cu revenire) este o metod general de rezolvare


a unei clase de probleme de optimizare bazat pe principiul construirii soluiilor, n
mod treptat, prin alegeri succesive ale soluiilor pariale. Spre deosebire de metoda
Greedy, bazat pe acelai principiu, Backtracking ofer posibilitatea revenirii
asupra unor decizii anterioare, fapt care conduce la obinerea soluiilor optime
globale n orice situaie. n plus, dac sunt mai multe soluii, metoda le determin
pe toate.
Metoda cutrii cu revenire realizeaz o explorare sistematic a spaiului
soluiilor posibile, construiete soluiile finale, fr a genera ns toate soluiilor
posibile din care s-ar putea extrage doar acele soluii ce ndeplinesc condiii
specifice problemei. Un dezavantaj major al metodei este acela c este o metod
relativ costisitoare n privina timpului de execuie.
Problemele rezolvabile cu metoda Backtracking sunt probleme n care soluia
este reprezentat sub forma unui vector:
x = {x1 , x 2 ,..., x n } S ,

i =1,..., n , xi Ai
unde S = A1 A2 .. An , i Ai sunt mulimi finite nu neaprat distincte.
Cerina problemei este determinarea tuturor soluiilor posibile, care satisfac
anumite condiii specifice problemei (denumite condiii interne).
O abordare simplist ar genera toate soluiile posibile - elementele produsului
cartezian S = A1 A2 .. An , i ulterior s-ar verifica aceste soluii n vederea
identificrii acelora care verific condiiile interne. Abordarea aceasta este
neeficient. Generarea tuturor soluiilor implic memorie i timp suplimentar.
Backtracking este o alternativ inspirat de rezolvare a problemelor de acest gen.
Metoda evit generarea tuturor soluiilor posibile, soluiile finale se obin prin
alegerea succesiv de elemente din mulimile A1 , A2 , , An cu posibilitatea
revenirii asupra unei alegeri dac aceasta nu a condus la obinerea unei soluii
finale.
Algoritmii Backtracking pornesc cu o soluie iniial reprezentat de vectorul
vid. Ulterior acesta va fi mrit prin adugarea elementelor x k din mulimea
corespunztoare Ak , treptat pentru k=1,2,. Dac vectorul parial
{x1 , x 2 ,..., x k } verific anumite condiii de validare, deduse din condiiile
interne, se va continua cu augmentarea vectorului curent prin adugarea unui nou
element x k +1 din mulimea corespunztoare Ak +1 . Dac vectorul nu
ndeplinete condiiile de validare se ncearc un nou element din mulimea Ak i
se reverific condiiile de validare. Exist posiblitatea ca Ak s nu mai conin
alte elemente care au rmas neverificate, ceea ce produce o revenire la o decizie
anterioar i ncercarea unui alt element pe poziia anterioar k-1 din vector.

147

Algoritmi i structuri de date

Asupra vectorului soluie se acioneaz prin doar dou operaii: adugare nou
element dup ultimul adugat, respectiv, eliminare ultim element adugat. Aceste
operaii corespund unor operaii cunoscute: push i pop; astfel, vectorul soluie
funcioneaz pe principiul unei stive.
O problem se identific ca o problem rezolvabil prin metoda Backtracking
dac putem identifica urmtoarele aspecte din specificaiile sale:
1. spaiul soluiilor este un produs cartezian S = A1 A2 .. An
2. soluia
probleme
poate
fi
reprezentat
ca
un
vector
x = {x1 , x 2 ,..., x n } S
3. exist un set de condiii prin care putem decide dac o soluie parial
dat de vectorul {x1 , x 2 ,..., x k } , k n este valid condiiile de
validitate
4. exist un set de condiii prin care putem decide dac o soluie parial
este final condiiile de finalitate
5. soluia (vectorul) se poate construi pas cu pas, astfel nct dac
{x1 , x 2 ,..., x k } este valid, are sens completarea vectorului cu un
element pe poziia k+1.
Considerm soluia parial {x1 , x 2 ,..., x k } reprezentat ca stiv n imaginile
alturate:
Cazul 1

Nivelul k+1
Nivelul k

xk+1

Nivelul k+1

xk+1

xk

Nivelul k

xk

Nivelul k-1

xk-1

Nivelul k-1

xk-1

Nivelul 2

x2

Nivelul 2

x2

Nivelul 1

x1

Nivelul 1

x1

Cazl 2.1

Nivelul k+1
Nivelul k

xk+1

Nivelul k+1

xk+1

xk

x k*

Nivelul k

Nivelul k-1

xk-1

Nivelul k-1

xk-1

Nivelul 2

x2

Nivelul 2

x2

Nivelul 1

x1

Nivelul 1

x1

148

Algoritmi i structuri de date

Cazul 2.2

Nivelul k+1
Nivelul k

xk+1

Nivelul k+1

xk+1

xk

Nivelul k
Nivelul k-1

xk

Nivelul k-1

xk-1

xk-1

Nivelul 2

x2

Nivelul 2

x2

Nivelul 1

x1

Nivelul 1

x1

Tratarea nivelului k se face difereniat pentru cazurile urmtoare:


Cazul 1. {x1 , x 2 ,..., x k } verific condiiiile de validitate: n acest caz se va
trece la completarea nivelului urmtor al stivei cu un element x k +1 din Ak +1 .
(operaie push). Dac vectorul parial verific i condiiile de finalitate se va
furniza soluia.
Cazul 2. {x1 , x 2 ,..., x k } NU verific condiiile de validitate i:
Cazul 2.1. mai exist elemente din Ak care nu au fost ncercate, atunci:
- se alege alt element x k* nencercat din Ak i se reverific
condiiile de validitate.
Cazul 2.2 Nu mai exist elemente din Ak rmase nencercate, atunci:
- se revine pe nivelul anterior k-1 (operaie pop).
Procesul este unul repetitiv, pentru fiecare nivel curent al stivei se va proceda
similar. Descrierea algoritmului se poate face n dou variante: iterativ i recursiv,
i n ambele situaii vom considera subalgoritmii de tip funcie prin care se verific
condiiile de validitate respectiv de finalitate pentru o soluie parial oarecare
{x1 , x 2 ,..., x k } .
Funcie Valid(k) este:
//verific dac soluia parial {x1 , x 2 ,..., x k } din stiv este valid sau
nu
//ntoarce Adevarat sau Fals n funcie de rezultatul testului
SfFuncie
Funcie Final(k) este:
//verific dac soluia parial {x1 , x 2 ,..., x k } este final sau nu
//ntoarce Adevarat sau Fals n funcie de rezultatul testului
SfFuncie
Varianta Iterativ:
Algoritm BackTrackingIterativ este:
149

Algoritmi i structuri de date

k=1 //iniializarea nivelului stivei


Cttimp (k1)
Dac ( x k Ak nencercat ) atunci
Stiva[ k ] x k //operaia push
Dac Valid(k)=Adevarat atunci
Dac Final(k)=Adevarat atunci
Tiprete Soluie: Stiva Stiva[1],.., Stiva[k ]
Altfel
k=k+1 //trece la alt nivel
SfDac
SfDac
Altfel
Stiva[ k ] = Null
//operaia pop
k=k-1
//revine la nivelul precedent
SfDac
SfCttimp
SfAlgoritm
Varianta recursiv:
Subalgoritm Backtracking( k) este: //trateaz nivelul k
Pentru fiecare x k Ak
Stiva[ k ] x k //operaia push
Dac Valid(k)=Adevarat atunci
Dac Final(k)=Adevarat atunci
Tiprete Soluie: Stiva Stiva[1],.., Stiva[k ]
Altfel
Cheam Backtracking( k+1) //autoapel
SfDac
SfDac
SfPentru
SfSubalgoritm
Observaie: n varianta recursiv, operaiile pop (revenirea pe un nivel inferior al
stivei soluie) se execut automat la revenirile din apelurile subalgoritmului.
Pentru furnizarea soluiilor, se va apela:
Cheam Backtracking (1).
Problema 1. Problema damelor. Se d o tabl de ah de n linii i n coloane.
Avnd n regine, se cer toate soluiile de aranjare a reginelor pe tabla de ah, astfel
nct oricare dou piese de acest tip s nu se atace.
Analiza problemei:
Pornind de la observaia c dou dame se atac n condiiile n care ambele se
situeaz: fie pe aceiai linie, fie pe aceiai coloan, fie pe o diagonal a tablei de
150

Algoritmi i structuri de date

ah, putem deduce o variant simplificat de reprezentare a unei soluii prin


mpiedicarea oricror dame de a fi situate pe aceiai coloan. Considerm astfel c
dama k este blocat s ocupe coloana k a tablei de ah. Astfel, oricare dou dame
sunt poziionate pe coloane distincte i soluia final poate fi reprezentat printr-un
vector de n elemente Stiva[1],.., Stiva[k ],..., Stiva[n ] astfel nct:
k reprezint indicele coloanei pe care se afl dama k
Stiva[k] elementul din stiv este indicele liniei ocupate de dama k
Practic, poziia n stiv: k - corespunde att indicelui de coloan ct i
identificatorului damei, iar valoare de pe nivelul k din stiv coincide cu numrul
liniei ocupate de dama k.
1.
2.
3.
4.
5.

Spaiul soluiilor posibile este produsul cartezian: A A ... A ,


unde A={1,2,,n}.
Soluia este reprezentat printr-un vector (stiv).
Condiiile ca o soluie parial Stiva[1],.., Stiva[ k ] s fie valid se
reduc la a verifica dac a k-a dam nu atac oricare din damele
poziionate pe primele k-1 coloane.
O soluie este final dac s-a reuit poziionarea tuturor damelor,
respectiv, dimensiunea vectorului construit este n (s-a completat
nivelul n al stivei)
Dac pe tabla de ah sunt dispuse k-1 dame astfel nct acestea nu se
atac (soluie parial valid), se poate trece la poziionarea celei de-a
k-a pies.

Pe baza consideraiilor 1-5 problema 1 se poate rezolva printr-un algoritm


Backtracking. Algoritmul general descris anterior nu se modific, n schimb vor fi
detaliate funciile Valid i Final:
Funcie Valid(k) este
//verific dac a k-a dam atac damele 1,2,,k-1
Valid=Adevrat
Pentru i de la 1 la k-1
Dac ( Stiva[ k ] = Stiva[i ] ) SAU ( (k-i)=( Stiva[ k ] Stiva[i ] ) )
atunci
/ damele k i i sunt situate pe aceiai linie sau pe aceiai diagonal
Valid=Fals
SfDac
SfPentru
SfFuncie
Funcie Final(k) este:
Dac k=n atunci
// am ajuns la nivelul n al stivei s-au poziionat toate cele n regine
Final=Adevrat
Altfel
Final=Fals
151

Algoritmi i structuri de date

SfDac
SfFuncie
Rezolvare1: Programul C pentru rezolvarea problemei damelor (varianta
nerecursiv)
#include <stdio.h>
#include <math.h>
int stiva[100]; //solutia se construieste in stiva
int nrsolutii;
int n;
int valid(int k)
{
int i;int cod=1;
for(i=1;i<=k-1;i++)
if ((stiva[k]==stiva[i]) ||
(abs(k-i)==abs(stiva[k]-stiva[i]) ) )
cod=0;
return cod;
}
int final(int k)
{
if (k==n)
return 1;
else
return 0;
}
void tipareste()
{
int i;
printf("\n");
for(i=1;i<=n;i++)
printf("%d ",stiva[i]);
}
void main()
{
nrsolutii=0;
int i;
int gasit;
printf("dati n:"); scanf("%d",&n);
int k=1; //incepem de la primul nivel;
while(k>=1)
{
gasit=0;
for(i=stiva[k]+1;(i<=n) ;i++)
{
stiva[k]=i;

152

Algoritmi i structuri de date


if (valid(k)==1) {gasit=1;break;}
}
if (gasit==0) //nu s-au mai gasit
{stiva[k]=0;
k=k-1;
}
else
if (final(k)==1)
{tipareste();
nrsolutii=nrsolutii+1;}
else
k=k+1;
}
printf("\n Numarul de solutii=%d",nrsolutii);
}

Rezolvare2: Programul C pentru rezolvarea problemei damelor (varianta


recursiv)
#include <stdio.h>
#include <math.h>
int stiva[100]; //solutia se construieste in stiva
int nrsolutii;
int n;
int valid(int k)
{
int i;
int cod=1;
for(i=1;i<=k-1;i++)
if ((stiva[k]==stiva[i])
|| (abs(k-i)==abs(stiva[k]-stiva[i]) ) )
cod=0;
return cod;
}
int final(int k)
{
if (k==n)
return 1;
else
return 0;
}
void tipareste()
{
int i;
printf("\n");
for(i=1;i<=n;i++)
printf("%d ",stiva[i]);
}

153

Algoritmi i structuri de date


void dame(int k)
{
for(int i=1;i<=n;i++)
{
stiva[k]=i;
if (valid(k)==1)
if (final(k)==1)
tipareste();
else
dame(k+1);
}
}
void main()
{
printf("\n dati n:");scanf("%d",&n);
dame(1);
}

Problema 2. Se d un labirint reprezentat sub forma de matrice cu m linii si n


coloane, ale crei elemente sunt valori 1 sau 0. Fiecare element al matricei
reprezint o camera a labirintului (1 pentru zid si 0 pentru drum liber). Intr-una din
camere, de coordonate Xstart si Zstart se afla o persoan. Determinai toate
drumurile prin care persoana va putea iei din labirint,dac aceasta va putea efectua
doar micri spre Nord, Sud, Est i Vest.
Analiza problemei:
Fie L matricea de reprezentare a labirintului:
a( n,1)
a( 1,1) a( 2,1)

a( 2,1) a( 2,2 )

L =
, a( i, j) {0,1}

a( m,1) a( m,2 )
a( m, n )

O soluie final a problemei poate fi reprezentat printr-un ir al micrilor


efectuate n labirint, ns configuraia stivei n care sunt salvate camerele traversate
este uor modificat fa de problema damelor. Astfel, fiecare poziie nou pe care
o ncearc persoana n labirint este identificat prin dou elemente: linia i coloana
matricei prin care este reprezentat labirintul. Aceast observaie ne este util n
modul de construire a stivei: fiecare nivel al stivei necesit memorarea a dou
elemente (spre deosebire de rezolvarea problemei 1): linia i coloana poziiei n
matrice.
Acest gen de probleme pentru care fiecare nivel al stivei reine mai multe
elemente se rezolv cu un algoritm Backtracking generalizat. Principul aplicat este
acelai ca i la Backtracking-ul simplu: soluia/soluiile se construiesc treptat.
Soluiile finale ale problemei labirintului sunt vectori de perechi (i,j) cu
semnificaia poziiei camerelor prin care a trecut persoana pentru a iei din labirint.
Lungimile acestor vectori soluie sunt diferite. Anumite drumuri parcurse pot fi mai
154

Algoritmi i structuri de date

scurte dect altele. Condiia ca un vector de perechi (i,j) s fie soluie final a
problemei este adevrat dac ultima camer (poziie) parcurs se afl la marginea
labirintului: pe prima sau ultima coloan, sau, pe prima sau ultima linie a matricei
de reprezentare.
Condiiile de validitate se deduc din specificaiile problemei. Drumul persoanei
poate continua ntr-o nou poziie (i,j) dac valoarea elementului de pe linia i i
coloana j din matricea L este 0 (drum liber). Mai mult, pentru a evita parcurgerea
acelorai camere de mai multe ori, se impune restricia ca poziiile prin care trece
persoana s fie distincte. Astfel, persoana poate trece ntr-o camer vecin dac: nu
este zid (camer liber) i nu a mai fost traversat.
De asemenea, tot specificaiile problemei ne indic faptul c mutarea ntr-o
nou camer se va face doar prin 4 micri posibile n poziiile vecine: stnga,
dreapta, sus i jos
n continuare este descris algoritmul backtracking de rezolvare a problemei
labirintului (varianta recursiv), punnd n eviden condiiile de validitate i
finalitate prin subalgoritmi corespunztori:
Funcie Valid(k)
// k-nivelul stivei
Fie (i,j) poziia memorat n Stiva[k]
Dac (a(i,j)=0) atunci
//camer liber
Valid=Adevrat
Pentru l de la 1 la k-1
Dac (i, j ) = Stiva[l ] atunci
//camera a mai fost traversat n drumul memorat n stiv
Valid=Fals
SfDac
SfPentru
Altfel
//camera ocupat - zid
Valid=Fals
SfDac
SfFuncie
Funcie Final (k)
Fie (i,j) poziia memorat n Stiva[k]
Dac i=1 sau j=1 sau i=m sau j=n atunci
Final=Adevarat
Altfel
Final=Fals
SfDac
SfFuncie
Subalgoritm Labirint(k) este:
Pentru fiecare vecin posibil (i,j) al camerei salvate n Stiva[k-1]
Stiva[k]=(i,j) //operaia push
155

Algoritmi i structuri de date

Dac Valid(k)=Adevrat atunci


Dac Final(k)=Adevrat atunci
Tiprete Soluie: Stiva Stiva[1],.., Stiva[k ]
Altfel
Cheam Labirint(k+1)
SfDac
SfDac
SfPentru
SfSubalgoritm
Rezolvarea problemei labirintului se face prin iniializarea stivei cu poziia de
start: Stiva[ 0] = ( Xstart , Ystart ) i apelul:Cheam Labirint(1).
Problem rezolvat PERMUTRI: Programul urmtor geneeaz permutrile de n
prin metoda Backtracking - varianta recursiv.
#include <stdio.h>
int n, stiva[200];
void TiparesteSolutia() {
printf("\n");
for (int i = 1; i <= n; i++)
printf("%d",stiva[i]);
}
int valid(int k) {
for (int i = 1; i < k; i++)
if(stiva[i] == stiva[k])
return 0;
return 1;
}
void permutari(int k) {
for (int i = 1; i <= n; i++)
{
stiva[k] = i;
if (valid(k))
if (k == n)
TiparesteSolutia();
else
permutari(k+1);
}
}
void main() {
printf("dati n");scanf("%d",&n);
permutari(1);
}

156

Algoritmi i structuri de date

XVI. METODE DE ELABORARE A ALGORITMILOR.PROGRAMARE


DINAMICA.
Metoda Programrii dinamice se poate considera ca o mbuntire a tehnicii
Greedy. n cazul metodei Greedy, alegerea unui element se face pe baza unui
criteriu de admisibilitate, ns la Programarea dinamic, acest criteriu este cel al
optimalitii; folosind-se de un criteriu mai puternic, rezultatul final oferit de
metoda programrii dinamice este ntotdeauna optimul global.
Ca i metoda Divide et impera, programarea dinamic presupune mprirea
problemei n subprobleme, rezolvarea i combinarea soluiilor acestora pentru a
obine rezultatul final. n situaia n care subproblemele obinute prin
descompunere conin subprobleme comune, este preferabil aplicarea metodei
Programrii dinamice.
O particularitate a metodei const n construirea si utilizarea unor tabele cu
informaii. Tabelele sunt construite prin completarea unui element folosind
elemente completate anterior. Denumirea metodei provine din maniera de
completare dinamic a tabelei de informaii intermediare.
Metoda programrii dinamice se caracterizeaz prin principiile fundamentale:
- sunt evitate calculele repetate ale aceluiai subcaz, rezultatele intermediare
obinute fiind memorate
- soluia este construit n manier bottom-up (de jos n sus): pornind de la
soluiile celor mai mici subprobleme, prin combinarea acestora se va
ajunge treptat la soluia global
- problema abordat respect principiul optimalitii
Programarea dinamic este o metod de rezolvare a problemelor de optimizare
pentru care soluia este rezultatul unui ir de decizii optime. Problema de
optimizare rezolvabil prin metoda programrii dinamice este formulat astfel:
Fie un sistem care se poate afla ntr-una din strile:
S0, S1, S2 , , Sn-1, Sn
i fie irul de decizii
D1, D2 , , Dn-1, Dn
prin care sistemul este trecut prin strile date:
S 0 D1 S 1 D2 S 2 ... S n 1 Dn S n
Problema verific unul dintre principiile optimalitii:
Varianta 1: Dac irul de decizii D1, D2 , , Dn-1, Dn duce sistemul din starea
iniial S0 n starea final Sn n mod optim, atunci:
k {1,2,..., n} , secvena de decizii Dk , , Dn-1, Dn duce sistemul din starea
Sk-1 n starea final Sn n mod optim.
Varianta 2: Dac irul de decizii D1, D2 , , Dn-1, Dn duce sistemul din starea
iniial S0 n starea final Sn n mod optim, atunci:

157

Algoritmi i structuri de date

k {1,2,..., n} , secvena de decizii D1 , , Dk-1, Dk duce sistemul din starea

iniial S0 n starea Sk n mod optim.


n funcie de varianta principiului optimalitii identificat din analiza
problemei, metoda de abordare a poate fi:
Metoda nainte aplicat n cazul verificrii principiului optimalitii n
forma 1.
Metoda napoi aplicat n cazul verificrii principiului optimalitii n
forma 2.
Esena celor dou variante (nainte sau napoi) este aceiai i const n
identificarea i rezolvarea relaiilor de recuren referitoare la criteriul de optimizat
sau la valoarea ce se calculeaz.
Metoda nainte opereaz n urmtoarele 4 faze:
1.
prin relaiile de recuren se vor calcula optimele corespunztoare strilor
ndeprtate de starea final n funcie de optimele corespunztoare strilor
apropiate de starea final (practic n aceast faz se completeaz tabela de
informaii intermediare)
2.
se determin deciziile care leag optimele determinate n faza anterioar
3.
se afl optimul total
4.
se determin soluia problemei reprezentat de un ir de decizii constituit
prin compunerea deciziilor de la etapa 2, n mod directe, de la nceput
spre sfrit (nainte)
Metoda napoi este dual metodei descrise anterior i opereaz n urmtoarele 4
faze:
1.
prin relaiile de recuren se vor calcula optimele corespunztoare strilor
apropiate de starea final n funcie de optimele corespunztoare strilor
deprtate de starea final
2.
se determin deciziile care leag optimele determinate n faza anterioar
3.
se afl optimul total
4.
se determin soluia problemei reprezentat de un ir de decizii constituit
prin compunerea deciziilor de la etapa 2, n mod invers, de la sfrit spre
nceput (napoi)
Dei pare o metod general de rezolvare a problemelor de optim,
aplicabilitatea programrii dinamice este restrns doar la acele probleme pentru
care se poate demonstra principiul optimalitii.
Problema 1 Subir cresctor de lungime maxim (metoda nainte)
Se d A = { X 1 , X 2 ,..., X n } o mulime (ir) neordonat de elemente.
Se cere determinarea lui B, B A , ir ordonat cresctor, de lungime maxim.
Exemplu: n irul 7 1 4 2 5 3, cel mai lung subir cresctor este 1,2,3

158

Algoritmi i structuri de date

Rezolvarea naiv a acestei probleme ar aceea prin care se vor determina toate
subirurile cresctoare urmnd ca ulterior s se extrag acela care are lungime
maxim. Rezolvarea aceasta este corect, ns neeficient prin necesitatea de a
construi i reine toate subirurile lui irului dat.
O soluie ingenioas are fi s se calculeze i s se rein ntr-o tabel doar
lungimile tuturor subirurilor cresctoare, fr a le genera i pe acestea.
n exemplul anterior, subirurile cresctoare i lungimile acestora sunt:
Subiru
l
7
123
45
23
5
3

Lungimea
1
3
2
2
1
1

Se poate observa c dac subirul 3 are lungime 1, subirul care l conine are
lungimea mai mare cu 1, respectiv 2 (1+1) i subirul maximal 1 2 3 are lungime 3
(2+1).
Astfel: Dac subirul 1 2 3 este subirul optimal cresctor de lungime maxim
(3), care ncepe cu 1, atunci:
2 3 este subirul optimal cresctor de lungime maxim (2) care ncepe cu 2 i
3 este subirul optimal cresctor de lungime maxim (1) care ncepe cu 3
Generaliznd, dac i1 , i 2 ,..., i n este subirul optimal cresctor de lungime n
care ncepe cu i1 : atunci:
- i 2 , i 3 ,..., i n este subirul optimal optimal de lungime n-1 care ncepe cu
i2
- i 3 , ,..., i n este optimal de lungime n-2 care ncepe cu i3
- ..
- i k , i k +1 ,..., i n este subirul optimal de lungime maxim care ncepe cu
ik, k > 1
Prin reformularea observaiilor anterioare se identific principiul optimalitii n
varianta 1, fapt pentru care se va aplica metoda nainte:
Etapa_1&2: se vor calcula pe baza relaiilor de recuren, pentru fiecare
element al irului iniial lungimea celui mai mare subir cresctor care se poate
forma ncepnd de la el.
Notm L(k) lungimea celui mai mare subir cresctor care se poate forma
ncepnd elementul de pe poziia k din irul dat. Relaia de recuren este
urmtoarea:
L(k):={1+maxL(i) , astfel nct Xi >=Xk, i={1, 2, , n}} i k {1, 2, , n} }
159

Algoritmi i structuri de date

Algoritm Determinare_Lungimi este:


L(n)=1
Pentru k=n-1 la 1 cu pas -1
L(k)=1
Caut i>k astfel nct i n i X i X k
Dac s-a gsit (i) atunci
L(k)=L(i)+1
SfDac
SfPentru
SfAlgoritm
Etapa3: Se determin maximul din tabela lungimilor L. Fie acesta L(imax). n
acest caz:
- L(imax) reprezint lungimea subirului cresctor de lungime maxim
(lungimea soluiei)
- imax reprezint indicele din irul iniial de la care ncepe subirul soluie
Etapa4: Se determin soluia problemei (subirul cresctor de lungime
maxim), prin construire, pornind de la elementul de pe poziia imax pn la
elementul de pe poziia n, reinndu-se doar elementele care verific relaia de
ordine.
Problema 2: Problema Robotului (metoda napoi)
Se d o tabl mprit n m linii i n coloane:

a11

a
A = 21
...

a
m1

a12
a22
...
am 2

a1n

a2 n
...

... amn
...
...
...

Fiecare csu a tablei conine obiecte de valori diferite (numere naturale):

aij N .

Un robot situat n csua de start a11 va traversa tabla pentru a ajunge la csua
final amn realiznd doar micrile posibile:
- la dreapta cu o csu: ( aij ai ,( j +1) )
- n jos cu o csu: ( aij a( i +1), j )
i colecteaz obiectele din csuele traversate.
S se determine secvena de micri (drumul) a robotului astfel nct suma
valorilor colectate la ieire s fie maxim.
Exemplu:
Pentru urmtorul caz:
160

Algoritmi i structuri de date

Soluia imediat este parcurgerea csuelor: (1,1)-(2,1)-(2,2)-(3,2)-(4,2)-(4,3)(4,4), robotul colecteaz obiecte de valoare total: 18 reprezentnd suma maxim a
sumelor colectate prin parcurgerea oricror drumuri de la csua iniial la csua
final.
Se observ c suma maxim cu care ajunge robotul n csua final poate fi
obinut fie prin mutarea anterioar de pe coloana din stnga (n-1), fie prin mutarea
anterioar din celula de pe linia (m-1). Astfel, dac suma final a valorilor este
maxim, atunci prin scderea valorii comune coninut n amn , nseamn c i
drumurile pariale efectuate pn n celula am1,n sau an ,m 1 sunt de sum
maxim parial.
Generaliznd:
- Dac n celula ai , j robotul a ajuns cu transport maxim de la celula
iniial a11 , efectund ultima mutare dreapta (robotul ajunge n celula
ai , j din celula ai , j
1 ) atunci n celula ai , j
1 robotul a ajuns cu sum
maxim posibil de la a11 la ai , j 1 .
- Dac n celula ai , j robotul a ajuns cu transport maxim de la celula
iniial a11 , efectund ultima mutare jos (robotul ajunge n celula ai , j
din celula ai 1, j ) atunci n celula ai 1, j robotul a ajuns cu sum
maxim posibil de la a11 la ai 1, j .
Observaie: Ultima formulare este chiar Principiul optimalitii exprimat n
varianta 2:
Dac secvena de celule parcurse de la celula : (1,1) la celula (m,n)
este de transport maxim: a11 , ,, aik 11 , jk 1 , aik , j k , , a m ,n
Atunci k < n , secvena de celule parcurse de la (1,1) la ( ik ,
jk ) este de transport maxim: a11 , ,, ai
, aik , j
,j
k 11 k 1

Din observaiile anterioare, se poate deduce o relaie de recuren prin care pot
fi determinate sumele maxime cu care robotul poate ajunge n oricare celul a
tablei A:

S ij = max ( S i 1, j , S i , j 1 ) + aij , i, i {1,..., m} i j , j {1,..., n}

161

Algoritmi i structuri de date

Pe baza relaiei de recuren se va construi o tabel suplimentar care reine


valorile sumelor maxime pe care le poate colecta robotul pn n fiecare celul a
tablei iniiale:

S11

S 21
S =
...

S
m1

S12
S 22
...
Sm2

S1n

S2n
...

... S mn
...
...
...

Algoritm DeterminareSumeMaxime este:


S(0,1):=0
S(1,0):=0
Pentru i de la 1 la m
Pentru j de la 1 la n
Dac S(i-1,j)> S(i,j-1) atunci
S(i,j):= S(i-1,j)+ ai,j
Altfel
S(i,j):= S(i,j-1)+ ai,j
SfDac
SfPentru
SfPentru
SfAlgoritm
Se constat c ultima valoare determinat, S m ,n reprezint chiar suma
maxim a drumului complet al robotului, iar soluia problemeivectorul format din
csuele (i,j) traversate cu transport maxim, poate fi determinat printr-o
parcurgerea invers a matricei S :
Algoritm RefacereDrum este:
Vector(1) (m,n) // Reine csua final (m,n)
Fie i=m, j=n, k=2
Cttimp ((i,j)(1,1))
Dac S i 1, j > S i , j 1 atunci
Vector(k) (i-1,j) //Reine csua (i-1,j),
i=i-1
k=k+1
Altfel
Vector(k) (i,j-1) //Reine csua (i,j-1)
j=j-1
k=k+1
SfDac
SfCttimp
//Vectorul de csue parcurs invers este soluia problemei.
Pentru i de la k la 1 cu pas -1
162

Algoritmi i structuri de date

Tiprete Vector(i)
SfPentru
SfAlgoritm
Observaie: Tehnica descris pentru rezolvarea problemei robotului este
Programarea dinamic, varianta metodei napoi.
Probleme:
1. Scriei un program de rezolvare a problemei robotului.
2. Scriei programul de rezolvare a problemei 1: Subir cresctor de lungime
maxim.
3. Scriei un program care determin cel mai lung subir comun al dou iruri
date.

163

Algoritmi i structuri de date

BIBLIOGRAFIE:
1. Liviu Negrescu, Limbajele C si C++ pentru incepatori Vol. I (p.1 si 2) Limbajul C , Editura Albastr, Cluj-Napoca, 2002.
2. Brian Kernighan, Dennis Ritchie, The C Programming Language,
Second Edition, Prentice Hall, 1988.
3. Bazil

Prv,

Alexandru

Vancea,

Fundamentele

limbajelor

de

programare, Editura Albastr, Cluj Napoca, 1996.


4. Knuth, Donald E., Arta programarii calculatoarelor: Algoritmi
fundamentali. vol.1 , Editura Teora, Bucureti, 1999.
5. Doina Logoftu, Algoritmi fundamentali n C++. Aplicaii, Editura
Polirom, Iai, 2007.
6. Freniu M, Prv B., Elaborarea programelor. Metode i tehnici
moderne, Editura PROMEDIA, Cluj-Napoca, 1994.
7. Petrovici V., Goicea F., Programare n limbajul C, Editura Tehnic,
Bucureti, 1993.

164

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