Info2

Werbung
Seite 1 von 68
Technische Universität München
Prof. A. Knoll
Einführung in die Informatik II
Sommersemester 2003
Ausgearbeitet von Coskun Tayfur
Vorbemerkung: In dieser Version sind noch diverse kleinere Fehler enthalten, da das korrigieren von diesen Kleinigkeiten sehr sehr viel Zeit
in Anspruch nimmt, und ich sowieso schon sehr sehr viel Zeit für dieses Skript investiert habe und nun meine mündliche Diplomvorprüfung
nun kurz vor mir liegt und weil gleich danach das Wintersemester beginnt, setze ich die Verion so online. Habt also bitte Nachsicht bei
kleineren Rchteschriebfhlern ;-).
Stuiden sgaen ürbigens, dsas die Bchusatben bleiebig vretuascht sien können und dsad der Mscenh sie imremncoh lseen knan, so lnag etsrer
und ltzeer Bchusatbe rchitig sind. Also seids net so kleinlich 
Homepage: www.in.tum.de/~coskun
Seite 2 von 68
Zusammenfassung Info2
Inhaltsverzeichnis
Programmerstellung
Definition Programmierung
Definition Programm
Grundtypen von Programmiersprachen
Grundtypen von Programmierstilen
Seite 4
Stack- Maschinen und –Sprachen
Dreidimesinaler Stack-Maschine „Entwurfsraum“
Forth
Entstehung und Verwendung
Funktionen durch Wörter
Arbeiten mit Zeichen
Kontollstrukturen in Forth
IF THEN ELSE
CASE
Zählschleife DO ... LOOP
Rekursion in Forth
Die Türme von Hanoi
In Prolog
In Forth
Seite 4 - 15
Seite 4,5
Seite 5 - 15
Seite 6
Seite 7
Seite 7
Seite 8 - 9
Postscript
Entstehung und Verwendung
Vektor Graphikausgabe
Einige wichtige graphische Operatoren
Vektorfonds
Operatoren und Arihtmetik
Die Stacks von Postscript:
Multi- Stack- Sprache
Variablen in Postscript
Prozeduren in Postscript
Kontrollstrukturen in Postscript
IF IFELSE
Rekursion in Postscript
Schleifen in Postscript
Fraktale
Wiederholung: Beziehung zwischen
Chomsky Grammatik und Automaten.
Turing - Maschine
Definition der Turing Maschine
Beispiel: Inkrementierung
Halbierung von Unärzahlen
Addition von Binärzahlen
Seite 9 - 13
Seite 10- 15
Seite 11- 12
Seite 12-13
Seite 13 - 19
Seite 13
Seite 13- 14
Seite 14
Seite 14
Seite 14- 15
Seite 15
Seite 15
Seite 15
Seite 16 – 18
Seite 18 -19
Seite 20
Seite 20 - 27
Seite 22
Seite 23-24
Seite 25
Seite 26
Seite 3 von 68
Busy Beaver
Funktionale Programmierung
Rekursion
lineare Rekursion
Nicht lineare Rekursion
Quicksort
Permutation auf Sequenzen
Verschränkte Rekursion
Geschachtelte Rekursion Bsp.: Ackermann
Terminierung funktionaler Programme
Abstiegsfunktion
Beispiel 1 ggT
Beispiel 2 y = mn
Beispiel 3 Ackermann
Beispiel 4 Klaus
Beispiel 5 Permutation von Sequenzen
Aufwand von Algorithmen
Korrektheit von Quicksort
MergeSort
Rekursive Datensturkturen
Verschränkte Rekursion
B- Bäume
AVL – Bäume
Funktoren: Datentypen mit Funktionen als Argument
Algorithmus zur Tiefensuche
Induktive Beweise über rekursive Sorten
Das Element Bottom: 
Ordnugnen über Mengen
Vollständige Halbordnung
Anwendung des Kleenschen Satzes
auf Strukturen funktionaler Sprachen:
Semantik rekursiver Funktionsdeklarationen
Beispiel (s. ÜB 5 Aufgabe 26)
Funktionale Programmierung und   Kalkül .
Seite 27
Seite 28
Seite 28
Seite 29
Seite 30
Seite 32
Seite 32 – 33
Seite 33 - 37
Seite 3
Seite 35
Seite 36
Seite 37
Seite 37
Seite 38 – 40
Seite 41
Seite 42
Seite 44
Seite 45
Seite 46
Seite 47
Seite 47
Seite 49
Seite 49
Seite 52
Seite 52
Seite 53
Seite 54
Seite 58
Seite 60
Seite 59 -
Seite 4 von 68
Programmerstellung
Definition Programmierung
ist die Umsetzung eines Algorithmus in eine Form, welche die Maschine interpretieren kann
Definition Programm
ist die Formuierung von Algorithmen in einer bestimmten Programmiersprache.
Grundtypen von Progammiersprachen
1.) Funktionale Programmiersprachen
2.) Logik-basierte Programmiersprachen
3.) Imperative Programmierprachen
Grundtypen von Programmierstilen
1.) Funktional
2.) Komponentbasiert imperativ
3.) Ereignisbasiert
4.) OOP (& Aspektorientiert)
5.) Maschinennah
Stack- Maschinen und -Sprachen
Stack Definition in Ocaml
type ’a t = { mutable c : ’a list }
exception Empty
let create () = { c = [] }
let clear s = s.c <- []
let push x s = s.c <- x :: s.c
let pop s = match s.c with hd :: tl -> s.c <- tl; hd | [] -> raise Empty
let length s = List.length s.c
let iter f s = List.iter f s.c
Infix- Darstellung (7 + 14) * 2 = 42
Postfix- Darstellung 2 7 14 + * = 2 21 * = 42
Kelleroperationen werden in der Postfix Darstellung gehandhabt. Sehen wir uns ein Beispiel
an:
in
Position
Stack
0
1
2
3
4
2
2
2
42
7
21
21
14
*
*
+
*
Schritt
1
2
3
4
Dreidimensionaler Stack- Maschine „Entwurfsraum“
5
6
7
8
Seite 5 von 68
Stack- Maschinen können natürlich auch mehrere Stacks haben, um ihre Mächtigkeit zu
steigern. Außerdem kann auch die Größe Variabel sein und auch die Typen, die in einem
Stackplatz gespeichert werden können.
Stackanzahl: Es können Stacks mit anderen Aufgaben existieren, z.B.: ein Return Stack zur
Speicherung von Rücksprungadressen bei Unterprogrammaufrufen.
Stackgröße: Wählt man optimal zwischen der Komplexität der zu entwickelnden Ausdrücke
und den Kosten für den Speicher.
Operatorenlänge: In einem Maschinen Befehlssatz können auch Operatoren durch opcodes
gespeichert werden. Diese sind Zeiger auf eine andere Stelle im Computer, üblicher Weise
der Hauptspeicher. Das heißt, der opcode gibt diejenige Adresse im Hauptspeicher an, ab die
der eigentliche Befehl beginnt. Die Anzahl dieser im Stack aufeinander folgenden codes gibt
die Operatorenlänge an. Üblich sind hier 0-, 1-, oder 2- opcode Maschinen.
In der ALU (Arithmetic Logic Unit), welches das arithmetische Herz eines Computers ist
können arithmetische Operationen wie Addition, Subtraktion, Logische Funktionen wie
AND, OR, XOR ausgeführt werden.
Das oberste Stackelement steht
immer im TOS (Top of Stack)
und ist damit für die ALU direkt
als Operant verfügbar. Das zweite
kann über den Datenbus (im Bild
blau) an die ALU weiter gereicht
werden.
Dabei zeigt PC (Program Counter) immer auf
den als nächstes auszuführenden Befehl im
Hauptspeicher.
Der Return Stack hat üblicher Weise eine Lifo
Struktur und leistet hervorragende Dienste bei der
Speicherung von Rücksprungadressen.
Forth
Wir wollen nun eine Stack- orientierte Programmiersprache kennen lernen: Forth.
Seite 6 von 68
Entstehung und Verwendung
0- Operand Stack Machines sind in Forth direkt 1:1 umsetzbar. Es wurde von Charles Moore
zur (Echtzeit-) Steuerung eines Observatoriums- Teleskops entwickelt. Das Ziel war es, die
Programmier- Produktivität gegenüber Assembler Programmen zu steigern.
Heute gilt Forth als Programmiersprache zur „direkten“ Kommmunikation zwischen Mensch
und Maschine. Daher ist die Syntax auch Maschinenorientiert. Außerdem ist Forth Sprache,
Betriebssystem und Maschinen-Monitor in einem. Forth zeichnet sich des weiteren durch
seine Flexibilität aus, beispielsweise ist eine Spracherweiterung möglich, was natürlich sehr
erwünscht ist.
Programmieren mit Forth
Es gibt verschiedene standardkonformer Versionen von Forth, sie lassen sich auf folgender
site runter laden: www.forth.org/compilers
Im Folgenden wird portable ANS FORTH (pforth) verwendet.
Der Stack lässt sich durch Eingabe von Operanden ganz einfach füllen. Beipsiel:
Begin AUTO.INIT
100 200
ok
Stack<10> 100 200
300 +
ok
Stack10> 100 500
Wir schreiben in den Stack 100 und 200
Aktueller Stack Inhalt
Wir schreiben 300 und + in den Stack
=> 100 200 300 + Da + ein zweitelliger
Operand ist, wählt er die letzten beiden
Werte aus dem Stack und wendet + darauf
an. => 100 500.
Da die Syntax sich dadurch von selbst erklärt folgen nun Befehle in Forth und ihre
Beschreibungen.
.
swap
rot
dup
over
2swap
2dup
2drop
2over
-rot
nip
pick
Tuck
Entfernt TOS aus dem stack und gibt dieses aus.
vertauscht die obersten beiden Elemente
1 2 3 4  1 3 4 2Das dritte Element von wird also an den TOS geholt und
die letzten beiden nach unten verschoben. Wir rotieren also die ersten drei
Elemente ein mal,
Kopiert TOS. Wir haben also das oberste Element nun 2 mal dastehen.
Kopiert das zweite Element auf den TOS
1 2 3 4 3 4 1 2 obersten 2 Zellen tauschen
12 1212
1 2  _ löscht die obersten 2 Elemente
1234123412
1 2 3  3 1 2 rückwärts rotieren
1 2  2 Entfernt Element unter TOS
Position
1 2 3 4 5
1 2 3 4 5 6 7

Stack
a b c d 2
a b c d b
12212
Seite 7 von 68
roll
Position 1 2 3 4 5
1 2 3 4 5
Stack
a
b c
d 2  a c d b
Wir rotieren also durch das Element, was an der Position steht, welches
wiederum im TOS angegeben ist – ganz einfach
Funktionen durch Wörter
Auch in forth sit es möglich Funktionen zu schreiben. Dazu speichert man Folgen von Daten
und Opertaionen zusammen unter einem Wort ab und fügt es damit dem Forth- Wörterbuch
hinzu,. Syntax: Doppelpunkt leitet Bezeichnung ein, Strichpunkt schleißt folge ab
Beispiel:
: sqr dup * ;
Stack: 100 5 sqr  100 5 dup *  100 5 5 *  100 25
Arbeiten mit Zeichen
Aud dem Stack können nur Zahlen abgelegt werden. Daher werden Zeichen und aamti vor
allem Zeichenketten durch Zahlen repräsentiert. Beispiel:
72 EMIT 105 EMIT
Hi ok
Der Operator EMIT wandelt also den TOS gemäß Ascii in das entsprechende Zeichen um.
Natürlich ist dies umgekeht mit dem Operator char ebenso möglich. Beispiel:
char W
ok
Stack: 87
Achtung. Der Operator char arbeitet nicht auf dem TOS sondern auf dem folgenden Symbol.
Zur Verarbeitung von Zeichenketten eignen sich nullstellige Funktionen:
: gemuese .“ Kohl, ...“
Durch Eingabe von gemuese . wird die Zeichenkette dann wieder ausgegeben. Hier ist .“ der
Operator, der den beginn der Zeichenktette definiert. Das Leerzeichen ist dabei sehr wichtig,
sonst funktionierts nicht und hat schon manchen Forth Programmierer bei der suche nach dem
Fehler zur Verzweiflung gebracht, also immer dran denken. “ definiert dann natürlich wieder
das Ende der Zeichenkette. Anderes etwas kompleyxeres und mächtigeres Beispiel:
: testkey
.“ Hit a key: „ KEY CR
.“ That = „ . CR
;
CR steht hier für Corsor Return und KEY wartet bis der Benutzer eine Taste gedrückt hat und
schreibt dann den entsprechenden Wert auf den Stack.
Seite 8 von 68
Desweiteren ist es natürlich auch mögich und sinnvoll Programme seperat abzuspeichern und
in forth einzulesen. Dies geht mit dem Befehel include. Es gibt Win Versionen von Forth wo
dies auch mittels Drag & drop möglich ist.
Kontollstrukturen in Forth
Explizite Sprünge existieren nicht, da Forth eine strukturrierte programmiersprache darstellt.
Jedoch wohl folgende Bedingungsreaktionen:




IF (Bedinung) THEN
IF (Bedingung) ELSE (false Code) THEN
DO … LOOP: Zählschleife
BEGIN… WHILE…UNTIL: While-Schleife
BNF- Syntax der einfachen Verzweigung: <cond> IF <true-body> THEN .
Gibt cond -1 = TRUE zurück, so wird der <true-body> ausgeführt.
Bei der allgemeinen Verzweigung mit dem ELSE Zweig sieht die BNF Syntax
folgendermaßen aus: <cond> IF <true-body> ELSE <false-body> THEN; Das wirkt recht
ungewöhnlich und unnötigerweise kompliziert, ist es auch. Aber schwer ist es nicht, IF und
THEN kann hierbei als Klammer betrachtet werden. So ergibt sich doch der Sinn des ganzen
auch viel leichter. Wenn auf dem Stack ein IF gefunden wird, wird als erstes der
vorangegangene Bool- Wert überprüft. Ist dieser -1, also inforth true, so wird der True Body
ausgeführt. Ist der boolsche Wert jedoch 0, so wird der false Body ausgeführt, so einfach ist
das.
Einfaches Beispiel dazu:
 Programmname
