Invatare Atomica

Algoritmi simpli cu bucle

Progres lectie:
0%
🎯

Obiectivul lectiei

Vei invata sapte tipare de algoritmi care apar in aproape orice problema de informatica: acumulare (suma, produs), comparare (minim, maxim), numarare, gasirea divizorilor si verificarea daca un numar este prim. Toate folosesc o bucla care parcurge date si o variabila care „tine minte” un rezultat.

Dupa aceasta lectie vei putea:

  • Sa calculezi suma si produsul unui sir de numere folosind un acumulator
  • Sa determini minimul si maximul dintr-un sir printr-o singura parcurgere
  • Sa numeri cate elemente respecta o conditie (contor)
  • Sa afisezi divizorii unui numar si sa-i numeri
  • Sa verifici daca un numar este prim si sa explici de ce O(√n) este suficient
  • Sa indici complexitatea (notatia O) a fiecarui algoritm

Incearca singur!

Provocare:

Ai numerele 7, 2, 9, 4, 9, 1, 6. Fara sa scrii cod, descrie in cuvinte cum ai gasi cel mai mare numar parcurgandu-le o singura data, de la stanga la dreapta. Ce „tii minte” pe parcurs?

💡 Ai nevoie de un indiciu?

Tine minte o singura valoare: „cel mai mare vazut pana acum”. Incepe cu primul element. La fiecare element nou, daca e mai mare decat ce ai memorat, actualizeaza memoria.

Acest tipar — o variabila care retine un rezultat partial care se actualizeaza in bucla — este inima tuturor algoritmilor din aceasta lectie!

1

1. Suma cu acumulator

Un acumulator este o variabila in care „adunam” treptat un rezultat. Pentru suma il initializam cu 0 (elementul neutru al adunarii), apoi parcurgem numerele si la fiecare pas il marim.
# Suma numerelor de la 1 la n
n = 5
suma = 0
for i in range(1, n + 1):
    suma = suma + i
print("Suma de la 1 la", n, "este", suma)
Output real (rulat):
Suma de la 1 la 5 este 15
Complexitate: bucla face exact n pasi → O(n) (liniar).
2

2. Produsul (factorialul)

Pentru produs folosim acelasi tipar, dar initializam acumulatorul cu 1 (elementul neutru al inmultirii). Produsul numerelor de la 1 la n se numeste n factorial si se noteaza n!.
# Produsul numerelor de la 1 la n (factorialul lui n)
n = 5
produs = 1
for i in range(1, n + 1):
    produs = produs * i
print("Produsul (n!) pentru n =", n, "este", produs)
Output real (rulat):
Produsul (n!) pentru n = 5 este 120
5! = 1·2·3·4·5 = 120. Complexitate: O(n).
3

3. Minim si maxim dintr-un sir

Pentru minim si maxim „tinem minte” doi candidati. Ii pornim cu primul element (sigur exista in sir), apoi la fiecare element comparam si actualizam daca am gasit ceva mai mic / mai mare.
# Minimul si maximul dintr-un sir de numere
numere = [7, 2, 9, 4, 9, 1, 6]
minim = numere[0]
maxim = numere[0]
for x in numere:
    if x < minim:
        minim = x
    if x > maxim:
        maxim = x
print("Minim =", minim)
print("Maxim =", maxim)
Output real (rulat):
Minim = 1
Maxim = 9
O singura parcurgere → O(n). Initializarea cu primul element este mai sigura decat cu o „valoare mare” aleasa la noroc.
4

4. Numarare cu contor

Un contor este o variabila pornita de la 0 pe care o crestem cu 1 de fiecare data cand un element respecta o conditie. Aici numaram cate numere sunt pare (rest 0 la impartirea cu 2).
# Numararea elementelor pare dintr-un sir
numere = [7, 2, 9, 4, 9, 1, 6]
contor = 0
for x in numere:
    if x % 2 == 0:
        contor = contor + 1
print("Numar de elemente pare:", contor)
Output real (rulat):
Numar de elemente pare: 3
Elementele pare sunt 2, 4 si 6 → 3 elemente. Complexitate: O(n).
5

