1
Teoria della computazione
• Quali problemi sappiamo risolvere
– Con quali macchine
– In assoluto
• A prima vista la domanda può sembrare troppo
generale:– Che cosa intendiamo per problema?
Un calcolo matematico; una decisione di un’assemblea di condominio; il
prelievo di contante dal Bancomat; …?
– Quante e quali macchine astratte dobbiamo considerare?
– Che significa saper risolvere “in assoluto” un problema:
Pinco può essere più capace di pallino;
Se non so risolvere un problema con un mezzo potrei riuscire a risolverlo
con un altro.
2
In realtà siamo già in grado di inquadrare per bene il tema pur nella
sua generalità
• Ricordiamo in primis che il concetto di linguaggio ci permette di
formalizzare qualsiasi “problema informatico” (non quello di conquistare il
cuore di un/a ragazza/o o il mondo ...):
x ∈ L?
y = τ(x)In realtà anche le due formulazioni di cui sopra possono essere ricondotte
l’una all’altra:
– Se ho una macchina che sa risolvere il problema y = τ(x) e voglio usarla per risolvere un problema x ∈ L?, mi basta definire τ(x) = 1 se x ∈ L, τ(x) = 0 se x ∉L.
– Viceversa, se ho una macchina che sa risolvere il problema x ∈ L?, posso
definire il linguaggiodopodiché, fissato un certo x, enumero tutte le possibili stringhe y sull’alfabeto di uscita e
per ognuna di esse domando alla machina se x$y ∈ Lτ: prima o poi, se τ(x) è definita,
troverò quella per cui la macchina risponde positivamente: quella è la y cercata (ricordiamo
il gioco di indovinare un oggetto formulando domande e ottenendo risposte “binarie”).
Procedimento forse “lunghetto” ma in questo momento la lunghezza del calcolo non ci
interessa.
)}(|${ xyyxL ττ ==
3
• Quanto alla macchina di calcolo … effettivamente ce n’è una
miriade, oltre quelle che conosciamo; e ben di più se ne possono
inventare al massimo potrei ambire a risultati del tipo
{anbn|n > 0} può essere riconosciuto da un AP e da una MT ma non da un
FSA.
• In realtà, a ben pensare, abbiamo già osservato che non è poi così facile
“superare” la MT: aggiungere nastri, testine, nondeterminismo, … non
produce aumento di potenza (nel senso dei linguaggi riconoscibili);
non è poi così difficile far fare alla MT ciò che fa un normale calcolatore:
basta simulare la memoria dell’uno con quella dell’altra (o viceversa)
(torneremo su questo tema in maniera meno ovvia quando ci interesserà
esaminare anche il costo delle computazioni)
con una generalizzazione potente e audace:
4
Tesi di Church (e Turing e altri):
Anni 30
• Non esiste meccanismo di calcolo automatico superiore alla MT o
ai formalismi ad essa equivalenti.
Fin qui potrebbe essere un Teorema di Church (da aggiornare
ogni volta che qualcuno si sveglia la mattina con un nuovo
modello di calcolo)
• Nessun algoritmo, indipendentemente dallo strumento utilizzato
per implementarlo, può risolvere problemi che non siano
risolvibili dalla MT: la MT è il calcolatore più potente che
abbiamo e che potremo mai avere!
• Allora è ben posta la domanda: “Quali sono i problemi risolvibili
algoritmicamente o automaticamente che dir si voglia?”:
Gli stessi risolvibili dalla semplicissima MT!
5
E allora studiamo per bene le MT
• Esistono problemi che non si possono risolvere?
• Come si fa a capirlo?
Le risposte che troveremo varranno anche per
programmi C, Modula, Erlang, ….
6
Primo fatto:
• Le MT sono algoritmicamente enumerabili
• Enumerazione di un insieme S:
• E: S N
• Enumerazione algoritmica: E può essere calcolata
mediante un algoritmo … cioe` mediante una MT
• Enumerazione algoritmica di {a, b}*:
• {ε, a, b, aa, ab, ba, bb, aaa, aab, aba, abb, …}
• {0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, ….}
7
• Tanto per fissare le idee e per semplicità, senza perdere
generalità alcuna:
• Fissiamo un alfabeto (unico) A
(negli esempi |A| = 2, A = {0 (_),1})
• MT a nastro singolo
• Ulteriori convenzioni seguiranno
• Lasciando stare le MT a uno stato … osserviamo quelle a due
stati:
0 1
q0
q1
⊥
⊥
⊥
⊥
0 1
q0
q1
⊥
⊥
⊥<q0, 0, S>
MT0MT1
8
• Quante MT con due stati?
δ: Q × A → Q × A × {R,L,S} … ∪ {⊥ }
• In generale: quante f: D → R?
• |R||D|
• Con |Q| = 2, |A| = 2, (2.2.3+1)(2.2) = 134 MT a due stati
• Mettiamo in ordine queste MT: {M0, M1, …M134-1 }
• Poi mettiamo in ordine alla stessa maniera le (3.2.3+1)(3.2) MT
con 3 stati e così via.
• Otteniamo un’enumerazione E: {MT} N
• E è algoritmica o effettiva: pensiamo a scrivere un programma
C (ovvero una MT) che, dato n, costruisce la n-esima MT (ad
esempio una tabella che definisce δ) e viceversa, data una (tabella che descrive una) MT, dice in che posizione è: E(M).
• E(M): numero di Goedel di M, E: goedelizzazione.
9
• Ulteriore convenzione: visto che stiamo parlando di
numeri, d’ora in avanti, e per un po’:
• Problema = calcolo di una funzione f: N → N
• fy = funzione calcolata dalla y-esima MT
• NB: fy(x) = ⊥ se My non si ferma quando riceve in
ingresso x
• Aggiungiamo la convenzione fy(x) = ⊥ se e solo se My
non si ferma quando riceve in ingresso x:basta far sì che una qualsiasi MT se si fermasse in uno stato che
non porta alla definizione di un valore significativo fy(x) si porti
in un nuovo stato che la fa procedere all’infinito, ad esempio
spostando la testina sempre a destra, senza più fermarsi (e` solo
una comodità)
10
Secondo fatto:
• Esiste una Macchina di Turing Universale (MTU): la
MT che calcola la funzione g(y,x) = fy(x)
• Ad essere precisi la MTU cosi’ definita non sembra
appartenere alla famiglia {My} perché fy è funzione di
una variabile, mentre g è funzione di due variabili
• Però noi sappiamo che N × N ↔ N : ad esempio:
0
1
2 5
4
3
6x
yxyxyxd +
+++=
2
)1)((),(
11
• Possiamo codificare g(y,x) come una g^(n) = g(d-1(n))
NB: d e d-1 sono computabili
• Schema di una MTU che calcola g^ (d’ora in poi per semplicità
scriveremo direttamente g):
– Dato n calcolo d-1(n) =<y,x>
– Costruisco la tabella di My (calcolando E-1(y)) e la memorizzo nel nastro
della MTU:
$ q a q’ a’ S $ .. ..
- In un’altra porzione di nastro simulo la configurazione di My
# 0 1 q0 .. 1 1 1 0 #
NB: I simboli speciali #, $ e gli stati sono codificati come stringhe di 0 e 1
Alla fine MTU lascia sul nastro fy(x), se e solo se My termina la
computazione su x
12
• La MT è un modello molto astratto e semplice di
calcolatore
• Approfondendo l’analogia:
• MT: calcolatore a programma cablato.
Una MT “normale” esegue sempre lo stesso algoritmo,
calcola sempre la stessa funzione
• MTU: calcolatore a programma memorizzato:
y = programma
x = dati del programma
13
Torniamo alla domanda “quanti e quali problemi sono
risolvibili algoritmicamente?”
• Quante e quali sono le funzioni fy: N → N ?
• Cominciamo con il “Quante”:
• {f: N → N } ⊇ {f: N → {0,1} } ⇒| {f: N → N }| >= | {f: N → {0,1} }| = ℘(N) = 2ℵ0
• D’altro canto l’insieme {fy: N → N} è per definizione
numerabile:
NB: E: {My} ↔ N induce E^: N → {fy} non biunivoca
(in molti casi fy = fz, con z ≠ y) ma basta per asserire
• |{fy: N → N}| = ℵ0 < 2ℵ0 ⇒
• la “gran parte” delle funzioni (problemi) non è calcolabile
(risolvibile) algoritmicamente!!
14
E` proprio una grave perdita?
• In realtà quanti sono i problemi definibili?
• Per definire un problema usiamo una frase (stringa) di un qualche linguaggio:
– f(x) = x2
– “Il numero che moltiplicato per se stesso dà y”
– …
• Ma un linguaggio è un sottoinsieme di A*, che è un insieme numerabile →• L’insieme dei problemi definibili è pure numerabile, come quello dei
problemi risolvibili (algoritmicamente).
• Possiamo ancora sperare che coincidano
Certamente {Problemi risolvibili} ⊆ {Problemi definibili}
(Una MT definisce una funzione, oltre a calcolarla)
∫=x
adzzgxf )()(
15
Passiamo allora alla domanda:
“Quali sono i problemi risolvibili?”
• Il problema della terminazione del calcolo
(molto “pratico”):
– Costruisco un programma
– Fornisco dei dati in ingresso
– So che in generale il programma potrebbe non terminare
l’esecuzione (in gergo: “andare in loop”)
– Posso determinare se questo fatto si verificherà?
• In termini -assolutamente equivalenti- di MT:
– Data la funzione g(y,x) = 1 se fy(x) ≠⊥, g(y,x) = 0 se fy(x) =⊥– Esiste una MT che calcola g?
16
Risposta: NO!
• Ecco perché il compilatore (un programma) non può
segnalarci nella sua diagnostica che il programma che
abbiamo scritto andrà in loop con certi dati (mentre può
segnalarci se abbiamo dimenticato un end):
• Stabilire se un’espressione aritmetica è ben
parentetizzata è un problema risolvibile (decidibile);
• Stabilire se un dato programma con un dato in ingresso
andrà in loop è un problema irrisolvibile (indecidibile)
algoritmicamente [ne vedremo molti altri: sono “molte”
(anche in termini qualitativi) le cose che il calcolatore
non sa fare]
17
Dimostrazione
• Sfrutta una tipica tecnica diagonale utilizzata anche nel teorema di Cantor
per dimostrare che ℵ0 < 2ℵ0
• Ipotesi assurda: g(y,x) = 1 se fy(x) ≠⊥, g(y,x) = 0 se fy(x) =⊥computabile
• Allora anche
h(x) = 1 se g(x,x) = 0 (fx(x) =⊥), ⊥ altrimenti (fx(x) ≠⊥)• e` computabile
(NB: ci siamo posti sulla diagonale y = x e abbiamo scambiato il si col no,
poi abbiamo fatto sì che il no diventasse una nonterminazione (sempre
fattibile)
• Se h è computabile h = fx0per qualche x0.
• Domanda h(x0) = 1 o h(x0) =⊥?
18
• Supponiamo h(x0) = f x0(x0) = 1
• Ciò significa g(x0,x0) = 0 ovvero f x0(x0) =⊥:
• Contraddizione
• Allora supponiamo il contrario: h(x0) = f x0(x0) =⊥
• Ciò significa g(x0,x0) = 1 ovvero f x0(x0) ≠⊥:
• Contraddizione in ambo i casi: QED
• Vedremo che “in conseguenza” di questa irrisolvibilità molti
altri problemi risultano irrisolvibili.
• Per il momento:
19
• Un primo “corollario” dell’irrisolvibilità del Problema dell’halt della MT
– La funzione h'(x) = g(x,x) = 1 se fx(x) ≠⊥; = 0 se fx(x) =⊥non è calcolabile
– In realtà, rigorosamente parlando, si tratta piuttosto di un lemma che di
un corollario.
– Infatti esso costituisce il “cuore” della dimostrazione del teorema
precedente
– Di per se stesso l’enunciato non significa molto
– E’ però importante menzionarlo per sottolineare che il suo enunciato non
può essere ricavato come conseguenza immediata del teorema
precedente (+generale):
– NB: in generale, se un problema è irrisolvibile, può darsi che un suo
caso particolare diventi risolvibile (: vedremo esempi irrisolvibili per
linguaggi qualsiasi, risolvibili per linguagi regolari); invece una sua
generalizzazione è necessariamente pure irrisolvibile.
Al contrario, se un problema è risolvibile, può darsi che una sua
generalizzazione diventi irrisolvibile mentre una sua particolarizzazione
rimane certamente risolvibile.
20
Un altro importante problema indecidibile
• La funzione k(y) = 1 se fy è totale, ossia fy(x) ≠ ⊥ ∀ x∈N; = 0 altrimentinon è calcolabile
• NB1: è un problema simile ma diverso dal precedente. Qui abbiamo una quantificazione rispetto a tutti i possibili dati in ingresso.In certi casi potrei essere in grado di stabilire, fissato x, se fy(x) ≠⊥, inoltrepoterlo fare ∀x senza per questo poter rispondere alla domanda “fy è una funzione totale?” (Ovviamente se trovo un x tale che fy(x) =⊥, posso concludere che fy non è totale, ma se non lo trovo?).Viceversa, potrei essere in grado di concludere che fy non è totale eppure potrei non poter decidere se fy(x) ≠⊥ per un singolo x. (Certo, se invece concludessi che è totale fy …)
• Dal punto di vista dell’impatto pratico questo teorema è forse ancor piùsignificativo del precedente: dato un programma, voglio sapere se esso terminerà l’esecuzione per qualsiasi dato in ingresso o corre il rischio, per qualche dato, di andare in loop. Nel caso precedente, invece mi interessava sapere se un certo programma con certi dati avrebbe terminato o meno l’esecuzione.
21
Dimostrazione (non indispensabile ma utile)
• Meccanismo standard: assurdo + diagonale, con qualche dettaglio tecnico in più.
• Ipotesi assurda: k(y) = 1 se fy è totale, ossia fy(x) ≠ ⊥ ∀ x∈N; altrimenti = 0
calcolabile, e, ovviamente, totale per definizione
• Definisco allora g(x) = w = indice (numero di Goedel) della x-esima MT (in E)
che calcola una funzione totale.
• Se k è calcolabile e totale, allora lo è anche g:
– calcolo k(0), k(1), …, sia w0 il primo valore tale che k(w0) = 1, allora pongo g(0) = w0;
– procedendo pongo g(1) = w1, essendo w1 il secondo valore tale che k(w1) = 1; …
– il procedimento è algoritmico; inoltre, essendo le funzioni totali infinite, g(x) è certo definita per
ogni x, ergo è totale.
• g è anche strettamente monotona: passando da x a x+1, wx+1 è certo > di wx;
• quindi g-1 è una funzione, pure strettamente monotona, anche se non totale: g-1(w)
è definita solo se w è il numero di Goedel di una funzione totale.
22
• Definisco ora
(α) h(x) = fg(x)(x) + 1 = fw(x) + 1:
fw è calcolabile e totale e quindi anche h lo è ⇒(β) h = fw0
per qualche w0;
siccome h è totale, g-1(w0) ≠ ⊥, poniamo g-1(w0) = x0
• Quanto vale h(x0) ?
– h(x0) = fg(x0)(x0) + 1 = fw0(x0) + 1 (da (α))– h = fw0 → h(x0) = fw0(x0) (da (β))
• Contraddizione!
23
NB (molto critico): sapere che un problema è risolvibile
non vuol dire saperlo risolvere!
• In matematica spesso ottengo dimostrazioni non costruttive:
dimostro che la soluzione di un problema esiste (ed è unica) ma
non per questo fornisco un modo di calcolarla
• Nel nostro caso:
– un problema è risolvibile se esiste una MT che lo risolve
– per certi problemi posso arrivare alla conclusione che esiste una MT che li
risolve ma non per questo essere in grado di fornirla
• Cominciamo con un caso banale
– Il “problema” consiste nel rispondere a una domanda la cui risposta è
necessariamente Sì o No:
• E’ vero che il numero di molecole dell’universo è 1010101010
?
• E’ vero che la “partita a scacchi perfetta” termina in parità?
• (20 anni fa ...) E’ vero che ?
• ….
)2|,,,( >∧=+∈∃¬ ywzxNwzyx yyy
24
• In questi casi so a priori che la risposta è Si o No anche se non so (sapevo)
quale sia (fosse).
• La cosa stupisce un po’ meno se ricordiamo che
Problema = funzione; risolvere un problema = calcolare una funzione
Che funzione associamo ai problemi di cui sopra?
Codificando TRUE = 1; FALSE = 0, tutti i problemi sopra sono espressi da
una delle due funzioni: f1(x) = 1, ∀x, f1(x) = 0, ∀x
Entrambe le funzioni sono banalmente calcolabili →Qualsiasi sia la risposta al problema essa è calcolabile ma non
necessariamente nota.
• In maniera un po’ più astratta:
g(10,20) = 1 se f10(20) ≠⊥, g(10,20) = 0 se f10(20) =⊥g(100,200) = 1 se f100(200) ≠⊥, g(100,200) = 0 se f100(200) =⊥g(7,28) = 1 se f7(28) ≠⊥, g(7,28) = 0 se f7(28) =⊥….
sono tutti problemi risolvibili, anche se non è detto che noi ne conosciamo la
soluzione.
25
• Esaminiamo ora casi un po’ meno banali ma molto istruttivi:
– f(x) = x-esima cifra dell’espansione decimale di π.f è sicuramente calcolabile e conosciamo algoritmi (MT) che la calcolano
– Basandosi sulla capacità di calcolare f (allo stato attuale non si hanno
altre fonti di conoscenza) investighiamo la calcolabilità di
g(x) = 1 se in π ∃ x 5 consecutivi, 0 altrimenti
Calcolando la sequenza
{f(0) = 3, f(1) = 1, f(2) = 4, f(3) = 1, f(4) = 5, f(5) ≠ 5, …}
Otteniamo g(1) = 1
In generale il grafico di g sarà del tipo seguente:
0 1 3 x 5 y 9 10
1
26
– Per qualche valore di x potremmo scoprire che g(x) = 1,
anzi, se g(x) = 1, prima o poi, pur di aver pazienza, lo scopriamo
(approfondiremo questo fatto più avanti) ma che elementi abbiamo per
concludere, ad esempio, che g(100000000) = 0 se dopo aver calcolato
f(1000000000000000) non abbiamo ancora trovato 100000000 5
consecutivi?
Allo stato attuale di conoscenze (personali), nessuno!
– Possiamo però escludere che sia vera la seguente congettura:
"Qualsiasi sia x, pur di espandere un numero sufficientemente grande di
cifre di π, prima o poi si troveranno x 5 consecutivi" ?
– Se essa fosse vera, ne ricaveremmo che g è la funzione costante
g(x) = 1 ∀ x e scopriremmo quindi anche che la sappiamo calcolare.
– In conclusione, allo stato attuale, non possiamo concludere né che g sia
calcolabile, né che non lo sia.
27
• Consideriamo ora la seguente “lieve” modifica di g:
h(x) = 1 se in π ∃ almeno x 5 consecutivi, 0 altrimenti
Ovviamente, se g(x) = 1 anche h(x) = 1;
Osserviamo però che se per qualche x h(x) = 1, allora h(y) = 1
∀ y ≤ x. Ciò significa che il grafico di h è di uno dei due tipi
seguenti:
– 1) (h(x) = 1 ∀ x)
– 2)
xxxh
xxxh
>∀=
≤∀=
0)(
1)(
x
28
• Quindi h appartiene sicuramente all’insieme delle funzioni
Si noti che ognuna delle funzioni di questo insieme é
banalmente calcolabile (fissato é immediato costruire una MT
che calcola ; idem per )
• Dunque h è sicuramente calcolabile: esiste una MT che la
calcola
• Sappiamo calcolare h?
Attualmente no: tra le infinite MT che calcolano le funzioni
dell’insieme suddetto non sappiamo quale sia quella giusta!
}1)(|{}0)(1)(|{ xxhhxxxhxxxhhxxx
∀=∪>∀=∧≤∀=
x
xh h
29
Decidibilità e semidecidibilità
Ovvero:
1/2 + 1/2 = 1
• Concentriamoci su problemi formulati in modo tale
che la risposta sia di tipo binario:
Problema = insieme S ⊆ N: x ∈ S?
• Funzione caratteristica di un insieme S:
cS(x) = 1 se x ∈ S, cS(x) = 0 se x ∉ S
• Un insieme S è ricorsivo (o decidibile) se e solo se la
sua funzione caratteristica è computabile (NB: cS è
totale per definizione).
30
• S è ricorsivamente enumerabile (RE) (o semidecidibile) se e
solo se:
– S è l’insieme vuoto
– oppure
– S è l’insieme immagine di una funzione totale e computabile:
da qui il termine “ricorsivamente (algoritmicamente) enumerabile”
– possiamo spiegare in termini intuitivi anche l’attributo “semidecidibile”:
se x ∈ S, enumerando gli elementi di S prima o poi trovo x e sono in
grado di ottenere la risposta giusta (Sì) alla domanda;
ma se x ∉ S?
– una conferma più formale ci viene dal seguente ...
),...}3(),2(),1(),0({
}),(|{
SSSS
Sg
ggggS
NyygxxISS
=
⇒
∈===
31
Teorema
• A) Se S è ricorsivo è anche RE
(decidibile è più che -non meno di- semidecidibile)
• B) S è ricorsivo se e solo se S stesso e il suo
complemento S^ = N - S sono RE
(due semidecidibilità fanno una decidibilità; ovvero
quando rispondere No equivale a (è ugualmente
difficile che) rispondere Sì (e quando invece …)
• Corollario: gli insiemi (linguaggi, problemi, …)
decidibili sono chiusi rispetto al complemento.
• Dimostrazione:
32
A): S ricorsivo implica S RE
• Se S è vuoto esso è RE per definizione!
• Assumiamo allora S ≠ ∅ e indichiamo con cs la sua funzione caratteristica:∃ k ∈ S, cioè cs(k) = 1
• Definiamo la funzione gs: gs(x) = x se cs(x) = 1, altrimenti gs(x) = k
• gs è totale, computabile e IgS= S
• → S è RE
• NB: dimostrazione non costruttiva:sappiamo se S ≠ ∅? Sappiamo calcolare gs?Sappiamo solo che esiste gs se S ≠ ∅: è quanto ci basta!
33B) S è ricorsivo se e solo se S e S^ = N - S sono RE
• B.1.) S ricorsivo → S RE (parte A)
• B.1.2) S ricorsivo → cS(x) (= 1 se x ∈ S, cS(x) = 0 se x ∉ S)
calcolabile → cS^(x) (= 0 se x ∈ S, cS(x) = 1 se x ∉ S)
calcolabile → S^ ricorsivo → S^ RE
• B.2)S RE →S^ RE →Ma S∪S^ = N, S∩S^ = ∅
perciò ∀x ∈ N ,
(x appartiene a una e una sola delle due enumerazioni) →Se costruisco l’enumerazione
sono sicuro di trovarvi qualsiasi x: a quel punto se trovo x in un
posto dispari concludo x ∈ S, se lo trovo in posto pari concludo
x ∈ S^. So quindi calcolare cS.
),...}3(),2(),1(),0({ SSSS ggggS =),...}3(),2(),1(),0({ ^^^^
^
SSSSggggS =
))()(|()()(| ^^ zgxzgxzygxygxy SSSS=∧=∃¬∧=∨=∃
),...}3(),3(),2(),2(),1(),1(),0(),0({ ^^^^ SSSSSSSS gggggggg
34
Alcuni enunciati con importanti risvolti pratici
• Abbia un generico insieme S le seguenti caratteristiche:
– i ∈ S → fi totale
– f totale e computabile → ∃ i ∈ S | fi = f
• Allora S non è RE
• Ciò significa: non esiste un formalismo (RE: Automi, grammatiche, funzioni
ricorsive, …) in grado di definire tutte e sole le funzioni calcolabili totali:
• I FSA, definiscono “funzioni totali” ma non tutte; le MT definiscono tutte le
funzioni calcolabili, ma anche quelle non totali; il C permette di programmare
qualsiasi algoritmo, ma anche quelli che non terminano: esiste un sottoinsieme
RE del C che definisca solo i programmi che terminano? NO!
Ad esempio l’insieme dei programmi i cui loop siano solo cicli for soggiacenti
a precise limitazioni può contenere solo programmi che terminano ma non tutti.
35
• Tentiamo un altro trucco per sbarazzarci delle scomode
funzioni non totali (=algoritmi che vanno in loop):
– estendiamo una funzione, ad esempio arricchendo N con il
nuovo valore {⊥}, oppure semplicemente attribuendo ad f un
valore convenzionale quando f è indefinita.
– Matematicamente l’operazione è perfettamente sensata
(infatti in matematica pura si presta poca attenzione alle
funzioni parziali)
– il trucco non funziona:
– Non esiste una funzione totale e computabile che sia
un’estensione della funzione computabile ma non totale
g(x) = {se fx(x) ≠⊥ allora fx(x) + 1, altrimenti ⊥}
• Posso prendere una funzione parziale e farla diventare totale, ma
nel farlo potrei perderne la computabilità: coperta corta!
36
• S è RE ↔– S = Dh, con h computabile e parziale: S = {x | h(x) ≠ ⊥}↔
– S = Ig, con g computabile e parziale: S = {x | x = g(y), y ∈ N}
• Dimostrazione omessa (qui) ma usa una tecnica
particolarmente utile e significativa (v. esercitazioni)
• Serve come Lemma per dimostrare che:
• Esistono insiemi semidecidibili che non sono decidibili:
• K = {x | fx(x) ≠ ⊥} è semidecidibile perché K = Dh con
h(x) = fx(x). Però sappiamo anche che cK(x) (= 1 se fx(x) ≠⊥, 0 altrimenti) non è computabile, → K non è decidibile
• Conclusione:
dominio di definizione di h
37
Insiemi
ricorsivi
Insiemi
RERERERE
PPPP(N)
I contenimenti sono tutti stretti
Corollario: gli insiemi RE (i linguaggi riconosciuti dalle MT)
non sono chiusi rispetto al complemento
38
Il potentissimo teorema di Rice:• Sia F un insieme di funzioni computabili
L’insieme S de(gli indici di) MT che calcolano funzioni di F
(S = {x| fx ∈ F}) è decidibile se e solo se F = ∅ o F è l’insieme di tutte le funzioni computabili
• → in tutti i casi non banali S non è decidibile!
→– La correttezza di un programma: P risolve il problema specificato? (Mx
calcola la funzione costituente l’insieme {f}?)
– L’equivalenza tra due programmi (Mx calcola la funzione costituente l’insieme {fy}?)
– Un programma gode di una qualsiasi proprietà riferibile alla funzione da esso calcolata (funzione a valori pari, funzione con insieme immagine limitato, …)?
– …
• Sono solo alcuni tra gli innumerevoli esempi di problemi la cui indecidibilitàdiscende banalmente dal teorema di Rice.
39Come facciamo, in pratica a stabilire se un problema è
(semi)decidibile o no?(ovviamente questo è a sua volta un problema indecidibile)
• Se troviamo un algoritmo che termina sempre → decidibile
• Se troviamo un algoritmo che potrebbe non terminare ma termina sempre se la risposta al problema è Sì → semidecidibile
• Ma se riteniamo che il problema in questione sia non (semi)decidibile, come facciamo a dimostrarlo?
• Tentiamo di costruirci una nuova dimostrazione di tipo diagonale ogni volta? … staremmo freschi!
• In realtà abbiamo ora mezzi più comodi:
• Un primo strumento potentissimo l’abbiamo già visto: il teorema di Rice
• Di fatto implicitamente abbiamo già usato più volte una tecnica naturale e generalissima:
40
La riduzione di problemi
• Se ho un algoritmo per risolvere il problema P lo posso
sfruttare per risolvere il problema P':
– Se so risolvere il problema della ricerca di un elemento in un
insieme posso costruire un algoritmo per risolvere il problema
dell’intersezione tra due insiemi
– In generale se trovo un algoritmo che, data un’istanza di P' ne
costruisce la soluzione ricavandone un’istanza di P per il quale a
sua volta so ricavarne la soluzione, ho ridotto P' a P.
– Formalmente:
• Voglio risolvere x ∈ S?
• So risolvere y ∈ S'
• Se trovo una funzione t calcolabile e totale tale che x ∈ S ↔ t(x) ∈ S'
sono in grado di rispondere algoritmicamente alla domanda x ∈ S?
41• Il procedimento può funzionare anche al contrario:
– Voglio sapere se posso risolvere x ∈ S?
– So di non saper risolvere y ∈ S’ (S’ non è decidibile)
– Se trovo una funzione t calcolabile e totale tale che y ∈ S’↔ t(y) ∈ Sne concludo che x ∈ S? è non decidibile, altrimenti ne ricaverei la conseguenza assurda che anche S’ è decidibile
• In realtà abbiamo usato questo meccanismo già varie volte in forma implicita:– Dall’indecidibilità del problema dell’halt della MT abbiamo concluso in
generale l’indecidibilità del problema della terminazione del calcolo:
• Ho una MT My un numero intero x un programma C, P e un file di ingresso f
• Costruisco un programma C, P che simula My e memorizzo x in un file di ingresso f
• P termina la sua computazione su f se e solo se g(y,x) ≠ ⊥• Se sapessi decidere se P termina la sua computazione su f saprei risolvere anche
il problema dell’halt della MT.
• NB: avremmo potuto ridimostrare in modo diretto l’indecidibilità della terminazione dei programmi C enumerando i programmi e applicando la stessa tecnica diagonale … con un po’ più di dettagli notazionali.
42Un meccanismo abbastanza generale
• E’ decidibile se durante l’esecuzione di un generico programma P si acceda ad una variabile non inizializzata?– Supponiamo per assurdo che sia decidibile
– Allora consideriamo il problema dell’halt e riduciamolo al problema nuovo come segue:
• Dato un generico P^ che riceve in ingresso generici dati D, costruisco un P cosiffatto:{ int x, y;
P^;y = x;
}
• avendo cura di usare identificatori x e y che non sono usati in P^
• è chiaro che l’assegnamento y := x produce un accesso ad x che non è inizializzata perché x non compare in P^
• Quindi l’accesso ad una variabile non inizializzata accade in P se e solo se P^ termina.
– Allora se sapessi decidere il problema dell’accesso ad una variabile non inizializzata, saprei decidere anche il problema della terminazione del calcolo, ciò che è assurdo.
43
• La stessa tecnica può essere applicata per dimostrare
l’indecidibilità di molte altre tipiche proprietà dei
programmi durante la loro esecuzione:
– Indici di array fuori dai limiti
– Divisione per 0
– Compatibilità dinamica tra tipi
– …
– Tipici errori a run-time: a questo proposito ...
44
• Riprendiamo in considerazione gli esempi precedenti
– l’halt della MT
– la divisione per 0 e gli altri errori a run-time, …
• Sono non decidibili ma semidecidibili: se la MT si
ferma, prima o poi lo scopro; se esiste un dato x di un
programma P tale per cui P tenti a un certo punto di
eseguire una divisione per 0 prima o poi lo scopro …
• Fermiamoci un momento e apriamo una parentesi:
– come scopro il fatto precedente: se io comincio ad eseguire P
sul dato x e P non si ferma su x come faccio a scoprire che
eseguendo P su y, P eseguirà una divisione per 0?
45
• In generale: Teorema (formulazione astratta dei vari casi concreti precedenti):
– Il problema di stabilire se ∃z | fx(z) ≠ ⊥ è semidecidibile
– Schema di dimostrazione
• E’ chiaro che se cerco di calcolare fx(0) e trovo che è ≠ ⊥ sono a posto;
• Però se la computazione di fx(0) non termina e fx(1) è ≠ ⊥ come posso scoprirlo?
• Uso allora il seguente trucco (ancora una volta di sapore diagonale):
– Simulo 1 passo di computazione di fx(0): se mi fermo, ho chiuso positivamente;
– in caso contrario simulo un passo di computazione di fx(1);
– se ancora non mi sono fermato simulo 2 passi del calcolo di fx(0); successivamente 1
passo di fx(2); 2 di fx(1); 3 di fx(0); e così via secondo lo schema già adottato di
figura:
In questa maniera se ∃z | fx(z) ≠ ⊥, prima o poi lo trovo perché
prima o poi simulerò abbastanza passi della computazione di fx(z)
per farla arrestare.
46
• Chiudendo la parentesi:
• abbiamo dunque un notevole numero di problemi (tipicamente gli errori a
run-time dei programmi) non decidibili ma semidecidibili.
• Attenzione però: qual’è esattamente il problema semidecidibile:
– la presenza dell’errore (se c’è lo trovo)
– non l’assenza!
• Ma, siccome si dà il caso che il complemento di un problema in RE - R non
sia neanche RE (altrimenti sarebbe anche decidibile),
• L’assenza di errori (ovvero la correttezza di un programma rispetto ad un
errore) non solo non è decidibile, ma non è neanche semidecidibile!
• Ne otteniamo quindi un modo sistematico per dimostrare che un problema
non è RE: dimostrare che il suo complemento lo è (ovviamente non essendo
decidibile).
47
La complessità del calcolo
• Non analisi di singoli algoritmi (si fa riferimento ai
corsi di fondamenti)
• Non algoritmica avanzata (si rimanda a corsi
successivi)
Bensì:
• Riesame critico del problema e dell’approccio
• Ricerca di principi di validità generale
• Costruire una capacità di inquadramento nel giusto
ambito di singoli problemi
48
La complessità come “raffinamento” della risolvibilità
• Non ci accontentiamo più di sapere se sappiamo risolvere (algoritmicamente)
un problema ma vogliamo sapere quanto ci costa risolverlo
• Analisi critica del concetto di “costo” (e beneficio):
– Costo di esecuzione (risorse fisiche necessarie), a sua volta diviso in:
• Tempo
– di compilazione
– di esecuzione
• Spazio
– Costo di sviluppo
– …
– Valutazioni oggettive e soggettive, trade-off tra obiettivi contrastanti, …
– … verso problematiche e approcci da Ingegneria del software
– Qui ci si limita a concetti di costo oggettivi e formalizzabili: tipiche
risorse: memoria e tempo (di esecuzione)
49
Sarebbe bello partire come per la risolvibilità:
• Le domande che ci poniamo e le risposte che otterremo
non dipendono dal modo con cui formuliamo il
problema né dallo strumento usato (Tesi di Church).
• Però:
– Fare la somma in unario è ben diverso dal fare la somma in
base k
– Se uso la tecnica z ∈ per calcolare τ(x) dovrò decidere un problema di appartenenza un numero
anche illimitato di volte per risolvere il problema originario
di traduzione
– E’ verosimile che cambiando calcolatore (o MT) non cambi
il tempo di esecuzione? Evidentemente no, però...
)}(|${ xyyxL ττ ==
50
• Certo l’obiettivo è arduo o addirittura mal posto
• Tuttavia alla fine riusciremo ad ottenere risultati di
notevole validità generale ….
• … una sorta di “Tesi di Church della complessità”
• Visto che per ora una Tesi di Church della complessità
non sussiste …
51
… cominciamo da un’analisi di complessità legata alle MT
• Complessità temporale: sia
c0 |-- c1 |-- c2 |-- c3 … |-- crTM(x) = r se la computazione termina in cr, altrimenti ∞
• Complessità spaziale:
c0 |-- c1 |-- c2 |-- c3 … |-- cr
• NB: SM(x)/k ≤ TM(x), ∀x
]},..,1[,max{)(1
rixSk
j
ijM ∈=∑=
αesima-i mossa alla j nastro del contenuto=ijα
52
Un primo esempio: riconoscimento di {wcwR}
Nastro di lettura
bNastro di memoria
B
a b c b b a
BA
OC
Z0
TM(x) = |x| + 1 se x ∈ L
|w|+1 se x = wv', w = vucuR, v = za, v' = bz'
|x| + 1 se x ∈ {a,b}* (non c'e` la c...)
…
SM(x) = |x| + 1 se x ∈ {a,b}*, |x|/2 + 1 se x ∈ L, ...
53
• Un po’troppi dettagli, …
• utili/necessari?
• Cerchiamo di semplificare e di andare al sodo:
• Complessità in f(x) → complessità in f(n),
“dimensione dei dati in ingresso”:
n = |x|, righe/colonne di una matrice, numero di record
in un file, …
Però in generale |x1| = |x2| ¬⇒ TM (|x1|) = TM (|x2|)
(idem per SM) ---->
54
• Scelta del caso pessimo:
TM(n) = max{TM(x), |x| = n} (idem per SM(n) )
• Scelta del caso medio:
TM(n) =
• Noi adotteremo per lo più il caso pessimo:
– ingegneristicamente più rilevante (per certe applicazioni)
– matematicamente più semplice (a rigore il caso medio
dovrebbe tener conto di ipotesi probabilistiche sulla
distribuzione dei dati: i nomi di una guida del telefono non
sono equiprobabili)
alfabetodell' àcardinalit ,
)(
=∑=
kk
xT
n
nx
M
55
• Uso della notazione Θ per valutare l’ordine di
grandezza di una funzione (di complessità)
• f Θ g ↔• è una relazione di equivalenza ---->
• Diremo che TM(n) è Θ(n), ….
• (Dire che TM(n) è Θ(n) è come dire che TM(n) è
lineare?)
• Non solo l’uso dell’ordine di grandezza permette di evidenziare con facilità
la parte più importante di una funzione di complessità, ma vedremo che in un
certo senso esso descrive anche la parte “indipendente dalla potenza di
calcolo”
∞≠≠=∞→
cccng
nf
n,0,
)(
)(lim
56
Riassumendo:
• Siamo partiti da T/S funzioni di x: complicato!
(anche per algoritmi semplici)
• I astrazione: T/S (n), dove n = |x|:
• II astrazione: caso pessimo:
• III astrazione: comportamento asintotico: utile per confrontare
algo diversi – considero n “grande”
n=1 n=2 n=3 n=4 n=5
T
n
57
Torniamo all’esempio {wcwR}
• TM(n) è Θ(n), SM(n) è pure Θ(n)• Si può fare di meglio?
• Per TM(n) difficile (in generale dovrò almeno leggere tutta la stringa)
• Per SM(n):
Nastro di lettura
b Nastro di memoria: contiene i
codificato in binarioa b c b b a
11
OC
Z0
Memorizzo solo la posizione i del carattere da esaminare; poi sposto la testina di
lettura in posizione i e n-i+1 e confronto i due caratteri letti ===>
58
• Ottengo:– SM(n): Θ(log(n))
ma
– TM(n): Θ(n2.log (n)):
• ∀i,
• costruisco i in binario (in un nastro) (Θ(log(i)) per implementare i := i+1);
• copio i su un nastro ausiliario (j := i);
• i volte:
– decremento j di 1 e sposto di 1 la testina nel nastro di ingresso (a partire dal centro)
(Θ(log(i));
– quando j = 0 la testina è in posizione i-esima
– (Θ(i .log(i));
– classico trade-off spazio-temporale
• L’esempio ci spiega anche perché nella MT a k nastri la testina di ingresso può
muoversi nelle due direzioni: in caso contrario non ci sarebbero esempi
significativi di complessità spaziale sublineare
59
A proposito di MT a k nastri ...
• Proviamo a cambiare modello di calcolo:
– FSA hanno sempre SA(n) Θ(1) e TA(n) Θ(n), anzi TA(n) = n (macchine real-time
…);
– PDA hanno sempre SA(n) ≤ Θ(n) e TA(n) Θ(n);
– MT a nastro singolo?
• Il riconoscimento di {wcwR} richiede in prima istanza Θ(n2),
• La complessità spaziale non potrà mai essere < Θ(n)(ciò fornisce un’ulteriore spiegazione della scelta della MT a k nastri come modello principale)
• Si può fare meglio di Θ(n2)? NO: dimostrazione tecnicamente complessa come quasi sempre per
limiti inferiori di complessità che non siano banali.
– MT a nastro singolo più potenti dei PDA ma talvolta meno efficienti
– E i calcolatori a’ la von Neumann?
Aspettiamo ancora un po’ ...
60
I teoremi di “accelerazione” lineare
• Se L è accettato da un MT M a k nastri con
complessità SM(n), per ogni c > 0 si può costruire una
MT M’ (a k nastri) con complessità SM’(n) < c. SM(n).
a1 a2 ai ar b1 b2 bi br c1 c2 ci
<a1... ai … ar> <b1... bi … br> <c1... ci … cr>
r.c ≥≥≥≥ 2
61
• Se L è accettato da un MT M a k nastri con
complessità SM(n), si può costruire una MT M’ a 1
nastro (non a nastro singolo) con complessità SM’(n) =
SM(n).
• Se L è accettato da un MT M a k nastri con
complessità SM(n), per ogni c > 0 si può costruire una
MT M’ a 1 nastro con complessità SM’(n) < c. SM(n).
62
• Se L è accettato da un MT M a k nastri con
complessità TM(n), per ogni c > 0 si può costruire una
MT M’ (a k+1 nastri) con complessità TM’(n) = max{n+1, c. TM(n)}
• Schema di dimostrazione analogo a quello usato per la
complessità spaziale. Però, con qualche dettaglio tecnico in più:
– occorre prima leggere e tradurre tutto l’input (richiede n mosse)
– ciò crea qualche problema all’interno della classe Θ(n)– nel caso pessimo occorrono 3 mosse per simulare almeno r + 1 mosse di
M
63
Conseguenze pratiche dei teoremi di accelerazione lineare
• Lo schema di dimostrazione è valido per qualsiasi tipo di modello di calcolo:
anche per calcolatori reali:
• significa aumentare il parallelismo fisico (da 16 bit a 32 a 64 …)
• pur di aumentare la potenza di calcolo in termini di risorse disponibili si può
aumentare “a piacere” la velocità di esecuzione
• però tale aumento di prestazioni rimane confinato nell’ambito di
miglioramenti al più lineari: non riesco a cambiare l’ordine di grandezza
• miglioramenti di ordini di grandezza possono essere ottenuti solo cambiando
algoritmo e in modo non automatico:
• per valori di n sufficientemente grandi ordinare una sequenza di n elementi
con il merge sort sarà sempre più efficiente che ordinarla mediante inserzione
lineare o bubble-sort, anche se il primo algoritmo viene eseguito su una
macchina di modesta “potenza” e il secondo da un supercalcolatore:
• l’intelligenza può superare di gran lunga la forza bruta!
64
Riprendiamo ora il confronto tra MT e calcolatori reali
• A prima vista il confronto è impari …:
– per calcolare la somma di due numeri una MT impiega Θ(n) (n è la lunghezza -della stringa di caratteri che codifica- i due
numeri) mentre un calcolatore fornisce questa operazioone
come elementare (eseguita in un ciclo macchina)
– un calcolatore può accedere direttamente a una cella di
memoria, mentre la MT ha accesso solo sequenziale:
• ad esempio, se cerchiamo di implementare la ricerca binaria mediante
una MT otteniamo addirittura una complessità Θ(n.log(n)) > Θ(n)
• Non possiamo perciò accontentarci di valutazioni di
complessità legate esclusivamente alle MT
65
Un modello molto astratto di calcolatore: la RAM
Nastro di lettura
Nastro di scrittura
x
n
Programma
(cablato)
Program
counterUnità
aritmetica
M[0]
M[1]
M[3]
M[2]
Accumulatore
Ogni cella contiene un intero, non un carattere!
66
• Il repertorio istruzioni della RAM:
– LOAD [=, *] X M[0] := M[X], X, M[M[X]]
– STORE [*] X M[X] := M[0], M[M[X]] := M[0]
– ADD [=, *] X M[0] := M[0]+M[X], +X, +M[M[X]]
– SUB, MULT, DIV ...
– READ [*] X M[X] := input, M[M[X]] := input
– WRITE [=, *] X output M[X], X, M[M[X]]
– JUMP lab PC := b(lab)
– JZ, JGZ, ... lab jump if M[0]=0, M[0]>0, ...
– HALT
67Un programma RAM che calcola la funzione
is_prime(n) = if n is prime then 1 else 0
READ 1 Il valore di ingresso n è memorizzato nella cella M[1]
LOAD= 1 Se n = 1, esso è banalmente primo ...
SUB 1
JZ YES
LOAD= 2 M[2] è inizializzato a 2
STORE 2
LOOP: LOAD 1 Se M [1] = M[2] allora n è primo
SUB 2
JZ YES
LOAD 1 Se M [1] = (M[1]div M[2] ) * M[2] allora
DIV 2 M[2] è un divisore di M[1];
MULT 2 quindi M[1] non è primo
SUB 1
JZ NO
LOAD 2 M[2] è incrementato di 1 e il ciclo viene ripetuto
ADD= 1
STORE 2
JUMP LOOP
YES WRITE= 1
HALT
NO WRITE= 0
HALT
68
Quanto costa eseguire il programma di cui sopra mediante una RAM?
• Ovviamente:
– SR(n) Θ(2)
– TR(n) Θ(n)– (Attenzione però: che cos’è n?? Non è la lunghezza della stringa di ingresso!
Attenzione al parametro di “dimensione dei dati”!!)
• Pure ovviamente:
– Riconoscimento di wcwR con
• SR(n) Θ (n)
• TR(n) Θ (n)
– Ricerca binaria con TR(n) Θ(log(n))– Ordinamento
– …
• Però ...
69
Calcoliamo 2(2n) usando una RAM (o macchina analoga)
• read n;
• x := 2;
• for i :=1 to n do
x := x*x;
• write x
• Ottengo ((2)2)2) … n volte ossia 2(2
n)
• Quale complessità temporale?
• Θ(n)! (non Θ(n!))• Siamo proprio sicuri?!
• In realtà occorrono almeno 2n bit solo per scrivere il risultato!
• L’analisi sembra decisamente irrealistica!
70
Il problema sta nel fatto che la RAM (macchina di von Neumann) è un po’ troppo ...
astratta
• Una cella contenente un numero arbitrario = unità di memoria?
• Un’operazione aritmetica = operazione elementare a costo unitario?
• Ciò è corretto solo fino a quando la macchina reale (a 16, 32, 64, … bit)
corrisponde esttamente alla macchina astratta.
• Altrimenti … doppia precisione ecc. ---> le operazioni relative non sono più
elementari e occorre programmarle ad hoc.
• ---->
• rifacciamo tutti gli algoritmi e le relative analisi di complessità in funzione
del livello di precisione (numero di bit) usati?
• Concettualmente sì ma più comodamente:
• Criterio di costo logaritmico: basato su un’analisi “microscopica” (vedi
“microcodice”) delle operazioni HW:
71
• Quanto costa copiare il numero i da una cella all’altra?:
tante microoperazioni elementari quanti sono i bit necessari a codificare i:
log(i)
• Quanto costa accedere alla cella di posizione i-esima?:
l’apertura di log(i) “cancelli” di accesso ad altrettanti banchi di memoria
• Quanto costa eseguire l’operazione
LOAD i?
• …
• Con semplice e sistematica analisi si ottiene la seguente ...
72Tabella dei costi logaritmici della RAM
• LOAD= x l(x)
• LOAD x l(x) + l(M[x])
• LOAD* x l(x) + l(M[x]) + l(M[M[x]])
• STORE x l(x) + l(M[0])
• STORE * x l(x) + l(M[x]) + l(M[0])
• ADD= x l(M[0]) + l(x)
• ADD x l(M[0]) + l(x) + l(M[x])
• ADD * x l(M[0]) + l(x) + l(M[x]) + l(M[M[x]])
• …
• READ x l(valore di input corrente) + l(x)
• READ* x l(valore di input corrente) + l(x) + l(M[x])
• WRITE= x l(x)
• WRITE x l(x) + l(M[x])
• WRITE * x l(x) + l(M[x]) + l(M[M[x]])
• JUMP lab 1
• JGZ lab l(M[0])
• JZ lab l(M[0])
• HALT 1
l(i) := if i=0 then 1
else log2|i|+1
73Applicando il nuovo criterio di costo
• Al calcolo di is-prime(n) (solo nei punti essenziali)
• LOOP: LOAD 1 1+l(n)
• SUB 2 l(n) +2 + l(M[2])
• JZ YES l(M[0])
• LOAD 1 1+l(n)
• DIV 2 l(n) +2 + l(M[2])
• MULT 2 l(n/M[2]) +2 + l(M[2]) (< l(n))
• SUB 1 l(M[0]) +1 + l(n) < 2. l(n) +1
• JZ NO ≤ l(n)
• LOAD 2 ≤ l(n) + k
• ADD= 1 ...
• STORE 2
• JUMP LOOP
• In conclusione si può facilmente maggiorare la singola iterazione del ciclo con Θ(log(n))
• Ergo la complessità temporale complessiva è Θ(n.log(n))
74
• Similmente otteniamo:
• Θ(n.log(n)) per il riconoscimento di wcwR (NB: più della MT! E’ possibile fare meglio?)
• Θ(log2(n)) per la ricerca binaria
• …
• Costo a criterio di costo logaritmico = Costo a criterio di costo costante * log(n)?
• Costo a criterio di costo logaritmico = Costo a criterio di costo costante * log(Costo a
criterio di costo costante )?
• Spesso ma non sempre:
• Per il calcolo di 22ncosto a criterio di costo logaritmico ≥ 2n (complessità temporale ≥
complessità spaziale)
• Esiste un criterio per scegliere il criterio?
– A buon senso (!):
– Se l’elaborazione non altera l’ordine di grandezza dei dati di ingresso, la memoria allocata
inizialmente (staticamente?) può non variare a run-time ---> non dipende dai dati ---> una
singola cella è considerabile elementare e con essa le operazioni relative ---> criterio di costo
costante OK
– Altrimenti (fattoriale, 2(2n) , ricorsioni “feroci”, …) indispensabile criterio logaritmico: l’unico
“garantito”!
75
Le relazioni tra le complessità relative ai diversi modelli di calcolo
• Lo stesso problema risolto con macchine diverse può aver complessità diverse
• Può darsi che per P1 il modello M1 sia meglio del modello M2 ma per P2
succeda il contrario (ricerca binaria, accesso diretto, oppure accesso e
memorizzazione sequenziale [riconoscimento di wcwR]
• Non esiste un modello migliore in assoluto
• Non esiste un analogo della tesi di Church per la complessità …
• Però:
• E’ possibile stabilire almeno almeno una relazione -di maggiorazione- a priori
tra le complessità di diversi modelli di calcolo.
• Teorema (tesi) di correlazione polinomiale (in analogia con la tesi di Church):
– Sotto “ragionevoli” ipotesi di criteri di costo (il criterio di costo costante per la
RAM non è “ragionevole”in assoluto!) se un problema è risolvibile mediante un
modello di calcolo M1 con complessità (spazio/temporale) C1(n) allora è risolvibile
da qualsiasi altro modello di calcolo M2 con complessità C2(n) ≤ P2(C1(n)), essendo
P2 un opportuno polinomio
76
Prima di dimostrare il teorema (non più tesi!) nel caso MT-RAM, valutiamone l’impatto:
• E’ vero che i polinomi possono anche essere n1000, ma è sempre meglio
dell’”abisso” esponenziale (nk contro 2n)
• Grazie al teorema di correlazione polinomiale possiamo parlare della classe dei
problemi risolvibili in tempo/spazio polinomiale (non di quelli risolvibili in
tempo quadratico!): la classe non dipende dal modello adottato
• Grazie a questo risultato -e ad altri importanti fatti teorici- si è da tempo adottata
l’analogia:
– classe dei problemi “trattabili” in pratica = classe dei problemi risolvibili in tempo
polinomiale : P
– La teoria include in P anche i problemi con complessità n1000 (comunque sempre
meglio di quelli a complessità esponenziale), ma l’esperienza pratica conferma che i
problemi di interesse applicativo (ricerche, cammini, ottimizzazioni, …) che sono in
P hanno anche grado del polinomio accettabile
– (similmente vedremo tra poco che la relazione di complessità tra MT e RAM è
“piccola”)
77La correlazione (temporale) tra MT e RAM:
1: Da MT (a k nastri) a RAM
• La memoria della RAM simula la memoria della MT:
1 cella RAM per ogni cella di nastro di MT
Però, invece di usare blocchi di memoria RAM per simulare ogni nastro,
associamo un blocco -di k celle- ad ogni k-pla di celle prese per ogni posizione
di nastro, + un blocco “di base”:
K+1 celle per memorizzare stato e
posizione delle k testine
Blocco 0
Blocco 1 K celle per memorizzare il primo
simbolo di ogni nastro
Blocco i K celle per memorizzare l’i-esimo
simbolo di ogni nastro
78• Una mossa della MT è simulata dalla RAM:
Blocco 0
Blocco 1
Blocco i
• Lettura:
• Viene esaminato il contenuto del blocco 0
(pacchetto di k+1 accessi, c.(k+1)
mosse)
• Vengono fatti k accessi indiretti in k blocchi per
esaminare il contenuto delle celle in
corrispondnza delle testine
• Scrittura:
• Viene cambiato lo stato
• Vengono aggiornati, mediante STORE indiretti,
i contenuti delle celle corrispondenti alla
posizione delle testine
• Vengono aggiornati, nel blocco 0, i valori delle
posizioni delle k testineUna mossa di MT richiede h.k mosse di RAM:
A criterio di costo costante TR ΘΘΘΘ (TM)
A criterio di costo logaritmico [quello “serio”] TR ΘΘΘΘ (TM.log(TM) (un accesso indiretto a i costa log(i)
79
La correlazione (temporale) tra MT e RAM:
2: Da RAM a MT
(in un caso semplice ma centrale: riconoscimento di linguaggi senza usare MULT e DIV:
la generalizzazione è banale)
• Il nastro principale della MT:
$ ij # M[ij ] $ $ ik # M[ik ] $
• NB:
– Le varie celle RAM sono tenute in ordine
– Inizialmente il nastro è vuoto ---> in un generico istante vi si trovano memorizzate
solo le celle che hanno ricevuto un valore (tramite una STORE)
– ij e M[ij] sono rappresntati in codifica binaria
• Ulteriori nastri:
– Un nastro contiene M[0] (in binario)
– Un nastro di servizio
80• Una mossa della RAM è simulata dalla MT:
$ ij # M[ij ] $ $ ik # M[ik ] $
• Esaminiamone un campione:
• LOAD h:
– Si cerca il valore h nel nastro principale (se non si trova: errore)
– Si copia la parte accanto, M[h] in M[0]
• STORE h:
– Si cerca h. Se non si trova si “crea un buco” usando il nastro di servizio
– Si memorizza h e si copia M[0] nella parte accanto (M[h]); si ricopia la parte
successiva dal nastro di servizio
– Se h esiste già si copia M[0] nella parte accanto (M[h]); ciò può richiedere l’uso del
nastro di servizio se il numero di celle già occupate non è uguale a quelle di M[0].
• ADD* h:
– Si cerca h; si cerca M[h]; …
• Con facile generalizzazione:
• Simulare una mossa di RAM può richiedere alla MT un numero di mosse
maggiorabile da c. lunghezza del nastro principale.
81• A questo punto:
• Lemma: la lunghezza del nastro principale è limitata superiormente da una
funzione Θ(TR)
$ ij # M[ij ] $ $ ik # M[ik ] $
Ogni “cella ij-esima” della RAM richiede nel nastro l(ij) + l(M[ij ]) (+2) celle del nastro.
Ogni “cella ij-esima” esiste nel nastro se e solo se la RAM ha eseguito almeno una
STORE su di essa.
La STORE è costata alla RAM l(ij) + l(M[ij ]) ---->
Per riempire r celle, di lunghezza complessiva
alla RAM occorre un tempo almeno proporzionale allo stesso valore.
Dunque, per simulare una mossa della RAM, la MT impiega un tempo al più
Θ(TR); una mossa di RAM costa almeno 1; se la RAM ha complessità TR esegue
al più TR mosse ---> la simulazione completa della RAM da parte della MT costa
al più Θ(T2R).
])[()(,1
iMlilrj
j +∑=
82
Alcune puntualizzazioni e avvertimenti conclusivi
• Attenzione al parametro di dimensione dei dati:
– lunghezza delle stringa di ingresso (valore assoluto)
– valore del dato (n)
– numero di elementi di una tabella, di nodi di un grafo, di righe di una matrice, …
– …
– tra tali valori sussistono certo relazioni, ma non sempre esse sono lineari (il
numero n richiede una stringa di ingresso di lunghezza log(n)!).
• La ricerca binaria implementata con una MT viola il teorema di correlazione
polinomiale??
• Attenzione all’ipotesi: riconoscimento di linguaggio ---> dati non già in
memoria ---> complessità almeno lineare.
• Operazioni dominanti (e.g. I/O): complessità lineare rispetto alle operazioni
dominanti e quadratica in complesso?
• Caso pessimo e caso medio nei casi pratici (Quicksort, compilazione, …:
eccezioni?)
83
Apriamo infine -fuori programma- una piccola finestra su aspetti avanzati ma
estremamente rilevanti della complessità del calcolo
• Alcune domande importanti:
– Esistono limiti inferiori alla complessità?
– Aumentando la complessità si aumenta (sempre) la classe dei problemi risolvibili?
(se spendo di più ottengo di più?)
– Esiste una sorta di “classe universale di complessità” (tutti i problemi risolvibili si
possono risolvere all’interno di una certa classe)
– Come si definisce una “classe di complessità?
– Ha senso, e se sì, come, definire la complessità di macchine nondeterministiche?
– L’introduzione del nondeterminismo può cambiare la complessità di soluzione dei
problemi?
– ...
84
Concentriamoci sulla computazione nondeterministica
• In primis: come si definisce?
– La computazione più veloce?
– La più lenta?
– E se alcune comoputazioni non terminano e altre sì?
– Solo le computazioni che accettano?
– La computazione più veloce tra quelle che accettano … se ce ne sono!
• Che significato pratico hanno le computazioni nondeterministiche, visto che le
macchine reali sono deterministiche?
• Per rispondere rifacciamoci al tema generale di computazione
nondeterministica: modello per parallelismo, ricerca “cieca” tra diverse vie, …
• Il grande impatto pratico di questo tema nasce proprio dal fatto ….
85
• … che moltissimi problemi di grande interesse pratico hanno semplice,
naturale ed “efficiente” soluzione in termini nondeterministici:
– Il cammino hamiltoniano in un grafo
– La soddisfacibilità di formule logiche proposizionali (requisiti su sistemi finiti)
– ….
• Caratteristica che accomuna la soluzione di tutti questi problemi è che è
“difficile” trovare la soluzione ma è facile verificare se una possibile soluzione
lo è effettivamente:
– se un diavoletto mi dice “prova questa”, verificare se la “soffiata” è giusta o no non
è difficile ---->
– tipici problemi risolti in maniera esaustiva: le “provo tutte”
– in modo nondeterministico: scelgo (ND) una possibile soluzione; verifico se lo è.
– Ovviamente passando alla versione deterministica, provarle tutte diventa molto
oneroso (si ricordi la visita degli alberi)
• Se ne ricava una -grandissima- classe di problemi (contenente gli esempi di
sopra e decine di migliaia di altri problemi):
86
• NP: la classe dei problemi risolvibili nondeterministicamente in tempo
polinomiale
• P: la classe dei problemi risolvibili deterministicamente in tempo polinomiale (i
problemi trattabili)
• La grande domanda: P = NP??
• Molto probabilmente no! Però …
• Se P = NP potremmo risolvere in maniera “efficiente” un’enorme quantità di
problemi oggi intrattabili o affrontati con euristiche, casi particolari, ecc.
• Il concetto di (NP) completezza: un “rappresentante” della classe racchiude in
sé l’essenza di tutti i problemi della classe: troviamo la soluzione per esso e l’abbiamo trovata per tutti!
• Il bello è che nell’enorme congerie di NP, una grandissima quantità di problemi
è a sua volta anche NP-completa: basterebbe risolverne uno in tempo
polinomiale (deterministicamente) e P sarebbe = NP; basterebbe provare per uno solo di essi che è intrattabile e tutti gli altri lo sarebbero pure!
• Infine: nondeterminismo non è sinonimo di casualità però … le affascinanti prospettive della computazione probabilistica.