Sunteți pe pagina 1din 179

Curs 1

 Declararea variabilelor pointer;


 Operatori specifici;
 Apel prin referinta utilizand parametrii de tip
pointer;
 Legatura dintre pointeri si tablouri;
 Operatii cu pointeri.
 Programul si datele sale sunt pastrate in memoria
RAM ( Random Access Memory ) a calculatorului.
Definitie:
 Un pointer este o variabila care pastreaza adresa
unei date, nu valoarea datei.
 Un pointer poate fi utilizat pentru referirea
diferitelor date si structuri de date.
Schimband adresa memorata in pointer, pot fi
manipulate informatii situate la diferite locatii de
memorie.
 Pointerii permit de asemenea crearea de noi
variabile in timpul executiei programului, prin
alocare dinamica.
 Ce este un pointer?

Exista diferite tipuri de date in C:


-- int pentru numere intregi,
-- long pentru numere intregi,
-- char pentru caractere,
-- float si double pentru numere reale.

Pointerul este un tip nou de data dar care spre


deosebire de celelalte tipuri memoreaza doar adrese.
 Ce inseamna asta ?

Cand concepeti un program folositi,bineinteles variabile.


La definirea unei variabile stiti ca e important sa cunoasteti
doua lucruri:
-- tipul si
-- numele acesteia.
In timpul rularii unui program apare un nou termen de
care trebuie tinut cont si anume adresa variabilei.
Un pointer este o variabila care tine minte acest al treilea
termen, adica adresa unei variabile.
 Cum se declara un pointer ?
Inainte de a folosi un pointer trebuie sa stim spre ce fel de
resursa va pointa, adica sa stim ce tip de variabila se gaseste
la adresa care o memoreaza.
In functie de tipul de variabila care se gaseste la adresa
respectiva, se declara pointerul.
Adica, daca avem nevoie, de exemplu, de un pointer care sa
pointeze catre o variabila de tip int il vom declara in felul
urmator:
int *pInt;
Exemple:
 int *i;

 float *j;

 char *c, *p;


 Dupa cum vedeti un pointer se declara ca si o
variabila normala, in cazul nostru de tip int, cu diferenta
ca numele variabilei are un asterisc ( * ) in fata ceea ce ne
spune ca este un pointer, adica este o variabila care
pastreaza o adresa a unei variabile de tip int, nu este o
variabila de tip int. Ca o conventie de nume majoritatea
programatorilor prefera ca atunci cand se creaza un
pointer sa i se dea un nume care incepe cu litera p (de la
pointer).
 Astfel de fiecare data cand folositi variabila respectiva

va amintiti ca e pointer, nu o variabila normala.


Prin analogie va puteti da seama cum se declara pointeri
pentru alte tipuri de date:
char *pCh; // un pointer catre un caracter
int *pfloat; // un pointer catre un numar intreg

Ok. E usor de declarat un pointer, dar cum ii spun sa


memoreze o adresa ?

Daca tot am declarat un pointer e bine sa si memoreze ceva


si ca sa fie util trebuie sa memoreze o adresa.
De fapt, asta e primul lucru care trebuie sa-l faceti dupa ce ati
declarat un pointer: sa-i dati o valoare, sa-l faceti sa pointeze
spre ceva.
Operatori specifici
Operatorul de referentiere ( & )
 Pentru a-i spune unui pointer adresa care trebuie sa o memoreze se
foloseste semnul egal ca in atribuirile normale cu diferenta ca inaintea
variabilei a carei adrese trebuie memorate se pune semnul '&' care ii
spune compilatorului ca acolo NU valoarea variabilei trebuie data ca
rvalue, ci adresa acesteia.
p=&x
Exemplu:
int *int_pointer; //pointer catre intreg
int numar; //variabila intreaga
int_pointer = &numar;

Aceasta bucata de cod introduce in int_pointer(variabila care poate stoca


adrese catre locații int) adresa variabilei numar. Aceasta adresa este
locatia interna din calculator a variabilei numar si nu are nici o legatura
cu valoarea din variabila numar. Puteti sa considerati & ca returnand
"adresa lui X"( X fiind variabila din dreapta operatorului). Dea aceea,
instructiunea de atribuire precedenta inseamna "int_pointer primeste
adresa variabilei numar".
 Operatorul de dereferentiere ( * )
OK, am invatat sa asociem o adresa trebuie sa vedem si cum se poate
citi (afisa) valoarea care se gaseste la adresa respectiva.
Al doilea operator pentru pointeri , * , este complementul lui &
El este un operator care returneaza valoarea inregistrata la adresa X ( X
fiind în dreapta steluței).
Asta se face ca si cum s-ar citi o valoare normala doar ca numele
Pointerului este prefixat de un asterisc ( * ).

Presupuneti urmatoarea situatie:


Variabila numar se afla la locatia 2000 in memorie.
Presupuneti ca variabila numar are valoarea 100. Daca urmarim codul de
mai sus asta ar inseamna ca pointerul int_pointer are valoarea 2000 (adica
adresa variabilei numar)
Presupunand ca int_pointer inca are valoarea 2000(acea adresa din
memorie):

 int alt_numar;
 alt_numar = *int_pointer;
 Acest cod plaseaza valoarea din variabila numar in
alt_numar. De aceea alt_numar va avea valoarea 100.
Valoarea 100 este stocata la locatia 2000, locatie care a fost
stocata in pointerul int_pointer.
 Va puteti gandi la * ca fiind "de la adresa".

Trebuie sa va asigurati ca pointerii dumneavoastra indica


intotdeauna catre tipul corect de date. Daca declarati un pointer
ca fiind de tipul int, compilatorul intelege ca la adresa pe care o
contine acel pointer va memora o variabila de tip int.
 Asta spune compilatorului sa NU se citeasca/afiseze

continutul pointerului si ceea ce se gaseste la adresa care


este tinuta in pointer.
*p=y
 Pentru variabilele de tip pointer exista doi
operatori specifici, si anume:
---- operatorul de referentiere & sau de adresare
---- operatorul de dereferentiere * sau de indirectare

 Operatorul unar de referentiere & se utilizeaza in


expresii a caror valoare reprezinta adresa variabilei
operand;
 Operatorul * este operator unar de indirectare

care furnizeaza valoarea din zona de memorie a carei


adresa este indicata de operand(variabila pointer)
1. int x;
int *p;
float y; float *q;
p=&x; --- corect
p=&y; --- incorect !!!
q=&y; --- corect
2. Pentru declaratiile :
int x, y;
int *p;
asignarea :
y=x+100;
este echivalenta cu :
p=&x;
y=*p+100;
Iar asignarea
x=y;
poate fi inlocuita cu : p=&x;
*p=y;
3. Urmatoarea secventa de program:
char *pc,c;
pc=&c;
Determina ca, in urma atribuirii, pc sa contina adresa variabilei c. Accesul la o variabila
prin intermediul pointerilor se face cu operatorul de indirectare *. Astfel, *pc este o
variabila de tip char, aflata la adresa pc, adica tocmai variabila c.

4. Exemplu in care un pointer indica obiecte diferite pe parcursul executiei programului:

void main()
{
int i=1, j=5, *p=&i;
*p=2;
(*(p=&j))++;
printf(“i=%d j=%d\n”, i, j);
}
Prin *p=2; se atribuie lui i valoarea 2. Apoi se atribuie lui p adresa lui j, dupa care se
incrementeaza continutul lui p,adica variabila j.Astfel programul afiseaza i=2, j=6.
 #include <stdio.h>
void main(){
int i, j;
int *pInt;

i = 5;
j = 2;

pInt = &i;
printf("*pInt are valoarea %d\n",*pInt);

pInt = &j;
printf("*pInt are valoarea %d\n",*pInt);

}
 Dupa ce compilati si rulati codul de mai sus va trebui sa vedeti
doua linii. Pe prima linie ne spune ca *pInt are valoarea 5 iar pe
cea de-a doua linie afisata ne spune ca *pInt are valoarea 2.
 void main(){
int i;
char sir[] = "abcdefghi";
char *pChar;

pChar = sir;

for( i = 0 ; i < strlen(sir) ; i++ )


printf("%c",*(pChar + i));

}
 Codul de mai sus declara variabila sir si o initializeaza cu sirul de
caractere "abcdefghi". Mai jos declaram un pointer, pChar, care
memoreaza date de tip char.
Mai jos punem in pChar adresa sirului de caractere declarat mai
sus ( sir ).
Initializarea cu adresa unei variabile se realizeaza prin:
tip variabila, *var_pointer;
var_pointer=&var;
Valoarea de la adresa indicata de pointer este data de:
*var_pointer
Spatiul ocupat de o variabila pointer se poate determina prin:
sizeof( var_pointer).
Pentru tiparirea valorii unui pointer se foloseste prototipul %p
valoarea fiind afisata sub forma a 4 cifre hexazezimale.

 Un pointer poate fi utilizat doar dupa initializare: prin atribuirea


