100% au considerat acest document util (2 voturi)
682 vizualizări134 pagini

Cursuri ATP

Documentul prezintă algoritmul Kruskal pentru determinarea arborelui parțial de cost minim. Sunt descrise etapele algoritmului, modul de implementare și un exemplu de execuție. De asemenea, sunt prezentate și elementele generale ale metodei Greedy aplicate în algoritmul Kruskal și Prim.

Încărcat de

Stefanescu Mircea
Drepturi de autor
© © All Rights Reserved
Respectăm cu strictețe drepturile privind conținutul. Dacă suspectați că acesta este conținutul dumneavoastră, reclamați-l aici.
Formate disponibile
Descărcați ca PDF, TXT sau citiți online pe Scribd
100% au considerat acest document util (2 voturi)
682 vizualizări134 pagini

Cursuri ATP

Documentul prezintă algoritmul Kruskal pentru determinarea arborelui parțial de cost minim. Sunt descrise etapele algoritmului, modul de implementare și un exemplu de execuție. De asemenea, sunt prezentate și elementele generale ale metodei Greedy aplicate în algoritmul Kruskal și Prim.

Încărcat de

Stefanescu Mircea
Drepturi de autor
© © All Rights Reserved
Respectăm cu strictețe drepturile privind conținutul. Dacă suspectați că acesta este conținutul dumneavoastră, reclamați-l aici.
Formate disponibile
Descărcați ca PDF, TXT sau citiți online pe Scribd

Algoritmul Dijkstra – descriere, implementare, exemple

Descriere etape – algoritm Dijkstra

1. Fie graful ponderat,


1

5 1

2 9
3
16
2
5 5
4

Considerînd u0=1, scrieți toate etapele în aplicarea algoritmului Dijkstra.


Rezolvare:

P1: i=0; S0={1}; L(1)=0, L(i)=  pentru toţi i  2,5 .

P2: S 0 ={2,3,4,5}, u0=1


L(2)=  >L(1)+5=5  L(2)=5, etichetează 2 cu 1
L(3)=  >L(1)+1=1  L(3)=1, etichetează 3 cu 1
L(4)=  >L(1)+9=9  L(4)=9, etichetează 4 cu 1
L(5)=  , w(1,5)=  , deci L(5) nu se modifică

P3: selectează u1=3, L(3)=1, cea mai mică dintre w-distanţele calculate la P2
P4: S1={1,3}
P5: i=i+1=1 ≠ 4, reia P2

P2: S1 ={2,4,5}, u1=3

Nu se modifică nici o etichetă şi nici o w-distanţă (w(3,i)=  , pentru toţi i din S1


P3: selectează u2=2, L(2)=5, cea mai mică dintre w-distanţele calculate la P2
P4: S2={1,3,2}
P5: i=i+1=2 ≠ 4, reia P2

P2: S 2 ={4,5}, u2=2


L(4)= 9>L(2)+2=7  L(4)=7, etichetează 4 cu 2
L(5)=  >L(2)+16=21, etichetează 5 cu 2
Vîrful v pînă la care este 1 2 3 4 5

Pagină 1 din 77
calculată w-distanţa
D(1,v), eticheta lui v 0, -1 5, 1 1, 1 7, 2 21,2

P3: selectează u3=4, L(4)=7, cea mai mică dintre w-distanţele calculate la P2
P4: S3={1,3,2,4}
P5: i=i+1=3 ≠ 4, reia P2

P2: S 3 ={5}, u3=4


L(5)= 21>L(4)+5=12, etichetează 5 cu 4
Vîrful v pînă la care este 1 2 3 4 5
calculată w-distanţa
D(1,v), eticheta lui v 0, -1 5, 1 1, 1 7, 2 12,4

P3: selectează u4=5, L(5)=12, cea mai mică dintre w-distanţele calculate la P2
P4: S3={1,3,2,4,5}
P5: i=i+1=4, stop.
Algoritmul calculează următoarele rezultate:

Drumurile de cost minim de la vârful 1 la fiecare dintre vârfurile grafului se stabilesc pe baza sistemului de
etichete astfel: drumul de la 1 la un vârf v este dat de: v1, eticheta lui v, v2 eticheta lui v1 …, până se ajunge la
eticheta 1. Astfel, v0 -drumurile de cost minim sunt:
până la 2: 2,1;
până la 3: 3,1;
până la 4: 4,2,1;
până la 5: 5,4,2,1.

Implementarea algoritmului Dijkstra


#include <stdio.h>
#include <malloc.h>
typedef struct{ float l;
int vf;
}eticheta;

//subprogram
//preia datele unui graf in forma tabelara din fisierul nume si obtine
//matricea ponderilor, unde MAX este valoarea cu semnificatie de infinit
//ultima linie a fisierului contine virful initial din algoritmul Dijkstra

Pagină 2 din 77
//subprogram
//functia pentru implementarea algoritmului Dijkstra - returneaza
//vectorul de etichete

//subprogram
//functia principala

void preia_graf(char *nume, float ***w, int *n, int *v0, float MAX)
{ int i,j,m,u,v; float p;
FILE *f=fopen(nume,"rt");
fscanf(f,"%i",n);
float **mw=(float **)malloc(*n*sizeof(float*));
for(i=0;i<*n;i++)
mw[i]=(float *)malloc(*n*sizeof(float));
fscanf(f,"%i",&m);
for(i=0;i<*n;i++)
for(j=0;j<*n;j++)
mw[i][j]=MAX;
for(i=0;i<m;i++)
{ fscanf(f,"%i",&u);
fscanf(f,"%i",&v);
fscanf(f,"%f",&p);
mw[u-1][v-1]=mw[v-1][u-1]=p; }
fscanf(f,"%i",v0);
fclose(f);
*w=mw;}

eticheta * Dijkstra (float **w, int n, int v0, float MAX)


{ int i,*prel,nrit,ui,v,vmin;
float lmin;
eticheta *v_et=(eticheta *)malloc(n*sizeof(eticheta));

for(i=0;i<n;i++)
v_et[i].l=MAX;
v_et[v0-1].l=0;

prel=(int*)malloc(n*sizeof(int));
//vector cu proprietatea prel[i]=1 daca pentru virful i+1 a

Pagină 3 din 77
//fost deja determinat un drum de cost minim
//prel[i]=0 in caz contrar

for(i=0;i<n;i++)
prel[i]=0;
prel[v0-1]=1;

ui=v0;
for(nrit=0;nrit<n-1;nrit++)
{ lmin=MAX;
//sunt recalculate w-distantele de la virfurile v cu prel[v-1]=0 si este determinat vmin, //urmatorul virf cu
proprietatea ca pentru acesta a fost determinat un drum de cost minim,
// lmin
for(v=1;v<=n;v++)
{ if((prel[v-1]==0)&&(v_et[v-1].l>v_et[ui-1].l+w[v-1][ui-1]))
{ v_et[v-1].l=v_et[ui-1].l+w[v-1][ui-1];
v_et[v-1].vf=ui;
}
if((prel[v-1]==0)&&v_et[v-1].l<lmin)
{ lmin=v_et[v-1].l; vmin=v;
}
}
ui=vmin; prel[ui-1]=1;
}
free(prel); return v_et;
}
void main()
{ float **w,MAX=1000000; int n,v0,v,u,i;
char numef[20];
printf("Introduceti numele fisierului care contine graful:");
scanf("%s", numef);
preia_graf(numef,&w, &n, &v0, MAX);
eticheta *rez=Dijkstra(w,n,v0,MAX);
for(v=1;v<=n;v++)
if(v!=v0)
{ printf("Costul unui cel mai ieftin drum de la %i la %i este %8.3f",v,v0,rez[v-1].l);
printf("\n Un drum de cost minim: %i ",v);
u=v;

Pagină 4 din 77
while(rez[u-1].vf!=v0)
{ printf("%i ",rez[u-1].vf);
u=rez[u-1].vf;
}
printf("%i \n\n",v0);
}
free(rez);
for(i=0;i<n;i++) free(w[i]);
free(w);
}
Exemplu de execuție

În anumite aplicaţii este necesară exclusiv determinarea w-distanţelor D(v0,v), pentru toţi vV. În acest caz
algoritmul Roy-Floyd permite o rezolvare a acestei probleme mai simplu de implementat decât algoritmul
Dijkstra.

Pagină 5 din 77
Teste de autoevaluare
2. Fie graful ponderat,
1

2 1

2 9
3
1
12
1 5
4

Considerînd u0=1, descrieţi rezultatele aplicării algoritmului Dijkstra acestui graf.


Raspuns:
Se vor parcurge toți pașii, la fel ca la problema 1. Rezultatul se va compara cu rezultatul execuției programului
(pentru verificare).
Algoritmul calculează următoarele rezultate:

Vîrful v pînă la care este 1 2 3 4 5


calculată w-distanţa
D(1,v), eticheta lui v 0, 1 2, 1 1, 1 4, 5 3, 2

Drumurile de cost minim de la vîrful 1 la fiecare dintre vîrfurile grafului se stabilesc pe baza sistemului de
etichete. Astfel, v0 -drumurile de cost minim sînt:
pînă la 2: 2,1;
pînă la 3: 3,1;
pînă la 4: 4,5,2,1;
pînă la 5: 5,2,1.
Fisierul [Link] trebuie sa se regaseasca in directorul proiectului.

Pagină 6 din 77
3. Fie graful,
1

5 1

2 9
3
16
2
5 5
4

Considerînd u0=4, descrieţi rezultatele aplicării algoritmului Dijkstra acestui graf.

Pagină 7 din 77
Arbori parțiali de cost minim: Kruskal, Prim

Arborele parțial de cost minim

Atât în algoritmul lui Prim cât și în algoritmul lui Kruskal putem identifica elementele generale
specifice metodei Greedy astfel:
 există o mulțime inițială 𝐴 din care se aleg elementele care vor compune soluția (𝐴 este
mulțimea muchiilor grafului);
 fiecărui element al mulțimii 𝐴 îi este asociată o valoare numerică (ponderea muchiei);
 mulțimea inițială se sortează crescător, în ordinea ponderilor asociate muchiilor;
 din mulțimea 𝐴 se aleg, în ordine, primele 𝑚 ― 1 elemente care nu formează cicluri
(criteriul de alegere).

1. Algoritmului Kruskal: exemplu, explicații, implementare, exemplu de execuție

Pentru implementarea algoritmului Kruskal, graful conex ponderat este reprezentat sub formă
tabelară, muchiile fiind ordonate crescător după ponderi. Muchiile selectate de algoritm pot fi
8
menţinute de asemenea într-o structură tabelară, sau doar marcate ca fiind incluse în mulţimea de
muchii din arborele parţial minim a cărui construcţie este dorită. În varianta prezentată în
continuare muchiile selectate sunt afişate.

Verificarea condiţiei ca muchia selectată să nu formeze nici un ciclu cu muchiile selectate la


etapele precedente este realizată prin utilizarea un vector TATA. Pentru fiecare vârf i (vârfurile
grafului fiind numerotate de la 1 la n, unde n este numărul de noduri ale grafului), componenta
TATA [i] este predecesorul său în arborele care conţine vârful i construit până la momentul
curent dacă i nu este rădăcina acelui arbore, respectiv TATA[i] este egal cu –numărul de vârfuri
ale arborelui de rădăcină i, în caz contrar. Componentele vectorului TATA sunt iniţializate cu
valoarea -1.

Calculul care realizează adăugarea unei noi muchii poate fi descris astfel:
- Este determinată o muchie de cost minim e=v1v2 care nu a fost selectată anterior;
- Dacă vârfurile v1 şi v2 nu aparţin aceluiaşi arbore, atunci proprietatea de aciclicitate este
îndeplinită şi muchia e este adăugată la structura curentă.
- Adăugarea muchiei e selectate este realizată prin reunirea arborilor din care fac parte v1 şi v2
de rădăcini r1, respectiv r2, astfel: dacă TATA[r1]<TATA[r2], atunci arborele rezultat prin
reunirea celor doi arbori are ca rădăcină vârful r1, iar vârful r2 devine fiu al lui r1. Altfel,
rădăcina arborelui rezultat prin reunire fiind r2, iar r1 devenind fiu al rădăcinii.
- Calculul se încheie după ce a fost adăugată şi cea de-a (n-1)-a muchie.

#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 kruskall(int a[][3],int nm, int nv)


{ int tata[50],i,j;
int v1,v2,k,p,c=0;
for(i=0;i<nv;i++)
tata[i]=-1;
for(j=i=0;i<nv-1;j++)
{ v1=a[j][0];
v2=a[j][1];
k=radacina(v2,tata);
p=radacina(v1,tata);
if(k-p)
{ if(tata[k]<tata[p])
{ tata[k]+=tata[p];
tata[p]=k;
}
else
9
{ tata[p]+=tata[k];
tata[k]=p;
}
c+=a[j][2];
printf("%i -> %i cost %i\n",v1+1,v2+1,a[j][2]);
i++;
}
}
return c;
}

void main()
{ int cost,i,j,nv,nm, a[100][3];
//graful este preluat de la tastatura
//datele citite:numarul de virfuri, numarul de muchii si tabela
//muchiilor in ordinea crescatoare a costurilor
printf("Numarul de virfuri:");scanf("%i",&nv);
printf("Numarul de muchii:");scanf("%i",&nm);
printf("Matricea de reprezentare\n");
for(i=0;i<nm;i++)
{ printf("Muchia %i si ponderea:",i+1);
for(j=0;j<3;j++)
scanf("%i",&a[i][j]);
}
for(i=0;i<nm;i++)
for(j=0;j<2;j++)a[i][j]--;
printf("Arborele de cost minim: \n");
cost=kruskall(a,nm,nv);
printf("\ncu costul %i",cost);
getch();
}

Exemplu de executie:

10
2. Algoritmul lui Prim: exemplu, explicații, implementare și exemplu de execuție

Exemplu:v0=4

Funcția care implementează algoritmul lui Prim primește matricea ponderilor (w), numărul de
vârfuri ale grafului (n), un vârf inițial (v0), adresa unde depune tabela muchiilor selectate (arb), o
valoare folosită pentru inițializare la căutarea muchiei cu cost minim (MAX). Prin numele
funcției se întoarce costul arborelui construit.

#include<stdio.h>
#include<conio.h>
11
#include<malloc.h>

float Prim(float **w,int n, int v0, int ***arb, float MAX)


{ float cost,cmin;
int i,u,v,vf1,vf2;
cost=0;
int *ind=(int*)malloc(n*sizeof(int));
for(i=0;i<n;i++)
ind[i]=0;
ind[v0-1]=1;
//ind este vectorul indicator:
//ind[i]=1 daca virful i+1 a fost trecut in A, altfel este 0
int **muchii=(int**)malloc((n-1)*sizeof(int*));
for(i=0;i<n-1;i++)
muchii[i]=(int*)malloc(2*sizeof(int));
for(i=0;i<n-1;i++)
{ cmin=MAX;
for(u=1;u<=n;u++)
if(ind[u-1])
for(v=1;v<=n;v++)
if((!ind[v-1])&&(w[u-1][v-1]<=cmin))
{ vf1=u;
vf2=v;
cmin=w[u-1][v-1];
}
cost+=cmin;
muchii[i][0]=vf1;
muchii[i][1]=vf2;
ind[vf2-1]=1;
}
*arb=muchii;
free(ind);
return cost;
}

void main()
{ int i,j,nv,nm,v,u, v0, **arb; float **w, MAX=1000000, cost,p;
//graful este preluat de la tastatura
//datele citite:numarul de virfuri, numarul de muchii si matricea ponderilor
printf("Numarul de virfuri:");scanf("%i",&nv);
printf("Numarul de muchii:");scanf("%i",&nm);

//alocare memorie pentru matricea ponderilor


w=(float**)malloc((nv)*sizeof(float*));
for(i=0;i<nv;i++)
w[i]=(float*)malloc(nv*sizeof(float));

printf("Matricea ponderilor\n");
12
for(i=0;i<nv;i++)
for(j=0;j<nv;j++)
w[i][j]=MAX;
for(i=0;i<nm;i++)
{ printf("Muchia %i si ponderea:",i+1);
scanf("%i %i %f",&v, &u, &p);
w[u-1][v-1]=w[v-1][u-1]=p;
}
printf("Introduceti varful de pornire:");
scanf("%i", &v0);

cost=Prim(w,nv,v0,&arb,MAX);
printf("\nArborele partial de cost minim este:\n");
for(i=0;i<nv-1;i++)
printf("%i -> %i\n", arb[i][0],arb[i][1]);
printf(" cu costul %4.2f\n",cost);

getch();
}

13
Subprograme recursive. Metoda Divide et Impera - exemple

1. Să se scrie subprogramul recursiv care determină numărul de elemente negative dintr-un vector.
Se va scrie și programul apelator.

a) Metoda reducerii unei probleme de grad n la o problema de grad n-1 și o problema primitiva
(verificarea unui element din vector)

#include<stdio.h>
int numara(float v[], int n)
{ int nr;
if( n == 0) nr = 0;
else {
nr=numara(v,n-1);nr+=(v[n-1]<0)?1:0;
}
return nr;
}

void main()
{ int n,i, rez; float x[100];
printf("n="); scanf("%d", &n);
for(i=0;i<n;i++)
{printf("x[%d]=", i);scanf("%f", &x[i]);}
rez=numara(x,n);
printf("numarul de elemente negative este %d ", rez);
}

b) Metoda descompunerii unei probleme în două subprobleme (doi vectori) cu rezolvare similară (până la
probleme primitive – cu rezolvare imediată, anume vectori cu un singur element)

#include<stdio.h>
int numara(float v[], int ls, int ld)
{ int nr;
if( ls==ld) nr = (v[ls]<0)?1:0;
else {
nr=numara(v,ls, (ls+ld)/2)+numara(v,(ls+ld)/2+1, ld);
}
return nr;
}

void main()
{ int n,i, rez; float x[100];
printf("n="); scanf("%d", &n);
for(i=0;i<n;i++)
{printf("x[%d]=", i);scanf("%f", &x[i]);}
rez=numara(x,0, n-1);
14
printf("numarul de elemente negative este %d ", rez);
}

2. Să se scrie subprogramul recursiv care determină produsul scalar dintre doi vectori. Se va scrie și
programul apelator.

#include<stdio.h>

//metoda reducerii
float ps(float *x, float *y, int n)
{ float s;
if (n==0) s=0;
else s= ps(x,y,n-1)+x[n-1]*y[n-1];
return s;
}

//metoda de descompunere
float ps1(float *x, float *y, int ls, int ld)
{ float s;
if( ls==ld) s= x[ls]*y[ls];
else {
s=ps1(x,y,ls, (ls+ld)/2)+ps1(x,y,(ls+ld)/2+1, ld);
}
return s;
}

void main()
{ int n,i; float x[100], y[100], rez;
printf("n="); scanf("%d", &n);
for(i=0;i<n;i++)
{printf("x[%d]=", i);scanf("%f", &x[i]);}
for(i=0;i<n;i++)
{printf("y[%d]=", i);scanf("%f", &y[i]);}
rez=ps(x,y,n);
printf("produsul scalar este %5.2f \n", rez);
rez=ps1(x,y,0, n-1);
printf("produsul scalar in varianta doi este %5.2f ", rez);
}

3. Să se scrie subprogramul recursiv care determină cmmdc dintre două numere naturale. Se va oferi
și un exemplu de apel.

#include<stdio.h>
15
int cmmdc_2(int a, int b)
{
int rez;
if (a%b==0) rez= b;
else rez= cmmdc_2(b,a%b);
return rez;
}

void main()

{
int a,b, cmmdc;
printf("a="); scanf("%d", &a);
printf("b="); scanf("%d", &b);
cmmdc=cmmdc_2(a,b);
printf("cmmdc=%d", cmmdc);
}

4. Să se scrie subprogramul recursiv care determină CMMDC dintr-un sir de numere naturale. Se va
oferi și un exemplu de apel.

#include<stdio.h>
unsigned int cmmdc_2(unsigned int a, unsigned int b)
{
unsigned int rez;
if (a%b==0) rez= b;
else rez= cmmdc_2(b,a%b);
return rez;
}

unsigned int cmmdc_n(unsigned int x[], int n)


{
unsigned int rez;
if (n==1) rez=x[0];
else rez= cmmdc_2(cmmdc_n(x,n-1),x[n-1]);
return rez;
}

void main()
{
unsigned int x[100], cmmdc;
int n,i;
printf("n="); scanf("%d", &n);
for(i=0;i<n;i++)
{printf("x[%d]=", i);scanf("%u", &x[i]);}
cmmdc=cmmdc_n(x,n);
printf("cmmdc=%u", cmmdc);
}

16
5. Să se scrie subprogramul recursiv care determină suma elementelor impare dintr-un vector. Se va
oferi un exemplu de apel.

#include<stdio.h>

//metoda reducerii
int suma(int *v, int n)
{ int s;
if (n==0) s=0;
else s= suma(v,n-1) + (v[n-1]%2==1)*v[n-1];
return s;
}

//metoda descompunerii
int suma2(int *v, int ls, int ld)
{ int s;
if (ls==ld) s=(v[ls]%2==1)*v[ls];
else s= suma2(v,ls, (ls+ld)/2) + suma2(v, (ls+ld)/2+1, ld);
return s;
}

void main()
{
int x[100], s;
int n,i;
printf("n="); scanf("%d", &n);
for(i=0;i<n;i++)
{printf("x[%d]=", i);scanf("%d", &x[i]);}
s=suma(x,n);
printf("suma numerelor impare este=%d\n", s);
s=suma2(x,0,n-1);
printf("suma numerelor impare in varianta doi este=%d", s);
}

6. Să se scrie subprogramul recursiv care determină produsul primelor n numere impare.

#include<stdio.h>

int produs(int n)
{
int rez;
if(n==1) rez=1;
else
rez=(2*n-1)*produs(n-1);
return rez;
}

void main()
{

17
int n;
printf("n="); scanf("%d", &n);
printf("produsul primelor %d numere impare=%d", n, produs(n));
}

7. Să se scrie subprogramul recursiv care determină suma cifrelor unui numar intreg.

#include<stdio.h>

int suma(int n)
{ int rez;
if(!n) rez=0;
else rez= n%10+suma(n/10);
return rez;
}

void main()
{
int n;
printf("n="); scanf("%d", &n);
printf("suma cifrelor este=%d", suma(n));
}

8. Scrieți un subprogram recursiv care rezolvă problema turnurilor din Hanoi.

Problema turnurilor din Hanoi este următoarea: se dau trei tije verticale (fie ele 1, 2, 3), și un n discuri de
diametre diferite, cu o gaură în centru, prin care poate trece o tijă. În situația inițială toate discurile sînt
introduse pe tija 1. Regulile sînt următoarele: a) un disc mai mare nu poate fi pus peste un disc mai mic; la
o mutare se poate muta un singur disc de pe o tijă pe alta. Situația inițială respectă regula a), discurile fiind
așezate în ordine descrescătoare a diametrelor, de la bază spre vîrf. Se cere să se găsească o serie de
mutări, prin care toate discurile să fie mutate pe tija 3.

Problema poate fi descrisă recursiv astfel:


 se mută n-1 discuri de pe tija 1 pe tija 2,
 se mută discul n de pe tija 1 pe tija 3,
 se mută n-1 discuri de pe tija 2 pe tija 3.
La fiecare mutare se folosesc următorii parametri: numărul de discuri care se mută, tija inițială (pe care se
află discurile respective), tija finală (pe care trebuie duse discurile) și tija intermediară (cealaltă tija, care
poate fi folosită pentru manevre).

Subprogramul următor memorează mutările necesare într-o matrice cu două coloane, în care fiecare linie
descrie o mutare, elementul de pe prima coloană fiind tija sursă, cel de pe a doua coloană este tija
destinație. Această matrice și numărul de mutări (numărul de linii) constituie parametri de intrare/ieșire la
fiecare apel.

//I: n, tija initiala, tija finala, tija interm., rezultat, nr mutari


//E: rezultat, nr mutari
#include<stdio.h>
18
void turnuri_hanoi(int n,int ti,int tf,int tint,int rez[][2],int *nr)
{
if(n==1)
{ rez[*nr][0]=ti;
rez[*nr][1]=tf;
(*nr)++;
}
else
{ turnuri_hanoi(n-1,ti,tint,tf,rez,nr);
rez[*nr][0]=ti;
rez[*nr][1]=tf;
(*nr)++;
turnuri_hanoi(n-1,tint,tf,ti,rez,nr);
}
}

