Documente Academic
Documente Profesional
Documente Cultură
Constantele în C++:
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
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)
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)
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++
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.
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.
Ex:
10%3 → 1
11%3 → 2
12%3 → 0
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
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.
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
Limbajul pseudocod
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
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.
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
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ă.
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
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 programelor de nivel bac, rolul nostru este să personalizăm acest program.
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)
Suma gradelor nodurilor unui graf neorientat este egala cu dublul numărului de muchii:
11
� 𝑑(𝑥) = 2𝑚
𝑥∈𝑋
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;
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.
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)
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
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
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
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
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.
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.
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.
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.
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)
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;
}
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: 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
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];
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;
}
27
Căutarea unei valori într-un vector
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.
Am aplicat deja acest procedeu atunci când am citit din fişier până la terminarea acestuia.
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).
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
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--;
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.
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);
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;
}
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
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
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.
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
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
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]
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):
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.
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
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, ...)
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.
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
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)
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();
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
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
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"
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"
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ă
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)
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
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];
Vezi apl04
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 !=
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).
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.
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;
}
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.
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.
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.
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.
Exemplu:
1
3 5
2 7
4 6 8
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.
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].
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 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ă).
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)
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)
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.
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.
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)
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.
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
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.
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)
7
1 6
2
9
3 8
5
1
4
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