1000 und > wird auf den stack geschrieben
=> Stack: x 1000 >. Ist x nun größer als 1000
so wird dies durch x 1000 ersetzt durch -1. ist
x kleiner werden diese beiden durch 0, also
false ersetzt. Enstprechend werden die
Zweige ausgeführt.
: gt1000
1000 >
IF .“ zu gross“
THEN
;
Case- Verzweigung
: Testcase
case
0 OF .“ Ausdruck 0“ ENDOF
1 OF .“ Ausdruck 1“ ENDOF
2 OF .“ ...“ ENDOF
DUP .“ ungueltige Eingabe
ENDCASE CR
;
Zählschleife: DO ... LOOP
Diese einfachste Form aller Schleifen, die Zählschleife hat in Forth folgende Syntax:
Seite 9 von 68
<upper> <lower> DO <Loop-Body> LOOP
Beispiel:
: spell
.” ba”
4 0 DO .” na”
LOOP
;
Was tut die Funktion spell? Nun, sie gibt zunächst einmal die Zeichenkette “ba” aus. In der
nächsten Zeile beginnt die Schleife. Die 4 gibt dabei an, von wo an die Schleife beginnen soll.
Nachjedem durchlauf wird diese Zahl dekrementiert. Sprich im zweiten durchlauf steht hier
nur noch eine 3. Ist die folgende Zahl 0 mit der „Startzahl“ identisch, wird die Schleife
verlassen, diese Schleife gibt also 4 mal hintereinander „na“ aus, nach dem sie „Ba“
ausgegeben hat. Der Stack durchlauft also folgende Schritte beim Aufruf von spell.
1.)
2.)
3.)
4.)
5.)
6.)
... spell
... ba 4 0 DO .“na“
...bana 30 DO .“na“
...banana 2 0 DO .“na“
…bananana 1 0 DO .”na”
…banananana 0 0
Rekursion in Forth
Wir zeigen die Rekursion in Forth am besten durch ein einfaches Beispiel:
1
2
3
4
5
6
: fak (n1 -- n2)
dup
1 > IF
DUP 1- recurse *
ELSE DROP 1
THEN ;
Analysieren wir mal dieses Programm in dem wir es in seine Einzelteile zerlegen und und
überlegen was es macht.
Schritt Stack
1
3 fak
Beschreibung
Hier wird also fak aufgerufen, 3 ist dabei unser n1 im Programm.
2
Und so sieht unser Stack dann aus. Wir können also nun schritt
für schritt die Operatoren anwenden.
3
4
3 3 1 > IF DUP
1- recurse *
ELSE DROP 1
THEN
3 -1 IF DUP 1recurse * ELSE
Drop 1 Then
3 DUP 1 -
da 3 > 1. Als nächstes kommt IF. Vor dem IF steht -1, also true.
Der True Teil der Verzweigung wird also ausgeführt und der
ELSE Teil verworfen.
Dup verdoppelt also unsere drei, das 1 – danach zieht
Seite 10 von 68
recurse *
5
3 2 recurse *
6
3 2 fak *
7
3 2 1 fak * *
8
9
10
321**
32*
6
dekrementiert die Kopie von unserer drei, so dass unser Stack im
nächsten Schritt so aussieht:
Recurse ruft die Funktion in der sie steht, also sich selbst noch
mal auf, wie wir es von Rekursionen ja bereits gewohnt sind.
Nun, hier geschieht das gleiche noch mal, ich verzichte hier auf
die gleiche detaillierte Angabe, da alles ganz analog zum ersten
rekursions schritt geschieht.
So sieht dann unser Stack aus. Hier passiert jedoch was anderes.
Denn wir erfüllen hier nicht mehr die Bedingung, dass TOS, also
für die Funktion fak unserre 1 größer 1 sein soll. Wir landen also
im False Zweig unserer Verzweigung. Das heißt unsere 1 wird
gedropped, also fallen gelassen und durch eine 1 ersetzt, ein sehr
unnötiger Schritt, meiner Meinung nach. Das Programm dürfte
die selber Mächtigkeit haben, wenn man den ELSE Teil
weglässt.
Nun einfaches Anwenden der Operatoren
2*1=2
3*2=6
Die Türme von Hanoi
Aufgabe: Versuche den dargestellten Stapel auf einen der
anderen Pfähle umzuschiten. Dabei gilt:
1.) Die größeren Scheiben müssen immer unten liegen.
2.) Es darf nur eine Scheibe auf einmal bewegt werden.
Lösungsidee:
Die Lösung des Problems lässt sich rekursiv lösen. Dazu überlegen wir uns einfach wir haben
einen Hanoi Turm mit n Scheiben. Den führen wir zurück auf n-1 Scheiben. Schauen wir uns
das mal bildlich an:
Die erste Zeile im Bild ist äquvialent zur zweiten. Wir verschieben anstatt des ganzen Turmes
mit n Blöcken nur einen Turm mit n-1 Blöcken und lassen einen Block liegen. Wenn wir
diesen Schritt rekursiv zurück denken, können wir so praktisch den ganzen Turm
verschieben. Verscuhen wir uns das mal klarer zu machen. Wir fangen bei null an, und haben
den Zustand wie in die erste Zeile in der Abbildung zeigt.
Seite 11 von 68
-
-
So, nun nehmen wir den ganzen Stapel bis auf den letzten Block (wir stellen uns vor
wir haben eine rekursive Funktion die n-1 Blöcke verschieben kann) und packen den
auf third.
Wir können nun den letzten Block leicht auf snd verschieben und packen dann wieder
rekursiv den Turm mit n-1 Blöcken auf third nach snd.
Wie würde diese Rekursion in Prolog aussehen?
bewege(A, _, C,1) :- writef(‚Lege die oberste Scheibe von Turm %w auf Turm %w.
\n’, [A,C]).
bewege (A,B,C,N) :- M is N-1,
bewege (A,C,B,M),
bewege( A,B,C,1),
bewege(B,A,C,M).
anoi(N) :- bewege(a,b,c,N).
Sehen wir uns ein Beispielaufruf in einem Baumdiagramm an:
bw(a,b,c,3)
bw(a,c,b,2)
bw(a,b,c,1)
bw(b,a,c,2)
bw(a,b,c,1) bw(a,c,b,1), bw(c,a,b,1)
bw(a,b,c,1)
bw(b,c,a,1) bw(b,a,c,1),
[a,c]
[a,c]
[a,b]
[c,b]
[b,a]
[b,c]
[a,c]
Seite 12 von 68
Was sagt uns dieser Baum?
Nun die eckig eingeklammerten Ausdrücke sind das Ende einer Rekursion, d.h. wir sind bei
der Abbruchbedingun angelant. [a,c] bedeutet z.B.: „Bewege obersten Block von a nach b.“
Die Rekursionen werden der Reihe nach ausgeführt, bis sie jeweils ihre Abbruchbedinung
erreichen, in diesem Programm ist die Abbruchbedinung die Stringausgabe in de rersten
Zeile. Wir erhalten folgenden Ablauf:
a
1
2
3
2
3
3
3
1
1
b
c
1
2
1
2
1
2
2
1
3
3
2
3
1
2
3
Wobei 1 der kleinste Turm ist, 2 der mittlere und drei der größte, d.h die Zahlen dürfen nur
von oben nach unten in der richtigen Reihenfolge auf einem Turm a, b oder c stehen.
Das selbe Program in Forth
: PRINTMOVE
.“ move“
..
„-->“
.
CR;
: TOWERMOVE
DUP 0 >
IF
( Unterprogramname)
( „move“ wird ausgegeben)
( Die beiden obersten stackelemente werden entfernt und
ausgegeben)
( Der Pfeil wird als String ausgegeben.)
( TOS wird ausgegeben)
( Ausgabe eines newline und Unterprogamm-Ende)
(Wir kopieren TOS und prüfen, ob es geößer null ist)
(reagiert auf den durch den Vergleich so eben entstandenen
boolschen Wert)
1(dec TOS)
2OVER 2OVER
(die letzten 4 Elemente kopieren und auf den Stack legen)
>r >r >r >r
( die Kopien scheiben wir dann auf den return stack)
1 ROLL 2 ROLL 3 ROLL 3 ROLL (Stack wird manipuliert)
RECURSE
2r@
( Elemente aus dem Return Stack zurückholen)
SWAP PRINTMOVE
( Vertausche und gib aus )
Seite 13 von 68
2DROP 2 DROP
2r> 2r>
SWAP 3 ROLL
SWAP RECURSE
( 4 Stack Elemente verwerfen)
( 4 zurück vom return stack)
(and so an ...)
THEN;
: HANOI (n -- )
3 1 2 3 ROLL TOWERMOVE
(Stack wird vorbereitet, und die Funktion wird
aufgerufen)
2Drop 2Drop;
(Stack wird aufgeräumt)
3 HANOI (Aufruf Turmhöhe 3)
Mit gutem Willen bin ich an dieses Programm ran gegangen um einen guten Überblick über
die Funktion dieses Programms zu schaffen. Beim Versuch Zahlen auf den Returnstack zu
schreiben ist allerdings mein PFORTH Scheiß abgestürzt und ich dachte mir: „Scheiß doch
auf PFORTH!!!“. Also nächstes Thema:
Postscript
Entstehung und Verwendung (könnt ihr eigentlich gleich überspringen)
Postscript ist eine Bildbeschrebungssprache der Firma Adobe. Das Ziel ist es eine einheitliche
Darstellung von seiten auf Bildschrim, Drucker und anderen Peripheriegeräten zu erhalten.
1984
1985
1990
1991
1993
1999
Erste Spezifikation zu Postscript
Erster Einsatz im Apple LaserWriter
Type 1 Font- Format (heute gibt es melr als 50.000 Fonts die verfügbar sind
Postscript Level 2
Adobe Acrobat
Level3






Postscript enthällt vollständgie Ausgabe- Geräteunabhängige Beschreibungen
Vollständig im Ascii Format => Betriebssystemunabhängig
Stack- Maschine basiertes Desgin wie bei scheiß Forth
Interpretierte Ausführung (Raster- Image- Prozessoren RIP)
Postscript Drucker besitzen spezielle Hardware zur Sprachausführung, oder
Hardware- RIP Lösungen zur Interpretation
Ghostscript: GNU_ Postscript zur On-Screen Visualisierung
Vektor Graphikausgabe
Grundlage: Vektormodell (Im Gegensatz zur Rastergraphik)
In Postscript stellt der Pfad (path) das wichtigste Gestaltungselement dar. Ein Pfad besteht
zunächst aus einer unsichtbaren Folge von graphischen Objekten, welche somit den Umriss
eines komplexeren Objektes beschreiben.
Kleines Beispielprogramm:
Seite 14 von 68
newpath
100 200 moveto
72 0 rlineto
% initialisiert einen neuen Pfad
% Setzen des Cursors an Koordinate [x] [y]
% Zeichnen einer zum Cursor relativen Linie
% und versetzen des Cursors den man net sieht.
% wir gehen jetzt praktisch nach oben mit cursor und linie
% setzen des Pfades (hier standardlinientyp) auf das Blatt
% bzw. screen
0 72 rlineto
stroke
Einige wichtige graphische Operatoren
Argumente
xy
xy
xy
xyrab
xyrab
Befehl
newpath
currentpoint
lineto
rlineto
rmoveto
arc
arcn
closepath
stroke
fill
setlinewidth
showpage
Erklärung
Startet einen neuen Path
Aktueller Punkt
Gerades Linienelement absolut
relativeslineto (also vom cursor aus)
relatives moveto
Kreisbogensegment (entgegen Uhrzeigersin)
ratet mal..... selbe wie grad nur um UZS
schließt den Pfad zurück zum ersten Punkt
Pfad beenden
mei mei, man muss ja nicht alles erklären
übersetzt einfach
eigentlich auch klar aber trotzdem:
Aktuelle Seite wird ausgegeben und gereseted,
das ist der eignetliche ausgabe operator
Vektorfonds
Was sind Vektorfonds?
Keine Ahnung, auf jeden Fall sind sie im FontDictionary abgelegt und es gibt über 50.000
Fonds, wie wir sehen drehen wir uns im Kreis in unseren Informationen, aber zurück zur
Sache:
In einem kleinen Beispielprogramm wird klar, wie man fonts in Postscript verwendet:
100 200 moveto
/Helvectica findfont
24 scalefont
setfont
(Hallo Welt) show
% wir setzen wieder unseren Cursor auf koordinaten wie
% wir lustig sind
% finde diesen font
% skaliere diesen
% setzen
% Wir begrüßen die Welt mit einem netten hallo
Operatoren und Arihtmetik
Nun auf die Stackoperatoren gehe ich hier mal nicht näher ein, sie sind im wesentlichen
ähnlich wie die von forth: pop exch dup usw...
Auf die Arithmetik geh ich nur kurz drauf ein. Statt x y + für (x + y) schreibt man in
Postscript x y add. Analog dazu x y sub und x y mul...
zu den boolschen Operatoen gibt es auch nicht viel zu sagen, statt x y > wie bei Forth schreibt
man x y gt bzw x y lt für kleiner.
Seite 15 von 68
Die Stacks von Postscript: Multi- Stack- Sprache
Postscript eine Multi- Stack- Sprache mit 4 Stacks.
1.) Operanden Stack
Enthällt alle Objekte, die Operanden oder Ergebnisse von Operatoren sind.
2.) Ausführungsstack (nicht programmierbar)
Enthält alle ausführbaren Objekte, d.h. im wesentlichen Prozeduren und Dateien, die
sich noch in der Ausführung befinden. Aufgrund des Ausführung eines anderen
Objektes unterbrochene Objekte können so ggf. fortgesetzt werden. Der Satz steht
genauso auf den Folien, der verwirrt mich allerdings irgendwie. Ich denk gemeint ist:
Wenn die Ausführung eines Objektes auf Grund dessen unterbrochen wurde, dass ein
anderes Objekt ausgeführt wurde, kann das unterbrochene Objekt hier fortgesetzt
werden. Ich hoffe mein Satz war besser 
3.) Dictionary Stack (vgl- forth)
Jeder Postscript Befehl wird vor Ausführung im Sict nach LIFO Proinzip gesucht.
Explizite Befehlsverdeckung (auch der Systembefehele) ist somit möglich!
Strukturierung nach: systemdict, userdict, errordict, Font Dictionary.
4.) Graphik- Status- Stack
Aktueller Kontext für die Ausgabe graphischer Elemente
Variablen in Postscript
Es ist möglich getype Variablen zu definieren: bool, integer, real, string, array
/Nummer 5 def
/Gruesse (Hallo Welt) def
% Nummer = 5
% Gruesse = Hallo Welt
Hey sorry bin irgendwie müde und mein Arsch tut von diesem scheiß Stuhl weh, ich
überspring die Erklärung da es ja sowieso selbstverständlich ist...
Prozeduren in Postscript
Prozeduren werden in Postscript ähnlich wie Variablen deklariert und verwendet. Syntax:
/<name> {block} def.
Die Parameterübergabe erfolgt dabei über den stack. Sehen wir uns dazu mal folgendes
Beispiel zur #Berechnung der Hypotenuse an:
/hypotenuse {
/b exch def
% stack: ... x /b exch def  .../b x def => b = x
/a exch def
% das gleiche wie grad nur mit a
a dup mult b dup mult add sqrt
% Formel für Hypotenuse in Postfix
% Darstellung
} def
Kontrollstrukturen in Postscript
Verzweigungen – IF IFELSE
Syntax:
Einfache Verzweigung: < boole- expr> { <true- body> } if
Seite 16 von 68
Verzweigung: < boole- expr> {true- body > } [ <false-body> } ifelse
Auch hierfür ein kleines Beispiel:
/signum {
dup
0>
{1}
{ dup 0 = { 0 } {1 neg} ifelse }
ifelse
} def
Wie wir sehen haben wir hier eine Funktion welche tested, ob die Zahl auf dem TOS negativ,
null oder positiv ist. Sie verdoppelt dazu zunächst die Zahl auf dem TOS. Mit dieser Kopie
können wir nun einen boolschenVergleich durchführen ( 0 >). Das heißt, es wird getestet, ob
die gerade eben verdoppelte Zahl größer null ist. Danach kommen die beiden Blöcke
{1}
{ dup 0 = { 0 } {1 neg} ifelse }
Das heißt, wenn die Bedinung war gewesen ist, wird der erste Block ausgeführt, und wenn
dies nicht der Fall war, der zweite. Also geben wir, für den Fall dass TOS > 0 war eine eins
zurück. Für den Fall dass TOS nicht größer null war muss mit einem weiteren boolschen
Vergleich
{ dup 0 = { 0 } {1 neg} ifelse } erst noch getestet werden, ob die Hzahl auf dem TOS = null
gewesen ist. Ist dies der Fall geben wir den ersten inneren Block { 0 } zurück, ist dies nicht
der Fall bleibt nur noch, dass TOS kleiner null gewesen ist, das heißt wir geben den zweiten
inneren Block zurück, indem die 1 negiert und zurückgegeben wird.
Rekursion in Postscript
Wie funktioniert Rekursion in Postscript?
In Postscript ist dies durch einfachen Aufruf der Funktion selbst im Funktionsrumpf möglich.
Wir sehen uns dazu das einfache Beispiel der Fakultätsfunktion an:
/factorial { 1 dict begin
/n exch def
n 1 eq
{ 1}
{ n n 1 sub factorial mul }
ifelse
end } def
% Wenn n = 1 ist Abbruchbed.
% wenn n nicht eins ist, Rekursion
Problem: Die Variable n wird in das Dictionary geschrieben und bleibt dort innerhalb des
begin… end BEreiches gültig. Bei großen Argumenten kann es deshalb zu einem Überlauf
des Dictionary- Stacks kommen. Auch nichtlinear rekursive Funktionen können in Postscriopt
geschrieben werden. Sehen wir uns dazu doch mal die Implementierung von den Türmen von
Hanoi in dieser Sprache an:
Seite 17 von 68
/bewege {
4 dict begin % bereite im dictionary Platz für 4 Variablen vor
/n exch def % n = was auf dem stack for dem n stand
/c exch def
% c = was vor dem n stand
/b exch def % b = was vor dem b stand
/a exch def
% a = was vor dem b stand
n 1 eq {
% Block 1
currentpoint
(Lege die oberste Scheibe von Turm ) show
a show
( auf Turm ) show
c show
(.) show
moveto
0 -12 rmoveto
}
{
}
end
% Block 2
a c b n 1 sub bewege
a b c 1 bewege
b a c n 1 sub bewege
ifelse
} def
Im Block rot markiert ist der Vergleich. Ist n = 1 wird wieder Block1 ausgeführt, sonst Block
2. Im Block eins ist die Ausgabe eines Strings programmiert. Diese stellt die
Abbruchbedinung der Rekursion dar. Werfen wir noch mal einen Blick auf die
Abbruchbedingung in Prolog:
bewege(A, _, C,1) :- writef(‚Lege die oberste Scheibe von Turm %w auf Turm %w. \n’,
[A,C]).
Wir erkennen das diese eigentlich genau das gleiche macht. Genauso analog zum
Prologprogramm ist auch die eigentliche Rekursion in Block2:
bewege (A,B,C,N) :- M is N-1,
bewege (A,C,B,M),
bewege( A,B,C,1),
bewege(B,A,C,M).
vergleicht dies mit dem Inhalt aus Block2 und wir erkennen sofort, dass beide Programme
den selben Algorithmus implementieren.
Schleifen in Postscript
Einfache Zählschleife, Syntax: <lower> >step> <upper> { >body> } for;
Der Programmrumpf wird hierbei mehrmals ausgeführt. Bei jedem Durchlauf wird die untere
Grenze <lower> um <step> erhöht, bis schließlich <upper> erreicht wird. Schauen wir uns
dafür doch mal ein kleines Programm an, welches ein reguläres n- Eck (Radius = 1) am
Bildschirm ausgibt:
Seite 18 von 68
/drawpolygon { 4 dict begin
/N exch def
/A 360 N div def
1 0 moveto
1 1 N {A cos A sin lineto
/A A 360 N div add def
} for
closepath
end } def
Fraktale
Was ist ein Fraktal? Ein Fraktal ist eine geometrische Figur, die durch einen mathematischen
Algorithmus definiert ist, der zu einer Form führt, welche die Eigenschaft der
Selbstähnlichkeit aufweist.
Darunter versteht man die Eigenschaft, dass bei der
Vergrößerung der Figur immer neue Verzweigungen und Muster erscheinen, wobei sich die
Grundformen im Kleinen wiederholen.
Ein sehr berühmtes Fraktal, ist der Barnley Farn:
Der Farn wird mit einem verblüffend einfachem Algorithmus als eine Folge von Punkten
erzeugt, wobei der nächste Punkt vom jeweiligen Vorgängen gemäß:
x’ = a*x + b*y +e
y’ = c*x + d*y +f
abhängt.
Diese Art Gleichungen nenn man in der Mathematik liebevoll „affine Transformation“.
Barnsley hat für das Farn folgende 4 Konstanensätze definiert. Bei jedem Schritt wird einer
davon zufällig ausgewählt:
A
B
C
D
E
F
0
0
0
0.16
0
0
0.85
-0.04
-0.04
0.85
0
1.6
0.2
0.23
0.26
0.22
0
1.6
-0.15
0.26
0.28
0.24
0
0.44
Wollen wir uns nun die Implementierung dieses Fraktals in Postscript aussieht:
%!PS-Adope-2.0
4.25 72 mul
1.5 72 mul translate % translate: Zeichen- Ursprungs- Verschiebung
0.8 72 mul dup scale % scale: Skalierung des Koordinatensystems
1 setlinecap
% Form der Linienenden
0.005 setlinewidth % Liniendicke
00
% x –y Startwerte belegen
150000 {
% 150000 Iterationen
rand 100 mod
% Zufallswert zwischen 1 und 100 bestimmen
dup 1 lt
% und Koeffizienten auswählen
{ pop [0.00 0.00 0.00 0.16 0.00 0.00] }
{ dup 86 lt { pop [0.85 -0.04 0.04 0.85 0.00 1.60]}
{ 93 lt {[0.20 0.23 -0.26 0.22 0.00 1.60] }
Seite 19 von 68
{[-0.15 0.26 0.28 0.24 0.00 0.44]}ifelse } ifelse} ifelse
transform 2 copy moveto
% Linear- Transformieren and so an ...
0.01 0.001 rlineto
stroke
} repeat
Und angeblich soll das dabei rauskommen:
Nun ok, das sieht ja sowas von geil aus, das sollte man
mal ausprobieren.. Probier...
Wau... es funzt tatsächlich, probiert es aus... Wie cool!
Wiederholung: Beziehung zwischen Chomsky
Grammatik und Automaten.
Wiederholen wir zunächst die unterschiedlichen
Chomsky- Typen von Grammatiken, die wir aus dem
ersten Semester kennen:
Typ 0:
Die
Regeln
unterliegen
keiner
Einschränkung aber es gibt welche, wie z.B. für das
Deutsche (sog. Phrasenstrukturgrammatiken).
Typ 1: Kontextsensitive Grammatiken Regeln der Form
wie oben dargestellt. Außerdem: die Länge der
abgeleiteten Wörter |w| nimmt nicht ab von
Ableitungsschritt zu Ableitungsschritt. Für alle Regeln
w1  w2 aus P gilt: |w1| ≤ |w2|
Typ 2:
Kontextfrei. Für alle Regeln w1  w2 aus P gilt, dass w1 eine Variable ist, d.h.
w1 є V.
Typ 3:
Regulär. Zusätzlich zu Typ2 sind die rechten Seiten der Regeln w1  w2
entweder Terminalsymbole oder ein Terminalsymbol gefolgt von einer
Variablen , also w2 є { A  A V}
Chomsky 3 < DEA (deterministischer endlicher Autaomat) / NEA
Chomsky 2 < Kellerautomat
Chomsky 1 < ?
Chomsky 0 > ?
Wie wir wissen, oder zumindest mal wussten und uns in Erinnerung holen sollten sind
Chomsky 1 und Chomsky 0 sprachen so nicht zu modellieren. Da aber alles geht, geht auch
das . Nur wie? Die Turing Maschine verspricht Hilfe...
Zunächst aber noch ein Beispiel für einen Kellerautomaten.
Wir wollen einen Automaten der folgende Sprache akzeptiert:L = {a1...an$an...a1 | ai  {a,b}}.
Ein Wort dieser Sprache wäre also zum Beispiel: abba$abba
Seite 20 von 68
Lösungsidee: Immer wenn wir ein a lesen, pushen wir ein A in den Keller. Analog pushen wir
ein B in den Keller, wenn wir ein B lesen bis wir beim $ angekommen sind. Hier poppen wir
dann die A und die B, sofern wir jeweils auch wieder a bzw. b gelesen haben. Mit dieser
Methode kommen wir nur dann beim # im Keller an, wenn das Wort Teil der Sprache L
gewesen ist. Also:
a,#/ A#
b,#/ B#
a,A/AA
a,B/AB
b,A/ BA
b,B/ BB
Dabei heißt a,#/ A#: auf dem
Eingabeband wird a gelesen,
und auf dem Keller befindet
sich ein #. Da # aus dem Keller
beim lesen entfernt wird,
müssen
wir
es
wieder
hinzufügen, deshalb A#.
a,A/
b,B/
,#/#
Z0
$,#/#
$,A/A
$,B,B
Z1
Turing Maschine
Nun, um das im letzten Abschitt beschriebene Problem zu lösen hatte Turing (1912 – 1984)
eine spitzenmäßige Idee: Der clevere Kerl erfannd die Turing Maschine. Er verwendet dabei
einen sequentiellen Speicher in Form eines Speicherbundes, auf das sowohl lesend, als auch
schreibend zugegriffen werden kann. Zur Verdeutlichung schauen wir uns folgendes Abbild
der Turingmaschine an:
e
i
n
g
a
b
e
 s
