Berechenbarkeit und Komplexität Vorlesung SS 2004 1. Einführung I. Berechenbarkeitstheorie, typische Fragen: wann ist eine Funktion berechenbar? wie lässt sich der intuitive Berechenbarkeitsbegriff formal präzisieren? wie verhalten sich unterschiedliche Formalisierungen zueinander? sind alle mathematisch exakt beschreibbaren Funktionen berechenbar? II. Komplexitätstheorie, typische Fragen: wie lässt sich die Komplexität eines Problems beschreiben? Was sind relevante theoretische Konzepte hierfür? hier zunächst vorläufige Grobgliederung von I: 1. Motivation, was will Berechenbarkeitstheorie, unlösbare Probleme, Whlg. Turingmaschinen 2. Turing-Berechenbarkeit, Mehrbandturingmaschinen, Äquivalenz zu normaler TM, Verknüpfung von TMs 3. LOOP, WHILE und GOTO - Berechenbarkeit 4. Äquivalenz TM, WHILE, GOTO 5. Rekursive Funktionen, Äquivalenz primitiv-rekursiv - LOOP - berechenbar 6. Äquivalenz -rekursiv - WHILE - berechenbar, Ackermannfunktion 7. Unentscheidbarkeit, Halteproblem 8. Weitere unentscheidbare Probleme, Satz von Gödel 1.1 Was will Berechenbarkeitstheorie? Zur Motivation: Frage: gibt es ein Programm, das bei Eingabe einer Zahl n als Ausgabe das kürzeste Programm liefert, das (ohne Eingabe) n ausdruckt? Antwort: Nein! Beweis: Voraussetzungen: zu jedem n gibt es ein Programm, das n berechnet, Länge eines Programms bestimmbar, es gibt nur endlich viele verschiedene Programme bestimmter Länge, Konstanten codiert zu Basis 2. Nehmen wir an, es gäbe so ein Programm. Nennen wir es P. Aus P ließe sich zu jedem m folgendes Programm Qm konstruieren: 1 Start y:= 0 Finde P(y) z:= Länge von P(y) z<m y:= y+1 Halte mit Output y Was tut Qm? Berechnet kleinstes i, so dass i nicht durch Programm kürzer als m ausgedruckt werden kann. Länge der Programme Qm? Konstant bis auf Darstellung von m in Programmtext. Also gibt es c mit Länge von Qm = c + Länge der Darstellung von m ≤ c + 1 + log m Für genügend großes m gilt damit: Länge von Qm < m. Aber dann druckt Qm kleinste Zahl, die nicht mit Programm kürzer als m gedruckt werden kann, ist aber selbst kürzer. Widerspruch!! Probleme und Sprachen: in der theoretischen Informatik werden Probleme und Sprachen (genauer: das Enthaltensein eines Wortes in einer Sprache) oft gleich gesetzt: Beispiel: ist eine vorgegebene natürliche Zahl n Primzahl? entspricht: gehört Binärcodierung von n zur Sprache L = {w {0,1}* | w Binärcodierung einer Primzahl} oder: ist f(x,y) = z für eine 2stellige Funktion f? entspricht: gehört xb$yb$zb , wobei ab Binärcodierung von a ist, zur Sprache L = {w1$w2$w3 | wi {0,1}* Binärcodierungen von n1, n2, n3 so dass f(n1,n2) = n3} 2 Wie viele Sprachen über einem Alphabet gibt es? Eine Menge M heißt abzählbar unendlich: gleichmächtig mit N, d.h. es gibt bijektive Funktion f: N -> M höchstens abzählbar: endlich oder abzählbar unendlich * abzählbar: Beispiel = {a,b} , a, b, aa, ab, ba, bb, aaa, aab, aba, abb, baa, bab, bba, bbb, aaaa, ... Pow(*) überabzählbar: Beweis (Diagonalverfahren): Sei f(0), f(1), ... Abzählung von * Sei g(0), g(1), ... Abzählung von Pow(*) Definiere D = {f(j) | f(j) g(j)} Da D eine Sprache über ist, müsste es ein n geben mit g(n) = D. Jetzt gilt: f(n) D <=> f(n) g(n) <=> f(n) D! Widerspruch Veranschaulichung: f(0) f(1) f(2) f(3) f(4) f(5) … g(0) 0 x x x x x … g(1) x 1 x x x x … g(2) x x 1 x x x … g(3) x x x 0 x x … g(4) x x x x 0 x … g(5) x x x x x 1 … … der j-te Wert in der i-ten Zeile gibt an, ob f(j) Element der Sprache g(i) ist: x = 1 bedeutet ja, x = 0 bedeutet nein. Die oben definierte Sprache D enthält f(n) genau dann, wenn in der Diagonale in Zeile n eine 0 steht (im Beispiel wären f(0), f(3), f(4), ... enthalten). Die Sprache D unterscheidet sich von jeder Sprache g(k) in der Abzählung an der k-ten Stelle. Damit ist D selbst nicht in der Abzählung enthalten. Es gibt also überabzählbar viele Sprachen, aber nur abzählbar unendlich viele Programme in einer vorgegebenen Programmiersprache. Also: es kann nicht für jede Sprache L ein Programm geben, das L akzeptiert. 3 Was heißt berechenbar? Intuitive Vorstellung: es gibt ein Verfahren, das bei bestimmter Eingabe gesuchte Ausgabe produziert. Vage, muss präzisiert werden (dabei sollen Größe des Speichers und Schnelligkeit der Maschine keine Rolle spielen) Bisher: Automaten, die "berechnen" ob w L. Entspricht Funktion fL: * -> {0,1}, die 1 liefert gdw. w L, 0 sonst. Des Weiteren: Funktionen über N n-stellige Funktion: Teilmenge F von Nn+1 so dass (x1, ..., xn, q) F und (x1, ..., xn, r) F impliziert q = r. totale Funktion: für jedes x1, ..., xn gibt es y mit (x1, ..., xn, y) F. nicht-totale Funktionen heißen partiell. Funktion f ist berechenbar, wenn es ein Verfahren (Algorithmus, Programm) gibt, das gestartet mit Eingabe x1, ..., xn, nach endlich vielen Schritten mit Ausgabe f(x1, ..., xn) stoppt. Ist f(x1, ..., xn) undefiniert, so läuft das Verfahren endlos. Beispiele: 1. total undefinierte Funktion berechenbar: INPUT(n); REPEAT UNTIL FALSE; 2. h(n) = 1 0 falls 5 n-mal hintereinander in vorkommt. sonst. berechenbar? Ja, denn falls es beliebig lange 5er-Ketten in gibt, dann ist h(n) = 1 für alle n, berechenbar falls es längste 5er-Kette gibt und diese die Länge k hat, dann ist h(n) = 1 0 falls n k sonst Auch diese Funktion ist für alle k berechenbar. Wir wissen nur nicht, welche dieser berechenbaren Funktionen die richtige ist. Merke: für die Berechenbarkeit einer Funktion genügt es, dass ein entsprechendes Verfahren existiert, wir müssen es nicht angeben können!! 3. ähnlich: i(n) = 1 0 falls det. und nichtdet. LBAs äquivalent sonst berechenbar, denn sowohl die konstante Funktion 1, wie die konstante Funktion 0 sind berechenbar. 4 4. Sei r beliebige reelle Zahl, definiere fr(n) = 1 0 falls n Anfangsstück der Dezimalbruchentwicklung von r ist sonst Kann nicht für alle r berechenbar sein, denn: Rechenverfahren immer durch endlichen Text beschrieben, deshalb abzählbar. Dagegen überabzählbar viele reelle Zahlen. Präzisierungsvorschläge für den intuitiven Berechenbarkeitsbegriff : Turing-Berechenbarkeit, While-Berechenbarkeit, Goto-Berechenbarkeit, -Rekursivität Hauptresultat: alle äquivalent Churchsche These: Die durch die Begriffe Turing-Berechenbarkeit (While-Berechenbarkeit, GotoBerechenbarkeit, -Rekursivität) erfassten Funktionen sind genau die im intuitiven Sinne berechenbaren Funktionen. 2. Turing-Berechenbarkeit Eine der Standardpräzisierungen des Berechenbarkeitsbegriffs verwendet TMs. Wiederholung: Def.: Eine Turingmaschine ist ein 7-Tupel M = (Z, , , , z0, , E). Hierbei ist Z endliche Zustandsmenge Eingabealphabet Arbeitsalphabet Überführungsfunktion z0 Anfangszustand Leerzeichen (Blank) E Endzustände falls M deterministisch: falls M nichtdeterministisch: : Z Z {L,R,N} : Z Pot(Z {L,R,N}) Def.: Eine Konfiguration einer TM ist ein Wort k *Z*. Die Eingabe x der TM steht auf dem Band: Startkonfiguration z0x Def.: Die Konfigurationsübergangsrelation |-- ist wie folgt definiert: a1...amzb1...bn |-- a1...amz'cb2...bn a1...amzb1...bn |-- a1...amcz'b2...bn a1...amzb1...bn |-- a1...am-1z'amcb2...bn falls (z', c, N) (z,b1), m 0, n 1 falls (z', c, R) (z,b1), m 0, n 2 falls (z', c, L) (z,b1), m 1, n 1 5 Sonderfälle, bei denen neues Feld besucht wird: a1...amzb1...bn |-- a1...amcz' zb1...bn |-- z'cb2...bn falls (z', c, R) (z,b1), m 0, n = 1 falls (z', c, L) (z,b1), m = 0, n 1 Def.: Die von einer TM M akzeptierte Sprache ist: T(M) = {x * | z0x |--* z, z E, , * } Def.: Eine Funktion f: Nk -> N heißt Turing-berechenbar, wenn es eine deterministische TM M gibt, so dass für alle n1, ..., nk, m N gilt: f(n1, ..., nk) = m genau dann wenn z0bin(n1)# bin(n2)#... #bin(nk) |--* ...zebin(m) ... wobei ze Endzustand, bin(x) Binärdarstellung von x (ohne führende Nullen). Def.: Eine Funktion f: * -> * heißt Turing-berechenbar, wenn es eine (deterministische) TM M gibt, so dass für alle x, y * gilt: f(x) = y genau dann wenn z0x |--* ...zey ... wobei ze Endzustand. Beispiel: TM, die die Funktion f(x) = x+1 berechnet: M3 = ({q0, q1, q2, qf}, {0,1}, {0,1, }, , q0, , {qf}) mit (q0,0) = (q0,0,R) (q0,1) = (q0,1,R) (q0,) = (q1,,L) (q1,0) = (q2,1,L) (q1,1) = (q1,0,L) (q1,) = (qf,1,N) (q2,0) = (q2,0,L) (q2,1) = (q2,1,L) (q2,) = (qf, ,R) Intuitiv: Wandern an rechtes Ende der Eingabe, Ersetzen der am weitesten rechts stehenden 0 durch 1, aller rechts davon stehenden 1en durch 0; wenn keine 0 vorkommt, dann 1 vor Eingabe schreiben und alle 1 en in 0 umwandeln: entspricht +1 in Binärdarstellung q010 |-- 1q00 |-- 10q0 |-- 1q10 |-- q211 |-- q211 |-- qf11 q011 |-- 1q01 |-- 11q0 |-- 1q11 |-- q110 |-- q100 |-- qf100 6 Graphische Repräsentation. Übergangsdiagramme: 0/0R 1/1R /L q0 q1 /1N 1/0L 0/1L qf q2 /R 0/0L 1/1L X/YZ an Pfeil bedeutet: bei Lesen von X wird X durch Y ersetzt und nach Z gegangen. Pfeil gibt Nachfolgezustand an. Mehrband-Turingmaschinen intuitiv: k Bänder, k Schreib-Lese-Köpfe, können sich unabhängig voneinander bewegen Übergangsfunktion: : Z × k -> Z × k × {L, R, N}k Beispiel: TM, die unäre Multiplikation realisiert: Band 3 := Band 2 * Band 1 Startkonfiguration: Band 3 leer, Band 1 und 2 enthalten Multiplikanden, unär codiert mit a’s. Lesekopf Band 1,2 auf jeweils erstem a, Startzustand z0. x und y sind beliebige Bandsymbole. (z0, a, a, x) -> (z1, a, a, a, R, N, R) (z0, a, , x) -> (ze, a, , x, N, N, N) (z0, , a, x) -> (ze, , a, x, N, N, N) (z1, a, x, y) -> (z1, a, x, a, R, N, R) (z1, , x, y) -> (z2, , x, y, L, N, N) (z2, a, x, y) -> (z2, a, x, y, L, N, N) (z2, , x, y) -> (z0, , , y, R, R, N) 7 Idee: z0: Startzustand, z1: Inhalt von Band 1 auf Band 3 kopieren, z2: auf Band 1 nach links gehen und wenn Blank gelesen 1 a auf Band 2 löschen und in z1 gehen. Ende wenn Band 2 leer. Beispiel: aa aaa z0 3: 2: 1: a aa aaa z1 aa aa aaa z1 aaa aa aaa z1 aaa aa aaa z2 aaa aa aaa z2 aaa a aaa z0 aaaaaa aaa ze Satz: Zu jeder Mehrband-Turingmaschine M gibt es eine (Einband) Turingmaschine M’, so dass 1. T(M) = T(M’) 2. M und M’ berechnen dieselbe Funktion f falls M akzeptierend, bzw. falls M berechnende TM Beweis: Band von M’ hat 2k „Spuren“: aus: ----------------------------------------------------------------------------------… a1 a2 a3 a4 a5 a6 a7 … --------------------------------------------------------------------------------------------------------------------------------------------------------------------… a1 a2 a3 a4 a5 a6 a7 … --------------------------------------------------------------------------------------------------------------------------------------------------------------------… a1 a2 a3 a4 a5 a6 a7 … ----------------------------------------------------------------------------------- wird: ----------------------------------------------------------------------------------… a1 a2 a3 a4 a5 a6 a7 … * … a1 a2 a3 a4 a5 a6 a7 … * … a1 a2 a3 a4 a5 a6 a7 … * -------------------------------------------------------------------------------------- ’ = ( {*})2k 8 Starte mit Eingabe x1…xn Erzeuge Darstellung der Startkonfiguration in Spurendarstellung. Simuliere einen Schritt von M durch mehrere Schritte von M’: gehe nach rechts bis * gefunden führe entsprechende Änderungen durch merke (im Zustand), dass entsprechende Spur abgearbeitet ist wenn alle Spuren abgearbeitet, gehe nach links hinter erstes * zurück Es muss gelten: |Z’| ≥ |Z × k Notation: Wenn M 1-Band TM, so ist M(i,k) die k-Band-TM, die auf Band i M simuliert, alle anderen Bänder unverändert lässt (k kann entfallen, wenn es implizit gegeben ist) Nenne: +1 TM: “Band:= Band+1“(i): “Band:= Band+1“ “Band i := Band i + 1“ Folgende Mehrband-TM sind leicht zu erstellen: “Band i := Band i -1“ “Band i := 0“ “Band i := Band j” Hintereinanderschaltung von Turing-Maschinen: Seien M1 = (Z1, , 1, 1, z11, , E1) M2 = (Z2, , 2, 2, z21, , E2) Turing-Maschinen, so dass Z1 Z2 = und M1 berechnet ein Wort aus *. start M1 M2 stop oder: M1; M2 bezeichnet die TM M = (Z1 Z2, , 1 2, , z11, , E2) mit = 1 2 {(zea -> z21aN | ze E1, a 1} Beispiel: start “Band:= Band + 1” “Band:= Band + 1” stop TM, die Bandinhalt um 2 inkrementiert. Fallunterscheidungen lassen sich ähnlich modellieren: 9 start M z2 M1 stop z1 M2 stop bezeichnet die TM, die M1 ausführt, wenn M in z1 terminiert, M2, wenn M in z2 terminiert. Eine Turingmaschine, genannt “Band = 0?“, die testet, ob auf dem Band die Eingabe 0 steht, lässt sich wie folgt definieren: Z = {z0, z1, zja, znein}, Startzustand z0, Endzustände zja, znein. z0a -> zneinaN z00 -> z10R z1a -> zneinaL z1 -> zjaL für a ≠ 0 für a ≠ Wir nennen die TM “Band = 0?“ (i): “Band i = 0?“ Sei M beliebige TM. Wir nennen folgende TM “While Band i ≠ 0 Do M“: start “Band i = 0?“ znein zja stop M Wir können also Turing-Maschinen mit Konstrukten verknüpfen, wie wir sie von üblichen imperativen Programmiersprachen her kennen. 10 3. LOOP-, WHILE- und GOTO-Berechenbarkeit Komponenten von LOOP-Programmen Variablen: Konstanten: Trennsymbole: Operationszeichen: Schlüsselwörter: x0, x1, x2, …, y, z, … 0, 1, 2, … ; := +, LOOP, DO, END Def.: Die Menge der LOOP-Programme ist induktiv wie folgt definiert: 1. jede Wertzuweisung der Form xi:= xj + c oder xi:= xj - c ist ein LOOP-Programm, wobei c Konstante ist; 2. falls P1 und P2 LOOP-Programme sind, so ist P1; P2 ein LOOP-Programm; 3. falls P ein LOOP-Programm ist, so ist LOOP xi DO P END ein LOOP-Programm. Semantik: LOOP-Programme berechnen Funktionen über N. Eingabewerte n1, ..., nk bei Beginn der Programmausführung an Variablen x1, ..., xk gebunden. Alle anderen Variablen haben Anfangswert 0. Ausgabewert ist Wert von x0 nach Beendigung des Programms. Wertzuweisung: wie üblich, dabei ist "-" modifizierte Subtraktion: xj - c = 0, falls c > xj. P1; P2 Hintereindanderausführung von P1 und P2. LOOP xi DO P END: P wird xi mal ausgeführt. Def.: Eine Funktion f: Nk -> N heißt LOOP-berechenbar, falls es ein LOOP-Programm gibt, das f berechnet, d.h. P gestartet mit der Variablenbelegung n1, ..., nk der Variablen x1, ..., xk (alle anderen Variablen initialisiert mit 0) stoppt mit f(n1, ..., nk) als Wert der Variable x0. Anmerkung: offensichtlich sind nur totale Funktionen berechenbar (aber nicht alle totalen -> später). Abkürzungen: xi:= xj xi:= c statt xi:= xj + 0 statt xi:= xj + 0, wobei xj eine Variable mit Wert 0 ist. Modellierung von Fallunterscheidungen: IF x = 0 THEN P END y := 1; LOOP x DO y := 0 END; LOOP y DO P END IF x = 0 THEN P1 ELSE P2 END y:= 1; z:= 0; (y,z Hilfsvariablen) LOOP x DO y := 0; z:= 1 END; LOOP y DO P1 END; LOOP z DO P2 END Beispiele: 11 (y Hilfsvariable) Addition: x0:= x1; LOOP x2 DO x0 := x0 + x1 END Multiplikation: x0:= 0; LOOP x2 DO x0 := x0 + x1 END ausführlich: x0:= 0; LOOP x2 DO LOOP x1 DO x0 := x0 + 1 END END WHILE-Programme Def.: Die Menge der WHILE-Programme ist induktiv wie folgt definiert: 1. jede Wertzuweisung der Form xi:= xj + c oder xi:= xj - c ist ein WHILE-Programm, wobei c Konstante ist; 2. falls P1 und P2 WHILE-Programme sind, so ist P1; P2 ein WHILE-Programm; 3. falls P ein WHILE-Programm ist, so ist LOOP xi DO P END ein WHILE-Programm; 4. falls P ein WHILE-Programm ist, so ist WHILE xi 0 DO P END ein WHILEProgramm. Semantik von WHILE-Schleife: P wird so lange ausgeführt, bis xi den Wert 0 hat. Anmerkung: LOOP-Konstrukt nicht mehr unbedingt nötig, da LOOP xi DO P END simuliert werden kann durch y := xi; WHILE y 0 DO y := y-1; P END Def.: Eine Funktion f: Nk -> N heißt WHILE-berechenbar, falls es ein WHILE-Programm gibt, das f berechnet, d.h. P gestartet mit der Variablenbelegung n1, ..., nk der Variablen x1, ..., xk (alle anderen Variablen initialisiert mit 0) stoppt mit f(n1, ..., nk) als Wert der Variable x0, falls f(n1, ..., nk) definiert ist. Ansonsten terminiert das Programm nicht. Simulation von WHILE-Programmen mit Turing-Maschinen: Wertzuweisung, Hintereinanderschaltung von Programmen sowie WHILE-Schleifen durch Mehrband-TMs simulierbar (jeder Variable des WHILE-Programms entspricht ein Band). Außerdem Mehrband-TMs durch 1-Band TM simulierbar. Deshalb gilt: Satz: Jede WHILE-berechenbare Funktion ist Turing-berechenbar. Umkehrung wird nach Einführung von GOTO-Programmen bewiesen. 12 GOTO-Programme Sequenzen von Anweisungen Ai, die Marke Mi besitzen: M1:A1; M2:A2; ... Mk:Ak Zulässige Anweisungen: Wertzuweisungen: unbedingter Sprung: bedingter Sprung: Stopanweisung: xi:= xj + c oder xi:= xj - c GOTO Mi IF xi = c THEN GOTO Mj HALT Bemerkungen: Marken, die nicht angesprungen werden können, dürfen entfallen. Die letzte Anweisung Ak ist entweder ein unbedingter Sprung oder HALT. Semantik: Wertzuweisung wie bisher, GOTO legt nächste auszuführende Programmanweisung fest, Stopanweisung bedeutet Terminieren, Ausgabe ist Wert von x0. Def. von GOTO-Berechenbarkeit analog zu WHILE-Berechenbarkeit. Jedes WHILE-Programm kann durch GOTO-Programm simuliert werden: WHILE xi 0 DO P END wird simuliert durch: M1 : M2 : IF xi = 0 THEN GOTO M2; P; GOTO M1; … wobei M1, M2 geeignete Marken sind. Satz: Jede WHILE-berechenbare Funktion ist auch GOTO-berechenbar. Umgekehrte Richtung: Gegeben GOTO-Programm M1:A1; M2:A2; ... Mk:Ak Das Programm wird simuliert durch folgendes WHILE-Programm: count := 1; WHILE count 0 DO IF count = 1 THEN A1' END; IF count = 2 THEN A2' END; … IF count = k THEN Ak' END; END 13 Hierbei gilt: Ai' = xi:= xj c; count = count + 1 count := n IF xj = c THEN count := n ELSE count := count + 1 END count := 0 falls Ai = falls Ai = xi:= xj c GOTO Mn falls Ai = falls Ai = IF xi = c THEN GOTO Mn HALT Anmerkung: IF … THEN … ELSE modellierbar durch LOOP IF x = 0 THEN P1 ELSE P2 END y:= 1; z:= 0; (y,z Hilfsvariablen) LOOP x DO y := 0; z:= 1 END; LOOP y DO P1 END; LOOP z DO P2 END x = c testet man durch x – c = 0 und c – x = 0. (obiger Spezialfall sogar ganz ohne Schleifen modellierbar -> Übungen) Satz: Jedes GOTO Programm kann durch ein WHILE-Programm simuliert werden. Damit ist jede GOTO-berechenbare Funktion WHILE-berechenbar. Anmerkung: Obiges Programm hat eine einzige WHILE-Schleife: Satz: (Kleenesche Normalform für WHILE-Programme) Jede WHILE-berechenbare Funktion kann durch ein WHILE-Programm mit nur einer WHILE-Schleife berechnet werden. Bew.: P überführt in GOTO-Programm P' überführt in WHILE-Programm P''. Bisher ergibt sich folgendes Bild (X Y heißt X-berechenbar impliziert Y-berechenbar bzw. Y-Programm kann X-Programm simulieren): GOTO WHILE TM LOOP Es fehlt noch TM GOTO, dann ist die Äquivalenz aller (bis auf LOOP) gezeigt. Sei M = (Z, , , , z1, , E) TM zur Berechnung von f. M wird simuliert durch ein GOTO-Programm M1:P1; M2:P2; M3:P3 P1: P2: P3: transformiert Anfangswerte der Variablen in Binärdarstellung erzeugt Darstellung der Startkonfiguration von M Codierung in 3 Variablen x,y,z Schritt-für-Schritt-Simulation der Rechnung von M erzeugt aus Zielkonfiguration Wert der Ausgabevariable x0. 14 Seien Z = {z1, ..., zk}, = {a1, ..., am}, b > | | (größer nötig, sonst evtl. führende Nullen) TM-Konfiguration ai1...aipzraj1…ajq wird durch Variablenwerte repräsentiert: Indizes der Symbole links von zr werden als Zahl zur Basis b gelesen, entsprechend Indizes der Symbole rechts (in umgekehrter Reihenfolge): x = (i1 ... ip)b y = (jq ... j1)b z=r (i1 ... ip)b ist Wert der zur Basis b dargestellten Zahl i1 ... ip : x = t=1...p it bp-t Analog für y, aber umgekehrte Reihenfolge. Beispiel: = {a1, ...,a3}, b = 4 Konfiguration a1a1z2a3a2 wird repräsentiert durch x = 1 41 + 1 40 = 5 y = 3 + 8 = 11 z=2 M2:P2 hat folgende Struktur: M2 : a := y MOD b; IF (z = 1) AND (a = 1) THEN GOTO M11; IF (z = 1) AND (a = 2) THEN GOTO M12; … IF (z = k) AND (a = m) THEN GOTO Mkm; M11: P11; GOTO M2; M12: P12; GOTO M2; … Mkm: Pkm; GOTO M2; Wie sind Programme Pij aufgebaut? Modellieren Konfigurationsübergänge der TM. Beispiel: es gelte (zi,aj) = (zi',aj',L) dann ist Pij: z := i'; y := y DIV b; y := b y + j'; y := b y + (x MOD b); x := x DIV b; % neuer Zustand % erstes Symbol des rechten Wortes weg % aj' vor rechtes Wort fügen % letztes Symbol linkes Wort rechts anfügen % letztes Symbol linkes Wort entfernen andere Fälle ähnlich. Falls zi E so ist Pij: GOTO M3. 15 Satz: GOTO-Programme können Turingmaschinen simulieren. Damit ist jede Turingberechenbare Funktion auch GOTO-berechenbar. Primitiv rekursive und -rekursive Funktionen Rekursion einer der ersten Ansätze, Berechenbarkeit zu präzisieren (parallel zu TM) basiert nicht auf Maschinenmodell (TM) oder Zustandsmodell (Bindung Wert an Variable, WHILE, GOTO) Grundidee: Angabe Basisfunktionen + Prinzipien, um aus Funktionen neue zu generieren. Rekursion bedeutet: Zurückführen eines Problems auf ein kleineres: hier Berechnung für n+1 auf die Berechnung für n. Def.: Die Klasse der primitiv rekursiven Funktionen (über natürlichen Zahlen) ist induktiv so definiert: 1. Alle konstanten Funktionen cjk sind primitiv rekursiv, wobei cjk die j-stellige Funktion ist, die als Ergebnis die Konstante k liefert. 2. Alle Projektionen ji sind primitiv rekursiv, wobei ji (i j) die j-stellige Funktion ist, die als Ergebnis das i-te Argument liefert. 3. Die Nachfolgerfunktion s(n) = n +1 ist primitiv rekursiv. 4. Jede Funktion, die durch Komposition (Einsetzung) von primitiv rekursiven Funktionen entsteht, ist primitiv rekursiv, d.h., wenn g: Nr -> N und h1: Nk -> N, …, hr:Nk -> N primitiv rekursiv sind, so ist auch f: Nk -> N mit f(n1, ..., nk) = g(h1(n1, ..., nk),…, hr(n1, ..., nk)) primitiv rekursiv. Anmerkung: statt ji(n1, ..., nk) schreiben wir ni, statt cjr(n1, ..., nk) schreiben wir r, also etwa: f(x,y) = add(x, add(y,5)) statt add(21(x,y), add(22(x,y), cj5(x,y))). 5. Jede Funktion, die durch primitive Rekursion aus primitiv rekursiven Funktionen entsteht, ist primitiv rekursiv. Eine k-stellige Funktion f entsteht aus g (k-1-stellig) und h (k+1-stellig) durch primitive Rekursion, wenn gilt: f(0, n2, ..., nk) f(n+1, n2, ..., nk) = = g(n2, ..., nk) h(f(n, n2, ..., nk), n, n2, ..., nk) Anmerkung: da beliebige Projektionen primitiv rekursiv sind, kommt es auf die Reihenfolge der Argumente von g und h nicht an. Es müssen auch nicht alle Argumente n2, ..., nk von f und g benutzt werden. Aus demselben Grund muss Rekursion in f nicht notwendiger Weise über das erste Argument laufen. Beispiele: add(0, x) add(n+1, x) =x identische Funktion, genauer: 11(x) = s(add(n,x)) mult(0, x) =0 mult(n+1, x) = add(mult(n,x),x) 16 sub(x,0) sub(x,n+1) =x = sub1(sub(x,n)) sub1(0) sub1(n+1) =0 =n Wir können auch Prädikate definieren (jeweils 1 "wahr", 0 "falsch"): =0(0) =0(n+1) =1 =0 0(0) 0(n+1) =0 =1 >(x,y) =(x,y) = 0(sub(x,y)) = mult(sub(1, >(x,y)), sub(1, >(y,x))) div(x,y) = div3(x,y,0) div3(0,y,z) = =(y,z) div3(n+1,y,z) = >(y,z) * div3(n,y,s(z)) + =(y,z) * s(div3(n,y,1)) Beispiel: div(5,2) = div3(5,2,0) = div3(4,2,1) = div3(3,2,2) = 1 + div3(2,2,1) = 1 + div3(1,2,2) = 1 + 1 + div3(0,2,1) = 2 div(4,2) = div3(4,2,0) = div3(3,2,1) = div3(2,2,2) = 1 + div3(1,2,1) = 1 + div3(0,2,2) = 1 + 1 = 2 mod(x,y) = mod3(x,y,0) mod3(0,y,z) = (y,z) * z mod3(n+1,y,z) = >(y,z) * mod3(n,y,s(z)) + =(y,z) * mod3(n,y,1) Beispiel: mod(5,2) = mod3(5,2,0) = mod3(4,2,1) = mod3(3,2,2) = mod3(2,2,1) = mod3(1,2,2) = mod3(0,2,1) = 1 sum(n) = i n i = n(n+1)/2 sum(0) sum(n+1) =0 = sum(n) + n + 1 Zur Vorbereitung des Beweises primitiv rekursiv LOOP-berechenbar zeigen wir, dass eine beliebige Anzahl natürlicher Zahlen eindeutig als natürliche Zahl codiert und wieder decodiert werden kann, und dass die dazu nötigen Funktionen primitiv rekursiv sind. 17 Betrachte die Funktion c(x,y) = sum(x + y) + x y\x 0 1 2 3 4 0 0 1 3 6 10 1 2 4 7 11 2 5 8 12 3 9 13 3 14 Beispiel: c(2,1) = 1+2+3+2 = 8 Bijektion zwischen N2 und N. Primitiv rekursiv Verallgemeinerung auf k+1-Tupel natürlicher Zahlen: <n0, n1,...nk> = c(n0, c(n1, ...,c(nk,0)...)) Wir benötigen noch die Umkehrfunktionen für c und < >. e liefert erstes Argument von c(x,y) zurück: e(c(x,y)) = x f zweites Argument: f(c(x,y)) = y Wenn e und f gegeben, dann kann man daraus auch die Umkehrfunktionen der k+1-stelligen Codierfunktion < > bekommen: d0(n) = e(n) d1(n) = e(f(n)) … dk(n) = e(f(f(…f(n)…)) k-mal f Diese Funktionen sind primitiv rekursiv, falls e und f es sind. Letzteres zeigen wir jetzt. Vorbemerkung: gegeben einstelliges Prädikat P(x) (Funktion, die x den Wert 0 oder 1 zuordnet) der beschränkte max-Operator qP(n) liefert das größte x n, für das P(x) = 1. max{x n | P(x) = 1} Falls es kein solches x gibt ist der Wert von qP(n) 0. qP(0) qP(n+1) = = = 0 n+1 falls P(n+1) qP(n) sonst P(n+1) * (n+1) + (1 - P(n+1) ) * qP(n) qP ist primitiv rekursiv, falls P primitiv rekursiv. Ähnlich können wir ein Prädikat QP(n) definieren, das Wert 1 hat, wenn es x n gibt, für das P(x) = 1: QP(n) = 1 gdw. y n : P(y) = 1. 18 QP(0) QP(n+1) = = P(0) P(n+1) + QP(n) - P(n+1) * QP(n) % wenn beide gelten 1 abziehen QP ist primitiv rekursiv, falls P primitiv rekursiv. Da x c(x,y) und y c(x,y) lassen sich Umkehrfunktionen so definieren: e'(n,m,k) e(n) = = max{x n | y k : c(x,y) = m} e'(n,n,n) f'(n,m,k) f(n) = = max{y n | x k : c(x,y) = m} f'(n,n,n) Da auch "c(x,y) = m" primitiv rekursiv ist, sind e und f auch primitiv rekursiv (max und beschränkter Existenzquantor sind gerade qQp und Qp für P = "c(x,y) = m". Satz: Die Klasse der primitiv rekursiven Funktionen stimmt genau mit der Klasse der LOOPberechenbaren Funktionen überein. Beweis: LOOP-berechenbar -> primitiv rekursiv Falls r-stellige Funktion f LOOP-berechenbar, so gibt es LOOP-Programm P mit k+1 (k+1 > r) Variablen, das f berechnet. Wir zeigen durch Induktion über die Struktur von P, dass es einstellige primitiv rekursive Funktion gP gibt, so dass gP(<a0,...,ak>) = <b0,...,bk> gdw. P gestartet mit Werten a0,...,ak der Variablen x0,...,xk terminiert mit Werten b0,...,bk dieser Variablen. Induktionsanfang: Falls P Zuweisung der Form xi := xj c ist, so ist gP(n) = <d0(n),... di-1(n), dj(n) c, di+1(n), …, dk(n)> Induktionsschritt: Seien Q, R Programme, für die es die Funktionen gQ(n) und gR(n) gibt (Induktionsannahme). Falls P die Form Q;R hat, so ist gP(n) = gR(gQ(n)). Falls P die Form LOOP xi DO Q END hat, so ist gP(n) = h(di(n),n), wobei h(0,x) = x h(n+1,x) = gQ(h(n,x)) h(z,x) modelliert z-maliges Anwenden von gQ auf x. Es gibt also für alle P ein entsprechendes gP(n). Jetzt gilt: f(x1,...,xr) = d0(gP(<0, n1,...,nr,0,...,0>) % rechts k-r Nullen Primitiv rekursiv -> LOOP-berechenbar Induktion über Struktur der primitiv rekursiven Funktion: 19 Basisfunktionen LOOP-berechenbar, Komposition von Funktionen durch Hintereinanderausführen von Programmen und Speichern von Zwischenergebnissen in Variablen, primitive Rekursion der Form f(0, x1, ..., xr) f(n+1, x1, ..., xr) = = g(x1, ..., xr) h(f(n, x1, ..., xr), n, x1, ..., xr) Berechnung von f(n, x1, ..., xr) durch: y := g(x1, ..., xr); z:= n; k:= 0; LOOP z DO y:= h(y, k, x1, ..., xr); k := k+1 END Anmerkung: das entsprechende LOOP-Programm in Schöning funktioniert nicht: Gegenbeispiel: f(0) = 0; f(x+1) = h(f(x),x) mit h(x,y) = x2 + y +1) Def.: Sei f eine k+1-stellige Funktion. Die durch Anwendung des -Operators auf f entstehende Funktion (f): Nk -> N ist definiert wie folgt: (f)(n1, ..., nk) = min{n | f(n, n1, ..., nk) = 0 und für alle m < n ist f(m, n1, ..., nk) definiert} Dabei gilt: min{} = undefiniert. Def.: Die Klasse der -rekursiven Funktionen ist die kleinste Klasse von Funktionen, die die Basisfunktionen enthält und abgeschlossen ist unter Einsetzung, primitiver Rekursion und Anwendung des -Operators. Satz: Die Klasse der -rekursiven Funktionen stimmt genau mit der Klasse der Turing(WHILE-, GOTO-) berechenbaren Funktionen überein. Beweis (Ergänzung zu Beweis für primitiv rekursiv LOOP-berechenbar, nur WHILE noch zu behandeln): (<=) Sei P Programm der Form WHILE xi 0 DO Q END. Wieder sei hQ(n,x) die Funktion, die den Zustand der Programmvariablen nach n Ausführungen von Q wiedergibt (siehe Beweis primitiv rekursiv LOOP-berechenbar). Dann ist gP(n) = hQ((di hQ)(n),n) (di h)(n) ist die kleinste Anzahl von Schleifendurchläufen, nach deren Ausführung die Variable xi im Programm P den Wert 0 bekommt. (=>) Sei g = (f), WHILE-Programm für f existiert nach Induktionsvoraussetzung. Folgendes Programm berechnet (f): x0 := 0; y := f(0, x1, ..., xk); WHILE y 0 DO x0 := x0 +1; y := f(x0, x1, ..., xk) END 20 Die Ackermannfunktion Eingeführt 1928 von Ackermann, später von Hermes vereinfacht, dessen Version heute gebräuchlich ist: ack(0,y) = y + 1 ack(x,0) = ack(x-1, 1) ack(x,y) = ack(x-1, ack(x, y-1)) x>0 x,y > 0 Satz: Die Ackermannfunktion ist total. Bew.: Induktion über 1. Argument Induktionsanfang: für x = 0 und für alle y gilt ack(x,y) = y + 1 Induktionsschritt: für x-1 und alle y gelte: ack(x-1,y) ist definiert ack(x,y) = ack(x-1, ack(x, y-1)) = ack(x-1, ack(x-1, … ack(x-1,1)…)) y+1 mal ack. Da ack(x-1,y) für alle y definiert ist, muss auch ack(x,y) definiert sein. Beispiele: ack(2,0) = ack(1,1) = ack(0, ack(1,0)) = ack(0, ack(0,1)) = ack(0,2) = 3 ack(1,2) = ack(0,ack(1,1)) = ack(0,3) = 4 ack(2,1) = ack(1,ack(2,0)) = ack(1,3) = ack(0,ack(1,2)) = ack(0,4) = 5 Ackermann Turing-berechenbar aber wächst schneller als alle primitiv rekursiven Funktionen. alternative Definition, nach der ersten Zahl entwickelt: ack(0,n) = n + 1 ack(1,n) = 2 + (n + 3) - 3 ack(2,n) = 2 * (n + 3) - 3 ack(3,n) = 2 ^ (n + 3) - 3 ack(4,n) = 2 ^ 2 ^ ... ^ 2 - 3 (wobei die Potenz (n+3)-mal vorkommt) Donald E. Knuth (1976) Up-Arrow-Notation: m n mal Addieren: Multiplikation vom m mit n (m + m + ... + m = m * n). m n mal Multiplizieren: Potenz mn oder m^n. m n mal Potenzieren: n-te Potenz (in Zeichen m^^n) m^^^n: m^^n n mal Ausführen usw. a(0,n) = n + 1 a(1, n) = 2 + (n + 3) - 3 a(2, n) = 2 (n + 3) - 3 a(3, n) = 2 ^ (n + 3) - 3 a(4, n) = 2 ^^ (n + 3) - 3 a(5, n) = 2 ^^^ (n + 3) - 3 ... Man kann beweisen: die Ackermannfunktion wächst schneller als jede LOOP-berechenbare Funktion, also gilt: Satz: Die Ackermannfunktion ist nicht LOOP-berechenbar. 21 Satz: Die Ackermannfunktion ist WHILE-berechenbar. Beweis: Wir zeigen zunächst, wie die Berechnung mit Hilfe eines Stacks durchgeführt werden, dann wie Stacks mit WHILE-Programmen simuliert werden können. PUSH(x) legt x auf den Stack ab, POP entfernt oberstes Stackelement und liefert dessen Wert, INIT initialisiert leeren Stack. Folgendes Programm berechnet ack(x,y): INIT; PUSH(x); PUSH(y); WHILE size 1 DO y := POP; x := POP; IF x = 0 THEN PUSH(y+1) ELSE IF y = 0 THEN PUSH(x-1); PUSH(1) ELSE PUSH(x-1); PUSH(x); PUSH(y-1) END END; x0 := POP % size: Größe des Stacks Stackinhalt n1n2…nk (n1 oberstes Element) bedeutet: ack(nk, ack(nk-1, ...ack(n2,n1)..)) zu berechnen. Mit den Codierungsfunktionen können wir den Stack als natürliche Zahl codieren und einer Variablen s zuweisen: s := < n1,n2,…,nk>. Die Stack-Operationen lassen sich so modellieren: INIT PUSH(x) z:= POP s := 0; size := 0 s := c(x,s); size := size + 1 z := e(s); s := f(s); size := size -1 Wertetabelle: y= ack(x,y) x= 0 1 2 0 1 2 3 3 4 5 6 7 4 5 6 7 8 1 2 2 3 3 4 5 6 5 7 9 11 13 15 17 3 5 4 13 13 29 61 125 253 509 1021 2045 4093 8189 2y + 3 - 3 7 8 9 8 9 9 10 10 11 y+1 10 11 12 y+2 19 21 23 2y + 3 65533 ack(4,2) y 2^^(y + 3) - 3 5 65533 2^^^(y + 3) - 3 ack(4,2) hat bereits 19809 Stellen in der Dezimaldarstellung. 22