2-up - ETH Zürich
2-up - ETH Zürich
2-up - ETH Zürich
Sie wollen auch ein ePaper? Erhöhen Sie die Reichweite Ihrer Titel.
YUMPU macht aus Druck-PDFs automatisch weboptimierte ePaper, die Google liebt.
Informatik II<br />
(Studiengang Informationstechnologie und Elektrotechnik)<br />
Vorlesung FS 2013<br />
Friedemann Mattern<br />
Departement Informatik, <strong>ETH</strong> <strong>Zürich</strong><br />
Version vom 24. Januar 2013<br />
© F. Mattern, 2013<br />
1<br />
Inhalt der Vorlesung<br />
1. Ein Algorithmus und seine<br />
Implementierung in Java<br />
2. Java: Elementare Aspekte<br />
3. Klassen und Referenzen<br />
4. Syntaxanalyse und Compiler<br />
5. Pakete in Java<br />
6. Objektorientierung<br />
7. Exceptions<br />
8. Binärbäume als<br />
Zeigergeflechte<br />
9. Binärsuche<br />
10. Backtracking<br />
11. Spielbäume<br />
12. Rekursives Problemlösen<br />
13. Komplexität von<br />
Algorithmen<br />
14. Simulation<br />
15. Heaps<br />
16. Parallele Prozesse<br />
und Threads<br />
Relevant für die Prüfung ist der gesamte Inhalt der Vorlesung<br />
und der Übungen, nicht alleine diese Präsentationskopie!<br />
2<br />
1
Wer sind wir?<br />
Fachgebiet „Verteilte Systeme“<br />
im Departement Informatik,<br />
Institut für Pervasive Computing<br />
Friedemann<br />
Mattern<br />
Simon Mayer<br />
Ansprechperson für<br />
organisatorische Aspekte<br />
(z.B. Übungsbetrieb)<br />
5<br />
Mit was beschäftigen wir uns sonst?<br />
Interaction<br />
Smart Environment<br />
Communication<br />
Context Awareness<br />
Internet of Things<br />
Cyber-Physical Systems<br />
Web Technologies<br />
Service Discovery<br />
Sensor Networks<br />
Privacy<br />
Ubiquitous Computing<br />
Security<br />
Augmented Reality<br />
Mehr zu uns:<br />
www.vs.inf.ethz.ch<br />
Social Impact<br />
Web of Things<br />
Smart Energy<br />
9<br />
2
Um was geht es in der Vorlesung?<br />
• Algorithmen und Datenstrukturen<br />
• Programmieren<br />
• Java, Objektorientierung<br />
• Allgemeine Grundlagen<br />
• Abstraktion, Modellbildung,<br />
Formalisierung,…<br />
• Teilaspekte der<br />
„praktischen Informatik“<br />
• Simulation, Multitasking,…<br />
11<br />
Themen<br />
1. Ein Algorithmus und seine<br />
Implementierung in Java<br />
2. Java: Elementare Aspekte<br />
3. Klassen und Referenzen<br />
4. Syntaxanalyse und Compiler<br />
5. Pakete in Java<br />
6. Objektorientierung<br />
7. Exceptions<br />
8. Binärbäume als<br />
Zeigergeflechte<br />
9. Binärsuche<br />
10. Backtracking<br />
11. Spielbäume<br />
12. Rekursives Problemlösen<br />
13. Komplexität von<br />
Algorithmen<br />
14. Simulation<br />
15. Heaps<br />
16. Parallele Prozesse<br />
und Threads<br />
12<br />
3
Um was geht es NICHT in der Vorlesung?<br />
• Die Vorlesung ist KEIN<br />
Java-Programmierkurs!<br />
• Es wird vorausgesetzt,<br />
dass man gut in C++<br />
programmieren kann<br />
• Vorlesung „Informatik I“<br />
13<br />
Organisatorisches<br />
• Bestellte Folienkopien auf Papier in der Pause<br />
• Übungsbetrieb beginnt in der 2. Vorlesungswoche!<br />
• Anmeldung für Übungsgr<strong>up</strong>pen via mystudies-Applikation baldmöglichst<br />
(d.h., man muss sich für die Vorlesung eingeschrieben haben!)<br />
• Heute: 14:00-16:00 Uhr, CAB G11: Java-Einführung<br />
(optional, aber empfohlen!)<br />
• Aufgabenblatt jede Woche<br />
• Testat: mind. insgesamt 75% bearbeitete Aufgaben und 50% Punkte<br />
• Bearbeitung in Zweiergr<strong>up</strong>pen<br />
• Prüfung (schriftlich) zusammen mit Informatik I<br />
• www.vs.inf.ethz.ch/edu/I2/ für weitere Informationen<br />
14<br />
4
Materialien zur Vorlesung<br />
• Folienkopien<br />
• in vorläufiger Version (gedruckt / pdf)<br />
• evtl. Nachträge später auf Web-Seite<br />
• Lehrbuch<br />
• Mark Allen Weiss: Data Structures & Problem<br />
Solving Using Java, Addison Wesley, 4. Auflage,<br />
2010, ISBN 0-321-54140-5, Fr. 160,00<br />
• Hörsaalverkauf (Fr. 135,- Barzahlung mit Legi;<br />
2. Semesterwoche, Mi 27. Feb., 9:00 Uhr)<br />
• Zu Java gibt es sehr viele Bücher (diverser Qualität), gut z.B.:<br />
• Christian Ullenboom: Java ist auch eine Insel, Galileo Computing,<br />
10. Auflage, 2011, ISBN 978-3-8362-1802-3 (auch online!)<br />
• Ken Arnold, James Gosling, David Holmes: The Java Programming<br />
Language, Addison-Wesley, 4th ed., 2005<br />
(evtl. in deutscher Übersetzung), auch Online-Tutorial im Web<br />
16<br />
FAQ<br />
• Wieso werden in Informatik I / Informatik II<br />
verschiedene Programmiersprachen verwendet?<br />
• Was unterscheidet Teil 2 von Teil 1 der Vorlesung?<br />
• Wieso benutzen wir in der Vorlesung so selten Computer?<br />
• Wieso gibt es keine Musterlösungen?<br />
• Wären die Konzerte von Beethoven<br />
nicht viel weniger chaotisch, wenn erst<br />
das Klavier alleine seinen Teil spielen<br />
würde und dann der Reihe nach...?<br />
17<br />
5
Der rote Faden zur Orientierung<br />
18<br />
Konzepte<br />
Die „Komposition“<br />
der Vorlesung<br />
Java<br />
Korrektheitsnachweis (Invarianten und vollst. Indukt.)<br />
Robustes Programmieren<br />
Bäume<br />
Syntaxdiagramme<br />
Rekursiver Abstieg<br />
Infix, Postfix, Operatorbaum, Stack<br />
Codegenerierung, Compiler, Interpreter<br />
Verzahnte und verwobene Einführung konzeptioneller<br />
Aspekte und programmiersprachlicher Konstrukte<br />
Java: C-Level<br />
Java-Klassen als Datenstrukturen<br />
Dynamische Klassen und Referenzen<br />
Der rote Faden<br />
Java-VM als Bytecode-Interpreter<br />
Pakete<br />
Klassenhierarchie<br />
Polymorphie<br />
Suchbäume, Sortieren<br />
Backtracking<br />
Spieltheorie, Minimax, AlphaBeta<br />
Rekursives Problemlösen<br />
Effizienz, O-Notation<br />
Simulation (zeitgesteuert, ereignisgesteuert)<br />
Heap, Heapsort<br />
Pseudoparallelität<br />
Abstrakte Klassen<br />
Exceptions<br />
Programmbeispiele dienen gleichzeitig der<br />
Einführung programmiersprachlicher Konstrukte<br />
und der Illustration von Informatikkonzepten<br />
Threads in Java<br />
20<br />
6
Confusion? Clarity?<br />
Sometimes … effective teaching<br />
involves the deliberate inducing<br />
of confusion, the withholding of<br />
clarity, the refusal to provide<br />
answers...<br />
Sometimes that disappointment,<br />
while extremely annoying at the<br />
moment, is the sign that you've<br />
just been the beneficiary of a<br />
great course, although you may<br />
not realize it for decades...<br />
Stanley Needless Fishto say, that kind of<br />
teaching is unlikely to receive<br />
high marks on a questionnaire.<br />
Stanley Fish<br />
The New York Times (June 29, 2010) http://opinionator.blogs.nytimes.com/2010/06/21/deep-in-the-hear-of-texas/<br />
21<br />
7
Laptops & Co.?<br />
• Notizen machen<br />
• Web?<br />
• Google,<br />
Wikipedia,…<br />
• Kommunikation?<br />
• Facebook,<br />
E-Mail,<br />
Twitter,…<br />
• Games?<br />
• …<br />
25<br />
Laptops & Co.?<br />
• Laptop-Nutzer künftig bitte nur hier sitzen<br />
Auch in der<br />
Toleranzzone<br />
bitte nur Vorlesungsbezogenes<br />
26<br />
8
Auszug aus dem <strong>ETH</strong>-Merkblatt für<br />
Studierende zum Thema Plagiate<br />
Ein Plagiat (teilweise Übernahme eines fremden Werks ohne Angabe von Quelle / Urheber)<br />
ist ein Disziplinarverstoss und muss umgehend der Rektorin gemeldet werden.<br />
a) Der Verfasser reicht ein Werk, das von einer anderen Person auf Auftrag erstellt wurde<br />
(„Ghostwriter“), unter seinem Namen ein.<br />
b) Der Verfasser reicht ein fremdes Werk unter seinem Namen ein.<br />
c) Der Verfasser reicht (Teile einer) Arbeit zu verschiedenen Prüfungs- oder Seminaranlässen ein.<br />
d) Der Verfasser übersetzt fremdsprachige Textteile und gibt sie als eigene aus.<br />
e) Der Verfasser übernimmt Textteile aus einem fremden Werk, ohne die Quelle mit einem Zitat<br />
kenntlich zu machen. Dazu gehört namentlich auch das Verwenden von Textteilen aus<br />
dem Internet ohne Quellenangabe.<br />
f) Der Verfasser übernimmt Textteile aus einem fremden Werk und nimmt leichte<br />
Textanpassungen und -umstellungen vor […], ohne die Quelle kenntlich zu machen.<br />
g) Der Verfasser übernimmt Textteile aus einem fremden Werk, paraphrasiert sie allenfalls und<br />
zitiert die entsprechende Quelle zwar, aber zitiert nicht im Kontext der übernommenen<br />
Textteile (Beispiel: Verstecken der plagiierten Quelle in einer Fussnote am Ende der Arbeit).<br />
Wissenschaftliches Ethos verlangt, dass geistige Schöpfungen, Ideen, Theorien anderer Personen<br />
kenntlich gemacht werden, auch wenn sie im Text bloss sinngemäss wiedergegeben sind.<br />
28<br />
Wissenschaftliches Ethos auch bei den Übungen<br />
• Alle Quellen / Ko-Autoren zitieren<br />
• Web, Bücher<br />
• Studierende anderer Übungsgr<strong>up</strong>pen<br />
(z.B. bei Lerngr<strong>up</strong>pen)<br />
• Studierende früherer Jahrgängen<br />
• …<br />
Coders’ top excuses<br />
• Nur Eigenbeitrag wird bewertet<br />
• Unehrliches Handeln führt zumindest<br />
zur Annullierung des gesamten<br />
Übungsblatts ( 0 Punkte)<br />
http://geekandpoke.com<br />
30<br />
9
36<br />
49<br />
70<br />
1.<br />
21<br />
07<br />
<br />
0<br />
24<br />
58<br />
Ein <br />
Algorithmus<br />
65<br />
<br />
und<br />
35<br />
seine<br />
Implementierung <br />
8<br />
in 93<br />
Java<br />
12<br />
84<br />
«Ein Algorithmus ist eine aus endlich vielen Schritten<br />
bestehende eindeutige 93<br />
Handlungsvorschrift 76<br />
zur Lösung<br />
eines Problems oder einer Klasse von Problemen.»<br />
49<br />
67<br />
[u.a.: Wikipedia]<br />
51<br />
12<br />
Einer der ältesten Algorithmen – das<br />
„altägyptische Multiplikationsverfahren“<br />
Grundidee bereits im Papyrus<br />
Rhind (16. Jh. v. Chr.); wird<br />
auch als abessinische oder<br />
russische Bauernmethode<br />
bezeichnet<br />
«Wenn ihr nur d<strong>up</strong>lieren und<br />
halbieren könnet, so könnet<br />
ihr das übrige ohne das<br />
Eins mal Eins multipliciren.»<br />
Christian von Wolff, 1679-1754<br />
Beispiel: 9 × 5 beziehungsweise 5 × 9:<br />
„Der Multiplikand wird ständig<br />
verdoppelt, der Multiplikator<br />
(unter Wegwerfen des<br />
Restes) ständig halbiert;<br />
aufaddiert werden sogleich<br />
oder schlussendlich – ganz<br />
wie man will – diejenigen<br />
Vielfachen, bei denen in der<br />
Multiplikatorhalbierung ein<br />
Rest weggeworfen wurde.“<br />
-- F. L. Bauer<br />
- Verdoppeln (linke Spalte) bzw. ganzzahliges Halbieren (rechte Spalte)<br />
- Zeilen streichen, bei denen rechts eine gerade Zahl steht<br />
- Übrige Zahlen der linken Spalte aufaddieren<br />
33<br />
10
Altägyptisches Multiplizieren<br />
Das Verfahren ist auch unter<br />
„ Abessinische Bauernregel“ bekannt<br />
Nach C. Stanley Ogilvy [„Ethiopian multiplication“, Pentagon, Herbst 1950, S. 17-18]:<br />
Ein Oberst unternahm einmal eine Expedition nach Äthiopien, und irgendwo im Landesinneren ergab sich<br />
die Gelegenheit, acht Ochsen zu kaufen.<br />
“Wir versuchten dies”, so berichtete er, “auf dem ersten Markt, auf den wir stiessen, doch obwohl es dort<br />
Ochsen zu kaufen gab, wusste weder ihr Besitzer noch mein Führer, wie viel Maria-Theresien-Taler dazu<br />
ihren Besitzer wechseln sollten. Da keiner von ihnen rechnen konnte, stritten sie nur miteinander und kamen<br />
nicht von der Stelle. Schliesslich wurde der Priester des Ortes gerufen, da er der einzige war, der mit solchen<br />
Fragen fertigzuwerden imstande war. Der Priester erschien mit einem jungen Diener und ging daran, eine<br />
Reihe von Löchern in den Boden zu graben, jedes etwa in der Grösse einer Teetasse. Die Löcher waren in<br />
zwei parallelen Reihen angeordnet; mein Dolmetscher sagte, sie würden ‘Häuser’ genannt. Ihr Tun umfasste<br />
die ganze Mathematik, die in jener Gegend zur Abwicklung von Geschäften notwendig war, und die einzigen<br />
Voraussetzungen hierfür bestanden darin, zählen sowie mit zwei multiplizieren und dividieren zu können.<br />
Der Diener des Priesters hatte eine mit Kieselsteinen gefüllte Tasche. In das erste Loch der ersten Reihe<br />
legte er acht Kiesel (für jeden Ochsen einen), und 11 in das erste Loch der zweiten Reihe, da jeder Ochse 11<br />
Maria-Theresien-Taler kosten sollte. Mir wurde erklärt, dass die erste Reihe für die Multiplikation mit zwei<br />
verwendet würde: d.h. dass die doppelte Anzahl der Kiesel im ersten Loch in das zweite gelegt würde, und<br />
davon wieder die doppelte Anzahl in das dritte usw. Die zweite Reihe diene für die Division durch zwei: die<br />
halbierte Anzahl von Kieseln im ersten Loch würde in das zweite gelegt, und so weiter, bis im letzten Loch<br />
nur mehr ein Kiesel liege. Brüche würden vernachlässigt.<br />
Dann würden die Löcher der Divisionsreihe geprüft, ob sie eine gerade oder ungerade Anzahl von Kieseln<br />
enthielten. Alle ‘Häuser’ mit einer geraden Anzahl würden als ‘böse’, alle mit eine ungeraden Anzahl als<br />
‘gute’ bezeichnet. Aus allen ‘bösen Häusern’ würden die Kiesel herausgenommen und nicht gezählt. Die in<br />
den verbleibenden Löchern der Multiplikationsreihe enthaltenen Kiesel würden dann gezählt, und die Summe<br />
lieferte das Ergebnis.” Der Oberst staunte darüber, dass dieses Multiplikationssystem immer das richtige<br />
Ergebnis liefert, obwohl beim Halbieren einer ungeraden Zahl ein Fehler begangen wird.<br />
34<br />
Ethiopian Multiplication<br />
C. Stanley Ogilvy, „Ethiopian multiplication“, Pentagon, Fall 1950, pp. 17-18:<br />
Col. L. B. Roberts, now with the Merritt-Chapman & Scott Co. in New York, and formerly a Colonel of the U.S.<br />
Army Engineers, gives an amusing account of what he calls “Mathematics a la Ethiopea.” During an expedition<br />
whichheoncemadeintotheinteriorofthatcountryforthe purpose of finding the headwaters of the Blue Nile,<br />
his party had occasion to purchase eight bulls.<br />
“This we attempted to do,” he writes, “at the first market place we came to, but although there were bulls for<br />
sale there, neither the owner of the stock nor my headman knew how many Maria Theresa dollars should change<br />
hands. As neither could do simple arithmetic, they just stood and yelled at each other, getting nowhere. Finally<br />
a call was put in for the local priest, as he was the only one who could handle questions like this.<br />
“The priest and his boy helper arrived and began to dig a series of holes in the ground, each about the size of<br />
a tea c<strong>up</strong>. These holes were ranged in two parallel columns; my interpreter said they were called houses. What<br />
they were about to do covered the entire range of mathematics necessary to transact business in this area, and<br />
the only requirement was ability to count, and to multiply by and divide by two.<br />
“The priest's boy had a bag full of little pebbles. Into the first c<strong>up</strong> of the first column he put eight stones (one<br />
for each bull), and eleven into the first c<strong>up</strong> of the second column, since each bull was to cost ($MT) 11. It was<br />
explained to me that in this way of doing business, the first column of houses is used for multiplying by two; that<br />
is, twice the number of pebbles in the first c<strong>up</strong> are placed in the second, then twice that number in the third,<br />
and so on. The second column is for dividing by two: half the number of pebbles in the first c<strong>up</strong> are placed in<br />
the second, and so on down until there is one pebble in the last c<strong>up</strong>. Fractions are discarded.<br />
“The division column is then examined for odd or even numbers of pebbles in the c<strong>up</strong>s.” (The colonel makes no<br />
comment on the fact that the comparatively subtle notion of parity is evidently within the grasp of these primitive<br />
mathematicians.) “All even houses are considered to be evil ones, all odd houses good. Whenever an evil house<br />
is discovered, the pebbles are thrown out and not counted. All pebbles left in the remaining c<strong>up</strong>s of the multiplication<br />
column are then counted. The sum of them is the answer.”<br />
35<br />
11
Vorteile der altägyptischen<br />
Multiplikationsmethode<br />
• Beschreibung ist kurz<br />
• Einfach in der Anwendung<br />
• Effizient für Computer im Dualsystem<br />
• Verdoppeln → „left shift“<br />
0 0 1 0 1<br />
0 1 0 1 0<br />
• Halbieren → „right shift“<br />
0 0 1 0 1<br />
0 0 0 1 0<br />
(„ganzzahlig“, d.h.<br />
abgerundet)<br />
• Algorithmus wird daher in der „arithmetic<br />
logic unit“ (ALU) einiger CPUs verwendet<br />
Grundidee bereits im Papyrus Rhind<br />
(16. Jh. v. Chr.). Der Papyrus Rhind<br />
ist eine altägyptische, auf Papyrus<br />
verfasste Abhandlung zu verschiedenen<br />
mathematischen Themen, die<br />
wir heute als Arithmetik, Algebra,<br />
Geometrie, Trigonometrie und Bruchrechnung<br />
bezeichnen.<br />
36<br />
Historische Notiz<br />
Gottfried Wilhelm Leibniz<br />
Explication de l'Arithmétique<br />
Binaire, Academie Royale<br />
des Sciences, Paris, 1703<br />
37<br />
12
Fragen zur altägyptischen<br />
Multiplikationsmethode<br />
• Funktioniert das immer?<br />
• Was heisst „immer“? (z.B. negative Zahlen?)<br />
• Wenn nicht: wann?<br />
• Warum funktioniert es für alle (?) natürlichen Zahlen?<br />
• Wie beweist man die Korrektheit?<br />
• Ist das besser als die „Schulmethode“?<br />
• Was heisst „gut“?<br />
• Lässt sich Güte linear anordnen?<br />
• Wie schreibt man das Verfahren unmissverständlich auf?<br />
• In welcher Notation / Sprache?<br />
Kernaspekte der Informatik!<br />
38<br />
Wieso funktioniert die Methode?<br />
• Beobachtung bei<br />
a × b = 3 × 18:<br />
a<br />
3<br />
6<br />
12<br />
24<br />
48<br />
54<br />
b<br />
18<br />
9<br />
4<br />
2<br />
1<br />
Die erste Zeile wird<br />
sowieso gestrichen<br />
a<br />
6<br />
Gleiches Ergebnis,<br />
als hätte man 6 × 9 12<br />
(statt 3 × 18) berechnet<br />
24<br />
48<br />
Denkübung: Was geschieht,<br />
wenn b eine Zweierpotenz ist?<br />
54<br />
b<br />
9<br />
4<br />
2<br />
1<br />
• Also: a × b wird auf<br />
2a × b/2 zurückgeführt,<br />
falls b gerade<br />
• Das ist offenbar richtig<br />
(wie jeder weiss)<br />
• Bei geradem b darf<br />
man die erste Zeile<br />
also einfach streichen<br />
• Aber bei ungeradem b?<br />
39<br />
13
Ungerade Multiplikatoren<br />
• Beispiel a x b = 6 x 9:<br />
a<br />
6<br />
12<br />
24<br />
48<br />
54<br />
b<br />
9<br />
4<br />
2<br />
1<br />
Würde man die erste<br />
Zeile einfach streichen,<br />
dann würde am Ende<br />
ein Mal die 6 (d.h. der<br />
Multiplikand a) fehlen<br />
+ Korrektur der<br />
„vergessenen“<br />
6 liefert 54!<br />
a<br />
12<br />
24<br />
48<br />
48<br />
b<br />
4<br />
2<br />
1<br />
• Also: Wenn b ungerade<br />
ist, muss man a zum<br />
Multiplikationsergebnis<br />
(des in der 2. Zeile spezifizierten<br />
Multiplikationsproblems)<br />
hinzuaddieren<br />
• Rechtfertigung:<br />
a × b = a + (2a × (b-1)/2)<br />
Wir haben also 6 × 9 durch 12 × 4 „approximiert“<br />
Bzw. durch 6 × 8 (indem 9 zu 8 „abgerundet“ wurde)<br />
40<br />
Problemreduktion<br />
• Es gilt a × b =<br />
falls b gerade<br />
falls b ungerade<br />
• Was nützt diese leicht zu beweisende Eigenschaft?<br />
Wenn D<strong>up</strong>lizieren und Halbieren triviale Operationen<br />
sind, dann kann die Multiplikation zweier Zahlen auf<br />
ein „einfacheres“ Problem zurückgeführt werden<br />
- 12 × 4 ist „einfacher“ als 6 × 9, da mit 4<br />
zu multiplizieren einfacher ist als mit 9<br />
- „Einfacher“ heisst näher bei der 1:<br />
mit 1 zu multiplizieren, ist ganz einfach!<br />
41<br />
14
Endlose Reduktion?<br />
• Wir müssen aber noch festlegen, wann man mit der<br />
Problemreduktion am Ende ist<br />
• Eine endlose Reduktion ist schliesslich nicht praktikabel<br />
• Wir hören auf, wenn das Problem (hoffentlich) trivial geworden ist<br />
• In diesem Fall, wenn b = 1 ist<br />
• Denkübung: Wieso nicht bei b = 0 (wäre doch noch einfacher!)?<br />
falls b = 1<br />
• Wir schreiben daher: a × b =<br />
falls b gerade<br />
sonst<br />
42<br />
Rekursion<br />
To understand recursion, one<br />
must first understand recursion<br />
• Wir haben ein Problem auf eine einfachere „Instanz“<br />
des gleichen Problems zurückgeführt („Rekursion“)<br />
• Im Beispiel sieht das etwa so aus:<br />
54<br />
-um 6 9 zu berechnen: 54<br />
berechne 12 4 und addiere 6<br />
-um 12 4 zu berechnen:<br />
berechne 24 2<br />
48<br />
-um 24 2 zu berechnen:<br />
berechne 48 1<br />
48<br />
-um 48 1 zu berechnen:<br />
fertig, das ist 48<br />
48<br />
Ein Multiplikationsalgorithmus<br />
könnte als rekursive Funktion f<br />
daher so definiert werden:<br />
Denkübung: Mathematisch exakte<br />
Rechtfertigung für Korrektheit?<br />
(Und für welchen Definitionsbereich?)<br />
43<br />
15
Der Algorithmus in Java<br />
Das interessiert<br />
uns jetzt nicht<br />
Die Methode<br />
heisst „f“<br />
Das Ergebnis und die beiden<br />
Parameter sind ganze Zahlen<br />
static int f(int a, int b){<br />
system.out.println(a + "<br />
Zugehörige schliessende<br />
Klammer „}“ am Ende<br />
" + b);<br />
if (b == 1) return a;<br />
if (b%2 == 0) return f(a+a, b/2);<br />
else return a + f(a+a, (b-1)/2);<br />
} Liefert der Rest bei der<br />
ganzzahligen Division<br />
Jedes Mal, wenn die Methode f<br />
„aufgerufen“ wird, schreibt sie<br />
die Parameter auf das Display<br />
Denkübung: Kann man das Schlüsselwort „else“ hier auch weglassen?<br />
44<br />
Ein ganzes Java-„Programm“<br />
In Java ist immer alles in Klassen eingepackt;<br />
unsere Klasse haben wir „Mult“ genannt<br />
class Mult {<br />
public static void main(String args[]) {<br />
int i = 5, j = 9; Zwei Variablen für ganzahlige<br />
Werte (hier 5 bzw. 9)<br />
system.out.println<br />
(i + " mal " + j + " ist " + f(i,j));<br />
} Das Ende der<br />
Methode „main“ Aufruf der Methode „f“<br />
static int f(int a, int b){<br />
system.out.println(a + " " + b);<br />
if (b == 1) return a;<br />
if (b%2 == 0) return f(a+a, b/2);<br />
else return a + f(a+a, b/2);<br />
}<br />
}<br />
Das Ende der Klasse<br />
Die Methode<br />
„f“ kennen<br />
wir schon<br />
Hier steht jetzt aber b/2 statt<br />
(b-1)/2: wieso ist das korrekt?<br />
Wir brauchen noch ein<br />
„Ha<strong>up</strong>tprogramm“, das<br />
die Methode f aufruft<br />
und das Ergebnis ausgibt<br />
- diese Methode, die<br />
alles startet, heisst<br />
„main“<br />
- um die anderen Dinge<br />
in dieser Zeile („public“<br />
etc.) kümmern wir uns<br />
nicht; man schreibe<br />
das zunächst immer<br />
exakt so hin<br />
45<br />
16
Ausführen des Java-Programms<br />
Angenommen, wir haben<br />
unter Unix oder Linux<br />
unser Programm in einer<br />
Datei Mult.java stehen<br />
- Dateien mit Java-<br />
Quelltext sollten immer<br />
den Suffix .java haben<br />
- bei einer einzigen<br />
Klasse sollte die Datei<br />
wie die Klasse heissen<br />
46<br />
Illegale Eingaben? Terminierung?<br />
• Was würde geschehen,<br />
wenn wir diese Zeile<br />
vergessen hätten?<br />
static int f(int a, int b){<br />
system.out.println(a + " " + b);<br />
if (b == 1) return a;<br />
if (b%2 == 0) return f(a+a, b/2);<br />
else return a + f(a+a, b/2);<br />
}<br />
• Oder: Woher wissen wir, dass stets b schliesslich 1 wird?<br />
• Was geschieht bei den Aufrufen f(1,0), f(0,1), f(0,5) ?<br />
• Und was bei negativen Zahlen?<br />
47<br />
17
Ein Experiment: f(1,0)<br />
1 0<br />
2 0<br />
4 0<br />
8 0<br />
16 0<br />
32 0<br />
64 0<br />
128 0<br />
256 0<br />
512 0<br />
1024 0<br />
2048 0<br />
4096 0<br />
8192 0<br />
16384 0<br />
32768 0<br />
65536 0<br />
131072 0<br />
262144 0<br />
524288 0<br />
1048576 0<br />
2097152 0<br />
4194304 0<br />
8388608 0<br />
16777216 0<br />
33554432 0<br />
67108864 0<br />
134217728 0<br />
268435456 0<br />
536870912 0<br />
1073741824 0<br />
-2147483648 0<br />
0 0<br />
0<br />
...<br />
0 ?<br />
java.lang.StackOverflowError<br />
48<br />
4. Juni 1996: „An Overflow Occurred“<br />
Historische Notiz<br />
Am 4. Juni 1996 misslang der erste Flug einer Ariane 5-Rakete. Sie explodierte<br />
nur 40 Sekunden nach dem Start in einer Höhe von ca. 3700 m.<br />
49<br />
18
An Overflow Occurred...<br />
Fri, 2 Aug 1996 – The causes of the Ariane 5 rocket crash (summary):<br />
• An overflow occurred in the Inertial Reference System (SRI) computer<br />
when converting a 64-bit floating point to 16-bit signed integer value.<br />
• There was no error handler for that specific overflow. The default<br />
handler shut down the SRI unit.<br />
• The standby SRI unit had previously shut itself down for the same<br />
reason. The hot SRI and the standby were running the same software.<br />
• The shutdown caused the SRI to output a memory dump on the bus.<br />
The main computer interpreted the memory dump as flight data, causing<br />
such a violent trajectory correction that the rocket disintegrated.<br />
• The program that failed was a pre-flight program, and should not have<br />
been running during the flight.<br />
50<br />
An Overflow Occurred...<br />
• “The investigation team concluded that the designers of<br />
the computer system put in protections against hardware<br />
faults but did not take into account software faults.”<br />
• Das gesamte Ariane-Programm wurde dadurch<br />
eineinhalb Jahre verzögert; dies kostete (die<br />
Steuerzahler...) schätzungsweise 10 Milliarden Euro<br />
Vgl. dazu: J. Jezequel, B. Meyer: “Design by Contract:<br />
The Lessons of Ariane”, Computer, Jan. 1997, 129-130.<br />
51<br />
19
Korrektheit von f(a,b) = a × b<br />
• Wir wollen zeigen, dass die Funktion<br />
, 1<br />
f(a,b) = <br />
2, /2 , <br />
2, <br />
<br />
, <br />
für alle a, b ∈ G + das Produkt von a und b berechnet.<br />
Wir beschränken also ganz bewusst den Definitionsbereich<br />
der Funktion (keine 0, keine negativen Zahlen,<br />
keine rationalen Zahlen bzw. Gleitpunktzahlen...)<br />
52<br />
Korrektheit des Java-Programms?<br />
• Damit wäre auch gezeigt, dass das Programmstück<br />
static int f(int a, int b){<br />
if (b == 1) return a;<br />
if (b%2 == 0) return f(2*a, b/2);<br />
else return a + f(2*a, (b-1)/2);<br />
}<br />
das erwartet tut.<br />
• Aber…<br />
• Woher wissen wir eigentlich, dass das Programm der oben<br />
definierten Funktion genau entspricht? (→ Semantik)<br />
• Wie ist eigentlich das „sonst“ zu interpretieren? Als Alternative zu<br />
„gerade“ (also: „ungerade“) oder auch zu b=1?<br />
• Müsste nicht zwischen den beiden if-Zeilen ein „else“ stehen?<br />
53<br />
20
Korrektheit von f induktiv<br />
• Beha<strong>up</strong>tung: ∀ a, b ∈ G + : f(a,b) = a × b<br />
, 1<br />
f(a,b) = <br />
2, /2 , <br />
2, <br />
<br />
, <br />
• Beweis induktiv über b:<br />
• b = 1:<br />
∀ a ∈ G + : f(a,1) = a = a × 1 (gilt offensichtlich nach der Def. von f, Fall 1)<br />
• b = n+1, mit der Induktionsannahme ∀ a ∈ G + , ∀ b ∈ {1,...,n}: f(a,b) = a×b:<br />
a) Sei b gerade: Es gilt<br />
f(a,b) = f(2a,b/2) [nach Definition, Fall 2]<br />
= 2a × b/2 [wg. Induktionsannahme, da b/2 ∈ {1,...,n}]<br />
= a × b.<br />
b) Sei b ungerade (und ≠1): Es gilt<br />
f(a,b) = a + f(2a, (b-1)/2) [nach Definition, Fall 3]<br />
= a + 2a × (b-1)/2 [wg. Induktionsannahme da (b-1)/2 ∈ {1,...,n}]<br />
= a + a × (b-1)<br />
= a × b.<br />
Aber wieso eigentlich?<br />
54<br />
Denkübungen zum Induktionsbeweis<br />
• Darf a (im Beweis) auch eine negative ganze Zahl sein?<br />
• Hätte man im Beweis nicht auch den Fall b=0 mit<br />
einschliessen können?<br />
• Augustus De Morgan (1806 - 1871): Induction<br />
Published inThe Penny Cyclopedia of the Society for<br />
the Diffusion of Useful Knowledge, Vol. 12., 1838<br />
Historische Notiz<br />
INDUCTION (Mathematics). The method of induction, in the sense in which the word is used in natural philosophy, is not known in pure<br />
mathematics. There certainly are instances in which a general proposition is proved by a collection of the demonstrations of different cases, which<br />
may remind the investigator of the inductive process, or the collection of the general from the particular. Such instances however must not be<br />
taken as permanent, for it usually happens that a general demonstration is discovered as soon as attention is turned to the subject.<br />
There is however one particular method of proceeding which is extremely common in mathematical reasoning, and to which we propose to give<br />
the name of successive induction. It has the main character of induction in physics, because it is really the collection of a general truth from a<br />
demonstration which implies the examination of every particular case; but it differs from the process of physics inasmuch as each case depends<br />
<strong>up</strong>on one which precedes. Substituting however demonstration for observation, the mathematical process bears an analogy to the experimental<br />
one, which, in our opinion, is a sufficient justification of the term ‘successive induction.’ A co<strong>up</strong>le of instances of the method will enable the<br />
mathematical reader to recognise a mode of investigation with which he is already familiar.<br />
55<br />
21
Ausnahmen (Exceptions)<br />
• Ausnahmen sind Fehlerereignisse<br />
• Werden oft vom System ausgelöst<br />
• Können aber auch explizit im Programm ausgelöst werden<br />
• Können abgefangen und behandelt werden („catch“)<br />
• Fehlertypen sind hierarchisch gegliedert<br />
(z.B. Exception ⊃ RuntimeException ⊃ ArithmeticException)<br />
• Strukturierung durch „try“ und „catch“:<br />
Z.B. StackOverflowError<br />
try {<br />
// Hier stehen Anweisungen, bei denen<br />
// eine Fehlerbedingung eintreten kann<br />
} catch (Fehlertyp1) {<br />
// "Behandlung" dieses Fehlertyps;<br />
} catch (Fehlertyp2) {<br />
// "Behandlung" dieses Fehlertyps;<br />
}<br />
57<br />
Abfangen einer Exception<br />
static int f(int a, int b) {<br />
if (b == 1) return a;<br />
try<br />
{<br />
if (b%2 == 0) return f(2*a, b/2);<br />
else return a + f(2*a, b/2);<br />
}<br />
}<br />
catch (StackOverflowError e)<br />
{<br />
...<br />
System.out.println("..."...a...b...);<br />
System.exit(1);<br />
}<br />
…<br />
Hier erwarten wir keine Fehler,<br />
daher ausserhalb des try-Blocks<br />
e könnte man benutzen,<br />
um mehr über den<br />
Fehler zu erfahren<br />
Notausstieg aus dem Programm<br />
(könnten wir statt dessen etwas sinnvolleres tun?)<br />
Zu Exceptions siehe Buch<br />
von M. A. Weiss, Kap. 2.5<br />
58<br />
22
Was geschah eigentlich bei f(1,0)?<br />
1 0<br />
2 0<br />
4 0<br />
8 0<br />
16 0<br />
32 0<br />
64 0<br />
128 0<br />
256 0<br />
512 0<br />
1024 0<br />
2048 0<br />
4096 0<br />
8192 0<br />
16384 0<br />
32768 0<br />
65536 0<br />
131072 0<br />
262144 0<br />
524288 0<br />
1048576 0<br />
2097152 0<br />
4194304 0<br />
8388608 0<br />
16777216 0<br />
33554432 0<br />
67108864 0<br />
134217728 0<br />
268435456 0<br />
536870912 0<br />
1073741824 0<br />
-2147483648 0<br />
0 0<br />
0<br />
...<br />
0<br />
?<br />
java.lang.StackOverflowError<br />
59<br />
Arithmetischer Überlauf<br />
• Eigentlich trat in f (bei der Multiplikation mit b=0)<br />
schon lange vor dem Stack-Überlauf ein Fehler auf:<br />
-1073741824 0<br />
-2147483648 0<br />
-0 0<br />
-0 0<br />
• Die Sprachspezifikation von Java erläutert das Phänomen:<br />
“If an integer multiplication overflows, then the<br />
result is the low-order bits of the mathematical<br />
product as represented in some sufficiently large<br />
two's-complement format. As a result, if overflow<br />
occurs, then the sign of the result may not be the<br />
same as the sign of the mathematical product of<br />
the two operand values.”<br />
60<br />
23
Arithmetischer Überlauf (2)<br />
• Hier kam es also zu einem „arithmetischen“ Überlauf<br />
• Durch die Multiplikation mit 2 entstand eine Zahl, die grösser ist als<br />
der bei int darstellbare Bereich (-2147483648 bis 2147483647), man<br />
läuft im Zahlenring in den anschliessenden negativen Bereich hinein<br />
• Dies könnte auch bei einem Eingabewert b 0 passieren!<br />
• Diesen Fehlertypus würde man gerne abfangen, aber:<br />
• “Despite the fact that overflow, underflow, or loss<br />
of information may occur, evaluation of a multiplication<br />
operator never throws a run-time exception.”<br />
• Man muss also selbst dafür sorgen, dass das Produkt<br />
aus a und b nicht grösser als 2147483647 wird<br />
• Denkübung: wie?<br />
61<br />
Nochmal zur „altägyptischen Multiplikation“<br />
• Beispiel 3 × 18:<br />
a<br />
3<br />
6<br />
12<br />
24<br />
48<br />
54<br />
b<br />
18<br />
9<br />
4<br />
2<br />
1<br />
Etwa<br />
log 2 b<br />
Zeilen<br />
1) Zeilen streichen, bei denen rechts eine gerade Zahl steht<br />
2) Übrige Zahlen der linken Spalte aufaddieren<br />
• Dieses Aufaddieren realisieren wir jetzt noch auf eine andere Art,<br />
indem wir schritthaltend „akkumulieren“<br />
62<br />
24
a<br />
a<br />
Altägyptische Multiplikation iterativ<br />
static int f(int i, int j)<br />
int a = i;<br />
int b = j;<br />
int z = 0;<br />
while (b > 0) {<br />
z akkumuliert die<br />
Werte der linken<br />
Spalte nicht gestrichener<br />
Zeilen<br />
}<br />
Hier ist b<br />
gerade<br />
if ungerade(b) {<br />
z = z+a;<br />
b = b-1;<br />
}<br />
b = b/2;<br />
a = 2*a;<br />
}<br />
return z;<br />
Zur Übung: Geht b/2 bzw. 2*a<br />
auch mit den Shift-Operatoren<br />
b >>= 1 bzw. a
Effizienz des Multiplikationsalgorithmus<br />
• Frage: Wie lange dauert eine Multiplikation von a und b?<br />
static int f(int a, int b){<br />
if (b == 1) return a;<br />
if (b%2 == 0) return f(a+a, b/2);<br />
else return a + f(a+a, b/2);<br />
} <br />
• Effizienzmass: Gesamtzahl der elementaren Operationen<br />
• Verdoppeln, Halbieren, Test auf „gerade“ bzw. „1“, Addition<br />
• Kommen offenbar pro Aufruf insgesamt nicht mehr als 5 Mal vor<br />
• Aber: Handelt es sich bei „b%2 == 0“ nicht um zwei Operationen statt einer einzigen?<br />
• Antwort: So wie der Test auf „gerade“ hier realisiert ist, werden für „b%2 == 0“<br />
tatsächlich mehr Maschineninstruktionen ausgeführt, als z.B. für „b == 1“ – wir wollen<br />
aber von der konkreten Realisierung abstrahieren; auf Maschineninstruktionsebene lässt<br />
sich ein Test auf „gerade“ jedenfalls einfach und effizient realisieren, indem man testet,<br />
ob das rechteste Bit der Speicherzelle von b eine 0 oder eine 1 ist.<br />
67<br />
Effizienzabschätzung<br />
• Genauere Überlegungen müssten die unterschiedlichen<br />
„Kosten“ der verschiedenen Operationen berücksichtigen<br />
• Verdoppeln ist „billiger“ als Addieren etc.<br />
Kosten ≈ Zeit<br />
• Grosse Zahlen sind „teurer“ als kleine Zahlen<br />
• Wir setzen aber (vereinfachend) alle Operationen als gleich „teuer“ an<br />
• Wesentliches Kriterium: Anzahl der (rekursiven) Aufrufe<br />
• Dies ist nur von b abhängig, nicht von a<br />
if (b == 1) return a;<br />
if (b%2 == 0) return f(a+a, b/2);<br />
else return a + f(a+a, b/2);<br />
• Also: wie viele rekursive Aufrufe gibt es?<br />
• Wie oft kann man b halbieren, bis ein Wert 1 herauskommt?<br />
(durch das Abrunden beim Halbieren ist dies konservativ geschätzt)<br />
• b / (2 x ) ≤ 1 ⇒ x ≥ log 2 b (Logarithmen in der Informatik i.Allg. zur Basis 2)<br />
• Es werden also nicht mehr als 5 log 2 b Operationen benötigt<br />
68<br />
26
Aufwands- / Qualitätsvergleich<br />
• Aus der Schule bekannt: „schriftliche“ Multiplikation<br />
• Wieso lernt man dies statt der altägyptische Methode?<br />
• Wie gut ist diese „Schulmethode“?<br />
• Wie viele (vergleichbar teure) Elementaroperationen?<br />
3 1 2 X 4 1 5<br />
1 2 4 8 +<br />
3 1 2 +<br />
1 5 6 0 =<br />
1 2 9 4 8 0<br />
69<br />
Aufwands- / Qualitätsvergleich (2)<br />
a<br />
b<br />
◊ ◊ ◊ ◊ X ◊ ◊ ◊ ◊ ◊<br />
◊ ◊ ◊ ◊<br />
◊ ◊ ◊ ◊<br />
<br />
log log <br />
log log <br />
≈ 0.3 log 2 b<br />
◊ ◊ ◊ ◊<br />
◊ ◊ ◊ ◊<br />
◊ ◊ ◊ ◊<br />
<br />
• Also N a N b „elementare“<br />
Multiplikationen (kleines<br />
Einmaleins kann man<br />
auswendig!) und fast<br />
ebenso viele Additionen,<br />
also insgesamt etwa<br />
2 (log 10 a) (log 10 b)<br />
elementare Operationen<br />
Wann ist 5 log 2 b besser<br />
als 2 (log 10 a) (log 10 b) ?<br />
• Die multiplikativen Konstanten<br />
5 bzw. 2 sind nicht exakt,<br />
das ist aber nicht so relevant<br />
• Hängt wesentlich von a ab:<br />
2 (log 10 a) (log 10 b) kann (bei<br />
festem b) mit grösserem a<br />
über alle Grenzen wachsen!<br />
70<br />
27
Kosten ≈ Zeit ?<br />
„Jede Minute kostet 33 Franken im Rechenzentrum<br />
der ICS-Corporation in <strong>Zürich</strong>. Das steht<br />
auf Schildern, die der Schichtleiter Martin Kern<br />
überall anbringen lässt. Damit seine Operatoren<br />
ständig vor Augen haben, warum die Computer<br />
Tag und Nacht laufen müssen.“ (Limmat-Verlag,<br />
1977)<br />
Emil Zopfi studierte nach einer Berufslehre zum<br />
Fernmelde- und Elektronikapparatemonteur<br />
Elektrotechnik am Technikum Winterthur und<br />
arbeitete als Programmierer und Systemingenieur<br />
am Institut für Physikalische Chemie der<br />
<strong>ETH</strong> <strong>Zürich</strong>, bei Siemens und IBM.<br />
71<br />
Elementaroperationen<br />
• Wir haben mit der altägyptischen Multiplikationsmethode<br />
die Multiplikation auf die Addition zurückgeführt<br />
• Elementaroperationen waren dabei<br />
• Halbieren<br />
• Verdoppeln<br />
• Test auf gerade / ungerade (bzw. =1)<br />
• Wie könnte man diese Operationen weiter reduzieren?<br />
72<br />
28
Drei “Hilfsfunktionen”<br />
static boolean gerade(int x){<br />
if (x == 0) return true;<br />
return !gerade(x-1);<br />
}<br />
static int verdopple(int x){<br />
if (x == 0) return 0;<br />
return 2 + verdopple(x-1);<br />
}<br />
Der Wertebereich sei G 0<br />
Wieder mittels Rekursion<br />
Datentyp aus den beiden<br />
Werten true und false<br />
Die gleichbenannten formalen<br />
Parameter x haben<br />
nichts miteinander zu tun<br />
(verschiedene Kontexte)<br />
static int halbiere(int x){<br />
if (x == 0) return 0;<br />
if (x == 1) return 0;<br />
return 1 + halbiere(x-2);<br />
}<br />
Was ergibt Halbieren<br />
ungerader Zahlen?<br />
Statt -2 (bzw. +2) könnten wir sogar -1-1 bzw. +1+1<br />
schreiben. Man braucht also eigentlich nur eine<br />
Maschine, die inkrementieren und dekrementieren kann<br />
(sonst keine Arithmetik) und würde z.B. schreiben:<br />
return incr(halbiere(decr(decr(x))))<br />
73<br />
Ein funktionales Programm<br />
• Die Funktion f für die Multiplikation sieht dann so aus:<br />
static int f(int a, int b){<br />
if (b == 0) return 0; // neu: 0 statt 1<br />
if gerade(b) return f(verdopple(a),halbiere(b));<br />
else return add(a, f(verdopple(a),halbiere(b)));<br />
}<br />
Die Methode add<br />
als Übungsaufgabe<br />
• Das ganze erscheint gekünstelt und unnötig aufwendig<br />
• Es soll hier auch nur das Prinzip illustrieren<br />
• Multiplikation geht eben auch im „funktionalen“ Stil!<br />
74<br />
29
Funktionale Programmiersprachen<br />
• Man kommt tatsächlich im Prinzip stets ohne explizite<br />
Zuweisungen und ohne „while“ aus!<br />
• Man könnte gewissermassen jedes Programm auch einfach<br />
in Form einer „mathematischen Funktion“ hinschreiben<br />
• Konsequent umgesetzt ist dies bei funktionalen Programmiersprachen<br />
(z.B. Lisp): im Gegensatz zu „imperativen<br />
Programmiersprachen“ (wie z.B. Java) enthalten diese<br />
• Keine Variablen (d.h. „Speicherzellen“), die im Programmablauf<br />
unterschiedliche Werte zugewiesen bekommen können<br />
• Keine Schleifen – wären auch überflüssig, wenn es keine<br />
Wertzuweisungen (und keine Seiteneffekte) gibt!<br />
75<br />
Apropos Lisp – die altägyptischen Multiplikation<br />
könnte man damit so programmieren:<br />
(defun mult (l r)<br />
(flet ((halbiere (n) (floor n 2))<br />
(verdopple (n) (* n 2))<br />
(gerade-p (n) (zerop (mod n 2))))<br />
(do ((product 0 (if (gerade-p l) product (+ product r)))<br />
(l l (halbiere l))<br />
(r r (verdopple r)))<br />
((zerop l) product))))<br />
Quelle: www.stumble<strong>up</strong>on.com/su/7yMsZ3/<br />
rosettacode.org/wiki/Ethiopian_Multiplication<br />
• Wobei hier „halbiere“, „verdopple“ und „gerade-p“ nicht selbst als<br />
rekursive Funktion definiert sind – was aber einfach möglich wäre<br />
• Dies nur der Kuriosität halber; wir gehen nicht näher darauf ein<br />
76<br />
30
Ist Dekrementieren elementar?<br />
• Kann man einer Maschine, die nur Vorwärtszählen („incr“)<br />
kann, das Rückwärtszählen „per Programm“ beibringen?<br />
• Ja, wir benutzen dazu eine rekursive Hilfsmethode h:<br />
static int h(int x, int y){<br />
if (x == incr(y)) return y;<br />
else return h(x, incr(y));<br />
}<br />
Bei y x terminiert<br />
die Methode nicht;<br />
das ist für unseren<br />
Zeck aber irrelevant<br />
• Ein Aufruf h(5, 4) liefert offenbar 4<br />
• Allgemein: h(u, u-1) liefert u-1<br />
• Der Aufruf h(5, 3) führt zu h(5, 4) in der Rekursion<br />
• Also liefert h(u, 0) schliesslich h(u, u-1) = u-1 (für u > 0)<br />
77<br />
Ist Dekrementieren elementar? (2)<br />
• Wegen h(u,0) = u-1 lässt sich decr (für z ∈ G 0 ) so definieren:<br />
static int decr(int z)<br />
if (z == 0) return 0;<br />
return h(z,0);<br />
}<br />
• Rückwärtszählen lässt sich durch Vorwärtszählen „simulieren“<br />
bzw. implementieren!<br />
• Erstaunlich?<br />
• Fragestellung der theoretischen Informatik: Was muss ein<br />
Computer („in Hardware“) nur mindestens können, damit<br />
er als „Universalrechner“ funktioniert?<br />
78<br />
31
2.<br />
Java<br />
Wir setzen gute Kenntnisse von C++ aus Teil I der Vorlesung voraus!<br />
Buch Mark Weiss „Data Structures & Problem Solving Using Java“ siehe:<br />
- 39-104 (primitive java, strings, arrays, input and output)<br />
79<br />
Java: Datenstrukturen und Algorithmen<br />
• Buch von Mark Allen Weiss: Data Structures &<br />
Problem Solving Using Java, Addison Wesley,<br />
4th edition, 2010, ISBN 0-321-54140-5<br />
• Zur Java-Sprache siehe insbes. Kapitel 1 – 4<br />
• Quellcode der Beispiele:<br />
http://users.cis.fiu.edu/~weiss/dsj4/code/<br />
• Schwerpunkt des Buches liegt allerdings nicht auf Java<br />
selbst, sondern auf Algorithmen und Datenstrukturen<br />
• Alternativ bzw. als Ergänzung:<br />
Adam Drozdek: Data Structures<br />
and Algorithms in Java, Cengage<br />
Learning, 3rd edition, 2008<br />
80<br />
32
Java: Programmiersprache<br />
• Zum Programmieren lernen mit Java ist<br />
eventuell auch geeignet: Michael Goedicke,<br />
Klaus Echtle: Lehrbuch der Programmierung<br />
mit Java, dpunkt-Verlag<br />
• Empfehlenswert auch die Online-Version<br />
des Buches von Guido Krüger, Thomas Stark:<br />
Handbuch der Java-Programmierung<br />
www.javabuch.de/download.html<br />
• Eine Referenz für die Klassenbibliotheken<br />
ist die Java Platform API Specification:<br />
http://java.sun.com/reference/api/index.html<br />
81<br />
Java: Tutorial<br />
• Man konsultiere evtl. auch das Java-Online-Tutorial<br />
http://java.sun.com/docs/books/tutorial/<br />
• Auch als Buch erhältlich:<br />
82<br />
33
Wieso Java statt C++ ?<br />
• C++ und Java bilden eine gemeinsame Sprachfamilie<br />
• Einheitliche Syntax und analoge Semantik<br />
nur Java<br />
Gemeinsamer<br />
Sprachkern<br />
Vorlesungen<br />
Informatik<br />
I und II<br />
nur C++<br />
83<br />
Java: „Removed from C/C++“<br />
• Templates<br />
• Strukturen, union (statt dessen: Klassen / Objekte)<br />
• Zeigerarithmetik, malloc (aber: Arrays und new)<br />
• Funktionen (statt dessen: Methoden)<br />
• Mehrfachvererbung (statt dessen: Interfaces)<br />
• Präprozessor: TYPEDEF, ...<br />
• #DEFINE und const (statt dessen: final)<br />
• Überladen von Operatoren<br />
• goto (statt dessen: break/continue, Exceptions)<br />
• using namespace, #include, .h-Dateien (aber: Pakete)<br />
• Destruktoren, free, delete (aber: Garbage-Collector; finalize)<br />
• Implizite Typkonvertierung<br />
• Friends (aber: „friendly access“ innerhalb von Paketen)<br />
• sizeof x (statt dessen: x.length)<br />
84<br />
34
Neu gegenüber C++ (u.a.)<br />
• Parallelverarbeitung in der Sprache („Threads“)<br />
• Viele vorgefertigte Pakete mit nützlichen Klassen<br />
• …<br />
• Generell: Java ist moderner, konsequenter, mehr „high-level“<br />
und von einigem historischen Ballast von C und C++ befreit<br />
• C++ hat allerdings in den letzten Jahren aufgeholt<br />
und auch einige gute Konzepte von Java „adoptiert“<br />
• C# („C sharp“) ist ebenfalls ein moderneres Element<br />
dieser Sprachfamilie, greift u.a. auch Konzepte der<br />
Sprachen Haskell und Delphi auf<br />
85<br />
Java-Entwicklung und Versionen<br />
• 1995: Version 1.0 (Sun Microsystems: James Gosling und Andere)<br />
• 2000: Java 1.3<br />
• Häufig benutzte Codefragmente werden zur Laufzeit von Bytecode<br />
in Maschinencode übersetzt Leistungssteigerung<br />
• 2002: Java 1.4<br />
• U.a. assertions<br />
Für unsere Zwecke sind die Unterschiede<br />
der Versionen weitgehend irrelevant<br />
• 2004: Java 5.0 (bzw. 1.5 oder „Java 2 Platform Standard Edition 5.0“)<br />
• U.a. generische Typen und Aufzählungen (enum)<br />
• Implizite Umwandlung einfacher Datentypen in Objekte und zurück<br />
• 2006: Java 6.0<br />
• Keine Sprachänderungen, aber Verbesserungen bei Anbindung von<br />
Skriptsprachen, Datenbanken, Web-Applikationen etc.<br />
• 2011: Java 7.0<br />
• Verbesserungen und Erweiterungen bestehender Funktionalitäten<br />
86<br />
35
Plattformunabhängigkeit durch<br />
Bytecode-Interpretation<br />
Kommando „javac“<br />
(z.B. bei Linux)<br />
Ein Java-Programm läuft (prinzipiell)<br />
auf allen gängigen Computern und<br />
Betriebssystemen (PC, Server, Smartphones,<br />
Linux, Windows, Anroid,...)<br />
Bytecode ist die „Maschinensprache“<br />
für eine virtuelle Maschine (VM)<br />
(steht in einer .class-Datei)<br />
87<br />
Die Virtuelle Machine (VM)<br />
• Die VM ist ein Bytecode-Interpreter<br />
• Programmierter Simulator eines abstrakten Prozessors<br />
• Relativ einfach für verschiedene Plattformen realisierbar<br />
• Unter Linux: Start der VM mit dem Kommando „java“<br />
• Effizienzverlust durch Interpretation?<br />
• Evtl. statt dessen Bytecode in Zielsprache (weiter-)übersetzen<br />
(zumindest wiederholt gebrauchte Programmteile „just in time“)<br />
88<br />
36
Ausführung von Applets<br />
Applets sind kleine „mobile“<br />
Java-Applikationen<br />
Der Bytecode-Verifizierer stellt<br />
sicher, dass der Bytecode nicht<br />
so verfälscht ist, dass (über<br />
Pointer, Indexüberschreitungen<br />
bei Arrays...) illegale Zugriffe<br />
möglich sind, die man mit Java<br />
gar nicht programmieren kann<br />
89<br />
Applets im Web-Browser<br />
• Ein Applet kann insbesondere von einem Web-Browser<br />
beim Anzeigen einer Webseite geladen werden:<br />
<br />
<br />
<br />
<br />
Hier sendet der Browser<br />
eine Request-Nachricht<br />
an den Server, das<br />
Applet (über das Netz)<br />
herunterzuladen<br />
90<br />
37
Java-Programmstruktur<br />
• Mit import werden evtl. anderweitig vorhandene<br />
Pakete von Klassen verfügbar gemacht<br />
• Der Klassenkörper enthält<br />
• Instanzen- und Klassenvariablen („Attribute“)<br />
• Benannte Konstanten<br />
• Klassenbezogene („static“) Methoden<br />
Manchmal auch<br />
„Felder“ genannt<br />
91<br />
Java-Programmstruktur (2)<br />
• Methoden übernehmen die Rolle von<br />
Funktionen bzw. Prozeduren anderer Sprachen<br />
• Konstruktoren sind spezielle Methoden (bei<br />
Erzeugen der Klasse automatisch aufgerufen)<br />
• Methoden haben einen Namen und bestehen aus<br />
• Parametern<br />
• Lokalen Variablen<br />
• Anweisungen<br />
92<br />
38
Java-Programmstruktur (3)<br />
• Bei eigenständigen Programmen (d.h. keinen<br />
Applets) muss es eine „main“- Methode geben:<br />
public static void main(String[] args) {<br />
...<br />
...<br />
}<br />
… main(…)<br />
• Jede Klasse kann eine solche main-Methode enthalten;<br />
sie wird bei „Aufruf“ der Klasse ausgeführt<br />
• Z.B. Linux: wenn der entsprechende Klassenname<br />
beim „java“-Kommando genannt wird<br />
• Klassen können getrennt übersetzt werden<br />
• Variablen im Klassenkörper ausserhalb von<br />
Methoden sind global zu allen Methoden der Klasse<br />
93<br />
Einfache Datentypen in Java<br />
Bildquelle: Christian Ullenboom: Java ist auch eine Insel,<br />
Galileo Computing, 8. Auflage, 2009, ISBN 3-8362-1371-4<br />
94<br />
39
Einfache Datentypen in Java<br />
• Integer (ganze Zahlen im 2er-Komplement):<br />
• byte (8 Bits)<br />
• short (16 Bits)<br />
• int (32 Bits)<br />
• long (64 Bits)<br />
Bereits von<br />
C++ bekannt<br />
Bsp. für Literale: 17 , -3914<br />
Range: -2147483648 ... 2147483647<br />
• Gleitpunktzahlen<br />
• float (32 Bits)<br />
• double (64 Bits)<br />
• Zeichen („Unicode“)<br />
• char (16 Bits)<br />
• Wahrheitswerte<br />
• boolean<br />
Bsp. für Literale: 18.0 , -0.18e2 , .341E-2<br />
65536 versch. Werte, dadurch<br />
„internationale“ Zeichensätze möglich<br />
Literale: true , false<br />
Operatoren: &&, | |, !<br />
95<br />
Einfache Datentypen in Java (2)<br />
Quelle: Christian Ullenboom: Java ist auch eine Insel,<br />
Galileo Computing, 8. Auflage, 2009, ISBN 3-8362-1371-4<br />
96<br />
40
Einfache Datentypen in Java (3)<br />
Quelle: http://de.wikipedia.org/wiki/Java-Syntax<br />
97<br />
Konventionen bei der Deklaration von Namen<br />
• Variablen und Methoden beginnen<br />
mit einem Kleinbuchstaben<br />
• int i, j, meinZaehler;<br />
• public ausgabe (…)<br />
• Klassennamen beginnen mit<br />
einem Grossbuchstaben<br />
• class Person {…}<br />
Wir halten uns im<br />
Folgenden aber nicht<br />
immer an diese<br />
Konventionen…<br />
• Benannte Konstanten ganz mit<br />
Grossbuchstaben<br />
• MAX_SIZE<br />
98<br />
41
Arrays<br />
• Arrays sind („mathematisch betrachtet“) endliche Folgen<br />
Grösse, d.h. Anzahl<br />
int [] x; // array of int<br />
der Elemente<br />
x = new int[7]; // Grösse 7 (Indexbereich 0..6)<br />
for (int i=0; i < x.length; i++) x[i]=17;<br />
int [] x = new int[7]; // so ginge es auch<br />
int [] y;<br />
y = x; // y zeigt auf das gleiche Objekt<br />
y[3] = 9; // x[3] ist daher jetzt auch 9<br />
Arrays sind Referenzen auf (Speicher)-<br />
Objekte: Vorsicht bzgl. der Kopiersemantik<br />
(„Aliaseffekt“) und beim<br />
Vergleich zweier Array-Variablen!<br />
99<br />
Arrays (2)<br />
• Mehrdimensionale Arrays:<br />
float [][] matrix = new float [4][4];<br />
matrix[0][3] = 2.71;<br />
• Da Arrays mit „new“ dynamisch erzeugt werden, kann<br />
die Grösse eines Arrays zur Laufzeit festgelegt werden:<br />
int n;<br />
...<br />
n = … // Wert berechnen oder einlesen<br />
int [] tabelle = new int [n];<br />
• Einmal angelegt, kann sich die Grösse aber nicht mehr ändern!<br />
100<br />
42
Typkonversion<br />
• Java ist eine streng typisierte Sprache<br />
• Compiler kann viele Typfehler entdecken<br />
Keine automatische Typkonversion<br />
wie bei C++<br />
• Gelegentlich muss dies jedoch durchbrochen werden<br />
• So geht es nicht ( Fehlermeldung durch Compiler):<br />
int myInt;<br />
float myFloat = -3.14159;<br />
myInt = myFloat;<br />
• Statt dessen explizite Typumwandlung („type cast“):<br />
int myInt;<br />
float myFloat = -3.14159;<br />
myInt = (int)myFloat;<br />
101<br />
Typkonversion (2)<br />
• Umwandlung hin zu einem grösseren Wertebereich<br />
(z.B. int → float) geht allerdings auch implizit<br />
float d = 5 + 3.2;<br />
• Typumwandlung ist gelegentlich<br />
sinnvoll bei Referenzen:<br />
Hund h; Tier fiffi;<br />
...<br />
if (fiffi instanceof Hund)<br />
h = (Hund)fiffi;<br />
Wenn das Tier mit dem Namen „fiffi“ hier ein<br />
Hund ist, dann betrachte fiffi als einen Hund<br />
Später mehr dazu („type cast“ bei Polymorphie)<br />
Das Problem inkompatibler Typen<br />
102<br />
43
Hüllenklassen<br />
• Einfache Datentypen (int, float,...) sind a priori keine<br />
echten Objekte (zu grosser Aufwand ineffizient!)<br />
• Für diese gibt es bei Bedarf sogenannte Hüllenklassen<br />
• Objekte davon können überall dort verwendet werden,<br />
wo eine Objektreferenz verlangt wird<br />
103<br />
Hüllenklassen (2)<br />
• Beispiel<br />
int x = 5; // normaler "int"<br />
Integer iob = x; // Instanz der Klasse "Integer"<br />
if (iob == 5) then ... // sind typkompatibel<br />
• Hüllenklassen bieten einige nützliche Methoden und Attribute<br />
• Vgl. dazu Dokumentationen im Web oder geeignete Java-Bücher<br />
• Z.B. bei Integer:<br />
floatValue ()<br />
toString ()<br />
• Beispiele: float f; … f = iob.floatValue();<br />
104<br />
44
Ein- und Ausgabe<br />
int count = 0;<br />
while (System.in.read() != -1) count++;<br />
System.out.println("Eingabe hat " +<br />
count + "Zeichen.");<br />
• System.in: Standard-Eingabestrom<br />
• System ist eine Klasse mit Schnittstellenmethoden zum<br />
ausführenden System (Betriebssystem, Computer)<br />
• System.in ist der Standard-Eingabestrom (vom Typ InputStream)<br />
• read liest ein einzelnes Zeichen; liefert -1 bei Dateiende<br />
• Es gibt noch einige weitere Methoden (skip, close,...)<br />
• Erst abgeleitete Typen von InputStream enthalten Methoden,<br />
um ganze Zeilen etc. zu lesen (z.B. Klasse DataInputStream)<br />
105<br />
Ein- und Ausgabe (2)<br />
Konkatenation<br />
von strings<br />
int count = 0;<br />
while (System.in.read() != -1) count++;<br />
System.out.println("Eingabe hat " +<br />
count + "Zeichen.");<br />
• System.out: Standard-Ausgabestrom<br />
• print gibt das übergebene Argument (auf einem Display) aus<br />
• println erzeugt zusätzlich danach noch einen Zeilenumbruch („newline“)<br />
• Es können u.a. int, float, string, boolean... ausgegeben werden<br />
106<br />
45
Einlesen von Zahlen – ein Beispiel<br />
import java.io.*<br />
class X {<br />
Der Eingabestrom muss<br />
public static void main(String args[]) beim Aufruf des Konstruktors<br />
angegeben werden<br />
throws java.io.IOException {<br />
int i=0; String zeile;<br />
DataInputStream ein = new DataInputStream(System.in);<br />
}<br />
}<br />
...<br />
}<br />
...<br />
In diesem Paket stehen die<br />
Ein-Ausgabe-Methoden<br />
while(true) {<br />
zeile = ein.readLine();<br />
i = i + Integer.parseInt(zeile);<br />
System.out.println(i);<br />
„parseInt“ ist eine Methode<br />
der Klasse „Integer“, die<br />
einen string in einen int-<br />
Wert konvertiert (analog<br />
kann man auch Gleitpunktzahlen<br />
einlesen)<br />
Die auftretbaren Exceptions<br />
müssen nach throws am Anfang<br />
einer Methode genannt werden<br />
Die Klasse DataInputStream<br />
enthält die Methode readLine,<br />
welche alle Zeichen bis<br />
Zeilenende liest und daraus<br />
einen string konstruiert<br />
Beachte: Die Methode readLine<br />
kann eine IOException auslösen!<br />
Man könnte hier auch<br />
ein.readLine() für<br />
zeile substituieren<br />
107<br />
Zeichenketten (strings)<br />
• Zeichenketten werden mit der Standardklasse String realisiert<br />
• Achtung: Strings sind im Unterschied zu C++ keine char-Arrays!<br />
String msg = "Die"; // String-Objekt wird<br />
int i = 7;<br />
// automatisch erzeugt<br />
msg = msg + " " + i; // Konkatenation<br />
msg = msg + " Zwerge";<br />
Alternativ zur<br />
ersten Zeile geht<br />
es auch so:<br />
msg = new<br />
String("Die");<br />
System.out.println(msg); // Die 7 Zwerge<br />
System.out.println(msg.length()); // 12<br />
String b = msg;<br />
msg = null;<br />
System.out.println(b); // Die 7 Zwerge<br />
109<br />
46
Zeichenketten (strings) (2)<br />
• Vergleich von strings:<br />
• Vergleich mit == ist oft nicht sinnvoll (Referenzvergleich)<br />
• Stattdessen Wertevergleich: s1.equals(s2)<br />
u<br />
y<br />
x B a u m<br />
Referenz<br />
z<br />
Wert<br />
V o g e l<br />
B a u m<br />
• x == u ist „false“<br />
• x.equals(u) ist „true“<br />
• u.equals(x) ist „true“<br />
• y==z ist „true“<br />
• y.equals(z) ist „true“<br />
• Lexikographischer Vergleich mit s1.compareTo(s2)<br />
(liefert einen int-Wert 0)<br />
„Lexikographisch“: Wie im Lexikon – also alphabetisch; bei gleichen Präfixen kommt<br />
es auf das erste unterschiedliche Zeichen an („Zucker“ < „Zug“); ganze Wörter kommen<br />
vor Präfixen anderer Wörter („Kuh“ < „Kuhle“); wo „ü“ angeordnet wird (bei<br />
/ vor / nach „u“, oder erst nach „z“), wie Gross-/Kleinbuchstaben, Ziffern, Sonderzeichen<br />
angeordnet werden, ist durch das Alphabet bestimmt (hier: Unicode).<br />
110<br />
Zeichenketten (strings) (3)<br />
• Es gibt eine Vielzahl von Methoden und Konstruktoren<br />
• Länge („length“)<br />
• Teilstrings („substring“)<br />
• Umwandlung von Zeichen (z.B. Gross- / Kleinschreibung)<br />
• Umwandlung von char- und byte-Arrays in strings<br />
• Umwandlung von anderen Datentypen in strings (und umgekehrt)<br />
• ...<br />
• Mehr dazu in der „Java Platform API Specification“:<br />
http://java.sun.com/reference/api/ ( „core API“ java.lang String)<br />
111<br />
47
Auszug aus der API-Beschreibung<br />
(API = „Application Programming Interface“)<br />
compareTo<br />
public int compareTo(String anotherString)<br />
Compares two strings lexicographically.<br />
Parameters:<br />
anotherString - the String to be compared.<br />
Die einzelnen Zeichen werden<br />
entsprechend ihrer Reihenfolge<br />
im Unicode-Zeichensatz verglichen.<br />
Möchte man Grossund<br />
Kleinbuchstaben gleich<br />
behandeln, dann verwende<br />
man compareToIgnoreCase<br />
statt dessen.<br />
Returns:<br />
The value 0 if the argument string is equal to this string; a value less<br />
than 0 if this string is lexicographically less than the string argument;<br />
and a value greater than 0 if this string is lexicographically greater than<br />
the string argument.<br />
112<br />
Auszug aus der API-Beschreibung (II)<br />
concat<br />
public String concat(String str)<br />
Concatenates the string argument to the end of this string.<br />
Parameters:<br />
str - the String which is concatenated to the end of this String<br />
Returns:<br />
A string that represents the<br />
concatenation of this object's<br />
characters followed by the<br />
string argument's characters.<br />
Beispiele:<br />
"Zeit".concat("geist");<br />
String s = "Drei";<br />
s.concat(" Millionen");<br />
113<br />
48
Auszug aus der API-Beschreibung (III)<br />
copyValueOf<br />
public static String copyValueOf(char data[])<br />
Parameters:<br />
data - the character array<br />
Returns:<br />
A String that contains the characters of the array.<br />
114<br />
3.<br />
Klassen und<br />
Referenzen<br />
Konzepte sind aus Teil I der Vorlesung aus C++ im Wesentlichen bekannt!<br />
Buch Mark Weiss „Data Structures & Problem Solving Using Java“ siehe<br />
Seiten: 115-122 (this, static); 66-70 (Referenzen); 69 (Gleichheit)<br />
117<br />
49
118<br />
Ein Datentyp für Datumsangaben<br />
Diese drei private-Attribute<br />
sind von ausserhalb der<br />
Klasse nicht sichtbar.<br />
Eine Methode mit dem gleichen<br />
Namen wie die Klasse<br />
stellt einen "Konstruktor" dar.<br />
Er wird beim Erzeugen eines<br />
Objekts automatisch aufgerufen;<br />
man kann (nur) damit<br />
die neuen Objekte (deren<br />
Variablen) initialisieren.<br />
Konstruktoren haben keinen<br />
Rückgabetyp.<br />
Diese Klasse hat einen<br />
zweiten Konstruktor mit einer<br />
unterschiedlichen Signatur.<br />
Welcher Konstruktor genommen<br />
wird, richtet sich nach<br />
der Signatur beim new-Aufruf.<br />
class Datum {<br />
private int Tag, Monat, Jahr;<br />
public Datum() {<br />
System.out.println("Datum mit Wert<br />
0.0.0 gegründet");<br />
}<br />
public Datum(int T, int M, int J) {<br />
Tag = T; Monat = M; Jahr = J;<br />
}<br />
Man hätte hier auch<br />
präziser sein können:<br />
public void Drucken() { this.Tag...; this.Monat etc.<br />
System.out.println(Tag + "." + Monat<br />
+ "." + Jahr);<br />
}<br />
public void Setzen(int T, int M, int J) {<br />
Tag = T; Monat = M; Jahr = J;<br />
}<br />
}<br />
119<br />
50
Ein Datentyp für Datumsangaben (2)<br />
„Accessor“-Methode:<br />
liefert Werte privater<br />
Attribute nach aussen<br />
„Mutator“-Methode: ändert<br />
Werte privater Attribute<br />
Da man auf die Attribute<br />
Tag, Monat und Jahr von<br />
aussen nicht zugreifen<br />
kann, wird zum Setzen<br />
des Datums eine Methode<br />
bereitgestellt.<br />
class Datum {<br />
private int Tag, Monat, Jahr;<br />
public Datum() {<br />
System.out.println("Datum mit Wert<br />
0.0.0 gegründet");<br />
}<br />
public Datum(int T, int M, int J) {<br />
Tag = T; Monat = M; Jahr = J;<br />
}<br />
public void Drucken() {<br />
System.out.println(Tag + "." + Monat<br />
+ "." + Jahr);<br />
}<br />
public void Setzen(int T, int M, int J) {<br />
Tag = T; Monat = M; Jahr = J;<br />
}<br />
}<br />
120<br />
Verwendung des Datum-Typs<br />
class Beispiel {<br />
public static void main (String args[]) {<br />
Datum Ostermontag = new Datum();<br />
/* ==> Datum mit Wert 0.0.0 gegründet */<br />
}<br />
}<br />
Ostermontag.Drucken();<br />
Ostermontag.Setzen(01,04,2013);<br />
Ostermontag.Drucken();<br />
...<br />
• „Ostermontag“ ist eine Variable vom Typ „Datum“<br />
• Genauer: eine Referenz, die auf Datum-Objekte zeigen kann<br />
• Ostermontag hat (als Datum-Objekt) auch einige Methoden<br />
• Hier: „Drucken“ und „Setzen“ (Zugriff mit Punktnotation)<br />
• Sowie zwei Konstruktoren (Aufruf bei new)<br />
Hier wird der erste<br />
Konstruktor aufgerufen<br />
Liefert 0.0.0<br />
Liefert 1.4.2013<br />
121<br />
51
Verwendung des Datum-Typs (2)<br />
• Eigentlich sollte „Setzen“ zumindest einen Plausibilitätstest<br />
machen (Monat ≤ 12, Tag ≤ 31 etc.)<br />
• Gleiches gilt für den zweiten Konstruktor<br />
• Auf diese Weise könnte garantiert werden, dass<br />
illegale Datumsangaben weitgehend vermieden werden<br />
• Der Zugriff auf private Attribute von ausserhalb der<br />
Klasse „Datum“ wird vom Compiler nicht zugelassen:<br />
Ostermontag.Jahr = 1789;<br />
liefert die Fehlermeldung:<br />
Variable Jahr in class Datum not<br />
accessible from class Beispiel.<br />
122<br />
Datumsvergleich<br />
• Wir fügen zu „Datum“ eine neue Methode hinzu, mit der ein<br />
Datum-Objekt entscheiden kann, ob es selbst früher als ein<br />
anderes (als Parameter übergebenes) Datum-Objekt ist:<br />
class Datum {<br />
...<br />
public boolean frueher_als(Datum d) {<br />
return Jahr < d.Jahr ||<br />
Jahr == d.Jahr && Monat < d.Monat ||<br />
Jahr == d.Jahr && Monat == d.Monat<br />
&& Tag < d.Tag;<br />
}<br />
true oder false<br />
d ist ein formaler Parameter<br />
vom Typ „Datum“<br />
Man kann auf d.Jahr, d.Monat, d.Tag zugreifen, obwohl<br />
diese Attribute als „private“ deklariert sind, da es sich<br />
um die gleiche Klasse (aber eine andere Instanz) handelt<br />
123<br />
52
Datumsvergleich (2)<br />
class Beispiel {<br />
...<br />
Datum d1 = new Datum(23,03,56);<br />
Datum d2 = new Datum(27,06,57);<br />
System.out.println(d1.frueher_als(d2));<br />
// true<br />
System.out.println(d2.frueher_als(d1));<br />
}<br />
// false<br />
...<br />
Hier wird die Methode „frueher_als“<br />
aus dem Objekt d2 aufgerufen, und<br />
zwar mit Objekt d1 als Parameter.<br />
• Durch diese in der Klasse „Datum“ definierten Methoden kann man nun<br />
Datum-Objekte bezüglich früher / später vergleichen – ganz analog,<br />
wie man z.B. ganze Zahlen mit dem Operator ’
Gleichheit und „this“<br />
class Datum...<br />
boolean frueher_als...<br />
boolean gleich(Datum d) {<br />
return !frueher_als(d) && !d.frueher_als(this);<br />
}<br />
...<br />
class Beispiel ...<br />
...<br />
Datum d1 = new Datum(23,03,56);<br />
Datum d2 = new Datum(27,03,56);<br />
System.out.println(d1.gleich(d2)); // false<br />
Datum d3 = new Datum(23,03,56);<br />
System.out.println(d1.gleich(d3)); // true<br />
...<br />
Beachte: „this“ ist ein Schlüsselwort, mit dem stets eine<br />
Referenz auf das eigene, aktuelle Objekt zurückgeliefert wird<br />
Hier wird die Funktion<br />
„frueher_als“ aus dem<br />
Objekt d aufgerufen,<br />
und zwar mit „einem<br />
selbst“ als Parameter!<br />
„gleich“ liesse sich<br />
natürlich auch direkter,<br />
ohne Rückgriff auf<br />
„frueher_als“ realisieren<br />
126<br />
Klassen- und Instanzenmethoden<br />
• Klassenmethoden bekommen (im Unterschied zu<br />
Instanzenmethoden) das Attribut „static“ vorangestellt<br />
• Klassenmethoden werden nicht für spezifische Objekte,<br />
sondern für die Klasse als Ganzes aufgerufen<br />
• Klassenmethoden lösen somit allgemeine Aufgaben<br />
für alle Objekte einer Klasse gemeinsam, z.B.:<br />
class Datum {<br />
private int Tag, Monat, Jahr;<br />
static String Monatsname(Datum d)<br />
{if (d.Monat == 1) return "Januar";<br />
}<br />
if ...<br />
...<br />
}<br />
...<br />
"Februar";<br />
Auf eigene Instanzenvariablen<br />
(z.B. Monat<br />
bzw. this.Monat) kann<br />
Monatsname nicht<br />
zugreifen, wohl aber<br />
auf d.Monat aus der<br />
per Referenzparameter<br />
d übergebenen<br />
Instanz<br />
127<br />
54
Klassenmethoden und klassenbezogene<br />
Variablen („static“)<br />
• Klassenmethoden können nur auf klassenbezogene<br />
Variablen (d.h. mit „static“ deklarierte Variablen) oder<br />
andere Klassenmethoden zugreifen<br />
• Nicht auf eigene Instanzenvariablen (= Deklaration ohne static)<br />
• Nicht auf „this“ (da kein spezifisches Objekt existiert)<br />
• Auf Klassenmethoden wird von ausserhalb i.Allg. durch<br />
Angaben des Klassennamens (statt über eine Referenz<br />
auf ein Objekt der Klasse) zugegriffen<br />
• Also z.B.: System.out.print(Datum.Monatsname(Ostermontag));<br />
• Klassenbezogene Variablen werden von allen Instanzen<br />
einer Klasse geteilt – Änderung bei einer Instanz wirkt<br />
sich auf alle anderen Instanzen der Klasse aus<br />
• Sind damit eine Art globale Variablen<br />
128<br />
Information Hiding („Geheimnisprinzip“)<br />
Verborgene Interna<br />
Schnittstelle mit<br />
Zugriffsmethoden<br />
130<br />
55
Information Hiding („Geheimnisprinzip“)<br />
• Kapselung aller relevanten Daten und Methoden<br />
• Kein direktes Lesen oder Schreiben (interner, d.h.<br />
„privater“) Daten, sondern nur über Zugriffsmethoden<br />
• Interne Repräsentation nach aussen unsichtbar machen<br />
• Verhindert inkompetenten Missbrauch garantiert Integrität<br />
• Klasse „Datum“ kann z.B. selbst prüfen, ob es mit einem illegalen<br />
Wert gesetzt werden soll (statt dass jeder Aufrufer immer prüft)<br />
• Dabei geht es nicht um „Datenschutz“, sondern um Abstraktion<br />
(von der Implementierung und Datenrepräsentation)<br />
• Konsequente Realisierung von sogenannten „abstrakten Datentypen“<br />
• Klare Schnittstelle (nämlich Zugriffsmethoden)<br />
• Änderungsfreundliche Software (da seiteneffektfrei)<br />
131<br />
Dynamische Klassen und Referenzen<br />
class Person {<br />
String name;<br />
int geburtsjahr;<br />
int groesse;<br />
Person vater, mutter;<br />
}<br />
Kein "static"!<br />
Das sind Platzhalter für<br />
Referenzen auf andere<br />
Personen (genauer: auf<br />
Instanzen bzw. Objekte<br />
des Typs „Person“)<br />
p1 ist eine „Referenzvariable“<br />
class Geburt {<br />
public static void main (String[] args) {<br />
Person p1;<br />
// p1.name = "Hugo"; geht nicht!<br />
p1 = new Person(); // erst erzeugen!<br />
p1.name = "Hugo";<br />
System.out.println(p1.name);<br />
}<br />
}<br />
132<br />
56
Dynamische Klassen und Referenzen (2)<br />
• Erzeugen von dynamischen Klassen geschieht mit<br />
new x();<br />
„Konstruktor“<br />
• Es wird eine Instanz bzw. Objekt der Klasse x erzeugt<br />
und eine Referenz („Zeiger“) darauf zurückgeliefert<br />
• Am besten die Referenz gleich „abspeichern“ : p1 = new x();<br />
• Referenzen sind implementierungstechnisch Adressen<br />
• Für Referenzvariablen gibt es eine einzige Konstante:<br />
null – diese gehört zum Wertebereich aller Referenztypen<br />
133<br />
Verändern von Referenzvariablen<br />
Hätte man vor p = null dagegen q = p ausgeführt:<br />
Endgültig verlorene Objekte<br />
werden vom Garbage-Collector<br />
automatisch eingesammelt<br />
Recycling von Speicher<br />
dann wäre das Objekt über q noch zugreifbar!<br />
134<br />
57
Zuweisung von Referenzen<br />
• Einzige Operation auf Referenzvariablen ist die Zuweisung<br />
p = new Person(); q = new Person();<br />
p.groesse = 186; p.name = "Mike";<br />
q.groesse = 170; q.name = "Sandra"<br />
• Zunächst die Situation nach diesem Programmstück:<br />
135<br />
Zuweisung von Referenzen (2)<br />
Zuweisung an Referenzvariable p:<br />
Hiernach gilt p==q ( Alias-Effekt!)<br />
System.out.println(p.name);<br />
System.out.println(q.name);<br />
p.groesse = 200;<br />
System.out.println(q.groesse);<br />
ergibt dann jeweils „Sandra“<br />
ergibt auch 200<br />
136<br />
58
Vergleich von Referenzen<br />
• Referenzvariablen können auf Gleichheit / Ungleichheit<br />
(mit ‘==’ bzw. ‘!=’) verglichen werden<br />
• p == q ist true genau dann, wenn die beiden<br />
Referenzvariablen auf das selbe Objekt zeigen<br />
(z.B. nach p = q) oder wenn beide null sind<br />
• aber nicht, wenn zwei verschiedene Objekte, auf die<br />
p bzw. q verweisen, die gleichen Werte haben!<br />
137<br />
Vergleich von Referenzen (2)<br />
p = new Person(); q = new Person();<br />
p.groesse = 186; p.name = "Mike";<br />
q.groesse = 170; q.name = "Sandra";<br />
if (p == q)... → liefert false<br />
p = q;<br />
if (p == q)... → liefert true<br />
p = new Person(); q = new Person();<br />
p.groesse = 186; p.name = "Mike";<br />
q.groesse = 186; q.name = "Mike";<br />
if (p == q)... → liefert false<br />
138<br />
59
4.<br />
Syntaxanalyse<br />
und Compiler<br />
Buch Mark Weiss „Data Structures & Problem Solving Using Java“ siehe Seiten:<br />
294-297 (Stacks); 473-502 (Stacks und Compiler); 625-629 ; 635-639 (Stack-<br />
Implementierung); 681 und folgende Seiten (Bäume); 697-708<br />
(Baumtraversierung)<br />
Aus der Übungen zu Informatik I ist bekannt: postfix („UPN“), inorder, postorder<br />
139<br />
Bäume in der Informatik<br />
• Bestehen aus Knoten und Kanten (d.h., sind „Graphen“)<br />
(1) Jede Kante verbindet genau 2 Knoten<br />
(2) Zwischen je 2 Knoten gibt es höchstens eine Kante<br />
(3) Anzahl der Knoten = 1 + Anzahl der Kanten<br />
(4) Sind „zusammenhängend“, d.h. von jedem Knoten kann<br />
man jeden anderen (evtl. indirekt) über Kanten erreichen<br />
Für jeden Baum gilt:<br />
• Zwischen je 2 verschiedenen<br />
Knoten gibt es genau einen „Weg“<br />
(Folge von „benachbarten“ Knoten<br />
bzw. Kanten)<br />
• Es gibt keine „Zyklen“ (Weg mit<br />
Anfangsknoten = Endknoten)<br />
140<br />
60
Fräulein, bitte einen<br />
Baum mit 4 Knoten!<br />
Wirbeltiere<br />
Bitteschön!<br />
Fische<br />
Säugetiere<br />
Noch einen<br />
anderen, bitte!<br />
Affen<br />
Wie wäre es damit?<br />
Und noch einen!<br />
• Ja, aber wie viele wirklich verschiedene Bäume gibt es?<br />
In welchem Sinne?<br />
142<br />
16<br />
Beispiel: Alle 16 verschiedenen Bäume mit 4 Knoten<br />
1 2 3 4 5 6 7 8 9 10 11 12<br />
Und was<br />
ist damit?<br />
Bei solchen sogen.<br />
labeled trees werden<br />
alle jew. Knoten als<br />
verschieden angesehen<br />
13 14 15 16<br />
• Vgl. auch die beiden (!) Isomere<br />
des Camping-Gases Butan C 4 H 10 :<br />
• n-Butan und<br />
• Isobutan<br />
x<br />
x<br />
x<br />
Baumgenerierung:<br />
3 geeignete Kästchen<br />
ankreuzen<br />
Adjazenzmatrix<br />
„unlabeled trees“:<br />
Keine individuell verschiedenen<br />
Knoten<br />
143<br />
61
Beispiel: Verschiedene Bäume der Grösse 1 bis 6<br />
1<br />
2<br />
3<br />
4<br />
5<br />
6<br />
Hier: „unlabeled trees“<br />
CH 3<br />
CH 2<br />
CH 2<br />
CH 2<br />
CH 2<br />
CH 3<br />
CH 3<br />
CH CH 2<br />
CH 2<br />
CH 3<br />
CH 3<br />
CH 3<br />
CH 2<br />
CH CH 2<br />
CH 3<br />
CH 3<br />
CH 3<br />
CH 3<br />
C CH 2<br />
CH 3<br />
CH 3<br />
Denkübung:<br />
Wieso aber gibt es<br />
nicht 6 (sondern nur 5)<br />
Isomere des Hexans?<br />
CH 3<br />
CH CH CH 3<br />
CH 3<br />
CH 3<br />
• Die Anzahl der Bäume bei n Knoten entspricht der Folge 1, 1, 1, 2, 3, 6,<br />
11, 23, 47, 106, 235, 551, 1301, 3159, 7741, 19320, 48629, 123867, …<br />
(wächst grössenordnungsmässig exponentiell mit n)<br />
144<br />
Sind Stammbäume wirklich „Bäume“?<br />
145<br />
62
Wurzelbäume<br />
• Ein bestimmter Knoten wird als „Wurzel“ ausgezeichnet<br />
• Bäume sind in der Informatik meist Wurzelbäume; daher<br />
meint man oft „Wurzelbaum“, wenn man einfach „Baum“ sagt<br />
• Werden i.Allg. „umgekehrt“ gezeichnet (Wurzel oben!)<br />
Knoten mit nur einer einzigen<br />
„inzidenten“ Kante heissen Blatt<br />
Jeden Knoten eines Baums kann man als<br />
Wurzel eines „Unterbaums“ auffassen<br />
Ausgehend von der Wurzel kann man die<br />
Knoten in Ebenen (Niveau, Level) einteilen<br />
gleiche Entfernung von der Wurzel<br />
Wurzelbäume eignen sich gut zur<br />
Darstellung hierarchischer Strukturen<br />
146<br />
147<br />
63
Wurzelbäume mit bis zu vier Blättern<br />
Historische Notiz<br />
Bäume als mathematische<br />
Strukturen wurden<br />
1857 von<br />
Arthur Cayley<br />
eingeführt.<br />
Auszug aus:<br />
Arthur Cayley:<br />
On the Theory<br />
of Analytical<br />
Forms called<br />
Trees. 2 nd Part.<br />
Philosophical<br />
Magazine, Vol.<br />
17, 374-378,<br />
1859.<br />
148<br />
Darstellung von Wurzelbäumen<br />
Alles sind (nur) unterschiedliche<br />
Darstellungen<br />
des gleichen<br />
„abstrakten“ Baums!<br />
Als (gerichteter) Graph<br />
Als Mengendiagramm<br />
(zeigt „Verschachtelung“)<br />
In eingerückter<br />
Form<br />
(„indentation“)<br />
A<br />
B<br />
D<br />
C<br />
E<br />
F<br />
A ( B ( D ) , C ( E , F ) )<br />
In Klammerdarstellung<br />
Name der Wurzel, dahinter in<br />
Klammern die Unterbäume,<br />
jeweils durch Komma getrennt<br />
Lineare Darstellung ist<br />
sehr kompakt, geeignet<br />
z.B. zur Ein- / Ausgabe<br />
150<br />
64
Binärbaum<br />
• Wurzelbaum, bei dem jeder Knoten höchstens zwei<br />
Nachfolger hat<br />
• Oft differenziert man dann zwischen dem linken und<br />
rechten Nachfolger (bzw. Unterbaum), so dass z.B. die<br />
folgenden Bäume als verschieden angesehen werden:<br />
W<br />
W<br />
X<br />
Y<br />
Y<br />
X<br />
Z<br />
Z<br />
Die Pfeilspitzen<br />
lässt man oft weg<br />
151<br />
Binärbäume in Arrays<br />
Die Idee besteht darin, die<br />
Wurzel an Stelle 1 eines<br />
Arrays zu speichern, und die<br />
beiden direkten Nachfolger<br />
von Stelle i an den Stellen 2i<br />
(Wurzel des „linken" Unterbaums)<br />
und 2i+1 (Wurzel<br />
des „rechten“ Unterbaums)<br />
Veranschaulichung<br />
durch Binärdarstellung<br />
der Indexnummer<br />
Konvention nötig für<br />
"leeren" Unterbaum<br />
152<br />
65
Binärbäume in Arrays (2)<br />
String [] Baum = new String [n+1];<br />
Baum[1] = "Katja";<br />
Baum[2] = "Eva";<br />
Baum[3] = "Stephan";<br />
Baum[4] = "Dirk";<br />
...<br />
→ „Niveauweise“ Speicherung<br />
der Baumknoten<br />
→ Verweise nur noch implizit<br />
153<br />
Syntaxbaum<br />
Der Syntaxbaum (oder:<br />
Ableitungsbaum) spiegelt<br />
die Struktur eines<br />
Programmtextes wider<br />
Die Konstruktion eines<br />
Syntaxbaums zu einem<br />
Programmtext nennt<br />
man Syntaxanalyse<br />
Dies ist das, was ein<br />
Compiler zunächst mit<br />
einem Programm tut,<br />
bevor er es in Maschinensprache<br />
(bzw.<br />
Bytecode) übersetzt<br />
154<br />
66
Syntaxdiagramme<br />
• Zweck: nur syntaktisch korrekte Programme generieren<br />
• Überprüfen, ob ein Programm syntaktisch korrekt ist:<br />
Versuchen, es mit einem Syntaxdiagramm zu generieren<br />
• Ein (sehr einfaches) Beispiel:<br />
Zuweisung:<br />
Variablenname = Variablenname<br />
;<br />
bezeichnet einen<br />
syntaktischen Begriff<br />
(„Nicht-Terminalsymbol“),<br />
welcher seinerseits weiter<br />
expandiert werden muss<br />
+<br />
Variablenname<br />
bezeichnet ein<br />
„Terminalsymbol“, welches<br />
so „wörtlich“ in einem Programm<br />
vorkommen kann<br />
156<br />
Durchlaufen von Syntaxdiagrammen<br />
• Vorgehensweise:<br />
• In Pfeilrichtung durchlaufen<br />
• Bei Verzweigungen „beliebige“ Richtung wählen<br />
• Durchlaufene Terminalsymbole aufschreiben<br />
• Wenn ein Nicht-Terminal getroffen wird, in das zugehörige<br />
Teildiagramm „abtauchen“ und nach dem „Auftauchen“ weitermachen<br />
Variablenname = Variablenname<br />
;<br />
Das Nicht-Terminalsymbol<br />
„Variablenname“ muss in<br />
diesem Beispiel entsprechend<br />
„expandiert“ werden<br />
+<br />
Variablenname<br />
157<br />
67
Syntaktisch korrekte Ausdrücke<br />
• Falls a, b, c gültige Variablennamen sind entsprechend<br />
diesem Syntaxdiagramm<br />
• Korrekt<br />
• a=b;<br />
• a=b+c;<br />
• b=b+c;<br />
• a=a+a;<br />
• Inkorrekt<br />
• a=b<br />
• a=b-c;<br />
• b=;<br />
• b=a+b+c;<br />
Was ändert sich, wenn<br />
diese Kante im Syntaxdiagramm<br />
hinzukommt?<br />
Zuweisung:<br />
Variablenname = Variablenname<br />
;<br />
+<br />
Variablenname<br />
158<br />
Ein Syntaxdiagramm-Beispiel:<br />
Klammerdarstellung eines Baums<br />
Baum:<br />
Knoten<br />
( Unterbäume )<br />
Beispiel: A ( B ( D ) , C ( E , F ) )<br />
Knoten:<br />
A<br />
B<br />
Unterbäume :<br />
Baum<br />
,<br />
Z<br />
Wie könnte man Binärbäume durch<br />
ein Syntaxdiagramm darstellen?<br />
159<br />
68
Syntax von arithmetischen Ausdrücken<br />
Term<br />
Term<br />
Faktor Faktor Faktor<br />
1 + 2 * 3<br />
Ausdruck<br />
Das ist übrigens ein Baum (in<br />
Mengendiagrammschreibweise)<br />
Ausdruck<br />
Term<br />
Faktor<br />
+<br />
Faktor<br />
Term<br />
*<br />
Faktor<br />
160<br />
Syntax von arithmetischen Ausdrücken (2)<br />
Faktor<br />
Term<br />
Ausdruck<br />
Faktor<br />
( 1 +<br />
2)<br />
* 3<br />
„Ausdruck“ ist (indirekt) rekursiv; die Aufspaltung<br />
in Term und Faktor ermöglicht die<br />
Darstellung der Bindungspriorität<br />
Vgl. die beiden nachfolgenden Beispiele: wie<br />
geht daraus die Priorität, also die (evtl. implizite)<br />
„Klammerung“ der Teilausdrücke hervor?<br />
161<br />
69
Syntaxdiagramme und Syntaxbäume<br />
• Durch eine Baumdarstellung<br />
(der Terminale und Nicht-<br />
Terminale zu einem Ausdruck)<br />
kann der im Syntaxdiagramm<br />
durchlaufene Weg dargestellt<br />
werden:<br />
Term<br />
Faktor<br />
Term<br />
Faktor Faktor<br />
1 + 2 * 3<br />
Ausdruck<br />
Faktor<br />
Faktor<br />
( 1 + 2<br />
) * 3<br />
Term<br />
Ausdruck<br />
(1+2)*3<br />
Übung: Die Bäume „mit Knoten und<br />
Kanten“ zeichnen und sich die Bindungspriorität<br />
von +, *, () in dieser<br />
Graphdarstellung bewusst machen.<br />
163<br />
Syntaxanalyse<br />
• Problem: Gegeben ein Stück Text, welches so aussieht,<br />
als wäre es ein Teil eines Java-Programms<br />
• Ist dieses wirklich syntaktisch korrekt, d.h. nach den<br />
Syntaxregeln (= Syntaxdiagramm) gebildet worden?<br />
• „Lösung“: Man versucht, das Syntaxdiagramm mit<br />
„Spürsinn“ so zu durchlaufen, dass man am Ende<br />
das gegebene Textfragment erzeugt hat<br />
• Wenn dies gelingt → Textfragment syntaktisch korrekt<br />
• Wenn dies nicht gelingt:<br />
(a) → Textfragment syntaktisch nicht korrekt<br />
(b) → man hat sich nicht geschickt genug angestellt<br />
168<br />
70
Syntaxanalyse und Syntaxchecker<br />
• Problem: Einen Algorithmus angegeben, der für<br />
- ein beliebiges Syntaxdiagramm und<br />
- einen beliebigen Text<br />
eindeutig entscheidet, ob sich der Text mit dem<br />
Diagramm generieren lässt (→ Syntaxchecker)<br />
→ Theorie der Syntaxanalyse → formale Sprachen, Compilerbau<br />
for(int i=1;<br />
i
Rekursiver Abstieg<br />
(am Beispiel von Infix-Ausdrücken)<br />
• Wiederholungen (z.B. am<br />
Ende von „Ausdruck“ und<br />
„Term“) werden durch<br />
while-Schleifen realisiert<br />
• Verzweigungen (z.B. in<br />
„Faktor“) werden durch<br />
if-Anweisungen realisiert<br />
171<br />
Ein Parser als Java-Programm<br />
(Parser = „Zerteiler“,<br />
Syntaxanalyseprogramm)<br />
Bemerkung: KbdInput ist eine Klasse mit<br />
Methoden (wie z.B. „getc“), die nicht zum<br />
Standard-Java gehört. Die Funktionalität<br />
(Zeichen vom Keyboard lesen) davon lässt<br />
sich aber leicht mit Methoden aus dem<br />
Paket java.io.* nachbilden (Übung!)<br />
//Anfangsteil kommt später<br />
...<br />
void int_const(){<br />
c = KbdInput.getc();<br />
}<br />
void Ausdruck(){<br />
Term();<br />
while (c == '+') {<br />
c = KbdInput.getc();<br />
Term();<br />
}<br />
...<br />
Integer-Konstanten<br />
bestehen bei uns<br />
nur aus einem<br />
einzigen Zeichen<br />
Verarbeitetes<br />
Zeichen „weglesen“;<br />
nachfolgendes<br />
Zeichen einlesen<br />
172<br />
72
Ein Parser als Java-Programm (2)<br />
//Anfangsteil kommt später<br />
...<br />
void int_const(){...}<br />
void Ausdruck(){...}<br />
void Term() {<br />
Faktor();<br />
while (c == '*') {<br />
c = KbdInput.getc();<br />
Faktor();<br />
}<br />
}<br />
void Faktor() {<br />
if ((c >='0') && (c
Erzeugen eines Syntaxbaums<br />
• Man möchte i.Allg. nicht<br />
nur einen Syntaxchecker<br />
mit dem Resultat<br />
„syntaktisch korrekt /<br />
falsch“, sondern zur<br />
weiteren Bearbeitung den<br />
zugehörigen Syntaxbaum<br />
• Dazu erweitern wir das<br />
Analyseprogramm so, dass<br />
eine Baum-Ausgabe in der<br />
„eingerückten Darstellung“<br />
erzeugt wird<br />
175<br />
Erzeugen eines Syntaxbaums (2)<br />
• Idee: Jede Methode, die für ein Terminal oder Nicht-<br />
Terminal steht, gibt ihren eigenen Namen aus<br />
• Und zwar eingerückt um eine Länge, die proportional zur<br />
Tiefe (= Abstand zur Wurzel) des Knotens ist<br />
• Jede Methode bekommt dazu die momentane Tiefe als<br />
int-Parameter übergeben<br />
• Dafür nutzen wir eine Hilfsprozedur „out“:<br />
void out(int t, String x) {<br />
System.out.println();<br />
for(int i=1;i
Erzeugen eines Syntaxbaums (3)<br />
void Ausdruck(int t) {<br />
out(t,"Ausdruck");<br />
Term(t+1);<br />
while (c == '+') {<br />
out(t+1,"+");<br />
c = KbdInput.getc();<br />
Term(t+1);<br />
}<br />
}<br />
"out" gibt den Namen entsprechend<br />
eingerückt aus<br />
"Term" und "+" haben als<br />
Nachfolger von Ausdruck<br />
eine um 1 grössere Tiefe<br />
void int_const(int t) {<br />
out(t,"int_const: " + c);<br />
c = KbdInput.getc();<br />
}<br />
Um den Wert<br />
der Konstanten<br />
zu erfahren<br />
// Entsprechend werden "Term"<br />
// und "Faktor" ergänzt<br />
177<br />
Binäre Operatorbäume<br />
• Entstehen durch Kompaktifizierung aus den<br />
eigentlichen Syntaxbäumen<br />
• Drücken aber noch gleichermassen die wesentliche<br />
Struktur eines Ausdrucks aus<br />
• Unwesentliches entfernen<br />
• Namen von Nicht-Terminalen und Klammer-Knoten<br />
• Operator zur Wurzel des Teilbaums hochziehen<br />
• Stellt damit ein sogenanntes Attribut des Knotens dar<br />
• Sind Binärbäume<br />
• Ein Knoten (≠ Blatt) hat nur zwei Nachfolger<br />
• Dazu Teilausdrücke evtl. klammern: 2*3*4 (2*3)*4<br />
178<br />
75
Auswertung binärer Operatorbäume<br />
• Wenn der Baum keine Unterbäume<br />
hat, dann ist der Wert des Baums<br />
das Attribut der Wurzel (= Blatt)<br />
• Ansonsten existiert ein linker und<br />
rechter Unterbaum. Berechne (in<br />
rekursiver Weise!) „so“ den Wert<br />
des linken Unterbaums, dann<br />
„genauso“ den Wert des rechten<br />
Unterbaums und wende die<br />
Operation (= Attribut der Wurzel)<br />
auf die beiden Werte an<br />
→ 2*3=6 → 7-6<br />
= 1 → 1+2 = 3<br />
→ 3+...12 = 15<br />
179<br />
Auswertung des Ausdrucks durch<br />
Schrittweise Reduktion des Operatorbaums<br />
1<br />
6<br />
15<br />
3 3 12<br />
180<br />
76
Symmetrisches Traversieren eines<br />
Binärbaums („inorder“)<br />
• Prinzip: Zuerst den linken Unterbaum traversieren,<br />
dann den Wert der Wurzel ausgeben, anschliessend<br />
den rechten Unterbaum traversieren<br />
• Beispielhafte Anwendung: Rückgewinnung<br />
des Ausdrucks in Infix-Notation („vollständig<br />
geklammert“) aus einem Operatorbaum:<br />
• Falls die Wurzel kein Blatt ist:<br />
• Ausgabe von ‘(‘ und wende Algorithmus<br />
auf linken Unterbaum an<br />
• Ausgabe des Attributs der Wurzel<br />
• Falls die Wurzel kein Blatt ist:<br />
• Wende Algorithmus auf rechten<br />
Unterbaum an und Ausgabe von ‘)‘<br />
Denkübung: Wie kann<br />
man die überflüssigen<br />
Klammern sparen?<br />
(Beachte auch den Fall,<br />
wo ’*’ und ’+’ im Baum<br />
vertauscht sind!)<br />
(((7-(2*3))+2)+(4*3))<br />
181<br />
Postfix-Ausdrücke<br />
• Bei Postfix-Ausdrücken kommt der Operator nach den<br />
zugehörigen Operanden, nicht dazwischen (infix)<br />
• Wird auch als „umgekehrte polnische Notation“ (UPN) bezeichnet<br />
• Entspr. Präfix-Ausdrücke: Operator vor seinen beiden Operanden<br />
• Bsp: 2 3 + 4 5 * + entspricht infix (2 + 3) + (4 * 5)<br />
Die Präfix-Notation („polnische<br />
Notation“) wurde<br />
in den 1920er-Jahren<br />
vom polnischen<br />
Mathematiker<br />
Jan<br />
Lukasiewicz<br />
(1878–1956)<br />
entwickelt.<br />
182<br />
77
Postfix-Ausdrücke (2)<br />
• Postfix-Ausdrücke sind für die maschinelle Verarbeitung<br />
besser geeignet als Infix-Ausdrücke (dazu später mehr)<br />
• Sie enthalten z.B. keine Klammern und sind dennoch eindeutig!<br />
• Problem also: Automatische Umwandlung infix → postfix<br />
• Idee am Beispiel 2 + 3 → 2 3 +<br />
• D.h. von links nach rechts lesen und den Operator<br />
für den späteren Gebrauch zwischenspeichern<br />
• Entsprechend: 2 + Ausdruck → 2 Ausdruck +<br />
• auch wenn „Ausdruck“ sehr lang ist, der selbst<br />
wieder nach dem gleichen Prinzip übersetzt wird!<br />
• Konkreter (rekursiver?) Algorithmus hierfür?<br />
183<br />
Baumtraversierung in „postorder“<br />
• Zuerst linken Unterbaum traversieren (falls vorhanden)<br />
• Dann rechten Unterbaum traversieren (falls vorhanden)<br />
• Dann die Wurzel „betrachten“<br />
• Z.B. das bei einem Operatorbaum vorhandene Attribut ausgeben<br />
Lässt sich daraus der Operatorbaum<br />
wieder eindeutig rekonstruieren?<br />
7 2 3 * - 2 + 4 3 * +<br />
Das ist offenbar<br />
der Infix-Ausdruck<br />
((7-(2*3))+2)+(4*3)<br />
in Postfix-Notation!<br />
184<br />
78
Umwandlung infix postfix<br />
• Eine erste Idee:<br />
• Infix-Ausdruck mit einem Parser analysieren<br />
• Dabei Operatorbaum aufbauen<br />
• Operatorbaum dann in postorder durchlaufen<br />
• Es geht aber auch ohne expliziten Operatorbaum!<br />
185<br />
Umwandlung infix postfix (2)<br />
• Wir kommen ohne expliziten Baum aus, wenn wir beim<br />
zeichenweisen Lesen von links nach rechts einen Operator<br />
für den späteren Gebrauch zwischenspeichern<br />
⇒<br />
<br />
⇒<br />
<br />
• Daher:<br />
• Operator in einen Stack; dort ruhen lassen<br />
• Inzwischen den Ausdruck2 bearbeiten<br />
• Nach Ende von Ausdruck2: Operator aus dem Stack herausholen<br />
• Aber wie erkennt man das Ende? Wir machen es uns zunächst<br />
einfach und fordern, dass jeder (Teil)ausdruck geklammert ist;<br />
dann erkennt man das an einer schliessenden Klammer „)“<br />
186<br />
79
Ein Stack („Keller“, „Stapel“)<br />
Bereits aus Teil I der<br />
Vorlesung bekannt<br />
• Für einzelne Zeichen<br />
(„character-Stack“)<br />
• Realisiert als Klasse,<br />
die ein Array enthält<br />
• Array-Grenzen sind durch<br />
0 und length-1 abgesteckt<br />
• p „zeigt“ immer schon auf<br />
das nächste freie Element<br />
class Stack {<br />
int p;<br />
char [] st;<br />
Stack(int size) {<br />
p = 0;<br />
st = new char[size];<br />
}<br />
void push(char c) {<br />
if (p >= st.length)<br />
System.out.println<br />
("Stack Overflow");<br />
else<br />
st[p++] = c;<br />
}<br />
char pop() {<br />
return st[--p];<br />
}<br />
}<br />
Stackpointer<br />
Konstruktor<br />
Sonst Fehler<br />
bei Zugriff auf<br />
st[st.length]<br />
Ein „Stack Underflow“<br />
sollte eigentlich auch<br />
überprüft werden!<br />
187<br />
Postfix-Umwandlung mittels Stack<br />
• Wir beschränken uns hier auf die beiden Operatoren<br />
+ und * sowie einziffrige Operanden<br />
• Lösungsidee:<br />
• Operanden (d.h. Zahlen) werden sofort ausgegeben<br />
• Operatoren kommen in den Stack<br />
• Bei jeder schliessenden Klammer „pop“: auf den obersten<br />
Operator anwenden (d.h. aus dem Stack holen) und ausgegeben<br />
• Offenbar spielen öffnende Klammern keine Rolle, daher ’(’<br />
genauso wie Leerzeichen etc. einfach überlesen!<br />
188<br />
80
Postfix-Umwandlung mittels Stack (2)<br />
class Stack //Wie gehabt...<br />
class InfToPost {<br />
Stack stk = new Stack(1000);<br />
char c;<br />
boolean eof(char c) {<br />
return (c == (char) -1);<br />
}<br />
void convert() {<br />
while (!eof(c = KbdInput.getc())) {<br />
if ((c=='+') || (c=='*'))<br />
stk.push(c);<br />
if ((c >= '0') && (c
Auswertung mittels Operandenstack<br />
Beispiel: 5 1 2 + 3 4 * * 6 + *<br />
191<br />
Ein Postfix-Auswerter in Java<br />
public static main void(String [])<br />
Stack stk = new Stack(1000); // hier: int-Stack (für Operanden)<br />
char c = ' '; int x;<br />
while (!eof(c)) {<br />
if (!(c == '+' || c == '*' || c >= '0' || c = '0' && c
Umwandlung von Ziffernfolgen in Zahlen<br />
• Verschiedene Operanden sind durch<br />
Leerzeichen getrennt; mehrstellige<br />
Operanden enthalten keine Leerzeichen!<br />
while (c>='0' && c 1 und Ziffern 0 c i < b) ergibt sich aus<br />
Z = Σ c i b i = (…(c n )b+c n-1 )b+…+c 1 )b+c 0 Hornerschema für Polynome<br />
• Wir verwenden Dezimalzahlen mit b = 10<br />
• Beim Berechnen der Operanden wird die Tatsache ausgenutzt,<br />
dass mit char-Werten wie mit int-Werten gerechnet werden kann<br />
(c-’0’) und die Ziffern 0 bis 9 im Zeichensatz hintereinander stehen<br />
• Ganz ähnlich könnte man z.B. auch binäre (b = 2) oder hexadezimale<br />
(b = 16) Operanden zulassen und nach int konvertieren<br />
• Statt (c-’0’) ginge weniger trickreich auch Character.digit(c,10)<br />
193<br />
Stellenschreibweise laut Adam Riese (AD 1550)<br />
Historische Notiz<br />
Zehen sind figurn / darmit ein jede zal geschrieben wirt /<br />
sind also gestalt. 1. 2. 3. 4. 5. 6. 7. 8. 9. 0. Die ersten neun<br />
bedeuten / die zehent als 0 gibt in fursetzung mehr<br />
bedeutung / gilt aber allein nichts / wie hie 10. 20. 30. 40.<br />
50. 60. 70. 80. 90. als Zehen, Zwentzig, Dreissig / etc.<br />
Werden zwey 0 furgesatzt / so hastu hundert vorhanden /<br />
also 100. 200. 300. 400. 500. 600. 700. 800. 900. Werden<br />
drey 0 furgesatzt / so hastu tausent / nemlich 1000. 2000.<br />
3000. Ein jede figur vnder den obgeschrieben zehen / gilt an<br />
der ersten stat gen der rechten handt sich selbst / an der<br />
anderen gen der lincken handt so vil zehen / an der dritten<br />
so offt hundert / Und an der vierden stat so vil tausent. Der<br />
halben zele von der rechten handt gen der lincken / eins<br />
zehen hundert tausent.<br />
194<br />
83
Adam Ries erklärt die Stellenschreibweise in seinem<br />
dritten Rechenbuch („Rechenung nach der lenge /<br />
auff den Linihen und Feder“) 1550 unter der<br />
Überschrift „Numerirn / Zelen“ wie folgt:<br />
Zehen sind figurn / darmit ein jede zal geschrieben<br />
wirt / sind also gestalt. 1. 2. 3. 4. 5. 6. 7. 8. 9. 0.<br />
Die ersten neun bedeuten / die zehent als 0 gibt in<br />
fursetzung mehr bedeutung / gilt aber allein nichts /<br />
wie hie 10. 20. 30. 40. 50. 60. 70. 80. 90. als<br />
Zehen, Zwentzig, Dreissig / etc. Werden zwey 0<br />
furgesatzt / so hastu hundert vorhanden / also 100.<br />
200. 300. 400. 500. 600. 700. 800. 900. Werden<br />
drey 0 furgesatzt / so hastu tausent / nemlich 1000.<br />
2000. 3000. Ein jede figur vnder den obgeschrieben<br />
zehen / gilt an der ersten stat gen der rechten handt<br />
sich selbst / an der anderen gen der lincken handt<br />
so vil zehen / an der dritten so offt hundert / Und an<br />
der vierden stat so vil tausent. Der halben zele von<br />
der rechten handt gen der lincken / eins zehen<br />
hundert tausent.<br />
http://dx.doi.org/10.3931/e-rara-81<br />
Man kann übrigens vermuten, dass die sprachliche Verwandtschaft des<br />
Zahlwortes „zehn“ mit „Zehen“ kein Zufall ist, vgl. die Abstammung des<br />
englischen Wortes „digit“ für „Ziffer“ vom lateinischen digitus (Finger).<br />
195<br />
Klammerchecker für Java-Programme<br />
• Eine weitere Anwendung für Stacks: Analyse<br />
der durch „{ ... }“ definierten hierarchischverschachtelten<br />
Blockstruktur von Programmen<br />
• Bei Beginn eines Blocks (bei ’{’) wird die gegenwärtige<br />
Zeilennummer in den Stack geschrieben<br />
• Nach Abarbeitung aller inneren Blöcke wird sie<br />
bei Blockende (d.h. ’}’) aus dem Stack geholt<br />
1 { …<br />
2 …<br />
3 { …<br />
4 { …<br />
5 }<br />
6 { …<br />
7 }<br />
8 }<br />
9 }<br />
Einen solchen Kommentar wollen<br />
wir automatisch erzeugen<br />
Block von Zeile 4 endet in Zeile 5<br />
Block von Zeile 6 endet in Zeile 7<br />
Block von Zeile 3 endet in Zeile 8<br />
Block von Zeile 1 endet in Zeile 9<br />
196<br />
84
Klammerchecker für Java-Programme (2)<br />
class Stack //hier auch wieder int-Stack<br />
class KlammerChecker {<br />
static boolean eof(char c) { return (c == (char) – 1); }<br />
public static void main(String[] args ) {<br />
int zeile = 1;<br />
Stack stk = new Stack(1000);<br />
while (!eof(c = KbdInput.getc())) {<br />
if (c == '\n') zeile++;<br />
if (c == '{') stk.push(zeile);<br />
if (c == '}')<br />
if (stk.empty())<br />
System.out.println("*** Fehlt: {");<br />
else<br />
System.out.println("Block von zeile " + stk.pop() +<br />
"endet in Zeile" + zeile);<br />
} //end while<br />
if (!stk.empty())<br />
System.out.println("*** Fehlt: }");<br />
} //end Methode main<br />
}<br />
Bei 'newline' wird der<br />
Zeilenzähler erhöht<br />
Wenn am Ende der<br />
Stack nicht leer ist,<br />
war etwas falsch!<br />
197<br />
Ein rekursiver Klammerchecker<br />
class KlammerChecker {<br />
//c und zeile sind global für alle Methoden<br />
char c; int zeile = 1;<br />
static boolean eof(char c) { return c == (char) – 1; }<br />
}<br />
void block(int z) {<br />
while (!eof(c = KbdInput.getc())) {<br />
switch(c) {<br />
case '\n':<br />
zeile++;<br />
break;<br />
case '}':<br />
System.out.println("Block von zeile " + z +<br />
"endet in Zeile" + zeile);<br />
return;<br />
case '{':<br />
block(zeile);<br />
} // end switch<br />
} //end while<br />
}<br />
Ginge auch<br />
continue?<br />
Rekursiver<br />
Aufruf!<br />
public static void main(String[] args) {<br />
KlammerChecker kc = new KlammerChecker();<br />
kc.block(0);<br />
}<br />
Übung: Wie könnte man Fehler der Klammerstruktur<br />
(zu viele bzw. zu wenige ’{’ oder ’}’) melden?<br />
Erzeugt gleiches Ergebnis<br />
wie vorheriges Programm<br />
Implizite Nutzung des<br />
Laufzeitstacks (statt<br />
explizitem Stack)<br />
Die Zeilennummer wird<br />
dabei auf dem jeweiligen<br />
Exemplar der Variablen z<br />
der einzelnen Methodeninstanzen<br />
gespeichert<br />
Nach Rückkehr aus einem<br />
rekursiven Methodenaufruf<br />
befindet man sich wieder<br />
in einer Instanz mit<br />
dem „alten“ Wert von z<br />
198<br />
85
Codegenerierung für Infix-Ausdrücke<br />
• Aufgabe eines Compilers ist nicht nur die Syntaxprüfung,<br />
sondern auch „Code“ für eine Zielsprache zu erzeugen<br />
• Wir postulieren hier eine Stackmaschine als Zielmaschine<br />
mit folgenden drei Operationen:<br />
• push(i): Int-Wert i auf den Stack schreiben<br />
• plus: Oberste beiden Stackelemente durch ihre Summe ersetzen<br />
• mult: ... Produkt ersetzen<br />
202<br />
Codegenerierung für Infix-Ausdrücke (2)<br />
• Wir wollen das Analyseprogramm an den „richtigen“<br />
Stellen so mit Codeerzeugungsanweisungen ausstatten,<br />
dass Folgendes generiert wird:<br />
Die Zielmaschine kann in Hardware<br />
realisiert sein, oder als<br />
virtuelle Maschine durch ein<br />
Laufzeit- oder Betriebssystem<br />
simuliert werden.<br />
Oder der generierte Code wird<br />
weiter in Code für eine echte<br />
Zielmaschine transformiert.<br />
Das ist eigentlich eine<br />
Übersetzung nach Postfix!<br />
203<br />
86
Der Parser mit Codeerzeugung<br />
void int_const(){ //hier nur einziffrig<br />
System.out.println("push(" + c + ")");<br />
c = KbdInput.getc();<br />
}<br />
void Ausdruck(){<br />
Term();<br />
while (c == '+') {<br />
c = KbdInput.getc();<br />
Term();<br />
System.out.println("plus");<br />
}<br />
}<br />
void Term() {<br />
Code-Generierung für<br />
Faktor();<br />
die Stackmaschine<br />
while (c == '*') {<br />
c = KbdInput.getc();<br />
Faktor();<br />
System.out.println("mult");<br />
}<br />
}<br />
Auch bei dieser Infix zu<br />
Postfix-Übersetzung wird<br />
(implizit) ein Stack benutzt:<br />
Der Laufzeitstack von Java,<br />
in dem die Rücksprungadressen<br />
abgelegt sind!<br />
204<br />
Ein Interpreter für Infix-Ausdrücke<br />
• Statt die Operationen einer Stackmaschine<br />
auszugeben, also etwa in eine Datei zu schreiben,<br />
und diese anschliessend von einer Stackmaschine<br />
ausführen zu lassen, können wir<br />
auch gleich push, plus, mult etc. auf einem<br />
Stack ausführen und so schritthaltend zur<br />
Analyse den Ausdruck („on the fly“) auswerten<br />
• Wir bekommen damit statt eines Compilers<br />
(=Übersetzer) einen Interpreter für Infix-Ausdrücke<br />
• Zur Realisierung dieses „Taschenrechners“ verwenden<br />
wir wieder unserer Service-Klasse „Stack“<br />
5*(((1+2)*(3*4))+6)<br />
push(5)<br />
push(1)<br />
push(2)<br />
plus<br />
push(3)<br />
push(4)<br />
mult<br />
mult<br />
push(6)<br />
plus<br />
mult<br />
205<br />
87
Ein Interpreter für Infix-Ausdrücke (2)<br />
class Stack // wie gehabt int-Stack<br />
class Parser {<br />
Stack stk = new Stack(100); char c;<br />
void int_const() {<br />
stk.push(Character.digit(c,10));<br />
c.KbdInput.getc();<br />
}<br />
void Ausdruck(){<br />
Term();<br />
while (c == '+') {<br />
c = KbdInput.getc(); Term();<br />
stk.push(stk.pop() + stk.pop());<br />
}<br />
}<br />
void Term() {<br />
Faktor();<br />
while (c == '*') {<br />
c = KbdInput.getc(); Faktor();<br />
stk.push(stk.pop() * stk.pop());<br />
}<br />
}<br />
... main ...<br />
Parser p = new Parser();<br />
p.c = KbdInput.getc();<br />
p.Ausdruck();<br />
System.out.println(p.stk.pop());<br />
}<br />
Umwandlung eines char<br />
in eine Zahl zur Basis 10<br />
Was wäre bei '-' zu beachten?<br />
Die obersten beiden Stackelemente<br />
werden durch deren Summe ersetzt<br />
Hier wird das Resultat ausgegeben<br />
206<br />
Historische Notiz<br />
207<br />
88
Bem.: Die klassische deutsche Bezeichnung<br />
für „Stack“ lautet „Keller“ (oder „Stapel“)<br />
208<br />
209<br />
89
210<br />
Wie kam es zu diesem Patent?<br />
Friedrich L. Bauer erinnert sich:<br />
• „Im Jahr 1951 erschien eine bahnbrechende, aufregende Zürcher Publikation<br />
von Heinz Rutishauser – ein Programm, das Programme produziert:<br />
• Über automatische Rechenplanfertigung bei programmgesteuerten Rechenmaschinen.<br />
Z. Angew. Math. Mech. 31(8/9):255, Aug./Sept. 1951“<br />
Friedrich Ludwig Bauer (*1924) ist ein deutscher<br />
Informatik-Pionier. Er konstruierte in<br />
den 1950er Jahren Verschlüsselungsmaschinen<br />
und eine elektromechanische „aussagenlogische<br />
Maschine“ (STANISLAUS). Ab 1956<br />
beteiligte er sich an der internationalen Zusammenarbeit<br />
zur Schaffung der Programmiersprache<br />
Algol 60, 1957 erfand er das<br />
Prinzip des Stacks. Er hielt 1967 an der TU<br />
München die erste offizielle Informatikvorlesung<br />
in Deutschland und prägte 1968 den<br />
Begriff „Software Engineering“.<br />
Friedrich Ludwig Bauer<br />
Heinz Rutishauser<br />
Aus: Friedrich L. Bauer: Über „Episoden aus den Anfängen der Informatik an<br />
der <strong>ETH</strong>“ von A. Speiser. Informatik-Spektrum 31(6), Dez. 2008, S. 600-612<br />
211<br />
90
H. Rutishauser: Automatische Rechenplanfertigung<br />
bei programmgesteuerten<br />
Rechenmaschinen. (Mitteilungen aus<br />
dem Institut für angewandte Mathematik<br />
an der <strong>ETH</strong>. <strong>Zürich</strong> Nr. 3.) 45 S.<br />
m. 8 Abb. und 3 Strukturdiagrammen.<br />
Basel 1952, Verlag Birkhäuser, 5,70 SFr<br />
214<br />
Auszug aus Rutishausers „Automatische<br />
Rechenplanfertigung“<br />
Blätter<br />
Innere<br />
Baumknoten<br />
216<br />
91
Friedrich L. Bauer erinnert sich…<br />
„Konrad Zuse hatte 1944 die Idee eines ‚Planfertigungsgeräts‘ (er benützte<br />
den Ausdruck ‚Plan‘ statt ‚Programm‘) zur Erleichterung der Programmierung<br />
der Z4, die er aber in den Wirren des letzten Kriegsjahrs nicht<br />
mehr weiterverfolgen konnte. … Rutishauser hatte jedenfalls schon 1951<br />
den glänzenden Einfall, statt ein eigenes Gerät zu bauen, die Rechenanlage<br />
selbst zur Planfertigung zu benützen, also ein ‚programmierendes<br />
Programm‘, wie es Andrei Ershov bald darauf nannte, einzusetzen. …<br />
‚That the same computer that solved a problem could prepare its own instructions<br />
was a critical moment in the birth of software‘ (Paul E. Ceruzzi,<br />
1998). Rutishausers Algorithmus … bewirkt den Abbau der durch die Klammern<br />
geschachtelten Strukturen, wie sie beispielsweise in der üblichen<br />
Infix-Schreibweise arithmetischer oder logischer Ausdrücke vorkommen,<br />
von innen nach aussen. Aus der Formel wird bei Rutishauser ein dem<br />
Syntaxbaum und der Schachtelstruktur äquivalentes ‚Klammergebirge‘<br />
hergestellt, das die vorgeschriebene Auswertungsreihenfolge eindeutig<br />
festlegt. Das zugehörige Programm … ist unmittelbar ablesbar beziehungsweise<br />
erzeugbar.“<br />
217<br />
Friedrich L. Bauer erinnert sich…<br />
„Rutishauser nahm noch an, dass die Formel explizit geklammert ist,<br />
und unter dieser Annahme zeigte Corrado Böhm 1952, dass man die<br />
Auswertung auch sequentiell, normalerweise von links nach rechts,<br />
vornehmen kann. In üblicher Schreibweise wird jedoch unter Annahme<br />
einer Präzedenz der Multiplikation über der Addition und der Subtraktion<br />
auf die vollständige Klammerung verzichtet. In FORTRAN wurde<br />
ab 1954 (P.B. Sheridan) durch einen vorgeschalteten Durchlauf die<br />
Klammerung vervollständigt. L.KalmarmachtedazudenwitzigenVorschlag,<br />
das Multiplikationszeichen × überall durch die Folge )×( zu ersetzen<br />
(und die ganze Formel extra einzuklammern). Die entstehenden<br />
redundanten Klammern störten Kalmar nicht.“<br />
218<br />
92
Friedrich L. Bauer erinnert sich weiter<br />
„Wir verbesserten das Programm von Rutishauser, das eine Springprozession<br />
über die Formel vollführte, zu einem streng sequentiellen Verfahren,<br />
das auch auf die vollständige Klammerung verzichtete unter<br />
Einführung von Operator-Rangordnungen. Das wurde die Grundlage<br />
unserer späteren Patentanmeldung von 1957, wobei es das ‚Kellerprinzip‘<br />
verwendete, nämlich den im Rechner STANISLAUS eingeführten<br />
mehrgeschossigen Speicher vom (last in - first out)-Typus.<br />
Klaus Samelson hatte auch die Idee, neben dem ‚Zahlkeller‘ fürZwischenergebnisse<br />
des STANISLAUS auch einen ‚Operationskeller‘ zu<br />
verwenden, um die jeweils ihres Ranges wegen ‚zurückgestellten‘<br />
Operationen zu kellern.“<br />
„Wie sehr das Kellerprinzip inzwischen die Informatik durchdrungen hat, zeigt<br />
folgender kleiner Vorfall: Als ich kürzlich einem jungen Mitarbeiter gegenüber<br />
äusserte, Prof. Bauer habe den Kellerspeicher erfunden, fragte er: ‚Was gab<br />
es denn da zu erfinden? Das ist doch einfach ein Stack!‘ “ -- Fritz Lehmann<br />
219<br />
Friedrich L. Bauer schrieb schon 1960…<br />
Auszug aus: K. Samelson, F. L. Bauer: Sequential Formula Translation,<br />
Communications of the ACM 3(2), pp. 76-83, Feb. 1960<br />
220<br />
93
Rutishauser (li.) und Speiser (re.) an<br />
der Zuse Z4-Rechenanlage der <strong>ETH</strong><br />
221<br />
Heinz Rutishauser (1918-1970)<br />
• 1942 Diplom <strong>ETH</strong> in Mathematik<br />
• 1948 Dissertation <strong>ETH</strong><br />
• 1949-1955 ERM<strong>ETH</strong>-Entwicklung<br />
(mit Ambros Speiser bei Prof. Stiefel)<br />
• 1951 Habilitation „Automatische<br />
Rechenplanfertigung“, <strong>ETH</strong><br />
• Ab 1955 Professor an der <strong>ETH</strong><br />
• Entscheidende Beiträge zur<br />
Programmiersprache ALGOL<br />
(Bild: <strong>ETH</strong>-Bibliothek, Com_M04-0265-0002)<br />
Ambros Speiser (li.) und Heinz Rutishauser (re.)<br />
an der Konsole von Zuses Z4-Rechenanlage, 1955.<br />
(Mit der Z4 hatte die <strong>ETH</strong> ab 1950 als erste Universität<br />
Kontinentaleuropas einen Computer)<br />
222<br />
94
223<br />
Heinz Rutishauser<br />
„1968 war Rutishauser im Besitz eines Rufes<br />
nach München auf eine Professur, die<br />
mit der Leitung des Leibniz-Rechenzentrums<br />
verbunden war. Es gelang mir im<br />
Mai 1968 nicht, ihn zu überreden, den Ruf<br />
anzunehmen; er nannte mir damals gesundheitliche<br />
Gründe und seine Befürchtungen<br />
waren, wie sich leider herausstellte, nicht<br />
grundlos. Dass er in <strong>Zürich</strong> verblieb, hat<br />
dann dem Aufbau der zunächst Computerwissenschaften<br />
genannten Informatik sehr<br />
geholfen.“<br />
Aus F.L. Bauer „Computer und Algebra“ (in: Zwanzig Jahre Institut für<br />
Informatik , Bericht des Instituts für Informatik, <strong>ETH</strong> <strong>Zürich</strong>, 1988)<br />
224<br />
95
Konrad Zuse (1910-1995) präsentiert seinen Z4-Computer.<br />
„Immerhin besass das verschlafene <strong>Zürich</strong> durch die ratternde Z4 ein, wenn auch bescheidenes,<br />
Nachtleben. Ich selbst besass einen Schlüssel zum Ha<strong>up</strong>tgebäude der <strong>ETH</strong>, und manches<br />
Mal bin ich spät in der Nacht durch die einsamen <strong>Zürich</strong>er Gassen gegangen, um nach<br />
der Z4 zu sehen. Es war ein eigenartiges Gefühl, in die menschenleere <strong>ETH</strong> einzutreten und<br />
bereits im Parterre zu hören, dass die Z4 im obersten Stock noch einwandfrei arbeitete.“<br />
225<br />
Die Z4 an der <strong>ETH</strong> <strong>Zürich</strong><br />
HG G 39<br />
226<br />
96
Der elektromechanische<br />
Computer Z4 von Zuse<br />
• Konstruiert 1942-1945 in Berlin<br />
• 2200 Telefonrelais, Gewicht ca. 1 t<br />
• 64 mechanische Speicherplätze für Zahlen<br />
• Aber kein Speicher für Befehle<br />
• Programme („Rechenpläne“) wurden auf<br />
Lochstreifen gestanzt<br />
• Hierzu dienten gebrauchte 35-mm-Kinofilme<br />
• Im Bild rechts: Lochstreifenstanzer sowie Lochstreifenabtaster<br />
für das Ha<strong>up</strong>tprogramm, links:<br />
Abtaster für ein Unterprogramm als Schleife<br />
• Rechenwerk im Dualsystem, Zahlen<br />
in Gleitpunktdarstellung<br />
• Ca. 1 s pro Befehl, ca. 3 s pro<br />
arithmetischer Operation<br />
Bildquellen: www.spiegel.de/fotostrecke/fotostrecke-56183-3.html,<br />
www.ethistory.ethz.ch/rueckblicke/departemente/dinfk/bilder/1951_z4-lochstreifen.jpg<br />
228<br />
Die Z4<br />
„Kommandostand“<br />
Relais-Schränke<br />
Schaltpult<br />
Lochstreifenabtaster<br />
Mechanischer Speicher<br />
http://farm3.staticflickr.com/2208/1814569165_5348377b80_o_d.jpg<br />
230<br />
97
Die Z4, beschrieben von Eduard Stiefel<br />
„Im Vordergrund steht das Schaltpult; es enthält in seinem Mittelstück zwei Abtaster und einen<br />
Locher für die Lochstreifen, links die Tastatur zum Eingeben von Zahlen und ein Lampenfeld<br />
zum Ablesen von solchen. Der rechte Teil dient der Herstellung von Programmen; durch<br />
Betätigung der unten sichtbaren Tasten werden die Befehle auf einen Filmstreifen gelocht.<br />
Hinter der elektrisch gesteuerten Schreibmaschine ist das Speicherwerk angebracht […].<br />
Rechts und im Hintergrund stehen die Schränke für Rechenwerk und Speicherwerk; die Z 4<br />
verwendet als Schaltelemente gewöhnliche Telefon-Relais und Schrittschalter.<br />
[…] zeigt die beiden Abtaster während des Durchrechnens eines mathematischen Problems.<br />
Im rechten Abtaster liegt das Ha<strong>up</strong>tprogramm, auf welchem etwa ein Integrationsschritt zur<br />
Lösung einer Differentialgleichung programmiert ist. Der linke Abtaster verarbeitet ein Unterprogramm,<br />
und zwar das Ausziehen einer Wurzel auf iterativem Weg. Man beachte, daß<br />
dieses Programm eine endlose Schleife ist; ein einmaliger Umlauf desselben ergibt einen<br />
Iterationsschritt. Während die Rechnung automatisch abläuft, kann man nun folgendes Spiel<br />
beobachten. Sobald das Ha<strong>up</strong>tprogramm an die Stelle kommt, wo eine Wurzel berechnet<br />
werden muß, bleibt es stehen und veranlaßt durch einen Sprungbefehl das Anlaufen des<br />
Unterprogramms. (Unbedingter Sprung.) Dieses macht so viele Umläufe, das heißt berechnet<br />
so viele immer bessere Annäherungen an den Wurzelwert, bis die Rechnung steht; dann<br />
nimmt das Ha<strong>up</strong>tprogramm seine Arbeit wieder auf.“<br />
In: Rechenautomaten im Dienste der Technik. Erfahrungen mit dem Zuse-Rechenautomaten Z4.<br />
Sonderdruck aus Arbeitsgemeinschaft für Forschung des Landes Nordrhein-Westfalen, Bd. 45,<br />
Köln 1954.<br />
231<br />
Der mechanische Speicher der Z4<br />
http://www.digitalbrainstorming.ch/weblog/Abb.%201%20Mechanischer%20Speicher%20Z4-pano.jpg<br />
233<br />
98
Konrad Zuse<br />
• Konrad Zuse (1910 - 1995) schloss 1935 sein Ingenieurstudium<br />
in Berlin ab. Danach arbeitete er<br />
zunächst als Statiker in der Flugzeugindustrie,<br />
gab diese Stelle jedoch bald auf und richtete eine<br />
Erfinderwerkstatt in der elterlichen Wohnung ein.<br />
• Mit seiner Entwicklung der Z3 im Jahre 1941 baute<br />
er den ersten vollautomatischen, programmgesteuerten<br />
und frei programmierbaren, in binärer Gleitkommarechnung<br />
arbeitenden Computer der Welt.<br />
• Zu Kriegsende 1945 Flucht aus Berlin über Göttingen in das Allgäu,<br />
wobei er den zuletzt entstandenen Rechner Z4 retten konnte.<br />
• 1949 spürte Prof. Eduard Stiefel von der <strong>ETH</strong> <strong>Zürich</strong> Zuse im<br />
Allgäu auf und liess sich die Z4 vorführen. Durch ihn kam ein<br />
grosszügiger Mietvertrag mit der <strong>ETH</strong> zustande, der Zuse die<br />
notwendigen Mittel verschaffte, um die Zuse KG zu gründen.<br />
• Zuse erhielt 1991 die Ehrendoktorwürde der <strong>ETH</strong> <strong>Zürich</strong>.<br />
Quelle u.a. Wikipedia<br />
Eduard Stiefel<br />
234<br />
Höhere Mathematik auf Knöpfen<br />
DER SPIEGEL 7. Juli 1949 (Auszug)<br />
Spiegel-Titel 28/1949<br />
Als er sich als Berliner TH-Student mit langen statischen Berechnungen<br />
herumquälte, kam ihm der Gedanke, „die geistigen<br />
Kräfte des Menschen zu verstärken, indem Maschinen<br />
zur Lösung von Aufgaben herangezogen werden, die bisher<br />
einen großen Teil der geistigen Arbeitskraft gebunden hatten“.<br />
„Ich war zu faul zum Berechnen“, interpretiert der lange Mann<br />
in den geflickten Mechanikerhosen heute seinen eigenen Broschürentext.<br />
Aus 15 000 Meter Draht, 20 000 Lötstellen, 2000 Telephonrelais,<br />
2500 kg Material, 250 000 Mark und 100 000 Arbeitsstunden<br />
entstand ein Gerät, das einen normalen Möbelwagen<br />
bequem ausfüllt. In diesem Möbelwagen fährt V 4 jetzt<br />
nach Hünfeld (Kreis Fulda). Dort soll sie<br />
Originalname der Z4<br />
der deutschen Wissenschaft rechnen helfen.<br />
In den USA existieren ähnliche Geräte wie V 4 unter dem Namen „Maschinen-Gehirn“. Sie arbeiten<br />
aber auf anderer Basis. Zuse ersetzte 18000 US-Elektronenröhren durch mechanische Einrichtungen.<br />
Die amerikanische Maschine kostet 400000 Dollar, wiegt 20 Tonnen und hat Stromlinienform.<br />
Auf den 149 Knöpfen und Tasten des V-4-Kommandostandes spielt Zuse höhere Mathematik. Daneben<br />
hängen unzählige verschieden gelochte Filmstreifen. Jede Lochung bedeutet eine mathematische<br />
Formel, von der einfachsten Wurzel bis zur schwierigsten Gleichung mit zehn Unbekannten.<br />
237<br />
99
Höhere Mathematik auf Knöpfen<br />
Um eine Rechenaufgabe zu lösen, wird der entsprechende Streifen in V 4 eingehängt. Paulas<br />
flinke Hände tippen auf der Tastatur die jeweiligen Zahlen, die je nach Bedarf von der V 4<br />
addiert, subtrahiert, multipliziert, dividiert oder gewurzelt oder alles auf einmal werden.<br />
Wenn die 60 Volt Gleichstrom durch die 20 Kilometer Draht gejagt sind, leuchtet das Ergebnis<br />
hinter weißen Glasscheiben auf. Eine schwierige Berechnung, zu der ein Stab von<br />
Wissenschaftlern Tage braucht, löst V 4 in Minuten.<br />
Zur V 4 gehört noch ein „mechanisches Gedächtnis“ in der Größe eines mittleren Kleiderschrankes.<br />
Dessen Einzelteile schnitt Zuse aus US-Konservenblech. Das Gedächtnis<br />
„notiert“ automatisch Zwischenergebnisse.<br />
Konrad Zuse braucht 20 000 DM, um der V 4 ein New-Look-Kleid zu geben. Im Augenblick<br />
sieht sie aus wie eine Großstadttelephonzentrale nach einem Erdbeben. Außerdem sucht<br />
er Geldgeber, um den Serienbau der Rechen- und der Problemmaschine zu beginnen.<br />
„20 000 DM zum Weiterexperimentieren täten es auch schon.“<br />
Das Ausland zeigt sich an V 4 interessiert. Aber Zuse wartet ab.<br />
Der Mietvertrag mit der <strong>ETH</strong> wurde am 7.9.1949 in der Gaststätte des Bad. Bahnhofs<br />
in Basel unterzeichnet; ein <strong>ETH</strong>-Schulratsprotokoll vom 6.10.1949 vermerkt:<br />
„Im Juli 1949 erfuhr die Kommission von einer Rechenmaschine des deutschen<br />
Ingenieurs Zuse, die von der <strong>ETH</strong> zu aussergewöhnlich günstigen Bedingungen<br />
übernommen werden konnte. Prof. Stiefel und Dr. Lattmann besichtigten die<br />
Apparatur. Sie rühmten besonders deren mathematische Disposition…“<br />
Bereits am 11.7.1950 wurde die generalüberholte Z4 an der <strong>ETH</strong> <strong>Zürich</strong> installiert.<br />
xx<br />
238<br />
<strong>ETH</strong>-Schulratsprotokoll vom 8.10.1949 zur Z4<br />
239<br />
100
<strong>ETH</strong>-Schulratsprotokoll vom 8.10.1949 zur Z4<br />
240<br />
<strong>ETH</strong>-Schulratsprotokoll vom 12.7.1952 zur Z4<br />
241<br />
101
ERM<strong>ETH</strong> - Elektronische Rechenmaschine der <strong>ETH</strong><br />
"Bereits kurz nach der Gründung des Instituts<br />
für angewandte Mathematik im Jahr<br />
1948 begann die Planung zum Bau eines<br />
eigenen Computers. Zu Beginn der 1950er<br />
Jahre waren auf kommerzieller Basis noch<br />
keine programmierbaren Rechner mit Speicher<br />
erhältlich, die für wissenschaftliches<br />
Rechnen geeignet gewesen wären. Deshalb<br />
auch die Idee zu einer Eigenentwicklung von<br />
Grund auf… "<br />
www.ethistory.ethz.ch/rueckblicke/departemente/dinfk/weitere_seiten/ermeth/index_DE<br />
ERM<strong>ETH</strong> („Elektronische Rechenmaschine der<br />
<strong>ETH</strong>“), gebaut 1948-1957 an der <strong>ETH</strong> <strong>Zürich</strong><br />
und genutzt bis 1963, Nachfolgesystem der Z4.<br />
242<br />
ERM<strong>ETH</strong><br />
• 1500 Elektronenröhren<br />
• Arbeitsspeicher: Magnettrommel<br />
mit Platz für 10000<br />
Wörter zu 14 Dezimalziffern<br />
• 1,5 Tonnen schwer<br />
• 6000 Umdrehungen pro Minute<br />
• Entwicklung kostete eine Million Franken<br />
• Ca. 100 Mal schneller als die Z4<br />
• Leistungsaufnahme von 30 kW<br />
• Die ERM<strong>ETH</strong> reagierte empfindlich auf Schwankungen der Netzspannung,<br />
etwa wenn morgens die Trams den Betrieb aufnahmen<br />
• Wesentliche Teile im Museum für Kommunikation in Bern<br />
243<br />
102
Ambrosius Speiser (1922 – 2003)<br />
• Studium der Elektrotechnik an der <strong>ETH</strong><br />
• 1950 Dissertation bei Eduard Stiefel<br />
• Entwurf eines elektronischen Rechengerätes unter besonderer<br />
Berücksichtigung der Erfordernis eines minimalen Materialaufwandes<br />
bei gegebener mathematischer Leistungsfähigkeit<br />
• Technische Leitung der ERM<strong>ETH</strong>-Entwicklung<br />
• Gründungsdirektor (ab 1955) des IBM-<br />
Forschungslabors in Rüschlikon<br />
• Gründungsdirektor (ab 1966) des Forschungszentrums<br />
von Brown, Boveri & Cie. (später:<br />
ABB) in Dättwil bei Baden<br />
• 1986 Ehrendoktorwürde der <strong>ETH</strong> <strong>Zürich</strong><br />
244<br />
Z4 und ERM<strong>ETH</strong> – Heinz Waldburger erinnert sich<br />
Quelle: Herbert Bruderer: Konrad Zuse und die Schweiz. Oldenbourg-Verlag, 2012<br />
• Studium an der <strong>ETH</strong>, ab 1953 Hilfsassistent von Rutishauser und Stiefel,<br />
später Informatikchef der Firma Nestlé<br />
• „Im <strong>ETH</strong>-Vorlesungsverzeichnis des Sommersemesters 1952 wurde angekündigt:<br />
Praktikum an der programmgesteuerten Rechenmaschine<br />
Z4 (Zuse) am Institut für angewandte Mathematik. Prof. Stiefel, Dr.<br />
Rutishauser, Dr. Speiser. […] Wir waren wenige Hörer, vielleicht ein<br />
Dutzend Mathematiker aus der Industrie: BBC, Sulzer usw. […] Meine<br />
Vorlesungsnotizen zeigen, dass Stiefel nicht in Erscheinung trat. Rutishausers<br />
56 Seiten betrafen folgende Themen: Struktur einer programmgesteuerten<br />
Rechenmaschine, externes Rechenprogramm, Rechenbefehle,<br />
Flussdiagramm, numerische Anwendungen, sauber strukturiertes<br />
Programmieren, Rechnen mit Befehlen. Hinzu kamen die 16 Seiten der<br />
‚Bedienungsanweisung Z4‘. […] Die 20 Seiten Notizen zu Speisers Vorträgen<br />
erklärten das Funktionieren von elektrischen und elektronischen<br />
digitalen Schaltungen und deren Kombinationen für die Rechen- und<br />
Datenübertragungsfunktionen…“<br />
245<br />
103
Z4 und ERM<strong>ETH</strong> – Heinz Waldburger erinnert sich<br />
• „Das Wechselbad von Theorie und Praxis an der <strong>ETH</strong> war äusserst<br />
anregend. Rutishauser war dabei ein echter Meister, zurückhaltend,<br />
zusammenführend, stets hilfsbereit. Zuses geniale Erfindungen und<br />
sein einzigartiger Rechenautomat liessen an der <strong>ETH</strong> dank Stiefel,<br />
Rutishauser und Speiser eine eigenständige schweizerische lnformatikkultur<br />
wachsen. Nicht die beim Bau von Prozessoren und Magnetspeichern<br />
erworbenen technischen Kenntnisse überlebten, sondern Rutishausers<br />
Programmiergrundsätze. Sie flossen in die von ihm geschaffene<br />
Programmiersprache Algol ein. […] Die Studierenden in den ersten<br />
Z4- und ERM<strong>ETH</strong>-Jahren waren sich wohl nicht bewusst, dass 1952 in<br />
<strong>Zürich</strong> meines Wissens die erste Informatikvorlesung auf dem<br />
europäischen Kontinent stattfand.“<br />
Ende der historischen Notiz<br />
Zum Weiterlesen der Geschichte der Computer und der Informatik an der <strong>ETH</strong> <strong>Zürich</strong>: Andreas Nef, Tobias<br />
Wildi: Informatik an der <strong>ETH</strong> <strong>Zürich</strong> 1948–1981, Preprints zur Kulturgeschichte der Technik / 2007 / 21,<br />
<strong>ETH</strong> <strong>Zürich</strong>, Institut für Geschichte / Technikgeschichte. Auch in: Peter Haber (Hrsg.): Computergeschichte<br />
Schweiz – eine Bestandesaufnahme, <strong>Zürich</strong>, 2009. www.tg.ethz.ch/dokumente/pdf_Preprints/Preprint21.pdf<br />
246<br />
Compiler<br />
Bereits aus Teil I der<br />
Vorlesung bekannt<br />
• Übersetzt ein Programm einer (höheren) Programmiersprache<br />
(z.B. C++) ganz in Maschinensprache<br />
Ein Prozessor kann keine<br />
Programme einer höheren<br />
Sprache ausführen, sondern<br />
nur Maschinenprogramme<br />
Maschinensprache ist<br />
Menschen nicht zumutbar<br />
Transformation muss bedeutungsäquivalent<br />
sein!<br />
247<br />
104
Interpreter<br />
• Programm, welches ein Programm einer anderen<br />
Programmiersprache Anweisung für Anweisung<br />
nur intern „übersetzt“ und diese jeweils unmittelbar<br />
ausführt (im Gegensatz zu einem Compiler)<br />
• Typisch für Dialog-, Skriptund<br />
Kommandosprachen<br />
• z.B. Perl, Python, oder Ruby<br />
• Vorteil: Einfaches Testen,<br />
da Programm sofort (weiter)<br />
ausführbar nach Änderung<br />
Eingabe-<br />
Daten<br />
Bereits aus Teil I der<br />
Vorlesung bekannt<br />
Java-<br />
Programm<br />
Java<br />
Interpreter<br />
Maschine<br />
M<br />
Resultat des<br />
Java-<br />
Programms<br />
• Nachteil: Programmausführung dauert länger als die<br />
Ausführung übersetzter (compilierter) Programme<br />
• Analyse, Adressberechnung etc. wird in einer Schleife<br />
z.B. jedes Mal (statt einmalig) gemacht!<br />
248<br />
Java-Bytecode<br />
• Bytecode ist die Maschinensprache der Java-VM<br />
• Bytecode ist ziemlich kompakt: Die meisten Instruktionen<br />
(„Operationen“) sind nur 1 Byte (= 8 Bit) lang<br />
• Kennzeichnung durch einen 8-Bit-Operationscode<br />
• Haben zusätzlich auch eine „symbolische“ Bezeichnung, z.B.:<br />
• add mit Operationscode 01100000<br />
(dezimal 96, hexadezimal 0x60)<br />
• iconst_3 mit Operationscode 00000100<br />
• pop mit Operationscode 01010111<br />
249<br />
105
Übersetzung in Java-Bytecode<br />
static int p1(int p) {<br />
int i;<br />
i = 5;<br />
int j = 7;<br />
int k = j+i+j+i+3;<br />
return k;<br />
}<br />
• Die Speicherplätze für Variablen werden<br />
vom Compiler durchnummeriert<br />
• p auf Platz 0, i auf 1, j auf 2, k auf 3<br />
• Diese Nummern werden dann bei istore<br />
und iload als „Adressen“ verwendet<br />
250<br />
Übersetzung in Java-Bytecode (2)<br />
static void q() {<br />
int i=0, j=0;<br />
boolean b = false;<br />
b = (4*i + (3+j)*2) > j+7*i;<br />
b = b & (33 > 2+j);<br />
}<br />
Mit dem Konsolen-Kommando<br />
javap -c xx<br />
kann man sich den Java-Bytecode der<br />
Klasse xx in symbolischer Form anzeigen<br />
lassen, den der Java-Compiler (z.B. in<br />
der Datei xx.class) generiert hat.<br />
Tipp: Dieses ausprobieren, man<br />
macht interessante Entdeckungen!<br />
0 iconst_0<br />
1 istore_0<br />
2 iconst_0<br />
3 istore_1<br />
4 iconst_0<br />
5 istore_2<br />
6 iconst_4<br />
7 iload_0<br />
8 imul<br />
9 iconst_3<br />
10 iload_1<br />
11 iadd<br />
12 iconst_2<br />
13 imul<br />
14 iadd<br />
15 iload_1<br />
16 bipush 7<br />
18 iload_0<br />
i<br />
j<br />
b<br />
19 imul<br />
20 iadd<br />
21 if_icmpgt 28<br />
24 iconst_0<br />
25 goto 29<br />
28 iconst_1<br />
29 istore_2<br />
30 iload_2<br />
31 iconst_2<br />
32 iload_1<br />
33 iadd<br />
34 bipush 33<br />
36 if_icmplt 43<br />
39 iconst_0<br />
40 goto 44<br />
43 iconst_1<br />
44 iand<br />
45 istore_2<br />
251<br />
106
Eine Auswahl von VM-Instruktionen<br />
• Instruktionen operieren i.Allg. auf dem Operandenstack<br />
• Aber einige dienen auch nur der Steuerung des Kontrollflusses<br />
• Instruktionen werden durch einen Operationscode<br />
gekennzeichnet (1 Byte, daher max. 256 Instruktionen)<br />
• Evtl. enthalten nachfolgende Bytes auch Operanden (z.B. Index<br />
einer Variablen oder einen direkten Wert, wie z.B. bei bipush);<br />
Instruktionen können daher auch zwei oder mehr Byte lang sein<br />
• Neben nachfolgenden Instruktionen gibt es noch weitere<br />
• Siehe dazu z.B. Tim Lindholm, Frank Yellin: The Java Virtual Machine<br />
Specification, Addison-Wesley, 1999 (ISBN 0-201-43294-3), online bei:<br />
http://docs.oracle.com/javase/specs/jvms/se5.0/html/VMSpecTOC.doc.html<br />
• Zur Java-VM generell (und Bytecode) siehe auch Bill Venners:<br />
Inside the Java Virtual Machine, 2000 (ISBN 0-07-135093-4) ,<br />
insbes. Kapitel 5, online bei www.artima.com/insidejvm/ed2/jvm.html<br />
252<br />
Eine Auswahl von VM-Instruktionen (2)<br />
iconst_m1<br />
Push the integer -1 onto the stack.<br />
iconst_0, ... iconst_5<br />
Push the integer 0,..., 5 onto the stack.<br />
iload_vindex<br />
The value of the local variable at<br />
vindex in the current Java frame is<br />
pushed onto the operand stack.<br />
istore_vindex<br />
Local variable vindex in the current<br />
Java frame is set to value.<br />
bipush byte<br />
Push one-byte signed integer.<br />
pop<br />
Pop top stack word from the stack.<br />
iadd<br />
Value1 and value2 must be integers.<br />
The values are added and replaced<br />
on the stack by their integer sum.<br />
imul<br />
...replaced on the stack by their<br />
integer product.<br />
nop<br />
Do nothing<br />
vindex: die Variablen eines „frames“ sind durchnummeriert;<br />
vindex ist dabei die Nummer („index“) einer Variablen<br />
253<br />
107
5.<br />
Pakete in Java<br />
Buch Mark Weiss „Data Structures & Problem Solving Using Java“ siehe:<br />
- 126-131 (Pakete)<br />
- 635-639 (Stack als Liste)<br />
- 122-126 („BigRational class“)<br />
254<br />
Pakete in Java<br />
• Paket = (zusammengehörige) Menge von Klassen<br />
• Sowie Unterpakete und Interfaces<br />
• Hierarchischer Aufbau<br />
• Paket „xyz“ im Paket „java“ → „java.xyz“<br />
• Wichtig für Strukturierung und Zugriffskontrolle<br />
• Klassen und Methoden (bzw. Attribute von Klassen) sind (ohne<br />
weitere Angaben) nur im eigenen Paket sichtbar und zugreifbar<br />
• Klassen befinden sich (logisch) immer in Paketen<br />
• Paketdeklaration direkt am Anfang einer Quelldatei, z.B.<br />
package abc;<br />
• Konvention: kleingeschriebene Namen<br />
• Falls package-Deklaration fehlt: „unnamed package“<br />
255<br />
108
Pakete in Java (2)<br />
• Attribute bzw. Methoden von Klassen können vollqualifiziert<br />
(d.h. mit dem Paketnamen) benannt werden<br />
• Z.B.: java.lang.String.substring<br />
Paket Klasse Methode<br />
• Importieren von Klassen (als Namensabkürzung)<br />
• Z.B. import java.util.Random<br />
(es wird diese Klasse importiert; kann mit Name „Random“ benutzt werden)<br />
• Oder import java.util.*<br />
(es wird alles aus diesem Paket importiert)<br />
• Das Java-System bietet eine Reihe von Standardpaketen<br />
mit nützlichen Klassen und Methoden für fast jeden Zweck an<br />
• Z.B. java.lang, java.io, java.net, java.applet, java.util,...<br />
256<br />
257<br />
109
Eigene Pakete<br />
Beispiel.java:<br />
import myPack.*;<br />
class Beispiel {<br />
...main...{<br />
Stack S;<br />
...<br />
Queue Q;<br />
}<br />
}<br />
Alternativ zu „import“: jew.<br />
vollqualifizierter Zugriff,<br />
also z.B. myPack.Queue<br />
Stack.java:<br />
package myPack;<br />
public class Stack<br />
{...}<br />
class Hilfselement<br />
{ void setzen()<br />
{...}<br />
}<br />
Queue.java:<br />
package myPack;<br />
public class Queue<br />
{...}<br />
class x<br />
{ private void y()<br />
{...}<br />
}<br />
Gemeinsames Paket (aber getrennte Dateien, da jede<br />
Datei nur eine einzige public-Klasse enthalten darf)<br />
• Die Klasse Stack in der Datei Stack.java (bzw. Queue in Queue.java) muss<br />
mit dem Zugriffsmodifikator „public“ qualifiziert werden, da diese Klassen<br />
ausserhalb des Paketes MyPack (in Beispiel.java) verwendet werden<br />
• Entsprechendes würde für öffentl. Methoden / Attribute von Stack und Queue gelten<br />
• Die Methode „setzen“ ist innerhalb von myPack (also z.B. von Queue aus)<br />
zugreifbar, nicht aber von ausserhalb des Paketes (z.B. von Beispiel aus)<br />
• y ist nur von Methoden der Klasse X aus zugreifbar (da „private“)<br />
258<br />
Beispiel 1: Ein Paket für int-Listen<br />
• Es geht um verkettete Listen der folgenden Art:<br />
25<br />
17 39<br />
• Nutzer unseres Paketes sollen „Listenelemente“ am Anfang<br />
(am Listenkopf: „head“) hinzufügen und entfernen können<br />
• add_head, remove_head<br />
• Ausserdem sollen sie weitere nützliche Methoden bekommen:<br />
• Feststellen, ob die Liste leer ist<br />
• Wie viele Elemente sie enthält<br />
259<br />
110
Beispiel 1: Ein Paket für int-Listen (2)<br />
package listPack;<br />
class ListElem {<br />
int val;<br />
ListElem next;<br />
ListElem ist ausserhalb<br />
des Paketes<br />
nicht sichtbar<br />
39<br />
}<br />
public ListElem(int i, ListElem e) { Konstruktor<br />
val = i;<br />
next = e;<br />
}<br />
Hierauf soll das neue<br />
Listenelement zeigen<br />
public class List {<br />
...<br />
}<br />
Nur eine einzige Klasse<br />
pro Datei darf public sein<br />
Fortsetzung des Paketes „listPack“<br />
auf der nächsten slide <br />
260<br />
public class List {<br />
private ListeElem first = null;<br />
private int size = 0;<br />
private boolean empty() {<br />
return (size == 0);<br />
}<br />
public int size() {return size;}<br />
public void add_head(int i) {<br />
first = new ListElem(i,first);<br />
size++;<br />
}<br />
public int remove_head() {<br />
int i = first.val;<br />
first = first.next;<br />
size--;<br />
return i;<br />
}<br />
// evtl. weitere Methoden<br />
}<br />
Kein Zugriff von aussen<br />
Methode liefert den<br />
Wert der gleichnamigen<br />
privaten Variablen<br />
Neues Element vorne<br />
einketten<br />
Grösse auf privater<br />
Variablen mithalten<br />
Ausketten<br />
Wert des (ehemaligen)<br />
vordersten Elements<br />
zurückliefern<br />
261<br />
111
Ein Stack aus einer int-Liste<br />
Bereits als Übung in<br />
Teil I der Vorlesung<br />
• Unter Verwendung realisierter Datentypen (z.B. List)<br />
können neue Datentypen konstruiert werden, z.B. ein<br />
Stack mit push, pop (ohne Wertrückgabe) und top<br />
• Interne Realisierung von push bzw. pop (nach aussen<br />
nicht sichtbar):<br />
262<br />
import listPack.List;<br />
public class Stack {<br />
private List L;<br />
public Stack() {<br />
L = new List();<br />
}<br />
public boolean empty() {<br />
return (L.size() == 0);<br />
}<br />
public void push(int i) {<br />
L.add_head(i);<br />
}<br />
public void pop() {<br />
// Test, ob !empty()...<br />
L.remove_head();<br />
}<br />
public int top() {<br />
// Test, ob !empty()...<br />
int i = L.remove_head();<br />
L.add_head(i);<br />
return i;<br />
}<br />
}<br />
Im Konstruktor des Stacks muss<br />
eine (leere) Liste erzeugt werden!<br />
Der zurückgelieferte Wert wird<br />
einfach ignoriert!<br />
Da List keinen direkten Zugriff auf<br />
die Listenelemente gestattet und<br />
die Werte nicht direkt ermittelt<br />
werden können, verwenden wir<br />
diesen Trick<br />
263<br />
112
Stack: Implementierungsmöglichkeiten<br />
• Wir wissen bereits von früher,<br />
wie ein Stack mit einem Array<br />
implementiert werden kann:<br />
• Dagegen Implementierung<br />
mit verketteter Liste wie eben:<br />
]<br />
Array-Implementierung: effizient,<br />
aber der Stack ist a priori begrenzt.<br />
Der Stack ist so potentiell unbegrenzt,<br />
wegen „new“ jedoch etwas aufwendiger<br />
(langsamer) und hat Speicher-<br />
Overhead durch die next-Referenzen.<br />
264<br />
Austausch von Klassen als<br />
Dienstleistungsanbieter<br />
• Eine Klasse als „Dienstleistungsanbieter“ kann „rücksichtslos“ gegen<br />
eine mit gleicher Schnittstelle (public-Variablen und -Methoden)<br />
sowie gleicher externer Wirkung ausgetauscht werden<br />
• Aber kann ein Array-basierter Stack (feste Maximalgrösse!) wirklich<br />
exakt das gleiche Verhalten aufweisen wie ein listenbasierter Stack?<br />
265<br />
113
Beispiel 2: Paket „Bruchrechnen“<br />
Bruch = Zähler<br />
Nenner<br />
• Paket, um mit rationalen Zahlen in Bruchform zu rechnen<br />
• Vermeidet so evtl. Rundungsfehler, die bei float zu erwarten<br />
sind (hier nur für nicht-negative Brüche realisiert)<br />
package bruchPak;<br />
public class Bruch {<br />
private long Zaehler = 0;<br />
private long Nenner = 1;<br />
public Bruch(long z, long n) {setzen(z,n);}<br />
public Bruch(long z) {setzen(z,1);}<br />
public Bruch() {setzen(0,1);}<br />
public void setzen(long z, long n) {<br />
if (z < 0 || n
Paket „Bruchrechnen“ (3)<br />
public Bruch plus(Bruch y) {<br />
long n = kgV(Nenner, y.Nenner);<br />
long z = (n/Nenner)*Zaehler + (n/y.Nenner)*y.Zaehler;<br />
}<br />
Bruch t = new Bruch(z,n);<br />
t.kuerzen();<br />
return t;<br />
public Bruch mult(Bruch y) {<br />
return new Bruch(Zaehler*y.Zaehler,Nenner*y.Nenner);<br />
}<br />
private void kuerzen() {<br />
if (Zaehler == 0)<br />
Nenner = 1;<br />
else {<br />
long g = ggT(Zaehler,Nenner);<br />
Zaehler = Zaehler/g;<br />
Nenner = Nenner/g;<br />
}<br />
}<br />
} // Ende der Klasse Bruch<br />
Das Ergebnis einer Addition<br />
ist ein neuer Bruch<br />
Durch die Multiplikationen kann es<br />
leicht zu einem Überlauf kommen<br />
besser: Faktoren frühzeitig kürzen,<br />
um das Probelm zu minimieren<br />
'kuerzen' ist als eine<br />
instanzenbasierte Funktion<br />
realisiert, die aber von<br />
aussen nicht zugänglich ist<br />
268<br />
Nutzung des Pakets „Bruchrechnen“<br />
import bruchPak;<br />
...<br />
Bruch a = new Bruch();<br />
Bruch b = new Bruch(1,2);<br />
Bruch c = new Bruch(5);<br />
a.drucken();<br />
a.setzen(2,3);<br />
a.drucken();<br />
b.drucken();<br />
c.drucken();<br />
c = a.mult(b);<br />
c.drucken();<br />
(a.plus(b)).drucken();<br />
c = a.plus(b.plus(a));<br />
c.drucken();<br />
c = c.plus(new Bruch(1,8));<br />
c.drucken();<br />
public Bruch(long z, long n)<br />
{ setzen(z,n); }<br />
public Bruch(long z)<br />
{ setzen(z,1); }<br />
public Bruch() { setzen(0,1); }<br />
0/1 = 0<br />
2/3 = 0.666667<br />
1/2 = 0.5<br />
5/1 = 5<br />
1/3 = 0.333333<br />
7/6 = 1.16667<br />
11/6 = 1.83333<br />
47/24 = 1.95833<br />
270<br />
115
Nutzung des Pakets „Bruchrechnen“ (2)<br />
c = (new Bruch(7,12)).plus(new Bruch(1,6));<br />
c.drucken();<br />
2/3 1/2<br />
System.out.println(a.kleiner(b));<br />
System.out.println(b.kleiner(a));<br />
c = a;<br />
System.out.println(a.identisch(c));<br />
System.out.println(a.identisch(b));<br />
a.setzen(0,3);<br />
b.setzen(0,4);<br />
c = a.plus(b);<br />
c.drucken();<br />
c.setzen(3,0);<br />
3/4 = 0.75<br />
false<br />
true<br />
true<br />
false<br />
0/1 = 0<br />
*** Fehler Wertebereich<br />
• Die interne Repräsentation von Zähler und Nenner durch „long“ ist<br />
beschränkt; man könnte sich andere Realisierungen vorstellen;<br />
z.B. bei der die einzelnen Ziffern von Zähler und Nenner in<br />
(potentiell beliebig langen) verketteten Listen gehalten werden.<br />
• Analoge Pakete auch für andere mathem. Objekte, z.B. Polynome, Matrizen...<br />
271<br />
Themen der Vorlesung<br />
1. Ein Algorithmus und seine<br />
Implementierung in Java<br />
2. Java: Elementare Aspekte<br />
3. Klassen und Referenzen<br />
4. Syntaxanalyse und Compiler<br />
5. Pakete in Java<br />
6. Objektorientierung<br />
7. Exceptions<br />
8. Binärbäume als<br />
Zeigergeflechte<br />
9. Binärsuche<br />
10. Backtracking<br />
11. Spielbäume<br />
12. Rekursives Problemlösen<br />
13. Komplexität von<br />
Algorithmen<br />
14. Simulation<br />
15. Heaps<br />
16. Parallele Prozesse<br />
und Threads<br />
273<br />
116
Resümee (5)<br />
• Arithmetische Infix-Ausdrücke<br />
• Codegenerierung für eine Stackmaschine<br />
• Interpreter für Infix-Ausdrücke ( „Taschenrechner“)<br />
• Übersetzung von Java nach Bytecode<br />
• Java-VM als Bytecode-Interpreter<br />
while (c == '+') { …<br />
stk.push(stk.pop()<br />
+ stk.pop()); }<br />
• Pakete in Java<br />
• Beispiel verkettete Liste / Stack<br />
• Beispiel Bruchrechnung<br />
import bruchPak; …<br />
a = b.plus(new Bruch(1,8));<br />
276<br />
6.<br />
Objektorientierung<br />
Aus Teil I der Vorlesung und aus C++ teilweise bekannt!<br />
Insbesondere wurde dort behandelt:<br />
- Basisklasse, abgeleitete Klasse, Vererbung („is-a“, „has-a“ etc.)<br />
-Dynamicbinding<br />
Buch Mark Weiss „Data Structures & Problem Solving Using Java“ siehe:<br />
- 105-109 (Objektorientierung)<br />
- 145-204 (Vererbung) 277<br />
277<br />
117
Objektorientiertes Programmieren<br />
• Weltsicht: Die Welt besteht aus verschiedenen<br />
interagierenden „Dingen“,<br />
welche sich klassifizieren lassen<br />
Simulationssprache SIMULA<br />
war die erste objektorientierte<br />
Programmiersprache (1967)<br />
• Ziel: Betrachteten Weltausschnitt<br />
strukturkonform mit kommunizierenden<br />
Objekten abbilden und modellieren<br />
278<br />
Objekte<br />
• Sind autonome, gekapselte Einheiten<br />
eines bestimmten Typs<br />
• Haben einen eigenen Zustand<br />
(= lokale Variablen)<br />
• Besitzen ein Verhalten<br />
(wenn sie aktiviert werden)<br />
• Bieten anderen Objekten Dienstleistungen an<br />
• Durchführung von Berechnungen<br />
• Änderungen des lokalen Zustandes<br />
• Zurückliefern von Variablenwerten oder<br />
Berechnungsergebnissen<br />
• Allgemein: „Reaktion“ auf Aufruf einer Methode<br />
279<br />
118
Objektorientierter Softwareentwurf<br />
• Strukturierung der Problemlösung als eine Menge<br />
kooperierender Objekte<br />
• Entwurf der Objekttypen, dabei ähnliche Objekte zu<br />
Klassen zusammenfassen<br />
• Herausfaktorisierung gemeinsamer Aspekte verschiedener<br />
Klassen ⇒ Hierarchie festlegen, Klassenbibliothek<br />
• Festlegung einzelner Dienstleistungen (→ Methoden)<br />
• Entwurf der Objektbeziehungen<br />
• Feinplanung der einzelnen Methoden, Festlegung der<br />
Klassenattribute etc.<br />
• Implementierung der Methoden<br />
(d.h. klassisches Programmieren im Kleinen)<br />
280<br />
Konzepthierarchie<br />
• Spezialisierung, Verallgemeinerung<br />
• Ein Rennfahrer ist ein spezieller Sportler<br />
• Ein Informatikstudent ist ein Mensch (mit Programmierkenntnissen)<br />
283<br />
119
Vererbung („inheritance“)<br />
• Alle Merkmale, Eigenschaften... des umfassenderen<br />
Begriffs werden an den Unterbegriff vererbt<br />
• Ein Rennfahrer hat mehr Eigenschaften als ein (allgemeiner) Sportler<br />
• Ein Rennfahrer erbt von zwei Oberkonzepten (Mehrfachvererbung)<br />
(Bemerkung: Mehrfachvererbung ist bei Java allerdings nicht möglich)<br />
284<br />
Vererbung („inheritance“) (2)<br />
• Ein Objekt (als „Begriffsinstanz“) ist oft polymorph<br />
• Ein Rennfahrer kann je nach Zweckmässigkeit auch<br />
nur als Sportler bzw. als Geschäftsmann oder schlicht<br />
einfach als Mensch betrachtet werden<br />
Vererbungsrelation<br />
ist transitiv: ein<br />
Bäcker hat alle Eigenschaften<br />
eines<br />
(„generischen“)<br />
Menschen<br />
285<br />
120
Klassenhierarchie als Konzeptbaum<br />
Basisklasse<br />
abgeleitete Klassen<br />
• Ein VW ist ein Personenwagen ist ein Auto ist ein Fahrzeug<br />
• Eine Trompete ist ein Blasinstrument ist ein Musikinstrument<br />
• Ein Fahrzeug hat Räder ⇒ ein Personenwagen hat Räder<br />
„is-a“-Relation<br />
286<br />
Abgeleitete Klassen, Redefinition<br />
• Eine abgeleitete Klasse besitzt automatisch alle<br />
Eigenschaften der zugehörigen Basisklassen<br />
• Konkret: besitzt alle Attribute und Methoden der Basisklassen<br />
• Ausser: Es werden explizit einige davon unsichtbar<br />
gemacht oder einige Methoden redefiniert<br />
• Redefiniert: heissen noch genauso, verhalten sich aber etwas anders<br />
• Eine abgeleitete Klasse kann zusätzliche Attribute und<br />
Methoden definieren ( Spezialisierung)<br />
287<br />
121
Abgeleitete Klassen, Redefinition – Beispiel<br />
• Eine Methode „berechneSteuer“ lässt sich nicht für alle<br />
Fahrzeuge gleichermassen definieren<br />
• Man würde z.B. in „Auto“ eine Standardmethode vorsehen<br />
(Benutzung von „Hubraum“), jedoch für spezielle Fahrzeuge<br />
(z.B. Elektroautos) diese Methode anders definieren<br />
288<br />
Ein Beispiel in Java<br />
class Fahrzeug{<br />
public int Radzahl;<br />
}<br />
class Auto extends Fahrzeug {<br />
public int PS;<br />
public float Hubraum;<br />
}<br />
class PW extends Auto {<br />
public int Beifahrerzahl;<br />
}<br />
void print() {<br />
System.out.println("Radzahl: " + Radzahl +<br />
", Beifahrerzahl: " +<br />
Beifahrerzahl);<br />
}<br />
class LW extends Auto {<br />
public float Zuladung;<br />
}<br />
Vorfahre weiss nicht von den<br />
Erben; Erbe muss sich seinen<br />
Vorfahren aussuchen!<br />
Erweiterung der Klasse<br />
"Fahrzeug": Alles, was in<br />
"Fahrzeug" deklariert ist,<br />
gehört damit auch zur "Auto"<br />
(sowohl Attribute als auch<br />
Methoden) – mit gewissen<br />
Einschränkungen (→ später)<br />
Auf "weiter oben"<br />
definierte Attribute<br />
kann ohne weiteres<br />
zugegriffen werden<br />
– diese sind Teil der<br />
abgeleiteten Klasse!<br />
Fahrzeug<br />
Auto<br />
PW LW<br />
289<br />
122
Ein Beispiel in Java (2)<br />
class Beispiel{<br />
public static void main(String args[]) {<br />
Fahrzeug f = new Fahrzeug();<br />
Auto a = new Auto();<br />
PW p = new PW();<br />
LW l = new LW();<br />
Hier werden Instanzen (also Objekte) der<br />
verschiedensten Hierarchiestufe erzeugt<br />
p.Beifahrerzahl = 5;<br />
p.PS = 70;<br />
p.Hubraum = 1794;<br />
p.Radzahl = 4;<br />
p.print();<br />
}<br />
}<br />
Zugriff auf Variablen und Methoden des<br />
mit 'p' bezeichneten PW-Objektes<br />
p.Zuladung geht<br />
natürlich nicht!<br />
Idee: Gemeinsame Aspekte<br />
herausfaktorisieren und in eine<br />
übergeordnete Klasse einbringen<br />
290<br />
Zuweisungskompatibilität<br />
• Objekte von abgeleiteten Klassen können an Variablen<br />
vom Typ der Basisklasse zugewiesen werden<br />
Fahrzeug<br />
• Fahrzeug f; Auto a; ... f = a;<br />
• Variable f kann Fahrzeugobjekte speichern<br />
• Ein Auto ist ein Fahrzeug<br />
Auto<br />
• Daher kann f auch Autoobjekte speichern<br />
• Die Umkehrung gilt jedoch nicht!<br />
• d.h. a = f; ist verboten!<br />
PW<br />
• Variable a kann Autoobjekte speichern<br />
• Ein Fahrzeug ist aber kein Auto (jedenfalls nicht immer)!<br />
LW<br />
• „Gleichnis“ zur Zuweisungskompatibilität: Auf einem<br />
Parkplatz für Fahrzeuge dürfen Autos, Personenwagen,<br />
Fahrräder... abgestellt werden. Auf einem Parkplatz für<br />
Fahrräder jedoch keine beliebigen Fahrzeuge!<br />
291<br />
123
Zuweisungskompatibilität (2)<br />
• Merke also: Eine Variable vom Typ „Basisklasse“ darf<br />
auch ein Objekt der abgeleiteten Klasse enthalten<br />
• Man nennt diese Eigenschaft auch Polymorphie, da eine<br />
Referenz auf Objekte verschiedenen Typs zeigen kann<br />
(bzw. eine Variable Werte unterschiedlichen Typs haben kann)<br />
• Beispiel: Eine Variable vom Typ „Referenz auf<br />
Fahrzeug“ kann zur Laufzeit sowohl zeitweise auf<br />
PW-Objekte, als auch zeitweise auf LW-Objekte zeigen<br />
292<br />
Ein Java-Beispiel zur Zuweisungskompatibilität<br />
class Fahrzeug {... int Radzahl;}<br />
class Auto extends Fahrzeug {... float Hubraum;}<br />
class PW extends Auto ...<br />
Fahrzeug f; Auto a; PW p; LW l;<br />
... new ...<br />
p.Hubraum = 1702;<br />
p.Radzahl = 4;<br />
Ein PW ist ein Auto<br />
und ein Fahrzeug<br />
a = p;<br />
f = p;<br />
f = a;<br />
/* a = f; */<br />
/* ERROR: Incompatible types */<br />
Ein Fahrzeug-Variable darf PW-<br />
Objekte und Auto-Objekte speichern<br />
a.Hubraum = 1100;<br />
f = a;<br />
System.out.println(f.Radzahl);<br />
System.out.println(f.Hubraum);<br />
ERROR: No variable Hubraum defined in Fahrzeug<br />
Fahrzeug<br />
Radzahl<br />
Auto<br />
Hubraum<br />
PW<br />
Es wurde zwar Hubraum und<br />
Radzahl zugewiesen; Hubraum<br />
ist aber über f nicht zugreifbar<br />
293<br />
124
Typkonversion („type cast“)<br />
• Aus gutem Grund ist f.Hubraum verboten: Auf f könnte<br />
ja zufällig ein Fahrrad (ohne Hubraum!) „parken“!<br />
• Durch Typkonversion kommt man aber notfalls auch<br />
über f an den Hubraum des Auto-Objektes:<br />
System.out.println(((Auto)f).Hubraum);<br />
• Aber wenn dort derzeit kein Auto (sondern ein Fahrrad)<br />
parkt? Das gibt einen Laufzeitfehler „ClassCastException“!<br />
• Dem kann man wie folgt vorbeugen:<br />
if (f instanceof Auto)<br />
System.out.println(((Auto)f).Hubraum);<br />
else System.out.println("kein Auto, kein Hubraum!");<br />
294<br />
Polymorphie<br />
• Polymorphie („Vielgestaltigkeit“)<br />
• Eine Variable kann Werte unterschiedlichen Typs annehmen<br />
• Bzw.: eine Referenz kann auf Objekte unterschiedlichen<br />
(aber „verwandten“) Typs verweisen<br />
• Objekte verwandten Typs können gleichnamige<br />
Methoden haben<br />
Beispiel:<br />
Methode „Flächenwert“<br />
ist für alle Objekttypen<br />
realisiert, jedoch unterschiedlich<br />
je nach Typ!<br />
Geometrisches<br />
Objekt<br />
Ellipse Rechteck Dreieck<br />
Kreis<br />
Quadrat<br />
295<br />
125
Abstrakte Methoden und Klassen<br />
• Abstrakte Methoden werden von den abgeleiteten Klassen<br />
jeweils spezifisch implementiert<br />
• Eine Klasse mit abstrakten Methoden muss selbst als<br />
„abstract“ deklariert werden<br />
• Beispiel:<br />
abstract class GeomObj {<br />
GeomObj next;<br />
public abstract float flaechenwert();}<br />
}<br />
Anker<br />
next<br />
Diese Methode muss<br />
für jede abgeleitete<br />
Klasse mit sinnvoller<br />
Semantik implementiert<br />
werden!<br />
296<br />
Polymorphe Liste<br />
class Dreieck extends GeomObj {<br />
// Koordinaten etc. als Attribute<br />
public float flaechenwert() {<br />
... // bekannte Formel anwenden<br />
System.out.print("Dreiecksfläche:...");<br />
}<br />
}<br />
class Kreis extends GeomObj {<br />
... ...flaechenwert() ...<br />
}<br />
• Eine polymorphe Liste kann man dann z.B. so durchlaufen:<br />
for (GeomObj z = Anker; z != null; z = z.next)<br />
f = f + z.flaechenwert();<br />
Anker<br />
next<br />
297<br />
126
Verarbeitung polymorpher Objekte<br />
• Die Verarbeitung der einzelnen Objekte kann<br />
unabhängig von ihrem eigentlichen Typ geschehen<br />
• In der Praxis wird eine solche „Verarbeitung“ i.Allg. wesentlich<br />
komplexer sein, als in obigem Beispiel angedeutet<br />
• Es können, ohne den Verarbeitungsalgorithmus<br />
anz<strong>up</strong>assen, neue Objekttypen als Unterklassen von<br />
„GeomObj“ eingeführt werden (z.B. „Quadrat“)<br />
• Nur hinzufügen, aber (im Idealfall) nichts verändern, vermindert<br />
den Wartungsaufwand beträchtlich!<br />
• Late binding: Es wird zur Laufzeit berechnet, welche<br />
konkrete Methode „angesprungen“ wird; dies steht zur<br />
Übersetzungszeit ( „early binding“) noch nicht fest!<br />
298<br />
Generische Methoden und abstrakte Klassen<br />
abstract class Sort {<br />
abstract boolean kleiner (Sort y);<br />
static void sort(Sort[] Tab) {<br />
for (int i=0; i
Generische Methoden und abstrakte Klassen (2)<br />
• Unabhängig davon, wie die Relation „kleiner“ konkret<br />
definiert ist, funktioniert unser Sortierverfahren!<br />
• Das Sortierverfahren kann also bereits implementiert (und<br />
getestet) werde, bevor überha<strong>up</strong>t die Daten selbst bekannt sind<br />
• Einmal entwickelt, kann man den Algorithmus auch zum<br />
Sortieren anderer Datentypen verwenden<br />
• int, float, Brüche als rationale Zahlen, Zeichenketten...<br />
300<br />
Anwendung des Sortierverfahrens<br />
• Es sollen zunächst einfache int-Werte sortiert werden,<br />
und zwar unter Benutzung der existierenden generischen<br />
Sortierroutine (ohne diese kennen zu müssen!):<br />
class IntSort extends Sort {<br />
int w;<br />
IntSort(int i) { Konstruktor<br />
w = i;<br />
}<br />
boolean kleiner(Sort y) {<br />
return w < ((IntSort)y.w);<br />
}<br />
}<br />
Hier wird die Relation<br />
"kleiner" definiert<br />
und implementiert<br />
Dies ist die vom Anwender bereitgestellte<br />
Klasse (aufbauend<br />
auf der Basisklasse Sort)<br />
301<br />
128
Anwendung des Sortierverfahrens (2)<br />
class ... {<br />
...<br />
IntSort[] Tabelle = new IntSort[12];<br />
}<br />
Diese Klasse verwendet<br />
beispielhaft IntSort<br />
Füllen mit Zufallszahlen<br />
for (int i=0; i
Eine andere Sortieranwendung (2)<br />
class ...<br />
...<br />
Studi[] Tab = new Studi[8];<br />
Tab[0] = new Studi(10,"Bio","Hase");<br />
Tab[1] = new Studi(8,"Mathe","Oberzahl");<br />
...<br />
Tab[7] = new Studi(6,"Physik", "Kraft");<br />
Studi.sort(Tab);<br />
for (int i=0; i
Sortieren geometrischer Objekte nach ihrer Fläche<br />
abstract class GeomObj extends Sort {<br />
public abstract float flaeche();<br />
boolean kleiner (Sort y) {<br />
GeomObj x = (GeomObj)y;<br />
return flaeche() < x.flaeche();}<br />
}<br />
class Quadrat extends GeomObj {<br />
float laenge;<br />
public float flaeche() {<br />
return (laenge*laenge);<br />
}<br />
Quadrat() {<br />
laenge = KbdInput.readInt("Länge? ");<br />
}<br />
}<br />
class Kreis extends GeomObj {<br />
float radius;<br />
public float flaeche() {<br />
return (3.1415926536*radius*radius);<br />
}<br />
Kreis() {<br />
radius = KbdInput.readInt("Radius? ");<br />
}<br />
}<br />
Abstrakte<br />
Klassen<br />
Quadrat<br />
laenge<br />
Sort<br />
GeomObj<br />
abstract<br />
kleiner<br />
abstract<br />
flaeche<br />
Kreis<br />
radius<br />
Die Fläche wird jeweils<br />
typspezifisch berechnet<br />
307<br />
Das zugehörige Testprogramm<br />
public static void main(String[] args) {<br />
GeomObj[] Tabelle = new GeomObj[3];<br />
}<br />
for (int i=0; i
Mehrfachvererbung<br />
• Zweck: „mix in“; Verheiraten mehrerer Konzepte<br />
• Beispiel: geometrische Formen eines Zeichenprogramms:<br />
• Dabei soll „gefülltes Quadrat“ Methoden und Attribute von<br />
mehreren Klassen erben können<br />
• Ist keine Baumstruktur, sondern ein gerichteter azyklischer Graph!<br />
311<br />
Probleme der Mehrfachvererbung<br />
• Gleich benannte Attribute in verschiedenen<br />
Vorgängerklassen: welches davon wird geerbt?<br />
• Problem oft nicht zu vermeiden, da Vorgängerklassen von<br />
verschiedenen Entwicklern realisiert wurden<br />
• Daher Mechanismen zur „Konfliktauflösung“ notwendig<br />
(z.B. beim Erben umbenennen; Angabe, welche davon man<br />
erben möchte; Vorrangregeln für den Konfliktfall etc.)<br />
• Keine Mehrfachvererbung bei Java! (Im Gegensatz zu C++)<br />
• Um Sprache einfach zu halten<br />
• Als teilweisen Ersatz dafür dienen bei Java die „Interfaces“<br />
312<br />
132
Schnittstellen („Interfaces“)<br />
• Interface = (abstrakte) Klasse, die alle Methoden nur<br />
deklariert, aber nicht implementiert<br />
• Enthält also nur (implizit) abstrakte Methoden (und evtl. Konstanten)<br />
interface Menge {<br />
int cardinal();<br />
void insert (Object x);<br />
void remove (Object x);<br />
}<br />
Alle Methoden sind automatisch<br />
public abstract<br />
Alle „Variablen“ sind automatisch<br />
public static final (d.h. „Konstanten“)<br />
• Interface muss von anderen Klassen implementiert werden:<br />
class S implements Menge {<br />
public int cardinal(); {<br />
...<br />
while ... i++ ...;<br />
return i;<br />
}<br />
}<br />
implements hat die gleiche Rolle<br />
wie extends bei „echten“ Klassen<br />
Es kann auch eine entsprechende<br />
Hierarchie entstehen<br />
313<br />
Mehrfacherweiterungen<br />
• Interfaces dürfen mehrere andere erweitern, z.B.:<br />
interface A {...}<br />
interface B {...}<br />
interface I extends A, B {...<br />
int m(); ...<br />
}<br />
A<br />
I<br />
B<br />
• I umfasst alle (abstrakten) Methoden von A und B (und zusätzlich m)<br />
• Eine Klasse dagegen kann nur eine einzige Klasse erweitern<br />
(aber gleichzeitig mehrere Interfaces implementieren):<br />
class P extends Q<br />
implements A,B,...<br />
A<br />
Q<br />
P<br />
B<br />
314<br />
133
„Diamond Inheritance“<br />
• Das sogen. „diamond inheritance problem“<br />
ist damit entschärft:<br />
interface W {...}<br />
interface X extends W {...}<br />
class Y implements W {...}<br />
class Z extends Y implements X {...}<br />
X<br />
W<br />
Z<br />
Y<br />
• Die von Z geerbten Attribute und Methodenimplementierungen<br />
können nur aus Y (und<br />
nicht indirekt doppelt aus W stammen)<br />
315<br />
7.<br />
Exceptions<br />
Buch Mark Weiss „Data Structures & Problem Solving Using Java“ siehe:<br />
- 83-87<br />
- 173-174<br />
320<br />
134
Exceptions – „offizielle“ Erläuterungen<br />
• “An exception is an event, which occurs during the<br />
execution of a program, that disr<strong>up</strong>ts the normal<br />
flow of the program's instructions."<br />
• "When an error occurs within a method, the<br />
method creates an object and hands it off<br />
to the runtime system. The object, called an<br />
exception object, contains information about<br />
the error, including its type and the state of<br />
the program when the error occurred."<br />
• "Creating an exception object and handing it to the<br />
runtime system is called throwing an exception."<br />
Source: http://download.oracle.com/javase/tutorial/essential/exceptions/<br />
321<br />
Ausnahmeereignisse (Exceptions)<br />
• Ausnahmeereignisse<br />
als Fehlerereignisse<br />
• Werden oft vom System<br />
ausgelöst („throw“)<br />
• Können aber auch explizit im<br />
Programm ausgelöst werden<br />
• Sollten abgefangen und<br />
behandelt werden („catch“)<br />
• Programmstrukturierung<br />
durch „try“ und „catch“:<br />
• Fehlerbehandlung muss auf<br />
diese Weise nicht mit dem<br />
„normalen“ Programmcode<br />
verwoben werden<br />
readFile() {<br />
try {<br />
// open the file;<br />
// determine its size;<br />
// allocate that much memory;<br />
// read the file into memory;<br />
// close the file; }<br />
catch (FileOpenFailed) {<br />
// doSomething; }<br />
catch (SizeDeterminationFailed){<br />
// doSomething; }<br />
catch (MemoryAllocationFailed){<br />
// doSomething; }<br />
catch (ReadFailed){<br />
// doSomething; }<br />
catch (FileCloseFailed){<br />
// doSomething; }<br />
}<br />
von Exceptions:<br />
Weitere Nutzen von Exceptions:<br />
http://tinyurl.com/5t9uesp<br />
322<br />
135
Fehlerarten<br />
• Typ. Situationen, in denen Ausnahmen auftreten können:<br />
• Ein- / Ausgabe (z.B. IOException)<br />
• Netz (z.B. MalformedURLException)<br />
• Erzeugen von Objekten mit „new“<br />
• Typkonvertierung<br />
(z.B. NumberFormatException)<br />
Ob ein Fehler auftritt, hängt<br />
vom Kontext ab, den man<br />
oft nicht selbst kontrolliert<br />
• Wichtige Fehlerklasse: Laufzeitfehler, z.B.:<br />
• Zugriff über Null-Referenz<br />
try {<br />
• Division durch 0<br />
value = value / x;<br />
• Array-Indexfehler<br />
}<br />
catch (ArithmeticException e){<br />
Solche „Programmierfehler“ können,<br />
System.out.println<br />
aber müssen nicht abgefangen werden<br />
("Division durch 0?");<br />
}<br />
323<br />
Ausnahmen: Hierarchie<br />
Throwable<br />
Die Klasse Throwable enthält<br />
umfangreiche Methoden zur<br />
Behandlung von Fehlern, z.B.:<br />
getStackTrace,<br />
printStackTrace, ...<br />
Error<br />
Exception<br />
RunTimeException<br />
...<br />
IOException<br />
324<br />
136
Fehler abfangen / weiterleiten<br />
• Alle Fehler (ausser Laufzeitfehler) müssen von einer Methode<br />
entweder selbst abgefangen oder explizit weitergeleitet werden<br />
• Entweder durch try / catch in der Methode<br />
• Oder durch Angabe (mittels „throws“), dass die Methode diese Ausnahme<br />
evtl. auslöst (und damit dem „Aufrufer“ weiterreicht), z.B.:<br />
import java.io.*;<br />
public eine_methode (...) throws java.io.IOException {<br />
... read ...<br />
} Der Aufrufer muss dann mit<br />
dieser Exception „rechnen“<br />
325<br />
Ausnahmen: E/A-Beispiel<br />
Eingabe / Ausgabe<br />
import java.io.*;<br />
public class EA_Beispiel {<br />
//print "Hello World" to a file specified by the first input parameter<br />
public static void main(String args[]) {<br />
FileOutputStream out = null;<br />
//try opening the file; if we can't,<br />
//display error and quit<br />
try {<br />
out = new FileOutputStream(args[0]);<br />
}<br />
catch (Throwable e) {<br />
System.out.println("Error in opening file");<br />
System.exit(1);<br />
}<br />
PrintStream ps = new PrintStream(out);<br />
try {<br />
ps.println("Hello World");<br />
out.close();<br />
}<br />
catch (IOException e) {<br />
System.out.println("I/O Error");<br />
System.exit(1);<br />
}<br />
}<br />
}<br />
Da wir Fehler selbst<br />
abfangen, können wir auf<br />
"throws…" verzichten<br />
Diese Fehlerklasse liegt<br />
ganz oben in die Hierarchie<br />
und fängt damit alles ab<br />
Z.B. Zugriffsrechte "falsch"<br />
Fehler hierbei würden<br />
nicht abgefangen<br />
Über diese Variable kann<br />
man mehr über den<br />
Fehler erfahren<br />
Notausstieg aus dem<br />
Programm<br />
326<br />
137
Definieren und Auslösen eigener Ausnahmen<br />
• Ausnahmen sind Objekte!<br />
• Eigener Ausnahmetyp<br />
muss von<br />
java.lang.Throwable<br />
abgeleitet sein<br />
• Kann mit „throw“<br />
ausgelöst werden<br />
• „s<strong>up</strong>er“ ruft<br />
Konstruktor<br />
der Basisklasse<br />
Fehlerhaftes Datum<br />
ist: 47.3.97 at Datum.<br />
Setzen(Datum.java:27)<br />
at Beispiel.main<br />
(Datum.java:39)<br />
class IllegalesDatum extends Throwable {<br />
IllegalesDatum(int Tag, int Monat, int Jahr) {<br />
s<strong>up</strong>er("Fehlerhaftes Datum ist: "<br />
Tag + "." + Monat + "." + Jahr");<br />
}<br />
Der Konstruktor von Throwable erwartet einen String, der<br />
}<br />
als Fehlermeldung (mit dem Stack-Trace) ausgegeben wird<br />
class Datum {<br />
...<br />
void setzen(int T, int M, int J)<br />
throws IllegalesDatum {<br />
Tag = T; Monat = M; Jahr = J;<br />
if (Tag > 31) throw new<br />
IllegalesDatum (Tag, Monat, Jahr);<br />
}<br />
}<br />
class Beispiel {<br />
...<br />
d.setzen(47,03,1997); //Zeile 39 in der Datei<br />
...<br />
}<br />
327<br />
8.<br />
Binärbäume als<br />
Zeigergeflechte<br />
Buch Mark Weiss „Data Structures & Problem Solving Using Java“ siehe:<br />
- 695-697 (Rekursion und Bäume)<br />
Aus den Übungen von Teil I der Vorlesung<br />
- 715-725 (Suchbaum)<br />
sind binäre Suchbäume und rekursive<br />
- 697-709 (Baumtraversierung)<br />
Traversierung bereits bekannt<br />
328<br />
138
Zur Erinnerung: Binärbaum<br />
• Jeder Knoten hat höchstens zwei Nachfolger<br />
• Unterscheide linken und rechten Nachfolger / Unterbaum<br />
W<br />
W<br />
X<br />
Y<br />
Y<br />
X<br />
Z<br />
Z<br />
331<br />
Binärbäume als Referenzstrukturen<br />
• Bsp: die Knoten eines Binärbaums<br />
sollen folgende Struktur haben:<br />
class Person {<br />
String name;<br />
Person left, right;<br />
}<br />
"Katja"<br />
left<br />
right<br />
"Eva"<br />
left<br />
right<br />
"Stephan"<br />
left<br />
right<br />
332<br />
139
Binärbäume als Referenzstrukturen (2)<br />
• Ein wenig umständlich könnte man dann so den<br />
Baum aufbauen:<br />
Person Wurzel;<br />
Wurzel = new Person();<br />
Wurzel.name = "Katja";<br />
Wurzel.left = new Person();<br />
Wurzel.left.name = "Eva";<br />
Wurzel.right = new Person();<br />
Wurzel.right.name = "Stephan";<br />
Wurzel.left.left = new Person();<br />
...<br />
• Es können dynamisch neue Knoten hinzugefügt werden<br />
• Im Unterschied zur früheren Array-Repräsentation eines Baums<br />
• Jedenfalls solange der Speicherplatz reicht…<br />
333<br />
Binärbäume als Referenzstrukturen (3)<br />
• Oft wird man in den einzelnen Knotenobjekten auch eine<br />
Referenzvariable vorsehen, die auf den Vorgänger zeigt:<br />
info<br />
Knoten mit<br />
Rückverweis:<br />
Ohne Rückverweis:<br />
back<br />
info<br />
class Person {<br />
String name;<br />
Person left, right;<br />
Person back;<br />
}<br />
left<br />
right<br />
left<br />
right<br />
334<br />
140
Binärbäume als Referenzstrukturen (4)<br />
• Ein Baum ist dann eine solche<br />
„verzeigerte“ Struktur aus<br />
dynamischen Objekten<br />
• Die rekursive Struktur von<br />
(Binär-)bäumen legt eine<br />
Reihe von rekursiven<br />
Algorithmen nahe, z.B.:<br />
• Anzahl der Knoten<br />
• Höhe des Baums<br />
• Suchen eines Elementes<br />
• Traversieren (inorder, postorder...)<br />
• …<br />
336<br />
Höhe eines Binärbaums<br />
• Die Tiefe eines Knotens ist sein Abstand<br />
(d.h. die Länge seines Weges) zur Wurzel<br />
• Die Wurzel hat also die Tiefe 0<br />
• Die Höhe eines Baums ist die maximale Tiefe<br />
• D.h. die Länge eines längsten Weges von<br />
der Wurzel zu einem Blatt (es kann mehrere<br />
solche – gleich langen – Wege geben!)<br />
• Mit anderen Worten: Der maximale Abstand<br />
zwischen einem Knoten und der Wurzel<br />
• Ein Baum, der nur aus einer Wurzel besteht,<br />
hat also die Höhe 0<br />
Höhe: 3<br />
337<br />
141
Beispiel: Alle 21 Binärbäume der Höhe 2<br />
„links“ / „rechts“ unterscheiden<br />
„unlabeled“<br />
• Es gibt 651 Binärbäume der Höhe 3,<br />
• 457653 der Höhe 4,<br />
• 210065930571 der Höhe 5,<br />
• 44127887745696109598901 der Höhe 6,<br />
• 1947270476915296449559659317606103024276803403 der Höhe 7,<br />
• …<br />
338<br />
Höhe eines Binärbaums rekursiv<br />
• Als lokale Methode in einer Knoten-Klasse:<br />
int height() {<br />
if (left!=null && right!=null)<br />
return 1 + Math.max(left.height(),<br />
right.height());<br />
else if (left!=null)<br />
return 1+left.height();<br />
else if (right!=null)<br />
return 1+right.height();<br />
else<br />
return 0;<br />
}<br />
339<br />
142
Höhe eines Binärbaums rekursiv (2)<br />
• Als globale Methode ausserhalb einer Knoten-Klasse:<br />
int height(Tree b) {<br />
if (b != null)<br />
return 1 + Math.max(height(b.left),<br />
height(b.right));<br />
else<br />
return ...;<br />
}<br />
Denkübung: Was muss hier hin?<br />
0? Oder -1? Oder etwas anderes?<br />
340<br />
Binäre Suchbäume<br />
• Voraussetzung:<br />
• Jeder Knoten hat ein Schlüsselattribut<br />
• Die Menge der Schlüsselattribute ist total geordnet<br />
• Def.: Für jeden Knoten mit Schlüsselattribut s gilt:<br />
• Alle Schlüssel im linken Unterbaum sind kleiner als s<br />
• Alle Schlüssel im rechten Unterbaum sind grösser als s<br />
Zu den gleichen Schlüsselattributen<br />
gibt es verschiedene Suchbäume.<br />
Bsp: „Günter“ anstelle von „Jan“<br />
mit „Jan“ als rechten Nachfolger<br />
von „Günter“.<br />
341<br />
143
Suchen in binären Suchbäumen<br />
• Suchen eines Elementes (für gegebenes<br />
Schlüsselattribut) ist sehr effizient<br />
• Suchpfad startet an der Wurzel<br />
• Dann jeweils links ode rechts weitersuchen,<br />
je nach Schlüsselwert des Knotens<br />
• Bei „gutartigen“ Bäumen (alle Niveaus<br />
gut gefüllt) kommt man schon nach<br />
etwa log(n) Schritten an ein Blatt!<br />
• n = Anzahl der Knoten<br />
• Hat man das gesuchte Element bis dahin<br />
nicht gefunden, ist es nicht im Baum!<br />
342<br />
Einfügen in Suchbäume<br />
static void insert (String n, int t, Person p) {<br />
if (n.compareTo(p.name) < 0)<br />
if (p.left != null)<br />
insert(n, t, p.left);<br />
else {<br />
p.left = new Person();<br />
p.left.name = n; p.left.telnr = t;<br />
}<br />
else<br />
if (p.right != null)<br />
insert(n, t, p.right);<br />
else {<br />
p.right = new Person();<br />
p.right.name = n; p.right.telnr = t;<br />
}<br />
}<br />
class Person {<br />
Person left;<br />
Person right;<br />
Person back;<br />
String name;<br />
int telnr;<br />
}<br />
Ein neuer Knoten<br />
wird immer als<br />
Blatt eingefügt<br />
Einfügen der<br />
back-Referenz auf<br />
den Vorgänger als<br />
Denkübung<br />
• Beachte: Der Aufruf der Methode setzt voraus, dass der dritte<br />
Parameter auf die Wurzel eines (nicht leeren) Suchbaums zeigt<br />
• Was geschieht eigentlich, wenn es im Baum den Namen schon gibt?<br />
343<br />
144
Eine kürzere, elegantere Lösung?<br />
insert(…,…,w)<br />
static void insert (String n, int t, Person p) {<br />
if (p == null) {<br />
p = new Person();<br />
p.name = n; p.telnr = t;<br />
}<br />
else if (n.compareTo(p.name) < 0)<br />
insert(n,t,p.left);<br />
else<br />
insert(n,t,p.right);<br />
}<br />
• Würde funktionieren, wenn p nicht eine Kopie (!) der Referenz w wäre,<br />
die beim Aufruf von insert als aktueller Parameter angegeben wird<br />
• Veränderungen des formalen Parameters p innerhalb von insert wirken<br />
sich so nicht auf den aktuellen Aufrufparameter „nach aussen“ aus!<br />
• Schade – sonst könnte man so auch in den noch leeren Baum einfügen<br />
• Merke: bei Java gilt für Referenzen die Wertübergabe!<br />
• Reality check für Spezialisten: wie ist es bei C++ ?<br />
344<br />
Des Rätsels Lösung<br />
1) Der Aufruf insert(…,…,w) von void insert(…,…,Person p)<br />
bewirkt als erstes eine implizit Zuweisung p=w des Wertes<br />
des „aktuellen“ Parameters w an den „formalen“ Parameter p<br />
2) Obige erste Lösung: p.left.telnr = 32168<br />
ist nach p=w aufgrund des Aliaseffekts<br />
identisch zu w.left.telnr = 32168, d.h.<br />
die Zuweisung der Telefonnummer wirkt<br />
p w<br />
nach aussen, ins Ha<strong>up</strong>tprogramm zurück<br />
telnr = 32168<br />
3) Situation der „kürzeren Lösung“ (im Fall von w == null) nach p=w:<br />
p<br />
Ø<br />
w<br />
Nach p = new… ergibt sich:<br />
Das heisst, w zeigt nach dem Aufruf weiter ins Leere,<br />
die Manipulationen über p wirken nicht auf w zurück<br />
p<br />
w<br />
Ø<br />
345<br />
145
Knoten in Suchbäumen löschen<br />
• Aufgabe: Einen Knoten mit einem bestimmten<br />
Schlüsselattribut so aus dem Suchbaum entfernen,<br />
dass der Rest ein Suchbaum bleibt<br />
• Trivial bei Blättern<br />
• Einfach bei Knoten mit<br />
einem einzigen Nachfolger:<br />
diesen Knoten „ausketten“<br />
(Beispiel: Jan oder Kerstin)<br />
Man überlege sich, wieso das „Ausketten“<br />
die Suchbaumeigenschaft invariant lässt<br />
346<br />
Löschen von Knoten mit zwei Nachfolgern<br />
• Ersetze das zu entfernende Element entweder durch<br />
• das grösste Element seines linken Teilbaums<br />
• (im linken Teilbaum ganz nach rechts laufen!)<br />
• oder das kleinste seines rechten Teilbaums<br />
• (im rechten Teilbaum ganz nach links laufen!)<br />
Das Ersatzelement<br />
ist immer entweder<br />
ein Blatt oder ein<br />
Knoten mit einem<br />
einzigen Nachfolger<br />
• Also durch den unmittelbaren Vorgänger<br />
bzw. Nachfolger entsprechend der Ordnung<br />
Beispiele:<br />
-Katja durch Jan<br />
-Katja durch Kerstin<br />
-Eva durch Dirk<br />
- Eva durch Günter<br />
- Stephan durch Stefanie<br />
- Stephan durch Tanja<br />
Man überlege sich wieder,<br />
wieso dies die Suchbaumeigenschaft<br />
invariant lässt<br />
347<br />
146
Inorder-Traversierung von Binärbäumen<br />
• Prinzip: Zuerst den linken Unterbaum traversieren,<br />
dann den Wert der Wurzel ausgeben, anschliessend<br />
den rechten Unterbaum traversieren<br />
• Bei einem Suchbaum werden dabei die Knoten in<br />
der Reihenfolge des Schlüsselattributs ausgegeben<br />
die Ausgabe ist aufsteigend sortiert!<br />
static void inorder(Person p) {<br />
if (p != null) {<br />
inorder(p.left);<br />
System.out.println(p.name);<br />
inorder(p.right);<br />
}<br />
}<br />
Dirk, Eva, Günter, Jan,<br />
Katja, Kerstin, Stefanie,<br />
Stephan, Tanja<br />
351<br />
Sortieren mit Suchbäumen<br />
• Prinzip:<br />
1. Unsortierte Eingabeliste in einen (anfangs leeren)<br />
Suchbaum überführen (d.h. „insert“ nacheinander<br />
auf alle Elemente der Eingabeliste anwenden)<br />
2. „inorder“ auf die Wurzel anwenden und beim<br />
rekursiven Traversieren die Werte ausgeben<br />
3<br />
3, 5, 2, 7, 1, 4, 8, 6 1, 2, 3, 4, 5, 6, 7, 8<br />
2 5<br />
1<br />
4 7<br />
6<br />
8<br />
352<br />
147
Zeitbedarf: Anzahl der Schritte als<br />
Funktion der Zahl der Elemente n<br />
• Methode „insert“ wird n Mal von aussen aufgerufen<br />
• Aber: Wie viele Schritte benötigt ein solcher insert-Aufruf?<br />
• jedes Mal wird (rekursiv) ein Ast ganz durchlaufen<br />
O(n log n)<br />
• Aber wie lange ist ein Ast (im Mittel, maximal...)?<br />
• Im Extremfall (Eingabe sortiert!): n (bzw. 0.5n „gemittelt“)<br />
• Im „Normalfall“, wenn gutartige Bäume entstehen: etwa log(n)<br />
• Methode „inorder“ wird 1 Mal von aussen und 2n Mal<br />
rekursiv aufgerufen – also linear zu n O(n)<br />
• O(n) ist klein gegenüber O(n log n); geht in O(n log n) auf<br />
• Eine genaue mathematische Analyse würde ergeben:<br />
Im häufigen Normalfall werden insgesamt nur ca.<br />
c n log(n) Schritte zum Sortieren benötigt<br />
kleine Konstante<br />
353<br />
9.<br />
Binärsuche<br />
Buch Mark Weiss „Data Structures & Problem Solving Using Java“ siehe:<br />
- 244-248<br />
- 341-342<br />
354<br />
354<br />
148
Binärsuche auf Arrays<br />
• Problem: Feststellen, ob ein Element in einem<br />
sortierten Array vorkommt<br />
• Lösung durch Intervallhalbierung:<br />
• Prüfen, in welcher Hälfte der gesuchte Wert liegen muss<br />
(wenn man nicht zufällig genau in der Mitte auf ihn gestossen ist!)<br />
• Verfahren dann iterativ (oder rekursiv) auf die „richtige“ Hälfte<br />
anwenden → entweder linke oder rechte Grenze neu setzen<br />
• Stoppkriterien:<br />
• Wert gefunden → liefere Index des gesuchten Elements im Array<br />
• Restbereich „kollabiert“ → liefere ’-1’ als Fehl-Indikator<br />
355<br />
Binärsuche auf Arrays – Beispiel<br />
• Ist die 76 vorhanden?<br />
356<br />
149
Binärsuche auf Arrays in Java<br />
// A sei hier ein globales int array<br />
int binSearch (int s) {<br />
int m;<br />
int li = 0; int re = A.length-1;<br />
}<br />
while (re >= li) {<br />
m = (li+re)/2;<br />
if (s == A[m]) return m;<br />
if (s < A[m])<br />
re = m - 1 ;<br />
else<br />
li = m + 1 ;<br />
}<br />
return (-1);<br />
Voraussetzung: A sei<br />
aufsteigend sortiert<br />
Was passiert wenn<br />
• li + re ungerade ist?<br />
• oder wenn li = re?<br />
Fragen:<br />
1) Dürfen gleiche Elemente<br />
mehrfach vorkommen?<br />
2) Was geschieht, wenn<br />
A doch nicht sortiert ist?<br />
(Wie könnte dies in linearer<br />
Zeit überprüft werden?)<br />
357<br />
Binärsuche – Anwendungen<br />
• Beispiele:<br />
• Ist diese Telefonnummer schon vergeben?<br />
• Gibt es einen Stern mit diesem Namen schon?<br />
• Oft hat man komplexere Datenstrukturen<br />
• Dann ist das Array nur bzgl. einer „Schlüsselkomponente“ sortiert<br />
• Beispiel: Wie lautet das Buch mit dieser ISBN-Nummer?<br />
ISBN = 3458342672<br />
Titel = Die Schatzinsel<br />
ISBN = 3723503871<br />
Titel = Kaspar Hauser<br />
ISBN = 3770123395<br />
Titel = Der Würger<br />
• Sucheffizienz: Anzahl der Schritte ist logarithmisch zur Array-Länge<br />
• Also O(log n); „naive“ sequentielle Suche wäre hingegen O(n)<br />
• Beachte: Einfügen neuer Elemente ist allerdings „teuer“, da das<br />
Array sortiert bleiben muss (→ Platz schaffen durch Verschieben)<br />
358<br />
150
Binärsuche – Terminierung<br />
// A sei hier ein globales int array<br />
int binSearch (int s) {<br />
int m;<br />
int li = 0;<br />
int re = A.length-1;<br />
}<br />
while (re >= li) {<br />
m = (li+re)/2;<br />
if (s == A[m]) return m;<br />
if (s < A[m])<br />
re = m - 1;<br />
else<br />
li = m + 1;<br />
}<br />
return (-1);<br />
Wieso terminiert die<br />
Schleife in jedem Fall?<br />
Es ist nicht a priori klar,<br />
dass der Algorithmus<br />
nicht eventuell (in einer<br />
Endlosschleife) „stecken<br />
bleibt“!<br />
359<br />
Binärsuche – Terminierung (2)<br />
while (re >= li) {<br />
m = (li+re)/2<br />
if (s == A[m]) return m;<br />
if (s < A[m])<br />
re = m - 1;<br />
else<br />
li = m + 1;<br />
}<br />
• Hätte man z.B. : ... re = m; else li = m;<br />
statt dessen, dann könnte folgendes geschehen:<br />
mit li = 4, re = 5 m = (li+re)/2 = 4<br />
(im Fall s > A[4]): li = 4, re = 5 Endlosschleife!<br />
• Wie können wir wirklich beweisen, dass so ein<br />
Problem bei unserer Lösung nicht vorkommt?<br />
• Korrektheitsbeweis (als Denkübung) muss bei re = m-1;<br />
li = m+1, aber darf nicht bei re = m; li = m funktionieren!<br />
(Unvollständiges)<br />
Testen hilft nicht<br />
immer!<br />
360<br />
151
OK, WE‘VE CHANGED<br />
">" TO ">=". BUT THAT<br />
DOESN‘T WORK EITHER.<br />
AND NOW?<br />
LET ’S TRY<br />
"
Lineare Interpolationssuche<br />
m = li + (s – A[li])*(re-li) / (A[re]-A[li]);<br />
if (m < li) m = li; Korrektur für den Fall, dass s<br />
if (m > re) m = re; ausserhalb des Bereichs liegt<br />
if (s == A[m]) return m;<br />
Und würde man so nicht<br />
if (s < A[m]) re = m-1; zu oft in der falschen<br />
else li = m+1; („grösseren“) Hälfte landen?<br />
Aber würde man nicht<br />
durch 0 dividieren,<br />
wenn A[re] = A[li] ist?<br />
• Wie viel besser als die „normale“ Binärsuche ist dies?<br />
• Typischerweise viel besser, wir analysieren das aber nicht weiter<br />
• Findet z.B. Anwendung bei Zugriff auf externe Dateien<br />
• Wo jeder Zugriff relativ lange dauert<br />
• Dafür weitere Optimierungen, z.B. Index im Ha<strong>up</strong>tspeicher halten<br />
363<br />
Binärsuche rekursiv<br />
„Comparable“ sei ein Interface<br />
import ...<br />
mit der Methode „compareTo“<br />
public class BinarySearch {<br />
public static int binarySearch(Comparable [] a, Comparable x)<br />
throws ItemNotFound {<br />
return binSearch(a, x, 0, a.length -1);<br />
}<br />
}<br />
...<br />
}<br />
// Verborgene rekursive Methode:<br />
private static int binSearch(Comparable [] a, Comparable x,<br />
int low, int high)<br />
throws ItemNotFound {<br />
if ( low > high )<br />
throw new ItemNotFound("BinarySearch fails");<br />
int mid = (low + high) / 2;<br />
if ( a[mid].compareTo(x) < 0 )<br />
return binSearch(a, x, mid+1, high);<br />
else if (a[mid].compareTo(x) > 0)<br />
return binSearch(a, x, low, mid-1);<br />
else return mid;<br />
Aber lohnt<br />
sich Rekursion<br />
bei Binärsuche<br />
wirklich?<br />
364<br />
153
Binärsuche rekursiv (2)<br />
// Test-Programm<br />
public static void main(String [] args) {<br />
int SIZE = 8;<br />
Comparable [] a = new MyInteger [SIZE];<br />
for ( int i = 0; i < SIZE; i++ )<br />
a[i] = new MyInteger(i * 2);<br />
for ( int i = 0; i < SIZE * 2; i++ ) {<br />
try {<br />
System.out.println("Found " + i + " at "<br />
+ binarySearch(a, new MyInteger(i)));<br />
}<br />
catch(ItemNotFound e) {<br />
System.out.println(i + " not found");<br />
}<br />
}<br />
}<br />
Deklaration des Arrays „a“<br />
Füllen von „a“ mit Werten<br />
binarySearch für<br />
Testwerte i aufrufen<br />
Das Beispiel findet sich<br />
so ähnlich im Buch von<br />
Mark Weiss, Seite 342<br />
• Benötigt wird noch „MyInteger“, das das Interface „Comparable“<br />
zusammen mit der Methode „compareTo“ implementiert ( als Übung)<br />
• „ItemNotFound“ sei kanonisch von java.lang.Exception abgeleitet<br />
365<br />
10.<br />
Backtracking<br />
Buch Mark Weiss „Data Structures & Problem Solving Using Java“ siehe:<br />
- 367-370<br />
366<br />
154
Backtracking<br />
• Ziel: Systematisches Durchmustern eines grossen Zustandsraumes,<br />
um zu einem Problem eine Lösung zu finden<br />
• Systematisches „trial and error”<br />
Passende, gute, optimale;<br />
alle Lösungen...<br />
• Beispiel: Ausgang in einem Labyrinth suchen:<br />
• Sich für eine Richtung entscheiden<br />
• In diese Richtung weitergehen<br />
• Wenn „letztendlich“ erfolglos:<br />
zurückkehren und eine andere Richtung wählen<br />
Backtracking<br />
Falls bereits alle Richtungen ausprobiert<br />
noch weiter zurück<br />
367<br />
Durchmustern des Entscheidungsbaums<br />
• Idee: Den Baum aller möglichen Entscheidungen<br />
systematisch (rekursiv) durchlaufen:<br />
• Knoten = Entscheidungssituation („Zustand“)<br />
• Kante = Entscheidung<br />
• Nachfolgeknoten = Situation<br />
nach der Entscheidung<br />
X<br />
?<br />
A<br />
? ?<br />
Y Z<br />
• Blatt = Endsituation<br />
P Q<br />
• Nützliche Strategie: Sackgassen möglichst früh entdecken<br />
• Ist abhängig vom konkreten Problem<br />
• Spart evtl. viel Suchaufwand (ganze Unterbäume!)<br />
• Beispiel: Analyse eines Spielbaums: „Dieser Zug ist hoffnungslos“<br />
gut<br />
schlecht<br />
Sackgasse<br />
368<br />
155
Eine schlechte Wahl<br />
Süddeutsche Zeitung, 9.3.2012<br />
Wer bei Entscheidungen zu früh manche Optionen ausschließt,<br />
landet oft bei einer unbefriedigenden Lösung<br />
Zu den Tragödien des Lebens gehört, dass man Entscheidungen treffen muss, von denen man<br />
nicht weiß, wo sie am Ende hinführen. Wer etwa einen Zug im Schachspiel ausführen will, kann<br />
nicht alle Optionen bedenken, die sich nach 30 weiteren Zügen ergeben könnten. Und wer eine<br />
Urlaubsreise plant, hat trotz Google Earth nicht die Zeit, alle in Frage kommenden Ziele ordentlich<br />
zu prüfen. Um dennoch rechtzeitig zu Ferienbeginn gebucht zu haben, beschneiden die<br />
meisten Menschen den Entscheidungsbaum schon beim ersten Schritt: Sie entschließen sich<br />
zum Beispiel aus Gründen der Bequemlichkeit, nur Ziele in Betracht zu ziehen, für die sie bei der<br />
Anreise nicht umsteigen müssen. Ein solches Vorgehen kann aber dazu führen, dass sie letztlich<br />
eine schlechte Entscheidung fällen: eine, die sie im Ergebnis unglücklicher macht als die Variante<br />
mit Umsteigen. Diese könnte nämlich andere, insgesamt größere Vorteile bieten. Dies zumindest<br />
meinen Forscher um Quentin Huys vom University College London belegen zu können (PLoS<br />
Computational Biology, Bd. 8, S. e1002410, 2012).<br />
In ihrer Studie mussten 46 Versuchsteilnehmer einen Weg durch verschiedene Entscheidungsbäume<br />
planen, bei dem sie an jeder Abzweigung Geld verlieren oder gewinnen konnten. Dabei<br />
zeigte sich, dass fast alle Probanden Wege vermieden, bei denen sie bereits beim ersten Schritt<br />
viel Geld verloren hätten. Dies war selbst dann der Fall, wenn sie an nur drei Abzweigungen je<br />
zwei Optionen bedenken mussten, sie sich also recht einfach hätten ausrechnen können, ob der<br />
Anfangsverlust am Ende wieder kompensiert wird. „Die Probanden tendierten dazu, Entscheidungsäste<br />
zu ignorieren, die auf größere Verluste folgen“, schreiben die Autoren.<br />
369<br />
Ein Puzzle<br />
• Gegeben seien 9 derartige Quadrate<br />
mit verschiedenen Hälften von Figuren<br />
• Aufgabe: Diese so zu einem 3×3-Feld zusammensetzen,<br />
dass die Ränder „passen“<br />
370<br />
156
371<br />
Ein Puzzle (2)<br />
• Schwierig (frustrierend und reizvoll zugleich!), da man evtl.<br />
erst recht spät merkt, dass das Puzzle nicht „aufgeht“ und<br />
man grosse Teile zurückbauen muss!<br />
• Der Puzzle-Designer hat es wohl absichtlich so gestaltet, dass<br />
viele partielle und wenig vollständige Lösungen existieren<br />
• Naiver Ansatz („brute force“): Es gibt nur endlich viele verschiedene<br />
Anordnungen der Quadrate in einem 3×3-Feld;<br />
also generiere alle und teste jeweils auf Korrektheit<br />
Davon gibt es 9 ×4 ×8 ×4 ×7 ×4 ×<br />
... 1 × 4 9 = 4 9 ×9! ≈ 100 000 000 000<br />
Aber sollte das nicht<br />
effizienter gehen? ?<br />
Das ist einfach! (Für die Kryptographie<br />
ist es übrigens entscheidend, dass es<br />
kombinatorische Probleme gibt, für die<br />
man nur sehr schwer Lösungen findet,<br />
diese aber einfach überprüfen kann)<br />
372<br />
157
Ein rekursiver Ansatz<br />
1 2 3<br />
4 5 6<br />
7 8 9<br />
Problem:<br />
- Gibt es eine Lösung?<br />
- Wenn ja, wie lautet sie?<br />
1) Bestimme zunächst (rekursiv) die Lösungen für<br />
ein Spielfeld, das um 1 Einzelfeld kleiner ist<br />
2) Versuche für eine solche Lösung (falls existent) das<br />
zusätzliche Einzelfeld so mit einem der restlichen Spielsteine<br />
zu besetzen, dass eine Gesamtlösung entsteht<br />
373<br />
Oder andersherum?<br />
• Solange noch nicht alle Spielsteine (in allen 4 Orientierungen)<br />
für Position 1 ausprobiert worden sind:<br />
1) Belege Position 1 auf eine neue Weise<br />
2) Löse rekursiv das einfachere Problem „Gibt es mit<br />
den restlichen Spielsteinen eine dazu passende<br />
Lösung für das Spielfeld der Positionen 2,...,9 ?“<br />
3) „ja“: → Gesamtlösung zurückliefern<br />
1 4 7 2 5 8 3<br />
6<br />
9<br />
• „Nein“ melden, wenn erfolglos alle Möglichkeiten probiert<br />
• Welcher der beiden Ansätze ist besser?<br />
• Sollte man nicht die Mitte (Position 5) recht früh belegen?<br />
• Diese induziert sehr viele „Constraints“ Sackgassen früh erkennbar?<br />
374<br />
158
Der Backtracking-Baum<br />
Je nach Problem kann man einem<br />
Zwischenzustand (= Teilproblem)<br />
eventuell ansehen, dass er keinesfalls<br />
zu einer Gesamtlösung<br />
beitragen kann „tree pruning“.<br />
• Der Baum wird „depth first“ durchlaufen:<br />
Zunächst wird ganz links abgestiegen...<br />
Gelegentlich wird ein Unterbaum<br />
auch dann gekappt, wenn man<br />
zwar nicht sicher ist, aber vermutet,<br />
dass sich in ihm keine (gute) Lösung<br />
befindet. Hierfür verwendet<br />
man problembezogene Heuristiken.<br />
• Illegale partielle Anordnungen werden<br />
nicht weiter ausgebaut ( Sackgasse)<br />
• Im Gegensatz zum „brute force“-Ansatz, der alle Situationen betrachtet<br />
• Wenn nicht frühzeitig viele Alternativen ausgeschlossen werden können,<br />
entsteht aber dennoch ein (abgeschwächter) exponentieller Aufwand<br />
375<br />
Das n-Damen-Problem<br />
Das 8-Damen-Problem:<br />
Es handelt sich um ein Problem,<br />
das erstmals von Max<br />
Bezzel 1845 in einer Schachzeitung<br />
veröffentlicht wurde<br />
(„Wieviel Steine mit der Wirksamkeit<br />
der Dame können<br />
auf das im übrigen leere<br />
Brett…“), aber zunächst unbeachtet<br />
blieb. Erst als die<br />
Aufgabe 1850 vom blinden<br />
Schachexperten Dr. Nauk erneut<br />
zur Diskussion gestellt<br />
wurde, fand sie breites Echo.<br />
Als Nauk 3 Monate später alle<br />
92 Lösungen publizierte, hatte<br />
der berühmte C.F. Gauss erst<br />
72 Lösungen gefunden!<br />
(Quelle: Wolfgang Urban, Wien)<br />
376<br />
159
Das n-Damen-Problem<br />
• Keine der n „Damen“ auf dem n×n<br />
Schachbrett darf eine andere bedrohen<br />
• d.h. keine zwei Damen stehen in der<br />
selben Zeile, Spalte, Diagonale<br />
• Wie viele Lösungen gibt es für n=8?<br />
x<br />
377<br />
Repräsentation der Spielsituation<br />
dia1[10]<br />
dia2[5]<br />
• Darstellung der Spielsituation durch<br />
ein (globales) int-Array dame[0..n]:<br />
x<br />
1<br />
2<br />
3<br />
4<br />
5<br />
6<br />
7<br />
8<br />
Spalte x<br />
dame[x]<br />
1<br />
5<br />
8<br />
6<br />
3<br />
7<br />
2<br />
4<br />
y-Koordinate<br />
x<br />
Man braucht also gar kein (aufwendiges)<br />
2-dimensionales Array für den Spielzustand!<br />
• Zweckmässig sind ferner 3 (globale!)<br />
boolean-Arrays als „abgeleitete<br />
Grössen“ aus der Spielsituation:<br />
• zeile[y] == true: Zeile y ist bedroht<br />
• dia1[k] == true: Ha<strong>up</strong>tdiagonale<br />
mit x+y=k ist bedroht (k=2,…,16)<br />
• dia2[k] == true: Nebendiagonale<br />
mit x-y+7=k ist bedroht (k=0,…,14)<br />
379<br />
160
Eine Lösung mit Backtracking<br />
Ansatz:<br />
Setze eine (einzige) Dame in<br />
Spalte x.<br />
Löse dann das Problem<br />
rekursiv für das Brett aus<br />
den Spalten x+1,..., n.<br />
x<br />
Dabei die Bedrohungen der<br />
Damen aus Spalten 1,...,x<br />
berücksichtigen!<br />
380<br />
Eine Lösung mit Backtracking (2)<br />
static void setze(int x) {<br />
for (int y=1; y
Eine Lösung mit Backtracking (3)<br />
• Der Aufruf erfolgt in main mit setze(1); dies erzeugt (in wenigen<br />
Millisekunden) 92 Lösungen (viele davon symmetrisch):<br />
• 15863724<br />
• 16837425<br />
• 17468253<br />
• 17582463<br />
• 24683175<br />
• ...<br />
• 84136275<br />
Positionen (1,1), (2,5), (3,8),<br />
(4,6), (5,3), (6,7), (7,2), (8,4)<br />
n Lösungen<br />
1 1<br />
2 0<br />
3 0<br />
4 2<br />
5 10<br />
6 4<br />
7 40<br />
8 92<br />
9 352<br />
10 724<br />
11 2680<br />
12 14200<br />
382<br />
Programm mit Demo-Ausgabe<br />
static void setze(int x) {<br />
for (int y=1; y
Backtrack-<br />
Beispiel<br />
für n=4<br />
384<br />
Backtrack-Beispiel für n=4<br />
Übung: Backtracking-<br />
Baum dazu skizzieren<br />
Dame gesetzt auf (4,3)<br />
Lösung gefunden: 2431<br />
http://ktiml.mff.cuni.cz/~bartak/<br />
constraints/images/backtrack.gif<br />
386<br />
163
11.<br />
Spielbäume,<br />
„game playing“<br />
Buch Mark Weiss „Data Structures & Problem Solving Using Java“ siehe: 459-469<br />
Literaturhinweis: Alexander Reinefeld: Entwicklung der Spielbaum-Suchverfahren:<br />
Von Zuses Schachhirn zum modernen Schachcomputer. In: W. Reisig, J.C. Freytag:<br />
Informatik – Aktuelle Themen im historischen Kontext, S. 241-273, Springer 2006<br />
388<br />
1949: Relais-basierte<br />
Tic-Tac-Toe-Maschine<br />
Historische Notiz<br />
Auf einem 3×3 Felder grossen<br />
Spielfeld machen die beiden<br />
Spieler abwechselnd ihre Zeichen<br />
(ein Spieler Kreuze, der andere<br />
Kreise). Der Spieler, der als erstes<br />
drei seiner Zeichen in eine Reihe,<br />
Spalte oder eine der beiden<br />
Ha<strong>up</strong>tdiagonalen setzen kann,<br />
gewinnt. Wenn allerdings beide<br />
Spieler optimal spielen, kann<br />
keiner gewinnen, und es kommt<br />
zu einem Unentschieden.<br />
http://de.wikipedia.org/wiki/Tic_Tac_Toe<br />
389<br />
164
1949: Relais-basierte<br />
Tic-Tac-Toe-Maschine<br />
Donald Davies (1924-2000) war<br />
britischer Physiker und Informatiker;<br />
er konstruierte um 1950<br />
den „Pilot ACE-Computer“ (nach<br />
Plänen des ACE-Computers<br />
von Alan Turing), ab 1955 von<br />
English Electric weiterentwickelt<br />
zum ersten kommerziellen britischen<br />
Computer (DEUCE –<br />
„Digital Electronic Universal<br />
Computing Engine“). 1960 erfand<br />
er die Paketvermittlung<br />
(engl. „packet switching“), die<br />
grundlegend für den Internet-<br />
Datenverkehr wurde.<br />
1949: Davies führt seine Maschine für Tic-Tac-Toe (bzw. „noughts and crosses”) vor<br />
„The noughts and crosses display worked absolutely beautifully – until<br />
some schoolchildren came along and pressed all the buttons at once!”<br />
390<br />
A Machine Using Post Office Relays<br />
• “A by-product of my electromechanical work was to finalise my<br />
quest for a machine to play noughts and crosses. In the evenings<br />
I built a machine using Post Office relays. NPL * put it forward as<br />
an exhibit at the Royal Society Soirée in 1949. Its clicking noise<br />
and nice display attracted people, so it received more attention<br />
than it deserved. I felt rather bad for the other exhibitors with<br />
real scientific achievements to show. Next day I was on the Daily<br />
Express front page and much more publicity followed…”<br />
• “This machine did not use the whole strategy, so I built a second<br />
model which was partly electronic, with a wire threaded through<br />
magnetic cores to represent each rule. This machine was a<br />
regular feature of the NPL children’s party.“<br />
* ) NPL = National Physical Laboratory (UK)<br />
Biogr. Mems Fell. R. Soc. 2002 48, 87-96; doi: 10.1098/rsbm.2002.0006<br />
391<br />
165
The Sydney Morning Herald – 12. Juni 1949<br />
Electric Brain to Play Noughts and Crosses<br />
392<br />
393<br />
166
Tic-Tac-Toe auf dem DEUCE-Computer (Davies)<br />
Focus<br />
Brilliance<br />
Focus<br />
Select DL<br />
Brilliance<br />
Vielleicht das erste interaktive Computerspiel (ca. 1952)… „the program enables<br />
the operator to compete with DEUCE at the game of Noughts and Crosses“<br />
394<br />
Die Konsole des DEUCE-Computers<br />
The control panel carries a row of 32 keys for the<br />
input of single numbers and a row of 32 lamps for<br />
the output of single numbers, an alarum buzzer and<br />
lamp to indicate the failure of an internal check in<br />
the program, two monitor cathode-ray tubes for displaying<br />
the contents of the various delay line storage<br />
positions, and keys for imitating almost everything<br />
that the DEUCE normally does automatically.<br />
395<br />
167
Die Konsole des DEUCE-Computers<br />
Single shot<br />
396<br />
60 Jahre später passt das<br />
als App in eine Smartwatch<br />
397<br />
168
Optimale Strategie für Tic-Tac-Toe<br />
“Optimal strategy for<br />
player X. In each grid, the<br />
shaded red X denotes the<br />
optimal move, and the<br />
location of O's next move<br />
gives the next subgrid to<br />
examine. Note that only<br />
two sequences of moves<br />
by O (both starting with<br />
center, top-right, left-mid)<br />
lead to a draw, with the<br />
remaining sequences<br />
leading to wins from X.”<br />
http://en.wikipedia.org/wiki/Tic-tac-toe<br />
398<br />
1951: Der NIM-Spielcomputer –<br />
Ein „Elektronengehirn“ von Ferranti<br />
Historische Notiz<br />
Das NIM-Spiel: Gegeben sei<br />
ein Haufen von n Zündhölzern.<br />
Die Spieler entfernen abwechselnd<br />
jeweils 1, 2, oder 3 Zündhölzer.<br />
Wer das letzte Hölzchen<br />
entfernt, hat verloren.<br />
It is not that the games … are chosen<br />
because they are clear and simple; rather it<br />
is that they give us, for the smallest initial<br />
structures, the greatest complexity, so that<br />
one can engage some really formidable<br />
situations after a relatively minimal diversion<br />
into programming. Marvin Minsky, 1968<br />
399<br />
169
Computerschach<br />
• Schach ist prominentester Vertreter des „automatischen<br />
strategischen Spielens“<br />
• Faszination: Verbindung von uraltem Spiel und moderner Technik<br />
• Herausforderung: Eine „intelligente“ Maschine bauen<br />
• Reiz: kleine, abgeschlossene Welt (klare Spielregeln)<br />
• Hoffnung (trügerisch!): Wenn man ein Spiel wie Schach<br />
beherrscht, dann auch viele andere Probleme (aus Wirtschaft,<br />
Politik...), zu deren Lösung „Intelligenz“ nötig ist<br />
• Es sind (nur noch) einige wenige menschliche Spieler<br />
(manchmal) besser als die besten Schachprogramme<br />
• Es gibt aber andere strategische Spiele, wo Maschinen<br />
den Menschen noch nicht ganz so überlegen sind<br />
400<br />
Der Schachtürke<br />
(Wolfgang von Kempelen, 1769)<br />
401<br />
170
Der Schachtürke<br />
K<strong>up</strong>ferstiche von Racknitz, 1789<br />
402<br />
Geschichtliches zum Computerschach<br />
• 1770: Baron von Kempelen baut einen „Türken“ als Schachmaschine<br />
(u.a. verliert Napoleon dagegen; E.A. Poe beschreibt den Betrug)<br />
• 1890: Torres y Quevedo baut ein mechanisches Gerät für Endspiel<br />
Turm und König gegen König<br />
• 1928: John von Neumann veröffentlicht einen Artikel „Zur Theorie der<br />
Gesellschaftsspiele“<br />
• 1944: Alan Turing versucht sich an handsimulierten Programmen<br />
• 1949: Claude E. Shannon (Bell Labs) verwendet das Minimax-Prinzip<br />
in einer Arbeit „Programming Computer for Playing Chess“<br />
• 1956: John McCarthy veröffentlicht den α-β-Algorithmus<br />
• ab 1957: Erste echte Schachprogramme<br />
(auf dem Elektronenröhren-Computer IBM 704)<br />
403<br />
171
“Mechanischer Apparat aus Spanien, 1911 konstruiert, setzt mit Turm und König<br />
matt, was eine krächzende Stimme von einer Schallplatte auf Spanisch verkündet.”<br />
404<br />
Paul Stern (links) und Nick<br />
Metropolis (rechts) am MANIAC-<br />
Computer in Los Alamos, ca. 1951<br />
405<br />
172
Dietrich Prinz (Ferranti), 1955<br />
Alex Bernstein an einer IBM 704-Konsole, 1958<br />
406<br />
Herbert Simon (1916–2001),<br />
Sozialwissenschaftler und Pionier<br />
der künstlichen Intelligenz,<br />
Turing-Preis 1975, Wirtschaftsnobelpreis<br />
1978, hier mit einem<br />
IBM 650-Computer (1958)<br />
408<br />
173
John McCarthy (1927-2011),<br />
Pionier der künstlichen Intelligenz,<br />
spielt Schach an der<br />
IBM 7090 in Stanford, ca. 1967<br />
409<br />
410<br />
174
George Arnold, Columbia University, 1972<br />
411<br />
Chris Daley (NASA), 1970<br />
412<br />
175
Alex Bell, Peter Kent,<br />
John Birmingham und<br />
J. Waldron an einer<br />
IBM 360/195, 1974<br />
413<br />
Northwestern University, CDC 6400<br />
414<br />
176
David Slate und Larry Atkin, 1975,<br />
via Teletype-Terminal mit Akustik-<br />
Koppler verbunden mit einem<br />
Mainframe-Computer an der<br />
Northwestern University<br />
415<br />
Geschichtliches zum Computerschach (2)<br />
• 1960er: Verbesserte Suchverfahren (z.B. Varianten von α-β), Bibliotheken<br />
für Eröffnungen und Endspiele, spezielle Schach-Hardware<br />
• 1968: „Levy-Wette“: Levy (internationaler Meister) wettet 250 Pfund,<br />
dass innerhalb von 10 Jahren kein Computer ihn im Schach schlägt<br />
• 1970: ACM first computer chess tournament<br />
• 1974: First world computer chess championship<br />
wird von KAISSA (UdSSR) gewonnen<br />
• 1978: Levy gewinnt seine Wette<br />
• 1979: Schaukampf im Fernsehen „Levy – chess 4.8“ geht<br />
unentschieden aus (Levy verlängert seine Wette um nur 2 Jahre)<br />
• Ende der 1970er Jahre tauchen die ersten<br />
Hobby-Schachcomputer im Spielwarenhandel auf<br />
418<br />
177
Hobby-Schachcomputer<br />
419<br />
Geschichtliches zum Computerschach (3)<br />
• 1987: Programm HITECH<br />
schlägt einen Schachgrossmeister<br />
(HITECH<br />
generiert ca. 10 Millionen<br />
Stellungen pro Sekunde)<br />
• 1990: „Deep Thought“<br />
(IBM; später in „Deep<br />
Blue“ umbenannt) generiert<br />
ca. 500 Millionen Stellungen pro Sekunde und<br />
erreicht damit eine „Denktiefe“ von ca. 11 Zügen<br />
• 1995: Beste Schachprogramme werden nur noch<br />
von ca. 100 menschlichen Spielern übertroffen<br />
• 1996: Turnier „Kasparov - Deep Blue“ endet 4:2 für den Schach-<br />
Weltmeister Kasparov (darunter zwei Spiele unentschieden)<br />
420<br />
178
Geschichtliches zum Computerschach (4)<br />
• 1997: Zweites Turnier „Kasparov -<br />
Deep Blue“ mit 6 Spielen endet<br />
3,5:2,5 für Deep Blue<br />
“In a shocking finale that lasted<br />
barely more than an hour, World<br />
Champion Garry Kasparov resigned<br />
19 moves into Game 6, handing a<br />
historic victory to Deep Blue.”<br />
• 2003: Deep Junior (Gewinner der World Computer Chess<br />
Championships in 2002) spielt gegen Kasparov 3:3 unentschieden<br />
• 2006: Deep Fritz 10 (auf Intel Core 2 Duo-Prozessor) gewinnt 4:2<br />
gegen Schach-Weltmeister Wladimir Kramnik (2 Siege, 2 Remis)<br />
421<br />
Sensation: Schach-Rechner schlägt Grossmeister<br />
Erstmals gewinnt ein Computer gegen einen Weltmeister<br />
12. Mai 1997 (tk)<br />
Der S<strong>up</strong>ercomputer Deep Blue von IBM (Branchenspitzname: Big Blue) schlägt<br />
den Schach-Grossmeister Garry Kasparov. Damit gewinnt erstmals ein Computer<br />
nicht nur über einen amtierenden Weltmeister. Experten halten Kasparov für den<br />
derzeit besten lebenden Schachspieler überha<strong>up</strong>t. Hatte die Schach-Ikone noch im<br />
letzten Jahr die Auseinandersetzung gegen die Maschine gewonnen, musste sich<br />
der Russe nun geschlagen geben.<br />
Das über sechs Partien gehende Match (dauerte eine Woche; komplette Dokumentation<br />
unter www.chess.ibm.com/home/b.html) endete 3,5 zu 2,5 für Deep Blue.<br />
Zwei der Partien gewann der auf Schach getrimmte S<strong>up</strong>er-Rechner, drei endeten<br />
Unentschieden und nur eine Partie verlor Deep Blue. Die letzte und entscheidende<br />
Partie eröffnete Kasparov mit einer Caro-Kann-Verteidigung. Zwar verlor Deep<br />
Blue dadurch einen Springer, trotzdem war der Schach-Grossmeister mit dieser<br />
Strategie chancenlos. Nach Verlust eines eigenen Springers, Läufers, Bauers und<br />
der Königin gab Kasparov auf. Die Spieldauer: Knapp eine Stunde oder 19 Züge.<br />
„Ich muss mich für die heutige Leistung entschuldigen“, so der 34jährige Kasparov in der Pressekonferenz.<br />
„Ich hatte keine richtige Energie für die Auseinandersetzung.“ Laut Beobachtern hatte der IBM-Rechner mit<br />
den ersten fünf Partien (bis dahin eine Niederlage, ein Sieg, drei Unentschieden) Kasparov‘s unerschütterliches<br />
Selbstbewusstsein der beste Schachspieler aller Zeiten zu sein, erschüttert. So gab Kasparov nach der<br />
mit einem weiteren Unentschieden endenden fünften Partie auch zu: „Ich muss mich vorsehen, denn ich kann<br />
jeden Spieler der Welt ausrechnen, aber ich kann nicht diese Maschine ausrechnen.“<br />
Deep Blue ist ein Computer der IBM-RS/6000-SP-Reihe. Seine Parallel-Prozessor-Architektur (Taktrate von<br />
130 Megahertz) mit für Schach optimierten Mikrochips kann zwei Millionen Positionen pro Sekunde errechnen.<br />
Verlor Deep Blue noch im letzten Jahr die erste Auseinandersetzung mit Kasparov (damals gewann der<br />
Grossmeister drei Partien, verlor eine und zwei endeten unentschieden), verdoppelten IBM-Techniker in den<br />
letzten Monaten die Rechenleistung des Computers auf den heutigen Stand. © 1997 by Ziff-Davis-Verlag GmbH<br />
179
2010 + : Mit neue Interaktionsmöglichkeiten<br />
425<br />
The Thinking Table<br />
“The Thinking Table, a work in<br />
progress, is a physical installation<br />
in which two people can play a<br />
game of chess. As they play, the<br />
table illuminates the board with<br />
thoughts of the future as seen by<br />
the artificial intelligence engine of<br />
the Thinking Machines. The board<br />
is both the arena in which the two<br />
players act, and a thought space<br />
in which their linked choices, deliberations<br />
and hostilities are made<br />
visible.”<br />
Martin Wattenberg, Marek Walczak<br />
http://mw2mw.com/thinking-table<br />
426<br />
180
„The machine's thoughts“<br />
“Thinking Machine 4 explores<br />
the invisible, elusive nature<br />
of thought. Play chess against<br />
a transparent intelligence, its<br />
evolving thought process visible<br />
on the board before you.<br />
A map is created from the<br />
traces of thousands of possible<br />
futures as the program<br />
tries to decide its best move.<br />
Those traces become a key<br />
to the invisible lines of force<br />
in the game as well as a window<br />
into the spirit of a thinking<br />
machine.”<br />
http://mw2mw.com/25<br />
427<br />
Zukunft?<br />
181
Spieltheorie<br />
„Spiele haben die Wissenschaft oft befruchtet: Glücksspiele inspirierten die<br />
Wahrscheinlichkeitstheorie, Gesellschaftsspiele die mathematische Spieltheorie.<br />
Die Frage, ob Computer Schach spielen könnten, dient der Informatik<br />
seit ihrer Frühzeit als Messlatte für die Wirksamkeit von heuristischen<br />
Suchverfahren und der Formalisierung von Wissen.“ (Jürg Nievergelt)<br />
• Untersuchung des rationalen / optimalen Verhaltens bei<br />
Konflikt- und Konkurrenzsituationen mehrerer Parteien<br />
• Optimierungstheorie (Gewinnmaximierung)<br />
• Operations Research<br />
• Wirtschaftstheorie (Kampf um Marktanteile)<br />
• Konflikttheorie (Politik, Militär)<br />
• Grundannahme: Jeder Spieler verhält sich „rational“ und<br />
ist bestrebt, seinen Gewinn zu maximieren<br />
• Meilenstein 1928 mit John von Neumanns<br />
Artikel „Zur Theorie der Gesellschaftsspiele“<br />
• In: Mathematische Annalen 100, pp. 295 - 320<br />
432<br />
433<br />
182
Endliche rein strategische 2-Personen-Spiele<br />
• Endlich:<br />
• Durch Sonderregeln (z.B. Verbot von Zugwiederholung)<br />
werden Spiele oft künstlich endlich gemacht<br />
• Bei endlichen Spielen ist vor allem das Ergebnis am Ende interessant<br />
(ob „das Spiel an sich“ reizvoll ist, ist mathematisch unerheblich)<br />
• Rein strategisch:<br />
• Spieler selbst treffen alle Entscheidungen<br />
• Zufall spielt keine Rolle<br />
• Gegenbeispiel: Roulett, Würfelspiele und viele Kartenspiele<br />
• 2-Personen:<br />
• Einfacher als n-Personen (keine Koalitionsbildung)<br />
• Kooperation, Überredung, Täuschung, List, Bluff, Belohnung,<br />
Bestrafung... bei 2 Personen i.Allg. weniger sinnvoll / notwendig<br />
als bei n Personen (insbes. bei Nullsummenspielen)<br />
437<br />
Nullsummenspiel;<br />
vollständige Information<br />
• Nullsummenspiel:<br />
• So hoch wie ein Spieler gewinnt, verlieren andere<br />
• Eigener Vorteil ist Nachteil des Gegners<br />
• Mit den Endsituationen ist für jeden Spieler eine<br />
Gewinnauszahlungsfunktion definiert; Summe<br />
aller Funktionswerte = 0 für jede Endsituation<br />
(z.B. +1, -1, 0 bei Gewinn, Verlust, Unentschieden)<br />
• Bei 2-Personen-Spielen handelt der Gegner den<br />
eigenen Interessen entgegengesetzt (unter der<br />
Grundannahme der Spieltheorie)<br />
Im folgenden interessieren uns nur:<br />
Endliche rein strategische 2-Personen-<br />
Nullsummenspiele mit vollständiger<br />
Information<br />
Bsp: Schach, Reversi,<br />
Dame, Mühle, Go und<br />
viele andere Brettspiele<br />
Gegenbeispiel:<br />
Erschliessen neuer Märkte<br />
durch Koalitionsbildung<br />
oder Kartelle (bei diesen<br />
Spielen gewinnen evtl.<br />
alle: „Win-Win-Situation“)<br />
• Vollständige Information:<br />
• Alle Spieler wissen (prinzipiell) gleich viel<br />
• Keiner hat einen verdeckten Informationsvorsprung<br />
• Jeder kann sich ganz in die Rolle des anderen<br />
hineinversetzen<br />
Gegenbeispiele: viele Kartenspiele<br />
(damit Spielwiederholung<br />
nicht langweilt,<br />
ist die verdeckte<br />
Information dann i.Allg.<br />
vom Zufall abhängig<br />
→ Karten mischen!)<br />
438<br />
183
Spielbäume<br />
Kann Max einen Sieg<br />
(positiv. Auszahlungswert)<br />
erzwingen?<br />
Spieler „Max“ und „Min“<br />
ziehen abwechselnd<br />
- Konvention: Max beginnt<br />
- Niveauweise abwechselnd<br />
Max-Knoten / Min-Knoten<br />
• Blätter beschreiben eine Endsituation<br />
• Sind mit dem Wert der Auszahlungsfunktion für Max markiert<br />
• Max ist bestrebt, ein Blatt mit hohem Wert zu erreichen<br />
• Entsprechend strebt Min einen möglichst kleinen (evtl. neg.) Wert an<br />
439<br />
Spielbäume (2)<br />
Echte Spielbäume sind<br />
sehr gross (Damespiel ca.<br />
10 78 Knoten, Schach ca.<br />
10 120 , Go ca. 10 761 ), daher<br />
vollständige Enumeration<br />
aller Zustände und Spielabläufe<br />
i.Allg. unmöglich<br />
• Ein Spiel ist vollständig durch den Baum beschrieben!<br />
• Abstrahiert von Spielregeln, vom ästhetischen Reiz des Spiels,...<br />
• In verschiedenen Knoten kann die gleiche Spielsituation auftreten<br />
• Jedem Ast entspricht ein möglicher Spielverlauf<br />
440<br />
184
Strategie<br />
• = Vollständiger Verhaltensplan: Handlungsvorschrift (d.h.<br />
Zug) für jede Situation, in die der Spieler kommen könnte<br />
• In der Praxis aber meist zu<br />
viel, um alles im Voraus zu<br />
berechnen<br />
• Daher oft nur kompakte,<br />
heuristische Regeln (z.B.:<br />
„wenn zwei Steine in einer<br />
Reihe stehen, dann...“)<br />
441<br />
Visualisierung einer Strategie σ für Max<br />
Strategie (für Max) dargestellt als<br />
Graph, der aus dem Spielbaum<br />
entsteht, indem man alle Kanten<br />
streicht und nur für jeden Max-<br />
Knoten eine einzige ausgehende<br />
Kante übrig lässt<br />
Das ist nur eine einzige von<br />
vielen denkbaren Strategien<br />
- Offenbar gibt es sehr viele Strategien<br />
- Welche Strategie wählt man?<br />
442<br />
185
Eine Strategie σ´ für Min<br />
Wie viele Strategien hat eigentlich<br />
ein Schachcomputer? Vielleicht<br />
nur eine einzige? Oder<br />
eine pro einstellbarer Spielstärke?<br />
Oder würfelt er heimlich?<br />
Damit ist der Spielverlauf (als „Überlagerung“<br />
der beiden Strategien σ und σ’) klar!<br />
Und es ist auch von vornherein<br />
klar, wer gewinnt!<br />
443<br />
Strategiewahl<br />
• In der Praxis wird ein Spieler i.Allg. vor jedem Zug neu<br />
nachdenken und die aktuelle Situation evaluieren<br />
• Rein theoretisch („mathematisch“) lässt sich ein Spiel<br />
aber auch so auffassen:<br />
• Σ = {σ 1 , σ 2 , ..., σ n } Strategiemenge für Max<br />
• Σ’ = {σ’ 1 , σ’ 2 , ..., σ’ m } Strategiemenge für Min<br />
• Max und Min wählen vor dem Spiel (in verdeckter Weise) je eine<br />
Strategie σ bzw. σ’ (ein Wechsel der Strategie im Laufe des Spiels<br />
ist bei dieser Auffassung sinnlos)!<br />
• Der Spielverlauf ist dadurch vollständig determiniert: Folgt man<br />
dem eindeutigen Ast, landet man bei einem Blatt, das mit dem<br />
Gewinn (bzw. „Verlust“, wenn negativ) markiert ist!<br />
444<br />
186
Auszahlungsmatrix<br />
• Für die Strategien Σ und Σ’ gibt es eine n×m-Matrix A<br />
(die „Auszahlungsmatrix“), die an der Stelle A[σ,σ’] den<br />
Gewinn (für Max) bzgl. des Strategiepaares (σ,σ’) angibt<br />
• Formal ist ein Spiel dann sogar alleine durch das Tripel<br />
(Σ, Σ’, A) definiert!<br />
445<br />
Beispiel: Das Spiel „Schere, Stein, Papier“<br />
(auch bekannt als „Schnick, Schnack, Schnuck“)<br />
• Zwei Spieler formen gleichzeitig (d.h. ohne Kenntnis der<br />
Entscheidung des Gegners) mit einer Hand ein Symbol<br />
• Spiel besteht nur aus einem einzigen (Doppel)-Zug<br />
• Gewinnregel:<br />
• Stein schlägt Schere (macht sie stumpf)<br />
• Schere schlägt Papier (schneidet es)<br />
• Papier schlägt Stein (wickelt ihn ein)<br />
Papier<br />
Stein<br />
• Auszahlungsmatrix:<br />
Schere Stein Papier<br />
Schere 0 1 -1<br />
Stein -1 0 1<br />
Papier 1 -1 0<br />
Schere<br />
Jeder Spieler kann zwischen 3<br />
Strategien Schere, Stein, Papier<br />
(für seinen einzigen Zug!) wählen<br />
Gibt es eine „beste“ Strategie?<br />
449<br />
187
Testspiel<br />
Schere Stein Papier<br />
Schere 0 1 -1<br />
Stein -1 0 1<br />
Papier 1 -1 0<br />
450<br />
Schnick, Schnack, Schnuck<br />
„Schnick, Schnack, Schnuck ist das Schach des kleinen Mannes: Fast alle<br />
essentiellen Mini-Konflikte (Wer bringt den Müll raus? Wer wechselt die<br />
vollen Babywindeln?) lassen sich über das Spiel entscheiden.“<br />
Mike Hanke (li.) und<br />
Marco Reus von Bor.<br />
Mönchengladbach<br />
knobeln per Schnick-<br />
Schnack-Schnuck, wer<br />
den Freistoss schiesst<br />
451<br />
188
Schnick, Schnack, Schnuck-Roboter<br />
„Schnick, Schnack, Schnuck ist das Schach des kleinen Mannes: Fast alle<br />
essentiellen Mini-Konflikte (Wer bringt den Müll raus? Wer wechselt die<br />
vollen Babywindeln?) lassen sich über das Spiel entscheiden.<br />
Jetzt haben japanische Forscher an einer<br />
Universität in Tokio die ultimative Siegesgarantie<br />
entwickelt: Eine Roboterhand gewinnt<br />
jeden Schnick-Schnack-Schnuck-<br />
Zweikampf.<br />
Nun ist der Knobel-Roboter kein Deep Fritz,<br />
er berechnet nicht aus Millionen von Möglichkeiten<br />
den besten Zug, sondern nutzt<br />
schlicht seine Schnelligkeit: Über die Handstellung<br />
erkennt seine Kamera innerhalb<br />
einer Millisekunde, ob sein menschliches<br />
Gegenüber Schere, Stein oder Papier zeigen<br />
wird. Der Roboter wählt daraufhin innerhalb<br />
einer weiteren Millisekunde seine Siegerstellung<br />
aus.“<br />
www.sueddeutsche.de/digital/maschine-vs-mensch-knobel-roboter-auf-siegeszug-1.1399531<br />
452<br />
S<strong>up</strong>erspiel aus Spielfolgen<br />
• „S<strong>up</strong>erspiel“: n-malige Wiederholung des einfachen Spiels<br />
• Denkübung: Wie sieht dann bei unserem Knobelspiel<br />
„Schere, Stein, Papier“ eine Strategie aus?<br />
• Ist diese einfach eine Folge von Symbolen<br />
„Schere“, „Stein“, „Papier“ der Länge n?<br />
• Gibt es gute und weniger gute Strategien?<br />
453<br />
189
Beste Strategie?<br />
• Zurück zu den „endlichen rein strategischen 2-Personen-<br />
Nullsummenspielen mit vollständiger Information“:<br />
• Annahme: Gegner spielt optimal<br />
• Nutzt jeden Fehler schonungslos aus<br />
• Lässt sich nicht bluffen<br />
• Weiss bestens Bescheid<br />
• Was ist dann die beste eigene „Strategie“?<br />
• Möglichst vorsichtig und risikolos spielen!<br />
Ist in der Praxis vielleicht<br />
eine übertrieben<br />
pessimistische Annahme!<br />
454<br />
Maximierung des Minimalgewinns<br />
• Betrachte alle Strategiepaare (σ,σ’):<br />
• Für festes σ von Max würde der Gegner Min<br />
ein solches σ’ wählen, das A[σ,σ’] minimiert<br />
• Diesen durch Min (potentiell) minimierten Gewinnwerten sollte Max<br />
diejenige eigene Strategie σ* entgegensetzen, die bezüglich der<br />
Auszahlungsmatrix den dann noch höchst möglichen Gewinn bringt<br />
σ*<br />
455<br />
190
Garantierter Mindestgewinn<br />
• Der garantierte Mindestgewinn G für Max beträgt also<br />
G = max i min j A[σ i ,σ’ j ]<br />
• Hierzu gehört für Max eine Strategie σ*<br />
Informatiker denken dabei<br />
an eine doppelte for-Schleife<br />
• Entsprechend ermittelt Min für sich einen garantierten<br />
Mindestgewinn G’ = min j max i A[σ i ,σ’ j ]<br />
• Hierzu gehört für Min eine Strategie σ’*<br />
(beachte: Matrix A gibt stets den Gewinn in Bezug auf Max an)<br />
• Wenn sowohl Max als auch Min ihren jew. Mindestgewinn<br />
garantiert haben wollen, wird sicherlich (σ*,σ’*) gespielt<br />
Fragen: - Erhält Max immer nur den Gewinn G oder evtl. sogar mehr?<br />
- Wie hängen G und G’ zusammen?<br />
456<br />
Optimale Strategien<br />
• Satz (o. Bew.): Für endliche rein strategische 2-Personen<br />
Nullsummenspiele mit vollständiger Information gilt G = G’<br />
In Von Neumanns Artikel:<br />
• Die zugehörigen Strategien σ* bzw. σ’* heissen<br />
optimale Strategien (für Max bzw. Min)<br />
• Das Paar (σ*,σ’*) bildet einen Gleichgewichtspunkt:<br />
• Es gilt: A[σ*,σ’*] ≥ A[σ,σ’*] für alle σ von Max<br />
• D.h.: wenn Min seine optimale Strategie σ’* anwendet, kann Max<br />
nichts sinnvolleres tun, als ebenfalls seine optimale Strategie σ*<br />
anzuwenden – entsprechendes gilt auch umgekehrt<br />
Eine Situation, bei der sich kein Spieler verbessern kann, indem (nur) er von<br />
der Strategiekombination abweicht, heisst auch Nash-Gleichgewicht (Theorie<br />
1951 von John Nash, dafür 1994 Nobelpreis für Wirtschaftswissenschaften)<br />
457<br />
191
Vernünftig aber langweilig…<br />
• Optimale Strategien wirken stabilisierend: Für keinen<br />
Spieler besteht Veranlassung, davon abzuweichen<br />
• Risiko würde steigen; Mindestgarantie würde verschlechtert<br />
• Weicht der Gegner von seiner optimalen Strategie ab, ist dies<br />
zum eigenen Vorteil<br />
• In diesem Sinne („optimaler“ Gegner!) sind die Strategien<br />
σ* und σ’* das Vernünftigste, was die Spieler tun können<br />
• Wenn es für Max das Vernünftigste ist, σ* zu spielen, dann weiss Min<br />
aber schon, dass Max diese Strategie wählen wird (und umgekehrt)!<br />
• So betrachtet ist Spielen „langweilig“ (risikolos!):<br />
• Für das Spiel (Σ, Σ’, A) bestimme σ* ∈ Σ sowie σ’* ∈ Σ’<br />
und streiche sodann den Gewinn A[σ*,σ’*] ein...<br />
459<br />
Gewinnstrategie und Gewinnstellung<br />
• Die Blätter des Spielbaums seien mit >0 („Max gewinnt“)<br />
bzw. 0 erreichbar ist:<br />
A[σ*,σ’*]<br />
>0 → σ* ist Gewinnstrategie für Max<br />
Minimax-Algorithmus<br />
• Eine optimale Strategie kann effizienter als nach der Definition<br />
(Enumeration aller Strategien) gefunden werden:<br />
• Sei γ die Auszahlungsfunktion für Blätter; dann definiere<br />
den Minimaxwert v(k) eines Knotens k so:<br />
γ k<br />
, falls k ein Blatt ist<br />
<br />
max v n | n ist direkter Nachfolger von k , falls k innerer MaxKnoten ist<br />
min v n | n ist direkter Nachfolger von k , falls k innerer MinKnoten ist<br />
• Die rekursive Definition lässt sich direkt in einen<br />
entsprechenden rekursiven Algorithmus umsetzen<br />
Minimax-<br />
Algorithmus<br />
Es wird von zwei Seiten am Werte … hin und her gezerrt, nämlich durch S1,<br />
der ihn möglichst groß, und durch S2, der ihn möglichst klein machen will.<br />
John von Neumann, 1928<br />
462<br />
Bottom-<strong>up</strong>-Minimax-Algorithmus<br />
Alternativ zum rekursiven<br />
Top-down-Ansatz können<br />
die Werte auch von den<br />
Blättern in Richtung Wurzel<br />
propagiert werden<br />
(„bottom <strong>up</strong>“)<br />
Blätter<br />
• Interpretation:<br />
• Die Wurzel erhält den Wert, den Max mindestens erreichen kann<br />
• Max wählt Alternative zum grössten direkten Nachfolger<br />
• Min wählt Alternative zum kleinsten direkten Nachfolger<br />
Optimale<br />
Strategie<br />
463<br />
193
Spielbäume mit binärem Gewinn<br />
• Verwende für die binären Werte „gewonnen“ / „verloren“<br />
die booleschen Werte ’true’ (’1’) bzw. ’false’ (’0’)<br />
• ’min’ und ’max’ ’und’ (’&’) bzw. ’oder’ (’|’)<br />
• Spielbaum entspricht damit einem Operatorbaum<br />
(für boolesche Ausdrücke!)<br />
Booleschen Ausdruck<br />
einfach ausrechnen!<br />
((0|1) & (1|0)) | ((0|0) & (1|0))<br />
Ermittlung der optimalen<br />
Strategie ist nun eine algebraische<br />
Rechenaufgabe!<br />
465<br />
Auswertung boolescher<br />
Operatorbäume / Spielbäume<br />
• Auswertung des Baums z.B. rekursiv und von links nach rechts:<br />
• Dabei Java-Shortcut-Operatoren ’&&’ bzw. ’||’ (statt ’&’ bzw. ’|’) anwenden: diese<br />
werten nur so viele Operanden aus wie nötig (’&’ bzw. ’|’ wertet dagegen alle aus)!<br />
• Führt zu Schnitten: Sobald ein |-Operator (Max-Knoten) den Wert ’1’ (bzw.<br />
’true’) von einem Nachfolger gemeldet bekommt, brauchen dessen restlichen<br />
Geschwister nicht mehr ausgewertet werden (analog für &-Operator)<br />
Denkübung: Wie müssen die direkten Nachfolger<br />
eines Knotens „sortiert“ sein, um möglichst<br />
viele Schnitte zu erzeugen? Und wie viele<br />
Blattauswertungen spart man so maximal?<br />
466<br />
194
467<br />
NO!<br />
./ .<br />
(Shortcut)<br />
468<br />
195
Auswerten von Spielbäumen<br />
• Man wird i.Allg. einen Spielbaum nicht erst ganz aufbauen<br />
und dann auswerten, sondern dynamisch nach Bedarf Knoten<br />
„expandieren“ und schritthaltend den Baum auswerten<br />
• Dazu existieren verschiedene Methoden:<br />
• Tiefensuche<br />
• Breitensuche<br />
• Bestensuche<br />
• ...<br />
469<br />
Tiefensuche („depth-first“)<br />
• Ein Knoten wird erst erzeugt, wenn alle „linken“<br />
Geschwister und alle deren (direkte und indirekte)<br />
Nachkommen erzeugt (und evtl. bewertet) wurden<br />
• z.B. L erst nach Z, P, B, K<br />
470<br />
196
Breitensuche („breadth-first“)<br />
• Knoten einer tieferen Ebene<br />
werden erst dann in Betracht<br />
gezogen, wenn alle Knoten<br />
der darüber liegenden Ebene<br />
bearbeitet wurden<br />
• Speicheraufwand zum<br />
Aufbewahren der Ebenen!<br />
471<br />
Bestensuche („best-first“)<br />
• Knoten bekommen einen „Schätzwert“; dann wird unter<br />
allen Geschwistern derjenige zuerst expandiert (d.h. alle<br />
dessen direkten Nachfolger ermittelt), der den besten<br />
Schätzwert hat<br />
• Man erzeugt damit Teilniveaus, steigt aber schnell in die<br />
Tiefe ab; wenn man aus der Tiefe wieder auftaucht,<br />
nimmt man als nächsten Knoten zum Expandieren<br />
denjenigen mit dem zweitbesten Wert etc.<br />
• Man hofft, so schnell zu einem guten Blatt zu kommen<br />
• Dann kann man sich evtl. damit zufrieden geben, ohne zu viele<br />
andere Teilbäume auswerten zu müssen<br />
• Ist aber nur nützlich, wenn der Schätzwert eines<br />
Knotens das Minimax-Resultat gut prognostiziert<br />
Heuristik: Wähle die Alternative<br />
mit den besten<br />
Erfolgsaussichten<br />
472<br />
197
Schlanke Spielbäume<br />
• Man versucht möglichst, Spielbäume<br />
schlank zu halten<br />
• Erlaubt eine grössere Tiefe<br />
bzgl. der interessanten Knoten<br />
• Erfordert aber eine Bewertung<br />
aller „Geschwister“ oder Nachkommen,<br />
bevor man tiefer absteigt,<br />
um nur die interessantesten<br />
weiterzuverfolgen<br />
( Bestensuche)<br />
• Menschen spielen offenbar sehr stark selektiv<br />
• Vorsicht: man kann sich bei der Bewertung täuschen<br />
und damit interessante Züge „irrtümlich“ abschneiden!<br />
473<br />
Beschränkung der Suchtiefe<br />
• Spielbäume sind i.Allg. zu gross, um sie bis zu den<br />
tatsächlichen Blättern durchlaufen zu können<br />
• Ausnahme: „Endspiel“ mit einigen wenigen Zügen<br />
• Man wird daher den Baum nur bis zu einer gewissen Tiefe<br />
aufbauen und dort die Suche abbrechen<br />
• Für die künstlichen Blätter schätzt man dann den Minimaxwert<br />
(d.h. man führt eine „statische“ Stellungsbewertung durch)<br />
• Die Bewertungsfunktion sollte „gut“ sein und effizient zu berechnen<br />
sein; sie ist oft entscheidend für gutes Spielverhalten!<br />
• Abbruchtiefe ist i.Allg. nicht fest<br />
• Hängt u.a. von der zur Verfügung stehenden Zeit ab<br />
• Kritische Situationen wird man tiefer analysieren als „stabile“<br />
474<br />
198
Baumschnitte<br />
• Man kann oft einige Knoten / Unterbäume abschneiden,<br />
die den Minimaxwert garantiert nicht beeinflussen<br />
• Ziel: Möglichst früh solche verzichtbaren Unterbäume<br />
erkennen und möglichst viele davon abschneiden<br />
Begründung für den Schnitt: Max<br />
wird von rechts einen Wert ≤ 2<br />
erhalten und daher sowieso den<br />
linken Zug (5) wählen; den Wert<br />
des abgeschnittenen Unterbaums<br />
braucht man also nicht zu kennen!<br />
Wie erkennt man Schnittmöglichkeiten<br />
systematisch?<br />
Wie maximiert man die Zahl der Schnitte?<br />
475<br />
Der Alpha-Beta-(„α-β“)-Algorithmus<br />
• Reduziert den Spielbaum systematisch durch Schnitte,<br />
aber liefert den gleichen Minimaxwert der Wurzel wie<br />
der eigentliche Minimax-Algorithmus<br />
• Tiefensuche, wobei Knoten nur dann expandiert werden,<br />
wenn sie (nach bisheriger Information) den Minimaxwert<br />
beeinflussen können<br />
• Analog zur Shortcut-Auswertung<br />
boolescher Operatorbäume<br />
476<br />
199
Der Minimax-Algorithmus (Pseudocode)<br />
eval sei die statische<br />
int maxValue(Gamestate g) {<br />
Bewertungsfunktion<br />
für Spielstellungen<br />
if cutofftest(g) return eval(g);<br />
for (GameState s=g.firstsucc; s!=g.lastsucc; s=s.nextsucc) {<br />
α = max(α, minValue(s) );<br />
}<br />
}<br />
return α;<br />
int minValue(Gamestate g) {<br />
}<br />
if cutofftest(g) return eval(g);<br />
for (GameState s=g.firstsucc; s!=g.lastsucc; s=s.nextsucc) {<br />
β = min(β, maxValue(s) );<br />
}<br />
return β;<br />
Bei (künstlichen)<br />
Blättern liefere<br />
cutofftest „true“<br />
477<br />
Der α-β-Algorithmus (Pseudocode)<br />
Bei (künstlichen)<br />
Blättern liefere<br />
cutofftest „true“<br />
eval sei die statische<br />
int maxValue(Gamestate g, int α, int β) {<br />
Bewertungsfunktion<br />
für Spielstellungen<br />
if cutofftest(g) return eval(g);<br />
for (GameState s=g.firstsucc; s!=g.lastsucc; s=s.nextsucc) {<br />
α = max(α, minValue(s, α, β) );<br />
if (α >= β) break; // β-Schnitt<br />
}<br />
return α;<br />
}<br />
int minValue(Gamestate g, int α, int β) {<br />
}<br />
if cutofftest(g) return eval(g);<br />
for (GameState s=g.firstsucc; s!=g.lastsucc; s=s.nextsucc) {<br />
β = min(β, maxValue(s, α, β) );<br />
if (β
Der α-β-Algorithmus<br />
for (GameState s=g.firstsucc; s!=g.lastsucc; s=s.nextsucc) {<br />
β = min(β, maxValue(s, α, β) );<br />
if (β
α-β-Schranken: Beispiel<br />
Bei Knoten X: α=2<br />
nach Auswertung<br />
von Blatt „2“; aber<br />
α=5 nach Auswertung<br />
von Blatt „5“<br />
α=9<br />
≥β=5<br />
for (GameState s=g.firstsucc; s!=g.lastsucc; s=s.nextsucc) {<br />
α = max(α, minValue(s, α, β) );<br />
if (α >= β) break; // β-Schnitt<br />
}<br />
481<br />
Tiefe α-β-Schnitte<br />
• Der α- bzw. β-Wert einer<br />
bestimmten Ebene kann<br />
(weiter rechts) Schnitte<br />
auf einer viel tieferen<br />
Ebene bewirken<br />
• Interpretation: Man braucht sich als<br />
Min-Spieler bei x keine Hoffnungen<br />
auf einen besseren Wert (
Übungsbeispiel α-β-Algorithmus<br />
• Illustriert auf 75 slides Schritt für Schritt<br />
den Ablauf des α-β-Algorithmus<br />
• Homepage der Vorlesung<br />
www.vs.inf.ethz.ch/edu/<br />
483<br />
Effizienz des α-β-Algorithmus<br />
• Wie viele Blätter ausgewertet werden, hängt von der<br />
Reihenfolge der Knoten einer Ebene ab:<br />
• Nachfolger von Min-Knoten sollten aufsteigend sortiert<br />
sein, Nachfolger von Max-Knoten absteigend, damit<br />
maximal viele Schnittmöglichkeiten gegeben sind<br />
(d.h. bester Zug sollte jeweils links stehen)<br />
484<br />
203
Wie viel spart α-β gegenüber Minimax?<br />
• Annahme: Tiefe d, jeder innere Knoten hat w direkte Nachfolger<br />
• Schlechtester Fall: es sind w d Blätter auszuwerten (wie Minimax)<br />
• Bester Fall (ohne Beweis): es sind ca. w d/2 Blätter auszuwerten<br />
• Das sind nur „√-soviele“ und erlaubt viel tiefere Bäume!<br />
• Wo zwischen w d/2 und w d liegt der „Normalfall“?<br />
• Wenn man die inneren Knoten entsprechend einer statischen Bewertungsfunktion<br />
sortiert, die eine gute Prognose für den tatsächlichen<br />
Wert darstellt, erhöht man die Chance auf Schnitte<br />
• Eine statische Bewertung wird man i.Allg. sowieso vornehmen, um die<br />
Bestensuche anwenden zu können bzw. den Baum schlank zu halten<br />
• Experimentelle Untersuchungen zeigen, dass (schon bei unsortierten<br />
Knoten), der effektive Verzweigungsgrad bei ca. 3/4 w im Durchschnitt<br />
für typische Spielbäume liegt bei gleichem Zeitaufwand kann die<br />
Suchtiefe etwa um 33% erhöht werden<br />
485<br />
Der α-β-Algorithmus<br />
Historische Notiz<br />
One of the inventors the alpha-beta algorithm was Arthur Samuel (1901–1990). He made the first<br />
checkers program on IBM’s first commercial computer. Since he had only a very limited amount of<br />
available computer memory, he implemented what is now called alpha-beta pruning. The program<br />
was a sensational demonstration of the advances in both hardware and skilled programming and<br />
caused IBM's stock to increase 15 points overnight. [From Wikipedia]<br />
486<br />
204
Spekulative Suchfenster<br />
• Idee: langsame Schrumpfung des Suchfensters vorwegnehmen<br />
• Dadurch bereits früh die Schnittmöglichkeiten erhöhen<br />
• Also: mit einem Suchfenster (α,β) ≠ (-∞,+∞) starten!<br />
• Was tun, wenn man sich „verspekuliert“ hat?<br />
• Wie stellt man das fest?<br />
• Bei einem Suchfenster (a,b) wird ein „Minimaxwert“ a geliefert<br />
→ tatsächlicher Minimaxwert ist ≤ a (also vermutlich < a)<br />
• Es wird der Wert b geliefert → analog (d.h. vermutlich > b)<br />
• Man wiederholt dann notgedrungen das ganze mit einem<br />
halbseitig erweiterten Suchfenster, z.B. (-∞,a) bzw. (b,+∞)<br />
• Denkübung: und wenn dabei wieder genau a bzw. b geliefert wird?<br />
487<br />
Last Move Improvement<br />
• Es kommt i.Allg. nicht auf den genauen Minimaxwert an,<br />
sondern nur auf den besten Zug aus der ggw. Stellung<br />
!<br />
• Skizze des Algorithmus:<br />
• Untersuche alle direkten Nachfolger<br />
eines Knotens wie gehabt,<br />
bis auf den letzten<br />
• Sei v der dabei ermittelte<br />
Minimaxwert<br />
• Für den Unterbaum des<br />
letzten Nachfolgers verwende<br />
das Suchfenster (v, v+1)<br />
488<br />
205
Last Move Improvement (2)<br />
• Beachte: (v, v+1) ist ein sogen. Nullfenster, es ist von vornherein<br />
klar, dass das Ergebnis ausserhalb des Suchfensters liegt<br />
• Es werden ganzzahlige Werte vorausgesetzt; die<br />
Randwerte führen schon Schnitte herbei<br />
• Von Bedeutung ist nur, ob das Ergebnis<br />
(bzgl. Max) ≤ v oder ob es ≥ v+1 ist<br />
• ≤ v → rechtester Zug ist uninteressant,<br />
da schlechter als einer der anderen Züge<br />
• ≥ v+1 → bester Wert liegt im rechtesten Unterbaum (genaue<br />
Lage und Wert allerdings unbekannt!), daher wähle diesen Zug<br />
• Vorteil: Durch das enge Fenster kommt es im rechtesten<br />
Unterbaum zu viel mehr Schnittmöglichkeiten<br />
Denkübung: Idee rekursiv auf den<br />
vorletzten Zug anwenden etc.?<br />
489<br />
Nullfenster-Suchverfahren<br />
• Im linksten Unterbaum den Minimaxwert v als Referenzwert ermitteln<br />
• Mit dem Nullfenster (v, v+1) zu „beweisen“ versuchen, dass alle<br />
anderen Unterbäume keinen besseren Wert ≥ v+1 (bei Max) liefern<br />
Falls der Beweis für einen Unterbaum nicht gelingt,<br />
abwarten, ob noch ein anderer Unterbaum einen<br />
Wert >v liefert; dann mit wiederholter Suche dessen<br />
genauen Minimaxwert ermitteln und als neuen Referenzwert<br />
für weiter rechts stehende Unterbäume<br />
verwenden (sonst ist der beste Zug eindeutig!)<br />
• Von Vorteil, wenn der linkeste Zug der beste oder zweitbeste ist<br />
• Ist oft der Fall, da man (z.B. für Bestensuche oder α-β) die Nachfolger<br />
einer statischen Bewertung unterzieht und entsprechend anordnet<br />
• Viele Schnitte durch das enge Suchfenster bei einem Reversi-Spielprogramm<br />
wurde so ca. 63% der Zeit gegenüber Standard-α-β gespart!<br />
490<br />
206
Das Horizont-Problem<br />
• Typisch für Spielprogramme: Eine unausweichliche<br />
Katastrophe wird nur um einige Züge hinausgeschoben<br />
• Über den Horizont (= maximale Suchtiefe), dadurch sieht die<br />
Zukunft zunächst wieder einigermassen rosig aus<br />
• Z.B. durch irrelevanten Zug,<br />
der mit der Vermeidung<br />
der Katastrophe nichts<br />
zu tun hat, aber den<br />
Gegner zunächst zu<br />
einer unmittelbaren<br />
Antwort zwingt<br />
Horizont<br />
491<br />
Iterative Vertiefung<br />
• Der Spielbaum wird zunächst bis zu<br />
einer Tiefe n ausgewertet; wenn<br />
dann noch genügend Zeit bleibt,<br />
wird er wiederholt ausgewertet,<br />
dann aber bis zur Tiefe n+1<br />
• Auswertung bis zur Tiefe n ist<br />
billig im Vergleich zur Auswertung<br />
bis zur Tiefe n+1 (Wiederholung<br />
kostet nicht viel)<br />
• Bei der früheren Iteration wird Information bzgl. der statischen<br />
Bewertung von Stellungen gewonnen – diese kann bei der n+1-ten<br />
Iteration als Sortierkriterium für den α-β-Algorithmus dienen<br />
• Um Mehrfachauswertungen von Knoten zu vermeiden, könnten evtl.<br />
einige Information aus früheren Auswertungen aufbewahrt werden<br />
492<br />
207
Endspielanalyse<br />
• Wenn man weiss, dass das Spiel nur noch einige wenige<br />
Züge dauert:<br />
• Evtl. kann dann der Baum vollständig analysiert werden<br />
• Falls es auf die Höhe des Gewinns nicht ankommt, genügt dafür<br />
die zweiwertige Menge {gewonnen, verloren-oder-unentschieden}<br />
• Bei zweiwertigen Knoten gibt es mehr Schnitte, daher wird man<br />
das Endspiel zunächst damit analysieren; wenn man dann noch<br />
näher am Spielende ist, wird auch die Gewinnhöhe maximiert<br />
• Zweiwertige Bäume können durch boolesche Operatoren<br />
besonders effizient analysiert werden<br />
493<br />
Weitere Spielaspekte<br />
• Zeitmanagement<br />
• Die zur Verfügung stehende Zeit (Gesamtzeit oder Zeit pro Zug) sollte<br />
bestmöglich genutzt, aber nicht überschritten werden<br />
• Andere Spielbaumsuchverfahren:<br />
• Neben den besprochenen Algorithmen gibt es noch andere Verfahren<br />
• Lernende Spielprogramme:<br />
• Was soll gelernt werden?<br />
• Beispiel: Optimierung der Gewichtung einzelner Merkmale, die in die<br />
statische Bewertung einer Stellung einfliessen<br />
• Kann ein Programm lernen, indem es gegen sich selbst spielt?<br />
• Literaturhinweise:<br />
• C.G. Diderich: A bibliography on minimax trees, ACM SIGACT News 24(4), 82-89<br />
• Alexander Reinefeld: Spielbaum-Suchverfahren. Springer, 1989<br />
(Informatik-Fachberichte 200, Subreihe Künstliche Intelligenz)<br />
494<br />
208
Kritik<br />
• Gegnermodell: Ist es realistisch, dass der Gegner<br />
• das Minimax-Prinzip anwendet?<br />
(und bis zu welcher Tiefe?)<br />
• rational spielt und unfehlbar ist?<br />
(sollte man auf Nachlässigkeit spekulieren?)<br />
• Schätzfehler: Bei den Werten künstlicher Blätter handelt<br />
es sich nur um Schätzwerte des echten Minimax-Wertes<br />
• Ist es in diesem Beispiel klug, den rechten Zug zu wählen?<br />
• Helfen Wahrscheinlichkeitsüberlegungen?<br />
Minimax zeigt sogar oft ein pathologisches<br />
Verhalten („Fehlerverstärkung“): Wenn die<br />
Genauigkeit der statischen Bewertungsfunktion<br />
nach unten hin nicht deutlich zunimmt, wird –<br />
mathematisch beweisbar – der nach oben<br />
propagierte Wert zunehmend ungenauer<br />
(kurz: je tiefer man analysiert, desto<br />
schlechter spielt man!)<br />
495<br />
Reversi<br />
• In den Übungen wird das Spiel „Reversi“programmiert<br />
• Auch unter „Othello“ bekannt<br />
• Gelegenheit, Teile der Theorie anzuwenden ( Turnier!)<br />
• Um 1880 von Lewis Waterman<br />
bzw. John Mollet erfunden<br />
• Die beiden haben sich deswegen<br />
heftig gestritten<br />
• Erste deutsche Version 1907<br />
bei Ravensburger<br />
• Ältere chinesische Vorläufer?<br />
• Spielregeln sind einfach, gute Strategien aber schwierig<br />
496<br />
209
Japperflocky<br />
’Twas logic, and the slippy lops<br />
Did gyre and rattle on the board:<br />
All ordered were the flippy flops,<br />
And the corner guards on toward.<br />
"Beware the Japperflock, my son!<br />
The end-game slip, the Stoner trap!<br />
Beware the frontier wall, and shun<br />
The nimble turning back."<br />
He took his Tower clock in hand;<br />
Tsukuda board for the tournament.<br />
So chatted he with the players, and<br />
to the proper table went.<br />
And, as in nervous thought he sat,<br />
The Japperflock began the game,<br />
Then whiffled through Rotating Flat,<br />
With furbled hands of flame.<br />
One, two! One, two! And follow through!<br />
His foe was in a Boscov Swindle!<br />
He kept him dead, and well ahead,<br />
The other’s discs did dwindle.<br />
"And hast thou wiped the Japperflock?<br />
Come to my arms, my counting boy!<br />
O fliptious snare! Stored move! X-Square!"<br />
He chortled in his joy.<br />
’Twas logic, and the slippy lops<br />
Did gyre and rattle on the board:<br />
All ordered were the flippy flops,<br />
And the corner guards on toward.<br />
– Hugo Calendar<br />
(Inspired by Othello and Jabberwocky, by Lewis Caroll)<br />
http://hugocalendar.com/documents/japperflocky.html<br />
498<br />
Literatur zu Reversi-Spielstrategien<br />
• K.-F. Lee , S. Mahajan: The Development of a World Class Othello Program,<br />
Artificial Intelligence 43, pp. 21-36, 1990 (empfehlenswert!)<br />
• A. Kierulf: New Concepts in Computer Othello: Corner Value, Edge Avoidance,<br />
Access and Parity; in: "Heuristic Programming in Artificial Intelligence: The<br />
First Computer Olympiad", ed.: D.N.L. Levy and D.F. Beal, pp. 225-240, Ellis<br />
Horwood, 1989<br />
• Dorrit Billman, David Shaman: Strategy Knowledge and Strategy Change in<br />
Skilled Performance: A Study of the Game Othello, American Journal of<br />
Psychology, Summer 1990, Vol. 103, No. 2, pp. 145-166<br />
• Michael Buro: Techniken für die Bewertung von Spielsituationen anhand von<br />
Beispielen, Dissertation der Uni-GH Paderborn, 1994,<br />
www.cs.ualberta.ca/~mburo/ps/mics_dis.pdf (beschreibt u.a. das Reversi-<br />
Spielprogramm "LOGISTELLO", eines der weltweit besten Spielprogramme)<br />
• Michael Buro: An Evaluation Function for Othello Based on Statistics,<br />
NEC Research Institute Technical Report #31, 1997,<br />
www.cs.ualberta.ca/~mburo/publications.html<br />
499<br />
210
Literatur zu Reversi-Spielstrategien (2)<br />
• Michael Buro: How Machines have Learned to Play Othello", IEEE Intelligent<br />
Systems J. 14(6) 1999, 12-14,<br />
www.cs.ualberta.ca/~mburo/publications.html<br />
• Mark G. Brockington: Keyano Unplugged - The Construction of an Othello<br />
Program, Technical Report 97-05, Department of Computing Science,<br />
University of Alberta, 1997,<br />
www.cs.ualberta.ca/~games/articles/keyanotr.ps.gz<br />
• Hugo Calendar (Editor): Othello Strategy and Tactics. Referenz bei<br />
www.andreazinno.it/orules.html (dort weitere Hinweise zu Reversi)<br />
• Michael Buro: The Evolution of Strong Othello Programs, Proc. IWEC-2002<br />
Workshop on Entertainment Computing (2002),<br />
www.cs.ualberta.ca/~mburo/ps/compoth.pdf<br />
500<br />
Internet-Ressourcen zu Reversi<br />
• http://home5.swipnet.se/~w-50714/othello/tutorial/intro.htm<br />
• http://home5.swipnet.se/~w-50714/othello/tutorial/tutorial.htm<br />
• Othello Gateway http://othellogateway.com/<br />
• http://www.othello.org.hk/tutorials/eng-tu2.html<br />
• broken link<br />
• http://www.othello.org.hk/tutorials/eng-tu3.html<br />
• broken link<br />
• Nethello ist ein Reversi-Programm, das direkt von einem<br />
WWW-Browser aus aufgerufen werden kann und gegen<br />
das man interaktiv spielen (und verlieren...) kann:<br />
• broken link: http://cgi.student.nada.kth.se/cgi-bin/d95-mih/nethello.pl<br />
501<br />
211
12.<br />
Rekursives<br />
Problemlösen<br />
Buch Mark Weiss „Data Structures & Problem Solving Using Java“ siehe:<br />
- 327-363 (rek. Problemlösen)<br />
- 393-396 (Mergesort)<br />
504<br />
Rekursion<br />
505<br />
212
506<br />
508<br />
213
511<br />
512<br />
214
513<br />
Ein Beispielproblem: die Türme von Hanoi<br />
Bildquelle: Wikipedia<br />
514<br />
215
Türme von Hanoi<br />
• Ein buddhistischer Mönchsorden versucht sich der<br />
Legende nach seit vielen Jahren an folgendem Problem:<br />
• 64 goldene Scheiben verschiedener<br />
Grösse sind aufeinander gestapelt<br />
• Es darf immer nur eine kleinere Scheibe<br />
auf einer grösseren liegen, nie umgekehrt<br />
• Scheiben dürfen nur einzeln von einem<br />
„Turm“ zu einem anderen gebracht werden<br />
• Am Ende sollen alle Scheiben korrekt auf<br />
Turm 3 liegen<br />
• Gesucht ist ein Algorithmus,<br />
der angibt, wann welche Scheibe<br />
von wo nach wo zubewegen ist<br />
(falls das Problem überha<strong>up</strong>t lösbar ist!)<br />
Ein Problem, das sich relativ leicht<br />
rekursiv lösen lässt, aber nicht so<br />
einfach ohne Rekursion<br />
1883 vom französischen Mathematiker<br />
Eduard Lucas beschrieben<br />
515<br />
Türme von Hanoi – Animation<br />
516<br />
216
Türme von Hanoi – Animation mit Roboter<br />
Video [2:21] by Yanyu Su and Kyuhwa Lee, Imperial College London, http://vimeo.com/41611733 or<br />
http://www.youtube.com/watch?v=KOgmSpfcxCY (“iCub Block Manipulation”)<br />
517<br />
Türme von Hanoi – Lösungsidee<br />
• Die einzige Möglichkeit, die unterste (grösste) Scheibe von<br />
Turm 1 nach Turm 3 zu bewegen:<br />
(a) Auf Turm 1 befindet sich nichts sonst<br />
(b) Turm 3 ist leer<br />
• Aus (a) und (b) folgt:<br />
(c) Alle anderen Scheiben<br />
befinden sich auf Turm 2!<br />
• → Es müssen zunächst die n-1 anderen Scheiben von<br />
Turm 1 nach Turm 2 gebracht werden<br />
518<br />
217
Rekursionsansatz<br />
• „Es müssen zunächst die n-1 anderen Scheiben von<br />
Turm 1 nach Turm 2 gebracht werden“<br />
• Dies ist das gleiche Problem in kleinerer Dimension<br />
(denn offenbar behindert die hier nicht betrachtete unterste<br />
Scheibe auf Turm 1 nicht die Lösung des Teilproblems)<br />
• Lösungsansatz für das Gesamtproblem:<br />
• Bringe den „n-1 Turm“ von 1 nach 2<br />
• Bewege die Scheibe von 1 nach 3<br />
• Bringe den „n-1 Turm“ von 2 nach 3<br />
• Wieso ist folgende ähnliche Überlegung falsch?<br />
• Bewege die oberste (kleinste) Scheibe von 1 nach 2<br />
• Bringe den restlichen „n-1 Turm“ von 1 nach 3<br />
• Bewege die eine (kleinste) Scheibe von 2 nach 3<br />
<br />
<br />
<br />
519<br />
Das rekursive Java-Programm<br />
class Hanoi {<br />
}<br />
Das ist ein Programm ohne Zuweisung!<br />
(→ funktionaler Programmierstil)<br />
void bewege(int von, int nach) {<br />
System.out.println("Eine Scheibe von Turm " +<br />
von + " nach Turm " + nach);<br />
}<br />
void hanoi(int groesse, int von, int nach) {<br />
if (groesse == 1)<br />
Könnte man die Rekursion nicht bei 0 statt 1<br />
bewege(von, nach); aufhören lassen? Wie sieht das dann aus?<br />
else {<br />
hanoi(groesse-1, von, 6-von-nach);<br />
bewege(von, nach);<br />
hanoi(groesse-1, 6-von-nach, nach);<br />
}<br />
}<br />
Der "andere" Turm<br />
520<br />
218
Die Programmausgabe von „hanoi(4,1,3)“<br />
void hanoi(int groesse, int von, int nach) {<br />
if (groesse == 1)<br />
bewege(von, nach);<br />
else {<br />
hanoi(groesse-1, von, 6-von-nach);<br />
bewege(von, nach);<br />
hanoi(groesse-1, 6-von-nach, nach);<br />
}<br />
}<br />
0)<br />
1)<br />
2)<br />
3)<br />
Eine Scheibe von Turm 1 nach Turm 2<br />
Eine Scheibe von Turm 1 nach Turm 3<br />
Eine Scheibe von Turm 2 nach Turm 3<br />
521<br />
Die Programmausgabe von „hanoi(4,1,3)“<br />
void hanoi(int groesse, int von, int nach) {<br />
if (groesse == 1)<br />
bewege(von, nach);<br />
else {<br />
hanoi(groesse-1, von, 6-von-nach);<br />
bewege(von, nach);<br />
hanoi(groesse-1, 6-von-nach, nach);<br />
}<br />
}<br />
0)<br />
1)<br />
2)<br />
3)<br />
4)<br />
5)<br />
6)<br />
7)<br />
Eine Scheibe von Turm 1 nach Turm 2<br />
Eine Scheibe von Turm 1 nach Turm 3<br />
Eine Scheibe von Turm 2 nach Turm 3<br />
Eine Scheibe von Turm 1 nach Turm 2<br />
Eine Scheibe von Turm 3 nach Turm 1<br />
Eine Scheibe von Turm 3 nach Turm 2<br />
Eine Scheibe von Turm 1 nach Turm 2<br />
522<br />
219
Dynamische Aufrufkette<br />
hanoi(4,1,3)<br />
if … bewege …<br />
hanoi(3,1,2)<br />
bewege(1,3)<br />
hanoi(3,2,3)<br />
hanoi(3,2,3)<br />
if … bewege …<br />
hanoi(2,2,1)<br />
bewege(2,3)<br />
hanoi(3,1,3)<br />
Tiefe der<br />
dynamischen<br />
Aufrufkette<br />
Schnappschuss des<br />
Berechnungszustandes<br />
hanoi(2,2,1)<br />
if … bewege …<br />
hanoi(1,2,3)<br />
bewege(2,1)<br />
hanoi(1,3,1)<br />
hanoi(1,2,3)<br />
bewege(2,3)<br />
hanoi(0,2,1)<br />
bewege(2,3)<br />
hanoi(0,1,3)<br />
void hanoi(int groesse, int von, int nach) {<br />
if (groesse == 1)<br />
bewege(von, nach);<br />
else {<br />
hanoi(groesse-1, von, 6-von-nach);<br />
bewege(von, nach);<br />
hanoi(groesse-1, 6-von-nach, nach);<br />
}<br />
}<br />
bewege(2,3)<br />
println(2,3)<br />
Es existieren gleichzeitig mehrere Instanzen<br />
der hanoi-Methode<br />
Jede wartet auf Fertigstellung der von ihr<br />
gerufenen Methode an der Aufrufstelle<br />
Mit der eigenen Instanz wird fortgefahren,<br />
wenn die Aufrufkette wieder bis dorthin<br />
abgebaut worden ist<br />
523<br />
Zeitkomplexität des Hanoi-Algorithmus<br />
• Nach der Legende ist das Ende der Welt erreicht,<br />
wenn die Mönche ihre Aufgabe ganz gelöst haben<br />
• Wann ist das bei unserem Algorithmus der Fall?<br />
• n = 64 Scheiben<br />
• Eine Scheibe / Minute<br />
• „Anfangsverdacht“ für die Zeitkomplexität (= Anzahl<br />
der Elementarschritte, hier: „bewege“) empirisch:<br />
n<br />
1<br />
2<br />
3<br />
4<br />
:<br />
t(n)<br />
1<br />
3<br />
7<br />
15<br />
:<br />
- Vermutung: t(n) = 2 n -1<br />
- Rekursionsformel: t(n) =<br />
1 für n=1<br />
2 t(n-1) + 1 sonst<br />
Korrektheit der Rekursionsformel ist klar, aber<br />
was ist mit der geschlossenen Formel 2 n -1?<br />
Beweis: zeige induktiv: 2 n -1erfüllt die Rekursionsformel<br />
524<br />
524<br />
220
Zum Weltuntergang<br />
• Da haben wir noch einmal Glück gehabt!<br />
• t(64) = 2 64 -1 min ≈ 10 19 min ≈ 10 14 Tage ≈ 3×10 11 Jahre<br />
• Die Welt existiert aber erst seit ca. 1.37×10 10 Jahren<br />
• Der Weltuntergang steht also noch nicht unmittelbar bevor...<br />
• Algorithmen exponentieller Zeitkomplexität t(n)= c n lassen<br />
sich in der Praxis oft selbst für „vernünftige“ Problemgrössen n<br />
auch auf sehr schnellen Computern kaum (jemals!) ausführen<br />
• Sie sind inhärent ineffizient<br />
525<br />
Effizientere und einfachere Algorithmen<br />
für das Problem?<br />
• Sind die Mönche vielleicht schlecht beraten?<br />
• D.h.: gibt es einen effizienteren Algorithmus (der<br />
die geforderten Nebenbedingungen des Problems einhält)?<br />
• Ja? (→ Algorithmus angeben!)<br />
• Nein? (→ Beweis, dass nicht!)<br />
• Oder: weiss vielleicht niemand, ob „ja“ oder „nein“ gilt?<br />
• Noch eine Frage: Gibt es einen (einfachen)<br />
nicht-rekursiven Algorithmus für das Problem?<br />
• Antwort: Ja, aber dieser wird hier nicht verraten<br />
• Tipp: Man betrachte 6-x-y für die Folge der Protokollzeilen bei<br />
Eine Scheibe von Turm x nach Turm y<br />
526<br />
221
Divide et impera – Ein interessantes Paradigma<br />
• Grundprinzip zur Problemlösung in der Informatik<br />
• Ursprung der Redewendung in der praktischen Politik<br />
• „Feinde“ (bezüglich Religion, Ethnie etc.) in<br />
Untergr<strong>up</strong>pen aufspalten<br />
• Einzelne Untergr<strong>up</strong>pen „besiegen“ oder<br />
„unter Kontrolle halten“<br />
• Evtl. Untergr<strong>up</strong>pen gegeneinander anstiften<br />
• Von den alten Römern bis zur Kolonialzeit<br />
und darüber hinaus angewendet<br />
• Oft mit langwierigen Konsequenzen<br />
antiquitatis.com<br />
527<br />
Beispiel: Minimum einer geordneten Menge<br />
Nach dem Prinzip „divide et impera“ („ divide and conquer“):<br />
(1) Teile die Menge P in 2 Teilmengen P1, P2 („Partition“), so dass:<br />
• P1 P2 = P<br />
• P1 P2 = Ø<br />
(2) Löse das Problem für die beiden<br />
jew. kleineren Mengen P1 und P2<br />
• Sei p’ das Minimum von P1<br />
• Sei p” die entspr. Lösung für P2<br />
(3) Vergleiche p’ mit p” und liefere<br />
den kleineren der beiden Werte<br />
als Ergebnis p zurück<br />
• Also: p = min(p’,p”)<br />
• Ist das gemogelt?<br />
• Geht das auch immer gut?<br />
(Für bel. Teilmengen P1,P2,…)<br />
• Lässt sich dieses Paradigma auch<br />
auf andere interessante algorithmische<br />
Probleme anwenden?<br />
528<br />
222
„Visualisierung“ der rekursiven<br />
Minimumbestimmung einer Menge<br />
Das ist ein Baum<br />
(in Darstellung als<br />
Mengendiagramm)<br />
529<br />
Eine andere Darstellung des Rekursionsbaums<br />
530<br />
223
Divide et impera: Voraussetzungen<br />
• Das Problem muss beim Partitionieren „einfacher“ / kleiner werden<br />
• Man muss richtig partitionieren:<br />
• Keine der beiden Teilmengen darf leer werden<br />
• Man kann nur richtig partitionieren, wenn die Ausgangsmenge zwei<br />
oder mehr Elemente enthält<br />
• Man muss also die Lösung für eine einelementige Menge anders bestimmen<br />
(aber: leere Menge?)<br />
• Man muss aus den Teillösungen die Gesamtlösung einfach<br />
zusammenbauen können<br />
• Die beiden Teilprobleme lassen sich u.U. sogar gleichzeitig bearbeiten<br />
(„Parallelisierung“)<br />
• Wieso eigentlich nicht gleich in mehr als 2 Teilprobleme aufspalten?<br />
531<br />
Beispiel: Mergesort<br />
• „Merge“ = Zusammenfassen von zwei sortierten Folgen<br />
zu einer einzigen sortierten Folge durch<br />
einfädeln,<br />
verflechten,<br />
verschmelzen<br />
fortgesetzte Auswahl des kleineren der<br />
beiden Anfangselemente<br />
532<br />
224
Mergesort – Prinzip<br />
Unsortierte Daten<br />
aufteilen<br />
Teil 1 Teil 2<br />
rekursiv sortieren<br />
Teil 1 sortiert Teil 2 sortiert<br />
merge<br />
Sortierte Daten<br />
muss nicht genau<br />
halbiert werden<br />
1) Sortiere die erste Hälfte der<br />
Daten mit Mergesort<br />
2) Sortiere die zweite Hälfte der<br />
Daten mit Mergesort<br />
3) Beide Hälften „mergen“<br />
• Rekursion abbrechen bei<br />
einer Folge der Länge 1<br />
533<br />
Mergesort – rekursiver Ansatz<br />
• Typische Anwendung des Divide-et-impera-Prinzips<br />
• Der rekursive top-down Mergesort-Algorithmus in Java:<br />
void mergesort(int li, re){<br />
...<br />
if (...) {<br />
m = (li+re)/2;<br />
mergesort(li,m);<br />
mergesort(m+1,re);<br />
...<br />
Denkübung: ist die Bestimmung von m korrekt,<br />
auch wenn li+re eine ungerade Zahl ergibt?<br />
534<br />
225
Mergesort – ein Beispiel<br />
535<br />
Mergesort mit verketteten Listen<br />
Sofern die Liste mehr als<br />
ein Element enthält:<br />
→ Aufspalten der Liste<br />
in zwei Teillisten (Durchlaufen<br />
und abwechselnd<br />
in L1 oder L2 einfügen):<br />
Die Listen L1 und L2<br />
jeweils rekursiv sortieren:<br />
L1 und L2 durchlaufen<br />
und das jeweils kleinere<br />
Objekt in eine neu aufgebaute<br />
Liste hinten anfügen<br />
(„merge“):<br />
537<br />
226
Variante: Bottom-<strong>up</strong>-Mergesort<br />
• Nicht rekursiv!<br />
1) Durchlaufe die Liste der Elemente und erzeuge geordnete Paare<br />
durch Mergen benachbarter sortierter Teillisten der Länge 1<br />
• Es werden sortierte Teillisten der Länge 2 erzeugt<br />
2) Durchlaufe die Liste und erzeuge geordnete Quadr<strong>up</strong>el<br />
durch Mergen benachbarter sortierter Teillisten der Länge 2<br />
• Es werden sortierte Teillisten der Länge 4 erzeugt<br />
3) …<br />
• → Bis eine sortierte Liste der Länge n erzeugt wird<br />
538<br />
Sortieren einer zufälligen Folge<br />
539<br />
227
Umgekehrt sortierte Ausgangsfolge<br />
540<br />
Bottom-<strong>up</strong>-Mergesort – Effizienz<br />
• Nach k Durchläufen hat man sortierte Teillisten der Länge 2 k<br />
• Nach log n Durchläufen hat man die Länge n ( fertig!)<br />
• Ein Durchlauf erfordert jeweils n „Schritte“<br />
• Pro Schritt: Vergleichen zweier Werte, Erhöhen eines Index...<br />
• Mergesort benötigt also (grössenordnungsmässig) n log n „Schritte“<br />
• Top-down- und Bottom-<strong>up</strong>-Mergesort führen i.w. die gleichen<br />
Merge-Operationen aus, allerdings in unterschiedlicher Reihenfolge!<br />
• Beim rekursiven Top-down-Mergesort gibt es folgenden<br />
Zwischenzustand: sortiert unsortiert<br />
• Dieser Zustand kann beim Bottom-<strong>up</strong>-Mergesort nicht<br />
auftreten, dort sind alle Teile stets etwa „gleich weit“<br />
541<br />
228
13.<br />
Komplexität von<br />
Algorithmen<br />
Buch Mark Weiss „Data Structures & Problem Solving Using Java“ siehe:<br />
- 223-227, 237-243, 248-250<br />
542<br />
Aufwand von Algorithmen<br />
• Algorithmen verbrauchen Rechenzeit<br />
• Benutzte Datenstrukturen benötigen Speicher<br />
• Wichtiges Ziel: Ressourcenverbrauch minimieren<br />
• Bsp. einer typ. Frage: Wie hoch ist der Ressourcenverbrauch beim<br />
Sortieren von n Elementen (bei einem konkreten Algorithmus)?<br />
• Frage präzisieren! (Im Durchschnitt? Im worst-case? Unter<br />
welchen Bedingungen?)<br />
• Es gibt Probleme, wo jeder mögliche Lösungsalgorithmus<br />
eine gewisse Mindestmenge von Ressourcen benötigt<br />
• Anwendung z.B.: sichere kryptographische Verfahren<br />
543<br />
229
Begriffe bei der Aufwandsanalyse<br />
• Problemumfang bzw. -grösse wird meist mit n bezeichnet<br />
• Oft: Anzahl der Eingabewerte<br />
• Manchmal aber auch: Anzahl der Bits der Eingabe<br />
• Der Aufwand (Bedarf an Zeit bzw. Speicherplatz in sinnvollen<br />
Einheiten) ist typischerweise von n abhängig<br />
• Wird daher als Funktion f(n) angegeben<br />
• Beim Zeitaufwand wird i.Allg. von konstanten Faktoren<br />
(und additiven Termen) abstrahiert<br />
• Also z.B.: f(n) = n log (n) statt genauer 3 + 7 n log (n)<br />
• Beim Speicheraufwand (bzgl. der Implementierung eines Algorithmus)<br />
abstrahiert man aber meist nicht von konstanten Grössen<br />
544<br />
Begriffe bei der Aufwandsanalyse (2)<br />
• Oft ist der Aufwand eines Algorithmus nicht nur von der<br />
Problemgrösse n, sondern von den konkreten Eingabewerten<br />
(bzw. deren Reihenfolge) abhängig, daher unterscheidet man:<br />
• günstigster Aufwand („best case“)<br />
• mittlerer Aufwand („average case“)<br />
• ungünstigster Aufwand („worst case“)<br />
• Oft ist man nur am (i.Allg. leichter zu bestimmenden) asymptotischen<br />
Aufwand (für n→∞) als Funktion von n interessiert<br />
• Achtung: für „kleine“ n kann ein Algorithmus mit schlechterem<br />
asymptotischen Aufwand besser sein (→ break even points)!<br />
• Komplexität eines Problems = geringstmöglicher Aufwand, der<br />
mit dem dafür besten Lösungsalgorithmus erreicht werden kann<br />
• Manche Probleme sind inhärent aufwendig / schwierig / „komplex“<br />
545<br />
230
Komplexitätsgrössenordnung<br />
• Zweck: Angabe der Grössenordnung der (Zeit)komplexität<br />
eines Algorithmus als Funktion der Eingabegrösse („n“)<br />
• Zeitkomplexität wird typw. als Anzahl der Schritte aufgefasst<br />
• Idee: Von unwesentlichen Konstanten abstrahieren<br />
• Technologieparameter, Implementierungsdetails<br />
• Schreibweise:<br />
• O(log n) logarithmische Komplexität<br />
• O(n) lineare Komplexität<br />
• O(n 2 ) quadratische Komplexität<br />
• O(c n ) exponentielle Komplexität<br />
• O(1) konstante Komplexität (d.h., unabhängig von n)<br />
546<br />
Die O-Notation<br />
• Man sagt, die Komplexität eines Algorithmus ist<br />
von der Grössenordnung O(f(n)), wenn für die<br />
„wirkliche“ Komplexität g(n) des Algorithmus gilt:<br />
∃<br />
∀<br />
∃n 0 , c>0 : ∀n ≥ n 0 : g(n) ≤ c f(n)<br />
• Beispiel: Für 7 + 1.5 n + 3n 2 schreibe O(n 2 )<br />
• Informelle Interpretation: Die Laufzeit wird „im<br />
wesentlichen“ durch O(...) beschränkt (oder oft<br />
auch unpräziser: „ist etwa proportional zu“)<br />
O(n 3 ) oder O(2 n )<br />
wäre auch richtig,<br />
aber „übertrieben“<br />
Interessant ist<br />
i.Allg. die „kleinste“<br />
Funktion f, die g<br />
majorisiert<br />
• Oft kann man die Grössenordnung in O-Notation angeben,<br />
ohne die wirkliche Komplexität exakt zu kennen!<br />
547<br />
231
Laufzeiten und Problemgrössen<br />
Laufzeit für kleine<br />
Problemgrössen bei<br />
Beispielalgorithmen<br />
548<br />
Laufzeiten und Problemgrössen (2)<br />
Laufzeit für grosse<br />
Problemgrössen<br />
549<br />
232
Beispiele aus der Vorlesung<br />
• Asymptotische Zeitkomplexität:<br />
• ägypt_mult(m,n): O(log n)<br />
• hanoi(n): O(2 n )<br />
• insertion sort, deletion sort: O(n 2 )<br />
aver.<br />
case<br />
worst<br />
case<br />
• Sortieren mit Suchbäumen: O(n log n) ... O(n 2 )<br />
• Mergesort: O(n log n)<br />
• Binärsuche: O(log n)<br />
average und worst case<br />
550<br />
Maximaler Problemumfang bei gegebener Zeit<br />
• Beispiel: Bei Zeitbedarf von 10 μs für n=1:<br />
551<br />
233
Zeitbedarf bei vergrössertem Problemumfang<br />
• Beispiel: Bei 1 μs für n=1:<br />
552<br />
Bewältigbarer Problemumfang bei<br />
schnellerer Maschine<br />
Wenn „jetzt“ Problemumfang M bewältigbar ist, dann<br />
kann in gleicher Zeit eine 10 Mal schnellere Maschine<br />
folgenden Problemumfang M' bewältigen:<br />
f(n)<br />
M'<br />
Anzahl Berechnungsschritte<br />
(„Laufzeit“) bei Problemumfang n<br />
Beispiel: In 2 M Berechnungsschritten wird ein<br />
Problem der Grösse M bewältigt. Mit der<br />
schnelleren Maschine kann man (in gleicher<br />
Zeit) 10 Mal mehr Schritte, also 10 × 2 M<br />
Schritte, ausführen. Welche Problemgrösse M'<br />
kann mit so vielen Schritten bewältigt werden?<br />
Wir setzen M' als unbekannte, zu bestimmende<br />
Variable in die Laufzeitformel f(n) = 2 n ein:<br />
2 M' = 10 × 2 M . Logarithmieren (zur Basis 2)<br />
beider Seiten liefert M' = log 2 10 + M ≈ 3.3 + M.<br />
553<br />
234
Komplexitätsklassen O(f)<br />
• Mit O(f) wird jeweils eine ganze Funktionsklasse bezeichnet<br />
• Genauer: O(f(n)) = { g(n) | ∃ n 0 , c > 0: ∀n ≥ n 0 : g(n) ≤ c f(n) }<br />
• Statt g ∈ O(f) schreibt man oft salopper: g = O(f)<br />
• Notation dann z.B.: g(n) = O(n 2 )<br />
oder kürzer g = O(n 2 )<br />
• Interpretation: g wächst<br />
höchstens so schnell wie f<br />
• f = O(1) bedeutet: f ist beschränkt<br />
• Überschreitet einen bestimmten konstanten Wert nicht<br />
• Wichtig ist auch die Klasse der polynomiellen Funktionen:<br />
POLY(n) =∪p>0 O(n p )<br />
• Falls f ∈ POLY(n), dann spricht man von polynomiellem Aufwand<br />
(wesentlicher Unterschied dazu: exponentieller Aufwand O(c n ))<br />
n 0<br />
n<br />
554<br />
Die Ω-Notation<br />
• Für untere Schranken verwendet man die Ω-Notation:<br />
• Ω(f(n)) = { g(n) | ∃ n 0 , c > 0: ∀n ≥ n 0 : c f(n) ≤ g(n) }<br />
• Interpretation von g = Ω(f):<br />
• g wächst mindestens<br />
so schnell wie f<br />
n 0<br />
n<br />
555<br />
235
Beispiel: Komplexität von Mergesort<br />
• Beha<strong>up</strong>tung: Die Zeitkomplexität beträgt O(n log n)<br />
Beweisansatz:<br />
Anzahl der Schritte<br />
der rekursiven<br />
Top-down-Lösung<br />
mittels einer Rekursionsgleichung<br />
556<br />
Beispiel: Komplexität von Mergesort<br />
• Oder einfacher: Wir zeigen, dass die „vom Himmel gefallene“<br />
Formel t(n) = n + n log n die Zahl der Schritte angibt<br />
• Beweis induktiv mit dem Rekursionsansatz<br />
t(n) = n + 2 t(n/2)<br />
= n + 2 (n/2 + n/2 log (n/2))<br />
= n + n + n log (n/2)<br />
= n + n - n + n log n<br />
= n + n log n<br />
∈ O(n log n)<br />
n = Aufwand für „merge“<br />
von n Elementen<br />
Induktionsannahme für n/2<br />
wg. log (n/2) = (-1 + log n)<br />
[Logarithmus zur Basis 2]<br />
Denkübung:<br />
Induktionsanfang?<br />
557<br />
236
Komplexität von Sortieren<br />
• Es gilt (hier ohne Beweis), dass das Sortieren von n<br />
Elementen ein Problem mit Zeitkomplexität O(n log n) ist<br />
• Unter gewissen allgemeinen Bedingungen (dass z.B. über<br />
den Wertebereich der Elemente a priori nichts bekannt ist)<br />
• Das heisst, dass Mergesort (grössenordnungsmässig)<br />
ein optimaler Algorithmus für das Sortierproblem ist<br />
• Es gibt keinen „wesentlich“ besseren Sortieralgorithmus!<br />
• Aber vielleicht hilft ja Parallelität?<br />
• Z.B. mit Mehrkernprozessoren („multicore“)<br />
558<br />
Laufzeitvergleich von drei<br />
O(n log n)-Sortierverfahren<br />
Sekunden<br />
Abhängig von der konkreten<br />
Implementierung können die<br />
Ergebnisse allerdings etwas<br />
unterschiedlich ausfallen<br />
Heapsort<br />
Mergesort<br />
Heapsort besprechen wir<br />
später; Quicksort behandeln<br />
wir in dieser Vorlesung nicht<br />
Quicksort<br />
O(n 2 )-Sortierverfahren<br />
benötigen hier bereits<br />
hunderte von Sekunden<br />
n<br />
559<br />
237
14.<br />
Simulation<br />
Buch Mark Weiss „Data Structures & Problem Solving Using Java“ siehe:<br />
- 543-555 (ereignisgesteuerte Simulation)<br />
567<br />
Zedlers „grosses vollständiges Universal-<br />
Lexicon aller Wissenschafften und Künste,“<br />
…welche bißhero durch menschlichen Verstand<br />
und Witz erfunden und verbessert worden,…<br />
1732 bis 1754 erschienenen; rund 284 000 Artikel auf<br />
ca. 63 000 Seiten; 64 Bände und 4 S<strong>up</strong>plementbände<br />
Historische<br />
Notiz<br />
Simulare<br />
„Stellen“ im Sinne von „einen Zustand vortäuschen“ bzw. „sich<br />
verstellen“ (sich krank stellen; er stellte sich, als ob er schliefe)<br />
568<br />
238
Meyers Konversations-Lexikon 1885 – 1892<br />
•<br />
571<br />
Brockhaus-Lexikon 1895<br />
Simulation (lat. „Erheuchelung“, „Vorspiegelung“)<br />
ein Verhalten, welches einen dem wirklichen Sachver≈<br />
halt nicht entsprechenden Schein eines anderen Sach≈<br />
verhalts hervorruft, meistens in der Absicht zu täu≈<br />
schen. Juristisch kommt in Betracht die Simulation<br />
von Geisteskrankheiten, namentlich zur Vermeidung<br />
einer dem Simulanten drohenden strafrechtlichen Ver≈<br />
folgung, die Vorschützung von Gebrechen oder körper≈<br />
lichen Krankheiten, um vermögensrechtliche Vorteile zu<br />
erlangen, beim Militär, um sich der Dienstpflicht zu<br />
entziehen. xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx<br />
572<br />
239
Simulation – neuere Begriffsbestimmung<br />
Brockhaus 1983: Darstellung oder Nachbildung physikalischer,<br />
technischer, biologischer, psychologischer oder<br />
ökonomischer Prozesse durch mathematische oder physikalische<br />
Modelle, die eine wirklichkeitsnahe, jedoch einfachere,<br />
billigere oder ungefährlichere Untersuchung als das<br />
Objekt erlauben. xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx<br />
was<br />
wie<br />
wozu<br />
VDI-Richtlinie 3633: Nachbildung eines dynamischen<br />
Prozesses in einem Modell, um zu Erkenntnissen zu gelangen,<br />
die auf die Wirklichkeit übertragbar sind. xxxxxxxx<br />
574<br />
Simulation als Problemlösungstechnik<br />
• Kurze Definition: Experimente an einem Modell<br />
• „Simulationsmodell“<br />
• Simulation ist ein „Modellexperiment“<br />
• Soll Rückschlüsse auf das reale System ermöglichen<br />
• Prinzip: „Wenn dies so wäre, dann würde folgendes geschehen“<br />
• Simulation ist also eine Problemlösungstechnik<br />
• Warum Simulation?<br />
• Für reale, komplexe Probleme / Abläufe gibt es oft keine „Lösungsformel“<br />
• Simulationen sind i.Allg. aufwendig<br />
• Oft muss man einen Ablauf in vielen verschiedenen Varianten untersuchen<br />
• Für statistisch Relevanz stochastische Simulationen mehrfach wiederholen<br />
• Hoher Detailierungsgrad benötigt lange Laufzeit<br />
575<br />
240
Modelle und Modellierung<br />
• Modell = Vereinfachtes Abbild der komplexen Realität<br />
• Soll sich bzgl. relevanter Aspekte (=?) aber analog zur Realität<br />
darstellen oder verhalten<br />
Reduktion,<br />
Vergröberung!<br />
Modelle dienen u.a. dem Begreifen<br />
der Wirklichkeit als Voraussetzung<br />
für planvolles Handeln<br />
- In diesem Sinne evtl. auch bereits<br />
prähistorische Höhlenmalereien<br />
- Spielen von Kindern<br />
„Dass die Folgen der Bilder stets<br />
wieder die Bilder seien von den<br />
naturnotwendigen Folgen der<br />
abgebildeten Gegenstände“<br />
(Heinrich Hertz)<br />
Simulation = Durchspielen am Modell<br />
576<br />
Zweck und Anwendung von Simulation<br />
• Zweck von Modellierung und Simulation<br />
• Optimierung (z.B. Wirkung der Beseitigung von Engpässen)<br />
• Entscheidungshilfe (Auswahl von Entwurfsalternativen)<br />
• Prognose (z.B. Wetter)<br />
• Validierung (z.B. Schaltplan eines neuen Mikroprozessors)<br />
• Theorienbildung (z.B. kognitive Vorgänge)<br />
• Animation, Erklärung (pädagogische Hilfsmittel, „Demonstrationsmodell“)<br />
• Anwendungsgründe für Simulation<br />
• Rein mathematische Analyse nicht möglich<br />
• Reales System liegt nicht vor (ist z.B. erst geplant)<br />
• Realität erträgt Experiment nicht (Ökologie, Militär)<br />
• Realität zu schnell (chemische Reaktionen)<br />
• Realität zu langsam (Entstehung von Galaxien)<br />
• Reales Experiment zu teuer<br />
577<br />
241
Ursprünge von Modellierung und Simulation<br />
• Landkarten, Globus<br />
• Sandkastenspiele der Militärs<br />
• Formalisierung der Physik<br />
• Differentialgleichungen<br />
• Digitale Computer ab ca. 1945<br />
• Zunächst militärische und ökonomische Anwendungen<br />
578<br />
Einsatzbereiche von Simulation<br />
• Meteorologie, Klima<br />
• Verkehrsplanung<br />
• Industrielle Fertigung, Logistik<br />
• Militär<br />
• Volkswirtschaft<br />
• Ökologie, Biologie<br />
• Architektur<br />
• Grundlagenforschung (z.B. Physik, Chemie)<br />
• …<br />
579<br />
242
Beispiel: Klimasimulation<br />
http://www.bgc-jena.mpg.de/~martin.heimann/vorlesung/Animations/tmp2m_web.gif<br />
580<br />
Beispiel: Klimasimulation (Niederschlagsmenge 2100)<br />
http://www.noaanews.noaa.gov/stories2007/images/global-precip-end-21st-century2.jpg<br />
581<br />
243
Beispiel: Simulation eines Tornados<br />
582<br />
Beispiel: Strömungssimulation<br />
583<br />
244
Beispiel: Strömungssimulation (2)<br />
587<br />
Beispiel: Strömungssimulation (3)<br />
589<br />
245
Beispiel: Strömungssimulation (4)<br />
590<br />
Beispiel: Simulation von Verbrennungsvorgängen<br />
593<br />
246
Beispiel: Simulation von Luftkühlung<br />
http://static.datacenterdynamics.com/assets/image/0004/235147/Simulation-of-thermal-flows-in-datacenter.jpg<br />
594<br />
Beispiel: Simulation von Schmelzvorgängen<br />
http://gtresearchnews.gatech.edu/wp-content/<strong>up</strong>loads/2009/12/dancing-atoms-simulation.jpg<br />
595<br />
247
Beispiel: Protein-Simulation<br />
596<br />
Beispiel: S<strong>up</strong>ernova-Simulation<br />
598<br />
248
Beispiel: Simulation virtueller Welten<br />
Stage Space AG<br />
Linden Research (second life)<br />
600<br />
Beispiel: Produktionssimulation<br />
Plan B Automatisierung GmbH<br />
602<br />
249
Beispiel: Montagesimulation<br />
http://www.ea-online.de/xml-import/bilder/ia/2007-52/600x/thumb_ia52070184_tif.jpg<br />
603<br />
Beispiel: Verkehrssimulation<br />
http://gamma.cs.unc.edu/DCrowd/images/crosswalkUserInput.jpg<br />
604<br />
250
Beispiel: Verkehrssimulation<br />
www.ib-hurrle.de/Simula_Bild.jpg<br />
605<br />
Beispiel: Crash-Simulation<br />
606<br />
251
Beispiel: Simulation einer Autosteuerung<br />
Analog-Computer mit Servo-Motoren (General Motors, 1958)<br />
607<br />
Beispiel: Fahrsimulator (1950er-Jahre)<br />
608<br />
252
Beispiel: Fahrsimulator (1950er-Jahre)<br />
609<br />
Beispiel: Flugsimulator (1950er-Jahre)<br />
612<br />
253
Beispiel: Flugsimulator (1970er-Jahre)<br />
http://www.historyofpia.com/forums/viewtopic.php?f=8&t=13664<br />
613<br />
Beispiel: Flugsimulator (1990er-Jahre)<br />
615<br />
254
Beispiel: Flugsimulator (2000er-Jahre)<br />
http://www.planetds.de/screens.php?typ=spiel&code=airds&shot=1<br />
616<br />
Beispiel: Flugsimulator (2010er-Jahre)<br />
Max-Planck-Institut für biologische Kybernetik<br />
617<br />
255
Simulatoren für alles…<br />
618<br />
Noch ein wichtiges Beispiel:<br />
Simulationsmodelle für die Wetterprognose<br />
www.wetter3.de<br />
619<br />
256
Historische Vision der Wettervorhersage<br />
mittels numerischer Simulation<br />
Historische Notiz<br />
Mehrere 10000 menschliche<br />
Rechner in einer kugelförmigen<br />
Galerie um einen Dirigenten<br />
berechnen die Wettervorhersage.<br />
Eine auf die<br />
Wände gemalte Erdkarte definiert<br />
das Rechengitter, das<br />
eine räumliche Auflösung<br />
von einem Grad hat. Die Ergebnisse<br />
der Berechnung<br />
werden in einem Eimer zu<br />
einem Telegraphen herabgelassen,<br />
der die Wetterprognose<br />
verbreitet.<br />
Nach L.F. Richardson, Weather<br />
Prediction by Numerical Process,<br />
1922<br />
620<br />
Historische Vision der Wettervorhersage<br />
mittels numerischer Simulation<br />
621<br />
257
L.F. Richardson über seine<br />
Central Forecast-Factory for the Whole Globe<br />
622<br />
L.F. Richardson über seine<br />
Central Forecast-Factory for the Whole Globe<br />
623<br />
258
L.F. Richardson über seine<br />
Central Forecast-Factory for the Whole Globe<br />
“Imagine a large hall like a theatre, except that the circles and galleries go right round<br />
through the space usually occ<strong>up</strong>ied by the stage. The walls of this chamber are painted<br />
to form a map of the globe. The ceiling represents the north polar regions, England<br />
is in the gallery, the tropics in the <strong>up</strong>per circle, Australia on the dress circle and the<br />
Antarctic in the pit. A myriad computers are at work <strong>up</strong>on the weather of the part of<br />
the map where each sits, but each computer attends only to one equation or part of an<br />
equation. The work of each region is coordinated by an official of higher rank. Numerous<br />
little ‘night signs’ display the instantaneous values so that neighboring computers can<br />
read them. Each number is thus displayed in three adjacent zones so as to maintain<br />
communication to the North and South on the map. From the floor of the pit a tall pillar<br />
rises to half the height of the hall. It carries a large pulpit on its top. In this sits the<br />
maninchargeofthewholetheatre;heissurroundedbyseveralassistantsandmessengers.<br />
One of his duties is to maintain a uniform speed of progress in all parts of the<br />
globe. In this respect he is like the conductor of an orchestra in which the instruments<br />
are slide-rules and calculating machines. But instead of waving a baton he turns a<br />
beam of rosy light <strong>up</strong>on any region that is running ahead of the rest, and a beam of<br />
blue light <strong>up</strong>on those who are behindhand.<br />
Four senior clerks in the central pulpit are collecting the future weather as fast as it is<br />
being computed, and dispatching it by pneumatic carrier to a quiet room. There it will<br />
be coded and telephoned to the radio transmitting station.”<br />
624<br />
625<br />
259
Als „Computer“ noch Menschen waren…<br />
626<br />
Lewis Fry Richardson, 1881-1953<br />
Perhaps some day in the dim future it<br />
will be possible to advance the computations<br />
faster than<br />
the weather advances<br />
and at a<br />
cost less than the<br />
saving to mankind<br />
due to the information<br />
gained. But<br />
that is a dream.<br />
1922<br />
627<br />
260
Wettervorhersage ab ca. 1850<br />
Synoptische Wetterbeobachtungen<br />
(und<br />
Prognosen mittels einfacher<br />
Modelle) bald<br />
nach der Erfindung des<br />
Telegraphen ab Mitte<br />
des 19. Jahrhunderts.<br />
Wetterkarte der<br />
deutschen Seewarte<br />
von 1887<br />
628<br />
Wettervorhersage ab ca. 1850<br />
629<br />
261
Wetterwarte auf dem Säntis-Gipfel ab 1882<br />
„Zehn vollamtliche Säntisbeobachter,<br />
mehrheitlich mit ihren Ehegattinnen,<br />
haben von 1882 bis 1969 ihre<br />
anspruchsvolle Aufgabe immer wieder<br />
auch unter schwierigsten Wetterbedingungen<br />
wahrgenommen. Kennzeichnend<br />
waren wochenlange Einsamkeit<br />
mit zeitweise fehlendem<br />
Nahrungsmittelnachschub im Winter<br />
(bis 1935), stundenlange Enteisungsaktionen<br />
vor allem am Windmesser,<br />
ermüdendes Schneeschaufeln, Reparieren<br />
von Instrumenten.“ [Thomas<br />
Gutermann, DMG-Mitteilungen 4/2000]<br />
632<br />
Wetterwarte auf dem Säntis-Gipfel (1922)<br />
Der Wetterwart hatte täglich<br />
dafür zu sorgen, dass sich<br />
das Windmesserrad frei drehen<br />
kann und nicht vereist.<br />
Säntisträger brachten Brennholz<br />
und Essen zum Gipfel.<br />
Telefon und Morseapparat<br />
im Arbeitszimmer des Observatoriums.<br />
633<br />
262
Wettervorhersage im Fernsehen<br />
635<br />
Erste Wettervorhersage am 17. Dez. 1946<br />
im französischen Fernsehen<br />
637<br />
263
Wettervorhersage ab 1951 im deutschen TV<br />
Meteorologen erklären<br />
live mit Kohlestift und<br />
grossen Gesten dem<br />
abendlichen Fernsehpublikum<br />
das Wetter.<br />
638<br />
Wettervorhersage ab 1951 im deutschen TV<br />
639<br />
264
Animierte Wetterkarten ab 1960<br />
„Automatisches Wetter“<br />
mit Trickfilmtechnik<br />
(jeweils<br />
rund 35 Meter Film<br />
aus ca. 4000 Einzelbildern,<br />
produziert<br />
in drei Stunden) –<br />
Computergraphik<br />
und -animation oder<br />
Satelittenbilder gab<br />
es noch nicht. Die<br />
Karte zeigt Deutschland<br />
„dreigeteilt“,<br />
in den Grenzen von<br />
1937.<br />
641<br />
Ab dem Millennium wird die Wetterprognose<br />
zum Entertainment – mit Weather Girls & Boys<br />
Numerische Wettermodelle<br />
werden jetzt weitgehend<br />
automatisch mit<br />
aktuellen Daten gefüttert<br />
– die Prognosekunst<br />
ist so einfacher<br />
und die Wetterkarte<br />
sowieso computergeneriert.<br />
Fast wichtiger als<br />
die Vorhersage ist nun<br />
die Frage, von wem sie<br />
präsentiert wird (und<br />
welcher Sponsor die Wettermenschen<br />
kleidet).<br />
Wettervorhersage ist in<br />
der Schweiz auch keine<br />
Nachricht mehr, die auf<br />
Hochdeutsch präsentiert<br />
wird, sondern Infotainment,<br />
wo Mundart üblich<br />
ist.<br />
644<br />
265
Jeder kann jetzt das Wetter ansagen – somit<br />
sind andere Präsentationseigenschaften gefragt!<br />
Private TV-Programme<br />
ermöglicht durch Fortschritte<br />
in der Kommunikationstechnik<br />
645<br />
“Why is the weather so often presented by<br />
attractive women?”<br />
“Are weather presenters chosen primarily for their expert knowledge of meteorology or just<br />
for their looks? Is the weather forecasting game more about being good with figures or is<br />
it real about having a figure that looks hot in shorts? […] Northern countries tend towards<br />
more serious weather presenting while countries further south go for weather glamour. It<br />
also appears, the hotter the climate the less clothing weather girls seem to wear.<br />
BBC Serious Weather. In the UK, BBC weather presenters are highly professional<br />
meteorologists from the UK Met Office, a government sponsored organisation with links<br />
to the Ministry of Defence. […] While the BBC weather presenter Laura Tobin is pretty and<br />
personable, the emphasis is firmly on the weather and the camera<br />
view is of the weather animation not the presenter's body.<br />
Greek Weather. Mapia, the leggy weather presenter and weather<br />
girl […] frequently strides in front of the map, swinging her hips.<br />
Look at her body language. Her body is talking but not about the<br />
weather!<br />
Italy. Gabbi is clearly top of the class<br />
as far as attractiveness goes but her presenting skills need<br />
considerable improvement. Reading the weather from a<br />
clipboard is certainly not appealing and she clearly has no<br />
interest whatsoever in the chart behind her. How on earth<br />
did she get the job?”<br />
http://rikravado.hubpages.com/hub/Weather-Girls<br />
646<br />
266
Wetterfee in Klatschmedien und auf Facebook<br />
„Jetzt hat doch das SF tatsächlich<br />
eine neue Wetterfee – vermutlich als<br />
Ersatz für Neo-Mama Sandra Boner.<br />
Daniela Schmuki heisst sie. Ist nicht<br />
unsympathisch, die junge Dame,<br />
obwohl ihr Auftritt eher an eine<br />
Schülertheater-Inszenierung von<br />
Grimms ‚Vom Fischer und seiner<br />
Frau‘ erinnert … Susanne, du bist<br />
doch viel natürlicher, charmanter.<br />
Du lullst uns ein mit deiner weichen Stimme. Was ihr Wetterfeen erzählt, glauben<br />
wir eh nicht, aber angenehm rüberkommen soll’s wenigstens. Und in dich, Susanne,<br />
in dich habe wir doch schon so viel Zeit und Energie investiert. Richtig gross machen<br />
wollten wir dich. Haben eigens eine Facebook-Gr<strong>up</strong>pe lanciert…“<br />
www.klatschheftli.ch/tag/susanne-aeschlimann<br />
647<br />
Das Weather Girl als Traumjob<br />
“The weather<br />
girl position is a<br />
dream job for a lot<br />
of women. You get<br />
to look great, styled<br />
for the camera, and<br />
have fun makeovers<br />
every season. Even<br />
if it’s raining, her<br />
fans will love to<br />
see whatever new<br />
fashion she’s<br />
wearing!”<br />
651<br />
267
Reality Check: Weather Girl 1981<br />
652<br />
Zurück zum Ernst der Dinge:<br />
Simulationstechnik<br />
653<br />
268
Zeitgesteuerte („synchrone“) Simulation<br />
• Entwicklung eines Systems in der Zeit untersuchen<br />
• Modellzustand Schritt für Schritt („Zeitschritt“) fortschreiben<br />
• Kontinuierlicher Ablauf wird „diskretisiert“ zeitdiskrete Simulation<br />
(„diskret“: abgegrenzt, getrennt, vereinzelt; lat. discernere unterscheiden)<br />
• Zum simulierten Modell gehört auch eine Simulationsuhr,<br />
die die aktuelle Zeit des Modells („Simulationszeit“) angibt<br />
• Abbild der realen Zeit im Modell, aber „gerastert“ (d.h. diskretisiert)<br />
• Pro Simulationsschritt: Erhöhe Simulationsuhr um festes ∆t<br />
• ∆t zu klein → langsam<br />
Diskretisierungsproblem<br />
• ∆t zu gross → ungenau / falsch<br />
• Mögliche „Totzeiten“ → Verschwendung von Rechenzeit<br />
Das ist etwas anderes<br />
als die Simulationszeit!<br />
654<br />
[T, T+Δt]-Epochen<br />
• Was real in [T, T+∆t] geschieht, wird erst am Ende der<br />
Epoche, zum Zeitpunkt T+∆t, in der Simulation wirksam<br />
• Ungenauigkeiten / Fehler insbes. bei kontinuierlichen Abläufen<br />
• Zustandsänderung eines Objektes wirkt sich frühestens<br />
in der nächsten Epoche auf die anderen Objekte aus<br />
• Falls ∆t ausreichend klein: quasi-kontinuierliche Simulation<br />
(vgl. Film aus vielen Einzelbildern pro Sekunde)<br />
• Auf allen Objekten sollte die jew. Zustandsänderung der<br />
Epoche [T, T+∆t] unabhängig ausgeführt werden können<br />
• „Gleichzeitig“ in Simulationszeit, d.h. in beliebiger Reihenfolge bei<br />
der Simulation einer Epoche<br />
Denkübung: Wie hängen die Begriffe „unabhängig“,<br />
„gleichzeitig“, „beliebige Reihenfolge“ zusammen?<br />
655<br />
269
Ein Beispielproblem<br />
• Dorfbewohner bauen Weizen an<br />
• Ernte im Juli beträgt 2 000 000 kg<br />
• Wird in einer Scheune gelagert<br />
• Pro Monat werden 100 000 kg verbraucht<br />
• Mäuse in der Scheune fressen auch Weizen<br />
• Aber Katzen fressen Mäuse<br />
• Die Dorfbewohner sorgen (monatlich) dafür, dass<br />
der Katzenbestand nicht unter ein Minimum fällt<br />
Erntemenge sollte also<br />
eigentlich ausreichen…<br />
• Typische Frage: Wie gross ist die Weizenmenge nach<br />
n Jahren, wenn der minimale Katzenbestand k beträgt?<br />
→ Werden mehr Katzen benötigt?<br />
→ Kann man Anbauflächen stilllegen?<br />
McAdvisor&Co mit einem<br />
Gutachten beauftragen!<br />
656<br />
Abhängigkeiten in der Miniwelt<br />
• Komplexe kausale<br />
Abhängigkeiten<br />
• Rückkoppelungen,<br />
Regelkreise<br />
• Zeitliche Fernwirkungen<br />
→ „Regelkatastrophen“<br />
• Dennoch stark vereinfacht<br />
gegenüber der Realität<br />
• Bevölkerungswachstum<br />
• Anbautechnik<br />
• Ernteschwankungen<br />
• Marktpreise<br />
• Essgewohnheiten<br />
• Gentechnik<br />
• Biosprit<br />
• …<br />
657<br />
270
Abhängigkeiten in der Miniwelt<br />
• Hier ist die Semantik der<br />
Pfeile noch etwas „diffus“<br />
• Ist also keine vollständige<br />
Problemspezifikation!<br />
• In analytisch-mathematischer<br />
Weise lassen sich solche<br />
Probleme kaum oder gar<br />
nicht mehr behandeln<br />
• Simulation ist die geeignete<br />
Problemlösungstechnik<br />
658<br />
Das Modell<br />
Ein Unternehmensberater von<br />
McAdvisor&Co analysiert das<br />
Weizenproduktionssystem und<br />
führt in seinem management<br />
summary sieben key findings auf:<br />
a) Jede Maus frisst monatl. 15 kg Weizen.<br />
b) 1/12 aller Mäuse und 1/120 aller<br />
Katzen sterben jeden Monat an<br />
Altersschwäche.<br />
c) Wenn die Scheune leer ist, sterben<br />
alle Mäuse; allerdings wandern dann<br />
20 neue von aussen ein.<br />
d) Falls mehr als 100 kg Weizen pro<br />
Maus vorhanden ist, verdoppelt sich<br />
deren Anzahl monatlich. Ansonsten<br />
beträgt die Anzahl der monatlichen<br />
Geburten: Weizenmenge/100.<br />
e) …<br />
659<br />
271
Das Modell (2)<br />
d) …<br />
e) Falls mehr als 50 Mäuse/Katze<br />
existieren, frisst jede Katze 30 Mäuse<br />
pro Monat. Sonst werden monatlich<br />
30×Mäusezahl/50 gefressen.<br />
f) Bei < 25 Mäuse/Katze verhungern<br />
(25×Katzen - Mäuse) /50 Katzen.<br />
g) Falls mehr als 50 Mäuse/Katze existieren,<br />
produziert jede (weibliche!)<br />
Katze im März und September 6<br />
Junge. Bei weniger als 25<br />
Mäuse/Katzen werfen die Katzen<br />
nicht. Ansonsten werden<br />
3×(Mäuse/25 - Katzen) Junge<br />
geboren.<br />
660<br />
Zeitgesteuerte Simulation des<br />
Weizen-Mäuse-Katzen-Problems<br />
• Diskretes Zeitraster, d.h. die Simulationszeit „springt“<br />
• Wie gross sollte man ∆t wählen?<br />
• Ist ∆t = 1 Monat vernünftig?<br />
• Aber ist der Weizenverbrauch<br />
nicht nahezu kontinuierlich?<br />
• Bei jedem Zeitschritt ∆t<br />
ändern sich die Grössen<br />
• Weizenmenge (∆ weiz)<br />
• Mäusezahl (∆ maus)<br />
• Katzenzahl (∆ katz)<br />
T = T + ∆t<br />
• Verlauf der drei Kenngrössen als Funktionsgraph darstellen<br />
661<br />
272
Monatliche Änderung der Weizenmenge<br />
a) Jede Maus frisst 15 kg<br />
Weizen pro Monat.<br />
662<br />
Änderung der Mäusezahl<br />
Annahme: Nur<br />
ganzzahlige<br />
Werte (d.h.<br />
„integer“) für<br />
dmaus etc.<br />
b) 1/12 aller Mäuse … sterben jeden<br />
Monat an Altersschwäche.<br />
c) Wenn die Scheune leer ist,<br />
sterben alle Mäuse …<br />
d) Falls mehr als 100 kg Weizen<br />
pro Maus vorhanden ist,<br />
verdoppelt sich deren Anzahl<br />
monatlich. Ansonsten beträgt<br />
die Anzahl der monatlichen<br />
Geburten: Weizenmenge/100.<br />
e) Falls mehr als 50 Mäuse/Katze<br />
existieren, frisst jede Katze 30<br />
Mäuse pro Monat. Sonst werden<br />
monatlich 30×Mäusezahl/50<br />
gefressen.<br />
663<br />
273
Änderung der Katzenzahl<br />
f) Bei < 25 Mäuse/Katze verhungern<br />
(25×Katzen - Mäuse)/50 Katzen.<br />
b) … 1/120 aller Katzen sterben<br />
jeden Monat an Altersschwäche.<br />
Random[i:j] sei eine Zufallszahl zwischen i und j.<br />
Eine Interpretation von Modelleigenschaft b):<br />
Auch bei weniger als 120 Katzen stirbt gelegentlich eine davon<br />
„zufällig“ (aber warum eigentlich nicht mehr?). Dies ist<br />
umso wahrscheinlicher, je grösser die Zahl der Katzen ist.<br />
Oder sollte man das Modell „verstetigen“ und mit<br />
Bruchteilen von Katzen (und Mäusen) rechnen?<br />
664<br />
Änderung der Katzenzahl (2)<br />
g) Falls mehr als 50 Mäuse/Katze<br />
existieren, produziert jede<br />
(weibliche!) Katze im März und<br />
September 6 Junge. Bei weniger<br />
als 25 Mäuse/Katzen werfen<br />
die Katzen nicht. Ansonsten<br />
werden 3×(Mäuse/25 - Katzen)<br />
Junge geboren.<br />
Stellen die Flussdiagramme eine<br />
„perfekte“ Implementierung der<br />
Modellbeschreibung dar?<br />
Gibt es wesentlich andere Interpretationen<br />
der Spezifikation?<br />
Spiegelt das Modell die Realität<br />
ausreichend genau wider?<br />
665<br />
274
Das Steuerprogramm<br />
a) Wert von minkatzen eingeben (z.B. 10)<br />
b) Initialisieren:<br />
• weizen = 0; katzen = minkatzen; mäuse = 20; jahr = 1; monat = 6;<br />
c) In einer Schleife (bis z.B. jahr==10):<br />
• monat++ ; falls monat==13: {monat=1; jahr++}<br />
• Methoden („Unterprogramme“) aufrufen zur Bestimmung von:<br />
• dweiz<br />
• dmaus<br />
• dkatz<br />
• weizen = max(0, weizen+dweiz)<br />
• mäuse = max(20, mäuse+dmaus)<br />
• katzen = max(minkatzen, katzen+dkatz)<br />
wieso?<br />
Ist hier eigentlich die Reihenfolge<br />
der Neuberechnung der drei<br />
Kenngrössen entscheidend?<br />
Könnte weizen+dweiz < 0 sein?<br />
Was bedeutet das?<br />
• Ergebnis ausgeben (z.B. Kurvenverlauf oder Balkendiagramme)<br />
• Versuch evtl. wiederholen mit anderem minkatzen-Wert<br />
Sensitivitätsanalyse: Reagiert das Modell empfindlich auf kleine Änderungen?<br />
666<br />
Simulationsergebnis<br />
• Jedes dritte Jahr eine<br />
Hungersnot<br />
• Exponentielle Mäusevermehrung<br />
im Vorjahr<br />
• Erst kein Weizen, dann keine<br />
Mäuse, dann keine Katzen<br />
• Einfluss der Katzen (bei minkatzen=10)<br />
auf die Mäuseplage<br />
ist nicht ausreichend<br />
• Ohne Katzen wäre aber alle<br />
2 Jahre eine Hungersnot<br />
• Wie kann man Hungersnöte<br />
ganz vermeiden ?<br />
667<br />
275
Die Grenzen des Wachstums<br />
Historische Notiz<br />
Standardlauf des Weltmodells<br />
von J.W. Forrester und<br />
D. Meadows (Die Grenzen<br />
des Wachstums, 1972)<br />
668<br />
Weltmodell von Forrester/Meadows (Ausschnitt)<br />
Quelle: Meadows,<br />
Abb. 26, pp. 88-91<br />
und François Cellier<br />
670<br />
276
671<br />
Ölkrise 1973<br />
• Schon ein Jahr später bekam man einen Vorgeschmack…<br />
– die Ölverknappung war allerdings nur politisch motiviert,<br />
und schon bald floss das Öl wieder in ausreichendem Masse<br />
277
Deutschland<br />
USA<br />
278
„Mit ‚Die Grenzen des Wachstums‘ legte der Club of Rome 1972 die heilige Schrift aller<br />
Zukunftsskeptiker vor. Die Computersimulation in Buchform war der erste Öko-Megaseller<br />
überha<strong>up</strong>t und prägte das gesellschaftliche Klima der Siebziger wie kein anderes Werk. …<br />
Eine Publikation mit 160 Seiten und 48 Kurvendiagrammen, die zum erfolgreichsten und<br />
einflussreichsten Umwelt-Buch nach der biblischen Schöpfungsgeschichte wurde. … Die<br />
Hochrechnungen des MIT reichen von 1900 bis 2100 und untersuchen das Auf und Ab von<br />
fünf Werten: Erdbevölkerung, Industrieproduktion, Nahrungsversorgung, Rohstoffvorräte<br />
und Umweltverschmutzung. Aus vielen Einzelstudien schält sich eine Erkenntnis heraus:<br />
Nur wenn Pro-Kopf-Produktion und Bevölkerungszahl konstant bleiben und der Ressourcenverbrauch<br />
auf ein Viertel schrumpft, kann die Menschheit<br />
der Katastrophe entgehen. In allen anderen Fällen drohen im 21.<br />
Jahrhundert globale Hungersnöte und der Zusammenbruch der<br />
Industriekapazität. … Am 2. März 1972 erscheint in den USA<br />
‚The Limits to Growth‘, die 160 Seiten starke Vorabversion des<br />
Projektberichts. Die deutsche Fassung ‚Die Grenzen des Wachstums‘,<br />
die zwei Monate nach der amerikanischen herauskommt,<br />
wird ... ein Bestseller. Die Botschaft vom ‚Nullwachstum‘ verbreitet<br />
sich rasant, und im Oktober 1973 erhält der Club of Rome<br />
den Friedenspreis des deutschen Buchhandels. … Weltweit wird<br />
‚The Limits to Growth‘ in 37 Sprachen übersetzt und mehr als 12<br />
Millionen mal verkauft. Zum einen trifft das Werk den Zeitgeist:<br />
... im Juni 1972 tagte in Stockholm die erste Umweltkonferenz<br />
der Vereinten Nationen. Zum anderen verknüpfte es den Mythos<br />
vom allwissenden Computer mit der Aura des Zirkels weiser<br />
Männer, als den man den Club of Rome wahrnimmt.“<br />
(Ralf Bülow: Apokalypse aus dem Computer, einestages.spiegel.de)<br />
675<br />
Die Grenzen des Wachstums<br />
„Warten wir nicht seit 30 Jahren, bequem in den Fernsehsessel gelümmelt, auf die<br />
Katastrophe? Seit 1972 sagen der Wissenschaftler Dennis Meadows und seine<br />
Kollegen Schlimmstes vorher. Dabei ist das Bier hierzulande immer noch schön kühl,<br />
und die Erde dreht sich…<br />
Damals stürmte die These vom nahen, selbstverschuldeten<br />
Untergang der Menschheit die Bestsellerlisten… Zum ersten<br />
Mal hatten Autoren den Mut und die argumentative Stärke,<br />
um mit Vehemenz auf die Endlichkeit der Erde aufmerksam<br />
zu machen. Und das zeigte glücklicherweise Wirkung: Sie<br />
verstärkten die Aufbruchsstimmung der 1980er Jahre, in<br />
denen sich die Grünen gründeten. Sie bereiteten den Boden<br />
für den internationalen Umweltschutz. Und sie brachten die<br />
Ökonomen dazu, über Wirtschaften ohne Wachstum<br />
nachzusinnen.“<br />
Petra Pinzler in „Die Zeit“ Nr. 19 vom 30. April 2008<br />
676<br />
279
Eine weitere Simulationsanwendung<br />
Systemspezifikation der telefonischen Ticketreservierung<br />
eines Reisebüro-Call-Centers:<br />
677<br />
Eine weitere Simulationsanwendung<br />
Systemspezifikation der telefonischen Ticketreservierung<br />
eines Reisebüro-Call-Centers:<br />
678<br />
280
Eine weitere Simulationsanwendung<br />
Systemspezifikation der telefonischen Ticketreservierung<br />
eines Reisebüro-Call-Centers:<br />
1) 5 Angestellte nehmen Buchungen entgegen<br />
2) 18 Telefonleitungen (d.h. max. 13 Anrufer warten)<br />
3) „Bitte warten“, wenn alle Angestellten belegt<br />
4) Angest. wird frei → am längsten wartenden Anrufer bedienen<br />
5) Wartebereitschaft normalverteilt mit Mittel 4 Minuten<br />
6) Endgültiger Verzicht eines Kunden, wenn keine Leitung frei<br />
oder wenn Wartebereitschaft überschritten<br />
7) Zwischenankunftszeiten exponentialverteilt (20 Sek.)<br />
8) Bedienzeit exponentialverteilt<br />
(mit Mittel 1 Min. bei „one way“, 2 Min. bei „round trip“)<br />
9) Wahrscheinlichkeit für round trip = 0.75<br />
679<br />
Systemspezifikation<br />
Warteschlange der Anrufer<br />
18 Telefonleitungen<br />
Anruf<br />
(alle 20s<br />
im Mittel)<br />
Wartegeduld<br />
begrenzt<br />
(4 Min im Mittel)<br />
5 Angestellte<br />
(Bedienzeit pro Kunde im<br />
Mittel 1 Min oder 2 Min)<br />
680<br />
281
Typische Fragestellungen für<br />
Simulationsexperimente<br />
1) Mehr Angestellte → Kosten / Nutzen?<br />
2) Ausfall von Angestellten → Konsequenzen?<br />
3) Wie wirkt sich eine Verkürzung der Bedienzeiten<br />
von 60 Sek. auf 55 Sek. aus?<br />
4) Musik in der Warteschleife lässt Kunden 25% länger<br />
warten; Konsequenzen für die Systemleistung?<br />
5) Bei einer 50%igen Steigerung der Anrufrate:<br />
Wie wirkt sich das auf Auslastung, Anzahl bedienter<br />
und Anzahl verärgerter Kunden aus?<br />
681<br />
Simulationsexperiment zur<br />
Analyse der Systemleistung<br />
• Zu bestimmende Leistungsparameter:<br />
• Mittlere Wartezeit der Anrufer? ( ca. 70 Sekunden)<br />
• Auslastung der Angestellten?<br />
( ca. 91%)<br />
• Auslastung der Leitungen?<br />
( ca. 45%)<br />
• Anteil sofort bedienter Anrufe?<br />
( 15800 = 88%)<br />
• Anzahl „verzichtender“ Kunden?<br />
( 2160)<br />
• Anteil der „besetzten“ Anrufe?<br />
( 161 von ca. 18000)<br />
Ergebnis nach 100 Stunden Simulationszeit<br />
(= simulierter Zeit)<br />
682<br />
282
Call-Center zeitgesteuert simulieren?<br />
• Prinzip:<br />
• Simulationsuhr pro Simulationsschritt um ∆t erhöhen<br />
• Alle „realen“ Zustandsänderungen im Intervall<br />
[T, T+∆t] zum simulierten Zeitpunkt T+∆t nachbilden<br />
• Bei jedem einzelnen Schritt prüfen und (mit Simulationszeitpunkt)<br />
für die Auswertung festhalten:<br />
• Sind „zwischenzeitlich“ Anrufe gekommen?<br />
• Sind Bediener freigeworden?<br />
• …<br />
683<br />
Wahl von Δt?<br />
• ∆t = 1 μs (Simulationszeit, nicht Rechenzeit!)<br />
→ Zustandsänderung ist sehr unwahrscheinlich<br />
→ Viele nutzlose Schritte<br />
→ 100 Stunden → 360 000 000 000 Schritte<br />
• ∆t = 1000 s<br />
→ Warteschlange hat „plötzlich“ ca. 50 Kunden!<br />
→ Alle Angestellten sind am Anfang lange unbeschäftigt<br />
→ Fehlerhafte Statistiken!<br />
• ∆t = 1 s<br />
→ Nur etwa in jedem zehnten Schritt geschieht etwas<br />
→ Wie „genau“ und gut ist dieser Kompromiss?<br />
Ereignisgetrieben geht dies<br />
genauer und effizienter!<br />
Überspringt automatisch Zeitintervalle,<br />
in denen nichts Relevantes geschieht.<br />
684<br />
283
Ereignisgesteuerte Simulation<br />
• Grundannahme: Zustand bleibt abschnittsweise<br />
konstant; es passiert nichts zwischen zwei Ereignissen<br />
• Zeit springt von Ereignis zu Ereignis<br />
• Nur Ereignisse ändern den Zustand<br />
• Typische Ereignisse<br />
bei Simulationen:<br />
„event<br />
• Anruf eines Kunden driven“<br />
• Betreten eines Aufzuges<br />
• Anstellen an die Warteschlange<br />
• Bearbeitungsende eines Werkstücks<br />
Der Fortschritt der Simulation wird<br />
also nicht durch Ändern einer Simulationsuhr<br />
getrieben; statt dessen<br />
wird umgekehrt die Simulationsuhr<br />
und die Welt von den stattfindenden<br />
Ereignisse vorangetrieben<br />
Vgl. sehr schnelles „Vorspulen“<br />
eines Videos bis zum nächsten<br />
Ereignis (z.B. Fussballtor)<br />
• Ereignis:<br />
• Hat einen Eintrittszeitpunkt<br />
• Bewirkt beim „Eintreten“ eine schlagartige Zustandsänderung<br />
685<br />
Was treibt die Welt voran?<br />
Raffael stellt in einem Eckfresko<br />
der Gewölbedecke<br />
der Stanza della Segnatura<br />
(Apostolischer Palast, Vatikan,<br />
Rom) einen „unbewegten<br />
Beweger“ (primus motor)<br />
dar, der nach der Metaphysik<br />
von Aristoteles die<br />
Welt antreibt.<br />
686<br />
284
Modellentwurf Reisebüro<br />
• Zustand des Modells („als Schnappschuss“):<br />
• Status (frei / beschäftigt) jedes Angestellten<br />
• Anzahl der freien Leitungen<br />
• Liste derzeit wartender Anrufer<br />
• Ereignisbedingte Zustandsübergänge<br />
• Aus Sicht eines individuellen Kunden:<br />
Oder genügt<br />
hier die Zahl der<br />
freien Angestellten?<br />
Ereignistypen:<br />
- Beginn Bedienung / Beginn Warten<br />
(nach Anruf)<br />
- Ende Warten<br />
(freie Bediener / abgelaufene Geduld)<br />
- Ende Bedienung<br />
690<br />
Der ereignisgesteuerte Simulationsablauf<br />
Der Initialzustand<br />
Ein erster „vorhergesehener“<br />
Kundenanruf<br />
Zeit springt, getrieben<br />
durch das nächste Ereignis<br />
„Ereignisliste“:<br />
Menge der z.Z.<br />
vorgemerkten<br />
Ereignisse in Form<br />
von sogenannten<br />
„Ereignisnotizen“<br />
Bedienungsende-Ereignis wird<br />
bereits bei Bedienstart vorgemerkt!<br />
691<br />
285
Der ereignisgesteuerte Simulationsablauf (2)<br />
Modellierung: Jeder<br />
erfolgte Anruf plant<br />
bereits den nächsten<br />
Kundenanruf ein <br />
Es gibt immer mind.<br />
ein vorgemerktes<br />
Anruf-Ereignis!<br />
Etc., bis zu folgendem Zustand:<br />
692<br />
Der ereignisgesteuerte Simulationsablauf (3)<br />
Wartende Kunden<br />
Warteschlange wird<br />
durch das „Eintreten“<br />
eines Anrufes erzeugt,<br />
falls der Kunde nicht<br />
sofort bedient wird,<br />
sondern warten muss<br />
693<br />
286
Der ereignisgesteuerte Simulationsablauf (4)<br />
Warteschlange<br />
(verkettete Liste)<br />
Frühester Kunde (44)<br />
kommt als nächster dran<br />
Kunde 46 hat<br />
aufgegeben<br />
694<br />
Ereignisroutine<br />
„Kundenanruf“<br />
Ereignisroutinen werden bei<br />
„Eintreten“ von Ereignissen<br />
des entsprechenden Typs<br />
ausgeführt<br />
Mit diesem „Trick“ wird<br />
das System am Leben<br />
gehalten: Immer wenn<br />
ein Kunde anruft, wird<br />
schon der nächste<br />
Anrufwunsch generiert<br />
Hier werden die relevanten<br />
Zustandsänderungen am<br />
Modell vorgenommen<br />
Mehr oder weniger viel<br />
Simulationszeit des Kunden<br />
und des Bedieners<br />
verbrauchen (d.h. „Bedienungsende-Ereignis“<br />
für einen späteren Zeitpunkt<br />
vormerken)<br />
695<br />
287
Ereignisroutine<br />
„Bedienungsende“<br />
Freigabe der<br />
Betriebsmittel<br />
696<br />
Ereignisroutine „Geduld-Ende“<br />
697<br />
288
Methode „Bedienung“<br />
Keine eigene Ereignisroutine;<br />
die Methode wird in den Ereignisroutinen<br />
„Kundenanruf“ und<br />
„Bedienungsende“ aufgerufen<br />
„Cancel“-Operation<br />
(vgl. Abstellen des<br />
Weckers, weil man<br />
schon vorzeitig<br />
aufgewacht ist)<br />
698<br />
Statistik und Animation<br />
• Evtl. weitere Aktionen in den Ereignisroutinen<br />
durchführen, um Statistikdaten sammeln<br />
• Evtl. auch „Animation“ durch Ausgabeanweisungen<br />
in den Ereignisroutinen, z.B.:<br />
• 08:17, Kunde 29 ruft an<br />
• 08:19, Kunde 26 wird bedient<br />
• 08:20, Bediener 3 beendet Bedienung von Kunde 24<br />
• 08:27, Kunde 28 gibt nach 5 Minuten entnervt auf<br />
Beachte, dass in einem so erzeugten „Ereignisprotokoll“<br />
die Zeit monoton steigend ist!<br />
699<br />
289
Der Simulatorzyklus<br />
Mindestens eine erste<br />
Ereignisnotiz in die<br />
Ereignisliste einfügen<br />
Oder: UHR < Endezeitpunkt?<br />
Prinzip:<br />
• Führe die Aktion des<br />
jeweils (chronologisch)<br />
nächsten eingeplanten<br />
Ereignisses aus<br />
• Dabei können neue<br />
Ereignisse „entstehen“<br />
Dies kann u.U. eine umfangreiche Manipulation<br />
von Datenstrukturen bedeuten!<br />
Evtl. neu einz<strong>up</strong>lanende<br />
Ereignisse in die<br />
Ereignisliste einfügen!<br />
700<br />
Ereignisgesteuerter Simulator<br />
Simulator enthält ausserdem noch<br />
Programmcode (Ereignisroutinen,<br />
Steuerung des Simulatorzyklus,<br />
Ausgabestatistik...)<br />
Ereignisnotizen in der Ereignisliste<br />
enthalten einen Eintrittszeitpunkt<br />
Simulationszeit („Uhr“) springt zum jew. nächsten Ereignis<br />
• Ausführung der zugehörigen Ereignisroutine, dabei:<br />
• Änderung des (globalen) Zustands<br />
• Evtl. Einplanen neuer Ereignisse (in der „Zukunft“)<br />
Simulatorzyklus<br />
701<br />
290
Quasi-Parallelismus<br />
• In der Realität gleichzeitig ablaufende Aktivitäten<br />
werden in der Simulation „stückweise“ sequentialisiert<br />
• Durch die Auflösung in Ereignisse (am Anfang und Ende<br />
von Teilaktivitäten) werden Aktivitäten zeitlich verzahnt<br />
• Beispiel: Zwischen Ankunftsereignis und Abgangsereignis<br />
eines S<strong>up</strong>ermarktkunden (also während<br />
dieser mit Einkaufen als „Aktivität“ beschäftigt ist)<br />
betreten weitere Kunden den S<strong>up</strong>ermarkt<br />
702<br />
Quasi-Parallelismus: Beispiel<br />
Kunde 1<br />
Eintritt<br />
S<strong>up</strong>ermarkt<br />
Anstellen<br />
Käsetheke<br />
Verlassen<br />
Käsetheke<br />
Anstellen<br />
Kasse<br />
Verlassen<br />
S<strong>up</strong>ermarkt<br />
Zeit in der<br />
Simulation<br />
Kunde 2<br />
Eintritt<br />
S<strong>up</strong>ermarkt<br />
Anstellen<br />
Kasse<br />
Verlassen<br />
S<strong>up</strong>ermarkt<br />
Kunde 3<br />
Eintritt Anstellen<br />
S<strong>up</strong>ermarkt Käsetheke<br />
Verlassen<br />
Käsetheke<br />
Anstellen<br />
Kasse<br />
Verlassen<br />
S<strong>up</strong>ermarkt<br />
Dauer der Simulation hängt vom<br />
Zeitbedarf der Ereignisroutinen ab<br />
Ausführungszeit („cpu“)<br />
der Ereignisroutinen<br />
des Simulators<br />
Ende der Simulation<br />
703<br />
291
Modellierung paralleler Aktivitäten<br />
• Die Modellierungskunst besteht darin, die Aktivitäten<br />
der Realität so in Ereignisse aufzulösen, dass<br />
• die Wechselwirkung zwischen den Aktivitäten auf die Ereignisse<br />
beschränkt bleiben;<br />
• die Ereignisse sich korrekt gegenseitig einplanen;<br />
• die zugehörigen Ereignisroutinen die Zustandsänderung des<br />
Modells korrekt wiedergeben;<br />
• das Gesamtverhalten die Realität adäquat widerspiegelt.<br />
Gibt es Ereignisse mit exakt gleichem Eintrittszeitpunkt? Soll es sie geben?<br />
(Wenn ja, dann sollte die Reihenfolge ihrer Bearbeitung keine Rolle spielen.)<br />
704<br />
Beispiele für das Einplanen von Ereignissen<br />
• Abfahren eines Autos an einer Kreuzung bewirkt<br />
Einplanung des Ankunftsereignisses zu einem<br />
späteren Zeitpunkt bei einer anderen Kreuzung<br />
• Ein Kassierer „würfelt“ das Bedienungsende des Kunden<br />
aus, sobald er einen Kunden anfängt zu bedienen<br />
• Ein beim S<strong>up</strong>ermarkt eintreffender Kunde „würfelt“ den<br />
Ankunftszeitpunkt des nächsten Kunden aus<br />
Das ist ein sehr effektiver Trick: Auf diese<br />
Weise muss man nicht initial alle jemals<br />
eintreffenden Kunden als vorgemerkte<br />
Ereignisse in die Ereignisliste einfügen;<br />
es ist statt dessen immer nur der jeweils<br />
nächste eintreffende Kunde vorgemerkt!<br />
⇒ „Scheinkausalität“:<br />
In Wirklichkeit ist das Eintreffen<br />
eines Kunden nicht<br />
die Ursache für das Eintreffen<br />
eines weiteren Kunden!<br />
705<br />
292
Ereignisverwaltung<br />
Hier dargestellt als<br />
verkettete Liste,<br />
die nach Eintrittszeitpunkt<br />
sortiert ist<br />
Ereignisnotizen benennen<br />
den Eintrittszeitpunkt und<br />
die Ereignisroutine (d.h.<br />
den Ereignistyp) eines eingeplanten<br />
(d.h. noch auszuführenden)<br />
Ereignisses<br />
Die Ereignisliste speichert<br />
alle Ereignisnotizen und<br />
liefert auf Anforderung<br />
die früheste<br />
Ereignisroutinen enthalten Anweisungen zur Änderung des<br />
Modellzustands (und evtl. zum Einplanen weiterer Ereignisse,<br />
d.h. Einfügen neuer Ereignisnotizen in die Ereignisliste)<br />
706<br />
Ereignisliste als abstrakter Datentyp<br />
• Die Ereignisliste muss man nicht<br />
als verkettete Liste realisieren:<br />
Definiert durch die Wirkung<br />
der Operationen, nicht durch<br />
eine Implementierung<br />
• Insert fügt eine Ereignisnotiz in<br />
einen „abstrakten Behälter“ ein<br />
• Get_min soll die Ereignisnotiz mit<br />
kleinstem Zeitstempel der im Behälter<br />
z.Z. gespeicherten zurückliefern<br />
und diese aus dem Behälter<br />
entfernen (Exception falls Behälter leer)<br />
Eine Datenstruktur mit<br />
diesen beiden Operationen<br />
heisst „priority queue“<br />
• Beispiel: Die Folge von Operationen<br />
insert(5); insert(2); insert(8); get_min; insert(4); insert(7); get_min;<br />
liefert: 2, 4<br />
707<br />
293
Auch ein abstrakter Behälter…<br />
„Der Kunde bedient hierbei einen zentralen<br />
Abgabe- und Abholautomaten. Nach Entrichten<br />
des Mietbetrags am Automaten wird<br />
durch ein automatisches Fördersystem ein<br />
leerer Behälter zur Aufnahme des Gepäcks<br />
bereitgestellt, in den der Reisende sein Gepäck<br />
einstellt. Nachdem dies erfolgt ist,<br />
fährt der Behälter ebenfalls automatisch in<br />
ein zentrales automatisiertes Lager, wo er<br />
für die Dauer der Aufbewahrung verbleibt.<br />
Für den Kunden ist dieser Vorgang nicht zu<br />
bemerken, was häufig zu Irritationen und<br />
Missverständnissen führt.“ de.wikipedia.org<br />
708<br />
Implementierung von priority queues<br />
1. Implementierung: Sortierte verkettete Liste<br />
• → insert benötigt O(n) Schritte<br />
(richtige Stelle finden und dort einfügen)<br />
• → get_min benötigt O(1) Schritte<br />
(vorderstes Element zurückliefern)<br />
Für ein „zufälliges“<br />
Element (bei<br />
Gleichverteilung)<br />
n = Anzahl der Elemente<br />
in der Liste<br />
709<br />
294
Implementierung von priority queues<br />
2. Implementierung: Unsortierte verkettete Liste<br />
• → insert benötigt O(1) Schritte<br />
(z.B. vorne anfügen)<br />
• → get_min benötigt O(n) Schritte<br />
(kleinstes Element suchen und ausketten)<br />
3. Implementierung: Heap-Datenstruktur<br />
„partiell“<br />
sortiert<br />
• → insert und get_min benötigen beide nur O(log n) Schritte<br />
!<br />
710<br />
15.<br />
Heaps<br />
Buch Mark Weiss „Data Structures & Problem Solving Using Java“ siehe:<br />
- 833-852 (Heap, Heapsort)<br />
711<br />
295
Die Heap-Datenstruktur<br />
• Heap = Binärbaum mit<br />
• Alle Niveaus (bis evtl. auf das letzte) sind vollständig besetzt<br />
• Für alle Knoten k ≠ Wurzel: Wert(Vorgänger(k)) < Wert(k)<br />
(alternative Def. mit ≤, >, ≥ auch möglich)<br />
Min-Heap<br />
• Zwei Operationen: get_min und insert<br />
712<br />
Heaps vs. Suchbäume<br />
Ich<br />
< ><br />
kleiner<br />
als ich<br />
Suchbaum<br />
grösser<br />
als ich<br />
kleiner<br />
als ich<br />
Ich<br />
< <<br />
Max-Heap<br />
auch<br />
kleiner<br />
als ich<br />
grösser<br />
als ich<br />
Ich<br />
> ><br />
Min-Heap<br />
auch<br />
grösser<br />
als ich<br />
713<br />
296
Heap: get_min und insert<br />
Man überlege sich<br />
genau, dass das<br />
der Fall ist!<br />
6<br />
16<br />
12<br />
Für get_min und insert gilt:<br />
- Lässt Heap-Eigenschaft invariant<br />
-Benötigt O(log n) Schritte<br />
19<br />
get_min:<br />
• Wurzel entfernen<br />
• Letzten Knoten des untersten Niveaus<br />
an die Wurzelposition setzen<br />
• Neue Wurzel so weit wie möglich nach<br />
unten sinken lassen: Mit kleinerem der<br />
beiden Nachfolger vertauschen; dies<br />
rekursiv (oder iterativ) auf entsprechenden<br />
Unterbaum anwenden<br />
insert:<br />
Im Unterschied zur Implementierung<br />
von priority<br />
queues als sortierte / unsortierte<br />
verkettete Liste!<br />
• Als neues Blatt auf unterstem<br />
Niveau einfügen<br />
• Soweit wie möglich nach oben<br />
wandern lassen: Iterativ mit<br />
Vorgänger vertauschen, wenn<br />
dieser grösser<br />
714<br />
Heaps niveauweise als Array<br />
• Wurzel hat Array-Index 1<br />
• Direkte Nachfolger eines Knotens mit Index i haben<br />
die Indizes 2i und 2i+1 (sofern vorhanden)<br />
Beispiel für einen Heap, der so<br />
„niveauweise“ gespeichert ist<br />
Niveau 1 2 3 4 …<br />
• Dadurch ist Aufsteigen / Absteigen entlang eines<br />
Pfades bei get_min / insert besonders einfach!<br />
• Halbieren / Verdoppeln von Indizes<br />
715<br />
297
Heap: Implementierung von „insert“<br />
Als neues Blatt auf<br />
unterstem Niveau<br />
einfügen.<br />
Soweit wie möglich<br />
nach oben wandern<br />
lassen: Iterativ mit<br />
Vorgänger vertauschen,<br />
wenn dieser<br />
grösser.<br />
void insert (int x) {<br />
int k = ++N;<br />
a[0] = -1;<br />
while (a[k/2] >= x) {<br />
a[k] = a[k/2];<br />
k = k/2;<br />
}<br />
a[k] = x;<br />
}<br />
N bezeichnet die Anzahl der<br />
gespeicherten Elemente<br />
Ein „Trick“, damit die Schleife<br />
immer verlassen wird<br />
Wird evtl. abgerundet<br />
Pfad hochwandern…<br />
• Trick a[0]=-1 klappt nur, wenn keine negativen Werte eingefügt werden!<br />
• Beachte: Bei Ersetzen von k/2 durch k-1 erhält man insertion-sort<br />
⇒ Interpretation: Beim Heap wird insertion-sort entlang eines einzigen Pfades<br />
angewandt; dieser hat nur eine Länge von O(log n)<br />
Denkübung: Was geschieht, wenn man ein schon gespeichertes Element noch mal eingefügt?<br />
716<br />
Heap: „get_min“<br />
int get_min() {<br />
int k = 1; int j; int x = a[1];<br />
}<br />
a[1] = a[N--];<br />
int w = a[1];<br />
while (k
Weitere Anwendungen von Heaps<br />
• Betriebssysteme<br />
• Job-Scheduling: Job mit höchster Priorität zuerst<br />
• Suchverfahren<br />
• Aussichtsreichstes Teilproblem zuerst bearbeiten<br />
• Spielbaumanalyse<br />
• Besten Zug zuerst analysieren<br />
• Codierungstheorie / Dateikomprimierung<br />
• Kurze Codewörter für häufigste Zeichen („Huffman-Code“)<br />
718<br />
Heapsort<br />
• Sortieren mit einem Heap:<br />
1) n Elemente (der unsortierten Folge) einzeln einfügen<br />
2) danach n mal get_min auf den Heap anwenden<br />
⇒ Sortierverfahren mit O(n log n) Worst-Case-Zeitkomplexität!<br />
• Man kann den Heap im Array selbst („in place“) aufbauen, indem der<br />
Heap von links heranwächst, während nacheinander Elemente des<br />
unsortierten Teils entfernt werden:<br />
Phase 1<br />
Heap aus bereits bearbeiteten Elementen<br />
Unsortierte Resteingabe<br />
• Anschliessend wird der Heap schrittweise abgebaut und das jeweils<br />
entfernte Element an eine im freigeräumten Bereich heranwachsende<br />
sortierte Folge angefügt:<br />
Phase 2<br />
Rest-Heap (mit get_min abgebaut)<br />
Bereits sortierter Teil<br />
721<br />
299
Animation ►<br />
722<br />
Animation<br />
http://de.wikipedia.org/wiki/Image:Sorting_heapsort_anim.gif<br />
723<br />
300
Heapsort-Beispiel – Phase 1<br />
1 2<br />
Ausgangszustand: unsortiertes Array<br />
Nach Phase 1: der aufgebaute Heap<br />
(hier: grösstes Element an der Wurzel links)<br />
724<br />
Heapsort-Beispiel – Phase 1<br />
1 2<br />
Ausgangszustand: unsortiertes Array<br />
Nach Phase 1: der aufgebaute Heap<br />
(hier: grösstes Element an der Wurzel links)<br />
725<br />
301
Heapsort-Beispiel – Phase 2<br />
Neue Wurzel so<br />
weit wie möglich<br />
nach unten<br />
sinken lassen<br />
Letzten Knoten des<br />
untersten Niveaus<br />
an die<br />
Wurzelposition<br />
setzen<br />
3 4<br />
bereits<br />
sortierter<br />
Bereich<br />
Schnappschuss: Abbau des Heaps<br />
durch wiederholtes Entfernen der<br />
Wurzel (mit „Heap-Reparatur“) und<br />
Anfügen an den sortierten Bereich<br />
Das Endergebnis<br />
726<br />
Animation ►<br />
727<br />
302
Animation<br />
http://de.wikipedia.org/wiki/Image:Sorting_heapsort_anim.gif<br />
728<br />
Heapsort im Jahre 1974<br />
(Aus: Creative Computing Magazine)<br />
Historische Notiz<br />
Heapsort wurde<br />
1964 entdeckt<br />
Die folgenden vier<br />
slides stellen lediglich<br />
eine „historische<br />
Kuriosität“ dar.<br />
Interessierte mögen<br />
aber Spass am „reverse<br />
engineering“<br />
des Programms haben<br />
und nebenbei<br />
einen Eindruck der<br />
seinerzeit populären<br />
Programmiersprache<br />
„Basic“ bekommen.<br />
729<br />
303
010 REM. KNUTH/WILLIAMS/FLOYD HEAPSORT ALGORITHM.<br />
020 ! PAS '74<br />
030 DIM N(150),C$(150)<br />
040 PRINT<br />
050 PRINT<br />
060 PRINT "TYPE C FOR CHARACTER STRING SORT,"<br />
070 PRINT "TYPE N FOR NUMBER SORT.";<br />
080 INPUT W$<br />
090 N=0 ! START COUNT=N AT 0<br />
100 PRINT<br />
110 IF W$="N" THEN 480<br />
120 IF W$ "C" THEN 60 ! BAD REPLY<br />
130 REM ********* CHARACTER STRING SORT ROUTINE: *********<br />
140 GOSUB 770 ! ASK FOR STOP CODE<br />
150 INPUT S$ ! GET STOP CODE<br />
160 PRINT<br />
170 N=N+1 ! INPUT LOOP:<br />
180 INPUT C$(N)<br />
190 IF C$(N)S$ THEN 170<br />
200 N=N-1 ! END OF INPUT...<br />
210 PRINT<br />
220 L=INT(N/2)+1 ! HEAPSORT PROPER:<br />
230 N1=N ! PRESERVE N, USE N1<br />
240 IF L=1 THEN 280<br />
250 L=L-1<br />
260 A$=C$(L)<br />
270 GOTO 320<br />
730<br />
280 A$=C$(N1)<br />
290 C$(N1)=C$(1) ! MOVE TOP OF HEAP TO END<br />
300 N1=N1-1 ! HEAP IS ONE SMALLER NOW<br />
310 IF N1=1 THEN 430 ! ONLY ONE LEFT? THEN WE'RE DONE.<br />
320 J=L ! NO, CONTINUE<br />
330 I=J<br />
340 J=2*J ! LOOK FOR "SONS" OF I<br />
350 IF J=N1 THEN 390<br />
360 IF J>N1 THEN 420 ! "N1" IS SIZE OF ACTIVE LIST<br />
370 IF C$(J)>=C$(J+1) THEN 390 ! CHOOSE LARGER "SON"<br />
380 J=J+1<br />
390 IF A$>=C$(J) THEN 420<br />
400 C$(I)=C$(J)<br />
410 GOTO 330 ! LARGER SON REPLACES PARENT<br />
420 C$(I)=A$<br />
425 GOTO 240 ! END OF SORT...<br />
430 C$(1)=A$<br />
440 FOR I=1 TO N<br />
450 PRINT C$(I) ! OR REVERSE ORDER: I=N TO 1 STEP -1<br />
455 NEXT I<br />
460 GOTO 60<br />
unwesentliche Teile hier weggelassen<br />
760 REM **********SUBROUTINE TO INPUT STOP CODE**********<br />
770 PRINT "PLEASE INDICATE A STOP CODE--SOM<strong>ETH</strong>ING NOT IN YOUR"<br />
780 PRINT "LIST, WHICH WILL ACT AS AN 'END OF LIST' SIGNAL: ";<br />
790 RETURN<br />
800 END<br />
731<br />
304
Hobby-Programmierer und Home-Computer-<br />
Nutzer der 1970er Jahre<br />
4 kB RAM;<br />
8-Bit cpu;<br />
1,77 MHz<br />
Stolze Mutter mit<br />
Kunsthaar-Perücke<br />
Regular coffee<br />
Kitchen TV (b&w)<br />
1249 BYTES FREE<br />
READY.<br />
? LOAD "HEAPSORT"<br />
PRESS PLAY ON TAPE<br />
SEARCHING<br />
FOUND HEAPSORT<br />
? LOAD<br />
READY.<br />
? LIST 280<br />
280 A$=C$(N1)<br />
290 C$(N1)=C$(1) Keine Kleinbuchstaben<br />
300 N1=N1-1<br />
310 IF N1=1 THEN 430<br />
320 J=L<br />
330 I=J<br />
340 J=2*J<br />
350 IF J=N1 THEN 390<br />
360 IF J>N1 THEN 420<br />
370 IF C$(J)>=C$(J+1) THEN<br />
380 J=J+1<br />
390 IF A$>=C$(J) THEN 420<br />
400 C$(I)=C$(J)<br />
Kassettenrekorder<br />
als Speicher<br />
Kein Drucker<br />
Kein Netzanschluss<br />
732<br />
Magnetband-Kassettenrekorder<br />
als Speichermedium<br />
733<br />
305
Heapsort Dance<br />
www.youtube.com/watch?v=ZbUbCe0WpBE<br />
734<br />
16.<br />
Parallele Prozesse<br />
und Threads<br />
The Java Tutorial (http://docs.oracle.com/javase/tutorial/),<br />
Lesson “Concurrency” (“Doing Two or More Tasks At Once”):<br />
http://docs.oracle.com/javase/tutorial/essential/concurrency/<br />
742<br />
306
Prozesse<br />
Abstrakter: „Vorgang einer<br />
algorithmisch ablaufenden<br />
Informationsverarbeitung“<br />
• Prozess = Programm in Ausführung<br />
• „Instanz“ eines Programms<br />
• Programm selbst ist passives Ding auf Papier oder in einer Datei<br />
• Es kann gleichzeitig mehrere Prozesse als verschiedene<br />
Instanzen des selben Programms geben<br />
• Z.B. mehrere aktive Web-Browser<br />
• Kontext eines Prozesses (zu einem Zeitpunkt) umfasst u.a.:<br />
• Aktuelle Position („Befehlszähler“)<br />
• Inhalt der CPU-Register<br />
Kontext wird oft einfach<br />
als Zustand bezeichnet<br />
• Werte aller Variablen<br />
• Inhalt des Laufzeitstacks (dynamische Aufrufsequenz)<br />
• Zustand zugeordneter Betriebsmittel (z.B. geöffnete Dateien)<br />
743<br />
Prozessverwaltung und Betriebsmittel<br />
• Ein Prozess benötigt Betriebsmittel („Ressourcen“)<br />
• CPU-Zeit, Ha<strong>up</strong>tspeicher (RAM), Dateien...<br />
• Prozesse werden durch das Betriebssystem verwaltet<br />
• Gründen (z.B. im Auftrag anderer Prozesse)<br />
• Terminieren (dann Freigabe aller belegten Betriebsmittel)<br />
• Kontrolle des Ressourcenverbrauchs (Schranken, Monopolisierung)<br />
• Scheduling („suspend“, „resume“ etc. zum Multiplexen der CPU)<br />
• Mechanismen zur Synchronisation<br />
• Vermittlung von Kommunikation zwischen den Prozessen<br />
(z.B. Signale, Ereignisse oder Nachrichten)<br />
744<br />
307
Multitasking<br />
• Mehrere Aufgaben (tasks) quasi-gleichzeitig ausführen<br />
745<br />
Multitasking<br />
Hier: Prozesse, daher<br />
auch „Multiprocessing“<br />
• Mehrere Aufgaben (tasks) quasi-gleichzeitig ausführen<br />
• Prozess unterbrechen; später fortsetzen<br />
• Multiplexen der CPU: time-sharing<br />
durch Zeitscheiben (Interr<strong>up</strong>t durch<br />
„timer“ erzwingt Freigabe der CPU)<br />
Prozesse in so kurzen Abständen<br />
immer abwechselnd aktivieren,<br />
dass der Eindruck<br />
der Gleichzeitigkeit entsteht<br />
747<br />
308
Multitasking<br />
Hier: Prozesse, daher<br />
auch „Multiprocessing“<br />
• Mehrere Aufgaben (tasks) quasi-gleichzeitig ausführen<br />
• Prozess unterbrechen; später fortsetzen<br />
• Multiplexen der CPU: time-sharing<br />
durch Zeitscheiben (Interr<strong>up</strong>t durch<br />
„timer“ erzwingt Freigabe der CPU)<br />
Schnelles Hin- und Herschalten geht dann<br />
gut, wenn sich mehrere Prozesse bereits<br />
im Ha<strong>up</strong>tspeicher befinden (und nicht<br />
erst jedes Mal geladen werden müssen)<br />
• Zweck:<br />
• Mehrere Abläufe der realen Welt gleichzeitig „kontrollieren“<br />
• Bessere CPU-Nutzung: Während ein Prozess auf ein device wartet,<br />
kann ein anderer ausgeführt werden (in 10 ms Plattenzugriffszeit<br />
sind z.B. ca. 10 7 Maschineninstruktionen anderer Prozesse möglich)<br />
748<br />
Echt und quasi-gleichzeitige Abläufe<br />
(Nebenläufigkeit, „concurrency“)<br />
E/A-Geräte können<br />
meist „echt“ parallel<br />
zur CPU arbeiten<br />
• Optimierungsziel:<br />
möglichst alle Geräte<br />
(und die CPU) ständig<br />
beschäftigen bzw. einen<br />
möglichst hohen Überlappungsgrad<br />
erzielen<br />
749<br />
309
Prozesszustände<br />
• Von aussen gesehen kann ein Prozess in 2 Zuständen sein:<br />
resume<br />
läuft<br />
suspend<br />
wait<br />
Prozess wird von<br />
einem anderen<br />
Prozess suspendiert<br />
Ein Ereignis von<br />
aussen reaktiviert<br />
den Prozess<br />
blockiert<br />
Prozess versetzt<br />
sich selbst in den<br />
Wartezustand<br />
Z.B. Warten auf Fertigwerden der E/A<br />
oder Warten auf ein Synchronisationssignal<br />
eines anderen Prozesses<br />
750<br />
Prozesszustände: Verfeinerung<br />
• Bild verfeinern, wenn mehrere Prozesse quasi-gleichzeitig<br />
laufen:<br />
Von aussen (z.B. „kill<br />
interr<strong>up</strong>t“) kann ein<br />
Prozess von jedem<br />
Zustand in den<br />
„beendet“-Zustand<br />
versetzt werden<br />
• Zu einem Zeitpunkt ist stets nur ein einziger Prozess tatsächlich laufend;<br />
die lauffähigen warten darauf, ein bisschen CPU-Zeit zu bekommen<br />
• Zustandswechsel lauffähig („ready“) / laufend („running“) wird vom Betriebssystem<br />
(„Scheduler“) vorgenommen (ohne dass der Prozess selbst<br />
dies merkt: aus seiner Sicht geht es nur mal wieder sehr langsam voran)<br />
• Unterscheide „blockiert“ (fehlende Ressourcen zum Weiterlaufen) und<br />
„lauffähig“ (könnte, aber darf nicht weitermachen – wartet nur auf CPU)<br />
751<br />
310
Prozesskontrollblock<br />
• Prozesse werden durch das Betriebssystem verwaltet<br />
• Multiplexen der CPU („Scheduling“)<br />
• Überwachung des Ressourcenverbrauchs<br />
• Dazu wird für jeden Prozess ein Prozesskontrollblock (PCB,<br />
„Process Control Block“) angelegt, der alle notwendige<br />
Verwaltungsinformationen enthält<br />
• Eindeutige Prozessnummer<br />
• Scheduling-Zustand (blockiert, lauffähig...)<br />
• Programmzähler<br />
• Inhalt der Register<br />
• Priorität<br />
• Maximal erlaubte Ha<strong>up</strong>tspeichernutzung<br />
• Zeiger auf verwendete Speicherbereiche und eigene Kind-Prozesse<br />
• Zeiger auf Betriebsmittel-Listen (z.B. geöffnete Dateien)<br />
• Rechte und Schutz-Information (z.B. zugehöriger „User“)<br />
• Abrechnungsinformation (Zeitlimit, verbrauchte Zeit, Startzeit,...)<br />
• ...<br />
752<br />
Prozesstabelle<br />
• Alle Prozesskontrollblöcke zusammen werden in einer<br />
Prozesstabelle gehalten<br />
PID<br />
1<br />
PCB<br />
Prozesskontrollblock<br />
Programmzähler<br />
2<br />
…<br />
n<br />
…<br />
Prozesskontrollblock<br />
Programmzähler<br />
Register<br />
Zustand<br />
Priorität<br />
Offene Dateien<br />
Adressraum<br />
Prozesskontrollblock<br />
Programmzähler<br />
Register<br />
Zustand<br />
Priorität<br />
Offene Dateien<br />
Adressraum<br />
Benutzer<br />
…<br />
Register<br />
Zustand<br />
Priorität<br />
Offene Dateien<br />
Adressraum<br />
Benutzer<br />
…<br />
Benutzer<br />
…<br />
753<br />
311
Kontextsicherung<br />
„laufend“ <br />
„lauffähig“ / „blockiert“<br />
• Wird der laufende Prozess unterbrochen, muss der<br />
aktuelle Kontext des Prozesses gesichert werden;<br />
hierzu dienen diverse Felder im Prozesskontrollblock<br />
• Der Prozesskontrollblock enthält u.a.:<br />
• …<br />
• Programmzähler<br />
(wenn nicht laufend)<br />
• Inhalt der Register<br />
• …<br />
Wenn der Prozess wieder<br />
laufend wird, lädt<br />
das Betriebssystem<br />
dies in die CPU zurück<br />
754<br />
Kontextwechsel<br />
Prozess P 1 Betriebssystem Prozess P 2<br />
Interr<strong>up</strong>t<br />
Zustand in PCB 1 retten<br />
blockiert /<br />
lauffähig<br />
Zustand von PCB 2 laden<br />
laufend<br />
Zustand in PCB 2 retten<br />
Interr<strong>up</strong>t<br />
Zustand von PCB 1 laden<br />
755<br />
312
Kosten eines Kontextwechsels<br />
• Der zu sichernde Prozesszustand ist recht umfangreich<br />
• Hinzu kommen diverse Verwaltungsaktionen, die das<br />
Betriebssystem bei einem Kontextwechsel durchführt<br />
• Speicherbereich des neuen Prozesses vor Zugriffen<br />
anderer abschotten<br />
• Zugriffsberechtigung auf Ressourcen prüfen<br />
• …<br />
• Kontextwechsel ist daher relativ teuer<br />
• Kostet typischerweise einige zehntausend Instruktionen <br />
• Man kann sich nicht viele „Prozesswechsel“ pro Sekunde erlauben<br />
756<br />
Leichtgewichtsprozesse (Threads)<br />
• Threads („of control“) sind parallele Aktivitätsträger,<br />
die nicht gegeneinander abgeschottet sind<br />
• Laufen innerhalb eines gemeinsamen Adressraumes<br />
• Teilen sich gemeinsame Ressourcen<br />
• Kontextwechsel zwischen den Threads ist viel<br />
effizienter als Kontextwechsel zwischen Prozessen<br />
• Kein Adressraumwechsel<br />
• Kein automatisches Scheduling<br />
• Kein Retten / Restaurieren des Kontextes (Ausnahme: Register,<br />
Programmzähler etc. analog zu Unterprogrammaufruf)<br />
Dadurch anfällig<br />
gegenüber Programmierfehlern!<br />
Instruktionsteil<br />
gemeinsame<br />
Daten<br />
3 Threads in unterschiedlichen<br />
Stadien<br />
(Programmzähler)<br />
• Pro Zeiteinheit viel mehr Threadwechsel als Prozesswechsel möglich<br />
• Wichtig für Server (z.B. Datenbanken oder Suchmaschinen), die pro<br />
Sekunde tausende von Anfragen quasi-gleichzeitig bearbeiten müssen<br />
757<br />
313
Multithreading<br />
• Quasi-gleichzeitig mehrere Vorgänge innerhalb einer<br />
einzigen Anwendung erledigen<br />
• Oft angewendet bei<br />
interaktiven Programmen<br />
• Z.B. „endlose“ Animation, wobei Interaktion<br />
jederzeit möglich sein soll<br />
• Lösung: Zwei Threads (Animation<br />
und Verwalter der input buttons)<br />
Andere typische Anwendung:<br />
Window-Manager, der mehrere<br />
Fenster auf dem Display verwaltet<br />
• Ohne Multithreading müsste der einzige Kontrollfluss selbst<br />
schnell genug zwischen den Aufgaben hin- und herschalten<br />
• Dies dann entweder aktiv durch regelmässiges Nachfragen<br />
(„liegt jetzt eine Anforderung vor?“)<br />
• Oder durch Interr<strong>up</strong>ts („bei Mausklick kurz mal etwas anderes machen“)<br />
• Dies ist i.Allg. ineffizient, komplex und fehleranfällig<br />
758<br />
Klasse java.lang.Thread<br />
• Konstruktor: public Thread()<br />
Hier nur ein Auszug; zu<br />
weiteren Aspekten vgl.<br />
die Java-Dokumentation<br />
(online bzw. in Büchern)<br />
• Methoden:<br />
- void start()<br />
- void suspend()<br />
- void stop()<br />
- void resume()<br />
- void wait()<br />
- void yield()<br />
- void sleep(int millis) // blockiert einige ms<br />
- void join() // Synchronisation zweier Threads<br />
- int getPriority()<br />
- void setPriority(int prio)<br />
- void setDaemon(boolean on)<br />
„Hintergrundprozess“: terminiert<br />
nicht mit dem Erzeuger<br />
759<br />
314
Programmstruktur eines Thread<br />
• Jeder Thread (genauer: jede von „Thread“ abgeleitete<br />
Klasse) muss eine void-Methode run() enthalten<br />
• Diese macht die eigentlichen Anweisungen des Thread aus!<br />
• „run“ ist in „Thread“ selbst nur abstrakt definiert<br />
• Ein typisches Gerüst für einen Thread:<br />
class Beispiel_Thread extends Thread {<br />
int myNumber;<br />
public Beispiel_Thread(int number) { // Konstruktor<br />
myNumber = number;<br />
}<br />
public void run() {<br />
// hier die Anweisungen des Thread<br />
}<br />
// hier weitere Methoden<br />
}<br />
760<br />
Erzeugen eines Thread<br />
(aus einem anderen Thread heraus)<br />
761<br />
315
Erzeugen eines Thread<br />
Beispiel_Thread m = new Beispiel_Thread(5);<br />
m.start();<br />
Damit kann man auf den Thread zugreifen<br />
und diesen kontrollieren (z.B. m.suspend();)<br />
Mit dieser Nummer identifizieren<br />
wir einen Thread individuell; jeder<br />
Thread kennt „seine“ Nummer<br />
m = new...<br />
m.start()<br />
Kontrollfluss des Erzeugers<br />
Thread m (Methode „run“)<br />
• „Anonyme“ Erzeugung als Alternative:<br />
new Beispiel_Thread(5).start();<br />
• Zusammenfassen der beiden Anweisungen (Gründen & Starten)<br />
• Dann aber keine Kontrolle möglich, da kein Zugriff auf den Thread<br />
762<br />
Ein Thread-Beispiel („Hin-Her“)<br />
• *****************<br />
• Ein Thread „Hin“ schreibt Sterne; ein anderer Thread<br />
„Her“ löscht Sterne; beide arbeiten (quasi)parallel<br />
• Wer gewinnt den Sternenkrieg?<br />
767<br />
316
Ein Thread-Beispiel („Hin-Her“)<br />
Nach einer Idee von<br />
Ralf Kühnel („Die Java-<br />
Fibel“, Addison-Wesley)<br />
• **************** ****************<br />
• Ein Thread „Hin“ schreibt Sterne; ein anderer Thread<br />
„Her“ löscht Sterne; beide arbeiten (quasi)parallel<br />
• Wer gewinnt den Sternenkrieg?<br />
public class HinHer {<br />
public static void main (String args[]) {<br />
System.out.print("********************");<br />
}<br />
System.out.flush();<br />
new Hin().start();<br />
new Her().start();<br />
}<br />
Ausgabe des durch „print“ gefüllten Puffers<br />
Kommt es dazu überha<strong>up</strong>t noch? Oder behält<br />
„Hin“ die ganze Zeit über die Kontrolle?<br />
768<br />
Ein Thread-Beispiel („Hin-Her“)<br />
class Hin extends Thread {<br />
}<br />
public void run() {<br />
try {<br />
while(true) {<br />
sleep((int)(Math.random() * 1000));<br />
paint();<br />
System.out.flush();<br />
}<br />
}<br />
catch (Interr<strong>up</strong>tedException e) {return;}<br />
}<br />
public void paint() { // Sterne hinzufügen<br />
System.out.print("****");<br />
}<br />
class Her extends Hin {<br />
public void paint() {<br />
System.out.print("\b\b\b\b \b\b\b\b");<br />
}<br />
}<br />
4 Leerzeichen („space“)<br />
Denkübung: Was geschieht,<br />
wenn ein Thread aufwacht,<br />
während der andere gerade<br />
mitten in „paint“ ist?<br />
Exception, falls während<br />
des sleep ein Interr<strong>up</strong>t<br />
ausgelöst wird<br />
Methode „run“ und damit<br />
den Thread beenden<br />
„Her“ erbt die Methode<br />
„run“ von „Hin“<br />
Redefinition von „paint“:<br />
Sterne mittels backspace<br />
löschen<br />
769<br />
317
Das Backspace-Steuerzeichen \b<br />
Als „Steuerzeichen“ bezeichnet man diejenigen Zeichen eines Zeichensatzes,<br />
die keine darstellbaren Zeichen repräsentieren. Ursprünglich wurden sie zur<br />
Ansteuerung von Fernschreibern oder Textdruckern (analog zu elektrischen<br />
Schreibmaschinen) verwendet. Durch Steuerzeichen ist es möglich, Steuerungsbefehle<br />
für die Ausgabegeräte – z.B. Zeilenvorschub („line feed“), Wagenrücklauf<br />
(„carriage return“), Klingel – innerhalb des Zeichenstroms selbst zu übertragen.<br />
Steuerzeichen werden in Zeichenketten durch ein vorangestelltes „\“ codiert, z.B.<br />
\n („line feed“ bzw. „new line“) oder \b („backspace“). Letzteres bewegte bei<br />
klassischen Textdruckern den Druckkopf eine Position zurück; bei Displays soll<br />
der Cursor um ein Zeichen nach links rücken.<br />
Ob die Steuerzeichen allerdings durch das jeweilige Ausgabegerät (bzw. die<br />
Systemroutinen des Betriebssystems) wirklich „korrekt“ interpretiert werden und<br />
damit der gewünschte Effekt auftritt, kann Java nicht garantieren. Insbesondere<br />
dann, wenn nicht die Systemkonsole als Ausgabe verwendet wird, kann es daher<br />
geschehen, dass ein \b als „nicht druckbares Zeichen“ interpretiert wird und auf<br />
dem Display als □ dargestellt wird.<br />
770<br />
Thread-Steuerung<br />
• Ein Thread läuft (d.h. ist laufend oder lauffähig) so lange, bis<br />
• seine run-Methode zu Ende ist<br />
• er mit stop() abgebrochen<br />
wird (von aussen<br />
oder durch sich selbst)<br />
• Ein laufender Thread kann sich selbst<br />
• die CPU entziehen: yield()<br />
(Übergang in den Zustand „lauffähig“; wird automatisch wieder<br />
„laufend“, wenn keine wichtigeren Threads mehr laufen möchten)<br />
• schlafen legen: sleep(…) (wie suspend, aber<br />
automatisches resume nach gegebener Zeit)<br />
• anhalten: suspend() bzw. wait()<br />
• beenden: stop()<br />
• in der Priorität verändern: setPriority(…)<br />
Prioritäten: normal 5,<br />
minimal 1, maximal 10<br />
(anfangs: Priorität des<br />
erzeugenden Threads)<br />
771<br />
318
Thread-Steuerung (2)<br />
• Ein Thread kann einen anderen Thread t<br />
• starten: t.start()<br />
• anhalten: t.suspend()<br />
• fortsetzbar machen:<br />
t.resume();<br />
• beenden: t.stop()<br />
• in der Priorität verändern: t.setPriority(…)<br />
hierzu ist eine<br />
Referenz auf den<br />
Thread notwendig<br />
• Beachte: stop, suspend und resume führen, unbedacht<br />
angewendet, zu unsicheren Programmen und sollte daher<br />
möglichst vermieden werden<br />
Vgl. auch: http://tinyurl.com/3dhrgfr<br />
772<br />
Stop is Being Deprecated<br />
Thread.stop is being deprecated because it is inherently unsafe. Stopping a<br />
thread causes it to unlock all the monitors that it has locked. (The monitors<br />
are unlocked as the ThreadDeath exception propagates <strong>up</strong> the stack.) If any<br />
of the objects previously protected by these monitors were in an inconsistent<br />
state, other threads may now view these objects in an inconsistent state.<br />
Such objects are said to be damaged. When threads operate on damaged<br />
objects, arbitrary behavior can result. This behavior may be subtle and difficult<br />
to detect, or it may be pronounced. Unlike other unchecked exceptions,<br />
ThreadDeath kills threads silently; thus, the user has no warning that the<br />
program may be corr<strong>up</strong>ted. The corr<strong>up</strong>tion can manifest itself at any time<br />
after the actual damage occurs, even hours or days in the future.<br />
If you have been using Thread.stop in your programs, you should substitute<br />
that use with code that provides for a gentler termination. Most uses of stop<br />
can and should be replaced by code that simply modifies some variable to<br />
indicate that the target thread should stop running. The target thread should<br />
check this variable regularly, and return from its run method in an orderly<br />
fashion if the variable indicates that it is to stop running.<br />
773<br />
319
Stop is Being Deprecated (2)<br />
Thread.suspend is inherently deadlock-prone so it is also being deprecated,<br />
thereby necessitating the deprecation of Thread.resume. If the target thread<br />
holds a lock on the monitor protecting a critical system resource when it is<br />
suspended, no thread can access this resource until the target thread is resumed.<br />
If the thread that would resume the target thread attempts to lock<br />
this monitor prior to calling resume, deadlock results. Such deadlocks typically<br />
manifest themselves as "frozen" processes.<br />
As with Thread.stop, the prudent approach is to have the "target thread" poll<br />
a variable indicating the desired state of the thread (active or suspended).<br />
When the desired state is suspended, the thread waits using Object.wait.<br />
When the thread is resumed, the target thread is notified using Object.notify.<br />
http://docs.oracle.com/javase/1.5.0/docs/guide/misc/threadPrimitiveDeprecation.html<br />
774<br />
Stop is Being Deprecated (3)<br />
Auszug aus: Guido Krüger: Handbuch der Java-Programmierung.<br />
Addison-Wesley, 2004, ISBN 3-8273-2201-4<br />
Mit dem JDK 1.2 wurde die Methode stop als deprecated markiert, d.h., sie<br />
sollte nicht mehr verwendet werden. Der Grund dafür liegt in der potentiellen<br />
Unsicherheit des Aufrufs, denn es ist nicht voraussagbar und auch nicht definiert,<br />
an welcher Stelle ein Thread unterbrochen wird, wenn ein Aufruf von<br />
stop erfolgt. Es kann nämlich insbesondere vorkommen, dass der Abbruch<br />
innerhalb eines kritischen Abschnitts erfolgt (der mit dem synchronized-<br />
Schlüsselwort geschützt wurde) oder in einer anwendungsspezifischen Transaktion<br />
auftritt, die aus Konsistenzgründen nicht unterbrochen werden darf.<br />
775<br />
320
Stop is Being Deprecated (4)<br />
Die alternative Methode, einen Thread abzubrechen, besteht darin, im Thread<br />
selbst auf Unterbrechungsanforderungen zu reagieren. So könnte beispielsweise<br />
eine Membervariable cancelled eingeführt und beim Initialisieren des<br />
Thread auf false gesetzt werden. Mit Hilfe einer Methode cancel kann der<br />
Wert der Variable zu einem beliebigen Zeitpunkt auf true gesetzt werden.<br />
Aufgabe der Bearbeitungsroutine in run ist es nun, an geeigneten Stellen<br />
diese Variable abzufragen und für den Fall, dass sie true ist, die Methode run<br />
konsistent zu beenden.<br />
Dabei darf cancelled natürlich nicht zu oft abgefragt werden, um das Programm<br />
nicht unnötig aufzublähen und das Laufzeitverhalten des Thread nicht<br />
zu sehr zu verschlechtern. Andererseits darf die Abfrage nicht zu selten erfolgen,<br />
damit es nicht zu lange dauert, bis auf eine Abbruchanforderung reagiert<br />
wird. Insbesondere darf es keine potentiellen Endlosschleifen geben, in den<br />
cancelled überha<strong>up</strong>t nicht abgefragt wird. Die Kunst besteht darin, diese gegensätzlichen<br />
Anforderungen sinnvoll zu vereinen.<br />
776<br />
Thread-Steuerung mittels stop und resume<br />
• Trotzdem sind stop, suspend und resume aber ganz bequem:<br />
class Spinner extends Thread // "endloser" Thread<br />
...<br />
void HitCancel() {<br />
Spinner.suspend(); // anhalten<br />
if (askYesNo("Wirklich abbrechen?","ja","nein"))<br />
Spinner.stop(); // abbrechen<br />
else<br />
Spinner.resume(); // weiter<br />
}<br />
...<br />
777<br />
321
Thread-Ende<br />
• Ein Thread ist beendet, wenn seine run-Methode<br />
beendet wird oder er durch „stop“ abgebrochen wird<br />
• Das Objekt eines beendeten Thread existiert weiter<br />
• Auf dessen Zustand kann also noch zugegriffen werden<br />
• Ein beendeter Thread kann mit start wieder neu loslaufen<br />
• Die run-Methode wird dann erneut ausgeführt<br />
• Methode join verwenden, wenn auf die Beendigung<br />
eines anderen Thread gewartet werden soll<br />
• Z.B. weil man auf die von ihm berechneten Daten zugreifen will<br />
778<br />
Warten auf einen Thread („join“)<br />
• Beispiel: Thread s wartet so lang, bis t beendet ist:<br />
• Alternativer Fall: Thread t ist zuerst fertig:<br />
In diesem Fall ist<br />
t.join bedeutungslos<br />
• Nach t.join ist in jedem Fall garantiert, dass t beendet ist<br />
779<br />
322
Wie lange ist ein Thread laufend?<br />
• Bis er sich beendet<br />
• „stop“ oder Ende von „run“<br />
• Bis ein Thread höherer<br />
Priorität lauffähig wird<br />
• Übergang nach „lauffähig“<br />
• Sofortiger Threadwechsel ist aber nicht garantiert!<br />
• Bis er in den „blockiert“-Zustand übergeht<br />
• Explizit mit „suspend“, „wait“, „sleep“ etc.<br />
• Evtl. implizit durch E/A (es ist aber nicht garantiert, dass ein auf<br />
E/A-wartender Thread die CPU tatsächlich für andere freigibt!)<br />
• Bis er mit „yield“ die Kontrolle dem Scheduler übergibt<br />
• Oder vom Scheduler zwangsweise die CPU entzogen bekommt<br />
Vom Lateinischen „schedula“, Diminutiv von „scheda“ (bzw.<br />
„scida“) für einen abgespalteten Streifen der Papyrusstaude,<br />
woraus dann auch das deutsche Wort „Zettel“ entstand<br />
Ein laufender thread wird<br />
von der CPU ausgeführt<br />
780<br />
Thread-Scheduling<br />
• Scheduling: Planvolle Zuordnung der CPU an<br />
die einzelnen Threads (jeweils für einige Zeit)<br />
• Genaue Scheduling-Strategie ist in Java nicht standardisiert<br />
• Kann jede VM-Implementierung für sich festlegen (und damit<br />
Eigenheiten des zugrundeliegenden Betriebssystems effizient nutzen)<br />
• Man darf sich daher nicht auf „Erfahrungen“ verlassen<br />
(konkret: nicht auf die Wirkung von Zeitscheiben oder Prioritäten)<br />
• Scheduling mit Zeitscheiben kann von<br />
der VM realisiert sein, muss aber nicht<br />
sonst sind Programme<br />
nicht deterministisch<br />
und nicht portabel!<br />
• Thread läuft dann längstens bis zum Ablauf der Zeitscheibe (i.Allg.<br />
zyklisches Scheduling unter Threads gleicher maximaler Priorität)<br />
781<br />
323
Prinzip eines zyklischen Zeitscheiben-Schedulers<br />
…<br />
while (true){<br />
if (current != last)<br />
next = current + 1;<br />
else<br />
next = 1;<br />
threadlist[current].suspend();<br />
threadlist[next].resume();<br />
current = next;<br />
sleep(TIMESLICE);<br />
}<br />
Der „System Idle“-Prozess nutzt die CPU am meisten<br />
• Der Scheduler selbst sollte mit höchster Priorität laufen<br />
• Der „System Idle“-Prozess mit niedrigster Priorität<br />
• Was ist bei Endlosschleifen in anderen Threads?<br />
• Ist generell garantiert, dass der Scheduler nach Ablauf<br />
der Zeitscheibe wieder die Kontrolle erhält?<br />
782<br />
Prioritäten<br />
• Implementierungsvorgabe: Ein Thread-Scheduler<br />
soll Threads mit höherer Priorität bevorzugen<br />
• Priorität entspricht initial der des Erzeuger-Thread<br />
• Priorität kann verändert werden (setPriority)<br />
• Wenn ein Thread mit höherer Priorität als der gegenwärtig ausgeführte<br />
lauffähig wird, wird der gegenwärtige i.Allg. unterbrochen<br />
• Typische Verwendung von Prioritäten:<br />
• Niedrige Priorität für dauernd laufende „Hintergrundaktivitäten“<br />
• Höhere Priorität für seltene aber wichtige Ereignisse<br />
(Benutzereingaben, Unterbrechungen...)<br />
• Prioritäten sollten besser nicht als Synchronisationsmittel<br />
(Erzwingen einer bestimmten Reihenfolge etc.) eingesetzt werden<br />
783<br />
324
Threads: Schwierigkeiten<br />
• Ein Thread mit Endlosschleife kann u.U. das ganze System<br />
blockieren (so dass andere Threads „verhungern“)<br />
• Daher eventuell rücksichtsvoll mit „yield“ dem Scheduler helfen<br />
• Ist insbesondere bei Systemen ohne Zeitscheiben wichtig<br />
• Bei Prozessoren mit mehreren CPUs bzw. Rechenkernen<br />
(„multicore“) könnten entsprechend viele Threads „echt<br />
gleichzeitig“ ausgeführt werden („multiprocessing“)<br />
• Schon deswegen kein Verlass, dass Synchronisation bzw. wechselseitiger<br />
Ausschluss, realisiert mittels Prioritäten, funktioniert!<br />
• Böses Erwachen, wenn ein solches Programm dann irgendwann<br />
einmal auf einem Multicore-Prozessor ausgeführt wird...<br />
784<br />
Threads: Schwierigkeiten (2)<br />
• Programmieren und „Debugging“ von Threads ist schwierig<br />
• Alle denkbaren verzahnten Abläufe („interleavings“) berücksichtigen<br />
• Menge der verzahnten Abläufe durch geeignete Synchronisation<br />
einschränken (nur „korrekte“ Abläufe zulassen)<br />
• Finden von Synchronisationsfehlern ist besonders mühsam<br />
(schlecht reproduzierbar; evtl. „Heisenberg-Effekt“!)<br />
• Portabilität ist bei dilettantischer Thread-Nutzung gefährdet<br />
• “The setPriority and yield methods are advisory. They constitute<br />
hints from the application to the JVM. Properly written, robust,<br />
platform-independent code can use setPriority() and yield() to<br />
optimize the performance of the application, but should not depend<br />
on these attributes for correctness.”<br />
785<br />
325
Parallele Threads auf dem Mars<br />
Historische Notiz<br />
Mars Pathfinder Mission 1997<br />
786<br />
The Lander’s Computer Appeared to Reset Itself<br />
JET PROPULSION LABORATORY<br />
CALIFORNIA INSTITUTE OF TECHNOLOGY<br />
NATIONAL AERONAUTICS AND SPACE ADMINISTRATION<br />
PASADENA, CALIF.<br />
MISSION STATUS - 14 July 1997, 10:00 am PDT<br />
Mars Pathfinder's lander sent about an hour's worth of data to Earth last night –<br />
including portions of a 360-degree color panorama image – before the lander's<br />
computer appeared to reset itself, terminating the downlink session.<br />
787<br />
326
Software that Manages a Number of Different<br />
Activities Simultaneously<br />
MISSION STATUS - 14 July 1997, 10:00 am PDT<br />
Engineers are continuing to debug the<br />
reset problem, which appears to be related<br />
to software that manages how the<br />
lander's computer handles a number<br />
of different activities simultaneously.<br />
“Saturday night, we ‘serialized’ activities<br />
by having the lander do one thing at<br />
a time, whereas last night the lander<br />
was handling a number of activities<br />
when the reset occurred,” said Brian<br />
Muirhead, Mars Pathfinder flight system<br />
manager. “Tonight we will return to<br />
a ‘serialized’ approach to try to avoid<br />
the possibility of a reset.” The reset occurred<br />
at 1:06 a.m. PDT, about halfway<br />
through a planned two-hour downlink<br />
session.<br />
788<br />
Handle One Activity at a Time!<br />
MISSION STATUS - 15 July 1997, noon PDT<br />
Recent incidents in which the<br />
Pathfinder lander's computer reset<br />
itself were discussed by Glenn<br />
Reeves, flight software team leader.<br />
According to him, computer resets<br />
have occurred a total of four times<br />
duringthemission–onJuly5,10,<br />
11 and 14. The flight team has<br />
attempted to avoid future resets by<br />
instructing the computer to handle<br />
one activity at a time – ‘serializing’<br />
activities – rather than juggling a<br />
number of activities at once.<br />
789<br />
327
Considering Changes in the Flight Software<br />
MISSION STATUS - 15 July 1997, noon PDT<br />
The team continues to trouble-shoot the<br />
problem by testing all of the sequences<br />
leading <strong>up</strong> to reset in JPL's Mars Pathfinder<br />
testbed; considering changes in<br />
the flight software that would allow for<br />
immediate recovery if the flight computer<br />
were to reset itself; and modifying<br />
operational activities to minimize data<br />
loss if a reset should occur again. “In a<br />
sense, the reset itself is not harmful<br />
because it brings us back into a safe<br />
state,” said Reeves. “But it does cause a<br />
disr<strong>up</strong>tion of the operational activities.”<br />
790<br />
The Task Had Not Been Given High Enough Priority<br />
MISSION STATUS - July 17, 1997, 11 am PDT<br />
Mars Pathfinder engineers... also noted that they have found and are in the<br />
process of fixing a software bug that had caused the lander's computer to<br />
reset itself four times in recent days.<br />
“The resets on the lander computer<br />
were caused by a software task that<br />
was unable to complete the task in the<br />
allotted time,” said Flight Director<br />
Brian Muirhead. “We found that the<br />
task was being cut short because it had<br />
not been given high enough priority to<br />
runthroughtocompletion.Basically,<br />
we just need to add one instruction to<br />
the computer software to raise the<br />
priority of that task.”<br />
791<br />
328
The Problem was Reproduced and Isolated<br />
MISSION STATUS - July 17, 1997, 11 am PDT<br />
The problem was reproducedandisolatedintesting<br />
at JPL. Further tests<br />
and verification will be<br />
completed today and tomorrow,<br />
with radio transmission<br />
of a software patch<br />
to change the lander's software<br />
scheduled for Saturday,<br />
Muirhead said.<br />
792<br />
Sent a Software Update to Mars<br />
MISSION STATUS - July 21, 1997, 10 am PDT<br />
The team... also sent a software<br />
<strong>up</strong>date to correct sequences onboard<br />
the flight computer which<br />
have caused it to automatically reset<br />
itself.<br />
MISSION STATUS –<br />
July 24, 1997, 2:30 pm PDT<br />
Flight Director Dave Gruel reported<br />
that no further flight software resets<br />
have occurred since the team sent<br />
modified flight software...<br />
793<br />
329
Andere Softwarefehler im Weltraum<br />
• 22. Juli 1962, Cape Canaveral / Florida:<br />
Start der ersten amerikanischen<br />
Venussonde „Mariner 1“<br />
• Ausschnitt aus dem FORTRAN-<br />
Programm zur Steuerung der<br />
Flugbahn der Trägerrakete<br />
• Der Start scheiterte. Die Trägerrakete<br />
Atlas Agena B kam vom Kurs ab und<br />
wurde 290 Sekunden nach dem Start<br />
durch Funkbefehl gesprengt. Wer erkennt<br />
den simplen Programmierfehler?<br />
IF (TVAL .LT. 0.2E-2) GOTO 40<br />
DO 40 M = 1, 3<br />
W0 = (M-1)*0.5<br />
X = H*1.74533E-2*W0<br />
DO 20 N0 = 1, 8<br />
EPS = 5.0*10.0**(N0-7)<br />
CALL BESJ(X, 0, B0, EPS, IER)<br />
IF (IER .EQ. 0) GOTO 10<br />
20 CONTINUE<br />
DO 5 K = 1. 3<br />
T(K) = W0<br />
Z = 1.0/(X**2)*B1**2+3.0977E-<br />
4*B0**2<br />
D(K) = 3.076E-<br />
2*2.0*(1.0/X*B0*B1+3.0977E-<br />
4**(B0**2-X*B0*B1))/Z<br />
E(K) =<br />
H**2*93.2943*W0/SIN(W0)*Z<br />
H = D(K)-E(K)<br />
5 CONTINUE<br />
10 CONTINUE<br />
Y = H/W0-1<br />
40 CONTINUE<br />
DO 5 K = 1.3 (statt 1,3) wurde vom<br />
Compiler als Zuweisung von 1.3 an<br />
die Variable DO5K verstanden!<br />
794<br />
Race conditions [Wettlaufsituationen]<br />
802<br />
330
Race conditions bei Threads<br />
• Threads kommunizieren über gemeinsame Variablen<br />
• „shared variables“<br />
• Dabei kann es zu unterschiedlichen Ergebnissen kommen,<br />
je nachdem, welcher Thread zuerst bzw. zuletzt auf eine<br />
Variable zugreift<br />
• „race condition“ bzw. „hazard“<br />
A<br />
B<br />
Wer hat das letzte Wort? Welcher Effekt ist hier wirksam?<br />
Wer zuerst kommt, mahlt zuerst – aber wer zuletzt lacht, lacht am besten<br />
Unbeabsichtigte Wettlaufsituationen<br />
sind ein häufiger<br />
Grund für schwer auffindbare<br />
Programmfehler; bezeichnend<br />
für solche Situationen<br />
ist nämlich, dass bereits die<br />
veränderten Bedingungen<br />
zum Programmtest zu einem<br />
völligen Verschwinden der<br />
Symptome führen können.<br />
de.wikipedia.org<br />
803<br />
Ein Beispiel für race conditions<br />
int x = 5; // initial<br />
Thread 1:<br />
{ y=x; y=y+1; x=y; }<br />
Thread 2:<br />
{ y=x; }<br />
Thread 3:<br />
{ y=x; y=y+5; x=y; }<br />
• Welches Ergebnis ist das richtige?<br />
• Ablauf ist nicht-deterministisch es gibt<br />
mehrere „gleich richtige“ (wie viele?)<br />
• Oder ist richtig nur das, was der Programmierer<br />
„eigentlich“ gemeint hat?<br />
Denkübung: was könnte bei<br />
echter Parallelität mit 2 oder<br />
3 CPU-Kernen passieren?<br />
804<br />
331
Race conditions bei Kontobuchung<br />
• Bsp.: Zwei parallele Threads nehmen Kontobuchung vor:<br />
Zeit<br />
• Kontobuchung des linken Threads geht verloren!<br />
• „lost <strong>up</strong>date problem“<br />
• Lösung?<br />
805<br />
Race conditions bei Kontobuchung (2)<br />
• Wie wäre es damit?:<br />
...<br />
Konto.Update(Betrag1+<br />
Konto.Stand());<br />
...<br />
...<br />
Konto.Update(Betrag2+<br />
Konto.Stand());<br />
...<br />
• Oder vielleicht jeweils Aufruf einer Methode „Buchung“?:<br />
void Buchung (... Konto, float Betrag) {<br />
float a = Konto.Stand();<br />
Konto.Update(a + Betrag);<br />
}<br />
ist das eigentlich<br />
kommutativ?<br />
...<br />
Buchung(k, 300.00);<br />
...<br />
...<br />
Buchung(k, -154.23);<br />
...<br />
806<br />
332
Atomarität<br />
• Ist eine Java-Anweisung „atomar“?<br />
• Z.B.: Konto.Update(Betrag + Konto.Stand());<br />
• Oder zumindest: Buchung(k, …);<br />
Auch dann gäbe es aber<br />
zwei mögliche Abläufe:<br />
Konto würde bei „unglücklicher“<br />
Reihenfolge evtl.<br />
kurzzeitig überzogen!<br />
• Atomare Folge von Operationen<br />
• Während die Folge ausgeführt wird, werden keine anderen<br />
Operationen (quasi) gleichzeitig ausgeführt<br />
• Wenn die CPU mit der ersten Operation der atomaren<br />
Folge beginnt, arbeitet sie diese bis zur letzten ab,<br />
ohne zwischendrin etwas anderes („störendes“) zu tun<br />
• Unterbrechungen sind höchstens zwischen atomaren Folgen erlaubt<br />
807<br />
Relative Atomarität<br />
• Aber: „unkritische Dinge“ könnten eigentlich doch<br />
parallel zu einer atomaren Folge ausgeführt werden<br />
• Quasi „heimlich“?<br />
• Und was genau ist (in welchem Sinne?) unkritisch?<br />
• Teile eines ganz anderen Programms?<br />
• Auf den Kontostand nur lesend zugreifen?<br />
• Leere Schnittmenge bzgl. gemeinsamer / veränderter Variablen?<br />
„Atomar“ ist ein interpretationsbedürftiger relativer Begriff!<br />
808<br />
333
Inkonsistenzen<br />
• Durch die Nicht-Atomarität von Anweisungsfolgen kommt es<br />
bei paralleler Ausführung leicht zu unerwünschten Effekten<br />
• Z.B. inkonsistente Zustände,<br />
die widersprüchlich sind und<br />
nicht auftreten dürften<br />
Inkonsistenz [de.wikipedia.org]: Ein Zustand, in dem zwei Dinge, die beide<br />
als gültig angesehen werden sollen, nicht miteinander vereinbar sind.<br />
809<br />
Bauanleitung für Selbstbaumöbel?<br />
810<br />
334
811<br />
Inkonsistenzen: Beispiel<br />
(Animation einer Teilchenbewegung)<br />
Animationsthread:<br />
Eventthread:<br />
Objekt springt bei „reset“ (manchmal!) an eine<br />
falsche Stelle (x-Koordinate ist dann nicht Null)<br />
812<br />
335
Lieber Thread-Scheduler!<br />
Wie kann man Atomarität von Anweisungsfolgen erreichen?<br />
…<br />
x = 0;<br />
y = 0;<br />
…<br />
„Lieber Thread-Scheduler, bitte<br />
unterbrich mich jetzt nicht!“<br />
…<br />
write(x,0)<br />
write(y,0)<br />
…<br />
„Danke, lieber Thread-Scheduler, du darfst<br />
mich jetzt gerne wieder unterbrechen!“<br />
813<br />
Unterbrechungssperren auf<br />
Java-Ebene mittels Prioritäten?<br />
…<br />
int p = getPriority();<br />
setPriority(Thread.MAX_PRIORITY);<br />
x = 0; y = 0; // kritischer Abschnitt<br />
setPriority(p);<br />
Ist das eine<br />
funktionierende<br />
und gute Lösung<br />
für Unterbrechungssperren?<br />
• Beachte: Auch bei einer „Unterbrechungssperre“ können<br />
durchaus andere Dinge parallel ablaufen<br />
• Der Systemprozess, wo der Thread-Scheduler eingebettet ist (z.B. die<br />
Java-VM), kann vom Betriebssystem zeitweise suspendiert werden<br />
• Ein Mehrkernprozessor könnte andere Threads „echt“ parallel ausführen<br />
• Die „Umwelt“ ändert sich auch während einer Unterbrechungssperre<br />
• Es kommt also darauf an, in welcher „Hinsicht“<br />
Atomarität gewährleistet werden soll / kann!<br />
814<br />
336
Java: Nicht-Atomarität von double und long<br />
• Double- und long-Variablen sind 64 Bit lang<br />
• Auf 32-Bit-Prozessoren sind beim Schreiben bzw.<br />
Lesen jeweils zwei Speicherzugriffe nötig; diese<br />
sind zwischendrin unterbrechbar!<br />
• Bei zwei Threads, die auf die gleiche Variable<br />
zugreifen, kann es zu Inkonsistenzen kommen:<br />
Ergebnis entspricht nicht unbedingt einem<br />
möglichen Interleaving auf Sprachebene<br />
815<br />
Java: Nicht-Atomarität von double und long (2)<br />
"Programmers are<br />
cautioned always to<br />
explicitly synchronize<br />
access to shared double<br />
or long variables“<br />
(The Java Virtual<br />
Machine Specification)<br />
Wie erreicht man Konsistenz, wenn es hardwaremässig<br />
keine atomaren Befehle zum Zugriff auf 64 Bits gibt?<br />
816<br />
337
Kritischer Abschnitt<br />
• Kritischer Abschnitt = Folge von Anweisungen, die<br />
bezüglich anderen „entsprechenden“ kritischen<br />
Abschnitten wechselseitig ausgeschlossen ist<br />
• D.h. während ein Thread im kritischen Abschnitt ist, darf kein anderer<br />
Thread einen entsprechenden kritischen Abschnitt betreten<br />
• Höchstens einer hat also die Erlaubnis<br />
• In einem kritischen Abschnitt werden diejenigen elementaren<br />
Operationen ausgeführt, die ungestört als<br />
Ganzes ausgeführt werden müssen<br />
• Z.B. Einfügen eines Elementes in den nächsten freien Array-<br />
Platz und Hochzählen der Variablen, die diesen Platz anzeigt<br />
• Oder: Zugriff auf ein exklusives Betriebsmittel in Konkurrenz<br />
zu anderen Prozessen (z.B. zu beschreibende Datei)<br />
817<br />
Kritischer Abschnitt: Anforderungsspezifikation<br />
• Das Problem besteht darin, ein „Protokoll“ zu entwerfen,<br />
an das sich dann alle Prozesse halten, und das die<br />
Semantik des kritischen Abschnitts (kA) realisiert<br />
• Was gehört zur Semantik des kA?<br />
• 3 Anforderungen:<br />
• Safety<br />
• Liveness<br />
• Fairness<br />
818<br />
338
Safety, Liveness, Fairness<br />
(1) Safety („nothing bad will ever happen“)<br />
Wenn ein Prozess im kA ist, dann kein anderer<br />
Das alleine genügt aber nicht;<br />
sonst wäre ein Protokoll, das<br />
keinem Prozess je den Zutritt<br />
erlaubt, korrekt! (Alle Verkehrsampeln<br />
auf Dauerrot<br />
machen <strong>Zürich</strong> sicher!)<br />
(2) Liveness („something good will eventually happen“):<br />
Wenn kein Prozess im kA ist, aber einige<br />
sich „bewerben“, dann kommt einer von<br />
diesen schliesslich in den kA<br />
Liveness ohne Safety<br />
ist auch wieder trivial!<br />
Gesucht ist eine Lösung,<br />
die Safety und zugleich<br />
Liveness erfüllt<br />
(3) Fairness<br />
Ein sich bewerbender Prozess darf<br />
nicht beliebig oft von anderen<br />
Prozessen übergangen werden<br />
Sonst könnten sich etwa<br />
zwei Prozesse abwechselnd<br />
das Recht zuspielen und<br />
einen dritten Prozess verhungern<br />
lassen<br />
819<br />
Java: das Synchronized-Konstrukt<br />
• Bei Java wird ein kritischer Abschnitt durch eine in<br />
{...}-geklammerte Anweisungsfolge mit vorangestelltem<br />
Schlüsselwort „synchronized“ spezifiziert:<br />
synchronized (xxx) {<br />
// Anweisung 1<br />
...<br />
// Anweisung n<br />
}<br />
Hier steht xxx für irgendeine<br />
Objektreferenz<br />
(„Sperrobjekt“; „lock“)<br />
• Meist wird xxx innerhalb der Anweisungsfolge verwendet<br />
• D.h. es handelt sich um das „kritische Objekt“, bezüglich<br />
dessen der wechselseitige Ausschluss realisiert sein soll<br />
• Es kann sich aber auch um irgendein stellvertretendes Sperrobjekt<br />
handeln, z.B. so deklariert:<br />
Object Sperre = new Object;<br />
820<br />
339
Wirkung von „synchronized“<br />
• Alle bezüglich des gleichen Sperrobjektes synchronisierten<br />
kritischen Abschnitte schliessen sich wechselseitig aus<br />
• Wird automatisch durch die Java-VM garantiert<br />
• Beispiel:<br />
synchronized (Konto) {<br />
float a = Konto.Stand();<br />
a = a + Betrag;<br />
Konto.Update(a);<br />
}<br />
Man versuche herauszufinden,<br />
was die Java-<br />
Sprachbeschreibung<br />
zur Fairness aussagt<br />
• Beachte: unkooperative Threads können aber weiterhin z.B.<br />
über die Objektreferenz (hier: „Konto“) auf das „gesperrte“<br />
Objekt zugreifen und es „parallel“ manipulieren<br />
• Die richtige Verwendung des Sperrmechanismus<br />
liegt in der Verantwortung des Programmierers!<br />
821<br />
Beispiel für „synchronized“<br />
Initial: int a = 1, b = 2;<br />
Thread 1: a = b;<br />
Thread 2: b = a;<br />
• Zuweisung ist nicht atomar!<br />
1. Lesen aus dem Ha<strong>up</strong>tspeicher in<br />
In the absence of explicit synchronization,<br />
a Java implementation is free<br />
to <strong>up</strong>date the main memory in an<br />
order that may be surprising. Therefore<br />
the programmer who prefers to<br />
avoid surprises should use explicit<br />
synchronization.<br />
(Java Language Specification)<br />
den Arbeitsbereich des Thread (u.a. Stack, CPU-Register...)<br />
2. Schreiben aus dem Arbeitsbereich in den Ha<strong>up</strong>tspeicher<br />
• Daher sind drei Ergebnisse möglich:<br />
• a = 2, b = 2 (Thread 1 ganz vor Thread 2)<br />
• a = 1, b = 1 (Thread 2 ganz vor Thread 1)<br />
• a = 2, b = 1 (vertauschte Werte durch Interleaving!)<br />
iload_1<br />
istore_0<br />
iload_0<br />
istore_1<br />
822<br />
340
Beispiel für „synchronized“ (2)<br />
• Durch „synchronized“ kann der 3. Fall vermieden werden:<br />
Thread 1: synchronized(Sperre) {a = b;}<br />
Thread 2: synchronized(Sperre) {b = a;}<br />
• Synchronisation schränkt die Anzahl möglicher Abläufe ein<br />
• Auch bei Atomarität gibt es aber noch zwei unterschiedliche Abläufe<br />
• Denkübung: wie könnte man erzwingen, dass auf<br />
alle Fälle erst Thread 1 und dann Thead 2 läuft?<br />
Best practice is that if a variable is ever to be assigned<br />
by one thread and used or assigned by another, then<br />
all accesses to that variable should be enclosed in<br />
synchronized methods or synchronized statements.<br />
(Java Language Specification)<br />
823<br />
Synchronized-Methoden<br />
• Ganze Methoden können mit dem Attribut<br />
„synchronized“ gekennzeichnet werden:<br />
synchronized void Update (int betrag) {<br />
konto = konto + betrag;<br />
System.out.println(konto);<br />
}<br />
• Synchronized-Methoden wirken wie Synchronized-<br />
Anweisungsfolgen, die bzgl. „this“ als Sperrobjekt<br />
wechselseitig ausgeschlossen sind<br />
824<br />
341
Eine Denkübung zu Synchronized-Methoden<br />
public class … {<br />
int z = 0;<br />
public synchronized void Incr() {<br />
println(z++);<br />
}<br />
// Gründen von zwei parallelen Threads,<br />
// welche beide fortlaufend Incr aufrufen;<br />
}<br />
1) Ist ausser der Ausgabefolge 0,1,2,3,4,… noch<br />
eine andere möglich?<br />
2) Bei Weglassen von „synchronized“:<br />
a) Ist die Ausgabe dann immer aufsteigend sortiert?<br />
b) Können Werte doppelt ausgegeben werden?<br />
c) Können Werte dreifach ausgegeben werden?<br />
825<br />
Das Deadlock-<br />
Problem<br />
830<br />
342
831<br />
Deadlock („Verklemmung“) durch „synchronized“<br />
<br />
<br />
<br />
<br />
Mit „Glück“ hätte das<br />
Scheduling der Threads<br />
so ausgesehen:<br />
(1) ; (4) ; Freigabe<br />
von A ; (2) ; (3)<br />
Auf Glück ist aber leider<br />
kein Verlass…<br />
Synchronisationsfehler<br />
sind schwer zu finden!<br />
• Thread 1 wartet auf Freigabe von B durch Thread 2<br />
• Thread 2 wartet auf Freigabe von A durch Thread 1<br />
Deadlock<br />
832<br />
343
Ein weiteres Problem:<br />
Der atomare Kontrolltransfer<br />
• Bei Nutzung von Threads taucht oft folgendes Problem auf:<br />
• Es soll („abwechselnd“) immer nur einer von n Threads laufen<br />
• Jeder Thread soll an einer gewissen Stelle die Kontrolle an<br />
einen bestimmten anderen Thread transferieren<br />
• Wenn die Kontrolle zurücktransferiert wird, dann soll genau an<br />
dieser Stelle weitergemacht werden<br />
• Wie implementiert man dies?<br />
833<br />
Lösung mit suspend / resume?<br />
• Idee: Alle bis auf jeweils einen Thread blockieren<br />
• Problem: T2 könnte unmittelbar nach T2.resume() loslaufen<br />
• T1.resume() würde dann verpuffen, da T1 noch nicht blockiert ist<br />
• T2 würde sich mit suspend() blockieren;<br />
• T1 läuft weiter und blockiert sich dann ebenfalls sofort mit der<br />
nächsten Anweisung Deadlock!<br />
834<br />
344
Lösung mit „synchronized“?<br />
Man hätte also gerne einen Mechanismus,<br />
der in atomarer Weise<br />
suspendiert und zugleich die<br />
Sperre freigibt (und damit einen<br />
anderen Thread lauffähig macht)<br />
wait / notify-Konzept von Java<br />
• Problem: Bei Blockierung durch suspend behält ein Thread alle Sperren<br />
• Nach suspend von T1 würde also T2 vor seinem „synchronized“ warten<br />
• Sowohl T1 als auch T2 warten aufeinander Deadlock!<br />
835<br />
Lösung mit wait / notify<br />
• Wait: passives Warten auf das Eintreffen einer Bedingung<br />
• Und in atomarer Weise gleichzeitig Freigabe der Sperre!<br />
• Notify: Benachrichtigen vom Eintreffen einer Bedingung<br />
• Dient neben der Synchronisation von Threads damit auch<br />
der Kommunikation zwischen Threads (in primitiver Form)<br />
• Wir diskutieren dieses Java-Konzept (und das generelle<br />
Synchronisationsproblem paralleler Prozesse) hier nicht<br />
genauer; in anderen Systemen gibt es für diesen Zweck<br />
noch eine Reihe weiterer Mechanismen<br />
!<br />
Moral: Umgang mit Parallelität<br />
erfordert mächtige und adäquate<br />
Konzepte (und grösste Sorgfalt!)<br />
836<br />
345
Das Ringen mit der Parallelität<br />
837<br />
Das Ringen mit der Parallelität<br />
838<br />
346
Das Bezwingen der Parallelität –<br />
eine Herkulesaufgabe<br />
839<br />
Resümee<br />
der Vorlesung<br />
844<br />
347
Resümee – Ziele der Vorlesung<br />
• Primär: Informatik-Grundbegriffe<br />
• Konzepte, Modelle, Problemlösungstechniken<br />
• Algorithmen und Datenstrukturen<br />
• Sekundär: Programmieren<br />
• Java<br />
• Auch: Techniken für qualitativ hochwertige Software<br />
• Strukturen (Objektorientierung...)<br />
• Qualitätsmerkmale<br />
• Korrektheit<br />
845<br />
Konzepte<br />
Java<br />
Korrektheitsnachweis (Invarianten und vollst. Indukt.)<br />
Robustes Programmieren<br />
Bäume<br />
Syntaxdiagramme<br />
Rekursiver Abstieg<br />
Infix, Postfix, Operatorbaum, Stack<br />
Codegenerierung, Compiler, Interpreter<br />
Verzahnte und verwobene Einführung konzeptioneller<br />
Aspekte und programmiersprachlicher Konstrukte<br />
Polymorphie<br />
Suchbäume, Sortieren<br />
Backtracking<br />
Spieltheorie, Minimax, AlphaBeta<br />
Rekursives Problemlösen<br />
Effizienz, O-Notation<br />
Simulation (zeitgesteuert, ereignisgesteuert)<br />
Heap, Heapsort<br />
Pseudoparallelität<br />
Java: C-Level<br />
Java-Klassen als Datenstrukturen<br />
Dynamische Klassen und Referenzen<br />
Java-VM als Bytecode-Interpreter<br />
Pakete<br />
Klassenhierarchie<br />
Abstrakte Klassen<br />
Exceptions<br />
Programmbeispiele dienen gleichzeitig der<br />
Einführung programmiersprachlicher Konstrukte<br />
und der Illustration von Informatikkonzepten<br />
Threads in Java<br />
847<br />
348
Anhang<br />
• In den ersten Übungen wird das „3n+1“-Problem erwähnt.<br />
Es stammt von Lothar Collatz (1910-1990).<br />
853<br />
Anhang<br />
854<br />
350
Anhang<br />
855<br />
Anhang<br />
856<br />
351
Anhang<br />
http://xkcd.com/<br />
857<br />
GLAUBEN SIE ABER JA NICHT,<br />
DASS DAS SKRIPT ALLEIN ZUM<br />
BESTEHEN DER PRÜFUNG<br />
AUSREICHT!<br />
http://page.math.tu-berlin.de/~scherfne/Bilder/studium1.jpg<br />
937<br />
352
Informatik II<br />
(Studiengang Informationstechnologie und Elektrotechnik)<br />
Vorlesung FS 2013<br />
Friedemann Mattern<br />
Departement Informatik, <strong>ETH</strong> <strong>Zürich</strong><br />
© F. Mattern, 2013<br />
353