100% au considerat acest document util (1 vot)
13 vizualizări232 pagini

Informatica Titularizare

Documentul prezintă programa pentru disciplina Informatică și Tehnologia Informației, evidențiind conceptele fundamentale ale algoritmilor, datelor și operațiilor asupra acestora. Algoritmii sunt descriși prin etapele de analiză, elaborare, implementare și testare, iar datele sunt clasificate în funcție de diverse criterii. Reprezentarea algoritmilor se face prin pseudocod și scheme logice, fiecare având specificații și convenții proprii.

Încărcat de

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

Informatica Titularizare

Documentul prezintă programa pentru disciplina Informatică și Tehnologia Informației, evidențiind conceptele fundamentale ale algoritmilor, datelor și operațiilor asupra acestora. Algoritmii sunt descriși prin etapele de analiză, elaborare, implementare și testare, iar datele sunt clasificate în funcție de diverse criterii. Reprezentarea algoritmilor se face prin pseudocod și scheme logice, fiecare având specificații și convenții proprii.

Încărcat de

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

ÎNVĂŢĂMÂNTUL PREUNIVERSITAR

PROGRAMA
PENTRU

DISCIPLINA
INFORMATICĂ ȘI TEHNOLOGIA INFORMAŢIEI

-București-
2020
Tematica știinţifică pentru disciplina Informatică și Tehnologia
Informaţiei

1. Algoritmi
Noţiunea de algoritm și caracteristici
Pentru orice rezolvare a unei probleme cu ajutorul calculatorului, trebuie parcurse
următoarele etape:
analiza problemei;
elaborarea unui algoritm de rezolvare a problemei;
implementarea algoritmului într-un limbaj de programare.
testarea programului și corectarea erorilor.

Un algoritm este o secventa finita de operatii, ordonata si complet definita, care, pornind
de la un set de date (intrari) produce rezultate (iesiri), in urma prelucrarii automate, prin
intermediul unui program.
Un algoritm trebuie sa fie:
- bine definit ( claritate - operatiile sale sa fie specificate riguros)
- exact ( pentru a-l putea prelucra ulterior o masina automata)
- finit ( sa se termine dupa un numar finit de pasi)
- universal ( sa permita rezolvarea unei clase de probleme)
- determinist ( executat la momente diferite de timp, cu aceleasi date de intrare, produce
aceleasi rezultate).
- eficient ( etapele care compun algoritmul trebuie alese astfel încât soluţia problemei să
fie obţinută după un număr minim de pași) .

Formele conventionale de reprezentare a algoritmilor sunt schemele logice si limbajele


pseudocod. Ele pot evidentia clar fluxul operatiilor reprezentate de algoritm.
Schemele logice folosesc pentru aceasta sageti si forme geometrice ce simbolizeaza
tipurile de operatii ce pot fi efectuate de algoritm.
Limbajele pseudocod folosesc cuvinte cheie pentru identificarea operatiilor de
executat.

Obiectele cu care lucreaza algoritmii


(date – constante sau variabile, expresii, operatii).

Data este un model de reprezentare a informaţiei, accesibil calculatorului, cu care se


poate opera pentru a obţine noi informaţii.
Data se defineste prin urmatoarele trei elemente:
- identificatorul - un nume asociat datei, format din unul sau mai multe caractere;
- valoarea - poate fi numar intreg, real (date numerice), valoarea logica sau sir de
caractere (exemple: 10, -3, 3, 25, 3e-3, true, false, ’abc’), reprezentind conţinutul zonei de
memorie în care este stocată data;
- atribute – reprezintă proprietăţi ale datelor care determină modul în care sistemul va
trata datele. Cel mai important atribut este tipul datei, care precizeaza multimea valorilor si
multimea de operatii ce pot fi efectuate cu respectiva data, constanta sau variabila.

Clasificarea datelor
1. în funcţie de momentul în care se produc:
- date de intrare
- date de ieșire
- date de manevră
2. în funcţie de valoare:
- variabile
- constante
3. în funcţie de modul de compunere:
- date elementare
- structuri de date
4. în funcţie de tip:
- date numerice
- date logice
- date șiruri de caractere

Operaţii asupra datelor


Operatorii sunt caractere speciale (*, /, <, = etc.) sau cuvinte cheie (mod, and etc.)
prin intermediul cărora se reprezintă operaţiile care se efectuează în cadrul unui algoritm.

Tipuri de operatori:
-operatori aritmetici;
-operatori relaţionali;
-operatori logici.

a. operatori aritmetici (matematici)


- definesc o operaţie aritmetică si pot fi:
 unari (se aplică asupra unui singur operand) sau
 binari (acţionează asupra a doi operanzi)

Operatorii aritmetici sunt de două tipuri:


- multiplicativi:
* (înmulţire)
/ (împărţire)
% (restul împărţirii întregi)
- aditivi:
+ (adunare)
- (scădere)

b. operatori relaţionali (de comparaţie)


- sunt operatori binari
- se aplică asupra operanzilor de tip numeric sau de tip șir de caractere
- furnizează un rezultat de tip logic.
Operatorii relaţionali sunt:
= (egal)
≠ (diferit)
< (mai mic)
> (mai mare)
≤ (mai mic sau egal)
≥ (mai mare sau egal)

c. operatori logici
-definesc o operaţie logică
-se pot aplica numai asupra operanzilor logici
-produc un rezultat de tip logic

Operatorii logici sunt:


not - ! (negaţie logică)
and - && (ȘI logic)
or - || (SAU logic)
Notaţii:
1 – adevărat (true)
0 – fals (false)
x y !x x || y x && y
0 0 1 0 0
0 1 1 1 0
1 0 0 1 0
1 1 0 1 1
Expresiile sunt constituite din constante, variabile si operatori dupa reguli bine
determinate, preluate de matematica, avind aceleasi patru tipuri ca si datele.
In expresiile intregi pot interveni constante si variabile intregi, operatorii +,-,*,
DIV(citul), MOD(restul), etc, rezultatul fiind intreg.
In expresiile reale pot interveni constante si variabile intregi si reale, operatorii +, -,
*, /, iar rezultatul este real.
Expresiile logice au rezultat true sau false, operatorii prezenti pot fi relationali (<, >, =,
<>, <=, >=) sau logici (OR, XOR, NOT, AND), iar operanzii pot avea oricare din tipurile
enumerate.
In expresiile de tip sir de caractere operanzii pot fi constante sau variabile de tip sir de
caractere. Operatorul (+) este pentru concatenare, rezultatul fiind tip sir de caractere.
Operatiile executate de un algoritm sunt:
Intrare - se preiau datele de intrare
Iesire - se furnizeaza rezultate
Decizie - se testeaza valoarea de adevar a unei conditii logice si in functie de rezultatul
testului, se alege o varianta de continuare a algoritmului.
Atribuire - permite alocarea unei valori unei variabile; printr-o atribuire se poate
transfera variabilei valoarea unei constante, a unei expresii sau a unei alte variabile.
Exemplu
Se citește de la tastatură un număr natural n. Să se calculeze și să se afișeze pe ecran
factorialul numărului natural n.
n!=1∙2 ∙3 ∙... ∙n
Date de intrare: n
Date de ieșire: p
Date de manevră: i
unde:
n – numărul natural pentru care se calculează factorialul
i – variabilă care va memora pe rând primele n numere naturale, iє[1,n]
p – variabilă care va memora rezultatele parţiale și rezultatul final (produsul primelor n
numere naturale nenule)
Trebuie să rezolvăm următoarea problemă:
- citirea lui n
- iniţializarea variabilei p cu valoarea 1 (elementul neutru pentru produs)
- înmulţirea pe rând a numerelor 1, 2, 3, ..., n la variabila p (pentru aceasta considerăm
variabila i, care va lua pe rând valorile 1, 2, 3, ..., n, iniţial valoarea lui i este 1)

Algoritmul
Pasul 1. citește valoarea lui n;
Pasul 2. atribuie lui p valoarea 1;
Pasul 3. atribuie lui i valoarea 1;
Pasul 4. atribuie lui p valoarea p*i;
Pasul 5. atribuie lui i valoarea i+1;
Pasul 6. dacă i≤n atunci treci la Pasul 4, altfel treci la Pasul 7;
Pasul 7. scrie valoarea lui p.

Reprezentarea algoritmilor (pseudocod, scheme logice)

Limbajul natural nu permite o descriere suficient de exactă a algoritmilor. Din acest


motiv pentru reprezentarea algoritmilor se folosesc diferite forme de descriere caracteristice.
Două din cele mai folosite forme de descriere a algoritmilor sunt:
 limbajul pseudocod;
 scheme logice.
A. Reprezentarea algoritmilor în limbaj pseudocod

Limbajul pseudocod folosește cuvinte cheie, adică niște cuvinte cu înţeles prestabilit,
care indică operaţia executată.
Ele se reprezinta sub forma de text si utilizeaza anumite conventii sau codificari pentru
descrierea obiectelor algoritmului.

Conventii de descriere a obiectelor algoritmului.


Datele (constante si variabile) se vor descrie astfel:
-constantele numerice – se utilizeaza ca-n matematica, cu deosebirea ca virgula va fi
notata cu punct;
-constantele tip caracter si sir de caractere se vor nota intre apostrofuri;
-constantele logice utilizate vor fi: fals si adevarat;
-variabilele – se vor specifica, numele si tipul de reprezentare (natural, intreg, real,
caracter etc), astfel: nume: tip;
Operatiile numerice, logice, intre caractere - conform operatiilor utilizate in
programare;
Comenzile (instructiunile) vor avea cuvinte rezervate: citeste, scrie, (pentru atribuire),
daca…atunci…altfel…cat timp…reia, executa…pana cand…stop.
Exemplu
Scrieţi un algoritm care calculează și afișează pe ecran suma a două numere întregi a și
b citite de la tastatură.
Date de intrare: a, b
Date de ieșire: S
a). algoritmul
Pasul 1. solicită valori pentru a și b;
Pasul 2. calculează S=a+b;
Pasul 3. furnizează rezultatul pentru S.
b). pseudocodul
citește a, b (numere întregi)
S←a+b
scrie S
În acest pseudocod s-au folosit două cuvinte cheie: citește și scrie.

B. Reprezentarea algoritmilor prin scheme logice

Este una din formele cele mai utilizate de reprezentare al algoritmilor. Intr-o schema
logica apar o serie de operatii exprimate sub forma grafica, numite blocuri, etichetate cu
informatii specifice si legate intre ele prin arce orientate (sageti), intrucat indica sensul
parcurgerii.
Presupunem ca programul actioneaza asupra unor date aflate pe o asa numita banda de
intrare, iar rezultatele sunt inregistrate pe o banda de iesire.
Accesul la date se face secvential (una dupa alta).

O schema logica este formata din urmatoarele blocuri:

1. Blocul de inceput (instructiunea „START”) - este etichetat cu cuvantul ”START”


El apare o singura data la inceputul schemei logice, din el pleaca un singur
start
arc si in el nu pot sosi alte arce.

2. Blocul de sfarsit (instructiunea „STOP”).

El apare o singura data la sfarsitul schemei logice, in el doar sosesc arce si


stop din el nu pleaca alte arce.

3. Blocul de citire(instructiunea „CITIRE”).

Este un arc etichetat cu cuvantul „cin>>” si o lista de variabile, si are ca efect


CITIRE prelucrarea de pe banda de intrare a unui nr. de date egal cu nr. de variabile din
lista de variabile si introducerea valorilor acestora in variabile
enumerate in lista.

4. Blocul de scriere ( instructiunea „SCRIE”).

Este un arc etichetat cu cuvantul „cout<<” si o lista de variabile, si are ca


efect depunerea de pe banda de iesire a valorilor variabile din lista atasata.
SCRIE
5. Blocul de atribuire

v e v=e

Este etichetata cu informatia de evaluare a unei expresii „e” si inscrierea (atribuirea)


rezultatului in locatia de memorie corespunde unei variabile ”v”.
V - variabila
„=”- operator de atribuire

6. Bloc de subprograme, de proceduri ( prelucrare compusa)


Prelucrare

compusa

DA NU

con
7. Bloc de decizie: d.

8. Blocul de ramificare (selectie) este o multime de n arce care pleaca din acelasi varf si
care sunt etichetate cu predicatele P1, …, Pn; sunt expresii cu rezultat logic: adevarat(1) si
fals(0), ce satisfac urmatoarele conditii:
SAU logic P v P 2 v …v P = 1 (cel putin unul adevarat)
SI logic P P ^ …^ P = 0 (cel mult unul sa fie adevarat)
Relatiile exprima ca una si numai una din conditii poate fi satisfacuta (adevarata).

P P … P
Efectul instructiunii consta in evaluarea predicatelor si continuarea executiei pe drumul
indicat de arcul etichetat de predicatul adevarat.

Def. Se numeste schema logica, un graf orientat, in care:


1. exista un unic bloc START si cel putin o instructiune STOP;
2. ( ) un varf (diferit de extremitatea finala a lui STOP) este extremitatea initala a unei
unice instructiuni;
3. ( ) un arc este etichetat cu una din instructiunile:
-START sau STOP
-CITIRE sau SCRIERE
-ATRIBUIRE
-Un predicat.
In acest caz extremitatea initiala a arcului trebuie sa fie extremitatea initiala a unei
instructiuni de ramificare.
4. Pentru orice arc exista cel putin un drum ce incepe cu START, se termina cu STOP si
contine arcul considerat.
Exemplu
Scrieţi un algoritm care calculează și afișează pe ecran suma a două numere naturale a și
b, citite de la tastatură.
Date de intrare: a, b
Date de ieșire: S
a). algoritmul
Pasul 1. solicită valori pentru a și b;
Pasul 2. calculează S=a+b;
Pasul 3. furnizează rezultatul pentru S.
b). pseudocodul
citește a, b (numere naturale)
S←a+b
scrie S
c). schema logică

Principiile programarii structurate

1. Structura secventiala
Schema logica Pseudocod
Actiune 1
Act.1 Actiune 2
Act.2 Actiune 3

Act.3
Actiunile structuri secventiale sunt executate succesiv in ordinea in care apar.
Implementare in C++:
Instructiuni secventiale:
Instructiunea mixta, atribuirea, instructiunea compusa (bloc)
{ i1;
i2;
i3;
.
DA NU
cond
.
.
in; }
Actiune1 Actiune2
2. Structuri decizionale
a) Alternativa
Schema logica Pseudocod

DACA cond.
ATUNCI
Act 1
ALTFEL
Act 2

SFARSIT.

Aceasta structura asigura executarea uneia dintre cele doua actiuni prevazute in functie
de o conditie data.
Implementare C++: if (daca), else (altfel).
If(expresie) instructiune1;
Else instructiune2;
SAU pseudoalternativa:
If(expresie) instructiune;

b) Selectie
Schema logica Pseudocod

ALEGE i DINTRE
i CAZ 1: act 1
CAZ 2: act 2
………………
1 2 3 n CAZ n: act n
SFARSIT.

act.1 act.2 act.3 act.n
3
3

Structura de selectie asigura selectarea a mai mult de doua posibilitati de continuare a


algoritmului.
Implementare C++:
switch (expresie)
{
case exp1: secventa instructiune1; break; switch (comutator, selectie)
case exp2: secventa instructiune2; break; case (caz); break (a intrerupe)
…………………………………………. default (implicit)
case exp n: secventa instructiunen; break;
[ default: secventa instructiune n+1];
}
3. Stucturi repetitive
a) Ciclica cu contor
Schema logica Pseudocod
Pentru i=m1 la m2 [pas m3]
i=m1 actiune
Repeta
actiune

Pasul ”m3” este optional fiind pus intre paranteze


patrate [m3]. Daca „m3” nu este specificat, este
actiune considerat egal cu „1”.

i=i+m3

DA
i<=m2

NU
Implementare C++: for (pentru)

for( expresie initializare; expresie test; expresie incrementare)


instructiune;

b) Ciclica cu test initial (cu numar necunoscut de repetitii)


Schema logica Pseudocod

Cat timp
conditie
actiune
Repeta
NU

Conditia
de
intrare
DA

actiune

Poate aparea pericolul nerealizarii acestei conditii (ciclare infinita). Actiunea va fi


actiune
repetata cat timp conditia de continuare va fi adevarata.
Implementare C++: while (cat timp);

While (expresie)
NU instructiune;
c) Ciclica cu testConditia
final (cu numar cunoscut de repetitii)
Schema logica de Pseudocod
iesire
DA
Repeta
actiune
Pana cand
conditie

Implementare C++: do (executa)


while (cat timp).
do
instructiune
while(expresie);

Algoritmi elementari

1. Prelucrarea numerelor
a). Prelucrarea cifrelor unui număr
b). Probleme de divizibilitate
Determinarea divizorilor unui număr
Algoritmul lui Euclid
Numere prime
2. Prelucrarea unor secvenţe de valori
a). Calculul unor expresii simple

SUMA si PRODUSUL cifrelor


#include <iostream>
using namespace std;
int s, p, m;
int suma(int x, int y, int z)
{s=x+y+z;
cout<<"Suma nr:"<<x<<","<<y<<","<<z<<" este: "<<s;
return 0;}
int produs(int x, int y, int z)
{p=x*y*z;
cout<<"Produsul nr:"<<x<<","<<y<<","<<z<<" este: "<<p;
return 0;}
int media(int x, int y, int z)
{m=(x+y+z)/3;
cout<<"Media aritm a nr:"<<x<<","<<y<<","<<z<<"este:"<<m;
return 0;}
int main()
{int a,b,c;
cout<<"Dati a:\n"<<"= ";
cin>>a;
cout<<"Dati b:\n"<<"= ";
cin>>b;
cout<<"Dati c:\n"<<"= ";
cin>>c;
suma(a,b,c);
produs(a,b,c);
media(a,b,c);
return 0;}

b). Determinare minim/ maxim

MAXIM
#include <iostream>
#include <algorithm>
using namespace std;
int main()
{cout<<"max(1,2)="<<max(1,2)<<endl;
cout<<"max(2,1)="<<max(2,1)<<endl;
cout<<"max('a','z')="<<max('a','z')<<endl;
cout<<"max(3.14, 2.72)="<<max(3.14, 2.72)<<endl;
return 0;}

MINIM
#include <iostream>
#include <algorithm>
using namespace std;
int main()
{cout<<"min(1,2)="<<min(1,2)<<endl;
cout<<"min(2,1)="<<min(2,1)<<endl;
cout<<"min('a','z')="<<min('a','z')<<endl;
cout<<"min(3.14, 2.72)="<<min(3.14, 2.72)<<endl;
return 0;}
c). Verificarea unor proprietăţi

Determinarea divizorilor
Pentru primul program, cu alte cuvinte trebuie sa impartim numarul citit la toate numerele mai mici
decat el, sa verificam daca x se imparte exact la acel numar, si apoi sa afisam toate numerele la care x se
divide.
//Divizori
#include <iostream>
#include <math.h>
using namespace std;
int main()
{int x, d; //x=nr citit; d=numerele la care se va imparti x;
cout<<”Divizorii unui numar”<<endl;
cout<<”Introduceti numarul: “;
cin>>x;
cout<<”Divizorii numarului x sunt: “;
for (d=1; d<=x; d++) //pentru d, initial 1, pana la numarul x;
{if (x%d==0) //verif daca x se imparte exact la acel pres divizor
cout<<d<<”; “;
return 0;}}//daca da, il afisam;

Determinarea daca un nr este prim sau nu


Pentru a verifica daca un numar este prim, se procedeaza asemanator ca la afisare, doar ca aici nu
afisam divizorii, ci testam daca numarul are exact 2 divizori, si anume 1 si el insusi.
//Numar prim
#include <iostream>
#include <math.h>
using namespace std;
int main ()
{int x, d, cnt, aux; /*x=nr citit; d=numerele la care se imparte x;
cnt=variabila care va numara cati divizori are x; aux=auxiliara care il
va memora pe x;*/
cout<<”Numar prim”<<endl;
cout<<”Introduceti numarul: “;
cin>>x;
cnt=0;
aux=x;
for (d=1; d<=x; d++)
{if (x%d==0) //daca x se imparte exact la d, atunci cnt=cnt+1;
cnt=cnt+1;}
if (cnt==2) //apoi verificam daca x are exact 2 divizori
cout<<”Numarul “<<aux<<” este prim.”; //si afis daca este prim
else
cout<<”Numarul “<<aux<<” nu este prim.”;}//sau nu…

d). Calculul unor expresii în care intervin valori din secvenţă


ALGORITMI PT. CONVERSII INTRE SISTEME DE NUMERATIE

Algoritm numar din baza 10 in baza "b"


#include<iostream>
using namespace std;
int main()
{int n, b, nr=0, r, p=1;// n-numărul de transformat, b-baza
cin>>n;// nr va conţine rezultatul conversiei
cin>>b;// p=1, puterile lui 10 iniţial 100=1
while(n!=0)
{r=n%b;// calculez restul împărţirii numărului la bază
nr=nr+r*p;// formez valoarea noului nr
(ex:24510=2*102+4*10+5=111101012 =27*1+26*1+25*1+24*1+23*0+22*1+21*0+20*1=24510)
p=p*10;// cresc puterea lui 10
n=n/b;}// calculez câtul împărţirii numărului la bază
cout<<nr;}// valoarea numărului în baza b

Pentru a transforma un nr. din baza 10 in baza "b" se imparte nr. la b si se retine restul. Se
imparte catul obtinut la b si se retine noul rest. Procedeul continua pana obtinem cat 0. Resturile luate
in ordine inversa formeaza nr. in baza b.

Algoritm numar din baza "b" in baza 10


#include<iostream>
using namespace std;
int main()
{int n, b, nr=0, r, p=1;// n – nr de trans, b baza în care este scris n cin
>> n;// nr va conţine rezultatul conversiei
cin >> b;
while(n!=0)
{r=n%10;// extrag cate o cifra din numarul dat
nr=nr+r*p;// form nr adunând cifr extr din n cu puteri ale bazei
p=p*b;// cresc puterea bazei
n=n/10;}// elimin ultima cifra din n
cout << nr;}// valoarea numărului în baza 10

Pentru a transforma un nr. din baza "b" in baza 10 se inmulteste fiecare cifra a lui n cu baza la
o putere egala cu nr. de cifre ramase si se insumeaza rezultatele incepind cu ultima cifra binara.
Exemplu: Descompunerea unui număr - (123,45)10=1*102+2*101+3*100+4*10-1+5*10-2=(123,45)10
Din baza 2 în 10 - (110011)2=1*20+1*21+0*22+0*23+1*24+1*25=1+2+16+32=(51)10

e). Generarea șirurilor recurente


Șirul lui Fibonacci
Algoritmul pentru generarea termenilor sirului lui Fibonacci este un exemplu clasic de algoritm
pentru definirea recurenta a termenilor unui sir. Sirurile recurente sunt acele siruri in care un anumit termen
se obtine utilizand valorile termenilor anteriori, un exemplu clasic fiind sirul lui Fibonacci.

 Notam cu a1, a2,…an termenii sirului. Definirea lor se realizeaza astfel:


a1=1;
a2=1;
a3=a1+a2;
…….
an=an-2+an-1
 Astfel primii 5 termeni ai sirului vor fi: 1, 1, 2, 3, 5.

Pasii algoritmului

 Pentru generarea primilor n termeni ai sirului se vor genera repetat termenii de rang i ai sirului.
 Se vor folosi trei variabile de memorie:
- a1 pentru ai-2
- a2 pentru ai-1
- a3 pentru termenul curent ai.
 Dupa fiecare generare a unui termen se vor actualiza valorile din variabilele a1 si a2.

Pas 1: se initializeaza termenii a1 si a2: a1←1,a2←1;


Pas 2: se afiseaza a1 si a2;
Pas 3: se initializeaza contorul i care numara termenii generati: i←3;
Pas 4: se calculeaza termenul a3 prin operatia: a3←a1+a2;
Pas 5: se afiseaza termenul a3;
Pas 6: se actualizeaza valorile pentru a1 si a2 : a1←a2, a2←a3;
Pas 7: se incrementeaza contorul i;
Pas 8: daca i<=n atunci se revine la pasul 4; altfel se termina algoritmul.

Pseudocod
intreg a1, a2, a3, n, i;
inceput
citeste n;
a1←1; a2←1;
scrie a1, a2;
pentru i←3, n executa
ai←ai-2+ai-1; scrie ai;
ai-2←ai-1; ai-1←ai;
sfarsit_pentru;
sfarsit.

Ex: Daca n=6 atunci primii n termeni sunt:


1, 1, 2, 3, 5, 8

3. Metode de ordonare
a). Bulelor
b). Inserţiei
SORTAREA PRIN INSERARE DIRECTĂ

Se consideră indicele j (1 < j ≤ n) ce corespunde elementului care urmează să fie


inserat. Elementele v1, v2, ..., vj-1 sunt elementele care sunt deja aranjate astfel încât v1 ≤ v2
≤...≤ vj-1. Indicele i se deplasează de la dreapta la stânga în interiorul vectorului. Plecând de
la poziţia j-1, elementele sunt succesiv, deplasate o poziţie spre dreapta până când este găsită
poziţia corectă pentru vj, moment în care acesta este inserat. Elementele de intrare sunt
sortate pe loc, în cadrul aceluiași vector v.
Deoarece vj va fi așezat la locul său, această metodă de sortare a fost denumită metoda
de cernere (sifting) sau scufundare (sinking).

PREZENTAREA ALGORITMULUI
Algoritmul de sortarea prin inserare directă va sorta elementele șirului v1, v2, …, vn, iar
după sortare, elementele vectorului vor fi în ordinea v1 ≤ v2 ≤ ... ≤ vn.
Pseudocodul pentru sortarea prin inserţie directă este reprezentat prin subprogramul numit
inserare_directa care are doi parametri:
- n prin care primește un număr natural ce reprezintă lungimea vectorului
- v prin care primește un vector care memorează un șir de n numere care trebuie sortat

Algoritmul în pseudocod este următorul:


Subalgoritm inserare_directa (n,v)
pentru j =2, n executa
element = v[j]
i = j -1
cat timp((i>=1) si (v[i]>element)) executa
v[i+1] = v[i]
i = i-1
sfârșit cat timp
v[i+1] = element
sfârșit pentru
sfârșit subalgoritm

Algoritmul în C++ este următorul:


void inserare_directa(int n, int v[20])
{int j, element, i;
for(j=2;j<=n;j++)
{i=j-1;
element=v[j];
while(i>=1 && v[i]>element)
{v[i+1]=v[i];
i=i-1;}
v[i+1]=element;}}
c). Selecţiei

d). Numărării
4. Interclasare (merge sort)

Fiind date două șiruri de numere, ordonate crescător (sau descrescător), se cere să se
obţină un șir care să fie de asemenea ordonat crescător (respectiv descrescător) și care să fie
formată din elementele șirurilor date.
Ideea:

Șirul rezultat se poate obţine direct (fără o sortare a șirului final) prin parcurgerea
secvenţială a celor două șiruri, simultan cu generarea șirului cerut. Prin compararea a două
elemente din listele de intrare se va decide care element va fi adăugat în lista de ieșire.

Exemplu:
șir 1: 1, 3, 4, 5
șir 1:-1, 2, 3
Rezultat: -1, 1, 2, 3, 3, 4, 5

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).
Algoritm de interclasare:
Sa presupunem ca cele doua siruri de numere sortate se memoreaza in doua variabile de
tip vector A si B.
Numerele sortate vor fi memorate intr-o alta variabila de tip vector (numita C). La
fiecare pas se alege un numar care va fi memorat in C (cel care urmeaza a fi adaugat in
ordinea ceruta).
Ex:
#include<iostream>
using namespace std;
int 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;
return 0;}

5. Metode de căutare

a). Secvenţială

Să presupunem că dorim să determinăm dacă un element aparţine sau nu unui vector de


elemente. Dacă nu știm nimic despre elementele vectorului avem soluţia căutării secvenţiale,
până găsim elementul căutat sau până testăm toate elementele.
Algoritm descris în pseudocod
găsit ←0
pentru i← 0, n-1 execută
dacă x=v[i] atunci
găsit←1
sfârșit dacă
sfârșit pentru

Implementare C++
int CautăSecv(int x,int v[],int n)
{for(int i=0;i<n;i++)
if(x==v[i])
return 1;
return 0;}
Atenţie! În for se execută instrucţiunea vidă.
În cazul cel mai favorabil când elementul se găsește pe prima poziţie se efectuează două
comparaţii. În cazul cel mai nefavorabil, când elementul nu se găsește deloc în vector, se
efectuează 2n+1 comparaţii. În continuare este prezentată o implementare ceva mai rapidă:
Implementare C++
int CautăSecv(int x,int v[],int n)
{int i=0;
while((v[i]!=x)&&(i<n))
i++;
if(i<n)
return 1;
else
return 0;}
Cu această implementare, în cazul cel mai favorabil avem două comparaţii, iar în cazul
cel mai nefavorabil avem n+2 comparaţii.

b). Binară

Dacă elementele vectorului sunt ordonate crescător, putem să ne dăm seama dacă
elementul nu există în vector fără a fi nevoie să parcurgem toate elementele vectorului. Unul
dintre algoritmii folosiţi în acest caz este algoritmul de căutare binară. Acest algoritm are la
bază principiul înjumătăţirii repetate a domeniului în care se caută elementul, prin împărţirea
vectorului în doi subvectori. Notăm cu st primul indice al vectorului și cu dr ultimul indice
al vectorului, iar m este indicele elementului din mijloc al vectorului m=(st+dr)/2. Se
compară valoarea căutată cu valoarea elementului din mijloc. Dacă cele două valori sunt
egale înseamnă că s-a găsit elementul. Dacă nu sunt egale, vectorul v-a fi împărţit în doi
subvectori. Operaţia de căutare constă în identificarea subvectorului în care se poate găsi
elementul, prin compararea valorii căutate cu cea din mijloc, după care se divizează acest
subvector în doi subvectori ș.a.m.d. până când se găsește elementul, sau până când nu se mai
poate face împarţirea în subvectori, ceea ce înseamnă că nu s-a găsit elementul.
Exemplu: Dorim să căutăm elementul x=19 într-un vector cu 9 elemente:
5 8 11 15 17 19 20 23 26

1 2 3 4 5 6 7 8 9

st=1 dr=9 m=5

elementul căutat este x=19


se caută în subvectorul din dreapta st=m+1=6

19 20 23 26

6 7 8 9

19<20 se caută în subvectorul din stânga dr=m-1=6


17 19 st=m=5

5 6

19>17 se caută în subvectorul din dreapta st=m+1=6 19

st=dr=m=6 Elementul 6 a fost găsit

Algoritm descris în pseudocod


Funcţia CautBin(n,A,x)
p←1 q←n
cât timp p≤q execută
m ← [(p+q)/2]
dacă x=A[m] atunci
CautBin ← m
altfel
dacă x<A[m] atunci q ← m-1
altfel p ← m+1
sfdacă
sfdacă
sfcât timp
sfCautBin
Implementare C++
int CautăBinar(int n, int a[], int x)
{int p, q, m;
p = 1; q = n;
while(p <= q)
{ m = (p + q) / 2;
if (x == a[m])
return m;
else
if (x < a[m])
q = m - 1;
else
p = m + 1;}
if(p>q)
return 0;}

Analiza complexităţii unui algoritm (considerând criteriile de eficienţă


durată de executare și spaţiu de memorie utilizat)

Pentru rezolvarea unei probleme se pot folosi mai mulţi algoritmi. În acest caz se va
alege algoritmul cel mai eficient.
Algoritmul cel mai eficient este cel care folosește cel mai puţin resursele calculatorului
și anume:
• memoria internă;
• procesorul.

A. Memoria internă
Pentru a face economie de memorie internă, trebuie avute în vedere următoarele:
- alegerea corectă a tipului de dată pentru fiecare variabilă de memorie folosită;
- rezolvarea problemei folosind cât mai puţine variabile de memorie. Analiza datei
trebuie să se facă în două moduri la alegerea tipului de dată: - logic; - fizic.

a. Logic (la nivel conceptual) -analiza se face pornind de la enunţul problemei; -


presupune identificarea domeniului de definiţie extern al datelor. Exemplu de domeniu de
definiţie extern al datelor: pentru a memora un număr de la extragerea loto “6 din 49” se va
folosi o variabilă care memorează un număr întreg pozitiv din intervalul [1,49].

b. Fizic (la nivelul reprezentării ei în memoria internă)

- analiza se face pornind de la tipurile de date implementate în limbajul de programare;


- fiecare tip de dată are un domeniu de definiţie intern al datelor;
- se alege tipul de dată care consumă cea mai puţină memorie, astfel încât domeniul de
definiţie extern al datei să fie inclus în domeniul de definiţie intern al datei.

B. Procesorul
Timplu de execuţie al unui algoritm depinde de câte valori ale datelor de intrare vor fi
prelucrate. Se definește dimensiunea datelor de intrare ca fiind numărul de valori pentru
datele de intrare ale unui algoritm. În funcţie de complexitatea algoritmului, evaluarea
timpului de execuţie se poate face prin:
a. numărul de operaţii elementare ale algoritmului;
b. timpul mediu al programului.

a. Numărul de operaţii elementare ale algoritmului


- o operaţie elementară este o operaţie sau o succesiune de operaţii care nu depind de
caracteristicile problemei (ex: o operaţie de citire/scriere, o operaţie aritmetică, o operaţie de
comparaţie, o operaţie de atribuire, un grup de astfel de operaţii);
- în schimb, n operaţii de atribuire nu reprezintă o operaţie elementară, deoarece depinde
de o caracteristică a problemei, și anume de valoarea lui n;
b. Timpul mediu al programului

- timpul mediu de execuţie depinde de timpul minim de execuţie care corespunde


cazului cel mai favorabil (când se execută cele mai puţine operaţii) și timpul maxim de
execuţie care corespunde cazului cel mai defavorabil (când se execută cele mai multe
operaţii);

Exemplu:

Suma a n numere pare.


- cazul cel mai favorabil: nu există numere pare (x=0) și numărul de operaţii este 4+4*n;
- cazul cel mai defavorabil: toate numerele sunt pare (x=n) și numărul de operaţii este
4+4*n+n ↔ 4+5*n;
- pentru a calcula timpul mediu de execuţie, va trebui să analizăm toate cazurile posibile (în
total n+1 cazuri): niciun număr par, un număr par, două numere pare, ..., n numere pare;
- numărul mediu de operaţii fiind dat de x calculat astfel:
2. Limbajul de programare C++

Structura si vocabularul programelor C++

_ #include…_este directiva pentru compilator in faza de procesare pentru inglobarea in


textul programului a unor biblioteci cu subprograme standard sau ale utilizatorului;
_ #define…_definirea de numere a unor constante sau definirea de macrouri;
_declaratii de tipuri de date;
_declaratii de variabile globale;
_subprograme utilizator;
_int main()_functia radacina „main” nu returneaza nici un rezultat, in paranteze se inscriu
optional anumiti parametri;
{_deschiderea corpului principal al programului;
_declaratii de tipuri de date(optional)
_declaratii de variabile si constante (optional)
_instructiuni de prelucrare;
}_inchiderea corpului principal al programului;

Observatii
- Limbajul face distinctie intre literele mari si cele mici.
- Nu are importanta nici plasarea cuvintelor pe linie, nici spatiile dintre ele.
- Este permis ca tipul functiei sa lipseasca, astfel se presupune ca functia intoarce un
rezultat de tipul int.

Sintaxa unui limbaj de programare


Orice limbaj de programare se caracterizeaza prin sintaxa si semantica.
- Sintaxa limbajului este data de totalitatea regulilor de scriere corecta (in sensul
acceptarii sale de programul traducator (compilator) care are rolul de a converti
programul in cod masina pentru a fi executat.
- Prin semantica unui limbaj se intelege semnificatia constructiilor sintactice corecte (ce
realizeaza instructiunile)
Sintaxa poate fi descrisa cu ajutorul diagramelor de sintaxa sau o notatie BNF.
Diagrama de sintaxa este constituita din urmatoarele simboluri grafice:
-cercuri-incadreaza anumite simboluri speciale;
-elipse-incadreaza anumite cuvinte rezervate;
-dreptunghiuri-incadreaza elemente definite prin alte diagrame de sintaxa;
-sageti-indica sensul de parcurgere a diagramei.

Mediul limbajului de programare studiat:


-editor de texte;
-compilator;
-interfata de meniuri.
Vocabularul limbajului
- Setul de caractere:
A_Z , a_z
0_9
+ - * / = ^ < > % ( ) [ ] { } . , ; : # $ @ _ blank
- Identificatori: o succesiune de litere, cifre sau _ din care prima nu trebuie sa fie cifra.
- Separatori si comentarii: separatorii pot fi blancuri, caracterul CR - (Carriage Return -
sfârșit de linie) sau caracterul ’;’.
Comentariile se înscriu intre:
/* */ - poate ocupa mai multe linii
/// - poate ocupa o singura linie

Citiri si scrieri
Pentru efectuarea citirilor si scrierilor, trebuie utilizata directiva:
#include <iostream>
includere numele fișierului (io-intrare-iesire; stream-flux de date de la o sursa catre o dest.)
si header(antet)
înaintea textului
cin>>-pentru citire
cout<<-pentru scriere
-pentru spaţiu intre date avem: ” ”
Ex: cout<<a<<” ”<<b≪endl;
-pentru scriere pe rândul următor se fol.: endl

Tipuri de date
(tipuri fundamentale, siruri de caractere, tipuri pointeri, tipuri structurate, tipuri definite
de utilizator, stucturi dinamice de date, tipul obiect)
Tipurile fundamentale (standard – adica este cunoscut de limbaj fara a fi
definit in cadrul programului)
Tipul logic: -bool - logic, ocupa 8 biti (1 octeți) [0 sau 1]
Tipurile intregi :
-tipul unsigned char - caracter fara semn, ocupa 8 biti (1 octeți)[0, 255]
-tipul char-caracter, ocupa 8 biti (1 octeți) [-128, 127]
-tipul short int – intreg scurt, ocupa 16 biti (2 octeți)
-tipul unsigned int – intreg fara semn, ocupa 32 biti (4 octeți)
-tipul int – intreg, ocupa 32 biti (4 octeți)
-tipul unsigned long int – intreg lung fara semn, ocupa 32 biti (4 octeți)
-tipul long int – intreg lung cu semn, ocupa 32 biti (4 octeți)
-tipul long long int – intregi foarte lungi cu semn, ocupa 64 biti (8 octeți)
Variabilele de tip intreg folosesc pentru memorarea datelor codul complementar (daca
se retin cu semn) sau baza 2 (fara semn).
Caracterele se memoreaza prin codul ASCII (0-255). Deci pentru memorarea datelor
negative(cu semn) folosim codul complementar, adica: reprezentam in baza 2 numarul, adica
numarul pozitiv, dupa care schimbam cifra 1 cu 0 si invers si adunam 1. Primul bit reprezinta
semnul ‘0’-pozitiv si ‘1’-negativ. –vezi anexa 2 cls.9-
Tipurile reale:
-tipul float (mobil), ocupa 32 biti (4 octeți)
-tipul double (dublu), ocupa 64 biti (8 octeți)
-tipul long double, ocupa 80 biti (10 octeți)
Pentru memorare se foloseste reprezentarea in virgula mobila; variabilele reale se tiparesc cu
6 zecimale. - vezi anexa 2 cls.9 -

Constante
- Constante întregi: zecimale (in baza 10: 23,1239, 56)
octale (in baza 8 constanta se declara precedata de ‘0’ nesemnificativ:
0123, din care se retine nr. intreg 123(8) )
hexazecimale (in baza 16 sunt precedate de ‘0X’ sau ‘0x’: 0X1A2 adica
1A2(16) sau 0x1a2 adica 1A2(16) )
- Constante caracter: acestea se trec intre doua caractere apostrof (‘)
Se mai declara si sub forma de secvenţa escape, care începe cu caracterul (‘\’)si se termina
cu codul constantei in baza 8 sau 16 {’\141’=a, ‘\x61’=a}, deoarece exista caractere care nu
se pot declara clasic pentru ca nu pot fi tastate: Ex. caracterul newline (codul 10(10)), in octal
‘\12’, sau in hexa ‘\xa’. Caracterul newline se poate scrie si ‘\n’.
Caracterele albe sunt: blank (‘ ‘); tab orizontal (\t), tab vertical (\v), newline (\n),
cr (\r).
- Constante reale
- Constante sir de caractere
Se declara intre doua caractere (“).
Pentru a da nume constantelor(declara), folosim [Link]. :const int numar=10; constanta de
tip int numita numar are valoarea 10.

Operatori aritmetici
- minus (unar) adică acţionează asupra unui singur operand
+ plus (unar)
+ plus (binar)
- minus (binar)
* inmultire (binar)
/ impartire (binar) (DIV)
% restul impartiri intregi (binar) : R=D-I*C . (MOD)

Operatori relationali
< ex. 3<5 – expresia ia valoarea ‘1’ adica adevarat.
<=
> ex. 3>5– expresia ia valoarea ‘0’
>=
Rezultatul unei operati logice adevarate va fi „1”iar false „0”.

Operatori de egalitate
= = pentru egalitate 3==3, rezultat 1;
!= pentru inegalitate 3!=3, rezultat 0.
Operatori de incrementare si decrementare
++ pentru incrementare(adica aduna 1)
-- pentru decrementare(adica scade 1)
Daca operatorul este prefixat: ++a ; --a
Daca operatorul este postfixat: a++ ; a--
Exemple :
1. a=1, 1+a++=2, dar dupa evaluare “a” va retine valoarea “2”.
2. a=1, 1- ++a= -1, a=2.
3. 1+++a, nu are sens.
4. a=1, b=3, a++*b++=3, dar dupa evaluare a=2 si b=4.
5. a=1, b=3, ++a*++b=8, a=2 si b=4.
6. a+++a++, trebuie evitata scrierea unor astfel de expresii .

Operatori logici
! - negare logica (daca operandul este o val. dif. de ‘0’, rez. este ‘0’, altfel este ‘1’)
&& - si logic (daca ambii operanzi sunt diferiti de ‘0’, rez. este ‘1’, altfel e ‘0’)
|| - sau logic (daca cel putin un operand are o valoare diferita ‘0’, rez. este ‘1’)

Operatori logici pe biti


<<, >> - operatori de deplasare
& - si pe biti (daca ambii operanzi sunt diferiti de ‘0’, rez. este ‘1’, altfel e ‘0’)
| - sau pe biti (daca cel putin un operand are o valoare diferita ‘0’, rez. este ‘1’)
^ - sau exclusiv pe biti (daca ambii op. sunt ‘0’ sau ‘1’, rez. este ‘0’, altfel e ‘1’)
~ - negare pe biti (operator unar) (dac un bit contine ‘0’ va contine ‘1’ si invers)

Operatori de atribuire
= de atribuire
*=, /=, %=, +=, -=, <<=, >>=, &>, ^=, |=, sunt tot de atribuire.

Operatorul `,` virgula


exp1, exp2, …, expn;

Operatorul conditional
exp1?exp2:exp3
Daca exp1 0, se evalueaza exp2 si ignora exp3 sau invers.

Operatorul sizeof(marime)
Returneaza numarul de octeti pentru memorarea unei valori:
sizeof (expresie)
sizeof (tip)

Operatorul de conversie explicita


Ex: float x=-1,9
Atunci : (int) x=-1
Se face conversia din float in int prin trunchiere si obligatoriu se pun paranteze.
Instructiunea expresie
Ea are forma: expresie ;
Ex:
1. Prg. Interschimba continutul a doua variabile care au fost citite.
#include<iostream>
#include<conio.h> //biblioteca pt ecran
using namespace std;
int 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;
return 0;
getch();}/*functie pt vizualizarea rezulatului in urma rularii
prog, fara a mai folosi comanda ALT F5 */
2. Se citesc a si b int si se cere sa se afiseze media aritmetica a lor.
#include<iostream>
using namespace std;
int main()
{ int a,b;
float medie;
cout<<"a="; cin>>a;
cout<<"b="; cin>>b;
medie=float(a+b)/2; /*De ce am pus float ? Pentru a se face
conversia in float*/
cout<<"media este "<<medie;
return 0;}
3. Se citesc 3 numere si se cere sa se afiseze primul numar, suma dintre primul si al doilea,
suma primelor 3 numere.
#include<iostream>
using namespace std;
int main()
{int s=0, nr;
cout<<"dati numarul "; cin>>nr;
s+=nr; //se poate scrie si s=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;
return 0;}
Instructiunea IF
If(expresie) instructiune1 else instructiune2
SAU
If(expresie)instructiune
Ex:
1. Maximul dintre doua numere citite.
#include<iostream>
using namespace std;
int main()
{double a,b,max;
cout<<"a="; cin>>a;
cout<<"b="; cin>>b;
if (a<b) max=b;
else max=a;
cout<<"maximul este: "<<max;
return 0;}
2. Se citesc a, b, c. Sa se calculeze: e=a+b, c>0; e=a*b, c=0; e=a-b, c<0.
#include<iostream>
using namespace std;
int 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;//se poate si if(!c)…
else e=a-b;
cout<<e;
return 0;}
3. Se citeste o valoare intreaga, iar in cazul in care este para (se imparte la 2) sa afiseze
mesajul “am citit un numar par “, altfel nimic.
#include<iostream>
using namespace std;
int main()
{int nr;
cin>>nr;
if (nr%2==0) cout<<"am citit valoare para";
//se poate si if(!(nr%2))
return 0;}
Instructiunea compusa
{i1;
i2;
….
in;}
i1, i2,……..in sunt instructiunile din corpul instructiunii compuse.
Ex:
Sa se scrie un program care rezolva ecuatia algebrica de gradul I cu o necunoscuta, cu
coeficienti reali ( a*x+b=0 ).
#include<iostream>
using namespace std;
int 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";
return 0;}

Instructiunea switch(selectie, comutator)


Are forma:
Switch(expresie)
{
(Caz) case exp1:secventa instructiune1;break; (intrerupere)
case exp :secventa instructiune ;break;
…………………………………….
case exp :secventa instructiune ;break;
(implicit) [default:secventa instructiunen+1];
}
Ex:
Probarea instructiunii switch:
#include<iostream>
using namespace std;
int 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";
return 0;}}
Instructiunea while(cat timp)
While(expresie) instructiune;
Ex:
1. Sa se calculeze suma cifrelor lui n=213:
#include<iostream>
using namespace std;
int main()
{int n,s=0;
cout<<"n=";
cin>>n;
while(n!=0)
{s=s+n%10;
n=n/10;}
cout<<"s="<<s<<endl;
return 0;}
2. Sa se afiseze numarul obtinut prin inversarea cifrelor sale (n=412, 214 ):
#include<iostream>
using namespace std;
int main()
{int n,ninv=0;
cout<<"n=";
cin>>n;
while(n!=0)//sau: while(ninv=ninv*10+n%10,n/=10);
{ //sau: while(ninv*=10, ninv+=n%10 ,n/=10);
ninv=ninv*10+n%10;
n=n/10;}
cout<<"nr este= "<<ninv;
return 0;}

Instructiunea do while
Are forma: do
instructiune
while(expresie);
Ex:
1. Sa se calculeze suma primelor n numere naturale:
#include<iostream>
using namespace std;
int main()
{int n,s=0,i=1;
cout<<"n="; cin>>n;
do { s+=i++;}
while(i<=n); //sau: do
cout<<"s="<<s<<endl; //{ s=s+i; i=i+1;
return 0;} // } while(i<=n);
2. Se citeste n, numar natural. Sa se descompuna in factori primi:
#include<iostream>
using namespace std;
int 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);
return 0;}

Instructiunea FOR
Are forma generala:
for (expresieintializare ;expresietest ; expresie incrementare )
instructiune;
Pentru a opri o ciclare infinita in:
 DOS – tastam CTRL+PAUSE;
 WINDOWS – tastam CTRL+ALT+DEL.
Ex:
1. Listarea numerelor 5, 4, 3, 2, 1.
#include<iostream>
using namespace std;
int main()
{int i;
for (i=5;i>=1;i--)
cout<<i<<" ";
return 0;}
2. Sa se listeze alfabetul in ordine inversa.
#include<iostream>
using namespace std;
int main()
{char car;
for (car='z';car>='a';car--)
cout<<car<<endl;
return 0;}
3. Sa se efectueze suma primelor n numere naturale. Exemplu: n=3. S=1+2+3=6.
#include<iostream>
using namespace std;
int main()
{int i, n, s=0;
cout<<"n=";
cin>>n;
for (i=1;i<=n;i++)
s+=i; /*sau: s=s+i;sau: for (i=1;i<=n;s+=i++);*/
cout<<"suma primelor n numere naturale este"<<s;
return 0;}
4. Sa se calculeze suma: s=0,1+0,2+…+0,9.
#include<iostream>
using namespace std;
int main()
{int i;
float s;
for (i=1;i<=9;i++)
s+=(float)i/10;
cout<<s;
return 0;}
sau:
#include<iostream>
using namespace std;
int main()
{double s=0, i;
for (i=0.1;i<=0.9;i+=0.1)
s=s+i; // sau: s+=i;
cout<<s;
return 0;}
Daca s si i sint de tip float, rezultatul este eronat deoarece numerele reale se reprezinta
aproximativ si pot aparea erori !
5. Sa se calculeze suma : 1+1*2+1*2*3+…+1*2*…*n
#include<iostream>
using namespace std;
int main()
{int i, n, s=0, p=1;
cout<<"n=";
cin>>n;
for (i=1;i<=n;i++)
{p*=i; // p=p*i;
s+=p; // s=s+p;}
cout<<s;
return 0;}
6. Sa se afiseze cel mai mare numar citit. Ex: Daca avem n=4, iar numerele sunt –7, 9, 2, 3,
se va afisa 9.
#include<iostream>
using namespace std;
int 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;
return 0;}
7. Sa se afiseze numarul obtinut prin inversarea cifrelor sale.
#include<iostream>
using namespace std;
int main()
{int n, ninv;
cout<<"n=";
cin>>n;
for (ninv=0;n>0;)// sau for (ninv=0;n>0;n/=10)
{ninv=ninv*10+n%10; // ninv=ninv*10+n%10;
n=n/10;}
cout<<ninv;
return 0;}

Functii
Daca utilizam functii matematice trebuie inclus fisierul #include<math.h>,
#include<stdlib.h>, sau #include<cstdlib.h> in code blocks.
Exemplu de prototip : int t (int, float); iar apelata arata asa int t (7, 9.2);

 abs are forma generala: #include<stdlib.h>


int abs(int x);
Intoarce modulul lui |x| .
#include<iostream>
#include<stdlib.h> //#include<cstdlib.h>
using namespace std;
int main()
{int n;
cout<<"n= ";
cin>>n;
cout<<"modulul este ="<<abs(n);
return 0;}
 fabs are forma generala: #include<math.h>
double fabs(double x);
Intoarce modulul lui |x| real.
 labs are forma generala: #include<stdlib.h>
long int labs (long int x);
Intoarce modulul lui |x| intreg lung.
 acos are forma generala: #include<math.h>
double acos(double x);
Calculeaza arccos(x):[-1,1] [0, п]
 asin #include<math.h>
double asin(double x);
Calculeaza arcsin(x):[-1,1] [-п/2, п/2]
 atan #include<math.h>
double atan(double x);
Calculeaza arctg(x):R (-п/2,п/2)
 atan2 #include<math.h>
double atan2(double y,double x);
calculeaza arctg (y/x)

 floor #include<math.h>
double floor (double x);
calculeaza [x] a lui x (rotunjirea se face in minus)
floor (123.78)=123 floor(-23.34)=-24
 ceil #include<math.h>
double ceil(double x);
ceil(123.78)=124
ceil(-23.34)=-23
 cos #include<math.h>
double cos (double x);
calculeza valoarea functiei cos
cos(x):R [-1,1]
 sin #include<math.h>
double sin (double x);
calculeaza valoarea functiei sin(x):R [-1,1]
 tan #include<math.h>
double tan (double x);
calculeaza valoarea functiei tg(x):R-{kп+п/2|kєz} R
 exp #include<math.h>
double exp(double x);
Calculeaza functia ex: R R+*
 log #include<math.h>
double log(double x);
Calculeaza functia ln(x):R+* R unde ln(x)=loge(x)
 log 10 #include<math.h>
double log 10(double x);
Calculeaza functia lg(x):R+* R, unde lg(x)=log10(x);
 pow #include<math.h>
double pow( double x, double y);
Calculeaza xy.

Generarea numerelor aleatoare


Au prototipurile in fisierul: stdlib.h
Are forma:
int rand(void);
- rand – genereaza un nr aleator intre 0 - 32767
Setarea generatorului de nr. aleatoare se face cu randomize:
Void randomize(void);
Ex:
Se tiparesc 10 nr. aleatoare, cuprinse intre 0 si 49.
#include<iostream>
#include<stdlib.h>
using namespace std;
int main()
{int i;
void randomize();
srand (time(NULL));//sa nu se repete nr.
for(i=0;i<10;i++)
cout<<rand()%50<<" ";
return 0;}
Pentru setarea generatorului de nr. aleatoare intr-un anumit punct:
void srand(unsigned n);

Rularea unei secvente intr-un interval de timp determinat


Prototipul functiei clock il gasim in fisierul: time.h
Forma generala: clock_t clock(void); (tipul functiei este long, clock_t este o denumire
data cu typedef)
Ex:
Programul următor ruleaza 30 secunde:
include<time.h>
#include<iostream>
#include<stdio.h>
using namespace std;
int main(void)
{
long start,end;
start=clock();
do
{cout<<"MA";
end=clock();}
while ((end-start)/CLK_TCK<30);
} /* CLK_TCK – este o const. definita in time.h pt conversia
in secunde*/

Tipuri de date structurate


Tablouri

Tabloul in interpretarea matematica


Ani ={1, 2, …, ni;} niN
Fie: M=An1 x An2 x … x Ank, produsul cartezian
Se numeste tablou o functie f: M T
Daca: K=1, tabloul se mai numeste si vector.
Daca: K=2, tabloul se mai numeste si matrice.
Ex_vector:
V=(1,3,6,2,4)
Avem K=1, n1=5 elemente.
Elementele vectorului sunt:
v1= 1, v2=3…….
Ex._matrice cu „n” si „m” coloane
A=

Tablouri in c++
Ex: int v[100];
Am declarat un vector cu 100 de componente de tip intreg.
Ex: int a[10][9];
Am declarat o matrice (tablou) care are 10 linii si 9 coloane. Ele se adreseaza astfel:
a[0][0], a[0][1], … a[9][8].
Ex:
1. Acest program citeste si tipareste variabila un vector.
Initial se citeste „n ”(nr. comp.). Dupa aceasta se citesc, pe rand, toate componentele.
Sa presupunem ca s-a citit n=3. Apoi se citesc 3 numere intregi, cate unul pentru
fiecare componenta, si anume 6, 3, 4.
Citirea s-a facut cu ajutorul unei „for ”, unde variabila de ciclare reprezinta chiar
indicele componentei care se citeste.
In final se tipareste continutul componentelor citite
#include<iostream>
using namespace std;
int main()
{int v[100],n,i;
cout<<"numarul 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;
return 0;}
2. Programul de mai jos utilizeaza doi vectori, cu componente de tip float.
Citim vectorul ”a ”.
Se face atribuirea pe componente.
La sfarsit se tipareste vectorul „b ”.
#include<iostream>
using namespace std;
int main()
{int n,i;
float a[50],b[50];
cout<<"numarul 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;
return 0;}
3. Se citeste si se tipareste un tablou. Initial se citesc numarul de linii si coloane ale
tabloului (m si n).
#include<iostream>
using namespace std;
int 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;}
return 0;}

Maxim, minim
Se citeste un vector cu „n” componente intregi.
Sa se afiseze cel mai mare numar intreg gasit.
#include<iostream>
using namespace std;
int main()
{int v[9],n,i,max;
cout<<"n=";
cin>>n;
for(i=0;i<n;i++)
{cout<<"v["<<i+1<<"]=";
cin>>v[i];}
max=v[0];
for(i=1;i<n;i++)
if(v[i]>max)
max=v[i];
cout<<"valoarea maxima este"<<max;
return 0;}
Elemente distincte
Se citeste „n” si o variabila de tip „array ”cu „n ” componente intregi.
- se compara valoarea retinuta de prima componenta cu valorile retinute de
componentele de la 2 la n;
- se compara valoarea retinuta de a doua componenta cu valorile retinute de
componentele de la 3 la n;
- …………………………………………………….
Daca in urma comparatiilor nu se gasesc doua valori egale rezulta ca numerele sunt
distincte.
#include<iostream>
using namespace std;
int main()
{int v[9],n,i,j,gasit;
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=i+1;j<n && !gasit;j++)
if(v[i]==v[j])
gasit=1;
if(gasit)
cout<<"numerele nu sunt distincte";
else cout<<"numerele sunt distincte";
return 0;}

MULTIMI

A citi o multime, inseamna a introduce elementele care o alcatuiesc. Acestea sunt


memorate intr-o variabila de tip vector. Presupunem ca elementele introduse sunt distincte.

A. TESTUL DE APARTENENTA
Se citeste o multime A de numere intregi. Se citeste un numar intreg “e ”. Sa se decida daca
“e” apartine lui A.
-o variabila ”gasit” va retine, initial, valoarea 0;
-apoi se testeaza fiecare element al multimii „A” daca este sau nu egal cu numarul retinut de
„e”;
-in caz de egalitate variabila gasit va retine „1” .
#include<iostream>
using namespace std;
int main()
{int mult[9], n, e, i, gasit;
cout<<"numarul de elemente al multimii";
cin>>n;
for(i=0;i<n;i++)
{cout<<"mult[ "<<i+1<<"]=";
cin>>mult[i];}
cout<<"e=";
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";
return 0;}

B. DIFERENTA A DOUA MULTIMI


Se citesc doua multimi A si B. Se cere sa se afiseze multimea C=A-B.
- Pentru fiecare element al multimii A, se face testul daca apartine sau nu multimii
B. In caz de neapartenenta, acesta este adaugat unei multimi C, initial vide.
- O variabila „K“ (cu valoarea initial „0“) retine indicele componentei din C care va
memora urmatorul element ce se adauga multimii diferenta.
- In final, se tipareste C.
#include<iostream>
using namespace std;
int main()
{int multa[9], multb[9], multc[9], n, m, i, j, k, gasit;
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;
return 0;}

C. REUNIUNEA A DOUA MULTIMI


Se citesc doua multimi A si B;
Sa se afiseze C=AUB.
- Aplicam C=AUB=BU(A-B):
#include <iostream>
using namespace std;
int main()
{int multa[9],multb[9],multc[9],n,m,i,j,k,gasit;
cout<<"Numarul de elemente al multimii A";
cin>>n;
for(i=0;i<n;i++)
{cout<<"multA["<<i+1<<"]=";
cin>>multa[i];}
cout<<"Numarul de elemente al multimii B";
cin>>m;
for(i=0;i<m;i++)
{cout<<"multB["<<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;
return 0;}

D. INTERSECTIA A DOUA MULTIMI.


Se citesc doua multimi de numere intregi A si B.
Sa se afiseze multimea C=AB. C=AB={x A|x B}
- Pentru fiecare element al multimii A se face testul de apartenenta la multimea B, iar in caz
afirmativ, este adaugat unei multimi C, initial vida.
#include<iostream>
using namespace std;
int main()
{int multa[9],multb[9],multc[9],n,m,i,j,k,gasit;
cout<<"Numarul de elemente al multimii A";
cin >>n;
for(i=0;i<n;i++)
{cout<<"multA["<<i+1<<"]=";
cin >>multa[i];}
cout<<"Numarul de elemente al multimii B";
cin>>m;
for(i=0;i<m;i++)
{cout<<"multB["<<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;
return 0;}
E. PRODUSUL CARTEZIAN DINTRE DOUA MULTIMI
Fie: A={1,2….n}, B={1,2…m}
- Se citesc ”m“ si ”n”;
- Sa se afiseze multimea C=AxB.

- Cunoastem ca C=AxB={(x,y) | xA, yB};


- Ex: A={1,2}, B={1,2,3};
- C=AxB={(1, 1), (1, 2), (1, 3), (2, 1), (2, 2), (2, 3)}
- Trebuie afisate toate perechile de numere (x, y) cu x{1,…, n}, y{1,…, m}.
#include<iostream>
using namespace std;
int 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;
return 0;}
Pentru cazul general avem: „n” si „m” caractere.
Avem: A={a, b}; B={c, d, e}.
Diferenta este ca, la afisare, in loc sa afisam perechea (i, j), afisam perechea (A[i], B[i]),
adica (a, c).
#include<iostream>
using namespace std;
int main()
{char multa[9],multb[9];
int n,m,i,j ;
cout<<"numarul de elemente al multimii A";
cin>>n;
for(i=0;i<n;i++)
{cout<<"multA["<<i+1<<"]=";
cin>>multa[i];}
cout<<"Numarul de elemente al multimii B";
cin>>m;
for(i=0;i<m;i++)
{cout<<"multB["<<i+1<<"]=";
cin>>multb[i];}
for(i=0;i<n;i++)
for(j=0;j<m;j++)
cout<<multa[i]<<" "<<multb[j]<<endl;
return 0;}
F. SUBMULTIMI
- Se citeste „n”, numar natural.
- Se cere sa se afiseze toate submultimile multimii {1,2,…., n}.
Ex: n=3. Multimea {1,2,3} are urmatoarele submultimi:
{1,2,3}, {1,2}, {1,3}, {2,3}, {1}, {2}, {3} si Ø.
Deci exista 2n submultimi ale ei:
#include<iostream>
using namespace std;
int main()
{int multa[9],n,i,s;
cout<<"numarul de elemente al multimii A";
cin>>n; // pt n=3
for(i=0;i<n;) // 0 1 2
multa[i++]=0; // multa[0]=0;multa[1]=0;multa[2]=0
do
{multa[n-1]++; // multa[2]++ este 0 0 1
for(i=n-1;i>=1;i--)//dupa un ciclu do, multa[2]++ este 0 0 2
if(multa[i]>1)//deci multa[2]>1
{multa[i]-=2;//scadem 2 adica va fi 0 0 0
multa[i-1]+=1;}//si adunam 1 la mult[1] adica va fi 0 1 0 smd
s=0;
for(i=0;i<n;i++)
s+=multa[i];
for(i=0;i<n;i++)
if(multa[i])
cout<<i+1<<" ";//deci la prima adunare va fi pentru i=2+1 va afisa 3
cout<<endl;}
//la a doua adunare va fi pentru i=1+1 va afisa 2
while(s<n);
cout<<"multimea vida";}

Metode de sortare

Operatia prin care se aranjeaza cele ”n” numere in ordine crescatoare


(descrescatoare ) se numeste sortare.
Algoritmul de sortare:

a) prin selectarea minimului (maximului):


- se determina minimul dintre valorile retinute, incepand cu prima pozitie;
- minimul este trecut in prima pozitie prin interschimbarea continuturilor dintre componenta
de indice 1 si componenta care il memoreaza;
- se determina minimul dintre valorile retinute, incepand cu a doua pozitie;
- minimul este trecut in pozitia doi, prin interschimbarea continuturilor dintre componenta de
indice 2 si componenta care il memoreaza;
- se determina minimul dintre valorile retinute incepind cu a treia pozitie;
- minimul este trecut in pozitia 3 prin interschimbarea continuturilor dintre componenta de
indice 3 si componenta care il memoreaza;
--------------------------------------------------------------------
Ex:
#include<iostream>
using namespace std;
int main()
{int a[9],n,i,j,k,max,min;
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;}
max=a[k];
a[k]=a[i];
a[i]=max;}
for(i=0;i<n;i++)
cout<<a[i]<<endl;
return 0;}
b) Sortarea prin interschimbare (bubble sort):
 Se parcurge variabila inversand continuturile componentelor alaturate care nu sunt in
ordine crescatoare (descrescatoare);
 Procedeul se repeta pana cand are loc o parcurgere in care nu se fac inversari.
Ex:
#include<iostream>
using namespace std;
int main()
{int a[9],n,i,max,gasit;
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])
{max=a[i];
a[i]=a[i+1];
a[i+1]=max;
gasit=1;}}
while(gasit);
for(i=0;i<n;i++)
cout<<a[i]<<endl;
return 0; }
c) Sortarea prin insertie:
Cu ajutorul variabilei citite A (vector) se aranjeaza valorile intr-o alta B in ordine
crescatoare.
Vom proceda astfel:
- B[1]=A[1];
- fiecare din valorile retinute de componentele de la 2 la n (in ordinea indicilor) se insereaza
intre valorile ordonate deja ale vectorului B.
Ex:
#include<iostream>
using namespace std;
int main()
{int a[9],b[9],n,i,j,k;
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[j]) 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;
return 0;}

Interclasare (merge sort)


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).
Algoritm de interclasare:
Sa presupunem ca cele doua siruri de numere sortate se memoreaza in doua variabile
de tip vector A si B.
Numerele sortate vor fi memorate intr-o alta variabila de tip vector (numita C). La fiecare pas
se alege un numar care va fi memorat in C (cel care urmeaza a fi adaugat in ordinea ceruta).
Ex:
#include<iostream>
using namespace std;
int 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;
return 0;}

Cautare binara
-Se citesc „n” numere intregi sortate crescator.
-Se citeste un numar intreg „nr”.
-Sa se decida daca ”nr” se gaseste in sirul celor ”n” numere citite.

Vom compara pe rand „nr”cu toate numerele citite.


Avem un algoritm in sensul acesta, ce se numeste „cautare binara”, si tine
cont de faptul ca numerele citite sunt sortate crescator.
Cautarea se efectueaza intre numerele retinute de componente de indice retinute de
doua variabile ”li ” si ”ls ” (initial ”li=1si ls=n ”);
- se calculeza indicele componentei din mijloc, in cazul in care „n” este impar, sau a uneia
din cele doua plasate in mijloc, in cazul in care „n” este par ( k=(li+ls) div 2 );
-apar trei posibilitati:
1) valoarea retinuta de componenta de indice calculat este egala cu „nr ” (caz in care
cautarea se termina cu succes);
2) valoarea retinuta de componenta de indice calculat este mai mică decat „nr ” (caz in
care numarul va fi cautat intre componentele de indice li=k+1 si ls);
3) valoarea retinuta de componenta de indice calculat este mai mare decat „nr” (caz in
care numarul va fi cautat intre componentele de indice li si ls=k-1).
Cautarea se termina cand numarul a fost identificat sau cand li>ls (cautare fara
succes).
Ex:
#include<iostream>
using namespace std;
int 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!!!";
return 0;}

Aplicatii cu matrice
Zone speciale în matrice pătratice
1. Diagonala principală și secundară
Diagonala principală

Diagonala principală este formată din elementele care îndeplinesc relația:

i = j — numărul liniei este egal cu numărul coloanei.

Diagonala secundară

Diagonala secundară conține elementele:

a[1][n], a[2][n−1], …, a[n][1]

caracterizate de relația:
i+j=n+1

Zona de deasupra diagonalei principale

Elementele de deasupra diagonalei principale sunt:

a[1][2], a[1][3], …, a[1][n]


a[2][3], …, a[2][n]

a[n−1][n]

Relația dintre coordonate comună tuturor elementelor din această zonă este:

i<j

Zona de sub diagonala principală

Elementele aflate sub diagonala principală au între coordonate relația:

i>j

Observații
În practică, prelucrarea elementelor se poate realiza fie exclusiv pe diagonale sau pe zonele
identificate mai sus (de exemplu: ordonarea diagonalelor, verificarea simetriei sau a
triangularității), fie prin parcurgerea tuturor elementelor matricei și prelucrarea diferențiată în
funcție de relația dintre coordonate (de exemplu: completarea elementelor cu anumite valori,
calculul simultan al mai multor rezultate obținute pentru fiecare zonă în parte).

Modalități de prelucrare a elementelor într-o matrice


pătratică de dimensiune n
Diagonala principală
for (i = 1; i <= n; i++)
<prelucrează a[i][i]>

Diagonala secundară
for (i = 1; i <= n; i++)
<prelucrează a[i][n - i + 1]>
Deasupra diagonalei principale
for (i = 1; i <= n - 1; i++)
for (j = i + 1; j <= n; j++)
<prelucrează a[i][j]>

Sub diagonala principală


for (i = 2; i <= n; i++)
for (j = 1; j <= i - 1; j++)
<prelucrează a[i][j]>

Prelucrarea într-o singură parcurgere a tuturor zonelor


for (i = 1; i <= n; i++)
for (j = 1; j <= n; j++)
{
if (i == j)
<prelucrează a[i][j] – diag. principală>
else if (i + j == n + 1)
<prelucrează a[i][j] – diag. secundară>
else if (i < j)
<prelucrează a[i][j] – deasupra diag. principală>
else
<prelucrează a[i][j] – sub diag. principală>

if (i + j < n + 1)
<prelucrează a[i][j] – deasupra diag. secundară>

if (i + j > n + 1)
<prelucrează a[i][j] – sub diag. secundară>
}

Problemă propusă
Se citesc elementele întregi ale unei matrice pătratice A de ordin n.

Afișați elementele din următoarele zone ale matricei:

a) diagonala principală
b) diagonala secundară
c) elementele aflate sub diagonala principală
d) elementele aflate deasupra diagonalei secundare

Determinați elementul minim, elementul maxim și suma elementelor din fiecare dintre
zonele a), b), c), d).
Zone speciale
Zona Nordică

Deasupra diagonalei principale și deasupra diagonalei secundare:

i < j și i + j < n + 1

Zona Sudică

Sub diagonala principală și sub diagonala secundară:

i > j și i + j > n + 1

Zona Vestică

Sub diagonala principală și deasupra diagonalei secundare:

i > j și i + j < n + 1

Zona Estică

Deasupra diagonalei principale și sub diagonala secundară:

i < j și i + j > n + 1

1. Interschimbare linii
Se citeste un tablou cu „m” linii si ”n” coloane.
Se citesc doua numere naturale ”x” si ”y”, cuprinse intre ” l ” si ”m”.
Se cere sa se interschimbe linia ”x” cu linia ”y”.
Ex:
#include<iostream>
using namespace std;
int 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;j<n;j++)
{cout<<"mat["<<i+1<<","<<j+1<<"]=";
cin>>mat[i][j];
cout<<endl;}
cout<<"x=";
cin>>x;
cout<<endl<<"y=";
cin>>y;
cout<<endl;
for(i=0;i<m;i++)
{for(j=0;j<n;j++)
cout<<mat[i][j]<<" ";
cout<<endl;}
for(j=0;j<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;j<n;j++)
cout<<mat[i][j]<<" ";
cout<<endl;}
return 0;}

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.
Ex:
Afisam elementele aflate pe fiecare latura a fiecarui patrat in ordinea ceruta, avind grija ca
elementele aflate in colturi sa nu fie afisate de doua ori!
#include<iostream>
using namespace std;
int 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<<','<<j<<"]=";
cin>>mat[i][j];}
for(k=1;k<=n/2+1;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;}
return 0;}
Am lucrat cu indicii intre ‚1 ‚ si „n”.

SIRURI DE CARACTERE

Conventia este ca ultimul octet sa retina „0” (codul caracterului nul).


Vectorii de caractere pot fi initializati la declarare, caracterul nul fiind memorat automat.
Ex:
 char vect [11]=”calculator”
 char vect []=”calculator”

CITIREA/SCRIEREA SIRURILOR DE CARACTERE


Ex:
Sa se citeasca si sa se afiseze cuvintul “calculator”.
#include<iostream>
using namespace std;
int main()
{char a[20];
int i;
for(i=0;i<10;i++)
cin>>a[i];
a[10]=0; //caract nul intrerupe citirea altor caractere
for(i=0;i<10;i++)
cout<<a[i];
return 0;}
SAU:
#include<iostream>
using namespace std;
int main()
{char a[20];
cin>>a;
cout<<a;
return 0;}
 Caracterul nul este adaugat automat
 Procedind ca mai sus, putem citi orice cuvant cu un numar de pana la „19” caractere -
excluzind caracterul nul.
 Putem sa rezervam - in limita memoriei pe care o are la dispozitie programul - un
numar mai mare de octeti. Ex: char a[1000].
 Un vector poate fi adresat pe componente. Ex: a[0]=’c’, a[1]=’a’, etc.
In momentul intalnirii unui caracter ”alb” calculatorul sfarseste citirea acestora. Pentru a
putea citi si caracterele „albe”, avem o functie.
Functia:
[Link](vector_de_caractere, int nr, char=’\n’)
- au fost citite ”nr –1” caractere;
- a fost intalnit caracterul transmis ca ultim parametru (implicit ’\n’).
Observatie:
a) sunt citite si caracterele albe;
b) este inserat caracterul nul;
c) caracterul transmis ca ultim parametru nu este inserat in sir.
Ex:
A. Se citeste un sir de maxim 2 caractere.
char a[10];
[Link](a,3);
cout<<a;
Daca tastam „mama” si apasam „enter” se citeste sirul „ma”.
B. Citirea se poate intrerupe si la un anumit caracter.
char a[10];
[Link](a,10,’g’);
cout<<a;
Ob. In cazul utilizarii repetate a functiei „[Link]()” cu trei parametri apare o problema.
Ex:
#include<iostream>
#include<cstring>
using namespace std;
int main()
{char sir1[1000],sir2[25];
cout<<"sir 1"<<endl;
[Link](sir1,1000);
[Link]();
cout<<"sir 2"<<endl;
[Link](sir2, 25);
cout<<sir1<<','<<endl<<sir2;
return 0;}
Daca, dupa prima citire, nu am fi folosit functia „[Link]( );” fara parametri, a doua citire nu
ar mai fi efectuata.
Al treilea parametru este trecut in mod facultativ. Daca nu este trecut, se presupune ca este ’\
n’.
(\n )-caracterul nul
Tipul char
Ex:
#include<iostream>
using namespace std;
int main()
{char a[ ]="masa";
cout<<a+1<<" "<<a+2<<" "<<a+3;
return 0;}
In urma enuntiei se tipareste fara 1,2,3 octeti: asa, sa, a.
Ex:
#include<iostream>
using namespace std;
int main()
{char a[]="Exemplu", *p;
p=a; cout<<p<<endl;
p++; cout<<p<<endl;
p++; cout<<p<<endl;
cout<<p[1]<<endl;
cout<<p-a;
return 0;}
Am declarat un vector de caractere numit „a ”, pe care l-am initializat cu un sir.
Am declarat si o variabila de tip „char ” numita ”p ”.
- p=a; „p ” va retine adresa vectorului de caractere ”a ”, adica adresa primului sau octet.
Atribuirea a=p este incorecta, deoarece a este o constanta ce reprezinta o adresa.
- daca la continutul variabilei „p ”se adauga ”1 ”, aceasta va retine adresa vectorului al carui
prim octet coincide cu al doilea octet al vectorului “v “.

Functii si algoritmi care lucreaza cu siruri de caractere

Pentru atribuirea unui sir de caractere se folosesc anumite functii, care sunt chemate de
fisierul antet <cstring>, vechiul<string.h>.
Cele mai uzuale functii sunt:
1.
- strlen - size_t strlen(char*);
Are rolul de a returna lungimea efectiva a unui sir fara caracterul nul.
 size _t - este un tip intreg, utilizat in adresarea memoriei definit in „cstring”.
Progamul citeste un sir si afiseaza numarul de caractere pe care le are sirul citit.
#include<iostream>
#include<cstring>
using namespace std;
int main()
{char a[100];
[Link](a,100);
cout<<" Sirul citit are "<<strlen(a)<<" caractere " ;
return 0;}
2.
- strcpy - char *strcpy(char* dest, char* sursa);
Are rolul de copia sirul de adresa ”sursa ” la adresa „dest ”. Copierea se termina dupa ce a
fost copiat caracterul nul. Se returneaza adresa „dest ”.
#include<iostream>
#include<cstring>
using namespace std;
int main()
{char a[100]="un sir",b[100]="alt sir";
strcpy(a,b);
cout<<a;
return 0;}
In progam se copiaza in vectorul „a ” sirul ”b ”.
Va afisa „alt sir ”.
Aceasta copiere simuleaza atribuirea „a=b ”.
3.
- strcat - char *strcat(char* dest, char* sursa);
Are rolul de a adauga sirului de adresa “dest ” sirul de adresa “sursa ”.
Sirul „sursa ”ramane nemodificat.
Progamul următor tipareste”mama merge ”:
#include<iostream>
#include<cstring>
using namespace std;
int main()
{char a[20]=" mama ",b[100]=" merge " ;
strcat(a,b);
cout<<a;
return 0;}
4.
- strncat - char *strncat(char *dest, const char *sursa, size_t nr);
Are acelasi rol ca „strcat ” cu deosebirea ca adauga sirului destinatie primii „[Link]” ai
sirului sursa. Adaugarea caracterelor se face inaintea caracterului nul. Functia returneaza
adresa de inceput a sirului destinatie.
5
- strchr - char *strchr(char *s, int c);
Are rolul de a cauta caracterul „c ” in sirul „s ”si a afisa din acel punct.
Cautarea se face de la stinga la dreapta.
Exemplu: Se cauta in sirul „a ” caracterul „t ”. Programul va tipari sirul „ta este ”.
#include<iostream>
#include<cstring>
using namespace std;
int main()
{char a[20]=" aceasta este ";
cout<<strchr(a,'t');
return 0;}
Aplicatia 1:
Indicele in cadrul vectorului a caracterului cautat se obtine ca diferenta intre doua valori de
tipul char*. Descazutul este adresa returnata de functie, iar scazatorul este adresa vectorului
in care se face cautarea. Programul tipareste indicele primei aparitii a caracterului ‘t’, si
anume 4.
#include<iostream>
#include<cstring>
using namespace std;
int main()
{char a[20]="Acesta este";
cout<<strchr(a,'t')-a;
return 0;}
Aplicatia 2:
Se citeste un sir si un caracter. Daca acesta este gasit in sir se tipareste indicele primei
aparitii a caracterului in sirul solicitat.
#include<iostream>
#include<cstring>
using namespace std;
int main()
{char a[100],*t,c;
cout<<"introduceti sirul";
[Link](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";
return 0;}
Ob:
Variabila t este de tipul char*.
Testul de apartenenta a caracterului la sir s-a facut prin a vedea daca variabila t retine sau nu
0.
Aplicatia 3:
Se listeaza indicii tuturor aparitiilor caracterului citit in sir.
#include<iostream>
#include<cstring>
using namespace std;
int main()
{char a[100], *t,c;
cout<<"Introduceti sirul!"<<endl;
[Link](a,100);
cout<<"Caracterul cautat!"<<endl;
cin>>c;
t=a-1;
do
{t++;
t=strchr(t,c);
if (t)
cout<<"Indicele este..."<<t-a<<endl;}
while (t);
return 0;}
6.
- strrchr - char *strrchr (const char *s, int c);
Are acelasi rol cu „strchr”, cu deosebirea ca returneaza adresa ultimei aparitii a caracterului.
7.
- strcmp - int strcmp (const char *s1, const char*s2);
Are rolul de a compara doua siruri de caractere.
Exemplu:
#include<iostream>
#include<cstring>
using namespace std;
int main()
{char a[100],b[100];
int semnal;
cout<<"introduceti sirul a"<<endl;
cin>>a;
cout<<"introduceti sirul b"<<endl;
cin>>b;
semnal=strcmp(a,b);
if(semnal<0)
cout<<"a<b";
else
if(semnal>0)
cout<<"a>b";
else
cout<<"a=b";
return 0;}
Aplicatia 1:
Se citesc n cuvinte care vor fi sortate alfabetic.
#include<iostream>
#include<cstring>
using namespace std;
int 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;
return 0;}
8.
- strstr - char *strstr(const char *s1, const char *s2);
Are rolul de a identifica daca sirul „s2” este subsir al sirului „s1”.
Aplicatia 1:
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>
#include<cstring>
using namespace std;
int main()
{char sir[1000],subsir[25],*t;
cout<<"introduceti textul";
[Link](sir,1000);
[Link]();
cout<<"introduceti subsirul cautat";
[Link](subsir,25);
t=strstr(sir,subsir);
if(t) cout<<"este subsir si are indicele"<<t-sir;
else cout<<"nu este subsir";
return 0;}
Aplicatia 2:
Stergerea tuturor aparitiilor unui subsir din cadrul unui sir. Imediat ce am identificat adresa
de inceput a subsirului, restul sirului (fara subsir ) este copiat pe pozitia de inceput a
subsirului.
#include<iostream>
#include<cstring>
using namespace std;
int main()
{char sir[1000],subsir[25],*p;
int lung_subsir;
cout<<"introduceti textul";
[Link](sir,1000);
[Link]();
cout<<"introduceti subsirul";
[Link](subsir,25);
lung_subsir=strlen(subsir);
p=strstr(sir,subsir);
while(p)
{strcpy(p,p+ lung_subsir);
p=strstr(p,subsir);}
cout<<sir;
return 0;}
Aplicatia 7:
Inlocuirea tuturor aparitiilor unui subsir cu alt subsir.
#include<iostream>
#include<cstring>
using namespace std;
int main()
{char sir[100],man[100],sterg[25],adaug[25],*p;
int lung_sterg,lung_adaug;
cout<<"introduceti textul";
[Link](sir,100);
[Link]();
cout<<"inlocuim subsirul";
[Link](sterg,25);
[Link]();
cout<<"cu subsirul";
[Link](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;
return 0;}
9.
- stricmp - int stricmp( char *s1,char *s2);
Are acelasi rol ca strcmp numai ca nu face distinctie intre literele mari si mici.

10.
- strtok - char *strtok( char *s1, const char *s2);

Aplicatia 1:
Se citeste un sir de caractere. Entitatile se considera a fi cuvinte – siruri de caractere care nu
sunt albe – separate prin blank-uri si/sau virgule. Programul listeaza entitatile depistate, care
incep cu ‘p’ fiecare pe un rind.
#include<iostream>
#include<cstring>
using namespace std;
int main()
{char sir[1000], separator[]=" , ", *p;
[Link](sir, 1000);
p=strtok(sir,separator);
while(p)
{cout<<p<<endl;
p=strtok(NULL, separator);}
return 0;}
11.
- strcspn - size_t strcspn ( const char *s1, const char *s2);
Are rolul de a returna nr. de caractere ale sirului „s1” - caractere consecutive care incep
obligatoriu cu primul caracter - care nu se gasesc in sirul „s2”.
12.
- strspn - size_t strspn( char *s1, char *s2);
Are rolul de a returna nr. de caractere ale sirului „s1” - caractere consecutive care incep
obligatoriu cu primul caracter - care se gasesc in sirul „s2”;
Aplicatia 1:
Se citeste un sir de caractere care nu contine caractere albe. Se verifica daca sirul este alcatuit
exclusiv din caractere numerice.
#include<iostream>
#include<cstring>
using namespace std;
int main()
{char cuvint[100],cifre[]="0123456789";
cout<<"introduceti cuvintul";
cin>>cuvint;
if(strspn(cuvint,cifre)==strlen(cuvint))
cout<<"numeric";
else
cout<<"nenumeric";
return 0;}
Aplicatia 2:
Se citeste un sir de caractere care nu contine caractere albe. Se verifica daca sirul este alcatuit
exclusiv din caractere nenumerice.
#include<iostream>
#include<cstring>
using namespace std;
int main()
{char cuvint[100],cifre[]="0123456789";
cout<<"introduceti cuvintul ";
cin>>cuvint;
if (strcspn(cifre,cuvint)==10)
cout<<"corect ";
else
cout<<"incorect ";
return 0;}
13.
- strlwr - char *strlwr(char *s);
Converteste toate literele mari in litere mici.
14.
- strupr - char *strupr(char *s);
Converteste toate literele mici in litere mari.
Ex:
Cuvintul citit se tipareste cu litere mari.
#include<iostream>
#include<cstring>
using namespace std;
int main()
{char a[20];
cout<<"introduceti cuvintul ";
cin>>a;
cout<<strupr(a);
return 0;}
15.
- strpbrk - char *strpbrk(char *s1, char *s2);
Aplicatia 13:
Se citesc doua cuvinte. Se cere sa se afiseze toate caracterele primului cuvint care se regasesc
in al doilea.
#include<iostream>
#include<cstring>
using namespace std;
int main()
{char cuvint1[10], cuvint2[10],*p;
cout<<"introduceti primul cuvint";
cin>>cuvint1;
cout<<"introduceti al doilea cuvint";
cin>>cuvint2;
p=strpbrk(cuvint1,cuvint2);
while(p)
{cout<<p[0]<<endl;
p++;
p=strpbrk(p,cuvint2);}
return 0;}
16.
- atof - double atof (const char *s);
Converteste un sir catre tipul double.
17.
- _atold - long double _atold(const char *s);
Converteste un sir catre tipul long double.
18.
- atoi - int atoi(const char *s);
Convertește un sir către tipul int.
19.
- atol - long atol(const char *s);
Converteste un sir catre tipul long int.
Aplicatia 14:
Se citeste un text. Sa se calculeze suma valorilor numerice intilnite in text.
#include<iostream>
#include<cstring>
#include<stdlib.h>
using namespace std;
int main()
{char sir[1000],separator[]=" ",cifre[]="0123456789.+-",*p;
double s=0;
[Link](sir,1000);
p=strtok(sir,separator);
while(p)
{if(strspn(p,cifre)==strlen(p))
s+=atof(p);
p=strtok(NULL,separator);}
cout<<"suma numerelor intilnite in sir este"<<s;
return 0;}
20.
- ecvt - char* ecvt(double valoare, int poz, int* zec, int* semn);
- valoare - valoarea de convertit;
- poz - nr. de pozitii ocupate de sir;
- zec - adresa unei variabile de tip int, care retine nr zecimalelor pe care le are nr.;
- semn - adresa unei variabile de tip int, care are rolul de a memora –1-pt negativ si 0-pt
pozitiv.
Are rolul de a converti o valoare double catre un sir.
Ex:
#include<stdlib.h>
using namespace std;
int main()
{double numar;
int zec,semn;
char numar_sir[20]="",numar_prel[20]="";
cout<<"n= ";
cin>>numar; /*convertesc numarul si tiparesc rezultatul
asa cum este furnizat de functie*/
strcpy(numar_sir,ecvt(numar,19,&zec,&semn));
cout<<"sirul este"<<numar_sir<<" "<<zec<<" "<<semn<<endl;
//prelucrez sirul pentru a tipari rezultatul asa cum
//este asteptat de utilizator.
if(semn)
strcat(numar_prel,"-");
strncat(numar_prel,numar_sir,zec);
strcat(numar_prel,".");
strncat(numar_prel,numar_sir+zec,3);
cout<<numar_prel;
return 0;}
21.
- itoa - char *itoa(int valoare, char *sir, int baza);
Are rolul de a converti o valoare tip int in sir, care este memorat in variabila sir.
22.
- ltoa - char *ltoa (long value, char *sir, int baza);
Are acelasi efect ca „ itoa ” cu deosebirea ca se converteste catre sir o valoare de tip long int.
23.
- ultoa - char *ultoa(unsigned long value, char *sir, int baza);
Are acelasi efect ca ” itoa ” cu deosebirea ca se converteste catre sir o valoare de tip
unsigned long.
24.
- strtol - long strol(const char *s, char **endptr, int radix);
Are rolul de a converti un sir catre long.
Aplicatia 15:
#include<iostream>
#include<cstring>
#include<stdlib.h>
using namespace std;
int main()
{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;
return 0;}
25.
- strtod - double strtod(const char *s, char **endptr);
Converteste un sir catre double.
26.
- _strtold - long double _strtold(const char *(s), char **endptr);
Converteste un sir catre long double.
27.
- strtoul - unsigned long strtoul(const char *s, char **endptr, int radix);
Converteste un sir catre unsigned long.

Tipul inregistrare
Presupunem ca dorim sa prelucram date referitoare la mai multi elevi.
1. Numele - char[20];
2. Prenume - char[20];
3. Nota matematica - float;
4. Nota informatica - float;
5. Varsta - int;
In C++ exista un tip de date numit „ struct ”.
Forma generala:
struct [nume structura]
{
[<tip><nume variabila[, nume variabila, …]>];
[<tip><nume variabila[, nume variabila, …]>];
…………….
} [lista de variabile];

struct elev
{
char nume [20], prenume[20];
float nota_mate, nota_info;
int varsta;
} inr 1,inr 2; //aceste variabile pot fi declarate si: elev inr1, inr2;

Definitia structurii poate figura si in fata si in cadrul functiei „ main()”.


Se foloseste operatorul de selectie directa „ . ” pentru a avea acces la campurile variabilei de
tip „ struct ”.
Ex :
#include<iostream>
using namespace std;
struct elev
{char nume[20],prenume[20];
float nota_mate, nota_info;
int virsta;};
int main()
{elev inr;
cout<<"Nume : ";
cin>>[Link];
cout<<"Prenume : ";
cin>>[Link];
cout<<"Nota matematica : ";
cin>>inr.nota_mate;
cout<<"Nota informatica : ";
cin>>inr.nota_info;
cout<<"Virsta : ";
cin>>[Link];
cout<<"Am citit : "<<endl
<<[Link]<<" "<<[Link]<<endl
<<inr.nota_mate<<endl
<<inr.nota_info<<endl
<<[Link];
return 0;}

Inregistrari imbricate
struct elev
{
char nume[20], prenume[20];
struct
{
int clasa;
float note[20];
}situatie;
int varsta;
};
sau:
struct elev1
{
char nume[20], prenume[20];
struct
{int clasa;
float note[20];
}situatie_1, situatie_2;
int varsta;
};

Inregistrari cu structura variabila


Avem un tip de data structurata, numit „ union ”.
#include<iostream>
using namespace std;
union test
{int a;
char b[10];
double c;};
int main()
{test var;
int i;
cin>>var.c;
cout<<var.c<<endl;
cin>>var.b;
cout<<var.b;
return 0;}
Variabila „ var ” este de tip „ union ”.
Forma generala a unei uniuni este:
union [<numele uniunii>]
{<tip> <nume variabila>;

} [lista de variabile];

Inregistrarea are structura variabila, dar ocupa un numar fix de octeti.


Observatie: Uniunea poate figura oriunde in interiorul structurii.
#include<iostream>
using namespace std;
struct persoana
{char nume[30],studii;
union
{int nr_clase;
struct
{int an_t;
char oras[20];} liceu;
struct
{char nume_f[30];
int nr_ani;} facultate;} std;};
int main()
{ persoana pers;
// citirea inregistrarii
cout<<”Nume persoana “; [Link]([Link], 30);
cout<<”Studii (f-fara, g-generala, l-liceu, s-superioare)“;
cin>>[Link];
switch ([Link])
{
case ‘g’: cout<<”numar clase”; cin>>[Link].nr_clase; break;
case ‘l’: cout<<”anul terminarii liceului”; cin>>[Link].an_t;
cout<<”orasul”; [Link]();[Link]([Link],20);break;
case ‘s’: cout<<”numele facultatii”; [Link]();
[Link]([Link].nume_f, 30);
cout<<”nr ani de studiu”; cin>>[Link].nr_ani;
}
// tiparesc inregistrarea
cout<<[Link]<<endl;
switch ([Link])
{
case ‘f’: cout<<”n-are saracu studii ”;break;
case ‘g’: cout<<”numar clase”<<[Link].nr_clase;break;
case ‘l’: cout<<”a terminat liceul in ”<<[Link].an_t
<<”in orasul ”<<[Link];break;
case ‘s’: cout<<”numele facultatii”<<[Link].nume_f
<<”nr ani de studiu”<<[Link].nr_ani;
}
return 0;}
Fisiere (definire, operatii specifice)
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.
- fisiere text;
- fisiere binare.

Fisierul text
Se caracterizeaza prin:
- datele sunt memorate sub forma unei succesiuni de caractere.
- fiecare caracter este memorat prin utilizarea codului ASCII.
- un fisier text se termina cu marcajul EOF(end of line), care se genereaza cu Ctrl+Z.
- fisierul text se considera alcatuit din una sau mai multe linii, separate de marcajul newline (
\n ) care se genereaza la apasarea tastei Enter.
- o variabilă specială, numită pointer reține întotdeauna un octet al fișierului.
Schema unui fisier text:

M a r i n …..…… \n 1 H …..……. EOF

pointer

Observatii:
1. Orice editor de texte, deci si cel din C++, lucreaza cu fisiere text.
2. Pana in prezent am lucrat cu doua tipuri de fisiere text si anume cu „cin” si „cout”.
- fisierul „cin” este privit ca fiind de intrare mai precis datele sunt citite din fisierul „cin”.
- fisierul „cout” este privit ca unul de iesire, adica in loc ca progamul sa scrie intr-un fisier
memorat pe suport magnetic, acesta scrie pe monitor, deci in fisierul „cout”.
3. In / din fisierele text putem scrie sau citi cu sau fara format.
Sa presupunem ca vrem sa scriem o data reala. Daca nu precizam conditiile in care aceasta sa
fie scrisa, de exemplu, intotdeauna cu semn, chiar daca este plus, aliniata stanga sau dreapta
intr-un camp cu un numar fix de pozitii, numarul de zecimale cu care sa apara etc., atunci
spunem ca scriem fara format, altfel scriem cu format.

Citiri sau scrieri fara format


1. Citirea sau scrierea variabilelor de tip char.
a) Citirea. Se citeste primul caracter care nu este alb. După 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.
2. Citirea sau scrierea variabilelor numerice .
a) Citirea. Aceasta se face incepind din pozitia curenta a pointerului. Daca numarul este
precedat de semn acesta este luat in considerare. Eventualele caractere albe sunt sarite.
Citirea se face pana cand este intilnit un caracter care nu poate face parte din tipul respectiv.
b) Scrierea. Se face incepind cu pozitia curenta a cursorului.
1). Fie secventa:
int a; -pentru 13 se afiseaza 13.
cin>>a; -pentru bbbb14 se afiseaza 14(prin b am notat blank).
cout<<a; -pentru 117abc se afiseaza 117.
2). Fie secventa:
int a; double b;
cin>>a >>b;
cout<<a<<”bbbb”<<b;
-pentru bb17bb18.00 afiseaza 17bbbb18.
-pentru 112.55 se afiseaza 112
Variabilele reale se tiparesc cu cel mult 6 zecimale daca le au.
[Link] sau scrierea sirurilor de caractere.
a) Citirea. Incepe cu pozitia curenta a pointerului. Caracterele albe sunt sarite. Citirea se
face pana la intalnirea unui caracter alb. Sirului i se adauga caracterul nul.
b) Scrierea. Se face pana la intalnirea caracterului nul.
Exemple:
char a[10];
cin>>a;
cout<<a;
Daca se tasteaza „un text” se va afisa „un”.

Citiri sau scrieri cu format


Caracteristici:
1. width - latimea, se utilizeaza la scriere si are rolul de a stabili numarul de caractere
utilizate pentru obtinerea unei date.
2. precision - precizia, stabileste numarul de zecimale.
3. fill - caracterul de umplere, in cazul in care data propriu-zisa ocupa mai putini octeti
decat latimea.
4. left-right - alinierea la stanga sau la dreapta.
5. saltul la citire se face prin introducerea unor blankuri si apasarea tastei „Enter” inainte
de a introduce o data.
Exista cateva variabile si constante definite in „iostream”:
-int x_precision - retine numarul de zecimale ce vor fi afisate pentru un numar real;
-int x_width - retine numarul de pozitii pe care se efectueaza afisarea;
-int x_fill - retine caracterul care va fi afisat in cazul in care numarul pozitiilor pe care
se face afisarea este mai mare decat al pozitiilor ocupate;
-long x_flags - retine anumiti parametri pentru formatarea intrarilor sau iesirilor.

Pentru a usura accesul la bit s-au definit constantele:


1. skipws=0x0001 - caracterele albe care preced valoarea de citit sunt sarite.
2. left=0x0002 - datele se tiparesc aliniate la stanga;
3. right=0x0004 - datele se tiparesc aliniate la dreapta;
4. internal=0x0008 – daca spatiul unde se va afisa numarul este mai mare atunci semnul
va fi aliniat la stinga si numarul la dreapta;
5. dec=0x0010 - conversie in zecimal;
6. oct=0x0020 - conversie in octal;
7. hex=0x0040 - conversie in hexazecimal;
8. showbase=0x0080 - afisarea indicatorului de baza;
9. showpoint=0x0100 - forteaza afisarea punctului zecimal;
10. uppercase=0x0200 - in cazul afisarii in hexa, se vor utiliza literele mari; A, B…
11. showpos=0x0400 - valorile numerice sunt afisate precedate de semn.
12. scientific=0x0800 - afisarea valorilor reale se face prin utilizarea formei stiintifice(1e-
8);
13. fixed=0x1000 - afisarea valorilor reale se face prin utilizarea formei normale
(-12.45)
Accesul la variabilele si constantele respective se face cu ajutorul unor functii speciale
numite manipulatori. Pentru a le putea utiliza este necesar sa includem fisierul antet
iomanip.h.
Primii trei manipulatori se utilizeaza exclusiv pentru scrieri:
- stabilirea latimii se face cu manipulatorul setw (int); unde parametrul este latimea
propriu-zisa;
- stabilirea preciziei(nr. de zecimale) se face cu ajutorul manipulatorului
setprecision(int), unde parametrul reprezinta nr. zecimalelor.
- stabilirea caracterului de umplere se face cu ajutorul manip. setfill(char);
Ex:
#include<iostream>
#include<iomanip>
using namespace std;
int main()
{double a=0.123456789;
cout<<setw(10)<<setfill('x')<<setprecision(2)<<a;
return 0;}
Variabila „a”este afisata pe 10 pozitii, cu 2 zecimale exacte;iar pozitiile ramase
neocupate de catre data sunt scrise cu caracterul de umplere ’x’.
Accesul la continutul variabilei ”x_flags” se face printr-o „masca „ procedeu des
intalnit in programare. Masca precizeaza la care biti doresc accesul. Daca doresc accesul
la bitii „0 si 5” din 16 biti de la „0 la 15”,ideea este de a pune ”1” in masca pentru fiecare
bit la care doresc accesul si „0” pentru bitii al caror continut ramane nemodificat. Deci
avem masca:0000000000100001.
Sunt doua aplicatii posibile:
a) setare - toti vor retine „1”;
b) resetare - toti vor retine „0”;
Manipulatorul „setiosflags(masca)” are rolul de a seta bitii din masca.
Manipulatorul ,,resetiosflags(masca)” are rolul de a reseta bitii din masca.
In formarea mastii sunt foarte utile constantele care sunt puteri ale lui „2” sau ne arata ce biti
trebuie setati.
Pentru a avea acces la constante, numele lor va fi precedat de „ios ::”
Ex.
setiosflags(ios::skipws|ios::oct)
Prin setarea bitilor prin aceasta masca solicitam ca citirea sa se faca prin saltul caracterelor
albe si conversia unei valori octale.
Ex:
#include<iostream>
#include<iomanip>
using namespace std;
int main()
{int a;
cin>>setiosflags(ios::skipws)>>oct>>a;
cout<<a;
return 0;}
Se citeste o valoare numerica in baza 8. Daca introduc 23, programul va afisa 19,aceeasi
valoare, dar in baza 10. Daca introducem 87 citirea se intrerupe, pentru ca valoarea introdusa
nu este in baza 8. Continutul variabilei „a” ramane nemodificat.

Fisiere text memorate pe suport magnetic


Pentru a putea fi prelucrat, orice fisier are doua nume: numele logic si numele fizic.
[Link] logic este cel folosit in program pentru referirea fisierului.
[Link] fizic este cel sub care fisierul se gaseste memorat pe suportul extern.
In C++ sunt definite anumite constante:
- in=0x01 - fisierul se deschide pentru citire
- out=0x02 - fisierul se deschide pentru scriere
- ate=0x04 - dupa deschidere, salt la sfarsitul fisierului - „ate” = at end (la sfârșit)
- app=0x08 - deschidere pentru a scrie la sfarsitul fisierului - „app” = append (a adăuga la
final)
- trunc=0x10 - daca fisierul care se deschide exista, in locul sau se creeaza altul - „trunc” =
truncate (a tăia, a curăța)
- nocreate=0x20 - deschide fisierul doar daca acesta exista.
- noreplace=0x40 - daca fisierul exista, el poate fi deschis doar pentru consultare.
- binary=0x80 - deschide fișierul în mod binar (nu text).
Orice program care lucreaza cu fisiere pe suport magnetic trebuie sa includa in prealabil
fisierul antet „fstream”.
#include<fstream>
Dupa includerea acestui fisier este inutila includerea fisierului „iostream.h”

Declararea fisierelor text memorate pe suport magnetic


Forma generala de declarare este:
fstream nume_logic(char* nume_fizic, int mod_de_deschidere);
Ex:
[Link] un fisier text in directorul radacina in vederea crearii lui cu numele fizic
„[Link]”, iar logic este “f”
fstream f („c:\\[Link]”, ios::out);
[Link] un fisier text cu numele fizic „[Link]”si numele logic „g”.A fost declarat in
vederea citirii, deci el trebuie sa existe pe suport. Daca nu e precizata calea rezulta ca este in
directorul curent.
fstream g(„[Link]”, ios::in);
[Link] doua fisiere, unul pentru consultare, altul pentru scriere.
fstream f(„c:\\[Link]”, ios::in), g(„c:\\[Link]”, ios::out);
[Link] un fisier pentru a scrie la sfarsitul său. De la inceput pointerul va fi plasat dupa
ultimul caracter pe care il contine. In absenta acestui parametru pointerul va fi pozitionat pe
primul caracter al sirului ,adica cel de indice „0”.
fstream f(„c:\\[Link]”, ios::app);
[Link](); - inchiderea fisierului.
Prelucrarea fisierului 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().
[Link] următor creeaza un fisier text cu intrarea de la tastatura. Nu sunt scrise
caracterele albe introduse de la tastatura.
#include<iostream>
#include<fstream>
using namespace std;
int main()
{fstream f("c:\\[Link]",ios::out);
char ch; //EOF adica ctrl+Z pt term citire
while(cin>>ch)
f<<ch;
[Link]();
return 0;}
[Link] fel ca mai sus, numai ca nu sunt sarite caracterele albe citite de la tastatura.
#include<iomanip>
#include<iostream>
#include<fstream>
using namespace std;
int main()
{fstream f("c:\\[Link]",ios::out);
char ch;
while (cin>>resetiosflags(ios::skipws)>>ch)
f<<ch;//doar citire si EOF adica ctrl+Z pt term citire
[Link]();
return 0;}
[Link] pe monitor fisierul creat la exemplul 2, fara caracterele albe.
#include<iostream>
#include<fstream>
using namespace std;
int main()
{fstream f("c:\\[Link]",ios::in);
char ch;
while (f>>ch)
cout<<ch;
[Link]();
return 0;}
[Link] pe monitor fisierul creat la ex.2, cu toate caracterele,inclusiv cele albe.
#include<iomanip>
#include<iostream>
#include<fstream>
using namespace std;
int main()
{fstream f("c:\\[Link]",ios::in);
char ch;
while (f>>resetiosflags(ios::skipws)>>ch)
cout<<ch;
[Link]();
return 0;}
[Link] la sfirsitul fisierului creat la ex.2 alte caractere, inclusiv cele albe citite de la tastatura.
#include<iomanip>
#include<iostream>
#include<fstream>
using namespace std;
int main()
{ofstream f("c:\\[Link]",ios::app);
char ch;//doar citire si EOF adica ctrl+Z pt term citire
while (cin>>resetiosflags(ios::skipws)>>ch)
f<<ch;
[Link]();
return 0;}
[Link] fisierului „[Link]” este copiat in fisierul „[Link]”. Sunt copiate si caracterele
albe.
#include<iomanip>
#include<fstream>
using namespace std;
int main()
{fstream f("c:\\[Link]",ios::in),g("c:\\[Link]",ios::out);
char ch;
while(f>>resetiosflags(ios::skipws)>>ch)
g<<ch;
[Link]();
[Link]();
return 0;}

Fisiere binare
- Fisierele sunt alcatuite din mai multe inregistrari de acelasi tip. Din acest motiv se mai
numesc si cu tip:
o Inregistrarile sunt de tip „int”;
o Inregistrarile sunt de un tip descris cu „struct”;
- Datele sunt memorate in format intern, in cod complementar.
- Fisierele binare se termina cu „EOF”ca si cele text.
[Link] 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<iostream>
#include<fstream>
using namespace std;
struct persoana
{char nume[30];
int varsta;};
int main()
{fstream f("c:\\[Link]", ios::out|ios::binary);
persoana p, *adr_p=&p;
char x;
int n,i;
cout<< "numarul de persoane";cin>>n;[Link]();
for(i=0;i<n;i++)
{cout<<"Numele";[Link]([Link],30);[Link](x);
cout<<"Varsta";cin>>[Link];[Link](x);
[Link]((char*)adr_p,sizeof (p));}
[Link]();
return 0;}
- intrucat lucram cu un fisier binar il vom deschide cu binary;
- am deschis structura corespunzatoare si am declarat variabila „p”, de acelasi tip;
- variabile de tip pointer adr_p este inițializată cu adresa variabilei „p”;
- dupa citirea variabilei „p”, urmeaza scrierea acesteia in fisier. Deci folosim functia ”write”
care scrie ”n” caractere ale unui sir și nu insereaza caracterul nul. Ea are forma: write(char*
nume, int n);
- „Pacalim” calculatorul sa scrie, de fapt continutul variabilei „p”, astfel: 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…
[Link] se listeze fisierul creat anterior
Functia ”eof()” nu ne ajuta.
Vom folosi functia ”read”: read(char* nume,int n);
- si citeste cele „n” caractere ale unui fisier intr-o variabila de tip vector caractere.
#include<iostream>
#include<fstream>
using namespace std;
struct persoana
{char nume[30];
int varsta;};
int main()
{persoana p, *adr_p=&p;
fstream g("c:\\[Link]", ios::in|ios::binary);
while(g. read((char*) adr_p, sizeof(p)))
cout<<[Link]<<" "<<[Link]<<endl;
[Link]();
return 0;}
3. Sa se scrie un program care adauga la sfarsitul fisierului creat la exs.1, alte „n”
inregistrari. Deosebirea dintre cele doua programe va fi ca la declararea fisierului, in loc de
ios::out, se scrie ios::app.
-În prelucrarea fisierului binar se folosesc urmatorii termeni:
- creare;
- exploatare;
- actualizare: -stergere;
-modificare;
-adaugare.

POINTERI
1.1 NOTIUNEA DE ADRESA A UNEI VARIABILE
Am invatat faptul ca memoria interna poate fi privita ca o succesiune de octeti. Pentru
a-i distinge acestia sunt numerotati. Numarul de ordine al unui octet se numeste adresa lui.
Orice variabila ocupa un nr de octeti succesivi. De exemplu, o variabila de tip int
ocupa doi octeti (in varianta BORLAND C++ 3.0). Adresa primului octet al variabilei se
numeste adresa variabilei.
Observatii:
-Nu trebuie confundata adresa unei variabile cu valoarea pe care aceasta o memoreaza.
-Uneori, in loc de adresa a unei variabile vom folosi termenul pointer!
-In unele cazuri, adresa unei variabile se scrie sub o forma mai complicata: adrese liniare.

1.2 VARIABILE DE TIP POINTER


Variabilele de tip pointer se caracterizeaza prin faptul ca valorile pe care le pot
memora sunt adrese ale altor variabile. Limbajul C++ face distinctie intre natura adreselor
care pot fi memorate. Astfel, exista adrese ale variabilelor de tip int(ocupa 2 octeti) dacă se
menționează explicit că este vorba de Borland C++ 3.0; altfel (ocupă 4 octeți pe majoritatea
platformelor moderne), adrese ale variabilelor de tip float(ocupa 4 octeti), adrese ale
variabilelor de tip char(ocupa 1 octet), etc.
O astfel de variabila capabila sa retina adrese se declara astfel:
tip *nume
Exemple:
- Variabile de tip pointer catre variabile de tip int. Variabilele adr1, adr2, pot retine adrese ale
variabilelor de tip int:
int *adr1, *adr2;
- Variabila de tip pointer catre variabile de tip float. Variabila „adresa”, poate retine adrese
ale variabilelor de tip float.
float *adresa;
- Variabile de tip pointer catre variabile de tip „elev”, care la randul lor sunt de tip ”struct”.
Variabilele ”a” si ”b”, pot retine adrese ale variabilelor de tipul ”elev”.
struct elev
{char nume[20], prenume[20];
float nota_mate, nota_info;
int varsta;};
elev *a, *b;
Caracterul ’*’ poate fi plasat in mai multe feluri, dupa cum se observa:
int* adr1;
int * adr1;
int *adr1;
În C/C++, spațiile din jurul caracterului * nu influențează tipul variabilei. Toate exemplele
de mai sus sunt echivalente.
- Pentru a declara mai multe variabile de acest tip, caracterul ’*’ se trece de fiecare data:
int *adr1, *adr2, *adr3;
- O declaratie de genul: int* adr1, adr2; are semnificatia ca adr1 este de tip pointer catre
int, in vreme ce adr2 este de tip int.
Adresa unei variabile se obtine cu ajutorul operatorului de referentiere ’&’, care
trebuie să precedă numele variabilei:
&Nume_variabila;
Exemplu: adr1:=&numar variabilei adr1 i se atribuie adresa variabilei numar.
- Fiind data o variabila de tip pointer catre variabile de un anume tip, care memoreaza o
adresa a unei variabile de acel tip, pentru a obtine continutul variabilei a carei adresa este
memorata se utilizeaza operatorul unar ‚ * ’, numit si operator de dereferentiere.
Exemple:
- Programul următor declara o variabila de tip int numita a si o variabila de tip int*, numita
adra si o variabila de tipul int** numita [Link] 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 afiseaza continutul variabilei a (7) pornind de la adresa ei
(retinuta de adra ) si pornind de la adresa variabilei adra (retinuta de adradra).
#include<iostream>
using namespace std;
int main()
{int a=7, *adra=&a, **adradra=&adra;
cout<<*adra<<" "<<**adradra;
return 0;}

Adresa
7 variabilei
a
a adra adradra
- In programul următor variabila a, de tip elev este inițializată, iar variabila adra de tip
pointer catre variabila de tip elev este inițializată cu adresa variabilei a. Programul tipareste
de doua ori continutul variabilei a.
#include<iostream>
#include<cstring>
using namespace std;
struct elev
{char nume[20],prenume[20];};
int main()
{elev a,*adra=&a;
strcpy([Link],"carolina");
strcpy([Link],"florin");
cout<<(*adra).nume<<"+"<<(*adra).prenume<<endl;
cout<<adra->nume<<"+"<<adra->prenume;
return 0;}
- Observati modul in care am obtinut continutul unui camp al variabilei a, pornind de la
pointerul catre a: (*adra).nume. De ce este nevoie de paranteze?
Operatorul ’.’ - numit operator de selectie, are prioritatea 1, deci maxima.
Operatorul ’*’ - unar, numit si operator de dereferentiere, are prioritatea 2, deci mai mică.
Prin urmare in absenta parantezelor rotunde, se incearca mai intai evaluarea expresiei
[Link], expresie care n-are sens!
Parantezele schimba ordinea de evaluare, se evalueaza mai intai *adra, expresie care are
sens.
- Pentru astfel de selectie in loc sa folosim trei operatori se poate unul singur, operatorul de
selectie indirecta: ’->’. Acesta acceseaza un camp al unei structuri pornind de la un pointer
(adresa) catre acea structura. El are prioritate maxima. Astfel, in loc sa scriem (*adra).nume,
vom scrie mai simplu, adra->nume.
- In progamul următor, variabilei a i se atribuie valoarea 3. Variabilei adr i se atribuie adresa
variabilei a. Variabila a este incrementata, pornind de la continutul variabilei adr. Se
afiseaza de doua ori continutul variabilei a, pornind de la adresa retinuta de adr si de numele
ei.
#include<iostream>
#include<cstring>
using namespace std;
int main()
{int a=3, *adr=&a;
++*adr;
cout<<*adr<<" "<<a;}
Observatii:
-Intre variabile de tip pointer sunt permise atribuiri doar in cazul in care au acelasi tip pointer
(retin adrese catre acelasi tip de variabile).
Exemplu:
int *adr1, *adr2;
float *adr3;
Atribuirea adr1=adr2 este corecta.
Atribuirea adr3=adr2 nu este corecta.
In acest caz se utilizeaza operatorul de conversie explicita. De aceasta data, pentru ex.
anterior, atribuirea: adr3=(float*)adr2 este corecta.
Programul următor tipareste continutul primului octet al unei variabile de tip int.
#include<iostream>
using namespace std;
int 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 intreg. Pentru. afisare, am folosit
din nou operatorul de conversie explicita (pt ca data sa nu fie afisata ca un caracter).
Mai elegant, aceeasi secventa poate fi scrisa si asa:
{int a=3;
char *adr_char=(char*)&a;
cout<<(int)*adr_char;}
RETINETI: octetii unei variabile numerice sunt retinuti in memorie in ordine inversa.
- In C++ exista si tipul pointer catre variabile de orice tip numit void*.
Unei variabile de tip void* ii putem atribui continutul oricarei variabile de tip pointer, dar
invers nu este posibil.
Ex:
In conditiile:
int a=3, *adr_int =&a;
void *adr_gen;
Atribuirea: adr_gen=adr_int ; este corecta.
Atribuirea: adr_int=adr_gen ; nu este corecta.
Programul următor afiseaza in binar continutul primului octet al unei variabile de tip int.
#include<iostream>
using namespace std;
int main()
{int a=11,j;
char *adr_byte=(char*)&a, masca=128;
cout<<endl;
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 variabila numita masca, retine 128 adica 10000000 in binar. Ea foloseste pt efectuarea
intersectiei logice intre continutul octetului si masca. Daca bitul aflat pe prima pozitie este 0,
programul va tipari 0, iar daca acelasi bit este 1, programul va tipari 1. Dupa fiecare
intersectie logica intre continutul octetului care trebuie afisat se deplaseaza catre stanga, pt a
putea afisa continutul urmatorului bit.

1.3 OPERATII CU VARIABILE DE TIP POINTER


Fie o variabila a, de tip pointer catre tipul x si n un nr. natural. Atunci au sens
operatiile a+n si a-n. Ce semnificatie au?
- a+n reprezinta adresa care se obtine ca suma intre adresa lui a si n inmultit cu nr. de octeti
care sunt ocupati de o variabila de tipul x.
- a-n reprezinta adresa care se obtine ca diferenta intre adresa lui a si n inmultit cu nr de
octeti care sunt ocupati de variabila de tipul x.
EX: daca a este o variabila de tip int si tipul int ocupa 2 octeti, atunci a+3 reprezinta
adresa octetului care se obtine ca suma intre adresa primului octet al variabilei a si 6.
Progamul următor listeaza in binar continutul unei variabile de tip int numita a.
#include<iostream>
using namespace std;
int 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;}
cout<<endl;
adr_byte++;}}
Initial, adr_byte retine adresa primului octet. Continutul acestuia este afisat in binar.
Apoi se obține adresa următorului octet în adr_byte. Intrucat adr_byte este de tip pointer
către char, iar tipul char ocupă un octet, prin faptul ca se aduna 1 la adr_byte, se obține
adresa următorului octet.
Subprograme
- Definire
- Evaluarea si transmiterea parametrilor
- Specificarea parametrilor unui subprogram
- Domeniul de vizibilitate a indentificatorilor
- Apel

Prin subprogram vom intelege un ansamblu alcatuit din tipuri de date, variabile si
instructiuni scrise in vederea unei anumite prelucrari (calcule, citiri, scrieri) si care poate fi
utilizat (rulat) doar daca este apelat de un program sau de alt subprogram.
- In C++, subprogramele sunt de tip functie.
- Parametri care se utilizeaza in antetul functiei se numesc parametri formali.
- Parametri care se utilizeaza la apel se numesc parametri efectivi.
Ex 1: Sa se calculeze E1=1+1/2+1/3+...+1/n.
#include<iostream>
using namespace std;
double subp(int n)
{double s=0;
int i;
for (i=1;i<=n;i++)
s+=1./i;
return s;}
int main()
{int n;
cout<<"n= " ;
cin>>n;
cout<<subp(n);}
Ex 2: Sa se calculeze E2=(1+1/2+1/3+...+1/n)n.
#include<iostream>
using namespace std;
double subp(int n)//n – parametru formal
{double s=0;
int i;
for(i=1;i<=n;i++)
s+=1./i;
return s;}
int main()
{int n,i;
double rez,prod=1;
cout<<"n= ";
cin>>n;
rez=subp(n);// n- parametru efectiv
for(i=1;i<=n;i++)
prod*=rez;
cout<<prod;}
Ex 3: Sa se tipareasca vectorul sortat.
#include<iostream>
using namespace std;
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!=0);}
void scriu(int vt[10],int n)
{int i;
for(i=0;i<n;i++)
cout<<vt[i];}
int main()
{int vt[10],n;
cout<<"n= ";
cin>>n;
citesc (vt,n);
sortez (vt,n);
scriu (vt,n);}

Structura functiilor si apelul lor


O functie este alcatuita din:
I. antet - contine informatii necesare compilatorului, numele functiei, lista parametrilor
formali, tipul rezultatului.
tip nume( lista parametrilor formali)

parametru1, parametru2, …, parametrun


Sau fara parametrii:
tip nume
II. o instructiune compusa – aceasta cuprinde declaratiile variabilelor locale si instructiunile
propriu-zise.
 Poate fi tip al unei functii orice tip de data cu exceptia masivelor. Tablourile
sunt structuri de date omogene cu un numar finit si cunoscut de elemente, ce
ocupa un spatiu contiguu de memorie. Vectorii sunt tablouri unidimensionale.
 Daca tinem neaparat, exista posibilitatea ca functia sa intoarca tablouri, daca
acestea sunt inglobate in tipuri declarate cu „struct”, ca in exemplul următor:
#include <iostream>
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++)
cin>>[Link][i][j];
return a;};
int main()
{int i,j;
mat mtr=cit (3,2);
for(i=0;i<3;i++)
for(j=0;j<2;j++)
cout<<[Link][i][j]<<” ” ;}
- O functie returneaza rezultatul la intalnirea instructiunii „return”, care are forma:
return expresie;
- tipul expresiei trebuie sa coincida cu tipul functiei
- o functie poate fi apelata de sine statator (prin nume si lista parametrilor efectivi), dar poate
fi inclusa si in cadrul expresiilor, caz in care, la evaluarea expresiei este apelata.
Nu este valabil in cazul functiilor de tip void.
#include<iostream>
using namespace std;
int prod(int x, int y)
{return x*y;}
int main()
{int x=2, y=3;
cout<<1+prod(2, 3);
return 0;}

DECLARAREA VARIABILELOR

Variabilele declarate in corpul functiilor, inclusiv in cel al functiei „main ( )”, se numesc
variabile locale.
- O variabila se caracterizeaza prin 4 atribute:
1. Clasa de memorare
2. Vizibilitate
3. Durata de viata
4. Tipul variabilei
1. Clasa de memorare - precizeaza locul unde este memorata variabila respectiva.
Sistemul de operare aloca fiecarui program trei zone distincte in memoria interna in care
se gasesc memorate variabilele programului.
O variabila poate fi memorata in:
-segmentul de date ;
-segmentul de stiva ;
-in Heap ;
-intr-un registru al microprocesorului ;
2. Vizibilitatea – precizeaza liniile textului sursa din care variabila respectiva poate fi
accesata .
Astfel avem :
a. Vizibilitate la nivel de bloc ( instructiune compusa );
b. Vizibilitate la nivel de fisier – in cazul in care programul ocupa un singur fisier
sursa;
c. Vizibilitatea la nivel de clasa – este in legatura cu programarea pe obiecte.
3. Durata de viata – reprezinta timpul in care variabila respectiva are alocat spatiu in
memoria interna .
Astfel avem :
a. Durata statica – variabila are alocat spatiu in tot timpul
executiei programului.
b. Durata locala – variabila are alocat spatiu in timpul in
care se executa instructiunile blocului respectiv.
c. Durata dinamica – alocarea si dezalocarea spatiului
necesar variabilei respective se face de catre programator prin operatori sau functii
speciale.
VARIABILE GLOBALE
Se declara in afara corpului oricarei functii ca in exemplul următor:
#include<iostream>
using namespace std;
int a;//este inițializată implicit cu zero.
int t()
{a=3;
cout<<a;}
int b;//este inițializată implicit cu zero.
int main()
{b=4;
cout<<a<<endl;
cout<<b<<endl;
t();
return 0;}
Variabilele „a” si „b” sunt globale - pot fi utilizate de toate functiile care urmeaza in textul
sursa declaratiei variabilei respective.
- la declarare, variabilele globale sunt initializate cu „0”.
Atributele variabilelor globale sunt:
1. Clasa de memorare - segmentul de date;
2. Vizibilitatea - in cazul in care declaratiile acestora sunt inaintea tuturor functiilor,
acestea sunt vizibile la nivelul intregului program (fisier);
De exemplu ”a” poate fi accesata din toate functiile, iar ”b” numai din „main()”.
3. Durata de viata a variabilelor globale este statica. Au spatiu rezervat in tot timpul
executiei programului.
VARIABILE LOCALE
Sunt declarate in corpul functiilor.
1. Clasa de memorare - segmentul de stiva;
E posibil ca acestea sa fie alocate in registrele microprocesorului, caz in care declaratia lor
trebuie precedata de cuvantul cheie „Register” - Register int b=4;
- variabilele locale nu sunt initializate implicit cu „0”.
2. Vizibilitatea variabilelor locale este la nivelul blocului la care au fost declarate.
3. Durata de viata a variabilelor locale este atata timp cat dureaza executia blocului
respectiv.
#include<iostream>
using namespace std;
int a;
void t()
{int a=4;
{int a=3;
cout<<a<<endl;}
cout<<a<<endl;}
int main()
{a=5;
t();
cout<<a;
return 0;}

TRANSMITEREA PARAMETRILOR

Parametrii care se gasesc in antetul functiei se numesc parametri formali, iar cei din
instructiunea de apel se numesc parametrii efectivi.
Ex: acest program contine o functie care calculeaza suma a doua numere naturale.
#include<iostream>
using namespace std;
int suma(int a, int b)
{return a+b;}
int main()
{int c=4,d=3;
cout<<suma(2,3)<<endl;
cout<<suma(2+7,3-1*2)<<endl;
cout<<suma(c,d)<<endl;
cout<<suma(1.9,3.3)<<endl;
return 0;}
- a si b sunt parametri formali
- Parametri efectivi sunt : 2, 3
2+7, 3-1*2;
c, d;
1.9, 3.3;
- Numarul parametrilor formali trebuie sa coincida cu numarul parametrilor efectivi. Exista si
o exceptie.
- Tipul parametrilor formali trebuie sa coincida cu tipul parametrilor efectivi.
- Pentru memorarea parametrilor subprogramele folosesc segmentul de stiva, pentru
variabilele locale.
- Memorarea parametrilor transmisi se face de la stanga la dreapta.
Exista doua mecanisme de transmitere a parametrilor, transmiterea prin valoare si
transmiterea prin referinta:
A. Transmiterea prin valoare se utilizeaza atunci cand suntem interesati ca subprogramul sa
lucreze cu acea valoare, dar in prelucrare, nu ne intereseaza ca parametrul efectiv (cel din
blocul apelant) sa retina valoarea modificata in subprogram.
Se pot transmite prin valoare:
1. Valorile retinute de variabile;
2. Expresii (acestea pot contine si functii).
1. Valorile retinute de variabile. In acest caz, parametrii efectivi trebuie sa fie numele
variabilelor.
Ex:
#include<iostream>
using namespace std;
void test(int n)
{n+=1;
cout<<n<<endl;}
int main()
{int n=1;
test(n);
cout<<n<<endl;
return 0;}
Parametrul n este transmis prin valoare.
 In main(), avem declarata variabila n, care este inițializată cu 1.
 Apelam functia. La apel, se rezerva spatiu in stiva, spatiu care are numele
parametrului (deci tot n) si este initializat cu valoarea memorata de variabila n a
programului principal. In acest moment avem doua variabile n si ambele retin
valoarea 1
 In functie, variabila n este incrementata(adica la vechiul continut se adauga 1).
Evident, este vorba de variabila rezervata in cadrul ei.
 Tiparim continutul variabilei n (cea din stiva), deci se tipareste 2.
 La iesirea din functie, variabila n( din stiva) se pierde-adica nu mai are spatiu
alocat. Prin urmare, valoarea 2 este pierduta.
 In main()se tipareste continutul variabilei n, adica 1.
2. Parametri efectivi sunt expresii, care mai intai se evalueaza.
Ex:
#include<iostream>
using namespace std;
void test (int n)
{cout<<n<<endl;}
int main()
{test(3);
test(3+4*5);
return 0;}
In functie se creeaza o variabila, retinuta in segmentul de stiva, numita n, care la
primul apel retine valoarea 3 si la al doilea valoarea 23. La iesirea din functie continutul
acestei variabile se pierde.
Asa cum am aratat, transmiterea parametrilor prin valoare se utilizeaza atunci cand nu
ne intereseaza ca, la intoarcerea din subprogram, parametrul efectiv sa retina valoarea
modificata acolo. Intrebare: daca n-ar exista decat transmiterea prin valoare, ar fi posibil sa
modificam valoarea anumitor variabile care sunt declarate in blocul apelant? Raspunsul este
afirmativ, daca lucram cu variabile de tip pointer. Functia intersc interschimba valorile
obtinute de doua variabile ale programului principal. Ea primeste ca parametri adresele celor
doua variabile.
#include<iostream>
using namespace std;
void intersc (int *x,int *y)
{int man;
man=*x;*x=*y;*y=man;}
int main()
{int a=2,b=3;
intersc(&a,&b);
cout<<a<<" "<<b;
return 0;}
Rețineți acest mecanism! El este general. Știați că, în limbajul C, acesta este singurul
mecanism de transmitere al parametrilor?
 Tablourile NU se transmit prin valoare în C / C++.
 Ele se degradează la pointer către primul element, deci sunt transmise efectiv prin
referință (adresă). De ce? Așa cum am învățat, numele tabloului este un pointer către
componentele sale. Prin valoare se transmite acest nume; cu ajutorul lui putem accesa
componentele tabloului.
In programul următor functia vector initializeaza vectorul transmis ca parametru, iar in
main()se afiseaza rezultatul.
#include<iostream>
using namespace std;
void vector(int x[10])
{for(int i=0;i<10;i++)
x[i]=i;}
int main()
{int a[10];
vector(a);
for(int i=0;i<10;i++)
cout<<a[i]<<" ";
return 0;}
B. Transmiterea prin referinta. Parametri sunt transmisi prin referinta atunci cand ne
intereseaza ca la revenirea din subprogram variabila transmisa sa retina valoarea stabilita in
timpul executiei subprogramului.
 In cazul transmiterii prin referinta parametri formali trebuie sa fie referinte la variabile
ale parametrilor actuali(efectivi) (vezi tipul referinta prezentat prin capitolul anterior).
 In cazul transmiterii prin referinta subprogramul retine in stiva, adresa variabilei
In acest caz ne putem intreba care este mecanismul prin care, desi pentru o variabila
transmisa se retine adresa ei, in subprogram putem adresa variabila normal (nu indirect)? La
compilare, orice referinta la variabila respectiva, este ”tradusa” ca adresare indirecta.
Programul următor utilizeaza o functie care interschimba valorile retinute de doua
variabile. Acestea sunt transmise prin referinta.
#include<iostream>
using namespace std;
void intersc(int &a, int &b)
{int man=a;a=b;b=man;}
int main()
{int x=2,y=3;
intersc(x,y);
cout<<x<<" "<<y;
return 0;}
Definirea si declararea unui subprogram
 A defini un subprogram inseamna a-l scrie efectiv, dupa structura anterior prezentata.
O problema importanta este locul unde se defineste subprogramul.
 A declara un subprogram inseamna a-l anunta. Un subprogram nedeclarat nu poate fi
folosit. Definitia unui subprogram tine loc si de declaratie!
Programul următor contine doua functii: s1 si s2. Definitiile ambelor functii se gasesc
inaintea functiei main(). Din acest motiv ele pot fi apelate din main(). Definitia functiei s1
este inaintea definitiei lui s2, deci functia s1 poate fi apelata din s2. In schimb, din s1 nu
poate fi apelata functia s2, pentru ca definitia lui s2 este dupa cea a lui s1.
#include<iostream>
using namespace std;
void s1()
{cout<<"Eu sunt s1"<<endl;}
void s2()
{s1();
cout<<"Eu sunt s2"<<endl;}
int main()
{s1();s2();}
In situatia prezentata, se poate, totusi, ca s1 sa apeleze pe s2, chiar daca sunt definite in
aceeasi ordine.
In astfel de cazuri se foloseste prototipul functiei (antetul urmat de ’;’). Prototipul are
rolul de a declara o functie. El nu contine definitia acestuia.
#include<iostream>
using namespace std;
void s2();
void s1()
{s2();
cout<<"Eu sunt s1"<<endl;}
void s2()
{cout<<"Eu sunt s2"<<endl;}
int main()
{s1();
return 0;}
 Programatorii c++ obisnuiesc sa scrie mai intai prototipurile tuturor functiilor utilizate
de program -fara main()- iar dupa functia main() sa le defineasca. In acest fel orice
functie-mai putin main()-poate fi apelata prin oricare alta:
#include<iostream>
using namespace std;
void s1();//prototip s1
void s2();//prototip s2
int main()
{s1();
return 0;}
void s1()
{s2();
cout<<"Eu sunt s1"<<endl;}
void s2()
{cout<<"Eu sunt s2"<<endl;}
Aplicatii care folosesc subprograme
Ex 1: Sa se tipareasca cmmdc si cmmmc a doua nr.
#include<iostream>
using namespace std;
int cmmdc(int m,int n)
{while (m!=n)
if (m>n)
m-=n;
else
n-=m;
return m;}
int main()
{int cm,m,n;
cout<<"m=";
cin>>m;
cout<<"n=";
cin>>n;
cm=cmmdc(m,n);
cout<<"cmmdc="<<cm<<" "<<"cmmmc="<<m*n/cm;}
Ex 2: Sa se tipareasca cmmdc al valorilor retinute de un vector.
#include<iostream>
using namespace std;
int v[9],n;
int cmmdc(int m,int n)
{while(m!=n)
if (m>n)
m-=n;
else
n-=m;
return m;}
int 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;}
Ex 3: Sa se tipareasca toate nr palindrom(121) aflate intre m si n.
#include<iostream>
using namespace std;
int palin(int i)
{int isalv=i,iinv=0;
while(i!=0)
{iinv=iinv*10+i%10;
i=i/10;}
return isalv==iinv;}
int main()
{int m,n,i;
cout<<"m=";
cin>>m;
cout<<"n=";
cin>>n;
for(i=m;i<n;i++)
if (palin(i)!=0)
cout<<i<<endl;}
Ex 4: Sa se tipareasca toate nr superpalindrom(121)2 aflate intre m si n.
#include<iostream>
using namespace std;
long palin(long i)
{long isalv=i,iinv=0;
while (i!=0)
{iinv=iinv*10+i%10;
i=i/10;}
return isalv==iinv;}
int main()
{long m,n,i;
cout<<"m=";
cin>>m;
cout<<"n=";
cin>>n;
for(i=m;i<n;i++)
if(palin(i)!=0)
if(palin(i*i)!=0)
cout<<i<<" "<<i*i<<endl;}
Ex 5: Programul complet cu functiile de citire si afisare a datelor unui elev este urmãtorul:
#include<iostream>
using namespace std;
typedef struct
{char nume[20];
char prenume[20];
float media;}Elev;
void citire_elev(Elev &e)
{cout<<"Nume: "; cin>>[Link];
cout<<"Prenume: "; cin>>[Link];
cout<<"Media: "; cin>>[Link];}
void afisare_elev(Elev e)
{cout<<"Nume: "; cout<<[Link]<<endl;
cout<<"Prenume: "; cout<<[Link]<<endl;
cout<<"Media: "; cout<<[Link]<<endl;}
int main()
{ Elev e;
citire_elev(e);
afisare_elev(e);
return 0;}
Ex 6: Programul complet cu functiile de citire si afisare a datelor elevilor cu struct imbric
este urmãtorul:
#include<iostream>
using namespace std;
typedef struct
{char nume[20];
char prenume[20];
float media;}Elev;
void citire_elevi(Elev e[],int &n)
{int i;
cout<<"n="; cin>>n;
for(i=1;i<=n;i++)
{cout<<"Nume: "; cin>>e[i].nume;
cout<<"Prenume: "; cin>>e[i].prenume;
cout<<"Media: "; cin>>e[i].media;}}
void afisare_elevi(Elev e[],int n)
{int i;
for(i=1;i<=n;i++)
{cout<<"Nume: "; cout<<e[i].nume<<endl;
cout<<"Prenume: "; cout<<e[i].prenume<<endl;
cout<<"Media: "; cout<<e[i].media<<endl;}}
int main()
{Elev e[31];
int n;
citire_elevi(e,n);
afisare_elevi(e,n);
return 0;}

STRUCTURI DE DATE
STRUCTURA DE TIP LISTA LINIARA

Definiție

O listă liniară este o colecție de n≥0n \ge 0n≥0 noduri X1,X2,…,XnX_1, X_2, \dots, X_nX1,X2,…,Xn aflate
într-o relație de ordine. Operațiile permise sunt:

 accesul la orice nod pentru citire sau modificarea informației sale;


 adăugarea unui nod, indiferent de poziția sa;
 ștergerea unui nod;
 schimbarea poziției unui nod în cadrul listei.
Tipuri de alocare

 Alocare secvențială: lista este stocată într-un vector. Operațiile necesită un efort mare de calcul
pentru inserții sau ștergeri.
 Alocare înlănțuită: fiecare nod conține un pointer către următorul, facilitând inserția și ștergerea
rapidă a nodurilor.

Liste liniare alocate simplu înlănţuit

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

In1 adr2 In2 adr3 Inn 0

adr1 adr2 … adrn

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


 adr1, adr2, adr3, ..., adrn reprezintă adresele celor n înregistrări;
 in1, in2, ..., inn reprezintă informaţiile conţinute de noduri, de altă natură decât cele
de adresă;
 0 - 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.
1. Accesul la un nod al listei se face prin parcurgerea nodurilor care îl preced. Aceasta
necesită un efort de calcul.
2. Informaţiile de adresă sunt prezente în cadrul fiecărui nod, deci ocupă memorie.
3. Avantajele alocării înlănţuite sunt date de faptul că operaţiile de adăugare sau eliminare
ale unui nod se fac rapid.
Rămâne de stabilit modul în care implementăm listele liniare alocate înlănţuit. În linii
mari, există două modalităţi: alocarea statică și alocarea dinamică. Pe scurt, în alocarea
statică, atât datele propriu-zise asociate nodurilor (altele decât cele de adresă) cât și datele
de adresă sunt memorate cu ajutorul vectorilor, iar aceștia sunt alocaţi în segmentul de
date. În cazul alocării dinamice, toate datele sunt alocate în Heap.

Liste alocate înlănțuit prin utilizarea vectorilor(alocare statică)


Ex: Modul de lucru cu o listă liniară simplu înlănțuită.
#include<iostream>
using namespace std;
typedef int adresa;
struct nod
{int info;
adresa adr_urm;};
nod L[1000];
int nr_elem;
adresa ocupat[1000], v, sf;
int Exista_spatiu()
{return nr_elem<1000;}
void Aloca(adresa& x)
{adresa i=1;
while(ocupat[i]) //daca nu exista este zero atunci e liber
i++;
x=i;
ocupat[i]=1; //se marcheaza cu 1 noul spatiu alocat
nr_elem++;}
void Elibereaza(adresa x)
{ocupat[x]=0;
nr_elem--;}
void adaugare(adresa& v, int val)
{ adresa c;
if(!v) //daca nu exista element in lista
{Aloca(v); //aloca element in lista
L[v].info=val; // valoarea elementului
L[v].adr_urm=0; //si adresa elementului
sf=v;}//ultimul element este 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 de spatiu";}
void Inserare_dupa(adresa& v, int val, int val1) //val1 este
valoarea ce urmeaza a fi inserata
{adresa c,d; //elementul c dupa care se insereaza
if(Exista_spatiu())//elementul d
{c=v; //initializez pe c=v primul element
while(L[c].info!=val) // cit timp L[c].info nu retine
//valoarea val a lui v
c=L[c].adr_urm; //c este urmatorul element cu adresa
Aloca(d); //urmatoare pina il gaseste pe val
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";}
void Inserare_inainte(adresa& v,int val, int val1)
{adresa c,d;//elementul c inaintea caruia se insereaza
if(Exista_spatiu()) //elementul d
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)// cit timp L[adresa
nodului următor].info nu retine valoarea val a lui v
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";}
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);}
void Listare(adresa& v)
{adresa c=v;
while(c)
{cout<<L[c].info<<" ";
c=L[c].adr_urm;}
cout<<endl;}
int main()
{int i;
for(i=1;i<=10;i++)
adaugare(v,i);Listare(v);
Inserare_dupa(v,1,15);Listare(v);
Inserare_dupa(v,10,25);Listare(v);
Inserare_dupa(v,25,26);Listare(v);
Inserare_inainte(v,1,44);Listare(v);
Inserare_inainte(v,3,43);Listare(v);
Inserare_inainte(v,26,52);Listare(v);
Sterg(v,52);Listare(v);
Sterg(v,44);Listare(v);
Sterg(v,5);Listare(v);}

Liste alocate înlănțuit folosind HEAP-ul(alocare dinamică)


Întreaga structură (lista) memorată în HEAP este gestionată printr-un singur pointer,
memorat în segmentul de date.
 În cazul listelor, prin acel pointer se poate accesa numai primul element al listei. Apoi,
pornind de la acesta se poate accesa al doilea element al listei, ș.a.m.d.
 Ultimul element al listei va avea memorat în câmpul de adresă o valoare cu
semnificaţia de nici o adresă. Cu ajutorul acestei valori programele vor detecta
sfârșitul listei.

Crearea listelor

Iniţial, o variabilă v reţine 0.


Presupunem că, la un moment dat, lista este cea de mai jos, iar v reţine adresa primului
element (adr1):

3 adr2 7 adr3 9 0

V adr1 adr2 adrn

Dacă se citește un nou număr (de exemplu 4), atunci acesta se adaugă într-o înregistrare
aflată la începutul listei, în următoarele etape:

a) Se alocă spaţiu pentru noua înregistrare, se completează câmpul numeric, iar adresa
următoare este cea din v, deci a primului element al listei.

3 adr2 7 adr3 9 0

V adr1 adr2 adrn


4 adr1

adrn+1

b) Variabila v va memora adresa noii înregistrări:

3 adr2 7 adr3 9 0

V adr1 adr2 adrn


4 adr1
adrn+1

Ex 1: Se aloca spatiu pt o variabila de tip complex, care este inițializată, afisata si stearsa.
#include<iostream>
using namespace std;
struct complex
{int m,n;};
int main()
{complex* adr;
adr=new complex;// // alocare dinamică
adr->m=7;//care este inițializată, afisata si stearsa.
adr->n=5;
cout<<adr->m<<" "<<adr->n<<endl;
delete adr;} // eliberare memorie
Ex 2: Se creeaza o lista liniara simplu inlantuita cu 3 noduri care retin 1, 2, 3.
#include<iostream>
using namespace std;
struct nod
{int info;
nod* adr_urm;};
int 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;}}
Ex 3: Modul de lucru cu o lista liniara simplu inlantuita memorata in HEAP.
#include<iostream>
using namespace std;
struct nod
{int info;
nod* adr_urm;};
nod *v = nullptr, *sf = nullptr;
void adaugare(nod*& v, int val)
{nod* c;
if(!v)
{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,int val, int val1)
{nod *c,*d;
c=v;
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)
{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,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<<" ";
c=c->adr_urm;}
cout<<endl;}
int main()
{int i;
for(i=1;i<=10;i++)
adaugare(v,i);listare(v);
inserare_dupa(v,1,15);listare(v);
inserare_dupa(v,10,25);listare(v);
inserare_dupa(v,25,26);listare(v);
inserare_inainte(v,1,44);listare(v);
inserare_inainte(v,3,43);listare(v);
inserare_inainte(v,26,52);listare(v);
sterg(v,52);listare(v);
sterg(v,44);listare(v);
sterg(v,5);listare(v);}

Liste liniare dublu înlănţuite

O listă dublu înlănţuită, este o structură de date de forma:

0 In1 adr2 adr1 In2 adr3 adrn-1 Inn 0

adr1 adr2 … adrn

Avantajul utilizării listei alocate dublu înlănţuit este dat de faptul că o astfel de listă poate fi
parcursă în ambele sensuri.
Operaţiile pe care le facem cu o listă dublu înlănţuită sunt următoarele:
1) creare;
2) adăugare la dreapta;
3) adăugare la stânga;
4) adăugare în interiorul listei;
5) ștergere din interiorul listei;
6) ștergere la stânga listei;
7) ștergere la dreapta listei;
8) listare de la stânga la dreapta;
9) listare de la dreapta la stânga.
Ex : Modul de lucru cu o lista liniara dublu inlantuita memorata in HEAP.
#include <iostream>
using namespace std;
struct nod
{int info;
nod *ast, *adr;};
nod *st, *dr;
int n, m, i;
void adaugare(nod*& st, nod*& dr)
{cout << "n=";
cin >>n;
st = new nod;
st->info = n;
st->ast = st->adr = nullptr;
dr = st;}
void adaugare_dr(nod*& dr)
{cout << "n=";
cin >> n;
nod* d = new nod;
d->info = n;
d->ast = dr;
d->adr = nullptr;
dr->adr = d;
dr = d;}
void inserare_dupa(nod*& st, int m)
{nod *d = st, *e;
while(d->info != m)
d = d->adr;
if (!d)
{cout << "Elementul nu exista!\n";
return;}
cout << "n=";
cin >> n;
e = new nod;
e->info = n;
e->ast = d;
e->adr = d->adr;
if (d->adr != nullptr)
d->adr->ast = e;
else
dr = e; // dacă inserez după ultimul element, actualizez dr
d->adr = e;}
void inserare_inainte(nod*& st, int m)
{nod *d = st, *e;
while(d->info != m) // caut nodul cu info = m
d = d->adr;
cout<<"n=";
cin>>n;
e = new nod; // aloc nodul nou
e->info = n;
e->adr = d; // îl leg în fața lui d
e->ast = d->ast;
d->ast->adr = e; // refacem legăturile nodului anterior
d->ast = e;
if(d == st) // dacă am inserat înaintea primului nod
st = e;} // st devine nodul nou
void sterg(nod*& st, int m)
{nod* d = st;
while(d->info != m)
d = d->adr;
if (!d)
{cout << "Elementul nu exista!\n";
return;}
if (d->ast != nullptr)
d->ast->adr = d->adr;
else
st = d->adr; // şterg primul nod
if (d->adr != nullptr)
d->adr->ast = d->ast;
else
dr = d->ast; // şterg ultimul nod
delete d;}
void listare(nod*& st)
{nod* d = st;
while(d)
{cout << d->info << endl;
d = d->adr;}}
int main()
{cout << "Lista cu o inreg" << endl;
adaugare(st, dr);
cout << "Cate inreg se adauga?";
cin >> m;
for(i = 1; i <= m; i++)
adaugare_dr(dr);
cout << "Listare de la stanga la dreapta" << endl;
listare(st);
cout << "Inserare la dreapta dupa..." << endl;
cin >> m;
inserare_dupa(st, m);
cout << "Acum listez de la stanga la dreapta" << endl;
listare(st);
cout << "Stergem inreg..." << endl;
cin >> m;
sterg(st, m);
cout << "Listare de la stanga la dreapta" << endl;
listare(st);}
//&-adica param nostru dr se poate schimba dupa prima rulare
d->ast->adr=d->adr;/*campul urm al comp ant comp de sters(denotata de adresa sa d->ast)va
contine adresa comp care urma in lista componentei de sters*/
d->adr->ast=d->ast;/*campul ant al comp ce urma comp de sters(denotata prin d->adr)va
contine adresa comp anterioare comp de sters*/

STRUCTURA DE TIP STIVA

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".
Analizaţi programul următor, care creează o stivă prin utilizarea unei liste liniare simplu
înlănţuite. Adăugarea unui element în stivă se face cu subprogramul PUSH, iar eliminarea,
cu subprogramul POP. Vârful stivei este reţinut de variabila v.
Ex 1: Stiva alocata dinamic(in HEAP).
#include<iostream>
using namespace std;
struct nod
{int info;
nod* adr_inap;};
nod* v = nullptr; // inițializare corectă
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<<endl;
v=v->adr_inap;
delete c;}}
void listare(nod*& v)
{nod* c=v;
while(c)
{cout<<c->info<<" ";
c=c->adr_inap;}
cout<<endl;}
int main()
{int m;
cout << "m=";
cin >> m;
push(v,m);listare(v);
push(v,2);listare(v);
push(v,3);listare(v);
pop(v);listare(v);
pop(v);listare(v);
pop(v);listare(v);
return 0;}
Ex 2: Functia Manna-Pnueli.(Rezolvare iterativa)
#include<iostream>
using namespace std;
int st[100],n,k;
int 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;
return 0;}
Ex 3: Functia Manna-Pnueli alocata dinamic.
#include<iostream>
using namespace std;
struct nod
{int info;
nod* adr_inap;};
nod* v = nullptr; // inițializare corectă;
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<<endl;
v=v->adr_inap;
delete c;}}
int main()
{int man;
cout<<"n=";
cin>>n;
push(v,n);
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;
return 0;}

STRUCTURA DE TIP COADA

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).
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 ultimului element 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 în sf.
v sf

7 3 5 2

Ex: Alocarea dinamica inlantuita a cozii.


#include<iostream>
using namespace std;
struct nod
{int info;
nod* adr_urm;};
nod *v = nullptr, *sf = nullptr; // inițializare corectă
int n;
void pune(nod*& v, nod*& sf, int n)
{nod* c;
if(!v)
{v=new nod;
v->info=n;
v->adr_urm=0;
sf=v;}
else
{c=new nod;
sf->adr_urm=c;
c->info=n;
c->adr_urm=0;
sf=c;}}
void scoate(nod*& v, nod*& sf)
{nod* c;
if(!v)
cout<<"coada e vida"<<endl;
else
{cout<<"am scos "<<v->info<<endl;
c=v;
v=v->adr_urm;
delete c;
if(!v) // dacă am scos ultimul element, golim și sf
sf = nullptr;}}
void listare(nod* v)
{nod* c=v;
while(c)
{cout<<c->info<<" ";
c=c->adr_urm;}
cout<<endl;}
int 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);
return 0;}

ALGORITMI ITERATIVI SI RECURSIVITATE


RECURSIVITATE
Prezentare generala
 Recursivitatea este un mecanism 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.
Recursivitate este proprietatea unei entitati (algoritm, program, structura de date,
figura, etc.) de a putea fi descrisa prin entitati de acelasi tip cu ea insasi. Exista o stransa
legatura intre recursivitate si rationamentul inductiv. Amintim ca, in acest rationament, se
stabileste ca o anumita proprietate P este valabila pt toate elementele unui sir de entitati E ,
E , …E , E , ….
Rationamentul contine 2 etape:
- se stabileste ca proprietatea P are loc pt elementul E (eventual pt un nr finit de elemente E
,E ,….,E );
- se demonstreaza ca, daca propr P are loc pt un element oarecare E , ea are loc si pt
elementul următor E , deci este valabila pt toate elementele sirului.

Mecanismul recursivitatii
Se doreste calculul lui n! folosind o functie recursiva.
 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)= n N
De exemplu, pentru a calcula 3!, procedam astfel:
3!=fact(3)=3xfact(2)=3x2xfact(1)=3x2x1xfact(0)=3x2x1x1=6.
Functia recursiva fact nu face altceva decat sa transcrie definitia recursiva
prezentata anterior:
#include<iostream>
using namespace std;
int fact(int n)
{if(!n) return 1;
else
return n*fact(n-1);}
int main()
{int n;
cout<<"n=";
cin>>n;
cout<<endl;
cout<<n<<"!="<<fact(n);
return 0;}
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
antet: de la stanga la dreapta.
 Pentru parametri 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 din lista parametrilor formali.
 In capitolul anterior am studiat proprietatile structurii numita stiva. Exact aceleasi
proprietati le are si segmentul 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.

Functii, proceduri si metode recursive


Functia recursiva este o functie in al carei corp exista apeluri la ea insasi. In mod
similar, o procedura al carei corp contine apeluri la ea insasi este recursiva.
In programarea orientata pe obiecte, functiile si procedurile se realizeaza sub forma de
metode. In consecinta, functiile si procedurile recursive sunt metode recursive.
Exemple de functii recursive:
1. Functia factorial:
n!=fact(n)= n N
2. Functia Manna-Pnueli:
manna(n)= n N
3. Functia lui Fibonacci:

fib(n)=

4. Functia lui Ackerman


Este o functie care creste mai rapid decat cea exponentiala si se defineste astfel:
Fie m si n numere naturale;

ack(m,n)=

Remarcam ca, in ultima linie a acestei definitii, are loc o dubla invocare recursiva a
functiei: o data pentru calcularea celui de-al doilea argument si inca o data pentru calcularea
valorii functiei. Exista totusi si situatii in care inlocuirea recursiei prin iteratie necesita
folosirea unei stive.
Din exemplele de mai sus, constatam urmatoarele:
 in definitia unei functii recursive trebuie sa fie obligatoriu prevazute si conditii de
terminare in care valoarea intoarsa este calculata direct, fara a mai recurge la
invocarea recursiva a aceleiasi functii;
 algoritmul trebuie sa fie astfel conceput, incat dupa efectuarea unui numar finit de
apeluri recursive sa fie satisfacute conditiile de terminare
Exista si posibilitatea ca recursia sa fie indirecta: in corpul functiei f este invocata functia
g, iar in corpul functiei g este invocata functia f. Cercul acestor invocari succesive poate sa
contina mai mult de doua functii
Procedurile nu au valoare intoarsa, ci numai efect lateral. In consecinta folosirea
procedurilor recursive se bazeaza pe cumularea efectelor laterale, care consta in modificarea
valorilor unor variabile globale sau valorilor parametrilor proceduri. In limbajul Java nu
exista variabile globale, dar pot fi modificate valorile unor campuri ale clasei careia ii
apartine metoda recursiva sau a unor componente ale obiectelor primite ca parametri.

COMPARATIE INTRE ITERATIE SI RECURSIE

Aceeasi functie poate fi, de regula, calculata atat printr-un algoritm iterativ, cat si prin
unul recursiv. Uneori insa, iteratia este mult mai rapida decat recursia si consuma mai putina
memorie. In schimb, utilizarea functiilor sau procedurilor recursive este mai eleganta si face
algoritmul mai usor de inteles si de verificat. In plus, folosirea recursivitatii este o tehnica
foarte eficienta de concepere a algoritmilor. Din aceasta cauza, multi algoritmi sunt
conceputi mai intai in varianta lor recursiva si apoi, numai daca aceasta varianta necesita
timp de calcul sau memorie prea mare, se cauta si o varianta iterativa.
De foarte multe ori, trecerea de la varianta recursiva la cea iterativa a algoritmului se face
prin inlocuirea apelurilor recursive cu un ciclu.
Ex 1: pentru calcularea factorialului se poate folosi;
- fie functia recursiva:
#include<iostream>
using namespace std;
int fact(int n)
{if(n==0) return 1; /*conditia de incheiere a recursiei*/
else
return n*fact(n-1);} // invocarea recursiva
int main()
{int n;
cout<<"n=";
cin>>n;
cout<<endl;
cout<<n<<"!n="<<fact(n);
return 0;}
- fie varianta iterativa:
#include<iostream>
using namespace std;
int main()
{int n,i,f;
cout<<"n=";
cin>>n;
f=1;
for(i=2;i<=n;i++) // ciclul de repetare/iterare a valorilor
f*=i;
cout<<endl;
cout<<n<<"!n="<<f;
return 0;}

Ex 2: functia Manna-Pnueli;
- rezolvare recursiva:
#include<iostream>
using namespace std;
int n;
int manna(int n)
{if(n>=12)
return n-1;
else
return manna(manna(n+2));}// dubla invocare recursiva
int main()
{cout<<"n=";
cin>>n;
cout<<manna(n);
return 0;}
Ex 3: functia Fibonacci;
- rezolvare recursiva:
#include<iostream>
using namespace std;
int n;
int fib(int n)
{if(!n)
return 0;
else
if(n==1)
return 1;
else
return fib(n-1)+fib(n-2);}
int main()
{cout<<"n=";
cin>>n;
cout<<fib(n);
return 0;}
- rezolvare iterativa:
#include<iostream>
using namespace std;
int main()
{int n, i, 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;}
return 0;}

Metoda Divide et Impera


Prezentare generala
Divide et impera (divide si cucereste) se bazeaza pe un principiu extrem de simplu:
descompunem problema in doua sau mai multe subprobleme (mai usoare), care se rezolva iar
solutia pentru problema initiala se obtine combinand solutiile problemelor in care a fost
descompusa. Se presupune ca fiecare dintre problemele in care a fost descompusa problema
initiala se poate descompune in alte subprobleme la fel cum a fost descompusa problema
initiala. Procedeul se reia pana cand, in urma descompunerilor repetate, se ajunge la
problema care admite rezolvare imediata.
Divide et impera este o tehnica care permite implementarea recursiva. Am invatat
principiul general prin care se elaboreaza algoritmii recursivi: ce se intampla la un nivel,
avand grija sa asiguram conditiile de terminare. Tot asa, se elaboreaza un algoritm prin
divide et impera; la un anumit nivel, avem doua posibilitati:
o am ajuns la o problema care admite o rezolvare imediata, caz in care se rezolva si se
revine din apel (conditia de terminare).
o nu am ajuns in situatia de la punctul 1, caz in care descompunem problema in doua sau
mai multe subprobleme, pentru fiecare dintre ele reapelam functia, combinam rezultatele
si revenim din apel.

Aplicatii

Ex 1: Se citeste un vector cu n componente, numere naturale. Se cere sa se tipareasca


valoarea maxima.
Trebuie tiparita valoarea maxima dintre numerele retinute in vector, de la i la j (initial i=1,
j=n). Pentru aceasta procedam astfel:
 daca i=j, valoarea maxima va fi v[i];
 contrar, vom imparti vectorul in doi subvectori: primul subvector va contine
componentele de la i la (i+j)/2, al doilea va contine componentele de la (i+j)/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>
using namespace std;
int v[10],n,i,j;
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;}}
int main()
{cout<<"n=";
cin>>n;
for(i=1;i<=n;i++)
{cout<<"v["<<i<<"]=";
cin>>v[i];}
cout<<"max= "<<max(1,n);
return 0;}
Algoritmul prezentat este exclusiv didactic, algoritmul este folosit doar ca exemplu
pentru a ilustra metoda Divide et Impera, nu pentru ca ar fi cea mai buna solutie in practica;
in practica se prefera algoritmul clasic (iterativ) parcurge vectorul o singura data; are
complexitate O(n); este mai simplu, mai rapid si consuma mai putina memorie.
Ex 2: Cautare binara. Se citeste un vector cu n componente, numere intregi, ordonate
crescator, si o valoare intreaga 'nr'. Sa se decida daca ‘nr’ se gaseste sau nu printre 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 printre numerele de indice
cuprins intre i si j (i=1, j=n). Pentru aceasta vom proceda astfel:
o daca numarul coincide cu valoarea de la indicele (i+j)/2 (valoarea de la mijloc), se
tipareste indicele si se revine din apel (problema a fost rezolvata);
o contrar, daca i<j (nu s-a cautat peste tot), problema se descompune astfel:
 daca numarul este mai mic decat valoarea testata (din mijloc), atunci sansele de gasire
sunt intre componentele cu indicele intre i si (i+j)/2 - 1; in acest caz reapelam functia
cu acesti parametri;
 daca numarul este mai mare decat valoarea testata (din mijloc), atunci sansele de
gasire sunt intre componentele cu indicele intre (i+j)/2 + 1 si j; in acest caz reapelam
functia cu acesti parametri.
Cautarea binara nu imparte problema in doua subprobleme rezolvate
independent, ci: alege doar una dintre ele; cealalta este abandonata. Deci avem reducere la o
subproblema, nu rezolvare si combinare.
#include<iostream>
using namespace std;
int v[100],n,nr;
void caut(int i,int j)
{if(nr==v[(i+j)/2])
cout<<"gasit pe pozitia "<<(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);};
int main()
{int i;
cout<<"n= ";
cin>>n;
for(i=1;i<=n;i++)
{cout<<"v["<<i<<"]= ";
cin>>v[i];}
cout<<"nr= ";
cin>>nr;
caut(1,n);
return 0;}
Care este motivul pentru care am ales aceasta metoda, comparativ cu cea elementara
pentru care exploram vectorul, componenta cu componenta? Numarul de comparatii. Pentru
analiza celor doi algoritmi, consideram cazul cel mai nefavorabil, in care valoarea nu este
identificata printre elemente. Numaram cate comparatii se efectueaza in ambele cazuri:
 In primul caz, se efectueaza cel mult n comparatii – valoarea se compara cu cea
retinuta de prima componenta apoi cu cea retinuta de a doua componenta.
 in cazul cautarii binare problema este putin mai complicata. Avem n valori, de indice
cuprins intre 1 si n. Calculam indicele componentei din mijloc, facem o comparatie
apoi problema se reduce la identificarea valorii fie intre componentele cu indicii in
multimea A, fie intre componentele cu indice in multimea B:

A={1,2…..[ ]-1} B={[ ]+1,…n}

card(A)=[ ]-1, card (B)=n-[ ];


Prin card(X) am notat numarul de elemente ale multimii X.
Daca n=2k (n este par) :
card(A)=k-1, card(B)=k, =k;
Daca n=2k+1 (n este impar):
card (A)=k, card(B)=k, k+ ;
In concluzie:
card (A), card(B)<=
Aceasta inseamna ca, dupa o comparatie, numarul valorilor care urmeaza sa fie testate
este cel putin jumatate. Supraestimand efortul de calcul, putem spune ca algoritmul
efectueaza p comparatii, unde p este cel mai mic numar natural pentru care:
<1 Rezulta n<2p=>log n<p; =>p=[log n]+1;
Pentru a verifica asta, propun sa scriem un mic program care calculeaza valoarea lui p.
Intrucat limbajul n-are decat functiile care calculeaza logaritmii natural si zecimal,
vom utiliza formula de schimbare a bazei:
log n=
#include<iostream>
#include<math.h>
using namespace std;
int main()
{long n,i;
cout<<"n=";
cin>>n;
cout<<n<<' '<<1+(int)(log(n)/log(2));}
Pentru n=10000, p este 14. Daca tinem cont de faptul ca se efectueaza cel mult 14
comparatii, eficienta algoritmului nu mai trebuie sa fie justificata.
O a doua metoda de estimare, cu mult mai eleganta, a numarului de comparatii facute
de algoritm este data in continuare.
Notez cu T(n) numarul de comparatii efectuate de algoritmul cautarii binare pentru un vector
cu n componente. Pentru simplitate, vom considera n=2 .
La un pas se compara elementul cautat cu cel din mijloc – de indice:

Se efectueaza o comparatie dupa care problema se reduce la alta mai simpla:


Astfel avem:
T(n)=T(2 )=1+T(2 )=2+T(2 )=….k=log n

1 daca n=1;
T(n)=

T( )+1 altfel.
Daca n nu este de forma 2 , atunci exista k astfel incat:
2 <n<2 =>k<T(n))≤k+1;
In concluzie, numarul maxim de operatii este:
k+1=[log n]+1

Ex 3: Sortare prin interclasare (merge sort). Se considera vectorul a cu n componente


numere intregi(sau reale). Sa se sorteze crescator, utilzand sortarea prin interclasare.
Algoritmul de interclasare este performant, pentru ca efectuează cel mult m+n-1
comparaţii.
Algoritmul de sortare prin interclasare se bazeaza pe urmatoarea idee: pentru a sorta un
vector cu n elemente il impartim in doi vectori care, odata sortati, se interclaseaza.
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 vectori 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<iostream>
using namespace std;
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++];
else
b[k++]=a[j++];
if(i<=m)
for(j=i;j<=m;j++)
b[k++]=a[j];
else
for(i=j;i<=q;i++)
b[k++]=a[i];
k=1;
for(i=p;i<=q;i++)
a[i]=b[k++];}
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);}}
int 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<<"]="<<a[i]<<endl;
return 0;}
In continuare calculam numarul aproximativ de comparatii efectuat de algoritm. Fie
acesta T(n). Mai simplu, presupunem n=2k.
O problema se descompune in alte doua probleme, fiecare cu n/2 componente dupa care
urmeaza interclasarea lor, care necesita n/2+n/2=n comparatii,

0 n=1;
T(n)= 2T( )+n altfel.
Avem:
T(n)=T(2 )=2(T(2 )+2 )=2T(2 )+2 =2(T(2 +2 )+2 =2T(2 )+2 +2
=…2 +2 +…+2 =n+n+…+n=n*k=n* log n
de k ori de k ori
Sortarea prin interclasare are complexitate T(n)=O(nlogn)

Ex 4: Sortare rapida (Quick sort). Fie vectorul a cu n componente 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:
 i ramane constant, j scade cu 1;
 i creste cu 1, j ramane constant .

Functia este conceputa astfel :


 initial i va lua valoarea li, iar j va lua valoarea ls (elementul 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);
 atat timp cat i<j, se executa:
o daca a[i] este strict mai mare decat a[j], atunci se inverseaza cele doua numere si
se schimba modul de lucru;
o i si j se modifica corespunzator modului de lucru in care se afla programul;
o k ia valoarea comuna a lui i si j;

Exemplu: a=(6,9,3,1,2), li=1, ls=5; modul de lucru a);


 i=1, j=5; a[1]>a[5], deci se inverseaza elementele aflate pe pozitiile 1 si 5, deci
a=(2,9,3,1,6) si programul trece la modul de lucru b);
 i=2, j=5; a[2]>a[5], deci a=(2,6,3,1,9) si se revine la modul de lucru a);
 i=2, j=4; a[2]>a[4], deci a=(2,1,3,6,9) se trece la modul de lucru b);
 i=3, j=4; functia se incheie, elementul aflat initial pe pozitia 1 se gaseste acum pe
pozitia 4, toate elementele din stanga lui, fiind mai mici decat el, totodata toate
elementele din dreapta lui fiind mai mari decat el (k=4).
Alternanta modurilor de lucru se explica prin faptul ca elementul care trebuie pozitionat
se compara cu un element aflat in dreapta sau in stanga lui, ceea ce impune o modificare
corespunzatoare a indicilor 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.
Functia 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>
using namespace std;
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);}}
int 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;
return 0;}

𝑂(𝑛log𝑛), in cel mai nefavorabil caz (vector deja sortat, pivot prost ales) 𝑂(𝑛2).
Sortarea rapida efectueaza in medie n*log2n operatii, adica in medie complexitate de

Ex 5: Turnurile din Hanoi. Se dau 3 tije simbolizate prin a, b, c. Pe tija a se gasesc


discuri de diametre diferite, asezate in ordine descrescatoare a diametrelor privite 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, tija c.
Conform strategiei DIVIDE 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 3 etape permite definirea recursiva 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,a
c,bc,ab,ca,cb,ab.
#include<iostream>
using namespace std;
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);}}
int main()
{cout<<"N=";cin>>n;
a='a' ;b='b' ;c='c' ;
han(n,a,b,c);
return 0;}

Complexitatea Turnurilor din Hanoi


Concluzie: Timp: O(2ⁿ) (exponențial)
Spațiu: O(n) (liniar)

Ex 6: Se da o bucata dreptunghiulara de tabla cu lungimea L si inaltimea H, avand


pe suprafata ei n gauri de coordonate numere intregi. Se cere sa se decupeze din ea o bucata
de arie maxima care nu prezinta gauri. Sunt permise numai taieturi verticale si orizontale.
Coordonatele gaurilor sunt retinute in doi vectori xv si yv. Dreptunghiul initial,
precum si dreptunghiurile care apar in procesul de taiere sunt memorate in program prin
coordonatele coltului stânga-jos (x, y), prin lungime si inaltime (L, H).
Pentru un dreptunghi (initial pornim cu toata bucata de tabla), verificam daca avem o
gaura sau nu in el (se cauta practic 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 cu aria unei alte bucati fara gaura, gasita in fazele
precedente .
Mentionam ca dreptunghiul de arie maxima fara gauri este retinut prin aceeasi parametri ca si
dreptunghiul cu gauri, in zonele xf, yf, Lf, Hf.
In concluzie problema intiala 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 dreptunghiul cu o gaura:

H
o xv(i), yv(i)
x,y
L
Pentru a se afla in interiorul dreptunghiului gaura trebuie sa indeplineasca simultan
conditiile:
1) xv(i)>x;
2) xv(i)<x+L;
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(i), y, L+x-xv(i), H.
In urma unei taieturi pe orizontala se obtin cele doua dreptunghiuri:
1) x, y, L, yv(i)-y;
2) x, yv(i), L, H+y-yv(i).
#include<iostream>
using namespace std;
int l,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)
if(xv[i]>x && xv [i]<x+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;}}
int 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="<<lf<<"h="<<hf;
return 0;}

Tehnica backtracking
5.1Aspecte teoretice
Aceasta tehnica se foloseste in rezolvarea problemelor care indeplinesc simultan
urmatoarele conditii:
 solutia lor poate fi pusa sub forma unui vector S=x1, x2, …, xn, cu x1ϵA1, x2ϵA2, …,
xnϵ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 ptr. toate problemele n este cunoscut de la inceput;
 x1, x2, …, xn pot fi la randul lor vectori;
 in multe probleme, multimile A1, A2, …, An coincid.
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 elaborata folosind structura de stiva. Rutina va
apela functii care au intotdeauna acelasi nume si care, din punct de vedere al 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 nu ne opreste
ca, dupa intelegerea metodei, sa scriem programe scurte, specifice fiecarei probleme in
parte.
 Am aratat ca orice solutie se genereaza sub forma de vector. Vom considera ca
generarea solutiilor se face intr-o stiva. Astfel x1ϵA1 se va gasi pe primul nivel al
stivei, x2ϵA2 se va gasi pe al doilea nivel al stivei, xkϵAk se va gasi pe nivelul k al
stivei. In acest fel, stiva (notata st) va arata ca mai jos:

xk

x2
x1
st

Nivelul k+1 al stivei trebuie initalizat (pentru a alege, in ordine, elementele multimii
k+1). Initializarea trebuie facuta cu o valoare aflata (in relatia de ordine considerata pentru
multimea Ak+1) inaintea tuturor valorilor posibile din multime.
De exemplu, pentru generarea permutarilor multimii {1, 2, …, 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 succesor 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_Valid() care
intoarce 1, daca conditiile sunt indeplinite sau 0 in caz contrar.

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--;}}
Observatii:
 Pe un anumit nivel (k) se cauta succesorul atat timp cat exista succesor care nu este
valid. Variabila AS are rolul de a retine daca, atunci cand s-a iesit din ciclu, am avut
succesor, caz in care acesta este valid (contrar nu s-ar fi iesit din ciclu ).
 Toate variabilele cum ar fi stiva (st) nivelul la care s-a ajuns (k), sunt variabile
globale.
 Problemele rezolvate prin aceasta metoda necesita un timp indelungat de rulare. Din
acest motiv este bine sa utilizam metoda numai atunci cand nu avem la dispozitie un alt
algoritm, mai eficient.
 Mentionam ca exista probleme ptr. care nu se cunosc algoritmi eficienti de rezolvare,
deci backtracking este indicat.
 Rezolvarea iterativa propusa incalca principiul stivei atunci cand verificam conditiile
de continuare, atunci cand tiparim solutia gasita, pentru ca accesam orice nivel al stivei.
Consider ca o structura trebuie folosita ca atare atunci cand este strict necesar. De
exemplu, chiar si segmentul de stiva al programului poate fi accesat oriunde. Asta nu
inseamna ca acolo nu se utilizeaza din plin "mecanismul" stivei.
 Rutina prezentata corespunde variantei iterative.

5.2 Aplicatii rezolvate iterativ


Ex 1: Generarea permutarilor. Se citeste un nr. natural n. Sa se genereze toate
permutarile multimii {1, 2, …, n}
Generarea permutarilor se va face tinand cont ca orice permutare va fi alcatuita din elemente
distincte ale multimii A={1, 2, …, n}.
Algoritmul corespunzator cazului n=3:

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

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

a) Se incarca in stiva pe nivelul 1 valoarea 1;


b) Incarcarea valorii 1 pe nivelul al doilea nu este posibila, intrucat aceasta valoare se
gaseste si pe nivelul 1 al stivei;
c) Incarcarea valorii 2 pe nivelul al doilea este posibila, deoarece aceasta valoare nu mai
este intalnita;
d) Valoarea 1 din nivelul al treilea se regaseste pe nivelul 1;
e) Valoarea 2 din nivelul al treilea se regaseste pe nivelul al doilea ;
f) Valoarea 3 pe nivelul al 3-lea nu e intalnita pe nivelurile anterioare; intrucat nivelul 3
este completat corect, afisam: 1 2 3
g) …
Algoritmul continua pana cand stiva devine vida.
#include<iostream>
using namespace std;
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[k]==st[i])
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
{AS=Am_Succesor();
if(AS)
E_Valid();}
while((AS) && !E_Valid());
if(AS)
if(Solutie())
Tipar();
else
{k++;
Init();}
else
k--;}}
int main()
{cout<<endl<<"n=";
cin>>n;
back();
return 0;} T(n)=O(n⋅n!)= O(n!) si iterativ si recursiv
Ex 2: 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 aceiasi linie, coloana sau diagonala
(damele sa nu se atace reciproc).

Exemplu: Presupunand ca dispunem de o tabla de dimensiune 4×4, o solutie ar fi urmatoarea:


D
D
D
D
Plasam prima dama pe linia 1, coloana 1. A doua dama nu poate fi
asezata decat in coloana a 3-a.
D D
D

Observam ca a treia dama nu poate fi plasata in linia a 3-a. Incercam atunci plasarea celei de-
a doua dame in coloana a 4-a. A treia dama nu poate fi plasata decat in
D
coloana a 2-a. D
D D
D

In aceasta situatie dama a 4-a nu mai poate fi asezata. Incercand sa avansam cu dama a 3-a,
observam ca nu este posibil sa o plasam 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. A doua dama nu poate fi asezata decat in coloana
a 4-a.

D D
D

Dama a 3-a se aseaza in prima coloana. Acum este posibil sa plasam a 4-a dama in coloana a
3-a si astfel am obtinut o solutie a problemei.

D D
D 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:

| st(i) - st(j) | = | i - j | - (diferenta, in modul, intre linii si coloane este aceeasi).

3 ST(4)
1 ST(3) In general ST(i)=k semnifica faptul
4 ST(2) ca pe linia i dama ocupa pozitia k.
ST(1)
2
Exemplu: in tabla 4×4 avem situatia:

D D

D D

sau situatia:
st(1)=1 i=1 st(1)=3 i=1
st(3)=3 j=3 st(3)=1 j=3
| st(1) - st(3) | = | 1 – 3 | = 2 | st(i) - st(j) | = | 3 – 1 | = 2
| i - j | = | 1- 3 | = 2 |i-j|=|1-3|=2
Programul de generare a tuturor solutiilor problemei celor n dame, are o singura
conditie suplimentara, in functia E_Valid, fata de programul de generare a permutarilor.
#include<iostream>
#include<stdlib.h>
using namespace std;
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] || abs(st[k]-st[i])==abs(k-i))
return 0;
return 1;}
int solutie()
{return k==n;}
void tipar()
{cout<<endl;
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--;}}
int main()
{cout<<"n=";
cin>>n;
back();
return 0;} Complexitate timp:O(n!) (mult mai mic decât
permutările brute, datorită tăierilor), de spațiu:O(n) (stiva de niveluri)
Ex 3: 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), (1,3,1), (1,3,2), (1,3,3), (2,1,1),
(2,1,2), (2,1,3), (2,2,1), (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 care retine numerele k1, k2, ..., kn,
utilizam metoda backtracking, usor modificata din urmatoarele motive:
 orice element aflat pe nivelul k al stivei este valid, motiv pentru care functia
E_valid nu face altceva decat sa returneze 1.
 limita superioara pe nivelul k al stivei este data de a[k].

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

2 3 1 2 3
2 2 3 3 3 3
1 1 1 1 1 1
…………………………………………………………………
 a[1], a[2], ..., a[k] nu contine elementele multimilor, ci dimensiunile lor.
Observatii : Acest algoritm a mai fost prezentat in capitolul anterior. 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>
#include<stdlib.h>
using namespace std;
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]<<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--;}}
int main()
{cout<<"numar total de multimi";
cin>>n;
for(int i=1;i<=n;i++)
{cout<<"a["<<i<<"]=";
cin>>a[i];}
back();
return 0;}
Ex 4: Generarea aranjamentelor. Se citesc n si p. Sa se genereze toate
aranjamentele de n luate cate p.
Din analiza problemei rezulta urmatoarele:
 stiva are inaltimea p;
 fiecare nivel ia valori intre 1 si n;
 elem. 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>
using namespace std;
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()
{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--;}}
int main()
{cout<<"n=";
cin>>n;
cout<<"p=";
cin>>p;
back();
return 0;}
Ex 5: 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 mică sau egala cu n-p+k.
#include<iostream>
using namespace std;
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 e_valid()
{return 1;}
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((as=am_succesor()) && !e_valid());
if(as)
if(solutie())
tipar();
else
{k++;
init();}
else
k-- ;}}
int main()
{cout<<"n=";
cin>>n;
cout<<"p=";
cin>>p;
back();
return 0;}
N-are rost sa apelam E_valid.
Ex 6: 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:
1 4

2 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 de adiacenta (tablou) A n,n.

A(i,j)= 1, tara i are frontiera comuna cu tara j;


0, altfel

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>
using namespace std;
int st[10],a[20][20],n,k;
void init()
{st[k]=0;}
int am_succesor()
{if(st[k]<4)
{st[k]++;
return 1;}
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;
return 1;}
int solutie()
{return k==n;}
void tipar()
{cout<<" / Varianta ";
for(int i=1;i<=n;i++)
cout<<"Tara "<<i<<" culoarea "<<st[i]<<"\n";}
void back()
{int as;
k=1;
init();
while(k>0)
{do
{as=am_succesor();
if(as)
e_valid();}
while((as) && !e_valid());
if(as)
if(solutie())
tipar();
else
{k++;
init();}
else
k--;}}
int main()
{cout<<endl<<"Numarul de tari n=";
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();
return 0;}
Ex 7: Problema comis-voiajorului. Un comis-voiajor trebuie sa viziteze un numar n
de orase. Initial, acesta se afla in unul dintre ele, notat 1. Comis voiajorul 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.
Exemplu: in figura alaturata sunt simbolizate cele 6 orase, precum si drumurile existente
intre ele.

2 3

1
4

6 5

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)

A(i,j)= 1, daca exista drum intre orasele i si j;


0, altfel
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 (nivelul 1) se incarca numarul
1. prezentam in continuare modul de rezolvare a problemei:

2 De la orasul 1 la orasul 2
1

2
2 Orasul 2 se mai gaseste in stiva, deci nu este acceptat.
1

3
De la orasul 2 la orasul 3 exista drum; prin orasul 3 nu s-a mai trecut, orasul 3
2
este acceptat.
1
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
indeplinite 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 cel aflat pe nivelul k;
 daca succesorul se gaseste pe nivelul n, sa existe drum de la el la orasul 1.
#include<iostream>
using namespace std;
int st[10],a[20][20],n,k;
void init()
{st[k]=1;}
int am_succesor()
{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 i=1;i<=k-1;i++)
if(st[i]==st[k])
return 0;
if(k==n && !a[1][st[k]])
return 0;
return 1;}
int solutie()
{return k==n;}
void tipar()
{for(int i=1;i<=n;i++)
cout<<"Nodul"<<st[i]<<endl;
cout<<"-------"<<endl;}
void back()
{int as;
k=2;
init();
while(k>0)
{do{}while((as=am_succesor()) && !e_valid());
if(as)
if(solutie())
tipar();
else
{k++;
init();}
else
k--;}}
int main()
{int i,j;
cout<<"Numar de noduri"<<endl;
cin>>n;
for(i=1;i<=n;i++)
for(j=1;j<=i-1;j++)
{cout<<"a["<<i<<”,”<<j<<"]=";
cin>>a[i][j];
a[j][i]=a[i][j];}
st[1]=1;
back();
return 0;}

Backtracking recursiv

Problemele incluse in acest capitol sunt rezolvate prin utilizarea tehnicii backtracking,
insa aceasta ca idee de cautare a solutiilor, nu ca rutina standardizata.
Ex 1: 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 aceiasi linie, coloana sau diagonala
(damele sa nu se atace reciproc).
#include<iostream>
#include<stdlib.h>
using namespace std;
int t[20],n; /*memorăm fiecare soluție într-un vector t, considerând că o căsuță k
a vectorului reprezintă linia k iar conținutul ei, adică t[k] va conține numărul
coloanei în care vom plasa regina*/
void tipar()
{for(int i=1;i<=n;i++)
cout<<t[i];
cout<<endl;}
void dame(int k)
{int i,j,corect;
if(k==n+1) //k a dep niv compl,pt ca nu se stie inca daca e sol pîn la dame(k+1);
tipar();
else
{for(i=t[k]+1;i<=n;i++)
{t[k]=i;//pe poz k incerc sa pun toate valorile de la 1 la n.
corect=1;
for(j=1;j<k;j++)//pt j<=k abs (t[k]-t[k])==abs(k-k))
if(t[j]==t[k] || abs(t[k]-t[j])==abs(k-j))//sa nu fie pe aceeasi coloana si diagonala
corect =0;
if(corect)
dame(k+1);}} //urca un nivel in stiva
t[k]=0;} //initializam cu zero nivelul respectiv in stiva daca nu e solutie
int main()
{cout<<"n=";
cin>>n;
dame(1);
return 0;}

Ex 2: 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. Avem:4,31,22,211,13,121,112,1111 - Partiții ordonate(fără restricții).
 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, functia va apela o alta pentru a tipari vectorul (initial se va tipari valoarea n).
Din valoarea care se gaseste pe un nivel (s[k]) se scad, pe rand, valorile 1, 2, s[k-1], valori cu
care se apeleaza functia pentru nivelul următor. La revenire, se reface valoarea existenta. In
concluzie, putem afirma:
 nivelul 1 ia valoarea n;
 fiind dat un nivel oarecare (la care s-a ajuns) se tipareste vectorul apoi se scad, pe rand,
toate valorile posibile (pana cand acesta ramane cu valoarea 1), valori cu care functia
este reapelata, avand grija ca la revenire sa refacem valoarea (pentru a se executa corect
scaderea urmatoare).
#include<iostream>
using namespace std;
int s[20],n;
void tipar(int k)
{for(int i=1;i<=k;i++)
cout<<s[i];
cout<<endl;}
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]+i;}}
int main()
{cout<<"n=";
cin>>n;
part(1,n);
return 0;}
1️⃣ Apelul inițial
part(1,4);
👉 stiva: k=1 s[1]=4
👉 se tipărește: 4
2️⃣ Intrăm în for la k=1
for(i=1;i<=s[1]-1;i++) // i=1..3
▶ i = 1 s[1] = 4 - 1 = 3;
part(2,1);
📌 aici apare stiva [3,1]
👉 explicație EXACTĂ:
s[1] era 4 scădem i=1 → s[1]=3
apelăm part(2,1) → pe nivelul 2 punem 1
👉 stiva este:
nivel 1: s[1] = 3
nivel 2: s[2] = 1
👉 adică: [3,1]
✔️de aici apare stiva [3,1]
3️⃣ Ce se întâmplă în part(2,1)
s[2]=1;
tipar(2);
👉 se tipărește: 31
Acum for-ul:
for(i=1;i<=s[2]-1;i++) // s[2]=1 → 1<=0 ❌
❌ NU intră în for
👉 funcția se termină
4️⃣ Revenire (backtracking)
Ne întoarcem în part(1,4):
s[1]=s[1]+i; // 3 + 1 = 4
👉 stiva revine la: [4]
✔️asta este refacerea stivei, foarte importantă
5️⃣ i = 2 (tot la k=1)
s[1] = 4 - 2 = 2;
part(2,2);
👉 stiva: [2,2]
👉 se tipărește: 22
Apoi din part(2,2):
i=1 → s[2]=1, apel part(3,1)
stiva devine [2,1,1]
se tipărește 211
6️⃣ i = 3 (la k=1) s[1] = 4 - 3 = 1;
part(2,3);
👉 stiva:[1,3]
👉 se tipărește:13
Din part(2,3) apar:
121
112
1111
O partiție ordonată descrescător a unui număr natural n este o descompunere a lui n ca sumă
de numere naturale nenule, ordonate descrescător, astfel încât fiecare termen este mai mic
sau egal decât precedentul.
Partiții ordonate (fără restricții) care sunt lexicografic descrescător
4
31
22
211
13
121
112
1111
Partiții ordonate (fără restricții) care sunt lexicografic crescător
1111
112
121
13
211
22
31
4
Partiții neordonate lexicografic descrescător
4
31
22
211
1111
Partiții neordonate lexicografic crescător
1111
211
22
31
4
Partiții neordonate care nu sunt lexicografic
4
22
13
112
1111
Partiții neordonate care nu sunt lexicografic
1111
112
13
22
4
Ex 3: Plata unei sume cu bancnote de valori date. Se dau suma s si n tipuri de
monede avand 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 presupune 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 acel tip si se retine in vectorul b). O solutie sub forma unui
vector cu n componente (sol1) in care componenta i retine un numar de monede de tipul i
care se folosesc pentru plata sumei s. Evident, acest numar poate fi si 0. Toate componentele
vectorului sol1 sunt initializate cu -1 (valoare aflata inaintea 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 sol1 care urmeaza a fi
completata (k) si suma platita pana in acel moment (s0). Apelul sau (din cadrul programului
principal) se face pentru prima componenta si suma 0. Fiind dat un nivel oarecare (k) si atat
timp cat este posibil sa utilizezi in plata inca o moneda din tipul respectiv (nu se depaseste
suma care trebuie platita) se efectueaza urmatoarele:
- se incrementeaza nivelul (se foloseste o noua moneda de tipul k);
- suma platita se majoreaza cu valoarea acelei monede;
- in cazul cand a fost obtinuta o solutie (s0=s), aceasta se tipareste, contrar se apeleaza
functia pentru nivelul următor, cu noua valoare s0.
#include<iostream>
using namespace std;
int sol[10], a[10], b[10],n,i,s;
void tipar(int k)
{cout<<" Solutie "<<endl;
for(i=1;i<=k;i++)
if(sol[i])
cout<<sol[i]<<" bancnote de "<<a[i]<<endl;}
void plata(int k,int s0)
{while(sol[k]<b[k]&& s0+a[k]<=s)
{sol[k]=sol[k]+1;
if (sol[k]>0)
s0+=a[k];
if(s0==s)
tipar(k);
else
if(k<n)
plata(k+1,s0);}
sol[k]=-1;}
int main()
{cout<<" Cate tipuri de bancnote 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);
return 0;}

Ex 4: Problema labirintului. Se 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 y, se 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. Acesta poate lua valori intre 0 si 15. Se considera
iesirile spre nord, est, sud, vest luate in aceasta ordine. Pentru fiecare directie cu iesire se
retine 1, iar in caz contrar se retine 0. Un sir de patru cifre alcatuit din 1 sau 0 formeaza un
numar in baza 2. Acest numar este convertit in baza 10 si retinut in l[i][j].
Exemplu: camera de coordonate (2, 3) are iesiri spre est si spre vest. Obtinem numarul
binar 0101, care in baza 10 inseamna 5. Elementul de coordonate (2, 3) al matricei va retine
numarul 5.
Pentru a testa usor iesirea din labirint, matricea se bordeaza 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 reprezinta o matrice
cu doua linii si 100 de coloane. Semnificatia elementelor acestei matrice este urmatoarea:
* d[k][0] 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 următor:
- se testeaza daca nu s-a iesit din labirint (l[x][y]=16);
- in caz ca aceasta conditie este indeplinita, se tipareste 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 nord, 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>
using namespace std;
int l[10][10],d[2][100],i,j,m,n,x,y,k;
void tipar(int k,int d[2][100])
{int i; cout<<"---------"<<endl;
for(i=1;i<=k;i++)
cout<<"l="<<d[0][i]<<" "<<"c="<<d[1][i]<<endl;}
void ies(int x,int y,int& k,int l[10][10],int d[2][100])
{int i,gasit;
if(l[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(l[x][y]&(int)8) ies(x-1,y,k,l,d);break;}
case 2:{if(l[x][y]&(int)4) ies(x,y+1,k,l,d);break;}
case 3:{if(l[x][y]&(int)2) ies(x+1,y,k,l,d);break;}
case 4:if(l[x][y]&(int)1) ies(x,y-1,k,l,d);}
k--;}}//8,4,2,1 in baza 2 reprez puncte cardinale
int main() // 8=1000-N, 4=0100-E, 2=0010-S, 1=0001-V
{cout<<"N=";cin>>n;
cout<<"M=";cin>>m;
for(i=1;i<=m;i++)
for(j=1;j<=n;j++)
{cout<<"l["<<i<<","<<j<<"]=";
cin>>l[i][j];}
cout<<"X=";cin>>x;
cout<<"Y=";cin>>y;
for(i=1;i<=n;i++)
{l[0][i]=16;
l[m+1][i]=16;}
for(i=1;i<=m;i++)
{l[i][0]=16;
l[i][n+1]=16;}
k=0;
ies(x,y,k,l,d);
return 0;}

Ex 5: Algoritmul de umplere a unei suprafete inchise (FILL). Se da o matrice


binara. Valorile 1 delimiteaza o anumita suprafata inchisa in cadrul matricei (elementele
apartinand acestei suprafete sunt marcate cu 0). Se dau coordonatele x si y ale unui element
al matricei, semnificand un punct din interiorul acestei suprafete.
Ex: Fie matricea:

0 1 1 0
A= 0 0 0 1
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 trebuie sa arate astfel:

1 1 1 0
A= 1 1 1 1
1 1 1 1
1 0 0 0
a) 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 algoritm 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. Acesta 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, acesta ia valoarea 1, iar functia se autoapeleaza pentru fiecare dintre
elementele invecinate (sus, jos, dreapta, stinga);
- in caz contrar, se iese din functie.
#include<iostream>
using namespace std;
int a[10][10],i,j,m,n,x,y;
void scriu(int x,int y,int a[10][10])
{if(a[x][y]==0)
{a[x][y]=1;
scriu(x+1,y,a);
scriu(x,y+1,a);
scriu(x-1,y,a);
scriu(x,y-1,a);}}
int 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]=1;
a[m+1][i]=1;}
for(i=1;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][j];
cout<<endl;}
scriu(x,y,a);
cout<<endl<<endl;
for(i=1;i<=m;i++)
{for(j=1;j<=n;j++)
cout<<a[i][j];
cout<<endl;}
return 0;}
Ex 6: Problema fotografiei (aplicaţie FILL). O fotografie alb-negru este prezentata
sub forma unei matrice binare. Ea infatiseaza unul sau mai multe obiecte. Portiunile
corespunzătoare obiectului (sau obiectelor) in matrice au valoarea 1. Se cere sa se determine
daca fotografia reprezintă unul sau mai multe obiecte.
In matricea următoare sunt reprezentate doua obiecte:
In matricea de mai jos este reprezentat un singur obiect.

0110
A= 0001
0111
1000
Ca si in problemele anterioare, spre a evita testul iesirii din matrice, aceasta este
bordata cu linii si coloane avand valoarea 0. Algoritmul este tot cel din problema anterioara
(FILL), dar aici cautarea se face pe 8 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
elementele matricei care apartin acestui obiect identificat. La revenire, se testeaza daca mai
exista elemente cu valoarea 1 in matrice. In caz afirmativ, se poate trage concluzia ca in
fotografie aveam initial mai multe obiecte; astfel, fotografia continea un singur obiect.

#include<iostream>
using namespace std;
int a[10][10],i,j,m,n,x,y,gasit;
void compact (int x,int y,int a[10][10])
{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);}}
int 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(gasit)
cout<<"mai multe obiecte";
else
cout<<"un obiect";
return 0;}

Greedy

Sa consideram o mulţime A cu n elemente. Se cere o submulţime a sa, eventual cu


m<=n elemente, astfel incat sa fie îndeplinite anumite condiţii.
 Se considera o mulţime de n numere reale. Se cere o submulţime a sa, astfel incat
suma elementelor ei sa fie maxima.
Pentru rezolvare, vom alege un prim element al mulţimii de numere reale. Daca este
posibil, acesta va fi adăugat soluţiei, iniţial vide. Posibilitatea ca acesta sa fie adăugat este
data de semnul numărului. Se alege un al doilea număr cu care se procedează in mod
asemănător.
Algoritmul se încheie când au fost alese, si eventual adăugate toate elementele
mulţimii.
 Pentru a rezolva o problema Greedy, soluţia se construiește, după algoritmul de mai
jos.
Pentru fiecare element care urmează sa fie adăugat soluţiei finale, se efectuează o
alegere a sa dintre elementele mulţimii A, iar daca este posibil acesta este adaugat.
Algoritmul se termina, fie când a fost găsita soluţia ceruta, fie când s-a constatat inexistenta
acesteia.
Intuitiv, alegem un element, al doilea,…pana când obţinem ce dorim sau pana când au
fost testate toate elementele mulţimii. De aici provine si numele metodei (Greedy=lacom)
 Cel care elaborează un algoritm Greedy trebuie sa știe faptul ca, procedând in
modul ales de el, ajunge la rezultatul dorit. Pentru fiecare problema in parte, după ce se
identifica un algoritm, este obligatoriu sa se demonstreze ca acesta conduce la soluţia
optima.
 In general numărul de operaţii de baza efectuate de un algoritm Greedy este o
expresie polinomiala - algoritmii sunt performanţi.
 De multe ori, este necesar ca elementele multimii A sa fie sortate, pentru ca apoi sa
alegem din acestea.
- întrebare fireasca: fiind data o problema, exista întotdeauna un algoritm de tip Greedy
care găsește soluţia optima? Evident, NU. Exista probleme pentru care nu se cunosc
astfel de algoritmi. Mai mult, pentru cele mai multe probleme nu se cunosc algoritmi.
 Intr-o sala, intr-o zi trebuie planificata n spectacole. Pentru fiecare spectacol se
cunoaște intervalul in care se desfasoara: [st, sf). Se cere sa se planifice un număr maxim
de spectacole astfel incat sa nu se suprapună.

Vom construi o soluţie dupa următorul algoritm:


P1 - sortam spectacolele după ora terminării lor;
P2 - primul spectacol programat este cel care se termina cel mai devreme;
P3 - alegem primul spectacol dintre cele care urmează in sir ultimului spectacol programat
care îndeplinește condiţia ca începe după ce sa terminat ultimul spectacol programat;
P4 - daca tentativa de mai sus a eșuat algoritmul, se termina, altfel se programează
spectacolul găsit si algoritmul se reia de la P3.
// spectacol
#include<iostream>
using namespace std;
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[1][o[i]]>s[1][o[i+1]])
{m=o[i];
o[i]=o[i+1];
o[i+1]=m;
gata=0;}}
while(!gata);}
int main()
{cout<<"n=";
cin>>n;
for(i=1;i<=n;i++)
{o[i]=i;
cout<<"ora de inceput pentru spectacolul "<<i<<" (hh
mm)= ";
cin>>h1>>m1;
s[0][i]=h1*60+m1;
cout<<"ora de sfirsit pentru spectacolul "<<i<<" (hh mm)=
";
cin>>h2>>m2;
s[1][i]=h2*60+m2;}
sortare();
cout<<"ordinea spectacolelor este "<<endl<<o[1]<<endl;
ora=s[1][o[1]];
for(i=2;i<=n;i++)
{if(s[0][o[i]]>=ora)
{cout<<o[i]<<endl;
ora=s[1][o[i]];}}
return 0;}
 O persoana are un rucsac cu ajutorul căruia poate transporta o
greutate maxima G. Persoana are la dispoziţie n obiecte si cunoaște pentru fiecare obiect
greutatea si câștigul care se obţine in urma transportului sau la destinatie. Se cere sa se
precizeze ce obiecte trebuie sa transporte persoana in așa fel incat câștigul sa fie maxim.
O precizare in plus transforma aceasta problema in alte doua probleme distincte.
Aceasta precizare se refera la faptul ca obiectele pot fi sau nu tăiate pentru transportul la
destinatie. In prima situaţie, problema poarta numele de problema continua a rucsacului, iar
in a doua avem problema discreta a rucsacului. Aceste doua probleme se rezolva diferit,
motiv pentru care ele sunt prezentate separat. Varianta continua a problemei rucsacului este
tratata in acest paragraf.
Algoritmul este următorul:
 se calculează, pentru fiecare obiect in parte eficienta de
transport rezultata prin impartirea câștigului la greutate;
 obiectele se sortează in ordine descrescătoare a eficientei de
transport si se preiau in calcul in aceasta ordine;
 castigul iniţial va fi 0, iar greutatea rămasa de încărcat va fi
G;
 atat timp cat nu a fost completata greutatea maxima a
rucsacului si nu au fost luate in considerare toate obiectele, se procedează astfel :
 dintre obiectele neîncărcate se selectează acela cu cea mai ridicata
eficienta de transport si avem doua posibilitati:
- acesta încape in totalitate in rucsac, deci se scade din greutatea rămasa de
încărcat greutatea obiectului, la câștig se cumulează câștigul datorat transportului acestui
obiect; se tipărește 1, in sensul ca întregul obiect a fost încărcat;
- obiectul nu încape in totalitate in rucsac, caz in care se calculează ce parte din
el poate fi transportata, se cumulează câștigul obţinut cu transportul acestei parţi din obiect,
se tipărește procentul care s-a încărcat din obiect iar greutatea rămasa de încărcat devine 0.
Dam un exemplu numeric. Greutatea care poate fi transportata cu ajutorul rucsacului este 3.
Avem la dispoziţie 3 obiecte. Greutatea si câștigul pentru fiecare obiect sunt prezentate mai
jos:
C 2 4 6
G 2 1 3

Eficienta de transport este 1 pentru primul obiect, 4 pentru al doilea si 2 pentru al treilea. In
concluzie, obiectul 2 se încarca in întregime in rucsac, obţinând un câștig de 4 si ramane o
capacitate de transport de 2 unitati de greutate. Se încarcă 2/3 din obiectul 3 pentru care se
obţine câștigul 4. Castigul obţinut in total este 8. Se remarca strategia GREEDY prin
alegerea obiectului care va fi transportat, alegere asupra căreia nu se revine.
// problema rucsacului
#include<iostream>
using namespace std;
double c[9],ef[9],g[9],gv,man,cistig;
int n,i,man1,inv,ordine[9];
int 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];
g[i+1]=man;
inv=1;
man1=ordine[i];
ordine[i]=ordine[i+1];
ordine[i+1]=man1;}}
while(inv);
i=1;
while(gv>0 && i<=n)
{if(gv>g[i])
{cout<<"obiectul "<<ordine[i]<<' '<<1<<endl;
gv-=g[i];
cistig+=c[i];}
else
{cout<<"obiectul "<<ordine[i]<<' '<<gv/g[i]<<endl;
cistig+=c[i]*gv/g[i];
gv=0;}
i++;}
cout<<"cistig total = "<<cistig;
return 0;}
Programarea orientată pe obiecte
Principiile programării orientate pe obiecte
5.2 Încapsularea

5.2.1 Definirea claselor

Definitie. Prin încapsulare intelegem mecanismul prin care datele (variabilele) si functiile
(numite in acest caz si metode) sunt plasate impreuna, intr-o unica structura.

In C++ aceasta se realizeaza printr-un tip abstract, numit class. In C++ se face distinctie intre
clasa, care este tipul abstract, si obiectul propriu-zis, care este instantierea clasei respective.
Iată formă simplificată a tipului de clasă:
class
{ [private]
date;
metode;
[protected]
date;
metode;
[public]
date;
metode; }
Între paranteze drepte sunt trecuţi modificatorii public, protected, private. Un modificator
are efect până la întâlnirea altuia, sau pînă la sfîrșitul definiţiei.
- modificatorul private are rolul de a interzice accesul la date și metode din afara
obiectului;
- modificatorul public are rolul de a permite accesul afara obiectului la date și metode;
- modificatorul protected va fi prezentat în alt context.
În absenţa modificatorilor, este interzis accesul la date și metode din afara obiectului.
Exemplu.
La matematică va-ti întâlnit cu numere complexe! Ele sunt de forma: z=x+iy, unde x, y
R. In C++ există mai multe tipuri care reţin numere reale! Prin mecanismul OOP
incercăm să construim singuri un tip care permite memorarea și prelucrarea numerelor
complexe.
class complex Un numar complex cuprinde:
{ - partea reala x;
public: - partea imaginara y;
float x, y; - o functie care returneaza modulul:
float modul();
};
Descrierea numarului complex o vom face intr-un tip separat, numit complex:
#include<math.h>
class complex
{public:
float x, y;
float modul();};
float complex: :modul()
{return sqrt(x*x+y*y);}
 Definirea funcţiei modul (metodă a clasei complex) s-a făcut în afara clasei
respective. Pentru a preciza faptul că funcţia este metodă a clasei complex, utilizâm în
antetul definiţiei operatorul “: :”, numit și operator de rezoluţie:
float complex: :modul()
Există posibilitatea ca funcţia să fie definită în interiorul clasei. In acest caz ea va fi tratată de
compilator ca funcţiei inline. Reamintim că pentru o astfel de funcţie orice apel este înlocuit
prin codul ei. Astfel se obţine o economie de timp - cea necesară saltului la cod și revenirii -
dar se mărește codul programului. Din acest motiv vom prefera să definim funcţia în afara
clasei.
Ex 1: Programul următor utilizează un obiect complex pentru care atribuie valori părtii reale
si imaginare apoi le tipăreste împreună cu modulul acestuia.
#include<iostream>
#include”[Link]”
using namespace std;
int main()
{complex z ;
z.x=3; z.y=4;
cout<<z.x<<” “<<z.y<<” “ <<[Link]();
return 0;
getche();}
 Obiectul propriu zis a fost declarat ca o variabilă. El se numeste z.
 Obiectul este, de fapt, instantierea clasei din care face parte.
 Ca si în cazul tipului struct accesul la datele(metodele) membre se realizează prin
numele obiectului urmat de operatorul ”.” si de numele variabilei (metodei): z.x, z.y,
[Link]().
Prin utilizarea obiectului de tip complex, construit acum, putem lucra “aproape” la fel ca
si cum limbajul ar contine tipul complex.
Ex 2: Programul următor citeste un vector de numere complexe si tipareste cel mai mare
modul al acestora.
#include<iostream>
#include”[Link]”
using namespace std;
int main()
{complex v[100] ;
int n,i;
float max;
cout<<”n=”;cin>>n;
for(i=0;i<n ;i++)
{cout<<”Numarul complex “<<i+1<<endl;
cout<<”Partea reala “; cin>>v[i].x;
cout<<”Partea imaginara ” cin>>v[i].y;}
max=v[0].modul();
for(i=1;i<n;i++)
if(v[i].modul()>max)
max=v[i].modul();
cout<<max;
return 0;}
Ex 3: Programul următor sorteaza un vector de numere complexe, descrescator, in functie de
modulul lor.
#include<iostream>
#include”[Link]”
using namespace std;
int main()
{complex v[100], man;
int n,i, inversari;
cout<<”n=”;cin>>n;
for(i=0;i<n ;i++)
{cout<<”Numarul complex “<<i+1<<endl;
cout<<”Partea reala “; cin>>v[i].x;
cout<<”Partea imaginara ” cin>>v[i].y;}
do
{inversari=0;
for(i=0;i<n-1;i++)
if(v[i].modul()<v[i+1].modul())
{man=v[i];
v[i]=v[i+1];
v[i+1]=man;
inversari=1;}}
while(inversari);
for(i=0;i<n;i++)
cout<<v[i].x<<” “<<v[i].y<<endl;
return 0;}

- Observati că, în cazul obiectelor, atribuirea este permisă întocmai ca la variabile. În


fapt, o atribuire constă într-o copiere a datelor unui obiect, în altul. O astfel de
atribuire, se numeste în termenii OOP copiere bit cu bit.
- Atentie! Copierea bit cu bit se poate face doar în anumite cazuri. Cazurile când aceasta
nu se poate face vor fi semnalate la momentul potrivit.
 Datele si metodele unui obiect pot fi accesate pornind de la un pointer către acesta. De
asemenea, obiectele pot fi alocate in HEAP. Întrucât nu există modificări fată de ceea
ce cunoastem deja, ne mărginim să dăm un exemplu în care un obiect de tipul
complex este alocat în HEAP.
#include<iostream>
#include”[Link]”
using namespace std;
int main()
{ complex *adr_comp=new complex;
adr_comp->x=3;
adr_comp->y=4;
cout<<adr_comp->modul() ;
delete adr_comp ;
return 0;}
 Obiectele pot fi descrise si cu struct, la fel cum am făcut-o acum cu ajutorul lui class.
Diferenta este, in acest caz, că datele si metodele sunt implicit publice, fată de
class, unde acestea sunt implicit private.

5.2.2 Constructori si destructori

Revenim asupra declaratiilor obiectelor. Fie declaratia: complex a, b; Au fost declarate două
obiecte numite a si b.
Întrebarea 1: care este mecanismul ce permite declararea (instantierea) obiectelor?
În absenta altor definitii, clasei i se atasează în mod implicit o metodă specială, numita,
constructor, care are rolul de a permite declararea obiectelor. În acest fel se alocă spatiul de
memorie necesară obiectelor declarate. De asemenea, numele lor pot fi folosite pentru
accesarea datelor si metodelor. Un astfel de constructor il numim constructor generat implicit
si are următoarele caracteristici:
- nu are parametrii formali;
- consecinta absentei parametrilor formali este dată de faptul că nu este permisă initializarea
la declarare a datelor membru;
- nu se generează, în cazul în care clasa are atasat un alt constructor, fara parametri.
Întrebarea 2: programatorul poate înzestra clasa cu o metodă constructor proprie?
Răspunsul este afirmativ, cu mentiunea că este necesar să respectăm anumite reguli
suplimentare, comparativ cu o metodă obisnuită.
- O metodă constructor are întotdeauna numele clasei din care face parte. Ea este apelată
automat la declararea obiectelor.
- Un constructor este o functie fără tip. Cu toate acestea, în dreptul tipului nu se trece
cuvântul cheie void.

Ex 1: Clasa întreg este înzestrată cu un constructor implicit. Cu ajutorul lui instantiem


obiectele x, y, z.
class intreg
{public:
int a;};
int main()
{intreg x, y, z;}
Ex 2: Atasăm clasei întreg un constructor fără parametri. În acest caz nu se atasează implicit
alt constructor. Programul tipăreste de 4 ori sirul “trece” pentru că au fost
declarate(instantiate) 4 obiecte. Aceasta înseamnă că s-a apelat constructorul de 4 ori.
#include<iostream>
using namespace std;
class intreg
{public:
int a;
intreg();};
intreg::intreg()
{cout<<”trece”<<endl ;}
intreg k;
int main()
{intreg x, y, z;
return 0;}
Ex 3: Programul următor prezintă un exemplu de utilizare a unui constructor cu parametru
implicit. La fiecare apel se afisează valoarea de initializare.
#include<iostream>
using namespace std;
class intreg
{public:
int a;
intreg(int v=0);};
intreg::intreg(int v)
{cout<<”trece”<<v<<endl ;}
intreg k;
int main()
{intreg x(3), y=4, z;
return 0;}
După cum observati, obiectele se declară în trei moduri:
a) Prin utilizarea operatorului de atribuire "=". În acest caz, valoarea aflată în dreapta
operatorului este convertită către tipul int (tipul parametrului formal al costructorului);
b) Prin utilizarea perechii de paranteze rotunde. Efectul este acelasi, dar apelul
constructorului este făcut întocmai ca apelul unei metode.
c) Obiectul se declară fără a fi initializat explicit (se initializează cu valoarea implicită).
- Analiza exemplului de mai sus conduce la observatia: constructorii obiectelor globale
se apelează primii.
- Stiati că si variabilele clasice se pot declara asa? Ce concluzie trageti de aici?
int x=3, y(4);
cout<<x<<” ”<<y ;
Întrebarea 3: O clasă poate avea mai multi constructori?
Răspunsul este afirmativ, dar există o restrictie: metodele constructor trebuie să difere
prin numărul si tipul parametrilor formali. Problema se reduce la apelul mai multor functii
(metode) care au acelasi nume, dar parametri formali diferiti, adică a supraîncărcării
functiilor!
Ex: Vom înzestra clasa complex cu doi constructori, unul cu doi parametri, altul cu un
singur parametru:
class complex
{public:
float x, y;
float modul();
complex(int v1, int v2);
complex(int v=0);};
complex: :complex(int v1, int v2)
{ x=v1 ;y=v2 ;}
complex : :complex(int v)
{ x=1 ;y=v ;}

Iata si un exemplu de utilizare:
#include<iostream>
#include”[Link]”
using namespace std;
int main()
{complex z1(3,4), z2(5) ;
cout<<z1.x<<” “<<z1.y<<” “ <<z2.x<<” “<<z2.y ;
return 0;}
In conditiile de mai sus priviti declaratiile:
complex z1(3,4), z2(5), z3=z1 ;
Întrebarea 4: Primele două declaratii au fost explicate. În schimb, ultima. z3=z1, ar
părea fără sens. După cum am văzut parametrul constructorului este de tip int. Aceasta
înseamnă că lui z1 nu i se poate atribui parametru. Atunci?
În realitate există două tipuri de constructori. Un tip de constructor care asigură alocarea
si initializarea obiectelor, altul de copiere, care permite atribuirea datelor unui obiect
altuia.
- Orice obiect este înzestrat cu un constructor implicit de copiere. În caz
contrar, atribuirea de mai sus ar fi semnalată ca eroare.
- Constructorul implicit de copiere realizează o copiere "bit cu bit" a datelor membru.
- Există posibilitatea ca programatorul să scrie si constructorul de copiere.
Iată, exemplu, cum arată acesta în cazul clasei complex:
class complex
{public:
float x, y;
float modul();
complex(int v1, int v2);
complex(int v=0);
complex (complex& v);};

complex: :complex (complex& v)
{cout<<”copiez”<<endl;
x=v.x;
y=v.y ;}
- La atribuire se afisează sirul "copiez". Aceasta are rolul de a testa apelul
constructorului: complex z3=z1;
- Observati ca parametrul unui constructor de copiere este chiar de tipul clasei din care
face parte constructorul respectiv.
- Transmiterea prin referintă este preferată transmiterii prin valoare. În practică
obiectele pot contine multe date si transmiterea prin valoare ar încărca stiva inutil.
Dacă alocarea obiectelor se face prin constructori, dezalocarea lor se face prin destructori.
În cazul în care nu a fost definit un destructor se generează automat unul.
Pentru a defini un destructor trebuie să tinem cont de următoarele:
- numele destructorului coincide cu numele clasei căreia îi apartine si este precedat de
"~";
- destructorul nu are parametri formali;
- o clasă are un singur destructor;
- destructorul se apelează automat atunci când viata unui obiect incetează (la sfârsitul
executării programului, pentru obiectele globale, la iesirea din blocul respectiv, pentru
obiectele automatice);
- destructorul poate fi apelat explicit. Întrucât apelul explicit al destructorului se
realizează, de regula, in legătură cu dezalocarea, din HEAP a obiectelor create
dinamic, vom reveni asupra sa, atunci când vom realiza primele aplicatii.
5.2.3 Supraincarcarea operatorilor

Ne propunem să adăugăm clasei complex metode de care permit operatii aritmetice cu


numere complexe. De exemplu, să adaugam o metodă adun care are rolul de a aduna două
numere complexe. Unul dintre ele este obiectul curent (cel prin care se apelează functia), iar
celălalt este transmis prin valoare. Iată cum arată clasa complex după adăugarea metodei
adun:
class complex
{public:
float x, y;
float modul();
complex(float v1, float v2);
complex(float v=0);
complex (complex& v);
complex adun(complex z);};
complex complex: :adun (complex z)
{complex t ;
t.x=x+z.x ;
t.y=y+z.y ;
return t ;}
Programul următor aduna doua numere complexe, z1 – obiectul curent – si z2 – obiectul
transmis ca parametru – iar rezultatul este atribuit lui z1.

int main()
{complex z1(3, 4), z2(7, 5) ;
z1=[Link](z2) ;
cout<<z1.x<<” ”<<z1.y<<” ”<<z2.x<<” ”z2.y;}
Dupa cum observam, apelul metodei nu este elegant, fiind departat de cel cunoscut in
matematica. Exista si o alta posibilitate si anume utilizarea functiilor operator.
Functia (metoda) de mai sus (adun) se va numi de aceasta data operator+. Cuvintul operator
este cuvint cheie. Prin urmare, prototipul si definitia devin:
complex operator+(complex z);
complex complex: :operator+(complex z)
{complex t ;
t.x=x+z.x ;
t.y=y+z.y ;
return t ;}
In aceste conditii funtia poate fi apelata in doua moduri:
1. z1=z1+operator+(z2);
2. z1=z1+z2;
Forma 2 este ideala.
In mod practic, operatorul ’+’ este folosit si in alt scop decit cel folosit de limbaj. Cu alte
cuvinte a fost supraincarcat.
Mai jos este prezentata noua clasa complex:
#include<math.h>
class complex
{public:
float x, y;
float modul();
complex(float v1, float v2);
complex();
complex operator+(complex z);
complex operator-(complex z);
complex operator*(complex z);
complex operator/(complex z);};
complex: :complex()
{x=0 ; y=0;}
complex: :complex(float v1, float v2)
{x=v1 ; y=v2;}
complex complex: :operator+(complex z)
{complex t ;
t.x=x+z.x;
t.y=y+z.y;
return t ;}
complex complex: :operator-(complex z)
{complex t ;
t.x=x-z.x;
t.y=y-z.y;
return t ;}
complex complex: :operator*(complex z)
{complex t ;
t.x=x*z.x-y*z.y;
t.y=x*z.y+z.x*y;
return t ;}
complex complex: :operator/(complex z)
{complex t ;
float num=z.x*z.x+z.y*z.y ;
if (num)
{t.x=(x*z.x+y*z.y)/num;
t.y=z.x*y-x*z.y)/num;}
else cout<<” impartire la 0 ” ;
return t ;}
float complex: :modul()
{return sqrt(x*x+y*y);}
Ex 1: Se citesc z1, z2 numere complexe si n număr natural. Se cere să se scrie un program
care calculează (z1/z2)n.
#include<iostream>
#include”[Link]”// fisier in care este declarata using
namespace std; // si definita clasa complex
int main()
{complex z1, z2, z3, z4(1,0) ;
int n, i ;
cout<<”n=”;
cin>>n;
cout<<”z1”<<endl;
cin>>z1.x<<z1.y;
cout<<”z2”<<endl;
cin>>z2.x<<z2.y;
z3=z1/z2 ;
for(i=1;i<=n;i++)
z4=z4*z3;
cout<<z4.x<<” “<<z4.y;
return 0;}

Observatii importante:
- Functiile obtinute prin supraîncărcarea operatorilor au aceeasi prioritate si asociativitate cu
operatorii respectivi;
- Functiile obtinute prin supraîncărcarea operatorilor au aceeasi n-aritate ca si
operatorii respectiv. Astfel:
 Dacă operatorul este unar si functia este unară deci nu are parametri;
Dacă operatorul este binar si functia este binară. Aceasta înseamnă că are un singur
parametru.
- Operatorul ’?:’ nu poate fi supraîncărcat.
- In cazul supraîncarcarii operatorilor "++" si " - - " nu se face distinctie între aplicarea a
postfixată si prefixată.
Anumiti operatori prezintă particularităti în legătură cu supraîncărcarea. Vom prezenta câtiva
dintre acestia.
Ex 2: Supraincărcarea operatorului [ ].
Acest operator este binar. De exemplu a[3] se poate scrie ca *(a+3). Vom lua un
exemplu, simplu. Dorim sa retinem un vector, ca tip class. Astfel, o functie va putea intoarce
un vector, ceea ce constituie un avantaj. De asemenea, dorim ca data membru sa fie privata si
accesul la ea sa aiba loc exclusiv prin intermediul unor metode. In aceste conditii obtinem
programul următor:
#include<iostream>
using namespace std;
class vector
{int v[100];
public:
void in(int,int);
int out(int);};
void vector::in(int i, int x)
{v[i]=x ;}
int vector::out(int i)
{return v[i] ;}
int main()
{vector a ;
[Link](3,5);
cout<<[Link](3);}
Observati modul de accesare a unei componente:
- [Link](3,5); componenta 3 ia valoarea 5.
- [Link](3); valoarea retinuta de componenta 3.
Accesul la componentele vectorului este deosebit de greoi. Suntem tentati sa avem un acces
de genul a[3]. Aceasta conduce la ideea supraincarcarii operatorului [ ]. Pornim de la forma
sa generala:
expresie1[expresie2]
Functia corespondenta ei este operatorul[ ]. Vom inlocui expresie1 cu obiectul curent, iar
expresie2 cu indicele de adresare. Metoda operator[ ] va intoarce un sinonim al componentei
accesate.
#include<iostream>
using namespace std;
class vector
{int v[100];
public:
int& operator[](int i);};
int& vector: :operator[](int i)
{return v[i] ;}
int main()
{vector a ;
a[3]=5;
cout<<a[3];}
Ex 3: Supraincărcarea operatorului ( ).
De regula, operatorul ( ) este supraincarcat in scopul permiterii accesului simplificat la datele
unui obiect. In exemplul următor supraincarcam operatorul ( ) pentru a avea acces simplificat
la elementele unei matrice.
#include<iostream>
using namespace std;
class matrice
{int v[10] [5];
public:
int& operator( ) (int i, int j);};
int& matrice::operator( ) (int i, int j)
{return v[i] [j] ;}
int main()
{matrice a ;
a(3,2)=71;
cout<<a(3,2);}
Ex 4: Supraincărcarea operatorului = .
Se pot face atribuiri între obiecte, asa cum suntem obisnuiti. În astfel de cazuri avem o
copiere bit de bit. Mecanismul este simplu: fiecare dată a obiectului aflat in dreapta
operatorului este copiată către data corespunzătoare aflata în stânga.
Nu întotdeauna o astfel de atribuire este satisfăcătoare. Pentru a demonstra aceasta vom porni
de la un exemplu. Programul următor utilizează o clasă numită string. Clasa a fost
construită in scopul efectuării anumitor operatii cu siruri de caractere. Din acest
motiv, fiecare obiect contine adresa din HEAP a sirului atasat, dar nu contine sirul.
Fie două obiecte a si b. Presupunem că a retine adresa sirului "un text", iar b retine adresa
sirului "alt text". Scriem expresia a=b. În ipoteza în care operatorul = nu este supraîncărcat
obiectul a va retine adresa sirului atasat lui b. Pe de o parte, sirul care avea adresa în obiectul
a se pierde, dar rămâne alocat în HEAP, chiar dacă nu mai avem adresa lui. Pe de altă parte,
a si b vor retine adresa unui sir. Dacă, de exemplu, stergem pe b, a va retine adresa unui sir
inexistent.
Datorita celor aratate, inzestram clasa cu un constructor de copiere care efectueaza
urmatoarele:
- alocă spatiu în HEAP – un număr de octeti care sa permita copierea;
- copiază sirul.
Tot asa, supraîncărcam operatorul = cu o metodă care efectuează următoarele:
- eliberează spatiul ocupat de sirul a;
- alocă spatiu în HEAP - număr de octeti care să permită copierea;
- efectueaza copierea.
#include<iostream>
#include <cstring>
using namespace std;
class string
{char* adr;
public:
//constructor
string(char sir[])
{adr=new char[strlen(sir)+1];
strcpy(adr,sir);}
//constructor de copiere
string(string& x)
{adr=new char[strlen([Link])+1];
strcpy(adr, [Link]);}
void operator=(string& x)
{delete adr;
adr=new char[strlen([Link])+1];
strcpy(adr, [Link]);}
void tip()
{cout<<adr<<endl;}};
int main()
{string a(“mama”),b(“bunica”),c=a;
[Link]();[Link]();[Link]();
a=b;[Link]();[Link]();}

5.2.4 Functii prieten

Există posibilitatea ca anumite functii, care nu apartin unei clase să poată accesa datele
private ale acesteia, caz in care functiile respective se numesc „functii prieten”.
Pentru a atasa unei clase o functie prieten introducem în interiorul definitiei sale prototipul
functiei prieten, precedat de cuvântul cheie friend.
O functie prieten nu poate accesa datele direct, asa cum poate o metodă a clasei respective, ci
prin intermediul parametrului de tip obiect transmis.
Ex 1: Clasa complex, definită mai jos, are functia prieten modul(), care returnează modulul
numărului complex.
#include<iostream>
#include<math.h>
using namespace std;
class complex
{float x, y;
public:
complex(float, float);
friend float modul(complex& z);};
complex::complex(float v1, float v2)
{x=v1;
y=v2;}
float modul(complex& z)
{return sqrt(z.x*z.x+z.y*z.y);}
int main()
{complex z(3,4);
cout<<modul(z);}
În C++ operatorii pot fi supraîncărcati prin functii prieten.
Ex 2: Dorim sa efectuam suma dintre un număr complex si unul real. Pentru aceasta vom
folosi o functie prieten care supraîncarcă operatorul +. Pentru ca o operatia să fie comutativa,
adică să se poată face si suma dintre un număr real si un număr complex,
vom folosi o altă functie prieten, în care ordinea parametrilor este inversă fată de cea
anterioară.
Vezi exemplul următor:
#include<iostream>
#include<math.h>
using namespace std;
class complex
{float x, y;
public:
complex(float, float);
friend float modul(complex& z);
friend complex operator+(complex& z, float nr);
friend complex operator+( float nr, complex& z);};
complex::complex(float v1, float v2)
{x=v1;
y=v2;}
float modul(complex& z)
{return sqrt(z.x*z.x+z.y*z.y);}
complex operator+(complex& z, float nr)
{complex t(z.x+nr, z.y) ;
return t ;}
complex operator+( float nr, complex& z)
{complex t(z.x+nr, z.y) ;
return t ;}
int main()
{complex z(3,4);
z=z+7 ;
z=3+z ;
cout<<modul(z);}

5.2.5 Aplicatii ale incapsularii


[Link] Multimi

Limbajul C++ nu are un tip special destinat lucrului cu multimi.


In acest paragraf vom creea un tip obiect, numit Multime cu ajutorul căruia putem lucra cu
multimi de numere naturale intre 0 si 799!
Cum gândim crearea unui astfel de tip?
1. Pentru a memora dacă un anumit element apartine sau nu unei multimi este suficient un
bit. Cum un octet are 8 biti, pentru a putea retine o multime cu 800 elemente sunt necesari
100 de octeti. Prin urmare, un astfel de obiect contine o variabilă, numita Mult cu 100 de
octeti(componente de tip char). Pentru memorare utilizăm mecanismul prezentat în manualul
pentru clasa a 10-a, dar poate fi cu usurintă dedus din codul sursă al metodelor utilizate.
2. Orice lucru cu o multime presupune posibilitatea de adaugare a unui anumit element
multimii (de a face reuniunea dintre o multime dată si una care contine un singur element).
Aceasta este făcută în mod direct prin intermediul functiei de tip prieten rezultată prin
supraîncărcarea operatorului + cu doi parametri, unul de tip Multime, transmis prin referinta,
si altul de tip int. Intrucit operatia este comutativa, vom utiliza, de fapt, doua functii care
difera doar prin ordinea parametrilor.
3. Pentru a putea testa daca un anumit element apatine sau nu multimii considerate, este
necesara existenta unei metode, numita in, cu un singur parametru, care contine elementul ce
se testeaza daca apartine sau nu multimii.
4. Penru a putea initializa o multime cu multimea vida vom utiliza un constructor. El are
rolul de a initializa obiectul cu multimea vida.
5. Pentru a putea efectua operatiile cunoscute cu multimi vom utiliza metode rezultate prin
supraincarcarea operatorilor + pentru reuniune, - pentru diferenta, si * pentru intersectie.
class Multime
{char Mult[100];
public:
Multime( );
int in(int n);
Multime operator+(Multime M);
Multime operator-(Multime M);
Multime operator*(Multime M);
friend Multime operator+(Multime& M, int n);
friend Multime operator+(int n, Multime& M);};
Multime: :Multime()
{for (int i=0; i<100; i++)
Mult[i]=0;}
int Multime: :in(int n)
{unsigned char Nr_Bit=7-n%8, masca=1 ;
int Nr_Octet=n/8 ;
masca=masca<<Nr_Bit ;
if (Mult[Nr_Octet] & masca)
return 1;
else
return 0;}
Multime operator+(Multime& M, int n)
{Multime M1=M;
unsigned char Nr_Bit=7-n%8, masca=1 ;
int Nr_Octet=n/8 ;
masca=masca<<Nr_Bit ;
[Link][Nr_Octet]=[Link][Nr_Octet] | masca;
return M1;}
Multime operator+( int n, Multime& M)
{Multime M1=M;
unsigned char Nr_Bit=7-n%8, masca=1 ;
int Nr_Octet=n/8 ;
masca=masca<<Nr_Bit ;
[Link][Nr_Octet]=[Link][Nr_Octet] | masca;
return M1;}
Multime Multime : : operator+ (Multime& M)
{Multime M1;
int i;
for (int i=0; i<100; i++)
[Link][i]= Mult[i] | [Link][i];
return M1;}
Multime Multime : : operator- (Multime& M)
{Multime M1;
int i;
for (int i=0; i<100; i++)
[Link][i]= Mult[i] & (Mult[i] ^ [Link][i]);
return M1;}
Multime Multime : : operator* (Multime& M)
{Multime M1;
int i;
for (int i=0; i<100; i++)
[Link][i]= Mult[i] & [Link][i];
return M1;}
Ex 1: Se genereaza doua multimisi se afiseaza reuniunea lor.
#include<iostream>
#include „[Link]”
using namespace std;
int main()
{Multime M, M1, M2;
int i;
M=M+15;
M=M+7;
M=M+23;
M=17+M;
M=M+799;
M1=M1+11;
M1=7+M1;
M1=0+M1;
M1=1+M1;
for (i=0; i<800; i++)
if ([Link](i))
cout<<i<<” “;}
Ex 2: Se citeste un sir de caractere. Se cere sa se tipareasca literele distincte care apar in sir.
De aceasta data este necesar sa memoram caractere, nu numere. Dar asta nu-i o problema.
Pentru fiecare caracter se memoreaza codul sau, fara nici o alta modificare, iar la afisare se
tipareste caracterul (se converteste codul in caractere prin utilizarea operatorului de conversie
explicita).
#include<iostream>
#include „[Link]”
using namespace std;
int main()
{Multime M;
int i;
char sir[20];
[Link](sir, 20);
i=0;
while (sir[i])
{if (sir[i]=’ ’)
M=M+sir[i];
i++;}
for (i=0; i<256; i++)
if ([Link](i))
cout<<(char)i;}

5.3 Mostenirea

5.3.1 Notiuni de baza care privesc mostenirea

Definitie. Prin mostenirese intelege acea proprietate a claselor prin care un tip nou construit
poate prelua datele si metodele unui tip mai vechi. In C++ acest mecanism mai este cunoscut
sub numele de „derivare a claselor”.
 Evident, clasei nou construite i se pot adauga noi date si metode.
 Prin acest procedeu se preia soft deja facut care se dezvolta. Avantajul urias al acestui
procedeu este ca persoana care preia un anumit soft trebuie sa cunoasca doar
documentatiade utilizare a soft-ului preluat.
Ideea de baza este urmatoarea: fiind date clasele X1, X2, …Xn, putem construi clasa Xn+1 care
are, pe linga datele si metodele claselo enumerate, date si metode proprii. Clasele X 1, X2, …
Xn se numesc clase de baza, iar clasa Xn+1 clasa derivata.

Ex 1: Clasa numar contine o data membru n si o metoda membru tip(). Clasa numar_d este
derivata din clasa numar. Prin urmare, un obiect obtinut prin instantierea acesteia, are data
membru n si metoda tip(). Pe linga acestea, clasa numar_d are o noua metoda tip(). In
paragraful următor vom analiza rolul modificatorului public din:
class numar_d : public numar
#include<iostream>
using namespace std;
class numar
{public :
int n;
void tip()
{cout<<n<<endl;}};
class numar_d : public numar
{public :
void cit()
{cout<<”n=”;
cin>>n;}};
int main()
{numar_d a;
[Link]();
[Link]();}
Ex 2: Clasa Complex ne permite sa lucram usor cu numere complexe. In timpul utilizarii
acestei clase ne-am dat seama ca ar fi bine sa contina o metoda booleana numita real( ), care
ne permite sa testam daca numarul complex este real sau nu. Dispunem de documentatia
clasei Complex. Nu-i o problema sa definim o clasa noua, numita Complex_d, care contine
datele si metodele clasei Complex, dar care dar care include si noua metoda.
#include<iostream>
#include”[Link]”
using namespace std;
class complex_d : public complex
{public :
int real;};
int complex_d : : real( )
{return y==0;}
int main()
{complex_d z;
z.x=3;
z.y=1;
if ([Link]())
cout<<”numarul este real”;
else
cout<<”numarul nu este real”;}
 Unui obiect al clasei de baza i se poate atribui un obiect al unei clase care este derivata
din ea.
 Unui obiect al unei clase derivate nu i se poate atribui un obiect al clasei de baza. Se
presupune ca o clasa derivata are date in plus.
Ex 3: Nimeni nu ne opreste sa inzestram clasa derivata cu o metoda obtinuta prin
supraincarcarea operatorului =. Dar si aici datele care apartin exclusiv clasei derivate iau
valori arbitrare (in exemplul următor 0).
#include<iostream>
using namespace std;
class numar
{public:
int m;};
class numar_d : public numar
{public :
int n;
void operator=(numar& x)
{m=x.m;
n=0;}};
int main()
{numar a;
numar_d b;
b.m=3;
b.n=4;
a=b;
cout<<a.m<<endl;
b=a; //nu ar fi permisa
cout<<b.m<<” ”<<b.n;}
 O clasa derivata poate contine o functie cu acelasi nume si cu aceeasi lista de
parametri formali ca una a clasei de baza.
Ex 4: In exemplul următor clasa de baza (numar) are o metoda numita tip( ), in care lista
parametrilor formali este vida. Clasa derivata are pe linga metoda tip( ), mostenita, o alta
numita tot tip( ), cu aceeasi lista a parametrilor formali (tot vida). Prin [Link]( ) se apeleaza
metoda tip( ) proprie clasei derivate. In cazul in care dorim sa apelam metoda tip( ) a
clasei de baza scriem: [Link] : : tip( ). Cele doua metode cu acelasi nume difera doar
prin mesajul afisat inaintea tiparirii valorii retinute de variabila.
#include<iostream>
using namespace std;
class numar
{public:
int i;
void tip( )
{cout<<”numar”<<i<<endl;}};
class numar_d : public numar
{public :
void tip( )
{cout<<”numar_d”<<i<<endl;}};
int main()
{numar_d a;
a.i=3;
[Link]( );
a=b;
[Link] : : tip( );}
 In situatia in care in clasa derivata se defineste o noua data cu aceleasi nume ca una a
clasei de baza, nu se semnaleaza eroare. Un obiect rezultat ca instantiere a unei clase
derivate va avea doua date cu acelasi nume. Implicit se adreseaza variabila definita in
clasa derivata, dar este posibil sa adresam si data clasei de baza la fel ca in cazul
functiilor.
Ex 4:
#include<iostream>
using namespace std;
class numar
{public:
int i;};
class numar_d : public numar
{public :
int i;};
int main()
{numar_d a;
a.i=2;
[Link] : : i=3;
cout<<a.i<<” “<<[Link] : : i;}

5.3.4 O aplicatie a mostenirii

In urma utilizarii obiectelor Multime s-a constatat ca ar mai fi nevoie de doua metode:
1. Un constructor care sa permita citirea unei multimi dintr-un fisier text,in care elementele
se gasesc toate pe o linie.
2. O metoda care sa returneze numarul de elemente ale multimii, numita Card( ).
Ex 1: Pentru aceasta vom construi o noua clasa numita Multime_g, care mosteneste clasa
Multime. La noul tip de obiect se adauga metodele de mai sus si se obtine:
#include<fstream>
#include „[Link]”
using namespace std;
class Multime_g : public Multime
{public:
Multime_g(char Nume_fis[20]);
Multime_g {};
int Card( );};

Multime_g : : Multime_g (char Nume_fis[20])


{Int n, Nr_Octet;
unsigned char Nr_bit, masca;
fstream f(Nume_fis, ios : : in);
while (f>>n)
{Nr_bit=7-n%8, masca=1 ;
Nr_Octet=n/8 ;
masca=masca<<Nr_Bit ;
Mult[Nr_Octet]= Mult[Nr_Octet] | masca;}}
int Multime_g : : Card( )
{int s=0,I;
for(i=0;i<800;i++)
if (in(i)) s++;
return s;}
Ex 2: Programul următor citeste o multime dintr-un fisier text, o afiseaza si tipareste
numarul de elemente pe care le contine multimea.
#include „[Link]”
using namespace std;
Multime_g A(“[Link]”);
int main()
{for(int i=0;i<=800;i++)
if ([Link](i))
cout<<i<<endl;
cout<<[Link]( );}

5.4 Polimorfism

Polimorfismul este legat de mostenire. Sa vedem in ce consta problema.


Fie o clasa O1 care contine o metoda M1 care o apeleaza pe alta M2 din aceesi clasa. Pina
aici nimic deosebit, este un caz pe care l-am intilnit. O alta clasa O2 o mosteneste pe prima.
In cadrul acesteia redefinim metoda M2, dar nu redefinim metoda M1. Declaram un obiect
de tip O2 si apelam metoda M1. Metoda apelata de M1 va fi M2 din O1.
Ex 1: Fie modulul următor, care exemplifica situatia prezentata: metoda care( ) apeleaza
metoda tip( ), redefinita.
#include<iostream>
using namespace std;
class unu
{public:
void care( )
{tip( );}
void tip( )
{cout<<”unu”<<endl;}};
class doi : public unu
void tip( )
{cout<<”doi”<<endl;}};
unu x ;
doi y ;
int main()
{[Link]( );
[Link]( );}
Evident, o astfel de situatie nu convine. De ce am redefinit metoda tip( ) daca este apelata tot
cea veche?
 O astfel de metoda de selectie a metodei prin obiectul care o apeleaza se numeste
selectie statica. Codul de apel al functiei este obtinut la compilare si ramine
nemodificat pe parcursul executarii programului.
Atunci cind a fost compilat obiectul numar, deci si metoda care( ), ea apela metoda tip( )
acolo definita. Prin urmare, aceasta explica situatia neplacuta.
Cum se poate remedia situatia, adica sa fie apelata metoda tip( ) redefinita?
 Exista posibilitatea ca selectia sa se faca in momentul apelului (nu la compilare). In
astfel de cazuri programatorul poate decide care metoda sa fie selectata. O selectie de
acest tip se numeste selectie dinamica sau virtuala.
Ex 2: Pentru a realiza selectia dinamica trebuie sa procedam astfel:
– cind se declara, in cadrul clasei de baza, o metoda ce va fi redefinita, se adauga
dupa declaratie cuvintul VIRTUAL;
– orice redefinire a metodei, in cadrul claselor derivate (descendente), se va face
utilizind acelasi cuvint cheie VIRTUAL;
#include<iostream>
using namespace std;
class unu
{public:
void care( )
{tip( );}
virtual void tip( )
{cout<<”unu”<<endl;}};
class doi : public unu
virtual void tip( )
{cout<<”doi”<<endl;}};
unu x ;
doi y ;
int main()
{[Link]( );
[Link]( );}
De aceasta data, selectia se va face corect, adica se apeleaza metoda tip( ) redefinita in clasa
doi.

Programare dinamică
6.1. Generalităţi

Fie o problemă a cărei rezolvare este cerută pentru un număr natural n dat. Uneori se
poate aplica un raţionament de genul: dacă știm să rezolvăm problema pentru toate valorile
strict mai mici decât n, atunci putem rezolva problema și pentru n dat. Dacă este așa, atunci,
pe baza aceluiași raţionament, înseamnă că dacă știm să rezolvăm problema pentru toate
valorile strict mai mici decât n-1, atunci știm să rezolvăm problema pentru n-1 și, așa cum
am arătat, pentru n. Repetând acest raţionament ajungem să rezolvăm problema pentru n=1
și, eventual, pentru n=2. O astfel de problemă este foarte ușor de rezolvat. După care
rezolvăm problema pentru n=3, apoi n=4, ș.a.m.d., până se ajunge la acel n cerut de
problemă. De aici rezultă necesitatea găsirii unor relaţii de recurenţă.
O persoană trebuie să urce n scări. Se știe că persoana respectivă poate urca fie o scară, fie
două deodată. Întrebarea este: în câte moduri poate urca persoana n scări?
O primă idee este de a descompune pe n ca sumă de 1 și 2 în toate modurile posibile și de a
număra soluţiile. Problema se poate rezolva aplicând tehnica Backtracking (exerciţiu!). Dar
în acest caz obţinem un timp exponenţial…
O rezolvare prin programarea dinamică se poate face în felul următor:
- dacă n=1, o scară se poate urca într-un singur fel;
- dacă n=2, se poate urca o scară și apoi o altă scară (un fel) sau deodată două scări (altă
modalitate), prin urmare, există două modalităţi de a urca două scări.
Până acum am rezolvat problema pentru n=1 și pentru n=2. Dacă notăm cu un numărul de
feluri în care se pot urca n scări, atunci știm că u1=1 și u2=2.
Acum, pentru a urca n scări putem proceda astfel: se urcă n-2 scări și deodată două scări sau
se urcă n-1 scări, după care se mai urcă o scară. În câte feluri se pot urca n-2 scări? În un-2
feluri. În câte feluri se pot urca n-1 scări? În un-1 feluri. Atunci, n scări se pot urca în un=un-
1+un-2 feluri. De ce le-am adunat? Pentru că în acest fel se obţin doar soluţii diferite. Orice
soluţie care provine din un-1 se termină prin a urca la sfârșit o scară și orice soluţie care
provine din un-2 se termină prin a urca la sfârșit două scări. Dacă soluţiile sunt diferite,
atunci se pot aduna.
Astfel, am obţinut o relaţie de recurenţă binecunoscută. Acum, problema se reduce la a
calcula un din această relaţie. Deja știm să rezolvăm o astfel de relaţie, revedeţi recursivitatea
(aţi întâlnit-o la șirul lui Fibonacci). Mai mult, un se poate calcula în O(n), adică liniar.
Tot atunci când aţi studiat recursivitatea aţi văzut că rezolvarea prin utilizarea mecanismului
recursivităţii a acestei relaţii este catastrofală din punct de vedere al timpului de calcul, fiind
exponenţială.
 În programarea dinamică, de cele mai multe ori, pentru a rezolva o problemă se scrie o
relaţie de recurenţă. Relaţia de recurenţă se rezolvă apoi iterativ.
Problema prezentată face parte din categoria problemelor de numărare. În acest capitol veţi
întâlni și alte probleme de numărare.
 Prin programare dinamică puteţi rezolva și probleme de optim în care se cere o soluţie care
să maximizeze sau să minimizeze o anumită funcţie. În acest caz, criteriile de mai jos vă pot
fi de folos. Menţionăm că înţelegerea lor se face în timp, studiind mai multe exemple, motiv
pentru care este bine ca, atunci când rezolvaţi o problemă, să reveniţi asupra lor.
Se consideră o problemă în care rezultatul se obţine ca urmare a unui șir de decizii D1, D2,...,
Dn. În urma deciziei D1, sistemul evoluează din starea S0 în starea S1, în urma deciziei D2,
sistemul evoluează din starea S1 în starea S2, ..., în urma deciziei Dn, sistemul evoluează din
starea Sn-1 în starea Sn.
Dacă D1, D2,....Dn este un șir de decizii care conduce sistemul în mod optim din S0 în Sn,
atunci trebuie îndeplinită una din condiţiile următoare (principiul de optimalitate):
1) Dk...Dn este un șir de decizii ce conduce optim sistemul din starea Sk-1 în starea Sn, ∀k,
1≤k≤n;
2) D1...Dk este un șir de decizii care conduce optim sistemul din starea S0 în starea Sk, ∀k,
1≤k≤n;
3) Dk+1...Dn, D1...Dk sunt șiruri de decizii care conduc optim sistemul din starea Sk în
starea Sn, respectiv din starea S0 în starea Sk, ∀k, 1≤k≤n.
⇒ Dacă principiul de optimalitate se verifică în forma 1), spunem că se aplică programarea
dinamică metoda înainte.
⇒ Dacă principiul de optimalitate se verifică în forma 2), spunem că se aplică programarea
dinamică metoda înapoi.
⇒ Dacă principiul de optimalitate se verifică în forma 3), spunem că se aplică programarea
dinamică metoda mixtă.
Programarea dinamică se poate aplica problemelor la care optimul general implică optimul
parţial.
Dacă drumul cel mai scurt între București și Suceava trece prin Focșani, atunci porţiunea din
acest drum, dintre București și Focșani, este cea mai scurtă, ca și porţiunea dintre Focșani și
Suceava (dacă n-ar fi așa, drumul considerat între București și Suceava nu ar fi optim).
Faptul că optimul general determină optimul parţial, nu înseamnă că optimul parţial
determină optimul general.
Fiind date drumurile cele mai scurte de la București la Cluj și de la Cluj la Suceava, nu
înseamnă că drumul optim de la București la Suceava trece prin Cluj.
Cu toate acestea, faptul că optimul general impune optimul parţial ne este de mare ajutor:
căutăm optimul general, între optimele parţiale, pe care le reţinem la fiecare pas. Oricum,
căutarea se reduce considerabil.
6.2. Problema triunghiului
 Enunţ. Se consideră un triunghi de numere naturale format din n linii. Prima linie conţine
un număr, a doua două numere, ..., iar ultima, n numere naturale. Cu ajutorul acestui triunghi
se pot forma sume de numere naturale în felul următor:
 se pornește cu numărul din linia 1;
 succesorul unui număr se află pe linia următoare plasat sub el (aceeași coloană) sau pe
diagonală la dreapta (coloana crește cu 1).
Care este cea mai mare sumă care se poate forma astfel și care sunt numerele care o
alcătuiesc?
Exemplu: Pentru n=4, se consideră triunghiul de mai jos:
2
35
634
5614
Se pot forma mai multe sume:
S1=2+3+6+5=16;
S2=2+5+4+1=12;
Sk=2+3+6+6=17 (care este și suma maximă).
 Rezolvare. Cum am putea rezolva această problemă?
A) O primă idee ar fi să încercăm o abordare a ei prin metoda Greedy. Aceasta presupune ca,
la fiecare pas, să selectăm de pe linia respectivă cel mai mare element dintre cele două care
pot fi alese. Astfel, de pe linia 1 selectăm 2, de pe linia a 2-a, 5, de pe linia a 3-a, 4, iar de pe
linia a 4-a, 4. Înseamnă că suma este 2+5+4+4=15. Observăm că în acest fel nu am reușit să
obţinem suma maximă. De ce? Când facem o alegere care maximizează suma la un moment
dat, s-ar putea să nu mai putem alege pentru liniile următoare elementele care maximizează
suma. Urmăriţi soluţia optimă din exemplul dat. Prin urmare, "soluţia" propusă nu este
corectă.
B) Să încercăm altfel. Se cere suma maximă. Atunci ar trebui să considerăm toate
"drumurile" posibile de la prima linie la ultima. Pentru fiecare astfel de drum să calculăm
suma care se poate forma și să selectăm suma maximă. Este corectă o astfel de rezolvare?
Evident, da. Cum am putea genera toate aceste drumuri? Dacă am spus "toate", atunci ne
gândim la metoda Backtracking. Mai jos, prezentăm programul obţinut aplicând acest
algoritm:
int t[50][50],sol[50],
drum[50],n,i,j,max=0;
void back(int k)
{int i,j,s=t[1][1];
if (k==n+1)
{for (i=2;i<=n;i++)
s+=t[i][sol[i]];
if (s>max)
{for (i=1;i<=n;i++)
drum[i]=sol[i];
max=s;}}
else
{for (i=sol[k-1];
i<=sol[k-1]+1;i++)
{sol[k]=i;
back(k+1);}}}
int main()
{cout<<"n="; cin>>n;
for (i=1;i<=n;i++)
for (j=1;j<=i;j++)
{cout<<"t["<<i<<','
<<j<<"]=";
cin>>t[i][j];}
sol[1]=1;
back(2);
cout<<"Suma maxima"<<endl;
for (i=1;i<=n;i++)
cout<<drum[i]<<" ";}
Să observăm că se pot forma 2n-1 sume de acest fel.
 Exerciţiu. Demonstraţi prin inducţie că numărul de sume care se pot forma este corect.
A lua în considerare toate aceste sume nu este eficient, pentru că avem un algoritm în O(2n).
Am putea accepta o astfel de soluţie, cu toate consecinţele ei, numai dacă n-ar exista o alta,
eficientă.
 Exerciţiu. Pentru a testa modul în care funcţionează acest algoritm, modificaţi programul
pentru a accepta intrări de la un fișier text, creaţi un astfel de fișier și analizaţi timpul în care
se obţine soluţia pentru n=40.
C) Încercăm să rezolvăm problema prin aplicarea metodei programării dinamice. Verificăm
principiul programării dinamice. Fie un șir de n numere care respectă condiţiile problemei și
care formează suma maximă: n1, n2, ..., ni, ..., nn. Este clar că numerele de la ni ... nn
formează o sumă maximă în raport cu sumele care se pot forma începând cu numărul ni.
Dacă această sumă nu ar fi maximă, atunci ar exista o altă alegere de numere care ar
maximiza-o. Înlocuind în șirul iniţial numerele de la ni ... nn cu cele alese pentru a maximiza
suma, vom obţine o soluţie în care suma este mai mare.
Pentru exemplul dat, cunoaștem soluţia optimă: 2+3+6+6. Aceasta înseamnă că, dacă de pe
linia 2 se pornește cu 3, cea mai mare sumă care se poate forma, în condiţiile problemei, este
3+6+6.
Aceasta contrazice ipoteza. În această situaţie, se poate aplica programarea dinamică, metoda
înainte.
Vom forma un triunghi, de la bază către vârf, cu sumele maxime care se pot forma cu fiecare
număr. Dacă citim triunghiul de numere într-o matrice T și calculăm sumele într-o matrice C,
vom avea relaţiile următoare:
C[n,1]:=T[n,1];
C[n,2]:=T[n,2];
C[n,n]:=T[n,n];
Pentru linia i (i<n), cele i sume maxime se obţin astfel:
C[i,j]=max{T[i,j]+C[i+1,j],T[i,j]+C[i+1,j+1]},
i∈{1,2,...,n-1}, j∈{1,...,i}.
Să rezolvăm problema propusă ca exemplu.
Linia 4 a matricei C va fi linia n a matricei T:
5 6 1 4.
Linia 3 se calculează astfel:
C[3,1]=max{6+5,6+6}=12;
C[3,2]=max{3+6,3+1}=9;
C[3,3]=max{4+1,4+4}=8;
Vom avea:
12 9 8
5614
Linia 2:
C[2,1]=max{3+12,3+9}=15;
C[2,2]=max{5+9,5+8}=14;
15 14
12 9 8
5614
Linia 1:
C[1,1]=max{2+15,2+14}=17;
17
15 14
12 9 8
5614
Aceasta este și cea mai mare sumă care se poate forma.
Pentru a tipări numerele luate în calcul se folosește o matrice numită DRUM în care pentru
fiecare i∈{1,...,n-1} și j∈{1,...,i} se reţine coloana în care se găsește succesorul lui T[i,j].
#include <iostream>
int t[50][50],c[50][50],
drum[50][50],n,i,j;
int main()
{ cout<<"n=";
cin>>n;
for (i=1;i<=n;i++)
for (j=1;j<=i;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;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]<<endl;
i=1; j=1;
while (i<=n)
{ cout<<t[i][j]<<endl;
j=drum[i][j];
i++;}}
6.3. Subșir crescător de lungime maximă
 Enunţ. Se consideră un vector cu n elemente întregi. Se cere să se tipărească cel mai lung
subșir crescător al acestuia.
Exemplu. Pentru n=5 se dă vectorul V=(4,1,7,6,7). În acest caz, subșirul tipărit va fi: 4,7,7.
 Rezolvare. Problema se poate rezolva pornind de la ideea de a calcula, pentru fiecare
element al vectorului, lungimea celui mai lung subșir crescător care se poate forma începând
cu el. În final, este selectat elementul din vector cu care se poate forma cel mai lung subșir
crescător și acesta este listat.
L(k)={1+max L(i)|V(i)≥V(k), i={k+1,...,n}},k∈{1,2,...,n}.
În practică, folosim un vector L cu n componente, unde L(k) are semnificaţia explicată.
Pentru exemplul nostru vom avea:
L=(3,3,2,2,1).
Componentele vectorului L au fost calculate astfel:
 cel mai lung subșir care se poate forma cu elementul 7, aflat pe ultima poziţie, are
lungimea 1;
 cel mai lung subșir care se poate forma cu elementul 6, aflat pe poziţia 4, are lungimea 2
(1+L(5)), pentru că pe poziţia 5 se găsește elementul 7 care este mai mare decât 6;
 cel mai lung subșir care se poate forma cu elementul aflat pe poziţia 3 are lungimea 2
(1+L(5)) deoarece 7 este egal cu 7;
 algoritmul continuă în acest mod până se completează L(1).
După aceasta se calculează maximul dintre componentele lui L, iar cel mai lung subșir
crescător format din elementele vectorului V va avea lungimea dată de acest maxim. Pentru a
lista efectiv acel subșir de lungime maximală se procedează astfel:
 se caută maximul din vectorul L precum și indicele t, la care se găsește acest maxim;
 se afișează V(t);
 se găsește și se listează primul element care este mai mare sau egalcu V(t) și are lungimea
mai mică cu 1 (max-1), se actualizează valoarea max cu max-1;
 algoritmul continuă până când se epuizează toate elementele subșirului.
Programul este următorul:
#include <iostream.h>
int v[20],l[20],n,i,k,max,t;
int main()
{ cout<<"n="; cin>>n;
for (i=1;i<=n;i++)
{ cout<<"v["<<i<<"]=";
cin>>v[i];}
l[n]=1;
for (k=n-1;k>=1;k--)
{ max=0;
for (i=k+1;i<=n;i++)
if (v[i]>=v[k] && l[i]>max)
max=l[i];
l[k]=1+max;}
max=l[1];
t=1;
for (k=1;k<=n;k++)
if (l[k]>max)
{ max=l[k];
t=k;}
cout<<"lungimea maxima:"<<max
<<endl<<v[t]<<endl;
for (i=t+1;i<=n;i++)
if (v[i]>v[t] && l[i]==max-1)
{ cout<<v[i]<<endl;
max--;}}
Exerciţiu. Cum verificaţi pentru această problemă principiul optimalităţii?
Complexitatea acestui algoritm este O(n2).
Pentru fiecare element al șirului se calculează un maxim. Aceasta presupune parcurgerea
șirului până la capăt. Pentru elementul aflat pe poziţia n-1 se face o comparare, pentru
elementul aflat pe poziţia n-2 se fac 2 comparări, ..., pentru elementul aflat pe poziţia 1 se fac
n-1 comparări.
Prin urmare, numărul de comparări este:
21)n(n1n...21S−=−+++= ,
deci algoritmul are complexitatea O(n2).
6.4. O problemă cu sume
 Enunţ. Se citește n>1, număr natural, Suma, număr natural și n numere naturale nenule. Se
cere să se decidă dacă numărul Suma poate fi obţinut ca sumă de numere naturale dintre cele
n citite. În caz afirmativ, să se afișeze un set de numere care, adunate, dau acest rezultat.
 Rezolvare. Putem considera toate submulţimile mulţimii {1,2,...,n} și pentru fiecare
submulţime calculăm suma obţinută. Dar... câte submulţimi avem? Avem 2n submulţimi.
Înseamnă că avem un algoritm în O(2n)... Inacceptabil!
Vom prefera un alt algoritm, bazat pe metoda programării dinamice.
Notăm numerele citite cu n1, n2, ..., nn. Fie S=n1+n2+...+nn. Evident, orice sumă care se
poate forma cu numerele citite, poate fi un număr între 1 și S. Vectorul Sume, un vector cu S
componente, va reţine 1 pentru fiecare sumă care poate fi formată și 0 în caz contrar.
La pasul i vom calcula toate sumele care se pot calcula cu numerele n1, n2, ..., ni.
La pasul 1 avem Sume[n1]=1.
La pasul 2 avem Sume[n2]=1 și Sume[n1+n2]=1.
La pasul 3 avem Sume[n3]=1 și Sume[n2+n3]=1, Sume[n1+n2+n3]=1.
...
Dar cum reţinem toţi termenii care alcătuiesc o sumă? Mai simplu decât pare la prima
vedere… Un vector, numit Alege, cu S componente, va reţine pentru fiecare sumă calculată
ultimul termen care intră în alcătuirea ei. Atunci când afișăm soluţia pentru Suma, tipărim
Alege[Suma], apoi Alege[Suma-Alege[Suma]]…
Vom prezenta ca exemplu cazul n=3. Numerele citite sunt 2, 3, 5.
Sumele calculate pot lua valori între 1 și 2+3+5=10. Un vector Suma, cu 10 componente va
reţine 1 pentru fiecare sumă care se poate calcula și 0 dacă, până la acel pas suma nu s-a
putut calcula.
Cu numărul 2 se poate forma o singură sumă: 2.
Sume Alege
0100000000 0200000000
1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10
Cu numerele 2 și 3 se mai pot forma sumele: 3, și 5=2+3.
Suma Alege
0110100000 0230300000
1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10

Cu numerele 2, 3, 5 se mai pot forma și sumele: 5 (a mai fost obţinută), 7=2+5 și


8=3+5, 10=5+5.
Suma Alege
0110101101 0230505505
1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10
Dacă Suma=7, se afișează Alege[7]=5, Alege[7-5]= Alege[2]=2.
Complexitatea algoritmului. Fie S suma numerelor. Algoritmul tratează fiecare număr citit (n
numere) și pentru fiecare număr citit parcurge vectorul Suma care are S componente. Prin
urmare, complexitatea lui este O(nS).
Pentru a nu calcula o sumă de mai multe ori, pentru calculul noilor sume se utilizează un
vector auxiliar, numit Sume1, după care se execută sau logic între conţinuturile celor doi
vectori (vedeţi programul următor).
#include <iostream.h>
int Nr[100],Sume[100],
Sume1[100],Alege[100],n,i,j,
S,Suma;
int main()
{ cout<<"n="; cin>>n;
for(i=1;i<=n;i++)
{ cin>>Nr[i];
S+=Nr[i];}
for (i=1;i<=n;i++)
{ Sume1[Nr[i]]=1;
for (j=1;j<=S;j++)
if(Sume[j]==1)
Sume1[j+Nr[i]]=1;
for(j=1;j<=S;j++)
if (Sume1[j]==1 &&
Sume[j]==0)
{ Sume[j]=1;
Alege[j]=Nr[i];
Sume1[j]=0;}}
cout<<"Suma="; cin>>Suma;
if (Suma<=S)
if (Alege[Suma])
while (Suma)
{ cout<<Alege[Suma]<<endl;
Suma-=Alege[Suma];}
else cout<<"Suma nu se poate
forma";
else cout<<"Suma nu se poate
forma";}
Dacă numerele sunt introduse în ordine crescătoare, atunci suma se obţine prin utilizarea
unui număr maxim de termeni și invers, dacă numerele sunt introduse în ordine
descrescătoare, atunci suma se obţine prin utilizarea unui număr minim de termeni. De ce?
6.5. Problema rucsacului (cazul discret)
 Enunţ (Varianta 1). O persoană are la dispoziţie un rucsac cu o capacitate de G unităţi de
greutate și intenţionează să efectueze un transport în urma căruia să obţină un câștig.
Persoana are la dispoziţie n obiecte. Pentru fiecare obiect se cunoaște greutatea sa Gri (număr
natural) și câștigul obţinut în urma transportului său: Ci. Ce obiecte trebuie să aleagă
persoana pentru a-și maximiza câștigul și care este acesta?
Cerinţă suplimentară. Odată ales un obiect, acesta trebuie transportat integral (nu se admite
transportul unei părţi din el). În acest fel trebuie să rezolvăm problema discretă a rucsacului
(rucsac 0/1).
 Rezolvare. Vom nota obiectele cu 1, 2, ..., n. Există posibilitatea ca problema să admită
mai multe soluţii optime. Fie S={i1,i2,...,ik} o soluţie optimă a problemei (unde i1<i2<...<ik
sunt obiecte dintre cele n). Dacă se înlătură obiectul ik, atunci greutatea G-Gr(ik) este
încărcată optim cu obiectele i1<i2<...<ik-1. Dacă, prin absurd, ar exista o altă încărcare a
rucsacului pentru greutatea G-Gr(ik), care aduce un câștig mai mare, atunci, dacă la acea
încărcătură s-ar adăuga obiectul ik, am obţine o soluţie mai bună decât cea iniţială, ceea ce
contrazice optimalitatea soluţiei.
Aceasta conduce la următoarea idee de rezolvare a problemei:
• Capacităţile 1,2,...,G se încarcă optim, la început cu obiectul 1, apoi se îmbunătăţește
soluţia cu obiectul 2, ... și, la sfârșit, se îmbunătăţește soluţia cu obiectul n.
• Pentru a calcula câștigul maxim vom utiliza matricea Castign+1,G+1, unde Castig(i,j) va
reţine câștigul obţinut prin transportul obiectelor 1, 2, ..., i, dacă capacitatea de transport este
j. Relaţiile de recurenţă sunt următoarele:
1...ni0,)Castig(i,0 a)==
1...Gj0,j)Castig(0,b)==
−−>+−−+−−=altfelj),1,Castig(i j)1,Castig(iC(i))Gr(i)j1,Castig(i
C(i),Gr(i))j1,Castig(ij)Castig(i,)c
Justificarea relaţiilor
a) Dacă rucsacul are capacitatea 0, evident, nu poate fi transportat nici un obiect, în
concluzie, în toate cazurile câștigul este 0.
b) Dacă nu transportăm nici un obiect, atunci, indiferent de capacitate, câștigul obţinut este 0.
c) Cazul corespunde deciziei de a alege sau nu pentru greutatea j produsul i. Dacă pentru
greutatea j, se alege produsul i, atunci câștigul care se obţine este suma dintre câștigul maxim
obţinut pentru capacitatea j-Gr(i) la care se adaugă câștigul obţinut din transportul obiectului
i. Dacă nu se alege spre transport obiectul i, atunci ne mulţumim cu câștigul maxim obţinut
pentru greutatea j, în cazul în care se transportă doar obiecte alese dintre primele i-1. Evident,
alegem sau nu pentru transport obiectul i în funcţie de câștigul care se obţine, care trebuie să
fie maxim.
Să observăm că matricea Castig se completează pe linii.
Exemplu: Pentru n=5:
Obiect 1 2 3 4 5
greutate 3 4 3 10 5
castig 2 3 8 12 7
Capacitatea rucsacului este de 10 unităţi de greutate. Persoana va alege obiectele 3 și 5 și va
obţine un câștig de 15 unităţi monetare. În acest fel se vor transporta numai 8 unităţi de
greutate.
Iată cum arată matricea Castig și matricea Alege după actualizarea, pe rând, cu obiectele 1, 2,
..., n:

matricea Castig matricea Alege

#include <iostream.h>
int Castig[50][50],
Alege[50][50],Gr[100],C[100],
i,j,n,G,Obiect;
int main()
{ cout<<"G=";cin>>G;
cout<<"n=";cin>>n;
for(i=1;i<=n;i++)
{ cout<<"Gr["<<i<<"]=";
cin>>Gr[i];
cout<<"C["<<i<<"]=";
cin>>C[i];}
for(i=1;i<=n;i++)
for(j=1;j<=G;j++)
if (Gr[i]<=j)
if(C[i]+Castig[i-1][j-Gr[i]]
>Castig[i-1][j])
{ Castig[i][j]=C[i]+
Castig[i-1][j-Gr[i]];
Alege[i][j]=i;}
else
{Castig[i][j]=Castig[i-1][j];
Alege[i][j]=Alege[i-1][j];}
else
{Castig[i][j]=Castig[i-1][j];
Alege[i][j]=Alege[i-1][j];}
i=n; j=G;
cout<<"Castig total "
<<Castig[i][j]<<endl;
while (Alege[i][j])
{ Obiect=Alege[i][j];
cout<<" Produsul "
<<Alege[i][j]
<<" Greutate "
<<Gr[Alege[i][j]]
<< " Castig "
<<C[Alege[i][j]]<<endl;
while (Obiect==Alege[i][j])
{ j-=Alege[i][j];
i--;}}}
 Enunţ (Varianta 2). La fel ca la prima variantă, numai că se presupune că se dispune de un
număr nelimitat de obiecte de un anumit tip. Pentru fiecare tip de obiect se cunoaște
greutatea unui exemplar și câștigul obţinut prin transportul său la destinaţie.
 Rezolvare. Pare mai complicat, dar, în realitate, este algoritmul de la problema anterioară,
simplificat. Matricele Castig și Alege devin vectori cu G+1 componente. Se parcurg vectorii
de mai sus, o dată pentru tipul de obiect 1, apoi pentru tipul de obiect 2, ... și, la sfârșit,
pentru tipul de obiect n. La fiecare parcurgere se urmărește să se încarce optim greutăţile
1,2,...,G.
Spre deosebire de algoritmul anterior, unde, la pasul i, se actualizează încărcarea greutăţii j,
pornind de la greutatea j-G[i], încărcată optim cu obiecte de tipuri 1, 2, ..., i-1, aici se
actualizează greutatea j-G[i], încărcată optim cu produsele 1, 2, ..., i-1, i. Aceasta face să se
selecteze mai multe produse de același tip. Evident, există posibilitatea să obţinem un câștig
general mai mare decât la problema precedentă.
Pentru exemplul anterior, puteţi observa mai jos conţinuturile vectorilor Castig și Alege,
după rulare. Soluţia aleasă va fi: Castig 24, se alege de 3 ori obiectul 3.
Programul este prezentat mai jos:
#include <iostream.h>
int Castig[100],Alege[100],
Gr[100],C[100],i,j,n,G;
int main()
{ cout<<"G="; cin>>G;
cout<<"n="; cin>>n;
for(i=1;i<=n;i++)
{ cout<<"Gr["<<i<<"]=";
cin>>Gr[i];
cout<<"C["<<i<<"]=";
cin>>C[i];}
for(i=1;i<=n;i++)
for(j=1;j<=G;j++)
if (Gr[i]<=j)
if(C[i]+Castig[j-Gr[i]] >
Castig[j])
{ Castig[j]=C[i]+
Castig[j-Gr[i]];
Alege[j]=i;}
j=G;
cout<<"Castig total "
<<Castig[G]<<endl;
while (Alege[j])
{ cout<<" Produsul "
<<Alege[j]
<<" Greutate "
<<Gr[Alege[j]]
<< " Castig "
<<C[Alege[j]]<<endl;
j-=Gr[Alege[j]];}}
 Indiferent de variantă, complexitatea este O(nG).
 O astfel de complexitate se numește complexitate pseudopolinomială. În unele cazuri se
obţin soluţii într-un timp foarte scurt, dar... ce ne facem dacă G este foarte mare, de exemplu
G=2n.
Mai jos, puteţi analiza modelul matematic al problemei rucsacului, varianta discretă, cazul 1.
Puteţi interpreta relaţiile?
Se cer {}0,1,...XX,Xn21∈, astfel încât:
∈+++= ∈≤++
+*n21nn332211*n21nn332211N...cc,c,X...cXcXcXcfmaxNG,...gg,gGX...gXgXgXg
Chiar dacă matematicienilor le place, uneori, să prezinte unele probleme pe un exemplu
copilăresc, ca în cazul de faţă, unde o persoană are un rucsac și încearcă să transporte niște
obiecte, problema are o importanţă uriașă în economie.
Analizaţi exemplele următoare:
1) O firmă de transport dispune de un vapor și într-un port găsește mai multe produse pe care
le poate transporta în ţara de origine. În urma transportului se pot obţine anumite câștiguri
care se cunosc de la început, pentru fiecare produs în parte. Iată că în locul rucsacului avem
un vapor, deja problema prezintă interes... economic!
6.6. Distanţa Levenshtein
 Enunţ. Se consideră două cuvinte A și B cu m, respectiv, n caractere. Se cere să se
transforme cuvântul A în cuvântul B prin utilizarea a 3 operaţii:
A – adăugarea unei litere;
M – modificarea unei litere;
S – ștergerea unei litere.
Transformarea se va face prin utilizarea unui număr minim de operaţii. Se va afișa numărul
minim de operaţii și șirul transformărilor.
Exemplu: A=’IOANA’ (m=5), B=’DANIA’ (n=5).
Se va afișa 3 (numărul transformărilor).
Numărul minim de transformări se numește distanţa Levenshtein (noţiunea a fost introdusă,
împreună cu algoritmul de calcul, în anul 1965 de către omul de știinţă rus Vladimir
Levenshtein).
DANIADANAOANAIOANAAMS→→→
 Rezolvare. Să observăm că numărul de operaţii necesar conversiei este mai mic sau egal
cu maximul dintre m și n. De exemplu, dacă m<n, putem modifica primele m caractere și
șterge ultimele n-m caractere.
Fie șirul optim care transformă pe A în B. Atunci, șirul transformărilor de la A la Tk este
optim. Dacă, prin absurd, nu ar fi optim, înseamnă că există un alt șir cu număr mai mic de
transformări de la A la Tk. Dacă înlocuim acest șir în șirul transformărilor de la A la B, se
obţin mai puţine transformări, deci se contrazice optimalitatea soluţiei iniţiale. În concluzie,
șirul transfomărilor de la A la Tk este optim.
În șirul transformărilor, de la un termen la altul, se trece prin efectuarea unei singure operaţii
dintre: adăugarea, modificarea sau ștergerea unui caracter. Aceasta înseamnă că doi termeni
consecutivi ai șirului diferă printr-un singur caracter. Mai mult, în șirul transformărilor, nu
contează ordinea în care efectuăm calculele.
De exemplu, un șir optim de transformări de la IOANA la DANIA este și:
pe lângă șirul modificărilor de mai jos:
DANIADANAOANAIOANAAMS→→→
În ambele șiruri s-au modificat, adăugat, șters aceleași caractere, aflate pe aceleași poziţii în
șirul iniţial, doar ordinea diferă. Astfel, în primul șir am adăugat caracterul I pe poziţia 4, am
șters caracterul I de pe prima poziţie, iar caracterul O de pe poziţia 2 a fost modificat în D. În
al doilea șir, am șters caracterul I de pe prima poziţie, am modificat caracterul O de pe a doua
poziţie în D și am adăugat caracterul I pe poziţia 4.
Dacă ordinea transformărilor nu contează, atunci vom prefera să calculăm numărul minim al
transformărilor, pornind de la A, și numărând adăugările, modificările, și ștergerile
caracterelor de la stânga către dreapta (în șirul iniţial).
Pentru rezolvare, vom presupune că fiecare cuvânt (A, B) începe cu un caracter vid, pe care-l
notăm cu V. În aceste condiţii, avem de efectuat transformarea:
VA1 A2... Am → VB1 B2... Bn
În acest fel, A va avea m+1 caractere, iar B va avea n+1 caractere.
...B...TTTAk21→→→→DANIAOANIAIOANIAIOANAMSA→→→
Pentru calculul distanţei vom utiliza matricea Cost cu m+1 linii și n+1 coloane. Liniile sunt
între 0 și m+1, iar coloanele între 0 și n+1. Elementul Cost[i,j] va reţine numărul mimim al
transformărilor primelor i caractere ale cuvântului A în primele j caractere ale cuvântului B.
Mai precis, Cost[i,j] înseamnă costul obţinerii cuvântului intermediar:
B1 B2 ... Bj Ai+1 Ai+2 ... Am
Avem:
1) Cost[0,j]=j, j=0, 1, 2, ..., n. Semnificaţie: costul transformării caracterului vid, care
precede pe A, în primele j caractere ale cuvântului B, este j (se fac j adăugări).
2) Cost[i,0]=i, i=0, 1, 2, ..., m Semnificaţie: costul transformării primelor i caractere ale lui A
în caracterul vid care îl precede pe B, este i (se fac i ștergeri).
3) 0<i≤m, 0<j≤n +
{}


−−−−+=−−=altfel,j)1,cost(i1),jcost(i,1),j1,cost(imin1BA1],j1,Cost[ij]Cost[i,ji
Semnificaţie: la fiecare pas, se compară caracterul aflat în A pe poziţia i (A[i]), cu cel aflat în
B pe poziţia j (B[j]). Dacă se cunosc costurile optime (numărul minim de operaţii): C[i-1,j-
1], C[i,j-1], C[i-1,j], atunci:
A) Dacă caracterul aflat pe poziţia i în A este egal cu caracterul aflat pe poziţia j în B
(Ai=Bj), se sare acel caracter. În acest caz,
Cost[i,j]=Cost[i-1,j-1].
B1B2...Bj-1AiAi+1...Am → B1B2...Bj-1BjAi+1...Am
B) Ai≠Bj. Atunci se efectuează una din operaţiile următoare, mai precis, cea care are costul
cel mai mic:
B1) Adăugarea unui caracter (Bj). Atunci costul total este: 1+Cost[i,j-1].
B1B2...Bj-1Ai+1...Am → B1B2...Bj-1BjAi+1...Am
B2) Ștergerea unui caracter, cel aflat pe poziţia Ai. Atunci costul total este 1+Cost[i-1,j].
B1B2...Bj-1BjAiAi+1...Am → B1B2...Bj-1BjAi+1...Am
B3) Modificarea unui caracter, Ai va fi egal Bj. Atunci costul total este 1+Cost[i-1,j-1].
B1B2...Bj-1AiAi+1...Am → B1B2...Bj-1BjAi+1...Am
Cazul ”IOANA→DANIA”. Costul transformării caracterului vid în caracterul vid este 0,
costul transformării caracterului vid în ”D” este 1, costul transformării caracterului vid în
”DA” este 2, ..., costul transformării caracterului vid în ”DANIA” este 5. Apoi, costul
transformării caracterului vid în caracterul vid este 0, costul transformării caracterului ”I” în
caracterul vid este 1, costul transformării caracterelor ”IO” în caracterul vid este 2, ..., costul
transformării caracterelor ”IOANA” în caracterul vid este 5.
VDANIA
V012345
I1
O2
A3
N4
A5
În continuare, completăm matricea pe linii:
C[1,1]. A[1]=I≠D=B[1].
1+min{cost[i-1,j-1],cost[i-1,j],cost[i,j-1]}=1+min{cost[0,0],
cost[0,1],cost[1,0]=1+min(0,1,1)=1.
Semnificaţia: modificarea minimă pentru a transforma ”I” în ”D” are costul 1. Se face o
modificare pentru că (i-1,i-1) trece în (i,j).
VDANIA
V012345
I11
O2
A3
N4
A5
...
În final, matricea este:
VDANIA
V012345
I112334
O222344
A332344
N443234
A554333
Numărul minim de transformări este: cost[m,n]=cost[5,5]=3.
După calculul elementelor matricei, se depistează operaţiile efectuate și acestea se afișează în
ordine inversă.
Depistarea unei operaţii
Pentru i=5 și j=5,
min{cost[4,4],cost[4,5],cost[5,4]}= min{3,4,3}.
Alegem 3 (prima valoare minimă din șir). Pentru că avem cost[5,5]=min,
înseamnă că la ultima operaţie s-a efectuat un salt (a fost găsită egalitate).
Acum i=4 și j=4. Apoi:
min{cost[3,3],cost[3,4],cost[4,3]}=min{3,4,2]=2.
cost[4,4]≠min
Pentru că am efectuat o operaţie de tipul (i,j-1)→(i,j) înseamnă că s-a
adăugat pe poziţia 4 a șirului A caracterul de pe poziţia 4 a șirului B (I). Aceasta
este ultima modificare făcută de algoritm.
Avem i=4, j=3. Depistăm apoi, penultima modificare, ș.a.m.d., până când i=0 și
j=0. Pentru a afișa modificările în ordine inversă vom utiliza o stivă (Sol). De
asemenea, se poate utiliza recursivitatea.
#include <iostream>
#include <cstring>
char A[100],B[100],
Sol[100][300],op;
int m,n,i,j,min,k,
cost[100][100];
void Next(int& i, int& j,
char& op)
{ int l=0,c=0;
min=1000;
if (i>0 && j>0 &&
min>=cost[i-1][j-1])
{ min=cost[i-1][j-1];
l=i-1; c=j-1;
op='m';}
if (i>0 && cost[i-1][j]<min)
{ min=cost[i-1][j];
l=i-1; c=j;
op='s';}
if (j>0 && cost[i][j-1]<min)
{ min=cost[i][j-1];
l=i; c=j-1; op='a';}
if (cost[i][j]==min) op='v';
i=l; j=c;}
int main()
{ int t;
cout<<"A="; cin>>A+1;
cout<<"B="; cin>>B+1;
m=strlen(A+1);
n=strlen(B+1);
cout<<m<<" "<<n<<endl;
for (i=0;i<=m;i++)
cost[i][0]=i;
for(j=0;j<=n;j++)
cost[0][j]=j;
for (i=1;i<=m;i++)
for(j=1;j<=n;j++)
if(A[i]==B[j])
cost[i][j]=cost[i-1][j-1];
else
{ min=cost[i-1][j-1];
if (min>cost[i-1][j])
min=cost[i-1][j];
if (min>cost[i][j-1])
min=cost[i][j-1];
cost[i][j]=1+min;}
cout<<"Distanta "
<<cost[m][n]<<endl;
i=m;
j=n;
while(i+j)
{ Next(i,j,op);
if (op!='v')
{ int x=0;
k++;
Sol[k][x++]=op;
Sol[k][x++]=' ';
for (t=1;t<=j;t++)
Sol[k][x++]=B[t];
for (t=i+1;t<=m;t++)
Sol[k][x++]=A[t];}}
for(k=cost[m][n];k>=1;k--)
cout<<Sol[k]<<endl;
cout<<B+1;}
După cum se poate observa, algoritmul are complexitatea O(n2).
6.7. Înmulţirea optimă a unui șir de matrice
Presupunem că avem de înmulţit două matrice: An,p cu Bp,m. În mod evident, rezultatul va
fi o matrice Cn,m. Se pune problema de a afla câte înmulţiri au fost făcute pentru a obţine
matricea C. Prin înmulţirea liniei 1 cu coloana 1 se fac p înmulţiri, întrucât au p elemente.
Dar linia 1 se înmulţește cu toate cele m coloane, deci se fac m*p înmulţiri. În mod analog se
procedează cu toate cele n linii ale matricei A, deci se fac n*m*p înmulţiri. Reţinem acest
rezultat.
Să considerăm produsul de matrice A1×A2×...×An (A1(d1,d2), A2(d2,d3),..., An(dn,dn+1)).
Se cunoaște că legea de compoziţie produs de matrice nu este comutativă, în schimb este
asociativă. De exemplu, dacă avem de înmulţit trei matrice A, B, C produsul se poate face în
două moduri: (AxB)xC sau Ax(BxC).
Este interesant de observat că nu este indiferent modul de înmulţire a celor n matrice. Să
considerăm că avem de înmulţit patru matrice A1(10,1), A2(1,10), A3(10,1), A4(1,10).
Pentru înmulţirea lui A1 cu A2 se fac 100 de înmulţiri și se obţine o matrice cu 10 linii și 10
coloane. Prin înmulţirea acesteia cu A3 se fac 100 de înmulţiri și se obţine o matrice cu 10
linii și o coloană. Dacă această matrice se înmulţește cu A4, se fac 100 de înmulţiri. În
concluzie, dacă acest produs se efectuează în ordine naturală, au loc 300 de înmulţiri.
Să efectuăm același produs în ordinea care rezultă din expresia
A1×((A2×A3)×A4).
Efectuând produsul A2 cu A3 se efectuează 10 înmulţiri și se obţine o matrice cu o linie și o
coloană. Această matrice se înmulţește cu A4, se fac 10 înmulţiri și se obţine o matrice cu 1
linie și 10 coloane. Dacă o înmulţim pe aceasta cu prima, efectuăm 100 de înmulţiri,
obţinând rezultatul final cu numai 120 de înmulţiri.
În concluzie, apare o problemă foarte interesantă și anume de a afla modul în care trebuie să
se înmulţească cele n matrice, astfel încât numărul de înmulţiri să fie minim.
Să vedem, mai întâi, în câte moduri se poate calcula un produs de n astfel de matrice. Pentru
n=4, putem avea:
((A1×A2)×(A3×A4)); (((A1×A2)×A)3×A4); ((A1×(A2×A3))×A4);
(A1×(A2×(A3×A4))); (A1×((A2×A3)×A4)).
Astfel, pentru n=4 avem 5 posibilităţi de calcul al acestui produs. Să observăm că pentru
produse de n matrice, sunt necesare n-1 perechi de paranteze.
 Exerciţiu. Puteţi demonstra prin inducţie acest rezultat?
Ţinând cont de aceasta, se poate formula problema următoare: în câte feluri se pot combina n
perechi de paranteze (desigur, pentru problema dată avem n-1 perechi)? Cei pasionaţi de
informatică pot studia numărarea arborilor binari, autoinstruire (vezi Capitolul 9, problema
propusă 23). Acum ne limităm să spunem că acest număr este:
nnCn211+
și că, în general, un astfel de număr este foarte mare.
 Exerciţiu. Calculaţi acest număr pentru n=10, 11, ..., 20.
Prin urmare, un algoritm în care calculăm în toate modurile posibile acest produs este
ineficient.
Din fericire, pentru această problemă există o rezolvare polinomială, prin utilizarea
programării dinamice. Să presupunem că produsul Ai×Ai+1×...Aj s-a calculat optim. În final,
s-au înmulţit două matrice (Ai×...×Ak)×(Ak+1×...×Aj). Atunci, produsele Ai×...Ak și
Ak+1×...×Aj au fost calculate optim. De ce? Demonstraţi prin reducere la absurd.
Pentru rezolvare, vom aplica principiul al 3-lea al programării dinamice.
În vederea rezolvării problemei, reţinem o matrice A cu n linii și n coloane. Elementul A(i,j),
i<j, reprezintă numărul minim de înmulţiri pentru efectuarea produsului Ai×Ai+1×...×Aj. De
asemenea, numărul liniilor și al coloanelor celor n matrice sunt reţinute într-un vector DIM
cu n+1 componente. Pentru exemplul nostru DIM reţine următoarele valori: 10, 1, 10, 1, 10.
Pentru rezolvare se ţine cont de următoarele relaţii existente între componentele matricei A:
()()()()()()
{}.1jDIM1kDIMiDIMj1,kAki,Aminji,A3)2);DIM(i1)DIM(iDIM(i)1)iA(i,2)0;i)A(i,1)jki+×+
×+++=+×+×=+=<≤
Justificarea acestor relaţii este următoarea:
1) o matrice nu se înmulţește cu ea însăși, deci se efectuează 0 înmulţiri;
2) liniile și coloanele matricei Ai se găsesc în vectorul DIM pe poziţiile i și i+1, iar ale
matricei Ai+1, pe poziţiile i+1 și i+2;
3)
 înmulţind matricele Ai×Ai+1×Ak, se obţine o matrice cu un număr de linii egal cu acela al
matricei Ai (DIM(i)) și cu un număr de coloane egal cu acela al matricei Ak (DIM(k+1));
 înmulţind matricele Ak+1×...×Aj, se obţine o matrice cu un număr de linii egal cu acela al
matricei Ak+1 (DIM(k+1)) și cu un număr de coloane egal cu acela al matricei Aj
(DIM(j+1));
 prin înmulţirea celor două matrice se obţine matricea rezultat al înmulţirii Ai×...×Aj, iar
pentru această înmulţire de matrice se efectuează DIM(i) ×DIM(k+1)×DIM(j+1) înmulţiri.
Observaţii
 Relaţia sintetizează faptul că pentru a obţine numărul de înmulţiri optim pentru produsul
Ai×...×Aj se înmulţesc două matrice, una obţinută ca produs optim între Ai×...×Ak și cealaltă
obţinută ca produs optim între Ak+1×...×Aj, în ipoteza în care cunoaștem numărul de
înmulţiri necesar efectuării acestor două produse, oricare ar fi k cuprins între limitele date.
 Această observaţie este o consecinţă directă a programării dinamice și anume că produsul
efectuat optim între matricele prezentate se reduce în ultimă instanţă la a efectua un produs
între două matrice cu condiţia ca acestea să fie calculate optim (produsul lor să aibă un
număr minim de înmulţiri).
 Să observăm faptul că orice secvenţă pentru calculul costului optim este de forma
Ai×Ai+1×...×Aj. Astfel, întotdeauna j≥i. Pentru a reţine costul optim, A(i,j) vom utiliza o
matrice. Cum j≥i înseamnă că din această matrice se va utiliza numai partea situată deasupra
diagonalei principale.
 Din relaţia 1, rezultă că mai întâi trebuie completate cu 0, elementele aflate pe diagonala
principală. Dacă matricea este declarată ca variabilă globală, elementele ei sunt oricum
iniţializate cu 0.
 Din relaţia 2, rezultă că, în continuare, trebuie completate elementele de coordonate
(i,i+1). Toate acestea sunt situate în partea aflată deasupra diagonalei principale, pe o
paralelă la diagonala principală ("cea mai apropiată").
 Din relaţia 3, rezultă că pentru a afla costul optim pentru produsul Ai×Ai+1×...×Aj, reţinut
de A(i,j), se fac j-i comparări, prin care produsul se descompune în alte două produse, al
căror cost optim este deja cunoscut și se alege descompunerea care asigură costul minim.
Elementele necesare din matricea costurilor optime se observă în tabelul de mai jos:
k Produsele Elemente necesare
i (Ai)×Ai+1×...×Aj A(i,i) , A(i+1,j)
i+1 (Ai×Ai+1)×Ai+2×...×Aj A(i,i+1), A(i+2,j)
... ... ...
j-1 (Ai×Ai+1×...× Aj-1)×Aj A(i,j-1), A(j,j)
Din tabel se observă că pentru calculul lui A(i,j) sunt necesare:
a) A(i,i), A(i,i+1), ..., A(i,j-1) - adică toate elementele din matricea A(i,j), aflate pe linia i,
până la coloana j.
b) A(i+1,j), A(i+2,j), ..., A(j,j) - adică toate elementele din matricea A(i,j), aflate pe coloana
j, până la linia i. De exemplu, dacă n=5, alăturat puteţi observa elementele implicate în
calculul costului optim a(2,5). Întrebarea este: pentru ce au fost prezentate toate aceste
amănunte? Răspuns: pentru a calcula costul optim, reţinut de A(i,j), este necesar ca
elementele matricei A să fie completate pe diagonala principală, apoi pe cea
mai apropiată paralelă de aceasta, apoi, din nou, pe cea mai apropiată diagonală paralelă cu
ultima completată, până se ajunge să se completeze A(1,n) care va reţine costul optim pentru
problema cerută. În acest fel, pentru orice element care se calculează, elementele necesare au
fost deja determinate. Se pune problema să aflăm cum putem efectua acest calcul utilizând
relaţiile prezentate. Pentru exemplificare vom utiliza exemplul dat la începutul acestui
paragraf. Datorită relaţiei 1, diagonala principală a matricei A (cu 4 linii și 4 coloane) va fi
alcătuită numai din elemente având valoarea 0.
 Rămâne să determinăm modul de generare a elementelor de pe paralelele
la diagonala principală, mai precis pe cele aflate deasupra diagonalei
principale. Astfel:
- pentru prima "paralelă": A(1,2), A(2,3), ..., A(n-1,n);
- pentru a doua "paralelă": A(1,3), A(2,4), ..., A(n-2,n);
- pentru a treia "paralelă": A(1,3) A(2,5), …, A(n-3,n);
...
- pentru ultima "paralelă": A(1,n).
Observăm că:
- pentru prima paralelă, liniile sunt între 1 și n-1;
- pentru a doua paralelă, liniile sunt între 1 și n-2;
- pentru a treia paralelă, liniile sunt între 1 și n-3;
...
- pentru ultima paralelă liniile sunt între 1 și 1.
Dacă notăm linia cu i și coloana cu j, atunci secvenţa:
pentru l de la 1 la n-1
pentru i de la 1 la n-l
j←l+i
generează pentru l=1 linii de la 1 la n-1, pentru l=2, linii de la 1 la n-2, ..., pentru l=n-1, linia
1. Să observăm faptul că pentru fiecare l și i, coloana este j. În acest fel, pentru o anumită
valoare a lui l, se obţine o paralelă la diagonala principală. De exemplu, dacă l=1, i=1, j=2, se
obţine A(1,2), dacă l=1, i=2, j=3, se obţine A(2,3), ..., iar dacă l=1, i=n-1, j=n, se obţine A(n-
1,n), adică prima paralelă la diagonala principală. Revenim la exemplul dat la începutul
acestui paragraf. Iniţial se pot calcula numai elementele A(i,i+1), adică A(1,2), A(2,3),
A(3,4) - elemente situate pe o paralelă la diagonala principală a matricei A. În concluzie,
avem A(1,2)=100, A(2,3)=10, A(3,4)= 100. Matricea A va arăta ca alăturat:
În continuare calculăm: ()()()()()(){}{}()()()()()(){}{}()()()()()(){}
{};1201011002010,101010010010,110200min5DIM1kDIM1DIM1,4kAk1,Amin1,4A20;101
101010,1011000min5DIM1kDIM2DIM1,4kAk2,Amin2,4A20;1101001001,110100min4DI
M1kDIM1DIM1,3kAk1,Amin1,3A4k14k23k1=××++××++××++==×+×+++==××++××+
+==×+×+++==××++××++==×+×+++=<≤<≤<≤
În concluzie, pentru exemplul nostru, se fac minimum 120 de înmulţiri, rezultat luat din
matricea A și anume A(1,4):
.0100020100120201000=xxxxxxA.010001001000=xxxxxxxxxA
Mai avem de lămurit o problemă. Chiar dacă cunoaștem costul optim (numărul minim de
înmulţiri), cum determinăm în ce mod se înmulţesc matricele pentru a obţine acest cost? Să
observăm că elementele din matrice, situate sub diagonala principală, au fost neutilizate. De
asemenea, pentru calculul optim al unui produs Ai×Ai+1×Ai+2×...×Aj se determină un
anumit k. Așa cum am arătat, k exprimă ordinea de înmulţire a matricelor. De exemplu, dacă
k=i+1, avem produsul (Ai×Ai+1)×Ai+2×...×Aj, înţelegând prin aceasta că produsul se obţine
ca produs între matricele Ai×Ai+1 și Ai+2×...×Aj, produse pe care știm să le obţinem în mod
optim. Dar, dacă în urma acestui calcul am determinat k, astfel încât produsul obţinut să fie
optim, această valoare poate fi reţinută de A(j,i). Pentru exemplul nostru, matricea A este
prezentată mai jos:
Grafic, valorile se pot prezenta într-un arbore. Întrucât arborii vor fi descriși într-un capitol
separat, vă rog să reveniţi după parcurgerea acelui capitol la această problemă.
Priviţi arborele următor:
Pentru produsul matricelor de la 1 la 4, se obţine k=1 (A(4,1)=1). Aceasta înseamnă că
produsul se va efectua astfel: A1×(A2×A3×A4). Pentru produsul A2×A3×A4 avem k=3.
Aceasta înseamnă că produsul va fi calculat astfel: (A2×A3)×A4. Prin urmare, produsul celor
4 matrice se va calcula în felul următor:
(A1×((A2×A3)×A4)).
Dacă listăm nodurile neterminale ale acestui arbore, arborele fiind parcurs în postordine,
obţinem modul în care se așează parantezele pentru calculul produsului de matrice. Pentru
exemplul nostru, subprogramul parc afișează (2,3), (2,4), (1,4). Aceasta ne spune cum să
aranjăm parantezele.
.0331100021201001120201000=A
(1,4)
(1,1)
(2,4)
(2,3)
(4,4)
(2,2)
(3,3)
Figura. 6.1. Reprezentarea valorilor sub forma unui arbore
#include <iostream.h>
long i,n,dim[10],a[10][10];
void parc (int l, int c)
{ int k=a[c][l];
if (k!=l) parc(l,k);
if (k+1!=c) parc(k+1,c);
cout<<l<<" "<<c<<endl;}
void costopt ()
{ long k,i,j,l,m;
for (l=1;l<=n-1;l++)
for (i=1;i<=n-l;i++)
{ j=i+l;
a[i][j]=100000;
for (k=i;k<=j-1;k++)
{ m=a[i][k]+a[k+1][j]+
dim[i]*dim[k+1]*
dim[j+1];
if (a[i][j]>m)
{ a[i][j]=m;
a[j][i]=k;}}}
cout<<"cost optim "
<<a[1][n]<<endl;}
int main()
{ cout<<"n="; cin>>n;
for (i=1;i<=n+1;i++)
{ cout<<"d=";
cin>>dim[i];}
costopt();
parc(1,n);}
Complexitatea algoritmului este O(n3). Observaţi că există trei cicluri for imbricate!
6.8. Probleme cu ordinea lexicografică a permutărilor
 Problema 1. Se citește n, număr natural, și o permutare a numerelor 1,2,...,n. Se cere să se
afișeze numărul de ordine al permutării, dacă se consideră permutările în ordinea
lexicografică. Alăturat, observaţi permutările mulţimii {1,2,3}, listate în ordinea
lexicografică.
Prima idee este să generăm prin Backtracking, în ordine lexicografică, toate permutările,
până la întâlnirea permutării date și în paralel să le numărăm. Când am întâlnit-o, afișăm
numărul de ordine. Dar, avem n! permutări. Cum n!=1×2×...n>2×2×2...2=2n-1, observăm că
algoritmul este exponenţial. Evident, renunţăm la o astfel de soluţie.
 Rezolvare. Să observăm primul element afișat al fiecărei permutări. Astfel, există două
permutări care afișează 1 ca prim element, două permutări care afișează 2, ca prim element și
2 permutări care afișează 3 ca prim element. Dacă analizăm și șirul permutărilor observăm
că, în general: elementul 1 este afișat de (n-1)! ori, elementul 2 este afișat de (n-1)! ori, ...,
elementul n este afișat de (n-1)! ori.
De fapt, !)!1(...)!1()!1(nnnnorinde=−++−+−.
Această simplă observaţie ne permite să scriem o relaţie de recurenţă, unde NR(P(n))
înseamnă numărul de ordine al permutării iniţiale, iar NR(P(n-1)) înseamnă numărul de
ordine al permutării alcătuite din ultimele n-1 elemente ale permutării iniţiale. Dacă
permutarea este a1 a2... an, atunci:
=>+=1111nn1)),-NR(P(n1)-(a1)!-(nNR(P(n))
Să observăm faptul că permutarea formată cu ultimele n-1 elemente ale permutării iniţiale
este formată din elementele de la 1 la n, din care lipsește exact un element. Din ea, se poate
obţine permutarea cu același număr de ordine (ordinea lexicografică) a mulţimii {1,2,...,n-1},
dacă din orice element mai mare decât cifra eliminată se scade 1 (această operaţie nu
afectează ordinea lexicografică!).
Exemplu: pentru n=5, P=4 5 2 3 1.
P=4!3+NR({5,2,3,1})=72+NR({4,2,3,1})=72+3!*3+NR({2,3,1})=
90+2!1+NR({3,1})=92+NR({2,1})=92+1!1+P(1)=93+1=94.
#include <iostream.h>
int P[10],n,i,k,Nr,Prod;
void Numar()
{ int nsalv=n;
while (n>1)
{ Prod/=n;
Nr+=(P[k]-1)*Prod;
for (i=k+1;i<=nsalv;i++)
if (P[i]>P[k]) P[i]--;
k++;n--;}
Nr++;}
int main()
{ cout<<"n=";cin>>n;
for(i=1;i<=n;i++) cin>>P[i];
Prod=1;
for(i=2;i<=n;i++) Prod*=i;
k=1;
Numar();
cout<<Nr;}
Algoritmul are complexitatea O(n2). Justificaţi!
 Problema 2. Se citește n>0, număr natural, și un număr natural 1≤Nr≤n! Care este
permutarea care, în ordine lexicografică, are numărul de ordine Nr?
Exemplu: pentru n=5 și Nr=94, se va afișa 4 5 2 3 1.
 Rezolvare. Știm că, în șirul tuturor permutărilor, fiecare element al permutării apare pe
prima poziţie de exact (n-1)! ori. În cazul nostru, (n-1)!=4!=24. Cum primele (în ordine
lexicografică) (n-1)! permutări au elementul 1 pe prima poziţie, următoarele (n-1)! permutări
au elementul 2 pe prima poziţie, înseamnă că:
Primul_element=1+[(Nr-1)/(n-1)!],
unde prin [x] am notat parte întreagă din x.
Observaţie foarte importantă. Dacă primul element al permutării poate fi depistat ca mai sus,
cu următoarele trebuie efectuate operaţii în plus, pentru că se selectează elementele unei
permutări ale cărei elemente nu sunt numere consecutive. De exemplu, dacă pentru n=5 se
selectează ca prim element numărul 3, atunci următorul element trebuie selectat din
mulţimea {1,2,4,5}. În acest caz, algoritmul va furniza indicele elementului în vector și nu
elementul propriu-zis. Astfel, dacă elementele ar ocupa în vectorul P poziţii consecutive,
acesta ar trebui să fie P(1,2,4,5) și dacă algoritmul returnează 3, ar trebui selectat 4.
Datorită celor arătate, vom prefera să lucrăm cu 2 vectori, ca alăturat. Vectorul P reţine
numerele 1, 2, ..., n, iar O[i] reţine 1 dacă elementul corespunzător din P a fost selectat, și 0
în caz contrar. Astfel, dacă algoritmul returnează valoarea k, va trebui afișat al k-lea element
neselectat, după care O[k] va reţine 1. Exemplul care urmează vă va lămuri.
Avem n=5 și Nr=94.
(94-1)/24+1=3+1=4. Numărul rămas este 94-3*24=94-72=22. Pentru că elementul selectat
este 4 (al 4-lea în ordine), marcăm în vectorul O acest fapt. În acest mod, problema s-a redus
la o alta, mai simplă: care este permutarea cu 4 elemente, 1, 2, 3, 5 care, în ordine
lexicografică, are numărul de ordine 22?
Avem: (n-1)!=3!=6. La fel: (22-1)/6+1=3+1=4. Al 4-lea element neselectat este 5. Îl selectăm
pentru a-l afișa și-l marcăm în O. Până în acest moment am afișat 4 și 5. Numărul rămas este
22-3*6=22-18=4.
Problema s-a redus la o alta, mai simplă: care este permutarea cu 3 elemente, 1, 2, 3 care, în
ordine lexicografică, are numărul de ordine 4?
(n-1)!=2!=2.
(4-1)/2+1=2.
Numărul rămas este 4-1*2=2.
Selectăm al doilea element neselectat, adică 2 și afișăm elementul. Astfel, am afișat 4, 5, 2.
Apoi:
(n-1)!=1!=1.
(2-1)/1+1=2.
Numărul rămas este 2-1*1=1. Afișăm 3, pentru că este al doilea element neselectat, după
care marcăm în O elementul 3. Am afișat astfel 4, 5, 2 și 3.
Afișăm ultimul element neafișat, adică 1. Am obţinut 4, 5, 2, 3 și 1.
1234500010PO1234500011PO1234501011PO1234501111PO1234500000PO
#include <iostream.h>
int n,Nr,i,k,fact,ic,
nsalv,P[10],O[10];
int main()
{ cout<<"n="; cin>>n;
cout<<"Nr="; cin>>Nr;
fact=1;
for(i=1;i<=n;i++)
{ P[i]=i;
fact*=i;}
nsalv=n;
while (Nr!=1)
{ fact/=n;
n--;
k=(Nr-1)/fact+1;
Nr-=fact*(k-1);
ic=0;i=1;
while(ic<k)
{ if (O[i]==0) ic++;
i++;}
O[i-1]=1;
cout<<P[i-1];}
for(i=1;i<=nsalv;i++)
if(O[i]==0) cout<<P[i];}
Algoritmul are complexitatea O(n2). Justificaţi!
6.9. Numărul partiţiilor unei mulţimi cu n elemente
 Enunţ. Se citește n, număr natural. Se cere ca programul să afișeze numărul partiţiilor unei
mulţimi cu n elemente.
 Rezolvare. Prima idee care ne vine în minte este să generăm toate partiţiile unei mulţimi
(vedeţi generarea lor prin Backtracking) și să le numărăm. Dar numărul partiţiilor este așa de
mare astfel încât o asemenea idee se dovedește dezastruasă. Pornind de la partiţiile unei
mulţimi cu n elemente, observaţi, pentru n=3, cum se pot obţine toate partiţiile unei mulţimi
cu n+1 elemente.
{1,2,3}, {4}
{1,2,3}
{1,2,3,4}
{1,4}, {2,3}
{1} {2,3} {1}, {2,3,4}
{1}, {2,3}, {4}
{2,4}, {1,3}
{2} {1,3} {2}, {1,3,4}
{2}, {1,3}, {4}
{3,4}, {1,2}
{3} {1,2} {3}, {1,2,4}
{3}, {1,2}, {4}
{1,4}, {2}, {3}
{1} {2} {3} {1}, {2,4}, {3}
{1}, {2}, {3,4}
{1}, {2}, {3}, {4}
Ideea de bază este ca, din fiecare partiţie a mulţimii de n elemente, să se genereze toate
partiţiile care provin din ea, ale mulţimii de n+1 elemente. Aceasta se obţine dacă se adaugă
pe rând, la fiecare mulţime a partiţiei, elementul n+1 și, la sfârșit, acesta va forma singur o
mulţime a partiţiei. Vedeţi mai sus!
Vom nota prin S(n,k) numărul partiţiilor unei mulţimi cu n elemente, partiţii în care numărul
mulţimilor este k. De exemplu, pentru n=3, avem: S(3,1)=1, S(3,2)=3 și S(3,3)=1. În aceste
condiţii, numărul partiţiilor unei mulţimi cu 3 elemente este:
S(3,1)+S(3,2)+S(3,3)=1+3+1=5. De aici rezultă că, pentru a calcula numărul partiţiilor unei
mulţimi cu n+1 elemente, trebuie să calculăm suma:
S(n+1,1)+S(n+1,2)+...+S(n+1,n+1).
Dacă am găsi o modalitate de calcul pentru S(n,k), atunci problema ar fi rezolvată.
a) Să observăm că, pentru orice n, S(n,1)=1, adică avem o singură partiţie în care toate
mulţimile au un singur element: {1}, {2}, {3}, …, {n}. Tot așa, S(n,n)=1, pentru că avem o
singură partiţie în care mulţimile au n elemente: {1,2,...,n}.
b) Urmărim să găsim o relaţie de recurenţă pentru S(n+1,k), adică să găsim numărul
partiţiilor unei mulţimi cu n+1 elemente, partiţii în care toate mulţimile au k elemente, în
condiţiile în care cunoaștem S(n,1), S(n,2) ...,S(n,n).
b1) Dacă cunoaștem numărul partiţiilor unei mulţimi cu n elemente, în care fiecare partiţie
are k submulţimi, S(n,k), atunci putem forma partiţii alcătuite din exact k mulţimi ale
mulţimii cu n+1 elemente. Pentru fiecare partiţie numărată de S(n,k), adăugăm elementul
n+1 în prima mulţime a partiţiei, apoi în a doua mulţime, ..., la sfârșit în a k-a mulţime a
partiţiei. În acest fel, din fiecare partiţie se obţin alte k partiţii. În concluzie, vom obţine în
acest mod k*S(n,k) partiţii. În exemplul dat, observaţi cum din cele 3 (S(3,2)) partiţii cu 2
submulţimi ale mulţimii cu 3 elemente, am obţinut 2*S(3,2)=6 partiţii cu 3 submulţimi ale
unei mulţimi cu 4 elemente.
{1,4}, {2,3}
{1} {2,3} {1}, {2,3,4}
{2,4}, {1,3}
{2} {1,3} {2}, {1,3,4}
{3,4}, {1,2}
{3} {1,2} {3}, {1,2,4}
b2) Dacă cunoaștem numărul partiţiilor unei mulţimi cu n elemente, partiţii care sunt
alcătuite din k-1 mulţimi, atunci din fiecare astfel de mulţime se poate obţine o altă partiţie
cu k mulţimi ale mulţimii cu n+1 elemente, adăugând la fiecare partiţie mulţimea alcătuită
din elementul n+1. În concluzie, în acest mod vom obţine alte S(n,k-1) partiţii cu k
submulţimi ale unei mulţimi cu n+1 elemente. În exemplul dat, avem:
{1,2,3} {1,2,3} {4}
Cum alte posibilităţi de obţinere a partiţiilor unei mulţimi cu k clase ale unei mulţimi cu n+1
elemente nu există și cum, astfel obţinute, partiţiile nu se repetă, relaţia este:
S(n+1,k)=S(n-1,k)+k*S(n,k).
Pentru exemplul dat, S(4,2)=S(3,3)+3*S(3,2)=1+2*3=7.
Pentru a scrie programul, completăm, mai întâi prima coloană a matricei cu 1 (pentru că
avem S(n,1)=1). Prima linie va avea numai un 1 în prima coloană, pentru că, evident,
S(1,k)=0, pentru k>1. În continuare, completăm pe linii elementele matricei S. În final,
facem suma pe linia n.
#include <iostream.h>
long S[20][20],n,k,i,j,s;
int main()
{ cout<<"n="; cin>>n;
for (i=1;i<=n;i++) S[i][1]=1;
S[i][i]=1;
for(i=2;i<=n;i++)
for (j=2;j<=i;j++)
S[i][j]=S[i-1][j-1]+j*S[i-1][j];
for (i=1;i<=n;i++)
s+=S[n][i];
cout<<s;}
Complexitatea algoritmului este O(n2).
📘 5. Teoria grafurilor
10. Grafuri
10.1. Grafuri neorientate
10.1.1. Noţiuni teoretice
Definiţie. Se numeşte graf neorientat o pereche ordonată de mulţimi ( X , U ), X fiind o mulţime finită
şi nevidă de elemente, numite vârfuri, iar U o mulţime de perechi neordonate din X, numite muchii.
Notăm G = ( X , U ) un graf neorientat
X se numeşte mulţimea vârfurilor
U se numeşte mulţimea muchiilor
Fie u  U care trece prin x, y. Atunci u = [ x , y ] sau u = ( x , y )
Definiţie. Pentru o muchie u = [ x , y ] :
– vârfurile x şi y sunt adiacente şi se numesc extremităţile muchiei u;
– muchia u şi vârful x sunt incidente în graf;
– muchia u şi vârful y sunt incidente în graf;
Observaţie. [ x , y ] = [ y , x ] deoarece nu există o orientare a muchiei
Exemplu. Fie G = ( X , U ) astfel încât :
X = { 1 , 2 , 3 , 4 , 5 , 6 , 7 , 8 , 9 , 10 , 11 }
U = { [ 1 , 5 ] , [ 3 , 7 ] , [ 4 , 6 ] , [ 9 , 8 ] , [ 10 , 2 ] , [ 1 , 2 ] , [ 9 , 4 ] , [ 1 , 10] , [ 6 , 8 ] }

1 10 3 7

4 6 11
5 2

9 8

Definiţie. Gradul unui varf x, notat d ( x ), reprezintă numărul muchiilor care trec prin vârful x
( incidente cu vârful x ).
Definiţie. Un varf care are gradul 0, se numeşte vârf izolat.
Un vârf care are gradul 1, se numeşte vârf terminal.
Exemplu. d ( 1 ) = 3; d ( 2 ) = 2; d ( 4 ) = 2; d ( 6 ) = 2; d ( 8 ) = 2; d ( 9 ) = 2; d (10 ) = 2;
d ( 11 ) = 0  11 = vârf izolat
d ( 3 ) = 1; d ( 5 ) = 1; d ( 7 ) = 1  3 , 5 , 7 = vârfuri terminale
Definiţie. Fie graful G = ( X , U ). Un graf parţial al lui G, este un graf G 1 = ( X , V ) astfel încât V
 U, adică G1 are aceeaşi mulţime de vârfuri ca G, iar mulţimea de muchii V este chiar U sau o
submulţime a acesteia.
Exemplu. G1 = ( X , V ) V = { [ 1 , 5 ] , [ 2 , 10 ] , [ 6 , 4 ] }

1 2

5 10 4 6
8 3 7 11 9

Definiţie. Fie G = ( X , U ). Un subgraf al lui G, este un graf G1 = ( Y , T ) astfel încât Y  X, iar T


conţine toate muchiile din U care au ambele extremităţi în Y ( se obţine din G eliminând o parte din
vârfuri şi toate muchiile incidente la acestea ).
Exemplu. Y = { 1 , 2 , 3 , 7 , 10 } T = { [ 1 , 2 ] , [ 1 , 10 ] , [ 2 , 10 ] , [ 3 , 7 ] }

1 2
3 7

10

Definiţie. Se numeşte graf complet cu n vârfuri, notat Kn , un graf G = ( X , U ) care are


proprietatea că oricare 2 vârfuri diferite sunt adiacente, adică :
x,yX u=[x,y]U

Exemplu. Graf complet cu 5 vărfuri K5

2
5

4 3

Un graf complet cu n noduri are n * (n-1) / 2 muchii.

Definiţie. Se numeşte graf bipartit, un graf G = ( X , U ) cu proprietatea că există 2


mulţimi A şi B incluse în X, astfel încât :
– AUB=X,AB=
– toate muchiile grafului au o extremitate în A şi cealaltă în B.

Exemplu. Graf bipartit A={1,2,4} B={3,5,6,7}

3
1
5
2 6
4 7
Definiţie. Se numeşte graf bipartit complet, un graf bipartit cu proprietatea că pentru orice vârf
x din A şi orice vârf y din B, există muchia [ x , y ].

Exemplu. graf bipartit complet A = { 1 , 3 , 4 } B={2,5}

1 2
3
5
4
Definiţie. Se numeşte lanţ în graful G, o succesiune de vârfuri L = ( z1 , z2 , … , zk ), unde z1 , z2 ,
… , zk  X, astfel încât oricare două vârfuri consecutive sunt adiacente, adică [ z 1 , z2 ] , [ z2 , z3 ] , … ,
[ zk–1 , zk ]  U.
Vârfurile z1 şi zk se numesc extremităţile lanţului.
Numărul de muchii care intră în componenţa sa reprezintă lungimea lanţului. Dacă vârfurile z1 , z2 , …
, zk sunt distincte două câte două, lanţul se numeşte elementar. În caz contrar, lanţul se numeşte
neelementar.

Exemplu:

Lanţ elementar:1,2,3,4,5; 6,7,3,9,4,8…


Lanţ neelementar: 1,2,3,2;

Definiţie. Se numeşte ciclu într–un graf, un lanţ L = ( z1 , z2 , … , zk ) cu proprietatea că z1 = zk şi


muchiile [ z1 , z2 ] , [ z2 , z3 ] , … , [ zk–1 , zk ] sunt distincte două câte două.
Dacă într–un ciclu, toate vârfurile cu excepţia primului şi a ultimului sunt distincte două câte două,
atunci ciclul se numeşte elementar. În caz contrar se numeşte neelementar.

Exemplu: c1=(3,4,5,3,7,6,1,2,3) - ciclu neelementar


c2=(1,2,3,7,6,1) - ciclu elementar

Reprezentarea grafurilor neorientate

Fie G = ( X , U ) cu m muchii şi n vârfuri numerotate 1, 2, …, n.


1. Reprezentare prin matrice de adiacenţă.

a  Mn x n

Observaţie. Pentru orice graf neorientat, matricea de adiacenţă a este simetrică faţă de diagonala
principală.

2 3

1 4
2. Reprezentare prin liste de adiacenţă

Nodul Lista de
adiacenţă
1 2
2 1,3,4
3 2,4
4 2,3

Proprietăţi ale grafurilor neorientate

Teoremă. Într–un graf G = ( X , U ) cu n vârfuri şi m muchii, suma gradelor tuturor


vârfurilor este egală cu dublul numărului de muchii.
d ( x1 ) + d ( x2 ) + … + d ( xn ) = 2  m
Consecinţă. În orice graf există un număr par de vârfuri cu grade impare.
Teoremă. Numărul total de grafuri neorientate cu n vârfuri este 2 n * ( n – 1 ) / 2 .
Teoremă. Un graf complet cu n vârfuri, are n  ( n – 1 ) / 2 muchii.

Conexitate. Descompunerea unui graf în componente conexe

Definiţie. Un graf G este conex, dacă oricare ar fi două vârfuri ale sale, există un lanţ care le leagă.
Observaţie. Dacă graful nu este conex, un graf poate fi descompus în componente conexe.
Definiţie. Se numeşte componentă conexă a grafului G = ( X , U ), un subgraf G 1 = ( X1 , U1 ) a
lui G, conex, cu proprietatea că nu există nici un lanţ care să lege un vârf din X1 cu un vârf din X – X1.

Exemplu.

Grafuri hamiltoniene şi euleriene

Definiţie. Se numeşte ciclu hamiltonian într–un graf, un ciclu elementar care conţine toate
vârfurile grafului.
Se numeşte graf hamiltonian, un graf care conţine un ciclu hamiltonian. Un graf complet
este hamiltonian.
Se numeşte lanţ hamiltonian într–un graf, un lanţ elementar care conţine toate vârfurile grafului.
Teoremă. Dacă într–un graf G = ( X , U ) cu n >= 3 vârfuri, gradul fiecărui vârf x verifică
condiţia d ( x ) >= n/2, atunci graful este hamiltonian.
Definiţie. Se numeşte ciclu eulerian într–un graf, un ciclu care conţine toate muchiile
grafului.
Se numeşte graf eulerian, un graf care conţine un ciclu eulerian.
Teoremă. Un graf fără vârfuri izolate este eulerian, dacă şi numai dacă este conex, şi gradele
tuturor vârfurilor sunt numere pare.
Exemplu. Graf hamiltonian Graf eulerian
4 1
7
1 2 8
6
5
2 3 3 4 5
10.1.2. Itemi cu alegere multiplă

1. Câte grafuri neorientate, distincte, cu 5 vârfuri se pot construi?


a) 230 b) 210 c)415 d) 430

2. Într-un graf neorientat cu 15 muchii, fiecare nod al grafului are gradul diferit de
0. Doar trei dintre noduri au gradul un număr par, restul nodurilor având gradele numere impare. Care
este numărul maxim de noduri pe care poate să le aibă graful?
a) 27 b) 24 c) 32 d) 16

3. Se consideră graful neorientat cu 6 noduri, numerotate de la 1 la 6 şi muchiile [1,2], [1,6], [2,3],


[3,4],[3,5],[3,6],[4,5],[5,6]. Nodurile cu grad minim sunt :
a) 1,3,4 b) 1,2,6 c) 1,2,4 d)1,2,3

4. Se consideră graful neorientat cu 7 noduri numerotate de la 1 la 7 şi muchiile [1,2], [1,4], [1,5],


[2,5],[2,6],[3,4],[3,7],[4,5] ,[6,7]. Care din următoarele succesiuni de noduri reprezintă un lanţ care
trece o singură dată prin toate nodurile grafului ?
a) 1,7,2,4,3,6,5 b) 1,6,2,4,5,3,7 c) 1,5,6,7,2,3,4 d) 1,2,5,4,3,7,6

5. Se consideră graful neorientat reprezentat prin matricea de adiacenţă alăturată. Câte dintre vârfurile
grafului au gradul impar?
01110
10001
10010
10101
01010
a) 3 b) 2 c) 5 d) 1

6. Fie graful neorientat cu 6 noduri, numerotate de la 1 la 6 şi muchiile [1,2], [1,3],


[1,4], [2,3], [2,4], [3,4], [3,5], [4,5], [4,6], [5,6]. Numărul maxim de muchii care poate fi eliminat astfel încât
graful parţial obţinut să fie hamiltonian este:
a) 4 b) 5 c) 2 d) 3

7. Fie graful neorientat cu 9 noduri, numerotate de la 1 la 9 şi muchiile [1,2], [2,3], [3,7], [4,8], [4,5], [4,6],
[5,9], [6,9], [7,8], [6,7], [1,7]. Numărul minim de muchii carepoate fi adăugat astfel încât graful să devină
eulerian este:
a) 4 b) 5 c) 2 d) 3

8. Se consideră graful neorientat reprezentat prin matricea de adiacenţă alăturată. Care este lista de
adiacenţă corespunzătoare grafului?
01110
10001
10010
10101
01010
a) 1: 2, 3 b) 1: 2, 3, 5 c) 1: 3, 4 d) 1: 2, 3, 4
2: 1, 4 2: 1, 5 2: 1, 3, 5 2: 1, 5
3: 2,5 3: 1,4 3: 2,4,5 3: 1,4
4: 1,3,5 4: 1,3,5 4: 1,3,5 4: 1,3,5
5: 2,4 5: 1,2,3 5: 2,4 5: 2,4

9. Care este numărul minim de muchii ce trebuie eliminat din graful alăturat astfel încât graful parţial
obţinut să nu fie conex?
2
1

3
4

a) 1 b) 2 c) 3 d) 0

10. Fie graful neorientat cu 6 noduri, numerotate de la 1 la 6 şi muchiile [1,2], [1,3], [1,4], [2,3], [2,4],
[3,4], [3,5], [4,5], [4,6], [5,6]. Matricea de adiacenţă corespunzătoare grafului este:
a) 1 0 0 1 1 1 b) 0 1 1 1 0 0 c) 0 1 1 1 1 0 d) 0 1 0 1 0 0
101100 101100 101111 101010
110110 110110 110111 110110
111011 111011 111011 111010
000101 001101 001101 111000
000111 000110 000000 000110

Răspunsuri itemi cu alegere multiplă


1. b; 2. a; 3. c; 4. d; 5. b; 6. a; 7. c; 8. d; 9. a; 10. b.

10.1.3. Probleme rezolvate


1. Se dă un graf cu n noduri prin matricea de adiacenţă. Să se scrie un program care afişează lista
vecinilor corespunzătoare grafului.

Exemplu: n=4 0111


1001
1000
1 1 0 0 se va afişa 1: 2,3,4
2: 1,4
3: 1
4: 1,2
Rezolvare
#include<iostream>
using namespace std;
int n, i, j, a[20][20];
int main()
{cin>>n;
for(i=1;i<=n;i++)
for(j=1;j<=n;j++)
cin>>a[i][j];
for(i=1;i<=n;i++)
{cout<<i<<":";
for(j=1;j<=n;j++)
if(a[i][j]==1)
cout<<j<<" ";
cout<<endl;}
return 0;}
2. Să se verifice dacă un şir de k noduri reprezintă un lanţ într-un graf neorientat memorat cu
ajutorul matricei de adiacenţă.

Exemplu: n=5, k=4, şirul 1, 2, 3, 4 şi matricea de adiacenţă 0 1 0 1 0


10100
01010
10101
0 0 0 1 0 se va
afişa Da

Rezolvare
#include <iostream>
using namespace std;
int b, n, i, j, a[20][20], v[20], k;
int main()
{cin>>n;
for(i=1;i<=n;i++)
for (j=1;j<=n;j++)
cin>>a[i][j];
cin>>k;
for(i=1;i<=k;i++)
cin>>v[i];
b=1;
for(i=1;i<k;i++)
if(a[v[i]][v[i+1]]!=1)
b=0;
if (b)
cout<<"DA";
else
cout<<"NU";
return 0;}

3. Se dă un graf cu n noduri prin matricea de adiacenţă. Să se scrie un program care afişează câte
dintre nodurile grafului au grad par şi câte grad impar.

Exemplu: n=4 0111


1001
1000
1 1 0 0 se va afişa par 2
impar 2
Rezolvare
#include<iostream>
using namespace std;
int n, i, j, a[20][20], sp=0, si=0, s;
int main()
{cin>>n;
for(i=1;i<=n;i++)
for(j=1;j<=n;j++)
cin>>a[i][j];
for(i=1;i<=n;i++)
{s=0;
for(j=1;j<=n;j++) s=s+a[i][j]; if(s
%2==0)
sp++;
else
si++;}
cout<<"Noduri cu grad par:"<<sp<<endl;
cout<<"Noduri cu grad impar:"<<si;
return 0;}

4. Prin parcurgerea unui graf înţelegem vizitarea nodurilor grafului o singură dată fiecare, după o
anumită regulă.
Parcurgerea în lăţime (BF- breadth first)
 Începem parcurgerea grafului în lăţime dintr-un nod i, pe care îl considerăm vizitat.
 Vizităm apoi rând pe rând toate nodurile adiacente cu nodul i, încă nevizitate.
 Pentru fiecare nod vizitat, vom vizita în continuare toate nodurile adiacente cu el, încă
nevizitate şi aşa mai departe, până când nu mai găsim noduri nevizitate adiacente cu
nodurile vizitate deja.
Parcurgerea nodurilor în lăţime nu dă o soluţie unică, soluţia depinde de ordinea parcurgerii nodurilor
adiacente.
La implementare pentru memorarea nodurilor vizitate vom utiliza o structură de tip coadă, pe care o
vom memora într-un vector. Se va mai folosi un vector viz care reţine informaţii referitoare la nodurile
care au fost vizitate astfel:
 1, daca nodul i a fost vizitat
viz [ i ] = 
0 , daca nodul i nu a fost vizitat 

int n; // numărul de noduri


int a[20][20]; // matricea de adiacență (graful)
int viz[20]; // vector care marchează dacă un nod a fost vizitat (0/1)
int c[20]; // vectorul-coadă
int ic = 1; // începutul cozii (indicele nodului care urmează să fie procesat)
int sc = 1; // sfârșitul cozii (indicele ultimului nod adăugat)
int m; // nodul de pornire
Rezolvare:
#include<iostream>
using namespace std;
int n, i, j, a[20][20], viz[20], c[20], ic=1, sc=1,m; //ic/sc-inceput/sfarsit coada.
int main()
{cin>>n; for(i=1;i<=n;i++)
for(j=1;j<=n;j++)
cin>>a[i][j];
cout<<"Nodul de pornire:";
cin>>m;
c[ic]=m; // punem nodul m în coadă
cout<<m<<" ";
viz[m]=1; // marcat ca vizitat
while(ic<=sc) // cât timp mai avem elemente în coadă
{for(i=1;i<=n;i++)
if (a[c[ic]][i]&& !viz[i]) // dacă i este vecin cu c[ic](a[i][j] = 1 muchie) și nu a fost vizitat
{sc++;// mărim coada
c[sc]=i; // punem nodul i în coadă
cout<<i<<" ";
viz[i]=1;}
ic++;}// trecem la următorul nod din coadă
return 0;}

Parcurgerea în adâncime (DF- depth first)


 Începem parcurgerea grafului în adâncime dintr-un nod i, pe care îl considerăm vizitat.
 Vizităm apoi primul nod adiacent cu el, încă nevizitat şi aşa mai departe până când
ajungem la un nod care nu mai are noduri adiacente cu el nevizitate.
 Revenim la ultimul nod vizitat care mai are noduri adiacente cu el încă nevizitate şi reluăm
algoritmul. Parcurgerea se termină când nu mai există noduri adiacente nevizitate cu nici
un nod parcurs deja.
Parcurgerea nodurilor în adâncime nu dă o soluţie unică, soluţia depinde de ordinea parcurgerii
nodurilor adiacente.
La implementare pentru memorarea nodurilor vizitate vom utiliza o structură de tip stivă, pe care o vom
memora într-un vector. Se va mai folosi un vector viz care reţine informaţii referitoare la nodurile care
au fost vizitate astfel:
 1, daca nodul i a fost vizitat
viz [ i ] = 
0 , daca nodul i nu a fost vizitat 
Rezolvare:
#include<iostream>
using namespace std;
int n, i, j, a[20][20], viz[20], m;
void adancime(int nod)
{int i;
cout<<nod<<" "; // afișăm nodul
viz[nod]=1; // marcat ca vizitat
for(i=1;i<=n;i++)
if(a[nod][i]&&!viz[i]) // dacă este vecin nevizitat
adancime(i);} // apel recursiv
int main()
{cin>>n;
for(i=1;i<=n;i++)
for(j=1;j<=n;j++)
cin>>a[i][j];
cout<<"Nodul de pornire:";
cin>>m; // nod de start
adancime(m);
return 0;}
5. Se dă un graf neorientat prin matricea de adiacenţă. Să se scrie un program care determină
componentele conexe ale grafului.
Rezolvare:
#include<iostream>
using namespace std;
int n,a[20][20],viz[20],k=0;
void adancime(int nod)
{cout<<nod<<" "; viz[nod]=1;
for(int i=1;i<=n;i++)
if(a[nod][i]&&!viz[i])
adancime(i);}
int main()
{int i,j; cout<<"Citeste nr de noduri: n= ";
cin>>n; for(i=1;i<=n;i++)
for(j=1;j<=n;j++)
cin>>a[i][j];
for(i=1;i<=n;i++)
if(!viz[i])
{k++; // componentă nouă
cout<<"Componenta "<<k<<":";
adancime(i); // o parcurgem complet
cout << endl;}
return 0;}
10.1.4. Probleme propuse
1. Să se scrie un algoritm care verifică dacă un graf neorientat memorat prin matricea de
adiacenţă este sau nu conex.

2. Se dă un graf neorientat cu n noduri memorat prin matricea de adiacenţă şi două noduri x şi y. Să se


determine toate ciclurile elementare având ca extremităţi nodurile date, daca există, altfel să se afişeze
mesajul „Nu există!”.

3. Se dă un graf neorientat cu n noduri memorat prin matricea de adiacenţă. Să se determine toate


lanţurile de lungime k din graf, unde k este număr natural citit de la tastatură.
4. Se dă un graf neorientat cu n noduri memorat prin lista de adiacenţă. Să se scrie un program care
construieşte în memorie matricea de adiacenţă şi verifică dacă graful este sau nu hamiltonian.

5. Se dă un graf neorientat cu n noduri memorat prin lista de adiacenţă. Să se scrie un program care
construieşte în memorie matricea de adiacenţă şi verifică dacă graful este sau nu eulerian.

6. Se dă un graf neorientat cu n noduri memorat prin lista de adiacenţă. Să se scrie un program care
construieşte în memorie matricea de adiacenţă şi verifică dacă o succesiune de noduri dată de la
tastatură este sau nu ciclu în graful dat.

7. Se dă un graf neorientat cu n noduri memorat prin matricea de adiacenţă. Să se determine toate


ciclurile de lungime k din graf, unde k este număr natural citit de la tastatură.

8. Pentru un graf cu n vârfuri şi m muchii să se construiască un subgraf cu k vârfuri, unde k este un


număr natural citit de la tastatură şi gradul tuturor nodurilor este număr par. Dacă acest lucru nu este
posibil se va afişa mesajul „Nu se poate!”.

9. Să se verifice dacă un graf memorat prin matricea de adiacenţă are „faţă umană”, adică are exact 3
componente conexe: una cu 2 vârfuri, una cu 3 vârfuri şi una cu 4 vârfuri.

10. Se dă un graf care are toate vârfurile de grad impar. Să se scrie un algoritm care determină toate
ciclurile elementare ale grafului.

11. Se dă un graf neorientat cu n noduri memorat prin matricea de adiacenţă şi două noduri x şi y. Să
se determine toate lanţurile hamiltoniene având ca extremităţi nodurile date, daca există, altfel să se
afişeze mesajul „Nu există!”.

12. Se dă un graf neorientat cu n noduri memorat prin matricea de adiacenţă şi un nod


x. Să se determine toate ciclurile euleriene având ca extremităţi nodul dat, daca există, altfel să se
afişeze mesajul „Nu există!”.

13. Se dă un graf neorientat cu n noduri memorat prin lista de adiacenţă. Să se scrie un program care
construieşte în memorie matricea de adiacenţă şi afişează subgraful obţinut prin eliminarea nodului de
grad maxim.

14. Să se scrie un program care pentru un graf memorat prin matricea de adiacenţă, cu n noduri,
determină numărul de muchii al grafului şi determină subgraful cu număr minim de vârfuri şi cu
proprietatea că orice vârf al său are gradul cel puţin egal cu 2. Dacă nu există un astfel de subgraf să se
afişeze mesajul „Nu există!”.

15. Se dă un graf neorientat cu n noduri memorat prin lista de adiacenţă. Să se scrie un program care
determină cel mai lung lanţ elementar din graf şi îl afişează.
10.2. Grafuri orientate

10.2.1. Noţiuni teoretice

Definiţie. Se numeşte graf orientat o pereche ordonată de mulţimi ( X , U ), unde :


– X este o mulţime finită şi nevidă de elemente, numite noduri sau vârfuri;
– U este o mulţime de perechi ordonate din X, numite arce.
X se numeşte mulţimea nodurilor sau vârfurilor
U se numeşte mulţimea arcelor
Definiţie. Pentru un arc u = [ x , y ] :
– x se numeşte extremitatea iniţială, iar y se numeşte extremitatea finală a arcului;
– nodurile x şi y sunt adiacente;
– arcul u şi nodul x sunt incidente în graf;
– arcul u şi nodul y sunt incidente în graf;
– nodul y se numeşte succesor al lui x;
– nodul x se numeşte predecesor al lui y.
Definiţie. Un arc de forma [ x , x ] se numeşte buclă.
Observaţie. [ x , y ]  [ y , x ] deoarece există o orientare a arcului
Exemplu. Fie G = ( X , U ) X = { 1 , 2 , 3 , 4 }
U={[1,1],[2,1],[3,2],[2,3],[2,4],[3,4],[3,4]}
Definiţie. Gradul exterior al unui vârf x, notat d + ( x ), reprezintă numărul arcelor care ies
din nodul x, adică numărul arcelor de forma [ x , y ]  U.
Definiţie. Gradul interior al unui vârf x, notat d – ( x ), reprezintă numărul arcelor care
intră în nodul x, adică numărul arcelor de forma [ y , x ]  U.
Exemplu. d+(2)=3 d–(2)=1
Definiţie.  + ( x ) = { y  X  ( x , y )  U } – mulţimea succesorilor lui x
 ( x ) = { y  X  ( y , x )  U } – mulţimea predecesorilor lui x

Exemplu. +(2)={1,3,4} –(2)={3}


Definiţie.  ( x ) = { u = ( x , y )  u  U } – mulţimea arcelor care ies din nodul x
+

 – ( x ) = { u = ( y , x )  u  U } – mulţimea arcelor care intră în


nodul x
Exemplu. +(2)={(2,1),(2,3),(2,4)}
–(2)={(3,2)}
Definiţie. Fie graful G=(X,U). Un graf parţial al lui G, este un graf G1=(X,V) astfel încât V  U,
adică G 1 are aceeaşi mulţime de noduri ca G, iar mulţimea de arce V este chiar U sau o submulţime a
acesteia.
Exemplu. G1=(X,V) V={[2,1],[3,2],[4,3],[5,6],[6,4]}

u2 u2
1 2 1 2
u1
u3 u3
u4 u4
3 u5 3
4 4
5 5
u7 u6 u7 u6

6 6
G=(X,U) Graf partial
Definiţie. Fie G = ( X , U ). Un subgraf al lui G, este un graf G 1 = ( Y , T ) astfel încât Y  X, iar
T conţine toate arcele din U care au ambele extremităţi în Y ( se obţine din G eliminând o parte din
noduri şi păstrând acele arce care au ambele extremităţi în mulţimea nodurilor rămase ).
Exemplu. Y={3,4,5,6} T={[4,3],[3,5],[5,6],[6,4]}

u2
1 2 u4 u5
3
u1 4
u3 5
u4 u7 u6
3 u5
4
5 6
u7 u6

6
G=(X,U) Subgraf

Definiţie. Se numeşte lanţ într–un graf orientat, o mulţime de arce L = { u 1 , u 2 , … , u k }, cu


proprietatea că oricare două arce vecine în mulţime au o extremitate comună.
Definiţie. Se numeşte drum în graful G, o succesiune de noduri D = ( z 1 , z 2 , … , z k ), unde z 1 , z
2 , … , z k  X, astfel încât oricare două noduri consecutive sunt adiacente, adică [ z 1 , z 2 ] , [ z 2 , z 3 ],…, [zk–1 , z k
]  U.
Nodurile z 1 şi z k se numesc extremităţile drumului.
Numărul de arce care intră în componenţa sa reprezintă lungimea drumului. Dacă nodurile z1 , z 2 ,
… , z k sunt distincte două câte două, drumul se numeşte elementar. În caz contrar, drumul se
numeşte neelementar.
Definiţie. Se numeşte circuit într–un graf, un drum D = ( z 1 , z 2 , … , z k ) cu proprietatea că z 1= zk
şi arcele [ z 1 , z 2 ] , [ z 2 , z 3 ] , … , [ z k–1 , z k ] sunt distincte două câte două.
Dacă într–un circuit, toate nodurile cu excepţia primului şi a ultimului sunt distincte două câte două,
atunci circuitul se numeşte elementar. În caz contrar, circuitul se numeşte neelementar.
Exemple. L 1 = ( u 1 , u 4 , u 7 ) – lanţul cu extremităţile 1 şi 6
L 2 = ( u 2 , u 3 , u 5 , u 6 ) – lanţul cu extremităţile 1 şi 6
D 1 = ( 1 , 3 , 5 , 6 , 4 , 3 , 2 ) – neelementar
D 2 = ( 3 , 5 , 6 , 4 , 3 , 5 ) – neelementar
D 3 = ( 2 , 1 , 3 , 5 , 6 , 4 ) – elementar
C 1 = ( 4 , 3 , 2 , 1 , 3 , 5 , 6 , 4 ) – neelementar
C 2 = ( 3 , 5 , 6 , 4 , 3 ) – elementar
C 3 = ( 1 , 3 , 2 , 1 ) – elementar
Reprezentarea grafurilor orientate

Fie G = ( X , U ) cu m arce şi n noduri.


1. Reprezentare prin matrice de adiacenţă.

AM n x n a[i,j]=

Observaţie. Matricea de adiacenţă nu este simetrică faţă de diagonala principală.

2. Reprezentare prin matricea vârfuri – arce

BM n x m b[i,j]=

3. Reprezentare prin matricea drumurilor

DM n x m d[i,j]=

Exemplu.

a= b= d=

4. Reprezentare prin vector de muchii


struct muchie
{
int nod_initial , nod_final ;
};
muchie v[50] ;

5. Reprezentare prin liste de Nodul L+(x) L–(x) vecini


L + ( x ) – lista vecinilor 1 3 2 succesori; conţine
nodurile ce sunt extremităţi 2 1 3 finale ale arcelor care
ies din nodul x 3 2,5 1,4
L – ( x ) – lista vecinilor 4 3 6 predecesori; conţine
5 6 3
6 4 5
nodurile ce sunt extremităţi iniţiale ale arcelor care intră în nodul x
Algoritmul lui Roy – Warshall de determinare a matricei drumurilor
Matricea drumurilor se obţine din matricea de adiacenta prin transformări succesive.
Spunem ca exista drum de la i la j daca găsim un nod k astfel incât sa existe drum de la i la k si de la k
la j.
Un element a[i][j] poate deveni 1 daca a[i][k]=1 si a[k][j]=1.
Pentru a găsi arcele nodului k trebuie parcurse pe rând in variabila k toate nodurile de la 1 la n.
for(k=1;k<=n;k++) // PAS 1: alegem nodul intermediar
for(i=1;i<=n;i++) // PAS 2: alegem nodul de start (sursă)
for(j=1;j<=n;j++)// PAS 3: alegem nodul destinație
if (d[i][j] == 0 && d[i][k]==1 && d[k][j]==1)
d[i][j] = 1;

Graf complet şi graf turneu

Definiţia Un graf orientat este complet dacă oricare două vârfuri, i şi j (i≠j), sunt adiacente.

Lema Avem 3n(n−1) 1/2 n- grafuri complete cu n noduri.

Definiţia Un graf orientat este turneu, dacă oricare ar fi două vârfuri i şi j, i≠j, între ele există un singur arc:
arcul (i,j) sau arcul (j,i).

Conexitate în grafuri orientate

Descompunerea unui graf orientat în componente conexe

Definiţie. Un graf G este conex, dacă oricare ar fi două noduri ale sale, există un lanţ care le leagă.
Observaţie. Dacă graful nu este conex, atunci poate fi descompus în componente conexe.
Definiţie. Se numeşte componentă conexă a grafului G = ( X , U ), un subgraf G 1 =( X 1 , U 1 ) a lui
G, conex, cu proprietatea că nu există nici un lanţ care să lege un nod din X 1 cu un nod din X – X 1 .
Exemplu. Graf neconex :

Tare conexitate în grafuri orientate

Descompunerea unui graf orientat în componente tare conexe

Definiţie. Un graf orientat G = ( X , U ) este tare conex, dacă pentru oricare două noduri x şi y 
X, există un drum de la x la y precum şi un drum de la y la x.
Observaţie. Dacă graful nu este tare conex, atunci poate fi descompus în componente tare conexe.
Definiţie. Se numeşte componentă tare conexă a grafului orientat G = ( X , U ), un subgraf G 1 =
( X 1 , U 1 ) a lui G, tare conex, cu proprietatea că pentru orice nod x
 X – X 1 subgraful indus de X 1  { x } nu mai este tare conex.

Drumuri minime şi maxime în grafuri orientate Algoritmul lui Dijkstra


Matricea costurilor
Fie un graf orientat G = ( X , U ) cu n noduri în care fiecărui arc îi este asociat un număr întreg numit
cost.
Matricea costurilor se defineşte astfel :
1. pentru determinarea drumurilor de cost minim :

 cost, daca exista un arc de cost  0 intre nodurile i si j



c  M nxn c [ i , j ] = 0 , daca i  j
  , daca nu exista arc intre i si j

2. pentru determinarea drumurilor de cost maxim :

 cost, daca exista un arc de cost  0 intre nodurile i si j



c  M nxn c [ i , j ] = 0 , daca i  j
  , daca nu exista arc intre i si j

- se selectează vârfurile grafului unul cate unul in n-1 paşi in ordinea crescătoare a costului drumului
de la vârful de start x la ele, intr-o multime care conţine initial doar vârful de start se foloseşte
vectorul prec definit astfel:
prec[i]=k atunci k este vârful precedent lui I pe drumul minim de la x la i
- se mai folosesc doi vectori d[i] reprezintă costul minim al drumului de la x la I si s[i]=1 daca vârful I
a fost vizitat si 0 un caz contrar
Paşii care se executa in algoritmul lui Dijkstra:
1. se selectează vârful de start
2. are loc repetarea următoarei secvenţe de cel mult n-2 ori:
2.1. se caută vârful k neselectat, cu d[k] minim si se selectează
2.2. se actualizează vectorul d pentru acele vârfuri I neselectate dar pentru care fostul d[i]
este mai mic decât d[k]+costul arcului (k,i); astfel d[i]=d[k]+c[k,i] iar prec[i]=k.
Aşadar drumul de la x la I are ca varf intermediar nodul k care se afla inainte de i
3. la sfarsit folosind vectorul prec se afişează drumurile de la x la fiecare varf I al grafului
precum si costurile acestor drumuri

Algoritmul lui Roy – Floyd

– determină lungimea drumurilor minime între oricare două noduri ale unui graf
– graful este reprezentat prin matricea de costuri, c
– dacă de la nodul i la nodul j nu există arc, c[i,j]=1000.
Plecăm de la următoarea idee: dacă drumul minim între două noduri oarecare i şi j trece printr-un nod k,
atunci drumurile de la i la k şi de la k la j sunt la rândul lor minime. Pentru fiecare pereche de noduri (i,
j ), cu i, j {1,2,…,n}, procedăm astfel:
 Dăm lui k pe rând valorile 1,2,…,n, pentru ca nodul k despre care vorbeam mai sus poate
fi, cel puţin teoretic, orice nod al grafului. Pentru fiecare k:
 dacă suma dintre costul drumului de la i la j şi costul drumului de la k la j este mai
mică decât costul drumului de la i la j {a[i, k]+a[k, j]<a[i, j]}, atunci drumul iniţial
de la i la j este înlocuit cu drumul indirect ikj. această înlocuire fireşte că se va
opera ca atare în matricea costurilor: {a[i, j]:=a[i, k]+a[k, j]}.

10.2.2. Itemi cu alegere multiplă

1. Câte noduri are graful orientat cu 6 noduri şi următoarele arce: (1,5), (1,6), (2,1),
(2,3), (3,1), (3,4), (4,3), (4,5), (5,4), (6,5) au gradul interior egal cu gradul exterior?
a) 2 b) 3 c) 6 d) 5
2. Se consideră un graf orientat cu 8 noduri numerotate de la 1 la 8 şi cu mulţimea arcelor formată
doar din arcele:
- de la fiecare nod numerotat cu număr prim i la nodurile numerotate cu număr par
- de la nodul 1 la nodul 6
- de la fiecare nod numerotat cu i impar la fiecare nod i+1.
Pentru graful dat care este cel mai lung drum format doar din noduri distincte care uneşte nodul 2 cu nodul
8?
a) 2 b) 3 c) 4 d) 5

3. Se consideră graful orientat reprezentat prin listele de adiacenţă alăturate. Câte nod lista
noduri au gradul extern mai mic decât gradul intern? 1: 2,6,5
2: 3,4
3: 1
4: 5, 6
a) 2 b) 3 c) 6 d) 5 5: 6

4. Se consideră graful orientat reprezentat prin matricea de adiacenţă 0 1 1 0 0 0 0


alăturată. Care este lungimea maximă a unui drum de la vârful 4 la vârful 6 0 0 0 0 1 1 0
format din vârfuri distincte două câte două? 0000000
0010100
a) 4 b) 3 c) 6 d) 5 1100010
1010000

5. Se consideră graful orientat reprezentat prin matricea de adiacenţă 0 1 1 0 0 0 0


alăturată. Care sunt nodurile pentru care gradul interior este mai mic decât 0 0 0 0 1 1 0
gradul exterior? 0000000
0010100
a) 4, 5, 7 b) 3, 5 c) 6, 4, 7 d) 5, 6, 7 1100010
1010000
nod lista
1: 3
2: 1,3,5,6
3: -
4: 3
5: -

6. Care este numărul de circuite distincte ale grafului orientat dat prin lista de adiacenţă alăturată?

a) 0 b) 1 c) 2 d) 3

7. Se consideră graful orientat cu 6 noduri şi următoarele arce (1,2), (1,5), (1,6), (2,3), (3,6), (4,1),
(6,4). Care este nodul accesibil din toate celelalte vârfuri prin intermediul unor drumuri elementare?
a) 6 b) 1 c) 5 d) 3

8. Câte grafuri orientate cu 5 vârfuri se pot construi?


a) 220 b) 215 c)415 d) 430

9. Gradul intern pentru nodul cu eticheta i dintr-un graf orientat la care se cunoaşte matricea de
adiacenţă este egal cu numărul de cifre egal cu 1 aflate pe:
a) linia i
b) diagonala principală
c) diagonala secundară
d) coloana i
10. Un graf orientat are 5 noduri numerotate de la 1 la 5 şi 4 arce: (1,2), (2,1), (2,3), (3,4). Prin
eliminarea nodului 2 şi a arcelor incidente cu acesta se obţine:
a) un subgraf cu 4 noduri şi un arc
b) un graf parţial
c) un subgraf cu 4 noduri şi nici un arc
d) un subgraf cu 3 noduri şi 3 arce distincte
Răspunsuri itemi cu alegere multiplă

10. Grafuri

10.1. Grafuri orientate


1. d 2. c 3. b 4. a 5. a 6. a 7. c 8. a 9. d 10. a

10.2.3. Probleme rezolvate

1. Fie un graf orientat memorat cu ajutorul matricei de adiacenţă. Să se determine vârfurile


fiecărei componente tare conexe.
Exemplu:
n=7 şi matricea 0101000
0010000
1000000
0000000
0001001
0001000
0 0 0 0 1 0 0 componentele tare conexe sunt: 1 2 3
57
4
6
Rezolvare:
#include<iostream.h>
int s[20],p[20], a[20][20],n,nrc,i,j; void citire()
{
cin>>n;
for (i=1;i<=n;i++)
for (j=1;j<=n;j++)
cin>>a[i][j];
}

void df1(int nod)


{ int k; s[nod]=nrc;
for(k=1;k<=n;k++)
if(a[nod][k]==1 && s[k]==0) df1(k);
}

void df2(int nod)


{ int k; p[nod]=nrc; for(k=1;k<=n;k+
+)
if(a[nod][k]==1 && p[k]==0) df2(k);
}

void main()
{
citire(); nrc=1;
for(i=1;i<=n;i++)
if (s[i]==0) {s[i]=nrc;
df1(i);
df2(i); for(j=1;j<=n;j++)
if(s[j]!=p[j]) {s[j]=0;p[j]=0;} nrc++;}
for(i=1;i<nrc;i++)
{cout<< "Componenta "<<i<<endl; for(j=1;j<=n;j++)
if(s[j]==i) cout<<j<<" "; cout<<endl;
}}

2. Algoritmul lui Dijkstra Fie un graf orientat cu costuri asociate arcelor. Se dă un varf r de plecare, să
se determine pentru orice pereche de vârfuri (r,x) costul drumului minim care le uneşte.
Exemplu:

n=5 şi matricea costurilor 0 1 9 ∞ 3


∞0 7 3 ∞
∞∞0 ∞ ∞
1 ∞ 2 0 ∞
∞ 4 ∞ 2 0 şi nodul de plecare r=1 se va afişa:
Drumul de cost minim de la 1 la 2 este: 1 Drumul de cost
minim de la 1 la 3 este: 6 Drumul de cost minim de la 1 la 3
este: 4 Drumul de cost minim de la 1 la 5 este: 3 Rezolvare
#include<iostream.h>
int s[20],t[20],d[20],min,a[20][20],n,i,j,poz,r;
void citire()
{cin>>n;
for(i=1;i<=n;i++)
for(j=1;j<=n;j++)
cin>>a[i][j];}
void drum(int i)
{if (t[i])
drum(t[i]);
cout<<i<<" ";}
void main()
{citire();
cin>>r;
s[r]=1;
for(i=1;i<=n;i++)
{d[i]=a[r][i];
if(r!=i)
if(d[i]<32000)
t[i]=r;}
for(i=1;i<n;i++)
{ min=32000;
for(j=1;j<=n;j++)
if(s[j]==0)
if(d[j]<min)
{min=d[j];
poz=j;}
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++)
cout<<d[i]<<" " ;
cout<<endl;
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;}

3. Algoritmul lui Roy – Floyd Fiind dat un graf orientat cu costuri asociate arcelor, se cere să se
determine pentru oricare două vârfuri lungimea minimă a drumului care le leagă.
Rezolvare
#include<iostream.h>
int a[20][20],n;
void citire()
{int i,j;
cin>>n;
for(i=1;i<=n;i++)
for (j=1;j<=n;j++)
cin>>a[i][j];}
void drum(int i,int j)
{int k=1, b=0;
while ((k<=n) && !b)
{if (i!=k && j!=k && a[i][j]==a[i][k]+a[k][j])
{ drum(i,k);
drum(k,j);
b=1;}
k++;}
if(!b)
cout<<j<<" ";}
void sdrum(int ni, int nf)
{if(a[ni][nf]<32000)
{cout<<"Drumul de la"<<ni<<"la"<<nf<<"are lungimea: "<<a[ni][nf]<<endl;
cout<<ni<<" ";
drum(ni,nf);}
else cout<<"Nu exista drum de la "<<ni<<" la "<<nf;}
void lung()
{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];}
void main()
{int ni,nf;
citire();
lung();
cin>>ni>>nf;
sdrum(ni,nf);}

4. Fiind dat un graf orientat prin matricea de adiacenţă se cere să se determine, dacă există, un ciclu
hamiltonian.
Rezolvare #include<iostream.h> int n,
s[20], a[20][20];
void citire()
{ int i,j; cin>>n;
for(i=1;i<=n;i++) for(j=1;j<=n;j++)
cin>>a[i][j];
}

int succesor(int k)
{
if (s[k]++<n) return 1; else return 0;
}
int valid (int k)
{
if(a[s[k-1]][s[k]]==0) return 0; else for(int
i=1;i<k;i++)
if (s[i]==s[k]) return 0; if(k==n) if (a[s[k]][1]==0)
return 0; return 1;
}

void tipar()
{
for (int i=1;i<=n;i++) cout<<s[i];
cout<<s[1]<<endl;
}
void back(int k)
{
if(k==n+1) tipar(); else
{ s[k]=1;
while(succesor(k))
if (valid(k)) back(k+1);
}
}

void main()
{citire();
s[1]=1;
s[2]=1;
back(2);
}

5. Algoritmul lui Roy – Warshall pentru determinarea matricei drumurilor unui graf orientat memorat
prin intermediul matricei de adiacenţă.
Rezolvare
#include<iostream.h>
int n,d[20][20],i,j,k;
void main()
{cin>>n;
for(i=1;i<=n;i++)
for (j=1;j<=n;j++)
cin>>d[i][j];
for (k=1;k<=n;k++)
for (i=1;i<=n;i++)
for (j=1;j<=n;j++)
if ((d[i][j]==0) && (i!=k) && (j!=k))
d[i][j]=d[i][k]*d[k][j];
for(i=1;i<=n;i++)
{for(j=1;j<=n;j++) cout<<d[i][j]<<"
";
cout<<endl;}}

10.2.4. Probleme propuse

1. Să se scrie un algoritm care verifică dacă un graf orientat memorat prin listele de adiacenţă are noduri
cu gradul intern nul.
2. Se dă un graf orientat cu n noduri memorat prin matricea de adiacenţă şi un nod x. Să se afişeze
nodurile legate de x prin drumuri de lungime impară, daca există, altfel să se afişeze mesajul „Nu
există!”.
3. Se dă un graf orientat memorat prin listele de adiacenţă. Să se scrie un program care verifică dacă o
succesiune de noduri citită de la tastatura este sau nu lanţ in graful dat şi afişează lungimea lanţului.

4. Se dă un graf orientat cu n noduri memorat prin matricea de adiacenţă. Să se afişeze listele de


adiacenţă corespunzătoare grafului.

5. Se dă un graf orientat cu n noduri memorat prin listele de adiacenţă. Să se scrie un algoritm care
elimină arcul (x,y).

6. Se dă un graf orientat cu n noduri memorat prin lista de adiacenţă. Să se scrie un program care elimină
din graf toate arcele corespunzătoare vârfurilor cu grad minim.

7. Se dă un graf orientat cu n noduri memorat prin lista de adiacenţă. Să se scrie un program care
construieşte în memorie matricea de adiacenţă şi verifică dacă acesta conţine lanţuri de lungime k,
unde k este un număr natural citit de la tastatură mai mic decât n.

8. Se dă un graf orientat cu n noduri memorat prin lista de adiacenţă. Să se scrie un program care
construieşte în memorie matricea de adiacenţă şi verifică dacă o succesiune de noduri dată de la
tastatură este sau nu ciclu în graful dat.

9. Pentru un graf cu n vârfuri şi m muchii să se construiască un subgraf cu k vârfuri, unde k este un


număr natural citit de la tastatură şi gradul extern al tuturor nodurilor este număr impar. Dacă acest
lucru nu este posibil se va afişa mesajul „Nu se poate!”.

10. Se dă un graf orientat in care are toate vârfurile au gradul intern par. Să se scrie un algoritm care
determină toate ciclurile elementare ale grafului.

11. Se dă un graf orientat cu n noduri memorat prin matricea de adiacenţă şi două noduri x şi y. Să se
determine toate vârfurile care au aceleaşi grade interne ca vârful x şi aceleaşi grade externe ca vârful
y.

12. Se dă un graf orientat cu n noduri memorat prin matricea de adiacenţă. Să se determine toate
ciclurile care trec prin noduri cu gradul extern număr prim, daca există, altfel să se afişeze mesajul
„Nu există!”.

13. Se dă un graf orientat cu n noduri memorat prin lista de adiacenţă. Să se scrie un program care
construieşte în memorie matricea de adiacenţă şi afişează subgraful obţinut prin eliminarea vârfurilor
care au gradul intern egal cu gradul extern.

14. Să se scrie un program care pentru un graf orientat memorat prin matricea de adiacenţă, cu n
noduri, determină numărul de arce ale grafului şi determină subgraful cu număr minim de vârfuri şi cu
proprietatea că orice vârf al său are gradul intern cel puţin egal cu 2 şi gradul extern cel mult 3. Dacă
nu există un astfel de subgraf să se afişeze mesajul „Nu există!”.

15. Se dă un graf orientat cu n noduri memorat prin lista de adiacenţă. Să se scrie un program care
determină cel mai lung lanţ elementar din graf şi îl afişează.
10.3. Arbori

10.3.1. Noţiuni teoretice

Definiţie. Un graf neorientat conex şi fără cicluri se numeşte arbore.


Nodurile unui arbore pot fi aşezate pe niveluri începând cu rădăcina care este plasată pe nivelul 1.
Definiţie. Rădăcina este un nod special care generează aşezarea unui arbore pe niveluri. Această
operaţie se efectuează în funcţie de lungimea lanţurilor prin care celelalte noduri sunt legate de
rădăcină.
Definiţie. Nodul y este descendentul nodului x dacă este situat pe un nivel mai mare decât nivelul
lui x şi există un lanţ care le uneşte.
Definiţie. Nodul y este fiul ( descendentul direct al ) nodului x dacă este situat pe nivelul imediat
următor nivelului lui x şi există muchie între x şi y.
Definiţie. Nodul x este ascendentul nodului y dacă este situat pe un nivel mai mic decât nivelul
lui y şi există un lanţ care le uneşte.
Definiţie. Nodul x este părintele ( ascendentul direct al ) nodului y dacă este situat pe nivelul
imediat superior nivelului lui y şi există muchie între x şi y.
Definiţie. Nodul x este fratele nodului y dacă au acelaşi părinte.
Definiţie. Nodul x este nod terminal ( frunză ) dacă nu are nici un descendent direct.

1 Niv 1 – Nodul 1 este rădăcina arborelui.


– Nodurile 5, 6, 7 sunt fiii nodului 3.
4 Niv 2 – Nodul 7 este părintele nodurilor 9 şi 10.
2 3
– Nodul 9 este descendentul nodului 3.
– Nodul 3 este ascendentul nodului 10.
5 6 7 8 Niv 3
– Nodurile 8, 9 şi 10 sunt frunze.
– Nodurile 5, 6 şi 7 sunt fraţi.
9 10 Niv 4
Arbore

Teoremă. Un arbore cu n vârfuri are n – 1 muchii.


Teoremă. Fie un graf G=(X, U). următoarele afirmaţii sunt echivalente:
1. G este arbore.
2. G este un graf conex, minimal cu această proprietate (eliminând o muchie oarecare se obţine un graf
neconex).
3. G este un graf fără cicluri, maximal cu această proprietate (dacă se adaugă o muchie se obţine un graf
care are măcar un ciclu).

Definiţie. Fie G un graf. Un graf parţial H al său care în plus este şi arbore se numeşte
arbore parţial.
Corolar. Un graf G=(X, U) conţine un arbore parţial dacă şi numai dacă G este conex.

Reprezentarea arborilor
Fie un arbore cu n noduri.
1. Reprezentare prin matrice de adiacenţă.
aM
nxn  1, daca i este tatal lui j
a[i,j]=
 0 , in caz contrar
 0 1 1 1 0 0 0 0 0 0 

 0 0 0 0 0 0 0 0 0 0

 0 0 0 0 1 1 1 0 0 0 
 0 0 0 0 0 0 0 1 0 0

a 0 0 0 0 0 0 0 0 0 0 
 0 0 0 0 0 0 0 0 0 0

 0 0 0 0 0 0 0 0 1 1 
 0 0 0 0 0 0 0 0 0 0
 
 0 0 0 0 0 0 0 0 0 0
 0 0 0 0 0 0 0 0 0 
 0

2. Reprezentare prin liste de descendenţi

Nodul Liste de descendenţi


1 2,3,4
2
3 5,6,7
4 8
5
6
7 9 , 10
8
9
10

3. Vector de taţi – memorează pentru fiecare nod părintele acestuia. Pentru rădăcină elementul
corespunzător din vector este egal cu 0.
T=(0,1,1,1,3,3,3,4,7,7)
Arborii pot fi parcurşi prin metode specifice grafurilor: în adâncime, lăţime.

Arbori binari

Definiţie. Un arbore în care fiecare nod are maxim doi descendenţi se numeşte
arbore binar.
Dacă aceşti descendenţi există ei se numesc descendent stâng, respectiv descendent drept.
Definiţie: Se numeşte arbore binar complet un arbore binar în fiecare nod, care nu este frunză, are exact doi
descendenţi.

Propoziţie: Un arbore binar complet care are p noduri terminale, toate situate pe acelaşi nivel, are în total 2p-
1 noduri.
Arborii binari pot fi parcurşi prin metode specifice grafurilor: în adâncime,
lăţime.
Metode specifice arborilor binari :

 Parcurgerea în preordine (rădăcină- stânga – dreapta RSD) Pas1. se


vizitează rădăcina;
Pas2. se traversează subarborele stâng; Pas3. se traversează subarborele drept.

 Parcurgerea în inordine (stânga –rădăcina – dreapta SRD) presupune: Pas1. se


traversează subarborele stâng;
Pas2. se vizitează rădăcina;
Pas3. se traversează subarborele drept.

 Parcurgerea in postordine (stânga – dreapta – rădăcină SDR) Pas1. se


traversează subarborele stâng;
Pas2. se traversează subarborele drept; Pas3. se vizitează rădăcina.

Exemplu.
Soluţiile de parcurgere ale arborelui din figura următoare :

Parcurgerea SRD – ( în inordine ) 4 2 5 1 6 3 7 8


1

Parcurgerea RSD – ( în preordine ) 1 2 4 5 3 6 7 8


2 3

Parcurgere SDR – ( în postordine ) 4 5 2 6 8 7 3 1


4 5 6 7

10.3.2. Itemi cu alegere multiplă


8

1. Care este înălţimea şi care sunt frunzele arborelui descris prin vectorul de „taţi”: (0, 1, 1, 2, 2, 5, 3,
3)?
a) înălţimea 3 şi frunze: 4, 5, 6 b) înălţimea 4 şi frunze: 4, 6, 7, 8
c) înălţimea 4 şi frunze: 4, 5, 6 d) înălţimea 3 şi frunze: 4, 6, 7, 8

2. Câte frunze are arborele descris prin următorul vector de „taţi”: (0, 1, 2, 2, 4, 1, 6, 6,
8, 8)?
a) 4 b) 6 c) 5 d) 3

3. Numărul de noduri ale unui arbore cu 14 muchii este:


a) 10 b) 14 c) 13 d) 15

4. Pentru arborele având următorul vector de „taţi” (2, 3, 0, 1, 1, 2, 4, 5) care este rădăcina şi care
sunt descendenţii direcţi ai nodului 2?
a) răd: 3 şi descendenţii direcţi: 1, 6 b) răd: 3 şi descendenţii direcţi: 2, 6
c) răd: 1 şi descendenţii direcţi: 4, 6 d) răd: 2 şi descendenţii direcţi: 1, 6

5. Care este vectorul de „taţi” pentru arborele din figură?


a) (0, 1, 2, 2, 1, 1, 5, 7, 8, 8)
b) (0, 1, 2, 1, 3, 1, 5, 7, 8, 8)
c) (0, 1, 2, 3, 1, 1, 5, 7, 8, 7)
d) (0, 1, 2, 3, 1, 3, 5, 7, 7, 8)

6. Care sunt nodurile care au exact 2 descendenţi pentru un arbore cu 9 noduri, numerotate de la 1 la 9,
dat de vectorul de „taţi”: (5, 1, 1, 5, 0, 4, 4, 7, 7)?
a) 1,2,3 b) 1,4,5 c) 1,4,5,7 d) 2,4,5,7

7. Câte noduri trebuie eliminate dintr-un graf complet cu 15 noduri, pentru ca acesta să devină arbore?
a) 120 b) 106 c) 116 d) 14

8. Se dă un arbore cu 9 noduri etichetate cu numere de la 1 la 9 prin intermediul vectorului de „taţi”: (5,


0, 2, 7, 3, 3, 2, 4, 7)?
a) 7 b) 5 c) 8 d) 6

9. Se consideră arborele cu 7 noduri şi muchiile (1,5), (1,2), (2,3), (3,4), (3,6), (1,7). Care dintre
nodurile arborelui ar putea fi alese ca rădăcină pentru ca arborele să aibă număr maxim de niveluri?
a) 1,4,5,6 b) 4,5,6,7 c) 1,6,7 d) 1,2

10. Se consideră vectorul de „taţi” al unui arbore oarecare t=(0, 1, 1, 2, 2, 3, 3, 7), în care nodurile
sunt numerotate de la 1 la 8. Alegeţi afirmaţia incorectă:
a) nodurile 4,5 sunt fraţi b) nodul 3 are gradul 2
c) nodul 3 este tatăl lui 1 d) nodurile 4,5,6,8 sunt frunze

11. Se consideră arborele cu 9 noduri şi muchiile (1,5), (1,2), (2,3), (3,4), (3,6), (1,7), (7,8), (5,9). Dacă
alegem ca rădăcină nodul 2 , atunci vectorul corespunzător de „taţi” al arborelui este:
a) (2, 0, 2, 3, 1, 1, 5, 7, 8)
b) (2, 0, 2, 1, 3, 1, 3, 7, 8)
c) (2, 0, 2, 3, 1, 3, 1, 7, 5)
d) (1, 0, 2, 3, 1, 3, 5, 7, 7)

12. Se consideră vectorul de „taţi” al unui arbore oarecare t=(0, 1, 1, 2, 2, 3, 3, 7), în care nodurile sunt
numerotate de la 1 la 8. Alegeţi afirmaţia corectă:
a) nodurile 2,4,6 sunt fraţi b) nodul 5 are gradul 3
c) nodul 3 este tatăl lui 1 d) nodurile 4,5,6,8 sunt frunze

13. Precizaţi câte muchii trebuie înlăturate din graful a cărui 01100000
matrice de adiacenţă este dată alăturat, astfel încât să devină 00011000
arbore? 00000110
00000000
a) 1 b) 2 c)0 d) 3 00000000
00000001
00000001
14. Fie G un graf conex cu 50 de noduri si 1500 muchii. Numărul de muchii care trebuie
eliminate din G astfel încât acesta să devină arbore este:
a) 1451 b) 49 c) 1450 d) 100

15. Se consideră un graf. Care din următoarele afirmaţii este adevărată?


a) are un nod izolat b) nodurile au grad impar
c) este aciclic d) are 3 componente conexe

Răspunsuri itemi cu alegere multiplă

10. Grafuri

10.2. Arbori
1.b 2.c 3.d 4.a 5.a 6.c 7.d 8.d 9.b 10.c 11.c 12.d 13.a 14.a 15.c

10.3.3. Probleme rezolvate

1. Se citeşte un graf prin intermediul matricei de adiacenţă. Să se scrie un program care verifică dacă
este arbore.
Exemplu:
n=5 şi matricea de adiacenţă 0 1 0 1 1
00000
00000
00000
0 0 1 0 0 se va afişa ARBORE
Rezolvare
Aceasta este o funcție recursivă de parcurgere în adâncime (DFS – Depth-First Search), care:
-pornește de la nodul nod
-vizitează toate nodurile accesibile
-verifică dacă există cicluri
-și dacă graful este conex
#include <iostream>
using namespace std;
int s[20],a[20][20], b,n,i, suma; // a - matrice adiacență, s - vector de vizitare, n - nr. noduri
void dfs(int nod)// s[] = vector de vizitare, b = 1 → marchează existența unui ciclu
{int k;
s[nod]=1; // marcăm nodul curent ca vizitat
for(k=1;k<=n;k++)
if (a[nod][k]==1)// a[nod][k] == 1 pentru a verifica dacă există o muchie de la nod la k
{a[k][nod]=0;// eliminăm muchia inversă (dacă este neorientat)
if(s[k]==0)
dfs(k); // nodul k nu a fost vizitat => îl vizităm recursiv
else
b=1;}} // nodul k a fost deja vizitat => CICLU detectat!
int main()
{cin>>n;
for(i=1;i<=n;i++)
for(int j=1;j<=n;j++)
cin>>a[i][j];
dfs(1); // pornim parcurgerea de la nodul 1
suma=0;
for(i=1;i<=n;i++)
suma=suma+s[i];
if (suma!=n)
cout<<"Nu este conex"<<endl;
else
if(b) cout<<"Are cicluri"<<endl;
else cout<<"Arbore"<<endl;
return 0;}

2. Se dă un graf conex. Să se afişeze un graf parţial al său care este arbore.


Exemplu:
n=5 şi matricea de adiacenţă 0 1 1 0 0
10111
11011
01101
0 1 1 1 0 se va afişa 12
23
34
45
Rezolvare
#include <iostream>

int s[20],a[20][20],n;// s[] este vectorul de vizitare (s[i] = 1 ⇒ nodul i a fost vizitat)
using namespace std;

void dfs(int nod) // a[i][j] este matricea de adiacență


{int k;
s[nod]=1;
for(k=1;k<=n;k++)
if(a[nod][k]==1 && s[k]==0) // daca exista muchie si nu a fost vizitat nodul k
{cout<<nod<<" "<<k<<endl;
dfs(k);}}
int main()
{cin>>n;
for(int i=1;i<=n;i++)
for(int j=1;j<=n;j++)
cin>>a[i][j];
dfs(1);
return 0;}

3. Se dă un arbore pentru care se cunoaşte rădăcina. Să se scrie un program care afişează


pentru fiecare nod nivelul pe care se află.
Exemplu:
1 Niv 1

2 3 4 Niv 2

5 6 7 8 Niv 3

9 10 Niv 4
Rezolvare:
#include<iostream>
using namespace std; //rad - radacina
221
int n, c[20],s[20],ic=1,sc=1,a[20][20], i, niv[20], rad; //n – număr de noduri, a[i][j] – matricea de
adiacență
void bfs(int nod) // s[i] – vector de vizitare, niv[i] – nivelul nodului i, c[] – coada pentru BFS(in lățime)
{c[ic]=nod; //Inițializăm coada cu rădăcina nod, pe care o marcăm ca vizitată, ic, sc – capul și coada cozii
s[nod]=1;
niv[nod]=1;
while(ic<=sc) //Cât timp avem noduri în coadă:
{i=1;
while(i<=n)
{if(a[c[ic]][i]==1 && s[i]==0) //Verificăm pt nodul c[ic] toți vecinii i, dacă i este vecin și nu a fost vizitat
{sc++; //Îl adăugăm în coadă
c[sc]=i;
s[i]=1;
niv[c[sc]]=niv[c[ic]]+1;} //Îi setăm nivelul ca nivelul tatălui + 1
i++;}
ic++;}}
int main()
{cin>>n;
for (i=1;i<=n;i++)
for(int j=1;j<=n;j++)
cin>>a[i][j]; cout<<"Radacina este ";
cin>>rad;
bfs(rad);
for(i=1;i<=n;i++)
cout <<"Nodul "<<i<<" nivelul "<<niv[i]<<endl;
return 0;}

4. Algoritmul lui Kruskal


Fie G=(X,U) un graf neorientat, conex. Fiecare muchie are asociat un cost. Se cere un arbore parţial al
lui G, astfel încât suma costului muchiilor sale să fie minimă.
Un astfel de arbore se numeşte arbore parţial de cost minim.
Arborele parţial de cost minim poate fi construit muchie, cu muchie, dupa următoarea metoda a
lui Kruskal (1956):
 Iniţial, fiecare nod va constitui un arbore, deci vom avea n arbori. Apoi, se execută de n-
1 ori pasul următor
 Se caută muchia de cost minim care uneşte arbori care aparţin la doi arbori diferiţi. Se
selectează muchia respectivă.
Exemplu:
Ordonăm crescător (în funcţie de cost) muchiile grafului: {1, 2}, {2, 3}, {4, 5},
{6, 7}, {1, 4}, {2, 5}, {4, 7}, {3, 5}, {2, 4}, {3, 6}, {5, 7}, {5, 6} şi apoi aplicăm
algoritmul. Structura componentelor conexe este ilustrată, pentru fiecare pas, în tabelul 1.

Pasul Muchia considerată Componentele conexe ale


subgrafului <V, A>

222
iniţializare {1}, {2}, {3}, {4}, {5}, {6}, {7}
1 {1, 2} {1, 2}, {3}, {4}, {5}, {6}, {7}
2 {2, 3} {1, 2, 3}, {4}, {5}, {6}, {7}
3 {4, 5} {1, 2, 3}, {4, 5}, {6}, {7}
4 {6, 7} {1, 2, 3}, {4, 5}, {6, 7}
5 {1, 4} {1, 2, 3, 4, 5}, {6, 7}
6 {2, 5} respinsa (formează ciclu)
7 {4, 7} {1, 2, 3, 4, 5, 6, 7}

Figura Un graf si arborele său parţial de cost minim.

Algoritmul lui Kruskal aplicat grafului din Figura a.

Mulţimea A este iniţial vidă şi se completează pe parcurs cu muchii acceptate (care nu formează un
ciclu cu muchiile deja existente în A). În final, mulţimea A va conţine muchiile {1, 2}, {2, 3}, {4, 5},
{6, 7}, {1, 4}, {4, 7}. La fiecare pas, graful parţial <V, A> formează o pădure de componente
conexe, obţinută din pădurea precedentă unind două componente. Fiecare componentă conexă este la
rândul ei un arbore parţial de cost minim pentru vârfurile pe care le conectează. La sfârşit, vom avea o
singură componentă conexă, care este arborele parţial de cost minim căutat (Figura b).
Rezolvare:
#include<iostream>
using namespace std;
struct muchie
{int x, y, z; //s[i] = 1- marchează că muchia i a fost aleasă în arbore
}v[20]; // vector de muchii - v[1], v[2], ..., v[m]-fiecare element e o muchie
int n, m, d[20], s[20]; //d[i] – componenta din care face parte nodul i; n – nr noduri; m – nr muchii
void ordonare(int m) //v[i].y - nodul de sosire al muchiei i
{int i, a=0, q, w, e; //v[i].z - costul muchiei i
while(a==0) //v[i].x - nodul de pornire al muchiei i
{a=1;
for (i=1;i<m;i++)
if (v[i].z>v[i+1].z)
{q=v[i+1].x;
w=v[i+1].y;
e=v[i+1].z;
v[i+1].x=v[i].x;
v[i+1].y=v[i].y;
v[i+1].z=v[i].z;
v[i].x=q;
v[i].y=w;
v[i].z=e;
a=0;}}}
int main()
{int i;
cin>>n>>m;
for (i=1;i<=m;i++)
cin>>v[i].x>>v[i].y>>v[i].z;
ordonare(m);
for (i=1;i<=n;i++)
d[i]=i;
for (i=1;i<=m;i++)
223
if (d[v[i].x]!=d[v[i].y]) // le unesc în aceeași componentă
{int aux = d[v[i].y];
for (int j=1; j<=n; j++)
if(d[j] == aux)
d[j] = d[v[i].x];
s[i] = 1; }// marchez muchia ca fiind aleasă
cout << "Muchiile arborelui de cost minim:\n";
for (i=1;i<=m;i++)
if(s[i]==1)
cout<<v[i].x<<" "<<v[i].y<<endl;
return 0;}

10.3.4. Probleme propuse

1. Se dă un graf conex. Se cere împărţirea acestuia în m arbori parţiali de cost minim fiecare cu p
vârfuri. Să se afişeze aceşti arbori.

2. Să se creeze un arbore binar care are drept chei numere întregi citite de la tastatură. Să se afişeze
produsul cheilor impare din arbore.

3. Să se creeze un arbore binar care are drept chei numere întregi citite de la tastatură. Să se afişeze
produsul cheilor pozitive şi numărul cheilor negative din arbore.

4. Să se creeze un arbore binar care are drept chei numere întregi citite de la tastatură. Să se afişeze
nodurile din arbore care au exact doi succesori.

5. Să se scrie un program care afişează cheile nodurilor unui arbore aflate pe un anumit nivel k citit
de la tastatură.

6. Să se scrie un program care afişează parcurgerea în preordine, inordine şi postordine a unui arbore
binar.

7. Să se scrie un program care afişează elementul de pe poziţia k, unde k este citit de la tastatură, din
parcurgerea în preordine, inordine şi postordine a unui arbore binar.

8. Se dă un vector x cu n componente numere întregi reprezentând parcurgerea în preordine a unui


arbore binar. Să se creeze arborele şi să se calculeze suma cheilor numere prime din nodurile
arborelui.
9. Să se scrie un program care construieşte un arbore binar cu cheile nodurilor numere întregi. Se
dau două numere a şi b şi se cere ca programul să verifice dacă ele se află în arbore, iar în caz
afirmativ să se verifice dacă b este descendent al lui a.

10. Să se creeze un arbore binar care are drept chei numere întregi citite de la tastatură. Să se afişeze
nodurile din arbore care au unul din succesori cu cheia număr par.

11. Se dă un arbore ale cărui chei sunt numere întregi, nu neapărat distincte. Să se afişeze cheile
distincte ale arborelui şi numărul acestora.

224
12. Se dă un arbore binar ale cărui chei sunt numere întregi. Să se afişeze suma cheilor pare aflate pe
poziţii impare la parcurgerea în inordine a arborelui.

13. Se dă un arbore binar ale cărui chei sunt numere întregi. Să se afişeze cheile divizibile cu primul
şi ultimul element din parcurgerea în postordine a arborelui.

14. Se dă un arbore binar ale cărui chei sunt numere întregi. Să se afişeze numărul de noduri a căror
cheie este egală cu o valoare k citită de la tastatură şi nivelul pe care acestea se află.

15. Se dă un arbore binar ale cărui chei sunt numere întregi. Să se afişeze numărul de noduri
terminale a căror cheie este egală cu o valoare k citită de la tastatură şi nivelul pe care acestea se află
este număr impar.

📘 6. BAZE DE DATE
6.1 Concepte de bază
Bază de date (BD)

O bază de date este o colecție organizată de date corelate logic, stocate electronic, care permite:

 introducerea, modificarea și ștergerea datelor


 regăsirea rapidă a informațiilor
 partajarea datelor între utilizatori
225
Exemplu: baza de date a unei școli conține:

ID_Elev Nume Clasa Media


1 Popescu Ion X A 9.50
2 Ionescu Ana IX B 9.00

Sistem de gestiune a bazelor de date (SGBD)

SGBD este software-ul care permite:

 definirea structurii bazei de date


 manipularea datelor (adăugare, modificare, ștergere)
 crearea și executarea interogărilor
 controlul securității și al accesului

Exemple de SGBD:

 MySQL
 Oracle Database
 Microsoft Access
 Microsoft SQL Server

6.2 Clasificarea bazelor de date


Model ierarhic

 Structură arborescentă (părinte → copil)


 Relații 1:N
 Rigid, dificil de modificat

Exemplu:
Firmă → Departament → Angajat

Model rețea

 Structură de tip graf


 Relații multiple între entități (1:N, N:M)
 Mai flexibil decât modelul ierarhic

Model relațional
226
 Datele sunt organizate în tabele
 Relațiile se realizează prin chei (primare și externe)
 Cel mai utilizat model

Exemplu tabel Elevi:

ID Nume Prenume Clasa


1 Popescu Ion XA

6.3 Model conceptual al bazei de date


Entități și instanțe

 Entitate: obiect despre care stocăm date (Elev, Profesor, Carte)


 Instanță: un exemplar concret al entității (Elev: Popescu Ion)

Atribute

 Proprietăți ale entității


 Tipuri:
o simple: nume

o compuse: adresă

o derivate: vârstă

o multivaloare: telefon

Identificator unic

 Atribut care identifică unic o entitate


 Exemple: ID elev, CNP, cod produs

Relații între entități

 1:1, 1:N, N:M


 Exemple: Profesor → Elevi (1:N), Elev → Materii (N:M)

Normalizarea datelor

 FN1: valori atomice, fără grupuri repetate


 FN2: fără dependențe parțiale
 FN3: fără dependențe tranzitive

6.4 Model fizic al bazei de date relaționale

227
Tabel

 Coloane = câmpuri
 Rânduri = înregistrări

Câmpuri și tipuri de date

 INT, FLOAT, CHAR, VARCHAR, TEXT, DATE, BOOLEAN

Cheie primară

 Identifică unic o înregistrare


 Nu poate fi NULL sau duplicată

Cheie străină

 Legătură între două tabele


 Exemplu: ID_Elev în tabelul Note

Constrângeri

 NOT NULL, UNIQUE, CHECK, DEFAULT, PRIMARY KEY, FOREIGN KEY

Integritate referențială

 Asigură existența relațiilor corecte între tabele

6.5 Operații cu bazele de date


 Crearea tabelelor: CREATE TABLE
 Adăugarea înregistrărilor: INSERT INTO
 Modificarea tabelelor: ALTER TABLE
 Modificarea datelor: UPDATE
 Ștergerea tabelelor/inregistrărilor: DROP TABLE, DELETE
 Crearea interogărilor: SELECT (simple și complexe, JOIN, agregări)

Exemple SQL

SELECT simplu:

SELECT nume, prenume FROM Elevi WHERE clasa='X';

INSERT:

INSERT INTO Elevi (nume, prenume, clasa) VALUES ('Ionescu', 'Ana', 'IX B');

UPDATE:
228
UPDATE Elevi SET clasa='XII' WHERE ID=2;

DELETE:

DELETE FROM Elevi WHERE ID=1;

💻 7. ARHITECTURA GENERALĂ A
SISTEMELOR DE CALCUL
Prezentare generală
 Sistem de calcul = hardware + software
 Scop: prelucrarea informațiilor

Tipuri de sisteme

 PC, laptop, server, smartphone, embedded, supercalculator

Componente
1. Hardware: unitate centrală, memorie, periferice
2. Software: sistem de operare, aplicații

Arhitectura unui calculator personal


Unitatea centrală (CPU + Placă de bază)

 CPU: ALU, unitate de control, registre


 Exemple caracteristici CPU:
o frecvență: 3.6 GHz

o număr nuclee: 4 sau 8

o cache: L1, L2, L3

Memorie internă

 RAM (volatilă)
 ROM (permanentă)
 Cache (foarte rapidă)

Memorie externă

229
 HDD, SSD, stick USB, CD/DVD

Dispozitive periferice

 Intrare: tastatură, mouse, scanner


 Ieșire: monitor, imprimantă, boxe
 Intrare/ieșire: touchscreen, modem

Ergonomie și siguranță

 Poziție corectă
 Iluminare adecvată
 Pauze regulate
 Distanța față de monitor: 50–70 cm

🖥️8. SISTEME DE OPERARE


Definiție

Software care gestionează resursele hardware și oferă interfață utilizator.

Tipuri

 Monotasking/multitasking
 Mono-user/multi-user
 În timp real
 Mobile

Caracteristici

 Structură: nucleu, drivere, interfață, utilitare


 Funcții: gestionare procesor, memorie, fișiere, periferice, securitate

Programe și accesorii livrate

 Notepad, Paint, Media Player


 Editoare grafice, aplicații 3D, audio-video

Organizarea logică a datelor

 Sistem de fișiere ierarhic


 Operații: creare, copiere, mutare, redenumire, ștergere

230
Programe pentru fișiere comprimate

 WinRAR, 7-Zip, WinZip

Securitatea datelor

 Viruși, troieni, ransomware


 Protecție: antivirus, firewall, backup

Drepturi de autor și licențe

 Comercial, freeware, shareware, open source

📄 9. APLICAȚII DE BIROTICĂ
Procesoare de text

 Microsoft Word
 Funcții: redactare, formatare, inserare tabele și imagini

Editoare de calcul tabelar

 Microsoft Excel
 Funcții: formule, grafice, sortare, filtrare

Editoare de prezentări

 Microsoft PowerPoint
 Funcții: diapozitive, animații, multimedia

🌐 10. REȚELE DE CALCULATOARE


Concepte de bază

 Rețea = calculatoare conectate pentru schimb de date și resurse

Tipuri

 LAN, MAN, WAN, WLAN, PAN

Funcții

 Partajare resurse
 Comunicare
 Colaborare
231
Protocoale

 TCP/IP, HTTP, FTP, SMTP, POP3, IMAP

Internet

 Servicii: WWW, email, transfer fișiere, cloud, aplicații colaborative


 Exemple aplicații colaborative: Google Docs, Microsoft Teams

Adresare

 IP, DNS, URL, domenii

Limbaj HTML

 Structură: taguri, atribute, elemente


 Exemplu:

<html>
<head><title>Pagina mea</title></head>
<body>
<h1>Salut</h1>
<p>Primul paragraf</p>
</body>
</html>

232

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