Sunteți pe pagina 1din 67

Date în C++

Tipurile principale simple de date în C++

Numere întregi: int (4 bytes), unsigned int (4 bytes),


short int (2 bytes), unsigned short int (2 bytes),
(long==int – 4 bytes),
long long (8 bytes), unsigned long long (8 bytes),
char (1 byte), unsigned char (1 byte)
Diferenţele între ele constau în plaja de valori pe care o pot lua
Ex: int aparţine de la -2147483648 la 2147483647
orice depăşire în vreun sens, NU dă eroare, însă reţine alte valori
short int aparţine de la -32768..32767
char folosit ca tip întreg permite valori între -128..127
unsigned char folosit ca tip întreg permite valori între 0..255
tipul int ocupă 4 octeţi (bytes)
short int – 2 octeţi
char – 1 octet

Numere reale: float – memorează aprox. 7-8 cifre, ocupă 4 octeţi


double – memorează aprox. 15-16 cifre, ocupă 8 octeţi

Caractere: char, unsigned char


Un caracter se memorează prin codul său ASCII – un număr între 0..255.
Cele de bază sunt între 0..127

Datele dintr-un program C++ se mai clasifică în constante şi variabile

Constantele în C++:

numerice întregi: numerele ca atare, ca la mate


Ex: 0, 14, -5, 33

numerice reale:
• numerele ca atare, în care separatorul zecimal este punctul: 3.14, -8.23
• numerele în forma ştiinţifică, adică ME+P
M se numeşte mantisă
P se numeşte putere sau exponent
Aceste numere au valoarea aritmetică M*10P
Ex: 1.234E+4 = 1.234*104 = 12340
1.234E-2 = 1.234*10-2 = 0.01234

caracter (unul singur):


caracterele ca atare, scrise între apostrofuri: 'A', 'B', '2', '$', 'a'
Obs: există câteva caractere neafişabile, care au coduri. Aceste coduri încep cu \.
Ex: '\n' – trecere la rând nou (endline)
'\t' – tab
'\\' – backslash
'\'' – apostrof
'\"' – ghilimele

1
şir de caractere (string):
caracterele ca atare, scrise între ghilimele: "ana are copaci"
"ana este eleva la colegiul \"Unirea\"" "ana are\nmere"

Variabile în C++

O variabilă este zonă din memoria calculatorului capabilă să reţină (să memoreze) o
valoare. În orice moment putem folosi valoarea memorată (operaţie care se numeşte
"evaluarea variabilei") sau putem memora o altă valoare (caz în care valoarea precedentă
se pierde)

O variabilă trebuie declarată înainte de a fi utilizată.


Acest lucru se face prin:
int a;
int x,y;
double q;
int x=3,y,z=x+1;
...

Orice variabilă are un nume (identificator) şi un tip de date.

Constante literale în C++

Pe lângă constantele de care am povestit mai sus (datele ca atare) există constante pe
car ele putem folosi prin identificatori, la fel ca pe variabile, doar că atribuirea de valori
către acestea nu este permisă (va da eroare)

Declararea şi iniţializarea lor se face prin

const tip identif_constantă=valoare;

Ex:
const int a=12;

folosind a este ca şi cum am scrie 12. Orice încercare de a schimba valoarea lui a ne dă
eroare.

Expresii în C++

O expresie implică efectuarea unor operaţii.


În C++ avem operaţii:
• aritmetice: +, -, *, /, %
• relaţionale: <, <=, >, >=, ==, !=
• logice: ||, &&, !

Operaţiile aritmetice se efecutează ca la matematică. Avem câteva particularităţi:


- operatorii +, - se fac ultimii, ca la mate, în ordinea apariţiei
- operatorii *, /, % au prioritate, ei se fac primii, în ordinea apariţiei

2
operatorul
/ în C++ funcţionează diferit în funcţie de membrii săi.
Şi anume:
dacă ambii parametri sunt numere întregi, ne dă câtul întreg (FĂRĂ ZECIMALE!)
dacă cel puţin un parametru este real, ne dă câtul real, cu zecimale.

Forţarea unui membru pentru a fi văzut de un tip anume dorit de noi:


(tip)valoare

Ex:
cout<<10/3; → afişează 3
cout<<10/4; → afişează 2
cout<<10/4.0; → afişează 2.5
cout<<(double)10/4; → afişează 2.5

cout<<(int)'A'; → 65
cout<<(int)3.785; → 3
cout<<(int)3.185; → 3

operatorul %
calculează restul împărţirii întregi dintre membrii săi.

E obligatoriu ca ambii membri să fie întregi. Dacă nu e aşa, va da eroare !

Ex:
10%3 → 1
11%3 → 2
12%3 → 0

(O împărţire cu rest arată aşa:

Deîmpărţit | Împărţitor Deîmpărţit:Împărţitor=Cât


+------------ ...
========== | Câtul ======
Restul Restul
Teorema împărţirii cu rest zice că: D = C*I+R
iar restul poate fi cuprins între 0 şi I-1
)

Expresii aritmetice uzuale:


Dacă x este număr natural, atunci:
x%10 reprezintă ultima cifră (a unităţilor)
x/10 reprezintă numărul de la care s-a "şters" ultima cifră
x%100 reprezintă numărul format de ultimele două cifre ale valorii date
x/100 reprezintă numărul de la care s-au şters ultimele două cifre
x/10%10 reprezintă cifra zecilor numărului dat

3
Există o serie de funcţii, aflate în biblioteca <cmath> care ajută la efectuarea unor operaţii
matematice.
Cele mai folosite:
sqrt(x) – rădăcina pătrată (dă rezultat real)
abs(x) – modul (valoarea absolută) – pentru numere întregi
fabs(x) – modul (valoarea absolută) – pentru numere reale

Expresiile relaţionale
Au ca scop verificarea valorii de adevăr a unei comparaţii (relaţie) dintre două valori.
O astfel de expresie are ca rezultat 0 (FALS) sau 1 (ADEVĂRAT)

Ex:
1<=5 → are valoarea 1
1>5 → are valoarea 0

Expresii relaţionale uzuale:


a%2==0 → expresia este adevărată dacă şi numai dacă numărul a este par
a%2!=0 → expresia este adevărată dacă şi numai dacă numărul a este impar
a%b==0 → expresia este adevărată dacă şi numai dacă numărul a se divide la b.
sqrt(x)==(int)sqrt(x) → expresia este adevărată dacă şi numai dacă numărul x este
un pătrat perfect

Expresiile logice
Au ca scop compunerea mai multor valori de adevăr (0 şi 1) pentru a verifica dacă
anumite lucruri se întâmplă simultan sau cel puţin unul este adevărat.

operatorii logici sunt: || (sau-ul logic, conjuncţia) – se face ultima


&& (şi-ul logic, disjuncţia) – se face înainte de ||
! (negaţia) – se face prima

Tabelele de adevăr sunt:


a b a||b
0 0 0
0 1 1
1 0 1
1 1 1

a b a&&b
0 0 0
0 1 0
1 0 0
1 1 1

a !a
0 1
1 0

4
Negaţia unei expresii relaţionale schimbă semnul, ţinând cont şi de egalitate:
!(a<b) ⇔ a>=b
!(a<=b) ⇔ a>b
!(a>b) ⇔ a<=b
!(a>=b) ⇔ a<b
!(a==b) ⇔ a!=b
!(a!=b) ⇔ a==b

Negaţia expresiilor compuse:


!(E1 || E2) = !E1 && !E2
!(E1 && E2) = !E1 || !E2

Limbajul pseudocod

Foloseşte la descrierea pur teoretică a unor algoritmi, de regulă nu foarte laborioşi.


Este exprimat în limba celui care-l utilizează şi nu are reguli foarte stricte.
Astfel, în pseudocod este permisă scrierea mai apropiată de matematică, folosirea unor
operatori diferiţi dacă ei sunt sugestivi, şi un oarecare grad de libertate.

În pseudocod, operaţia de împărţire NU este ca în C++, adică întotdeauna se consideră că


dă rezultat real.
Din acest motiv, dacă în pseudocod întâlnim [a/b] acest lucru în C++, dacă a şi b sunt
întregi, îl vom transcrie ca a/b.

Iată principalele instrucţiuni în pseudocod şi transcrierile lor în C++:


Pseudocod C++
1. Citirea:
citeşte v1,v2,v3,... cin>>v1>>v2>>v3>>....;
Obs: toate entităţile citite trebuie să fie
VARIABILE
2. Afişarea:
scrie e1,e2,e3,... cout<<e1<<e2<<e3....;
Obs: entităţile afişate pot fi constante,
variabile, expresii
3. Atribuirea:
variabilă ← valoare variabilă = expresie;
Obs: în membrul drept (valoare) putem
avea o expresie, o constantă sau o altă
variabilă

5
Pseudocod C++
4. Structura condiţională / de decizie / 4a.
alternativă / de tip "dacă" if(condiţie)
4a. {
┌dacă condiţie atunci secv_adevăr;
│ secv_adevăr }
└■ 4b.
4b. if(condiţie)
┌dacă condiţie atunci {
│ secv_adevăr secv_adevăr;
│altfel }
│ secv_fals else
└■ {
secv_fals;
}
Obs: Dacă vreuna dintre secvenţe (adevăr
sau fals) este formată dintr-o singură
instrucţiune, atunci putem omite parantezele
acolade.
5. Repetitiva cu test anterior / iniţial /
precondiţionată / de tip "cât timp" while(condiţie)
┌cât timp condiţie execută {
│ secv secv;
└■ }
Dacă din start condiţia este falsă, nu face
nimic.
Dacă e adevărată se apucă de repetat şi tot
repetă cât timp condiţia rămâne adevărată
6. Repetitiva cu test posterior / final /
postcondiţionată / de tip "repetă..până când" do
┌repetă {
│ secv secv;
└până când condiţie }while(neg condiţie);
Mai întâi se trece "ca-n brânză" şi se
execută odată secvenţa.
După executare verifică dacă este falsă
condiţia şi, dacă da, continuă repetarea
până când condiţia devine adevărată

6
Pseudocod C++
7. Repetitiva cu contor / de tip "pentru" (for)
7a. for crescător
┌pentru contor←li,lf execută
│ secv 7a.
for(contor=li;contor<=lf;contor++)
└■
{
contor ia rând pe rând, crescător, din 1 în 1, secv;
toate valorile dintre li şi lf şi, pentru fiecare }
dintre ele, execută secvenţa.
Dacă din start li>lf – nu execută nimic.
7b. for descrescător
┌pentru contor←li,lf,-1 execută 7b.
│ secv for(contor=li;contor>=lf;contor--)
└■ {
La fel, doar că merge descresc. de la li la lf. secv;
}
Dacă din start li<lf – nu execută nimic.

7
Transformări echivalente între structuri repetitive

Între anumite tipuri de instrucţiuni repetitive putem avea "rescrieri" perfect


echivalente, care pot fi aplicate în orice tip de situaţie.
Le vom prezenta în continuare (sunt 4 tipuri).

Din păcate, orice rescriere care NU se află printre ele, necesită o analiză atentă şi
de multe ori se bazează pe un artificiu, formulă de calcul, pe deducerea unui fapt realizat
de algoritm, etc.

1) De pe repetitivă cu test iniţial pe repetitivă cu test final

Se dă:
┌cât timp condiţie execută
│ instrucţiuni
└■

Transformarea sa:
┌dacă condiţie atunci
│ ┌repetă
│ │ instrucţiuni
│ └până când not condiţie
└■

Obs: În C++ NU există instrucţiune adaptată după "repetă..până când" (spre exemplu,în
Pascal există repeat..until, în Basic există do..loop until). Din acest motiv, se acceptă şi
scrierea în Pseudocod de tipul execută ... cât timp. Prin urmare, unui programator
de C++ îi va veni mult mai uşor să transcrie astfel:
┌dacă condiţie atunci
│ ┌execută
│ │ instrucţiuni
│ └cât timp condiţie
└■

Exemplu:
Să se rescrie următorul pseudocod utilizând o structură repetitivă cu test final

citeşte x Iată un exemplu de rulare:


Dacă citim x=4184 se va afişa 4
pc ← x
Dacă citim x=217986 se va afişa 2
┌cât timp pc>9 execută
Dacă citim x=7 se va afişa 7
│ pc ← [pc/10] Dacă citim x=4 se va afişa 4
8
└■
scrie pc
Iată rescrierea atât pe "repetă..până când":
citeşte x
pc ← x
┌daca pc>9 atunci
│ ┌repetă
│ │ pc ← [pc/10]
│ └până când pc<=9
└■
scrie pc

... cât şi pe "cât timp..execută":


citeşte x
pc ← x
┌dacă pc>9 atunci
│ ┌execută
│ │ pc ← [pc/10]
│ └cât timp pc>9
└■
scrie pc

Obs: Există o serie de cazuri în care acel "dacă" ce îmbracă repetitiva rescrisă NU mai
este necesar. Totuşi, păstrarea sa în scriere, conform "reţetei" de mai sus, NU este
greşită, este DOAR inutilă.

2) De pe repetitivă cu test final pe repetitivă cu test iniţial