adresei unei variabile(statice) sau prin alocarea dinamica.
In concluzie:
-- pointerii reprezinta adrese ale unor zone de memorie ;
-- putem avea acces la acele zone prin operatorul *;
-- dimensiunea si semnificatia unei zone indicate de un pointer
depinde de tipul pointerului.
#include<stdio.h>
int i, *p;
float f;
void main()
{
i=1;
p=&i;
f=-1.5;
printf("i=%d,f=%f,val=%d,p=%p,adr_i=%p,adr_p=p,
adr_f=%p",i,f,*p,p,&i,&p,&f);
printf("%d %d %d %d\n",
sizeof(&i), sizeof(p), sizeof(&p), sizeof(&f));
}
 In C apelul prin valoare, deci parametrilor formali li se
atribuie valorile parametrilor actuali.
 In cazul in care un parametru actual este nume de tablou,
apelul prin valoare devine apel prin referinta(se transmite
adresa primului element al tabloului).
 Folosind parametrii, la apel se poate transmite adresa
operandului(apel prin referinta).
 Exemplu:
int x;
h(x); // se transfera valoarea lui x
……
g(&x); //se transfera adresa lui x
……..
 unde
void h(int i);
void j(int *pi);
………..
*pi=100;

In principiu, in C, apelul prin referinta se poate


realiza daca parametrul actual are ca valoare o
adresa, iar parametru formal corespunzator este
un pointer.
 Numele unui tablou este un pointer, deoarece el are ca
valoare adresa primului sau element. Totusi exista diferenta
intre numele unui tablou si o variabila de tip pointer.

 Unei variabile de tip pointer i se atribuie valori la


executie, in timp ce acest lucru nu este posibil sa se realizeze
pentru numele unui tablou.

 Numele unui tablou are ca valoare, tot timpul adresa


primului sau element. De aceea se obisnuieste sa se spuna ca
numele unui tablou este un pointer constant.
Exemplu:
int t[10]
int *p;
int x;
………
p=t; /* p au ca valoare adresa lui
t[10]*/
x=t[0]; /* x ia valoarea elementului
t[0], acelasi efect cu:*/
x=*p;
 Fie p,q pointeri spre tipul t si r=sizeof(t)
1. Operatiile ++/_ _

Mareste/micsoreaza adresa r (nr. de octeti egal cu


dimensiunea tipului datei spre care pointeaza ).
Exemplu:
int*p
double *p
++p; //p=p+2
- -q; //q=p-8
2. Operatiile +,- cu un intreg
Fie p pointer si n un intreg atunci putem utiliza:
p+n; // p+n*r unde r este numarul de octeti
necesari pentru a pastra in memorie o data de
tipul t
p-n; //p - n*r
Observatie: Daca tab este un tablou unidimensional
de tip t, atunci tab+n  &tab[n]
O expresie de forma: X=tab[n]  x=*(tab+n)
Inlocuirea variabilelor cu indici prin expresii cu
pointeri duc la optimizari , inlocuindu-se operatiile
de inmultire care intervin in evaluarea expresiilor
cu indici prin adunari.
#define N 10
float *p, t[N]
int i;
void main()
{
p=t; // sau p=&t; sau p=&t[0]
scanf(“%f”,&t[2]); //citirea valorii lui t[2]
//scanf (“%f”,p+2)
printf(“%f”,t[2]); // tiparirea valorii lui t[2]
//printf(“%f”,*(p+2));
}
3. Compararea a doi pointeri
Doi pointeri care pointeaza spre elementul aceluiasi
tablou pot fi comparati folosind operatorii de relatie
si egalitate.
Fie : p=&tab[i];
q=&tab[j].
Atunci expresiile :
p<q,p<=q, p>q,p>=q, p==q, p!=q
[i<j] sunt legale.
Operatorii ==, != pot fi folositi pentru a compara
pointerii cu constanta speciala NULL definita in
stdio.h
4. Diferenta a doi pointeri
Doi pointeri care pointeaza spre elementele
aceluiasi tablou pot fi scazuti.
Fie p un pointer spre elementul t[i] al tabloului t si
q un pointer spre elementul t[i+n] al tabloului t
atunci q-p are valoarea n.
Exemplu:
Sa se scrie o functie care citeste cel mult n elemente de
tip double si le pastreaza in zona de memorie a carui
adresa de inceput este valoarea parametrului formal p
al functiei.functia returneaza numarul elementelor
citite.
int citeste (int n, double *p)
{
double d;
int i=0;
double *q=p+n;
while (p<q)
{
printf(“elemetul [%d]=“,i);
scanf(“%f”,&d);
*p++=d;
i++;
}
return i;
}
INTRĂRI ŞI IEŞIRI

- fişier = o colecţie ordonată de elemente, numite


înregistrări, care sunt păstrate pe diferite suporturi
externe.

- fişier de intrare

- fişier de ieşire

- sfârşitul de fişier ( EOF , <Ctrl> Z)

Limbajul C permite operarea cu fişiere:


• de tip text
• de tip binar

Există două canale predefinite:


• stdin – fişier de intrare, text, intrarea standard
(tastatura)
• stdout – fişier de ieşire, text, ieşirea standard
(ecranul)

Tratarea fişierelor se poate face la două niveluri:

- primul nivel face apel direct la SO, acesta este


nivelul inferior de prelucrare a fişierelor,

1
- al doilea nivel se realizează prin intermediul unor
proceduri specializate de prelucrare a fişierelor care
utilizează structuri speciale de tip FILE (fişier).
Acesta este nivelul superior de prelucrare a
fişierelor.

Nivelul inferior de prelucrare a fişierelor

I. Deschiderea unui fişier

int open (const char * cale, int acces);

acces poate avea una din valorile:


O_RDONLY – (read – only);
O_WRONLY – (write – only);
O_RDWR – (read- write);
O_APPEND – (adăugare de înregistrări la sfârşit fişierului);
O_BINARY –indică faptul că fişierul se prelucrează binar;
O_TEXT – arată că fişierul se prelucrează pe caractere, adică este
de tip text.

#include <io.h>
#include <fcntl.h>

Apelul:

df = open (lista_par);

unde df este o variabilă de tip întreg, numită descriptorul de fişier.

2
Exemple:

1. char nume[] = “fis1.dat”;


int df;
df = open (nume, O_RDONLY)
2. int d;
d = open (“C:\\User\\Test.CPP”, O_RDWR);
3. int c;
c = open (”exemplu.c”, O_WRONLY);

II. Crearea
- crearea unui fişier nou
int creat (const char * calea, int mod);

mod reprezintă un întreg care poate fi definit folosind următoarele


constante simbolice:
S_IREAD – în acest caz, proprietarul poate citi fişierul;
S_IWRITE – în acest caz, proprietarul poate scrie în fişier;
S_IEXE – în acest caz, proprietarul poate executa programul
conţinut în fişierul respectiv

#include <io.h>
#include <stat.h>

III. Citirea

int read (int df, void * zona, unsigned lung);

unde

3
df este descriptorul de fişier

zona este un pointer spre zona de memorie în care se păstrează


înregistrarea citită din fişier şi

lung reprezintă lungimea în octeţi a înregistrării citite.

Utilizarea funcţiei read implică includerea fişierului io.h

IV. Scrierea
int write (int df, void * zona, unsigned lung);

Funcţia write returează numărul de octeţi scrişi în fişier, adică o


valoare care coincide cu valoarea celui de-al treilea parametru al
ei.

V. Poziţionarea în fişier
long lseek (int df, long deplasament, int origine);

Utilizarea funcţiei lseek implică includerea fişierului io.h în


programul corespunzător.

VI. Închiderea fişierului

int close (int df);

Utilizarea funcţiei close implică includerea fişierului io.h.

4
Exemplu de utilizare a funcţiilor prezentate:

Să se scrie un program care citeşte un şir de numere de la intrarea


standard şi crează două fişiere: fis1.dat şi fis2.dat, primul conţine
numerele de ordin impar citite, iar cel de-al doilea conţine
numerele de ordin par citite. Să se afişeze conţinutul fişierelor
astfel create.

#include <stdio.h>
#include <conio.h>
#include <stdlib.h>
#include <fcntl.h>
#include <io.h>
#include <sys\stat.h>
void main()
{ clrscr();
union unr
{
float nr;
char tnr[sizeof(float)];
};
union unr nrcit;
int df1,df2;
int i,j;
/* se deschid fisierele fis1.dat si fis2.dat in creare
cu acces in citire-scriere
*/
if((df1=creat("fis1.dat",S_IWRITE|S_IREAD))==-1)
{
printf("nu se poate deschide fisierul fis1.dat in creare \n");
exit(1);
}
if((df2=creat("fis2.dat",S_IWRITE|S_IREAD))==-1)
{
printf("nu se poate deschide fisierul fis2.dat in creare \n");
exit(1);
}

5
/* se citeste sirul de numere si se depune in fisiere */
j=1;
printf("tastati nr. separarte prin caractere albe \n");
while((scanf("%f",&nrcit.nr))==1)
{
if(j&1)
{
if(write(df1,nrcit.tnr,sizeof(float))!=sizeof(float))
{
printf("eroare la crearea fisierului fis1.dat \n");
exit(1);
}
}
else /* j este par */
if(write(df2,nrcit.tnr,sizeof(float))!=sizeof(float))
{
printf("eroare la crearea fisierului fis2.dat \n");
exit(1);
}
j++;
}
/* s-a terminat crearea, se inchid fisierele */
if(close(df1)<0)
{
printf("eroare la inchiderea fisierului fis1.dat \n");
exit(1);
}
if(close(df2)<0)
{
printf("eroare la inchiderea fisierului fis2.dat \n");
exit(1);
}
/* se deschide fis1.dat si se afiseaza continutul */
if((df1=open("fis1.dat",O_RDONLY))==-1)
{
printf("nu se poate deschide fisierul fis1.dat \n");

6
exit(1);
}
while((i=read(df1,nrcit.tnr,sizeof(float)))>0)
printf("%g\t",nrcit.nr);
if(i<0)
{
printf("eroare la citirea fisierului \n");
exit(1);
}
close(df1);
printf("\n");
getch();
/* la fel pentru fis2.dat */
if((df2=open("fis2.dat",O_RDONLY))==-1)
{
printf("nu se poate deschide fisierul fis2.dat \n");
exit(1);
}
while((i=read(df2,nrcit.tnr,sizeof(float)))>0)
printf("%g\t",nrcit.nr);
if(i<0)
{
printf("eroare la citirea fisierului \n");
exit(1);
}
close(df2);
printf("\n");
getch();
}

7
Nivelul superior de prelucrare a fişierelor
Fiecărui fişier i se ataşează o structură de tip FILE, tip
definit în fişierul stdio.h.
Deschiderea unui fişier

FILE * fopen(const char * calea, const char * mod);

returnează un pointer spre tipul FILE (tipul fişier) sau


pointerul nul în caz de eroare

mod este un pointer spre un şir de caractere care defineşte


modul de prelucrare al fişierului după deschidere

“r” – deschidere în citire (read)


“w” – deschidere în scriere (write)
“a” – deschidere pentru adăugare (append)
“r+” – deschidere pentru modificare (citire/scriere)
“rb” – deschidere pentru citire binară
“wb” – deschidere pentru scriere binară
“r+b” – deschidere pentru citire/scriere binară

Fişierele pot fi prelucrate pe caractere:

putc – pentru scriere


getc – pentru citire

int putc (int c, FILE * pf);


int getc (FILE * pf);

1
Închiderea unui fişier

int fclose (FILE *pf);

Exemplu :
Fiind dat un fişier text, să se determine şi să se afişeze câte
litere mari conţine fişierul.

#include <stdio.h>
#include <conio.h>
void main()
{ clrscr();
FILE *df;
char c;
int n=0;
df=fopen("text.txt","r");

// if((df=fopen("c:\\text.txt","rt"))= =0)
//{ printf("Fisierul nu poate fi deschis in citire !");
// exit(1);
//}
while(!feof(df))
{
c=fgetc(df);
if (c>='A' && c<='Z') n++;
}
fclose(df);
printf("in fisier sunt %d litere mari \n",n);
getch();
}

2
Citirea şi scrierea înregistrărilor care sunt şiruri de
caractere.
- funcţiile fgets şi fputs
char * fgets (char *s, int n, FILE *pf);

int fputs ( const char *s, FILE *pf ) ;


Exemplu :
/* Sa se afiseze linia de lungime maxima si numarul de linii
dintr-un fisier text
*/

#include <stdio.h>
#include <conio.h>
#include <string.h>
#include <stdlib.h>
void main()
{ clrscr();
FILE *df;
char linie[256],liniemax[256];
int lmax=0,nrlinii=0;
// df=fopen("text.txt","r");
if((df=fopen("c:\\text.txt","rt"))= =0)
{ printf("Fisierul nu poate fi deschis in citire !");
exit(1);
}
while(!feof(df))
{
fgets(linie,256,df);
nrlinii++;

3
if (lmax<strlen(linie))
{
lmax=strlen(linie);
strcpy(liniemax,linie);
}
}
fclose(df);
//if(close(df1)<0)
//{
//printf("eroare la inchiderea fisierului fis1.dat \n");
//exit(1);
//}
printf("fisierul are %d linii",nrlinii);
printf("\n ultima linie de lungime maxima este: %s\n",
liniemax);
getch();
}

Funcţii care permit operaţii de intrare/ieşire cu format.

- fscanf şi fprintf, care au un parametru de tip pointer


spre tipul FILE

- sscanf şi sprintf, care au un parametru de tip pointer


spre o zonă în care se păstreză caracterele

Funcţia fscanf are prototipul:


int fscanf ( FILE *pf, const char * format , . . . ) ;
Funcţia fprintf are prototipul :
int fprintf ( FILE *pf, const char *format , . . . ) ;

4
Eliberarea zonei tampom folosite de respectivul fişier.
”Curăţirea” (sau vidarea) zonei tampon se realizează cu
funcţia fflush, care are prototipul :
int fflush ( FILE *pf ) ;

Funcţia returnează 0, dacă operaţia a decurs normal şi -1 în


caz de eroare.

Poziţionarea într-un fişier.


Funcţia fseek permite deplasarea capului de citire/scriere al
discului în vederea prelucrării înregistrărilor fişierului
într-o ordine diferită de cea secvenţială.

int fseek ( FILE *pf, long deplasament, int origine ) ;

Funcţia fseek returnează valoarea 0 la o poziţionare corectă


şi o valoare diferită de zero în caz de eroare.
long ftell ( FILE *pf ) ;

Funcţia ftell returnează valoarea care defineşte poziţia


curentă a capului de citire/scriere.
Valoarea returnată de ftell este deplasamentul în octeţi a
poziţiei capului de citire/scriere faţă de începutul fişierului.

5
Prelucrarea fişirelor binare

Fişierele organizate ca date binare pot fi prelucrate folosind


funcţiile fread şi fwrite.

unsigned fread(void *ptr, unsigned dim, unsigned nrart,


FILE *pf);

Funcţia returnează numărul de articole citite sau -1 în caz


de eroare.

unsigned fwrite (void *ptr, unsigned dim, unsigned nrart,


FILE *pf);

Funcţia returnează numărul de articole scrise în fişier sau -1


în caz de eroare.

Pentru prelucrarea fişierelor binare, la deschiderea lor se


foloseşte modul ”b”, astfel:
”wb” - pentru creare
”rb” - pentru citire
”r+b” - pentru citire/scriere
”w+b” - pentru citire/scriere
”ab” - pentru adăugare

6
/* Tratarea fis. la nivel superior Prelucrarea fisierelor
binare
*/

#include <conio.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <io.h>
#define MAX 30

typedef struct
{
char titlu[MAX];
char autor[12];
} articol;

articol carte;
char Numefis[30];
char Numenou[30];
void creare (void);
void adaug_sf(void);
void modificare(void);
void listare(void);

void main()
{ char op;
strcpy(Numefis,"C:\\carti.txt");
strcpy(Numenou,"C:\\carti1.txt");
do
{

7
clrscr();
printf("\n OPERATII ASUPRA CARTILOR\n");
printf("C. Creare fisier\n");
printf("A. Adaugarea unei carti la sfarsit\n");
printf("M Modificare carte\n");
printf("L Afisare carti\n");
printf("E Terminare program\n\n\n");
printf(" Introduceti optiunea:");
scanf("%c",&op);op=toupper(op);
switch (op)
{
case 'C':creare();break;
case 'A':adaug_sf();break;
case 'M':modificare();break;
case 'L':listare();break;
case 'E':printf("Terminare program\n");break;
default :printf("Optiune gresita!\n");break;
}/* switch */
printf("Tastati ENTER!"); fflush(stdin);
getch();
}
while (op != 'E');
}/* main */

void creare (void)


{
FILE *pf;
int i,nr;
char c='D';
clrscr();

8
/* printf("\nDati numele si calea fis. care il creati:");
fflush(stdin);scanf("%s",Numefis);*/
/* se deschide fis in scriere */
pf=fopen(Numefis,"wb");
if (pf==NULL)
{
printf("Nu se poate crea fisierul\n");
exit(1);
};
do
{
printf("\n Introduceti titlul:");
fflush(stdin);gets(carte.titlu);
printf("\n Introduceti autorul:"); fflush(stdin);
gets(carte.autor);
fwrite(&carte,sizeof(carte),1,pf);
printf("Mai adaugati(D/N):");
fflush(stdin);c=getche();c=toupper(c);
} while(c=='D');
fclose(pf);
}/* creare */

void adaug_sf(void)
{
FILE *pf;
int i,n;
clrscr();
/* printf("\nDati numele si calea fis. in care adaugati:");
scanf("%s",Numefis); */
/* se deschide fis pentru adaugare */

9
if ((pf=fopen(Numefis,"a"))==0)
{
printf("Nu se poate deschide fisierul\n");
exit(1);
}
printf("\n Introduceti titlul:");
fflush(stdin);gets(carte.titlu);
printf("\n Introduceti autorul:"); fflush(stdin);
gets(carte.autor);
fwrite(&carte,sizeof(carte),1,pf);
fclose(pf);
}/* adaug_sf */

void modificare(void)
{
FILE *pf;
int i,sant=0;
long n,m;
char numedat[30];
char titlu1[30];
char autor1[30];
clrscr();
/* printf("\nDati numele si calea fis. care-l modificati:");
fflush(stdin);scanf("%s",Numefis); */
/* se deschide fis pentru citire */
pf=fopen(Numefis,"r+");
if (pf==0)
{
printf("Nu se poate modifica fisierul\n");
exit(1);
}

10
printf("\nTitlul de modificat= ");
fflush(stdin);gets(numedat);
while(!feof(pf))
{
n=ftell(pf); /* n contine poz. curenta a point. de fis. */
i=fread(&carte,sizeof(carte),1,pf);
/* point de fis. indica acum art. urmator, celui citit. */
if(i!=0)
{
if(strcmp(carte.titlu,numedat)==0)
{
/* Acum point. de fisier = n+sizeof(carte) oct. */
printf("\n Articolul de modificat:");
printf("\n titlul: %s",carte.titlu);
printf("\n autor:%s",carte.autor);
printf("\nNoul titlu= ");fflush(stdin);gets(titlu1);
printf("\nNoul autor= ");fflush(stdin);gets(autor1);
strcpy(carte.titlu,titlu1);
strcpy(carte.autor,autor1);
/* n contine pozitia in fisier a articolului de modificat */
/* repun point. de fis.pe poz. unde fac modific,indicata de
n */
fseek(pf,n,0);
fwrite(&carte,sizeof(articol),1,pf);
/* Acum point. de fisier = n+sizeof(carte) oct. */
sant=1;
}
}
}/* while */
if (sant==0)
printf("\n\n Nu am gasit articolul in fisier\n\n");

11
fclose(pf);
}/* modificare */

void listare(void)
{
FILE *pf;
int i,n;
clrscr();
/* printf("\nDati numele si calea fis.de listat:");
fflush(stdin);scanf(Numefis); */
pf=fopen(Numefis,"rb");
if(pf==0)
{
printf("\n nu pot deschide fis\n");
exit(1);
}
printf("\n Lista cartilor:\n");
while(!feof(pf))
{
i=fread(&carte,sizeof(carte),1,pf);
if(i!=0)
{
printf("\n titlu: %s",carte.titlu);
printf("\n autor:%s",carte.autor);
}
};
fclose(pf);
}

12
METODE DE SORTARE

Sortare:
- internă
- externă

În funcţie de spaţiul de manevră necesar pentru efectuarea sortării există:

1. Sortare ce foloseşte o zonă de manevră de dimensiunea setului de date.


Dacă setul initial de date este reprezentat de tabloul x[1..n], cel sortat se
va obtine într-un alt tablou y[1..n].
2. Sortare în aceeaşi zonă de memorie (sortare pe loc - in situ)

- Simplitate
- Eficientă
- Naturaleţe.
- Stabilitate.

1. SORTARE PRIN INTERSCHIMBARE


2. SORTARE PRIN INSERŢIE
3. SORTARE PRIN SELECŢIE
4. SORTARE PRIN NUMĂRARE
5. SORTARE PRIN INTERCLASARE

1
1. SORTARE PRIN INTERSCHIMBARE

Structura generală a algoritmului

Interschimbari (x[1..n])
REPETA
|| se parcurge şirul şi dacă 2 elemente vecine nu sunt
|| în ordinea corectă, se interschimbă
PÂNĂCÂND <nu mai este necesară nici o interschimbare>

Variante

Interschimbari1 (x[1..n])
PENTRU i=n,2 executa
|| PENTRU j=1, i-1 executa
|| DACA x[j]>x[j+i] atunci x[j] ¨ x[j+1]
Return (x[1..n])

2
Interschimbari2 (x[1..n])
REPETA
schimb=fals
PENTRU i=1,n-1 executa
|| DACA x[i]>x[i+i] atunci x[i] ¨ x[i+1]
schimb=adevarat
|| SFDACA
SFPENTRU
PÂNĂCÂND schimb=fals
Return (x[1..n])

Interschimbari3 (x[1..n])
m=n {la inceput se parcuge tot tabloul}
REPETA
t=0 { in t se retine cel mai mare indice
pentru care se face interschimbarea}
|| PENTRU i=1, m-1 executa
|| DACA x[i]>x[i+i] atunci x[i] ¨ x[i+1]
t=i
|| SFDACA
|| SFPENTRU
m=t
PÂNĂCÂND t<=1
Return (x[1..n])

3
2. SORTAREA PRIN INSERŢIE

Începând de la al doilea element, fiecare este inserat pe


poziţia adecvată în subşirul care îl precede

Structura generală a algoritmului

Inserţie (x[1..n])
PENTRU i=2, n execută
|| inserează x[i] în subşirul x[1..i-1] astfel încât
|| x[1..i] să fie ordonat

Descrierea detaliată

Inserţie (x[1..n])
PENTRU i=2, n execută
|| j= i-1
|| aux=x[i]
|| CÂTTIMP aux<x[j] && j>=1 execută
x[j+1]=x[j]
j=j-1
|| x[j+1]=aux
SFPENTRU
Return (x[1..n])

4
- vectorul iniţial are elemente distincte; fiecare element
este inserat în locul corespunzător, în raport cu elementele
sortare anterior
- se consideră x[1]<=x[2]<=...x[j-1]
- se compară x[j] cu x[j-1], x[j-2],... până când se
determină că x[j] trebuie inserat între x[i] şi x[i+1]
- apoi se deplasează secvenţa x[i+1],...,x[j-1] cu o poziţie şi
se va introduce noul element în poziţia i+1

Alta metoda de implementare a sortarii prin insertie.


- pentru a evita efectuarea comparatiei j>=1 la fiecare iteratie
interioara se plaseaza pe pozitia 0 in tabloul x valoarea lui x[i],
aceasta pozitie jucand rolul unui fanion.
- Astfel, cel mai tarziu cand j = 0 se ajunge la x[j] = x[i].

insertie2(x[1..n])
PENTRU i= 2, n executa
|| x[0]= x[i] { plasarea lui x[i] pe pozitia fanionului }
|| j= i- 1
||CATTIMP (x[0] < x[j]) executa
|| x[j + 1]= x[j]
|| j= j- 1
||SFCATTIMP
|| x[j + 1] =x[0] {inserarea val. fanion pe poz. adecvata}
SFPENTRU
Return(x[1..n)

5
3. SORTARE PRIN SELECŢIE

- pentru fiecare poziţie i, începând cu prima, se selectează


din subşirul ce începe cu acea poziţie cel mai mic
element şi se plasează pe locul respectiv

Structura generală a algoritmului

Selecţie (x[1..n])
PENTRU i=1, n-1 execută
|| se determină valoarea minimă din subşirul x[i..n]
|| şi se interschimbă cu x[i]

Descrierea detaliată

Selecţie (x[1..n])
PENTRU i=1, n-1 execută
|| k= i
|| PENTRU j=i+1, n execută
|| DACĂ x[k]>x[j] atunci k=j
|| SFDACĂ
|| SFPENTRU
|| DACĂ i∫ k atunci x[k] ¨ x[i]
|| SFDACĂ
SFPENTRU
Return (x[1..n])

6
4. SORTAREA PRIN NUMĂRARE

- fiecare element este comparat cu toate celelalte; se


numără câte elemente sunt mai mici decât el şi se găseşte
poziţia finală a elementului iniţial

Structura generală a algoritmului

Numărare (x[1..n])
PENTRU j=2, n execută
|| se determină cîte elemente din subşirul x[1..j-1]
|| sunt mai mici decât fiecare x[j]

Descrierea detaliată

Numărare (x[1..n])
PENTRU i=1, n execută
|| contor[i] =1
SFPENTRU
PENTRU j=2, n execută
|| PENTRU i= 1, j-1 execută
|| DACĂ x[i]<x[j] atunci contor[j]=contor[j]+1
altfel contor[i]=contor[i]+1
|| SFDACĂ
|| SFPENTRU
SFPENTRU
PENTRU i=1, n execută
|| a[contor[i]] = x[i]
SFPENTRU
Return (a[1..n])

7
5. SORTARE PRIN INTERCLASARE

- se consideră 2 vectori x şi y de dimensiune m, respectiv


n, sortaţi crescător;
- se cere să se determine un nou vector z, format din
elementele lui x şi y, care să fie sortat crescător
- noul vector sortat z are dimensiunea m+n şi se obţine
astfel:
z[k]=x[i] pentru (x[i]<y[j], i<=m, j<=n) sau (j>n) şi
z[k]=y[j] pentru (x[i]>=y[j], i<=m, j<=n) sau (i>m)
(k=1,...,m+n)

8
RECURSIVITATE

proces nume_proces(parametrii)
{
Calcul_iniţial
IF
Condiţie_oprire
THEN
Calcul_nerepetabil
ELSE
Calcul_repetabil
ENDIF
Calcul_final
}

void p( )
{ //functie recursiva
p(); //apel infinit
}
//apelul trebuie conditionat in una din variantele:
if(cond)
p();
while(cond)
p();
do
p()
while(cond);

1
Calculul n!
n!=(n-1)!*n

Varianta recursivă

1, n=0
f (n) = 
n * f(n - 1), n >= 1

long int fact(int n)


{
if(n<0) return 0;
else if (n= =1 || n= =0) return 1;
else return fact(n-1)*n;
}

Calculul pentru n=5:


f(5)=5*f(4)=5*(4*f(3))=5*(4*(3*f(2)))=5*(4*(3*(2*f(1))))
= 5*(4*(3*(2*1*f(0)))))= 5*(4*(3*(2*(1*1))))=120.

2
//afisarea unui sir de caractere in ordine inversa

#include <stdio.h>
#include <conio.h>
#include <string.h>
int n;
char s[100];
void afisare(int i)
{
if (i<n) afisare(i+1);
printf("%c",s[i]);
}
void main()
{ printf("sirul de caractere "); scanf("%s",s);
n=strlen(s);
printf("sirul are %d caractere \n",n);
afisare(0);
printf("\n");
getch();
}

3
Să se scrie o funcţie care determină valoarea pentru
combinări de n luate câte k, C kn .
Pentru k=0, Cn0 = 1, ∀n∈Ν şi avem relaţia de recurenţă
n
Cnk = Cnk−−11 .
k

#include <stdio.h>
#include <conio.h>
short n,k;
double comb(int n, int k)
{
if (k= =0) return 1;
else return n*comb(n-1,k-1)/k;
}
void main( )
{
printf("n="); scanf("%d",&n);
printf("k="); scanf("%d",&k);
printf("%d!=%lf\n", n, comb(n,k));
getch();
}

4
0
4
---
1 1
5 5
--- ---
2 2 2
6 6 6
--- --- ---
3 3 3 3
7 7 7 7
--- --- --- ---

Evoluţia stivei program la execuţia exemplului pentru n=7


şi k=3 (expandare recursivitate)

1
1
5
--- 5
2 2
6 6
--- --- 15
3 3 3
7 7 7
--- --- --- 35

Evoluţia stivei program la execuţia exemplului pentru n=7


şi k=3 (rezolvare calcul recursiv)

5
Algoritmi de căutare

Fie v un tablou unidimensional de dimensiune n şi k un


element de acelaşi tip cu elementele tabloului. (k=cheie de
căutare).
Căutarea lui k revine la a găsi dacă există sau nu, un element în
v de valoare k.
În funcţie de aranjarea elementelor în tabloul v, există trei
tipuri de căutări:
1. secvenţială – nu se specifică nimic despre elementele
tabloului v
2. secvenţială în tabloul ordonat
3. căutare binară (caz în care tabloul este ordonat)

Căutarea secvenţială

Cautare(v[1..n], k)
i=1
CÂTTIMP (i<=n şi v[i]∫k) execută i=i+1
SFCÂTTIMP
DACĂ (i§ n) ATUNCI cauta=true
ALTFEL cauta=false
SFDACĂ
SfCautare

1
Căutarea secvenţială într-un tablou ordonat

Fie v tabloul ordonat crescător v1 ≤ v2 ≤ ... ≤ vn .


La compararea unui element vi cu k avem următoarele 3
situaţii:
a. vi = k , algoritmul se încheie, căutarea cu succes
b. vi > k , algoritmul se încheie, căutarea fără succes
c. vi < k , se continuă căutarea dacă i≤n

Cautare(v[1..n], k)
gata=false
gasit=false
CÂTTIMP (i<=n şi not gata) execută
DACĂ (v[i]= k) ATUNCI
gata=true
gasit=true
ALTFEL
DACĂ v[i]>k ATUNCI
gata=true
ALTFEL
i=i+1
SFDACĂ
SFDACĂ
SFCÂTTIMP
DACĂ (i>n sau not gasit) ATUNCI “cautare fara succes”
ALTFEL “cautare cu succes”
SFDACĂ
SfCautare

2
Căutarea binară

Căutarea binară (caz în care tabloul este ordonat) se poate


realiza recursiv sau iterativ.
Fie v tabloul ordonat crescător v1 ≤ v2 ≤ ... ≤ vn .
Varianta recursivă presupune compararea cheii cu elementul
din mijlocul tabloului, obţinându-se următoarele 3 situaţii:
a. vmij = k , stop s-a găsit cheia
b. vmij < k , se continuă căutarea în secvenţa vmij +1, vmij + 2 ,..., vn
c. vmij > k , se continuă căutarea în secvenţa v1 , v2 ,..., vmij −1

Algoritmul pentru varianta recursivă

Caut(k, inf, sup)


DACĂ (inf § sup)
ATUNCI
mij= (inf+sup)/2
DACĂ (v[mij]=k)
ATUNCI return mij
ALTFEL
DACĂ (v[mij]>k) ATUNCI Caut(k, inf, mij-1)
ALTFEL Caut(k, mij+1, sup)
SFDACĂ
SFDACĂ
ALTFEL return 0
SFDACĂ
SfCaut

3
Fie v tabloul ordonat crescător v1 ≤ v2 ≤ ... ≤ vn .
Algoritmul pentru varianta iterativă

Caut(v[1..n], k)
inf=1
sup=n
ind=0
CÂTTIMP (inf § sup şi ind=0) execută
mij= (inf+sup)/2
DACĂ (v[mij]=k) ATUNCI ind=1
ALTFEL
DACĂ (v[mij]<k) ATUNCI inf=mij+1
ALTFEL sup=mij-1
SFDACĂ
SFDACĂ
SFCÂTTIMP
DACĂ (ind=1) ATUNCI return mij
ALTFEL return 0
SFDACĂ
SfCaut

4
Tehnica alegerii local optimale

Metoda Greedy

Să se determine xœX a.i.


(i) x să satisfacă anumite restricţii (condiţii)
(ii) x optimizează (maximizează sau minimizează) un
crietriu

O subclasă importantă în informatică o reprezintă cea în care X este o


mulţime finită, problema de optimizare fiind numită în acest caz
discretă sau combinatorială.

Exemplu. (problema submultimii de sumă dată şi cardinal minim)


n
Se consideră mulţimea A ={a1, a2,..., an}ÕR şi valoarea C ≤ ∑ ai . Se
i =1
cere să se determine (dacă există) o submulţime având cât mai puţine
elemente şi a căror sumă să fie egală cu C.

Principiul tehnicii

Fie A ={a1, a2,..., an} un set fnit de elemente (nu neapărat distincte).
Să se determine S = (s1, s2, ..., sk) Õ A astfel încât S să satisfacă
anumite restricţii şi să optimizeze un criteriu.

1
Structura generală a algoritmului este:

greedy(A)
S= ∅
CÂTTIMP
("S nu este soluţie" & "există elemente neselectate în A")
execută
selectează a din A
dacă nu sunt încălcate restricţiile se adaugă a la S
SFCÂTTIMP
return S

functia GREEDY (A, n)


S=∅
PENTRU i = 1, n executa
x = SELECT (A)
DACA POSIBIL (S, x) ATUNCI S = UNION (, x)
SFDACA
SFPENTRU
Solutia= S
SfGREEDY

2
O variantă a acestei proceduri este următoarea:

functia GREEDY (A, n)


S =∅
REPETA
x =SELECT (A)
A = A – {x}
DACA POSIBIL (S, x) ATUNCI S = UNION (S, x)
SFDACA
ATÂTATIMPCÂT A∫ ∅
Solutia= S
SfGREEDY

Problema rucsacului

Se dă un rucsac de capacitate C şi n obiecte. Fiecare obiect i


are o greutate gi şi poate fi fracţionat, fracţiunile sale fiind
notate cu xi, 0<=xi<=1. Fiecare obiect dacă este plasat în
rucsac aduce un profit (pondere) pi. Să se plaseze obiectele
în rucsac aşa fel încât profitul să fie maxim.

3
Rezolvare:
În mod formal, problema cere de fapt să se maximizeze
n n
∑ p i xi supusă la restricţiile ∑ g i xi <=M, unde 0<=xi<=1 şi
i =1 i =1

1<=i<=n. Profiturile şi greutăţile sunt numere reale pozitive.


O soluţie posibilă este orice mulţime (x1, x2,…,xn) care care
n
satisface condiţiile ∑ g i xi <=M, 0<=xi<=1, 1<=i<=n, iar o
i =1

n
soluţie optimă este o soluţie posibilă pentru care ∑ pi xi este
i =1

maxim.
Strategia care se aplică ia în considerare atât factorul de
greutate cât şi cel de profit. Astfel, vom aplica strategia
conform căreia se adaugă la fiecare pas obiectul care are
raportul pi / gi maxim. Dacă obiectele sunt aranjate astfel încât:
p1 / g1 ≥ p2 / g2 ≥…≥ pn / gn

4
Algoritmul corespunzător acestei strategii este:

Rucsac (p, g, M, x, n) este:


PENTRU i = 1, n executa x(i) = 0
SFPENTRU
rest = M
i=1
CATTIMP (i ≤ n si rest <> 0) executa
DACA (g(i) > rest) ATUNCI
x(i) = rest/g(i)
rest = 0
ALTFEL
x(i) =g(i)
rest = rest- g(i)
SFDACA
i=i+1
SFCATTIMP
SfAlgoritm

5
Problema spectacolelor.
Într-o sală, într-o zi trebuie planificate n spectacole. Pentru fiecare
spectacol se cunoaşte intervalul de timp în care se desfăşoară (ora de
început , ora de sfârşit).
Se cere, să se planifice un număr maxim de spectacole astfel încât să
nu se suprapună spectacolele.

Algoritmul de rezolvare a problemei:


1. sortare spectacole după ora terminării lor ;
2. primul spectacol programat este cel care se termină cel mai
devreme
3. alegem primul spectacol din cele ce urmează în şir ultimului
spectacol programat, acela care începe după ce s-a terminat
ultimul spectacol programat ;
4. dacă nu s-a găsit un astfel de spectacol, algortimul se încheie,
altfel se programează spectacolul găsit şi algoritmul se reia de la
pasul 3

6
(Problema monedelor) Fiind date monede de valori v1, v2,...,vn şi o
sumă C, să se determine numărul minim de monede necesar pentru a
acoperi suma C. Se presupune că există un număr nelimitat de
monede.

Monede(v[1..n],C)
OrdonareDescrescatoare[v[1..n])
Pentru i=1,n executa S[i]=0
SfPentru
i=1
CatTimp C>0 si i<=n executa
S[i] = [ C / v[i] ]
C = C mod v[i]
i=i+1
SfCatTimp
Daca C=0 atunci Return S[1..n]
altfel “nu s-a gasit solutia”
SfDaca

7
TEHNICI DE REDUCERE SI DIVIZARE

- rezolvarea unei probleme de dimensiune n se


înlocuieşte cu rezolvarea uneia sau mai multor
probleme similare, dar de dimensiune mai mică

Considerăm problema calculul x n pentru x>0 şi n = 2m ,


m¥1 fiind un număr natural.

Putere1(x, n)
p=1;
PENTRU i=1, n executa
|| p=p*x;
SFPENTRU
Return p

Putere2(x, m)
p=x;
PENTRU i=1, m executa
|| p=p*p;
SFPENTRU
Return p

Putere3(x, n)
DACA n=2 ATUNCI Return x*x;
ALTFEL
|| p=Putere3(x, n/2);
|| Return p*p
SFDACA

1
Tehnica Divide et Impera

Descrierea metodei

DivI(p,q)
a[1,n];
n, p, q, m; 1<=p; q<=n
DACA MIC(p,q)
ATUNCI Return G(p,q)
ALTFEL
|| m=DIVIDE(p,q); p<=q
|| Return (combină (DivI(p,m), DivI(m+1,q))
SFDACA

MIC(p,q) o funcţie care determină dacă submulţimile


sunt suficient de mici pentru a mai fi sau nu divizate.
Pentru determinarea lui m problema iniţială se împarte în
două subprobleme A(p,m) şi A(m+1,q) ale căror soluţii se
găsesc aplicând recursiv DI. Funcţia combină soluţiile celor
două subprobleme.

2
Exemplu : determinarea maximului dintr-o secvenţă finită
de valori reale.

Maxim (x[s..d])
DACA s=d ATUNCI max=x[s]
ALTFEL
|| max1= maxim(x[1..n/2])
|| max2= maxim(x[n/2+1..n])
|| DACA max1<max2 ATUNCI max=max2
ALTFEL max=max1
|| SFDACA
|| SFDACA
Return max

Problema turnurilor din Hanoi.

Se dau trei tije simbolizate prin a, b, c.


Pe tija a se găsesc n discuri de diametre diferite aşezate în
ordinea descrescătoare a diametrelor privite de jos în sus.
Se cere să se mute discurile de pe tija a pe tija b, folosind
ca tijă auxiliară c, respectând următoarele reguli:
1. la fiecare pas se mută un singur disc,
2. nu este permis să se aşeze un disc cu diametrul mai
mare peste un disc cu diametrul mai mic.

Pentru n=1, problema se rezolvă mutând discul de pe tija a


pe tija b, deci se face mutarea ab.

3
Dacă n=2, mutările care rezolvă problema sunt: ac, ab, cb.
În cazul n>2, problema este echivalentă cu:
1. mutarea a n-1 discuri de pe tija a pe tija c, utilizând ca
tijă intermediară tija b;
2. mutarea discului rămas pe tija a, pe tija b;
3. mutarea a n-1 discuri de pe tija c pe tija b, utilizând ca
tijă intermediară tija a.

Parcurgerea celor trei etape permite definirea recursivă a


şirului H(n,a,b,c) astfel:

ab dacă n = 1
H (n, a, b, c) = 
 H (n − 1, a, c, b), ab, H(n - 1, c, b, a) dacă n > 1

De exemplu:

1. pentru n=2 avem

H(2,a,b,c) = H(1,a,c,b),ab,H(1,c,b,a) = ac,ab,cb

2. pentru n=3 avem

H(3,a,b,c)=H(2,a,c,b),ab,H(2,c,b,a)=

=H(1,a,b,c),ac,H(1,b,c,a),ab,H(1,c,a,b),cb,H(1,a,b,c)=

=ab, ac, bc, ab, ca, cb, ab

4
Metoda interclasării

Se dă un tablou unidimensional a de numere reale şi se cere


să se ordoneze crescător elementele acestuia, folosind
metoda interclasării.

Se împarte mulţimea a în două submulţimi a1, a2, …,a[n/2]


şi a[n/2]+1, a[n/2]+2, …,an. Fiecare submulţime va fi ordonată
separat, apoi cele două submulţimi vor fi interclasate pentru
a da în final o singură secvenţă ordonată.

5
Pentru rezolvarea problemei se vor utiliza 3 funcţii:
- o funcţie sort care va sorta un vector de maxim 2
elemente;
- o funcţie intercl care interclasează rezultatele şi
- o funcţie divimp care implementează strategia tehnicii
Divide et Impera.

void sort(int p, int q, int a[]) ;

void intercl(int p, int q, int m, int a[]);

(i<=m, j<=q, i=p,p+1,…,m , j=m+1,m+2,…,q )


- declarare tablou b
k=1;
for(i=p;i<=q;i++)
{ a[i]=b[k]; k++;
}

void divimp(int p, int q, int a[10]);

DACA ((q-p)<=1) ATUNCI sort(p,q,a);


ALTFEL
m=(p+q)/2;
divimp(p, m, a);
divimp(m+1, q, a);
intercl(p, q, m, a);

6
Quicksort (sortarea rapidă)

Se consideră un vector a de dimensiune n. Să se sorteze


crescător elementele vectorului.

- folosirea unui pivot


- folosirea unei pozitii de partitionare
(HOARE)

Ex.

(3,1,2,4,7,5,8) ai<=aq i=1,2,…,q-1


ai>=aq i=q+1,q+2,…,n
(3,1,2)
(7,5,8)

(3,1,2,7,5,4,8) ai<=aj
pentru i=1,2,…,q
j=q+1,q+2,…,n

7
QuickSort1(a[li..ls])
Daca li<ls atunci
q=partitie1(a[li..ls])
QuickSort(a[1..q-1])
QuickSort(a[q+1..n])
SfDaca
Return a[li..ls]

QuickSort2(a[li..ls])
Daca li<ls atunci
q=partitie2(a[li..ls])
QuickSort(a[1..q])
QuickSort(a[q+1..n])
SfDaca
Return a[li..ls]

Obs.

- QuickSort1 - Se asigura pozitia de partitionare si plasarea pe


pozitia q a valorii finale

- QuickSort2 - Se determina doar pozitia de partitionare

8
partitie1(a[li..ls])
v=a[ls] // alegere pivot
i=li-1 // indice pentru parcurgere sir de la stanga la dreapta
j=ls // indice pentru parcurgere sir de la dreapta la stanga
CatTimp i<j executa
Repeta
i=i+1
PanaCand a[i]>=v
Repeta
j=j-1
PanaCand a[j]<=v
Daca i<j atunci a[i]¨ a[j]
SfDaca
SfCatTimp
a[i] ¨ a[ls]
Return i

partitie1(a[li..ls])
v=a[ls] // alegere pivot
i=li-1 // indice pentru parcurgere sir de la stanga la dreapta
j=ls+1 // indice pentru parcurgere sir de la dreapta la stanga
CatTimp i<j executa
Repeta
i=i+1
PanaCand a[i]>=v
Repeta
j=j-1
PanaCand a[j]<=v
Daca i<j atunci a[i]¨ a[j]
SfDaca
SfCatTimp
a[i] ¨ a[ls]
Return j

9
Metoda Backtracking

Metoda backtracking se foloseşte în rezolvarea problemelor care

îndeplinesc simultan următoarele condiţii:

-soluţia lor poate fi pusă sub forma unui vector


x = (x1,x2,…,xn) dintr-un spaţiu A = A1xA2 x…xAn, astfel încât
x1∈A1, x2 ∈A2, …, xn ∈An.

-mulţimile Ai, i=1…n, sunt mulţimi finite, card(Ai) = ai, iar elementele
lor se consideră că se află într-o relaţie de ordine bine stabilită;

-există anumite relaţii între componentele x1,x2,…,xn ale vectorului X,


numite condiţii interne;

-nu se dispune de o altă metodă de rezolvare mai rapidă.

Mulţimea A= A1xA2 x…xAn se numeşte spaţiul soluţiilor posibile.


Soluţiile posibile care satisfac condiţiile interne se numesc soluţii
rezultat.

Observaţii:
- x1,x2,…,xn pot fi la rândul lor vectori;

- în multe probleme mulţimile A1xA2 x…xAn coincid.

Scopul acestei metode este de a determina toate soluţiile rezultat

(pentru a le afişa sau pentru a alege dintre ele una care

maximizează sau minimizează o eventuală funcţie obiectiv dată).

1
Descrierea metodei :

I. - se alege primul element x1, ce aparţine lui A1;


II. - presupunând generate elementele x1,x2,…,xk aparţinând
mulţimilor A1 x A2 x…x Ak se alege (dacă există) xk+1, primul element
disponibil din mulţimea Ak+1; apar două posibilităţi:

1) nu s-a găsit un astfel de element, caz în care se reia căutarea


considerând generate elementele x1,x2,…,xk-1, iar aceasta se reia de la
următorul element al mulţimii Ak, rămas netestat

2) a fost găsit un astfel de element, caz în care se testează dacă


