Algoritmi Elementari

Lecția de față adună algoritmii elementari pe care se construiesc majoritatea problemelor de informatică din liceu și din concursuri: lucrul cu cifrele unui număr, divizibilitate, numere prime, CMMDC și șirul Fibonacci. Pentru fiecare algoritm găsești ideea din spatele lui, codul în C++ și observațiile care te scapă de greșelile clasice.

1. Spargerea unui număr în cifre

Aproape orice problemă cu cifrele unui număr se rezolvă cu același tipar: extragem ultima cifră cu n % 10, o prelucrăm, apoi „tăiem” ultima cifră cu n / 10. Repetăm cât timp numărul mai are cifre.

if (n == 0) prelucrare_caz_special();
while (n > 0)
{
    cif = n % 10;
    // ... prelucrăm cifra determinată, conform problemei
    n = n / 10;
}
Ideea-cheie: cifrele sunt obținute de la dreapta la stânga (de la unități spre cifra cea mai semnificativă). Dacă problema cere ordinea naturală, vezi varianta (d) de mai jos.
Atenție la cazul n = 0! Bucla while (n > 0) nu se execută deloc pentru 0, deși 0 are o cifră. Tratează cazul separat, înaintea buclei.

a) Numărul de cifre

nrcifre = 0;
if (n == 0) nrcifre = 1;
while (n > 0)
{
    nrcifre++;
    n = n / 10;
}

b) Prima cifră a numărului

Împărțim repetat la 10 până când rămâne o singură cifră — exact prima.

if (n == 0) primacif = 0;
while (n > 9)
    n = n / 10;
primacif = n;

c) Oglinditul unui număr

Oglinditul lui 12345 este 54321. Construim noul număr cifră cu cifră: la fiecare pas, mutăm cifrele deja adunate cu o poziție la stânga (înmulțire cu 10) și adăugăm cifra curentă.

ogl = 0;
while (n > 0)
{
    cif = n % 10;
    ogl = ogl * 10 + cif;
    n = n / 10;
}
De reținut: verificarea „n este palindrom” se face comparând n (copiat înainte!) cu oglinditul său.

d) Generarea cifrelor în ordinea din număr

Dacă vrem cifrele de la stânga la dreapta, calculăm mai întâi puterea lui 10 corespunzătoare primei cifre, apoi extragem pe rând cifra cea mai semnificativă.

p = 1;
while (p * 10 <= n)
    p = p * 10;

while (n > 0)
{
    cif = n / p;
    // prelucrăm cifra cif
    n = n % p;
    p = p / 10;
}
Cazul n = 0: ca și la tiparul de bază, bucla nu se execută deloc pentru 0, deși acesta are cifra 0. Tratează cazul separat, înaintea algoritmului: if (n == 0) prelucrează cifra 0;

e) Numărarea aparițiilor cifrei k

nrap = 0;
if (n == 0 && k == 0) nrap = 1;
while (n > 0)
{
    cif = n % 10;
    if (cif == k) nrap++;
    n = n / 10;
}

f) Eliminarea cifrelor pare

Reconstruim numărul doar din cifrele impare. Pentru că parcurgem cifrele de la dreapta la stânga, le lipim la noul număr tot „de la dreapta”, folosind o putere a lui 10 care crește doar când păstrăm o cifră — astfel ordinea inițială se păstrează.

p = 1; nr = 0;
while (n > 0)
{
    cif = n % 10;
    if (cif % 2 != 0)
    {
        nr = nr + cif * p;
        p = p * 10;
    }
    n = n / 10;
}

Două proprietăți utile despre divizori

  • n are un număr impar de divizori ⇔ n este pătrat perfect: sqrt(n) == (int)sqrt(n)
  • n are exact 3 divizori ⇔ n este pătratul unui număr prim: sqrt(n) trebuie să fie întreg și număr prim.

2. Verificarea dacă un număr este prim

Un număr n ≥ 2 este prim dacă nu are niciun divizor între 2 și √n. E suficient să mergem până la √n, deoarece divizorii apar în perechi (d, n/d): dacă n ar avea un divizor mai mare decât √n, ar avea automat și unul mai mic.

if (n < 2) prim = 0;
else
{
    prim = 1;                       // presupunem că n este prim
    for (d = 2; d * d <= n; d++)
        if (n % d == 0) { prim = 0; break; }

    if (prim == 1)
    {
        // ... operațiile de efectuat când n este prim
    }
}
Greșeli frecvente: uitarea cazurilor n = 0 și n = 1 (nu sunt prime!) și scrierea condiției d <= sqrt(n) cu numere reale — preferă d * d <= n, care lucrează doar cu întregi.