Se dă:
┌repetă
│ instrucţiuni
└până când condiţie

Transformarea sa:
instrucţiuni
┌cât timp not condiţie execută
│ instrucţiuni
└■

Obs: Partea mai anevoioasă la acest tip de transformare poate consta în faptul că, dacă
instrucţiunile din corpul repetitivei sunt multe, rescrierea lor necesită mai mult spaţiu.

O serie de repetitive pot fi scrise şi mai scurt, însă acest lucru depinde de la caz la caz,
neavând o reţetă clară, ci depinde de ingeniozitatea programatorului.

Exemplu:
Să se rescrie următorul pseudocod utilizând o structură repetitivă cu test iniţial:
citeşte n
Iată un exemplu de rulare:
9
Dacă citim n=3185 se va afişa 4
Dacă citim n=7 se va afişa 1
Dacă citim n=0 se va afişa 1
nc ← 0
┌repetă
│ n ← [n/10]
│ nc ← nc+1
└până când n=0
scrie nc
citeşte n
nc ← 0
n ← [n/10]
nc ← nc+1
┌cât timp n≠0 execută
│ n ← [n/10]
│ nc ← nc+1
└■
scrie nc

3) De pe repetitivă cu contor pe repetitivă cu test iniţial

Se dă: 3b. For descrescător:


3a. For crescător: ┌pentru contor ← li,lf,-1 execută
│ instrucţiuni
┌pentru contor ← li,lf execută └■
│ instrucţiuni
└■
Transformarea sa:
Transformarea sa: contor ← li
┌cât timp contor >= lf execută
contor ← li │ instrucţiuni
┌cât timp contor <= lf execută
│ contor ← contor - 1
│ instrucţiuni
└■
│ contor ← contor + 1
└■

4) De pe repetitivă cu contor pe repetitivă cu test final


Ne putem gândi că aplicăm 3) şi apoi 1)

Se dă: 3b. For descrescător:


4a. For crescător: ┌pentru contor ← li,lf,-1 execută
│ instrucţiuni
┌pentru contor ← li,lf execută └■
│ instrucţiuni
└■
Transformarea sa:
Transformarea sa: contor ← li
┌dacă contor >= lf atunci
contor ← li │┌execută
┌dacă contor <= lf atunci ││ instrucţiuni
10 ││ contor ← contor - 1
│└cât timp contor >= lf
└■
│┌execută
││ instrucţiuni
││ contor ← contor + 1
│└cât timp contor <= lf
└■
Transcrierea în C++

Scopul de bază al limbajului pseudocod este să descrie într-un limbaj natural, relaxat,
fără reguli stricte de sintaxă (de ex. noţiunea de diferit se poate scrie ca <>, !=, ≠, sau
atribuirea poate fi scrisă şi cu variabilă := valoare; în loc de ←, etc) un anumit
algoritm.

O primă abilitate pe care trebuie s-o deprindă un elev care studiază informatica este să
transcrie un astfel de limbaj într-un limbaj practic de programare, cu reguli foarte stricte.

În C++ structura unui program este următoarea (aceste lucruri NU fac parte din programul
pseudocod, ele trebuie scrise în orice program C++):

#include<iostream>
using namespace std;

int main()
{
declaraţii de variabile;
instrucţiuni program;
return 0;
}

În cazul mediului de programare CodeBlocks, acest program este generat automat.

În cazul programelor de nivel bac, rolul nostru este să personalizăm acest program.

Grafuri euleriene şi hamiltoniene

Eulerian = există un ciclu care trece prin toate MUCHIILE ("o singură dată" pt. ciclu este
pleonasm)
!!pt. ca un ciclu prin definiţie NU are voie să repete muchii!!
Hamiltonian = există un ciclu care trece prin toate NODURILE (o singură dată)

Un graf este Eulerian dacă şi numai dacă, abstracţie făcând de nodurile izolate, are
proprietatea că este conex iar gradul fiecărui nod al său este par
(gradul unui nod = numărul de muchii incidente în nod = care au acel nod pe post de unul
din capete)

Graf complet = oricare două noduri sunt adiacente (legate printr-o muchie)

Un graf complet cu n noduri are un număr de m=n*(n-1)/2

Suma gradelor nodurilor unui graf neorientat este egala cu dublul numărului de muchii:

11
� 𝑑(𝑥) = 2𝑚
𝑥∈𝑋

Interschimbarea valorilor a două variabile


Se face cu ajutorul unei a treia variabile. De regulă o numim "aux" (de la
"auxiliară"):
aux = a;
a = b;
b = aux;

Interschimbarea fără o a treia variabilă (aux)

Se poate aplica DOAR valorilor aritmetice (sau, în mod extins, celor care suportă
operaţii algebrice de adunare şi scădere) astfel: (fie a şi b cele două variabile)
Iată două posibile scrieri ale acestora:
a = a-b; SAU a = a+b;
b = a+b; b = a-b;
a = b-a; a = a-b;

Algoritmi care operează asupra cifrelor unui număr

1. Separarea cifrelor unui număr

Algoritmul se bazează pe împărţiri succesive la 10.


Practic, luând un număr natural n, prin expresia n%10 obţinem ultima sa cifră iar, prin
atribuirea n = n/10; se "şterge" ultima lui cifră.
Bazându-ne pe aceste două operaţii, putem deduce următorul algoritm:
(fie n=numărul căruia îi separăm cifrele)
do
{
c=n%10;
...prelucrăm cifra c...
n/=10;
}while(n);

Obs:
1. Am folosit atribuirea "prescurtată" n/=10; care e echivalentă cu n=n/10;
(există astfel de forme prescurtate pentru toate operaţiile.
De ex: s+=x; e echiv. cu s=s+x;)
2. Am folosit condiţia prescurtată while(n). În C++, orice condiţie, dacă OMITE
comparatorii, trebuie să subînţelegem (adică adăugăm "în minte") un "!=0". Deci, în cazul
de faţă subînţelegem while(n!=0)

12
3. În urma aplicării algoritmului, valoarea lui n se distruge. Prin urmare, dacă mai avem
nevoie de ea, îi facem o copie înainte de a aplica algoritmul.
4. Cifrele numărului se obţin în ordine inversă (de la dreapta la stânga)
Tipul de date int
ocupă 4 octeţi (32 biţi) şi permite memorarea unei valori cuprinse între -231..231-1, adică
-2147483648.. 2147483647 (deci aprox. ±2 miliarde, deci putem să ne bazăm pe maxim 9
cifre).
Depăşirea acestui domeniu de valori produce obţinerea de valori eronate.

2. Formarea unui număr când se dau cifrele sale de la stânga la dreapta


Formarea se face progresiv, din aproape în aproape, mergând pe ideea că, la fiecare pas,
la un număr existent se va lipi o nouă cifră.

Fie nr = variabila în care formăm numărul nou.


Avem două etape:
1. Iniţializare (trebuie făcută o singură dată, la început):
nr = 0;
2. Lipirea unei cifre "c" la numărul deja existent:
nr = nr*10 + c;

3. Formarea unui număr când se dau cifrele sale de la dreapta la stânga


Formarea se face de asemenea progresiv. Pentru alipirea unei cifre la stânga e necesar
ca, pe măsură ce formăm, să mai menţinem într-o altă variabilă p o putere a lui 10
convenabilă adăugării unei cifre în faţă.
Avem două etape:
1. Iniţializare ((trebuie făcută o singură dată, la început):
nr = 0;
p = 1;
2. Lipirea unei cifre "c" la numărul deja existent:
nr = nr + c*p;
p *= 10;

Indicaţii generale pentru problemele cu numere

13
În general ne putem descurca cu algoritmii de mai sus pe o serie de algoritmi.
Lucrurile se pot simplifica de multe ori şi prin inventarea de diferite artificii. Acestea, de
regulă, folosesc ca principiu aritmetic împărţirile şi resturile la puteri ale lui 10.

Ex:
1634376 / 1000 → 1634 (taie 3 cifre de la coadă)
1634376 % 1000 → 376 (păstrează doar ultimele 3 cifre)

De exemplu, pentru a obţine prima cifră a unui număr în variabila pc


pc=număr;
while(pc>=10)
pc/=10;

Pentru a obţine numărul fără prima sa cifră:


p=1;
while(p<=numar)
p*=10;
raspuns=numar%(p/10);

Grafuri – recapitularea câtorva noţiuni

Graf neorientat = muchiile se pot parcurge în ambele sensuri.


Terminologia este "noduri" şi "muchii"
O înşiruire de noduri legate prin muchii se numeşte lanţ.
Un lanţ în care NU se repetă muchii şi primul nod coincide cu ultimul se numeşte ciclu

Graf orientat = muchiile se numesc "arce" şi se pot parcurge doar într-un singur sens.
Terminologia este "vârfuri" şi "arce"
O înşiruire de vârfuri legate prin arce se numeşte drum.
Un drum în care NU se repetă arce şi primul vârf coincide cu ultimul se numeşte circuit

Atât lanţurile, ciclurile, drumurile cât şi circuitele se numesc elementare în momentul în


care NU se repetă niciun nod / vârf.

Exemplu:
Pentru fiecare dintre itemii 1 şi 2 scrieţi pe foaia de examen litera care corespunde
răspunsului corect.
1. Considerăm un graf orientat cu 7 noduri, numerotate de la 1 la 7, şi arcele: (1,6), (2,1),
(3,1), (3,4), (3,5), (6,2), (7,3). Care este lungimea maximă a unui circuit
elementar care se poate obţine în graf prin adăugarea unui singur arc? (4p.)
a. 6 b. 4 c. 3 d. 5

14
Răspuns corect: 5

Argumentare: tre' să găsim un drum elementar de lungime maximă căruia-i mai adăugăm
un arc.
Astfel, circuitul ar fi: 7 3 1 6 2 7 unde arcul adăugat de noi ar fi 2 7.
Expresii relaţionale
Sunt ceea ce popular numim "condiţie" ↔ o expresie relaţională verifică valoarea de
adevăr a relaţiei dintre două elemente (mai mic, mai mare, ...)
Operatorii în C++ sunt:
< mai mic
<= mai mic sau egal
> mai mare
>= mai mare sau egal
== egal
!= diferit

(Ex: cout<<(1<=3); //afişează 1


cout<<(1==3); //afişează 0)

Expresii logice
Sunt cele cunoscute de la logica matematică, adică, în ordinea priorităţii efectuării lor:
! → negaţie
&& → conjuncţie
|| → disjuncţie

Regulile negaţiei:
!(E1&&E2) = !E1 || !E2
!(E1||E2) = !E1 && !E2

Exemple
Condiţia ca valoarea variabilei de tip int x să fie cifră:
x>=0 && x<=9
Condiţia ca valoarea variabilei de tip int x să NU fie cifră:
!(x>=0 && x<=9) ↔ !(x>=0) || !(x<=9) ↔ x<0 || x>9

15
Variabile de tip flag (switch)
Sunt variabile de tip indicator, pe care le folosim DOAR cu două stări, şi anume 0 (în
general 0 = fals) şi 1 (adevărat).
Cu ajutorul lor se pot verifica următoarele două clase de probleme:
1) Testarea dacă TOATE elementele unei mulţimi satisfac o proprietate
2) Testarea dacă există cel puţin un element al unei mulţimi care satisface o proprietate

Principiul constă în iniţializarea flag-ului cu 1 sau 0, în funcţie de presupunerea făcută şi,


ulterior, verificarea elementelor mulţimii unul câte unul.
Dacă la un moment dat un anumit element NU convine, schimbăm valoarea flag-ului.
Este esenţial ca în timpul verificării element cu element să NU permitem revenirea flag-ului
la valoarea iniţială.

Numere prime (prime numbers)


Def: Se numeşte număr prim un număr care are doar doi divizori distincţi şi anume 1 şi
numărul însuşi

Exemple de numere prime:


2 3 5 7 11 13 17 19 97 101 127 1237 12347 123457
Exemple de numere NEprime:
0 1 4 6 8 10 91(=7*13) 119(=7*17)

Teoremă: Dacă un număr prim NU are niciun divizor propriu cuprins între 2 şi radicalul
său, atunci numărul este prim.
Teorema ne dă şi unul dintre algoritmii eficienţi de verificare dacă un număr este prim sau
nu: dat fiind un număr n, testăm toţi potenţialii divizori săi cuprinşi între 2 şi √𝒏. Dacă îl
prindem pe n că se divide la vreunul dintre ei → NU este prim.
Verificarea se face cu ajutorul unei variabile de tip flag.
Iată două variante de program (una clasică şi alta uşor eficientizată) care verifică dacă un
număr natural "n" este prim:
16
17
V1) Varianta clasică
is_prime=1;//asta e variabila de tip flag
for(d=2;d*d<=n;d++)
if(n%d==0)
is_prime=0;
if(n<=1)is_prime=0;//aceste două cazuri trebuie tratate separat
if(is_prime) -> este prim
else -> NU este prim

V2) Varianta îmbunătăţită