L R
p
e
i
c
H
e
r
Endliches
steuerlemement
hier Rot markiert ist der Schreib. Lesekopf der sich nach links und rechts bewegen kann.,
dabei schreiben und lesen kann.
Die Beschreibung des Arbeitsablaufes erfolgt durch eine Übergangsfunktion:
 (z,a) = ( z`, b,x)
Wir interpretieren das folgenermaßen:
Zu Beginn befindet sich die TM im Zustand z und liest a.
Im Berechnungsschritt geht die SE (Steuereinheit) von z nach z`, es wird „b“ geschrieben und
der Kopf bewegt sich nach x  { L (links) ,R(rechts) ,N (nichts) }
Graphisch:
a /b, x
z
z’
Seite 21 von 68
Beispiel:
Die Sprache L = { anbncn >= 0 }
Bekannt: L ist nicht vom Typ 2, kann also nicht von einem KA akzeptiert werden.
Um eine Turing Maschine für L zu konstruieren benötigt man das Konzept der TM in voller
Allgemeinheit. Insbesondere genügt der sog. LBA – Automat (Linear Bounded Automat, d.h.
der Schreibkopf bewegt sich nur auf dem Eingabereich).
Lösungsidee:
 Ausgangszustand: SK (Steuerkopf) steht auf dem ersten Eingabezeichen
 Markierung des ersten a durch a`
 Verschiebung des SK nach rechts, bis b gefunden wird; Markierung von b durch b`.
 Verschiebung des SK nach rechts bis das erste c gefunden wird; Markierung von c
durch c`.
 Verschiebung nach links, bis a` gefunden wird.

Wenn kein a mehr gefunden wird, muss sichergestellt werden, dass alle b und c
markiert sind  fertig.
Kurz drüber nachgedacht macht klar, dass des funktionieren muss. Graphisch sieht dieser
Automat für die Sprache L = { anbncn >= 0 } also folgendermaßen aus:
Graphisch:
a/ a`, R
1
b/ b`, R
2
a/ a, R
b’/ b’, R
5
/ ,R
4
b / b, R
c’ / c’, R
a`/ a`,R
b’/b’,R
c/ c`, L
3
b/ b, L
b`/b’, L
c’/c’, L
a/ a, L
6
b’/b’,R
c`/ c`,R
Machen wir schnell ein Beispiel durch. Wir wissen aabbcc ist Tel der Sprache L. Der
Schreibkopf befindet sich also auf dem erssten Element des Wortes aabbcc. Wir befinden uns
Seite 22 von 68
im zustand 1, ersetzen also a durch a’ und bewegen den Schreibkopf eins nach rechts.
Außerdem wechselt der Zustand von 1 nach 2. Unser Wort sieht also fogemdermaßen aus:
a’abbcc wobei sich der Schreibkopf auf dem roten a befindet. Im Zustand 2 finden wir
hierfür die Anweisung a/ a, R d.h. wir ersetzen a durch a, tun also nichts und bewegen den
Schreibkopf weiter nach rechts und bleiben im Zustand 2. Dies wird solange so weiter gehen,
bis entweder ein b oder b’ gefunden wird. Wird ein b’ gefunden sollen diese auf die gleiche
Art und Weise übersprungen wie schon die a übersprungen wurden. Schließlich wird ein b
gefunden was den autopmaten in den dritten Zustand wechselt und dieses b durch ein b’
ersetz damit markiert. Mit c wird im Zustand 2 ganz analog verfahren. Im Zustand 4 bewegen
wir nun den schreibkopf soweit nach links zurück, bis ein a’ gefunden wird. Im Zustand 1
wird nun das zweite a durch a’ markiert usw.
Wie wir an der Abbildung des Automaten sehen kann wird erst dann in zustand 5 gewechselt,
bis kein a, sondern ein b’ gefunden wird, d.h. alle vorhandenen a wurden durch a’ bereits
ersetzt und das wiederum bedeutet, dass auch alle b und c markiert sind, sofern das Wort Teil
der Sprache L ist. Deshalb testen wir in Zustand 5 noch, ob tatsächloch alle b und c ersetzt
wurden und nur dann, wenn wir nach dem Wort die Leere Speicherzelle, die durch ein
Viereck gekennzeichnet wird, erreichen, war das wort tatsächlichn Teil von L. Fertig.
Satz (ohne Bew.)
1) Menge der Typ – 1 Sprachen wird genau durch die Menge der LBA- Automaten
beschrieben
2) Menge der Typ- 0 Sprachen werden durch die Menge der TM beschrieben.
Unterschied zwischen LBA und TM: LBA bewegt sich nur auf dem Eingabeband, TM auch
darüber hinaus.
Definition der Turingmaschine
Die Turingmaschine TM ist ein 7-Tupel: M = { z, A, Γ, , z0, , E }
z:
A:
Γ:
:
endliche Zustandsmänge
Eingabealphabet
Arbeitsalphabet: Γ  A
Z x Γ  Z x Γ x {L,R,N} (determ. TM)
Z x Γ  P (Z x Γ x {L,R,N }, wobei P als „ Platzmenge“ bezeichnet
wird (nicht determ. Übergangsfunktion).
z0  z: Anfangszustand
  Γ \ A: Speicherbegrenzungszeichen
E c Z : Menge der Endzustände
Definition: (Konfiguration einer TM)
Eine Konfiguration einer TM ist ein Wort t  Γ* z Γ*.
Erläuterungen: Die Konfiguration einer TM ist also eine Momentaufnahme einer TM.
Sei R =  z  eine Konfiguration. Dann ist  β das Speicherband. Der Lesekopf befindet sich
genau über dem ersten Zeichen von β.
Seite 23 von 68
Ableitung mit Hilfe von TM
Def: Auf der Menge der Konfigurationen einer TM sei folgende 2 – stellige
Relation gegeben.
m >= 0 , n <= 1
a1...am z b1...bn

a
a1...am z ' cb2 ...bn ,  ( z, b1 )  ( z ', c, N )

a1...am cz ' b2 ...bn ,  ( z, b1 )  ( z ', c, R)

a1...an 1 z ' am cb2 ...bn ,  ( z, b1 )  ( z ', c, L)
Sonderfälle :

