Primitive Rekursion Stelligkeit. Basisfunktionen: Konstante Funktion: 3 const 3 Wert des Ergebnisses. Unabhängig von den Parametern. 3 const 3 (1,1, pr1,3(g,h) (1,1)) Stelligkeit. Projektion: proj 32 Gibt die Komponente an, auf die abgebildet wird. proj 32 (1,1, pr1,3(g,h) (1,1)) Komposition: comp3,2 Gibt die Anzahl der Funktionen an, die miteinander verknüpft werden. Stelligkeit: ist immer gleich der Stelligkeit der verwendeten Funktionen. comp3,2 (proj 32 , proj 13 , add) (1,2,3) = add (proj 32 (1,2,3), proj 13 (1,2,3)) = add (2,1) =3 1 Beispiel dafür, daß die Additionsfunktion primitiv rekursiv ist Die Additionsfunktion add: ` x ` → ` , (n, m) → n + m ist primitiv rekursiv, denn die folgende durch primitive Rekursion definierte Funktion ist aus der Klasse der primitiv rekursiven Funktionen, und berechnet add: add(0,m) = m+0 = m = g(m) g(m) = proj 11 add(n+1,m) = m+n+1 = h(n, m, add(n,m)) 3 h(n, m, add(n,m)) = comp3,2 (proj 3 , proj 13 ; succ) [n+m und davon den Nachfolger =: +1] Klasse der primitiv rekursiven Funktionen Die Klasse der primitiv rekursiven Funktionen ist aufgebaut aus drei Grundfunktionen und zwei Erzeugungsstrategien. Definition: Uwe Schöning: „Theoretische Informatik – kurzgefasst“, 3. Auflage, S.109. Die Klasse der primitiv rekursiven Funktionen auf den natürlichen Zahlen ist induktiv wie folgt definiert: 1) Alle konstanten Funktionen sind primitiv rekursiv. 2) Alle identischen Abbildungen (Projektionen) sind primitiv rekursiv. 3) Die Nachfolgerfunktion succ(n) = n+1 auf den natürlichen Zahlen ist primitiv rekursiv. 4) Jede Funktion, die durch Einsetzung (Komposition) von primitiv rekursiven Funktionen entsteht, ist selber auch primitiv rekursiv. 5) Jede Funktion, die durch primitive Rekursion aus primitiv rekursiven Funktionen entsteht, ist primitiv rekursiv. Für eine primitiv rekursive Funktion f gilt: ` → ` Primitive Rekursion Primitive Rekursion bedeutet, daß die Definition von f(n+1,...) zurückgeführt wird auf f(n,...). Formal: f muss das Gleichungssystem der folgenden Form erfüllen: f (0, ... ) = g ( ... ) f (n+1, ... ) = h (f(n, ... ), ...) wobei g und h bereits primitiv rekursive Funktionen sind. Dabei gilt: ist f n-stellig, so ist g n-1-stellig und h n+1-stellig. Falls f durch eine primitive Rekursion definiert ist, hat sie die folgende allgemeine Form bzw. entspricht sie dem folgenden Schema: f(0, x1, ...., xr) = g(x1,....., xr) f(n+1, x1, ...., xr) = h(f(n, x1, ...., xr), n, x1, ...., xr) Rekursive Funktion Eine rekursive Funktion ist eine Funktion, die durch eine endliche Menge von Regeln definiert und für verschiedenen Argumente mit Hilfe folgender Komponenten spezifiziert ist: Variablen, nichtnegativen ganzzahligen Konstanten, der Nachfolger-Funktion, der Funktion selbst sowie einem durch Komposition von Funktionen aus diesen erzeugtem Ausdruck. Die Klasse der rekursiven Funktionen ist identisch zur Klasse der partiell rekursiven Funktionen. 2 Primitiv rekursive Funktion Eine Funktion heißt primitiv rekursiv, wenn sie aus einer endlichen Anzahl von Anwendungen der Komposition und der primitiven Rekursion, angewandt auf die Konstante 0, die Nachfolger-Funktion oder eine Projektion Pi(x1,.....,xn) = xi, besteht. Jede primitiv rekursive Funktion ist eine total rekursive Funktion. Klasse der μ -rekursiven Funktionen: Uwe Schöning: „Theoretische Informatik – kurzgefasst“, 3. Auflage, S.115: Definition: Die Klasse der μ -rekursiven Funktionen ist die kleinste Klasse von (eventuell partiellen) Funktionen, die die Basisfunktionen (konstante Funktionen, identische Abbildung, Nachfolgerfunktion) enthält und abgeschlossen ist unter Einsetzung, primitiver Rekursion und Anwendung des μ -Operators. μ - Operator: μ f: `k → ` Funktionen, die Funktionen in Funktionen abbilden, heißen Funktionale oder auch Operatoren. Durch den μ -Operator wird eine (durch das erste Argument einer Funktion f) beschränkten Suche nach einer Nullstelle der Funktion f realisiert. Eine echte Erweiterung der Klasse der primitiv rekursiven Funktionen wird durch Hinzunahme des μ -Operators erreicht. Sei f eine gegebene k+1-stellige Funktion. Die durch Anwendung des μ -Operators auf f entstehende Funktion ist g: ` k → ` mit g(x1,......,xk) = min f(n, x1,......,xk) = 0 falls m ≥ n f(m, x1,......,xk) falls m < n n| Hierbei wird min∅ = undefiniert gesetzt. Das heißt, durch Anwendung des μ -Operators können wirklich partielle Funktionen entstehen. Beispielsweise entsteht durch Anwendung des μ -Operators auf die zweistellige, konstante Funktion f(x,y) = 1 die vollständig undefinierte Funktion Ω. Mit μ f: ` k → ` bezeichnen wir die solcherart über die (k+1)-stellige Funktion f definierte Funktion. μ -Operator minimalisiert das Rekursionsargument Der Rekursionsargument schon den Wert Null angenommen hat. und prüft, ob das Der μ -Operator ist ein Minimierungsoperator min(f(x)), der als das kleinste x definiert ist, für das f(x) = 0 gilt. Durch den μ -Operator können auch partielle Funktionen erzeugt werden. Die Anwendung des μ -Operator auf eine berechenbare Funktion führt wieder zu einer berechenbaren Funktion. 3 Primitiv rekursive Funktionen entsprechen den loop-Algorithmen. Zur Definition rekursiver Funktionen, die den while-Algorithmen entsprechen, vergegenwärtigen wir uns noch mal, daß bei Verwendung des Rekursionsschemas zur Berechnung von f(m, ...) der Wert m die Rolle des Zählers für die Anzahl der Rekursionsschritte spielt. m entspricht bei der Umsetzung in einen loop-Algorithmus dem Schleifenzähler. Die Verallgemeinerung zu whileAlgorithmen erreichen wir, wenn wir m solange hochzählen, bis eine bestimmte Bedingung, z.B. f(m,...) = 0, erfüllt ist. Genau dazu wird der μ -Operator definiert. μ m: f(m, n1, ..., nk) = min f(m, n1, ..., nk) = 0 und m alle f(j, n1, ..., nk) mit 0 ≤ j < m sind definiert μ m: f(m, n1, ..., nk) ist undefiniert, wenn es kein solches m gibt, oder wenn beim Hochzählen m = 0, 1, 2, ... zuerst ein Argument-Tupel (m, n1, ..., nk) erreicht wird, für das f nicht definiert ist. Der μ-Operator μ m: f(m, n1, ..., nk) definiert im allgemeinen eine partielle Funktion. h(n1, ..., nk). h ist zum Beispiel dann total, wenn f eine totale Funktion ist und es für jedes kTupel (n1, ..., nk) mindestens einen Wert m mit f(m, n1, ..., nk) = 0 gibt. Satz von Kleene Uwe Schöning: „Theoretische Informatik – kurzgefasst“, 3. Auflage, S.116. Für jede n-stellige μ -rekursive Funktion f gibt es zwei (n+1)-stellige, primitiv rekursive Funktionen p und q, so daß sich f darstellen lässt als f(x1,......,xn) = p(x1,......,xn, μ q( x1,......,xn)) Hierbei ist μ q die durch Anwendung des μ -Operators auf q entstehende (n-stellige) Funktion. P: Klasse der primitiv rekursiven Funktionen P⊂R R: Klasse der Erweiterung durch den μ - Operator μ -rekursiven Funktionen P: Klasse der primitiv rekursiven Funktionen 4 R: Klasse der μ rekursiven Funktionen Korollar Eine totale Funktion f: ` → ` , für die nur an endlich vielen Stellen f(n) ≠ 0 gilt, ist primitiv rekursiv. Alle Funktionen aus der Klasse der primitiv rekursiven Funktionen sind total, weil die drei Grundfunktionen total sind, und die Erzeugungsstrategien Einsetzung und primitive Rekursion von totalen Funktionen wieder zu totalen Funktionen führen. Die Klasse der primitiv rekursiven Funktionen ist für die Charakterisierung der im intuitiven Sinn (Churche These) berechenbaren Funktionen nicht geeignet, weil die partiellen Funktionen ausgeschlossen sind Tatsächlich gibt es sogar totale Funktionen, die im intuitiven Sinn berechenbar, aber nicht primitiv rekursiv sind. Bekanntestes Beispiel dafür ist die Ackermann Funktion. Ackermann Funktion Die Ackermann Funktion ist nicht primitiv rekursiv, aber μ-rekursiv! Definition a(0,y) = y+1 a(x,0) = a(x-1,1), a(x,y) = a(x-1, a(x,y-1)), x>0 x, y > 0 Die Ackermann Funktion ist nicht primitiv rekursiv, aber μ -rekursiv. Für den Beweis braucht man mehrere Lemmata: 1. Lemma: y < a(x,y) 2. Lemma: a(x,y) < a(x,y+1) 3. Lemma: a(x,y+1) ≤ a(x+1,y 4. Lemma: a(x,y) < a(x+1,y) 5. Lemma: Für jedes LOOP-Programm gibt es eine Konstante k, so daß für alle n gilt : fP(n) < a(k,n). ⇒ Die Ackermann Funktion ist nicht LOOP-berechenbar, sonder WHILE-berechenbar. Die Ackermann Funktion ist ein Beispiel dafür, daß es totale, berechenbare Funktionen gibt, die nicht primitiv rekursiv sind, denn es läßt sich zeigen, daß die Ackermann Funktion schneller wächst als jede primitiv rekursive Funktion. In einem loop-Algorithmus ist die Anzahl der ineinander geschachtelten Schleifen konstant und aus dem Programmtext ersichtlich. Mit while-Algorithmen und allgemein mit jeder Turing-mächtigen Sprache lassen sich jedoch auch Schleifen formulieren, die eine variable Anzahl geschachtelter for-Schleifen simulieren. a(1, n) = n+2 a(2, n) = 2 ⋅ n+3 eine for-Schleife a(3, n) = 2n+3 – 3 zwei geschachtelte for-Schleifen .. 2 a(4, n) = 22 drei geschachtelte for-Schleifen 5 Das erste Argument charakterisiert die Schachtelungstiefe, denn es gilt für m ≥ 1: a(m,n) = a(m-1, a(m-1, ... a(m-1,1)...)) Die Schachtelungstiefe von for-Schleifen liefert zusammen mit der maximalen Eingabe eine Schranke für die größte von einem loop-Algorithmus berechenbare Zahl. Es läßt sich zeigen, daß die Ackermann Funktion schneller wächst als alle Ergebnisse von loop-Algorithmen bei gleicher Eingabe. Die Ackermann Funktion stellt also selbst eine solche Schranke dar. Die Ackermann Funktion ist nicht primitiv rekursiv. Beweis durch Widerspruch: Wir nehmen an, daß die Ackermann Funktion primitiv rekursiv ist. Dann ist auch die Funktion f(n) = ack(n,n) primitiv rekursiv, denn f entsteht aus ack durch Einsetzung (f(n) = ack(proj 11 (n), proj 11 (n))). Es gibt eine Konstante c mit f(n) < ack(c,n) für alle n ∈ ` . Betrachtet man nun speziell n = c, so folgt ack(c,c) = f(c) < ack(c,c), was ein Widerspruch ist. Ein einfaches Programm in Java 1.2 zur Berechnung der Ackermann Funktion. Die Parameter können über die Konsole eingegeben werden. import java.io.*; public class Ackermann { static int a, b; static int Ack(int m, int n) { if ((m==0) && (n>0)) return (n+1); else if((n==0) && (m>0)) return (Ack(m-1,1)); else return (Ack(m-1, Ack(m, n-1))); } public static void main (String args[]) { InputStreamReader in = new InputStreamReader(System.in); BufferedReader puffer = new BufferedReader (in); System.out.println("\nBitte geben Sie zwei natuerliche Zahlen ein."); try { int x, y; System.out.println("\nDer erste Parameter ist:"); a = new Integer(puffer.readLine()).intValue(); System.out.println("\nDer zweite Parameter ist:"); b = new Integer(puffer.readLine()).intValue(); System.out.println("\nDas Ergebnis von Ack("+a+","+b+") ist:"); System.out.println(Ack(a,b)); } catch (IOException e) { e.printStackTrace(); } } } 6 Minimalisierung Bei der Minimalisierung ist das Rekursionsargument gesucht, für das die Funktion Null ergibt. Eine Minimalisierung durchführen heißt, zu prüfen, ob die Funktion den Wert Null hat. Bekannte primitiv rekursive Funktionen add(n,m) sub(n,m) mult(n,m) succ(n) hoch(n,m) turm(n,m) minus(n,m) m1(n) Imperative Programmierung in Modula 2 von primitiv rekursiven Funktionen 1 PROC const 0 (n : CARD) : CARD; BEGIN RETURN 0 END; PROC proj 32 (n1, n2, n3 : CARD) : CARD; BEGIN RETURN n2 END; 3 PROC proj 3 (n1, n2, n3 : CARD) : CARD; BEGIN RETURN n3 END; PROC P1 (n1, n2, n3 : CARD) : CARD; BEGIN RETURN n2 3 add (proj 32 (n1, n2, n3), proj 3 (n1, n2, n3) END; n2 n2 P1: add (n2, n3) PROC P2 (n, n1 : CARD) : CARD; VAR x, i : CARD; BEGIN 1 x : = const 0 (n1); FOR i := 1 TO n DO x := P1 ( I-1, n1, x) END RETURN x END; 7 PROC proj 11 (n1 : CARD) : CARD; BEGIN RETURN n1 END; PROC suc (n1 : CARD) : CARD; BEGIN RETURN n1 + 1 END; PROC P3 (n1, n2, n3 : CARD) : CARD; BEGIN 3 RETURN suc (proj 3 (n1, n2, n3)) END; PROC P4 (n, n1 : CARD) : CARD; VAR x, i : CARD; BEGIN x : = proj 11 (n1); FOR i := 1 TO n DO x := P3 ( i -1, n1, x) END RETURN x END; PROC P3´ (n1, n2, n3 : CARD) : CARD; BEGIN RETURN n3 + 1 END; PROC P4´ (n, n1 : CARD) : CARD; VAR x, i : CARD; BEGIN x : = n1; FOR i := 1 TO n DO x := x + 1 END RETURN x END; PROC P2´ (n, n1 : CARD) : CARD; VAR x, i : CARD; BEGIN x : = 0; FOR i := 1 TO n DO x := P4´ ( n1, x) END RETURN x END; PROC P2´´ (n, n1 : CARD) : CARD; VAR x, i, i´, x´, n1, n´ : CARD; BEGIN x : = 0; 8 FOR i := 1 TO n DO n´ := n1; n1´ := x; x := n1`; FOR i` := 1 TO n` DO x` := x` + 1 END x := x` END RETURN x END; Beispiel Gegeben ist die primitiv rekursive Funktion in Termdarstellung: 3 3 f = pr1,3 (const 11 , comp3,2(comp3,2(proj 32 , const 3 ;add),proj 3 ;mult)) mult ist hierbei definiert als mult(n,m) = n ∗ m und add sei add(n,m) = n+m; beide Funktionen können als bekannte primitiv rekursive Funktionen genutzt werden. a) Geben Sie eine Abwicklung des Schemas für das Argumentpaar (2,1) an. f(2,1) = pr1,3(g,h) (2,1) 3 3 = pr1,3 (const 11 , comp3,2(comp3,2(proj 32 , const 3 ;add),proj 3 ;mult)) (2,1) 3 3 = comp3,2 (comp3,2 (proj 32 , const 3 ;add), proj 3 ;mult) (1,1, pr1,3(g,h) (1,1)) 3 3 = mult (comp3,2(comp3,2 (proj 32 , const 3 ;add)) (1,1, pr1,3(g,h) (1,1), proj 3 (1,1, pr1,3(g,h) (1,1)) 3 3 = mult (add (proj 32 (1,1, pr1,3(g,h) (1,1)), const 3 (1,1, pr1,3(g,h) (1,1)), proj 3 (1,1, pr1,3(g,h) (1,1)) = mult (add (1,3), pr1,3 (g,h)(1,1)) = mult (4, pr1,3 (g,h)(1,1)) = 4 ∗ pr1,3 (g,h)(1,1) pr1,3 (g,h)(1,1) 3 3 = pr1,3 (const 11 , comp3,2(comp3,2(proj 32 , const 3 ;add),proj 3 ;mult)) (1,1) 3 3 = comp3,2(comp3,2(proj 32 , const 3 ;add),proj 3 ;mult)) (0,1, pr1,3(g,h) (0,1)) 3 3 = mult (comp3,2(proj 32 , const 3 ;add) (0,1, pr1,3(g,h) (0,1), proj 3 (0,1, pr1,3(g,h) (0,1))) 3 3 = mult ( add (proj 32 (0,1, pr1,3(g,h) (0,1)), const 3 ), proj 3 (0,1, pr1,3(g,h) (0,1))) = mult (add(1,3), pr1,3(g,h) (0,1))) = mult (4, pr1,3(g,h) (0,1)) = 4 ∗ pr1,3 (g,h)(0,1)) pr1,3 (g,h)(0,1) 3 3 = pr1,3 (const 11 , comp3,2(comp3,2(proj 32 , const 3 ;add),proj 3 ;mult)) (0,1) = const 11 (0,1) =1 ⇒ 4 ∗ pr1,3 (g,h)(1,1) = 4 ∗ 4 ∗ pr1,3 (g,h)(0,1) = 4∗4∗1 = 42 = (1+3)2 9