Kapitel 4 Theoretische Informatik 1 28. Januar 2003 Berechenbarkeit 1 Fahrplan 2 1.1.2 Turing-Maschinen Turings Vorstellung von Algorithmen: -werden ausgeführt mit Bleistift, Radiergummi und (in unbegrenzten Mengen vorhandenem) Papier. 4.1 Präzisierung des Algorithmenbegriffs -Papier ist unterteilt in Felder (z.B. Karos). In jedem Karo steht ein Symbol (anfangs das “leere Symbol”) 4.2 Die Unentscheidbarkeit des Halteproblems 4.3 Die Beweismethode Diagonalisierung -Schritt: Ein bestimmtes Karo auswählen und lesen und/ oder schreiben 4.4 Weitere unentscheidbare Probleme 4.5 Die Unentscheidbarkeit der Erfüllbarkeit in der Prädikatenlogik 3 Turing-Maschine: Beispiel δ X = {|,$,*} (* = Leerzeichen) Z = {z0,...,z4} z0 * | z0 | z0 * Erläuterung | * $ z1, $, R z0, *, S z0, $, S Starte Iteration z1 z1, |, R z2, *, R z0, $, S nach R bis zu * z2 z2, |, R z3, |, R z0, $, S nach R, + ein | z3 z3, | , S z4, |, L z0, |, S ein weit. | z4 z4, |, L z4, *, L z0, |, R zurück zum $ * * * * * -lesen: Abhängig vom Symbol im ausgewählten Karo eine Entscheidung treffen (z.B. welcher Schritt als nächstes auszuführen ist) -schreiben (das Symbol am ausgewählten Karo wegradieren und 4 durch ein neues ersetzen) Turing-Berechenbarkeit Eine n-stellige zahlentheoretische Funktion heißt Turing-berechenbar, falls es eine Turing-Maschine M gibt, die, gestartet auf einer Konfiguration * * | | ... | * | | ... | * ... * | | ... | * * * * * * * x2+1 xn+1 z0 x1+1 -nicht terminiert, falls [x1,...,xn] Df -in der Konfiguration * * | | * 5 | * | | | * * | | | * | | | * * x2+1 x1+1 xn+1 f(x1,...xn)+1 terminiert, falls [x1,...,xn] ∈ Df 6 1 Partiell-rekursive Funktionen Die Church-sche These Anfangsfunktionen N, Cij, Iij Substitution φ1,...,φn m-stellig ψ n-stellig intuitiv-anschaulich berechenbar Der heikle Punkt der Churchschen These χ m-stellig χ(x1,...,xm) := ψ(φ1(x1,...,xm),...,φn(x1,...xm)) offensichtlich partiellTuring= rekursiv berechenbar = Formaler Begriff 3 Primitive Rekursion φ n-stellig χ (n+1)-stellig ψ(n+2)-stellig = ......... beweisbar 7 Jenseits von primitiv rekursiv... χ(x1,...,xn,0) = φ(x1,....,xn) χ(x1,...,xn,i+1) = ψ(x1,...,xn,i,χ(x1,...,xn,i)) “primitiv-rekursive Funktionen” Finally: Der dritte Induktionsschritt Es gibt intuitiv-anschaulich berechenbare Funktionen, die nicht primitiv rekursiv sind: µ-Rekursion z.B. die von R. Peter als nicht primitiv rekursiv nachgewiesene totale Funktion Falls φ eine (n+1)-stellige partiell-rekursive Funktion ist,so ist die n-stellige Funktion ψ, die wie folgt definiert ist, partiell rekursiv peter(0,n) = n+1 peter(m+1,0) = peter(m,1) peter(m+1,n+1) = peter(m,peter(m+1,n)) peter(0,n) = n+1 peter(1,n) = n+2 peter(2,n) = 2n+3 peter(3,n) = 2n-3-3 peter(4,0) = 13 peter(4,1) = 65536 peter(4,2)=265536-3 8 ψ(x1,...,xn) ist - das kleinste j mit φ(x1,...,xn,j) = 0, falls - ein solches j existiert und - für alle k<j die Werte φ(x1,...,xn,k) definiert sind, - nicht definiert, sonst. Einige Werte: 9 psi(x1,...,xn){ y=0; while(phi(x1,...,xn,y) != 0) { y++; } return y; } Schreibweise: ψ(x1,...,xn) = µj[φ(x1,...,xn,j)=0] 10 Allgemein Beispiel Die Menge der partiell-rekursiven Funktionen entspricht genau den Funktionen, die sich in “normalen” Programmiersprachen mit -Zuweisung (Expressions mit +,-,*,DIV,MOD) -Sequenz (...;...) -if-then-else -Zählschleifen (for ...) -while-Schleifen und/oder Rekursion implementieren lassen Sei f(x,y) = 7 –• x • y und g(x) = µj[f(x,j)=0] Dann ist g(1) = 7, weil f(1,0)=7, f(1,1)=6,...,f(1,6)=1, f(1,7)=0 g(2) = 4, weil f(2,0)=7, f(2,1)=5, f(2,2)= 3, f(2,3)=1, f(2,4)=0 g(200) = 1, weil f(200,0)=7, f(200,1) = 0 g(0) nicht definiert, weil f(0,0)=7, f(0,1)=7, ...., f(0,i)=7,.... 11 µ-Rekursion kann aus einer totalen Funktion eine partielle, nicht totale Funktion generieren, aber auch aus einer partiellen, nicht totalen Funktion eine totale. Alle partiell-rekursiven Funktionen sind int.-ansch. berechenbar 12 (Beweis: mitgelieferte Code-Fragmente). 2 Zur Äquivalenz formaler Berechenbarkeitsbegriffe Bezeichnungen und Inklusionen ANF – Menge der Anfangsfunktionen PRIM – Menge der primitiv-rekursiven Funktionen PREK – Menge der partiell-rekursiven Funktionen REK – Menge der (Allgemein-)rekursiven Funktionen = Menge der totalen partiell-rekursiven Funktionen Wollen jetzt, um die Church-sche These zu erhellen, uns Ideen des Beweises der Äquivalenz zweier formaler Berechnbarkeitsbegriffe erarbeiten. Satz: Eine zahlentheoretische Funktion ist Turing-berechenbar genau dann, wenn sie partiell-rekursiv ist. ANF Addition PRIM REK peter PREK Beweis Teil 1: Jede partiell-rekursive Funktion ist Turing-berechenbar die nirgends def. Funktion Beweis Teil 2: Jede Turing-berechenbare Funktion ist partiell-rekursiv 13 Jede partiell-rekursive Funktion ist Turing-berechenbar 14 mit Turing-Maschinen geht’s genauso Eine analoge Aussage wäre: Jede partiell-rekursive Funktion ist Pseudocode-berechenbar: Nachfolger: Argument kopieren (das Verdoppelungsbeispiel zeigt die Idee, wie), dann einen Strich anhängen, fertig. Beweis durch Induktion Induktionsanfang: Induktionsschritt: Konstanten: Einfach j Striche generieren N(x) { y = x+1; return y;} Cij(x1,..,xi){ return j;} Iij(x1,..,xi){ return xj;} chi(x1,....,xm) { y1 = phi_1(x1,..,xm); ... yn = phi_n(x1,..,xm); z = psi(y1,...,yn); return z; } chi(x1,...,xn,i) { y = phi(x1,...,xn); for(j=0;j<i;j++) { y = psi(x1,...,xn,j,y);} return y; } psi(x1,...,xn){ y=0; while(phi(x1,...,xn,y) != 0) { y++; } return y; } 15 mit Turing-Maschinen geht’s genauso Für die Induktionsschritte braucht man “Unterprogrammrufe”. Das geht wegen folgender Beobachtung: Wenn f T.-berechenbar ist, dann gibt es auch eine T.-Maschine, die f berechnet und dabei nie den Kopf um mehr als 1 nach links über die Argumente hinausfährt (“normierte” T.-Maschine) Idee: Anfang und Ende des “Arbeitsbereiches” durch spezielle Symbole “A” und “E” kennzeichnen. - Kopf auf “E”: “E” eins nach rechts verschieben, ein * an die alte Position, fortsetzen wie bisher - Kopf auf “A”: den gesamten Arbeitsbereich um 1 nach rechts kopieren, an die alte Position ein *, weiter wie vorgesehen Unterprogrammrufe: Argumente ans rechte Ende kopieren, Sprung zum Anfangszst. der Unterprogrammmaschine, 17 statt STOP Rücksprung ins Hauptprogramm Argumentauswahl: Das richtige Argument kopieren: man muss lediglich “mitzählen”, über wieviele “*” der Kopf bewegt werden muss, um zur richtigen Strichfolge zu gelangen. Das erreicht man über Wechsel von Zuständen: | * z1 [z1,|,R] [z2,*,R] z2 [z2,|,R] [z3,*,R] z3 [z3,|,R] [z4,*,R] 16 z4 .... Jede Turing-berechenbare Funktion ist partiell rekursiv Ebenfalls eine ideenspendende Analogie: Jede Turing-berechenbare Funktion ist Pseudocode-berechenbar f(x1,...,xn) - Anfangskonfiguration aufs Band schreiben z := z0; /* band und kopf sind vorgegeben */ while true do x := band[kopf]; [z’,x’,b] := δ(z,x); band[kopf] := x’; switch(b) of case N: skip; case L: kopf := kopf – 1; case R: kopf := kopf + 1; case S: exit; end z := z’; end - Resultatstriche der Endkonfiguration zählen – return result; 18 3 Mit PREK geht’s genauso, nur: Gödelisierung Hauptschwierigkeit: Partiell-rekursive Funktionen haben keine strukturierten Datenstrukturen! Zunächst: eine nat. Zahl für “kopf” reicht, wg. Überlegungen zu normierten Turing-Maschinen Z und X können ebenfalls mit nat. Zahlen dargestellt werden S zi 0 i L X = {x1,...,xn} xj * 0 | 1 N 2 R 3 1 j 19 Die Gödelzahl einer Turing-Maschine ... ist die Gödelisierung ihrer Zustandsüberführungsfunktion δ: Schritt 1: einen Tabelleneintrag [z’,x’,b] gödelisieren Schritt 2: Eine Tabellenzeile gödelisieren Schritt 3: Die Tabellenspalten gödelisieren δ gö( | * z0 z1,$,R z0, *, S z0, $, S z1 z1, |, R z2, *, R z0, $, S z2 z2, |, R z3, |, R z3 z3, | , S z4, |, L z4 (* z4, |, L z4, *, L 0, | 1, $ 2) 23 21•31•53 22•30•53 x3 x8 x2 x0 x2 x0 x0 0 2 2 5 3 7 4 5 11 13 6 17 .... 1 3 gö([x3,x8,x2,x0,x2,x0,x0,...]) = 23•38•52•70•112•130•170•... Nehmen 0 als Code für das Leerzeichen, dann hat jede für Turing-Berechenbarkeit relevante Bandbeschriftung einen wohldefinierten Code. 20•32•50 •2 •5 •33 ) = 322•31•53•223•31•53•520•32•50 z0, $, S •5 23•31•50•224•31•51•520•31•50 z0, |, S •73 z0, |, R 24•31•51•224•30•51•520•31•53 •113 21 Der Kleene-sche Darstellungssatz Eine Zusammenfassung der Implementation Turingberechenbarer Funktionen durch partiell-rekursive Funktionen Satz: Es gibt eine primitiv-rekursive Funktion α, so daß zu jeder Stellenzahl n eine primitiv-rekursive n+2-stellige Funktion β existiert, so daß zu jeder Turing-berechenbaren Funktion f eine Zahl t existiert, so daß für alle nat. Zahlen x1,....,xn gilt f(x1,....,xn) = α( µj( β(t,x1,...,xn,j) = 0) ) α n β f t x1... xn 20 Zugriff auf Komponenten gödelisierter Daten Beide Funktionen sind primitiv-rekursiv (d.h. man braucht in “normalen” Sprachen nur for- aber keine while-Schleifen): For-Schleifen reichen für p, weil p(n+1) zwischen p(n)+1 und p(n)!+1 liegen muss, und ein Primzahltest für z über Restberechnungen bei Division duch 2 ... z-1 implementiert werden kann. For-Schleifen reichen für exp, weil z nicht mehr als z mal durch die n-te Primzahl ohne Rest dividierbar ist. 22 α n β f t x1... xn µj(β β(t,x1,...,xn,j)=0) f(x1,...,xn) = α(µ gö([z23,x45,L]) = 223•345•51 [23,45,1] Brauchen: 1. p(n) = die n-te Primzahl 2. exp(n,z) = der Exponent der n-ten Primzahl in z 21•32•53•220•30•50•520•32•50 $ [z23,x45,L] (leer, wg, normiert) Wie implementiert man also band[...], δ[z,x], [z’,x’,b]? Z = {z1,...,zm} = Kodierung komplexer Daten als einfache nat. Zahlen, basierend auf Eindeutigkeit der Primzahlzerlegung 23 f(x1,...,xn) var kopf, band, z,z’,x,x’,b; - Anfangskonfiguration x1,....,xn aufs Band schreiben z := z0; while true do x := band[kopf]; [z’,x’,b] := δ(z,x); band[kopf] := x’; switch(b) of case N: skip; case L: kopf := kopf – 1; case R: kopf := kopf + 1; case S: exit; end z := z’; end - Resultatstriche der Endkonfiguration zählen – return result; 24 4 Konsequenzen aus dem Kleeneschen Darstellungssatz Universelle Turing-Maschinen α n β f t x1... xn f(x1,....,xn) = α( µj( β(t,x1,...,xn,j) = 0) ) 1. (unwichtig, aber lustig): Jede berechenbare Funktion kann so programmiert werden, daß nur eine einzige while-Schleife verwendet wird, ansonsten nur for-Schleifen, Zuweisungen und if-then-elses. Spezialfall für n=1: α β f t x f(x) = α( µj( β(t,x,j) = 0) ) 2. (wichtig) Jede Turing-berechenbare Funkion ist partiell-rekursiv; dies ist eine wichtige Erhärtung der Church-schen These Also: A f t x f(x) = A(t,x) 3. (wichtig; hier spielt die Quantorenreihenfolge eine entscheidende Rolle) Es gibt sogenannte “universelle” Turing-Maschinen, das sind Maschinen, die die Arbeit jeder beliebigen Turing-Maschine nachvollziehen 25 können Veranschaulichung 1 0 1 2 3 4 5 6 0 0 3 n.d. 3 0 2 910 222 42 1 2 3 5 7 11 13 17 19 23 2 0 0 0 0 0 0 0 0 0 3 1 2 3 4 5 6 7 8 9 4 n.d n.d n.d n.d n.d n.d n.d n.d n.d 5 1 4 6 7 n.d. 7 7 111 939 933 382 281 810 n.d. 729 1 8 900 100 200 300 400 929 039 930 291 16 25 n.d. 7 36 49 n.d. 7 64 Setzen A(t,x) = α( µj( β(t,x,j) = 0) ) Interpretiert: Es gibt eine 2-stellige Turing-berechenbare Funktion A, so daß der Wert jeder einstelligen Turing-berechenbaren Funktion f an jeder beliebigen Stelle x dadurch berechnet werden kann, daß A an der Stelle t und x berechnet wird, wobei t die Gödelzahl einer f berechnenden Turing-Maschine ist. 26 Veranschaulichung 2 A 9 7 8 Programm als t Zahl (ASCII statt Gödelisierung) 81 n.d. 7 C10 x Daten A die nirgends definierte Funktion f(x) = (x+1)2 27 ....jede einstellige berechenbare Funktion taucht als Zeile in A auf! Ein Rechner, der alle Progamme abarbeiten kann 28 4.2 Die Unentscheidbarkeit des Halteproblems Bemerkung Ausgangspunkt: Eine universelle TM A. Es gibt sogar eine universelle Turing-Maschine, mit der sich alle Funktionen beliebiger Stelligkeit simulieren lassen. Einziger Trick: Das Halteproblem: Entscheide die Menge derjenigen geordneten Paare [t,x], für die A(t,x) definiert ist Mehrstellige Argumenttupel zu einem Argument “zusammengödelisieren” (oder, informaler: Entscheide, ob eine gegebene Turing-Maschine bei gegebener Eingabe terminiert) Wir zeigen durch einen Widerspruchsbeweis, daß das Halteproblem nicht entscheidbar ist, also die Funktion 1, falls A(t,x) definiert h(t,x) = 0, falls A(t,x) nicht definiert, nicht berechenbar ist. 29 30 5 Angenommen, h wäre berechenbar,... dann wäre sicherlich auch die einstellige Funktion g mit 0, falls A(x,x) nicht definiert g(x) = A(x,x) + 1, falls A(x,x) definiert, berechenbar: g(x) = if h(x,x) > 0 then A(xx) + 1 else 0. g wäre also total. Wenn nun g berechenbar wäre, müsste g irgendwo als Zeile in A auftauchen, es gäbe also ein tg so, daß für alle x g(x) = A(tg,x) ist. Nun ist aber A(tg,tg) = g(tg) = A(tg,tg) + 1, Widerpruch! 31 (weil Zeile tg von A g repr.) (nach Def. von g) Veranschaulichung A 0 1 2 3 4 5 6 0 0 3 n.d. 3 0 2 910 222 42 1 2 3 5 7 11 13 17 19 23 2 0 0 0 0 0 0 0 0 0 3 1 2 3 4 5 6 7 8 9 4 5 n.d. n.d. n.d. n.d. n.d n.d. n.d. n.d. n.d. . 1 4 9 16 25 36 49 64 81 6 7 7 111 939 933 382 281 810 n.d. 729 1 8 900 100 200 300 400 929 039 930 291 tg 1 n.d. 7 4 1 n.d. 7 5 0 n.d. 7 37 8 7 8 tg n.d. 7 730 292 Wid 32 6