n = 1 und maschine läuft nach rechts.
a1...amzb1
a1...am c z’  falls  ( z, b1 )  ( z' , c, R)
 m = 0 und Maschine bewegt sich nach links
 z b1 ... bn
z’ c b2... bn falls  ( z, b1 )  ( z' , c, b)
Randbemerkung: in der Vorlesung wurde das Relationszeichen
ohne Pfeil
benutzt.
diese Ableitungen sehen kompliziert aus, sind es aber natürlich nicht. Um das primitive in der
Sache offensichtlicher zu machen hier nun eine Beschreibung der ersten genannten
Ableitung, die oben dargestellt ist. Wir haben also :
a1...am z b1...bn
a
a1...am z ' cb2 ...bn ,  ( z , b1 )  ( z ', c, N )

 ( z, b1 )  ( z ', c, N ) das bedeutet also, wir gehen vom Zustand z in den Zustand z’ zbd
ersetzen dabei b1 durch c. N bedeutet dass der Schreibkopf Nichts tut also weder nach rechts
noch nach links geht. Nichts anderes steht rechts von dem Relationspfeil. In der zweiten oben
genannten Ableitung haben wir die Übergangsfunktion  ( z, b1 )  ( z ', c, R) . Der einzige
unterschied ist also, dass wir den Schreibkopf eins nach rechts verschieben. Deshalb befindet
sich das z’ eins rechts vom c. Ganz einfach also.
Um nun auch die Motivation für das ganze ein wenig zu stärken zeigen wir eine einfache
Verwendung füt die TM.
Beispiel: TM zur Inkrementierung von Binärzahlen
Wir überlegen uns zunächst was beim Inkrementieren von Binärzahlen passiert. Wir haben
also z.B. b = 1001. b+1 wäre dann 1010. Hätten wir aber 1000 sehe b inkrementiert so aus:
1001. Ist also das erste Bit eine 0, so wird sie einfach durch eine 1 ersetzt. Ist dagegen diese
eine 1, so wird die 1 mit einer 0 ersetzt und mit dem zweiten Bit fortgefahren und der gleiche
Vergleich ausgeführt. Wie könnte man das in einer TM implementieren?
Nun logisch ist, dass wir den Schreibkopf zunächst ans erste Bit bewegen müssen. Dann kann
der Vergleich mittels einem Automaten ganz einfach durchgeführt werden. Unsere
Lösungidee sieht also folgendermaßen aus:
Seite 24 von 68
Lösungsidee: (Beispiel einer Startkonf.  Z0 101 )






Bewege Schreibkopf zum rechten Wortende und bleibe in Zustand z 0.
Wenn rechtes Ende erreicht (d.h. Kopf über ), dann Zustandsübergang Z0  Z1 und bewege Kopf
nach links.
Wenn Kopf über 0, schreibe 1 gehe nach Z2 und bewege Kopf weiter nach links
Wenn Kopf über 1 gehe nach links
Wenn Kopf über  (linkes Ende ist erreicht und noch keine Inkrementierung) schreibe 1 und gehe in
Endzustand Ze
Bewege Kopf solange nach links, bis linkes Ende erreicht wird und gehe dann in Endzustand Z e
Wir errinern uns an die Definition der TM: M = { z, A, Γ, , z0, , E }
Wir wollen nun die einzelnen Tupel- Glieder angeben. Wir haben also die Zustände : z = (Z0,
Z1, Z2, Ze). Unser Eingabealphabet sind entweder „0“ oder „1“. Unser arbeitsalphabet enthällt
zusätzlich noch . Die Übergangsfunktion ist  . Anfangszustand z0 . Das leere Wort  und
unser Endzustand ze. Damit hättten wir schon alles was wir brauchen. Also:
TM = {(Z0, Z1, Z2, Ze), {0,1}, {0,1, }, , Z0, , Ze }
Wobei (Z0,0) = (Z0,0,R)
(Z0,1) = (Z0,1,R)
(Z0, ) = (Z1,,L)
(1)
(2)
(3)
Diese 3 bewegen den Schreibkopf ganz nach
(Z1,0) = (Z2,1,L)
(Z1,1) = (Z1,0,L)
(Z1, ) = (Ze,1,N)
(4)
(5)
(6)
Wenn „0“ schreib „1“ und geh Links z1  z2
Wenn „1“ schreib „0“ geh links und bleib in z1
Wenn  dann Endzustand. Fertig.
(Z2,0) = (Z2,0,L)
(Z2,1) = (Z2,1,L)
(7)
(8)
Im Zustand z2 wurde die Binärzahl bereits
inkrementiert. Wir verschieben den Sk nur noch
(Z2, ) = (Ze, ,R)
(9)
links bis zum Wortende, der durch  markiert ist.
rechts
nach
Beispiel für Ableitung:
Z01010
1Z0010
10Z010
101Z00
Z21011
Z21011
Z21011
1010Z0
101Z10
10Z211
1Z2011
Ganz analog: Dekrementierung von Binärzahlen.
Lösungsidee: Um Binärzahlen zu dekrementieren, muss ein Suffix der Form 10000 auf die
Form 01111 gebracht werden.
Beispiel: 1100 – 1 = 1011


Die Regeln der Übergangsfunktion (1) .. (3) wie beim Inkrementieren
Statt Regeln (4) und (5) nunmehr:
(Z1,0) = (Z1,1,L)
Wir bleiben also in z1
(Z1,1) = (Z2,0,L)
Wir gehen also in z2 über.

Regel (6) tritt nicht auf, weil Binärzahlen keine „führenden Nullen“
haben
Regel (7) und (8) wie beim Inkrementieren
Statt Regel (9)
(Z2, ) = (Z3, ,R)


Seite 25 von 68
(Z3,1) = (Ze,1,N)
(Z3,0) = (Z3, ,R)
Stellen
abgeleitet
Halbierung von Unärzahlen
Zunächst schreiben wir uns 2 Hilfsprogramme für die Turingmaschine, welche das Band ganz
nach links bzw. rechts bewegt.
Links:
Die Maschine L(inks) wird mit folgender Konfiguration gestartet: #|......|start# und endet mit
end#|...|#. Die gesuchte Turingmaschine ist hierbei gegeben durch:
L = {{start, end, move}, {|}, {|}, L, start,#,end}
Wir sehen die Übergansfunktionen aus? Wir lesen also im Startzustand ein ‚#’. Das heißt wir
lassen das unverändert, indem wir auch ein ‚#’ schreiben und bewegen den Schreibkopf nach
links. Außerdem wechseln wir den Zustand zu move. Hier lesen wir ein ‚|’ und ersetzen dieses
wieder durch ‚|’ lassen es also auch unverändert. Wir ändern den Zustand nicht, das heißt wir
gelangen rekursiv soweit nach links, bis was anderes als ‚|’ gelesen wird. Wenn wir dann
wieder ein ‚#’ lesen, lassen wir dies wieder unverändert, bewegen den Schreibkopf nicht und
wechseln in den Endzustand. Fertig. Also sehen wir uns mal die Übergangsfunktionen an:
(start,#) = (move, #, L)
(move,|) = (move,|,L)
(move,#) = (end, #,N)
Der passende Automat dazu:
(|/|,L)
start
(#/#,L)
move
(#/#,N)
end
rechts:
R(echts) ist ganz analog zu L, deshalb lass ich es hier aus. Wir setzen dann im nächsten
Schritt voraus, dass wir R schon haben.
Div2
Nun soll also diese Unärzahl in Form von ||||... halbiert werden. Das heißt wir müssen es
schaffen dass nur noch halb so viele ’|’ auf dem Band sind, wie vorher.
Es gibt mehrere Möglichkeiten dies zu Verwirklichen. Wir werden folgende verwenden:
Von der EinEingabe werden iom allgemeinen 2 ’|’ Zeichen gelöscht. Links der Eingabe wird
eine neue Zeichenkette aufgebaut, die von der Eingabe durch ein # getrennt ist und das
Ergebnis enthällt; nach jedem Löschen von 2 solchen Zeichen muss der Ergebnissbereich
ums eines erweiter werden. =>
L= {[start,end,zero,one,two,stepl,append,steprr,stepr},{|}{|},D , start,#,end}
Seite 26 von 68
So sieht unser passender Automat dazu
aus. Vielleicht ist es hier angebrachtein
Beispiel durchzugehen. Nehmen wir
also an, wir wollen 4 halbieren. Unser
Eingabeband sieht also so aus
#||||start#. Leiten wir mal hier einfach
der Reihe nach die einzelnen Schritte
ab:
#||||start#  #|||zero|#  #||one|## 
#||two##  stepl#||#  append##|| 
steprr|#|| #|#||stepr#  #|#||start#...
Wir sehen bereits dass es funktioniert,
also brech ich ab 
Addition von Binärzahlen
Grundsätzlich Vorgehensweise
Da x + y = (x – 1) + (y + 1) nach x- maligem Dekrementieren des 1. Summanden und
xmaligem Inkrementieren des 2. Summanden wird der 1. Summand 0 und der 2.
Summand
ist die Lösung.
Damit Addition möglich durch Kombination der TM für Inkrement und Dekrement.
Unbedingt noch nachzutragen.... Siehe Übungsblätter...
Seite 27 von 68
Die fleißigen Bieber ( „Busy beaver Problem“)
Formulierung durch T.Rador 1965
Gegeben:
 det. TM
 Arbeitsalphabet  = {| , }
 Unbeschränktes Schreibband, zu Beginn leer, d.h. enthällt nur 
 Übergangsfunktion muss so beschaffen sein, dass Kopf sich nur nach links oder rechts
bewegt, darf nicht stehenbleiben.
 Es gibt genau ein Halteabstand, der nicht zu den Zuständen zählt.
Busy- Beaver Funktion T(M): Maximale Anzahl von Strichen, die eine TM mit n Zuständen
(ohne Haltezustand) auf das Band schreiben kann und danach hällt.
Beispiel n = 3:
 / |, R
| / |, R
 / |, L
q1
q2
| / |, R
q3
H
 / |, L
| / |, L
(q1, )  (q2,|,R)
(q1, |)  (q3,|,L)
(q2, )  (q1,|,L)
(q2, |)  (q2,|,R)
(q3, )  (q2,|,L)
(q3, |)  (H,|,R)
Ableitung:
q1
 | q2 
 | | | | | | | |
 q1 | | 
q3 | | 
 q2  | | | 
q1  | | | | 
 | q2  | | | | 
...
Seite 28 von 68



Man kann zeigen: T(3) = 6
Eine TM, die die maximale Zahl von Stirchen auf das Band schreibt, heißt „Busy
Beaver“.
Man kann zeigen, dass die Busy- Beaver- Funktion eine nicht berechenbare Funktion
ist, da nicht einmal festgestellt werden kann, ob eine TM mit n Zuständen überhaupt
anhällt (Halteproblem der Informatik.)
Man kann zeigen:
„
1
2
3
4
5
6
T(n)
1
4
6
13
>= 4098
>= 95524079
Funktionale Programmierung
Zentrale Strukturen und Algorithmen
Bisher kennen wir rekursive Funktionsaufrufe (linear, einstellig)
Datenstrukturen wie z.B.: Listen und Bäume. Ziel dieses Kapitels ist es:
1.)
2.)
3.)
und
rekursive
Vertiefung der Rekursion über Funktion
Terminierung rekursiver Funktionen
Verallgemeinerung / Vertiefung rek. Datenstrukturen: Polymorphie, Pattern
matching
Korrektheit von Implementierungen
Wichtigsten rekursiv zu formulierenden Algorithmen
4.)
5.)
1.) Vertiefung rekursiver Funktionen
Definition (informell): Wir sprechen von Rekursion, wenn auf der rechten Seite einer
Funktionsdefinition der Form let f1 = (m x) n: E im Ausdruck E die zu definierende Fkt. F1
auftritt.
m
x
n
E
Sorte
Parametersatz
Sorte der Rückgabewerte
Berechnungsausdruck
Seite 29 von 68
Allgemein: Sei
let f1 = ( m1 x1,…, mnxn)n1:E1
eine Funktionsdeklaration. Dann ist f1 rekursiv, wenn der Bezeichner f1 im Ausdruck E1
enthalten ist (also: rein syntaktisch prüfbar!)
Lineare Rekursion:
Der Bezeichner f1 tritt in E1 innerhalb einer Verzweigung einer Fallunterscheidung höchstens
ein mal auf.
Beispiel:
a) der durch den euklidischen Algorithmus definierte GGT ist linear rekursiv
b) Die Funktion lowerpart, welche die Elemente einer liste l berechnet, die kleiner als
ein best. Element el sind, ist linear rekursiv
let rec lp (el, liste) = match (el, liste) with
| (el, [])  []
| (el, a :: rest) when (a<el)  a::(lp el rest)
| (el, a:: rest)  lp el rest
;;
Nichtlineare Rekursion:
Der Bezeichner f1 tritt in E1 innerhalb eines Zweiges einer Fallunterscheidung mind. 2 mal
auf.
Beispiel:
a) Sortieralgorithmus Quicksort (Moore)
 Sortieren durch Zerlegen
o Wähle ein beliebiges Element x aus der zu sortierenden Menge M aus. (x heißt
auch „Pirot- Element“)
o Zerlege M in die Mengen
M< = { L| L<x}, M= = {c| c =x}, M > = {u| u>x}
o Sortiere M<, M=, M >
o Die sortierte Menge M`ergibt sich dann durch Konkatenation wie folgt:
o M`= M< ◦ M= ◦ M >, wenn auf M eine Ordnung existiert.
Implementierung Quicksort:
Let rec quicksort liste = match liste with
| []  []
| [e]  [e]
| (e::rest)  quicksort (lp(e,liste))
@ ep (e, liste)
@ quicksort (up(e,liste))
;;
Bemerkung: „ep“ und „up“ sind analog zu „lp“ der equalpart und upponpart
Seite 30 von 68
b) Permutation auf Sequenzen
( Erinnerung: Kryptographie, Kombinatorik, Zufallszahlen- Erzeugung)
Permutationen sind bijektive Abbildungen
: [1,…,n]  [[1,…,n]]
Gesucht:
Rechenvorschrifft, die zu einer Sequenz s die Sequenzen aller
Permutationen von s liefert. Zum Beispiel;
perm([abc]) = ([abc],[acb],[bac],[bca],[cab],[cba])
Lösungsvorschläge für Berechnung der Permutation:
1.) systemantsiches Vorgehen: „Durchschieben“ des Elements am Kopf der Liste auf
alle Listenpositionen und Permutation der aus den anderen Elementen gebildeten
Liste.
n=1
a
n=2
ab
ba
n=3
abc
acb
bac
cab
bca
cba
n=4
abcd
abdc
acbd
adbc
acdb
adcb
…
…
1
2
3
4
5
6
...
„a“ durchschieben je
„bc permutieren.
Wenn alle Permutationen des Listenrestes bekannt sind, folgt die Permutation durch
Durchschieben des Listenkopfes durch alle (Teil)Permutationen.
Sehen wir uns das Beispiel mit n = 4 genauer an. Wir sehen wir haben a. a soll
durchgeschoben werden, sobald wir alle Permutationssequenzen von bcd angewendet haben.
Das heißt wir lassen hier mal a außer acht und tun so, als hätten wir nur bcd. hier wiederholt
sich das gleiche Prinzip, so dass wir mal b außer acht lassen, um cd permutieren zu können.
Nun die Permutationen von cd sind, wie ja offensichtlich ist und auch in den ersten beiden
Zeilen in der obigen Tabelle zu sehen ist cd und dc. Wir haben also alle Permutationen von cd
in den ersten beiden Zeilen angewendet. Sprich: Wir können nun b durchschieben. Wir haben
also in der dritten Zeile cbd. Nun wenden wir die zweite Permutation von cd, also dc auf
diese Sequenz an. Somit ergibt sich für die vierte Zeile dbc. Das a bleibt bisher unverändert
vorne angehängt. Wir können nun, da von cd wieder alle Permutationen angewendet wurden
b weiterschieben. Es ergibt sich also für die fünfte Zeile cdb. Wir wenden wieder die
Permutationen von cd an und es ergibt sich somit für die sechste Zeile dcb. Nun haben wir
alle Permutationen von bcd. Das heißt wir können a eins weiter schieben und die einzelnen
Permutationen mit nun a an zweiter Stelle anwenden usw. Am Ende haben wir alle
Permutationen der Sequenz abcd erhalten.

