Documente Academic
Documente Profesional
Documente Cultură
2. Subprograme .......................................................................................................... 13
Pointerul este un tip de dată predefinit, care are ca valoare adresa unei
zone de memorie (figura 1.1).
Memoria internă
Segment:offset
3
Tipuri dinamice de date. Pointeri
Exemple:
• int* n; ⇒ n este o variabilă de tip pointer spre întreg;
• 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 puţin void) atunci tipul TIP* este adresa
unei zone de memorie de un tip cunoscut. Operaţiile 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
cunoaşte tipul zonei de memorie, nu sunt definite operaţiile care se pot efectua
asupra ei.
Pentru pointerii din exemplele anterioare se rezervă în memoria principală
(în segmentul de date) câte o zonă de 4B în care se va memora o adresă (sub forma
segment:offset).
Când variabila nume nu este iniţializată prin declarare, ea primeşte implicit
valoarea NULL. La execuţie, poate primi ca valoare adresa unei variabile numai de
tipul TIP. Dacă TIP este void, atunci nume poate primi adresa oricărei 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 oricărei variabile, de orice tip.
Iniţializarea pointerilor se poate realiza ca în exemplul precedent sau, ca şi
pentru celelalte variabile, la declarare, astfel:
4
Programarea calculatoarelor
pentru definirea tipului pointer, cât ş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;
Incrementare/decrementare
Dacă nume este pointer spre un tip TIP, prin incrementare/decrementare,
valoarea lui nume se incrementează/decrementează cu numărul de octeţi necesari
pentru a memora o dată de tip TIP, adică cu sizeof(TIP).
nume nume2 c a b
4B 4B 2B 2B 2B
5
Tipuri dinamice de date. Pointeri
nume nume2 c a b
4B 4B 2B 2B 2B
Analog se execută operaţiile ++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 octeţi,
adică adresa lui v[i+1].
6
Programarea calculatoarelor
Pe ecran se va afişa:
Pointer neinitializat!
Pointeri egali
7
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 diferenţa între
cele două adrese (în octeţi), 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 acelaşi tip de dată, altfel rezultatul nu are semnificaţie.
Operaţia este utilă în lucrul cu masive.
m i j
a b
7
Tipuri dinamice de date. Pointeri
m
m[0,0] m[0,1] … m[0,49]
m[0,0] m[0,1] … m[0,49]
m[0]
m[1] m[2,0] m[2,1] … m[2,49]
m[2]
m[3,0] m[3,1] … m[3,49]
m[3]
m[4] m[4,0] m[4,1] … m[4,49]
…
… … … …
m[49]
m[49,0] m[49,1] … m[49,49
8
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
către 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 iniţializat cu adresa de început a masivului,
iar elementele masivului să fie referite prin intermediul acestui pointer.
Exemple: float* v[10]; float* p;
p=v;
După atribuire, pointerul p conţine adresa de început a masivului şi poate fi folosit
pentru referirea elementelor masivului. De exemplu, v[3] şi p[3] referă aceeaşi
zonă de memorie.
Să se scrie secvenţa de program care citeşte 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 );
}
Observaţie:
*(m+i)+j este un pointer, care conţine adresa elementului a[i][j]; în funcţia
scanf trebuie transmise ca parametri adresele unde se depun valorile citite; în
exemplul anterior se putea scrie &*(*(m+i)+j), şi, reducînd, rezultă *(m+i)+j.
9
Tipuri dinamice de date. Pointeri
int* masiv;
masiv=(int*)calloc(50,sizeof(int)); ⇔ rezervă spaţiu de
memorie pentru un vector cu 50 de elemente întregi.
Exemple:
1. Alocarea de spaţiu în heap pentru o matrice.
int** m;
int n,p;
/* se alocă spaţiu pentru vectorul cu adresele celor n linii ale
matricei */
m=(int**)malloc(m*sizeof(int*));
for(int i=0;i<m;i++)
/*se alocă spaţiu pentru fiecare linie a matricei, cîte p elemente*/
m[i]=(int*)malloc(n*sizeof(int));
10
Programarea calculatoarelor
for(i=0;i<*n;i++)
{ printf("v(%d)= ",i);
scanf("%f",&(*v)[i]);
}
}
11
Tipuri dinamice de date. Pointeri
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 conţine adresele de memorie
unde s-au stocat şirurile de caractere care constituie parametrii programului. Primul
şir (argv[0]) conţine identificatorul fişierului (inclusiv calea completă) care
memorează programul executabil. Următoarele şiruri conţin parametrii în ordinea
în care au apărut în linia de comandă (parametrii în linia de comandă sunt şiruri de
caractere separate prin spaţii). Interpretarea acestor parametri cade în sarcina
programului.
#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
tip nume([lista-parametri-formali])
unde:
• tip poate fi un tip simplu de dată. Dacă lipseşte, este considerat tipul implicit
(int pentru unele compilatoare, void pentru altele);
• nume este un identificator care reprezintă numele funcţiei;
• lista-parametrilor-formali conţine parametrii formali sub forma:
13
Subprograme
Parametrii sunt separaţi prin virgulă. La limită, lista poate fi vidă. Pentru fiecare
parametru trebuie specificat tipul, chiar dacă mai mulţi parametri sunt de acelaşi
tip (nu este posibilă definirea de liste de parametri cu acelaşi tip).
Pentru funcţiile care nu întorc o valoare prin numele lor, tipul funcţiei va fi
void sau va fi omis.
return(expresie); sau
return expresie; sau
return;
14
Programarea calculatoarelor
Exemple: Să se scrie o funcţie care calculează cel mai mare divizor comun dintre
două numere întregi nenule, utilizând 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!"); }
#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
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). Operaţiile
efectuate asupra unui parametru formal scalar (care nu este masiv) nu modifică, la
ieşirea din subprogram, parametrul real corespunzător.
Transferul valorii este însoţit de eventuale conversii de tip realizate pe
baza informaţiilor 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
Exemplu:
tip_returnat nume(tip_parametru *p); Ù p este transferat prin valoare,
fiind adresa parametrului real.
Exemple:
1. Să se calculeze produsul scalar dintre doi vectori.
a) rezultatul se întoarce prin numele funcţiei:
16
Programarea calculatoarelor
17
Subprograme
18
Programarea calculatoarelor
#include<stdio.h>
#include<conio.h>
#include<alloc.h>
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
Exemplu:
1. Fie un subprogram care calculează suma elementelor unui vector v de lungime n.
20
Programarea calculatoarelor
Variabilele globale se declară în afara funcţiilor. Ele pot fi referite din orice
alte funcţii. De aceea, schimbul de valori între apelant şi apelat se poate realiza prin
intermediul lor. Variabilele declarate într-o funcţie se numesc locale (din clasa
automatic) şi pot fi referite numai din funcţia respectivă. Domeniul de valabilitate
a unei variabile locale este blocul (funcţia sau instrucţiunea 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;
……………
}
}
21
Subprograme
unde nume_var este o variabilă de tip procedural şi are tipul pointer spre funcţie 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 funcţii de
prototip:
tip_rezultat nume_f(lista_parametrilor_formali);
tip_rezultat nume_functie(lista_parametrilor_formali)
{ … }
void main()
{ …
f(…, nume_functie, …); }
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;
}
/* 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);
}
24
Programarea calculatoarelor
25
Subprograme
unde ultim reprezintă numele ultimului parametru din sublista variabilă. În unele
situaţii (vezi exemplele) se transferă în acest parametru numărul de variabile
trimise.
26
Programarea calculatoarelor
unde tip_element este tipul elementului transferat din listă. După fiecare apel al
funcţiei va_arg, variabila ptlist este modificată astfel încât să indice următorul
parametru.
- va_end încheie operaţia de extragere a valorilor parametrilor şi trebuie apelată
înainte de revenirea din funcţie. Prototipul funcţiei este:
Exemple:
1. Să se calculeze cel mai mare divizor comun al unui număr 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));
}
27
Subprograme
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++]);
}
29
3 Subprograme recursive
long fact(unsigned n)
{ if (!n) return 1;
return n*fact(n-1);
}
n!
Utilizarea formulei C nk = pentru calculul combinărilor
k ! (n − k )!
( n , k ∈ N date) este ineficientă şi uneori imposibilă deoarece n!, pentru n ≥ 13 nu
30
Subprograme recursive
31
Programarea calculatoarelor
exemplu, calculul valorilor funcţiei h=f◦g◦f , unde f,g:R→R sunt funcţii date poate
fi descris astfel. Pentru funcţiile f, g definite prin:
3 2
Fact=3*Fact(2)
Fact=2*Fact(1)
(o)
Fact=3*Fact(2)
(o)
1 0
Fact=1*Fact(0) Fact=1
2 1
Fact=2*Fact(1) Fact=1*Fact(0)
2
3
Fact=2*Fact(1)
Fact=3*Fact(2)
(o)
Fact=3*Fact(2)
(o)
Figura 3.1.a Evoluţia în stivă până la verificarea condiţiei terminale n=0
32
Subprograme recursive
1 2
Fact=1 Fact=2
2 3
Fact=2*Fact(1) Fact=3*Fact(2)
3
3
Fact=3*Fact(2)
Fact=6
(o)
(o)
Figura 3.1.b Eliberarea stivei după execuţia determinată de condiţia terminală
⎧2 x 3 + 1, x < 5
⎪ ⎧⎪5 x 2 − 3 x + 2 , x ≤ 1
f (x ) = ⎨ x 4 + 2 , 5 ≤ x < 8 , g (x ) = ⎨ 3
⎪3 , x > 8 ⎪⎩ x − x + 5 , x > 1
⎩
funcţiile C pentru calculul h=f◦g◦f 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. Problema calculului celui mai mare divizor comun dintre două numere
naturale a şi b poate fi rezolvată recursiv, conform definiţiei următoare,
⎧a , a = b
(a ,b ) = ⎪⎨( a − b ,b ), a > b
⎪( a ,b − a ), b > a
⎩
Funcţia C cmmdc(a,b) este,
34
Subprograme recursive
Exemplu
Presupunând că discurile sunt numerotate în ordinea crescătoare a
diametrelor cu etichetele 1, 2, 3, o soluţie a problemei pentru n=3 poate fi descrisă
astfel.
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();
}
#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();
}
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);
}
37
Programarea calculatoarelor
H1 H2 H3
Dacă cele patru părţi ale unei curbe Hilbert Hk sunt notate A, B, C, D şi se
reprezintă prin săgeţi rutinele care desenează segmentele care le interconectează,
atunci rezultă următoarele scheme recursive.
A: D ← A↓ A→ B
A: D ← A↓ A→ B
B: C↑B→B↓ A
C: B→C ↑C ← D
D: A↓ D← D↑C
Prin executarea următoarei surse C sunt obţinute curbele Hilbert H4.
#include <stdio.h>
#include <graphics.h>
#include <stdlib.h>
#include <conio.h>
const n=5;
const h0=480;
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(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
A: A B⇒D A
40
Subprograme recursive
B: B C⇓ A B
C: C D⇐B C
D: D A⇑ C D
#include <stdio.h>
#include <graphics.h>
#include <stdlib.h>
#include <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);
}
}
42
Subprograme recursive
43
4 Tipul de dată articol
unde tip_articol este identificatorul asociat tipului articol, iar var1, var2,…, varn
sunt identificatorii asociaţi variabilelor de tipul articol declarat.
Parametrii declaraţiei pot lipsi (dar nu toţi 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 declarări. Dacă lipseşte
tip_articol, atunci trebuie să fie prezentă lista de variabile (nevidă), caz în care este
vorba de o declarare de variabile de tip articol, fără î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 căror
44
Tipul de dată articol
a)
45
Programarea calculatoarelor
Din punct de vedere practic, utilizarea tipului articol este strâns legată de
prelucrarea fişierelor. Î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 rădăcină spre extremităţi şi de la stânga 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 declaraţia 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;
Exemplu:
Considerând declaraţiile anterioare, expresia sizeof(data_nasterii) are
valoarea 8, iar sizeof(angajat) are valoarea 90.
46
Tipul de dată articol
#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;
}
47
Programarea calculatoarelor
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];
};
49
Programarea calculatoarelor
50
Tipul de dată articol
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 Fişiere de date
52
Fişiere de date
53
Programarea calculatoarelor
54
Fişiere de date
prin deplasare, în octeţi, faţă de începutul fişierului (la unele sisteme numărul
relativ este stabilit de la unu: P*(Ak)=k).
La scriere, articolul Ak (numărul relativ k-1) se memorează pe poziţia sa,
celelalte k-1 articole anterioare putând să nu existe (pe suport există însă rezervat
loc pentru ele). La citire, articolul Ak (cu numărul relativ k-1, kn) este localizat
direct şi conţinutul lui se transferă în memoria internă.
Acces direct prin numărul relativ k-1
P(Ak)=k-1
55
Programarea calculatoarelor
F1 D1 D2 D3 F2 F3
F4 F2 D4 D5 F5 F6 F7
F8 F9
56
Fişiere de date
Exemple:
6. Standard MS-DOS:
.COM → program executabil;
.EXE → program executabil;
.SYS → driver de sistem;
.OBJ → program obiect;
.BAT → fişiere de comenzi DOS (prelucrări BATCH).
7. Standarde de firmă:
.ARC → arhivă compactată cu PKPAK sau ARC;
.ZIP → arhivă compactată cu PKZIP sau WINZIP;
.DBF → bază de date DBASE.
8. Formate ASCII:
.ASM → program sursă ASSEMBLER;
.BAS → program sursă BASIC;
.PAS → program sursă PASCAL;
.CBL → program sursă COBOL;
.C → program sursă C;
.TXT → fişier text;
9. Formate grafice:
.PCX → Paint Brush;
.MSP → Microsoft Windows;
.WPG → WordPerfect.
57
Programarea calculatoarelor
58
Fişiere de date
59
Programarea calculatoarelor
errno, care defineşte tipul erorii. Valorile obişnuite pentru errno sunt EBADF
(manipulator eronat, nu a fost găsit fişierul) sau EACCES (fişierul nu poate fi
accesat).
Deschiderea unui fişier existent se realizează prin apelul funcţiei open,
care are următorul prototip:
Scrierea într-un fişier se realizează prin apelul funcţiei write, care are
următorul prototip:
60
Fişiere de date
Închiderea unui fişier se realizează prin apelul funcţiei close, care are
următorul prototip:
Poziţionarea într-un fişier se realizează prin apelul funcţiei lseek, care are
următorul prototip:
Exemple:
1. Apelul vb=lseek(nf, 0l, 2); Ù realizează poziţionarea la sfârşitul fişierului
(în continuare se poate scrie în fişier folosind write);
Ştergerea unui fişier existent se realizează prin apelul funcţiei unlink, care
are următorul prototip:
61
Programarea calculatoarelor
unde cale este specificatorul de fişier, iar mod noile permisiuni. Permisiunile sunt
aceleaşi ca la funcţia open. Rezultatul întors de chmod are aceeaşi semnificaţie ca
şi unlink.
Exemplu:
#include <sys\stat.h>
#include <string.h>
#include <stdio.h>
#include <fcntl.h>
#include <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;}
62
Fişiere de date
nume_intern=fopen(sir_nume_extern,sir_mod);
Exemplu:
FILE* f;
f = fopen("PROD.DAT","r");
63
Programarea calculatoarelor
La opţiunile de mai sus se poate adăuga b pentru fişiere binare sau t pentru
fişiere 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ă fişier binar; dacă valoarea este O_TEXT, se consideră fişier text. De
obicei implicită este valoarea O_TEXT.
Modurile uzuale pentru deschiderea fişierelor sunt prezentate în tabelul 5.2.
64
Fişiere de date
Dacă fişierul f are asociată o zonă tampon de ieşire, funcţia scrie în fişier
toate informaţiile din acesta, la poziţia curentă. Dacă fişierul are asociată o zonă
tampon de intrare, funcţia îl goleşte. Î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 rămase în zona
tampon de la o citire anterioară a unei valori numerice). Ştergerea se realizează prin
apelul:
fflush(stdin);
Aflarea poziţiei curente în fişier se realizează prin apelul uneia din funcţiile
fgetpos sau ftell:
65
Programarea calculatoarelor
După apel, la adresa poziţie se află poziţia pointerului de citire/scriere din fişierul f,
ca număr relativ al octetului curent. Primul octet are numărul 0. Valoarea returnată
poate fi folosită pentru poziţionare cu funcţia fsetpos. În caz de succes funcţia
întoarce valoarea 0, iar în caz de eroare o valoare nenulă şi setează variabila errno
la valoarea EBADF sau EINVAL.
Redenumirea sau mutarea unui fişier existent se poate realiza prin apelul
funcţiei rename, care are următorul prototip:
int rename(const char* n_vechi,const char* n_nou);
unde n_vechi reprezintă vechiul nume al fişierului, iar n_nou reprezintă numele
nou.
Dacă numele vechi conţine numele discului (de exemplu C:), numele nou
trebuie să conţină acelaşi nume de disc. Dacă numele vechi conţine o cale, numele
nou nu este obligat să conţină aceeaşi cale. Folosind o altă cale se obţine mutarea
fişierului pe disc. Folosind aceeaşi cale (sau nefolosind calea) se obţine
redenumirea fişierului. Nu sunt permise wildcard-uri (?, *) în cele două nume.
66
Fişiere de date
Ştergerea unui fişier existent se poate realiza prin apelul funcţiei unlink,
prezentată anterior, sau remove, care are următorul prototip:
Citirea dintr-un fişier binar se realizează prin apelul funcţiei fread, care are
următorul prototip:
Exemplu:
67
Programarea calculatoarelor
Exemplu:
Exemplu:
Să se scrie funcţia care calculează numărul de articole dintr-un fişier binar,
cunoscând lungimea în octeţi a unui articol. Funcţia are ca parametri fişierul şi
lungimea în octeţi a unui articol. Prin numele funcţiei se întoarce numărul de
articole din fişier.
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;}
68
Fişiere de date
69
Programarea calculatoarelor
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 de
fisier
clearerr(f);}
fclose(f);
return 0;}
Exemplu:
Să se scrie un program care calculează şi afişează valoarea unei funcţii
introduse de la tastatură într-un punct dat. Funcţia se introduce ca şir de caractere şi
poate conţine apeluri de funcţii standard C (vezi şi [Smeu95]). Programul creează
un fişier sursă C (în care este scrisă forma funcţiei, ca subprogram C), apoi
compilează şi execută un alt program, care va include subprogramul creat.
Descrierea funcţiei introduse de la tastatură trebuie să conţină maxim 200
caractere.
a) Fişierul 51_iii_a.cpp conţine programul care realizează citirea formei
funcţiei, compilarea şi execuţia programului care calculează valoarea funcţiei.
#include<stdlib.h>
#include<stdio.h>
#include<conio.h>
#include<string.h>
#include<process.h>
void main()
70
Fişiere 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);
}
#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 fişierelor binare
72
Algoritmi de prelucrare a fişierelor binare
73
Programarea calculatoarelor
Operatii initiale
Operatii finale
Figura 6.2
74
Algoritmi de prelucrare a fişierelor binare
START
Operaţiiinitiale
Operatii iniţiale
SF = fread (…)
SF != 0 Da
Prelucrare articol
Nu
Operaţiifinale
Operatii finale SF = fread (…)
STOP
Figura 6.3
Operatii initiale
Operatii finale
Figura 6.4
75
Programarea calculatoarelor
unde: variabila p, de tip long reţine poziţia curentă în fişier; f este fişierul a cărui
lungime trebuie calculată; variabila l reţine poziţia curentă (în număr de octeţi faţă
de începutul fişierului, deci lungimea fişierului măsurată în octeţi); variabila nr va
primi ca valoare numărul de articole din fişier; tip_articol este tipul articolelor din
fişier (din punctul de vedere al utilizatorului). Împărţirea se face exact, deoarece
fişierul conţine un număr întreg de articole – utilizarea acestei secvenţe asupra unui
fişier care conţine articole de alt tip (sau are conţinut de altă natură) va duce la
rezultate incorecte.
Operatii initiale
Operatii finale
Figura 6.5
76
Algoritmi de prelucrare a fişierelor binare
<citire articol>
while(!feof(f))
{ <prelucrare articol citit>
<citire articol>
}
Exemplu:
Crearea şi consultarea unui fişier text care memorează elemente întregi,
folosind funcţia feof pentru gestionarea sfârşitului de fişier. La crearea fişierului, fişier
conducător este fişierul standard de intrare. La afişare, conducător este fişierul 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();}
77
Programarea calculatoarelor
78
Algoritmi de prelucrare a fişierelor binare
unde n este numărul relativ al articolului iar tip_articol este tipul de dată care îi
corespunde.
Exemplu:
1. Să se creeze cu populare densă un fişier PRODUSE.DAT cu informaţii despre
producţia cantitativă într-un an, la o societate comercială. Articolele au următoarea
structură logică:
79
Programarea calculatoarelor
#include <stdio.h>
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 poate fi creat
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);
}
}
80
Algoritmi de prelucrare a fişierelor binare
Exemplu:
2. Să se afişeze pe ecran conţinutul fişierului 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 fi deschis
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);
}
}
3. Obţinerea unei situaţii cu mai multe grade de total. Pentru aceasta se stabilesc
câmpuri asociate gradelor de total, numite caracteristici de grupare sau caracteristici
de control. O caracteristică de control este un câmp al articolului din fişierul de date,
care are aceeaşi valoare pentru mai multe înregistrări. Astfel, articolele care au
valoare comună pentru o caracteristică de grupare se pot ordona pe submulţimi,
formând o grupă de control. Fişierul poate constitui, în ansamblul său, caracteristica
81
Programarea calculatoarelor
de grupare de cel mai înalt nivel, pentru care se poate calcula totalul general.
Numărul maxim de grade de total este superior cu unu numărului de
caracteristici de control stabilite. Între caracteristicile de grupare se stabileşte o relaţie
de ordine ierarhică. Pentru prelucrarea fişierului, cu utilizare minimă de memorie,
articolele trebuie sortate după caracteristicile de control. Acest tip de prelucrare intră
în categoria consultărilor secvenţiale integrale şi urmează algoritmul de principiu din
figurile 6.2 sau 6.3. Prelucrarea unui fişier sortat după criteriile enunţate, presupune
existenţa unor operaţii standard, executate la schimbarea valorii fiecărei caracteristici
de control stabilite:
operaţii iniţiale ale unei grupe de control prin care se iniţializează variabila
de total specifică grupei; se salvează valoarea caracteristicii primului articol din
grupă; alte operaţii iniţiale specifice grupei;
operaţii finale ale unei grupe de control prin care se afişează totalul calculat
pentru caracteristica ce se schimbă; se cumulează totalul grupei curente la totalul
grupei ierarhic superioare; alte operaţii finale specifice grupei;
condiţia de prelucrare a unei grupe de control conţine, pe lângă condiţia
specifică, toate celelalte condiţii din amonte.
Raportul final este listat la imprimantă, fie direct, ca fişier de ieşire, fie creat
pe suport magnetic, ca fişier text, în vederea imprimării 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
obţinere a unui raport final, cu control după două caracteristici şi trei grade de total,
unde cîmp_1 şi cîmp_2 sunt caracteristicile de control (câmpuri din articol), v1 şi v2
sunt variabile de lucru pentru salvarea caracteristicilor, val_art e valoarea care
interesează din fiecare articol (câmp al articolului sau valoare calculată pe baza unor
câmpuri ale articolului), iar TOTG, TOT1 şi TOT2 sunt variabile pentru calculul
gradelor de total. Analog, se poate extinde pentru oricâte caracteristici şi grade de
total.
82
Algoritmi de prelucrare a fişierelor binare
START
Operaţii iniţiale
generale
TOTG=0
Citeşte
articol
!feof(f) Da
Operaţii iniţiale
Grupa 1
TOT1=0
v1=cîmp_1
! feof(f) şi
Da
v1==cîmp_1
Operaţii iniţiale
Nu Grupa 2
Operaţii finale
Geupa 1 TOT2=0
v2=cîmp_2
TOTG+=TOT1
! feof(f) şi
v1==cîmp_1 şi Da
v2==cîmp_2
Prelucrare articol
TOT1+=TOT2
Citeşte
STOP articol
Nu
fseek(f,nr*sizeof(tip_articol), SEEK_SET);
fread(&art,sizeof(tip_articol), 1, f);
83
Programarea calculatoarelor
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);
}
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 fişierelor binare
{ fread(&art,sizeof(tip_articol),1,f);
// -----------------------
// Prelucrare articol
// -----------------------
}
}
else printf(" Nu este indeplinita conditia de limite");
fclose(f)
}
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)
}
85
Programarea calculatoarelor
Exemplu:
7.
{ // articolele fisierului sunt in ordinea
// crescatoare a valorii campului 1
// citire nume fisier extern *)
f=fopen(nume_fisier, "rb+ ");
86
Algoritmi de prelucrare a fişierelor binare
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
afişează valorile actuale ale fiecărui câmp de modificat, se poziţionează succesiv
cursorul la începutul fiecărui câmp, cu două răspunsuri posibile ale utilizatorului:
<ENTER>, caz în care se menţine actuala valoare, respectiv o tastă diferită de
<ENTER>, reprezentând primul caracter al noii valori.
87
Programarea calculatoarelor
IS Articol propriu-zis
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ă convenţie, operaţiile de acces la articole se realizează în următoarele condiţii:
scrierea în fişier este permisă numai pentru articolele cu IS=0; citirea din fişier este
permisă numai pentru articolele cu IS=1.
Preformarea presupune deschiderea fişierului ca nou (crearea unui fişier
nou) şi scrierea unui număr de articole (la limită, zero) cu IS=0. Includerea operaţiei
de preformare conduce la dispariţia distincţiei dintre populare şi adăugare. Datorită
faptului că fişierul se deschide ca existent, orice operaţie de scriere a unui nou articol
se tratează ca adăugare. Într-un sistem de programe, deschiderea cu modul wb a unui
fişier se realizează o singură dată, în procedura de preformare.
Scrierea în acces direct presupune furnizarea numărului relativ (nr) al artico-
lului. În funcţie de valoarea lui nr se disting următoarele situaţii:
- dacă nr<dimensiune fişier, se citeşte articolul respectiv din fişier şi
adăugarea este permisă numai dacă IS=0;
- dacă nr>=FileSize(f), are loc extinderea fişierului cu preformarea
articolelor cu numerele relative cuprinse în domeniul dimensiune fişier..nr-1. Noul
articol se scrie pe poziţia nr.
Se remarcă faptul că scrierea în acces direct permite preformarea iniţială cu
zero articole.
Scrierea în acces secvenţial se face fără verificare de existenţă. Scrierea are
88
Algoritmi de prelucrare a fişierelor binare
89
Programarea calculatoarelor
condiţii diferite, după cum această operaţie are loc printre articolele existente,
respectiv după ultimul articol (extindere). În ambele situaţii, condiţiile
de realizare sunt determinate de modul de acces folosit: secvenţial sau direct.
Adăugarea în acces secvenţial se bazează pe presupunerea că utilizatorul nu
impune o corespondenţă prestabilită între conţinut şi numărul articolului, adică în alţi
termeni, că se acceptă o codificare automată. Adăugarea în acces secvenţial poate fi
utilizată în două variante:
Cu verificarea existenţei de articole libere, caz în care se adaugă noul articol
în prima poziţie găsită disponibilă (IS=0), eventual la sfârşit (extindere), dacă nu mai
există articole libere în interior. Această variantă presupune existenţa unei soluţii de
gestiune a articolelor libere (în urma preformării sau a ştergerii logice). Dintre
soluţiile posibile pot fi menţionate:
Folosirea articolului zero pentru colectarea numărului articolelor libere,
într-o structură de forma celei din figura 6.9.
pal ual
0 au 0 au ... 0 au
...
90
Algoritmi de prelucrare a fişierelor binare
Indicatorul de stare (IS) are rol identic cu cel prezentat în §6.3.1. Cheie este
un câmp în care se memorează valoarea cheii articolului existent logic în fişierul de
date, al cărui număr relativ corespunzător este memorat în câmpul nr. Articolele
tabelei de indexuri sunt, în orice moment, sortate crescător după valorile câmpului
cheie. Articolele din fişierul de date sunt memorate aleator. O parte dintre acestea nu-
şi regăsesc corespondent în tabela de indexuri, fiind considerate şterse. Orice operaţie
de acces la articolele fişierului de date se realizează numai prin intermediul tabelei,
91
Programarea calculatoarelor
92
Algoritmi de prelucrare a fişierelor binare
furnizării unei chei inexistente în fişier. În aplicaţii, este preferabilă ştergerea în acces
secvenţial, deoarece permite (datorită citirii care o precede), vizualizarea articolului şi
luarea unei decizii în condiţii de siguranţă.
Exemplu:
9. Exemplul următor descrie funcţii, tipuri de date şi variabile publice pentru
prelucrarea unui fişier organizat indexat. Pentru aceste exemplu, fişierul de date este
format din articole cu următoarea structură:
93
Programarea calculatoarelor
¾ Funcţia pentru citirea în acces secvenţial a unui articol din fişierul de date,
cu prototipul
int ReadSec(fisier f,articol *a);
Funcţia are ca parametri numele intern al fişierului 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 fişierul de date este realizată prin intermediul
tabelei de indexuri, astfel: este citit un articol din tabelă, de la poziţia curentă a
pointerului de fişier şi apoi este citit articolul cu numărul relativ dat de câmpul
nr_rel al articolului citit din fişierul de indexuri. Dacă, în tabela de indexuri,
pointerul de fişier indică sfârşitul de fişier, atunci citirea nu este posibilă şi funcţia
returnează valoarea 0. Prin apelul repetat al funcţiei ReadSec, dacă tabela de
indexuri are poinetrul plasat înaintea primului articol, sunt obţinute articolele din
fişierul de date în ordinea strict crescătoare a valorii cheii.
¾ Funcţia pentru citirea în acces direct a unui articol din fişierul de date, cu
prototipul
int ReadKey(fisier f,articol *a,char *Key);
Funcţia are ca parametri numele intern al fişierului 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.
Funcţia apelează modulul de căutare binară în tabela de indexuri a cheii
Key, SeekKey. Atunci când cheia este găsită, citeşte articolul cu numărul relativ
corespunzător articolului din tabela de indexuri şi returnează valoarea 1, altfel
returnează valoarea 0.
94
Algoritmi de prelucrare a fişierelor binare
95
Programarea calculatoarelor
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 fişierelor 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
98
Algoritmi de prelucrare a fişierelor 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;
}
Exemple
10. Scrieţi programul C pentru crearea în acces direct unui fişier binar cu articole
având structura:
cheie denumire preţ cantitate
Datele sunt preluate de la tastatură până la apăsarea combinaţiei CTRL/Z pentru
câmpul cheie. Fişierul 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 fişierul de date din exemplul anterior. Scrieţi
programul C pentru ştergerea acelor articole ale căror 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 fişierelor binare
clrscr();
printf("Introduceti cheia:");
fflush(stdin);
gets(a.cheie);
}
fclose(f);
close_index();
getch();
}
}
101
Programarea calculatoarelor
exemplu, dacă NUME şi PRENUME sunt două câmpuri distincte, declarate de tip şir
de caractere, forma canonică a cheii de sortare după nume şi prenume este dată de
lungimea efectivă a fiecărei date de tip şir.
Dacă pentru sortarea simplă cheia de sortare poate fi însuşi câmpul din
articol, pentru cea multiplă este necesară o zonă auxiliară de memorie, în care se
construieşte cheia de sortare, în forma canonică.
Sortarea unui fişier se poate realiza cu aducerea lui integrală în memorie
(sortare în memorie) sau cu aducerea în memorie a câte unui articol (sortare "direct
pe disc"). Indiferent de modul utilizat, sortarea poate fi realizată printr-unul din
algoritmii cunoscuţi pentru masivele de date: sortare prin interschimbare, prin
selecţie, prin inserţie etc.
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 fişierelor binare
for(i=0;i<n;i++)
fwrite(&x[i],sizeof(STUDENT),1,f);
fclose (f); }
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
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)
}
104
Algoritmi de prelucrare a fişierelor binare
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);
105
Programarea calculatoarelor
...
1 2 3 4 n
Interclasare 1 ………..
Fişier 1
Interclasare 2 ………………………...
Fişier 2
Interclasare 3 …………………………………………...
Fişier 3
...
Interclasare 3 ………………………………………………………………………………………………………….
Fişier
final Fişier n-1
Se obţin astfel n-1 fişiere intermediare (fişier i), din care numai ultimul se
păstrează, celelalte (împreună cu fişierele iniţiale) se şterg, fie în finalul procesului,
fie la sfârşitul fiecărei etape intermediare (recomandat). Interclasarea a două fişiere
este similară operaţiei aplicate pentru doi vectori. Dimensiunea fişierului rezultat este
suma dimensiunilor fişierelor iniţiale.
Exemplu:
18. Se prezintă structura principială a unui program pentru interclasarea a două fişiere
binare. Cheile de interclasare se află în câmpul c aparţinând articolelor art_1 şi art_2,
corespunzătoare fişierelor 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 fişierelor 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)
}
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); }
108
Algoritmi de prelucrare a fişierelor binare
Exemple:
20. Să se afişeze elementul maxim de pe fiecare coloană a unei matrice de dimensiuni
mxn, memorate dens, într-un fişier binar, în ordine lexicografică. Primul articol
conţine numărul de coloane.
• Observaţie: primul articol are dimensiune diferită de celelalte: numărul de
coloane este de tip întreg iar elementele matricei sunt reale. Din dimensiunea
totală a fişierului, primii sizeof(int) octeţi sunt ocupaţi de numărul de
coloane, restul constituie matricea propriu-zisă. La calcularea poziţiei unui
element în matrice trebuie ţinut cont de faptul că matricea nu începe la
începutul fişierului, ci după sizeof(int) octeţi.
#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);
}
109
7 Structuri dinamice de date. Liste
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 înlănţuite este:
a) Listă simplu înlănţuită b) Listă dublu înlănţuită
111
Programarea calculatoarelor
112
Structuri dinamice de date. Liste
113
Programarea calculatoarelor
114
Structuri dinamice de date. Liste
caz în care este eliminat cu ştergere ultimul nod al listei. Dacă lista este vidă,
funcţia 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;
}
115
Programarea calculatoarelor
free(aux);
return 1;
}
return 0;
}
#include<stdio.h>
#include<conio.h>
#include<alloc.h>
116
Structuri dinamice de date. Liste
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();
}
117
Programarea calculatoarelor
printf("%i ",p->inf);
}
else printf("\nLista vida");
}
118
Structuri dinamice de date. Liste
else{
for(ultim=*cap;ultim->leg!=(*cap); ultim=ultim->leg);
ultim->leg=nou;
}
return 1;
}
return 0;
}
7.4.1 Stiva
119
Programarea calculatoarelor
7.4.2 Coada
#include<stdio.h>
#include<conio.h>
#include<alloc.h>
typedef struct nod{
int inf;
struct nod *leg;
} list, *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();
}
121
8 Grafuri
122
Grafuri
Cea mai simplă reprezentare a unui graf este cea intuitivă, grafică; fiecare
vârf 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, avînd ca extremităţi punctele corespunzătoare
vârfurilor 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,
2
4
6
5
3
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,
1
4
2
3
5
4
2
3
5
5 1
2 3
4
2
7
124
Grafuri
Exemplu
8.1.5 Graful din exemplul 8.1.1, digraful din exemplul 8.1.2 şi graful
direcţionat din exemplul 8.1.3 sunt reprezentate prin matricele de adiacenţă,
⎛0 1 1 0 0 0⎞
⎜ ⎟ ⎛0 1 1 0 1⎞ ⎛0 1 1 0 1⎞
⎜1 0 0 0 1 0⎟ ⎜ ⎟ ⎜ ⎟
⎜0 0 0 0 1⎟ ⎜0 0 0 0 1⎟
⎜1 0 0 0 1 0⎟
A=⎜ ⎟ (8.1.1), A = ⎜ 0 0 0 0 1 ⎟ (8.1.2), A = ⎜ 0 0 0 0 1 ⎟ (8.1.3)
⎜0 0 0 0 0 0⎟ ⎜ ⎟ ⎜ ⎟
⎜ ⎟ ⎜1 0 0 0 0⎟ ⎜0 0 0 1 0⎟
⎜0 1 1 0 0 1⎟ ⎜ ⎟ ⎜ ⎟
⎝0 0 0 1 1⎠ ⎝0 0 0 0 0⎠
⎜0
⎝ 0 0 0 1 0 ⎟⎠
Exemplu
8.1.6 Presupunând că ponderile reprezintă costuri, matricea de reprezentare
⎛∞ 5 1 7⎞
⎜ ⎟
⎜5 ∞ 4 2⎟
a grafului din exemplul 8.1.4. este W = ⎜ .
1 4 ∞ ∞⎟
⎜ ⎟
⎜7 2 ∞ ∞ ⎟⎠
⎝
125
Programarea calculatoarelor
Exemple
8.1.7 Graful din exemplul 8.1.1 poate fi reprezentat astfel, VS=(4),
⎛1 2⎞
⎜ ⎟
⎜1 3⎟
A = ⎜2 5⎟
⎜ ⎟
⎜3 5⎟
⎜5 6 ⎟⎠
⎝
⎛1 2⎞
⎜ ⎟
⎜1 3⎟
⎜1 5⎟
⎜ ⎟
8.1.8 Digraful din exemplul 8.1.2 este reprezentat prin A = ⎜ 2 5⎟ .
⎜ ⎟
⎜3 5⎟
⎜4 1⎟
⎜⎜ ⎟
⎝5 4 ⎟⎠
⎛1 2⎞
⎜ ⎟
⎜1 3⎟
⎜1 5⎟
8.1.9 Graful direcţionat din 8.1.3. este reprezentat prin A = ⎜ ⎟.
⎜2 5⎟
⎜ ⎟
⎜3 5⎟
⎜4 4 ⎟⎠
⎝
8.1.10 Graful ponderat din exemplul 8.1.4. nu are vârfuri izolate, deci este
⎛1 2 5⎞
⎜ ⎟
⎜1 3 1⎟
reprezentat prin intermediul matricei A = ⎜ 2 3 4⎟ .
⎜ ⎟
⎜1 4 7⎟
⎜ ⎟
⎝2 4 2⎠
126
Grafuri
127
Programarea calculatoarelor
Exemple
8.2.1 Fie graful,
1
2 3
6
4
7
5
şi v0=1.Valorile calculate prin aplicarea metodei prezentate sunt,
vârf 1 2 3 4 5 6 7
d
0 0 ∞ ∞ ∞ ∞ ∞ ∞
1 0 1 1 1 ∞ ∞ 1
2 0 1 1 1 2 2 1
0 1 1 1 2 2 1
128
Grafuri
3
2
6
4 9 10
11
5 7
vârf 1 2 3 4 5 6 7 8 9 10 11
d
0 0 ∞ ∞ ∞ ∞ ∞ ∞ ∞ ∞ ∞ ∞
1 0 1 1 ∞ 1 ∞ 1 ∞ ∞ ∞ ∞
2 0 1 1 2 1 2 1 ∞ ∞ ∞ ∞
0 1 1 2 1 2 1 ∞ ∞ ∞ ∞
129
Programarea calculatoarelor
Exemplu
8.2.3. Pentru graful din exemplul 8.2.1., aplicarea metodei de traversare BF
determină următoarea evoluţie,
c
t 1 2 3 4 5 6 7
t=1 1 0 0 0 0 0 0
t=2 1 1 1 1 0 0 1
t=3 1 1 1 1 1 0 1
t=4 1 1 1 1 1 1 1
t=5 1 1 1 1 1 1 1
t=6 1 1 1 1 1 1 1
t=7 1 1 1 1 1 1 1
t=8 1 1 1 1 1 1 1
C
t
t=1 1
t=2 2 3 4 7
t=3 3 4 7 5
t=4 4 7 5 6
t=5 7 5 6
t=6 5 6
t=7 6
t=8
Observaţie Deoarece graful din exemplul 8.2.1. este conex, traversarea BF
realizează vizitarea tuturor vârfurilor grafului. Aplicarea metodei BF grafului din
exemplul 8.2.2. nu determină vizitarea vârfurilor 8,9, 10 şi 11, deoarece acestea sunt
vârfuri neconectate cu vârful iniţial. Cu alte cuvinte, metoda BF aplicată unui graf
determină vizitarea tuturor vârfurilor care sunt conectate cu vârful iniţial selectat.
130
Grafuri
131
Programarea calculatoarelor
132
Grafuri
Exemplu
8.2.4 Prin aplicarea procedurii BF grafului din 8.2.1, obţinem,
1
2
3 4 7
5 6
133
Programarea calculatoarelor
2 3
6
4
7
5
şi v0=1, prin aplicarea metodei descrise, rezultă următoarea evoluţie.
c
1 2 3 4 5 6 7
t
t=1 1 0 0 0 0 0 0
t=2 1 1 1 1 0 0 1
t=3 1 1 1 1 0 1 1
t=4 1 1 1 1 0 1 1
t=5 1 1 1 1 1 1 1
t=6 1 1 1 1 1 1 1
t=7 1 1 1 1 1 1 1
t=8 1 1 1 1 1 1 1
S
t
t=1 1
t=2 7 4 3 2
t=3 6 4 3 2
t=4 4 3 2
t=5 5 3 2
t=6 3 2
t=7 2
t=8
134
Grafuri
Exemplu
8.2.7 Pentru graful,
1
2 3
6
4
7
5
şi v0=1, prin aplicarea metodei descrise, rezultă următoarea evoluţie.
c
1 2 3 4 5 6 7
t
t=1 1 0 0 0 0 0 0
t=2 1 1 0 0 0 0 0
t=3 1 1 0 1 0 0 0
t=4 1 1 1 1 0 0 0
t=5 1 1 1 1 0 1 0
t=6 1 1 1 1 0 1 1
t=7 1 1 1 1 0 1 1
t=8 1 1 1 1 0 1 1
t=9 1 1 1 1 0 1 1
t=10 1 1 1 1 1 1 1
t=11 1 1 1 1 1 1 1
t=12 1 1 1 1 1 1 1
t=13 1 1 1 1 1 1 1
t=14 1 1 1 1 1 1 1
135
Programarea calculatoarelor
S
t
t=1 1
t=2 2 1
t=3 4 2 1
t=4 3 4 2 1
t=5 6 3 4 2 1
t=6 7 6 3 4 2 1
t=7 6 3 4 2 1
t=8 3 4 2 1
t=9 4 2 1
t=10 5 4 2 1
t=11 4 2 1
t=12 2 1
t=13 1
t=14
#include <stdio.h>
#include <conio.h>
#include <alloc.h>
typedef struct nn{
int inf;
struct nn *leg;
}nod,* pnod;
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);
}
137
Programarea calculatoarelor
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++;
}
138
Grafuri
Observaţie
Fie G=(V,E) un graf sau un graf direcţionat. Pe baza procedurii DFG poate
fi realizată următoarea clasificare a elementelor e = (u , v ) ∈ E ,
1) muchii de tip arbore în DF- graful pădure G p , etichetate cu T: (u , v )
are eticheta T dacă procesarea vârfului v a fost decisă ca rezultat al
testării existenţei muchiei e;
2) muchii de tip înapoi, cu etichetă B: (u , v ) este muchie B dacă v este
ancestorul lui u într-o componentă conexă a DF- grafului pădure G p ;
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 DF-
grafului pădure G p ;
4) muchii de tip trecere, etichetate cu C: toate muchiile (u , v ) rămase
neetichetate după încheierea etichetării cu T, B şi F.
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 direcţionate
Un graf direcţionat 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 8
3
2
6
4 9 10
5 7
obţinem,
1) ordinea de parcurgere DFG a vârfurilor: 1,2,3,4,6,7,5,8,9,10
139
Programarea calculatoarelor
2) graful pădure G p ,
8
1
2 9
3
10
4
6 5
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,
8
T
T 1 T
2 9
3
T
10 T
4 B
B T
T
6 5 B
T
7
6 7 1 2
5 8 4 3
140
Grafuri
1 2
4 3
7
6 8
1 2
B
T T T
7 F 4 3
C
T C
T
B 6
8
T
C
141
Programarea calculatoarelor
elementar dacă oricare două vârfuri din Γ sunt distincte, cu excepţia, eventual, a
extremităţilor. 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
v1 v3
Γ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.
Definiţia 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 încât ui=vj.
Observaţie
Orice drum cu lungime cel puţin 1 conţine cel puţin un drum elementar cu
aceleaşi extremităţi.
Într-adevăr, dacă Γ: u0, u1,..,un nu este elementar, atunci există
0 ≤ i < j ≤ n şi i ≠ 0 sau j ≠ n astfel încât ui=uj. Atunci drumul
⎧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. Aplicînd în continuare eliminarea duplicatelor
vârfurilor în modul descris, rezultă în final un u0-um drum elementar.
142
Grafuri
Exemplu
8.3.2 În graful,
v2 v6 v7
v1 v4 v5 v8 v10
v3 v9
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.
143
Programarea calculatoarelor
4
⎛0 1 1 1⎞ ⎛1 0 1 1⎞ ⎛1 1 1 1⎞ ⎛1 1 1 1⎞
⎜ ⎟ ⎜ ⎟ ⎜ ⎟ ⎜ ⎟
⎜1 0 0 0⎟ 2 ⎜0 1 1 1⎟ 3 ⎜1 0 1 1⎟ ⎜1 1 1 1⎟
A=⎜ , A =⎜ , A =⎜ , M =⎜
1 0 0 1⎟ 1 1 1 1⎟ 1 1 1 1⎟ 1 1 1 1⎟
⎜ ⎟ ⎜ ⎟ ⎜ ⎟ ⎜ ⎟
⎜1 0 1 0 ⎟⎠ ⎜1 1 1 1⎟⎠ ⎜1 1 1 1⎟⎠ ⎜1 1 1 1⎟⎠
⎝ ⎝ ⎝ ⎝
Observaţie
Calculul matricei existenţei 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 existenţei drumurilor
într-un graf G cu n vârfuri.
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
Definiţia 8.3.5 Fie G=(V,E) graf netrivial. Vârfurile u,v ∈ V sunt conectate
dacă există un u-v drum în G.
Definiţia 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
5
2
6
4
3
Observaţii
1) Un graf este conex dacă şi numai dacă numărul componentelor sale
conexe este 1.
2) Mulţimile de vârfuri corespunzătoare oricăror două componente
conexe distincte sunt disjuncte. Rezultă că mulţimile de vârfuri
corespunzătoare componentelor conexe ale unui graf formează o
partiţie a mulţimii vârfurilor grafului.
145
Programarea calculatoarelor
Exemplu
8.3.5 Pentru graful,
1 7 3
2
4 5 8 9 6
I Vi Ei
i=0 {1} Ø
i=1 {1,2,4} {(1,2),(1,4)}
i=2 {1,2,4,7,8,5} {(1,2),(1,4),(2,7),(2,8),(7,8),(4,5),(4,7),(5,8)}
Observaţie
Cu toate că este utilizat termenul de w-distanţă, în general D nu este o
distanţă în sensul matematic al cuvîntului.În particular, dacă funcţia pondere
asociază valoarea 1 fiecărei muchii a grafului, atunci pentru fiecare pereche de
vârfuri distincte ale grafului, costul D(u,v) este lungimea unui cel mai scurt drum
între cele două vârfuri. În acest caz D este o distanţă pe mulţimea vârfurilor.
146
Grafuri
Algoritmul Dijkstra
Următorul algoritm a fost propus de către E. W. Dijkstra pentru
determinarea w-distanţelor D(u0,v) şi a câte unui u0-v drum de cost minim pentru
fiecare vârf v≠u0 într-un graf ponderat, unde u0 este prestabilit.
Fie G=(V,E,w) un graf conex ponderat, u0∈V, S⊂V, u0∈S. Se notează
( ) { }
S = V \ S şi D u 0 , S = min D(u 0 , x ); x ∈ S . Fie v∈ S astfel încât D(u0,v)=D(u0,
S ), Γ : u0, u1,…,upv un u0-v drum de cost minim. Evident, ∀0≤i≤p ui∈S şi Γ ’: u0,
u1,…,up un u0- up drum de cost minim. De asemenea,
( ) {
D u0 , S = min D(u0 ,u ) + w( uv ); u ∈ S , v ∈ S ,uv ∈ E . }
( )
Dacă x∈S, y∈ S astfel încât D u 0 , S = D(u 0 , x ) + w( xy ) , rezultă
D(u 0 , y ) = D(u 0 , x ) + w( xy ) .
Pentru determinarea a câte unui cel mai ieftin u0-v drum, algoritmul
consideră o etichetare dinamică a vârfurilor grafului.Eticheta vârfului v este
(L(v),u), unde L(v) este lungimea unui cel mai ieftin u0-v drum determinat până la
momentul respectiv şi u este predecesorul lui v pe un astfel de drum.
Pentru (V,E,w) graf conex ponderat, V = n şi u0∈V, calculul implicat de
algoritmul Dijkstra poate fi descris astfel:
Pas 1: i=0; S0={u0}; L(u0)=0, L(v)= ∞ pentru toţi v ∈ V, v≠u0. Dacă n=1
atunci stop
Pas 2: Pentru toţi 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 încât
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.
Observaţie
Dacă (V,E,w) graf ponderat neconex, atunci, pentru u0∈V, algoritmul lui
Dijkstra permite determinarea w-distanţelor D(u0,v) şi a câte unui u0-v drum de cost
minim pentru toate vârfurile v din componenta conexă căreia îi aparţine u0.
Exemplu
8.3.6 Fie graful ponderat,
1
5 1
2 9
3
16
2
5 5
4
147
Programarea calculatoarelor
148
Grafuri
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();
}
Algoritmul Roy-Floyd
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];
}
Algoritmul Yen
Algoritmul propus de Yen pentru calculul tuturor w-distanţelor într-un graf
ponderat este mai eficient din punctul de vedere al volumului de operaţii decît
algoritmul Roy-Floyd. Fie (V,E,w) un graf ponderat şi W matricea ponderilor.
Pentru determinarea w-distanţelor de la vârful vk fixat la celelalte vârfuri ale
grafului, algoritmul Yen iniţiază următoarele operaţii,
Pas 1: D=W
Pas 2: i=1; λ(k)=0, b(k)=0; λ(j)=0, pentru toţi 1 ≤ j ≤ n , j ≠ k
Pas 3: Calculează min{dkj; 1 ≤ j ≤ n , λ(j)=1};
Determină j0 astfel încât λ(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 toţi j, 1 ≤ j ≤ n
i=i+1
Pas 4: Dacă i<n, reia Pas 3, altfel stop.
151
Programarea calculatoarelor
Exemplu
8.3.7 Fie graful
1
4
3
5 7
2
2
1
5
4 3
4
Se consideră vk=1.
⎛∞ 3 2 7 4 ⎞
⎜ ⎟
⎜3 ∞ 5 ∞ ∞⎟
Pas 1: D = ⎜ 2 5 ∞ 4 1⎟
⎜ ⎟
⎜7 ∞ 4 ∞ ∞⎟
⎜4 ∞ 1 ∞ ∞ ⎟⎠
⎝
152
Grafuri
Exemple
8.4.1 În graful,
v1
v4
v2
v3
v5 v6
v1 v2
v3 v4
153
Programarea calculatoarelor
8.4.3 Digraful,
V1 V2
V3 V4
nu conţine cicluri.
Definiţia 8.4.5 Fie D=(V,E) un digraf. Funcţiile grad exterior, odD,
respectiv grad interior, idD, sunt definite prin, od D : V → N ; id D : V → N ,
∀u ∈ V , od D (u ) = {v / v ∈ V , uv ∈ E} ,
∀u ∈ V , id D (u ) = {v / v ∈ V , vu ∈ E}
Funcţia 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 afişat mesajul “DA”, dacă digraful D este
aciclic, respectiv “NU”, în caz contrar. Descrierea pe paşi a algoritmului Marimont
este,
Pas 1: V0=V, E0=E, D0=(V0,E0)
Pas 2: Dacă od D 0 (v ) ≥ 1 pentru toţi v∈V0, scrie “NU”, stop (dacă toate
vârfurile sunt extremităţi iniţiale ale măcar unui arc, atunci există cicluri în D0);
altfel, continuă.
Pas 3: Selectează v∈V0 cu od D 0 (v ) = 0 ;V0=V0\{v}; E0=E0-{e/ e∈E0, 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 e2
2
4 e3
e4
3 e5
5
evoluţia algoritmului Marimont este,
154
Grafuri
1
e1 e2
2
4 e3
e4
3
1
6
e7 e4 e1
5 4 e5
e9 e6 2
e8 e3 e2
7 3
1
6
e7 e4 e1
5 4 e5
e6 2
e3 e2
3
Pas 2: od D 0 (6 ) = 0 , continuă
155
Programarea calculatoarelor
1
e4 e1
4 e5
2
e3 e2
3
1
e1
2
156
Grafuri
D0: • 1
Pas 4: reia de la pasul 2
Pas 2: od D 0 (1) = 0 , continuă
Pas 3: Selectează vârful 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
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 parţial de cost minim, arbori direcţionaţi, arbori cu
rădăcină şi arbori binari. Pe lângă operaţiile primitive asupra arborilor – căutarea
unei informaţii, 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ă.
Structurile cele mai simple şi care apar cel mai frecvent în aplicaţii sunt
cele arborescente (arbori). Grafurile arbori constituie o subclasă a grafurilor
conexe.
Definiţia 9.1.1 Graful G este arbore dacă G este aciclic şi conex.
Definiţia 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
1
2
3 4
5 6
7
160
Structuri arborescente
este arbore, deoarece, orice (i,j) ∈ E , i≠j, există un i-j drum şi graful nu conţine
cicluri.
9.1.2. Graful
1 3
4 2 7
5
7
nu este arbore, deoarece drumul Γ :1,4,6,2,1 este un ciclu.
9.1.3. Graful
1 3 7
4 2
5
9
6
8
nu este arbore, deoarece conţine trei componente conexe: {1,2,3,4,6}, {3} şi {7,8}.
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.
Observaţie
Fie G=(V,E) un graf. Următoarele afirmaţii sunt echivalente,
1. G este graf arbore;
2. G este graf conex minimal: oricare ar fi e∈E, prin eliminarea muchiei e
din E, graful rezultat nu este conex;
3. G este graf aciclic maximal: prin adăugarea unei noi muchii în graf
rezultă cel puţin un ciclu.
Exemple
9.1.10. Arborele direcţionat
1
3 4
6
5
2
7 8 9 10
este arbore cu rădăcină 1.
162
Structuri arborescente
3 7
5 6
nu are rădăcină.
9.1.12. Arborele
1
4
6
5
2
8 10
este un subarbore cu rădăcină 1 al arborelui din 9.1.10.
163
Programarea calculatoarelor
din afara mulţimii de numere ataşate vârfurilor (de obicei valoarea 0).
Exemplu
9.1.13. Arborele orientat
1
2 3 4
5 6 7 8
9 10 11 12 13 14 15 16
este reprezentat astfel,
N=16, R=1 (rădăcina),
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 reprezentării FIU-FRATE poate fi obţinută prin utilizarea
structurilor de date dinamice. Presupunând că fiecare vârf al arborelui are cel mult
n descendenţi, fiecărui vârf îi este ataşată structura,
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();}
165
Programarea calculatoarelor
Parcurgerea în A-preordine
Modalitatea de vizitare a vârfurilor în parcurgerea în A-preordine poate fi
descrisă astfel. Iniţial, rădăcina arborelui este selectată drept vârf curent. Este vizitat
vârful curent şi sunt identificaţi descendenţii lui. Se aplică aceeaşi regulă de vizitare
pentru arborii avînd ca rădăcini descendenţii vârfului curent, arborii fiind vizitaţi în
ordinea precizată prin numerele ataşate vârfurilor rădăcină corespunzătoare.
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.
Parcurgerea A-postordine
Regula de parcurgerea în A-postordine este asemănătoare traversării A-
preordine, singura diferenţă fiind aceea că, în acest tip de traversare, rădăcina
fiecărui arbore este vizitată după ce au fost vizitate toate celelalte vârfuri ale
arborelui.
Exemplu
9.1.15. Pentru arborele orientat din exemplul 9.1.13. ordinea de vizitare a
vârfurilor este: 5,9,10,11,12,13,6,7,2,3,14,15,16,8,4,1.
Pentru arbori reprezentaţi prin structuri dinamice de date, implementarea
parcurgerii în A-postordine poate fi obţinută pe baza următoarei funcţii recursive.
Parametrul de intrare al funcţiei A_postordine reprezintă rădăcina arborelui curent
în momentul apelului.
166
Structuri arborescente
Observaţie
Parcurgerile în A-preordine şi A-postordine sunt variante de parcurgeri în
adâncime (variante ale metodei DF). Ambele metode consideră prioritare vârfurile
aflate la distanţă maximă faţă de rădăcina arborelui iniţial.
Parcurgerea pe niveluri
Parcurgerea unui arbore orientat pe niveluri constă în vizitarea vârfurilor
sale în ordinea crescătoare a distanţelor faţă de rădăcină.
Exemplu
9.1.16. Pentru arborele definit în exemplul 9.1.13., prin aplicarea
parcurgerii pe niveluri, rezultă următoarea ordine de vizitare a nodurilor, 1, 2, 3, 4,
5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16.
Observaţie
Funcţiile 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., evoluţia algoritmului este,
C
t
t=1 1 8
t=2 2 3 4
t=3 3 4 5 6 7
t=4 4 5 6 7
t=5 5 6 7 8
167
Programarea calculatoarelor
C
t
t=6 6 7 8
t=7 7 8 9 10 11 12 13
t=8 8 9 10 11 12 13
t=9 9 10 11 12 13 14 15 16
t=10 10 11 12 13 14 15 16
t=11 11 12 13 14 15 16
t=12 12 13 14 15 16
t=13 13 14 15 16
t=14 14 15 16
t=15 15 16
t=16 15
t=17
Observaţie
Metoda BF pentru parcurgerea grafurilor este o generalizare a tehnicii de
parcurgere pe niveluri a arborilor orientaţi.
O alternativă de implementare a parcurgerii pe niveluri poate fi descrisă
prin intermediul funcţiilor recursive frati şi parc. Coada C este o variabilă globală
şi este iniţializată cu rădăcina arborelui. Parcurgerea este realizată prin apelul
parc(C).
void frati(v)
{if (v){push(C,v);
fraţi(FRATE[v]);
}
}
void parc()
{if (C){pop(C,v);VIZIT(v);
frati(FIU[v]); parc();
}
}
168
Structuri arborescente
Exemplu
9.1.18. Pentru graful ponderat
1
4 3
2
2 6 8 5
2 9
1 12
4 3
4 2 3
2 6 5
8 9
4 3
Definiţia 9.1.12. Arborele parţial T0∈T(G) este arbore parţial minim pentru
G dacă W(T0)=min{W(T); T∈T(G)}, unde T(G) este mulţimea arborilor parţiali
corespunzători grafului G.
Observaţie
Dacă G este graf finit, atunci T(G) este o mulţime finită, deci orice graf
finit ponderat şi conex are cel puţin un arbore parţial minim.
În continuare este prezentat algoritmul Kruskal pentru determinarea unui
arbore parţial minim al unui graf ponderat conex G=(V,E,w).
Pas 1: i=1; E0=∅
Pas 2: Determină R={e/e∈E \ Ei-1 astfel încât graful (V,Ei-1 ∪ {e}) este
aciclic}
Dacă R=∅, atunci stop; altfel, selectează ei∈R cu w(ei)=min{w(e), e∈R};
Ei=Ei-1 ∪ {ei}
Pas 3: i=i+1 şi reia pasul 2.
169
Programarea calculatoarelor
#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
Exemplu
9.1.19. Evoluţia determinată de program pentru graful
1 ⎛2 3 1⎞
⎜ ⎟
⎜2 4 2⎟
⎜1 6 2⎟
4 2 3 ⎜ ⎟
⎜1 5 3⎟
2 6 8 5 A = ⎜3 ⎟
⎜ 4 4⎟
⎜1 2 4⎟
9 ⎜ ⎟
2 8 1 ⎜4 6 8⎟
⎜5 6 8⎟
⎜⎜ ⎟
4
⎝3 6 9 ⎟⎠
4 3
este:
171
Programarea calculatoarelor
2 3
4 5 6 7
8 9 10
subarborii rădăcinii
2 3
6 7
4 5
8 9 10
Subarbore stâng Subarbore drept
sunt:
172
Structuri arborescente
30 70
10 40 90
20 80
173
Programarea calculatoarelor
Observaţie
Parcurgerea în inordine a unui arbore de sortare determină obţinerea
secvenţei informaţiilor asociate vârfurilor arborelui în ordine crescătoare.
Exemplu
9.2.4 Aplicarea algoritmul descris pentru inserarea informaţiei 55 în
arborele de sortare din exemplul 9.2.3 determină următoarele operaţii,
INF(v)=50; 50<55, inserează în subarborele cu rădăcina avînd informaţia
ataşată 70.
INF(v)=70; 70>55, inserează în subarborele stâng cu rădăcina NULL.
Este creat nodul cu informaţie 55, fiu stâng al nodului de informaţie 70.
Arborele rezultat este
50
30 70
10 40 55 90
20 80
174
Structuri arborescente
Exemplu
9.2.5 Ştergerea informaţiei 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: rădăcina etichetată cu 70;
există subarbore stâng iar acesta nu are subarbore drept- nodul cu informaţie
70 este etichetat cu 55, iar p este înlocuit cu subarborele său stâng (vid). Arborele
rezultat
50
30 55
10 40 90
20 80
este arbore de sortare.
175
Programarea calculatoarelor
Observaţie
Punctul c) de la pasul 2 al algoritmului de eliminare a unei informaţii
dintr-un arbore de sortare poate fi înlocuit cu:
c) dacă INF(v)=nr atunci:
c1) dacă subarborele drept este vid, atunci adresa vârfului v este memorată
într-o celulă suplimentară aux, v devine fiul stânga 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 stâng, atunci
informaţia ataşată fiului dreapta este transferată în vârful curent, iar
fiul dreapta este înlocuit cu fiul său dreapta şi este eliberată memoria
corespunzătoare celulei v->fiud.
c2.2) altfel, se transferă în rădăcină informaţia ataşată ultimului nod p
determinat la c2), nodul p este înlocuit cu fiul său dreapta şi celula
corespunzătoare lui p este eliberată din memorie.
În următoarea sursă C sunt implementaţi algoritmii de adăugare şi ştergere
în arbori de sortare.
#include<stdio.h>
#include<conio.h>
#include<alloc.h>
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
Exemplu
9.2.6 Pentru expresia matematică (a+b)*(c-d)+e/g, arborele de structură
corespunzător este
+
* /
+ - e g
a b c d
178
Structuri arborescente
Exemplu
9.2.7 Etapele calculului sistemului de priorităţi şi al arborelui de structură
pentru expresia de la exemplul 9.2.6 pot fi descrise astfel,
+
* în construcţie
179
Programarea calculatoarelor
+ +
în construcţie * în construcţie
*
+ în construcţie + în construcţie
* în construcţie * în construcţie
+ în construcţie + -
a b a b în construcţie în construcţie
+ +
* în construcţie * în construcţie
+ - + -
a b c în construcţie a b c d
180
Structuri arborescente
* /
+ - în construcţie în construcţie
a b c d
* /
+ - e în construcţie
a b c d
+
* /
+ - e g
a b c d
Observaţie
Construcţia arborelui de structură poate fi realizată în ipoteza în care
expresia este corectă.
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/+.
Observaţie
Parcurgerea arborelui în inordine determină secvenţa de simboluri rezultată
prin eliminarea parantezelor din expresia dată. Restaurarea unei forme parantezate
poate fi realizată printr-o parcurgere SRD şi anume în modul următor. La
momentul iniţial vârful curent este rădăcina arborelui de structură. Dacă vârful
curent v nu este vârf terminal, atunci se generează (s1) eticheta(v)(s2), unde
eticheta(v) este operatorul etichetă a vârfului, s1 este secvenţa rezultată prin
traversarea SRD a subarborelui stâng, s2 este secvenţa rezultată prin traversarea
SRD a subarborelui drept. Dacă v este vârf terminal atunci este generată secvenţa
eticheta(v).
Exemplu
9.2.9 Prin aplicarea metodei de evaluare descrise pentru a=3, b=2, c=5,
d=2, e=6 şi g=2, obţinem:
18
15 3
5 3 6 2
3 2 5 2
182
Structuri arborescente
#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 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
186
Elemente de programare orientată obiect
187
Programarea calculatoarelor
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 rândul 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 acelaşi tip (Persoana).
Definirea atributelor unei clase ca tipuri ale altei clase pune în evidenţă o
relaţie între clase şi deci între obiectele acestora.
Din punct de vedere funcţional, metodele unei clase au destinaţii diverse.
În multe cazuri şi depinzând 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ă spaţiu şi/sau iniţializează atributele acestuia.
Destructorul este o metodă care încheie ciclul de viaţă al unui obiect, eliberând
spaţiul 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 programării, furnizând un mecanism care asigură accesul controlat la
189
Programarea calculatoarelor
Stiva
Cap: Nod
Partea privată
Contor: Integer
Push ( )
Pop ( )
Partea publică (Interfaţa)
Top ( )
Empty ( )
190
Elemente de programare orientată obiect
Punct
X: int
Y:int X= 100
Desenează() Y= 100
Distanţa(p: punct): float
Cerc
Raza: int
X= 200
Arie():float
Y= 200
Desenează()
Raza= 50
Distanţa(p: punct): float
191
Programarea calculatoarelor
192
Elemente de programare orientată obiect
193
Programarea calculatoarelor
Membrii unei clase pot fi atribute sau metode. Atributele sunt descrise
asemănător declaraţiilor de variabile independente (şi asemănător câmpurilor unui
articol – struct), specificând tipul şi numele atributului respectiv. Membrii unei
clase pot fi de orice tip, mai puţin de acelaşi tip cu clasa descrisă (dar pot fi pointeri
către clasa descrisă).
Metodele sunt descrise asemănător funcţiilor independente. Ele pot fi
descrise integral în interiorul clasei (descriind antetul şi corpul lor) sau specificând
în interiorul clasei doar prototipul funcţiei, corpul urmând să fie descris ulterior, în
afara clasei. Este preferată a doua variantă, deoarece descrierea clasei este mai
compactă decât în primul caz. Atunci când se descrie ulterior corpul unei metode,
pentru a specifica apartenenţa sa la clasa respectivă, numele metodei este prefixat
cu numele clasei din care face parte, folosind operatorul de rezoluţie (::), astfel:
tip_rezultat nume_clasă::nume_metodă(lista parametrilor)
corp metodă
Mai mult, funcţiile care sunt integral descrise în interiorul clasei sunt
considerate funcţii inline1, de aceea ele trebuie să fie simple. Pentru funcţiile mai
complexe, întotdeauna se recomandă să fie descrise folosind a doua variantă.
1
Apelul funcţiilor inline nu produce un salt în segmentul de cod către codul executabil al funcţiei, aşa
cum se întâmplă în cazul funcţiilor obişnuite. Pentru aceste funcţii, compilatorul inserează în
program, în locul apelului, secvenţa de cod corespunzătoare corpului funcţiei, înlocuind parametrii
formali cu valorile actuale. Funcţiile inline au un comportament asemănător macrodefiniţiilor.
194
Elemente de programare orientată obiect
Exemplu
Definirea clasei Complex, care implementează entitatea matematică număr
complex. Clasa are atributele p_reala şi p_imaginara şi o metodă pentru afişarea
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 afişează ţine cont de semnul părţii imaginare. Dacă aceasta este negativă,
semnul minus este afişat înaintea simbolului i al părţii imaginare. Se declară
obiectul tc de tipul Complex.
nume_obiect.nume_membru
unde numele obiectului specifică din ce obiect este accesat atributul respectiv sau
în contextul cărui obiect se execută metoda respectivă. Acest mod de accesare este
folosit atunci când se lucrează cu obiecte statice. În cazul în care nu avem un obiect
ci un pointer către un obiect, este necesară şi dereferenţierea pointerului, înainte de
accesul la membri. Acest lucru este realizat folosind operatorul -> în locul
operatorului de calificare:
p_obiect -> nume_membru
195
Programarea calculatoarelor
De obicei atributele unei clase sunt declarate ca fiind private, iar metodele
sunt împărţite, unele fiind publice (interfaţa clasei) şi unele private (detalii şi
mecanisme interne de implementare a clasei. Deşi este tehnic posibil ca toţi
membrii unei clase să fie privaţi, un obiect de acest tip nu poate fi folosit, neavând
o interfaţă cu mediul exterior lui. De asemenea, toţi membrii unei clase pot fi
publici, dar nu este recomandată această tehnică din motive de protecţie şi
securitate.
Exemplu
În acest context, clasa Complex definită în exemplul anterior nu poate fi folosită,
toţi membrii ei fiind privaţi. Pentru a putea folosi obiecte de tipul Complex, metoda
afişează 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);
}
Exemplu
Adăugând metode accesorii clasei Complex, descrierea acesteia devine:
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, următoarea 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 următorul rezultat:
5.00-i* 4.00
5.00-i* 4.00
-2.00+i* 3.00
197
Programarea calculatoarelor
10.3 Constructori
Declararea obiectelor are ca efect alocarea de spaţiu în memorie, la fel ca
în cazul declarării oricărei variabile. Acest spaţiu nu este iniţializat însă. Mai mult,
în cazul în care obiectele clasei au şi spaţiu extins de memorie, acesta nu este alocat
automat, obiectul declarat fiind astfel incomplet. Atributele unui obiect nu pot fi
iniţializate la declarare într-o manieră asemănătoare datelor de tip articol (struct),
deoarece de obicei atributele sunt private, deci inaccesibile din exteriorul
obiectului. Pentru rezolvarea problemei iniţializării obiectelor există posibilitatea
utilizării 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 spaţiu 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
măcar void).
Constructorii sunt metode care au acelaşi nume cu clasa căreia îi aparţin. O
clasă poate avea mai mulţi constructori, cu liste diferite de parametri (ca tip şi/sau
număr) – metode supraîncărcate. Dacă nu este definit niciun constructor pentru o
clasă, compilatorul va genera un constructor implicit, care nu face decât alocarea
spaţiului propriu al obiectului, în momentul în care acesta a fost declarat. Ca
urmare, în acest caz vom avea obiecte neiniţializate, urmând ca iniţializarea
atributelor să se facă ulterior, prin intermediul metodelor accesorii. În exemplul
anterior, pentru clasa Complex s-a generat un constructor implicit care alocă spaţiu
pentru atributele p_reala şi p_imaginara. Iniţializarea s-a făcut prin intermediul
metodelor SetR şi SetI. În cazul în care clasa prezintă cel puţin un constructor
explicit, compilatorul nu mai generează constructorul implicit. Ca urmare nu se vor
putea declara obiecte neiniţializate dacă parametrii constructorului nu au valori
implicite.
Constructorii nu pot fi apelaţi explicit, precum metodele obişnuite. Apelul
lor se realizează numai la declararea obiectelor. De asemenea, nu se poate
determina adresa constructorilor, aşa cum se poate face în cazul funcţiilor
obişnuite. Am văzut mai sus că declaraţia unui obiect care nu are constructor
explicit este identică cu declaraţia unei variabile simple. În cazul în care clasa
prezintă constructori expliciţi valorile pentru iniţializare sunt transmise acestuia la
declararea obiectului, asemănător listei de parametri reali la apelul unei funcţii:
nume_clasă nume_obiect(lista_valori);
198
Elemente de programare orientată obiect
Exemplu
Pentru clasa Complex se poate defini un constructor care să iniţializeze 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;
}
Având acest constructor în cadrul clasei, nu putem declara obiecte neiniţializate (ca
în exemplele anterioare), ci doar obiecte iniţializate:
Complex a(3,4); //a reprezintă numarul 3+i*4
Complex b(-1,3.2); //b reprezintă numarul -1+i*3.2
Complex c; //incorect
Exemplu
Se defineşte 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 următoarele obiecte:
Complex a; //a reprezintă numarul 0+i*0
Complex b(-1); //b reprezintă numarul -1+i*0
Complex c(2,3); //c reprezintă numarul 2+i*3
199
Programarea calculatoarelor
Complex::Complex()
{
}
Exemplu
Clasa Complex conţine un constructor de copiere:
2
În C++ este implementat transferul parametrilor prin adresă. Pentru a transmite un parametru prin
adresă, în lista de parametri se pune înaintea numelui său operatorul de referenţiere &
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;
}
10.4 Destructori
Destructorii sunt metode speciale, asemănătoare constructorilor, care au rol
invers: încheierea ciclului de viaţă al obiectelor. Aşa cum pentru fiecare clasă se
generează un constructor implicit (dacă nu a fost prevăzut unul explicit),
compilatorul generează şi un destructor implicit, dacă nu a fost prevăzut 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 făcut şi nu e necesară descrierea
unui destructor explicit. Acest exemplu urmăreşte doar să arate cum se declară un
destructor explicit.
Spre deosebire de constructori, destructorii pot fi apelaţi explicit, atunci
când 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 când se lucrează cu date
dinamice, deoarece destructorii impliciţi nu pot elibera spaţiul alocat dinamic.
În cazul în care la crearea unui obiect au fost apelaţi mai mulţi constructori,
la ştergerea lui se apelează destructorii corespunzători, în ordine inversă.
Exemplu
În acest exemplu funcţia Afişează va fi scoasă în afara clasei Complex şi va fi
declarată ca funcţie 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 întâi clasa Nod, urmând 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 conţine atributele info (informaţia utilă din nod) şi next, iar ca metode
un constructor care iniţializează atributele obiectului şi metode accesorii pentru
accesarea valorilor atributelor.
Clasa Stiva conţine un singur atribut, Cap care are ca valoare adresa primului nod
al stivei (vârful stivei). Constructorii clasei asigură crearea unei stive vide sau a
unei stive cu un element. Metodele asigură adăugarea unei informaţii în stivă,
respectiv extragerea unei informaţii. Metoda Afiseaza asigură afişarea pe ecran a
informaţiilor din stivă.
204
Elemente de programare orientată obiect
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, adăugând un nou atribut (raza) şi o nouă metodă, pentru
desenarea cercului.
205
Programarea calculatoarelor
punct a, *pp;
cerc b, *pc;
pp=&a;
pc=&b;
a=b;
pp=pc;
pp=&b;
Nu sunt corecte următoarele atribuiri:
pc=&a;
b=a;
pc=pp;
pc=(cerc *)pp;
pc=(cerc *)&a;
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 redefineşte 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
moştenit de la clasa părinte, în cadrul unei metode a obiectului x trebuie folosită
expresia
Clasa_parinte::a
207
Programarea calculatoarelor
şi apoi destructorii claselor părinte, în ordine inversă celei în care acestea apar în
lista claselor părinte.
Pentru a preciza parametrii reali utilizaţi pentru fiecare din constructorii
claselor părinte, 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
}
208
Elemente de programare orientată obiect
209
Programarea calculatoarelor
Pointerul po poate lua ca valoare atât adresa unui obiect de tipul cp, cât şi adresa
unui obiect de tipul cf.
Fie apelul
po->executa();
210
Elemente de programare orientată obiect
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, Roşca Ion Gh., Apostol Constantin,
Stoica Marian, Lucia Cocianu Cătălina, Algoritmi în
programare, Bucureşti, 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
începători, 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. Universităţii din Bucureşti, 1990
12. [Smeureanu, 1995] Ion Smeureanu, Ion Ivan, Marian Dârdală, Limbajul
C/C++ prin exemple, Bucureşti,Editura Cison, 1995
13. [Tom] Tomescu I.., Probleme de combinatorică şi teoria grafurilor,
Bucureşti, Editura Didactică şi Pedagogică, 1981
14. [Tud] Tudor S., Tehnici de programare, Bucureşti, Editura Teora, 1994
15. [Wil] Wilf H., Algorithms and Complexity, Prentice-Hall, 1986
212