4.3 Primitive Rekursion kann nicht alles: die Ackermann-Funktion

In den vorhergehenden Teilkapiteln haben wir gesehen, dass Sie allerhand mit primitiv rekursiven Funktionen implementieren können. Darunter Dinge, die komplexere Rekursion oder Schleifen mit mehreren lokalen Variablen zu brauchen scheinen (wie die Fibonacci-Zahlen), ja sogar Dinge, die über den Bereich der natürlichen Zahlen hinausgehen, wie zum Beispiel das Sortieren eines beliebig langen Arrays. Kernpunkt war die Erkenntnis, dass wir komplexere "Datenstrukturen" wie Paare von natürlichen Zahlen als eine natürliche Zahl codieren können und somit der primitiven Rekursion zugänglich machen können.

Es liegt also Nahe, zu vermuten, dass primitive Rekursion bereits den Berechenbarkeitsbegriff zufriedenstellend formalisiert: das also alles "Berechenbare" auch primitiv rekursiv sei. Der Wikipedia-Artikel über die Ackermann-Funktion schreibt, dass der deutsche Mathematiker David Hilbert dies auch vermutete. Im Jahre 1926 jedoch definierte Wilhelm Ackermann eine Funktion, die "offensichtlich berechenbar", jedoch nicht primitiv rekursiv ist. Die Funktion, die wir heute die Ackermann-Funktion nennen, ist allerdings eine vereinfachte Version, die 1935 von der ungarischen Mathematikerin Rózsa Péter gefunden wurde (obwohl der letztere Artikel das Jahr 1955 nennt).

