Torna alla pagina di Informatica Teorica
:: Informatica Teorica - Macchina di Turing ::
Appunti & Dimostrazioni del 21 Aprile
La Macchina di Turing (da ora, MdT
) è un modello per calcolatore general purpose. Si tratta di un modello molto potente, molto più di tutti quelli visti finora, ed è in grado di fare tutto ciò che un computer reale può fare, quindi risolvere problemi che si trovano entro il limite della calcolabilità.
Vediamo le caratteristiche:
Lo schema di una possibile MdT è il seguente:
La MdT come modello di calcolo è stato introdotta nel 1936 da Alan Turing per dare risposta all'Entscheidungsproblem (problema di decisione) proposto da Hilbert nel suo programma di fondazione formalista della matematica.
Dobbiamo creare una MdT M che verifichi l'appartenenza di una stringa al linguaggio:
B = {w#w | w ∈ {0,1}* }
In pratica M dovrà accettare stringhe binarie composte da due sottostringhe identiche separate dal simbolo #, e rifiutare tutte le altre. Come fare? Memorizzare tutti i caratteri di una sottostringa e confrontarli con la seconda sarebbe folle, perché possono essere di lunghezza spropositata. La soluzione è far spostare avanti e indietro la testina lungo il nastro per confrontare i caratteri delle due sottostringhe che si trovano alla stessa posizione, e se questi corrispondono ci mettiamo un bel marcatore per ricordarci che li abbiamo già esaminati.
Questa descrizione non ci basta, ne vogliamo una non troppo formale ma sicuramente più precisa:
M = "su ingresso w:
x
;
x
, controlla se ne rimangono altri non segnati a destra. Se sì, allora RIFIUTA; altrimenti ACCETTA."
Vediamo un esempio di esecuzione (la v
sopra un carattere indica la posizione della testina):
v 0 1 1 0 # 0 1 1 0 _ ... v x 1 1 0 # 0 1 1 0 _ ... v x 1 1 0 # x 1 1 0 _ ... v x 1 1 0 # x 1 1 0 _ ... v x x 1 0 # x 1 1 0 _ ... ... v x x x x # x x x x _ ... accetta!
Una MdT è una 7-upla (Q,Σ,Γ,δ,q0,qaccetta,qrifiuta), dove:
La funzione di transizione δ in pratica dice che quando la macchina si trova in uno stato q e la testina è su una cella che contiene il simbolo b, sostituisce quest'ultimo col simbolo a e si sposta nello stato r. Lo spostamento è verso sinistra se il terzo componente della funzione di transizione è L
; mentre se è R
si sposta verso destra. Riassumendo il tutto, spostandoci ad esempio a sinistra: δ(q,a)=(r,b,L).
Siano dati:
Inizialmente w occupa le prime n celle a sinistra del nastro, e la testina si trova sulla prima cella a sinistra. Poiché l'alfabeto Σ non contiene il simbolo blank, il primo |_| che appare sul nastro coincide con la fine dell'ingresso.
Una volta che M è partita, la computazione procede in accordo alle regole descritte dalla funzione di transizione. Si noti che se ci viene chiesto di spostarci a sinistra del limite sinistro del nastro, rimaniamo fermi sulla prima cella senza sforare. La computazione continua fino a quando non si arriva a un qaccetta o un qrifiuta, nel qual caso la MdT termina; altrimenti va avanti all'infinito.
Durante la computazione, M continua a modificare lo stato corrente, il contenuto del nastro e la posizione della testina. I valori assunti da questi tre elementi ad ogni passo prendono il nome di configurazione della MdT. Le configurazioni possono essere rappresentate in una forma molto semplice, che spieghiamo con un esempio: 010001q301011, significa che il contenuto del nastro è 01000101011, che lo stato corrente è q3, e che la testina si trova sul carattere all'immediata destra dello stato, ovvero sul quinto 0.
Si dice che una configurazione C1 produce una configurazione C2 se la MdT può passare legalmente da C1 a C2 in un unico passo.
Alcune configurazioni importanti di M sono:
Tutte queste definizioni ci servivano per dire che M accetta l'ingresso w se esiste una sequenza di configurazioni C1, ... , Ck tali che:
L'insieme di stringhe che M accetta sono chiamate linguaggio di M, o linguaggio riconosciuto da M, scritto anche L(M).
Chiamiamo un linguaggio Turing-riconoscibile se esiste una MdT che lo riconosce.
Una MdT può accettare, rifiutare o andare in loop su un certo ingresso, dove per loop si intende quel comportamento per cui non si arriva mai a uno stato di terminazione. Dato che è difficile sapere se la fase di loop è temporanea o definitiva, spesso alle MdT generiche si preferisce una variante chiamata decisori, che arrivano sempre a una decisione finale (la stringa è accettata oppure no). Se un decisore riconosce un certo linguaggio, si può dire anche che lo decide.
Chiamiamo un linguaggio Turing-decidibile o semplicemente decidibile se esiste una MdT che lo decide.
Della MdT vedremo due varianti molto importanti: la MdT multinastro e quella n.d.
. Si noti che ognuna di esse ha le stesse potenzialità dato che riconosce gli stessi linguaggi delle altre (sono quindi equivalenti); questa proprietà è detta robustezza.
Una MdT multinastro si differenzia da quelle "standard" per le seguenti caratteristiche:
Come dicevamo, e contrariamente a quanto possa sembrare, la MdT multinastro è equivalente a quella a nastro singolo, e per convincervi scomodiamo un teorema.
Ogni MdT multinastro ha una equivalente MdT a singolo nastro.
Dimostrazione
Ciò che dobbiamo fare è riuscire a convertire una MdT multinastro M nell'equivalente a singolo nastro S. L'idea è copiare il contenuto dei k nastri di M sull'unico nastro di S, e separarli da un carattere delimitatore, ad esempio il #. Per quanto riguarda invece le testine di M, possiamo mettere un pallino sopra ogni carattere in S corrispondente. Nel nostro caso useremo il simbolo °; ad esempio nella stringa w1w°2w3 la testina "virtuale" è sul carattere w2.
Proviamo a dare una descrizione più completa dell'algoritmo di conversione, che tenga conto anche di casi critici come lo spostamento su un simbolo #.
S = "su ingresso w = w1...wn:
Un linguaggio è Turing-riconoscibile se e solo se esiste una MdT multinastro che lo riconosce.
Dimostrazione
Il teorema è un "se e solo se", quindi vanno dimostrati entrambi i sensi delle implicazioni.
(I) Se un linguaggio è Turing-riconoscibile, allora esiste una MdT multinastro che lo riconosce
Dato che un linguaggio è Turing-riconoscibile se esiste una MdT a singolo nastro che lo riconosce, e che questa è un caso particolare di una MdT multinastro (dove il numero di nastri k=1), l'implicazione è banalmente risolta.
(II) Se un linguaggio è riconosciuto da una MdT multinastro, allora è Turing-riconoscibile
Dato che per il Teorema 1 ogni MdT multinastro ha una equivalente MdT a singolo nastro, e che un linguaggio è Turing-riconoscibile se esiste una MdT (a singolo nastro) che lo riconosce, anche la seconda implicazione è banalmente risolta.
Una MdT non deterministica può procedere ad ogni punto della computazione secondo diverse possibilità. La funzione di transizione assumerà dunque la nuova forma:
δ: Q × Γ → P(Q × Γ × {L,R,S})
Ogni MdT n.d.
ha una equivalente MdT deterministica.
Dimostrazione
Dobbiamo dimostrare che è possibile simulare una MdT n.d.
N con una MdT deterministica D. L'idea è semplice: D dovrà tentare tutti i possibili rami di computazione n.d.
di N, e se arriva a uno stato di accettazione, accetta.
Come sappiamo, la computazione di N può essere vista come un albero, in cui ogni ramo rappresenta un ramo del nondeterminismo ed ogni nodo una configurazione di N (ad esempio, la radice è quella di partenza). D dovrà quindi esplorare l'albero alla ricerca di una configurazione di accettazione. Ocio però: non conviene fare un'esplorazione per profondità (dalla radice alla foglia, e poi ancora su) perché potrei incappare in un ramo infinito e non uscirne mai più (è il nondeterminismo, bellezza!). La strategia giusta è quella in larghezza, in cui prima controllo tutti i nodi sullo stesso livello, poi passo a quello più in basso.
Ora che sappiamo cosa fare, cosa facciamo? :|
Costruiamo D come una MdT a 3 nastri (tanto il Teorema 1 ci dice che possiamo sempre convertirla in una a singolo nastro), ognuno dei quali ha un proprio ruolo:
n.d.
di computazione, su cui fa le simulazioni;
n.d.
di N. Ad esempio la sequenza 123
significa che devo partire dalla radice, spostarmi sul suo primo figlio, da qui andare al suo secondo figlio, e infine da qui al suo terzo figlio.
Ecco uno schema:
Descriviamo D, in modo meno formale del solito:
D = "
n.d.
. Prima di ogni passo di N dobbiamo consultare il prossimo simbolo sul terzo nastro, così da capire quale scelta dobbiamo fare tra quelle consentite dalla funzione di transizione di N. Se non ci sono più simboli sul terzo nastro, o quella scelta non è valida, usciamo da questo ramo di computazione e andiamo al passo 4. Stessa cosa se troviamo una configurazione di rifiuto. Se invece troviamo una configurazione di accettazione, D ACCETTA l'ingresso;
Un linguaggio è Turing-riconoscibile se e solo se esiste una MdT n.d.
che lo riconosce.
Dimostrazione
Il teorema è un "se e solo se", quindi vanno dimostrati entrambi i sensi delle implicazioni.
(I) Se un linguaggio è Turing-riconoscibile, allora esiste una MdT n.d.
che lo riconosce
Dato che un linguaggio è Turing-riconoscibile se esiste una MdT deterministica che lo riconosce, e che questa è un caso particolare di una MdT n.d.
, l'implicazione è banalmente risolta.
(II) Se un linguaggio è riconosciuto da una MdT n.d.
, allora è Turing-riconoscibile
Dato che per il Teorema 2 ogni MdT n.d.
ha una equivalente MdT deterministica, e che un linguaggio è Turing-riconoscibile se esiste una MdT deterministica che lo riconosce, anche la seconda implicazione è banalmente risolta.