Pleacă de la observaţia că putem tăia din start numerele pare, care oricum NU sunt prime,
şi care sunt destul de numeroase.
Dacă le-am tăiat pe cele pare, putem să considerăm divizori începând DOAR de la 3, şi
mergând din 2 în 2.
is_prime=1;//asta e variabila de tip flag
if(n<=1 || n>2 && n%2==0) is_prime=0;
else
for(d=3;d*d<=n;d=d+2)
if(n%d==0)
is_prime=0;
if(is_prime) -> este prim
else -> NU este prim

CMMDC (Cel mai mare divizor comun) GCF (Greatest Common Factor)
(GGT Grösster Gemeinsamer Teiler)

Cazuri excepţie:
cmmdc(a,0) = cmmdc(0,a) = a

Obs: Două numere al căror cmmdc este egal cu 1 se numesc "prime între ele".
Ex: 14 şi 15 sunt prime între ele, pt. că cmmdc(14,15)=1

18
Unul dintre algoritmii pe care e bine să-i recunoaşteţi, dar să nu-i folosiţi
NICIODATĂ (din cauză că este neoptim) este cel prin scăderi repetate.
Principiul său constă în scăderea în prostie a valorii mai mici din cea mai mare,
până când cele două devin egale.
Valoarea la care devin egale reprezintă cmmdc-ul lor.

Ex: a=90 b=12


a b
90 12
78 12
66 12
54 12
42 12
30 12
18 12
6 12
6 6
În momentul egalităţii, valoarea respectivă reprezintă cmmdc.
Iată exprimarea acestui algoritm:
while(a!=b)
if(a>b)
a=a-b;
else
b=b-a;

Obs:
- este foarte neoptim. Imaginaţi-vă că a=2 şi b=3.000.000
- dacă a=0 sau b=0 se blochează în buclă infinită
- valorile iniţiale ale lui a şi b se distrug.

Algoritmul optim este algoritmul lui Euclid

Este dedus de fapt din algoritmul precedent, plecând de la observaţia că, dacă
scădem un număr b din alt număr a până când valoarea lui a devine mai mică decât b, de
fapt în a va rămâne RESTUL împărţirii sale la b.
Aşadar, algoritmul lui Euclid împarte iniţial cele două numere date. Se fac apoi
împărţiri repetate, împărţind împărţitorul la rest (considerându-le pe cele de la ultima
împărţire efectuară). Când în acest mod dăm peste o împărţire care are împărţitorul 0, ne
oprim. Deîmpărţitul acesteia este de fapt cmmdc-ul dorit.

19
Ex:
12 90
0 0
==
12

90 12
84 7
==
6

12 6
12 2
==
0

6 0
STOP

Iată codul:
while(b)
{
r=a%b;
a=b;
b=r;
}
cmmdc este dat de a.

Obs:
- este foarte optim. Pentru orice numere a şi b cu maxim 9 cifre, sunt suficienţi cel mult 45
de paşi pt. calculul său.
- dacă a=0 sau b=0 rezultatul este corect
- valorile iniţiale ale lui a şi b se distrug.

Eficienţa algoritmului lui Euclid


Algoritmul lui Euclid este uşor dependent de valorile cărora le calculăm cmmdc.
E limpede, spre exemplu, că:
⋅ pentru a=2 şi b=3000000 algoritmul va face 1 pas (cazul favorabil)
⋅ Pentru numere ca a=192 şi b=90 va face 3 paşi (cazul mediu)
⋅ Pentru numere ca a=21 şi b=13 va face 6 paşi (cazul nefavorabil)

20
Dacă facem o analiză ceva mai atentă a alg. lui Euclid, vom constata că numărul maxim
de paşi (cazul defavorabil) este atins în momentul în care cele două numere reprezintă
termeni vecini ai şirului lui Fibonacci.
De fapt, chiar şi acest caz, aşa naşpa cum pare, face un număr foarte mic de paşi.
Spre exemplu dacă luăm cei mai mari 2 termeni din şirul lui Fibonacci reprezentabili pe int,
aceştia fiind 1836311903 şi 1134903170 se fac 46 de paşi, ceea ce oricum este puţin.

De fapt, pentru că orice termen din Fibonacci se poate scrie ca

din punctul nostru de vedere, înseamnă că ordinul de complexitate este logaritmic, adică,
faţă de valorile lui a şi b, numărul de paşi este de genul logxa sau logxb.
CMMMC (cel mai mic multiplu comun)

Metoda cea mai eficientă de a-l calcula se bazează pe proprietatea matematică:


produsul dintre cmmdc-ul şi cmmmc-ul a două numere este egal cu produsul numerelor.
Adică:
a * b = cmmdc * cmmmc ⇒
cmmmc = a / cmmdc * b

Probleme care prelucrează şiruri de numere fără a folosi vectori


Acest tip de probleme citesc valorile şirului într-o singură variabilă, orice valoare nou citita
distrugând valoarea citită la pasul anterior.
Evident, NU orice problemă se pretează acestei abordări. Spre exemplu, pentru a sorta
un şir de valori, acesta trebuie OBLIGATORIU memorat într-un vector.
Multe probleme însă se pot rezolva fără vectori, eventual, acolo unde cerinţa impune, e
nevoie de memorarea ultimelor două sau trei valori introduse.

Iată câteva tipuri de enunţuri, cu tot cu implementarea lor:


A) Se citeşte n, se citesc apoi n valori. Să se ... (va fi luată în considerare fiecare dintre
valorile introduse)
21
cin>>n; //n=câte valori se vor introduce
for(i=1;i<=n;i++)
{
cin>>x;//în variab. x citim valoare cu valoare
..prelucrăm x...
}

B) Se citesc valori în mod repetat, până la îndeplinirea unei condiţii (gen până se bagă un
0, sau un nr. negativ). Să se ... (va fi luată în considerare fiecare dintre valorile introduse)
cin>>x;
while(x NU indeplineşte condiţia de terminare)
{
..prelucrăm x...
cin>>x;
}
A1) Se citeşte n, se citesc apoi n valori. Să se ... ţinând cont de toate perechile de valori
vecine în timpul introducerii.
cin>>n; //n=câte valori se vor introduce
cin>>x;//se citeşte separat prima valoare
for(i=2;i<=n;i++)
{
cin>>y;//în variab. y citim următoarea valoare
..prelucrăm perechea (x,y)...
x=y;
}

B1) Se citesc valori în mod repetat, până la îndeplinirea unei condiţii (gen până se bagă un
0, sau un nr. negativ). Să se ... ţinând cont de toate perechile de valori vecine în timpul
introducerii.
cin>>x>>y;
while(y NU indeplineşte condiţia de terminare)
{
..prelucrăm perechea (x,y)...
x=y;
cin>>y;
22
}

A2) Se citeşte n, se citesc apoi n valori. Să se ... ţinând cont de toate tripletele de valori
vecine în timpul introducerii.
cin>>n; //n=câte valori se vor introduce
cin>>x>>y;//se citesc separat primele doua valori
for(i=3;i<=n;i++)
{
cin>>z;//în variab. z citim următoarea valoare
..prelucrăm tripletul (x,y,z)...
x=y;
y=z;
}
B2) Se citesc valori în mod repetat, până la îndeplinirea unei condiţii (gen până se bagă un
0, sau un nr. negativ). Să se ... ţinând cont de toate tripletele de valori vecine în timpul
introducerii.
cin>>x>>y>>z;
while(z NU indeplineşte condiţia de terminare)
{
..prelucrăm tripletul (x,y,z)...
x=y;
y=z;
cin>>z;
}

Descompunerea în factori primi

Se face în mod asemănător celei de la matematică:


Ex: n=2100
2100 2
1050 2
525 2
525 3
175 3
175 4
175 5
35 5

23
7 5
7 6
7 7
1 7
STOP
Algoritmul presupune următoarele:
- luăm divizori începând de la valoarea d=2.
Pe o repetitivă "mare", ne ocupăm de fiecare divizor în parte, astfel:
- dacă numărul dat se divide la d, îl divizăm până nu se mai poate şi numărăm câte
împărţiri s-au efectuat (într-o variabilă p).
- după fiecare testare a unui divizor d, dacă acesta a fost un divizor real, afişăm
informaţiile despre el : valoarea (d) şi puterea (p)
Iată algoritmul:
fie n = nr. de descompus
d=2;
while(n>1)
{
p=0;//în p numărăm puterea la care apare factorul d în n
while(n%d==0)
{
n/=d;
p++;
}
if(p)
cout<<"Factor = "<<d<<" putere = "<<p<<"\n";
d++;
}

Obs: În urma algoritmului, valoarea lui n se distruge.

Şirul lui Fibonacci

Este un şir definit astfel (recurent):


- primii doi termeni sunt egali cu 1
- orice alt termen este egal cu suma celor 2 de dinaintea sa.
Iată primii 11 termeni ai săi:
1 1 2 3 5 8 13 21 34 55 89

El poate fi foarte uşor generat "jonglând" cu 3 variabile în care reţinem:


t1, t2 = ultimii doi termeni calculaţi
tc = termenul curent, care la fiecare pas se obţine ca suma celor 2 de dinainte.
Iată cum se pot genera şi afişa primii n termeni:
24
t1=1;
t2=1;
for(i=1;i<=n;i++)
{
cout<<t1<<" ";
tc=t1+t2;
t1=t2;
t2=tc;
}

Obs: Raportul t2/t1, când n→ ∞ poartă numele de "raportul de aur" sau "phi" şi are valoare
aproximativă de 1,61803...
Determinarea minimului dintr-un şir de valori

A) Se ia o variabilă "min" care se iniţializează cu:


- cu prima dintre valorile dintre care calculăm minimul
- cu o valoare SIGUR mai mare decât cele dintre care calculăm minimul
- cu cea mai mare valoare acceptată de int, şi anume constanta INT_MAX
(care e definită în biblioteca <climits>)
(INT_MAX este de fapt egală cu 2147483647)

B) se compară rând pe rând valorile introduse (fie o astfel de valoare "x") şi se


actualizează min dacă este cazul:
if(x<min)
min=x;

Determinarea celor mai mici două minime printr-o singură parcurgere

A) Se procedează analog, considerând două variabile min1 şi min2, în care vom


determina, în ordine, cele mai mici două minime (min1 < min2)

Iniţializăm ca mai înainte, preferabil cu ceva f. mare:


min1 = INT_MAX;
min2 = INT_MAX;

B) se compară rând pe rând valorile introduse prin situarea lor între min1 şi min2 respectiv
actualizările convenabile:

if(x<min1)
{
min2=min1;
min1=x;
}
else
if(x<min2)
min2=x;

25
Vectori

Un vector este o variabilă capabilă să memoreze simultan mai multe valori. Toate valorile
vectorului trebuie să fie de acelaşi tip (numere întregi, reale, alte tipuri).
Elementele sunt structurate (memorate) în funcţie de un indice (poziţie) în cadrul
vectorului.
Declararea unui vector în C++:
tip nume_vector[nr_elemente];
Ex:
int a[4];

Obs:
1) Indicii încep de la 0. Indicele maxim va fi aşadar numărul de elemente din declaraţie,
minus 1. Spre exemplu, vectorul de mai sus are elementele: a[0], a[1], a[2] şi a[3].
2) nr_elemente (în cadrul declaraţiei) trebuie să fie O CONSTANTĂ.
3) Un stil mai şcolăresc foloseşte indici de la 1 (chiar dacă elementul a[0] există, îl
ignorăm). În acest caz trebuie să avem grijă ca, în declarare, să prevedem cu 1 mai mult
decât maximul posibil.
De exemplu dacă o problemă specifică "folosim vectorul a cu cel mult 10 elemente" iar noi
dorim să programăm de la 1, vom declara
int a[11];

Citirea unui vector

1) De la tastatură, când în prealabil se dă şi numărul de elemente din vector:


Fie vectorul "a" cu "n" elemente, cu maxim 50 de valori.
int a[51];
cin>>n;//trebuie să introducem mai întâi numărul de elemente din
vector
for(i=1;i<=n;i++)
cin>>a[i];

26
2) De la tastatură, când în prealabil NU se dă şi numărul de elemente din vector, ci se tot
citesc elemente până la îndeplinirea unei condiţii:
cin>>x;//citim prima valoare
n=0;
while(x NU îndeplineşte condiţia de terminare)
{
a[++n]=x; //asta e echivalentă cu : {n++;a[n]=x;}
cin>>x;
}

3) Din fişier, în cazul în care în fişier se dau numere separate prin caractere albe, fără a fi
menţionat anterior şi numărul de valori ce se vor citi:
fie fin = fişierul de intrare
(de regulă se deschide prin: ifstream fin("nume.extensie");
şi trebuie inclusă biblioteca <fstream> )
n=0;
while(fin>>x) //treaba asta are atât rolul de a citi cât şi de a
{ //întrerupe while-ul în momentul în care NU mai are ce citi

a[++n]=x;
}

Afişarea unui vector


1) Simplu:
for(i=1;i<=n;i++)
cout<<a[i]<<" ";
cout<<"\n";

2) Fancy: între paranteze şi cu virgule între ele:


cout<<"("<<a[1];
for(i=2;i<=n;i++)
cout<<","<<a[i];
cout<<")\n";

27
Căutarea unei valori într-un vector

Fie vectorul a cu n elemente. Ne interesează să căutăm o valoare care îndeplineşte o


condiţie. Mai precis să vedem dacă apare sau nu, iar dacă apare, să determinăm indicele
primei sale apariţii.
Metoda 1:
pe for întrerupt forţat cu break:
indice=-1;//în această variabilă vom determina indicele. Marca -1 va fi un
//semn că valoarea căutată NU a fost găsită
for(i=1;i<=n;i++)
{
if(a[i] are proprietatea cerută)
{
indice=i;
break;
}
}
if(indice==-1) → NU apare
else → apare la indicele indice

Metoda 2:
se pretează doar atunci când condiţia pe care trebuie s-o îndeplinească elementul se
poate reduce la un simplu if. Această metodă face căutarea pe un while:
k=1;
while(k<=n && a[k] NU îndepl. condiţia)
k++;
if(k>n) → NU apare
else → apare la indicele k.

(k<=n se traduce prin "mai avem unde căuta" iar


a[k] NU îndepl. condiţia se traduce prin "N-am găsit" iar
k++; se traduce prin "continuăm să căutăm"

Formarea unui vector element cu element

Am aplicat deja acest procedeu atunci când am citit din fişier până la terminarea acestuia.

Principiul este următorul:


- se iniţializează n=0; (n va conţine în final numărul de elemente ale vectorului)
- de fiecare dată când dorim să adăugăm un element nou după cele existente:

28
a[++n] = valoarea dorită;
Conversii între tipuri de date

Prin conversie se înţelege interpretarea unei valori care aparţine unui tip de date ca şi
valoare care aparţine altui tip de date.
Astfel, conversia de la int la double sau float este într-o oarecare măsură naturală, pe când
conversia inversă se va face cu pierderi de informaţie.
Pentru a face conversia de date în C++ se folosesc "operatorii de casting", mai precis
cuvintele cheie ale tipurilor de date incluse între paranteze şi scrise înaintea datelor de
convertit.
Ex:
(int)8.98 → 8
(double)8 → 8.0

Cele de mai sus sunt conversii explicite (adică i le precizează utilizatorul programului).

Mai există şi conversii implicite:


• dacă se atribuie o valoare întreagă unei variabile reale, aceasta va primi valoarea
respectivă cu zecimalele .0
(dacă avem double x; x=7; - de fapt x primeşte 7.0)
• dacă se atribuie o valoare reală unei variabile întregi, zecimalele vor fi trunchiate.
(dacă avem int x=11.0/4; x va primi 2, deşi rezultatul împărţirii este 2.75)

Operatorii / şi %
În C++ expresia a / b are următoarele semnificaţi:
1) Dacă a şi b sunt întregi, ne va furniza câtul întreg.
(Dacă vreunul dintre a sau b sunt negativi, se respectă regula semnelor).
Ex:
11 / 4 → 2
-11 / 4 → -2
11 / -4 → -2
-11 / -4 → 2

29
2) Dacă cel puţin unul dintre a sau b este real (float sau double), ne va furniza câtul cu
zecimale.
Ex:
11.0 / 4 → 2.75
11 / 4.0 → 2.75
11.0 / 4.0 → 2.75
-11.0 / 4 → -2.75
(double)11/4 → 2.75
(double)(11/4) → 2.0

Expresia a%b reprezintă restul împărţirii întregi.


Obs. Dacă cel puţin unul dintre membrii lui % este de tip real, operaţia va da eroare
(nici măcar NU compilează).

Restul se calculează din teorema împărţirii cu rest, adică R = D – C*I.


(practic, a%b = a – b*(a/b).)
Ex:
11%4 → 3
11%-4 → 3
-11%4 → -3
-11%-4 → -3
(Obs: Restul are semnul deîmpărţitului. !!! LA MATE NU E LA FEL !!!)

Ştergerea unui element dintr-un vector, de la un indice cunoscut, k


Fie vectorul a cu n elemente. Indicele k este dat sau obţinut dintr-o etapă anterioară.
Pentru a şterge elementul de la indicele k, de fapt toate elementele de după acest indice,
adică cele cu indici între k+1 şi n trebuie mutate cu o poziţie la stânga:
a[1] a[2] ... a[k-1] a[k] a[k+1] a[k+2] ... a[n-2] a[n-1] a[n]

După mutare, actualizăm valoarea lui n: vectorul va avea cu 1 element mai puţin.
Iată codul:
for(i=k;i<=n-1;i++)

30
a[i]=a[i+1];
n--;

Inserarea unui element


Fie vectorul a cu n elemente. Indicele k este dat sau obţinut dintr-o etapă anterioară.
Pentru a insera o valoare v la indicele k (adică pe locul vechiului a[k], in sensul că toate
valorile de la a[k] şi până la a[n] se mută), de fapt toate elementele de după acest indice,
adică cele cu indici între k şi n, trebuie mutate cu o poziţie la dreapta iar pe poziţia pe care
ne-am făcut loc se inserează elementul. Mutarea trebuie făcută de la dreapta la stânga:
a[1] a[2] ... a[k-1] a[k] a[k+1] a[k+2] ... a[n-2] a[n-1] a[n] a[n+1]

După mutare, actualizăm valoarea lui n: vectorul va avea cu 1 element mai mult.
La indicele la care ne-am făcut loc, inserăm noul element:
for(i=n;i>=k;i--)
a[i+1]=a[i];
n++;
a[k]=valoarea_de_inserat

Sortări
Prin sortare înţelegem rearanjarea elementelor unui vector astfel încât să le aducem în
ordine crescătoare sau descrescătoare.
Există foarte multe metode de a realiza acest lucru.

În continuare ne ocupăm de sortări crescătoare. E clar că cele descrescătoare se fac "în


oglindă". Teoria de mai jos se va referi la un vector a cu n elemente, pe care le sortam
deci crescător.

Sortarea prin selecţia minimului


Este o sortare în n-1 paşi. La fiecare pas k, (k=1,n-1) se fac următoarele:
- se determină minimul dintre elementele cuprinse între indicii k şi n, cu tot cu indicele său
- se interschimbă minimul cu elementul de la indicele k.

Iată codul:

31
for(k=1;k<=n-1;k++)
{
min=a[k];imin=k;
for(j=k;j<=n;j++)
if(a[j]<min)
{
min=a[j];
imin=j;
}
aux=a[k];a[k]=a[imin];a[imin]=aux;
}
Sortarea prin metoda bubble-sort
Este o sortare care face multiple traversări ale vectorului.
La fiecare traversare se compară câte două elemente vecine, a[i] şi a[i+1] (să avem grijă
să mergem cu i până la n-1). Dacă elementele NU sunt în ordinea care convine, se vor
interschimba.
Algoritmul se încheie în momentul în care, la o astfel de traversare, n-am mai
interschimbat nimic.

Iată codul:
do
{
gata=1;
for(i=1;i<=n-1;i++)
if(a[i]>a[i+1])
{
aux=a[i];a[i]=a[i+1];a[i+1]=aux;
gata=0;
}
}while(gata==0);

Sortarea prin interschimbare (!preferabil s-o utilizaţi pe asta faţă de celelalte!)


Este o sortare bazată pe cea a selecţiei minimului. Are codul cel mai simplu şi e cel mai
uşor de adaptat la diverse situaţii.

32
Ideea algoritmului: Se compară fiecare element DOAR cu cele de DUPĂ el. Dacă ordinea
a două elemente NU convine, le interschimbăm.
Iată codul:
for(i=1;i<=n-1;i++)
for(j=i+1;j<=n;j++)
if(a[i]>a[j])
{
aux=a[i];a[i]=a[j];a[j]=aux;
}

Sortarea prin inserţie


Presupune menţinerea sortată a unui vector în care, la fiecare pas, inserăm câte un
element astfel încât acesta să rămână tot sortat.
Iată algoritmul care citeşte n elemente şi formează cu ele vectorul a, sortat:
m=0;
cin>>n;
for(k=1; k<=n; k++)
{
cin>>v;//inseram v in vectorul a cu m elemente direct unde-şi are locul
j=1;//determinăm j=indicele la care trebuie inserat
while(j<=m && a[j]<=v)//adică primul elem. mai mare ca v
j++;
for(i=m; i>=j; i--)
a[i+1]=a[i];
m++;
a[j]=v;
}

Sortarea prin numărare


Este un algoritm eficient d.p.d.v. al timpului de executare, în general superior celor
prezentate anterior. Din păcate, este foarte restrictiv asupra datelor cărora li se poate
aplica: nu se poate aplica decât dacă valorile de sortat sunt numere naturale, suficient de
mici (şi anume de maxim 4 cifre, iar raportul dintre valoarea maximă şi numărul de valori
să nu fie foarte mare (1 = raport bun, 10 = raport acceptabil, >100 – nu e foarte bun).
(Nu e musai ca valorile să fie naturale şi cuprinse între 0 şi vmax, însă dacă nu sunt aşa
trebuie ca, cel puţin, printr-un artificiu, să fie realizată o echivalenţă între valorile date şi
nişte valori naturale de la 0 la vmax)

33
Principiul constă în a NU memora valorile pe care le sortăm, ci a memora informaţii
despre ele într-un vector care se numeşte "de numărare". Dacă acest vector este numit
"nr", atunci elementele sale vor avea semnificaţia: nr[v]=numărul de apariţii ale valorii v.
De exemplu, să considerăm că avem de sortat următorul şir de valori, care sunt TOATE
cifre (0<=cifra<=9):
1 3 3 2 8 9 8 6 5 4 4 0 1 0 1 0 2 1 0 2
Iată cum arată vectorul nr pentru ele:
v 0 1 2 3 4 5 6 7 8 9
nr[v] 4 4 3 2 2 1 1 0 2 1

Pe baza lui nr[v] pot liniştit să afişez valorile sortate:


0 0 0 0 1 1 1 1 2 2 2 3 3 4 4 5 6 8 8 9

Iată algoritmul pentru n valori naturale, cuprinse între 0 şi vmax, pe care le sortăm
crescător:
int nr[vmax+1];
for(i=0;i<=vmax;i++) vmax[i]=0;//umplem vectorul cu 0.
//obs: putem sări complet peste această etapă dacă declarăm vectorul global,
//însă dacă facem asta la bac trebuie să punem un comentariu imediat după ce
//l-am declarat, de genul "//fiind declarat global este iniţializat cu 0"
for(i=1;i<=n;i++)
{
cin>>v;//v=valoarea de la pasul curent
nr[v]++;//o numărăm, adică incrementăm nr[v]
}
//pe baza lui nr afişăm valorile gata sortate:
for(i=0;i<=vmax;i++)
for(j=1;j<=nr[i];j++)
cout<<i<<" ";
Numărarea în bazele 4, 10 şi 2
Numărul în baza 2
Numărul în baza 4 Numărul în baza 10
(baza 2=binar)
0 0 0
1 1 1
2 2 10
3 3 11
10 4 100
11 5 101

34
12 6 110
13 7 111
20 8 1000
21 9 1001
22 10 1010
23 11 1011
30 12 1100
31 13 1101
32 14 1110
33 15 1111
100 16 10000

abcde(x) = a⋅x4+ b⋅x3+ c⋅x2+ d⋅x1+e

10(2) = 2(10)

Interclasare
Interclasarea este algoritmul OPTIM prin care, din elementele a doi vectori gata sortaţi
se obţine un vector care şi el este tot sortat.
Principiul constă în parcurgerea ambilor vectori astfel încât în fiecare să reţinem câte un
indice curent. La fiecare pas se compară elementele de la indicele curent iar, cel care este
mai mic, va trece în vectorul final, iar cu acel indice se avansează.
Iată transcrierea sa pentru vectorul a, cu n elemente pe care-l interclasăm cu vectorul b,
cu m elemente, rezultatul fiind trecut în vectorul c, care va avea în final k=n+m elemente:
i=1;j=1;k=0;//i=indicele curent pentru vectorul a;
//j=indicele curent pentru vectorul b;
//k=indicele folosit în formarea pas cu pas a vectorului c.
while(i<=n && j<=m)//kt timp mai avem elemente în ambii vectori
if(a[i]<b[j])
c[++k]=a[i++];
else
c[++k]=b[j++];
while(i<=n)//dintre cele două while-uri sigur se va executa DOAR
c[++k]=a[i++];//unul singur, pt. că în urma terminării
while(j<=m)//while-ului de mai sus, fie i>n fie j>n
c[++k]=b[j++];

Căutarea binară
Căutarea binară este algoritmul OPTIM prin care putem determina dacă un element se
găseşte sau nu într-un vector SORTAT. Dacă se găseşte, atunci se determină indicele
său. Dacă nu, se determină doi indici între care acesta ar putea fi încadrat, ca valoare.
Fie vectorul a, cu n elemente, iar valoarea pe care o căutăm este x.

35
Principiul algoritmului este următorul: se consideră la fiecare pas două limite (margini -
indici) între care efectuez căutarea. Fie cei doi indici li (iniţial egal cu 1) şi lf (iniţial egal
cu n). Algoritmul se petrece astfel:
- calculez indicele din mijloc: m=(li+lf)/2.
- compar x cu a[m]. Dacă sunt egale, algoritmul se încheie cu succes.
- Dacă nu, dacă x<a[m], atunci ne mutăm cu căutarea în stânga: lf=m-1;
dacă x>a[m], atunci ne mutăm cu căutarea în dreapta: li=m+1;
Dacă la un moment dat ajungem în situaţia li>lf, înseamnă că algoritmul se încheie fără
succes (adică elementul x Nu se află în vector)
Totuşi, pe baza valorilor rămase în li şi lf, ne putem da seama că:
- dacă li>n, înseamnă că x este mai mare decât TOATE elementele vectorului
- dacă lf<1, înseamnă că x este mai mic decât TOATE elementele vectorului
- dacă nu-i nici unul de mai sus, înseamnă că a[lf]<x<a[li]
Iată codul:
li=1;lf=n;
m=(li+lf)/2;
while(li<=lf && a[m]!=x)//deci cat timp elem. NU a fost gasit shi indicii nu s-au incalecat
{
if(x<a[m])
lf=m-1;
else
li=m+1;
m=(li+lf)/2;
}
if(li<=lf)
cout<<"Gasit la indicele "<<m;
else
if(lf<1)
cout<<"Element negasit, insa mai mic decit toate";
else
if(li>n)
cout<<"Element negasit, insa mai mare decit toate";
else
cout<<"Element negasit, insa cuprins intre elem. de pe indicii "<<lf<<" si "<<li;

Numărul de paşi al acestui algoritm este în cel mai rău caz log2n ceea ce este un rezultat
FOARTE bun. Ca să aveţi o imagine, dacă n=1.000.000 orice valoare se poate găsi în
maxim 20 de paşi.

Funcţii matematice din biblioteca <cmath>

36
1) double sqrt(double x);
calculează rădăcina pătrată a numărului real x.
!!Rezultatul este întors de tip real!! – chiar dacă valoarea este naturală, e privită ca şi cum
ar avea după ea zecimalele .0000!!
Ex:
cout<<sqrt(2); → 1.4142
cout<<sqrt(81); → 9
cout<<sqrt(81)/2; → 4.5
cout<<(int)sqrt(81)/2; → 4
cout<<sqrt(169)%10; → EROARE