Definition 4.3.1 (Péter-Ackermann-Funktion). A(m,n):={n+1 if m=0,A(m1,1) if m1,n=0A(m1,A(m,n1)) if m,n1 . Manchmal sehen Sie in der Literatur auch die äquivalente, vielleicht lesbarere Definition: A(0,n):=n+1A(m+1,0):=A(m,1)A(m+1,n+1):=A(m,A(m+1,n)) Für festes mN schreiben wir auch Am für die ein-parametrige Funktion Am:NNnA(m,n) .

Es lohnt sich, diese Funktion für kleine Werte von m zu untersuchen. Aus der Definition geht unmittelbar hervor, dass A0(n)=n+1 ist. Für A1(n) rechnen wir A1(n)=A0(A1(n1))=A0(A0(A1(n2)))==A0(A0(...(A0i mal(A1(ni)))...))=A0(A0(...(A0n mal(A1(0)))...))=A0(A0(...(A0n+1 mal(1))...))=n+2 . Soweit schaut die Funktion nicht besonders beindruckend aus. A2 bekommen iwr nach dem gleichen Schema: A2(n)=A1(A1(...(A1n+1 mal(1))...)) , wir fangen also mit 1 an und zählen n+1 mal eine 2 drauf. Wir erhalten 2n+3, also A2(n)=2n+3 . A3 wird etwas unangenehmer, weil wir keine gute Intuition haben, was "n+1 mal verdoppeln und jedes Mal 3 draufzählen" ergibt. Mit 1 beginnend erhalten wir also 1A25A213A229A261A2125 Das ist schon genug, um eine Vermutung zu formulieren: A3(n)=2n+33 und diese dann per Indution zu beweisen.

Die Péter-Ackermann-Funktion ist berechenbar

Theorem 4.3.2 Sei mN gegeben. Die Funktion Am ist primitiv rekursiv.
Beweis. Wir verwenden Induktion über m. Für m=0 gilt A0(n)=n+1 und somit A0=succ. Sei nun also m1. Wir sehen, dass Am(t)=Am1(A(m,t1)) . Ich schreibe nun t anstatt n als Parameter, um mich der Notation der primitiven Rekursion anzunähern. Wir wissen bereits, dass Am1 primitiv rekursiv ist. Wir können Am(t) also schreiben als Am(t)={Am1(1) if t=0Am1(Am(t1)) if t1. Wir können also h=Comp(Am1,π13) wählen. Was ist g? Der Startwert soll g(x) sein, wir haben aber kein x, bzw. dies ist der "leere Vektor". Wir brauchen eine Funktion C, die null Argumente nimmt und Am1(1) zurückgibt. Also: g=Comp(Am1,one). Wir könnten sogar noch frecher sein und g=Comp(succ,Comp(succ,Comp(...Comp(succ,zero)))) schreiben, einfach Am1(1) mal hintereinander; diesen Wert also hard-coden. Allerdings ist die erste Variante einfacher, und somit Am=PrimRec(Comp(Am1,one),Comp(Am1,π13)) . Wir sehen also: jedes Am, betrachtet als Funktion mit einem Eingabeparameter, ist primitiv rekursiv.

Wir haben also gezeigt, dass jedes Am primitiv rekursiv ist. Heißt das auch, dass die zwei-parametrige Funktion A(m,n) berechenbar ist? In der primitiven Rekursion haben wir keine Möglichkeit, den Index m als Eingabewert zu lesen und dann aus dem unendlichen Array primitiv rekursiver Funktionen [A0,A1,A2,] den Eintrag Am auszulesen. Aber berechenbar in einem ganz allgemeinen Sinn? Diese Frage können wir zu diesem Zeitpunkt nicht formal beantworten, weil wir den Begriff allgemeiner Berechenbarkeit noch gar nicht definiert haben. Es ist allerdings an der Definition von A(m,n) nichts magisches, und intuitiv würden wir sagen, dass es klar berechenbar ist; so wie wir ja auch leicht eine Funktion implementieren könnten, die A(m,n) berechnet.

Die Ackermann-Péter-Funktion ist nicht primitiv rekursiv

Theorem 4.3.3 Die Ackermann-Péter-Funktion ist nicht primitiv rekursiv.
Mein Beweis paraphrasiert im Wesentlichen den auf planetmath.org

Die Aussage verstehen. Beachten Sie, dass Theorem 3.3.3 nicht mit Theorem 3.3.2 im Widerspruch steht. Theorem 3.3.3 besagt, dass Am primitiv rekursiv ist, für jedes m. Die Zahl m ist hier also Teil der Aussage, nicht Eingabeparameter der Funktion; die Funktion Am hat nur einen Eingabeparameter. Theorem 3.3.3 hingegen macht eine Aussage über eine Funktion A(m,n) mit zwei Eingabeparametern, und m ist nun einer dieser beiden.

Anders ausgedrückt: sie können zwar jedes einzelne Am in der "Programmiersprache" der primitiven Rekursion implementieren, allerdings brauchen Sie für jedes m neuen Code. Sie können keinen Code schreiben, der, gegeben m, den Code für Am irgendwie implizit produziert und dann mit n aufruft. Jedenfalls nicht in der Programmiersprache der primitiven Rekursion.

Beweisidee. Für jedes feste m ist die ein-parametrige Funktion Am:=nA(m,n) primitiv rekursiv und kann als Funktion mit m verschachtelten for-Schleifen betrachtet werden. Es wird sich herausstellen, dass Am in gewisser Weise die am schnellsten wachsende aller solcher Funktionen ist. Wäre also A(m,n) primitiv-rekursiv, dann könnten wir es mit d verschachtelten for-Schleifen berechnen; was ein Widerspruch ist, weil bereits Ad+1 schneller wächst als jede primitiv-rekursive Funktion mit d verschachtelten Schleifen.

Beweis. Wir brauchen eine robuste Definition, was es heißt, dass eine Funktion f:NN schneller wächst als g:NkN.

Definition. 4.3.4 Sei f:NN und g:NkN. Die Funktion f majorisiert g, wenn f(max(x1,,xk)>g(x1,,xk) . für alle x1,,xkN gilt. Wir schreiben auch kurzerhand f>g.
Sei zum Beispiel g(x,y)=xy und f(x)=2x. Dann majorisiert 2x die Funktion xy nicht; sei beispielsweise (x,y)=(3,2), dann ist g(x,y)=6 aber f(max(3,2))=23=8. Allerdings majorisiert 2x+2 die Funktion x+y, ganz allgemein weil 2x+2>x2 für alle xN gilt. Wir werden nun zeigen, dass jede primitiv rekursive Funktion g:NkN von einem Ar majorisiert wird; hierbei ist wichtig, dass der Index r als Konstante betrachtet wird, also von der "Bauweise" von g abhängen darf, nicht aber von den Eingabewerten, die g bekommt.
Lemma 4.3.5 Für jede primitiv rekursive Funktion f:NkN gibt es ein rN, so dass f von Ar majorisiert wird.

Aus diesem Lemma folgt dann, dass A(m,n) selbst nicht primitiv rekursiv sein kann. Wäre es dies, dann gäbe es ja ein r, so dass das ein-parametrige Ar die zwei-parametrige Funktion A(m,n) majorisieren würde: Ar(max(m,n))>A(m,n) für alle Werte m,nN und somit insbesondere Ar(r)>A(r,r) , was offensichtlich ein Widerspruch ist, da beide Seiten gleich sind.

Wir werden nun das Lemma beweisen. Wir verwenden Induktion über die Weise, in der die Funktion f konstruiert worden ist, also über die Anzahl der Comp- und PrimRec-Kombinatoren, die wir zum Bau von f gebraucht haben. Im folgenden schreiben wir, wenn wir einen Vektor x=(x1,,xn) aus natürlichen Zahlen haben, oft x:=max(x1,,xn).

Induktionsbasis. Wir betrachten wir die Basisfunktionen zero,succ,πkn. Wir wissen bereits, dass A0(n)=n+1 ist, also A0=succ. Leider majorisiert A0 also succ nicht. Wie steht es mit A1? Es gilt A1(n)=n+2, und somit ist zero(x)=0<1<A1(x)succ(x)=x+1<x+2=A1(x)πkn(x)=xkx<x+2=A1(x) , wobei wir die Schreibweise x=max(x)=max(x1,,xn) verwenden. Wir schlussfolgern: A2 majorisiert jede Basisfunktion.

Induktionsschritt. Wenn f keine Basisfunktion ist, dann wurde f mittels Komposition oder primitiver Rekursion konstruiert. Für jeden Fall führen wir eine getrennte Rechnung durch.

Komposition: f(x)=g(h1(x),,hk(x)), für primitiv rekursive Funktionen g,h1,,hk. Jede dieser Funktionen wurde mit weniger Kombinatoren konstruiert; somit wird jede dieser Funktionen von einem Ar majorisiert: Ar>g,As1>h1,,Ask>hk. Für einen Eingabevektor x schreiben wir x:=max(x1,,xn) und rechnen: f(x)=g(h1(x),,hk(x))(weil Aq>g)<Ar(max(h1(x),,hk(x)))(weil Ar monoton und Asi>hi)Ar(max(As1(x),As2(x),,Ask(x))) Wir setzen nun q:=max(r,s1,,sk). Dann ist der obige Wert höchstens: Aq(Aq(x))Aq(Aq+1(x))=Aq+1(x+1) . Schlussendlich behaupte ich, dass Aq+1(x+1)Aq+2(x) gilt: Aq+2(x)=Aq+1(Aq+2(x1))Aq+1(A2(x1))=Aq+1(x+1) . Also: Aq+2 majorisiert f.

Primitive Rekursion: f=PrimRec(g,h), also f(t,x)={g(x) if t=0h(f(t1,x),t1,x) if t1 , wobei g,h bereits mit weniger Kombinatoren konstruierte primitiv rekursive Funktionen sind. Daher gibt es ein qN mit Aq>g und Aq>h.

Behauptung 4.3.6 f(t,x)Aq+1(t+x), wobei x=max(x)=max(x1,,xn).
Beweis. Wir verwenden Induktion über t. Wenn t=0 ist, dann gilt f(0,x)=g(x)<Aq(x)Aq+1(x) . Wenn t1 ist, dann gilt f(t,x)=h(f(t1,x),t1,x)(weil Aq>h)<Aq(max(f(t1,x),t1,x1,,xn))(für x:=max(x1,,xn))<Aq(max(f(t1,x),t1,x))(per Induktionshypothese für t1)Aq(max(Aq+1(t1+x),t1,x))=Aq(Aq+1(t1+x))=Aq+1(t+x) . Somit ist die Behauptung bewiesen.

Die Behauptung reicht aber noch nicht, da die rechte Seite der Ungleichung f(t,x)<Aq+1(t+x) eine Funktion in zwei Parametern ist: t und x, wir aber für das zu beweisende Lemma aber eine Funktion in einem Parameter brauchen, nämlich max(t,x). Sei also z:=max(t,x)=max(t,x1,,xn). Dann gilt

Aq+1(x+t)Aq+1(2z)Aq+1(2(z1)+3)(da A2(n)=2n+3)=Aq+1(A2(z1))Aq+1(Aq+2(z1))=Aq+2(z) . Es gilt also Aq+2>f und das Lemma ist bewiesen.

In der Vorlesung am 7. Mai 2024 hatte ich den Grad einer primitiv-rekursiven Funktion definiert. Das ist in etwa die "Verschachtelungstiefe" von f. Betrachten wir beispielsweise die Funktion pair(x,y)=(x+y+12)+x und dröseln auf, wie wir diese als primitiv-rekursive Funktion konstruiert haben:

pair = Comp(add, p0, Comp(choose2,Comp(add,p0,Comp(succ,p1))))
choose2 = PrimRec(zero, Comp(add,p0,p1))
add = PrimRec (p0, Comp(succ, p0))

dann können wir das auf Baum darstellen: