Sunteți pe pagina 1din 241

ELEMENTE DE BAZA ALE LIMBAJULUI C++

Structura programelor C++


Un program c++ este alcatuit din una sau mai multe functii.
In linii mari putem spune ca:
 Fiecarui algoritm ii corespunde o functie care codifica algoritmul in instructiuni c++.
 O functie poate intorce un rezultat sau nu.Daca aceasta nu intoarce nici un rezultat, spunem
ca tipul rezultatului este void.Tot asa, exista functii cu rezultat de tip intreg,real,etc.
 O functie poate contine declaratii de variabile, care pot fi utilizate doar de ea, nu si de
celelalte functii.
 Programul C++ este alcatuit din mai multe functii, din care una este radacina.Aceasta se
numeste main.
Sa vedem cum arata cel mai simplu program c++:
Void main()
{
}
Programul de mai sus este corect.Cu toate acestea el nu face nici o operatie.

Descrirea limbajului cu ajutorul diagramelor de sintaxa

Sintaxa limbajului este data de totalitatea regulilor de scriere corecta.Dar un program bun din punct de
vedere sntactic nu este automat un program bun.Corectitudinea sintactica este numai o cerinta a
programelor, tot asa cum pentru a fi campion mondial la alergari este necesar sa nu ai picioarele
amputate.Cel mai greu este ca programul sa execute intocmai ce si-a propus cel ce l-a realizat.
Prin semantica unui limbaj se intelege semnificatia constructiilor sintactice corecte.
Sintaxa este formalizta perfect din punct de vedere matematic dar nu acelasi lucru se intampla si cu
semantica.Sintaxa poate fi descrisa cu ajutorul diagramelor de sintaxa sau a notatiei BNF.
In esenta, o diagrama de sintaxa este constituita din urmatoarele simboluri grafice:
 Cercuri—incadreaza anumite simboluri speciale;
 Elipse—incadreaza cuvinte rezervate;
 Dreptunghiuri—incadreaza elemente definite prin alte diagrame de sintaxa;
 Sageti—indica sensul de parcurgere al diagramei.
Orice drum de la intrare la iesirea din diagrama reprezinta o constructie sintactica corecta.

VOCABULARUL LIMBAJULUI

Vocabularul oricarui limbaj este format din:


 Setul de caractere;
 Identificatori;
 Separatori;
 Comentarii.
Setul de caractere reprezinta ansamblul de caractere cu ajutorul carora se poate realiza un program C+
+.Acesta este alcatuit din:
→litere mari si mici ale alfabetului englez;
→cifrele sistemului de numeratie in baza 10;
→caractere speciale.
Prin identificatori se intelege o succesiune de litere, cifre sau caracterul special ”_” din care prima nu
trebuie sa fie cifra.Cu ajutorul identificatorilor se asociaza nume constantelor, variabilelor,
procedurilor, etc.

CITIRI, SCRIERI

Sa analizam programul de mai jos, care citeste un numar intreg si il tipareste:


#include<iostream.h>
void main()
{
int a;
cin>>a;
cout<<a;
}
In cazul in care introducem numarul 10, calculatorul va tipari 10, daca introducem 16, calculatorul va
tipari 16, s.a.m.d.

TIPURI DE DATE, TIPURI STANDARD


Prin tip de date se intelege:
 O multime de valori;
 O regula de codificare a lor;
 O multime de operatii definite pe multimea valorilor.
Spunem ca variabilele au un tip standard daca acesta este cunoscut de catre limbaj fara a fi definit in
cadrul programului.Limbajul C++ contine urmatoarele tipuri standard:
1. tipuri intregi;
2. tipuri reale.

TIPURI INTREGI

Tipurile intregi sunt:


 unsigned char→caracter fara semn, ocupa 8 biti si ia valori intre 0 si 255;
 char→caracter, ocupa 8 biti si ia valori intre -128 si 127;
 unsigned int→intreg fara semn,ocupa 16 biti si ia valori intre 0 si 65535;
 short int→intreg scurt, ocupa 16 biti si ia valori intre -32768 si 32767;
 int→intreg, ocupa de regula 16 biti si ia valori intre -32768 si 32767;
 unsigned long→intreg lung fara semn, ocupa 32 de biti si ia valori intre 0 si 4.294.967.295;
 long→intreg lung cu semn, ocupa 32 de biti si ia valori intre -2.147.483.648 si 2.147.483.647.
Variabilele de tip intreg folosesc pentru memorarea datelor codul complementar.Caracterele se
memoreaza prin codul ASCII.

TIPURI REALE

Variabilele de tip real retin numere cu zecimale.Tipurile reale sunt:


 float→ocupa 32 de biti si ia valori intre 3.4x10-38 si 3.4x1038;
 double→ocupa 64 de biti si ia valori intre 1.7x10-308 si 1.7x10308;
 long double→ocupa 80 de biti si ia valori intre 3.4x10-4932 si 1.1x104932.
pentru memorare datelor se foloseste reprezentarea in virgula mobila.

CONSTANTE
Exista mai multe tipuri de constante:
 zecimale;
 octale;
 hexazecimale;

EXPRESII

Se numeste expresie o succesiune de operatori si operanzi legati intre ei, dupa reguli specifice
limbajului, in scopul efectuarii unor operatii.Operanzii pot fi: constante, variabile, functii.
Esential este sa intelegem modul in care se evaluaza o expresie.Pentru aceasta prezentam cateva notiuni
fundamentale.
 Prioritatea operatorilor.Cu aceasta notiune suntem obisnuiti.Ea indica ordinea in care se
efectueaza operatiile.In c++ avem 16 niveluri de prioritate.
 Asociativitatea operatorilor.Notiunea este noua.Asociativitatea este de doua feluri: de la
stanga la dreapta si de la dreapta la stanga.De la inceput, precizam faptul ca operatorii cu
aceeasi prioritate au aceeasi asociativitate.
 Regula conversiilor implicite.Fiind dat un operator binar(care leaga doi operanzi din care unul
este de un tip iar altul de alt tip) se cere sa se pecizeze tipul rezultatului .Tipul acestuia este dat
de regula conversiilor implicite, regula prezentata in cadrul acestui capitol.

OPERATORI C++
Operatorii constituie unul din conceptele cele mai importante si mai interesante care stau la baza unui
limbaj de programare. Limbajul C este vestit pentru marea varietate de operatori pe care-I pune la
dispozitia programatorului rezultand si o diversitate de expresii ce se pot forma pe baza acestora. Dupa
cum se stie, o expresie este formata din variabile, constante, functii si operatori. In acest paragraf voi
aminti cateva din categoriile de operatori ai limbajului C:
Operatorii aritmetici:
+ adunare, - scadere, * produs, / impartire, % - restul impartirii intregi
++, -- incrementare si decrementare
Operatori relationali:
<,>
= = egal
                   <= mai mic sau egal
                          >= mai mare sau egal
                           != diferit
Operatori logici:
&& - si logic
|| -sau logic
! - negatie logica
Operatorii logici si relationali sunt implicati in formarea expresiilor cu valoare logica. Spre exemplu:
a<2, 7/5+2<s-1, (X>-3) &&(x<3), !(a==b)
sunt expresii cu valoare logica.
Operatori de atribuire.
In sectiunea precedenta am amintit, vorbind despre reprezentarea algoritmilor prin scheme logice, de
operatia de atribuire. In limbajul C se considera ca atribuirea este un operator. Atribuirea are
urmatoarea sintaxa:
variabila = expresie;
Modul de functionare este urmatorul:
se evalueaza expresia;
valoarea obtinuta este stocata in zona de memorie a variabilei, realizindu-se eventuale conversii de tip.
valoarea rezultata in urma atribuirii este valoarea atribuita variabilei.
OBS: Atribuirea fiind un operator, se considera ca operanzii sunt variabila din partea stanga, respectiv
expresia din partea dreapta. Orice expresie trebuie sa aiba, in urma evaluarii sale, o valoare (un
rezultat). Valoarea expresiei de atribuire este valoarea obtinuta prin evaluarea expresiei din partea
dreapta a atribuirii.
Daca valoarea obtinuta prin evaluarea expresiei din dreapta atribuirii este de alt tip decat tipul variabilei
din stanga atunci se incearca conversia tipului valorii la tipul variabilei. Nu intotdeauna conversia este
posibila, caz in care se va afisa un mesaj de eroare in urma compilarii.
Exemplul 1 : Fie urmatoarea secventa de program:
void main(void)
{
int a;
float c;
a=3./2.+9./4.;
cout<<”a=”<<a<<”\n”;
c=3./2.+9./4.;
cout<<”c=”<<c<<”\n”
/* aceeasi expresie are valori diferite, in functie de tipul variabilei
din stanga atribuirii */
}
 
Obs: Executand programul de mai sus se vor obtine valori diferite pentru variabilele a si c, desi
expresia care apare in dreapta ambelor atribuiri este aceeasi. Pentru a se obtine valoarea 3, iar pentru c
se obtine 3,75. Explicatia sta in modul in care se realizeaza conversiile de tip in cadrul atribuirilor.
Valoarea expresiei 3./2.+9./4. este 3,75 dar variabilei a i se va atribui partea intreaga a acestei valori,
adica 3. Variabila c este de tip float (numar real), deci nu va mai fi nevoie de conversia rezultatului
evaluarii expresiei la tipul variabilei.
Exemplul 2:
void main(void)
{
float a;
a=5/2;
cout<<a;
}
OBS:   Executand acest program se va afisa valoarea 2 si nu valoarea 2,5 asa cum ar fi fost de asteptat.
Explicatia este urmatoarea: operatorul / realizeaza, atunci cand operenzii sunt intregi, impartirea
intreaga, deci vom obtine catul impartirii intregi dintre 5 si 2. Exista doua modalitati de rezolvare a
problemei: fie inlocuim expresia cu 5./2. (5.=5.0) fie utilizam operatorul de conversie explicita ( ).
Acest nou operator ( numit cast), se utilizeaza astfel:
( tip_de_date) expresie;
Semnificatia este urmatoarea: Se cere convertirea valorii rezultate din evaluarea expresiei la o valoare
de tipul specificat intre paranteze. In exemplul de mai sus vom inlocui atribuirea cu:
a=(float) 5/2;
(rezultatul expresiei va fi de tip float, deci variabilei a i se va atribui valoarea 2.5).
Exercitiu: Fie urmatorul program, care calculeaza media aritmetica a trei numere a,b,c , primele doua
introduse de la tastatura.:
void main(void)
{
int a,b,c;
float s;
cin>>a;
cin>>b;
c=a/2+b/3;
s=(a+b+c)/3;
cout<<”Media aritmetica este=”<<s;
}
Executati acest program si identificati greselile. Corectati greselile gasite.
 
operatori de atribuire compusa: +=, -=, *=, /=, %=
Operatorii de atribuire compusa au fost introdusi pentru a fi utilizati in locul atribuirilor de tipul:
v=v+expresie;
v=v-expresie;
v=v*expresie; e.t.c
unde:              v-variabila
           expresie-orice expresie corecta in limbajul C
 
In locul acestor expresii se vor folosi:
v+=expresie;
v-=expresie; e.t.c
Folosirea operatorilor de atribuire compusa sporeste lizibilitatea programului si viteza de executie .
Daca atribuirea este de forma:
v=v+1;
sau
v=v-1;
se pot utiliza operatorii de incrementare si decrementare ++ si --. Expresiile de mai sus se vor putea
scrie:
v++;
respectiv
v--;
Exemplul 3: Se da urmatorul program:
void main(void)
{
int a,b,i=3;
a=++i; //preincrementare a lui i
cout<<a;
b=a--; //postdecrementare
cout<<”a=”<<a<<”\n”;
cout<<”b=”<<b<<”\n”;
a+=++b; //atribuire compusa
cout<<”a=”<<a<<”\n”;
cout<<”b=”<<b<<”\n”;
}
 
Obs: Cand apar expresii de tipul :
++variabila
sau
--variabila;
spunem ca se realizeaza o preincrementare, respectiv predecrementare. In cazul preincrementarii,
variabila va primi ca valoare 1+valoarea initiala. Daca preincrementarea apare intr-o expresie se
realizeaza intai aceasta operatie si abia apoi celelalte operatii care mai apar in expresie
(preincrementarea are prioritate mai mare). In atribuirea a=++I se realizeaza intai preincrementarea, in
urma careia i va avea valoarea 4, si abia apoi se realizeaza atribuirea (a va avea valoarea 4).
In expresia b=a—se realizeaza o postdecrementare, adica se modifica valoarea variabilei a doar dupa ce
s-au efectuat celelalte operatii din expresie. In cazul nostru, b va promi valoarea lui a (4, in momentul
acela) si abia apoi se va micsora valoarea lui a cu o unitate.
Exercitiu: Executati programul de mai sus si urmariti rezultatele care se obtin.
Mai exista operatori de atribuire compusa, folosindu-se operatorii logici ce actioneaza la nivel de bit,
dar despre acestia vom vorbi intr-o sectiune viitoare. Tot atunci vom discuta si despre atribuirea
multipla.

Instructiuni iterative
 
Toate problemele prezentate pana acum au putut fi rezolvate folosind numai functii de citire/scriere,
atribuiri si instructiunea de decizie (IF). Cele mai multe dintre probleme vor necesita structuri de date
mai complexe precum si folosirea unor noi instructiuni, care sa permita repetarea de un numar oarecare
de ori a unor parti din algoritm.
Sa luam ca exemplu algoritmul de calcul al sumei a 2 numere introduse de la tastatura. El consta in
citirea valorilor pentru cele doua numere si afisarea sumei acestora. Nu era nevoie decat de doua
variabile, cate una pentru fiecare numar. Acest exemplu este doar unul didactic, in practica
neintalnindu-se prea des cazuri in care sa fie nevoie de a suma doua numere. Generalizarea problemei
pentru n numere va modifica substantial algoritmul nostru. Nu vom putea folosi cate o variabila pentru
fiecare numar introdus deoarece nu cunoastem exact cate numere avem (n este introdus de utilizatorul
programului, altfel algoritmul nu ar mai fi general). Chiar daca s-ar sti ca avem, sa zicem, 2500 de
numere ar fi cam dificil sa utilizam 2500 de variabile distincte.
Problema noastra se poate rezolva simplu daca utilizam o instructiune iterativa (ciclica), care sa ne
permita repetarea de n ori a urmatoarei secvente de operatii:
citim o valoare folosind variabila “a”;
adunam valoarea citita la rezultatul partial, memorat in variabila “s”;
Instructiunile ce descriu aceste prelucrari ciclice sunt compuse din doua parti:
corpul ciclului, format din prelucrarile ce se doresc a fi realizate de mai multe ori;
conditia, pe baza careia se stabileste daca se vor mai executa prelucrarile din ciclu (este obligatorie
executarea prelucrarilor din corpul instructiunii de un numar finit de ori);
Corpul ciclului sau conditia trebuie sa contina acele operatii care, dupa efectuarea de un numar de ori a
corpului instructiunii, sa determine schimbarea valorii de adevar a conditiei, permitand incheierea
executarii instructiunii ciclice si trecerea la urmatoarele instructiuni ale algoritmului. In cazul in care
este neglijat acest aspect, programul se va executa la infinit.
Conditia terbuie sa fie o expresie cu valoare logica. Reamintesc faptul ca in limbajul C++ nu exista un
tip de date logic, dar se utilizeaza conventia ca orice valoare nenula sa fie considerata ca adevar iar
valoarea zero ca valoarea fals. In consecinta, orice expresie cu valoare de tip intreg va putea fi utilizata
drept conditie intr-o instructiune iterativa.
In finalul paragrafului o chestiune de terminologie: o executie a corpului instructiunii poarta numele se
iteratie.
Instructiunile iterative pot fi clasificate, in functie de momentul evaluarii conditiei, astfel:
cicluri cu test initial (While si For)-evaluarea conditiei se face inaintea fiecarei iteratii;
cicluri cu test final (Do..While)- evaluarea conditiei se face la sfarsitul fiecarei iteratii;
2. Instructiunea While
Instructiunea While (“atat timp cat”) este o instructiune iterativa cu test initial si are urmatoarea
sintaxa:
while ( <conditie>)  instructiune;
unde:
<conditie> -orice expresie cu valoare intreaga;
instructiune- orice instructiune valida a limbajului;
Mod de functionare:
daca expresia este adevarata se executa prelucrarile din ciclu;
altfel, se trece la urmatoarea instructiune de dupa while;
Cu alte cuvinte, prelucrarile din ciclu se executa atat timp cat conditia este adevarata. Daca cexpresia
este falsa de la inceput corpul ciclului nu se va executa deloc.
Problema propusa in paragraful precedent se poate rezolva astfel:
Exemplul 1 Suma a n numere introduse de utilizator
#include <iostream.h>
void main()
{
float a;
int i,n;
float suma=0;
i=1;
cout<<"\nNumarul de elemente=";
cin>>n;
while(i<=n)
{
cout<<"Elementul "<<i<<"este: ";
cin>>a;
suma=suma+a; //se mai poate scrie suma+=a
i++;
}
cout<<"Suma este= "<<suma;
}
Exemplul 2: Suma primelor n numere naturale.
 
#include <iostream.h>
void main()
{
int n;
int i;
int suma=0;
cout<<"n=";
cin>>n;
i=1;
while(i<=n)
{
suma=suma+i;
i++;
}
cout<<"Suma este: "<<suma;
}
 
Observatie: Se poate utiliza o scriere mai compacta, care elimina si necesitatea utilizarii variabilei i:
while (n--) suma+=n;
Modul de executie este urmatorul:
valoarea conditiei este valoarea variabilei n, valoare care scade cu o unitate dupa fiecare iteratie
(postdecrementare)
conditia devine falsa atunci cand valoarea lui n devine zero;
3. Instructiunea do..while
 
Instructiunea do..while este o instructiune iterativa cu test final si are urmatoarea sintaxa:
                                                                                    do
instructiune;
     while (<conditie>)
Mod de functionare:
se executa corpul instructiunii;
se evalueaza conditia: daca aceasta este adevarata se reia executia, altfel se trece la urmatoarea
instructiune din program;
Deosebirea esentiala fata de instructiunea while este aceea ca expresia se evalueaza dupa iteratie.
In cazul in care conditia este falsa de la inceput, corpul instructiunii se executa o singura data.
Utilizarea instructiunii do..while este mai putin frecventa (se foloseste pentru acele prelucrari care
trebuiesc executate cel putin o data).
Daca rescriem exemplul 1 utilizand do..while, obtinem:
………………………….
do
{
cin>>a;
suma+=a;
i++;
} while(i<=n);
4. Instructiunea for
 
Una dintre cele mai puternice instructiuni iterative ale limbajului C (si nu numai) este instructiunea for,
care are urmatoarea sintaxa:
for (expresie1; expresie2; expresie3) [instructiune];
unde:
expresie1, expresie2, expresie3 -expresii valide in C++;
instructiune - orice instructiune a limbajului C++;
Parantezele patrate semnifica faptul ca instructiunea este optionala;
Mod de executie:
expresiile au urmatoarea semnificatie generala:
expresie1 - expresie de initializare;
expresie2 - conditia de continuare a executiei ciclului;
expresie3 - expresie de actualizare;
atat timp cat expresie2 este adevarata se executa corpul ciclului;
de fiecare data se evalueaza expresia de actualizare, care are rolul esential de a determina ca, dupa un
numar de iteratii, expresie2 sa devina falsa;
expresie1 se evalueaza o singura data;
Exemplul 3: Reluam exemplul 1 folosind instructiunea for:
#include <iostream.h>
void main()
{
int i,n;
float a, suma=0;
cin>>n;
for(i=1;i<=n;i++)
{
cin>>a;
suma+=a;
}
cout<<”suma =”<<suma;
}
Observatii:
Variabila i este utilizata pe post de “contor” al instructiunii for, numarand la a cata iteratie s-a ajuns.
Executia instructiunii for se incheie atunci cand numarul de iteratii devine egal cu n. Initializarea lui i
cu 1 se realizeaza o singura data, la inceput; i<=n este conditia de continuare a executiei; i++ se
efectueaza dupa fiecare executie a ciclului (postincrementare).
Aceasta forma a instructiunii for este cea mai utilizata. Un alt mod de a descrie executia acesteia este
urmatorul: pentru i de la 1 la n se executa corpul instructiunii.
Nu este obligatoriu ca valoarea lui i sa se mareasca de fiecare data cu o unitate.
Nu este obligatorie utilizarea tuturor celor trei expresii din for, dar este obligatorie scrierea
separatorului “;” . Exemplul de mai sus se poate rescrie astfel:
……………………………
for( ;n--; )
{
cin>>a;
suma+=a;
}

Instructiunile limbajului C++


1.Instructiunea expresie

In C++ exista instructiunea expresie si este de forma:


expresie;
Se observa ca instructiunea expresie se termina cu “ ; ”.Daca expresia este vida, obtinem asa-
numita instructiune vida.
Programul de mai jos este corect!
main ( )
{
;
;;
}
Instructiunea expresie este des utilizata in efectuarea atribuirilor.

Exemplul 1. Programul urmator interschimba continutul a doua variabile care au fost citite.La
sfarsit, se afiseaza noul continut al variabilelor.
#include <iostream.h>
main ( )
{
int a,b,c;
cout<<”a=”; cin>>a;
cout<<”b=”; cin>>b;
c=a; a=b; b=c;
cout<<”a=”<<a<<endl;
cout<<”b=”<<b<<endl;
}

Exemplul 2. Se citesc doua valori intregi a si b.Se cere sa se afiseze media lor aritmetica.
#include <iostream.h>
main ( )
{
int a,b;
float medie;
cout<<”a=”; cin>>a;
cout<<”b=”; cin>>b;
medie=float (a+b)/2;
cout<<”media este “<<medie;
}

Exemplul 3. Se citesc 3 numere naturale.Se cere sa se afiseze primul numar, suma dintre
primul si al doilea, suma primelor 3 numere.
Exemplu : daca se citeste 2,5 si 7, se va tipari 2, 7, 14.
#include <iostream.h>
main ( )
{
int s=0,nr;
cout<<”dati numarul “; cin>>nr;
s+=nr;
cout<<s<<endl;
cout<<”dati numarul “; cin>>nr;
s+=nr;
cout<<s<<endl;
cout<<”dati numarul “; cin>>nr;
s+=nr;
cout<<s<<endl;
}
2.Instructiunea IF

Ca si in pseudocod, exista doua forme ale instructiunii IF:


Forma 1.
if (expresie) instructiune1 else instructiune2
Forma 2.
if (expresie) instructiune

Exemplu 1. Programul care urmeaza calculeaza maximul dintre doua numere citite este:
#include <iostream.h>
main ( )
{
int a,b,max;
cout<<”a=”; cin>>a;
cout<<”b=”; cin>>b;
if (a>b) max=a;
else max=b;
cout<<”maximul este”<<max;
}
Obs.: Se pune “ ; “ inainte de ELSE.
Exemplul 2. Se citesc trei variabile reale a, b si c.Sa se calculeze
a+b, c>0
e= a*b, c=0
a-b, c<0
#include <iostream.h>
main ( )
{
double a,b,c,e;
cout<<”a=”; cin>>a;
cout<<”b=”; cin>>b;
cout<<”c=”; cin>>c;
if (c>0) e=a+b;
else
if (c==0) e=a*b;
else e=a-b;
cout<<e;
}

Exemplul 3. Se citeste o valoare intreaga.In cazul in care aceasta este para (se imparte exact la
2) se va afisa mesajul “am citit un numar par”.Altfel, programul nu va da mesaj.
#include <iostream.h>
main ( )
{
int nr;
cin>>nr;
if (nr%2==0) cout<<”am citit valoare para”;
}

3.Instructiunea compusa

Pentru a putea scrie mai multe instructiuni care sa fie interpretate de compilator ca una
singura se foloseste instructiunea compusa.Aceasta are forma urmatoare:

{
i1;
i2;
:
:
In ;
}
Exemplul 1. Sa se scrie un program care rezolva ecuatia algebrica de gradul 1 cu o
necunoscuta, cu coeficienti reali (a*x+b=0).Modul de rezolvare a acestei ecuatii a fost discutat pe larg
atunci cand am exemplificat elaborarea algoritmilor prin utilizarea schemelor logice si a limbajului de
tip pseudocod.Aici prezentam programul C++ care rezolva aceasta problema.Se observa folosirea unei
instructiuni compuse la calculul radacinii si tiparirea ei.
#include <iostream.h>
main ( )
{
float a,b,x;
cout<<”a=”; cin>>a;
cout<<”b=”; cin>>b;
if (a)
{
x= -b/a;
cout<<x;
}
else
if (b==0) cout<<”infinitate de solutii”;
else cout<<”nu are solutie”;
}

4.Instructiunea switch

Instructiunea switch are forma generala:


switch (expresie)
{
case exp1:secventa instructiuni1;break;
case exp 2:secventa instructiuni2;break;
……………………..
case expn:secventa instructiunin;break;
[default :secventa instructiunin+1];
}
Exemplu: Programul care urmeaza probeaza instructiunea switch.
#include <iostream.h>
main ( )
{
int i;
cin<<i;
switch(i);
{
case 1 : cout<<”am citit 1”; break;
case 2 : cout<<”am citit 2”; break;
default : cout<<”am citit altceva”;
}
}

5.Instructiunea while

Forma generala a acestei instructiuni este:


while (expresie) instructiune

Exemplul 1. Se citeste n, numar natural.Sa se calculeze suma cifrelor sale (pentru n=213, se va
tipari 6).
#include <iostream.h>
main ( )
{
int n,s=0;
cout<<”n=”; cin>>n;
while (n)
{
s=s+n%10;
n=n/10;
}
cout<<s;
}

Exemplul 2. Se citeste n, numar natural.Sa se afiseze numarul obtinut prin inversarea cifrelor
sale (pentru n=412,se va tipari214).

#include <iostream.h>
main ( )
{
int n,ninv=0;
cout<<”n=”; cin>>n;
while (n)
{
ninv=ninv*10+n%10;
n=n/10;
}
cout<<”numarul inversat ”<<ninv;
}
In programul de mai jos s-au folosit mai multe expresii separate prin virgula:
#include <iostream.h>
main ( )
{
int n,ninv=0;
cout<<”n=”; cin>>n;
while (ninv=ninv*10+n%10,n/=10);
cout<<ninv;
}
Programul mai poate fi scris si asa:
#include <iostream.h>
main ( )
{
int n,ninv=0;
cout<<”n=”; cin>>n;
while (ninv*=10,ninv+=n%10,n/=10);
cout<<ninv;
}

6.Instructiunea DO WHILE

Aceasta instructiune este asemanatoare cu structura REPEAT UNTIL.Forma ei este


urmatoarea:
do
instructiune
while(expresie)
Exemplul 1. Se citeste un numar natural n, mai mare sau egal cu 1.Sa se calculeze suma
primelor n numere naturale.
#include <iostream.h>
main ( )
{
int n,s=0,i=1;
cout<<”n=”; cin>>n;
do
{ s=s+i;
i:=i+1;
} while (i<=n);
cout<<s;
}
Programul se poate scrie si asa:
#include <iostream.h>
main ( )
{
int n,s=0,i=1;
cout<<”n=”; cin>>n;
do s+=i++; while (i<=n);
cout<<s;
}
Exemplul 2. Se citeste n, numar natural.Sa se descompuna in factori primi.
#include <iostream.h>
main ( )
{
int n,i=2,fm;
cout<<”n=”; cin>>n;
do
{
fm=0;
while (n%i==0)
{
fm++; / / sau fm=fm+1;
n/=i; / / sau n=n/i;
}
if (fm) cout<<i<<”la puterea “<<fm<<endl;
i++;
} while (n!=1);
}

7.Instructiunea FOR

Instructiunea for are forma generala:


for (expresieinitializare;expresietest;expresieincrementare) instructiune
 Expresieinitializare se foloseste, de regula, pentru initializarea variabilei de ciclare.Este de remarcat
faptul ca in cadrul acestei expresii este posibil chiar sa declaram variabila de ciclare (cu valoare
initiala).
 Expresietest se foloseste pentru a testa daca se executa instructiunea subordonata – daca expresia
produce la evaluare o valoare diferita de 0, instructiunea subordonata for se executa.
 Expresieincrementare se foloseste pentru incrementarea variabilei de ciclare.

Obs.: Toate expresiile pot fi vide.In concluzie, expresiile de mai sus au rolul precizat in mod normal –
dar nu obligatoriu si nici restrictiv.De exemplu, daca expresietest este vida, se executa un ciclu infinit :
main ( )
{
for(; ;);
}
Exemplul 1. Programul urmator listeaza numerele 5, 4, 3, 2, 1.
#include <iostream.h>
main ( )
{
int i;
for (i=5;i>=1;i--) cout<<i<<” “;
}
Exemplul 2. Sa se listeze alfabetul in ordine inversa.
#include <iostream.h>
main ( )
{
char car;
for (car=’z’;car<=’a’;car--) cout<<car<<endl;
}

Exemplul 3. Se citeste n (numar natural).Se cere sa se efectueze suma primelor n numere


naturale.Exemplu: n=3, s=1+2+3=6.
#include<iostream.h>
main( )
{
int i,n,s=0;
cout<<”n=”; cin>>n;
for (i=1;i<=n;i++) s+=i; / / sau s=s+i;
cout<<”suma primelor n numere naturale este “<<s;
}
Programul se mai poate scrie si asa :
#include<iostream.h>
main( )
{
int i,n,s=0;
cout<<”n=”; cin>>n;
for (i=1;i<=n;s+=i++);
cout<<”suma primelor n numere naturale este “<<s;
}
Exemplul 4. Sa se calculeze suma: s=0,1+0,2+…+0,9.
#include<iostream.h>
main( )
{
int i;
float s;
for (i=1;i<=9;i++) s+=(float)i/10;
cout<<s;
}
Avand in vedere ca variabila de ciclare poate sa fie reala, putem scrie si asa:
#include<iostream.h>
main( )
{
double s=0,i;
for (i=0;i<=0.9;i+=0.1) s=s+i; / / sau s+=i;
cout<<s;
}
Exemplul 5. Se citeste n un numar natural.Sa se calculeze suma S=1+1*2+1*2*3+…+1*2*…
*n.

#include<iostream.h>
main( )
{
int i,s=0,p=1,n;
cout<<”n=”; cin>>n;
for (i=1;i<=n;i++)
{
p*=i; / / p=p*i;
s+=p; / / s=s+p;
}
cout<<s;
}
Exemplul 6. Se citesc n numere intregi.Se cere sa se afiseze cel mai mare numar
citit.Exemplu: daca avem n=4, iar numerele sunt –7, 9, 2, 3, se va afisa 9.
#include<iostream.h>
main( )
{
int i,max,n,nr;
cout<<”n=”; cin>>n;
cout<<”nr=”; cin>>nr;
max=nr;
for (i=2;i<=n;i++)
{
cout<<”nr=”; cin>>nr;
if (nr>max) max=nr;
}
cout<<”maximul este “<<max;
}
Exemplul 7. Se citeste un numar natural.Sa se afiseze numarul obtinut prin inversarea cifrelor
sale.
#include <iostream.h>
main ( )
{
int i,s,n,ninv;
cout<<”n=”; cin>>n;
for (ninv=0;n>0;)
{
ninv=ninv*10+n%10;
n=n/10;
}
cout<<ninv;
}
Se poate si asa :
#include <iostream.h>
main ( )
{
int i,s,n,ninv;
cout<<”n=”; cin>>n;
for (ninv=0;n>0;n/=10) ninv=ninv*10+n%10;
cout<<ninv;
}

8.Ce trebuie sa stim pentru a utiliza o functie

Functiile sunt apelate cu un numar de parametri efectivi.Parametrii de apel ai unei functii si


tipul ei sunt continuti de prototipul functiei respective.Prototipurile functiilor din acelasi domeniu se
gasesc grupate intr-un fisier header.De exemplu, daca utilizam functii de citire/scriere trebuie inclus in
textul sursa al programului fisierul iostream.h, daca utilizam functii matematice trebuie inclus fisierul
math.h.
Exemplu de prototip:
int t (int , float);

9.Functii matematice

Fiecare dintre functiile prezentate mai jos are prototipul in math.h :


 Functia abs are forma generala: int abs(int x); Aceasta inseamna ca are un parametru de tip int, iar
rezultatul este tot de tip int. Inaintea numelui se gaseste tipul rezultatului.Rolul ei este de a intoarce
|x| (modulul lui x).
Iata cum arata un program care o foloseste (tipareste 1234):
#include <iostream.h>
#include <math.h>
main ( )
{
int n= -1234;
cout<<abs(n);
}
 Functia fabs are forma generala double fabs(double x);
are acelasi rol cu abs, numai ca intoarce valoarea unui numar real (chiar double).
 Functia labs are forma generala long int labs(long int x);
are acelasi rol cu abs, numai ca intoarce valoarea unui intreg
lung.
 Functia acos are forma generala double acos(double x); si calculeaza valoarea functiei arccos(x):
[-1,1][0,π].
 Functia asin are forma generala double asin(double x); si calculeaza valoarea functiei arcsin(x): [-
1,1][-π/2,π/2].
 Functia atan are forma generala double atan(double x); si
calculeaza valoarea functiei arctg(x):R(-π/2,π/2).
 Functia atan2 are forma generala
double atan2(double y,double x)
si calculeaza arctg(y/x).Rezultatul este in intervalul (-π,π).
 Functia ceil are forma generala double ceil(double x); si calculeaza |x| a lui x (rotunjirea se face
in minus).
 Functia floor are forma generala double floor(double x); si
calculeaza valoarea rotunjita a lui x (rotunjirea se face in plus).
 Functia cos are forma genarala double cos(double x); si calculeaza valoarea functiei cos(x):R[-
1,1].
 Functia sin are forma generala double sin(double x); si
calculeaza valoarea functiei sin(x):R[-1,1].
 Functia tan are forma generala double tan(double x); si calculeaza valoarea functiei tg(x):R—
{k*π+π/2|k din Z}R.
 Functia exp are forma generala double exp(double x); si calculeaza functia e x:RR*+.
 Functia log are forma generala double log(double x); si
calculeaza functia ln(x): R*+R, unde ln(x)=loge(x).
 Functia log10 are forma generala double log10(double x); si calculeaza functia lg(x): R*+R,
unde lg(x)=log10(x).
 Functia pow are forma generala double pow(double x, double y); si calculeaza xy.
Observatie: Este sarcina programatorului, ca pentru o astfel de functie, valoarea argumentului sa fie
in domeniul de definitie al functiei respective.

10.Generarea numerelor aleatoare

Functiile prezentate in acest paragraf au prototipurile in stdlib.h.


Functia rand are forma int rand(void); si rolul de a genera un numar aleator intreg cuprins
intre 0 si 32767.
Setarea generatorului de numere aleatoare cu un numar aleator se face cu randomize.
void randomize (void);
Exemplu: in programul urmator se tiparesc 10 numere aleatoare, cuprinse intre 0 si 49.
#include <stdlib.h>{pentru a se folosi functii de nr.aleatoare}
#include <iostream.h>
main ( )
{
int i;
randomize ( ); {setarea generatorului de numere aleatoare}
for (i=0;i<10;i++)
cout<<rand( ) %50<<’ ‘; {genereaza un numar aleator}
}
Pentru setarea generatorului de numere aleatoare intr-un anumit punct se foloseste functia
srand:
void srand (unsigned n);
Daca in programul anterior se inlocuieste randomize( ) cu srand(n) , pentru valoarea fixata a
lui n, seria celor 10 numere va fi intotdeauna aceeasi, la rulari succesive ale programului.

11.Rularea unei secvente un interval de timp determinat

In fisierul time.h se gaseste prototipul functiei clock :


clock_t clock (void);
Functia clock intoarce din sistem timpul.
Algoritmul prin care se determina ca o secventa sa ruleze un timp determinat este urmatorul:
 o variabila retine momentul intrari in secventa;
 se executa secventa;
 o alta variabila retine timpul;
 secventa se repeta daca diferenta de timp, convertita in secunde, este mai mica decat timpul de
lucru permis.
Pentru conversia in secunde, timpul scurs este impartit la o constanta definita in time.h si
anume CLK_TCK.
Programul urmator ruleaza 30 de secunde.
#include <time.h> {pentru a se folosi functia de timp}
#include <stdio.h>
int main (void)
{
long start,end;
start = clock( );
do
end = clock( );
while ( (end-start)/CLK_TCK<30);
}

Tipuri de date structurate


1.Tablouri
1.1.Tabloul in interpretare matematica

Fie Ani ={1,2,…,ni} multimea primelor ni numere naturale.Fie M=An1 X An2 X … XAnk
produsul cartezian a k astfel de multimi.
Se numeste tablou o functie f:MT, unde T este o multime oarecare.Numarul k este
dimensiunea tabloului.Daca k=1 tabloul se mai numeste si vector.Vectorul are n1 componente.Daca k=2
tabloul se mai numeste si matrice.Matricea are n1X n2 elemente.
Exemple:
1. Vector cu 5 componente numere naturale
V = (1,3,6,2,4 )
Aici k=1,n1 =5,T=N, elementele vectorului sunt v1=1,v2=3,…,v5=4.
2.Vector cu n componente reale
Z=(z1, z2,…,zn), zi real, i=1,n
Aici k=1,n1 =n,T=R.

3.Matrice cu n linii si m coloane, cu elemente din R:

a1,1 a1,2 . . a1,n


a2,1 a2,2 . . a2,n
a3,1 a3,2 . . a3,n
. . . . .
A= ai,j nr. reale
. . . . .
am-1,1 am-1,2 . . am-1,n
am,1 am,2 . . am,n

 Tabloul de mai sus se numeste A.


 Elementele sale sunt a1,1 ,a1,2 , … , am,n .
 Prin ai,j se intelege elementul care se gaseste in linia i si coloana j.
Aici k=2, n1 =m,n2 =n,T=R.
1.2.Tablouri in C++

In C++ tipul tablou se declara astfel:

tip nume[numar natural1] [numar natural2]…[numar naturaln]

Adresarea unei componente se face prin indice, care se trece intre paranteze drepte, iar
componentele au indici intre 0 si 99.
Exemple:
1. int v[100] - am declarat un vector cu 100 de componente de tip intreg.
2. float a[50],b[50] – am declarat doi vectori, a si b, care au componente de tip float.
3. int a[10][9] – am declarat o matrice a care are 10 linii si 9 coloane;liniile sunt de la 0 la 9, iar
coloanele de la 0 la 8, iar fiecare element al ei este de tip int.

Exemplul 1. Programul urmator citeste si tipareste variabila v[100].Initial se citeste numarul


componentelor (n), dupa care se citesc toate componentele, cu ajutorul instructiunii for.In final, se
tipareste continutul componentelor citite.
#include <iostream.h>
main ( )
{
int v[100],n,i;
cout<<”numar de componente”; cin>>n;
for (i=0;i<n;i++)
{
cout<<”v[“<<i+1<<”]=”;
cin>>v[i];
}
for (i=0;i<n;i++) cout<<v[i]<<endl;
}
Observatie: Nu sunt permise atribuiri de forma b=a, unde a si b sunt tablouri.In acest caz,
atribuirea se face pe componente.

Exemplul 2. Programul de mai jos utilizeaza doi vectori, cu componente de tip float.Se citeste
vectorul a, dupa care se face atribuirea pe componente.La sfarsit, se tipareste vectorul b.
#include <iostream.h>
main ( )
{
int n,i;
float a[50], b[50];
cout<<”numar de componente”; cin>>n;
for (i=0;i<n;i++)
{
cout<<”a[“<<i+1<<”]=”;
cin>>a[i];
}
for (i=0;i<n;i++) b[i]=a[i];
for (i=0;i<n;i++) cout<<b[i]<<endl;
}
Exemplul 3. In programul urmator se citeste si se tipareste un tablou.Initial se citesc numarul
de linii si de coloane ale tabloului (m si n).
#include <iostream.h>
main ( )
{
int m,n,i,j,a[9][9];
cout<<”m =”; cin>>m;
cout<<”n =”; cin>>n;
for (i=0;i<m;i++)
for (j=0;j<n;j++)
{
cout<<”a[“<<i+1<<’,’<<j+1<<”]=”;
cin>>a[i][j];
}
for (i=0;i<m;i++)
{
for (j=0;j<n;j++) cout<<a[i] [j]<<’ ‘;
cout<<endl;
}
}
Observatie: In memorie, tablourile sunt memorate pe linii.

1.3. Algoritmi fundamentali care lucreaza cu vectori


1.3.1. Maxim, minin

Se citeste un vector cu n componente numere intregi.Se cere sa se afiseze cel mai mare numar
intreg gasit.Exemplu: n=4 si se citesc valorile 2, -4, 3, 5.Se va afisa 5.
#include <iostream.h>
int v[9],n,i,max;
main ( )
{
cout<<”n =”; cin>>n;
for (i=0;i<n;i++)
{
cout<<”v[“<<i+1<<”]=”;
cin>>v[i];
}
max=v[0];
for (i=0;i<n;i++)
if (v[i]>max) max=v[i];
cout<<”valoarea maxima citita este “<<max;
}

1.3.2. Elemente distincte

Se citeste n si o variabila de tip array cu n componente numere intregi.Sa se decida daca


numerele citite sunt distincte (nu exista doua numere egale).
#include <iostream.h>
int v[9],n,i,j,gasit;
main ( )
{
cout<<”n =”; cin>>n;
for (i=0;i<n;i++)
{
cout<<”v[“<<i+1<<”]=”;
cin>>v[i];
}
gasit=0;
for(i=0;i<n && !gasit;i++)
for(j=0;j<n && !gasit;j++)
if (v[i]==v[j]) gasit=1;
if (gasit) cout<<”numerele nu sunt distincte”;
else cout<<”numerele sunt distincte”;
}

1.3.3. Multimi

La matematica am invatat faptul ca multimile nu se pot defini, dar pot fi descrise ca fiind date
de mai multe elemente de acelasi tip.In cadrul unei multimi, un element apare o singura data.
A citi o multime inseamna a introduce elementele care o alcatuiesc.Acestea sunt memorate
intr-o variabila da tip vector.

a.Testul de apartenenta. Se citesc o multime A de numere intregi si un numar intreg e.Sa se


decida daca e apartine multimii.
#include <iostream.h>
int mult[9], n,e,i,gasit;
main ( )
{
cout<<”numarul de elemente ale multimii “; cin>>n;
for (i=0;i<n;i++)
{
cout<<”mult[“<<i+1<<”]=”; cin>>mult[i];
}
cout<<”elementul considerat “; cin>>e;
gasit=0;
for(i=0;i<n && !gasit;i++)
if (mult[i]==e) gasit=1;
if (gasit) cout<<”elementul apartine multimii”;
else cout<<”elementul nu apartine multimii”;
}
b.Diferenta a doua multimi. Se citesc doua multimi A si B.Se cere sa se afiseze multimea
C=A-B.
#include <iostream.h>
int multa[9], multb[9],multc[9],n,m,i,j,k,gasit;
main ( )
{
cout<<”numarul de elemente al multimii A “; cin>>n;
for (i=0;i<n;i++)
{
cout<<”mult[“<<i+1<<”]=”; cin>>multa[i];
}
cout<<”numarul de elemente al multimii B “; cin>>m;
for (i=0;i<m;i++)
{
cout<<”mult[“<<i+1<<”]=”; cin>>multb[i];
}
k=0;
for (i=0;i<n;i++)
{
gasit=0;
for (j=0;j<=m && !gasit;j++)
if (multa[i]==multb[j]) gasit=1;
if (!gasit) multc[k++]=multa[i];
}
cout<<”A-B”<<endl;
for (i=0;i<k;i++) cout<<multc[i]<<endl;
}
c. Reuniunea a doua multimi. Se citesc doua multimi de numere intregi A si B.Se cere sa se
afiseze multimea C=A U B.Se va aplica formula C=A U B=B U (A-B).

#include <iostream.h>
int multa[9], multb[9],multc[9],n,m,i,j,k,gasit;
main ( )
{
cout<<”numarul de elemente al multimii A “; cin>>n;
for (i=0;i<n;i++)
{
cout<<”mult[“<<i+1<<”]=”; cin>>multa[i];
}
cout<<”numarul de elemente al multimii B “; cin>>m;
for (i=0;i<m;i++)
{
cout<<”mult[“<<i+1<<”]=”; cin>>multb[i];
}
k=0;
for (i=0;i<n;i++)
{
gasit=0;
for (j=0;j<=m && !gasit;j++)
if (multa[i]==multb[j]) gasit=1;
if (!gasit) multc[k++]=multa[i];
}
cout<<”A reunit cu B”<<endl;
for (i=0;i<m;i++) cout<<multb[i]<<endl;
for (i=0;i<k;i++) cout<<multc[i]<<endl;
}
d. Intersectia a doua multimi. Se citesc doua multimi de numere intregi A si B.Se cere sa se
afiseze multimea multimea rezultata prin intersectia celor doua multimi.
#include <iostream.h>
int multa[9], multb[9],multc[9],n,m,i,j,k,gasit;
main ( )
{
cout<<”numarul de elemente al multimii A “; cin>>n;
for (i=0;i<n;i++)
{
cout<<”mult[“<<i+1<<”]=”; cin>>multa[i];
}
cout<<”numarul de elemente al multimii B “; cin>>n;
for (i=0;i<m;i++)
{
cout<<”mult[“<<i+1<<”]=”; cin>>multb[i];
}
k=0;
for (i=0;i<n;i++)
{
gasit=0;
for (j=0;j<=m && !gasit;j++)
if (multa[i]==multb[j]) gasit=1;
if (gasit) multc[k++]=multa[i];
}
cout<<”A intersectat cu B”<<endl;
for (i=0;i<k;i++) cout<<multc[i]<<endl;
}
e. Produsul cartezian dintre doua multimi. Fie multimile A={1,2,…,n} si B={1,2,…,m} (m si
n se citesc).Se cere sa se afiseze multimea C=AxB.
#include <iostream.h>
main ( )
{
int n,m,i,j;
cout<<”numarul de elemente al multimii A ”; cin>>n;
cout<<”numarul de elemente al multimii B ”; cin>>m;
for (i=1;i<=n;i++)
for (j=1;j<=m;j++)
cout<<i<<” “<<j<<endl;
}
Problema a fost rezolvata pentru cazul particular in care multimile sunt formate din numere
naturale.
Pentru cazul general, in care A este o multime formata din n caractere, iar B este o multime
formata din m caractere, rezolvarea este redata de programul de mai jos:
#include <iostream.h>
char multa[9],multb[9];
int n,m,i,j;
main ( )
{
cout<<”numarul de elemente al multimii A “; cin>>n;
for (i=0;i<n;i++)
{
cout<<”mult[“<<i+1<<”]=”; cin>>multa[i];
}
cout<<”numarul de elemente al multimii B “; cin>>m;
for (i=0;i<m;i++)
{
cout<<”mult[“<<i+1<<”]=”; cin>>multb[i];
}
for (i=0;i<=n;i++)
for (j=0;j<=m;j++)
cout<<multa[i]<<” “<<multb[j]<<endl;
}
f. Submultimi. Se citeste n, numar natural.Se cere sa se afiseze toate submultimile multimii
{1,2,…,n}.
#include <iostream.h>
int multa[9],n,i,s;
main ( )
{
cout<<”numarul de elemente al multimii A ”; cin>>n;
for (i=0;i<n;) multa[i++]
do
{
multa[n-1]++;
for (i=n-1;i>=n;i- -)
if (multa[i]>1)
{
multa[i]-=2;
multa[i-1]+=1;
}
s=0;
for (i=0;i<n;i++) s+=multa[i];
for (i=0;i<n;i++)
if (multa[i]) cout<<i+1<<’ ‘;
cout<<endl;
} while (s<n) ;
cout<<”multimea vida “;
}

1.3.4 Metode de sortare

Se citesc n numere intregi.Se cere ca acestea sa fie scrise in ordine crescatoare


(descrescatoare).Exemplu: Se citeste n=4 si se citesc numerele: 3, 1, 6, 2.Numerele vor fi afisate in
ordinea 1, 2, 3, 6 (crescatoare) sau 6, 3, 2, 1 (descrescatoare).
Operatia prin care se aranjeaza cele n numere in ordine crescatoare sau descrescatoare se
numeste sortare.
Pentru a sorta cele n valori, acestea se citesc intr-o variabila se tip tablou.Sortarea propriu-zisa
se face in cadrul acestei variabile.Se cunosc o multime de algoritmi de sortare, care au fost elaborati in
timp.
a. Sortarea prin selectarea minimului (maximului)

#include <iostream.h>
int a[9],n,i,j,k,man,min;
main ( )
{
cout<<”n=”; cin>>n;
for (i=0;i<n;i++)
{
cout<<”a[“<<i+1<<”]=”; cin>a[i];
};
for (i=0;i<n-1;i++)
{
min=a[i];k=i;
for (j=i+1;j<n;j++)
if (a[j]<min)
{
min=a[j];
k=j;
}
man=a[k];
a[k]=a[i];
a[i]=man;
}
for (i=0;i<n;i++) cout<<a[i]<<endl;
}

b.Sortarea prin interschimbare

#include <iostream.h>
int a[9],n,i,j,k,man,gasit;
main ( )
{
cout<<”n=”; cin>>n;
for (i=0;i<n;i++)
{
cout<<”a[“<<i+1<<”]=”; cin>a[i];
};
do
{
gasit=0;
for (i=0;i<n-1;i++)
if (a[i]>a[i+1])
{
main=a[i]; a[i]=a[i+1]; a[i+1]=man;
gasit=1;
}
} while (gasit);
for (i=0;i<n;i++) cout<<a[i]<<endl;
}

c. Sortarea prin insertie

#include <iostream.h>
int a[9], b[9], n,i,j,k,gasit;
main ( )
{
cout<<”n=”; cin>>n;
for (i=0;i<n;i++)
{
cout<<”a[“<<i+1<<”]=”; cin>a[i];
};
b[0]=a[0];
for (i=1;i<n;i++)
{
j=i-1;
while (j>=0 && a[i]<b[i]) j- -;
for (k=i-1;k>=j+1;k- -) b[k+1]=b[k];
b[j+1]=a[i];
}
for (i=0;i<n;i++) cout<<b[i]<<endl;
}

1.3.5 Interclasare

Se citesc m numere intregi sortate crescator (descrescator).De asemenea, se citesc alte n


numere intregi sortate crescator (descrescator).Se cere sa se afiseze cele m+n valori in ordine
crescatoare (descrescatoare).

#include <iostream.h>
main ( )
{
int a[100],b[100],c[200],m,n,i,j,k;
cout<<”m=”; cin>>m;
for (i=0;i<m;i++)
{
cout<<”a[“<<i+1<<”]=”; cin>a[i];
};
cout<<”n=”; cin>>n;
for (i=0;i<n;i++)
{
cout<<”b[“<<i+1<<”]=”; cin>b[i];
};
i=j=k=0;
while (i<m && j<n)
if (a[i]<b[j]) c[k++]=a[i++];
else c[k++]=b[j++];
if (i<m)
for (j=i;j<m;j++) c[k++]=a[j];
else
for (i=j;i<n;i++) c[k++]=b[i];
for (i=0;i<k;i++) cout<<c[i]<<endl;
}

1.3.6. Cautare binara

Se citesc n numere intregi sortate crescator.De asemenea, se citeste un numar intreg nr.Sa se
decida daca nr se gaseste in sirul celor n numere citite.

#include <iostream.h>
main ( )
{
int a[100],n,i,li,ls,k,nr,gasit;
cout<<”n=”; cin>>n;
for (i=0;i<n;i++)
{
cout<<”a[“<<i+1<<”]=”; cin>a[i];
};
cout<”nr=”; cin>>nr;
li=0;ls=n-1;gasit=0;
do
{
k=(li+ls)/2;
if (a[k]==nr)
{
cout<<”gasit pe pozitia “<<k+1;
gasit=1;
}
else
if (a[k]<nr) li=k+1;
else ls=k-1;
} while (li<=ls && !gasit);
if (li>ls) cout<<”negasit”;
}

1.4. Aplicatii cu matrice

1. Interschimbare linii. Se citeste un tablou cu m linii si n coloane.Se citesc de asemenea si


doua numere naturale, distincte, x si y, cuprinse intre 1 si m.Se cere sa se interschimbe linia x cu
coloana y.La inceput vom afisa tabloul initial, apoi pe cel obtinut prin interschimbarea liniilor x si y.

#include <iostream.h>
main ( )
{
int mat[10][10],m,n,i,j,x,y,man;
cout<<”m=”; cin>>m;
cout<<”n=”; cin>>n;
for (i=0;i<m;i++)
for (j=0;i<n;j++)
{
cout<<”mat[“<<i+1<<’,’<<j+1<<”]=”;
cin>>mat[i][j];
};
cout<<”x=”; cin>>x;
cout<<”y=”; cin>>y;
cout<<endl;
for (i=0;i<m;i++)
{
for (j=0;i<n;j++) cout<<mat[i][j]<<’ ‘;
cout<<endl;
}
for (j=0;i<n;j++)
{
man=mat[x-1][j];
mat[x-1][j]=mat[y-1][j];
mat[y-1][j]=man;
}
cout<<endl;
for (i=0;i<m;i++)
{
for (j=0;i<n;j++) cout<<mat[i][j]<<’ ‘;
cout<<endl;
}
}
2. Spirala. Se citeste un tablou cu n linii si n coloane.Se cere sa se afiseze elementele tabloului
in ordinea rezultata prin parcurgerea acestuia in spirala, incepand cu primul element din linia 1 in
sensul acelor de ceas.
#include <iostream.h>
main ( )
{
int mat[10][10],n,i,j,k;
cout<<”n=”; cin>>n;
for (i=1;i<=n;i++)
for (j=1;j<=n;j++)
{
cout<<”mat[“<<i+1<<’,’<<j+1<<”]=”;
cin>>mat[i][j];
}
for (k=1;k<=n/2;k++)
{
for (i=k;i<=n-k+1;i++) cout<<mat[k][i]<<endl;
for (i=k+1;i<=n-k+1;i++) cout<<mat[i][n-k+1]<<endl;
for (i=n-k;i>=k;i- -) cout<<mat[n-k+1][i]<<endl;
for (i=n-k;i>=k+1;i- -) cout<<mat[i][k]<<endl;
}
}

2. Siruri de caractere
2.1. Memorarea si declararea vectorilor de caractere

In memoria interna, o constanta de tip sir este retinuta sub forma unui vector de
caractere.Conventia este ca ultimul octet sa retina 0 (caracterul nul).Pentru fiecare caracter este retinut
codul ASCII.
Vectorii de caractere pot fi initializati la declarare, caracterul nul fiind memorat automat.
Exemple:
 char vect[11]=”calculator” - initializari la declarare
 char vect[ ]=”calculator” – in acest caz, calculatorul face calculul pentru octetii necesari
 char vect[100]=”calculator” – s-au rezervat mai multi octeti decat era necesar.

2.2. Citirea / scrierea sirurilor de caractere

Se propune problema citirii si afisarii cuvantului “calculator”.Programul urmator realizeaza


aceste lucruri:

#include <iostream.h>
main ( )
{
char a[20];
int i;
for (i=0;i<10;i++) cin>>a[i];
a[10]=0;
for (i=0;i<10;i++) cout<<a[i];
}
Limbajul C++ permite simplificarea lucrului cu siruri de caractere.Programul anterior poate fi
scris si astfel:
#include <iostream.h>
main ( )
{ char a[20];
cin>>a; cout<<a;
}
Observatie: Un vector poate fi adresat pe componente.De exemplu a[0]=’c’,a[1]=’a’
s.a.m.d.Din pacate, utilizand vectorii, spatiile nu se pot citi, ceea ce constituie un dezavantaj.
Functia cin.get(vector_de_caractere, int nr, char=’\n’) , care citeste un sir de caractere,
memorat sub forma unui vector cu nr-1 componente, pana la intalnirea caracterului dat.Al treilea
parametru este optional.Daca nu este trecut, se presupune ca este ‘\n’.
Exemple:
1.Se citeste un sir de maxim 2 caractere.
char a[10];
cin.get(a,3);
cout<<a;
2.La fel ca mai sus, dar citirea se intrerupe la intalnirea caracterului g, sau cand s-au citit 9 caractere
ale sirului.
char a[10];
cin.get(a,10,’g’);
cout<<a;
Functia cin.get( ) citeste un caracter, alb sau nu.
Observatie: In cazul utilizarii repetate a functiei cin.get( ) cu trei parametri, este necesara
introducerea unui caracter gol pentru a continua citirea, ca in programul urmator:
#include <iostream.h>
#include <string.h>
main ( )
{
char sir1[1000], sir2[25];
cout<<”sir 1 “; cin.get(sir1,1000);
cin.get ( );
cout<<”sir 2 “; cin.get(sir2,25);
}

2.3. Tipul char*

Limbajul C++ permite ca un vector de caractere sa fie adresat incepand de la un anumit octet
al sau.De aceea, in urma executiei programului urmator, se tipareste: asa sa a.
#include <iostream.h>
main ( )
{
char a[ ]=’masa’;
cout<<a+1<<” “<<a+2<<” “<<a+3;
}
Observatii:
 Octetii memoriei interne sunt numerotati incepand cu 0.
 Numarul de ordine al unui octet in memoria interna se numeste adresa octetului respectiv.
 Prin definitie, adresa unui vector de caractere este ardesa primului sau octet.
 O variabila de tipul char* poate retine adresa unui vector de caractere.
 In C++ numele unui vector de caractere este o adresa constanta de vector si poate fi atribuit unei
variabile de tip char*.
Exemplu:
#include <iostream.h>
main ( )
{ char a[ ]=”Exemplu”, *p;
p=a; cout<<p<<endl;
p++; cout<<p<<endl; { ne deplasam pe urmatorul octet}
p++; cout<<p<<endl;
cout<<p[1]<<endl; { se tipareste numai acest octet}
cout<<p-a;
}

2.4. Functii si algoritmi care lucreaza cu siruri de caractere

Fiind dati doi vectori de caractere a si b, nu sunt permise atribuiri de forma a=b sau o atribuire
de forma a=”un sir”.Aceste operatii , ca si multe altele se fac cu anumite functii specifice
limbajului.Pentru folosirea acestor functii, trebuie sa fie inclus fisierul antet string.h.Cele mai uzuale
functii sunt:
 Functia strlen are rolul de a returna lungimea efectiva a unui sir (in calculul lungimii nu intra
caracterul nul).Forma generala este:
size_t strlen (char*);
 size_t este un tip intreg, utilizat in adresarea memoriei (il putem privi ca pe tipul unsigned int)
 argumentul este de tip char* - un vector de caractere
Programul de mai jos citeste un sir si afiseaza numarul de caractere pe care le are sirul citit
(exclusiv caracterul nul).
#include <iostream.h>
#include <string.h>
main ( )
{ char a[100];
cin.get(a,100);
cout<<”Sirul citit are “<<strlen (a)<<”caractere”;
}
 Functia strcpy are forma generala:
char *strcpy (char* dest,char* sursa);
si are rolul de a copia sirul de adresa sursa la adresa dest.
#include <iostream.h>
#include <string.h>
main ( )
{ char a[100]=”un sir”,b[100]=”alt sir”;
strcpy (a,b);
cout<<a;
}
 Functia standard strcat are forma generala:
char *strcat (char* dest, char* sursa);
si rolul de a adauga sirului de adresa dest sirul de adresa sursa.Sirul de adresa sursa
ramane nemodificat.Aceasta operatie se numeste concatenare si nu este comutativa.
#include <iostream.h>
#include <string.h>
main ( )
{
char a[20]=”mama”,b[100]=”merge”;
strcat (a,b);
cout<<a;
}
Programul de mai sus tipareste “mama merge”.
 Functia strncat are forma generala:
char *strncat (char *dest, const char *sursa, size_t nr);
si acelasi rol ca strcat cu deosebirea ca adauga sirului destinatie primii nr octeti ai sirului
sursa.Adaugarea se face inaintea caracterului nul.
 Functia strchr are forma generala:
char *strchr (char *s, int c);
si rolul de a cauta caracterul c in sirul s.Cautarea se face de la stanga la dreapta.In cazul in
care caracterul este gasit, functia
intoarce adresa subsirului care incepe cu prima aparitie a caracterului citit si se termina cu
caracterul nul al sirului in care se face cautarea.

Exemplu. In programul urmator se cauta in sirul a caracterul t.Acesta este gasit, iar programul
tipareste sirul “ta este”.
#include <iostream.h>
#include <string.h>
main ( )
{
char a[20]=”Aceasta este”;
cout<<strchr (a,’t’);
}
Aplicatia 1. Indicele in cadrul vectorului a unui caracter cautat se obtine ca diferenta intre
adresa vectorului in care se face cautarea si adresa returnata de functie.Programul de mai jos tipareste
indicele primei aparitii a caracterului ‘t’, si anume 4. #include <iostream.h>
#include <string.h>
main ( )
{
char a[20]=”Aceasta este”;
cout<<strchr (a,’t’)-a;
}
Aplicatia 2. In programul urmator se citeste un sir si un caracter.Daca acesta este gasit in sir se
tipareste indicele primei aparitii a caracterului in sirul solicitat, altfel programul semnaleaza ca acest
caracter nu se afla in sir.
#include <iostream.h>
#include <string.h>
main ( )
{
char a[100], *t,c;
cout<<”Introduceti sirul “; cin.get(a,100);
cout<<”caracterul cautat “; cin>>c;
t=strchr(a,c);
if (t) cout<<”Indicele este “<<t-a;
else cout<<”Sirul nu contine acest caracter “;
}
Aplicatia 3. In programul urmator se listeaza indicii tuturor aparitiilor caracterului citit in sir.
#include <iostream.h>
#include <string.h>
main ( )
{
char a[100], *t,c;
cout<<”Introduceti sirul “; cin.get(a,100);
cout<<”caracterul cautat “; cin>>c;
t=a-1;
do
{
t++;
t=strchr(t,c);
if (t) cout<<”Indicele este “<<t-a<<endl;
} while (t);
}

 Functia strrchr are forma generala:


char *strrchr (const char *s, int c);
si are acelasi rol cu strchr , deosebirea fiind data de faptul ca returneaza adresa ultimei
aparitii a caracterului – cautarea se
face de la dreapta catre stanga.

 Functia strcmp are forma generala:


int strcmp (const char *s1, const char *s2);
si rolul de a compara doua siruri de caractere.Valoarea returnata este:
 <0, daca s1<s2;
 =0, daca s1=s2;
 >0, daca s1>s2;
Mecanismul prin care se compara doua siruri de caractere este urmatorul:
Fie m numarul de caractere ale sirului s1 si n numarul de caractere ale sirului s2.Sa
presupunem ca primele i caractere ale lui s1 coincid cu primele i ale lui s2.
 In cazul in care codul caracterului i+1 al sirului s1 este mai mare decat codul caracterului
corespunzator al sirului s2, avem s1>s2.
 In cazul in care codul corespunzator i+1 al sirului s1 este mai mic decat codul caracterului
corespunzator al sirului s2, avem s1<s2.In cazul in care avem egalitate, avem patru posibilitati:
 Ambele siruri au un numar strict mai mare de caracatere decat i+1, caz in care se compara ca
inainte caracterele de pe pozitia i+2;
 s1 are i+1 caractere, iar s2 are un numar de caractere mai mare decat i+1, in acest caz s1<s2;
 s2 are i+1 caractere, iar s1 are un numar de caractere mai mare decat i+1, in acest caz s1>s2;
 atat s1 cat si s2 au i+1 caractere, caz in care s1=s2.
Programul urmator probeaza relatia de ordine dintre doua cuvinte(siruri care nu contin
caractere albe):
#include <iostream.h>
#include <string.h>
main ( )
{
char a[100],b[100];
int semnal;
cout<<”Introduceti sirul a “; cin>>a;
cout<<”Introduceti sirul b “; cin>>b;
semnal=strcmp(a,b);
if (semnal<0) cout<<”a<b”;
else
if (semnal>0) cout<<”a>b”;
else cout<<”a=b”;
}
Observatie: Functia strcmp nu face distinctie intre literele mari si mici ale alfabetului.

Vectori de cuvinte

Vectorii de cuvinte, unde prin cuvant intelegem o succesiune de cararctere diferite de


caracterul alb, se pot declara ca matrice cu elemente de baza de tip char.
Aplicatia 4.In programul urmator se citesc n cuvinte.Acestea sunt sortate alfabetic.Se compara
cele 2 cuvinte si daca primul e mai lung decat celalalt se face interschimbarea.
#include <iostream.h>
#include <string.h>
main ( )
{ char cuvinte[10][25], man[25];
int i,n,gasit;
cout<<”n=”; cin>>n;
for (i=0;i<n;i++)
{
cout<<”cuvant “;
cin>>cuvinte[i];
}
do
{ gasit=0;
for (i=0;i<n-1;i++)
if (strcmp(cuvinte[i], cuvinte[i+1])>0)
{
strcpy(man,cuvinte[i]);
strcpy(cuvinte[i],cuvinte[i+1]);
strcpy(cuvinte[i+1],man);
gasit=1;
}
}
while (gasit);
for (i=0;i<n;i++) cout<<cuvinte[i]<<endl;
}
 Functia strstr are forma generala:
char *strstr (const char *s1, const char *s2);
si rolul de a identifica daca sirul s2 este subsir al sirului s1.
Daca acesta este identificat, functia returneaza adresa de inceput in cadrul sirului s1, altfel
returneaza adresa 0.Cautarea se face de la stanga la dreapta.
Aplicatia 5. In programul urmator se citesc doua siruri de caractere si se testeaza daca al
doilea este subsir al primului.In caz afirmativ, programul tipareste si indicele caracterului de inceput al
subsirului.
#include <iostream.h>
#include <string.h>
main ( )
{ char sir[1000], subsir[25], *t;
cout<<”Introduceti textul “; cin.get(sir,1000);
cin.get( );
cout<<”Introduceti subsirul cautat “; cin.get(subsir,25);
t=strstr(sir,subsir);
if (t) cout<<”este subsir si are indicele “<<t-sir;
else cout<<”nu este subsir”;
}
Aplicatia 6. Stergerea tuturor aparitiilor unui subsir din cadrul unui sir.Dupa identificarea
adresei de inceput a subsirului, restul sirului este copiat pe pozitia de inceput a subsirului.
#include <iostream.h>
#include <string.h>
main ( )
{
char sir[1000], subsir[25], *p;
int lung-subsir;
cout<<”introduceti textul”; cin.get(sir,1000);
cin.get( );
cout<<”introduceti subsirul”; cin.get(subsir,25);
lung_subsir=strlen(subsir);
p=strstr(sir,subsir);
while (p)
{
strcpy(p,p+lung_subsir);
p=strstr(p,subsir);
}
cout<<sir;
}
Aplicatia 7. Inlocuirea tuturor aparitilor unui subsir cu alt subsir.
#include <iostream.h>
#include <string.h>
main ( )
{
char sir[100],man[100],sterg[25],adaug[25],*p;
int lung_sterg,lung-adaug;
cout<<”introduceti textul”; cin.get(sir,100);
cin.get( );
cout<<”inlocuim subsirul”; cin.get(sterg,25);
cin.get( );
cout<<”cu subsirul “; cin.get(adaug,25);
lung_sterg=strlen(sterg);
lung_adaug=strlen(adaug);
p=strstr(sir,sterg);
while (p)
{
man[0]=0; / / subsir vid;
strncat(man,sir,p-sir);
strcat(man,adaug);
strcat(man,p+lung_sterg);
strcpy(sir,man);
p=strstr(p+lung_adaug,sterg);
}
cout<<sir;
}
 Functia stricmp are forma generala:
int strcmp(char *s1,char *s2);
si are acelasi rol ca strcmp, cu deosebirea ca nu face distinctie intre literele mari si mici.
 Functia strtok are forma generala:
char *strtok(char *s1, const char *s2);
Aplicatia 8. In programul urmator se citeste un sir de caractere.Entitatile se considera a fi
cuvinte.Programul listeaza entitatile depistate, fiecare pe un rand.
#include <iostream.h>
#include <string.h>
main ( )
{
char sir[1000],separator[ ]=”, “, *p;
cin.get(sir,1000);
p=strtok(sir,separator);
while (p)
{
cout<<p<endl;
p=strtok(NULL, separator);
}
}
Aplicatia 9. Programul urmator citeste un sir de caractere si tipareste sirul obtinut prin
eliminarea blank-urilor (separa entitatile, care sunt afisate una dupa alta).
#include <iostream.h>
#include <string.h>
main ( )
{
char sir[1000],separator[ ]=”, “, *p;
cin.get(sir,1000);
p=strtok(sir,separator);
while (p)
{
cout<<p;
p=strtok(NULL, separator);
}
}

 Functia strcspn are forma generala:


size_t strcspn(const char *s1, const char *s2);
si rolul de a returna numarul de caractere ale sirului s1 (caractere consecutive care
incep obligatoriu cu primul caracter) care nu se gasesc in sirul s2.
 Functia standard strspn are forma generala:
size_t strspn(char *s1, char *s2);
si rolul de a returna numarul de caractere ale sirului s1 (caractere consecutive care
incep obligatoriu cu primul caracter) care se gasesc in sirul s2.

Aplicatia 10. Se citeste un sir de caractere care nu contine caractere albe.Sa se verifice daca
sirul este alcatuit exclusiv din caractere numerice.
In vectorul cifre se retin toate caracterele numerice de la 0 la 9.
#include <iostream.h>
#include <string.h>
main ( )
{ char cuvant[100],cifre[ ]=”0123456789”;
cout<<”Introduceti cuvantul “; cin>>cuvant;
if (strspn(cuvant,cifre)==strlen(cuvant))
cout<<”numeric”;
else cout<<”nenumeric”;
}
Aplicatia 11. Se citeste un sir de caractere care nu contine caractere albe.Sa se verifice daca
sirul e alcatuit exclusiv din caractere nenumerice.
#include <iostream.h>
#include <string.h>
main ( )
{ char cuvant[100],cifre[ ]=”0123456789”;
cout<<”Introduceti cuvantul “; cin>>cuvant;
if (strcspn(cifre,cuvant)==10) cout<<”corect”;
else cout<<”incorect”;
}
 Functia strlwr are forma generala:
char *strlwr(char *s);
si converteste toate literele mari in litere mici.Restul literelor raman neschimbate, iar functia
intoarce adresa s.
 Functia strupr are forma generala:
char *strupr(char *s);
si converteste toate literele mici in litere mari.Restul literelor raman neschimbate, iar functia
intoarce adresa s.
In programul urmator se citeste un cuvant, care apoi se tipareste cu litere mari.
#include <iostream.h>
#include <string.h>
main ( )
{
char a[20];
cout<<”Introduceti cuvantul”; cin>>a;
cout<<strupr(a);
}

 Functia strpbrk are forma generala:


char *strpbrk(char *s1, char *s2);
si actioneaza astfel:
 Cauta primul caracter al sirului s1 in s2.Daca este gasit,
returneaza adresa sa din cadrul sirului s1 si executia se termina, altfel trece la pasul urmator.
 Cauta al doilea caracter al sirului s1 in s2.Daca este gasit,
returneaza adresa sa din cadrul sirului s1 si executia se termina, altfel trece la pasul urmator.
 …
 Daca nici un caracter al sirului s1 nu apartine sirului s2, functia returneaza adresa nula.
Aplicatia 12. Se citesc doua cuvinte.Se cere sa se afiseze toate caracterele primului cuvant
care se regasesc in al doilea.
#include <iostream.h>
#include <string.h>
main ( )
{
char cuvant1[10],cuvant2[10],*p;
cout<<”Introduceti primul cuvant ”; cin>>cuvant1;
cout<<”Introduceti al doilea cuvant ”; cin>>cuvant2;
p=strpbrk(cuvant1,cuvant2);
while (p)
{
cout<<p[0]<<endl;
p++;
p=strpbrk(p,cuvant2);
}
}

Urmatoarele functii au prototipul in stdlib.h si folosesc pentru conversia valorilor numerice in


sir si invers.
 Functia atof converteste un sir catre tipul double si are forma generala:
double atof (const char *s);
Daca conversia esueaza (se intalneste un caracter nenumeric), valoarea intoarsa este 0.Daca
primele caractere ale sirului sunt albe, acestea sunt ignorate.
Aceste observatii sunt valabile si pentru urmatoarele 3 functii.
 Functia _atold converteste un sir catre tipul long double:
long double _atold(const char *s);
 Functia atoi converteste un sir catre tipul int:
int atoi(const char *s);
 Functia atol converteste un sir catre tipul int:
long atol(const char *s);
Aplicatia 13. Se citeste un text alcatuit din mai multe cuvinte.Se cere sa se calculeze suma
valorilor numerice intalnite in text.Se presupune ca valorile numerice sunt introduse corect.
#include <iostream.h>
#include <string.h>
#include <stdlib.h>
main ( )
{
char sir[1000],separator[ ]=” “, cifre[ ]=”0123456789.+-”, *p;
double s=0;
cin.get(sir,1000);
p=strtok(sir,separator);
while (p)
{
if (strspn(p,cifre)==strlen(p))
s+=atof(p);
p=stetok(NULL, separator);
}
cout<<”suma numerelor intalnite in sir este “<<s;
}
 Functia ecvt are rolul de a converti o valoare double catre un
sir.Caracterul nul este adaugat automat sirului obtinut.Forma generala este:
char ecvt(double valoare, int poz, int*zec, int *semn);
unde:
 valoare – valoarea de convertit;
 poz – numarul de pozitii ocupate de sir;
 zec – adresa unei variabile de tip int, care retine numarul zecimalelor pe care le are numarul;
 semn – adresa unei variabile de tip int care are rolul de a memora, dupa apel, -1, daca numarul
este negativ sau 0, in caz contrar.
#include <iostream.h>
#include <string.h>
#include <stdlib.h>
main ( )
{
double numar;
int zec,semn;
char numar_sir[20]=” “,numar_prel[20]=” ”;
cout<<”n=”; cin>>numar;
strcpy(numar_sir,ecvt(numar,19,&zec, &semn));
cout<<”Sirul este “<<numar-sir<<” “<<zec<<” “<<semn<<endl;
if (semn) strcat(numar_prel,”-“);
strncat(numar_prel,numar_sir,zec);
strcat(numar_prel,”.”);
strncat(numar_prel,numar_sir+zec,3);
cout<<numar_prel;
}
 Functia itoa are rolul de a converti o valoare de tip int in sir,
care este memorat in variabila sir .Valoarea baza retine baza de numeratie catre care sa se faca
conversia.In cazul bazei 10, sirul obtinut retine si eventualul semn -.Functia intoarce adresa sirului
obtinut.
char *itoa(int valoare, char *sir, int baza);
 Functia ltoa are acelasi efect ca itoa , deosebirea fiind data de faptul ca se converteste o
valoare de tip long int.
char *ltoa(long value, char *sir, int baza);
 Functia ultoa are acelasi efect ca itoa , deosebirea fiind data de faptul ca se converteste catre
sir o valoare de tip unsigned long.
char *ultoa(unsigned long value, char *sir, int baza);
Pentru conversii se mai pot utiliza urmatoarele functii:
 Functia:
long strtol(const char *s, char **endptr, int radix);
are rolul de a converti un sir catre long.In afara sirului care trebuie convertit, functia primeste ca
parametru de intrare adresa unei variabile de tip char *, care dupa apel va retine pozitia primului
caracter de sir care nu poate fi convertit.
Aplicatia 14. Programul urmator testeaza daca o valoare introdusa este numerica si daca este
cuprinsa in intervalul [10,20].
Testul propriu-zis se face atunci cand comparam numarul caracterelor convertite cu lungimea
sirului.Egalitatea are semnificatia ca intreg sirul este numeric.

#include <iostream.h>
#include <string.h>
#include <stdlib.h>
main (void)
{
char numar[20], *adresa;
long v;
cin>>numar;
v=strtol(numar,&adresa,10);
if (adresa-numar!=strlen(numar))
cout<<”Data contine caractere nenumerice”;
else
if (v<10 | | v>20)
cout<<”Data numerica in afara limitei “;
else cout<<v<<endl;
}
 Functia :
double strtod(const char *s, char **endptr);
converteste un sir catre double.
 Functia :
long double _strtold(const char *(s), char **endptr);
converteste un sir catre long double.
 Functia :
unsigned long strtoul(const char *s, char **endptr, int radix);
converteste un sir catre unsigned long.
In cazul in care numarul nu poate fi memorat, fiind in afara tipului, ultimele patru functii
returneaza cea mai mare sau cea mai mica valoare care poate fi memorata de tipul in care se face
conversia, dupa cum valoarea este pozitiva sau negativa.

Fisiere

1.Notiunea de fisier
Definitie: Se numeste fisier o colectie de date omogene (adica de acelasi tip),
stocate pe suport extern si accesata printr-un nume, care reprezinta numele fisierului.
Exemple:
1.Programele sub forma executabila sunt memorate pe suport sub forma de
fisiere.Acestea se recunosc dupa extensia numelui, care este .exe sau .com.
2.Programele in format sursa (adica textul introdus de programator) sunt
stocate si ele sub forma de fisiere.Acestea pot fi recunoscute dupa extensia lor .pas
pentru surse Pascal si .cpp pentru surse C++.
3.Informatiile de natura economica sunt memorate, de asemenea, sub forma
de fisier.Informatiile referitoare la un material constituie o inregistrare, iar ansamblul
inregistrarilor constituie fisierul propriu-zis.
In C++ se lucreaza cu doua tipuri de fisiere:
a) Fisiere text;
b) Fisiere binare.

2.Fisiere text
2.1 Notiunea de fisier text
Fisierele text se caracterizeaza prin urmatoarele:

 Datele sunt memorate sub forma unei succesiuni de caractere.


 Fiecare caracter este memorat prin utilizarea codului ASCII.
 Un fisier text se termina intotdeauna cu caracterul EOF, care permite ca la
prelucrare sa poata fi identificat sfarsitul fisierului.Daca dorim sa-l introducem de la
tastatura, se tasteaza CTRL+Z.
 Fisierul text se considera alcatuit din una sau mai multe linii.O linie, mai putin
ultima, se termina prin caracterul newline (\ n).
 O variabila speciala, numita pointer, retine intotdeauna un octet al fisierului.

Schema unui fisier text este urmatoarea:

M a r ii i n 1 H EOF
\n
pointer

Orice editor de texte lucreaza cu fisiere text.


Cin si cout sunt doua tipuri speciale de fisiere text, care nu sunt memorate pe
suport extern, ci corespund dispozitivelor standard de intrare, respectiv de iesire.
Fisierul cin este privit ca fiind de intrare, adica fluxul de date este de la el catre
memoria interna.El este asimilat tastaturii, iar programul citeste datele din acest fisier.
Fisierul cout este privit ca unul de iesire, adica fluxul de date este de la
memoria interna catre el.El este asimilat monitorului, iar programul scrie astfel in
acest fisier.
Toate scrierile / citirile efectuate in / din aceste fisiere sunt identice cu cele
efectuate in / din fisierele memorate pe suport magnetic.
In fisierele text putem scrie sau citi cu sau fara format.Chiar daca scriem fara
format, exista anumite reguli implicite care trebuie respectate.

2.2 Citiri / scrieri fara format


 Citirea / scrierea variabilelor de tip char
a) Citirea. Se citeste primul caracter care nu este alb.Dupa citire, pointerul
indica pozitia urmatoare caracterului citit.
b) Scrierea. Se afiseaza caracterul pe pozitia curenta a pointerului, indiferent
daca este alb sau nu.Dupa afisare, pointerul va indica pozitia urmatoare.
Fie secventa:

char a;
cin>>a;
cout<<a;
 daca tastam ‘a’ se afiseaza ‘a’;
 daca tastam “ a” se afiseaza ‘a’ , pentru ca, la citire, au fost sarite
caracterele albe.

 Citirea / scrierea variabilelor numerice


a) Citirea se face incepand din pozitia curenta a pointerului, pana cand este
intalnit un caracter care nu face parte din tipul respectiv.
b) Scrierea se face incepand cu pozitia curenta a cursorului.
1) Fie secventa:

int a;
cin>>a;
cout<<a;
In acest caz, fie ca se tasteaza cifre, litere sau blank-uri, se afiseaza doar
caracterele care reprezinta cifre.
2) Fie secventa:

int a;double b;
cin>>a>>b;
cout<<a<<” “<<b;
In acest caz:
 daca se tasteaza bb17bb18.00, se afiseaza 17bbbb18
 daca se tasteaza 112.55, se afiseaza 112 si 0.55.

 Citirea / scrierea sirurilor de caractere


a) Citirea incepe cu pozitia curenta a pointerului si se face pana la intalnirea
unui caracter alb.Sirului astfel obtinut i se adauga caracterul nul, dupa care este
memorat.
b) Scrierea se face pana cand este intalnit caracterul nul.

Exemplu:

char a[10];
cin>>a;
cout<<a;
In cazul acestei secvente, daca se tasteaza un text , se afiseaza un.

2.3 Citiri / scrieri cu format


Citirile / scrierile cu format sunt caracterizate de urmatoarele notiuni:
1. Latimea –width- se utilizeaza la scriere si are rolul de a stabili numarul de
caractere utilizate pentru afisarea unei date.
2. Precizia –precision- se utilizeaza la scriere si se foloseste pentru a stabili
numarul de zecimale care vor fi afisate pentru o valoare reala.
3. Caracterul de umplere –fill- se utilizeaza la scriere, in cazul in care data
propriu-zisa ocupa mai putini octeti decat latimea, si precizeaza caracterul care se
afiseaza in spatiile neocupate de data.
4. Alinierea se utilizeaza in cazul in care data propriu-zisa ocupa mai putin decat
latimea si precizeaza unde anume sa fie afisata data, la stanga –left- sau la dreapta –
right-.
5. Salt sau nu peste caracterele albe.

In limbajul C++ exista biblioteca iostream.h in care sunt definite cateva


variabile si constante cu rol important in efectuarea de citiri / scrieri cu
format.Variabilele sunt:
int x_precision;
int x_width;
int x_fill;
long x_flags;

 x_precision retine numarul de zecimale ce vor fi afisate pentru un numar real;


 x_width retine numarul de pozitii pe care se efectueaza afisarea
(latimea).Valoarea este retinuta pana cand se efectueaza prima afisare,dupa care
variabila este initializata cu 0;
 x_fill retine caracterul care va fi afisat in cazul in care numarul pozitiilor pe
care se face afisarea este mai mare decat cel al pozitiilor efectiv ocupate de date;
 x_flags retine anumiti parametri pentru formatarea intrarilor / iesirilor, care
sunt memorati la nivel de bit si vor fi analizati in amanunt.Pentru a usura accesul la
bit s-au definit constantele:
 skipws=0x0001 - caracterele albe care preced valoarea care trebuie
citita sunt sarite;
 left=0x0002 - datele se tiparesc aliniate la stanga;
 right=0x0004 - datele se tiparesc aliniate la dreapta;
 internal=0x0008 - se foloseste la tiparirea semnului unui
numar sau a specificatorului de baza;
 dec=0x0010 - conversie in zecimal;
 oct=0x0020 - conversie in octal;
 hex=0x0040 - conversie in hexazecimal;
 showbase=0x0080 - afisarea indicatorului de baza;
 showpoint=0x0100 -forteaza afisarea punctului zecimal;
 uppercase=0x0200 - in cazul afisarii in hexa, se vor utiliza litere mari;
 showpos=0x0400 - valorile numerice sunt afisate precedate de semn;
 scientific=0x0800 - afisarea valorilor reale se face prin utilizarea formei
stiintifice (1e - 8);
 fixed=0x1000 - afisarea valorilor reale se face prin utilizarea formei
normale (-12.45).
Accesul propriu-zis la variabilele respective se face cu ajutorul unor functii
speciale, numite manipulatori.Pentru utilizarea acestora se include fisierul antet
iomanip.h.

Exemple de manipulatori:

 setw(int) – stabileste latimea;


 setprecision(int) – stabileste precizia (numarul de zecimale)
 setfill(char) – stabileste caracterul de umplere
 setiosflags(masca) – seteaza bitii din masca
 resetiosflags(masca) – reseteaza bitii din masca

Observatii:

1. De regula, utilizarea manipulatorilor are efect numai asupra primei citiri / scrieri,
chiar daca operatia recpectiva este scrisa in cadrul aceleiasi expresii.Una dintre
exceptiile de la aceasta regula este saltul peste caracterele albe.
2. In cazul in care latimea nu este suficienta, ea este ignorata.
3. Daca numarul de zecimale nu este precizat prin manipulator, atunci se tiparesc cel
mult 6 zecimale (daca valoarea respectiva are 6 zecimale).
4. In cazul in care nu a fost specificat caracterul de umplere, acesta este
blank-ul.
5. Conversia bazelor 8, 10, 16 se poate realiza simplificat prin utilizarea
manipulatorilor oct dec hex.

2.4. Fisiere text memorate pe suport magnetic

Toate operatiile de citire / scriere efectuate asupra fisierelor cu numele logic


cin / cout pot fi efectuate si asupra fisierelor text memorate pe suport magnetic.
Pentru a putea fi prelucrat, orice fisiere are doua nume:
- un nume logic, folosit in program pentru referirea fisierului;
- un nume fizic, sub care fisierul se afla memorat pe suportul extern
In C++, pentru a putea lucra usor asupra fisierelor sunt definite anumite
constante, dupa cum urmeaza:
 in =0x01 – fisierul se deschide pentru citire;
 out =0x02 – fisierul se deschide pentru scriere;
 ate =0x04 – dupa deschidere, salt la sfarsitul fisierului;
 app =0x08 – deschidere pentru a scrie la sfarsitul fisierului;
 trunc =0x10 – daca fisierul care se deschide exista, in locul sau se creeaza
altul
 nocreate =0x20 – deschide fisierul doar daca acesta exista (nu este permisa
crearea lui)
 noreplace =0x40 – daca fisierul exista, el poate fi deschis doar pentru consultare
 binary =0x80 – fisier binar – se utilizeaza constructorul ofstream(), apoi se
utilizeaza metoda open, in forma generala.

Orice program care lucreaza cu fisiere pe suport magnetic trebuie sa includa


fisierul antet fstream.h.In concluzie, se va scrie:
#include <fstream.h>
Includerea acestui fisier face inutila includerea fisierului iostream.h.

2.4.1. Declararea fisierelor text memorate pe


suport magnetic

Forma generala a unei declaratii de fisier text memorat pe suport magnetic


este:
fstream nume_logic(char*nume_fizic,int mod_de_deschidere);
Modul de deschidere (pentru citire, scriere etc) se descrie cu ajutorul
constantelor prezentate anterior si operator.

Observatii:

1. Calea se declara cu doua caractere backslash (\\).Caracterul respectiv este folosit


pentru a scrie secvente escape si, din acest motiv, pentru a fi inclus
intr-un sir de caractere este necesar sa fie scris de doua ori.
2. In anumite cazuri, numele fizic al fisierului trebuie citit de la tastatura.Astfel,
avem posibilitatea ca programul sa prelucreze fisiere cu orice nume fizic.In acest caz,
declaratia fisierului trebuie sa contina adresa vectorului de numere si sa fie plasata
dupa citirea sirului respectiv.

Exemplu:

char nume[20];
cout<<”numele fisierului “;cin>>nume;
fstream f(nume,ios: :out);

In astfel de cazuri, atunci cand se introduce calea, este necesar sa scriem un


singur backslash.
Dupa prelucrare, fisierul trebuie inchis.Inchiderea unui fisier se face printr-o
functie speciala close( ), fara parametri, dar precedata de numele fisierului si punct.

2.4.2. Prelucrarea fisierelor text

In general, prelucrarea unui fisier se face dupa urmatoarea schema:

while (daca nu este sfarsit de fisier)


{
citeste
prelucreaza
}

Pentru detectarea sfarsitului de fisier, C++ contine o functie specializata,


numita eof( ).Pentru a preciza fisierul al carui sfarsit se testeaza, functia trebuie
precedata de numele logic al fisierului si punct.In cazul detectarii sfarsitului de fisier,
functia returneaza o valoare diferita de 0, altfel returneaza 0.
Exemple:

1.Programul urmator creeaza un fisier text cu intrarea de la tastatura.


Caracterele citite se sfarsesc prin EOF (CTRL+Z).Nu sunt scrise caracterele albe
introduse de la tastatura.

#include <fstream.h>
main ( )
{
fstream f(“c:\\fis.txt”,ios: :out);
char ch;
while (cin>>ch) f<<ch;
f.close( );
}

2.La fel ca mai sus, numai ca nu sunt sarite caracterele albe citite de la
tastatura.

#include <fstream.h>
#include <iomanip.h>
main ( )
{
fstream f(“c:\\fis.txt”,ios: :out);
char ch;
while (cin>>resetiosflags(ios: :skipws)>>ch) f<<ch;
f.close( );
}

3.Afisez pe monitor fisierul creat la exemplul 2, fara caracterele albe.

#include <fstream.h>
main ( )
{
fstream f(“c:\\fis.txt”,ios: :in);
char ch;
while (f>>ch) cout<<ch;
f.close( );
}

4.Afisez pe monitor fisierul creat la exemplul 2, cu toate caracterele, inclusiv


cele albe.

#include <fstream.h>
#include <iomanip.h>
main ( )
{
fstream f(“c:\\fis.txt”,ios: :in);
char ch;
while (f>>resetiosflags(ios: :skipws)>>ch) cout<<ch;
f.close( );
}

5.Scriu la sfarsitul fisierului creat la exemplul 2 alte caractere, inclusiv cele


albe citite de la tastatura.

#include <fstream.h>
#include <iomanip.h>
main ( )
{
fstream f(“c:\\fis.txt”,ios: :app);
char ch;
while (cin>>resetiosflags(ios: :skipws)>>ch) f<<ch;
f.close( );
}

6.Continutul fisierului “fis.txt” este copiat in fisierul “fis1.txt”.Sunt copiate si


caracterele albe.

#include <fstream.h>
#include <iomanip.h>
main ( )
{
fstream f(“c:\\fis.txt”,ios: :in),g(“c:\\fis1.txt”,ios: :out);
char ch;
while (f>>resetiosflags(ios: :skipws)>>ch) g<<ch;
f.close( );
}

7.Programul de mai jos creeaza un fisier text cu primele 100 de numere


naturale.Acestea sunt memorate in hexa.Fiecare numar este scris pe o linie, ocupa 5
caractere si este aliniat dreapta (implicit).

#include <fstream.h>
#include <iomanip.h>
main ( )
{
fstream f(“c:\\numere.in”,ios: :out);
int i;
for (i=1;i<=100;i++)
f<<setw(5)<<hex<<i<<endl;
f.close( );
}

8.Programul urmator listeaza pe monitor fisierul creat la exemplul 7.


Citirea se face in hexa, dar listarea se face in baza 10.

#include <fstream.h>
#include <iomanip.h>
main ( )
{
fstream f(“c:\\numere.in”,ios: :in);
int i;
while (f>>hex>>i)cout<<dec<<i<<endl;
f.close( );
}

9.Programul citeste de la tastatura un sir de caractere.El este alcatuit din mai


multe cuvinte separate prin blank-uri.Sirul se sfarseste prin EOF (CTRL+Z)
Sunt afisate cuvintele citite.

#include <fstream.h>
#include <iomanip.h>
main ( )
{
fstream f(“c:\\numere.in”,ios: :in);
char cuvant[20];
while (!cin.eof( ))
{
cin>>cuvant;
cout<<cuvant<<endl;
}
}

10.In cazul in care se deschide un fisier care exista pentru creare, vechiul fisier
este distrus.In acest fel, din neatentie, se pot pierde date importante.Pentru a evita
acest lucru, se recurge la un artificiu, ca in programul urmator:

#include <fstream.h>
#include <iomanip.h>
main ( )
{
fstream f(“c:\\fis.txt”,ios: :in),g(“c:\\fis1.txt”,ios: :out);
char ch;
if (f)
{
cout<<”fisierul exista”;
f.close( );
return 0;
}
while (cin>>resetiosflags(ios: :skipws)>>ch) g<<ch;
f.close( );
}

11.In programul urmator se citeste primul cuvant al unui text memorat intr-un
fisier text.Acesta este afisat, apoi, pointerul este pozitionat la inceputul fisierului.In
fisier se suprascriu primele 4 caractere ale fisierului cu sirul “tata”.

#include <fstream.h>
#include <string.h>
main ( );
{
fstream f(“c:\\fis.txt”,ios: :in | ios: :out);
char cuvant[10];
f>>cuvant;
cout<<cuvant;
f.seekp(0,ios: :beg);
strcpy(cuvant, “tata”);
f<<cuvant;
f.close( );
}

12.Programul urmator citeste un fisier text alcatuit din mai multe linii – unele
pot fi si vide.El afiseaza liniile fisierului citit.Observati ca si in cazul fisierelor putem
folosi fara probleme functia get.

#include <fstream.h>
main ( )
{
fstream f(“c:\\fis.txt”,ios: :in);
char linie [100];
while (f.get(linie,100))
{
cout<<linie<<endl;
f.get( );
}
}

Exista mai multe functii cu ajutorul carora programatorul poate actiona asupra
pointerului dintr-un fisier de intrare:
 Functia long tellp( ) returneaza pozitia pointerului la un moment dat.Pozitia este
relativa la inceputul fisierului.
 Functia seekp(long, seek_dir) pozitioneaza pointerul.Primul parametru
reprezinta pozitia, iar al doilea reperul in raport de care este calculata pozitia.
In acest sens, au fost definite 3 constante.Pentru accesul la ele se foloseste si de
aceasta data ios:
 beg – inceput de fisier;
 cur – pozitia curenta in fisier;
 end – sfarsit de fisier.
Cand stabilim o pozitie aflata in stanga reperului, primul parametru este
negativ, iar in dreapta reperului, parametrul este pozitiv.

13.Fiind dat un fisier text fis.txt si doua numere naturale i<j, se cere sa se
listeze caracterele fisierului text dintre i si j (inclusiv) in alt fisier text numit fis1.txt.

#include <fstream.h>
main ( )
{
fstream f(“c:\\fis.txt”,ios: :in);
int i,j;
char ch;
cout<<”i=”;cin>>i;
cout<<”j=”;cin>>j;
/ / pozitionez pointerul direct pe octetul i
f.seekp(i,ios: :beg);
/ / atat timp cat pointerul este mai mic sau egal cu j
while (f.tellp( )<=j)
{
f>>ch;
cout<<ch;
}
f.close( );
}

2.5.Aplicatii cu fisiere text

1.Se da un fisier text alcatuit din mai multe cuvinte.se cere sa se afiseze toate
pozitiile de inceput ale tuturor aparitiilor unui anumit cuvant citit de la tastatura.
Rezolvare.Algoritmul consta in a citi fisierul, cuvant cu cuvant, pana la
sfarsitul sau.In cazul in care acesta coincide cu cuvantul citit de la tastatura, este
afisata pozitia in care se gaseste.Tinand cont ca dupa citirea unui cuvant, pointerul
este plasat pe primul caracter alb care ii urmeaza, pozitia se poate afla cu tellp( ), iar
daca din aceasta valoare scadem lungimea cuvantului, aflam pozitia de inceput a
acestuia.

#include <fstream.h>
#include <string.h>
main( )
{
fstream f(“c:\\fis.txt”,ios: :in);
char cuvant[20],cuvant1[20];
cout<<”dati cuvantul “;cin>>cuvant;
while (f>>cuvant1 && !f.eof( ))
if (!strcmp(cuvant,cuvant1))
cout<<f.tellp( )-strlen(cuvant)<<endl;
}

2.Se dau de la tastatura numele unui fisier text si un cuvant (succesiune de


caractere care nu contine caractere albe).Se cere sa se creeze un alt fisier text care sa
cuprinda informatiile din primul fisier, dar in care au fost sterse toate aparitiile
cuvantului dat.
Rezolvare.Se vor citi cuvintele prin salt peste caracterele albe.Se va retine
pozitia de unde incepe citirea in pv si pozitia primului caracter alb de dupa cuvantul
citit in pn.
Dupa citirea unui cuvant, acesta fie nu coincide cu cel citit de la tastatura si nu
trebuie eliminat, fie trebuie eliminat.
Observatii:

1) Orice citire se face incepand cu pozitia curenta a pointerului.Pozitionarea


pointerului in fisier se face cu seekp( ).
2) Dupa citirea ultimului cuvant, citirea se blocheaza, pentru ca a fost intalnit
EOF.Pentru a debloca citirea, se foloseste o functie speciala, numita clear( ).
3) De fiecare data cand citim, trebuie precizat modul de efectuare a citirii, cu sau fara
salt peste caracterele albe, deoarece acesta se mosteneste pentru citirile ulterioare.

#include <fstream.h>
#include <string.h>
#include <iomanip.h>
main( )
{
fstream f(“c:\\fis.txt”,ios: :in);
g(“c:\\fis1.txt”,ios: :out);
char cuvant[20],cuvant1[20],ch;
long pv=0,pn,lung,semnal=0;
cout<<”dati cuvantul “;cin>>cuvant;
lung=strlen(cuvant);
while (!f.eof( ))
{
f>>setiosflags(ios: :skipws)>>cuvant1;
pn=f.tellp( );
f.seekp(pv,ios: :beg);
if (f.eof( )) { semnal=1;f.clear( ); };
if (strcmp(cuvant,cuvant1))
while (f.tellp( )<pn)
{
f>>resetiosflags(ios: :skipws)>>ch;
g<<ch;
}
else
{ while (f.tellp( )<pn-lung)
{
f>>resetiosflags(ios: :skipws)>>ch;
g<<ch;
}
f.seekp(pn,ios: :beg);
}
pv=pn;
if (semnal) f.clear(1);
}
f.close( );
g.close( );
}
3.Se da un fisier text si doua cuvinte (suucesiune de caractere care sa nu
contina caractere albe).Se cere sa se creeze un alt fisier text care sa cuprinda
informatiile din primul fisier, dar in care toate aparitiile primului cuvant sunt inlocuite
cu al doilea cuvant.

#include <fstream.h>
#include <string.h>
#include <iomanip.h>
main( )
{
fstream f(“c:\\fis.txt”,ios: :in);
g(“c:\\fis1.txt”,ios: :out);
char cuvant[20],cuvant1[20],cuvanta[20],ch;
long pv=0,pn,lung,semnal=0;
cout<<”dati cuvantul care se sterge“;cin>>cuvant;
cout<<”dati cuvantul care se adauga“;cin>>cuvanta;
lung=strlen(cuvant);
while (!f.eof( ))
{
f>>setiosflags(ios: :skipws)>>cuvant1;
pn=f.tellp( );
f.seekp(pv,ios: :beg);
if (f.eof( )) { semnal=1;f.clear( ); };
if (strcmp(cuvant,cuvant1))
while (f.tellp( )<pn)
{
f>>resetiosflags(ios: :skipws)>>ch;
g<<ch;
}
else
{ while (f.tellp( )<pn-lung)
{
f>>resetiosflags(ios: :skipws)>>ch;
g<<ch;
}
g<<cuvanta;
f.seekp(pn,ios: :beg);
}
pv=pn;
if (semnal) f.clear(1);
}
f.close( );
g.close( );
}

4.Sa se genereze un fisier text cu n numere naturale de tip int.Numarul n este


citit de la tastatura.Numerele vor fi generate aleator.

#include <fstream.h>
#include <stdlib.h>
main( )
{
char nume[20];
cout<<”numele fisierului “;cin>>nume;
fstream f(nume,ios: :out);
long n,i;
cout<<”n=”;cin>>n;
f<<n<<endl;
randomize( );
for(i=0;i<n;i++) f<<rand( )<<’ ‘;
f.close( );
}

5.Sa se sorteze crescator numerele din fisierul text anterior creat.Datele sortate
vor fi scrise in alt fisier text.

#include <fstream.h>
int v[20000],n,i,man,gasit;
main( )
{
char nume1[20],nume2[20];
cout<<”numele fisierului de intrare “;cin>>nume1;
cout<<”numele fisierului de iesire “;cin>>nume2;
fstream f(nume1,ios: :in);
g(nume2,ios: :out);
/ /citesc datele
f>>n;
for(i=0;i<n;i++) f>>v[i];
/ /sortez
do
{
gasit=0;
for(i=0;i<n-1;i++)
if (v[i]>v[i+1])
{
man=v[i];
v[i]=v[i+1];
v[i+1]=man;
gasit=1;
}
} while (gasit);
/ /tiparesc
for(i=0;i<n;i++) g<<v[i]<<’ ‘;
}

6.Sa se interclaseze datele aflate in doua fisiere text.rezultatul va fi scris in alt


fisier text.

#include <fstream.h>
main( )
{
char int1[20],int2[20],ies[20];
long a,b,i,j,m,n,flag;
cout<<”primul fisier “;cin>>int1;
cout<<”al doilea fisier “;cin>>int2;
cout<<”iesirea “;cin>>ies;
fstream f(int1,ios: :in);
g(int2,ios: :in);
h(ies,ios: :out);
i=j=1;
f>>m>>a; g>>n>>b;
h<<n+m<<endl;

while (i<=m && j<=n)


if (a<=b)
{
h<<a<<’ ‘;
f>>a;
i++;
flag=1;
}
else
{
h<<b<<’ ‘;
g>>b;
j++;
flag=2;
}
if (flag==1) h<<b<<’ ‘;
else h<<a;
while (f>>a) h<<a<<’ ‘;
while (g>>b)h<<b<<’ ‘;
f.close( );
g.close( );
h.close( );
}

7.Programul urmator “sparge“ un fisier text cu numere generate aleator, in alte


doua fisiere text.El poate fi folosit cu succes in sortarea unui numar foarte mare de
date intregi.

#include <fstream.h>
main( )
{
char intr[20],ies1[20],ies2[20];
long m,n,i,j,man;
cout<<”fisierul de intrare “;cin>>intr;
cout<<”prima iesire “;cin>>ies1;
cout<<”a doua iesire “;cin>>ies2;
fstream f(intr,ios: :in);
g(ies1,ios: :out);
h(ies2,ios: :out);
f>>n;
m=n/2;
n=-m;
g<<m<<endl;
for(i=0;i<m;i++)
{
f>>man;
g<<man<<’ ‘;
}
h<<n<<endl;
for(i=0;i<n;i++)
{
f>>man;
h<<man<<’ ‘;
}
f.close( );
g.close( );
h.close( );
}

3.Fisiere binare

Caracteristicile fisierelor binare sunt:

 Fisierele binare sunt alcatuite din mai multe inregistrari de acelasi tip.
Din acest motiv, aceste fisiere se mai numesc si cu tip.
Exemple:
 inregistrarile sunt de tip int;
 inregistrarile sunt de un tip descris cu struct.
 Datele sunt memorate in format intern.De exemplu, daca inregistrarea este de tip
int, datele sunt memorate in cod complementar.
 Fisierele binare se termina cu EOF, ca si cele text.

Observatie:Tot ce s-a studiat la fisierele text ramane valabil si pentru fisierele binare!

1.Sa se creeze un fisier binar cu n inregistrari de tip struct, in care fiecare


inregistrare retine numele unei persoane si varsta sa.Datele de intrare se preiau de la
tastatura.

#include <fstream.h>
struct persoana
{
char nume[30];
int varsta;
};
main( )
{
fstream f(“pers.dat”,ios: :out | ios: :binary);
persoana p,adr_p=&p;
char x;
int n,i;
cout<<”Care este numarul de persoane “;cin>>n;
cin.get( );
for(i=0;i<n;i++)
{
cout<<”Numele “;cin.get(p.nume,30);cin.get(x);
cout<<”Varsta “;cin>>p.varsta;cin.get(x);
f.write((char*)adr_p,sizeof p);
}
f.close( );
}

Observatii:

 Intrucat lucram cu un fisier binar, il deschidem cu parametrul respectiv, binary.


 Am descris structura corespunzatoare si am declarat variabila p, de acelasi tip.
 Variabila de tip pointer adr_p este initializata cu adresa variabilei p.
 Dupa citirea variabilei p urmeaza scrierea acesteia in fisier.Pentru aceasta, se
foloseste functia write, care scrie n caractere ale unui sir si nu insereaza caracterul
nul.Ea are forma:
write (char*nume,int n);
 “Pacalim” calculatorul sa scrie, de fapt, continutul varabilei p.Adresa de inceput a
variabilei p o convertim catre un sir de caractere prin utilizarea operatorului de
conversie explicita.Numarul de caractere care trebuie scrise este dat de lungimea
variabilei p.In acest moment, calculatorul “crede” ca scrie un sir de caractere.Daca
e sir de caractere, nu este cazul sa faca conversiile necesare, dar el preia
informatia in format intern si o scrie asa!

2.Sa se listeze fisierul creat anterior.

#include <fstream.h>
struct persoana
{
char nume[30];
int varsta;
};
main( )
{
persoana p, *adr_p=&p;
fstream g(“pers.dat”,ios: :in | ios: :binary);
while (g.read((char*)adr_p,sizeof(p)))
cout<<p.nume<<” “<<p.varsta<<endl;
g.close( );
}
3.Sa se scrie un program care adauga la sfarsitul fisierului creat la exemplul 1,
alte n inregistrari.

#include <fstream.h>
struct persoana
{
char nume[30];
int varsta;
};
main( )
{
fstream f(“pers.dat”,ios: :app | ios: :binary);
persoana p,adr_p=&p;
char x;
int n,i;
cout<<”Care este numarul de persoane “;cin>>n;
cin.get( );
for(i=0;i<n;i++)
{
cout<<”Numele “;cin.get(p.nume,30);cin.get(x);
cout<<”Varsta “;cin>>p.varsta;cin.get(x);
f.write((char*)adr_p,sizeof p);
}
f.close( );
}

4.Sa se scrie un program care modifica varsta unei persoane din fisierul creat
la exemplul 1.
Rezolvare.Mai intai deschidem fisierul ca pe unul de intrare / iesire.Se citeste
indicele inregistrarii (intre 1 si n), apoi pozitionam pointerul cu seekp( ) pe
inregistrarea de indice dat, tinand cont ca ele sunt numerotate de la 0 la n-1.
Citim inregistrarea, citim noua varsta de la tastatura, repozitionam pointerul pentru ca
prin citirea inregistrarii acesta se pozitioneaza pe urmatoarea inregistrare, apoi
rescriem inregistrarea modificata.

#include <fstream.h>
struct persoana
{
char nume[30];
int varsta;
};
main( )
{
int i;
cout<<”care inregistrare o modific (1..n) ?”;cin>>i;
persoana p,*adr_p=&p;
fstream g(“pers.dat”,ios::in | ios::out | ios: :binary);
g.seekp((i-1)*sizeof(p),ios::beg);
g.read((char*)adr_p,sizeof(p));
cout<<”varsta “;cin>>p.varsta;
g.seekp((i-1)*sizeof(p),ios: :beg);
g.write((char*)adr_p,sizeof(p));
g.close( );
}

In prelucrarea fisierelor binare se folosesc urmatorii termeni:

 Creare – initial fisierul nu exista, dar dupa executia programului, acesta se va gasi
pe suport.Intrarile sunt de la tastatura sau din alte fisiere.
 Exploatare – utilizarea fisierului pentru a extrage informatii din el.In urma
exploatarii, fisierul ramane nemodificat.
 Actualizare – aducerea la zi a fisierului.Aceasta se face in trei feluri:

1) stergere – se sterg anumite inregistrari ale sale.In C++ nu exisat functii


care permit ca aceasta operatiune sa se faca direct.Din acest motiv, in
practica sunt folosite doua metode:

a) stergere fizica – se creeaza alt fisier cu inregistrarile din primul


fisier care nu se sterg.Apoi primul fisier se sterge, iar cel proaspat
creat este “rebotezat” cu numele celui care a fost sters.
b) stergere logica – fiecare inregistrare are un camp care precizeaza
daca ea a fost stearsa logic sau nu.Toate programele de prelucrare
tin cont de ea, in sensul ca daca a fost stearsa logic, inregistrarea nu
mai intra in prelucrare.Pentru a nu retine date inutile, din cand in
cand, inregistrarile sterse logic sunt sterse fizic.
2) modificare – in urma prelucrarii unei inregistrari (sau mai multor
inregistrari), se modifica anumite campuri.
3) adaugare – se adauga la sfarsitul fisierului alte inregistrari.

CLASA a-X-a
Capitolul I

Pointeri

1.1 Noţiunea de adresă a unei variabile


Am învăţat faptul că memoria internă poate fi privită ca o
succesiune de octeţi. Pentru a-i distinge, aceştia sunt numerotaţi. Numărul
de ordine al unui octet se numeşte adresa lui.

Orice variabilă ocupă un număr succesiv de octeţi. De exemplu, o


variabilă de tip int ocupă doi octeţi (în varianta Borland C++ 3.0).
Adresa primului octet al variabilei se numeşte adresa variabilei.

Observaţii:

 Nu trebuie confundată adresa variabilei cu valoarea pe care aceasta o


memorează.

 Uneori, în loc de adresă a unei variabile vom folosi termenul pointer!

 În unele cazuri, adresa unei variabile se scrie sub o formă mai


complicată: segment : deplasare. Acest fapt este neesenţial pentru
înţelegerea noţiunilor prezentate în acest manual.

1.2 Variabile de tip pointer


Variabilele de tip pointer se caracterizează prin faptul că valorile
pe care le pot memora sunt adresa ale unor variabile.

Limbajul C++ face distincţie între natura adreselor care pot fi


memorate. Astfel, există adrese ale variabilelor de tip int, adrese ale
variabilelor de tip float, adrese ale variabilelor de tip char, etc.

 O astfel de variabilă - capabilă să reţină adrese – se declară astfel:


Tip *nume

Exemple:

 Variabile de tip pointer către variabile de tip int. Variabile adr1,


adr2, pot reţine adrese ale variabilelor de tip int;

Int *adr1, *adr2;

 Variabilă de tip pointer către variabilă de tip float. Variabila adresa,


poate reţine adrese ale variabilelor de tip float.

Float* adresa;

 Variabile de tip pointer către variabile de tip elev, care la timpul lor
sunt de tipul struct. Variabilele a şi b, pot reţine adrese ale
variabilelor de tip elev.

Struct elev
{
char nume [20], prenume [20];
float nota_mate, nota_info;
int vârsta;
};

elev *a, *b;

 Caracterul “*” poate fi plasat în mai multe feluri, după cum se observă:

int* adr1;
int * adr1;
int *adr1;

 Pentru a declara mai multe variabile de acest tip, caracterul “*” se trece
de fiecare dată: int *adr1, *adr2, *adr3;

 O declaraţie de genul: int* adr1, adr2; are semnificaţia că adr1 este


de tip pointer către int, în vreme ce adr2este de tip int.

 Adresa unei variabile se obţine cu ajutorul operatorului de referinţe


“&”, care trebuie să preceadă numele variabilei:
&Nume_variabilă;

Exemplu:
Adr:=&număr Variabilei adr1 i se atribuie adresa variabilei număr.

 Fiind dată o variabilă de tip pointer către variabile de un anume tip,


care memorează o adresă a unei variabile de acel tip, pentru a obţine
conţinutul variabilei a cărei adresă este memorată se utilizează
operatorul unar “*”, numit şi operator de diferenţiere.

Exemple:

‫ ٱ‬Programul următor declară o variabilă de tip int numită a şi o variabilă


de tip int*, numită adra, şi o variabilă de tipul int** numită adradra.
Variabila a este iniţializată cu 7, iar variabila adra este iniţializată cu
adresa lui a, iar variabila adradra este iniţializată cu adresa variabilei
adra. Programul afişează conţinutul variabilei a (7) pornind de la dresa
variabilei adra (reţinută de adradra )

#include <iostream.h>
main ( )
{ int a=7, *adr=&a, **adradra=&adr;
cout <<*adr<<” “<<**adradra;
}

Adresa Adresa
7 Variabilei Variabilei
a adra

a adra adradra

‫ٱ‬ În programul următor variabila a, de tip elev, este iniţializată, iar


variabila adra, de tip pointer către variabile de tip elev este iniţializată cu
adresa variabilei a. programul tipăreşte de două ori conţinutul variabilei
a.

# include <iostream.h>
include <string.h>

struct elev
{
char nume [20] , prenume [20];
};

main ( )

{
elev a, *adra=&a;
strcpy(a.nume, “Bojian”);
strcpy(a.prenume, “Andronache”);
cout<<(*adra):nume<<” “<<(*adra).prenume<<end;
cout<<adra->nume<<” “<<adra->prenume;
}

 Observaţi modul în care am obţinut conţinutul unui câmp al variabilei


a, pornind de la pointerul către a: (*adra) . nume. De ce este nevoie de
paranteze?
Operatorul “.” –numit operator de selecţie, are prioritatea 1, deci maximă.
Operatorul “*” –unar, numit şi operator de dereferenţiere, are prioritatea
2, mai mică. Prin urmare, în absenţa parantezelor rotunde, se încearcă mai
întâi evaluarea expresiei adra.nume, expresie care n-are sens!
Parantezele schimbă ordinea de evaluare, se evaluează mai întâi *adra,
expresie care are sens.

 Pentru o astfel de selecţie, în loc să folosim trei operatori, se poate


utiliza unul singur, operatorul de selecţie indirectă: “->”. Acesta
accesează un câmp al unei structuri pornind de la un pointer (adresă)
către acea structură. El are prioritate maximă –vezi tabelul
operatorilor. Astfel, în loc să scriem (*adra).nume, vom scrie, mai
simplu, adra ->nume.

‫ ٱ‬În programul următor, variabilei a i se atribuie valoarea 3. Variabilei


adr i se atribuie adresa variabilei a. Variabila a este incrementată,
pornind de la conţinutul variabilei adr. Se afişează de două ori conţinutul
variabilei a, pornind de la adresa reţinută de adr şi de la numele ei.

# include <iostream.h>
main ( )
{ int a=3, *adr=&a;
++*adr;
cout<<*adr<<” “<<a;
}
Observaţii:

 Între variabilele de tip pointer sunt permise atribuiri doar în cazul în


care au acelaşi tip pointer (reţin adrese către acelaşi tip de variabile).

Exemplu:

Int *adr, *adr2;


Float *adr3;

Atribuirea adr1=adr2 este corectă;


Atribuirea adr3=adr2 nu este corectă.

În aceste condiţii vă puteţi întreba: cum putem atribui conţinutul


unei variabile de tip pointer către tipul y? În definitiv, amândouă reţin o
adresă…

În acest caz se utilizează operatorul de conversie explicită. De


această dată, pentru exemplul anterior, atribuirea: adr3=(float*)adr2 este
corectă.

‫ ٱ‬programul următor tipăreşte conţinutul primului octet al unei variabile


de tip int.

# include <iostream.h>
main ( )
{ int a=3, *adr_int=&a;
char adr_char=(char*)adr_int;
cout<<(int)*adr_char;
}

Variabilei adr_char i-am atribuit adresa variabilei de tip întreg.


Pentru afişare, am folosit din nou operatorul de conversie explicită
(pentru ca data să nu fie afişată ca un caracter).

Mai elegant, aceeaşi secvenţă poate fi scrisă şi aşa:

{ int a=3;
char adr_char=(char*)&a;
cout<<(int)*adr_char;
}

 Reţineţi: octeţii unei variabile numerice sunt reţinuţi în memorie în


ordine inversă. Astfel, octetul cel mai puţin semnificativ este primul
memorat.

Acesta este motivul pentru care programul anterior tipăreşte 3, deşi


acesta este conţinutul octetului cel mai puţin semnificativ!
În C++ există şi tipul către variabile de orice tip, numit void*. Unei
variabile de tip void* îi putem atribui conţinutul oricărei variabile de tip
pointer, dar invers nu este posibil. Exemple:

În condiţiile:

Int a=3, *adr_int=&a;


Void * adr_gen;

Atribuirea: adr_gen=adr_int; este corectă;


Atribuirea: adr_int=adr_gen; nu este corectă.

‫ ٱ‬Programul următor afişează în binar conţinutul primului octet al unei


variabile de tip int.

# include <iostream.h>
main ( )
{
int a=5, j;
char adr_byte=(char*)&a, masca=128;
for (j=0; j<8; j++)
{
if (*adr_byte & masca)
cout<<1;
else cout<<0;
*adr_byte= *adr_byte<<1; // sau *adr_byte<<=1;
}
}

O variabilă, numită masca, reţine 128, adică 10000000 în binar. Ea


foloseşte pentru efectuarea intersecţiei logice între conţinutul octetului şi
masca. Dacă bitul aflat pe prima poziţie este 0, programul va tipări 0, iar
dacă acelaşi bit este 1, va tipări 1. După fiecare intersecţie logică, întreg
conţinutul octetului care trebuie afişat se deplasează către stânga, pentru a
putea afişa conţinutul următorului bit.

1.3 Operaţii cu variabile de tip pointer


Fie o variabilă a, de tip pointer către tipul x şi n un număr natural.
Atunci au sens operaţiile a+n şi a-n.
a+n reprezintă adresa care se obţine ca diferenţă între adresa lui a şi n
înmulţit cu numărul de octeţi care sunt ocupaţi de o variabilă de tipul x.
a-n reprezintă adresa care se obţine ca diferenţă între adresa lui a şi n
înmulţit cu numărul de octeţi care sunt ocupaţi de variabila de tipul x.

Exemplu: dacă a este o variabilă de tip int şi tipul int ocupă doi octeţi,
atunci a+3 reprezintă adresa octetului care se obţine ca sumă între adresa
primului octet al variabilei a şi 6.

În particular, conţinutul unei variabile de tip pointer poate fi


incrementat, decrementat (se adună respectiv se scade 1).

□ Programul următor listează în binar conţinutul unei variabile de tip int,


numită a.

# include <iostream.h>
main ( )
{
int a=257, i , j;
char *adr_byte=(char *)&a, masca;
for (i=0; i<sizeof(a); i++)
{
masca=128;
for (j=0; j<8; j++)
{ if (*adr_byte & masca) cout<<1;
else cout<<0;
masca>>=1;
}
adr_byte++;
}
}

Iniţial, adr_byte reţine adresa primului octet. Conţinutul acestuia


este afişat în binar. Apoi, se obţine adresa următorului octet, în adr_byte.
Întrucât adr_byte este de tip pointer către char, iar tipul char ocupă un
octet, prin faptul că se adună 1 la adr_byte, se obţine adresa următorului
octet.

1.4 Din nou despre tablouri


Până în prezent am lucrat cu vectori şi matrice (tablouri cu una sau
două dimensiuni). Pentru ei am utilizat o notaţie apropriată de
matematică. De exemplu, un vector cu 10 componente de tip float se
declară ca float a[10].
 În C++ numele unui masiv (tablou) este pointer. Vom analiza în
amănunt acest fapt mai întâi pentru vectori şi apoi pentru tablouri p-
dimensionale.

1. Vectori

 Numele vectorului este un pointer constant (nu poate fi modificat)


către tipul de bază al vectorului.

■ În secvenţa următoare am declarat un vector a, cu 10 componente.


Compilatorul rezervă spaţiu în memoria internă pentru cele 10
componente ale vectorului, iar a reţine adresa primei componente a
vectorului, ca pointer către tipul float. Variabila b este de tip pointer către
float. Componenta de indice 7 a vectorului este iniţializată cu 3. Prin
atribuirea b=a, b va reţine pointerul a. De acum, componentele vectorului
pot fi adresate pornind de la b, ca în exemplu, unde se tipăreşte 7.
Atribuirea a=b nu este corectă, pentru că a nu se poate modifica.

Float a[10], *b;


A[7]=3; b=a; cout<<[7];

 Dacă numele vectorului este un pointer către tipul de bază, rezultă că


putem adresa componentele acestuia şi altfel, utilizând cunoştinţele
dobândite în paragraful anterior.

■ În condiţiile de mai sus expresiile tipăresc aceeaşi valoare 3, care este


conţinutul componentei de indice 7. În ambele cazuri se face suma între
un pointer către float şi valoarea 7. Tipul float ocupă 4 octeţi. Deci, de
fiecare dată, se adună la dresa de început a vectorului 7x4, obţinând
adresa componentei de indice 7.

cout<<*(a+7)<<end1;
cout<<*(7+a)<<end2;
 Dacă, în cazul vectorului, adresările a[7] şi *(a+7), *(7+a) sunt
echivalente, înseamnă că parantezele drepte reprezintă, de fapt, un
operator, numit operator de tip array, care face suma între un pointer
şi o constantă. Astfel, în cazul vectorilor avem:

a[n]=*(a+n)=*(n+a)=n[a]

Dacă aţi citit bine, componenta de indice n a unui vector a, poate fi


adresată prin n[a]. De exemplu, pe componenta de indice 3 o putem
obţine prin 3[a]!. Suma între un pointer şi un număr natural este
comutativă!

 Poate că v-aţi mirat de faptul că prima componentă a unui vector are


indicele 0. Normal ar fi fost să aibă indicele 1. Dar, aşa cum am arătat,
numele vectorului este un pointer către prima componentă. Prin urmare,
prima componentă se adresează prin b[0].

 Revedeţi modul de memorare a şirurilor de caractere. Acum înţelegeţi


de ce acestea au tipul char*. Şirurile se memorează sub formă de vectori
de caractere. Tipul de bază este char, iar vectorii au tipul char*.

 Tot acum înţelegeţi de ce dacă a şi b sunt vectori, atribuirea a=b nu


este corectă! Ea trebuie făcută pe componente. Atât a cât şi b sunt
pointeri către tipul de bază. În plus, sunt constanţi, deci nu pot fi
modificaţi.

2. Tablouri p-dimensionale

În cazul tablourilor acţionează acelaşi mecanism, care de această


dată este generalizat.

 Un tablou p-dimensional se declară astfel:

tip nume [n₁] [n₂] … [np]

Exemple:
■ float A[7] [4] [2]; am declarat un tablou cu 3 dimensiuni, unde tipul de
bază este float.

■ long b[9] [7] [8] [5]; am declarat un tablou cu 4 dimensiuni, unde


tipul de bază este long.

 Numele tabloului p dimensional de mai sus este pointer constant către


un tablou p-1 dimensional de forma [n2] …[np], care are
componentele de bază de acelaşi tip cu cele ale tabloului. Exemplele
se referă la tablourile anterior prezentate.

■ A este un pointer constant către tablouri cu [4] [2] componente de tip


float.

■ b este un pointer constant către tablouri cu [7] [8] [5] componente de


tip long.

 Un pointer către un tablou k dimensional cu [1₁] [1₂] …[1k]


componente de un anumit tip se declară astfel:

tip (*nume) [1₁] [1₂] …[1k]

■ O variabilă de tip pointer, numită p, care poate reţine pe A se declară:

float (*p) [4] [2]

■ O variabilă de tip pointer, numită q, care poate reţine pe b se declară:

long (*q) [7] [8] [5]

 De ce se folosesc parantezele rotunde? Operatorul de tip array ([]) are


cea mai mare prioritate (mai mare decât operatorul *). Declaraţia:

float *p[7] [8] [5];

este interpretată ca masiv cu [7] [8] [5] componente de tip float*. Deci
este masiv care are componente de tip pointer şi nu pointer către masive.
Atenţie! Aici ce fac multe confuzii!
Exemplu:

#include <iostream.h>
main( )
{
float G[7] [8] [3], (*A) [8] [3], (*B)[3], *C;
G[2] [3] [1] = 15;
// A este un pointer către masive cu [8] [3] componente de tip float
// şi poate reţine pointerul G

A=G; cout<=A[2] [3] [1]<<end;

// B este pointer către masive cu [3] componente şi poate reţine un


submasiv al lui G

B=G[2]; cout<<B[3] [1]<<end1;

// C este pointer către float*;


C=G[2] [3]; cout<<C[1]<<end1;
}

 În adresarea masivelor se folosesc expresii indiciate. O espresie


indiciată are forma de mai jos, unde nume reprezintă numele unui
masiv de dimensiune mai mare sau egală cu p, iar exp₁…expp sunt
expresii de tip întreg:

nume [exp₁]…[expp];

 O expresie indiciată se evaluează prin intermediul operatorului de tip


array ‘[]’ astfel:

Pasul 1. Se evaluează e₁=nume+exp₁;


Pasul 2. Se evaluează e₂=e₁+exp₂;

Pasul p. Se evaluează ep=ep-1+expp;

Exemplu: Fie declaraţia float M [2] [7] [9] [6]; Presupunem că efectuăm
atribuirea: M [1] [5] [3] [2]=9.5. Numele masivului este M şi reprezintă
un pointer către masive cu [7] [9] [6] elemente de tip float, mai precis de
tipul: float (*) [7] [9] [6].

Pasul 1. Se calculează e₁=M+1.

Ţinând cont de modul în care se efectuează operaţiile cu pointeri se


adună spaţiul ocupat de un submasiv tridimensional adică: 7x9x6x4
(ultimul factor reprezintă numărul de octeţi necesari tipului float).
Valoarea e₁ are tipul float (*) [9] [6], deci este pointer către masive
bidimensionale.

Pasul 2. Se calculează e₂=e₁+5. De această dată se adună 5x9x6x4, iar e₂


are tipul float (*) [6], pointer către masive unidimensionale;

Pasul 3. Se calculează e₃=e₂+3. De această dată se adună 3x6x4, iar e₂


are tipul float *.

Pasul 4. Se calculează e₄=e₃+2. De această dată se adună 2x4, iar e₂ are


tipul float, adică tipul de bază.

 Ca să vă daţi seama mai bine de acţiunea acestui operator, priviţi cum


adresăm o variabilă, pornind de la un pointer către ea!

#include <iostream.h>
main ( )
{ int a=7, *b=&a;
cout <<b[0]<<end1;
}

 Elementele unei expresii indicate pot fi adresate şi prin utilizarea


operatorului de dereferenţiere ‘*’. Exemplu: M[1] [5] [3] [2] poate fi
obţinută şi altfel:

*(*(*(*(M+1)+5)+3)+2)

 Operatorul ‘*’ are rolul de a schimba tipul expresiei rezultat,


simplificându-l. Diferenţa între el şi cel de tip array este dată de faptul că
are doar rolul de a schimba tipul expresiei, nu şi acela de a efectua
calcule.
 Operatorul de referenţiere ‘&’ are efectul invers operatorului de
dereferenţiere.

În continuare, vom da mai multe exemple, pentru a vă familiariza


cu pointerii!

1. Fie declaraţiile: float a[3],b[3],c; a[3] este vector cu trei


componente, float *a[3] este vector cu trei componente de tip float*
(adică fiecare componentă reţine adrese către float). Atunci expresiile
următoare sunt corecte:

■ a[1]=b - b este pointer către float;

■ a[0]=&c - &c este adresă (pointer) către float.

2. Fie declaraţiile: float (*a) [3], c[3], d[7] [3]; a este pointer, float (*a)
[3] este pointer către vector cu 3 componente de tip float. Atunci
expresiile următoare sunt corecte:

■ a=d – d este pointer către vectori cu 3 componente de tip float.

■ a=&c – c este vector cu 3 componente de tip float, &c este pointer


către vector cu 3 componente de tip float.
3. Fie declaraţiile: float a[3] [2], c[3], d[7] [3], e; a[3] [2] este masiv,
float a[3] [2] este masiv cu elemente de tip float* (deci poate reţine
pointeri către float). Atunci expresiile următoare sunt corecte:

■ a [0] [0]=&e - &e este adresă către float;

■ a [1] [1]=c – c este vector cu 3 componente de tip float, deci este


pointer către float;

■ a [1] [1]=&c[0]; - c[0] este de tip float, &c[0] este pointer către float;

■ a [1] [1]=&d [6] [2]; d [6] [2] este de tip float; &d [6] [2] este de tip
pointer către float.

■ a [1] [1]=d [3]; - d este pointer către vectori cu 3 componente de tip


float, d [3] este de tipul float.

4. Fie declaraţiile: int (*a[3] ) [2] [5], b [2] [5], c[9] [2] [5]; a[3] este
vector, *a[3] este vector care au componente de tip pointer, (*a[3] ) [2]
[5] este vector care au componente de tip pointer către masive cu [2] [5]
componente de tip int. Atunci expresiile următoare sunt corecte:

■ a[0]=&b – b este masiv cu 2 linii şi 5 coloane de tip int, &b este


pointer către masiv cu 2 linii şi 5 coloane de tip int.

■ a[1]=c – c este pointer către masive cu 2 linii şi 5 coloane.

5. Fie declaraţiile: int *(*a) [3], *b[9] [3]; *a este pointer, (*a) [3] este
pointer către vectori cu 3 componente de tip *int. Atunci expresiile
următoare sunt corecte:

■ a=b; b este masiv cu 9 linii şi 3 coloane de tip int*.

■ a=&b[8]. [8] este pointer către vectori cu 3 componente care reţin


date de tip int*, &b[8] este adresă către masiv cu 9 linii şi 3 coloane cu
componente de tip int*.

1.5 Tipul referinţă


În C++ există un tip special care permite ca o variabilă să fie
“botezată” cu mai multe nume, numit tip referinţă. Aparent, programul
lucrează cu mai multe variabile, dar în fapt se lucrează cu una singură. O
astfel de declaraţie se face ca mai jos:

tip&nume variabilă = valoare de iniţializare

Analizaţi programul următor. Variabila b este o referinţă pentru


variabila a. Ele au aceeaşi valoare şi aceeaşi adresă, sunt diferite prin
faptul că poartă nume diferite!

#include <iostream.h>
main ( )
{ int a=7; int&b=a;
cout<<”variabila a are conţinutul “<<a<<” şi adresa “<<&a<<end1;
cout<<”variabila b are conţinutul “<<b<<” şi adresa “<<&b<<end1;
}

 Mai uşor, declaraţia se putea face şi aşa: int a=7, &b=a;

 Poate vă întrebaţi la ce folosesc variabilele de acest tip? Rolul lor este


uriaş în transmiterea parametrilor pentru subprograme.
Capitolul II

Subprograme

2.1 Noţiunea de subprogram


Prin subprogram vom înţelege un ansamblu alcătuit din tipuri de
date, variabile şi instrucţiunii scrise în vederea unei anumite prelucrări
(calcule, citiri, scrieri) şi care poate fi utilizat (rulat) doar dacă este
apelat de un program sau de alt subprogram.

Până în prezent am fost doar utilizatori de subprograme. Exemple


de subprograme folosite:

■ matematice: sin, cos, exp, log.

■ de prelucrare a şirurilor de caractere: strlen, strcpy, strncat.

■ chiar citirile şi scrierile se fac sub controlul unor subprograme speciale.

În acest capitol învăţăm să le creăm. Pentru a înţelege noţiunea de


subprogram, vom porni de la două exemple:

□ Se citeşte n, număr natural. Să se scrie programele care tipăresc


valoarea calculată a expresiilor:

E1=1+1/2+1/3+1/4+…+1/n

E2=(1+1/2+1/3+1/4+…+1/n)ⁿ

Ce observăm? A doua expresie, se poate obţine din prima. Relaţia


este:

E₂=E₁ⁿ
Cerinţa este de a scrie programe separate. Cum procedăm? Prin
utilizarea cunoştinţelor dobândite până în prezent, scriem cele două
programe separat, iar în al doilea program, secvenţa care calculează prima
expresie, se copiază din primul.

Oare nu se poate lucra mai eficient? Răspunsul este afirmativ. Se


scrie un program care calculează E₁ şi care este apelat de ambele
programe. Primul program va tipări valoarea transmisă de subprogram, al
doilea va ridica la putere această valoare şi va afişa rezultatul.

□ Se citeşte n, număr natural. Se citeşte un vector cu n componente


numere reale. Se cere să se tipărească vectorul sortat.

Desigur, ştim să rezolvăm această problemă şi clasic, în mai multe


feluri, pentru că am studiat mai mulţi algoritmi de sortare. De această dată
vom rezolva problema prin utilizarea subprogramelor.

Vom scrie un subprogram care citeşte un vector, unul care tipăreşte


un vector şi un al treilea care sortează vectorul, după una din metodele
cunoscute.

În acest caz, programul ar arăta astfel:

Pasul 1 apelează subprogramul care citeşte vectorul;


Pasul 2 apelează subprogramul care sortează vectorul;
Pasul 3 apelează subprogramul care tipăreşte vectorul.

Faţă de scrierea clasică, aici problema a fost descompusă în altele


mai mici (citire, sortare, tipărire). În general, o problemă complexă se
rezolvă mai uşor dacă o descompunem în altele mai simple. Apoi, şansele
de a greşi la scrierea unui subprogram sunt cu mult mai mici decât acelea
de a greşi la scrierea unui program mare. Acesta din urmă rezultă din
“asamblarea” subprogramelor la care se adaugă, eventual, câteva linii
scrise în programul principal. Putem acum enumera avantajele utilizării
subprogramelor:

■ reutilizarea codului –o dată scris, un subprogram poate fi


utilizat de mai multe programe;

■ elaborarea algoritmilor prin descompunerea problemei în altele mai


simple. În acest fel, rezolvăm cu mult mai uşor problema;
■ reducerea numărului de erori car pot apărea la scrierea programelor;

■ depistarea cu uşurinţă a erorilor –verificăm la început subprogramele,


apoi modul în care le-am asamblat (le-am apelat din cadrul programului).

În C++, subprogramele sunt de tip funcţie.

2.2 Exemple de utilizare a funcţiilor


Revenim la exemplele date în 2.1. Acestea vor fi prezentate în
totalitate. De asemenea, ne vom familiariza cu terminologia utilizată
pentru subprograme, urmând ca toate noţiunile să fie descrise riguros.

□ Se citeşte n, număr natural. Să se scrie programele care tipăresc


valoarea calculată a expresiilor:

E1=1+1/2+1/3+1/4+…+1/n

E2=(1+1/2+1/3+1/4+…+1/n)ⁿ

În continuare prezentăm cele două programe care utilizează funcţia


în C++, apoi în Pascal:

#include <iostream.h>
double subp(int n)
{
double s=0; int i;
for (i=1; i<=n; i++) s+=1./i;
return s;
}
main ( )
{ int n;
cout<<”n=”; cin>>n;
cout<<subp(n);
}
#include <iostream.h>
double subp(int n)
{
double s=0; int i;
for (i=1; i<=n; i++) s+=1./i; // sau s=s=1. /i
return s;
}

main ( )
{
int n,i;
double rez,prod=1;
cout<<”n=”; cin>>n;
rez=subp(n);
for (i=1; i<=n; i++) prod*=rez;
cout<<prod;
}

var n:integer;
function subp(ninteger):real;
var i:integer; s:real;
begin
s:=0;
for i:=1 to n do
s:=s+1/i;
subp:=s;
end;
begin
read(n);
write*subp(n));
end.

var n,i:integer;
prod,rez:real;
function subp(ninteger):real;
var i:integer; s:real;
begin
s:=0;
for i:=1 to n do
s:=s+1/i;
subp:=s;
end;
begin
read(n);
prod:=1;
rez:=subp(n);
for i:=1 to n do
prod:=prod*rez;
write(prod);
end.

Funcţia care calculează E₁ este:

Double subp(int n)
{
double s=0; int i;
for (i=1; i<=n; i++) s+=1./i; // sau s=s=1. /i
return s;
}

● Antetul funcţiei este: double subp(int n)

● Funcţia se numeşte: subp.

● Funcţia are un parametru numit n. Rolul său este important şi anume


precizează pentru ce valoare trebuie calculată expresia. Aşa cum vom
vedea, există posibilitatea să avem mei mulţi parametri, de diferite tipuri;

● Funcţia are variabile proprii – adică variabile care sunt gefinite în


cadrul ei. În exemplu, s şi i. Aceste variabile se numesc variabile locale.

● Am văzut că funcţia întoarce un anumit rezultat – în exemplu, de tip


double. Observaţi mecanismul prin acre am obţinut aceasta. Calculez
expresia în mod obişnuit. Rezultatul este reţinut de variabila locală s.
Prin instrucţiunea return s; funcţia a primit ca valoare de retur
conţinutul variabilei s.

În terminologia utilizată în teoria subprogramelor – în particular, în


cazul funcţiilor – se utilizează termenii parametrii formali şi parametri
efectivi.

 Parametrii care se găsesc în antetul funcţiei se numesc parametrii


formali.

Atunci când scriem o funcţie nu cunoaştem valoarea propriu-zisă a


parametrilor. Funcţia trebuie să întoarcă rezultatul corect, oricare ar fi
valoarea lor. Din acest punct de vedere ei se numesc formali.

 Parametrii care se utilizează la apel se numesc parametrii afectivi.


La apel, lucrurile stau altfel: valorile acestora sunt cunoscute. Prin
urmare aceştia se cunosc parametrii efectivi.

Pentru apelul rez=subp(n); parametrul afectiv este n.

□ Se citeşte n, număr natural. Se citeşte un vector cu n componente


numere reale. Se cere să se tipărească vectorul sortat.

#include <iostream.h>
void citesc(int vt[10], int n)
{
int i;
for (i=0; i<=n; i++)
{ cout<<”v[“<<i+1<<”]=”; cin>>vt[i];
}

void sortez(int vt[10], int n)


{
int gasit,i,man;
do
{
gasit=0;
for (i=0; i<n-1; i++)
if (vt[i]>vt[i+1])
{ man=vt[i]; vt[i]=vt[i+1]; vt[i+1]=man; gasit=1; }
}
while (gasit);
}

void scriu(int vt[10], int n)


{
int i;
for (i=0; i<n; i++)cout<<vt[i]<<end1;
}

main ( )

{
int v[10], n;
cout<<”n=”; cin>>n;
citesc(v,n);
sortez(v,n);
scriu(v,n);
}

Type vect=array[1..10] of integer;


var n:integer;
v:vect;
procedure citire(vt:vect; n:integer);
var i:integer;
begin
for i:=1 to n do begin
writeln(‘v[‘,i,’]’);
readln(v[i]);
end; end;
procedure sortez(vt:vect; n:integer);
var gasit:boolean;
man:boolean;
i:integer;
begin
repeat
gasit:=false;
for i:=1 to n-1 do
if vt[i]>vt[i+1] then
begin
man:=vt[i];
vt[i]:=vt[i+1];
vt[i+1]:=man;
gasit:=true; end;
until not gasit;
end;
procedure scriu(vt:vect; n:integer);
var i:integer;
begin
read(n);
citesc(v,n);
sortez(v,n);
scriu(v,n);
end.

Programul conţine trei funcţii: citesc, sortez, tipăresc.


● La apel, controlul programului este transferat la prima instrucţiune a
funcţiei. După execuţia funcţiei, se revine în programul principal la
prima instrucţiune care urmează celei de apel – în cazul de faţă se
întâlneşte o altă instrucţiune de apel.

 Cele trei funcţii au tipul void, adică nu au valoare de retur. Ele


returnează rezultatul prin intermediul parametrilor.
2.3 Structura funcţiilor şi apelul lor

2.3.1 Generalităţi

În esenţă, o funcţie este alcătuită din:

 Antet – acesta conţine mai multe informaţii importante necesare


compilatorului, numele funcţiei, lista parametrilor formali, tipul
rezultatului.

Structura antetului este:

tip nume(lista parametrilor formali)

Lista parametrilor formali este de forma:

parametru₁, parametru₂,…,parametrun

Există şi posibilitatea ca lista parametrilor formali să fie vidă.


Fiecare parametru are forma:

tip nume

 O instrucţiune compusă – aceasta cuprinde declaraţiile variabilelor


locale, şi instrucţiunile propriu-zise.

 Poate fi tip al unei funcţii, orice tip de dată cu excepţia masivelor.

 Dacă ţinem neapărat, există posibilitatea ca funcţia să întoarcă masive,


dacă acestea sunt înglobate în tipuri declarate cu struct, ca în exemplul
următor, unde funcţia citeşte un masiv bidimensional pe care-l întoarce
prin tipul ei:

#include <iostream.h>
struct mat
{
float matrice [6] [8];
};

mat cit(int m, int n)

{
mat a;
int i,j;
for (i=0; i<m; i++)
for(j=0; j<n; j++) cit>>a.matrice [i] [j];
return a;
};
main ( );
{ int i,j;
mat mtr=cit(3,2);
for (i=0; i<3; i++)
for (j=0; j<2; j++) cout<<mtr.matrice [i] [j] <<” “;
}

type mat=array[1..6,1..8] of real;


var i,j:integer;
procedure cit(m,n:integer; var a:mat);
var i,j:integer;
begin
for i=1 to m do
for j:=1 to n do
read(a[i,j]);
end;
begin
cit(3,2);
for i:=1 to 3 do
for j:=1 to 2 do
write(a[i,j]);
end.
Exemple de antet:

- int suma(int a, int b) – funcţia se numeşte suma, returnează un


rezultat de tip int şi are doi parametrii formali de tip int, numiţi a şi b.

- void t(int n, float v[20]) – funcţia se numeşte t, este de tip void (nu
returnează rezultat prin nume), are doi parametrii formali, primul
numit n, de tip int, al doilea numit v, de tip float*.
- char* sir(int n, char a[200]) – funcţia se numeşte şir, întoarce un
pointer către un şir de caractere şi are doi parametrii formali, unul de
tip int, numit n şi altul de tip char*, numit a.

- elev mana(int n, char a[200]) – funcţie de tip elev, unde elev este
de tip struct;

struct elev
{
char nume[20];
int varsta;
};

 O funcţie returnează rezultatul la întâlnirea instrucţiunii return, care


are forma:

return expresie;

Trebuie ca tipul expresiei să coincidă cu tipul funcţiei.

La întâlnirea instrucţiunii return, după atribuirea valorii, execuţia


funcţiei se încheie şi se revine la funcţia care a apelat-o. În absenţa
instrucţiunii return, execuţia funcţiei se încheie după execuţia ultimei
instrucţiuni. În acest caz nu se întoarce nici o valoare.

 O funcţie poate fi apelată de sine stătător (prin nume şi lista


parametrilor efectivi), dar poate fi inclusă şi în cadrul expresiilor, caz
în care, la evaluarea expresiei este apelată. Această ultimă formă de
apel nu este valabilă în cadrul funcţiilor de tip void.

#include <iostream.h> În programul alăturat este apelată


o funcţie care calculează produsul
int prod (int x, int y) a două numere întregi.
{ return x*y;}

main ( ) Programul tipăreşte suma între 1


{ şi produsul calculat. În exemplu,
int x=2, y=3; 7.
cout<<1+prod(x,y);
}

Apelul funcţiei s-a realizat din interiorul expresiei: 1+prod(x,y)


Observaţii:

 În cadrul expresiei, apelul este un operand. El intră în calcul cu valoarea


returnată de funcţie.

 După apelul funcţiei se continuă evaluarea expresiei.

 La apel, ordinea de evaluare a parametrilor nu este definită. De


exemplu, dacă apelăm funcţia test astfel:

test (2-3, 2+3)

nu ştim dacă înainte se efectuează scăderea sau adunarea.

2.3.2 Declararea variabilelor

2.3.2.1. Noţiuni generale

Până în prezent am declarat variabile doar în corpul funcţiilor –


inclusiv în cel al funcţiei main ( ). Variabilele declarate astfel se numesc
locale.

 Sistemul de operare alocă fiecărui program trei zone distincte în


memoria internă în care se găsesc memorate variabilele programului.

Segment de date
Segment de stivă
Heap
De asemenea, există posibilitatea ca variabilele să fie memorate
într-un anumit registru al microprocesorului. În acest caz timpul de acces
la astfel de variabile este foarte mic, deci se pot obţine programe
optimizate.

 În general o variabilă se caracterizează prin 4 atribute. Acestea sunt:

1. Clasa de memorare.
2. Vizibilitate.
3. Durata de viaţă.
4. Tipul variabilei, singurul pe care l-am studiat până în prezent.
1. Clasa de memorare – precizează locul unde este memorată variabila
respectivă. O variabilă poate fi memorată în segmentul de date, în cel
de stivă, în heap sau într-un registru al microprocesorului.

2. Vizibilitatea – precizează liniile textului sursă din care variabila


respectivă poate fi accesată. Astfel avem:

a. Vizibilitate la nivel de bloc (instrucţiune compusă);

b. Vizibilitatea la nivel de fişier – în cazul în care programul ocupă


un singur fişier sursă, singurul caz pe care îl tratăm acum.

c. Vizibilitatea la nivel de clasă – este în legătură cu programarea


pe obiecte, pe care o veţi studia în anul următor.

3. Durata de viaţă – reprezintă timpul în care variabila respectivă are


alocat spaţiu în memoria internă. Astfel avem:

a. Durata statică – variabila are alocat spaţiu în tot timpul execuţiei


programului.

b. Durata locală - variabila are alocat spaţiu în timpul în care se


execută instrucţiunile blocului respectiv.

c. Durata dinamică – alocarea şi dezalocarea spaţiului necesar


variabilei respective se face de către programator prin operatori
sau funcţii speciale.

În C++ variabilele pot fi împărţite în 3 mari categorii: locale,


globale şi dinamice. Variabilele locale şi globale sunt prezentate în acest
capitol, iar cele dinamice în capitolul următor.

2.3.2.2. Variabile globale

Acestea se declară în afara corpului oricărei funcţii, ca în exemplul


următor:

#include <iostream.h>
int a;

int t( )
{
a=3;
cout<<a;
}

int b;

main ( )
{
b=4;
cout<<a<<end1;
t( );
}

Variabilele a şi b sunt globale. În astfel de cazuri, variabilele


respective pot fi utilizate de toate funcţiile care urmează în textul sursă
declaraţiei variabilei respective. Din acest motiv astfel de variabile se
numesc globale.

 La declarare, variabilele globale sunt iniţializate cu 0.

Atributele variabilelor globale sunt:

1. Clasa de memorare – segmentul de date.

2. Vizibilitatea – În cazul în care declaraţiile acestora sunt înaintea tuturor


funcţiilor, acestea sunt vizibile la nivelul întregului program (fişier). Dacă
anumite funcţii se află plasate înaintea declaraţiilor acestor variabile,
atunci ele sunt vizibile doar pentru funcţiile care sunt plasate după aceste
declaraţii. În exemplul anterior, variabile a poate fi accesată din corpul
oricărei funcţii, dar variabila b poate fi accesată doar din funcţia main( ).

2. Durata de viaţă a variabilelor globale este statică. Ele au spaţiu


rezervat în tot timpul execuţiei programului.

2.3.2.3. Variabile locale

Acestea sunt declarate în corpul funcţiilor. Mai precis, pot fi


declarate în orice bloc (instrucţiune compusă) al acestora.

 Variabilele declarate în corpul funcţiei main( ) sunt tot locale. În


programul următor variabilele a şi b sunt locale.
Variabila a este declarată în corpul funcţiei t( ), iar variabila b este
declarată în corpul funcţiei main( ).

void t( )
{ int a=3;}

main( )
{ int b=4;}

1. Clasa de memorare a variabilelor locale este, implicit, segmentul de


stivă. Există posibilitatea ca acestea să fie alocate în registrele
microprocesorului, caz în care declaraţia lor trebuie precedată de cuvântul
cheie register.
Exemplu: register int b=4;

 Variabilele locale nu sunt iniţializate implicit cu 0. În ipoteza în care


acestea nu sunt iniţializate explicit de programator, reţin o valoare
oarecare, numită valoare reziduală.

2. Vizibilitatea variabilelor locale este la nivelul blocului la care au fost


declarate.

În funcţia următoare am declarat două variabile de tip int, numite b


şi c. Variabila b este vizibilă la nivelul funcţiei, dar variabila c este
vizibilă doar la nivelul blocului în care a fost declarată.

void t( )
{
int b=4;
{ int c=3;
cout<<b<<” “<<c;
}
}

În programul următor am declarat trei variabile , toate numite a.


Una este globală, iar două sunt locale, dar declarate în blocuri diferite.

#include <iostream.h>
int a;

voit t( )
{ int a=4;
{ int a=3;
cout<<a<<end1;
}
cout<<a<<end1;
}

main ( )
{
a=5; t( );
cout<<a;
}

var a:integer;
procedure t;
var a:integer;
begin
a:=4;
write(a);
end;
begin
a:=3;
write(a);
t;
end.

În program se afişează conţinutul tuturor acestor variabile (3,4,5).

 În cazul în care, într-un anumit bloc sunt vizibile (se pot accesa) mai
multe variabile, toate au acelaşi nume, dar au domenii de vizibilitate
diferite, se accesează variabila cu vizibilitatea cea mai mică. De exemplu,
dacă în programul anterior se tipăreşte variabila a din cadrul subblocului
funcţiei, se tipăreşte 3, pentru că acesta este conţinutul variabilei cu cea
mai mică vizibilitate (cea declarată în subblocul respectiv).

 Există posibilitatea ca, un ciclu for să conţină declaraţia unei variabile


locale. În secvenţa următoare se calculează suma primelor 4 numere
naturale. Variabila i este declarată (şi în consecinţă vizibilă) doar în
blocul for.

int n=4, s=0;


for (int i=1; i<=n; i++) s+=i;
cout<<s;

3. Durata de viaţă a variabilelor locale este atât timp cât durează execuţia
blocului respectiv.

2.3.3. Transmiterea parametrilor

Aşa cum am arătat, parametrii care se găsesc în antetul funcţiei se


numesc parametrii formali, iar cei care se găsesc în instrucţiunea de apel
se numesc parametrii efectivi.

Priviţi programul următor. Acesta conţine o funcţie care calculează


suma a două numere naturale.

#include <iostream.h> Parametrii formali sunt a şi b.


Funcţia este apelată de mai multe
int suma(int a, int b) ori. Parametrii efectivi sunt, pe
{ return a+b;} rând:
main( )
{
int c=4, d=3;
cout<<suma(2,3)<<end1; -2,3 ;
cout<<suma(2+7,3-1*2)<<end1; -2+7, 3-1*2;
cout<<suma(c,d)<<end1; -c, d;
cout<<suma(1.9, 3.3)<<end1; -1.9, 3.3;
}

După fiecare apel al funcţiei se tipăreşte suma obţinută. Între


parametrii formali şi cei efectivi trebuie să existe o anumită concordanţă,
care este descrisă prin regulile următoare:

 Numărul parametrilor formali trebuie să coincidă cu numărul


parametrilor efectivi. La această regulă există o excepţie care va fi
prezentată într-un paragraf separat. În exemplul dat, numărul
parametrilor formali şi efectivi este tot 2 (pentru un apel).
 Tipul parametrilor formali trebuie să coincidă cu tipul parametrilor
efectivi sau tipul parametrilor efectivi să poată fi convertit implicit
către tipul parametrilor formali la fel ca în cazul atribuirii.

- suma (2,3) – parametrii formali sunt expresii constante de tip întreg;


- suma (2+7, 3-1*2) – parametrii formali sunt expresii constante de tip
întreg. În acest caz, înainte de apel se evaluează expresiile de mai sus.

- suma (c,d) – parametrii formali sunt expresii de tip întreg (dat de tipul
variabilelor).

- suma (1.9, 3.3) – parametrii formali sunt expresii constante de tip real.
În acest caz ele sunt convertite către int la fel ca în cazul atribuirii (se
trunchiază zecimala). Prin urmare, suma calculată este: 4=1+3.

Observaţie: Nu este obligatoriu ca numele parametrilor formali să


coincidă cu numele parametrilor efectivi.

Vom analiza modul în care sunt memoraţi parametrii transmişi, în


momentul lansării în execuţie a funcţiei.

 Pentru memorarea parametrilor subprogramele folosesc segmentul de


stivă, întocmai ca pentru variabilele locale.

 Memorarea parametrilor transmişi se face în ordinea în care aceştia


figurează în antet: de la stânga la dreapta.

 În cadrul subprogramului, parametrii transmişi şi memoraţi în stivă


sunt variabile. Numele lor este cel din lista parametrilor formali.

 Variabilele obţinute în urma memorării parametrilor transmişi sunt


variabile locale.

Iată, de exemplu, cum sunt memoraţi în stivă parametrii în cazul


primului apel:

stiva  2 3

a b

Observaţii:

 La revenirea în blocul apelant, conţinutul variabilelor memorate în stivă


se pierde – după cum ştim, durata de viaţă a variabilelor locale este
locală.
 Memorarea în stivă are şi alte consecinţe, dar acestea vor fi tratate la
momentul potrivit.

Există două mecanisme de transmitere a parametrilor, transmiterea


prin valoare şi transmiterea prin referinţă. Acestea vor fi tratate în
continuare:

A. Transmiterea prin valoare se utilizează atunci când suntem interesaţi


ca subprogramul să lucreze cu acea valoare, dar, în prelucrare, nu ne
interesează ca parametrul efectiv (cel din blocul apelant) să reţină
valoarea modificată în subprogram.

Se pot transmite prin valoare:

1. Valorile reţinute de variabile.


2. Expresii (acestea pot conţine şi funcţii).

1. Valorile reţinute de variabile. În acest caz, parametrii efectivi trebuie să


fie numele variabilelor. Exemplu:

#include <iostream.h>

void test (int n)


{ n+=1; cout<<n<<end1;}

main( )
{ int n=1;
test (n);
cout<<n<<end1;
}

Parametrul n este transmis prin valoare.

 În main( ), avem declarată variabila n, care este iniţializată cu 1.

 Apelăm funcţia. La apel, se rezervă spaţiu în stivă, spaţiu care are


numele parametrului (deci tot n) şi este iniţializat cu valoarea memorată
de variabila n a programului principal. În acest moment avem două
variabile n şi ambele reţin valoarea 1.
 În funcţie, variabila n este incrementată (adică la vechiul conţinut se
adaugă 1). Evident, este vorba de variabila rezervată în cadrul ei.

 Tipărim conţinutul variabilei n (cea din stivă), deci se tipăreşte 2.

 La ieşirea din funcţie, variabila n (din stivă) se pierde – adică nu mai are
spaţiu alocat. Prin urmare, valoarea 2 este pierdută.

 În main( ) se tipăreşte conţinutul variabilei n, adică 1.

3. Parametrii efectivi sunt expresii, care mai întâi se evaluează. Exemplu:

#include <iostream.h>

void test(int n)
{ cout<<n<<end1;}

main( )
{ test(3);
test(3+4*5);
}

procedure test(n:integer);
begin
write(n);
end;
begin
test(3);
test(3+4*5);
end.

În funcţie se creează o variabilă, reţinută în segmentul de stivă,


numită n, care la primul apel reţine valoarea 3 şi la al doilea valoarea 23.
La ieşirea din funcţie conţinutul acestei variabile se pierde.

Aşa cum am arătat, transmiterea parametrilor prin valoare se


utilizează atunci când nu ne interesează ca, la întoarcerea din subprogram,
parametrul efectiv să reţină valoarea modificată acolo. Întrebare: dacă n-
ar exista decât transmiterea prin valoare, ar fi posibil să modificăm
valoarea anumitor variabile care sunt declarate în blocul aparent?
Răspunsul este afirmativ, dacă lucrăm cu variabile de tip pointer.
Funcţia intersc interschimbă valorile de două variabile ale programului
principal. Ea primeşte ca parametrii adresele celor două variabile.

#include <iostream.h>
void intersc (int *x, int *y)
{ int man;
man=*x; *x=*y; *y=man;
}

main( )
{ int a=2; b=3;
intersc(&a,&b);
cout<<a<<” “<<b;
}

Reţineţi acest mecanism! El este general! Ştiaţi că în limbajul C++


acesta este singurul mecanism de transmitere a parametrilor?

 Transmiterea prin valoare a masivelor permite ca funcţiile să întoarcă


noile valori ale acestora (care au fost atribuite în funcţii). De ce? Aşa
cum am învăţat, numele masivului este un pointer către componentele
lui. Prin valoare se transmite acest nume. Cu ajutorul acestuia accesăm
componentele masivului.

În programul următor funcţia vector iniţializează vectorul transmis ca


parametru, iar în main( ) se afişează rezultatul.

#include <iostream.h>

void vector (int x[10] )


{
for (int i=0; i<10; i++) x[i]=1;
}

main( )

{
int a[10];
vector(a);
for (int i=0; i<10; i++) cout<<a[i]<<” “;
}
type vect=array[1..10] of integer;
var a:vect;
i:integer;
procedure vector(x:vect);
var i:integer;
begin
for i:=1 to n do
x[i]:=i;
end;
begin
vector(a);
for i:=1 to 10 do
writeln(a[i]);
end.

B. Transmiterea prin referinţă. Parametrii sunt transmişi prin referinţă


atunci când de interesează ca la revenirea din subprogram variabila
transmisă să reţină valoarea stabilită în timpul execuţiei
subprogramului.

 În cazul transmiterii prin referinţă, parametrii efectivi trebuie să fie


referinţe la variabile (vezi tipul referinţă prezentat în capitolul
anterior).

 În cazul transmiterii prin referinţă subprogramul reţine în stivă, adresa


variabilei.

În acest caz ne putem întreba care este mecanismul prin care, deşi
pentru o variabilă transmisă se reţine adresa ei, în subprogram putem
adresa variabila normal (nu indirect)? La compilare, orice referinţă la
variabila respectivă, este “tradusă” ca adresare indirectă.

Programul următor utilizează o funcţie care interschimbă valorile


reţinute de două variabile. Acestea sunt transmise prin referinţă.

#include <iostream.h>
void intersc(int &a, int &b)
{ int man=a; a=b; b=man;}
main( )
{ int x=2; y=3;
intersc(x,y);
cout<<x<<” “<<y;
}
var x,y:integer;
procedure intersc(var a,b:integer);
var man:integer;
begin
man:=a;
a:=b;
b:=man;
end;
begin
x:=2;
y:=3;
intersc(x,y);
writeln(x,y);
end.

2.4 Definirea şi declararea unui


subprogram
Deşi aparent asemănătoare, cele două noţiuni diferă. Buna
înţelegere a lor ne ajută să evităm anumite erori. Mai mult, aceste noţiuni
sunt utilizate de orice limbaj de programare, nu numai în C++.

□ A defini un subprogram, înseamnă a-l scrie efectiv, după structura


anterior prezentată. O problemă importantă este locul unde se defineşte
subprogramul.

□ A declara un subprogram, înseamnă a-l anunţa. Un subprogram


nedeclarat nu poate fi folosit. Definiţia unui subprogram ţine loc şi de
declaraţie!

Programul următor conţine două funcţii: s1 şi s2. Definiţiile


ambelor funcţii se găsesc înaintea funcţiei main( ). Din acest motiv ele
pot fi apelate din main( ). Definiţia funcţiei s1 este înaintea definiţiei lui
s2, deci funcţia s1 poate fi apelată din s2. În schimb, din s1 nu poate fi
apelată funcţia s2, pentru că definiţia lui s2 este după cea a lui s1.
#include <iostream.h>

void s1( )
{ cout<<”Eu sunt s1”<<end1;}

void s2( )
{ s1( )
cout<<”Eu sunt s2”<<end1;
}

main( )
{ s1( ); s2( );}

procedure s1;
begin
writeln(‘1’);
end;
procedure s2;
begin
writeln(‘2’);
end;
begin
s1;
s2;
end.

În situaţia precedentă, se poate, totuşi, ca s1 să apeleze pe s2, chiar


dacă sunt definite în aceeaşi ordine.

În astfel de cazuri se foloseşte prototipul funcţiei (antetul urmat de


‘;’). Prototipul are rolul de a declara o funcţie. El nu conţine definiţia
acesteia.

#include <iostream.h>
void s2( );

void s1( )
{ s2( ); cout<<”Eu sunt s1”<<end1;}

void s2( )
{ cout<<”Eu sunt s2”<<end1;}
main( )
{ s1( );}

procedure s2;
forward;
procedure s1;
begin
s2;
writeln(‘1’);
end;
procedure s2;
begin
writeln(‘2’);
end;
begin
s1;
end.

2.5 Aplicaţii care folosesc subprograme


□ Se citesc două numere întregi m şi n. Se cere să se tipărească cel mai
mare divizor comun şi cel mai mic multiplu comun al lor.

Rezolvare. Un algoritm prin care se determină cmmdc pentru două


numere a fost prezentat în manualul de anul trecut, motiv pentru care nu
revenim asupra sa. Cmmmc a două numere se poate determina împărţind
produsul lor la cmmdc al lor. Prin urmare, scriem o funcţie cmmdc cu
doi parematrii formali m şi n, care întoarce o valoare întreagă – cmmdc
al lor.

#include <iostream.h>
int cmmdc(int m, int n)
{ while (m!=n)
if (m>n) m-=n;
else n-=m;
return m;
}
main( )
{ int cm, m, n;
cout<<”m=”; cin>>m;
cout<<”n=”; cin>>n;
cm=cmmdc(m,n);
cout<<cm<<” “<<m*n/cm;
}

var cm, m, n:integer;


function cmmdc(m,n:integer):integer;
begin
while (m<>n) do
if m>n then m:=m-n
else n:=n-m;
end;
read(m,n);
cm:=cmmdc(m,n);
write(cm);
end.

Evident, problema putea fi rezolvată fără utilizarea


subprogramelor. Atunci care este motivul pentru care am preferat
utilizarea lor? Algoritmul prin care se determină cmmdc este des utilizat.

□ Se citeşte un vector cu n componente numere întregi. Se cere să se


tipărească cmmdc al valorilor reţinute de vector.

Rezolvare. Dispunem deja de o funcţie care calculează cmmdc a


două numere. Nu ne rămâne decât s-o folosim. Cum? Cu ajutorul ei
calculăm cmmdc pentru valorile reţinute de primele două componente.

Această valoare este reţinută de variabila cm. Apoi, pentru fiecare


componentă i, cu i între 2 şi n-1 se calculează cmmdc între valoarea
reţinută de cm şi cea reţinută de componenta i. Cmmdc va fi reţinut, din
nou, de cm.

#include <iostream.h>
int v[9], n;

int cmmdc(int m, int n)


{ while (m!=n)
if (m>n) m-=n;
else n-=m;
return m;
}
main( )
{ int i, cm;
cout<<”n=”; cin>>n;
for (i=0; i<n; i++) cin>>v[i];
cm=cmmdc(v[0], v[1] );
for (i=2; i<n; i++) cm=cmmdc(cm, v[i] );
cout<<cm;
}

var v:array[1..9] of integer;


i, n, m, cm:integer;
function cmmdc(m,n:integer):integer;
begin
while (m<>n) do
if m>n then m:=m-n
else n:=n-m;
end;
begin
read(n);
for i:=1 to n do
read(v[i]);
cm:=cmmdc(v[1], v[2]);
for i:=3 to n do
cm:=cmmdc(cm, v[i]);
write(cm);
end.

□ Se citesc două numere naturale m<n. Se cere să se tipărească toate


numerele palindrom aflate între m şi n. Un număr este palindrom dacă
citit direct şi invers rezultatul este acelaşi. Exemplu: 121.

Rezolvare. Să analizăm problema. Este necesar să fie testate toate


numerele între m şi n, pentru ca apoi să fie tipărite numai cele care
îndeplinesc condiţia cerută. Pentru aceasta, vom scrie o funcţie numită
palin care are rolul de a testa dacă numărul este sau nu palindrom.
Evident, funcţia trebuie să întoarcă 1 sau 0, în funcţie de i, dacă este
palindrom sau nu. O astfel de valoare este de fapt booleană.

Care este avantajul scrierii funcţiei? Acesta este dat de faptul că,
atunci când scriem funcţia, ne concentrăm exclusiv asupra ei, deci
posibilitatea de a greşi este minimă. Dacă dispunem de funcţie, algoritmul
este extrem de simplu: for de la m la n. În plus, este posibil ca această
funcţie să poată fi folosită şi în alte cazuri.

#include <iostream.h>
int palin(int i)
{
int isalv=i, iinv=0;
while (i)
{
iinv=iinv*10+i%10;
i=i/10;
}
return isalv==iinv;
}

main( )
{ int m, n, i;
cout<<”m=”; cin>>m;
cout<<”n=”; cin>>n;
for (i=m; i<n; i++)
if (palin(i)) cout<<i<<end1;
}

var m, n, i:integer;
function palin(i:integer):integer;
var isalv, iinv:integer;
begin
isalv:=i;
iinv:=0;
while (i<>0) do
begin
iinv:=iinv*10+i mod 10;
i:=i div 10;
end;
palin:=(isalv=iinv);
end;
begin
read(m,n);
for i:=m to n do
if palin(i) then
cout(i);
end.

□ Superpalindrom. Un număr natural este palindrom dacă citit de la


stânga la dreapta rămâne nemodificat. De exemplu, numărul 12321 este
palindrom. Un număr natural este superpalindrom dacă este palindrom
atât el cât şi pătratul său. Scrieţi un program care listează toate numerele
cu această proprietate aflate între doi întregi a şi b – (a<b<32000).

Junior Division

Rezolvare. Modul în care testăm dacă un număr este palindrom


este acum lămurit. Mai mult, avem şi funcţia. Nu trebuie decât să o
folosim.

#include <iostream.h>
long palin(long i)
{
long isalv=i, iinv=0;
while (i)
{
iinv=iinv*10+i%10;
i=i/10;
}
return isalv==iinv;
}

main( )
{ long m, n, i;
cout<<”m=”; cin>>m;
cout<<”n=”; cin>>n;
for (i=m; i<n; i++)
if (palin(i))
if (palin(i*i)) cout<<i<<” “<<end1;
}

var m, n, i:integer;
function palin(i:integer):integer;
var isalv, iinv:integer;
begin
isalv:=i;
iinv:=0;
while (i<>0) do
begin
iinv:=iinv*10+i mod 10;
i:=i div 10;
end;
palin:=(isalv=iinv);
end;
begin
read(m,n);
for i:=m to n do
if palin(i) then
if palin(i*i) then
writeln(i, i*i);
end.

□ Suma cuburilor cifrelor. Se citeşte un număr natural n<=9999999. Se


calculează suma cuburilor cifrelor sale. Exemplu: dacă se citeşte 25 se
calculează 2³+5³=133. Cu numărul obţinut procedăm la fel:
1³+3³+3³=55. Repetăm procedeul: 5³+5³=250. Şi iar: 2³+5³+0³=133.
De această dată am regăsit un număr care era deja prezent în seria
generată, deci ne oprim. Am obţinut seria:
25 133 55 250 133
Junior Division

Rezolvare. Suma cuburilor cifrelor unui număr natural este


calculată de funcţia suma. Numărul n, citit, va fi memorat de prima
componentă a vectorului seria. Apoi suma cuburilor cifrelor sale de
componenta a doua, suma cuburilor cifrelor numărului reţinut de a doua
componentă va fi reţinută de a treia componentă ş.a.m.d. După ce este
memorată o valoare, ea este comparată, pe rând, cu cele obţinute anterior.
Dacă este regăsită, se tipăreşte numărul de termeni ai seriei şi seria
propriu-zisă. Funcţia suma calculează suma cuburilor cifrelor unui
număr.

#include <iostream.h>
int seria[1000], n, ind, i, gasit;

int suma(int n)
{ int s=0, c;
while (n)
{ c=n%10; s+=c*c*c; n/=10;
return s;
}

main( )
{
cout<<”n=”; cin>>n;
ind=1; seria[ind]=n;
do
{
seria[++ind]=suma(seria[ind-1]);
for (i=1; i<ind; i++)
if (seria[i]==seria[ind]) gasit=1;
} while (!gasit);
cout<<”lungimea este “<<ind<<end1;
for (i=1; i<=ind; i++) cout<<seria[i]<<end1;
}
var i, n, ind:integer;
gasit:boolean;
seria:array[1..100] of integer;
function suma(n:integer):integer;
var s, c:integer;
begin
s:=0;
while (n<>0) do
begin
c:=n mod 10;
s:=c*c*c;
n:=n div 10;
end;
suma:=s;
end;
begin
read(n);
ind:=1;
suma[ind]:=n;
gasit:=true;
repeat
seria[ind+1]:=suma(seria[ind-1]);
for i:=1 to ind do
if suma[i]=suma[ind] then
gasit:=true;
until not gasit;
writeln(ind);
for i:=1 to ind do
write(seria[i]);
end.

□ Triunghi special. Se citesc 2 numere naturale m şi n strict mai mici


decât 10. Se cere ca programul dv. Să afişeze un triunghi, după regulile
pe care le deduceţi din exemplele următoare:

1. m=6, n=1

1
2 3
4 5 6
7 8 9 1
2 3 4 5 6
7 8 9 1 2 3
2. m=7, n=9
9
1 2
3 4 5
6 7 8 9
1 2 3 4 5
6 7 8 9 1 2
3 4 5 6 7 8 9

Rezolvare. Problema este de a putea număra începând da la n, iar


când s-a depăşit valoarea 9 de a relua numărul de la 1. De exemplu, dacă
n=5 trebuie să obţinem şirul 5, 6, 7, 8, 9, 1, 2, 3, …. Sintetizând, ajungem
la concluzia că este necesar să utilizăm o funcţie (succ) care primeşte ca
parametru de intrare un număr k şi returnează valoarea următoare, în
logica prezentată mai sus.

Cum procedăm? Mai întâi, avem m linii, deci vom folosi for cu i
de la 1 la m. Apoi, în prima linie trebuie tipărit un număr, în linia a doua
două numere, ş.a.m.d. Vom folosi un alt for cu j de la 1 la i. Când i este
1, j este 1, deci se tipăreşte o valoare. Când i este 2, j ia valorile 1 şi 2,
deci se tipăresc două valori ş.a.m.d. algoritmul este conţinut de funcţia
tipar, care are ca parametrii formali pe m şi n, transmişi prin valoare.
Valorile lor sunt citite în programul principal.
#include <iostream.h>
int succ(int k)
{ if (k==9) return 1;
else return k+1;
}
void tipar(int m, int n)
{ int i, j, s=n;
for (i=1; i<=m; i++)
{ for (j=1; j<=i; j++)
{ cout<<s;
s=succ(s);
}
cout<<end1;
}
}

main( )
{ int m, n;
cout<<”m=”; cin>>m;
cout<<”n=”; cin>>n;
tipar (m,n);
}

var m, n:integer;
function succ(k:integer):integer;
begin
if (k=9) then succ:=1
else succ:=k+1;
end;
procedure tipar(m, n:integer);
var i, j, n, s:integer;
begin
s:=n;
for i:=1 to m do
for j:=1 to i do
begin
write(s);
s:=succ(s);
end;
end;
begin
read(m,n);
tipar(m,n);
end.

□ Numere rotunde. Se citeşte n<10000. Se cere să se tipărească numărul


de numere mai mici sau egale cu n care, în binar, au acelaşi număr de
cifre 0 şi 1. De exemplu, 9 este un astfel de număr 9₁₀=1001₂.

Rezolvare. Trebuie testate numerele de la 1 la n. Deci folosim


instrucţiunea for cu i de la 1 la n. Acum trebuie testat dacă, în binar,
fiecare i are tot atâtea cifre 0 câte cifre 1. Pentru asta folosim o funcţie
care returnează, după caz, 1 sau 0, numită valabil.

#include <iostream.h>

int valabil(int nr)


{ int zero=0, unu=0;
while (nr)
{ if (nr%2) unu++;
else zero++;
nr=nr / 2;
}
return zero==unu;
}

main( )
{ int n, i, s=0;
cout<<”n=”; cin>>n;
for (i=1; i<=n; i++)
if (valabil(i)) s++;
cout<<s;
}

var n, s, i:integer;
function valabil(nr:integer):boolean;
var zero, unu:integer;
begin
while (nr<>0) do
begin
if nr mod 2 = 0 then
unu:=unu+1
else
zero:=zero+1;
nr:=nr div 2;
end;
valabil:=(zero=unu);
end;
begin
s:=0;
read(n);
for i:=1 to n do
if valabil(i) then s:=s+1;
write(s);
end.

□ Statistică. Se citeşte un fişier text, alcătuit din mai multe linii. Se cere
să se listeze numărul apariţiilor şi frecvenţa fiecărei litere mari a
alfabetului în fişier, ca mai jos:

litera număr apariţii frecvenţa

A 29 12.24
B 26 10.97

Junior Division

Rezolvare. Fişierul se citeşte caracter cu caracter, până la sfârşit.


Revedeţi fişierele text. Funcţia numara testează caracterul citit, dacă este
literă mare sau nu. În caz afirmativ, incrementează componenta
corespunzătoare caracterului (componentă al cărei indice se obţine
scăzând din caracterul citit codul caracterului ‘A’) şi incrementează
variabila care reţine numărul de litere mari citite. În final, se obţine lista
cerută cu ajutorul funcţiei tipar.

#include <fstream.h>
int frecv[26], suma;

void numara(char ch)


{ if ((ch>=’A’ && ch<=’Z’)) ++frecv[ch-‘A’], ++suma;}
void tipar( )
{ for (char c=’A’; c<=’Z’; c++)
cout<<c<<” “<<frecv[c-‘A’]<<” “<<
(float)frecv[c-‘A’]/suma*100<<end1;
}

main( )
{ fstream f(“c: \\ borlandc \\ bin \\ fis.txt”, ios::in);
char ch;
while (f>>ch) numara(ch);
tipar( );
}

În acest caz am utilizat subprograme pentru a descompune


problema în subprobleme, deci pentru a o rezolva mai uşor.

□ Să se scrie o funcţie care citeşte o matrice cu elemente numere întregi


dintr-un fişier text. Pe prima linie a fişierului sunt două valori m (numărul
de linii ale matricei), n (numărul de coloane ale matricei). Următoarele m
linii ale fişierului conţin, în ordine, elementele aflate pe fiecare linie a
matricei.

Exemplu: fişierul f.in conţine

3 4
3 1 7 9
1 2 3 4
9 1 3 8

Pentru a putea fi folosită la citirea oricărei matrice astfel memorate,


funcţia va trebui să primească parametrii de intrare, referinţele la
variabilele m şi n, şi adresa masivului citit, care are cel mult 10 linii şi cel
mult 10 coloane. Observaţi că nu ne interesează faptul că elementele
matricei sunt plasate pe linii diferite, întrucât, implicit, caracterele albe
sunt sărite.

#include <iostream.h>
void citmat(int &m, int&n, int mat [10] [10] )
{
fstream f(“c:\\borlandc\\bin\\fis.txt”, ios::in);
f>>m>>n;
for (int i=1; i<=m; i++)
for (int j=1; j<=n; j++) f>>mat [i] [j];
}

main( )
{ int m, n, mat [10] [10];
citmat(m, n, mat);
cout<<m<<” “<<n<<end1;
for (int i=1; i<=m; i++)
{ for (int j=1; j<=n; j++) cout<<mat[i] [j]<<” “;
cout<<end1;
}
}

2.5 Funcţii cu un număr variabil de


parametrii (Facultativ)
Limbajul C++ ne permite să lucrăm cu funcţii care au un număr
variabil de parametrii.

 Este obligatoriu ca cel puţin primul parametru să apară în lista


parametrilor formali.

 Pentru parametrii variabili antetul va conţine “…”. De exemplu, iată


cum arată antetul unei funcţii în care numai primul parametru (un
întreg n) apare întotdeauna:

int suma(int n,…)

Aşa cum am învăţat, parametrii efectivi sunt depuşi, unul după


altul, în ordinea în care i-am trecut în antet, în stivă. Dacă la adresa
primului parametru adunăm 1, obţinem adresa parametrului următor (să
nu uităm că dacă adunăm 1 la adresa unui parametru, se adună, de fapt,
numărul de octeţi ocupaţi de acesta). De regulă, parametrii ficşi conţin
informaţii despre cei variabili (numărul lor, în unele cazuri tipul lor)

□ Programul următor, conţine o funcţie cu rolul de a calcula suma


parametrilor variabili care apar în antet. Parametrul fix precizează
numărul de parametrii variabili. La primul apel se calculează suma între
1, 7 şi 9, la al doilea suma între 1, 2, 0, 9 şi 7.

#include <iostream.h>
int suma(int n,…)
{ int suma =0;
for (int i=1; i<=n; i++) suma+=(&n+i);
return suma;
}

main( )
{ cout<<suma(3, 1, 7, 9)<<end1;
cout<<suma(5, 1, 2, 0, 9, 7);
}

2.6 Supraîncărcarea funcţiilor


În C++ există posibilitatea ca mai multe funcţii să poarte acelaşi
nume. Totuşi, ele trebuie să fie diferite , fie ca număr de parametrii, fie ca
tip. În acest caz, este necesară o condiţie suplimentară: parametrii efectivi
să nu poată fi convertiţi implicit către cei formali.

□ Programul următor conţine trei funcţii care au acelaşi nume.

#include <iostream.h>
void functia(int n)
{ cout<<”Eu sunt functia cu un parametru de tip int”<<end1;
}

void functia(int n, int m)


{ cout<<”Eu sunt functia cu doi parametrii de tip int”<<end1;
}

void functia(int* n)
{ cout<<”Eu sunt functia cu un parametru de tip int*”<<end1;
}
main( )
{ functia (2); functia (3,5);
int n; functia (&n);
}

Compilatorul nu poate face distincţie între funcţiile de mai jos


(pentru că între tipul celor doi parametrii se pot face conversii explicite):

void functia(int n)
{ cout<<”Eu sunt functia cu un parametru de tip int”<<end1;
}
void functia(float n)
{ cout<<”Eu sunt functia cu un parametru de tip float”<<end1;
}

 V-aţi putea întreba: la ce foloseşte acest fapt, întrucât nu este esenţial?


Aveţi dreptate, pentru materia studiată deocamdată, acest fapt nu este
esenţial. În schimb, rolul său este uriaş în cazul programării orientate pe
obiect, mecanism pe care îl vom studia anul următor.

Capitolul III

Structuri de date

3.1 Conceptul de structură de date


Orice algoritm primeşte date de intrare, le prelucrează şi obţine
date de ieşire. În acest caz, datele de intrare, datele intermediare – cele
create în timpul prelucrării – şi datele de ieşire sunt structurate
(organizate) într-un anumit fel care corespunde intrării, necesităţilor de
prelucrare sau a celor de utilizare ulterioară.

Pentru a veni în sprijinul programatorilor, limbajele de programare


evoluate (de exemplu C++, Pascal) pun la dispoziţia acestora
posibilitatea organizării datelor în anumite “şabloane” numite tipuri de
date. Mai precis, prin tip de dată se înţelege:

■ o mulţime de valori;
■ o regulă de codificare a acestora;
■ o mulţime de operaţii definite pe mulţimea datelor.

La rândul lor, tipurile de date pot fi:

■ simple – descriu date care aparţin unor mulţimi care nu sunt rezultate
ca produs cartezian al altor mulţimi. Exemplu: int.
■ structurate – descriu date care aparţin unor mulţimi rezultate ca produs
cartezian al altor mulţimi.

Exemple:
1. struct rational
{
int p, q;
};

Prin tipul de mai sus se descrie structura unei variabile capabilă să


reţină numere raţionale. Fie A₁mulţimea valorilor care pot fi memorate
prin utilizarea tipului integer. Fie A₂=A₁-{0}; Atunci o variabilă de tip
rational poate memora valori care aparţin mulţimii A₁XA₂. Evident, este
sarcina programatorului ca valoarea reţinută de variabila corespunzătoare
lui q să fie diferită de 0.

2. double a[100];

Fie B mulţimea valorilor care pot fi reţinute de tipul double. Atunci,


variabila a poate reţine la un moment dat un element al mulţimii:

B x B x …x B x B

de n ori

Practica impune utilizarea unor structuri ale datelor de o mare


varietate, care nu se suprapun întotdeauna peste tipurile care pot fi
descrise prin limbaj.

 Prin structură de date vom înţelege un ansamblu de date caracterizat


prin relaţiile existente între ele şi a operaţiilor care pot fi efectuate cu
datele respective.

Vom numi nod o variabilă de un tip oarecare. De obicei, acest tip


este structurat. După caz, termenul nod poate fi înlocuit cu articol,
înregistrare sau entitate.

În cele mai multe cazuri, “ansamblul de date” care alcătuieşte


structura este alcătuit dintr-o mulţime cu un număr variabil de noduri.

Relaţiile existente între noduri şi operaţiile care pot fi efectuate cu


ele vor fi prezentate prin exemple.
De reţinut:
 Tipul variabilei care alcătuieşte nodul nu caracterizează structura de
date.

 Structura de date este un concept abstract. Mai precis, conceptul în sine


nu precizează locul unde structura respectivă va fi memorată (clasa de
memorare), nici detaliile de implementare (cele care ţin de limbajul
folosit).
 În facultăţile de profil (calculatoare, informatică) se studiază disciplina
numită “Structuri de date”. Ea este una dintre disciplinele care se include
în cadrul mai larg al informaticii, tot aşa cum, de exemplu, algebra se
studiază ca disciplină aparte a matematicii.

 În practică s-au impus anumite structuri. Aceasta pentru că există mulţi


algoritmi care le utilizează. Ele vor fi tratate, pe scurt, în paragrafele
următoare. Un studiu amănunţit al lor îl veţi întâlni în manualul din anul
următor.

3.2 Structura de tip listă liniară

3.2.1. Prezentarea structurii

 O listă liniară este o colecţie de n≥0 noduri, X₁, X₂, … , Xn aflate


într-o relaţie de ordine. Astfel, X₁ este primul nod al listei, X₂ este al
doilea nod al listei … Xn este ultimul nod. Operaţiile permise sunt:

□ Accesul la oricare nod al listei în scopul citirii sau modificării


informaţiei conţinute de acesta.

□ Adăugarea unui nod, indiferent de poziţia pe care o ocupă în listă.

□ Ştergerea unui nod, indiferent de poziţia pe care o ocupă în listă.

□ Schimbarea poziţiei unui nod în cadrul listei.

Există două metode de alocare a unei liste liniare: alocarea


secvenţială şi alocarea înlănţuită.
3.2.2. Liste alocate secvenţial

Nodurile listei ocupă poziţii succesive în memorie. Acest tip de


alocare l-am întâlnit des, de câte ori am utilizat vectori.

Exemplu: un vector are n componente de tip float. Se cere să se sorteze


vectorul crescător. Algoritmul are ca dată de intrare o listă liniară, cu n
noduri de tip real. Ieşirea este tot o listă liniară, cu aceleaşi noduri, dar în
altă ordine. Să presupunem că utilizăm sortarea prin interschimbare. O
interschimbare a nodurilor i şi j se reduce la următoarele operaţii, premise
în listă:

■ man = v[i]; - se citeşte nodul i şi se memorează – accesez nodul i în


vederea citirii;

■ v[i] v[i+1]; - accesez nodul i+1 în vederea citirii (operaţie permisă) şi


accesez nodul i în vederea modificării informaţiei reţinute (operaţie
permisă).

■ v[i+1] = man; - accesez nodul i+1 în vederea modificării informaţiei


reţinute de el.

În concluzie, sortarea se realizează prin utilizarea operaţiilor


permise asupra unei liste liniare.

 Avantajul alocării secvenţiale este dat de faptul că programatorul are


acces direct la oricare din nodurile listei, la fel ca la componentele unui
vector.

 Dezavantajul alocării secvenţiale este dat de faptul că operaţiile de


adăugare, eliminare sau schimbare de poziţie a unui nod necesită un efort
mare de calcul, ca în exemplul următor, în care se elimină un nod:
Fie lista alocată secvenţial:
7 3 1 2 8 9 5 8 3 2 6 ∙∙∙ ∙∙∙ ∙∙∙ ∙∙∙ ∙∙∙ ∙∙∙ ∙∙∙ ∙∙∙ ∙∙∙

Eliminăm al doilea nod –conţinut 3.


7 1 2 8 9 5 8 3 2 6 ∙∙∙ ∙∙∙ ∙∙∙ ∙∙∙ ∙∙∙ ∙∙∙ ∙∙∙ ∙∙∙ ∙∙∙
◄▬▬▬▬▬▬▬▬▬▬▬▬
Este obligatoriu ca nodurile următoare să fie deplasate către stânga:
7 1 2 8 9 5 8 3 2 6 ∙∙∙ ∙∙∙ ∙∙∙ ∙∙∙ ∙∙∙ ∙∙∙ ∙∙∙ ∙∙∙ ∙∙∙ ∙∙∙

3.2.3. Liste alocate înlănţuit

Există două feluri de alocare înlănţuită: alocare simplu înlănţuită şi


alocare înlănţuită. În acest paragraf prezentăm principiile alocării
înlănţuite, urmând ca în paragraful următor să arătăm modul în care
implementăm listele alocate înlănţuit.

1. O listă liniară simplu înlănţuită este o structură de forma:

in₁ adr₂ in₂ adr₃ inn nil

adr₁ adr₂ adrn

Semnificaţia notaţiilor folosite este următoarea:

■ adr₁, adr₂, adr₃, … , adrn reprezintă adresele celor n înregistrări;

■ in₁, in₂, in₃, … , inn reprezintă informaţiile conţinute de noduri, de altă


natură decât cele de adresă;

■ nil – are semnificaţia “nici o adresă” – elementul este ultimul în listă.

După cum observăm, fiecare nod, cu excepţia ultimului, reţine


adresa nodului următor.

2. Alocarea dublu înlănţuită. Alocarea simplu înlănţuită permite


parcurgerea listei într-un singur sens (de la stânga la dreapta). În cazul
în care se doreşte ca lista să poată fi parcursă în ambele sensuri se
utilizează alocarea dublu înlănţuită. Aici fiecare nod reţine adresele
predecesorului şi succesorului său, aşa cum se vede în figura
următoare:
adr₁ in₂ adr₃ adrn-1 inn nil

nil in₁ adr₂

adr₁ adr₂ adrn

 Dezavantajele alocării înlănţuite sunt:

1. Accesul la un nod al listei se face prin parcurgerea nodurilor care în


preced. Aceasta necesită un efort de calcul.
2. Informaţiile de adresă, prezente în cadrul fiecărui nod, ocupă
memorie.

 Avantajele alocării înlănţuite sunt date de faptul că operaţiile de


adăugare sau eliminare a unui nod se fac rapid. Exemplele sunt date
pentru lista liniară simplu înlănţuită, dar bine înţelese, ne permit să
deducem singuri modul de efectuare a operaţiilor respective pentru liste
dublu înlănţuite.

a) Adăugarea unui nod.

Fie lista:

3 adr₂ 9 nil
7 adr₃

adr₁ adr₂ adrn

Dorim să adăugăm după nodul cu informaţia 3, un altul cu informaţia 5.

Etapele sunt:

□ Se alocă spaţiu în memorie pentru un nou nod – indiferent unde:

7 adr₃
9 nil
3 adr₂
adr₁ adr₂ adrn

adrt

□ Se completează informaţiile pentru nodul creat – câmpul de adresă


trebuie să conţină adresa nodului care trebuie să-i urmeze în listă:

3 adr₂
7 adr₃
9 nil

5 adr₂
adrt

□ Se modifică adresa nodului care precede nodul nou creat. Adresa


trebuie să fie a nodului nou creat:

3 adrt 7 adr₃ 9 nil

5 adr₂
adrt

De acum, putem “privi” lista aşa cum am fost obişnuiţi:

5 adr₂ 7 adr₃ 9 nil


3 adrt

adr₁ adrt adr₂ adrn

b) Ştergerea unui nod. Pentru a exemplifica operaţiile efectuate în acest


caz vom folosi a de mai sus, la care ştergem al doilea nod (cel cu
informaţia 5). Iată etapele:

□ Informaţia de adresă a nodului care îl precede trebuie să reţină adresa


nodului următor:

3 adr₂ 5 adr₂ 7 adr₃ 9 nil

adr₁ adrt adr₂ adrn

□ Memoria ocupată de nodul care urmează a fi şters este eliberată:

7 adr₃ 9 nil

3 adr₂
adr₁ adr₂ adrn

De acum, putem privi lista aşa cum am fost obişnuiţi.

7 adr₃ 9 nil

3 adr₂

adr₁ adr₂ adrn


Observaţii:
 În cazul alocării înlănţuite, adresele de memorare ale nodurilor
consecutive nu sunt neapărat consecutive. Pentru a realiza acest lucru
este suficient să analizaţi cazul ştergerii unui nod (sau acela al adăugării
unui nod).

 Prin alocarea memoriei pentru un nod înţelegem rezervarea spaţiului


necesar memorării informaţiilor conţinute de acesta. Evident, se poate
aloca memorie doar dacă există memorie disponibilă, adică nu este
ocupată de alte variabile.

 Pentru eliberarea memoriei ocupate de un nod înţelegem că spaţiul


ocupat de acesta devine disponibil – este pus la dispoziţia
programatorului, pentru ca, eventual, acesta să fie din nou alocat.

 Este important să folosim termenii corect. De exemplu, nu putem folosi


în loc de “alocarea memoriei” termenul “crearea memoriei”, tot aşa cum
nu este corect să folosim în loc de “eliberarea memoriei” termenul
“ştergere a memoriei”.

 Noţiunile care privesc alocarea şi eliberarea memoriei sunt prezentate în


paragraful următor.
3.2.4. Implementarea alocării înlănţuite prin
utilizarea vectorilor

Aşa cum am învăţat, lista liniară este alcătuită din mai multe
noduri, între care există o relaţie de ordine. În cazul alocării înlănţuite,
informaţia memorată de fiecare nod va cuprinde şi un câmp de adresă –în
cazul alocării simplu înlănţuite –sau două câmpuri de adresă –în cazul
alocării dublu înlănţuită. În acest paragraf vom studia implementarea
alocării simplu înlănţuite.

■ Iată cum se descrie informaţia dintr-un nod, în cazul listelor alocate


simplu înlănţuit, atunci când acesta reţine un număr întreg.

Typedef int adresa;


Struct nod
{
int info;
adresa adr_urm;
};

Info adresa nodului următor

Pentru a nu face confuzie între informaţia de adresă, care este tot


de tip int, şi restul informaţiei memorate, am “rebotezat” tipul int.

■ Pentru memorarea listei folosim un vector care are componente de tip


Nod, descris mai jos:

nod L[1000];

Din descriere rezultă că lista poate avea 1000 de noduri. Acesta


este spaţiul disponibil. Spaţiul disponibil se gestionează prin următoarele
variabile:

■ int nr_elem; - reprezintă numărul de noduri conţinute la un moment


dat de listă.

■ adresa ocupat[1000] reţine 1 dacă componenta corespunzătoare din L


memorează un nod, sau 0 în caz contrar.

Priviţi figura următoare:

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

Ocupat
0 1 1 1 0
1
1 2 3 4 5 6

Ea reprezintă modul în care este memorată o listă cu 4 noduri, într-


un vector cu 6 componente. Lista este: 7, 5, 1, 4.

□ Funcţia următoare testează dacă există spaţiu disponibil pentru alocarea


unui nou nod:

int Există_spaţiu( )
{ return nr_elem<1000; }
□ Alocarea propriu-zisă se face prin funcţia următoare:

void Aloca(adresa& x)
{
adresa i=1;
while (ocupat[i] ) i++;
x=i; ocupat[i]=1; nr_elem++;
}

Funcţia caută prima componentă liberă (care nu memorează nici un


nod). În momentul găsirii acesteia, se marchează vectorul ocupat şi se
returnează adresa unde a fost găsită (indicele în vector).

□ Eliberarea spaţiului la ştergerea unui nod al listei se face prin funcţia


Eliberează:

void Eliberează(adresa x)
{
ocupat[x]=0; nr_elem --;
}

□ Funcţia Adăugare are rolul de a adăuga la sfârşitul listei un nod, care


reţine valoarea transmisă de variabila val. Programul lucrează cu lista
pornind de la adresa primului nod memorat, adresă care se găseşte în v.
De asemenea, pentru uşurinţa lucrării, se reţine şi adresa ultimului nod sf.

Void Adaugare(adresa& v,int val)


{
int c;
if ( ! v )
{
Aloca(v);
L[v].info=val; L[val].adr_urm=0;
sf=v;
}
else
if (Exista_spatiu)
{
Aloca ( c );
L[sf].adr_urm=c; L[c].info=val; L[c].adr-urm=0;
sf=c;
}
else cout<<”lipsa spatiu”;
}

Funcţia tratează diferit cazurile în care lista este vidă, deci se


creează primul nod al ei şi cazul în care adăugarea se face la o listă
nevidă.

■ În primul caz se alocă nodul, se completează cu informaţia numerică,


iar câmpul de adresă va reţine 0.

■ În al doilea caz, se testează existenţa spaţiului şi dacă acesta există se


alocă pentru nod. Nodul reţine 0, pentru adresa nodului următor (pentru
că este ultimul în listă). Ultimul nod de până acum al listei (cu adresa în
sf va reţine adresa nodului nou creat.

□ Funcţia Inserare_dupa inserează un nod după un altul, care reţine


valoarea numerică val. Noul nod reţine valoarea numerică val1.

void Inserare_dupa (adresa v, int val, int val1)


{
adresa c, d;
if (Exista_spatiu)
{
c=v;
while (L[c].info!=val) c=L[c].adr_urm;
Aloca(d);
L[d].info=val1; L[d].adr_urm=L[c].adr_urm; L[c].adr_urm=d;
If (L[d].adr_urm==0) sf=d;
}
else cout<<”Nu exista spatiu”;
}

În cazul în care există spaţiul necesar se procedează astfel:


■ Se caută nodul care reţine valoarea val.
■ Se alocă spaţiu pentru noul nod.
■ Noul nod va memora val1, iar ca adresă a nodului următor va memora
valoarea reţinută de câmpul adr_urm a nodului care reţine val.
■ Nodul care reţine val va memora la câmpul de adresă adresa nodului
nou creat.

□ Funcţia Inserare_inainte inserează un nod înaintea altuia care reţine o


valoare dată, transmisă prin val:

void Inserare_inainte (adresa v, int val, int val1)


{
adresa c, d;
if (Exista_spatiu( ))
if (L[v].info==val)
{
Aloca (d);
L[d].info=val1; L[d].adr_urm=v; v=d;
}
else
{
c=v;
while (L[L[c].adr_urm].info!=val) c=L[c].adr_urm;
Aloca(d);
L[d].info=val1; L[d].adr_urm=L[c].adr_urm; L[c].adr_urm=d;
}
else cout<<”Nu exista spatiu”;
}
□ Funcţia Sterg şterge nodul care memorează valoarea transmisă prin
val:

void Sterg(adresa& v, int val)


{
adresa c, man;
if (L[v].info==val)
{
man=v; v=L[v].adr_urm;
}
else
{
c=v;
while (L[L[c].adr_urm].info!=val) c=L[c].adr_urm;
man=L[c].adr_urm; L[c].adr_urm=L[man].adr_urm;
if (man==sf) sf=c;
}
Elibereaza (man);
}

□ Afişarea listei se face cu funcţia următoare:

void Listare (adresa v)


{ adresa c=v;
while ( c );
{ cout<<L[c].info<<” “;
c=L[c].adr_urm;
}
cout<<end1;
}

Observaţii:

 Mecanismele generale pentru adăugarea şi ştergerea unui nod dintr-o


listă au fost prezentate în paragraful anterior. Recomand să-l revedeţi.

 Alocarea înlănţuită prin utilizarea vectorilor ţine deja de istoria


informaticii. Cu adevărat, pentru alocarea înlănţuită a unei structuri se
utilizează o zonă de memorie, special rezervată în limbajele de
programare actuale, numită HEAP.

 Programul care testează această implementare va fi scris de


dumneavoastră.
 Funcţiile prezentate nu efectuează validare logică. De exemplu, dacă se
apelează funcţia pentru adăugarea unui nod după altul, care reţine o
anumită informaţie, acesta se presupune că există în listă. Evident, dacă
lista nu-l conţine, programul va da eroare.

3.2.5. Implementarea alocării înlănţuite folosind


HEAP-ul

De la început precizez faptul că acest mecanism va fi studiat în


amănunt anul viitor. Este bine, totuşi, ca în limita timpului disponibil, să
încercaţi să vă familiarizaţi cu aceste noţiuni începând din acest an.
Există posibilitatea ca anumite variabile să fie alocate într-o zonă
de memorie specială, numită HEAP. Aceste variabile se alocă atunci
când doreşte programatorul, printr-un operator special, numit new, iar
zona se eliberează, tot la dorinţa acestuia, printr-un alt operator numit
delete.
Variabilele astfel alocate se numesc dinamice, au o durată de viaţă
dinamică, iar clasa de memorare este HEAP-ul (mai corect segmentul
HEAP). Pe scurt, o astfel de alocare a memoriei se numeşte alocare
dinamică.

 Operatorul new are rolul de a aloca spaţiul necesar memorării


obiectului în heap. El se utilizează după schema următoare:

variabilă de tip pointer către tip == new tip

 Operatorul delete are rolul de a elibera spaţiul ocupat de variabilă.

delete variabilă de tip pointer către tip

Exemplu: se alocă spaţiu pentru o variabilă de tip complex, care este


iniţializată, afişată şi apoi ştearsă:

#include <iostream.h>
struct complex
{ int m, n; };
main( )
{ complex* adr;
adr=new complex; adr->m=7; adr->n=5;
cout<<adr->m<<” “<<adr->n<<end1;
delete adr;
}
□ Următorul program creează o listă liniară simplu înlănţuită cu 3 noduri
care reţin 1 2 3. Exemplul este pur didactic.

#include <iostream.h>
struct Nod
{
int info;
Nod* adr_urm;
};
main( );
{
Nod *c, *d, *v;
v=new Nod; v->info=1;
c=new Nod; c->info=2; v->adr_urm=c;
d=new Nod; d->info=3; d->adr_urm=0; c->adr_urm=d;
c=v;
while ( c );
{ cout<<c->info<<” “;
c=c->adr_urm;
}
}

Analizăm programul pas cu pas:

v
1 v=new Nod; v->info=1;

Am alocat memorie în HEAP pentru un nod. Adresa este reţinută


în v.
v
1 c=new Nod; c->info=2;

c
2

Am alocat memorie pentru alt nod. Adresa este reţinută în c.

v
1 v->adr_urm=c;

c
2

Adresa următoare, pentru primul nod alocat va reţine adresa


următorului nod alocat.

v
d=new Nod; d->info=3;
d-> adr_urm=0;
c->adr_urm=d;
1

c
2

Am alocat spaţiu pentru al treilea nod. Acesta va reţine 3 în info şi


0 (nici o adresă) pentru adr_urm.
v Din acest moment, se
poate lucra cu lista cunoscând
1 doar adresa sa de început (a
primului nod) v.

Exerciţiu de adresare. Priviţi modul în care se efectuează adresarea


pornind de la v, în lista de mai sus.

■ v se referă la adresa primului nod al listei;

■ v->info se referă la valoarea reţinută de câmpul numeric al înregistrării


care are adresa memorată în variabila v; În exemplu, este 1.

■ v->adr_urm semnifică valoarea reţinută de câmpul de adresă al


primului nod. În exemplu este 2.

■ v->adr_urm->info semnifică valoarea reţinută de câmpul info al


nodului 2. În exemplu este 2.
■ v->adr_urm->adr_urm semnifică valoarea reţinută de câmpul de
adresă al nodului 2. În exemplu, este adresa nodului 3.

■ v->adr_urm->adr_urm->info semnifică valoarea reţinută de câmpul


numeric al nodului 3. În exemplu, este 3.

■ v->adr_urm->adr-urm->adr_urm semnifică valoarea reţinută de


câmpul de adresă al nodului 3. În exemplu, este 0 (nici o adresă).

Observaţii:

 Alocarea dinamică a memoriei prezintă avantajul că programul


utilizează o zonă de memorie oricum rezervată, numită HEAP. În acest
fel programul are “la dispoziţie” mai multă memorie.

 Alocarea înlănţuită prin utilizarea alocării dinamice prezintă avantajul


că alocarea şi eliberarea memoriei se face cu operatori speciali ai
limbajului – nu este nevoie ca programatorul să scrie propriile rutine. S-ar
putea spune că, în comparaţie cu alocarea prin intermediul vectorilor,
prezintă numai avantaje.

3.3 Structura de tip stivă


 Stiva este o listă pentru care singurele operaţii permise sunt:

● Adăugarea unui element în stivă;


● Eliminarea, consultarea, sau modificarea ultimului element introdus în
stivă.

Stiva funcţionează pe principiul LIFO (Last In First Out) –


“ultimul intrat, primul ieşit”.
Pentru a înţelege modul de lucru cu stiva, ne imaginăm un număr n
de farfurii identice, aşezate una peste alta (o “stivă” de farfurii).
Adăugarea sau scoaterea unei farfurii se face, cu uşurinţă, numai în vârful
stivei. Oricât ar părea de simplu principiul stivei, el are consecinţe uriaşe
în programare.

 Stivele se pot aloca secvenţial (ca vectori). Fie ST[i] un vector. ST[1],
ST[2], … , ST[n] pot reţine numai litere sau numai cifre. O variabilă
k indică în permanenţă vârful stivei, adică ultimul element introdus.

A În stivă iniţial vidă se introduce litera A, vârful stivei va fi la


nivelul 1, (k=1);

B
A Introducem în stivă litera B, deci k va lua valoarea 2.

A Scoatem din stivă pe B (A nu poate fi scos deocamdată); k=1;

Scoatem din stivă pe A; stiva rămâne vidă k=0.

Observaţii:

 În mod practic, la scoaterea unei variabile din stivă, valoarea variabilei


ce indică vârful stivei scade cu 1, iar atunci când scriem ceva în stivă, o
eventuală valoare reziduală se pierde.

 Pe un anumit nivel se reţine, de regulă, o singură informaţie (literă sau


cifră), însă este posibil, aşa cum va rezulta din exemplele prezentate în
lucrare, să avem mai multe informaţii, caz în care avem stive duble,
triple, etc.

 În cazul stivei, alocarea secvenţială nu prezintă mari dezavantaje, ca în


cazul mai general, al listelor, pentru că nu se fac operaţii de inserare sau
ştergere în interiorul stivei. Singurul dezavantaj, în comparaţie cu
alocarea dinamică înlănţuită este dat de faptul că numărul de noduri care
pot fi memorate la un moment dat este mai mic – depinde de gradul de
ocupare al segmentului de date.

 În literatura de specialitate veţi întâlni termenul PUSH pentru operaţia


de adăugare în stivă a unei înregistrări şi POP, pentru extragere.

Exemple:

□ Funcţia Manna-Pnueli. Se citeşte x∈Z. Se cere programul pentru


calculul funcţiei:

F(x)= |x-1, x≥12


|F(F(x+2)), x<12

Vom începe prin a studia modul de calcul al funcţiei pentru x=15 şi


x=8.

f(15)=14;
f(8)=f(f(10))=f(f(f(12)))=f(f(11))=f(f(f(13)))=f(f(12))=f(11)=f(f(13))=
f(12)=11.

Algoritmul va folosi o stivă ST şi o variabilă k, ce indică în


permanenţă vârful stivei. Algoritmul se bazează pe următoarele
considerente:

■ la o nouă autoapelare a funcţiei f, se urcă în stivă (k se incrementează


cu 1) şi se pune noua valoare.

■ în situaţia în care pentru valoarea aflată pe nivelul k se poate calcula


funcţia, se coboară în stivă, punându-se pe acest nivel noua valoare.

■ algoritmul se încheie când se ajunge în stivă la nivelul 0.

Pentru exemplul dat, prezentăm schematic funcţionarea sa:

12 13

10 10 11 11

8 8 8 8 8
12 13

8 11 11 12
f=11

#include <iostream.h>
int st[100], n, k;
main( )
{ cout<<”n=”; cin>>n;
k=1; st[1]=n;
while (k>0)
if st([k]<12)
{ k++;
st[k]=st[k-1]+2;
}
else
{ k--;
if (k>0) st[k]=st[k+1]-1;
}
cout<<”n=”<<st[1]-1;
}
 Se poate demonstra uşor că pentru valori mai mici decât 12 funcţia ia
valoarea 11. Observaţia simplifică mult programul, dar exemplul a fost
dat în alt scop.

□ Funcţia lui Ackermann. Se dă funcţia următoare, definită pe produsul


cartezian NXN.. Se citesc m şi n. Să se calculeze Ack(m, n).

| n+1, m=0
Ack(m,n)= | Ack(m-1, 1), n=0
| Ack(m-1, Ack(m, n-1)), altfel

Pentru a elabora algoritmul, studiem un exemplu numeric:


ack(2,1)=ack(1,ack(2,0))=ack(1, ack(1,1))=ack(1, ack(0, ack(1,0))=
ack(1, ack(0, ack(0,1))=ack(1, ack(0,2))=ack(1,3)=ack(0, ack(1,2))=
ack(0, ack(0, ack(1,1))=ack(0, ack(0, ack(0, ack(1,0))))= ack(0, ack(0,
ack(0, ack(0,1))))=ack(0, ack(0, ack(0,2)))=ack(0,,ack(0,3))=
ack(0,4)=5.

Pentru calculul acestei funcţii, folosim o stivă dublă, ST. Iniţial,


valorile m şi n se reţin la nivelul 1. Pe nivelul k al stivei se reţin valorile
curente m şi n. În funcţie de valorile acestora se procedează astfel:

■ pentru m şi n diferite de 0, este necesar un nou calcul de funcţie, caz în


care se urcă în stivă şi pe noul nivel se pun argumente m şi n-1.

■ pentru cazul n=0, se rămâne pe acelaşi nivel în stivă, punând în locul


lui m valoarea m-1, iar în locul lui n valoarea 1.

■ în situaţia în care m=0, funcţia se poate calcula; se coboară în stivă şi


se înlocuieşte valoarea lui m cu m-1, valoarea lui n cu valoarea calculată
anterior.

În continuare, prezentăm grafic modul de funcţionare a


algoritmului pentru exemplul ack(2,1):

10 01
20 11 11 11
21 21 21 21 21

10
11 11
02 13 12 12
21 13 12 13 13
01
11 02
12 12 03
13 13 13 04

ack(2,1)=5.

int st[10000] [2];


main( )
{ int m, n, k;
cout<<”m=”; cin>>m;
cout<<”n=”; cin>>n;
k=1; st[k] [0] =m; st[k] [1] =n;
while (k>0)
if (st[k] [0] && st[k] [1])
{
k++;
st[k] [0] =st[k-1] [0];
st[k] [1] =st[k-1] [1] – 1;
}
else
if (!st[k] [1])
{
st[k] [0] =st[k] [0] – 1;
st[k] [1] = 1;
}

else
{
k--;
if (k>0)
{ st[k] [0] =st[k[] [0] – 1;
st[k] [1] =st[k+1] [1] +1;
}
}
cout<<”ac(“<<m<<’, ‘<<n<<”) = ”<<st[1] [1] +1;
}

 Funcţia lui Ackermann ia valori extrem de mari pentru valori mici ale
lui m şi n. De exemplu, nu veţi reuşi să calculaţi Ack(4,4).
 Stivele se pot aloca şi înlănţuit. În continuare, vom prezenta alocarea
dinamică (în HEAP).

3. Vârful v reţine adresa ultimului nod introdus, iar dacă


v stiva este vidă, acesta reţine nil (0).

Alăturat
observaţi
modul de
memorare
a unei
stive.
Nodurile
reţin
numere
întregi.
Ultimul
nod
introdus
este cel
care reţine
Analizaţi programul următor, care creează o listă liniară simplu
înlănţuită. Adăugarea unui element în stivă se face cu funcţia PUSH,
eliminarea cu funcţia POP. Vârful stivei este reţinut de variabila v.

#include <iostream.h>
struct Nod
{
int info;
Nod* adr_inap;
};

Nod* v;
int n;

void Push( Nod*& v, int n)


{
Nod* c;
If (!v)
{v= new Nod; v->info=n; v->adr_inap=0;}
else
{c= new Nod; c->info=n; c->adr_inap=v;
v=c;
}
}

void Pop(Nod*& v)
{
Nod* c;
If (!v) cout<<”stiva este vida”;
Else
{
c=v;
cout<<”am scos”<< c->info<<end1;
v=v->adr_inap;
delete c;
}
}

main( )

128
{ Push(v,1); Push(v,2); Push(v,3);
Pop(v); Pop(v); Pop(v); Pop(v);
}

□ Programul următor calculează funcţia Manna-Pnueli, utilizând stiva


alocată dinamic. Lucrul cu stiva se face prin utilizarea funcţiilor PUSH şi
POP.
#include <iostream.h>
struct Nod
{ int info;
Nod* adr_inap;
};
Nod* v;
Int n;
Void Push(Nod*& v, int n)
{ Nod* c;
if (!v)
{ v= new Nod; v->info=n; v->adr_inap=0;}
else
{ c= new Nod; c->info=n; c->adr_inap=v;
v=c;
}
}
void Pop (Nod*& v)
{ Nod* c;
if (!v) cout<< ”stiva este vida”;
else
{ c=v;
cout<<” am scos”<<c->info<<end1;
v=v->adr_inap;
delete c;
}
}
main( )
{ int Man;
cout<<”n=”; cin>>n;
Push(v,n);

129
While (v)
If (v->info<12)
Push(v, v->info+2);
Else
{ Man=v->info;
Pop(v);
If (v) v->info=Man-1;
}
cout<<”f=”<<Man-1;
}
3.4 Structura de tip coadă
 O coadă este o listă pentru care toate inserările sunt făcute la unul din
capete, toate ştergerile (consultările, modificările) la celălalt capăt.

Coada funcţionează pe principiul FIFO (First In First Out) –


“primul intrat, primul ieşit”.

Este cu totul nerecomandabilă alocarea secvenţială a cozii,


deoarece în această situaţie, are loc un fenomen de migraţie a datelor
către ultimele componente ale vectorului (cele de indice mare).

Să presupunem că simulăm o coadă cu ajutorul unui vector cu zece


componente, care reţin numere întregi. Introducem în coadă, pe rând,
numerele 1, 2, 3, 4.

1 2 3 4

Dacă scoatem din coadă pe 1 şi introducem în coadă pe 5, coada va


arăta astfel:

2 3 4 5

Scoatem din coadă pe 2 şi introducem pe 6:

3 4 5 6

Se observă acest fenomen de “migraţie”.

130
Alocarea dinamică înlănţuită a cozii. O variabilă v va reţine adresa
elementului care urmează a fi scos (servit). O alta, numită sf, va reţine
adresa elementului introdus în coadă. Figura următoare prezintă o coadă
în care primul element care urmează a fi scos are adresa în v, iar ultimul
introdus are adresa sf.

v sf

3 5 2

#include <iostream.h>
struct Nod
{
int info;
Nod* adr_urm;
}

Nod* v, *sf;
int n;

void Pune( Nod*& v, Nod*& sf, int n)


{
Nod* c;
If (!v)
{ v= new Nod; v->info=n; v->adr_inap=0;
sf=v;
}
else
{ c= new Nod;
sf->adr_urm=c;
c->info=n;
c->adr_urm=0;
}
}

131
void Scoate(Nod*& v)
{ Nod* c;
if (!v) cout<<”coada este vida”<<end1;
else
{ cout<<”Am scos”<<v->info<<end1;
c=v; v=v->adr_urm;
delete c;
}
}

void Listare(Nod* v)
{ Nod* c=v;
while ( c )
{ cout<<c->info<<” “;
c=c->adr_urm;
}
cout<<end1;
}
main( )
{ Pune(v, sf, 1); Pune(v, sf, 2); Pune(v, sf, 3); Listare(v);
Scoate(v); Listare(v);
Scoate(v); Listare(v);
Scoate(v); Listare(v);
Scoate(v); Listare(v);
}

132
Capitolul 4

Introducere in recursivitate

4.1 Prezentare generala

Recursivitatea este una din notiunile fundamentale ale informaticii. Utilizarea


frecventa a recursivitatii s-a facut dupa anii 80.Multe din limbajele de programare
evaluate si mai mult utilizate -Fortran, Cobol- nu permiteau scrierea programelor
recursive.

● Recursivitatea este un mechanism general de elaborare a programelor .Ea


consta in posibilitatea ca un subprogram sa se autoapeleze.

Recursivitatea a aparut din necesitati practice date de transcrierea directa a


formulelor matematice recursive.In timp. Acest mecanism a fost extins, fiind utilizat
in elaborarea multor algoritmi.

4.2 Mecanismul recursivitatii

▫ Sa se calculeze recursiv n!.

A) Pentru a scrie o functie recursiva care efectueaza acelasi calcul, vom


porni de la o definitie recursiva a lui n !. Aceasta este :

n !=fact(n)= 1, n=0
n€N
n fact(n-1), astfel

De exemplu, pentru a calcula 3!, procedam astfel :

3 !=fact(3)=3xfact(2)=3x2xfact(1)=3x2x1xfact(0)=3x2x1x1=6.

Functia recursive fact nu face altceva decat sa transcribe definitia recursive


prezentata anterior:

#include <iostream.h>
int fact(int n)

133
{ if (!n) return 1;
else return n* fact(n-1);
}
main( )
{ int n;
cout<<”n=”; cin>>n;
cout<<fact(n) ;
}

Observati ca functia fact se autoapeleaza. Autoapelul se realizeaza prin: return


n*fact(n-1).

Care este mecanismul prin care subprogramele se pot autoapela? Sa ne amintim


modul in care subprogramele memoreaza parametrii transmisi:

● Pentru memorarea parametrilor, subprogramele folosesc o zona de memorie


numita stiva (mai exact, aceasta zona se numeste segment de stiva).

● Memorarea parametrilor transmisi se face in ordinea in care acestia figureaza in


atent: de la stanga la dreapta.

● Pentru parametrii transmisi prin valoare, se memoreaza valoarea transmisa, iar


pentru cei transmisi prin referinta se memoreaza adresa variabilei.

● In cadrul subprogramului, parametrii transmisi si memorati in stiva sunt


variabile.Numele lor este cel di lista parametrilor formali.

In capitolul anterior am studiat proprietatile structurii numita stiva. Exact aceleasi


proprietati le are si segmental de stiva.Singura diferenta este data de faptul ca
gestiunea segmentului de stiva este facuta automat de catre calculator.Mai exact,
codul in limbaj masina, obtinut in urma compilarii, contine secvente prin care se
gestioneaza segmentul de stiva.

● La apelul subprogramului se depun in stiva, in ordine, parametrii transmisi. DE


asemenea, tot in stiva se rezerva spatiu pentru variabilele locale.(cele declarate in
subprogram).Acesta este un prim nivel al stivei.

● In cazul in care subprogramul se autoapeleaza pe un al doilea nivel se depun


din nou parametrii transmisi si se rezerva un nou spatiu pentru variabilele locale.

In continuare prezentam grafic modul in care functioneaza recursivitatea in cazul


functiei fact a programului anterior. Aceasta functie nu are variabile locale, deci in
stiva se depune doar parametrul n.

134
n→3 La primul apel al functiei fact se creeaza in segmental de
stiva o
variabila numita n, care retine 3- valoarea parametrului
formal.

n→2
n→3 Functia se autoapeleaza. De aceasta data parametrul n ia
valoarea 2
.In stiva se creeaza un nou nivel, care retine n cu valoarea
2.

n→1 Functia se autoapeleaza. De aceasta data parametrul n ia


n→2 valoarea 1. In stiva se creeaza un nou nivel, care retine n
cu
n→3 valoarea 1.

n→0 Functia se autoapeleaza.Parametrul n ia valoarea 0.In stiva


se
n→1 creeaza un nou nivel, care retine n cu valoarea 0.Functia ia
n→2 valoarea 1, autoapelul nu mai are loc.Se revine pe nivelul
3.
n→3

n→1 Pe nivelul 3 se efectueaza calculul 1*1=1.Se revine apoi


pe
n→2 nivelul 2.
n→3

n→2 Pe nivelul 2 se efectueaza calculul 2*1=2.Se revine apoi


n→3 pe nivelul 1.

n→3 Pe nivelul 1 se efectueaza calculul 3*2=6.Se revine apoi


in
main( ).

Aici se afiseaza 6, adica s-a calculat 3 !.

B) Mai putin eficient, n ! se poate calcula si printr-o functie ca in exemplu


urmator :

135
#include <iostream.h >
void fact(int val, int n,int&prod)
{ if (val<=n)
{ prod*=val;
fact(val+1,n,prod);
}
}
main( )
{ int val,n,p=1;
cout<<”n=”; cin>>n;
fact(1,n,p) ;
}

Sa analizam si pentru acest exemplu modul in care se efectueaza calculul,pentru


n=3.Functia are trei parametri : doi transmisi prin valoare, unul prin referinta.

Iata continutul variabilelor dupa primul apel :

1 3 Referinta catre p
1 3 3 Referinta catre p
2 3 Referinta catre p
p val n 1 3 Referinta catre p prod
Pentru ca val este mai mic sau egal ca n se
efectueaza : prod :=prod*val ;Intrucat variabila p a fost transmisa prin referinta,
programul lucreaza cu aceasta variabila, in loc de prod.In concluzie, se efectueaza
p :=p*val ;.Apoi, functia se autoapeleaza :

2 3 Referinta catre p
1 1 3 Referinta catre p

p val n prod

Pentru ca val este 2 –mai mic ca 3 - se efectueaza prod :=prod*val ; dupa care


functia se autoapeleaza.

2
p val n prod

136
Pentru ca val este 3 –mai mic sau egal cu 3 – se efectueaza prod :=prod*val, deci p
ia valoarea 6, dupa care se revine pe nivelul 2, apoi 1, apoi in programul principal.

Observatii :

▫Dupa cum rezulta din aceste exemple, subprogramul lucreaza cu datele aflate pe un
anumit nivel al stivei pentru variabilele transmise prin valoare si variabilele locale
ale sale, dar si cu variabilele functiei main( ) , daca acesteav sunt transmise prin
referinta.

▫Exista posibilitatea ca subprogramul sa lucreze direct cu variabilele globale far aca


acestea sa fie transmise prin referinta.Dupa cum stim variabilele globale pot fi
accesate din orice subprogram , daca sunt declarate la inceputul textului sursa.In
exemplu, am preferat varianta de a trece ca parametru variabila transmisa prin
referinta din motive didactice si pentru a asigura independenta
subprogramului.Dezavantajul este dat de faptul ca stiva va fi incarcata suplimentar cu
adresa acestei variabile.

▫In cazul unui numar mare de autoapelari , exista posibilitatea ca segmentul de stiva
sa se ocupe total, in caz in care programul se va termina cu eroare.

▫Recursivitatea presupune mai multa memorie(ma refer la stiva), aceasta este oricum
rezervata.

4.3 Cum gandim un algoritm recursiv

Pentru a ne familiariza cu rationamentul recursiv vom porni de la cateva exemple


intuitive.

1.Wirth.O camera de luat vederi are in obiectiv un televizor care transmite imaginile
primite de la camera.Evident, in televizor se va vedea un televizor, iar in acesta
un televizor...s.a.m.d

Pe scurt :
▪ in orice televizor, se vede un televizor.

2.Anunt. In anumite restaurante am intalnit anuntul : Azi nu se fumeaza !.

137
3.Constatare. Intr-o tara cu nivel de trai scazut, orice partid care ajunge la putere
afirma:de vina nu suntem noi, sunt cei de la care am preluat puterea!.

4.Logica. Orice militar aflat in ciclu 2 de instruire afirma :cand eram in ciclu 1, cei
din ciclu 2 ‘m-au chinuit inutil’.Asa fac si eu cu cei din ciclu 1.

Lasand gluma la o parte, constatam ca, in general, o gandire recursiva exprima


concentrat o anumita stare, care se repeta la infinit. Aceasta gandire se aplica in
elaborarea algoritmilor recursivi, dar cu o modificare esentiala :adaugarea conditiei
de terminare.In absenta acestei conditii, nu poate fi vorba de algoritm, intrucat
algoritmul trebuie sa fie finit.

→In elaborarea algoritmilor recursivi se aplica rationamentul : ce se intampla la un


nivel, se intampla la orice nivel.

→Subprogramul care se autoapeleaza trebuie sa contina instructiunile


corespunzatoare unui nivel.

→Starea tratata de subprogram se gaseste pe un anumit nivel al stivei (variabilele


rezultate in urma transmiterii parametrilor si variabilele locale ale subprogramului).

Observatii

▪Un algoritm recursiv se elaboreaza utilizand acest tip de gandire, nu o gandire


precum cea folosita pana acum, cand am elaborat algoritmi iterativi.

▪Pentru orice algoritm recursiv exista unul iterativ care rezolva aceeasi problema.

▪Nu intodeauna alegerea unui algoritm recursiv reprezinta un avantaj.

▪Numeroase exemple care urmeaza va vor lamuri asupra celor afirmate.

4.4 Aplicatii recursive

□ Se citeste x € Z. Se cere programul pentru calculul functiei :

x-1, x>=12
F(x)=
F(F(x+2)),x<12

Aceasta aplicatie a fost tratata iterativ, prin utilizarea stivei.In cazul tratarii recursive ,
nu faceam altceva decat sa transcriem definitia recursiva a functiei.

138
#include <iostream.h>
int x ;
int manna (int x)
{if (x>=12) return x-1;
else return manna(manna(x+2));
}

main( )
{ cout<<”x=”;cin>>x;
cout<<manna(x) ;
}

▪In comparatie cu abordarea iterativa, abordarea recursiva prezinta avantajul scrierii


concentrate.

□ Se da functia, definita pe NxN. Se citesc m si n.Sa se calculeze Ack(m,n).

n+1, m=0
Ack(m,n)= Ack(m-1,1), n=0
Ack(m-1,Ack(m,n-1)),astfel

Si aceasta aplicatie a fost tratata iterative, prin utilizarea stivei. In cazul tratarii
recursive, nu facem altceva decat sa transcriem definitia recursive a functiei.
Algoritmul recursiv nu necesita comentarii.

#include <iostream.h>
int m,n;

int Ack(int m,int n)


{ if (!m) return n+1;
else if (!n) return Ack(m-1, 1);
else return Ack(m-1, Ack(m,n-1));
}
main( )
{ cout <<”m=”;cin>>m ;
cout<<”n=”; cin>>n;
cout <<Ack(m,n) ;
}

▪In comparatie cu abordarea iterativa, abordarea recursiva prezinta avantajul scrierii


concentrate si acela al scutirii programatorului de un efort suplimentar in elaborarea
algoritmului.

□Sirul lui Fibonacci se considera sirul definit astfel :

0, n=0

139
Un= 1, n=1
Un+1+Un-2 astfel

Se citeste n,numar natural. Sa se calculeze Un.


Functia fib transcrie defenitia recursiva :

#include <iostream.h>
int n ;
int fib (int n)
{ if (!n) return 0;
else if (n= =) return 1;
else return fib(n-1)+fib(n-2);
}
main( )
{ cout<<”n=”;cin>>n;
cout<<fib(n) ;
}

▪In aceasta situatie,este corect sa se foloseasca un program care calculeaza Un


iterativ.Sa ne imaginam cum functioneaza aceasta functie.

Pentru calculul lui fib(n) este necesar sa se cunoasca fib(n-1) si fib(n-2).Parametrii


acestor functii sunt depusi in stiva.Procedeul continua pana este calculat fib(n-1), apoi
se reia calculul pentru fib(n-2).Acest lucru este extrem de ineficient pentru valori
mari ale lui n (ex.n=100).
Se calculeaza U100 ca suma intre U99 si U98.Pentru calculul lui U99 se calculeaza U98 si
U97. Dupa ce calculam U99,reluam calculele pentru U98.

O astfel de recursivitate se numeste recursivitate in cascada.Rulati programul pentru


o valoare mai mare a lui n si … asteptati.Cat de simplu se rezolva problema iterativ si
cat este de rapida aceasta metoda…

Prezentam varianta iterativa, care nu necesita comentarii.

#include <iostream.h>
main( )
{ int n, f0=0,f1=1,f2;
cout<<”n=”;cin>>n;
if (!n) cout<<f0;
else
if (n= =1) cout<<f1;
else
{ for (int i=2; i<=n; i++)
{f2=f0+f1; f0=f1; f1=f2;}
cout<<f2;
}
}

140
□ Se dau doua numere naturale a si b.Se cere sa se calculeze cel mai mare divizor
comun al lor.

Pentru rezolvare, utilizam o definitie recursiva a celui mai mare divizor comun pentru
doua numere naturale a si b.

a, a=b
cmmdc(a,b)= cmmdc(a-b,b), a>b
cmmdc(a,b-a), a<b

Aceasta definitie este transcrisa in functie recursive cmmdc.

#include <iostream.h>
int a,b;

int cmmdc (int a,int b)


{ if (a= =b) return a;
else
if (a>b) return cmmdc(a-b,b);
else return cmmdc(a,b-a);
}

main( )
{ cout<<”a=”;cin>>a;
cout<<”b=”;cin>>b;
cout<<cmmdc(a,b);
}

Rezolvam aceeasi problema iterative (utilizand definitia de mai sus).

#include <iostream.h>
main( )
{ int a,b ;
cout<<”a=”; cin>>a;
cout<<”b=”;cin>>b;
while (a!=b)
if (a>b) a=a-b;
else b=b-a;
cout<<”cmmdc=”<<a;
}

▪Pentru aceasta problema, este indifferent ce varianta alegem.

141
□Sa se scrie o functie recursive pentru a calcula suma cifrelor unui numar natural.

Initial, prezentam varianta iterativa :

#include <iostream.h>
main( )
{ int n,s=0 ;
cout<<”n=”;cin>>n;
while (n)
{s+=n%10; n/=10;}
cout<<”s=”<<s;
}

Retinem idea:se izoleaza ultima cifra, iar lui n i se atribuie catul intreg dintre vechea
valoare si 10.Aceasta idee foloseste pentru a gasi o relatie de recurenta, necesara
elaborarii variantei recursive :

0, n=0
S(n)=
n%10+S(n/10), astfel

Programul de mai jos, calculeaza suma utilizand relatia prezentata.

#include <iostream.h>
int n ;
int s(int n)
{ if (!n) return 0;
else return n%10 +s(n/10);
}
main( )
{ cout<<”n=”; cin>>n;
cout<<s(n) ;
}

□ Sa se scrie o functie recursiva care citeste caractere si le afiseaza in ordinea inversa


citirii.Sfarsitul sirului este marcat de caracterul »0 ».

Comform principiului, ce se intampla la un nivel se intampla la orice nivel. Vom


gandi functia recursiva astfel :

▪ se citeste un caracter ;
▪ daca este diferit de 0, se reapeleaza functia ;
▪ se tipareste caracterul ;

Rationamentul este suficient pentru a scrie programul.

#include <iostream.h>
void inv( )

142
{ char a; cin>>a;
if (a!=’0’) inv( );
cout<<a;
}
main( )
{ inv( );
cout<<endl;
}

Exercitiu. Modificati programul astfel incat caracterul 0 –care marcheaza sfarsitul


sirului- sa nu fie tiparit.

□ Sa se scrie o functie recursiva pentru a transforma un numar natural n, din baza 10


in baza k (1<k<10).

Sa ne amintim algoritmul clasic de trecere din baza 10 in baza k. Numarul se


imparte la k, se retine restul, catul se imparte la k, se retine restul… pana cand catul
este mai mic decat impartitorul. Rezultatul se obtine prin scrierea in ordine inversa a
resturilor obtinute. Practic, tiparirea restului se face dupa autoapel (ca si la problema
anterioara).

#include <iostream.h>
int n,b ;
void transform (int n,int b)
{int rest=n%b;
if (n>=b) transform(n/b,b);
cout<<rest;
}
main( )
{ cout<<”n=”;cin>>n;
cout<<”baza=”;cin>>b;
transform(n,b);
}

Exista posibilitatea ca un subprogram sa se autoapeleze prin intermediul altui


subprogram.Din moment ce s-a realizat autoapelul, este vorba de recursivitate si cum
aceasta nu s-a realizat direct –ca in exemplele anterioare- o astfel de recursivitate se
numeste indirecta.

□ Se considera sirurile definite recurent astfel :a0=a ; b0=b ; a,b>0 :

an=an-1+bn-1/2, bn=radical an-1 bn-1


Sa se scrie un program care sa citeasca a,b si n si sa se calculeze an si bn.

#include <iostream.h>
#include <math.h>
double a,b;

143
int n;

double bn (int n);


double an (int n);
{ if (!n) return a;
else return (an(n-1)+bn(n-1))/2;
}

double bn (int n)
{ if (!n) return b;
else return sqrt(an(n-1)*bn(n-1));
}

main( )
{ cout<<”a=”;cin>>a;
cout<<”b=”;cin>>b;
cout<<”n=”;cin>>n;
cout<<an(n)<<’ ‘<<bn(n) ;
}

□ Generarea recursiva a produsului cartezian se citeste n, numar natural, care are


semnificatia de numar de multimi.Se citesc, de asemenea numerele, naturale n1,n2,
….nn unde ni reprezinta numarul de elemente al multimii Ai.Se cere sa se calculeze
produsul cartezian al multimilor A1,A2…An.Se considera ca elementele multimii i
sunt {1,2,…,ni}.

▪ Daca n este 1, avem o singura multime A1.In acest caz programul afiseaza : {1},{2},
…,{n1}.

▪ Daca n este 2,avem doua multimi, caz in care se afiseaza :{1,1},{1,2},…{1,n1},


{2,1},{2,2}…{2,n2}…{n1,n2}.

Fiind dat un element al produsului cartezian al multimilor A1,…,Ap de forma (i1,i2,


…,ip), putem obtine urmatoarele elemente ale produsului cartezian al primelor p+1
elemente, daca dam pe rand , componentei p+1 valorile 1,2,…,np+1.

Pentru fiecare element al produsului cartezian al primelor p+1 multimi apelam din
nou functia (din cadrul ei, deci recursiv).Cand s-a ajuns la nivelul n+1, se tipareste
rezultatul.

In program, vectorul p retine numarul de elemente al celor n multimi, iar elementele


produsului cartezian se construiesc in cadrul vectorului pc.

#include <iostream.h>
int p[9],pc[9],n ;

void tipar( )

144
{ for (int i=1; i<=n;i++) cout<<pc[i];
cout<<endl;
}

void pcart(int pc[9], int pas, int n)


{ if (pas= =n+1) tipar( ) ;
else
for (int i=1 ;i<=p[pas]; i++)
{ pc[pas]=I;
pcart(pc,pas+1,n);
}
}
main( )
{ cout<< »numar de multimi » ; cin>>n ;
for (int i=1 ; i<=n,i++)
{ cout<<”numar elemente multimea “<<i<<’ ‘;
cin>>p[i];
pcart(pc,1,n);
}
}

□ Generarea permutarilor se da multimea {1,2,…,n}. Se cer toate permutarile acestei


multimi.

Exemplu :n=3 : {3,1,2}, {2,3,1},{2,1,3}, {3,2,1},{1,2,3},{1,3,2}.

In vederea rezolvarii problemei observam urmatoarele :

▪ multimea cu un singur element {1} are o singura permutare: {1};

▪ din fiecare permutare a multimii {1,2,…,n-1} ({a1,a2,…,an-1}) se obtin urmatoarele


permutari ale multimii {1,2,…,n}:

{n,a2,a3,…,an-1,a1};
{a1,n,a3,…,an-1,a2};
{a1,a2,n,…,an-1,a3};

{a1,a2,a3,…,an-1,an}.

Avem in vedere posibilitatea de revenire la situatia initiala: dup ace am operat o


interschimbare a elementelor ai si aj, urmata de o reapelare a functiei pentru valoarea
k+1, interschimbam din nou ai cu aj.

#include <iostream.h>
int p[10],n;

void tipar( )
{ for (int i=1;i<=n,i++) cout<<p[i];

145
cout<<endl;
}

void permut (int k,int n, int p[10])


{ int I,c;
if (k= =n+1) tipar( );
else
{p[k]=k
for (i=1; i<=k;i++)
{c=p[i]; p[i]=p[k]; p[k]=c;
permut( k+1,n,p);
c=p[i];p[i]=p[k];p[k]=c;
}
}
}

main( )
{ cout<<”n=”;cin>>n;
permut(1,n,p) ;
}

□ Generarea aranjamentelor. Se citesc n si k (k<=n) numere naturale.Se considera ca


o multime A are elementele {1,2,…,n}.Se cere sa se afiseze toate aranjamentele de n
luate cate k.

Daca k este 1 se va tipari {1},{2},…,{n}.

Sa presupunem ca am generat o submultime cu p (p<=k) elemente {i1,i2,…,ip}.Cu


ajutorul ei se pot genera submultimi cu p+1 elemente, Pentru aceasta,procedam , in
felul urmator :

▪ se calculeaza maximul dintre i1,i2,…ip;

▪ componenta p+1 va lua valori incepand de la valoarea maxima gasita la care se


adauga 1, pana la valoarea n;

▪ la fiecare noua valoare a componentei p+1, se inverseaza, pe rand, componenta p+1


cu componentele 1,2,…,p se reapeleaza functia, dupa care se revine la ordinea initiala
(se procedeaza asa, pentru a obtine o submultime cu p+1 elemente in toate modurile
posibile).

#include <iostream.h>
int c[10],n,k ;

void tipar( )
{
for (int i=1;i<=k,i++) cout<<c[i];

146
cout<<endl;
}

void aranj(int c[10],int k, int pas)


{
int i,j,max,m
if (pas= =k+1) tipar( ) ;
else
if (pas= =1)
for (i=1,i<=n,i++);
}
else
{
max=c[1];
for (i=2;i<=pas-1;i++)
if (c[i]>max) max=c[i];
for (i=max+1;i<=n;i++)
{
c[pas]=i;
for (j=1;j<=pas;j++)

{
m=c[pas]; c[pas]=c[j]; c[j]=m;
aranj(c,k,pas+1) ;
m=c[pas];c[pas]=c[j]; c[j]=m;
}
}
}
}
main ( )
{ cout<<”n=”,cin>>n;
cout<<”k=”;cin>>k;
aranj (c,k,1);

o Generarea recursiva a combinarilor.Se citesc doua numere naturale,n si k


(k≤n).Se cinsidera o multime cu elementele {1,2,3…n} si se cere sa se
tipareasca toate submultimile sale care au k elemente-combinari de n luate
cate k.
Daca k este 1,trebuie sa se tiparesca toate submultimile cu un singur
element.Acestea sunt: {1},{2},…,{n}.

Intrucat elementele unei submultimi sunt distincte,le vom retine intr-un vector in
ordine crescatoare.
 Presupunem ca am generat o submultime cu p elemente (p<k)a multimii
A.Aceasta este {i1,i2,..,ip},i1<i2<…ip.Utilizand aceasta submultime,se pot
genera urmatoarele submultimi cu p+1 elemente ale multimii A(componenta

147
p+1 va lua valorile de la ip+1 pana la n,pentru ca,pentru a evita repetitia,o
solutie contine numere strict crescatoare):
{i1,i2,…,ip,ip+1}
{i1,i2,…,ip,ip+2}

{i1,i2,…,ip,in}.

#include <iostream.h>
int c[10],n,k;

void tipar( )
{ for (int i=1;i<=k;i++) cout<<c[i[;
cout<<endl;
}

void combin(int c[10],int k,int pas)


{ int i;
if (pas==k+1) tipar( );
else
if (pas==1)
for (i=1;j<=n;i++)
{ c[pas]=I;
combin(c,k,pas+1);
}
else
for (i=c[pas-1]+1;i<=n,i++)
{ c[pas]=I;
combin(c,k,pas+1);
}
}
main( )
{cout<<”n=”;cin>>n;
cout<<”k=”;cin>>k;
combin(c,k,1);
}
o Generarea partitiilor unei multimi.Se considera multimea {1,2,3…,n}.Se
cer toate partitiile acestei multimi.
Partitiile unei multimi se vor obtine recursive astfel:
 O multime cu un element {1} are o singura partitie formata dintr-o
singura multime: {1};
 Din fiecare partitie a multimii {1,2,…,n-1}, A1,A2,…,Ap obtinem
p+1 partitii ale multimii {1,2,…,n},astfel:
PARTITIA 1:A1 U{n},A2,…,Ap;
PARTITIA 2:A1, A2 U{n},…,Ap;
………………..............
PARTITIA p: A1,A2,…Ap-1,Ap U{n};
PARTITIA p+1 A1,A2,…,Ap, {n}.
Exemplu:

148
n=1 {1};
n=2 {1,2};
{1},{2};
n=3 {1,2,3};
{1,2},{3};
{1,3},{2};
{1},{2,3};
{1},{2},{3}.
O partitie se reprezinta utilizand un vector p cu n componente.P(i)=k semnifica
faptul ca elemental I al multimii {1,2,…,n} se gaseste in submultimea k a partitiei.
 Este neceasr ca submultimile sa fie numeroatte utilizand numere
consecutive.
Utilizand acesata conventie sa generam toate partitiile pentru n=3;
n=1 1 {1};
n=2 11 {1,2};
12 {1},{2};
n=3 111 {1,2,3};
112 {1,2},{3};
121 {1,3},{2};
122 {1},{2,3};
123 {1},{2 ,{3}.
→ Fiind data o partitie a multimii {1,2,…k-1} sub forma de vector,pentru a obtine
partitiile corespunzatoare ei ale multimii {1,2..,k},componenta k a vectorului ia valori
intre 1 si maximul dintre componentele 1,2,…k-1, la care se aduga 1.
Aceasta este si idea de realizare a programului.

#include <iostream.h>
int p[10],n;

void tipar( )
{
int i,j,max;
cout<<”-----Partitie------“<<endl;
max=1;
for (i=2;i<=n;i++)
if (max<p[i])max=p[i];
for (i=1;i<=max);i++
{
cout<<”multimea”<<i<< ‘ ‘;
for (j=1;j<=n;j++)
if (p[j]==i)cout<<j;
cout<<endl;
}
}
void part(int k,int n,int p[10])
{
int max,i;
if (k==n+1) tipar( );

149
else
{ max=0;
for (i=1;i<=k-1;i++)
if (max<p[i]) max=p[i];
for (i=1;i<=max+1;i++)
{p[k]=I;
part(k+1,n,p);
}
}
}
main( )
{
cout<<”n=”;cin>>n;
part(1,n,p);
}

4.5 Metode Divide et impera


4.5.2 Aplicatii
o Se citeste un vector cu n componente,numere naturale.Se cere sa se
tipareasca valoarea maxima.
Problema de ami sus este binecunoscuta.Cum o rezolvam utilizand metoda divide et
impera?
Trebuie tiparita valoarea maxima dintre numerele retinute in vector de la i la j(initial
i=1,j=n).Pt acesta procedam astfel:
 Daca i=j,valoarea amxima va fi v[i];
 Contrar vom imparti vectorul in doi vectori(primul vector va contine
componentele de la i la (i+j) div 2,al doilea va contine componentele de la
(i+j)div 2+1 la j),rezolvam subproblemele(aflam maximul pentru fiecare
dintre ele) iar solutia problemei va fi data de valoarea maxima dintre
rezultatele celor doua subprobleme.

#include<iostream.h>
int v[10],n;

int max(int i,int j)


{ int a,b;
if (i==j) return v[i];
else
{ a=max(i,(i+j)/2);
b=max((i+j)/2+1,j);
if (a>b) return a;
else return b;
}
}
main ( )

150
{ cout<<”n=”;cin>>n;
for (int i=1;i<=n;i++)
{cout<<”v[“<<i<<”]=”;cin>>v[i];}
cout<<”max=”<<max(1,n);
}

o Cautare binara.Se citeste un vector cu n componente numere intregi,unde


numerele se presupun ordonate crescator si o valoare intreaga(nr).Sa se
decida daca nr se gaseste sau nu printer numerele citite,iar in caz afirmativ
sa se tipareasca indicele componentei care contine acea valoare.
Problema este de a decide daca valoarea cautata se gaseste printer numerele de indice
cuprins intre i si j (initial i=1,j=n).Pt aceasta vom proceda astfel:
 Daca nr coincide cu valoarea de indice (i+j)/2 (valoarea de la mijloc),se
tipareste indicele si se revine din apel(problema a fost rezolvata);
Contrar,daca i<j (ni s-a cautat peste tot) problema se descompune astfel:
 Daca numarul este mai mic decat valoarea testate(din mijloc),inseamna ca
avem sanse sa-l gasim intre componentele cu indicele intre i si (i+j)/2-1,caz in
care reapelam functia cu acesti parametrii
 Daca numarul este mai amre decat valoarea testate(din mijloc),inseamna ca
avem sanse sa-l gasimb intre componentele cu indicele intre (i+j)/2+1 si j,caz
in care reapelam functia cu acesti parametrii.
#include <iostream.h>
int v[100],n,nr;

void caut(int i,int j)


{ if (nr==v[(i+j)/2])
cout<<”gasit”<<’ ‘<<”indice”<<(i+j)/2;
else
if(i<j)
if (nr<v[(i+j)/2])
caut(i,(i+j)/2-1);
else caut ((i+j)/2+1,j);
};
main( )
{ cout<<”n=”;cin>>n;
for (int i=1;i<=n;i++)
{ cout<<”v[“<<i<<”]=”;cin>>v[i];}
cout<<”nr=”;cin>>nr;
caut(1,n);
}

o Sortare prin interclasare.Se considera vectorul a cu n componente numere


intregi(sau reale).Sa se sorteze crescator,utilizand sortarea prin
interclasare.
Algoritmul de sortare prin interclasare se bazeaza pe urmatoarea idée:pentru a sorta
un vector cu n elemante il impartim in doi vectori care,odata sortati,se interclaseaza.

151
Conform strategiei generale Divide et impera,problema este descompusa in alte doua
subprobleme de acelasi tip si,dupa rezolvarea lor,rezultatele se combina(in particular
se interclaseaza).Descompunerea unui vector in alti doi vectori care urmeaza a fi
sortati are loc pana cand avem de sortat vetori de una sau doua componente.
In aplicatie,functia sort sorteaza un vector cu maximum doua elemente;interc
interclaseaza rezultatele ;divimp implementeaza strategia generala a metodei studiate.
#include <iostraem.h>
int a[10],n;

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


{
int m;
if (a[p]>a[q])
{
m=a[p];
a[p]=a[q];
a[q]=m;}
}
void interc(int p,int q,int m,int a[10])
{
int b[10],I,j,k;
i=p;j=m+1;k=1;
while (i<=m && j<=q)
if (a[i]<=a[j])
{
b[k]=a[i];
i=i+1;
k=k+1;
}
else
{
b[k]=a[j];
j=j+1;
k=k+1;
}
if (i<=m)
for (j=i;j<=m;j++)
{
b[k]=a[j];
k=k+1;
}
else
for (i=j;j<=q;j++)
{
b[k]=a[i];
k=k+1;
}
k=1;

152
for (i=p;i<=q;i++)
{
a[i]=b[k];
k=k+1;
}
}
void divimp (int p,int q,int a[10])
{
int m;
if ((q-p)<=1) sort(p,q,a);
else
{ m=(p+q)/2;
divimp(p,m,a);
divimp(m+1,q,a);
interc(p,q,m,a);
}
}
main( )
{ int i;
cout<<”n=”;cin>>n;
for (i=1;i<=n;i++)
{
cout<<”a[“<<i<<”]=”;cin>>a[i];}
divimp(1,n,a);
for (i=1;i<=n;i++)
cout<<a[i]<<” “;
}

o Sortare rapida.Fie vectorul a cu n numere intregi(sau reale).Se cere ca


vectorul sa fie sortat crescator.

Pentru rezolvare este necesara o functie poz care trateaza o portiune din vector
cuprinsa intre indicii dati de li (limita inferioara) si ls(limita superioara).Rolul acestei
functii este de a pozitiona prima componenta a[li] pe o pozitie k cuprinsa intre li si
ls,astfel incat toate componentele vectorului cuprinse intre li si k-1 sa fie mai mici sau
egale decat a[k] si toate componentele vectorului cuprinse intre k+1 si ls sa fie mai
mari sau egale decat a[k].
In aceasta functie exista doua moduri de lucru:
a) i ramane constant,j scade cu 1;
b) i creste cu 1,j ramane constant.
Functia este conceputa astfel:
 initial,i va lua valoarea li,iar j va lua valoarea ls (elemental care initial
se afla pe pozitia li se va gasi mereu pe o pozitie data de i sau de j);
 se trece in modul de lucru a);
 atata timp cat i<j,se executa:
-daca a[i] este strict mai amre decat a[j],atunci se inverseaza celee
doua numere si se schimba modul de lucru;

153
-i si j se modifica corespunzator modului de lucru in care se afla
programul;
-k ia vlaoraea comuna a lui i si j.
 Dupa aplicarea functiei poz,este evident ca elementul care se afla initial
in pozitia li va ajunge pe o pozitie k si va ramane pe acea pozitie in
cadrul vectorului deja sortat,fapt care reprezinta esenta algoritmului.
Functoa quick are parametrii li si ls(limita inferioara si limita superioara).In cadrul ei
se utilizeaza metoda Divide et impera,dupa cum urmeaza:
-se apeleaza poz;
-se apeleaza quick pentru li si k-1;
-se apeleaza quick pentru k+1 si ls.
#include<iostream.h>
int a[100],n,k;

void poz(int li,int ls,int& k,int a[100])


{
int i=li,j=ls,c,i1=0,j1=-1;
while (i<j)
{ if (a[i]>a[j])
{
c=a[j];
a[j]=a[i];
a[i]=c;
c=i1;
i1=-j1;
j1=-c;
}
i=i+i1;
j=j+j1;
}
k=i;
}

void quick (int li,int ls)


{ if (li<ls)
{ poz (li,ls,k,a);
quick(li,k-1);
quick(k+1,ls);
}
}
main( )
{ int I;
cout<<”n=”;cin>>n;
for (i=1;i<=n;i++)
{ cout<<”a[“<<i<<”]=”;cin>>a[i];}
quick(1,n);
for (i=1;i<=n;i++) cout <<a[i]<<endl;
}

154
o Turnurile din Hanoi.Se dau 3 tije simbolizate prin a,b,c.Pe tija a se gasesc
discuri de diameter diferite,asezate in orine descrescatoare a diametrelor
private de jos in sus.Se cere sa se mute discurile de pe tija a pe tija
b,utilizand ca tija intermediara tija c,respectand urmatoarele reguli:
 La fiecare pas se muta un singur disc;
 Nu este permis sa se aseze un disc cu diametrul mai mare peste un disc cu
diametrul mai mic.
Rezolvare:
Daca n=1 se face mutarea ab,adica se muta discul de pe tija a pe tija b.
Daca n=2 se fac mutarile ac,ab,cb.
In cazul in care n>2 problema se complica.Notam cu H(n,a,b,c) sirul mutarilor celor n
discuri de pe tija a pe tija b,utilizand ca tija intermediara,toja c.

Conform strategiei Divede et impera incercam sa descompunem problema in alte


doua subprobleme de acelasi tip,urmand apoi combinarea solutiilor.In acest
sens,observam ca mutarea celor n discuri de pe tija a pe tija b,utilizand ca tija
intermediara tija c,este echivalenta cu:
 Mutarea a n-1 discuri de pe tija a pe tija c,utilizand ca tija intermediara tija b;
 Mutarea discului ramas pe tija b;
 Mutarea a n-1 discuri de pe tija c pe tija b,utilizand ca tija intermediara tija a.
Parcurgerea celor trei etape permite definirea recursive a sirului H(n,a,b,c) astfel:
| ab, daca n=1
H(n,a,b,c)= |
| H(n-1,a,c,b),ab,H(n-1,c,b,a), daca n>1
Exemple:
Pentru n=2 avem: H(2,a,b,c)=H(1,a,c,b),ab,H(1,c,b,a)=ac,ab,cb.
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.

#include <iostream.h>
char a,b,c;
int n;

void han(int n,char a,char b,char c)


{
if (n==1) cout<<a<<b<<endl;
else
{
han(n-1,a,c,b);
cout<<a<<b<<endl;
han(n-1,c,b,a);
}
}
mian ( )
{
cout<<”N=”;cin>>n;

155
a=’a’;b=’b’;c=’c’;
han(n,a,b,c);
}

o Se da o bucata dreptunghiulara de tabla de lungime 1 si inaltimea h,avand pe


suprafata ei n gauri de coordinate numere intregi.Se cere sa se decupeze din ea
o bucata de arie maxima care nu prezinta gauri.Sunt premise numai taieturi
verticale si orizontale.
Coordonatele gaurilor sunt retinute in doi vectori xv si yv.Dreptunghiul initial,precum
si dreptunghiurile care apar in procesul taierii sunt memorate in program prin
coordonatele coltului din stanga-sus (x,y),prin lungime si inaltime (L,H).

Pentru un dreptunghi (initial pornim cu toata bucata de tabla),verificam daca avem


sau nu o gaura in el(se cauta practice prima din cele n gauri).In situatia cand acesta
prezinta o gaura,problema se descompune in alte patru probleme de acelasi tip.Daca
bucata nu prezinta gauri,se compara aria ei cu aria unei alte bucati fara gaura,gasita in
fazele precedente.

Mentionam ca dreptunghiul de arie maxima fara gauri este retinut prin aceiasi
parametric ca si dreptunghiul cu gauri,in zonele XF,YF,LF,HF

In concluzie,problema initiala se descompune in alte patru probleme de acelasi tip,mai


usoare,intrucat fiecare nou dreptunghi are cel mult n-1 gauri,daca dreptunghiul initial
avea n gauri.La aceasta problema compararea solutiilor consta in a retine dreptunghiul
cu aria maxima dintre cele fara gauri.

Fie dreptungiul cu o gaura : h


 xv(i),yv(i)
x,y
Pentru a se afla in interiorul dreptunghiului,gaura trebuie sa indeplineasca simultan
conditiile:
1) xv(i)>x;
2) xv(i)<x+1;
3) yv(i)>y;
4) yv(i)<y+h.
Daca facem o taietura verticala prin aceasta gaura,obtinem doua dreptunghiuri:
1) x,y,xv(i)-x,h;
2) xv,yv(i),1+x-xv(i),h.
In urma unei taieturi pe orizontala se obtin cele doua dreptunghiuri:
1) x,y,1,yv(i)-y;
2) x(i),yv(i),1,h+y-yv(i).

#include <iostream.h>
int 1,h,I,n,xf,yf,lf,hf,xv[10],yv[10];

void dimp(int x,int y,int l,int h,int& xf,int&yf,int& lf,int& hf,int xv[10],int yv[10])
{ int gasit=0,i=1;
while (i<=n && !gasit)

156
if (xv[i]>x && xv[i]<l && yv[i]>y && yv[i]<y+h)
gasit=1;
else i++;
if (gasit)
{ dimp(x,y,xv[i]-x,h,xf,yf,lf,hf,xv,yv);
dimp(xv[i],y,l+x-xv[i],h,xf,yf,lf,hf,xv,yv);
dimp(x,y,l,yv[i]-y,xf,yf,lf,hf,xv,yv);
dimp(x,yv[i],l,h+y-yv[i],xf,yf,lf,hf,xv,yv);
}
else
if (l*h>lf*hf)
{ xf=x;yf=y;
lf=l;hf=h;
}
}
main ( )
{ cout<<”n=”;cin>>n;
for(int i=1;i<=n;i++)
{ cout<<”x[“<<i<<”]=”;cin>>xv[i];
cout<<”y[“<<i<<”]=”;cin>>yv[i];
}
cout<<”l=”;cin>>l;cout<<”h=”;cin>>h;
dimp(0,0,l,h,xf,yf,lf,hf,xv,yv);
cout<<”x=”<<xf<<”y=”<<yf<<”l=”<<”h=”<<hf;
}

Tehnica backtracking

5.1 Aspecte teoretice


Aceasta tehnica se foloseste in reezolvarea problemelor care indeplinesc simultan
urmatoarele conditii:
 solutia lor poate fi pusa sub forma unui vector S=x1,x2,..,xn,cu x1 apartine de
de A1,x2 apartine de A2,…,xn apartine de An;
 multimile A1,A2,…,An sunt multimi finite,iar elementele lor se considera ca
se afla intr-o relatie de ordine bine stabilita;
 nu se dispune de o alta metoda de rezolvare,mai rapida.
Observatii:
 nu pentru toate problemele n este cunoscut de la inceput;
 x1,x2,…,xn pot fi la randul lor vectori;
 in multe probleme,multimile A1,A2,…,An coincide.
Observatie: tehnica Backtracking are ca rezultat obtinerea tuturor solutiilor
problemei.In cazul in care se cere o singura solutie,se poate forta oprirea,atunci cand a
fost gasita.
 Pentru usurarea intelegerii metodei,vom prezenta o rutina unica(aplicabila
oricarei probleme),rutina care este elaborate folosind structura de stiva.Rutina
va apela functii care au intotdeauna acelasi nume si care,din punct de vedere al

157
metodei,realizeaza acelasi lucru.Sarcina rezolvitorului este sa scrie
explicit,pentru fiecare problema in parte,functiile apelate de rutina
backtracking.
 Evident o astfel de abordare conduce la programe lungi.Nimeni nun e opreste
ca,dupa intelegerea metodei,sa scriem programe scurte,specifice fiecarei
probleme in parte(de exemplu,scurtam substantial textul doar daca renuntam
utilizarea unor functii,scriind instructiunile lor chiar in corpul rutinei).
De exemplu,pentru generarea permutarilor multimii {1,2,3…,n},orice nivel al stivei
va lua valori de la 1 la n.Initializarea unui nivel(oarecare) se face cu valoarea
0.Functia de initializare se va numi init( ).
→ Gasirea urmatorului element al multimii Ak+1,element netestat,se face cu ajutorul
functiei int Am_Succesor( ).Daca exista successor,acesta este pus in stiva si functia
returneaza 1,altfel functia returneaza 0.
→ Testul daca s-a ajuns sau nu la solutia finala se face cu ajutorul functiei int
Solutie( )
→ Solutia se tipareste cu ajutorul functiei Tipar( ).
→Testarea conditiilor de continuare(adica daca avem sansa sau nu ca prin valoarea
aflata pe nivelul k+1 sa ajungem la solutie) se face cu functia int E_Vakid( )
careintoarce 1 daca conditiile sunt indeplinite,sau 0 in caz contrar.
Prezentam rutina backtracking:
void back( )
{int AS;
k=1;Init( );
while (k>0)
{
do { } while ((AS=AM_Succesor( )) && !E_Valid( ));
if (AS)
if (Solutie()))Tipar();
else {k++;Init( );}
else k--;
}
}

5.2 Aplicatii rezolvate iterativ


o Generarea permutarilor.Se citeste un numar natural n.Sa se genereze toate
permutarile multimii {1,2,..,n}.
Generarea permuatrilor se va face tinand cont ca orice permutare va fi alcatuita din
elemente distincte ale multimii A={1,2…,n}.
Prezentam algoritmul corespunzator cazului n=3;
1 2 3

1 2 2 2 2

1 1 1 1 1 1

158
1 2 3
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

 se incarca in stiva pe nivelul 1 valoarea 1;


 incarcarea valorii 1 pe nivelul al 2-lea nu este posibila,intrucat aceasta valoare
se gaseste sip e nivelul 1 al stivei;
 incarcarea valorii 2 pe nivelul al 2-lea este posibila,deoarece aceasta valoare
nu mai este intalnita;
 valoarea1 din nivelul al 3-lea se regaseste pe nivelul 1;
 valoarea 2 din nivelul al 3-lea se regaseste pe nivelul al 2-lea;
 valoarea 3 pe nivelul al 3-lea nu e intalnita pe nivelurile anterioare;intrucat
nivelul 3 este completat correct,afisam 1 2 3
….
Algoritmul continua pana cand stiva devine vida.

#include <iostream.h>
int st[10],n,k;

void Init( )
{ st[k]=0;}

int Am_Succesor( )
{ if st[k]<n)
{ st[k]++;
return 1;
}
else return 0;
}
int E_Valid( )
{ for (int i=1;i<k;i++)
if (st[i]==st[k]) return 0;
return 1;
}

int Solutie ( )
{ return k==n;}

void Tipar( )

159
{
for (int i=1;i<=n;i++) cout<<st[i];
cout<<endl;
}

void back( )
{int AS;
k=1;Init( );
while (k>0)
{ do {}while ((AS=Am_Succesor( )) && !E_Valid( ));
if (AS)
if (Solutie( )) Tipar( );
else (k++;Init( ));}
else k--;
}
}
main ( )
{ cout<<””n=;cin>>n;
back( );
}
o Problema celor n dame.Fiind data o tabla de sah nxn,se cer toate solutiile de
aranjare a n dame,astfel incat san u se afle doua dame pe aceeasi linie,coloana
sau diagonala(damele san u se atace reciproc).
Exemplu:Presupunand ca dispunem de o tabla de dimensiune 4X4,o solutie ar fi
urmatoarea:
D

D
D
D

Cum procedam?Observam, ca o dama trebuie sa fie plasata singura pe linie.Plasam


prima dama pe linia 1 coloana 1.

A doua dama nu poate fi asezata decat in coloana a 3-a.

160
D
D

Observam ca a treia daman u poate fi plasata in linia a 3-a.Incercam atunci plasarea


celei de-a doua dame in coloana a 4-a.
D
D

A treia daman u poate fi plasata decat in coloana a 2-a.

D
D
D

In aceasta situatie dama a patra nu mai poate fi asezata.Incercand sa avansam cu dama


a treia,observam ca nu este posibil sa o plasma nici in coloana a 3-a,nici in coloana a
4-a,deci o vom scoate de pe tabla.Dama a doua nu mai poate avansa,deci si ea este
scoasa de pe tabla.Avansam cu prima dama in coloana a 2-a.
D

A doua dama nu poate fi asezata decat in coloana a-4-a.

D
D

161
Dama a treia se aseaza in prima coloana.

D
D
D

Acum este posibil sa plasam a patra dama in coloana a-3-a si astfel am obtinut o
solutie a problemei.

D
D
D
D

Algoritmul continua in acest mod pana cand trebuie scoasa de pe tabla prima dama.

Pentru reprezentarea unei solutii putem folosi un vector cu n componente (avand in


vedere ca pe fiecare linie se gaseste o singura dama).Exemplu:pentru solutia gasita
avem vectorul st ce poate fi asimilat unei stive.
Doua dame se gasesc pe aceeasi diagonala daca si numai daca este indeplinita
conditia:l st(i)-st(j) l=li-jl: diferenta, in modul, intre linii si coloane este aceeasi).

ST(4)
ST(3)
ST(2)
ST(1)

In general ST(i)=k semnifica faptul ca pe linia i dama ocupa pozitia k.

Exemplu: in tabla 4x4 avem situatia:


D st(1)= 1 i=1
st(3)=3 j=3
D l st(1) – st(3) l = l 1-3 l=2
l i-j l = l 1-3 l= 2

3 sau situatia:
1
4 D st(1) = 3 i = 1
2 st(3) = 1 j=3
D | st(i) – st(j) l|= | 3-1 | = 2
| i-j | = |1-3 | = 2

162
Intrucat doua dame nu se pot gasi in aceeasi coloana, rezulta ca o solutie este sub
forma de permutare. O prima idee ne conduce la generarea tuturor permutarilor si la
extragerea solutiilor pentru problema ( ca doua dame sa nu fie plasate in aceeasi
diagonala).A proceda astfel, inseamna ca nu lucram conform strategiei
backtracking.Aceasta presupune ca imediat ce am gasit doua dame care se ataca, sa
reluam cautarea in alte conditii fata de programul de generare a permutarilor,
programul de generare a tuturor solutiilor problemei celor n dame, are o singura
conditie suplimentara, in functia E_valid.

#include <iostream.h>
#include <math.h>
int st[100],n,k;

void init( )
{ st[k]=0; }

int Am_Succesor ( )
{ if (st[k]<n)
{ st[k]++;
return 1;
}
else return 0;
}
int E_Valid ( )
{ for (int i=1; i<k; i++)
if (st[k]= =st[i] l l abs(st[k] – st[i])= =abs(k-1) ) return 0;
return 1;
}

int Solutie( )
{ return k= =n;}

void Tipar( )
{ for (int i=1; i<=n; i++) cout<<st[i];
cout<<endl;
}
void back( )
{ int AS; k=1;
Init( );
While (k>0)
{
do { } while ((AS=Am_Succesor( )) && !E_Valid( ));
if (AS)
if (Solutie( )) Tipar ( );
else {k++; Init( );}
else k- - ;

163
}
}

main( )
{ cout<<”n=”; cin>>n;
back( );
}

□ Produsul cartezian a n multimi se dau multimile de mai jos si se cere produsul


cartezian al lor.

A1={1,2,…,k1}
A2={1,2,…,k2}
……………….
An={1,2,…,kn}.

Exemplu: A1={1,2}, A2={1,2,3}, A3={1,2,3}. A1xA2xA3={(1,1,1),(1,1,2),(1,1,3),


(1,2,1),(1,2,2),(1,2,3),(2,2,2),(2,2,3),(2,3,1),(2,3,2),(2,3,3)}

Pentru rezolvare , se folosesc stiva ST si un vector A ce retine numerele


k1,k2,..,kn.Utilizam metoda backtracking, usor modificata din urmatoarele motive:

a) orice element aflat pe nivelul k al stivei este valid, motiv pentru care functia
E_Valid nu face decat sa returneze 1.
b) limita superioara pe nivelul k al stivei este data de a[k].

Modul de concepere a algoritmului rezulta din cele ce urmeaza:

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

3 3
2 3 1 2
1 3
2 2 3 3 1
1 1 1 1
……………………………......

Observatii:

▪ Acest algoritm a mai fost prezentat in capitolul anterior.

164
▪ Algoritmul prezentat aici este de tip backtracking? Intrebarea are sens pentru ca
este absent mecanismul de intoarcere. Vom admite ca si acesta este backtracking, dar
„degenerat”.

#include <iostream.h>
int st[10],a[10],n,k;

void init( )
{ st[k]=0; }

int Am_Succesor ( )
{ if (st[k]<a[k])
{ st[k]++; return 1;}
else return 0;
}
int E_Valid ( )
{ return 1;}

int Solutie( )
{ return k= =n;}

void Tipar( )
{ for (int i=1; i<=n;i++) cout<<st[i];
cout<<endl;
}

void back( )
{int AS;
k=1; Init( );
while (k>0)
{ do { } while ((AS=Am_Succesor( )) && !E_valid( ));
if (Solutie( )) Tipar( );
else {k++; Init( );}
else k- -;
}
}
main( )
{ cout<<”numar total de multimi=”; cin >>n;
for (int i=1; i<=n; i++)
{ cout<<”a[“<<i<<”]=”; cin>>a[i];}
back( );
}

□ Generare aranjamentelor. Se citesc n si p. Sa se genereze toate aranjamentele de n


luate cate p.

Din analiza problemei rezulta urmatoarele:

165
▪ stiva are inaltimea p;
▪ fiecare nivel ia valori intre 1 si n;
▪ elementele plasate pe diverse niveluri trebuie sa fie distincte.

Algoritmul este asemanator cu cel de la permutari, cu deosebirea ca aici stiva are


inaltimea p.

#include <iostream.h>
int st[10],n,k,p;

void Init ( )
{ st[k]=0; }

int Am_Succesor( )
{ if (st[k]<n)
{ st[k]++;
return 1;
}
else return 0;
}
int E_Valid( )
{
for (int i=1; i<k; i++)
if (st[k]= =st[i]) return 0;
return 1;
}
int Solutie( )
{ return k= =p;)

void Tipar( )
{
for (int i=1; i<=p;i++) cout<<st[i];
cout<<endl;
}

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

166
main ( )
{ cout<<”n=”; cin>>n;
cout<<”p=”; cin>>p;
back( );
}

□ Generarea combinarilor. Se citesc n si p numere naturale n>=p.Se cere sa se


genereze toate submultimile cu p elemente ale multimii{1,2,…,n}.

Pentru rezolvarea problemei trebuie tinut cont de urmatoarele:

▪ stiva are inaltimea p;


▪ elementele aflate pe niveluri diferite ale stivei trebuie sa fie distincte;
▪ pentru a evita repetitia elementele se aseaza in ordine crescatoare: pe nivelul k se va
afla o valoare mai mare decat pe nivelul k-1 si mai mica sau egala cu n-p+k.

#include <iostream.h>
int st[10],n,k,p;
void Init ( )
{ if (k>1) st[k]=st[k-1];
else st[k]=0;
}

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

int Solutie ( )
{ return k= =p;}

void Tipar ( )
{ for ( int i=1; i<=p;i++) cout <<st[i];
cout<<endl;
}

void back ( )
{int AS;
k=1; Init( );
while (k>0)
{
do { } while ((Am_Succesor( )) && !E_valid( ));
if(AS)
if (Solutie( )) Tipar( );

167
else {k++;Init( );}
else k- -;
}
}

main ( )
{ cout<<”n=”; cin>>n;
cout<<”p=”;cin>>p;
back( );
}

Felicitari celor care observa ca n-are rost sa apelam E_valid.Precizez ca prefer sa


lucram standardizat!

□ Problema colorarii hartilor fiind data o harta cu n tari, se cer toate solutiile de
colorare a hartii, utilizand cel mult 4 culori, astfel incat doua tari cu frontiera comuna
sa fie colorate diferit. Este demonstrat faptul ca sunt suficiente numai 4 culori pentru
ca orice harta sa poata fi colorata.

Pentru exemplificare, vom considera urmatoarea harta unde tarile sunt numerotate cu
cifre cuprinse intre 1 si 5.

O solutie a acestei probleme, este urmatoarea:

● tara 1 – culoarea 1;
● tara 2 - culoarea 2;
● tara 3 - culoarea 1;
● tara 4 - culoarea 3;
● tara 5 - culoarea 4.

Harta este furnizata programului cu ajutorul unei matrice (tablou) An,n.

1, tara i are frontiera comuna cu tara j;


A(i,j)=
0, astfel

Matricea A este simetrica.Pentru rezolvarea problemei se utilizeaza stiva st, unde


nivelul k al stivei simbolizeaza tara k, iar st[k] culoarea atasata tarii k. Stiva are
inaltimea n si pe fiecare nivel ia valori intre 1 si 4.
#include <iostream.h>
int st[10],a[20] [20],n,k;

void Init( )
{ st[k]=0; }

168
int Am_Succesor ( )
{ if (st[k]<4)
{st[k]++;
return 11;
}
else return 0;
}

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

void Tipar( )
{ cout<<”varianta”<<endl;
for (int i=1;i<’n; i++) cout”tara”<<i<<”culoarea”
<<st[i]<<endl;
cout<<endl;
}

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

main ( )
{ cout<<”numar de tari”; cin>>n;
for (int i=1; i<=n; i++)
for (int j=1; j<=i-1;j++)
{ cout<<”a[„<<i<<’,’<<j<<”]=”; cin>>a[i] [j]
a[j] [i]=a[i] [j];
{
back( )
}

169
□ Problema comis-voiajorului.Un comis-voiajor trebuie sa viziteze un numar n de
orase. Initial, acesta se afla intr-unul dintre ele, notat 1.
Comis-voiajorului doreste sa nu treaca de doua ori prin acelasi oras, iar la intoarcere
sa revina in orasul 1. Cunoscand legaturile existente intre orase, se cere sa se
tipareasca toate drumurile posibile pe care le poate efectua comis-voiajorul.

Comis-voiajorul are urmatoarele posibilitati 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.

Legaturile existente intre orase sunt date in matricea An,n. Elementele matricei A pot
fi 0 sau 1 ( matricea este binara).
1, daca exista drum intre orasele I si j
A(i,j)=
0, astfel.

Se observa ca A(I,j)=A(j,i), oricare ar fi i si j apartinand multimii {1,2,…,n} –


matricea este simetrica.

Pentru rezolvarea problemei folosim o stiva st .La baza stivei (nivelul1) se incarca
numarul 1. Prezentam in continuare modulul de rezolvare a problemei:

2 de la orasul 1 la orasul 2 exista drum, deci se va urca in stiva.


1

2 orasul 2 se mai gaseste in stiva, deci nu este acceptat.


2
1

3 de la orasul 2 la orasul 3 exista drum ; prin orasul 3 nu s-a mai trecut,


2 orasul 3 este acceptat.
1

170
Algoritmul continua in acest mod pana se ajunge din nou la nivelul 1, caz in care
algoritmul se incheie.

Un succesor, intre 2 si n, aflat pe nivelul k al stivei, este considerat valid daca sunt
ineplinite simultan urmatoarele conditii :

● nu s-a mai trecut prin orasul simbolizat de succesor, deci acesta nu se regaseste in
stiva ;
● exista drum intre orasul aflat pe nivelul k-1 si cela flat pe nivelul k ;
● daca succesorul se gaseste pe nivelul n, sa existe drum de le el le orasul 1.

BACKTRAKING RECURSIV
Problemele incluse in acest capitol sunt rezolvate prin metoda backtracking insa
caestea ca idee de cautare a solutiilor,nu ca rutina standardizata.
*Problema celor n dame.Fiind data o tabla de sah n*n,se cer toate solutiile de
aranjare a n dame astfel incat sa nu se afle doua dame pe aceeas linie,coloana sau
diagonala (damele sa nu se atace reciproc).
#include <iostream.h>
#include <math.h>
int t[20],n;
void tipar ( )
{for (int i:=1;i<=n;i++) cout<<t[i];
cout<<end1;}
void dame(int k)
{int i,j,corect;
if (k==n=1) tipar( );
else
{for (i=t[k]+1;i<=n;i++)}
t[k]=i;
corect=1;
for (i=t[k]+1;i<=n;i++)}
t[k]:=i;
corect=1;
for (j=1;j<=n;i==)
{t[k]:=i;
corect=1;
for (j=1;j<=k-1;j++)
if (t[j]==t[k] // abs (t[k]-t[j]==(k-j))
corect=0;
if (corect) dame (k+1);}}t[k]=0;}main( )}
cout<<"n=";cin>>n;
dame(1);
Generarea partitiilor unui numar natural :se citeste un numar natural n.Se cere sa
se tipareasca toate modurile de descompunere a lui ca suma de numere naturale .
Exemplu:n=4.Avem4,31,212,211,13,121,112,1111.
Ordinea numerelor din suma este importanta.Astfel,se tipareste 112 dar si 211,121.
Functia part are doi parametri:componenta la care s-a ajuns (k) si o valoare
v.Initial,aceasta este apelata pentru nivelul 1 si valoarea n.
Imediat ce este apelta functias va apela o alta pentru a tipari vectorul.Din valoarea
care se gaseste pe un nivel (s[k]) se scad,pe rand,valorile 1,2,s[k-1], valori cu care es
apeleaza functia pentru nivelul urmator.La revenire se reface valoarea existenta.In
concluzie putem apela:

171
-nivelul 1 la valoarea n;
-fiind dat un nivel oarecare se tipareste vectorul apoi se scad,pe rand ,toate valorile
posibile ,valori cu care functia este reapelata ,avand grija ca la revenire sa refacem
valoarea (pentru a se executa corect scaderea urmatoare ).
#include<iostream.h>
int s[20],n;
void tipar (int k)
{ for (int i=1;i<=k;i++) cout<<s[i];
cout<<end1;}void part (int k,int v)
{int i;
s[k]=v;
tipar(k);
for (i=1;i<=s[k]-1;i++)
{s[k]=s[k]-i;
part(k+1,i);
s[k]=s[k]+1;}}.
main( )
{cout<<"n=";cin>>n;
part(1,n);}

-Plata unei sume cu bacnote de valori date s .Se dau suma s si n tipuri de
monedeavand valori de a1,a2,.......,an lei,Se cer toate modalitatile de plata a sumei s
utilizand aceste monede.
Valorile celor n monede se retin in vectorul a.Se presupunem ca dispunem de atatea
monede din fiecare tip cate sunt necesare (acest numar se obtine in ipoteza ca toata
suma s este platita numai cu monede de ace;ls tip si se retine in vectorul b).O solutie
este sub forma unui vector cu n componente (sol) in componenta i retine numarul de
monedede tipul i care se folosesc pentru plata sumei s.Evident acest numar poate fi
si 0.Toate compo=nentele vectorului so1 sunt initializate cu -1(valoare aflata inaitea
valorilor posibile).Initializarea unei componente se face si atunci cand se revine la
componenta de indice imediat inferior.\
Functia plata ( ) are doi parametri :indicele componentei din so1 care urmeaza a fi
completata (k) si suma platita pana in acel moment (so0).Apelul sau (din cadrul
programului prinvcipalse face pentru prima componenta din suma 0.
Find dat un nivel oarecare (k) se procedeaza astfel:P
-atata timp cat este posibil sa utilizam in plata inca o moneda de tipul respectiv(nu se
depaseste suma care trebuie platita) se efectueaza urmatarele:
-se incrementeaza nuvelul(se foloseste o noua moneda de tipul k);
-suma platita se majoreaza cu valoarea ace;lei monede;
-in cazxul cand a fost obtinuta o solutie ,acesta se tipareste contrar se apeleaza
functia pentru nivelul urmator,cu noua valoare s0.
#include <iostream.h>
int sol[10],a[10],b[10],n,i,s;

void tipar (int k)


{cout<<"Solutie"<<end1;
for (i=1,i<=k,i++)
if (sol[i] cout<<sol[i]<<" bancnote de "<<a[i]<<end1;}
void plata (int k,int s0)
{while(sol[k]<b[k]&& s0+a[k]<=s)
{sol[k]=sol[k]+1;
if (sol[k]>0)

172
s0+=a[k];
if (s0==s) tipar(k);
else if (k<n) plata (k+1,s0);}
sol[k]=-1;}
main( )
{cout<<"cate tipuri de bacnote avem?";cin>>n;
cout<<"suma=";
cin>>s;
for (i=1;i<=n;i++)
{cout<<"valoarea monedei de tipul"<<i<<' ';
cin>>a[i];
b[i]=s/a[i];
sol[i]:=-1;
}
plata{1,0);}

PROBLEMA LABIRINTULUI.Se da da un labirint sub forma de matrice cu m linii si n


coloane.Fiecare element al matricei reprezinta o camera a labirintului.Intr-una din
camere ,de coordonate x si yse gaseste un om.Se cere sa se gaseasca toate iesirile
din labirint.
O prima problema care se pune este precizarea modului de codificare a iesirilor din
fiecare camera a labirintului.
Fie l[i][j] un element al matricei.aaaaaAcesta poate lua valori intre 1 si 15.Se
considera iesirile spre nord,est,sud,vest luate in aceasta ordine.Pentru fiecare
durectie cu iesire se retine 1,iar in caz contrar se retine 0.Un sir de 4 cifre alcatuit din
1 sau 0 formeaza un numar in baza 2.Acest numar este convertit in baza 10 in l[i][j].
Exemplu:camera de coordonate (2,3) are iesire spre vest si spre vest.Obtinem
numarul binar 0101 care in baza 10 inseamna 5.
Pentru a testa uasor iesirea din labirint,matricea se bordeza cu cu doua linii si doua
coloane,ale caror elemente au valoarea 16.
Drumul parcurs la un moment dat se retine intr-o variabila d,care care reprezinta o
matrice cu 2 linii si 100 coloane.Semnificatia elementelor acestei matrice este
urmatoarea:
-d[k][o] reprezinta linia camerei in care a ajuns omul;
-d[k][1] reprezinta coloana camerei respective.
La gasirea unei iesiri din labirint,drumul este tiparit.
Functia ies ( ) de gasire a drumului de iesire din labirint este conceputa in felul
urmator:
-se testeaza daca nu s-a iesit din laboirint (1[x] [y]=16);
-In caz ca aceasta conditie a fost indeplinita se tipatreste drumul gasit;
-in caz contrar se procedeaza astfel:
-se retin in d coordonatele camerei vizitate (d[k] [0]=x,d[k] [2]=y,k avand
semnificatia de numar curent al camerei vizitate);
- se verifica daca drumul parcurs de om pana in acest moment a mai trecut
prin aceasta camera situatie in care se iese din functie
-se testeaza pe rand iesirile spre noerd,est ,sud,vest si acolo unde este gasita
o astfel de iesire se reapeleaza functia cu noile coordonate;
-inaintea iesirii din functie se decrementeaza valoarea lui k.
OBSERVATIE: un apel al functiei ies ( ) corespunde vizitei intr-o camera.
#include <iostream.h>
int l[10],d[2] [100],i,j,m,n,x,y,k;
void tipar (int k,int d[2] [100])
{

173
int i;
cout <<"-----------"<<end1;
for (i]1;i<=k;i++;)
cout<<"1="<<d[0] [i]<<' '>>"c="<<d[1] [i] <<end1;
}
void ies (int x,int y,int& k,int 1[10] [10],int d[2] [100])
{
int i,gasit;
if (1[x] [y]==16) tipar(k,d);
else

{
k++;
d[0] [k]=x;d[1] [k]=y;
gasit=0;
for (i=1;i<=k-1;i++)
if (d[0] [i]==d[0] [k] && d[1] [i]==d[1] [k] gasit=1;
if (!gasit
for (i=1;i<=4;i++)
switch (i)
{
case 1: {if (1[x] [y] & (int)8) ies(x-1,y,k,1,d);break;}
case 2;{if (1 [x] [y] & (int)4) ies(x,y+1,k,l,d);break;}
case 3: {if (1[x] [y] & (int)2) ies(x+1,y,k,l,d);break;}
case 4: if (1[x] [y] & (int)1) ies(x,y-1,k,l,d);
k--;
}
}
main( )
{
cout<<"N=';cin>>n;
cout<<"M=";cin>>m;
for (i=1;;i<=m;i++)
for (j=1;j<=n;j++)
{
cout<<"1"["<<i<<','<<j<<"]=";
cin>>1[i] [j];
}
cout<<"X="{;cin>>X;
cout<<"Y=";cin>>Y;
for (i=1;i<=n;i++)
{1[0] [i]=16;
l[m+1 [i]=16;
}
k=0;
ies(x,y,k,l,d);
}

Algoritmul de umplere a unei suprafete inchise (FILL).Se da o matrice binara


Valorile 1 delimiteza o anumita suprafata inchisa in cadrul matricei(elementele
apartinand acestei suprafete sunt marcate cu 0).Se dau, de asemenea coordonatele
x si y ale unui element al matricei,semnificand un punct din interioprul acestei
suprafete.

174
Ex, Fie matricea:

0 1 1 0
0 0 0 1
A= 0 1 1 1
1 0 0 0
Suprafata inchisa este data de elementele
A(1,1),A(2,1),A(2,2),A(2,3),A(3,1).Consideram coordonatele (2,3) ale unui punct
situat in interiorul acestei suprafete.
Dupa executia programului matricea va arata astfel:

1 1 1 0
1 1 1 1
A= 1 1 1 1
1 0 0 0
Algoritmul se dovedeste extrem de util in colorarea unei suprafete inchise atunci
cand sunt cunoscute coordonatele unui punct situat in interiorul ei.Acest algoritm
este cunoscut si sub denumirea de algoritmul FILL.

Pentru rezolvare se foloseste functia Scriu( ),care se autoapeleaza.Initial,matricea se


bordeaza cu doua linii si doua coloane ce contin elemente care au valoarea
1.Aceasta are ca scop evitarea testului de iesire din matrice.
Functia Scriu( ) functioneaza astfel:
-testeaza daca elementul matricei la care s-a ajuns are valoarea 0;
-in caz afirmativ,aceasta ia valoarea 1 iar functia se autoapeleaza pentru fiwecare
dintre elementele invecinate(sus,jos,dreapat, stanga);
-in caz contrar,se iese din functie.
#include <iostream.h>
int a[10] [10],i,j,m,n,x,y;
void scriu (int x,int y,int a[10] [10])
{
if (!a[x] [y])
{
a[x] [y]=1;
scriu(x+1,y,q);
scriu(x,y+1,a);
scriu(x-1,y,a);
scriu(x,y-1,a);
}
{
main( )
{
cout <<"M=;cin>>m;
cout<<"N=";cin>>n;
for (i=1;i<=m;i++;)
for (j=1;j<=n;j++)
{
cout<<"a["<<i<<'.'<<j<<"]=";
cin>>a[i] [j];
{
for (i=1;i<=n;i+=)
{

175
a[0] [i]=1;
a[m+1][i]=1
}
for (i=i;i<=m;i++);
{
a[i] [0]=1;a[i] [n+1]=1;
{
cout<<"X=";
cin>>x;
cout<<"Y=";
cin>>y;
for (i=1;i<=m;i++)
{
for (j=1;j<=n;j+=)
cout<,a[i]][i];
cout<< end1;
}
scriu(x,y,a);
cout<<end1<<end1;
for (i=1;i<=m;i++)
{
for (j=1;j<=n;j++)
cout<<a[i] [j];
cout<<end1;
}
})
Problema fotografiei (aplicatie FILL).O fotografie alb-negru este prezentata sub
forma unei matrice binare.Ea infatiseaza unul sau mai multe obiecte.Portiunile
corespunzatoare obiectului(sau obiectelor) in matrice au valoarea unu.Se cere sa se
determine daca fotografia reprezinta unul sau mai multe obiecte.
Exemple:
In matricea urmatoare sunt reprezentate doua obicte.
1 1 0 0
0 0 0 1
A= 1 1 1 1
1 1 1 1
In matricea de mai jos este reprezentat un singur obiect

0 1 1 0
0 0 0 1
A=0 1 1 1
1 0 0 0
Ca si in problemele anterioare spre a evita testul iesiri din matrice, aceasta este
bordata cu linii si coloane avand valoarea 0.
Algoritmul este tot cel din problema anteriara (FILL),dar aici cautarea se face pe opt
directii.In programul principal se citeste matricea si se cauta primul element 1 printre
elementele acesteia.Se apeleaza apoi functia compact( )care are rolul de a marca
cu 0 toate elemntele matricei care apartin acestui [prim obiect identificat.La
revenire ,se testeaza daca mai exista elemente cu valoarea 1in matrice .In caz
afirmativ se poate trage concluzia ca in fotografie aveam initial mai multe
obiecte;astfel,fotografia continea un singur obiect
#include <iostream.h>
int a[10][10],i,j,m,n,x,y,gasit;

176
{
if (a[x][y])
{
a[x][y]=0;
compact (x-1,y,a);
compact (x-1,y+1,a);
compact (x,y+1,a);
compact (x+1,y+1,a);
compact(x+1,y,a);
compact(x+1,y-1,a);
compact(x,y-1,a);
compact(x-1,y-1,a);
}
}
main( )
{cout <<"M=";cin>>m;
cout<<"N=";cin>>n;
for (i=1;i<=m i++)
for(j=1;j<=n;j++)
{cout<<"a["<<i<<','<<j<<"]=";
cin>>a[i][j];
}
for (i=1;i<n;i++)
{a[0][i]=0;
a[m+1][i]=0;
for (i=1;i<=m;i++)
{a[i][0]=0;a[i][n+1]=0;
}
x=0;
do
{x++;
y=0;
do
{y++;}
while (y!=n &&a[x][y]!=1);
}while((x!=m)&&a[x][y]!=1);
compact(x,y,a);
gasit=0;
for (i=1;i<=m;i++)
for(j=1;j<=n;j++)
if (a[i][j]==1) gasit=1;
if(gadsit0cout<<"mai multe obiecte";else cout<<"un obiect";
}

ALTE TEHNICI DE ELABORARE A


ALGORITMILOR

GREEDY

177
Sa considaram o multime A cu n elemente.Se cere o submultime a sa, eventual
m<=n elemente, astfel incat sa fie indeplinite anumite conditii(acestea difera de la o
problema la alta).
Se considera o multime de n enumere reale..Se cere o submultime a sa astfel incat
suma elemetelor ei sa fie maxima.Pentru rezolvare vom alege un prim element al
multimii de numere reale.Daca este posibil acesta va fi adaugat solutiei,initial
vide.Posibilitatea ca acesta sa fie edagat este data de semnul numarului(acesta
trebuie sa fie mai mare ca 0).Se alege un al doilea numar cu care se procedeaza in
mod asemanator.
Algoritmul se incheire cand au fost alese si, 4eventual ,adaugate toate elementele
multimi.
Pentru a rezolva o problema cu Greedy ,solutiia se construieste ,dupa algoritmul de
mai jos.
Pentru fiecare element care urmeaza sa fie adaugat solutiei finale, se efectueaza
oalegere a sa dintre elementele multimi A(dupa un mecanism specific fiecarei
probleme in parte),iar daca este posibil,acesta este adaugat.Alghoritmul se
termina,fie cand a fost gasita solutia ceruta ,fie cand afost gasita solutia ceruta fie
cand s-a constatat inexistenta acesteia.
Intuitiv,alegem un element,al doilea,.... pana cand obtinem ce dorim sau pana cand
au fost testate toate elementele multimii.De aici provine si numele
metodei(greedy=lacom.)
Cel care elaboreaza un algoritm greedy trebuie sa stie faptul ca, procedand in modul
ales de el, se ajunge la rezultatul dorit.Pentru fiecare problema in parte,dupa ce se
identifica un algoritm,este onbligatoriu sa se demonstreze ca acesta conduce la
solutia optima.
In general,numarul de operatii de baza efectuate de un algoritm greedy este o
expresie polinomiala -algoritmi sunt performanti
De multe ori este necesar ca elementele multimii A sa fie sortate,pentru ca apoi sa
slegem din acestea.
Intr-o zi trebuie planificate n spectacole.Pentru fiecare spectacol se cunoaste
intervalul in care se desfasoara:[st,sf(.Se cere sa se planifice un numar maxim de
spectacole astfel incat sa nu se suprapuna.
Vom construi o solutie dupa urmatorul algoritm:
P1 Sortam spectacolele dupa ora terminarii lor;
P2 Primul spectacol programat este celo care se termina cel ma devreme;
P3 Alegem primul spectacol dintre cele care urmeaza in sir ultimului spectacol
programat care indeplineste conditia ca incepe dupa ce s-a terminat ultimul
spectacol programat;
P4 Daca tentativa de mai sus a esuat (nu am gasit un astfel de spectacol) algoritmul
se termina,astfel se progreameaza spectacolul gasit si algoritmul se reia de la P3
Demonstratia se poate gasi in "Tehnici de programare"
#include <iostream.h>
int s[2][10],o[10],n,i,h1,m1,h2,m2,ora;
void sortare( )
{
int gata,m,i;
do{ gata=1;
for (i=1;i<=n-1;i++);
if (s[i][o[i]] s[1][o[i+1]])
{m=o[i];o[i]=o[i+1];
o[i+1]=m;

178
gata=0;
}
}
while (!gata)
}
main( )
{ cout<<"n=";cin"n;
for (i=1;i<=n;i+=)
{o[i]=i;
cot<<"ora de inceput pentru spectacolul"<<i<<"( h h) m m )=";
cin >>h1>m1;
s[0][i]=h1*60+m1;
cout <<"oradesfarsit pentru spectacolul "<<i<,"(hh mm)=";
cin>>h2>>m2;
s[1][i]=h2*60+m2;
}
sortare( );
cout,<<ordinea spectacolelor este"<<end1<<0[1]<<end1;
ora=s[1][o][1]];
for (i=2;i<=n;i++)
{ if (s[0][o[i]]>=ora{cout<<o[i]<<end1;
ora=s[1][o[i]];}
}
}
 persoana are un rucsac cu ajutorul caruia poate transporta o
greutate maxima G.Persoana are la dispozitie n obiecte si
cunoaste pentru fiecare obiect greutatea si castigul care se obtine
in urma transportului sau la destinatie.Se cere sa se precizeze ce
obiecte trebuie sa transporte persoana in asa fel incat castigul sa
fie maxim.
O precizare in plus trnsforma adceasta problema in alte doua probleme
distincte.Aceasta precizare serefera la faptul ca obiectele pot fi sau taiate pentru
transportul la destinatie.In prima situatie,problema poarta nyumele de problema
continua a rucsacului ,iar in a doua avem problema discreta a rucsacului.Aceste
doua probleme se rezova diferit,mpotiv pentru care ele sunt prezentate
separat .Varianta continua a probelmei rucsacului este tratata in acest
paragraf.Algoritmul este urmatorul:
-se calculeaza,pentru fiecare obiect in parte,eficienta de transport rezultata prin
impartirea castigului la greutate(de fapt,acesta reprezinta castigul optinut din
transportul unitatii de greutate);
-obiectele se sorteaza in prdine descrescatoare a eficintei de transport si se preiau in
calcul in aceasta ordine4;
-castigul initial va fi 0 iar greutatea ramasa deincarcat va fi G
-atat timp cat nu a fost completata greuataea maxima a rucsacului si nu a fodst luate
un considerare toate obiectele se procedeaza astfel:
-dintre obiectele neincarcate se selecteaza acela cu cea mai mica eficienta de
transport si avem doua posibilitati:
-acesta incape in totalitate in rucsac.deci se scade din greutatea ramasa de incarcat
greutatea obictulyui,la castig se cumul3aza castugul datorat transportului acestui
obict;
-se tipareste 1 in sensul ca intregul obict a fost incarcat;

179
-obiectul nu incape in totalitate in rucsac,caz in care se calculeaza ce patre din el
poate fi transportata cse cumuleaza castigul obtinut cu transportul aceste plati din
obiect iar greutatea ramasa de incarcat devine 0.
#include<iostream.h>
double c[9],g[9],ef[9],gv,man,castig;
int n,i,man1,inv,ordine[9];
main( )
{cout<<''Greutatea ce poate fi transportata='';cin>>gv;
cout<<''Numar de obiecte='';cin>>n;
for(i=1;i<=n;i++)
{
cout<<''c[''<<i<<'']='';cin>>c[i];
cout<<''g[''<<i<<''];cin>>g[i];
ordine[i]=i; ef[i]=c[i] /g[i];
}
do
{
inv=0;
for(i=1;i<=n-1;i++)
if(ef[i]<ef[i+1])
{ man=ef[i];ef[i]=ef[i=1];ef[i=1]=man;
man=c[i]; c[i]=c[i=1]; c[i=1]=man;
man=g[i];g[i]=g[i+1]=man;
inv=1;
man1=ordine[i];ordine[i]=ordine[i+1];ordine[i+1]=man1;
}
}
while (inv);
i=1;
while(gv>o && i<=n)
{
if (gv>g[i])
{ cout <<''Obieclul ''<<ordine[i]<< ' '<<1<<endl;
gv-=g[i];castig+=+c[i];
}
else
{cout<<''Obiectul ''<<ordine[i]<<' ' <<gv /g[i]<<endl;
castig+c[i]*gv /g[i];
gv=0;
}
i++;
}
cout<<''Castig total=''<<castig;
}
PROGRAMARE DINAMICA
Alaturi de Greedy,programarea dinamica este o tehnica ce conduce, de
cele mai multe ori, la un timp de calcul polinomial.Mai mult, ea furnizeaza in
totdeauna solutia optima .Din nefericire, programarea dinamica nu se poate
aplica tuturor problemelor, ci numai care indeplinesc anumite conditii.
Se considera o problema in care rezultatul se obtine ca urmare a unui sir de
decizii D1, D2,......Dn. In urma decizei D1 sistemul evolueaza din starea S0 in
starea S1,in urma decizei D2 sistemul evolueaza din starea S1 in starea
S2,....,in urma decizei Dn sistemul evolueaza din starea Sn-1 in stareaSn.

180
Daca D1, D2,....Dn este un sir de decizii care comduce sistemul in mod optim
din S0 in Sn,atunci trebuie indeplinita una din conditiile urmatoare (principiul
de optimalitate):
1)Dk...Dn este un sir de decizii ce conduce optim sistemul din starea Sk-1 in
stareaSn,Ak,1<=k<=n;
2)D1....Dk este un sir de decizii ce conduce optim sistemul din starea S0 in
stareaDk,Ak,1<=K<=;
3)Dk+1...Dn,D1...Dk sunt siruri de decizii care conduc optim sistemul starea Sk
in starea Sn, respectiv din starea D0 in starea Sk,Ak,1<=k<=n.
=>Daca principiulde optimalitate se verifica in forma 1,spunem ca se aplica
programarea dinamica metoda inainte.
=>Daca principul de oplimalitate se verifica in forma 2, spunem ca se aplica
programarea dinamica inapoi.
=>Daca primcipiul de optimalitate se verifica in forma 3, spunem ca se aplica
programarea dinamica metoda mixta.
Programarea dinamica se poate aplica problemelor la care optimul general implica
optimul partial.
Faptulca optimul general determina optimul partial, nu inseamna ca optimul partial
determina optimul general.
cu toate acestea, faptul ca potimul general impune optimul partial ne este de mare
ajutor:cautam optimul general,intre optimele partiale, pe care le retinem la fiecare
pas.Oricum,cautarea se reduce considerabil.
Problema triunghiului.Se considera un triunghi de numere naturale format din n
linii.Prima linie contine un numar,a doua doua numere,........ultims n numere
naturale.Cu ajutorul acestuoi triunghi se pot forma sume ne numer naturale in felul
urmator:
-se porneste cu numarul din linia unu;
-succesorul unui numar se afla pe linia urmatoare plasat sub el(acees coloana) sau
pe diagonala la dreapta(coloana creste cu 1).
Care este cea mai mare suma care se poate forma astfel si care sunt numerele care
o alcatuiesc:
Exemplu:n=4;
2
3 5
6 3 4
5 6 1 4
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 sete[i suma maxim').
Saq observam ca se pot forma 2 la puterea n-1 sume de acest fel.A le lua in
considerare pe toate pentru a gasi valoarea maxima nu este eficient.
Pentru etapa i se trateza linia i a triunghiului.Fie un sir de n numere care respecta
conditile problemei si care formeaza suma maxima.In acest sir,consideram numarul
care a fost preluat de pe linia i.Numerele intre i+1 si n,formeaza o suma maxima in
raport cu sumele care se pot forma incepand cu nu,marul preluat de pe linia
i,contrar,se contrazice ipoteza.In aceasta situatie se poate aplica programarea
dinamica,metoda inainte.

Vom forma un triunghi,de la baza catre varf,cu sumele maxime care se pot forma cu
fiecare numar.Daca am citit triunghiul de numere intr-o matrice T si calculam sumele
intr-o matrice C vom avea relatiile urmatoare:
C[n][1]:=T[n][1];

181
C[n][2]:=T[n][2];
C[n][n]:=T[n][n];
Pentru linia i (i<n), cele i sume maxime care se obtin aqstfel:
C[i][j]=max{T[i][j]+C[i+1][j],T[i][j]+C[i+1][j+1]},i apatine multimii {1,2,....,n-1) iar j
apartine multimii {1,.....,i}.
Sa rezolvam problema propusa ca exemplu:
Linia 4 a matricei C va fi linia n a matricei T:5 6 1 4;
Linia 3 se calculeaza 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][3]=max{5+9,5+8}=14;
Linia 1
C[1][1]=max{2+15,2+14}=17.
Aceasta este si cea mai mare suma care se poate forma.
Pentru a tipari numerele luate in calcul se foloseste o matrice numita DRUM in care
pentru fiecare i apartinand multimii mai sus mentionate si j la fel apartinand multimii
mentionate mai sus se retine coloana in care se gaseste succesorul lui T[i][j].
#include<iostream.h>
int t[50][50],c[50][50],drum[50][50].n,i,j;
main()
{ cout<<"n=";cin>>n;
for (i=1;i<=n;i++)
for (j=1;j<=n;j+=)
{ cout <<"t["<<i<<','<<j<<"]=";
cin>>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;i++
{ for (j=1;j<=n;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;}
}
cout<<"suma maxima="<<c[1][1]<<end1;
i=1;j=1;
while (i<=n)
{ cout<<t[i][j]<<end1;
j=drum[i][j];
i++;
}
}

Subsir crescator de lungime maxima.Se considera un ve ctor cu n


elementeintregi.Se cere sa se tipareasca cel mai lung subsir crescator al acestuia.
Exemplu:Pentru n=5 se da V=(4,1,7,6,7).In acest caz subsirul tiparit va fi :4,7,7.
Problema se poate rezolva pornind de la idee a de a calcula ,pentru fiecare element
al vectorului lungimea celui mai lung subsir crescator care se poate forma incepand

182
cu el.In final este selectat elementul din vector cu care se poate forma cel mai lung
subsir crescator si acesta este listat,
L(k)={1+max L{i}/V{i}>=V(k). i apartine multimii {k+1,...,n} iar k apatrtine multimii
{1,2,....,n}.
In practica,folosim un vector L cu n componente,unde L(k) are semnificatia
explicata.Pentru examenul nostru vom avea:
L=(3,3,2,2,1).
Componentele vectorului L au fost calculate astfel:
-cel mai lung subsir care se poate forma cu elementul 7,aflat pe ultima pozitie,are
lungimea 1;
-cel mai lung subsir care se poate forma cu elementul 6 aflat pe pozitia 4 are
lungimea (1+L(5)), pentru ca pe pozitia 5se gaseste elementul 7 care este mai mare
decat 6;
-cel mai lung subsir care se poate forma cu elementul aflat pe pozitia 3 are
lungimea2(1+L(5)) pentru ca 7 este egal cu 7;
-algoritmul continua in acest mod pana se completeazaL(1).
Dupa aceasta se calculeaza maximul dintre componentele luiL,iar cel mai lung
subsir crescator format din elementele vectoruluiV vaavea lungimea data pe acest
maxim. Pentru a lista efectiv acel subsir de lungime maximala se procedeaza astfel:
-se cauta mazimul din vectorulL precum si indicele t,la care se gaseste acest maxim;
-se afiseazaV(t);
-se gaseste si se listeaza primul element care este mai mare sau egel cu V(t) si are
lungimea mai mica cu 1(max-1), se actualizeaza valoarea max cu max-1;
-algoritmul continua pana cand se epuizaza toate elementele subsirului.
#include<iostream.h>
int v[20],1[20],n,i,k,max,t;
main()
{
cout<<''n=''; cin>>n; for(i=1;i<=n;i++)
{cout<<''v[''<<i<<'']='';cin>>v[i];}
1[n]=1;
for(k=n-1;k>=1;k--)
{max=0;
for(i=k+1;i<=n;i++)
if (v[i]>=v[k]&& 1 [i]>max)
max=1[i];
1[k]=1+max;
}
max=i[1]; t=1;
for(k=1; k<=n;k++)
if (i[k]>max)
{max=i[k]; t=k;}
cout<<''lungimea maxima:''<<max<<end1<<v[t]<<end1;
for(i=t+1;i<=n;i++)
if (v[i]>v[t]&& 1 [i] ==max-1)
{cout<<v[i] << end1;
max--;
}
}
-Inmultirea optima a unui sir de matrice
Presupunem ca avem inmultiti doua matrice:An,p cu Bp,m.In mod evident, rezultatul
va fi o matrice Cn,m.Se pune problema de a afla cate inmultiri au fost facute pentru
a oobtine matricea C. Prin inmultirea liniei 1 cu coloana 1 se fac p inmultiri, intrucat

183
au p elemente. Dar linia 1 se inmulteste cu toate cele m coloane, deci se fac m*p
inmultiri.In mod analog se procedeaza cu toate cele n linii ale matricei A,deci se fac
n*m*p inmultiri.Retinem acest rezultat.

Sa consideram produsul de matrice A1xA2x....xAn


(A1(d1,d2),A2(d2,d3)...,An(dn,dn+1)).Se cunoaste ca legea de compozitie produs de
matrice nu este comutativa in schimb este asociativa.De exemplu ,daca avem de
inmultit trei m atrice A,B,C produsul se poate face in doua moduri:(AxB) xC;
Ax(BxC).Este interesant de observat ca nu este indiferent modul de inmultire a celor
n matrice.Sa considera ca avem de inmultit patru matrice
A1(10,1),A2(1,10),A3(10,1),A4(1,10).
Pentru inmultirea lui A1 cu A2 se fac 100 de inmultiri si se obtine o matrice cu 10 lini
si 10 coloane.Prin inmul
tirea acesteia cu A3 se fac 100 de inmultiri si se obtine o matrice cu 10 linii si o
coloana.daca aceasta matrice se inmulteste cu A4 se fac 100 inmultiri. In concluzie,
daca acest produs se efectueaza in ordine naturala,se efectueaza 300 inmultiri.
SAefectuam acelas produs in ordinea care rezulta din expresia A1x((A2XA3) x
A4).Efectuand produsul A2 cu A3 se efectueaza 10 inmultiri si se obtine o matrice cu
o linie si cu o coloana.aceasta matrice se inmulteste cu A4 se fac 10 inmultiri si se
obtine o matrice cu 1 linie si 10 coloane.Daca o inmultim pe aceasta cu prima,
efectuam 100 inmtltiri , obtinand rezultatul final cu numai 120 de inmultiri.
In concluzie,apare o problema foarte interesanta si anume de a afla modul in care
trebuie sa se inmulteasca cele n matrice,astfel incat nr de inmultiri sa fie minim.Pt
rezolvare vim aplica principiul 3 al programari dinamice.

In vvederea rezolvari problemei,retinem o matrice A cu n linii si n coloane.Elementul


A(i,j) i<j,reprzinta nr minim de inmultiri pt efectuarea produsului AixAi+1x....xAj.De
asemenea,nr liniilor si al coloanelor celor n matrice sunt retinute intrun vector DIM cu
n+1 componente.Pt exemplu nostru DIM retine urmatoarele valori:10,1,10,1,10.
Pt rezolvare se tine cont de urmatoarele relatii existente intre cimponentele matricei
A:
1)A(i,i)=0;
2)A(i,i+1)=DIM(i)xDIM(i+1)xDIM(i+2);
3)A(i,j)=min{A(i,k)+A(k+1,j)+DIM(i)xDIM(k+1)xDIM(j+1)}.
i<=k<j
Justificarea acestor relati este urmatoarea:
1)o matrice nu se inmulteste cu ea insasi,deci se efectueaza 0 inmultiri;
2)liniile si coloanele matricei A1 se gasesc in vectorul DIM pe pozitiile i si i+1, iar ale
matricei Ai+1-pe pozitiile i+1 si i+2;
3)
-inmultind matriceleAixAi+1xAk se obtine o matrice cu un nr de linii egal cu acela al
matricei Ai (DIM(i)) si cu un nr de coloane egal cu acela al matricei Ak (DIM(k+1));
-inmultind matricele Ak+1x.....xAj se obtine o matrice cu un nr de linii egal cu acela
al matricei Ak+1 (DIM(k+1)) si cu un nr de coloane egal cu acela al matricei Aj
(DIM(j+1));
-prin inmultirea celor doua matrice se obtine matricea rezultat al inmultirii Aix....xAj,
iar pt aceasta inmultire de matrice se efectueaza DIM(i) xDIM(k+1) xDIM(j+1)
inmultiri;
~Relatia sintetizeaza faptul ca pt a obtine nr de inmultiri optim pt produsul Aix....xAj
se inmultesc doua matrice, una obtinuta ca produs optim intre Aix....xAk si cealalalta
obtinuta ca produs optim intre Ak+1x....xAj, in ipoteza in care cunoastem nr de
inmultiri necesar efectuarii acestor doua produse orucare ar fi k, cuprins intre limitele
date.

184
~Aceasta observatie este o consecinta directra a programarii dinamice si anume ca
produsul efectuat optim intre matricele prezentate se reduce in ultima istanta la a
efectua un produs intre 2 matrice cu conditia ca acestea sa fie calculate
optim(produsul lor sa aiba un numar minim de inmultit).
Se pune problema cum putem efectua acest calcul utilizand relatiile
prezentate.Pentru exemplificare vom utiliza exemplul dat la inceputul acestui
capitol.Datorita relatiei 1, diagonala pricipala a maricei A( cu 4 lini si 4 coloane) va fi
alcatuita numai din elemente avand valoarea 0. s
Initial se pot calcula numai elemente A(i,i+1),adica A(1,2),A(2,3),A(3,4)-elemente
situate pe o paralela la digonala principala a matricei A.Este cazul sa observam ca
portiunea din matrice situata sub diagonala principala este neeutilizata.In
concluzie,avem A(1,2)=100,A(2,3)=10,A(3,4)=100.Matricea A va arata astfel:

0 100 x x
x 0 10 x
A= x x 0 100
x x x 0

In continuare calculam:
A(1,3)=min{A(1,k)
+A(k+1,3)+DIM(1)*DIM(k+1)*DIM(4)}=min{0+10+10*1*1,100+0+10*10*1}=20;
A(2,4)=min{A(2,k)
+A(k+1,4)+DIM(2)*DIM(k+1)*DIM(5)}=min{0+100+1*10*10,10+0+1*1*10}=20;
A(1,4)=min{A(1,k)
+A(k+1,4)+DIM(1)*DIM(K+1)*DIM(5)}=min{0+20+10*1*10,100+100+10*10,20+0+10*
1*10}=120;

.
0 100 20 120
x 0 10 20
A= x x 0 100
x x x 0
In concluzie,pt. exemplul nostru, se fac minim 120 de inmultiri,rezultat luat din
matricea A si anume A(1,4).
#include<iostream.h>
int i,n,dim[10]
long a[10] [10];

void costopt(int n,int dim[10],long a[10] [10])


{int k,i,j,l;
long m;
for (i=1;i<=n;i++) a[i] [i]=0;
for (k=1;k<=n-1;K++)
for (i=1;i<=n-k;i++)
{ j=i+k;
a[i] [j]=100000;
for(l=i;l<=j;l++)
{m=a[i] [l] +a[l+1] [j]+dim[i]*dim[l+1]*dim[j+1];
if (a[i] [j]>m)
{
a[i] [j]=m;;
a[j] [i]=l;

185
}
}
}
cout<<"cost optim:"<<a[1] [n]<<end1;

}
main()
{cout<<"n=";cin>>n;
for (i=1;i<=n+1;i++)
{ cout<<"d=";cin>>dim[i];}
costopt(n,dim,a);
}

ALTE NOTIUNI NECESARE PROGRAMARII IN C++


PREPROCESORUL
In C++compilarease realizeaza in doua fraze:preprocesarea si compilarea propriu -
zisa.

Preprocesarea este realizata de un program special, numit preprocesor.El este


apelat automat de mediul integratC++.Preprocesorul actioneaza asupra textului
sursa, iar rezultatul actiunii sale este tot text sursa.
Cu ajutorul preprocesorului se realizeaza urmatoarele:
-se definesc macro-uri;
-se includ in textul sursa ale fisiere;
-se seteaza anumiti parametri de compilare.
Comenzile preprocesorului se numesc directive de preprocesare.Orice directiva de
preprocesare incepe cu "#".Intre caracterul respectiv si numele directivei se pot
gasi,optional, mai multe caractere albe,exceptand new line.

DIRECTIVE #define si #undef


Cu ajutorul directivei #define se defineste un macro.Un macro este caracterizat de
numele sau si o secventa de constante ,cuvinte cheie ,identificatori.
Preprocesorul are rolul de a inlocui fiecare aparitie a numelui macro-ului(in textul
sursa a programului C++) cu secventa pe care o contine.Pentru a intelege modul in
care se lucreaza cu macro-urile vom da cateva exemple semnificative.

1.Definirea constantelor.Pentru a defini constantele se foloseste formatul simplificat


al directivei #define:
define nume_macro secventa
Analiza programul urmator:
#include <iostream.h>
#define pi 3.14
#define text "Valoarea aproximativa a lui pi este"
#define pi_plus_1 pi+1
main()
{cout<<text<<pi<<" ""<<pi_plus_1;}
Directiva #undef are forma generala:
#undef nume_macro
si are rolul ca ,din momentul in care a frost intalnita ,sa nu mai fie recunoscut numele
macro-ului,fiind astfel posibil sa-l redefinim:
#include<iostream.h>
#define pi 3.14

186
main()
{cout<<pi<<end1;
#undef pi
#define pi 3.1414
cout<<pi;
}
programul tipareste la inceput 3.14,apoi 3.1414.
2."Rebotezarea" tipurilor.Exista posibilitatea ca pentru anumite tipuri sa folosim alte
nume.
#include <iostream.h>
#define intreg long
main()
{intreg a=43; cout<<a<<end1;}
3.macro-uri generalizate au rolul de a permite scrierea unor secvente asemanatoare
functiilor.
#include <iostream.h>
#define patrat (x) x*x
main()
{
int a=10;
cout<<patrat(a);
}
Linia cout <<patrat(a); se transforma in cout<<cout<<a*a;.Mecanismul de inlocuire
ete urmatorul:initial linia se transforma in cout<<a*a; apoi fiecare aparitie a lui x cu a.
Diferenta esentiala fata de folosirea unei functii este data de faptul ca ,prin inlocuirea
in text a expresiei este evitat saltul catre codul functiei.Acest lucru este util prin faptul
ca se castiga timp,desi,in cazul in care exista mai multe apelari,codul programului
executabil este mai mare.Sa nu uitam faptul ca ,datorita progreselor imense
inregistrate in tehica de calcul memoria ocupata de program nu mai constituie o
problema,dar timpul de executie da.Mai ales atunci cand se raealizeaza programe de
o mare complexitate.
Macro-ul prezentat are o deficienta majora. Despre ce este vorba?Fie apelul
patrat(a+1).El este inlocuit in text cu a+1*a+1. daca a este 10 rezultatul este 21, in
loc de 121. Pt. a evita astfel de erori rescrise macroul ca in exemplul urmator:
#include <istream.h>
#define patrat(x) ((x)*(x))
main()
{ int a=10;
cout<<patrat(a+1);
}
Linia:cout<<patrat(a+1) se inlocuieste cu:cout<(a=10)*I(a+1). Prima urmare ,
rezultatul va fi corect. Oare in acest caz efectul apelului este identic cu cel al unei
functii care realizeaza acelas lucru?Raspunsul este negativ. De exemplu, in cazul
apelului patrat (++a); se obtine(++a)*(++a); rezultat , evident, gresit. In cazul
existentei unei functii variabila a este incrementata o singura data,inainte de apel.In
concluzie,trebuie sa fim atenti la modul in care scriem apelul.
Ne putem intreba ,care este motivul pt. care intreaga expresie a fost inclusa intr-o
pereche de paranteze rotunde?Am fi putut sa o scriem sa asa:#define patrat(x)
(x)*(x)..Procedand ca anterior,avem avantajul ca intreg rezultatul este inlocuit ca un
singur operand.In cazul existentei parantezelor,expresia cout<<1./patrat(a+10;
afiseaza rezultatul corect,iar in absenta lor,tinand cont de ordinea de efectuare a
operatiilor,rezultatul este...1(se imparte la a+1 si se inmulteste cu a+1).

187
Putem avea macro-uri cu mai multi parametri.Astfel ,macro-ul suma are rolul de a
calcula suma celor doi parametri.
#include <iostream.h>
#define suma(x,y) (x+y)
main()
{ int a=10,b=5;
cout<<suma(a,b);
}
Uneori este util ca lista parametrilor sa fie procedata de o secventa de caractere si
acest lucru este posibil.In exemplul urmator este cout<<.
#include <iostream.h>
#define tip(x,y) cout<<(x+y)
main()
{int a=10,b=5;
tip(a,b);
}
Macro-urile pot folosi un operator propriu "##".El are rolul de a concatena doi
parametri .In exemplul urmator linia cout<<concat(x,1); este transformata de
preprocesor in cout<<x1,deci se tipareste 10.
#include <iostream.h>
#define concat(x,y) (x##y)
main()
{int x1=10;
cout<<concat(x,1);
}
Tot asa, putem folosi un alt operator specific macro-urile si anume "#".El are rolul de
a converti parametrul primit in sir si de a returna un pointer catre acesta .In
exemplu ,constanta intreaga 23 este convertita in sirul "23".
#include <iostream.h>
#define sir(x) (#x)
main()
{ char*sir=sir(23);
cout<<sir;
}
Macro-urile pot fi folosite in mod asemanator cu functiile.Mai jos este prezentat un
macro ,numit modul care are rolul de a calcula modulul unei valori numerice.
-Pentru ca un macro sa poata ocupa mai multe linii ,este necesar ca dupa fiecare
linie ,cu exceptia ultimei ,sa punem caracterul'\'
-Datorita mecanismului de inlocuire ,parametri unui macro pot avea orice tip .De
exemplu macro-ul de mai jos poate fi utilizat si pentru a calcu;la modulul unui numar
de tip real ca si pentru a calcula modulul unui numar intreg.
-Tot asa ,spre deosebire de functii,unde apelul presupune un salt la codul
ei,utilizarea unui macro exclude saltul,pentru ca nu se efectueaza decat o
inlocuire.In schimb in cazul folosirii repetat,se utilizeaza mai multa memorie.
#include <iostream.h>
#define modul(mod,x)\
if (X>=0) mod=x;\
else mod=-x;
main()
{float a=-10.27,mod;
modul(mod,a);
cout<<mod;
}

188
O problema interesanta apare atunci cand macro-ul trebuie sa utilizeze variabile
auxiliare.De exemplu,dorim sa scriem un macro care interschimba cintinutul a doua
variabile.Daca ea nu este declarata ,apare eroare de sintaxa.Daca este declarata ,o
eventuala refolosire a macro-ului,in cadrul aceluias bloc,o redeclara,deci apare
eroare de sintaxa.Din acest motiv,preferam ca si variabila de manevra utilizata sa fie
declarata ca un parametru ,asa cum se vede mai jos.
#define schimb(x,y,manevra)\
manevra=x;=y;y=manevra;
main()
{float a=-10.27,b=9.12,man;
schimb(a,b,man);
cout<<a<<" "<<b<<end1;
schimb(a,b,man);
cout<<a<<" "<<b;

DIRECTIVA #INCLUDE
Directiva include a fost des folosita pana in prezent.Dupa cum stim ,rolul sau
este acela de a include in fisierul sursa(cel care contine programul pe care l-am
scris) a unui fisier antet(extensie.h)sau al altui fisier.Rolul acestui paragraf este
de a prezenta cateva notiuni suplimentare.
Directiva include are formele:
1.#include<nume_fisier>
2.#include "nume_fisier"
Pentru a intelege modul de utilizare al directivei,precizam faptul ca meniul mediului
integrat C++ are optiunea OPTION care la randul ei contine un meniu cu optiunea
DIRECTORIES.Daca utilizam aceasta optiune putem trece directoarele in care se
vor cauta fisierele.De exemplu am putea avea:
c:\tcwin\bin;c:\tcwin\include
Revenind la prezentarea directivei #include retinem ca:
-in prima forma fisierul ce trebuie inclus este cautat in subdirectoarele prezentate in
optiunea DIRECTORIES.
-in a doua forma fisierul este cautat mai intai la directorul curent ,iar apoi este cautat
ca in prima forma.
Exemplu:
Un program foloseste mai multe functii.Desigur,le putem include in textul
sursa.Exista si posibilitatea sa scriem functiile in alt fisier sursa.De exemplu ,functiile
urmatoare se gasesc in fisierul functii.cpp:
float adun(float x,floaty)
{return x+y;}
float scad(float x,floaty)
{return x-y;}

float produs(float x,floaty)


{return x*y;}

float impart(float x,floaty)


{ if (y) return x/y;}
Programul care foloseste cel putin una dintre ele va trebui sa includa fisierul
functii.cpp.
#include <iostream.h>
#include"functii.cpp"
main()
{cout<<adun(15,17);}

189
Observatii:
-directiva #include se poate utiliza pentru includerea oricarui fisier ,nu numai a
fisierelor antet.
-dupa includere,textul sursa ramane neschimbat.Acesta inseamna ca includerea este
numai logica - se considera ca noul fisier este format din:liniile fisierulyi sursa pana la
intalnirea fisierului,fisierul care a fost inclus ,liniile fisierului aflate dupa directiva pana
la sfarsitul sau.

DIRECTIVE DE COPILARE CONDITIONATA


Rolul acestor directive este de a determina copilarea selectiva a textelor
sursa.Pentru a intelege rostul acestor directive vom scrie doua functii pe care se
gasesc in fisiere diferite.
Ansamblul de directive #if #elif #else #endif se foloseste intr-o constructie de forma:
#IF EXPRESIE -CONSTANTA1
SEVENTA1
[#ELIF EXPRESIE -CONSTANTA2
SEVENTA2]
[#ELSE
SEVENTAn]
[#ELSE
SEVENTAn]
Ce este trecut intre paranteze drepte este facultativ.Principiul de executie este
urmatorul:
-daca expresie-constanta1 este diferita de 0 se executa secventa1 si executia
ansamblului este terminata,astfel se evalueaza Expresie-constanta2;
-daca expresie-constanta2 este diferita de 0 se executa secventa2 si executia
ansamblului este terminata ,astfel se evalueaza expresie-constanta3;
-daca expresie-constanta3 este diferita de 0 se executa secventan si executia
ansamblului este terminata,astfel se executa secventan+1
Exemplu:Fisierul p1.cpp contine functia:
void p()
{cout<<"tiparesc sirul 1"}
Fisierul p2.cpp contine functia:
void p()
{cout<<"tiparesc sirul 2";}
Programul este:
#define param 0
#include <iostream.h>
#if param
#include "p1.cpp"
#else
#include "p2.cpp"
#endif
main()
{p();}
Daca param are o valoare diferita de 0 se compileaza textul din p1.cpp, astfel se
compileaza textul din p2.cpp,deci programul va tipari un sir sau altul.
-Iata ca exista posibilitatea de a selecta o functie sau alta ,chiatr daca nu difera nici
macar prin parametri transmisi.
La fel ca directiva #if se pot folosi si directivele #ifdef si #ifndef.
Daca parametrul a fost definit se executa secventa respectiva.

190
In exemplul urmator daca param a fost definit (chiar are valoarea 0) se executa prima
secventa ,astfel se executa a doua secventa .
#define param 0
#include <iostream.h>
#ifdef param
#include "p1.cpp"
#else
#include "p2.cpp"
#endif
main() {p();} \
Daca doriti sa fie inclus fisierul p2.cpp,excludeti linia #define param 0 sau introduceti
directiva #undef param care are rolul de a anula definirea parametrului respectiv.
* Numarul directivelor preprocesor este cu mult mai mare,dar studiul lor nu face
obiectul acestui curs.

PROIECTE

NOTIUNI GENERALE
Pentru programele scrise de noi am utilizat un singur fisier.In practica,programele
sunt cu mult mai mari,motiv pentru care este dificil ca acestea sa fie scrise prin
utilizarea unui singur fisier.Mai mult la scrierea acestor programe participa mai multi
programatori si este foarte geru ca acestia sa utilizeze un singur fisier sursa.Pentru
aceasta se folosesc proiectele.
-Proiectul include mai multe texte sursa-numite module-,dar si alte componente cum
ar fi bibliotecile sau fisierele de tip resursa care contin descrierea anumitor obiecte
grafice,necesare in programarea vizual.
-Fisierele proiect au extensia .prj.
-Penru a crea un proiect se utilizeaza mediul integrat optiunea Project,apoi Open
Project.Apare o fereastra in care se testeaza numele proiectului.Meniul
prezinta optiunile Add item si Delete item,care permit adaugarea sau stergerea
unor fisiere sursa sau biblioteci.
Exemplu:Mai inainte am prezentat un fisier, numit functii.cpp.Exista posibilitatea sa-l
folosim cu ajutorul unui proiect.Pentru aceasta procedam astfel:
!.Scriem programul care-l foloseste:princ.cpp.
#include <iostream.h>
#include"mate.h";
main()
{cout<<adun(10,12);}
2,Pentru a putea folosi functiile scrise ,acestea trebuie declarate ,deci vomm scrie un
fisier antet,numit mate.h.Fisierul se salveaza in directorul curent.;
float adun(float x,float y);
float scad(float x,float y);
float produs(float x,float y);
float impart(float x,float y);
-Observati ca programul care-l foloseste include fisierul respectiv (mate.h).
3.Cream proiectul respectiv,adaugand,pe rand ,la el functii.cpp, mate.h,princ.cpp.
In continuare se ruleaza proiectul ca orice program (prin run)

BIBLIOTECI

191
Exista posibilitatea,asa cum am aratat,ca anumite functii sa fie scrise separat,in asa
numitele module.Astfel de module pot fi compilate separat.Pentru aceasta selectati
din nou meniul COMPILE.In urma compilarii,pe hard vom avea fisiere cu
extensia.obj.De exemplu daca compilam fisierul functii.cpp vom avea pe hard fisierul
functii.obj.Aceasta este ,de fapt,un modul compilat.
Modulele compilate pot fi incluse in biblioteci.O biblioteca are extensia .lib si poate
contine unul sau mai multe module compilate .
Pentru creearea unei biblioteci ,se foloseste un program special,numit tlib si aflat in
c:\borlandc\bin.
De exemplu daca dorim creearea biblioteci biblio.lib care sa contina modulul
functii,dam comanda tlib biblio +functii(extensiile sunt implicite).Daca se doreste
extragerea unui modul dintr0o biblioreca se da o comanda similara ,in care semnul +
este inlocuit cu-.
-Proiectele pot include biblioteci.De exemplu ,in proiectul anterior ,stergeti modulul
functii.cpp si adaugati biblioteca biblio.lib.Veti obtine acels rezultat.
-C++ foloseste biblioteci implicite.Pentru utilizarea acestoara nu sunt necesare
utilizarea proiectelor!De exemplu toate citirile si scrierile au fost facute cu ajutorul
unor functii aflate in biblioteciile implicite ale sistemului.
Programatorul nu face altceva decat sa includa fisierul iostream.h.
Aceste funcii se obtin prin utilizarea operatorilor ">>" si "<<".
Posibilitatea ca o functie sa fie apelata prin utilizarea unor opratori specifici poarta
numele de "supraincarcarea operatorilor".Procedeul tine de programarea orientata
pe obiecte si-l veti invata in cursul anului urmator.
=Proiectele pot include si fisiere gata compilate .

MAI MULT DESPRE VIZIBILITATE


In cazul utilizarii mai multor module apar anumite probleme legate de vizibilitatea
variabilelor sau a functiilor.
De exemplu,un modul defineste o variabila globala cou o anumita valoare
initiala.Dupa cum stim o variabila globala este vizibila la nivelul modulului.Daca
dorim putem utiliza variabila respectiva si in alt modul al proiectului.Conditia este ca
acolo sa o declaram .Declaratia se face cu ajutorul cuvantului cheie EXTERN.
Exemplu:proiectul contine doua module m1.cpp si m2.cpp.Modulul m1 defineste
variabila a,iar m2 o foloseste.
//modulul 1;
int a=7;
//modulul 2
#include <iostream.h>
extern a;
main()
{cout<<a;}
-Exista situatii cand programatorul doreste ca o variabila sa poate fi folosita numai la
nivelel modulului la care a fost declarata ,chiar daca exista tentativa de a o declara
ca anterior.In astfel de cazuri definitia varoiabilei este precedata de cuvantul cheie
static:static int a=7;
Functiile au acelas comportament ca variabilele.Asa cum am invatat pentru a le
utiliza chiar daca au fost definite in alt modul,ele sunt declarate cu ajutorul
prototipurilor.
- ca si variabilele functiile pot fi declarate statice,deci nu pot fi folosite in alte module.
Exemplu:Iata o functie statica:
static int t()
{return a+1;}
-un exemplu de proiect va fi dat in capitolul urmator.

192
FUNCTII INLINE
In mod normal la fiecare apel al unei functii se executa un transfer catre codul ei si
dupa ce a fost executata se revine la instructiunea care urmeaza apelului.Pentru cei
care tin cu orice pret sa economiseasca timp de executie,in C++ exista functii
inline.Ce au ele specific?
Inaintea rulari9i prograamului fiecare apel este inlocuit cu codul functiei.Inlocuirea nu
este vizibila pentru programator.Avantajul este ca se elimina timpul de transfer de la
si catre functie.Dezavantajul este dat de faptul ca programul ocupa mai multa
memorie interna.Din acest motiv se refcomanda utilizarea acestor functii doar daca
au codul masina restrans.
In programul urmator functia mes este inline.
#include <iostream.h>

inline void mes()


{cout<<"Un mesaj"<<end1;}
main()
{mes ();mes();}
-Functiile inline nu trbuie sa contina instructiuni repetitive.In cazul in care acestea
exista ,functiile sunt tratate ca unele care nu sunt inline.
-Chiar daca eficienta utilizarii acestui mecanism nu este atat de evidenta, totusi sa nu
uitam faotul ca C++ este u=tilizat in scrierea sistemelor de operare .Acolo orice
fractiune de secunda economisita are un rolurias.

POINTERI CATRE FUNCTII


In C++ exista posibilitatea de a lucra cu variabile de tip pointerb catre functii.Acestea
REtin adrese ale zonelor de memorie in casre incepe codul functiilor.
O variabila de acest tip se declara dupa schema :
tip_functie(*nume) (lista tipurilor)=[nume functie]
-tip-functie-este dat de natura rezultatului produs de una din functiile a carei adresa
poate fi retinuta de variabila respectiv.
-nume-numele variabilei;
-lista tipurilor-reprezinta lista parametrilor formali ai uneia din functiile a carei adresa
poate fin retinuta de variabila respectiva.
-nume functie-numele functiei a carei adresa se memoreaza.
In programul urmator variabila v retine un pointer catre functia a.Pornind de la
continutul ei,se poate apela functia asa cum se vede.
#include <iostream.h>
int suma (int a,int b)
{return a+b;}
main()
{int (*v) (int,int)=suma;
cout<<v(3,5);
}
-Cu pointeri catre functii nu se pot efectua operatii aritmetice.Este normal sa fie asa
pentru ca nu toate functiile au aceeas lungime.
-Pointeri se pot imparti in doua mari grupe:pointeri catre date si pointeri catre functii.
*Se cere sa se scrie o functie care sa afiseze primele n valori ale unei functii definite
pe multimea numerelor naturale.Functi va afisa valotrile oicarei functii.
#include <iostream.h>

193
double an(int);
double bn(int0;
void tipar (double(*) (int),int);
main()
{
int n;
cout<<"n=";cin>>n;
cout<<"Primii "<<n<<"termini ai sirului an sunt"<<end1;
tipar(an,n);
cout<<" Primii "<<n<<"termeni ai sirului bn sunt"<<end1;
tipar(bn,n);
}
double an(int n)
{return(1./n+1);;}
double bn(int n)
{return (double)(n*(n+1)/(3*n*n+2);}
void tipar(double(*sir)(int),int n)
{
for (int i=1;i<=n;i++) cout<<sir(i)<<end1;
}

APELUL PROGRAMULUI CU PARAMETRI


Un program poate fi apelat cu anumiti parametri de intrare.Acestia se pot introduce in
doua feluri:
1.Scriem linia de comanda a programului uramata de parametri separati de
spatii.Exemplu t par1 par2 par3-se apeleaza programul t cu trei parametri.
2.Lista parametrilor separati dprin spatii este dat cu ajutorul comenzi ARGUMENTS a
meniului RUN din mediul integrat de programare C++.
Problema este de a extrage ,in vederea utilizarii,fiecare parametru aflat pe linia de
comanda.Pentru aceasta se declara doi parametri formali ai functiei main():
-Primul parametru este de tip int si contine numarul parametrilor se apel.Acesta este
pus de sistem(nu este trecut explicit de catre programator).
-Al doilea parametru este un vector de pointeri catre char.Fiecare sir retine un
parametru.
OBSERVATIE:Intodeauna exista un parametru.Acesta este dat de calea de aple a
programului respectiv:
#include <iostrem.h>
main(int nr_par,char*adr_p[])
{cout<<"Numzr parametri"<<nr_i++)
for (int i=0;i<nr_par;i++)
cout<<adr_p[i]<<end1;;
}

PROIECTAREA APLICATIILOR PENTRU


REZOLVAREA UNOR PROBLEME
In cazul in care veti alege meseria de informatician vetimfi pusi in dsituatia de a
elabora aplicatii care vor fi efectiv folosite.Etapele de proiectare a aplicatiilor sunt
situate amanuntit la disciplina "Sisteme informatice",disciplina predata in facultatiile
de profil.
In acest capitol invatatm cate ceva la nivelul cunostintelor dobandite pana in acest
moment,despre modul in care se proiecteaza aplicatiile,

194
De la inceput trebuie sa stiti ca astazi,nu se mai utilizeaza mediile de programare
(Borland Pascal 7.0, Borland C++ 3.1),pe care le studiem in liceu din considerente
exclusiv didactice.In schimb,majoritatea cunostintelor dobandite prin utilizarea
acestor medii sunt indispensabile progaramarii profresionalee.Pentru a realiza
aplicatii care se folosesc efectiv in practica este necesar sa studiati programarea
vizuala.
Pentru a in telege probleme;le de care "e loceste" un programator plecam de la
ipoteza ca sunteti angajat la o firma de profil si vi se da sarcina elaborarii unei
aolicatii.
*Sunteti solicitat sa elaborati un set de subprograme care sa permita
programatorilor accesul cu usurinta la niveleul de bit.
Ce observam?Enuntul este ambiguu,nu se dau nici un fel de amanunte .Nu se
precizeaaza ce subprograme trebuie scrise ,ce trebuie sa realizeze.
Dar asa se intampla in practica.Beneficiarul,in cele mai multe cazuri nu este
informnatician.Rareori veti intalni beneficiari care pot spunbeclar ce doresvc.Mai
mult,in timpul elaborarii aplicatiei,beneficiarul poate veni cu cerinte noi sau poate
schimba cerintele initiale.Este sarcina d-voastra sa faceti de asa natuara inacat
aplicatia sa corespunda dorintelor acestiaia .Nu trebuie sa va enervati si sa inbtrati
in conflict cu acesta.Beneficiarul este cel care plateste deci poate apela oricand la
serviciile altei firme.
Inexemplul dat ,se presupune ca beneficiarul este tot programator ,asa ca din acest
punct de vedere sarcinile pe care lke aveti sunt mai usoare.
Pentru a realiza aplicatia ceruta trebuie sa parcugem mai multe etape.
Etapa1.Analiza problemei.
Beneficiarul doreste accesul simplunla nivelel de bit.Limbajul de programare nua are
instructiuni specializate pentru acesta si nici nu contine programe "gata fabricate" pt
a le folosi in acest scop.Aceasata inseamana ca ca este sarcina dumneavoastra sa
scrieti aceste subprograne.Dar care sunt?
-Orice programator doreste sa poata vizualiza in binar,valoarea retinuta de o anumita
variabila .Desigur poate sa-si scrie propria secventa care sa se realizeze aceastta ,
dar n-ar fi mai bine daca ar dipune de un subprogram gata scris?
-Apoi,este important ca programatorul sa poata modifica valoarea memorata de un
bit al variabilei.In concluzie este necesar sa scriem un subprogrram care sa realizeze
aceasata operatie.
-Exista situatii in care programatoryl doreste sa stie ce memoreaza un anumit
bit.Insemna ca trebuie scris un subprogram care sa reyurneze aceasta valoare.
Suntt eficientre cele tri subprograme?Daca pot afisa in binar continutul unei
variabile, daca pot modifica un sungur bit al ei.daca pot afla ce valoare memoreza
un anumit bit ,se apre ca da!
Acum trbie sa "botezam"cele trei subprograme.Este important sa alegem nume
sugestive pentru ca utilizatorul lor sa le poate retina usor.Cele trei subprograme se
pot nnumi Vidaj,Mod_bit,Cit_bit.
Etapa 2.PROIECAREA PROPRIU_ZISA A APLICATIEI(Conceptia)
In etapa anteriaoara b am stabilit subprogramele de care avem nevoie .Acum este
necesar sa gandim modul in care ele vor fi utilizate .Cu alte cuvinte trebuie sa
analizan parametrii formali ai subgramele si modul in care aceste a returneaza
rezultatele.

→Vidaj.Are rolul de a permitea afisarea continutului in binar (baza 2) a unei


variabile.Avem variabile care ocupa 2 octeti,deci trebuie sa afisam 16 valori,variabile

195
care ocupa 6 octeti,deci trebuie sa fie afisate 48 valori.Cam mult!Utilizatorul va avea
dificultati in a “descifra” informatia pe care o primeste.

-Toate acestea ne conduc la ideea de a afisa informatia in hexa.Traducerea in binary


este imediata,spatiul ocupat de rezultat nu este asa mare.
-Nu este lipsit de interes sa analizam si modul de afisare.Rzultatul trebiu afisat de asa
natura incat sa poata fi citit cu usurinta de benificiar.Din acest motiv,intre valorile
retinute de doi octeti se va afisa un spatiu,ca alaturat: ff 01 ba c7 d3 e8.
-Rezultatul va fi returnat sub forma de sir de caractere,inclus in structura:
struct hexa{ char sir[20];};
-Desigur,s-ar fi putut ca rezultatul sa fie afisat de subprogram.In acest
caz,posibilitatile programatorului ar fi limitate.Poate ca acesta doreste ca rezultatul sa
fie prelucrat in alte secvente.
-Parametiii formali sunt:nuamrul de octeti ocupati de variabila si adresa
acesteia.Obtinem astfel urmatorul antet:
hexa Vidaj(int lungime, void*adresa)
1 Mod_bit.Are rolul de a modifica continutul unui bit al variabilei.

Asa cum am invatat,o variabila ocupa mai multi octeti consecutive.In cazul
variabilelor numerice octetii sunt ocupati invers:octetul cel mai putin semnificativ este
primul memorat,iar cel mai semnificativ ultimul.Aceasta este o informatie pe care
utilizatorul trebuie sa o cunoasca.

-Daca dorim modificarea unui bit al unui obiect al variabilei,este necesar ca un


parametru sa contina numarul octetului care contine bitul -octet- si altul numarul
bitului in cadrul octetului -bit.Consideram octetii numerotati de la 1 -octetul aflat la
adresa cea mai mica,la n octetul aflat la adresa cea mai mare.Bitii unui octet se
considera numerotati de la 0 la 7,unde bitul 0 este bitul cel mai putin semnificativ.

Iata antetul:

void Mod_bit(void*adr_var,int octet,int bit,int valoare)

→ Cit_bit.Trebiue sa cunoastem adresa variabilei care contine bitul,octetul si indicele


bitului,la fel ca anterior.Subprogramul trebuie sa returneze 0 sau 1.

Int Cit_bit(void*adr_var,int octet,int bit)

1 Este indicat ca dupa aceasta etapa sa purtam o discutie cu beneficiarul.Poate


acesta doreste si altceva sau poate nu este multumit de modul in care am
gandit pana acum.

Etapa 3.Realizarea programelor(subprogramelor).

Primul subprogram este:

struct hexa{ char sir[20];};

196
struct hexa{ char sir[100];};
hexa Vidaj(int lungime, void*adresa)
{
hexa retur;strcpy(retur.sir,” ”);
unsigned char man [10] =” ‘,*adr=(char*)adresa,i;
for (i=0;i<lungime;i++)
{
/ / convertesc bitii 4-7 in hexa
strcat(retur.sir,itoa(*adr/16,man,16));
/ / convertesc bitii 0-3 in hexa
strcat(retur.sir,itoa(*adr%16,man,16));
/ / adaug spatiu
strcat(retur.sir,” ”);
adr++;
}
return retur;
}

Adresa variabilei este convertita ca adresa catre char(char*).Apoi,fiecare octet al


variabilei este convertit in hexa (functia itoa) si adaugat sirului in hexa (strcat).Dupa
fiecare octet se adauga spatiu.

Al doilea subprogram care trebuie realizat este:

void Mod_bit(void*adr_var,int octet,int bit,int valoare)


{
unsigned char *adr=(char*)adr_var, masca=1;
/ /obtin adresa octetului
adr+=octet-1;
/ / formez masca
masca<<=bit;
/ / dau valoarea dorita bitului
if (!valoare) *adr&=!masca;
else *adr|=masca;
}

Presupun ca in acest moment este clar modul in care accesam un anumit octet prin
utilizarea pointerilor.Sa analizam mecanismul prin care,fiind dat un anumit octet prin
adresa sa,putem face ca un anumit bit al sau,identificat prin numar de ordine,sa ia o
anumita valoare.
1 Initial o variabila de tip char,va retine 1 pe pozitia care trebuie modificata si 0
in rest.In programare,o astfel de variabila se numeste masca.De exemplu,daca
dorim ca bitul 3 sa ia o anumita valoare masca este 00001000-bitii sunt
numerotati de la 0 la 7,bitul cel mai putin semnificativ este bitul 0.
2 Daca vrem ca bitul marcat sa retina 0,se efectueaza urmatoarele:
a) se inverseaza continutul bitilor-cei care retin 0 vor retine 1 si invers.Masca de
exemplu devine: 11110111 (operatorul !)

197
b) Se efectueaza si logic pe biti intre masca si octetul care trebuie modificat.In
acest fel bitul caruia in masca ii corespunde 0,va fi 0,iar restul raman
nemodifeicati.
1 Daca vrem ca bitul marcat sa retina 1,atunci se efectueaza sau logic intre
masca nemodificata si octetul al carui bit trebuie modificat.
→ Iata si al treilea subprogram care are rolul de a returna valoarea memorata de
un bit.

int Cit_bit(void*adr_var,int octet,int bit)


{
unsigned char *adr=(char*)ard_var,masca=1;
/ / adresa octetului
adr+=octet-1;
/ / formez masca
masca<<=bit;
/ / returnez valoarea in functie de rezultatul intersectiei
/ / logice dintre masca si octet
if (*adr & masca) return 1;
else return 0;
}
2 Observati faptul ca subprogramele au fost commentate.Este posibil ca aplicatia
sa fie dezvoltata de o alta persoana.Ar fi imposibil ca aceasta sa se descurce in
absenta unor comentarii in secventele scrise de dumneavoastra!
3 La firmele serioase nu se primesc aplicatii necomentate.

Etapa 4. Finalizarea proiectului

Subprogramele au fost scrise.Cum le oferim beneficiarului?Cea mai buna solutie este


includerea intr-o biblioteca.Sa numim aceasta biblioteca biti.
Vom da comanda:tlib biti +biti,unde biti reprezinta fisierul obiect(extensia obj)-
obtinut dupa compilarea fisierului care contine sursele functiilor.De
asemenea,beneficiarul va primi fisierul antet acbiti.h,pe care-l va memeora directorul
unde are aplicatia.Acesta este:

struct hexa{char sir[100];};


hexa Vidaj (int lungime, void*adresa);
void Mod_bit(void*ard_var,int octet,int bit,int valoare);
int Cit_bit(void*adr_var,int octet,int bit);

Pentru a putea utilize functia utilizatorul va crea un proiect,care va include suursa


lui(programul care utilizeaza functiile) si biblioteca.Fisierul antet va fi inclus de catre
utilizator,asa cum rezulta din programul urmator:

#include <iostream.h>
#include “acbiti.h”

main( )
{

198
double a=2;int I;
cout<<Vidaj(sizeof(a),&a).sir<<end1;
Mod_bit(&a,2,3,1);
cout<<Vidaj(sizeof(a),&a).sir<<end1;
for (i=0;i<8*sizeof(a);i++)
cout<<Cit_bit(&a,i/8+1,7-i%8);
}
Etapa 5.Elaborarea documentatiei

Exista doua tipuri de documentatii pe care trebuie sa le elaborate:


1) Documentatia care ajunge la beneficiar
2) Documentatia catre firma la care sunteti angajati
3) Documentatia catre beneficiar cuprinde:
 Titlul aplicatiei
 Prezentarea aplicatiei;

Aici trebuie sa fiti atenti ca aplicatia sa fie prezentatat intr-un mod


commercial.Glumind(pentru ca aplicatia data de exemplu este extreme de
simpla)puteti scrie: “De acum programatorii pot rasufla usurati.Prin utilizarea
aplicatiei Biti,accesul la nivel de bit nu mai este o probelma.Un soft
performant,pentru adevaratii profesionisti!”.

1 Descrierea functionalitatii programelor (subprogramelor) care o alcatuiesca si


exemple de utilizare,cum ar fi programul de mai sus.Documentatia catre firma
trebuie sa cuprinda sursele commentate ale programelor,subprogramelor ca,de
fapt,orice considerati ca poate fi de folos unui coleg care va aduce la zi
aplicatia dvs.
o Sa se scrie un set de subprograme care permit lucrul cu multimi de
numere naturale intre 0 si 2039.

In C++ nu exista un tip de date care sa permita lucrul cu multimi,desi ar fi fost util.

Am invatat faptul ca o miltime se retine prin vectorul characteristic.Aplicatiile pe care


le-am realizat anul trecut cu ajutorul acestuia,foloseau pentru fiecare element un octet
care memora 0 sau 1.In acest fel se aloca memorie inutil,pentru ca o valoare 0 asu 1
poate fi retinuta de un bit.

In acest fel ajungem al idea de a memora o multime la nivel de bit.Pentru ca multimile


sa poata avea ca elemente numere naturalae intre 0 si 2039 sunt suficienti 255
octeti(2040/8).

Este necesar sa declaram un tip,numit Multime,pentru care orice variabila sa ocupe


255 de octeti.
struct Multime { char sir[255];};
In acest fel avem posibiliatea sa facem declaratii de genul:
Multime A,B;

Pentru a putea lucra cu multimi vom utilize urmatoarele subprograme:

199
→ void Init(Multimea A);
{
for (int i=0; i<=255;i++) A.sir[i]=0;
}

→ int Aprtine(int n,Multime A);

Testeaza daca elemental n apartine sau nu mult5imii si returneaza 1,respective


0.Atentie!In cadrul unui octet bitii sunt memorati invers.
int Apartine(int n,Multime A)
{
unsigned char Nr_Octet=n/8,Nr_Bit=7-n%8,masca=1;
masca<<=Nr_Bit;
if (masca & A.sir[Nr_Octet]) return 1;
else return 0;
}
→ void Adaug(int n,Multime& A)

Face reuniunea intre multimea cu un singur element n si multimea A.Rezultatul se


gaseste in A.Trebuie ca bitul corespunzator lui n sa fie 1.

Void Adaug(int n,Multime& A)


{
unsigned char Nr_Octet=n/8,Nr_Bit=7-n%8,masca=1;
masca<<=Nr_Bit;
A.sir[Nr_octet]|=masca;
}
→ Multime Reuniune(Multime A,Multime B);

Face reuniunea multimilor A si B si returneaza rezultatul.S-a folosit operatorul sau pe


biti.

Multime Reuniune(Multime A,Multime B)


{
Multime C;
for (int i=0;i<=255;i++) c.sir[i]=A.sir[i] | B.sir[i];
return C;
}
→Multime intersectie(Multime A,Multime B);
Face intersectia multimilor A si B si returneaza rezultatul.
Multime Intersectie(Multime A,Multime B)
{
Multime C;
for (int i=0;i<=255;i++) C.sir[i]=A.sir[i] & B.sir[i];
return C;
}

→ Multime Diferenta (Multime A,Multime B)

200
Calculeaza diferenta intre multimile A si B.
Multime Diferenta (Multime A,Multime B)
{
Multime C;
for (int i=0;i<=255;i++) C.sir[i]=A.sir[i] & (a.sir[i] ^ B.sir[i]);
return C;
}

1 Subprogramele scrise nu necesita comentarii.Sunt atat de simple…

Etapa 4. Finalizarea aplicatiei.

De aceasta data,pentru a putea folsi apliactia,vom include fisierul care contine textul
sursa.

In continuare,prezentam doua aplicatii:

A) Se citesc n numere naturale mai mici decat 200.Se cere sa se tipareasca fiecare
numar o singura data.

Problema este calsaica.Se rezolva prin utilizarea tipului Multime.O multime initial
vida este reunite,pe rand,cu fiecare numar citit.Evident ca in cazul in care un numar
este citit de doua ori,in multime nu apare decat o data.In final,se listeaza multimea.
#include <iostream.h>
#include “multimi.cpp”
main( )
{
Multime a; Init(a);
init n,nr,I;
cout<<’n=’;cin>>n;
for (i=0;i<n;i++)
{
cin>>nr;
Adaug(nr,a);
}
for (i=0;i<=200;i++)
if (Apartine(I,a)) cout<<i<<” “;
}
B) La fel ca al problema anterioara,numai ca se citesc literele mici ale alfabetului.

Sa nu uitam de faptul ca tipul char poate fi privit ca tip numeric.Limbajul permite


asta.In concluzie,nu exista mari diferente fata de programul anterior.
#include <iostream.h>
#include “multimi.cpp”
main ( )
{
Multime a; Init(a);
init n,I;

201
unsigned char ch;
cout<<”n=”;cin>>n;
for (i=0;i<n;i++)
{
cin>>ch;
Adaug(ch,a);
}
for (ch=’a’;ch<=’z’;ch++)
if (Apartine(ch,a)) cout <<ch<<” ”;

CLASA a-XI-a

Capitolul 1
Alocarea dinamica a memoriei

1.Variabile de tip pointer

Memoria interna poate fi privita ca o succesiune de octeti.Numarul de


ordine al unui octet se numeste adresa lui.Adresa unei variabile nu trebuie
confundata cu valoarea pe care aceasta o memoreaza.
Definitie: Adresa primului octet al variabilei se numeste adresa variabilei.
Adresele variabilelor se memoreaza cu ajutorul variabilelor de tip pointer.
Tipul unei variabile de tip pointer se declara astfel:
tip *nume
Exemple: int *adr1, *adr2
float * adresa

202
Adresa unei variabile se obtine cu ajutorul operatorului ‘ & ‘, care trebuie sa
preceada numele variabilei:
&Nume_variabila;
Exemplu: adr1=&numar – variabilei adr1 i se atribuie adresa variabilei numar.

Exemple:
1.Variabila a este initializata cu 7, iar variabila adr este initializata cu adresa lui
a.Secventa afisaza continutul variabilei a pornund de la adresa:

int a=7, *adr=&a;


cout<<*adr;

2.Variabila a, de tip elev, este initializata, iar variabila adra,de tip pointer
catre variabile de tip elev este initializata cu adresa variabilei a.Secventa
tipareste continutul variabilei a:


struct elev
{
char nume[20], prenume[20];
};


elev a, *adra=&a;
strcpy(a.nume, “Bojian”);
strcpy(a.prenume, “Andronache”);
cout<<(*adra).nume<<” “<<(*adra).prenume<<endl;
Se poate folosi si operatorul de selectie indirecta ‘-> ’, cu prioritate
maxima:
cout<<adra->nume<<” “<<adra->prenume;

Intre variabilele de tip pointer sunt permise atribuiri numai in cazul in care
retin adrese catre acelasi tip de variabile.

2.Alocarea dinamica a memoriei

Spatiul necesar memorarii este rezervat in HEAP, in timpul


executiei programului, iar atunci cand variabila nu mai este utila, spatiul
din memorie este eliberat.
In C++, pentru alocarea dinamica se utilizeaza urmatorii operatori:
 Operatorul new aloca spatiu in HEAP pentru o variabila
dinamica.Dupa alocare, adesa variabilei se atribuie lui p, care este o
variabila de tip pointer catre tip:

203
p=new tip
 Operatorul delete elibereaza spatiul rezervat pentru variabila a
carei adresa este retinuta in p.Dupa eliberare, continutul variabilei p
este nedefinit.
delete p
Exemple:
1.Variabile de tip pointer catre variabile de tip int.Variabila adr1 poate
retine adrese ale variabilelor de tip int:

int *adr1;
adr1=new int;
*adr1=7;
cout<<*adr1;
delete adr1;

2.Variabile de tip pointer catre variabile de tip float.Variabila adresa


poate retine adrese ale variabilelor de tip float:

float* adresa;
adresa=new float;
*adresa=7.65;
cout<<adresa;

3.Variabile de tip pointer catre variabile de tip inreg, care la randul lor
sunt de tip struct.
Variabila adr_inr poate retine adrese ale variabilelor de tipul inreg.

#include<iostream.h>
struct inreg
{ char nume[20], prenume[20];
int varsta;
};
main ( )
{ inreg* adr;
adr=new inreg;
cin>>adr->nume>>adr->prenume<<endl<<adr->varsta;
}

3.Alocarea dinamica a masivelor

 Un tablou p-dimensional se declara astfel:


tip nume[n1] [n2] … [np]

204
 Numele tabloului de tip p-dimensional de mai sus este pointer
constant catre un tablou p-1 dimensional de forma [n2] … [np], care
are componentele de baza de acelasi tip cu cele ale tabloului.
 Un pointer catre un tablou k dimensional cu [l1] [l2] …[lk] componente
de un anumit tip
se declara astfel:
tip (*nume) [l1] [l2] …[lk];
 Intrucat numele unui masiv p dimensional este pointer catre un masiv
p-1 dimensional, pentru a aloca dinamic un masiv se va utiliza un
pointer catre masive p-1 dimensionale (ultimele p-1 dimensiuni).

Exemple:
1.Alocam in HEAP un vector cu 4 componente de tip int.Numele unui
astfel de vector are tipul *int.Prin urmare, variabila a are tipul int*.Dupa
alocare, ea va contine adresa primului element al vectorului din HEAP.

int *a=new int[4];


a[3]=2;

2.Declaram in HEAP o matrice cu 3 linii si 5 coloane, cu elemente de tip


double:

double (*a)[5]=new double [3] [5];


a[1] [2]=7.8;

 In cazul masivelor, la eliberarea memoriei cu delete trebuie tinut cont


de tipul pointerului.

 Programul urmator citeste si afiseaza o matrice alocata in HEAP.

#include<iostream.h>
main ( )
{
int m,n,i,j,(*adr)[10];
adr=new int[10] [10];
cout<<”m=”; cin>>m;
cout<<”n=”; cin>>n;
for(i=0;i<m;i++)
for(j=0;j<n;j++)
cin>>adr[i][j];
for(i=0;i<m;i++)

205
{
for(j=0;j<n;j++)
cout<<adr[i][j]<<” “;
cout<<endl;
}
}

 Functiile nu pot intoarce masive, insa pot returna un pointer catre


orice tip.Programul urmator citeste doua matrice si afiseaza suma
lor.Matricele sunt rezervate in HEAP.

#include<iostream.h>
void* Cit_Mat(int m, int n)
{
int i,j,(*adr1)[10]=new int [10] [10];
for(i=0;i<m;i++)
for(j=0;j<n;j++) cin>>adr1[i][j];
return adr1;
}
void Tip_Mat(int m, int n, int(*adr1)[10])
{ int i,j;
for(i=0;i<m;i++)
{
for(j=0;j<n;j++)
cout<<adr1[i][j]<<” “;
cout<<endl;
}
}
void* Suma_Mat(int m, int n, int(*adr1)[10],int (*adr2)[10])
{ int i,j,(*adr)[10]=new int[10][10];
for(i=0;i<m;i++)
for(j=0;j<n;j++) adr[i][j]=adr1[i][j]+adr2[i][j];
return adr;
}
main ( )
{ int m,n,i,j,(*adr)[10],(*adr1)[10],int (*adr2)[10];
cout<<”m=”; cin>>m;
cout<<”n=”; cin>>n;
adr1=(int(*)[10]) Cit_Mat(m,n);
adr2=(int(*)[10]) Cit_Mat(m,n);

206
adr=(int(*)[10]) Suma_Mat(m,n,adr1,adr2);
Tip_Mat(m,n,adr);
}

 Pentru fiecare dintre cele n materiale aflate intr-o magazie se


cunoaste: denumirea materialului, unitatea de masura si cantitatea care
se gaseste in magazie.Programul de mai jos citeste informatiile si le
organizeaza astfel:
-segmentul de date contine un vector cu n componente, unde
fiecare componenta
retine un pointer catre inregistrarea cu datele citite pentru fiecare
material;
-datele citite pentru fiecare material se organizeaza ca struct, alocat
in HEAP.
#include<iostream.h>
struct material
{ char denumire[20], unit_mas[5];
float cantitate;
};
void Cit_Material(int i,material* v[100])
{
v[i]=new material;
cout<<”Denumire ”; cin>>v[i]->denumire;
cout<<”Unitate de masura ”; cin>>v[i]->unit_mas;
cout<<”Cantitate “; cin>>v[i]->cantitate;
}
void Tip_Material(int i,material* v[100])
{
cout<<v[i]->denumire<<endl<<v[i]->unit_mas<<endl<<v[i]-
>cantitate;
}
main ( )
{ int n,i;
material* v[100];
cout<<”n=”; cin>>n;
for(i=0;i<n;i++) Cit_Material(i,v);
for(i=0;i<n;i++) Tip_Material(i,v);
}

Capitolul 2

207
Liste liniare

1.Definitia listelor

Def.:O lista liniara este o colectie de n>=0 noduri, X1,X2,…,Xn, aflate


intr-o relatie de ordine.Astfel, X1 este primul nod al listei, X2 este al doilea
nod al listei,…, Xn este ultimul nod.Operatiile permise sunt:
 Accesul la oricare nod al listei in scopul citirii sau modificarii informatiei
continute de acesta.
 Adaugarea unui nod, indiferent de pozitia pe care o ocupa in lista.
 Stergerea unui nod,indiferent de pozitia pe care o ocupa in lista.
 Schimbarea pozitiei unui nod in cadrul listei.

2.Liste liniare alocate simplu inlantuit


2.1 Prezentare generala

O lista liniara simplu inlantuita este o structura de forma:


in1 adr1 in2 adr2 inn 0
adr1 adr2 adrn

Dupa cum se observa, fiecare nod, cu exceptia ultimului, retine adresa


nodului urmator.
1. Accesul la un nod al listei se face parcurgand nodurile care il preced.
2. Informatiile ocupa memorie, fiind prezente in cadrul fiecarui nod.
3. Avantajele alocarii inlantuite sunt date de rapiditatea operatiilor de
adaugare si eliminare.

2.2 Crearea si afisarea listelor

Crearea listelor

#include<iostream.h>
struct Nod
{
int info;
Nod* adr_urm;
};

208
Nod*v;
int nr;

void Adaug(Nod*& v, int nr)


{
Nod* c=new Nod;
c->info=nr;
c->adr_urm=v;
v=c;
}

void Tip(Nod* v)
{
Nod*c=v;
while(c)
{
cout<<c->info<<endl;
c=c->adr_urm;
}
}

main( )
{
cout<<”numar=”; cin>>nr;
while(nr)
{
Adaug(v,nr);
cout<<”numar=”; cin>>nr;
};
Tip(v);
}

Procedand astfel, lista va contine informatiile in ordinea inversa in


care au fost introduse.

Urmatorul algoritm de creare a listei este cel recursiv.De aceasta


data, lista va contine informatiile in ordinea in care acestea au fost
introduse.

209
#include<iostream.h>
struct Nod
{
int info;
Nod* adr_urm;
};

Nod*v;

Nod* Adaug
{
Nod* c;
int nr;
cout<<”numar ”; cin>>nr;
if(nr)
{
c=new(Nod);c->adr_urm=Adaug( );
c->info=nr; return c;
}
else
return 0;
}

void Tip(Nod* v)
{
Nod*c=v;
while(c)
{
cout<<c->info<<endl;
c=c->adr_urm;
}
}

main( )
{ v=Adaug( );
Tip(v);
}

Mai jos este prezentata o functie recursiva care tipareste


informatiile in ordinea inversa fata de modul in care se gasesc in lista.

210
void Tip_inv(Nod* v)
{
if(v)
{
Tip_inv(v->adr_urm);
cout<<v->info<<endl;
}
}

2.3 Operatii asupra unei liste liniare

1.Adaugarea. Fiind data o lista liniara, se cere sa se adauge la


sfarsitul ei un nod, cu o anumita informatie, de exemplu o valoare
intreaga.Functia care realizeaza acest lucru are antetul:
void Adaugare(Nod*& v, Nod*& sf, int val)
Se disting doua cazuri:

a) Lista este vida – v retine 0.Se aloca in HEAP nodul respectiv, adresa sa
va fi in v, iar adresa primului nod va fi si adresa ultimului.
b) Lista este nevida.Initial se aloca spatiu pentru nod.Campul de adresa al
ultimului nod va retine adresa nodului nou creat, dupa care si sf va retine
aceeasi valoare.

2.Inserarea unui nod dupa un altul, de informatie


cunoscuta.Operatia este executata de functia:
void Inserare_dupa(Nod* v, Nod*& sf, int val, int val1)
-val este informatia cautata
-val1 este informatia care se adauga.

3.Inserarea unui nod inaintea altuia, de informatie


cunoscuta.Functia care realizeaza aceasta este:
void Inserare_inainte(Nod*& v, int val, int val1)

4.Stergerea unui nod de informatie data.Se realizeaza cu functia:


void Sterg(Nod*& v, Nod *& sf, int val)
a) Nodul nu este primul:
- informatia de adresa a nodului care il precede trebuie sa retina adresa
nodului urmator
- memoria ocupata de nodul care urmeaza a fi sters este eliberata
b) Nodul este primul:
- variabila v va retine adresa celui de-al doilea nod

211
- spatiul ocupat de primul nod va fi eliberat

Mai jos este prezentat modulul care contine functiile:

#include<iostream.h>
struct Nod
{
int info;
Nod* adr_urm;
};

void Adaugare(Nod*& v, Nod*& sf, int val)


{
Nod* c;
if(v==0)
{
v=new(Nod); v->info=val; v->adr_urm=0;
sf=v;
}
else
{
c=new(Nod); sf->adr_urm=c;
c->info=val; c->adr_urm=0;
sf=c;
}
}

void Inserare_dupa(Nod* v, Nod*& sf, int val, int val1)


{
Nod* c=v, *d;
while(c->info!=val) c=c->adr_urm;
d=new Nod; d->info=val1;
d->adr_urm=c->adr_urm; c->adr_urm=d;
if(d->adr_urm==0) sf=d;
}

void Inserare_inainte(Nod*& v, int val, int val1)


{
Nod*c,*d;
if(v->info==val)
{

212
d=new Nod; d->info=val1; d->adr_urm=v; v=d;
}
else
{
c=v;
while(c->adr_urm->info!=val) c=c->adr_urm;
d=new Nod; d->info=val1; d->adr_urm=c->adr_urm; c-
>adr_urm=d;
}
}

void Sterg(Nod*& v, Nod *& sf, int val)


{
Nod* c,*man;
if(v->info==val)
{
man=v; v=v->adr_urm;
}
else
{
c=v;
while(c->adr_urm->info!=val) c=c->adr_urm;
man=c->adr_urm; c->adr_urm=man->adr_urm;
if(man==sf) sf=c;
}
delete man;
}

void Listare(Nod* v)
{
Nod* c=v;
while(c)
{
cout<<c->info<<endl;
c=c->adr_urm;
}
cout<<endl;
}

Programul urmator are rolul de a testa modulul:

213
#include “lista_u.cpp”
Nod* v,*sf;
int i;
main ( )
{
for(i=1;i<=10;i++) Adaugare(v,sf,i);
Listare(v);
Inserare_dupa(v,sf,7,11);
Inserare_dupa(v,sf,10,12);
Inserare_dupa(v,sf,1,13);
Listare(v);
Inserare_inainte(v,13,14);
Inserare_inainte(v,1,15);
Listare(v);
Sterg(v,sf,15);
Sterg(v,sf,13);
Sterg(v,sf,12);
Listare(v);
}

2.4 Aplicatii ale listelor liniare


2.4.1 Sortarea prin insertie

Se citesc de la tastatura n numere naturale.Se cere ca acestea sa fie


sortate crescator prin utilizarea sortarii prin insertie.Problema se reduce la
insertia unui numar intr-o lista deja sortata.
Mai intai se aloca spatiu in HEAP pentru o valoare, apoi aceasta
este citita.Se disting doua cazuri:

1. Valoarea citita este mai mica decat prima valoare a listei.Aceasta


inseamna ca ea este cea mai mica din lista si va fi introdusa prima in lista.
2. Valoarea citita nu este cea mai mica din lista.Daca nu a fost indeplinita
conditia de la cazul 1, ea va trebui introdusa in interiorul listei.

#include<iostream.h>
const MaxInt=32000;
struct Nod
{
int info;
Nod* adr_urm;
};

214
int n,i;
Nod *v,*adr,*c,*c1;
main ( )
{
cout<<”n=”; cin>>n;
v=new Nod; v->info=MaxInt; v->adr_urm=0;
for(i=1;i<=n;i++)
{
adr=new Nod;
cout<<”numar=”; cin>>adr->info;
if(adr->info<=v->info)
{
adr->adr_urm=v;
v=adr;
}
else
{
c=v;
c1=v->adr_urm;
while(c1->info<adr->info)
{
c=c->adr_urm;
c1=c1->adr_urm;
}
c->adr-urm=adr;
adr->adr_urm=c1;
}
}
c=v;
while(c->info!=MaxInt)
{
cout<<c->info<<endl;
c=c->adr_urm;
}
}

2.4.2 Sortarea topologica

Presupunem ca dorim sortarea numerelor 1,2,..,n, numere care se


gasesc intr-o ordine oarecare, alta decat cea naturala.Pentru a afla relatia
de ordine in care se gasesc numerele, introducem un numar finit de

215
perechi (i,j).O astfel de pereche ne spune faptul ca, in relatia de ordine
considerata, i se afla inaintea lui j.
Pentru fiecare numar intre 1 si n trebuie sa avem urmatoarele
informatii:
- numarul predecesorilor;
- lista succesorilor.

Pentru aceasta folosim doi vectori:


- contor – vector care retine numarul predecesorilor fiecarui k
intre1 si n;
- a, care retine adresele de inceput ale listelor de succesori ai
fiecarui element.
Pentru fiecare element exista o lista simplu inlantuita a succesorilor
sai.
Citirea unei perechi (i,j) presupune incrementarea variabilei
contor(j) si adaugarea lui j la lista succesorilor lui i.
Toate elementele care au 0 in campul contor se retin intr-un vector
c.
Fiecare element al vectorului c se tipareste, se marcheaza cu –1
campul sau de contor, iar pentru toti succesorii sai se scade 1 din campul
contor.
Daca au fost tiparite toate elementele, algoritmul se incheie.
Daca nu avem nici un element cu 0 in campul contor, se reia
algoritmul.

#include<iostream.h>
struct Nod
{
int succ;
Nod* urm;
};

int n,m,i,j,k,gasit;
int contor[100],c[100];
Nod* a[100];

void adaug(int i,int j)


{ Nod *c,*d;
contor[j]++;
c=a[i];
d=new Nod;

216
d->urm=0;d->succ=j;
if (c==0)
a[i]=d;
else
{ while(c->urm) c=c->urm;
c->urm=d;
}
}
void actual(int i)
{ Nod* c=a[i];
while(c)
{ contor[c->succ]- -;
c=c->urm;
}
}
main( )
{ cout<<”n=”; cin>>n;
for(i=1;i<=n;i++)
{ contor[i]=0;
a[i]=0;
}
while(i)
{ cout<<”Tastati i, j=”; cin>>i>>j;
if (i) adaug(i,j)
}
m=n;
do
{ k=1;
gasit=0;
for(i=1;i<=n;i++)
if(contor[i]==0)
{ gasit=1;
m- -;
c[k]=i;
k++;
contor[i]=-1;
}
for(i=1;i<=k-1;i++)
{ actual(c[i]);
cout<<c[i]<<endl;
}

217
}
while(gasit && m);
if(m) cout<<”relatii contradictorii”;
else cout<<”Totul e OK”;
}

2.3 Liste alocate dublu inlantuit

O lista alocata dublu inlantuit este o structura de date de forma:

0 in1 adr2 adr1 in2 adr3 adrn-1 inn 0 0


0000adr3
adr1 adr 2
adrn
Operatiile posibile asupra unei liste dublu inlantuite sunt:

1) creare;
2) adaugare la dreapta;
3) adaugare la stanga;
4) adaugare in interiorul listei;
5) stergere din interiorul listei;
6) stergere la stanga listei;
7) stergere la dreapta listei;
8) listare de la stanga la dreapta;
9) listare de la dreapta la stanga.

1) Creare

O lista dublu inlantuita se creeaza cu o singura inregistrare.Pentru a


ajunge la numarul de inregistrari dorit, utilizam functii de adaugare la
stanga si la dreapta.Functia creare realizeaza citirea informatiei numerice,
alocarea de spatiu pentru inregistrare, completarea inregistrarii cu
informatia si completarea adreselor la dreapta si la stanga cu 0.

2) Adaugare la dreapta

Functia add citeste informatia numerica, aloca spatiu pentru inregistrare,


completeaza adresele, modifica adresa la dreapta a inregistrarii din s cu
adresa noii inregistrari si ii atribuie
lui s valoarea noii inregistrari.

218
3) Adaugare la stanga

4) Adaugare in interiorul listei

Functia includ parcurge lista pentru a gasi inregistrarea cu informatia m,


in dreapta careia urmeaza sa introducem noua inregistrare, citeste
informatia, aloca spatiu, completeaza informatia, completeaza adresa
stanga a noii inregistrari cu valoarea adresei inregistrarii de informatie m,
si completeaza adresa dreapta a noii inregistrari cu valoarea adresei
dreapta a inregistrarii cu informatia utila m.

5) Stergere in interiorul listei

Functia Sterg parcurge lista pentru a gasi informatia care va fi stearsa,


atribuie inregistrarii precedente campul de adresa dreapta al inregistrarii
care va fi stearsa, iar inregistrarii care urmeaza celei care va fi stearsa i se
atribuie campul de adresa stanga al inregistrarii pe care o stergem, dupa
care se elibereaza spatiul de memorie rezervat inregistrarii care se sterge.

6)-7) Stergere la stanga si la dreapta listei

8) Listare de la stanga la dreapta

Functia listare porneste din stanga listei si tipareste informatia numerica a


fiecarei inregistrari, atata timp cat nu s-a ajuns la capatul listei.

9) Listare de la dreapta la stanga

#include<iostream.h>
struct Nod
{ Nod *as,*ad;
int nr;
};

Nod *b,*s,*c;
int n,m,i;

void Creare(Nod*& b, Nod*& s)


{ cout<<”n=”; cin>>n;
b=new Nod; b->nr=n;
b->as=b->ad=0;

219
s=b;
}
void Addr(Nod*& s)
{ cout<<”n=”; cin>>n;
Nod* d=new Nod;
d->nr=n;
d->as=s; d->ad=0;
s->ad=d; s=d;
}

void Listare(Nod*& b)
{ Nod* d=b;
while(d)
{ cout<<d->nr<<endl;
d=d->ad;
}
}

void Includ(int m,Nod* b)


{
Nod *d=b, *e;
while(d->nr!=m) d=d->ad;
cout<<”n=”; cin>>n;
e=new Nod;
e->nr=n;
e->as=d;
d->ad->as=e;
e->ad=d->ad;
d->ad=e;
}

void Sterg(int m, Nod *b)


{ Nod* d=b;
while(d->nr!=m) d=d->ad;
d->as->ad=d->ad;
d->ad->as=d->as;
delete d;
}

main ( )
{ cout<<”Creare lista cu o singura inregistrare “<<endl;

220
Creare(b,s);
cout<<”Cate inregistrari se adauga ?”; cin>>m;
for(i=1;i<=m;i++) Addr(s);
cout<<”Acum listez de la stanga la dreapta”<<endl;
Listare(b);
cout<<”Includem la dreapta o inregistrare”<<endl;
cout<<”Dupa care inregistrare se face includerea?”; cin>>m;
Includ(m,b);
cout<<”Acum listez de la stanga la dreapta”<<endl;
Listare(b);
cout<<”Acum stergem o inregistrare din interior”<<endl;
cout<<”Ce inregistrare se sterge?”;
cin>>m;
Sterg(m,b);
cout<<”Acum listez de la stanga la dreapta”<<endl;
Listare(b);
}

Capitolul 3
Elemente de teoria grafurilor

1. Grafuri orientate
1.1 Notiuni introductive
_
Definitie.Se numeste graf orientat perechea ordonata G=(x,l ).
-Daca in acest graf [x,y] apartin multimii gama, vom spune ca x si y
sunt adiacente, iar varfurile x si y sunt incidente cu muchia [x,y].
-Daca gama(g)=multimea vida, acest graf se numeste graf nul si
reprezentarea lui in plan se reduce la puncte izolate.
Definitie.Un graf partial al unui graf orientat dat este un graf G1=(x,g1)
unde g1este inclus sau = cu g.
Definitie. Un subgraf al unui graf orientat G=(x,g) este un graf
H=(y1,g1),unde y1-inclus sau = cu x, iar muchiile din g1 sunt toate
muchiile din g care au ambele extremitati in multimea y.
-Graful unui nod x(d(x)) este de doua feluri:
*graf exterior
*graf interior
Definitie.Un drum al unui graf orientat L=[x0,x1,x2,…xp] este o
succesiune de varfuri cu proprietatea ca [x0,x1]aparrtine de g, [x1,x2] la
fel, s.a.

221
-x0 si xp = extremitatile drumului;
-p=lungimea drumului;
-daca x0,x1,…,xp sunt distincte doua cate doua drumul=elementar;
-daca x0=xp,drumul=circuit
-daca toate varfurile circuitului, cu exceptia primului si ultimului sunt
distincte, circuitul=elementar;
-un circuit elementar care trece prin toate nodurile grafului se numeste
circuit hamiltonian.

1.2 Metode de reprezentare in memorie


a unui graf

Exista mai multe metode de reprezentare in memorie a unui graf orientat:


1-metoda matricei adiacente:
{1, pentru[I,j] apartin de g
a[I,j]={
{0, pentru[I,j]nu apartinde g
*Pentru citirea fisierului si memorarea sa sub forma matricei de adiacenta
vom utiliza functia Citire.
#include <fstream.h>
void Citire(char Nume_fis[20],int a[50][50],int& n)
{
Fstream f(Nume_fis,ios::in);
Int I,j;
f>>n;
while (f>>I>>j) A[I][j]=1;
f.close();
}
Iata acum un program care citeste matricea asociata si o afiseaza pe
monitor:
#include “grafuri.cpp”
int A[50][50],n,I,j;
main()
{
Citire(“Graf.txt”,A,n);
For (I=1;i<=n;i++)
{ for (j=1;j<=n;j++) cout <<A[I][j[]<<” “;
cout<<endl;
}
}

222
2-metoda listelor de adiacenta:
*Functia Citire_l are rolul de aciti graful din fisierul organizat ca
anteriorul si de a-l memora sub aceasta forma.In continuare sunt
prezentate si tipurile de date utilizate.Toate sunt incluse in modulul
grafuri.
Struct Nod
{
int nd;
Nod* adr_urm;
};

Iata acum un exemplu de program care creeaza, citeste si afiseaza listele


de adiacenta:
Void Citire_1(char Nume_fis[20], Nod* L[50], int& n)
{
Nod* p;
Int I,j;
Fstream f(Nume_fis,ios::in);
f>>n;
for (I=1;I<=n;I++) L[I]=0;
while (f>>I>>j)
{ p=new Nod;
p->adr_urm=L[I]; p->nd=j;
L[I]=p;
}
f.close();
}
1.3 Parcurgerea grafurilor orientate

1.Parcurgerea in latime(BF-breadth first)


-parcurgerea in latime se face incepand de la un nod I, pe care il
consideram parcurs;
-parcurgem apoi toti descendentii sai – multimea nodurilor j pentru care
exista [I,j]apartin de g;
-parcurgem apoi toti descendentii nodurilor parcurse la pasul anterior.
Parcurgerea BF se efectueaza prin utilizarea structurii numita coada,
avand grija ca un nod sa fie vizitat o singura data. Coada va fi alocata prin
utilizarea unui vector.
1)Cazul grafului memorat prin liste da adiacenta –recursivsi nerecursiv
#include “grafuri.cpp”
Nod* L[50];

223
Int coada[50,s[50],I_csf_c,I,n;
{ functia de parcurgere BF_R sau BF
main()
{ Citire_l(“Graf.txt”,L,n);
I_c=1; sf_c=1; coada[I_c]=1; s[1]=1;
Bf_r();
For (I=1;I<=sf_c;I++) cout<<coada[I]<<” “;
}

void bf_r() void bf()


{ {
Nod*p; Nod*p;
If (I_c<=sf_c) while (I_c<=sf_c)
{ {
p=L[coada[I_c]]; p=L[coada[I_]];
while (p) while (p)
{ {
if (s[p->nd]==0) if (s[p->nd]==0)
{ {
sf_c++; sf_c++;
coada[sf_c]=p->nd; coada[sf_c]=p-
>nd;
s[->nd]=1; s[p->nd]=1;
} }
p=p->adr_urm; p=p->adr_urm;
} }
I_c++; I_c++;
bf_r(); }
} }
}

2)Cazul grafului memorat prin matricea de adiacenta varianta recursiva:

#include “grafuri.cpp”
int coada[50],s[50],A[50][50],I_c,sf_c,I,n;
void bf_r1()
{ int k;
if (I_c<=sf_c)
{ for (k=1;k<=n;k++)
if ( (A[coada[I_c]][k]==1) && (s[k]==0) )
{ sf_c++;

224
coada[sf_c]=k;
s[k]=1;
}
I_c++;
Bf_r1();
}
}
main()
[ Citire(“graf.txt”,A,n);
I_c=sf_c=1;
Coada[I_c]=s[1]=1;
Bf_r1();
For(I=1;I<=sf_c;I++)cout<<coada[I]<<” “;
}
2.Parcurgerea in adancime(DF-depth first)
-se face incapand de la un nod I
-dupa parcurgerea unui nod se parcurge primul dintre descendntii sai
neparcursii inca.
1)-memorare prin matricea de adiacenta:
#include “grafuri.cpp”
int s[50],A[50][50],n;
void df_r9int nod)
{
int k;
cout<<nod<<” “;
s[nod]=1;
for (k=1;k<=n;k++)
if ((A[nod][k]==1) && (s[k]==0))
df_r(k);
}
main()
{
Citire(‘Graf.txt”,A,n);
Df_r(1);
}
2)-memorare prin liste de adiacenta;
#include “grafuri.cpp”
int s[50],n;
Nod *L[50];
Void df_r1(int nod)
{

225
Nod* p;
Cout<<nod<<” “;p=L[nod];
S[nod]=1;
While (p)
{
if (s[p->nd]==0)
df_r1(p->nd);
p=p->adr_urm;
}
}
main()
{
Citire_l(grag.txt”,L,n);
Df_r1910;
}
3.Estimarea timpului necesar parcurgerii grafurilor
-deoarece m<n, parcurgerea grafurilor se face in timp polinomial, adica
cel mult O(n*n).
1.4Matricea drumurilor
m[I,j]= 1, daca exista drum de la I la j,
=0, daca nu exista drum de la ila j, sau I=j.
1)Obtinem o linie a matricei;
2)Apelul functiei DF, permite obtinerea matricei drumurilor.

#include “grafuri.cpp”
int s[50],n,I,j,M_dr[50][50];
Nod* L[50];
Void df(int baza,int nod)
{
Nod* p;
If (baza!=nod) M_dr[baza][nod]=1;
P=L[nod]; s[nod]=1;
While(p)
{
if (s[p->nd]==0) df(baza,p-.nd);
p=p->adr_urm;
}
}
main()
{
Citire_l(“Graf.txt”,L,n);

226
For (I=1;I<=n;I++)
{
for (j=1;j<=n;j++) s[j]=0;
df(I,I);
}
for 9I=1;I<=n;I++)
{
for (j=1;j<=n;j++) cout<<M_dr[I][j],’ ‘;
cout<<endl;
}
}
1.5Componente tare conexe

Definitie.G1=(X1g1) este o componenta tare conexa daca:


1)Oricare ar fi x,y din multimea X1, exista drum de la x la y si drum de la
y la x.
2)Nu exista un alt subgraf al lui G care indeplineste conditia 1)
*Fie un graf orientat , memorat prin matricea de adiacenta si un nod i. Se
cere sa se determine componenta tare conexa careia ii apartine i.
#include “grafuri.cpp”
int suc[50],A[50][50],n,I,j;
void df_r1(int nod)
{
int k;
suc[nod]=I;
for (k=1;k<=n;k++)
if ( (A[nod] [k]==1)&&(suc[k]==0) )
df_r1(k);
}
void df_r2(int nod)
{
int k;
pred[nod]=I;
for (k=1;k<=n;k++)
if ( (A[k][nod]==1)&&(pred[k]—0) )
df_r2(k);
}
main()
{
Citire(“Graf.txt”,A,n);
Cout<<”Introduceti nodul de pornire “;cin>>I;

227
Suc[I]=pred[I]=I;
Df_r1(I);df_r2(I);
For (j=1;j<=n;j++)
If ( (suc[j]==pred[j])&& (suc[j]==I) )
Cout<<j<<” “;
}

*Fie un graf orientat, memorat prin matricea de adiacenta.Sa se


descompuna graful in componente tari conexe.
#include “grafuri.cpp”
int suc[50],pred[50],A[50][50],n,nrc,I,j;
void df_r1(int nod)
{
int k;
suc[nod]=nrc;
for (k=1;k<=n;k++)
if ( (A[nod][k]==1)&&(suc[k]==0) )
df_r1(k);
}
void df_r2(int nod)
{
int k;
pred[nod]=nrc;
for (k=1;k<=n;k++)
if ( (A[k][nod]==1)&&(pred[k]==0)
df_r2(k);
}
main()
{
Citire(“Graf.txt”,A,n);
Nrc=1;
For (I=1;I,=n;I++)
If (suc[I]==0)
{
suc[I]=nrc;
df_r1(I);df_r2(I);
for (j=1;j<=n;j++)
if (suc[j]!=pred[j]) suc[j]=pred[j]=0;
nrc++;
}
for (I=1;I<=n;I++)

228
{
cout<<:”Componenta “<<I<<endl;
for (j=1;j<=n;j++)
if (suc[j]==I) cout<<j<<” “;
cout<<endl;
}
}
1.6Drumuri in grafuri

1.Matricea ponderilor
1)Urmatorul program va citi graful si va creea matricea ponderilor ,cu
functia urmatoare(primul caz):
void Citire_cost (char Nume_fis[20],float A[50][50],int& n)
{
int I,j;
float c;
fstream f(Nume_fis,ios::in);
f>>n;
for (I=1;I<=n;I++)
for (j=1;j<=n;j++)
if (I==j) A[I][j]=0;
else A[I][j]=Pinfinit;
while (f>>I>>j>>c) A[I][j]=c;
f.close();
}
2)Urmatorul program va citi grafului prin functia urmatoare(al doilea
caz):
void Citire_cost1(char Nume_fis[20], float A[50][50], int& n)
{
int I,j;
float c;
fstream f(Nume_fis,ios::in);
f>>n;
for (I=1;I<=n;I++)
for (j=1;j<=n;j++)
if (I==j) A[I][j]=0;
else A[I][j]=Minfinit;
while (f>>I>>j>>c) A[I][j]=c;
f.close();
}

229
*Algoritmul lui ROY-FLOYD-vom aplica strategia generala Divide
et Impera(pentru lungimea minima):
#include “grafuri.cpp”
float A[50][50];
int n;
void Drum(int I, int j)
{
int k=1,gasit=0;
while ( (k<=n) && !gasit)
{
if ( (I!=) && (j!=k) && (A[I][j]==A[I][k]+A[k][I]) )
{
Drum(I,k);Drum(k,j);
Gasit=1;
K++;
}
if (!gasit) cout<<j<<” “;
}
void Scriu_drum(int Nod_Initial, int Nod_Final)
{
if (A[Nod_Initial][Nod_Final]<Pinfinit)
{
cout<<”Drumul de la “<<Nod_Initial<<” la “<<Nod_Final<<” are
lungimea “<<A[Nod_Initial][Nod_Final]<<endl;
cout<<Nod_Initial<<” “;
Drum(Nod_Initial,Nod_Final);
}
else
cout<<”Nu exista drum de la “<<Nod_Initial<<” la
“<<Nod_Final;
}
void Lungime_Drumuri()
{
int I,j,k;
for (k=1;k<=n;k++)
for(I=1;I<=n;I++)
for (j=1;j<=n;j++)
if (A[I][j]>A[I][k]+A[k][j]
A[I][j]=A[I][k]+A[k][j];
}
main()

230
{
Citire_cost(“Graf.txt”,A,n);
Lungime_Drumuri();
Scriu_drum(4,2);
}

*Algoritmul ROY-FLOYD pentru a gasi drumuri de lungime maxima:


#include “grafuri.cpp”
float A[50][50]
int n;
void Drum(int I,int j)
{
int k=1,gasit=0;
while ( (k<=n) && !gasit)
{
if ( (I!=k) && (j!=k) && (A[I][j]==A[I][k]+A[k][j]) )
{
Drum(I,k0;Drum(k,j);
Gasit=1;}
K++;
}
if (!gasit) cout<<j<<” “;
}
void Scriu_drum(int Nod_Initial,Nod_Final)
{
if (A[Nod_Initial][Nod_Final].Minfinit)
{
cout<<”Drumul de la “<<Nod_Initial<<” la “<<Nod_Final<<” are
lungimea “<<A[Nod_Initial][Nod_Final]<<endl;
cout<<Nod_Initial<<” “;
Drum(Nod_Initial,Nod_Final);
}
else
cout<<”Nu exista drum de la “<<Nod_Initial<<” la “<<Nod_Final;
}
void Lungime_Drumuri()
{
int I,j,k;
for (k=1;k<=n;k++)
for (I=1;I<=n;I++)
for (j=1;j<=n;j++)

231
if (A[I][j]=A[I][k]+A[k][j];
}
main()
{
Citire_cost1(“Graf.txt”,A,n);
Lungime_Drumuri();
Scriu_drum(1,2);
}
ALGORITMUL DIJKSTRA
*Fiind dat un graf memorat prin matricea ponderilor, se cere sa se
determine pentru orice x,y apartin de X lungimea minima a drumului de
la nodul x la nodul y.Prin lungimea unui drum intelegem suma ponderilor
arcelor care-l alcatuiesc.
Include “grafuri.cpp”
Float A[50][50],D[50],min;
Int S[50],T[50],n,I,j,r,poz;
Void drum(int I)
{
if (T[I]) drum(T[I]);
cout<<I<<” “;
}
main()
{
Citire+cost(“Graf.txt”,A,n);
Cout<<”Introduceti nodul de pornire “<<endl;
Cout<<”r=”;cin>>r;S[r]=1;
For (I=1;I<=n;I++)
{
D[I]=A[r][I];
If (I!=r)
If (D[I]<Pinfinit) T[I]=r;}
For (I=1;I<=n;I++)
{
min=Pinfinit;
for(j=1;j<=n;j++)
if (S[j]==0)
if (D[j]<min)
{
min=D[j];
poz=j;
}

232
S[poz]=1;
For (j=1;j<=n;j++)
If (s[j]==0)
If (D[j]>D[poz]+A[poz][j])
{
D[j]=D[poz]+A[poz][j];
T[j]=poz;
}
for (I=1;I<=n;I++)
if (I!=r)
if(T[I])
{
cout<<”distanta de la “<<r<<” la “<<I<<” este “<<D[I]<<endl;
drum(I);
cout<<endl;
}
else
cout<<”nu exista drum de la “<<r<<” la “<<I<<endl;
}

2.Grafuri neorientate
2.1.Notiuni introductive

Definitie.Dca multimea g are proprietatea de simetrie, graful se numeste


neorientat.
Definitie.Un graf partial al unui graf neorientat dat G=(X,g) este un graf
G1=(X,g1 unde g1 inclus sau=cu g.
Definitie.Unsubgraf al unui graf neorientat G=(X,g) este un graf
H=(Y<g1), unde Yinclus in X, iar muchiile din g1 sunt toate muchiile din
g care au ambele extremitati in multimea Y.
Definitie.Lant.Pentru graful neorientat , un lant este o succesiune de
varfuri cu proprieatea ca oricare doua varfuri sunt adiacente.
*Citirea grafurilor neorientate din fisiere text:
1)Cazul 1
void CitireN(char Nume_fis[20]int A[50][50],int& n)
{ int I,j;
fstream f(Nume_fis,ios::in);
f>>n;
while (f>>I>>j)A[I][j]=A[j][I]=1;
f.close()
}

233
2)Cazul 2
void Citire_l_N(char Nume_fis[20],Nod* L[50] int& n)
{
Nod* p;
Int I,j;
Fstreamf(Nume_fis,ios::in);
f>>n;
while (f>>I>>j)
{
p=new Nod; p->adr_urm=L[I];p->nd=j;L[I]=p;
p=new Nod; p->adr_urm=L[j];p->nd=I;L[j]=p;
}.close();
}
3)Cazul 3
void Citire_cost_N(char Nume_fis[20],float A[50][50],int &n)
{
int I,j;
float c;
fstream f(Nume_fis,ios::in);
f>>n;
for (I=1;I<=n;I++)A[I][I]=0;
for (I=1;I<=n-1;I++)
for (j=I+1;j<=n;j++) A[I][j]=A[j][I]=Pinfinit;
while(f>>I>>j>>c)A[I][j]=A[j][I]=c;
f.close();
}
4)Cazul 4
Fata de cazul 3 ar deosebirile:
Citire_cost_N=Citire_costN1;
Pinfinit=Minfinit.
*Determinarea componentelor conexe ale unui graf neorientat:
#include “grafuri.cpp”
int S[50],A[50][50],n,I,k;
void df_r(int nod)
{
int k;
cout<<nod<<” “; S[nod]=1;
for (k=1;k<=n;k++)
if ( (A[nod][k]==1)&& (s[k]==0) ) df_r(k);
}
main()

234
{
CitireN(“Graf.txt”,A,n);
K=1;
For (I=1;I<=n;I++)
If (s[I]==0)
{
cout<<”componenta “<<k<<endl;
df_r(I);
cout<<endl;
k++;
}
}
2.2.Grafuri hamiltoniene

Definitie.Se numeste ciclu hamiltonian un ciclu elementar care trece prin


toate varfurile grafului.
*Se da un graf neorientat, prin matricea da adiacenta.Se cere sa se
determine, daca exista, un ciclu hamiltonian.
#include “grafuri.cpp”
int st[50],a[50][50],n,k;
void Init()
{ st[k]=1; }
int AmSuccesor ()
{ if (st[k]<n)
{ st[k]++
return 1;
}
else return 0;
}
int E_Valid()
{ if (!a[st[k-1]][st[k]]) return 0;
else
for (int 1=1;I<=k-1;I++)
if (st[I]==st[k]) return 0;
if (k==n && !a[1][st[k]]) return 0;
return1;
}
int Solutie()
{ return k==n;}
void Tipar()
{ for (int I=1;I<=n;I++) cout<<”Nodul “<<st[I]<<endl;

235
k=0;
}
void back()
{ int AS;
k=2; Init();
while (k>1)
{ do {} while ((AS=Am_Succesor()) && !E_Valid());
if (AS)
if (Solutie()) Tipar();
else {k++;Init();}
else k--;
}
}
main () { st[1]=1;k=2;st[k]=1;
CitireN(“graf.txt”,a,n);
Back();
}
2.3.Grafuri euleriene

Definitie.Un graf care contine un ciclu eulerian se numeste eulerian.


*Prograul urmator vverifica daca un graf este onex si are gradele tuturor
varfurilor pare.Daca aceste conditii sunt indeplinite, programul gaseste un
ciclu eulerian.
#include “grafuri.cpp”
int A[50][50],S[50],n;
Nod*Alista, *Indice;
Int A[50][50],S[50],n;
Nod Alista,Indice;
Void ciclu(Nod* v)
{int Nodul;
Nod Anod_baza,Anod_gasit,*Anod_urm;
Anod_urm=v->adr_urm;
Anod_baza=v;
Do
{ Nodul=1;
while (A[Anod_baza->nd][Nodul]==0) Nodul++;
A[ANod_baza->nd][Nodul]=0;A[Nodul][ANod_baza->nd]=0;
ANod_gasit=new Nod; ANod_gasit->nd=Nodul;
ANod_gasit->adr_urm=0;
ANod_baza->adr_urm=ANod_gasit;ANod_baza=ANod_gasit;
} while ((ANod_gasit->nd!=v->nd);

236
ANod_baza->adr_urm=ANod_urm;
Int adauga()
{
int I,gasit=0;
Indice=Alista;
While (Indice && !gasit)
{
for (I=1;I<=n;I++)
if (A[Indice->nd][I]==1) gasit=1;
if (!gasit) Indice=Indice->adr_urm;
}
if (Indice)
{
ciclu (Indice);
return 1;
}
else return 0;
}
int grade_pare()
{
int I=1,j,s,gasit=0;
while ( (I<=n) && ! gasit)
{
s=0;
for (j=1;j<=n;j++) s+=A[I][j];
if (s%2) gasit=1;
I++;
}
return !gasit;
}
void df(int nod)
{
int k;
S[nod]=1;
For (k=1;k<=n;k++)
If ( (A[nod][k]==1) && (S[k]==0)) df(k);
}
int conex()
{
int gasit=0,I;
df(1);

237
for (I=1;I<=n;I++)
if (S[I]==0) gasit=1;
return !gasit;
}
main()
{ CitireN(“Graf.txt”,A,n);
if (conex())
if (grade_pare())
{
Alista=new Nod;
Alista->nd=1;Alista->adr_urm=0;
While (adauga());
Indice=Alista;
While (Indice)
{ cout<<Indice->nd<<” “;
Indice=Indice->adr_urm;
}
}
else cout <<”Graful nu indeplineste conditiile de paritate”;
else cout<<”Graful nu este conex”;
}
3.Retele de transport
3.1.Ce este o retea de transport

Definitie.Se numeste retea de transport ansamblul R=(G,st,fin,c), unde:


-G este graful orientat;
-st apartine multimiiX si d+(st)>0,d-(st)=0;
-fin apartine multimiiX, fin diferit de st, d-(fin)>0, d+(fin)=0;
-c:X*X->N.Dca [I,j]nu apartine de gama,atunci c[I,j]=0.
*Pentru citirea retelei vom utiliza functia de mai jos, inclusa in modulul
grafuri:
void Citire_Cap(char Nume_fis[20],int A[50][50][2],int& n,int&
st,int& fin)
{
int I,j cost;
fstream f(Nume_fis,ios::in);
f>>n>>st>>fin;
while (f>>I>>j>>cost)A[I][j][0]=cost;
f.close();
}
3.2.Flux intr-o retea de transport

238
Defnitie.fluxul unei retele R este o functie definit pe X*X cu valori in N
care indeplineste anumite conditii.
3.3.Determinarea fluxului de valoare maxima

Acest lucru va va fi prezantat in continuare prin folosirea algoritmului lui


FORD FULKERSON optimizat.Iata programul:
#include “grafuri.cpp”
#include <math.h>
int A[50][50][2],n,st,fin,gasit,coada[50],s[50],f[50],I_c,sf_c,I,j,min;
void refac(int Nod)
{
if (Nod!=st)
if (s[Nod}>0)
{
if (min>A[s[Nod]][Nod][0]-A[s[Nod]][Nod][1])
min=A[s[Nod]][Nod][0]-A[s][Nod][Nod][1];
refac(s[Nod]);
A[s[Nod]][Nod][1]+=min;
}
else
{
if (min>A[Nod][s[abs(Nod)]][1])
min=A[Nod][abs(s[Nod])][1];
refac(abs(s[Nod}));
A[Nod][abs(s[Nod)][1]-=min;
}
}
void drum_in_crestere()
{
gasit=0;
I_c=sf_c=1;coada[I_c]=st;
While( (I_c=sf_c) && (coada[sf_c]!=fin) )
{
I=1;
While ( (I<=n) && !gasit)
{
if ((A[coada[I_c]][I][0]-A[coada[I_c]][I][1]>0) && (s[I]==0))
{
s[I]=coada[I_c];coada[++sf_c]=I;
}

239
else
if ((A[I][coada[I_c]][1]>0) && (s[I]==0) && (I!=st) )
{
s[I]=-coada[I_c];coada[++sf_c]=I;
}
I++;
}
I_c++;
}
if (coada[sf_c]==fin gasit=1;
}
void caut()
{
do
{
for (I=1;I<=n;I++)s[I]=0;
drum_in_crestere();
if (s[fin])
{
min=32000;
refac(fin);
}
} while (gasit);
}
main()
{
Citire_Cap(“Graf.txt”,A,n,st,fin);
Caut();
For (I=1;I<=n;I++)
{
for (j=1;j<=n;j++) cout <<A[I][j][1]<<” “;
cout<<endl;
}
}

240
241

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