Invatare Atomica

Cautarea binara

Progres lectie:
0%
⚠ Nota programa: Cautarea binara si prelucrarea listelor ordonate sunt incluse explicit in curricula cls. X mat-info (Domeniu 2 — Algoritmi specializati pe clase de probleme). Modulul curent este plasat in arborele cls. XII din motive de organizare a site-ului; continutul este conform programei cls. X si poate fi studiat anticipat sau ca recapitulare.
🎯

Obiectivul lectiei

Vei intelege de ce cautarea binara este de pana la 50.000 de ori mai rapida decat cautarea liniara, vei implementa corect algoritmul in C++ si Python si vei stii sa aplici variantele cerute la BAC.

Dupa aceasta lectie vei putea:

  • Sa enunti conditia obligatorie pentru cautarea binara (sirul sortat)
  • Sa implementezi cautarea binara iterativ in Python si C++
  • Sa demonstrezi complexitatea O(log n) prin numararea pasilor
  • Sa aplici variantele lower_bound si upper_bound
  • Sa rezolvi probleme de tip BAC cu cautare binara

Incearca singur!

Provocare:

Ai sirul sortat: [2, 5, 8, 12, 16, 23, 38, 56, 72, 91] (10 elemente). Cauti 23. Cate comparatii faci verificand element cu element? Dar injumatatind intervalul la fiecare pas?

💡 Ai nevoie de un indiciu?

Cautare liniara: 2, 5, 8, 12, 16, 23 — ai verificat 6 elemente.

Cautare binara: mijloc=v[4]=16 (prea mic), mijloc=v[7]=56 (prea mare), mijloc=v[5]=23 (gasit!) — doar 3 comparatii.

La fiecare pas eliminam jumatate din candidati.

1

1. Conditia si intuitia: de ce injumatatim?

Cautarea binara (binary search) gaseste un element intr-un sir sortat in timp O(log n), comparand cu elementul din mijlocul intervalului curent si eliminand la fiecare pas jumatatea imposibila.
Analogie: Dictionar tiparit

Cand cauti cuvantul "masina" intr-un dictionar de 1000 de pagini, nu citesti pagina 1, 2, 3... Deschizi la pagina 500, vezi "L", stii ca "M" e dupa — deschizi la 750 etc. Asta e cautarea binara: exploatezi ordinea pentru a elimina jumatati.

De ce trebuie sortat?

La pasul "mijloc=56 > 23, deci 23 nu poate fi dupa pozitia 7" — acest rationament e valid NUMAI daca sirul e sortat crescator. Pe un sir nesortat, 23 ar putea fi oriunde.

2

2. Algoritmul pas cu pas: traseul complet

Invariant: elementul cautat, daca exista, se afla mereu in [stanga, dreapta]. La fiecare pas calculam mijloc, comparam si reducem intervalul.
Trace: cautam target=23 in v=[2,5,8,12,16,23,38,56,72,91]
stanga=0, dreapta=9
  Pas 1: mijloc=(0+9)/2=4, v[4]=16 < 23  ->  stanga=5
  Pas 2: mijloc=(5+9)/2=7, v[7]=56 > 23  ->  dreapta=6
  Pas 3: mijloc=(5+6)/2=5, v[5]=23 == 23 ->  GASIT la index 5!

Numar de comparatii: 3  (cautare liniara ar fi facut 6)
// Pseudocod cautare binara
stanga ← 0 ; dreapta ← n - 1
cat timp stanga <= dreapta executa
    mijloc ← (stanga + dreapta) / 2
    daca v[mijloc] = target atunci returneaza mijloc
    altfel daca v[mijloc] < target atunci stanga ← mijloc + 1
    altfel dreapta ← mijloc - 1
returneaza -1
3

3. Implementare in Python

# Cautare binara - returneaza indexul sau -1
def cautare_binara(v, n, target):
    stanga = 0
    dreapta = n - 1
    while stanga <= dreapta:
        mijloc = (stanga + dreapta) // 2
        if v[mijloc] == target:
            return mijloc
        elif v[mijloc] < target:
            stanga = mijloc + 1
        else:
            dreapta = mijloc - 1
    return -1

v = [2, 5, 8, 12, 16, 23, 38, 56, 72, 91]
n = len(v)
print(cautare_binara(v, n, 23))  # 5
print(cautare_binara(v, n, 10))  # -1
Output real (rulat cu python):
5
-1
4

4. Complexitatea O(log n)