void main()
{
int i,n,mutari[100][2],nrm;
printf("numarul de discuri=");scanf("%d", &n);
nrm=0;
turnuri_hanoi(n,1,3,2,mutari,&nrm);
for(i=0;i<nrm;i++)
printf("%d, %d\n", mutari[i][0], mutari[i][1]);
}

9. Metoda bisectiei
Funcţia are ca parametri de intrare capetele intervalului în care se caută
soluţia (x0 şi x1), numărul maxim de iteraţii (n), precizia dorită (eps),
funcţia asociată ecuaţiei (f) şi adresa unde se va înscrie soluţia. Prin
numele funcţiei se returnează un cod de eroare cu următoarea semnificaţie: 0 –
nu s-a găsit soluţie datorită numărului prea mic de iteraţii sau preciziei
prea mari cerute; 1 – s-a obţinut soluţia exactă; 2 – s-a obţinut o soluţia
aproximativă; 3 – intervalul dat nu conţine nici o soluţie.

#include <stdio.h>
#include<math.h>

float f(float x)
{ return (pow(x,2)-5*pow(x,1)+6);
}

19
int bisectie(float x0,float x1,unsigned n,float eps,float (*f)(float),float
*sol)
{ int cod;
if ((*f)(x0)*(*f)(x1)>0) cod=3;
else if (n==0) cod=0;
else {*sol=(x0+x1)/2;
if((*f)(*sol)==0) cod=1;
else if(fabs(x0-x1)<=eps) cod=2;
else if((*f)(*sol)*(*f)(x0)<0)
cod=bisectie(x0,*sol,n-1,eps,f,sol);
else cod=bisectie(*sol,x1,n-1,eps,f,sol);
}
return cod;
}
int main()
{ float eps,x0,x1,x2;
int n;
printf("x0= ");
scanf("%f",&x0);
printf("x1= ");
scanf("%f",&x1);
printf("n= ");
scanf("%d",&n);
printf("epsilon= ");
scanf("%f",&eps);
int cod=bisectie(x0,x1,n,eps,f,&x2);
switch (cod)
{case 0:printf("Nu s-a gasit nici o solutie !"); break;
case 1:printf("Solutia exacta: %f",x2); break;
case 2:printf("Solutia aproximativa: %f",x2); break;
case 3:printf("Nu exista solutie in intervalul dat"); break;
}
return 0;
}

Temă:
1. Să se scrie subprogramul recursiv care determină un număr la o putere (xn), precum și programul
apelator.
2. Să se scrie subprogramul recursiv care calculează valoarea unui polinom într-un punct dat, precum
și programul apelator.

3. Scrieți un subprogram recursiv care calculează (aranjamente de n luate cîte k), precum
și programul apelator.
4. Să se scrie subprogramul recursiv care determină rezultatul ridicării unei matrice la o putere data,
precum și programul apelator.

Se vor studia ambele metode de scriere a unui subprogram recursiv (metoda reducerii și metoda
descompunerii).

20
Complexitatea algoritmilor. Algoritmi de sortare

1. Complexitatea algoritmilor
- Calcul timp de execuție pentru sortarea prin inserție
2. Algoritmi de sortare
1. Sortarea Shell
2. Sortarea prin interclasare
3. Sortarea Heap
4. Sortarea rapidă
5. Sortarea prin numărare

1. Complexitatea algoritmilor
- Exemplu - Calcul timp de execuție pentru sortarea prin inserție
Obs:
 Timpul total de execuție este egal cu numărul prelucrărilor elementare executate.
 Termenul dominant este termenul care devine semnificativ mai mare decât ceilalți atunci
când dimensiunea problemei crește (dictează comportarea algoritmului când dimensiunea
problemei crește).
 Ordinul de creștere caracterizează creșterea termenului dominant al timpului de execuție
în raport cu dimensiunea problemei.
Pseudocod:
do-for j=2,n,1

{ aux=x[j];

i=j-1;

while i≥1 and x[i]>aux do

{ x[i+1]=x[i];

i=i-1

}endwhile

X[i+1]=aux

}enddo

21
Operație Cost Nr. repetări Caz favorabil Caz nefavorabil
1 C1 n n n
2 C2 n-1 n-1 n-1
3 C3 n-1 n-1 n-1
𝑛 𝑛 𝑛
4 C4 𝑛(𝑛 + 1)
1=n-1
𝑡𝑗 𝑗=2 𝑗= ―1
𝑗=2
2
𝑗=2
𝑛 𝑛
5 C5 0 𝑛(𝑛 ― 1)
(𝑡𝑗 ― 1) 𝑗=
𝑗=2
2
𝑗=2
𝑛 𝑛
6 C6 0 𝑛(𝑛 ― 1)
(𝑡𝑗 ― 1) 𝑗=
𝑗=2
2
𝑗=2
7 C7 n-1 n-1 n-1
T(n) (c1+c2+c3+c4+c7)n- ( c4/2 + c5/2 + c6/2 )n2
(c2+c3-c4-c7) +(c1+c2+c3+ c4/2 − c5/2
− c6/2
+c7)n−(c2+c3+c4+c7)

O(n) O(n2)

Cazul cel mai favorabil: tabloul este sortat.

tj = 1, j = 2, . . . , n

T(n) = (c1 + c2 + c3 + c4 + c7)n − (c2 + c3 + c4 + c7)

Cazul cel mai nefavorabil: tabloul este sortat în ordine inversă.

tj = j, j = 2, . . . , n

T(n) = c1n+c2(n-1)+c3(n-1)+c4(n(n+1)/2-1)+c5n(n-1)/2+c6n(n-1)/2+c7(n-1)=

=( c4/2 + c5/2 + c6/2 )n2 +(c1+c2+c3+ c4/2 − c5/2 − c6/2 +c7)n−(c2+c3+c4+c7)

2. Algoritmi de sortare
2.1. Sortarea Shell (cu micsorarea incrementului)

#include<stdio.h>
void sort_shell(double v[], int l)
{ int i,j,inc;
double a;
for(inc=l/2;inc>0;inc=inc/2)

22
for(i=inc;i<l;i++)
for(j=i-inc;(j>=0)&&(v[j]>v[j+inc]);j=j-inc)
{ a=v[j];
v[j]=v[j+inc];
v[j+inc]=a;
}
}

void main()
{ int n,i; double v[1000];
printf("n="); scanf("%i",&n);
for(i=0;i<n;i++)
{
printf("v[%d]=",i);
scanf("%lf",&v[i]);
}
sort_shell(v,n);
printf("Vectorul sortat\n");
for(i=0;i<n;i++) printf("%8.4lf ",v[i]);
}

2.2. Sortarea prin interclasare

#include<stdio.h>
void interclasare(float v[], int ls, int m, int ld)
{ int i, j, k; float v1[100];
for(i=ls, j=m+1, k=0; i<=m && j<=ld;k++)
v1[k]= (v[i]<v[j])?v[i++]:v[j++];
while(i<=m)v1[k++]=v[i++];
while(j<=ld)v1[k++]=v[j++];
for(i=0;i<k;i++)v[ls+i]=v1[i]; //for(i=ls;i<=ld;i++) v[i]=v1[i-ls];
}

void sortare(float v[], int ls, int ld)


{ int m;
if(ld-ls>0)
{ m=(ld+ls)/2;
sortare(v, ls, m);
sortare(v, m+1, ld);
interclasare(v, ls, m, ld);
}
}
void main()
{ int n,i; float x[100];

23
printf("n="); scanf("%d", &n);
for(i=0;i<n;i++)
{printf("x[%d]=", i);scanf("%f", &x[i]);}
sortare(x, 0, n-1);
for (i=0;i<n;i++) printf("%3.1f ", x[i]);
}

2.3. Sortarea Heap


#include<stdio.h>
int stanga(int i)
{return 2*i+1;}
int dreapta(int i)
{return 2*i+2;}

void aranjeaza_heap(double *H, int i, int n)


{ int s,d,max;
s=stanga(i);
d=dreapta(i);
if ((s<n)&&(H[s]>H[i]))max=s;
else max=i;
if ((d<n)&&(H[d]>H[max]))
max=d;
if(max!=i){ double aux=H[i];
H[i]=H[max];
H[max]=aux;
aranjeaza_heap(H,max,n);
}
}
void construieste_heap (double *H, int n)
{ int i;
for (i=n/2;i>=0;i--)

24
aranjeaza_heap(H,i,n);
}

void heap_sort(double *H, int n)


{ int i;
construieste_heap(H,n);
for(i=n-1;i>=1;i--){
double aux=H[i];
H[i]=H[0];
H[0]=aux;
aranjeaza_heap(H,0,i);
}
}

void main()
{ int n,i; double x[100];
printf("n="); scanf("%d", &n);
for(i=0;i<n;i++)
{printf("x[%d]=", i);scanf("%lf", &x[i]);}
heap_sort(x,n);
for (i=0;i<n;i++) printf("%3.1lf ", x[i]);
}

2.4. Sortarea rapida

#include<stdio.h>

25
int poz(float *x,int p,int u)
{ int i,j,l,di,dj;
float v;
//di, dj: pasii de incrementare pentru i si j; ei indica sensul parcurgerii
i=p;j=u;di=0;dj=-1;
while(i<j)
if (x[i]>x[j])
{ v=x[i];x[i]=x[j];x[j]=v;
l=di;di=-dj;dj=-l;
i+=di;j+=dj;
}
else
{ i+=di;j+=dj;
}
return i;
}

void quick(float *x,int p,int u)


{ int i;
if (p<u)
{ i=poz(x,p,u);
quick(x,p,i-1);
quick(x,i+1,u);
}
}

void main()
{ int n,i; float v[1000];
printf("Dimensiunea vectorului:"); scanf("%i",&n);
printf("Elementele vectorului\n");
for(i=0;i<n;i++)
{
printf("v[%d]=",i);
scanf("%f",&v[i]);
}
quick(v,0,n-1);
printf("Vectorul sortat\n");
for(i=0;i<n;i++) printf("%8.4f ",v[i]);
}

2.5. Sortarea prin numarare

#include<stdio.h>
#include<malloc.h>
void sort_numarare(double v[], int l)

26
{ int i,j, *num; double *temp;
temp=(double*)malloc(l*sizeof(double));
num=(int*)malloc(l*sizeof(int));

for(i=0;i<l;i++) num[i]=0;
for(i=0;i<l-1;i++)
for(j=i+1;j<l;j++)
if(v[j]<v[i]) num[i]++;
else num[j]++;

for(i=0;i<l;i++) temp[num[i]]=v[i];

for(i=0;i<l;i++) v[i]=temp[i];

free(temp); free(num);
}

void main()
{ int n,i; double x[100];
printf("n="); scanf("%d", &n);
for(i=0;i<n;i++)
{printf("x[%d]=", i);scanf("%lf", &x[i]);}
sort_numarare(x,n);
for (i=0;i<n;i++) printf("%3.1lf ", x[i]);
}

27
Exemple la metoda backtracking

Probleme propuse:

1. Să se genereze toate permutările unei mulțimi cu 𝒏 elemente.


2. Să se genereze toate aranjamentele dintr-o mulțime cu 𝒏 elemente, luate cîte 𝒌.
3. Să se genereze toate combinările dintr-o mulțime cu 𝒏 elemente, luate cîte 𝒌.
4. Problema celor 8 (n) regine. Se cere să se așeze 8 regine pe o tablă de șah astfel încît să nu existe
două regine care să se atace. Trebuie găsite toate posibilitățile de așezare a celor 8 regine pe
tabla de șah.
5. Plata unei sume (cu/fără bancnotă unitate). Fie 𝒏 tipuri de bancnote, cu valorile nominale 𝒕𝒊
, 𝒊 = 𝟏,𝒏. Din fiecare tip este disponibilă cantitatea 𝒏𝒓𝒊, 𝒊 = 𝟏,𝒏. Să se determine toate
modalitățile în care se poate plăti o sumă 𝑺 folosind aceste bancnote.

Rezolvări:

1. Să se genereze toate permutările unei mulțimi cu 𝒏 elemente.

Problema generării permutărilor unei mulțimi se exprimă foarte ușor în termenii metodei backtracking,
dacă se reprezintă mulțimea liniar și se asociază fiecărui element o valoare numerică, în funcție de
poziția ocupată. În continuare fiecare element va fi reprezentat de o valoare numerică 𝑥𝑖, 𝑖 = 1,𝑛 cu
semnificația următoare: 𝑥𝑖 este poziția elementului 𝑖 în permutare; pentru mulțimea inițială 𝑥𝑖 = 𝑖. Cu
aceste considerații, spațiul soluțiilor este definit prin intermediul mulțimilor identice 𝑆𝑖, 𝑥𝑖 ∈ 𝑆𝑖 =
{1, 2,…, 𝑛}, 𝑖 = 1,𝑛 . Aceste mulțimi pot fi exprimate ca progresii aritmetice cu elementul inițial 𝑎𝑖 = 1,
rația 𝑟𝑖 = 1 și ultimul element 𝑠𝑖 = 𝑛.

Deoarece într-o permutare nu pot fi două elemente pe aceeași poziție, valoarea atribuită unui element
𝑥𝑖 este acceptabilă dacă este diferită de valorile atribuite elementelor anteriore din soluția parțială
construită: 𝑥𝑖 ≠ 𝑥𝑗, 𝑗 = 1,𝑖 ― 1. Condiția este suficientă, astfel încît nu e necesară verificarea vreunei
condiții suplimentare după construirea unei soluții. La găsirea unei soluții (permutări) aceasta este
numărată (și afișată pe ecran, în exemplul următor de implementare).

#include <stdio.h>

#include<malloc.h>

// Verifica daca valoarea elementului i este acceptabila

int posibil( int* v, int i)

