Documente Academic
Documente Profesional
Documente Cultură
Continuă
Fie următoarea problemă:
Se dă un număr natural N mai mare decat 0. Să se afișeze câte cifre are. Exemplu: 4321 Se va
afișa 4
Soluția 1
O primă metodă ar fi să verificăm dacă numărul este mai mic decât 10, în acest caz numărul
având o cifră. În caz contrar vom verifica dacă numărul este mai mare sau egal cu 10 și mai mic
decât 100, în acest caz numărul având două cifre. Urmând această metodă, putem să tratăm
fiecare caz până când ajungem la lungimea maxim posibilă a numărului dat.
#include <iostream>
using namespace std;
int main() {
int N;
cin>>N;
if(N < 10) {
cout<<"Numarul dat are o cifra";
}
if(10 <= N && N < 100) {
cout<<"Numarul dat are doua cifre";
}
if(100 <= N && N < 1000) {
cout<<"Numarul dat are trei cifre";
}
// putem continua sa adaugam if-uri pana cand acoperim toate posibilitatile
return 0;
}
Putem să ne folosim de else if și să scurtăm puțin lungimea codului sursă.
#include <iostream>
using namespace std;
int main() {
int N;
cin>>N;
if(N < 10)
cout<<"Numarul dat are o cifra";
else if(N < 100) // intrucat N nu a fost mai mic decat 10 putem
cout<<"Numarul dat are doua cifre";
else if(N < 1000)
cout<<"Numarul dat are trei cifre";
// putem continua sa adaugam if-uri pana cand acoperim toate posibilitatile
return 0;
}
Soluția 2
Pentru a scurta soluția putem să numărăm de câte ori putem împărți numărul la 10. Întrucât în
C++ împărțirea numerelor întregi se face folosind aproximarea prin lipsă, împărțind un număr la
10 vom obține numărul fără ultima sa cifră. De exemplu cout<<5687/10;va afișa 568.
#include <iostream>
using namespace std;
int main() {
int N;
cin>>N;
int numar_cifre = 0;
while (N > 0) {
++numar_cifre;
N = N/10; // eliminam ultima cifra
}
cout<<numar_cifre;
return 0;
}
Exemplu
Să executăm codul de mai sus pentru numărul 4321. La prima intrare în while, N se împarte la
10 și devine 432, iar variabila numar_cifre e incrementată și devine 1. Uite ce se întâmplă după
fiecare rulare a instrucțiunilor din while:
Cerinţă
Se dau două numere naturale nenule a şi b. Aflaţi numărul rez care se obţine prin concatenarea
celor două numere.
Idee
Pentru a adăuga un număr de o singură cifră la un număr x trebuie doar să înmulţeşti x cu 10 şi
apoi să aduni numărul dorit. De exemplu dacă dorești să-l adaugi pe 3 la finalul lui 12, îl vei
înmulți pe 12 cu 10 obținând 120 la care vei aduna 3.
La fel dacă dorești să îl concatenezi pe 53 cu 12, îl vei înmulți pe 53 cu 100 obținând 5300.
Acum dacă îl aduni pe 12 la 5300 vei obține 5312 care este rezultatul dorit.
Gândeşte-te cum poţi adăuga un număr format din trei cifre, apoi din cinci cifre și apoi gândește-
te cum poți rezolva problema în general.
Soluţie
Să presupunem că numărul a este de forma a1a2...an, iar b este de forma b1b2...bm. Înmulţind
numărul a cu 10m vom obţine a1a2...an000...00. Practic numărul obținut va avea m zerouri la final.
Putem observa că dacă adunăm la acest număr numărul b vom obţine exact rez. Ideea pe care se
bazează această soluţie este să adăugăm zerouri la finalul lui a de atâtea ori câte cifre avem în b.
Astfel prima dată trebuie să aflăm numărul de cifre al lui b, iar apoi să adăugăm la finalul
lui a câte un 0 pentru fiecare cifră a lui b.
Atenție!
Pentru a nu pierde valoarea lui b atunci când îi aflăm numărul de cifre, este nevoie să copiem
valoarea lui b într-o nouă variabilă.
Cod
#include <iostream>
using namespace std;
int main() {
int a, b;
cin>>a>>b;
// Ii facem o copie lui b pentru a nu pierde valoarea sa
// cand ii numaram cifrele
int cb = b;
int cifre_b = 0;
while(b > 0) {
++cifre_b;
b /= 10;
}
int i = 1;
while(i <= cifre_b) {
a *= 10;
++i;
}
int rez = a + cb;
cout<<rez;
return 0;
}
Îmbunătățire
Putem să scurtăm codul sursă puțin dacă în loc să numărăm cifrele lui b, adăugăm câte un 0 la
finalul lui a pentru fiecare cifră a lui b.
#include <iostream>
using namespace std;
int main() {
int a, b;
cin>>a>>b;
// Ii facem o copie lui b pentru a nu pierde valoarea sa
// cand ii numaram cifrele
int cb = b;
while(b > 0) {
a *= 10; // Adaugam un 0 pentru fiecare cifra a lui b
b /= 10;
}
int rez = a + cb;
cout<<rez;
return 0;
}
Se dă un număr pozitiv X. Să se afișeze inversul (sau oglinditul) acestui număr.
Inversul unui număr se obține luând cifrele numărului de la dreapta la stânga. De exemplu,
pentru numărul 1234 se va afișa 4321.
Date de intrare
Date de ieșire
Restricții
Exemplu
7612 2167
Un număr N este prim dacă singurii săi divizori sunt 1 și N. Ni se dă un număr și vrem să
verificăm dacă acesta este prim.
Idee
Pentru a face acest lucru vom parcurge toate numerele între 2 și N-1 și vom verifica dacă există
cel puțin un număr dintre ele care îl divide pe N.
Soluție
#include <iostream>
using namespace std;
int main() {
int N;
cin>>N;
int i = 2, este_prim = 1;
while (i < N) { // Parcurgem numerele de la 2 la N-1
if (N % i == 0) { // Daca N se divide la i
este_prim = 0; // Atunci N nu este prim
}
++i;
}
// Numarul 1 nu e prim prin conventie, desi nu are
// niciun divizor intre 1 si el insusi
if (N == 1) {
este_prim = 0;
}
if (este_prim == 1) {
cout<<"Numarul dat este prim";
} else {
cout<<"Numarul dat nu este prim";
}
return 0;
}
Pentru a ține minte dacă am găsit cel puțin un număr care să-l dividă pe N, folosim
variabila este_prim. Ea va fi inițializată la început cu 1. De fiecare dată când găsim un divizor
vom inițializa variabila este_prim cu 0. În cazul în care nu găsim nici un divizor, ea va
rămâne 1.
Această idee seamănă cu folosirea unui întrerupător. Inițial întrerupătorul este îndreptat în sus.
Atunci când găsim un divizor, poziția întrerupătorului va fi schimbată, acesta rămânând îndreptat
în jos.
Atunci când avem de scris un program mai complex, acesta poate fi împărțit în mai multe părți
din punct de vedere logic. De exemplu dacă ar trebui să implementez un program pentru un robot
care dă goluri, programul ar trebui să facă mai multe lucruri:
1. Ia mingea de la adversar
2. Depășește o parte din jucătorii din fața ta
3. Șutează pe poartă fără să lovești portarul
4. Dacă nu ai dat gol, revino la pasul 1
Fiecare pas din acest program presupune mai mulți pași intermediari. La fel și când rezolvăm o
problemă mai complexă avem nevoie să rezolvăm probleme mai simple și pe urmă să combinăm
aceste rezolvări pentru a rezolva problema inițială. Practic ca să rezolvăm problema marcării
unui gol, trebuie să rezolvăm prima dată cele 3 probleme mai "mici".
În realitate noi primim doar problema complexă și trebuie să ne dăm singuri seama care sunt
problemele intermediare pe care trebuie să le rezolvăm pentru a reuși să facem problema grea.
Exemplu
Se dau două numere a și b. Să se afișeze numărul care are suma cifrelor mai mare. Dacă cele 2
numere au aceeași sumă a cifrelor, se poate afișa oricare dintre ele. Exemplu: pentru a = 1112
și b = 99 se va afișa 99
Primul lucru pe care trebuie să-l facem pentru a rezolva problema este să ne dăm seama care sunt
problemele mai "mici" pe care trebuie să le rezolvăm pentru a rezolva problema inițială. Înainte
să citești mai departe, gândește-te cum ai împărți tu pe pași problema.
Etape
#include <iostream>
using namespace std;
int main() {
int a, b;
cin>>a>>b;
// deoarece atunci cand aflam numarul de cifre ale lui a si b, vom pierde
// valorile acestora, trebuie sa le facem inainte o copie pentru a sti
// valoarea lor initiala
int ca = a, cb = b;
int sumCifA = 0, sumCifB = 0;
// Etapa 1
while (a > 0) {
sumCifA += a % 10; // a += b; inseamna a = a + b;
a /= 10; // a /= b; inseamna a = a / b;
}
// Etapa 2
while (b > 0) {
sumCifB += b % 10;
b /= 10;
}
// Etapa 3
if (sumCifA > sumCifB) {
cout << ca;
} else {
cout << cb;
}
return 0;
}
Continuă
Dându-se un număr natural a, să se verifice dacă a și inversul (oglinditul) lui a sunt ambele
numere prime.
Date de intrare
Date de ieșire
Să se afișeze DA dacă numărul a și inversul său sunt ambele prime sau NU, în caz contrar.
Restricții și precizări
1 ≤ a ≤ 300000
a nu are ultima cifră 0
Exemplu
Cerinţă
Fiind date a,b şi c, să se verifice dacă numărul x este ABC. Dacă condiţia este respectată afişaţi
"DA", altfel afişaţi "NU".
Date de intrare
Date de ieşire
Se va afişa pe ecran doar "DA"(dacă numărul x este ABC) sau "NU"(numărul x nu este ABC).
Restricţii
1 < a < 9
0 < b, c < 10
Exemplu
3 1 2 102 DA
4 2 3 1234 NU
Așa cum putem folosi un if într-un alt if, la fel putem folosi un while într-un while,
un if într-un while, etc.
while (...) {
while (...) {
if (...) {
...
}
}
}
Acest lucru ne ajută să scriem programe mai complexe.
Pentru a înțelege cât mai bine această lecție, construiește tabelele de valori pentru cele două
exemple la fel ca și în lecția despre debug.
Exemplu 1
Pentru a înțelege cât mai ușor cum funcționează înlănțuirea, execută programul de mai jos în
CodeBlocks:
#include <iostream>
using namespace std;
int main() {
int n;
cin>>n;
int i = 1, j;
while (i <= n) {
cout << "i ia valoarea: " << i << "\n";
j = 1;
while (j <= n) {
cout << "j ia valoarea: " << j << "\n";
++j;
}
++i;
}
return 0;
}
Înainte să treci mai departe, asigură-te că înțelegi exact de ce se afișează numerele pe care le vezi
pe ecran. În cazul în care nu îți dai seama, gândește-te că poți să privești separat o instrucțiune
din interiorul altei instrucțiuni. Practic primul while așteaptă după cel de-al doilea.
Exemplu 2
Să se afișeze toate numerele mai mici sau egale cu un n dat care au suma cifrelor pară.
#include <iostream>
using namespace std;
int main() {
int n;
cin>>n;
int i = 1, copie, sum_cif;
while (i <= n) {
// ii facem o copie lui i pentru a nu pierde valoarea lui cand ii
// calculam suma cifrelor
copie = i;
sum_cif = 0;
// calculam suma cifrelor
while (copie > 0) {
sum_cif += copie % 10;
copie /= 10;
}
if (sum_cif % 2 == 0)
cout<<i<<' ';
++i;
}
return 0;
}
Înainte să treci mai departe, gândește-te și răspunde singur la următoarele întrebări:
Dacă ai reușit să răspunzi la întrebări și îți sunt clare cele două exemple, înseamnă că ești pregătit
să treci mai departe.
Date de intrare
Date de ieșire
Programul va afișa pe ecran triunghiul cerut mai sus.
Restricții
0 < N < 50
În multe probleme apare nevoia de a parcurge numere într-o anumită ordine. Ca să parcurgem
numerele de la 1 la n am putea folosi un while în felul următor:
#include <iostream>
using namespace std;
int main() {
int N;
cin>>N;
int i = 1;
while (i <= N) {
cout<<i<<" ";
++i;
}
return 0;
}
for
Pentru a parcurge mai ușor șiruri de numere în C++, putem să folosim for.
#include <iostream>
using namespace std;
int main() {
int N;
cin>>N;
int i;
for (i = 1; i <= N; ++i)
cout<<i<<" ";//afiseaza numerele de la 1 la N
for (i = N; i >= 1; --i)
cout<<i<<" ";//afiseaza numerele de la N la 1
return 0;
}
Deși nu aduce nimic nou față de instrucțiunea while, folosirea instrucțiunii for ajută la creșterea
clarității programelor. Este mult mai ușor să ne dăm seama ce se întâmplă când vedem
i=1;
while (i <=n) {
//instructiuni
++i;
}
Partea 2 conține condiția de terminare și este verificată înainte de rularea instrucțiunilor care
aparțin lui for.
Partea 3 se va executa după ce sunt rulate instrucțiunile cuprinse între acolade. După execuția
acestei părți, este verificată condiția din partea a doua, iar în cazul în care condiția nu mai este
îndeplinită, rularea programului continuă cu instrucțiunile de după for.
Practic putem scrie orice for cu ajutorul lui while în felul următor:
/*Partea 1*/;
while(/*Partea 2*/) {
//Instructiunile din for
/* Partea 3 */;
}
Atenție
În cazul instrucțiunilor if, while și for dacă în interiorul acestora se află o singură instrucțiune,
atunci nu mai trebuie să punem acolade care să marcheze instrucțiunile din interiorul acestora.
De exemplu
if (n % 2 == 0)
cout<<"n este par";
Întotdeauna când nu punem acolade, prima instrucțiune de după if, while sau for va fi
considerată ca făcând parte din structura folosită, iar următoarele vor fi considerate afară din
aceasta.
int i;
for (i = 1; i <= 5; ++i)
cout<<i<<" ";
cout<<"test";
După execuția acestui cod se va afișa pe ecran 1 2 3 4 5 test.
Se dă un număr N și apoi un șir de N numere. Se cere să se afișeze cel mai mic număr si cel mai
mare număr dintre cele Nnumere.
Date de intrare
Date de ieșire
Programul va afișa pe ecran două numere separate printr-un spatiu intre ele, reprezentând cel mai
mic și cel mai mare număr din șir, în această ordine.
Restricții și precizări
0 < N < 50
Elementele șirului sunt numere întregi cu valori mai mari decât -100 și mai mici
decât 100.
Când testezi programul, poți introduce numerele în program cu spațiu între ele, dar nu
uita să apeși enter după ce ai introdus toate numerele.
Exemplu
Date intrare
Date de ieşire
Pe ecran se vor scrie b numere, fiecare rezultat parţial al înmulţirii celor două numere.
Restricţii
Exemplu
5 3 5 10 15
Date de intrare
Date de ieșire
Restricții
Exemplu 4 – 7
Nevoia de șir de numere
Atunci când lucrăm cu multe date, avem nevoie să le stocăm în variabile în așa fel încât să le
putem accesa ușor.
Exemplu
Numărul de apariții
Se dă un număr care conține cifrele 0, 1, 2 și 3. Să se afișeze de câte ori apare fiecare cifră în el.
Am putea să folosim 4 variabile și să verificăm dacă ultima cifră este egală cu 0, 1, 2 sau 3.
#include <iostream>
using namespace std;
int main() {
int n;
int n0 = 0,n1 = 0,n2 = 0,n3 = 0; // numarul de cifre de 0, 1, ...
cin>>n;
while (n > 0) {
if (n % 10 == 0)
++n0;
if (n % 10 == 1)
++n1;
if (n % 10 == 2)
++n2;
if (n % 10 == 3)
++n3;
n /= 10;
}
cout<<"Aparitiile lui 0: "<<n0<<"\n";
cout<<"Aparitiile lui 1: "<<n1<<"\n";
cout<<"Aparitiile lui 2: "<<n2<<"\n";
cout<<"Aparitiile lui 3: "<<n3<<"\n";
return 0;
}
Deja pentru 4 cifre programul este destul de lung și multe instrucțiuni sunt foarte asemănătoare
între ele. În cazul în care ar trebui să rezolvăm problema pentru 10 cifre, programul ar fi și mai
lung și mai repetitiv.
La finalul celor trei zile vom avea 22 de cutii în depozitul 3 și 5 cutii în depozitul 23. Restul
depozitelor vor fi goale.
În viața reală
Foarte multe probleme din viața reală seamănă cu problema gestionării depozitelor. Ne putem
imagina că in loc de camioane avem utilizatori de pe Facebook, iar ciocolata sunt pozele pe care
aceștia le încarcă pe platformă. În fiecare moment Facebook trebuie să ne poată afișa pozele unui
anumit utilizator.
Așa cum ai văzut în lecția precedentă, avem nevoie de o modalitate ușoară de a accesa multe
variabile.
Pentru a face acest lucru, putem să folosim un șir de variabile. Avându-le într-un șir, putem să-i
dăm calculatorului instrucțiuni de forma:
int v[8];
Șirul v va conține 8 variabile numere întregi, prima va fi pe poziția 0, următoarea pe poziția 1, iar
ultima pe poziția 7.
Între [ și ] (paranteze drepte) vom specifica dimensiunea șirului. Dimensiunea șirului trebuie să
fie un număr întreg.
Întrucât variabilele se alocă în memoria RAM a calculatorului, trebuie să avem grijă ca atunci
când declarăm un șir de variabile, acesta să nu ocupe prea multă memorie. De exemplu, un
șir int de lungime 2 000 000 000 va ocupa 8 GB de memorie pentru că un int ocupă 4 bytes.
int main() {
int a=3,b=4,c=5,v[10];
v[4]=5; // Atribuim o valoare variabilei de pe pozitia 4 din v
v[a]=5; // Atribuim elementului de pe pozitia valorii lui a (3)
// din v valoarea 5
v[b]+=v[a]; // Adaugam elementului de pe pozitia valorii lui b (4)
// valoarea de pe pozitia lui a din v
++a;
v[a]-=2; // Scadem 2 din valoarea lui v[a] adica v[4]
cout<<v[3]<<" "<<v[a];
return 0;
}
Așa cum vedem în exemplul de mai sus, variabilele dintr-un șir se folosesc la fel ca și variabilele
normale.
Atenție!
Atunci când accesăm poziții folosindu-ne de o variabilă, trebuie să avem grijă ca acea variabilă
să aibă o valoare care se încadrează în dimensiunea șirului. De exemplu dacă avem un șir
de 8 elemente putem accesa elemente de la poziția 0 până la poziția 7 (de
la 0 la 7 sunt 8 numere).
Exemplu
#include <iostream>
using namespace std;
int main() {
int v[8];
int a=-1;
cout<<v[a];// nu va afisa un element din v!
a=8;
cout<<v[a];// nu va afisa un element din v!
return 0;
}
Pentru a putea lucra cu un șir de numere, trebuie prima dată să inițializăm valorile din șir. În
problemele din acest curs va trebui, în general, să citim un șir de numere și să facem diverse
operații asupra lui, iar la final să-l afișăm.
Șirul de numere este dat, în general, prin numărul de elemente pe care le conține, urmat pe linia
următoare de elementele șirului:
6
12 -3 -6 1 -1 0
Citirea
Pentru a citi șirul de numere vom folosi următorul program:
#include <iostream>
using namespace std;
int main() {
int N, v[101];
int i;
cin>>N;
for (i = 1; i <= N; ++i)
cin>>v[i];
return 0;
}
Deși pozițiile din șir încep de la 0, vom folosi pe parcursul cursului pozițiile începând de la 1.
Facem acest lucru pentru ca primul element citit să fie pe poziția 1, al doilea pe poziția 2, etc.
Din acest motiv este important să declarăm șirurile pe care le folosim să aibă dimensiunea mai
mare decât numărul maxim de elemente citite (un șir de 8 elemente are poziții de la 0 la 7).
Afișarea
Pentru a afișa elementele dintr-un șir de numere este suficient să parcurgem pozițiile unde am
pus numere și să afișăm valoarea de la acea poziție:
#include <iostream>
using namespace std;
int main() {
int N, v[101];
int i;
cin>>N;
// Prima data citim sirul
for (i = 1; i <= N; ++i)
cin>>v[i];
// Si il afisam
for (i = 1; i <= N; ++i)
cout<<v[i]<<' ';
return 0;
}
În cazul în care vrem să afișăm elementele de la dreapta la stânga adică de la cea mai mare
poziție la cea mai mică, trebuie doar să parcurgem pozițiile în ordine inversă:
Date de intrare
Se vor citi:
Date de ieșire
Pe ecran se va afișa șirul modificat. Elementele șirului vor fi separate prin spații.
Restricții
N < 1000
Numerele din șir vor fi mai mari decât -10000 și mai mici decât 10000
Exemplu
Soluție
Pentru a afla suma cerută vom afla prima poziție a unui element par precum și ultima poziție a
unui element par. Având aceste poziții vom însuma valorile elementelor aflate pe poziții cuprinse
între cele două poziții aflate.
int i,prima_pozitie;
for (i = 1; i <= N; ++i)
if (v[i] % 2 == 0) // am gasit un numar par
prima_pozitie = i;
Totuși codul este greșit. Înainte să citești mai departe, poți să-ți dai seama de ce este greșit?
Aflarea sumei
Având prima și ultima poziție calculate, mai trebuie să aflăm doar suma parcurgând pozițiile de
la prima la ultima poziție pară.
int suma = 0;
for (i = prima_pozitie; i <= ultima_pozitie; ++i)
suma += v[i];
___________________________________________________________________-_
In lucrul cu șiruri, de multe ori este nevoie să adăugăm elemente într-un șir. De exemplu atunci
când primim un mail nou, acesta se adaugă la șirul mailurilor primite.
++N;
v[N] = x;
Adăugarea unui număr la o poziție dată
Lucrurile se complică atunci când vrem să adăugăm un număr pe o poziție dată. Pentru șirul 6 4
2 9 8 7 dacă dorim să adăugăm numărul 5 pe poziția 3, șirul obținut va fi 6 4 5 2 9 8 7.
Ca în cazul anterior, trebuie să creștem lungimea șirului. Pe lângă creșterea lungimii, trebuie să
mutăm toate elementele de pe poziții mai mari sau egale cu poziția pe care adăugăm elementul
cu o poziție la dreapta.
#include <iostream>
using namespace std;
int main() {
int N, v[100], i;
cin>>N;
for (i = 1; i <= N; ++i) // Citim sirul
cin>>v[i];
int x,p; // Numarul si pozitia pe care sa fie inserat
cin>>x>>p;
++N; // Crestem lungimea sirului
for (i = N; i > p; --i) // Mutam elementele cu o pozitie la dreapta
v[i]=v[i-1];
v[p]=x;
for (i = 1; i <= N; ++i) // Afisam sirul
cout<<v[i]<<' ';
return 0;
}
Fie că ștergem un comentariu care nu ne place de la o postare sau un mail pe care l-am citit,
acestea sunt exemple de cazuri când ștergem elemente din șiruri.
Pentru a face acest lucru trebuie doar să scădem dimensiunea șirului cu 1. Dacă lungimea șirului
o avem salvată în variabila N, atunci o vom scădea cu 1 în felul următor: --N;. Acum de fiecare
dată când vom lucra cu șirul, vom lucra cu noua lungime așa că ultimul element va fi ignorat.
Când ștergem un element de la o poziție dată, pentru a nu rămâne cu o "gaură" în șir, trebuie să
mutăm toate elementele de la dreapta elementului șters cu o poziție spre stânga și să scădem
lungimea șirului cu 1.
#include <iostream>
using namespace std;
int main() {
int N, v[100], i;
cin>>N;
for (i = 1; i <= N; ++i) // Citim sirul
cin>>v[i];
int p; // Pozitia de pe care vrem sa stergem
cin>>p;
for (i = p; i < N; ++i) // Mutam elementele cu o pozitie la stanga
v[i]=v[i+1];
--N; // Scadem lungimea sirului
for (i = 1; i <= N; ++i) // Afisam sirul
cout<<v[i]<<' ';
return 0;
}
Continuă
Dă quiz-ul din nou
Rezultate quiz
Date de intrare
Pe prima linie se citește numărul natural N, iar pe urmatoarea linie N numere naturale, separate
prin spații.
Date de ieșire
Restricții
N <= 1 000
0 < valorile din șir <= 1 000
Exemplu
4 4 2
1 2 3 4
Fiind dat un șir de N numere întregi pozitive, să se afișeze pe ecran numerele șirului inițial, cu
următoarele modificări:
Numerele pare se vor afla pe primele poziții din șir, în ordine crescătoare a pozițiilor
în șirul inițial.
Numerele impare se vor afla după numerele pare, în ordine descrescătoare a
pozițiilor în șirul inițial.
Date de intrare
Se vor citi:
Un număr întreg N
Un șir de N numere întregi pozitive
Date de ieșire
Restricții
N < 1000
Numerele din șir vor fi mai mici sau egale cu 1000
Exemplu
Există mai multe metode de a sorta un șir, iar pe parcursul cursului vor fi prezentate mai multe
dintre ele. În această lecție va fi prezentată cea mai simplă dintre ele: sortarea prin selecție.
Din fericire există o metodă mult mai simplă de a implementa acest algoritm folosind un singur
șir.
#include <iostream>
using namespace std;
int main() {
int N, v[100], i, j, aux;
cin>>N;
for (i = 1; i <= N; ++i) // Citim sirul
cin>>v[i];
for (i = 1; i < N; ++i)
for (j = i + 1; j <= N; ++j) // Cautam elemente mai mici decat v[i]
if (v[i] > v[j]) { // Am gasit un element mai mic
// Punem valoarea din v[j] in v[i] si din v[i] in v[j]
aux = v[i];
v[i] = v[j];
v[j] = aux;
}
for (i = 1; i <= N; ++i) // Afisam sirul
cout<<v[i]<<' ' ;
return 0;
}
În această variantă pentru fiecare poziție notată cu i de la 1 la N-1 căutăm elementul cu valoare
minimă de pe pozițiile de la ila N, iar de fiecare dată când găsim un element mai mic decât v[i],
îl punem pe acesta în locul lui v[i] și vechea valoare care se afla în v[i] o punem în locul
elementului găsit.
Practic facem un schimb de poziții între cele două elemente ale șirului de numere, folosindu-ne
de o variabilă auxiliară care să rețină valoarea unuia dintre elementele care își schimbă locul.
De multe ori când lucrăm cu date, acestea sunt organizate sub forma unui tabel. De exemplu
putem organiza apelurile de pe telefon în felul următor:
Așa cum poți vedea, este foarte greu să ne dăm seama pentru fiecare element pe ce rând și pe ce
coloană s-ar afla.
Pentru a ne ușura munca cu acest gen de date, în C++ putem lucra cu șiruri bidimensionale
(matrice).
Matricele sunt șiruri de numere în care fiecare număr este identificat prin două poziții: indicele
rândului (liniei) și al coloanei pe care se află.
Așa cum se vede în figură, liniile se numără de sus în jos, iar coloanele de la stânga la dreapta.
Numerele de pe linia 0 sunt 1,5, 11 și 8, cele de pe linia 1 sunt 7,2, 3 și 9, iar cele de pe
linia 2 sunt 10,6, 4 și 0.
Fiecare număr din matrice poate fi găsit după indicele liniei și al coloanei de pe care face
parte. 3 este pe linia 1 coloana 2, 0este pe linia 2 coloana 3.
La fel ca în cazul șirurilor, vom folosi elementele din matrice începând de la linia și coloana 1.
int a[3][4];
Exemplu
În problemele cu matrice vom primi prima dată numărul de linii și numărul de coloane ale
matricei urmate de elementele din ea. Un exemplu de date de intrare care conțin o matrice e
următorul:
5 4
1 2 2 5
6 9 1 1
8 7 6 2
2 2 2 2
3 5 2 7
În exemplul de mai sus este o matrice cu 5 linii și 4 coloane. Exemplul are pe prima linie
numărul de linii, urmat de numărul de coloane. Pe următoarele 5 linii se află câte 4 numere
reprezentând numerele de pe fiecare coloană din linia respectivă din matrice.
Citirea
Pentru a citi o matrice, prima dată vom citi în două variabile N și M numărul de linii și de coloane
ale matricei. Pe urmă vom citi conținutul matricei în felul următor: pentru fiecare linie din
matrice vom parcurge toate numerele de pe ea și le vom citi în coloana corespunzătoare.
#include <iostream>
using namespace std;
int main() {
int N, M, matrice[100][100];
cin>>N>>M;
int i,j;
for (i = 1; i <= N; ++i)
for (j = 1; j <= M; ++j)
cin>>matrice[i][j];
return 0;
}
Afișarea
La fel ca și în cazul șirurilor, felul în care se face afișarea este foarte similar cu cel în care se face
citirea. Pentru a afișa, parcurgem elementele pe linii și le afișăm.
Transpusa unei matrice este o matrice obținută prin oglindire față de diagonala principală.
În practică, calculăm transpusa unei matrice transformând fiecare linie în coloană. Elementele de
pe linia 1 vor deveni elementele coloanei 1, elementele de pe linia 2 vor deveni elementele
coloanei 2 etc. Același lucru se întâmplă și cu coloanele: coloana 1va deveni linia 1 etc.
Exemplu
Matricea Matricea
inițială transpusă
3 4 4 3
1 2 3 4 1 5 9
5 6 7 8 -> 2 6 1
9 1 2 3 3 7 2
4 8 3
Implementare
Pentru a afișa transpusa unei matrice putem să facem în felul următor
#include <iostream>
using namespace std;
int main() {
int linii, coloane, mat[35][35];
int linii_transpusa, coloane_transpusa, transpusa[35][35];
int i,j;
// Citire
cin>>linii>>coloane;
for (i = 1; i <= linii; ++i)
for (j = 1; j <= coloane; ++j)
cin>>mat[i][j];
// Calculare transpusa
linii_transpusa = coloane;
coloane_transpusa = linii;
for (i = 1; i <= linii; ++i)
for (j = 1; j <= coloane; ++j) {
// linia i devine coloana i, coloana j devine linia j
// asta inseamna ca elementul care a fost pe linia i, coloana j
// va ajunge pe coloana i, linia j
transpusa[j][i] = mat[i][j];
}
for (i = 1; i <= linii_transpusa; ++i) {
for (j = 1; j <= coloane_transpusa; ++j)
cout<<transpusa[i][j]<<" ";
cout<<"\n";
}
return 0;
}
Alege toate secvențele de instrucțiuni care afișează pe ecran transpusa transpusei matricei a,
unde matricea e citită de la tastatură.
a. int a[100][100], n, m, i, j;
b.
c. cin >> m >> n; // matricea are m linii si n coloane
d. for (i = 1; i <= m; i++)
e. for (j = 1; j <= n; j++)
f. cin >> a[i][j];
g. for (i = 1; i <= m; i++) {
h. for (j = 1; j <= n; j++)
i. cout << a[i][j] << " ";
j. cout << "\n";
k. }
l.
m. int a[100][100], n, m, i, j;
n.
o. cin >> m >> n; // matricea are m linii si n coloane
p. for (i = 1; i <= m; i++)
q. for (j = 1; j <= n; j++)
r. cin >> a[i][j];
s. for (i = 1; i <= m; i++) {
t. for (j = 1; j <= n; j++)
u. cout << a[j][i] << " ";
v. cout << "\n";
w. }
x.
Date de intrare
Date de ieșire
Restricții
Exemplu
332 123
123 789
456
789
Date de intrare
Date de ieșire
Restricții
Exemplu
332 13
Date de intrare Date de ieșire
123 46
456 79
789
O matrice pătratică este o matrice în care numărul liniilor este egal cu cel al coloanelor. Un
exemplu de date de intrare în care este dată o matrice pătratică este următorul:
5
3 2 1 9 8
1 1 2 7 7
6 4 3 3 3
1 1 9 8 5
5 2 1 4 2
Citirea
int N, m[101][101], i, j;
cin>>N; // Citim numarul de linii si de coloane
for (i = 1; i <= N; ++i) // Parcurgem fiecare linie
for(j = 1; j <= N; ++j) // si fiecare coloana
cin>>m[i][j]; // citim elementul de pe linia i, coloana j
Afișarea se face în mod similar.
Diagonale
Datorită faptului că matricea are formă pătratică, aceasta are două diagonale. Diagonala care
pornește din colțul din stânga sus și se termină în cel din dreapta jos se numește diagonală
principală, iar cea care începe în dreapta sus și se termină în stânga jos se numește diagonală
secundară.
Pe lângă parcurgerea de sus în jos de la stânga la dreapta mai putem parcurge matricea și în alte
feluri.
Când vrem să parcurgem o matrice, ne ajută să folosim un desen în care să avem marcate
coordonatele fiecărei celule:
Primul număr din fiecare celulă reprezintă indicele liniei, iar al doilea număr reprezintă indicele
coloanei. La fel ca și în lecțiile anterioare, am folosit indici începând de la 1.
Parcurgerea diagonalelor
În figura de mai sus am marcat cu albastru diagonala principală, iar cu roșu diagonala secundară.
Elementul 3, 3 face parte din ambele diagonale, așa că a fost marcat și cu roșu, și cu albastru.
Așa cum se vede în figură, elementele de pe diagonala principală au indicele liniei egal cu cel al
coloanei.
Atunci când vrem să parcurgem diagonala secundară de sus în jos, observăm că indicele coloanei
scade de fiecare dată când indicele liniei crește. La primul pas indicele liniei este egal cu 1, iar
cel al coloanei cu numărul de coloane adică N. Știind acest lucru, putem să deducem o formulă
pentru indicele coloanei în funcție de indicele liniei. Indicele coloanei va fi egal cu N -
indice_linie + 1
În acest capitol îți voi arăta probleme mai complicate care se rezolvă cu șiruri de numere. Te-ai
gândit vreodată cum de filmele de pe youtube se încarcă așa de repede deși pe serverele lor sunt
milioane de GB de filme? Un alt lucru interesant este viteza cu care funcționează Facebook, o
rețea socială cu peste 2 miliarde de utilizatori.
*Durata unei operații depinde foarte mult de viteza procesorului pe care e rulat programul tău,
dar și de tipul operației. Unele operații sunt mult mai lente decât altele.
Exemple de probleme
Înainte să treci la lecția următoare citește următoarele probleme și gândește-te la felul în care le-
ai rezolva.
Se dă un șir de numere (presupunem că este deja stocat într-o variabilă de tip șir) în ordine
crescătoare și un element x. Cum poți afla dacă x se află în șir fără să parcurgi tot șirul de la
stânga la dreapta?
La alegeri participă N candidați și M alegători. Pentru fiecare alegător știi cu care dintre
cei N candidați a votat. Cum poți afla ce candidat a câștigat alegerile făcând un număr cât mai
mic de operații? Un candidat câștigă alegerile dacă obține cel puțin M/2voturi.
Hai să revenim la problema din introducerea capitolului.
M candidați participă la alegeri. În ziua votului N alegători și-au exprimat voturile. Să se afle
câte voturi a obținut fiecare candidat.
Putem reformula problema în felul următor: Se dă un șir cu N elemente. Să se afle de câte ori
apare fiecare element de la 1 la M în șirul dat.
Soluţia 1
O primă idee ar fi să parcurgem fiecare element de la 1 la M și să numărăm de câte ori apare în
șir.
#include <iostream>
using namespace std;
int main() {
int N, M, v[1001];
cin>>N>>M;
for (int i = 1; i <= N; ++i)
cin>>v[i];
Această soluție este foarte lentă. Pentru fiecare număr de la 1 la M sunt parcurse toate
cele N numere din șir deci se vor face N*Mcalcule.
Soluţia 2
Această soluţie se bazează pe construirea unui şir suplimentar, pe care îl vom numi şir de apariții
(sau șir de frecvență). Şirul de apariții va reţine de câte ori apare fiecare element în șirul dat.
Pentru a fi mai expliciţi, vom nota şirul cu AP. Astfel, poziţia i din şirul AP va reţine numărul de
apariţii al numărului i în şirul dat. La început şirul AP conţine doar valori 0. Când întâlnim o
nouă valoare (o vom nota x), va trebui doar să incrementăm poziţia x din AP. După ce am parcurs
toate numerele, vom parcurge şirul AP şi vom afişa fiecare valoare din el.
Să luam un exemplu:
Se dă şirul: 1 2 4 3 2 5
Elementul curent va fi notat cu x, iar şirul AP va conţine 5 elemente, iniţial 0, care vor fi scrise
unul după altul, separate printr-un spaţiu, reprezentând numărul de apariţii ale numerelor 1,2,3,4,
respectiv 5(în această ordine).
1 10000
2 11000
4 11010
3 11110
2 12110
5 12111
#include <iostream>
using namespace std;
int main()
{
int N, M, v[1001], AP[1001];
cin>>N>>M;
// Initializam cu 0 fiecare pozitie pentru a putea numara aparitiile
for (int i = 1; i <= M; ++i)
AP[i] = 0;
Observaţii
1. Dimensiunea șirului de frecvență trebuie setată în funcție de cât de mari pot fi elementele din șir.
Dacă elementele șirului sunt numere până la 1000, atunci trebuie să setăm dimensiunea șirului la
cel puțin 1001.
2. Această soluție este mai eficientă și face doar 2 parcurgeri de la 1 la M și 2 parcurgeri de la 1 la N.
3. Şirurile de frecvenţă sunt utile în cazul în care trebuie să reţinem numărul de apariţii ale tuturor
numerelor dintr-un şir.
4. Şirurile de frecvenţă se pot folosi în cazul în care numerele care trebuie gestionate fac parte dintr-
un domeniu restrâns. Spre exemplu un şir de frecvenţă care ar ţine minte numărul de apariţii
pentru numere până la un miliard ar ocupa mai mult de 3 GB. De obicei, se folosesc pentru a
reţine numărul de apariţii al unor numere dintr-o mulţime de maxim un milion de elemente.
Această problemă este dată frecvent la interviuri. De multe ori apare sub forma:
Să se afișeze prima poziție dintr-un șir sortat pe care se află un element mai mare sau egal cu un
element x dat
Soluție 1
Pentru a rezolva prima problemă, putem parcurge șirul de la stânga la dreapta și să verificăm,
pentru fiecare element, dacă este egal cu x.
int gasit = 0, i;
for (i = 1; i <= N; ++i)
if (v[i] == x)
gasit = 1;
if (gasit == 1)
cout<<"x apare in sir";
else
cout<<"x nu apare in sir";
Cu cât șirul devine mai lung, numărul de operații va fi din ce în ce mai mare.
Soluție 2
Idee
Pentru a rezolva mai eficient problema, trebuie să ne folosim de faptul că șirul este sortat.
Dacă alegem o poziție p din șir, iar v[p] este mai mare sau egal decât x, atunci știm că x se va
afla în secvența de elemente de la poziția 1 până la poziția p. Vom nota această secvență [1 ...
p]. În cazul în care x este mai mare, atunci vom căuta în secvența [p + 1 ... N].
Exemplu
Fie șirul 1 2 5 11 12 23 25.
Dorim să aflăm dacă elementul 5 se află în șir. Dacă ne uităm la poziția 4, observăm că putem să
căutăm doar în secvența [1 ... 4] din șir deoarece 11 >= 5.
Acum avem de rezolvat aceeași problemă pentru un șir mai mic: 1 2 5 11. Dacă alegem
poziția 2, 2 < 5, deci va fi suficient să-l căutăm pe 5 în secvența [3 ... 4].
Generalizare
La fiecare pas vom împărți șirul în două subsecvențe care nu au nici un element în comun și care
împreună formează șirul de la care am pornit la pasul curent. În funcție de elementul pe care
vrem să-l găsim în șir, vom alege una dintre secvențe și îl vom căuta acolo folosind același
procedeu.
Eficiență
Eficiența algoritmului depinde de felul în care alegem poziția la care împărțim șirul. Dacă
alegem tot timpul începutul șirului, iar xeste mai mare decât elementul de la început, vom
parcurge tot șirul.
Pentru șirul 1 2 3 4 5 dacă căutăm elementul 5 și alegem prima poziție, la pasul următor, vom
căuta pe 5 în șirul 2 3 4 5, la pasul următor în 3 4 5 și așa mai departe.
Pentru a obține eficiența maximă, ne vom uita la elementul de la mijlocul șirului. Astfel
indiferent dacă trebuie să alegem subsecvența din stânga sau pe cea din dreapta, vom efectua
puțini pași.
Exemplu 2
e dă un șir v cu N numere ordonate crescător și un număr x. Să se afle dacă există un element cu
valoarea x în șir.
Pentru a implementa ideea din lecția precedentă vom ține minte la fiecare pas capătul din stânga
(îl notăm st) și capătul din dreapta (îl notăm dr) al secvenței în care căutăm elementul x.
Pentru a trece la pasul următor, vom compara elementul din mijlocul subsecvenței cu x și vom
actualiza capătul din stânga sau capătul din dreapta.
Dacă x este mai mare decât v[m] (am notat m mijlocul secvenței la care ne aflăm), atunci vom
căuta în subsecvența din dreapta [m+1 ... dr]. În caz contrar vom căuta în subsecvența din
stânga [st ... m].
Pentru a afla mijlocul unei subsecvențe care începe la poziția st și se termină la poziția dr este
suficient să aflăm media celor două capete. Astfel mijlocul m va fi m=(st+dr)/2.
Terminarea algoritmului
Atunci când ajungem să avem o secvență cu un singur element, putem verifica dacă acel element
este egal cu x. Putem face acest lucru deoarece la fiecare pas al algoritmului am avut grijă ca în
stânga subsecvenței să avem tot timpul elemente mai mici sau egale cu elementul căutat, iar in
dreapta elemente mai mari.
Implementare
#include <iostream>
using namespace std;
int main()
{
int N, v[100001], x;
cin>>N;
for (int i = 1; i <= N; ++i)
cin>>v[i];
cin>>x;
Exemplu
Pentru șirul 1 2 3 4 5 și elementul 5, se va căuta elementul în următoarele intervale:
[1, 5] 3
[4, 5] 4
La final, stânga devine egală cu dreapta, având intervalul [5, 5]. Aici algoritmul se va opri.
Se dă un șir v cu N numere întregi. Să se ordoneze crescător elementele acestuia
Pe lângă metoda de sortare pe care am prezentat-o acum câteva lecții, putem ordona crescător
elementele unui șir folosindu-ne și de alte idei.
Idee
Un lucru care ne poate ajuta când vrem să sortăm șiruri este să ne gândim cum putem să ne dăm
seama că un șir nu este sortat.
Un șir nu este sortat dacă reușim să găsim o poziție i (i merge de la 1 la N-1) în așa fel
încât v[i] > v[i + 1], adică dacă există două elemente consecutive care nu sunt în ordine
crescătoare.
Dacă de fiecare dată când găsim două astfel de numere, le interschimbăm, vom avea la un
moment dat un șir sortat întrucât vom rămâne fără elemente pe care să le interschimbăm.
Exemplu
Implementare
Implementarea ideii este foarte simplă: cât timp găsim elemente care nu sunt în ordine
crescătoare, le căutăm și le interschimbăm.
#include <iostream>
using namespace std;
int main()
{
int N, v[100001];
cin>>N;
for (int i = 1; i <= N; ++i)
cin>>v[i];
Până acum am văzut două metode de sortare a unui șir de numere. Totuși cele două metode devin
foarte lente atunci când dimensiunea șirului devine mare.
Ca să te convingi de acest lucru, încearcă să folosești Bubble sort pentru a sorta crescător un șir
de numere în care numerele se află în ordine descrescătoare.
Faptul că numerele din șir sunt între 1 și 99 ne permite să găsim un algoritm mai eficient
folosind șirurile de frecvență.
Simplificare
Pentru a găsi algoritmul, să ne gândim cum am rezolva problema dacă toate elementele șirului
sunt distincte (nici o valoare nu apare de două ori).
Putem folosi un șir nou, apar, care să ne spună care elemente apar în șirul inițial. Mai
exact apar[i] va fi egal cu 1 dacă valoarea i apare în șirul dat.
#include <iostream>
using namespace std;
int main()
{
int N, v[100001], apar[100];
cin>>N;
for (int i = 1; i <= N; ++i) {
cin>>v[i];
apar[v[i]] = 1;// marcam ca exista in sir un element cu valoarea v[i]
}
// stim ca numerele din sir sunt < 100, deci putem sa parcurgem toate
// numerele pana la 100 si sa vedem care apar in sir
for (int i = 1; i < 100; ++i)
if (apar[i] == 1)
cout<<i<<' ';
return 0;
}
Soluție
În cazul în care unele numere apar de mai multe ori, în loc să afișăm o singură dată, vom afișa
fiecare număr de atâtea ori de câte apare în șir.
#include <iostream>
using namespace std;
int main()
{
int N, v[100001], fr[100];
cin>>N;
// Initializam intai fiecare pozitie din vectorul de frecventa cu 0
for (int i = 1; i < 100; ++i) fr[i] = 0;
for (int i = 1; i <= N; ++i) {
cin>>v[i];
++fr[v[i]];// crestem frecventa elementului v[i]
}
Atenție!
Dacă valorile din șir sunt prea mari, nu putem aplica acest algoritm deoarece am avea nevoie să
folosim prea multă memorie pentru șirul de frecvență.
Continuă
Date de intrare
Date de ieșire
Restricții și precizări
1 ≤ N, M ≤ 100 000
-2 000 000 000 ≤ v[i], w[i] ≤ 2 000 000 000
Exemplu
Sunt sigur că ai salvat imagini, documente sau fișiere pe calculator pentru a le putea folosi mai
târziu. Aceste fișiere sunt deschise de diverse programe. Ar fi foarte util dacă am putea să ne
salvăm matricile, vectorii sau numerele în fișiere și să îi spunem programului nostru să citeasca
din fișier, pentru a nu trebui să introducem datele de fiecare dată de la tastatură.
#include <fstream>
using namespace std;
int main() {
ifstream fin("date-intrare.txt");
ofstream fout("date-iesire.txt");
int a,b;
fin>>a>>b;
fout<<a+b;
return 0;
}
Programul de mai sus citește două numere a și b din fișierul date-intrare.txt și afișează
suma lor în fișierul date-iesire.txt.
În video-ul de mai jos poți vedea cum să folosești fișierele text în CodeBlocks
Explicație
#include <fstream> - include biblioteca fstream care ne ajută să citim și să afișăm
date din fișiere (la fel cum iostream ne ajută cu citirea datelor de la tastatură și afișarea
lor pe ecran).
ifstream fin("date-intrare.txt"); - deschide fișierul date-intrare.txt și îl
pregătește pentru când vrem să citim din el. În loc de date-intrare.txt putem scrie
orice nume de fișier.
Pentru a citi date din acest fișier, în loc de cin>> folosim fin>>. În acest caz fin este o
variabilă pe care o folosim pentru acces la fișier, iar ea respectă regulile de denumire a
variabilelor pe care le cunoști de la variabilele întregi. Astfel, bucata de cod din exemplul
de mai jos e corectă și va citi un număr întreg din fișierul date.txt.
Exemplu
int z;
ifstream ab1c("date.txt");
ab1c>>z;
ofstream fout("date-iesire.txt"); - La fel ca și în cazul precedent, informează
calculatorul că urmează să afișăm date în fișierul date-iesire.txt de fiecare dată când
scriem fout<<. Aceleași reguli se aplică pentru denumirea fișierului și pentru fout ca și
la punctul anterior. Diferența între când declarăm un fișier de intrare și când îl declarăm
de ieșire e dată de ifstream sau ofstream. Primul vine de la input file stream, iar
al doilea de la output file stream și specifică tipul variabilei pe care o declarăm.
fin>> și fout<< - pentru a citi și scrie date se aplică aceleași reguli ca și
pentru cin și cout doar că în acest caz folosim fișiere text.
CodeBlocks
Pentru a folosi fișiere text în cadrul unui proiect în CodeBlocks, trebuie să dai click pe File >
New > Empty file sau să apeși Ctrl + Shift + N, să apeși Yes la Do you want to add this
new file in the active project?. Înainte să dai Save, trebuie să îi dai fișierul un nume și să alegi
opțiunea pentru Save as type să fie ultima din listă, adică All files(.).
Execută pasul de mai sus pentru fiecare fișier pe care vrei să îl folosești în program.
Pentru a deschide fișierele create în CodeBlocks, apasă pe + din stânga folderului Others și dă
dublu click pe fiecare fișier pe care vrei să-l deschizi, alege Open it inside the Code::Blocks
editor și apasă OK.
Acum poți scrie date în fișierele create și să le citești sau să le scrii în alt fișier din main.cpp.