5. Divizorii unui numar

Un numar d este divizor al lui n daca n se imparte exact la d (rest 0). Ii cautam testand fiecare candidat de la 1 la n.
# Afisarea divizorilor lui n si numararea lor
n = 24
contor = 0
for d in range(1, n + 1):
    if n % d == 0:
        print(d, end=" ")
        contor = contor + 1
print()
print("Numar de divizori:", contor)
Output real (rulat):
1 2 3 4 6 8 12 24
Numar de divizori: 8
Aceasta varianta testeaza toti candidatii pana la n → O(n). Se poate accelera testand doar pana la √n (vezi atomul urmator).
6

6. Numar prim (test pana la √n)

Un numar prim este un numar natural mai mare ca 1 care are exact doi divizori: 1 si pe el insusi. Nu e nevoie sa testam toti candidatii pana la n: divizorii vin in perechi (d si n/d), iar cel mic din pereche este mereu ≤ √n. De aceea conditia d * d <= n este suficienta.
# Verificare daca un numar este prim (varianta simpla)
n = 29
if n < 2:
    prim = False
else:
    prim = True
    d = 2
    while d * d <= n:
        if n % d == 0:
            prim = False
        d = d + 1
if prim:
    print(n, "este prim")
else:
    print(n, "NU este prim")
Output real (rulat):
29 este prim
Bucla merge pana cand d*d depaseste n → complexitate O(√n), mult mai rapid decat O(n) pentru numere mari.
⚡ EXCLUSIV INTENSIV (C++): acelasi algoritm in C++

La specializarea intensiv informatica scriem si in C++. Acelasi test de primalitate, cu acelasi rationament O(√n):

#include <iostream>
using namespace std;

int main() {
    int n = 29;
    bool prim = (n >= 2);
    for (int d = 2; (long long)d * d <= n; d++) {
        if (n % d == 0) {
            prim = false;
        }
    }
    if (prim)
        cout << n << " este prim" << endl;
    else
        cout << n << " NU este prim" << endl;
    return 0;
}
Output real (compilat cu g++ -std=c++17, rulat):
29 este prim

Observa cum (long long)d * d protejeaza de depasire pentru n mare, iar logica este identica cu Python.

Exercitii practice

Exercitiul 1 (Nivel minim) - Suma cifrelor

Scrie un program care citeste un numar natural n si afiseaza suma cifrelor sale. Indiciu: foloseste n % 10 pentru ultima cifra si n // 10 pentru a o elimina, intr-o bucla while. Care este complexitatea fata de numarul de cifre?

Vezi rezolvarea

Rezolvare, dupa modelul acumulatorului si al buclei WHILE cu n % 10 / n // 10 din lectie:

  1. n = 1234
  2. s = 0
  3. while n != 0:
  4. s = s + n % 10
  5. n = n // 10
  6. print(s)

Pentru n = 1234: cifrele extrase pe rand sunt 4, 3, 2, 1, deci s = 4+3+2+1 = 10. Trasare: s merge, in ordine, prin valorile 0, 4, 7, 9, 10, iar n merge prin 1234, 123, 12, 1, 0 (bucla se opreste cand n devine 0). Complexitate: bucla ruleaza exact o data pentru fiecare cifra a lui n, deci este O(numarul de cifre) - liniara fata de numarul de cifre ale lui n, nu fata de valoarea lui n.

Exercitiul 2 (Nivel standard) - Maximul si pozitia lui

Pentru sirul [4, 11, 2, 11, 8] afiseaza valoarea maxima si pozitia primei aparitii a acesteia. Atentie la cazul cu valori egale: trebuie sa retii prima pozitie, nu ultima.

Vezi rezolvarea

Rezolvare, combinand tiparul de minim/maxim (atom 3) cu tiparul de contor (atom 4), pentru a retine si pozitia:

  1. numere = [4, 11, 2, 11, 8]
  2. maxim = numere[0]
  3. pozitie = 1
  4. i = 1
  5. for x in numere:
  6. if x > maxim:
  7. maxim = x
  8. pozitie = i
  9. i = i + 1
  10. print("Maxim =", maxim, "la pozitia", pozitie)