La fiecare pas, intervalul se injumatateste. Dupa k pasi, intervalul are n/2^k elemente. Cand n/2^k < 1 (adica k > log2(n)), am terminat. Numarul de pasi este O(log n).
Comparatie: cautare liniara O(n) vs binara O(log n)
n =        10:  liniar=10 pasi,   binar~ 4 pasi
n =       100:  liniar=100 pasi,  binar~ 7 pasi
n =      1000:  liniar=1000 pasi, binar~10 pasi
n = 1.000.000:  liniar=1M pasi,   binar~20 pasi
Calcul real (python, import math):
log2(10) = 3.3219, max_pasi = 4

La BAC, pentru analiza eficientei: T(n) = O(log n), mai eficienta decat cautarea liniara O(n).

5

5. Implementare in C++ EXCLUSIV INTENSIV

⚡ Sectiune doar pentru intensiv informatica

La profilul intensiv se cere implementarea in C++. Diferente fata de Python: tipuri declarate explicit (int, vector<int>), formula anti-overflow pentru mijloc, -1 ca valoare santinela.

#include <iostream>
#include <vector>
using namespace std;

// Returneaza indexul lui target sau -1 daca nu exista
int cautare_binara(vector<int>& v, int target) {
    int stanga = 0;
    int dreapta = (int)v.size() - 1;
    while (stanga <= dreapta) {
        int mijloc = stanga + (dreapta - stanga) / 2;  // anti-overflow
        if (v[mijloc] == target) return mijloc;
        else if (v[mijloc] < target) stanga = mijloc + 1;
        else dreapta = mijloc - 1;
    }
    return -1;
}

int main() {
    vector<int> v = {2, 5, 8, 12, 16, 23, 38, 56, 72, 91};
    cout << cautare_binara(v, 23) << endl;  // 5
    cout << cautare_binara(v, 10) << endl;  // -1
    return 0;
}
Output real (compilat g++ -std=c++17, rulat):
5
-1
6

6. Variante: lower_bound si upper_bound EXCLUSIV INTENSIV

⚡ Sectiune avansata - intensiv informatica

In competitii si BAC la profil intensiv apar variantele lower_bound si upper_bound, esentiale pentru a numara aparitii sau a gasi intervale in siruri sortate.

lower_bound(target): primul indice i cu v[i] >= target.
upper_bound(target): primul indice i cu v[i] > target.
Numarul de aparitii ale lui target = upper_bound(t) - lower_bound(t).
# lower_bound: primul indice >= target
def lower_bound(v, n, t):
    s, d = 0, n
    while s < d:
        m = (s + d) // 2
        if v[m] < t: s = m + 1
        else: d = m
    return s
v2 = [1, 3, 3, 5, 7, 9, 9, 11]
print(lower_bound(v2, 8, 3))   # 1
print(lower_bound(v2, 8, 4))   # 3
print(lower_bound(v2, 8, 12))  # 8
Output real (rulat cu python):
1
3
8
C++ lower_bound manual (EXCLUSIV INTENSIV) - output real g++ -std=c++17: lb(3)=1, lb(4)=3, lb(12)=8
int lb(vector<int>& v, int t) {
    int s = 0, d = (int)v.size();
    while(s < d) {
        int m = s + (d-s) / 2;
        if(v[m] < t) s = m + 1; else d = m;
    }
    return s;
}
// Echivalent STL: lower_bound(v.begin(), v.end(), t) - v.begin()

Exercitii practice

Exercitiul 1 (Nivel minim) - Traseaza algoritmul

Fie sirul sortat v = [3, 7, 10, 15, 22, 30, 45, 60] (n=8). Aplica manual cautarea binara pentru target = 15. Scrie valorile lui stanga, dreapta si mijloc la fiecare pas si numarul total de comparatii efectuate.

Vezi rezolvarea

Sirul este v = [3, 7, 10, 15, 22, 30, 45, 60], cu indicii 0..7. Aplicam algoritmul din lectie: stanga=0, dreapta=n-1=7.

  1. Pasul 1: stanga=0, dreapta=7, mijloc=(0+7)/2=3 (impartire intreaga). v[3]=15, exact targetul cautat (15). GASIT la indexul 3.

Numar total de comparatii: 1. Cautarea s-a incheiat din primul pas pentru ca 15 se afla chiar la mijlocul sirului, pozitia (0+7)/2=3. Daca targetul ar fi fost alt numar, s-ar fi continuat prin injumatatirea intervalului [stanga, dreapta] (ca in ATOM 2 din lectie) pana la gasire sau pana cand stanga > dreapta.

Exercitiul 2 (Nivel standard) - Implementare si analiza

