You are on page 1of 212

Cuprins

1. Tipuri dinamice de date. Pointeri ...................................................................... 2. Subprograme .......................................................................................................... 3. Subprograme recursive ....................................................................................... 4. Tipul de dat ARTICOL ...................................................................................... 5. Fiiere de date ...................................................................................................... 6. Algoritmi de prelucrare a fiierelor binare ................................................... 7. Structuri dinamice de date. Liste .................................................................... 8. Grafuri .................................................................................................................... 9. Structuri arborescente ...................................................................................... 10. Elemente de programare orientat obiect ................................................... Bibliografie .................................................................................................................

3 13 30 44 52 72 110 122 160 186 212

1 Tipuri dinamice de date. Pointeri

Pointerul este un tip de dat predefinit, care are ca valoare adresa unei zone de memorie (figura 1.1).
Memoria intern Segment:offset Pointer Zona de memorie indicat de pointer

Figura 1.1 Un pointer este adresa unei alte zone de memorie Folosirea pointerilor prezint urmtoarele avantaje: nlocuirea expresiilor cu indici nmulirile din formula de calcul al rangului se transform n adunri i deplasri; posibilitatea alocrii dinamice a memoriei; folosirea tipurilor procedurale de date; calculul adreselor. n operaiile cu pointeri se folosesc urmtorii operatori specifici: Operatori Simbol Utilizare Operator de refereniere * tip* Operator de refereniere & &nume Operator de derefereniere * *nume * definete un nou tip de dat (pointer la tip); & extrage adresa unei variabile (creeaz o referin); * acceseaz coninutul zonei de memorie indicate de pointer. Cei doi operatori au efect invers: *&nume nume. Exemplu: *&nume reprezint valoarea de la adresa variabilei nume (valoarea variabilei nume).

Tipuri dinamice de date. Pointeri

1.1 Declararea i iniializarea pointerilor


Fie TIP un tip de dat oarecare n limbajul C (inclusiv void). Declararea TIP* nume; este o declaraie de pointer. TIP* este un nou tip de dat denumit pointer spre TIP, iar nume este o variabil de tipul pointer spre TIP. Exemple: n este o variabil de tip pointer spre ntreg; int* n; struct complex {a,b:real;}* x; x este o variabil de tip pointer spre o structur de tipul complex; void* p; p este o variabil de tip pointer spre void; p poate primi ca valoare adresa unei zone de memorie de orice tip. Dac TIP este un tip oarecare (mai puin void) atunci tipul TIP* este adresa unei zone de memorie de un tip cunoscut. Operaiile care se pot efectua asupra zonei respective de memorie sunt definite de tipul acesteia. Dac TIP este void, atunci TIP* este adresa unei zone de memorie de tip necunoscut. Deoarece nu se cunoate tipul zonei de memorie, nu sunt definite operaiile care se pot efectua asupra ei. Pentru pointerii din exemplele anterioare se rezerv n memoria principal (n segmentul de date) cte o zon de 4B n care se va memora o adres (sub forma segment:offset). Cnd variabila nume nu este iniializat prin declarare, ea primete implicit valoarea NULL. La execuie, poate primi ca valoare adresa unei variabile numai de tipul TIP. Dac TIP este void, atunci nume poate primi adresa oricrei variabile, de orice tip. Exemple:
int* nume; int a; float b; nume = &a; => este o atribuire corect; nume are ca valoare adresa variabilei a. nume = &b; => este o atribuire incorect; nume poate primi ca valoare doar

adresa unei variabile ntregi.


void* nume; int a; float b; nume = &a; => nume = &b;

ambele atribuiri sunt corecte; nume poate primi ca valoare adresa oricrei variabile, de orice tip. Iniializarea pointerilor se poate realiza ca n exemplul precedent sau, ca i pentru celelalte variabile, la declarare, astfel:
int a; int* nume=&a;

Se observ folosirea operatorului de refereniere & pentru a crea o referin ctre variabila a. La alocarea dinamic a memoriei se folosete o alt metod pentru iniializarea unui pointer. Operatorul de derefereniere se utilizeaz att

Programarea calculatoarelor

pentru definirea tipului pointer, ct i pentru referirea datelor de la adresa indicat de pointer. Exemplu:
int a,b,c; int* nume; void* nume2; b=5; nume=&a; *nume=b; c=*nume+b; nume2=&b; *(int*)nume2=10; c=*(int*)nume2;

Se observ folosirea conversiei de tip (typecasting), atunci cnd se lucreaz cu pointeri spre tipul void (fr tip). Chiar dac un pointer spre tipul void poate primi ca valoare adresa unei variabile de orice tip, pentru a putea lucra cu ea este necesar gestionarea corect a tipului operanzilor.

1.2 Utilizarea pointerilor


1.2.1 Operaii cu pointeri
Asupra pointerilor se pot efectua operaii aritmetice. Fie secvena:
int *nume,*nume2, c, a, b; nume=&a; nume2=&a;

Incrementare/decrementare Dac nume este pointer spre un tip TIP, prin incrementare/decrementare, valoarea lui nume se incrementeaz/decrementeaz cu numrul de octei necesari pentru a memora o dat de tip TIP, adic cu sizeof(TIP).
nume++ nume are ca valoare o adres care este incrementat i primete valoarea nume+sizeof(int) (care este adresa lui b); nume2-nume are ca valoare o adres care este decrementat i primete valoarea nume-sizeof(int) (care este adresa lui c);

Situaia iniial este urmtoarea:

nume 4B

nume2 4B

a 2B 2B

b 2B

Tipuri dinamice de date. Pointeri

Dup cele dou operaii:


nume 4B nume2 4B c a 2B 2B b 2B

Analog se execut operaiile ++nume i --nume. Exemplu:


float v[20]; float* p; int i; p=&v[i];

=> i poate avea valori ntre 0 i 19

n urma atribuirii ++p sau p++, p va avea ca valoare adresa lui v[i] plus 4 octei, adic adresa lui v[i+1]. Adunarea/scderea unui ntreg n general, dac p este un pointer spre un tip TIP, atunci cnd se adun un ntreg n la pointerul p, rezultatul va fi tot un pointer spre TIP, care are ca valoare adresa memorat n p, la care se adun de n ori numrul de octei necesari pentru a memora o dat de tip TIP, adic n*sizeof(TIP). Asemntor se execut scderea unui ntreg dintr-un pointer. nume+n nume primete valoarea nume+n*sizeof(int) nume-n nume primete valoarea nume-n*sizeof(int) Exemplu: Fie p i q pointeri spre tipul float (float* p, *q). Presupunnd c p a fost iniializat cu valoarea 0x0fff:0x3450, n urma operaiei q=p+3, q primete valoarea 0xfff:0x345c (se adun 3*4 octei). n urma operaiei q=p-2, q primete valoarea 0xffff:0x344a (se scad 2*4 octei). Operaiile descrise anterior se folosesc frecvent n lucrul cu masive. Compararea a doi pointeri Limbajul C permite compararea a doi pointeri ntr-o expresie, folosind oricare din operatorii relaionali (==, !=, <, >, <=, >=). Rezultatul expresiei nume op nume2 (unde op este unul din operatorii precizai anterior) este adevrat (nenul) sau fals (zero) dup cum nume este egal, mai mare sau mai mic dect nume2. Doi pointeri sunt egali dac adresele care constituie valorile lor sunt egale. Privind memoria intern liniar, ncepnd de la 0x0000:0x0000, un pointer p este mai mare dect altul q, dac adresa pe care o conine p este mai ndeprtat de nceputul memoriei dect adresa coninut de q. Este permis i compararea unui pointer cu o valoare constant. Uzual se folosete comparaia cu valoarea NULL pentru a verifica dac pointerul a fost iniializat (un pointer neiniializat are valoarea NULL), folosind unul din operatorii == sau !=. Valoarea NULL este definit n stdio.h astfel: #define NULL 0

Programarea calculatoarelor

De multe ori se prefer comparaia direct cu zero (nume==0 sau nume!=0). n loc de nume=0 se poate folosi expresia nume. Aceasta se interpreteaz astfel: dac nume nu a fost iniializat, atunci are valoarea NULL (adic 0), deci expresia este fals. n caz contrar valoarea expresiei este nenul, deci adevrat. Asemntor se folosete expresia !nume. Exemplu:
float* p,q,r,t; float a,b; p=&a; q=&b; r=&a; a=5; b=7; if(t) printf("Pointer initializat!\n"); else printf("Pointer neinitializat!\n"); if(p==r) printf("Pointeri egali\n"); else printf("Pointeri diferiti\n"); if(p>q) printf("%d\n",a); else printf("%d\n",b);

Pe ecran se va afia:
Pointer neinitializat! Pointeri egali 7

deoarece t are valoarea NULL, variabilele p i r au ca valoare adresa lui a, iar q conine adresa lui b, care este mai mare dect a lui a (datorit faptului c a a fost alocat primul). Diferena dintre doi pointeri Fie secvena:
int m[50],* a, * b; a=&m[i]; b=&m[j];

unde i i j sunt ntregi n intervalul [0..49]. Expresia a-b are valoarea i-j, interpretat ca distan ntre adresele a i b, exprimat n zone de memorie de lungime sizeof(int). Valoarea unei expresii diferen se calculeaz astfel: se face diferena ntre cele dou adrese (n octei), apoi se mparte la dimensiunea tipului de dat referit de cei doi pointeri (tipul int n exemplul de mai sus vezi figura 1.2). Cei doi pointeri trebuie s refere acelai tip de dat, altfel rezultatul nu are semnificaie. Operaia este util n lucrul cu masive.
m i j

Figura 1.2 Reprezentarea semnificaiei variabilelor din exemplul anterior

Tipuri dinamice de date. Pointeri

1.2.2 Legtura ntre pointeri i masive


n limbajul C numele unui masiv este un pointer ctre tipul de dat al elementele masivului. Pentru masivele unidimensionale: int m[50]; m are tipul int* int* p; p are tipul int* Diferena const n faptul c zona de memorie ctre care puncteaz m este rezervat la compilare (ceea ce nu se ntmpl n cazul pointerilor declarai ca atare). De aceea m nici nu poate primi valori n timpul execuiei programului (nu se poate schimba adresa memorat n m). El memoreaz adresa primului element din masiv. Referirea unui element m[i] este echivalent cu *(m+i) coninutul de la adresa m+i. Limbajul C nu face niciun fel de verificri n privina depirii limitelor indicilor masivului, de aceea expresiile m[500] sau m[-7] vor fi considerate corecte de compilator, existnd riscul unor erori logice. Este sarcina programatorului s se asigure c indicii nu vor depi limitele. Pentru masivele bidimensionale: int m[50][50]; m are semnificaia urmtoare: m[i][j] *(*(m+i)+j), reprezint coninutul de la adresa j plus coninutul de la adresa memorat n i plus m. Aceasta poate fi interpretat astfel: m este un pointer spre un vector de pointeri, fiecare element al vectorului fiind la rndul lui un pointer spre o linie a matricei (un vector de elemente de tip float). n acest fel se aloc matricele n mod dinamic (figura 1.3). Analog pot fi interpretate masivele cu mai multe dimensiuni.
m m[0] m[1] m[2] m[3] m[0,0] m[0,0] m[2,0] m[3,0] m[4,0] m[0,1] m[0,1] m[2,1] m[3,1] m[4,1]

m[0,49] m[0,49] m[2,49] m[3,49] m[4,49] m[49,49

m[4]
m[49]

m[49,0] m[49,1]

Figura 1.3 Reprezentarea modului de alocare dinamic a spaiului necesar pentru memorarea unei matrice 50x50

Programarea calculatoarelor

Exemple: un masiv cu trei dimensiuni float m[10][10][10] poate fi interpretat ca un pointer spre un vector de pointeri spre matrice; un masiv cu n dimensiuni este tratat ca un pointer spre un vector de pointeri ctre masive cu n-1 dimensiuni. Pentru a lucra cu elementele unei matrice se poate folosi adresarea indexat (m[i] pentru vectori sau m[i][j] pentru matrice) sau adresarea elementelor prin pointeri (*(m+i) pentru vectori sau *(*(m+i)+j) pentru matrice etc.). De asemenea se poate declara un pointer iniializat cu adresa de nceput a masivului, iar elementele masivului s fie referite prin intermediul acestui pointer. Exemple:
float* v[10]; p=v; float* p;

Dup atribuire, pointerul p conine adresa de nceput a masivului i poate fi folosit pentru referirea elementelor masivului. De exemplu, v[3] i p[3] refer aceeai zon de memorie. S se scrie secvena de program care citete de la tastatur elementele unei matrice, folosind un pointer pentru adresarea elementelor matricei.
int m,n; float a[10][10]; printf("Nr. linii:\n"; scanf("%d", &m); printf("Nr. coloane:\n"); scanf("%d", &n); for(i=0;i<m;i++) for(j=0;j<n;j++) { printf("a(%d,%d)= ",i,j); scanf("%f", *(m+i)+j ); }

Observaie: *(m+i)+j este un pointer, care conine adresa elementului a[i][j]; n funcia scanf trebuie transmise ca parametri adresele unde se depun valorile citite; n exemplul anterior se putea scrie &*(*(m+i)+j), i, reducnd, rezult *(m+i)+j.

1.2.3 Alocarea dinamic a memoriei


Pentru a memora o valoare de un anumit tip n heap este necesar s se declare un pointer ctre acel tip de dat, apoi s se rezerve memoria necesar. Pentru a rezerva spaiu n heap se folosete funcia standard:
void* malloc(unsigned n);

Funcia rezerv o zon de n octei n heap i returneaz adresa acesteia. Deoarece funcia returneaz pointer spre void este necesar conversia spre tipul dorit, astfel:
int* nume; nume=(int *) malloc(sizeof(int));

rezerv n heap spaiu

pentru o valoare de tip ntreg.

Tipuri dinamice de date. Pointeri

Eliberarea unei zone de memorie rezervate anterior se face prin funcia standard:
void free(void* p);

Funcia primete ca parametru un pointer (indiferent de tip) spre zona de memorie pe care trebuie s o elibereze. Limbajul C ofer posibilitatea de a aloca contiguu zone de memorie pentru mai multe date de acelai tip, prin funcia standard:
void* calloc(unsigned nr_elem, unsigned dim_elem);

Funcia calloc rezerv o zon contigu de memorie pentru mai multe elemente de acelai tip, ntorcnd un pointer spre zona respectiv.
int* masiv; masiv=(int*)calloc(50,sizeof(int));

rezerv spaiu de

memorie pentru un vector cu 50 de elemente ntregi. Exist i o variant a lui malloc care returneaz n mod explicit un pointer ndeprtat (far):
void* farmalloc(unsigned long n);

Pentru eliberarea unei zone de memorie rezervate prin farmalloc se folosete funcia standard:
void farfree(void* p);

Exemple: 1. Alocarea de spaiu n heap pentru o matrice.


int** m; int n,p; /* se aloc spaiu pentru vectorul cu adresele celor n linii ale matricei */ m=(int**)malloc(m*sizeof(int*)); for(int i=0;i<m;i++) /*se aloc spaiu pentru fiecare linie a matricei, cte p elemente*/ m[i]=(int*)malloc(n*sizeof(int));

2. S se scrie un subprogram pentru citirea de la tastatur a dimensiunii i elementelor unui vector memorat n heap.
void cit_vect(int *n, float **v) { int i; printf("Nr. elemente: "); scanf("%d ", n); *v=(float*)malloc(*n*sizeof(float));

10

Programarea calculatoarelor
for(i=0;i<*n;i++) { printf("v(%d)= ",i); scanf("%f",&(*v)[i]); } }

3. S se scrie o funcie care s citeasc cel mult n numere ntregi i le pstreze n zona de memorie a crei adres de nceput este dat printr-un pointer. Funcia returneaz numrul valorilor citite.
int cit_nr(int n, int* p) { int nr, i; int* q=p+n; // q este adresa unde se termina zona //rezervata pentru cele n numere i=0; while(p<q) //cit timp nu s-au citit n numere { printf("Numarul %d= ", i); if(scanf("%d", &nr)!=1) break; //in caz de eroare la citire //se termina ciclul *p=nr; p++; i++; } return(i); }

1.2.4 Modificatorul const


n limbajul C constantele simbolice se declar prin directiva de preprocesare #define. O alt posibilitate de lucru cu constante este iniializarea unei variabile cu o valoare i interzicerea modificrii valorii acesteia. n acest scop se folosete modificatorul const. Sunt permise urmtoarele forme de utilizare:
a) tip const nume = valoare; const tip nume = valoare;

sau

Declaraia este echivalent cu tip nume=valoare dar, n plus, nu permite modificarea valorii lui nume printr-o expresie de atribuire nume = valoare_noua; Fa de o constant simbolic, n acest caz se rezerv spaiu de memorie n care se nscrie valoarea constantei (constant obiect). b)
tip const* nume = valoare; const tip* nume = valoare;

sau

Prin aceast declarare se definete un pointer spre o zon cu valoare constant. Nu este permis atribuirea de genul *nume=valoare_noua, dar se poate ca variabilei nume s i se atribuie o adres (de exemplu, nume = p, unde p este un pointer spre tip). Pentru a modifica valoarea nscris n memorie la adresa memorat de

11

Tipuri dinamice de date. Pointeri

pointerul nume se poate folosi totui un alt pointer:


tip *t; t=nume; *t=valoare_noua;

c)

const tip* nume;

Construcia se folosete la declararea parametrilor formali, pentru a mpiedica modificarea lor n corpul subprogramelor, n cazul n care apelatorul are nevoie de valorile iniiale.

1.2.5 Tratarea parametrilor din linia de comand


n linia de comand a unui program pot s apar parametri (sau argumente). Acetia sunt iruri de caractere desprite prin spaii. Programul poate accesa argumentele prin intermediul parametrilor predefinii ai funciei main:
void main(int argc, char* argv[])

unde argc conine numrul de parametri ai programului, incrementat cu 1. Exemplu: Dac programul nu are niciun parametru, argc are valoarea 1, dac programul are doi parametri, argc are valoarea 3 etc. Variabila argv este un vector de pointeri care conine adresele de memorie unde s-au stocat irurile de caractere care constituie parametrii programului. Primul ir (argv[0]) conine identificatorul fiierului (inclusiv calea complet) care memoreaz programul executabil. Urmtoarele iruri conin parametrii n ordinea n care au aprut n linia de comand (parametrii n linia de comand sunt iruri de caractere separate prin spaii). Interpretarea acestor parametri cade n sarcina programului. Exemplu: S se scrie un program care afieaz parametrii din linia de comand.
#include<stdio.h> main(int argc, char *argv[]); { int i; printf("Fisierul executabil: %s\n", argv[0]); for(i=1;i<argc;i++) printf("Parametrul nr. %d: %s\n",i, argv[i]); }

12

2 Subprograme

Conform teoriei programrii, subprogramele sunt clasificate n funcii, care returneaz un singur rezultat prin numele funciei i oricte prin parametri de ieire i proceduri, care returneaz oricte rezultate, prin intermediul parametrilor de ieire. Un program C este un ansamblu de funcii care realizeaz activiti bine definite. Exist o funcie, numit main(), care este apelat la lansarea n execuie a programului. Subprogramele C sunt, n mod nativ, funcii. Pot fi construite subprograme care nu returneaz niciun rezultat prin numele lor, comportndu-se ca o procedur (conform definiiei din teorie). Sistemele C au colecii de biblioteci care conin funcii standard. Textul surs al unui program C poate fi partiionat n mai multe fiiere. Fiecare fiier const dintr-un set de funcii i declaraii globale. Fiierele care constituie partiia pot fi compilate i, eventual, testate separat, dar numai unul va conine funcia main().

2.1 Construirea i apelul subprogramelor


Funciile C sunt formate din antet i un corp. Antetul are forma:
tip nume([lista-parametri-formali])

unde: tip poate fi un tip simplu de dat. Dac lipsete, este considerat tipul implicit (int pentru unele compilatoare, void pentru altele); nume este un identificator care reprezint numele funciei; lista-parametrilor-formali conine parametrii formali sub forma:
[tip1 identificator1[,tip2 identificator[,tip3 identificator ]]]

13

Subprograme

Parametrii sunt separai prin virgul. La limit, lista poate fi vid. Pentru fiecare parametru trebuie specificat tipul, chiar dac mai muli parametri sunt de acelai tip (nu este posibil definirea de liste de parametri cu acelai tip). Pentru funciile care nu ntorc o valoare prin numele lor, tipul funciei va fi void sau va fi omis. Corpul este o instruciune compus: conine declaraiile locale i instruciunile executabile care implementeaz algoritmul. Corpul funciei se execut pn la ultima instruciune sau pn la executarea instruciunii return. Forma ei general este:
return(expresie); return expresie; return;

sau sau

Prima i a doua form sunt folosite n cazul funciilor care returneaz o valoarea prin numele lor. Prin executarea acestei instruciuni se evalueaz expresia, valoarea sa este atribuit funciei i se ncheie execuia funciei. A treia form este folosit n cazul funciilor care nu returneaz nicio valoare prin numele lor (poate chiar s lipseasc). Dac este prezent, efectul ei este ncheierea execuiei funciei. Tipul expresiei din instruciunea return trebuie s coincid cu tipul funciei. n limbajul C nu este admis imbricarea, adic definirea unui subprogram n cadrul altui subprogram i nu sunt permise salturi cu instruciunea goto (instruciune de salt necondiionat) n afara subprogramului. Declararea unui subprogram apare, n cadrul fiierului surs, naintea primului apel. Exist cazuri particulare n care, fie funciile se apeleaz unele pe altele (de exemplu, cazul recursivitii mutuale), fie definiia nu se afl n fiierul surs. Pentru a oferi compilatorului posibilitatea s efectueze verificarea validitii apelurilor, sunt prevzute declaraii ale subprogramelor fr definire. Aceste declaraii se numesc prototipuri i apar n afara oricrui corp de funcie. Sintaxa general este:
tip nume ([lista-parametri-formali]);

Prototipul este de fapt un antet de funcie dup care se scrie caracterul; (punct i virgul). Numele parametrilor pot lipsi, fiind suficient specificarea tipurilor lor. Prototipul trebuie inserat n program naintea primului apel al funciei. Domeniul de valabilitate a declaraiei unui subprogram este limitat la partea care urmeaz declaraiei din fiierul surs. Prototipurile funciilor standard se afl n fiiere header (cu extensia .h). Utilizarea unei funcii din bibliotec impune includerea fiierului asociat, cu directiva #include.

14

Programarea calculatoarelor

Fiind funcii, subprogramele C se apeleaz ca operanzi n expresii, prin numele funciei urmate de lista parametrilor reali. Expresia care conine apelul poate la limit s conin un singur operand i chiar s fie o instruciune de tip expresie. n aceste cazuri valoarea returnat de funcie se pierde, nefiind folosit n niciun fel. Exemple: S se scrie o funcie care calculeaz cel mai mare divizor comun dintre dou numere ntregi nenule, utiliznd algoritmul lui Euclid i un apelant pentru testare.
#include <stdio.h> /*definirea functiei cmmdc*/ int cmmdc(int a, int b) { int r,d=a,i=b; do {r=d%i; d=i; i=r;} while(r<>0); return i;} void main() { int n1,n2; printf("Numerele pentru care se va calcula cmmdc:"); scanf("%d%d",&n1,&n2); if(n1&&n2) printf("\ncmmdc=%d",cmmdc(n1,n2)); else printf("Numerele nu sunt nenule!"); }

Acelai exemplu folosind un prototip pentru funcia cmmdc:


#include <stdio.h> /* prototipul functiei cmmdc*/ int cmmdc(int, int); void main() { int n1,n2; printf("Numerele pentru care se va calcula cmmdc:"); scanf("%d%d",&n1,&n2); if(n1&&n2) printf("\ncmmdc=%d",cmmdc(n1,n2)); else printf("Numerele nu sunt nenule! "); } /*definirea functiei cmmdc*/ int cmmdc(int a, int b) { int r,d=a,i=b; do {r=d%i; d=i; i=r;} while(r<>0); return i; }

15

Subprograme

2.2 Transferul datelor ntre apelant i apelat


n practica programrii, s-au conturat dou posibiliti de transfer al datelor ntre apelant i apelat: prin parametri i prin variabile globale. Prin utilizarea variabilelor globale nu se face un transfer propriu-zis, ci se folosesc n comun anumite zone de memorie.

2.2.1 Transferul prin parametri


Principial, transferul se poate face prin valoare sau prin adres. n limbajul C este implementat numai transferul prin valoare (valoarea parametrului real este copiat n stiv, iar subprogramul lucreaz numai cu aceast copie). Operaiile efectuate asupra unui parametru formal scalar (care nu este masiv) nu modific, la ieirea din subprogram, parametrul real corespunztor. Transferul valorii este nsoit de eventuale conversii de tip realizate pe baza informaiilor de care dispune compilatorul despre subprogram. Dac prototipul precede apelul subprogramului i nu exist o sublist variabil de parametri, conversiile se fac similar atribuirilor. Exemplu:
tip_returnat nume(tip_parametru p);

p este transferat prin valoare

Folosind transferul prin valoare se pot transmite numai parametri de intrare n subprogram. Pentru a putea folosi parametri de ieire trebuie simulat transferul prin adres. n acest scop, se vor efectua explicit operaiile care se fac automat la transferul prin adres din alte limbaje: se transmite ca parametru adresa parametrului real, iar n subprogram se lucreaz cu indirectare. Exemplu:
tip_returnat nume(tip_parametru *p);

p este transferat prin valoare,

fiind adresa parametrului real. Pentru parametrii de tip masiv, simularea transferului prin adres se face n mod implicit, datorit modului de construire a masivelor n C: numele masivului este un pointer. La apel, n stiv se va transfera adresa masivului iar referirea elementelor se face automat prin calcul de adrese (vezi capitolul Tipuri dinamice de date. Pointeri). Urmtoarele prototipuri sunt echivalente:
tip_returnat nume1(float v[], int n); tip_returnat nume2(float *v, int n);

Exemple: 1. S se calculeze produsul scalar dintre doi vectori. a) rezultatul se ntoarce prin numele funciei:

16

Programarea calculatoarelor
float ps(float x[], float y[], int n) { int i,prod=0; for(i=0;i<n;prod+=x[i]*y[i++]); return prod; }

Apelul se realizeaz astfel:


float a[30],b[30]; int dimensiune; printf("Produsul scalar al vectorilor a si b este:%f", ps(a,b,dimensiune));

b)

rezultatul se ntoarce prin parametru, simulnd transferul prin adres:


void ps(float x[], float y[], int n, float *prod) { int i; *prod=0; for(i=0;i<n;(*prod)+=x[i]*y[i++]); }

Apelul se realizeaz astfel:


float a[30],b[30],produs_scalar; int dimensiune; ps(a,b,dimensiune,&produs_scalar); printf("Produsul scalar al vectorilor a si b este:%f", produs_scalar);

2. S se calculeze elementul maxim dintr-un vector i poziiile tuturor apariiilor acestuia (v, n sunt parametri de intrare; max, nr_ap, poz sunt parametri de ieire).
void maxim(float v[],int n,float *max,int poz[]) { int i; for(*max=v[0],i=1;i<n;i++) if(*max<v[i]) {*nr_ap=1;poz[0]=i; max=v[i];} else if(*max==v[i])poz[*nr_ap++]=i; } *nr_ap,int

Apelul se realizeaz astfel:


float a[30],el_max; int dimensiune,nr_aparitii,pozitii[30]; maxim(a,dimensiune,&max,&nr_aparitii,pozitii);

Antetul subprogramului este echivalent cu construcia


void maxim(float *v, int n, float *max, int *nr_ap, int *poz)

pentru care corpul subprogramului este acelai.

17

Subprograme

3.

S se calculeze produsul a dou matrice statice.


void produs(float a[][10],float b[][20], float c[][20],int m, int n,int p) { int i,j,k; for(i=0;i<m;i++) for(j=0;j<p;j++) for(c[i][j]=0,k=0;k<n;k++)c[i][j]+=a[i][k]*b[k][j];}

Observaie: Dei un tablou nu poate fi returnat ca tip masiv prin numele unei funcii, se pot scrie funcii care returneaz prin nume un tablou ca pointer deoarece numele tabloului este echivalent n C cu adresa sa (pointer la nceputul masivului). Unui astfel de masiv i se aloc memorie n funcia care l calculeaz. Numele su este returnat ca pointer la primul element al tabloului. Exemple: 1. S se calculeze produsul dintre o matrice i un vector.
#include<malloc.h> float * prod(float a[][30], float v[],int m, int n) { float *p;int i,j; p=(float *)malloc(sizeof(float)*m); for(i=0;i<m;i++) for(p[i]=0,j=0;j<n;j++) p[i]+=a[i][j]*v[j]; return p; }

Apelul se realizeaz astfel: a)


float a[20][30], b[30], *c; int m,n; c=prod(a,b,m,n);

Cu vectorul c se lucreaz n modul obinuit: elementele se refer prin indexare (c[i], i=0..m ). b)
float a[20][30], b[30]; int m,n;

Se lucreaz cu vectorul prod(a,b,m,n) elementele sale se refer ca prod(a,b,m,n)[i], i=0..m. Observaie: La fiecare referire de element se apeleaz i se execut funcia, ceea ce duce la consum mare i inutil de resurse. Este preferabil prima variant. 2. S se realizeze un program C pentru ridicarea unei matrice la o putere. Pentru aceasta se folosesc dou funcii care returneaz, prin pointeri, produsul a dou matrice (nmulire), respectiv ridicarea unei matrice la o putere (putere).

18

Programarea calculatoarelor
#include<stdio.h> #include<conio.h> #include<alloc.h> float** inmultire(float **a,float **b,int n) { int i,j,k; float **c; c=(float **)malloc(n*sizeof(float *)); for(i=0;i<n;i++) *(c+i)=(float *)malloc(n*sizeof(float)); for(i=0;i<n;i++) for(j=0;j<n;j++) for(k=0,c[i][j]=0;k<n;c[i][j]+=a[i][k]*b[k++][j]); return c; } float** putere(float **a,int p,int n) { float **c,**ap;int l,m,i; ap=(float **)malloc(n*sizeof(float *)); for(i=0;i<n;i++) *(ap+i)=(float *)malloc(n*sizeof(float)); for(l=0;l<n;l++) for(m=0;m<n;ap[l][m]=a[l][m],m++); for(i=0;i<p-1;i++) {c=inmultire(a,ap,n); for(l=0;l<n;l++) for(m=0;m<n;ap[l][m]=c[l][m],m++); } return ap; } void main() { int i,j,p,n,l,m; float **a,**ap,f; clrscr(); printf("\n n="); scanf("%i",&n); a=(float **)malloc(n*sizeof(float *)); for(i=0;i<n;i++) *(a+i)=(float *)malloc(n*sizeof(float)); for(i=0;i<n;i++) for(j=0;j<n;j++) {scanf("%f ",&f); *(*(a+i)+j)=f; } scanf("%i",&p); ap=putere(a,p,n); for(i=0;i<n;i++) {for(j=0;j<n;j++) printf("%f ",*((*(ap+i)+j))); printf("\n"); } getch(); }

19

Subprograme

Simularea transmiterii parametrilor prin adres


Limbajul C permite transmiterea parametrilor numai prin valoare (la apelul subprogramelor se copiaz n stiv valoarea parametrului real i subprogramul lucreaz cu aceast copie). Subprogramul nu poate modifica valoarea parametrului din apelator. Dac parametrul formal este un masiv, el este de fapt un pointer (adresa de nceput a masivului). Folosind aceast proprietate, se pot modifica valorile elementelor masivului, iar modificrile se vor propaga n blocul apelator, deoarece valoarea care se copiaz n stiv este adresa de nceput a masivului. Masivul rmne n memoria principal i poate fi modificat prin intermediul adresei sale de nceput. Astfel se poate simula transmiterea parametrilor prin adres folosind pointerii. Subprogramul poate modifica valori care s se propage n apelator. n acest scop se transmite ca parametru un pointer spre variabila cu care trebuie s lucreze subprogramul apelat, care va lucra n mod explicit cu pointerul. Un exemplu n acest sens este funcia de citire a datelor de la tastatur. Parametrii acestei funcii sunt adresele variabilelor ale cror valori trebuie citite. Exemplu: 1. Fie un subprogram care calculeaz suma elementelor unui vector v de lungime n.
void suma(float s, float v[], int n) { int i; for(s=0,i=0;i<n;i++) s+=v[i]; }

Subprogramul suma calculeaz suma elementelor vectorului, dar nu poate fi folosit de apelator deoarece valoarea sumei este cunoscut numai n interiorul funciei (parametrul a fost transmis prin valoare). n apelator valoarea variabilei corespunztoare parametrului formal s nu va fi modificat. Pentru ca subprogramul s fie utilizabil, trebuie ca parametrul s s fie un pointer spre variabila n care se va memora suma elementelor vectorului:
void suma(float* s, float v[], int n) { int i; for(s=0,i=0;i<n;i++) *s+=v[i]; }

La apelul funciei, primul parametru actual este adresa variabilei n care se memoreaz suma:
void main() { float x, m[20]; // suma(&x, m, n); // } int n;

20

Programarea calculatoarelor

2. S se realizeze un subprogram care citete de la tastatur o valoare ntreag care aparine unui interval dat.
void citire(int a, int b, int* x) { do printf("Introduceti numarul: "); scanf("%d", x); while((*x<=a)||(*x>=b)); }

2.2.2 Transferul prin variabile globale


Variabilele globale se declar n afara funciilor. Ele pot fi referite din orice alte funcii. De aceea, schimbul de valori ntre apelant i apelat se poate realiza prin intermediul lor. Variabilele declarate ntr-o funcie se numesc locale (din clasa automatic) i pot fi referite numai din funcia respectiv. Domeniul de valabilitate a unei variabile locale este blocul (funcia sau instruciunea compus) n care a fost definit. Exemplu:
#include <stdio.h> int a; float z(char b) { int b; } main() { int c; { /* instructiunea compusa r */ int d; } }

Domeniile de valabilitate a referirilor variabilelor declarate sunt: b poate fi referit doar n funcia z; c poate fi referit doar n funcia main; d poate fi referit doar n instruciunea compus r; a este global i poate fi referit de oriunde.

2.3 Pointeri spre funcii


n limbajul C, numele unei funcii este un pointer care indic adresa de memorie unde ncepe codul executabil al funciei. Aceasta permite transmiterea funciilor ca parametri n subprograme precum i lucrul cu tabele de funcii. n acest scop trebuie parcurse urmtoarele etape: a. Declararea unei variabile de tip procedural (pointer spre funcie):
tip_rezultat (*nume_var)(lista_parametri_formali);

21

Subprograme

unde nume_var este o variabil de tip procedural i are tipul pointer spre funcie cu parametrii lista_parametri_formali i care returneaz o valoare de tipul tip_rezultat. Lui nume_var i se poate atribui ca valoare doar numele unei funcii de prototip:
tip_rezultat nume_f(lista_parametrilor_formali);

b. Descrierea funciei care utilizeaz parametrii procedurali:


void f(,tip_rezultat (*nume)(lista_parametrilor_formali),) { tip_rezultat x; x=(*nume)(lista_parametrilor_actuali); }

unde nume este parametrul formal de tip procedural. c. Apelul funciei cu parametri procedurali:
tip_rezultat nume_functie(lista_parametrilor_formali) { } void main() { f(, nume_functie, ); }

Exemplu: Fie o funcie care efectueaz o prelucrare asupra unui vector. Nu se cunoate apriori tipul prelucrrii, aceasta fiind descris de o alt funcie, primit ca parametru. Pot exista mai multe funcii care descriu prelucrri diferite asupra unui vector i oricare din ele poate fi transmis ca parametru.
float suma(float *v, int n) { for(int i=0, float s=0; i<n; i++) s+=v[i]; return(s); } float media(float *v, int n) { for(int i=0, float m=0; i<n; i++) m+=v[i]; m/=n; return(m); } void functie( float vec[],int dim,float(* prelucrare)(float*, int)) {printf("Rezultatul prelucrarii este: %5.2f\n",(*prelucrare)(vec,dim)); }

Apelul se realizeaz prin transmiterea ca parametru real a funciei potrivite prelucrrii dorite.

22

Programarea calculatoarelor
void main() { float tab[10]; int m,i; printf("Numarul de elemente(<10): "); scanf("%d ", &m); for(i=0,i<m;i++) {printf("a(%d)=",i); scanf("%f",&tab[i]); } printf("Se calculeaza suma elementelor\n"); functie(tab, m, suma); printf("Se calculeaza media elementelor\n"); functie(tab, m, media); return; }

Limbajul C permite lucrul cu variabile de tip pointer, care conin adresa de nceput a unei funcii (a codului su executabil). Aceste variabile permit transferul adresei funciei asociate ca parametru, precum i apelul funciei prin intermediul pointerului su. Urmtoarea declaraie definete pointer_f ca pointer spre funcia cu rezultatul tip_returnat i parametrii parametri.
tip_returnat (*pointer_f)([parametri])

Observaie: Nu trebuie s se confunde un pointer la o funcie cu o funcie care are ca rezultat un pointer, cu sintaxa de forma tip_returnat *pointer_f([parametri]). Adresa unei funcii se obine prin simpla specificare a identificatorului acesteia (fr specificarea parametrilor sau parantezelor) i poate fi atribuit unui pointer spre funcie cu rezultat i parametri compatibili. Pointerul poate fi folosit ulterior pentru apelul funciei sau transmis ca parametru real n apelul unui subprogram care conine, n lista parametrilor formali, un pointer la un prototip de funcie compatibil. Exemple: 1. S se aproximeze soluia unei ecuaii de forma f(x)=0 prin metoda biseciei.
#include<stdio.h> #include<conio.h> #include<math.h> /*prototipul functiei bisectie*/ void bisectie(float,float,float(*f)(float),float,long,int *,float *); /*prototipul functiei pentru care se aplica metoda bisectiei*/ float fct(float); /* functia principala*/ void main() { float a,b,eps,x;

23

Subprograme
int cod; long n; float (*functie)(float); clrscr(); printf("Introduceti capetele intervalului:"); scanf("%f%f",&a,&b); printf("\nEroarea admisa:"); scanf("%f",&eps); printf("\nNumarul maxim de iteratii:"); scanf("%li",&n); functie=fct; bisectie(a,b,functie,eps,n,&cod,&x); if(!cod) printf("\nNu se poate calcula solutia aproximativa"); else printf("\n Solutia aproximativa este: %f",x); } /*descrierea functiei pentru care se aplica metoda bisectiei*/ float fct(float x) { return x*x*x-3*x+14; } /*functia ce implementeaza metoda bisectiei*/ void bisectie(float a,float b,float (*f)(float),float eps,long n, int *cod,float *x) { int gata=0; long c; for(c=0;(c<n)&&!gata;c++) { *x=(a+b)/2; gata=fabs(*x-a)<eps; if ((*f)(*x)*(*f)(a)<0) b=*x; else a=*x; } *cod=gata; }

2. S se sorteze un ir cu elemente de un tip neprecizat, dar pe care se poate defini o relaie de ordine (de exemplu numeric, ir de caractere, caracter). Metoda aleas spre exemplificare este sortarea prin selecie direct. Un subprogram de sortare care s nu depind de tipul elementelor i de criteriul de sortare considerat trebuie s aib ca parametri formali: - vectorul de sortat, ca pointer la tipul void, asigurndu-se astfel posibilitatea realizrii operaiei de schimbare a tipului (cast) n funcie de necesitile ulterioare (la momentul apelului se poate realiza modificarea tipului void * n tip_element *, unde tip_element reprezint tipul elementelor vectorului de sortat); - dimensiunea vectorului de sortat i numrul de octei din reprezentarea tipului elementelor vectorului; - pointerul la o funcie de comparare, cu argumente de tip void *, care s permit la apel att schimbarea de tip, ct i descrierea efectiv a relaiei de ordine.

24

Programarea calculatoarelor

Cum tipul elementelor vectorului nu este cunoscut la momentul descrierii procedurii de sortare, operaia de atribuire nu poate fi folosit, ea fiind nlocuit de o funcie de copiere a unui numr prestabilit de octei, de la o adres surs la una destinaie. O astfel de funcie exist n biblioteca mem.h, sintaxa ei fiind:
void *memmove(void *destinaie, const void *surs, unsigned n)

Pentru accesarea adresei elementului de rang i din vector se folosete formula (char *)v+i*nr_octeti. Fiierul surs care conine funcia de sortare descris anterior este:
//fisier exp_tip.cpp #include <mem.h> include<alloc.h> void sort(void *v, int n, int dim, int (*compara)(const void * ,const void * )) { int i,j; void *aux; aux=malloc(dim); for(i=0;i<n-1;i++) for(j=i+1;j<n;j++) if((*compara)((char*)v+dim*i,(char*)v+dim*j)) { memmove(aux,(char*)v+dim*i,dim); memmove((char*)v+dim*i,(char*)v+dim*j,dim); memmove((char*)v+dim*j,aux,dim); } free(aux); }

Exemplu de apel pentru un vector de numere reale:


#include <stdio.h> #include<conio.h> #include "exp_tip.cpp" int compara(const void *a, const void *b) { if(*(float *)a>*(float *)b)return 1; else return 0; } void main() { float vect[20]; int n,i; clrscr(); printf("Dimensiunea vectorului:"); scanf("%d",&n); printf("\nElementele:"); for(i=0;i<n;i++) scanf("%f",&vect[i]); sort(vect,n,sizeof(float),compara); printf("\nElementele sortate:"); for(i=0;i<n;i++) printf("\n%f",vect[i]); getch(); }

25

Subprograme

Exemplu de apel pentru un vector de cuvinte (iruri de caractere):


#include <stdio.h> #include <string.h> #include<conio.h> #include "exp_tip.cpp" int compara(const void *a, const void *b) { if(strcmp((char *)a, (char *)b)>0)return 1; else return 0; } void main() { typedef char cuvant[10]; cuvant vect[20]; int n; clrscr(); printf("Dimensiunea vectorului de cuvinte:"); scanf("%d",&n); printf("\nCuvintele:"); for(int i=0;i<n;i++) scanf("%s",&vect[i]); sort(vect,n,10,compara); printf("\nCuvintele sortate:"); for(i=0;i<n;i++) printf("\n%s",vect[i]); getch();}

2.4 Funcii cu numr variabil de parametri


Bibliotecile limbajului C conin subprograme standard cu numr variabil de parametri. Limbajul C permite definirea funciilor utilizator cu numr variabil de parametri, prin utilizarea unui set de macrodefiniii, declarate n biblioteca stdarg.h, care permit accesul la lista de parametri. Fiierul stdarg.h declar tipul va_list i funciile va_start, va_arg i va_end, n care: - va_list este un pointer ctre lista de parametri. n funcia utilizator corespunztoare trebuie declarat o variabil (numit n continuare ptlist) de acest tip, care va permite adresarea parametrilor; - va_start iniializeaz variabila ptlist cu adresa primului parametru din sublista variabil. Prototipul acestei funcii este:
void va_start(va_list ptlist, ultim)

unde ultim reprezint numele ultimului parametru din sublista variabil. n unele situaii (vezi exemplele) se transfer n acest parametru numrul de variabile trimise.

26

Programarea calculatoarelor

- va_arg ntoarce valoarea parametrului urmtor din sublista variabil. Prototipul acestei funcii este:
tip_element va_arg(va_list ptlist, tip_element)

unde tip_element este tipul elementului transferat din list. Dup fiecare apel al funciei va_arg, variabila ptlist este modificat astfel nct s indice urmtorul parametru. - va_end ncheie operaia de extragere a valorilor parametrilor i trebuie apelat nainte de revenirea din funcie. Prototipul funciei este:
void va_end(va_list ptlist)

Problema numrului de parametri i tipurilor lor este tratat de programator. Exemple: 1. S se calculeze cel mai mare divizor comun al unui numr oarecare de numere ntregi.
#include<stdio.h> #include<conio.h> #include<stdarg.h> int cmmdc_var(int,...); int cmmdc(int, int); void main() { int x,y,z,w; clrscr(); scanf("%d%d%d%d",&x,&y,&z,&w); printf("\nCmmdc al primelor 3 numere:%d\n",cmmdc_var(3,x,y,z)); printf("\nCmmdc al tuturor numerelor:%d\n",cmmdc_var(4,x,y,z,w)); } //cel mai mare divizor comun a doua numere int cmmdc(int x,int y) { int d=x,i=y,r; do{ r=d%i; d=i;i=r; } while(r); return d; } //cel mai mare divizor comun a nr numere int cmmdc_var(int nr,...) { va_list ptlist; /*initializarea lui ptlist cu adresa de parametri*/ va_start(ptlist,nr); //extragerea primului parametru, de tip int x=va_arg(ptlist,int); for(int i=1;i<nr;i++)

inceput a listei de

27

Subprograme
{ //extragerea urmatorului element din lista de parametri y=va_arg(ptlist,int); z=cmmdc(x,y);x=z;

} va_end(ptlist); return x; }

2. S se interclaseze un numr oarecare de vectori. Spre deosebire de exemplul anterior, n care n lista de parametri a funciei cu numr oarecare de parametri figurau elemente de acelai tip (int), acest exemplu ilustreaz modul de transfer i acces la elemente de tipuri diferite. Funciei intre_var i se transmit la apel vectorul rezultat, iar pentru fiecare vector de interclasat, adresa de nceput (pointer la tipul double) i numrul de elemente (int). Numrul parametrilor din lista variabil este, n acest, caz 2*numrul de vectori de interclasat.
#include<stdarg.h> #include<stdio.h> #include<conio.h> void inter(double *,int,double *,int,double *); void inter_var(double *,int nr,...); void main() { int n1,n2,n3,n4; double x1[10],x2[10],x3[10],x4[10],z[50]; clrscr(); scanf("%d%d%d%d",&n1,&n2,&n3,&n4); for(int i=0;i<n1;i++) scanf("%lf",&x1[i]); for(i=0;i<n2;i++) scanf("%lf",&x2[i]); for(i=0;i<n3;i++) scanf("%lf",&x3[i]); for(i=0;i<n4;i++) scanf("%lf",&x4[i]); inter_var(z,4,x1,n1,x2,n2); printf("\nRezultatul interclasarii primilor 2 vectori\n"); for(i=0;i<n1+n2;i++) printf("%lf ",z[i]); inter_var(z,8,x1,n1,x2,n2,x3,n3,x4,n4); printf("\nRezultatul interclasarii celor 4 vectori\n"); for(i=0;i<n1+n2+n3+n4;i++) printf("%lf ",z[i]); }

28

Programarea calculatoarelor
void inter(double *x, int n1, double *y, int n2, double *z) { int i,j,k; for(i=0,j=0,k=0;(i<n1)&&(j<n2);k++) if(x[i]<y[j]) z[k]=x[i++]; else z[k]=y[j++]; if(i<n1) for(;i<n1;z[k++]=x[i++]); else for(;j<n2;z[k++]=y[j++]); } void inter_var(double *z,int nr,...) { va_list ptlist; double *x,*y,x1[100]; int n1,n2; /*initializarea lui ptlist cu adresa de parametri*/ va_start(ptlist,nr); //extragerea primului vector x=va_arg(ptlist,double *); //extragerea dimensiunii lui n1=va_arg(ptlist,int); for(int j=0;j<n1;j++)x1[j]=x[j]; for(int i=1;i<(int)(nr/2);i++) { //extragerea urmatorului vector y=va_arg(ptlist,double *); //extragerea numarului sau de elemente n2=va_arg(ptlist,int); inter(x1,n1,y,n2,z); for(j=0;j<n1+n2;j++) x1[j]=z[j];n1+=n2; } va_end(ptlist); }

inceput

listei

de

29

3 Subprograme recursive

Recursivitatea este tehnica de programare n care un subprogram se autoapeleaz. Limbajul C face parte din clasa limbajelor de programare care admit scrierea de funcii recursive. n continuare sunt prezentate cteva exemple simple de subprograme C prin intermediul crora sunt calculate recursiv valorile k n! , C n , f g f , unde n , k N i f, g funcii, f , g : R R. De asemenea, este ilustrat maniera n care sunt efectuate apelurile recursive i tratarea condiiilor terminale.

3.1 Calcul recursiv


Calculul valorii n! pentru n dat poate fi efectuat pe baza formulei recursive

Fie fact(n) funcia C care calculeaz n!. Dac n 1, evaluarea lui fact(n) rezult prin multiplicarea cu n a valorii calculate de apelul fact(n-1), cu fact(0)=1. Cu alte cuvinte, apelul funciei fact(n) realizeaz calculul imediat dac n=0, altfel presupune un nou apel al aceleiai funcii pentru valoarea argumentului decrementat. Cazurile n care este posibil evaluarea imediat se numesc condiii terminale. n limbajul C, funcia fact este
long fact(unsigned n) { if (!n) return 1; return n*fact(n-1); }

1, n = 0 n! = . n(n 1)! , n > 0

Utilizarea formulei

k Cn =

n! k ! (n k )!

pentru calculul combinrilor

( n , k N date) este ineficient i uneori imposibil deoarece n!, pentru n 13 nu

30

Subprograme recursive
k poate fi reprezentat n calculator ca dat de un tip ntreg, chiar dac numrul C n este relativ mic i poate fi reprezentat prin intermediul unui tip ntreg. Pe baza k k k 1 k relaiei de recuren C n = C n 1 + C n 1 , valoarea C n poate fi calculat astfel. Fie k comb(n,k) funcia care calculeaz C n . Conform relaiei de recuren, dac n k 1, atunci evaluarea corespunztoare apelului comb(n,k) revine la nsumarea rezultatelor obinute prin apelurile comb(n-1,k) i comb(n-1, k-1), unde comb(n,0)=1, n 0. Dac evalurile comb(n-1,k) i comb(n-1, k-1) sunt realizate n acelai mod, rezult c apelul comb(n,k) va determina o secven de apeluri ale aceleiai funcii pentru valori ale argumentelor din ce n ce mai mici, pn cnd este ndeplinit una din condiiile terminale comb(n,0)=1, comb(k,k)=1. k Soluia recursiv a evalurii C n este:

long comb(unsigned n, unsigned k) { if (k>n) return 0; if ((k==0)||(k=n)) return1; return comb(n-1,k)+comb(n-1,k-1); }


k Funciile C recursive pentru calculul n! , C n , unde n , k N realizeaz apeluri recursive directe. Schema unui apel recursiv poate fi descris astfel: se verific dac este ndeplinit cel puin una din condiiile terminale; dac este ndeplinit o condiie terminal, atunci calculul este ncheiat i controlul este returnat unitii apelante, n caz contrar este iniiat calculul pentru noile valori ale parametrilor, calcul care presupune unul sau mai multe apeluri recursive. Mecanismul prin care este efectuat apelul unui subprogram se bazeaz pe utilizarea stivei memoriei calculatorului. Fiecare apel determin introducerea n stiv a valorilor parametrilor formali, a adresei de revenire i a variabilelor locale. La momentul execuiei, aceste informaii sunt extrase cu eliminare din stiv, eliberndu-se spaiul ocupat. n cazul subprogramelor recursive, mecanismul funcioneaz astfel: este generat un numr de apeluri succesive cu ocuparea spaiului din stiv necesar efecturii apelurilor pn la ndeplinirea unei condiii terminale; apelurile sunt executate n ordinea invers celei n care au fost generate, iar operaia de inserare n stiv poate produce depirea spaiul de memorie rezervat. De exemplu, n cazul apelului fact(3), secvena de apeluri recursive este: fact(2), fact(1), fact(0). n continuare execuia determin fact(0)=1, fact(1)=1*fact(0)=1, fact(2)=2*fact(1)=2, fact(3)=3*fact(2)=6. Evoluia determinat de apelul fact(3) n stiv este ilustrat n figurile 3.1.a i 3.1.b, unde () reprezint adresa de revenire n punctul de unde a fost efectuat apelul fact(3). Apelurile recursive ale unui subprogram S1 pot fi i indirecte, n sensul c este efectuat un apel al unui alt subprogram S2 i S2 iniiaz un apel al lui S1. De

31

Programarea calculatoarelor

exemplu, calculul valorilor funciei h=fgf , unde f,g:RR sunt funcii date poate fi descris astfel. Pentru funciile f, g definite prin:

3 Fact=3*Fact(2)

2 Fact=2*Fact(1) 3 (o) Fact=3*Fact(2) (o)

1 Fact=1*Fact(0) 2 Fact=2*Fact(1)

0 Fact=1 1 Fact=1*Fact(0) 2 Fact=2*Fact(1)

3 Fact=3*Fact(2)

3 (o) Fact=3*Fact(2) (o)


Figura 3.1.a Evoluia n stiv pn la verificarea condiiei terminale n=0

32

Subprograme recursive

1 Fact=1 2 Fact=2*Fact(1)

2 Fact=2 3 Fact=3*Fact(2)

3 Fact=3*Fact(2) (o)

3 Fact=6

(o)
Figura 3.1.b Eliberarea stivei dup execuia determinat de condiia terminal

2 x 3 + 1, x < 5 2 5 x 3 x + 2 , x 1 f (x ) = x 4 + 2 , 5 x < 8 , g (x ) = 3 x x + 5, x > 1 3 , x > 8


funciile C pentru calculul h=fgf pot fi descrise astfel,
float f(float x) { if (x<5) return 2*pow(x,3)+1; if (x<8) return pow(x,4)+2; return 3; } float g(float x) { if (x<=1) return 5*x*x-3*x+2; return pow(x,3)-x+5; } float h(float x) { return f(g(f(x))); }

33

Programarea calculatoarelor

3.2 Aplicaii cu subprograme recursive


1. S se scrie o funcie C care citete o secven oarecare de cuvinte a1, a2, .., an terminat cu simbolul # i afieaz anan-1a1. Pentru rezolvarea problemei se utilizeaz funcia recursiv Scrie.
void Scrie() { char cuvant[100]; scanf(%s,&cuvant); if (strcmp(cuvant,#)) { Scrie; printf( %s\n,cuvant); } }

2. Calculul valorii funciei Ackermann. Funcia Ackermann este definit pentru argumentele m,n numere naturale prin

n + 1, m = 0 a (m , n ) = a (m 1,1), n = 0 a (m 1, a (m , n 1)), altfel


Funcia C Ackermann calculeaz valoarea funciei a pentru m, n parametri naturali dai.
long Ackermann(unsigned m, unsigned n) { if (!m) return n+1; if (!n) return Ackermann(m-1,1); return Ackermann(m-1,Ackermann(m,n-1)); }

3. Problema calculului celui mai mare divizor comun dintre dou numere naturale a i b poate fi rezolvat recursiv, conform definiiei urmtoare,

a , a = b (a ,b ) = ( a b ,b ), a > b ( a ,b a ), b > a
Funcia C cmmdc(a,b) este,
long cmmdc(long a, long b) { if (a==b) return a; if (a>b) return cmmdc(a-b,b); return cmmdc(a,b-a); }

34

Subprograme recursive

4. Problema turnurilor din Hanoi ilustreaz foarte bine avantajele recursivitii. Problema poate fi enunat astfel: se presupune c exist trei tije a, b, c, pe tija a fiind plasate n discuri de diametre diferite n ordinea descresctoare a acestora. Se cere ca cele n discuri de pe tija a s fie deplasate pe tija c astfel nct s fie ndeplinite condiiile: la fiecare mutare este deplasat unul dintre discurile aflate pe poziia superioar pe una din tije; oricare din discuri poate fi aezat numai pe un disc de diametru mai mare; tija b poate fi folosit pentru deplasri intermediare.
Notnd cu P(n,a,c) problema transferului celor n discuri de pe tija a pe tija c, pentru rezolvarea ei putem raiona n modul urmtor. Dac s-a rezolvat problema P(n-1,a,b), atunci discul de diametru maxim care se afl nc pe tija a este deplasat pe tija c i n continuare se rezolv problema P(n-1,b,c). Soluia recursiv este prezentat n funcia Hanoi. Exemplu Presupunnd c discurile sunt numerotate n ordinea cresctoare a diametrelor cu etichetele 1, 2, 3, o soluie a problemei pentru n=3 poate fi descris astfel. Tija a 1 2 3 2 3 3 3 Tija b Tija c Mutarea efectuat ac ab cb ac ba bc ac

1 2 1 2 1 2 2 1 3 3 2 3 1 2 3

1 1

#include <stdio.h> #include <conio.h> void Hanoi(unsigned n,unsigned a, unsigned b,unsigned c) {

35

Programarea calculatoarelor
if(n>0) { Hanoi(n-1,a,c,b); printf("Transfer disc de pe tija %u pe tija %u\n",a,b); Hanoi(n-1,c,b,a); } } void main() { unsigned n,a,b,c; clrscr(); printf("n=");scanf("%u",&n); Hanoi(n,1,2,3);getch(); }

5. Cutarea n vectori sortai (cutarea binar) Fie v este un vector de numere reale sortat cresctor i k este un numr real dat. Problema este de a identifica (dac exist) o valoare poz, astfel nct v[poz]=k. Rezolvarea ei pornete cu luarea n considerare a ntregului vector v i determinarea poziiei mijlocului m = . Dac v[m]=k, atunci poz:=m. Dac 2 v[m]>k, atunci se procedeaz n acelai mod cu vectorul format din primele m componente din v, altfel cu cel format din componentele v[m+1],,v[n-1]. Se genereaz astfel subvectori din ce n ce mai mici pn cnd valoarea k este gsit sau pn cnd nu mai poate fi generat un nou subvector.
#include <stdio.h> #include <conio.h> int cauta_binar(float *,int,int,float); void main() { clrscr(); printf("Dimensiunea vectorului:"); int n; scanf("%i",&n); printf("Elementele vectorului\n"); float v[100]; for(unsigned i=0;i<n;i++) scanf("%f",&v[i]); printf("Cheia de cautare:"); float k; scanf("%f",&k); int c=cauta_binar(v,0,n-1,k); if(c==-1) printf("Cheia nu a fost gasita"); else printf("Cheia pe pozitia:%i",c); getch(); } int cauta_binar(float *v,int li,int ls,float k) { if(li>ls) return -1; int mij=(li+ls)/2; if(v[mij]==k)

36

Subprograme recursive
return mij; if(v[mij]>k) return cauta_binar(v,li,mij-1,k); return cauta_binar(v,mij+1,ls,k); }

6. Sortarea cresctoare prin inserare Pentru sortarea cresctoare a unei secvene de numere reale se poate raiona astfel: dac P(n) este problema sortrii cresctoare a secvenei a1, a2,,an i P(n-1) este problema sortrii primelor n-1 componente, atunci soluia problemei P(n) rezult din soluia problemei P(n-1) prin inserarea lui an n soluia problemei P(n-1). Fiecare problem intermediar P(k), k = 2 ,..., n este rezolvat aplicnd aceeai metod P(1) fiind o problem gata rezolvat (condiie terminal). Funcia insera realizeaz inserarea valorii x n vectorul v n poziia corect. Funcia recursiv inssort realizeaz sortarea vectorului cu n componente prin inserie.
void insera(float *v,int *n,float x) { for(int i=0;(i<*n)&&(x>v[i]);i++); for(int j=*n;j>=i+1;j--)v[j]=v[j-1]; v[i]=x;(*n)++; } void inssort(float *v,int n) { if(n) { inssort(v,n-1);int m=n-1; insera(v,&m,v[n-1]); } }

7. Pot fi realizate desene prin compunerea ntr-o manier recursiv a unor figuri geometrice primitive. Compunerea const n repetarea primitivelor considerate i a rezultatelor obinute prin rotirea lor ntr-un sens sau cellalt. Astfel, dac mulimea de primitive H0 const dintr-un punct i pentru compunere este considerat un segment de lungime h, atunci: H1 rezult din patru exemple (cpii, realizri, instane, clone) de primitive din H0 unite prin segmente de lungime h; H2 rezult din 16 exemple din H0 unite prin 15 segmente de lungime h/2 .a.m.d. De asemenea, H2 se poate obine prin interconectarea a patru cpii ale lui H1 rotite cu unghiuri drepte i prin interconectarea punctelor izolate prin segmente de aceeai lungime. Generaliznd, o curb Hn rezult din patru cpii ale unei curbe Hn-1, punctele izolate fiind unite prin segmente de lungime hn=h/2n. Curbele rezultate se numesc curbele Hilbert Hi, i 0.

37

Programarea calculatoarelor

H1

H2

H3

Dac cele patru pri ale unei curbe Hilbert Hk sunt notate A, B, C, D i se reprezint prin sgei rutinele care deseneaz segmentele care le interconecteaz, atunci rezult urmtoarele scheme recursive.

A: D A A B
B: CBB A
A: D A A B

C: BC C D D: A D DC
Prin executarea urmtoarei surse C sunt obinute curbele Hilbert H4.
#include #include #include #include <stdio.h> <graphics.h> <stdlib.h> <conio.h>

const n=5; const h0=480; int int int int void void void void i=0; h; x,y,x0,y0,gm; gd=DETECT; A(int); B(int); D(int); C(int);

void main() { clrscr(); initgraph(&gd,&gm,"D:\BC\BGI"); setbkcolor(0); setcolor(4); h=h0;y0=x0=h/2; do{ i++;h/=2; x0+=h/2;y0+=h/2; x=x0;y=y0;moveto(x,y); A(i); }

38

Subprograme recursive
while(i<n); getch(); closegraph(); } void A(int i) { if (i>0) { D(i-1);x-=h;lineto(x,y); A(i-1);y-=h;lineto(x,y); A(i-1);x+=h;lineto(x,y); B(i-1); } } void B(int i) { if (i>0) { C(i-1);y+=h;lineto(x,y); B(i-1);x+=h;lineto(x,y); B(i-1);y-=h;lineto(x,y); A(i-1); } } void C(int i) { if (i>0) { B(i-1);x+=h;lineto(x,y); C(i-1);y+=h;lineto(x,y); C(i-1);x-=h;lineto(x,y); D(i-1); } } void D(int i) { if (i>0) { A(i-1);y-=h;lineto(x,y); D(i-1);x-=h;lineto(x,y); D(i-1);y+=h;lineto(x,y); C(i-1); } }

39

Programarea calculatoarelor

Curba Hilbert obinut este

8. n cazul curbelor Hilbert, toate unghiurile determinate de segmentele care unesc punctele sunt de msur 900. Dac se consider ca valori pentru msurile unghiurilor determinate de aceste segmente 450, 900, 1350, rezult curbele Sierpinski Sn, n 1. Curba Sierpinski S2 este,

Recursia pentru obinerea curbelor Sierpinski poate fi descris astfel. S: A A: A B C BD D A

40

Subprograme recursive

B: B C: C D: D

C A DB A C

B C D

unde sgeile duble indic segmente de lungime 2h. Urmtorul program deseneaz curbele Sierpinski S4.
#include #include #include #include <stdio.h> <graphics.h> <stdlib.h> <conio.h>

const n=4; const h0=412; int i=0; int h; int x,y,x0,y0,gm; int gd=DETECT; void A(int); void B(int); void D(int); void C(int); void main() { clrscr(); initgraph(&gd,&gm,"d:\bc\bgi"); setbkcolor(15); setcolor(8); h=h0/4; x0=2*h; y0=3*h; do{ i++; x0-=h;h/=2;y0+=h; x=x0;y=y0; moveto(x,y); A(i);x+=h;y-=h;lineto(x,y); B(i);x-=h;y-=h;lineto(x,y); C(i);x-=h;y+=h;lineto(x,y); D(i);x+=h;y+=h;lineto(x,y);} while(i!=n); getch(); closegraph(); } void A(int i) { if (i>0) { A(i-1);x+=h;y-=h; lineto(x,y); B(i-1);x+=2*h; lineto(x,y);

41

Programarea calculatoarelor
D(i-1);x+=h;y+=h; lineto(x,y); A(i-1); } } void B(int i) { if (i>0) { B(i-1);x-=h;y-=h; lineto(x,y);C(i-1); y-=2*h; lineto(x,y); A(i-1);x+=h;y-=h; lineto(x,y); B(i-1); } } void C(int i) { if (i>0) { C(i-1);x-=h;y+=h; lineto(x,y); D(i-1);x-=2*h; lineto(x,y); B(i-1);x-=h;y-=h; lineto(x,y); C(i-1); } } void D(int i) { if (i>0) { D(i-1);x+=h;y+=h; lineto(x,y); A(i-1);y+=2*h; lineto(x,y); C(i-1);x-=h;y+=h; lineto(x,y); D(i-1); } }

Rezultatul execuiei programului este prezentat n urmtoarea figur.

42

Subprograme recursive

43

4 Tipul de dat articol

Articolul este o structur de date eterogen, cu acces direct la elementele sale, ntre care exist o relaie de ordine ierarhic.

4.1 Caracteristici generale i mod de declarare


Articolul poate fi reprezentat sub form de arbore, ale crui noduri sunt asociate componentelor structurii. Componentele de pe ultimul nivel sunt scalare i se numesc date elementare sau cmpuri. Datele de pe celelalte niveluri, denumite date de grup, se constituie prin agregarea datelor de pe nivelurile inferioare. Data de grup de cel mai nalt nivel (rdcina arborelui) corespunde articolului n ansamblu. Conceptual, datele de grup de pe diverse niveluri au aceleai proprieti ca i articolul, ceea ce permite ca aceast structur s fie construit recursiv, prin descompunerea n structuri cu aceleai proprieti (figura 4.1). Declararea mpreun a tipului articol i a variabilelor de acest tip se realizeaz conform sintaxei:
struct tip_articol{lista_campuri} var1,var2,,varn;

unde tip_articol este identificatorul asociat tipului articol, iar var1, var2,, varn sunt identificatorii asociai variabilelor de tipul articol declarat. Parametrii declaraiei pot lipsi (dar nu toi deodat). Dac lipsesc parametrii var1, var2,, varn, atunci tip_articol trebuie s fie prezent, fiind numai o declarare explicit de tip nou, utilizabil ulterior la alte declarri. Dac lipsete tip_articol, atunci trebuie s fie prezent lista de variabile (nevid), caz n care este vorba de o declarare de variabile de tip articol, fr ns a declara i un tip utilizator nou. n continuare, tip_articol este un tip nou de date, iar var1, var2,, varn sunt variabile de tipul tip_articol. Variabilele pot fi declarate i ca masive, ale cror

44

Tipul de dat articol

elemente sunt de tip articol: var1[dim1][dim2][dimn].


DATA ZI LUNA AN a) PERSOANA dat de grup (articol) dat de grup (articol) NUME ADRESA ZI b) DATA NATERII LUNA AN dat de grup (articol) date elementare

date elementare

Figura 4.1 Exemple de structuri de articole O variabil de tip articol poate fi declarat i ulterior definirii tipului:
struct tip_articol var1;

Descrierea constituie o definire implicit de un nou tip de dat. Este posibil definirea explicit a unui nou tip de dat, adugnd cuvntul rezervat typedef n faa declarrii (n acest caz nu mai pot fi declarate simultan i variabile). Lista_campuri este o niruire de declaraii de cmpuri separate prin punct i virgul, asemntoare declaraiilor de variabile, de forma tip_camp nume_camp. Cmpurile unei structuri pot fi variabile simple, masive sau alte articole. Lista cmpurilor nu poate fi vid. Exemplu: definirea tipului de dat numr complex, a unei variabile simple i a unui masiv unidimensional cu elemente de acest tip se poate face n oricare din urmtoarele variante (pentru un numr complex se vor reine partea real i partea imaginar):
a) struct COMPLEX{float r,i;}a,b[100]; b) struct COMPLEX{float r,i;}; struct COMPLEX a,b[100]; c) struct COMPLEX{float r,i;}; COMPLEX a,b[100]; d) struct {float r,i;}COMPLEX; COMPLEX a,b[100]; e) typedef struct {float r,i;} COMPLEX; COMPLEX a,b[100]; f) typedef struct COMPLEX{float r,i;}; struct COMPLEX a,b[100]; g) typedef struct COMPLEX{float r,i;}; COMPLEX a,b[100];

45

Programarea calculatoarelor

Din punct de vedere practic, utilizarea tipului articol este strns legat de prelucrarea fiierelor. n lucrul cu variabilele de tip articol se recomand declararea identificatorului de tip. n acest mod, identificatorul de tip articol poate fi folosit n definirea mai multor variabile. n procesul de descriere a unui articol, arborele se parcurge n preordine (de la rdcin spre extremiti i de la stnga la dreapta). Exemplu: pentru exemplele din figura 4.1, declararea poate fi realizat prin definire recursiv, astfel:
struct tip_data { unsigned zi; char luna[3]; int an; }; struct persoana { char nume[30]; char adresa[50]; struct tip_data data_nasterii; } angajat;

Dac nu ar fi existat declaraia tipului articol tip_data, atunci tipul persoana putea fi scris astfel:
struct persoana { char nume[30]; char adresa[50]; struct { unsigned zi; char luna[3]; int an; } data_nasterii; } angajat;

Variabilele de tip articol se reprezint intern ca succesiuni de cmpuri elementare, cu reprezentarea intern i lungimea fizic specifice tipurilor lor. Lungimea zonei de memorie rezervat pentru variabila de tip articol rezult din nsumarea lungimilor cmpurilor. Aceasta nu poate depi 65520 octei (ca orice variabil de tip structurat). Pentru structura unui articol i dovedete utilitatea operatorul sizeof, care asigur determinarea lungimii zonei de memorie asociate unei variabile sau unui tip de date. Exemplu: Considernd declaraiile anterioare, expresia sizeof(data_nasterii) are valoarea 8, iar sizeof(angajat) are valoarea 90. Din punct de vedere fizic, identificatorii cmpurilor din descrierea articolului reprezint deplasri fa de nceputul acestuia. Adresa fizic a unui cmp rezult din nsumarea adresei articolului cu deplasarea sa. Structura arborescent a articolelor poate fi exprimat sugestiv i prin machete, care evideniaz componentele, natura, lungimea declarat i lungimea fizic ale acestora (figurile 4.2 i 4.3).

46

Tipul de dat articol

4.2 Referirea articolului i a elementelor componente


Datele de tip articol pot fi referite n dou moduri: global sau pe componente. Referirea global este permis numai n operaia de atribuire, cu condiia ca ambele variabile (surs i destinaie) s fie articole de acelai tip. Referirea pe componente (prin numele lor) este o reflectare a faptului c articolul este o structur cu acces direct. Referirea unor componente de tip articol din structura altui articol este posibil numai n operaia de atribuire, n condiiile precizate anterior la referirea global. n cele ce urmeaz se are n vedere numai referirea componentelor de tip dat elementar, situate pe ultimul nivel al structurii. Referirea cmpurilor unei structuri se face prin calificare, folosind operatorul. (punct). n referirea prin calificare, asigurarea identificrii unice a cmpurilor se realizeaz prin asocierea numelui acestora cu numele articolului care le conine. Construcia rmne la aceast form n cazul n care structura are numai dou niveluri: articolul i cmpurile elementare ale acestuia. Exemple: Folosind tipul COMPLEX definit anterior, avem:

a.r , a.i - se refer partea real, respectiv imaginar a variabilei a b[10].r - se refer partea real a celui de-al 11-lea element al vectorului b #include <string.h> main() { struct articol {char nume[40]; char adresa[30]; int an, luna, zi;} struct articol pers; strcpy(pers.nume, "Popescu Ion"); strcpy(pers.adresa, "Bucuresti, Pta. Romana 6"); pers.an=1979; pers.luna=3; pers.zi=15; }

n articolele cu structur recursiv se realizeaz calificarea progresiv cu articolele de pe nivelurile superioare, primul calificator fiind numele articolului rdcin. n lanul de calificri, numele articolului rdcin este nume de variabil, celelalte fiind nume de cmpuri ale articolului. Dac anumite componente sunt structuri de date de alte tipuri (de exemplu masive sau iruri de caractere), n referirea elementelor lor se aplic, pe lng calificare, regulile specifice acestor structuri. Exemple: 1. Referirea prin calificare a cmpurilor articolului angajat de tipul persoana (vezi exemplele anterioare) se realizeaz astfel:
angajat.nume; angajat.adresa; angajat.data_nasterii.zi; angajat.data_nasterii.luna; angajat.data_nasterii.an

47

Programarea calculatoarelor

n aceste referiri, angajat este identificatorul variabilei articol, celelalte elemente sunt identificatori de cmpuri. Construciile angajat.nume i angajat.adresa corespund referirii globale a cmpurilor respective, care sunt iruri de caractere. Pentru a referi, de exemplu, primul caracter din ir, se scrie: angajat.nume[0]. 2. Se presupune un articol cu structura din figura 4.2. Cod Magazin ntreg 2 Luna 1 real 4 Vnzri lunare Luna 2 Real 4 Luna 12 real 4

Figura 4.2 Structura de articol pentru exemplul 2 Articolul se declar astfel:


struct magazin { int cod_magazin; float vanzari_lunare[12]; } articol;

Articolul are 50 de octei, iar referirea cmpurilor se realizeaz astfel:


articol.cod_magazin; articol.vanzari_lunare[i], cu i=0,1,,11.

3. Se presupune un articol cu structura din figura 4.3.


Cod produs ntreg 2 Numr materii prime ntreg 1 Materia prim 1 Cod ntreg 2 Norma de consum real 4 ... ... ... ... Materia prim 30 Cod ntreg 2 Norma de consum real 4

Figura 4.3 Structura de articol pentru exemplul 3 Cu toate c numrul de materii prime utilizate poate fi variabil de la un produs la altul, n descrierea articolului se alege valoarea maxim a acestuia:
struct a { int cod_mat; float norma; }; struct produs { int cod_produs; unsigned char nr_mat; struct a materii_prime[30]; } articol ;

Articolul are 183 de octei, iar referirea cmpurilor se realizeaz astfel:

articol.cod_produs; articol.nr_mat; articol.materii_prime[i].cod_mat; articol.materii_prime[i].norma;

Constantele de tip articol sunt cu tip i pstreaz caracteristicile acestora, descrise n 4.2. n momentul compilrii se rezerv zone de memorie pentru acestea, iar cmpurile articolelor sunt iniializate cu valorile precizate de utilizator. Declararea constantelor presupune definirea anterioar a tipului articol. Valoarea iniial trebuie s fie de acelai tip cu cmpul cruia i corespunde. Cnd articolul conine la rndul su alt articol, identificarea cmpului care se iniializeaz se face pe niveluri, folosind perechi corespunztoare de acolade.

48

Tipul de dat articol

Exemple:

#include <stdio.h> void main() { //exemplul 1 struct persoana { char nume[40]; char adresa[30]; struct { int zi, luna, an;} datan; }; //exemplul 2 struct magazin { int cod_magazin; float vanzari_lunare[12]; }; //exemplul 3 struct a { int cod_mat; float norma;}; struct produs { int cod_produs; unsigned char nr_mat; struct a materii_prime[30]; }; //Initializarea articolului din exemplul 1: struct persoana p={"Popescu Ion", "Bucuresti, Magheru 14", 2, 4, 1960}; //sau cu evidentierea structurii data nasterii: struct persoana p1={"Popescu Ion", "Bucuresti, Magheru 14", {2, 4, 1960}}; printf("\n%i",p1.datan.an); //Initializarea articolului din exemplul 2: struct magazin gigel_srl={200, 1,2,3,4,5,6,7,8,9,10,11,12}; //sau cu evidentierea structurii de masiv: struct magazin gigel_srl1={200, {1,2,3,4,5,6,7,8,9,10,11,12}}; printf("\n%6.2f",gigel_srl1.vanzari_lunare[10]); //Initializarea articolului din exemplul 3 (doar primele 4 materii //prime, restul de 26 vor fi initializate automat cu valori nule: struct produs z={243,5,{{2420,25.4},{3251,70.21},{1421,8.4},{51,7.2}}}; printf("\n%6.2f",z.materii_prime[2].norma); }

4.3 Articole cu structuri complexe


n activitatea de programare pot fi ntlnite aplicaii care reclam utilizarea articolelor cu structur variabil. La iniializarea cmpurilor unui astfel de articol, constanta de tip articol se asociaz unei singure structuri, deoarece zona de memorie rezervat pentru articol este unic. Pentru acest tip de articol, limbajul pune la dispoziia utilizatorilor tipul predefinit reuniune (union), care se comport ca i tipul struct cu o singur diferen: la un moment dat al execuiei programului, n zona de memorie rezervat articolului nu este memorat dect unul dintre cmpurile acestuia.

49

Programarea calculatoarelor

Declararea tipului reuniune se realizeaz astfel:


union nume_tip { tip_cimp1 cimp1; tip_cimp2 cimp2; ................ tip_cimpn cimpn;};

Lungimea zonei de memorie rezervate pentru o variabil de tip reuniune va fi egal cu maximul dintre lungimile cmpurilor componente. Gestiunea coninutului respectivei zone de memorie va trebui realizat de ctre programator. Exemplu: Se presupune un articol cu structura din figura 4.4.
Nume Data naterii lun zi an a An de studiu bursa char[40] int char Forma de nvmnt zi valoare float loc de munc char[30] zi id data angajrii lun an

Figura 4.4 Articol cu structur variabil Declararea i iniializarea cmpurilor unui student la zi pentru structura articolului din figura 6.4 se realizeaz astfel:
#include <stdio.h> void main() {//Declararea articolului cu structura variabila: struct articol { char nume[40]; struct { int zi, luna, an;} datan; int an_st; char forma_inv; union { struct {char bursa; float valoare;}zi; struct {char loc_m[30]; struct {int zi, luna, an;}data_ang; }id; } parte_vb; }; //Initializarea campurilor unui student la zi: struct articol a={"Popescu Felix",{4,1,1974} ,1,'Z',{'D',250.5}}; printf("\nData nasterii: %i.%i.%i, Forma de inv.: %c, Val. bursa: %6.2f", a.datan.zi, a.datan.luna, a.datan.an, a.forma_inv, a.parte_vb.zi.valoare); }

Din punct de vedere fizic, existena prii variabile ntr-un articol genereaz, la compilare, deplasri egale fa de nceputul articolului pentru toate variantele de descriere. Astfel, pentru descrierea din exemplul de mai sus se genereaz deplasarea 49 fa de nceputul articolului, att pentru cmpul bursa, ct i pentru loc_m.

50

Tipul de dat articol

4.4 Constante de tip articol


Constantele de tip articol pot fi constante cu tip (variabile iniializate la compilare) i constante obiect, pentru care n momentul compilrii se rezerv zone de memorie, iar cmpurile articolelor sunt iniializate cu valorile precizate de utilizator. Valoarea iniial trebuie s fie de acelai tip cu cmpul cruia i corespunde. Cnd articolul conine la rndul su alt articol, identificarea cmpului care se iniializeaz se face pe niveluri, folosind perechi corespunztoare de acolade. Constantele cu tip joac rol de variabile care se iniializeaz cu o valoare n faza de compilare, ele putnd s-i modifice valoarea pe parcursul execuiei programului.
tip nume_const = {lista_valori};

Constantele obiect sunt variabile iniializate la declarare, pentru care se rezerv memorie, dar coninutul lor nu poate fi modificat pe parcursul programului.
const tip nume_const = {lista_valori};

Exemplul 1:
#include<stdio.h> void main() { struct persoana { char nume[40]; char adresa[30]; struct { int zi, luna, an;} datan; }; persoana pers={"Popescu Ion", "Bucuresti; Magheru 14", {2, 4, 1960}}; //constanta cu tip pers.datan.zi=4; }

Exemplul 2:
#include<stdio.h> void main() { struct persoana { char nume[40]; char adresa[30]; struct {int zi, luna, an;} datan; }; const persoana pers={"Popescu Ion", "Bucuresti; Magheru 14", {2, 4, 1960}}; //constanta obiect // pers.datan.zi=4; genereaza eroare la compilare }

51

5 Fiiere de date

Prelucrarea automat a datelor presupune un sistem de organizare a acestora dup metode i procedee specifice. Organizarea datelor este un proces complex care include identificarea, clasificarea i descrierea proprietilor acestora, gruparea lor n colecii, reprezentarea pe purttori tehnici, definirea i realizarea procedurilor de prelucrare etc. Deoarece datele se memoreaz, de obicei, pe purttori tehnici de informaii, dar se prelucreaz numai cnd sunt prezente n memoria intern, acestea trebuie organizate att extern ct i intern. n organizarea extern a datelor se identific dou niveluri de abordare, dup cum se are n vedere acest proces din perspectiva utilizatorului sau a purttorilor fizici externi pe care se nregistreaz datele. Cele dou niveluri de abordare, numite logic, respectiv fizic, precum i realizarea trecerii de la unul la cellalt, n condiiile specifice diverselor sisteme de calcul, se bazeaz pe o serie de concepte, cum ar fi: fiierul i articolul, purttorul tehnic de date, metoda de organizare i modul de acces, operaiile de prelucrare etc.

5.1 Fiierul i articolul


Fiierul reprezint termenul generic care desemneaz structurile de date externe. El este o mulime (colecie) de date omogene din punct de vedere al semnificaiei i al cerinelor de prelucrare. n purttorul extern, fiierul are, pe lng partea de date, i alte informaii de identificare (etichete). Privit din punctul de vedere al prelucrrii, un fiier este o colecie ordonat de date, numite articole. Articolul este constituit dintr-o mulime ordonat de valori ale unor caracteristici ce aparin, uzual, unei singure entiti (obiect, fenomen, proces etc.) din domeniul de activitate abordat. De exemplu, ntr-un fiier care conine datele personale ale salariailor dintr-o unitate economic, un articol grupeaz valorile caracteristicilor unei singure persoane.

52

Fiiere de date

Componentele articolului destinate diverselor caracteristici sunt denumite cmpuri de date. Depinznd de natura, ordinul de mrime i forma de reprezentare extern a valorilor asociate, fiecare cmp de date are o lungime, exprimat uzual n octei. Lungimea unui articol este dat de suma lungimilor cmpurilor care l compun. Dup cum toate articolele dintr-un fiier au sau nu aceeai lungime, se face distincie ntre fiierele cu articole de lungime fix sau variabil. Modul de implementare fizic a celor dou tipuri de fiiere difer de la un sistem la altul i chiar de la un limbaj la altul. Pe purttorul fizic extern, partea de date a fiierului se prezint ca o succesiune de octei cu un coninut binar fr semnificaie informaional. n momentul prelucrrii, prin descrieri i operaii adecvate, din succesiunea memorat extern se decupeaz" entiti (articole, blocuri, linii sau cmpuri) cu structuri corespunztoare prelucrrii. Tipul entitii care se decupeaz" depinde de tipul fiierului.

5.2 Metode de organizare i tipuri de acces


Principiile i regulile dup care se memoreaz articolele unui fiier pe purttorul extern, cu asigurarea proteciei i regsirii acestora, constituie metoda de organizare. n evoluia organizrii datelor externe s-au cristalizat mai multe metode, dintre care, cele mai uzuale sunt secvenial, relativ i indexat. Principala difereniere ntre metodele de organizare o reprezint tipurile de acces admise. Tipul de acces reprezint modalitatea de regsire (localizare) a articolelor din fiier. Noiunea de acces trebuie aplicat att pentru operaia de scriere, ct i pentru cea de citire a datelor. Poziia din/n care se face citirea/scrierea n cadrul fiierului este indicat de un pointer. Accesul la datele nregistrate pe un purttor tehnic poate fi secvenial sau direct, n funcie de modul n care se stabilete pointerul. Accesul secvenial este posibil la toi purttorii tehnici de date i presupune nscrierea nregistrrilor n ordinea furnizrii lor sau regsirea n ordinea n care au fost nscrise n suport (figura 5.1).
P(Ak)=f (P(Ak-1)) Traversare

A1

A2

...

Ak-1

Ak

...

An

EOF

Figura 5.1 Principiul de realizare a accesului secvenial la articole Pointerul de fiier avanseaz, n scriere i citire, de la o entitate (articol, bloc, linie sau cmp) la alta. Dac pointerul se exprim prin deplasare fa

53

Programarea calculatoarelor

de nceputul fiierului, atunci, matematic, acest lucru se poate exprima astfel: P(A1) = 0; P(Ak) = f(P(Ak-1)) = P(Ak-1)+lartk-1; pentru k=2,n; unde Ak este articolul k i lartk este lungimea articolului k. O problem important care se pune la consultarea (citirea) n acces secvenial este controlul ajungerii la sfritul fiierului. Dup citirea ultimei entiti (articol, bloc, linie sau cmp), pointerul indic marcatorul de sfrit de fiier EOF (figura 5.2).
Poziia pointerului dup citirea ultimului articol

A1

A2

...

Ak-1

Ak

...

An

EOF

Figura 5.2 Pointerul dup citirea ultimului articol din fiier n limbajele de programare se regsesc dou modaliti de sesizare a sfritului de fiier: a) Sesizarea sfritului de fiier n cadrul operaiei de citire (limbajele FORTRAN, COBOL, C). Sfritul este sesizat la citirea marcatorului de sfrit de fiier. Situaia din figura 5.2 nu este considerat sfrit de fiier. Abia la urmtoarea citire se ntmpl acest lucru (pointerul de fiier avanseaz dup marcatorul de sfrit de fiier). b) Sesizarea sfritului de fiier independent de operaia de citire (limbajele BASIC, PASCAL). n acest caz, dac pointerul este pe marcatorul de sfrit de fiier (dup ultimul articol, bloc, linie, cmp, ca n figura 5.2) se consider sfrit de fiier. Urmtoarea citire produce eroare de intrare/ieire (I/E). Proiectarea algoritmilor de prelucrare a fiierelor este determinat de modalitatea n care se sesizeaz sfritul de fiier. Accesul direct este posibil numai la fiierele care au o anumit organizare, au ca entitate de transfer articolul sau blocul i sunt memorate pe discuri magnetice. Accesul direct se bazeaz pe existena unui algoritm implementat n sistem care asigur regsirea (localizarea) articolelor n funcie de o informaie de regsire. Valoarea pointerului este determinat direct, fr s depind de valoarea sa anterioar: P(Ak)=f(irk), unde Ak este articolul k, iar irk este o informaie de regsire a articolului k. n funcie de algoritmul i informaia de regsire, exist dou tipuri de acces direct: dup cheie i dup numrul relativ al articolului. n cazul accesului direct dup cheie, articolul este regsit prin aplicarea unui algoritm asupra unei informaii de identificare de tip cheie: P(Ak)=f(cheiek). n cazul accesului direct dup numrul relativ - care se mai numete, simplu, acces relativ - (figura 5.3), articolul este localizat n fiier prin numrul su, stabilit, n cadrul fiierului, de la valoarea zero: P*(Ak)=(k-1); P(Ak)=P*(Ak)lart. P*(Ak) reprezint poziia exprimat n numr relativ, iar P(Ak) reprezint poziia exprimat

54

Fiiere de date

prin deplasare, n octei, fa de nceputul fiierului (la unele sisteme numrul relativ este stabilit de la unu: P*(Ak)=k). La scriere, articolul Ak (numrul relativ k-1) se memoreaz pe poziia sa, celelalte k-1 articole anterioare putnd s nu existe (pe suport exist ns rezervat loc pentru ele). La citire, articolul Ak (cu numrul relativ k-1, kn) este localizat direct i coninutul lui se transfer n memoria intern.
Acces direct prin numrul relativ k-1 P(Ak)=k-1

A1 0 Numr relativ

A2 1

... ...

Ak-1 k

Ak k-1

... ...

An n-1

EOF

Figura 5.3 Principiul de realizare a accesului direct prin numr relativ Fiierele organizate secvenial, cu articole de lungime variabil, admit numai accesul secvenial. Fiierele organizate secvenial, cu articole sau blocuri de lungime fix, admit att accesul secvenial, ct i pe cel relativ. Acest lucru deriv din faptul c accesul relativ este realizat de sistem printr-o deplasare secvenial fa de nceputul acestuia, deplasare care este egal cu valoarea expresiei: numr_relativ lungime_articol.

5.3 Structura sistemului de fiiere sub MS-DOS/Windows


Sistemul de operare MS-DOS utilizeaz o form logic arborescent de grupare a fiierelor de pe discuri n directoare i subdirectoare. Un director (subdirector) poate conine fiiere i/sau alte subdirectoare (figura 5.4). n limbajul curent folosit de practicieni se utilizeaz noiunea de director i n cazul subdirectoarelor. Un disc DOS conine un singur director rdcin, care la rndul lui are zero sau mai multe subdirectoare i/sau fiiere. Subdirectoarele pot avea oricte niveluri de imbricare. Frunzele arborelui sunt, cel mai adesea, fiiere, dar pot fi i subdirectoare vide. Unitatea de disc, directorul i subdirectorul n care se lucreaz la un moment dat se numesc curente. Pentru ca un fiier s fie localizat n cadrul structurii arborescente se folosete un identificator extern (specificator) care are urmtoarea form sintactic: [n:][cale][\]nume_fiier[.extensie] n este numele unitii de disc (A:, B:, C: etc.). Prin lips, se consider unitatea curent; cale (path) este calea de acces de la directorul rdcin pn la subdirectorul dorit. Fiecare nume de director (subdirector) din interiorul cii este

55

Programarea calculatoarelor

precedat de caracterul backslash (\). Prin lips, se consider calea subdirectorului curent. Calea selectat la un moment dat poate ncepe de la rdcin sau de la subdirectorul curent. Cnd calea ncepe cu caracterul backslash (\) cutarea ncepe de la rdcin; n caz contrar, cutarea ncepe de la directorul curent.
Rdcina (pe discul C:\)

F1

D1

D2

D3

F2

F3

F4

F2

D4

D5

F5

F6

F7

F8

F9

Figura 5.4 Exemplu de structur arborescent de directori Fiecare subdirector conine dou intrri speciale marcate prin caracterul ".", respectiv caracterele ".." n locul numelui de fiier. Prima intrare realizeaz autopunctarea, indicnd faptul c entitatea este subdirector (nu fiier de date), a doua intrare puncteaz subdirectorul printe. n construirea cii de acces se poate folosi succesiunea de dou puncte pentru a indica subdirectorul printe. nume_fiier este numele extern al fiierului, format din maxim 8 caractere alfanumerice, mai puin unele caractere speciale, ca: ." \ / : ' > < + = ; , ). Exist o serie de nume prestabilite, asociate unor dispozitive standard de intrare/ieire, care nu pot fi utilizate de programator pentru propriile fiiere: CON, AUX, COM1, COM2, LPT1, LPT2, LPT3, NULL, PRN, CLOCK$ (dispozitiv pentru ceasul de timp real). extensia este format din maxim trei caractere alfanumerice prin care utilizatorul are posibilitatea s-i identifice fiiere cu coninuturi diferite. Prin lips nu se asum nicio valoare. Exemple (structura din figura 5.4): 1. C:\F1 Fiierul F1 din rdcin; 2. C:\D2\F2 Fiierul F2 din subarborele C:-D2; 3. C:\F2 Fiierul F2 din directorul rdcin; 4. C:\D2\D4\F9 Fiierul F9 din subarborele C:-D2-D4; 5. Pentru a indica fiierul F9 se poate folosi una din scrierile: C:\D2\D4\F9 de oriunde; \D2\D4\F9 de oriunde din unitatea C:; ..\D4\F9 din subdirectorul D5; F9 din subdirectorul D4.

56

Fiiere de date

Productorii de software au impus unele denumiri de extensii, care, dei opionale, ofer posibilitatea simplificrii referirii fiierelor n unele comenzi sau aplicaii. Exemple: 6. Standard MS-DOS: .COM .EXE .SYS .OBJ .BAT 7. Standarde de firm: .ARC .ZIP .DBF 8. Formate ASCII: .ASM .BAS .PAS .CBL .C .TXT 9. Formate grafice: .PCX .MSP .WPG

program executabil; program executabil; driver de sistem; program obiect; fiiere de comenzi DOS (prelucrri BATCH). arhiv compactat cu PKPAK sau ARC; arhiv compactat cu PKZIP sau WINZIP; baz de date DBASE. program surs ASSEMBLER; program surs BASIC; program surs PASCAL; program surs COBOL; program surs C; fiier text; Paint Brush; Microsoft Windows; WordPerfect.

5.4 Operaii de prelucrare a fiierelor


Asupra unui fiier se pot executa diverse operaii de prelucrare, numite i de gestiune, care se mpart n operaii la nivel de fiier i la nivel de articol. Operaiile la nivel de fiier se refer la aspecte ca: nscrierea fiierului n [sub]directoare, validarea i interzicerea accesului la fiier (deschidere/nchidere), tergerea fiierului din [sub]directoare (tergere) etc. Aceste operaii se regsesc, n totalitate, la prelucrarea fiierelor pe discuri magnetice. n cazul purttorilor nereutilizabili, singurele operaii care au sens sunt cele de deschidere/nchidere a fiierelor. Operaiile la nivel de articol se refer la accesul la entitile de date ale fiierului (articole, blocuri, linii sau cmpuri) n vederea prelucrrii lor. Privite sub aspectul semnificaiei pentru utilizator, aceste operaii se refer la: nscrierea iniial a entitilor pe purttorul tehnic (populare), actualizarea fiierului prin includerea de noi entiti (adugare), modificarea valorilor unor cmpuri din

57

Programarea calculatoarelor

anumite entiti (modificare), eliminarea entitilor care nu mai sunt necesare (tergere), regsirea entitilor n vederea satisfacerii unor cerine de informare (consultare). n programele C, operaiile de I/E sunt realizate cu ajutorul unei mulimi de funcii specializate pentru cutare, scriere, citire etc. n concluzie, dac din punctul de vedere al utilizatorului operaiile de prelucrare se descriu relativ simplu, prin apeluri de funcii, realizarea efectiv a lor de ctre sistemul de calcul este complex. n sistemul de operare MS-DOS sunt incluse funcii de ntrerupere care, prin intermediul BIOS (Basic Input Output System), lanseaz anumite operaii cu un echipament. Din punct de vedere al reprezentrii datelor n suportul extern, se disting fiiere text, n care toate datele sunt sub form ASCII (un caracter/octet) i fiiere binare, n care toate datele sunt memorate n forma identic cu cea din memoria principal (MP). Strns legat de lucrul cu cele dou tipuri de fiiere este modul n care se face transferul datelor ntre memoria principal i suportul extern: transfer posibil cu conversie (n cazul fiierelor text) i transfer fr conversie (n cazul fiierelor binare). Trebuie fcut remarca, deosebit de important, c din punct de vedere fizic fiierul se reprezint n suportul extern ca o succesiune de octei. Aceast succesiune poate fi tratat logic ca un fiier de un tip sau altul. Este sarcina programatorului s asigure suprapunerea corect a fiierului logic peste cel fizic. Din acest punct de vedere se poate spune c prin fiier logic se nelege, prioritar, un mod de prelucrare i mai puin un mod de memorare. Indiferent de limbajul de programare folosit, operaiile necesare pentru prelucrarea fiierelor sunt: descrierea fiierului (crearea tabelei care memoreaz caracteristicile fiierului); asignarea fiierului intern (numele logic) la unul extern (fizic); deschiderea fiierului; operaii de acces la date (articole); nchiderea fiierului. Pentru lucrul cu fiiere trebuie identificate tipurile acestora, metodele de organizare, modurile de acces i tipurile de articole acceptate. Din punct de vedere al tipurilor de date, n C exist un singur tip de fiiere: flux de octei (niruire de octei, fr niciun fel de organizare sau semnificaie). Organizarea acestui flux de octei este secvenial. Accesul la fiiere se poate face secvenial sau direct (cu excepia fiierelor standard, la care accesul este numai secvenial). n bibliotecile limbajului exist funcii predefinite pentru prelucrarea fiierelor. Funciile de prelucrare la nivel superior a fiierelor trateaz fluxul de octei acordndu-i o semnificaie oarecare. Putem spune c din punct de vedere al prelucrrii, la acest nivel, ne putem referi la fiiere text i fiiere binare. Exist fiiere standard, care sunt gestionate automat de sistem, dar asupra crora se poate interveni i n mod explicit. Acestea sunt: fiierul standard de intrare (stdin);

58

Fiiere de date

fiierul standard de ieire (stdout); fierul standard pentru scrierea mesajelor de eroare (stderr); fiierul standard asociat portului serial (stdaux); fiierul standard asociat imprimantei cuplate la portul paralel (stdprn). Fiierele standard pot fi redirectate conform conveniilor sistemului de operare, cu excepia lui stderr care va fi asociat ntotdeauna monitorului. n lucrul cu fiiere (sau la orice apel de sistem), n caz de eroare n timpul unei operaii se seteaz variabila errno, definit n errno.h, stddef.h i stdlib.h. Valorile posibile sunt definite n stdlib.h. n limbajul C exist dou niveluri de abordare a lucrului cu fiiere: nivelul inferior de prelucrare (fr gestiunea automat a zonelor tampon de intrare/ieire) i nivelul superior de prelucrare (se folosesc funcii specializate de gestiune a fiierelor). n continuare, prin specificator de fiier se va nelege un nume extern de fiier, conform conveniilor sistemului de operare. Specificatorul de fiier poate s conin strict numele fiierului sau poate conine i calea complet pn la el.

5.4.1 Nivelul inferior de prelucrare a fiierelor


Nivelul inferior de prelucrare este folosit rar, numai n programele de sistem. La acest nivel, descrierea fiierelor se realizeaz n corpul programelor, caracteristicile acestora obinndu-se din context. Maniera de prelucrare este asemntoare celei de la nivelul sistemului de operare. Nu exist un tip anume de dat, fiierul fiind referit printr-un index care indic intrarea ntr-o tabel de gestiune a resurselor sistemului de operare. Acest index este de tip int i se numete manipulator de fiier (handle). Manipulatorul este creat i gestionat de ctre sistemul de operare. Utilizatorul l folosete pentru a indica sistemului fiierul asupra crui dorete s fac prelucrri. Pentru utilizarea acestui nivel, n programul C trebuie incluse bibliotecile standard io.h, stat.h i fcntl.h. Crearea i asignarea unui fiier nou se realizeaz prin apelul funciei creat, care are urmtorul prototip:
int creat(const char* numef, int protecie);

Funcia returneaz manipulatorul fiierului nou creat; numef este un pointer spre un ir de caractere care definete specificatorul de fiier, iar protecie definete modul de protecie a fiierului creat (protecia este dependent de sistemul de operare). n biblioteca stat.h sunt definite urmtoarele valori pentru parametrul protecie: S_IREAD (citire), S_IWRITE (scriere), S_IEXEC (execuie). Aceste valori pot fi combinate folosind operatorul | (sau logic pe bii). Funcia creat poate fi apelat i pentru un fiier existent. Efectul produs este tergerea fiierului existent i crearea unuia gol, cu acelai nume; coninutul fiierului existent se pierde. n caz de eroare se returneaz valoarea 1 i se seteaz variabila global

59

Programarea calculatoarelor

errno, care definete tipul erorii. Valorile obinuite pentru errno sunt EBADF (manipulator eronat, nu a fost gsit fiierul) sau EACCES (fiierul nu poate fi accesat). Deschiderea unui fiier existent se realizeaz prin apelul funciei open, care are urmtorul prototip:
int open(const char *path,int access[,unsigned mod]);

Funcia returneaz manipulatorul fiierului; numef este pointer spre un ir de caractere care definete specificatorul de fiier; acces este modul de acces la fiier; constantele care descriu modurile de acces la fiier sunt descrise n fcntl.h. Cele mai importante sunt: O_RDONLY fiierul va fi accesat numai pentru citire; O_WRONLY fiierul va fi accesat numai pentru scriere; O_RDWR fiierul va fi accesat att pentru citire, ct i pentru scriere; O_CREAT: fiierul va fi creat ca nou. Aceste moduri pot fi combinate folosind operatorul |. Mod este folosit numai dac parametrul acces conine i valoarea O_CREAT, caz n care indic modul de protecie a acestuia: S_IWRITE se permite scrierea n fiier; S_IREAD se permite citirea din fiier; S_IREAD|S_IWRITE se permite att scrierea, ct i citirea din fiier. Citirea dintr-un fiier se realizeaz prin apelul funciei read, care are urmtorul antet:
int read(int nf, void* zonat, unsigned n);

Funcia returneaz numrul de octei citii din fiier; nf este manipulatorul de fiier (alocat la crearea sau deschiderea fiierului), zonat este un pointer spre zona tampon n care se face citirea (aceasta este definit de programator), iar n este dimensiunea zonei receptoare (numrul maxim de octei care se citesc). Numrul maxim de octei care pot fi citii este 65534 (deoarece 65535 0xFFF se reprezint intern la fel ca -1, indicatorul de eroare). n cazul citirii sfritului de fiier se va returna valoarea 0 (0 octei citii), iar la eroare se returneaz -1 (tipul erorii depinde de sistemul de operare). Fiierul standard de intrare (stdin) are descriptorul de fiier 0. Scrierea ntr-un fiier se realizeaz prin apelul funciei write, care are urmtorul prototip:
int write(int nf, void* zonat, unsigned n);

Funcia returneaz numrul de octei scrii n fiier; nf este manipulatorul de fiier (alocat la crearea sau deschiderea fiierului), zonat este un pointer spre zona tampon din care se face scrierea (aceasta este definit de programator); n este numrul de octei care se scriu. Numrul maxim de octei care pot fi citii este 65534 (deoarece 65535 0xFFF se reprezint intern la fel ca -1, indicatorul

60

Fiiere de date

de eroare). n general, trebuie ca la revenirea din funcia write, valoarea returnat s fie egal cu n; dac este mai mic, s-a produs o eroare (probabil discul este plin). La scrierea n fiiere text, dac n fluxul octeilor care se scriu apare caracterul LF, write va scrie n fiier perechea CR/LF. n caz de eroare, valoarea returnat este -1 i se seteaz variabila errno. Fiierul standard de ieire (stdout) are manipulatorul 1, iar cel de eroare (stderr) are manipulatorul 2. nchiderea unui fiier se realizeaz prin apelul funciei close, care are urmtorul prototip:
int close(int nf);

Funcia returneaz valoarea 0 (nchidere cu succes) sau -1 (eroare); nf este manipulatorul de fiier. De asemenea, nchiderea unui fiier se realizeaz automat, dac programul se termin prin apelul funciei exit. Poziionarea ntr-un fiier se realizeaz prin apelul funciei lseek, care are urmtorul prototip:
long lseek(int nf, long offset, int start);

Funcia returneaz poziia fa de nceputul fiierului, n numr de octei; nf este manipulatorul de fiier; offset este un parametru de tip long (numrul de octei peste care se va deplasa pointerul n fiier), iar start este poziia fa de care se face deplasarea: 0 (nceputul fiierului), 1 (poziia curent n fiier) sau 2 (sfritul fiierului). La eroare returneaz valoarea -1L. Exemple: realizeaz poziionarea la sfritul fiierului 1. Apelul vb=lseek(nf, 0l, 2); (n continuare se poate scrie n fiier folosind write); 2. Apelul vb=lseek(nf, 0l, 0); fiierului. realizeaz poziionarea la nceputul

tergerea unui fiier existent se realizeaz prin apelul funciei unlink, care are urmtorul prototip:
int unlink(const char* numef);

Funcia returneaz 0 (tergere cu succes) sau -1 (eroare); numef este un pointer spre un ir de caractere care definete specificatorul de fiier. n caz de eroare se seteaz variabila errno cu valoarea ENOENT (fiierul nu a fost gsit) sau EACCES (accesul interzis pentru aceast operaie, de exemplu pentru fiiere read only). Pentru a putea terge un fiier read only trebuie mai nti schimbate

61

Programarea calculatoarelor

drepturile de acces la fiier, folosind funcia chmod:


int chmod(const char *cale, int mod);

unde cale este specificatorul de fiier, iar mod noile permisiuni. Permisiunile sunt aceleai ca la funcia open. Rezultatul ntors de chmod are aceeai semnificaie ca i unlink. Verificarea atingerii sfritului de fiier se face folosind funcia eof:
int eof(int nf);

unde nf este manipulatorul fiierului. Funcia returneaz valoarea 1 dac pointerul este poziionat pe sfritul fiierului, 0 n caz contrat i -1 n caz de eroare (nu este gsit fiierul errno primete valoarea EBADF). Exemplu:
#include #include #include #include #include <sys\stat.h> <string.h> <stdio.h> <fcntl.h> <io.h>

int main(void) { int handle; char msg[] = "This is a test"; char ch; /* create a file */ handle = open("TEST.$$$", O_CREAT | O_RDWR, S_IREAD | S_IWRITE); /* write some data to the file */ write(handle, msg, strlen(msg)); /* seek to the begining of the file */ lseek(handle, 0L, SEEK_SET); /* reads chars from the file until we hit EOF */ do {read(handle, &ch, 1); printf("%c", ch);} while (!eof(handle)); close(handle); return 0;}

Bibliotecile limbajului conin i alte funcii pentru prelucrarea fiierelor la nivel inferior, inclusiv variante ale funciilor anterioare, aprute odat cu dezvoltarea sistemelor de operare.

62

Fiiere de date

5.4.2 Nivelul superior de prelucrare a fiierelor


La acest nivel, un fiier se descrie ca pointer ctre o structur predefinit (FILE tabela de descriere a fiierului (FIB)):
FILE* f;

Tipul FILE (descris n stdio.h) depinde de sistemul de operare. Fiierul este considerat ca flux de octei, din care funciile de prelucrare preiau secvene pe care le trateaz ntr-un anumit fel (sau n care insereaz secvene de octei). Funciile folosite la acest nivel pot fi mprite n trei categorii: funcii de prelucrare generale, funcii de citire/scriere cu conversie i funcii de citire/scriere fr conversie. Funciile de prelucrare general se aplic tuturor fiierelor, indiferent de tipul informaiei coninute; prelucrarea efectuat de acestea nu are niciun efect asupra coninutului fiierului. Funciile care lucreaz cu conversie se aplic fiierelor care conin informaie de tip text (linii de text, separate prin perechea CR/LF, iar la sfrit se gsete caracterul CTRL-Z). Funciile care lucreaz fr conversie se aplic fiierelor care conin informaie binar. Funciile de citire/scriere deplaseaz pointerul de citire/scriere al fiierului, spre sfritul acestuia, cu un numr de octei egal cu numrul de octei transferai (fr a trece de sfritul de fiier).

Funcii de prelucrare general


Deschiderea i asignarea se realizeaz prin apelul funciei fopen. Funcia returneaz un pointer spre o structur de tip FILE (n care sunt nscrise date referitoare la fiierul deschis) sau NULL dac fiierul nu se poate deschide:
FILE* fopen(const char* nume_extern,const char* mod);

Parametrul nume_extern constituie specificatorul de fiier iar mod este un ir de caractere care specific modul de deschidere a fiierului. Asignarea se realizeaz prin expresie de atribuire de tipul:
nume_intern=fopen(sir_nume_extern,sir_mod);

Exemplu:
FILE* f; f = fopen("PROD.DAT","r");

63

Programarea calculatoarelor

Modurile n care poate fi deschis un fiier sunt prezentate n tabelul 5.1. Modurile de deschidere a unui fiier
Tabelul 5.1

Mod

Scop Deschide un fiier existent pentru adugare la sfrit (extindere) sau l creeaz a dac nu exist. Este permis numai scrierea. Numai pentru fiiere text. r Deschide un fiier existent numai pentru citire. Suprascrie un fiier existent sau creeaz unul nou, permindu-se numai w operaia de scriere. Deschide un fiier existent pentru adugare la sfrit (extindere) sau l creeaz a+ dac nu exist. Sunt permise citiri i scrieri. Numai pentru fiiere text. r+ Deschide un fiier existent pentru citire i scriere Suprascrie un fiier existent sau creeaz unul nou, permindu-se att citiri, w+ ct i scrieri.

La opiunile de mai sus se poate aduga b pentru fiiere binare sau t pentru fiiere text. Dac nu este prezent nici litera b, nici litera t, modul considerat depinde de valoarea variabilei _fmode: dac valoarea este O_BINARY, se consider fiier binar; dac valoarea este O_TEXT, se consider fiier text. De obicei implicit este valoarea O_TEXT. Modurile uzuale pentru deschiderea fiierelor sunt prezentate n tabelul 5.2. Moduri uzuale pentru deschiderea fiierelor
Tabelul 5.2

Operaia de gestiune Creare Consultare Actualizare Creare i actualizare Extindere

Fiiere text w r w+ a

Fiiere binare wb rb r+b rwb, w+b

nchiderea fiierelor se realizeaz prin apelul funciei fclose, care are urmtorul prototip:
int fclose(FILE* f);

Funcia nchide fiierul primit ca parametru i returneaz valoarea 0 n caz de succes sau -1, n caz de eroare. nainte de nchiderea fiierului, sunt golite toate zonele tampon asociate lui. Zonele tampon alocate automat de sistem sunt eliberate.

64

Fiiere de date

Revenirea la nceputul fiierului se realizeaz prin funcia rewind, cu prototipul:


void rewind(FILE *f);

Executarea funciei are ca efect poziionarea la nceputul fiierului f (care era deschis anterior), resetarea indicatorului de sfrit de fiier i a indicatorilor de eroare (se nscrie valoarea 0). Dup apelul lui rewind poate urma o operaie de scriere sau citire din fiier. Testarea sfritului de fiier, se realizeaz prin apelul macrodefiniiei feof:
int feof(FILE* f);

Macro-ul furnizeaz valoarea indicatorului de sfrit de fiier asociat lui f. Valoarea acestui indicator este setat la fiecare operaie de citire din fiierul respectiv. Valoarea ntoars este 0 (fals) dac indicatorul are valoarea sfrit de fiier i diferit de zero (adevrat) n caz contrar. Apelul lui feof trebuie s fie precedat de apelul unei funcii de citire din fiier. Dup atingerea sfritului de fiier, toate ncercrile de citire vor eua, pn la apelul funciei rewind sau nchiderea i redeschiderea fiierului. Golirea explicit a zonei tampon a unui fiier se realizeaz prin apelul funciei fflush, care are urmtorul prototip:
int fflush(FILE* f);

Dac fiierul f are asociat o zon tampon de ieire, funcia scrie n fiier toate informaiile din acesta, la poziia curent. Dac fiierul are asociat o zon tampon de intrare, funcia l golete. n caz de succes returneaz valoarea zero, iar n caz de eroare valoarea EOF (definit n stdio.h). Exemplu: nainte de a citi un ir de caractere de la tastatur, zona tampon trebuie golit pentru a preveni citirea unui ir vid (datorit unei perechi CR/LF rmase n zona tampon de la o citire anterioar a unei valori numerice). tergerea se realizeaz prin apelul:
fflush(stdin);

Aflarea poziiei curente n fiier se realizeaz prin apelul uneia din funciile fgetpos sau ftell:
int fgetpos(FILE* f,fpos_t* poziie);

65

Programarea calculatoarelor

Dup apel, la adresa poziie se afl poziia pointerului de citire/scriere din fiierul f, ca numr relativ al octetului curent. Primul octet are numrul 0. Valoarea returnat poate fi folosit pentru poziionare cu funcia fsetpos. n caz de succes funcia ntoarce valoarea 0, iar n caz de eroare o valoare nenul i seteaz variabila errno la valoarea EBADF sau EINVAL.
long ftell(FILE* f);

returneaz poziia n fiierul f a pointerului de citire/scriere n caz de succes sau -1L n caz contrar. Dac fiierul este binar, poziia este dat n numr de octei fa de nceputul fiierului. Valoarea poate fi folosit pentru poziionare cu funcia fseek. Modificarea poziiei pointerului de citire/scriere se poate face prin poziionare relativ:
int fseek(FILE* f,long deplasare,int origine);

unde deplasare reprezint numrul de octei cu care se deplaseaz pointerul n fiierul f, iar origine reprezint poziia fa de care se deplaseaz pointerul. Parametrul origine poate fi: SEEK_SET (0) poziionare fa de nceputul fiierului; SEEK_CUR (1) poziionare fa de poziia curent; SEEK_END (2) poziionare fa de sfritul fiierului. Funcia returneaz valoarea 0 n caz de succes (i uneori i n caz de eec). Se semnaleaz eroare prin returnarea unei valori nenule numai n cazul n care f nu este deschis. Poziionarea absolut se face cu funcia:
int fsetpos(FILE* f,const fpos_t poziie);

Pointerul de citire/scriere se mut n fiierul f la octetul cu numrul indicat de parametrul poziie (care poate fi o valoare obinut prin apelul lui fgetpos). Ambele funcii reseteaz indicatorul de sfrit de fiier i anuleaz efectele unor eventuale apeluri anterioare ale lui ungetc asupra acelui fiier. Redenumirea sau mutarea unui fiier existent se poate realiza prin apelul funciei rename, care are urmtorul prototip:
int rename(const char* n_vechi,const char* n_nou);

unde n_vechi reprezint vechiul nume al fiierului, iar n_nou reprezint numele nou. Dac numele vechi conine numele discului (de exemplu C:), numele nou trebuie s conin acelai nume de disc. Dac numele vechi conine o cale, numele nou nu este obligat s conin aceeai cale. Folosind o alt cale se obine mutarea fiierului pe disc. Folosind aceeai cale (sau nefolosind calea) se obine redenumirea fiierului. Nu sunt permise wildcard-uri (?, *) n cele dou nume.

66

Fiiere de date

n caz de succes se ntoarce valoarea 0. n caz de eroare se ntoarce -1 i errno primete una din valorile: ENOENT nu exist fiierul, EACCES nu exist permisiunea pentru operaie sau ENOTSAM dispozitiv diferit (mutarea se poate face doar pe acelai dispozitiv). tergerea unui fiier existent se poate realiza prin apelul funciei unlink, prezentat anterior, sau remove, care are urmtorul prototip:
int remove(const char* cale);

unde cale reprezint specificatorul fiierului (trebuie s fie nchis).

Funcii de citire/scriere fr conversie


Funciile efectueaz transferuri de secvene de octei ntre memoria intern i un fiier de pe disc, fr a interveni asupra coninutului sau ordinii octeilor respectivi. Citirea dintr-un fiier binar se realizeaz prin apelul funciei fread, care are urmtorul prototip:
size_t fread(void* ptr,size_t dim,size_t n,FILE* f);

Funcia citete din fiierul f, de la poziia curent, un numr de n entiti, fiecare de dimensiune dim, i le depune, n ordinea citirii, la adresa ptr. fread returneaz numrul de entiti citite. n total se citesc, n caz de succes, n*dim octei. n caz de eroare sau cnd se ntlnete sfritul de fiier, funcia returneaz o valoare negativ sau 0; size_t este definit n mai multe header-e (ntre care stdio.h) i este un tip de dat folosit pentru a exprima dimensiunea obiectelor din memorie. Este compatibil cu tipul unsigned. Exemplu:
struct complex {int x,y} articol; FILE * f_complex; if(f_complex=fopen("NR_COMPL.DAT", "rb") fread(&articol,sizeof(articol),1,f_complex); else printf("Fisierul nu poate fi deschis");

n exemplul anterior se deschide un fiier binar din care se citete un articol de tip struct complex, care se depune n variabila articol. Scrierea ntr-un fiier binar se poate realiza prin apelul funciei fwrite, care are urmtorul prototip:
size_t fwrite(const void* ptr,size_t dim,size_t n,FILE* f);

67

Programarea calculatoarelor

Funcia scrie n fiierul f, ncepnd cu poziia curent, un numr de n entiti contigue, fiecare de dimensiune dim, aflate n memorie la adresa ptr; fwrite returneaz numrul entitilor scrise cu succes. n caz de eroare se returneaz o valoare negativ. Exemplu:
struct complex {int x,y} articol; FILE *pf; pf=fopen("NR_COMPL.DAT","wb"); fwrite(& articol,sizeof (articol),1,pf);

Exemplul anterior creeaz un fiier binar nou n care scrie o secven de octei coninnd reprezentarea binar a unei date de tip struct complex. Exemplu: S se scrie funcia care calculeaz numrul de articole dintr-un fiier binar, cunoscnd lungimea n octei a unui articol. Funcia are ca parametri fiierul i lungimea n octei a unui articol. Prin numele funciei se ntoarce numrul de articole din fiier.
int nrart(FILE *f, int l) { long p; int n; p=ftell(f); fseek(f,0,2); n=ftell(f)/l; fseek(f,0,p); return n;}

Funcii de citire/scriere cu conversie


Funciile efectueaz transferuri de secvene de octei ntre memoria intern i un fiier de pe disc, convertind secvena de la reprezentarea intern (binar) la reprezentarea extern (ASCII) i invers. Transferul de caractere se efectueaz prin urmtoarele funcii:
int int int int fgetc(FILE* f); fputc(int c, FILE *f); getc(FILE* f); putc(int c, FILE *stream);

Funcia fgetc i macrodefiniia getc returneaz urmtorul caracter din fiierul f (dup ce l convertete la reprezentarea de tip ntreg fr semn). Dac s-a ajuns la sfritul fiierului, funcia va ntoarce EOF (valoarea -1). Tot EOF va ntoarce i dac sunt probleme la citirea din fiier. Funcia fputc i macrodefiniia putc scriu caracterul c n fiierul f. n caz de eroare se returneaz valoarea c, altfel se returneaz EOF.

68

Fiiere de date

Funcia ungetc pune caracterul c n zona tampon de citire asociat fiierului f. La urmtoarea citire cu fread sau getc acesta va fi primul octet/caracter citit. Un al doilea apel al funciei ungetc, fr s fie citit primul caracter pus n flux, l va nlocui pe acesta. Apelarea funciilor fflush, fseek, fsetpos, sau rewind terge aceste caractere din flux. n caz de succes, ungetc returneaz caracterul c, iar n caz de eroare returneaz EOF. Transferul de iruri de caractere se efectueaz prin funciile:
char* fgets(char* s,int n,FILE* f); int fputs(const char* s,FILE* f);

Funcia fgets citete un ir de caractere din fiierul f i l depune la adresa s. Transferul se ncheie atunci cnd s-au citit n-1 caractere sau s-a ntlnit caracterul newline. La terminarea transferului, se adaug la sfritul irului din memorie caracterul nul \0. Dac citirea s-a terminat prin ntlnirea caracterului newline, acesta va fi transferat n memorie, caracterul nul fiind adugat dup el (spre deosebire de gets, care nu l reine). La ntlnirea sfritului de fiier (fr a fi transferat vreun caracter) sau n caz de eroare fgets returneaz NULL. n caz de succes returneaz adresa irului citit (aceeai cu cea primit n parametrul s). Funcia fputs scrie n fiierul f caracterele irului aflat la adresa s. Terminatorul de ir (\0) nu este scris i nici nu se adaug caracterul newline (spre deosebire de puts). n caz de succes fputs returneaz ultimul caracter scris. n caz de eroare returneaz EOF. Transferul de date cu format controlat este realizat prin funciile:
int fprintf(FILE* f,const char* format[,]); int fscanf(FILR* f,const char* format[,]);

Cele dou funcii lucreaz identic cu printf i scanf. Singura diferen const n fiierul n/din care se transfer datele. Dac printf i scanf lucreaz cu fiierele standard stdin i stdoud, pentru fprintf i fscanf este necesar precizarea explicit a fiierului cu care se lucreaz, prin parametrul f. Dei nu lucreaz cu fiiere n mod direct, se pot folosi i funciile
int sprintf(char *s,const char *format[,...]); int sscanf(const char *s,const char *format[,...]);

Aceste funcii lucreaz identic cu printf i scanf, diferena constnd n entitatea din/n care se transfer datele. n locul fiierelor standard, acest funcii folosesc o zon de memorie de tip ir de caractere, a crei adres este furnizat n parametrul s. irul de la adresa s poate fi obinut prin transfer fr format dintr-un fiier text (pentru sscanf) sau poate urma s fie scris ntr-un fiier text prin funcia fputs.

69

Programarea calculatoarelor

Pentru tratarea erorilor se folosesc urmtoarele funcii:


void clearerr (FILE* f);

Funcia reseteaz indicatorii de eroare i indicatorul de sfrit de fiier pentru fiierul f (se nscrie valoarea 0). Odat ce indicatorii de eroare au fost setai la o valoare diferit de 0, operaiile de intrare/ieire vor semnala eroare pn la apelul lui clearerr sau rewind.
int ferror (FILE* nume_intern);

Ferror este o macrodefiniie care returneaz codul de eroare al ultimei operaii de intrare/ieire asupra fiierului nume_intern (0 dac nu s-a produs eroare). Exemplu:
#include <stdio.h> int main(void) { FILE *f; /* deschide fisierul pentru scriere*/ f=fopen("test.ttt","w"); /* se produce eroare la incercarea de citire */ getc(f); if(ferror(f)) /* s-a produs eroare de I/E? */ {/* afiseaza mesaj de eroare */ printf("Eroare al citirea din test.ttt\n"); //reseteaza indicatorii de eroare si sfarsit fisier clearerr(f);} fclose(f); return 0;}

de

Exemplu: S se scrie un program care calculeaz i afieaz valoarea unei funcii introduse de la tastatur ntr-un punct dat. Funcia se introduce ca ir de caractere i poate conine apeluri de funcii standard C (vezi i [Smeu95]). Programul creeaz un fiier surs C (n care este scris forma funciei, ca subprogram C), apoi compileaz i execut un alt program, care va include subprogramul creat. Descrierea funciei introduse de la tastatur trebuie s conin maxim 200 caractere. a) Fiierul 51_iii_a.cpp conine programul care realizeaz citirea formei funciei, compilarea i execuia programului care calculeaz valoarea funciei.
#include<stdlib.h> #include<stdio.h> #include<conio.h> #include<string.h> #include<process.h> void main()

70

Fiiere de date
{ char s1[213]="return("; char s2[]="double f(double x)\r\n\{\r\n"; FILE *f; int n,i,j; f=fopen("functie.cpp","w"); fputs(s2,f); printf("functia f(x)="); gets(&s1[7]); strncat(s1,");\r\n}",6); fputs(s1,f); fclose(f); system("bcc Id:\borlandc\include -Ld:\borlandc\lib 51_iii_b.cpp>> tmp.txt"); execl("51_iii_b ",NULL); }

b) Fiierul 51_iii_b conine programul care face citete punctul x, calculeaz valoarea funciei n acest punct i o afieaz.
#include<stdio.h> #include<conio.h> #include<math.h> #include"functie.cpp" void main() { double x; printf("x=");scanf("%lf",&x); printf("f(%7.2lf)=%7.2lf",x,f(x)); getch(); }

71

6 Algoritmi de prelucrare a fiierelor binare

Din punct de vedere al operaiilor de gestiune solicitate de diverse aplicaii, fiierele binare se pot grupa n: fiiere care nu sunt actualizate (inute la zi) i fiiere care sunt actualizate. De obicei, fiierele din prima grup se regsesc n aplicaii matematice sau ca fiiere temporare i de tranzacii n aplicaii de gestiune economic. Fiierele din cea de-a doua grup sunt, de obicei, fiiere permanente (principale) n aplicaii de gestiune economic i au particulariti de proiectare, referitoare, n special, la asigurarea tergerii i adugrii de articole.

6.1 Caracteristici generale ale algoritmilor de prelucrare a fiierelor


Organizarea datelor n fiiere memorate pe medii magnetice externe presupune proiectarea unor algoritmi specifici operaiilor de gestiune a acestora, denumii generic algoritmi de prelucrare a fiierelor de date. Datorit complexitii aplicaiilor care prelucreaz fiiere este recomandat aplicarea metodei modularizrii algoritmilor i programelor. Modularizarea presupune ca, pe baza analizei problemei, s se descompun rezolvarea ei n pri distincte, numite module, astfel nct fiecare dintre acestea s ndeplineasc anumite funcii. Descompunerea se poate realiza n mai multe faze (pe mai multe niveluri), prin metoda top-down. Criteriile de descompunere n module depind, n mare msur, de experiena programatorilor. Ele se refer, n principal, la: omogenizarea funciilor; utilizarea diverselor structuri de date; separarea funciilor de intrare/ieire de funciile de prelucrare; utilizarea unor module deja existente; utilizarea eficient a resurselor calculatorului (timp UC, memorie intern, periferie) etc. Modulele se implementeaz n program prin subprograme interne sau externe. De cele mai multe ori, o aplicaie necesit existena mai multor fiiere

72

Algoritmi de prelucrare a fiierelor binare

active simultan, cu rol diferit (de intrare, de ieire, de intrare/ieire). Indiferent de numrul fiierelor utilizate, n marea majoritate a algoritmilor, logica prelucrrii este coordonat, la un moment dat, de un singur fiier, obligatoriu de intrare, parcurs secvenial, numit fiier conductor (sau director). Fiierul conductor are proprietatea c articolele lui pot fi citite logic independent de prelucrarea altor fiiere. Altfel spus, un fiier nu este conductor dac prelucrarea articolelor sale este dependent de existena (de citirea) articolului altui fiier. Accesul la datele memorate n fiierul conductor se realizeaz la nivel de articol. De aceea, algoritmii de prelucrare, indiferent de operaia de gestiune, necesit utilizarea unei structuri repetitive pentru parcurgerea (parial sau integral) a fiierului respectiv. Algoritmii de prelucrare cu fiier conductor pot fi reprezentai prin schema logic generalizat, conceput modularizat, redat n figura 6.1.

Figura 6.1 Schema logic general a unui algoritm de prelucrare cu fiier conductor Modulul NCEPUT se realizeaz o singur dat, naintea prelucrrii primului articol al fiierului conductor i cuprinde urmtoarele grupe de operaii: Operaii iniiale standard, obligatorii oricrui algoritm i care includ: punerea n coresponden a fiierelor logice cu fiiere fizice, deschiderea fiierelor, i, pentru anumite variante, iniializarea unei variabile logice pentru sfrit de fiier (SF) i citirea primului articol. Operaii iniiale specifice, facultative, existena lor depinznd de particularitile problemei abordate i care includ, n principal: iniializri de variabile de total, afiri ale antetului, titlului i/sau a capului de tabel pentru situaii de ieire etc. Modulul PRELUCRARE se execut repetitiv i cuprinde, pe de o parte,

73

Programarea calculatoarelor

totalitatea operaiilor de prelucrare a articolului curent al fiierului conductor operaii specifice fiecrei probleme - i, pe de alt parte, citirea unui articol din fiierul conductor. Ordinea celor dou operaii (citire i prelucrare) depinde de varianta de algoritm aleas. Modulul SFRIT se execut o singur dat, dup prelucrarea ultimului articol al fiierului conductor i include urmtoarele grupe de operaii: operaii finale standard, corespunznd nchiderii fiierelor implicate n prelucrare; operaii finale specifice, care depind de natura problemei i includ, de regul: afiarea variabilelor de total, a statisticilor privind operaiile de gestiune executate, nchiderea situaiilor de ieire etc. Modalitatea de detectare/tratare a sfritului de fiier conduce la existena mai multor variante ale schemei generale de prelucrare cu fiier conductor, prin forme particulare ale condiiei sfrit_de_prelucrare. n funcie de variantele alese, se pot construi scheme logice valabile pentru toate tipurile de fiiere sau numai pentru fiierele binare. Scheme valabile pentru toate tipurile de fiiere Detectarea sfritului de fiier, cu macrodefiniia feof, caz n care testarea sfritului de fiier trebuie s urmeze dup o operaie de citire a unui articol. Algoritmul trebuie s conin o citire iniial n modulul NCEPUT i o citire curent la sfritul modulului PRELUCRARE - (figura 6.2). Acest algoritm se poate aplica fiierelor vide sau nevide.

Operatii initiale

Operatii finale

Figura 6.2 Scheme logice valabile numai pentru fiiere binare

74

Algoritmi de prelucrare a fiierelor binare

Detectarea sfritului de fiier prin operaia de citire, verificnd rezultatul ntors de funcia de citire (fread). Dac rezultatul este mai mic dect numrul de blocuri de date care trebuie citite, nseamn c s-a ajuns la sfritul fiierului. ntruct, uzual, la o operaie de citire se citete un articol ntreg, n cazul atingeri sfritului de fiier, rezultatul ntors de funcia fread va fi 0. Rezultatul poate fi preluat ntr-o variabil pentru a fi folosit n condiia de terminare a prelucrrii (figura 6.3) sau poate fi verificat direct, folosind apelul funciei fread n expresia (condiia) care controleaz sfritul prelucrrii (figura 6.4). n ambele variante fiierul conductor este binar, vid sau nevid.
START

Operaii iniiale Operatii initiale

SF = fread ()

SF != 0

Da Prelucrare articol

Nu

Operaii finale Operatii finale

SF = fread ()

STOP

Figura 6.3

Operatii initiale

Operatii finale

Figura 6.4

75

Programarea calculatoarelor

Prelucrarea unui numr cunoscut de articole, prin determinarea n modulul NCEPUT a numrului de articole din fiierul conductor se regsete n figura 6.5. Limbajul C nu ofer o funcie standard pentru calcularea numrului de articole dintr-un fiier binar, deoarece, din punctul de vedere al limbajului, fiierele nu conin articole. Din punctul de vedere al utilizatorului, cunoscnd dimensiunea unui articol, se poate calcula numrul de articol de fiier, mprind lungimea acestuia la lungimea unui articol (ambele msurate n numr de octei). Lungimea fiierului este egal cu poziia curent, atunci cnd pointerul de citire se afl la sfritul fiierului. Pentru aflarea numrului de articole, se folosete secvena urmtoare:
p=ftell(f); fseek(f,0,SEEK_END); l=ftell(f); nr=l/sizeof(tip_articol); fseek(f,p,SEEK_SET);

unde: variabila p, de tip long reine poziia curent n fiier; f este fiierul a crui lungime trebuie calculat; variabila l reine poziia curent (n numr de octei fa de nceputul fiierului, deci lungimea fiierului msurat n octei); variabila nr va primi ca valoare numrul de articole din fiier; tip_articol este tipul articolelor din fiier (din punctul de vedere al utilizatorului). mprirea se face exact, deoarece fiierul conine un numr ntreg de articole utilizarea acestei secvene asupra unui fiier care conine articole de alt tip (sau are coninut de alt natur) va duce la rezultate incorecte.

Operatii initiale

Operatii finale

Figura 6.5

76

Algoritmi de prelucrare a fiierelor binare

Caracteristica general a algoritmilor de prelucrare cu fiier conductor este parcurgerea secvenial a fiierului conductor i efectuarea unor prelucrri n funcie de fiecare articol citit din acesta. Problema care se pune este detectarea sfritului de fiier. n C, macrodefiniia feof nu face dect s furnizeze valoarea indicatorului de sfrit de fiier, care este setat de operaia de citire; n program, citirea trebuie s apar naintea verificrii sfritului de fiier. Forma general a algoritmului este:
<citire articol> while(!feof(f)) { <prelucrare articol citit> <citire articol> }

Exemplu: Crearea i consultarea unui fiier text care memoreaz elemente ntregi, folosind funcia feof pentru gestionarea sfritului de fiier. La crearea fiierului, fiier conductor este fiierul standard de intrare. La afiare, conductor este fiierul f.
#include<stdio.h> #include<conio.h> void main() { FILE *f; int x; long dim; clrscr(); f=fopen("numere.dat","w+"); scanf("%d",&x); while(!feof(stdin)) {fprintf(f,"%d\n",x); scanf("%d",&x);} fseek(f,0,SEEK_SET); fscanf(f,"%d",&x); while(!feof(f)) {printf("%d\t",x); fscanf(f,"%d",&x);} fclose(f); getch();}

Acelai exemplu, folosind fiier binar:


#include<stdio.h> #include<conio.h> void main() { FILE *f; int x,g; long dim; clrscr(); f=fopen("numere.dat","wb+"); scanf("%d",&x); while(!feof(stdin)) {fwrite(&x,sizeof(x),1,f); scanf("%d",&x);} fseek(f,0,SEEK_SET); fread(&x,sizeof(x),1,f); while(!feof(f)) {printf("%d\t",x); fread(&x,sizeof(x),1,f);} fclose(f); c=getch();}

77

Programarea calculatoarelor

Fiierele utilizate ntr-o aplicaie informatic au rol diferit n procesul prelucrrii, n funcie de scopul lor: de intrare, de ieire, de intrare/ieire, temporare, de tip list etc. Aceste caracteristici conduc la algoritmi specifici fiecrei operaii de gestiune n parte (creare, populare, consultare i actualizare), fiind ns variante derivate din schema general a unui algoritm de prelucrare cu fiier conductor. Deoarece aplicaiile informatice din domeniul economic, social, administrativ etc. utilizeaz, cu predilecie, fiiere cu articole de aceeai structur (sau un numr mic de structuri diferite), alegnd limbajul C, se poate aprecia c cele mai performante sunt fiierele binare, ale cror articole sunt date declarate ca structuri (folosind tipul de date struct). Aceast alegere este motivat din urmtoarele puncte de vedere: descrierea articolelor este apropiat att descrierii naturale a structurii unei entiti din lumea real (format din cmpuri cu nume, lungime, reprezentare intern proprie, semnificaie i factor de repetabilitate diferite), ct i descrierii din alte limbaje; exist posibilitatea de a descrie explicit mai multe structuri pentru articolele aceluiai fiier (articole cu structur variabil); operaiile de acces la nregistrri se realizeaz cu vitez mare, datorit lipsei conversiilor la transferul ntre memoria principal i memoria extern. Fiierele cu coninut de tip text sunt recomandate a fi utilizate ca fiiere de ieire, pentru realizarea de liste, situaii finale, rapoarte etc., fiind rezidente pe disc, n general, pn la listarea lor la imprimant. Fiierele cu coninut de tip text pot constitui i sursa de creare a fiierelor binare, dac acestea au fost populate cu date, fie prin editoare de texte, fie prin alte limbaje (Cobol, Fortran, Basic, Pascal), sisteme de gestiune a bazelor de date (DBase, FoxPro, Oracle etc.), constituind unicul mijloc de compatibilitate direct.

6.2 Algoritmi de prelucrare a fiierelor binare care nu necesit actualizare


Asupra fiierelor binare care nu necesit actualizare se realizeaz, de obicei, operaiile de creare (populare) i consultare. Dintre operaiile de actualizare pot fi realizate, fr mari complicaii, modificarea i adugarea dens de articole. Popularea fiierelor se realizeaz prin preluarea datelor fie din alte fiiere primare (cu coninut binar sau de tip text), fie de la tastatur (popularea interactiv). n ultimul caz, cel mai des ntlnit n practic, fiierul conductor corespunde mulimii datelor introduse de la tastatur. Articolele sunt preluate cmp cu cmp, neexistnd posibilitatea citirii unei variabile de tip articol i, n plus, introducerea unei date este adesea nsoit de proceduri de validare specifice, cu reintroducerea ei n cazul unei erori. Sfritul introducerii datelor de la tastatur (i implicit al procesului de populare a fiierului) poate fi: De tip chestionar, prin consultarea utilizatorului, privind continuarea sau nu a introducerii articolelor. Pentru un volum mare de date, varianta prezint dezavantajul

78

Algoritmi de prelucrare a fiierelor binare

mririi timpului de prelucrare. Convenional, prin introducerea pentru primul cmp din articol a unei valori prestabilite, cu semnificaie de sfrit de prelucrare. Standard, prin introducerea caracterului CTRL-Z, cu rol de sfrit de fiier text. Schema logic a algoritmului de prelucrare este similar celei din figura 6.2., cu urmtoarele particulariti: modulul NCEPUT are ca ultime operaii, afiarea numelui primului cmp din articol i citirea valorii sale; modulul PRELUCRARE ncepe cu citirea urmtorului cmp, urmat de citirea celorlalte cmpuri (eventual cu validrile stabilite) i se termin cu afiarea numelui primului cmp i cu citirea valorii acestuia (pentru articolul urmtor) (similar operaiei din modulul NCEPUT). O alt problem a populrii fiierelor binare o reprezint aezarea articolelor pe suportul extern. Din acest punct de vedere se ntlnesc dou modaliti: Populare dens, prin care articolele se scriu unul dup altul, n ordinea n care au fost furnizate, fr a se lsa locuri libere (acces secvenial). Pentru fiierele care nu necesit actualizare acesta este tipul recomandat. Populare aleatoare, prin care articolele sunt scrise n casetele (virtuale) ale cror numere relative sunt furnizate explicit de utilizator (acces direct). Scrierea unui articol se realizeaz dup poziionarea pe numrul relativ dorit. La populare, nr_relativ nu este limitat dect de spaiul existent pe suportul extern. Metoda are dezavantajul c necesit evidena "articolelor vide". n cazul fiierelor care nu necesit actualizare, popularea aleatoare se recomand numai dac, dup creare, fiierul este dens. Pentru poziionarea pe articolul cu numrul relativ n se folosete funcia fseek astfel:
fseek(f, n*sizeof(tip_articol), SEEK_SET);

unde n este numrul relativ al articolului iar tip_articol este tipul de dat care i corespunde. Exemplu: 1. S se creeze cu populare dens un fiier PRODUSE.DAT cu informaii despre producia cantitativ ntr-un an, la o societate comercial. Articolele au urmtoarea structur logic: Cod produs Denumire Produs Pre Mediu 1 Cantiti lunare 2 ... 12

Articolele sunt introduse de la terminal, cmp cu cmp. Terminarea introducerii datelor este marcat standard, prin introducerea caracterului CTRL-Z.

79

Programarea calculatoarelor

#include <stdio.h> typedef struct { int cod; char denumire[20]; float pret_mediu; int cant[12]; } PRODUS; void main() { FILE* f; PRODUS x; char nume_fisier[20]; int i; //---INCEPUT--printf("\n\nNumele fisierului: "); gets(nume_fisier); if(!(f=fopen(nume_fisier,"wb"))) printf("\n\nNu fisierul cu numele %s",nume_fisier); else { printf("\nCod produs: "); scanf("%d",&x.cod); //---Aici se termina operatiile initiale--while(!feof(stdin)) { //---PRELUCRARE ARTICOL--printf("Denumire produs: "); fflush(stdin); gets(x.denumire); printf("Pret mediu: "); scanf("%f",&x.pret_mediu); printf("Cantitate lunara:\n"); for(i=0;i<12;i++) { printf(" - luna %d: ",i+1); scanf("%d",&x.cant[i]); } fwrite(&x,sizeof(PRODUS),1,f); //---Aici se incheie prelucrarea articolului--printf("\nCod produs: "); scanf("%d",&x.cod); } //---SFIRSIT--fclose(f); } }

poate

fi

creat

Observaii: Dac se dorete crearea fiierului de date cu populare n acces direct, programul este similar, cu urmtoarele diferene: cmpul COD indic numrul relativ al articolului n fiier i nu va fi memorat (nu va face parte din declaraia tipului PRODUS), fiind redundant; scrierea articolului va fi precedat de apelul funciei
fseek(f,codt*sizeof(PRODUS),SEEK_SET);

unde codt este o variabil independent n care se citete codul de la terminal. Consultarea fiierelor are numeroase variante, n funcie de scopul prelucrrii. Dup modul de regsire a articolelor n cadrul fiierului, ea poate fi secvenial, direct sau mixt.

80

Algoritmi de prelucrare a fiierelor binare

Consultarea secvenial presupune regsirea articolelor n ordinea n care au fost scrise pe suportul tehnic de informaii. Dup numrul articolelor prelucrate, consultarea secvenial poate fi: integral, cnd se prelucreaz toate articolele fiierului, ncepnd cu primul i terminnd cu ultimul; cu selecie, cnd se prelucreaz numai acele articole care au una sau mai multe caracteristici comune (valori identice pentru acelai cmp). Dup numrul de caracteristici, selecia poate fi simpl, dubl, multipl. Pentru consultarea secvenial se poate utiliza oricare din tipurile de algoritmi prezentai anterior. Exemplu: 2. S se afieze pe ecran coninutul fiierului creat la exemplul 1.
#include <stdio.h> typedef struct { int cod; char denumire[20]; float pret_mediu; int cant[12]; } PRODUS; void main() { FILE* f; PRODUS x; char nume_fisier[20]; int i; //---INCEPUT--printf("\n\nNumele fisierului: "); gets(nume_fisier); if(!(f=fopen(nume_fisier,"rb"))) printf("\n\nNu poate fisierul cu numele %s",nume_fisier); else { fread(&x,sizeof(PRODUS),1,f); //---Aici se termina operatiile initiale--while(!feof(f)) { //---PRELUCRARE ARTICOL--printf("\n\nCod produs:\t\t%d",x.cod); printf("\nDenumire produs:\t%s",x.denumire); printf("\nPret mediu:\t\t %7.2f",x.pret_mediu); printf("\nCantitati lunare:\t"); for(i=0;i<12;i++) printf("%3d ",x.cant[i]); //---Aici se incheie prelucrarea articolului--fread(&x,sizeof(PRODUS),1,f); } //---SFIRSIT--fclose(f); } }

fi

deschis

3. Obinerea unei situaii cu mai multe grade de total. Pentru aceasta se stabilesc cmpuri asociate gradelor de total, numite caracteristici de grupare sau caracteristici de control. O caracteristic de control este un cmp al articolului din fiierul de date, care are aceeai valoare pentru mai multe nregistrri. Astfel, articolele care au valoare comun pentru o caracteristic de grupare se pot ordona pe submulimi, formnd o grup de control. Fiierul poate constitui, n ansamblul su, caracteristica

81

Programarea calculatoarelor

de grupare de cel mai nalt nivel, pentru care se poate calcula totalul general. Numrul maxim de grade de total este superior cu unu numrului de caracteristici de control stabilite. ntre caracteristicile de grupare se stabilete o relaie de ordine ierarhic. Pentru prelucrarea fiierului, cu utilizare minim de memorie, articolele trebuie sortate dup caracteristicile de control. Acest tip de prelucrare intr n categoria consultrilor secveniale integrale i urmeaz algoritmul de principiu din figurile 6.2 sau 6.3. Prelucrarea unui fiier sortat dup criteriile enunate, presupune existena unor operaii standard, executate la schimbarea valorii fiecrei caracteristici de control stabilite: operaii iniiale ale unei grupe de control prin care se iniializeaz variabila de total specific grupei; se salveaz valoarea caracteristicii primului articol din grup; alte operaii iniiale specifice grupei; operaii finale ale unei grupe de control prin care se afieaz totalul calculat pentru caracteristica ce se schimb; se cumuleaz totalul grupei curente la totalul grupei ierarhic superioare; alte operaii finale specifice grupei; condiia de prelucrare a unei grupe de control conine, pe lng condiia specific, toate celelalte condiii din amonte. Raportul final este listat la imprimant, fie direct, ca fiier de ieire, fie creat pe suport magnetic, ca fiier text, n vederea imprimrii ulterioare. Structura unei pagini a raportului i controlul trecerii la o nou pagin trebuie asigurate de programator. n continuare (figura 6.6) se prezint structura de principiu a unui program de obinere a unui raport final, cu control dup dou caracteristici i trei grade de total, unde cmp_1 i cmp_2 sunt caracteristicile de control (cmpuri din articol), v1 i v2 sunt variabile de lucru pentru salvarea caracteristicilor, val_art e valoarea care intereseaz din fiecare articol (cmp al articolului sau valoare calculat pe baza unor cmpuri ale articolului), iar TOTG, TOT1 i TOT2 sunt variabile pentru calculul gradelor de total. Analog, se poate extinde pentru oricte caracteristici i grade de total.

82

Algoritmi de prelucrare a fiierelor binare


START Operaii iniiale generale TOTG=0

Citete articol

!feof(f)

Da Operaii iniiale Grupa 1 TOT1=0 v1=cmp_1

! feof(f) i v1==cmp_1 Nu Operaii finale Geupa 1 TOTG+=TOT1

Da Operaii iniiale Grupa 2 TOT2=0 v2=cmp_2

! feof(f) i v1==cmp_1 i v2==cmp_2

Da Prelucrare articol

Operaii finale generale

Nu

Operaii finale Grupa 2 TOT1+=TOT2

TOT2+=val_art Citete articol

STOP Nu

Figura 6.6 Schema logic problema cu grade de total Consultarea n acces direct presupune regsirea articolului dup numrul relativ. ntruct fiierele sunt considerate ca fluxuri de octei, trebuie calculat poziia articolului dorit n fiier ca produs ntre numrul su relativ i dimensiunea unui articol n octei (nr*sizeof(tip_articol)). Secvena care realizeaz acest lucru este: fseek(f,nr*sizeof(tip_articol), SEEK_SET); fread(&art,sizeof(tip_articol), 1, f);

83

Programarea calculatoarelor

Numrul relativ este furnizat de utilizator i trebuie s aparin domeniului 0..dimensiune fiier-1 (dimensiunea calculat ca numr de articole). Pentru evitarea situaiilor n care numrul relativ se afl n afara acestui domeniu, se va include n program validarea apartenenei numrului relativ la intervalul acceptat. Algoritmul de consultare n acces direct a unui fiier are un alt fiier conductor (de exemplu tastatura). Exemplu:
4.
{ // citire nume fisier extern f=fopen(nume_fisier, "rb"); // calculare numar de articole din fisier printf("\nNr. relativ: "); scanf("%d",&r); //citirea numarului relativ al articolului while(!feof(stdin)) { if(r>=nr_art) printf("\n Articol inexistent !"); else { fseek(f,r*sizeof(tip_articol),SEEK_SET); fread(&art,sizeof(tip_articol),1,f); // -----------------------//PRELUCRARE ARTICOL //-----------------------} printf("\nNr. Relativ (sau CTRL-Z): "); scanf("%d",&r); } fclose(f); }

Consultarea n acces mixt utilizeaz o combinaie ntre accesul direct i cel secvenial, n vederea prelucrrii unui grup de articole, memorate contiguu n fiier i selectabile printr-o condiie. Pentru fiierele binare, metoda poate fi aplicat dac se dorete selectarea articolelor dintre dou limite ale numerelor relative (limita inferioar - li i limita superioar - ls). Algoritmul trebuie s verifice relaia 0lilsdimensiune fiier, dup care parcurgerea fiierului poate fi realizat prin orice tip de structur repetitiv. Exemplu: 5.
{ // citire nume fisier extern f=fopen(nume_fisier, "rb"); // calculare numar articole din fisier printf("\nLimita inferioara: "); scanf("%d",&li); // citirea nr. relativ al primului articol // din secventa printf("\nLimita superioara: "); scanf("%d",&ls); // citirea nr. relativ al ultimului articol // din secventa if((0<li)&&(li<=ls)&&(ls<=nr_art)) { fseek(f,li*sizeof(tip_articol),SEE_SET); for(i=li;i<=ls;i++)

84

Algoritmi de prelucrare a fiierelor binare


{ fread(&art,sizeof(tip_articol),1,f); // ----------------------// Prelucrare articol // ----------------------} } else printf(" Nu este indeplinita conditia de limite"); fclose(f) }

Adugarea de articole se realizeaz, n general, cu tranzacii de la terminal, similar operaiei de populare. Pentru o corect exploatare ulterioar, adugarea trebuie s fie dens. Acest lucru poate fi realizat astfel: Adugare la sfrit (extindere), dup ultimul articol scris. Operaia se realizeaz similar populrii n acces secvenial, dup poziionarea pe marcatorul de sfrit de fiier, apelnd funcia fseek: fseek(f,0,SEEK_END); Exemplu: 6.
{ // citire nume fisier extern f=fopen(nume_fisier, "rb"); fseek(f,0,SEEK_END); // pozitionare dupa ultimul // articol scris printf("Cimp 1: "); scanf("%d ",&art.cimp_1); while(!feof(stdin)) { // ----------------------------------------------// Preluare de la tastatura a celorlalte // campuri din articol // ----------------------------------------------printf("Cimp 1: "); scanf("%d ",&art.cimp_1); } fclose(f) }

Inserarea unor articole. Se aplic n cazul n care articolele sunt scrise n fiier n ordinea cresctoare (descresctoare) a valorilor unui anumit cmp. n acest caz, noul articol va fi inserat ntre dou articole, astfel: se caut (cu un algoritm secvenial, binar etc.) poziia k n care trebuie inserat noul articol; se copiaz, glisnd cu o poziie spre dreapta, toate articolele de la sfritul fiierului pn la articolul cu numrul relativ k; se scrie n acces direct noul articol, n poziia k.

85

Programarea calculatoarelor

Exemplu: 7.
{ // articolele fisierului sunt in ordinea // crescatoare a valorii campului 1 // citire nume fisier extern *) f=fopen(nume_fisier, "rb+ "); // calculare numar de articole din fisier printf("\nCimp 1: "); // introducerea campului dupa care // sunt sortate articolele scanf("%d ",&art_nou.cimp_1); while(!feof(stdin)) // adaugarea mai multor articole { // ----------------------------------// Preluare de la tastatura a celorlalte // campuri din articolul de adaugat // ----------------------------------// secventa de cautare a pozitiei //in care se va insera articolul } fseek(f,0,SEEK_SET); // pozitionare pe inceput de fisier fread(&art_existent,sizeof(tip_articol),1,f); while((!feof(f))&&(art_existent.cimp_1<art_nou.cimp_1) fread(&art_existent,sizeof(tip_articol),1,f); if(!feof(f)) { k=ftell(f)-sizeof(tip_articol); //articolul se va // insera in pozitia k for(i=nr_art-1;i>=k;i--) { fseek(f,i*sizeof(tip_articol),SEK_SET); fread(&art_existent,sizeof(tip_articol),1,f); fwrite(&art_existent,sizeof(tip_articol),1,f); } } else k=nr_art; // articolul se adauga la sfirsitul // fisierului fseek(f,k*sizeof(tip_articol),SEK_SET); fwrite(&art_nou,sizeof(tip_articol),1,f); printf("\nCimp 1: "); // introducerea campului dupa care // sunt sortate articolele scanf("%d ",&art_nou.cimp_1);rite('Camp 1: ') } fclose(f); }

Modificarea valorii unor cmpuri din articol se realizeaz n mai multe etape: se citete articolul care se modific (fseek i fread); se modific (n zona articol din memoria principal) cmpurile cu valorile dorite, introduse, n general, de la tastatur; se repoziioneaz pe articolul respectiv cu fseek(f,ftell(f)-sizeof(tip_articol),SEEK_SET); se scrie articolul modificat, cu funcia fwrite. O problem important rmne selectarea cmpurilor care se modific, pentru fiecare articol n parte. O variant simpl este afiarea vechii valori, urmat de introducerea noii valori, n variabile independente de tip ir de caractere. n cazul n care irul introdus este vid (s-a apsat numai ENTER), respectivul cmp, prin convenie, nu se modific.

86

Algoritmi de prelucrare a fiierelor binare

Altfel, cmpului respectiv i se va atribui valoarea citit n variabila independent, eventual prin conversie, pentru cmpurile numerice. Exemplu:
8.
// -----------------------------// cautare articol de modificat // ------------------------------ *) fread(&art,sizeof(tip_articol),1,f); printf("Codul: %d - ",art.cod); //afisare vechea valoare fflush(stdin); gets(cods); // citire noua valoare; cods este de tip sir if(strlen(cods)) { art.cod=atoi(cods); // conversie din ASCII in binar printf("Denumire: %s - ",art.den); // afisare vechea // valoare gets(dens); // citire noua valoare if(strlen(dens) strcpy(dens,art.den); // copiere noua valoare // ---------------------------------// Introducerea celorlalte campuri // din articol // ---------------------------------// repozitionare pe articol feek(f,ftell(f)-sizeof(tip_articol),SEEK_SET); // rescriere articol modificat fwrite(&art,sizeof(tip_articol),1,f); }

O alt variant se poate realiza prin folosirea unei machete de ecran n care se afieaz valorile actuale ale fiecrui cmp de modificat, se poziioneaz succesiv cursorul la nceputul fiecrui cmp, cu dou rspunsuri posibile ale utilizatorului: <ENTER>, caz n care se menine actuala valoare, respectiv o tast diferit de <ENTER>, reprezentnd primul caracter al noii valori.

6.3 Algoritmi de prelucrare a fiierelor binare care necesit actualizare


Prelucrarea fiierelor binare care necesit actualizare trebuie s asigure posibilitatea tergerii articolelor i s elimine riscul de suprascriere a articolelor adugate. Pentru aceasta, trebuie proiectate structuri particulare de articole i concepute operaii de gestiune specifice. Fr a epuiza multitudinea soluiilor de rezolvare a problemelor de gestiune a fiierelor care necesit actualizare, n continuare, se prezint cteva soluii posibile. n orice situaie, limitrile de regsire prin acces secvenial sau relativ a articolelor n fiier reduc aria folosirii limbajului n probleme de gestiune. Marele inconvenient l constituie lipsa accesului dup cheie, cel care corespunde cel mai bine gestiunii n sistemele reale.

87

Programarea calculatoarelor

n cele ce urmeaz se analizeaz trei tipuri de probleme: probleme n care se utilizeaz asocierea extern a numrului relativ la articolul corespunztor (codificare prin numr relativ); probleme n care se utilizeaz asocierea intern a numrului relativ la articolul corespunztor, iar acesta poate emana extern (se genereaz nomenclatoare dup fiecare actualizare de fiier); probleme n care se utilizeaz extern coduri (chei) i intern numere relative. 6.3.1 Probleme care utilizeaz codificarea extern prin numere relative Nomenclatorul de articole conine numrul relativ al fiecruia dintre ele. Nomenclatorul este elaborat extern (automat sau neautomat). Orice operaie de regsire n acces relativ presupune introducerea din exterior a numrului relativ. La crearea iniial, fiecare articol este nscris la numrul su relativ predefinit. Asigurarea tergerii i adugrii controlate poate fi fcut n diverse moduri: Extinderea articolelor logice cu un indicator de stare (un octet), ajungndu-se la forma din figura 6.8.
IS Articol propriu-zis

Figura 6.8 Structura articolului care include indicatorul de stare Indicatorul de stare (notat IS) poate lua una din cele dou valori posibile (de exemplu 0 pentru articol inactiv inexistent sau ters, 1 pentru articol prezent). Cu aceast convenie, operaiile de acces la articole se realizeaz n urmtoarele condiii: scrierea n fiier este permis numai pentru articolele cu IS=0; citirea din fiier este permis numai pentru articolele cu IS=1. Preformarea presupune deschiderea fiierului ca nou (crearea unui fiier nou) i scrierea unui numr de articole (la limit, zero) cu IS=0. Includerea operaiei de preformare conduce la dispariia distinciei dintre populare i adugare. Datorit faptului c fiierul se deschide ca existent, orice operaie de scriere a unui nou articol se trateaz ca adugare. ntr-un sistem de programe, deschiderea cu modul wb a unui fiier se realizeaz o singur dat, n procedura de preformare. Scrierea n acces direct presupune furnizarea numrului relativ (nr) al articolului. n funcie de valoarea lui nr se disting urmtoarele situaii: - dac nr<dimensiune fiier, se citete articolul respectiv din fiier i adugarea este permis numai dac IS=0; - dac nr>=FileSize(f), are loc extinderea fiierului cu preformarea articolelor cu numerele relative cuprinse n domeniul dimensiune fiier..nr-1. Noul articol se scrie pe poziia nr. Se remarc faptul c scrierea n acces direct permite preformarea iniial cu zero articole. Scrierea n acces secvenial se face fr verificare de existen. Scrierea are

88

Algoritmi de prelucrare a fiierelor binare

loc n poziia dat de pointerul curent. Procedura face IS=1. Utilizarea ei se recomand numai la popularea dens. Citirea n acces direct presupune furnizarea numrului relativ (nr). Ea verific dac IS=1. Citirea n acces secvenial analizeaz articolele ncepnd cu cel de la pointerul curent. Articolele cu IS=0 sunt ignorate, pn la ntlnirea primului articol cu IS=1 sau pn se ajunge la sfrit de fiier. tergerea se realizeaz n acces direct. Ea presupune citirea articolului i, dac tergerea este permis (IS=1), se modific indicatorul de stare (IS=0) i se scrie articolul pe vechiul loc. Rescrierea realizeaz scrierea unui articol n poziia ftell(f)sizeof(tip_articol), dac vechiul articol din aceast poziie are IS=1. Folosirea articolului zero ca tabel de ocupare n fiier. Fiecrui articol din fiier i corespunde cte un octet n primul articol: articolului cu numrul relativ i i corespunde octetul a[i]. Primul articol are structura char a[max], unde max este o constant care indic numrul maxim de articole pe care le poate avea fiierul pe durata existenei sale. Dac articolul i este prezent, a[i]=1; dac articolul i este inactiv (inexistent sau ters), a[i]=0. Cu aceast structur, operaiile de acces la articole se realizeaz n urmtoarele condiii: scrierea n fiier a articolului cu numrul relativ i este permis numai dac a[i]=0; citirea din fiier a articolului cu numrul relativ i este permis numai dac a[i]=1. tergerea articolului i presupune verificarea existenei sale (a[i]=1) i realizarea operaiei a[i]=0. Adugarea unui articol i presupune verificarea inexistenei lui (a[i]=0), nscrierea articolului i realizarea operaiei a[i]=1. Utilizarea acestei modaliti necesit ncrcarea iniial n memoria principal a articolului cu numrul relativ zero. n programele care realizeaz tergeri sau/i adugri, nainte de nchiderea fiierului trebuie rescris articolul zero n fiier. Datorit restriciei impuse pentru numrul de articole din fiier, acest model de gestiune a articolelor este ineficient pentru multe probleme. Se pot concepe algoritmi prin care n tabela de ocupare n fiier fiecrui articol i corespunde un bit, n loc de un octet. n acest fel numrul maxim de articole ce pot fi adugate n fiier se mrete de 8 ori. 6.3.2 Probleme care utilizeaz codificarea intern prin numere relative nscrierea articolelor n fiiere, chiar cea iniial, se face n primele articole inactive, asociindu-se astfel intern un numr relativ fiecrui articol. Deosebirea esenial ntre aceast soluie i cea prezentat anterior, const n modul de realizare a adugrii secveniale. Dup fiecare sesiune de actualizare va trebui listat nomenclatorul de coduri interne (numere relative). Articolelor din fiier li se asociaz structura din figura 6.8. Preformarea, consultarea, modificarea i tergerea sunt similare celor prezentate n 6.3.1. Adugarea unui articol se realizeaz n

89

Programarea calculatoarelor

condiii diferite, dup cum aceast operaie are loc printre articolele existente, respectiv dup ultimul articol (extindere). n ambele situaii, condiiile de realizare sunt determinate de modul de acces folosit: secvenial sau direct. Adugarea n acces secvenial se bazeaz pe presupunerea c utilizatorul nu impune o coresponden prestabilit ntre coninut i numrul articolului, adic n ali termeni, c se accept o codificare automat. Adugarea n acces secvenial poate fi utilizat n dou variante: Cu verificarea existenei de articole libere, caz n care se adaug noul articol n prima poziie gsit disponibil (IS=0), eventual la sfrit (extindere), dac nu mai exist articole libere n interior. Aceast variant presupune existena unei soluii de gestiune a articolelor libere (n urma preformrii sau a tergerii logice). Dintre soluiile posibile pot fi menionate: Folosirea articolului zero pentru colectarea numrului articolelor libere, ntr-o structur de forma celei din figura 6.9.
Articolul 0 nal WORD al[1] WORD al[2] WORD ... ... al[nal] WORD

Figura 6.9 Structura articolului zero, pentru gestiunea articolelor libere n aceast soluie, nal este numrul articolelor libere i al[i], cu i=1..nal, reprezint poziiile relative ale articolelor libere. Soluia prezint avantajul timpului redus de cutare i de atribuire a unei poziii pentru noul articol. Numrul de articole libere ce pot fi gestionate n acest mod este limitat de descrierea articolelor principale ale fiierului. De exemplu, dac articolul principal are 128 de octei, aceast soluie permite gestionarea a 63 de articole libere (dac se impune pentru articolul zero aceeai lungime ca i pentru celelalte articole; pentru primul articol se poate accepta i o dimensiune diferit mai mare dar nu cu mult mai mare i oricum este o dimensiune stabilit de la nceput, care nu mai poate fi mrit la nevoie). La tergerea logic a unui articol se realizeaz incrementarea valorii lui nal, iar al[nal] primete ca valoare numrul relativ al articolului ters. Folosirea articolului zero ca nceput al unei liste simple (sau dublu) nlnuite a articolelor libere, ntr-o structur de principiu de forma celei din figura 6.10.
pal ual

au

au ...

...

au

Figura 6.10 Gestionarea articolelor libere prin liste

90

Algoritmi de prelucrare a fiierelor binare

n aceast soluie, articolul zero puncteaz pe primul (pal) i pe ultimul (ual) articol liber, iar fiecare articol puncteaz pe urmtorul (au). Numrul articolelor libere care pot fi gestionate n acest mod este oarecare. La adugarea unui nou articol, se verific dac exist articole libere (pal<>0) i dac exist, se atribuie primul articol din list articolului de adugat, actualizndu-se componenta articolului zero. La tergerea unui articol, trebuie asigurat includerea sa n lista articolelor libere, operaia fiind posibil la oricare din capetele listei. Cutarea secvenial a primului articol liber, fr organizarea unei gestiuni a acestora. Dei mai costisitoare ca timp de cutare, aceasta este soluia cea mai simpl sub aspectul programrii, eliminnd necesitatea unei structuri distincte a articolului zero i operaiile legate de ntreinerea coleciei sau listei articolelor libere. n concluzie, este de preferat ultima variant atunci cnd timpul de cutare nu este prohibitiv. Fr verificarea existenei de articole libere, caz n care articolul este adugat direct la sfrit (extindere). Aceast variant este avantajoas cnd are loc introducerea de la nceput a majoritii articolelor. Ea poate fi asociat cu preformarea cu zero articole, fiecare sesiune de adugare de noi articole fiind realizat prin extinderea fiierului. Adugarea n acces direct presupune o codificare anterioar (preluarea numrului relativ din nomenclatorul editat dup fiecare creare/adugare secvenial) i se realizeaz identic cu operaia de scriere direct prezentat n 6.3.1. 6.3.3 Probleme care utilizeaz corespondena intern dintre chei i numere relative Majoritatea aplicaiilor de gestiune economic utilizeaz fiiere de date n care articolele trebuie regsite dup valorile unui cmp de identificare, numit cheie. Problema corespondenei ntre chei i numerele relative ale articolelor din fiierul de date se poate rezolva prin intermediul unui fiier binar suplimentar, cu rol de tabel de indexuri. O astfel de organizare se numete indexat. Articolele fiierului tabel de indexuri au structura din figura 6.11.
IS Cheie Numr relativ (nr)

Figura 6.11 Structura articolului din tabela de indexuri Indicatorul de stare (IS) are rol identic cu cel prezentat n 6.3.1. Cheie este un cmp n care se memoreaz valoarea cheii articolului existent logic n fiierul de date, al crui numr relativ corespunztor este memorat n cmpul nr. Articolele tabelei de indexuri sunt, n orice moment, sortate cresctor dup valorile cmpului cheie. Articolele din fiierul de date sunt memorate aleator. O parte dintre acestea nui regsesc corespondent n tabela de indexuri, fiind considerate terse. Orice operaie de acces la articolele fiierului de date se realizeaz numai prin intermediul tabelei,

91

Programarea calculatoarelor

gestionat automat de funciile unei biblioteci specializate i care este netransparent utilizatorului (bibliotec utilizator, nu face parte din limbaj). Operaiile de acces la nivel de fiier sunt deschiderea i nchiderea fiierului de date. La rndul ei, deschiderea poate fi pentru creare (ca fiier nou) sau pentru consultare i ntreinere (ca fiier vechi). Procedurile realizeaz, pe lng operaiile asupra fiierului de date i gestionarea automat a tabelei de indexuri: formarea numelui su extern, asignarea numelui fizic la numele logic, deschiderea ca fiier nou sau vechi, nchiderea. Operaiile de gestiune ce se pot realiza cu fiierele de date astfel organizate sunt: Crearea n acces secvenial presupune furnizarea articolelor sortate strict cresctor dup valorile cmpului ales drept cheie. Articolele sunt scrise cu ajutorul funciei de scriere n acces secvenial. Eroarea de cheie invalid poate aprea la tentativa de scriere a unui articol a crui cheie este mai mic sau egal dect ultima nscris n fiierul de date. Crearea n acces direct se realizeaz cu funcia de scriere n acces direct, articolele fiind furnizate n orice ordine. Eroarea de cheie invalid apare la tentativa de scriere a unui articol a crui cheie este egal cu una din cele prezente n fiier. Consultarea n acces secvenial presupune regsirea articolelor n ordinea strict cresctoare a valorilor cheii. Ea se realizeaz cu ajutorul funciei de citire n acces secvenial, care detecteaz i sfritul fiierului de date. Consultarea n acces mixt permite selectarea unui grup de articole, memorate logic contiguu n fiier, selecie realizat pe baza valorilor cheii primului i ultimului articol din grupul dorit. Accesul mixt presupune poziionarea pe primul articol prin citirea n acces direct (sau poziionare i citire n acces secvenial), urmat de exploatarea n acces secvenial, pn la gsirea cheii ultimului articol dorit, sau pn la sfritul fiierului. Adugarea de articole se realizeaz utiliznd funcia de scriere n acces direct. Articolele pot fi furnizate n orice ordine a valorilor cheii. Eroarea de cheie invalid apare la tentativa de scriere a unui articol a crui cheie este egal cu una deja existent. Modificarea unor cmpuri din articol se realizeaz n urmtoarele etape: citirea articolului de modificat (n acces secvenial sau direct); modificarea, n zona articol corespunztoare, a cmpurilor dorite, cu excepia cmpului cheie; rescrierea articolului, cu procedura de rescriere. Eroarea de cheie invalid apare n cazul tentativei de rescriere a unui articol a crui cheie este diferit de cea a articolului citit anterior. tergerea n acces secvenial elimin articolul curent din fiierul de date. n general, articolul trebuie mai nti identificat printr-o citire (n acces secvenial sau direct) sau prin poziionare. Procedura returneaz eroare n cazul tentativei de tergere dup ultimul articol existent. tergerea n acces direct elimin articolul a crui cheie este precizat. Operaia nu trebuie precedat de citirea articolului i returneaz eroare n cazul

92

Algoritmi de prelucrare a fiierelor binare

furnizrii unei chei inexistente n fiier. n aplicaii, este preferabil tergerea n acces secvenial, deoarece permite (datorit citirii care o precede), vizualizarea articolului i luarea unei decizii n condiii de siguran. Exemplu: 9. Exemplul urmtor descrie funcii, tipuri de date i variabile publice pentru prelucrarea unui fiier organizat indexat. Pentru aceste exemplu, fiierul de date este format din articole cu urmtoarea structur: cheie denumire pre cantitate

Figura 6.12 Structura articolului din tabela de indexuri Exemplul poate fi adaptat pentru orice alt structur, modificnd corespunztor structura articolului. n acest exemplu, fiierul index va fi o variabil global, accesibil tuturor subprogramelor. Tipurile definite sunt urmtoarele:
typedef struct{ char cheie[7]; char den[35]; float pu; float cant; } ARTICOL; //tipul articol din fisierul de date typedef struct{ char is; char cheie[7]; long nr_rel; } ART_INDEX; //tipul articol din tabela de indexuri FILE* ind; char nume_index[20]; //fisierul index //numele extern al fisierului index

Pentru implementarea operaiilor de gestiune specifice unui fiier organizat indexat sunt necesare urmtoarele subprograme: Funcia de deschidere a tabelei index ca fiier nou, cu prototipul
void new_index(char *nume);

Funcia primete ca parametru numele extern al fiierului de date (nume) i creeaz un fiier nou, tabela de indexuri, cu extensia .idx. Funcia de deschidere a tabelei de indexuri, pentru consultare i ntreinere, cu prototipul
void open_index(char *nume);

Funcia primete ca parametru numele extern al fiierului de date (nume), i deschide ca existent tabela de indexuri, cu extensia .idx. Funcia de nchidere a tabelei de indexuri, cu prototipul
void closeindex();

93

Programarea calculatoarelor

Funcia realizeaz nchiderea tabelei de indexuri asociate fiierului de date. Funcia pentru citirea n acces secvenial a unui articol din fiierul de date, cu prototipul
int ReadSec(fisier f,articol *a);

Funcia are ca parametri numele intern al fiierului de date i adresa unde se depune articolul citit, dac acest lucru este posibil i returneaz - 1, dac citirea a fost posibil; - 0, n caz contrar. Citirea unui articol din fiierul de date este realizat prin intermediul tabelei de indexuri, astfel: este citit un articol din tabel, de la poziia curent a pointerului de fiier i apoi este citit articolul cu numrul relativ dat de cmpul nr_rel al articolului citit din fiierul de indexuri. Dac, n tabela de indexuri, pointerul de fiier indic sfritul de fiier, atunci citirea nu este posibil i funcia returneaz valoarea 0. Prin apelul repetat al funciei ReadSec, dac tabela de indexuri are poinetrul plasat naintea primului articol, sunt obinute articolele din fiierul de date n ordinea strict cresctoare a valorii cheii. Funcia pentru citirea n acces direct a unui articol din fiierul de date, cu prototipul
int ReadKey(fisier f,articol *a,char *Key);

Funcia are ca parametri numele intern al fiierului de date, adresa unde se depune articolul citit, dac acest lucru este posibil, precum i cheia articolului care va fi citit i returneaz - 1, dac citirea a fost posibil; - 0, n caz contrar. Funcia apeleaz modulul de cutare binar n tabela de indexuri a cheii Key, SeekKey. Atunci cnd cheia este gsit, citete articolul cu numrul relativ corespunztor articolului din tabela de indexuri i returneaz valoarea 1, altfel returneaz valoarea 0. Funcia pentru scrierea n acces secvenial a unui articol n fiierul de date, cu prototipul
int WriteSec(fisier f,articol a);

Funcia are ca parametri numele intern al fiierului de date i articolul ce va fi scris, dac acest lucru este posibil, i returneaz - 1, dac scrierea a fost posibil; - 0, n caz contrar. Funcia adaug un articol n fiierul de date, concomitent cu extinderea tabelei de indexuri cu o nou nregistrare, a crei cheie este mai mare dect cele existente. n cazul n care cheia este mai mic sau egal cu a ultimului articol din tabel, este returnat valoarea 0, corespunztoare situaiei n care scrierea nu este posibil.

94

Algoritmi de prelucrare a fiierelor binare

Funcia pentru scrierea n acces direct a unui articol n fiierul de date, cu prototipul
int WriteKey(fisier f,articol a);

Funcia are ca parametri numele intern al fiierului, articolul ce va fi scris, dac acest lucru este posibil, i returneaz - 1, dac scrierea a fost posibil; - 0, n caz contrar. Funcia adaug un articol la sfritul fiierului de date. Cheia acestuia, a.cheie, poate avea orice valoare (care nu exist deja n tabela de indexuri). Iniial, tabela se extinde cu un nou articol i apoi este reordonat (prin apelul funciei Sort). n cazul n care cheia articolului de scris este deja prezent n tabela de indexuri, articolul nu este scris n fiier i funcia returneaz valoarea 0. Cutarea cheii n tabela de indexuri pentru stabilirea posibilitii scrierii este realizat prin apelul funciei SeekKey. Funcia pentru tergerea n acces secvenial a unui articol, cu prototipul
int DeleteSec();

Funcia returneaz - 1, dac tergerea a fost posibil; - 0, n caz contrar. Funcia terge logic articolul curent din fiierul de date. tergerea se realizeaz fizic n tabela de indexuri. Iniial, indicatorul de stare este setat pe 0 i apoi se elimin articolul din tabel, prin apelul funciei Sort. Funcia returneaz valoarea 0, corespunztoare situaiei de eroare, dac pointerul curent al tabelei de indexuri indic marcatorul de sfrit de fiier. Funcia pentru tergerea n acces direct a unui articol, cu prototipul
int DeleteKey(char *Key);

Funcia primete ca parametru de intrare cheia articolului care va fi ters i returneaz - 1, dac tergerea a fost posibil; - 0, n caz contrar. Funcia terge logic din fiierul de date articolul a crui cheie este primit ca parametru. tergerea este realizat fizic din tabela de indexuri, analog tergerii n acces secvenial. Funcia returneaz valoarea 0, corespunztoare situaiei de eroare, dac Key nu este regsit n tabela de indexuri. Cutarea este realizat prin apelul funciei SeekKey. Pentru implementarea funciilor descrise mai sus, sunt utilizate urmtoarele funcii auxiliare: Funcia pentru sortarea tabelei de indexuri, cu eliminarea articolelor cu stare 0, cu prototipul
void Sort();

Funcia realizeaz sortarea articolelor tabelei de indexuri, cresctor dup cmpul cheie, precum i tergerea fizic a tuturor articolelor cu indicator de stare 0.

95

Programarea calculatoarelor

Funcia pentru cutarea articolului cu cheie dat, cu prototipul


int SeekKey(char *Key)

Funcia primete ca parametru de intrare cheia articolului cutat i returneaz - 1, dac articolul a fost gsit; - 0, n caz contrar. Funcia realizeaz cutarea binar n tabela de indexuri, dup cmpul cheie. Dac articolul cu cheia Key este gsit, funcia las pointerul de fiier pe acel articol (o citire secvenial ulterioar determinnd obinerea articolului corespunztor din fiierul de date). Textul surs care implementeaz toate aceste funcii C este prezentat n continuare (n exemplele care urmeaz, aceste text va fi considerat salvat n fiierul index1.cpp).
#include <stdio.h> #include <string.h> #define fisier FILE* typedef struct{ char cheie[7]; char den[35]; float pu; float cant; } ARTICOL; //tipul articol din fisierul de date typedef struct{ char is; char cheie[7]; long nr_rel; } ART_INDEX; //tipul articol din tabela index fisier ind; char nume_index[20]; //fisierul index //numele extern al fisierului index

void Sort() { ART_INDEX a,b; fisier ind1; long i,j; ind1=fopen("temp.idx","wb+"); rewind(ind); fread(&a,sizeof(a),1,ind); while(!feof(ind)) { if(a.is)fwrite(&a,sizeof(a),1,ind1); fread(&a,sizeof(a),1,ind); } fclose(ind); fseek(ind1,0,SEEK_END); long n=ftell(ind1)/sizeof(a); for(i=0;i<n-1;i++) { fseek(ind1,i*sizeof(a),SEEK_SET); fread(&a,sizeof(a),1,ind1); for(j=i+1;j<n;j++) { fseek(ind1,j*sizeof(a),SEEK_SET); fread(&b,sizeof(a),1,ind1); if(strcmp(a.cheie,b.cheie)>0) { fseek(ind1,i*sizeof(a),SEEK_SET); fwrite(&b,sizeof(a),1,ind1); fseek(ind1,j*sizeof(a),SEEK_SET);

96

Algoritmi de prelucrare a fiierelor binare


fwrite(&a,sizeof(a),1,ind1); } } } rewind(ind1); ind=fopen(nume_index,"wb+"); fread(&a,sizeof(a),1,ind1); while(!feof(ind1)) { if(a.is)fwrite(&a,sizeof(a),1,ind); fread(&a,sizeof(a),1,ind1); } fclose(ind1); remove("temp.idx"); } /* cautarea articolului cu cheia Key si plasarea pointerului de fisier in tabela de indexuri pe articolul respectiv*/ int SeekKey(char *Key) { long ls=0, ld, m, n; ART_INDEX a; int gasit=0; fseek(ind,0,SEEK_END); n=ftell(ind)/sizeof(ART_INDEX); ld=n-1; while((ls<=ld)&&(!gasit)) { m=(ls+ld)/2; fseek(ind,m*sizeof(a),SEEK_SET); fread(&a,sizeof(a),1,ind); if(strcmp(a.cheie,Key)==0) gasit=1; else if(strcmp(a.cheie,Key)>0) ld=m-1; else ls=m+1; } if(gasit) fseek(ind,m*sizeof(a),SEEK_SET); return gasit; } void new_index(char *nume) { strcpy(nume_index,nume); strcat(nume_index,".idx"); ind=fopen(nume_index,"wb+"); } void open_index(char *nume) { strcpy(nume_index,nume); strcat(nume_index,".idx"); ind=fopen(nume_index,"rb+"); } void close_index() { fclose(ind); } int ReadSec(fisier f,ARTICOL *a) { ART_INDEX a1; int r; fread(&a1,sizeof(a1),1,ind); if(feof(ind))r=0; else { fseek(f,a1.nr_rel*sizeof(*a),SEEK_SET); fread(a,sizeof(*a),1,f); r=1; } return r; }

97

Programarea calculatoarelor
int ReadKey(fisier f,ARTICOL *a,char *Key) { ART_INDEX a1; int r; if(SeekKey(Key)) { fread(&a1,sizeof(a1),1,ind); fseek(f,a1.nr_rel*sizeof(*a),SEEK_SET); fread(a,sizeof(*a),1,f); r=1; } else r=0; return r; } int WriteSec(fisier f,ARTICOL a) { ART_INDEX a1, ai; long n, nl; int r; fseek(ind,0,SEEK_END); n=ftell(ind)/sizeof(a1); if(n>0) { fseek(ind,(n-1)*sizeof(a1),SEEK_SET); fread(&a1,sizeof(a1),1,ind); if(strcmp(a1.cheie,a.cheie)>0) r=0; else { ai.is=1; strcpy(ai.cheie,a.cheie); fseek(f,0,SEEK_END); n1=ftell(f)/sizeof(a); ai.nr_rel=n1; fseek(ind,0,SEEK_END); fwrite(&ai,sizeof(ai),1,ind); fwrite(&a,sizeof(a),1,f); r=1; } } else r=0; return r; } int WriteKey(fisier f,ARTICOL a) { char Key[7]; ART_INDEX a1; long n; strcpy(Key,a.cheie); if(SeekKey(Key)) r=0; else { a1.is=1; strcpy(a1.cheie,a.cheie); fseek(f,0,SEEK_END); n=ftell(f)/sizeof(a); a1.nr_rel=n; fwrite(&a,sizeof(a),1,f); fseek(ind,0,SEEK_END); fwrite(&a1,sizeof(a1),1,ind); Sort(); r=1; } return r; }

98

Algoritmi de prelucrare a fiierelor binare


int DeleteSec() { ART_INDEX a1; long pos=ftell(ind); fread(&a1,sizeof(a1),1,ind); if(feof(ind)) r=0; else { fseek(ind,pos,SEEK_SET); a1.is=0; fwrite(&a1,sizeof(a1),1,ind); Sort(); r=1; } return r; } int DeleteKey(char *Key) { int r; if(SeekKey(Key)) r=DeleteSec(); else r=0; return r; }

Exemple 10. Scriei programul C pentru crearea n acces direct unui fiier binar cu articole avnd structura: cheie denumire pre cantitate Datele sunt preluate de la tastatur pn la apsarea combinaiei CTRL/Z pentru cmpul cheie. Fiierul creat este organizat indexat.
#include "index1.cpp" #include <conio.h> void main() { ARTICOL a; char nume[20],nume1[20]; char x[7]; fisier f; clrscr(); printf(" numele fisierului de date in care adaugati:"); fflush(stdin); gets(nume); strcpy(nume1,nume); strcat(nume1,".dat"); f=fopen(nume1,"rb+"); if(f==NULL) { printf("Fisierul va fi creat"); f=fopen(nume1,"wb+"); new_index(nume); } else open_index(nume); printf("\nAdaugarea in acces direct dupa cheie\n"); printf("Introduceti cheia:"); fflush(stdin); gets(a.cheie); while(!feof(stdin)) { printf("Denumire produs:"); fflush(stdin); gets(a.den); printf("Pret produs:"); scanf("%f",&a.pu);

99

Programarea calculatoarelor
printf("Cantitate:"); scanf("%f",&a.cant); if(WriteKey(f,a)) printf("Articol adaugat"); else printf("Exista articol"); getch(); clrscr(); printf("Introduceti cheia:"); fflush(stdin); gets(a.cheie); } fclose(f); close_index(); getch(); }

11. Se presupune creat i populat fiierul de date din exemplul anterior. Scriei programul C pentru tergerea acelor articole ale cror chei sunt introduse de la tastatur. ncheierea introducerii datelor este marcat standard.
#include "index1.cpp" #include <conio.h> void main() { ARTICOL a; char nume[20],nume1[20]; char Key[7]; fisier f; char r; int i; clrscr(); printf(" numele fisierului de date din care stergeti:"); fflush(stdin); gets(nume); strcpy(nume1,nume); strcat(nume1,".dat"); f=fopen(nume1,"rb+"); if(f==NULL) printf("Fisierul nu exista!!"); else { open_index(nume); printf("\nStergerea in acces direct dupa cheie\n"); printf("Introduceti cheia:"); fflush(stdin); gets(Key); while(!feof(stdin)) { if(ReadKey(f,&a,Key)) { printf("Articolul:\n"); printf("Denumire:%20s\n",a.den); printf("Pret:%7.2f\n",a.pu); printf("Cantitate:%8.2f\n\n",a.cant); printf("Doriti stergerea?(D/Altceva)"); r=getch(); if(r=='D') { i=DeleteKey(Key); printf("Stergere efectuata"); } else printf("Stergerea nu a fost efectuata"); } else printf("Nu exista articol"); getch();

100

Algoritmi de prelucrare a fiierelor binare


clrscr(); printf("Introduceti cheia:"); fflush(stdin); gets(a.cheie); } fclose(f); close_index(); getch(); } }

12. Scriei programul C pentru afiarea informaiilor memorate n fiierul creat n exemplul 10. Articolele sunt afiate n ordine cresctoare a cmpului cheie.
#include "index1.cpp" #include <conio.h> void main() { ARTICOL a; char nume[20],nume1[20]; char x[7]; fisier f; clrscr(); printf(" numele fisierului de date care este consultat:"); fflush(stdin); gets(nume); strcpy(nume1,nume); strcat(nume1,".dat"); f=fopen(nume1,"rb+"); if(f==NULL) printf("Fisierul nu exista!!"); else { open_index(nume); while(ReadSec(f,&a)) { printf("Cheie:"); puts(a.cheie); printf("\nDenumire produs:"); puts(a.den); printf("Pret produs:"); printf("7.2%f\n",a.pu); printf("Cantitate:"); printf("%8.2f\n\n",a.cant); getch(); } fclose(f); close_index(); getch(); } }

6.4 Sortarea fiierelor binare memorate dens


Operaia de sortare a unui fiier binar presupune aranjarea articolelor n ordinea cresctoare (descresctoare) a valorilor unei zone, numit cheie de sortare. n cazul n care cheia de sortare este format dintr-un singur cmp din cadrul articolului, operaia se numete sortare simpl. Sortarea multipl presupune aranjarea articolelor dup valorile a dou sau mai multe cmpuri, alctuind, prin juxtapunere, cheia de sortare. Juxtapunerea cmpurilor (nu neaprat adiacente n cadrul articolului) se realizeaz pe lungimea efectiv a lor, alctuind forma canonic a cheii de sortare. De

101

Programarea calculatoarelor

exemplu, dac NUME i PRENUME sunt dou cmpuri distincte, declarate de tip ir de caractere, forma canonic a cheii de sortare dup nume i prenume este dat de lungimea efectiv a fiecrei date de tip ir. Dac pentru sortarea simpl cheia de sortare poate fi nsui cmpul din articol, pentru cea multipl este necesar o zon auxiliar de memorie, n care se construiete cheia de sortare, n forma canonic. Sortarea unui fiier se poate realiza cu aducerea lui integral n memorie (sortare n memorie) sau cu aducerea n memorie a cte unui articol (sortare "direct pe disc"). Indiferent de modul utilizat, sortarea poate fi realizat printr-unul din algoritmii cunoscui pentru masivele de date: sortare prin interschimbare, prin selecie, prin inserie etc. Sortarea n memorie este o metod rapid i presupune: citirea ntregului fiier n memoria principal, ntr-o structur intern de date (vector, arbore bina de sortare); sortarea efectiv dup cheia de sortare (n cazul folosirii unui vector, operaia nu este necesar dac se folosete arbore de sortare); recrearea fiierului pe disc. Metoda se poate aplica numai fiierelor reduse ca dimensiuni sau cu lungime mic de articol, dat fiind capacitatea limitat a memoriei interne asociat unui program. Ea poate avea mai multe variante: Sortarea cu vehicularea ntregului articol, presupune memorarea ntregului fiier ntr-un vector de articole. Compararea pentru sortare se va realiza pe cmpul cheie de sortare, ns interschimbarea se realizeaz la nivelul ntregului articol. Exemplu: 13.
typedef struct { char grupa; char nume_student[30]; float medie; }STUDENT; void main() { FILE* f; STUDENT x[250], aux; int i,j,n; f=fopen("STUDENT.DAT","rb+"); fseek(f,0,SEEK_END); n:=ftell(f)/sizeof(STUDENT); rewind(f); // citirea fisierului initial in memorie for(i=0;i<n;i++) fread(&x[i],sizeof(STUDENT),1,f); //sortarea for(i=0;i<n-1;i++) for(j=i+1;j<n;j++) if(x[i].medie>x[j].medie) { aux:=x[i]; //interschimbarea articolelor x[i]:=x[j]; //nu se poate folosi atribuirea mereu x[y]:=aux; } rewind(f);

102

Algoritmi de prelucrare a fiierelor binare for(i=0;i<n;i++) fwrite(&x[i],sizeof(STUDENT),1,f); fclose (f); }

Sortarea cu vehicularea cheii i indexului, presupune memorarea ntr-un vector numai a valorii cheii de sortare, mpreun cu numrul relativ al articolului din fiierul iniial (indexul). Interschimbarea se va realiza la nivelul cheii de sortare, rezultnd n final ordinea n care articolele vor fi scrise n fiier. Deoarece articolele, n ntregime, sunt rezidente pe disc, fiierul sortat va fi creat cu un alt nume fizic, n acces secvenial, prelund articolele din fiierul iniial, n acces direct. Cheile de sortare i indexurile pot fi memorate n vectori distinci sau ntr-unul singur, cu elemente de tip articol. Exemplu: 14.
typedef struct { char grupa; char nume_student[30]; float medie; }STUDENT; typedef struct { float medie; int index; }CHEIE; void main() { FILE *f,*g; STUDENT y; CHEIE aux,x[250]; int i,j,n; f=fopen("STUDENT.DAT","rb"); fseek(f,0,SEEK_END); n:=ftell(f)/sizeof(STUDENT); rewind(f); g=fopen("STUDENTS.DAT","wb"); // ---------------------------------for(i=0;i<n;i++) { fread(&y,sizeof(STUDENT),1,f); x[i].medie=y.medie; x[i].index=i; } //------------------------------------for(i=0;i<n-1;i++) for(j=i+1;j<n;j++) if(x[i].medie>x[j].medie) { aux=x[i]; x[i]:=x[j]; x[j]:=aux } //-------------------------------------for(i=0;i<n;i++) { fseek(f,x[i].index*sizeof(STUDENT),SEEK_SET); fread(&y,sizeof(STUDENT),1,f); fwrite(&y,sizeof(STUDENT),1,g); } fclose(f); fclose(g); unlink(f); rename("STUDENTS.DAT","STUDENT.DAT"); }

103

Programarea calculatoarelor

Sortarea numai cu vehicularea indexului este o metod mai bun dect precedenta, deoarece micoreaz timpul de execuie, prin eliminarea interschimbrii valorilor cheii de sortare (mai ales cnd aceasta este multipl). Valorile cheii de sortare i numrului relativ corespunztor indexului se memoreaz n vectori distinci. Comparaiile se realizeaz pentru valorile cheii, dar interschimbarea se efectueaz numai pentru indexuri. Se va crea un alt fiier fizic. Exemplu: 15.
typedef struct { char grupa; char nume_student[30]; float medie; }STUDENT; void main() { FILE *f,*g; float x[250]; int index[250]; int i,j,n; STUDENT y; f=fopen("STUDENT.DAT","rb"); fseek(f,0,SEEK_END); n:=ftell(f)/sizeof(STUDENT); rewind(f); g=fopen("STUDENTS.DAT","wb"); //-----------------------------------------for(i=0;i<n;i++) { fread(&y,sizeof(STUDENT),1,f); x[i]=y.medie; index[i]=i; } //-----------------------------------------for(i=0;i<n-1;i++) for(j=i+1;j<n;j++) if(x[i]>x[j]) { aux=index[i]; index[i]=index[j]; index[j]=aux } //------------------------------------------for(i=0;i<n;i++) { fseek(f,index[i]*sizeof(STUDENT),SEEK_SET); fread(&y,sizeof(STUDENT),1,f); fwrite(&y,sizeof(STUDENT),1,g); } fclose(f); fclose(g) }

Sortarea pe disc se aplic fiierelor mari, la care este imposibil aducerea n memoria principal chiar i a minimului de informaii necesare sortrii. n acest caz, operaia se va realiza "direct" pe mediul magnetic, cu aducerea n memorie doar a dou articole (pentru comparaii) i scrierea n acces direct n acelai fiier, prin utilizarea numrului relativ al articolelor prelucrate. Timpul de prelucrare va fi substanial mai mare dect la metodele de sortare n memorie, deoarece operaiile de intrare/ieire sunt costisitoare din punct de vedere al resursei timp calculator.

104

Algoritmi de prelucrare a fiierelor binare

Se poate aplica oricare din algoritmii de sortare cunoscui, cu meniunea c indicii i i j vor fi utilizai pentru controlul numrului relativ al articolelor n fiier. Exemple: 16. Sortarea prin interschimbare
do { vb=0; for(i=0;i<nr_art-1;i++) { fseek(f,sizeof(STUDENT)*i,SEEK_SET); fread(&x,sizeof(STUDENT),1,f); fread(&y,sizeof(STUDENT),1,f); if(x.medie>y.medie) { fseek(f,sizeof(STUDENT)*i,SEEK_SET); fwrite(&y,sizeof(STUDENT),1,f); fwrite(&x,sizeof(STUDENT),1,f); vb=1; } while(vb);

17. Sortare prin selecie


for(i=0;i<nr_art-1;i++) { fseek(f,sizeof(STUDENT)*i,SEEK_SET); fread(&x,sizeof(STUDENT),1,f); for(j=i+1;j<nr_art;j++) { fseek(f,sizeof(STUDENT)*j,SEEK_SET); fread(&y,sizeof(STUDENT),1,f); if(x.medie>y.medie) { fseek(f,sizeof(STUDENT)*i,SEEK_SET); fwrite(&y,sizeof(STUDENT),1,f); fseek(f,sizeof(STUDENT)*j,SEEK_SET); fwrite(&x,sizeof(STUDENT),1,f); } } }

6.5 Interclasarea fiierelor binare memorate dens


Interclasarea este operaia prin care, din dou sau mai multe mulimi ordonate, se obine o nou mulime, ordonat dup acelai criteriu. Interclasarea fiierelor apare ca necesitate n aplicaiile economice, mai ales n faza de postpopulare a fiierelor mari de date, create simultan pe submulimi de mai muli utilizatori i necesitnd, n final, reunirea acestora ntr-unul singur. Condiia apriori interclasrii este ca toate fiierele pariale s fie sortate dup valorile aceluiai cmp, pe baza cruia se va realiza, prin comparri succesive, operaia de interclasare. Cmpul poart denumirea de cheie de interclasare. Interclasarea a n fiiere se poate realiza simplu prin aplicarea de n-1 ori a operaiei de interclasare a dou fiiere (figura 6.12).

105

Programarea calculatoarelor

...

Interclasare 1 ..

Fiier 1

Interclasare 2 ...

Fiier 2 Interclasare 3 ...

Fiier 3 ... Interclasare 3 . Fiier final

Fiier n-1

Figura 6.13 Interclasarea a n fiiere Se obin astfel n-1 fiiere intermediare (fiier i), din care numai ultimul se pstreaz, celelalte (mpreun cu fiierele iniiale) se terg, fie n finalul procesului, fie la sfritul fiecrei etape intermediare (recomandat). Interclasarea a dou fiiere este similar operaiei aplicate pentru doi vectori. Dimensiunea fiierului rezultat este suma dimensiunilor fiierelor iniiale. Exemplu: 18. Se prezint structura principial a unui program pentru interclasarea a dou fiiere binare. Cheile de interclasare se afl n cmpul c aparinnd articolelor art_1 i art_2, corespunztoare fiierelor de intrare f i g, considerate populate dens.
{ //--------------------------------//citire nume externe ale fisierelor //--------------------------------f=fopen(nume_fisier_intrare_1, "rb"); g=fopen(nume_fisier_intrare_2, "rb"); h=fopen(nume_fisier_iesire, "wb"); fread(&art_1,sizeof(tip_articol),1,f); fread(&art_2,sizeof(tip_articol),1,g); while((!feof(f)&&(!feof(g))) if(art_1.c>art_2.c) { fwrite(&art_1,sizeof(tip_articol),1,h); fread(&art_1,sizeof(tip_articol),1,f); }

106

Algoritmi de prelucrare a fiierelor binare


else { fwrite(&art_2,sizeof(tip_articol),1,h); fread(&art_2,sizeof(tip_articol),1,g); } while(!feof(f)) { fwrite(&art_1,sizeof(tip_articol),1,h); fread(&art_1,sizeof(tip_articol),1,f); } while(!feof(g)) { fwrite(&art_2,sizeof(tip_articol),1,h); fread(&art_2,sizeof(tip_articol),1,g); } fclose(f); fclose(g); fclose(h) }

6.6 Prelucrarea masivelor memorate n fiiere binare


Una dintre aplicaiile des ntlnite n lucrul cu fiiere este memorarea masivelor de date de dimensiuni foarte mari, care fac imposibil aducerea lor integral n memoria intern. Problema principal a prelucrrii masivelor (vectori, matrice etc.) memorate n fiiere binare, o constituie determinarea poziiei unui anumit element de masiv n cadrul fiierului. Indiferent de numrul de dimensiuni ale masivului i de modalitile de memorare a elementelor sale n cadrul fiierului, legtura ntre elementul de masiv care se refer i numrul relativ al articolului care l conine se realizeaz pe baza funciei rang. n cazul masivelor memorate n fiiere, prelucrarea acestora depinde de unele caracteristici particulare: numrul de dimensiuni ale masivului; ordinea de memorare n fiier (n ordine lexicografic sau invers lexicografic); modul de memorare (dens sau nedens); ordinea de parcurgere a masivului. 6.6.1 Prelucrarea vectorilor De regul, vectorii se memoreaz dens. Numrul relativ al articolului depinde de rangul elementului n cadrul vectorului, astfel: nr_relativ = rang(xi)+1 = i+1, pentru i=0..n-1, dac articolul cu numrul relativ 0, fie nu este utilizat (caz n care dimensiunea vectorului este n = dimensiune fiier-1), fie memoreaz numrul efectiv de componente ale vectorului; nr_relativ = rang(xi) = i, pentru i=0..n, dac vectorul se memoreaz ncepnd cu primul articol (caz n care dimensiunea vectorului este n =dimensiunea fiierului). Exemplu: 19. S se determine media aritmetic a elementelor unui vector foarte mare, memorat ntr-un fiier binar.

107

Programarea calculatoarelor

#include<stdio.h> void main() { FILE* vector; float element, medie; long i,n; vector=fopen("VECTOR.DAT","rb"); fseek(vector,0,SEEK_END); n=ftell(f)/sizeof(float); rewind(f); medie=0; for(i=0;i<n;i++) { fread(&element,sizeof(float),1,vector); medie+=element; } medie/=n; printf("\nMedia: %7.3f",medie); fclose(vector); }

6.6.2 Prelucrarea matricelor O matrice poate fi memorat ntr-un fiier binar nedens (similar memorrii n MP) sau dens, n ordine lexicografic sau invers lexicografic. Numrul relativ al elementului aij se determin pe baza funciei rang, astfel: rang(aij) = i * nr_coloane + j, n cazul memorrii lexicografice, unde nr_coloane este fie numrul coloanelor efective (populare dens), fie numrul coloanelor rezervate (populare nedens); rang(aij) = j * nr_linii + i, n cazul memorrii invers lexicografice, unde nr_linii este fie numrul liniilor efective (populare dens), fie numrul liniilor rezervate (populare nedens). Fie m i n numrul liniilor, respectiv coloanelor efective i mr i nr numrul liniilor, respectiv coloanelor rezervate (mr i nr corespund dimensiunilor maxime din declaraia unui masiv aflat n memoria principal). Pentru ca fiierul s conin informaii complete despre matrice, trebuie s memoreze, pe lng elementele ei, i: m (sau n), n cazul memorrii dense. Cnd se memoreaz m, n se determin mprind dimensiunea fiierului (mai puin primul articol, unde se afl m) la m; cnd se memoreaz n, m se determin mprind dimensiunea fiierului (mai puin primul articol, unde se afl n) la n. Funcia rang depinde de m sau n, dup cum matricea este memorat invers lexicografic sau lexicografic; n i nr, n cazul memorrii nedense n ordine lexicografic. m se determin mprind dimensiunea fiierului (mai puin primele dou articole, unde se afl n i nr) la nr, iar mr nu are relevan. Funcia rang depinde de nr; m i mr, n cazul memorrii nedense n ordine invers lexicografic. N se determin mprind dimensiunea fiierului (mai puin primele dou articole, unde se afl m i mr) la mr, iar nr nu are relevan. Funcia rang depinde de mr. Funcia rang se calculeaz i se utilizeaz numai dac problema de rezolvat implic parcurgerea matricei n alt ordine dect cea n care este memorat n fiier, deci consultarea acestuia se realizeaz n acces direct.

108

Algoritmi de prelucrare a fiierelor binare

Exemple: 20. S se afieze elementul maxim de pe fiecare coloan a unei matrice de dimensiuni mxn, memorate dens, ntr-un fiier binar, n ordine lexicografic. Primul articol conine numrul de coloane. Observaie: primul articol are dimensiune diferit de celelalte: numrul de coloane este de tip ntreg iar elementele matricei sunt reale. Din dimensiunea total a fiierului, primii sizeof(int) octei sunt ocupai de numrul de coloane, restul constituie matricea propriu-zis. La calcularea poziiei unui element n matrice trebuie inut cont de faptul c matricea nu ncepe la nceputul fiierului, ci dup sizeof(int) octei.
#include<stdio.h> void main() { FILE *f; float max, element; long i,j,r,m,n; f=fopen("MATRICE.DAT", "rb"); fread(&n,sizeof(int),1,f); //citire numar de coloane fseek(f,0,SEEK_END); m=(ftell(f)-sizeof(int))/(sizeof(float)*n); for(j=0;j<n;j++) { //pozitonare pe primul element din coloana j fseek(f,j*sizeof(float)+sizeof(int),SEEK_SET); fread(&element,sizeof(float),1,f); max=element; for(i=1;i<m;i++) { r=i*n+j; //rangul elementului in matrice fseek(f,r*sizeof(float)+sizeof(int),SEEK_SET); fread(&element,sizeof(float),1,f); if(element>max) max=element; } printf("\Maximul pe coloana %2d este %7.3f",j,max); } fclose(f); }

21. S se determine elementul maxim de pe fiecare coloan a unei matrice de dimensiuni m x n, memorat nedens ntr-un fiier binar, n ordine lexicografic. Primele dou articole conin numrul de coloane efective i, respectiv, numrul rezervat de coloane. Rezolvarea este similar cu exemplul anterior, cu urmtoarele modificri: din dimensiunea total a fiierului, primii 2*sizeof(int) octei sunt ocupai de dimensiunile matricei (numr de coloane efectiv respectiv rezervat), iar restul constituie matricea propriu zis. La calcularea poziiei unui element n matrice trebuie inut cont de faptul c matricea nu ncepe la nceputul fiierului, ci dup 2*sizeof(int) octei. La calcularea rangului unui element (i implicit a poziiei sale n fiier) se folosete numrul de coloane rezervare, nu numrul de coloane efective.

109

7 Structuri dinamice de date. Liste

Organizarea de tip list corespunde unei structurri lineare a datelor, n sensul c la nivelul fiecrei componente exist suficient informaie pentru identificarea urmtoarei componente a coleciei. Datele unei mulimi structurate prin intermediul listelor sunt referite de obicei prin termenii de noduri, celule, componente etc.

7.1 Reprezentarea listelor


Reprezentarea unei liste poate fi realizat static prin intermediul structurii de date vector. n acest caz ordinea componentelor este dat de ordinea pe domeniul de valori corespunztor indexrii i, n consecin, urmtoarea component este implicit specificat. Memorarea unei mulimi de date {d1, d2,, dn} prin intermediul unei structuri statice poate fi realizat n limbajul C utiliznd un masiv unidimensional. Principalele dezavantaje ale utilizrii reprezentrii statice rezid din volumul de calcule necesare efecturii operaiilor de inserare/eliminare de noduri i din necesitatea pstrrii unei zone de memorie alocat, indiferent de lungimea efectiv a listei. Aceste dezavantaje pot fi eliminate prin opiunea de utilizare a structurilor dinamice. Componentele unei liste dinamice sunt omogene, de tip articol. Fiecare nod, considerat separat, este o structur eterogen, coninnd o parte de informaie i cmpuri de legtur care permit identificarea celulelor vecine. Cmpurile de legtur sunt reprezentate de date de tip referin (adres). n cazul listelor cu un singur cmp de legtur (simplu nlnuite), valoarea cmpului indic adresa nodului urmtor, n timp ce n cazul listelor cu dubl legtur (dublu nlnuite), valorile memorate n cmpurile de legtur sunt adresele componentelor care preced i, respectiv, urmeaz celulei. n ambele situaii, cmpul de legtur pentru indicarea celulei urmtoare corespunztor ultimei componente a listei are valoarea NULL n cazul listelor deschise (lineare)

110

Structuri dinamice de date. Liste

i respectiv indic adresa primei componente din list n cazul listelor nchise (circulare). Declararea tipurilor de date C pentru definirea structurilor de liste dinamice simplu i respectiv dublu nlnuite este:
a) List simplu nlnuit b) List dublu nlnuit

typedef struct nod{ tip_informatie inf; struct nod *leg; } list, *lista;

typedef struct nod{ tip_informatie inf; struct nod *ls, *ld; } list, *lista;

unde tip_informatie este numele tipului de date C utilizat pentru memorarea fiecrei date din mulimea{d1, d2,, dn}. n cele ce urmeaz vom considera c tip_informatie este tipul C int.

7.2 Operaii primitive asupra listelor


Accesul la informaia stocat ntr-o variabil de tip list revine la efectuarea urmtoarelor operaii primitive: regsirea nodului (dac exist) care corespunde unei chei date (condiie impus asupra valorii cmpului de informaie), inserarea unei noi componente n list, eliminarea componentei (componentelor) cu proprietatea c valorile cmpurilor de informaie satisfac o anumit cerin i nlocuirea cmpului de informaie corespunztor unei componente printr-o informaie dat (modificat). Accesarea componentelor unei liste reprezentat printr-o structur static poate fi realizat att secvenial, ct i direct, utiliznd valorile indicelui considerat pentru indexare, n timp ce accesarea componentelor unei liste dinamice se realizeaz de regul numai secvenial, ncepnd cu prima component i continund cu urmtoarele, pe baza valorilor cmpurilor de legtur. Convenional, numim cap al listei dinamice pointerul a crui valoare este adresa primei componente a listei. n continuare ne vom referi exclusiv la liste dinamice, studiul listelor reprezentate prin intermediul vectorilor fiind propus cititorului. 1. Parcurgerea datelor memorate ntr-o list Funcia C parc implementeaz parcurgerea unei liste dinamice n varianta simplu nlnuit i n cazul listelor dublu nlnuite. Se presupune c declaraiile de tip pentru definirea structurilor de liste menionate anterior sunt globale, relativ la procedurile descrise n continuare. a) Lista reprezentat prin structur dinamic simplu nlnuit
void parc(lista cap) { if(cap) { printf("%i ",cap->inf); parc(cap->leg); } }

111

Programarea calculatoarelor

b) Lista reprezentat prin structur dinamic dublu nlnuit


void parc(lista cap) { if(cap) { printf("%i ",cap->inf); parc(cap->ls); } }

2. Regsirea unei date ntr-o colecie memorat ntr-o list Funcia C cauta calculeaz adresa nodului n care este gsit elementul cutat. Dac valoarea cutat nu se regsete printre elementele listei, funcia returneaz valoarea NULL. a) Lista reprezentat prin structur dinamic simplu nlnuit
lista cauta(lista cap,int info) { if(cap==NULL)return NULL; else if(cap->inf==info) return cap; else return cauta(cap->leg,info); }

b) Lista reprezentat prin structur dinamic dublu nlnuit


lista cauta(lista cap,int info) { if(cap==NULL)return NULL; else if(cap->inf==info) return cap; else return cauta(cap->ls,info); }

3. Inserarea unei date ntr-o list Includerea unei noi componente ntr-o list poate fi realizat, n funcie de cerinele problemei particulare, la nceputul listei, dup ultima component din list, naintea/dup o component cu proprietatea c valoarea cmpului de informaie ndeplinete o anumit condiie. Deoarece prin inserarea unei componente se poate ajunge la depirea spaiului disponibil de memorie, este necesar verificarea n prealabil dac este posibil inserarea sau nu (dac se poate aloca spaiu de memorie pentru componenta de inserat). n continuare ne vom referi la liste dinamice simplu nlnuite. Operaiile de inserare n cazul listelor dinamice dublu nlnuite pot fi realizate similar cazului listelor simplu nlnuite, cu specificarea ambelor cmpuri de adres ale nodurilor. Pentru exemplificarea operaiei de inserare sunt prezentate funciile de inserare la nceputul listei, inserare dup ultimul element al listei i inserarea unei celule dup un nod cu informaie dat. Inserarea la nceputul listei Funcia C inserare_la_inceput returneaz valoarea 1 dac adugarea unui nou element este posibil (spaiul de memorie este suficient pentru o nou alocare), altfel returneaz 0. n cazul n care inserarea este posibil, prin apelul funcii este realizat adugarea unui nou nod la nceputul listei.

112

Structuri dinamice de date. Liste


int inserare_la_inceput(lista *cap,int info) { lista nou; if(nou=(lista)malloc(sizeof(list))) { nou->inf=info; nou->leg=*cap; *cap=nou; return 1; } return 0; }

Inserarea dup ultima component a unei liste Funcia C inserare_la_sfarsit returneaz 1 dac i numai dac este posibil o inserare, altfel calculeaz 0. Pentru inserarea unui nou nod n list dup ultima celul este necesar calculul ultimului nod al listei, notat p.
int inserare_la_sfarsit(lista *cap,int info) { lista nou; if(nou=(lista)malloc(sizeof(list))) { nou->leg=NULL;nou->inf=info; if(cap==NULL)*cap=nou; else { for(lista p=*cap;p->leg;p=p->leg); p->leg=nou; } return 1; } return 0; }

Inserarea unei informaii dup o celul cu informaie cunoscut Inserarea unui nou nod ntr-o list identificat prin variabila cap dup o celul p cu informaie cunoscut, infod, poate fi realizat astfel. Este apelat funcia de cutare cauta, care calculeaz nodul p cu proprietatea c informaia memorat n p este infodat. Dac p este adresa vid sau dac spaiul de memorie disponibil nu este suficient, inserarea nu poate fi realizat. n caz contrar, este inserat un nou nod ntre celulele p i p->leg.
int inserare_dupa_informatie(lista cap,int info,int infod) { lista nou,p; if(nou=(lista)malloc(sizeof(list))) if(p=cauta(cap,infod)){ nou->inf=info; nou->leg=p->leg; p->leg=nou; return 1; } return 0; }

113

Programarea calculatoarelor

4. Eliminarea unei date dintr-o list Modificarea coninutului unei liste prin eliminarea uneia sau mai multor componente poate fi descris secvenial, astfel nct este suficient s dispunem de o procedur care realizeaz eliminarea unei singure componente. Criteriile de eliminare pot fi formulate diferit, cele mai uzuale fiind: prima component, ultima component, prima component care ndeplinete o anumit condiie, respectiv componenta care precede/urmeaz primei componente care ndeplinete o condiie dat. n aceste cazuri este necesar verificarea existenei n lista considerat a componentei ce trebuie eliminat. Verificarea asigur i testarea faptului c lista prelucrat este vid sau nu. Informaia i ataat nodului eliminat din list reprezint dat de ieire pentru orice modul de eliminare, n cazul n care i nu este cunoscut naintea eliminrii (de exemplu, atunci cnd este solicitat eliminarea unei celule care conine o informaie dat) . Eliminarea unui nod p poate fi realizat logic sau fizic. Eliminarea logic a celulei p este efectuat excluznd p din lista dinamic prin setarea legturii nodului care precede p pe adresa succesorului lui p, dac p nu este adresa primului element al listei, cap, respectiv prin atribuirea adresei primului element al listei cu cap->leg, n caz contrar. Eliminarea cu tergere fizic unui nod p presupune redenumirea acelui nod n scopul eliberrii memoriei ocupate de p i efectuarea operaiilor descrise n cadrul procesului de eliminare logic. n continuare sunt prezentate urmtoarele tipuri de eliminri, cu tergere fizic. Eliminarea primei componente a unei liste Funcia C elimina_de_la_inceput returneaz 1 dac lista nu este vid, deci eliminarea primului nod este posibil, altfel returneaz 0. Dac lista conine mcar un nod, este eliminat prima celul.
Int elimina_de_la_inceput(lista *cap,int *info) { if(*cap) { lista aux=*cap; *info=aux->inf; *cap=(*cap)->leg; free(aux); return 1; } return 0; }

Eliminarea ultimei componente a unei liste Similar operaiei de inserare a unui nod dup ultima celul a unei liste, eliminarea ultimului nod presupune determinarea acelei celule p cu proprietatea c p->leg este NULL. Funcia C elimina_ultim returneaz 1 dac lista nu este vid,

114

Structuri dinamice de date. Liste

caz n care este eliminat cu tergere ultimul nod al listei. Dac lista este vid, funcia calculeaz valoarea 0.
Int elimina_ultim(lista *cap,int *info) { if (*cap) { if((*cap)->leg) { for(lista p=*cap;p->leg->leg;p=p->leg); *info=p->leg->inf; free(p->leg); p->leg=NULL; } else { *info=(*cap)->inf; free(*cap); *cap=NULL; } return 1; } return 0; }

Eliminarea primei celule a unei liste care are informaia egal cu o informaie dat Pentru realizarea acestei operaii se poate proceda astfel. Sunt calculate aux i p, unde aux este nodul care precede celulei cu informaie dat n lista din care este efectuat eliminarea (funcia C cautaprecedent) i p=aux->leg.. Dac p este NULL, atunci eliminarea este imposibil. Dac aux este NULL, atunci eliminarea revine la extragerea cu tergere a primului nod din list, altfel este eliminat celula p, succesoare a lui aux n list. Funcia C elimina_informatie implementeaz operaia de eliminare a unui nod cu informaie dat, info, din lista identificat prin parametrul cap.
lista cautaprecedent(lista cap,int info, lista *aux) { lista p; if(cap==NULL)return NULL; else { for(p=NULL,*aux=cap;(*aux)&& ((*aux)->inf-info);p=*aux,*aux=(*aux)->leg); if((*aux)==NULL)return NULL; return p; } } int elimina_informatie(lista *cap,int info) { lista aux,p; p=cautaprecedent(*cap,info,&aux); if(aux==*cap) { *cap=(*cap)->leg; free(aux); return 1; } else if(p) { p->leg=aux->leg;

115

Programarea calculatoarelor
free(aux); return 1; } return 0; }

Eliminarea nodului care succede primei componente al crei cmp de informaie este cunoscut Funcia C elimina_dupa_informatie returneaz valoarea 1 dac eliminarea este posibil, altfel calculeaz valoarea 0. n situaia n care informaia infodat a fost gsit n cmpul corespunztor nodului nodul p (p nu este NULL) i p are succesor n list, este realizat eliminarea nodului p->leg.
int elimin_dupa_informatie(lista cap,int *info, int infodat) { lista aux,p; p=cauta(cap,infodat); if((p)&&(p->leg)) { aux=p->leg; p->leg=aux->leg; *info=aux->inf; free(aux); return 1; } return 0; }

7.3 Liste circulare


n anumite situaii este preferabil renunarea la structura de tip linear a listelor i utilizarea unei legturi de la ultima component ctre capul listei, rezultnd structura de list circular. Principalul avantaj al utilizrii acestui tip de structur rezid din posibilitatea de accesare oricrui alt element al listei pornind din orice element. Dac nodul cutat este situat dup nodul curent, este iniiat un proces de cutare similar listelor lineare. n caz contrar, nodul poate fi accesat prin parcurgerea listei de la primul su element, care, n procesul de cutare, este atins dup parcurgerea n ntregime a listei, ncepnd de la nodul curent. n continuare sunt prezentate funciile C pentru realizarea unor operaii de baz n lucrul cu liste circulare.
#include<stdio.h> #include<conio.h> #include<alloc.h> typedef struct nod { int inf; struct nod *leg; } list, *lista;

116

Structuri dinamice de date. Liste


int inserare_la_inceput(lista int stergere_la_inceput(lista int inserare_la_sfarsit(lista int stergere_la_sfarsit(lista void parc(lista); lista cauta(lista,int); *,int); *,int *); *,int); *,int *);

void main() { clrscr(); int n,info; lista cap=NULL; printf("Numarul de noduri:"); scanf("%i",&n); printf("Introduceti informatiile\n"); for(int i=0;i<n;i++){ scanf("%i",&info); if(inserare_la_inceput(&cap,info)); else {printf("\n Spatiu insuficient \n"); return; } } printf("\nLista rezultata\n"); parc(cap); printf("\n\nLista dupa extragerea primului element:\n"); if(stergere_la_inceput(&cap,&info)) parc(cap); else printf("\nEroare: lista vida"); printf("\n\nInformatia nodului de introdus la sfarsit:"); scanf("%i",&info); if(inserare_la_sfarsit(&cap,info)){ printf("Lista rezultata\n"); parc(cap); } else printf("\n Spatiu insuficient \n"); printf("\n\nLista dupa extragerea ultimului element:"); if(stergere_la_sfarsit(&cap,&info)){ printf("\nInformatia extrasa %i\nLista rezultata:",info); parc(cap); } else printf("\nEroare:Lista vida"); getch(); } void parc(lista cap) { lista p=cap; if(cap){ printf("%i ",cap->inf); for(p=p->leg;p-cap;p=p->leg)

117

Programarea calculatoarelor
printf("%i ",p->inf); } else printf("\nLista vida"); } lista cauta(lista cap,int info) { if(cap==NULL)return NULL; if(cap->inf==info) return cap; for(lista p=cap->leg;p!=cap;p=p->leg) if(p->inf==info) return p; return NULL;} int inserare_la_inceput(lista *cap,int info) { lista nou,ultim; if(nou=(lista)malloc(sizeof(list))){ nou->inf=info; nou->leg=*cap; if(*cap){ for(ultim=*cap;ultim->leg!=(*cap);ultim=ultim->leg); ultim->leg=nou; } else nou->leg=nou; *cap=nou; return 1; } return 0; } int stergere_la_inceput(lista *cap,int *info) { if(*cap){ lista aux=*cap; *info=aux->inf; for(lista ultim=*cap; ultim->leg!=(*cap);ultim=ultim->leg); if(ultim==(*cap)) *cap=NULL; else{ *cap=(*cap)->leg; ultim->leg=*cap; } free(aux); return 1; } return 0; } int inserare_la_sfarsit(lista *cap,int info) { lista nou,ultim; if(nou=(lista)malloc(sizeof(list))){ nou->leg=*cap;nou->inf=info; if(*cap==NULL){ *cap=nou; (*cap)->leg=*cap; }

118

Structuri dinamice de date. Liste


else{ for(ultim=*cap;ultim->leg!=(*cap); ultim=ultim->leg); ultim->leg=nou; } return 1; } return 0; } int stergere_la_sfarsit(lista *cap,int *info) { if (*cap){ if((*cap)->leg!=(*cap)){ for(lista pultim=*cap; pultim->leg->leg!=(*cap);pultim=pultim->leg); *info=pultim->leg->inf; free(pultim->leg); pultim->leg=(*cap); } else{ *info=(*cap)->inf; free(*cap); *cap=NULL; } return 1; } return 0; }

7.4 Stive i cozi


Aa cum a rezultat din subcapitolele precedente, operaiile de inserare i eliminare sunt permise la oricare dintre componentele unei liste. O serie de aplicaii pot fi modelate utiliznd liste lineare n care introducerea i respectiv eliminarea informaiilor este permis numai la capete. n acest scop au fost introduse tipurile de list stiv i coad prin impunerea unui tip de organizare a aplicrii operaiilor de inserare i eliminare. 7.4.1 Stiva Se numete stiv o list organizat astfel nct operaiile de inserare i eliminare sunt permise numai la prima component. Acest mod de organizare corespunde unei gestiuni LIFO (Last In First Out) a informaiei stocate. Operaiile de baz efectuate asupra unei stive pot fi realizate similar cazului listelor dinamice lineare, innd cont c inserarea/extragerea unui element sunt posibile numai n prima poziie (vezi modulul de inserare la nceputul unei liste i respectiv funcia de extragere a primului nod dintr-o list, prezentate n 7.2).

119

Programarea calculatoarelor

7.4.2 Coada Se numete coad o list organizat astfel nct operaia de inserare este permis la ultima component, iar operaia de eliminare este permis numai la prima component. Acest mod de organizare corespunde unei gestiuni FIFO (First In First Out) a informaiei stocate. Implementarea unei liste de tip coad poate fi efectuat att printr-o structur static (masiv unidimensional), ct i printr-o structur dinamic de tip list. Pentru optimizarea operaiilor de inserare/extragere, n cazul implementrii cozilor prin structuri dinamice lineare, este necesar utilizarea a dou informaii: adresa primei componente i adresa ultimei componente. Aceste informaii pot fi meninute explicit prin utilizarea a doi pointeri sau prin utilizarea unui pointer i a unei structuri de list circular. O variant alternativ de implementare a unei liste de tip coad dinamic este obinut prin considerarea unei liste circulare, cu memorarea adresei ultimului element. n continuare sunt prezentate operaiile de inserare i extragere a unei informaii dintr-o list liniar de tip coad.
#include<stdio.h> #include<conio.h> #include<alloc.h> typedef struct nod{ int inf; struct nod *leg; } list, *lista; int inserare(lista *,lista *,int); int extragere(lista *,lista *,int *); void parc(lista); void main() { clrscr(); int n,info; lista cap=NULL,ultim=NULL; printf("Numarul de noduri:"); scanf("%i",&n); printf("Introduceti informatiile\n"); for(int i=0;i<n;i++){ scanf("%i",&info); if(inserare(&cap,&ultim,info)); else {printf("\n Spatiu insuficient \n"); return; } }

120

Structuri dinamice de date. Liste


printf("\nCoada rezultata\n"); parc(cap); printf("\n\nCoada dupa o extragere:\n"); if(extragere(&cap,&ultim,&info)) parc(cap); else printf("\nEroare: Coada vida"); getch(); } void parc(lista cap) { if(cap){ printf("%i ",cap->inf); parc(cap->leg); } } int extragere(lista *cap,lista *ultim,int *info) { if(*cap){ lista aux=*cap; *info=aux->inf; if((*ultim)==(*cap)) *cap=*ultim=NULL; else *cap=(*cap)->leg; free(aux); return 1; } return 0; } int inserare(lista *cap,lista *ultim,int info) { lista nou; if(nou=(lista)malloc(sizeof(list))){ nou->inf=info;nou->leg=NULL; if(*cap==NULL) *cap=*ultim=nou; else{ (*ultim)->leg=nou; (*ultim)=nou; } return 1; }

return 0;}

121

8 Grafuri

Grafurile sunt structuri de date cu aplicaii n multe domenii ale informaticii, algoritmii pentru reprezentarea i prelucrarea grafurilor fiind considerai fundamentali n acest domeniu. n subcapitolul 8.1 sunt prezentate principalele noiuni ale domeniului, precum i modalitile uzuale de reprezentare a structurii de graf. n continuare sunt descrise tehnicile de parcurgere a grafurilor n lime i n adncime. Traversarea n adncime a grafurilor determin obinerea unei clasificri a muchiilor, n funcie de care pot fi derivate diferite proprieti ale grafurilor. Verificarea conexitii i calculul drumurilor n grafuri sunt tratate n subcapitolul 8.3. n finalul capitolului este studiat problema determinrii circuitelor i ciclurilor n grafuri i digrafuri.

8.1 Definiii i reprezentri ale grafurilor


Definiia 8.1.1 Se numete graf sau graf neorientat o structur G=(V,E), unde V este o mulime nevid, iar E este o submulime posibil vid a mulimii perechilor neordonate cu componente distincte din V. Elementele mulimii V se numesc vrfuri, iar obiectele mulimii E se numesc muchii. Dac e E, e = (u,v) not.uv , vrfurile u i v se numesc extremiti ale lui e, muchia e fiind determinat de vrfurile u i v. Dac e=uv E se spune c vrfurile u, v sunt incidente cu muchia e. Definiia 8.1.2 Fie G=(V,E) graf. Vrfurile u, v sunt adiacente n G dac uv E. Definiia 8.1.3 Graful G=(V,E) este graf finit, dac V este o mulime finit. n cadrul acestui capitol vor fi considerate n exclusivitate grafurile finite, chiar dac acest lucru nu va fi precizat n mod explicit. Definiia 8.1.4 Fie Gi =(V i,Ei), i=1,2 grafuri. G2 este un subgraf al grafului G1 dac V2 V1 i E 2 E1 . G2 este un graf parial al lui G1 dac V2=V1 i G2 este subgraf al lui G1.

122

Grafuri

Definiia 8.1.5 Un digraf este o structur D=(V,E), unde V este o mulime nevid de vrfuri, iar E este o mulime posibil vid de perechi ordonate cu componente elemente distincte din V. Elementele mulimii E sunt numite arce sau muchii ordonate. Un graf direcionat este o structur D=(V,E), unde V este o mulime nevid de vrfuri, iar E este o mulime posibil vid de perechi ordonate cu componente elemente din V, nu neaprat distincte. Evident, orice digraf este un graf direcionat. Terminologia utilizat relativ la digrafuri este similar celei corespunztoare grafurilor. n continuare vom referi prin muchie i elementele mulimii E ale unui graf direcionat, n situaia n care este tratat cazul unui graf oarecare (neorientat sau direcionat). Definiia 8.1.6 Se numete graf ponderat o structur (V,E,W), unde G=(V,E) este graf i W este o funcie definit prin W : E (0 , ) . Funcia W este numit pondere i ea asociaz fiecrei muchii a grafului un cost/ctig al parcurgerii ei. Definiia 8.1.7 Fie G=(V,E) un graf, u,vV. Secvena de vrfuri :u0,u1,..,un este un u-v drum dac u0=u, un=v, uiui+1E pentru toi i, 0 i n . Definiia 8.1.8 Fie G=(V,E) un graf. Elementul vV se numete vrf izolat dac, pentru orice e E, u nu este incident cu e. 8.1.1 Moduri de reprezentare a grafurilor Cea mai simpl reprezentare a unui graf este cea intuitiv, grafic; fiecare vrf este figurat printr-un punct, respectiv muchiile sunt reprezentate prin segmentele de dreapt, orientate (n cazul digrafurilor) sau nu i etichetate (n cazul grafurilor ponderate) sau nu, avnd ca extremiti punctele corespunztoare vrfurilor care o determin Exemple 8.1.1 Fie G=(V,E) graf, cu V={1,2,3,4,5,6}, E={(1,2),(1,3),(2,5),(3,5),(5,6)}. O posibil reprezentare grafic este,

1 2 4 3 5 6

123

Programarea calculatoarelor

8.1.2 Fie D=(V,E) digraf, V={1,,5}, E={(1,2), (1,3), (1,5), (2,5), (3,5), (4,1), (5,4)}. Digraful poate fi reprezentat grafic astfel,

2 3

5
8.1.3 Fie D=(V,E) graf direcionat, V={1,2,3,4,5}, E={(1,2), (1,3), (1,5) (2,5), (3,5), (4,4)}. Reprezentarea grafic este,

1 4

2 3

8.1.4 Fie G=(V,E,W) graf ponderat, V={1,2,3,4}, E={(1,2), (1,3), (1,4), (2,3), (2,4)}, W((1,2))=5, W((1,3))=1, W((1,4))=7, W((2,3))=4, W((2,4))=2. O posibil reprezentare grafic este:

1 4 7

2
2

4
n scopul reprezentrii grafurilor n memoria calculatorului sunt utilizate n general urmtoarele structuri de date.

124

Grafuri

8.1.2

Reprezentarea matriceal

Grafurile, digrafurile i grafurile direcionate pot fi reprezentate prin matricea de adiacen. Dac G=(V,E ) este graf, digraf sau graf direcionat cu V = n , atunci matricea de adiacen A Mnxn({0,1}) are componentele,

1, dac (vi , v j ) E , aij = 0 , altfel

unde vi, vj reprezint cel de-al i-lea, respectiv cel de-al j-lea nod din V. n cazul unui graf neorientat, matricea de adiacen este simetric. Exemplu 8.1.5 Graful din exemplul 8.1.1, digraful din exemplul 8.1.2 i graful direcionat din exemplul 8.1.3 sunt reprezentate prin matricele de adiacen,
0 1 1 A= 0 0 0 1 1 0 0 0 0 0 0 0 1 0 0 0 0 0 1 0 (8.1.1), A = 0 0 0 0 0 0 1 1 1 0 0 1 0 0 0 0 1 0

1 0 0 0 0

1 0 0 0 0

0 0 0 0 1

1 0 1 0 1 (8.1.2), A = 0 0 0 1 0

1 0 0 0 0

1 0 0 0 0

0 0 0 1 0

1 1 1 (8.1.3) 0 0

n cazul grafurilor ponderate, reprezentarea poate fi realizat prin matricea ponderilor. Dac G=(V,E,W) este graf ponderat, V = n , W Mnxn((0, )) are componentele,

W (( vi , v j )), dac (vi , v j ) E wi , j = , altfel

= 0 , dac ponderea are semnificaia de ctig, respectiv = n cazul n care


se dorete reprezentarea costurilor ca ponderi ale grafului. Exemplu 8.1.6 Presupunnd c ponderile reprezint costuri, matricea de reprezentare

unde vi, vj reprezint cel de-al i-lea, respectiv cel de-al j-lea nod din V,

5 a grafului din exemplul 8.1.4. este W = 1 7

5 1 7 4 2 . 4 2

125

Programarea calculatoarelor

8.1.3 Reprezentarea tabelar Reinnd muchiile prin intermediul extremitilor i eventual valoarea ponderii ei, se obine reprezentarea tabelar, mai economic din punctul de vedere al spaiului de memorie necesar. Dac graful conine vrfuri izolate atunci este necesar pstrarea acestora ntr-un vector suplimentar VS. Mulimea muchiilor este reinut ntr-o matrice A cu E linii i c coloane, unde c=2 dac graful nu este ponderat, altfel c=3. n primele dou coloane se scriu perechile de vrfuri ce determin muchiile, n cazul grafurilor ponderate cea de-a treia coloan conine valoarea ponderii muchiei respective. Exemple 8.1.7 Graful din exemplul 8.1.1 poate fi reprezentat astfel, VS=(4),

1 1 A = 2 3 5

2 3 5 5 6
2 3 5 5 . 5 1 4

1 1 1 8.1.8 Digraful din exemplul 8.1.2 este reprezentat prin A = 2 3 4 5

1 1 1 8.1.9 Graful direcionat din 8.1.3. este reprezentat prin A = 2 3 4 1 1 reprezentat prin intermediul matricei A = 2 1 2 2 3 3 4 4 5 1 4 . 7 2

2 3 5 . 5 5 4

8.1.10 Graful ponderat din exemplul 8.1.4. nu are vrfuri izolate, deci este

126

Grafuri

8.1.4 Reprezentarea prin intermediul listelor Aceast reprezentare permite utilizarea economic a spaiului de memorare i, n anumite cazuri, implementri mai eficiente pentru anumite clase de algoritmi. Vrfurile grafului sunt memorate ntr-o list, fiecare nod al listei N coninnd o referin spre lista vecinilor vrfului memorat ca informaie n N. Dac graful nu este ponderat, el poate fi reprezentat prin structura list de liste, i anume: nodurile grafului se trec ntr-o list L_nod, fiecare celul avnd structura, informaie legtur vecini legtur nod urmtor unde, cmpul informaie conine identificatorul nodului; legtur vecini reprezint referina spre nceputul listei vecinilor; legtur nod urmtor conine adresa urmtoarei celule din lista L_nod. Un graf ponderat poate fi reprezentat n mod similar, cu diferena c, fiecare celul din lista vecinilor conine i ponderea muchiei respective (muchia care are ca extremiti vrful referit prin identificatorul de nod din lista vecinilor i respectiv vrful indicat de informaia acelei celule din L_nod ce conine adresa primului element al listei vecinilor).

8.2 Modaliti de parcurgere a grafurilor


Modalitatea de vizitare a tuturor vrfurilor grafului n care fiecare vrf al grafului este vizitat o singur dat se numete parcurgere sau traversare. n acest paragraf sunt prezentate metodele de parcurgere BF (n lime), DF (n adncime) i metoda DF generalizat, notat DFG. Primele dou metode de parcurgere sunt aplicate grafurilor neorientate respectiv grafurilor direcionate i presupun selectarea unui vrf iniial v0 i identificarea acelor vrfuri ale grafului v cu proprietatea c exist cel puin un drum de la vrful iniial ctre v. Grafurile cu proprietatea c oricare dou vrfuri sunt conectate printr-un drum se numesc grafuri conexe i sunt prezentate n 8.3. Dac graful este conex, atunci prin aplicarea metodelor de parcurgere vor fi identificate toate vrfurile grafului. Cele dou modaliti de parcurgere sunt prezentate n continuare n cazul grafurilor neorientate, extinderea la digrafuri i grafuri direcionate fiind imediat. Studiul proprietii metodei BF de a calcula distanele minim ntre orice vrf al grafului conectat de vrful iniial i vrful iniial este prezentat n cazul grafurilor oarecare. Parcurgerea DFG presupune vizitarea tuturor vrfurilor unui graf sau graf direcionat prin aplicarea metodei DF tuturor vrfurilor care, dup ultima traversare DF, nu au fost nc vizitate.

127

Programarea calculatoarelor

8.2.1 Metoda de parcurgere BF (Breadth First)


Traversarea BF presupune parcurgerea n lime a grafului, n sensul c, vrfurile grafului sunt prelucrate n ordinea cresctoare a distanelor la vrful iniial (teorema 8.2.1). Distana de la u la v, notat (u , v ) , este numrul de muchii ale unui cel mai scurt u-v drum. La momentul iniial vrf curent este v0. Deoarece vrful curent la fiecare moment trebuie s fie unul dintre vrfurile aflate la distan minim de v0 se poate proceda n modul urmtor: iniial lui v0 i se asociaz valoarea 0, d [v0 ] = 0 i

fiecrui vrf v v0 i se asociaz valoarea , d [v ] = . Dac valoarea asociat vrfului curent este m, atunci fiecruia dintre vecinii acestuia de valoare li se asociaz valoarea m+1. Se observ c, dac dup ce toate vrfurile de valoare m au fost considerate i nici unui vrf nu i-a fost recalculat valoarea, atunci toate vrfurile conectate cu v0 au fost vizitate, deci calculul se ncheie. Exemple 8.2.1 Fie graful,

1 2 3 6 4 5 7

i v0=1.Valorile calculate prin aplicarea metodei prezentate sunt,


vrf d 0 1 2 0 0 0 0 1 2 3 4 5 6 7

1 1 1

1 1 1

1 1 1


2 2


2 2

1 1 1

128

Grafuri

8.2.2 Fie graful,

1 2 3

4 5
iniial.

9 11

10

i v0=1. Se observ c vrfurile 8, 9, 10 i 11 nu sunt conectate cu vrful Valorile rezultate prin aplicarea metodei sunt:
vrf d 0 1 2 0 0 0 0 1 2 3 4 5 6 7 8 9 10 11

1 1 1

1 1 1


2 2

1 1 1


2 2

1 1 1

Se observ c valorile lui d calculate n final reprezint numrul de muchii corespunztor celui mai scurt drum care conecteaz vrful iniial cu vrful respectiv, pentru vrfurile neconectate cu v0 valoarea d[v0] rezultat la terminarea calculului este . Fie G=(V,E) un graf, V = n . O alternativ de implementare a metodei BF este construit prin utilizarea urmtoarelor structuri de date, A matricea de adiacen a grafului; o structur de tip coad, C, n care sunt introduse vrfurile ce urmeaz a fi vizitate i procesate (n sensul cercetrii vecinilor lor); un vector c cu n componente, unde, 1, dac i a fost adugat n coad ci = 0, altfel Componentele vectorului c sunt iniializate cu valoarea 0. Parcurgerea BF poate fi descris astfel, coada C este iniializat cu vrful v0; ct timp C , este extras i vizitat un vrf i din coad, apoi sunt introdui n coad vecinii lui i care nu au fost deja introdui (acele vrfuri k cu proprietatea c c[k]=0 i a[i][k]=1). Vrfurile i ce au fost introduse n coad sunt marcate prin c[i]=1.

129

Programarea calculatoarelor

Exemplu 8.2.3. Pentru graful din exemplul 8.2.1., aplicarea metodei de traversare BF determin urmtoarea evoluie,
t t=1 t=2 t=3 t=4 t=5 t=6 t=7 t=8 c 1 1 1 1 1 1 1 1 1 t t=1 t=2 t=3 t=4 t=5 t=6 t=7 t=8 2 0 1 1 1 1 1 1 1 C 1 2 3 4 7 5 6 3 0 1 1 1 1 1 1 1 4 0 1 1 1 1 1 1 1 5 0 0 1 1 1 1 1 1 6 0 0 0 1 1 1 1 1 7 0 1 1 1 1 1 1 1

3 4 7 5 6

4 7 5 6

7 5 6

Observaie Deoarece graful din exemplul 8.2.1. este conex, traversarea BF realizeaz vizitarea tuturor vrfurilor grafului. Aplicarea metodei BF grafului din exemplul 8.2.2. nu determin vizitarea vrfurilor 8,9, 10 i 11, deoarece acestea sunt vrfuri neconectate cu vrful iniial. Cu alte cuvinte, metoda BF aplicat unui graf determin vizitarea tuturor vrfurilor care sunt conectate cu vrful iniial selectat. Sursa C pentru implementarea metodei BF este,
#include <stdio.h> #include <conio.h> #include <alloc.h> typedef struct nn { int inf; struct nn *leg; } nod,* pnod; int insereaza_coada(pnod *head,pnod *tail,int info) { pnod nou; if(nou=(pnod)malloc(sizeof(nod))){ nou->inf=info; nou->leg=NULL; if(*head==NULL) *head=nou; else (*tail)->leg=nou; *tail=nou; return 1; } else return 0; }

130

Grafuri
int extrage_coada(pnod *head,pnod *tail, int *info) { if(*head){ pnod aux=*head; *info=(*head)->inf; (*head)=(*head)->leg; free(aux); if(*head==NULL)*head=*tail=NULL; return 1; } else return 0;} void breadth_first(int v0,int a[10][10],int n) { pnod head=NULL; pnod tail=NULL; int c[10]; for(int i=0;i<n;c[i++]=0); int r=insereaza_coada(&head,&tail,v0); c[v0]=1; while(head){ r=extrage_coada(&head,&tail,&i); printf("\n%i",i+1); for(int k=0;k<n;k++) if((a[i][k]==1)&&(c[k]==0)){ r=insereaza_coada(&head,&tail,k); c[k]=1; } } } void main() { int n,v0,a[10][10]; clrscr(); printf("Numarul de varfuri:"); scanf("%i",&n); printf("\nMatricea de adiacenta\n"); for(int i=0;i<n;i++) for(int j=0;j<i;j++){ scanf("%i",&v0); a[j][i]=a[i][j]=v0; } for(i=0;i<n;i++)a[i][i]=0; printf("\nVarful initial "); scanf("%i",&v0); printf("\nParcurgerea BF a grafului este"); breadth_first(v0,a,n); }

n continuare sunt prezentate o serie de rezultate prin care este demonstrat proprietatea parcurgerii BF de a calcula distana minim de la orice vrf v conectat de vrful iniial v0 la v0. pentru orice muchie (u , v ) E , (v0 , v ) (v0 ,u ) + 1 . Lema 8.2.1 Fie G=(V,E) un graf oarecare i v0 V arbitrar. Atunci,

131

Programarea calculatoarelor

Demonstraie Dac u este conectat de v0 n G, atunci, evident, i v este conectat de v0 n G. n acest caz, cel mai scurt drum de la v0 la v nu poate fi mai lung dect cel mai scurt drum de la v0 la u prelungit cu muchia (u,v), deci afirmaia este demonstrat. n situaia n care u nu este conectat de v0 n G, atunci, evident, rezult inegalitatea (v0 , v ) (v0 ,u ) + 1 . Lema 8.2.2 Fie G=(V,E) un graf neorientat sau graf direcionat i v0 V vrf iniial al procedurii de traversare BF. Atunci orice v V vizitat, are loc inegalitatea d [v ] (v0 , v ) . Demonstraie Afirmaia este demonstrat prin inducie dup ordinea vizitrii BF a elementelor v V conectate cu v0 n G.

d [v ] = 0 v = v0 , rezult i, u V \ {v}, d [u ] = (u , v ) , deci afirmaia este adevrat.


Dac

pentru

orice

Fie v vrful vizitat ca rezultat al procesrii vrfului u. Prin aplicarea ipotezei inductive, d [u ] (v0 ,u ) , a rezultatului lemei 8.2.1 i a procedurii de parcurgere BF obinem, d [v ] = d [u ] + 1 (v0 ,u ) + 1 (v0 , v ) . Deoarece vrful v nu a fost anterior gsit n lista vecinilor nici unui nod studiat naintea vrfului u, v este inserat n C.

C = {v1 , v 2 ,...,v p } coada calculat la un moment al aplicrii procedurii de


parcurgere BF. Atunci urmtoarele inegalitile sunt verificate, [Cor,Lei a]

Lema 8.2.3 Fie G=(V,E) un graf neorientat sau graf direcionat i

d v p d [v1 ] + 1

d [vi ] d [vi +1 ], i = 1,..., p 1 .

[ ]

Teorema 8.2.1 Corectitudinea procedurii BF Fie G=(V,E) graf neorientat sau graf direcionat i v0 V vrf iniial al procedurii de traversare BF. Atunci metoda BF calculeaz toate vrfurile v conectate cu v0 n G i, pentru orice v v0 , v V vizitat, cel mai scurt v0-v drum este format dintr-un v0-u drum i muchia (u,v), unde u este acel vrf prin procesarea cruia este determinat vizitarea lui v. Demonstraie Fie Vk = {v V / (v0 , v ) = k } mulimea vrfurilor situate la distan k de v0. Rezultatul teoremei este demonstrat prin inducie dup k, cu ipoteza inductiv, Ik: v Vk , exist un singur moment al execuiei procedurii BF n care este determinat urmtoarea evoluie, d [v ] = k i v C ;

132

Grafuri

dac v v0 , vrful u care determin inserarea lui v n C este element al Pentru k = 0 , V0 = {v0 } . La momentul iniial, C v0 i d [v0 ] = 0 , deci I este verificat. Verificarea ipotezei Ik n condiiile n care I0 ,,Ik-1 sunt adevrate este bazat pe urmtoarea observaie. Pe tot parcursul execuiei procedurii BF, C i, dac u C , atunci d [u ] i vrful care a determinat procesarea lui u rmn constante. Din lema 8.2.3 rezult c, dac C = v1 , v 2 ,..., v p , atunci Fie v Vk , k 1 . Din proprietatea de monotonie i ipoteza Ik-1, rezult c v a fost inserat n C dup ce toate vrfurile u Vk 1 au fost deja inserate n coad. Deoarece (v0 , v ) = k , obinem c exist un v0-v drum de lungime k i u Vk 1 astfel nct (u , v ) E . Fr a pierde din generalitate, vom presupune c u este primul vrf din Vk 1 inserat n C. La momentul n care vrful u devine prim element al cozii C, toate vrfurile vecine cu u n G sunt inserate n C, deci i vrful v. Rezult c d [v ] = d [u ] + 1 = k , unde u este acel vrf care precede v pe un cel mai scurt v0-v drum. Observaii 1. Demonstrarea teoremei de corectitudine a parcurgerii BF stabilete i o modalitate de calcul al unui cel mai scurt v0-v drum astfel. Pentru orice v V conectat cu v0 n G, fie p[v ] V vrful a crui procesare a determinat inserarea lui prelungit cu muchia ( p[v ], v ) . 2. Aplicarea metodei BF unui graf oarecare G determin obinerea unui arbore (vezi capitolul 9) Gp, numit subgraful predecesorilor definit de BF pe G, unde G p = V p , E p i v n C. Un v0-v drum de lungime minim este v0- p[v ] drumul cel mai scurt mulimii Vk 1 .

d [vi ] d [vi +1 ], i = 1,..., p 1 .

V p = {v V / p[v ] V } {v0 } , E p = {( p[v ], v ) E / v V \ {v0 }} .

Exemplu 8.2.4 Prin aplicarea procedurii BF grafului din 8.2.1, obinem,

2 5

3 6

133

Programarea calculatoarelor

8.2.2 Metoda de parcurgere DF (Depth First)


Ideea metodei DF revine la parcurgerea n adncime a grafurilor. Considernd v0 vrf iniial i M mulimea vrfurilor vizitate de procedur, pentru vizitarea vecinilor este considerat unul din vrfurile din M cu proprietatea c lungimea drumului calculat de metod pn la vrful iniial v0 este maxim. Implementarea acestei metode poate fi realizat n mai multe moduri, pentru meninerea mulimii vrfurilor grafului disponibilizate pn la momentul curent fiind utilizat o structur de de date de tip stiv S. La momentul iniial se introduce n stiv v0. La fiecare pas, se preia cu tergere ca vrf curent vrful stivei S i se introduc n stiv vecinii nc nevizitai ai vrfului curent. Un vrf se marcheaz ca vizitat n momentul introducerii lui n S. Calculul continu pn cnd este efectuat un acces de preluare din stiv i se constat c S este vid. Pentru gestiunea vrfurilor vizitate, se utilizeaz un vector c cu n componente, unde n reprezint numrul vrfurilor grafului i, la fiecare moment, componentele sunt:

Componentele vectorului c vor fi iniializate cu valoarea 0. Exemple 8.2.5 Pentru graful,


2

1, dac i a fost vizitat ci = 0, altfel

1 3 6 4 5 7

i v0=1, prin aplicarea metodei descrise, rezult urmtoarea evoluie.


t t=1 t=2 t=3 t=4 t=5 t=6 t=7 t=8 c 1 1 1 1 1 1 1 1 1 t t=1 t=2 t=3 t=4 t=5 t=6 t=7 t=8 2 0 1 1 1 1 1 1 1 S 1 7 6 4 5 3 2 3 0 1 1 1 1 1 1 1 4 0 1 1 1 1 1 1 1 5 0 0 0 0 1 1 1 1 6 0 0 1 1 1 1 1 1 7 0 1 1 1 1 1 1 1

4 4 3 3 2

3 3 2 2

2 2

134

Grafuri

Ordinea n care sunt vizitate vrfurilor corespunztor acestei variante de parcurgere DF este: 1, 2, 3, 4, 7, 6, 5. 8.2.6 Pentru graful din exemplul 8.2.2 vrfurile 8,9,10 care nu sunt conectate cu vrful iniial nu vor fi vizitate nici prin aplicarea metodei DF. Ordinea n care sunt vizitate vrfurile corespunztore acestei variante este: 1, 2, 3, 4, 6, 7, 5. O variant de implementare a metodei DF rezult prin gestionarea stivei S n modul urmtor. Iniial vrful v0 este unicul component al lui S. La fiecare etap se preia, fr tergere, ca vrf curent vrful stivei. Se introduce n stiv unul dintre vecinii vrfului curent nc nevizitat. Vizitarea unui vrf revine la introducerea lui n S. Dac vrful curent nu are vecini nc nevizitai, atunci el este eliminat din stiv i este efectuat un nou acces de preluare a noului vrf al stivei ca vrf curent. Calculul se ncheie n momentul n care este efectuat un acces de preluare a vrfului stivei ca vrf curent i se constat c S este vid. Evident, nici n cazul acestei variante nu vor fi vizitate vrfurile care nu sunt conectate cu vrful iniial ales. Exemplu 8.2.7 Pentru graful,

1 3 6 4

5
c t t=1 t=2 t=3 t=4 t=5 t=6 t=7 t=8 t=9 t=10 t=11 t=12 t=13 t=14 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 2 0 1 1 1 1 1 1 1 1 1 1 1 1 1 3 0 0 0 1 1 1 1 1 1 1 1 1 1 1 4 0 0 1 1 1 1 1 1 1 1 1 1 1 1

i v0=1, prin aplicarea metodei descrise, rezult urmtoarea evoluie.


5 0 0 0 0 0 0 0 0 0 1 1 1 1 1 6 0 0 0 0 1 1 1 1 1 1 1 1 1 1 7 0 0 0 0 0 1 1 1 1 1 1 1 1 1

135

Programarea calculatoarelor
S t t=1 t=2 t=3 t=4 t=5 t=6 t=7 t=8 t=9 t=10 t=11 t=12 t=13 t=14 1 2 4 3 6 7 6 3 4 5 4 2 1 1 2 4 3 6 3 4 2 4 2 1

1 2 4 3 4 2 1 2 1

1 2 4 2 1 1

1 2 1

Ordinea n care sunt vizitate vrfurile corespunztor acestei variante este: 1, 2, 4, 3, 6, 7, 5. Urmtoarea surs C implementeaz varianta precedent de parcurgere DF.
#include <stdio.h> #include <conio.h> #include <alloc.h> typedef struct nn{ int inf; struct nn *leg; }nod,* pnod; int insereaza_stiva(pnod *head,int info) { pnod nou; if(nou=(pnod)malloc(sizeof(nod))){ nou->inf=info; nou->leg=*head; *head=nou; return 1; } else return 0; } int extrage_stiva(pnod *head,int *info) { if(head){ pnod aux=*head; *info=(*head)->inf; (*head)=(*head)->leg; free(aux); return 1; } else return 0;} void depth_first(int v0,int a[10][10],int n) {

136

Grafuri
pnod head=NULL; int c[10]; for(int i=0;i<n;c[i++]=0); int r=insereaza_stiva(&head,v0); c[v0]=1; printf("\n%i",v0+1); while(head){ r=extrage_stiva(&head,&i); for(int k=0;k<n;k++) if((a[i][k]==1)&&(c[k]==0)){ r=insereaza_stiva(&head,k); c[k]=1;printf("\n%i",k+1); } } } void main() { int n,v0,a[10][10]; clrscr(); printf("Numarul de varfuri:");scanf("%i",&n); printf("\nMatricea de adiacenta\n"); for(int i=0;i<n;i++) for(int j=0;j<i;j++){ scanf("%i",&v0); a[j][i]=a[i][j]=v0; } for(i=0;i<n;i++)a[i][i]=0; printf("\nVarful initial ");scanf("%i",&v0); printf("\nParcurgerea DF a grafului este"); depth_first(v0,a,n); }

8.2.3 Parcurgerea n adncime n varianta generalizat DFG Urmtoarea variant de implementare a parcurgerii n adncime, DFG, determin vizitarea tuturor vrfurilor grafului analizat (considerat neorientat sau direcionat), indiferent dac acesta este conex sau neconex. Fie G=(V,E) un graf oarecare. Vom presupune n continuare c niciun vrf din V nu este etichetat cu informaia 0. Implementare traversrii DFG utilizeaz urmtoarele structuri, A, matricea de adiacen a grafului; p, vectorul predecesorilor (vezi 8.2.1); f, vectorul care marcheaz ncheierea analizrii listei vrfurilor vecinilor nodului curent; mark, definit pentru orice v V prin, 0, dac v nu a fost nc analizat mark[v]= 1, dac v este procesat la momentul curent 2, dac consultarea lui v este ncheiat d, definit pentru orice v V prin d[v]=t, unde t este momentul de timp la care este iniiat analiza vrfului v.

137

Programarea calculatoarelor

Considernd t variabil public desemnnd momentul prelucrrii, procedura DFG poate fi descris prin intermediul urmtoarelor funcii.
void DFG(graf G) { for( u V ){ mark[u]=0; p[u]=0; } t=0; for( u V ) if(!mark[u])DF_Visit(u); } void DF_Visit(varf u) { mark[u]=1; d[u]=t++; for( v V :A[u][v]==1) if(!mark[v]){ p[v]=u; DF_Visit(v); } mark[u]=2; f[u]=t++; }

Prin aplicarea parcurgerii n adncime n varianta generalizat sunt obinute informaii suplimentare ale grafului de intrare. Metoda DFG determin obinerea subgraful predecesorilor, G p , de tip graf pdure (componentele conexe vezi 8.3- sunt arbori - vezi capitolul 9). Fiecare component conex a lui G p este construit prin executarea modulului DF_Visit. De asemenea, vrfurile u , v V au proprietatea c u = p[v ] dac i numai dac funcia DF_Visit(v) a fost apelat la momentul cutrii n lista vecinilor vrfului u. O alt proprietate important a metodei de parcurgere DFG este aceea c, dup ncheierea calculului, este determinat o structur de tip parantez astfel. Dac momentul selectrii vrfului u pentru procesare este marcat prin (u i momentul ncheierii prelucrrii lui u este notat u), atunci istoricul traversrii DFG pentru calculul fiecrui arbore din G p poate fi reprezentat prin intermediul unei expresii corecte din punct de vedere al parantezrii. Teorema 8.2.2 Corectitudinea parantezrii determinat de aplicarea metodei DFG Fie G=(V,E) un graf sau un graf direcionat. Prin aplicarea traversrii DFG este obinut urmtorul rezultat. Pentru orice u , v V , una i numai una din urmtoarele afirmaii este adevrat, [Cor,Lei a]

138

Grafuri

1) 2) 3)

[d [u ], f [u ]] i [d [v], f [v]] sunt disjuncte; [d [u ], f [u ]] [d [v ], f [v]] i u este un descendent al lui v n arborele


corespunztor din G p ; corespunztor din G p .

[d [u ], f [u ]] [d [v ], f [v]]

i u este un ancestor al lui v n arborele

Observaie Fie G=(V,E) un graf sau un graf direcionat. Pe baza procedurii DFG poate fi realizat urmtoarea clasificare a elementelor e = (u , v ) E , 1) muchii de tip arbore n DF- graful pdure G p , etichetate cu T: (u , v ) are eticheta T dac procesarea vrfului v a fost decis ca rezultat al testrii existenei muchiei e; 2) muchii de tip napoi, cu etichet B: (u , v ) este muchie B dac v este 3) muchii de tip nainte, notate cu F: acele muchii (u , v ) , neetichetate cu T i n care v este descendent al lui u ntr-o component conex a DFgrafului pdure G p ; 4) muchii de tip trecere, etichetate cu C: toate muchiile (u , v ) rmase neetichetate dup ncheierea etichetrii cu T, B i F. ancestorul lui u ntr-o component conex a DF- grafului pdure G p ;

Teorema 8.2.3 Fie G=(V,E) un graf neorientat. Orice element e E este fie de tip T, fie de tip B. [Cor,Lei a] Teorema 8.2.4 Criteriu de aciclicitate pentru grafuri direcionate Un graf direcionat este aciclic (vezi 8.4) dac i numai dac niciuna dintre muchii nu este de tip B. [Cor,Lei a] Exemple 8.2.8. Pentru graful
1 2 3 8

4 5 7

10

obinem, 1) ordinea de parcurgere DFG a vrfurilor: 1,2,3,4,6,7,5,8,9,10

139

Programarea calculatoarelor

2) graful pdure G p ,

8 2 4 6 7
3) structurile parantez: (1 (2 (3 3) (4 (6 (7 7) 6) (5 5) 4) 2) 1) i (8 (9 (10 10) 9) 8) 4) clasificarea muchiilor,

1 3 10

2 T 4 B 6 T 7 T T 5

T 3 9 10 T

8 T

B B

8.2.9 Fie graful direcionat,

6 5

7 8

1 4

2 3

Prin parcurgerea DFG obinem urmtoarea ordonare: 1,7,6,8,5,2,4,3. Subgraful predecesorilor este format din urmtoarele componente,

140

Grafuri

1 4 8

2 3

7 6

5
Structurile parantez: (1 (7 (6 (5 5) 6) (8 8) 7) 1) i (2 (4 4) (3 3) 2). Clasificarea muchiilor grafului direcionat este,

1 T 7 T B T 5 6 T 8 C F C 4

2 T T C

B 3

8.3 Drumuri n grafuri. Conexitate


8.3.1 Drumuri; definiii
Una dintre cele mai importante proprieti ale grafurilor o constituie posibilitatea de accesare, prin intermediul unei secvene de muchii (arce), dintr-un vrf dat a oricrui alt vrf al grafului, proprietate cunoscut sub numele de conexitate sau conexiune. Aa dup cum a rezultat n 8.2., dac G=(V,E) este un graf conex, atunci pentru orice vrf iniial v0 considerat metodele BF i DF permit vizitarea tuturor vrfurilor din V. Definiia 8.3.1. Fie G=(V,E) un graf, u,vV. Secvena de vrfuri : u0, u1,..,un este un u-v drum dac u0=u, un=v, uiui+1E pentru toi i, 0 i n . Lungimea drumului, notat l() este egal cu n. Convenional, se numete drum trivial, un drum cu l()=0. Definiia 8.3.2. Fie : u0, u1,..,un un drum n graful G=(V,E). este un drum nchis dac u0=un; n caz contrar, se numete drum deschis. Drumul este

141

Programarea calculatoarelor

elementar dac oricare dou vrfuri din sunt distincte, cu excepia, eventual, a extremitilor. Drumul este proces dac, pentru orice 0 i j n 1 uiui+1 ujuj+1. Evident, orice drum elementar este un proces. Exemplu 8.3.1 Pentru graful,

v2

v4 v5

1: v1, v2, v3, v2, v5, v3, v4 este un v1- v4 drum care nu este proces; 2: v1, v2, v5, v1, v3, v4 este un v1- v4 proces care nu este drum elementar; 3: v1, v3, v4 este un v1- v4 drum elementar.
Definiia 8.3.3. Fie : u0, u1,..,un un drum n graful G=(V,E). : v0, v1,..,vm este un subdrum al lui dac este un drum i pentru orice j, 0 j m , exist i, 0 i n astfel nct ui=vj. Observaie Orice drum cu lungime cel puin 1 conine cel puin un drum elementar cu aceleai extremiti. ntr-adevr, dac : u0, u1,..,un nu este elementar, atunci exist 0 i < j n i i 0 sau j n astfel nct ui=uj. Atunci drumul

v1

v3

u j u j +1 ...u n , dac i = 0 ' : u0 u1 ...u i , dac j = 0 u u ...u u ...u , dac i 0 , j n 0 1 i j +1 n


este de asemenea un u0-un drum. Aplicnd n continuare eliminarea duplicatelor vrfurilor n modul descris, rezult n final un u0-um drum elementar.

142

Grafuri

Exemplu 8.3.2 n graful,

v2 v1 v3 v4 v5

v6

v7 v8 v9 v10

dac : v1, v2, v4, v5, v3, v1, v2, v5, v6, v7, v8, v9, v5, v9, v8, v10, atunci 1: v1, v2, v5, v9, v8, v10, 2: v1, v2, v4, v5, v9, v8, v10 sunt v1-v10 subdrumuri elementare.

8.3.2 Matricea existenei drumurilor; algoritmul Roy-Warshall


Lema 2.3.1 Fie G=(V,E) un graf, V = n . Dac A este matricea de
( adiacen asociat grafului, atunci, pentru orice p1, a ijp ) este numrul vi-vj ( drumurilor distincte de lungime p din graful G, unde A p = a ij p ) .

Demonstraie Demonstrarea acestei afirmaii este realizat prin inducie dup p Pentru p=1, deoarece pentru orice 1 i , j n exist cel mult un vi-vj drum de lungime 1 i dac exist, fie acesta : vi, vj. Rezult c numrul vi-vj drumurilor ( de lungime 1 este egal cu aij1) .

(a

( p 1 )
ij

) este egal cu numrul v -v drumurilor de lungime p-1 n G. = a Cum A =A A = (a ( ) ) , rezult c, 1 i , j n , a


i j p p-1
p ij

( Presupunem c Ap-1 = a ijp 1) are proprietatea c pentru toi 1 i , j n ,

( p) ij

k =1

( p 1 ) ik

a kj .

Orice vi-vj drum de lungime p n G conine un vi-vk drum de lungime p-1 pentru un anume vk adiacent cu vj i reciproc, pentru orice vk adiacent cu vj oricrui vi-vk drum de lungime p-1 i corespunde un vi-vj drum de lungime p. ( Din relaia care caracterizeaz elementele aijp ) , utiliznd ipoteza

( )

inductiv, rezult afirmaia enunat mai sus. Definiia 8.3.4 Fie Mn({0,1)} mulimea matricelor de dimensiuni nxn, componentele fiind elemente din mulimea {0,1}. Pe Mn({0,1)}se definesc operaiile binare, notate i , astfel: pentru orice A=(aij), B=(bij) din Mn({0,1)}, A B=(cij), A B=(dij), unde 1 i , j n , cij=max{aij, bij} dij=max{min{aik, bkj}, 1 k n }.

143

Programarea calculatoarelor

Dac A=(aij) Mn({0,1)}, se noteaz A = a ij matrice definit prin:

{ ( ); k 1} secvena de
k (k)

, k 2 . Dac A este matricea de adiacen a unui graf G=(V,E), atunci pentru fiecare k, 1 k n 1, A a ij
(k )

(1 )

= A, A = A A

( k 1 )

1, dac exist drum de la i la j de lungime k = 0 , altfel


(1) (2) ( n 1 )

se numete matricea existenei Matricea M = A A A drumurilor n graful G. Semnificaia componentelor matricei M este:

0 , dac nu exist vi v j drum n G 1 i , j n , mij = 1, altfel


Exemplu 8.3.3 Pentru graful,
2 1 3

0 1 A= 1 1

1 1 1 0 0 0 , 0 0 1 0 1 0

1 2 0 A = 1 1

0 1 1 1 1 1 , 1 1 1 1 1 1

1 3 1 A = 1 1

1 1 1 0 1 1 , 1 1 1 1 1 1

1 1 M = 1 1

1 1 1 1 1 1 1 1 1 1 1 1

Observaie Calculul matricei existenei drumurilor permite verificarea dac un graf dat este conex. Graful este conex dac i numai dac toate componentele matricei M sunt egale cu 1. Algoritmul Roy-Warshall calculeaz matricea existenei drumurilor ntr-un graf G cu n vrfuri.
void Roy_Warshall (unsigned char a[10][10],unsigned n,unsigned char m[10][10]) {int i,j,k; for (i=0;i<n;i++) for (j=0;j<n;j++) m[i][j]=a[i][j]; for (j=0;j<n;j++) for (i=0;i<n;i++) if(m[i][j]) for (k=0;k<n;k++) if (m[i][k]<m[k][j]) m[i][k]=m[k][j];}

144

Grafuri

Datele de intrare sunt: n, numrul de noduri i A, matricea de adiacen corespunztoare grafului. Matricea M calculat de algoritm constituie ieirea i este matricea existenei drumurilor n graful G.

8.3.3 Componente conexe ale unui graf


Definiia 8.3.5 Fie G=(V,E) graf netrivial. Vrfurile u,v V sunt conectate dac exist un u-v drum n G. Definiia 8.3.6 Dac G este un graf, atunci o component conex a lui G este un subgraf conex al lui G, maximal n raport cu proprietatea de conexitate. Exemplu 8.3.4 Componentele conexe ale grafului

1 2

6 4 3
sunt: C1={1,2,3}, C2={4,5}, C3={6}. Observaii 1) Un graf este conex dac i numai dac numrul componentelor sale conexe este 1. 2) Mulimile de vrfuri corespunztoare oricror dou componente conexe distincte sunt disjuncte. Rezult c mulimile de vrfuri corespunztoare componentelor conexe ale unui graf formeaz o partiie a mulimii vrfurilor grafului. Problema determinrii componentelor conexe corespunztoare unui graf poate fi rezolvat n modul urmtor. Iniial, este selectat drept vrf curent un vrf al grafului pentru care este calculat componenta conex care l conine. Dac exist vrfuri care nu aparin componentei conexe determinate, este ales drept vrf curent unul dintre aceste vrfuri. n continuare este aplicat aceeai metod, pn cnd au fost gsite toate componentele conexe ale grafului. Determinarea componentei conexe care conine un vrf v0 dat poate fi realizat pe baza urmtorului algoritm. Pentru G=(V,E), V = n , n 1 i v0 V, paii algoritmului sunt: Pas1: V0={v0}; E0= ; i=0;

145

Programarea calculatoarelor

Pas 2: repet Pas 3 pn cnd Vi=Vi-1 i Ei=Ei-1 Pas 3: i=i+1;

E i = E i 1 {e / e E, u Vi 1 , u incident cu e};
Ieirea este Gi=(Vi,Ei), componenta conex din care face parte v0. Exemplu 8.3.5 Pentru graful,

Vi = Vi 1 {v / v V, u Vi 1 , uv E};

Aplicarea algoritmului descris pentru v0=1, determin urmtoarea evoluie: I i=0 i=1 i=2 Vi {1} {1,2,4} {1,2,4,7,8,5} Ei {(1,2),(1,4)} {(1,2),(1,4),(2,7),(2,8),(7,8),(4,5),(4,7),(5,8)}

8.3.4 Drumuri de cost minim


Definiia 8.3.7. Fie G=(V,E,w) un graf ponderat. Costul drumului : u1,u2,..,un, notat L(), este definit prin:

L( ) = w(u i ,u i +1 ) .
Pentru orice u i v vrfuri conectate n G, u v, w-distana ntre u i v, notat D(u,v), este definit prin, D(u , v ) = min{L( ), Duv } , unde Duv desemneaz mulimea tuturor u-v drumurilor elementare din G. Dac Duv este astfel nct D(u,v)=L(), drumul se numete drum de cost minim. Observaie Cu toate c este utilizat termenul de w-distan, n general D nu este o distan n sensul matematic al cuvntului.n particular, dac funcia pondere asociaz valoarea 1 fiecrei muchii a grafului, atunci pentru fiecare pereche de vrfuri distincte ale grafului, costul D(u,v) este lungimea unui cel mai scurt drum ntre cele dou vrfuri. n acest caz D este o distan pe mulimea vrfurilor.
i =1

n 1

146

Grafuri

Algoritmul Dijkstra Urmtorul algoritm a fost propus de ctre E. W. Dijkstra pentru determinarea w-distanelor D(u0,v) i a cte unui u0-v drum de cost minim pentru fiecare vrf vu0 ntr-un graf ponderat, unde u0 este prestabilit. Fie G=(V,E,w) un graf conex ponderat, u0V, SV, u0S. Se noteaz S = V \ S i D u 0 , S = min D(u 0 , x ); x S . Fie v S astfel nct D(u0,v)=D(u0,

} )

S ), : u0, u1,,upv un u0-v drum de cost minim. Evident, 0ip uiS i : u0,
u1,,up un u0- up drum de cost minim. De asemenea,

Dac xS, y S astfel nct D u 0 , S = D(u 0 , x ) + w( xy ) , rezult

D u0 , S = min D(u0 ,u ) + w( uv ); u S , v S ,uv E .

D(u 0 , y ) = D(u 0 , x ) + w( xy ) .

Pentru determinarea a cte unui cel mai ieftin u0-v drum, algoritmul consider o etichetare dinamic a vrfurilor grafului.Eticheta vrfului v este (L(v),u), unde L(v) este lungimea unui cel mai ieftin u0-v drum determinat pn la momentul respectiv i u este predecesorul lui v pe un astfel de drum. Pentru (V,E,w) graf conex ponderat, V = n i u0V, calculul implicat de algoritmul Dijkstra poate fi descris astfel: Pas 1: i=0; S0={u0}; L(u0)=0, L(v)= pentru toi v V, vu0. Dac n=1 atunci stop Pas 2: Pentru toi v Si , dac L(v)>L(ui)+w(uiv), atunci L(v)=L(ui)+w(uiv) i eticheteaz v cu (L(v),ui). Pas 3: Se determin d=min{L(v), v Si } i se alege ui+1 Si astfel nct L(ui+1)=d. Pas 4: Si+1=Si {ui+1} Pas 5: i=i+1. Dac i=n-1, atunci stop. Altfel, reia Pas 2. Observaie Dac (V,E,w) graf ponderat neconex, atunci, pentru u0V, algoritmul lui Dijkstra permite determinarea w-distanelor D(u0,v) i a cte unui u0-v drum de cost minim pentru toate vrfurile v din componenta conex creia i aparine u0. Exemplu 8.3.6 Fie graful ponderat,
1 5 2 9 16 2 5 4 5 1

147

Programarea calculatoarelor

Considernd u0=1, etapele n aplicarea algoritmului Dijkstra sunt: P1: i=0; S0={1}; L(1)=0, L(i)= pentru toi i = 2,5 . P2: S 0 ={2,3,4,5}, u0=1 L(2)= >L(1)+5=5 L(2)=5, eticheteaz 2 cu 1 L(3)= >L(1)+1=1 L(3)=1, eticheteaz 3 cu 1 L(4)= >L(1)+9=9 L(4)=9, eticheteaz 4 cu 1 L(5)= , w(1,5)= , deci L(5) nu se modific P3: selecteaz u1=3, L(3)=1, cea mai mic dintre w-distanele calculate la P2 P4: S1={1,3} P5: i=i+1=1 4, reia P2 P2: S1 ={2,4,5}, u1=3 Nu se modific nicio etichet i nicio w-distan (w(3,i)= , pentru toi i din S1 ) P3: selecteaz u2=2, L(2)=5, cea mai mic dintre w-distanele calculate la P2 P4: S2={1,3,2} P5: i=i+1=2 4, reia P2 P2: S 2 ={4,5}, u2=2 L(4)= 9>L(2)+2=7 L(4)=7, eticheteaz 4 cu 2 L(5)= >L(2)+16=21, eticheteaz 5 cu 2 P3: selecteaz u3=4, L(4)=7, cea mai mic dintre w-distanele calculate la P2 P4: S3={1,3,2,4} P5: i=i+1=3 4, reia P2 P2: S3 ={5}, u3=4 L(5)= 21>L(4)+5=12, eticheteaz 5 cu 4 P3: selecteaz u4=5, L(5)=12, cea mai mic dintre w-distanele calculate la P2 P4: S3={1,3,2,4,5} P5: i=i+1=4, stop. Algoritmul calculeaz urmtoarele rezultate: Vrful v pn la care este 1 calculat w-distana D(1,v), eticheta lui v 0, 1 2 5, 1 3 1, 1 4 7, 2 5 12, 4

Drumurile de cost minim de la vrful 1 la fiecare dintre vrfurile grafului se stabilesc pe baza sistemului de etichete astfel: drumul de la 1 la un vrf v este dat de: v1, eticheta lui v, v2 eticheta lui v1 amd, pn se ajunge la eticheta 1. Astfel, v0 -drumurile de cost minim sunt: pn la 2: 2,1; pn la 3: 3,1; pn la 4: 4,2,1; pn la 5: 5,4,2,1.

148

Grafuri

Urmtoarea surs C implementeaz algoritmul Dijkstra.


#include<stdio.h> #include<conio.h> #include<alloc.h> typedef struct{ int predv; float L; } eticheta; void creaza(int *s,int *sb,int nv,int u0) { s[0]=u0; for(int j=0,i=0;i<nv;i++) if(i-u0)sb[j++]=i; } void modifica(int *s,int *sb,int ui, int *ns, int *nb) { s[*ns]=ui; (*ns)++; for(int i=0;i<*nb;i++) if(sb[i]==ui){ for(int j=i+1;j<*nb;j++) sb[j-1]=sb[j]; (*nb)--; return; } } eticheta *Dijkstra(float w[][50],int nv,int u0) { eticheta *r=(eticheta *)malloc(nv*sizeof(eticheta)); for(int i=0;i<nv;i++)r[i].L=1000; r[u0].L=0; r[u0].predv=u0; int s[50],sb[50],ns=1,nb=nv-1; creaza(s,sb,nv,u0); for(i=0;i<nv-1;i++){ float dmin=1000; for(int j=0;j<nb;j++) for(int k=0;k<ns;k++) if(r[sb[j]].L>r[s[k]].L+w[sb[j]][s[k]]){ r[sb[j]].L=r[s[k]].L+w[sb[j]][s[k]]; r[sb[j]].predv=s[k]; } int ui; for(j=0;j<nb;j++) if(r[sb[j]].L<dmin){ dmin=r[sb[j]].L; ui=sb[j]; } modifica(s,sb,ui,&ns,&nb); }

149

Programarea calculatoarelor
return r; } void main() { int n,i,j; clrscr(); printf("Numarul de varfuri"); scanf("%i",&n); printf("Matricea ponderilor:\n"); float w[50][50]; for(i=0;i<n;i++) for(j=0;j<n;j++) scanf("%f",&w[i][j]); int u0; printf("\nVarful initial:"); scanf("%i",&u0); u0--; eticheta *rez=Dijkstra(w,n,u0); for(i=0;i<n;i++){ printf("Distanta de la vf. %i la vf. %i este %7.2f\n",u0+1,i+1,rez[i].L); printf("Un drum de cost minim este:"); printf("%i, ",i+1); j=rez[i].predv; while(j-u0){ printf("%i, ", j+1); j=rez[j].predv; } printf("%i\n\n",u0+1); } free(rez); getch(); }

n anumite aplicaii este necesar exclusiv determinarea w-distanelor D(v0,v), pentru toi vV. n acest caz algoritmul Roy-Floyd permite o rezolvare a acestei probleme mai simplu de implementat dect algoritmul Dijkstra. Algoritmul Roy-Floyd Pentru (V,E,w) graf ponderat, V = n i W matricea ponderilor, sistemul de w-distane D(v0,v), vV, poate fi calculat pe baza urmtoarei funcii (similar algoritmului Roy-Warshall),
void Roy_Floyd (float w[10][10],unsigned n,float d[10][10],float MAX) {int i,j,k; for (i=0;i<n;i++) for (j=0;j<n;j++) d[i][j]=w[i][j]; for (j=0;j<n;j++) for (i=0;i<n;i++) if(d[i][j]<MAX)

150

Grafuri
for (k=0;k<n;k++) if (d[i][k]>d[i][j]+d[j][k]) d[i][k]=d[i][j]+d[j][k]; }

Matricea D calculat de algoritm este matricea w-distanelor D(u,v) n graful ponderat conex (V,E,w); pentru orice 1 i , j n

D( vi , v j ), vi , v j sunt conectate d ij = , altfel


ntr-adevr, procedura realizeaz calculul dinamic al w-distanei ntre oricare dou vrfuri i i k, astfel: dac exist un drum i-k drum ce trece prin j ( 1 j n ), cu costul corespunztor (dij+djk) inferior costului curent (dik), atunci noul drum de la i la k via j este de cost mai mic dect costul drumului vechi, deci w-distana ntre i i k trebuie reactualizat la dij+djk. Algoritmul Yen Algoritmul propus de Yen pentru calculul tuturor w-distanelor ntr-un graf ponderat este mai eficient din punctul de vedere al volumului de operaii dect algoritmul Roy-Floyd. Fie (V,E,w) un graf ponderat i W matricea ponderilor. Pentru determinarea w-distanelor de la vrful vk fixat la celelalte vrfuri ale grafului, algoritmul Yen iniiaz urmtoarele operaii, Pas 1: D=W Pas 2: i=1; (k)=0, b(k)=0; (j)=0, pentru toi 1 j n , j k Pas 3: Calculeaz min{dkj; 1 j n , (j)=1}; Determin j0 astfel nct (j0)=1 i d kj0 = min{dkj; 1 j n , (j)=1} B(j0)= d kj0 , (j0)=0 d[k,j] =min{d[k,j],d[k,j0]+d[j0,j]}, pentru toi j, 1 j n i=i+1 Pas 4: Dac i<n, reia Pas 3, altfel stop. La terminarea algoritmului componentele vectorului B sunt respectiv egale cu w-distana de la vrful vk la orice alt vrf al grafului. ntr-adevr, componentele egale cu 1 ale vectorului indic, la fiecare reluare a pasului 3, vrfurile grafului pentru care nu s-a calculat nc w-distana la vrful vk. Dup fiecare efectuare a etapei 3, dac j0 a fost selectat, atunci B(j0)=D(vk, v j0 ).

151

Programarea calculatoarelor

Exemplu 8.3.7 Fie graful

4 5 7 1 4 2

3 2 5 3

4
Se consider vk=1.

3 Pas 1: D = 2 7 4

3 2 7 4 5 5 4 1 4 1
3 2 6 5 5 4 4 1 3 1 ; i=2

Pas 2: i=1, =(0,1,1,1,1); B(1)=0

3 Pas 3: j0=3, B(3)=2, =(0,1,0,1,1); D = 2 7 4


Pas 4: i<5, reia Pas 3

Pas 3: j0=2, B(2)=3, =(0,0,0,1,1); nicio modificare n matricea D; i=3 Pas 4: i<5, reia Pas 3 Pas 3: j0=5, B(5)=3, =(0,0,0,1,0); nicio modificare n matricea D; i=4 Pas 4: i<5, reia Pas 3 Pas 3: j0=4, B(4)=6, =(0,0,0,0,0); nicio modificare n matricea D; i=5 Pas 4: i=5, stop.

152

Grafuri

8.4 Circuite i cicluri n grafuri i n digrafuri


Definiia 8.4.1 Fie G=(V,E) un graf netrivial, u, vV i un u-v drum n G. se numete proces dac toate muchiile drumului sunt distincte. Drumul este trivial dac : u,u. Definiia 8.4.2 Drumul este un circuit dac este un proces netrivial nchis. Definiia 8.4.3 Circuitul : v1, v2,., vn, v1 cu n3 este un ciclu al grafului, dac, pentru orice i, j, cu 1 i , j n , i j , rezult vivj. Observaie Orice ciclu este un drum elementar nchis. Definiia 8.4.4 Graful G este aciclic dac nu exist cicluri n G. Observaie ntr-un digraf D noiunile de proces, circuit, ciclu sunt definite ca i n cazul grafurilor. Exemple 8.4.1 n graful,
v1 v4 v2 v3 v5 v6

1: v1, v2, v3, v6, v5 este un proces; 2: v1, v2, v3, v6, v5, v3, v4, v1 este un circuit i nu este ciclu; 3: v1, v3, v5, v4, v1 este un ciclu.
8.4.2 Drumul : v1,v2,v4,v3,v1 este un ciclu, deci graful conine cicluri.

v1

v2

v3

v4

153

Programarea calculatoarelor

8.4.3 Digraful,

V1

V2

V3

V4

nu conine cicluri. Definiia 8.4.5 Fie D=(V,E) un digraf. Funciile grad exterior, odD, respectiv grad interior, idD, sunt definite prin, od D : V N ; id D : V N ,

u V , id D (u ) = {v / v V , vu E}

u V , od D (u ) = {v / v V , uv E} ,

Funcia grad, notat degD, este definit astfel,

deg D : V N, u V, deg D (u ) = id D (u ) + od D (u ) .

Algoritmul Marimont Procedura Marimont verific dac un digraf D=(V,E), V = n , este sau nu aciclic. La terminarea calculului este afiat mesajul DA, dac digraful D este aciclic, respectiv NU, n caz contrar. Descrierea pe pai a algoritmului Marimont este, Pas 1: V0=V, E0=E, D0=(V0,E0) Pas 2: Dac od D 0 (v ) 1 pentru toi vV0, scrie NU, stop (dac toate vrfurile sunt extremiti iniiale ale mcar unui arc, atunci exist cicluri n D0); altfel, continu. Pas 3: Selecteaz vV0 cu od D 0 (v ) = 0 ;V0=V0\{v}; E0=E0-{e/ eE0, e incident cu v n D0}; D0=(V0,E0) Pas 4: Dac V0, atunci reia pasul 2; altfel scrie DA, stop. Exemple 8.4.4 Pentru digraful,

1 e1 4 e4 3 e2 e3 e5 5
evoluia algoritmului Marimont este,

154

Grafuri

Pas 1: V0={1,2,3,4,5}, E0={e1,e2,e3,e4,e5} Pas 2: od D 0 (5) = 0 , continu

1 e1 4 e4 3 e2 e3 2

Pas 3: Selecteaz vrful 5, elimin 5 din V0, elimin arcul e5 E0 Pas 4: reia de la pasul 2 Pas 2: od D 0 (i ) = 1 pentru toi i din V0 ={1,2,3,4}, scrie NU, stop. 8.4.5 Pentru digraful D:

6 e9 7

e7 e8

5 e6

1 e4 e3 3

e5

e1 2 e2

algoritmul Marimont determin urmtoarea secven de operaii: Pas 1: V0={1,2,3,4,5,6,7}, E0={e1,e2,e3,e4,e5,e6,e7,e8,e9} Pas 2: od D 0 (7 ) = 0 , continu Pas 3: Selecteaz vrful 7, elimin 7 din V0, elimin arcele e8 i e9 din E0

D0:

e7

5 e6

1 e4 e3 3

e5

e1 2 e2

Pas 4: reia de la pasul 2 Pas 2: od D 0 (6 ) = 0 , continu

155

Programarea calculatoarelor

Pas 3: Selecteaz vrful 6, elimin 6 din V0, elimin arcul e7 din E0 D0:

5 e6

1 e4

e5

e1 2 e2

e3 3

Pas 4: reia de la pasul 2 Pas 2: od D 0 (5) = 0 , continu Pas 3: Selecteaz vrful 5, elimin 5 din V0, elimin arcul e6 din E0

D0:

1 e4 e3 3

e5

e1 2 e2

Pas 4: reia de la pasul 2 Pas 2: od D 0 (4 ) = 0 , continu Pas 3: Selecteaz vrful 4, elimin 4 din V0, elimin arcele e4, e5 i e3 din E0

D0:

1 e1 2 3 e2

Pas 4: reia de la pasul 2 Pas 2: od D 0 (3) = 0 , continu

D 0:

Pas 3: Selecteaz vrful 3, elimin 3 din V0, elimin arcul e2 din E0

1 e1 2

156

Grafuri

Pas 4: reia de la pasul 2 Pas 2: od D 0 (2 ) = 0 , continu Pas 3: Selecteaz vrful 2, elimin 2 din V0, elimin arcul e1 din E0

D0: 1

Pas 4: reia de la pasul 2 Pas 2: od D 0 (1) = 0 , continu Pas 3: Selecteaz vrful 1, elimin 1 din V0 V0= Pas 4: scrie DA, stop. Algoritmul Marimont poate fi descris n C astfel,
#include<stdio.h> #include<conio.h> typedef struct{ int vi,vf;} arc; int grad_exterior(arc *arce,int na,int v) { int od=0; for(int i=0;i<na;i++) if(arce[i].vi==v) od++; return od; } void elimina_varf(int *varf,int *nv,int v) { int gasit=0; for(int i=0;(i<*nv)&&!gasit;i++) if(varf[i]==v){ gasit=1; for(int j=i+1;j<*nv;j++) varf[j-1]=varf[j]; } (*nv)--; } void elimina_arce(arc *arce,int *na, int v) { for(int i=0;i<*na;) if((arce[i].vi==v)||(arce[i].vf==v)){ for(int j=i+1;j<*na;j++){ arce[j-1].vi=arce[j].vi; arce[j-1].vf=arce[j].vf; } (*na)--; } else i++; }

157

Programarea calculatoarelor
int ciclic(int *varf,arc *arce,int nv, int na) { for(int i=0;i<nv;i++) if(!grad_exterior(arce,na,varf[i])) return 0; return 1; } int Marimont(int *varf,arc *arce,int nv, int na) { while(nv){ printf("\n\nGraful curent\n"); printf("Varfuri:"); for(int i=0;i<nv;i++) printf("%i ",varf[i]); printf("\nArce:"); for(i=0;i<na;i++) printf("(%i,%i) ",arce[i].vi,arce[i].vf); getch(); if(ciclic(varf,arce,nv,na)) return 0; int gasit=0; for(i=0;(i<nv)&&!gasit;i++) if(!grad_exterior(arce,na,varf[i])){ gasit=1; elimina_arce(arce,&na,varf[i]); elimina_varf(varf,&nv,varf[i]); } } return 1; } void main() { int n,nv, na; int vf[20],i,j,a[20][20]; arc arce[100]; clrscr(); printf("Numarul de varfuri"); scanf("%i",&n); for(i=0;i<n;i++) vf[i]=i+1; nv=n;na=0; printf("Matricea de adiacenta:\n"); for(i=0;i<n;i++) for(j=0;j<n;j++){ scanf("%i",&a[i][j]); if(a[i][j]){ arce[na].vi=i+1; arce[na].vf=j+1; na++; } }

158

Grafuri

if(Marimont(vf,arce,nv,na)) printf("\n\nDigraful este aciclic"); else printf("\n\nDigraful este ciclic"); getch(); }

159

9 Structuri arborescente

Una dintre cele mai studiate clase de grafuri sunt cele de tip arbore. n acest capitol sunt prezentate principalele caracteristici ale arborilor, algoritmi pentru calculul arborelui parial de cost minim, arbori direcionai, arbori cu rdcin i arbori binari. Pe lng operaiile primitive asupra arborilor cutarea unei informaii, inserarea unui nod, extragerea unui nod i metode de parcurgere, sunt prezentate dou clase importante de arbori binari: arbori de sortare i arbori de structur.

9.1 Grafuri de tip arbore


9.1.1 Definiii i caracterizri ale grafurilor arbori
Structurile cele mai simple i care apar cel mai frecvent n aplicaii sunt cele arborescente (arbori). Grafurile arbori constituie o subclas a grafurilor conexe. Definiia 9.1.1 Graful G este arbore dac G este aciclic i conex. Definiia 9.1.2. Fie G=(V,E) graf arbore. Subgraful H=(V1,E1) al lui G este subarbore al lui G dac H este graf arbore. Exemple 9.1.1. Graful
2 3

1 4

6 7

160

Structuri arborescente

este arbore, deoarece, orice (i,j) E , ij, exist un i-j drum i graful nu conine cicluri. 9.1.2. Graful

2 5 7

nu este arbore, deoarece drumul :1,4,6,2,1 este un ciclu. 9.1.3. Graful

2 5 6 8 9

nu este arbore, deoarece conine trei componente conexe: {1,2,3,4,6}, {3} i {7,8}. Verificarea proprietii unui graf de a fi arbore poate fi realizat prin intermediul unor algoritmi care s verifice calitile de conexitate i respectiv aciclicitate. De asemenea, verificarea proprietii unui graf de a fi arbore poate fi realizat astfel. Proprietatea 1. Un graf G=(V,E), cu V = n , E = m este graf arbore dac i numai dac G este aciclic i n=m+1. Exemple 9.1.4. Graful din 9.1.1 este arbore, pentru c este aciclic i n=7, m=6. 9.1.5. Graful din 9.1.2. nu este arbore pentru c este ciclic. 9.1.6. Graful din exemplul 9.1.3. nu este arbore deoarece este aciclic, dar n=9, m=6. Proprietatea 2 Un graf G=(V,E), cu V = n , E = m este graf arbore dac i numai dac G este conex i n=m+1. Exemple 9.1.7. Graful din 9.1.1. este arbore deoarece este conex i n=m+1.

161

Programarea calculatoarelor

9.1.8. Graful conex din exemplul 9.1.2. nu este arbore pentru c n=6 i m=8. 9.1.9. Graful din 9.1.3. nu este conex, deci nu este graf arbore. Observaie Fie G=(V,E) un graf. Urmtoarele afirmaii sunt echivalente, 1. G este graf arbore; 2. G este graf conex minimal: oricare ar fi eE, prin eliminarea muchiei e din E, graful rezultat nu este conex; 3. G este graf aciclic maximal: prin adugarea unei noi muchii n graf rezult cel puin un ciclu. Definiia 9.1.3. Se numete graf asimetric un digraf D=(V,E) cu proprietatea c pentru orice u ,v E dac uvE, atunci vu E. Digraful D este simetric dac u , v E , uvE, dac i numai dac vuE. Definiia 9.1.4. Fie D=(V,E) digraf netrivial. Graful G=(V,E), unde E={uv/ uvE sau vuE} se numete graf suport al digrafului D. Definiia 9.1.5. Un arbore direcionat este un graf orientat asimetric i astfel nct graful suport corespunztor lui este graf arbore. Definiia 9.1.6. Arborele direcionat T=(V,E) este arbore cu rdcin dac exist rV astfel nct, pentru orice uV, u r, exist r-u drum n T. Vrful r se numete rdcina arborelui direcionat T. Definiia 9.1.7. Fie T=(V,E) arbore direcionat. Arborele T1=(V1,E1) este subarbore al lui T dac V1V, E1E i T1 este arbore direcionat. Observaie Graful suport al unui arbore direcionat este aciclic, deci, pentru orice uV, u r, r-u drumul din T este unic. De asemenea, un arbore direcionat are cel mult o rdcin. Rezult c, pentru orice uV, u r, distana de la rdcin la vrful u este egal cu numrul de muchii ale r-u drumului n T. Exemple 9.1.10. Arborele direcionat

1 5

3 6

7
este arbore cu rdcin 1.

10

162

Structuri arborescente

9.1.11. Arborele direcionat

1 3 4 5
nu are rdcin. 9.1.12. Arborele

2 7

1 5 6

8
este un subarbore cu rdcin 1 al arborelui din 9.1.10.

10

9.1.2 Reprezentri i parcurgeri ale arborilor orientai


Definiia 9.1.8. Un arbore orientat este un arbore direcionat cu rdcin. Definiia 9.1.9. Fie T=(V,E), un arbore orientat cu rdcin r. Un vrf v V este situat pe nivelul i al arborelui T, dac distana de la vrf la rdcin este egal cu i. Rdcina arborelui este considerat de nivel 0. Deoarece orice arbore orientat este n particular digraf, reprezentarea arborilor orientai poate fi realizat prin utilizarea oricreia dintre modalitile prezentate n 8.1. Datorit caracteristicilor arborilor orientai pot fi ns obinute reprezentri mai eficiente din punct de vedere al spaiului de memorie solicitat. Una dintre modaliti este reprezentarea de tip FIU-FRATE, care const n numerotarea convenional a vrfurilor grafului i memorarea, pentru fiecare vrf i al arborelui, a urmtoarelor informaii, - FIU(i): numrul ataat primului descendent al vrfului i; - FRATE(i): numrul ataat vrfului descendent al tatlui vrfului i i care urmeaz imediat lui i; - INF(i): informaia ataat vrfului i (de obicei valoarea i). Pentru reprezentarea arborelui sunt reinute rdcina i numrul nodurilor. Absena fiului, respectiv a :fratelui unui vrf este marcat printr-o valoare

163

Programarea calculatoarelor

din afara mulimii de numere ataate vrfurilor (de obicei valoarea 0). Exemplu 9.1.13. Arborele orientat

9 10

11

12 13

14

15

16

este reprezentat astfel, N=16, R=1 (rdcina), FIU=(2,5,0,8,0,9,0,14,0,0,0,0,0,0,0,0) FRATE=(0,3,4,0,6,7,0,0,10,11,12,13,0,15,16,0) O alternativ a reprezentrii FIU-FRATE poate fi obinut prin utilizarea structurilor de date dinamice. Presupunnd c fiecare vrf al arborelui are cel mult n descendeni, fiecrui vrf i este ataat structura, identificator vrf vector de legturi ctre descendenii vrfului adres fiu 1 adres fiu n

Urmtoarea surs C implementeaz problema construciei unui arbore orientat, reprezentat prin intermediul unei structuri dinamice arborescente. Numrul maxim de descendeni ai unui nod este 4. n cazul unui numr mai mare de descendeni este preferat n general reprezentarea FIU-FRATE, datorit dimensiunii spaiului de memorie ocupat. Afiarea informaiilor arborelui creat este realizat prin traversarea n A-preordine (a se vedea paragraful urmtor).
#include<stdio.h> #include<conio.h> #include<alloc.h> typedef struct nod{ int inf; struct nod *fiu[4]; } arb, *arbore; void inserare_tata(arbore *ptata,int k,int info) { arbore nou=(arbore)malloc(sizeof(arb)); nou->inf=info;

164

Structuri arborescente
for(int i=0;i<4;i++)nou->fiu[i]=NULL; (*ptata)->fiu[k]=nou; } void inserare(arbore *ppred) { int j,info; arbore *pred; for(int nr=0;(*ppred)->fiu[nr];nr++){ (*pred)=(*ppred)->fiu[nr]; printf("Numarul de fii ai nodului %i:",(*pred)->inf); scanf("%i",&j); for(int k=0;k<j;k++){ scanf("%i",&info); inserare_tata(pred,k,info); } } for(nr=0;(*ppred)->fiu[nr];nr++) inserare(&((*ppred)->fiu[nr])); } void A_preordine(arbore r) { if(r){ printf("%i ",r->inf); for(int i=0;i<4;i++) A_preordine(r->fiu[i]); } } void main(){ clrscr(); int n,j,info; arbore radacina=NULL; printf("Introduceti informatiile pe niveluri\n"); printf("Introduceti radacina\n"); scanf("%i",&info); radacina=(arbore)malloc(sizeof(arb)); radacina->inf=info; for(int i=0;i<4;i++)radacina->fiu[i]=NULL; printf("Numarul de fii ai nodului %i",radacina->inf); scanf("%i",&j); for(int k=0;k<j;k++){ scanf("%i",&info); inserare_tata(&radacina,k,info); } arbore ppred=radacina; inserare(&ppred); printf("Parcurgerea A-preordine a arborelui : \n"); A_preordine(radacina); getch();}

Parcurgerea unui arbore orientat revine la aplicarea sistematic a unei reguli de vizitare a vrfurilor arborelui. Cele mai utilizate reguli de parcurgere a arborilor orientai sunt A-preordine, A-postordine i parcurgerea pe niveluri.

165

Programarea calculatoarelor

Parcurgerea n A-preordine Modalitatea de vizitare a vrfurilor n parcurgerea n A-preordine poate fi descris astfel. Iniial, rdcina arborelui este selectat drept vrf curent. Este vizitat vrful curent i sunt identificai descendenii lui. Se aplic aceeai regul de vizitare pentru arborii avnd ca rdcini descendenii vrfului curent, arborii fiind vizitai n ordinea precizat prin numerele ataate vrfurilor rdcin corespunztoare. Exemplu 9.1.14. Pentru arborele orientat din exemplul 9.1.13., prin aplicarea parcurgerii n A-preordine, rezult: 1,2,5,6,9,10,11,12,13,7,3,4,8,14,15,16. n reprezentarea FIU-FRATE, implementarea parcurgerii n A-preordine este realizat prin urmtoarea funcie recursiv, cu parametru de intrare rdcina arborelui curent.
void A_preordine (nod R) { if (R){ vizit (R); A_preordine(FIU[R]); A_preordine(FRATE[R]); } }

n sursa prezentat n paragraful precedent, funcia A-preordine implementeaz acest tip de traversare n cazul arborilor orientai reprezentai prin intermediul structurilor arborescente. Parcurgerea A-postordine Regula de parcurgerea n A-postordine este asemntoare traversrii Apreordine, singura diferen fiind aceea c, n acest tip de traversare, rdcina fiecrui arbore este vizitat dup ce au fost vizitate toate celelalte vrfuri ale arborelui. Exemplu 9.1.15. Pentru arborele orientat din exemplul 9.1.13. ordinea de vizitare a vrfurilor este: 5,9,10,11,12,13,6,7,2,3,14,15,16,8,4,1. Pentru arbori reprezentai prin structuri dinamice de date, implementarea parcurgerii n A-postordine poate fi obinut pe baza urmtoarei funcii recursive. Parametrul de intrare al funciei A_postordine reprezint rdcina arborelui curent n momentul apelului.
void A_postordine (nod R) { if (R) { for(i=0;i<n;i++) A_postordine(R->leg[i]); vizit (R); } }

166

Structuri arborescente

Observaie Parcurgerile n A-preordine i A-postordine sunt variante de parcurgeri n adncime (variante ale metodei DF). Ambele metode consider prioritare vrfurile aflate la distan maxim fa de rdcina arborelui iniial. Parcurgerea pe niveluri Parcurgerea unui arbore orientat pe niveluri const n vizitarea vrfurilor sale n ordinea cresctoare a distanelor fa de rdcin. Exemplu 9.1.16. Pentru arborele definit n exemplul 9.1.13., prin aplicarea parcurgerii pe niveluri, rezult urmtoarea ordine de vizitare a nodurilor, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16. Ca i n cazul metodei BF, implementarea parcurgerii pe niveluri este bazat pe utilizarea unei structuri de coad C. La momentul iniial rdcina arborelui este inserat n C. Atta timp ct timp coada este nevid, este preluat cu tergere un vrf din C, este vizitat i sunt introdui n coad descendenii si. Calculul este ncheiat cnd C=. n cazul reprezentrii FIU-FRATE a arborelui de traversat, parcurgerea pe niveluri poate fi implementat prin urmtoarea funcie.
void parcurgere_pe_niveluri(nod R,int FIU[],int FRATE[],int n) { ptcoada C=NULL;push(C,R); while (C) { pop(C,v); VIZIT(v); v=FIU[v]; while (v){ push(C,v); v=FRATE[v]; } } }

Observaie Funciile push i pop implementeaz inserarea unuei celule n coad, respectiv extragerea unui element al cozii. Exemplu 9.1.17. Pentru arborele de la exemplul 9.1.13., evoluia algoritmului este,
C t t=1 t=2 t=3 t=4 t=5 1 2 3 4 5 8 3 4 5 6 4 5 6 7 6 7 8 7

167

Programarea calculatoarelor
C t t=6 t=7 t=8 t=9 t=10 t=11 t=12 t=13 t=14 t=15 t=16 t=17 6 7 8 9 10 11 12 13 14 15 15 7 8 9 10 11 12 13 14 15 16 8 9 10 11 12 13 14 15 16 10 11 12 13 14 15 16 11 12 13 14 15 16 12 13 14 15 16 13 15 16 16

deci vrfurile sunt vizitate n ordinea: 1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16. Observaie Metoda BF pentru parcurgerea grafurilor este o generalizare a tehnicii de parcurgere pe niveluri a arborilor orientai. O alternativ de implementare a parcurgerii pe niveluri poate fi descris prin intermediul funciilor recursive frati i parc. Coada C este o variabil global i este iniializat cu rdcina arborelui. Parcurgerea este realizat prin apelul parc(C).
void frati(v) {if (v){push(C,v); frai(FRATE[v]); } } void parc() {if (C){pop(C,v);VIZIT(v); frati(FIU[v]); parc(); } }

9.1.3 Arbori pariali. Algoritmul Kruskal


Definiia 9.1.10. Fie G un graf. Subgraful parial H este un arbore parial al lui G dac H este graf arbore. Definiia 9.1.11. Fie G=(V,E,w) un graf ponderat conex.Dac T=(V,E0) este un arbore parial al grafului G=(V,E), ponderea arborelui T, notat W(T), este definit prin W(T)= w( e ) .
eE0

168

Structuri arborescente

Exemplu 9.1.18. Pentru graful ponderat

1 4 2 2 6 9 2 8 3 5

1 3

12

4
T este un arbore parial de pondere 32.

1 4 2 8 4 9 3 2 6 3 5

Definiia 9.1.12. Arborele parial T0T(G) este arbore parial minim pentru G dac W(T0)=min{W(T); TT(G)}, unde T(G) este mulimea arborilor pariali corespunztori grafului G. Observaie Dac G este graf finit, atunci T(G) este o mulime finit, deci orice graf finit ponderat i conex are cel puin un arbore parial minim. n continuare este prezentat algoritmul Kruskal pentru determinarea unui arbore parial minim al unui graf ponderat conex G=(V,E,w). Pas 1: i=1; E0= Pas 2: Determin R={e/eE \ Ei-1 astfel nct graful (V,Ei-1 {e}) este aciclic} Dac R=, atunci stop; altfel, selecteaz eiR cu w(ei)=min{w(e), eR}; Ei=Ei-1 {ei} Pas 3: i=i+1 i reia pasul 2.

169

Programarea calculatoarelor

Arborele parial de cost minim al grafului G este (V,Ei-1). Pentru implementarea algoritmului Kruskal, graful conex ponderat este reprezentat sub form tabelar, muchiile fiind ordonate cresctor dup ponderi. Muchiile selectate de algoritm pot fi meninute, de asemenea, ntr-o structur tabelar, sau doar marcate ca fiind incluse n mulimea de muchii din arborele parial minim a crui construcie este dorit. n varianta prezentat n continuare muchiile selectate sunt afiate. Verificarea condiiei ca muchia selectat s nu formeze niciun ciclu cu muchiile selectate la etapele precedente este realizat prin utilizarea un vector TATA, definit astfel. Pentru fiecare vrf i (vrfurile grafului fiind numerotate de la 1 la n, unde n este numrul de noduri ale grafului), componenta TATA [i] este predecesorul su n arborele care conine vrful i construit pn la momentul curent dac i nu este rdcina acelui arbore, respectiv TATA[i] este egal cu numrul de vrfuri ale arborelui de rdcin i, n caz contrar. Componentele vectorului TATA sunt iniializate cu valoarea -1. Calculul care realizeaz adugarea unei noi muchii poate fi descris astfel. Este determinat o muchie de cost minim e=v1v2 care nu a fost selectat anterior. Dac vrfurile v1 i v2 nu aparin aceluiai arbore, atunci proprietatea de aciclicitate este ndeplinit i muchia e este adugat la structura curent. Adugarea muchiei e selectate este realizat prin reunirea arborilor din care fac parte v1 i v2 de rdcini r1, respectiv r2, astfel: dac TATA[r1]<TATA[r2], atunci arborele rezultat prin reunirea celor doi arbori are ca rdcin vrful r1, iar vrful r2 devine fiu al lui r1. Altfel, rdcina arborelui rezultat prin reunire fiind r2, iar r1 devenind fiu al rdcinii. Calculul se ncheie dup ce a fost adugat i cea de-a (n-1)-a muchie. Algoritmul Kruskall poate fi implementat prin urmtoarea surs C:
#include<stdio.h> #include<conio.h> int radacina(int v,int *tata) { int u=v; while(tata[u]>=0) u=tata[u]; return u; } int kruskal(int a[][3],int nm, int nv) { int tata[50],i,j; int c=0; for(i=0;i<nv;i++)tata[i]=-1; for(j=i=0;i<nv-1;j++){ int v1=a[j][0]; int v2=a[j][1]; int k=radacina(v2,tata);int p=radacina(v1,tata); if(k-p){ if(tata[k]<tata[p]){ tata[k]+=tata[p];tata[p]=k; } else{ tata[p]+=tata[k];tata[k]=p; }

170

Structuri arborescente
c+=a[j][2];printf("%i -> %i cost %i\n",v1+1,v2+1,a[j][2]); i++; } } return c; } void main() { clrscr(); int nv,nm, a[100][3]; printf("Numarul de varfuri:");scanf("%i",&nv); printf("Numarul de muchii");scanf("%i",&nm); printf("Matricea de reprezentare\n"); for(int i=0;i<nm;i++) for(int j=0;j<3;j++) scanf("%i",&a[i][j]); for(i=0;i<nm;i++) for(int j=0;j<2;j++)a[i][j]--; printf("Arborele de cost minim: \n"); int cost=kruskal(a,nm,nv); printf("\ncu costul%i",cost); getch(); }

Exemplu 9.1.19. Evoluia determinat de program pentru graful


1 4 2 8 4 6 9 2 8 3
2 2 1 1 5 A = 3 1 4 5 3 3 1 4 2 6 2 5 3 4 4 2 4 6 8 6 8 6 9

2 4

este:
i, j dup cea de-a t-a iteraie muchia selectat TATA t=0 (-1,-1,-1,-1,-1,-1) t=1,i=0,j=0 (2,3) (-1,-2,2,-1,-1,-1) t=2,i=1,j=1 (2,4) (-1,-3,2,2,-1,-1) t=3,i=2,j=2 (1,6) (-2,-3,2,2,-1,1) t=4,i=3,j=3 (1,5) (-3,-3,2,2,1,1) t=5,i=4,j=4 (-3,-3,2,2,1,1) t=6,i=4,j=5 (1,2) (-5,1,1,2,1,1) MUCHIILE ARBORELUI MINIM: {(2,3),(2,4),(1,6),(1,5),(1,2)} Costul 1 2 2 3 4 COSTUL: 12

171

Programarea calculatoarelor

9.2 Arbori binari


9.2.1 Reprezentarea arborilor binari. Modaliti de parcurgere
Definiia 9.2.1 Un arbore binar este un arbore orientat cu proprietatea c pentru orice vrf v, od(v)2. Dac od(v)=2, cei doi descendeni sunt desemnai ca descendent stng (fiu stnga) respectiv descendent drept (fiu dreapta). Pentru vrfurile cu od(v)=1, unicul descendent este specificat fie ca fiu stnga, fie ca fiu dreapta. Definiia 9.2.2 Se numete nod terminal orice vrf v al arborelui cu od(v)=0. n caz contrar nodul v este neterminal. Reprezentarea unui arbore binar este realizat printr-o structur arborescent. Pentru fiecare nod N al arborelui binar sunt memorate informaia asociat lui N i legturile ctre descendenii lui. Absena unui descendent este reprezentat prin NULL. identificator legtur fiu legtur fiu nod stng drept Definiia 9.2.3 Fie T=(V,E) un arbore binar cu rdcina R. Subarborele stng al lui T este ST=(V\{R},E\{RS}), unde S este fiul stnga al rdcinii. Subarborele drept al lui T este DT=(V\{R},E\{RD}), unde D este fiul dreapta al rdcinii. Exemplu 9.2.1 Pentru arborele binar, 1

4 8 subarborii rdcinii

5 9

6 10

3 6 7

4 8
sunt:

5 9

10 Subarbore drept

Subarbore stng

172

Structuri arborescente

n plus fa de metodele A-preordine, A-postordine i pe niveluri, parcurgerile n preordine (RSD), inordine (SRD) i respectiv postordine (SDR) sunt special considerate pentru arbori binari i au multiple aplicaii. Regula de vizitare pentru aceste tipuri de parcurgere revine la parcurgerea subarborelui stng i parcurgerea subarborelui drept corespunztori vrfului curent. La momentul iniial vrful curent este rdcina arborelui. Diferena dintre cele trei tipuri de parcurgere este dat de momentul n care devine vizitat fiecare vrf al arborelui. n parcurgerea RSD (rdcin-subarbore stng-subarbore drept), fiecare vrf al arborelui este vizitat n momentul n care este vrf curent; n parcurgerea SRD, vizitarea vrfului curent R este efectuat dup ce a fost parcurs subarborele stng al lui R, respectiv n parcurgerea SDR vizitarea fiecrui vrf este efectuat dup ce au fost parcuri subarborii afereni lui. Exemplu 9.2.2 Pentru arborele de la exemplul 9.2.1., secvenele de vrfuri rezultate prin aplicarea parcurgerilor RSD, SRD, SDR sunt: - preordine: 1,2,4,8,5,3,6,9,10,7 - inordine: 4,8,2,5,1,9,6,10,3,7 - postordine: 8,4,5,2,9,10,6,7,3,1.

9.2.2 Arbori de sortare


Definiia 9.2.4 Un arbore de sortare este un arbore binar cu urmtoarele proprieti, - fiecrui nod i al arborelui i este ataat o informaie INF(i) dintr-o mulime ordonat de valori; - pentru fiecare nod i, INF(i) este mai mare dect INF(j), pentru toate nodurile j din subarborele stng al arborelui cu rdcin i; - pentru fiecare nod i, INF(i) este mai mic dect INF(j), pentru toate nodurile j din subarborele drept al arborelui cu rdcin i; - pentru orice vrfuri i i j daca ij atunci INF(i)INF(j). Exemplu 9.2.3 Arborele binar
50

30 10 20 40

70 90 80

este arbore de sortare.

173

Programarea calculatoarelor

Operaiile primitive asupra arborilor de sortare sunt inserarea unui nod, tergerea unui nod i parcurgerea arborelui (n preordine, inordine sau postordine). Inserarea i tergerea de noduri aplicate unui arbore de sortare trebuie realizate astfel nct arborele rezultat s fie de asemenea arbore de sortare. Observaie Parcurgerea n inordine a unui arbore de sortare determin obinerea secvenei informaiilor asociate vrfurilor arborelui n ordine cresctoare. Inserarea unui nod ntr-un arbore de sortare Algoritmul de inserare a unei informaii nr n arborele de sortare de rdcin rad este recursiv i const n efectuarea urmtoarelor operaii: vrful curent v la momentul iniial este rdcina arborelui; dac arborele de rdcin v este vid, este generat arborele cu un singur nod, cu informaia ataat nr; altfel: - dac informaia ataat nodului v este mai mare dect nr, atunci vrf curent devine fiul stnga al lui v; - dac informaia ataat nodului v este egal cu nr, atunci stop; - dac informaia ataat nodului v este mai mic dect nr, atunci vrf curent devine fiul dreapta al lui v. Exemplu 9.2.4 Aplicarea algoritmul descris pentru inserarea informaiei 55 n arborele de sortare din exemplul 9.2.3 determin urmtoarele operaii, INF(v)=50; 50<55, insereaz n subarborele cu rdcina avnd informaia ataat 70. INF(v)=70; 70>55, insereaz n subarborele stng cu rdcina NULL. Este creat nodul cu informaie 55, fiu stng al nodului de informaie 70. Arborele rezultat este

50

30 10 20 40 55

70 90 80

tergerea unei informaii dintr-un arbore de sortare Algoritmul pentru tergerea unei informaii nr din arborele de sortare de rdcin rad este recursiv i poate fi descris astfel. Vrful curent v la momentul iniial este rdcina arborelui.

174

Structuri arborescente

1. dac arborele este vid atunci stop; 2. altfel a) dac informaia ataat nodului v este mai mare dect nr, atunci vrful curent devine fiul stnga al lui v; b) dac informaia ataat nodului v este mai mic dect nr, vrful curent devine fiul dreapta al lui v; c) dac INF(v)=nr atunci: c1) dac subarborele stng este vid, atunci adresa vrfului v este memorat ntr-o celul suplimentar aux, v devine fiul dreapta al lui v, iar celula aux este eliberat din memorie; c2) dac subarborele stng este nevid atunci se determin cel mai mare element din subarborele stng; c2.1) dac fiul stnga al lui v nu are subarbore drept, atunci informaia ataat fiului stnga se transfer n vrful curent, iar fiul stnga este nlocuit cu fiul su stnga i este eliberat memoria corespunztoare celulei v->fius; c2.2) altfel, se transfer n rdcin informaia ataat ultimului nod p determinat la c2), nodul p este nlocuit cu fiul su stng i celula corespunztoare lui p este eliberat din memorie. Exemplu 9.2.5 tergerea informaiei 70 din arborele de sortare din exemplul 9.2.4. este realizat astfel: 70>50, decide tergerea din subarborele drept 70=70, decide tergerea din arborele curent: rdcina etichetat cu 70; exist subarbore stng iar acesta nu are subarbore drept- nodul cu informaie 70 este etichetat cu 55, iar p este nlocuit cu subarborele su stng (vid). Arborele rezultat

50

30 10 20
este arbore de sortare.

55 40 80 90

175

Programarea calculatoarelor

Observaie Punctul c) de la pasul 2 al algoritmului de eliminare a unei informaii dintr-un arbore de sortare poate fi nlocuit cu: c) dac INF(v)=nr atunci: c1) dac subarborele drept este vid, atunci adresa vrfului v este memorat ntr-o celul suplimentar aux, v devine fiul stnga al lui v, iar celula aux este eliberat din memorie; c2) dac subarborele drept este nevid atunci se determin cel mai mic element din subarborele drept, altfel: c2.1.) dac fiul dreapta al lui v nu are subarbore stng, atunci informaia ataat fiului dreapta este transferat n vrful curent, iar fiul dreapta este nlocuit cu fiul su dreapta i este eliberat memoria corespunztoare celulei v->fiud. c2.2) altfel, se transfer n rdcin informaia ataat ultimului nod p determinat la c2), nodul p este nlocuit cu fiul su dreapta i celula corespunztoare lui p este eliberat din memorie. n urmtoarea surs C sunt implementai algoritmii de adugare i tergere n arbori de sortare.
#include<stdio.h> #include<conio.h> #include<alloc.h> typedef struct nod{ int inf; struct nod *l,*r; } arb, *arbore; void inserare(arbore *radacina,int info) { if(*radacina==NULL){ arbore nou; nou=(arbore)malloc(sizeof(arb)); nou->inf=info; nou->l=nou->r=NULL; *radacina=nou; } else if((*radacina)->inf>info) inserare(&((*radacina)->l),info); else if((*radacina)->inf<info) inserare(&((*radacina)->r),info); } int extragere(arbore *radacina,int info) { if(*radacina==NULL) return 0; else if((*radacina)->inf>info) return extragere(&((*radacina)->l),info); else if((*radacina)->inf<info) return extragere(&((*radacina)->r),info);

176

Structuri arborescente
else{ if((*radacina)->l==NULL){ arbore aux=*radacina; *radacina=(*radacina)->r; free(aux); } else{ arbore p,p1; for(p=(*radacina)->l;p->r;p1=p,p=p->r); if(((*radacina)->l)->r==NULL){ (*radacina)->inf=p->inf; (*radacina)->l=p->l; free(p); } else{ (*radacina)->inf=p->inf; arbore aux=p; p1->r=p->l; free(aux); } } return 1; } } void srd(arbore radacina) { if(radacina){ srd(radacina->l); printf("%i ",radacina->inf); srd(radacina->r); } } void main() { clrscr(); int n,info; arbore radacina=NULL; printf("Numarul de noduri:"); scanf("%i",&n); printf("Introduceti informatiile\n"); for(int i=0;i<n;i++){ scanf("%i",&info); inserare(&radacina,info); } printf("Parcurgerea SRD a arborelui de sortare: \n"); srd(radacina); printf("\nInformatia nodului de extras:"); scanf("%i",&info); if(extragere(&radacina,info)){ printf("\nArborele rezultat in parcurgere SRD\n"); srd(radacina); } else printf("\nInformatia nu este in arbore"); getch(); }

177

Programarea calculatoarelor

9.2.3 Arbori de structur


Expresiile aritmetice n care intervin numai operatori binari pot fi reprezentate prin intermediul arborilor binari n care fiecare nod neterminal are doi fii. Definiia 9.2.5 Un arbore de structur are vrfurile etichetate astfel: - fiecare nod neterminal este etichetat cu un simbol corespunztor unuia dintre operatori; - fiecare nod terminal este etichetat cu un operand. Construcia arborelui de structur corespunztor unei expresii aritmetice date se realizeaz pe baza parantezrii existente n expresie i a prioritilor convenional asociate operatorilor (ordinea operaiilor) astfel nct rdcina fiecrui subarbore este etichetat cu operatorul care se execut ultimul n evaluarea subexpresiei corespunztoare acelui subarbore. Exemplu 9.2.6 Pentru expresia matematic (a+b)*(c-d)+e/g, arborele de structur corespunztor este +

* + a
etape:

/ e d g

Construcia arborelui de structur pentru o expresie s este realizat n dou 1. ataarea de prioriti operatorilor i operanzilor; prioritile ataate permit eliminarea parantezelor fr ca semnificaia expresiei s se modifice; 2. construcia propriu-zis. Prima etap este realizat astfel: - prioritatea iniial a operatorilor +,- este 1 (dac expresia nu conine paranteze atunci n construcie aceti operatori vor fi primii luai n considerare n ordinea de la dreapta la stnga); - prioritatea iniial a operatorilor /,* este 10 (dac expresia nu conine paranteze, acetia sunt considerai dup operatorii de prioritate 1 n ordinea de la dreapta la stnga); - prioritatea fiecrui operator este incrementat cu valoarea 10 pentru fiecare pereche de paranteze n interiorul crora se afl;

178

Structuri arborescente

- prioritatea ataat fiecrui operand este MAXINT. Dup stabilirea sistemului de prioriti sunt eliminate parantezele din expresie, ordinea de efectuare a operaiilor n cadrul expresiei fiind indicat de vectorul de prioriti ataat. Construcia arborelui de structur pe baza expresiei s din care au fost eliminate parantezele i a vectorului de prioriti, poate fi realizat recursiv n modul urmtor (la momentul iniial expresia curent este expresia dat): - pentru expresia curent se determin operatorul/operandul de prioritate minim care se ataeaz ca etichet a rdcinii r a subarborelui de structur corespunztor ei; fie i poziia acestuia n cadrul expresiei; - dac expresia are un singur simbol, atunci r->fius=r->fiud=NULL; - altfel, se consider subexpresiile s1 i s2, constnd din simbolurile de pe poziiile 0 pn la i-1 i respectiv i+1 pn la lungimea irului s.; arborii de structur corespunztori subexpresiilor s1 i s2 se ataeaz ca subarbore stng, respectiv subarbore drept vrfului r. Exemplu 9.2.7 Etapele calculului sistemului de prioriti i al arborelui de structur pentru expresia de la exemplul 9.2.6 pot fi descrise astfel,
Dim 1 2 3 3 4 4 5 6 7 7 8 9 10 11 vectorul prioritate (MAXINT) (MAXINT,11) (MAXINT,11,MAXINT) (MAXINT,11,MAXINT) (MAXINT,11,MAXINT,10) (MAXINT,11,MAXINT,10) (MAXINT,11,MAXINT,10,MAXINT) (MAXINT,11,MAXINT,10,MAXINT,11) (MAXINT,11,MAXINT,10,MAXINT,11,MAXINT) (MAXINT,11,MAXINT,10,MAXINT,11,MAXINT) (MAXINT,11,MAXINT,10,MAXINT,11,MAXINT,1) (MAXINT,11,MAXINT,10,MAXINT,11,MAXINT,1,MAXINT) (MAXINT,11,MAXINT,10,MAXINT,11,MAXINT,1,MAXINT,10) (MAXINT,11,MAXINT,10,MAXINT,11,MAXINT,1,MAXINT,10,MAXINT)

Dup eliminarea parantezelor, expresia rezultat este s=a+b*c-d+e/g. Arborele de structur este construit astfel:
+

+
* n construcie n construcie

n construcie

n construcie

n construcie

179

Programarea calculatoarelor

* + n construcie

n construcie

* + a

n construcie

n construcie n construcie +

n construcie

n construcie
+

* + a b

n construcie + a + b

* -

n construcie

n construcie

n construcie +

n construcie

* + a b c -

n construcie + n construcie a b

* c

n construcie

180

Structuri arborescente

* + a b c -

/ n construcie d + n construcie

* + a b c d
+

/ e n construcie

* + a b c d e

/ g

Observaie Construcia arborelui de structur poate fi realizat n ipoteza n care expresia este corect. Definiia 9.2.6 Se numete forma polonez direct a unei expresii, expresia rezultat n urma parcurgerii RSD a arborelui de structur. Se numete forma polonez invers a unei expresii, expresia rezultat n urma parcurgerii SDR a arborelui de structur.

181

Programarea calculatoarelor

Exemplu 9.2.8 Pentru expresia considerat la exemplul 9.2.7, forma polonez direct este +*+ab-cd/eg. Forma polonez invers a expresiei date este ab+cd-*eg/+. Observaie Parcurgerea arborelui n inordine determin secvena de simboluri rezultat prin eliminarea parantezelor din expresia dat. Restaurarea unei forme parantezate poate fi realizat printr-o parcurgere SRD i anume n modul urmtor. La momentul iniial vrful curent este rdcina arborelui de structur. Dac vrful curent v nu este vrf terminal, atunci se genereaz (s1) eticheta(v)(s2), unde eticheta(v) este operatorul etichet a vrfului, s1 este secvena rezultat prin traversarea SRD a subarborelui stng, s2 este secvena rezultat prin traversarea SRD a subarborelui drept. Dac v este vrf terminal atunci este generat secvena eticheta(v). Evaluarea expresiilor aritmetice pe baza arborilor de structur Traversarea SRD a arborelui de structur ataat unei expresii aritmetice permite evaluarea expresiei pentru valorile curente corespunztoare variabilelor. Evaluarea poate fi efectuat n mod recursiv astfel. La momentul iniial vrful curent este rdcina arborelui. Dac v este vrf curent atunci noua informaie asociat lui v este: - val(eticheta(v)), dac v este vrf terminal; - val(s1)eticheta(v)val(s2), dac v este neterminal, unde val(s1), val(s2) sunt valorile rezultate prin evalurile subarborilor stng i respectiv drept ai lui v, val(eticheta(v)) este valoarea curent a variabilei, dac eticheta lui v este variabil, respectiv valoarea constantei, dac eticheta lui v este o constant. Exemplu 9.2.9 Prin aplicarea metodei de evaluare descrise pentru a=3, b=2, c=5, d=2, e=6 i g=2, obinem:

18

15 5 3 2 5 3 2 6

3 2

182

Structuri arborescente

Construcia arborelui de structur asociat unei expresii i evaluarea expresiei pentru valori date ale operanzilor pot fi implementate prin intermediul urmtoarei surse C.
#include<stdio.h> #include<conio.h> #include<alloc.h> #include<values.h> #include<string.h> #include<math.h> typedef struct nod{ char inf; float v; struct nod *l,*r; } arb, *arbore; void prioritati(char *s, int *prioritate) { int i,j,dim; //stabilirea prioritatilor for(i=j=dim=0;i<strlen(s);i++) switch(s[i]){ case ')':j-=10;break; case '(':j+=10;break; case '+':{prioritate[dim]=j+1;dim++;break;} case '-':{prioritate[dim]=j+1;dim++;break;} case '*':{prioritate[dim]=j+10;dim++;break;} case '/':{prioritate[dim]=j+10;dim++;break;} default:{prioritate[dim]=MAXINT;dim++;break;} } //eliminarea parantezelor for(i=0;i<strlen(s);) if((s[i]==')')||(s[i]=='(')){ for(j=i+1;j<strlen(s);j++)s[j-1]=s[j]; s[strlen(s)-1]='\0';} else i++; } void cr_arb_str(arbore *rad, unsigned p, unsigned u, char *s,int *pri) { int min=pri[p]; int poz=p; for(int i=p+1;i<=u;i++) if(min>pri[i]){min=pri[i];poz=i;} (*rad)=(arbore)malloc(sizeof(arb)); (*rad)->inf=s[poz]; if(p==u) (*rad)->l=(*rad)->r=NULL; else{ cr_arb_str(&((*rad)->l),p,poz-1,s,pri); cr_arb_str(&((*rad)->r),poz+1,u,s,pri); } }

183

Programarea calculatoarelor
void forma_poloneza(arbore rad) { if(rad){ printf("%c",rad->inf); forma_poloneza(rad->l); forma_poloneza(rad->r); } } float eval(arbore rad) { char s[1]; if(rad){ if((rad->r==rad->l)&&(rad->l==NULL))return rad->v; else{ switch (rad->inf){ case '+':rad->v=eval(rad->l)+eval(rad->r);break; case '-':rad->v=eval(rad->l)-eval(rad->r);break; case '*':rad->v=eval(rad->l)*eval(rad->r);break; case '/':rad->v=eval(rad->l)/eval(rad->r);break; } return rad->v; } } } void atribuie_arbore(arbore rad) { if(rad){ if((rad->r==rad->l)&&(rad->l==NULL)){ printf("%c =",rad->inf); float t; scanf("%f",&t); rad->v=t; } else {atribuie_arbore(rad->l); atribuie_arbore(rad->r); } } } void main() { clrscr(); char s[100]; int p[100]; arbore radacina=NULL; printf("Expresia:"); scanf("%s",&s); prioritati(s,p);

184

Structuri arborescente
int n=strlen(s); cr_arb_str(&radacina,0,n-1,s,p); printf("\nForma poloneza inversa "); forma_poloneza(radacina); printf("\n Valori pentru varabile\n"); atribuie_arbore(radacina); printf("\nEvaluarea: %7.3f",eval(radacina)); getch(); }

185

10 Elemente de programare orientat obiect

Programarea orientat obiect (Object Oriented Programming - OOP) reprezint o tehnic ce s-a impus n anii 90, dovedindu-se benefic pentru realizarea sistemelor software de mare complexitate. Noiunea de obiect dateaz din anii 60, odat cu apariia limbajului Simula. Exist limbaje ca Smalltalk i Eiffel care corespund natural cerinelor programrii orientate obiect, fiind concepute n acest spirit. Recent au fost dezvoltate i alte limbaje orientate obiect, fie pentru programare general, fie pentru realizarea de scripturi Java, Delphi, C++, Visual Basic .NET, C#, Python, Ruby. Unele dintre ele ofer n continuare i posibilitatea programri procedurale (Delphi, C++). Toate limbajele folosite n prezent ofer i faciliti de programare orientat obiect ADA, Fortran, Cobol, PHP etc. n prezent, exist n funciune sisteme software de mare anvergur realizate n tehnica programrii orientat obiect, principiile ei fiind suficient de bine clarificate, astfel nct s se treac din domeniul cercetrii n cel al produciei curente de programe. Acest capitol prezint o introducere n lucrul orientat obiect n limbajul C++, fr a acoperi toat problematica specific.

10.1 Modelul de date orientat obiect


OOP reprezint o abordare cu totul diferit fa de programarea procedural, devenit deja clasic. Dac n programarea clasic programatorul era preocupat s rspund la ntrebarea ce trebuie fcut cu datele?, adic s defineasc proceduri care s transforme datele n rezultate, n OOP accentul cade asupra datelor i legturilor ntre acestea, ca elemente prin care se modeleaz obiectele lumii reale. Se poate afirma, ntr-o prim analiz, c OOP organizeaz un

186

Elemente de programare orientat obiect

program ca o colecie de obiecte, modelate prin date i legturi specifice, care interacioneaz dinamic, adic manifest un anumit comportament, producnd rezultatul scontat. n general, pentru modelul de date orientat pe obiect, se consider definitorii urmtoarele concepte: abstractizare, obiect, atribut, metod, clas, spaiu propriu, spaiu extins, ncapsulare, motenire i polimorfism. Abstractizarea constituie procesul de simplificare a realitii prin reinerea caracteristicilor i comportamentelor eseniale i constituirea lor ntr-un model adecvat rezolvrii problemelor. Obiectul este un model informaional al unei entiti reale, care posed, la un anumit nivel, o mulime de proprieti i care are, n timp, un anumit comportament, adic manifest reacii specifice n relaiile cu alte entiti din mediul su de existen. Ca model, un obiect este o unitate individualizabil prin nume, care conine o mulime de date i funcii. Datele descriu proprietile i nivelul acestora, iar funciile definesc comportamentul. Avnd n vedere proprietile comune i comportamentul similar al entitilor pe care le modeleaz, obiectele pot fi clasificate n mulimi. O mulime de obiecte de acelai fel constituie o clas de obiecte, descris prin modelul comun al obiectelor sale. De exemplu, n figura 10.1, numerele complexe, ca perechi de numere reale de forma (parte real, parte imaginar) pot fi descrise printr-un model comun, denumit ClasaComplex. Modelul arat c orice obiect de acest fel se caracterizeaz printr-o pereche de numere ntregi i c pe aceast mulime sunt definite operaii unare i binare care arat cum interacioneaz obiectele n interiorul mulimii: un numr complex poate da natere modulului i opusului su, dou numere complexe pot produce un alt numr complex ca sum, produs etc. Generaliznd, se poate afirma c o clas de obiecte se manifest ca un tip obiect, iar modelul comun al obiectelor este modelul de definire a tipului obiect. Astfel, obiectele individuale apar ca manifestri, realizri sau instanieri ale clasei, adic exemplare particulare generate dup modelul dat de tipul obiect. Altfel spus, o clas poate fi considerat ca un tip special de dat, iar obiectele sale ca date de acest tip.

Figura 10.1 Clas i obiecte mulimea numerelor complexe

187

Programarea calculatoarelor

Acceptarea acestei semnificaii pentru clase de obiecte este de natur s simplifice descrierea obiectelor i s asigure un tratament al acestora similar tipurilor structurate de date din limbajele de programare: este suficient o descriere a tipului obiect i apoi se pot declara constante i variabile de acest tip. Datele care reprezint proprietile obiectelor se numesc atribute i sunt de un anumit tip (de exemplu ntregi, reale, caractere etc.). Setul de valori ale atributelor unui obiect la un moment dat formeaz starea curent a obiectului respectiv. Funciile care definesc comportamentul obiectelor sunt cunoscute ca metode ale clasei. mpreun, atributele i metodele sunt membrii clasei, identificabili prin nume. Pentru a pune n eviden faptul c un membru aparine unui obiect se utilizeaz calificarea, astfel: nume_obiect.nume_membru. n figura 10.1, a.P_real refer valoarea 1.0, iar a.Modul refer metoda Modul a obiectului a pentru a produce obiectul rezultat. Aa cum sugereaz figura 10.1, fiecare obiect trebuie s conin valorile atributelor sale, deoarece ele definesc starea obiectului respectiv. Spaiul de memorie ocupat de atributele unui obiect se numete spaiu propriu al obiectului. n multe cazuri, ntre atribute se afl pointeri care indic anumite zone de memorie alocate dinamic pentru obiect (de exemplu, clasa list are ca membru atributul cap care conine adresa primului nod al unei liste dinamice simplu nlnuite). Acest spaiu alocat dinamic aparine tot obiectului, dar el se numete spaiu extins al obiectului. Gestiunea acestui spaiu extins trebuie asigurat de metodele clasei. Metodele, care descriu aciuni identice pentru toate obiectele clasei, sunt memorate o singur dat, ntr-o zon comun tuturor obiectelor clasei. ntruct metodele descriu comportamentele obiectelor, ele nu pot fi apelate independent, ci numai n legtur cu un anumit obiect. Despre o metod apelat pentru un anumit obiect se spune c se execut n contextul obiectului respectiv, iar acesta este numit obiect curent. Apelarea metodei este considerat ca trimitere de mesaj ctre obiectul curent, iar execuia metodei reprezint rspunsul (reacia) obiectului curent la mesajul primit. Faptul c o metod se execut n contextul obiectului curent nseamn c are, n mod implicit, acces la toate atributele i metodele obiectului. Acestea nu trebuie s apar ca parametri ai metodei. Pentru a utiliza alte obiecte, din aceeai clas sau din clase diferite, metoda trebuie s aib parametri corespunztori. De asemenea, pentru a simplifica scrierea, n interiorul unei metode referirea la membrii obiectului curent se face fr calificare. Pe baza acestor convenii, n funciile Conjugat, Suma i Modul, scrise n pseudocod, s-a specificat cu un parametru mai puin dect numrul de operanzi pe care i presupune operaia respectiv, deoarece un operand este obiectul curent. Referirea la atributele obiectului curent se distinge de celelalte prin lipsa calificrii. Descrierea n pseudocod a metodelor Conjugat, Suma i Modul din clasa CComplex (figura 10.1) poate fi fcut astfel:
void Conjugat(b); begin b.p_reala:=p_reala; b.p_imaginara:=-p_imaginara; end;

188

Elemente de programare orientat obiect


void Suma(b,c); begin c.p_reala:=p_reala+b.p_reala; c.p_imaginara:=-p_imaginara+b.p_imaginara; end; float Modul(); begin Modul=sqrt(p_reala*p_reala+p_imaginara*p_imaginara); end;

Deoarece o clas este un tip de dat, n definirea unei clase B se pot declara atribute de tip A, unde A este la rndul ei o clas. Mai mult, o clas A poate defini atribute de tip A. De exemplu clasa Carte, din figura 10.2 are atributul Autor de tipul Persoana care este, de asemenea, o clas. Mai mult, Persoana are atributul Sef care este de acelai tip (Persoana).

Figura 10.2 Atribute de tip clas Definirea atributelor unei clase ca tipuri ale altei clase pune n eviden o relaie ntre clase i deci ntre obiectele acestora. Din punct de vedere funcional, metodele unei clase au destinaii diverse. n multe cazuri i depinznd de limbaj, unei clase i se poate defini o metod (sau mai multe) constructor i o metod destructor. Un constructor este o metod care creeaz un obiect, n sensul c i aloc spaiu i/sau iniializeaz atributele acestuia. Destructorul este o metod care ncheie ciclul de via al unui obiect, elibernd spaiul pe care acesta l-a ocupat. ncapsularea exprim proprietatea de opacitate a obiectelor cu privire la structura lor intern i la modul de implementare a metodelor. Ea este legat de securitatea programrii, furniznd un mecanism care asigur accesul controlat la

189

Programarea calculatoarelor

starea i funcionalitatea obiectelor. Se evit astfel modificri ale atributelor obiectelor i transformri ale acestora care pot s le deterioreze. Potrivit acestui mecanism, o clas trebuie s aib membrii mprii n dou seciuni: partea public i partea privat. Partea public este constituit din membri (atribute i metode) pe care obiectele le ofer spre utilizare altor obiecte. Ea este interfaa obiectelor clasei respective cu lumea exterioar i depinde de proiectantul clasei. Modalitatea extrem de constituire a interfeei este aceea a unei interfee compus numai din metode. Dac se dorete ca utilizatorii obiectelor clasei s poat prelua i/sau stabili valorile unor atribute ale acestora, interfaa trebuie s prevad metode speciale, numite accesorii, care au ca unic rol accesul la atribute. Partea privat cuprinde membri (atribute i/sau metode) care servesc exclusiv obiectelor clasei respective. De regul, n aceast parte se includ atribute i metode care faciliteaz implementarea interfeei i a funcionalitii interne a obiectului. De exemplu, o stiv, ca tip de dat poate fi descris de o clas Stiva n care interfaa este constituit din metodele Push, Pop, Top, Empty, n timp ce pointerul la capul stivei, Cap i numrtorul de noduri, Contor, ca atribute, sunt ascunse n partea privat. Ea se servete de obiectele altei clase, denumit Nod, ale crei obiecte le nlnuiete n stiv (figura 10.3)
Stiva

Cap: Nod Contor: Integer

Partea privat

Push ( ) Pop ( ) Top ( ) Empty ( )

Partea public (Interfaa)

Figura 10.3 Interfaa obiectelor Trebuie remarcat c ncapsularea nseamn i faptul c utilizatorul metodelor nu trebuie s cunoasc codul metodelor i nici nu trebuie s fie dependent de eventuala schimbare a acestuia, interfaa fiind aceea care i ofer funcionalitatea obiectelor n condiii neschimbate de apelare. Motenirea reprezint o relaie ntre clase i este, probabil, elementul definitoriu al OOP. Relaia permite constituirea unei noi clase, numit derivat (sau fiu) pornind de la clase existente, denumite de baz (sau printe). Dac n procesul de construire particip o singur clas de baz, motenirea este simpl, altfel este multipl.

190

Elemente de programare orientat obiect

Se spune c o clas D motenete o clas A, dac obiectele din clasa D conin toate atributele clasei A i au acces la toate metodele acestei clase. Din aceast definiie, dac D motenete A, atunci obiectele din D vor avea toate atributele i acces la toate metodele lui A, dar n plus: D poate defini noi atribute i metode; D poate redefini metode ale clasei de baz; metodele noi i cele redefinite au acces la toate atributele dobndite sau nou definite. n figura 10.4, clasa Cerc motenete clasa Punct, deci un obiect de tipul Cerc va avea ca membri coordonatele x,y motenite i ca atribut propriu Raza. Funcia Distana, definit pentru calculul distanei dintre punctul curent i punctul p, dat ca parametru, este accesibil i pentru obiectele Cerc i va calcula distana dintre centrul cercului i un alt punct, primit ca parametru. Funcia Arie calculeaz aria din interiorul cercului, fiind nou definit. Funcia Deseneaz este redeclarat de clasa Cerc, lucru impus de codul diferit pe care trebuie s-l aib pentru desenarea obiectelor din aceast clas (cerc sau alt figur).
Punct X: int Y:int Deseneaz() Distana(p: punct): float Cerc Raza: int Arie():float Deseneaz() Distana(p: punct): float X= 200 Y= 200 Raza= 50 X= 100 Y= 100

Figura 10.4 Motenirea simpl. Dac se au n vedere mulimi de clase, atunci se observ c relaia de motenire simpl induce un arbore ierarhic de motenire pe aceast mulime. Exist o singur clas iniial, rdcina arborelui, fiecare clas are un singur ascendent (printe) i orice clas care nu este frunz poate avea unul sau mai muli descendeni (fii). n fine, cu privire la motenirea simpl se pot face urmtoarele observaii: dac se aduc modificri n clasa de baz, prin adugarea de atribute i/sau metode, nu este necesar s se modifice i clasa derivat; motenirea permite specializarea i mbogirea claselor, ceea ce nseamn c, prin redefinire i adugare de noi membri, clasa derivat are, n parte, funcionalitatea clasei de baz, la care se adaug elemente funcionale noi; motenirea este mecanismul prin care se asigur reutilizarea codului sporind productivitatea muncii de programare.

191

Programarea calculatoarelor

Cobornd (de obicei, n reprezentrile grafice ale arborilor de clase, rdcina se afl n partea superioar) n arborele ierarhic al claselor de la rdcin ctre frunze, se poate spune c ntlnim clase din ce n ce mai specializate. Prin motenire se realizeaz o specializare a claselor. n sens invers, de la frunz ctre rdcin, clasele sunt din ce n ce mai generale, avem o relaie de generalizare. Clasa aflat la baza ierarhiei este cea mai general. Limbajele de programare orientate obiect au implementate ierarhii standard extinse de clase, care corespund necesitilor generale ale programrii. Utilizatorii pot deriva clase noi din cele standard. Polimorfismul este un concept mai vechi al programrii, cu diferite implementri n limbajele de programare care se bazeaz pe tipuri de date (limbaje cu tip). El i-a gsit extensia natural i n modelul orientat pe date, implementat prin limbaje cu tip, n care clasa reprezint tipul de date obiect. Polimorfismul n limbajele de programare cu tip. Noiunea de polimorfism exprim capacitatea unui limbaj de programare cu tip de a exprima comportamentul unei proceduri independent de natura (tipul) parametrilor si. De exemplu, o funcie care determin cea mai mare valoare dintr-un ir de valori este polimorfic dac poate fi scris independent de tipul acestor valori. n funcie de modul de implementare, se disting mai multe tipuri de polimorfism. Polimorfismul ad-hoc se materializeaz sub forma unor funcii care au toate acelai nume, dar se disting prin numrul i/sau tipul parametrilor. Acest polimorfism este denumit i suprancrcare, avnd n vedere semantica specific fiecrei funcii n parte. Polimorfismul de incluziune se bazeaz pe o relaie de ordine parial ntre tipurile de date, denumit relaie de incluziune sau inferioritate. Dac un tip A este inclus (inferior) ntr-un tip B, atunci se poate trimite un parametru de tip A unei funcii care ateapt un parametru de tip B. Astfel, un singur subprogram definete funcional o familie de funcii pentru toate tipurile inferioare celor declarate ca parametri. Un exemplu clasic este cazul tipului int, inferior tipului float n toate operaiile de calcul. Polimorfism parametric const n definirea unui model de procedur pentru care chiar tipurile sunt parametri. Acest polimorfism, denumit i genericitate, presupune c procedura se genereaz pentru fiecare tip transmis la apel ca parametru. Cele trei tipuri de polimorfism exist (toate sau numai o parte din ele) n limbajele clasice de programare, dar unele pot s nu fie accesibile programatorului. Polimorfismul n limbajele orientate obiect. Limbajele orientate obiect sau extensiile obiect ale unor limbaje cu tip ofer, n mod natural, polimorfismul ad-hoc i de incluziune. Polimorfismul ad-hoc intrinsec reprezint posibilitatea de a defini n dou clase independente metode cu acelai nume, cu parametri identici

192

Elemente de programare orientat obiect

sau diferii. Acest polimorfism nu necesit mecanisme speciale i decurge simplu, din faptul c fiecare obiect este responsabil de tratarea mesajelor pe care le primete. Polimorfismul este de aceeai natur i n cazul n care ntre clase exist o relaie de motenire, cu precizarea c, n cazul n care o metod din clasa derivat are parametrii identici cu ai metodei cu acelai nume din clasa de baz, nu mai este suprancrcare, ci redefinire, dup cum s-a precizat mai sus. Polimorfimsul de incluziune este legat de relaia de motenire i de aceea se numete polimorfism de motenire. ntr-adevr, relaia de motenire este o relaie de ordine parial, astfel nct dac clasa D motenete direct sau indirect clasa A, atunci D este inferior lui A. n aceste condiii, orice metod a lui A este aplicabil la obiectele de clas D i orice metod, indiferent de context, care are definit un parametru de tip A (printe) poate primi ca argument corespunztor (parametru actual) un obiect de clas D (fiu). Observaie: un obiect de clas A nu poate lua locul unui obiect de clas D, deoarece A acoper numai parial pe D, care este o extensie i o specializare a lui A. Legare static i dinamic a metodelor. Legarea static a metodelor se regsete att n limbajele orientate obiect, ct i n cele clasice. Compilatorul poate determina care metod i din care clas este efectiv apelat ntr-un anumit context i poate genera codul de apel corespunztor. Fie o clas A i o clas D, unde D este derivat din A. Fie o metod din clasa A, numit calculeaz, care este redefinit n clasa derivat, D. Atunci cnd metoda este apelat n contextul unui obiect static, compilatorul poate determina tipul acelui obiect (ca fiind parte a clasei A sau D). Astfel, el va ti ce metod s apeleze (a clasei de baz sau cea redefinit, a clasei derivate). n acest caz are loc o legare static a metodelor (decizia este luat n momentul compilrii). Fie un pointer p, definit ca pointer spre clasa A. Datorit polimorfismului, n limbajele orientate obiect unui obiect din clasa printe, desemnat indirect prin referin (pointer) i nu prin nume, i se poate atribui un obiect fiu. n acest context, p poate primi ca valoare, n timpul execuiei programului, adresa unui obiect din clasa A sau din clasa D. Nu se poate ti la momentul compilrii ce se va ntmpla n timpul execuiei programului, ca urmare nu se poate determina dac, n contextul dat, trebuie apelat metoda clasei de baz sau metoda clasei derivate. De aceea, n locul din program n care este apelat metoda, compilatorul adaug o secven de cod care, la momentul execuiei, va verifica tipul efectiv al obiectului i, dup caz, va realiza apelarea metodei adecvate. n acest caz are loc legarea dinamic a metodelor (sau la momentul execuiei). Legarea dinamic este evident mai costisitoare dect cea static, dar reprezint o necesitate pentru a asigura elasticitatea necesar n realizarea programelor OOP, obiectele putnd avea caracter de variabile dinamice.

193

Programarea calculatoarelor

10.2 Definirea claselor


Definiia unei clase este asemntoare cu definiia unui articol, ns n locul cuvntului rezervat struct se folosete cuvntul class:
class nume { descriere membri; };

Membrii unei clase pot fi atribute sau metode. Atributele sunt descrise asemntor declaraiilor de variabile independente (i asemntor cmpurilor unui articol struct), specificnd tipul i numele atributului respectiv. Membrii unei clase pot fi de orice tip, mai puin de acelai tip cu clasa descris (dar pot fi pointeri ctre clasa descris). Metodele sunt descrise asemntor funciilor independente. Ele pot fi descrise integral n interiorul clasei (descriind antetul i corpul lor) sau specificnd n interiorul clasei doar prototipul funciei, corpul urmnd s fie descris ulterior, n afara clasei. Este preferat a doua variant, deoarece descrierea clasei este mai compact dect n primul caz. Atunci cnd se descrie ulterior corpul unei metode, pentru a specifica apartenena sa la clasa respectiv, numele metodei este prefixat cu numele clasei din care face parte, folosind operatorul de rezoluie (::), astfel:
tip_rezultat nume_clas::nume_metod(lista parametrilor) corp metod

Mai mult, funciile care sunt integral descrise n interiorul clasei sunt considerate funcii inline1, de aceea ele trebuie s fie simple. Pentru funciile mai complexe, ntotdeauna se recomand s fie descrise folosind a doua variant. ntruct o clas este un tip de dat, declararea unui obiect se face asemntor oricrei declaraii de dat:
nume_clas nume_obiect;

Atunci cnd se dorete lucrul cu obiecte dinamice, se poate declara o variabil de tip pointer ctre clas: nume_clas* p_obiect; Declararea unui obiect mai este numit i instanierea clasei, n sensul c se creeaz o instan a acelei clase, o entitate concret din mulimea descris de clasa respectiv.

Apelul funciilor inline nu produce un salt n segmentul de cod ctre codul executabil al funciei, aa cum se ntmpl n cazul funciilor obinuite. Pentru aceste funcii, compilatorul insereaz n program, n locul apelului, secvena de cod corespunztoare corpului funciei, nlocuind parametrii formali cu valorile actuale. Funciile inline au un comportament asemntor macrodefiniiilor.

194

Elemente de programare orientat obiect

Exemplu Definirea clasei Complex, care implementeaz entitatea matematic numr complex. Clasa are atributele p_reala i p_imaginara i o metod pentru afiarea valorii obiectului afiseaza.
class Complex { float p_reala,p_imaginara; void Afiseaza(); }; void Complex::Afiseaza() { printf("\n%5.2f%ci*%5.2f\n",p_reala,p_imaginara>=0?'+':'-', p_imaginara>=0?p_imaginara:-p_imaginara); } Complex tc;

Metoda afieaz ine cont de semnul prii imaginare. Dac aceasta este negativ, semnul minus este afiat naintea simbolului i al prii imaginare. Se declar obiectul tc de tipul Complex. Accesul la membrii obiectului se face folosind operatorul de calificare:
nume_obiect.nume_membru

unde numele obiectului specific din ce obiect este accesat atributul respectiv sau n contextul crui obiect se execut metoda respectiv. Acest mod de accesare este folosit atunci cnd se lucreaz cu obiecte statice. n cazul n care nu avem un obiect ci un pointer ctre un obiect, este necesar i dereferenierea pointerului, nainte de accesul la membri. Acest lucru este realizat folosind operatorul -> n locul operatorului de calificare:
p_obiect -> nume_membru

Pentru implementarea conceptului de ncapsulare, n interiorul unei definiii de clas pot fi folosii modificatori de acces. Acetia sunt private, protected i public (urmai de caracterul: dou puncte). Domeniul de aciune al unul modificator de acces ncepe n locul unde apare el i se ncheie la apariia altui modificator sau la terminarea descrierii clasei. Implicit, toi membri sunt considerai sub influena modificatorului private, deci orice membru aflat n afara domeniului de aciune al unui modificator este considerat privat. Modificatorul public face ca toi membri aflai n domeniul su de aciune s poat fi accesai att de ctre metodele clasei, ct i de ctre orice entitate din afara clasei (membri publici). Modificatorul private face ca membrii aflai n domeniul su de aciune s poat fi accesai numai de ctre metodele clasei respective (membri privai). Modificatorul protected este similar cu modificatorul private, dar membrii respectivi pot fi accesai i de ctre metodele claselor derivate, dar numai n obiecte aparinnd claselor derivate.

195

Programarea calculatoarelor

De obicei atributele unei clase sunt declarate ca fiind private, iar metodele sunt mprite, unele fiind publice (interfaa clasei) i unele private (detalii i mecanisme interne de implementare a clasei. Dei este tehnic posibil ca toi membrii unei clase s fie privai, un obiect de acest tip nu poate fi folosit, neavnd o interfa cu mediul exterior lui. De asemenea, toi membrii unei clase pot fi publici, dar nu este recomandat aceast tehnic din motive de protecie i securitate. Exemplu n acest context, clasa Complex definit n exemplul anterior nu poate fi folosit, toi membrii ei fiind privai. Pentru a putea folosi obiecte de tipul Complex, metoda afieaz trebuie s fie public. Descrierea clasei devine:
class Complex { float p_reala,p_imaginara; public: void Afiseaza(); }; void Complex::Afiseaza() { printf("\n%5.2f%ci*%5.2f\n",p_reala,p_imaginara>=0?'+':'-', p_imaginara>=0?p_imaginara:-p_imaginara); }

Prin adugarea modificatorului de acces public, metoda afieaz este pus la dispoziia mediului extern, ca interfa a obiectului. Pentru accesul controlat la atributele private, clasele pot pune la dispoziia mediului extern metode publice de acces, numite uzual metode accesorii. Acestea au rolul de a prezenta mediului extern valorile unora dintre atribute (acelea care pot prezenta interes) sau de a modifica valorile unora dintre atribute, n mod controlat (numai acele atribute pentru care modificarea la iniiativa mediului extern are sens). Controlul depinde n fiecare caz de scopul clasei respective. Exemplu Adugnd metode accesorii clasei Complex, descrierea acesteia devine:
class Complex { float p_reala,p_imaginara; public: void Afiseaza(); float GetR(); float GetI(); void SetR(float r); void SetI(float i); }; void Complex::Afiseaza() { printf("\n%5.2f%ci*%5.2f\n",p_reala,p_imaginara>=0?'+':'-', p_imaginara>=0?p_imaginara:-p_imaginara); }

196

Elemente de programare orientat obiect


float Complex::GetR() { return p_reala; } float Complex::GetI() { return p_imaginara; } void Complex::SetR(float r) { p_reala=r; } void Complex::SetI(float i) { p_imaginara=i; }

Metodele accesorii definite mai sus (GetR, GetI, SetR, SetI) au rolul de a prezenta valorile atributelor i respectiv de a stabili noi valori pentru ele. n acest exemplu nu se face nici un fel de control asupra modului n care sunt stabilite noile valori. Folosind descrierile de mai sus, urmtoarea secven de program:
void main() { Complex tc; Complex *pc; tc.SetR(5); tc.SetI(-4); tc.Afiseaza(); pc=&tc; pc->Afiseaza(); pc->SetR(-2); pc->SetI(3); pc->Afiseaza(); }

produce pe ecran urmtorul rezultat:


5.00-i* 4.00 5.00-i* 4.00 -2.00+i* 3.00

Pentru a evita eventualele confuzii, n interiorul corpului metodelor poate fi folosit pointerul implicit this atunci cnd se refer membrii obiectului curent. Acesta este gestionat automat i are ca valoare adresa obiectului curent. Ca urmare, metoda SetR ar putea fi rescris astfel:
void Complex::SetR(float r) { this -> p_reala = r; }

Pointerul this poate fi folosit i pentru accesarea metodelor obiectului curent, n acelai mod. innd cont de domeniul de valabilitate al declaraiilor de tipuri de date, descrierea unei clase se face de obicei la nceputul fiierului surs n care urmeaz a fi folosit. Pentru o mai mare generalitate i reutilizare mai uoar, se prefer ca fiecare clas nou s fie descris ntr-un fiier surs separat, care s fie inclus folosind directiva #include n programe.

197

Programarea calculatoarelor

10.3 Constructori
Declararea obiectelor are ca efect alocarea de spaiu n memorie, la fel ca n cazul declarrii oricrei variabile. Acest spaiu nu este iniializat ns. Mai mult, n cazul n care obiectele clasei au i spaiu extins de memorie, acesta nu este alocat automat, obiectul declarat fiind astfel incomplet. Atributele unui obiect nu pot fi iniializate la declarare ntr-o manier asemntoare datelor de tip articol (struct), deoarece de obicei atributele sunt private, deci inaccesibile din exteriorul obiectului. Pentru rezolvarea problemei iniializrii obiectelor exist posibilitatea utilizrii unor metode speciale, numite constructori. La terminarea ciclului de via al obiectelor, este necesar dezalocarea lor. n general, aceasta se realizeaz automat, dar n cazul lucrului cu spaiu extins, ea trebuie gestionat n mod explicit. Problema ncheierii ciclului de via al obiectelor este rezolvat prin utilizarea unor metode speciale numite destructori. Constructorii i destructorii nu ntorc niciun rezultat prin numele lor i antetele lor nu precizeaz nici un tip pentru rezultat (nici mcar void). Constructorii sunt metode care au acelai nume cu clasa creia i aparin. O clas poate avea mai muli constructori, cu liste diferite de parametri (ca tip i/sau numr) metode suprancrcate. Dac nu este definit niciun constructor pentru o clas, compilatorul va genera un constructor implicit, care nu face dect alocarea spaiului propriu al obiectului, n momentul n care acesta a fost declarat. Ca urmare, n acest caz vom avea obiecte neiniializate, urmnd ca iniializarea atributelor s se fac ulterior, prin intermediul metodelor accesorii. n exemplul anterior, pentru clasa Complex s-a generat un constructor implicit care aloc spaiu pentru atributele p_reala i p_imaginara. Iniializarea s-a fcut prin intermediul metodelor SetR i SetI. n cazul n care clasa prezint cel puin un constructor explicit, compilatorul nu mai genereaz constructorul implicit. Ca urmare nu se vor putea declara obiecte neiniializate dac parametrii constructorului nu au valori implicite. Constructorii nu pot fi apelai explicit, precum metodele obinuite. Apelul lor se realizeaz numai la declararea obiectelor. De asemenea, nu se poate determina adresa constructorilor, aa cum se poate face n cazul funciilor obinuite. Am vzut mai sus c declaraia unui obiect care nu are constructor explicit este identic cu declaraia unei variabile simple. n cazul n care clasa prezint constructori explicii valorile pentru iniializare sunt transmise acestuia la declararea obiectului, asemntor listei de parametri reali la apelul unei funcii:
nume_clas nume_obiect(lista_valori);

198

Elemente de programare orientat obiect

Exemplu Pentru clasa Complex se poate defini un constructor care s iniializeze cei doi membri astfel:
class Complex { float p_reala,p_imaginara; public: Complex(float a,float b); }; Complex::Complex(float a,float b) { p_reala=a; p_imaginara=b; }

Avnd acest constructor n cadrul clasei, nu putem declara obiecte neiniializate (ca n exemplele anterioare), ci doar obiecte iniializate:
Complex a(3,4); Complex b(-1,3.2); Complex c; //a reprezint numarul 3+i*4 //b reprezint numarul -1+i*3.2 //incorect

Atunci cnd exist un singur constructor explicit, se aplic regulile transferului parametrilor similar ca la funciile obinuite (se realizeaz conversia parametrilor reali ctre tipurile formale). Dac sunt mai muli constructori, cu liste diferite de parametri, se alege acela a crui list de parametri corespunde ca tip i numr cu lista valorilor (expresiilor) precizate la declararea obiectului. Pentru constructori, ca i pentru orice alt funcie, pot fi precizate valori implicite ale parametrilor. Acolo unde la apel lipsesc parametrii actuali, se folosesc valorile implicite. Ca urmare, la declararea obiectelor pot s nu fie precizate valori pentru toate atributele. Exemplu Se definete clasa Complex astfel:
class Complex { float p_reala,p_imaginara; public: Complex(float a=0,float b=0); }; Complex::Complex(float a,float b) { p_reala=a; p_imaginara=b; }

Putem declara urmtoarele obiecte:


Complex a; Complex b(-1); Complex c(2,3); //a reprezint numarul 0+i*0 //b reprezint numarul -1+i*0 //c reprezint numarul 2+i*3

Dac prototipul constructorului ar fi fost Complex(float a,float b=0); atunci la declaraia obiectului trebuie precizat obligatoriu valoarea pentru primul parametru (cel care va deveni valoarea atributului p_reala); ca urmare, dintre declaraiile anterioare ar fi fost corecte numai cele ale obiectelor b i c.

199

Programarea calculatoarelor

Acolo unde se poate folosi un singur parametru la declararea obiectului, se poate face iniializarea asemntor iniializrii variabilelor simple. Folosind oricare dintre cei doi constructori din exemplul anterior, putem declara un obiect i n modul urmtor:
Complex a = 1; //numarul 1+i*0

Valoarea declarat este atribuit primului parametru al constructorului. Pentru situaiile n care avem nevoie att de obiecte iniializate, ct i de obiecte neiniializate, se adaug n descrierea clasei att un constructor care realizeaz iniializarea obiectului, ct i un constructor vid, care simuleaz constructorul implicit, adugat de compilator atunci cnd nu se definesc constructori implicii. Constructorul vid are urmtoarea form:
Complex::Complex() { }

n acest context, declaraia Complex a; are ca efect crearea obiectului a neiniializat (se utilizeaz constructorul vid, nu constructorul cu valori implicite pentru parametri). Parametrii unui constructor pot fi de orice tip, mai puin de tipul clasei respective (n exemplele anterioare, constructorul clasei Complex nu poate avea un parametru de tipul Complex. Este posibil ns s avem un parametru de tip pointer ctre clasa respectiv sau referin2 ctre clasa respectiv. Un constructor care primete ca parametru o referin ctre un obiect din acea clas se numete constructor de copiere. Prin utilizarea unui constructor de copiere se poate iniializa un obiect nou cu atributele unui obiect existent (se realizeaz o copie a acelui obiect). Constructorii de copiere pot avea i ali parametri, dar acetia trebuie s aib valori implicite. Compilatorul genereaz automat un constructor de copiere pentru toate clasele care nu au un constructor de copiere definit explicit. Exemplu Clasa Complex conine un constructor de copiere:
class Complex { float p_reala,p_imaginara; public: Complex(float a=0,float b=0); Complex(Complex &x); };

n C++ este implementat transferul parametrilor prin adres. Pentru a transmite un parametru prin adres, n lista de parametri se pune naintea numelui su operatorul de refereniere &

200

Elemente de programare orientat obiect Complex::Complex(float a,float b) { p_reala=a; p_imaginara=b; } Complex::Complex(Complex &x) { p_reala=x.p_reala; p_imaginara=x.p_imaginara; }

Putem declara urmtoarele obiecte:


Complex a(3,4); Complex b = a; Complex c(b); //numarul 3+i*4 //numarul 3+i*4 //numarul 3+i*4

Obiectul b este o copie a obiectului a i va avea aceeai stare, imediat dup declarare (aceleai valori ale atributelor). De asemenea, obiectul c este o copie a obiectului b. Pentru ambele obiecte s-a apelat constructorul de copiere. Constructorul implicit de copiere realizeaz o copie binar a spaiului propriu de memorie al obiectului surs. Ca urmare, dac obiectele au spaiu extins de memorie, n urma copierii ambele obiecte refer acelai spaiu extins de memorie. Pentru a evita aceast situaie anormal, atunci cnd clasele descriu obiecte care lucreaz i cu spaiu extins este necesar folosirea unui constructor de copiere explicit, care s realizeze, pe lng copierea atributelor, alocarea de spaiu extins propriu pentru obiectul nou i copierea spaiului extins al obiectului surs n acest spaiu nou. Constructorii nu pot fi apelai n mod explicit n program. Singura situaie n care poate s apar un astfel de apel este la declararea unui obiect, n modul urmtor:
Complex a = Complex(2,5);

n cazul n care o clas are ca membri obiecte, la declararea unui obiect al clasei se apeleaz nti constructorii pentru obiectele membru i apoi constructorul noului obiect.

10.4 Destructori
Destructorii sunt metode speciale, asemntoare constructorilor, care au rol invers: ncheierea ciclului de via al obiectelor. Aa cum pentru fiecare clas se genereaz un constructor implicit (dac nu a fost prevzut unul explicit), compilatorul genereaz i un destructor implicit, dac nu a fost prevzut unul explicit. Spre deosebire de constructori, o clas poate avea numai un destructor explicit. Ca i constructorii, destructorii nu ntorc niciun rezultat. Numele destructorului este numele clasei precedat de caracterul ~ (tilda). Destructorii nu au parametri.

201

Programarea calculatoarelor

Exemplu

class Complex { float p_reala, p_imaginara; public ~Complex(); } Complex::~Complex() { //descrierea corpului destructorului }

Pentru clasa Complex destructorul nu are nimic de fcut i nu e necesar descrierea unui destructor explicit. Acest exemplu urmrete doar s arate cum se declar un destructor explicit. Spre deosebire de constructori, destructorii pot fi apelai explicit, atunci cnd este necesar tergerea unui obiect. Apelul se face la fel ca pentru orice alt metod, n contextul obiectului care trebuie ters:
a.~Complex();

Utilizarea destructorilor este obligatorie atunci cnd se lucreaz cu date dinamice, deoarece destructorii implicii nu pot elibera spaiul alocat dinamic. n cazul n care la crearea unui obiect au fost apelai mai muli constructori, la tergerea lui se apeleaz destructorii corespunztori, n ordine invers.

10.5 Funcii prieten


n unele situaii este nevoie ca funcii care nu sunt membri ai unei clase s poat accesa atributele protejate ale clasei. n acest scop a fost introdus conceptul de funcie prieten n C++. O funcie prieten nu face parte din clas, dar poate accesa atributele protejate. Pentru a specifica o funcie prieten, n interiorul descrierii clasei se scrie prototipul funciei prieten, prefixat cu cuvntul rezervat friend. ntruct funcia prieten nu este membru al clasei, n interiorul su nu este definit pointerul this, ceea ce face ca funcia prieten s nu poat accesa direct atributele obiectului. De aceea este necesar ca obiectul s fie parametru al funciei prieten. Ca urmare, o funcie prieten are un parametru n plus fa de o metod. Modificatorii de acces nu au nicio influen asupra funciilor prieten, de aceea ele pot fi specificate oriunde n cadrul descrierii clasei. Exemplu n acest exemplu funcia Afieaz va fi scoas n afara clasei Complex i va fi declarat ca funcie prieten.
class Complex { float p_reala,p_imaginara; public: friend void Afiseaza(Complex x); Complex(float a=0,float b=0); Complex(Complex &x); };

202

Elemente de programare orientat obiect


void Afiseaza(Complex x) { printf("\n%5.2f%ci*%5.2f\n",x.p_reala,x.p_imaginara>=0?'+':'-', x.p_imaginara>=0?x.p_imaginara:-x.p_imaginara); } void main() { Complex a(1,2); Afiseaza(a); }

Exemplu S se implementeze clasa Stiv dinamic. O list dinamic este format din noduri, deci putem defini nti clasa Nod, urmnd a folosi tipul Nod pentru a descrie clasa Stiv. Pentru acest exemplu, datele memorate n nodurile stivei sunt de tip float.
#include <stdio.h> typedef float TIP_INFO; class Nod { TIP_INFO info; Nod* next; public: float GetInfo(); Nod* GetNext(); Nod(float a, Nod* n); }; float Nod::GetInfo() { return info; } Nod* Nod::GetNext() { return next; } Nod::Nod(float a, Nod* n) { info=a; next=n; } class Stiva { Nod* Cap; public: Stiva(); Stiva(float a); ~Stiva(); void Push(float a); float Pop(); int Empty(); void Afiseaza(); }; void Stiva::Afiseaza() { Nod* x; x=Cap; while(x) { printf("%5.2f ",x->GetInfo()); x=x->GetNext(); } } Stiva::~Stiva() { Nod* x; while(Cap) { x=Cap; Cap=Cap->GetNext(); delete x; } }

203

Programarea calculatoarelor
float Stiva::Pop() { float x; Nod* y; y=Cap; x=Cap->GetInfo(); Cap=Cap->GetNext(); delete y; return x;} void Stiva::Push(float a) { Cap=new Nod(a,Cap); } int Stiva::Empty() { return Cap?0:1; } Stiva::Stiva(float a) { Cap= new Nod(a,NULL); } Stiva::Stiva() { Cap=NULL; } void main() { Stiva s; int i; float x; if(s.Empty()) printf("\nStiva este goala"); else printf("\nStiva contine date"); for(i=0;i<10;i++) s.Push((float)i); s.Afiseaza(); x=s.Pop(); s.Afiseaza(); if(s.Empty()) printf("\nStiva este goala"); else printf("\nStiva contine date");

Clasa Nod conine atributele info (informaia util din nod) i next, iar ca metode un constructor care iniializeaz atributele obiectului i metode accesorii pentru accesarea valorilor atributelor. Clasa Stiva conine un singur atribut, Cap care are ca valoare adresa primului nod al stivei (vrful stivei). Constructorii clasei asigur crearea unei stive vide sau a unei stive cu un element. Metodele asigur adugarea unei informaii n stiv, respectiv extragerea unei informaii. Metoda Afiseaza asigur afiarea pe ecran a informaiilor din stiv.

10.6 Derivarea claselor


Derivarea claselor este legat de implementarea conceptului de motenire. n limbajul C++ este permis motenirea multipl. Pentru a defini o clas fiu ca fiind derivat dintr-o clas printe (sau mai multe clase printe), se procedeaz astfel:
class nume_clasa_fiu : lista_clase_printe { descriere membri noi ai clasei fiu};

204

Elemente de programare orientat obiect

n lista claselor printe se specific numele claselor printe, separate prin virgul i, eventual, precedate de modificatori de acces se pot folosi modificatorii public sau private. Aceti modificatori de acces definesc nivelul de protecie a membrilor clasei printe n clasa fiu, conform tabelului urmtor: Nivel acces n clasa printe private protected public Modificator de acces n lista claselor printe public private public private public private Nivel acces n clasa fiu inaccesibil inaccesibil protected private public private

Pentru fiecare clas printe se poate specifica un modificator de acces. Dac nu se specific niciun modificator de acces atunci, implicit, se consider modificatorul public. Se observ c o clas derivat are acces la membrii clasei printe care au fost definii ca fiind publici sau protejai i nu are acces la membrii privai. Prin derivare se construiesc ierarhii de clase, deci din clasa fiu se pot deriva alte clase noi. Dac la derivare s-a folosit modificatorul de acces private, atunci toi membrii clasei printe vor deveni privai n clasa fiu i o derivare n continuare nu mai este posibil, ei fiind inaccesibili pentru orice alt clas derivat din clasa fiu. ntruct ierarhiile de clase nu sunt definitive, ci ofer posibilitatea extinderii prin adugarea de noi clase derivate, se prefer ca la derivare s se foloseasc modificatorul de acces public. De aceea acesta este modificatorul explicit. Exemplu Fie clasa Punct care implementeaz entitatea punct geometric. Aceasta are atributele x i y, care reprezint coordonatele punctului n plan. Este inclus o singur metod, care deseneaz punctul pe ecran (aceast metod nu va implementat n acest exemplu).
class punct { int x,y; public: void deseneaza(); }; void punct::deseneaza() { //corpul nu este descris in acest exemplu }

Fie clasa Cerc care implementeaz entitatea geometric cerc. Aceasta este descris prin coordonatele centrului cercului i raza sa. Ca urmare clasa Cerc poate fi derivat din clasa Punct, adugnd un nou atribut (raza) i o nou metod, pentru desenarea cercului.

205

Programarea calculatoarelor
class cerc: punct { float raza; public: void deseneaza(); };

void cerc::deseneaza() { //corpul nu este descris in acest exemplu }

Clasa cerc o s aib ca membri atributele x, y i raza i metodele deseneaz (motenit de la clasa Punct, care va fi folosit pentru desenarea centrului cercului) i deseneaz (nou definit, care va fi folosit pentru desenarea cercului). n lucrul cu ierarhii de clase se pune problema compatibilitii tipurilor de date (clase) n cadrul atribuirilor i a conversiilor tipurilor de date. Ca principiu, un obiect al unei clase printe poate primi ca valoare un obiect al unei clase derivate. Acelai principiu este valabil i n cazul pointerilor ctre obiecte. Utiliznd exemplul de mai sus i declaraiile
punct a, *pp; cerc b, *pc;

sunt corecte atribuirile


pp=&a; pc=&b; a=b; pp=pc; pp=&b;

Nu sunt corecte urmtoarele atribuiri: pc=&a; b=a; pc=pp; Pot fi realizate atribuiri folosind conversia explicit a tipurilor de date, astfel:
pc=(cerc *)pp; pc=(cerc *)&a;

10.6.1 Redefinirea atributelor Este posibil ca o clas fiu s redefineasc atribute motenite de la clasa printe (atribute publice sau protejate, ntruct cele private sunt oricum inaccesibile n clasa fiu). n acest caz, clasa fiu va avea dou atribute cu acelai nume. Implicit, utilizarea numelui atributului respectiv refer atributul redefinit. Pentru a accesa atributul motenit, trebuie folosit operatorul de rezoluie, prefixnd numele atributului cu numele clasei printe.

206

Elemente de programare orientat obiect

Exemplu Fie o clas Clasa_parinte care are un atribut a de tip float, atribut protejat, i o clas Clasa_fiu care redefinete atributul a, de tip double. x este un obiect de tipul Clasa_fiu.
class Clasa_parinte { protected: float a; //descrierea restului clasei }; Class Clasa_fiu: public Clasa_parinte { protected: double a; //descrierea restului clasei } Clasa_fiu x;

Expresia

x.a

refer atributul a al clasei derivate, de tip double. Pentru a accesa atributul a motenit de la clasa printe, n cadrul unei metode a obiectului x trebuie folosit expresia
Clasa_parinte::a

10.6.2 Redefinirea metodelor La fel ca n cazul atributelor, o clas fiu poate s redefineasc metodele motenite de la clasa printe, n cazul n care metoda motenit nu corespunde necesitilor. n exemplul anterior, clasa Cerc redefinete metoda deseneaz. Dac a este un obiect de tipul Cerc, atunci apelul a.deseneaz(); sau deseneaz(); efectuat din interiorul clasei Cerc va lansa n execuie metoda redefinit, cea descris de clasa Cerc, care va desena conturul cercului. Atunci cnd e nevoie s se apeleze metoda motenit numele acesteia se prefixeaz cu numele clasei printe, folosind operatorul de rezoluie:
punct::deseneaza();

Un astfel de apel poate s apar n interiorul metodei deseneaz a clasei Cerc pentru a desena centrul cercului. 10.6.3 Constructori i destructori n relaia de motenire Constructorii i destructorii nu se motenesc precum alte metode. La crearea unui obiect al unei clase fiu se apeleaz nti constructorul clasei printe i apoi constructorul clasei fiu. Dac sunt mai multe clase printe (motenire multipl) se apeleaz constructorii claselor printe, n ordinea n care acestea apar n lista claselor printe. La tergerea unui obiect al unei clase fiu se apeleaz destructorii n ordine invers fa de constructori: nti destructorul clasei fiu

207

Programarea calculatoarelor

i apoi destructorii claselor printe, n ordine invers celei n care acestea apar n lista claselor printe. Pentru a preciza parametrii reali utilizai pentru fiecare din constructorii claselor printe, antetul constructorului clasei derivate are o form special:
class Clasa_fiu: clasa_p1, clasa_p2, clasa_p3 { //attribute public: Clasa_fiu(); //constructorul clasei fiu } Clasa_fiu::clasa_fiu():clasa_p1(),clasa_p2(),clasa_p3() { //descrierea corpului constructorului }

Se observ c n descrierea clasei, constructorul se descrie n mod obinuit, dar ulterior, n antetul constructorului apar apeluri ale constructorilor claselor printe. Ordinea n care apar aceste apeluri nu are nici o importan, deoarece ordinea de apelare este dat de ordinea n care sunt specificate clasele printe. Dac una din clasele printe nu are constructor, atunci nu o s apar un apel corespunztor n antetul constructorului clasei fiu, pentru ea apelndu-se automat constructorul implicit. Dac niciuna dintre clase nu are constructor explicit, atunci se folosesc constructorii implicii pentru toate clasele. O situaie deosebit este aceea n care clasa fiu nu are constructor explicit, dar cel puin una din clasele printe are un constructor explicit. Deoarece n aceast situaie nu se pot descrie explicit parametrii pentru apelarea constructorilor claselor printe, aceti constructori trebuie s aib valori implicite pentru toi parametrii. 10.6.4 Clase virtuale n cazul motenirii multiple pot s apar situaii n care o clas derivate motenete un atribut (sau mai multe) de mai multe ori, prin intermediul mai multor linii de motenire. Aceste situaii produc ambiguiti legate de referirea atributului respectiv. Limbajul C++ ofer un mecanism simplu prin care s se revin astfel de situaii, prin utilizarea claselor virtuale. Fie urmtoare ierarhie, n care clasa cf este derivat din clasele cp1, cp2 i cp3, toate acestea fiind la rndul lor derivate din clasa cb. Clasa de la baza ierarhiei, cb are un atribut x. Acest atribut va fi motenit n clasele cp1, cp2, cp3. Clasa cf va moteni 3 exemplare ale atributului x.

208

Elemente de programare orientat obiect

Figura 10.5 Exemplu de motenire multipl Pentru a evita aceast situaie, clasa cb poate fi declarat ca virtual la descrierea claselor cp1, cp2 i cp3, astfel:
class { }; class { }; class { }; class { }; cp1: virtual public cb cp2: virtual public cb cp3: virtual public cb cf: public cp1, public cp2, public cp3,

Considernd aceste declaraii, clasa cf motenete o singur dat atributul x, prin intermediul clasei cp1. Ca principiu, atributul este motenit prin intermediul clasei care apare prima n lista claselor printe. n cazul n care n lista claselor printe apar i clase virtuale, se apeleaz nti constructorii claselor virtuale, n ordinea n care au fost specificate, apoi constructorii claselor nevirtuale, n ordinea n care sunt acestea specificate. 10.6.5 Funcii virtuale Exist situaii n care nu se poate decide n momentul compilrii care este contextul curent n care se apeleaz o metod, care a fost redefinit ntr-o clas fiu. Astfel de situaii apar atunci cnd se lucreaz cu pointeri. Fie o clas cp care conine metoda executa, i o clas derivat din ea, numit cf, care redefinete

209

Programarea calculatoarelor

metoda executa, cu aceeai list de parametri. Fie urmtoarele declaraii:


cp a; cf b; cp* po; //a este obiect de tipul cp //b este obiect de tipul cf // po este pointer catre clasa cp

Pointerul po poate lua ca valoare att adresa unui obiect de tipul cp, ct i adresa unui obiect de tipul cf. Fie apelul
po->executa();

n momentul compilrii nu se poate stabili ce metod s se apeleze, a clasei printe sau a clasei fiu. n astfel de situaii compilatorul genereaz un apel ctre metoda clasei printe. Limbajul C++ ofer posibilitatea de a ntrzia decizia pn la momentul execuiei. n acest scop metoda clasei printe se declar ca fiind virtual prin scrierea cuvntului rezervat virtual naintea antetului su. Este suficient ca metoda clasei de baz s fie declarat ca fiind virtual, n mod automat i metodele claselor derivate vor fi virtuale. Constructorii, destructorii i funciile inline nu pot fi virtuale. 10.6.6 Clase abstracte n limbajul C++ este definit conceptul de funcie virtual pur. Acest concept este necesar n cazul ierarhiilor de clase. Exist situaii n care toate clasele ierarhiei trebuie s conin o anumit metod, implementarea ei fiind diferit n fiecare clas derivat. Clasa de la baza ierarhiei este prea general pentru a putea implementa metodele. n aceast situaie n clasa de baz se includ metode virtuale pure. Prezena unei astfel de metode oblig toate clasele derivate s o conin, fie c o redefinesc fie c nu. O metod virtual pur se declar asemntor cu o metod virtual, adugnd la sfritul antetului =0:
virtual tip_rezultat nume_metoda(lista parametri) =0;

Metodele virtuale pure nu au un corp, nefiind implementate. O clas care conine o metod virtual pur se numete clas abstract. O clas abstract nu poate fi instaniat, ea coninnd metode care nu sunt implementate. Clasele derivate din clase abstracte pot s redefineasc metodele virtuale pure sau nu. Dac metoda virtual pur nu este implementat, atunci i clasa respectiv este clas abstract i nu poate fi instaniat.

210

Elemente de programare orientat obiect

De exemplu, ntr-o ierarhie de clase care descriu figuri geometrice, fiecare clas are nevoie de metode pentru desenarea figurii respective, calculul ariei sau perimetrului. La baza ierarhiei se afl o clas abstract care include metode virtuale pure pentru aceste operaii. Clasele derivate, vor implementa metodele conform specificului fiecrei figuri geometrice.

211

Bibliografie
1. [Aho, Hop a] Aho A., Hopcroft J., Ullman J., Data Structures and Algorithms, Addison-Wesley, 1983 2. [Bras, Brat] Brassard G., Bratley P., Algoritmics: Theory and Practice, Prentice-Hall, 1988 3. [Cor, Lei a] Cormen T., Leiserson C., Rivest R., Introduction to Algorithms, MIT Press, sixteenth printing, 1996 4. [Ghilic, 2003] Ghilic-Micu Bogdan, Roca Ion Gh., Apostol Constantin, Stoica Marian, Lucia Cocianu Ctlina, Algoritmi n programare, Bucureti, Editura ASE, 2003 5. [Gon] Gonnet G.H., Handbook of Algorithms and Date Structures, Addison-Wesley, 1984 6. [Hor] Horowitz E., Sahni S., Fundamentals of Computer Algorithms, Computer Science Press, 1978 7. [Knu] Knuth D., Fundamental Algorithms, vol 1 of The Art of Computer Programming, Addison-Wesley, 1973 8. [Knu] Knuth D., Sorting and Searching, vol 3 of The Art of Computer Programming, Addison-Wesley, 1973 9. [Negrescu, 1994] Negrescu Liviu, Limbajele C i C++ pentru nceptori, Cluj-Napoca, Editura Microinfomatica, 1994 10. [Man] Manmber U., Introduction to Algorithms: A Creative Approach, Addison-Wesley, 1989 11. [Pop, Geo a] Popovici Ct., Georgescu H., State L., Bazele informaticii, vol 1, Tip. Universitii din Bucureti, 1990 12. [Smeureanu, 1995] Ion Smeureanu, Ion Ivan, Marian Drdal, Limbajul C/C++ prin exemple, Bucureti,Editura Cison, 1995 13. [Tom] Tomescu I.., Probleme de combinatoric i teoria grafurilor, Bucureti, Editura Didactic i Pedagogic, 1981 14. [Tud] Tudor S., Tehnici de programare, Bucureti, Editura Teora, 1994 15. [Wil] Wilf H., Algorithms and Complexity, Prentice-Hall, 1986

212

You might also like