Formulierung unter Nutzung von Hilfsfkt. wie folgt:
a) Funktion „StickOn“: ’a x list x list  list x list
Seite 31 von 68
Platziert ein Element x an den Anfang jedes Elements jeder Teilliste innerhalb
einer übergebenen Gesamtliste.
let rec stickOn x l = match l with
| []  []
| (h :: t)  (x :: h) :: stickOn x t;;
Aufruf: stickOn 3 [[1;4;5];[4;1;5];[4;5;1]];;
b) Funktion putInAll ’a x list  list x list
let rec putInAll x l = match l with
| []  [[x]]
| (h::t)  (x::l) :: stickOn h (putInAll x t);;
c) Funktion permuteAux: ’a *list*list  list * list die unter Nutzung von putInAll ein
Element x durch die Liste von Teilpermutationen schiebt und daraus die
Ergebnisliste erzeugt.
let rec permuteAux x l = match l with
| []  []
| (h::t)  putInAll x h @ permuteAux x t;;
let rec permute l = match l with
| [] -> [[]]
| (h :: t) -> permuteAux h (permute l);;
2. Möglichkeit: Lösung unter Verwendung einer nichtlinearen rekursiven Funktion mit
Einbettung
Lösungsidee:
- Sei t eine Sequenz, die bereits ein Präfix der Permutation enthällt.
- Die Sequenz lhinters besteht aus allen Zeichen, die noch nicht in der Teilpermutation
enthalten sind.
- Der erste rekursive Aufruf berechnet also alle Permutationen, die das Präfix t @ [ x ]
haben.
- Der zweite rekursive Aufruf berechnet alle Permutationen, die dieses Präfix nicht
enthalten (also logischerweise nur das Präfix t)
Implementierung:
let rec permEmb (t,h,s) = match (t,h,s) with
| (t,h[])  [ ]
| (t,h,[x]) [t @ [x]]
| (t,l,x::rest)  permEmb(t@[x], [ ], l @ rest)
@ permEmb(t, l@[x],rest);;
Aufruf mit
let permutation (s) = permEmb([ ],[ ],s) ;;
Seite 32 von 68
Verschränkte Rekursion:
Definition: Bei einem System von rekursiven Funktionsdeklarationen f1…fj, bei dem die
Bezeichner f1… fj in E1…Ej auftreten, spricht man (für j >= 2) von verschränkter Rekursion
(führt zu zyklischem Aufruf)
Beispiel:
let rec qisodd n = match n with
| 0  false
| n when (n mod 2 = 0)  gisodd (n/2)
| n  qiseven (n/2)
and rec qiseven n = match n with
| 0  true
| n when ( n mod 2 = 0)  qiseven(n/2)
| n  qisodd(n/2);;
Warum and? Weil dies eine verschränkte Rekursion ist, und die Funktion qisodd qiseven
sonst noch nicht kennen würde. Achtung, man lässt in der ersten Funktion dann auch die
Strickpunkte weg.
Geschachtelte Rekursion
Definition: Treten bei einer rekursiven Funktion in den aktuellen Parameterausdrücken des
rekursiven Aufrufs weitere rekursive Aufrufe auf, so spricht man von geschachtelter
Rekursion ( rested recursion).
Bekanntest Beispiel: Ackermannfunktion (eingeführt von Hilburst und Ackermann in der
Berechenbarkeitstheorie).
let rec ackermann (m,n) = match (m,n) with
| (0,n)  n + 1
| (m,0)  ackermann (m - 1, 1)
| (m,n)  ackermann (m - 1, ackermann (m,n-1));;
Funktionswerte der Ackerman- Funktion wachsen mit steigenden n außerdordentlich schnell
an. Ackermannfunktion ist ein Beispiel für eine sogenannte  - rekursive Funktion (  4.
Semester).
Wesentliches Kennzeichen:
Rekursionsschleife lässt sich nicht abschätzen, sie sind nicht durch äquivalente
Schleifenumformungen berechenbar.
Ein Beispielsaufruf bringt das benötigte Gefühl in die Sache. Im folgenden Beispiel ist fett
markiert, was als nächstes geändert wird und unterstrichen, was zuletzt geändert wurde.
am (2,2)  am (1, am (2,1))  am (1, am (1,am (2,0)))  am (1, am (1, am (1,1))) 
am (1, am (1,am (0, am (1,0)))  am (1, am (1,am (0,am (0,1)))  am (1, am (1, am (0, 2)))

am (1, am (1, 3))  am (1, am (0, am (1,2)))  am (1, am(0, am(0, am (1,1)))  am (1,
am(0, am(0, am (0, am (1,0)))))  am (1, am(0, am(0, am (0, am (0,1)))))  am (1, am(0,
Seite 33 von 68
am(0, am (0, 2))))  am (1, am(0, am(0, 3)))  am (1, am(0, 4))  am (1, 5)  am (0,
am(1,4)) 
am (0, am (0, am (1,3)))  am (0, am (0, am (0,am (1,2)))  am (0, am (0, am (0, am (0, am
(1,1)))))  am (0, am (0, am (0, am (0, am (0, am (1,0))))))  am (0, am (0, am (0, am (0,
am (0, am (0,1))))))  am (0, am (0, am (0, am (0, am (0, 2)))))  am (0, am (0, am (0, am
(0, 3))))
 am (0, am (0, am (0, 4)))  am (0, am (0, 5))  am (0, 6)  7
Bemerkung zum Wachstum:
Sei Bn: IN  IN spezifiziert durch Bn(m) = ackermann (m,n), dann lässt sich durch
Induktion zeigen (Hausaufgabe!)
B0(m) = m +1
additive
B1(m) = m +2
B2(m) = 2*m +3
m-fache additive
Hier trifft unser obiges Beispiel zu: B2(2) = 2*2 +3 = 7
B3(m) = 2**(m+3) -3 m-fache Multiplikation
B4(m) = ?
d.h. bereits B4(m) kann nicht mehr durch elementare Fkt. Dargestellt werden. Im
wesentlichen entsteht damit Bn+1(m) durch m- fache Iteration der Funktion Bn.
Letzter Errinerungspunkt: Repetitiver Rekursion (Fall- Rekursion).
Erscheint in einer linear rekursiven Funktionsdeklaration in einem Zweig einer
Fallunterscheidung der rekursive Aufruf als letzte („äußerste“) Aktion, so heißt die
Rekursion repetitiv.
Beispiel: Eingebettete Fakultätsfunktion
let rec facEmb(m,n) = match (m,n) with
| (m,1) m
| (m,n)  facEmb(m*n, n-1);;
Bei repetitiver Aufrufstruktur erfolgt Auswertung besonders effizient.
Bevor mit Auswertung eines rekursiven Aufruf fortgefahren wird, sind alle Auswertungen
des davorliegenden Ausdrucks ausgeschlossen. Parameter müssen also nicht auf einem Stack
abgelegt werden, repetitve rekursive Funktionen lassen sich also sehr einfach in imperative
Prozeduren (Schleifen) transformieren.
Terminierung funktionaler Programme
Obwohl die Terminierung von rekursiven Rechenvorschirfften für den Menschen oft
offensichtlich ist, kann bei vielen anderen rekusiven Funktionen oft nicht direkt abgelesen
werden, ob die jeweilige Funktion nun terminiert, oder nicht! Im Beispiel der vorher
erwähnten Ackermannfunktion ist dies z.B. schon bedeutend schwieriger. => Wir bentöigen
einen festen Formalismus. Dabei betrachten wir Rechenvorschrifften der funktionalität
fct f = (m x)n :E
Seite 34 von 68
Sei M die Trägermenge der Sorte m und M- = M \{  }.
Hier verstanden als Ergebniss einer nicht terminierenden Funktion
Das bedeutet M- ist diejenige Trägermenge, welche nur Funktionen enthällt, die auch
tatsächlich terminieren.
Dann terminiert die Funktion f xM, wenn xM: f(x) ≠ 
Um die Terminierungseigenschaft einer Fkt. zu zeigen, verwendet man eine sog.
„Abstiegsfunktion“
h: M-  IN0;
h gibt eine Abschätzung für die Anzahl rekursiver Aufrufe bei linearer Rekursion bzw. der
Höhe des Aufrufbaums bei nichtlinearer Rekursion.
Zum Beweis der Terminierung muss folgendes Prädikat gezeigt werden:
P [k  IN 0 , x  M  , h( x)  k  f ( x) ]
Es muss also für alle k gezeigt werden, dass P gilt, bzw. Berechnung der Funktion f umfasst
max. k Schritte für Argument x.
Nachweis durch Induktion:
 Für den IA (k=0) ist zu zeigen: x  M  , wenn h(x) = 0 =>
f ( x)  , d.h. f(x) terminiert.
 IS:
# Ind.annahme: P gilt für alle k’  k
# z.z ist dann, dass aus (h( x)  k  1) die Gültigkeit von f ( x)  folgt.
1. Beispiel: Terminierung der FKT ggT(a,b)
(mit a und b größer gleich 1)
let rec ggT(a,b) = match (a,b) with
|(a,b) when a= b  a
|(a,b) when a<b  ggT(a,b-a)
|(a,b) when a>b  ggT(a-b,b);;
1.Schritt:
Definition einer Ableitungsfunktion
h : INxIN  IN0
für a=b (Erzwinge ‘0’ für Terminierungsfall)
0
h( a, b)  
für (a>b) v (b>a)
a  b
Es geht in diesem Schritt immer darum eine Fkt. zu finden, die in Abhängigkeit der
Argumente des Fkt.aufrufes monoton fällt, und beim Terminierungsfall 0 landen wird.
Induktionsanfang:
h(a,b) = 0
=>
a = b => ggT(a,b) = a  
Induktionsschritt:
- Induktionsannahme: a, b 
Sei also :
: h(a, b)  k  ggT (a, b) 
Seite 35 von 68
0  h( a, b)  k  1
 h(a, b)  a  b  k  1
 (a  k )  (b  k ) weil a  1  b  1
Fall I
(a  b)  ggT (a, b)  ggT (a  b, b)
Für (a  b, b) ist jedoch h(a-b,b)= a  k
=> h(a-b,b)  k=>ggT(a-b,b) ≠ 
dann mit Definition der FKT ggT(a,b) terminiert auch ggT(a,b)
Fall II (a<b) analog
Prinzipielle Vorgehensweise
Sei f(x) = E die zu untersuchende Fkt.
1.Schritt:
2.Schritt:
2.Beispiel:
Zeige, dass unter der Voraussetzung h(x)=0 der Ausdruck E zu einem Ergebnis
  führt.
(Ind.Schluss) Unter der Voraussetzung h( x)  k  1 forme den Ausdruck E so
um, dass nur noch rekursive Aufrufe vorhanden sind, für die h(x)  k gilt.
Schnelle Berechnung von y = mn, m,n  IN
Naive Implementierung: y  m  m  ...  m
n 1 Multiplikation
Geschicktes Vorgehen:
Rückgriff auf bereits berechneten Teilprodukte, Zwei Fälle:
I.)
y  m  m  ...  m  m  m  ...  m = A  A für geradzahlige n
A
II.)
y  m  m  ...  m  m  m  ...  m m = A  A  m für geradzahlige n.
A
also:
A
 n  2
 m 2 


y
2
 n21 
 m 


A
für gerades n
für ungerades n
Damit fkt.:
1. Variante:
let rec expo (m,n) = match n with
| 0 -> 1
| n when (n mod 2 = 0) -> expo(m,n/2)*expo(m,n/2)
| n -> m * expo (m,(n-1)/2)*expo(m,(n-1)/2);;
Seite 36 von 68
2. Variante:
let expo2 m =
let rec aux(n)=match n with
| 0 ->1
| n -> let y = aux(n/2) in
if (n mod 2 = 0) then
y*y else m * y * y
in aux ;;
Für Terminierung :
Mögliche Abstiegsfunktion h(n) = n
Bei jedem rek. Aufruf Integer- Division
 Terminierung bei 0 gewährleistet.
Bemerkung: Häufig lässt sich die Terminierung durch strukturelle Ind. schnell zeigen.
Vorgehen:
- sei f(x) die Fkt. und x  M-;
- wähle für die Argumente der Fkt eine ’geeignete fundierte Ordnung’
- IA: zeige f(x min) ≠ wo x min die minimalen Elemente der Ordnung sind.
- Induktionsschluss: Wenn aus der Gültigkeit von f(z) ≠ für alle z = y die
Gültigkeit von f(y) ≠ folgt, dann ist f(x) ≠ für alle Elemente der Ordnung Fkt.
terminiert
Beispiel 3: Ackermann:
n  1 für m  0

A(m, n)   A(m  1,1) für m  0, n  0

 A(m  1, A(m, n  1)) für m, n  0
-
a)
b)
fundierte Ordnung: lexikographische Ordnung über IN0xIN0 also:
(0,0)<(0,1)<(0,2)< … <(1,0)<(1,1)<…
Damit Beweisführung über geschachtelte Ind.
# über m (äußere Ind)
# über n bei festem m (innere Ind))
Induktionsanfang: m= 0
A(0,n)=n+1 ≠
Induktionsschritt:
Voraussetzung äußere Induktion
A(k,n) ≠ für 0  k < m und beliebigen n
- Innere Ind. über n zum Schluss auf m
o Anfang n = 0: A(m,0) = A(m-1,1)
≠  nach Voraussetzung der äußeren Induktion
o Voraussetzung: A(m,l) ≠  für bestes m und alle l = n
o Schluss auf n:
A(m,n)= A(m-1,A(m,n-1))
A(m,n-1) terminiert nach Voraussetzung und erzeugt k.
also:
Seite 37 von 68
A(m -1,k) und dies terminiert nach äußerer Voraussetzung
Also terminiert A(m,n) für alle drei Rekursionsfälle.
Bsp 4: (Klaus Funktion):
Rechenvorschrifft, bei der bis heute nicht bekannt ist, ob sie für alle Eingaben terminiert:
 Ausgehend von a0  IN und a0 > 0 erzeuge Zahlenfolge a0,a1…,an,… in der
folgenden Weise:
Brich ab, falls an=1
Bsp. Aufruf: für a0 = 3  3,10,5,16,8,4,2,1
Es ist offen, ob bei beliebiger Eingabe a0 die Länge der Liste endlich ist
Nehmen wir hier ein weiteres Beispiel aus den Übungsblättern her:
Bsp. 5: Permutation von Sequenzen
Wir kennen bereits 2 Varianten dies zu implementieren. Wir werden nun die Terminierung
der eingebetteten Version beweisen. Zur Wiederholung noch mal die Funktion in Ocaml:
let perm (s) = permEmb ([],[],s];;
let rec permEmb (t,h,s) = match (t,h,s) with
| (t,h,[]) -> []
| (t,h,[x] -> [t @ [x]]
| (t,l, x :: rest ) -> permEmb (t @ [x],[], l @ rest)
@ permEmb ( t, l @ [x], rest );;
Aufgabe ist es nun, eine geeignete Abstiegsfunktion zu wählen und damit zu zeigen, dass die
Funktion terminiert. Der gleiche Satz wie grad eben formal ausgedrückt:
Zu zeigen ist, dass die Funktion permEmb (t , l , s )  für beliebige Tripel
(t , l , s)  M   (c * xC * xc*)  .
Es bezeichnen n1 und ns die Längen der Sequenzen l und s. Wir wählen folgende
Abstiegsfunktion:
0,
ns  1

h(t , l , s)  ns ,
nl  0

(ns  nl ) ns sonst
Auch bei diesem Beispiel ist mir die Abstiegsfunktion leider ein Rätsel. Ein Versuch von mir
selbst h(t,l,s) zu erklären (ohne Gewähr):
die erste Zeile für den Fall das ns kleinergleich 1 ist, gibt 0 aus. Dies modelliert die beiden
Terminierungsfälle in unserem OcamlProgramm in den ersten und zweiten Pattern Matching
Zweigen. Der zweite Fall trifft zu, wenn die Länge von l = 0 ist. Hierbei wird dann die Länge
von s ausgegeben. Welchen Fall das in unserem Ocaml Programm abdecken soll? Keine
Ahnung. Für mich sieht das so aus, als wären die ersten beiden schon abgedeckt. Die dritte
Zeile: ... Bei bestem Willen: Keine Ahnung.
Seite 38 von 68
Leider ist auch keinerlei Erklärung angegeben, wenn ich noch was darüber herausfinde,
werde ich das nachtragen, nehmen wir jetzt mal die Abstiegsfunktion als gegeben an. Wir
beweisen nun dass diese terminiert mittels Induktion. Zu zeigen ist das Prädikat:
P(k )  x  M  : h( x)  k  permEmb( x) 
Induktionsanfang:
Zu zeigen ist x  M  : h( x)  0  permEmb( x)  . Da die Abstietgsfunktion h, genau
dann Null ist, wenn ns  0  ns  1 und permEmb in diesem Fall per Definition terminiert, ist
der IA gezeigt.
Induktionsschluss:
Gemäß Induktionshypothese terminiert die Funktion permEmb für alle x  M-, für die h(x) 
k. Dann bedeutet dies, dass der Aufruf der Funktion permEmb terminiert für alle Tripel (t,l,s)
mit
ns
l  []  ns  1
k  h(t , l , s )  
(ns  nl ) ns l  []  ns  1
Wir betrachten nun alle x  M- für die 0 < h(x)  k+1. Es sind nun also Argumenttripel zu
bterachten, bei denen die Sequenz l oder s um ein Element größer wurde.

l  x :: l; Gemäß Funktionsdefinition hat der Aufruf permEmb (t, x::l,s) Aufrufe
mit den Argumenttripeln ( t @ [y] ,[],(x::l)@rest(s)) und (t, (x::l) @ [y], rest(s))
zur Folge. Da nun
h( t @ [y] ,[],(x::l)@rest(s))= ns + nl k
und
h( t ,(x::l)@[y],rest(s))= (ns + nl + 1) * ns –(nl + 1) k

s  x :: s; Gemäß Funktionsdefinitoon hat der Aufruf permEmb ( t, l, x :: s)
Aufrufe mit den Argumenttripeln ( t @ [x], [], l @ s) und (t,l,@[x],s) zur Folge.
Für erstes erhalten wir die Absitiegsfunktion
h(t@[x],[],l@s) = nl + ns k
und damit terminiert die Funktion permEmb für den entsprechenden Funktionsaufruf. Bei
dem Argumenttripel (t, l @ [x],s) wächst die Sequenz l um ein Element. Für derartige
Funktionsauifrufe wurde jedoch bereits oben gezeigt, dass die Aufrufe terminieren.
Aufwand von Algorithmen

Speicher – bzw. Berechnungsaufwand eines Algorithmus hängen typischer Weise
empfindlich von den Parametern ab, die den Algorithmus übergeben werden.
Beispielsweise benötigen wir zur Berechnung von fak(n) n rekursive Aufrufe, zur
Berechnung der Permutation einer Sequenz der Länge n ca. n! rekursive Aufrufe.

Um den Aufwand abschätzen
Komplexitätsklassen ein.
zu
können
führt
man
sogenannte
Seite 39 von 68
Definition:
O( f (n))  {g (n) | c  0, n0  IN
mit 0  g (n)  c  f (n)
für n  n0 }
O(f(n)) definiert eine Klasse von Funktionen. Eine Funktion g(n) ist element der
Klasse O(f(n)), wenn ab einem bestimmten n0 die Funktion c*f(n) eine andere
Schranke für g(n) bildet.
Also:
g(n) wächst höchstens so schnell wie f(n). Wichtig ist das asymptotische Verhalten für
n  unendlich
Beispiel:
n2
2
 O(n 2 )
n
 O(n 2 )
n log n  O (n 2 )
n3
 O(n 2 )
Bei der Fakultätsfunktion ist der Berechnungsaufwand proportional zu n => gehört der
Komplexitätsklasse O(n) an.
 Intuitiv erkennt man, dass der Berechnungsaufwand bei der Fakultätsfunktion
propotional zu n ist, wenn man annimmt, dass Multiplikationsaufwand konstant ist.
 Auch möglich:
Berechnungsaufwand hängt ab von Anzahl übergebener Parameter, z.B. bei
Summenberechnungen
Beispiel:
DFT, diskete Fourirtransformation
Aufgabe:
Transformation eines Zeitreienabschnittes in ein „Spektum“, welches die
einzelnen in der Zeitreihe enthaltenen Frequenzkomponenten repräsentiert.
Anwendung: Filterung, (Bild,- Audio -) Kompression, Spektralanalyse.
Seite 40 von 68
also: DFT: Abbildun IRn  n
oder: Abbildung n 
N 1
Rechenvorschrifft: F   f n e
j
2 kn
N
n
wo j²=-1
n 0
Für jeden der k  0 … N-1 Spektralwerte sind N Operationen auszuführen, für komplettes
Spektrum also N².

Die schnelle Fourirtransformation FFT macht sich die Tatsache zu Nutze, dass bei der
Summenauswertung sehr oft gleiche Resultate erneut berechnet werden (z.B. gleiche
Produkte k  n ),
und sie speichert diese ab. Damit Reduktion des Aufwandes auf N log N.
Weiteres Beispiel:
Sortieren durch Einfügen.
Beim Sortieren durch Einfügen wird aus einer zu sortierenden Quellliste ein beliebiges
Element entnommen und an der „richtigen Stelle“ in die Ergebnissliste eingefügt.
let rec insert (el, s) = match (el,s) with
| (el,[])  [el]
| (el, x::rest) when (el >= x)  el :: s
| (el, x::rest)  x :: insert (el,rest);;
let rec insertSeq (s,r) = match (s,r) with
| (s,[])  s
| (s, x::rest)  insertSeq(insert(x,s),rest);;
Quellliste, umsortiert
Seite 41 von 68
let insertSort(s) = insertSeq([],s)
Rechenaufwand:
Sei n die Länge der zu sortierenden Liste, dann wird insertSeq n mal aufgerufen. Bei
„Gleichverteilung“ wird die Funktion n/2 – mal aufgerufen. Also; n²/2 rekursive
Aufrufe, Rechenaufwand O(n²).
Vergleich mit Quicksort:
Bei zufälliger Wahl des Pivot-Elements wird die Liste bei jedem Aufruf im Mittel
halbiert. Spätestens nach log2n rekursiven Aufrufen gelagt man zur 0- bzw. 1elementiges Liste. Das Aufteilen einer Teilliste in lower, upper- equal-Part erfordert
im Mittel n/2 Schritte. Also: Bei Quicksort im Mittel Zeitaufwand O(nlogn) .
Bemerkung:
Kurioserweise kommt Quicksort auf den Aufwand O(n²), wenn die Liste schon sortiert
ist.
Häufig vorkommende Komplexitätsklassen:
N
log N
linear
logarithmisch
N log N
Fakultät
Suche in Binärbäumen,
schnelle Exp.
Quicksort (im Mittel)
FFT
Nm
polynomiell
DFT
mN
exponentiell
Pfadsuche in Graphen, z.B.
„Traveling Salesman“
Problem
Korrektheit von Quicksort
Hier nun ein kleiner Einschub. Wir wollen die Korrektheit von Quicksort beweisen. Diese
Aufgabe besteht aus folgenden drei Teilaufgaben:
a) Geben sie ein Prädikat an, das die Anforderungen an einen Sortieralgorithmus formal
spezifiziert!
b) Zeigen sie, dass die aus der Vorlesung bekannte Implementierung des QuicksortAlgorithmus partiell korrekt ist.
c) Erläutern sie informell, weshalb der mittlere Aufwand beim Sortieren gemäß
Quicksort durch O(n log n) gegeben ist.
Die erste Teilaufgabe a) sieht auf den ersten Blick komplizierter aus, als sie ist. Hier soll
lediglich angegeben werden, was der Quicksort- Algorithmus machen soll:
a) Im folgenden sei l eine Liste von natürlichen Zahlen:
Bei Eingabe von l gibt der Algorithmus Quicksort eine aufsteigend sortierte liste
Quicksort(l) zurück, die genau aus den Elementen von l besteht.
Seite 42 von 68
b) Sei {} die leere Liste; dann ist Quicksort durch die beiden folgenden Gleichungen
rekursiv definiert:
QuickSort({}) := {}
QuickSort(m :: l) := QuickSort(left(split(l,m))) ° (m) ° QuickSort(right(split(l,m)))
Was hier geschieht ist uns bereits bekannt und wurde weiter oben auch schon erklärt.
Dennoch hier noch mal die formale Erklärung ddes Algorithmuses:
m :: l ist eine liste mit Kopf m und Rest l. split erwartet 2 Argumente, das
Pivotelement (hier m) und eine Liste (hier l). Sie gibt ein Tupel aus 2 Elementen
zurück, welche wiederum 2 Listen l1 und l2 sind. Dabei besteht l1 aus den Elementen k
 l so dass k  m und l2 aus k  l mit k > m. left und right geben jeweils den linken