Implementeaza o functie care numara de cate ori apare valoarea x intr-un sir sortat, folosind lower_bound si upper_bound. Testeaza pe v = [1, 2, 2, 2, 3, 5, 5, 8]: x=2 (raspuns: 3), x=4 (raspuns: 0). Calculeaza complexitatea solutiei.

Vezi rezolvarea

Refolosim lower_bound din lectie (ATOM 6) si construim upper_bound la fel, dar cu conditia v[m] <= t in loc de v[m] < t (cautam prima pozitie STRICT mai mare ca t).

Numarul de aparitii ale lui x este upper_bound(x) - lower_bound(x).

  1. Pe v = [1, 2, 2, 2, 3, 5, 5, 8], pentru x=2: lower_bound(2)=1 (primul indice cu v[i]>=2), upper_bound(2)=4 (primul indice cu v[i]>2). Aparitii = 4-1 = 3.
  2. Pentru x=4: lower_bound(4)=5 si upper_bound(4)=5 (4 nu exista in sir, insa ambele functii se opresc la aceeasi pozitie de inserare). Aparitii = 5-5 = 0.

Complexitate: fiecare apel e O(log n), ca la cautarea binara clasica (ATOM 4), deci solutia completa ramane O(log n).

Exercitiul 3 (Nivel performanta) - Cautare binara pe raspuns INTENSIV

Ai N lemne de lungimi date. Trebuie sa obtii exact K bucati de lungime egala. Gaseste lungimea maxima L posibila. Proprietate monotona: daca L e posibila, orice L' < L e si ea posibila. Aplica cautare binara pe raspuns L in [1, max(lungimi)]. Implementeaza si calculeaza T(n, L_max).

Vezi rezolvarea

Aceasta e cautarea binara PE RASPUNS, nu pe un sir - o generalizare care nu apare explicit in lectie (lectia preda doar cautarea pe sir sortat, ATOM 1-4, si lower_bound/upper_bound, ATOM 6). Ideea de baza — stanga, dreapta, mijloc, injumatatire — e insa aceeasi.

Structura de rezolvare:

  1. Scrie fezabil(L): pentru L dat, suma de lungime_i // L pe toate lemnele (impartire intreaga); returneaza true daca suma >= K.
  2. Cauta binar in [1, max(lungimi)]: stanga=1, dreapta=max(lungimi). Cat timp stanga<=dreapta: mijloc=(stanga+dreapta)/2; daca fezabil(mijloc), retine raspunsul si stanga=mijloc+1; altfel dreapta=mijloc-1.
  3. Complexitate: O(N log(max_lungime)) — fezabil() e O(N), cautarea are O(log(max_lungime)) pasi.

Capcane: L porneste de la 1, nu 0; impartirea e in jos, nu rotunjita; daca nicio lungime nu ajunge la K bucati, raspunsul e 0/imposibil. Evaluare: corectitudinea lui fezabil(), marginile alese, cazul imposibil, complexitatea justificata.

Ce ai invatat astazi

  • Conditia necesara: sirul trebuie sa fie sortat
  • Algoritmul: injumatatim intervalul [stanga, dreapta] la fiecare pas
  • Complexitate: O(log n) — pe 1.000.000 elemente: max 20 pasi
  • Formula anti-overflow in C++: mijloc = stanga + (dreapta - stanga) / 2
  • Variante: lower_bound (primul >= target), upper_bound (primul > target)
  • Aplicatie avansata: cautare binara pe raspuns (functie cu proprietate monotona)

Vrei mai mult?

  • Provocare: implementeaza upper_bound (dupa modelul lui lower_bound din atomul 6) si foloseste ambele functii ca sa numeri de cate ori apare fiecare valoare distincta in v2 = [1,3,3,5,7,9,9,11], fara sa parcurgi sirul liniar. Verifica manual pentru valoarea 9.
  • De gandit: cautarea binara pe raspuns (exercitiul 3) nu cauta o valoare intr-un sir, ci un raspuns intr-un interval numeric. Ce anume trebuie sa fie adevarat despre o problema — ce fel de proprietate a functiei — ca sa poti aplica injumatatirea si aici, desi nu exista niciun sir sortat propriu-zis?
  • Deschidere: baza de date din spatele oricarui site cu milioane de inregistrari (un index dintr-o baza SQL, cautarea unui cuvant intr-un dictionar electronic) foloseste variante ale cautarii binare pentru a gasi o inregistrare fara sa citeasca tot tabelul. Diferenta dintre O(log n) si O(n) e motivul pentru care o cautare pe un site cu un milion de produse dureaza milisecunde, nu secunde.

Urmatoarea lectie

Continua cu sortarile eficiente: Merge Sort si Quick Sort, ambele cu complexitate O(n log n).

Continua →