Vedremo diverse tecniche per risolvere problemi:

  • Tecnica greedy
  • Divide et impera
  • Programmazione dinamica
  • Backtracking

Complessità

Un algoritmo si dice efficiente se la sua complessità è di ordine polinomiale nella dimensione dell’input ovvero di complessità per una qualche costante .

Lo diciamo invece inefficiente se superpolinomiale ovvero:

  • Esponenziale - Una funzione di ordine
  • Super Esponenziale - Una funzione che cresce più velocemente di un esponenziale ad esempio oppure
  • Sub Esponenziale - Una funzione che cresce più lentamente di un esponenziale ovvero ad esempio dove è una costante inferiore di 1. Oppure infatti basta scomporre la potenza per riottenere la parte a sinistra, scritta come a destra abbiamo un esponente più piccolo di .

I problemi più studiati sono quelli per cui non si conosce un algoritmo subesponenziale, ad esempio i problemi della fattorizzazione e dell’isomorfismo tra grafi sono molto noti e da tempo esistono algoritmi superpolinomiali con complessità .

Pensiamo ad esempio al test di primalità, l’algoritmo banale è esponenziale, questo perché ha complessità ma la lunghezza dell’input è dato che i numeri sono in binario. Infatti, riscriviamo in funzione di lunghezza in bit:

Quindi è esponenziale sulla grandezza in bit del numero infatti abbiamo un costo di , ad esempio se prendiamo 1024 dobbiamo si eseguire circa 1024 operazioni ma se guardiamo i bit abbiamo circa 10 bit e 1024 operazioni, aggiungendo un solo bit andiamo a 2048 operazioni mentre una soluzione ottimale dovrebbe essere polinomiale e quindi .

Anche se ci fermassimo prima nella ricerca dei divisori, fermandoci alla radice otteniamo comunque un costo esponenziale:


Per un problema ovviamente possono esistere diversi algoritmi, ad esempio per ordinare una lista di interi abbiamo algoritmi che vanno da complessità come fino a ridurci a . È importante quindi non confondere la complessità del problema con quella dell’algoritmo.

Un algoritmo di complessità fornisce una limitazione superiore alla complessità del problema, significa che per un grande numero il nostro algoritmo si comporta come . Se invece si dimostra che un qualsiasi algoritmo per un problema ha complessità si è stabilita una limitazione inferiore alla complessità del problema, significa che avremo almeno quel comportamento della funzione.

Se allora l’algoritmo è detto ottimo perché la sua complessità in ordine di grandezza risulta la migliore.

Calcolare le limitazioni inferiori non è semplice ed esistono pochi modi per dimostrare che queste sono vere, vediamone due:

  • Dimensione dei dati - Se un problema ha in ingresso dati e richiede di esaminarli tutti allora la limitazione inferiore è

  • Eventi Contabili - Se un problema richiede che un certo evento sia ripetuto almeno volte allora una limitazione inferiore alla complessità è . Ad esempio per gli algoritmi di ordinamento se abbiamo elementi è stato dimostrato che bisogna effettuare almeno confronti.


Un problema si dice intrattabile se non può avere algoritmi efficienti come ad esempio stampare tutte le permutazioni di elementi, il problema in questo caso ha un limite inferiore di e quindi non possiamo raggiungere la complessità polinomiale.

Ma perché consideriamo efficiente un algoritmo con complessità con una generica e non un numero fisso?

Tesi di Church-Turing - I modelli di calcolo realistici sono equivalenti dal punto di vista computazionale, se qualcosa è non calcolabile su una macchina lo resterà su qualunque altra.

Testi di Church-Turing estesa - I modelli di calcolo realistici sono tra loro polinomialmente correlati, quindi il concetto di trattabilità è indipendente dalla macchina.