{ int j, r; r=1;

for(j=0;j<i;j++) //for sau while? Eficienta?

if(v[i]==v[j]) r=0;

28
return r;

// Afisare solutie pe ecran

// I: numarul solutiei (num), dimensiunea permutarii (nr), permutarea (v)

void retine_solutia(int num, int nr, int* v)

{ int i;

printf("\nSolutia numarul %d:",num);

for(i=0; i<nr; i++) printf("%2d ",v[i]);

// Genereaza permutari de n elemente (1..n)

// I: n

// E: numar permutari

int permutari(int n)

{ int* p,i,am,nr;

p=(int *)malloc(n*sizeof(int)); //vectorul solutie

nr=0; //numar solutii

i=0;

p[0]=0; //prima valoare minus ratia

while(i>=0) //cit timp nu am ajuns la configuratia finala

{ am=0;

while( p[i]<n && !am) //alege urmatoarea valoare acceptabila pentru x[i]

{ p[i]++; //urmatoarea valoare pentru x[i]

am=posibil(p,i); //este acceptabila?

if(!am) //impas, revenire

i--;

else

if( i==n-1 ) //configuratie solutie

retine_solutia(++nr,n,p);

29
else

p[++i]=0; //prima valoare minus ratia

free(p); return nr;

//Permutari recursiv

//I: dimensiune (n), pas curent (i), sol. partiala curenta (x), nr. crt. sol. (nr)

//E: numar solutii (nr)

int permutari_r(int n, int i, int* x, int nr)

{ int j;

if( i==n)

retine_solutia(++nr,n,x);

else

for(j=1; j<=n; j++ )

{ x[i]=j;

if( posibil(x,i) )

nr=permutari_r(n,i+1,x,nr);

return nr;

void main(){

int n,nr;

printf("n="); scanf("%d", &n);

int* x=(int *)malloc(n*sizeof(int));

//nr=0; nr=permutari_r(n,0,x,0); //apel pentru varianta recursiva

nr=permutari(n); //apel pentru varianta iterativa

printf("\nNumarul total de permutari este: %d", nr);

free(x);

30
2. Să se genereze toate aranjamentele dintr-o mulțime cu 𝒏 elemente, luate cîte 𝒌.

#include <stdio.h>

#include<malloc.h>

// Verifica daca valoarea elementului i este acceptabila

// (diferita de cele anterioare)

int posibil( int* v, int i)

{ int j, r;

r=1;

j=0;

while(j<i && v[i]!=v[j])j++;

if(j<i)r=0;

else r=1;

return r;

// Afisare solutie pe ecran

// I: numarul solutiei (num), dimensiunea (k), solutia (v)

void retine_solutia(int num, int k, int* v)

{ int i;

printf("\nSolutia numarul %d: ",num);

for(i=0; i<k; i++)

printf("%2d ",v[i]);

31
}

//I: dimensiune (n), numarul maxim k, pas curent (i),

//sol. partiala curenta (x), nr. crt. sol. (nr)

//E: numar solutii (nr)

int aranjamente(int n, int k,int i, int* x, int nr)

{ int j;

if( i==k)

retine_solutia(++nr,k,x);

else

for(j=1; j<=n; j++ )

{ x[i]=j;

if( posibil(x,i) )

nr=aranjamente(n,k, i+1,x,nr);

return nr;

void main()

{ int n, k, nr;

printf("n="); scanf("%d", &n);

printf("k="); scanf("%d", &k);

int* x=(int*)malloc(n*sizeof(int));

if(k>0 && k<=n) {nr=aranjamente(n,k,0,x,0); printf("\nNumarul de aranjamente este: %d\n",nr);}

else printf ("Nu exista solutii");

free(x); }

32
3. Să se genereze toate combinările dintr-o mulțime cu 𝒏 elemente, luate cîte 𝒌.

#include <stdio.h>

#include<malloc.h>

void init(int *n, int *k, int **x)

{ printf ("n="); scanf("%d", n);

printf ("k="); scanf("%d", k);

*x=(int*) malloc (*n*sizeof(int));

for(int i=0;i<*n;i++)

*(*x+i)=0;

//afiseaza solutia

void afiseaza(int k, int *x)

{for(int i=0;i<k;i++)

printf("%d ", x[i]);

printf("\n");

//verifica daca poate fi adaugat numarul la solutie

int posibil(int p, int *x)

{ int r=1;

if(p>0 && x[p]<=x[p-1])

r=0;

return r;
33
}

//subprogramul recursiv de generare combinari de n luate cite k

void combinari(int p, int n, int k, int *x)

{ int pval;

for(pval=1;pval<=n;pval++)

{x[p]=pval;

if(posibil(p,x))

{if(p==k-1)

afiseaza(k,x);

else

combinari(p+1, n,k,x);

void main()

{ int n, k, *x;

init(&n,&k,&x);

if(k>0 && k<=n) combinari(0,n,k,x);

else printf ("Nu exista solutii");

free(x);

4. Problema celor 8 (n) regine. Se cere să se așeze 8 regine pe o tablă de șah astfel încît să nu existe două
regine care să se atace. Trebuie găsite toate posibilitățile de așezare a celor 8 regine pe tabla de șah.

34
Problema se poate extinde la 𝑛 regine, pe o tablă de dimensiuni 𝑛 × 𝑛, 𝑛 > 2, de aceea în continuare se
va folosi 𝑛 ca dimensiune a problemei.

Considerând liniile și coloanele tablei de șah numerotate de la 1 la 𝑛 (8), începînd din colțul din stînga
sus, poziția unei regine pe tabla de șah este determinată de o pereche de coordonate de forma 𝑃𝑖 = (
𝑙𝑖𝑛𝑖𝑎𝑖, 𝑐𝑜𝑙𝑜𝑎𝑛𝑎𝑖), 𝑖 = 1,𝑛, deci soluția problemei este o mulțime cu 𝑛 astfel de poziții. Ținînd cont că
reginele nu trebuie să se atace, rezultă că pe fiecare linie trebuie să se afle o singură regină și numai una.
Ca urmare, în mulțimea soluție va exista câte un element și numai unul cu linia 1, respectiv linia 2 etc.
adică 𝑙𝑖𝑛𝑖𝑎𝑖 = 𝑖, 𝑖 = 1,𝑛 . Putem considera mulțimea soluție ca o mulțime ordonată, cu 𝑛 elemente de
forma (𝑖,𝑐𝑜𝑙𝑜𝑎𝑛𝑎𝑖), 𝑖 = 1,𝑛. Pentru reprezentarea acestei mulțimi este necesară doar reținerea
coordonatei coloană. În aceste condiții, putem exprima soluția problemei ca o mulțime 𝑋 = {𝑥𝑖| 𝑖 = 1,𝑛},
unde 𝑥𝑖 este coloana pe care se află regina de pe linia 𝑖. Altfel spus, poziția reginei 𝑖 este (𝑖,𝑥𝑖), 𝑖 = 1,𝑛.

Deoarece o regină se poate afla pe una din cele 𝑛 coloane ale tablei de șah, rezultă că elementele 𝑥𝑖 pot
lua valori din mulțimea 𝑆𝑖 = {1, 2, …, 𝑛}, 𝑖 = 1,𝑛. Mulțimile 𝑆𝑖 se pot exprima ca progresii aritmetice cu
elementul inițial 𝑎𝑖 = 1, rația 𝑟𝑖 = 1 și ultimul element 𝑠𝑖 = 𝑛.

Pentru a evita confuziile legate de utilizarea indicilor masivelor în C, se poate folosi un vector cu un
element în plus (lungime 𝑛 + 1), în care elementul cu indicele 0 rămîne nefolosit (risipa de resurse este
insignifiantă în acest caz).

 posibil – poziția aleasă pentru regina curentă, (𝑖,𝑥𝑖), este acceptabilă dacă nu este atacată de nici una
din reginele anterior plasate pe tablă. Această poziție nu trebuie să se afle pe aceeași linie sau coloană
cu una din reginele anterioare și nici pe diagonală cu vreuna din ele. Deoarece fiecare regină e plasată
pe o linie nouă, cu siguranță prima parte a condiției este îndeplinită. Pentru a verifica dacă nu se află
pe aceeași coloană cu o regină plasată anterior, se compară coloana curentă 𝑥𝑖 cu coloanele atribuite
reginelor anterioare 𝑥𝑗, 𝑗 = 1,𝑖 ― 1. Se acceptă valoarea curentă dacă 𝑥𝑖 ≠ 𝑥𝑗, 𝑗 = 1,𝑖 ― 1. Două regine
se află pe diagonală dacă distanțele pe verticală și orizontală între ele sînt egale; se acceptă valoarea
curentă dacă |𝑖 ― 𝑗| ≠ |𝑥𝑖 ― 𝑥𝑗|, 𝑗 = 1,𝑖 ― 1.

#define _CRT_SECURE_NO_WARNINGS

#include <stdio.h>

#include<malloc.h>

// Conditia de continuare: reginele i si j nu se ataca daca nu sint pe aceeasi coloana

// - distantele pe verticala si orizontala sint diferite

// I: solutia partiala (x), numarul de elemente (i)

// E: 1 daca e acceptabila, 0 daca nu e acceptabila

35
int posibil(int *x, int i)

{ int j, p;

p=1;

for( j=1; j<i; j++)

if( x[i]==x[j] || abs(i-j)==abs(x[i]-x[j]) )

p=0;

return p;

// Retine o configuratie solutie (afisare)

// I: nr. solutie (nr), nr dame (n), vector solutie

// E: -

void retine_solutia(int nr, int n, int* x)

{ int i,j;

printf("\n Solutia numarul %d\n",nr);

for(i=1; i<=n; i++)

{ for(j=1; j<=n; j++)

printf("%c ",j==x[i]?'R':'x');

printf("\n");

// I: numar regine/dimensiune tabla (n)

// E: numar solutii

int regine(int n)

{ int nr; int* x; int i, am;

x= (int*) malloc ((n+1)* sizeof(int)); //vectorul solutie

nr=0; //numar solutii

36
i=1;

x[1]=0; //prima valoare minus ratia

while(i>0) //cit timp nu am ajuns la configuratia finala

{ am=0;

while( x[i]<n && !am) //alege urmatoarea valoare acceptabila pentru x[i]

{ x[i]++; //urmatoarea valoare pentru x[i]

am=posibil(x,i); //este acceptabila?

if(!am) i--;

else

if( i==n ) retine_solutia(++nr,n,x);

else

x[++i]=0; //prima valoare minus ratia

} free(x);

return nr;

// I: numar dame (n), element curent (i), vector solutie (x),

// numar solutii (nr)

// E: nr. solutii

int regine_r(int n, int i, int* x, int nr)

{ int j;

if( i==n+1)

retine_solutia(++nr,n,x);

else

for(j=1; j<=n; j++ )

{ x[i]=j;

if( posibil(x,i) )

nr=regine_r(n,i+1,x,nr); }

return nr; }

37
void main(){

int n;

printf("n="); scanf("%d", &n);

int* x=(int*)malloc ((n+1)*sizeof(int));

int nr=0;

printf("\n\nNumarul de solutii este %d ", regine_r(n,1,x,nr));

//printf("\n\nNumarul de solutii este %d ", regine(n));

free(x);

5. Plata unei sume (cu/fără bancnotă unitate). Fie 𝒏 tipuri de bancnote, cu valorile nominale 𝒕𝒊, 𝒊 = 𝟏,𝒏.
Din fiecare tip este disponibilă cantitatea 𝒏𝒓𝒊, 𝒊 = 𝟏,𝒏. Să se determine toate modalitățile în care se
poate plăti o sumă 𝑺 folosind aceste bancnote.

Pentru rezolvare, se reprezintă tipurile de bancnote disponibile într-un vector (prin valorile lor nominale)
iar în alt vector cantitățile disponibile din tipurile respective (asigurând corespondența tip-cantitate prin
folosirea aceleiași poziții în cei doi vectori). Soluția problemei poate fi exprimată sub forma unui vector
𝑋 în care elementul 𝑥𝑖 indică numărul de bancnote de tipul 𝑡𝑖 utilizate: 𝑥𝑖 ∈ 𝑆𝑖 = {0,1,2,…,𝑛𝑟𝑖}, 𝑖 = 1,𝑛.
Mulțimile 𝑆𝑖 se pot exprima ca progresii aritmetice cu elementul inițial 𝑎𝑖 = 0, rația 𝑟𝑖 = 1 și ultimul
element 𝑠𝑖 = 𝑛𝑟𝑖.

38
O valoare atribuită elementului 𝑥𝑖 este acceptabilă dacă prin adăugarea bancnotelor respective (cu
valoarea 𝑥𝑖 ∗ 𝑡𝑖) la bancnotele anterior alese nu se depășește suma de plată. Această condiție nu este
suficientă; după ce se aleg și acceptă valori pentru toate elementele 𝑥𝑖, valoarea tuturor bancnotelor
𝑛
selectate (∑𝑖=1 𝑥𝑖 ∗ 𝑡𝑖) trebuie să fie egală cu suma de plată.

Pentru a evita calcularea acestei sume la fiecare pas, se poate păstra într-o variabilă suma curentă
plătită, care se ajustează de fiecare dată când se atribuie o valoare nouă unui element al soluției. Inițial
aceasta este zero. Algoritmul funcționează și dacă între tipurile de bancnote disponibile nu se află și
bancnota unitate.

#define _CRT_SECURE_NO_WARNINGS

#include <stdio.h>

#include<malloc.h>

//I: solutia (x), pasul curent (i), numarul de tipuri (n), suma de plata (s)

// suma curenta (c)

//E: 1 daca valoarea este acceptabila, 0 daca nu e acceptabila

int posibil(int* x, int i, int n, int s, int c)

{ int rez;

if(i==n-1)

rez=(s==c)?1:0;

else

rez=(s>=c)?1:0;

return rez;

//I: tipuri (t), solutia (x), numarul de tipuri (n), numarul de solutii (ns)

void retine(int* t, int* x, int n, int ns)

{ int i,s; s=0;

39
printf("\n\nSolutia numarul %d", ns);

for(i=0;i<n;i++) printf("\n %3d * %2d = %4d, suma=%4d",t[i],x[i],t[i]*x[i],s+=t[i]*x[i]);

//Plata unei sume

//I: suma, suma curenta (crt), tipuri (t), cantitati (nr), numar de tipuri (n)

// numarul de solutii gasite (ns), pasul curent (i), solutia (x)

//E: numarul de solutii

int plata(int suma, int crt, int* t, int* nr, int n, int ns, int i, int* x)

{ int j;

if(i==n )

retine(t, x, n, ++ns);

else

{ for(j=0; j<=nr[i]; j++)

{ x[i]=j;

crt+=t[i]*j;

if( posibil(x, i, n, suma,crt) )

ns=plata(suma, crt, t, nr, n, ns, i+1, x);

crt-=t[i]*j;

return ns;

void main(){

int i,n, x[20], suma;

// int t[20]={500, 200, 100, 50, 10, 5, 1};

//int nr[20]={3, 6, 2, 5, 4, 3, 7};

//n=7;

40
int t[20],nr[20];

printf("introduceti numarul de bacnote disponibile:"); scanf("%d", &n);

printf("Introduceti tipurile de bacnote si numarul de bacnote din fiecare tip\n");

for(i=0;i<n;i++)

printf("t[%d]=",i+1); scanf("%d", &t[i]);

printf("nr[%d]=",i+1); scanf("%d", &nr[i]);

printf("Introduceti suma de plata:"); scanf("%d",&suma);

printf("\nNumarul de solutii este %d:", plata(suma,0,t,nr,n,0,0,x));

41
Lucru cu fișiere text: exemple

Enunțuri:

1. Să se scrie un subprogram pentru crearea unui fișier text care să conțină elemente reale (în fișier sunt memorate
elementele unui vector – un element pe fiecare linie). Se va oferi și un exemplu de utilizare.
2. Să se scrie un subprogram pentru preluarea vectorului în memorie. Se va scrie și programul apelator.
3. Să se scrie programul care memorează într-un fişier text date despre studenţii unei facultăţi. Pe fiecare linie se
memorează: numărul matricol, numele, prenumele, anul, grupa, cele 10 note obţinute la examene. Datele sunt separate
prin cîte un spaţiu.
4. Să se scrie programul care calculează media studenţilor integralişti şi cea mai mare dintre mediile studenţilor
integralişti din fişierul creat anterior.

Rezolvări:

1. Să se scrie un subprogram pentru crearea unui fișier text care să conțină elemente reale (în fișier sunt memorate
elementele unui vector – un element pe fiecare linie). Se va oferi și un exemplu de utilizare.

#define _CRT_SECURE_NO_WARNINGS //în cazul în care apar avertismente legate de securitate


#include <stdio.h>
void scriere_vector(char *nume){
FILE *f; float x;
f=fopen(nume,"w");
if(!f)printf("nu se poate");
else{
printf("x=");
scanf("%f", &x);
while(!feof(stdin)){
fprintf(f, "%4.2f\n", x);
printf("x=");
scanf("%f", &x);}
fclose(f);
}
}

void main()
{
char numef[20];
printf("Introduceti numele fisierului:");
scanf("%s", numef);
scriere_vector(numef);
}

42
2. Să se scrie un subprogram pentru preluarea vectorului în memorie. Se va scrie și programul apelator.

#define _CRT_SECURE_NO_WARNINGS //în cazul în care apar avertismente de securitate


#include<stdio.h>
#include<conio.h>
//in fisier sunt memorate elementele vectorului
void preluare_vector_1(char * nume, float v[], int* n)
{ float x; FILE* f;
f=fopen(nume,"r"); //deschide fisier existent si se permite citirea
if(f)
{ *n=0;
fscanf(f,"%f",&x); //citeste un element real din fisier
while(!feof(f))
{ v[(*n)++]=x;
fscanf(f,"%f",&x); }
fclose(f);
}
else printf("nu se poate");
}

void main()
{
float a[100]; int n;
char numef[20];
printf("Introduceti numele fisierului:");
scanf("%s",numef);
preluare_vector_1(numef, a, &n); //fișierul [Link] de la exemplul 1
for (int i=0;i<n;i++) printf("%4.1f ,", a[i]);
_getch();
}

3. Să se scrie programul care memorează într-un fişier text date despre studenţii unei facultăţi. Pe fiecare linie se
memorează: numărul matricol, numele, prenumele, anul, grupa, cele 10 note obţinute la examene. Datele sunt
separate prin câte un spaţiu. Acolo unde nu se cunoaşte încă nota se va introduce valoarea 0.

#define _CRT_SECURE_NO_WARNINGS //în cazul în care apar avertismente de securitate


#include<stdio.h>
void main()
{FILE *f;
char nume[30],s[20];
int nr,an,gr,n[10],i;
printf("Nume fisier: ");
gets(s);

43
f=fopen(s,"w");
printf("Nr. matricol: "); scanf("%d",&nr);
while(!feof(stdin))
{fflush(stdin); //getchar();
printf("Nume si prenume: ");gets(nume);
printf("An: ");scanf("%d",&an);
printf("Grupa: ");scanf("%d",&gr);
fprintf(f,"%4d %30s %2d %2d ",nr,nume,an,gr);
for(i=0;i<10;i++)
{printf("nota %d: ",i+1); scanf("%d",&n[i]);
fprintf(f,"%2d ",n[i]);}
fprintf(f,"\n");
printf("Nr. matricol: "); scanf("%d",&nr);}
fclose(f);}

4. Să se scrie programul care calculează media studenţilor integralişti şi cea mai mare dintre mediile studenţilor
integralişti din fişierul creat anterior.

#include<stdio.h>
void main()
{FILE *f;
char nume[30],s[200]; int nr,an,gr,n[10],i,e,p; float m,max,m1;
printf("Nume fisier: "); gets(s);
if(!(f=fopen(s,"r")))printf("Nu se poate deschide fisierul.");
else {p=0;max=0;m1=0;
fscanf(f,"%d",&nr);
while(!feof(f))
{fscanf(f,"%s %d %d",nume,&an,&gr);
e=1;m=0;
for(i=0;i<10;i++)
{fscanf(f,"%d",&n[i]);
if(n[i]<5)e=0; m+=n[i];}
if(e){m/=10; p++; m1+=m;
if(m>max)max=m;}
44
fscanf(f,"%d",&nr);}
if (p==0) printf ("Nu exista niciun student integralist");
else
{printf("\nmedia: %5.2f",m1/p); printf("\nmedia maxima: %5.2f", max);}
}
fclose(f);}

45
Lucrul cu fişiere organizate secvenţial: exemple
(inclusiv problema cu grade de total)

Enunțuri:

1. Să se scrie programul care creează un fişier secvenţial cu date despre studenţii unei facultăţi. Articolele au
următoarea structură: număr matricol, nume, anul, grupa, numărul de note, notele (maxim 15). Datele se preiau
de la tastatură, sfârşitul introducerii fiind marcat standard. (expl. de creare)
2. Să se scrie programul care afişează datele despre studenţii ale căror numere matricole se introduc de la tastatură.
Sfârşitul introducerii este marcat standard. ([Link] consultare un articol)
3. Să se scrie programul care listează, în fişiere text, situaţia studenţilor din grupele ale căror numere se introduc de
la tastatură. Sfârşitul introducerii este marcat standard. (expl. de consultare cu selecție)
4. Să se scrie programul care listează, într-un fişier text, sub formă de tabel, conţinutul fişierului creat la problema
anterioara. (expl. de consultare integrală)
5. Să se scrie programul pentru adăugarea notei la disciplina algoritmi și tehnici de programare pentru studenul al
cărui număr matricol este introdus de la tastatură. (expl. de modificare articol unic)
6. Să se scrie programul pentru adăugarea punctului din oficiu la nota la ATP pentru studenţii din grupa al cărei
număr este introdus de la tastatură. (expl. de modificare cu selecție)
7. Să se scrie programul care listează, într-un fişier text, studenţii integralişti, pe ani şi grupe, calculând media
fiecărei grupe şi a fiecărui an. (expl. de problemă cu grade de total)

Alte exemple de probleme – lucru suplimentar:


1) Să se scrie programul care realizează adăugarea în fișierul binar creat la problema 1
2) Să se scrie programul care realizează o modificare integrală a articolelor din fișierul binar
3) Să se scrie programul care realizează ștergerea unor articole din fișierul secvențial (simulare: copierea tuturor
articolelor care nu trebuie şterse în alt fişier; ştergerea fişierului iniţial; redenumirea celui nou)

46
Rezolvări:

1. Să se scrie programul care creează un fişier secvenţial cu date despre studenţii unei facultăţi. Articolele au
următoarea structură: număr matricol, nume, anul, grupa, numărul de note, notele (maxim 15). Datele se preiau
de la tastatură, sfârşitul introducerii fiind marcat standard. Acolo unde nu se cunoaşte încă nota se va introduce
valoarea 0. (Numele fisierului binar [Link])

Observație: Pentru recunoașterea funcției gets în Visual Studio versiunile mai noi -> se va schimba extensia
fișierului sursă. Noua extensie va fi .c (in loc de .cpp)

#define _CRT_SECURE_NO_WARNINGS //în cazul în care apar avertismente de securitate


#include<stdio.h>
typedef struct{int nr;
char nume[30];
int an;
int grupa;
int n;
int note[15];
}Student;

void main()
{FILE *f;
char s1[20];
Student s;
int i;
printf("\nFisier: ");gets(s1);
f=fopen(s1,"wb");
printf("[Link]: ");scanf("%d",&[Link]);
while(!feof(stdin))
{printf("Nume: ");
getchar();
gets([Link]);
printf("An: ");scanf("%d",&[Link]);
printf("Grupa: ");scanf("%d",&[Link]);
printf("[Link]:(<15)");scanf("%d",&s.n);
for(i=0;i<s.n;i++)
{printf("Nota %d: ",i+1);
scanf("%d",&[Link][i]);}
fwrite(&s,sizeof(Student),1,f);
printf("[Link]: ");scanf("%d",&[Link]);
}
fclose(f);}

2. Să se scrie programul care afişează datele despre studenţii ale căror numere matricole se introduc de la
tastatură. Sfârşitul introducerii este marcat standard.

#define _CRT_SECURE_NO_WARNINGS //în cazul în care apar avertismente de securitate


#include<stdio.h>

typedef struct{int nr;


47
char nume[30];
int an;
int grupa;
int n;
int note[15];
}Student;

void main()
{FILE *f;
char s1[20];
Student s;
int i,n,j;
printf("\nFisier: ");gets(s1);
f=fopen(s1,"rb");
if(!f)printf("\nFisierul %s nu poate fi deschis", s1);
else{printf("\nNr. matricol: ");
scanf("%d",&n);
while(!feof(stdin))
{rewind(f);
fread(&s,sizeof(Student),1,f);
i=0;
while((!feof(f))&&(!i))
{if(n==[Link])
{i=1;
printf("\[Link]:%3d Nume: %-30s An: %2d Grupa: %4d\nNote: ",[Link],[Link],[Link],[Link]);
for(j=0;j<s.n;j++)
printf("%2d ",[Link][j]);}
fread(&s,sizeof(Student),1,f);}
if(!i)printf("\nNu a fost gasit.");
printf("\nNr. matricol: ");
scanf("%d",&n);}
fclose(f);}
}

3. Să se scrie programul care listează, în fişiere text, situaţia studenţilor din grupele ale căror numere se introduc
de la tastatură. Sfârşitul introducerii este marcat standard.

#define _CRT_SECURE_NO_WARNINGS //în cazul în care apar avertismente de securitate


#include<stdio.h>

typedef struct{int nr;


char nume[30];
int an;
int grupa;
int n;
int note[15];
}Student;

void main()
{FILE *f,*g;
char s1[20];
Student s;
int i,n,j;

48
printf("\nFisier: ");gets(s1);
f=fopen(s1,"rb");
if(!f)printf("\nFisierul %s nu poate fi deschis",s1);
else{printf("\nNr. grupei: ");

scanf("%d",&n); //grupa
while(!feof(stdin))
{rewind(f);
getchar();
printf("\nFisier rezultat: ");gets(s1);
g=fopen(s1,"w");

fread(&s,sizeof(Student),1,f);
i=0;
while(!feof(f))
{if(n==[Link])
{i=1;
fprintf(g,"\[Link]:%3d Nume: %-30s An: %2d Grupa: %4d\nNote: ",[Link],[Link],[Link],[Link]);
for(j=0;j<s.n;j++)
fprintf(g,"%2d ",[Link][j]);}
fread(&s,sizeof(Student),1,f);}
if(!i)printf("\nNu a fost gasita.");
printf("\nNr. grupei: ");
scanf("%d",&n);
fclose(g);}
fclose(f);}
}

4. Să se scrie programul care listează, într-un fişier text, sub formă de tabel, conţinutul fişierului creat la problema
1.

#define _CRT_SECURE_NO_WARNINGS //în cazul în care apar avertismente de securitate


#include<stdio.h>

typedef struct{int nr;


char nume[30];
int an;
int grupa;
int n;
int note[15];
}Student;

void main()
{FILE *f,*g;
char s1[20];
Student s;
int i,n;
printf("\nNumele fisierului binar: ");gets(s1);
f=fopen(s1,"rb");
if(!f)printf("\nFisierul %s nu poate fi deschis",s1);
else{printf("\nFisier rezultat (text): ");gets(s1);
g=fopen(s1,"w");
49
fprintf(g,"\nNr. Nume %25s An Grupa Note"," ");
fread(&s,sizeof(Student),1,f);
n=0;
while(!feof(f))
{fprintf(g,"\n%3d %-30s %2d %4d ",++n,[Link],[Link],[Link]);
for(i=0;i<s.n;i++)
fprintf(g,"%2d ",[Link][i]);
fread(&s,sizeof(Student),1,f);
}
fclose(g);
fclose(f);}
}

5. Să se scrie programul pentru adăugarea notei la disciplina algoritmi și tehnici de programare pentru studenul al
cărui număr matricol este introdus de la tastatură. Nota la disciplina ATP se găseste pe poziția zero în vectorul de
note.

#define _CRT_SECURE_NO_WARNINGS //în cazul în care apar avertismente de securitate


#include<stdio.h>

typedef struct{int nr;


char nume[30];
int an;
int grupa;
int n;
int note[15];
}Student;

void main()
{FILE *f;
char s1[20];
Student s;
int i,n,j;
printf("\nFisier: ");gets(s1);
f=fopen(s1,"rb+");
if(!f)printf("\nFisierul %s nu poate fi deschis", s1);
else{printf("\nNr. matricol: ");

scanf("%d",&n);
while(!feof(stdin))
{rewind(f);
fread(&s,sizeof(Student),1,f);
i=0;
while((!feof(f))&&(!i))
{if(n==[Link])
{i=1;
printf("\[Link]:%3d Nume: %-30s An: %2d Grupa: %4d\nNote: ",
[Link],[Link],[Link],[Link]);
for(j=0;j<s.n;j++)
printf("%2d ",[Link][j]);
printf("Introduceti nota la ATP");
50
scanf ("%d", &[Link][0]);
fseek(f, ftell(f)-sizeof(Student),0);
fwrite(&s, sizeof(Student), 1, f);
}else
fread(&s,sizeof(Student),1,f);
}
if(!i)printf("\nNu a fost gasit.");
printf("\nNr. matricol: ");
scanf("%d",&n);}
fclose(f);}
}

6. Să se scrie programul pentru adăugarea punctului din oficiu la nota la ATP pentru studenţii din grupa al cărei
număr este introdus de la tastatură. Nota la ATP se regăsește pe poziția zero în vectorul de note.

#define _CRT_SECURE_NO_WARNINGS //în cazul în care apar avertismente de securitate


#include<stdio.h>

typedef struct{int nr;


char nume[30];
int an;
int grupa;
int n;
int note[15];
}Student;

void main()
{FILE *f;
char s1[20];
Student s;
int n,i;
printf("\nFisier: ");gets(s1);
f=fopen(s1,"rb+");
if(!f)printf("\nFisierul %s nu poate fi deschis",s1);
else{
printf("\nNr. grupei: ");
scanf("%d",&n);
while(!feof(stdin))
{rewind(f);
fread(&s, sizeof(Student), 1, f);
i=0;
while(!feof(f))
{if(n==[Link])
{i=1;
[Link][0]=([Link][0]==10)? [Link][0]: [Link][0]+1;
fseek(f, ftell(f)-sizeof(Student), 0);
fwrite(&s, sizeof(Student), 1, f);
fseek(f, 0, 1); //folosit la trecerea de la scriere la citire
}
fread(&s, sizeof(Student), 1, f);
}
if(!i) printf("\n Nu a fost gasit nici un student");
51
printf("\nNr grupei: ");scanf("%d",&n);}
fclose(f);}
}

7. Pentru problema cu grade de total, fisierul trebuie să fie ordonat după caracteristicile de control

a) Să se scrie programul care sortează studenţii după anii de studiu şi grupe. (se va apela apoi problema 4).
Se vor verifica datele daca sunt sortate)

#include<stdio.h>

int nrart(FILE *f, int l)


{long p;
int n;
p=ftell(f);
fseek(f,0,2);
n=ftell(f)/l;
fseek(f,p,0);
return n;}

typedef struct{int nr;


char nume[30];
int an;
int grupa;
int n;
int note[15];
}Student;

void main()
{FILE *f,*g;
char s[20];
Student s1,s2;
int ok,n,i;
printf("\nFisier: ");gets(s);
f=fopen(s,"rb+");
if(!f)printf("\nFisierul %s nu poate fi deschis",s);
else{n=nrart(f,sizeof(Student));
ok=1;
while(ok)
{ok=0;
for(i=0;i<n-1;i++)
{fseek(f, i*sizeof(Student), 0);
fread(&s1, sizeof(Student), 1, f);
fread(&s2, sizeof(Student), 1, f);
if (([Link]>[Link])|| (([Link]==[Link]) && ([Link]>[Link])))
{ok=1;
fseek(f,i*sizeof(Student),0);
fwrite(&s2, sizeof(Student), 1, f);
fwrite(&s1, sizeof(Student), 1, f);
}
}
}
fclose(f);}
}

52
b) Să se scrie programul care listează, într-un fişier text, studenţii integralişti, pe ani şi grupe, calculând
media fiecărei grupe şi a fiecărui an. (fisierul rezultat se va numi [Link] si se va verifica rezultatul
comparand cu datele din [Link])

#include<stdio.h>

typedef struct{int nr;


char nume[30];
int an;
int grupa;
int n;
int note[15];
}Student;

void main()
{FILE *f,*g;
char s1[20];
Student s;
int i,na,j,e,ng,ca,cg;
float ma,mg,ms;
printf("\nFisier: ");gets(s1);
f=fopen(s1,"rb");
if(!f)printf("\nFisierul %s nu poate fi deschis",s1);
else {printf("\nFisier text: ");gets(s1);
g=fopen(s1,"w");

fread(&s,sizeof(Student),1,f);
while(!feof(f))
{// operatii initiale pentru an
ca=[Link];
fprintf(g,"\n\nAnul %d",ca);
ma=0; na=0;

while((!feof(f))&& ([Link]==ca))
{// operatii initiale pentru grupa
mg=0; ng=0;
cg=[Link];
fprintf(g,"\n\tGrupa %d",cg);

while((!feof(f))&&(ca==[Link])&&(cg==[Link]))
{e=1;ms=0;
for(j=0;j<s.n;j++)
if([Link][j]<5)e=0;
else ms+=[Link][j];
if(e){mg+=ms/s.n;
ng++;
fprintf(g,"\n\t\t%4d %-30s Media %5.2f Note: ",[Link],[Link],ms/s.n);
for(j=0;j<s.n;j++)fprintf(g,"%2d ",[Link][j]);}
fread(&s,sizeof(Student),1,f);
}
//operatii finale grupa
if(ng){fprintf(g,"\n\tGrupa %d, media: %5.2f",cg,mg/ng);ma+=mg;na+=ng;}
else fprintf(g,"\n\Grupa nu are integralisti");}

//operatii finale an
if(na)fprintf(g,"\nMedia anului %d este: %5.2f",ca,ma/na);
else fprintf(g,"\nAnul nu are integralisti");}
fclose(f);
fclose(g);}
}

53
Algoritmi și tehnici de programare 145

4.1. Complexitatea algoritmilor

Pentru unele probleme există mai multe variante (algoritmi) de rezolvare


(de exemplu algoritmii de sortare). Acești algoritmi au fost dezvoltați fie în paralel,
de autori diferiți, fie în timp, în încercarea de a obține un algoritm mai bun.
Întrebarea naturală este „care algoritm e mai bun (pentru o problemă dată)?”.
Răspunsul nu poate fi formulat fără a stabili întîi criteriile după care se face
clasificarea. Criteriile pentru compararea algoritmilor sînt legate de resursele
consumate (timp de execuție, memorie consumată) și calitatea rezultatelor (optim,
cvasi-optim, acceptabil etc.). Chiar și avînd un criteriu clar, clasificarea nu este
neapărat definitivă, rezultatele reale fiind influențate de diverși factori. De
exemplu, timpul de execuție depinde și de procesorul care execută algoritmul (vezi
exemplul de mai jos) și de sursa de date (memoria internă sau dispozitiv extern).
Un alt factor care poate avea impact puternic în rezolvarea unei probleme reale este
setul de date de intrare. Uneori cunoștințele apriori privind setul de date de intrare
pot influența alegerea algoritmului iar aceasta se poate concretiza într-un algoritm
considerat „mai slab” la nivel teoretic sau în utilizarea unei combinații de
algoritmi.

Exemplu. Fie un masiv cu 10 milioane de elemente care trebuie sortat. Pentru


acest exemplu, se consideră algoritmul de sortare prin inserare și algoritmul de
sortare prin interclasare. Sortarea prin interclasare se execută pe un PC al cărui
procesor poate executa 107 instrucțiuni pe secundă iar sortarea prin inserare pe un
supercalculator al cărui procesor poate executa 1010 instrucțiuni pe secundă.
Considerînd n dimensiunea setului de date pe care se lucrează:

Calculator Viteză procesor Algoritm Instrucțiuni


(instrucțiuni/sec.) de sortare necesare

Supercalculator 1010 Inserare 2 ∙ 𝑛2

PC 107 Interclasare 50 ∙ 𝑛 ∙ log (𝑛)


Algoritmi și tehnici de programare 146

Fără a lua în considerare alte operații consumatoare de timp, supercalculatorul are


nevoie de
2
2 ∙ (107)
𝑠𝑒𝑐. = 20000 𝑠𝑒𝑐. = 5ℎ:33𝑚:20𝑠
1010

iar calculatorul personal are nevoie de numai

50 ∙ 107 ∙ log (107)


𝑠𝑒𝑐. = 1162.7 𝑠𝑒𝑐. = 0ℎ:19𝑚:22.67𝑠
107

Se observă că un calculator mai slab are nevoie de mult mai puțin timp pentru a
realiza aceeași prelucrare, dacă utilizează un algoritm cu o complexitate mai redusă
(„mai bun”).

Observație. În practică s-a constatat că algoritmul de sortare prin inserare, deși are
o complexitate mai mare, este mai rapid decît cel de sortare prin interclasare în
cazul masivelor de dimensiuni reduse. Ca urmare, cei doi algoritmi pot fi combinați
astfel: în algoritmul de sortare prin interclasare (cel cu complexitate mai mică),
atunci cînd în urma divizării masivului de sortat rezultă masive de dimensiuni mici,
acestea se sortează folosind algoritmul de sortare prin inserare, în loc să se utilizeze
recursiv sortarea prin interclasare.

Ca urmare a acestor considerații și constatări practice, au fost dezvoltate


criterii de comparare a algoritmilor la nivel teoretic. Cea mai cunoscută categorie
de criterii este cea a indicatorilor de complexitate. Acești indicatori se calculează la
nivel teoretic, fără a ține cont de mașina care va executa algoritmul.

Conceptele utilizate în analiza complexității algoritmilor sînt dimensiunea


datelor de intrare și timpul necesar pentru execuție. În mod natural, timpul de
execuție al unui algoritm depinde de dimensiunea datelor de intrare și poate fi
considerat o funcție (în sens matematic) avînd ca parametru această dimensiune.

Dimensiunea datelor de intrare e mai greu de definit la modul general, ea


depinzînd de problema concretă de rezolvat. Pentru multe probleme ea poate fi
definită ca fiind numărul de elemente care compun setul de date de intrare (de
exemplu în cazul problemelor de sortare). În unele situații, dimensiunea e definită
prin două valori, ca în cazul algoritmilor de prelucrare a grafurilor, unde
dimensiunea grafului e definită de numărul de vîrfuri și numărul de muchii. Alteori
Algoritmi și tehnici de programare 147

dimensiunea utilizată poate fi numărul de biți care descriu datele de intrare (de
exemplu operații aritmetice cu numere).

Timpul de execuție, așa cum se vede și în exemplul de mai sus, depinde


puternic de mașina utilizată. Pentru a putea analiza algoritmii, timpul de execuție se
abstractizează, fiind măsurat în operații primitive care trebuie executate de
procesor, devenind în acest fel independent de mașina utilizată.

În afara de algoritmul propriu-zis, timpul de execuție depinde și de valorile


concrete ale datelor de intrare. Acest aspect este luat în considerare prin definirea
următoarelor concepte: cel mai bun caz, cazul mediu, cel mai rău caz. Cel mai bun
caz reprezintă situația în care datele se află deja în starea dorită iar execuția
algoritmului nu duce la schimbări. Cel mai rău caz este acela în care algoritmul
trebuie să efectueze cel mai mare număr posibil de operații pentru a aduce datele în
starea dorită. Cazul mediu corespunde timpului de execuție previzionat pentru o
situație uzuală. Corespunzător acestor cazuri, pentru algoritmi pot fi definite trei
tipuri de complexitate.

În fiecare din cele trei cazuri timpul de execuție poate fi exprimat ca o


funcție avînd ca parametru dimensiunea datelor. Întrucît analiza se face pentru
dimensiuni mari ale datelor de intrare, se pot face următoarele simplificări: pentru
dimensiuni mari ale setului de date de intrare, deși este posibil, este ineficient să se
calculeze exact timpul de execuție; dimensiunea mare face ca termenul de rang
maxim să domine ceilalți termeni și coeficienții, de aceea se ia în considerare
numai termenul de rang maxim și nu se consideră coeficientul acestuia. Ceea ce
rămîne se numește ordinul de creștere a timpului de execuție în funcție de
dimensiunea datelor de intrare.

Uzual, în comparația dintre doi algoritmi, se consideră un algoritm „mai


bun” cel care are ordinul de creștere mai mic pentru cazul cel mai rău.

Atunci cînd se studiază timpul de execuție în funcție de dimensiunea


datelor pentru valori mare ale acesteia, astfel încît ordinul de creștere este factorul
relevant, spunem că se studiază eficiența asimptotică a algoritmului. Un algoritm
care este asimptotic mai bun este în general mai bun pentru toate cazurile, cu
excepția unor extreme.

Notația asimptotică este o convenție pentru definirea limitelor asimptotice


(superioară și inferioară) pentru timpul de execuție. Notațiile uzuale sînt: notația Θ
(theta), notația 𝑂 (o mare) și notația Ω (omega).
Algoritmi și tehnici de programare 148

Notația 𝚯 definește limita asimptotică strînsă superioară și inferioară a


timpului de execuție al unui algoritm pentru valori mari ale dimensiunii datelor de
intrare. Considerînd dimensiunea datelor de intrare notată cu 𝑛, notația Θ este
definită astfel:

Θ(𝑔(𝑛)) = {𝑓(𝑛) | ∃ 𝑐1, 𝑐2, 𝑛0 𝑎.î. 0 ≤ 𝑐1 ∙ 𝑔(𝑛) ≤ 𝑓(𝑛) ≤ 𝑐2 ∙ 𝑔(𝑛), ∀𝑛 ≥ 𝑛0 ]}

Cu alte cuvinte, o funcție 𝑓(𝑛) aparține mulțimii Θ(𝑔(𝑛)) dacă există


constantele 𝑐1 și 𝑐2 astfel încît pentru valori mari ale lui 𝑛 valorile funcției 𝑓(𝑛)
sînt cuprinse între 𝑐1 ∙ 𝑔(𝑛) și 𝑐2 ∙ 𝑔(𝑛). Funcția 𝑔(𝑛) este numită limită
asimptotică strînsă pentru 𝑓(𝑛).

Exemplu. Acest exemplu justifică simplificările menționate anterior în privința


ordinului de creștere.

1. Fie funcția 𝑓(𝑛) = 3𝑛2 ―2𝑛 (fără termen de rang zero, deoarece este irelevant
în discuția despre timpul asimptotic de execuție). Conform simplificărilor
menționate, ea aparține mulțimii Θ(𝑛2) – am păstrat doar termenul cu cel mai mare
grad și am eliminat coeficientul lui. Conform definiției, trebuie găsite constantele
𝑐1, 𝑐2 și pragul 𝑛0 astfel încît

𝑐1 ∙ 𝑛2 ≤ 3𝑛2 ― 2𝑛 ≤ 𝑐2 ∙ 𝑛2, ∀𝑛 ≥ 𝑛0
2 2
Împărțind la 𝑛2 obținem 𝑐1 ≤ 3 ― 𝑛 ≤ 𝑐2, unde 3 ― 𝑛 ∈ [1,3), ∀𝑛 ≥ 1. Rezultă că
putem alege 𝑐1 = 1 (sau orice valoare mai mică decît 1), 𝑐2 = 3 (sau orice valoare
mai mare decît 3) și 𝑛0 = 1. Ca urmare, într-adevăr funcția dată 𝑓(𝑛) ∈ Θ(𝑛2)
(uneori în literatură se folosește notația 𝑓(𝑛) = Θ(𝑛2), cu aceeași semnificație).

2. Fie funcția ℎ(𝑛) = 2𝑛3. Presupunînd că ℎ(𝑛) ∈ Θ(𝑛2), ar trebui să existe

𝑐1 ∙ 𝑛2 ≤ 2𝑛3 ≤ 𝑐2 ∙ 𝑛2, ∀𝑛 ≥ 𝑛0

Împărțind la 𝑛2 obținem 𝑐1 ≤ 2𝑛 ≤ 𝑐2. Dacă pentru 𝑐1 se poate găsi o valoare (de


exemplu 2, pentru 𝑛0 = 1), pentru 𝑐2 nu se poate găsi o valoare, deoarece lim 𝑛
𝑛→∞
= ∞ și 𝑐2 este constantă. Ca urmare, presupunerea inițială nu e adevărată, deci ℎ
(𝑛) ∉ Θ(𝑛2).

Coeficientul termenului de rang maxim poate fi ignorat, deoarece el


modifică doar constantele 𝑐1 și 𝑐2 cu un factor constant. Eliminarea termenilor de
Algoritmi și tehnici de programare 149

rang inferior poate fi compensată prin utilizarea unor valori mai mici (pentru 𝑐1),
respectiv mai mari (pentru 𝑐2).

În general, o funcție polinomială de grad 𝑘 aparține mulțimii Θ(𝑛𝑘).

Pentru o funcție constantă se utilizează uneori (ca abuz de notație) formula


Θ(1) sau Θ(𝑛0), corespunzătoare unui algoritm trivial, care nu efectuează nici o
operație.

Notația 𝑶 definește o limită asimptotică superioară a unei funcții, astfel:

𝑂(𝑔(𝑛)) = {𝑓(𝑛) | ∃ 𝑐, 𝑛0 𝑎.î. 𝑓(𝑛) ≤ 𝑐 ∙ 𝑔(𝑛), ∀𝑛 ≥ 𝑛0 ]}

Cu alte cuvinte, o funcție 𝑓(𝑛) aparține mulțimii 𝑂(𝑔(𝑛)) dacă există


constanta 𝑐 astfel încît pentru valori mari ale lui 𝑛 valorile funcției 𝑓(𝑛) sînt mai
mici decît 𝑐 ∙ 𝑔(𝑛). Funcția 𝑔(𝑛) este numită limită asimptotică superioară pentru 𝑓
(𝑛).

Evident, dacă 𝑓 aparține Θ(𝑔(𝑛)), atunci 𝑓 aparține și 𝑂(𝑔(𝑛)), în notație


matematică: Θ(𝑔(𝑛)) ⊆ 𝑂(𝑔(𝑛)).

Observație. Funcția identică 𝑓(𝑛) = 𝑛 aparține mulțimii 𝑂(𝑛). Dar, întrucît 𝑛2 ≥ 𝑛


, în același timp 𝑓(𝑛) aparține mulțimii 𝑂(𝑛2) (ș.a.m.d.), deci există o infinitate de
limite asimptotice superioare pentru o funcție. În literatură se folosește adesea
notația 𝑂 în loc de notația Θ pentru a referi limita asimptotică strînsă, de aceea
notația 𝑂 este mai cunoscută.

Folosirea notației O permite estimarea complexității unui algoritm prin


inspectarea structurii acestuia: o structură repetitivă (de exemplu un algoritm de
căutare secvențială) are complexitatea 𝑂(𝑛), în timp ce un algoritm cu două cicluri
imbricate (de exemplu sortarea prin metoda bulelor sau sortarea prin inserare) are
complexitatea 𝑂(𝑛2).

Complexitatea 𝑂(𝑛2) înseamnă că în cel mai rău caz timpul de execuție


este 𝑂(𝑛2), dar uneori poate fi și mai scurt, pentru cazuri particulare (de exemplu,
dacă masivul este deja sortat, timpul de execuție pentru algoritmul de sortare prin
inserare este 𝑂(𝑛)).

Notația 𝛀 semnifică limita asimptotică inferioară a unei funcții:

Ω(𝑔(𝑛)) = {𝑓(𝑛) | ∃ 𝑐, 𝑛0 𝑎.î. 0 ≤ 𝑐 ∙ 𝑔(𝑛) ≤ 𝑓(𝑛), ∀𝑛 ≥ 𝑛0 ]}


Algoritmi și tehnici de programare 150

Similar considerațiilor anterioare, notația Ω definește timpul de execuție


pentru cel mai bun caz. De exemplu, timpul de execuție pentru algoritmul de
sortare prin inserare în cel mai bun caz (vectorul este deja sortat) este Ω(𝑛).

În figurile următoare sînt prezentate grafic semnificațiile notațiilor


discutate anterior: Θ(𝑔(𝑛)), 𝑂(𝑔(𝑛)) respectiv Ω(𝑔(𝑛)).

cc22*g(n)
*g(n)

f(n)
f(n)

cc11*g(n)
*g(n)

n
n00 n
n

Fig. 1. 𝚯(𝒈(𝒏))

c*g(n)

f(n)

n00 n

Fig. 2. 𝑶(𝒈(𝒏))
Algoritmi și tehnici de programare 151

f(n)

c*g(n)

n00 n

Fig. 3. 𝛀(𝒈(𝒏))

În figura următoare este prezentată o comparație grafică a formelor uzuale


ale funcțiilor care definesc limitele asimptotice ale algoritmilor.

Fig. 4. Grafic comparativ privind ordinul de creștere a timpului de execuție

Tabelul următor prezintă sintetic complexitatea principalilor algoritmi de


sortare. Coloana „pe loc” arată dacă sortarea are loc în spațiul de memorie ocupat
de masivul original sau se folosesc zone suplimentare de memorie, rezultatul
Algoritmi și tehnici de programare 152

ocupînd un spațiu de memorie separat (în acest caz masivul original nu este
modificat).

Algoritm de Cel mai bun Cel mai rău


Cazul obișnuit Pe loc
sortare caz caz
Bule 𝑂(𝑛) 𝑂(𝑛2) 𝑂(𝑛2) 
Selectie 𝑂 (𝑛 )
2
𝑂 (𝑛 )
2
𝑂 (𝑛 )
2

Inserare 𝑂(𝑛) 𝑂 (𝑛 )
2
𝑂 (𝑛 )
2

Shell 𝑂(𝑛) ≥ 𝑂(𝑛 ∙ log(𝑛)) 𝑂 (𝑛 )
2

Interclasare 𝑂(𝑛 ∙ log (𝑛)) 𝑂(𝑛 ∙ log (𝑛)) 𝑂(𝑛 ∙ log (𝑛)) -
Heap 𝑂(𝑛 ∙ log (𝑛)) 𝑂(𝑛 ∙ log (𝑛)) 𝑂(𝑛 ∙ log (𝑛)) 
Quick 𝑂(𝑛 ∙ log (𝑛)) 𝑂(𝑛 ∙ log (𝑛)) 𝑂 (𝑛 )
2

Prin 𝑂(𝑛) 𝑂(𝑛) 𝑂(𝑛)
-
numărare
Bucket 𝑂(𝑛 + 𝑘) 𝑂(𝑛 + 𝑘) 𝑂(𝑛2) -
Radix 𝑂(𝑑 ∙ 𝑛) 𝑂(𝑑 ∙ 𝑛) 𝑂(𝑑 ∙ 𝑛) -

Exemplu. În continuare este prezentată analiza complexității algoritmului de


sortare prin inserție. În acest scop liniile algoritmului sunt numerotate și referite în
tabelul următor, pentru evidențierea costurilor exprimate în funcție de n,
dimensiunea vectorului.

L1 pentru k=1:n-1
L2 aux=v[k]
L3 j=k-1
L4 cât timp j>=0 și v[j]>aux
L5 v[j+1]=v[j]
L6 j=j-1
L7 v[j+1]=aux

Notând cu 𝑡𝑘 numărul de repetări “cât timp” pentru 𝑘 = 1,…,𝑛, rezultă că, în cel
mai favorabil caz, 𝑡𝑘 = 1, în timp ce, în situația cea mai defavorabilă, 𝑡𝑘 = 𝑘 + 1.
În următorul table sunt prezentate costurile la nivel de linie a algoritmului.

Linia Cost Număr repetări Cel mai Cel mai


favorabil caz defavorabil caz
Algoritmi și tehnici de programare 153

L1 C1 n n n
L2 C2 n-1 n-1 n-1
L3 C3 n-1 n-1 n-1
L4 C4 𝒏―𝟏 n-1 𝒏(𝒏 + 𝟏)
―𝟏
𝒕𝒌 𝟐
𝒌=𝟏
L5 C5 𝒏―𝟏 0 𝒏(𝒏 ― 𝟏)
(𝒕 𝒌 ― 𝟏 ) 𝟐
𝒌=𝟏
L6 C6 𝒏―𝟏 0 𝒏(𝒏 ― 𝟏)
(𝒕 𝒌 ― 𝟏 ) 𝟐
𝒌=𝟏
L7 C7 n-1 n-1 n-1

Obținem că, în cel mai favorabil caz, costul este

𝑇𝐵(𝑛) = 𝑐1𝑛 + 𝑐2(𝑛 ― 1) + 𝑐3(𝑛 ― 1) + 𝑐4(𝑛 ― 1) + 𝑐7(𝑛 ― 1)


= (𝑐1 + 𝑐2 + 𝑐3 + 𝑐4 + 𝑐7)𝑛 ― (𝑐2 + 𝑐3 + 𝑐4 + 𝑐7) = 𝑎 ∙ 𝑛 + 𝑏

Similar, în cazul cel mai defavorabil, rezultă

𝑇𝑅(𝑛)

(𝑛(𝑛2+ 1) ― 1) + 𝑐 𝑛(𝑛2― 1) + 𝑐
= 𝑐1𝑛 + 𝑐2(𝑛 ― 1) + 𝑐3(𝑛 ― 1) + 𝑐4 5 6

𝑐 𝑐 𝑐
+ 𝑐 (𝑛 ― 1) = ( + + )𝑛
𝑛(𝑛 ― 1) 4 5 6
2
7
2 2 2 2
𝑐 𝑐 𝑐
+ (𝑐 + 𝑐 + 𝑐 + ― ― + 𝑐 ) 𝑛 ― (𝑐 + 𝑐 + 𝑐 + 𝑐 ) = 𝑎
4 5 6
1 2 3 7 2 3 4 7
2 2 2
∙ 𝑛2 + 𝑏 ∙ 𝑛 + 𝑐
Algoritmi și tehnici de programare 154
Grafuri. Reprezentări. Metode de parcurgere.

Probleme propuse și rezolvate

1. Fie graful G=(V,E) graf, cu V={1,2,3,4,5,6}, E={(1,2),(1,3),(2,5),(2,6),(3,5),(3,6),(5,6)}. Care


este matricea de adiacenţă a grafului?
0 1 1 0 0 0 
 
1 0 0 0 1 1 
1 0 0 0 1 1 
Raspuns: A   
0 0 0 0 0 0
0 1 1 0 0 1 

0 1 1 0 1 0 

2. Fie D=(V,E) digraf, V={1,…,5}, E={(1,2), (1,3), (2,5), (3,5), (4,1), (5,1), (5,3), (5,4)}. Care este
matricea de adiacenţă a digrafului?
0 1 1 0 0
 
0 0 0 0 1
Raspuns: A   0 0 0 0 1
 
1 0 0 0 0
1 0 1 1 0 

3. 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

Care este reprezentarea tabelară?


T
1 1 1 2 3 4 5
Raspuns: Digraful este reprezentat prin A    .
 2 3 5 5 5 1 4

4. Fie G=(V,E,W) graf ponderat, V={1,2,3,4,5}, E={(1,2), (1,4), (2,3), (2,4), (3,4)}, W((1,2))=5,
W((1,4))=7, W((2,3))=4, W((2,4))=2, W((3,4))=1. Specificaţi matricea ponderilor.

Pagină 154 din 77


Algoritmi și tehnici de programare 155
 5  7  
 
5  4 2  
Raspuns: W    4  1  
7 2 1  
 
     

Parcurgerea grafurilor in latime (BF)


5. Fie graful,
1

2 3

6
4

7
5

şi v0=1. Realizați parcurgerea în lățime a grafului pornind din vârful 1 (tabelul distanțelor).

Răspuns: Valorile calculate sunt,


1 2 3 4 5 6 7
vîrf
d
0 0      
1 0 1 1 1   1
2 0 1 1 1 2 2 1
0 1 1 1 2 2 1

Fie G=(V,E) un graf, V  n . O alternativă de implementare a metodei BF este construită prin


utilizarea următoarelor structuri de date,
 A matricea de adiacenţă a grafului;
 o structură de tip coadă, C, în care sunt introduse vârfurile ce urmează a fi vizitate şi procesate (în
sensul cercetării vecinilor lor);
 un vector c cu n componente, unde,
1, dacă i a fost adăugat în coadă
ci  
0, altfel
Componentele vectorului c sunt iniţializate cu valoarea 0.

Parcurgerea BF poate fi descrisă astfel,


 coada C este iniţializată cu vârful v0;

Pagină 155 din 77


Algoritmi și tehnici de programare 156
 cât timp C  Ø, este extras şi vizitat un vârf i din coadă, apoi sunt introduşi în coadă vecinii lui i
care nu au fost deja introduşi (acele vârfuri k cu proprietatea că c[k]=0 şi a[i][k]=1). Vârfurile i ce au
fost introduse în coadă sunt marcate prin c[i]=1.
Aplicarea metodei de traversare BF determină următoarea evoluţie,
c C
1 2 3 4 5 6 7
t t
t=1 1 0 0 0 0 0 0 t=1 1
t=2 1 1 1 1 0 0 1 t=2 2 3 4 7
t=3 1 1 1 1 1 0 1 t=3 3 4 7 5
t=4 1 1 1 1 1 1 1 t=4 4 7 5 6
t=5 1 1 1 1 1 1 1 t=5 7 5 6
t=6 1 1 1 1 1 1 1 t=6 5 6
t=7 1 1 1 1 1 1 1 t=7 6
t=8 1 1 1 1 1 1 1 t=8

Observaţie Deoarece graful este conex, traversarea BF realizează vizitarea tuturor vârfurilor grafului.
Aplicarea metodei BF unui graf neconex nu determină vizitarea tuturor vârfurilor. Cu alte cuvinte,
metoda BF aplicată unui graf determină vizitarea tuturor vârfurilor care sunt conectate cu vârful iniţial
selectat.

Implementarea metodei de parcurgere BF

#define _CRT_SECURE_NO_WARNINGS
#include <stdio.h>
#include <conio.h>
#include<malloc.h>

int insereaza_coada(int *coada,int n, int vf)


{ coada[n]=vf;
n++;
return n;
}

int extrage_coada(int *coada,int n, int *vf)


{ int i;
*vf=coada[0];
for(i=0;i<n-1;i++)
coada[i]=coada[i+1];

Pagină 156 din 77


Algoritmi și tehnici de programare 157
n--;
return n;
}

void breadth_first(int v0,int **a,int n)


{ int *coada, *m,i,nr_c,k;
coada=(int*) malloc(n*sizeof(int));
m=(int*) malloc (n*sizeof(int));
for(i=0;i<n;m[i++]=0);
nr_c=0;
nr_c=insereaza_coada(coada,nr_c,v0);
m[v0]=1;
while(nr_c)
{ nr_c=extrage_coada(coada,nr_c,&i);
printf("\n%i",i+1);
for(k=0;k<n;k++)
if((a[i][k]==1)&&(m[k]==0))
{ nr_c=insereaza_coada(coada,nr_c,k);
m[k]=1;
}
}
free(coada);
free(m);
}

void main()
{ int n,v0,**a,m,i,j,vf1,vf2;
printf("Numarul de virfuri:");
scanf("%i",&n);
//se va aloca memorie pentru matricea a
a=(int **)malloc(n*sizeof(int*));
for(i=0;i<n;i++)
a[i]=(int*)malloc(n*sizeof(int));
for(i=0;i<n;i++)
for(j=0;j<=i;j++)
a[j][i]=a[i][j]=0;
printf("\nNumarul de muchii:");

Pagină 157 din 77


Algoritmi și tehnici de programare 158
scanf("%i",&m);
for(i=0;i<m;i++)
{ printf("Virf initial:");scanf("%i",&vf1);
printf("Virf final:");scanf("%i",&vf2);
a[vf1-1][vf2-1]=a[vf2-1][vf1-1]=1;
}
printf("\nVirful initial pentru parcurgerea BF ");
scanf("%i",&v0);
printf("\nOrdinea de vizitare a virfurilor grafului este:");
breadth_first(v0-1,a,n);
//se va elibera zona de memorie alocata pentru matricea a
for(i=0;i<n;i++) free(a[i]);
free(a);
}

Ordinea de vizitare a vârfurilor este 1,2,3,4,7,5,6 care poate fi văzută atât din exemplul numeric, cât și
prin execuția programului pentru același graf și vârf de pornire.

Pagină 158 din 77


Algoritmi și tehnici de programare 159

Metoda de parcurgere DF (Depth First)

6. Pentru graful,

2 3

6
4

7
5

şi v0=1, prin aplicarea metodei descrisa in continuare , rezultă următoarea evoluţie.

O variantă de implementare a metodei DF rezultă prin gestionarea stivei S în modul următor. Iniţial
vîrful v0 este unicul component al lui S. La fiecare etapă se preia, fără ştergere, ca vîrf curent vârful
stivei. Se introduce în stivă unul dintre vecinii vârfului curent încă nevizitat. Vizitarea unui vârf revine
la introducerea lui în S. Dacă vârful curent nu are vecini încă nevizitaţi, atunci el este eliminat din stivă
şi este efectuat un nou acces de preluare a noului vârf al stivei ca vârf curent. Calculul se încheie în
momentul în care este efectuat un acces de preluare a vârfului stivei ca vârf curent şi se constată că S
este vidă. Evident, nici în cazul acestei variante nu vor fi vizitate vârfurile care nu sunt conectate cu
vârful iniţial ales.
c S
1 2 3 4 5 6 7
t T
t=1 1 0 0 0 0 0 0 t=1 1
t=2 1 1 0 0 0 0 0 t=2 2 1
t=3 1 1 0 0 1 0 0 t=3 5 2 1
t=4 1 1 0 1 1 0 0 t=4 4 5 2 1
t=5 1 1 1 1 1 0 0 t=5 3 4 5 2 1
t=6 1 1 1 1 1 1 0 t=6 6 3 4 5 2 1
t=7 1 1 1 1 1 1 1 t=7 7 6 3 4 5 2 1
t=8 1 1 1 1 1 1 1 t=8 6 3 4 5 2 1
t=9 1 1 1 1 1 1 1 t=9 3 4 5 2 1
t=10 1 1 1 1 1 1 1 t=10 4 5 2 1
t=11 1 1 1 1 1 1 1 t=11 5 2 1
t=12 1 1 1 1 1 1 1 t=12 2 1
t=13 1 1 1 1 1 1 1 t=13 1

Pagină 159 din 77


Algoritmi și tehnici de programare 160
t=14 1 1 1 1 1 1 1 t=14

Ordinea în care sunt vizitate vârfurile corespunzător acestei variante este: 1,2,5,4,3,6,7

Implementarea metodei DF
#define _CRT_SECURE_NO_WARNINGS
#include <stdio.h>
#include <conio.h>
#include<malloc.h>
int insereaza_stiva(int *stiva,int n, int vf)
{ int i;
for (i=n-1;i>=0;i--)
stiva[i+1]=stiva[i];
stiva[0]=vf;
n++;
return n;
}
int sterge_stiva(int *stiva,int n)
{ int i;
for (i=0;i<n-1;i++)
stiva[i]=stiva[i+1];
n--;
return n;
}
int citeste_stiva(int *stiva, int n)
{ return stiva[0];
}

void depth_first(int v0,int **a,int n)


{ int *stiva, *m, i, nr_c, gasit;
// se aloca memorie pentru stiva si pentru m – n elemente
stiva=(int*)malloc(n*sizeof(int));
m=(int*)malloc(n*sizeof(int));
for(int i=0;i<n;m[i++]=0);
nr_c=0;
nr_c=insereaza_stiva(stiva,nr_c,v0);
m[v0]=1;

Pagină 160 din 77


Algoritmi și tehnici de programare 161
printf("\n%i",v0+1);
while(nr_c)
{ i=citeste_stiva(stiva,nr_c);
gasit=0;
for(int k=0;(k<n)&&!gasit;k++)
if((a[i][k]==1)&&(m[k]==0))
{ nr_c=insereaza_stiva(stiva,nr_c,k);
m[k]=1;
printf("\n%i",k+1); gasit=1;
}
if(!gasit)
nr_c=sterge_stiva(stiva,nr_c);
}
//eliberare memorie alocata – pentru stiva si pentru m;
free(stiva); free(m);
}

void main()
{ int n,v0,**a,m,i,j,vf1,vf2;;
printf("Numarul de virfuri:");
scanf("%i",&n);
a=(int **)malloc(n*sizeof(int*));
for(i=0;i<n;i++)
a[i]=(int*)malloc(n*sizeof(int));
for(i=0;i<n;i++)
for(j=0;j<=i;j++)
a[j][i]=a[i][j]=0;
printf("\nNumarul de muchii:");
scanf("%i",&m);
for(i=0;i<m;i++)
{ printf("Virf initial:");scanf("%i",&vf1);
printf("Virf final:");scanf("%i",&vf2);
a[vf1-1][vf2-1]=a[vf2-1][vf1-1]=1;
}
printf("\nVirful initial pentru parcurgerea DF ");
scanf("%i",&v0);
printf("\nOrdinea de vizitare a virfurilor grafului este:");

Pagină 161 din 77


Algoritmi și tehnici de programare 162
depth_first(v0-1,a,n);
for(i=0;i<n;i++) free(a[i]);
free(a);
getch();
}

Pagină 162 din 77


Algoritmi și tehnici de programare 163
Conectivitate, drumuri in graf

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.

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.

3. Pentru graful,

4
Determinați matricea existenței drumurilor.

Răspuns:
Pagină 163 din 77
Algoritmi și tehnici de programare 164
Matricea existenţei drumurilor; algoritmul Roy-Warshal
(1) (2) ( n 1 )
Matricea M  A  A    A se numeşte matricea existenţei drumurilor în graful G.
Semnificaţia componentelor matricei M este:
0 , dacă nu există vi  v j drum în G
1  i , j  n , mij  
1, altfel

0 1 1 1 1 0 1 1 1 1 1 1 1 1 1 1
       
1 0 0 0 0
2 1 1 1 1
3 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.

4. Care sunt componentele conexe ale grafului:


1
5

2
6

4
3

Răspuns: C1={1,2,3}, C2={4,5}, C3={6}.

Observaţii
Un graf este conex dacă şi numai dacă numărul componentelor sale conexe este 1.

5. Pentru graful,
1 7 3
2

4 5 8 9 6
Determinați componenta conexă care conține vârful 1.

Răspuns:

Determinarea componentei conexe care conţine un vârf v0 dat poate fi realizată pe baza următorului
algoritm.

Pagină 164 din 77


Algoritmi și tehnici de programare 165
Pentru G=(V,E), V  n , n  1 şi v0  V, paşii algoritmului sînt:

Pas1: V0={v0}; E0=  ; i=0;


Pas 2: repetă Pas 3 pînă cînd Vi=Vi-1 şi Ei=Ei-1
Pas 3: i=i+1;
Vi  Vi 1  v / v  V, u  Vi 1 , uv  E;
E i  E i 1  e / e  E, u  Vi 1 , u incident cu e;
Ieşirea este Gi=(Vi,Ei), componenta conexă din care face parte v0.

Aplicarea algoritmului descris pentru v0=1, determină următoarea evoluţie:


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)}

6. Fie graful,
1 8

3
2

6
4 9 10

11
5 7

Câte component conexe are graful?


Răspuns: Graful are 2 componente conexe.

7. Pentru graful de mai sus, determinaţi componenta conexă care conţine vârful 5.

Răspuns:
Aplicarea algoritmului descris pentru v0=5, determină următoarea evoluţie:
I Vi Ei
i=0 {5} Ø
i=1 {1,4,5} {(1,5),(4,5)}
i=2 {1,2,3,4,5,7} {(1,5),(4,5),(1,2),(1,3),(2,4),(3,4),(1,7),(4,7)}
i=3 {1,2,3,4,5,6,7} {(1,5),(4,5),(1,2),(1,3),(2,4),(3,4),(1,7),(4,7),(3,6),(3,7)}

8. Fie graful,
Pagină 165 din 77
Algoritmi și tehnici de programare 166
1

2
4
6
5
3

Care este matricea existenţei drumurilor?

Răspuns:

1 1 1 10 1
 
1 1 1 10 1
1 1 1 10 1
Matricea existenţei drumurilor este A   
0 0 0 00 0
1 1 1 1 
0 1

1 1 1 0 1 1 

9. Scrieți un subprogram care calculează matricea existenței drumurilor într-un graf neponderat
(algoritmul Roy-Warshall), precum și programul apelator.

#include <stdio.h>
#include <malloc.h>

//I: matricea de adiacenta, nr. vârfuri


//E: matricea existentei drumurilor, dimensiune
int** Roy_Warshall(int** a, int n,int *dim)
{ int i,j,k;
int** m;

*dim=n;
m=(int**)malloc(n*sizeof(int*));
for(i=0;i<n;i++)
m[i]=(int*)malloc(n*sizeof(int));

for(i=0;i<n;i++)
for(j=0;j<n;j++)
m[i][j]=a[i][j];
for(i=0;i<n;i++)

Pagină 166 din 77


Algoritmi și tehnici de programare 167
for(j=0;j<n;j++)
if(m[i][j]==1)
for(k=0;k<n;k++)
if(m[j][k]==1)
m[i][k]=m[k][i]=1;
return m;
}
//preia graful din fisierul text
void preia_graf(char *nume, int ***a, int *n)
{ int i,j,m,u,v;
FILE *f=fopen(nume,"rt");
fscanf(f,"%i",n);
int **mw=(int **)malloc(*n*sizeof(int*));
for(i=0;i<*n;i++)
mw[i]=(int *)malloc(*n*sizeof(int));
fscanf(f,"%i",&m);
for(i=0;i<*n;i++)
for(j=0;j<*n;j++)
mw[i][j]=0;
for(i=0;i<m;i++)
{ fscanf(f,"%i",&u);
fscanf(f,"%i",&v);
mw[u-1][v-1]=mw[v-1][u-1]=1; }
fclose(f);
*a=mw;}

void main()
{ int **a,**m_dr,l;
int n,i,j;
char numef[20];
printf("Introduceti numele fisierului care contine graful:");
scanf("%s", numef);
preia_graf(numef,&a, &n);
m_dr=Roy_Warshall(a,n,&l);
for(i=0;i<l;i++)
{ for(j=0;j<l;j++) printf("%3i ", m_dr[i][j]);

Pagină 167 din 77


Algoritmi și tehnici de programare 168
printf("\n");
}
for(i=0;i<n;i++) free(a[i]);
free(a);
for(i=0;i<l;i++) free(m_dr[i]);
free(m_dr);
}

Pentru graful preluat din fișierul [Link] (fisierul text va fi adaugat in directorul proiectului), anume:
11 11
1 2
1 3
2 4
2 6
3 4
3 5
4 5
5 6
7 8
7 9
8 9
matricea existenței drumurilor este:
1 1 1 1 1 1 0 0 0 0 0
1 1 1 1 1 1 0 0 0 0 0
1 1 1 1 1 1 0 0 0 0 0
1 1 1 1 1 1 0 0 0 0 0
1 1 1 1 1 1 0 0 0 0 0
1 1 1 1 1 1 0 0 0 0 0
0 0 0 0 0 0 1 1 1 0 0
0 0 0 0 0 0 1 1 1 0 0
0 0 0 0 0 0 1 1 1 0 0
0 0 0 0 0 0 0 0 0 0 0
0 0 0 0 0 0 0 0 0 0 0

Pagină 168 din 77


Seminar 8

Aplicații ale metodei Greedy

Probleme propuse:

1. Problema rucsacului, varianta continuă. Să se scrie subprogramul C pentru rezolvarea problemei


rucsacului. Se consideră un mijloc de transport cu o capacitate dată (𝑪). Cu acesta trebuie
transportate obiecte dintr-o mulțime 𝑨 = {𝒂𝒊| 𝒊 = 𝟏,𝒏}. Fiecare obiect ocupă o capacitate specifică
𝒄𝒊 și aduce un câștig specific 𝒗𝒊 – fiecare obiect luat separat încape în mijlocul de transport. Se cere
să se determine o modalitate de încărcare care să maximizeze cîștigul obținut la un transport.
Obiectele transportate pot fi fractionate. În acest caz se va utiliza întotdeauna întreaga capacitate
de transport.
2. Problema rucsacului, varianta întreagă. Să se scrie subprogramul C pentru rezolvarea problemei
rucsacului. Se consideră un mijloc de transport cu o capacitate dată (𝑪). Cu acesta trebuie
transportate obiecte dintr-o mulțime 𝑨 = {𝒂𝒊| 𝒊 = 𝟏,𝒏}. Fiecare obiect ocupă o capacitate specifică
𝒄𝒊 și aduce un câștig specific 𝒗𝒊 – fiecare obiect luat separat încape în mijlocul de transport. Se cere
să se determine o modalitate de încărcare care să maximizeze cîștigul obținut la un transport.
Obiectele transportate nu pot fi fractionate.
3. Se dă o mulțime de elemente reale 𝑨 = {𝒂𝟏, 𝒂𝟐, …, 𝒂𝒏,}. Se cere să se scrie funcția care determină
o submulțime 𝑺 ⊆ 𝑨 astfel încât suma elementelor submulțimii 𝑺 să fie cea mai mare posibilă
(problema sumei maxime).
4. Să se scrie subprogramul C care permite plata unei sume 𝑺 ∈ ℕ folosind cît mai puține bancnote din
tipurile (valorile) 𝒂𝒊, 𝒊 = 𝟏,𝒏, știind că printre acestea se află și bancnota cu valoare unitate. Sunt
disponibile cantități nelimitate din fiecare tip de bancnotă.

5. Fiind dat numărul natural k>1 să se scrie programul care determină cel mai mic număr natural
n având exact k divizori naturali proprii (diferiți de 1 și de n).

Probleme rezolvate

1. Problema rucsacului - descriere


Să se scrie subprogramul C pentru rezolvarea problemei rucsacului. Se consideră un mijloc de transport
cu o capacitate dată (𝑪). Cu acesta trebuie transportate obiecte dintr-o mulțime 𝑨 = {𝒂𝒊| 𝒊 = 𝟏,𝒏}. Fiecare
obiect ocupă o capacitate specifică 𝒄𝒊 și aduce un câștig specific 𝒗𝒊 – fiecare obiect luat separat încape în
mijlocul de transport. Se cere să se determine o modalitate de încărcare care să maximizeze cîștigul
obținut la un transport.

Dacă obiectele transportate pot fi fracționate, problema este numită continuă. În acest caz se va utiliza
întotdeauna întreaga capacitate de transport.

Dacă obiectele nu pot fi fracționate, problema este numită întreagă. În acest caz e posibil ca soluția
obținută să nu utilizeze întreaga capacitate de transport. De asemenea, soluția obținută poate să nu fie
optimă. În plus, e posibil să existe o soluție de utilizare a întregii capacități de transport, dar aceasta să nu
fie găsită prin algoritmul de tip Greedy.
Cea mai bună soluție se obține dacă se folosește o valoare derivată: câștigul unitar (𝑐𝑖/𝑣𝑖). La fiecare pas
se ia în considerare următorul element al mulțimii sortate descrescător (alegere). Dacă acesta încape în
mijlocul de transport (verificare), e adăugat la soluție (adăugare) și se diminuează capacitatea de transport
rămasă disponibilă. Dacă nu încape, atunci:

 dacă problema e continuă, se ia din obiectul curent atât cât încape (adăugare);
 dacă problema e întreagă, se respinge obiectul curent.

În ambele cazuri restul obiectelor se resping și algoritmul se termină.

Pentru reprezentarea soluției se poate folosi un vector 𝐵 în care fiecare element are valoarea 1 (obiect
acceptat) sau 0 (obiect respins). În cazul în care se acceptă fracționarea obiectelor, unul singur va fi
fracționat iar elementul din vectorul soluție corespunzător lui va avea o valoare în intervalul (0,1).
Elementul 𝑏𝑖 arată dacă obiectul 𝑎𝑖 este acceptat sau respins (sau acceptat fracționat).

Exemplu de apel.
Se va modifica programul principal astfel incat datele de intrare sa fie citite de la tastatura.

#define _CRT_SECURE_NO_WARNINGS
#include<stdio.h>
#include<conio.h>

#define N 10

//problema rucsacului, continua


// I: capacitate totala (q), nr. obiecte (n), capacitate ocupata (c), cistig (v)
// E: solutia x (x[i]= raportul in care e incarcat obiectul i)
void Rucsac_c(float q, int n, float* c, float* x)
{ float qr;
int i,j;

qr=q; //capacitatea ramasa disponibila, initial capacitatea totala


for(i=0; i<n && qr>0; i++)
if(qr>=c[i])
{ x[i]=1;
qr-=c[i]; //qr-=c[i]*x[i]
}
else
{ x[i]=qr/c[i];
qr=0; //qr-=c[i]*x[i]
for(j=i+1;j<n;j++)
x[j]=0;
}
}

void main()
{ //exemplu de apel
float CT=33; //capacitate totala de transport
float c[N]={1,2,3,4,5,6,7,8,9,10}; //capacitati
float v[N]={3,2,1,4,5,3,2,7,1,8}; //venituri
//pentru cazul general datele de intrare se vor citi de la tastatura

float sol[N],a, cistig;


int i,j;

//prelucrare preliminara
//sortarea obiectelor
for(i=0;i<N-1;i++)
for(j=i+1;j<N;j++)
if(v[i]/c[i] < v[j]/c[j]) //cistig unitar
{ a=v[i]; v[i]=v[j]; v[j]=a;
a=c[i]; c[i]=c[j]; c[j]=a;
}

//apel functie
Rucsac_c(CT, N, c, sol);

//afisare rezultate
cistig=0;
for(i=0;i<N;i++)
{ printf("\n%2d: c=%5.2f v=%5.2f %2f, cistig=%5.2f",i+1, c[i], v[i], sol[i],
cistig+=v[i]*sol[i]);
}
_getch();

}
2. Problema rucsacului, varianta întreagă

Se va modifica programul principal astfel incat datele de intrare sa fie citite de la tastatura.

#define _CRT_SECURE_NO_WARNINGS
#include<stdio.h>
#include<conio.h>

#define N 10

//problema rucsacului, intreaga


// I: capacitate totala (q), nr. obiecte (n), capacitate ocupata (c), cistig (v)
// E: solutia x (x[i]= 0-obiectul i nu e incarcat, 1-obiectul i e incarcat)
void Rucsac_i(float q, int n, float c[], int x[])
{ float qr;
int i;

qr=q; //capacitatea ramasa disponibila, initial capacitatea totala


for(i=0; i<n; i++)
if(qr>=c[i])
{ x[i]=1;
qr-=c[i];
}
else
x[i]=0;
}

void main()
{ //exemplu de apel
float CT=33; //capacitate totala de transport
float c[N]={1,2,3,4,5,6,7,8,9,10}; //capacitati
float v[N]={3,2,1,4,5,3,2,7,1,8}; //venituri
//pentru cazul general datele de intrare se vor citi de la tastatura

int sol[N]; float cistig,a;


int i,j;

//prelucrare preliminara
//sortarea obiectelor
for(i=0;i<N-1;i++)
for(j=i+1;j<N;j++)
if(v[i]/c[i] < v[j]/c[j]) //cistig unitar
{ a=v[i]; v[i]=v[j]; v[j]=a;
a=c[i]; c[i]=c[j]; c[j]=a;
}

//apel functie
Rucsac_i(CT, N, c, sol);
//afisare rezultate
cistig=0;
for(i=0;i<N;i++)
{ printf("\n%2d: c=%5.2f v=%5.2f %2d, cistig=%5.2f",i+1, c[i], v[i], sol[i],
cistig+=v[i]*sol[i]);
}
_getch();

3. Problema sumei maxime

Se dă o mulțime de elemente reale 𝑨 = {𝒂𝟏, 𝒂𝟐, …, 𝒂𝒏,}. Se cere să se scrie funcția care determină o
submulțime 𝑺 ⊆ 𝑨 astfel încât suma elementelor submulțimii 𝑺 să fie cea mai mare posibilă (problema
sumei maxime).

O sortare inițială descrescătoare a mulțimii date asigură un număr mai mic de pași la aplicarea algoritmului
Greedy (oprire la primul element nul/negativ), dar crește complexitatea la nivel general, de aceea e
preferabil (în acest caz) să nu se facă prelucrarea inițială și să se analizeze toate elementele mulțimii
inițiale. Funcția următoare rezolvă problema sumei maxime, fără sortare prealabilă a mulțimii inițiale,
fiind astfel necesară parcurgerea întregii mulțimi.

#define _CRT_SECURE_NO_WARNINGS

#include<stdio.h>
#include<conio.h>

// Submultimea care da suma maxima: se iau doar elementele (strict) pozitive

// I: multimea (a), numar de elemente (n)

// E: submultimea (b), numar de elemente (nr)

void suma_maxima(float* a, int n, float *b, int *nr)

{ int i;

*nr=0;

for(i=0;i<n;i++)

if(a[i]>=0) // = pentru zero, care nu afecteaza suma.

b[(*nr)++]=a[i];

void main()

float a[100], b[100];

int n, i, nr;

printf("n="); scanf("%d", &n);

for(i=0;i<n;i++)

{printf("a[%d]=",i); scanf("%f", &a[i]);}

suma_maxima(a,n,b,&nr);

if(nr==0) printf("Nu exista numare pozitive");

else {

printf("Solutia este:");

for(i=0;i<nr;i++)

printf("%3.1f ", b[i]);

}
4. Plata unei sume cu bacnota unitate

Să se scrie subprogramul C care permite plata unei sume 𝑺 ∈ ℕ folosind cît mai puține bancnote din
tipurile (valorile) 𝒂𝒊, 𝒊 = 𝟏,𝒏, știind că printre acestea se află și bancnota cu valoare unitate. Sunt
disponibile cantități nelimitate din fiecare tip de bancnotă.

Pentru a folosi cât mai puține bancnote, trebuie utilizate bancnote cu valori cât mai mare, ceea ce duce la
sortarea mulțimii date în ordine descrescătoare. Astfel, bancnotele cu valorile cele mai mari vor fi primele
luate în considerare. Soluția poate fi reprezentată ca un vector 𝐵 = {𝑏𝑖 ∈ ℕ| 𝑖 = 1,𝑛}, în care fiecare
element 𝑏𝑖 arată câte bancnote de tipul 𝑎𝑖 trebuie utilizate.

Pornind de la valoarea inițială 𝑆, se acoperă cît se poate de mult cu cel mai mare tip de bancnotă, apoi se
ajustează suma rămasă de plată. Se continuă cu fiecare tip de bancnotă, pînă cînd suma rămasă este zero.
Dacă mai rămîn tipuri de bancnote neinvestigate, ele rămân nefolosite.

Funcția următoare constituie un exemplu de implementare a rezolvării. Parametrul 𝑡 este un vector cu


tipurile de bancnote, aranjate în ordinea descrescătoare a valorii nominale.

#define _CRT_SECURE_NO_WARNINGS

#include<stdio.h>

#include<conio.h>

// Plata unei sume, cu bancnota unitate, cu numar minim de bancnote

// I: suma (s), tipuri bancnote (t), numar tipuri (n)

// E: nr. din fiecare tip

void plata_unitate(int s, int* t, int n, int* sol)

{ int i, sr;

sr=s;
for(i=0;i<n;i++)

{ sol[i]=sr/t[i];

sr=sr%t[i];

void sortare (int x[], int n)

int i,j, aux;

for (i=0;i<n-1;i++)

for(j=i+1;j<n;j++)

if(x[i]<x[j])

aux=x[i];x[i]=x[j];x[j]=aux;

void main()

int t[100], sol[100], s,n,i;

printf("n="); scanf("%d", &n);

for(i=0;i<n;i++)

{printf("t[%d]=",i); scanf("%d", &t[i]);}

printf("Suma de plata:"); scanf("%d", &s);

sortare(t,n); // se sorteaza vectorul cu tipurile de bacnote in ordine descrescatoare a valorilor

plata_unitate(s,t,n,sol);

for(i=0;i<n;i++)

printf("%d x %d \n", sol[i],t[i]);


}

Daca vectorul initial nu este sortat, el va fi sortat inainte de apelarea subprogramului

5. Fiind dat numărul natural k>1 să se scrie programul care determină cel mai mic număr natural n
având exact k divizori naturali proprii (diferiți de 1 și de n).

Verificările vor începe de la k+2, dacă are k divizori proprii. Verificările vor continua până la determinarea
primului număr natural care are exact k divizori proprii, care va reprezenta și cel mai mic număr natural
cu proprietatea cerută.

#include <stdio.h>
int verificare (int n, int k)

{ int i, d=0;

for (i=2; i<=n/2;i++)

if(n%i==0)d++;

int r=(d==k)?1:0;

return r;

int divizori(int k)

{ int i, n;

n=k+2;

while (verificare(n,k)==0)n++;

return n;

void main()

{ int k;

printf("Introduceti nr. de divizori proprii:");

scanf("%d", &k);

printf("Cel mai mic nr. natural cu %d divizori proprii este %d\n", k,divizori(k));

}
Seminar 5
Lucrul cu fișiere text și fișiere binare – probleme economice

Probleme propuse:

1. Fie un fişier organizat secvențial, cu date referitoare la punctele obținute de studenți la disciplina Algoritmi și
tehnici de programare. Articolele au următoarea structură:
Puncte proba practică Puncte teme (0-1)
Nr. matricol Nume şi prenume Grupa Puncte examen (0-50)
(0-30) 1 2 … 10
cha cha cha cha
int char[30] int char char
r r r r
Scrieți programul care înregistrează în fișier punctajul obținut de un student la examen. Studentul este identificat
prin numărul matricol. Programul trebuie să ofere posibilitatea repetării, pentru înregistrarea punctajelor mai
multor studenți.
Includeți în program un subprogram care generează, într-un fișier text, o listă cu toate datele din fișierul binar.
Lista trebuie să apară ca tabel, cu coloanele corect aliniate.

2. Fie un fişier organizat secvențial, cu date referitoare la punctele obținute de studenți la disciplina Algoritmi și
tehnici de programare. Articolele au următoarea structură:
Puncte proba practică Puncte teme (0-1)
Nr. matricol Nume şi prenume Grupa Puncte examen (0-50)
(0-30) 1 2 … 10
cha cha cha cha
int char[30] int char char
r r r r
Scrieți programul care înregistrează înmatricularea unui nou student (punctajele vor avea valori nule).
Includeți în program un subprogram care generează, într-un fișier text, o listă cu toate datele din fișierul binar.
Lista trebuie să apară ca tabel, cu coloanele corect aliniate.

3. Fie un fişier organizat relativ, cu date referitoare la absențele studenților de la cursuri și seminarii/laboratoare
pentru fiecare dintre cele 15 discipline specifice fiecărui an de studiu. Cheia relativă este numărul matricol al
studentului (domeniul de valori pentru numerele matricole începe de la 0). Articolele au următoarea structură:
An Absențe (0-14)
Indicator de stare (0/1) Nr. matricol Nume şi prenume Grupa
(1-5) 1 2 … 15
C S C S C S
char int char[25] int char
char char char char char char
Numărul de activități la fiecare disciplină este 14.
Scrieţi un program care înregistrează exmatricularea unui student. Studentul este identificat prin numărul matricol.
Includeți în program un subprogram care generează, într-un fișier text, o listă cu toate datele din fișierul binar.
Lista trebuie să apară ca tabel, cu coloanele corect aliniate.

4. Fie un fişier organizat relativ, cu date referitoare la absențele studenților de la cursuri și seminarii/laboratoare
pentru fiecare dintre cele 15 discipline specifice fiecărui an de studiu. Cheia relativă este numărul matricol al
studentului (domeniul de valori pentru numerele matricole începe de la 0). Articolele au următoarea structură:
An Absențe (0-14)
Indicator de stare (0/1) Nr. matricol Nume şi prenume Grupa
(1-5) 1 2 … 15
C S C S C S
char int char[25] int char
char char char char char char
Numărul de activități la fiecare disciplină este 14.

11
Scrieţi un program care înregistrează o nouă absență a unui student la o activitate. Studentul este identificat prin
numărul matricol, activitatea este identificată prin poziția în vector (1-15) și tip (Curs/Seminar).
Includeți în program un subprogram care generează, într-un fișier text, o listă cu toate datele din fișierul binar.
Lista trebuie să apară ca tabel, cu coloanele corect aliniate.

Exemple de rezolvare:
Observatii:
 Fisierul binar trebuie sa se regaseasca in directorul proiectului
 În Visual Studio versiuni mai noi extensia fișierului sursă trebuie să fie .c (nu .cpp) – (dacă aveți eroare
legată de funcția gets)
 #define _CRT_SECURE_NO_WARNINGS //în cazul în care apar avertismente legate de securitate

1. Problema 1
//Fisierul binar se numeste [Link]
#include<stdio.h>

typedef struct{int nr;


char nume[30];
int grupa;
char pp;
char teme[10];
char examen;
}STUDENT;

void lista_studenti(FILE *f)


{
char nume[30];
STUDENT s;
FILE *g;
int n,i;
printf("\nFisier rezultat (text): ");gets(nume);
g=fopen(nume,"w");
fprintf(g,"\nNr. Nr. mat Nume %15s Grupa PP Punctaj teme Examen"," ");
fread(&s,sizeof(STUDENT),1,f);
n=0;
while(!feof(f))
{
fprintf(g,"\n%3d %8d %-30s %2d %2d ",++n,[Link], [Link],[Link], [Link]);
for(i=0;i<10;i++)
fprintf(g,"%2d ",[Link][i]);
fprintf(g,"%6d ",[Link]);
fread(&s,sizeof(STUDENT),1,f);
}
fclose(g);
}

void main()
{FILE *f;
12
char nume[30];
STUDENT s;
int gasit,n,j;
printf("\nFisier: ");gets(nume);
f=fopen(nume,"rb+");
if(!f)printf("\nFisierul %s nu poate fi deschis", nume);
else
{
lista_studenti(f);
printf("\nNr. matricol: ");scanf("%d",&n);
while(!feof(stdin))
{rewind(f);
fread(&s,sizeof(STUDENT),1,f);
gasit=0;
while((!feof(f))&&(gasit==0))
{
if(n==[Link])
{gasit=1;
printf("\[Link]:%3d Nume: %-30s Grupa: %4d Punctaj pp: %2d\n: ",[Link],[Link],[Link], [Link]);
for(j=0;j<10;j++)
printf("%2d ",[Link][j]);
printf("Punctaj examen: %2d ",[Link]);
printf("Introduceti punctajul la examen:");
scanf ("%d", &[Link]);
fseek(f, ftell(f)-sizeof(STUDENT),0);
fwrite(&s, sizeof(STUDENT), 1, f);
}else
fread(&s,sizeof(STUDENT),1,f);
}
if(gasit==0)printf("\nNu a fost gasit.");
printf("\nNr. matricol: ");scanf("%d",&n);
}
fclose(f);}
}

13
Va fi creat fișierul text cu studenții din fișierul binar

2. Problema 2

//Fisierul binar se numeste [Link]


#include<stdio.h>

typedef struct{int nr;


char nume[30];
int grupa;
char pp;
char teme[10];
char examen;
}STUDENT;

void lista_studenti(FILE *f)


{
char nume[30];
STUDENT s;
FILE *g;
int n,i;
printf("\nFisier rezultat (text): ");gets(nume);
g=fopen(nume,"w");
fprintf(g,"\nNr. Nr. mat Nume %15s Grupa PP Punctaj teme Examen"," ");
fread(&s,sizeof(STUDENT),1,f);
n=0;
while(!feof(f))
{
fprintf(g,"\n%3d %8d %-30s %2d %2d ",++n,[Link], [Link],[Link], [Link]);

14
for(i=0;i<10;i++)
fprintf(g,"%2d ",[Link][i]);
fprintf(g,"%6d ",[Link]);
fread(&s,sizeof(STUDENT),1,f);
}
fclose(g);
}

void main()
{FILE *f;
char nume[30];
STUDENT s;
int i;
printf("\nFisier: ");gets(nume);
f=fopen(nume,"rb+");
if(!f)printf("\nFisierul %s nu poate fi deschis", nume);
else
{
lista_studenti(f);
fseek(f,0,2);//pozitionare la sfarsitul fisierului
printf("[Link]: ");scanf("%d",&[Link]);
while(!feof(stdin))
{
printf("Nume: ");getchar(); gets([Link]);
printf("Grupa: ");scanf("%d",&[Link]);
[Link]=0;
for(i=0;i<10;i++)[Link][i]=0;
[Link]=0;
fwrite(&s,sizeof(STUDENT),1,f);
printf("[Link]: ");scanf("%d",&[Link]);
}
fclose(f);}
}

La urmatoarea listare in fișier text se poate observa că cei doi studenți au fost adaugați la sfârșitul fișierului

15
3. Problema 3

//Fisierul binar se numeste [Link]


#include<stdio.h>
typedef struct {char c,s;} DISCIPLINA;
typedef struct{
char is;
int nr;
char nume[25];
int grupa;
char an;
DISCIPLINA abs[15];
}STUDENT;

void lista_studenti(FILE *f)


{
char nume[30];
STUDENT s;
FILE *g;
int n,i;
printf("\nFisier rezultat (text): ");gets(nume);
g=fopen(nume,"w");
fprintf(g,"\nNr. Nr. mat Nume Grupa An Absente curs si seminar ");
fread(&s,sizeof(STUDENT),1,f);
n=0;
while(!feof(f))
{
if([Link]==1)
{fprintf(g,"\n%3d %8d %-30s %6d %4d ",++n,[Link], [Link],[Link], [Link]);
for(i=0;i<15;i++)
fprintf(g,"%2d,%2d ",[Link][i].c,[Link][i].s);
}
fread(&s,sizeof(STUDENT),1,f);
}
fclose(g);
}

int nrart(FILE *f, int l)


16
{long p; int n;
p=ftell(f);
fseek(f,0,2);
n=ftell(f)/l;
fseek(f,p,0);
return n;
}

void main()
{FILE *f;
char nume[30];
STUDENT s;
int n;
printf("\nFisier: ");gets(nume);
f=fopen(nume,"rb+");
if(!f)printf("\nFisierul %s nu poate fi deschis", nume);
else
{
lista_studenti(f);
printf("\n Numarul matricol:"); scanf("%d",&n);
while(!feof(stdin))
{
if(n>=nrart(f,sizeof(STUDENT)))
printf("Studentul nu exista!");
else{
fseek(f,n*sizeof(STUDENT),0);
fread(&s,sizeof(STUDENT), 1, f);
if([Link]==0) printf("Studentul nu exista!");
else{
fseek(f,n*sizeof(STUDENT),0);
printf("Studentul %s va fi exmatriculat: ",[Link]);
[Link]=0;
fwrite(&s,sizeof(STUDENT), 1, f);
}
}
printf("\nNumar matricol: "); scanf("%d",&n);
}
fclose(f);}
}

După încă o apelare se poate observa că în fișierul text rezultat nu mai apare studentul cu numarul matricol
respectiv
17
4. Problema 4 – tema
Obs. Este tot o modificare (ca si la problema anterioara), dar a altui câmp din structura articolului

Important: se va avea în vedere lucrul cu fișiere binare organizate secvențial și relativ (a se vedea toate operațiile
discutate la curs), precum și rapoarte în fișiere text

18
Seminar 7
Complexitatea algoritmilor. Algoritmi de sortare

1. Complexitatea algoritmilor
- Calcul timp de execuție pentru sortarea prin inserție
2. Algoritmi de sortare
1. Sortarea Shell
2. Sortarea prin interclasare
3. Sortarea Heap
4. Sortarea rapidă
5. Sortarea prin numărare

1. Complexitatea algoritmilor
- Exemplu - Calcul timp de execuție pentru sortarea prin inserție
Obs:
 Timpul total de execuție este egal cu numărul prelucrărilor elementare executate.
 Termenul dominant este termenul care devine semnificativ mai mare decât ceilalți atunci
când dimensiunea problemei crește (dictează comportarea algoritmului când dimensiunea
problemei crește).
 Ordinul de creștere caracterizează creșterea termenului dominant al timpului de execuție
în raport cu dimensiunea problemei.
Pseudocod:
do-for j=2,n,1

{ aux=x[j];

i=j-1;

while i≥1 and x[i]>aux do

{ x[i+1]=x[i];

i=i-1

}endwhile

X[i+1]=aux

}enddo

19
Operație Cost Nr. repetări Caz favorabil Caz nefavorabil
1 C1 n n n
2 C2 n-1 n-1 n-1
3 C3 n-1 n-1 n-1
𝑛 𝑛 𝑛
4 C4 𝑛(𝑛 + 1)
1=n-1
𝑡𝑗 𝑗=2 𝑗= ―1
𝑗=2
2
𝑗=2
𝑛 𝑛
5 C5 0 𝑛(𝑛 ― 1)
(𝑡𝑗 ― 1) 𝑗=
𝑗=2
2
𝑗=2
𝑛 𝑛
6 C6 0 𝑛(𝑛 ― 1)
(𝑡𝑗 ― 1) 𝑗=
𝑗=2
2
𝑗=2
7 C7 n-1 n-1 n-1
T(n) (c1+c2+c3+c4+c7)n- ( c4/2 + c5/2 + c6/2 )n2
(c2+c3-c4-c7) +(c1+c2+c3+ c4/2 − c5/2
− c6/2
+c7)n−(c2+c3+c4+c7)

O(n) O(n2)

Cazul cel mai favorabil: tabloul este sortat.

tj = 1, j = 2, . . . , n

T(n) = (c1 + c2 + c3 + c4 + c7)n − (c2 + c3 + c4 + c7)

Cazul cel mai nefavorabil: tabloul este sortat în ordine inversă.

tj = j, j = 2, . . . , n

T(n) = c1n+c2(n-1)+c3(n-1)+c4(n(n+1)/2-1)+c5n(n-1)/2+c6n(n-1)/2+c7(n-1)=

=( c4/2 + c5/2 + c6/2 )n2 +(c1+c2+c3+ c4/2 − c5/2 − c6/2 +c7)n−(c2+c3+c4+c7)

2. Algoritmi de sortare
2.1. Sortarea Shell (cu micsorarea incrementului)

#include<stdio.h>
void sort_shell(double v[], int l)
{ int i,j,inc;
double a;

20
for(inc=l/2;inc>0;inc=inc/2)
for(i=inc;i<l;i++)
for(j=i-inc;(j>=0)&&(v[j]>v[j+inc]);j=j-inc)
{ a=v[j];
v[j]=v[j+inc];
v[j+inc]=a;
}
}

void main()
{ int n,i; double v[1000];
printf("n="); scanf("%i",&n);
for(i=0;i<n;i++)
{
printf("v[%d]=",i);
scanf("%lf",&v[i]);
}
sort_shell(v,n);
printf("Vectorul sortat\n");
for(i=0;i<n;i++) printf("%8.4lf ",v[i]);
}

2.2. Sortarea prin interclasare

#include<stdio.h>
void interclasare(float v[], int ls, int m, int ld)
{ int i, j, k; float v1[100];
for(i=ls, j=m+1, k=0; i<=m && j<=ld;k++)
v1[k]= (v[i]<v[j])?v[i++]:v[j++];
while(i<=m)v1[k++]=v[i++];
while(j<=ld)v1[k++]=v[j++];
for(i=0;i<k;i++)v[ls+i]=v1[i]; //for(i=ls;i<=ld;i++) v[i]=v1[i-ls];
}

void sortare(float v[], int ls, int ld)


{ int m;
if(ld-ls>0)
{ m=(ld+ls)/2;
sortare(v, ls, m);
sortare(v, m+1, ld);
interclasare(v, ls, m, ld);
}
}
void main()

21
{ int n,i; float x[100];
printf("n="); scanf("%d", &n);
for(i=0;i<n;i++)
{printf("x[%d]=", i);scanf("%f", &x[i]);}
sortare(x, 0, n-1);
for (i=0;i<n;i++) printf("%3.1f ", x[i]);
}

2.3. Sortarea Heap


#include<stdio.h>
int stanga(int i)
{return 2*i+1;}
int dreapta(int i)
{return 2*i+2;}

void aranjeaza_heap(double *H, int i, int n)


{ int s,d,max;
s=stanga(i);
d=dreapta(i);
if ((s<n)&&(H[s]>H[i]))max=s;
else max=i;
if ((d<n)&&(H[d]>H[max]))
max=d;
if(max!=i){ double aux=H[i];
H[i]=H[max];
H[max]=aux;
aranjeaza_heap(H,max,n);
}
}
void construieste_heap (double *H, int n)
{ int i;

22
for (i=n/2;i>=0;i--)
aranjeaza_heap(H,i,n);
}

void heap_sort(double *H, int n)


{ int i;
construieste_heap(H,n);
for(i=n-1;i>=1;i--){
double aux=H[i];
H[i]=H[0];
H[0]=aux;
aranjeaza_heap(H,0,i);
}
}

void main()
{ int n,i; double x[100];
printf("n="); scanf("%d", &n);
for(i=0;i<n;i++)
{printf("x[%d]=", i);scanf("%lf", &x[i]);}
heap_sort(x,n);
for (i=0;i<n;i++) printf("%3.1lf ", x[i]);
}

2.4. Sortarea rapida

23
#include<stdio.h>
int poz(float *x,int p,int u)
{ int i,j,l,di,dj;
float v;
//di, dj: pasii de incrementare pentru i si j; ei indica sensul parcurgerii
i=p;j=u;di=0;dj=-1;
while(i<j)
if (x[i]>x[j])
{ v=x[i];x[i]=x[j];x[j]=v;
l=di;di=-dj;dj=-l;
i+=di;j+=dj;
}
else
{ i+=di;j+=dj;
}
return i;
}

void quick(float *x,int p,int u)


{ int i;
if (p<u)
{ i=poz(x,p,u);
quick(x,p,i-1);
quick(x,i+1,u);
}
}

void main()
{ int n,i; float v[1000];
printf("Dimensiunea vectorului:"); scanf("%i",&n);
printf("Elementele vectorului\n");
for(i=0;i<n;i++)
{
printf("v[%d]=",i);
scanf("%f",&v[i]);
}
quick(v,0,n-1);
printf("Vectorul sortat\n");
for(i=0;i<n;i++) printf("%8.4f ",v[i]);
}

2.5. Sortarea prin numarare

#include<stdio.h>
#include<malloc.h>

24
void sort_numarare(double v[], int l)
{ int i,j, *num; double *temp;
temp=(double*)malloc(l*sizeof(double));
num=(int*)malloc(l*sizeof(int));

for(i=0;i<l;i++) num[i]=0;
for(i=0;i<l-1;i++)
for(j=i+1;j<l;j++)
if(v[j]<v[i]) num[i]++;
else num[j]++;

for(i=0;i<l;i++) temp[num[i]]=v[i];

for(i=0;i<l;i++) v[i]=temp[i];

free(temp); free(num);
}

void main()
{ int n,i; double x[100];
printf("n="); scanf("%d", &n);
for(i=0;i<n;i++)
{printf("x[%d]=", i);scanf("%lf", &x[i]);}
sort_numarare(x,n);
for (i=0;i<n;i++) printf("%3.1lf ", x[i]);
}

25
Sortarea HEAP (Heapsort)

Contents
1. Heap-uri ............................................................................................................................................1
2. Reconstituirea proprietații de heap..................................................................................................3
3. Construirea unui heap ......................................................................................................................6
4. Algoritmul heapsort..........................................................................................................................9
5. Cozi de prioritați .............................................................................................................................11

Timpul de execuție al agoritmului heapsort este O(n lg n), asemănător sortării prin
interclasare. Heapsort ordonează elementele în spațiul alocat vectorului, asemănător sortării prin
inserție: la un moment dat doar un număr constant de elemente ale vectorului sunt păstrate în afara
spațiului alocat vectorului de intrare. Astfel, algoritmul heapsort combină calitațile celor doi
algoritmi de sortare menționați mai sus.
Heapsort introduce o tehnică nouă de proiectare a algoritmilor bazată pe utilizarea unei
structuri de date. Structura de date heap este utilă nu doar pentru algoritmul heapsort, ea poate fi
la fel de utilă și în tratarea eficientă a unei cozi de prioritate. Termenul heap a fost introdus și
utilizat inițial în contextul algoritmului heapsort, dar acesta se foloseste și în legatură cu alocarea
dinamică.

1. Heap-uri

Structura de date heap (binar) este un vector care poate fi vizualizat sub forma unui arbore
binar aproape complet, conform figurii 1. Fiecare nod al arborelui corespunde unui element al
vectorului care conține valorile atasate nodurilor. Arborele este plin, exceptând eventual nivelul
inferior, care este plin de la stânga la dreapta doar pâna la un anumit loc. Un vector A care
reprezintă un heap are două atribute: lungime[A], reprezintă numărul elementelor din vector și
dimensiune-heap[A] reprezintă numărul elementelor heap-ului memorat în vectorul A. Astfel,

Pag 26 din 57
chiar dacă A[1..lungime[A]] conține în fiecare element al său date valide, este posibil ca
elementele urmatoare elementului A[dimensiune-heap[A]], unde dimensiune-heap[A] ≤
lungime[A], să nu aparțină heap-ului. Radacina arborelui este A[1]. Dat fiind un indice i,
corespunzator unui nod, se pot determina ușor indicii părintelui acestuia Parinte(i), al fiului
Stânga(i) și al fiului Dreapta(i).
Parinte(i)
returneaza [i/2]
Stânga(i)
returneaza 2i
Dreapta(i)
returneaza 2i + 1

1
16

2 3
14 10

4 5 6 7
8 7 9 3

8 9 10
2 4 1

Figura 1. a)

1 2 3 4 5 6 7 8 9 10
16 14 10 8 7 9 3 2 4 1

Figura 1. b)
Figura 1. Un heap reprezentat sub forma unui arbore binar (a) și sub forma unui vector (b)

Numerele înscrise în cercurile reprezentând nodurile arborelui sunt valorile atasate


nodurilor, iar cele scrise lânga cercuri sunt indicii elementelor corespunzatoare din vector. În cele
mai multe cazuri, procedura Stânga poate calcula valoarea 2i cu o singura instrucțiune, translatând
reprezentarea binară a lui i la stânga cu o poziție binară. Similar, procedura Dreapta poate
determina rapid valoarea 2i + 1, translatând reprezentarea binară a lui i la stânga cu o poziție binară,
iar bitul nou intrat pe poziția binară cea mai nesemnificativa va fi 1. În procedura Parinte valoarea
[i/2] se va calcula prin translatarea cu o poziție binară la dreapta a reprezentării binare a lui i. Într-o

Pag 27 din 57
implementare eficientă a algoritmului heapsort, aceste proceduri sunt adeseori codificate sub
forma unor “macro-uri” sau a unor proceduri “in-line”.
Pentru orice nod i, diferit de rădăcină, este adevarată urmatoarea proprietate de heap:
A[Parinte(i)] ≥A[i], (1)
adică valoarea atașată nodului este mai mică sau egală cu valoarea asociată părintelui său. Astfel
cel mai mare element din heap este păstrat în rădăcină, iar valorile nodurilor oricărui subarbore al
unui nod sunt mai mici sau egale cu valoarea nodului respectiv.
Definim înalțimea unui nod al arborelui ca fiind numărul muchiilor aparținând celui mai
lung drum care leagă nodul respectiv cu o frunză, iar înălțimea arborelui ca fiind înălțimea
rădăcinii. Deoarece un heap cu n elemente corespunde unui arbore binar complet, înălțimea
acestuia este Θ(lg n). Timpul de execuție al operațiilor de bază, care se efectuează pe un heap, este
proporțional cu înălțimea arborelui și este O(lg n). În cele ce urmează, sunt prezentate cinci
proceduri și modul lor de utilizare în algoritmul de sortare, respectiv într-o structură de tip coadă
de prioritate.
 Procedura Reconstituie-Heap are timpul de execuție O(lg n) și este de primă importanță în
întreținerea proprietații de heap (1);
 Procedura Construieste-Heap are un timp de execuție liniar și generează un heap dintr-un
vector neordonat, furnizat la intrare;
 Procedura Heapsort se execută în timpul O(n lg n) și ordonează un vector în spațiul alocat
acestuia;
 Procedurile Extrage-Max și Insereaza se execută în timpul O(lg n), iar cu ajutorul lor se
poate utiliza un heap în realizarea unei cozi de prioritate.

2. Reconstituirea proprietații de heap

Procedura Reconstituie-Heap este un subprogram important în prelucrarea heap-urilor.


Datele de intrare ale acesteia sunt un vector A și un indice i din vector. Atunci când se apelează
Reconstituie-Heap, se presupune că subarborii, având ca rădăcini nodurile Stânga(i) respectiv
Dreapta(i), sunt heap-uri. Dar, cum elementul A[i] poate fi mai mic decât descendenții săi, acesta
nu respectă proprietatea de heap (1). Sarcina procedurii Reconstituie-Heap este de a “scufunda” în
heap valoarea A[i], astfel încât subarborele care are în rădăcină valoarea elementului de indice i,
să devină un heap.
Reconstituie-Heap(A, i)
l ←Stânga(i)
r ← Dreapta(i)

Pag 28 din 57
dacă l ≤ dimesiune-heap[A] și A[l] > A[i] atunci
maxim ← l
altfel
maxim ← i
dacă r ≤ dimesiune-heap[A] și A[r] > A[maxim] atunci
maxim ← r
dacă maxim ≠ i atunci
schimbă A[i] ↔ A[maxim]
Reconstituie-Heap(A, maxim)

Figura 2 ilustrează efectul procedurii Reconstituie-Heap. La fiecare pas se determină cel


mai mare element dintre A[i], A[Stânga(i)] și A[Dreapta(i)], iar indicele său se păstrează în
variabila maxim. Dacă A[i] este cel mai mare, atunci subarborele având ca radacină nodul i este
un heap și procedura se termină. În caz contrar, cel mai mare element este unul dintre cei doi
descendenți și A[i] este interschimbat cu A[maxim]. Astfel, nodul i și descendenții săi satisfac
proprietatea de heap. Nodul maxim are acum valoarea inițiala a lui A[i], deci este posibil ca
subarborele de radacină maxim să nu îndeplinească proprietatea de heap. Rezultă că procedura
Reconstituie-Heap trebuie apelată recursiv din nou pentru acest subarbore.

1
16

2 3
i 4 10

4 5 6 7
14 7 9 3

8 9 10
2 8 1

Figura 2. a)

Pag 29 din 57
1
16

2 3
14 10

4 5 6 7
i 4 7 9 3

8 9 10
2 8 1

Figura 2. b)

1
16

2 3
14 10

4 5 6 7
8 7 9 3

8 9 10
2 4 1

Figura 2. c)

Figura 2. Efectul procedurii Reconstituie-Heap(A, 2), unde dimesiune-heap[A] = 10. (a)


Configurația inițială a heap-ului, unde A[2] (pentru nodul i = 2), nu respectă proprietatea de heap
deoarece nu este mai mare decât descendenții săi. Proprietatea de heap este restabilită pentru nodul
2 în (b) prin interschimbarea lui A[2] cu A[4], ceea ce anulează proprietatea de heap pentru nodul
4. Apelul recursiv al procedurii Reconstituie-Heap(A, 4) poziționează valoarea lui i pe 4. După
interschimbarea lui A[4] cu A[9], asa cum se vede în (c), nodul 4 ajunge la locul său și apelul
recursiv Reconstituie-Heap(A, 9) nu mai găseste elemente care nu îndeplinesc proprietatea de
heap.
Timpul de execuție al procedurii Reconstituie-Heap, corespunzator unui arbore de radacină
i și dimensiune n, este Θ(1), timp în care se pot analiza relațiile dintre A[i], A[Stânga(i)] și
A[Dreapta(i)] la care trebuie adăugat timpul în care Reconstituie-Heap se execută pentru
subarborele având ca radacină unul dintre descendenții lui i. Dimensiunea acestor subarbori este
de cel mult 2n/3 – cazul cel mai defavorabil fiind acela în care nivelul inferior al arborelui este
plin exact pe jumatate – astfel, timpul de execuție al procedurii Reconstituie-Heap poate fi descris
prin urmatoarea inegalitate recursivă:
T(n) ≤ T(2n/3) + Θ(1).

Pag 30 din 57
Soluția acestei recurențe se obține pe baza celui de-al doilea caz al teoremei master: T(n)
= O(lg n). Timpul de execuție al procedurii Reconstituie-Heap pentru un nod de înalțime h poate
fi exprimat alternativ ca fiind egal cu O(h).

3. Construirea unui heap

Procedura Reconstituie-Heap poate fi utilizată “de jos în sus” pentru transformarea


vectorului A[1..n] în heap, unde n = lungime[A]. Deoarece toate elementele subșirului
A[([n/2]+1)..n] sunt frunze, acestea pot fi considerate ca fiind heap-uri formate din câte un element.
Astfel, procedura Construieste-Heap trebuie să traverseze doar restul elementelor și să execute
procedura Reconstituie-Heap pentru fiecare nod întâlnit. Ordinea de prelucrare a nodurilor asigură
ca subarborii, având ca rădăcină descendenți ai nodului i să formeze heap-uri înainte ca
Reconstituie-Heap să fie executat pentru aceste noduri.

Construieste-Heap(A)
dimesiune-heap[A] ← lungime[A]
pentru i ← [lungime[A]/2],1 execută
Reconstituie-Heap(A, i)

Figura 3 ilustrează modul de funcționare al procedurii Construieste-Heap. Timpul de


execuție al procedurii Construieste-Heap poate fi calculat simplu, determinând limita superioară
a acestuia: fiecare apel al procedurii Reconstituie-Heap necesită un timp O(lg n) și, deoarece pot
fi O(n) asemenea apeluri, rezultă că timpul de execuție poate fi cel mult O(n lg n). Această estimare
este corectă, dar nu este suficient de tare asimptotic.
Vom obține o limită mai tare observând că timpul de execuție a procedurii Reconstituie-
Heap depinde de înălțimea nodului în arbore, aceasta fiind mică pentru majoritatea nodurilor.
Estimarea mai riguroasă se bazează pe faptul că un heap având n elemente are înalțimea lg n și că
pentru orice înălțime h, în heap există cel mult [n/2h+1] noduri de înalțime h.

1 2 3 4 5 6 7 8 9 10
A 4 1 3 2 16 9 10 14 8 7

1
4

2 3
1 3

4 5 6 7
2 i 16 9 10

8 9 10
14 8 7

Pag 31 din 57
Figura 3. a)

1
4

2 3
1 3

4 5 6 7
i 2 16 9 10

8 9 10
14 8 7

Figura 3. b)
1
4

2 3
1 3 i

4 5 6 7
14 16 9 10

8 9 10
2 8 7

Figura 3. c)
1
4

2 3
i 1 10

4 5 6 7
14 16 9 3

8 9 10
2 8 7

Figura 3. d)

Pag 32 din 57
1
i 4

2 3
16 10

4 5 6 7
14 7 9 3

8 9 10
2 8 1

Figura 3. e)
1
16

2 3
14 10

4 5 6 7
8 7 9 3

8 9 10
2 4 1

Figura 3. f)

Figura 3. Modul de execuție a procedurii Construieste-Heap. În figură se vizualizează structurile


de date în starea lor anterioară apelului procedurii Reconstituie-Heap (linia 3 din procedura
Construieste-Heap). (a) Se consideră un vector A având 10 elemente și arborele binar
corespunzător. După cum se vede în figură, variabila de control i a ciclului, în momentul apelului
Reconstituie-Heap(A, i), indică nodul 5. (b) reprezintă rezultatul; variabila de control i a ciclului
acum indică nodul 4. (c) - (e) vizualizează iterațiile succesive ale ciclului pentru din Construieste-
Heap. Se observă că, atunci când se apelează procedura Reconstituie-Heap pentru un nod dat,
subarborii acestui nod sunt deja heap-uri. (f) reprezintă heap-ul final al procedurii Construieste-
Heap.

Timpul de execuție a procedurii Reconstituie-Heap pentru un nod de înalțime h fiind O(h), obținem
pentru timpul de execuție a procedurii Construieste-Heap:
lg 𝑛] 𝑛
∑[ℎ=0 [𝑙𝑔𝑛] ℎ
[2ℎ+1]𝑂(ℎ) = 𝑂 𝑛 ∑ℎ=0 2ℎ (2)( )
Ultima însumare poate fi evaluată prin înlocuirea x:
1
ℎ 2
∑∞
ℎ=0 2ℎ ― 1 2
= 2 (3)
(1 ― ) 2

Pag 33 din 57
Astfel, timpul de execuµie al procedurii Construieste-Heap poate fi estimat ca fiind:
( [lg〖𝑛]〗 ℎ
) ∞
(ℎ
𝑂 𝑛 ∑ℎ=0 ℎ = 𝑂 𝑛 ∑ℎ=0 ℎ = 𝑂(𝑛) (4)
2 2 )
De aici rezultă că se poate construi un heap dintr-un vector într-un timp liniar.

4. Algoritmul heapsort

Algoritmul heapsort începe cu apelul procedurii Construieste-Heap în scopul transformării


vectorului de intrare A[1..n] în heap, unde n = lungime[A]. Deoarece cel mai mare element al
vectorului este atașat nodului rădăcină A[1], acesta va ocupa locul definitiv în vectorul ordonat
prin interschimbarea sa cu A[n]. În continuare, “excluzând” din heap cel de-al n-lea element (și
micsorând cu 1 dimesiune-heap[A]), restul de A[1..(n-1)] elemente se pot transforma usor în heap,
deoarece subarborii nodului rădăcină au proprietatea de heap (1), cu eventuala excepție a
elementului ajuns în nodul rădăcină.

Heapsort(A)
Construieste-Heap(A)
pentru i ← lungime[A], 2 execută
schimbă A[1] ↔ A[i]
dimesiune-heap[A] ← dimesiune-heap[A] - 1
Reconstituie-Heap(A,1)

Apelând procedura Reconstituie-Heap(A,1) se restabilește proprietatea de heap pentru


vectorul A[1..(n-1)]. Acest procedeu se repetă micsorând dimensiunea heap-ului de la n-1 la 2.
Figura 4 ilustrează, pe un exemplu, modul de funcționare a procedurii Heapsort, după ce
în prealabil datele au fost transformate în heap. Fiecare heap reprezintă starea inițiala la începutul
pasului iterativ (linia 2 din ciclul pentru).
Timpul de execuție al procedurii Heapsort este O(n lg n), deoarece procedura Construieste-
Heap se execută într-un timp O(n), iar procedura Reconstituie-Heap, apelată de n-1 ori, se execută
în timpul O(lg n).

16 14

14 10 8 10

8 7 9 3 4 7 9 3

2 4 1 2 1 16 i

Figura 4. a) Figura 4. b)

Pag 34 din 57
10 9

8 9 8 3

4 7 1 3 4 7 1 2

i i
2 14 16 10 14 16

Figura 4. c) Figura 4. d)
8 7

7 3 4 3

4 2 1 i 9 1 2 8 i 9

10 14 16 10 14 16

Figura 4. e) Figura 4. f)
4 3

2 3 2 1

1 7 i 8 9 4 i 7 8 9

10 14 16 10 14 16

Figura 4. g) Figura 4. h)
2 1

1 3 i 2 i 3

4 7 8 9 4 7 8 9

10 14 16 10 14 16

Figura 4. i) Figura 4. j)

1 2 3 4 5 6 7 8 9 10
A 1 2 3 4 7 8 9 10 14 16

Figura 4. Modul de funcționare a algoritmului Heapsort. (a) Structura de date heap, imediat după
construirea sa de către procedura Construieste-Heap. (b)-(j) Heap-ul, imediat după câte un apel al
procedurii Reconstituie-Heap (linia 5 în algoritm). Figura reprezintă valoarea curenta a variabilei
i. Din heap fac parte doar nodurile din cercurile nehașurate. (k) Vectorul A ordonat, obținut ca
rezultat.

Pag 35 din 57
5. Cozi de prioritați

Heapsort este un algoritm excelent, dar o implementare bună a algoritmului de sortare


rapidă, se poate dovedi de multe ori mai eficientă. În schimb, structura de date heap este foarte
utilă. În această secțiune este prezentată una din cele mai frecvente aplicații ale unui heap:
utilizarea lui sub forma unei cozi de prioritați.
Coada de prioritați este o structură de date care conține o mulțime S de elemente, fiecare
având asociată o valoare numita cheie. Asupra unei cozi de prioritați se pot efectua urmatoarele
operații:
 Inserează(S, x) inserează elementul x în mulțimea S. Această operație poate fi scrisă în felul
următor: 𝑆←𝑆 ∪ {𝑥};
 Maxim(S) returnează elementul din S având cheia cea mai mare;
 Extrage-Max(S) elimină și returnează elementul din S având cheia cea mai mare.

O aplicație a cozilor de prioritați constă în planificarea lucrărilor pe calculatoare partajate.


Sarcinile care trebuie efectuate și prioritațile relative se memorează într-o coadă de prioritate. Când
o lucrare este terminată sau întreruptă, procedura Extrage-Max va selecta lucrarea având prioritatea
cea mai mare dintre lucrările în asteptare. Cu ajutorul procedurii Inserare, în coadă poate fi
introdusă oricând o lucrare nouă.
O coadă de prioritați poate fi utilizată și în simularea unor evenimente controlate. Din coadă
fac parte evenimentele de simulat, fiecăruia atasându-i-se o cheie reprezentând momentul când va
avea loc evenimentul. Evenimentele trebuie simulate în ordinea desfășurării lor în timp, deoarece
simularea unui eveniment poate determina necesitatea simulării altuia în viitor. În cazul acestei
aplicații, este evident că ordinea evenimentelor în coada de prioritați trebuie inversată, iar
procedurile Maxim și Extrage-Max se vor înlocui cu Minim și Extrage-Min. Programul de simulare
va determina urmatorul eveniment cu ajutorul procedurii Extrage-Min, iar dacă va trebui introdus
un nou element în șir, se va apela procedura Inserare.
Rezultă în mod firesc faptul că o coadă de prioritați poate fi implementată utilizând un
heap. Operația Maxim-Heap va determina în timpul Θ(1) cel mai mare element al heap-ului care,
de fapt, este valoarea A[1]. Procedura Extrage-Max-Din-Heap este similară structurii repetitive
pentru (liniile 3–5) din procedura Heapsort:

Extrage-Max-Din-Heap(A)
dacă dimesiune-heap[A] < 1 atunci
eroare “depășire inferioară heap”
max ←A[1]
A[1] ← A[dimesiune-heap[A]]
dimesiune-heap[A] ← dimesiune-heap[A] - 1
Reconstituie-Heap(A,1)
returnează maxim

Timpul de execuție al procedurii Extrage-Max-Din-Heap este O(lg n), deoarece conține


doar câțiva pași, care se execută în timp constant înainte să se execute procedura Reconstituie-
Heap, care necesită un timp de O(lg n).

Pag 36 din 57
Procedura Insereaza-În-Heap inserează un nod în heap-ul A. La prima expandare a heap-
ului se adaugă o frunză arborelui. Apoi, se traversează un drum pornind de la această frunză către
rădăcină, în scopul găsirii locului definitiv al noului element.

Insereaza-În-Heap(A,cheie)
dimesiune-heap[A] ← dimesiune-heap[A] + 1
i ←dimesiune-heap[A]
cât timp i > 1 și A[Parinte(i)] < cheie execută
A[i] ←A[Parinte(i)]
i ←Parinte(i)
A[i] ← cheie

În figura 5 este ilustrat un exemplu al operației Insereaza-În-Heap. Timpul de execuție al


procedurii Insereaza-În-Heap, pentru un heap având n elemente, este O(lg n), deoarece drumul
parcurs de la nouă frunză către rădăcină are lungimea O(lg n).
În concluzie, pe un heap se poate efectua orice operație specifică unei cozi de prioritați,
definită pe o mulțime având n elemente, într-un timp O(lg n).

16 16

14 10 14 10

8 7 9 3 8 7 9 3

2 4 1 2 4 1

Figura 5. a) Figura 5. b)
16 16