acesta îndeplineşte anumite condiţii de continuare, apărând astfel 2
posibilităţi:

2.1. le îndeplineşte, caz în care se testează dacă s-a ajuns la


soluţie şi apar, din nou, 2 posibilităţi:
2.1.1. s-a ajuns la soluţie, se tipăreşte soluţia şi se reia
algoritmul considerând generate elementele x1,x2,…,xk (se caută,
în continuare, un element al mulţimii Ak+1 rămas netestat);
2.1.2. nu s-a ajuns la soluţie, caz în care se reia algoritmul
considerând generate elementele x1,x2,…,xk+1 şi se caută un prim
element xk+2∈Ak+2;

2.2. nu le îndeplineşte, caz în care se reia algoritmul considerând


generate elementele x1,x2,…,xk, iar elementul xk+1 se caută între
elementele mulţimii Ak+1 rămase netestate.

III. Algoritmul se termină atunci când nu mai există nici un


element x1∈ A1, netestat.

2
xk


x2
x1

• funcţia void Init(); are rolul ca la urcarea în stivă, pe nivelul la care


s-a ajuns să pună o valoare care nu se află în mulţimea considerată, dar
de la care, la pasul următor, se ajunge la primul element din acea
mulţime

• funcţia int Am_Succesor(); returnează 1, dacă există succesor, acesta


fiind pus în stivă şi 0 în caz contrar.

• funcţia int Valid(); returnează 1 dacă elementul îndeplineşte


condiţiile de continuitate şi 0 în caz contrar.

• funcţia int Solutie(); returnează 1 dacă s-a ajuns la soluţie, respectiv


0 dacă nu s-a ajuns încă la soluţie.

• funcţia void Tipar(); tipăreşte soluţia.

3
Cu aceste notaţii rutina backtracking este următoarea:

void back()
{ int As;
k=1;
Init();
while (k>0)
{
do { } while ((As=Am_Succesor()) && !Valid());
if (As)
if (Solutie()) Tipar();
else { k++;
Init();
}
else k--;
}
}

4
Altă variantă a rutinei backtracking, în care funcţiile au parametrii de
intrare:

• funcţia de iniţializare o vom numi init şi va avea doi parametri: k,


nivelul care trebuie iniţializat şi st (stiva).
void Init(int st[], int k);

• funcţia Am_succesor are doi parametri: k, nivelul stivei unde


elementul găsit va fi pus în stivă şi st (stiva). Funcţia returnează 1, dacă
există succesor şi 0 în caz contrar.
int Am_Succesor(int st[],int k);

• funcţia Valid cu doi parametri: k, nivelul stivei unde elementul găsit


va rămâne în stivă dacă îndeplineşte condiţiile de continuitate şi st
(stiva). Funcţia returnează 1, dacă există succesorul este valid şi 0 în
caz contrar.
int Valid(int st[],int k);

• funcţia Soluţie având ca şi parametru k, numărul de elemente puse în


stivă în vederea formării soluţiei. Funcţia returnează 1 dacă s-a ajuns
la soluţie, respectiv 0 dacă nu s-a ajuns încă la soluţie.
int Solutie(int k);

• funcţia tipar care tipăreşte pe rând soluţiile problemei.


void Tipar();

5
Cu aceste funcţii rutina backtracking devine:

void back()
{
int k;
int As, Ev;
k=1;
Init(st,k);
while (k>0)
{ do {
As=Am_Succesor(st,k);
if (As= =1) Ev=Valid(st,k);
} while ( (As= =1 && Ev= =0));
if (As)
if (Solutie(k)) Tipar();
else {
k++;
Init(st,k);
}
else k--;
}
}

6
Exemplul 1. Generarea permutărilor

Se citeşte un număr natural n. Să se genereze toate permutările


mulţimii {1,2,…, n}.

#include <stdio.h>
#include <conio.h>
int st[10],n;
void Init(int st[], int k)
{
st[k]=0;
}
int Am_Succesor(int st[],int k)
{
if(st[k]<n)
{
st[k]++;
return 1;
}
else return 0;
}
int Valid(int st[],int k)
{
for(int i=1;i<k;i++)
if(st[i]= =st[k]) return 0;
return 1;
}

7
int Solutie(int k)
{
return k= =n;
}
void Tipar()
{
for(int i=1;i<=n;i++) printf("%d ",st[i]);
printf("\n");
}
void back()
{ int k, As, Ev;
k=1;
Init(st,k);
while (k>0)
{ do
{ As=Am_Succesor(st,k);
if (As==1) Ev=Valid(st,k);
} while ( (As==1 && Ev==0));
if (As)
if (Solutie(k)) Tipar();
else {
k++;
Init(st,k);
}
else k--;
}

8
}
void main()
{
clrscr();
printf("n="); scanf("%d",&n);
back();
getch();
}

Exemplul 2. Problema celor n dame.

Fiind dată o tablă de şah n×n, se cer toate soluţiile de aranjare a n


dame, astfel încât să nu se afle două dame pe aceeaşi linie, coloană sau
diagonală (damele să nu se atace reciproc).

Obs.
Două dame se găsesc pe aceeaşi diagonală dacă şi numai dacă este
îndeplinită condiţia:
|st(i) - st(j)|=|i-j| ,
(diferenţa, în modul, dintre linii şi coloane este aceeaşi).
În general, st(i)=k semnifică faptul că pe linia i dama ocupă poziţia k.

int Valid(int st[], int k)


{
for(int i=1;i<k;i++)
if(st[i]==st[k] || abs(st[k]-st[i])==abs(k-i)) return 0;
return 1;
}

9
Metoda Backtracking

Exemplul 4. Problema colorării harţilor

Fiind dată o hartă cu n ţări, se cer toate soluţiile de colorare a hărţii,


utilizând cel mult 4 culori, astfel încât două ţări cu frontiera comună să
fie colorate diferit.
Pentru a exemplifica vom considera următoarea hartă unde ţările sunt
numerotate cu de la 1 la 5:

1 Numerotând cele 4 culori cu 1,2,3,4,


o posibilă soluţie a problemei ar fi
următoarea:
4 • Ţara 1 - culoarea 1
• Ţara 2 - culoarea 2
• Ţara 3 - culoarea 1
2 3 • Ţara 4 - culoarea 3
• Ţara 5 - culoarea 4
5

A[i,j]=1 sau 0
st[k] reprezentă culoarea ataşată ţării k.

int Valid(int st[], int k)


{
for(int i=1;i<=k-1;i++)
if(st[i]= =st[k] && a[i][k]= =1) return 0;
return 1;
}

1
void Tipar()
{
//for(int i=1;i<=n;i++) printf("tara %d are culoarea %d ",i,st[i]);
for(int i=1;i<=n;i++) printf(" %d ",st[i]);
printf("\n");
nr++;
}

