Definizione:
Sia M una MdT deterministica che si ferma su ogni possibile input.
La complessità di tempo (o tempo di esecuzione) di M è la funzione f : N → N, dove f(n) è il numero massimo di passi che M compie su un input di lunghezza n.
Se f(n) è la complessità di tempo di M, allora si dice che M lavora in tempo f(n), o che M è una macchina di tempo f(n).
Il tempo di esecuzione di un algoritmo è spesso dato una espressione, talvolta complessa, che dipende dalla particolare implementazione realizzata e dal particolare modello usato, la cui diversa scelta si concretizza nell’avere fattori moltiplicativi costanti diversi nel calcolo della complessità.
Per questo (ed altri motivi), solitamente si preferisce stimare la complessità di tempo per valori molto grandi, trascurando i fattori costanti. Questa stima della complessità è chiamata analisi asintotica.
Se il tempo di esecuzione di un algoritmo è f(n) = 3n3+4n2 (dove n è la lunghezza dell’input), per valori di n molto grandi, il primo termine domina sul secondo. Inoltre, dovendo tralasciare i valori costanti, possiamo affermare che questo algoritmo cresce nell’ordine di n3 e indichiamo questo con la notazione del grande O, scrivendo f(n) = O(n3).
La notazione asintotica è stata ampiamente studiata in corsi propedeutici a questo corso e non verrà trattata qui in dettaglio.
Quello che è utile ricordare è che un algoritmo O(nc) è detto polinomiale, e un algoritmo 2O(n) è detto esponenziale.
Analizziamo asintoticamente il seguente algoritmo M per decidere il linguaggio A={0k1k|k≥0}.
Su una stringa w in input, M si comporta come segue:
Per analizzare M consideriamo ogni passo separatamente.
Al passo 1:
Nei passi 2 e 3:
Al passo 4:
Dunque, il tempo totale di M1 su input di lunghezza n è O(n)+O(n2)+O(n). Cioè, O(n2).
Utilizziamo la stessa notazione per definire la classe di linguaggi per misurare il tempo.
Definizione:
Per una funzione f: N→ R+, si definisce classe di complessità di tempo, TIME(f(n)), l’insieme di tutti i linguaggi che sono decisi in tempo O(f(n)) da una Macchina di Turing.
Quindi rifacendoci alla definizione di notazione che abbiamo definito nell’esempio precedente possiamo dire che il linguaggio A ∈ TIME(n2) poichè M1 decide A in tempo O(n2) e TIME(n2) contiene tutti i linguaggi che possono essere decisi in tempo O(n2).
Esiste una macchina che asintoticamente decide A più velocemente?
Possiamo migliorare il tempo di elaborazione cancellando due 0 e due 1 ad ogni scansione in modo da ridurre il numero di scansioni di metà. Ma migliorare solo il tempo di elaborazione con un fattore di 2 non ha effetto asintoticamente sul tempo di elaborazione. La seguente macchina M2, usa un metodo differente per decidere A che risulta asintoticamente migliore. Mostriamo che A ∈ TIME(nlogn).
M2=”Su input w stringa:
Prima di analizzare il funzionamento di M2, verifichiamo se A decide il linguaggio.
Ad ogni scansione effettuata al passo 4, il numero totale di 0 rimanenti è ridotto di metà e qualsiasi altra cosa rimasta viene scartata. In questo modo se partiamo con tredici 0, dopo la singola esecuzione del passo 4 rimangono solo sei 0. Questo stage ha lo stesso effetto sul numero di 1.
Ora esaminiamo la parità pari/dispari del numero di 0 e il numero di 1 ad ogni esecuzione dello stage 3. Supponiamo di partire sempre con tredici 0 e tredici 1. La prima esecuzione dello stage 3 trova un numero dispari di 0 e un numero dispari di 1. Dalla sottosequenza di esecuzioni, otteniamo un numero pari (6), poi un numero dispari (3), e in seguito un numero dispari (1). Non eseguiamo questo stage su 0 o su 1 perché la condizione di ripetizione del loop è specificata allo stage 2.
Per la sequenza di parità trovata (dispari, pari, dispari, dispari) se sostituiamo le parità con gli 0 e le disparità con gli 1 e quindi invertiamo la sequenza, otteniamo 1101, che è la rappresentazione binaria di 13, o del numero di 0 e di 1 che avevamo inizialmente. La sequenza di parità prende sempre il contrario della rappresentazione binaria.
Quando lo stage 3 effettua la verifica per determinare che il numero totale di 0 e 1 rimanenti è pari, verifica la corrispondenza tra la parità di 0 e la parità di 1. Se tutte le parità corrispondono, la rappresentazione binaria del numero di 0 e di 1 corrisponde, quindi i due numeri sono uguali.
Per analizzare il tempo di elaborazione di M2, prima osserviamo che ogni stage è eseguito in tempo O(n). In seguito determiniamo il numero di volte che viene fatta un esecuzione. Lo stage 1 e 5 sono eseguiti una solo volta, quindi con un tempo totale di O(n). Lo stage 4, saltando la metà degli 0 e degli 1 esegue al massimo 1+log2n iterazioni di ripetizione del loop. Quindi il tempo totale dello stage 2,3,e 4 è (1+log2n)O(n) o anche O(nlogn).
In conclusione il tempo di elaborazione di M2 è:
O(n)+O(nlogn)=O(nlogn)
Teorema
Sia M una MdT multinastro deterministica con tempo di esecuzione O(t(n)). La MdT a singolo nastro deterministica corrispondente ha un tempo di esecuzione O(t2(n)).
La prova si basa sulle costruzioni viste nelle lezioni precedenti. I dettagli sono lasciati per esercizio agli studenti.
Il “running time” o la complessità di tempo di una MdT M deterministica (che si ferma sempre) è la funzione f: N → N, dove f(n) è il massimo numero di operazioni che M esegue su ogni input di lunghezza n.
Se f(n) è il running time di M, allora si dice che M lavora in tempo f(n).
L’analisi asintotica di un algoritmo permette di valutarne la sua effettiva complessità pur tralasciando costanti e termini di basso ordine.
Sia t: N → N una funzione. La classe di complessità di tempo TIME(t(n)) è la collezione dei linguaggi decidibili in tempo 0(t(n)) da una MdT deterministica.
Una complessità nc per un algoritmo (con c>0) è chiamata polinomiale. In particolare con c=1 tale complessità è chiamata lineare.
Una complessità della forma 2O(nc) è chiamata esponenziale.
Ritorniamo sull’esempio del linguaggio L={0n 1n | n>0}
Abbiamo visto che una MdT M può accettare L scandendo avanti e indietro il nastro, accorciando l’input di una unità ad ogni inversione. In questo caso, le operazioni da compiere sono n + (n-1) + (n-2) + …. Dunque M ha tempo O(n2) e si può concludere che L è in TIME(n2).
La seguente macchina di Turing M1 impiega invece tempo O(n log n)
Si noti che ad ogni scorrimento del nastro, M1 dimezza il numero di 0 e di 1.
Dunque, L è in TIME(n log n) e questo migliora l’upper bound precedente
É possibile mostrare che questo risultato non può essere ulteriormente migliorato su una macchina di Turing deterministica a nastro singolo.
Si noti che una MdT M a due nastri può accettare L in tempo lineare
Si noti anche che nella teoria della calcolabilità, MdT con numero differente di nastri e MdT deterministiche e non deterministiche sono tutte equivalenti. Nella teoria della complessità questo non è vero.
Difatti, le MdT deterministiche possono richiedono tempo esponenziale rispetto a quelle nondeterministiche e la simulazione può richiedere tempo O(n log n).
Sia N una MdT nondeterministica che è un decisore. La complessità di tempo di N è una fuzione f: N->N dove f(n) è il massimo numero di passi che N usa su ogni ramo della sua computazione su input di lunghezza n come mostrato nella figura a lato.
Teorema
Sia M una MdT nondeterministica con tempo di esecuzione O(t(n)). La MdT deterministica corrispondente ha un tempo di esecuzione 2O(t(n)).
La prova si basa sulle costruzioni viste nelle lezioni precedenti. I dettagli sono lasciati per esercizio agli studenti.
P è la classe dei linguaggi che sono decisi in tempo polinomiale da una macchina di Turing deterministica a singolo nastro. In altre parole
P = Uk TIME(nk).
Osservazioni su P:
Quando ci interessiamo della polinomialità, gli algoritmi possono essere descritti ad alto livello senza riferirsi a particolari caratteristiche del modello utilizzato. In questo modo possiamo evitare dettagli tediosi sul natro o sul movimento della testina.
Possiamo descrivere gli algoritmi con stage numerate, dove in genere uno stage è analogo a un passo della macchina di Turing anche se implementare uno stage in genere richiede molti passi della TM.
Usando l’analisi asintotica possiamo trascurare queste differenze.
Per vedere che un algoritmo può essere eseguito in tempo polinomiale è sufficiente mostrare che:
La codifica degli oggetti deve essere “ragionevole”, per esempio codificare 12 con 111111111111 non è ragionevole perché questa codifica è esponenziale rispetto alla codifica binaria.
Tra le codifiche ragionevoli per i grafi c’è quella che usa la matrice di adiacenza. Visto che la matrice di adiacenza diferisce in modo polinomiale dal numero di nodi(O(N2)) si può vedere se l’algoritmo è polinomiale rispetto al numero dei nodi piuttosto che rispetto alla matrice.
Il problema di verificare se un grafo ammette un percorso appartiene alla classe P. [Esercizio per gli studenti].
Un altro problema in P è quello dei primi relativi. Due numeri sono primi relativi se il loro massimo comun divisore è 1.
Un algoritmo brute force per il problema dei primi relativi fornisce una complessità esponenziale (si controlla ogni singolo numero da 2 al minimo dei due numeri per vedere che non divide entrambi).
L’algoritmo di Euclide invece richiede tempo polinomiale.
Sia RELPRIME{ : x e y sono primi relativi}
L’algoritmo di Euclide può essere calcolato dalla seguente macchina E: su input , con x e y numeri naturali in binario E si comporta come segue:
1. ripeti finche y == 0:
2. Assegna a x = x mod y.
3. scambia x e y.
4. restituisci x.”
Mostriamo adesso una macchina R in grado di risolvere RELPRlME in tempo polinomiale, usando E come subroutine.
R = “su input , con x e y naturali in binario:
1. Se necessario, scambia x e y in modo che risulti x>y
.
2. esegui E su (x, y).
2. Se il risultato è 1, accetta. Altrimenti rifiuta.”
4. Macchine di Turing multinastro
5. Macchine di Turing non-deterministiche
6. Macchina di Turing universale e problema della fermata
7. Problemi decidibili e non decidibili
8. Problemi non decidibili e riducibilità - prima parte
9. Problemi non decidibili e riducibilità - seconda parte
11. Decidibilità delle teorie logiche
12. Misurazione della Complessità e introduzione alla classe P
14. NP-Completezza - prima parte
15. NP-Completezza - seconda parte
16. Altri problemi NP-Completi
17. Esercitazione su problemi NP-Completi
18. Space Complexity
19. Savitch Theorem