10 15 10

8 14 9 3 8 14 9 3

2 4 1 7 2 4 1 7

Figura 5. c) Figura 5. d)

Figura 5. Operația Insereaza-În-Heap. (a) Heap-ul din figura 4(a) înainte de inserarea nodului
având cheia 15. (b) Se adaugă arborelui o frunză nouă. (c) Se “scufundă” valorile de pe drumul
dintre frunză și rădăcină până la găsirea nodului corespunzator cheii 15. (d) Se inserează cheia 15.

Pag 37 din 57
Sortări în timp liniar

Cuprins
1. Sortarea prin numărare (Counting Sort)...........................................................................................1
2. Sortare pe baza cifrelor (Radix Sort).................................................................................................3
3. Sortarea pe grupe (Bucket Sort) .......................................................................................................3

În cursurile precedente au fost prezentaţi câţiva algoritmi de complexitate O(n lg n).


Această margine superioară este atinsă de algoritmii de sortare prin interclasare şi heapsort în cazul
cel mai defavorabil; respectiv pentru quicksort corespunde în medie. Mai mult decât atât, pentru
fiecare dintre aceşti algoritmi putem efectiv obţine câte o secvenţă de n numere de intrare care
determină execuţia algoritmului în timpul Ω(n lg n).
Aceşti algoritmi au o proprietate interesantă: ordinea dorită este determinată în exclusivitate pe
baza comparaţiilor între elementele de intrare. Astfel de algoritmi de sortare se numesc sortări prin
comparaţii. Orice sortare prin comparaţii trebuie să facă, în cel mai defavorabil caz, Ω(n lg
n) comparaţii pentru a sorta o secvenţă de n elemente. Astfel, algoritmul de sortare prin interclasare
şi heapsort sunt asimptotic optimale şi nu există nicio sortare prin comparaţii care să fie mai rapidă
decât cu un factor constant.
In continuare sunt prezentați trei algoritmi de sortare care se execută în timp liniar. Acești
algoritmi sunt sortarea prin numărare, ordonare pe baza cifrelor (radix sort) şi ordonare pe grupe
(Bucket sort). Pentru a determina ordinea de sortare, cei trei algoritmi menționați folosesc alte
operaţii decât comparaţiile, pentru ei neaplicându-se marginea inferioară Ω(n lg n).