3. Cel mai mare divizor comun (CMMDC) — algoritmul lui Euclid

Înlocuim repetat perechea (a, b) cu (b, a % b) până când restul devine 0. Ultimul împărțitor nenul este CMMDC-ul.

rest = a % b;
while (rest != 0)
{
    a = b;
    b = rest;
    rest = a % b;
}
cmmdc = b;
Atenție la CMMMC! Cel mai mic multiplu comun se calculează cu formula cmmmc = (ca * cb) / cmmdc, unde ca și cb sunt copii ale valorilor inițiale, salvate înainte de a aplica algoritmul lui Euclid (care modifică a și b).
Cazul b = 0: varianta de mai sus calculează a % b încă dinaintea buclei, deci pentru b = 0 se produce o împărțire la zero. O variantă mai robustă mută testul pe b și funcționează pentru orice pereche cu cel puțin o valoare nenulă (prin convenție, cmmdc(a, 0) = a):
while (b != 0)
{
    rest = a % b;
    a = b;
    b = rest;
}
cmmdc = a;

4. Divizorii proprii ai lui n

Divizorii proprii sunt divizorii diferiți de 1 și de n. Cel mai mare divizor propriu posibil este n/2, deci parcurgem doar până acolo.

for (d = 2; d <= n / 2; d++)
    if (n % d == 0)
    {
        // operații cu divizorul propriu d
        // s = s + d;   — suma divizorilor proprii
        // nrd++;       — numărul divizorilor proprii
    }

5. Câte numere din [a, b] sunt divizibile cu k?

Nu este nevoie de nicio buclă — doar de puțină aritmetică:

  • Numerele ≤ n divizibile cu k: n / k (împărțire întreagă)
  • Numerele din [a, b] divizibile cu k: b / k − (a − 1) / k
  • Multiplii comuni ai lui a și b din [1, n]: n / cmmmc(a, b)
  • Multiplii lui a care nu sunt multipli de b, din [1, n]: n / a − n / cmmmc(a, b)
De ce funcționează: scădem din numerele divizibile cu k aflate până la b pe cele aflate strict înainte de a — adică până la a − 1.

6. Descompunerea în factori primi

Încercăm pe rând divizorii d = 2, 3, 4, … și, pentru fiecare, împărțim numărul cât timp se mai poate, numărând puterea m. Deși d parcurge și numere compuse, ele nu mai divid n la momentul respectiv (factorii lor primi au fost deja eliminați), deci doar divizorii primi sunt prelucrați efectiv.

d = 2;
while (d * d <= n)
{
    m = 0;
    while (n % d == 0)
    {
        m++;
        n = n / d;
    }
    if (m > 0)
    {
        // operații cu divizorul prim d, aflat la puterea m
    }
    d++;
}
if (n > 1)
{
    // n rămas este ultimul divizor prim, la puterea 1
}
Nu uita verificarea finală! Dacă după buclă n > 1, valoarea rămasă este un factor prim mai mare decât √(valoarea inițială) și apare la puterea 1. De exemplu, pentru n = 2 · 13, după eliminarea lui 2 rămâne 13, care trebuie prelucrat separat.

7. Numărul divizorilor lui n — formula lui Euler

Dacă N = p₁m₁ · p₂m₂ · … · pkmk, atunci numărul total de divizori este:

Nrdiv = (m₁ + 1)(m₂ + 1)…(mk + 1)

Intuiția: un divizor se formează alegând pentru fiecare factor prim pi o putere între 0 și mi — adică mi + 1 variante.

d = 2; nrdiv = 1;
while (d * d <= n)
{
    m = 0;
    while (n % d == 0)
    {
        m++;
        n = n / d;
    }
    nrdiv = nrdiv * (m + 1);
    d++;
}
if (n > 1) nrdiv = nrdiv * 2;   // ultimul factor prim, la puterea 1: (1+1) = 2

8. Cel mai mare număr Fibonacci ≤ n

Generăm termenii șirului 1, 1, 2, 3, 5, 8, … păstrând mereu ultimii doi (f0 și f1) și ne oprim înainte ca suma lor să depășească n.