void main()
{
clrscr();
printf("nr tari=");
scanf("%d",&n);
nr=0;
// matricea A reprezentand tarile cu vecinii se va completa
// tinand cont ca este simetrica
for (int i=1;i<=n;i++)
for (int j=1;j<=i-1;j++)
{ printf("a[%d,%d]=",i,j);
scanf("%d",&a[i][j]);
a[j][i]=a[i][j];
}
back();
printf("nr. solutiilor=%d",nr);
getch();
}

2
Exemplul 5. Problema plăţii unei sume s utilizând n tipuri de monede.

Se dau : suma s şi n tipuri de monede având valori de a1, a2, …, an lei.


Se cer toate modalităţile de plată pentru suma s utilizând aceste
monede.
Pentru rezolvare, se calculează în vectorul B numărul maxim de
monede de tipul i cu care se poate forma suma s. Componentele
vectorului B reprezintă câtul dintre suma s şi valoarea corespunzătoare
a monedei i, cu i cuprins între 1 şi n. Acestea reprezintă limitele de
căutare ale succesorului, pe fiecare nivel al stivei st. În fiecare nivel
prima valoare validă este 0. Pentru a avea succesor pe nivelul k al stivei
este necesar să fie îndeplinite simultan condiţiile:
• succesorul să fie mai mic sau egal cu valoarea corespunzătoare
din vectorul B;
• suma calculată utilizând numărul de monede de tipul i găsite pe
nivelul i al stivei (i este mai mic sau egal cu k), trebuie să fie mai
mică sau egală cu suma s ce trebuie plătită.

/* Problema monedelor
*/
#include <stdio.h>
#include <conio.h>
int st[10],a[10],b[10],n,s,nr;
void Init(int st[], int k)
{st[k]=-1;
}
int Am_Succesor(int st[], int k)
{ int s1,i,as;
if((st[k]<b[k])&&(k<=n))
{
s1=0;
for(i=1;i<=k;i++)
s1=s1+st[i]*a[i];

3
if (s1<s) as=1;
else as=0;
}
else as=0;
if (as) st[k]++;
return as;
}
int Solutie(int k)
{
int s1,i;
s1=0;
for(i=1;i<=k;i++)
s1=s1+st[i]*a[i];
if (s= =s1) return 1;
else return 0;
}
void back()
{ int k;
int As,Ev;
k=1;
Init(st,k);
while (k>0)
{ As=Am_Succesor(st,k);
if (As)
if (Solutie(k))
{
for(int i=1;i<=k;i++) printf(" %d de %d lei \t ",st[i],a[i]);
printf("\n");
nr++;
}
else {
k++;
Init(st,k);
}
else k--;

4
}
}
void main()
{
clrscr();
printf("suma="); scanf("%d",&s);
printf("nr monede="); scanf("%d",&n);
nr=0;
for (int i=1;i<=n;i++)
{ printf("valoare moneda %d= ",i);
scanf("%d",&a[i]);
b[i]=s/a[i];
}
back();
printf("nr. solutiilor=%d",nr);
getch();
}

Backtracking recursiv

Principiul de funcţionare al procedurii back, corespunzător unui nivel


k, este următorul:
- în situaţia în care avem o soluţie, o tipărim şi revenim pe nivelul
anterior;
- în caz contrar, se iniţializează nivelul şi se caută un succesor;
- când am găsit un succesor, verificăm dacă este valid, apoi se
autoapelează procedura pentru k+1, în caz contrar urmând a se
continua căutarea succesorului;
- dacă nu avem succesor, se trece pe nivel inferior (k-1) prin ieşirea
din procedura back.

Rutina backtracking este în acest caz:

5
void back(int k)
{
if (Solutie(k)) Tipar();
else
{
Init(st,k);
while (Am_Succesor(st,k))
{
if (Valid(st,k)) back(k+1);
}
}
}

Apelul acestei funcţii este back(1);

Utilizarea acestei metode recursive pentru generarea aranjamentelor


Anp .

/* Se citesc n si p numere naturale, n>=p. Se cere sa se genereze


toate aranjamentele de n luate cate p
*/
int Solutie(int k)
{ return k= =p+1;}

Backtracking recursiv fără schemă

Exemplul 1. Problema celor n dame.

6
Fiind dată o tablă de şah, de dimensiune n×
×n, se cer toate soluţiile
de aranjare a n dame astfel încât să nu se afle două dame pe
aceeaşi linie, coloană sau diagonală (damele să nu se atace
reciproc).

Mecanismul de rezolvare este acelaşi ca în cazul standardizat (o soluţie


se reţine într-un vector numit st). Pentru o anumită componentă a
vectorului se caută un succesor de la valoarea care se găseşte la un
moment dat până la valoarea maximă posibilă (n). În cazul în care se
găseşte un succesor, se verifică dacă este valid, caz în care se apelează
procedura pentru valoarea următoare, altfel, se caută următorul
succesor. În cazul în care acesta nu există se revine la nivelul anterior.
Iniţializarea se face la început (pentru tot vectorul) şi la revenire.
Tipărirea soluţiei se realizează în momentul atingerii nivelului n+1.

#include <stdio.h>
#include <conio.h>
#include <math.h>
int st[10],n;
void Tipar()
{
for(int i=1;i<=n;i++) printf("%d ",st[i]);
printf("\n");
}
void back(int k)
{ int i, j, As;
if (k= =n+1) Tipar();
else
{
for(i=st[k]+1;i<=n;i++)
{
st[k]=i;
As=1;
for(j=1; j<k; j++)

7
if (st[j]= =st[k] || abs(st[k]-st[j])= =(k-j))
As=0;
if (As) back(k+1);
}
}
st[k]=0;
}
void main()
{
printf("n="); scanf("%d",&n);
back(1);
getch();
}

8
Metoda Backtracking

Backtracking recursiv

Principiul de funcţionare al procedurii back, corespunzător unui nivel


k, este următorul:
- în situaţia în care avem o soluţie, o tipărim şi revenim pe nivelul
anterior;
- în caz contrar, se iniţializează nivelul şi se caută un succesor;
- când am găsit un succesor, verificăm dacă este validă procedura,
se autoapelează pentru k+1, în caz contrar urmând a se continua
căutarea succesorului;
- dacă nu avem succesor, se trece pe nivel inferior (k-1) prin ieşirea
din procedura back.

Rutina backtracking este în acest caz:

void back(int k)
{
if (Solutie(k)) Tipar();
else
{
Init(st,k);
while (Am_Succesor(st,k))
{
if (Valid(st,k)) back(k+1);
}
}
}

Apelul acestei funcţii este back(1);

1
Backtracking recursiv fără schemă

Exemplul 1. Problema celor n dame.


Fiind dată o tablă de şah, de dimensiune n××n, se cer toate soluţiile
de aranjare a n dame astfel încât să nu se afle două dame pe
aceeaşi linie, coloană sau diagonală (damele să nu se atace
reciproc).

Mecanismul de rezolvare este acelaşi ca în cazul standardizat (o soluţie


se reţine într-un vector numit t). Pentru o anumită componentă a
vectorului se caută un succesor de la valoarea care se găseşte la un
moment dat până la valoarea maximă posibilă (n). În cazul în care se
găseşte un succesor, se verifică dacă este valid, caz în care se apelează
procedura pentru valoarea următoare, altfel, se caută următorul
succesor. În cazul în care acesta nu există se revine la nivelul anterior.
Iniţializarea se face la început (pentru tot vectorul) şi la revenire.
Tipărirea soluţiei se realizează în momentul atingerii nivelului n+1.

2
#include <stdio.h>
#include <conio.h>
#include <math.h>
int st[10],n;
void Tipar()
{
for(int i=1;i<=n;i++) printf("%d ",st[i]);
printf("\n");
}
void back(int k)
{ int i, j, As;
if (k= =n+1) Tipar();
else
{
for(i=st[k]+1;i<=n;i++)
{
st[k]=i;
As=1;
for(j=1; j<k; j++)
if (st[j]= =st[k] || abs(st[k]-st[j])= =(k-j))
As=0;
if (As) back(k+1);
}
}
st[k]=0;
}
void main()
{
printf("n="); scanf("%d",&n);
back(1);
getch();
}

3
Exemplul 2. Generarea recursivă a combinărilor.
Se citesc două numere naturale n şi k (k≤n). Se consideră o mulţime cu
elementele {1,2,…,n} şi se cere să se tipărească toate submulţimile sale care au k
elemente (combinări de n luate câte k).

Pe nivelul k se va afla o valoare mai mare decât pe nivelul k-1 şi mai mică sau
egală cu n-p+k.

void Init(int st[], int k)


{
if (k>1) st[k]=st[k-1];
else st[k]=0;
}
int Am_Succesor(int st[], int k)
{
if(st[k]<n-p+k)
{
st[k]++;
return 1;
}
else return 0;
}

4
/* Avem la dispozitie 6 culori: alb, galben, rosu, verde, albastru, negru.
Sa se precizeze toate drapelele tricolore care se pot proiecta, stiind ca
trebuie respectate regulile:
-Orice drapel are culoarea din mijloc galben sau verde;
-Cele trei culori din drapel sunt distincte.

*/
#include <stdio.h>
#include <conio.h>
int st[6],n,p,nr;
struct
{
char *culoare;
} cul[6];
void Init(int st[], int k)
{
st[k]=0;
}
int Am_Succesor(int st[],int k)
{
if(st[k]<n)
{
st[k]++;
return 1;
}
else return 0;
}
int Valid(int st[],int k)
{
for(int i=1;i<k;i++)
if(st[i]= =st[k]) return 0;
return 1;
}
int Solutie(int k)
{ return k= =p;}

5
void Tipar()
{
if (st[2]= =2 || st[2]= =4)
{for(int i=1;i<=p;i++) printf("%s ",cul[st[i]-1].culoare);
printf("\n"); nr++;
}
}
void back()
{ int k;
int As,Ev;
k=1;
Init(st,k);
while (k>0)
{ do { As=Am_Succesor(st,k);
if (As= =1) Ev=Valid(st,k);
} while ( (As= =1 && Ev= =0));
if (As)
if (Solutie(k)) Tipar();
else {
k++;
Init(st,k);
}
else k--;
}
}
void main()
{
clrscr();
/*printf(" 1- alb \n");
printf(" 2- galben \n");
printf(" 3- rosu \n");printf(" 4- verde \n");
printf(" 5- albastru \n");printf(" 6- negru \n");
*/
cul[0].culoare="alb";
cul[1].culoare="galben";cul[2].culoare="rosu";

6
cul[3].culoare="verde";cul[4].culoare="albastru";
cul[5].culoare="negru";
n=6; p=3; nr=0;
back();
printf("problema are %d solutii \n", nr);
getch();
}

Proprietarul unui magazin trebuie să se aprovizioneze de la un depozit


en-gros. Produsele existente în depozit sunrt numerotate cu 1,2,…,n.
Pentru fiecare produs se cunoaşte categoria (grupa) din care face parte,
codificată printr-un caracter (‘a’=produse alimentare, ‘c’=produse
cosmetice, etc). Determinaţi toate variantele pe care le are proprietarul
de magazin pentru a alege produsele pe care le va cumpăra, ştiind că,
dintr-o categorie, indiferent care ar fi aceasta, va cumpăra cel mult b
produse (unde b este cunoscut).

#include <stdio.h>
#include <conio.h>
int st[10],n,b,m,nr;

void Init(int st[], int k)


{ if (k>1) st[k]=st[k-1];
}

7
int Am_Succesor(int st[], int k)
{
if(st[k]<n)
{ st[k]++;
return 1;
}
else return 0;
}
int Valid(int st[],int k)
{
for(int i=1;i<k;i++)
if(st[i]= =st[k]) return 0;
return 1;
}
int Solutie(int k)
{ return k= =m;}
void Tipar()
{
for(int i=1;i<=m;i++) printf("%d ",st[i]);
printf("\t"); nr++;
}
void back()
{ int k;
int As,Ev;
k=1;
st[k]=0;
while (k>0)
{ do
{ As=Am_Succesor(st,k);
if(As==1) Ev=Valid(st,k);
} while (As==1 && Ev==0);
if (As)
if (Solutie(k)) Tipar();
else {
k++;

8
Init(st,k);
}
else k--;
}
}
void main()
{
clrscr();
do
{
printf("n="); scanf("%d",&n);
printf("b="); scanf("%d",&b);
}while(n<b);
nr=0;
for(m=1;m<=b;m++)
{
back();
}
printf("\n nr. solutiilor=%d\n",nr);
getch();
}

9
10
Exemplul 4. Problema comis voiajorului.

Un comis-voiajor trebuie să viziteze un număr n de oraşe. Iniţial, acesta se află


într-unul dintre ele, notat 1. Comis-voiajorul doreşte să nu treacă de două ori prin
acelaşi oraş, iar la întoarcere să revină în oraşul 1. Cunoscând legăturile existente
între oraşe, se cere să se tipărească toate drumurile posibile pe care le poate
efectua comis-voiajorul.
Exemplu:
În figura alăturată sunt simbolizate 6 oraşe, precum şi drumurile existente între
ele.

2 3

1 4

6 5

Comis-voiajorul are următoarele posibilităţi de parcurgere:


• 1,2,3,4,5,6,1;
• 1,2,5,4,3,6,1;
• 1,6,3,4,5,2,1;
• 1,6,5,4,3,2,1.

Legăturile existente intre oraşe sunt date în matricea An,n. Elementele matricei A pot fi 0 sau 1
(matricea este binară).

1, dacă exista drum între orasul i si j,


A(i, j ) = 
0, în caz contrar.

Se observă că A(i,j)=A(j,i), oricare ar fi i şi j aparţinând mulţimii {1,2,…,n} (matricea este


simetrică).

Pentru rezolvarea problemei folosim o stivă st. La baza stivei (nivelul 1) se încarcă numărul 1.
Prezentăm în continuare modul de rezolvare a problemei:

11
2 de la oraşul 1 la oraşul 2 există drum, deci se va
1 urca în stivă;

2
oraşul 2 se mai găseşte în stivă, deci nu este
2
acceptat;
1

3
de la oraşul 2 la oraşul 3 există drum; prin oraşul
2
3 nu s-a mai 2 trecut, deci oraşul 3 este acceptat;
1

Algoritmul continuă în acest mod până se ajunge din nou la nivelul 1, caz în care algoritmul se
încheie.
Un succesor, între 2 şi n, aflat pe nivelul k al stivei, este considerat
valid dacă sunt îndeplinite următoarele condiţii:
• nu s-a mai trecut prin oraşul simbolizat de succesor, deci acesta
nu se regăseşte în stivă;
• există drum între oraşul aflat pe nivelul k-1 şi cel aflat pe nivelul
k,
• dacă succesorul se găseşte pe nivelul n, să existe drum de la el la
oraşul 1.

12
#include <stdio.h>
#include <conio.h>
int st[10],a[10][10],n;
void Init(int st[], int k)
{st[k]=1;
}
int Am_Succesor(int st[], int k)
{if(st[k]<n)
{
st[k]++;
return 1;
}
else return 0;
}
int Valid(int st[], int k)
{
if (a[st[k-1]][st[k]]==0) return 0;
for(int i=1;i<=k-1;i++)
if(st[i]==st[k]) return 0;
if ((k==n)&& (a[1][st[k]]==0)) return 0;
return 1;
}
int Solutie(int k)
{ return k==n;}
void Tipar()
{
for(int i=1;i<=n;i++) printf(" Nodul= %d \n ",st[i]);
printf("-------------\n");
}
void back()
{ int k;
int As,Ev;
st[1]=1;k=2;
Init(st,k);
while (k>1)
{ do {As=Am_Succesor(st,k);
if(As==1) Ev=Valid(st,k);

} while (As==1 && Ev==0);


if (As)

13
if (Solutie(k)) Tipar();
else {
k++;
Init(st,k);
}
else k--;
}
}
void main()
{
clrscr();
printf("nr noduri=");scanf("%d",&n);
for (int i=1;i<=n;i++)
for (int j=1;j<=i-1;j++)
{ printf("a[%d,%d]=",i,j);
scanf("%d",&a[i][j]);
a[j][i]=a[i][j];
}
back();
for (i=1;i<=n;i++)
printf("%d \t",st[i]);
printf("\n");
getch();
}

14
Metoda Programării Dinamice

Metoda programării dinamice este aplicabilă problemelor de optim în


care soluţia este privită ca un rezultat al unui şir de decizii d1,d2, …,dn.
Fiecare decizie depinde de deciziile deja luate şi nu este unic
determinată.
d1 d2 d3 d4 dn-1 dn

S0 S1 S2 S3 S4 Sn-1 Sn

Pentru aplicarea metodei este necesară satisfacerea principiului


optimalităţii.

Dacă d1,d2, …,dn este un şir de decizii care conduce sistemul în


mod optim din starea iniţială S0 în starea finală Sn trecând prin stările
intermediare S1, …,Sn-1, atunci trebuie îndeplinită una din condiţiile
următoare (principiul de optimalitate):

1. dk, …,dn este un şir de decizii ce conduce optim sistemul din


starea Sk-1 în starea Sn , ∀ k ∈ {1,2, …, n};
2. d1,d2, …,dk este un şir de decizii care conduce optim sistemul din
starea S0 în starea Sk-1 , ∀ k∈ {1,2, …, n};
3. dk+1, …, dn , d1,d2, …,dk sunt şiruri de decizii care conduc optim
sistemul din starea Sk în starea Sn, respectiv din S0 în starea Sk-1 ,
∀ k ∈ {1,2, …, n}.

Dacă principiul de optimalitate se verifică în forma 1, spunem că se


aplică programarea dinamică metoda înainte.
Dacă principiul de optimalitate se verifică în forma 2, spunem că se
aplică programarea dinamică metoda înapoi.
Dacă principiul de optimalitate se verifică în forma 3, spunem că se
aplică programarea dinamică metoda mixtă.

1
Programarea Dinamică reprezintă o metodă de rezolvare a problemelor pentru care soluţia
poate fi privită ca rezultatul a unor decizii (D1,D2,D3,...,Dn). Fiecare decizie trebuie să
satisfacă principiul optimalităţii.

Luarea deciziilor poate fi realizată în două moduri:

a) decizia Dx depinde de deciziile Dx+1,...,Dn. Spunem că se aplică metoda Inainte. Deciziile


se iau în ordinea : Dn, Dn-1,..., D1.

b) decizia Dx depinde de deciziile D1, D2,..., Dx-1. In acest caz se aplică metoda Inapoi.
Deciziile se iau în ordinea : D1, D2,..., Dn.

Programarea dinamică se poate aplica problemelor la care optimul


general implică optimul parţial.

În programarea dinamică se rezolvă problema combinând soluţiile


subproblemelor ca şi la metoda Divide et Impera. Când subproblemele
conţin subprobleme comune, în locul metodei Divide et Impera este
mai avantajos să aplicăm metoda programării dinamice. Să vedem ce
se întâmplă cu un algoritm Divide et Impera în această situaţie.
Descompunerea recursivă a cazurilor în subcazuri ale aceleiaşi
probleme care apoi sunt rezolvate independent, poate conduce la
calcularea de mai multe ori a aceluiaşi subcaz şi deci conduce la o
scădere a eficienţei algoritmilor. Ca exemplu justificativ, să calculăm
coeficientul biomial

C kn =C n−k1 + C kn−−11 sau, altfel spus:

 k − 1  k 
 k   + , 0 < k < n
  =  n − 1  n − 1
n 
1, in rest

care, în mod direct conduce la următorul algoritm, scris în pseudocod:

2
function C(n,k)
DACA (k=0) or (k=n) ATUNCI return 1
ALTFEL return C(n-1,k-1)+C(n-1,k)
SFDACA

De ce nu s-a folosit formula C nk = n!


k ! ( n − k )! ?

Evident că pe lângă faptul că în prima soluţie se fac numai adunări, mai


avem avantajul că algoritmul se poate programa fără dificultăţi pentru
valori mai mari ale lui n, k. Această rezolvare este imediată, dar ea are
şi un dezavantaj: o mare parte din valorile C(i,j), cu i<n, j<k sunt
calculate în mod repetat.
De exemplu, pentru: C(12,7), funcţia C(1,1) a fost invocată de 210 ori,
iar C(2,2) de 126 ori.

Rezultatele intermediare se vor memora într-un tablou de forma:


0 1 2 3 .... k −1 k
0 1
1 1 1
2 1 2 1
3 1 3 3 1
...
 k − 1 k 
n −1    
 n − 1  n − 1
k 
n  
n

Dacă rezultatele intermediare se vor memora în tabloul de mai


sus, se va obţine un algoritm mai eficient pentru că este suficient să
construim un vector de lungime k pe care îl actualizăm de la dreapta la
stânga. Funcţia de calcul este:

