Berechenbarkeit

Werbung
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 |-- q211 |-- qf11
q011 |-- 1q01 |-- 11q0 |-- 1q11 |-- q110 |-- q100 |-- 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 -> zjaL
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
Herunterladen