6. Sortarea prin numărare (Counting Sort)

In cadrul sortării prin numărare se presupune că fiecare dintre cele n elemente ale datelor de
intrare este un număr întreg între 1 şi k, pentru un număr întreg k oarecare. Când k = O(n), sortarea
se execută în timpul O(n).
Ideea de bază în sortarea prin numărare este de a determina numărul de elemente mai mici
decât x, pentru fiecare element x din datele de intrare. Această informaţie poate fi utilizată pentru
a poziţiona elementul x direct pe poziţia sa din tabloul de ieşire. De exemplu, dacă există 10
elemente mai mici decât x, atunci x va fi pe poziţia 11 în tabloul de ieşire.
Un caz particular apare atunci când în datele de intrare există mai multe elemente care au
aceeași valoare și acestea nu trebuie așezate toate pe aceeași poziție. In acest caz, schema de
rezolvare trebuie puțin modificată.

Pag 38 din 57
În algoritmul pentru sortarea prin numărare presupunem că datele de intrare formează un tablou
A[1..n], şi deci lungime [A] = n (figura 1). Sunt necesare alte două tablouri suplimentare:
- tabloul B [1..n] care cuprinde datele de ieşire sortate
- tabloul C [1..k] pentru stocarea temporară în timpul lucrului.