Trasare: la x = 11 (pozitia 2) avem 11 mai mare decat 4 (11 > 4), deci maxim = 11, pozitie = 2. La a doua aparitie a lui 11 (pozitia 4), conditia 11 > 11 este falsa, deci pozitia NU se schimba. Rezultat: maxim = 11, pozitia = 2 (numerotand de la 1). Cheia e comparatia stricta > (nu >=): ea garanteaza ca retinem prima aparitie, nu ultima.

Exercitiul 3 (Nivel performanta) - Optimizeaza numararea divizorilor

Rescrie algoritmul de numarare a divizorilor lui n astfel incat sa ruleze in O(√n) in loc de O(n), folosind faptul ca divizorii vin in perechi (d, n/d). Atentie la patratul perfect (cand d == n/d, divizorul se numara o singura data). Verifica pe n = 24 ca obtii tot 8 divizori.

Vezi rezolvarea

Schema de rezolvare (nu solutia completa - la nivel performanta trebuie sa o scrii singur):

  1. Foloseste o bucla care merge cu d doar pana la radical din n, adica atata timp cat d * d <= n, exact conditia din testul de prim de la atomul 6.
  2. Cand n % d == 0, d este divizor, iar partenerul lui, n // d, este automat tot divizor - deci numeri amandoi dintr-o singura verificare.
  3. Exceptie obligatorie: cand d == n // d (n este patrat perfect, ex. d=5 la n=25), acel divizor se numara o singura data, nu de doua ori.
  4. Daca se cere si afisarea divizorilor (nu doar numararea lor), retine cele doua liste separat (cei mici, crescator, si cei mari, descrescator) si afiseaza-le pe rand.

Capcane: limita buclei trebuie sa fie d * d <= n (nu d <= n / 2); uitarea cazului patrat perfect duce la numarare dubla. Verificare: pentru n = 24 trebuie sa obtii tot 8 divizori (1, 2, 3, 4, 6, 8, 12, 24), dar bucla se opreste la d=4 (5*5=25>24), fata de varianta O(n) din lectie care testa pana la 24.

Criterii de evaluare: functioneaza corect pe patrat perfect (ex. 36) si pe numar prim (ex. 29, doar 2 divizori); complexitatea O(radical din n) e reala, nu doar afirmata; cod fara divizori dublati sau lipsa.

Ce ai invatat astazi

  • Acumulator pentru suma (init 0) si produs / factorial (init 1) — O(n)
  • Minim si maxim printr-o singura parcurgere, init cu primul element — O(n)
  • Contor pentru numararea elementelor care respecta o conditie — O(n)
  • Divizorii unui numar si numararea lor — O(n), optimizabil la O(√n)
  • Test de primalitate pana la √n, cu justificarea perechilor de divizori — O(√n)
  • (Intensiv) acelasi test de primalitate in C++

Vrei mai mult?

Provocare: Pentru sirul [12, 45, 7, 23, 45, 8, 90], scrie un program care numara de cate ori apare valoarea maxima in sir (nu doar pozitia primei aparitii, ci de CATE ori se repeta acea valoare). Combina tiparul de maxim (atomul 3) cu tiparul de contor (atomul 4) intr-o singura parcurgere.

De ce: Testul de primalitate cu conditia d*d <= n (atomul 6) functioneaza si pentru numere care sunt patrate perfecte. Verifica manual pentru n = 49: la ce valoare a lui d se opreste bucla, si ramane variabila prim True sau False?

Deschidere: Testarea numerelor prime nu e doar un exercitiu de scoala: criptografia care securizeaza platile online (algoritmul RSA) se bazeaza pe numere prime foarte mari si pe cat de greu e sa le gasesti factorii. Testul O(radical din n) pe care l-ai invatat e varianta simpla; pentru numere de sute de cifre se folosesc algoritmi mult mai avansati, ca Miller-Rabin.

Urmatoarea lectie

Aplicam toate aceste tipare pe probleme reale de tip Bacalaureat: trasarea codului, determinarea rezultatului si completarea codului.

Continua →