bzw. den rechten Teil eines Tupels zurück. Sprich wir haben hier die selbe Funktion
wie vorhin die Lösung mit lp ep und up. Wir gehen davon aus, dass die eben
erwähnten Algorithmen richtig sind, und beweisen auf deren Grundlage die
Korrektheit von Quicksort:
Wir wissen, dass left(split(l,m)) und right(split(l,m)) Listen zurück geben, deren
Längen jeweils kleiner gleich der Länge von l sind.
Induktionsannahme:
Für alle Listen l mit einer Länge < n arbeitet Quicksort korrekt.
Induktionsanfang:
Für l = {} gilt nach der ersten Gleichung die Annahme.
Induktionsschritt:
Zu zeigen ist dass Quicksort auch auf Listen der Länge n korrekt arbeitet. Sei
also l = m::r eine Liste der Länge n. Dann ist r eine Liste der Länge (n-1), und
left(split(r,m)) und right(split(r,m)) jeweils Listen mit einer Länge < n. Für
diese kann man die Induktionsannahme anwenden und erhällt dass Quicksort
auf diesen Listen korrekt arbeitet. Denn für Listen der Länge < n arbeitet
Quicksort laut Induktionsannahme schließlich korrekt.
Da nun die Elemente aus left(split(r,m)) alle kleiner gleich m sind, und da die
Elemente aus right(split(r,m)) alle größer m sind haben wir eine aufsteigend
sortierte Liste, welche genau aus den Elementen besteht, aus der l bestand, qed.
c) Der Zeitaufwand bei der Sortierung durch Quicksort lässt sich folgendermaßen
abschätzen: Jeder Aufruf von Quicksort hat zwei rekursive Aufrufe zur Folge. Im
Mittel wird die Länge der zu sortierenden Teilsequenzen halbiert. So gelangt man
nach log2 n Schritten zu einer Liste der Länge n < 2, die per Definition sortiert ist. In
jedem Schritt muss die zu sortierende Liste darüber hinaus in up, ep und ep
aufgespalten werden. Das erfordert im Mittel n/2 rekursive Aufrufe der
entsprechenden Funktionen. Wir bekommen also insgesamt ca. 1,2n log2 n Aufrufe
und damit einen Zeitaufwand von O(nlogn).
MergeSort
Der MergeSort Algorithmus beruht auf dem vereinigen (merge) von in sich sortierten Listen.
Die vereingte Liste ist dann wieder sortiert.
Seite 43 von 68
Hier eine Lösung in Ocaml:
let rec merge l1 l2 = match (l1,l2) with
| ( _ , [] ) -> l1
| ( [],_ ) -> l2
| (a :: xs, b :: ys) when a < b -> a :: (merge xs (b::ys))
| (a :: xs, b::ys) -> b :: (merge (a::xs) (ys)
;;
Wir verwenden hier eine weitere Variante von MergeSort die zuerst aus der Liste eine Liste
von einelementigen Listen macht und diese dann nacheinander mit der Funktion merge
vereinigt, bis nur noch eine einzige, sortierte, Liste vorliegt.
In jedem Schritt vereinigen wir die jeweils zwei hintereinander stehenden Listen zu einer.
let rec mergesort_step l = match l with
| [] -> []
| [a] -> [a]
| a::b::xs -> (merge a b) :: (mergesort_step xs)
;;
Falls die Liste nur noch aus einer Liste besteht sind wir fertig, ansonsten das ganze nochmal.
let rec mergesort l = match l with
| [] -> []
| [a] -> a
| _ -> mergesort (mergesort_step l)
;;
Jetzt noch eine Hilfsfunktion, um die Liste von Listen zu erzeugen:
let rec explode l = match l with
| [] -> []
| a :: xs -> [] :: (explode xs)
;;
Und in Prolog:
Zunächst brauchen wir eine Funktion, die zwei sortierte Listen zu einer sortierten Liste
zuzammensetzt, wie in Ocaml auch:
merge([],List, List).
merge(List, [], List).
merge( [A|Alist], [B|Blist], [B|Rlist]) :merge(A|Alist], [B| Blist], [B|Rlist]) :-
A =< B,
merge( Alist, [B|Blist], Rlist).
A > B,
merge([A|Alist],Blist,Rlist).
Wir brauchen noch eine Funktion die eine Liste in zwei (ungefähr gleich große Teile zerlegt.
Wir spalten immer die ersten beiden Elemente ab und veteilen sie abwechselnd auf die beiden
Listen.
Seite 44 von 68
split([],[],[]).
split([A][A],[]).
split([A,B|T], [A|L1], [B|L2]) :- split(T,L1,L2).
Jetzt wieder die eigentliche Funktion: Die Liste wird sortiert indem wir sie zuerst in zwei
Teile aufspalten die jeder für sich sortiert werden; diese beiden sortierten Listen vereinigen
wir dann wieder zum Ergebnis.
mergesort([],[]).
mergesort([A],[A]).
mergesort(List,Rlist) :- split(List, Alist, Blist),
mergesort(Alist, Asort), mergesort(Blist,Bsort),
merge(Asort, Bsort, Rlist).
Rekursive Datenstrukturen
Die allgemeine Syntax einer Datentypdefinition ist wie folgt.
„Topic“
type <Tvar1> <Topi> = <C11> of <T11> |… | < T1n>
…
and <Tvarm> <Topm> = <Cm1> of <Tm1> |…| <Cmn> of <Tmn>
wo:
<Topi>:
<Tvari>:
<Tij>:
<Cij>:
Name des jeweiligen Datentyps
Sequenzen von Typvariablen
Ausdruck von Typen
Konstruktionen, die Lemente vom Typ <Tvar> <Top> enthalten.
Beispiel:
a) Linear rekursive Sorten z.B. die bekannte Lifo-Listen (siehe Folie Seite 102).
b) nichtlinear rekursive Sorten: Typdefinition enthällt in mindestens einem Typausdruck
mindestens zweimal den zu definierenden Typ.
Bäume
type (’a,’b) tree = Leaf of ’a
| Node of ((‘a,’b) tree * b * (‘a,’b) tree);;
 Baum, dessen Blätter vom Typ ’a sind, und dessen Knoten Elemente vom Typ ’b