Fig. 1 Algoritm sortare prin numărare

Modul de funcţionare al algoritmului de sortare prin numărare pe un tablou A[1..8] de intrare,


unde fiecare element din tabloul A este un număr întreg pozitiv nu mai mare decât k = 6 (figura
2).
(a) Tabloul A (tabloul cu valorile date la intrare) şi tabloul auxiliar C după executarea liniei
4 din algoritm. In tabloul C avem:
- ca poziții: numerele de la 1 la 6 (intervalul pentru numerele oferite)
- ca valori: numărul de apariții pentru fiecare valoare din tabloul A.
Exemple:
- în tabloul C pe poziția 1 apare valoarea 2. Valoarea 1 apare de 2 ori în tabloul A.
- în tabloul C pe poziția 2 apare valoarea 0. Valoarea 2 nu apare în tabloul A.
(b) Tabloul C după executarea liniei 7 din algoritm.
(c)-(e) Tabloul de ieşire B şi tabloul auxiliar C după unul, două, respectiv trei iteraţii ale
buclei din liniile 9–11. Doar elementele haşurate din tabloul B au fost completate.
(f) Tabloul B sortat, furnizat la ieşire.

Fig. 2 Sortare prin numărare - exemplu

Pag 39 din 57
Algoritmul de sortare prin numărare conform algoritmului descris presupune:
- iniţializarea din liniile 1–2
- în liniile 3–4 se contorizează fiecare element de intrare. Dacă valoarea unui element
de intrare este i, se incrementează valoarea lui C [i]. Astfel, după liniile 3–4, C[i]
păstrează un număr de elemente de intrare egal cu i pentru fiecare număr întreg i =
1, 2, … , k.
- în liniile 6–7 se determină, pentru fiecare i = 1, 2,…, k, numărul elementelor de
intrare mai mici sau egale decât i; aceasta se realizează prin păstrarea în C[k] a
sumei primelor k elemente din tabloul iniţial.
- în liniile 9–11, fiecare element A[j] se plasează pe poziţia sa corect determinată din
tabloul de ieşire B, astfel încât acesta este ordonat. Dacă toate cele n elemente sunt
distincte, la prima execuţie a liniei 9, pentru fiecare A[j], valoarea C[A[j]] este
poziţia finală corectă a lui A[j] în tabloul de ieşire, întrucât există C[A[j]] elemente
mai mici sau egale cu A[j]. Deoarece elementele ar putea să nu fie distincte,
decrementăm valoarea C[A[j]] de fiecare dată când plasăm o valoare A[j] în tabloul
B; aceasta face ca următorul element de intrare cu o valoare egală cu A[j], dacă
există vreunul, să meargă în poziţia imediat dinaintea lui A[j] în tabloul de ieşire.