2) double pow(double x,double y);


Calculează xy.
Dacă puteţi calcula xy prin înmulţiri repetate, e bine să NU utilizaţi această funcţie
deoarece:
- rezultatul întors este de tip double
- pe anumite cazuri există mici erori de calcul (de genul 10-16 care sunt suficient de mici,
însă deranjante. De ex., pow(27,1.0/3) care ar fi menită să scoată radical de ordin 3 din
27, în loc să dea 3 dă 2.9999999999)

3)
int abs(int x); - calculează valoarea absolută (modul).
În anumite versiuni ale limbajului, această funcţie NU este prezentă în
<cmath> ci în <cstdlib>

cout<<abs(-17); - afişează 17
cout<<abs(-17)%10; - afişează 7
cout<<abs(-9)/2; - afişează 4
cout<<(double)abs(-9)/2; - afişează 4.5

3') double fabs(double x); - analog, doar că se aplică nr. reale, dând rezultat real.
cout<<fabs(-17); - afişează 17
cout<<fabs(-17)%10; - EROARE

37
cout<<fabs(-9)/2; - afişează 4.5
cout<<(int)fabs(-9)/2; - afişează 4

4) double floor(double x); - calculează partea întreagă (ca la mate)


!! funcţia, deşi întoarce rezultat de tip int, îl întoarce privit ca valoare de tip
double!!
Ex:
cout<<floor(35.4); - afişează 35
cout<<floor(35.9); - afişează 35
cout<<floor(35.9)/10; - afişează 3.5
cout<<floor(35)/10; - afişează 3.5
cout<<floor(35.9)%10; - EROARE

5) double ceil(double x); - calculează întregul obţinut prin rotunjire prin adaos (în
sus)
Ex:
cout<<ceil(35.4); - afişează 36
cout<<ceil(35.9); - afişează 36
cout<<ceil(35.9)/10; - afişează 3.6
cout<<ceil(35)/10; - afişează 3.5
cout<<ceil(35.9)%10; - EROARE
Matrice
(substantiv neologic invariabil
o matrice – două matrice / elementele matricei / elementele matricelor)
O matrice se mai numeşte şi "tablou bidimendsional".
Este o variabilă structurată pe "linii" şi "coloane".
Declararea unei matrice:
tip nume[nr_linii][nr_coloane];
Ex:
int a[2][4];
Ca şi în cazul vectorilor, indicii încep de la 0. În funcţie de stilul programatorului, putem
lucra de la 0 sau de la 1.
Orice element al matricei se identifică prin indicii de linie şi de coloană:
38
a[i][j]
De exemplu, matricea de mai sus are următoarele elemente:
a[0][0] a[0][1] a[0][2] a[0][3]
a[1][0] a[1][1] a[1][2] a[1][3]

Citirea unei matrice: Se face pe linii, adică se ia fiecare linie de la 1 la n şi în cadrul


fiecărei linii se iau din nou şi se citesc elementele acesteia:
cin>>n>>m;//nr. linii şi nr. coloane
for(i=1;i<=n;i++)
for(j=1;j<=m;j++)
cin>>a[i][j];
Afişarea unei matrice: Se face pe linii, şi după elementele fiecărei linii se trece la rând
nou:
for(i=1;i<=n;i++)
{
for(j=1;j<=m;j++)
cout<<a[i][j]<<" ";
cout<<"\n";
}
Notaţii general utilizate: n şi m = linii şi coloane, i = indice de linie, j = indice de coloană

Scrierea generică a unei matrice: De multe ori, când rezolvăm o problemă, e indicat să
scriem elementele "generic", adică cu n, m şi ... astfel încât să putem identifica o linie,
coloană sau altă zonă care ne interesează ("ciorna" rezolvării)
Iată o astfel de scriere (indicii pot fi scrişi schematic, ca la mate):

a1,1 a1,2 a1,3 ... a1,j ... a1,m-1 a1,m


a2,1 a2,2 a2,3 ... a2,j ... a2,m-1 a2,m
a3,1 a3,2 a3,3 ... a3,j ... a3,m-1 a3,m
: : : : : :
ai,1 ai,2 ai,3 ... ai,j ... ai,m-1 ai,m
: : : : : :
an,1 an,2 an,3 ... an,j ... an,m-1 an,m

39
Ca să parcurgem o linie anumită, i (elem. cu roşu + cel mov)
for(j=1;j<=m;j++)
..prelucrăm elementul a[i][j]..
Ca să parcurgem o coloană anumită, j (elem. cu verde + cel mov)
for(i=1;i<=n;i++)
..prelucrăm elementul a[i][j]..

Matrice pătratice
= au acelaşi număr de linii şi coloane.
Specific acestor matrice este că, datorită simetriei organizării lor apar noţiunile de
diagonale (principală şi secundară) şi respectiv zone delimitate de acestea.

Diagonală principală şi zonele separate de aceasta:


a1,1 a1,2 a1,3 ... ... a1,n-1 a1,n
a2,1 a2,2 a2,3 ... ... a2,n-1 a2,n
a3,1 a3,2 a3,3 a3,4 ... ... a3,n-1 a3,n
: : : .. : :

ai,1 ai,2 ai,3 ... ai,i-1 ai,i ai,i+1 ... ai,n-1 ai,n

: : :
an-1,1 an-1,2 an-1,3 an-1,n-2 an-1,n-1 an-1,n
an,1 an,2 an,3 ... ... an,n-2 an,n-1 an,n

I) Diagonala principală
Este caracterizată de faptul că indicii de linie şi coloană sunt egali:
i == j
Dacă dorim s-o parcurgem DOAR pe ea:
for(i=1;i<=n;i++)
..parcurgem a[i][i]..
II) Zona de sub diagonala principală
Este caracterizată de faptul că indicele de linie este mai mare faţă de indicele de coloană:
i > j
Dacă dorim să parcurgem DOAR această zonă FĂRĂ a trece prin toată matricea:
for(i=2;i<=n;i++)
for(j=1;j<=i-1;j++)
..parcurgem a[i][j]..
III) Zona de deasupra diagonalei principale

40
Este caracterizată de faptul că indicele de linie este mai mare faţă de indicele de coloană:
i < j
Dacă dorim să parcurgem DOAR această zonă FĂRĂ a trece prin toată matricea:
for(i=1;i<=n-1;i++)
for(j=i+1;j<=n;j++)
..parcurgem a[i][j]..
Diagonală secundară şi zonele separate de aceasta:
a1,1 a1,2 a1,3 ... ... a1,n-2 a1,n-1 a1,n
a2,1 a2,2 a2,3 ... ... a2,n-2 a2,n-1 a2,n
a3,1 a3,2 a3,3 a3,4 ... ... a3,n-3 a3,n-2 a3,n-1 a3,n
: : : .. : :

ai,1 ai,2 ... ai,n-i ai,n-i+1 ai,n-i+2 ... ... ai,n-1 ai,n

: : :
an-1,1 an-1,2 an-1,3 an-1,n-2 an-1,n-1 an-1,n
an,1 an,2 an,3 ... ... an,n-2 an,n-1 an,n

I) Diagonala secundară
Este caracterizată de faptul că suma dintre indicii de linie şi coloană este aceeaşi:
i+j==n+1 (⇔ j==n-i+1)
Dacă dorim s-o parcurgem DOAR pe ea:
for(i=1;i<=n;i++)
..parcurgem a[i][n-i+1]..
II) Zona de sub diagonala secundară
Este caracterizată de faptul că suma indicilor devine > decât n+1
i+j>n+1
Dacă dorim să parcurgem DOAR această zonă FĂRĂ a trece prin toată matricea:
for(i=2;i<=n;i++)
for(j=n-i+2;j<=n;j++)
..parcurgem a[i][j]..
III) Zona de deasupra diagonalei secundare
Este caracterizată de faptul că indicele de linie este mai mare faţă de indicele de coloană:
i+j<n+1
Dacă dorim să parcurgem DOAR această zonă FĂRĂ a trece prin toată matricea:
for(i=1;i<=n-1;i++)
for(j=1;j<=n-i;j++)
..parcurgem a[i][j]..

41
Şiruri de caractere (stringuri)
Un şir de caractere se declara pur şi simplu ca şi vector de char, însă este dotat cu
proprietăţi suplimentare faţă de un vector cu elemente numerice.
Mai precis, o serie de instrucţiuni tratează stringul ca un "tot".
Spre exemplu, poate fi iniţializat cu un şir particular de caractere "dintr-un foc" fără a face
acest lucru caracter cu caracter.

Tipul char
Permite memorarea unui singur caracter. În memorie orice caracter este reprezentat prin
un număr cuprins între 0..255 care se numeşte codul său ASCII (American Standard
Character Information Interchange)
O variabilă de tip char este duală: poate fi precizată atât prin numărul care reprezintă
codul ASCII fie prin constanta caracter respectivă, adică acel caracter delimitat de
apostrofuri.(!!!)
Exemplu:
char c;
c='A';
cout<<c;//se afişează A
c++;
cout<<c;//se afişează B
cout<<(int)c;//se afişează 66
c=99;
cout<<c;//se afişează c

Obs: Tipul char, d.p.d.v. numeric, memorează de fapt valori cuprinse între -128..127.
Tipul unsigned char, d.p.d.v. numeric memorează valori cuprinse între 0..255
Obs: Există câteva constante caracter speciale, numite "secvenţe escape". Ele sunt cele
care încep cu '\'. Cele mai uzuale sunt:
\n = rând nou
\t = tab
\\ = backslash
\' = apostrof
\" = ghilimele

Iată o schiţă a tabelei ASCII:


Codul Caracterele Obs

