În acest laborator vom explora comportamentul probabilist al hashing-ului și fenomenele care apar din cauza coliziunilor. Vom lucra pe două exemple simple care ilustrează concepte teoretice importante:

  • Paradoxul zilelor de naștere — probabilitatea unei coliziuni într-un spațiu finit de valori;
  • Problema colecționarului de cupoane — timpul așteptat până acoperim toate cazurile posibile.

Context

În hashing, fiecare element este atribuit aleatoriu unui dintre cele ( k ) sloturi. Problemele pe care le studiem astăzi au aplicații directe în analiza performanței tabelelor hash.


Exemplul 1: Paradoxul zilelor de naștere

Scenariu: avem ( n ) persoane și ( k = 365 ) zile posibile. Dorim să aflăm probabilitatea ca cel puțin două persoane să aibă aceeași zi de naștere.

Formula teoretică este:

$$ P(\text{coliziune}) = 1 - \frac{k!}{(k-n)! \, k^n} $$

Codul de mai jos simulează fenomenul:

paradoxul_nasterilor.py
import random
 
def paradox_zile_nastere(trials=10000, grup=23, zile=365):
    """Estimează probabilitatea ca două persoane să aibă aceeași zi de naștere."""
    count = 0
    for _ in range(trials):
        zile_aleatoare = [random.randint(1, zile) for _ in range(grup)]
        if len(zile_aleatoare) != len(set(zile_aleatoare)):
            count += 1
    return count / trials
 
for n in [10, 20, 23, 30, 40, 50]:
    p = paradox_zile_nastere(grup=n)
    print(f"Grup de {n} persoane → Probabilitate ≈ {p:.3f}")

Întrebări:

1. Pentru ce valoare a lui ( n ) probabilitatea depășește 0.5?
2. Cum se aseamănă acest fenomen cu coliziunile dintr-o tabelă hash?

Exemplul 2: Problema colecționarului de cupoane

Scenariu: avem ( n ) cupoane distincte. La fiecare pas tragem unul aleator (uniform). Dorim să aflăm câte extrageri sunt necesare în medie până le avem pe toate.

Rezultatul teoretic: $$ E[T] = n H_n = n(1 + \tfrac{1}{2} + \tfrac{1}{3} + \dots + \tfrac{1}{n}) $$

Simulare:

cupoane.py
import random
 
def colector_cupoane(n, incercari=500):
    """Returnează numărul mediu de extrageri până la colectarea tuturor cupoanelor."""
    total = 0
    for _ in range(incercari):
        colectate = set()
        pasi = 0
        while len(colectate) < n:
            colectate.add(random.randint(0, n-1))
            pasi += 1
        total += pasi
    return total / incercari
 
for n in [10, 50, 100]:
    estimat = colector_cupoane(n)
    print(f"n = {n}: medie ≈ {estimat:.2f}")

Întrebări:

1. Cum crește numărul așteptat de extrageri odată cu ( n )?
2. Ce analogie există între această problemă și procesul de „umplere” a tuturor sloturilor într-o tabelă hash?

Rezumat

  • Hashing-ul se poate analiza folosind concepte probabiliste simple.
  • Paradoxul zilelor de naștere arată cât de rapid apar coliziunile.
  • Problema colecționarului de cupoane arată cât de lent se „umple” complet un spațiu finit.