Timp de execuție. Timp necesar pentru realizarea sortării prin numărare.


- bucla pentru din liniile 1–2 necesită timpul O(k)
- bucla pentru din liniile 3–4 necesită timpul O(n)
- bucla pentru din liniile 6–7 necesită timpul O(k)
- bucla pentru din liniile 9–11 necesită timpul O(n).
Deci timpul total este O(k + n).
În practică se utilizează sortarea prin numărare când avem k = O(n), caz în care timpul necesar
este O(n).
O proprietate importantă a sortării prin numărare este stabilitatea: numerele cu aceeaşi valoare
apar în tabloul de ieşire în aceeaşi ordine în care se găsesc în tabloul de intrare. Adică,legăturile
dintre două numere sunt distruse de regula conform căreia oricare număr care apare primul în
vectorul de intrare, va apărea primul şi în vectorul de ieşire. Desigur, stabilitatea este importantă
numai când datele învecinate sunt deplasate împreună cu elementul în curs de sortare.

7. Sortare pe baza cifrelor (Radix Sort)

Pag 40 din 57
Ordonarea pe baza cifrelor este algoritmul folosit de către maşinile de sortare a cartelelor,
care se mai găsesc la ora actuală numai în muzeele de calculatoare.
Modalitate de organizare a cartelelor. Cartelele sunt organizate în 80 de coloane, şi fiecare
coloană poate fi perforată în 12 poziţii. Sortatorul poate fi “programat” mecanic să examineze o
coloană dată a fiecărei cartele dintr-un pachet şi să distribuie fiecare cartelă într-una dintre cele 12
cutii, în funcţie de poziţia perforată. Un operator poate apoi să strângă cartelele cutie cu cutie,
astfel încât cartelele care au prima poziţie perforată să fie deasupra cartelelor care au a doua poziţie
perforată ş.a.m.d.
Pentru cifre zecimale sunt folosite numai 10 poziţii în fiecare coloană. (Celelalte două poziţii
sunt folosite pentru codificarea caracterelor nenumerice). Un număr având d cifre ar ocupa un
câmp format din d coloane. Întrucât sortatorul de cartele poate analiza numai o singură coloană la
un moment dat, problema sortării a n cartele în funcţie de un număr având d cifre necesită un
algoritm de sortare.
Intuitiv, s-ar putea sorta numerele în funcţie de cea mai semnificativă cifră, să se sorteze recursiv
fiecare dintre cutiile ce se obţin, şi apoi să se combine pachetele în ordine. Un dezavantaj al acestei
abordări ar fi că acele cartele din 9 dintre cele 10 cutii trebuie să fie păstrate pentru a putea sorta
fiecare dintre cutii, această procedură generând teancuri intermediare de cartele care trebuie
urmărite.
Ordonarea pe baza cifrelor rezolvă problema sortării cartelelor într-un mod care contrazice
intuiţia, sortând întâi în funcţie de cea mai puţin semnificativă cifră. Cartelele sunt apoi combinate
într-un singur pachet, cele din cutia 0 precedând cartelele din cutia 1, iar acestea din urmă
precedând pe cele din cutia 2 şi aşa mai departe. Apoi întregul pachet este sortat din nou în funcţie
de a doua cifră cea mai puţin semnificativă şi recombinat apoi într-o manieră asemănătoare.
Procesul continuă până când cartelele au fost sortate pe baza tuturor celor d cifre.
De remarcat că în acel moment cartelele sunt complet sortate în funcţie de numărul având d
cifre. Astfel, pentru sortare sunt necesare numai d treceri prin lista de numere. În figura 3 este
ilustrat modul de operare al algoritmului de ordonare pe baza cifrelor pe un “pachet” de şapte
numere de câte trei cifre.