42
0 Marca de sfârşit de string
1..31 Coduri neafişabile de exemplu \n = 13
\a = 7 (codul pt. beep)
codul 27 = la citire, tasta ESC
32 Spaţiul: ' '
33..47 !"#$%&'()*+,-./ Semne
48..57 0123456789 Cifrele
58..64 :;<=>?@ Semne
65..90 ABCDEFGHIJKLMNOPQRSTUVWXYZ Literele mari ale alfab. englez
91..96 [\]^_` Semne
97..122 abcdefghijklmnopqrstuvwxyz Literele mici ale alfab. englez
123..127 {|}~⌂ Semne
Obs: Cele mai mari ca 127 fac parte din codul ASCII extins.
Dacă le memorăm pe tipul char, ele se iau de la cele negative (-128, -127, ...)

Codul ASCII extins depinde de setările device-ului care îl utilizează.

Compararea a două caractere se face prin operatorii obişnuiţi (<, <=,...). La comparare se
ia după codul ASCII. Astfel, literele mari sunt mai mici decât literele mici.

Citirea unui caracter:


1) Doar caractere nealbe:
cin>>caracterul;
Dacă la o astfel de citire îi dăm spaţiu, Tab sau Enter, NU va citi, ci stă după noi până
introducem un caracter.

2) Orice caracter (inclusiv \n):


caracterul=cin.get();

Deja putem concepe algoritmi care să citească şiruri de caractere fără a folosi efectiv
structuri de tip string. Condiţia este ca datele să poată fi prelucrate caracter cu caracter, în
sensul că citirea unui nou caracter îl distruge pe cel anterior:
c=cin.get();
while(c!='\n')
{
..prelucrăm c..
c=cin.get();
}
Vezi apl01
Operaţii cu caractere

43
Orice operaţie aritmetică implicând date de tip char are REZULTAT NUMERIC.
Dacă avem nevoie de caracterul care are codul dat de respectivul rezultat, folosim
operatorul (char)rezultat.
!! Dacă unei variabile de tip char îi atribui o valoare numerică, NU mai trebuie folosit
operatorul (char) !!

Ex:
char c='A',d;
cout<<'A'+1;//afişează 66
cout<<(char)('A'+1);//afişează B
d='A'+1;
cout<<d;//afişează B
cout<<(int)d;//afişează 66

!!Atenţie la caracterele cifră, al căror cod ASCII NU coincide cu cifra!!


Dacă dorim să convertim un caracter cifră la cifra corespunzătoare:
caracterul_cifră – '0'
Invers, dacă dorim să convertim o cifră la caracterul cifră corespunzător:
(char)(cifra+'0')

Funcţii cu char-uri (pt. o parte dintre ele treb. inclus #include<cctype>)

toupper(char) – dacă char este o literă mică, întoarce codul ASCII (!număr!) al său,
transformat la litera mare corespunzătoare. În rest, întoarce codul
ASCII al caracterului neschimbat.
Ex:
cout<<toupper('a'); //afişează 65
cout<<(char)toupper('a'); //afişează A
cout<<toupper('2'); //afişează 50
cout<<(char)toupper('2'); //afişează 2
char c;
c=toupper('2');
cout<<c; //afişează 2

44
tolower(char) – analog, de la literă mare la literă mică (dacă e cazul)

isdigit(char) – întoarce 1 dacă char este un caracter cifră, 0 în caz contrar.


isalpha(char) – întoarce 1 dacă char este literă mare, 2 dacă e literă mică, 0 în caz
contrar
isupper(char) – întoarce 1 dacă char este literă mare, 0 în caz contrar
islower(char) – întoarce 2 dacă char este literă mică, 0 în caz contrar

Obs: ultimele 4 funcţii de mai sus NU sunt în programa de bac.


Dacă le utilizaţi, scrieţi lângă ele şi un mic comentariu de genul:
//funcţia ... foloseşte la verificarea caracterului ... dacă este ....
Şiruri de caractere (stringuri)

Un string se declară la fel ca un vector, elementele fiind de tip char.


Ex:
char s[14];
Elementele sunt indexate de la 0. Prima literă din string are indicele 0 and we cannot
change this!!
O astfel de variabilă se poate accesa şi la nivel de char, adică fiecare dintre valorile
s[0], s[1], ... este de tip char însă, are marele avantaj al accesării sale în întregime.
Astfel, dacă un vector de char este accesat prin numele variabilei (în cazul nostrul s) de
fapt accesăm adresa de început.
Toate contextele care permit această accesare se referă la caracterele din string care
încep la adresa specificată şi ţin până la întâlnirea caracterului cu codul ASCII 0 = marca
de final de string.
În orice constantă de tip string, acest caracter (marca 0) se subînţelege la închiderea
ghilimelelor.
Ex:
"Rica" înseamnă de fapt caracterele: 'R' (codul 82), 'i' (codul 105), 'c' (codul 99), 'a' (codul
97), '\0' (codul 0)
Astfel, trebuie să avem în vedere că orice string trebuie dimensionat cu numărul maxim de
caractere care ne dorim să încapă în el PLUS 1.
Pe exemplul de mai sus "Rica" are nevoie de 5 elemente.

45
Citirea unui string:
Fie stringul
char s[100];
1) Putem citi prin:
cin>>s; - citeşte în s până la primul caracter alb (spaţiu, Enter, Tab). Este incapabilă să
citească aceste caractere albe. Lucrurile necitite rămân pe buffer.

2)
cin.get(s,nr_maxim_caractere+1); citeşte either nr_maxim_caractere either până
dă de sfârşitul de linie. În principiu, la nivel bac, trebuie să-i dăm suficient de multe a.î. să
citească până la sfârşitul de linie.

!! În cazul unei citiri cu cin.get(...), dacă înainte de ea a fost făcută orice altă citire
cu cin>> .... trebuie pus un cin.get(); ca să poată trece de sfârşitul de linie.
Ex: sa presupunem că introducem:
7
Ana behaie
int n;
char s[100];
cin>>n;
cin.get(s,100); - ăsta NU se va citi corect, ci s va citi şirul vid. Motivul cin>>n;
după ce l-a citit pe n NU a trecut la rând nou, ci a rămas fix după ultima cifră a lui n.
cin.get(s,100) a continuat din acel loc (care e sfârşitul liniei) până la sfârşitul liniei, deci nu
citeşte nimic.
Corectarea se face printr-un cin.get();

Accesarea stringurilor prin intermediul pointerilor


Dacă avem un string
char s[100];
prin s se înţelege adresa de început a stringului.

Una dintre cele mai de bază funcţii care preiau adresa de început şi prelucrează datele
de-acolo şi până dau de marca de final de string este:
cout<<s;

46
Prin adunarea lui s cu un număr natural x se obţin adrese care sunt cu x poziţii la dreapta
lui s.
Ex:
char s[100]="Phoque";
cout<<s; //Phoque
cout<<s+1; //hoque
cout<<s+2; //oque
cout<<s+3; //que

Pointerii de tip char* permit memorarea unor astfel de adrese de stringuri.


Practic, un astfel de pointer este ca o "căpuşă" care "stă" pe o anumită adresă a stringului
şi prin intermediul căreia poate modifica (schimba) conţinutul acestuia.
Pointerul de tip char * se comportă EXACT ca un string.
Ex:
char s[100]="Phoque",*p;
p=s+2;
cout<<p; //oque
cout<<p[0]; // 'o'
cout<<p[1]; // 'q'
p[1]='x';
cout<<s;//Phoxue

Funcţii cu stringuri
strlen(adresa); - numărul de caractere din string
Ex:
char s[100]="testoasa",*p;
cout<<strlen(s); //afişează 8
cout<<strlen(s+2); //afişează 6
p=s+2;
cout<<strlen(p+1); //afişează 5

strcpy(adr1,adr2); - copiază la adr1 caracterele care încep la adr2. După copiere


pune şi marca de sfârşit de string după caracterele copiate.
Ex:
47
char s[20]="foame",q[20]="zoocamila";
strcpy(s+2,q+3);
cout<<s;// focamila

Iată o formă particulară care, pe sistemele de calcul viitoare NU va mai putea fi utilizată:
strcpy(s+i,s+j);
Şterge secvenţa de caractere care începe cu caracterul s[i] şi se termină cu s[j-1].

Ex:
char s[100]="Cotofana";
strcpy(s+2,s+5);
cout<<s; //afişează "Coana"

Obs: Forma particulară


strcpy(s+i,s+i+1);
şterge DOAR caracterul de la indicele i.
!! NU merge dat acest tip de strcpy pentru inserare, ci DOAR pentru ştergere!!
!! În ultima vreme NU se mai recomandă această funcţie NICI pentru ştergere, deoarece
implementarea sa pe arhitecturi paralele de calcul (cu mai multe procesoare) NU ia,
neapărat, caracterele la rând !!

strncpy(adr1,adr2,n); - copiază la adr1 doar primele n caractere care încep la adr2


însă după copiere NU pune şi marca de sfârşit de string după caracterele copiate(!)

char s[20]="zoocamilele",q[20]="logica";
strcpy(s+3,q,3);
cout<<s;// zoologilele -a se remarca faptul că, după caracterele copiate, "log",
NU a pus marca de final de string, ci restul stringului a rămas

pe cind:
char s[20]="zoocamilele",q[20]="log";
strcpy(s+3,q);
cout<<s;// zoolog -a se remarca faptul că, după caracterele copiate, "log",
a pus marca de final de string

48
strcat(adr1,adr2); - concatenează după caracterele stringului de la adr1
caracterele stringului de la adr2. După lipire pune şi marca de sfârşit de string.
Ex:
char s[20]="iepure",p[20]="rechin";
strcat(s,p+2);
cout<<s;//afişează "iepurechin"

strncat(adr1,adr2,n); - concatenează după caracterele stringului de la adr1


primele n caractere ale stringului de la adr2. După lipire pune şi marca de sfârşit de string.
Ex:
char s[20]="iepure",p[20]="chinuitor";
strncat(s,p,4);
cout<<s;//afişează "iepurechin"
Obs: Există şi forma
cin.getline(string,nr_caractere+1);
care, acţionează la fel ca cin.get(string,nr_caractere+1) pentru citire însă, după efectuarea
citirii, trece automat la rândul următor (chiar dacă ar mai fi rămas caractere necitite pe linia
curentă, le ignoră).

Astfel, dacă avem de citit mai multe propoziţii (Care conţin şi caractere albe) câte una de
pe fiecare linie, e de preferat să citim cu cin.getline(...)
Şiruri de caractere (stringuri)
• funcţia strchr(string,char) caută prima apariţie a lui char în string şi întoarce
ADRESA la care char-ul a fost găsit în string.
Dacă NU găseşte întoarce NULL (!!NULL este o constantă specială şi înseamnă adresă
de memorie nulă - inexistentă)

Exemple:
cout<<strchr("Etienne",'e');//afişează enne
char s[30]="Steven",*p;
p=strchr(s,'v');
cout<<p; //afişează ven

49
cout<<p-s; //afişează 3 (!!knd skdem adrese de memorie, se obţine diferenţa de poziţii
dintre ele!!)
p[0]='f';p[1]='a';
cout<<p<<"\n"; //afişează fan
cout<<s<<"\n"; //afişează Stefan

Obs: funcţia strchr poate fi foarte utilă atunci când dorim să verificăm dacă un caracter
aparţine unei anumite mulţimi de caractere, pentru că:
strchr(string_cu_mulţimea_de_caractere, caracter) are valoarea NULL
dacă NU aparţine, respectiv !=NULL dacă aparţine.
Ex:
if(strchr("AEIOUaeiou",c)!=NULL) => e vocală
if(isalpha(c) && strchr("AEIOUaeiou",c)==NULL) => e consoană

• funcţia strrev(var_string) inversează (oglindeşte) literele din var_string.


Ex:
char s[10]="Rique";
strrev(s);
cout<<s; //euqiR

Formarea unui string caracter cu caracter


- ne luăm o variabilă care va avea la fiecare pas indicele la care urmează să punem un
caracter. Fie ea nc. Iniţial nc=0;
- de fiecare dată când vrem să adăugăm un caracter nou la string, atribuim:
v_string[nc++]=caracterul_nou;
- la final, încheiem stringul:
v_string[nc++]=0;
Vezi apl01
Funcţia strtok
Permite separarea unui string în cuvinte în funcţie de un separator.
!!Operaţia distruge stringul de la care plecăm!!

Modelul aplicării:

50
- în urma aplicării lui strtok, prin intermediul unui pointer sunt întoarse adresele cuvintelor
separate. Aceste adrese reprezintă de fapt locaţii din stringul dat (fiecare strtok va pune
căpuşa la un nou cuvânt)

- apelul iniţial ("starter-ul" splituirii) se face prin:


strtok(s,"separatori")
Această funcţie întoarce adresa primului cuvânt separat. Delimitarea se face până la
primul dintre separatorii din grup care este întâlnit. Noua căutare va începe de după
cuvântul separat, de la primul caracter al lui s care NU face parte din lista de separatori
specificată.

- toate celelalte apeluri se fac prin:


strtok(NULL,"separatori")
Fiecare apel de acest fel va întoarce adresa următorului cuvânt separat. Când nu
mai sunt cuvinte de separat, va întoarce NULL.
Ex:
char s[100]="Dan Gabriel DRAGOMIR",fn[100],mn[100],ln[100];
strcpy(fn,strtok(s," "));
strcpy(mn,strtok(NULL," "));
strcpy(ln,strtok(NULL," "));
cout<<ln<<"\n"<<mn<<"\n"<<fn;
va afişa:
DRAGOMIR
Gabriel
Dan

Dacă dorim separarea "automată", ne folosim de un pointer de tip char.


Scrierile pot fi de una dintre formele următoare:
I.
char *p;
p=strtok(s,"separatori");
while(p)
{
//prelucram p
p=strtok(NULL,"separatori");
51
}

II.
char *p;
for(p=strtok(s,"sep."); p ; p=strtok(NULL,"sep."))
{
//prelucram p
}

Vezi apl02
Conversii
Conversie = transformarea aceluiaşi conţinut între diferite tipuri de date.
• atoi(string); - converteşte stringul la număr int, atâta cât poate, adică cel mai mare
prefix al stringului care POATE fi interpretat ca număr întreg.
(atoi este în cstdlib)
Ex:
atoi("123"); → 123
atoi("123abc"); → 123
atoi("12.3"); → 12

• atof(string); - converteşte stringul la număr real, atâta cât poate, adică cel mai
mare prefix al stringului care POATE fi interpretat ca număr real.
(atof este în cstdlib)
Ex:
atof("123.4"); → 123.4
atof("12.3abc"); → 12.3

• itoa(int,var_string,baza); - converteşte numărul int, transformat în baza


specificată, în variabila string.
(itoa este în cstdlib)
Ex:
itoa(100,s,10); - s primeşte "100"
itoa(106,s,16); - s primeşte "6a"

52
• sprintf(string,"%f",numar); - converteşte numărul real în variabila string.
(itoa este în cstdio)

Ex:
sprintf(s,"%f",3.14); - s primeşte "3.14"

Vezi apl03

Vectori de stringuri
Un vector de stringuri se declară ca o matrice de char-uri, adică:

char vs[nr_stringuri][lmax];

Stringurile sunt indexate de la 0, însă putem lucra cu ele de la 1, la fel ca într-un


vector de int. Lmax reprezintă lungimea maximă admisă pentru oricare dintre stringuri.
Fiecare string va putea fi accesat ca orice string, prin vs[indice] şi se comportă la fel.
Ex: Dacă vrem să facem un vector de stringuri în care să memorăm pe indicii de la 1 la 3
"Stef", "Phoque" şi "Rique", minimul declaraţiei tre' să fie:
char nume[4][7];
strcpy(nume[1],"Stef");
strcpy(nume[2],"Phoque");
strcpy(nume[3],"Rique");
cout<<strlen(nume[2]); //afiş. 6
cout<<nume[2][4]; //afiş. u
cout<<nume[2]+3; //afiş. "que"

Vezi apl04

Pointeri către stringuri (căpuşe:D)

Dacă pointerul p are o adresă a unui string, am văzut deja că p[0] reprezintă primul
caracter al acestui string. Acest p[0] este echivalent cu *p.

53
De fapt, în C++, valabil nu doar pentru pointeri către char, ci pentru orice tip de pointeri
(chiar şi vectori obişnuiţi)
x[i] = *(x+i) = *(i+x) = i[x]
Compararea lexicografică a două stringuri
Se face ca în dicţionar, însă, din păcate, spre deosebire de mai toate celelalte limbaje de
programare, în C++ NU putem compara două stringuri cu <, <=, >, >=, == sau !=

Pentru compararea a două stringuri în C++ e obligatoriu să utilizăm funcţia


strcmp(s1,s2)
Aceasta întoarce:
-1 dacă lexicografic, s1 este înainte de s2
0 dacă lexicografic, s1 este identic cu s2
1 dacă lexicografic, s1 este după s2
Ex:
strcmp("mama","bunica") → 1
strcmp("alb","albastru") → -1
strcmp("albastru","albi") → -1
strcmp("cici","cici") → 0

Subprograme în C++

Un subprogram este modul de program (de cod) parametrizabil, în urma căruia se poate
întoarce o valoare, respectiv subprogramul poate fi capabil de a schimba valorile
parametrilor săi.

Una dintre modalităţile de definire a subprogramelor constă în scrierea lor înainte de main,
punând mai întâi antetul şi apoi conţinutul (codul).

În funcţie de faptul că subprogramul NU întoarce sau întoarce o valoare acesta poate fi de


tip void sau de unul dintre tipurile simple de date (int, char, double şi variaţiuni ale
acestora) sau poate fi şi de tip struct, însă NU poate fi tablou!!.

Iată sintaxa antetului:

54
tip_rez_întors nume_subprogram(tip1 param1, tip2 param2 ...)
{ //de aici urmeaza corpul
codul subprogramului
}
Parametrii din această linie de definiţie se numesc parametrii formali.
Parametrii din linia de apel (cea care cheamă funcţia să se execute) se numesc
parametrii actuali sau parametrii efectivi.

La rândul lor, parametrii formali sunt de 2 feluri:


- cei a căror valoare NU se modifică la ieşirea din subprogram, chiar dacă îi modificăm în
cadrul subprogramului. Aceşti parametrii se numesc "transmişi prin valoare". Parametrii
prin valoare sunt toate variabilele simple (NE-vectori) care NU au & în faţă.
- cei a căror valoare se modifică şi în programul care a apelat funcţia, imediat ce îi
modificăm în cadrul subprogramului. Aceşti parametrii se numesc "transmişi prin referinţă".
Parametrii prin referinţă sunt orice vectori şi orice variabilă simplă în faţa căreia apare un
&. (!! În faţa vectorilor NU putem pune & - pentru că primim eroare !!)
- rezultatul întors de funcţie se stabileşte prin comanda
return valoare;
care printre altele iese IMEDIAT din funcţie, întrerupând orice repetitivă în curs de
executare.
Acest mecanism ne permite relaxarea codului care în mod normal ar fi conţinut flag-uri,
deoarece se poate merge pe tehnica:
- imediat ce o anumită dată NU convine, întoarcem 0.
- dacă firul execuţiei funcţiei supravieţuieşte până la final, la acest final putem lăsa un
return 1;

Ex: Una dintre funcţiile foarte utile este cea care verifică dacă un număr este sau nu prim,
întorcând 0.
Iată codul său:
int is_prime(int x)
{
if(x<=1 || x>2 && x%2==0) return 0;
for(int i=3;i*i<=x;i+=2)
if(x%i==0) return 0;
55
return 1;
}

Generări de tip backtracking

Ex: Să generăm toate cuvintele formate din 4 vocale distincte, în care vocalele apar în
ordine alfabetică (Vocalele fiind A E I O U)
Soluţiile:
AEIO
AEIU
AEOU
AIOU
EIOU

1) Generare de permutări
Permutări de n: toate posibilităţile de a pune într-un vector cu n elemente numerele de la
1 la n fără a le repeta (vectorul, d.p.d.v. matematic, reprezintă o "mulţime ordonată". Prin
"mulţime ordonată" se înţelege o mulţime de elemente în care contează ordinea. Mulţimile
clasice (cele din clasele 1-8) sunt neordonate, adică nu contează ordinea scrierii. Ele se
scriu între acolade, pe când mulţimile ordonate se scriu între paranteze rotunde.)
Ex:
Iată permutările de 4:
(1, 2, 3, 4) (2, 1, 3, 4) (3, 1, 2, 4) (4, 1, 2, 3)
(1, 2, 4, 3) (2, 1, 4, 3) (3, 1, 4, 2) (4, 1, 3, 2)
(1, 3, 2, 4) (2, 3, 1, 4) (3, 2, 1, 4) (4, 2, 1, 3)
(1, 3, 4, 2) (2, 3, 4, 1) (3, 2, 4, 1) (4, 2, 3, 1)
(1, 4, 2, 3) (2, 4, 1, 3) (3, 4, 1, 2) (4, 3, 1, 2)
(1, 4, 3, 2) (2, 4, 3, 1) (3, 4, 2, 1) (4, 3, 2, 1)
Tipic pentru permutări
- elementele NU se repetă
- o permutare cu n elemente foloseşte TOATE cele n elemente ale unei mulţimi date.
Numărul lor Pn = n!
2) Generare de aranjamente ale unei mulţimi cu n elemente, luate câte m

56
Aranjamente de n luate câte m : toate posibilităţile de a pune într-un vector cu DOAR m
elemente numerele de la 1 la n (unde m≤n) fără a repeta valori în acelaşi vector. Din nou
avem de-a face cu mulţimi ordonate.

Ex: Iată aranjamentele de 5 luate câte 3:


(1, 2, 3) (2, 1, 3) ...... (5, 1, 2)
(1, 2, 4) (2, 1, 4) (5, 1, 3)
(1, 2, 5) (2, 1, 5) (5, 1, 4)
(1, 3, 2) (2, 3, 1) (5, 2, 1)
(1, 3, 4) (2, 3, 4) (5, 2, 3)
(1, 3, 5) (2, 3, 5) (5, 2, 4)
(1, 4, 2) (2, 4, 1) (5, 3, 1)
(1, 4, 3) (2, 4, 3) (5, 3, 2)
(1, 4, 5) (2, 4, 5) (5, 3, 4)
(1, 5, 2) (2, 5, 1) (5, 4, 1)
(1, 5, 3) (2, 5, 3) (5, 4, 2)
(1, 5, 4) (2, 5, 4) (5, 4, 3)
𝑛!
Numărul lor: 𝐴𝑚
𝑛 = (𝑛−𝑚)!

Tipic pentru aranjamente


- elementele NU se repetă
- o soluţie particulară dintre aranjamente foloseşte DOAR m elemente dintre cele n date
- ordinea scrierii e importantă: Dacă schimbăm ordinea a două elemente, se obţine o cu
totul altă soluţie.
3) Generare de combinări ale unei mulţimi cu n elemente, luate câte m
Combinări de n luate câte m : toate posibilităţile de a pune într-o mulţime neordonată cu
DOAR m elemente numerele de la 1 la n (unde m≤n) fără a repeta valori. De data asta
avem de-a face cu mulţimi neordonate (obişnuite). De regulă, pentru a NU repeta valori,
se convine ca elementele unei anumite soluţii particulare să fie scrise în ordine
crescătoare (sau mai rar, descrescătoare)
Ex: Iată combinările de 6 luate câte 4:
{1, 2, 3, 4} {2, 3, 4, 5} {3, 4, 5, 6}
{1, 2, 3, 5} {2, 3, 4, 6}
{1, 2, 3, 6} {2, 3, 5, 6}
{1, 2, 4, 5} {2, 4, 5, 6}
{1, 2, 4, 6}
57
{1, 2, 5, 6}
{1, 3, 4, 5}
{1, 3, 4, 6}
{1, 3, 5, 6}
{1, 4, 5, 6}
𝑛!
Numărul lor: 𝐶𝑛𝑚 = 𝑚!(𝑛−𝑚)!

Tipic pentru combinări


- elementele NU se repetă
- o soluţie particulară dintre combinări foloseşte DOAR m elemente dintre cele n date
- ordinea scrierii NU e importantă: dacă schimbăm ordinea a două elemente, se obţine de
fapt aceeaşi soluţie. Din acest motiv, elementele unor combinări apar de regulă în ordine
fie crescătoare, fie descrescătoare.

4) Generare elemente ale unui produs cartezian


Produs cartezian dintre nişte mulţimi M1, M2, M3, ... Mn este totalitatea mulţimilor ordonate
de elemente x1, x2, ..., xn în care x1 ∈ M1, x2 ∈ M2, xn ∈ Mn fără absolut nicio restricţie.
Mai mult, mulţimile M1, M2, ... se pot repeta, pot fi chiar una şi aceeaşi mulţime.
Ex:
Dacă
M1={q,w}
M2={∆, Ω, Ψ}
M3={•, ⊥}
Elementele produsului cartezian M1xM2xM3 sunt:
(q, ∆, •) (q, ∆, ⊥) (q, Ω, •) (q, Ω, ⊥) (q, Ψ, •) (q, Ψ, ⊥)
(w, ∆, •) (w, ∆, ⊥) (w, Ω, •) (w, Ω, ⊥) (w, Ψ, •) (w, Ψ, ⊥)

Numărul total de elemente din produsul cartezian este date de produsul numărului de
elemente din toate mulţimile. La noi 2*3*2=12.

Tipic pentru produs cartezian


- elementele se pot repeta (nu au nicio restricţie) evident, în cazul în care mulţimile pe care
se face produsul cartezian sunt aceleaşi.

58
Exemplu: Generarea tuturor codurilor PIN de 4 cifre reprezintă un produs cartezian al
mulţimii M={0,1,2,3,4,5,6,7,8,9} cu ea însăşi de 4 ori.

Exemplu de exerciţiu de simulare a unei generări:


Să se genereze în ordine lexicografică toate numerele cu cifre distincte, astfel încât suma
cifrelor să fie egală cu 3.
102
12
120
201
21
210
3
30

Exerciţiu
Să generăm submulţimile nevide ale mulţimii {1,2,3,4} în ordine lexicografică
1
12
123
1234
124
13
134
14
2
23
234
24
3
34
4

59
(Ne reamintim că o mulţime cu n elemente are un număr total de 2n submulţimi,
incluzând-o şi pe cea vidă)
Graf Eulerian

Def: Un ciclu Eulerian este un ciclu care trece prin TOATE muchiile grafului.

Un graf care admite un ciclu Eulerian se numeşte graf Eulerian.

Exemplu:

1
3 5
2 7

4 6 8

este un graf Eulerian.


Iată ciclul Eulerian din el:
(1,2,4,6,8,7,6,2,7,1)

Există o teoremă de caracterizare a acestor grafuri: Un graf este Eulerian dacă şi numai
dacă, abstracţie făcând de nodurile izolate, este conex iar gradul fiecărui nod al său este
un număr par.

Verificarea dacă un graf este Eulerian (algoritmul):


- facem un vector cu gradele tuturor nodurilor. Dacă prindem vreun grad impar ⇒ NU este
- dacă toate gradele sunt pare – verificăm dacă este conex abstracţie făcând de nodurile
izolate. Pentru asta, facem DFS plecând din primul nod cu grad nenul pe care îl găsim.
În urma DFS-ului dacă există noduri nevizitate cu grad nenul ⇒ NU este Eulerian
În orice alt caz este Eulerian.

Grafuri neorientate

Un graf neorientat este format dintr-o mulţime de noduri legate între ele prin muchii.

Ex:

60
7
1
2
9

3 8

5
1
6
4
El se mai notează G=(X,U) unde X = mulţimea nodurilor (la noi X={1, 2, 3, 4, 5, 6, 7, 8, 9,
10})
şi U = mulţimea muchiilor. În cazul grafurilor neorientate, U = mulţime de perechi
neordonate, adică muchia se notează cu [x,y] şi dacă există muchia [x,y] e ca şi cum
există şi muchia [y,x].

În cazul grafului nostru, U = { [1,4],[1,5], [3,4], [3,5],[3,6],[4,5],[4,6],[7,8],[7,9],[8,9], [9,10] }

Notăm de regulă
n = | X | = numărul de noduri
m = | U | = numărul de muchii

Definiţii
noduri adiacente = legate printr-o muchie
muchie incidentă într-un nod = muchia are nodul respectiv pe post de unul dintre capete
gradul unui nod d(x) = degree of x = numărul de muchii incidente în acel nod.

Dacă gradul unui nod este 0 - se numeşte nod izolat (EMO :D)
Dacă gradul este 1 - se numeşte nod terminal.

Teoremă: Suma gradelor tuturor nodurilor unui graf neorientat este egală cu dublul
numărului de muchii: ∑ d ( x ) = 2m
x∈ X
Dacă avem muchiile unui graf, putem scrie direct care este şirul gradelor, fără a mai
desena graful :
[1,4],[1,5],[3,5], [3,4], [3,6],[4,5],[4,6],[7,8],[7,9],[8,9], [9,10]

nodul x 1 2 3 4 5 6 7 8 9 10
gradul d(x) ** *** **** *** ** ** ** *** *
→ nodul 2 este EMO (izolat) şi nodul 10 este terminal.

Graf complet : un graf în care oricare două noduri sunt adiacente.


n ⋅ (n − 1)
Teoremă: un graf complet cu n noduri are m = muchii.
2

Graf parţial : se obţine dintr-un graf păstrând TOATE nodurile şi DOAR o submulţime a
muchiilor.

61
Teoremă : O mulţime cu n elemente are 2n submulţimi (incluzând-o şi p-aia vidă).

Corolar din ultimele două : Un graf cu m muchii are 2m grafuri parţiale.

Subgraf : se obţine dintr-un graf păstrând o submulţime de noduri şi DOAR acele muchii
care leagă nodurile păstrate şi existau şi înainte în graf.

Corolar : Un graf cu n noduri admite 2n subgrafuri. (din mulţimea celor n noduri păstrăm o
submulţime)

Lanţ : Un şir de noduri legate prin muchii (vecinii în lanţ)


Ex: pe graful din poza, lanţ: 4, 5, 3, 4, 6, 3
Lungimea unui lanţ : numărul de muchii ale sale ( = nr. de noduri minus 1)

Lanţ elementar : nu se repetă niciun nod.


Lanţ neelementar : se repetă vreun nod.

Graf conex: Între oricare două noduri există un lanţ.


Evident, graful nostru din poză NU este conex.

Componentă conexă este un subgraf al unui graf dat, care este conex şi este maximal cu
această proprietate (orice alt nod am fi păstrat în subgraf, el NU mai rămânea conex)

Pe graful nostru avem 3 componente conexe: {1, 3, 4, 5, 6} {2} {7, 8, 9, 10}

Ciclu : Un lanţ în care NU SE REPETĂ MUCHII şi în care nodul iniţial coincide cu cel final.
Dacă se repetă noduri → ciclul este neelementar. În caz contrar este elementar.
Exemplu de ciclu elementar în graful nostru : 1, 5, 3, 6, 4, 1 - de lungime 5
Exemplu de ciclu neelementar : 1, 4, 3, 6, 4, 5, 1 - lungime 6

Ciclu hamiltonian - un ciclu elementar care trece prin toate nodurile grafului. În cazul în
care există, şi graful s.n. hamiltonian

Ciclu eulerian - un ciclu care trece prin toate muchiile grafului (nu neapărat elementar). În
cazul în care există, şi graful s.n. eulerian.
Există o teoremă puternică de caracterizare a unui graf eulerian:
Un graf este eulerian dacă şi numai dacă este conex (abstracţie făcând de nodurile
izolate) şi gradul fiecărui nod al său este par.

Arbore d.p.d.v. al teoriei grafurilor (se mai numesc şi "arbori fără rădăcină") = un graf
conex fără cicluri.

Teoremă: un arbore cu n noduri are exact n-1 muchii.


Orice muchie i-am scoate, NU mai e conex
Orice muchie i-am adăuga, se formează un ciclu.
Grafuri neorientate

Un graf neorientat : o structura de date formata din noduri (mulţimea lor se notează cu X, de regulă
se notează n = | X | = nr. de noduri) şi din muchii.
O muchie este o pereche (x,y) de noduri legate între ele.
62
Graful fiind neorientat, orice pereche de noduri se consideră legată în ambele sensuri: dacă se poate
merge de la x la y se poate şi invers.

Mulţimea muchiilor se notează U, iar | U | = m = numărul de muchii.

Noţiuni:
Noduri adiacente (vecine): sunt legate printr-o muchie.
Muchia se numeşte incidentă în ambele sale capete.
Gradul unui nod x = se notează d(x) = numărul de muchii incidente în nodul x (care au legătură cu
x)

Un nod de grad 1 se numeşte nod terminal.


Un nod de grad 0 se numeşte nod izolat.

Se numeşte lanţ o succesiune (un şir) de noduri pentru care, între orice două noduri scrise unul după
altul în lanţ, există muchie între ele.
Lungimea unui lanţ este dată de numărul de muchii ale sale (deci numărul de noduri minus 1)
Un lanţ se numeşte simplu dacă nu se repetă nicio muchie în el.
Un lanţ se numeşte elementar daca nu se repetă niciun nod în el.
Un lanţ se numeşte neelementar daca se repetă vreun nod în el.

Se numeşte ciclu un lanţ în care:


- nu are voie să se repete nicio muchie (!!într-un ciclu NU e permisă repetarea vreunei muchii!!)
- primul nod coincide cu ultimul.

Lungimea unui ciclu este dată de numărul său de muchii.

Un ciclu se numeşte elementar daca nu se repetă niciun nod în el.


Un ciclu se numeşte neelementar daca se repetă vreun nod în el.

Teoremă : Într-un graf neorientat suma gradelor tuturor nodurilor este egală cu dublul nr. de muchii
(explicaţie: Orice muchie produce adunarea în total cu 2 la gradele deja existente)

63
Exemple:
X = {1,2,3,4,5,6,7,8,9} n=9
U = {[1,2],[2,4],[2,5],[4,5],[5,9],[5,8],[8,9],[3,7]} m=8

Noduri terminale:1, 3 şi 7 Noduri izolate: 6


Lanţ elementar: 2,4,5,9 - lungime 3
Lanţ simplu dar neelementar: 1,2,4,5,9,8,5,2 - lungime 7
Lanţ neelementar: 1,2,4,2,4,2,4,2,5,9,8,5,2,5,4

Ciclu elementar: 2,4,5,2 <=> 4,5,2,4 <=> 2,5,4,2 - lungime 3


Ciclu neelementar: 2,4,5,9,8,5,2 - lungime 6

Şirul gradelor nodurilor:


1, 3, 1, 2, 4, 0, 1, 2, 2

Verif. teoremă : nr. de muchii = 8, deci dublul este 16


suma din şirul de mai sus = 16
Grafuri orientate

Un graf orientat este format dintr-o mulţime de vârfuri legate între ele prin arce.

Ex:

7
1
2
9

3 8

5
1
6
4

64
El se mai notează G=(X,U) unde X = mulţimea vârfurilor (la noi X={1, 2, 3, 4, 5, 6, 7, 8, 9,
10})
şi U = mulţimea arcelor. În cazul grafurilor orientate, U = mulţime de perechi ordonate,
adică arcele se notează cu (x,y) iar arcele (x,y) şi (y,x) sunt independente.

În cazul grafului nostru, U = { (1,5), (4,1), (4,3), (5,3), (5,4), (6,3), (6,4), (7,8), (8,9), (9,7),
(9,10), (10,9) }

Notăm de regulă
n = | X | = numărul de vârfuri
m = | U | = numărul de arce

Definiţii
vârfuri adiacente = legate printr-un arc (indiferent de sens)
muchie incidentă într-un vârf = arcul are vârful respectiv pe post de unul dintre capete
Capetele unui arc capătă şi ele denumiri diferenţiate :
extremitate iniţială = vârful din care pleacă arcul
extremitate finală = vârful în care soseşte arcul
gradul unui vârf d(x) = degree of x = numărul de arce incidente în acel vârf.
Şi acesta este detaliat în
d+(x) = gradul exterior al vârfului x = numărul de arce care pleacă din el
d-(x) = gradul interior al vârfului x = numărul de arce care sosesc în el

Dacă gradul unui nod este 0 - se numeşte nod izolat (EMO :D)
Dacă gradul este 1 - se numeşte nod terminal.

Teoremă: Suma gradelor exterioare ale tuturor vârfurilor unui graf orientat este egală cu
suma gradelor interioare, fiind egale ambele cu
numărul de arce: ∑ d (x ) = ∑ d (x ) = m
x∈ X
+

x∈ X

Dacă avem arcele unui graf, putem scrie direct care este şirul gradelor, fără a mai desena
graful :
(1,5), (4,1), (4,3), (5,3), (5,4), (6,3), (6,4), (7,8), (8,9), (9,7), (9,10), (10,9)
nodul x 1 2 3 4 5 6 7 8 9 10
gradul d+(x) * ** ** ** * * ** *
-
gradul d (x) * *** ** * * * ** *
→ gradul 2 este EMO (izolat)
Graf complet : un graf în care oricare două vârfuri sunt adiacente.
Teoremă: un graf orientat complet cu n vârfuri are m = n ⋅ (n − 1) arce.

Graf parţial : se obţine dintr-un graf păstrând TOATE vârfurile şi DOAR o submulţime a
arcelor.

Teoremă : O mulţime cu n elemente are 2n submulţimi.

Corolar din ultimele două : Un graf orientat cu m arce are 2m grafuri parţiale.

Subgraf : se obţine dintr-un graf păstrând o submulţime de vârfuri şi DOAR acele arce
care leagă vârfurile păstrate şi existau şi înainte în graf.

65
Corolar : Un graf cu n vârfuri admite 2n subgrafuri. (din mulţimea celor n vârfuri păstrăm o
submulţime)

Lanţ : Un şir de vârfuri legate prin arce (vecinii în lanţ). NU se ţine cont de orientarea
arcelor!
Ex: pe graful din poza, lanţ: 4, 5, 3, 4, 6, 3
Lungimea unui lanţ : numărul de arce ale sale ( = nr. de noduri minus 1)
Drum : Este un lanţ în care se ţine cont şi de orientarea arcelor.
Ex: pe graful din poză, lanţul de mai sus NU e şi drum
Un drum ar fi : 6, 4, 1, 5, 4, 3
Noţiunile de elementar / neelementar se păstrează (înseamnă să NU repetăm vârfuri)

Graf conex: Între oricare două noduri există un lanţ.


Evident, graful nostru din poză NU este conex.

Graf tare conex : Între oricare două noduri există un drum.


Graful nostru din poză nefiind nici măcar conex NU e cu atât mai puţin tare conex.

Componentă conexă la fel


Componentă tare conexă → se deduce uşor ce vrea să însemne (subgraful maximal ce
e tare conex)

Fie următorul graf:

7
1 6
2
9

3 8

5
1
4

Componentele sale tare conexe ar fi :


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

Noţiunea de Ciclu : se păstrează, nu se ţine cont de orientarea arcelor.


Dacă se repetă vârfuri → ciclul este neelementar. În caz contrar este elementar.
Ne aducem aminte că într-un ciclu prin definiţie NU au voie să se repete arce.

Dacă ţinem cont de orientarea arcelor noţiunea de ciclu devine circuit. (pe acelaşi model
pe care lanţul devine drum)

Circuit hamiltonian - un circuit elementar care trece prin toate vârfurile grafului.
E mai rar să povestim de circuit hamiltonian.

66
Circuit eulerian - un circuit care trece prin toate arcele grafului (nu neapărat elementar).
În cazul în care există, şi graful orientat s.n. eulerian.
Există o teoremă puternică de caracterizare a unui graf orientat eulerian:
Un graf orientat este eulerian dacă şi numai dacă este conex (abstracţie făcând de
vârfurile izolate) şi gradul interior al FIECĂRUI vârf este egal cu gradul exterior al
fiecărui vârf.

67

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