3
int comb(int n, int k)
{
int i,j;
int c[20][20];
for( i=0; i<=n ;i++)
{ c[i][0]=1;
c[i][i]=1;
for( j=1;j<= i-1 ;j++)
c[i][j]=c[i-1][j-1]+c[i-1][j];
}
return c[n][k];
}

Se observă că nu avem nevoie de întreaga matrice. La fiecare pas sunt


necesare doar ultimele două linii. S-a ajuns astfel la primul principiu al
programării dinamice: evitarea calculării de mai multe ori a unor
subcazuri prin memorarea rezultatelor intermediare.

Metoda Divide et Impera operează de sus în jos (top-down),


descompunând cazuri în subcazuri pe care le rezolvă separat. Ajungem
astfel la al doilea principiu al programării dinamice: metoda
programării dinamice operează de jos în sus (bottom-up). Se porneşte
de la cele mai mici subcazuri, se combină rezultatele lor şi se obţin
soluţii pentru cazuri din ce în ce mai mari până se ajunge la soluţia
cazului iniţial.
Programarea dinamică este utilizată în probleme de optimizare,
de unde al treilea principiu al programării dinamice: metoda
programării dinamice este folosită pentru optimizarea problemelor
care satisfac principiul optimalităţii, adică într-o secvenţă optimă de
decizii/alegeri, fiecare subsecvenţă trebuie să fie de asemenea optimă.

4
Un algoritm de programare dinamică poate fi descris prin următoarea
succesiune de paşi:
1. se caracterizează structura unei soluţii optime;
2. se obţine recursiv valoarea unei soluţii optime;
3. se calculează de jos în sus valoarea unei soluţii optime;

În cazul în care se doreşte şi soluţia propriu-zisă atunci avem şi:

4. din informaţiile calculate, se construieşte de sus în jos o soluţie


optimă. Pasul patru se rezolvă printr-un algoritm recursiv, care
efectuează o parcurgere în sens invers a secvenţei optime de
decizii calculate anterior prin algoritmul de programare dinamică.

5
Problema triunghiului

Se consideră un triunghi de numere. Să se calculeze cea mai mare


sumă a numerelor ce apar pe drumurile ce pleacă din vârf şi ajung la
bază astfel:
- în fiecare drum succesorul unui număr se află pe rândul de mai jos,
dedesupt sau pe diagonală la dreapta.
Se cere, de asemenea, care sunt numerele ce o alcătuiesc.

Exemplu: Pentru triunghiul


2
3 5
6 3 4
5 6 1 4

rezultatul va fi 17.

Rezolvare:
Se pot forma mai multe sume:
S1=2+3+6+5=16
S2=2+5+4+1=12
…………….
Sk=2+3+6+6=17 (care este şi suma maximă).
Se observă că se pot forma 2n-1 astfel de sume. A le lua în considerare
pe toate pentru a găsi valoarea optimă (maximă) nu este eficient.

Fie un şir de n numere (care respectă condiţiile problemei şi care


formează suma maximă). În acest şir considerăm numărul care a fost
preluat de pe linia i. Numerele între i+1 şi n, formează o sumă maximă
în raport cu sumele care se pot forma începând cu numărul preluat de
pe linia i (contrar, se contrazice ipoteza).
În această situaţie se poate aplica programarea dinamică, metoda
înainte.

6
Vom forma un triunghi, de la bază către vârf, cu sumele maxime care
se pot forma cu fiecare număr. Dacă am memorat triunghiul de numere
într-o matrice T şi calculăm sumele într-o matrice C, vom avea relaţiile
următoare:
C[n,1]:=T[n,1];
C[n,2]:=T[n,2];
…………
C[n,n]:=T[n,n];
Pentru linia i (i<n), cele i sume maxime se obţin astfel:
C[i,j]:=max{T[i,j]+C[i+1,j], T[i,j]+C[i+1, j+1]}, unde iœ{1,2,…,n-1}
şi jœ{1, …,i}.
Să rezolvăm problema propusă ca exemplu:
Linia 4 a matricii C va fi linia n a matricii T:
5 6 1 4;
Linia 3 se calculează astfel:
C[3,1]=max{6+5,6+6}=12;
C[3,2]=max{3+6,3+1}=9;
C[3,3]=max{4+1,4+4}=8;
Linia 2:
C[2,1]=max{3+12,3+9}=15;
C[2,2]=max{5+9, 5+8}=14;
Linia 1:
C[1,1]=max{2+15,2+14}=17.
Aceasta este şi cea mai mare sumă care se poate forma.

Pentru a tipări numerele luate în calcul se foloseşte o matrice numită


drum, în care, pentru fiecare i din {1,…,n-1} şi j din {1,…,i} se reţine
coloana în care se găseşte succesorul lui T[i,j].

7
#include <stdio.h>
#include <conio.h>
int n,i,j;
int t[20][20],c[20][20],drum[20][20];
void main()
{
printf("nr. linii ale triunghiului ="); scanf("%d",&n);

for(i=1;i<=n;i++)
for(j=1;j<=i;j++)
{
printf("a[%d,%d]=",i,j); scanf("%d",&t[i][j]);
}
for(j=1;j<=n;j++) c[n][j]=t[n][j];

for(i=n-1;i>=1;i--)
{
for(j=1;j<=i;j++)
if (c[i+1][j]<c[i+1][j+1])
{
c[i][j]=t[i][j]+c[i+1][j+1];
drum[i][j]=j+1;
}
else
{
c[i][j]=t[i][j]+c[i+1][j];
drum[i][j]=j;
}
}

printf("suma maxima este=%d \n",c[1][1]);

8
i=1;
j=1;
while (i<=n)
{
printf("\t %d",t[i][j]);
j=drum[i][j];
i=i+1;
}
printf("\n");
getch();
}

Metoda Programării Dinamice

Un algoritm de programare dinamică poate fi descris prin următoarea


succesiune de paşi:

1. se identifică subproblemele problemei date


2. se alege o structură de date capabilă să reţină soluţiile
subproblemelor
3. se caracterizează substructura optimală a problemei printr-o
relaţie de recurenţă
4. pentru a determina soluţia optimă, se rezolvă relaţia de recurenţă
de jos în sus (bottom-up – se rezolvă subprogramele în ordinea
crescătoare a dimensiunii lor)

9
Problema pachetelor

Ionel descarcă de pe Internet o aplicaţie. Aplicaţia a fost împărţită în


mai multe pachete, iar pachetele trebuie descărcate într-o ordine fixată.
Se cunoaşte timpul de descărcare pentru fiecare pachet, precum şi
timpul necesar instalării fiecărui pachet.
Ionel vrea să testeze aplicaţie cât mai repede şi vrea să înceapă
instalarea înainte de a se fi terminat descărcarea tuturor pachetelor. Se
ştie că odată ce începe instalarea nu mai poate fi întreruptă (va da
eroare dacă la momentul în care este necesară instalarea unui pachet
acesta nu este complet descărcat).

Scrieţi un program care să determine numărul minim de secunde (din


momentul în care a început descărcarea) după care Ionel poate începe
instalarea aplicaţiei.

n = numărul de pachete
Inst = timpul de instalare a pachetului
D= timpul de descărcare a pachetului

1§ n § 100000
1§ Inst, D § 1000

Se cere timpul (exprimat în secunde) după care poate începe instalarea


aplicaţiei, timp calculat din momentul începerii descărcării primului
pachet.
O subproblemă:

“Determinaţi timpul minim după care poate începe instalarea aplicaţiei,


în ipoteza că aplicaţia este formată numai din pachetele i, i+1, ..., n-1
(0 § i< n), timp calculat din momentul începerii descărcării pachetului
i”

10
Soluţia fiecărei subprobleme o vom reţine într-un vector min, unde
min[i]= numărul minim de secunde după care poate începe instalarea
aplicaţiei, calculat din momentul începerii descărcării pachetului i, 0 §
i< n.

Relaţia de recurenţă:
• min[n-1]=D[n-1]
(nr. minim de secunde după care poate începe instalarea pachetului
n-1, ultimul pachet, este egal cu timpul necesar descărcării complete
a pachetului n-1)
• min[i]=D[i] + max{0, min(i+1) –Inst[i]}, " 0 § i< n-1.

Numărul minim de secunde după care poate începe instalarea


pachetelor i+1, i+2, ..., n-1 este min[i+1].
Dacă Inst[i] ¥ min[i+1], atunci min[i]=D[i] (deoarece timpul necesar
instalării pachetului i a fost suficient de mare a.i. să acopere timpul
necesar pentru demararea instalării celorlalte pachete)
Dacă Inst[i] < min[i+1], atunci min[i]=D[i]+ (min[i+1]- Inst[i]) (
trebuie adăugat nr. de secunde ce se scurge de la terminarea instalării
pachetului i până când începe instalarea celorlalte pachete)

min[0] este soluţia problemei

11
#include <stdio.h>
#include <stdlib.h>
#include <conio.h>
#define NMAX 1000
#define max(x,y) ((x)>(y)?(x):(y))
FILE *f; // declararea descriptorului de fisier intrare
FILE *g; // declararea descriptorului de fisier iesire
int min[NMAX], D[NMAX], Inst[NMAX];
void main()
{
if((f=fopen("c:\\borlandc\\fin.txt","r"))==0)
{
printf("Fisierul nu poate fi deschis !");
exit(1);
}
if((g=fopen("c:\\borlandc\\fout.txt","w"))==0)
{
printf("Fisierul nu poate fi creat !");
exit(1);
}
int n, i;
fscanf(f,"%d",&n);
for(i=0;i<n;i++)
fscanf(f,"%d%d",&Inst[i],&D[i]);
fclose(f);
min[n-1]=D[n-1];
for(i=n-2;i>=0;i--)
min[i]=D[i]+max(0, min[i+1]-Inst[i]);
fprintf(g, "%d\n",min[0]);
fclose(g);
getch();
}

12
Tehnici Avansate de Programare Îndrumător de laborator

Laborator 1.Pointeri în C
Problema 1. Să se scrie un program care declară o variabilă de tip “ int” și una de tip “float”, apoi să se
afișeze valorile acestora, adresele și dimensiunile lor.

#include <stdio.h> "p = %p\n\n", *p, p);


#include <conio.h>
// afisarea adreselor celor 3 variabile
void main()
{ printf("Afisare adrese variabile:\n"
int i = 1; "adr_i = %p\n"
float f = -1.5; "adr_p = %p\n"
int *p = &i; "adr_f = %p\n\n", &i, &p, &f);

// afisare valori; // afisarea dimensiuni

printf("Afisare valori:\n" printf("Afisare dimensiuni:\n"


"i = %5d\n" "&i = %d\n"
"f = %.2f\n\n", i, f); "p = %d\n"
"&p = %d\n"
// afisarea valorii si adresei lui i folosind "&f = %d\n\n", sizeof(&i),
pointerul p; sizeof(p), sizeof(&p), sizeof(&f));

printf("Afisare valoare si adresa:\n" getch();


"*p = %d\n" }

La lansarea în execuție a programului pe ecran va apărea:

Figura .1.

1
Tehnici Avansate de Programare Îndrumător de laborator

Problema 2. Să se scrie un program care realizează interschimbarea a două variabile folosind o funcție.

#include <stdio.h>
#include <conio.h>
// afisare valori noi
void interschimba(int *, int *); printf("a = %d\tb = %d", a, b);

void main() getch();


{ }
int a = 3, b = 7;
void interschimba(int *p, int *q)
// afisare valori initiale {
printf("a = %d\tb = %d\n", a, b); int temp;

// interschimbare temp = *p;


interschimba(&a, &b); *p = *q;
*q = temp;
}
La lansarea în execuție a programului pe ecran va apărea:

Figura.2.

2
Tehnici Avansate de Programare Îndrumător de laborator

Problema 3. Să se scrie un program care citește și afișează elementele a două tablouri, la primul tablou
accesul se face indexat, iar la al doilea tablou, accesul se realizează prin pointeri.

#include <stdio.h> puts("Introduceti elementele lui tab2:");


#include <conio.h> for (pi=t; pi<t+N; pi++)
#define N 5 {
putchar(':');
void citire1(int *t) scanf_s("%d", pi);
{ }
int i = 0; }
puts("Introduceti elementele lui tab1:"); void tiparire2(int *t)
for (i; i<N; i++) {
{ int *pi;
putchar(':'); puts("Elementele lui tab2 sunt:");
scanf_s("%d", &t[i]); for (pi=t; pi<t+N; pi++)
} printf("%d ", *pi);
} putchar('\n');
void tiparire1(int *t) }
{ void main()
int i = 0; {
puts("Elementele lui tab1 sunt:"); int tab1[N], tab2[N];
for (i=0; i<N; i++) citire1(tab1);
printf("%d ", t[i]); tiparire1(tab1);
putchar('\n'); putchar('\n');
} citire2(tab2);
void citire2(int *t) tiparire2(tab2);
{ getch();
int *pi; }

La lansarea în execuție a programului pe ecran va apărea:

Figura.3.

3
Tehnici Avansate de Programare Îndrumător de laborator

Problema 4. Se citesc și se tipăresc elementele a trei tablouri de numere întregi, de dimensiuni diferite.
Citirea elementelor unui tablou se face până la tastarea lui CTRL+Z sau până la introducerea numărului
maxim de elemente. Pentru citirea, respectiv tipărirea tablourilor se va utiliza câte o funcție care trebuie
să primească ca parametru dimensiunile tablourilor.

#include <stdio.h> void tiparire(int *tab, int dc, char *nume)


#include <conio.h> {
int i = 0;
void citire(int *tab, int dim, int *dc, char *nume) printf("Elementele tabloului '%s'\n",
{ nume);
/*functia primeste adresa tabloului, numarul for (; i<dc; i++)
maxim de elemente, adresa variabilei ce va printf("%d ", tab[i]);
contine numarul de elemente cititsi numele putchar('\n');
tabloului*/ }

int i = 0; void main()


printf("Introduceti elementele tabloului {
'%s' (max %d sau terminati cu CTRL+Z):\n", int t1[2], t2[30], t3[40], d1, d2, d3;
nume, dim); citire(t1, 2, &d1, "tabelul 1");
citire(t2, 30, &d2, "tabelul 2");
for (; i<dim; i++) citire(t3, 40, &d3, "tabelul 3");
if (scanf_s("%d", &tab[i]) == EOF) tiparire(t1, d1, "tabelul 1");
break; tiparire(t2, d2, "tabelul 2");
*dc = i; tiparire(t3, d3, "tabelul 3");
} getch();
}

La lansarea în execuție a programului pe ecran va apărea:

Figura.4.

4
Tehnici Avansate de Programare Îndrumător de laborator

Problema 5. Înlocuirea variabilelor cu indici prin expresii cu pointeri duc la optimizări, înlocuindu-se
operațiile de înmulțire care intervin în evaluarea expresiilor cu indici, prin adunări.

#include <stdio.h>
#include <conio.h> scanf_s("%f", &t[2]);
// sau scanf_s("%f", p+2);
void main()
{ printf("%.2f", t[2]);
float *p, t[10]; // sau printf("%f", *(p+2));

p = t; getch();
// sau p = &t; }
// sau p = &t[0];

La lansarea în execuție a programului pe ecran va apărea:

Figura.5.

Problema6. Să se scrie o funcție care citește cel mult n elemente de tip double și le păstrează în zona de
memorie a cărei adresă de început este valoarea parametrului formal p a funcției.

#include <stdio.h> double *q = p + n;


#include <malloc.h>
#include <conio.h> while (p<q)
{
void citeste(int n, double *p) printf("elementul [%d] = ", i);
{ scanf_s("%lf", p++);
int i = 0;
5
Tehnici Avansate de Programare Îndrumător de laborator
i++;
} puts("\nElementele citite sunt:");
} for (i=0; i<n; i++)
printf("elementul [%d] = %.2lf\n",
void main() i, pd[i]);
{
int n = 3, i; free(pd);
double *pd = (double *)malloc(n *
sizeof(double)); getch();
}
citeste(n, pd);

La lansarea în execuție a programului pe ecran va apărea:

Figura.6.

6
Tehnici Avansate de Programare Îndrumător de laborator

Laborator 2 & 3. Prelucrarea fișierelor

Problema1. Să se scrie un program pentru crearea unui fișier binar, având articole structuri cu
următoarele câmpuri:

•nume depunător : șir de maximum 30 caractere;


•data depunerii : zi, luna, an;
•suma depusă : o valoare întreagă.

Articolele sunt grupate pe zile în ordine cronologică. Să se afișeze conținutul fișierului.

#include <stdio.h> for (i=0; i<n-1; i++)


#include <conio.h> {
#include <stdlib.h> if (
(a[i].data.an > a[i+1].data.an) ||
void main() (a[i].data.an == a[i+1].data.an &&
{ a[i].data.luna > a[i+1].data.luna) ||
struct (a[i].data.an == a[i+1].data.an &&
{ a[i].data.luna > a[i+1].data.luna &&
char nume[30]; a[i].data.zi > a[i+1].data.zi)
struct )
{ {
int zi, luna, an; aux = a[i];
} data; a[i] = a[i+1];
long int suma; a[i+1] = aux;
} a[20], aux, citite[20]; ind = 1;
}
FILE *df; }
int n, ind, i; } while (ind);
printf("citire date intrare\n");
printf("Introduceti numarul de articole : "); // deschidere fisier pentru creare
scanf("%d", &n); if ((df = fopen("persoana.dat", "wb")) == 0)
{
for (i=0; i<n; i++) printf(" :( Nu se poate deschide fisierul...\n");
{ exit(1);
printf("\nnumele : "); }
fflush(stdin);
gets(a[i].nume); // scriem in fisier
printf("data ZZ/LL/AAAA : "); scanf("%d/ fwrite(&n, sizeof(int), 1, df);
%d/%d", &a[i].data.zi, &a[i].data.luna, fwrite(&a, sizeof(aux), n, df);
&a[i].data.an); fclose(df);
printf("suma : "); scanf("%ld", &a[i].suma);
} // deschidere fisier pentru citire
if ((df = fopen("persoana.dat", "rb")) == 0)
// sortare {
do printf(" :( Nu se poate deschide fisierul...\n");
{ exit(1);
ind = 0; }

1
Tehnici Avansate de Programare Îndrumător de laborator
printf("\n %s \t %02d/%02d/%04d \t
// afisam continutul fisierului %ld\n", a[i].nume, a[i].data.zi, a[i].data.luna,
fread(&n, sizeof(int), 1, df); a[i].data.an, a[i].suma);
fread(&citite, sizeof(aux), n, df); fclose(df);
for (i=0; i<n; i++) getch();

La lansarea în execuție a programului pe ecran va apărea:

Figura.1.

Problema2. Să se scrie un program care copiază conținutul unui fișier al cărui nume este specificat la
linia de comandă într-un alt fișier al cărui nume este predefinit în cadrul programului.

#include <stdio.h> puts(" :( Nu am putut sa deschid fisierele.");


#include <conio.h> getch();
int main(int argc, char *argv[]) return 1;
{ }
FILE *fisi, *fise; while ((ch = fgetc(fisi)) != EOF)
int ch, num_bytes = 0; {
if (argc < 2) fputc(ch, fise);
{ num_bytes++;
puts("Nu ati indicat fisierul de copiat."); }
getch();
return 1; fclose(fisi);
} fclose(fise);
if ( printf("Bytes copiati : %d", num_bytes);
(!(fisi = fopen(argv[1], "rb"))) || getch();
(!(fise = fopen("fis_copiat.dat", "wb")))
) return 0;
{ }

2
Tehnici Avansate de Programare Îndrumător de laborator
La lansarea în execuție a programului pe ecran va apărea:

Figura.2.

Problema 3. Să se scrie un program care afișează lungimea celei mai lungi linii din fișierul text TEST:
/*------------------------------------------------*/
/* */
/* afiseaza lungimea lg_max a celei mai lungi */
/* linii a fisierului */
/* */
/*------------------------------------------------*/

