12.01.2014 Aufrufe

2-up - ETH Zürich

2-up - ETH Zürich

2-up - ETH Zürich

MEHR ANZEIGEN
WENIGER ANZEIGEN

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

Hurra! Ihre Datei wurde hochgeladen und ist bereit für die Veröffentlichung.

Erfolgreich gespeichert!

Leider ist etwas schief gelaufen!