Fig. 3 Sortare pe baza cifrelor - exemplu

Pag 41 din 57
Modul de funcţionare al algoritmului de ordonare pe baza cifrelor pe o listă de şapte numere a
câte 3 cifre (figura 3):
- prima coloană este intrarea
- celelalte coloane prezintă lista după sortări succesive în funcţie de poziţiile cifrelor în
ordinea crescătoare a semnificaţiei.
- săgeţile verticale indică poziţia cifrei după care s-a sortat pentru a produce fiecare listă din
cea precedentă.
Este important ca sortarea cifrelor în acest algoritm să fie stabilă. Sortarea realizată de către un
sortator de cartele este stabilă, dar operatorul trebuie să fie atent să nu schimbe ordinea cartelelelor
pe măsură ce acestea ies dintr-o cutie, chiar dacă toate cartelele dintr-o cutie au aceeaşi cifră în
coloana aleasă.
Într-un calculator care funcţionează pe bază de acces secvenţial aleator, ordonarea pe baza cifrelor
este uneori utilizată pentru a sorta înregistrările de informaţii care sunt indexate cu chei având
câmpuri multiple.
De exemplu, am putea dori să sortăm date în funcţie de trei parametri: an, lună, zi.
Am putea executa un algoritm de sortare cu o funcţie de comparare care, considerând două date
calendaristice, compară anii, şi dacă există o legătură, compară lunile, iar dacă apare din nou o
legătură, compară zilele. Alternativ, am putea sorta informaţia de trei ori cu o sortare stabilă: prima
după zi, următoarea după lună, şi ultima după an.
Pseudocodul pentru algoritmul de ordonare pe baza cifrelor este intuitiv (figura 4). Următoarea
procedură presupune:
- că într-un tablou A având n elemente, fiecare element are d cifre
- cifra 1 este cifra cu ordinul cel mai mic
- cifra d este cifra cu ordinul cel mai mare.

Fig. 4 Algoritm de sortare pe baza cifrelor

Timp de execuție. Analiza timpului de execuţie depinde de sortarea stabilă folosită ca algoritm
intermediar de sortare. Când fiecare cifră este în intervalul [1; k], iar k nu este prea mare, sortarea
prin numărare este opţiunea evidentă. Fiecare trecere printr-o mulţime de n numere a câte d cifre
se face în timpul ʘ(n + k). Se fac d treceri, astfel încât timpul total necesar pentru algoritmul de
ordonare pe baza cifrelor este ʘ(dn + kd). Când d este constant şi k = O(n), algoritmul de ordonare
pe baza cifrelor se execută în timp liniar.
Unii informaticieni consideră că numărul biţilor într-un cuvânt calculator este ʘ(lg n). Pentru
exemplificare, să presupunem că d lg n este numărul de biţi, unde d este o constantă pozitivă.

Pag 42 din 57
Atunci, dacă fiecare număr care va fi sortat încape într-un cuvânt al calculatorului, îl vom putea
trata ca pe un număr având d cifre reprezentat în baza n.
De exemplu, să considerăm sortarea a 1 milion de numere având 64 de biţi. Tratând aceste numere
ca numere de patru cifre în baza 216, putem să le sortăm pe baza cifrelor doar prin patru treceri,
comparativ cu o sortare clasică prin comparaţii de timp ʘ(n lg n) care necesită aproximativ lg n =
20 de operaţii pentru fiecare număr sortat.
Din păcate, versiunea algoritmului de ordonare pe baza cifrelor care foloseşte sortarea prin
numărare ca sortare intermediară stabilă nu sortează pe loc, lucru care se întâmplă în cazul multora
din sortările prin comparaţii de timp ʘ(n lg n). Astfel, dacă se doreşte ca necesarul de memorie să
fie mic, atunci este preferabil algoritmul de sortare rapidă.

8. Sortarea pe grupe (Bucket Sort)

Sortarea pe grupe se execută, în medie, în timp liniar. Ca şi sortarea prin numărare, cea pe
grupe este rapidă pentru că face anumite presupuneri despre datele de intrare. În timp ce sortarea
prin numărare presupune că intrarea constă din întregi dintr-un domeniu mic, ordonarea pe grupe
presupune că intrarea este generată de un proces aleator care distribuie elementele în mod uniform
în intervalul [0; 1).
Principiul algoritmului de ordonare pe grupe este de a împărţi intervalul [0; 1) în n
subintervale egale, numite grupe (engl. buckets) şi apoi să distribuie cele n numere de intrare în
aceste grupe. Întrucât datele de intrare sunt uniform distribuite în intervalul [0; 1), nu ne aşteptăm
să fie prea multe numere în fiecare grupă. Pentru a obţine rezultatul dorit, sortăm numerele din
fiecare grupă, apoi trecem prin fiecare grupă în ordine, listând elementele din fiecare.
Pseudocodul pentru ordonarea pe grupe presupune că datele de intrare formează un tablou
A având n elemente şi că fiecare element A[i] satisface relaţia 0 <= A[i] < 1.
Codul necesită un tablou auxiliar B[0..n-1] de liste înlănţuite (reprezentând grupele) şi
presupune că există un mecanism pentru menţinerea acestor liste.

Fig. 5 Algoritm pentru sortarea pe grupe

Pentru a demonstra că acest algoritm funcţionează corect, se consideră două elemente A[i] şi A[j].
Dacă aceste elemente sunt distribuie în aceeaşi grupă, ele apar în ordinea relativă adecvată în
secvenţa de ieşire, deoarece grupa lor este sortată de sortarea prin inserţie. Să presupunem, totuşi,
că ele sunt distribuite în grupe diferite. Fie aceste grupe B[i’] şi respectiv B[j’], şi să presupunem,
fără a pierde din generalitate, că i’ < j’ . Când listele lui B sunt concatenate în linia 6, elementele
grupei B[i’] apar înaintea elementelor lui B [j’], şi astfel A[i] precede A[j] în secvenţa de ieşire.

Pag 43 din 57
Timp de execuție. Pentru a analiza timpul de execuţie, să observăm mai întâi că toate liniile, cu
excepţia liniei 5, necesită, în cel mai defavorabil caz, timpul O(n). Timpul total pentru a examina
în linia 5 toate grupele este O(n) şi, astfel, singura parte interesantă a analizei este timpul consumat
de sortările prin inserţie din linia 5.
Pentru a analiza costul sortărilor prin inserţie, fie ni o variabilă aleatoare desemnând numărul de
elemente din grupa B[i]. Întrucât sortarea prin inserţie se execută în timp pătratic (timpul de
execuţie în cazul cel mai defavorabil poate fi exprimat sub forma a*n2 + b*n + c, unde constantele
a, b şi c depind, din nou, de costurile ci ale instrucţiunilor, fiind astfel o funcţie pătratică de n),
timpul necesar sortării elementelor în grupele B [i] este:

În consecinţă, timpul mediu total necesar sortării tuturor elementelor în toate grupele va fi:

In concluzie, timpul mediu pentru sortarea prin inserţie este O(n). Astfel, întregul algoritm de
ordonare pe grupe se execută în timp mediu liniar.

Exemplul ilustrează modul de funcţionare al algoritmului de ordonare pe grupe pe un tablou de


intrare cu 10 numere (figura 6).

Fig. 6 Algoritm pentru sortarea pe grupe – exemplu

Descriere etape exemplu:


a) Tabloul de intrare A[1..10].
b) Tabloul B[0..9] al listelor (reprezentând grupele) sortate după linia a cincea a algoritmului.
Grupa i cuprinde valorile din intervalul [i/10, (i + 1)/10). Ieşirea sortată constă dintr-o
concatenare în ordine a listelor B [0], B [1] , …, B [9].

Pag 44 din 57
Lucrul cu fişiere organizate relativ: exemple

Enunțuri:
1. Să se scrie programul care creează un fişier organizat relativ cu date despre produsele unei societăţi
comerciale. Datele care se reţin despre produse sunt: denumirea, pret, cantitate (maxim 12). Cheia relativă
a fişierului este codul produsului. Datele se preiau de la tastatură, sfârşitul introducerii fiind marcat
standard. (exemplu de creare). Să se modifice programul pentru a realiza și operația de adăugare.
2. Să se scrie programul care listează, într-un fişier text, produsele cu cea mai mare valoare pe an. (exemplu
de consultare cu selecție)
3. Să se scrie programul pentru stergerea produselor care au inregistrat productie zero în cel puţin trei luni.
(exemplu de ștergere cu selecție)

Temă:
4. Să se scrie programul care modifică o cantitate pentru produsele ale căror coduri se introduc de la
tastatură. De la tastatură se va introduce luna (1-12) pentru care se modifica cantitatea. (exemplu de
modificare cod unic) Să se modifice programul astfel încât să se realizeze operațiile de consultare și
ștergere articol unic.
5. Să se scrie programul care listează într-un fişier text toate produsele realizate de societatea comercială.
(exemplu de consultare integrală)

Pag 45 din 57
Rezolvări:

1. Să se scrie programul care creează un fişier organizat relativ cu date despre produsele unei societăţi
comerciale. Datele care se reţin despre produse sunt: denumirea, pret, cantitate (maxim 12). Cheia relativă
a fişierului este codul produsului. Datele se preiau de la tastatură, sfârşitul introducerii fiind marcat
standard.
#define _CRT_SECURE_NO_WARNINGS //în cazul în care apar avertismente de securitate
#include<stdio.h>
typedef struct{
char denumire[20];
float pret;
int cant[12];
char is;
}PRODUS;
int nrart(FILE *f, int l)
{long p; int n;
p=ftell(f); fseek(f,0,2); n=ftell(f)/l;
fseek(f,p,0);return n;}
void main()
{FILE *f;
char nume[20]; PRODUS p; int i, cod;
printf("\nFisier: ");gets(nume);
f=fopen(nume,"wb+");
printf("\n Cod produs"); scanf("%d",&cod);
while(!feof(stdin))
{if(cod>=nrart(f,sizeof(PRODUS)))
{[Link]=0; fseek(f,0,2);
for(i=nrart(f,sizeof(PRODUS));i<=cod;i++)
fwrite(&p,sizeof(PRODUS), 1, f);
}
fseek(f,cod*sizeof(PRODUS),0); fread(&p,sizeof(PRODUS), 1, f);
if([Link]) printf("\nExista deja un produs cu acest cod");
else{fseek(f,cod*sizeof(PRODUS),0);
printf("Denumire: "); getchar();gets([Link]);
printf("pret:"); scanf("%f",&[Link]);
for(i=0;i<12;i++) {printf("Cant %d: ",i+1); scanf("%d",&[Link][i]);}
[Link]=1; fwrite(&p,sizeof(PRODUS), 1, f);}
printf("\nCod: "); scanf("%d",&cod);}
fclose(f);}

Pag 46 din 57
2. Să se scrie programul care listează, într-un fişier text, produsele cu cea mai mare valoare pe an.
#define _CRT_SECURE_NO_WARNINGS
#include<stdio.h>
typedef struct{
char denumire[20];
float pret;

int cant[12];
char is;
}PRODUS;
void main()
{FILE *f, *g; char nume[20];
PRODUS p; float max, val; int i, cod;
printf("\nFisier: ");gets(nume);
f=fopen(nume,"rb");
if(!f)printf("\nFisierul %s nu poate fi deschis",nume);
else{printf("\nFisier text: "); gets(nume); g=fopen(nume,"w");
max=0; cod=0;
fprintf(g,"\n Produsele sunt:");
fread(&p,sizeof(PRODUS), 1, f);
while(!feof(f))

Pag 47 din 57
{if([Link])
{val=0;
for(i=0;i<12;i++)val+=[Link][i]*[Link];
if(val>max)
{fclose(g);
g=fopen(nume,"w");
max=val;
fprintf(g,"\n Produsele cu valoarea %5.2f sunt:", max);
}
if(val==max)
fprintf(g,"\n%4d %-30s",cod, [Link]);}
cod++;
fread(&p,sizeof(PRODUS), 1, f);}
fclose(g);
fclose(f);}
}

3. Să se scrie programul pentru stergerea produselor care au inregistrat productie zero în cel puţin trei luni.

#define _CRT_SECURE_NO_WARNINGS
#include<stdio.h>
typedef struct{
char denumire[20];
float pret;
int cant[12];
char is;
}PRODUS;
void main()
{FILE *f; char nume[20]; PRODUS p; int i, e, n;
printf("\nFisier: ");gets(nume);
f=fopen(nume,"rb+");
if(!f)printf("\nFisierul %s nu poate fi deschis",nume);
else{n=0;
fread(&p,sizeof(PRODUS), 1, f);
Pag 48 din 57
while(!feof(f))
{if([Link])
{e=0;
for(i=0;i<12 && e<3;i++) if([Link][i]==0) e++;
if(e>=3)
{printf("\n%-30s",[Link]);
fseek(f,ftell(f)-sizeof(PRODUS),SEEK_SET);
[Link]=0; fwrite(&p,sizeof(PRODUS), 1, f);
fseek(f,0,1);
n++;}
}
fread(&p,sizeof(PRODUS), 1, f);}
fclose(f); printf("\nAu fost sterse %d produse",n);}
}

Pag 49 din 57
Lucrul cu fișiere text și fișiere binare – probleme economice

Probleme propuse:

1. Fie un fişier organizat secvențial, cu date referitoare la punctele obținute de studenți la disciplina Algoritmi și
tehnici de programare. Articolele au următoarea structură:
Puncte proba practică Puncte teme (0-1)
Nr. matricol Nume şi prenume Grupa Puncte examen (0-50)
(0-30) 1 2 … 10
cha cha cha cha
int char[30] int char char
r r r r
Scrieți programul care înregistrează în fișier punctajul obținut de un student la examen. Studentul este identificat
prin numărul matricol. Programul trebuie să ofere posibilitatea repetării, pentru înregistrarea punctajelor mai
multor studenți.
Includeți în program un subprogram care generează, într-un fișier text, o listă cu toate datele din fișierul binar.
Lista trebuie să apară ca tabel, cu coloanele corect aliniate.

2. Fie un fişier organizat secvențial, cu date referitoare la punctele obținute de studenți la disciplina Algoritmi și
tehnici de programare. Articolele au următoarea structură:
Puncte proba practică Puncte teme (0-1)
Nr. matricol Nume şi prenume Grupa Puncte examen (0-50)
(0-30) 1 2 … 10
cha cha cha cha
int char[30] int char char
r r r r
Scrieți programul care înregistrează înmatricularea unui nou student (punctajele vor avea valori nule).
Includeți în program un subprogram care generează, într-un fișier text, o listă cu toate datele din fișierul binar.
Lista trebuie să apară ca tabel, cu coloanele corect aliniate.

3. Fie un fişier organizat relativ, cu date referitoare la absențele studenților de la cursuri și seminarii/laboratoare
pentru fiecare dintre cele 15 discipline specifice fiecărui an de studiu. Cheia relativă este numărul matricol al
studentului (domeniul de valori pentru numerele matricole începe de la 0). Articolele au următoarea structură:
An Absențe (0-14)
Indicator de stare (0/1) Nr. matricol Nume şi prenume Grupa
(1-5) 1 2 … 15
C S C S C S
char int char[25] int char
char char char char char char
Numărul de activități la fiecare disciplină este 14.
Scrieţi un program care înregistrează exmatricularea unui student. Studentul este identificat prin numărul matricol.
Includeți în program un subprogram care generează, într-un fișier text, o listă cu toate datele din fișierul binar.
Lista trebuie să apară ca tabel, cu coloanele corect aliniate.

4. Fie un fişier organizat relativ, cu date referitoare la absențele studenților de la cursuri și seminarii/laboratoare
pentru fiecare dintre cele 15 discipline specifice fiecărui an de studiu. Cheia relativă este numărul matricol al
studentului (domeniul de valori pentru numerele matricole începe de la 0). Articolele au următoarea structură:
An Absențe (0-14)
Indicator de stare (0/1) Nr. matricol Nume şi prenume Grupa
(1-5) 1 2 … 15
C S C S C S
char int char[25] int char
char char char char char char
Numărul de activități la fiecare disciplină este 14.
Scrieţi un program care înregistrează o nouă absență a unui student la o activitate. Studentul este identificat prin
numărul matricol, activitatea este identificată prin poziția în vector (1-15) și tip (Curs/Seminar).
50
Includeți în program un subprogram care generează, într-un fișier text, o listă cu toate datele din fișierul binar.
Lista trebuie să apară ca tabel, cu coloanele corect aliniate.

Exemple de rezolvare:
Observatii:
 Fisierul binar trebuie sa se regaseasca in directorul proiectului
 În Visual Studio versiuni mai noi extensia fișierului sursă trebuie să fie .c (nu .cpp) – (dacă aveți eroare
legată de funcția gets)
 #define _CRT_SECURE_NO_WARNINGS //în cazul în care apar avertismente legate de securitate

1. Problema 1
//Fisierul binar se numeste [Link]
#include<stdio.h>

typedef struct{int nr;


char nume[30];
int grupa;
char pp;
char teme[10];
char examen;
}STUDENT;

void lista_studenti(FILE *f)


{
char nume[30];
STUDENT s;
FILE *g;
int n,i;
printf("\nFisier rezultat (text): ");gets(nume);
g=fopen(nume,"w");
fprintf(g,"\nNr. Nr. mat Nume %15s Grupa PP Punctaj teme Examen"," ");
fread(&s,sizeof(STUDENT),1,f);
n=0;
while(!feof(f))
{
fprintf(g,"\n%3d %8d %-30s %2d %2d ",++n,[Link], [Link],[Link], [Link]);
for(i=0;i<10;i++)
fprintf(g,"%2d ",[Link][i]);
fprintf(g,"%6d ",[Link]);
fread(&s,sizeof(STUDENT),1,f);
}
fclose(g);
}

void main()
{FILE *f;
char nume[30];

51
STUDENT s;
int gasit,n,j;
printf("\nFisier: ");gets(nume);
f=fopen(nume,"rb+");
if(!f)printf("\nFisierul %s nu poate fi deschis", nume);
else
{
lista_studenti(f);
printf("\nNr. matricol: ");scanf("%d",&n);
while(!feof(stdin))
{rewind(f);
fread(&s,sizeof(STUDENT),1,f);
gasit=0;
while((!feof(f))&&(gasit==0))
{
if(n==[Link])
{gasit=1;
printf("\[Link]:%3d Nume: %-30s Grupa: %4d Punctaj pp: %2d\n: ",[Link],[Link],[Link], [Link]);
for(j=0;j<10;j++)
printf("%2d ",[Link][j]);
printf("Punctaj examen: %2d ",[Link]);
printf("Introduceti punctajul la examen:");
scanf ("%d", &[Link]);
fseek(f, ftell(f)-sizeof(STUDENT),0);
fwrite(&s, sizeof(STUDENT), 1, f);
}else
fread(&s,sizeof(STUDENT),1,f);
}
if(gasit==0)printf("\nNu a fost gasit.");
printf("\nNr. matricol: ");scanf("%d",&n);
}
fclose(f);}
}

52
Va fi creat fișierul text cu studenții din fișierul binar

2. Problema 2

//Fisierul binar se numeste [Link]


#include<stdio.h>

typedef struct{int nr;


char nume[30];
int grupa;
char pp;
char teme[10];
char examen;
}STUDENT;

void lista_studenti(FILE *f)


{
char nume[30];
STUDENT s;
FILE *g;
int n,i;
printf("\nFisier rezultat (text): ");gets(nume);
g=fopen(nume,"w");
fprintf(g,"\nNr. Nr. mat Nume %15s Grupa PP Punctaj teme Examen"," ");
fread(&s,sizeof(STUDENT),1,f);
n=0;
while(!feof(f))
{
fprintf(g,"\n%3d %8d %-30s %2d %2d ",++n,[Link], [Link],[Link], [Link]);
for(i=0;i<10;i++)

53
fprintf(g,"%2d ",[Link][i]);
fprintf(g,"%6d ",[Link]);
fread(&s,sizeof(STUDENT),1,f);
}
fclose(g);
}

void main()
{FILE *f;
char nume[30];
STUDENT s;
int i;
printf("\nFisier: ");gets(nume);
f=fopen(nume,"rb+");
if(!f)printf("\nFisierul %s nu poate fi deschis", nume);
else
{
lista_studenti(f);
fseek(f,0,2);//pozitionare la sfarsitul fisierului
printf("[Link]: ");scanf("%d",&[Link]);
while(!feof(stdin))
{
printf("Nume: ");getchar(); gets([Link]);
printf("Grupa: ");scanf("%d",&[Link]);
[Link]=0;
for(i=0;i<10;i++)[Link][i]=0;
[Link]=0;
fwrite(&s,sizeof(STUDENT),1,f);
printf("[Link]: ");scanf("%d",&[Link]);
}
fclose(f);}
}

La urmatoarea listare in fișier text se poate observa că cei doi studenți au fost adaugați la sfârșitul fișierului

54
3. Problema 3

//Fisierul binar se numeste [Link]


#include<stdio.h>
typedef struct {char c,s;} DISCIPLINA;
typedef struct{
char is;
int nr;
char nume[25];
int grupa;
char an;
DISCIPLINA abs[15];
}STUDENT;

void lista_studenti(FILE *f)


{
char nume[30];
STUDENT s;
FILE *g;
int n,i;
printf("\nFisier rezultat (text): ");gets(nume);
g=fopen(nume,"w");
fprintf(g,"\nNr. Nr. mat Nume Grupa An Absente curs si seminar ");
fread(&s,sizeof(STUDENT),1,f);
n=0;
while(!feof(f))
{
if([Link]==1)
{fprintf(g,"\n%3d %8d %-30s %6d %4d ",++n,[Link], [Link],[Link], [Link]);
for(i=0;i<15;i++)
fprintf(g,"%2d,%2d ",[Link][i].c,[Link][i].s);
}
fread(&s,sizeof(STUDENT),1,f);
}
fclose(g);
}

int nrart(FILE *f, int l)


55
{long p; int n;
p=ftell(f);
fseek(f,0,2);
n=ftell(f)/l;
fseek(f,p,0);
return n;
}

void main()
{FILE *f;
char nume[30];
STUDENT s;
int n;
printf("\nFisier: ");gets(nume);
f=fopen(nume,"rb+");
if(!f)printf("\nFisierul %s nu poate fi deschis", nume);
else
{
lista_studenti(f);
printf("\n Numarul matricol:"); scanf("%d",&n);
while(!feof(stdin))
{
if(n>=nrart(f,sizeof(STUDENT)))
printf("Studentul nu exista!");
else{
fseek(f,n*sizeof(STUDENT),0);
fread(&s,sizeof(STUDENT), 1, f);
if([Link]==0) printf("Studentul nu exista!");
else{
fseek(f,n*sizeof(STUDENT),0);
printf("Studentul %s va fi exmatriculat: ",[Link]);
[Link]=0;
fwrite(&s,sizeof(STUDENT), 1, f);
}
}
printf("\nNumar matricol: "); scanf("%d",&n);
}
fclose(f);}
}

După încă o apelare se poate observa că în fișierul text rezultat nu mai apare studentul cu numarul matricol
respectiv
56
4. Problema 4 – tema
Obs. Este tot o modificare (ca si la problema anterioara), dar a altui câmp din structura articolului

Important: se va avea în vedere lucrul cu fișiere binare organizate secvențial și relativ (a se vedea toate operațiile
discutate la curs), precum și rapoarte în fișiere text

57

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