#include <stdio.h>
while ((c=getc(f))!=EOF)
void main(void) if (c=='\n')
{ {
FILE *f; if (lg_max<lg_curenta)
char c; lg_max = lg_curenta;
int lg_max, lg_curenta; lg_curenta = 0;
}
lg_max=lg_curenta=0; else
lg_curenta++;
if (!(f=fopen("TEST", "r")))
{ fclose(f);
puts("Fisierul TEST nu poate fi deschis"); printf ("\nLinia cea mai lunga are lungimea
return; %d", lg_max);
} }

Problema 4. Să se scrie un program care copiază un fișier binar sursă în alt fișier binar destinație.

/*---------------------------------------*/
/* */
/* copiaza continutul unui fisier binar */
/* in alt fisier binar */
/* */
/----------------------------------------*/

3
Tehnici Avansate de Programare Îndrumător de laborator
#include <stdio.h> fprintf(stderr, "Fisierul dest nu poate fi
deschis\n");
void main(void) return;
{ }
FILE *fs, *fd;
char c; c=getc(fs);
while (!feof(fs))
if ((fs=fopen("sursa", "rb"))==NULL) {
{ putc(c, fd);
fprintf(stderr, "Fisierul sursa nu poate fi c=getc(fs);
deschis\n"); }
return;
} fclose(fs);
fclose(fd);
if ((fd=fopen("dest", "wb"))==NULL) }
{

Problema 5. Să se realizeze un program care păstrează evidența unei grupe de studenți. Datele despre
studenți (numele, vârsta, media) se păstrează sub forma unui fișier text. Programul trebuie să permită
următoarele opțiuni:

• a, A - adăugarea unui nou student în fișier;


• l, L - listarea datelor tuturor studenților;
• m, M - modificarea datelor unui student;
• x, X - terminarea programului;

/*------------------------------*/ /* adauga datele unui nou student in fisier */


/*evidenta unei grupe de studenti /*-----------------------------------------------*/
*/
/*------------------------------*/
void AdaugStudent(void)
#include <stdio.h> {
#include <conio.h> FILE *f;
#include <ctype.h> student s;
#include <stdlib.h>
#include <string.h> if (!(f=fopen(fisier, "at")))
#define LNUME 20 {
/* lungimea maxima a numelor */ puts("\nFisierul nu poate fi deschis.");
return;
typedef struct }
{
char nume[LNUME]; printf("\nNumele, varsta, media: ");
int varsta; scanf("%s %d %f", s.nume, &s.varsta,
float medie; &s.medie);
} student;
fflush(stdin); /* se goleste buffer-ul tastaturii */
char fisier[13]; /* numele fisierului */ fprintf(f, "%20s %2d %6.2f\n", s.nume,
s.varsta, s.medie);
/*----------------------------------------------*/ fclose(f);
/* */ }
4
Tehnici Avansate de Programare Îndrumător de laborator
{ /* studentul a fost gasit, se afiseaza datele
/*-------------------------------------------*/ sale */
/* */ printf("%-20s %-2d %-
/* afiseaza datele tuturor studentilor */ 6.2f\n",s.nume,s.varsta,s.medie);
/* */ gasit=1;
/*--------------------------------------------*/ break;
}
void ListezStudenti(void)
{ if (!gasit)
FILE *f; printf("\nStudentul %s nu exista in fisier.", n);
student s; else
{
if (!(f=fopen(fisier, "rt"))) printf("\nNumele, varsta, media: ");
{ scanf("%s %d %f", s.nume, &s.varsta,
puts("\nFisierul nu poate fi deschis."); &s.medie);
return; fflush(stdin);
}
/* pozitionare la inceputul inregistrarii */
while(fscanf(f,"%s %d fseek(f, -30, SEEK_CUR);
%f",s.nume,&s.varsta,&s.medie)!=EOF)
printf("\n%-20s %-2d %- fprintf(f,"%20s %2d %6.2f\n", s.nume,
6.2f\n",s.nume,s.varsta,s.medie); s.varsta, s.medie);
}
fclose(f);
} fclose(f);
}
/*-----------------------------------------------*/
/* */ /*---------------------------------- */
/* modifica datele studentului */ /* */
/* al carui nume se citeste in variabila n */ /* afiseaza meniul programului */
/*-----------------------------------------------*/ /* */
/*----------------------------------*/
void ModificStudent(void)
{ void AfisezMeniu(void)
int gasit=0; {
FILE *f; puts("\na, A ---- adaugare student");
student s;
char n[LNUME]; /* numele studentului ale */ puts("m, M ---- modificare date student");
/* carui date se vor modifica */ puts("l, L ---- listare studenti");
puts("x, X ---- parasire program");
if (!(f=fopen(fisier, "r+t"))) }
{
puts("Fisierul nu poate fi deschis."); void main(void)
return; {
} char opt;

printf("\nNume student: "); puts("Nume fisier: ");


fgets(n, LNUME, stdin); fgets(fisier, 13, stdin);

while(fscanf(f,"%s %d while (1)


%f",s.nume,&s.varsta,&s.medie)!=EOF) {
if (!strcmp(n,s.nume)) AfisezMeniu();
5
Tehnici Avansate de Programare Îndrumător de laborator
opt=tolower(getche()); case 'x':
exit(0);
switch(opt) default:
{ puts("Comanda eronata\n");
case 'a': }
AdaugStudent(); break; }
case 'm': }
ModificStudent(); break;
case 'l':
ListezStudenti(); break;

Se observă că cele trei operații principale efectuate asupra fișierului: adăugare, listare și modificare sunt
implementate în trei funcții distincte.

Funcția adaugă înscrie în fișier datele unui student sub forma unei înregistrări de 30 de octeți (20 pentru
nume, 1 spațiu, 2 pentru vârstă, 1 spațiu, 6 pentru medie). Acest lucru este folosit în funcția modifică, când
se repoziționează indicatorul de poziție în fișier la începutul înregistrării care urmează a fi modificată.

6
Indrumător de laborator

Laborator 4. Metode de sortare

Problema1. Să se scrie un program care realizează sortarea unui vector prin inserție și prin
interclasare.

#include <stdio.h> v[d] = aux;


#include <malloc.h> }
#include <conio.h> }
void insertie(int v[], int n); void interclasare(int x[], int m, int y[], int n,
void insertie2(int v[], int n); int z[])
void interclasare(int x[], int m, int y[], int n, {
int z[]); int i = 0, j = 0, k = 0;
void insertie(int v[], int n) while (i < m && j < n)
{ {
int i, j, k; if (x[i] < y[j])
for (j=1; j<n; j++) {
{ z[k] = x[i];
k = v[j]; i++;
i = j - 1; }
while (i>=0 && v[i] > k) else
{ {
v[i+1] = v[i]; z[k] = y[j];
i = i - 1; j++;
} }
v[i+1] = k;
} k++;
} }
void insertie2(int v[], int n) if (i < m)
{ {
int i, j, s, d, mij, aux; for (j=i; j<m; j++)
for (i=1; i<n; i++) {
{ z[k] = x[j];
aux = v[i]; k++;
s = 0; }
d = i; }
while (s<d) else if (j < n)
{ {
mij = (s+d)/2; for (i=j; i<n; i++)
if (v[mij] <= v[i]) {
s = mij + 1; z[k] = y[i];
else k++;
d = mij; }
} }
for (j=i; j>d; j--) }
v[j] = v[j-1]; void main()

1
Indrumător de laborator

{ }
int *v;
int n, i; // sortare
int x[] = {30, 80, 120, 140, 170}; insertie(v, n);
int y[] = {2, 9, 11, 20}; //insertie2(v, n);
printf("Dimensiunea vectorului: "); //interclasare(x, 5, y, 4, v);
scanf("%d", &n);
v = (int *)malloc(n * sizeof(int)); // afisare vector sortat
printf("\nVector sortat:\n");
// citire vector for (i=0; i<n; i++)
for (i=0; i<n; i++) printf("%d ", v[i]);
{ free(v);
printf("v[%d] = ", i); getch();
scanf("%d", &v[i]); }

La lansarea în execuție a programului pe ecran va apărea:

Figura.1.

2
Indrumător de laborator

Problema2. Să se scrie un program care realizează sortarea unui vector prin interschimbare.

#include <stdio.h> {
#include <malloc.h> if (v[j-1] > v[j])
#include <conio.h> {
void sort(int v[], int n) aux = v[j-1];
{ v[j-1] = v[j];
int i, ind, aux; v[j] = aux;
k = j;
do }
{ }
ind = 0; d = k;
for (i=0; i<n-1; i++) } while (s < d);
{ }
if (v[i] > v[i+1]) void main()
{ {
aux = v[i]; int *v;
v[i] = v[i+1]; int n, i;
v[i+1] = aux; printf("Dimensiunea vectorului: ");
ind = 1; scanf("%d", &n);
} v = (int *)malloc(n * sizeof(int));
}
} while (ind != 0); // citire vector
} for (i=0; i<n; i++)
{
void shaker(int v[], int n) printf("v[%d] = ", i);
{ scanf("%d", &v[i]);
int s = 0, d = n, k = n, j, aux; }

do sort(v, n);
{ //shaker(v, n);
for (j=d-1; j>s; j--)
{ // afisare vector sortat
if (v[j-1] > v[j]) printf("\nVector sortat:\n");
{ for (i=0; i<n; i++)
aux = v[j-1]; printf("%d ", v[i]);
v[j-1] = v[j];
v[j] = aux; free(v);
k = j;
} getch();
}
}
s = k;
for (j=s; j<d; j++)

3
Indrumător de laborator

La lansarea în execuție a programului pe ecran va apărea:

Figura.2.

Problema3. Să se scrie un program care realizează sortarea unui vector prin selecție.

#include <stdio.h> }
#include <malloc.h> }
#include <conio.h>
void main()
void selectie(int v[], int n) {
{ int *v;
int i, j, aux; int n, i;
for (i=0; i<n; i++) printf("Dimensiunea vectorului: ");
{ scanf("%d", &n);
for (j=i; j<n; j++) v = (int *)malloc(n * sizeof(int));
{
if (v[i] > v[j]) // citire vector
{ for (i=0; i<n; i++)
aux = v[i]; {
v[i] = v[j]; printf("v[%d] = ", i);
v[j] = aux; scanf("%d", &v[i]);
} }
}

4
Indrumător de laborator

selectie(v, n);
free(v);
// afisare vector sortat
printf("\nVector sortat:\n"); getch();
for (i=0; i<n; i++) }
printf("%d ", v[i]);

La lansarea în execuție a programului pe ecran va apărea:

Figura.3.

5
Indrumător de laborator

Laborator 5.Recursivitate

Problema 1. Se dă un tablou A(1,n) de numere întregi și se cere să se determine maximul elementelor sale
folosind o funcție recursivă.

#include <stdio.h> else


#include <conio.h> return y;
}
int a[10], n; }
int maxim(int i, int j) void main()
{ {
int x, y; int i;
printf("Numarul de elemente:
if (i == j) ");scanf("%d", &n);
{
return a[i]; for (i=0; i<n; i++)
} {
else printf("a[%d] = ", i); scanf("%d",
{ &a[i]);
x = maxim(i, (i+j)/2); }
y = maxim ((i+j)/2+1, j); printf("\nMaximul este %d", maxim(0, n));
getch();
if (x > y) }
return x;

La lansarea în execuție a programului pe ecran va apărea:

Figura.1.

1
Indrumător de laborator

Problema 2. Să se scrie un program care citește pe n de tip int, calculează și afișeaza n! Folosind funcția
recursivă factorial.

#include <stdio.h> for( ; ; ){


#include<stdlib.h> printf („valoarea lui n:”);
#define MAX 170 if ( gets(t)==0){
double fact(int n) /*calculeaza pe n!*/ printf („s-a tastat EOF\n”);
{ exit(1);
if (n==0) return 1.0; }
else return n*fact(n-1); if (sscanf (t,”%d” ,&n)==1 &&n>=0&&n<=MAX
} ) break;
/*citeste pe n,calculeaza si afiseaza n!*/ printf(„se cere un intreg in intervalul
void main() [0,%d]\n”,MAX);
{ }
int n; printf („n=%d\tn!=%g\n”,n,fact(n));
char t[255]; }

Problema 3. Citirea a n cuvinte (şiruri de caractere), fiecare terminat cu spaţiu şi tipărirea lor în oglindă.

/* Programul citeste n cuvinte separate cu spatiu; (dupa ultimul cuvant va exista spatiu si <ENTER>) si
le afiseaza "in oglinda" */

#include <stdio.h> int n,i;


#include <conio.h> printf("\nNumarul de cuvinte=");
void revers(void) scanf("%d",&n);
{ for(i=1;i<=n;++i)
char c; {
scanf("%c",&c); revers();
if (c!='\40') printf("\n");
{printf("%c",c);revers();}; };
printf("%c",c); printf("\nPROGRAMUL S-A
} TERMINAT!!!\n");
getch();
void main(void) }
{

Funcţia revers citeşte câte un caracter pe care îl afişează până la întâlnirea spaţiului (terminatorul
şirului de caractere). Fiecare autoapel conduce la păstrarea în stivă a variabilei locale c. Apariţia
spaţiului conduce la terminarea apelurilor recursive ale funcţiei, urmând scrierea spaţiului şi a
caracterelor în ordinea inversă introducerii lor.

Problema 4. Determinarea termenului minim al unui şir de n întregi.

#include <stdio.h> int minim(int x,int y)


#include <conio.h> {
/* Programul calculeaza minimul dintr-un sir cu if (x<=y) return x;
termeni intregi */ else return y;
#define NMAX 100 }
#define MAXIM 32767
int sir[NMAX]; int termen_minim(int dim_sir)

2
Indrumător de laborator

{ printf("sir[%d]=",i);
if (dim_sir>=0) return scanf("%d",&sir[i]);
minim(sir[dim_sir],termen_minim(dim_sir-1)); };
else return MAXIM; printf("\nSIRUL INTRODUS\n");
} for(i=0;i<n;++i)
{
void main(void) printf("%6d",sir[i]);
{ if ((i+1) % 10 == 0) printf("\n");
int i,n; };
printf("\nIntroduceti nr de termeni ai sirului printf("\nCel mai mic termen este
n="); %d\n",termen_minim(n-1));
scanf("%d",&n); printf("\nApasati o tasta!");
printf("\nIntroduceti valorile termenilor\n"); getch();
for(i=0;i<n;++i) }
{

Problema 5. Varianta recursivă şi nerecursivă a găsirii valorii celui de al n-lea termen al şirului lui
Fibonacci.

Şirul lui Fibonacci este definit astfel:

Fib(0)=0; Fib(1)=1;

Fib(n)=Fib(n-1)+Fib(n-2) pentru n > 2

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

/* Numerele lui Fibonacci */ void main(void)


int fib1(int n) {
/* Varianta recursivă*/ int n;
{ char ch;
if (n==0) return 0; ch='D';
else if (n==1) return 1; while ((ch=='d')|| (ch=='D'))
else return (fib1(n-1)+fib1(n-2)); {
} printf("\nIntroduceti n=");
scanf("%d",&n);
int fib2(int n) printf("\nCALCUL RECURSIV: fib(%d)=
/* Variante nerecursivă*/ %d\n",n,fib1(n));
{ printf("\nCALCUL NERECURSIV: fib(%d)=
int i,x,y,z; %d\n",n,fib2(n));
if (n==0) return 0; printf("\nDoriti sa continuati ? Da=D/d");
else if (n==1) return 1; ch=getch();
else { }
x=1;y=0; }
for(i=2;i<=n;++i)
{
z=x;x=x+y;y=z;
};
return x;

3
Indrumător de laborator

Laborator 6 & 7. Metoda Greedy


Problema 1. Se generează un vector f(n), conţinând primele n<=30 elemente din şirul lui
Fibonacci. (f(0)=1, f(1)=1, f(n)=f(n-1)+f(n-2)). Folosind algoritmul de căutare binară, să se
stabilească dacă un element k citit de la tastatură aparţine sau nu vectorului. În caz afirmativ
să se precizeze şi poziţia pe care apare.

#include <stdio.h> int i,k,c;


#include <conio.h>
for(i=0; i<30; i++)
int f; {
int a[30]; a[i]=fib(i);
int fib(int f) // printf("f(%d)=%ld\n",i,a[i]);
{
if(f<0) return 0;
else if (f==1 || f==0) return 1;
else return fib(f-1)+fib(f-2); }
} printf("Caut:");
scanf("%d",&k);
int cautare(int a[], int n,int k) if(!cautare(a,30,k))
{ printf("Element
int mij, inf, sup,ind; negasit!");
inf=1; sup=n; else
ind=0; {
do for(i=0; i<30; i++)
{ {
mij=(inf+sup)/2; a[i]=fib(i);
if(a[mij]==k) ind=1; if(k==a[i])
else if(a[mij]<k) inf=mij+1; {
else sup=mij-1; c=i
} while(inf<=sup && ind==0); printf("Element gasit! la pozitia:%d\n",c);
if(ind==1) return mij; }
else return 0; }
} }

void main(void) getch();


{ }

1
Indrumător de laborator

Problema 2. Fiind date monede de valori v1, v2,...,vn şi o sumă C, să se determine numărul
minim de monede necesar pentru a acoperi suma C. Se presupune că există un număr
nelimitat de monede. (Problema monedelor)

A rezolva problema înseamnă a determina mulțimea ( ), unde ,reprezintă


numărul de monede de valoare ( poate fie gal cu 0), astfel încât:

și
să fie mai mică.

O abordare de tip Greedy conduce la următorul algoritm :

Monede (v[1...n],C) C = C mod v[i]


OrdonareDescrescatoare(v[1...n]) i=i+1
FOR i=1,n DO S[i]=0 ENDWHILE
ENDFOR IF C=0 THEN Return S[1...n]
i=1 ELSE “nu s-a gasit solutia”
WHILE (C>0 si i<=n) DO ENDIF
S[i]= [C/v[i]] End

s[i]=0;
#include<stdio.h> i=1;
#include<conio.h> while((c>0)&&(i<=6))
void main() {
{ s[i]=c/a[i];
int a[6]; c=c%a[i];
a[1]=100; a[2]=50; printf("%d %d \n", s[i], c);
a[3]=20; a[4]=10; i++;
a[5]=5; a[6]=1; }
int s[6], i ,c; if (c==0)
clrscr(); for(i=1; i<=6; i++)
printf("Suma de platit="); scanf("%d", &c); printf("%d \t", s[i]);
for(i=1; i<=6; i++) else printf("nu se poate achita suma");
printf("\n");
getch();
}

2
Indrumător de laborator

Observatie:
Tehnica Greedy nu conduce pentru orice valori la Soluția optimă.
De exemplu, pentru cazul monedelor cu valorile (25,20,10,5,1) și suma C=40, tehnica Greedy
conduce la Soluția (1,0,1,1,0), care nu este Soluția optimă; varianta (0,2,0,0,0) este Soluția
optimă. Pe de altă parte există situații în care problema nu are soluții. De exemplu, pentru cazul
monedelor cu valorile (25,20,10,5) și suma C=27, problema nu are soluție.
Un exemplu de valori pentru care se poate demonstra optimitatea soluției este , multiplu al
lui pentru orice i≥2.

Problema 3. Se consideră n obiecte și un rucsac. Obiectul i are greutatea , iar rucsacul are
capacitatea M. În momentul în care se depune în rucsac o fracțiune xi, 0 <= xi <= 1 din
obiectul i profitul obținut este . Se cere să se umple rucsacul astfel încât să se obțină un cost
maxim.( Problema rucsacului ).

Problema care se pune este selectarea unei submulțimi de obiecte care să nu depășească
capacitatea rucsacului :

și să asigure un profit maxim.

Pentru această problemă se cunosc 2 variante:


1. Varianta discretă. În acest caz, obiectele preluate pentru a fi depuse în rucsac, nu pot fi
fracționate, adică un obiect va fi preluat în întregime sau deloc.
2. Varianta continuă (fracționară). În acest caz, obiectele preluate pentru a fi depuse în rucsac,
pot fi fracționate, profitul fiind proporțional cu fracționarea.

Aplicând tehnica Greedy, doar pentru varianta continuă putem obține o soluție optimă. În
continuare această variantă o vom analiza.
În momentul în care se depune în rucsac o fracțiune 0 ≤ ≤1 din obiectul i profitul
obținut este.
Formal problema se formulează astfel : să se determine astfel încât consumul
(1)

să fie maxim, în condițiile în care :

(2)

3
Indrumător de laborator

și
(3) 0≤ ≤1, ≥0, ≥0, 1 ≤i ≤n.

Se consideră soluție posibilă setul de valori ( ) care satisface condițiile (2) și (3).
Deci trebuie determinată acea soluție care maximizează costul (1).
De exemplu, pentru problema care are n = 3, M=20,( )=(25,24,15) și (
)=(18,15,10) există cel puțin următoarele două soluții :

( )
(1/2,1/3,1/4) 16,5 24.25
(0,0,1) 10 15

Analizând problema, ajungem la următoarele concluzii :

1. În cazul în care este evident că =1 pentru oricare 1 ≤ i ≤ n si deci (1,1,...,1)


este o soluție optimală.
2. În cazul în care este o soluție optimală se umple rucsacul. Să presupunem
. În aceste condiții există j astfel încât <1.
Fie Soluția ( ) pentru care avem dacă i≠j și unde
. Soluția astfel construită are costul mai mare decât cea
inițială, deci s-a ajuns la o contradicție.
3. Dacă se consideră strategia Greedy care include în rucsac obiectele în ordinea
descrescătoare a profitului, nu se obține o soluție optimă. De exemplu, în cazul
problemei pentru care n=2, M=2, ( )=(4,3) și ( )=(4,2), strategia Greedy
anterioară ne conduce la Soluția (1/2,0) care are costul 2, în timp ce Soluția (0,1) are
costul 3.
4. Dacă se consideră strategia Greedy care include obiectele în ordinea crescătoare a
greutății, nu se obține o soluție optimă. De exemplu, pentru problema dată de n=2, M=2,
( )=(2,1),( )=(1,8), strategia anterioară conduce la soluția (1,0) care are
costul 1, în timp ce Soluția (1,1) are costul 8.5.

Observațiile de mai sus ne conduc la ideea încercării unei strategii care să ia în considerare
factorul de greutate cât și cel de profit. Deci, vom încerca strategia conform căreia se adaugă la
fiecare pas obiectul care are raportul maxim. Dacă obiectele sunt aranjate astfel încât :
, algoritmul corespunzător acestei strategii este :

procedura RUCSAC(p,w,M,x,n)este: ENDFOR


FOR i=1,n DO x(i)←0 rest←M

4
Indrumător de laborator

i←1 x(i)←w(i)
WHILE(i≤n si rest <>0)DO rest←rest – w(i)
IF w(i) >rest THEN ENDIF
x(i)←rest/w(i) i←i+1
rest ←0 ENDWHILE
ELSE End

#include <stdio.h> inv = 1;


#include <conio.h> aux1 = ordine[i]; ordine[i] = ordine[i+1];
ordine[i+1] = aux1;
void main() }
{ }
double c[9], g[9], ef[9], gv, aux, castig; } while (inv);
int n, i, aux1, inv, ordine[9];
printf("Greutatea = "); scanf("%lf", &gv); i = 0;
printf("Nr. obiecte = "); scanf("%d", &n); castig = 0;
for (i=0; i<n; i++) while (gv > 0 && i<n)
{ {
printf("c[%d] = ", i); scanf("%lf", &c[i]); if (gv > g[i])
printf("g[%d] = ", i); scanf("%lf", &g[i]); {
printf("\n"); printf("Obiectul %d intreg\n", ordine[i]);
ordine[i] = i; gv -= g[i];
ef[i] = c[i] / g[i]; castig += c[i];
} }
do else
{ {
inv = 0; printf("Obiectul %d in raport de %lf\n",
for (i=0; i<n-1; i++) ordine[i], gv/g[i]);
{ castig += c[i] * gv / g[i];
if (ef[i] < ef[i+1]) gv = 0;
{ }
aux = ef[i]; ef[i] = ef[i+1]; ef[i+1] = aux; i++;
aux = c[i]; ef[i] = c[i+1]; c[i+1] = aux; }
aux = g[i]; ef[i] = g[i+1]; g[i+1] = aux; printf("Castig total = %lf\n", castig);
getch();
}

5
Indrumător de laborator

La lansarea în execuție a programului pe ecran va apărea:

Figura.1.

Problema 4. Într-o sală, într-o zi trebuie planificate n spectacole. Pentru fiecare spectacol se
cunoaște intervalul de timp în care se desfășoară (ora de început, ora de sfârșit). Se cere să se
planifice un număr maxim de spectacole astfel încât să nu se suprapună spectacolele.
(Problema spectacolelor).

Se consideră o mulțime de spectacole ( activități ) care au nevoie de o anumită resursă și la un


moment dat o singură activitate poate beneficia de resursa respectivă. Presupunem că fiecare
activitate se desfășoară în intervalul [ ). ( momentul de la începutul activității și
momentul de încheiere al activității). Două activități se consideră compatibile dacă intervalele
asociate sunt disjuncte. Se cere să se determine un număr cât mai mare de activități compatibile.
O soluție a acestei probleme constă dintr-o submulțime de activități B=( ) care
satisfac condiția [ ) [ )= Ø, pentru orice i≠j.
Criteriul de selecție ar putea fi pentru această problemă unul din următoarele:
1. Cea mai mică durată de desfășurare a activității;
2. Cel mai mic moment de început a activității;
3. Cel mai mic moment de terminare a activității;
4. Intervalul care se intersectează cu cele mai puține alte intervale.
Dintre aceste variante cea pentru care se poate demonstra că asigură obținerea soluției optime
este a treia, drept exemplificare vom considera problema spectacolelor.

6
Indrumător de laborator

Într-o sală, într-o zi trebuie planificate n spectacole. Pentru fiecare spectacol se cunoaște
intervalul de timp în care se desfășoară (ora de început, ora de sfârșit). Se cere, să se planifice un
număr cât mai mare de spectacole astfel încât să nu se suprapună spectacolele.

Algoritmul problemei :
1. Sortarea crescătoare a spectacolelor dupa ora terminării lor;
2. Primul spectacol programat este cel care se termină cel mai devreme;
3. Alegem primul spectacol din cele ce urmează în șir după ultimul spectacol programat,
deci, acela care începe după ce s-a terminat ultimul spectacol programat;
4. Dacă nu s-a găsit un astfel de spectacol, algoritmul se încheie, asfel se programează
spectacolul găsit și algoritmul se reia de la pasul 3.

#include <stdio.h>
#include <conio.h>
int s[2][10], a[10], n, i, ora_sf; printf("- ora de inceput = ");
void sortare() scanf("%d", &s[0][i]);
{ printf("- ora de sfarsit = ");
int gata, aux; scanf("%d", &s[1][i]);
do a[i] = i;
{ }
gata = 1; sortare();
for (i=0; i<n-1; i++) ora_sf = s[1][a[0]];
{ printf("Primul spectacol este nr
if (s[1][a[i]] > s[1][a[i+1]]) %d\n", a[0]);
{ i = 1;
aux = a[i]; a[i] = a[i+1]; a[i+1] = aux; while (i<n)
gata = 0; {
} if (s[0][a[i]] >= ora_sf)
} {
} while (!gata); printf("Spectacolul urmator este nr %d\n",
} a[i]);
void main() ora_sf = s[1][a[i]];
{ }
printf("Nr. spectacole = "); i++;
scanf("%d", &n); }
for (i=0; i<n; i++) getch();
{ }
printf("Spectacolul %d\n", i);

7
Indrumător de laborator

La lansarea în execuție a programului pe ecran va apărea:

Figura.2.

8
Indrumător de laborator

Laborator 8.Tehnica Divide et Impera

Problema 1. Se dau trei tije simbolizate prin a, b și c. Pe tija a se găsesc n discuri de diametre
diferite așejate în ordinea descrescătoare a diametrelor privite de jos în sus. Se cere să se mute
discurile de pe tija a pe tija b, folosind ca tijă auxiliară c, respectând următoarele reguli:
1. la fiecare pas se mută un singur disc;
2. nu este permis să se așeje un disc cu diametrul mai mare peste un disc cu diametrul
mai mic.( Problema turnurilor din Hanoi)

Prezentarea algoritmului rezolvării


Fie trei tije verticale notate A,B,C. Pe tija A se găsesc așezate n discuri de diametre diferite ,în
ordinea crescătoare a diametrelor, privind de sus în jos. Ințial, tijele B și C sunt goale. Să se
afișeze toate mutările prin care discurile de pe tija A se mută pe tija B, în aceeași ordine
,folosind ca tijă de manevră C și respectând următoarele reguli:
-la fiecare pas se mută un singur disc;
-un disc se poate așeza numai peste un disc cu diametrul mai mare .

Rezolvarea acestei probleme se bazează pe următoarele considerente logice :


-dacă n=1 ,atunci mutarea este imediată AB(mut discul de pe A pe B);
-dacă n=2,atunci șirul mutărilor este : AC,AB,CB;
-dacă n>2 procedăm astfel :
-mut (n-1)discuri AC;
-mut un disc AB ;
-mut cele (n-1)discuri CB.
Observăm că problema inițială se descompune în trei subprobleme mai simple, similare
problemei inițiale: mut (n-1)discuri AC, mut ultimul disc pe B, mut cele (n-1)discuri C-->B.
Dimensiunile acestor subprobleme sunt : n-1,1,n-1.
Aceste subprobleme sunt independente, deoarece tijele ințial (pe care sunt dispuse discurile ),
tijele finale și tijele intermediare sunt diferite. Notăm H(n,A,B,C)= șirul mutărilor a n discuri de
pe A pe B, folosind C.
PENTRU
n=1 AB
n>1 H(n,A,B,C)= H(n-1,A,C,B),AB, H(n-1,C,B,A)

#include <stdio.h> }
#include <conio.h> else
{
int n; hanoi(n-1, a, c, b);
char a, b, c; printf("%c%c ", a, b);
void hanoi(int n, char a, char b, char c) hanoi(n-1, c, b, a);
{ }
if (n == 1) }
{
printf("%c%c ", a, b); void main()

1
Indrumător de laborator

{ c = 'c';
printf("Numarul de discuri : "); hanoi(n, a, b, c);
scanf("%d", &n); getch();
a = 'a'; }
b = 'b';

La lansarea în execuție a programului pe ecran va apărea:

Figura.1.

Problema 2. Se citesc n numere naturale strict pozitive. Să se calculeze c.m.m.d.c. al lor


utilizând metoda Divide et Impera. Să se afişeze rezultatul.

#include <stdio.h> int m,d1,d2;


#include <conio.h> if (p<q)
{
int a[30]; m=(p+q)/2;
d1=divimp(p,m,a);
int cmmdc(int x, int y) d2=divimp(m+1,q,a);
{ return cmmdc(d1,d2);
int r; }
while(y!=0) return a[p];
{ }
r=x%y;
x=y; void main()
y=r; {
} clrscr();
return x; int n;
} int i;
do
int divimp(int p, int q, int a[]) {
{ printf("n=");

2
Indrumător de laborator

scanf("%d",&n); scanf("%d",&a[i]);
} while (n>30 || n<=0); }
for(i=1; i<=n; i++) printf("cmmdc=%d\n",divimp(1,n,a));
{ getch();
printf("a[%d]=",i); }

Problema 3.Se dă un tablou de numere întregi și se cere să se determine maximul elementelor


sale folosind metoda Divide Et Impera

#include<stdio.h> {
#include<conio.h> x=maxim(i,(i+j)/2);
int a[10],n; y=maxim((i+j)/2+1,j);
int maxim(int i, int j) if (x>y) return x;
{ else return y;
int x,y; }
if (i==j) return a[i]; }
else
void main ()
{
int i;
printf("nr. de elemente = ");
scanf("%d",&n);
for (i=1;i<=n;i++)
{
printf("a[%d]=",i);scanf("%d",&a[i]);
}
printf("maximul = %d\n",maxim(1,n));
getch();
}

3
Indrumător de laborator

Laborator 9.Metoda Backtracking

Problema 1. Generarea permutărilor utilizând metoda Backtracking.

Permutări

Formula de recurență a permutărilor:

Se citeşte un număr natural n. Să se genereze toate permutările mulţimii{1,2,3…,n}.


Generarea permutărilor se va face ţinând cont că orice permutare va fi alcătuită din elemente
distincte ale mulţimii A. Din acest motiv, la generarea unei permutări, vom urmării ca numerele
să fie distincte.
Prezentăm algoritmul corespunzător cazului n=3.

- se încarcă în stivă pe nivelul unu, valoarea 1;


- încărcarea valorii unu pe nivelul al doilea nu este posibilă întrucât aceasă valoare se
găseşte şi pe nivelul unu al stivei;
- încărcarea valorii doi pe nivelul al doilea este posibilă, deoarece această valoare nu mai
este întâlnită;
- valoarea unu de pe nivelul trei se regăseşte pe nivelul unu;
- valoarea doi de pe nivelul trei se regăseşte pe nivelul doi;
- valoarea trei pe nivelul trei nu e întâlnită pe nivelurile anterioare, deci se încarcă în stivă;
întrucât nivelul trei este completat corect, tipărim prima soluţie: 1 2 3.
- algoritmul continuă până când stiva devine vidă.

1 2 3

1 2 2 2 2

1 1 1 1 1 1

1 2 3

1
Indrumător de laborator

3 3 3 3 1

1 1 1 1 2 2

1 2 3 1

1 1 1 2 3 3

2 2 2 2 2 2

#include <stdio.h> {
#include <conio.h> int k = 1;
int st[10], n; int as, ev;
void init(int st[], int k) { st[k] = 0; } init(st, k);
int am_succesor(int st[], int k) while (k > 0)
{ {
if (st[k] < n) do
{ {
st[k]++; if (as =
return 1; am_succesor(st, k))
} ev = valid(st,
return 0; k);
} } while (as == 1 && ev ==
int solutie(int k) { return k==n; } 0);
int valid(int st[], int k) if (as)
{ if (solutie(k))
int i; { tiparire(); }
for (i=1; i<k; i++) else
if (st[i] == st[k]) {
return 0; k++;
init(st, k);
return 1; }
} else
void tiparire() k--;
{ }
int i; }
for (i=1; i<=n; i++)
printf("%d ", st[i]); void main()
printf("\n"); {
} printf("n = "); scanf("%d", &n);
back();
void back() getch();

2
Indrumător de laborator

La lansarea în execuție a programului pe ecran va apărea:

Figura.1.

Problema 2. Fiind dată o tablă de șah n x n, se cer toate soluțiile de aranjare a n dame, astfel
încât să nu se afle două dame pe aceeași linie, coloană sau diagonală (damele să nu se atace
reciproc).( Problema celor n dame).

Presupunând că dispunem de o tablă de dimensiune 4x4, o soluţie ar fi următoarea:

Observăm că o damă trebuie sa fie plasată singură pe linie.Plasăm prima damă pe linia 1, coloana
1.
D

3
Indrumător de laborator

A doua damă nu poate fi aşezată decât în coloana a 3-a.

D
D

Observăm că a treia damă nu poate fi plasată în linia a 3-a.Încercam atunci plasarea celei de-a
doua dame în coloana a 4-a.

D
D

În acest caz, a treia damă nu poate fi plasată decât în coloana a doua.

D
D
D

În această situaţie dama a patra nu mai poate fi aşezată pe tabla de şah. Încercând să avansăm cu
dama a treia, observăm că nu este posibil să o plasăm nici în coloana a treia, nici în coloana a
patra, deci o vom scoate de pe tablă. Dama a doua nu mai poate avansa, deci şi ea este scoasă de
pe tablă. Avansăm cu prima damă în coloana a 2-a.

A doua damă nu poate fi aşezată decât în coloana a 4-a.

D
D

Dama a treia se aşează în prima coloană.

4
Indrumător de laborator

D
D
D

Acum este posibil să plasăm a patra damă în coloana a treia şi astfel am obţinut o soluţie a
problemei.

D
D
D

Algoritmul continuă în acest mod până când trebuie scoasă de pe tablă prima damă.
Pentru reprezentarea unei soluţii putem folosi un vector cu n componente (având în vedere că pe
fiecare linie se gaseşte o singură damă). De exemplu, pentru soluţia găsită avem vectorul st ce
poate fi asimilat unei stive.
Două dame se găsesc pe aceeaşi diagonală dacă şi nu mai dacă este îndeplinită condiţia:
|st(i) – st(j)|=|i-j| , (diferenţa , în modul, dintre linii şi coloane este aceeaşi).
În general, st(i)=k semnifică faptul că pe linia i dama ocupă poziţia k.

st(4) 3
st(3) 1
st(2) 4
st(1) 2

Întrucât două dame nu se pot găsi în aceeaşi coloană, rezultă că o soluţie este sub formă de
permutare. O primă idee ne conduce la generarea tuturor permutărilor şi la extragerea soluţiilor
pentru problemă. A proceda astfel, înseamnă că lucrăm conform strategiei backtracking. Aceasta
presupune ca imediat ce am găsit două dame care se atacă, să reluăm căutarea.Faţă de programul
de generare a permutărilor, programul de generare a tuturor soluţiilor problemei celor n dame are
o singură condiţie suplimentară în funcţia Valid; de aceea vom prezenta numai funcţia Valid in
continuare.

int Valid(int st[], int k) if(st[i]= =st[k] || abs(st[k]-st[i])=


{ =abs(k-i)) return 0;
for(int i=1;i<k;i++) return 1;
}

#include <stdio.h> {
#include <conio.h> st[k] = 0;
#include <math.h> }
int st[10], n; int am_succesor(int st[], int k)
void init(int st[], int k) {

5
Indrumător de laborator

if (st[k] < n) int k = 1;


{ int as, ev;
st[k]++; init(st, k);
return 1; while (k > 0)
} {
do
return 0; {
} if (as = am_succesor(st, k))
int solutie(int k) ev = valid(st, k);
{ } while (as == 1 && ev == 0);
return k==n; if (as)
} if (solutie(k))
int valid(int st[], int k) {
{ tiparire();
int i; }
for (i=1; i<k; i++) else
if (st[i] == st[k] || abs(st[k] - st[i]) == abs(k- {
i)) k++;
return 0; init(st, k);
return 1; }
} else
void tiparire() k--;
{ }
int i; }
for (i=1; i<=n; i++) void main()
printf("%d ", st[i]); {
printf("\n"); printf("n = "); scanf("%d", &n);
} back();
void back() getch();
{ }

La lansarea în execuție a programului pe ecran va apărea:

6
Indrumător de laborator

Figura.2.

Problema 3. Fiind dată o hartă cu n țări, se cer toate soluțiile de colorare a hărții, utilizând cel
mult 4 culori, astfel încât două țări cu frontiera comună să fie colorate diferit.
( Problema colorării hărților).

Fiind dată o hartă cu n ţări,se cere determinarea tuturor soluţiilor de colorare a hărţii, utilizând
cel mult 4 culori , astfel încât două ţări cu frontiera comună să fie colorate diferit.

Pentru exemplificare vom considera o hartă în care ţările sunt numerotate de la 1 la 5, având
forma următoare:

Numerotând cele 4 culori cu 1,2,3,4, o


posibilă soluţie a problemei ar fi
următoarea:
• Ţara 1 – culoarea 1
• Ţara 2 – culoarea 2
• Ţara 3 – culoarea 1
• Ţara 4 – culoarea 3
• Ţara 5 – culoarea 4

Harta va fi furnizată programului cu ajutorul unei


matrici A, unde elementele matricii sunt definite astfel :

7
Indrumător de laborator

A [i,j]=1, dacă ţara i se învecinează cu ţara j şi


A [i,j]=0, în caz contrar.

Facem precizarea că această matrice este simetrică.


Soluţia problemei va fi pusă sub formă de stivă st, ea având numărul de nivel egal cu cel al
ţărilor, iar st [k] reprezentând culoarea ataşată ţării k.Deci, dacă pe nivelul 3 al stivei se află
valoarea 4 înseamnă că ţara 3 are culoarea 4.
Pe fiecare nivel al stivei se va afla câte o culoare. Deci condiţia de a avea succesor se limitează la
faptul că st[k]< 4.
Condiţia de validare a succesorului (adică validarea culorii de introdus în stivă pe nivelul k) este
următoarea: dacă, culoarea respectivă (pe care dorim să o introducem) mai apare în stivă pe un
nivel inferior(i) ţările reprezentate pe cele două nivele trebuie să nu se învecineze, adică
A[i,k]=0.

#include <stdio.h> for (i=1; i<=n; i++)


#include <conio.h> printf("%d ", st[i]);
int st[10], a[10][10], n, nr; printf("\n");
void init(int st[], int k) }
{ void back()
st[k] = 0; {
} int k = 1;
int am_succesor(int st[], int k) int as, ev;
{ init(st, k);
if (st[k] < n) while (k > 0)
{ {
st[k]++; do
return 1; {
} if (as = am_succesor(st, k))
return 0; ev = valid(st, k);
} }
int solutie(int k) while (as == 1 && ev == 0);
{ if (as)
return k==n; if (solutie(k))
} {
int valid(int st[], int k) tiparire();
{ }
int i; else
for (i=1; i<k; i++) {
if (st[i] == st[k] && a[i][k] == 1) k++;
return 0; init(st, k);
return 1; }
} else
void tiparire() k--;
{ }
int i; }

8
Indrumător de laborator

void main() scanf("%d", &a[i][j]);


{ a[j][i] = a[i][j];
int i, j; }
printf("Numarul de tari = ");
scanf("%d", &n); back();
nr = 0; getch();
for (i=1; i<=n; i++) }
for (j=1; j<=i-1; j++)
{
printf("a[%d][%d] = ", i, j);

La lansarea în execuție a programului pe ecran va apărea:

Figura.3.

Problema 4 .Un comis-voiajor trebuie să viziteze un număr de n orașe. Inițial, acesta se află
într-unul dintre ele, notat cu 1. Comis-voiajorul dorește să nu treacă de două ori prin același
oraș, iar la întoarcere să revină în orașul 1. Cunoscând legăturile existente între orașe, se cere
să se tipărească toate drumurile posibile pe care le poate efectua comis-voiajorul.

9
Indrumător de laborator

(Problema comis voiajorului).

În figura următoare sunt simbolizate 6 oraşe, precum şi drumurile existente între ele.

Comis-voiajorul are următoarele posibilităţi de parcugere:


• 1,2,3,4,5,6,1;
• 1,2,5,4,3,6,1;
• 1,6,3,4,5,2,1;
• 1,6,5,4,3,2,1.

Legăturile existente între oraşe sunt date în matricea An ,n . Elementele matricei A pot fi 0 sau 1
(matricea este binară).

Se observă ca A(i,j)=A(j,i), oricare ar fi i si j aparţinând mulţimii {1,2,…,n} (matricea este


simetrică)

Pentru rezolvarea problemei folosim o stivă st. La baza stivei (nivelul 1 ) se încarcă numărul 1.
Prezentăm în ontinuare modul de rezolvare a problemei:

1 de la oraşul 1 la oraşul 2 există drum,deci se va urca în stivă;

1 oraşul 2 se mai găseşte în stivă, deci nu este acceptat;

2 de la oraşul 2 la oraşul 3 există drum;prin oraşul 3 nu s-a mai trecut,deci


1
10
Indrumător de laborator

oraşul 3 este acceptat;

Algoritmul continuă în acest mod până se ajunge din nou la nivelul 1, caz în care algoritmul se
încheie.
Un succesor, între 2 şi n, aflat pe nivelul k al stivei, este considerat valid dacă sunt îndeplinite
următoarele condiţii:
• nu s-a mai trecut prin oraşul simbolizat de succesor, deci acesta nu se regăseşte în stivă;
• există drum între oraşul aflat pe nivelul k-1 şi cel aflat pe nivelul k;
• dacă succesorul se găseşte pe nivelul n, să existe drum de la el la oraşul 1.

#include <stdio.h> void back()


#include <conio.h> {
int st[10], a[10][10], n; int k = 2;
void init(int st[], int k) { st[k] = 1; } int as, ev;
int solutie(int k) { return k==n; } st[1] = 1;
int am_succesor(int st[], int k) init(st, k);
{ while (k > 1)
if (st[k] < n) {
{ do
st[k]++; {
return 1; if (as = am_succesor(st, k))
} ev = valid(st, k);
return 0; }
} while (as == 1 && ev == 0);
int valid(int st[], int k) if (as)
{ if (solutie(k))
int i; { tiparire(); }
if (a[st[k-1]][st[k]] == 0) return 0; else
for (i=1; i<=k-1; i++) { k++;
if (st[i] == st[k]) init(st, k);
return 0; }
if ((k==n) && (a[1][st[k]] == 0)) else
return 0; k--;
return 1; }
} }
void main()
void tiparire() {
{ int i, j;
int i; printf("Nr noduri = "); scanf("%d", &n);
for (i=1; i<=n; i++) for (i=1; i<=n; i++)
printf("Nodul = %d\n", st[i]); {
printf("------------\n"); for (j=1; j<=i-1; j++)
} {
11
Indrumător de laborator

printf("a[%d][%d] = ", i, j);


scanf("%d", &a[i][j]); back();
a[j][i] = a[i][j]; getch();
} }
}

La lansarea în execuție a programului pe ecran va apărea:

Figura.4.

12
Indrumător de laborator

Laborator 10 & 11. Backtracking recursiv după schemă

Problema 1. Se citesc n și p numere naturale, n mai mare sau egal cu p. Se cere să se genereze
toate aranjamentele de n luate câte p.(Generarea recursivă a aranjamentelor)

Aranjamente

, sau

Formula de recurență a aranjamentelor:

#include <stdio.h> void tiparire()


#include <conio.h> {
int st[10], n, p; int i;
void init(int st[], int k) for (i=1; i<=p; i++)
{ printf("%d ", st[i]);
st[k] = 0; printf("\n");
} }
int am_succesor(int st[], int k) void back(int k)
{ {
if (st[k] < n) if (solutie(k))
{ {
st[k]++; tiparire();
return 1; }
} else
return 0; {
} init(st, k);
int valid(int st[], int k) while (am_succesor(st, k))
{ if (valid(st, k))
int i; back(k+1);
for (i=1; i<k; i++) }
if (st[i] == st[k]) }
return 0; void main()
return 1; {
} printf("n = "); scanf("%d", &n);
int solutie(int k) printf("p = "); scanf("%d", &p);
{ back(1);
return k == p+1; getch();
} }

1
Indrumător de laborator

La lansarea în execuție a programului pe ecran va apărea:

Figura.1.

Problema 2. Se citesc două numere naturale n și k (k <= n). Se consideră o mulțime cu


elementele {1, 2, ..., n} și se cere să se tipărească toate submulțimile sale care au k elemente,
(combinări de n luate câte k).( Generarea recursiva a combinarilor).

Combinări

, sau

Formula de recurență a combinărilor:

2
Indrumător de laborator

Se citesc două numere naturale n și p (p<=n). Se consideră o mulțime cu elemente{1,2,…,n} și


se cere să se tipărească toate submulțimile sale care au p elemente distinct (combinări de n luate
câte p)

Pentru k=1, trebuie să se tipărească toate submulțimile cu un singur element. Acestea sunt:
{1}, {2}…,{n}. Întrucât elementele unei submulțimi sunt distincte, le vom reține într-un singur
vector în ordine crescătoare.
Presupunem că am generat o submulțime cu p elemente (p<=k) a mulțimii A. Aceasta este
{i1,i2,…,ip}, i1<i2<…ip. Utilizând această submulțime cu p+1 elemente ale mulțimii A
(componenta (p+1) va lua valorile de la ( p+1) pana la n, pentru că, pentru a evita repetiția, o
soluție conține numere strict crescătoare):
• {i1,i2,…,ip,ip+1};
• {i1,i2,…,ip,ip+2};
• {…………………….};
• {i1,i2,…,ip,in}.

Pe nivelul k se va află o valoare mai mare decât pe nivelul ( k-1) și mai mică sau egală cu ( n-
p+k).

#include <stdio.h> return 1;


#include <conio.h> }
int st[10], n, k, p; int solutie(int k)
void init(int st[], int k) {
{ return k == p+1;
if (k > 1) }
st[k] = st[k-1]; void tiparire()
else {
st[k] = 0; int i;
} for (i=1; i<=p; i++)
int am_succesor(int st[], int k) printf("%d ", st[i]);
{
if (st[k] < n - p + k) printf("\n");
{ }
st[k]++; void back(int k)
return 1; {
} if (solutie(k))
return 0; {
} tiparire();
int valid(int st[], int k) }
{ else
int i; {
for (i=1; i<k; i++) init(st, k);
if (st[i] == st[k]) while (am_succesor(st, k))
return 0; back(k+1);
}

3
Indrumător de laborator

} printf("p = "); scanf("%d", &p);


back(1);
void main() getch();
{ }
printf("n = "); scanf("%d", &n);

La lansarea în execuție a programului pe ecran va apărea:

Figura.2.

Problema 3. Fiind dată o tablă de șah, de dimensiune n*n, se cer ca toate soluțiile de aranjare
a n dame astfel încât să nu se afle două dame pe aceeași linie, coloană sau diagonal (damele
să nu se atace reciproc).

Mecanismul de rezolvare este același ca în cazul standardizat (o soluție se reține într-un vector
numit t). Pentru o anumită componentă a vectorului se caută un succesor de la valoarea care se
găsește la un element dat până la valoarea maximă posibilă(n). În cazul în care se găsește un
succesor, se verifică dacă este valid, caz în care se apelează procedura pentru valoarea
următoare, astfel, se caută următorul succesor. În cazul în care acesta nu există se revine la
nivelul anterior. Initializarea se face la început (pentru tot vectorul) și la revenire. Tipărirea
soluției se realizează în momentul atingerii nivelului n+1.

#include<stdio.h> else
#include<conio.h> {
#include<math.h> for(i=st[k]+1;i<=n;i++)
{
void Tipar st[k]=i;
{ As=1;
for(int i=1;i<=n;i++) for(j=1;j<k;j++)
printf(“%d”,st[i]); if(st[j]==st[k]||abs(st[k])-
printf(“\n”); st[j]==(k-j))
} As=0;
void back(int k) if(As) back(k+1);
{ }
Int I,j,As; }
if(k==n+1) Tipar(); st[k]=0;

4
Indrumător de laborator

}
void main()
{
printf(“n=”);
scanf(“%d”,&n);
back(1);
getch();
}

5
Tehnici Avansate de Programare Îndrumător de laborator

Laborator 12. Metoda Alocării Dinamice

Problema 1.
Se consideră un triunghi de numere. Să se calculeze cea mai mare sumă a numerelor ce apar pe
drumurile ce pleacă din vârf și ajung la bază astfel:
- în fiecare drum succesorul unui număr se află pe rândul de mai jos,sub sau pe diagonală
la dreapta.
Se cere, de asemenea, să se determine care sunt numerele ce alcătuiesc suma maximă
determinată. ( Problema triunghiului ).

Exemplu:
Pentru triunghiul

3 5

6 3 4

5 6 1 4

Rezultatul va fi 17.

Rezolvare:
Se pot forma mai multe sume:

S1=2+3+6+5=16
S2=2+5+4+1=12
……………………….

Sk=2+3+6+6=17(care este și suma maximă).

Se observă că se pot forma 2 n − 1 astfel de sume. A le lua în considerare pe toate pentru a găsi
valoarea optimă (maximă) nu este eficient.

Fie un șir de n numere (care respectă condițiile problemei și care formează suma maximă). În
acest șir considerăm numărul care a fost preluat de pe linia i. Numerele între i+1 și n, formează o
sumă maximă în raport cu sumele care se pot forma începând cu numărul preluat de pe linia i.

În această situație se poate aplica programarea dinamică, metoda înainte.

1
Tehnici Avansate de Programare Îndrumător de laborator

Vom forma un triunghi, de la bază la vârf, cu sumele maxime care se pot forma cu fiecare
număr. Dacă memorăm triunghiul de numere într-o matrice T și calculăm sumele într-o matrice
C, vom avea relațiile următoare:

C[n,1]:=T[n,1];
C[n,2]:=T[n,2];
…………………
C[n,n]:=T[n,n];

Pentru linia i (i<n), cele i sume maxime se obțin astfel:

C[i,j]:=max{T[i,j]+C[i+1,j], T[i,j]+C[i+1,j+1]}, unde i€{1,2,………,n-1} și j€{1,…….i}.

Să rezolvăm problema propusă ca exemplu:

Linia 4 a matricii C va fi linia n a matricii T:

5 6 1 4;

Linia 3 se calculează astfel:

C[3,1]=max{6+5,6+6}=12;
C[3,2]=max{3+6,3+1}=9;
C[3,3]=max{4+1,4+4}=8;

Linia 2;

C[2,1]=max{3+12, 3+9}=15;
C[2,2]=max{5+9,5+8}=14;

Linia 1:

C[1,1]=max{2+15,2+14}=17.

Aceasta este și cea mai mare sumă care se poate forma.


Pentru a tipări numerele luate în calcul se folosește o matrice numită drum, în care se găsește
succesorul lui T[i,j].

Programul în C care implementează acest algoritm este următorul:

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

2
Tehnici Avansate de Programare Îndrumător de laborator

{
int a[20][20], t[20][20], c[20][20], drum[20] c[i][j] = t[i][j] + c[i+1][j+1];
[20], n, i, j; drum[i][j] = j + 1;
}
void main() else
{ {
printf("Nrumarul de linii ale triunghiului = c[i][j] = t[i][j] + c[i+1][j];
"); scanf("%d", &n);
for (i=1; i<=n; i++) drum[i][j] = j;
for (j=1; j<=i; j++) }
{ }
printf("a[%d][%d] = ", i, j); scanf("%d", }
&t[i][j]); printf("Suma maxima este = %d\n",
} c[1][1]);
for (j=1; j<=n; j++) i = j = 1;
c[n][j] = t[n][j]; while (i <= n)
for (i=n-1; i>=1; i--) {
{ printf("\t%d", t[i][j]);
for (j=1; j<=i; j++) j = drum[i][j];
{ i++;
if (c[i+1][j] < c[i+1][j+1]) }
printf("\n");
getch();
}

La lansarea în execuție a programului pe ecran va apărea:

Figura.1.

3
Tehnici Avansate de Programare Îndrumător de laborator

Problema 2.
Presupunem că avem de înmulțit două matrici : An , p cu B p ,m . În mod evident, rezultatul va fi o
matrice C n ,m . Se pune problema de a afla câte înmulțiri au fost făcute pentru a obține
matricea C. Prin înmulțirea liniei 1 cu coloana 1 se fac p înmulțiri, întrucât au p elemente.
Dar linia 1 se înmulțește cu toate cele m coloane, deci se fac m*p înmulțirii. În mod analog se
procedează pentru toate cele n linii ale matricii A, deci se fac n*p*m înmulțiri. Reținem acest
rezultat.( Inmultirea optima a unui sir de matrici )

Să considerăm produsul de matrici

A1 ∗ A2 ∗ ................. ∗ An ( A1 (d1, d 2 ), A2 (d 2 , d 3 ),........... An (d n , , d n + 1 )).

Se cunoaște că legea de compoziție produs de matrici nu este comutativă, în schimb este


asociativă. Să considerăm că avem de înmulțit patru matrici: A1 (10,1), A2 (1,10), A3 (10,1), A4 (1,10).
Pentru înmulțirea lui A1 cu A2 se fac 100 de înmulțiri și se obține o matrice cu 10 linii și 10
coloane. Prin înmulțirea acesteia cu A1 (10,1), A2 (1,10), A3 (10,1), A4 (1,10).
Pentru înmulțirea lui A1 cu A2 se fac 100 înmulțiri și se obține o matrice cu 10 linii și 10
coloane. Prin înmulțirea acesteia cu A3 se fac 100 de înmulțiri și se obține o matrice cu 10 linii și
o coloană. Dacă această matrice se înmulțește cu A4 se fac 100 de înmulțiri. În concluzie, dacă
acest produs se efectuează în ordine naturală, se fac 300 de înmulțiri.
Să efectuăm același produs în ordinea care rezultă din expresia A1 ∗ ( A2 ∗ A3 ) ∗ A4 . Efectuând
produsul A2 cu A3 se efectuează 10 înmulțiri și se obține o matrice cu o linie și o coloană. Dacă
această matrice se înmulțește cu A4 se mai fac 10 înmulțiri și se obține o matrice cu o linie și cu
10 coloane. Dacă o înmulțim pe aceasta cu prima, efectuăm 100 de înmulțiri, obținând rezultatul
final cu numai 120 de înmulțiri.
În concluzie, apare o problemă foarte interesantă și anume de a afla modul în care trebuie să se
înmulțească cele n matrici, astfel încât numărul de înmulțiri să fie minim. Pentru rezolvare se
aplică metoda înainte și apoi metoda înapoi a programării dinamice.
În vederea rezolvării problemei, reținem o matrice A cu n linii și m coloane. Elementul a(i,j), i, j,
reprezintă numărul minim de înmulțiri pentru efectuarea produsului Ai ∗ Ai + 1 * ....... A j . De
asemenea numărul liniilor și al coloanelor celor n matrici sunt reținute într-un vector DIM cu
n+1 componente. Pentru exemplul nostru DIM reține următoarele valori:10,1,10,1,10.

Pentru rezolvare se ține cont de următoarele relații existente între componentele matricii A:

• A(i,j)=0;
• A(i,i+1)=DIM(i)*DIM (i+1)*DIM(i+2);
• A(i,j)=min{A(i,k)+A(k+1,j)+DIM(k+1)*DIM(j+1)}

4
Tehnici Avansate de Programare Îndrumător de laborator

Justificarea acestor relații este următoarea:

1. Matricea nu se înmulțește cu ea însăși, deci se efectuează o înmulțireș


2. Liniile și coloanele matricii Ai se găsesc în vectorul DIM pe pozițiile i+1 și i+2;
3. Înmulțind matricile Ai ∗ Ai + 1 * ....... A j . se obține o matrice cu un număr de linii egal cu
acela al matricii Ai (DIM(i)) și cu un număr de coloane egal cu acela al matricii Ak
(DIM(j+1));
4. Înmulțind matricile Ak + 1 * Ak + 2 ∗ ......... * A j se obține o matrice cu un număr de linii egal
cu acela al matricii Ak + 1 (DIM(k+1)) și cu un număr de coloane egal cu acela al matricii
A j (DIM(j+1));
5. Prin înmulțirea celor două matrici se efectuează DIM(i)*DIM(k+1)*DIM(j+1) înmulțiri.

Relația sintetizează faptul că pentru a obține numărul de înmulțiri optim pentru produsul
Ai ∗ Ai + 1 * ....... A j . , se înmulțesc două matrici, una obținută ca produs optim între
Ai ∗ Ai + 1 * ....... A j . și cealaltă obținută ca produs optim între Ak + 1 * Ak + 2 ∗ ......... * A j , în ipoteza în
care cunoaștem numărul de înmulțiri necesar efectuării acestor două produse, oricare ar fi k,
cuprins între limitele date.
Această observație este o consecință direct a programării dinamice și anume că produsul efectuat
optim între matricile prezentate se reduce în ultimă instanță la a efectua un produs între două
matrici cu condiția ca acestea să fie calculate optim ( produsul lor să aibă un număr minim de
înmulțiri).

Programul C care rezolvă problema este :

#include <stdio.h> j = i + k;
#include <conio.h> a[i][j] = maxint;
#define maxint 32565
for (l=i; l<=j-1; l++)
long a[10][10]; {
int dim[10], i, n; m = a[i][l] + a[l+1][j] + dim[i] * dim[l+1] *
void costopt(int n, int dim[], long a[][10]) dim[j+1];
{
int k, i, j, l;
long m; if (a[i][j] > m)
{
for (i=1; i<=n; i++) a[i][j] = m;
a[i][i] = 0; a[j][i] = l;
}
for (k=1; k<=n-1; k++) }
for (i=1; i<=n; i++) }
{ printf("cost optim = %ld\n", a[1][n]);

5
Tehnici Avansate de Programare Îndrumător de laborator

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


{
void main() printf("dim[%d] = ", i);
{ scanf("%d", &dim[i]);
}
costopt(n, dim, a);
getch();
}
printf("n = "); scanf("%d", &n);

La lansarea în execuție a programului pe ecran va apărea:

Figura .2.

Problema 3.
Un student descarcă de pe Internet o aplicație. Aplicația a fost împărțită în mai multe pachete,
iar pachetele trebuie descărcate într-o ordine fixată.Se cunoaște timpul de descărcare pentru
fiecare pachet, precum și timpul necesare instalării fiecărui pachet.

Studentul vrea să testeze aplicația cât mai repede și vrea să înceapă instalarea înainte de a se
fi terminat descărcarea tuturor pachetelor. Se știe că odată ce începe instalarea nu mai poate
fi întreruptă(va da eroare la momentul în care este necesară instalarea unui pachet acesta nu
este complet descărcat).( Problema pachetelor ) .

Programul C care rezolvă problema este :

6
Tehnici Avansate de Programare Îndrumător de laborator

#include <stdio.h> void main()


#include <stdlib.h> {
#include <conio.h> int n, i;
#define NMAX 1000
#define max(x, y) ((x) > (y) ? (x) : (y))

FILE *f, *g;


int min[NMAX], D[NMAX], Inst[NMAX];

if ((f = fopen("C:\\fin.txt", "r")) == 0)


{ for (i=0; i<n; i++)
printf("Fisierul nu poate fi deschis."); fscanf(f, "%d%d", &Inst[i], &D[i]);
exit(1); fclose(f);
} min[n-1] = D[n-1];
if ((f = fopen("C:\\fout.txt", "w")) == 0) for (i=n-2; i>=0; i--)
{ min[i] = D[i] + max(0, min[i+1] - Inst[i]);
printf("Fisierul nu poate fi creat."); fprintf(g, "%d\n", min[0]);
exit(1); fclose(g);
} getch();
fscanf(f, "%d", &n); }

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