enthalten (Achtung: Kein a-b-Baum!)
Spezialiesierung:
’a = (); entspricht einem Baum des Typs
type ’b tree = Empty | Node of ’b tree * b * ’b tree
Funktionen zum Aufbau solcher Bäume:
let rec insertInnerTree x tree = match tree with
| Leaf ()  Node(Leaf(), x, Leaf())
| Node(l,el,r) when (el < x)  Node (insertInnerTree x l, el,r)
Seite 45 von 68
| Node (l,el,r)  Node(l,el,insert Inner Tree x r);;
let buildTree list = match list with
| []  tree
| x:: rest  v (insertIInnerTree x tree) rest
in f (Leaf()) list;;
Verschränkte Rekursion
Typdefinition umfasst mehrere neue Typen und in den Typausdrücken können alle Typen
auftreten.
type `b wald = LeerW | Wald of `b * `b wald
and ‘a baum = LeerB| Baum of ‘a * (‘a baum) wald;;
Der erste Teil dieser Typdef. hat hierbei die Struktur einer Liste, der zweite führt zur
Verschränkung durch Rückgriff auf „wald“.
Durch verschränkt rekursive Sortendeklaration werden auch die darauf arbeitenden
Funktionen rekursiv. Beispiel:
Funktion zur Suche nach einem Teilbaum mit einer bestimten Wurzel x.
let rec baumsuche(x,baum) = match baum with
| LeerB  LeerB
| Baum(y,wald) when x = y  Baum(y,wald)
| Baum(y,wald)  waldsuche(x,wald)
and waldsuche (x,wald) = match wald with
| LeerW  LeerB
| Wald (ersterB,restWald)
let t = baumsuche (x,ersterBaum)
in match t with
| LeerB  waldsuche (x,restWald)
|_t
;;
Weiteres Beispiel für Bäume mit mehreren „Kindbäumen“: (a,b)- Bäume.
Ein (a,b)-Baum ist ein externer Suchbaum (d.h. alle Informationen befinden sich in den
Blättern), für den gilt:
- alle Blätter haben die selbe Tiefe
- für die Anzahl N der Kinder eines jeden internen Knotens (außer
der Wurzel) gilt: a < N < b
- für die Wurzel gilt: 2 < N < b
außerdem gilt für b: b  2a 1
Spezialfall: b = 2 a -1, hier spricht man von B-Bäumen.
B-Bäume finden vor allem als Datenbank- Externspeicher Verwendung.
Seite 46 von 68
Aufgabe 19 B- Bäume
Bei einem B- Baum vom Grad t haben die Knoten zwischen up(t/2) und t Kinder, wobei up(x)
x aufrundet. Die Wurzel hat zwischen 2 und t Kinder. Alle Bläter haben gleiche Tiefe.
a) Zeigen sie, dass in einem B- Baum bom Grad t bei einer Zahl von n Knoten die
 n 1 
Höhe h immer begrenzt ist durch h  1  log t 
.
up ( )  2 
2
Berechnen sie damit die maximale eines Baumes vom Grad 128 für eine Millionen
bzw. eine Millarde Knoten.
b) B- Bäume werden gerne für das Suchen in grossen Datenbanken verwendet. Was
für einen Voteil bietet diese Datenstruktur hierfür?
c) Wir fügen nun die Zahlen1, 2, 3, ... aufsteigend sortiuert in einen anfangs leeren
B- Baum bvom Grad t = 128 ein. Bei welcher Zahl hat der B- Baum erstmalig die
Tiefe 4?
d) Worauf muss man beim Löschen aus einem B- Baum achten?
Lösung zu Aufgabe 19
a) Zu zeigen ist, dass in einem B- Baum vom Grad t bei einer Zahl von n Knoten die
 n 1 
Höhe h immer begrenzt ist durch h  1  log t 
.
up ( )  2 
2
Ein B- Baum mit n Einträgen hat n + 1 Blätter. Wegen der Mindestzahl von 2 für die
Wuzel, bzw. up(t/2) für andere Knoten ist die Anzahl von Einträgen auf Ebene 1, 2, 3,
... immer grösser als 2, 2* up(t/2), 2* up(t/2) * up(t/2)... Damit erhällt man:
n  1  2 (up(t / 2)) h 1
 n 1 
h  1  log up (t / 2) 

 2 
max.h(Millionen) = ca. 4
max.h(Millarden) = ca. 5



b) Da bei heutigen Massenspeichern, insbesondere magnetische Festplatten die
Daetenzugriffszeit weit über der (kontinuierlichen) Leserate liegt, ist es sinnvoll
grosse Datenmengen immer im Block auszulesen.
c) Wir fügen nun die Zahlen 1, 2, 3, ... aufsteigend sortiert in einen anfangs leeren BBaum vom Grad t = 128. Bei welcher Zahl hat der Baum erstmalig die Tiefe 4?
Jeder Knoten kann zwischen up(t/2) und (t-1) in unserem Fall also zwischen 64 und
127 Einträge haben.
Hier hätt ich gern die Zeichnungen des Lösungsblattes eingetragen, die wirklich sehr
sehr schön gemacht sind, nur leider funktioniert im moment meine Zwischenablage
nicht und ich hab, nachdem ich 6 Stunden gestern nacht mich um dem Computer eines
Freundes gekümmert habe echt keine Lust mich darum auch noch zu kümmern, kuckt
für die Lösungen einfach das Übungsblatt an!
Seite 47 von 68
AVL Bäume
AVL Bäume sind Binärbäume, für die gelten, dass sich die Tiefe des linken Teilbaumes
maximal um 1 von der Tiefe des rechten Teilbaumes unterscheidet. Damit ergibt sich ein
guter Kompromiss zwischen einer geringen Gesamthöhe und dem Aufwand beim Einfügen
oder Löschen von Elementen. Eine geringe Gesamthöhe ist erwünscht, da sich die Laufzeit
beim Suchen nach Elementen proportional zur Höhe des Baumes verhällt. Ein völlig
ausgeglichener Baum erreicht eine Laufzeit von O(log n ) [n = Anzahl der Elemente], und
auch die Suche im (theoretisch) am schlechtesten ausgeglichenem AVL- Baum ist nur
geringfügig langsamer. Aufgrund des gegenüber normalen Binärbäumen aufwendigeren
Einfügens und Löschens sind AVL- Bäume besonders da attraktiv, wo auf einem fest
vorgegebenen Datenbestand viele Zuigriffe notwendig sind, insbesondere dann, wenn auf alle
Knoten im Mittel gleich oft zugegriffen wird.
Die AVL Eiugenschaft wird durch spezielle Algorithmen beim Einfügen und Löschen von
Knoten in/aus dem Binärbaum erreicht. Dabei wird für jeden Knoten zuzätzlich ein Wert
Balance mitgespeichert, der den Uinterschied zwischen der Tiefe des linken und des rechten
Teilbaumes beinhaltet. Wird diese Balance kleiner als -1 oder größer als +1 wird dieser
Knoten rotiert um die Balance wieder auszugleichen. Dabei unterscheiden wir zwischen
links-, rechts-, linksrecht-, rechtslinks- Rotation.
Funktoren: Datentypen mit Funktionen als Argument.
Bis hierher: Datentypen enthielten bisher Konstruktoren, deren Argumente wieder
Datentypen sind.
Bei den häufig verwendeten Sorten wie etwa stacks, Listen etc. sind die Zugriffsfunktionen
immer dieselben und eng mit der Datenstruktur verbunden. Deshalb sinnvoll: Funktionen
werden zum Bestandteil der Datenstruktur (ebenfalls zentrales Konzept oder OO.
Programmierung)
type ’a listFum = Wert of ’a |
Fum of (‘a  ‘a) * ‘a listFum;;
Der Konstruktor “Fum” enthällt hierbei aber als erstes Argument die Signatur ‘a  ‘a und als
zweites Argument ein Element des zu definiernden Datentyps.
Soll ein Objekt dieses Datentyps erzeugt/ instanziert werden, so müssen dem Konstruktor
geeignete Eigenschaften übergeben werden, z.B.:
let succ = (+) 1;;
let div8= (/) 8;;
let a = Fum(div8, Fum(succ, Wert 3));;
Der Datentyp listFum ist also Listenstruktur, wobei der Kopf der Liste eine Funktion ist, die
auf den Rest der Liste angewendet werden kann. Ausgeführt werden könnte eine solche
Funktionsdeklaration mit Hilfe der Funktion:
so:
oder so:
let rec compute = function
let rec compute l = match l with
| Wert v  v
| Wert v -> v
| Fum (f,x)  f(compute x);;
| Fum (f,x) -> f (compute x);;
hiermit ergäbe sich für compute a = 2.
Seite 48 von 68
Beispiel 2: Graphen und Suche in Graphen.
In einfacher Weise charakterisieren wir einen Graphen, indem wir jedem Knoten die Menge
seiner direkten Nachfolger angeben. Graph besteht also aus einer Funktion, die zu jedem
Knoten die Menge seiner Nachfolger berechnet bzw. zuordnet.
type ’a graph = Graph of (’a  ‘a list)
Beispiel: Graph zur Darstellung der echten Teiler einer natürlichen Zahl.
12
13
14
8
9
6
4
7
5
2
3
1. Schritt: Definition der NachfolgerFunktion.
let teiler n =
let rec f m = match m with
| m when (m < 2)  []
| m when ( n mod m = 0)& ( m < = n /2)  m :: f(m-1)
| m  f (m-1)
in f n;;
2. Schritt: Erzeugung des Objekts
let graph1 = Graph(teiler);;
Start
Ziel
In derart definierten Graphen soll nun nach Elementen gesucht werden, die gewisse Prädikate
pred erfüllen. Vorgehen:
1.)
Ausgehend vom Startknoten werden die direkten Nachfolgerknoten ermittelt.
Seite 49 von 68
2.)
3.)
Bei Breitensuche werden zuerst alle Nachgfolger geprüft und dann erst die
Nachfolger der Nachfolger; bei Teifensuche umgekehrt.
Jeder Nachfolgerknoten verweist seinerseits auf eine Menge von Knoten, hierbei
sollen nur die Knoten besucht (geprüft) werden, die vorher nicht besucht wurden (
Vermeidung von Zyklen)
Algortihmus zur Tiefensuche:
type ’a reachable = OK of ’a | Fail;;
type ‘a graph = Graph of (‘a  ‘a List);;
let getSuccFum(Graph(f))= f;;
let rec isElem list a = match list with
| [] -> false
| hd :: rest when a = hd -> true
| hd:: rest -> isElem rest a;;
let depthsearch graph pred startnodes=
let rec find visited startnodes = match startnodes with
| []  Fail
| a:: rest when isElem (a,visited) -> find visited rest
| a:: rest when (pred a)  OK a
| a:: rest  find (a:: visited)
((( get SuccFum graph)a) @ rest)
in find [] [startnodes];;
Induktive Beweise über rekursive Sorten
Definition:
(Partielle Korrektheit) Eine Funktion heißt partiell korrekt, wenn sie keine falschen
Resultate (im Sinne der Spezifikation) liefert.
Bemerkung:



Funktionen, die für gewisse Argumente nicht terminieren, sind auch partiell
Korrekt
Eine partiell Korrekte Funktion, die stets terminiert, heißt total Korrekt.
Um partiell Korrekte Funktionen zu zeigen, ist das Prädikat f Im p ( x)  f spez ( x)
zu zeigen.
Verallgemeinerung: Man benötigt eine Technik, um ein Prädikat P über einer Datenstruktur
zu zeigen.
=>
strukturelle Induktion.
Notwendig:

Begriff der freien Algebra (Informell).
Die Werte der freien Algebra sind die Ausdrücke, die aus den Konstruktoren
gebildet werden
Seite 50 von 68

Jedem Konstruktor wird eine Funktion zugeordnet, die auf den Werten der
Algebra operiert.
Sei
type t = C0 | C1 of t | C2 of t * t;;
ein Datentyp. Dann ist C0 der einzige Ausdruck, der durch einmaliges Anwenden
eines Konstruktors entsteht. (Term der „Tiefe 1“). Durch zweimaliges Anwenden
entstehen
C1(C0); C2(C0,C0);
dreimaliges Anwenden:
C1(C1(C0))); C1(C2(C0,C0));… usw.
Die Vereinigung aller derart Konstruiierbaren Werte bildet die Menge der Werte der
freien Algebra:
Funktionen der freien Algebra:
C0:
C1:
C2:
t
tt:
t*tt
(Konstante)
C1(E1) = E2
C2(E1’,E2’) = E3
Strukturelle Induktion:
Offenbar werden alle Werte der freien Algebra aus (endlichen) Werten geringerer Tiefe
erzeugt. Das Prinzip der strukturellen Induktion lässt sich also auf derartige freie Algebren
anwenden.
Gegeben sei ein Datentyp t mit den Konstruktoren C0,…,Cn: Dann gilt das Prädikat
x  t : P ( x)


wenn für alle nullstelligen Konstruktoren Ci das Präsikat P(Ci) gilt. (IA)
wenn für Konstruktoren Ci vom Typ T1 * … * Tj (j >0) die Gültigkeit von
P(Ci(x1,…,xj)) aus der Gültigkeit von P(x1),…, P(xj) folgt.
Beispiel: Binärbäume
type ’a btree = Leaf of ’a | Node of ‘a btree * ‘a btree;
Es soll nun x  (T btree) : P( x) gelten.

es gilt P(Leaf(a))

es gilt P(Node(t1,t2)), wenn P(t1)  P(t 2) gilt.
Wir zeigen nun, dass die Funktion
let rec reflect tree = match tree with
| Leaf(a) -> tree
| Node (t1,t2) ->
Node(reflect(t2), reflect(t1));;
die Bedingung :
Seite 51 von 68
reflect(reflect(x))=x
erfüllt.
Beweis :
Induktionsanfang :
x= Leaf(a)
reflect(reflect(Leaf(a))
 reflect ( Leaf (a))
Def
 Leaf (a); qed
Def
Induktionsschluss:
Induktionsannahme: Es gelte reflect(reflect(ti)) =ti
Dann folgt:
i Element {1;2}
reflect (reflect ( Node(t1, t 2)))
 reflect (( Node(reflect (t 2), reflect (t1)))
Def
 Node(reflect (reflect (t1), reflect (t 2)))
Def
 Node(t1, t 2);qed
I . A.
Denotationale Semantik rekursiver Strukturen
Problematik:
Einfache Ausdrücke
let add x y = x+y;;
type farbe =Rot | Gelb |…
 intuitiv klar.

schwieriger :
let rec add (x,y) = match x with
| 0 -> y
| z +1 -> add (z,y) +1;;
type lifo =Empty | App of int * Lifo;;

unverständlich
let f x y = f x y;;
type t =T of t;;
Erläuterung zur Vorgehensweise:
Um die Semantik (Bedeutung) rekursiver Struktren zu definieren, verwenden wir im
Weiteren ein iteratives Vorgehen
Seite 52 von 68
Wir beginnen mit einer vollkommen undefinierten Sturktur.
Über iterative Rechenvorschrifften erweitern wir sukzesive den Definitionsbereich
und gelangen schließlich zu einer Funktion mit max. Definitionsbereich.
Das Element Bottom: 




Jedes Funktionsresultat soll eine Bedeutung (Sinn) haben
Auf der anderen Seite gibt es Funktionen, bei denen nicht entscheidbar ist, ob
sie terminieren (d.h. ein sinnvolles Resultat liefern)
Folgerung: Wir müssen ein Objekt einführen, das nicht terminierende
Funktionsresultate beschreibt => 
Dieses Objekt ist ein Wert (mit Semantik) wie beispielsweise 1;2;7…
Ordnungen über Mengen M u {  } = M 
Bei der Diskrepanz der Semantik rekursiver Strukturen muss die Wertemenge dieser
Funktionen, d.h. M u {  } diskutiert werden. D.h. wir benötigen geeignete Ordnung für M  ,
=> Definition einer speziellen Ordnung (flache Ordnung):
x  y  x   y 
In Worten: x ist genau dann dem y untergeordnet, wenn x nicht terminiert,
jedoch y sehr wohl.
(sprich: x ist schwächer definiert als y). Zusammen mit Gleichheit bildet „  “ eine partielle
oder Halb-Ordnung, denn es gibt nicht vergleichbare Elemente, Beispiel: in  sind alle
Zahlen nicht miteinander vergleichbar.
Flache Ordnung für

...-2
-1
0
1…

Diese Ordnungen lassen sich verallgemeinern auf Tupel und insbesondere Funktionen
( x, y)  ( x ', y ')  x  x ' y  y '
f  g  x  M  : f ( x)  g ( x)
Die Ordnung „  “ über Funktion drückt aus, wie „definiert“ eine Funktion ist.
n
f
…-2
…
-1

0

1

2
…
Seite 53 von 68
g
h
... 
... 

3
1


1
2 ...
 ...
=> f  g; f  h
g und h können nicht zueinander in Relation gesetzt werden.
Bemerkung:
Durch Einführung des Objektes  lassen sich partielle Funktionen zu totalen machen.
Vollständige Halbordnung
Eine Halbordnung heißt vollständig, wenn jede aufsteigende Kette,
c1  c2  ... , eine kleinste obere Schranke, d.h. ein Supremum, besitzt.
Beispiel:
Für flache Ordnungen, beispielsweise über  , kann eine derartige Kette höchstens 2
Elemente haben.
 3  3  ...
Monotonie:
Eine Abbildung f heißt monoton, wenn x  y  f ( x)  f ( y )
Bemerkung:
Wenn x =  und y  dann muss f ( x)  sein. f(x) kann durch Anwendung von f
nicht „stärker“ werden als f(y).
Stetigkeit
Sei fi eine Folge von Funktionen mit vollständiger Halbordnung. Dann heißt eine
Abbildung  über diesen Funktionen stetig:
sup  [ fi ]   [sup  fi  ]
mit i  IN
Fixpunkt: Sei  : M  N eine Abbildung, dann heißt ein Element a  M mit
a   [a ]
ein Fixpunkt der Abbildung  .
Satz von Kleene:
Sei  eine stetige Abbildung, dann ist der kleinste Fixpunkt von  [f] identisch mit:
f  sup  fi ; i 

wobei
f 0 ;
fi 1   [ fi ];
Beispiel:
Heron- Verfahren zu Berechnung von
2.
Seite 54 von 68
1
2
y²  2  y   y  
2
y
y  [ y ]
Iterationsvorschrifft:
y0  1
1
2
yn 1   yn   ; n  0
2
yn 
Der Fixpunkt dieser Abbildung ist
Satz ist das der Fixpunkt.
2 . Denn y ist
2 und nach dem Kleenschen
y0  1
1 2 3
y1  1     1,5
2 1 2
13
2
y2    2   1, 42
22
3
…
Frage:
Halbordnung für Heron Verfahren:
x
2
y  ( x ²  2)  ( y ²  2)
 y0 
2
y1 
2
y2 ...
Weiteres Beispiel:
Diff. Gleichung:
y
d
y
dx
y   [ y]
=>
Lösung ist ex
Anwendung des Kleenschen Satzes auf Strukturen funktionaler Sprachen:
(1)
Rekursive Datentypen
Beispiel: Liste
type seq = Empty | Prepend od int * seq;;
(*)
Seite 55 von 68
Vorgehensweise: (Zur Lösung derartiger Gleichungen)
I. Übetragung der sprachspezifischen Form in eine mathematische
II. identifizieren des Fixpunktoperators
III. Berechnung der Lösung mittels des Kleenschen Iterationsverfahrens
Die Gleichung (*) definiert eine Abbildung zwischen Mengen. Sei seq Trägermenge
der Sorte seq.
=>
 : Seq   Seq 
 ( s)      n, r  ; n  , r  S    
Es ergibt sich also die Fixpunktgleichung:
(**)
S   [S ]
Da (**) nicht nach S auflösbar ist, bleibt nur die iterative Lösung nach Kleene.
Iterationsvorschrifft:
S0 
Si 1   [ Si ]
Wir bilden also Ketten:
S0   S1   [S0 ]   S2   [S1 ]  ...
Bemerkung: (Stetigkeit von  ?)  . Beweis, indem man die Stetigkeit für die
Vereinigung , Tupelbildung etc. …, zeigt
Durchführung der Iteration
S0  {}
S1   [ S0 ]


S1  { }   n, r  : n  , r    {}

 
S1  { }  {}
S 2   [ S1 ]   , (0,  ), (1,  ),...
S3  { , (0,  ), (1,  )......(0, (1,  )), (0, (2,  ))...}
=> Nach der i-ten Iteration erhällt man alle Listen mit maximal(i-1) Elementen
(2)
Anwendung von „Kleene“ auf rekursive Funktionen:
Seite 56 von 68
Fakultätsfunktion:
let rec f n = match n with
| 0 ->1
| n -> n * f(n-1);;
(I)
Zuordnung des entsprechenden  .Operators
 n  0

 [ F ](n)  1 n  0
n F (n  1) n  0

Bemerkung:
 wird häufg Funktional genannt, d.h. eine Abbildung die Funktionen auf
Funktionen abbildet.

Fixpunktgleichung:
f (n)   [ f ](n)
Frage:
Welche Halbordnung ist zu verwenden? => Halbordnung über Funktionen:
f  f '  x : f ( x)  f '( x)
Iterative Lösung von f (n)   [ f ](n) :
f 0 ( n )  (n) 
fi 1 (n)   [ f i ](n)
f 0 (n) 
f1 (n)   [ f 0 ](n)
 n  0
 n  0


f1 (n)  1 n  0
 1 n  0


n  n  0  n  0
 n  0

f 2 (n)   [ f1 ](n)  1 n  0

n f (n  1) n  0
Seite 57 von 68
 n  0

1 n  0

f 2 (n)  n  n  1  0  n  0

 n 1 n  1  0  n  0
n  n  1  0  n  0

 n  0  n  0


1 n  0
1 n  0
f 2 ( n)  

n n  0 1 n  1
 n  1  n  1


 n  0

1 n  0

f3 (n)  1 n  1

2 n  2
 n  2

Folgerung:
nach der i-ten
0  n  i bekannt.
n
f0
f1
f2
f3
Tabelle:
-1




Teration
0

1
1
1
ist
der
1


1
1
Wertebereich
2



2
der
Fakultätsfunktion
3




Umsetzung in ein Ocaml Programm:
type intbot = Bottom | int;;
let rec tau f x = match x with
| 0 -> 1
| x -> x * f(x-1)
| n when n <0 -> Bottom;;
let rec iteration tau n =match n with
| 0 -> Bottom
| n -> tau (iteration tau (n-1));;
Beispiel: (6- Iteration) Der Aufruf
iteration tau 6;;
liefert eine Funktion, die die Werte der Fakultätsfunktion für 0  n  6 berechnet.
4




für
Seite 58 von 68
Semantik rekursiver Funktionsdeklarationen Beispiel (s. ÜB 5 Aufgabe 26)
Gegeben sei folgende rekursive Funktionsdeklaration:
fxt f = (natx, nat y) nat:
if y = 0 then 1
else x* f(x,y-1) fi
Wir wollen nun herausfinden was diese Funktion macht. Dazu geben wir das zu f zugehörige
Funktional  an:

falls x   y 

 [ g ]( x, y)  1
falls x   y  0

 x * g ( x, y  1) falls x   y  0
Nun berechnen wir die ersten vier Approximationsfunktionen und werden dann sicher sehen,
was diese Funktion tut.
f 0 ( x, y ) 
fi 1 ( x, y )   [ f i ]

falls x   y 


falls x   y  0

1
f1 ( x, y )   [ f 0 ]  
 1
 x*  falls x   y  0  ( y  1   y  1  1) 
x
 x *1 falls x  y  1  0

falls x   y   y  1
falls x   y  0
falls x   y  1
Ich habe gerade mühsehlig die ganzen Berechnungsschritte in einem Formeleditor
eingegeben. Durch einen blöden leichtsinnsfehler habe ich alle Daten bis auf die ersten
beiden Approximationsfunktionen verloren. Bin zu genervt und hab auch besseres zu tun, als
das ganze noch ein mal nieder zu tippen, also habt Nachtsicht mit der Qualität folgender
Ersatzabbildung:
Seite 59 von 68
Funktionale Programmierung und   Kalkül .
Beim Entwurf funktionaler Programme spielt der Funktionsname eine zentrale Rolle. Das
zentrale Konzept der Rekursion stütz sich ebenfalls auf benannte Funktionen.
Aber es gibt zahlreiche Fälle, bei denen man auf Namensgebung verzichten möchte, z.B.:
o Beim Arbeiten mit Funktionalen (also z.B.: Fixpunktoperatoren) möchte man
beteiligten Funktionen keinen festen Namen geben; er soll vielmehr variabel
sein
o Bei der Umformung von algebraischen Ausdrücken arbeiten wir mit Namen
und geben „Zwischenausdrücken“ keinen Namen.


Die Idee, auf benannte Funktionen zu verzichten, stammt von Russell (1910). Er
schrieb Ausdrücke der Form x  t(x) in der Form t[ x ].
Da Typograph das „^“ nicht setzen konnte, wurde zunächst ^x.t(x) daraus (von
Church (1930) verwendet) und schließlich
 x.t ( x)
  Kalkül von Church systematisch ausgearbeitet. Literatur: Barendrengt (1984):
  Calculus . Es gelang Church, mit dem   Kalkül alle mathematischen Ausdrücke,
Zahlen, Funktionen, Boolsche Werte auszudrücken. Darin liegt die Beduetung für die
Informatik. Mit Hilfe des   Kalkül ’s lassen sich die zentralen Elemente
funktionaler Sprachen ( = Datenstrukturen und Funktionen ) einheitlich beschreiben
und auswerten.
Syntax des (reinen )   Kalkül ’s)
Terme im   Kalkül sind folgendermaßen definiert:
t := c | x | (t1t2) |  x.t
Hierbei heißt:
(t1 t2): Applikation, d.h. eine Funktion t1 wird auf ein Argument t2 angewendet (t1(t2))
 x.t : Abstraktion; represäntiert eine Funktion mit formalen Paramter x und dem
Funktionsrumpf t; x ist in t gebunden.
c:
Konstante
x,y,z: Variablen
Bemerkungen

Applikationen sind linksassoziativ:
(t1 t2 t3 t4) = (((t1 t2)t3)t4)

 bindet so weit wir möglich nach rechts
Seite 60 von 68
 x.xx   x.( xx)  ( x.x) x

Bemerkung:  - Terme lassen sich als Bäume darstellen
Term
Baum
c
c
 x.t
x
x
x
(t1t2)
t1
t
t2
( x. fx) y
Beispiel:
x
f
y
x
Ob eine Variable in einem Term frei oder gebunden ist, wird durch Funktionen „free“
bzw. „bound“ bestimmt:
free(x) = {x}
free(t1 t2) = free(t1)  free(t2)
free(  x.t ) = free(t)\{x}
bound(x)= 
bound(t1 t2) = bound (t1)  bound(t2)
bound(  x.t ) ={x}  bound(t)
Beispiel: In den Ausdrücken
y
x  IN : P( x, y)oder  x ²dx oder
0
y
 x² ist jeweils x gebunden und y frei.
x 0
Definition :
-
Beispiel:
Ein Kombinator ist ein  -Ausdruck , der keine freien Varialen
besitzt.
Currying: Reduktion nachstelliger Funktionen auf solche mit nur
einem Argument
Seite 61 von 68
 INxIN  IN
Funktion y: 
 ( x, y )  x  y
Darstellungn im  -Kalkül:
y =  x. y.  x y
d.h. y: IN  IN  IN
Auswertung dieser Funktion im  -Kalkül.
y 5 3 = (x. y.+x y) 5 3
= (y.+5 y) 3
=+53
Bemerkung:
wird verändert
ist gerade verändert worden
Systematik des  -Kalküls: siehe unten, Bedeutung und Verwendung der bisher undefinierten
Terme „+“, „5“ und „3“ wird unten erläutert
Reduktionsregeln im  -Kalkül
1.) - Konversion „Gebundene Variablen können beliebig umbenannt werden“
Jede  Abstraktion  x.t darf ersetzt werden durch  y. (t[ y / x]) „links bleibt
stehen“
Beispiel:
x( x.  y. x y)  x( z.  y. zy )

not
 z ( z. y.zy )

not
 x( x. x.xx)

2.) - Konversion: Durch - Konversion werden  - Terme vereinfacht, Konversion ist die zentrale Operation bei der Berechnung von  - Ausdrücken
Jeder Ausdruck ( x.t )t ' darf ersetzt werden durch t[t '/ x ] , d.h. „  x.“
fällt weg und alle Vorkommen von x in t werden durch t’ ersetzt. Gegebenenfalls
müssen vorher - Konversionen durchgeführt werden. Durch - Konversion wird
also eine Relation  C Expr x Expr mit Expr gleich der Menge aller  - Terme

definiert mit
( x.t )t '  t[t '/ x]

Beispiel:
Seite 62 von 68
1.) (  f.  x. fx) x
 (  f.  x’. fx’) x

 (  x’. fx’)[x/f]

 (  x’.x x’)

2.) f = (  x.(y(y x)))
y = (  y.y)
Berechnung von f(  z.z):
 (  x.(y(y x))) (  z.z)
 (y(y x)) [(  z.z)/x]

y(y(  z.z))
y((  y.y)(  z.z))
 y(y[  z.z/y])

y(  z.z)
 (  y.y)(  z.z)  (  z.z)

Bemerkung: y ist Identitätsfunktion
3.)  -Konversion: Die  -Konversion ist ein Spezialfall der -Konversion;
Relation   Expr x Expr

 x.tx  t
falls x  free(t )

Begründung:
(x.tx) f  (t x) [f/x]  t f , falls x nicht frei in t ist.


*
Bezeichen wir mit
 den reflexiven und transitiven Abschluss der Relation  ,


so lassen sich Normalformen definieren:
*
Ein - Term t’ ist die Normalform von t, falls gilt t  t’ und kein t’’  Expr existiert,

so dass t’  t’’.

28.05.03
www.teachscheme.org
Bemerkung: Es gibt Terme, die keine Normalform besitzen
( x.xx)( x.xx) ( xx)[( x '.x ' x ') / x]  ( x.xx)( x.xx)


Seite 63 von 68
Satz: (ohne Beweis): Jeder  - Term besitzt höchstens eine Normalform
Darstellung zentraler Datentypen funktionaler Sprachen im  - Kalkül.
Der reine  -Kalkül beschäftigt sich nur mit Ausdrücken. Dennoch können damit auch
Objekte der üblichen Datentypen dargestellt werden: Boolesche Werte und Zahlen durch
geschlossene Terme in Normalform.
Boolsche Werte: Darstellung von „True“ und „False“ stützt sich auf ihre Verwendung in
bedingten Ausdruck
if b then e1 else e2
Im Sinne dieses Ausdrucks kann b als Funktion aufgefasst werden, die zwei Argumente e1
und e2 besitzt, ist sie „True“, dann wird erstes Argument ausgewählt; ist sie „False“, dann das
zweite.
Wir definieren:
true   x. y.x
false   x. y. y
cond  b. x. y. b x y
mit b Element {True, False}
Beispiel: true e1e2
(( x. y.x)e1 )e2
(( y.x)[e1 / x])e2

 ( y.e1 )e2
 e1[e2 / y ]

 e1
*
Analog: False e1 e2  e2

Für den Aufruf if true then e1 else e2 schreiben wir:
2
2


Cond true e1 e2 = ((b. x. y b x y )true)e1e2 ((( x. y.true x y )e1 )e2  true e1 e2  e1

Natürliche Zahlen (Church- Numerale)
Wie Boolsche Werte, so werden auch natürliche Zahlen durch geeignete  -Terme dargestellt.
Man nennt solche Zahlen „ Church- Numerale“. Trick: Die Zahl n wird durch ein Funktional
repräsentiert, das eine Funktion f n-mal auf ein Argument x anwendet. Also:
Seite 64 von 68
0   f . x.x
1   f . x.( f x)
2   f . x. f ( f x)
...
n   f . x. f n x
 Anzahl der Anwendungen von f repräsentiert die Zahl
Bemerkung: Bei der Codierung von 0   f . x.x ist  x.x Identitätsfunktion.
Nachfolgerfunktion und Vergleichsoperator über Church-Numeralen
succ   n. f . x. f ( n
Argument
f x
)
wirdBenötigt ,UmNumeral
ApllikationAuszuführen 1
Beispiel:
1
1
succ 1  ( n. f . x. f (n f x)) ( f . x. f x)   f . x. f (( f . x. f x) f x)   f . x. f ((  x. f x) x)


  f . x. f ( f x)  2

Vergleichsoperator für Vergleich mit 0
isZero   n.n( x.False)True
Beispiel:
1
1
isZero 1  ( n.n( x.False)True) ( f . x f x)  ( f . x ( f x)) ( x.False)True ( x '.( x .False) x ')True
 ,
( x .False)True  False


Additionsfunktion für Church- Numerale
add   m. n. f . x. m f (n f x)
Also: add n m
2
2
 ,

  f . x.m f (n f x)   f . x.m f ( f n x)   f . x. f m ( f n x)   f . x. f m n x  m  n
Datentype „Liste“ in  - Kalkül

Seite 65 von 68
Neben elementaren Datentypen wie Zahlen und Wahrheitswerte sind Listen unverzichtbarer
Bestandteil funktionaler Sprachen: Die Liste war ursprünglich der einzige Datentyp in Lisp.
Zum Aufbau einer Liste werden Sprachmittel für die leere Liste und Konstruktoren benötigt.
Im  - Kalkül ist der Listen- Konstruktor eine dreistellige Funktion mit den Argumenten für
den Kopf, für den Rest der Liste und Selektorfunktion zur Extraktion des Kopfs.
cons   h.t. s.s h t
cons
CAR
CDR
Die Selektoren head und tail geben Kopf und Rest zurück; sie werden wie folgt definiert:
head  l.l True
tail  l.l False
wobei True und False die bekannten zweistelligen Funktionen sind, die das erste bzw. zweite
Argument extrahieren.
Beispiel:
head(cons a b)
= ( l.l True)(( h.t. s.s h t )a b)
 ((h.t.s.s h t) True) ( a b )
 (s. s a b) True
 True a b
a
 - Terme in angereicherten  -Kalkül
Für das Weitere gehen wir davon aus, dass die Grundtypen für Boolean und IN, sowie die
wichtigsten Operationen auf diesen Datentypen definiert sind. Berechnung von  Ausdrücken wird dadruch wesentlich übersichtlicher.
Ein Ausdruck wie
let x = 5 and y = 3 in x + y;;
schreiben wir in der Form:
(x.y.add x y) 5 3
 add 5 3  8
Rekursion Im - Kalkül
Seite 66 von 68
Rekursion als wesentlichen Elements funktionaler Sprachen ist im - Kalkül leicht
darstellbar.
Hierbei lassen sich rekursive Funktionen sowohl über rekursive - Terme (intuitiv) als auch
über nicht rekursive Terme und geeignete Operatoren darstellen.
Im Folgenden wird zunächst die intuitive, anschließend die operator gestützte Variante
betrachtet.
Grundidee: Ein benannter - Ausdruck enthällt seinen Namen im Rumpf.
Beispiel: Paritätsberechnung einer Zahl u  IN
let even n =
let rec even2 n = match n with
| 0 -> True
| 1 -> false
| n -> even2 (n-2)
in even;;
Diese Funktion lässt sich in - Kalkül wie folgt berechnen ( die Funktion sub, prod, isZero
und isOne seien gegeben).
even = n.(isZero n) true
((isOne n) False
(even 2 (sub n 2)))
  (isZero 2) True ((isOne 2) False (even2 (sub 2 2 )))
  False True (isOne 2)…)
 * (isOne 2) False (even2 (sub 2 2))
0
 * False
(rekursiver Aufruf)
* even2 0
= n.(isZero n) True ((isOne) False (even (sub n 2))) 0
 * isZero 0 True
* True
Rekursion im - Kalkül mittels Fixpunktoperatoren
In der vorigen Darstellung spielte der Name eine wesentliche Rolle.Wichtiger Vorteil
des - Kalküls ist es jedoch, auf Namen verzichten zu können. Mit Hilfe spezieller
Operanten kassen sich „namenlose“ rekursive Funktionen verwirklichen.
Wie errinern uns: Fakultätsfunktion ließ sich als Lösung der Fixpunktgleichung
 n  0

f (n)   [ f ](n) mit  [ f ](n)  1; n  0
n  f (n  1); n  0

Seite 67 von 68
verstehen.
As - Term ergäbe sich also:
f = tau f mit tau = f.n.t
wobei t ein geeigneter Term zur Umsetzung der jeweiligen Funktionen ist, imFalle der
Fakutätsfunktion wäre z.B.
t = (isZero n) 1 (mult n f (pred n))
Sei fix eine (von mehreren möglichen) Fixpunktoperatoren mit der speziellen
Eigenschaft
*
für beliebige - Terme t
fix t  t (fix t)

Dann kann die Lösung der obigen Fixpunktgleichung durch f = fix tau (anzuwenden
auf f(n)) definert werden.
Bemerkung:
Der Operator fix iteriert  auf der Funktion.
Beispiel:
Rekursive Definition der Additionsfunktion
 y
für x  0
add ( x, y )  
add ( pred x, succ y ) sonst
Dazu lautet der -Term:
add = x.y.(isZero) y (add (pred x) (succ y ))
bzw.
add = (f.x.y. (isZero x) y ( f (pred x) (succ y))) add
Der tau- Operator hat also für diese Funktion die Gestalt:
=>
tau+ = f.x.y. (isZero x) y (f pred x) (succ y))
add = fix tau+
= (f.x.y.(isZero x) y (f (pred x) (succ y)) (fix tau+) 1 2
*
 (isZero 1) 2 (fix tau (pred 1) (succ 2))

false
0
3
Seite 68 von 68
(Rekursion durch fix)
 fix tau 0 3
 tau (fix tau) 0 3
*
3

Für den Gebrauch von Fixpubnktoperatoren siehe Übung
Von hier an hat Knoll wieder Folien benutzt. Es ist also nicht mehr sinnvoll hier weiter zu
machen. Siehe Folien von Knoll zu Beziehen auf http://www6.in.tum.de/info2/index.html
Herunterladen