Primitive Rekursion

Werbung
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
Herunterladen