fin >> n;
f0 = f1 = 1;
while (f0 + f1 <= n)
{
    f2 = f0 + f1;
    f0 = f1;
    f1 = f2;
}
// f1 este cel mai mare termen Fibonacci <= n
Cazul n = 0: șirul folosit aici începe cu 1, 1, 2, …, deci pentru n = 0 nu există niciun termen ≤ n. Bucla nu se execută, iar f1 rămâne 1 — un răspuns greșit dacă îl prelucrăm orbește. Tratează cazul separat, înaintea algoritmului (de exemplu, afișează un mesaj sau, dacă enunțul consideră F(0) = 0 ca termen al șirului, răspunsul este 0).

9. Cifra de control a lui n

Cifra de control se obține adunând repetat cifrele numărului până rămâne o singură cifră. Nu este nevoie de bucle: rezultatul coincide cu restul împărțirii la 9 (cu două cazuri speciale).

if (n == 0) cifc = 0;
else if (n % 9 == 0) cifc = 9;
else cifc = n % 9;

10. Ultima cifră a lui xy

Ultimele cifre ale puterilor se repetă periodic, dar perioada depinde de ultima cifră a lui x: cifrele 0, 1, 5 și 6 au perioada 1 (de exemplu, 6, 36, 216, … se termină mereu în 6), cifrele 4 și 9 au perioada 2 (4, 16, 64, … alternează 4, 6, 4, 6), iar cifrele 2, 3, 7 și 8 au perioada 4 (puterile lui 2 se termină în 2, 4, 8, 6, 2, 4, 8, 6, …).

De ce merge totuși y % 4 pentru toate cifrele: toate perioadele posibile (1, 2 și 4) sunt divizori ai lui 4. Prin urmare, pentru orice y ≥ 1, ultima cifră a lui xy coincide cu ultima cifră a lui cr, unde c este ultima cifră a lui x, iar r = y % 4 (cu r = 4 atunci când y % 4 = 0). Algoritmul de mai jos nu presupune că perioada este 4 — doar că ea divide 4.
c = x % 10;
if (y % 4 == 1) ucif = c;
else if (y % 4 == 2) ucif = (c * c) % 10;
else if (y % 4 == 3) ucif = (c * c * c) % 10;
else if (y % 4 == 0) ucif = (c * c * c * c) % 10;   // y multiplu de 4
Cazuri particulare: formula presupune x > 0 și y ≥ 1. Pentru y = 0, rezultatul este 1 (orice număr nenul la puterea 0), caz care trebuie tratat separat, înaintea algoritmului.

11. Citirea pe rând a n numere

Când avem multe numere de prelucrat, nu le memorăm pe toate: citim câte unul și îl prelucrăm pe loc, refolosind aceeași variabilă.

fin >> n;
for (i = 1; i <= n; i++)
{
    fin >> a;
    // prelucrăm a cu oricare dintre algoritmii de mai sus
}

12. Prelucrarea perechilor de numere consecutive

Pentru perechi de tipul (primul, al doilea), (al doilea, al treilea) etc., păstrăm mereu valoarea anterioară în a și pe cea curentă în b, iar la final mutăm b în a.

fin >> n;
fin >> a;
for (i = 2; i <= n; i++)
{
    fin >> b;
    // prelucrăm perechea (a, b)
    a = b;
}

13. Minimul / maximul a n numere citite pe rând

Presupunem că primul element este minimul, apoi comparăm fiecare număr nou cu minimul curent și îl actualizăm dacă găsim ceva mai mic.

fin >> n;
fin >> a;
min = a;                        // presupunem că minimul este primul element
for (i = 2; i <= n; i++)
{
    fin >> a;
    if (a < min) min = a;       // pentru maxim: if (a > max) max = a;
}
Greșeala clasică: inițializarea minimului cu 0 sau cu o constantă „mare”. Dacă toate numerele sunt pozitive, min = 0 nu va fi niciodată actualizat corect. Inițializează întotdeauna cu primul element citit.

Recapitulare și exerciții propuse

  1. Verificați dacă un număr citit de la tastatură este palindrom (egal cu oglinditul său).
  2. Afișați suma cifrelor pare și produsul cifrelor impare ale unui număr.
  3. Citiți n numere și afișați câte dintre ele sunt prime.
  4. Determinați CMMDC-ul și CMMMC-ul a două numere citite.
  5. Afișați descompunerea în factori primi a unui număr sub forma n = p1^m1 * p2^m2 * ...
  6. Câte numere din intervalul [a, b] sunt divizibile cu 3, dar nu și cu 5?
  7. Citiți n numere și afișați cea mai mare diferență dintre două numere citite consecutiv.