Location via proxy:   [ UP ]  
[Report a bug]   [Manage cookies]                
Dispense per il corso di Dicembre 2001—Luglio 2003 Fondamenti dell’Informatica: Linguaggi Formali e Calcolabilità Agostino Dovier Dipartimento di Matematica ed Informatica Università degli Studi di Udine Via delle Scienze, 206, Loc. Rizzi 33100 Udine, Italy dovier@dimi.uniud.it Roberto Giacobazzi Dipartimento di Informatica Università degli Studi di Verona Strada Le Grazie 15 37134 Verona, Italy giaco@sci.univr.it 1 Contents Chapter 1. Introduzione 1. Il quadro storico 2. Una prima classificazione 3. Il presente volume 7 7 9 10 Chapter 2. Notazione e concetti di base 1. Insiemi 2. Relazioni e induzione ben fondata 3. Funzioni 4. Cenni di Logica Matematica 5. Cardinalità di insiemi 6. Ordinali 7. Il problema dell’informatica 13 13 14 16 19 20 23 26 Part 1. 29 Linguaggi formali Chapter 3. Automi a stati finiti 1. Alfabeti e Linguaggi 2. Automi 3. Automi deterministici 4. Automi non-deterministici 5. Equivalenza tra DFA e NFA 6. Automi con ε-transizioni 7. Equivalenza di ε-NFA e NFA 8. Automi con output 31 31 32 33 35 36 37 38 39 Chapter 4. Espressioni regolari 1. Operazioni sui linguaggi 2. Definizione formale 3. Equivalenza tra DFA e ER 41 41 41 42 Chapter 5. Proprietà dei linguaggi regolari 1. Il “Pumping Lemma” 2. Proprietà di chiusura 3. Risultati di decidibilità 47 47 49 50 3 4 CONTENTS 4. Il teorema di Myhill-Nerode 5. Minimizzazione di DFA 51 53 Chapter 6. Grammatiche libere dal contesto 1. Definizione formale 2. Linguaggio generato 3. Alberi di derivazione 4. Ambiguità delle derivazioni 5. Semplificazione 6. Forma normale di Chomsky 7. Forma normale di Greibach 57 57 58 59 60 61 65 65 Chapter 7. Automi a pila 69 Chapter 8. Proprietà dei linguaggi liberi dal contesto 1. Il pumping lemma per i linguaggi CF 2. Proprietà di chiusura 3. Algoritmi di decisione 75 75 76 77 Chapter 9. Le grammatiche regolari e la gerarchia di Chomsky 1. Grammatiche Regolari 2. Grammatiche di tipo 0 3. Grammatiche di tipo 1 4. Gerarchia 79 79 81 81 82 Part 2. 85 Teoria della calcolabilità Chapter 10. Nozione intuitiva di algoritmo 1. Requisiti di un algoritmo 2. Funzioni calcolabili 3. Algoritmi e Programmi 87 87 89 90 Chapter 11. Macchine di Turing 1. Descrizione modellistica e matematica 2. Funzioni calcolabili da MdT 3. MdT generalizzate 91 91 96 98 Chapter 12. Funzioni parziali ricorsive di Kleene & Robinson 1. Funzioni primitive ricorsive 2. Diagonalizzazione 3. Funzioni parziali ricorsive 4. Equivalenza tra MdT e funzioni parziali ricorsive 99 99 105 107 109 Chapter 13. Tesi di Church-Turing 113 Chapter 14. Aritmetizzazione e universalità 117 CONTENTS 1. Enumerazione delle MdT 2. Macchina di Turing Universale 3. Il Teorema s-m-n 5 117 121 122 Chapter 15. Problemi insolubili 125 Chapter 16. Calcolabilità e Linguaggi di Programmazione 1. Il linguaggio While 2. Strutture dati 3. Sintassi 4. Semantica 5. Espressività di While e Turing completezza 6. For-calcolabilità e funzioni primitive ricorsive 7. Interpreti e Metaprogrammazione 8. Specializzatori e Proiezioni di Futamura 129 129 130 131 131 134 139 141 144 Part 3. 149 Teoria matematica della ricorsione Chapter 17. Insiemi ricorsivi e ricorsivamente enumerabili 151 Chapter 18. I Teoremi di Ricorsione 1. Il primo Teorema di ricorsione 2. Il secondo Teorema di ricorsione 3. Il Teorema di Rice 4. Proprietà di programmi 161 161 162 163 167 Chapter 19. Riducibilità funzionale e gradi di risolvibilità 1. La relazione di riducibilità  2. Insiemi creativi e produttivi 3. Insiemi semplici 171 172 175 179 Part 4. 181 Complessità Computazionale Chapter 20. Classi di complessità e principali risultati 1. Problemi, insiemi, linguaggi 2. Classi di complessità in tempo e Tesi di Church computazionale 3. Il non determinismo 4. Una inclusione stretta 5. Complessità in spazio 183 184 185 188 190 192 Chapter 21. Riduzioni e NP-completezza 1. Riduzioni tra problemi 2. I Teoremi di Cook 3. Problemi NP-completi 195 195 197 199 6 Bibliography CONTENTS 205 CHAPTER 1 Introduzione ’informatica come scienza si fonda sulla teoria della calcolabilità effettiva L sviluppata nell’ambito dello studio dei fondamenti della matematica, intorno agli anni ’30. Tale avventura intellettuale fornisce non solo le basi concettuali e teoriche dell’informatica, ma anche rappresenta un affascinante viaggio verso i limiti dell’informatica stessa. Ogni disciplina scientifica si definisce pienamente nel momento in cui essa viene delimitata da una teoria in grado di evidenziarne i limiti e le potenzialità. È cosı̀ per la fisica classica e quantistica, per la psicoanalisi, per la chimica etc. Anche l’informatica ha, come ogni scienza, una teoria che ne definisce in modo universale i limiti e le potenzialità. Tale disciplina, la cui nascita si può far risalire agli anni ’30 in un effervescente panorama culturale e scientifico di inizio secolo—sono di quel periodo gli studi sulla materia, sulla meccanica quantistica e sui fondamenti della matematica—si sviluppa pienamente indipendentemente dalla realizzazione, avvenuta solo successivamente negli anni ’40, del primo calcolatore elettronico. Possiamo quindi affermare, in modo forse provocatorio, che il successo attuale dell’informatica realizzata mediante dispositivi elettronici piuttosto che biomolecolari, quantistici o altro, sia stato più un caso che ha voluto la maturazione contemporanea dell’informatica come scienza e dell’elettronica intorno agli anni ’40 e ’50, piuttosto che una necessità intrinseca del processo di calcolo. L’indipendenza da una particolare macchina fisica è uno dei punti di forza dell’informatica come scienza. L’errore comunemente compiuto di identificare l’informatica con il computer moderno, o almeno con l’architettura che conosciamo oggi, ancora basata sulle idee di von Neumann-Turing, limita questa disciplina ad una mera programmazione di particolari macchine. Al contrario, l’informatica prescinde dal particolare strumento di calcolo, sia esso il computer moderno, un insieme di molecole (DNA-computing [24]) o particelle (Quantumcomputing [12]), o sia esso definito da un mero calcolo simbolico (λ-calcolo [3, 11]). In questo senso l’informatica può definirsi a pieno titolo come una scienza universale dell’informazione, ovvero di come l’informazione possa essere codificata, manipolata, valutata, analizzata e misurata. 1. Il quadro storico Questa affascinante avventura intellettuale inizia negli anni ’30 grazie al contributo di eminenti studiosi quali Church, Gödel, Kleene, Post e Turing. Il quadro 7 8 1. INTRODUZIONE storico-scientifico dell’epoca vede il progressivo sgretolarsi delle teorie classiche del ’700 e ’800, sia in ambito fisico che matematico che medico. Non è un caso che la scoperta di leggi fisiche nuove che superano le leggi classiche di Newton nello studio di fenomeni atomici, la relatività di Einstein, la scoperta della psicoanalisi di Freud come strumento per investigare l’inconscio, e gli studi sui fondamenti della matematica compiuti da Hilbert, Russell, Weyl, e Gödel, siano tutti riconducibili ad un medesimo quadro storico: gli inizi del 1900. In questo contesto, si sviluppa in particolar modo la teoria della calcolabilità effettiva. Essa si può ragionevolmente collocare nell’ambito degli studi sui fondamenti della matematica, in particolar modo dell’aritmetica. La necessità di capire profondamente la natura di ciò che è effettivamente costruibile mediante una sequenza di passi elementari di calcolo nasce dalla volontà di costruire effettivamente teorie complesse, quali l’aritmetica. Questo ambizioso programma inizia già nel 1879 con i lavori di Frege [8] aprendo l’era della ricostruzione logica della matematica culminata con i Principia Mathematica di Russell. Le prime critiche a questo programma emergono già con la scoperta dei paradossi. Il paradosso di Russell (1902-3) è destinato a minare alle fondamenta l’impianto teorico su cui si fonda la teoria degli insiemi di Cantor. Esso riguarda l’insieme di tutti gli insiemi che non sono membri di sé stessi. Se chiamiamo T questo insieme, si ha che se T ∈ T allora T 6∈ T e analogamente se T 6∈ T allora T ∈ T . Più volgarmente: consideriamo il barbiere di un villaggio che rade solo coloro del villaggio che non si radono da soli. Il barbiere del villaggio si rade? Paradossi simili si riscontrano già nell’antica grecia, ad esempio il paradosso del mentitore o di Epimenide si fonda su un simile ragionamento “circolare”: L’affermazione: I cretesi sono bugiardi, è attribuita ad Epimenide di Creta VI B.C. È vera questa affermazione? [17] A seguito dell’emergere di paradossi che minano apparentemente alla base il tentativo di ricostruire in chiave puramente logica l’intera matematica, è emersa la necessità di definire una assiomatizzazione completa per la matematica, in particolar modo la teoria degli insiemi su cui essa si fonda. Assiomatizzare una teoria significa definire un insieme di assiomi universalmente validi ed esenti da contraddizioni per quella teoria ed essere in grado di derivare i teoremi che ne conseguono a partire da quegli assiomi. Il contributo di Hilbert in questo quadro è fondamentale. Secondo Hilbert, la formalizzazione di una teoria, mediante la definizione di assiomi e regole di derivazione, comporta una astrazione dal significato degli oggetti manipolati e la definizioni di principi e metodi per studiare il sistema formale risultante. Nel programma di Hilbert è possibile formalizzare in modo completo la matematica, riducendo la matematica stessa ad un mero calcolo simbolico a partire da una sua formalizzazione, ovvero da un insieme di assiomi e regole di inferenza. Il risultato fondamentale che apre definitivamente la strada alla nascita dell’informatica è dovuto a Gödel (1931) [10]. In questo fondamentale lavoro, Gödel dimostra che esistono teoremi dell’aritmetica che non sono decidibili nell’aritmetica stessa. In particolare la verità di una formula non risulta dimostrabile in modo effettivo (ovvero decisa finitamente) nell’aritmetica. L’importanza di questo risultato sta 2. UNA PRIMA CLASSIFICAZIONE 9 nel fatto che esso è del tutto indipendente dal particolare sistema formale scelto, purché sufficientemente espressivo da rappresentare l’aritmetica stessa. In questo senso i risultati di incompletezza di Gödel risultano validi per tutti i sistemi formali derivanti da una assiomatizzazione degli insiemi, aggiungendo un qualsiasi numero finito di assiomi, purché questi non generino inconsistenze. L’impatto di questo risultato è enorme e di portata universale: Gödel dimostra l’esistenza di questioni riguardanti i numeri che non sono decidibili nella teoria stessa ed in ogni sua finita estensione consistente. In questo senso, Gödel stabilisce i limiti stessi della formalizzazione di teorie quali l’aritmetica, e pone le basi per il successivo ragionamento su ciò che è effettivamente costruibile. In questo contesto nasce quindi la necessità di approfondire la natura stessa del calcolo logico-formale e di come da questo sia possibile derivare in modo sistematico enunciati la cui verità sia decidibile. Il fallimento quindi dell’idea di ridurre l’intera matematica ad un mero calcolo automatico a partire da assiomi e regole di inferenza, costituisce il terreno ideale per lo studio di ciò che effettivamente è calcolabile in questo senso, ovvero stabilisce i limiti di quella disciplina che oggi si chiama informatica. La necessità di formalizzare il processo di calcolo, sia mediante la definizione di una macchina calcolatrice che attraverso sistemi formali di calcolo quali il λ-calcolo, ha portato all’analisi della calcolabilità di Turing e Church nel 1936 [4, 30]. Dai loro lavori scaturisce di fatto la teoria presentata in modo didattico nel presente volume e si definiscono le basi per i successivi sviluppi pratici e teorici del calcolo mediante calcolatore. Ad esempio è possibile garantire in modo formale l’esistenza di una macchina universale (calcolatore programmabile) o caratterizzare rigorosamente la nozione di programma come sequenza di istruzioni, a prescindere dall’esistenza di una macchina fisica, insieme di circuiti e apparecchiature, in grado di eseguire tali programmi. Si dimostra l’esistenza di funzioni non calcolabili e, di conseguenza, l’esistenza di problemi non risolvibili in modo completo e automatico mediante calcolatore. 2. Una prima classificazione Una prima classificazione dei problemi tra quelli che possono essere risolti mediante algoritmi e quelli che non ammettono tale soluzione, è ottenibile applicando il Teorema di Cantor. Vedremo nel seguito, nel capitolo 2, questo teorema applicato alla determinazione della cardinalità di insiemi di funzioni. Tuttavia, è possibile darne una dimostrazione del tutto informale, ricorrendo a semplici nozioni intuitive, senza ricorrere a notazioni più complesse sui numeri. Questa dimostrazione permette di dare una prima classificazione, piuttosto grossolana, degli insiemi e distinguere, se non altro per quanto riguarda la loro “dimensione”, tra i problemi che possono essere definiti matematicamente e quelli che invece ammettono una soluzione effettiva o algoritmica. Supponiamo di avere a disposizione un linguaggio per programmare una macchina. Tale linguaggio è caratterizzato da un insieme finito di istruzioni in grado di far eseguire operazioni più o meno complesse alla macchina. I dati sono semplici numeri 10 1. INTRODUZIONE naturali. Un algoritmo è quindi una sequenza finita di istruzioni nel linguaggio della macchina. Secondo queste ipotesi, è quindi possibile mettere in sequenza tutti i possibili algoritmi, ovvero enumerare gli algoritmi cosı̀ come è possibile enumerare i numeri naturali: P0 , P1 , P2 , . . . , Pn , . . . Se un algoritmo è destinato a manipolare dati in input , producendo dati in output , allora un problema è rappresentabile come una funzione ovvero una associazione tra dati in input e dati in output. Nell’esempio, un problema è quindi una funzione sui numeri naturali: f : N −→ N. Un problema ammette soluzione algoritmica se esso è programmabile da un algoritmo, ovvero se esiste un n tale che Pn programma la funzione del problema: per ogni x ∈ N, Pn (x) = f(x). Costruiamo allora il seguente problema h : N −→ N: input: n determina il programma Pn calcola Pn (n) output: Pn (n) + 1 Se il linguaggio è sufficientemente potente per poter programmare il precedente problema, allora esso corrisponderà ad una sequenza di istruzioni del linguaggio, ovvero esisterà n0 tale che Pn0 = h. Calcoliamo ora h(n0 ): h(n0 ) = Pn0 (n0 ) + 1 = h(n0 ) + 1 Questo è un assurdo, poiché nessun numero è uguale al suo successore. Da questo assurdo segue il fatto che esistono problemi, ovvero funzioni input/output, non programmabili, ovvero per le quali non è possibile costruire un algoritmo che le risolva. Le ipotesi molto deboli del precedente ragionamento lo rendono applicabile a tutti i linguaggi di programmazione noti. Questa prima osservazione stabilisce già una ripartizione tra i problemi definibili matematicamente come funzioni e quelli effettivamente risolvibili mediante una macchina calcolatrice. Dimostrazioni come quella appena vista saranno ricorrenti nel presente volume. 3. Il presente volume In questo volume presenteremo le principali nozioni e risultati che riguardano la teoria della calcolabilità effettiva e della ricorsione. La conoscenza di queste nozioni è indispensabile per intraprendere l’avventura intellettuale che ha portato alla nascita dell’informatica, per conoscerne i limiti e le potenzialità e per poterne tracciare i confini nel panorama delle scienze. Studiare la calcolabilità significa capire se un dato problema è risolvibile mediante calcolatore. Al contrario dello studio della complessità (cf. [9, 23]), presentato nella parte finale del testo, che studia le condizioni per cui un dato problema è risolvibile o meno avendo a disposizione una quantità limitata di risorse, siano esse il tempo o la memoria (spazio), la calcolabilità non pone restrizioni alle risorse disponibili. Capire se un problema 3. IL PRESENTE VOLUME 11 è calcolabile significa capire se esso ammette una soluzione algoritmica indipendentemente da quanto tempo/spazio è necessario per risolverlo. Gli strumenti utilizzati per studiare la calcolabilità sono per molti aspetti simili a quelli utilizzati per studiare la complessità dei problemi (ad esempio il concetto di riduzione funzionale è presente nella definizione di completezza sia per classi di ricorsività che per classi di complessità). In questo senso, la teoria della calcolabilità è di fatto propedeutica ad ogni altra teoria che studi modelli per il calcolo automatico, sia essa la complessità, la semantica etc. . . Scopo di questo testo è quello di fornire i risultati principali che permettono di analizzare un problema, classificandolo in termini della sua risolvibilità algoritmica. L’approccio seguito in questo testo non corrisponde allo sviluppo storico dei concetti presentati. Partendo da semplici macchine a stati finiti, raggiungeremo i confini di ciò che è calcolabile arricchendo via via le nostre macchine con strutture dati opportune per memorizzare informazioni. Scopriremo poi che l’architettura del computer moderno corrisponde in tutto e per tutto all’architettura ideale della Macchina di Turing (1937) [30], ottenibile arricchendo semplici automi a stati finiti con opportune strutture dati. In questo modo arriveremo allo studio astratto (matematico) della calcolabilità effettiva attraversando la teoria dei linguaggi formali e delle macchine preposte al loro riconoscimento. Lo studio formale dei linguaggi generabili a partire da un dato alfabeto è alla base dello sviluppo delle tecniche di traduzione dei moderni linguaggi di programmazione, quali interpreti e compilatori. Lo studio della complessità fornisce metodologie che permettono al programmatore di stabilire a priori il grado di difficoltà in termini di tempo di esecuzione e/o consumo di memoria del problema da risolvere, che rappresenta il limite da raggiungere dall’algoritmo sviluppato per risolverlo. Tali metodologie e risultati devono costituire, per un informatico, ma più in generale per chi utilizza professionalmente un calcolatore, una base di conoscenze indispensabile come le nozioni fondamentali di algebra e di analisi lo sono per il matematico. In letteratura vi sono molti ottimi testi sull’argomento (si consulti la Bibliografia). La riorganizzazione a “moduli” di 4–6 crediti (30–50 ore di didattica frontale a seconda delle scelte della sede) del sistema universitario italiano rende tuttavia molti di questi testi sovrabbondanti oppure troppo avanzati oppure orientati ad un pubblico con solide basi matematiche (per esempio i testi di HopcroftUllman [13], di Rogers [27] o di Cutland [6]). Si è pertanto cercato di fornire una presentazione il più possibile adatta a studenti del Corso di Laurea in Informatica orientata a uno o due possibili corsi di 6 crediti del II–IV anno. Gli unici prerequisiti sono una conoscenza di base della teoria elementare degli insiemi e di logica che vengono comunque richiamati nel Capitolo 2. Riteniamo pertanto che il testo possa essere utilizzato in un corso di Fondamenti teorici dell’Informatica nei Corsi di laurea in Informatica, Matematica, Fisica, Statistica e Ingegneria. Il testo è diviso in 4 parti, per ciascuna delle quali sono necessarie poco meno di 30 ore. Complessivamente, pertanto, il testo è complessivamente strutturato per due corsi di 6 crediti l’uno. 12 1. INTRODUZIONE Per quanto gli autori comprendano ma non condividano la scelta di un unico corso di Fondamenti da 6 crediti, ritengono che il testo possa comunque venire utilizzato anche in tale contesto. Il contenuto di diversi capitoli della Parte 2, ed i principi generali della parte 4, infatti, vengono talvolta anticipati in altri corsi (per esempio Programmazione, Linguaggi di Programmazione o Algoritmi e Strutture Dati). Questo consente di ridurre a meno di 20 ore la parte 2 e, eventualmente, di omettere la parte 4 o di riassumerne i principali risultati in circa 4 ore. Omettendo inoltre parte delle dimostrazioni dei paragrafi 6.5, 6.6 e 6.7, la Parte 1 può a sua volta essere svolta in circa 20 ore. Le ore rimanenti del corso possono essere impiegate per i principali risultati della parte 3, in particolare per il Capitolo 17. I Teoremi di ricorsione e la riducibilità funzionale possono eventualmente essere omessi in un unico corso di Fondamenti di 6 crediti. Il presente volume è corredato da un relativo eserciziario, con esercizi risolti, realizzato dalla Dott.ssa Isabella Mastroeni. Si intende ringraziare gli studenti del corso di laurea in Informatica dell’Università di Verona per i loro commenti che hanno aiutato a chiarire e correggere alcune parti di questo testo. Si ringraziano Isabella Mastroeni, Mila Dalla Preda, Carla Piazza, Andrea Fusiello e Nicola Vitacolonna per le discussioni ed osservazioni relative alla redazione del presente volume, e tutti gli studenti del III e IV anno del Corso di Laurea in Informatica dell’Università di Verona, A.A. 1998–99, per il loro attivo contributo alla realizzazione di questo testo. Un grazie particolare ad Antonella Meneghetti per l’attenta rilettura delle bozze. Gli autori saranno grati a tutti coloro che avessero la gentilezza di segnalare sviste ed errori o volessero comunque far pervenire i loro commenti sul contenuto e la forma del testo. Dicembre 2001—Settembre 2003 Agostino Dovier Roberto Giacobazzi CHAPTER 2 Notazione e concetti di base questo breve capitolo viene stabilita la notazione impiegata nel testo relativaI nmente ai concetti base di matematica e logica utilizzati. Con ciò non si intende fornire una presentazione auto-contenuta di tali concetti, per la quale si rimanda ai testi classici, quali ad esempio [7, 20, 16]. 1. Insiemi Con lettere latine maiuscole denoteremo in genere insiemi di oggetti. Alcuni insiemi hanno un nome particolare che li identifica univocamente: ad esempio N rappresenta l’insieme dei numeri naturali (N = {0, 1, 2, 3, . . .}). x ∈ A significa che x è un elemento dell’insieme A. Mediante la rappresentazione intensionale di insiemi {x | E(x)} si identifica l’insieme costituito dagli x che soddisfano (rendono vera) una data espressione E, la quale dipende da x ed ha valori booleani. Se gli elementi di un insieme hanno un indice e sono tutti rappresentabili come una sequenza (anche infinita) di elementi xi al variare di i ∈ I, allora l’insieme costituito da questi oggetti è rappresentato con {xi }i∈I . A ⊆ B indica che A è un sottoinsieme di B, ovvero che ogni elemento di A è anche elemento di B. Con la notazione A ⊂ B si denoterà l’inclusione stretta ovvero vale che A ⊆ B e A 6= B. Con ℘(A) si denota l’insieme delle parti dell’insieme A, ovvero l’insieme costituito da tutti i suoi sottoinsiemi: ℘(A) = {X | X ⊆ A}. Unione ed intersezione di insiemi sono rispettivamente denotati con i simboli ∪ e ∩. Il simbolo \ rappresenta la differenza insiemistica: A\B = {x | x ∈ A ∧ x 6∈ B}. Ā denota il complemento di A, ovvero x ∈ Ā se e solo se x ∈ / A. Se assumiamo (come faremo spesso nel seguito) che gli insiemi sono insiemi di numeri naturali, allora considerando N come insieme universo, si ha che Ā = N \ A. Dati due o più insiemi, è possibile costruire coppie, triple, etc. di oggetti. In generale una n-upla di oggetti x1 , . . . , xn di un insieme A è rappresentata con hx1 , . . . , xn i. L’insieme delle n-uple di elementi di A è rappresentato con An e corrisponde al prodotto cartesiano di A n-volte. Insiemi diversi possono essere messi in prodotto cartesiano: A1 × A2 × · · · × An = {hx1 , . . . , xn i | x1 ∈ A1 , . . . , xn ∈ An } indica l’insieme delle n-uple di oggetti appartenenti rispettivamente agli insiemi A1 , . . . , An . 13 14 2. NOTAZIONE E CONCETTI DI BASE 2. Relazioni e induzione ben fondata Una relazione (binaria) è un sottoinsieme del prodotto cartesiano di (due) insiemi; dati A e B, R ⊆ A × B è una relazione su A e B. Ad esempio, la relazione di ordinamento sui numeri naturali “≤” ⊆ N × N è definita nel modo seguente: ≤ = {hx, yi | x ∈ N, y ∈ N, x ≤ y}. Il fatto che hx, yi ∈ ≤ viene solitamente indicato con x ≤ y. Una relazione binaria su un insieme, R ⊆ S × S, stabilisce una precedenza tra gli oggetti di S: se ha, bi ∈ R diremo che a precede b. R verrà spesso chiamata relazione di precedenza. R è detta: riflessiva: se per ogni a ∈ S si ha che a R a, simmetrica: se per ogni a, b ∈ S si ha che se a R b allora b R a, e transitiva: se per ogni a, b, c ∈ S si ha che se a R b e b R c allora a R c. Una relazione R di equivalenza è una relazione binaria riflessiva, simmetrica e transitiva. Per ogni relazione R ⊆ S × S, la chiusura transitiva di R è il più piccolo insieme R∗ tale che: ha, bi ∈ R∗ ∧ hb, ci ∈ R → ha, ci ∈ R∗ . Trattandosi di una definizione induttiva, la chiusura transitiva di una relazione R è ottenibile come unione delle seguenti relazioni Ri per i ∈ N:   R1 = R  Ri+1 = ha, ci : ha, bi ∈ Ri ∧ hb, ci ∈ R S ovvero R∗ = i∈N Ri . Una relazione binaria R ⊆ S × S è un preordine se essa è riflessiva e transitiva. R è un ordine parziale se è un preordine ed è antisimmetrica, ovvero aRb ∧ bRa → a=b Useremo nel seguito i simboli ⊑, , ≤, . . . per rappresentare relazioni d’ordine parziale. Un insieme S con una relazione di precedenza (non necessariamente una relazione d’ordine) ≺⊆ S × S è rappresentato nel modo seguente: (S, ≺). Se ≺ è una relazione d’ordine parziale, (S, ≺) è un insieme parzialmente ordinato. (S, ) è un ordinamento totale se, per ogni x, y ∈ S, o x  y oppure y  x. Sia (S, ≺) un insieme con relazione di precedenza ≺. L’elemento x ∈ S è minimale rispetto a ≺ se ∀y ∈ S. y 6≺ x. Gli elementi massimali di un insieme con relazione di precedenza, quando esistono, sono definiti invertendo la relazione ≺. Ovvero x ∈ S è massimale rispetto a ≺ se ∀y ∈ S. x 6≺ y. Esempio 2.1. Sia N l’insieme dei numeri naturali, e ≺ la relazione definita da def x ≺ y = y = x + 1. In altri termini, x ≺ y se e solamente se y è il successore di x. L’unico elemento minimale rispetto a ≺ è 0. Analogamente, possiamo estendere def ≺ all’insieme Z dei numeri interi stipulando che (x ≺ y = y = x + 1) anche per i numeri negativi. Non esistono, però, in Z elementi minimali rispetto a ≺. Un esempio di relazione di precedenza non banale su N è dato invece dalla seguente definizione di ❁: def x ❁ y = x 6= y ∧ divide(x, y) 2. RELAZIONI E INDUZIONE BEN FONDATA 15 dove divide(x, y) esprime il fatto che x è un divisore di y. L’unico elemento minimale di (N, ❁) è 1. Cos’è la chiusura transitiva di ≺? Una relazione binaria R ⊆ S × S è ben-fondata se per ogni insieme non vuoto Y ⊆ S esiste z ∈ Y tale che hy, zi 6∈ R per ogni y ∈ Y \ {z}. Se la relazione in questione è una relazione di ordine parziale , allora  è ben-fondata se ogni insieme non vuoto Y ⊆ S ha un elemento minimale. Se R ⊆ S × S è ben-fondata, allora si dirà pure che S (o, con abuso di notazione, (S, R)) è un insieme benfondato. Veniamo all’enunciato del principio di induzione ben fondata, che generalizza i principi di induzione sui naturali. Sia S un insieme. Il seguente principio di induzione ben fondata permette di dimostrare proprietà di insiemi generici, ovvero non solo di numeri naturali, equipaggiati con una relazione ben fondata. Teorema 2.2 (Induzione Ben Fondata). Sia (S, ≺) un insieme ben fondato e sia φ(x) una proprietà su S. Allora vale il seguente asserto: se allora ∀x ∈ S. ∀y, y ≺ x. φ(y) → φ(x) ∀x ∈ S. φ(x). Una lettura informale del principio di induzione ben fondata può essere data come segue: per dimostrare che un asserto φ(x) vale per ogni elemento x di un insieme ben fondato (S, ≺) è sufficiente dimostrare che φ vale su un generico elemento x di S, nell’ipotesi che φ valga su ogni elemento y di S che precede x secondo ≺. Fissato un generico x in S, la formula ∀y, y ≺ x. φ(y) viene detta ipotesi induttiva rispetto a x. Si noti che, in pratica, la dimostrazione di un asserto φ basata sul principio di induzione ben fondata avviene in due passi. Analogamente al caso dell’induzione sui naturali, il primo passo della dimostrazione viene comunemente detto caso base, mentre il secondo viene detto caso induttivo. Caso Base: si dimostra che φ vale su tutti gli elementi di S minimali rispetto a ≺ (ricordiamo che dato (S, ≺), un elemento x ∈ S si dice minimale se non esiste nessun elemento y ∈ S che preceda x). Infatti, se x è minimale, l’insieme degli y ∈ S tali che y ≺ x è vuoto, e dunque l’ipotesi induttiva rispetto a x si riduce a True. Di conseguenza la formula (∀y, y ≺ x. φ(y)) → φ(x) si riduce semplicemente a φ(x). Caso induttivo: detto x un generico elemento di S, non minimale rispetto a ≺, si dimostra la validità di φ(x) utilizzando, laddove si renda necessaria, l’ipotesi induttiva rispetto a x, ovvero l’ipotesi che φ stessa vale su tutti gli elementi di S che precedono x. 16 2. NOTAZIONE E CONCETTI DI BASE Si osservi come il principio di induzione ben fondata enunciato sopra, generalizzi entrambi i principi di induzione matematica sui naturali, ed induzione completa. Il principio di induzione matematica sui naturali coincide con il principio di induzione ben fondata rispetto all’insieme con precedenza ben fondato (N \ {0, . . . , n0 − 1}, ≺), dove ≺ è definita come segue: def x ≺ y = y = x + 1. Il principio di induzione completa corrisponde invece al principio di induzione ben fondata rispetto alla chiusura riflessiva e transitiva di ≺, ovvero rispetto all’insieme ben fondato (N \ {0, . . . , n0 − 1}, <) (vedi esempio 2.1). Prima di vedere alcuni esempi di uso del principio di induzione ben fondata, ne diamo una dimostrazione informale. Dimostrazione: Sia (S, ≺) un insieme ben fondato e sia φ una proprietà su S per cui l’ipotesi del teorema di induzione ben fondata è verificata. Consideriamo l’insieme X = {x | ¬φ(x)}. Quindi, X 6= ∅ se e solo se esiste un elemento x0 ∈ S per cui non vale φ. Supponiamo per assurdo che esista x0 ∈ X. Se x0 è minimale abbiamo immediatamente una contraddizione con la validità del principio di induzione ben fondata, visto che, come abbiamo appena osservato, tale principio garantisce la validità di φ su tutti gli elementi minimali di S (Caso Base). Dunque x0 non è un elemento minimale: deve allora esistere un elemento x1 ∈ S tale che x1 ≺ x0 e x1 6= x0 , per il quale non vale φ. Se cosı̀ non fosse, infatti, avremmo di nuovo una contraddizione con la validità del principio di induzione ben fondata, che garantisce la validità di φ su ogni elemento non minimale x, nell’ipotesi che φ valga su tutti gli elementi che precedono x. Possiamo ora ripetere questo ragionamento su x1 . Di nuovo quest’ultimo non può essere un elemento minimale e deve esistere un elemento di S, sia esso x2 che precede x1 (x2 ≺ x1 e x2 6= x1 ), e su cui non vale φ, etc. In questo modo possiamo costruire una sequenza infinita di elementi, tutti distinti tra loro, tali che · · · ≺ xk ≺ xk−1 ≺ · · · ≺ x2 ≺ x1 ≺ x0 e tali che, per ogni xi nella sequenza, φ(xi ) non vale. Ma, osservando che la sequenza cosı̀ costruita è una catena decrescente infinita, contraddiciamo l’ipotesi che (S, ≺) sia un insieme ben fondato, ovvero che non contenga catene decrescenti infinite. 3. Funzioni Consideriamo una relazione n-aria, ovvero R ⊆ A1 ×· · ·×An . Sia k < n. Una relazione R n-aria è una funzione di k argomenti se, dati x1 ∈ A1 , . . . , xk ∈ Ak , esiste uno ed un solo zk+1 ∈ Ak+1 , . . . , zn ∈ An tale che hx1 , . . . , xk , zk+1 , . . . , zn i ∈ 3. FUNZIONI 17 R. In questo caso, la funzione è definita in A1 × · · · × Ak ed ha valori in Ak+1 × · · · × An , ovvero è del tipo: R : A1 × · · · × Ak −→ Ak+1 × · · · × An Nel caso n = 2 e k = 1, otteniamo la definizione usuale di funzione di un argomento: R : A1 −→ A2 come relazione binaria R ⊆ A1 × A2 . A1 è detto dominio di R mentre A2 è il suo co-dominio. Mentre una funzione è anche a sua volta una relazione, il viceversa non vale sempre. È sempre possibile invece associare una funzione ad una relazione, modificandone il codominio: Sia R ⊆ A × B una relazione, definiamo fR : A −→ ℘(B) come segue:  fR (x) = y : x R y Denoteremo come ~x una generica n-upla (vettore) di oggetti. La lunghezza di tale n-upla si evincerà dal contesto. Una funzione è iniettiva se f(~x1 ) = f(~x2 ) implica che ~x1 = ~x2 per ogni ~x1 , ~x2 ; è suriettiva se per ogni elemento ~y del codominio (nel caso sopra Ak+1 ×· · ·×An ) esiste ~x nel dominio (nel caso sopra A1 ×· · ·×Ak ) tale che f(~x) = ~y; è biiettiva se è iniettiva e suriettiva. Sia f : A −→ B una funzione. Con f(D) indichiamo l’immagine di D secondo f. L’immagine f(D) è definita per ogni D ⊆ A come l’insieme:  f(D) = y ∈ B : y = f(x) ∧ x ∈ D Con f−1 indichiamo l’immagine inversa di f. L’immagine inversa di una funzione è un insieme cosı̀ definito per ogni D ⊆ B:  f−1 (D) = x ∈ A : f(x) ∈ D Una funzione può non essere definita su alcuni dei suoi argomenti, ovvero se la funzione di k argomenti è data dalla relazione R ⊆ A1 × · · · × An , con k < n, allora possono esistere degli x ∈ Ai argomenti di R (per i ≤ k) tali che nessuna n-upla h. . . , x, . . .i avente x nella i-esima posizione appartiene a R. In questo caso diremo che la funzione R è parziale. Al contrario, una funzione sempre definita su ogni argomento è detta totale. Con la notazione A −→ B si intende l’insieme delle funzioni totali da A a B. Nota 2.3. Normalmente si usa parlare di funzioni f : A −→ B e f(x) è definita per ogni elemento x dell’insieme A (dominio). Ad esempio, la funzione f(x) = 1000 div x, ove div ritorna il quoziente della divisione intera, è definita da A = N \ {0} a B = N. In questo testo, per mantenere un’analogia con le funzioni calcolabili mediante un calcolatore (che potrebbe non fornire risultato per certi valori di input), si preferisce parlare di f : N −→ N e parlare di funzioni parziali. Nel seguito faremo largo uso di funzioni parziali e totali. Al fine di evitare confusione, saremo soliti rappresentare le funzioni secondo la notazione usuale: R : A1 × · · · × Ak −→ Ak+1 × · · · × An 18 2. NOTAZIONE E CONCETTI DI BASE identificando in questo modo gli insiemi su cui vengono considerati gli argomenti e gli insiemi su cui la funzione restituisce risultati. In particolare, rappresenteremo con lettere minuscole latine le funzioni totali; ad esempio: f : A1 × · · · × Ak −→ Ak+1 × · · · × An rappresenta una funzione totale di k argomenti, mentre rappresenteremo con lettere minuscole greche le funzioni parziali: ϕ : A1 × · · · × Ak −→ Ak+1 × · · · × An rappresenta una funzione parziale di k argomenti. Poiché nell’insieme delle funzioni parziali vi sono anche le funzioni totali, nel caso non vi siano ipotesi sulla totalità della funzione considerata, rappresenteremo con lettere greche minuscole una generica funzione. Per indicare che una funzione ϕ è definita in x, scriveremo ϕ(x) ↓. Analogamente, per indicare che una funzione ϕ non è definita in x, scriveremo ϕ(x) ↑, o equivalentemente ϕ(x) = ↑. Il simbolo ↑ rappresenta la non definizione della funzione ϕ. Supponiamo quindi di ammettere come possibile valore della funzione anche il valore indefinito ↑. Questo ci permette di vedere le funzioni parziali come funzioni totali del seguente tipo: ϕ : A1 × · · · × Ak −→ (Ak+1 ∪ {↑}) × · · · × (An ∪ {↑}) Data dunque una generica funzione (parziale o totale) ϕ chiameremo dominio della funzione ϕ l’insieme su cui ϕ è definita, ovvero l’insieme: dom(ϕ) = {x | ϕ(x) ↓} Analogamente chiameremo immagine o range di ϕ l’insieme dei risultati definiti della funzione, ovvero: range(ϕ) = {x | ∃z. ϕ(z) = x 6=↑} Un valore della funzione ϕ è un elemento di range(ϕ). In genere la definizione di una funzione f(X, Y) = X + Y crea un’associazione (relazione) tra il nome f della funzione ed il corpo della funzione stessa X + Y. Eseguire f non significa altro che eseguire il suo corpo. In alcuni contesti è utile potersi riferire ad una funzione senza essere costretti ad associare ad essa un nome; il concetto di funzione ha infatti un suo significato a prescindere dal nome che gli viene assegnato con la definizione. A questo scopo viene introdotta la cosiddetta λ-notazione. Per esempio la funzione che riceve due argomenti e ne restituisce la somma può essere definita con: λX, Y . X + Y dove λ è un simbolo speciale, generalmente seguito da una lista di variabili separate da una virgola (i parametri della funzione). Il corpo della funzione segue il punto: si tratta di un’espressione in cui possono apparire le variabili introdotte dopo il simbolo λ. Queste variabili risultano legate all’interno dell’espressione (bound variables), mentre si definiscono libere quelle variabili che appaiono nel corpo e non appaiono dopo il simbolo lambda. 4. CENNI DI LOGICA MATEMATICA 19 L’applicazione di una funzione definita con la λ-notazione ha la seguente forma: alla definizione della funzione racchiusa fra parentesi seguono tante espressioni quanti sono i parametri. Ad esempio: (λX, Y . X + Y)7 4 associa 7 a X e 4 a Y e restituisce 11. 4. Cenni di Logica Matematica Un linguaggio del prim’ordine L è costituito da • un insieme di simboli relazionali o predicativi, • un insieme di simboli funzionali, • un insieme di simboli di costante e da • un insieme di simboli logici. I simboli logici presenti in ogni linguaggio sono: • le parentesi “)”, “(” e la virgola “,” • un insieme numerabile di variabili v0 , v1 , . . . • un insieme di connettivi: ¬, ∧, ∨, →, ↔ • i quantificatori ∀ (per ogni) ed ∃ (esiste). Si ricorda che l’implicazione e la doppia implicazione sono superflue in quanto ϕ → ψ è equivalente a (¬ϕ)∨ψ mentre ϕ ↔ ψ è equivalente a (ϕ → ψ)∧(ψ → ϕ). Anche uno tra ∨ e ∧ è superfluo (si vedano le leggi di De Morgan più avanti), mentre uno solo dei due quantificatori è sufficiente. Tuttavia tali operatori, di uso comune, sono usualmente accettati per fornire maggiore chiarezza alle formule.1 Un termine di un linguaggio del prim’ordine è definito ricorsivamente nel seguente modo: (1) una variabile v è un termine; (2) un simbolo costante c è un termine; (3) se t1 , . . . , tm sono termini e f è un simbolo funzionale m-ario, allora f(t1 , . . . , tm ) è un termine. Se p è un simbolo relazionale n-ario di L e se t1 , . . . , tn sono termini allora p(t1 , . . . , tn ) è una formula atomica (o atomo). Una formula di un linguaggio del prim’ordine è definita ricorsivamente nel seguente modo: (1) una formula atomica è una formula; (2) se ϕ è una formula allora (¬ϕ) è una formula; (3) se ϕ e ψ sono formule, anche (ϕ ∨ ψ), (ϕ ∧ ψ) e (ϕ → ψ) sono formule; (4) se ϕ è una formula e v è una variabile allora ∀v(ϕ) e ∃v(ϕ) sono formule. Per semplificare la notazione, si assumono le comuni convenzioni per eliminare, qualora ciò non generasse ambiguità, alcune coppie di parentesi dalle formule. Il significato (la semantica) di una formula del prim’ordine è stabilito qualora venga fornita una interpretazione, associata ad un insieme non vuoto detto dominio (ad esempio, l’insieme dei numeri naturali N) 1 Un solo operatore, ad esempio il nor definito come ∨(ϕ, ψ) = ¬(ϕ ∨ ψ) è in realtà sufficiente, in quanto ¬ϕ = ∨(ϕ,ϕ) e ϕ ∨ ψ = ∨(∨(ϕ,ψ), ∨(ϕ,ψ)). 20 2. NOTAZIONE E CONCETTI DI BASE • per i simboli di costante (ad esempio, la costante c è il numero 5, d il numero 10), • per i simboli di funzione (ad esempio, il simbolo funzionale binario f sta a significare il ‘+’ tra numeri interi) e • venga assegnata un’interpretazione ai predicati sul dominio, ovvero, in parole povere, un valore di verità (vero oppure falso) agli atomi costruiti con i simboli predicativi e gli oggetti del dominio (ad esempio, f(5, 5) = 10 è vero). Fissata una interpretazione per un linguaggio del prim’ordine, usando la semantica degli operatori, è possibile estendere l’interpretazione ad ogni formula chiusa (in cui ogni variabile che vi occorre è quantificata) del linguaggio. Ad esempio, nell’interpretazione accennata sopra, la formula ∃v(v = f(c, c)) è vera in quanto per v = 10 è soddisfatta, mentre la formula ∀v(v = f(c, c)) è falsa (per esempio, si prenda v = 11). Per quanto concerne l’utilizzo della logica del prim’ordine nel prosieguo, sarà fondamentale saper negare una formula logica, ovvero data una formula ϕ, scrivere in modo equivalente ma più leggibile la formula ¬ϕ. Relativamente alle formule che non hanno quantificatori come simboli più “esterni”, le regole da utilizzare sono le seguenti: Doppia negazione: ¬(¬ϕ) = ϕ De Morgan 1: ¬(ϕ ∧ ψ) = (¬ϕ) ∨ (¬ψ) De Morgan 2: ¬(ϕ ∨ ψ) = (¬ϕ) ∧ (¬ψ) Si osservi in particolare che¬(ϕ → ψ) è equivalente a ϕ∧¬ψ. Per quanto riguarda la semplificazione della negazione di quantificatori valgono: • ¬∃ϕ = ∀¬ϕ • ¬∀ϕ = ∃¬ϕ Pertanto, ad esempio, ¬∀n∃y(r(y) ∧ y > n ∧ ∃u∃v(y = u + v ∧ ∀i(r(u + vi )))) ove r è un simbolo unario di predicato, risulta essere: ∃n∀y(¬r(y) ∨ y ≤ n ∨ ∀u∀v(y 6= u + v ∨ ∃i(¬r(u + vi )))) Esplicitando le implicazioni, quest’ultima diventa: ∃n∀y((r(y) ∧ y > n) → ∀u∀v(y = u + v → ∃i(¬r(u + vi )))) 5. Cardinalità di insiemi Nel seguito, se S è un insieme, rappresentiamo la sua cardinalità con il simbolo |S|. In questa sezione richiamiamo i concetti di base in relazione alla cardinalità di insiemi. Diciamo che due insiemi A e B sono equipotenti se esiste una funzione biiettiva f : A −→ B. Se A e B sono equipotenti, scriveremo A ≈ B. |A| ≤ |B| se esiste una funzione iniettiva f : A −→ B. Si osservi che la funzione f stabilisce una corrispondenza tra gli elementi dei due insiemi. L’iniettività assicura 5. CARDINALITÀ DI INSIEMI 21 che la corrispondenza è stabilita elemento per elemento, mentre la suriettività assicura che la quantità degli oggetti nei due insiemi coincide. Per insiemi finiti, la cardinalità sarà quindi un numero naturale, corrispondente al numero di oggetti contenuti nell’insieme. Per insiemi infiniti invece, dobbiamo ricorrere ad una generalizzazione. Più formalmente quindi |A| rappresenta la collezione degli insiemi Y tale che Y ≈ A. Tale collezione è detta appunto cardinalità di A. È evidente che se A ⊆ B allora |A| ≤ |B|. Nel seguito saremo particolarmente interessati ad insiemi la cui cardinalità è infinita e numerabile, ovvero che possono essere enumerati, stabilendo un primo, secondo, terzo, etc. elemento. Questo interesse è giustificato dal fatto che i dati manipolati in informatica sono enumerabili, ovvero è possibile metterli in corrispondenza biunivoca con i numeri naturali, essendo essi rappresentati da numeri. Un insieme A è detto enumerabile se esso è equipotente all’insieme dei numeri naturali N: A ≈ N. La cardinalità degli insiemi infiniti enumerabili è comunemente denotata ℵ0 . Un insieme A è finito se |A| < ℵ0 . Un insieme A è numerabile se |A| = ℵ0 . Il seguente teorema ha come conseguenza che esistono insiemi non enumerabili. Teorema 2.4 (Cantor). Sia S un insieme. |S| < |℘(S)|. Proof.  Per assurdo assumiamo esista una funzione f : S −→ ℘(S) biiettiva. Sia A = x ∈ S : x ∈ / f(x) . Poiché A ∈ ℘(S) e f è per ipotesi suriettiva, deve esistere a ∈ S tale che f(a) = A. Ora, chiediamoci se a appartenga o meno ad A: • se a ∈ A allora, per definizione di A, a ∈ / f(a) = A; • se a ∈ / A = f(a) allora, per definizione di A, a ∈ A. In entrambi i casi si giunge ad una contraddizione: l’unica ipotesi che abbiamo fatto è quella dell’esistenza di una f biiettiva, che pertanto non può essere vera.  Come conseguenza immediata si ha che: |N| < |℘(N)| e che per ogni insieme S, |S| < |S −→ {0, 1}|. Questo segue dal fatto che è possibile definire un isomorfismo c : ℘(S) −→ (S −→ {0, 1}) tale che   1 se a ∈ X c(X)(a) =  0 se a 6∈ X Inoltre, dal teorema di Cantor segue che è possibile definire una successione di insiemi di cardinalità sempre maggiore: |N| < |℘(N)| < |℘(℘(N))| < |℘(℘(℘(N)))| < · · · Il seguente teorema stabilisce invece una relazione tra la cardinalità dell’insieme dei numeri reali R e la cardinalità delle funzioni sui naturali. Teorema 2.5. |R| = |℘(N)|. 22 2. NOTAZIONE E CONCETTI DI BASE Traccia della Dimostrazione. Si dimostra prima l’equicardinalità tra |℘(N)| e l’intervallo [0, 1) di R. La rappresentazione in aritmetica binaria di ogni elemento dell’intervallo [0, 1) è costituita da una sequenza infinita di bits della forma: 0.b0 b1 b2 b3 b4 b5 . . . con bi ∈ {0, 1}. Ad esempio “0.0000 . . . ” rappresenta lo 0, “0.1000 . . . ” rappresenta 1 2 , e cosı̀ via. Ogni sequenza b0 b1 b2 . . . è inoltre il grafico di una funzione c(X) di quelle descritte sopra che identifica un insieme X ⊆ N e ogni funzione di quel tipo è rappresentabile in questo modo. Esiste pertanto una biiezione tra |℘(N)| e l’intervallo [0, 1). Per completare la dimostrazione bisogna mostrare che l’intervallo [0, 1) è equipotente a tutto R. Questa parte è lasciata per esercizio (si suggerisce di leggere prima l’intera pagina seguente).  Le cardinalità di insiemi si possono sommare e moltiplicare tra loro. Intuitivamente, la somma della cardinalità di due insiemi A e B è data dalla cardinalità della loro unione A ∪ B. Analogamente, il prodotto della cardinalità di due insiemi A e B è data dalla cardinalità dell’insieme prodotto A × B: |A| + |B| = |A ∪ B| |A| · |B| = |A × B| È importante osservare che coppie, triple, etc. di numeri sono anch’esse numerabili, ovvero vale il seguente risultato: Teorema 2.6. (1) ℵ0 + ℵ0 = ℵ0 (2) ℵ0 · ℵ0 = ℵ0 (3) ∀n ∈ N. ℵn 0 = ℵ0 . Proof. Dimostrare il punto (1) per esercizio. Per quanto riguarda il punto (2), consideriamo l’insieme N × N. Per questo insieme vogliamo trovare una enumerazione, ovvero un insieme S = hni , mi i : ni , mi , i ∈ N tale che S ≈ N. Ordiniamo N × N nel modo seguente: Il primo elemento è la coppia hn0 , m0 i = h0, 0i. Quindi ordiniamo le coppie hn, mi tali che ni + mj = 1 in base alla prima componente: hn1 , m1 i = h0, 1i, hn2 , m2 i = h1, 0i. Quindi in modo analogo ordiniamo le coppie hn, mi tali che ni + mj = 2: hn3 , m3 i = h0, 2i, hn4 , m4 i = h1, 1i, etc. È facile vedere che la funzione ι : N −→ N × N definita nel modo seguente: ι(i) = hni , mi i è un isomorfismo tra N e N × N. La seguente funzione è l’inversa di ι [dimostrare per esercizio] ; δ(n, m) = n + n+m+1 X (ℓ + 1) = n + ℓ=0 Il punto (3) segue per induzione dal punto (2). (n + m)(n + m + 1) 2  6. ORDINALI 23 Abbiamo visto che ℵ0 corrisponde alla cardinalità degli insiemi enumerabili infiniti, ovvero dei numeri naturali. Inoltre, ∀n ∈ N. ℵn 0 = ℵ0 e, per il Teorema di Cantor: ℵ0 = |N| < |℘(N)| = |R|. Ci chiediamo quindi se |℘(N)| è la più piccola cardinalità non enumerabile. Questa congettura è nota con il nome di ipotesi del continuo: Ipotesi del continuo (CH): Ogni sottoinsieme di R è o numerabile o di cardinalità |℘(N)|. Assumendo valida l’ipotesi del continuo2 , è quindi possibile affermare che un insieme A è non-numerabile se |A| ≥ |℘(N)| > ℵ0 . 6. Ordinali In questo paragrafo parleremo del concetto di (insieme) ordinale. Mentre i cardinali sono introdotti per misurare in qualche modo il numero di elementi di un dato insieme, gli ordinali sono introdotti per confrontare tra loro insiemi bene ordinati. Gli ordinali sono introdotti anche per poter definire un’aritmetica all’interno della teoria degli insiemi. Un insieme x si dice transitivo se e solo se ogni elemento di x è sottoinsieme di x. In breve, se ∀y ∈ x.∀z ∈ y.z ∈ x. Per esempio: • ∅, {∅}, {∅, {∅}} sono transitivi. • {{∅}} non è transitivo. • Se Ω è un insieme tale che Ω = {Ω}, allora Ω è transitivo. Si noti però che in quest’ultimo insieme l’appartenenza non è una relazione ben fondata su di esso (vale Ω ∈ Ω che permette di generare catene discendenti infinite di appartenenza). Questo insieme è un insieme non ben fondato [1]. Un insieme x è un ordinale se x è transitivo e l’appartenenza è ben fondata su x (ovvero, (x, ∈) è ben fondato). Per esempio: • ∅, {∅}, {∅, {∅}} sono ordinali. • {{∅}} e Ω non sono ordinali.  Teorema 2.7 (Paradosso Burali-Forti). Non esiste l’insieme ON = x : x è un ordinale . La dimostrazione di questo teorema è lasciata per esercizio. La parte tecnica è quella di dimostrare che se ON esistesse, allora sarebbe un ordinale. Ottenuto ciò il paradosso emerge immediatamente. Dati due insiemi con una relazione (A, R) e (B, S), un isomorfismo tra (A, R) e (B, S) è una funzione biiettiva f : A −→ B tale che ∀x ∈ A.∀y ∈ A.x R y ↔ f(x) S f(y). Se esiste un isomorfismo tra (A, R) e (B, S), diremo che sono isomorfi e lo de∼ (B, S). Quando X è un ordinale, scriveremo semplicemente noteremo con (A, R) = X in luogo di (X, ∈). 2 L’indipendenza dell’ipotesi del continuo dalla formulazione classica della teoria degli insiemi ZF, è dovuta a Gödel e Cohen. Rimandiamo a [16] per maggiori dettagli su questa affascinante branca della moderna teoria degli insiemi. 24 2. NOTAZIONE E CONCETTI DI BASE Vale il seguente: Teorema 2.8. Se (A, R) è un buon ordine, allora esiste un unico ordinale C ∼ C. tale che (A, R) = Tale unico ordinale viene indicato come type(A, R) e vuole rappresentare una misura di quel buon ordine. Gli ordinali sono tutti basati su insiemi puri (ovvero ∅, {∅, {∅}} etc.) e sulla relazione di appartenenza e sono confrontabili tra loro, come vedremo nel seguito. Il poter assegnare univocamente un ordinale ad ogni buon ordine permette pertanto di confrontare buoni ordini basati su insiemi qualunque (per esempio {mario, luigi, . . . }) e su relazioni d’ordine ben fondate qualunque (per esempio l’ordine lessicografico tra i codici fiscali). Più precisamente, dati due ordinali α e β, diciamo che α < β se α ∈ β; diciamo che α ≤ β se α < β oppure α = β. Se X è un insieme di ordinali, definiamo come sup(X) l’unione unaria di X  ovvero l’insieme z : ∃y ∈ X.z ∈ y . Si può dimostrare che sup(X) è il minimo ordinale maggiore o uguale a tutti gli elementi di X. Se α è un ordinale, allora l’ordinale successore di α è definito come S(α) = α ∪ {α}. Per ogni ordinale α si ha che S(α) è un ordinale. β è detto un ordinale successore se esiste un ordinale α tale che β = S(α). α è invece un ordinale limite se è un ordinale diverso da ∅ e non è un ordinale successore. A partire da queste nozioni siamo in grado di definire alcuni ordinali familiari: 0 def = ∅ 1 def S(0) = ∅ ∪ {∅} = {∅} = {1} 2 def 3 .. . def = .. . S(2) = {∅, {∅}, {{∅, {∅}}} = {0, 1, 2} .. . n+1 = S(n) = n ∪ {n} = {0, 1, . . . , n} = = S(1) = {∅} ∪ {{∅}} = {∅, {∅}} = {0, 1} Questa successione corrisponde alla successione dei numeri naturali. Si osservi che la definizione di < data sopra per gli ordinali è consistente con quella usuale sui numeri naturali. In teoria degli insiemi (p. es., [18]) si definiscono i numeri naturali esattamente in questo modo: un ordinale α è un numero naturale se ∀β ≤ α.(β = ∅ ∨ β è un ordinale successore). Per quanto riguarda gli ordinali limite, il primo ordinale limite è ω definito come ω = sup α : α è un naturale ovvero ω = 0 ∪ 1 ∪ 2 ∪ · · · = {0, 1, 2, . . . } Abbiamo visto come gli insiemi ordinali permettano di definire in qualche modo i numeri naturali e la relazione d’ordine <. Vedremo ora alcune nozioni di aritmetica degli ordinali. 6. ORDINALI 25 Dati due ordinali α e β l’ordinale somma α + β è l’ordinale type(α × {0} ∪ β × {1}, R) dove R è la relazione: R =  hhx, 0i, hy, 0ii : x < y < α ∪  hhx, 1i, hy, 1ii : x < y < β ∪ ((α × {0}) × (β × {1})) A parole, gli elementi di α e β vengono messi in sequenza. Prima tutti quelli di α, poi quelli di β. Ad esempio, 2 + 3 risulta il type dell’insieme costituito dalle coppie (ordinate da sinistra a destra): {h0, 0i, h1, 0i, h0, 1i, h1, 1i, h2, 1i} ovvero all’ordinale 5 = {0, 1, 2, 3, 4} ad esso isomorfo. Dunque, 2 + 3 fa proprio 5 come si sperava avvenisse. Per l’aritmetica ordinale valgono le seguenti proprietà: Teorema 2.9. (1) α + (β + γ) = (α + β) + γ (2) α + 0 = α (3) α + 1 = S(α) (4) α + S(β) = S(α + β)  (5) Se β è ordinale limite, allora α + β = sup α + ξ : ξ < β . Si osservi però che il + non è in generale commutativo (lo è, per fortuna, sui numeri naturali). Studiamo infatti chi sono 1 + ω e ω + 1. • 1 + ω è il type dell’insieme: {0} × {0} ∪ ω × {1} ovvero: {h0, 0i, h0, 1i, h1, 1i, h2, 1i, h3, 1i, . . . } che altri non è se non ω stesso. Dunque 1 + ω = ω. • ω + 1 è il type dell’insieme: ω × {0} ∪ {0} × {1} ovvero: {h0, 0i, h1, 0i, h2, 0i, h3, 0i, h4, 0i, . . . , h0, 1i} Si tratta di un ordine diverso da ω. Vi è prima una quantità numerabile di elementi ordinati come ω. Poi c’è un unico elemento che sta sopra tutti loro. L’ordinale corrispondente è S(ω) = ω ∪ {ω}. Studiamo ora la moltiplicazione tra ordinali. Dati due ordinali α e β l’ordinale prodotto α · β è l’ordinale type(β × α, R) dove R è la relazione d’ordine lessicografico su β × α, ovvero: hx, yi R hx ′ , y ′ i sse x < x ′ ∨ (x = x ′ ∧ y < y ′ ) 26 2. NOTAZIONE E CONCETTI DI BASE In parole povere, viene ricopiato α tante volte quante β. Ad esempio, 2 · 3 risulta il type dell’insieme costituito dalle coppie (ordinate da sinistra a destra): {h0, 0i, h0, 1i, h1, 0i, h1, 1i, h2, 0i, h2, 1i} ovvero all’ordinale 6 = {0, 1, 2, 3, 4, 5} ad esso isomorfo. Dunque, 2 · 3 fa proprio 6 come si sperava avvenisse. Per la moltiplicazione valgono le seguenti proprietà: Teorema 2.10. (1) α · (β · γ) = (α · β) · γ (2) α · 0 = 0 (3) α · 1 = α (4) α · S(β) = α · β + α (5) α · (β + γ) = α · β + α · γ  (6) Se β è ordinale limite, allora α · β = sup α · ξ : ξ < β . Anche in questo caso la commutatività vale solo per i naturali. Studiamo 2 · ω e ω · 2. • 2·ω è il type dell’insieme: ω × {0, 1} ordinato lessicograficamente, ovvero: {h0, 0i, h0, 1i, h1, 0i, h1, 1i, h2, 0i, h2, 1i, . . . } che altri non è se non ω stesso. Dunque 2 · ω = ω. • ω · 2 è invece il type dell’insieme: {0, 1} × ω ovvero: {h0, 0i, h0, 1i, h0, 2i, h0, 3i, h0, 4i, . . . , h1, 0i, h1, 1i, h1, 2i, h1, 3i, h1, 4i, . . . , } Si tratta di un ordine diverso da ω. Vi è prima una quantità numerabile di elementi ordinati come ω. Poi c’è un un elemento che sta sopra tutti loro (h1, 0i) ed infine una quantità numerabile di elementi sopra ad esso.  L’ordinale corrispondente è ω · 2 = sup ω + n : n < ω . Non è da confondere la nozione di ordinale con quella di cardinalità. Ad esempio, è facile osservare in base al Teorema 2.6, che |ω · 2| = |ω + 1| = |ω| = ℵ0 . Tuttavia, vista la corrispondenza tra il primo ordinale limite infinito ω e l’insieme dei numeri naturali N, faremo abuso di notazione identificando ω con ℵ0 . Pertanto, scriveremo |A| = ω per affermare che A è un insieme infinito enumerabile. 7. Il problema dell’informatica Abbiamo visto come la cardinalità dell’insieme delle funzioni sui naturali sia strettamente superiore alla cardinalità dei numeri naturali stessi (Teorema 2.4). Già questo risultato permette di stabilire una prima distinzione tra ciò che possiamo formulare come funzione (problema) e ciò che possiamo dare come algoritmo (soluzione algoritmica del problema). Se pensiamo che i programmi di un calcolatore siano tutti rappresentabili come sequenze finite di istruzioni date in un determinato linguaggio Σ, allora l’insieme dei programmi che possiamo scrivere è equipotente all’insieme dei numeri naturali. 7. IL PROBLEMA DELL’INFORMATICA 27 Infatti, se Σ è l’alfabeto con cui possiamo scrivere i nostri programmi, allora un programma non è altro che una sequenza finita di simboli di Σ: s1 s2 s3 . . . sn con n ∈ N. Gli algoritmi o programmi che possiamo descrivere sono quindi tanti quante le sequenze finite di simboli di Σ. Chiamiamo Alg l’insieme degli algoritmi che possiamo descrivere in un dato linguaggio. La seguente funzione stabilisce una biiezione tra algoritmi e numeri: π : Alg −→ N P n−i dove, se P ∈ Alg e P = s1 s2 s3 . . . sn , allora π(P) = n dove p(si ) è i=1 p(si ) · 2 il numero primo associato in modo univoco al simbolo si . Se supponiamo inoltre che i dati manipolabili dai programmi siano anch’essi numerabili, ovvero siano numeri, o sequenze di simboli in un alfabeto Σ ′ (ad esempio immagini come sequenze di bytes che rappresentano il colore, la luminosità ed il contrasto per ogni pixel dell’immagine) allora un problema di tipo informatico è descrivibile come una funzione sui dati D: f : D −→ D che associa ad ogni dato in input un dato in output. Come nel ragionamento sopra si ha che: |D| = |N|. Ne consegue dal Teorema di Cantor che l’insieme dei problemi è equipotente all’insieme delle funzioni D −→ D che in cardinalità è strettamente maggiore dell’insieme di tutti i possibili programmi che possiamo scrivere in un dato alfabeto: |D −→ D| > |Alg| In particolare, ed in modo un po’ provocatorio [22], possiamo affermare che, come l’analisi, lo studio della ricorsività coincide con lo studio di |D −→ D|, ovvero di un insieme equipotente ai numeri reali. I successivi capitoli permetteranno quindi di appropriarci degli strumenti per analizzare tale insieme ed in particolare la relazione tra problemi e loro soluzioni algoritmiche (programmi). Part 1 Linguaggi formali CHAPTER 3 Automi a stati finiti capitolo sarà presentato il concetto di automa a stati finiti. Si partirà I ndalquesto formalismo più semplice (automi a stati finiti deterministici) per poi presentare diverse tipologie di automa, che si dimostreranno essere tutte equivalenti dal punto di vista delle potenzialità del loro utilizzo, ovvero del riconoscimento di un dato linguaggio. 1. Alfabeti e Linguaggi Un simbolo è un’entità primitiva astratta che non sarà definita formalmente (come punto, linea, etc.). Lettere e caratteri numerici sono esempi di simboli. Una stringa (o parola) è una sequenza finita di simboli giustapposti (uno dietro l’altro). Ad esempio, se a, b, c sono simboli, abcba è una stringa. La lunghezza di una stringa w, denotata come |w|, è il numero di occorrenze di simboli che compongono una stringa. Ad esempio, |abcba| = 5. La stringa vuota, denotata con ε è la stringa costituita da zero simboli: |ε| = 0. Sia w = a1 · · · an una stringa. Ogni stringa della forma: • a1 · · · aj , con j ∈ {1, . . . , n} è detta un prefisso di w; • ai · · · an , con i ∈ {1, . . . , n} è detta un suffisso di w; • ai · · · aj , con i, j ∈ {1, . . . , n}, i ≤ j, è detta una sottostringa di w; • ε è sia prefisso che suffisso che sottostringa di w. Si osservi che se n = 0, allora w = ε. Dunque ε è sia prefisso che suffisso di ε. Ad esempio, i prefissi di abc sono ε, a, ab, e abc. I suffissi sono ε, c, bc, e abc. Le sottostringhe sono: ε a ab b abc bc c Un prefisso, un suffisso o una sottostringa di una stringa, quando non sono la stringa stessa, sono detti propri. Esercizio 3.1. Sia data una stringa w di lunghezza |w| = n. Quanti sono i suoi prefissi, i suoi suffissi e le sue sottostringhe? 31 32 3. AUTOMI A STATI FINITI La concatenazione di due stringhe v e w è la stringa vw che si ottiene facendo seguire alla prima la seconda. La concatenazione è una operazione (associativa) che ammette come identità la stringa vuota ε. Un alfabeto Σ è un insieme finito di simboli. Un linguaggio formale (in breve linguaggio) è un insieme di stringhe di simboli da un alfabeto Σ. L’insieme vuoto ∅ e l’insieme {ε} sono due linguaggi formali di qualunque alfabeto. Con Σ∗ verrà denotato il linguaggio costituito da tutte le stringhe su un fissato alfabeto Σ. Dunque Σ∗ = {a1 · · · an : n ≥ 0, ai ∈ Σ} Ad esempio, se Σ = {0}, allora Σ∗ = {ε, 0, 00, 000, . . . }; se Σ = {0, 1}, allora Σ∗ = {ε, 0, 1, 00, 01, 10, 11, 000, . . . }. Esercizio 3.2. Si provi che se Σ 6= ∅, allora Σ∗ è numerabile. Definendo inoltre:   Σ0 = {ε}  Σn+1 = {ax : a ∈ Σ, x ∈ Σn } S si osservi che Σ∗ = i≥0 Σi . Qual è la cardinalità di Σi ? 2. Automi Un automa a stati finiti è un modello matematico di un sistema avente input, ed eventualmente output, a valori discreti. Il sistema può essere in uno stato tra un insieme finito di stati possibili. L’essere in uno stato gli permette di tener traccia della storia precedente. Un buon esempio di automa a stati finiti è costituito dall’ascensore: l’input è la sequenza di tasti premuti, mentre lo stato è il piano in cui si trova. Un automa a stati finiti si può rappresentare mediante una testina che legge, spostandosi sempre nella stessa direzione, un nastro di lunghezza illimitata contenente dei simboli. La testina si può trovare in un certo stato; a seconda dello stato q e del simbolo si letto, la testina si porta in un altro stato (o rimane nello stesso) e si sposta a destra per apprestarsi a leggere il simbolo sucessivo: s1 s2 s3 ··· ⇑ q Quando la lettura dei simboli termina, a seconda dello stato raggiunto dalla testina, l’automa fornisce un risultato di accettazione o di refutazione della stringa (parola) letta. Il comportamento dell’automa si definisce in maniera univoca mediante una tabella, detta matrice di transizione, come ad esempio: 3. AUTOMI DETERMINISTICI a b q0 q1 q2 q1 q1 q0 q2 q1 q0 33 Un’automa siffatto si rappresenta bene anche con un grafo della forma seguente: ✛ ✘ ★✥ ✤✜ a q1 ✛ ✙ ✒ ✧✦ ✣✢ ❅ ■ a ❅ b ★✥ ★✥ ❅ ✠ b ✲ q2 q0 ✛ ✧✦ ✧✦ b a Nel prossimo paragrafo si fornirà una definizione formale del concetto ora esposto, necessaria per una trattazione precisa dell’argomento. Nota 3.3. Il fatto che, a differenza delle MdT (Capitolo 11.1), la testina non possa produrre degli output (eventualmente sul nastro) non è essenziale (si vedano, ad esempio, le macchine di Moore e di Mealy (Capitolo 8 e [13]). Intuitivamente, anche se scrivesse qualcosa sul nastro, non lo potrebbe più riutilizzare. Neppure il permettere la bidirezionalità aumenterebbe di fatto la potenzialità del dispositivo (two-way automata [13]). Intuitivamente, la testina andrebbe su e giù ma sempre sugli stessi dati e con un controllo finito. E’ la somma delle due caratteristiche che permette di passare da questo formalismo al più potente formalismo di calcolo costituito dalla MdT. 3. Automi deterministici Un automa a stati finiti deterministico (DFA) è una quintupla hQ, Σ, δ, q0 , Fi dove: • • • • • Q è un insieme finito di stati; Σ è un alfabeto (alfabeto di input); δ : Q × Σ −→ Q è la funzione di transizione; q0 è lo stato iniziale; F ⊆ Q è l’insieme degli stati finali. 34 3. AUTOMI A STATI FINITI Notazione 3.4. Useremo p, q, r con o senza pedici per denotare stati, P, Q, R, S per insiemi di stati, a, b con o senza pedici per denotare simboli di Σ, x, y, z, u, v, w sempre con o senza pedici per denotare stringhe. Dalla funzione δ si ottiene in modo univoco la funzione ^δ : Q × Σ∗ −→ Q nel modo seguente:   ^δ(q, ε) = q  δ(q, ^ wa) = δ(^δ(q, w), a) Una stringa x è detta essere accettata da un DFA M = hQ, Σ, δ, q0 , Fi se ^δ(q0 , x) ∈ F. Il linguaggio accettato da M, denotato come L(M) è l’insieme delle stringhe accettate, ovvero: L(M) = {x ∈ Σ∗ : ^δ(q0 , x) ∈ F} . Un linguaggio L è detto regolare se è accettato da qualche DFA, ovvero se esiste M tale che L = L(M). Esempio 3.5. ∅ e Σ∗ sono linguaggi regolari. Sia Σ = {s1 , . . . , sn }: un automa M0 che riconosce il linguaggio ∅ (ovvero: nessuna stringa è accettata) è il seguente: q0 s1 ... sn q0 ... q0 ove F = ∅. Infatti, poiché ∀x x ∈ / ∅, si ha che: (∀x ∈ Σ∗ )(^δ(q0 , x) ∈ / F) . Un automa per Σ∗ , è invece l’automa M1 : q0 s1 ... sn q0 ... q0 ove F = {q0 }. Si dimostra facilmente infatti, per induzione su |x| che (∀x ∈ Σ∗ )(^δ(q0 , x) = q0 ) . Esercizio 3.6. Si determini il linguaggio accettato dai seguenti automi rapresentati mediante la matrice di transizione (in tutti F = {q1 }). (1) 0 1 q0 q1 q2 q1 q1 q1 q2 q1 q0 4. AUTOMI NON-DETERMINISTICI 35 (2) 0 1 q0 q2 q0 q1 q2 q0 q2 q1 q2 Esercizio 3.7. Si verifichi che i seguenti linguaggi, con Σ = {0, 1}, sono regolari: (1) (2) (3) (4) l’insieme di tutte le stringhe aventi tre 0 consecutivi; l’insieme di tutte le stringhe tali che il penultimo simbolo è 0; l’insieme di tutte le stringhe tali che il terzultimo simbolo è 0; l’insieme di tutte le stringhe tali che, se interpretate come numero intero (binario), sono divisibili per 2; (5) l’insieme di tutte le stringhe tali che, se interpretate come numero intero (binario), sono divisibili per 4; (6) l’insieme di tutte le stringhe tali che, se interpretate come numero intero (binario), sono divisibili per 5. Esercizio 3.8. Si dimostrino le seguenti proprietà (1) Siano vx e wx due stringhe e M = hQ, Σ, δ, q0 , Fi un DFA. Allora, ^ δ(q0 , v) = ^ δ(q0 , w) implica ^δ(q0 , vx) = ^δ(q0 , wx). (2) Siano u e v due stringhe e q uno stato. Allora ^δ(q, uv) = ^δ(^δ(q, u), v). (3) Siano u e v due stringhe e q uno stato. Se ^δ(q, u) = q1 e ^δ(q, uv) = q2 allora ^ δ(q1, v) = q2 . (4) Sia L ⊆ Σ∗ un linguaggio accettato da un DFA M. Allora esiste M ′ = hQ ′ , Σ ′ , δ ′ , q0′ , F ′ i tale che Σ ′ = Σ e L(M ′ ) = L(M). 4. Automi non-deterministici Un automa a stati finiti non-deterministico (NFA) è una quintupla hQ, Σ, δ, q0 , Fi dove Q, Σ, q0 e F ⊆ Q mantengono il significato visto per gli automi deterministici, mentre la funzione di transizione δ è ora definita δ : Q × Σ −→ ℘(Q) . Si osservi che è ora ammesso: δ(q, a) = ∅ per qualche q ∈ Q ed a ∈ Σ. Anche per gli NFA dalla funzione δ si ottiene in modo univoco la funzione ^δ : Q×Σ∗ −→ ℘(Q) nel modo seguente:   ^ δ(q, ε) = {q} S  ^ δ(p, a) δ(q, wa) = ^ p∈δ(q,w) 36 3. AUTOMI A STATI FINITI Una stringa x è accettata da un NFA M = hQ, Σ, δ, q0 , Fi se ^δ(q0 , x) ∩ F 6= ∅. Il linguaggio accettato da M è l’insieme delle stringhe accettate, ovvero: L(M) = {x ∈ Σ∗ : ^δ(q0 , x) ∩ F 6= ∅} . Mentre la rappresentazione mediante tabella degli automi non deterministici è profondamente diversa da quella per i deterministici (in ogni casella si deve inserire ora un insieme di stati), la rappresentazione a grafo rimane pressoché immutata. L’unica differenza è che da un nodo possono uscire più archi (o nessuno) etichettati dallo stesso simbolo. Dal punto di vista invece del modello con testina e nastro, il non-determinismo va immaginato come la possibilità contemporanea di procedere la computazione in ognuno degli stati raggiunti. Si apre dunque un gran numero di computazioni virtuali parallele. 5. Equivalenza tra DFA e NFA In questo paragrafo si mostrerà che i linguaggi accettati dai DFA e dagli NFA coincidono. Poiché un DFA si può vedere come un NFA in cui δ(q, a) restituisce sempre insiemi costituiti da un solo stato (detti anche singoletti), si ha che ogni linguaggio regolare è un linguaggio accettato da un qualche NFA. Per l’implicazione inversa ci serve il seguente teorema: Teorema 3.9 (Rabin-Scott, 1959). Sia M = hQ, Σ, δ, q0 , Fi un NFA. Allora esiste un DFA M ′ tale che L(M) = L(M ′ ). Proof. Definisco M ′ = hQ ′ , Σ ′ , δ ′ , q0′ , F ′ i come segue: • Σ ′ = Σ; • Q ′ = ℘(Q) (sarebbe più preciso definire Q ′ = {q1 , . . . , q2|Q| } e poi stabilire una corrispondenza biunivoca fra tali stati e gli elementi di ℘(Q). Tuttavia, con tale abuso sintattico la dimostrazione diventa molto più snella); • q0′ = {q0 }; • F ′ = {P ⊆ Q S : P ∩ F 6= ∅}; • δ ′ (P, a) = p∈P δ(p, a), per P ∈ ℘(Q). Mostriamo per induzione sulla lunghezza della stringa di input x che ^δ(q0 , x) = ^δ ′ (q0′ , x) Base: Per |x| = 0 il risultato è banale, poiché q0′ = {q0 } e x = ε. Passo: Supponiamo che l’ipotesi induttiva valga per tutte le stringhe x tali che |x| ≤ m. Sia xa una stringa di lunghezza m + 1. Allora: ^ δ ′ (q0′ , xa) = = = = δ ′ (^δ ′ (q0′ , x), a) Def. di ^· nei DFA δ (^δ(q0 , x), a) Ip. ind. S ′ ^ 0 ,x) δ(p, a) Def. di δ p∈δ(q ′ ^ δ(q0 , xa) Def. di ^· nei NFA 6. AUTOMI CON ε-TRANSIZIONI 37 Il teorema segue dal fatto che: x ∈ L(M) ^ 0 , x) ∩ F 6= ∅ sse δ(q def. di linguaggio NFA sse δ proprietà sopra ^′ (q0′ , x) ∩ F 6= ∅ sse δ^ ′ (q0′ , x) ∈ F ′ sse x ∈ L(M ′ ) def. di F ′ def. di linguaggio DFA  Esercizio 3.10. Si determini il DFA equivalente al NFA: 0 1 q0 {q0 } {q0 , q1 } q1 {q1 } {q0 , q2 } q2 {q1 , q2 } {q0 , q1 , q2 } ove F = {q2 }. Qual è il linguaggio accettato? Esercizio 3.11. Si descriva un NFA a 4 stati che riconosce il linguaggio delle stringhe di 0 e 1 con terzultimo elemento a 0. Si passi poi al DFA equivalente e lo si confronti con quello ottenuto nell’esercizio 3.7(3). 6. Automi con ε-transizioni In questo paragrafo sarà presentato un terzo tipo di automa che estende il modello non-deterministico ma che, come sarà mostrato nel Teorema 3.12, ne è equivalente dal punto di vista dei linguaggi accettati. Un NFA con ε-transizioni è una quintupla hQ, Σ, δ, q0 , Fi dove: Q, Σ, q0 e F ⊆ Q sono come per gli automi non deterministici, mentre la funzione di transizione δ è ora definita δ : Q × (Σ ∪ {ε}) −→ ℘(Q) . L’idea è che da uno stato è permesso passare ad un altro stato anche senza “leggere” caratteri di input. La costruzione della funzione ^δ : Q × Σ∗ −→ ℘(Q) nel caso dei ε-NFA risulta leggermente più complessa che nei casi precedenti. Per far ciò si introduce la funzione ε-closure che, applicata ad uno stato, restituisce l’insieme degli stati raggiungibili da esso (compreso sè stesso) mediante ε-transizioni. La costruzione di tale funzione è equivalente a quella che permette di conoscere i nodi raggiungibili da un nodo in un grafo e può facilmente essere calcolata a partire dalla funzione δ (un arco p → q si ha quando q ∈ δ(p, ε)). Il concetto di ε-closure si estende in modo intuitivo ad insiemi di stati: [ ε-closure(P) = ε-closure(p) p∈P 38 3. AUTOMI A STATI FINITI ^δ si può ora definire nel modo seguente:   ^ δ(q, ε) = ε-closure(q) S  ^ ε-closure(δ(p, a)) δ(q, wa) = ^ p∈δ(q,w) Si noti che in questo caso ^ δ(q, a) può essere diverso da δ(q, a). Ad esempio, nell’automa: ✛✘ ✛✘ ✛✘ a ✲ ε ✲ q q′ q ′′ ✚✙ ✚✙ Si ha che δ(q, a) = {q ′ }, mentre ^δ(q, a) = S ^ p∈δ(q,ε) ✚✙ ε-closure(δ(p, a)) = {q ′ , q ′′ }. Definiamo dunque il linguaggio accettato dall’automa L(M) = {x ∈ Σ∗ : ^δ(q0 , x) ∩ F 6= ∅}. Si osservi come per questa classe di linguaggi si potrebbe assumere che l’insieme F abbia esattamente un elemento. 7. Equivalenza di ε-NFA e NFA Ogni NFA è, per definizione, un caso particolare di un ε-NFA. Con il seguente teorema mostreremo che la classe dei linguaggi riconosciuti dagli automi ε-NFA non estende propriamente quella dei linguaggi riconosciuti da automi NFA: Teorema 3.12. Sia M = hQ, Σ, δ, q0 , Fi un ε-NFA. Allora esiste un NFA M ′ tale che L(M) = L(M ′ ). Proof. Definisco M ′ = hQ ′ , Σ ′ , δ ′ , q0′ , F ′ i come segue: • Q ′ = Q, • Σ ′ = Σ, • q0′ =  q0 ,  F ∪ {q } Se ε-closure(q ) ∩ F 6= ∅ 0 0 • F′ =  F altrimenti ′ ^ a). • δ (q, a) = δ(q, Dobbiamo mostrare che ^ δ(q0 , x) ∩ F 6= ∅ sse ^δ ′ (q0′ , x) ∩ F ′ 6= ∅ (1) ^ 0 , ε) = ε-closure(q0 ) per definizione. Se x = ε, si ha che: δ(q D’altro canto: ^ δ ′ (q0 , ε) = {q0 }. Se ε-closure(q0 )∩F 6= ∅ allora, per def. di F ′ vale che q0 ∈ F ′ ; dunque {q0 }∩F ′ 6= ∅. Sia ora {q0 } ∩ F ′ 6= ∅. Allora q0 ∈ F ′ . Due casi sono possibili: se q0 ∈ F allora ε-closure(q0 ) ∩ F 6= ∅ (in quanto q0 ∈ ε-closure(q0 )). Altrimenti, se q0 ∈ F ′ \ F per definizione di F ′ si ha che ε-closure(q0 ) ∩ F 6= ∅. Mostreremo per induzione su |x| ≥ 1 che ^δ ′ (q0′ , x) = ^δ(q0 , x). 8. AUTOMI CON OUTPUT 39 Base: |x| = 1. Allora x = a per qualche simbolo a ∈ Σ. Ma allora ^δ ′ (q0′ , a) = ^ 0 , a) per definizione. δ ′ (q0′ , a) = δ(q Passo: Assumiamo che la tesi valga per tutte le stringhe x tali che 1 ≤ |x| ≤ m. Sia xa una stringa di lunghezza m + 1. Allora: S ′ ^ def. di δ^ ′ δ ′ (q0′ , xa) = ^ ′ (q ′ ,x) δ (p, a) p∈δ 0 S δ(p, a) def. di δ ′ = ^ ′ (q ′ ,x) ^ p∈δ 0 S ^ ip. ind. = ^ 0 ,x) δ(p, a) p∈δ(q S ^ e (∗) = ^ 0 ,x) ε-closure(δ(p, a)) def. di δ p∈δ(q = ^ δ(q0 , xa) def. di δ^ (∗) si osservi che per definizione di ^δ, la ε-closure(p) è contenuta in ^δ(q0 , x). Per concludere la dimostrazione, si deve mostrare la proprietà (1), ovvero che ^δ ′ (q0′ , x) contiene uno stato di F ′ sse ^δ(q0 , x) contiene uno stato di F. Poiché F ⊆ F ′ e ^ δ ′ (q0′ , x) = ^ δ(q0 , x) l’implicazione (→) deriva immediatamente. Per l’altra implicazione, l’unico problema si avrebbe nel caso: (1) q0 ∈ ^ δ ′ (q0′ , x), ′ (2) q0 ∈ F ′ , (3) q0 ∈ / F. ^ ′ (q ′ , x) = δ(q ^ 0 , x), si ha che q0 ∈ δ(q ^ 0 , x). Ma, per definizione di δ, ^ Poiché δ 0 ^ anche ogni elemento della sua ε-closure appartiene a δ(q0 , x). Tale ε-closure, poiché q0 ∈ F ′ , interseca F.  Corollario 3.13. Le classi di linguaggi riconosciute da DFA, NFA e ε-NFA coincidono (linguaggi regolari). Proof. Immediato dai Teoremi 3.9 e 3.12.  8. Automi con output Gli automi visti fin qui, tutti equivalenti dal punto di vista della classe di linguaggi accettati (riconosciuti) non sono in grado di fornire alcun tipo di output se non il messaggio (booleano) del raggiungimento di uno stato finale o meno. Esistono in letteratura due tipi di automi provvisti di output: le macchine di Moore e quelle di Mealy. Tali macchine sono automi a stati finiti deterministici (e dunque ereditano la classe di linguaggi accettati) che possiedono un alfabeto di output: • le macchine di Moore forniscono in output un simbolo in funzione di ogni stato raggiunto. Poiché per una stringa di n elementi si raggiungono al più n + 1 stati (compreso quello iniziale), si può ottenere un output di (al più) n + 1 caratteri. 40 3. AUTOMI A STATI FINITI • le macchine di Mealy forniscono in output un simbolo ogni volta che avviene una transizione δ(q, a). In questo caso al più n caratteri saranno forniti in output. Gli output possono essere utili per differenziare stati finali. Cosı̀, ad esempio, una stringa accettata può anche avere una più precisa classificazione (si veda l’Esempio 3.14). Le macchine di Moore e Mealy vengono impiegate per la sintesi di reti sequenziali e sono formalismi tra loro equivalenti. Per maggiori dettagli, si veda [13]. Esempio 3.14. Il seguente automa è una macchina di Moore che, oltre a riconoscere se una stringa sia o meno un elemento di una espressione numerica, segnala se si tratta di un numero intero o di un identificatore di variabile. L’output è rappresentato dalle scritte (Idf. di variabile, etc.) posizionate accanto ai vari stati. ✓ ✏ ✓✏ ✲ q 1 Idf. di variabile ✒ ✒✑ ✒ ❅ ❅‘ ’ a| · · · |z ❅ ✓✏ ✓✏ Idf. non valido ❘ ❅ ‘ ’ ✲ q3 ✛ ✏ q0 ✒✑ ✒✑ ✒✒ ✑ ❅ 0| · · · |9 a| · · · |z|0| · · · |9| ‘ ❅ ✓✏ ❅ a| · · · |z| ‘ ’ ❘ q ✛ ✏ ❅ 2 Cost. numerica ✒✑ ✒ ✑ a| · · · |z|0| · · · |9 ’ 0| · · · |9 Si osservi come esso racchiuda in sè due DFA privi di output. CHAPTER 4 Espressioni regolari questo capitolo verrà illustrato un modo alternativo per descrivere i linguaggi I naccettati da automi finiti (linguaggi regolari). definiremo un algebra di operazioni per manipolare linguaggi regolari. 1. Operazioni sui linguaggi Sia Σ un alfabeto e L, L1 , L2 insiemi di stringhe di Σ∗ . La concatenazione di L1 e L2 , denotata come L1 L2 è l’insieme: L1 L2 = {xy ∈ Σ∗ : x ∈ L1 , y ∈ L2 } . Definiamo ora   L0 = {ε}  Li+1 = LLi Si osservi che L0 = {ε} 6= ∅. La chiusura (di Kleene) di L, denotata come L∗ è l’insieme: [ L∗ = Li i≥0 S mentre la chiusura positiva di L, denotata come L+ è l’insieme: L+ = i≥1 Li (si verifica immediatamente che L+ = LL∗ , dunque tale operatore, seppur comodo, non è essenziale). Si osservi come la definizione sia consistente con la nozione di Σ∗ fin qui adoperata. 2. Definizione formale Sia Σ un alfabeto. Le espressioni regolari su Σ e gli insiemi che esse denotano sono definiti ricorsivamente nel modo seguente: (1) ∅ è una espressione regolare che denota l’insieme vuoto. (2) ε è una espressione regolare che denota l’insieme {ε}. (3) Per ogni simbolo a ∈ Σ, a è una espressione regolare che denota l’insieme {a}. (4) Se r e s sono espressioni regolari denotanti rispettivamente gli insiemi R ed S, allora (r + s), (rs), e (r∗ ) sono espressioni regolari che denotano gli insiemi R ∪ S, RS, e R∗ rispettivamente. 41 42 4. ESPRESSIONI REGOLARI Se r è una espressione regolare, indicheremo con L(r) il linguaggio denotato da r. Tra espressioni regolari valgono delle uguaglianze che permettono la loro manipolazione algebrica. Varrà che r = s se e solo se L(r) = L(s). Notazione 4.1. Qualora non si crei ambiguità, nello scrivere espressioni regolari saranno omesse le parentesi. Le precedenze degli operatori sono le stesse dell’algebra, interpretando ∗ come esponente e la concatenazione come prodotto (si veda Esercizio 4.2). E’ ammesso usare l’abbreviazione r+ per l’espressione regolare rr∗ . Esercizio 4.2. Si provino o refutino le seguenti identità tra espressioni regolari: (1) (2) (3) (4) (5) (6) (7) (8) (9) (10) (11) (12) (13) (14) r + s = s + r; (r + s) + t = r + (s + t); r(st) = (rs)t; r(s + t) = rs + rt; (r + s)t = rt + st; ∅∗ = ε; (r∗ )∗ = r∗ ; (ε + r∗ r) = r∗ ; (ε + r)∗ = r∗ ; (r∗ s∗ )∗ = (r + s)∗ ; (rs + r)∗ r = r(sr + r)∗ ; s(rs + s)∗ r = rr∗ s(rr∗ s)∗ ; (r + s)∗ = r∗ + s∗ ; (r∗ + s∗ )∗ = (rs)∗ . 3. Equivalenza tra DFA e ER Teorema 4.3 (McNaughton & Yamada, 1960). Sia r una espressione regolare. Allora esiste un ε-NFA M tale che L(M) = L(r). Proof. Costruiremo un ε-NFA siffatto, con un unico stato finale, per induzione sulla complessità strutturale dell’espressione regolare r. Base: Ci sono tre casi base: • l’automa: ✓✏ ❵ ✒✑ q0 riconosce il linguaggio {ε}; • l’automa riconosce il linguaggio ∅; ✓✏ ✓✏ ❵ q 0❵ q1 ✒✑ ✒✑ 3. EQUIVALENZA TRA DFA E ER • l’automa 43 ✓✏ ✓✏ ❵ a ✲ q1 q 0❵ ✒✑ ✒✑ riconosce il linguaggio {a}. Passo: Anche qui abbiamo tre casi da analizzare: • r = r1 + r2 . Per i = 1, 2, sia Mi , con stato iniziale qi0 e stato finale qif l’automa che riconosce L(ri ). L’esistenza di tali automi è assicurata dall’ipotesi induttiva. Il seguente automa, con stato iniziale q0 e stato finale qf riconosce il linguaggio L(r) = L(r1 ) ∪ L(r2 ): ✬ ✓✏ ✒✒✑ ε ✫ ✓✏ q1 0 M1 ✬ ✒✑ ◗ ε ◗ ✓✏ ◗ s 2 ◗ M2 q0 ✒✑ ✫ q0 ✩ ✓✏ ✒✑ ❅ ε ✪ ❅ ❘✓✏ ❅ q1 f ✒✑ ✩ ✒ ε ✓✏ qf ✒✑ ✪ q2 f • r = r1 r2 . Per i = 1, 2, sia Mi , con stato iniziale qi0 e stato finale qif l’automa che riconosce L(ri ). L’esistenza di tali automi è assicurata dall’ipotesi induttiva. Il seguente automa, con stato iniziale q0 e stato finale qf riconosce il linguaggio L(r1 )L(r2 ): ✬ ✓✏ ✓✏ ε ✲ q 10 q0 M1 ✒✑ ✒✑ ✫ ✬ ✩ ✓✏ ✓✏ ε ✲ q2 M2 q1 0 f ✒✑ ✒✑ ✫ ✪ ✩ ✓✏ ✓✏ ε ✲ qf q2 f ✒✑ ✒✑ ✪ • r = r∗1 . Sia M1 , con stato iniziale q10 e stato finale q1f l’automa che riconosce L(r1 ). L’esistenza di tale automa è assicurata dall’ipotesi induttiva. Il seguente automa, con stato iniziale q0 e stato finale qf riconosce L(r) = (L(r1 ))∗ : 44 4. ESPRESSIONI REGOLARI ε ✬ ✬ ✓✏ ✓✏ ❄ ε ✲ q 10 q0 M1 ✒✑ ✒✑ ✫ ε ✫ ✩ ✩ ✓✏ ✓✏ ε ✲ qf q1 f ✒✑ ✒✑ ✪ ✻ ✪ Le dimostrazioni che tali automi riconoscono esattamente i linguaggi a loro assegnati è lasciata per esercizio.  Esercizio 4.4. Si costruisca l’ε-NFA per l’espressione regolare: (0 + 1)∗ 0(0 + 1)(0 + 1). Si passi poi automaticamente al NFA e quindi al DFA equivalente. Lo si confronti dunque con l’automa dell’esercizio 3.11. Si vuole ore mostrare il contrario, ovvero che preso a caso un automa M (senza perdita di generalità è sufficiente considerare un DFA), il linguaggio accettato da M è denotato da una espressione regolare. Si darà una dimostrazione costruttiva di questo fatto. Per una dimostrazione alternativa (e più rigorosa), si veda [13]. Teorema 4.5. Sia M un DFA. Allora esiste una espressione regolare r tale che L(M) = L(r). Proof. (Traccia) Si costruirà, a partire da M = hQ, Σ, δ, q0 , Fi un sistema di equazioni in cui ad ogni stato qi viene associata una variabile Qi . Si fornirà una metodologia per calcolare una soluzione a tale sistema. La soluzione calcolata assegnerà una espressione regolare ri ad ogni variabile Qi , che ne denota il linguaggio. Se F = {qj1 , . . . , qjp }, avremo che: r = rj1 + · · · + rjp e L(r) è il linguaggio accettato dall’automa. Per ogni qi ∈ Q, siano: • qi1 , . . . , qih gli stati diversi da qi tali che esiste aij ∈ Σ t.c. δ(qij , aij ) = qi ; • bi1 , . . . , bik i simboli tali che δ(qi , bij ) = qi . Assegnamo a qi l’equazione: Qi = (Qi1 ai1 + · · · + Qih aih )(bi1 + · · · + bik )∗ Nel caso che qi fosse q0 bisogna aggiungere +ε alla parte destra. Un tale sistema si può risolvere mediante manipolazioni algebriche sulle espressioni regolari presenti ed usando il metodo di sostituzione (standard) qualora (come accade all’inizio) Qi non compaia nella parte destra dell’equazione che lo definisce. Se non fosse cosı̀, poiché le variabili Qj compaiono sempre all’inizio delle espressioni presenti nelle parti destre delle equazioni, ci si riesce a ricondurre, mediante 3. EQUIVALENZA TRA DFA E ER 45 manipolazioni algebriche, ad una equazione della forma: Qi = Qi s1 + s2 per opportuni s1 ed s2 in cui Qi non compare. La soluzione di questa equazione è Qi = s2 s∗1 ; infatti: + ∗ (s2 s∗1 )s1 + s2 = s2 s+ 1 + s2 = s2 (s1 + ε) = s2 s1 . Si applichi dunque la sostituzione Qi = s2 s∗1 al resto del sistema.  Esercizio 4.6. Mediante la costruzione del Teorema 4.3 si identifichi un εNFA che riconosca il linguaggio denotato dall’espressione regolare ((0+1)∗ 0011(0+ 1)∗ )∗ . Si passi poi, mediante le trasformazioni suggerite nei Teoremi 3.12 e 3.9 al corrispondente DFA. Di quanti stati si ha bisogno? Si cerchi di costruire direttamente un DFA che riconosca lo stesso linguaggio. Esercizio 4.7. Si determinino le espressioni regolari associate ai DFA finora presentati nel testo o descritti per risolvere gli esercizi. CHAPTER 5 Proprietà dei linguaggi regolari 2 e 4 si è imparato che se un linguaggio è riconosciuto da un automa D aia Capitoli stati finiti (equivalentemente, DFA, NFA, ε-NFA) allora è un linguaggio regolare. Argomenti di questo capitolo saranno i principali risultati e metodi utili per stabilire se un dato linguaggio sia o meno regolare. 1. Il “Pumping Lemma” Il primo metodo che proponiamo e che viene spesso usato per mostrare che un linguaggio non è regolare deriva dal seguente risultato noto come Pumping Lemma: Lemma 5.1 (Bar-Hillel, Perles, Shamir, 1961). Sia L un linguaggio regolare. Allora esiste una costante n ∈ N tale che per ogni z ∈ L tale che |z| ≥ n esistono tre stringhe u, v, w tali che: (1) z = uvw, (2) |uv| ≤ n, (3) |v| > 0, e (4) per ogni i ≥ 0 vale che uvi w ∈ L. Proof. Sia M = h{q0 , . . . , qn−1 }, Σ, δ, q0 , Fi DFA tale che L = L(M) (esiste poiché L è regolare). Sia z = a1 · · · am , m ≥ n, z ∈ L (si noti l’arbitrarietà della scelta di z; se una tale z non esistesse, il lemma varrebbe banalmente). Per i = 1, . . . , n (n ≤ m) si consideri l’evoluzione degli stati ^δ(q0 , a1 · · · ai ). In tal modo, considerando anche lo stato iniziale q0 , si raggiungono in tutto n + 1 stati. Poiché gli stati dell’automa sono n per ipotesi, esiste (almeno) uno stato q̄ raggiunto (almeno) due volte. Dunque avremo una situazione del tipo ^ δ(q0 , a1 · · · ai1 ) = = q̄ ^δ(q0 , a1 · · · ai1 · · · ai2 ) i2 > i1 A questo punto, si prenda u = a1 · · · ai1 , v = ai1 +1 · · · ai2 , w = ai2 +1 · · · am . δ(q0 , u) = q̄ e ^δ(q0 , uv) = q̄. Usando l’esercizio 3.8 si Sappiamo dunque che ^ δ(q̄, v) = q̄. ha che ^ Inoltre, sappiamo per ipotesi che ^δ(q0 , uvw) = q ′ per qualche q ′ ∈ F; assieme al fatto che ^ δ(q0 , uv) = q̄, usando l’esercizio 3.8 si ha che ^δ(q̄, w) = q ′ . 47 48 5. PROPRIETÀ DEI LINGUAGGI REGOLARI Per induzione su n ≥ 0 si mostra che ^δ(q0 , uvi ) = q̄. Con l’ultima proprietà vista, si ha dunque che ^ δ(q0 , uvwi ) = q ′ ∈ F. |uv| ≤ n per costruzione.  Corollario 5.2. n del Lemma 5.1 può essere preso come il numero di stati del più piccolo automa riconoscente L. Proof. Immediato dalla dim. del Lemma.  Si osservi come il lemma asserisca la veridicità di una formula logica quantificata non banale, ovvero, per ogni linguaggio L, se L è regolare, allora vale:   (z ∈ L ∧ |z| ≥ n) → ∃u, v, w       ∃n ∈ N ∀z  (1.1)  z = uvw ∧ |uv| ≤ n ∧ |v| > 0∧     ∀i (i ∈ N → uvi w ∈ L) Il Pumping lemma viene largamente usato per mostrare che un dato linguaggio non è regolare (qualora non lo sia!). Per mostrare ciò, si assume che L non sia regolare e si deve mostrare che vale la negazione della formula (1.1), ovvero (si veda il Capitolo 4 per le tecniche di complementazione di una formula logica):   z ∈ L ∧ |z| ≥ n∧     (1.2) ∀n ∈ N ∃z  ∀u, v, w (z = uvw ∧ |uv| ≤ n ∧ |v| > 0    → ∃i (i ∈ N ∧ uvi w ∈ / L) Nel prossimo esempio si illustrerà un corretto uso di questa tecnica, che dato l’annidamento di quantificazioni, può indurre ad errore. Esempio 5.3. Il linguaggio L = {0i 1i : i ≥ 0} non è regolare. Supponiamo, per assurdo, sia regolare e cerchiamo di verificare la formula (1.2). Si prenda un numero naturale n arbitrario e si scelga (questo è il punto in cui bisogna avere la “fortuna” di scegliere una stringa “giusta” tra tutte quelle di lunghezza maggiore o uguale a n) la stringa z = 0n 1n . Per tale stringa vale che z ∈ L e |z| ≥ n. Rimane da dimostrare che comunque si suddivida la stringa z in sottostringhe uvw (in modo tale cioè che z = uvw) tali da soddisfare i vincoli |uv| ≤ n e |v| > 0 esiste almeno un i ≥ 0 tale per cui uvi w ∈ / L. I modi per partizionare z sono molti (per esercizio, si provi a pensare quanti) però, il fatto che |uv| ≤ n permette di ricondursi al seguente unico “schema di suddivisione”: • u = 0a , • v = 0b , • w = 0c 1n , con a + b + c = n, b > 0. “Pompando” v si ottengono stringhe al di fuori del linguaggio, da cui l’assurdo. Ad esempio, con i = 0 vale che uv0 w = 0a 0c 1n ∈ / L, poiché a + c < n. 2. PROPRIETÀ DI CHIUSURA 49 Esercizio 5.4. Dimostrare che i seguenti linguaggi non sono regolari: n (1) {02 : n ≥ 1}; n (2) {0 : n è primo}; (3) {0m 1m+1 0m+2 : m ≥ 0}; (4) {0m 1n 0m+n : m ≥ 1, n ≥ 1}; (5)  {0m 1n : m, n ≥ 0, m < n}. (6) x ∈ {0, 1}∗ : # di 0 in x = # di 1 in x 2. Proprietà di chiusura La definizione di linguaggio è una definizione di tipo insiemistico (L ⊆ Σ∗ ). Pertanto è lecito parlare di unione, intersezione, complementazione (etc.) di linguaggi. In questo paragrafo mostreremo che la proprietà di essere un linguaggio regolare si conserva sotto queste usuali operazioni. Teorema 5.5. I linguaggi regolari sono chiusi rispetto alle operazioni di unione, concatenazione e chiusura di Kleene. Proof. Immediato dalla definizione di espressione regolare e dai Teoremi 4.3 e 4.5.  Teorema 5.6. I linguaggi regolari sono chiusi rispetto alla operazione di complementazione. Ovvero, se L ⊆ Σ∗ è regolare, anche L̄ = Σ∗ \ L è regolare. Proof. Sia M = hQ, Σ ′ , δ, q0 , Fi il DFA che riconosce L. Per l’esercizio 3.8.4, possiamo assumere Σ ′ = Σ. Allora, banalmente, M ′ = hQ, Σ, δ, q0 , Q\Fi riconosce L̄.  Corollario 5.7. I linguaggi regolari sono chiusi rispetto all’intersezione. Proof. Immediato dal fatto che L1 ∩ L2 = (L̄1 ∪ L̄2 ).  Esercizio 5.8. Dati due DFA M1 e M2 , si costruisca l’automa che riconosce M1 ∩ M2 in maniera diretta. Di quanti stati avete bisogno? Esercizio 5.9. Si dimostri che l’insieme delle stringhe di 0 e 1 tali che: • non vi sono mai 3 “0” consecutivi e non vi sono mai tre “1” consecutivi, oppure • vi sono almeno 2 “0” seguiti da 2 “1” è un linguaggio regolare (Suggerimento: si suddivida il problema in problemi elementari e si usino i risultati di questo paragrafo per combinarli). Esercizio 5.10. Si dimostri che il linguaggio composto da stringhe di 0 e 1 tale che: • contengono almeno 5 “0” consecuitivi, e • se vi sono 3 “1” consecutivi, allora essi sono seguiti da almeno due “0” è regolare. 50 5. PROPRIETÀ DEI LINGUAGGI REGOLARI 3. Risultati di decidibilità In questo paragrafo saranno mostrati alcuni risultati relativi al problema di effettuare delle decisioni riguardanti linguaggi regolari. Un primo risultato scontato in questa classe di linguaggi (meno in altre!) è la decidibilità del problema dell’appartenenza, ovvero: data una descrizione del linguaggio L (mediante e.r. o automa) e una stringa x, decidere se x ∈ L o meno. Se è data l’e.r., possiamo prima calcolare l’automa. Dato l’automa il problema diventa banale: tale problema è evidentemente decidibile. Vediamo ora altri problemi. Teorema 5.11 (Vuoto-infinito). L’insieme delle stringhe accettate da un DFA M con n stati è: (1) non vuoto se e solo se accetta una stringa di lunghezza inferiore a n; (2) infinito se e solo se l’automa accetta una stringa di lunghezza ℓ, n ≤ ℓ < 2n. Proof. (1) (←) Se accetta una stringa (di lunghezza di inferiore a n) allora è banalmente non vuoto. (→) Supponiamo sia non vuoto. Allora, per definizione M accetta almeno una stringa z, |z| = m. Se m < n, la tesi è provata. Altrimenti come conseguenza del il pumping lemma, z = uvw con |v| ≥ 1, e uv0 w = uw è accettata da M. Se |uw| < n la tesi è provata, altrimenti si proceda iterativamente ripartendo con z = uw (che ha lunghezza strettamente minore a m). Dopo al più m − n iterazioni si otterrà una stringa di lunghezza inferiore a n; (2) (←) Supponiamo M accetti una stringa z di lunghezza ℓ, n ≤ ℓ < 2n. Allora, per il pumping Lemma, z = uvw, |v| ≥ 1 e {uvi w : i ∈ N} ⊆ L(M). Ma {uvi w : i ∈ N} è un insieme infinito. (→) Sia L(M) infinito. Allora esiste z ∈ L(M) tale che |z| = m ≥ 2n. Per il pumping lemma z = uvw con |uv| ≤ n e |v| ≥ 1 (e dunque |uw| ≥ n). Per il pumping lemma, z ′ = uw ∈ L(M). Se |z ′ | < 2n, allora la tesi è dimostrata. Altrimenti, si reiteri il procedimento partendo dalla stringa (più corta) z ′ = uw. In un numero finito di passi si trova la stringa cercata.  Corollario 5.12. Sia M DFA. Allora i problemi ‘L(M) = ∅’ e ‘L(M) è infinito’ sono entrambi decidibili (cioè esiste una procedura effettiva per determinare la loro veridicità o falsità). Teorema 5.13 (Equivalenza). Dati due DFA M1 e M2 , il problema di stabilire se L(M1 ) = L(M2 ) è decidibile. Proof. L(M1 ) = L(M2 ) è insiemisticamente equivalente a (L(M1 ) ∩ L(M2 )) ∪ (L(M1 ) ∩ L(M2 )) = ∅ . 4. IL TEOREMA DI MYHILL-NERODE 51 Dai Teoremi 5.5 e 5.6 sappiamo esistere un automa M3 tale che L(M3 ) = (L(M1 ) ∩ L(M2 )) ∪ (L(M1 ) ∩ L(M2 )) . Il risultato segue dalla decidibilità del problema del vuoto (Corollario 5.12).  4. Il teorema di Myhill-Nerode Iniziamo il paragrafo richiamando alcune definizioni. Dato un insieme S, una relazione di equivalenza R ⊆ S × S (si veda il Capitolo 2) induce (univocamente, a meno di ridenominazione degli indici) una partizione di S = S1 ∪ S2 ∪ . . . ove per ogni i Si 6= ∅ e per ogni i, j, i 6= j, si ha che: (1) Si ∩ Sj = ∅; (2) (∀a, b ∈ Si )a R b; (3) (∀a ∈ Si )(∀b ∈ Sj )¬(a R b). Le varie Si sono dette classi di equivalenza. Se a ∈ Si allora con la notazione [a]R (o semplicemente [a] quando il contesto è chiaro) si denota la classe Si . Se S è partizionato in un numero finito di classi S1 ∪ S2 ∪ · · · ∪ Sk allora R si dice di indice finito (k) su S. Date due relazioni di equivalenza R1 e R2 sullo stesso insieme S, R1 è un raffinamento di R2 se ogni classe di equivalenza della partizione indotta da R1 è sottoinsieme di qualche classe di equivalenza della partizione indotta da R2 . Ad esempio, se S = {2, 3, 4, 5}, P1 = {{2, 3, 5}, {4}} e P2 = {{2}, {3, 5}, {4}} denotano le partizioni di S ottenute a partire dalle relazioni (R1 ) xR1 y sse x e y sono entrambi primi o uguali tra loro (R2 ) xR2 y sse x e y sono entrambi primi e dispari, o uguali tra loro allora R2 è un raffinamento di R1 . Esercizio 5.14. Dato un insieme S finito, quanti sono i modi distinti per partizionarlo? Dato un linguaggio L ⊆ Σ∗ , gli associamo la relazione RL ⊆ Σ∗ × Σ∗ definita come segue: x RL y sse (∀z ∈ Σ∗ )(xz ∈ L ↔ yz ∈ L) . Similmente, se M = hQ, Σ, δ, q0 , Fi è un DFA, gli associamo la relazione RM ⊆ Σ∗ × Σ∗ definita come: x RM y sse ^δ(q0 , x) = ^δ(q0 , y) . Definiamo come Lq = {x ∈ Σ∗ : ^δ(q0 , x) = q} il linguaggio associato allo stato q dell’automa. Le classi di equivalenza di RM sono esattamente i linguaggi associati ad ogni stato dell’automa M. Lemma 5.15. RL e RM sono relazioni di equivalenza. 52 5. PROPRIETÀ DEI LINGUAGGI REGOLARI Proof. (Esercizio)  Una relazione R ⊆ Σ∗ × Σ∗ che gode della proprietà: x R y implica (∀z ∈ Σ∗ )(xz R yz) si dice invariante a destra (rispetto alla concatenazione). Dall’esercizio 3.8.1, si evince che RM è invariante a destra. Anche RL è invariante a destra: siano x, y ∈ Σ∗ tali che xRL y. Sia z ∈ Σ∗ : dobbiamo mostrare che xzRL yz. Se cosı̀ non fosse, allora esisterebbe w tale che xzw ∈ L e yzw ∈ / L (o viceversa). Ma in tal caso, con z ′ = zw, mostreremmo che x 6RL y: assurdo. Teorema 5.16 (Myhill-Nerode, 1957–58). I seguenti enunciati sono equivalenti: (1) L ⊆ Σ∗ è accettato da un qualche DFA; (2) L è l’unione di classi di equivalenza di Σ∗ indotte da una relazione invariante a destra e di indice finito; (3) RL è di indice finito. Proof. (1) → (2) Sia L accettato da un DFA M = hQ, Σ, δ, q0 , Fi. Mostreremo che RM è la relazione che soddisfa il punto (2). Per definizione di linguaggio riconosciuto da un automa: [ L= {x ∈ Σ∗ : ^δ(q0 , x) = q} q∈F Per l’esercizio 3.8.1, RM è invariante a destra, dunque i vari insiemi: Lq = {x ∈ Σ∗ : ^δ(q0 , x) = q} costituiscono le classi di equivalenza della partizione indotta da RM . (2) → (3) Mostreremo che ogni relazione di equivalenza R che soddisfa (2) è un raffinamento di RL . Sia x ∈ Σ∗ , vogliamo mostrare che [x]R ⊆ [x]RL . Sia y ∈ [x]R (dunque x R y). Poiché R è invariante a destra per ipotesi, allora xz R yz per ogni z ∈ Σ∗ . Poiché L è unione di classi di equivalenza di R, ciò implica che ogni qualvolta vRw si ha che v ∈ L sse w ∈ L. Pertanto per ogni z ∈ Σ∗ , xz ∈ L sse yz ∈ L. Ma allora x RL y per definizione, dunque y ∈ [x]RL . L’indice di RL è minore di quello di R, che per ipotesi è finito. (3) → (1) Si costruisce un DFA M ′ = hQ ′ , Σ ′ , δ ′ , q0′ , F ′ i che riconosce L. Sia • Q ′ l’insieme (finito per ipotesi) di classi di equivalenza di RL , • Σ ′ lo stesso di L, • δ ′ ([x], a) = [xa] (la definizione ha senso indipendentemente dalla scelta di x in quanto RL è invariante a destra), • q0′ = [ε], • F ′ = {[x] : x ∈ L}. 5. MINIMIZZAZIONE DI DFA 53 Si tratta di mostrare che L(M ′ ) = L. Si verifica per induzione su |y| ≥ 0 che ^δ ′ ([x], y) = [xy]. Pertanto si ha che: ^ δ ′ (q0′ , x) = δ^ ′ ([ε], x) = [εx] = [x] e dunque x ∈ L(M ′ ) sse ^ δ ′ (q0′ , x) ∈ F ′ sse [x] ∈ F sse x ∈ L .  5. Minimizzazione di DFA Il Teorema 5.16 ha, tra le sue varie conseguenze, quella di stabilire che se un linguaggio L è riconosciuto da un DFA M allora ne esiste uno minimo M ′ (nel senso del numero degli stati) equivalente a M. Teorema 5.17. Per ogni linguaggio regolare L esiste un automa M con minimo numero di stati tale che L = L(M), unico a meno di isomorfismo (ovvero ridenominazione di stati). Proof. Sia L accettato da un DFA M. Σ∗ è partizionato dalla relazione di equivalenza RM negli insiemi di linguaggi accettati dai singoli stati di M. Dal Teorema 5.16 (1 → 2) sappiamo che RM raffina RL e pertanto il numero di stati di M è maggiore o uguale a quello di M ′ costruito come nella dimostrazione (3 → 1) del Teorema 5.16. Dunque l’automa M ′ ha il minor numero di stati possibile. Supponiamo che tale numero coincida con il numero di stati di M. Definiamo la funzione f tra gli insiemi di stati dei due automi nel modo seguente: f(q) = [x] sse ^δ(q0 , x) = q . Mostriamo che questa è una buona definizione. Vogliamo essere sicuri che f(q) è univoca. Supponiamo, per assurdo, f(q) = [y1 ] e f(q) = [y2 ]. Allora ^δ(q0 , y1 ) = q e^ δ(q0 , y2 ) = q. Ma allora y1 RM y2 . Poiché RM è raffinamento di RL si ha anche [y1 ] = [y2 ]. La funzione è suriettiva, in quanto per ogni x ∈ Σ∗ si ha che esiste q = ^δ(q0 , x) tale che f(q) = [x]. Poiché |Q| = |Q ′ | ciò implica che f è biiettiva, dunque vi è una funzione di rinomina dei nodi. Per mostrare che i due automi sono isomorfi rimane da mostrare che il seguente diagramma commuta, per ogni q ∈ Q e per ogni a ∈ Σ: q ↓a q ′ f −→ f [x] ↓a −→ [xa] In altri termini, dati f(q) = [x], δ(q, a) = q ′ , δ ′ ([x], a) = [xa], dobbiamo mostrare che f(q ′ ) = [xa]: 54 5. PROPRIETÀ DEI LINGUAGGI REGOLARI f(q ′ ) = [xa] sse q ′ = ^δ(q0 , xa) def. di f sse q ′ = δ(^δ(q0 , x), a) def. di ^δ sse q ′ = δ(q, a) vero per ipotesi.  In Fig. 1 è presentato un algoritmo che permette di ottenere automaticamente l’automa minimo corrispondente ad uno dato. Esempio 5.18. La minimizzazione dell’automa: 0 1 q0 q1 q3 q1 q2 q3 q2 q0 q3 q3 q3 q3 0 1 q0 q0 q3 q3 q3 q3 F conduce all’automa: F in un solo passo. Invece la minimizzazione dell’automa: 0 1 q0 q0 q1 q1 q1 q2 q2 q2 q3 q3 q3 q3 F restituisce l’automa stesso. Sono però necessarie 3 (= |Q| − 1) iterazioni. Esercizio 5.19 (Facoltativo). Si dimostri che l’algoritmo di Fig. 1 permette effettivamente di calcolare un automa equivalente e minimo. (Suggerimento: dato M = hQ, Σ, δ, q0 , Fi e p, q ∈ Q, diciamo che p è distinguibile da q se esiste una stringa x tale che ^ δ(p, x) ∈ F ↔ ^δ(q, x) ∈ / F. Per il pumping lemma, sappiamo che per verificare la distinguibilità di due stati è sufficiente provare tutte le stringhe x di lunghezza minore a |Q|. Si tratta di mostrare che due stati sono non distinguibili se e solo se sono equivalenti per RL(M) e che con al più |Q| iterazioni dell’algoritmo tutti gli stati distinguibili sono scovati.) 5. MINIMIZZAZIONE DI DFA 55 procedure minimizza; M = hQ, Σ, δ, q0 , Fi; input: output: M ′ = hQ ′ , Σ, δ ′ , q0′ , F ′ i t.c. L(M) = L(M ′ ) e (∀M ′′ = hQ ′′ , Σ, δ ′′ , q0′′ , F ′′ i)(L(M) = L(M ′′ ) → |Q ′ | ≤ |Q ′′ |); begin Πnew := {Q − F, F}; repeat Π := Πnew ; Πnew := ∅; for S in Π do begin partiziona S in S1 , . . . , Sm usando il valore di m più piccolo possibile t.c. p, q ∈ Si sse (∀a ∈ Σ)(∃S ′ ∈ Π)(δ(p, a) ∈ S ′ ↔ δ(q, a) ∈ S ′ ) Πnew := Πnew ∪ {S1 , . . . , Sm } end until Πnew = Π; for S in Π let S̄ = mini {qi ∈ S}; Q ′ := {S̄ : S ∈ Π}; δ ′ (S̄, a) := mini {qi : qj ∈ S, δ(qj , a) = qi }; F ′ := {[S] : S ∈ Π, (∃q ∈ S)(q ∈ F)}; q0′ := q0 ; si eliminino ricorsivamente da Q ′ e F ′ gli stati diversi da q0′ privi di archi entranti e si eliminino da δ gli archi uscenti da essi. end. Figure 1. Algoritmo di minimizzazione 56 5. PROPRIETÀ DEI LINGUAGGI REGOLARI Esercizio 5.20. Si concretizzi in un linguaggio di programmazione ad alto livello l’algoritmo di Fig. 1 in modo tale che il problema sia risolto con complessità O(|Q|2 |Σ|). Si mediti su come arrivare alla complessità O(|Q| log |Q||Σ|). Esercizio 5.21. Si determini, usando le tecniche di trasformazione viste finora, il DFA minimo equivalente all’automa: ✛✘0 ✛✘ ✲ q0 ✛ q2 0 ✚ ❃ ✚✙ ✚✙ ◗ ✚ ✻ ◗ ✚✚ ✚ ◗ 1 0 0 0 ✚✚ ✚ ✚◗◗1 ✛✘ ❄ ❄ ✛✘ s ✓✏ ◗ ✚✚ ❂✚ 0 ✲ q3 q1 ✒✑ 1 ✚✙ ✚✙ Si determini inoltre, dimostrando in modo formale la propria affermazione, il linguaggio riconosciuto dal DFA calcolato. Esercizio 5.22. Si determini l’automa minimo per il linguaggio denotato dall’espressione regolare: (0∗ + 1∗ + (01)∗ ) CHAPTER 6 Grammatiche libere dal contesto na grammatica è, intuitivamente, un insieme di regole che permettono di U generare un linguaggio. Un ruolo fondamentale tra le grammatiche è costituito dalle grammatiche libere dal contesto mediante le quali vengono solitamente descritti i linguaggi di programmazione. 1. Definizione formale Una grammatica libera dal contesto (CF) è una quadrupla G = hV, T, P, Si, ove: • V è un insieme finito di variabili (dette anche simboli non terminali), • T è un insieme finito di simboli terminali (V ∩ T = ∅), • P è un insieme finito di produzioni; ogni produzione è della forma A → α,1 ove: – A ∈ V è una variabile, e – α ∈ (V ∪ T )∗ . • S ∈ V è una variabile speciale, detta simbolo iniziale. Esempio 6.1. Sia G la grammatica seguente: G = {{E}, {or, and, not, (, ), 0, 1}, P, E} ove P è costituito da: E 7→ 0 E 7→ 1 E 7→ (E or E) E 7→ (E and E) E 7→ (not E) Come vedremo, G genererà le possibili espressioni booleane. Notazione 6.2. Per le grammatiche saranno utilizzate le seguenti notazioni: lettere maiuscole A, B, C, D, E, S denotano variabili, S—a meno che non sia esplicitamente detto il contrario—il simbolo iniziale. a, b, c, d, e, 0, 1 denotano simboli terminali; X, Y, Z denotano simboli che possono essere sia terminali che variabili; 1 Oppure, equivalentemente, si può vedere come una coppia hA, αi ∈ V × (V ∪ T)∗ . 57 58 6. GRAMMATICHE LIBERE DAL CONTESTO u, v, w, x, y, z denotano stringhe di terminali, α, β, γ, δ stringhe generiche di simboli (sia terminali che non). Se A → α1 , . . . , A → αn ∈ P, esprimeremo questo fatto scrivendo: A → α1 | · · · |αn . Con A-produzione denoteremo una generica produzione con la variabile A a sinistra. La grammatica dell’esempio 6.1 può essere dunque schematicamente rappresentata da: E 7→ 0|1|(E or E)|(E and E)|(not E) . 2. Linguaggio generato Le grammatiche servono a generare un linguaggio. Daremo ora le definizioni necessarie a definire il linguaggio generato da una grammatica G = hV, T, P, Si. G G G Definiremo le relazioni ⇒, ⇒i , ⇒∗ ⊆ (V ∪ T )∗ × (V ∪ T )∗ nel seguente modo: G • se A → β ∈ P e α, γ ∈ (V ∪ T )∗ , allora αAγ ⇒ αβγ. Diremo in questo caso che da αAγ deriva immediatamente αβγ; • se α1 , . . . , αi ∈ (V ∪ T )∗ , i ≥ 1, e i−1 ^ j=1 G G αj ⇒ αj+1 , allora α1 ⇒i αm . In questo caso diremo che da α1 deriva αm in i passi. G Per ogni α, diremo anche che α ⇒0 α; G G • se esiste i tale per cui α ⇒i β, allora α ⇒∗ β. Diremo in tal caso che da α deriva β. G G Si noti che ⇒∗ è la chiusura transitiva e riflessiva della relazione ⇒. In partiG colare vale che α ⇒∗ α per ogni α ∈ (V ∪ T )∗ . Il linguaggio generato da G è: G L(G) = {w ∈ T ∗ : S ⇒∗ w} . L è un liguaggio libero dal contesto (CF) se esiste una grammatica CF G tale che L = L(G). Due grammatiche G1 e G2 sono equivalenti se L(G1 ) = L(G2 ). Esempio 6.3. Σ∗ è un linguaggio CF. Infatti, sia Σ = {s1 , . . . , sn }, allora Σ∗ è generato da: S → ε|s1 S| · · · |sn S . Esempio 6.4. La grammatica schematicamente definita da: genera il linguaggio S → ASB | ε, A → 0, B → 1 {0n 1n : n ≥ 0} (dimostrare ciò per induzione su n come esercizio). 3. ALBERI DI DERIVAZIONE 59 Per quanto dimostrato nell’esempio 5.3 usando il pumping lemma, il linguaggio {0n 1n : n ≥ 0} non è un linguaggio regolare. Pertanto l’insieme dei linguaggi regolari e quello dei linguaggi CF non sono lo stesso insieme. Vedremo, comunque, che il primo insieme è incluso nel secondo. Esempio 6.5. La grammatica dell’Esempio 6.1 genera tutte le espressioni booleane (si dimostri questo fatto per per induzione sulla struttura dell’espressione). 3. Alberi di derivazione Le derivazioni generate da una grammatica vengono ben visualizzate mediante l’ausilio di strutture dati ad albero. Sia G = hV, T, P, Si una grammatica CF. Un albero è un albero di derivazione (parse tree) per G se: (1) ogni vertice ha una etichetta, presa tra V ∪ T ∪ {ε}; (2) l’etichetta della radice appartiene a V; (3) ogni vertice interno (ovvero, non una foglia) ha etichetta appartenente a V; (4) se un vertice n è etichettato con A e n1 , . . . , nk sono (ordinatamente, da sinistra a destra) i vertici figli di A etichettati con X1 , . . . , Xk , allora A → X1 · · · Xk ∈ P ; (5) se un vertice n ha etichetta ε, allora n è una foglia ed è l’unico figlio di suo padre. Si noti che la definizione di albero non impone che tutte le foglie siano etichettate con simboli terminali. L’albero avente un solo nodo etichettato da S è un caso particolare di albero di derivazione. Gli alberi verrano utilizzati per dare una controparte grafica delle derivazioni. Se vi sono foglie etichettate con simboli non terminali, allora l’albero rappresenta una derivazione parziale. Diremo che un albero descrive una stringa α ∈ (V ∪ T )∗ se α è proprio la stringa che possiamo leggere dalle etichette delle foglie da sinistra a destra. Esempio 6.6. La grammatica dell’esempio 6.1 genera, in particolare, la stringa w = ((0 or 1) and (not 0)). La derivazione per w è rappresentata dall’albero seguente (che descrive w): jjjtEJTJTJTJTTTT jjjtjttt JJ TTTTT j j j JJ TTTT jj ttt j j j J TT j t (jj ) ? / E and E /  /  /??   / /   ?     //   // ?? ?     (not E ) ( E or E ) 0 1 0 60 6. GRAMMATICHE LIBERE DAL CONTESTO Dato un albero ed un suo nodo n, il sottoalbero identificato da quel nodo è costituito da quel nodo più tutti i suoi discendenti (figli, nipoti, etc.), nonchè le varie etichette associate ad essi. G Teorema 6.7. Sia G = hV, T, P, Si una grammatica CF. Allora S ⇒∗ α se e solo se esiste un albero di derivazione con radice etichettata S per G che descrive α. Proof. Si proverà un enunciato più forte (e più comodo per la dimostrazione G induttiva), ovvero: dato un qualunque simbolo non terminale A ∈ V, A ⇒∗ α se e solo se esiste un albero di derivazione per G che descrive α la cui radice è etichettata A. (→) Proviamo l’enunciato per induzione sul numero i di passi di derivazione per G A ⇒i α. G Base: i = 0: A ⇒0 α implica, per definizione, che α = A. Ma l’albero con unico nodo (radice) etichettato con A è un albero di derivazione (parziale) che descrive A stesso. G G Passo: Supponiamo A ⇒i β1 Bβ3 ⇒1 β1 β2 β3 . Per ipotesi induttiva esiste un | {z } α albero di derivazione T con radice etichettata A che descrive β1 Bβ3 . Ma per G definizione, β1 Bβ3 ⇒1 β1 β2 β3 se e solo se esiste la produzione B → β2 in P. Ma allora, applicando la regola (4) di definizione di albero, dall’albero T si ottiene l’albero che soddisa i requisiti. (←) Proviamo l’enunciato per induzione sul numero di nodi interni dell’albero di derivazione etichettato in A che descrive α. [Completare per esercizio]  4. Ambiguità delle derivazioni Se ad ogni passo di una derivazione la produzione è applicata al simbolo non terminale più a sinistra, allora la derivazione è detta sinistra (leftmost). Similmente, se viene applicata sempre a quello a destra, allora è detta destra (rightmost). Se w ∈ L(G), dal Teorema 6.7 sappiamo che per essa esiste almeno un albero di derivazione con radice etichettata S. Si può dimostrare che, fissato un albero di derivazione con radice etichettata S, esattamente una derivazione sinistra (suggerimento: si visiti l’albero in preordine) ed una derivazione destra (simmetricamente) possono essere desunte. Un albero di derivazione rappresenta dunque un certo insieme di possibili derivazioni distinte di una stessa stringa. Ci possono tuttavia essere più alberi di derivazione per la stessa parola: Esempio 6.8. Si consideri la grammatica Una derivazione sinistra è: G E → E + E|E ∗ E|0|1|2 . G G E ⇒ E + E ⇒ E ∗ E + E ⇒3 2 ∗ 0 + 1 . 5. SEMPLIFICAZIONE 61 Un’altra derivazione sinistra per la stessa stringa è: G G G G E ⇒ E ∗ E ⇒ 2 ∗ E ⇒ 2 ∗ E + E ⇒2 2 ∗ 0 + 1 . Tuttavia gli alberi associati alle due derivazioni (che potrebbero essere visti come alberi per la computazione dell’espressione associata alla stringa generata) sono diversi: oEOOOO OOO ooo o o OOO oo o OO o o o + E?? E   ???  ??   ∗ 1 E E 2 oEOOOO OOO ooo o o OOO oo o OO o o o ∗ E E??  ???  ??   + 2 E E 0 0 1 Il primo è intuitivamente associato a (2∗0)+1 = 1 il secondo, invece, 2∗(0+1) = 2. Una grammatica CF tale per cui esiste una parola con più di un albero di derivazione con radice etichettata S è detta ambigua. Un linguaggio CF per cui ogni grammatica che lo genera è ambigua è detto essere inerentemente ambiguo. Un esempio di tali linguaggi è: L = {an bn cm dm : n ≥ 1, m ≥ 1} ∪ {an bm cm dn : n ≥ 1, m ≥ 1} . La dimostrazione di questa proprietà (3 pagine!) si può trovare in [13]. 5. Semplificazione Vi sono molti modi per restringere la forma delle produzioni permesse alle grammatiche CF senza alterare la classe dei linguaggi riconosciuti dall’insieme delle possibili grammatiche. In questo paragrafo si presenteranno due di queste trasformazioni, che conducono alla forma normale di Chomsky e a quella di Greibach. Mostreremo che ogni linguaggio CF può essere generato da una grammatica G dalle seguenti due proprietà: (1) ogni variabile e ogni simbolo terminale di G compaiono in almeno una derivazione di una qualche parola di L; (2) non ci sono produzioni della forma A → B con A e B variabili. Se, inoltre, ε ∈ / L, non avremo bisogno di produzioni A → ε; sempre sotto tale ipotesi potremo richiedere che ogni produzione sia della forma A → BC o A → a (Chomsky) oppure, alternativamente, che ogni produzione sia della forma A → aα ove α è una stringa di variabili (Greibach). Nel prosieguo del capitolo alcune dimostrazioni tecniche ma dimostrabili immediatamente per induzione saranno lasciate per esercizio. 62 6. GRAMMATICHE LIBERE DAL CONTESTO 5.1. Eliminazione di simboli inutili. Sia G = hV, T, P, Si una grammatica CF. Un simbolo X ∈ V ∪ T è utile se esiste una derivazione G G S ⇒∗ αXβ ⇒∗ w ; altrimenti è detto inutile. Mostreremo come simboli inutili si possano eliminare in due passi. Innanzitutto si eliminano le variabili che non conducono in nessun modo ad una stringa di terminali. Lemma 6.9. Data una grammatica CF G = hV, T, P, Si tale che L(G) 6= ∅, si può calcolare in modo effettivo una grammatica equivalente G ′ = hV ′ , T, P ′ , Si tale G che per ogni A ∈ V ′ esiste w ∈ T ∗ tale che A ⇒∗ w. Proof. Per calcolare V ′ usiamo il seguente operatore Γ : ℘(V) −→ ℘(V): Γ (W) = {A ∈ V : (∃α ∈ (T ∪ W)∗ )(A → α ∈ P)} Γ è monotono—ovvero X ⊆ Y → Γ (X) ⊆ Γ (Y)—per definizione. Definiamo inoltre l’applicazione iterata di un operatore:   Γ 0 (W) = W  Γ i+1 (W) = Γ (Γ i (W)) Per induzione su i ≥ 1, si mostra simultaneamente su tutti gli A ∈ T che:    esiste un albero di derivazione che descrive w ∈ T ∗  sse A ∈ Γ i (∅)  con radice etichettata con A e di altezza ≤ i  (completare per esercizio). G Dunque, per il Teorema 6.7, si ha che A ∈ Γ i (∅) per qualche i sse A ⇒∗ w. Poiché V è finito e Γ monotono, si ha che esiste i ≤ |V| tale per cui Γ i (∅) = Γ (Γ i (∅)) = Γ i+1 (∅)(= Γ i+2 (∅) = · · · ) Pertanto si riesce a calcolare finitamente l’insieme V ′ = Γ |V| (∅), e P ′ = {A → α ∈ P : A ∈ V ′ ∧ α ∈ (V ′ ∪ T )∗ }.  L’ipotesi che il linguaggio sia non vuoto è indispensabile. Se infatti fosse L = ∅, allora l’applicazione del Lemma 6.9 fornirebbe una grammatica con S ∈ / V′ e dunque priva di significato in quanto S sarebbe il simbolo iniziale ma non apparterrebbe alla grammatica. In ogni caso, per definizione, ogni simbolo di una grammatica che genera un linguaggio vuoto è inutile. Si osservi che l’applicazione della procedura del lemma permette anche di determinare se il linguaggio riconosciuto da un automa sia vuoto o meno. Fatto ciò eliminiamo le variabili che non sono mai raggiunte da S: 5. SEMPLIFICAZIONE 63 Lemma 6.10. Data una grammatica CF G = hV, T, P, Si, si può calcolare in modo effettivo una grammatica equivalente G ′ = hV ′ , T ′ , P ′ , S ′ i tale che per ogni G X ∈ (V ′ ∪ T ′ ) esistono α e β in (V ′ ∪ T ′ )∗ per cui S ⇒∗ αXβ. Proof. Definiamo un operatore Γ : ℘(V ∪ T ) −→ ℘(V ∪ T ): Γ (W) = {X ∈ V ∪ T : (∃A ∈ W)(A → αXβ ∈ P) } ∪ {S} Per induzione su i ≥ 0, si mostra (esercizio) che:    esiste un albero di derivazione (parziale)   con radice etichettata con S di altezza ≤ i     in cui X compare come foglia          sse X ∈ Γ i ({S}) Pertanto, per il Teorema 6.7 si ha che X ∈ Γ i ({S}) per qualche i se e solo se esistono G α e β in (V ∪ T )∗ per cui S ⇒∗ αXβ. Γ è monotono, V e T sono finiti, dunque esiste i ≤ |V| + |T | tale che Γ i ({S}) = Γ i+1 ({S})(= Γ i+2 ({S}) = · · · ) Siano dunque: • V ′ = Γ |V|+|T | ({S}) ∩ V; • T ′ = Γ |V|+|T | ({S}) ∩ T ; • P ′ = {A → α ∈ P : A ∈ V ′ ∧ α ∈ (V ′ ∪ T ′ )∗ }.  Si osservi come, introducendo anche ε in Γ (W) qualora A −→ ε ∈ P e A ∈ W, G saremmo anche in grado di capire se S ⇒∗ ε. Teorema 6.11. Ogni linguaggio CF non vuoto è generato da una grammatica CF priva di simboli inutili. Proof. Immediato, applicando prima il Lemma 6.9 e poi il Lemma 6.10.  Esercizio 6.12. Si cerchi una grammatica con linguaggio non vuoto tale per cui applicando prima la procedura del Lemma 6.10 e poi quella del Lemma 6.9 non si giunge all’eliminazione dei simboli inutili. 5.2. Eliminazione di ε-produzioni. Data una grammatica CF che genera un linguaggio L, si desidera eliminare le produzioni della forma A → ε. Qualora ε ∈ L, ammetteremo la presenza della produzione S → ε. G Il metodo è quello di determinare quali variabili A ∈ V sono tali che A ⇒∗ ε. Tali variabili sono dette annullabili. Ciò che poi si farà sarà quello di eliminare, tra le Xi di ogni produzione A → X1 · · · Xn zero, una, . . . , o tutte le variabili annullabili. Se togliendo tutte le variabili annullabili da X1 · · · Xn la stringa diventasse vuota, allora anche A sarà annullabile (ma non aggiungeremo la produzione A → ε, a meno che A non sia S). 64 6. GRAMMATICHE LIBERE DAL CONTESTO Teorema 6.13. Se L = L(G) per qualche grammatica CF G = hV, T, P, Si, allora L \ {ε} è un linguaggio CF generato da una grammatica G ′ = hV ′ , T ′ , P ′ , S ′ i senza simboli inutili e senza ε-produzioni. Proof. Definiamo un operatore Γ : ℘(V) −→ ℘(V): Γ (W) = {A ∈ V : (∃A → α ∈ P) α “privata” delle variabili di W è ε} Anche qui, Γ è monotono, V è finito, dunque esiste i ≤ |V| tale che Γ i (∅) = Γ i+1 (∅). Siano dunque N = Γ |V| (∅) e P ′′ = {A → α1 · · · αn : A → X1 · · · Xn ∈ P, se Xi ∈ / N, allora αi = Xi , se Xi ∈ N, allora αi = Xi o αi = ε, α1 · · · αn 6= ε } E’ immediato mostrare che L(G) = L(hV, T, P ′′ , Si). Applichiamo dunque il Teorema 6.11 alla grammatica hV, T, P ′′ , Si.  Ovviamente, se ε ∈ L, aggiungeremo la produzione S → ε. Si osservi come questa procedura permetta di capire se ε ∈ L. E’ infatti sufficiente vedere se esiste in P una produzione S → A1 · · · Ak con le Ai ∈ N per i = 1, . . . , k, oppure direttamente la produzione S → ε. 5.3. Eliminazione di produzioni unitarie. Una produzione della forma A → B, con A e B variabili, si dice unitaria. Teorema 6.14. Ogni grammatica CF senza ε è definita da una grammatica senza simboli inutili, senza ε-produzioni e senza produzioni unitarie. Proof. Grazie al Teorema 6.13, possiamo partire da una grammatica senza simboli inutili e senza ε-produzioni. Innanzitutto identifichiamo tutte le coppie G di variabili A, B che permettono derivazioni della forma A ⇒∗ B (immediato, seguendo i cammini del grafo ottenuto prendendo come nodi le variabili e come archi le produzioni unitarie). Sia, per ogni variabile A, seguenti(A) l’insieme delle variabili B soddisfacenti la richiesta sopra. A questo punto togliamo da P tutte le produzioni unitarie e, per ogni A ∈ V e per ogni produzione non unitaria B → β ∈ P con B ∈ seguenti(A), B 6= A, aggiungo la produzione A → β. Dimostrare l’equivalenza dei linguaggi è immediato (induzione sul numero di produzioni unitarie presenti in una derivazione). Tuttavia, la procedura potrebbe aver generato simboli inutili (si pensi alla grammatica S −→ A, A −→ a): applichiamo il Teorema 6.11 (si noti che produzioni unitarie non possono essere introdotte dalla procedura di eliminazione di simboli inutili) e otteniamo il risultato cercato.  7. FORMA NORMALE DI GREIBACH 65 6. Forma normale di Chomsky Teorema 6.15 (Chomsky, 1959). Ogni linguaggio CF senza ε è generato da una grammatica in cui tutte le produzioni sono della forma A → BC e A → a. Proof. Per il Teorema 6.14 possiamo partire da una grammatica G = hV, T, P, Si senza ε-produzioni, simboli inutili e produzioni unitarie. Sia A → X1 · · · Xm ∈ P, m ≥ 1 (non vi sono ε-produzioni): • se m = 1, allora, poiché non vi sono produzioni unitarie, X1 ∈ T : è già nella forma cercata; • se m = 2 e X1 , X2 ∈ V, siamo nella forma cercata; • se m ≥ 2 e qualcuno degli Xi ∈ T , allora per ogni Xi ∈ T introduco in V una nuova variabile, diciamo Bi , aggiungo la produzione Bi → Xi e rimpiazzo la produzione A → X1 · · · Xm con A → Y1 · · · Ym , ove Yi = Xi se Xi ∈ V, Yi = Bi altrimenti; • se m > 2 e tutti gli Xi sono variabili, allora rimpiazzo la produzione A → X1 · · · Xm con le produzioni A → BX3 · · · Xm , B → X1 X2 , ove B è una nuova variabile da aggiungere a V. E’ immediato verificare che il procedimento termina e che l’insieme finale di produzioni (P ′ ) è nella forma normale di Chomsky. Si tratta di verificare che le grammatica G ′ = hV ′ , T, P ′ , Si (ove V ′ si ottiene da V con l’aggiunta delle nuove variabili) ottenuta è equivalente a quella di partenza. Ovvero, per A ∈ V: G G′ A ⇒∗ w sse A →∗ w G Dimostriamo, per induzione su i ≥ 1 che A ⇒i w implica che esiste k tale per cui G′ A →i+k w. [Esercizio] G′ G Viceversa, dimostriamo, per induzione su i ≥ 1 che A →i w implica che A ⇒j w per qualche j ≤ i. [Esercizio]  7. Forma normale di Greibach Per raggiungere questa forma normale abbiamo bisogno di due lemmi preliminari. Lemma 6.16 (Unfolding). Sia G = hV, T, P, Si una grammatica CF, sia A → αBγ una produzione di P e B → β1 | · · · |βn l’insieme delle B-produzioni. Sia G ′ = hV, T, P ′ , Si ottenuta da G eliminando la produzione A → αBγ da P e aggiungendo le produzioni A → αβ1 γ| · · · |αβn γ. Allora L(G) = L(G ′ ). Proof. (Esercizio)  Lemma 6.17 (Eliminazione ricorsione sinistra). Sia G = hV, T, P, Si una grammatica CF e sia A → Aα1 | · · · |Aαm l’insieme delle A-produzioni di G per cui A è anche il simbolo più a sinistra della stringa di destra delle produzioni. Siano 66 6. GRAMMATICHE LIBERE DAL CONTESTO A → β1 | · · · |βn le rimanenti A-produzioni. Sia G ′ = hV ∪ {B}, T, P ′ , Si la grammatica CF ottenuta aggiungendo una nuova variabile B a V e rimpiazzando tutte le A-produzioni con: (1) A → βi |βi B per i ∈ {1, . . . , n}; (2) B → αi |αi B per i ∈ {1, . . . , m}. Allora L(G ′ ) = L(G). G Proof. Mostriamo prima che se x ∈ L(G) allora x ∈ L(G ′ ). Se S ⇒∗ x e non vi sono in essa produzioni della forma A → Aαi non c’è nulla da dimostrare. Altrimenti, sia G G G G G G A ⇒ Aαi1 ⇒∗ Aγ1 ⇒ Aαi2 γ1 ⇒∗ Aγ2 ⇒ Aαi3 γ2 ⇒∗ · · · · · · una sottoderivazione in cui tali produzioni si usano (e vengono esplicitate: nei G tratti ⇒∗ assumiamo che tali derivazioni non ci sono). Poiché alla fine si deve ottenere una stringa di terminali, ad un certo punto la A sempre presente in testa sarà rimpiazzata da una βj usando la produzione A → βj . La derivazione può essere mimata con G ′ nel modo seguente: G Aαi1 ⇒ G Aαjk γk−1 G βj B ⇒∗ · · · βj Bγk−1 ⇒ A Aγk−1 A βj Bαj2 γ1 G ⇒ ⇒∗ G Aγi1 G Aγk G βj Bαi1 ⇒∗ ⇒∗ G ⇒ βj αik−1 γk−1 L’implicazione inversa è lasciata per esercizio. G ⇒∗ Aαi2 γ1 G βj Bγ1 G βj γk G ⇒ ⇒∗ ⇒∗ βj γk G ⇒∗ · · · G ⇒∗  Teorema 6.18 (Greibach, 1965). Ogni linguaggio CF senza ε è generato da una grammatica in cui tutte le produzioni sono della forma A → aα, ove α è una stringa (eventualmente vuota) di simboli non terminali. Proof. Sia G una grammatica in forma normale di Chomsky. Sia V = {A1 , . . . , Am }. Si costruirà in tre passi una grammatica G ′ in forma normale di Greibach equivalente a G. (1) Si applichi il seguente algoritmo: for k := 1 to m do begin for j := 1 to k − 1 do elimina le produzioni Ak → Aj α mediante unfolding; end; elimina le produzioni Ak → Ak α mediante elim. ricors. sinistra 7. FORMA NORMALE DI GREIBACH 67 Nuove variabili B1 , . . . , Bh sono state introdotte dalla fase di eliminazione della ricorsione sinistra. L’equivalenza tra G e la grammatica ottenuta deriva dai lemmi appena dimostrati. A questo punto tutte le produzioni sono della forma: Ai Ai Bi → Aj α j > i → aβ a∈T → γ Inoltre, poiché siamo partiti da una grammatica in forma normale di Chomsky, si osserva che α ha un prefisso della forma Ai1 · · · Aih seguito da un simbolo non terminale, seguito ancora da una sequenza eventualmente vuota di variabili. Per lo stesso motivo, β è una stringa di variabili. Per quanto riguarda le stringhe γ, esse non iniziano mai con un Bi . (2) Pertanto, ogni produzione Am → α ha la parte destra iniziante con un simbolo terminale. Posso effettuare l’unfolding delle Ai -produzioni rimpiazzando Am con ciascuna delle sue possibili parti destre. Posso iterare questo procedimento all’indietro, ottenendo produzioni della forma: Ai → aβ a ∈ T per ogni i = 1, . . . , m, con β stringa di variabili. (3) Si tratta ora di semplificare le produzioni Bi → γ. Applicando la sostituzione sul primo (eventuale) simbolo Ai della stringa, si giunge al risultato.  Esercizio 6.19. Si applichi la procedura descritta per condurre in forma normale di Greibach la grammatica dalle produzioni: A1 −→ A3 A3 |a A2 −→ A1 A2 |b A3 −→ A3 A1 |c Esercizio 6.20. Si riconduca alle forme normali di Chomsky e di Greibach la grammatica: S −→ ε|aSb Un importante caso particolare di grammatiche in forma normale di Greibach sono le grammatiche CF in cui le produzioni hanno tutte la forma A → a oppure A → aB. Tali grammatiche, studiate nel Capitolo 9, sono grammatiche lineari destre ed hanno l’importante caratteristica di generare esattamente i linguaggi regolari. CHAPTER 7 Automi a pila n questo capitolo descriviamo una macchina astratta per il riconoscimento dei I linguaggi CF. Questa macchina corrisponde ad un arricchimento degli automi già visti per il caso dei linguaggi regolari, mediante una struttura dati opportuna per memorizzare informazione: la pila. Abbiamo visto che, grazie al pumpinglemma per i linguaggi regolari, il linguaggio  n n a b : n≥0 non è regolare. Intuitivamente l’impossibilità di riconoscere questo linguaggio da parte di un automa a stati finiti è legata all’impossibilità da parte di questi automi di memorizare una quantità arbitrariamente grande (anche se finita) di simboli e ricordarne il numero. Solo in questo modo infatti si può pensare di poter confrontare il numero di occorrenze di “a” e “b” nella stringa da leggere. Una struttura dati adeguata per risolvere questo problema è la pila, ovvero una struttura dati LIFO (Last In First Out). Da un punto di vista architetturale, un automa a pila è una macchina costituita da un controllo che è un automa a stati finiti, un nastro di input che, come negli NFA e DFA, è di sola lettura, ed una pila sulla quale sono possibili le operazioni di: push(X), pop(X), empty (si veda la Figura 1): • L’istruzione push(X) sposta una stringa X in testa alla pila, • l’istruzione pop(X) preleva il simbolo in testa alla pila, e • l’istruzione empty verifica, restituendo valore booleano, se la pila è vuota o meno. s1 s2 s3 · · · nastro · · · Z2 Z3 · · · pila · · · ⇑ q m Z1 Figure 1. Automa a pila 69 70 7. AUTOMI A PILA Le azioni possibili di un automa a pila sono: • Leggere dal nastro e dalla pila (pop) contemporaneamente – avanzare la testina a destra sul nastro – cambiare stato nel controllo – inserire una stringa (anche vuota) sulla pila (push) • Leggere solo dalla pila (pop) – cambiare stato nel controllo – inserire una stringa (anche vuota) sulla pila (push) Definizione 7.1. Un automa a pila non-deterministico (APND) M è una 7-upla: M = hQ, Σ, R, δ, q0 , Z0 , Fi dove: • • • • • • • Q è un insieme finito di stati Σ è un alfabeto finito R è l’alfabeto finito della pila q0 ∈ Q è lo stato iniziale Z0 ∈ R è il simbolo iniziale sulla pila F ⊆ Q è l’insieme degli stati finali δ : Q × (Σ ∪ {ε}) × R → ℘f (Q × R∗ ) è la funzione di transizione L’evoluzione di un automa a pila è determinata dall’evoluzione delle sue descrizioni istantanee. Una descrizione istantanea rappresenta la “fotografia” dello stato (stato + nastro + pila) della macchina ad un certo istante. Definizione 7.2. Una descrizione istantanea per un APND è una tripla (q, x, γ) dove q ∈ Q è lo stato corrente, x ∈ Σ∗ è la stringa ancora da leggere sul nastro e γ ∈ R∗ è la sequenza di simboli contenuti sulla pila. Se (p, γ) ∈ δ(q, a, Z) allora è definito un passo di derivazione dell’APND: (q, aw, Zα) 7→M (p, w, γα) Il linguaggio L(M) riconosciuto da un APND M si può definire in due modi diversi: per pila vuota Lp (M) o per stato finale LF (M):  Lp (M) = x ∈ Σ∗ : (q0 , x, Z0 ) 7→∗M (q, ε, ε), q ∈ Q  LF (M) = x ∈ Σ∗ : (q0 , x, Z0 ) 7→∗M (q, ε, γ), γ ∈ R∗ , q ∈ F Proposizione 7.3. Per ogni APND M, esiste un APND M ′ tale che LF (M) = Lp (M ′ ). Nel seguito, quando l’APND è previsto per riconoscere le stringhe per pila vuota, assumeremo F = ∅. 7. AUTOMI A PILA 71 Esempio 7.4. Consideriamo l’APND che riconosce, per pila vuota, il linguaggio xcxr , dove c ∈ {a, b}∗ e xr è la stringa inversa della stringa x (ad esempio, se x = aab allora xr = baa). L’APND è definito nel modo seguente: h{q0 , q1 }, {a, b, c}, {Z, A, B}, δ, q0, Z, ∅i Dove δ risulta definita dalla seguente matrice (le celle vuote possono essere definite in qualunque modo che conduca sicuramente ad una refutazione): ε q0 a Z b c q1 q0 , ZA q0 , ZB q1 , ε ε a b c Z q1 , Z q1 , Z A A q1 , ε q1 , Z q1 , Z B B q1 , Z q1 , ε q1 , Z Ad esempio, il riconoscimento per pila vuota della stringa abcba avviene come segue: a bcba ↑ a l pila ⇒ l pila Z b ↑ a nastro q1 l B abcb pila A ⇒ ab ⇒ l pila ⇒ BA abcba nastro q1 A nastro Z a l ba q0 A ↑ c ↑ nastro q0 Z abc cba ↑ nastro q0 b pila ⇒ ↑ nastro q1 l pila Si osserva che il precedente APND è in realtà deterministico. Al contrario di quanto accade per gli automi a stati finiti gli automi a pila non-deterministici sono strettamente più potenti di quelli deterministici. Nel caso del linguaggio dell’esempio precedente, questo è facilmente riconoscibile da un automa a pila deterministico in quanto esso è costituito da tutte e sole le stringhe del tipo xcxr . La presenza del simbolo ausiliario c a delimitare le due parti simmetiche della stringa, permette di avere sulla stringa stessa, un elemento di demarcazione tra le fasi di caricamento della pila (la lettura della stringa x) ed il suo scaricamento (la lettura della stringa xr ). In questo modo, il riconoscimento della stringa avviene 72 7. AUTOMI A PILA in modo deterministico: non è necessario attivare nessun processo parallelo di verifica alla ricerca del punto mediano della stringa. Al contrario, ciò non è possibile nel riconoscimento del linguaggio CF delle stringe palindrome. In questo caso, per determinare se la lettura del simbolo corrente sul nastro corrisponde alla lettura del simbolo mediano della stringa, da cui inizia lo scaricamento della pila, ci appoggiamo ad una computazione non-deterministica. È importante notare che gli automi a pila deterministici sono solitamente utilizzati per il riconoscimento sintattico dei linguaggi di programmazione. In particolare, essi costituiscono la base per la realizzazione di compilatori (fase di parsing). A tal proposito, si noti come la sintassi degli usuali linguaggi di programmazione sia strutturata in modo tale da interporre tra categorie sintattiche dei simboli o sequenze di simboli marcatori, ad esempio if, while, then, etc., come nel caso dell’Esempio 7.4. Questo permette di realizzare la fase di parsing del linguaggio attraverso un APD. Esempio 7.5. Costruiamo un APND  che riconosca il linguaggio delle stringhe palindrome sull’alfabeto {a, b}: L = wwr : w ∈ {a, b}∗ . L’automa è definito come segue: • Q = {q0 , q1 } • Σ = {a, b} • R = {Z, A, B} e relazione di transizione: q0 ε a b Z q0 , AZ q0 , BZ q1 ε A q0 , AA q0 , BA Z q1 , ε q1 , ε B q0 , AB A q0 , BB B a b q1 , ε q1 , ε q1 , ε Esercizio 7.6. Scrivere le derivazioni necessarie per riconoscere la stringa: abbba. Lemma 7.7. Se L = Lp (M) con M APND, allora L = Lp (M ′ ) con M ′ APND con 1 solo stato. Proof. (Idea). Sia M = hQ, Σ, R, δ, q0 , Z0 , Fi. L’idea è quella di aumentare i simboli nella pila per descrivere ogni coppia (qi , Zi ) con qi ∈ Q e Zi ∈ R. Per ogni coppia di questo tipo, si aggiunge un nuovo simbolo Zi′ in R e si modifica δ in modo tale che se Z ′ è il simbolo nuovo in corrispondenza della coppia (q, Z), allora: δ(q, a, Z) = δ ′ (q0 , a, Z ′ ). Il lemma segue quindi banalmente per costruzione.  I risultati seguenti dimostrano l’equivalenza tra i linguaggi riconosciuti dagli APND e i linguaggi CF. 7. AUTOMI A PILA 73 Teorema 7.8. Se L = Lp (M) con M APND con 1 stato, allora L è CF. Proof. Sia M = h{q}, Σ, R, δ, q, Z0 , ∅i un APND con 1 solo stato che riconosce il linguaggio Lp (M). Definiamo la grammatica libera da contesto: G = hR, Σ, Z0 , Pi, avente R come alfabeto di simboli non terminali, Z0 ∈ R come simbolo iniziale e le seguenti produzioni: Z → aZ1 Z2 · · · Zn se (q, Z1 Z2 · · · Zn ) ∈ t(q, a, Z) È ovvio che se α ∈ R∗ , allora: G (q, a, Zα) 7→M (q, ε, Z1 Z2 · · · Zn α) sse Zα ⇒ aZ1 Z2 · · · Zn α Per induzione si dimostra che per ogni x ∈ Σ∗ : da cui segue la tesi. G (q, x, Z0 ) 7→∗M (q, ε, ε) sse Z0 ⇒ x  Il seguente teorema stabilisce l’equivalenza tra i linguaggi riconosciuti dagli APND ed il linguaggi CF. Prima di vedere questo risultato, osserviamo l’analogia tra la derivazione da una grammatica CF ed i passi di caricamento/svuotamento della pila durante le varie fasi di riconoscimento delle sottostringhe di una data stringa da parte di un APND. Esempio 7.9. Consideriamo la seguente grammatica G in forma normale di Greibach. S → aB A → a S → bA B A → aS B A → bAA B → bS → aBB → b Sia M l’APND con 1 solo stato q definito dalla seguente relazione di transizione: a b S q, B q, A A q, ε q, AA q ε q, S B q, BB q, ε q, S Si noti come le seguenti derivazioni siano del tutto simmetriche: Teorema 7.10. Sia L CF. Allora esiste un APND M tale che L = Lp (M). Proof. Sia L = L(G) con G = hV, T, P, Si grammatica CF in forma normale di Greibach. Definiamo: 74 7. AUTOMI A PILA S (q, aababb, S)  aB  (q, ababb, B)  aaBB ??  ??  ??    aabSB aabB  (q, babb, ??BB)  ??  ??    (q, abb, B) (q, abb, SB)  aabaBB  aabaBB ??  ??  ??    aababB aababSB o o o ooo  wwooo  aababb aababbAB aababbS   (q, bb, BB) (q, bb,?BB)   ??? ??     (q, b, B) (q, b, SB) o o o ooo  wwooo  (q, ε, ε) (q, ε, S) (q, ε, AB) Figure 2. Simmetria tra albero di derivazione e APND. e dove: • • • • • Q := {q} Σ := T R := V Z0 := S F := ∅ (q, α) ∈ t(q, a, A) sse A → aα ∈ P Si osserva che: G xAβ ⇒1 xaαβ dove α, β ∈ V ∗ e x ∈ T ∗ Ma questo avviene se e solo se l’APND cosı̀ definito fa una mossa del tipo: (q, a, Aβ) 7→1M (q, ε, αβ) G Per induzione [completare per esercizio] sul numero di passi della derivazione ⇒ si dimostra che: G xAβ ⇒n xyαβ sse (q, y, Aβ) 7→n M (q, ε, αβ) G Ne consegue che: S ⇒∗ y sse (q, y, S) 7→∗M (q, ε, ε).  CHAPTER 8 Proprietà dei linguaggi liberi dal contesto i linguaggi CF valgono delle proprietà simili a quelle studiate per i linguaggi P erregolari nel Capitolo 5. 1. Il pumping lemma per i linguaggi CF Lemma 8.1 (Bar-Hillel, Perles, Shamir, 1961). Sia L un linguaggio CF. Allora c’è una costante n ∈ N (dipendente dal linguaggio L) tale che per ogni z ∈ L, |z| ≥ n, esistono stringhe uvwxy tali che (1) z = uvwxy, (2) |vx| ≥ 1, (3) |vwx| ≤ n, e (4) per ogni i ≥ 0 vale che uvi wxi y ∈ L. Proof. Sia G = hV, T, P, Si la grammatica che genera L \ {ε}. Senza perdita di generalità, possiamo assumere che G sia in forma normale di Chomsky (se ε ∈ L, potremmo costruire una nuova grammatica G ′ che estende G con un nuovo simbolo iniziale S ′ e le produzioni S ′ → ε e S ′ → S e modificare appena la dimostrazione— si noti che G ′ non è in forma normale. Poiché il significato del lemma è per stringhe “lunghe”, non complicheremo la dimostrazione in questo modo). Per induzione su i ≥ 1, si mostra (esercizio) che se un albero di derivazione per una stringa z ∈ T ∗ ha tutti i cammini di lunghezza minore o uguale a i, allora |z| ≤ 2i−1 . Sia |V| = k (k > 0) e sia n = 2k (n > 1). Se z ∈ L e |z| ≥ n, allora ogni albero di derivazione per z deve avere un cammino di lunghezza almeno k + 1. Ma tale cammino ha almeno k + 2 nodi, tutti, eccetto l’ultimo, etichettati da variabili. Pertanto vi deve essere una variabile ripetuta nel cammino. In particolare, dunque, vi devono essere due vertici v1 , v2 tali che: (1) entrambi sono etichettati dalla stessa variabile, diciamo A; (2) v1 è più vicino alla radice di v2 ; (3) poiché n ≥ 2, a v1 è associata una produzione del tipo: A → BC; (4) da v1 alla foglia al più la lunghezza è k + 1. Si prenda ora il sottoalbero T1 con radice in v1 ; esso rappresenta una derivazione di una parola z1 di lunghezza al più 2k . Sia z2 invece la parola relativa al sottoalbero T2 di T1 con radice in v2 . Possiamo scrivere z1 = vz2 x. Inoltre almeno una 75 76 8. PROPRIETÀ DEI LINGUAGGI LIBERI DAL CONTESTO tra v e x deve essere non vuota, in quanto a v1 è associata una produzione del tipo: A → BC ed il sottoalbero T2 è un sottoalbero di esattamente uno dei due sottoalberi individuati da B e C. Allora abbiamo che: G A ⇒∗ vAx e G A ⇒∗ z2 ove |vz2 x| ≤ 2k = n. Ma allora ne segue anche che G A ⇒∗ vi z2 xi per ogni i ≥ 0. Si ponga w = z2 e u e y il prefisso e il suffisso di z necessari affinchè z = uvwxy.  Esempio 8.2. Il linguaggio {ai bi ci : i ≥ 1} non è CF. Supponiamo per assurdo che lo sia. Come fatto nel caso dei linguaggi regolari (Lemma 5.1), dobbiamo mostrare la veridicità del complemento della formula dimostrata nel lemma, ovvero che per ogni n ≥ 1 sappiamo trovare una stringa z ∈ L, |z| ≥ n che soddisfa una certa condizione. Scegliamo z = an bn cn . Dobbiamo mostrare che per ogni quintupla di stringhe u, v, w, x, y tali che an bn cn = uvwxy, |vx| ≥ 1 e |vwx| ≤ n esiste un i ≥ 0 tale per cui uvi wxi y ∈ / L. Vi sono molti modi di partizionare z in uvwxy che soddisfano i requisiti. Tuttavia questi si possono raggruppare nelle seguenti casistiche: (1) uvwx = ah con h ≤ n (2) u = ah , vwx = ak bm con k > 0, k + m ≤ n (3) u = an bh , vwx = bm con m ≤ n (4) u = an bh , vwx = bk cm con k + m ≤ n (5) u = an bn ch , vwx = cm con m ≤ n In ciascuna delle casistiche, tuttavia, si ha che uv0 wx0 y = uwy ∈ / L, in quanto vengono alterati al massimo due tra i numeri di ripetizioni dei tre tipi di carattere a, b e c. Esercizio 8.3. Si dimostri che i seguenti linguaggi non sono liberi dal contesto: (1) {0a 1b 0ab : a, b ∈ N, a, b > 0} b (2) {0a 1b 0a : a, b ∈ N, a, b > 0} (3) {0a 1b 0a 1b : a, b ∈ N, a, b > 0} 2. Proprietà di chiusura Teorema 8.4. I linguaggi CF sono chiusi rispetto all’unione, alla concatenazione, ed alla chiusura di Kleene. Proof. La dimostrazione è costruttiva. Siano G1 = hV1 , T1 , P1 , S1 i e G2 = hV2 , T2 , P2 , S2 i le grammatiche generanti i linguaggi L1 e L2 . Per semplicità si assuma che V1 e V2 siano disgiunti (altrimenti si proceda a ridenominare le variabili). Sia S una nuova variabile. 3. ALGORITMI DI DECISIONE 77 Allora • G∪ = hV1 ∪ V2 ∪ {S}, T1 ∪ T2 , P1 ∪ P2 ∪ P, Si, ove P consta delle produzioni: S → S1 |S2 è la grammatica che genera L1 ∪ L2 . • G◦ = hV1 ∪ V2 ∪ {S}, T1 ∪ T2 , P1 ∪ P2 ∪ P, Si, ove P consta delle produzioni: S → S1 S2 è la grammatica che genera L1 L2 . • G∗ = hV1 ∪ {S}, T1 , P1 ∪ P, Si, ove P consta delle produzioni: S → ε|S1 S è la grammatica che genera L∗1 .  Teorema 8.5. I linguaggi CF non sono chiusi rispetto all’intersezione. Proof. Consideriamo L1 = {ai bi cj : i ≥ 1, j ≥ 1} generato da: i j j S → RC, R → ab | aRb, C → c | cC e L2 = {a b c : i ≥ 1, j ≥ 1} generato da: S → AR, R → bc | bRc, C → c | cC i i i La loro intersezione è {a b c : i ≥ 1}, mostrato essere non CF nell’esempio 8.2.  Corollario 8.6. I linguaggi CF non sono chiusi rispetto alla complementazione. Proof. Se lo fossero, allora lo sarebbero anche rispetto all’intersezione, in quanto A ∩ B = A ∪ B, contraddicendo il Teorema 8.5.  3. Algoritmi di decisione Teorema 8.7 (Vuoto, Finito, Infinito). Data una grammatica CF G = hV, T, P, Si, i problemi: (1) L(G) = ∅, (2) L(G) è finito, (3) L(G) è infinito, sono decidibili. Proof. 1) Come osservato nel paragrafo 5.1, se la grammatica ottenuta dall’applicazione dell’algoritmo presente nella dimostrazione del Lemma 6.9 è tale che S ∈ / V ′ , allora il linguaggio è ∅. Altrimenti no. 2,3) Se L(G) = ∅ oppure L(G) = {ε} (entrambe proprietà che si dimostrano durante la normalizzazione) il linguaggio è chiaramente finito. Altrimenti, G ′ = hV ′ , T, P ′ , Si una grammatica t.c. L(G ′ ) = L(G) \ ε in forma normale di Chomsky e priva di simboli inutili. Creiamo un grafo G avente: 78 8. PROPRIETÀ DEI LINGUAGGI LIBERI DAL CONTESTO • un nodo per ogni variabile A ∈ V ′ ; • un arco hA, Bi ed un arco hA, Ci per ogni produzione A → BC ∈ P ′ . Allora L(G) (= L(G ′ )) è finito se e solo se G non ha cicli. Se G non ha cicli, la finitezza è immediata: solo un numero finito di alberi di derivazione con radice etichettata da S possono essere costruiti. Viceversa, supponiamo G abbia (almeno) un ciclo passante per un nodo associato ad una variabile A. Allora riusciamo a costruire un albero con un cammino dalla radice verso le foglie (qui è importante l’ipotesi che la grammatica non abbia simboli inutili) che attraversa due nodi etichettati con A. Ripetendo la dimostrazione del pumping lemma, si riescono a generare infiniti alberi di derivazione diversi.  Teorema 8.8 (Appartenenza). Data una grammatica CF G = hV, T, P, Si, e una stringa z, il problema z ∈ L(G) è decidibile. Proof. Per il caso z = ε, dal Teorema 6.13 si ha che ε ∈ L sse esiste S → A1 · · · An (n ≥ 0) in P tale per cui Ai ∈ N per i = 1, . . . , n. Sia z 6= ε e G ′ = hV ′ , T, P ′ , Si la grammatica equivalente a G in forma normale di Greibach. Allora, se z ha una derivazione, ne ha una di esattamente |z| passi. Generiamo esaustivamente tutte le derivazioni di |z| passi e verifichiamo se esiste una di queste che deriva z.  L’algoritmo impiegato nella dimostrazione del Teorema 8.8 ha complessità esponenziale. Fortunatamente per chi necessita di riconoscere o refutare stringhe (ad esempio, per capire se un programma Pascal o C contiene degli errori sintattici) può avvalersi di metodi più efficienti. In particolare, l’algoritmo di CockeYounger-Kasami (1965–67) che è O(|z|3 ), oppure l’algoritmo di Valiant (1975) evente complessità O(|z|2.8 ). Si osservi che nel caso di linguaggi regolari invece tale problema viene deciso in tempo O(n). Infatti, dato il DFA che accetta il linguaggio, la computazione avviene in tempo proporzionale alla lunghezza della stringa. Per quanto riguarda il problema dell’equivalenza di due linguaggi liberi dal contesto, il problema è mostrato essere indecidibile in [13], Teorema 8.12. Il problema corrispondente nel caso dei linguaggi regolari è invece decidibile (si confronti il Teorema 5.13) ed è dimostrato essere NL-completo in [14]. CHAPTER 9 Le grammatiche regolari e la gerarchia di Chomsky n questo capitolo si studieranno le famiglie di grammatiche CF che generano I esattamente i linguaggi regolari. Saranno dunque collocate in un contesto più ampio: riassumeremo brevemente dei risultati (alcuni dei quali presenti in questo testo) che permettono di classificare le grammatiche in base all’espressività dei linguaggi generabili e presenteremo la classificazione insiemistica completa delle grammatiche. 1. Grammatiche Regolari Una grammatica CF si dice lineare destra se ogni produzione è della forma: A → wB w ∈ T + , oppure A → w w ∈ T+ A → Bw w ∈ T + , oppure più eventualmente S → ε. Se invece tutte le produzioni sono della forma: A → w w ∈ T+ più eventualmente S → ε, si dice lineare sinistra. Lemma 9.1. Data una grammatica lineare destra G esiste una grammatica G ′ equivalente (in forma normale di Greibach) tale che tutte le produzioni sono della forma: A → aB a ∈ T, oppure più eventualmente S → ε. A → a a∈T Proof. Ogni produzione della forma A → a1 a2 · · · an B viene sostituita dalle produzioni: A → a1 C1 , C1 → a2 C2 , . . . , Cn−1 → an B, ove C1 , . . . , Cn sono nuovi simboli non terminali. Similmente, A → a1 a2 · · · an viene sostituita dalle produzioni: A → a1 C1 , C1 → a2 C2 , . . . , Cn−1 → an .  79 80 9. LE GRAMMATICHE REGOLARI E LA GERARCHIA DI CHOMSKY Teorema 9.2. Se L è generato da una grammatica lineare destra, allora L è un linguaggio regolare. Proof. Grazie al Lemma 9.1 possiamo supporre che G = hV, T, P, Si tale che L(G) = L sia nella forma semplificata descritta nell’enunciato di tale lemma. Costruiremo un NFA M = hQ, Σ, δ, q0 , Fi che riconosce L. • Q = V ∪ {⊥}; • Σ = T; • B ∈ δ(A, a) sse A → aB ∈ P, ⊥ ∈ δ(A, a) sse A → a ∈ P; δ(⊥, a) è indefinito per ogni simbolo a ∈ Σ. • q0 = S; •   {⊥, S} se S → ε ∈ P F=  {⊥} altrimenti G Si deve mostrare che ^ δ(S, w) ∩ F 6= ∅ se e solo se S ⇒∗ w, per ogni stringa w. Iniziamo mostrando, per induzione su |w| ≥ 0, che per ogni A ∈ V (dunque A 6= ⊥) A ∈ ^δ(S, w) sse G S ⇒∗ wA Base: Sia w = ε. Per definizione, ^δ(S, ε) = {S}. Per la forma della grammatica G S ⇒∗ εA sse S = A. Passo: Sia w = va. A∈^ δ(S, va) sse A ∈ sse A ∈ G S def di ^δ nei NFA δ(B, a) ^ B∈δ(S,v) S G B : S⇒ ∗ vB δ(B, a) ip. induttiva sse S ⇒∗ vaA def di δ (B −→ aA ∈ P) Da questo risultato, per definizione di F, si ha che: G (1) S ⇒∗ ε se e solo se ^ δ(S, ε) ∩ F 6= ∅; G G (2) S ⇒∗ va se e solo se esiste A tale che S ⇒∗ vA e A → a ∈ P. Ciò accade se e solo se A ∈ ^ δ(S, v) e ⊥ ∈ δ(A, a), ovvero ⊥ ∈ ^δ(S, va).  Teorema 9.3. Se L è un linguaggio regolare, allora L è generato da una grammatica lineare destra. Proof. Sia M = hQ, Σ, δ, q0 , Fi il DFA che riconosce L. Costruiamo una grammatica lineare destra G = hV, T, P, Si tale che L(G) = L nel modo seguente: • V = Q; • T = Σ; 3. GRAMMATICHE DI TIPO 1 • • S = q0 .   A → aB ∈  A→ε ∈ P sse δ(A, a) = B P sse A ∈ F 81 G Si mostra per induzione su |w| che ^δ(q0 , w) = p se e solo se q0 ⇒|w| wp [Esercizio]. A questo punto si ha immediatamente che ^δ(q0 , w) ∈ F se e solo se G q0 ⇒|w| w. Per costruzione, la grammatica ottenuta può avere ε-produzioni e dunque non è della forma desiderata. Si applichi dunque l’eliminazione di tali ε-produzioni per ottenere la grammatica nella forma voluta.  Il Teorema 9.3 implica che ogni linguaggio regolare è anche CF. Si possono mostrare risultati analoghi ai teoremi 9.2 e 9.3 per le grammatiche lineari sinistre (si veda [13]). 2. Grammatiche di tipo 0 Le grammatiche di tipo 0 (a struttura di frase) sono le grammatiche della forma G = hV, T, P, Si in cui P contiene produzioni ∗ + ove α ∈ (V ∪ T ) , β ∈ (V ∪ T ) . α→β Teorema 9.4. L = L(G) per G grammatica a struttura di frase se e solo se L è un insieme ricorsivamente enumerabile. Proof. (Si veda [13])  3. Grammatiche di tipo 1 Le grammatiche di tipo 1 (dipendenti dal contesto) sono le grammatiche della forma G = hV, T, P, Si in cui P contiene produzioni + + α→β ove α ∈ (V ∪T ) , β ∈ (V ∪T ) , e |α| ≤ |β|, con l’eccezione dell’eventuale produzione S −→ ε (in tal caso richiediamo che S non occorra mai in β). Per queste grammatiche esiste una forma normale per le produzioni, che devono essere del tipo: α1 Aα2 → α1 βα2 con β 6= ε. Teorema 9.5. Ogni linguaggio L generato da una grammatica dipendente dal contesto è ricorsivo. Proof. (Si veda [13])  82 9. LE GRAMMATICHE REGOLARI E LA GERARCHIA DI CHOMSKY Tuttavia: Teorema 9.6. Ci sono insiemi ricorsivi che non sono generati da nessuna grammatica dipendente dal contesto. Proof. (Si veda [13])  i i i Nell’Esempio 8.2 abbiamo mostrato che {a b c : i ≥ 1} non è un linguaggio CF. Mostreremo ora che tale linguaggio è un linguaggio dipendente dal contesto. Esempio 9.7. La grammatica di tipo 1: S → aSBC | aBC CB → BC bB → bb i i i genera esattamente il linguaggio {a b c : i ≥ 1}. bC cC → bc → cc aB → ab 4. Gerarchia Ricordiamo: • dagli esempi 5.3 e 6.4 sappiamo che il linguaggio {ai bi : i ≥ 0} è un linguaggio CF ma non regolare. Sappiamo inoltre che le grammatiche (CF) lineari destre e sinistre generano esattamente i linguaggi regolari. • Dagli esempi 8.2 e 9.7 sappiamo che il linguaggio {ai bi ci : i ≥ 1} è un linguaggio dipendente dal contesto ma non CF. • Dai Teoremi 9.6 e 9.4 sappiamo che la classe di linguaggi generati da grammatiche dipendenti dal contesto è inclusa strettamente nella classe di linguaggi generati da grammatiche a struttura di frase. Pertanto viene indotta una gerarchia di grammatiche, nota come gerarchia di Chomsky, fatta di inclusioni proprie, che si può trovare in Figura 1. 4. GERARCHIA ✬ ✬ 83 ✩ Struttura di frase ✬ Dipendenti dal contesto ✬✬ ✩ ✩ ✩✩ Libere dal contesto Lineari destre Lineari sinistre ✫✫ ✫ ✫ ✫ ✪✪ ✪ ✪ ✪ Figure 1. La gerarchia di Chomsky Part 2 Teoria della calcolabilità CHAPTER 10 Nozione intuitiva di algoritmo In questa parte del testo presenteremo alcuni sistemi formali introdotti per studiare in modo rigoroso i concetti di algoritmo, programma e funzione calcolabile. In generale, un sistema formale S è un sistema di formule e regole per combinare formule, descritto mediante un insieme finito o numerabile S di simboli, e tale che per ogni regola R ⊆ Sk per un qualche k ∈ N, sia possibile stabilire in modo finito (decidibile) se una formula f è conseguenza di f1 , . . . , fk−1 secondo R, ovvero se hf1 , . . . , fk−1 , fi ∈ R. Il senso dei sistemi formali è quello di definire in modo rigoroso, mediante simboli e regole, l’impalcatura entro cui derivare asserzioni su un dato modello della realtà. Il calcolo proposizionale e la logica del prim’ordine sono esempi noti di sistemi formali. Studieremo dunque alcuni sistemi formali per descrivere in modo rigoroso la nozione intuitiva di calcolabilità. Ciò permetterà di stabilire al tempo stesso i limiti intrinseci dell’informatica moderna e studiare alcuni formalismi per rappresentare il “calcolo”, apparentemente lontani tra loro, ma equivalenti nel potere espressivo riguardante ciò che è calcolabile. La corrente Parte 2 del testo, che riguarda questi argomenti, è strutturata nel modo seguente: nel par. 1 del presente capitolo sarà introdotta la nozione informale ed intuitiva di algoritmo, o di funzione calcolabile. In seguito, nel Capitolo 11 sarà presentata la nozione di Macchina di Turing (MdT in breve), come primo sistema formale per definire il concetto di funzione calcolabile. L’importanza della MdT è, oltre che culturale, essendo una delle prime formalizzazioni del concetto di calcolabilità, anche tecnica: ad esempio, mediante MdT sono attualmente formalizzate le nozioni più importanti nella teoria moderna della complessità degli algoritmi che studieremo nella parte 4 del testo. Nel Capitolo 12 introdurremo le funzioni parziali ricorsive, come modello matematico per le funzioni calcolabili. L’equivalenza tra questi due modelli di calcolo ci porterà, nel Capitolo 13, all’analisi della Tesi di Church-Turing e successivamente, nei Capitoli 14 e 15 ad enunciare alcuni risultati generali sulla calcolabilità ed alcuni problemi algoritmicamente insolubili. 1. Requisiti di un algoritmo Discuteremo ora le caratteristiche che deve avere un algoritmo partendo da quella che è l’esperienza e l’idea intuitiva che tutti ne abbiamo. Un algoritmo viene descritto in un certo linguaggio, che può anche essere semplicemente l’italiano, 87 88 10. NOZIONE INTUITIVA DI ALGORITMO cosı̀ come usando i linguaggi nati appositamente per descrivere algoritmi quali i linguaggi di programmazione e i sistemi formali che vedremo nei prossimi capitoli. Facciamo qui riferimento all’esperienza personale e immaginiamo di guardare un algoritmo. Possiamo concordare che, indipendentemente dal problema che intende risolvere, ha delle caratteristiche comuni. a: Un algoritmo è di lunghezza finita. b: Esiste un agente di calcolo che porta avanti il calcolo eseguendo le istruzioni dell’algoritmo. c: L’agente di calcolo ha a disposizione una memoria dove vengono immagazzinati i risultati intermedi del calcolo. d: Il calcolo avviene per passi discreti. e: Il calcolo non è probabilistico. I punti a–c hanno una ovvia interpretazione. Il punto d afferma che il calcolo non avviene mediante dispositivi analogici. Il punto e afferma che il calcolo non obbedisce a nessuna legge di probabilità. Associato al punto a parleremo di espressioni simboliche ovvero espressioni in un linguaggio di simboli che costituisce il linguaggio per decrivere un algoritmo. Altre caratteristiche degli algoritmi sono: f: Non deve esserci alcun limite finito alla lunghezza dei dati di ingresso. g: Non deve esserci alcun limite alla quantità di memoria disponibile. Mentre il punto f è ragionevole: ad esempio un algoritmo di somma deve poter funzionare per ogni possibile addendo, ovvero numero naturale, per il punto g è necessario un chiarimento. Per evidenziare la necessità di assumere una memoria illimitata facciano osservare che, limitandola, alcuni algoritmi noti per calcolare semplici funzioni non potrebbero funzionare. Ad esempio la funzione λx. x2 non sarebbe calcolabile poiché lo spazio di memoria necessario per calcolare il quadrato di x dipende da x, e per il punto f, esso deve essere illimitato. Le seguenti osservazioni sono essenziali per comprendere la natura del calcolo e la sua complessità. h: Deve esserci un limite finito alla complessità delle istruzioni eseguibili dal dispositivo. i: Sono ammesse esecuzioni con un numero di passi finito ma illimitato. Il punto h, in relazione al punto a, stabilisce la intrinseca finitezza del dispositivo di calcolo. Ad esempio, pensando ad un calcolatore, esso sarà in grado di calcolare solo indirizzando direttamente una parte finita della sua memoria (registri o memoria RAM) stabilendo quindi un limite nella complessità delle istruzioni eseguibili. Questo è un punto chiave nell’analisi che vedremo della nozione di effettiva calcolabilità. Un dispositivo di calcolo può effettivamente tenere traccia solo di un numero finito di simboli, ovvero esso può reagire (eseguendo un istruzione) tenendo conto di un numero finito di simboli ricordati. Questo venne intuitivamente giustificato da Turing che per primo analizzò formalmente il concetto di effettiva calcolabilità, con l’intrinseca limitazione della memoria umana. Tuttavia, 2. FUNZIONI CALCOLABILI 89 non c’è limite alla memoria ausiliaria, come visto nel punto g. Per quanto riguarda il punto i, osserviamo che non essendo limitabile il numero di passi richiesti per eseguire un generico algoritmo (si pensi alla moltiplicazione o alla funzione λx. x2 discussa nel punto g), non è possibile stabilire a priori un limite massimo sul numero di passi nell’esecuzione di un algoritmo. Uno studio approfondito su come legare il numero di passi alla lunghezza dei dati è argomento trattato nella teoria della complessità degli algoritmi (si veda la Parte 4 del testo). 2. Funzioni calcolabili Secondo quanto appena illustrato, un algoritmo definisce una funzione, ovvero una associazione {input} ❀ {output}. Questa può essere rappresentata matematicamente come una funzione sui dati manipolati dall’algoritmo. Possiamo pertanto asserire che un algoritmo “calcola” funzioni. Diremo quindi che una funzione f è calcolabile se esiste un algoritmo ed un agente di calcolo tale che ad ogni input x restituisce come risultato del calcolo f(x). Tuttavia, una definizione intuitiva di questo tipo riguardante ciò che e’ intuitivamente calcolabile non è soddisfacente. Supponiamo di trattare solo funzioni sui numeri naturali.1 Supponiamo di poter descrivere in questo modo tutti i processi di calcolo sui numeri naturali, ovvero che una funzione f : N −→ N sia calcolabile se esistono un algoritmo ed un agente di calcolo come descritti nei punti a–i sopra, tale che per ogni input x ∈ N, l’algoritmo resituisce f(x) ∈ N. Ovvero, secondo queste ipotesi, una funzione è calcolabile se esiste un algoritmo ed un agente di calcolo che terminano restituendo comunque un risultato, per ogni input. Pur non assumendo limiti nel tempo e spazio impiegati dall’agente di calcolo per eseguire i suoi conti, le precedenti ipotesi stabiliscono che ciò che è calcolabile lo sia comunque, per ogni input, a seguito di un insieme potenzialmente illimitato (ma finito) di passi elementari di calcolo eseguiti dall’agente. Ovvero le funzioni considerate come “calcolabili” secondo le ipotesi a–i sono tutte totali, ovvero sempre definite per ogni input. Essendo gli algoritmi di lunghezza finita ed essendo possibile descrivere l’agente di calcolo mediante un insieme finito di simboli e regole che ne disciplinano il calcolo, è possibile enumerare gli algoritmi e gli agenti di calcolo. Sia dunque Px l’xesimo programma-agente che calcola una data funzione gx . Per le ipotesi a–i la funzione gx è totale, ovvero sempre definita. Definiamo la funzione h(x) = gx (x) + 1. È chiaro che la funzione h è calcolabile: tutti noi conosciamo un algoritmo per incrementare un naturale, ed inoltre per ipotesi gx è calcolabile, essendo calcolata dall’algoritmo-agente Px . Quindi, essendo h calcolabile, deve esistere un algoritmoagente che la calcola e che avrà un dato indice nella numerazione. Sia questo indice x0 , ovvero h sia calcolata dal programma-agente Px0 (h = gx0 ). Si ottiene in 1 Questa non è una limitazione, poiché, come vedremo in seguito, ogni struttura dati, anche la più complessa, può essere messa in corrispondenza biunivoca con un sottoinsieme dei numeri naturali, modulo una opportuna codifica dei dati in numeri. 90 10. NOZIONE INTUITIVA DI ALGORITMO questo modo una evidente contraddizione: gx0 = h(x0 ) = gx0 + 1 Non è possibile che una funzione sempre definita sui naturali sia uguale al successore di se stessa. La funzione h è quindi non calcolabile nel nostro formalismo scelto, che pertanto, essendo h intuitivamente calcolabile, non esprime tutto ciò che è intuitivamente calcolabile. 3. Algoritmi e Programmi Con le suddette ipotesi si è dimostrato che non si è mai in grado di descrivere tutto ciò che è “intuitivamente calcolabile”. Ovvero, sarà sempre possibile costruire funzioni, intuitivamente calcolabili e totali, non esprimibili nel sistema formale adottato. Questo chiaramente rappresenta un limite insormontabile alla nostra teoria, rendendola inadeguata a trattare la nozione di calcolabilità. Abbiamo bisogno di estendere i precedenti punti a–i con una nuova ipotesi fondamentale: si assumerà che un processo di calcolo possa non terminare. Questo corrisponde all’intuizione che, un programma chiaramente non terminante del tipo: read(x); while true do · · · endw corrisponde ad una funzione non definita su tutti gli argomenti, per la quale sappiamo pensare ad un programma (quello sopra) ed un agente di calcolo che la calcola (un computer). Si noti ora la necessità di introdurre degli oggetti sintatticamente del tutto simili agli algoritmi, ma senza la proprietà di sicura terminazione. Sono questi i programmi. È quindi necessario, per superare la precedente limitazione dei punti a–i, assumere che, oltre ai punti a–i vale la seguente ipotesi: l: Sono ammesse esecuzioni con un numero di passi infinito (cioè non terminanti). Il punto l rappresenta una necessità intrinseca, e come abbiamo visto irrinunciabile, nella trattazione matematica di ciò che è effettivamente calcolabile. Secondo questa ipotesi, la funzione h descritta in precedenza può risultare indefinita su alcuni argomenti (ad esempio x0 ), ovvero indicando con ↑ il simbolo di indefinito, la precedente uguaglianza può risultare valida (non contraddittoria): ↑ = gx0 = h(x0 ) = gx0 + 1 = ↑ . CHAPTER 11 Macchine di Turing In questo capitolo introduciamo le Macchine di Turing (MdT), ideate da Alan Turing negli anni ’30 per rappresentare in modo formale una macchina in grado di eseguire algoritmi costituiti da passi elementari e discreti di calcolo, secondo la definizione informale data precedentemente. Turing, nella sua analisi del concetto di calcolabilità, introduce per primo la parola “computer”, con il significato di una persona che fa un calcolo composto da passi elementari e discreti utilizzando un foglio di carta (memoria). Questa nozione di computer include tutte le caratteristiche di base dei calcolatori moderni (memoria, input/output, stato) e, come vedremo, modella in modo soddisfacente il concetto di effettiva calcolabilità. 1. Descrizione modellistica e matematica Nell’eseguire un calcolo (manuale), dato un input, calcoliamo un risultato con l’ausilio della scrittura su carta. L’idea di Turing è quella di pensare ad una semplice macchina in grado di scrivere simboli su un nastro potenzialmente infinito, rappresentando il fatto (anche espresso nel punto g) che nei calcoli abbiamo solitamente a disposizione una quantità illimitata di carta dove registrare i risultati parziali del calcolo. L’organizzazione del nastro è fatta mediante celle che rappresentano la quantità di memoria unitaria. La macchina cosı̀ pensata utilizzerà un alfabeto finito, col quale possiamo rappresentare una infinità di dati. Il corpo pensante della macchina (detto controllo) sarà realizzato mediante un automa a stati finiti deterministico (DFA). Questa restrizione modella perfettamente il punto h precedente, dove è stata assunta una complessità limitata nell’esecuzione delle istruzioni da parte della macchina. Tutto questo viene rappresentato matematicamente con una macchina detta Macchina di Turing (MdT in breve). Una MdT è un dispositivo di calcolo rappresentabile con un nastro di lunghezza infinita nel quale vengono immagazzinati i dati o sequenze di simboli del calcolo. Uno di questi simboli è $ rappresentante l’assenza di simboli (spazio bianco). Il controllo della MdT ha accesso al nastro attraverso una testina di lettura e scrittura che permette di leggere o scrivere un simbolo alla volta. Una MdT è pertanto costituita da due parti: il programma finito secondo cui verrà eseguito il calcolo e gli organi meccanici per lo scorrimento del nastro e il comando della testina. La struttura a stati finiti della parte di controllo modella una macchina con una memoria che potremmo dire a breve termine, finita, mentre il nastro modella una 91 92 11. MACCHINE DI TURING memoria a lungo termine, potenzialmente infinita. Questa distinzione tra memoria a breve e lungo termine è riscontrabile anche nelle architetture dei moderni calcolatori. ··· ℓ3 ℓ2 ℓ1 s r1 r2 r3 ··· ⇑ q Figure 1. Il modello meccanico della Macchina di Turing Ad ogni istante nel calcolo, il simbolo presente nella casella esaminata dalla testina rappresenta l’input alla macchina (in Figura 1, il simbolo s). In risposta la macchina può decidere di modificare il simbolo e/o spostare il nastro a sinistra o a destra della casella esaminata. Questo permette di avere in input un simbolo diverso per eseguire il passo successivo della computazione (il simbolo ℓ1 se lo spostamento sarà a sinistra, r1 se a destra). Inoltre la testina (che si trova nello stato q—memoria finita) può cambiare il suo stato scegliendolo da un insieme finito di stati possibili. Il comportamento di una MdT è descrivibile mediante una tabella detta matrice funzionale della macchina in cui le righe rappresentano gli stati del controllo mentre le colonne rappresentano i simboli di ingresso. In una generica cella è descritta l’azione eseguita dalla macchina nello stato corrispondente in riga e leggendo dal nastro il simbolo corrispondente in colonna. sj qi qr sk L L’azione compiuta, essendo il controllo nello stato qi e leggendo il simbolo sj , è in questo caso: • portarsi in uno stato (interno) qr • scrivere il simbolo sk • spostare il nastro a destra R o a sinistra L. Nel caso sj = sk , la macchina lascia inalterato il simbolo sul nastro. Se la casella corrispondente alla coppia hsj , qi i è vuota, la macchina si ferma. Una computazione di una MdT sarà dunque una sequenza di passi compiuti dalla macchina. Più formalmente, definiamo una MdT come segue. 1. DESCRIZIONE MODELLISTICA E MATEMATICA 93 Definizione 11.1. Una Macchina di Turing M consiste di: (1) un alfabeto finito Σ = {s0 , . . . , sn } con almeno due simboli distinti s0 = $ (blank) e s1 = 0 (tally); (2) un insieme finito di stati Q = {q0 , . . . , qm } tra i quali vi è lo stato iniziale q0 (dunque Q è non vuoto); (3) un insieme finito non vuoto di istruzioni (o quintuple) P = {I1 , . . . , Ip } ognuna delle quali di uno dei seguenti 2 tipi base: • q s q′ s′ R • q s q′ s′ L tale che non esistono due istruzioni che iniziano con la medesima coppia q, s. Si può dunque immaginare P come la descrizione “estensionale” (ovvero per casi) di una funzione (in generale) parziale δ : Q × Σ −→ Q × Σ × {R, L}. Tale funzione δ è detta funzione di transizione. Si osservi la caratteristica locale delle istruzioni: il loro significato è univocamente determinato dalla parte di nastro immediatamente adiacente alla testina e dallo stato in cui la macchina si trova. Lo stato in cui si trova l’intera MdT in un certo istante del calcolo è dunque esprimibile mediante la nozione di descrizione istantanea di una MdT, che rappresenta lo stato interno del controllo, il posizionamento della testina e il nastro. Definizione 11.2. Una descrizione istantanea (ID) della macchina è una quadrupla hq, v, s, wi ⋆ dove v, w ∈ Σ rappresentano i caratteri significativi (cioè escludendo la sequenza illimitata di $ sempre presenti a destra e a sinistra) presenti sul nastro a sinistra ed a destra della testina, s è il simbolo letto dalla testina, essendo la macchina nello stato q. Ad esempio, se la situazione è . . . $ $ $ s1 . . . si−1 si si+1 . . . sj $ $ $ . . . ⇑ q allora v = s1 . . . si−1 , s = si e w = si+1 . . . sj . Vediamo ora come definire formalmente il generico passo di calcolo di una MdT. Definiamo una relazione tra descrizioni istantanee, ovvero una relazione ⊢ ⊆ ID × ID, che rappresenti l’esecuzione di un singolo passo di calcolo in una data MdT. Definizione 11.3. Il successore ⊢ è una “mappa” tra descrizioni istantanee definita come: • hq, v, r, s wi ⊢ hq ′ , v r ′ , s, wi se q r q ′ r ′ R è una istruzione di P; 94 11. MACCHINE DI TURING • hq, v s, r, wi ⊢ hq ′ , v, s, r ′ wi se q r q ′ r ′ L è una istruzione di P. Una computazione sarà dunque definita come segue, ovvero come una sequenza finita di passi. Definizione 11.4. Una computazione è una sequenza finita di ID α0 , . . . , αn tale che • α0 = v s q0 w e, • αi ⊢ αi+1 per ogni i ∈ {0, . . . , n − 1}. Una computazione è detta terminante se esiste un n ≥ 0 per cui αn = v s q w e non vi è nessuna istruzione di P iniziante con q ed s (cioè δ(q, s) non è definita), i.e. αn 6⊢. In accordo con il punto l della precedente sezione, esistono MdT che non terminano, ovvero che non calcolano nulla a partire da un dato input. Osserviamo come una MdT cosı̀ descritta soddisfa i requisiti per la definizione ragionevole di algoritmo. a: Le istruzioni della MdT sono finite. b: La MdT è l’agente di calcolo che esegue l’algoritmo. c: Il nastro rappresenta la memoria della macchina. d: La MdT opera in modo discreto. e: Ad ogni ID corrisponde una sola azione (determinismo della MdT). f: Non esiste alcun limite all’input, essendo il nastro illimitato. g: La capacità della memoria (nastro) è illimitata. h: Le operazioni eseguibili sono semplici e quindi di complessità limitata. i: Non esiste alcun limite al numero di istruzioni eseguite in quanto la medesima quintupla può essere usata più volte. l: Possono esistere MdT che non calcolano nulla generando una sequenza infinita di ID. Teorema 11.5 (Determinismo). Se α ⊢ β e α ⊢ γ con α, β, γ ∈ ID, allora β ≡ γ. Proof. Ovvio, poiché per definizione non esistono due quintuple q s q ′ s ′ X aventi la medesima coppia q, s.  Esempio 11.6. Incremento di uno in binario. Sia Σ = {$, 0, 1}. Si vuole passare da una configurazione iniziale ad una finale . . . $ $ $ sn . . . s0 $ $ $ $ . . . ⇑ q0 ′ . . . $ $ $ $ sm . . . s0′ $ $ $ . . . ⇑ q2 1. DESCRIZIONE MODELLISTICA E MATEMATICA 95 ove m X i=0 n X 2i · si′ = 1 + i=0 2i · si . Una possibile definizione della funzione δ sarà la seguente: $ δ 0 q0 q1 $ L q1 q2 1 L 1 q2 1 L q1 0 L q2 q2 0 L q2 1 L Esempio 11.7. Copia di una stringa unaria. Sia Σ = {$, 0}. Si vuole passare da una configurazione iniziale . . . $ $ $ 0| .{z . . 0} $ $ $ $ . . . n ⇑ ad una finale q0 . . . $ $ $ 0| .{z . . 0} $ ⇑ n qf 0| .{z . . 0} $ $ $ . . . n Una possibile definizione della funzione δ sarà la seguente: δ q0 $ 0 q1 $ L q1 q2 $ R q2 q3 $ R q2 0 R q3 q4 0 L q3 0 R q4 q5 $ L q4 0 L q5 q6 0 L q5 0 L q6 q7 $ R q2 $ R q7 q7 0 R Si provi, per esercizio, a definire δ supponendo di disporre di Σ = {$, 0, 1}. Esempio 11.8. Definiamo una MdT che calcola il successore in base 10 di un numero. S = {0, . . . , 9}, Q = {q0 , q1 } essendo q1 lo stato di terminazione. La 96 11. MACCHINE DI TURING δ q0 q1 0 1 2 ··· 7 8 9 $ q1 1 R q1 2 R q1 3 R ··· q1 8 R q1 9 R q0 0 L q1 1 R ··· Figure 2. MdT per l’incremento in base 10 matrice funzionale in figura 2 definisce la MdT desiderata che calcola il successore di un numero scritto sul nastro supponendo che la MdT inizi il calcolo essendo la testina posizionata sulla cifra meno significativa del numero da incrementare. Esercizio 11.9. Si scriva la funzione di transizione per le macchine di Turing soddisfacenti i seguenti requisiti: (1) si calcoli il successore di un numero decimale (2) decisione di quale sia la più lunga tra due stringhe di 0 poste una a sinistra ed una a destra della testina. Si scelga una opportuna forma di risposta (che tenga conto anche della possibilità che le due stringhe siano uguali); (3) si effettui la somma (in binario) di due stringhe non nulle di 0 e 1 poste entrambe a sinistra della testina ed inframmezzate da un $; (4) si sposti la testina di n posizioni a destra rispetto a quella di partenza, ove n è letto da un input binario; (5) si calcoli il numero di occorrenze di 1 in una data sequenza binaria; (6) si ordini, con ripetizioni, una stringa in input di 0 e 1. 2. Funzioni calcolabili da MdT Ad ogni MdT può essere facilmente associata una funzione che diremo calcolata dalla MdT. Poiché in questa parte del corso trattiamo solo funzioni sui naturali, è necessario fissare una opportuna codifica dei naturali come stringhe nell’alfabeto della MdT. Ad esempio è possibile utilizzare una codifica binaria nel caso Σ = {$, 0, 1} o una codifica decimale. Nella seguente definizione, assumeremo invece una codifica unaria, ovvero un generico numero n ∈ N sarà rappresentato come una sequenza di n + 1-simboli 1 consecutivi. Questa codifica è particolarmente conveniente, anche se del tutto inessenziale, per la seguente definizione di funzione calcolabile da una MdT. Definizione 11.10. Una funzione f : Nn −→ N è Turing–calcolabile se esiste una MdT tale che, partendo dalla configurazione iniziale . . . $ $ $ x1 $ . . . $ xn $ $ $ $ . . . ⇑ q0 2. FUNZIONI CALCOLABILI DA MDT 97 termina nella configurazione . . . $ $ $ . . . $ f(x1 , . . . , xn ) $ $ $ $ . . . ⇑ qf se f(x1 , . . . , xn ) è definita, non termina altrimenti; dove per ogni (x1 , . . . , xn ) ∈ Nn , x1 , . . . , xn , f(x1 , . . . , xn ) sono le rappresentazioni in unario rispettivamente di x1 , . . . , xn , f(x1 , . . . , xn ) (mentre qf ∈ Q è tale per cui δ(qF , $) è indefinito). Chiaramente, una funzione sui naturali Turing-calcolabile è parziale, poiché può non essere definita su alcuni (o tutti) gli argomenti. Vedremo in seguito il significato più profondo di funzione parziale nella teoria della calcolabilità. Esempio 11.11. Le seguenti funzioni sono Turing-calcolabili: • λ x.x: Q = {q0 }; δ sempre indefinito. • λ x.0 : δ $ q0 q1 $ R 0 q1 • λ x.x + 1 : δ $ q0 q1 0 R 0 q1 • λ x1 . . . xn .xi : for j = 0, . . . , i − 1 δ(qj , 0) = (qj , 0, L); δ(qj , $) = (qj+1 , $, L). Esercizio 11.12. Si supponga di disporre delle definizioni per le macchine di Turing computanti le funzioni: f : Nk −→ N, g1 , . . . , gk : Nn −→ N. Come esercizio si mostri che la funzione h : Nn −→ N, definita come def h(x1 , . . . , xn ) = f(g1 (x1 , . . . , xn ), . . . , gk (x1 , . . . , xn )), è Turing calcolabile. 98 11. MACCHINE DI TURING Nota 11.13. Si osservi che, se esiste una MdT computante la funzione f, allora ne esistono infinite calcolanti la stessa funzione (si aggiungano arbitrariamente stati o simboli ‘inutili’). 3. MdT generalizzate Il modello di MdT che abbiamo visto è solo uno dei possibili modelli matematici per definire l’effettiva calcolabilità di funzioni. È infatti possibile definire una vasta gamma di MdT differenti ma equivalenti rispetto alla classe di funzioni Turing-calcolabili che esse possono indurre. Questo perchè qualunque MdT ottenuta anche con modifiche strutturali (per esempio aumentando il numero dei nastri o delle testine) se soddisfa le 10 condizioni che abbiamo definito per caratterizzare la nozione intuitiva di algoritmo, può essere simulata da una MdT definita come nella Definizione 11.1. I seguenti risultati, dei quali non riportiamo dimostrazione, forniscono un esempio di come si possa modificare il modello di MdT senza modificare l’insieme delle corrispondenti funzioni calcolabili. La potenza del formalismo delle MdT non viene dunque ridotta/aumentata modificando l’insieme dei simboli/stati o modificando la struttura stessa della macchina (numero di nastri e/o testine). • Una MdT con n nastri ed m testine (m ≥ n) può essere simulata da una MdT con 1 nastro ed una testina; • Una MdT con n simboli ed m stati può essere simulata da una MdT con 2 stati, aumentando opportunamente il numero dei suoi simboli; • Una MdT con n simboli ed m stati può essere simulata da una MdT con 2 simboli, aumentando opportunamente il numero dei suoi stati; • Ogni MdT può essere simulata da una MdT che può solo scrivere e non rimpiazzare simboli sul nastro. Per approfondimenti su queste tematiche, si consulti, ad esempio [13]. CHAPTER 12 Funzioni parziali ricorsive di Kleene & Robinson Lo scopo primario di questa parte del corso è di studiare le funzioni sui numeri naturali, esprimibili in modo effettivo o algoritmico. La definizione intuitiva del concetto di funzione effettivamente calcolabile o funzione calcolabile mediante un algoritmo, ovvero mediante una sequenza discreta di passi elementari di calcolo di una MdT, fornita nel capitolo precedente, può sembrare limitativa rispetto a ciò che intuitivamente ci sembra effettivamente calcolabile. Per valutare la robustezza di questa formalizzazione del concetto di funzione calcolabile, nei seguenti due paragrafi studieremo diverse nozioni di calcolabilità che, come vedremo, si equivalgono e sono a loro volta tutte equivalenti alla calcolabilità mediante MdT. Nel Paragrafo 1 studieremo la caratterizzazione di effettiva calcolabilità sui naturali nota con il termine di funzione ricorsiva. Arriveremo alla trattazione delle funzioni parziali ricorsive, dette anche funzioni ricorsive generali, di Kleene & Robinson, arricchendo via via con nuovi costrutti, o metodi di composizione, alcune funzioni elementari sui naturali, dette funzioni ricorsive di base.1 Questi costrutti, come vedremo nel Cap. 16, corrispondono a costrutti noti ed ampiamente utilizzati nei moderni linguaggi di programmazione. 1. Funzioni primitive ricorsive L’insieme dei numeri naturali è facilmente caratterizzabile mediante induzione, ovvero affermando che ogni numero naturale n è esprimibile come una iterazione della operazione elementare di successore applicata al valore costante 0: n = Sn (0). Pertanto N = {0, S(0), S(S(0)), S(S(S(0))), . . .} Nel seguito indicheremo spesso S(x) con x + 1. Questo procedimento induttivo per definire i numeri naturali si basa sull’idea che ogni numero naturale è definibile a partire da un numero più semplice (in questo caso minore nell’ordinamento usuale sui naturali) applicando l’operazione di successore. Questo modo di procedere è comune in matematica e informatica (si veda il paragrafo 2.6), e trova svariate applicazione nelle cosı̀ dette definizioni ricorsive di funzioni. Una definizione ricorsiva di funzione è una definizione dove il valore della funzione per un dato argomento è direttamente correlato al valore della medesima funzione su argomenti 1 Nel seguito per funzione ricorsiva intenderemo una funzione ricorsiva totale. Nel caso di funzioni parziali, parleremo di funzioni parziali ricorsive. 99 100 12. FUNZIONI PARZIALI RICORSIVE DI KLEENE & ROBINSON “più semplici” o al valore di funzioni “più semplici”. Ad esempio, la seguente funzione definisce ricorsivamente la successione di Fibonacci: 1, 1, 2, 3, 5, 8, 13, ... f(0) = 1 f(1) = f(x + 2) = 1 f(x + 1) + f(x). Al fine di comprendere meglio queste definizioni è pertanto dare un significato preciso al concetto informale di “più semplice”. Identifichiamo le funzioni “più semplici” in assoluto con le funzioni costanti. Trattando i naturali, consideriamo dunque la costante 0 come funzione costante elementare. Supponiamo inoltre di avere a disposizione come funzione elementare l’operazione di successore, che ci permette di definire qualsiasi numero naturale, e la funzione identica. Queste assunzioni ci portano a definire il concetto di funzione ricorsiva di base. Definizione 12.1. Si dicono funzioni ricorsive di base le seguenti funzioni: • la funzione costante 0: λx. 0; • La funzione successore S: λx. x + 1; • La funzione identità, o i-esima proiezione, πi : λx1 · · · xn . xi con 1 ≤ i ≤ n. Formalizziamo ora il meccanismo di definizione di funzioni per composizione e ricorsione primitiva. Questo meccanismo generalizza i precedenti esempi. Definizione 12.2. Una funzione f : Nn −→ N si dice: • definita per composizione da g1 , . . . , gk : Nn −→ N e h : Nk −→ N se f(x1 , . . . , xn )=h(g1 (x1 , . . . , xn ), . . . , gk (x1 , . . . , xn )) •  definita per ricorsione primitiva da g : Nn−1 −→ N e h : Nn+1 −→ N se  f(x , . . . , x 1 n−1 , 0) = g(x1 , . . . , xn−1 )  f(x , . . . , x , y + 1) = h(x , . . . , x , y, f(x , . . . , x , y)) 1 n−1 1 n−1 1 n−1 Tutte e sole le funzioni definibili a partire dalle precedenti funzioni ricorsive di base mediante composizione e ricorsione primitiva definiscono l’insieme delle funzioni primitive ricorsive. L’idea è quella di costruire via via funzioni effettivamente calcolabili a partire dalle funzioni (banalmente) effettivamente calcolabili di base, ovvero 0 e S, costruendo da queste, per composizione e ricorsione primitiva, funzioni via via più complesse Definizione 12.3. La classe P delle funzioni primitive ricorsive è la più piccola classe (ovvero l’intersezione di tutte le classi) di funzioni contenenti le funzioni ricorsive di base e chiuse per composizione e ricorsione primitiva. Nota 12.4. Segue direttamente dalla definizione che, per ogni funzione f, f ∈ P sse esiste una sequenza finita di funzioni f1 , . . . , fn , tale che: fn = f e per ogni funzione fj , con j ≤ n, o fj è una funzione ricorsiva di base, oppure è ottenuta 1. FUNZIONI PRIMITIVE RICORSIVE 101 mediante composizione o ricorsione primitiva a partire da funzioni fi1 , . . . , fik con i1 , . . . , ik < j e per le quali vale la stessa proprietà. Queste non sono altro che le condizioni per fissare un sistema formale. Esempio 12.5. Consideriamo la sequenza di funzioni ottenute come visto in precedenza: f5 f1 = λx. x f2 = λx. x + 1 f3 = λx1 x2 x3 . x2 f4 = f2 ◦f3 = λx1 x2 x3 . x2 + 1 = Tale che: f5 (0, x2 ) = f5 (y + 1, x2 ) = f6 f1 (x2 ) f4 (y, f5 (y, x2 ), x2 ) = f5 (f1 , f1 ) È facile vedere che f6 = λx. 2x e che f5 = λxy. x + y. Dimostrare per esercizio. Le funzioni primitive ricorsive sono molto frequenti in matematica ed informatica. Il seguente esempio elenca alcune tra le più note funzioni primitive ricorsive. Esempio 12.6. Le seguenti funzioni sono ricorsive primitive (per semplicità si denoteranno numerali con numeri): Zero: 0n = λx1 · · · xn . 0: definibile come 0(π1 (x1 , . . . , xn )); Costante: cn = λx1 · · · xn .c: definibile come S(· · · S(0n (x1 , . . . , xn ) ) · · · ) ; | {z } | {z } c Somma:   +(x, 0) = x  +(x, y + 1) = S(+(x, y)) c Rispetto alla notazione utilizzata: • h è +, di arità 2, • g è la funzione identica, di arità 1, • f(x, y, z), di arità 3, è la funzione ottenuta come S(π3 (x, y, z)). Un esempio di ‘computazione’ nel formalismo che si sta definendo è il seguente: +(S(S(0)), S(S(S(0)))) = S(+(S(S(0)), S(S(0)))) = S(S(+(S(S(0)), S(0))) = S(S(S(+(S(S(0)), 0))))) = S(S(S(S(S(0)))))) 102 12. FUNZIONI PARZIALI RICORSIVE DI KLEENE & ROBINSON Moltiplicazione:   ·(x, 0) = 0  ·(x, y + 1) = +(·(x, y), y)) Potenza:   x0 = S(0)  xy+1 = ·(xy , x)) Iper-potenza:   iper(x, 0) = π (x) 1  iper(x, y + 1) = iper(x, y)x ... x y Si osservi che iper(x, y) = x x|{z} = x(x ) y Predecessore:   pred(0) = 0  pred(y + 1) = π (y) 1 Differenza:   −(x, 0) = π (x) 1  −(x, y + 1) = pred(−(x, y)) Fattoriale:   0! = S(0)  (y + 1)! = ·(S(y), y!) Segno:   sg(0) = 0  sg(y + 1) = S(0) Definiamo inoltre sg(x) = −(1, sg(x)). Valore assoluto della differenza: | − (x, y)| = +(−(x, y), −(y, x)); Minimo e massimo:    min(x, y) = −(x, −(x, y))   max(x, y) = +(x, −(y, x))  Divisione intera: Assumiamo che div(x, 0) = mod(x, 0) = 0.   mod(0, x) = 0  mod(y + 1, x) = S(mod(y, x)) · sg(| − (x, S(mod(y, x)))|)   div(0, x) = 0  div(y + 1, x) = div(y, x) + sg(| − (x, S(mod(y, x)))|) 1. FUNZIONI PRIMITIVE RICORSIVE 103 Produttoria: Sia f una funzione ricorsiva primitiva binaria, allora definiamo la funzione y−1 Π z<y f(x, z) = Πz=0 f(x, z):  Π f(x, z) = S(0) z<0  Π z<y+1 f(x, z) = ·(f(x, y), Πz<y f(x, z)) Sommatoria: Sia f una funzione ricorsiva primitiva binaria, allora definiamo la funzione y−1 Σ z<y f(x, z) = Σz=0 f(x, z):  Σ f(x, z) = 0 z<0  Σ z<y+1 f(x, z) = +(f(x, z), Σz<y f(x, z)) µ-operatore limitato di minimizzazione: Sia f una funzione primitiva ricorsiva n + 1 aria. g(x1 , . . . , xn , y) = = µz < y. (f(x1 , . . . , xn , z) = 0)    il più piccolo z minore di y   tale che f(x1 , . . . , xn , z) = 0 se tale z esiste     y altrimenti Tale funzione è primitiva ricorsiva: g(x1 , . . . , xn , y) = Σv<y (Πu≤v sg(f(x1 , . . . , xn , u))). Esercizio 12.7. Si definiscano le seguenti funzioni ricorsive primitive: (1) la funzione che, dato x, fornisce il numero dei suoi divisori; (2) la funzione che, dato x, restituisce 1 se x è primo, 0 altrimenti; (3) la funzione p che, dato x, restituisce il p-esimo numero primo (si assuma p(0) = 0). (4) la funzione che restituisce l’esponente di p(y) nella fattorizzazione di x; (5) la funzione che restituisce 1 se e solo se x è un cubo perfetto; (6) la funzione che restituisce 1 se e solo se x è ottenibile come somma di due cubi. Teorema 12.8. Le funzioni primitive ricorsive sono totali. Proof. Per induzione strutturale sulla complessità (nel senso di numero di operazioni di composizione e ricorsione primitiva) delle funzioni definite in P osserviamo che le funzioni ricorsive di base sono totali, e la composizione di funzioni totali è totale. Resta da dimostrare che la composizione mediante ricorsione primitiva di funzioni totali è totale. Questo passo è lasciato per esercizio.  I precedenti esempi ci lascerebbero pensare che la nozione di funzione primitiva ricorsiva catturi esattamente il concetto intuitivo di funzione calcolabile mediante un algoritmo. Questo è falso! Innanzitutto, il Teorema 12.8 afferma che le funzioni 104 12. FUNZIONI PARZIALI RICORSIVE DI KLEENE & ROBINSON primitive ricorsive sono totali e, come vedremo, questa è una limitazione, ovvero una funzione calcolabile può essere indefinita su alcuni (o tutti) i valori di input. Le funzioni primitive ricorsive hanno inoltre un’ulteriore limitazione anche all’interno delle funzioni totali sui naturali. Per dimostrare questo fatto, è sufficiente dare un testimone, ovvero una funzione evidentemente calcolabile e totale, ma non appartenente a P. Una tale funzione è la funzione di Ackermann. La funzione di Ackermann è una funzione in 3 argomenti, ack : N3 −→ N, definita come segue:    ack(0, 0, y) = y       ack(0, x + 1, y) = ack(0, x, y) + 1   ack(1, 0, y) = 0      ack(z + 2, 0, y) = 1      ack(z + 1, x + 1, y) = ack(z, ack(z + 1, x, y), y). Per esercizio si verifichi che la funzione è totale (ed è ben definita, cioè non vi sono chiamate ricorsive ad oggetti ‘più grandi’). Vediamo di capire il significato della definizione di ack. Dalle prime due equazioni segue che ack(0, x, y) = y+x. La terza e quarta definzione costituiscono le condizioni iniziali per la quinta che è la vera e propria chiamata ricorsiva di ack. Questa afferma che λxy. ack(z + 1, x, y) è ottenuta calcolando   0 se z + 1 = 1 y0 = ack(z + 1, 0, y) =  1 se z + 1 > 1 e quindi applicando la funzione λwy. ack(z, w, y) x volte, ovvero la sequenza: y1 = ack(z + 1, 1, y) = ack(z, y0 , y) y2 = ack(z + 1, 2, y) = ack(z, y1 , y) y3 = ack(z + 1, 3, y) = ack(z, y2 , y) ... yx = ... ... ack(z + 1, x, y) = ack(z, yx−1 , y) 2. DIAGONALIZZAZIONE 105 Quindi abbiamo: f(0, x, y) = y+x f(1, x, y) = y·x f(2, x, y) = yx f(3, x, y) = yy ... y ·· x-volte ... Ad esempio, ack(3, 3, 3) = 327 > 1014 . Teorema 12.9. ack non è ricorsiva primitiva [25]. La dimostrazione del Teorema 12.9 è piuttosto complessa ed è basata sul fatto che ack cresce più velocemente di qualunque funzione ricorsiva primitiva. Questa funzione, universalmente accettata come funzione calcolabile non è primitiva ricorsiva pur essendo totale. Inoltre esistono infinite funzioni totali e chiaramente calcolabili che non sono esprimibili mediante ricorsione primitiva: basta aggiungere alla funzione ack una generica costante. 2. Diagonalizzazione In questa sezione studieremo uno degli strumenti fondamentali della teoria delle funzioni calcolabili: la diagonalizzazione. Il suo uso, già evidenziato nel Capitolo 10, sarà frequente nel seguito. La tecnica di diagonalizzazione fu per la prima volta utilizzata da Cantor nel 1874 per dimostrare uno dei risultati fondamentali nella teoria classica degli insiemi, ovvero la non enumerabilità dei numeri reali (si veda anche il Capitolo 2). L’idea della diagonalizzazione è la seguente: Dato un insieme S numerabile, costruiamo una matrice nel modo seguente: in ogni riga i è presente una possibile enumerazione di S: si,0 , si,1 , si,2 , . . . che identifica univcocamente una funzione totale f : N −→ S. Supponiamo che l’insieme di tali enumerazioni sia numerabile e tale enumerazione la leggiamo scandendo la matrice riga per riga. s0,0 s0,1 s0,2 ... s1,0 s1,1 s1,2 ... s2,0 s2,1 s2,2 ... ... ... ... ... Sia d : S −→ S una funzione totale che non sia mai l’identità su S, ovvero tale che ∀s ∈ S. d(s) 6= s (una tale funzione ovviamente esiste). Da questa costruzione, consideriamo la diagonale s0,0 , s1,1 , s2,2 , . . . che viene trasformata da d nella sequenza: d(s0,0 ), d(s1,1 ), d(s2,2 ) . . . . 106 12. FUNZIONI PARZIALI RICORSIVE DI KLEENE & ROBINSON La caratteristica essenziale di questa sequenza è che essa differisce da ogni altra riga della matrice. Pertanto esiste una enumerazione di S non presente nella matrice. Ciò contraddice con l’ipotesi che l’insieme delle enumerazioni n(e dunque delle funzioni totali da N a S è numerabile. Pertanto, data una qualsiasi enumerazione degli oggetti di un sistema formale dato, in questo modo sarà sempre possibile “costruire” un oggetto che non appartiene alla numerazione, ovvero esterno al sistema formale dato. Come esempio di applicazione di questa tecnica, dimostreremo l’inadeguatezza della classe delle funzioni primitive ricorsive nell’esprimere ciò che intuitivamente viene ritenuto effettivamente calcolabile. Ovvero mediante un “argomento diagonale” dimostreremo che è possibile definire effettivamente una classe di funzioni calcolabili non esprimibili mediante ricorsione primitiva. In realtà la diagonalizzazione ha un ben più ampio campo di utilizzo in quanto è applicabile ad ogni sistema formale le cui istruzioni definite su un alfabeto dato, sono effettivamente enumerabili. L’inadeguatezza della classe delle funzioni primitive ricorsive per esprimere la calcolabilità era comunque già chiara dal fatto che la funzione calcolabile di Ackermann non è primitiva ricorsiva. Consideriamo l’insieme delle funzioni primitive ricorsive P ed un formalismo, ovvero un insieme finito di simboli con il quale sia possibile descrivere tutte le possibili derivazioni associate alle funzioni in P. Un siffatto alfabeto può essere ad esempio Σ = {a, b, c, . . . , z, 0, 1, 2, . . . 9, (, )} Ogni derivazione associata ad una funzione in P è quindi descrivibile con una opportuna sequenza finita di simboli in Σ∗ . È inoltre facile definire una procedura che verifichi se una data stringa σ ∈ Σ∗ corrisponde ad una derivazione legittima per una funzione in P (si veda anche la Nota 12.4). È pertanto possibile enumerare tutte le derivazioni legittime per funzioni primitive ricorsive esaminando le stringhe in Σ di lunghezza 1, quindi quelle di lunghezza 2 etc. Sia quindi Qx l’(x + 1)-esima derivazione in questa lista e sia gx la funzione di cui Qx è derivazione. Definiamo la funzione h come segue: h(x) = gx (x) + 1 Evidentemente abbiamo un algoritmo per calcolare h: dato x, generiamo la lista delle derivazioni fino a Qx , quindi utilizziamo Qx per calcolare gx (x) e sommiamo 1. h cosı̀ costruita non è primitiva ricorsiva altrimenti, se lo fosse, avremmo h = gx0 per un qualche x0 , e quindi l’assurdo: gx0 (x0 ) = h(x0 ) = gx0 (x0 ) + 1 Si noti l’analogia di questa dimostrazione (informale) con la dimostrazione di Cantor sulla non enumerabilità di ℘(N). È pertanto possibile “costruire”, mediante diagonalizzazione, una funzione a tutti gli effetti calcolabile che non è primitiva ricorsiva. 3. FUNZIONI PARZIALI RICORSIVE 107 Il problema posto dalla diagonalizzazione sembra limitare in modo intrinseco e insuperabile ogni definizione matematica di funzione calcolabile codificabile con un alfabeto finito di simboli. Infatti, per diagonalizzazione sembra possibile costruire funzioni calcolabili che sfuggono ad ogni definizione formale, basata ad esempio su un alfabeto finito, di funzione calcolabile. L’idea fondamentale che permette di superare la difficoltà indotta dalla diagonalizzazione su un dato sistema formale è quella di ammettere algoritmi, o insiemi di istruzioni per calcolare sia funzioni totali che parziali. Per comprendere meglio questo aspetto fondamentale da cui è iniziato lo studio della teoria della calcolabilità, cerchiamo di applicare un argomento diagonale al caso di sistemi formali per esprimere funzioni parziali. Osserveremo che un sistema formale che caratterizzi un sottoinsieme opportuno di funzioni parziali, non è soggetto a limitazioni dovute ad argomenti diagonali. Sia ψx la funzione parziale corrispondente all’(x+1)-esimo insieme di istruzioni Px in un dato sistema formale per funzioni parziali, e sia x0 tale che ψx0 è la funzione parziale ϕ definita dalle seguenti istruzioni: trova Px , calcola ψx (x) e, se e quando si ottiene un risultato, restituisci come output di ϕ il valore ψx (x) + 1. Trattandosi di funzioni parziali, l’equazione ψx0 (x0 ) = ϕ(x0 ) = ψx0 (x0 ) + 1 non genera contraddizioni, poiché ϕ(x0 ) può non essere definita. Questo metodo di procedere, ovvero considerare la teoria della effettiva calcolabilità come una teoria di funzioni parziali è alla base degli approcci di Kleene, Church e Turing sviluppati negli anni ‘30. Tutti i sistemi formali che vedremo infatti hanno in comune una descrizione in un opportuno sistema formale, ovvero mediante un opportuno insieme di simboli e regole, di ciò che sono gli algoritmi ed una conseguente caratterizzazione del sottoinsieme delle funzioni parziali calcolabili in quel sistema formale, ovvero calcolabile dagli algoritmi cosı̀ descritti. Abbiamo già visto un esempio di questo modo di procedere nella descrizione delle MdT come sistema formale per esprimere algoritmi e nella funzioni calcolabili da una MdT come descrizione di opportune funzioni parziali. Queste considerazioni generali sulla natura parziale delle funzioni calcolabili giustificano l’assunzione l nella descrizione intuitiva di ciò che è effettivamente calcolabile. Indicheremo nel seguito con f(x) ↓ il fatto che f(x) è definita mentre con f(x) ↑ il fatto che f(x) non è definita. 3. Funzioni parziali ricorsive Introduciamo ora un metodo generale per costruire funzioni parziali a partire da funzioni totali (ad esempio primitive ricorsive). 108 12. FUNZIONI PARZIALI RICORSIVE DI KLEENE & ROBINSON Definizione 12.10. Sia f una funzione totale n + 1 aria, allora definiamo la funzione ϕ(x1 , . . . , xn ) = µz. (f(x1 , . . . , xn , z) = 0)   il più piccolo z t.c. f(x , . . . , x , z) = 0 se z esiste 1 n =  ↑ altrimenti Tale operatore tra funzioni è detto µ-operatore ed una funzione cosı̀ definita è detta definita per minimizzazione o µ-ricorsione da f. Chiaramente una funzione definita per minimizzazione è in generale, per definizione, parziale. Nota 12.11. Analizziamo l’ipotesi che f sia una funzione totale. Questa ipotesi può essere rilasciata definendo la minimizzazione, o µ-ricorsione nel modo seguente: Sia ψ una funzione parziale; ϕ è definita per µ-ricorsione da ψ se ϕ(x1 , . . . , xn ) = µz. (∀y ≤ z. ψ(x1 , . . . , xn , y) ↓ ∧ ψ(x1 , . . . , xn , z) = 0) La rimozione dell’ipotesi di totalità nella Definizione 12.10 provocherebbe un chiaro non senso. Infatti, per determinare il minimo z tale che f(x1 , . . . , xn , z) = 0 si ricorre al seguente algoritmo (facilmente descrivibile con una MdT) calcolando la sequenza f(x1 , . . . , xn , 0), f(x1 , . . . , xn , 1), . . . fino a che non si trova il primo valore z tale che f(x1 , . . . , xn , z) = 0. Se f fosse parziale, ovvero il suo calcolo potesse non convergere, la procedura cosı̀ descritta non sarebbe effettiva. Inoltre, come dimostrato da Kleene nel ‘52, le funzioni parziali ricorsive non sono chiuse rispetto allo schema2 ϕ(x1 , . . . , xn ) = µz. (ψ(x1 , . . . , xn , z) = 0). Definizione 12.12. La classe delle funzioni parziali ricorsive è la minima classe PR di funzioni contenente P e chiusa per µ-ricorsione. Esempio 12.13. Si osservi come si possa utilizzare il µ-operatore per la definizione delle seguenti funzioni: • logaritmo (intero): ⌊loga x⌋ = µy. (leq(x, a(y+1) ) = 0), ove leq è una funzione ricorsiva primitiva tale che leq(x, y) = 0 se e solo se x < y; 2 Tale risultato si può mostrare facilmente ma utilizzando risultati e nozioni che vedremo in seguito nel Cap. 17. Anticipando alcune nozioni, se A è un insieme ricorsivamente enumerabile ma non ricorsivo, allora definendo: ψ(x, y) = 0 ⇔ (y = 0 ∧ x ∈ A) ∨ y = 1 si ha che ψ è parziale ricorsiva; ma definendo f(x) = µy. (ψ(x,y) = 0), abbiamo che f non può essere parziale ricorsiva altrimenti, se lo fosse, essendo chiaramente totale, sarebbe ricorsiva e poiché f(x) = 0 ⇔ x ∈ A anche A sarebbe ricorsivo. 4. EQUIVALENZA TRA MDT E FUNZIONI PARZIALI RICORSIVE 109 • radice√n-esima (intera): ⌊ n x⌋ = µy. (leq(x, (y + 1)n ) = 0); 4. Equivalenza tra MdT e funzioni parziali ricorsive Il seguente risultato dimostra l’equivalenza delle funzioni calcolabili da MdT e le funzioni parziali ricorsive di Kleene & Robinson. Teorema 12.14. f : N −→ N è parziale ricorsiva se e solo se è Turingcalcolabile. Proof. Per dimostrare il verso (→), conviene dimostrare un teorema più forte: se ϕ è parziale ricorsiva, allora esiste una MdT che la calcola che: (1) Funziona anche se il nastro a sinistra dell’input e a destra della posizione iniziale della testina non è una sequenza infinita di $. (2) Quando termina, sappiamo che deve terminare a destra dell’output. Richiediamo inoltre che subito a sinistra dell’output ci sia l’input (o una copia di esso). (3) La parte del nastro che sta a sinistra della posizione iniziale della testina (in particolare l’input) non viene modificata. Queste 3 assunzioni ci permetteranno di applicare l’ipotesi induttiva. Si tratta ora di realizzare (o descrivere a parole) le MdT che calcolano le funzioni di base soddisfacendo alle restrizioni suddette e poi mostrare che disponendo delle MdT che soddisfano le condizioni sopra e che calcolano delle funzioni, siamo in grado di definire quelle necessarie per calcolare la funzione che fa la loro composizione (qui le proprietà sopra sono essenziali), ricorsione primitiva e minimizzazione [Completare per esercizio]. Mostriamo invece l’implicazione inversa e cioé che se ϕ è Turing-calcolabile allora ϕ è parziale ricorsiva. Sia ϕ : N −→ N una funzione calcolabile da una MdT Z. L’obiettivo è quello di codificare la MdT Z come una funzione sui naturali che sia parziale ricorsiva. Per semplicità assumiamo, senza incorrere in limitazioni, che la MdT Z sia definita con 2 simboli, che corrispondono ai numeri 0 e 1 rispettivamente: Σ = Q = {0, 1} {q0 , . . . , qk } Con queste ipotesi possiamo rappresentare una generica ID come una tupla di numeri, associando un numero nell’intervallo [0, k] a ogni simbolo di stato in Q. Definiamo ar : ID −→ N4 tale che, per ogni α ∈ ID della forma: α = · · · b2 b1 b0 s qh c0 c1 c2 · · · 110 12. FUNZIONI PARZIALI RICORSIVE DI KLEENE & ROBINSON con s ∈ {0, 1}, h ∈ [0, k] e {bi , ci }i∈N ⊆ Σ, allora: ar(α) = (h, m, s, n) dove m = n = P∞ i=0 P∞ i=0 bi 2i = ci 2i = Pkm i=0 Pkn bi 2i i i=0 ci 2 ove km (kn ) è il massimo intero i per cui bi 6= 0 (rispettivamente ci 6= 0). In questo modo, poiché il numero di 1 sul nastro non può essere infinito (siamo partiti con un numero finito di 1 ed abbiamo effettuato un numero finito di passi), per ogni ID in una computazione di Z, segue che ar associa in modo univoco ad ogni ID una quadrupla di naturali (aritmetizzazione di ID). Una computazione di una MdT è definita come una sequenza (anche infinita) di ID: α1 ⊢ α2 ⊢ . . .. Definiamo dunque una funzione che esprime un passo di transizione tra ID, ovvero una funzione che manipola quadruple di naturali. A tal proposito, definiamo le seguenti funzioni: ∆ : Q × Σ −→ Q, ∇ : Q × Σ −→ Σ e X : Q × Σ −→ {0, 1} tali che: • ∆(q, s) è lo stato che la MdT Z assume trovandosi nello stato q con simbolo in lettura s; • ∇(q, s) è il simbolo che la MdT Z produce trovandosi nello stato q con simbolo in lettura s; • X (q, s) è lo spostamento a destra (= 1) o sinistra (= 0) compiuto dalla MdT Z trovandosi nello stato q con simbolo in lettura s. Per i valori non definiti si assegni il valore k + 1 per renderle totali. Si noti che ∆, ∇ e X sono funzioni definite dalla matrice funzionale di Z, e sono chiaramente funzioni primitive ricorsive (lo si dimostri per esercizio). Possiamo quindi definire le trasformazioni compiute eseguendo un singolo passo della MdT Z sulle quadruple rappresentanti una generica ID di Z: q′ s′ m n′ ′ = ∆(q, s) = (m mod 2)(1 − X (q, s)) + (n mod 2)X (q, s) = (2m + ∇(q, s))X (q, s) + (m div 2)(1 − X (q, s)) = (2n + ∇(q, s))(1 − X (q, s)) + (n div 2)X (q, s). Queste funzioni sono chiaramente primitive ricorsive, essendo la composizione di funzioni primitive ricorsive. Possiamo dunque rappresentare una transizione α ⊢ β nel modo seguente: Se ar(α) = (q, s, m, n) allora ar(β) = (q ′ , s ′ , m ′ , n ′ ). Per rappresentare l’effetto di t-transizioni o passi di calcolo della MdT Z, definiamo le seguenti funzioni di 5 argomenti: • P∆ (t, q, s, m, n) è lo stato (∈ [0, k]) ottenuto dopo t-passi partendo da una ID α tale che ar(α) = (q, m, s, n); • P∇ (t, q, s, m, n) è il simbolo (∈ {0, 1}) ottenuto dopo t-passi partendo da una ID α tale che ar(α) = (q, m, s, n); 4. EQUIVALENZA TRA MDT E FUNZIONI PARZIALI RICORSIVE 111 • PM (t, q, s, m, n) è il valore (∈ N) rappresentante il nastro a sinistra della testina ottenuto dopo t-passi partendo da una ID α tale che ar(α) = (q, m, s, n); • PN (t, q, s, m, n) è il valore (∈ N) rappresentante il nastro a destra della testina ottenuto dopo t-passi partendo da una ID α tale che ar(α) = (q, m, s, n). È facile vedere che, ad esempio, P∆ può essere definita nel modo seguente: P∆ (0, q, s, m, n) = P∆ (t + 1, q, s, m, n) = q P∆ (t, q ′ , s ′ , m ′ , n ′ ) Supponiamo che la MdT Z abbia uno stato q1 ∈ Q di terminazione, ovvero tale che le uniche caselle vuote della matrice funzionale di z siano in corrispondenza della riga q1 . È chiaro che ogni MdT può essere trasformata in modo tale da avere uno stato di terminazione di questo tipo. Pertanto, la MdT Z termina il suo calcolo nel primo (minimo) t tale che: P∆ (t, q0 , s0 , m0 , n0 ) = 1, essendo α0 la configurazione iniziale tale che ar(α0 ) = (q0 , m0 , s0 , n0 ). Ovvero, il numero di passi necessario per terminare, è definibile mediante µ-ricorsione come: µt. (P∆ (t, q0 , s0 , m0 , n0 ) − 1 = 0). Sia x ∈ N un numero naturale di input. Assumiamo che la MdT inizi il suo calcolo avendo il numero x sul nastro alla destra della sua testina codificato come una sequenza di x-zeri seguita da 1 (rappresentata matematicamente dal numero 2x ). Assumiamo anche che il risultato, se calcolato, sia rappresentato nel medesimo modo sul nastro della macchina. È dunque chiaro che, avendo un modo algoritmico primitivo ricorsivo dato da una funzione C per rappresentare come potenza di 2 il numero alla destra della testina nella ID terminale, la funzione ϕ calcolata da Z è esprimibile dalla seguente funzione parziale ricorsiva: ϕ(x) = C(PN (µt. (P∆ (t, 0, 0, 0, 2x ) − 1 = 0), 0, 0, 0, 2x )). Per esercizio si trovi la funzione C che chiude la dimostrazione e si dimostri che è primitiva ricorsiva.  Corollario 12.15 (Forma normale di Kleene). Per ogni funzione ϕ ∈ PR (o equivalentemente Turing calcolabile) esistono f, g ∈ P tali che ϕ = λx. f((µt. g(t, x) = 0), x) . CHAPTER 13 Tesi di Church-Turing Negli stessi anni in cui venivano proposti i vari formalismi per definire la effettiva calcolabilità, ad esempio le MdT, le funzioni ricorsive, il λ-calcolo etc., veniva dimostrata anche la loro l’equivalenza. Si dimostrava cioè che i diversi sistemi permettono di calcolare esattamente la stessa classe di funzioni. Abbiamo visto degli esempi nelle sezioni precedenti riguardanti le MdT e le funzioni parziali ricorsive di Kleene & Robinson. Questi risultati (in particolare l’equivalenza del λ-calcolo con le funzioni parziali ricorsive) portarono Church e Turing nel ‘36 a formulare la seguente Tesi fondamentale: Tesi di Church-Turing: La classe delle funzioni “intuitivamente calcolabili” coincide con la classe delle funzioni Turing calcolabili. Si tratta chiaramente di una tesi indimostrabile, vista l’impossibilità di maneggiare formalmente (= matematicamente) la nozione di “intuitivamente calcolabile”. Accettando la Tesi di Church-Turing si osserva immediatamente la notevole importanza che assumono le funzioni calcolabili da MdT, o equivalentemente, le funzioni parziali ricorsive o λ-definibili. Queste classi di funzioni vengono a rappresentare, in virtù della Tesi di Church-Turing, la classe delle funzioni calcolabili effettivamente mediante un algoritmo. La Tesi di Church-Turing ha pertanto notevole interesse metamatematico. Lo stesso Gödel scrisse nel ‘46 le seguenti osservazioni riguardanti la Tesi di Church-Turing, in riferimento ad una presentazione di Tarski sul medesimo argomento: Tarski has stressed in his lecture (and I think justly) the great importance of the concept of general recursiveness1 (or Turing’s computability). It seems to me that this importance is largely due to the fact that with this concept one has for the first time succeeded in giving an absolute definition of an interesting epistemological notion, i.e. one not depending on the formalism chosen. [ . . . ] By a kind of miracle it is not necessary to distinguish orders and the diagonal procedure does not lead outside the defined notion. 1 . . . che noi chiamiamo ricorsività a la Kleene & Robinson. 113 114 13. TESI DI CHURCH-TURING [K. Gödel, Princeton 1946] La Tesi di Church-Turing permette di limitare l’estensione di ciò che è effettivamente calcolabile: se dimostriamo che una funzione non è parziale ricorsiva, allora, in virtù della Tesi di Church-Turing, essa non è calcolabile in nessun modo effettivo. Analogamente, dare un algoritmo per una funzione corrisponde a dimostrare, per la Tesi di Church-Turing, che essa è una funzione parziale ricorsiva. Questo utilizzo della Tesi di Church-Turing è fondamentale in questo corso. Infatti, sarà sufficiente dimostrare la realizzabilità di un algoritmo per affermare l’esistenza di una MdT o di una funzione parziale ricorsiva corrispondente, senza dover esibire una tale macchina o una funzione per calcolarlo. Questo ci permetterà di sviluppare un certo numero di strumenti a supporto della teoria delle calcolabilità, senza dover esibire esplicitamente complesse MdT o funzioni parziali ricorsive. Questa caratteristica della Tesi di Church-Turing darà un aspetto apparentemente informale alle dimostrazioni di effettiva calcolabilità di funzioni. Dimostrazioni di questo tipo, che si basano su una non banale applicazione della Tesi di ChurchTuring, saranno dette dimostrazioni mediante Tesi di Church-Turing. Pertanto, ogni volta che una funzione risulterà “palesemente” calcolabile, la si accetterà come effettivamente calcolabile da uno dei formalismi prescelti, senza dover costruire la MdT o altra funzione corrispondente. Nel seguito dunque considereremo equivalenti i concetti di calcolabilità: “effettiva” (ovvero associata all’esistenza di una MdT), “algoritmica” (ovvero associata all’esistenza di un algoritmo che soddisfi i requisiti a–l) o “ricorsiva” (ovvero associata ad una funzione ricorsiva parziale che la calcola). A questo punto della trattazione, è bene distinguere, per chiarezza, tra due nozioni diverse di calcolabilità. Infatti è possibile definire una funzione g per cui sappiamo esistere un algoritmo ma per la quale non sappiamo dare l’algoritmo che la calcola. Consideriamo le funzioni definite nel modo seguente:    1 se esattamente x ‘5’ consecutivi appaiono nella   f(x) = espansione decimale di π     0 altrimenti g(x) =    1       0 se almeno x ‘5’ consecutivi appaiono nella espansione decimale di π altrimenti È chiaro che g è calcolabile: dato un algoritmo che genera l’espansione decimale di π, g sarà o la funzione costante g(x) = 1, nel caso vi sia un numero arbitrariamente grande di occorrenze successive di ‘5’ in π, che è chiaramente una funzione calcolabile; oppure sarà la funzione calcolata da un qualche algoritmo che, fissato 13. TESI DI CHURCH-TURING un k (ed esempio il massimo numero di ‘5’ consecutivi in π), calcola:   1 se x ≤ k g(x) =  0 se x > k 115 In questo secondo caso esiste sicuramente un tale k che permette di individuare l’algoritmo cercato, ma ci è impossibile sapere quale sia il k giusto. In entrambi i casi la funzione g è calcolabile (ed é in particolare primitiva ricorsiva), ma in generale non sappiamo costruire effettivamente un algoritmo per calcolarla. Al contrario, per f non siamo in grado a tutt’oggi di affermare nulla sulla sua calcolabilità effettiva. Pertanto, mentre per g è possibile invocare la Tesi di Church-Turing, ed affermare che esiste una MdT che la calcola, anche se non si sa quale, per f questo non è possibile. Accettando quindi di considerare calcolabile ogni funzione per la quale esiste un algoritmo che la calcoli, possiamo nel seguito utilizzare la Tesi di Church-Turing ogni qual volta sia chiara l’esistenza di un algoritmo per calcolare una data funzione. CHAPTER 14 Aritmetizzazione e universalità Aritmetizzazione significa semplicemente traduzione nel linguaggio dell’aritmetica. Il primo utilizzo dell’aritmetizzazione è dovuto a Gödel nel 1931 nella dimostrazione del risultato fondamentale di incompletezza dell’aritmetica; da qui anche il termine equivalente di Gödelizzazione. Nel seguito considereremo le MdT come esempio ed applicheremo alle MdT il concetto di aritmetizzazione. Per la Tesi di ChurchTuring, sappiamo che questa non è una restrizione, ed analoghe aritmetizzazioni possono essere definite per ogni sistema formale equivalente alle MdT. 1. Enumerazione delle MdT Sia Σ = {$, 0}. Quante sono le macchine di Turing ‘distinte’ descrivibili? Si supponga Q = {q0 }. Si tratterà di riempire la tabella $ 0 q0 in tutti i modi (significativi) possibili, ossia ogni singola casella potrà essere (1) vuota (si immagini di scrivere $ $ $); (2) q0 $ R; (3) q0 $ L; (4) q0 0 R; (5) qo 0 L. Dunque ci sono esattamente 25 = 5 × 5 macchine di Turing distinte ad un solo stato. Si può pensare a questo punto di definire un ordinamento ✁ sulle macchine di Turing ad uno stato definibili; fissato un ordinamento su Σ (poniamo $ <Σ 0), sia inoltre L <M R e si consideri la cella a sinistra più importante di quella a destra, essendo M insieme dei possibili movimenti della testina, cioé M = {L, R}. Si assuma che $ $ $ sia minore di qualunque terna q0 s m, allora si avrà (in una estensione lessicografica i cui parametri sono, nell’ordine cella e contenuto): h$ $ $, $ $ $i ✁ h$ $ $, q0 $ Li ✁ · · · ✁ h$ $ $, q0 0 Ri ✁ hq0 $ L, $ $ $i ✁ · · ·✁ hq0 0 R, q0 0 Ri. Più in generale, sia Q = {q0 , . . . , qn−1 }, Σ = {s0 = $, s1 = 0}, m0 = L,m1 = R; si definisca inoltre un ordinamento tra le 2 · n celle presenti (per righe, colonne, 117 118 14. ARITMETIZZAZIONE E UNIVERSALITÀ a zig zag etc.). Definendo esplicitamente l’ordinamento per ogni cella nel modo seguente: • $ $ $ ≺ qi s m per ogni qi ∈ Q, s ∈ Σ, m ∈ {L, R}; • qi1 sj1 mk1 ≺ qi2 sj2 mk2 se i1 < i2 oppure (i1 = i2 e j1 < j2 ) oppure (i1 = i2 e j1 = j2 e k1 < k2 ). si ha un buon ordine sull’insieme delle macchine di Turing aventi n stati, che sono in tutto (n · 2 · 2 + 1)2n = (4 · n + 1)2n , dunque ci saranno 25 MdT ad uno stato, 94 MdT a due stati, 136 MdT a tre stati,. . . . Si può dunque pensare di associare un numero naturale (un indice) ad ogni macchina di Turing in maniera biunivoca: 1 ÷ 25 MdT ad uno stato 4 26 ÷ 25 + 9 MdT a due stati 25 + 94 + 1 ÷ 25 + 94 + 136 MdT a tre stati ... ... all’interno di ogni gruppo, poi ci si basa sulla relazione di ordinamento. In generale, con un semplice algoritmo, dato x ∈ N si può risalire alla MdT x-esima. Esempio 14.1. Assumendo un ordinamento delle celle per cui la più importante è quella in alto a sinistra, la macchina di Turing n. 4399 ha due stati (Q = {q0 , q1 }), e la sua funzione di transizione è la seguente: δ $ q0 q1 $ R 0 q1 Si osservi che tale MdT computa la funzione λx.0. Una differente aritmetizzazione delle MdT si basa sulle proprietà della decomposizione in fattori primi dei numeri naturali. Analogamente al caso precedente, è possibile, in modo algoritmico, passare da una MdT ad un numero naturale che la aritmetizza, e viceversa è possibile determinare se un numero è o no l’artimetizzazione di una MdT. Consideriamo ancora una generica MdT definita su un alfabeto finito: Σ = {s0 , . . . , sn } Q = {q0 , . . . , qm } X = {L, R} 1. ENUMERAZIONE DELLE MDT 119 ed associamo, ad ogni simbolo x ∈ Σ ∪ Q ∪ X un numero dispari maggiore di 1: a(x) = 2k + 1 per qualche k ≥ 1, tale che se x 6= y allora a(x) 6= a(y). Ad esempio: R L s0 q1 s1 q2 ... ↓ ↓ ↓ ↓ ↓ ... 3 5 ↓ 7 9 11 13 ... Definiamo numero di Gödel di una espressione E ∈ (Σ ∪ Q ∪ X)∗ il numero: gn(E) = k Y a(xi ) pi i=1 essendo k il numero dei simboli in E, pi l’i-esimo numero primo e xi l’i-esimo simbolo di E. Per l’unicità della decomposizione in fattori primi, per ogni espressione esiste uno ed un solo numero di Gödel corrispondente; inoltre tale numero è pari nell’ipotesi E sia una stringa di almeno un simbolo. Poiché le quintuple di una MdT sono a tutti gli effetti espressioni in (Σ∪Q∪X)∗ , è possibile in modo algoritmico associare ad ogni quintupla un corrispondente numero di Gödel, e viceversa verificare se un dato numero è numero di Gödel di una quintupla legittima. Definiamo numero di Gödel di una MdT Z, ovvero di una sequenza di quintuple Z = {E1 , . . . , Eh } il numero: h Y gn(Ei ) pi i=1 Questo numero è anch’esso unico, per l’unicità della decomosizione in fattori primi, e non risulta confondibile con gn(E) per una qualche espressione E ∈ (Σ ∪ Q ∪ X)∗ , poiché, al contrario di gn, è ottenuto mediante esponenziazione pari. Analogamente, due sequenze con lo stesso numero di Gödel sono identiche (verificare per esercizio). In generale è possibile definire infinite aritmetizzazioni diverse per MdT, e quindi funzioni parziali ricorsive. L’artimetizzazione scelta non è quindi sostanziale nello sviluppo della teoria della calcolabilità. Essenziale è che, come dimostrato in precedenza, esistano metodi algoritmici per codificare MdT all’interno dei numeri naturali (suoi argomenti di calcolo). La possibilità di vedere i numeri come al tempo stesso rappresentanti gli argomenti del calcolo e le MdT che eseguono il calcolo, permette di applicare ai sistemi formali analizzati fino ad ora, uno dei principi alla base dell’informatica moderna, ovvero quello di poter passare programmi come argomenti ad altri programmi. In tutto ciò la scelta della particolare aritmetizzazione è inessenziale.1 1 La libertà di poter applicare MdT a MdT (autoapplicazione o referenziazione) condurrebbe immediatamente a paradossi tipo paradosso di Russell nella teoria che stiamo sviluppando. Ancora una volta, l’assunzione fondamentale che le funzioni calcolabili siano essenzialmente parziali, 120 14. ARITMETIZZAZIONE E UNIVERSALITÀ Nel seguito dunque indicheremo con: • Px l’insieme di istruzioni di una MdT di indice x, ovex il corrispondente indice o numero di Gödel della macchina, in una data aritmetizzazione. • ϕx la funzione calcolata dalla macchina Px . Il seguente risultato è banale in seguito ad una qualsiasi aritmetizzazione delle MdT. Teorema 14.2. Ci sono ℵ0 macchine di Turing distinte o funzioni parziali ricorsive, e ℵ0 funzioni ricorsive. Proof. Segue banalmente dal fatto che tutte le funzioni costanti sono ricorsive. Pertanto ci sono almeno ℵ0 funzioni ricorsive.  Segue dunque per la Tesi di Church-Turing che: Teorema 14.3. Ci sono esattamente ℵ0 funzioni calcolabili. Indicheremo nel seguito con ϕx la funzione parziale ricorsiva di indice o numero di Gödel x. Chiaramente sappiamo che esiste un algoritmo per passare da x a Px e/o ϕx e viceversa. Pertanto, con ϕx indicheremo indifferentemente l’x-esima MdT o funzione parziale ricorsiva in una data aritmetizzazione. Il seguente risultato dimostra che la cardinalità delle funzioni calcolabili è strettamente minore della cardinalità di tutte le possibili funzioni sui naturali. Si tratta di una rivisitazione del Teorema di Cantor, nel contesto delle funzioni calcolabili. Ne consegue che l’insieme delle funzioni calcolabili è strettamente contenuto (ne rappresenta una piccolissima parte) nell’insieme di tutte le funzioni, ovvero nell’insieme di tutti i possibili problemi esprimibili mediante funzioni sui naturali. In particolare, potendo selezionare una funzione in N −→ N, è facile dimostrare che la probabilità di selezionarne una calcolabile è 0. Teorema 14.4. Esistono funzioni (totali) f : N −→ N non Turing calcolabili. Proof. Sappiamo che, dal Teorema di Cantor (vedi Cap. 2) che: |{f : N −→ N}| ≥ |{f : N −→ {0, 1}}| = |℘(N)| > |N|  | f : N −→ N : f è Turing calcolabile | ≥  Rispetto a quanto visto nelle sezioni precedenti, abbiamo pertanto dimostrato le inclusioni tra classi di funzioni come evidenziate nella Figura 1. permette di superare tali paradossi, rendendo la teoria della calcolabilità una teoria consistente. Si veda in proposito la Nota 15.4. 2. MACCHINA DI TURING UNIVERSALE ✬ N −→ N ✛ calcolabili ✫ ✚ 121 ✩ ✘ ✙ ✪ Figure 1. Diagramma di inclusione tra insiemi di funzioni 2. Macchina di Turing Universale Per quanto si è illustrato nella sezione 1, dato un numero naturale x si può facilmente risalire ad una tabella definente la funzione di transizione della x-esima macchina di Turing. D’altro canto ogni singola macchina di Turing è un oggetto provvisto di un compito fisso. Una volta definita la funzione di transizione non vi è modo di modificarla. Il passo immediatamente successivo (che fornisce la base teorica al concetto di calcolatore programmabile) è il seguente: si supponga di disporre della macchina di Turing con un unico input che, interpretiamo come una coppia hx1 , x2 i, con il seguente comportamento: • x1 è l’indice di una macchina di Turing Px1 ; • x2 è la rappresentazione di un input che va immaginato come input per la macchina Px1 . La macchina in questione dapprima calcola la funzione di transizione della macchina Px1 , memorizzando questa funzione. Suppone poi (cioè lo memorizza opportunamente) di essere nello stato q0 . Si posiziona dunque in corrispondenza del primo carattere di input (chiamiamolo s) e va a vedere—sulla tabella memorizzata—cosa farebbe Px1 , ossia computa δPx1 (q0 , s). Procede dunque nella simulazione andando a modificare il carattere sulla zona di nastro dedicata all’input, lo stato nella opportuna zona in cui va memorizzato, e sposta idealmente la testina, in base al risultato di δPx1 (q0 , s). Una tale MdT si riesce ovviamente a costruire (anche se non può essere considerato un facile esercizio) e dunque avrà un suo indice nella enumerazione delle MdT; sia esso u. Si avrà pertanto: ∀x1 x2 . Pu (hx1 , x2 i) = Px1 (x2 ) ove il simbolo = in questo contesto sta a significare che, se una macchina termina, allora terminano entrambe fornendo lo stesso risultato, altrimenti entrambe ciclano. Come interessante ed immediata conseguenza avremo, ad esempio, Pu (hu, ui) = Pu (u). Più in generale possiamo dimostrare il seguente risultato, che rappresenta una prima dimostrazione mediante Tesi di Church. 122 14. ARITMETIZZAZIONE E UNIVERSALITÀ Teorema 14.5. Esiste un indice z tale che per ogni x e y,   ϕ (y) se ϕ (y) è definita x x ϕz (x, y) =  ↑ altrimenti Proof. Sia Px l’insieme di istruzioni di una MdT di indice x. Sappiamo che esiste un metodo effettivo per ottenere Px da x. Applichiamo Px all’input y e, quando questa termina, prendiamo il risultato come output di una funzione di 2 argomenti ψ(x, y). Pertanto abbiamo che:   ϕ (y) se ϕ (y) converge x x ψ(x, y) =  ↑ se ϕ (y) diverge x Applicando la Tesi di Church-Turing, possiamo concludere che ψ è parziale ricorsiva e pertanto esiste un indice z tale che ψ = ϕz .  La funzione ϕz ottenuta nella dimostrazione del precedente teorema è detta funzione parziale universale e corrisponde alla MdT universale vista in precedenza. Chiaramente, il precedente teorema può essere generalizzato a funzioni di k ≥ 1 variabili, in modo tale che, per ogni funzione di k ≥ 1 variabili, esiste una funzione di k + 1 variabili che gioca il ruolo di funzione universale parziale. Esercizio 14.6. Formulare e dimostrare il precedente teorema per funzioni di k ≥ 1 variabili. 3. Il Teorema s-m-n Il seguente risultato, noto con il nome Teorema s-m-n e dovuto a Kleene, sarà fondamentale nel seguito. Anch’esso rappresnta un esempio di dimostrazione basata sulla Tesi di Church-Turing. Teorema 14.7 (s-m-n). Per ogni coppia di interi m, n ≥ 1 esiste una funzione ricorsiva sm n di m + 1 variabili tale che per ogni x, y1 , . . . , ym abbiamo che: λz1 · · · zn . ϕx (y1 , . . . , ym , z1 , . . . , zn ) = ϕsm n (x,y1 ,...,ym ) Proof. Consideriamo per semplicità il caso m = n = 1. La dimostrazione per il caso più generale per m, n ≥ 1 è simile. Fissati x0 e y0 , la funzione λz. ϕx0 (y0 , z) è chiaramente una funzione calcolabile in una sola variabile. Per ogni x0 e y0 , la funzione λz. ϕx0 (y0 , z) dipenderà costruttivamente da x0 e y0 , pertanto esisterà per la Tesi di Church-Turing, una funzione ricorsiva (totale!) s tale che: λz. ϕx0 (y0 , z) = ϕs(x0 ,y0 ) .  Il Teorema s-m-n gioca un ruolo essenziale nello sviluppo della teoria della ricorsività, e spesso verrà utilizzato, come la Tesi di Church-Turing, tacitamente. Riportato alla programmazione ‘tradizionale’, il significato è più o meno il seguente. Se avete scritto un programma che implementa un certo algoritmo su m + n dati 3. IL TEOREMA S-M-N 123 in imput e da un certo momento in poi m di questi sono fissati, allora sapete ottenere un programma ‘specializzato’ che accetta solo n parametri in input. Questa è la giustificazione al lavoro delle software house che ‘installano’ del software gestionale generale alle varie ditte (che permettono di fissare, con i loro dati, gli m parametri). Il seguente risultato è una semplice applicazione del Teorema s-m-n. Teorema 14.8. Esiste una funzione ricorsiva g in due argomenti tale che per ogni x e y: ϕg(x,y) = ϕx ◦ϕy . Traccia. Si definisca ψ(x, y, z) = ϕx (ϕy (z)) se ϕy (z) ↓ e ϕx (ϕy (z)) ↓, indefinita altrimenti. Concludere per esercizio.  CHAPTER 15 Problemi insolubili In questo Capitolo analizzeremo alcuni problemi classici non risolvibili nella teoria della effettiva calcolabilità sviluppata fino ad ora. Il più classico problema insolubile è il problema della terminazione posto da Turing. Esiste una procedura effettiva tale che dati x e y determina se ϕx (y) è definita o no? Il seguente risultato preliminare dimostra l’insolubilità di un problema correlato.1 Lemma 15.1. Non esiste una funzione ricorsiva g tale che per ogni x:   1 se ϕx (x) ↓ g(x) =  0 se ϕ (x) ↑ x Proof. Supponiamo per assurdo che esista una MdT di indice i0 tale che g = ϕi0 . Allora possiamo definire la funzione:   ↑ se g(x) = 1 = ϕ (x) i0 g ′ (x) =  0 se g(x) = 0 = ϕ (x) i0 ′ Dunque anche g sarà calcolabile e dunque esiste i1 tale che ϕi1 = g ′ . Ma allora abbiamo che: ϕi1 (i1 ) ↓ ⇔ g ′ (i1 ) = 0 ϕi1 (i1 ) ↑ ⇔ g ′ (i1 ) ↑ ⇔ g(i1 ) = 0 ⇔ ϕi1 (i1 ) ↑ ⇔ g(i1 ) = 1 ⇔ ϕi1 (i1 ) ↓ Che è assurdo. Dunque non esiste un tale indice i1 , ovvero non esiste l’indice i0 che calcoli g, ovvero g non è ricorsiva.  In base al Lemma 15.1, segue il teorema: Teorema 15.2. Non esiste una funzione ricorsiva ψ tale che per ogni x e y:   1 se ϕ (y) ↓ x ψ(x, y) =  0 se ϕ (y) ↑ x 1 Si ricorda che con ϕ (y) ↓ si intende che la macchina di Turing x-esima sull’input y x termina o equivalentemente, che la funzione ricorsiva parziale x-esima è definita per l’input x. Con ϕx (y) ↑ si intende l’opposto di tale affermazione. 125 126 15. PROBLEMI INSOLUBILI Proof. Se esistesse allora esisterebbe un indice x0 tale che ϕx0 (x) = ψ(x, x). ψ(x, x) non è altro che la funzione g(x) dimostrata non esistere nel Lemma 15.1.  Il problema della terminazione è stato, storicamente, uno dei primi problemi matematici dimostrati come insolubili, ovvero per i quali non esiste una procedura in grado di “decidere” il problema per ogni possibile input, e rappresenta, per la sua semplicità ed importanza pratica e teorica, uno dei risultati più importanti ottenuti in matematica nel ventesimo secolo. Il seguente risultato, dovuto a Kleene, dimostra la non risolubilità di un altro semplice problema correlato alle funzioni ricorsive, ovvero non è possibile decidere se una funzione parziale ricorsiva data è totale. Il problema è chiaramente legato alla non decidibilità della terminazione. Teorema 15.3. Non esiste una funzione ricorsiva f tale che per ogni x:   1 se ϕ è totale x f(x) =  0 se ϕ non è totale x Proof. La dimostrazione utilizza un argomento diagonale. Supponiamo che esista una tale funzione f. Allora definiamo una funzione che associa ad ogni naturale una corrispondente funzione ricorsiva. g(0) = g(x + 1) = µy. (f(y) = 1) µy (y > g(x) ∧ f(y) = 1). Poiché sappiamo che esistono ω funzioni ricorsive totali, anche g sarà totale. Per la tesi di Church e questa osservazione è quindi ricorsiva. Definiamo una funzione h tale che h = λx. ϕg(x) (x) + 1. Per la definizione di f segue che h è totale, e quindi, per la Tesi di Church-Turing, ricorsiva. Sia dunque z0 l’indice per cui h = ϕz0 e sia y0 tale che g(y0 ) = z0 . Questo indice esiste per definizione di g. Allora h(y0 ) = ϕg(y0 ) (y0 ) + 1. Ma ϕg(y0 ) (y0 ) = h(y0 ) per definizione di y0 . Poiché h è totale, questa è una contraddizione.  Nota 15.4. Argomenti diagonali e insolubilità sono nozioni strettamente legate tra loro. Ritorniamo brevemente sulla questione delle funzioni parziali. Abbiamo visto che, ricorrendo alle funzioni parziali, si possono superare i limiti imposti da argomenti di tipo diagonale ai sistemi formali introdotti per definire il concetto di effettiva calcolabilità. La necessità di utilizzare funzioni parziali in luogo di funzioni totali segue anche dalla possibilità, dimostrata in precedenza, di definire macchine universali o funzioni ricorsive universali. Questa possibilità, associata alla ipotesi di totalità delle funzioni calcolabili, conduce infatti direttamente a 15. PROBLEMI INSOLUBILI 127 paradossi simili al paradosso di Russell.2 L’aritmetizzazione infatti non impone nessuna distinzione tra funzioni (MdT) e argomenti, come nella teoria degli insiemi classica non vi è distinzione tra insiemi e loro argomenti. Questa mancanza di struttura permette una indisciplinata possibilità di definizioni di insiemi o funzioni, che conduce ai ben noti paradossi. È noto che è possibile codificare coppie, terne etc. con numeri naturali. Ad esempio la seguente funzione g : N × N −→ N è biiettiva (verificare per esercizio) e chiaramente calcolabile: 1 g(x, y) = (x2 + 2xy + y2 + 3x + y). 2 Essa permette di associare in modo univoco, ad ogni coppia di naturali, un numero naturale corrispondente. Quindi per la Tesi di Church-Turing esisterà un indice x0 tale che, se U(x, y) è la funzione parziale universale vista in precedenza, allora: U(x, y) = ϕx0 (g(x, y)) Inoltre, essendo sia g che ϕx0 calcolabili, esisterà un indice y0 tale che ϕy0 (x) = ϕx0 (g(x, x)). Sarà quindi lecito definire la funzione:   1 se ϕ (x) = ϕ (x) = 0 y0 x f(x) =  0 se ϕ (x) = ϕ (x) 6= 0 y0 x Anche questa funzione avrà un opportuno indice z0 . Segue quindi che ϕz0 (z0 ) = 0 se e solo se ϕz0 (z0 ) 6= 0, che è chiaramente una contraddizione. Per superare questo paradosso, del tutto analogo al paradosso di Russell, analogamente a quanto visto per superare i limiti imposti dalla diagonalizzazione, basterà ancora assumere che la funzione calcolabile f, calcolata dalla z0 -esima MdT, diverga in z0 , essendo quindi parziale ricorsiva. La funzione   1 se ϕx (y) ↓ U(x, y) =  ↑ se ϕ (y) ↑ x è dunque necessariamente parziale ricorsiva, ed infatti, l’algoritmo che la calcola è facilmente identificabile con la MdT universale vista in precedenza. Esercizio 15.5. Dimostrare che i seguenti problemi non ammettono soluzione ricorsiva: (1) Decidere se, per ogni x: ϕx = λx.0. (Traccia: Sia f la funzione ricorsiva parziale definita come f(x, y) = 0 se ϕx (y) ↓, indefinita altrimenti. Per s-m-n si ha che f(x, y) = ϕr(x) (y) per r totale ricorsiva. Se, per assurdo, esistesse g t.c. g(x) = 1 se ϕx = λx.0, g(x) = 0 altrimenti, si definisca 2 Il paradosso di Russell nella teoria classica degli insiemi è ottenuto considerando come definibile nella teoria stessa l’insieme A = {x | x 6∈ x}. Quindi x ∈ A ⇔ x 6∈ x, da cui segue che A ∈ A ⇔ A 6∈ A. 128 15. PROBLEMI INSOLUBILI h(x) = g(r(x)) e si giunga ad un assurdo rispetto all’enunciato del Lemma 15.1). (2) Decidere se, per ogni x: ϕx è costante; (3) Decidere se per ogni x e y: ϕx = ϕy . (Traccia: si mostri che allora si saprebbe decidere il problema 1). (4) Decidere se per ogni x, y e z: ϕx (y) = z. CHAPTER 16 Calcolabilità e Linguaggi di Programmazione In questo capitolo vedremo alcune applicazioni tra le più importanti della teoria elementare della calcolabilità vista nel precedente capitolo al progetto ed alla implementazione dei moderni linguaggi di programmazione. In particolare metteremo in evidenza come l’espressività (in senso lato) di un linguaggio dipenda dalla possibilità offerta da quest’ultimo di codificare uno dei formalismi affrontati per definire il concetto di effettiva calcolabilità. Questi problemi hanno dato origine, negli anni ‘60, alla ricerca di linguaggi di programmazione ad alto livello, in grado cioè di esprimere tutte le funzioni effettivamente calcolabili. Tale ricerca ha dato origine a una vasta famiglia di linguaggi come ALGOL e PASCAL, progenitori dei più moderni linguaggi C, C++, e Java. Inoltre vedremo come l’esistenza (almeno teorica) di strumenti ampiamente utilizzati nella moderna pratica informatica per manipolare programmi, come i compilatori, gli interpreti, e i programmi di specializzazione, dipenda dai risultati di universalità visti nella caratterizzazione delle funzioni calcolabili. 1. Il linguaggio While Abbiamo fino ad ora definito il concetto di effettiva calcolabilità su una semplicissima struttura dati: i numeri naturali N. Come vedremo, questa apparente restrizione in realtà si dimostra essere perfettamente equivalente ad ogni definizione di calcolabilità data su strutture dati più evolute, quali stringhe, alberi etc. I numeri naturali sono pertanto sufficienti per rappresentare ogni possibile struttura dati su cui si vogliono definire algoritmi. Al fine di evitare l’aritmetizzare dei programmi, in questo capitolo studieremo un semplice linguaggio di programmazione imperativo chiamato While in grado di manipolare strutture dati più complesse. While è un linguaggio ad alto livello adeguato per rappresentare tutte le funzioni calcolabili che permette di manipolare semplici strutture dati. Le strutture dati impiegate in While sono molto simili a quelle impiegate nei linguaggi Scheme e LISP. Questa estensione ci permetterà di evitare l’aritmetizzazione (operazione di interesse preminentemente matematico) permettendo quindi di considerare direttamente i programmi come dati. Questo, come abbiamo visto, è il passo fondamentale per definire il concetto di funzione universale. 129 130 16. CALCOLABILITÀ E LINGUAGGI DI PROGRAMMAZIONE 2. Strutture dati Le strutture dati del linguaggio While sono alberi binari. Questi permettono di rappresentare la sintassi concreta dei programmi come dati. Sia A un insieme finito di atomi, o espressioni elementari, e nil l’albero vuoto. L’insieme degli alberi DA è definito ricorsivamente come il più piccolo insieme tale che: nil ∈ DA A ⊆ DA ∀d1 , d2 ∈ DA . (d1 .d2 ) ∈ DA In altri termini, se A = {a1 , . . . , an }, DA è il linguaggio generato dalla grammatica CF: DA −→ nil | a1 | · · · | an | (DA .DA ). Ad esempio, l’albero in Figura 1 è rappresentato come: ((a.((b.b).c)).d), dove A = {a, b, c, d} è il corrispondente insieme di atomi. ((a.((b.b).c)).d) •??  ??? ??    (a.((b.b).c)) •?? d ?  ??  ??      a •??((b.b).c) ??  ??   ? ?(b.b) • c ?   ??  ??  ?  b b Figure 1. L’albero ((a.((b.b).c)).d) Teorema 16.1. DA è isomorfo a N Traccia. È semplice definire una biiezione tra N e DA . Ad esempio si possono prima contare gli alberi di altezza 0, poi quelli di altezza 1 e cosı̀ via. Ad esempio, se A = {a1 , . . . , an }: 0 1 2 ... n nil a1 a2 ... an n+1 n+2 ... (nil.nil) (nil.a1 ) . . . n2 + 3n + 2 n2 + 3n + 3 . . . (an .an ) (nil.(nil.nil)) ...  4. SEMANTICA 131 3. Sintassi Nel seguito assumeremo di avere a disposizione un insieme infinito e numerabile di variabili Var. La sintassi di While è definita da una grammatica CF nel modo seguente dove x, y ∈ Var, e d ∈ DA (per essere precisi, dovremmo scrivere DA in luogo di d; tuttavia ci farà comodo nel seguito identificare con d un arbitrario elemento di d ∈ DA , pertanto usiamo questa notazione): Exp Com Prog → x | d | cons(Exp1 , Exp2 ) | hd(Exp) | tl(Exp) | Exp1 = Exp2 → x := Exp | Com1 ; Com2 | skip | while Exp do Com endw → read(Listavar); Com; write(Listavar) Listavar → x | x, Listavar Assumiamo inoltre che in Listavar le variabili siano tutte distinte. Si osservi come in questo linguaggio non vi sia la dichiarazione di tipo per le variabili. Tutte le variabili infatti possono assumere solo valori di “tipo” DA . Esempio 16.2. Il seguente programma While è ben definito [darne l’albero di derivazione per esercizio]. read(x); y := nil; while x do y := cons(hd(x), y); x := tl(x) endw; write(y) 4. Semantica La semantica di un programma While è data in termini di un sistema di transizione [26, 31]. Per definirla, dobbiamo prima definire il concetto di stato. Questo rappresenta la memoria della macchina preposta all’esecuzione di programmi While. La memoria può essere vista come una nastro in cui ogni cella corrisponde esattamente ad una variabile in Var e può contenere esclusivamente valori in DA oppure non contenere alcun valore (essere indefinita o cella vuota)—in tal caso diremo che contiene ⊥. Pertanto, uno stato σ è rappresentabile come una funzione parzialmente definita da variabili in valori σ : Var → DA ∪ {⊥}. L’insieme di tutti gli stati è detto State. Se σ ∈ State, allora il valore dello stato (memoria) σ in corrispondenza alla variabile x ∈ Var è dato dal valore della funzione σ in x, ovvero σ(x) ∈ DA . Un comando ha lo scopo di modificare lo stato corrente. 132 16. CALCOLABILITÀ E LINGUAGGI DI PROGRAMMAZIONE E[[x]]σ = σ(x) E[[d]]σ = d E[[cons(E1 , E2 )]]σ = (E[[E1 ]]σ.E[[E2 ]]σ)   c se E[[E]]σ = (t.c) E[[tl(E)]]σ =  nil altrimenti E[[E1 = E2 ]]σ = (E[[E1 ]]σ = E[[E2 ]]σ)   t se E[[E]]σ = (t.c) E[[hd(E)]]σ =  nil altrimenti Table 1. Semantica delle espressioni La semantica intuitiva dei comandi di While è data nel seguente modo: I programmi hanno variabili di ingresso (read) e uscita (write). Possono usare inoltre quante altre variabili si voglia, prese da Var. I comandi modificano lo stato nel seguente modo: • skip lascia lo stato invariato; • x := Exp memorizza il valore di Exp calcolato nello stato corrente nella cella di memoria rappresentata dalla variabile x; • C1 ; C2 corrisponde all’esecuzione del comando C2 nello stato lasciato dall’esecuzione del comando C1 ; • while E do C endw esegue il comando C fino a che l’albero rappresentato dalla espressione E non è vuoto. La semantica di un programma: read(x1 , . . . , xn ); C; write(y1 , . . . , ym ) è quindi data dalla funzione che associa ad ogni variabile in ingresso x1 , . . . , xn il valore delle variabili in uscita y1 , . . . , ym determinato nello stato modificato da C. Possiamo quindi dare la semantica formale di While induttivamente sulla sintassi come sistema di transizione: Semantica delle espressioni: Ogni espressione rappresenta un albero. Al fine di valutare espressioni contenenti variabili è necessario per la loro valutazione ricorrere alla memoria. La semantica delle espressioni è data dunque da una funzione di interpretazione semantica: E : Exp × State → DA ∪ {⊥} definita come in Tabella 1. Il risultato dell’espressione E1 = E2 può essere false oppure true; queste entità sintattiche possono essere usate liberamente come abbreviazioni degli alberi nil e (nil.nil), rispettivamente. 4. SEMANTICA 133 Semantica dei comandi: La semantica dei comandi è definita induttivamente sulla sintassi dal sistema di transizione in Tabella 2. Le configurazioni del sistema di transizione sono definite da coppie: Com × State. Ogni configurazione hC, σi rappresenta il comando C da eseguire e lo stato σ in cui questo viene eseguito. Il sistema di transizione definisce una relazione −→⊆ (Com × State) × (Com × State) che rappresenta il generico passo di calcolo. Nel seguito, se il comando è vuoto (stringa vuota ε), allora la configurazione corrispondente hε, σi è rappresentata semplicemente come σ. E[[E]]σ=d hx:=E,σi−→ σ[d/x] hskip,σi−→ σ hC1 ,σi−→ σ ′ hC1 ;C2 ,σi−→ hC2 ,σ ′ i hwhile E E[[E]]σ=nil C endw,σi−→ σ do E[[E]]σ6=nil hwhile E do C endw,σi−→ hC;while E do C endw,σi Table 2. Semantica dei comandi Semantica dei Programmi: La semantica di un programma While è definita a partire dalla semantica dei comandi, o meglio dalla chiusura transitiva −→∗ della relazione di transizione dei comandi. Essa definisce una funzione parziale da alberi in alberi, ovvero: [[·]]W : Prog → (DA → DA ∪ {↑}) La semantica [[P]]W di un programma P = read(x1 , . . . , xn ); C; write(y1 , . . . , ym ) è definita come segue, per ogni    e1 , . . . , em   W [[P]] (d1 , . . . , dn ) =     ↑ d1 , . . . , dn ∈ DA : se hC, [d1 /x1 , . . . , dn /xn ]i −→∗ σ ′ e σ ′ (y1 ) = e1 , . . . , σ ′ (ym ) = em altrimenti Esempio 16.3. Come esempio di semantica di un semplice programma While, consideriamo la semantica del programma P dell’Esempio 16.2 che calcola l’inverso di una lista rappresentata come un albero sbilanciato a sinistra. 134 16. CALCOLABILITÀ E LINGUAGGI DI PROGRAMMAZIONE Esercizio 16.4. Il caso del ⊥, o meglio della propagazione del valore indefinito, è trattato in modo incompleto nella Tabella 1. Per esercizio, si pensi a come modificare la semantica delle espressioni in modo da migliorare questo aspetto. Esercizio 16.5. Si fornisca una semantica formale per i comandi: • if Exp then C1 else C2 , dal significato intuitivo seguente: si valuti l’espressione Exp; se è diverso da nil si esegua l’istruzione C1 , altrimenti si esegua l’istruzione C2 . • for x := Exp do C endfor. La semantica intuitiva di questo importante costrutto, ripreso nella Sezione 6, è la seguente: si valuta Exp sul valore dello stato iniziale. Essa sarà un albero che necessita di esattamente n ≥ 0 operazioni di tipo tl per restituire l’espressione nil. L’istruzione C viene quindi ripetuta per n volte; x all’inizio ha il valore iniziale di Exp, poi viene via via ridotto fino a raggiungere x = nil. x e le variabili che occorrono in Exp non possono essere modificate entro C. Esercizio 16.6. Si mostri, scrivendo frammenti di codice While in cui sono introdotte (se servono) ulteriori variabili, che i due costrutti suddetti possono essere definiti all’interno del linguaggio While (in altri termini che il codice scritto ha, sulle variabili originarie, la stessa semantica). Esercizio 16.7. Si mostri come il comando if-then-else dell’esercizio precedente possa essere simulato dal comando for definito nello stesso esercizio. Suggerimento: se α è l’espressione dell’if-then-else, si assegnino le variabili u e v nel seguente modo: u := (α = nil); v := (u = nil); Si eseguano dunque due cicli for: uno controllato da u che esegue C2 e uno da v che esegue C1 . Esercizio 16.8. Sia A = {a, . . . , z}. Si scriva un Programma While che verifica se un elemento x1 di A è presente nell’albero x2 (restituisce y = 1 in caso affermativo, y = 0 altrimenti). 5. Espressività di While e Turing completezza È possibile rappresentare i numeri naturali in DA . La rappresentazione in DA del numero n è denotata n. La seguente definizione illustra uno dei modi a nostra disposizione di codificare i numeri naturali: 0 = nil n + 1 = (nil . n) Utilizzando il cons e la sua semantica, anche cons(nil, cons(nil, . . . , cons(nil, nil) · · · ) {z } | rappresenta il numero n. n 5. ESPRESSIVITÀ DI WHILE E TURING COMPLETEZZA 135 Definizione 16.9. Una funzione f : Nk → N è While-calcolabile se esiste un programma While P tale che per ogni x1 , . . . , xk ∈ N: [[P]]W (x1 , . . . , xk ) = f(x1 , . . . , xk ) Esempio 16.10. Il seguente programma While calcola la funzione f(x, y) = x + y: read(x1 , x2 ); while x2 do x1 := cons(nil, x2 ); x2 := tl(x2 ) endw; write(x1 ) Esercizio 16.11. Si scrivano i programmi While per il calcolo della differenza tra numeri naturali, del prodotto, delle operazioni div, mod, e del test x < y. L’ampiezza della classe delle funzioni While-calcolabili è determinata dal seguente teorema, che conferma la Tesi di Church-Turing anche per i linguaggi di programmazione imperativa. Teorema 16.12. f : N → N è While-calcolabile sse è Turing-calcolabile. Proof. (→) Per induzione sulla sintassi di While, costruiamo una MdT che calcola la medesima funzione calcolata dal corrispondente programma While. Poiché i programmi sono costituiti da sequenze di comandi, è sufficiente indurre sulla complessità sintattica dei comandi. Similmente a quanto fatto nella dimostrazione del Teorema 12.14, poniamo delle restrizioni alle MdT che costruiamo in modo tale da semplificare l’applicazione dell’ipotesi induttiva. (1) Per semplicità, l’alfabeto delle MdT coincide con l’alfabeto della grammatica usata per costruire DA . (2) Tali macchine hanno, prima dell’esecuzione, a sinistra dell’input la descrizione del contenuto delle variabili x1 , x2 , x3 , . . . (solo un numero finito di queste è diverso da 0). Alla fine della computazione (se termina) a sinistra della testina vi sarànno invece i valori finali di tali variabili (poiché all’inizio solo un numero finito di queste era diverso da 0 e la computazione ha per ipotesi lunghezza finita, anche alla fine avremo solo un numero finito di x1 , x2 , x3 , . . . diverse da 0). (3) Ci serviranno MdT per il calcolo di espressioni. In tal caso assumiamo che la parte sinistra del nastro sia come sopra, mentre immediatamente a destra della testina sia memorizzata l’espressione calcolata. (4) Inoltre le macchine sono definite in modo tale da essere indipendenti dal contenuto del nastro sulla parte destra rispetto alla posizione iniziale della testina. 136 16. CALCOLABILITÀ E LINGUAGGI DI PROGRAMMAZIONE Base: Vi sono 3 MdT di base: • Calcolo di una espressione E: In E vi saranno dati da DA e variabili. Si costruisce l’espressione a partire da ciò. [Esercizio], mettendo il risultato subito a destra della posizione finale della testina. • istruzione skip: una MdT che non fa nulla soddisfa a tutti i requisiti. • xi := E. Si parte dal calcolo dell’espressione (vedi sopra). Una volta pronta, si mette il valore dell’espressione calcolata nella posizione della variabile xi , dopo avere spostato a destra i contenuti delle variabili x1 , . . . , xi−1 . Passo: Come caso induttivo, affrontiamo i 3 casi possibili: (1) Supponiamo che la MdT M1 simuli l’istruzione C1 e la MdT M2 simuli l’istruzione C2 : Si attiva la macchina M1 . Se questa termina, si attiva la macchina M2 . (2) Analizziamo il caso del comando while. Supponiamo di avere, per ipotesi induttiva, una MdT MC per il comando C ed una ME per la valutazione della espressione E. La seguente MdT corrisponde alla esecuzione del comando while E do C endw: Si attiva ME . Se sul nastro a destra della testina vi è nil, allora termina. Altrimenti si attiva MC e, qualora termini, si reitera. (→) Sia f una funzione Turing calcolabile. Dal Teorema 12.14, sappiamo che f ∈ PR. La dimostrazione prosegue per induzione sulla struttura delle funzioni parziali ricorsive. Costruiamo ed utilizziamo solo programmi che inizializzano tutte le variabili che usano. Base: • λx. 0: read(x); x := nil; write(x); • λx. x + 1: read(x); x := cons(nil, x); write(x); • λx1 . . . xn . xi : read(x1 , . . . , xn ); skip; write(xi ). Passo: Analizziamo i tre casi induttivi. (1) Supponiamo per ipotesi induttiva che esista un programma Ph : h h read(xh 1 , . . . , xk ); Ch ; write(y ) che calcola la funzione h e Pg1 , . . . , Pgk : read(xg1 i , . . . , xgni ); Cgi ; write(ygi ) che calcolano le funzioni g1 , . . . , gk . Supponiamo inoltre che tali programmi usino tutti variabili diverse (altrimenti le ridenominiamo). Sia µ il massimo indice di variabile usato in quei programmi. Allora la funzione f(x1 , . . . , xk ) = h(g1 (x1 , . . . , xn ), . . . , gk (x1 , . . . , xn )) si calcola nel modo 5. ESPRESSIVITÀ DI WHILE E TURING COMPLETEZZA 137 seguente: read(xµ+1 , . . . , xµ+n ); xg1 1 := xµ+1 ; . . . ; xgn1 := xµ+n ; Cg1 ; .. . gk g1 h xg1 k := xµ+1 ; . . . ; xgnk := xµ+n ; Cgk ; xh 1 := y ; . . . ; xk := y ; Ch ; write(yh ) (2) Sia ora f definita per ricorsione primitiva (per semplicità denotazionale usiamo x invece di x1 , . . . , xn ):   f(x, 0) = g(x)  f(x, y + 1) = h(x, y, f(x, y)) e supponiamo, per ipotesi induttiva, che alle funzioni g ed h corrispondano i programmi: read(xg1 ); Cg ; write(yg ) h h read(xh 1 , x2 , x3 ); Ch ; write(yh ) Supponiamo ancora che tali programmi usino variabili diverse e sia µ il massimo indice di variabile usato. Il seguente programma implementa la funzione f: read(xµ+1 , xµ+2 ); xg := xµ+1 ; Cg ; xµ+3 := yg ; xµ+4 := nil; while xµ+2 do h 3 xh 1 := xµ+1 ; x2 := xµ+4 ; xh := xµ+3 ; Ch ; xµ+2 := tl(xµ+2 ); xµ+4 := cons(nil, xµ+4 ); xµ+3 := yh ; endw; write(xµ+3 ) (3) Sia infine ϕ definita per minimizzazione a partire da una funzione f : Nn+1 −→ N, calcolata da: read(xf1 , . . . , xfn , xfn+1 ); Cf ; write(yf ). 138 16. CALCOLABILITÀ E LINGUAGGI DI PROGRAMMAZIONE e siano x1 , . . . , xn , y, temp variabili non usate in esso. Il seguente programma calcola la funzione ϕ: read(x1 , . . . , xn ); y := nil; x1f := x1 ; . . . ; xfn := xn ; xfn+1 := y; Cf ; while yf do y := cons(nil, y); x1f := x1 ; . . . ; xfn := xn ; xfn+1 := y; Cf ; endw; write(y)  È possibile generalizzare quanto visto per il linguaggio While ad un arbitrario linguaggio di programmazione. Sia L un linguaggio di programmazione che definisce funzioni su un insieme di dati D. Nel seguito con L indicheremo la classe dei programmi sintatticamente corretti scritti nel linguaggio L. Supponiamo che esista una codifica univoca dei numeri naturali in D, ovvero per ogni n: n ∈ D e per ogni n 6= m: n 6= m. Sia [[·]]L la semantica del linguaggio L definita in modo formale:1 [[·]]L : L → (D → D ∪ {↑}) Una funzione f : Nk → N è L-calcolabile se esiste un programma P ∈ L tale che per ogni x1 , . . . , xk ∈ N: [[P]](x1 , . . . , xk ) = f(x1 , . . . , xk ) Definizione 16.13. Un linguaggio di programmazione L è detto Turingcompleto se l’insieme delle funzioni L-calcolabili coincide con la classe delle funzioni Turing-calcolabili. Il linguaggio While è dunque Turing-completo per il Teorema 16.12. In particolare, ogni linguaggio di programmazione sufficientemente espressivo per codificare i numeri naturali e comprendente i comandi di base di assegnamento, composizione ed iterazione condizionata tipo While è Turing-completo. Infatti, per dimostrare la Turing-completezza di un linguaggio è sufficiente dimostrare che questo è in grado di simulare i costrutti di base di While. 1 È noto che ad ogni linguaggio di programmazione, anche complesso e di alto livello, è possibile associare una semantica formale [31, 21]. 6. FOR-CALCOLABILITÀ E FUNZIONI PRIMITIVE RICORSIVE 139 Esercizio 16.14. Una Macchina di Turing si può rappresentare mediante un insieme (lista) di quintuple (lista di 5 elementi). Si scriva un programma While in grado di simulare il comportamento (interpretare) una data macchina di Turing (questa è una dimostrazione alternativa che ogni funzione Turing-calcolabile è While-calcolabile). 6. For-calcolabilità e funzioni primitive ricorsive In questa sezione presenteremo una variante al linguaggio While. Sostituiremo al comando while due comandi, uno di selezione ed uno iterativo molto usati in programmazione: l’if-then-else ed il ciclo for. Nell’esercizio 16.5 tali costrutti sono stati definiti e la loro simulazione utilizzando il while è stata richiesta come (semplice) esercizio. Mostreremo ora come un linguaggio che disponga di questi due costrutti più l’assegnamento permette una espressività pari a quella del formalismo delle funzioni primitive ricorsive. Nelle stesse ipotesi generali dei programmi While, definiamo un programma For mediante una grammatica CF nel modo seguente dove x, y ∈ Var, e d ∈ DA (in particolare, d può essere nil): Exp → x | d | cons(Exp1 , Exp2 ) | hd(Exp) | tl(Exp) | Exp1 = Exp2 Com → x := Exp | Com1 ; Com2 | skip | if Exp then C1 else C2 | for x := Exp do C endfor Prog → read(x1 , . . . , xn ); Com; write(y1 , . . . , ym ) La semantica intuitiva dei due costrutti è definita nell’esercizio 16.5. Inoltre, come mostrato nell’esercizio 16.7, il costrutto if-then-else è simulabile dal for e dunque superfluo (non ne parleremo nelle dimostrazioni). Una funzione f : Nk → N è For-calcolabile se se esiste un programma For P tale che per ogni x1 , . . . , xk ∈ N: [[P]]L (x1 , . . . , xk ) = f(x1 , . . . , xk ) Nel teorema seguente si forniranno delle definizioni più tecniche e precise, ma la sua comprensione e definizione ne può fare a meno. Teorema 16.15. f : Nm −→ N è For-calcolabile se e solo se f è primitiva ricorsiva. Traccia. Per mostrare che una funzione primitiva ricorsiva è For-calcolabile è sufficiente ripetere la seconda parte della dimostrazione del Teorema 16.12 fino al passo induttivo 1. Bisogna mostrare anche il passo induttivo 2 con il nuovo linguaggio. Supponiamo che alla funzione h : N3 −→ N corrisponda il programma: read(x1h , x2h , x3h ); Ch ; write(yh ). Il seguente programma implementa la funzione f 140 16. CALCOLABILITÀ E LINGUAGGI DI PROGRAMMAZIONE definita per ricorsione primitiva: read(n, x); xg := x; Cg ; temp := yg ; for w := n do x1h := temp; x2h := w; x3h := x; Ch ; temp := yh ; endfor; write(temp) Leggermente più tecnico è il viceversa. Dobbiamo mostrare che ogni funzione calcolata da un programma For è calcolabile con una funzione primitiva ricorsiva. Innanzitutto partiamo dalle seguenti assunzioni: • Assumiamo che ogni espressione usata nel programma sia tale che per ogni istanziazione delle variabili in essa in numerali si ottiene un numerale. Questa assunzione è eliminabile grazie all’isomorfismo tra DA e N, ma snellisce la dimostrazione. • Con le stesse motivazioni, assumiamo che ogni variabile non presente tra quelle di input sia immediatamente inizializzata a nil (0). • Assumiamo che un programma usi tutte e sole le variabili v1 , . . . , vp e che le variabili x1 , . . . , xn di input siano le prime n variabili v1 , . . . , vp . In generale si avrà che m ≤ p e n ≤ p (n è il numero di variabili di output). • Ogni comando C è visto come un insieme di p funzioni da Np −→ N. • Le m funzioni calcolate da un programma P read(v1 , . . . , vn ); C; write(vi1 , . . . , vim ) saranno dunque le funzioni fPi1 , . . . , fPim definite nel seguente modo: se fC i1 , . . . , fim sono le funzioni calcolate da C sulle variabili di output, allora, per j = 1, . . . , m fPij (x1 , . . . , xn ) = fC , . . . , xn , 0, . . . , 0) ij (x {z } |1 p ovvero con le variabili del programma non presenti tra gli input inizializzate a 0. Ovviamente, se le fC sono primitive ricorsive, lo saranno anche le fP . 7. INTERPRETI E METAPROGRAMMAZIONE 141 • Per brevità, con x̄ si denota la lista x1 , . . . , xp . Ci si concentra dunque sulle funzioni calcolate dai comandi. Base: skip: Per ogni i = 1, . . . , p si avrà che fC i (x̄) = xi ovvero una proiezione. vj := E: Per ogni i = 1, . . . , p, i 6= j si avrà che fC i (x̄) = xi ovvero una proiezione. Per quanto riguarda fC (x̄), per l’assuzione sopra, o E è un j numerale ℓ ∈ N e dunque fC (x̄) = ℓ (ovviamente p.r.) oppure E è del tipo i cons(nil, · · · , cons(nil, vk ) · · · ) ovvero fC (x̄) = x + ℓ per qualche ℓ intero. k i Anche in questo caso la funzione è p.r. Passo: 1 2 C1 ; C2 : Siano, per ipotesi induttiva, per i = 1, . . . , p le funzioni fC e fC i i . Allora le funzioni associate alla composizione sono, per i = 1, . . . , p: C1 C1 2 fC i (f1 (x̄), . . . , fp (x̄)) for vj := E do C; endfor: Siano, per ipotesi induttiva e per i = 1, . . . , p, le E funzioni fC i (x̄) primitive ricorsive equivalenti al comando C e fi quelle associate al comando vj := E. Definisco allora per i = 1, . . . , p, le funzioni hji (l’apice j ricorda che stiamo parlando della variabile vj ) nel seguente modo:    hji (x̄, 0) = xi i 6= j      hjj (x̄, 0) = 0 j j   hji (x̄, n + 1) = fC  i (h1 (x̄, n), . . . , hp (x̄, n)) i 6= j     hj (x̄, n + 1) = hj (x̄, n) + 1 j j La funzione da associare al comando for, per ogni i, sarà: gi (x̄) = hji (x̄, fE j (x̄))  7. Interpreti e Metaprogrammazione In questa sezione introduciamo il concetto di interprete e di metaprogrammazione. Per metaprogrammazione si intende la costruzione di programmi che manipolano programmi. Questa possibilità, sancita dalla universalità dei sistemi di calcolo Turing-completi, risiede nel fatto che i programmi possono essere codificati in modo univoco nei dati manipolati dai programmi stessi. La MdT universale U è il prototipo di metaprogramma: essa prende in input l’indice x di una data MdT M ed un dato y, e restituisce in output il risultato ottenibile attivando la macchina Mx sul dato y:   ϕ (y) se ϕ (y) ↓ x x U(x, y) =  ↑ altrimenti 142 16. CALCOLABILITÀ E LINGUAGGI DI PROGRAMMAZIONE Traslando questo ragionamento sui linguaggi di programmazione, si ottiene il concetto di interprete. Siano L ed S due linguaggi di programmazione Turingcompleti; assumiamo che operino sul medesimo insieme di dati D. Definizione 16.16. Un programma int ∈ L tale che, per ogni S-programma P e per ogni dato d ∈ D: [[int]]L (P, d) = [[P]]S (d) è un interprete in L di S-programmi (o semplicemente di S). Ovviamente, se [[P]]S (d) ↑, allora anche [[int]]L (P, d) ↑. L’esistenza di un interprete è assicurata dalla Turing-completezza dei linguaggi in oggetto. In altri termini in L si deve simulare l’esecuzione del S-programma P sull’input d istruzione per istruzione. Per far ciò ci si baserà sulla semantica (formalmente definita) del programma P nel linguaggio S. Un interprete in L per L programmi è detto metainterprete del linguaggio L. Vedremo nel dettaglio come realizzare un metainterprete del linguaggio While. Per altri linguaggi Turing-completi, il lavoro da fare sarà analogo. Innanzitutto è necessario rappresentare in DA i programmi While. A tal fine definiamo un insieme finito di atomi: A = {‘‘ := ′′ , ‘‘var ′′ , ‘‘while ′′ , ‘‘cons ′′ , ‘‘tl ′′ , ‘‘hd ′′ , ‘‘nil ′′ , ‘‘; ′′ , ‘‘quote ′′ } In DA possiamo codificare i numeri naturali, mediante sintassi concreta: n = (nil.(nil. · · · (nil. nil) · · · )) {z } | n Assumendo che l’insieme delle variabili Var = {v0 , v1 , v2 , . . . }, useremo la seguente codifica per codificare un insieme infinito di variabili: la variabile vi è codificata da (var.i). Useremo poi una parola chiave per ogni costrutto, espressione o comando, previsto nella sintassi di While. Useremo quote per dire che l’espressione (un albero d privo di variabili) non necessita di essere ulteriormente codificata. La seguente funzione : While −→ DA di traduzione da programmi While ad alberi DA , è definita induttivamente sulla sintassi in tabella 3. Se vi sono più variabili di ingresso/uscita si userà una lista del tipo: lista ((var.1).((var.2). · · · (var.n)) · · · ). Se P è un programma While, allora P ∈ DA è detta sintassi concreta di P. Ad esempio, la sintassi concreta del programma 7. INTERPRETI E METAPROGRAMMAZIONE read(vi ); C; write(vj ) = C1 ; C2 = while E do C endw = vi := E = vi = d = cons(E1 , E2 ) = 143 ((var.i).(C.(var.j))) (; .(C1 .C2 )) (while(E.C)) (:= .((var.i).E)) (var.i) (quote.d) (cons.(E1 .E2 )) hd(E) = (hd.E) tl(E) = (tl.E) E1 = E2 = (= .(E1 .E2 )) Table 3. nell’Esempio 16.3, assumendo x come v1 e y come v2 è data dal seguente albero: ( (var.1). ( (; . ( (:= .((var.2).(quote nil))). ( (while.((var.1). (; . ( (:= .((var.2).(cons (hd (var.1)(var.2)))). (:= .((var.1).(tl (var.1)))) ). (var.2) )) Teorema 16.17. Esiste un interprete in While per While. Proof. Scrivere l’interprete per esercizio, utilizzando i costrutti di While, eventualmente estesi con costrutti condizionali tipo case. Suggerimento: utilizzare le strutture dati ad albero per rappresentare la sintassi concreta dei programmi ed una pila implementata con una lista per la valutazione delle espressioni.  La possibilità offerta da un linguaggio Turing-completo della metaprogrammazione è alla base della esistenza di problemi algoritmicamente non risolvibili. I limiti di ciò che è calcolabile nascono quindi dalle potenzialità del sistema di calcolo stesso. Cosı̀ come l’indecidibilità della terminazione è dimostrata utilizzando funzioni universali, daremo una dimostrazione della indecidibilità della terminazione utilizzando il linguaggio While, senza ricorrere all’aritmetizzazione dello stesso. 144 16. CALCOLABILITÀ E LINGUAGGI DI PROGRAMMAZIONE def Supponiamo esista un programma halt = read(x1 , x2 ); C; write(y) ∈ While, tale che:   (nil.nil) [[P]]W (d) ↓ [[halt]]W (P, d) =  nil [[P]]W (d) ↑ Definiamo il programma R ∈ While nel modo seguente: read(x); x1 := x; x2 := x; C; while y do y := y endw; write(y) Sia dunque R la rappresentazione in D del programma R. • Se [[R]]W (R) ↓, allora l’output di C, che termina sempre per ipotesi, è y = (nil.nil). Ma allora il programma R entra in loop nel ciclo while. Questo implica che [[R]]W (R) ↑. • Se [[R]]W (R) ↑ allora l’output di C è nil. Ma allora il programma R non entra nel ciclo while e dunque termina restituendo nil. Dunque [[R]]W (R) ↓. Entrambi i casi portano ad un assurdo. 8. Specializzatori e Proiezioni di Futamura Dalla Turing-completezza dei linguaggi di programmazione derivano altri strumenti importanti per la moderna programmazione di sistemi complessi. Abbiamo già visto che la Turing-completezza assicura l’esistenza di programmi universali, ovvero di interpreti. Vedremo ora che essa permette di implementare programmi che operano come specializzatori di programmi. L’esistenza di questi programmi segue dal Teorema s-m-n, la cui dimostrazione segue a sua volta dalla possibilità di implementare programmi che manipolano programmi. Supponiamo che L, S e T siano linguaggi di programmazione Turing-completi operanti sul medesimo insieme di dati D. Definizione 16.18. Un programma spec scritto in un linguaggio di implementazione L è uno specializzatore spec ∈ L se per ogni programma P scritto in un linguaggio di programmazione S e per ogni porzione di suo input s ∈ D, restituisce un T -programma: [[spec]]L (P, s) tale che per ogni dato d ∈ D: [[[[spec]]L (P, s)]]T (d) = [[P]]S (s, d). Dunque uno specializzatore non interpreta un programma ma ne restituisce uno nuovo sulla base di uno esistente (in un dato linguaggio) e di una parte del suo input. Il suo compito è simile a quello della funzione ricorsiva totale del Teorema 8. SPECIALIZZATORI E PROIEZIONI DI FUTAMURA 145 s-m-n. Per dimostrare l’esistenza di specializzatori, dimostriamo il Teorema s-m-n nel caso dei programmi While. Teorema 16.19 (Teorema s-m-n). Esiste un programma While spec tale che per ogni programma While P e per ogni porzione del suo input s ∈ D, [[spec]](P, s) è un programma While e per ogni d ∈ D vale che: [[[[spec]](P, s)]](d) = [[P]](s, d) Proof. Vediamo cosa dovrebbe fare il programma spec. Dato P: read(x1 , x2 ); C; write(y) e s ∈ D deve restituire il programma: read(x2 ); x1 := s; C; write(y). E’ immediato scrivere un programma che fa ciò in While (ma anche in ogni altro linguaggio di programmazione).  Dunque esiste uno specializzatore all’interno della classe dei programmi While. Cerchiamo di generalizzare la cosa a tre linguaggi generici S, T , e L. Per la definizione e l’esistenza di un interprete in T per L, so che: [[P]]S (s, d) = [[int]]T (P, s, d) Per il Teorema s-m-n (dimostrato in T ), vedendo P ed s come input per int so che esiste uno specializzatore spec tale che: [[int]]T (P, s, d) = [[[[int]]T (int, P, s)]]T Ora, poiché int è fissato a priori, lo posso anche togliere dai parametri di input. Inoltre tale programma di conversione (che nella dimostrazione sopra avevamo scritto in While) lo si può sicuramente scrivere con un altro linguaggio Turing completo (quello con cui preferiamo scrivere gli algoritmi—p.es., il C o il Prolog). Dalla combinazione di interpreti int e specializzatori spec è possibile costruire una famiglia di programmi assai complessi e utili, quali i compilatori e programmi che generano compilatori. Per ottenere questi strumenti a supporto della programmazione, utilizziamo le proiezioni di Futamura [15]. Esse permettono di costruire semplici compilatori e programmi che generano compilatori a partire dai concetti e dall’esistenza di interpreti e specializzatori. Chiaramente, più è sofisticato lo specializzatore, es. programmi per la ottimizzazione del codice, più è possibile costruire in questo modo strumenti di compilazione sofisticati ed utili. Nei tre risultati sotto riportati assumiamo che S, T , L, I siano linguaggi di programmazione Turing-completi operanti sul medesimo insieme di dati D. Teorema 16.20 (I proiezione di Futamura). Dato un programma source ∈ S possiamo generare un programma equivalente target ∈ T . 146 16. CALCOLABILITÀ E LINGUAGGI DI PROGRAMMAZIONE Proof. Dimostreremo che tale programma è: def target = [[spec]]I (int, source) ∈ T Sia spec uno specializzatore, scritto in I di L programmi in T programmi. Allora: [[source]]S (d) = [[int]]L (source, d) = [[[[spec]]I (int, source)]]T (d) = T Per Def. di Interprete in L Per Def. di Specializzatore Per Def. di target [[target]] (d)  Dunque, dato uno specializzatore spec, assegnandogli come input l’interprete di S programmi scritto in L e il programma source, restituisce il T programma desiderato. Teorema 16.21 (II proiezione di Futamura). Possiamo generare un compilatore da S a T scritto in T . def Proof. Dimostreremo che: comp = [[spec]]L (spec, int). Sia target ∈ T come stabilito dalla I proiezione. Supponiamo che I = L (uso lo stesso linguaggio per implementare). Allora: target = [[spec]]L (int, source) = [[[[spec]]L (spec, int)]]T (source) = [[comp]]T (source) Per definizione di specializzatore  Dunque, per generare un compilatore, basta prendere uno specializzatore e mettere come input il suo codice e quello di un interprete. Il terzo risultato di Futamura riguarda i generatori di compilatori: Teorema 16.22 (III proiezione di Futamura). Possiamo generare un generatore di compilatori scritto in T . Proof. Dimostreremo che il generatore di compilatori è: def compgen = [[spec]]L (spec, spec) Anche in questo caso assumiamo che I = L. Dalla definizione di specializzatore e dalla II proiezione segue che: comp = [[spec]]L (spec, int) = [[[[spec]]L (spec, spec)]]T (int) = [[compgen]]T (int)  8. SPECIALIZZATORI E PROIEZIONI DI FUTAMURA 147 Dunque compgen accetta come input un interprete e genera un compilatore. Esercizio 16.23. Dimostrare che compgen = [[compgen]](spec). Part 3 Teoria matematica della ricorsione CHAPTER 17 Insiemi ricorsivi e ricorsivamente enumerabili Abbiamo visto nel Cap. 10 diverse formalizzazioni per definire ciò che noi riteniamo effettivamente calcolabile. Queste comprendono le MdT, le funzioni parziali ricorsive di Kleene & Robinson, ed i linguaggi di programmazione imperativa, tipo While. In questo capitolo studieremo alcuni risultati classici della teoria della ricorsività, indipendentemente dal sistema formale adottato per esprimere algoritmi, o funzioni calcolabili. L’equivalenza tra le diverse definizioni di calcolabilità e la Tesi di Church-Turing ci permettono di studiare l’effettiva calcolabilità in un modo per cosı̀ dire “più astratto”. In particolare la Tesi di Church-Turing ci permette di astrarre dalla particolare MdT o programmi while, e trattare in modo formale il concetto (informale) di funzione effettivamente calcolabile, indipendentemente dal formalismo adottato per rappresentarla. Nel seguito assumeremo di avere fissato una data enumerazione {ϕi }i∈N delle MdT o delle funzioni parziali ricorsive o dei programmi While. Il materiale contenuto in questa parte è tratto in larga parte dai testi di riferimento [27, 2, 19, 15, 22]. In questo capitolo studieremo le proprietà ricorsive di insiemi di numeri naturali. La scelta di studiare insiemi di numeri naturali non è affatto limitativa per gli scopi dell’informatica. Tutti i dati manipolabili da un algoritmo sono infatti numerabili e possono quindi essere messi in corrispondenza biunivoca con l’insieme N. Un analogo ragionamento si può applicare ad insiemi di stringhe su di un alfabeto, ovvero ad un linguaggio, o ad insiemi di alberi sintattici, rappresentanti While-programs. È infatti noto l’isomorfismo tra N e rispettivamente Σ∗ e DA , essendo Σ un dato alfabeto e A un dato insieme di atomi. Da questo consegue che studiare proprietà degli elementi di ℘(N) equivale a studiare le proprietà degli elementi di ℘(Σ∗ ) e ℘(DA ), e che ogni definizione/proprietà che vedremo per insiemi di naturali, si applica senza restrizione alcuna a linguaggi (ovvero elementi di ℘(Σ∗ )) e a insiemi di alberi sintattici. In questo senso, il passaggio da funzioni ad insiemi è naturale e rappresenta una astrazione successiva verso una più astratta definizione di problema ricorsivamente risolvibile. Questo ci permetterà di studiare, analogamente a quanto visto per le funzioni calcolabili, insiemi che risultano essere effettivamente costruibili, ovvero i cui elementi possono essere determinati in modo algoritmico. 151 152 17. INSIEMI RICORSIVI E RICORSIVAMENTE ENUMERABILI Consideriamo un insieme I ⊆ N. Ci chiediamo se I è costruibile in modo algoritmico, ovvero se è possibile generare gli elementi di I mediante una funzione che sia effettivamente calcolabile. Definizione 17.1. Un insieme I ⊆ N è detto ricorsivamente enumerabile (r.e.) se esiste una funzione parziale ricorsiva ψ tale che I = dom(ψ). Nel seguito indicheremo con RE ⊆ ℘(N) la classe degli insiemi ricorsivamente enumerabili. Pertanto A ∈ RE sse A è r.e. Inoltre, nel seguito indicheremo con Wx l’insieme r.e. associato alla funzione ϕx secondo una opportuna Gödelizzazione, ovvero Wx = dom(ϕx ) essendo ϕx l’x-esima MdT nella numerazione fissata. Sia A ∈ RE. La funzione parziale:   1 se x ∈ A ψA (x) =  ↑ se x 6∈ A è detta funzione semicaratteristica dell’insieme A. È chiaro che A = dom(ψA ). Inoltre, essendo per ipotesi A r.e. , la verifica della condizione x ∈ A è semidecidibile, ovvero se y è tale che A = Wy , x ∈ A sse ϕy (x) ↓. Sia dunque def Py = read x; C; write z un programma While equivalente a ϕy . È naturale definire il seguente programma: read(x); C; z := 1; write(z). Questo programma corrisponde ad una MdT, ed implementa la funzione ψA . Pertanto abbiamo dimostrato che un insieme è detto r.e. sse ha una funzione semicaratteristica parziale ricorsiva. All’interno della famiglia degli insiemi r.e. , esistono insiemi con caratteristiche più forti. Per questi insiemi vale la condizione aggiuntiva che la loro funzione caratteristica (che è sempre una funzione totale) è ricorsiva, ovvero esiste un algoritmo in grado di decidere l’appartenenza o meno di un elemento all’insieme. Definizione 17.2. Un insieme A è detto ricorsivo se esiste una funzione ricorsiva (totale) fA tale che:   1 se x ∈ A fA (x) =  0 se x 6∈ A Esempio 17.3. I seguenti sono insiemi ricorsivi: • L’insieme {0, 2, 4, 6, . . .} dei numeri pari; • N e ∅; • Ogni insieme finito (Dimostrare per esercizio); • Ogni insieme A tale che Ā è finito (Dimostrare per esercizio). Esisteranno dunque ℵ0 insiemi r.e. di cui una parte di uguale cardinalità sarà ricorsiva. Si nota infatti subito della definizioni che A ricorsivo ⇒ A ∈ RE 17. INSIEMI RICORSIVI E RICORSIVAMENTE ENUMERABILI 153 Dato A insieme ricorsivo, basta infatti definire la funzione parziale e chiaramente ricorsiva:   1 se f (x) = 1 A ϕ(x) =  ↑ altrimenti Esercizio 17.4. Dimostrare che i seguenti insiemi sono ricorsivi:  • x ∈ N : ∃n∃p primo . x = pn ; • hx, yi ∈ N × N : x4 + y6 = 1238 ; • x ∈ N : ∃n. x = 2n + 1 . Teorema 17.5 (Teorema di Post). Un insieme A è ricorsivo se solo se A e Ā sono r.e. Proof. Sia A ⊆ N e Ā = N \ A. (⇒) Sia A ricorsivo e fA la sua funzione caratteristica (totale) ricorsiva. Definiamo:   1 se f (x) = 1 A ψ(x) =  ↑ altrimenti È chiaramente una funzione parziale ricorsiva. Ad esempio il seguente frammento def di programma implementa ψ: Sia FA = read x; CA ; write y. G: read x; CA ; while y = nil do y := y endw; write y A ha quindi una funzione semicaratteristica parziale ricorsiva. Analogo discorso si applica a Ā, essendo fĀ = 1 − fA la corrispondente funzione caratteristica. Abbiamo pertanto dimostrato che A e Ā sono r.e. (←) Supponiamo A e Ā r.e. , con funzioni semicaratteristiche ψA e ψĀ . Definiamo:   1 se ψ (x) ↓ A f(x) =  0 se ψ (x) ↓ Ā È banale osservare che, dato x ∈ N: o x ∈ A o x ∈ Ā, allora eseguendo a turno un’istruzione della MdT che calcola ψA e un’istruzione della MdT che calcola ψĀ , prima o poi una delle due MdT termina, rendendo la funzione f totale ricorsiva.  Si osservi come la ricorsività di N segua anche dal fatto che sia N̄ = ∅ che N sono r.e. 154 17. INSIEMI RICORSIVI E RICORSIVAMENTE ENUMERABILI Passi ϕx (0) ϕx (1) ϕx (2) ϕx (3) ... ◦ ◦?? ◦?? ◦??             ◦   ◦??     ◦??         ??   ◦ ◦        ◦     ϕx (2) ↓  ◦ •?? Argomenti .. . Figure 1. Dovetail: ϕx (2) converge dopo 2 passi Il seguente teorema caratterizza gli insiemi r.e. come la famiglia degli insiemi che possono essere effettivamente enumerati da un algoritmo, ovvero i cui elementi possono essere “listati” in modo effettivo come “output” di un programma. Gli insiemi r.e. possono dunque essere visti come liste, eventualmente infinite, di elementi generabili da un algoritmo. Il teorema di caratterizzazione fa uso di una metodologia effettiva per esplorare il campo di convergenza di una funzione parziale, ovvero l’insieme degli elementi su cui una funzione parziale converge (è definita). Questa metodologia per esplorare il campo di convergenza di una funzione parziale è detta a coda di colomba (dovetail) e sarà spesso utilizzata nel seguito. In essa ha un ruolo essenziale il concetto di passo di calcolo o derivazione. Come abbiamo visto nel Cap. 10 ogni sistema formale introdotto definisce il concetto di calcolo come sequenza discreta di passi elementari. Pertanto, il procedimento a dovetail è perfettamente accettabile come metodo algoritmico per esplorare il campo di definizione (o convergenza) di una funzione parziale ricorsiva. Sia dunque ϕx una funzione parziale ricorsiva. Applicare la tecnica dovetail a ϕx significa costruire l’insieme L in modo induttivo, tale che: Passo 0: Si faccia un passo di calcolo per ϕx (0), e se termina allora L := {ϕx (0)}; Passo n + 1: Si faccia un passo nel calcolo di tutte le funzioni ϕx (0), . . . , ϕx (n) ove il calcolo non sia già terminato al passo precedente, e per ogni m tale che ϕx (m) converge, si pone L := L ∪ {ϕx (m)}. In pratica si esplora il grafico della funzione seguendo un andamento a zig-zag, come evidenziato nella figura 1. 17. INSIEMI RICORSIVI E RICORSIVAMENTE ENUMERABILI 155 L’insieme L è stato dunque effettivamente costruito ed i suoi valori corrispondono al codominio definito di ϕx . Siamo ora in grado di dimostrare il seguente teorema di Kleene di caratterizzazione degli insiemi r.e. Teorema 17.6 (Caratterizzazione degli insiemi r.e. ). Le seguenti affermazioni sono equivalenti: (1) A ∈ RE; (2) esiste ϕ ∈ PR: A = range(ϕ); (3) A = ∅ oppure esiste una funzione f ∈ R tale che A = range(f). Proof. Consideriamo una data enumerazione delle MdT (o delle funzioni parziali ricorsive) {ϕx }x∈N . (1 ⇒ 2) Sia A ∈ RE. Per definizione, esisterà ϕx parziale ricorsiva t.c. A = dom(ϕx ). Definiamo:   y se ϕ (y) ↓ x δ(y) =  ↑ altrimenti δ è parziale ricorsiva e range(δ) = dom(ϕx ) = A. (2 ⇒ 3) Sia A = range(ϕx ) e A 6= ∅. Applicando il dovetail alla funzione:   ϕ (y) se ϕ (y) ↓ x x ψ(y) =  ↑ altrimenti poiché A = range(ϕx ) 6= ∅, esiste n0 ∈ N tale che all’n0 -esimo stadio del dovetail si osserva una terminazione. Definiamo la funzione f induttivamente sullo stadio n-esimo del dovetail, in modo tale che f abbia valori tutti e soli nella lista L generata dal dovetail di ψ. • Se ψ converge in {m0 , . . . , mj0 } con 0 ≤ j0 ≤ n0 allora per ogni i ∈ [0, j0 ]: f(i) = ψ(mi ); • Supponiamo che per definire f(jp ) si sia usato lo stadio np -esimo del dovetail. Possiamo avere i seguenti casi: – Se nell’np + 1-esimo passo del dovetail non si rivelano nuove terminazioni, allora f(jp + 1) = f(jp ). – Se nell’np +1-esimo passo del dovetail ψ converge con input {p0 , . . . , pk } con 0 ≤ k ≤ np + 1, allora per ogni i ∈ [0, k]: f(jp + 1 + i) = ψ(pi ). • Iteriamo il precedente punto ponendo jp+1 = jp + 1 + k. La procedura che abbiamo descritto è effettiva e definisce una funzione ricorsiva totale f tale che A = range(f). (3 ⇒ 1). Se A = ∅ allora A = dom(λx. ↑). Sappiamo che λx. ↑∈ PR, e quindi A è r.e. Se A = range(f) con f ∈ R, basta porre: ϕ(x) = µz.(f(z) − x = 0) ϕ ∈ PR ed inoltre è immediato osservare che ϕ(x) ↓ ⇔ ∃z. f(z) = x, quindi A = dom(ϕ) che dimostra che A è r.e.  156 17. INSIEMI RICORSIVI E RICORSIVAMENTE ENUMERABILI Nota 17.7. Nel testo di Rogers [27] la definizione di insieme r.e. viene data con la caratterizzazione (3) del Teorema appena visto. Tale caratterizzazione spiega anche il nome “ricorsivamente enumerabile” ovvero enumerabile mediante l’impiego di una funzione ricorsiva. Nei testi più recenti (p. es., [15, 6]) si adotta invece la definizione 17.1. Il Teorema sancisce che le due sono del tutto equivalenti. Esempio 17.8. N è r.e. , poiché λx. x ∈ R e N = range(λx. x). Corollario 17.9. Esiste una funzione totale ricorsiva f tale che Wx = range(ϕf(x) ). È chiaro che non è detto che ϕf(x) sia totale, pur essendolo f. Teorema 17.10. Le seguenti affermazioni sono equivalenti: (1) A è ricorsivo; (2) A = ∅ oppure A = range(f) con f ∈ R non decrescente. Proof. (⇒). Sia A 6= ∅ e A ricorsivo. Poiché A ⊆ N, esiste a = min(A) minimo valore tra quelli contenuti in A. Definiamo: f(0) f(n + 1) = a   n + 1 se n + 1 ∈ A =  f(n) altrimenti È chiaro che f è totale ricorsiva non decrescente e che A = range(f). (←). Se A è finito, allora A è banalmente ricorsivo. Supponiamo che A sia infinito e A = range(f) con f totale ricorsiva non decrescente. Sia x ∈ A e zx = µy. (f(y) > x). È chiaro che un tale zx esiste (perchè?) e che x ∈ A ⇔ x ∈ {f(0), . . . , f(z)} È quindi decidibile l’appartenenza di x ad A, ovvero A è ricorsivo (definire la funzione caratteristica di A).  Teorema 17.11. Ogni insieme r.e. infinito ha un sottoinsieme ricorsivo infinito. Proof. Sia A ⊆ N tale che A = range(f) con f funzione ricorsiva totale e |A| = ω. Definiamo la funzione g tale che: g(0) g(n + 1) = f(0) = f(µy. f(y) > g(n)). g(n + 1) è dunque il più piccolo elemento di A più grande di g(n). g è crescente, quindi range(g) è ricorsivo per il Teorema 17.10. Inoltre range(g) ⊆ A, poiché per definizione di g: range(g) ⊆ range(f) = A.  Esercizio 17.12. Si dica se i seguenti insiemi sono ricorsivi o r.e. : 17. INSIEMI RICORSIVI E RICORSIVAMENTE ENUMERABILI 157 (1) A = range(f) con = n0 f(0) f(n + 1) = 2f(n). (2) B = range(g) con g(0) = n0 g(n + 1) = h(n)g(n). essendo h una generica funzione ricorsiva (totale). (3) Data una funzione ricorsiva f, dimostrare che  l’immagine inversa di f e Wx , con x ∈ N, ovvero l’insieme f−1 (Wx ) = y : f(y) ∈ Wx , è r.e. (4) Sia D = range(ψ) con: ψ(0) = ψ(n + 1) = n0 ϕn (n)ψ(n). D è ricorsivo? Fino a questo momento abbiamo visto insiemi ricorsivi e r.e. Tuttavia non abbiamo ancora visto un esempio di un insieme che sia r.e. ma non ricorsivo. Questo insieme (se esiste) rappresenta l’insieme che discrimina le due classi di insiemi che abbiamo definito. È chiaro che un insieme di questo tipo deve essere r.e. ma non ricorsivo, ovvero l’appartenenza ad esso deve essere un predicato semidecidibile. È naturale chiedersi se questo insieme possa essere in qualche modo correlato alla non decidibilità della terminazione. Al solito, fissiamo una numerazione delle MdT o delle funzioni parziali ricorsive {ϕx }x∈N .  Definizione 17.13. Definiamo K = x : ϕx (x) ↓ .  È chiaro per la definizione sopra che K = x : x ∈ Wx 1 . Teorema 17.14. K è r.e. ma non ricorsivo. 1 Questo insieme ricorda l’insieme utilizzato per contraddire la teoria classica degli insiemi, ovvero il Paradosso di Russell [28]. Se infatti fosse possibile definire un insieme del tipo: M = x : x 6∈ x allora otterremmo immediatamente un paradosso poiché M ∈ M sse M 6∈ M. L’idea utilizzata per risolvere questo paradosso nella teoria degli insiemi è quella di non ammettere M come insieme, ovvero di definire una “disciplina” per la costruzione di insiemi che vieti una costruzione tipo M. Si osservi come la teoria della ricorsione è immune da questo tipo di paradossi. Infatti K̄ = x : x 6∈ Wx , l’insieme dei numeri non appartenenti agli insiemi r.e. da essi generati, non può essere r.e. , altrimenti, essendolo K per il Teorema 17.14, K sarebbe ricorsivo (Teorema di Post) il che per il Teorema 17.14 è assurdo. Quindi gli insiemi r.e. non includono K̄, ovvero non esiste y0 tale che K̄ = Wy 0 158 17. INSIEMI RICORSIVI E RICORSIVAMENTE ENUMERABILI ✬ ✬ ℘(N) · ✬ r.e. K · ✬ Ricorsivi ✫ ✩ ✩ N ✩ ∅ ✪ ✪ ✪ ✪ · Finiti ✫ ✫ ✫ K̄ ✩ · Figure 2. La gerarchia degli insiemi Ricorsivi Proof. definiamo la funzione ψK tale che:   1 se ϕ (x) ↓ x ψK =  ↑ altrimenti ψK è parziale ricorsiva (perchè?) e semicaratteristica di K, quindi K è r.e. Supponiamo K ricorsivo. Allora K̄ è r.e. Sia y0 tale che K̄ = Wy0 . Segue che: che è chiaramente assurdo. y0 ∈ Wy0 = K̄ ⇔ y0 ∈ K  Esercizio 17.15.  (1) Dimostrare che K2 = hx, yi : x ∈ Wy è r.e. ma non ricorsivo; (2) Dimostrare che le seguenti proprietà caratterizzano gli insiemi finiti; • tutti i sottoinsiemi sono r.e. • tutti i sottoinsiemi sono ricorsivi (3) Dimostrare che esistono funzioni ricorsive (totali) f e g tali che: Wx ∩ Wy = Wf(x,y) e Wx ∪ Wy = Wg(x,y) ; (4) Dimostrare che A è ricorsivo infinito sse esiste una funzione f ricorsiva crescente (i.e. ∀n. f(n) < f(n + 1)) tale che A = range(f). Abbiamo quindi la seguente rappresentazione in “classi” di risolvibilità degli insiemi di numeri naturali. La classe degli insiemi ricorsivi corrisponde alla classe degli insiemi decidibili, quella degli insiemi r.e. corrisponde alla classe degli insiemi semidecidibili, mentre gli insiemi non r.e. (che sono la maggior parte di ℘(N)) sono 17. INSIEMI RICORSIVI E RICORSIVAMENTE ENUMERABILI 159 gli insiemi per cui non è possibile dare alcun metodo effettivo per verificare sia l’appartenenza che la non appartenenza di un elemento a questi insiemi. Valgono quindi le seguenti inclusioni, mostrate anche in Figura 2, tra le varie classi di insiemi viste fino ad ora: ricorsivi ⊂ r.e. ⊂ non − r.e. CHAPTER 18 I Teoremi di Ricorsione I teoremi di ricorsione, detti anche di punto fisso, sono tra i risultati più importanti che si incontrano nello studio della teoria della ricorsività. In questo capitolo studieremo i teoremi di ricorsione e le loro conseguenze nello studio di proprietà di programmi. 1. Il primo Teorema di ricorsione Teorema 18.1 (Teorema di ricorsione di Kleene). Sia t ∈ R una funzione ricorsiva (totale). Allora esiste n ∈ N tale che ϕn = ϕt(n) . Proof. Sia u l’indice di una generica MdT ϕu . Definiamo la seguente funzione parziale:   ϕ ϕu (u) (x) se ϕu (u) ↓ ψ(u, x) =  ↑ altrimenti È chiaro che la funzione cosı̀ definita è parziale ricorsiva (si descriva a parole una MdT che “calcola” ψ). Quindi, per il Teorema s-m-n, esiste una funzione ricorsiva (totale) g tale che: ψ(u, x) = ϕg(u) (x). Sia ora t una funzione ricorsiva (totale). Poiché la composizione di funzioni ricorsive è ricorsiva, t ◦ g è ricorsiva. Quindi t ◦ g = ϕv per qualche v. Pertanto t ◦ g(v) = ϕv (v). Vista la genericità di u, sostituendo u con v nella definizione di ψ(u, x) otteniamo:   ϕ t(g(v)) (x) se ϕv (v) = t(g(v)) ↓ ψ(v, x) = ϕg(v) (x) =  ↑ altrimenti Poiché t ◦ g è totale, ϕg(v) (x) = ϕt(g(v)) (x). Posto quindi n = g(v), abbiamo che ϕn = ϕt(n) .  Il significato intuitivo del teorema di ricorsione di Kleene sarà più chiaro in seguito, in particolare nella Sezione 4. Per ora ci basti utilizzarlo come potente metodo per dimostrare proprietà di ricorsività sugli elementi di ℘(N). Vediamo qualche esempio: Esempio 18.2. Esiste un n ∈ N t.c. range(ϕn ) = {n}? Utilizziamo il Teorema di Ricorsione per stabilire ciò. Sia g(x, y) = x. g è calcolabile e totale. Per il 161 162 18. I TEOREMI DI RICORSIONE Teorema s-m-n avremo che esiste h ricorsiva tale che per ogni y: ϕh(x) (y) = g(x, y) Per definizione, range(ϕh(x) ) = {x}. Per il Teorema di ricorsione, si ha che esiste n ∈ N t.c. ϕh(n) (y) = ϕn (y). Pertanto range(ϕn ) = range(ϕh(n) ) = {n}. Esempio 18.3. Esiste un n ∈ N t.c. Wn = {n}? Sia g(x, y) definita come:   0 y=x g(x, y) =  ↑ altrimenti g è calcolabile. Per il Teorema s-m-n avremo che esiste h ricorsiva tale che per ogni y: ϕh(x) (y) = g(x, y) Per definizione, Wh(x) = {x}. Per il Teorema di ricorsione, si ha che esiste n ∈ N t.c. ϕh(n) (y) = ϕn (y). Pertanto Wn = Wh(n) = {n}. Esercizio 18.4. Sia, per i > 0, pi l’i-esimo numero primo (p1 = 2, p2 = 3, . . . ). Si dimostri che esiste un indice u tale che Wu = {p1 , p2 , . . . , p(u!) } e Eu = {1, 2, . . . , u}. Esercizio 18.5. Si mostri che esiste ν ∈ N tale che:   2y ϕν (y)  5 y≤ν y>ν 2. Il secondo Teorema di ricorsione In questo paragrafo enunceremo il secondo teorema di ricorsione che estende il risultato del primo. Utilizzeremo questo teorema per la dimostrazione del teorema di Myhill nel Capitolo 19. Teorema 18.6. Sia f : N2 −→ N ∈ R una funzione ricorsiva (totale). Allora esiste una funzione ν : N −→ N ∈ R ricorsiva totale tale che: ∀y ∈ N : ϕf(ν(y),y) = ϕν(y) . Proof. Definiamo la seguente funzione parziale:   ϕ f(ϕx (x),y) (z) se ϕx (x) ↓ ψ(x, y, z) =  ↑ altrimenti Per il Teorema s-m-n applicato al primo argomento, esiste una funzione calcolabile totale s tale che ψ(x, y, z) = ϕs(x,y) (z) . Si noti che, se ϕx (x) ↓, allora (2.1) ϕs(x,y) = ϕf(ϕx (x),y) 3. IL TEOREMA DI RICE 163 Per il Teorema s-m-n applicato al secondo argomento di s sappiamo che esiste una funzione calcolabile totale t tale che: (2.2) s(x, y) = ϕt(y) (x) Definiamo la funzione def ν(y) = ϕt(y) (t(y)) Poiché sia t che ϕt(y) sono ricorsive e totali, lo sarà anche ν (dunque in particolare ϕν (ν) ↓). Dunque: ϕν(y) = ϕϕt(y) (t(y)) Def di ν = ϕs(t(y),y) (2.2), con x = t(y) = ϕf(ϕt(y) (t(y)),y) = ϕf(ν(y),y) Poiché ϕν (ν) ↓ e (2.1) Def di ν  3. Il Teorema di Rice Uno dei risultati più importanti dimostrabili mediante il teorema di ricorsione è il Teorema di Rice. Esso afferma che ogni proprietà non banale che rappresenti ciò che viene calcolato dalle MdT (o dai programmi, vedi Sezione 4) non è ricorsiva. Definiamo innanzitutto cosa si intende per proprietà sulle MdT. Sia al solito {ϕi }i∈N una enumerazione delle MdT. Definizione 18.7. Una proprietà sulle MdT è un qualsiasi sottoinsieme di {ϕi }i∈N (con abuso di notazion, qualsiasi sottoinsieme di N). Una proprietà sulle MdT è pertanto univocamente identificata dall’insieme degli indici delle MdT in essa contenuti. Nel seguito quindi non faremo distinzione tra proprietà intesa come insieme di indici (e quindi di numeri) di MdT o proprietà intesa come insieme di MdT. Nel primo caso, ovviamente potremo parlare di proprietà ricorsive (o decidibili), r.e. (o semidecidibili), e proprietà non r.e. Definizione 18.8. Sia dunque Π una proprietà sulle MdT, Π ⊆ N. Π è estensionale (o chiusa per eguaglianza estensionale) se per ogni x, y ∈ N: x ∈ Π ∧ ϕx = ϕy ⇒ y ∈ Π Intuitivamente, una proprietà è estensionale quando questa “parla” di cosa calcolano le MdT in essa contenute, e non di come queste macchine sono “fatte”. Il seguente risultato sancisce l’esistenza di un numero arbitrario di MdT tra loro equivalenti. Lemma 18.9 (Tecnica del Padding). ∀i, k. ∃j : (ϕj = ϕi ∧ j > k). 164 18. I TEOREMI DI RICORSIONE Proof. Siano dati i e k e sia ϕi l’i-esima MdT. Se i > k allora ponendo j = i si ha la tesi. Se i ≤ k allora è sufficiente aggiungere a ϕi un numero congruo di quintuple (con stati mai raggiungibili dal calcolo) in modo tale che si ottenga ϕj con j > k.  Esercizio 18.10. Si dimostri una proprietà analoga per i programmi While. Mediante la tecnica del padding si osserva che esistono infinite MdT equivalenti ad una MdT data. Pertanto, una proprietà estensionale sulle MdT non può che essere un insieme infinito. Esercizio 18.11. Si dimostri che le seguenti proprietà sono estensionali: • • • • ∅ e N. In particolare, tali proprietà sono dette banali;  Dato F ⊆ PR, i : ϕ = ψ ∧ψ ∈F ; i  i : ϕ = λx. x ; i  i : ϕi è definitivamente ≥ 3 . Al contrario, la proprietà {157} che include solo la MdT di indice 157 non è estensionale. Infatti esistono infinite MdT equivalenti ad una data MdT (Lemma 18.9), e pertanto l’insieme {157} non può essere estensionale. Teorema 18.12 (Teorema di Rice). Sia Π una proprietà estensionale. Π è ricorsiva sse è banale (ovvero Π = ∅ oppure Π = N). Proof. La direzione (←) segue per definizione. (→) Sia Π ricorsivo e g la sua funzione caratteristica:   1 se x ∈ Π g(x) =  0 se x 6∈ Π Supponiamo per assurdo che Π non sia banale. Allora esistono a, b ∈ N tali che a ∈ Π e b 6∈ Π. Definiamo la funzione   b se g(x) = 1 h(x) =  a se g(x) = 0 Essendo g ricorsiva per ipotesi, chiaramente h è ricorsiva (e totale). Per il Teorema di ricorsione di Kleene, esiste n0 tale che ϕn0 = ϕh(n0 ) . Da questo fatto deriviamo l’assurdo: • Se n0 ∈ Π allora essendo Π estensionale, h(n0 ) ∈ Π, ma questo è assurdo poiché h(n0 ) = b e b 6∈ Π. • Se n0 6∈ Π allora essendo Π estensionale, h(n0 ) 6∈ Π, ma questo è assurdo poiché h(n0 ) = a e a ∈ Π. Quindi Π non può essere ricorsivo e non banale.  3. IL TEOREMA DI RICE 165 Il Teorema di Rice dunque afferma che ogni funzione parziale ricorsiva o MdT ammette un insieme infinito non ricorsivo di indici di funzioni o macchine equivalenti. In particolare osserviamo che i seguenti problemi, di chiaro interesse informatico, non sono decidibili in seguito al Teorema di Rice. Esempio 18.13. Data ψ ∈ PR, ed una MdT M, non è in generale decidibile se M “calcola” ψ. Infatti se  j è l’indice della MdT M, allora questo problema equivale a decidere se j ∈ i : ϕi = ψ che è una proprietà estensionale non banale ( i : ϕi = ψ 6= ∅ poichéesiste una MdT che calcola la funzione parziale ricorsiva ψ essendo ψ ∈ PR e i : ϕi = ψ 6= N poiché non tutte le MdT calcolano la stessa funzione) delle MdT, e quindi per il Teorema di Rice è una proprietà non ricorsiva. Esempio 18.14. Date M1 e M2 , non è decidibile se esse sono equivalenti (ovvero calcolano la stessa funzione). Dimostrare questo per esercizio applicando il teorema di Rice. È necessario fare molta attenzione ad utilizzare il Teorema di Rice. Esso infatti ha come ipotesi il fatto che l’insieme di cui si vuole studiare la ricorsività sia estensionale. Alcuni insiemi possono trarre in inganno, come nel seguente esempio. Esempio 18.15. Il seguente esempio, ideato da G. Longo [19], non può essere risolto invocando il Teorema di Rice. Definiamo una nozione debole di equivalenza tra MdT (o programmi) come segue: ϕi ∼ ϕj ⇔ (∀x. (ϕi (x) ↓ ∧ϕj (x) ↓ ⇒ ϕi (x) = ϕj (x))) Ci chiediamo se l’insieme D = range(ψ) con ψ(i) = µj. (ϕi ∼ ϕj ) è ricorsivo. Si osservi che la funzione sempre indefinita λx. ↑ è debolmente equivalente ad ogni altra funzione parziale ricorsiva, ovvero, per ogni i ∈ N: ϕi ∼ ϕu con u indice della più piccola MdT nella numerazione delle MdT {ϕn }n∈N , tale che ϕu = λx. ↑. Quindi, D ⊆ {0, . . . , u} è un insieme finito e quindi ricorsivo. Tuttavia interpretando erroneamente la proprietà come estensionale, l’utilizzo del Teorema di Rice porterebbe ad affermare che D non è ricorsivo. L’insieme D non caratterizza una proprietà estensionale: infatti, = ⇒ ∼, se i ∈ D e ϕk = ϕi con k > i (k è facilmente ottenibile con la tecnica del Padding), allora k 6∈ D (dimostrare per esercizio). Esercizio 18.16. (1)  Dire (giustificando formalmente) per quali A ⊆ N il seguente insieme i : Wi = A è ricorsivo.  (2) Dire (giustificando formalmente) se i : ∀x. ϕi (x) ↑ è ricorsivo, r.e. o non r.e.  (3) Dimostrare che i : ϕi è definitivamente ≥ 3 è non ricorsivo. (4) Dimostrare che i : ∃x. ϕi (x) = x è non ricorsivo. 166 18. I TEOREMI DI RICORSIONE Il Teorema di Rice è assai disarmante di fronte a proprietà estensionali che riguardano MdT (quindi programmi). Di fatto, ogni proprietà non banale che tratti ciò che calcolano le MdT è non ricorsiva (non decidibile). Tuttavia, è possibile rappresentare alcune proprietà estensionali non ricorsive (ma r.e. ) in modo decidibile, ricorrendo a proprietà su MdT non estensionali decidibili. Definizione 18.17. Sia Π una proprietà. A ⊆ N è una rappresentazione di Π se per ogni ϕi ∈ Π esiste j ∈ A tale che ϕi = ϕj e, viceversa, per ogni j ∈ A esiste ϕi ∈ Π tale che ϕi = ϕj . Ogni insieme A ⊆ N può essere esteso in modo estensionale ad una proprietà  Π(A) tale che A ⊆ Π(A) e A rappresenta Π(A). Basta definire Π(A) = i : ∃j ∈ A. ϕi = ϕj . Teorema 18.18. Sia A un insieme r.e. . Allora esiste una rappresentazione ricorsiva B della proprietà Π(A). Proof. Se A = ∅ allora basta porre B = A = ∅. B è chiaramente una rappresentazione ricorsiva di Π(A) = ∅. Supponiamo che A 6= ∅. Per il Teorema 17.6, esiste una funzione ricorsiva (totale) f tale che A = range(f). Definiamo g tale che: g(0) = f(0) g(n + 1) = jn dove ϕjn = ϕf(n+1) e jn > g(n) Mostriamo che g è ben definita: per il Lemma del Padding jn cosı̀ costruito esiste sempre, basta porre i = f(n + 1) e k = g(n) nel Lemma del Padding. g è inoltre crescente e ricorsiva. Quindi per il Teorema 17.10 B = range(g) è ricorsivo. Inoltre, è chiaro che B è una rappresentazione per Π(A) (segue banalmente per costruzione, verificare per esercizio).  Esercizio 18.19. Si mostri che esiste una rappresentazione ricorsiva della proprietà Π(A) per i seguenti insiemi: (1) A = {i : 2 ∈ Wi } (2) A = {i : ϕi (2) = ϕ2·i (4)} (3) A = {i : ∃j ≤ i.j ∈ Wi } (4) A = {i : ∀j ∈ {1900, . . . , 2100} : j ∈ Wi } Una ulteriore importante caratterizzazione di proprietà r.e. o delle rappresentazioni è dato dal seguente Teorema: Teorema 18.20 (Rice e Shapiro). Sia Π una proprietà estensionale. Se Π è r.e. allora per ogni i ∈ N i ∈ Π sse ∃j ∈ Π.ϕj ⊆ ϕi ∧ Wj è finito Proof. (→) Sia i ∈ Π. Se Wi è finito, si prenda j = i. Supponiamo che i ∈ Π, Wi è infinito, ma j ∈ / Π ogni qual volta ϕj ⊆ ϕi e Wj è finito. Sia P una 4. PROPRIETÀ DI PROGRAMMI 167 MdT t.c. P(z) ↓ sse ϕz (z) ↓ e sia g definita come segue:   ϕ (t) se P(z) non termina in ≤ t passi i g(z, t) =  ↑ altrimenti Per il Teorema s-m-n esiste s ricorsiva tale che: g(z, t) = ϕs(z) (t). Ora: • z ∈ K implica che Ws(z) è finito e ϕs(z) ⊆ ϕi . Per ipotesi, s(z) ∈ / Π. • z∈ / K implica che ϕs(z) = ϕi . Pertanto s(z) ∈ Π. Dunque l’appartenenza a Π è equivalente all’appartenenza a K̄ che sappiamo non essere r.e.: assurdo. (←) Supponiamo che esista i ∈ N tale che: ∃j.ϕj ⊆ ϕi e inoltre Wj è finito e i∈ / Π. Sia g definita come segue:   ϕ (t) se t ∈ W oppure z ∈ K i j g(z, t) =  ↑ altrimenti Per il Teorema s-m-n esiste s ∈ R tale che g(z, t) = ϕs(z) (t). Abbiamo che: • z ∈ K implica che ϕs(z) = ϕi . Per l’ipotesi su i, s(z) ∈ / Π. • z∈ / K implica che ϕs(z) |Wj = ϕi . Dunque s(z) è estensionalmente equivalente a ϕj e pertanto s(z) ∈ Π. Anche in questo caso l’appartenenza a Π è equivalente all’appartenenza a K̄ che sappiamo non essere r.e.: assurdo.  Esercizio 18.21. Si mostri che i seguenti insiemi non sono r.e.: (1) {i : Wi = N} (2) {i : Wi è infinito } (3) {i : ∀x ∈ N(x è pari → x ∈ Wi )} 4. Proprietà di programmi Il Teorema di Rice fornisce un importante risultato per comprendere i limiti dei sistemi automatici per dimostrare o verificare proprietà di programmi. Consideriamo un linguaggio di programmazione Turing-completo L. π è una proprietà dei programmi di L se π ⊆ L, ovvero π è l’insieme dei programmi che verificano la proprietà. Possiamo distinguere tra proprietà sintattiche e semantiche dei programmi. Le prime riguardano come i programmi sono scritti, mentre le seconde riguardano il comportamento. Esempi di proprietà di programmi sono:  P ∈ L : ∀x ∈ D. P termina proprietà semantica  P ∈ L : P contiene un ciclo while proprietà sintattica  P ∈ L : |P| < 1000 proprietà sintattica  P ∈ L : P è un programma sicuro proprietà semantica 168 18. I TEOREMI DI RICORSIONE È evidente che le proprietà dei programmi di maggior interesse sono quelle semantiche, ovvero quelle che riguardano il comportamento dei programmi, ovvero ciò che questi fanno/calcolano. Il concetto di proprietà estensionale caratterizza le proprietà semantiche dei programmi. Una proprietà π ⊆ L è estensionale se per ogni P, G ∈ L: P ∈ π ∧ [[P]]L = [[Q]]L ⇒ Q ∈ π Data una proprietà π ⊆ L è sempre possibile estendere π alla più piccola proprietà estensionale che contiene π. Sia P ∈ L un programma. Definiamo: def  Pз = Q ∈ L : [[P]]L = [[Q]]L def S È chiaro che πз = P∈π Pз è la più piccola proprietà estensionale che contiene π. Una proprietà π è estensionale sse π = πз . È importante osservare che, per il Lemma del Padding, una proprietà estensionale non vuota è infinita. Il Teorema di Rice afferma dunque che una proprietà estensionale non vuota di programmi, ovvero in grado di caratterizzare almeno un programma, è ricorsiva, ovvero decidibile, se e solo se questa è banale, ovvero π = L. La proprietà π = L corrisponde ad affermare che un programma è un programma. Solo quindi questa banale affermazione sui programmi è decidibile, ovvero esiste un algoritmo (programma) in grado di verificarla automaticamente. Diamo di seguito una dimostrazione alternativa del Teorema di Rice per le proprietà del linguaggio While. Questa dimostrazione mette in luce il legame diretto tra il problema della terminazione e la decidibilità di proprietà estensionali. Proof. Supponiamo che π sia estensionale (π = πз ) non banale (π 6= L e π 6= ∅) e decidibile. Dimostriamo che questo implicherebbe la decidibilità del problema della terminazione. Consideriamo il programma W sempre divergente: read(x); while (nil.nil) do x := x endw; write(y) È chiaro che per ogni d ∈ D, [[W]](d) ↑. Supponiamo che W ∈ π. Poiché π è non banale (π 6= L), esiste un programma R ∈ L tale che R ∈ / π: read(h); CR ; write(k) Consideriamo un generico programma P: read(x); CP ; write(y) 4. PROPRIETÀ DI PROGRAMMI 169 e sia e ∈ D. Possiamo assumere che i programmi P ed R non condividano variabili. Costruiamo il seguente programma Q: read(x); h := x; [memorizzo l’input x in h] x := e; CP ; ResP := y; [ResP := [[P]](e)] CR ; ResR := k; [ResR := [[R]](x)] write(ResR) Se [[P]](e) =↑ allora ∀d ∈ D. [[Q]](d) =↑. Se [[P]](e) =↓ allora ∀d ∈ D. [[Q]](d) = [[R]](d). Pertanto abbiamo che: • Se [[P]](e) ↑ allora [[Q]] = [[W]] che per estensionalità di π e poiché W ∈ π implica che Q ∈ π. • Se [[P]](e) =↓ allora [[Q]] = [[R]] che per estensionalità di π e poiché R 6∈ π implica che Q 6∈ π. Pertanto, avremo che [[P]](e) ↓ se e solo se Q ∈ π: la decidibilità di π implicherebbe la decidibilità della terminazione di [[P]](e). Vista la genericità di P e dell’argomento e ∈ D, avremmo dimostrato la decidibilità della terminazione per un linguaggio Turing-completo, il che è assurdo. Un assurdo analogo si dimostra se W 6∈ π [completare per esercizio].  L’indecidibilità delle proprietà estensionali deriva quindi direttamente dalla indecidibilità della terminazione. È importante osservare che questa indecidibilità non deriva necessariamente da complicati programmi di raro interesse pratico. La terminazione del seguente semplice programma, ad esempio, costituisce da anni un enigma per gli scienziati, enigma che a tutt’oggi non risulta essere stato risolto (utilizziamo un linguaggio While esteso con semplici operazioni aritmetiche e con l’istruzione if-then-else): while (n ≥ 2) do if n mod 2 = 0 then n := n div 2 else n := 3n + 1 endw L’impatto del Teorema di Rice sullo sviluppo di strumenti automatici di verifica non è tuttavia cosı̀ drammatico. Lo sviluppo crescente di software di grandi dimensioni (dell’ordine di 10Mlinee) rende impossibile la verifica manuale di importanti proprietà quali la correttezza, la sicurezza, l’assenza di guasti, la fault-tolerance, etc. Queste proprietà, tutte chiaramente estensionali, risultano di vitale interesse per l’utilizzo del software in ambienti safety-critical, quali i sistemi di controllo del traffico aereo, delle transazioni bancarie, di centrali per la produzione di energia. 170 18. I TEOREMI DI RICORSIONE Risulta quindi necessario sviluppare strumenti automatici (programmi) che analizzano altri programmi. Il Teorema di Rice, pur imponenedo severe limitazioni a ciò che è effettivamente possibile dimostrare sui programmi mediante i programmi stessi (eventualmente definiti in linguaggi diversi), non deve scoraggiare la ricerca di strumenti e metodi per verificare proprietà di programmi. Mentre infatti questo risultato impedisce definitivamente la realizzazione di algoritmi di verifica automatica per tutti i programmi, esso non impedisce che strumenti di verifica automatica siano realizzabili per classi significative di programmi, ovvero che per alcune classi di programmi, proprietà estensionali significative siano decidibili. Il Teorema di Rice stabilisce che non esiste una soluzione universale in grado di verificare in tempo finito se un generico programma soddisfa o meno una data proprietà estensionale. Tuttavia esistono potenti strumenti e metodi di analisi statica ovvero a tempo di compilazione, quindi decidibili, per verificare ampie classi di programmi significativi. Questo ambito di studio è competenza dell’area dei metodi formali e costituisce uno degli ambiti di ricerca sia teorica che applicata più attivi in questi anni. CHAPTER 19 Riducibilità funzionale e gradi di risolvibilità L’inclusione insiemistica, intesa come relazione tra insiemi, non dà nessuna informazione circa la ricorsività di un insieme. In particolare, il fatto che un insieme sia contenuto in un dato insieme ricorsivo non implica in alcun modo la ricorsività del primo: la ricorsività di N non implica la ricorsività di ogni suo sottoinsieme (e.g. K ⊂ N). Anzi, come si è visto, la maggior parte dei sottoinsiemi di N non è nemmeno r.e. In questa sezione studieremo il concetto di riducibilità funzionale. Questo concetto permette di correlare tra loro insiemi in modo tale che la ricorsività (o la non ricorsività) di un insieme possa essere propagata agli insiemi ad esso correlati mediante riduzione funzionale.  Consideriamo l’insieme K2 = hx, yi : y ∈ Wx . Con hx, yi si intende in questo contesto una possibile codifica delle coppie di naturali in numero naturale (ad esempio 2x · 3y ). In questo modo ci occupiamo solo di relazioni tra insiemi di numeri naturali e non di insiemi di tuple generiche di numeri naturali. Lemma 19.1. K2 è r.e. . Proof. Si definisca g nel modo seguente: g(z) = 1 se z = hx, yi e ϕx (y) ↓, indefinita altrimenti. g è la funzione semicaratteristica di K2 ed è chiaramente calcolabile.  K2 ha una struttura molto simile a K, che sappiamo essere un insieme non ricorsivo. Ci chiediamo se anche K2 sia non ricorsivo. Si osservi che K2 rappresenta una generalizzazione di K: x ∈ K sse hx, xi ∈ K2 Pertanto, se K2 fosse ricorsivo, avremmo un modo per decidere l’appartenenza a K (ovvero K sarebbe ricorsivo) che sappiamo essere assurdo. In altri termini, l’esistenza di una funzione totale e calcolabile f tale che x ∈ K sse f(x) ∈ K2 ci ha permesso di dire qualcosa (di negativo) su K2 . Quello che abbiamo visto è un metodo per ridurre la decidibilità dell’appartenenza ad un insieme alla decidibilità del’appartenenza ad un altro insieme. Questo esempio conduce alla seguente definizione. 171 172 19. RIDUCIBILITÀ FUNZIONALE E GRADI DI RISOLVIBILITÀ Definizione 19.2. Siano A, B ⊆ N. A è (funzionalmente) riducibile a B (A  B) se esiste una funzione (totale) ricorsiva f tale che: x ∈ A ⇔ f(x) ∈ B Nel caso A  B con funzione ricorsiva f, diremo che A si riduce a B via f. L’ordine A  B fa pensare che A non è più difficile di B. La relazione  è denominata <m (many-one reducibility) nel testo di Rogers [27]. 1. La relazione di riducibilità  In questo paragrafo vedremo alcuni esempi e proprietà della nozione di riducibilità funzionale. Esempio 19.3. Sia T = {x : ϕx = 0}. Mostriamo che K  T . Sia f(x, y) = 0 se ϕx (x) ↓, indefinita altrimenti. f è calcolabile e per s-m-n esiste g totale ricorsiva tale che ϕg(x) (y) = f(x, y). Si osserva che x ∈ K se e solo se g(x) ∈ T . Lemma 19.4. La relazione ⊆ ℘(N) × ℘(N) è riflessiva e transitiva. Esercizio.  Pertanto, possiamo definire una relazione di equivalenza ≡ tra insiemi di numeri, in modo tale che A ≡ B sse A  B e B  A. Denoteremo con [A]≡ la classe di equivalenza di A, ovvero l’insieme B ⊆ N : A ≡ B . In questa relazione l’insieme vuoto ∅ e l’insieme N giocano un ruolo particolare e forse inatteso: Lemma 19.5. (1) Sia A un insieme. N  A se e solo se A 6= ∅. (2) Sia A un insieme. ∅  A se e solo se A 6= N. Proof. (1) Sia A 6= ∅, sia a ∈ A e sia f = λx.a. Si osservi che x ∈ N ↔ f(x) ∈ A. Viceversa, per assurdo N  ∅. Allora esisterebbe f tale che x ∈ N → g(x) ∈ ∅. Ma questo è assurdo in quanto, ad esempio 0 ∈ N, ma f(0) non appartiene a ∅. (2) Mostriamo che se A ⊂ N, allora ∅  A. Sia b ∈ N \ A e sia g = λx.b. Dobbiamo mostrare che x ∈ ∅ ↔ g(x) ∈ B. Poiché nessun x appartiene ad ∅ devo solo mostrare che per x ∈ / ∅ (ovvero x ∈ N) vale che: g(x) ∈ / A. Ciò è immediato per la definizione di g. Viceversa, supponiamo per assurdo che ∅  N. Allora esisterebbe g tale che x ∈ ∅ → f(x) ∈ N. Dunque dovrebbe valere in particolare per x = 0: 0 ∈ / ∅ e g(0) ∈ / N. Ma g è totale e dunque g(0) ∈ N. Assurdo.  Pertanto ∅ e N sono degli insiemi minimali rispetto alla relazione  ma tra loro incomparabili (si veda Figura 1—l’insieme S sarà illustrato nel Teorema 19.24). Vediamo alcune proprietà fondamentali della nozione di riduzione funzionale. Teorema 19.6. Sia A, B ⊆ N. (1) A  B ⇒ Ā  B̄; 1. LA RELAZIONE DI RIDUCIBILITÀ  173 non r.e. K r.e. S X ∈ R \ ?{∅, N}  ??? ??   ??  ??    ∅ N Figure 1. Diagramma della relazione  (2) A  B e B ∈ RE ⇒ A ∈ RE. (3) A  B e B ricorsivo ⇒ A ricorsivo. Proof. Il primo punto è banale. Dimostriamo che se A  B e B ∈ RE allora A ∈ RE. Sia ψB la funzione semicaratteristica parziale ricorsiva di B e supponiamo che A  B via f. Consideriamo la funzione parziale ricorsiva ψA definita come: ψA (x) = ψB (f(x)). Allora abbiamo che   1 se f(x) ∈ B ψA =  ↑ se f(x) 6∈ B Poiché x ∈ A sse f(x) ∈ B, ψA è la funzione semicaratteristica di A, e quindi A ∈ RE. Il terzo punto ha una dimostrazione del tutto analoga al secondo punto, considerando la funzione caratteristica di B.  Abbiamo quindi dimostrato che la proprietà di essere ricorsivo o ricorsivamente enumerabile si propaga verso il basso secondo l’ordinamento  tra insiemi. Il seguente lemma evidenzia una importante classe di insiemi  equivalenti. Lemma 19.7. B ricorsivo e A 6= ∅ e A 6= N implica B  A. Proof. Sia a ∈ A e b ∈ N \ A e sia ψB la funzione caratteristica di B. Definisco   a ψB (x) = 1 g(x) =  b ψ (x) = 0 B g è ricorsiva e totale e vale che x ∈ B se e solo se g(x) ∈ A.  174 19. RIDUCIBILITÀ FUNZIONALE E GRADI DI RISOLVIBILITÀ Come corollario, tutti gli insiemi ricorsivi diversi da ∅ e da N (insiemi ricorsivi non banali) sono tra loro -equivalenti. Analizziamo ora gli insiemi r.e. . Teorema 19.8. Per ogni A ∈ RE, A  K2 . Proof. Sia A ∈ RE. Allora esiste y0 tale che A = Wy0 . Per definizione di K2 , x ∈ A = Wy0 sse hy0 , xi ∈ K2 . Definiamo dunque fy0 = λx. hy0 , xi. È chiaro che x ∈ A sse fy0 (x) ∈ K2 .  Pertanto, ogni insieme r.e. è riducibile a K2 . Questo significa che K2 ha un ruolo speciale all’interno della classe degli insiemi r.e. : ogni elemento di questa classe è riducibile a K2 . Definizione 19.9. Un insieme C ∈ RE è completo (più precisamente, r.e. completo) se per ogni A ∈ RE: A  C. K2 è dunque completo. Il seguente teorema dimostra che anche K è completo. Pertanto, K ≡ K2 , ed entrambi giocano un ruolo speciale (massimale) all’interno della classe degli insiemi r.e. È inoltre banale osservare che A è completo sse A ∈ [K]≡ . Teorema 19.10. K è completo. Proof. Basta dimostrare che K2  K. Infatti, in questo modo, poiché ogni insieme r.e. è riducibile a K2 (vedi Teorema 19.8), allora per transitività, ogni insieme r.e. è anche riducibile a K, dimostrando la completezza di K. Sia hx, yi ∈ K2 . Per definizione, ciò è vero sse y ∈ Wx . Definiamo la funzione   1 se ϕ (y) ↓ x ψ(x, y, z) =  ↑ altrimenti È chiaro che ψ è parziale ricorsiva, quindi, per il teorema s-m-n, esiste una funzione ricorsiva (totale) g tale che: ψ(x, y, z) = ϕg(x,y) (z). Si noti come, fissato x, y ∈ N, la funzione λz. ϕg(x,y) (z) sia indipendente dal valore dell’argomento z. Si osserva che hx, yi ∈ K2 sse ϕg(x,y) (z) ↓ per z ∈ N qualsiasi. Quindi, a maggior ragione hx, yi ∈ K2 sse ϕg(x,y) (g(x, y)) ↓. Questo dimostra che hx, yi ∈ K2 sse g(x, y) ∈ K, con g funzione ricorsiva.  Corollario 19.11. C ∈ RE è completo se e solo se K ≤ C. Proof. Immediato dalla definizione.   Esempio 19.12. Sia F = x : |Wx | è finito . Dimostriamo che K  F. Definiamo la funzione:   1 se ϕ (x) non converge in meno di y passi x ψ(x, y) =  ↑ altrimenti 2. INSIEMI CREATIVI E PRODUTTIVI 175 Questa funzione è parziale ricorsiva (si descriva a parole una possibile MdT che calcola ψ). Per il teorema s-m-n, esiste g ricorsiva (totale) tale che ψ(x, y) = ϕg(x) (y). • Sia x ∈ K. Allora, per definizione di K: ϕx (x) ↓. Dunque esiste n tale che ϕx (x) ↓ in n passi. Dunque Wg(x) = {0, . . . , n − 1}. Pertanto g(x) ∈ F. • Sia x ∈ / K. Allora ϕx (x) ↑. Dunque Wg(x) = N e dunque g(x) ∈ / F. Si osservi che l’esempio non dimostra che F è completo. Manca infatti la proprietà che F sia RE. Per mostrare che F non è r.e. si mostri per esercizio che K̄  F. Si osservi inoltre che se A è r.e. e non ricorsivo, allora non vale né che A  Ā né che Ā  A. Se fosse A  Ā allora avremmo un modo per semidecidere Ā che è assurdo poiché A non è ricorsivo. Se fosse Ā  A allora, in virtù del Teorema 19.6(1), varrebbe anche A  Ā. Dunque in particolare K 6 A per ogni A che sia complemento di un insieme r.e. . Nel seguente esercizio si mostra comunque che esistono insiemi non r.e. che stanno sopra a K (ad esempio K join K̄). Esercizio 19.13. Consideriamo due insiemi A e B. Chi è un insieme (possibilmente il minimo) X tale che A  X e B  X? Non può essere A ∪ B. Si giunge infatti ad un assurdo partendo da B = Ā, con A 6= ∅ e A 6= N. Verificare che tale insieme è: A join B = {y : (y = 2x ∧ x ∈ A) ∨ (y = 2x + 1 ∧ x ∈ B)} Si mostri inoltre che se A ≤ B allora A join B ≡ B. Esercizio 19.14. Dimostrare che: • Se A ∈ RE, A 6= ∅ e A 6= N, allora A ricorsivo ⇔ A  Ā. • A ∈ [B]≡ e A ricorsivo allora per ogni X ∈ [B]≡ , X è ricorsivo. 2. Insiemi creativi e produttivi Abbiamo visto che all’interno della classe degli insiemi r.e. , esistono insiemi (e.g. K) che hanno la proprietà per cui ogni altro insieme r.e. è ad essi riducibile. Questi insiemi, detti completi, rappresentano quindi la classe degli insiemi r.e. , quasi fossero dei rappresentanti “canonici” di questa classe. In questa sezione caratterizzeremo gli insiemi completi. Caratterizzare gli insiemi completi significa dare una condizione che determini in modo univoco se un dato insieme è o meno completo, senza dovere ricorrere ad altri insiemi r.e. K̄ non è r.e. . Ma questa proprietà può essere mostrata anche in modo costruttivo e convincente: ogni volta che un insieme r.e. Wx è incluso in K̄ siamo in grado di trovare un elemento che appartiene a K̄ \ Wx . Tale elemento, in questo caso è proprio x. La seguente definizione generalizza questa proprietà. Definizione 19.15. Un insieme A ⊆ N è detto produttivo se esiste una funzione (totale) ricorsiva fA , detta funzione produttiva di A, tale che: ∀x ∈ N. (Wx ⊆ A ⇒ fA (x) ∈ A \ Wx ) 176 19. RIDUCIBILITÀ FUNZIONALE E GRADI DI RISOLVIBILITÀ Un insieme A è detto creativo se A è r.e. ed il suo complemento è produttivo, ovvero se: A ∈ RE e ∀x ∈ N. (Wx ⊆ Ā ⇒ fA (x) ∈ Ā \ Wx ) Intuitivamente un insieme è produttivo se per ogni tentativo di enumerarlo in modo effettivo mediante un algoritmo di indice x (ovvero per ogni Wx ⊆ A), esiste una trasformazione effettiva di x in un punto di A che sfugge all’enumerazione. È evidente che un insieme produttivo non può essere r.e. , altrimenti avremmo l’assurdo che A = Wx0 per un certo x0 e al tempo stesso A \ Wx0 6= ∅, poiché f(x0 ) ∈ A \ Wx0 . Al contrario, un insieme creativo è sempre, per definizione, r.e., ma il suo complemento non lo può essere. Riassumendo: Teorema 19.16. Sia A ⊆ N. (1) A produttivo ⇒ A non r.e. (2) A creativo ⇒ A non ricorsivo Proof. Immediato per definizione.   L’insieme K = x : x ∈ Wx ancora una volta è rappresentativo per la classe di insiemi creativi, come dimostrato nel seguente teorema. Teorema 19.17. K è creativo e K̄ è produttivo. Proof. Dimostriamo che K̄ è produttivo. Consideriamo la funzione ricorsiva di base id = λx. x. Ci chiediamo in quale regione del diagramma di Venn possa stare x: x ∈ Wx ? x ∈ K? x ∈ K̄ \ Wx ? Mostreremo che le prime due ipotesi portano ad un assurdo. Se Wx ⊆ K̄ allora id(x) = x 6∈ Wx . Infatti, se x ∈ Wx , allora per definizione di K: x ∈ K. Poiché Wx ⊆ K̄, allora x ∈ K̄ che sarebbe assurdo. Quindi id(x) = x 6∈ Wx . Se x ∈ K allora avremmo x ∈ Wx ⊆ K̄, ovvero x ∈ K̄, che è assurdo poiché x non può essere al tempo stesso in K e K̄ = N \ K. Quindi se Wx ⊆ K̄ allora id(x) = x ∈ K̄ \ Wx , ovvero K̄ è produttivo. La dimostrazione che K è creativo è immediata poiché K ∈ RE e K̄ è produttivo.  Il seguente teorema dimostra una proprietà fondamentale degli insiemi creativi e produttivi, ovvero che la produttività e la creatività di un insieme viene ereditata per riduzione funzionale. Teorema 19.18. Siano A, B ⊆ N. (1) A produttivo e A  B ⇒ B produttivo. (2) A creativo, A  B, e B ∈ RE ⇒ B creativo. Proof. Dimostriamo il punto (2) e lasciamo il punto (1) per esercizio. La dimostrazione è del tutto analoga al caso creativo. 2. INSIEMI CREATIVI E PRODUTTIVI 177 (2). Sia A creativo e B ∈ RE tale che A  B via f. Sia h la funzione produttiva  di Ā. Nella dimostrazione faremo uso della notazione f−1 (X) = x : f(x) ∈ X . E’ immediato verificare che f−1 è monotona, ovvero X ⊆ Y → f−1 (X) ⊆ f−1 (Y). f è ricorsiva, dunque esiste una funzione parziale ricorsiva ψ(x, y) tale che   1 se f(y) ∈ Wx ψ(x, y) =  ↑ altrimenti Per il teorema s-m-n, esiste una funzione totale ricorsiva g tale che: ψ(x, y) = ϕg(x) (y). Inoltre, per definizione di ψ, fissato x ∈ N: Wg(x) = f−1 (Wx ). Dimostriamo ora che B è creativo. Per fare ciò basta dimostrare che B̄ è produttivo. Mostreremo che se Wx ⊆ B̄, allora f(h(g(x))) ∈ B \ Wx . • Mostriamo che f(g(g(x))) ∈ B. Se Wx ⊆ B̄, dunque f−1 (Wx ) ⊆ f−1 (B̄). Poiché A  B via f, −1 f (B̄) = Ā. Inoltre sappiamo già che f−1 (Wx ) = Wg(x) . Pertanto si ha che Wg(x) ⊆ Ā. Per la produttività di Ā si ha dunque che: (2.1) h(g(x)) ∈ Ā \ Wg(x) Poiché A  B via f, e h(g(x)) ∈ Ā si ha che f(h(g(x))) ∈ B̄. • Mostriamo che f(h(g(x))) ∈ / Wx . Se per assurdo fosse f(h(g(x))) ∈ Wx , per la definizione di ψ si avrebbe che h(g(x)) ∈ Wg (x) che contraddice (2.1).  Il seguente teorema caratterizza gli insiemi completi come tutti e soli gli insiemi creativi, ovvero aventi un complemento produttivo. Gli insiemi completi hanno dunque complementi non r.e. . Nella dimostrazione del Teorema seguente faremo uso del secondo Teorema di ricorsione (Teorema 18.6). Teorema 19.19 (Myhill). Sia A ∈ RE. A completo ⇔ A creativo Proof. (⇒). Sia A ∈ RE. A è completo sse per ogni X ∈ RE: X  A. Poiché K è creativo e A completo, K  A. Per il Teorema 19.18, anche A è creativo. (←). Sia A creativo con funzione produttiva per Ā, f. Sia B un generico insieme in RE che vogliamo ridurre ad A. Definiamo la funzione:   0 se y ∈ B e z = f(x) ψ(x, y, z) =  ↑ altrimenti Per il teorema s-m-n, esiste una funzione calcolabile totale g tale che ψ(x, y, z) = ϕg(x,y) (z). Quindi:   {f(x)} se y ∈ B Wg(x,y) =  ∅ se y 6∈ B 178 19. RIDUCIBILITÀ FUNZIONALE E GRADI DI RISOLVIBILITÀ Per il secondo teorema di ricorsione (Teorema 18.6) esiste una funzione calcolabile ν tale che per ogni y ∈ N ϕg(ν(y),y) = ϕν(y) Allora, per ogni y si ha che: Wg(ν(y),y) = Wν(y)   {f(ν(y))} =  ∅ se y ∈ B se y 6∈ B Mostriamo che f ◦ ν è la funzione di riduzione da B ad A. • Se y ∈ B allora Wν(y) = {f(ν(y))}. Dunque, poiché f è la funzione produttiva di Ā, se fosse f(ν(y)) ∈ / A, allora varrebbe che Wν(y) ⊆ Ā e pertanto f(ν(y)) ∈ Ā \ Wν(y) ovvero che f(ν(y)) ∈ / Wν(y) . Assurdo. • Se y ∈ / B, allora Wν(y) = ∅. Dunque Wν(y) ⊆ Ā, e pertanto f(ν(y)) ∈ Ā.  Come conseguenza di questo fatto, osserviamo che tutti gli insiemi creativi sono tra loro equivalenti secondo ≡. Inoltre, se A è creativo, allora la classe di equivalenza [A]≡ è massima nell’ordinamento indotto da  sulle classi di equivalenza di insiemi r.e. Esercizio 19.20. (1) Studiare  la relazione  o ≡ esistente tra i seguenti insiemi: • x : x è primo ; • x : x è pari ; •  x : Wx = ∅ ; • x : |Wx | = ω ; • x : |Wx | < ω ; • x : ϕx è totale .  (2) Dimostrare che x : ϕ è totale è produttivo (Suggerimento: dimostrare x  che K̄  x : ϕxè totale ). (3) Dimostrare che i : Wi è ricorsivo è produttivo. (4) Dimostrare che i : ϕi (i) = 0 è creativo. (5) Dimostrare che x : ∃y. x ∈ Wy ∧ y ∈ Wx è creativo. (6) Dimostrare che x : ∃u∃v.x = hu, vi ∧ ϕu·v (u + v) = 0 è creativo. Il seguente teorema stabilisce che ogni insieme produttivo contiene un insieme r.e. infinito. Teorema 19.21. Sia A un insieme produttivo. Esiste n0 ∈ N tale che Wn0 ⊆ A ∧ |Wn0 | = ω ovvero esiste un sottoinsieme r.e. e infinito di A. 3. INSIEMI SEMPLICI 179 Proof. Sia f la funzione di produttività di A. Costruiamo una funzione totale ricorsiva g tale che: range(g) ⊆ A ∧ |range(g)| = ω La definizione di g è data per induzione. ∅ è r.e. , e so costruire una MdT M tale che M(x) ↑ per ogni x. Sia z0 il suo indice (dunque ∅ = Wz0 ). def • Poniamo g(0) = ψ(z0 ). Poiché Wz0 = ∅ ⊆ A per definizione di produttività abbiamo che: ψ(z0 ) ∈ A e ψ(z0 ) 6∈ Wz0 . • Supponiamo di aver già calcolato g(0), . . . , g(n): so costruire una MdT M tale che M(g(0)) = · · · = M(g(n)) = 0 e M(x) ↑ per ogni x diverso da g(0), . . . , g(n). Sia zn+1 il suo indice: dunque Wzn+1 = {g(0), . . . , g(n)}. def Poniamo g(n + 1) = f(zn+1 ). Poiché Wzn+1 = {g(0), . . . , g(n)} ⊆ A per definizione di produttività otteniamo: f(zn+1 ) ∈ A e f(zn+1 ) 6∈ Wzn+1 . La iniettività di g deriva dal fatto che, per produttività di A, g(n) = f(zn ) ∈ A \ Wzn = {g(0), . . . , g(n − 1)}. Da questo segue che |range(g)| = ω. La ricorsiva enumerabilità di range(g) ⊆ A deriva dal Teorema di caratterizzazione di Kleene degli insiemi r.e. .  3. Insiemi semplici Da quanto abbiamo visto: creativi ∪ ricorsivi ⊆ r.e. . Ci chiediamo se tutti gli insiemi r.e. sono o ricorsivi o creativi. La risposta è no. Per stabilire questo fatto, definiamo il concetto di insieme semplice. Definizione 19.22. A ⊆ N è semplice se • A è r.e. , • |Ā| = ω, ovvero Ā è infinito, • ∀x ∈ N. |Wx | = ω → A ∩ Wx 6= ∅. Teorema 19.23. (1) A semplice → A non ricorsivo (2) A semplice → A non creativo Proof. Per dimostrare il punto (1), supponiamo che A sia ricorsivo. Per il Teorema di Post, Ā è ricorsivo. Pertanto, esiste x0 ∈ N tale che Ā = Wx0 e, essendo |Ā| = ω, |Wx0 | = ω. Per definizione di insieme semplice, A ∩ Wx0 = A ∩ Ā 6= ∅ che è assurdo. Per dimostrare il punto (2), supponiamo che A sia creativo. Per definizione, Ā è produttivo e, per il Teorema 19.21, esiste x0 tale che Wx0 ⊂ Ā e |Wx0 | = ω. Per definizione di insieme semplice, questo implica che A ∩ Wx0 6= ∅, ma questo è assurdo poiché Wx0 ⊂ Ā.  Resta ora da vedere se esiste un insieme di numeri con le caratteristiche degli insiemi semplici. Il seguente teorema, dovuto a Post, dimostra che esistono insiemi semplici, che non sono né ricorsivi né creativi. 180 19. RIDUCIBILITÀ FUNZIONALE E GRADI DI RISOLVIBILITÀ Teorema 19.24. Esiste un insieme semplice. Proof. definiamo il seguente insieme:  def  A = (x, y) : y ∈ Wx ∧ y > 2x = (x, y) : ϕx (y) ↓ ∧y > 2x È ovvio che A è r.e. e che A 6= ∅. Pertanto, esiste x0 ∈ N tale che A = Wx0 e A = range(ϕh(x0 ) ). Definiamo una relazione di precedenza ✁ sulle coppie di numeri tale che: (x ′ , y ′ ) ✁ (x, y) sse ∃n ≤ m. (x ′ , y ′ ) = ϕh(x0 ) (n) ∧ (x, y) = ϕh(x0 ) (m). ovvero, (x ′ , y ′ ) ✁ (x, y) se (x ′ , y ′ ) viene generata prima nella enumerazione di A da parte di ϕh(x0 ) rispetto a (x, y). Definiamo l’insieme: def  B = (x, y) ∈ A : ∀z 6= y. (x, z) ∈ A → (x, y) ✁ (x, z) Si noti che B è r.e. [perchè?]. Definiamo dunque l’insieme S: def  S = y : ∃x. (x, y) ∈ B e mostriamo che è semplice. • S è r.e. . (in quanto lo è B) • |S̄| = ω. Infatti, sia n ∈ N. Al più n elementi di {0, . . . , 2n} sono in S poiché per ogni numero pari x c’è al più un elemento y tale che (x, y) è la prima coppia di A. • Inoltre, sia |Wx | = ω. Allora esiste y > 2x tale che y ∈ Wx (essendo Wx infinito) e y ∈ Wx ∩ S, da cui segue la tesi.  Corollario 19.25. Vale che S  K ma K 6 S. Proof. S è semplice e pertanto r.e.; dunque S  K per completezza di K. Se fosse K  S allora per il Teorema di Myhill S sarebbe creativo. Questo condraddirebbe il Teorema 19.23.  Siamo dunque in grado di collocare S nel diagramma di Figura 1. Part 4 Complessità Computazionale CHAPTER 20 Classi di complessità e principali risultati Una delle linee guida del testo è quella di investigare la nostra conoscenza di sottoinsiemi di N o, equivalentemente, di Σ∗ (ovvero di insiemi di stringhe di caratteri di lunghezza finita costruiti a partire da un dato alfabeto Σ). Più precisamente, il criterio di conoscenza richiesto riguarda la capacità di stabilire se un elemento appartenga o meno ad uno di questi insiemi. Si è mostrato come per alcuni di essi tale problema sia decidibile (insiemi ricorsivi). In particolare si sono caratterizzati mediante grammatiche e automi alcune importanti famiglie di insiemi ricorsivi quali i linguaggi regolari, quelli liberi, e quelli dipendenti dal contesto. Si è mostrato come per altri il problema sia solo semidecidibile (insiemi r.e.), e che vi siano insiemi per cui l’appartenenza non è nemmeno semidecidibile (ad esempio per gli insiemi produttivi). Quando un programmatore è chiamato a risolvere un problema, il primo obiettivo è quello di scrivere un programma che lo risolva e che termini per tutte le possibili istanze di input, al fine di evitare spiacevoli attese infinite all’utilizzatore del programma stesso. Dunque, per quanto non esista un programma universale in grado di stabilire se un programma goda della suddetta proprietà di terminazione, il programmatore deve scrivere il codice sincerandosi, mediante opportune dimostrazioni spesso di tipo induttivo, che la proprietà di terminazione per quel particolare programma valga. L’impiego di linguaggi di programmazione ad alto livello è particolarmente di aiuto in questa fase. Tuttavia nella pratica questa proprietà è necessaria ma non sufficiente. Sapere che il programma prima o poi terminerà può non essere d’aiuto a chi necessita della risposta entro pochi minuti. Un passo avanti si potrebbe avere fornendo all’utilizzatore una ulteriore informazione: se l’input consta di k bits, allora il programma su quell’input terminerà entro f(k) secondi. L’utente ora sa quanto k aspettare. Ma se f(k) = 2(2 ) allora per un input di 1 Byte avremo bisogno di 256 69 2 s ∼ 3.7 · 10 anni. Se questo programma decide l’appartenenza ad un insieme, sapere che quell’insieme è ricorsivo, non pare aiutarci troppo. Ci rimane però un dubbio: quel programma è il più veloce possibile per decidere l’appartenza a quell’insieme e dunque quella funzione doppiamente esponenziale è una misura di complessità dell’insieme stesso, oppure è solo basato un algoritmo male architettato per risolvere un problema più facile? 183 184 20. CLASSI DI COMPLESSITÀ E PRINCIPALI RISULTATI Rispondere a domande di questo tipo è il principale scopo della teoria della complessità. In particolare si cerca di suddividere i problemi, gli insiemi in classi di complessità. Ciò permette di partizionare la classe degli insiemi ricorsivi in vare famiglie a complessità crescente o, meglio, non decrescente. Sono infatti numerosi e stimolanti i problemi aperti ancora esistenti riguardanti l’inclusione stretta o meno ? tra classi di complessità, tra cui il più famoso è P = NP, aperto ufficialmente da Cook nei primi anni 70 [5] ma esistente già nella sostanza nel contenuto di lettere private intercorse nei primi anni 50 tra Gödel e Von Neumann [29]. 1. Problemi, insiemi, linguaggi Il concetto di problema, il concetto di insieme, inteso come sottoinsieme di N, e quello di linguaggio vengono utilizzati in modo equivalente nella teoria della complessità. Un problema (decisionale) viene tipicamente posto fornendo una certa caratterizzazione degli input possibili e una condizione da verificarsi su ciascuna istanza dell’input. Vi saranno delle istanze con risposta affermativa (istanze yes) e istanze con risposta negativa (istanze no). Ad esempio, prendiamo il problema: dato un grafo G = hN, Ei, il grafo è connesso? Vi saranno pertanto istanze yes come G = h{1, 2}, {{1, 2}}i e istanze no come G = h{1, 2}, ∅i. Possiamo tranquillamente assumere che ogni istanza (o input) x sia una stringa di un alfabeto Σ fissato (nel caso sopra Σ = {‘0’, . . . , ‘9’, ‘{’, ‘}’, ‘h’, ‘i’, ‘,’}). Il linguaggio associato al problema sarà l’insieme delle istanze yes ovvero L = {x ∈ Σ∗ : x verifica la condizione del problema}. Tale linguaggio è per definizione stessa un insieme. Si osservi che in base a tale definizione ogni stringa che non descrive correttamente un’istanza dell’input (nell’esempio sopra potrebbe essere: h{{{, , , 112i) finisce nelle istanze no. Ovviamente, poiché, dato Σ finito, Σ∗ è numerabile, potremmo senza perdita di generalità studiare semplicemente sottoinsiemi di N, come fatto nella parte 3 del presente testo. Si osservi che la scelta di definire un problema nel modo suddetto permette di stabilire in modo naturale e rigoroso il concetto di dimensione dell’input come la lunghezza della stringa x, al solito denotata come |x|. Un’ulteriore precisazione va fatta relativamente alla scelta dei problemi decisionali. Abbiamo appena mostrato come questo permetta di concentrarsi sul problema di appartenenza ad un insieme che è stata anche la guida per la classificazione di insiemi ricorsivi e riscorsivamente enumerabili. E’ inoltre vero che ciò non rappresenta una limitazione significativa. Ragioniamo su un esempio. Si considerino i seguenti due problemi, il primo funzionale, il secondo, ottenuto a partire dal primo, decisionale: (1) Dato un grafo completo G = hN, Ei e una funzione c : E −→ N che assegna un costo ad ogni arco, calcolare il circuito di costo minimo che parte e arriva nel nodo 1 e passa una sola volta per tutti gli altri nodi. (2) Dato un grafo completo G = hN, Ei, una funzione c : E −→ N che assegna un costo ad ogni arco, e un intero t, dire se esiste un circuito che parte e 2. CLASSI DI COMPLESSITÀ IN TEMPO E TESI DI CHURCH COMPUTAZIONALE 185 arriva nel nodo 1 e passa una sola volta per tutti gli altri nodi, di costo minore o uguale a t? E’ chiaro che sapendo risolvere (1) avremmo immediatamente un metodo per risolvere (2). Viceversa, assumiamo di disporre dell’algoritmo due che risolve (2). Procederemo per bisezione: m := 0; P M := e∈E c(e); while (M − m > 1) do t := (M + m)/2; if due(G, c, t) = yes then m := t else M := t endw Quando M = m + 1 ci si ferma. Il valore tf del circuito di lunghezza minima per (1) va cercato solo tra M e m. Si tratta P dunque di ripetere l’esecuzione di due un numero di volte massimo pari a log( e∈E c(e)) che è proporzionale al numero di caratteri necessari per descrivere la funzione c ovvero |c|. Pertanto il numero di iterazioni risulta limitato linearmente dalle dimensioni dell’input. Per scoprire quali sono gli archi utilizzati nella soluzione con valore tf , si parta dal nodo iniziale. Uno ad uno, si assegni ad un arco in uscita da lui il valore tf . Si lanci dunque due con limite tf . Se la risposta è yes, quell’arco non stava nel cammino minimo. Altrimenti ci stava e dunque riassegnamo a lui il suo costo indicato da c e passiamo al nodo successivo. Dunque, dalla soluzione al problema decisionale si può ottenere la soluzione a quello funzionale con un numero di operazioni che dipende polinomialmente dall’algoritmo per il problema decisionale moltiplicato per le dimensioni dell’input. 2. Classi di complessità in tempo e Tesi di Church computazionale Seguendo il testo [23], a cui rimandiamo il lettore per approfondimenti, useremo il seguente modello computazionale: Definizione 20.1. Una k-MdT è una Macchina di Turing M = hQ, Σ, q0 , Pi con k-nastri. Più precisamente: (1) Q è l’insieme finito degli stati e q0 ∈ Q è lo stato iniziale. (2) Σ è l’alfabeto dei simboli presenti sui nastri. Assumiamo che i simboli ✄ e $ appartengano a Σ. 186 20. CLASSI DI COMPLESSITÀ E PRINCIPALI RISULTATI (3) L’insieme delle istruzioni P rappresenta una funzione di transizione δ che assumiamo essere una funzione totale δ : Q × Σk −→ (Q ∪ {h, yes, no}) × (Σ × {L, R, F})k (4) h, yes, no sono degli stati finali non presenti in Q, rispettivamente, di terminazione, accettazione, e refutazione. δ non è definita su di essi. (5) Si osservi che l’insieme dei movimenti (per ogni nastro) consta di tre possibilità: L (sinistra), R (destra), e F (fermo). (6) Assumiamo inoltre che il simbolo ✄ non possa essere cancellato e quando viene letto su un nastro, l’effetto è che la testina, relativamente a quel nastro, si sposta a destra. (7) La configurazione iniziale è del tipo: ✄, ε} , . . . , |ε, {z ✄, ε} ) (qo , |ε, {z ✄, x} , |ε, {z nastro 1 nastro 2 nastro k ove per ogni nastro 1, . . . , k i tre parametri sono: la stringa significativa a sinistra della testina, il simbolo letto e la stringa significativa a destra della testina, mentre x è l’input (sul nastro 1). Si osservi che l’assumere δ totale permette di parlare di δ anziché dell’insieme delle produzioni P. Le assunzioni (6) e (7) fanno sı́ che le k MdT lavorino su nastri semi-infiniti (non si vai mai più a sinistra del ✄ iniziale presente su ogni nastro). Vi è inoltre la possibilità del movimento nullo (F). Sebbene esso possa essere facilmente mimato da due movimenti L–R consecutivi, avere a disposizione anche il movimento F permette di scrivere del codice più snello. Nota 20.2. Si osservi che per k = 1 la nozione di MdT appena definita risulta essere una restrizione della definizione generale (salvo per il movimento nullo che però, come appena detto, può facilmente essere simulato). La restrizione riguarda la presenza del simbolo ✄ e le sue proprietà, la totalità di δ nonché gli stati h, yes, no. E’ comunque facile convincersi che queste restrizioni non pregiudicano la Turing completezza del modello delle 1-MdT (richieste analoghe sono state fatte nelle dimostrazioni di equivalenza tra funzioni Turing-calcolabili e gli altri formalismi nei Capitoli 13 e 16). Le nozioni già viste di relazione di successore tra configurazioni istantanee, computazioni eccetera, si adattano immediatamente a questo formalismo. In particolare, la macchina termina se e solo se raggiunge una configurazione con lo stato h, yes o no. Definizione 20.3. Una k-MdT M è di tipo decisionale se ogni qual volta essa termina, raggiunge uno degli stati finali yes o no. L’output della macchina è, in questo caso, lo stato raggiunto. Una k-MdT M invece calcola una funzione se ogni qual volta termina, essa raggiunge lo h. In questo caso l’output è il contenuto del k-esimo nastro. 2. CLASSI DI COMPLESSITÀ IN TEMPO E TESI DI CHURCH COMPUTAZIONALE 187 Nello studio classico della complessità si è interessati alle macchine decisionali. Per completezza, tuttavia, abbiamo fornito anche la definizione nel caso più generale, che permette di definire le classi di complessità funzionali (si veda [23]). Definizione 20.4. Data una k-MdT M e un input x, il tempo richiesto da M per x è il numero di passi di computazione necessari a M con input x per terminare. Sia f : N −→ N una fuzione totale. Si dice che una k-MdT M opera in tempo f(n) se per ogni input x il tempo richiesto da M per x è minore o uguale a f(|x|) (ove, al solito, |x| denota la lunghezza della stringa x). Sia Σ un alfabeto. Assumiamo che ✄ ∈ / Σ e $ ∈ / Σ. Tali simboli invece occorrono nell’alfabeto delle k-MdT di cui parleremo. Useremo tali k-MdT per decidere l’appartenenza di un elemento a un dato linguaggio L ⊆ Σ∗ . Definizione 20.5 (Classi in tempo). Un linguaggio L ⊆ Σ∗ è deciso da una k-MdT M se per ogni x ∈ Σ∗ vale che: • Se x ∈ L allora M con input x termina nello stato yes (in breve M(x) = yes).1 • Se x ∈ / L allora M con input x termina nello stato no (in breve M(x) = no). Se L ⊆ Σ∗ è deciso da una k-MdT M e M opera in tempo f(n), allora L ∈ TIME(f(n)). TIME(f(n)) cosı́ definita è una classe di complessità in tempo. Essa rappresenta l’insieme di linguaggi (o, equivalentemente, di insiemi) che possono essere decisi (dunque si tratta di insiemi ricorsivi) in tempo limitato da una funzione nota a priori. Il seguente Teorema mostra che il formalismo ora descritto non permette di descrivere algoritmi sensibilmente più efficienti rispetto alle MdT a un nastro. Teorema 20.6. Se M è una k-MdT che opera in tempo f(n), possiamo costruire una 1-MdT M ′ equivalente e che opera in tempo O(nf(n) + f(n)2 ). L’idea della dimostrazione è semplicemente quella della simulazione di una computazione su k nastri in un solo nastro. Il contributo nf(n) si può omettere qualora, come spesso accade, f(n) ≥ n. Per quanto riguarda la notazione O-grande, date due funzioni f, g : N −→ N, si dice che f(n) è O(g(n)) se esiste c > 0 e n0 ∈ N tali che (∀n ≥ no )(f(n) ≤ cg(n)). f(n) ≤ k per qualche k ≥ 0. Dire dunque che una In altri termini, che limn→ ∞ g(n) MdT opera in tempo O(g(n)) significa che opera in tempo f(n) con f(n) che è O(g(n)). Il seguente Teorema giustifica l’utilizzo della notazione O-grande per la caratterizzazione delle classi di complessità, mostrando come le ‘costanti’ non contino. 1 Se vale solo questa condizione allora L è semi-deciso da M. 188 20. CLASSI DI COMPLESSITÀ E PRINCIPALI RISULTATI Teorema 20.7 (Speed-up). Sia L ∈ TIME(f(n)). Allora: ∀ǫ > 0 : L ∈ TIME(ǫf(n) + n + 2). L’idea della dimostrazione è la seguente: data una k-MdT M che decide L. Costruiamo una h-MdT (ove h = k se k ≥ 2, 2 altrimenti) il cui linguaggio codifica m-uple di Σ. In tal modo ad ogni passo di computazione di M ′ corrispondono m passi di computazione di m. Scegliendo m in funzione di ǫ (m > ǫ6 ) si ottiene il risultato. Nota 20.8. Come corollario ‘pratico’ del risultato suddetto, si mostra che un potenziamento dell’HW può permettere solo un miglioramento lineare dei tempi di esecuzione. Algoritmi inefficienti rimangono tali anche se il calcolatore diventa 10, 100, 1000 volte più veloce. Verificato che il formalismo delle k-MdT e quello delle 1-MdT sono sostanzialmente equivalenti a meno di un fattore polinomiale, può a questo punto sorgere il dubbio che altri formalismi (per esempio, le URM [6], le RAM [23], i programmi While [15]) permettendo di descrivere algoritmi più veloci, rendano la MdT non adatta a ciò. Vale invece una versione computazionale della tesi di Church: Tutti i formalismi di calcolo ragionevoli sono computazionalmente equivalenti a meno di fattori polinomiali. Ad esempio, nel caso delle RAM, vale il seguente risultato [23]: Teorema 20.9. Sia L ∈ TIME(f(n)). Allora esiste un programma RAM che calcola la funzione caratteristica di L in tempo O(f(n)). Sia P un programma RAM che calcola la funzione φ in tempo f(n). Allora esiste una 7-MdT che calcola φ in tempo O(f(n)3 ). Le dimostrazioni sono, ovviamente, basate su simulazione di un formalismo Turing completo in un altro. In Figura 1 sono definite le importanti classi P ed EXPTIME relative a quanto abbiamo ora descritto. 3. Il non determinismo In questa sezione presenteremo un modello di computazione non ragionevole (nel senso della tesi di Church sopra). Il concetto che si vuol catturare è quello del non-determinismo. Diversamente da quanto accade per DFA e NFA in cui sostanzialmente si è provata l’equivalenza tra i formalismi, in questo caso i risultati saranno meno ovvi e alcuni non sono noti. Definizione 20.10. Una MdT M = hQ, Σ, q0 , Pi (a un nastro) è non-deterministica (in breve, una ND-MdT) se P è una relazione: P ⊆ (Q × Σ) × ((Q ∪ {h, yes, no}) × Σ × {L, R, S}) 3. IL NON DETERMINISMO P def EXPTIME def NP def NEXPTIME def L def PSPACE def NL def NPSPACE def = = = = = = = = 189 S TIME(nk ) S NTIME(nk ) k≥0 k S TIME(2n ) S NTIME(2n ) k≥0 k≥0 k≥0 k SPACE(log n) S k k≥0 SPACE(n ) NSPACE(log n) S k k≥0 NSPACE(n ) Figure 1. Sommario delle classi di complessità introdotte Per il resto questo modello eredita le definizioni fornite per il caso deterministico. La differenza sta che i successori a una data configurazione non sono solo 0 (quando è terminante) o 1 (tutti gli altri casi). Una configurazione (q, ℓ, s, r) ha un numero di successori pari all’insieme delle quintuple in P che iniziano con q, s. Data una configurazione iniziale (q0 , ε, ✄, x) esiste una famiglia di possibili computazioni non deterministiche. La relazione di successore −→∗ dunque è una vera G e propria relazione simile alla ⇒∗ usata nelle produzioni possibili per un linguaggio CF. Definizione 20.11. Un linguaggio L ⊆ Σ∗ è deciso da una ND-MdT M se per ogni x ∈ Σ∗ : • Se x ∈ L, allora esiste una computazione non deterministica tale che: (q0 , ε, ✄, x) −→∗ (yes, u, s, v) • Se x ∈ / L, allora non esiste una computazione non deterministica tale che: (q0 , ε, ✄, x) −→∗ (yes, u, s, v) Si noti la grande asimmetria tra x ∈ L e x ∈ / L. Quando x ∈ / L tutte le computazioni potrebbero addirittura essere non terminanti. Con la sola definizione sopra, il linguaggio L appare solo semideciso. Definizione 20.12. Una ND-MdT M opera in tempo f(n) se ogni computazione non deterministica sull’input x ha al più lunghezza f(|x|). Se una ND-MdT M che opera in tempo f(n) decide un linguaggio L ∈ Σ∗ , allora siamo in grado anche di stabilire in modo effettivo se x ∈ / L. Più precisamente: Definizione 20.13. Se un linguaggio L ∈ Σ∗ è deciso da una ND-MdT M che opera in tempo f(n), allora L ∈ NTIME(f(n)). 190 20. CLASSI DI COMPLESSITÀ E PRINCIPALI RISULTATI In Figura 1 sono definite le importanti classi non deterministiche NP ed NEXPTIME. Uno dei problemi aperti più affascinanti dell’informatica teorica è collocare insiemisticamente P e NP. Ovviamente vale: Lemma 20.14. P ⊆ NP. Proof. Se L ∈ P, allora è deciso da una k-MdT deterministica M in tempo O(nh ) per qualche h ∈ N. Per il Teorema 20.6 sappiamo che esiste una 1-MdT M ′ che opera in tempo O(n2h ) equivalente a M. M ′ non è altro che una ND-MdT nel cui insieme delle istruzioni vi è esattamente una quintupla in P per ogni coppia q, s.  Non si sa ancora né se P = NP né se P 6= NP. Vale tuttavia il seguente: S Teorema 20.15. NTIME(f(n)) ⊆ c≥1 TIME(cf(n) ). Proof. Sia L ∈ NTIME(f(n)) e M = hQ, Σ, q0 , Pi una ND-MdT che decide L. Sia c il massimo grado di non determinismo di M, ovvero c= max hq,si∈Q×Σ |{hq, s, q ′ , s ′ , Mi ∈ P}|. Si tiene un vettore di lunghezza f(n) che rappresenta un numero in base c di f(n) caratteri inizializzato a [0, 0, . . . , 0]. Ad ogni passo di computazione il numero i ∈ {0, . . . , c − 1} indica quale delle scelte non deterministiche debba essere selezionata. Si simula dunque la computazione che effettua, ad ogni passo, le scelte illustrate dal vettore. Se alla fine si raggiunge yes, si accetta x. Altrimenti si incrementa il vettore di 1 (in base c) e si riparte. Se dopo il tentativo con il vettore a [c − 1, c − 1, . . . , c − 1] non si è ancora raggiunto yes, si restituisce no.  Corollario 20.16. NP ⊆ EXPTIME. 4. Una inclusione stretta In letteratura si trovano svariate classi di complessità che permettono di partizionare l’insieme dei linguaggi (insiemi) ricorsivi. Tuttavia, sono molto pochi i risultati di inclusione stretta finora dimostrati. Uno di questi sarà presentato in questo paragrafo. Innanzitutto una premessa. Quali sono le funzioni che si prestano ad essere funzioni √ proprie per il calcolo della complessità? Sicuramente tra queste ci sono log n, n, n, n2 , n3 , 2n , . . . . Non sembra accettabile una funzione f del tipo:   2n Se n è primo f(n) =  Πn π altrimenti i=1 i dove πi è l’i-esima cifra nell’espansione decimale di π. Si può formalizzare la nozione di essere una buona funzione per la complessità. 4. UNA INCLUSIONE STRETTA 191 Definizione 20.17. f è una funzione propria di complessità se è debolmente crescente (f(n + 1) ≥ f(n)) ed esiste una una macchina di Turing a k > 1 nastri M tale che: • non modifica mai il contenuto del primo nastro • il k-esimo nastro è a sola scrittura e la testina in esso può solo (a) stare ferma o (b) scrivere e spostarsi a destra. • (q0 , |ε, {z ✄, x} , |ε, {z ✄, ε} , . . . , |ε, {z ✄, ε} ) −→t nastro 1 nastro 2 nastro k j2 0jk−1} , |✄1f(|x|) (h, |ε, {z ✄, x} , |ε, ✄, {z0 } , . . . , |ε, ✄,{z {z , $, ε}) nastro 1 nastro 2 nastro k−1 nastro k • t = O(|x| + f(|x|)) e, • per i = 2, . . . , k − 1, ji = O(f(|x|)). Dunque una macchina di Turing per il calcolo delle funzioni proprie calcola f in unario e non usa spazio inutile (in particolare è una I/O-k-MdT—si confronti la Def. 20.23). Sia f(n) ≥ n una funzione di complessità propria. Definiamo l’insieme Hf nel modo seguente:  def (4.1) M; x : M accetta x in ≤ f(|x|) + 5|x| + 4 passi Hf = Hf è una versione ‘limitata’ dell’Halting problem. Teorema 20.18. Hf ∈ TIME(f(n)3 ). Idea della dimostrazione. Si costruisce una 4-MdT M che decide Hf . Si userà la MdT universale, la macchina che permette lo speed-up per togliere le costanti e la macchina per calcolare f che è propria. A parte il limite cubico, per il quale è necessario sviluppare la dimostrazione in dettaglio, è abbastanza facile convincersi della possibilità di realizzare una M che decide Hf e che opera in tempo polinomialmente legata a f(n). E questo è quello che veramente importa in questo contesto. Teorema 20.19. Hf ∈ / TIME(f(⌊ n 2 ⌋)). Proof. Supponiamo per assurdo che esista MHf che decide Hf in tempo f(⌊ n 2 ⌋). Allora possiamo costruire una macchina di Turing D con la seguente funzionalità: def D = if MHf (M; M) = yes then no else yes Quanti passi impiega D sull’input M? So che   |M; M| = f(|M|) MHf (M; M) = f 2 192 20. CLASSI DI COMPLESSITÀ E PRINCIPALI RISULTATI D deve, partendo dall’input M, duplicarlo e aggiungere un ; in mezzo. Questo può essere fatto in tempo 5|M| + 4. Inoltre va modificato P di MHf per terminare in modo opposto. Dunque (4.2) D(M) impiega tempo ≤ f(|M|) + 5|M| + 4 Cerchiamo dunque di giungere ad una contraddizione: • Se D(D) = no allora MHf (D; D) = yes, ovvero D accetta D in ≤ f(|D|)+ 5|D| + 4 passi. Dunque D(D) = yes: una contraddizione. • Se D(D) = yes allora MHf (D; D) = no, ovvero D su D non termina in ≤ f(|D|) + 5|D| + 4 passi. Ma questo contraddice (4.2).  Corollario 20.20. Se f(n) ≥ n è una funzione di complessità propria, allora: TIME(f(n)) ⊂ TIME(f(2n + 1)3 ) Corollario 20.21. P ⊂ EXPTIME. Proof. P ⊆ TIME(2n ) ⊂ TIME((2n )3 ) ⊆ EXPTIME  Dunque sappiamo che: P ⊆ NP ⊆ EXPTIME e che almeno una delle due inclusioni è propria. Ma non sappiamo quale, né sappiamo se lo siano entrambe! Se rimuoviamo l’ipotesi di essere propria, vale il seguente Teorema Teorema 20.22 (GAP). Esiste f ricorsiva tale che TIME(f(n)) = TIME(2f(n) ). Idea: si definisce f in modo tale che nessuna MdT su un input di lunghezza n termini in un numero di passi compreso tra f(n) e 2f(n) . 5. Complessità in spazio Sebbene il tempo di computazione sia considerato la risorsa più importante da tenere bassa nello sviluppo di algoritmi (lo spazio, a differenza del tempo, può essere riutilizzato), la quantità di memoria (nastro) utilizzata durante la computazione ci fornisce una misura interessante e meritevole di essere studiata. Per studiare queste nozioni viene prima definito un ulteriore raffinamento del modello k-MdT che permette di escludere, nel conteggio di tale misura, lo spazio necessario per l’input e l’output (input e output saranno infatti ugualmente presenti in ogni k-MdT che risolve lo stesso problema e limitano dunque le possibilità di discriminazione. Definizione 20.23. Una macchina di Turing a k nastri e con input e output (I/0-k-MdT ) M è una k-MdT con almeno due nastri. Il primo, che contiene l’input, è un nastro a sola lettura. L’ultimo, che conterrà l’output, è a sola scrittura e la testina in esso può solo (a) stare ferma o (b) scrivere e spostarsi a destra. 5. COMPLESSITÀ IN SPAZIO 193 Pertanto, se si volesse modificare il nastro che contiene l’input, sarebbe necessario prima ricopiarlo in un altro nastro e modificare quest’ultimo. Si osservi che questo non è un nuovo modello di MdT ma semplicemente una restrizione al formalismo della k-MdT definita in Def. 20.1. Definizione 20.24 (Classi in spazio deterministico). Data una I/0-k-MdT M e un input x, se ✄, ε} , . . . , |ε, {z ✄, ε} ) −→∗ (q, u1 , s1 , v1 , u2 , s2 , v2 , . . . , uk , sk , vk ) (qo , |ε, {z ✄, x} , |ε, {z | {z } | {z } | {z } nastro 1 nastro 2 nastro k nastro 1 nastro 2 nastro k Pk−1 con q ∈ {h, yes, no} allora diremo che lo spazio richiesto da M per x è i=2 |ui | + |vi | + 1. M opera in spazio f(n) se per ogni input x lo spazio richiesto da M per x è minore o uguale a f(|x|). Se esiste una I/0-k-MdT M che decide un linguaggio L ⊆ Σ∗ e opera in spazio f(n), allora L ∈ SPACE(f(n)). Le classi di spazio deterministiche L e PSPACE sono definite in Figura 1. Vediamo ora l’importante teorema che collega le classi deterministiche di spazio e tempo: Teorema 20.25. SPACE(f(n)) ⊆ TIME(2O(f(n)) ). Proof. Sia L ∈ SPACE(f(n)) e sia M la I/O-k-MdT che decide L. Se durante la computazione M si trovasse nella medesima configurazione (q, ℓ1 , s1 , r1 , . . . , ℓk , sk , rk ) per due volte, il determinismo garantirebbe che la situazione si ripeterebbe all’infinito e dunque M non terminerebbe (assurdo). Il numero di configurazioni diverse (l’ultimo nastro non conta in quanto non viene letto) è dell’ordine: (Q + 3) | {z } stati O(f(|x|) Σ | {z } (|x| + 1)f(|x|)k {z } | stringhe sui nastri posizione testina  Corollario 20.26. L ⊆ P e PSPACE ⊆ EXPTIME. Anche nel caso del non-determinismo si possono definire le classi in spazio. Una I/0-k-MdT M è non deterministica se l’insieme P delle istruzioni descrive una relazione anziché una funzione δ. Valgono dunque le stesse definizioni delle ND-MdT e delle I/0-k-MdT. Definizione 20.27 (Classi in spazio non deterministico). Una ND-I/0-k-MdT M decide L ⊆ Σ∗ in spazio f(n) se M decide L e per ogni x ∈ Σ∗ , se (qo , |ε, {z ✄, x} , |ε, {z ✄, ε} , . . . , |ε, {z ✄, ε} ) −→∗ (q, u1 , s1 , v1 , u2 , s2 , v2 , . . . , uk , sk , vk ) | {z } | {z } | {z } nastro 1 nastro 2 Pk−1 nastro k allora i=2 |ui | + |vi | + 1 ≤ f(|x|). In tal caso si dice che L ∈ NSPACE(f(n)). nastro 1 nastro 2 nastro k 194 20. CLASSI DI COMPLESSITÀ E PRINCIPALI RISULTATI L ⊆ NL ⊆ P ⊆ NP ⊆ | PSPACE NPSPACE {z ⊂ stretta ⊆ EXPTIME ⊆ NEXPTIME } Figure 2. Classi in spazio e tempo presentate e loro posizioni reciproche Si osservi che tale definizione permette che M che decide L in spazio f(n) sia potenzialmente non terminante! Le classi NL e NPSPACE sono definite in Figura 1. Ovviamente vale: Teorema 20.28. SPACE(f(n)) ⊆ NSPACE(f(n)) Pertanto si avrà: Corollario 20.29. NL ⊆ L e PSPACE ⊆ NPSPACE. Tuttavia vale il risultato (di cui si omette del tutto la dimostrazione): Teorema 20.30 (Savitch). Se f(n) ≥ n ed è una funzione propria di complessità, allora NSPACE(f(n)) ⊆ SPACE(f(n)2 ). Come corollario, si ottiene il risultato: Corollario 20.31. NPSPACE = PSPACE In realtà un sospetto su questo risultato lo si ha simulando una ND-MdT mediante una MdT deterministica (Teorema 20.15). Prima di iniziare ciascuna delle possibili (esponenziali in numero) simulazioni si può riportare prima le testine nella configurazione iniziale. Si userà cosı̀ lo stesso spazio richiesto dalla ND-MdT di partenza. Similmente, si può dimostrare: Teorema 20.32. NP ⊆ PSPACE. Il riassunto delle inclusioni discusse in questo capitolo si trova in Figura 2. CHAPTER 21 Riduzioni e NP-completezza Nel capitolo 19 è stata introdotta la riducibilità funzionale tra insiemi. Ridurre A a B mediante una funzione ricorsiva f ci permetteva di dire che A non poteva essere più difficile di B. In questo capitolo si rafforza la nozione di riduzione aumentando le richieste per la funzione f. f deve essere non solo ricorsiva, ma calcolabile in modo semplice rispetto alla complessità degli insiemi A e B che si vuole correlare. In questo modo potremmo dire che sapendo calcolare B efficientemente, la proprietà positiva ricade su A. Viceversa, che se la decisione di A richiede algoritmi inefficienti, lo stesso varrà per B. 1. Riduzioni tra problemi Definizione 21.1. Dati due linguaggi L1 e L2 si dice che L1 ⊆ Σ∗1 è riducibile a L2 ⊆ Σ∗2 (in breve, L1  L2 ) se esiste una funzione f : Σ∗1 −→ Σ∗2 tale che: • f è calcolabile da una I/O-k-MdT (deterministica) in spazio O(log n) e • per ogni x ∈ Σ∗1 si ha che: x ∈ L1 sse f(x) ∈ L2 f è chiamata una riduzione da L1 a L2 . In letteratura si può trovare la richiesta per f di essere calcolabile polinomialmente. Seguendo [23] facciamo una richiesta più forte (si ricorda che: L ⊆ P). Qual’è il senso di questa scelta? Similmente a quanto fatto per la riducibilità usando funzioni ricorsive, si vorrebbe avere che L2 ∈ C e L1  L2 implica che L1 ∈ C. Se ciò è vero, allora C è detta chiusa per riduzione. Si consideri il seguente esempio: • y ∈ L2 si decide in tempo |y|2 (ovvero L2 ∈ P) • f riduce L1 a L2 nel senso che x ∈ L1 sse f(x) ∈ L2 , ma • f si calcola in tempo 2|x| Malgrado ci sia la riduzione non possiamo usarla per dire qualcosa sulla classe di complessità di L1 . Pertanto la funzione di riduzione deve poter essere calcolata in modo semplice rispetto alla complessità di L2 . La scelta della complessità—spazio O(log n)—permette di usarla per tutte le classi da noi introdotte in Fig. 1: esse sono tutte chiuse per riduzione. Anche per questo tipo di riduzioni si può introdurre il concetto di completezza: 195 196 21. RIDUZIONI E NP-COMPLETEZZA Definizione 21.2. Data una classe C, un problema L è C-completo se ogni problema della classe può essere ridotto a L. Quando si riducono problemi in P o in classi che contengono P, si può tranquillamente usare la richiesta più debole che f sia calcolabile in tempo polinomiale, che alle volte è più semplice da dimostrare. Vedremo subito un esempio di riduzione: Definizione 21.3 (SAT). Input: Una formula logica proposizionale ϕ data come congiunzione di disgiunzioni (clausole) di letterali, costruita su un insieme di variabili V, ovvero: m ϕ ≡ (ℓ11 ∨ · · · ∨ ℓ1n1 ) ∧ · · · ∧ (ℓm 1 ∨ · · · ∨ ℓnm ) ove ogni ℓij è o una variabile X ∈ V o la negazione di una variabile ¬X con X ∈ V. Problema: Esiste un assegnamento per le variabili in V che renda vera la formula? SAT sta, ovviamente, per SATisfiability. Definizione 21.4 (HP). Input: Un grafo (non diretto) G = hN, Ei. Problema: Esiste un cammino che visita ogni nodo esattamente una volta? HP sta per Hamilton Path. In entrambi i casi, come visto, la dimensione dell’istanza del problema è la lunghezza dela stringa che descrive l’input. Lemma 21.5. HP  SAT . Proof. Dato un grafo G = hN, Ei, costruiremo una formula f(G) su un insieme di variabili V. Assumiamo, per semplicità notazionale, che N = {1, . . . , n}. • Definiamo V = {Xij : 1 ≤ i, j ≤ n}. Informalmente, Xij ha il significato: il nodo j è l’i-esimo nodo del cammino Hamiltoniano (proprietà Booleana). • Descriviamo ora le varie clausole di f(G) con cui vogliamo modellare il problema del cammino Hamiltoniano: – Prima di tutto, ogni nodo j deve stare nel cammino, anche se non sappiamo in quale posizione. Questo può essere espresso come: def φj = X1j ∨ X2j ∨ · · · ∨ Xnj – Ogni nodo j non può apparire sia come i-esimo nodo del cammino che come k-esimo, con i 6= k. Questo si può esprimere con la clausola: def ψj (i, k) = ¬Xij ∨ ¬Xkj – Inoltre, per ogni i = 1, . . . , n vi dev’essere un nodo che sia l’i-esimo del cammino: def ηi = Xi1 ∨ · · · ∨ Xin 2. I TEOREMI DI COOK 197 – Per ogni coppia (i, j) tale che {i, j} che non sia in E devo escludere la possibilità che i sia successore di j nel cammino o viceversa. Questo può essere espresso ripetendo, per k = 1, . . . , n − 1:1 def ρij (k) = ¬Xki ∨ ¬Xk+1j • Possiamo dunque definire f(G): V V def f(G) = i,j,k=1,...,n:i6=k ψj (i, k)∧ j=1,...,n φj ∧ V V i,j,k=1,...,n:k6=n,{i,j}∈E / ρij (k) i=1,...,n ηi ∧ Per costruzione, f trasforma un’istanza di HP (ovvero un grafo) in un’istanza di SAT . (1) Dobbiamo mostrare che f si calcola in spazio logaritmico. L’idea della dimostrazione è la seguente: sostanzialmente si tratta di eseguire dei cicli for (annidati) ma limitati da n = O(|G|). n si rappresenta in spazio logaritmico rispetto al suo valore. (2) Dobbiamo inoltre mostrare che se π (una permutazione di {1, . . . , n}) è un cammino Hamiltoniano in G, allora l’assegnamento che corrisponde a π rende vera la formula f(G). Questo è immediato per costruzione di f(G). (3) Viceversa, se π non lo è, dobbiamo mostrare che l’assegnamento non rende vera la formula. Equivalentemente, mostriamo che se θ è un assegnamento che rende vera la formula, da esso si desume un cammino Hamiltoniano per G. Ogni assegnamento θ che renda vere le tre prime famiglie di clausole garantisce che per ∀j∃!i.Xij = true e ∀i∃!j.Xij = true. Dunque θ descrive una permutazione. L’ultima famiglia di clausole garantisce che tra nodi successivi nella permutazione vi sia un arco.  Avendo ridotto HP a SAT via f, quello che si può dire è che HP non può essere più difficile di SAT . Infatti, per stabilire se x ∈ HP effettuo la riduzione (in spazio logaritmico e dunque tempo polinomiale) e mi riduco cosı̀ al problema di appartenenza a SAT . 2. I Teoremi di Cook Studieremo ora l’esistenza di problemi completi per la classe P e per la classe NP. Definizione 21.6 (CIRCUIT VALUE). Input: Un circuito logico con porte and, or, e not con una sola uscita e i valori per gli ingressi. Problema: Stabilire se l’output è true. 1 Per una dimostrazione analoga relativa al cammino Hamiltoniano nei grafi diretti questo è l’unico punto da modificarsi leggermente (Esercizio). 198 21. RIDUZIONI E NP-COMPLETEZZA Definizione 21.7 (CIRCUIT SAT). Input: Un circuito logico con porte and, or, e not con una sola uscita e n porte di ingresso. Problema: Stabilire se esiste un assegnamento per le porte di ingresso che rende l’output true. Teorema 21.8. CIRCUIT VALUE è P-completo. Proof. Innanzitutto va osservato che CIRCUIT VALUE sta in P. Dati i valori degli input, si percorre il circuito (aciclico) valutando gli output di ogni porta in funzione dei suoi input. Ciò può ovviamente essere fatto in tempo polinomiale. Mostriamo ora che è P-completo, riducendo ogni problema L in P a lui. Sia L ∈ P. Allora esiste una 1-MdT M che decide L ⊆ Σ∗ e che opera in tempo p(n) con p(n) un polinomio in n. Dobbiamo mostrare che esiste una f tale che, dato x ∈ Σ∗ , mi permette di ottenere un circuito f(x) tale che f(x) ha output true se e solo se x ∈ L. Il circuito lo si ottiene dalla forma delle computazioni di M. In al più p(|x|) passi M termina su |x|. Sia k = p(|x|) + 1: al più k celle del nastro possono essere utilizzate. Simuliamo la computazione con una matrice k × k in cui ogni riga i rappresenta la configurazione dopo l’i-esimo passo. La cella j rappresenta dunque il simbolo presente sul nastro oppure un simbolo che identifica che la testina si trova nello stato q e legge il simbolo s (una sola cella per riga avrà memorizzato questa informazione aggiuntiva). Assumiamo, senza perdita di generalità, che M termini sempre a destra del ✄ iniziale. Inoltre, se M termina prima di p(|x|) passi, si ricopia l’ultima configurazione. Sia x = a1 . . . an . (✄, q0 ) a1 a2 ... an $ ... $ ✄ .. . (a1 , q1 ) .. . a2 .. . ... .. . an .. . $ .. . ... .. . $ .. . ✄ (y1 , yes/no) y2 ... yn yn+1 ... yk Quello che si osserva è che ogni elemento Mi,j può essere calcolato a partire dalla matrice di transizione di M e dai valori di sole tre celle: Mi−1,j−1 , Mi−1,j , Mi−1,j+1 . A partire da una codifica binaria dei simboli di Σ e di Σ × Q è facile scrivere le funzioni booleane che calcolano la codifica del contenuto della cella Mi,j . Il tutto viene poi composto per ottenere un circuito che simula l’evoluzione della tabella per righe, ovvero la computazione di M. L’output si otterrà dalla cella Mk,2 con un circuito che restituisce 1 se (y1 , yes), 0 altrimenti.  Teorema 21.9. CIRCUIT SAT è NP-completo. Proof. Bisogna innanzitutto mostrare che CIRCUIT SAT sta in NP. Scrivere una ND-MdT che risolve SAT è semplice. In una prima fase si aprono le strade non deterministiche per assegnare i possibili valori per le variabili di input. A questo punto in ogni strada non deterministica ci troviamo di fronte ad una istanza di CIRCUIT VALUE che è in P. 3. PROBLEMI NP-COMPLETI 199 La dimostrazione di NP-completezza ricalca quella di P-completezza di CIRCUIT VALUE. Sia L ∈ NP. Assumiamo senza perdita di generalità che L sia deciso da una ND-k-MdT che opera in tempo p(n) e tale che il suo grado di non determinismo sia 2. Si ripete il ragionamento sopra solo che in ogni porta costruita si aggiunge un input che tiene conto del grado di non-determinismo. La lista di tali variabili di input è l’input di CIRCUIT SAT.  Teorema 21.10. SAT è NP-completo. Proof. La dimostrazione che SAT ∈ NP è identica a quella per CIRCUIT SAT, vista nel Teorema 21.9. Per concludere la dimostrazione, riduciamo CIRCUIT SAT a SAT. Dato un circuito G, si tratta di trovare una formula logica in forma clausale equivalente al circuito dato. Grazie ai teoremi di De Morgan, possiamo assumere che il circuito abbia solo porte or e not. Vi sono tre tipi di nodi; ogni nodo or ha 2 archi entranti, ogni nodo not ha 1 arco entrante, mentre ogni nodo di input ha 0 archi entranti. Assegnamo una variabile di output ad ogni nodo del circuito. or: la variabile Y di output rappresenta una funzione Y ↔ A ∨ B ove A e B sono le variabili di output dei due nodi da cui partono gli archi entranti. Y ↔ A ∨ B è equivalente a: (¬Y ∨ A ∨ B) ∧ (¬A ∨ Y) ∧ (¬B ∨ Y) not: la variabile Y di output rappresenta una funzione Y ↔ ¬A ove A è la variabile di output del nodo da cui parte l’arco entrante: Y ↔ ¬A è equivalente a: (¬Y ∨ A) ∧ (Y ∨ ¬A) input: la variabile di output è la stessa dell’input. La congiunzione delle formule ottenute è equisoddisfacibile al circuito finale. La riduzione si può fare in spazio log n (guardo i nodi uno ad uno, dobbiamo solo scrivere temporaneamente degli indici che vanno da 1 a |G| e che dunque si rappresenta in spazio log |G|).  3. Problemi NP-completi La gran parte dei problemi pratici che un informatico deve affrontare (problemi su grafi, scheduling, problemi di logica) presenta molte caratteristiche simili a quelle di SAT, che riassumiamo: facilità di verifica di una soluzione; ampiezza esponenziale dello spazio di ricerca della stessa (delle stesse). In base al Teorema 21.10 e al fatto che la classe NP è chiusa per riduzione, è possibile cercare di dimostrare formalmente la NP-completezza di un problema L che ci sembra appartenere a questa famiglia. Per far ciò si tratta di dimostrare che: (1) L ∈ NP; e (2) SAT  L. 200 21. RIDUZIONI E NP-COMPLETEZZA Una volta mostrato che L è NP-completo, si potrà utilizzarlo, come si è fatto per SAT nel punto (2), per dimostrare la NP-completezza di altri problemi. Per quanto riguarda l’appartenenza a NP, si può utilizzare la seguente definizione alternativa (ed equivalente): L ∈ NP se per ogni x ∈ L esiste una certificazione ‘succinta’ (un testimone polinomiale) di questa proprietà che può essere verificata in tempo polinomiale. Nel caso di SAT, il testimone è un assegnamento di verità. Nel caso di HP, è proprio il cammino. In entrambi i casi la dimensione del testimone è succinta, nel senso che è di dimensioni polinomiali rispetto all’input. La verifica che si tratti di una soluzione è parimenti polinomiale. Ovviamente quello che si cerca di fare è di rendere esplicita la parte di non-determinismo necessaria per la definizione di una MdT che decida non deterministicamente il linguaggio: in modo non deterministico va generato il testimone. Per quanto riguarda la proprietà di riduzione, bisogna invece avere sufficiente fantasia/esercizio. Questa proprietà (ovvero che esiste un NP-completo riducibile a lui) è detta NP-hardness. Vedremo ora alcune riduzioni famose, alcuni problemi NP-completi fondamentali. 3.1. Varianti di SAT. Definizione 21.11 (3SAT). Input: Istanze di SAT in cui ogni clausola consta di esattamente 3 elementi. Problema: Come per SAT, stabilire se esiste un assegnamento che rende vera la formula. Teorema 21.12. 3SAT è NP-completo. Proof. L’appartenenza ad NP si mostra in questo caso banalmente in quanto ogni istanza di 3SAT è anche istanza di SAT. Mostriamo ora che SAT  3SAT . Sia x input di SAT. Traduciamo ogni clausola ℓ1 ∨ · · · ∨ ℓm di x in una equisoddisfacibile clausola di 3SAT. • Se m = 1: la trasformo in ℓ1 ∨ ℓ1 ∨ ℓ1 • Se m = 2: la trasformo in ℓ1 ∨ ℓ2 ∨ ℓ2 • Se m = 3 non devo fare nulla. • Se m > 3, introduco una nuova variabile X: restituisco la clausola ℓ1 ∨ ℓ2 ∨ X e riapplico la riscrittura alla clausola ¬X ∨ ℓ3 ∨ · · · ∨ ℓm .  Definizione 21.13 (NAESAT). Input: Tutti gli input di SAT. Problema: Stabilire se esiste un assegnamento delle variabili di input che renda vera la formula e tale che in ogni clausola non tutti i letterali sono a true. In altri termini, non ci vanno bene gli assegnamenti che mettono tutti i letterali di almeno una clausola a false o tutti a true: da cui il nome Not All Equal. Teorema 21.14. NAESAT è NP-completo. 3. PROBLEMI NP-COMPLETI 201 Proof. Come per SAT, l’appartenenza a NP si può mostrare usando la definizione Alternativa fornita in questa sezione. Fornito l’input e un assegnamento, la verifica di ‘bontà’ di quell’assegnamento è chiaramente polinomiale. Per mostrare la NP-hardness in modo semplice, basta modificare appena la dimostrazione del Teorema 21.8. Sia Z un’unica variabile aggiuntiva. Y ↔ A ∨ B viene trasformata in: (¬Y ∨ A ∨ B) ∧ (¬A ∨ Y ∨ Z) ∧ (¬B ∨ Y ∨ Z) mentre Y ↔ ¬A viene trasformata in: (¬Y ∨ A ∨ Z) ∧ (Y ∨ ¬A ∨ Z) Sia σ che soddisfa l’istanza di CIRCUIT SAT. Consideriamo la traduzione non estesa con Z. Supponiamo, per assurdo, che nella prima clausola dell’or tutti i tre letterali siano true. Allora la seconda e la terza non sarebbero soddisfatte. Dunque σ non rende veri tutti e tre i letterali delle prime clausole dell’or. Ma allora con σ ′ = σ ◦ [Z/false] si ha una soluzione per NAESAT. Viceversa, sia σ una soluzione per NAESAT. E’ immediato verificare che σ è una soluzione sse σ̄ lo è. In una delle due Z sarà true: quella sarà la soluzione per CIRCUIT SAT.  La versione più estrema di SAT, ove ogni clausola ha esattamente 2 letterali e nota come 2SAT, è invece polinomiale. Si provi per esercizio questa proprietà. 3.2. Problemi su Grafi. Dato un grafo non diretto G = hN, Ei e un insieme I ⊆ N, si dice che I è un insieme indipendente (Independent Set) per G se per ogni i, j ∈ I non vi è in E l’arco {i, j}. Definizione 21.15 (IS). Input: Un grafo non diretto G = hN, Ei e un intero k. Problema: Stabilire se esiste un insieme indipendente I per G tale che |I| = k. Teorema 21.16. IS è NP-completo. Proof. L’appartenenza a NP è immediata. Mostriamo che 3SAT  IS. La dimostrazione è semplice. Tuttavia, per non renderla troppo complicata da apici e pedici annidati, mostriamo la riduzione su una istanza. Sia ϕ = (X1 ∨ X2 ∨ ¬X3 ) ∧ (X1 ∨ ¬X2 ∨ X4 ) ∧ (¬X1 ∨ X4 ∨ X5 ) ∧ (¬X1 ∨ ¬X4 ∨ ¬X5 ) Per ogni clausola costruisco un sottografo completo triangolare A ogni nodo associo (intuitivamente) un letterale (ad esempio, per il primo X1 , X2 , e ¬X3 . Avremo tanti triangoli quante sono le clausole in gioco (in questo caso 4), come raffigurato in Fig. 1. Si aggiungano poi degli archi tra tutti (e solo tra quelli) i nodi che rappresentano letterali complementari. Ebbene, ϕ è soddisfacibile se e solo se il grafo ottenuto ammette un insieme indipendente di 4 (in generale, il numero delle clausole) 202 21. RIDUZIONI E NP-COMPLETEZZA ✓✏ ¬X1 s X1 s ✁❅ ❆ ✁❆ ✒✑ ✁ ❆❅ ✁ ❆ ✁ ❆ ❅ ✁ ❆ ✓✏ s s ✁ ❆ s ✁ ❆s X5 ❅ X2 ❅ ✒✑ X X3 4 ❅ ❅ ✓✏ ✓✏ X 4s s ¬X4 s ¬X5 ❅ ¬X2 s ❆ ✁ ❆ ✁ ❅✒✑ ✒✑ ❆ ✁ ✁ ❅ ❆ ❆ ✁ ❅❆ ✁ ❆s✁ ❅❆s✁ X1 ¬X1 Figure 1. Grafo usato per la riduzione 3SAT  IS elementi. I nodi cerchiati in figura costituiscono un insieme indipendente di 4 nodi e forniscono una soluzione per ϕ: X1 = true, X2 = false, X4 = false, X5 = true. Il valore di X3 non ha importanza.  Elenchiamo altri problemi NP-completi molto noti. Definizione 21.17 (CLIQUE). Input: Un grafo non diretto G = hN, Ei e un intero k. Problema: Stabilire se esiste un sottografo di G di k elementi che sia un grafo completo. Si osservi che CLIQUE su G = hN, Ei corrisponde a IS su G ′ = hN, (N×N)\Ei Definizione 21.18 (NODE COVER). Input: Un grafo non diretto G = hN, Ei e un intero k. Problema: Stabilire se esiste un insieme di nodi C ⊆ N di k elementi tale che per ogni arco {i, j} di E i ∈ C o j ∈ C. Si osservi che I è un IS di G = hN, Ei se e solo se C = N \ I è un NODE COVER di G. Esercizio 21.19. Si formalizzi la dimostrazione di NP-completezza di CLIQUE e NODE COVER. Anche il problema HP già discusso in precedenza è NP-completo (difficile riduzione da 3SAT—omessa). Vediamo ora la versione decisionale del noto problema del commesso viaggiatore (Traveling Salesman Problem). 3. PROBLEMI NP-COMPLETI 203 Definizione 21.20 (TSP(k)). Input: Un grafo non diretto G = hN, Ei, una funzione di costi c : N −→ N, e un intero k. Problema: Stabilire se esiste un circuito che passa esattamente una volta per ogni nodo e di costo complessivo ≤ k. Esercizio 21.21. Assumendo la NP-completezza di HP, si formalizzi la dimostrazione di NP-completezza di TSP(k). Definizione 21.22 (k-COLORING). Input: Un grafo non diretto G = hN, Ei e un intero k. Problema: Stabilire se esiste un assegnamento col : N −→ {1, . . . , k} tale che per ogni arco {i, j} di E col(i) 6= col(j). Per k = 2 il problema sta in P (verificare per esercizio). Per k = 4 e grafi planari, il problema ha sempre soluzione. Per grafi qualunque, invece: Teorema 21.23. 3-coloring è NP-completo. Proof. L’appartenenza a NP è immediata. Riduciamo NAESAT a 3-coloring. Come nella dimostrazione del Teorema 21.16, mostriamo la riduzione su un esempio. Sia ϕ = (X1 ∨ X2 ∨ ¬X3 ) ∧ (X1 ∨ ¬X2 ∨ ¬X3 ) ∧ (¬X1 ∨ X2 ∨ ¬X3 ) ∧ (¬X1 ∨ ¬X2 ∨ X3 ) Costruiamo un grafo nel seguente nodo. Sia µ un nodo. Per ogni variabile vengono introdotti due nodi (uno per Xi uno per ¬Xi ). Si inseriscono archi per avere i triangoli µ, Xi , ¬Xi . Il nodo µ è comune a tutti i triangoli. Per ogni clausola vengono introdotti tre nodi C1 , C2 , C3 ed archi tra loro per ottenere un triangolo per ogni clausola. Ogni nodo è associato ad uno dei letterali della clausola. Si aggiungono dunque degli archi tra tutti (e solo tra quelli) i nodi dei triangoli sopra i nodi relativi ai letterali corrispondenti. Si mostra che σ è una soluzione di ϕ, allora la funzione che mette il colore 0 ai nodi dei triangoli variabile corrispondenti a false, il colore 1 a quelli corrispondenti a true e il colore 2 al nodo µ e a un nodo per ogni triangolo clausola è un 3-coloring. Viceversa, se non esiste un 3-coloring, non esiste un assegnamento di verità per σ.  Esercizio 21.24. Si consideri il seguente problema ST (k): Siano dati un grafo non diretto G = hN, Ei e un intero k ∈ N. Si vuol sapere se esiste uno SPANNING TREE per G tale che nessun nodo dell’albero abbia grado maggiore di k. In altri termini, se esiste E ′ ⊆ E t.c.: • |E ′ | = |N| − 1, • il grafo hN, E ′ i è connesso, e • nessun nodo di N è incluso in più di k archi di E ′ . Si mostri che ST (k) è NP-completo. 204 21. RIDUZIONI E NP-COMPLETEZZA Esercizio 21.25. Si dimostri la NP-completezza del problema del cruciverba, ovvero, dato un insieme di parole p1 , . . . , pk in un dato alfabeto A (N.B. potrebbe essere un alfabeto con più delle 26 lettere che usiamo di solito!) e uno schema di cruciverba C (una matrice rettangolare di caselle bianche e nere) con le caselle nere già fissate, stabilire se esiste un modo per scrivere le k parole nello schema, con le regole di incrocio standard dei cruciverba, che riempia esattamente tutte le caselle bianche. Per l’esercizio sopra può far comodo conoscere ed utilizzare il seguente problema, che è NP-completo: Exact Cover by 3 sets: Dato un insieme U = {1, . . . , 3 · m}, e dati n insiemi S1 , . . . , Sn ⊆ U, con |S1 | = |S2 | = · · · = |Sn | = 3 stabilire se esistono m insiemi (disgiunti) tra S1 , . . . , Sn tali che la loro unione sia U. Esercizio 21.26. Si consideri il problema che chiamiamo ZSAT: Input: Una congiunzione di clausole C1 ∧ · · · ∧ Cn ove ogni Ci è una disgiunzione di esattamente i letterali (non necessariamente diversi tra loro). Problema: Stabilire se esiste un assegnamento di verità per le variabili che compaiono nella formula che rende l’istanza vera. Si dimostri che ZSAT è NP-completo. Esercizio 21.27. Si consideri il problema della fila tra detenuti (FTD): vi sono n detenuti ed è nota una relazione x odia y tra coppie di detenuti. Il problema è: esiste un modo per mettere in fila gli n detenuti in modo tale che non vi sia mai un detenuto con uno che lo odia alle spalle? Si mostri che il problema FTD è NP-completo. Bibliography [1] P. Aczel. Non-well-founded sets, volume 14 of CSLI Lecture Notes. Stanford University Press, 1988. [2] M. Aiello, A. Albano, G. Attardi, and U. Montanari. Teoria della Computabilità, logica, teoria dei linguaggi formali. Materiali didattici ETS, Pisa, 1976. [3] H. P. Barendregt. The Lambda Calculus, volume 103 of Studies in Logic and the Foundations of Mathematics. Elsevier North-Holland, 1984. [4] A. Church. A note on the Entscheidungsproblem. J. of Symbolic Logic, 1:40–41, 1936. [5] S. A. Cook. The Complexity of Theorem Proving Procedures. In Proceedings of ACM Symposium on the Theory of Computing, pages 151–158. ACM Press, 1971. [6] N. J. Cutland. An Introduction to Recursive Function Theory. Cambridge University Press, 1980. [7] H. B. Enderton. A mathematical introduction to logic. Academic Press, 1973. 2nd printing. [8] G. Frege. —. In J. Van Heijenoort, editor, From Frege to Gödel: A Source Book in Mathematical Logic, 1879-1931. Harvard Univ Press, 2001. [9] M. R. Garey and D. S. Johnson. Computers and Intractability – A Guide to the Theory of NP-Completeness. W. H. Freeman and Company, New York, 1979. [10] K. Gödel. —. In Solomon Feferman, Stephen C. Kleene, John W. Dawson, and Robert M. Solovay, editors, Kurt Gödel Collected Works: Publications 1929-1936, volume 1. Oxford University Press, 1999. [11] C. Hankin. Lambda Calculi: A Guide for Computer Scientists, volume 3 of Graduate Texts in Computer Science. Springer-Verlag, 1996. [12] M. Hirvensalo. Quantum Computing. Springer-Verlag, 2001. [13] J. E. Hopcroft and J. D. Ullman. Introduction to Automata Theory, Languages and Computation. Addison-Wesley, 1979. [14] N. D. Jones and Y. E. Lien. New problems complete for nondeterministic log space. Mathematical Systems Theory, 10:1–17, 1976. [15] N.D. Jones. Computability and Complexity. MIT Press, 1997. [16] W. Just and M. Weese. Discovering modern set theory. I: The basics, volume 8 of Graduate studies in mathematics. American mathematical society, 1996. [17] S. C. Kleene. Introduction to Metamathematics. North-Holland, 1996. [18] K. Kunen. Set Theory. An Introduction to Independence Proofs. Studies in Logic. North Holland, Amsterdam, 1980. [19] G. Longo. Metodi per il trattamento dell’informazione. Servizio Editoriale Universitario di Pisa, 1984. [20] E. Mendelson. Introduction to Mathematical Logic. Van Nostrand, Princeton, N. J., 1979. [21] J. C. Mitchell. Foundations for Programming Languages. Foundations of Computing. MIT Press, 1996. [22] P. Odifreddi. Classical Recursion Theory. Studies in Logic and the Foundations of Mathematics. North-Holland, 1989. [23] C. H. Papadimitriou. Computational complexity. Addison-Wesley, 1994. 205 206 BIBLIOGRAPHY [24] G. Paun, G. Rozenberg, A. Salomaa, and W. Brauer. DNA Computing: New Computing Paradigms. Texts in Theoretical Computer Science. Springer-Verlag, 1998. [25] R. Péter. Rekursive funktionen. Akadémiai Kiadó, Budapest, 1953. [26] G. Plotkin. A structural approach to operational semantics. DAIMI-19 Aarhus University, Denmark, 1981. [27] H. J. Rogers. Theory of Recursive Functions and Effective Computability. The MIT Press, 1988. [28] J.R. Shoenfield. Axioms of set theory. In J. Barwise, editor, Handbook of Mathematical Logic, pages 321–344. North-Holland, Amsterdam, 1977. [29] M. Sipser. The History and Status of the P versus NP Question. In Proceedings of ACM Symposium on the Theory of Computing, pages 603–618. ACM Press, 1992. [30] A. M. Turing. On computable numbers with an application to the Entscheidungsproblem. Proc. of the London Math. Society, 42(2):230–265, 1936-7. [31] G. Winskel. The formal semantics of programming languages: an introduction. MIT press, 1993.