Die Ackermannfunktion ist eine 1926 von Wilhelm Ackermann gefundene, extrem schnell wachsende mathematische Funktion, mit deren Hilfe in der theoretischen Informatik Grenzen von Computer- und Berechnungsmodellen aufgezeigt werden können. Heute gibt es eine ganze Reihe von Funktionen, die als Ackermannfunktion bezeichnet werden. Diese weisen alle ein ähnliches Bildungsgesetz wie die ursprüngliche Ackermannfunktion auf und haben auch ein ähnliches Wachstumsverhalten.
Ebenfalls 1926 konstruierte Ackermann eine Funktion, die diese Vermutung widerlegt, und veröffentlichte sie 1928. Diese Funktion wird heute, ihm zu Ehren, Ackermannfunktion, genannt. Die Ackermannfunktion kann also von einem Computer in endlicher Zeit ausgewertet werden, ist aber nicht primitiv-rekursiv.
1955 konstruierte Rózsa Péter eine vereinfachte Version, die die gleichen Eigenschaften besitzt. Diese Funktion, gelegentlich auch als Ackermann-Péter-Funktion bezeichnet, wird heute vorwiegend benutzt.
Die Ackermannfunktion, notiert als , ist also eine Funktion, die die folgenden Gleichungen erfüllt:
Ab der vierten Zeile können die Funktionswerte nicht mehr mit herkömmlichen Operatoren formuliert werden; man braucht erweiterte Notationen, wie beispielsweise den Hyper-Operator.
Die Ackermannfunktion definiert man üblicherweise rekursiv, d. h., man macht für einige Anfangswerte explizite Angaben und gibt eine Anleitung (das Rekursionsschema), wie man weitere Funktionswerte aus den bereits berechneten erhält.
Dabei ist eine weitere Funktion, die Ackermann nicht weiter beschrieb. (Sie liefert die Startwerte , , , .)
Wenn man vom Wachstum der Ackermannfunktion spricht, meint man oftmals die Funktion .
Auch hier meint man im Zusammenhang mit Wachstumsuntersuchungen oftmals die Funktion , wenn man von der Ackermannfunktion spricht.
Aus dieser Definition ist nicht sofort ersichtlich, dass die Funktion für alle nicht negativen, ganzzahligen und definiert ist. Das ergibt sich jedoch daraus, dass in jedem Schritt, entweder verringert oder aber erhöht und verringert wird. Jedesmal, wenn Null erreicht, wird verringert, also muss auch irgendwann Null erreichen, und dann liefert die erste Zeile der Definition den Funktionswert. Zu beachten ist allerdings, dass es bei einer Verringerung von keine obere Schranke für das Wachstum von in den folgenden Funktionsaufrufen gibt.
Auf der Suche nach den Grenzen von Computern stößt man sehr schnell auf den Begriff der berechenbaren Funktionen. Das sind all die Funktionen, für deren Auswertung man einen Algorithmus angeben kann, also alle Funktionen, die ein Computer berechnen kann. Diese Definition stellt einen sehr schnell vor ein Problem, wenn man von einer konkreten Funktion entscheiden möchte, ob sie berechenbar ist. Findet man einen Algorithmus, der die Funktion berechnet, so ist sie offensichtlich berechenbar. Andernfalls ist ungewiss, ob die Funktion wirklich nicht berechenbar ist oder ob es zwar einen Algorithmus gibt, man ihn aber nicht gefunden hat.
Aus diesem Grund sucht man nach alternativen Definitionen, mit denen man einen solchen Nachweis einfacher führen kann. Ein erster Ansatz hierfür waren die primitiv-rekursiven Funktionen. Dies sind Funktionen, die sich durch einige wenige Regeln aus sehr einfachen Funktionen zusammensetzen lassen.
Einige Zeit vermutete man, dass alle berechenbaren Funktionen primitiv-rekursiv sind, mit den primitiv-rekursiven Funktionen also ein Werkzeug zur Lösung des oben geschilderten Problems gefunden sei. Diese Hoffnung zerstörte jedoch die Ackermannfunktion, von der man nachweisen kann, dass sie berechenbar, aber nicht primitiv-rekursiv ist. (Siehe nachfolgenden Expertenabschnitt.)
Anmerkung: Inzwischen weiß man, dass man aus den primitiv-rekursiven Funktionen die berechenbaren erhält, wenn man noch eine weitere Konstruktionsregel, die sogenannte µ-Rekursion, zulässt.
Für Details zum Beweis, sehe man z. B. im Buch von Schöning nach. (Siehe Literatur)
In diesem Zusammenhang wird die Ackermannfunktion gerne als Benchmark zur Überprüfung von rekursiven Prozedur-Aufrufen benutzt, da ein Programm zur Berechnung der Ackermannfunktion im Wesentlichen nur aus solchen Prozeduraufrufen besteht. In der Definition von Péter wird ja nur direkt berechnet. Die Schwierigkeit bei der Berechnung der Funktionswerte sind also nicht allein deren Größe, sondern die tiefe Verschachtelung der Funktionsaufrufe, die leicht zu einem Stapelüberlauf (engl. Stack Overflow) führt, also dazu, dass dem System der Speicher ausgeht.
Diese Idee geht zurück auf Yngve Sundblad, der 1971 die Funktion benutzte, um diverse Programmiersprachen zu vergleichen. Um zu berechnen, werden Aufrufe getätigt.
Sundblad testete unter anderem, wie groß gewählt werden kann, damit der Computer noch in der Lage ist, diese Zahl zu berechnen. Damals erreichte er . Zum Vergleich hierzu: Mit Java 1.4.2, mit den Standardspeichereinstellungen, erreicht man heutzutage .
Im Laufe der Berechnung werden viele identische Aufrufe mehrfach ausgerechnet. Ein intelligenter Compiler kann dies ausnutzen und die Ergebnisse zwischenspeichern, um solche Aufrufe nur einmal durchführen zu müssen. Damit waren schon 1971 Aufrufe bis durchführbar. Einen bedeutenden Zeitvorteil erhält man auch, wenn man direkt berechnet, statt es rekursiv zu zu expandieren. Die direkte Berechnung von erfordert lineare Zeit in . Die Berechnung von erfordert quadratische Zeit, denn sie führt zu (also für eine Konstante ) verschachtelten Aufrufen von für verschiedene . Die Berechnung von erfordert schließlich eine zu proportionale Zeit (; zur Erklärung der Groß-O-Notation siehe Landau-Symbole).
Diese Umkehrfunktion taucht in der Laufzeitanalyse bestimmter Algorithmen auf, zum Beispiel beim Union-Find-Problem und in Chazelles Algorithmus für minimale Spannbäume. In diesem und anderen Zusammenhängen wird die ursprüngliche Ackermannfunktion oft durch Weglassen additiver Konstanten, oder anderer Modifikationen, leicht umdefiniert zu einer Funktion mit ähnlichem asymptotischen Verhalten. Diese modifizierten Funktionen sind nicht äquivalent zur Ackermannfunktion, aber nach den Maßstäben der Laufzeitanalyse können sie als äquivalent betrachtet werden.
Die rekursive Implementierung der Ackermannfunktion (hier in Pseudo-Code) entspricht direkt der Definition:
function ack(n, m) if n = 0 return m + 1 else if m = 0 return ack(n - 1, 1) else return ack(n - 1, ack(n, m - 1))
Etwas effizienter ist die folgende, teilweise iterative Implementierung:
function ack(n, m) while n ≠ 0 if m = 0 m := 1 else m := ack(n, m - 1) n := n - 1 return m + 1
Noch effizientere Implementierungen verwenden Arrays zur Zwischenspeicherung bereits berechneter Werte, siehe auch Dynamische Programmierung.
In Haskell, einer funktionalen Programmiersprache, spiegelt die Implementierung direkt die Definition wider:
ack 0 m = m+1 ack (n+1) 0 = ack n 1 ack (n+1) (m+1) = ack n (ack (n+1) m)
In Prolog sieht die Implementierung so aus:
ackermann(0,X,Y) :- X >= 0, !, Y is X + 1. ackermann(X,0,Z) :- X > 0, !, X1 is X - 1, ackermann(X1,1,Z). ackermann(X,Y,Z) :- X > 0, Y > 0, X1 is X-1, Y1 is Y - 1, ackermann(X,Y1,W), ackermann(X1,W,Z).
¹eine Zahl mit 19729 Dezimalstellen
Trotz der unvorstellbar großen Zahlen, die schon in dieser Tabelle auftauchen, wurden rekursive Verfahren definiert, die noch schneller wachsende Werte liefern, so zum Beispiel Grahams Zahl.
Wir betrachten nun einen komplexeren Fall, nämlich , den ersten Funktionswert, der so groß ist, dass er praktisch nicht dezimal aufgeschrieben werden kann.
a(4, 3) = a(3, a(4, 2)) = a(3, a(3, a(4, 1))) = a(3, a(3, a(3, a(4, 0)))) = a(3, a(3, a(3, a(3, 1)))) = a(3, a(3, a(3, a(2, a(3, 0))))) = a(3, a(3, a(3, a(2, a(2, 1))))) = a(3, a(3, a(3, a(2, a(1, a(2, 0)))))) = a(3, a(3, a(3, a(2, a(1, a(1, 1)))))) = a(3, a(3, a(3, a(2, a(1, a(0, a(1, 0))))))) = a(3, a(3, a(3, a(2, a(1, a(0, a(0, 1))))))) = a(3, a(3, a(3, a(2, a(1, a(0, 2)))))) = a(3, a(3, a(3, a(2, a(1, 3))))) = a(3, a(3, a(3, a(2, a(0, a(1, 2)))))) = a(3, a(3, a(3, a(2, a(0, a(0, a(1, 1))))))) = a(3, a(3, a(3, a(2, a(0, a(0, a(0, a(1, 0)))))))) = a(3, a(3, a(3, a(2, a(0, a(0, a(0, a(0, 1)))))))) = a(3, a(3, a(3, a(2, a(0, a(0, a(0, 2)))))) = a(3, a(3, a(3, a(2, a(0, a(0, 3))))) = a(3, a(3, a(3, a(2, a(0, 4))))) = a(3, a(3, a(3, a(2, 5)))) = ...
Das ist für einige Zeit der einfachste Fall einer solchen Expansion, und es ist an Hand der Tabelle offensichtlich, warum Funktionswerte wie dieser selten direkt berechnet werden. Es ist auch interessant festzustellen, wie viele Schritte nötig sind, um schon sehr einfach aussehende Ackermann-Ausdrücke zu vereinfachen. Jede Zeile im vorigen Beispiel ist eine einzige Anwendung eines der drei Teile der Definition der Ackermannfunktion.
... = a(3, a(3, a(3, 13))) = ... = a(3, a(3, 65533))
Wenn wir an dieser Stelle mehrere logische Schritte überspringen, könnten wir zu 13 auswerten, und dann versuchen, auszuwerten – das ist 65533. Der nächste Funktionsaufruf, , liefert eine Zahl, die der Zahl der Atome im Universum entspricht, einige dutzend mal mit sich selbst multipliziert. Diese Zahl wird schließlich in die Berechnung eingesetzt, die irgendwann zu einem Ausdruck der Form ausgeschrieben würde, die aber mit unseren Mitteln nicht mehr aufgeschrieben werden kann.
Der wirklich faszinierende Aspekt der Ackermann-Funktion ist, dass die einzige Berechnung, die neben den rekursiven Aufrufen tatsächlich auftaucht, die Berechnung von ist, die einfach um 1 erhöht.
1962 gab Tibor Radó mit der Funktion Fleißiger Biber (busy beaver) eine noch stärker wachsende Funktion als die Ackermannfunktion an, die allerdings nicht mehr berechenbar ist.
Ackermannova funkce | Ackermann function | Función de Ackermann | Fonction d'Ackermann | Ackermann-függvény | アッカーマン関数 | Ackermannfunctie | Funkcja Ackermanna | Ackermann işlevi | Hàm số Ackermann | 阿克曼函數
This article is licensed under the GNU Free Documentation License.
It uses material from the
"Ackermannfunktion".
Home Page • arts • business • computers • games • health • hospitals • home • kids & teens • news • physicians • recreation• reference • regional • science • shopping • society • sports • world