Algorithmische Grundlagen Sommersemester 2016 Martin Mundhenk Uni Jena, Institut für Informatik 5. Juli 2016 Vorlesung Algorithmische Grundlagen (Sommer 2016) Die Vorlesung/Übung orientiert sich am Buch Introduction to Programming in Python – An Interdisciplinary Approach von Robert Sedgewick, Kevin Wayne und Robert Dondero (Addison Wesley, 2015) Die Webseite zum Buch http://introcs.cs.princeton.edu/python/home/ ist auch für diese Vorlesung nützlich. Organisatorisches http://tinyurl.com/AlgorithmischeGrundlagen § § Vorlesung montags, 16-18 Uhr, Raum 3325 Ernst-Abbe-Platz 2 Übung donnerstags, 8-10 Uhr, Raum 3410 (Linux-Pool 2) EAP2 Zur Teilnahme brauchen Sie eine Nutzerkennung beim FRZ! § Es gibt ein wöchentliches Übungsblatt (ca. 12). Abgabe montags 16:15 Uhr (an [email protected]) § Zur Zulassung zur Modulprüfung müssen jeweils mindestens 50% der Punkte der Übungsblätter aus der ersten und der zweiten Hälfte erreicht sein. Die Abschlussprüfung ist mündlich und dauert ca. 30 Minuten. Prüfungstermine sind 8.8., 12.8. und 7.9. (oder nach Vereinbarung). Bitte tragen Sie sich für einen Termin auf die Prüfungsliste ein! Wer bei der Abschlussprüfung ein Programmierprojekt vorstellen will, muss alle Dateien bis spätestens 30 Stunden vor Beginn der Prüfung an mich schicken ([email protected]). § Inhaltsverzeichnis 1. Elemente des Programmierens 2. Funktionen und Module 4. Algorithmen 5. Abschlussprojekte (und Anmerkungen dazu) Die Nummerierung der Kapitel 1, 2 und 4 entspricht der Nummerierung im Buch von Sedgewick et al. 0.0.4 1 Elemente des Programmierens Ein Programm zu schreiben ist nicht schwieriger, als einen Aufsatz zu schreiben. Wir schauen uns die Grundbausteine von Python-Programmen an und starten mit dem Programmieren. 1. Elemente des Programmierens 1.1 Grundlegende Daten-Typen 1.2 Verzweigungen und Schleifen 1.3 Arrays 1.4 Ein- und Ausgabe 1.5 Page Rank 1.1 Grundlegende Datentypen Typ Werte Operatoren int ganze Zahlen ` ´ ˚ {{ % ˚˚ 2016 -12 11267456 float Dezimalbrüche ` ´ ˚ { ˚˚ 3.14 2.5 6.022e+23 bool Wahrheitswerte and str Zeichenfolgen or ` Beispiele für Literale not True 'AB' False 'Hello world' '2.5' int . . . integer float . . . floating-point number (Fließkommazahl) bool . . . boolean (George Boole, 1815-1864) str . . . string 1.1.1 Grundlegende Begriffe Ein (sehr) einfaches Python-Programm a = 123 b = 99 c = a + b * 2 Das Programm besteht aus drei Anweisungen. Es werden drei Objekte vom Typ int erzeugt. Die Variablen a, b und c werden an sie gebunden. Am Ende ist c an ein Objekt vom Typ int mit Wert 321 gebunden. 123 ist ein Literal, das einen Wert vom Typ int bezeichnet. a + b * 2 ist ein Ausdruck (expression). Der Ausdruck besteht aus Variablen, die an Objekte vom Typ int gebunden sind, einem int-Literal und den Operatoren + und *. Bei der Auswertung des Ausdrucks wird ein (neues) Objekt erzeugt, dessen Wert der Ausdruck beschreibt. 1.1.2 Ausdrücke vom Typ int bestehen aus § Literalen vom Typ int § Variablen, die an Objekte vom Typ int gebunden sind § den Operatoren + - * // % ** § ... + - * stehen für Addition, Subtraktion und Multiplikation. // ist die ganzzahlige Division. 17 // 6 ist 2 (da 17 “ 2 ¨ 6 ` 5), und -17 // 6 ist -3 (da ´17 “ ´3 ¨ 6 ` 1). Mathematische Schreibweise für die ganzzahlige Division: a // b entspricht t ba u txu ist die größte ganze Zahl ď x ( untere Gaußklammer“) ” 1.1.3 % ist der Rest bei der ganzzahligen Division (modulo-Operation). 17 % 6 ist 5 (da 17 “ 2 ¨ 6 ` 5), und -17 % 6 ist 1 (da ´17 “ ´3 ¨ 6 ` 1). Mathematische Schreibweise für a % b ist a mod b . ** ist die Potenz. 3**4 ist 81 (da 34 “ 3 ¨ 3 ¨ 3 ¨ 3 “ 81). Die Operatoren haben unterschiedliche Bindungsstärke (entsprechend Punktrechnung geht vor Strichrechnung“). ” Im Zweifel: Klammern setzen . . . Weitere Beispiele für int-Ausdrücke (mit int-Variablen): Stunden * 60 * 60 + Minuten * 60 + Sekunden (53 - 6) * 40 // LP pro Jahr Jahr - ( ((Monat-3)%12) // 10 ) (49*48*47*46*45*44*43)//(1*2*3*4*5*6) max( a, b ) 1.1.4 Anweisungen assignment statements Eine Anweisung besteht aus einer Variablen, = und einem Ausdruck. Jahrhundertzahl = (Jahr-(((Monat-3)%12)//10))//100 Arbeitsstunden pro LP = (53 - 6) * 40 // LP pro Jahr zaehler = zaehler + 1 Beim Abarbeiten einer Anweisung an eine Variable eines grundlegenden Daten-Typs wird § zuerst der Ausdruck ausgewertet und ein Objekt mit Typ und Wert des Ausdrucks erzeugt, § danach wird die Variable an das Objekt gebunden. 1.1.5 # Berechne das Volumen und die Oberfläche eines Quaders. # Zuerst werden die Maße des Quaders angegeben. Hoehe = 5 Breite = 12 Tiefe = 32 # Daraus können nach den bekannten Formeln # das Volumen und die Oberfläche berechnet werden. Volumen = Hoehe * Breite * Tiefe Oberflaeche = 2 * ( Hoehe*Breite + Hoehe*Tiefe + Breite*Tiefe ) Der Typ str . . . dient dem Verarbeiten von Texten. Literale sind Folgen von Zeichen, die durch ' ... ' eingeschlossen sind. 'Das folgende ist kein int-Literal.' '1234' Die Operation + schreibt Strings hintereinander (Konkatenation). 'Das ist ' + 'ein Satz.' ergibt 'Das ist ein Satz.' . Die Funktion str( ) macht aus einem Objekt einen String. Z.B. liefert str(123) den String '123'. Entsprechend macht die Funktion int( ) aus einem Objekt ein int-Objekt. Z.B. liefert int('123') ein int-Objekt mit Wert 123. 1.1.7 #----------------------------------------------------------------------# ruler.py #----------------------------------------------------------------------import stdio # # # # # Write to standard output the relative lengths of the subdivisions on a ruler. The nth line of output is the relative lengths of the marks on a ruler subdivided in intervals of 1/2^n of an inch. For example, the fourth line of output gives the relative lengths of the marks that indicate intervals of one-sixteenth of an inch on a ruler. ruler1 = '1' ruler2 = ruler1 + ' 2 ' + ruler1 ruler3 = ruler2 + ' 3 ' + ruler2 ruler4 = ruler3 + ' 4 ' + ruler3 stdio.writeln(ruler1) stdio.writeln(ruler2) stdio.writeln(ruler3) stdio.writeln(ruler4) #----------------------------------------------------------------------# python ruler.py # 1 # 1 2 1 # 1 2 1 3 1 2 1 # 1 2 1 3 1 2 1 4 1 2 1 3 1 2 1 1.1.8 #----------------------------------------------------------------------# einundausgabe.py #----------------------------------------------------------------------import stdio import sys # Lies drei Strings aus der Kommandozeile # und gib sie in umgekehrter Reihenfolge wieder aus. a = sys.argv[1] b = sys.argv[2] c = sys.argv[3] stdio.writeln( c + ' ' + b + ' ' + a ) #----------------------------------------------------------------------# python einundausgabe.py Hallo Martin . # . Martin Hallo # # python einundausgabe.py Hallo Martin. # Traceback (most recent call last): # File "einundausgabe.py", line 12, in <module> # c = sys.argv[3] # IndexError: list index out of range 1.1.9 int-Werte zu str-Werten umwandeln #------------------------------------------------------------------------# teilen_v1.py #------------------------------------------------------------------------import stdio a = 15 b = 213 stdio.write( str(b) + ' geteilt durch ' + str(a) + ' ergibt ' ) stdio.writeln( str( b//a ) + ' Rest ' + str( b%a ) + '.' ) #------------------------------------------------------------------------# # python teilen_v1.py # 213 geteilt durch 15 ergibt 14 Rest 3. # 1.1.10 str-Werte zu int-Werten umwandeln #------------------------------------------------------------------------# teilen_v2.py #------------------------------------------------------------------------import stdio import sys # Lies zwei Argumente a und b von der Kommandozeile. # Gib den (ganzzahligen) Quotienten und den Rest bei der Division von a durch b aus. a = int( sys.argv[1] ) b = int( sys.argv[2] ) stdio.write( str(a) + ' geteilt durch ' + str(b) + ' ergibt ' ) stdio.writeln( str( a//b ) + ' Rest ' + str( a%b ) + '.' ) #------------------------------------------------------------------------# python teilen_v2.py 1234 23 # 1234 geteilt durch 23 ergibt 53 Rest 15. # # python teilen_v2.py 1234 0 # File "teilen_v2.py", line 13, in <module> # stdio.writeln( str( a//b ) + ' Rest ' + str( a%b ) + '.' ) # ZeroDivisionError: integer division or modulo by zero 1.1.11 Der Typ float . . . dient dem Rechnen mit Dezimalbrüchen. Literale sind z.B. 4.0 123.45 3.141e+12 (steht für 3, 141 ¨ 1012 ) Die Operationen mit float sind + - * / ** . Kommt in einem Ausdruck ein float-Wert vor, dann hat das Ergebnis Typ float (und nicht Typ int). float-Werte haben nur beschränkte Genauigkeit. stdio.writeln(str(1.23456789012345e+12)) hat Ausgabe 1.23456789012e+14 1.1.12 Die quadratische Gleichung a ¨ x 2 ` b ¨ x ` c “ 0 hat die Lösungen ? ´b ˘ b 2 ´ 4 ¨ a ¨ c x1,2 “ . 2¨a # Das Programm mitternacht.py liest float-Werte a, b und c von der Kommandozeile ein # und gibt die beiden durch die Mitternachtsformel bestimmten Lösungen aus. import stdio import sys import math a = float( sys.argv[1] ) b = float( sys.argv[2] ) c = float( sys.argv[3] ) diskriminante = math.sqrt(b**2 - 4 * a * c) stdio.writeln( (-b + diskriminante) / (2*a) ) stdio.writeln( (-b - diskriminante) / (2*a) ) 1.1.13 # Das Programm mitternacht.py liest float-Werte a, b und c von der Kommandozeile ein # und gibt die beiden durch die Mitternachtsformel bestimmten Lösungen aus. import stdio import sys import math a = float( sys.argv[1] ) b = float( sys.argv[2] ) c = float( sys.argv[3] ) diskriminante = math.sqrt(b**2 - 4 * a * c) stdio.writeln( (-b + diskriminante) / (2*a) ) stdio.writeln( (-b - diskriminante) / (2*a) ) #-----------------------------------------------------------------------------------# python mitternacht.py 4 6 2 # -0.5 # -1.0 # python mitternacht.py 1 2 3 # Traceback (most recent call last): # File "mitternacht.py", line 17, in <module> # diskriminante = math.sqrt(b**2 - 4 * a * c) # ValueError: math domain error 1.1.14 float-Werte können ungenau sein #----------------------------------------------------------------------# fehler_mit_float.py #----------------------------------------------------------------------import stdio # 10^16 +1 wird als int-Literal dargestellt, # und 10^16 +1 wird als float-Wert dargestellt. int_zahl = 10000000000000001 float_zahl = 10.0**16 +1 # Beide Zahlen werden ausgegeben. stdio.writeln('int Zahl: ' + str(int_zahl)) stdio.writeln('float Zahl: ' + str(float_zahl)) stdio.writeln('float Zahl als int: ' + str(int(float_zahl))) #----------------------------------------------------------------------# python fehler_mit_float.py # int Zahl: 10000000000000001 # float Zahl: 1e+16 # float Zahl als int: 10000000000000000 1.1.15 Bemerkungen zu Funktionen Eingebaute“ Funktionen: ” str( ), int( ), float( ), bool( ), abs( ) (der Betrag einer Zahl), round( ) (nächster int-Wert), max( , ), min( , ) (Maximum bzw. Minimum zweier Zahlen) . . . Standard-Funktionen aus Standard-Modulen von Python: math.sqrt( ), math.log( , ), random.random() (eine zufällige float-Zahl x mit 0 ď x ă 1), random.randrange(x,y) (eine zufällige int-Zahl aus rx, y q), . . . Funktionen aus den Modulen für das Buch: stdio.write( ), stdio.writeln( ), . . . 1.1.16 Der Typ bool . . . dient dem Rechnen mit Wahrheitswerten True und False. Es gibt die Operation and, or und not auf bool-Objekten. a b False False False True True False True True a and b False False False True a or b False True True True a False True not a True False 1.1.17 Vergleichsoperationen liefern bool-Werte Operator == != < <= > >= Bedeutung Beispiel True False gleich 3==3 2==3 ungleich 2!=3 3!=3 kleiner als 2<3 3<3 kleiner oder gleich 3<=3 4<=3 größer als 4>3 3>4 größer oder gleich 4>=3 3>=4 1.1.18 #----------------------------------------------------------------------# leapyear.py #----------------------------------------------------------------------import stdio import sys # Accept an int year as a command-line argument. Write True to # standard output if year is a leap year. Otherwise write False. year = int(sys.argv[1]) isLeapYear = (year % 4 == 0) isLeapYear = isLeapYear and (year % 100 != 0) isLeapYear = isLeapYear or (year % 400 == 0) stdio.writeln(isLeapYear) #----------------------------------------------------------------------# python leapyear.py 2016 # True # python leapyear.py 1900 # False # python leapyear.py 2000 # True 1.1.19 #----------------------------------------------------------------------# tippspiel.py #----------------------------------------------------------------------import stdio import sys import random # # # # Spieler 1 gibt sein Zahl über die Kommandozeile ein. Anschließend würfelt Spieler 2 (das Programm) eine Zahl aus dem Bereich 1,2,3,4,5,6. Falls beide Spieler eine ungerade Zahl haben oder beide Spieler eine gerade Zahl haben, dann gewinnt Spieler 1. Sonst gewinnt Spieler 2. zahl_von_spieler_1 = int(sys.argv[1]) zahl_von_spieler_2 = random.randrange(1,7) stdio.write('Spieler 1 hat ' + str(zahl_von_spieler_1) + ' gewaehlt und ') stdio.writeln('Spieler 2 hat ' + str(zahl_von_spieler_2) + ' gewuerfelt.') ergebnis = ( (zahl_von_spieler_1 % 2 ) == (zahl_von_spieler_2 % 2) ) stdio.writeln('Spieler 1 gewinnt ist ' + str(ergebnis) + '.') #-------------------------------------------------------------------------# python tippspiel.py 3 # Spieler 1 hat 3 gewaehlt und Spieler 2 hat 3 gewuerfelt. # Spieler 1 gewinnt ist True. 1.1.20 Zusammenfassung § Wir haben die elementaren Daten-Typen int, float, str und bool von Python kennengelernt. § Wir können Ausdrücke aus Literalen, Variablen, Operatoren und Funktionen schreiben. § Wir können sehr einfache Programme mit Eingabe von Argumenten Abarbeitung einer festen Folge von Anweisungen Ausgabe eines Ergebnisses schreiben und deren Ausführung nachvollziehen. 1.1.21 1.2 Verzweigungen und Schleifen Die bisher betrachteten Programme bestanden aus Anweisungen, die der Reihe nach von der ersten bis zur letzten ausgeführt werden. Nun werden wir sehen, wie man diese Reihenfolge beeinflussen kann. Abhängig von Bedingungen können Anweisungs-Blöcke ausgeführt oder nicht ausgeführt werden (Verzweigungen), oder Anweisungs-Blöcke können mehrfach ausgeführt werden (Schleifen). Mittels Verzweigungen und Schleifen werden wir bereits viel mächtigere Programme schreiben können als bisher. 1.2.1 if-Anweisungen Viele Programme sollen bei verschiedenen Eingaben unterschiedlich arbeiten. Z.B. könnte das Programm teilen v2.py, das bei Eingabe zweier Werte a und b den Quotient und den Rest beim Teilen von a durch b berechnet, entweder 'Durch Null kann man nicht teilen!' ausgeben (falls b “ 0 ist) oder die gesuchten Werte ausgeben (falls b ‰ 0 ist). 1.2.2 Teilbarkeit überprüfen #------------------------------------------------------------------------# teilen_v3.py #------------------------------------------------------------------------import stdio, sys # Lies zwei Argumente a und b von der Kommandozeile. # Gib den (ganzzahligen) Quotienten und den Rest bei der Division von a durch b aus. a = int( sys.argv[1] ) b = int( sys.argv[2] ) if b == 0: stdio.writeln(’Durch Null darf man nicht teilen!’) else: stdio.write( str(a) + ’ geteilt durch ’ + str(b) + ’ ergibt ’ ) stdio.writeln( str( a//b ) + ’ Rest ’ + str( a%b ) + ’.’ ) #------------------------------------------------------------------------# python teilen_v3.py 1234 23 # 1234 geteilt durch 23 ergibt 53 Rest 15. # # python teilen_v3.py 1234 0 # Durch Null darf man nicht teilen! 1.2.3 Geschachtelte if-Anweisungen Bei der Lösung der quadratischen Gleichung a ¨ x 2 ` b ¨ x ` c “ 0 mittels der Formel ? ´b ˘ b 2 ´ 4 ¨ a ¨ c x1,2 “ 2¨a kann es 1. keine Lösung geben, da b 2 ´ 4 ¨ a ¨ c ă 0 oder a “ 0, 2. eine Lösung geben, da b 2 ´ 4 ¨ a ¨ c “ 0 und a ‰ 0, oder 3. zwei Lösungen geben, da b 2 ´ 4 ¨ a ¨ c ą 0 und a ‰ 0. 1.2.4 # Das Programm midnight.py liest float-Werte a, b und c von der Kommandozeile ein # und gibt die beiden durch die Mitternachtsformel bestimmten Lösungen aus, falls sie import stdio, sys, math a = float( sys.agv[1] ) b = float( sys.argv[2] ) c = float( sys.argv[3] ) z = b**2 - 4 * a * c if z<0 or a==0: stdio.writeln('Die Gleichung hat keine Loesung!') else: if z==0: stdio.writeln( -b / (2*a) ) else: diskriminante = math.sqrt(b**2 - 4 * a * c) stdio.writeln( (-b + diskriminante) / (2*a) ) stdio.writeln( (-b - diskriminante) / (2*a) ) #-------------------------------------#-------------------------------------# python midnight.py 3 4 5 # python midnight.py 2 4 2 # Die Gleichung hat keine Loesung! # -1.0 # # # python midnight.py 3 8 5 # python midnight.py 0 8 5 # -1.0 # Die Gleichung hat keine Loesung! # -1.66666666667 # 1.2.5 # Das Programm midnight.py liest float-Werte a, b und c von der Kommandozeile ein # und gibt die beiden durch die Mitternachtsformel bestimmten Lösungen aus, falls sie import stdio, sys, math a = float( sys.agv[1] ) b = float( sys.argv[2] ) c = float( sys.argv[3] ) z = b**2 - 4 * a * c if z<0 or a==0: stdio.writeln('Die Gleichung hat keine Loesung!') elif z==0: stdio.writeln( -b / (2*a) ) else: diskriminante = math.sqrt(b**2 - 4 * a * c) stdio.writeln( (-b + diskriminante) / (2*a) ) stdio.writeln( (-b - diskriminante) / (2*a) ) #-------------------------------------#-------------------------------------# python midnight.py 3 4 5 # python midnight.py 2 4 2 # Die Gleichung hat keine Loesung! # -1.0 # # # python midnight.py 3 8 5 # python midnight.py 0 8 5 # -1.0 # Die Gleichung hat keine Loesung! # -1.66666666667 # # Das Programm midnight.py liest float-Werte a, b und c von der Kommandozeile ein # und gibt die beiden durch die Mitternachtsformel bestimmten Lösungen aus, # falls sie existieren. import stdio, sys, math a = float( sys.argv[1] ) b = float( sys.argv[2] ) c = float( sys.argv[3] ) z = b**2 - 4 * a * c if z<0 or a==0: stdio.writeln('Die Gleichung hat keine Loesung!') else: diskriminante = math.sqrt(z) if diskrimante > 0: stdio.writeln( (-b + diskriminante) / (2*a) ) stdio.writeln( (-b - diskriminante) / (2*a) ) #-------------------------------------#-------------------------------------# python midnight.py 3 4 5 # python midnight.py 2 4 2 # Die Gleichung hat keine Loesung! # -1.0 # # # python midnight.py 3 8 5 # python midnight.py 0 8 5 # -1.0 # Die Gleichung hat keine Loesung! # -1.66666666667 # # Das Programm midnight.py liest float-Werte a, b und c von der Kommandozeile ein # und gibt die beiden durch die Mitternachtsformel bestimmten Lösungen aus, # falls sie existieren. import stdio, sys, math a = float( sys.argv[1] ) b = float( sys.argv[2] ) c = float( sys.argv[3] ) z = b**2 - 4 * a * c if z<0 or a==0: stdio.writeln('Die Gleichung hat keine Loesung!') else: diskriminante = math.sqrt(z) if diskrimante > 0: stdio.writeln( (-b + diskriminante) / (2*a) ) stdio.writeln( (-b - diskriminante) / (2*a) ) #-------------------------------------#-------------------------------------# python midnight.py 3 4 5 # python midnight.py 2 4 2 # Die Gleichung hat keine Loesung! # -1.0 # # # python midnight.py 3 8 5 # python midnight.py 0 8 5 # -1.0 # Die Gleichung hat keine Loesung! # -1.66666666667 # while-Schleifen Üblicherweise bestehen Programme aus Anweisungen, die häufig wiederholt werden. Die while-Schleife erlaubt die Wiederholung eines Anweisungs-Blocks, solange eine vorgegebene Bedingung erfüllt ist. 1.2.9 # Das Programm while-zaehler.py liest int-Wert a von der Kommandozeile ein # und gibt die Zahlen von 0 bis a aus. import stdio, sys a = int( sys.argv[1] ) zaehler = 0 while zaehler <= a: stdio.writeln(str(zaehler)) zaehler = zaehler + 1 #----------------------------# python while-zaehler.py 4 # 0 # 1 # 2 # 3 # 4 # 5 #----------------------------# python while-zaehler.py 9 # 0 # 1 # 2 # 3 # 4 # 5 # 6 # 7 # 8 # 9 1.2.10 # Das Programm alleteiler.py liest int-Wert a von der Kommandozeile ein # und gibt alle Teiler von a aus. import stdio, sys a = int( sys.argv[1] ) zaehler = 1 while zaehler <= a: if a%zaehler == 0: stdio.writeln(str(zaehler)) zaehler = zaehler + 1 #-----------------------------------------------------------------------------------# python alleteiler.py 8 # 1 # 2 # 4 # 8 1.2.11 #-----------------------------------------------------------# while_hochzaehlen.py # Gibt * mit größer werdendem Abstand zum linken Rand aus. #-----------------------------------------------------------import stdio n =12 i=0 while i<n: stdio.writeln(' '*i + '*') i = i+1 #-----------------------------------------------------------# python while_hochzaehlen.py # * # * # * # * # * # * # * # * # * # * # * # * 1.2.12 #------------------------------------------------------# zufallspfad.py # Gibt wiederholt * aus, dessen Abstand zum linken # Rand von Zeile zu Zeile # zufällig um -1,0,+1 verändert wird. #------------------------------------------------------import stdio, random i=0 abstand = 5 while i<20: abstand = abstand + random.randrange(-1,2) stdio.writeln(' '*abstand + '*') i = i+1 #-------------------------------------------------------- # python zufallspfad.py # * # * # * # * # * # * # * # * # * # * # * # * # * # * # * # * # * # * # Das Programm ganzlog.py liest int-Wert n von der Kommandozeile ein # und gibt den ganzzahligen Logarithmus von n zur Basis 2 aus. import stdio, sys n = int( sys.argv[1] ) ergebnis = 0 while n > 1: ergebnis = ergebnis + 1 n = n//2 stdio.writeln(str(ergebnis)) #-----------------------------------------------------------------------------------# python ganzlog.py 2016 # 10 # # python ganzlog.py 2048 # 11 # # python ganzlog.py 2047 # 10 # 1.2.14 # Das Programm ganzlog-trace.py liest int-Wert n von der Kommandozeile ein # und gibt den ganzzahligen Logarithmus von n zur Basis 2 aus. import stdio, sys n = int( sys.argv[1] ) ergebnis = 0 while n > 1: stdio.writeln('n ist ' + str(n) + ', ergebnis ist ' + str(ergebnis)) ergebnis = ergebnis + 1 n = n//2 stdio.writeln(str(ergebnis)) #-----------------------------------------------------------------------------------# python ganzlog-trace.py 538 n ist 538, ergebnis ist 0 n ist 269, ergebnis ist 1 n ist 134, ergebnis ist 2 n ist 67, ergebnis ist 3 n ist 33, ergebnis ist 4 n ist 16, ergebnis ist 5 n ist 8, ergebnis ist 6 n ist 4, ergebnis ist 7 n ist 2, ergebnis ist 8 9 1.2.15 # Das Programm zweierpotenz-w.py liest int-Werte n von der Kommandozeile ein # und gibt die 2erPotenzen 1,2,...,2^n aus. # falls sie existieren. import stdio, sys n = int( sys.argv[1] ) i = 0 potenz = 1 # der Zaehler fuer die Potenzen # die erste 2erPotenz ist 2^0 while i <= n: stdio.writeln(' 2 hoch ' + str(i) + ' ist ' + str(potenz) + '.') i = i + 1 potenz = potenz * 2 #--------------------------------------------------------------------------------# python zweierpotenz-w.py 5 # 2 hoch 0 ist 1. # 2 hoch 1 ist 2. # 2 hoch 2 ist 4. # 2 hoch 3 ist 8. # 2 hoch 4 ist 16. # 2 hoch 5 ist 32. 1.2.16 for-Schleifen Häufig wird in einer Schleife u.a. eine Variable hochgezählt und die Schleifen-Wiederholung endet, wenn ein bestimmter Wert erreicht wurde. Die for-Schleife erleichtert das Aufschreiben solcher Schleifen. 1.2.17 # Das Programm zweierpotenz-f.py liest int-Werte a von der Kommandozeile ein # und gibt die 2erPotenzen von 2^0, 2^1,...,2^n aus. import stdio, sys n = int( sys.argv[1] ) potenz = 1 # die erste 2erPotenz ist 2^0 for i in range(0,n+1): stdio.writeln(' 2 hoch ' + str(i) + ' ist ' + str(potenz) + '.') potenz = potenz * 2 #--------------------------------------------------------------------------------# python zweierpotenz-f.py 6 # 2 hoch 0 ist 1. # 2 hoch 1 ist 2. # 2 hoch 2 ist 4. # 2 hoch 3 ist 8. # 2 hoch 4 ist 16. # 2 hoch 5 ist 32. # 2 hoch 5 ist 64. 1.2.18 Simulation von Zufallsereignissen Zwei Würfel werden 24mal geworfen. Wie wahrscheinlich ist es, dass mindestens einmal dabei zwei 6en geworfen werden? Statt die Wahrscheinlichkeit exakt auszurechnen, simulieren wir das Experiment mit einem Programm. 1.2.19 #----------------------------------------------------------# zweiwuerfel.py #----------------------------------------------------------import stdio, sys, random erfolgszaehler = 0 for i in range(0,100000): # Zwei Würfel werden 24mal geworfen. # Das Spiel ist erfolgreich, wenn dabei ein Wurf aus zwei 6en gelingt. for i in range(24): if random.randrange(1,7)==6 and random.randrange(1,7)==6: erfolgszaehler +=1 break # Auswertung stdio.writeln(float(erfolg)/versuchszahl) #-----------------------------------------------------------# python zweiwuerfel.py # python zweiwuerfel.py # 0.49203 # 0.49133 # # # python zweiwuerfel.py # python zweiwuerfel.py # 0.49068 # 0.49023 # 17 und 4 Wir spielen 17+4 mit Würfeln. Zwei Spieler spielen. Jeder würfelt, ohne dass es der andere sehen kann. Ziel des Spiels ist es, den Wert 21 zu erreichen. Dazu würfelt der Spieler mit seinem Würfel und summiert die gewürfelten Zahlen. Man kann jederzeit aufhören zu würfeln. Das Ergebnis des Spielers ist seine Summe der gewürfelten Zahlen. Ein Spieler mit einem Ergebnis ą 21 hat verloren. Haben beide Spieler ein Ergebnis ď 21, dann gewinnt der Spieler mit dem höheren Ergebnis. Wir wollen ein Programm entwickeln, mit dem man eine gute Strategie für das Spiel finden kann. Zuerst schreiben wir ein Programm, das einen Spieler simuliert, der solange würfelt, bis er mindestens 21 erreicht hat. #----------------------------------------------------------------------------------# siebzehnundvier_1.py #----------------------------------------------------------------------------------import stdio, sys, random Wuerfelsumme = 0 while Wuerfelsumme<21: Wuerfelsumme = Wuerfelsumme + random.randrange(1,7) stdio.writeln('Wuerfelsumme ist ' + str(Wuerfelsumme)) stdio.writeln('Ergebnis: ' + str(Wuerfelsumme)) #-----------------------------------------------------------------------------------# python siebzehnundvier_1.py # Wuerfelsumme ist 5 # Wuerfelsumme ist 8 # Wuerfelsumme ist 10 # Wuerfelsumme ist 11 # Wuerfelsumme ist 15 # Wuerfelsumme ist 20 # Wuerfelsumme ist 21 # Ergebnis: 21 Eine Strategie ist es, nicht weiterzuwürfeln, wenn man 16 oder mehr erreicht hat. Dann hat man garantiert ein Ergebnis ď 21. #----------------------------------------------------------------------------------# siebzehnundvier_2.py #----------------------------------------------------------------------------------import stdio, sys, random Wuerfelsumme = 0 while Wuerfelsumme<=15: Wuerfelsumme = Wuerfelsumme + random.randrange(1,7) stdio.writeln('Wuerfelsumme ist ' + str(Wuerfelsumme)) stdio.writeln('Ergebnis: ' + str(Wuerfelsumme)) #-----------------------------------------------------------------------------------# python siebzehnundvier_2.py # Wuerfelsumme ist 6 # Wuerfelsumme ist 8 # Wuerfelsumme ist 10 # Wuerfelsumme ist 12 # Wuerfelsumme ist 16 1.2.23 Eine andere Strategie ist es, nicht weiterzuwürfeln, wenn man 17 oder mehr erreicht. Wenn man 16 erreicht hat, geht man ein kleines Risiko ein . . . #----------------------------------------------------------------------------------# siebzehnundvier_3.py #----------------------------------------------------------------------------------import stdio, sys, random Wuerfelsumme = 0 while Wuerfelsumme<=16: Wuerfelsumme = Wuerfelsumme + random.randrange(1,7) stdio.writeln('Wuerfelsumme ist ' + str(Wuerfelsumme)) stdio.writeln('Ergebnis: ' + str(Wuerfelsumme)) #-----------------------------------------------------------------------------------# python siebzehnundvier_3.py # Wuerfelsumme ist 2 # Wuerfelsumme ist 4 # Wuerfelsumme ist 7 # Wuerfelsumme ist 10 # Wuerfelsumme ist 14 # Wuerfelsumme ist 16 # Wuerfelsumme ist 22 # Ergebnis: 22 1.2.24 Welche der beiden Strategien ist besser? Wir wollen das experimentell überprüfen. Dafür lassen wir zwei Spieler mit den beiden Strategien gegeneinander spielen und schauen nach, wer gewinnt. #----------------------------------------------------------------------------------# siebzehnundvier_4.py #----------------------------------------------------------------------------------import stdio, sys, random # Spieler 1 spielt mit seiner Strategie. Wuerfelsumme1 = 0 while Wuerfelsumme1<=15: Wuerfelsumme1 = Wuerfelsumme1 + random.randrange(1,7) # Spieler 2 spielt mit seiner Strategie. Wuerfelsumme2 = 0 while Wuerfelsumme2<=16: Wuerfelsumme2 = Wuerfelsumme2 + random.randrange(1,7) # Der Gewinner wird bestimmt. if Wuerfelsumme1<=21 and ( Wuerfelsumme2>21 or Wuerfelsumme1>Wuerfelsumme2): stdio.writeln('Spieler 1 gewinnt.') elif Wuerfelsumme2<=21 and ( Wuerfelsumme1>21 or Wuerfelsumme2>Wuerfelsumme1): stdio.writeln('Spieler 2 gewinnt.') 1.2.25 Schließlich lassen wir die Spieler 100000mal gegeneinander spielen, und zählen, wie oft jeder gewinnt. 1.2.26 # Wir benutzen zwei Zähler für die Gewinne jedes Spielers. gewinne1 = 0 gewinne2 = 0 # Wir lassen die beiden Spieler 1000000 Spiele spielen. for i in range(100000): # Spieler 1 spielt mit seiner Strategie. Wuerfelsumme1 = 0 while Wuerfelsumme1<=15: Wuerfelsumme1 = Wuerfelsumme1 + random.randrange(1,7) # Spieler 2 spielt mit seiner Strategie. Wuerfelsumme2 = 0 while Wuerfelsumme2<=16: Wuerfelsumme2 = Wuerfelsumme2 + random.randrange(1,7) # Der Gewinner wird bestimmt und sein Gewinn-Zähler wird um 1 erhöht. if Wuerfelsumme1<=21 and ( Wuerfelsumme2>21 or Wuerfelsumme1>Wuerfelsumme2): gewinne1 = gewinne1 + 1 elif Wuerfelsumme2<=21 and ( Wuerfelsumme1>21 or Wuerfelsumme2>Wuerfelsumme1): gewinne2 = gewinne2 + 1 # Gib aus, wer öfter gewonnen hat. if gewinne1>gewinne2: stdio.writeln('Spieler 1 hat ' + str(gewinne1-gewinne2) + 'mal oefter gewonnen als Spieler 2.') else: stdio.writeln('Spieler 2 hat ' + str(gewinne2-gewinne1) + 'mal oefter gewonnen als Spieler 1.') #----------------------------------------------------------------------------------------------# python siebzehnundvier_5.py # Spieler 2 hat 26584mal oefter gewonnen als Spieler 1. 1.2.27 Zusammenfassung § Wir haben if-Anweisungen, while-Schleifen und for-Schleifen kennengelernt. § Wir kennen die Strukturierung von Programmen in Anweisungs-Blöcke. § Wir können einfache Programme mit Eingabe von Argumenten Abarbeitung von Anweisungs-Blöcken, die in Schleifen wiederholt oder durch if-Anweisungen ausgeführt oder übersprungen werden Ausgabe von Ergebnissen schreiben und deren Ausführung nachvollziehen. 1.3 Arrays Die bisher benutzten Daten waren recht einfach – Zahlen, Texte oder Wahrheitswerte. Eine Datenstruktur dient der Organisation von Daten zur Verarbeitung mit einem Computer-Programm. Eine einfache Datenstruktur sind Arrays, mit denen man z.B. beliebig lange Folgen von Zahlen verarbeiten kann. Das erlaubt uns Programme zu schreiben, die viel größere Mengen von Daten verarbeiten als bisher. 1.3.1 Beispiel für ein eindimensionales Array Die einfachste Vorstellung eines Arrays ist eine Zuordnung von Werten zu Indizes. a = [’Gallia’, ’omnis’, ’div’, ’est’] erzeugt ein Array a[] mit Index zugeordneter Wert 0 1 2 3 ’Gallia’ ’omnis’ ’div’ ’est’ a[2] hat den String ’div’ als Wert. a[3] = a[2] + ’isa’ ändert den Wert von a[3] zu ’divisa’ Array a[] Index zugeordneter Wert 0 1 2 3 ’Gallia’ ’omnis’ ’div’ ’divisa’ Die Länge von a[] ist 4 und ist das Ergebnis von len(a). 1.3.2 Beispiel für ein zweidimensionales Array a = [ [’b’, ’b’, ’c’], [’d’, ’e’, ’f’, ’g’], [’h’, 1], [2]] erster Index zweiter Index Wert 0 1 2 3 0 1 2 0 1 2 3 0 1 0 ’b’ ’b’ ’c’ ’d’ ’e’ ’f’ ’g’ ’h’ 1 2 a[1][2] hat den str-Wert ’f’. Die Länge des zweidimensionalen Arrays a[][] ist 4, d.h. len(a) hat den Wert 4. a[0][] hat das Array [’b’, ’b’, ’c’] als Wert. Die Länge von a[0][] ist 3, d.h. len(a[0]) hat den Wert 3. 1.3.3 Berechnung des Durchschnitts der Werte eines Arrays #---------------------------------------------------------# durchschnitt.py #---------------------------------------------------------import stdio # Berechne den Durchschnitt der Werte, # die im Array werte[] gespeichert sind. werte = [17, 4, 23, 999, 46, 24, 1, 1, 13, 18] summe = 0 for i in range(len(werte)): stdio.writeln('i: ' + str(i) + ' summe = summe + werte[i] #-----------------------# python durchschnitt.py # i: 0 summe: 0 # i: 1 summe: 17 # i: 2 summe: 21 # i: 3 summe: 44 # i: 4 summe: 1043 # i: 5 summe: 1089 # i: 6 summe: 1113 # i: 7 summe: 1114 # i: 8 summe: 1115 # i: 9 summe: 1128 # 114.6 summe: ' + str(summe)) stdio.writeln(str(float(summe)/len(werte))) werte[] ist ein Array der Länge 10. Die Elemente von werte sind mit werte[0], werte[1],werte[2],. . . , werte[9] ansprechbar. len(werte) ist die Länge des Arrays werte. Man kann das auch kompakter aufschreiben #---------------------------------------------------------# durchschnitt.py #---------------------------------------------------------import stdio # Berechne den Durchschnitt der Werte, # die im Array werte[] gespeichert sind. werte = [17, 4, 23, 999, 46, 24, 1, 1, 13, 18] summe = 0 for i in werte: stdio.writeln('i: ' + str(i) + ' summe += i summe: ' + str(summe)) stdio.writeln(str(float(summe)/len(werte))) #-----------------------# python durchschnitt.py # i: 17 summe: 1146 # i: 4 summe: 1163 # i: 23 summe: 1167 # i: 999 summe: 1190 # i: 46 summe: 2189 # i: 24 summe: 2235 # i: 1 summe: 2259 # i: 1 summe: 2260 # i: 13 summe: 2261 # i: 18 summe: 2274 # 114.6 Das Gleiche für ein zweidimensionales Array . . . #-------------------------------------------------------# ddurchschnitt.py # Berechnet den Durchschnitt aller Werte # eines 2-dimensionalen Arrays. #-------------------------------------------------------import stdio werte = [ [17, 4], [23, 999, 46], [24, 1, 1, 13], [18] ] summe = 0 anzahl = 0 for z in range(len(werte)): for s in range(len(werte[z])): stdio.writeln('z: ' + str(z) + ' s: ' + str(s) + ' summe: ' + str(summe)) summe = summe + werte[z][s] anzahl = anzahl + 1 stdio.writeln(str(float(summe)/anzahl)) #------------------------# python ddurchschnitt.py # z: 0 s: 0 summe: 0 # z: 0 s: 1 summe: 17 # z: 1 s: 0 summe: 21 # z: 1 s: 1 summe: 44 # z: 1 s: 2 summe: 1043 # z: 2 s: 0 summe: 1089 # z: 2 s: 1 summe: 1113 # z: 2 s: 2 summe: 1114 # z: 2 s: 3 summe: 1115 # z: 3 s: 0 summe: 1128 # 114.6 . . . und nochmal kompakter aufgeschrieben #--------------------------------------------------------#--------------------------# ddurchschnitt-k.py # python ddurchschnitt-k.py # Berechnet den Durchschnitt der durchschnittlichen # [17, 4] 0 # Zeilensummen eines 2-dimensionalen Arrays. # s: 17 zeilens.: 0 #--------------------------------------------------------# s: 4 zeilens.: 17 import stdio # [23, 999, 46] 10.5 # s: 23 zeilens.: 0 werte = [ [17, 4], [23, 999, 46], [24, 1, 1, 13], [18] ] # s: 999 zeilens.: 23 # s: 46 zeilen.: 1022 durchschnittsumme = 0 # [24, 1, 1, 13] 366.5 # s: 24 zeilens.: 0 for z in werte: # s: 1 zeilens.: 24 stdio.writeln(str(z) + ' ' + str(durchschnittsumme)) # s: 1 zeilens.: 25 zeilensumme = 0 # s: 13 zeilens.: 26 for s in z: # [18] 376.25 stdio.writeln(' ' + ' s: ' + str(s) + # s: 18 zeilens.: 0 ' zeilens.: ' + str(zeilensumme)) # 98.5625 zeilensumme += s durchschnittsumme += float(zeilensumme)/len(z) stdio.writeln(str(durchschnittsumme/len(werte))) #-------------------------------------------------------# array-kopieren.py #-------------------------------------------------------import stdio, stdarray a = [1, 2, 3, 4, 5] # Kopiere Array a ins Array b. # Dazu wird zuerst ein Array der Länge 0 erzeugt. b = [] for i in range(len(a)): stdio.writeln('Laenge von b: ' + str(len(b))) b += [a[i]] # hänge Wert a[i] an das Array b an #-------------------------# python array-kopieren.py # Laenge von b: 0 # Laenge von b: 1 # Laenge von b: 2 # Laenge von b: 3 # Laenge von b: 4 # [1, 2, 3, 4, 5] # Laenge von c: 5 # Laenge von c: 5 # Laenge von c: 5 # Laenge von c: 5 # Laenge von c: 5 # [1, 2, 3, 4, 5] stdio.writeln(b) # Kopiere Array a ins Array c. # Dazu wird zuerst ein Array der Länge len(a) erzeugt. c = stdarray.create1D(len(a),0) for i in range(len(a)): stdio.writeln('Laenge von c: ' + str(len(c))) c[i] = a[i] stdio.writeln(c) 1.3.8 Lesen vieler Eingaben von der Konsole #-------------------------------------------------------------------------# durchschnitt-eingabe.py #-------------------------------------------------------------------------import stdio, sys # Berechne den Durchschnitt der Werte, die über die Konsole eingegeben werden. # Sie stehen im Array sys.argv[] unter den Indizes 1,2,...,len(sys.arg)-1 summe = 0 for i in range(1,len(sys.argv)): summe = summe + int(sys.argv[i]) # for i in sys.argv[1:] # summe = summe + int(i) stdio.writeln(str(float(summe)/(len(sys.argv)-1))) #-------------------------------------------------------------------------# python durchschnitt-eingabe.py 1 2 3 4 5 6 7 8 9 10 # 5.5 1.3.9 # Es wird ausprobiert, wielange man mit einem Würfel mit n Zahlen # würfeln muss, bis man alle n Zahlen gewürfelt hat. n = int(sys.argv[1]) # gewuerfelt ist ein Array mit n Einträgen. # Eintrag i gibt an, ob Zahl i bereits gewuerfelt wurde. gewuerfelt = stdarray.create1D(n,False) runden = 100000 wurfzaehler = 0 # Das Experiment wird runden mal ausgeführt. for i in range(runden): # Solange nicht jede Zahl einmal gewürfelt wurde, wird weitergewürfelt. while not min(gewuerfelt): wurf = random.randrange(0,n) wurfzaehler += 1 gewuerfelt[wurf] = True # Nachdem alle Zahlen gewürfelt wurden, wird alles wieder zurückgesetzt. for i in range(len(gewuerfelt)): gewuerfelt[i] = False stdio.writeln('Um ' + str(n) + ' Zahlen zu wuerfeln braucht man im Mittel ' + str(wurfzaehler/runden+1) + ' Wuerfe.') 1.3.10 # Es wird ausprobiert, wie oft man mit einem Würfel mit n Zahlen würfeln muss, # bis man eine Zahl zum zweiten Mal wirft. (Geburtstagsparadoxon) n = int(sys.argv[1]) gewuerfelt = stdarray.create1D(n,False) wurfzaehler = 0 # Der Versuch wird runden mal wiederholt. runden = 10000 for i in range(runden): # Es werden Zahlen aus 0..n-1 gewuerfelt. # Sobald eine Zahl zum zweiten Mal gewürfelt wird, ist das Experiment beendet. for j in range(n): gewuerfelt[j] = False runde_beenden = False while not runde_beenden: z = random.randrange(0,n) wurfzaehler += 1 if gewuerfelt[z]: runde_beenden = True else: gewuerfelt[z] = True stdio.writeln('Um mit einem ' + str(n) + '-Wuerfel eine Zahl doppelt zu wuerfeln, ' +'braucht man im Mittel ' + str(float(wurfzaehler)/runden) + ' Versuche.') #---------------------------------------------------------------------------------------------# python geburtstagsparadoxon.py 6 # Um mit einem 6-Wuerfel eine Zahl doppelt zu wuerfeln, braucht man im Mittel 3.759 Versuche. # # python geburtstagsparadoxon.py 365 # Um mit einem 365-Wuerfel eine Zahl doppelt zu wuerfeln, braucht man im Mittel 24.5187 Versuche. 1.3.11 #------------------------------------------------------------------------# minsort.py # Sortieren mittels Minimum-Suche. # Die zu sortierenden Strings werden von der Kommandozeile gelesen. #------------------------------------------------------------------------import stdio, sys m = sys.argv[1:] for i in range(len(m)-1): # Bestimme den Index des kleinsten Elements im Indexbereich i..len(m) min = i for j in range(i+1,len(m)): if m[min] > m[j]: min = j # Tausche das kleinste Element im Indexbereich i..len(m) # mit dem Element mit Index i. t = m[min] m[min] = m[i] m[i] = t stdio.writeln(m) #--------------------------------------------------------------------------# python minsort.py Gallia est omnis divisa in partes tres . # ['.', 'Gallia', 'divisa', 'est', 'in', 'omnis', 'partes', 'tres'] 1.3.12 #--------------------------------------------------------------# magicsquare.py # Überprüfe, ob das eigegebene Zahlenquadrat ein # magisches Quadrat ist. # Dazu müssen alle Zeilen, Spalten und Diagonalen # die gleiche Summe haben. #--------------------------------------------------------------import stdio, sys ms = [ [16, 3, 2, 13], [5, 10, 11, 9], [9, 6, 7, 12], [4, 15, 14, 1] ] # Prüfe, ob das Array ein Quadrat ist. laenge = len(ms) for s in range(laenge): if len(ms[s]) != laenge: stdio.writeln('Das ist kein Quadrat!') # Berechne die Mastersumme: das ist die Summe der ersten Zeile. mastersumme = 0 for s in range(laenge): mastersumme += ms[0][s] # Vergleiche sie mit den Summen der anderen Zeilen. for z in range(1,laenge): zeilensumme = 0 for s in range(len(ms[z])): zeilensumme += ms[z][s] if zeilensumme != mastersumme: stdio.writeln('Zeile ' + str(z) + ' stimmt nicht!') 1.3.13 # Vergleiche die Mastersumme mit den Summen der Spalten. for s in range(0,laenge): spaltensumme = 0 for z in range(laenge): spaltensumme += ms[z][s] if spaltensumme != mastersumme: stdio.writeln('Spalte ' + str(s) + ' stimmt nicht!') # Vergleiche die Mastersumme mit den Summen der Diagonalen. diagonalsumme1 = 0 diagonalsumme2 = 0 for d in range(0,laenge): diagonalsumme1 += ms[d][d] diagonalsumme2 += ms[d][laenge-1-d] if diagonalsumme1 != mastersumme or diagonalsumme2 != mastersumme: stdio.writeln('Eine Diagonale stimmt nicht!') #--------------------------------------------------------------------------------------# python magicsquare.py # Zeile 1 stimmt nicht! # Spalte 3 stimmt nicht! 1.3.14 Zusammenfassung Wir wissen grundlegend, wie man Arrays erzeugt und auf ihre Elemente zugreift. Erzeugen von Arrays: [1, 2, 3, [4, 5], 6] Angabe des gesamten Arrays b += [15] Anhängen eines Elements an ein Array stdarray.create1D(n, val) Array der Länge n, jedes Element hat Wert val stdarray.create2D(n, m, val) n-mal-m Array, jedes Element hat Wert val a[i:j] aus Array a[] wird ein neues Array [a[i], a[i+1], ..., a[j-1]] erzeugt, i hat Default 0 und j hat Default len(a) Zugriff auf Elemente von Arrays: a[i] das i-te Element von a[] (indizierter Zugriff) a[i] = x ersetze das i-te Element von a[] durch x (indizierte Zuweisung) for v in a: weise v jedes Element von a[] zu (Durchlaufen) a[i:j] ein neues Array [a[i], a[i+1], ..., a[j-1]], i hat Default 0 und j hat Default len(a) (Slicing) Funktionen zum Arbeiten mit Arrays: len(a) die Anzahl der Elemente von a[] sum(a) die Summe der Elemente von a[] min(a) ein kleinstes Element von a[] max(a) ein größtes Element von a[] (sie müssen summierbar sein) (sie müssen vergleichbar sein) (sie müssen vergleichbar sein) 1.4 Ein- und Ausgabe Die bisher benutzten Programme konnten Eingaben von der Kommandozeile lesen und Ausgaben auf dem Bildschirm ausgeben. Eingabe von der Kommandozeile § können über das Array sys.argv[] benutzt werden § sind stets vom Typ string und müssen mittels Funktionen wie int() oder float() zum passenden Typ konvertiert werden. Ausgaben auf dem Bildschirm § können mit den Funktionen stdio.write() und stdio.writeln() gemacht werden § sind stets vom Typ string § bilden den Datenstrom standard output. Standard output Ausgabe auf standard output: stdio.write(x) schreibe String x auf standard output stdio.writeln(x) schreibe String x und einen Zeilenumbruch auf standard output (wenn x weggelassen wird, wird nur ein Zeilenumbruch geschrieben) stdio.writef(fmt, arg1, arg2, ...) schreibe die Argumente arg1, arg2, ... auf standard output entsprechend der Formatierung durch fmt 1.4.2 # wuerfele.py # # Das Programm liest eine Zahl n von der Kommandozeile # und gibt n Würfe mit einem Würfel aus. import stdio, sys, random n = int(sys.argv[1]) for i in range(n): stdio.writeln(random.randrange(1,7)) #------------------------------------------------------------------# python wuerfele.py 7 # 4 # 4 # 1 # 2 # 4 # 6 # 1 # # python wuerfele.py 100000 > wuerfelergebnisse.txt # Man kann das Programm beliebig viele Werte ausgeben lassen (Datenstrom). Standard output kann auch in eine Datei umgeleitet werden. 1.4.3 # fmt-1.py import stdio, math n = 1000 r = math.sqrt(n) # gib int n und float r aus stdio.writef('Die Wurzel von %d ist %f .\n', n, r) # gib int n mit 5 Zeichen und float r mit 10 Zeichen aus stdio.writef('Die Wurzel von %5d ist %10f .\n', n, r) # gib int n mit 5 Zeichen unf float r mit 8 Zeichen und 3 Nachkommastellen aus stdio.writef('Die Wurzel von %5d ist %8.3f .\n', n, r) # wie zuvor, aber r wird linksbündig ausgegeben stdio.writef('Die Wurzel von %5d ist %-8.3f .\n', n, r) # wenn die angegebene Zahl der Zeichen zu klein ist, wird das ignoriert stdio.writef('Die Wurzel von %2d ist %2.3f .\n', n, r) #---------------------------------------------------# python fmt-1.py # Die Wurzel von 1000 ist 31.622777 . # Die Wurzel von 1000 ist 31.622777 . # Die Wurzel von 1000 ist 31.623 . # Die Wurzel von 1000 ist 31.623 . # Die Wurzel von 1000 ist 31.623 . 1.4.4 # fmt-2.py # Würfele 100000-mal mit zwei Würfeln; zähle, wie oft # welche Summe gewürfelt wurde und gib die relative Häufigkeit zur Summe 2 aus. import stdio, stdarray, random wurfsumme = stdarray.create1D(13,0) for i in range(100000): wurf1 = random.randrange(1,7) wurf2 = random.randrange(1,7) wurfsumme[wurf1+wurf2] += 1 fmt = 'Summe %2d wurde %5d-mal gewuerfelt (Faktor %4.2f).\n' for i in range(2,len(wurfsumme)): stdio.writef(fmt, i, wurfsumme[i], float(wurfsumme[i])/wurfsumme[2]) #-----------------------------------------------------------------------------------------# python fmt-2.py Summe 2 wurde 2855-mal gewuerfelt (Faktor 1.00). Summe 3 wurde 5588-mal gewuerfelt (Faktor 1.96). Summe 4 wurde 8283-mal gewuerfelt (Faktor 2.90). Summe 5 wurde 11166-mal gewuerfelt (Faktor 3.91). Summe 6 wurde 13870-mal gewuerfelt (Faktor 4.86). Summe 7 wurde 16608-mal gewuerfelt (Faktor 5.82). Summe 8 wurde 13708-mal gewuerfelt (Faktor 4.80). Summe 9 wurde 11126-mal gewuerfelt (Faktor 3.90). Summe 10 wurde 8424-mal gewuerfelt (Faktor 2.95). Summe 11 wurde 5514-mal gewuerfelt (Faktor 1.93). Summe 12 wurde 2858-mal gewuerfelt (Faktor 1.00). 1.4.5 Standard input Standard input ist ein Datenstrom für Daten, die von einem Programm als Eingabe gelesen werden. Der Datenstrom wird als Folge von Token aufgefasst, die durch Leerzeichen (Zeilenumbrüche, Tabs etc.) getrennt sind. Zum Lesen von Token gibt es die folgenden Funktionen. stdio.isEmpty() liefert True, falls der Eingabestrom beendet ist, sonst False stdio.readInt() liest ein Token, konvertiert es zu int und gibt es zurück stdio.readFloat() liest ein Token, konvertiert es zu float und gibt es zurück stdio.readBool() liest ein Token, konvertiert es zu bool und gibt es zurück stdio.readString() liest ein Token und gibt es zurück 1.4.6 # # # # ein-aus.py Von der Kommandozeile wird n eingelesen. Anschließend wird die Eingabe von n int-Werten erwartet. Der Durchschnitt der Werte wird ausgegeben. import stdio, sys # Die Anzahl der zu lesenden Werte wird von der Kommandozeile gelesen n = int(sys.argv[1]) summe = 0 for i in range(n): stdio.write('Bitte geben Sie Wert ' + str(i+1) + ' von ' + str(n) + ' ein: ') summe += stdio.readInt() stdio.writef('Der Durchschnitt ist %.2f.\n', float(summe)/n) #--------------------------------------------------------------------------------# # # # # # # python ein-aus.py 5 Bitte geben Sie Wert Bitte geben Sie Wert Bitte geben Sie Wert Bitte geben Sie Wert Bitte geben Sie Wert Der Durchschnitt ist 1 von 2 von 3 von 4 von 5 von 18.80 5 5 5 5 5 ein: ein: ein: ein: ein: 12 20 22 28 12 # python ein-aus.py 5 # Bitte geben Sie Wert 1 von 5 ein: # Bitte geben Sie Wert 2 von 5 ein: geben Sie Wert 3 von 5 ein: Bitte Wert 4 von 5 ein: 40 23 # Bitte geben Sie Wert 5 von 5 ein: Durchschnitt ist 24.60. 10 20 30 Bitte geben Sie Der 1.4.7 # # # # ein-aus-2.py Es werden float-Werte eingelesen, bis die Eingabe beendet wird (an der Konsole mit <Strg-d>, sonst mit Dateiende). Der Durchschnitt der Werte wird ausgegeben. import stdio, sys n = 0 summe = 0 while not stdio.isEmpty(): summe += stdio.readFloat() n += 1 stdio.writef('\nEs wurden %d Werte eingegeben.\n', n) stdio.writef('Der Durchschnitt ist %.2f.\n', summe/n) #-----------------------------------------------------------------------------# python ein-aus-2.py # 12 # 13 # 15 89 # 56 # # Es wurden 5 Werte eingegeben. # Der Durchschnitt ist 37.00. 1.4.8 Bisher haben wir den Datenstrom standard input an der Konsole erzeugt. Die Eingabeumleitung erlaubt auch das Lesen einer Datei. # # # # ein-aus-2.py Es werden float-Werte eingelesen, bis die Eingabe beendet wird. (an der Konsole mit <Strg-d>, sonst mit Dateiende). Der Durchschnitt der Werte wird ausgegeben. import stdio, sys n = 0 summe = 0 while not stdio.isEmpty(): summe += stdio.readFloat() n += 1 stdio.writef('\nEs wurden %d Werte eingegeben.\n', n) stdio.writef('Der Durchschnitt ist %.2f.\n', summe/n) #-----------------------------------------------------------------------------# python ein-aus-2.py < Jena-hoechsttemp.txt # # Es wurden 9497 Werte eingegeben. # Der Durchschnitt ist 15.11. Funktionen zum Lesen des ganzen (restlichen) Eingabestroms auf einen Schlag“: ” stdio.readAllInts() liest alle Token und gibt sie als Array von int zurück stdio.readAllFloats() liest alle Token und gibt sie als Array von float zurück stdio.readAllBools() liest alle Token und gibt sie als Array von bool zurück stdio.readAllStrings() liest alle Token und gibt sie als Array von string zurück stdio.readAllLines() liest alle Zeilen und gibt sie als Array von string zurück stdio.readAll() liest den Rest von standard input und gibt ihn als string zurück 1.4.10 # durchschnitt.py # Es werden float-Werte von standard input eingelesen. # Der Durchschnitt der Werte wird ausgegeben. import stdio, sys summe = 0.0 eingabe = stdio.readAllFloats() for v in eingabe: summe += v stdio.writeln(float(summe)/len(eingabe)) #----------------------------------------------------------# python durchschnitt.py < Jena-hoechsttemps.txt # 15.1101821628 # # python durchschnitt.py # 1 3 5 8 # 23 # 45 # # 14.1666666667 1.4.11 Funktionen zum Lesen ganzer Zeilen von standard input: stdio.hasNextLine() hat standard input noch eine weitere Zeile? stdio.readLine() liest die nächste Zeile und gibt sie (als string) zurück stdio.readAllLines() liest alle Zeilen und gibt sie als Array von string zurück 1.4.12 (Direktes) Verarbeiten einer csv-Datei Eine csv-Datei (comma separated values) enthält in Zeilen zusammengehörige Daten, die als Strings dargestellt und durch Kommas (o.ä.) getrennt sind. Bsp.: Datei airports.csv mit Namen, Breiten- und Längengrad etc. von Flughäfen. 1,"Goroka","Goroka","Papua New Guinea","GKA","AYGA",-6.081689,145.391881,5282,10,"U","Pacific/Port 2,"Madang","Madang","Papua New Guinea","MAG","AYMD",-5.207083,145.7887,20,10,"U","Pacific/Port_Mor 3,"Mount Hagen","Mount Hagen","Papua New Guinea","HGU","AYMH",-5.826789,144.295861,5388,10,"U","Pa 4,"Nadzab","Nadzab","Papua New Guinea","LAE","AYNZ",-6.569828,146.726242,239,10,"U","Pacific/Port_ 5,"Port Moresby Jacksons Intl","Port Moresby","Papua New Guinea","POM","AYPY",-9.443383,147.22005, 6,"Wewak Intl","Wewak","Papua New Guinea","WWK","AYWK",-3.583828,143.669186,19,10,"U","Pacific/Por Wir wollen nun die Koordinaten jedes Flughafens ausgeben. Dazu können wir jede Zeile in ein Array aufteilen, dessen Elemente die durch Kommas getrennten Stellen der Zeile sind. zeile = '1,"Goroka","Goroka","Papua New Guinea","GKA","AYGA",' + '-6.081689,145.391881,5282,10,"U","Pacific/Port_Moresby"' array = string.split( zeile, ',' ) stdio.writeln(array) liefert ['1', '"Goroka"', '"Goroka"', '"Papua New Guinea"', '"GKA"', '"AYGA"', '-6.081689', '145.391881', '5282', '10', '"U"', '"Pacific/Port_Moresby"'] 1.4.13 # coords-aus-csv.py # Es werden die Zeilen einer csv-Datei eingelesen. # Die Einträge an Stelle 6 und 7 werden ausgegeben. import stdio, string while stdio.hasNextLine(): line = stdio.readLine() breitengrad = string.split(line,',')[6] laengengrad = string.split(line,',')[7] stdio.writef('%8s %8s\n', breitengrad, laengengrad) #--------------------------------------------------------# python coords-aus-csv.py < airports.csv # -6.081689 145.391881 # -5.207083 145.7887 # -5.826789 144.295861 # -6.569828 146.726242 # -9.443383 147.22005 # -3.583828 143.669186 # ... 1.4.14 Eine csv-Datei vom DWD mit den in Jena gemessenen Tageshöchsttemperaturen. Element;Messstation;Datum;Wert;Einheit;Geo-Breite (Grad);Geo-Länge (Grad);Höhe (m);Sensorhöhe (m); Erstellungsdatum;Copyright; Lufttemperatur Tagesmaximum;Jena (Sternwarte);1990-01-01;1,5;Grad C;50,925;11,583;155;keine Daten vorhanden;2016-04-22;© Deutscher Wetterdienst 2016; Lufttemperatur Tagesmaximum;Jena (Sternwarte);1990-01-02;1,4;Grad C;50,925;11,583;155;keine Daten Lufttemperatur Tagesmaximum;Jena (Sternwarte);1990-01-03;0,4;Grad C;50,925;11,583;155;keine Daten Lufttemperatur Tagesmaximum;Jena (Sternwarte);1990-01-04;2,2;Grad C;50,925;11,583;155;keine Daten Lufttemperatur Tagesmaximum;Jena (Sternwarte);1990-01-05;1,2;Grad C;50,925;11,583;155;keine Daten Lufttemperatur Tagesmaximum;Jena (Sternwarte);1990-01-06;1,5;Grad C;50,925;11,583;155;keine Daten ... Lufttemperatur Lufttemperatur Lufttemperatur Lufttemperatur Lufttemperatur Tagesmaximum;Jena Tagesmaximum;Jena Tagesmaximum;Jena Tagesmaximum;Jena Tagesmaximum;Jena (Sternwarte);2015-12-28;9,5;Grad (Sternwarte);2015-12-29;8,7;Grad (Sternwarte);2015-12-30;6,3;Grad (Sternwarte);2015-12-31;3,8;Grad (Sternwarte);2016-01-01;2,7;Grad C;50,925;11,583;155;keine C;50,925;11,583;155;keine C;50,925;11,583;155;keine C;50,925;11,583;155;keine C;50,925;11,583;155;keine Daten Daten Daten Daten Daten 1.4.15 # temperaturen-aus-csv.py # Es werden die Zeilen einer csv-Datei vom DWD eingelesen. # Der Eintrag an Stelle 3 wird ausgegeben. import stdio, string # In der ersten Zeil der csv-Datei stehen andere Informationen. stdio.readLine() while stdio.hasNextLine(): line = stdio.readLine() temperatur = string.split(line,';')[3] # Da in der csv-Datei die Temperaturen mit Komma (2,3) statt Punkt (2.3) # stehen, muss das Komma durch einen Punkt ersetzt werden. temperatur = string.replace(temperatur, ',', '.') stdio.writef('%4s\n', temperatur) #--------------------------------------------------------------# python temperaturen-aus-csv.py < Jena_Temperaturen.csv # # 1.5 # 1.4 # 0.4 # 2.2 # 1.2 # ... # # python temperaturen-aus-csv.py < Jena_Temperaturen.csv > JT.txt # 1.4.16 # tage-ueber-30.py # Als Eingabe wird Jena-hoechsttemps.txt erwartet. # Dort steht der Reihe nach für jeden Tag vom 1.1.1990 bis 1.1.2016 # die Tageshoechsttemperatur, die an der Sternwarte Jena gemessen wurde. # Es wird für jedes Jahr ausgegeben, an wievielen Tagen es über 30 Grad warm war. import stdio, sys, stdarray temperaturen = stdio.readAllFloats() # Zähle die Anzahl der Tage pro Jahr mit Temperaturen über 30 Grad for jahr in range(26): if (jahr+2)%4 == 0: schalttag = 1 else: schalttag = 0 jahreslaenge = 365 + schalttag anz_warme_tage = 0 for d in range(jahr*365 + (jahr+2)/4, jahr*365 + (jahr+2)/4 + jahreslaenge) : if temperaturen[d] >= 30: anz_warme_tage += 1 stdio.writeln(anz_warme_tage) #------------------------------------------------------------------------------------------------# python tage-ueber-30.py < JT.txt > Tage-ueber-30-pro-Jahr.txt # 1.4.17 Piping Die Ausgabe eines Programms kann direkt als Eingabe für ein anderes Programm verwendet werden. # python wuerfele.py 10000 | durchschnitt.py 3.5197 # ( python temperaturen-aus-csv.py < Jena Temperaturen.csv ) | python tage-ueber-30.py > Tage-ueber-30-pro-Jahr.txt 1.4.18 Zusammenfassung standard input und standard output Wir haben gesehen, wie Daten § von der Konsole eingelesen und auf der Konsole ausgegeben werden können § aus Dateien eingelesen und in Dateien geschrieben werden können (Eingabeumleitung < und Ausgabeumleitung >) § als Ausgabe eines Programms weitergereicht und als Eingabe eines anderen Programms benutzt werden können (Piping |). Graphische Ausgabe mit standard drawing Das Modul stddraw erlaubt das Malen von Punkten, Strichen, Kreisen, Rechtecken etc. # Es wird ein Strich gemalt. import stddraw # die x- und y-Kooordinate des ersten Punktes x1 = 0.2 y1 = 0.3 # die x- und y-Kooordinate des zweiten Punktes x2 = 0.9 y2 = 0.9 # male eine Linie vom ersten zum zweiten Punkt stddraw.line(x1, y1, x2, y2) # zeige das gemalte Bild stddraw.show() 1.4.20 Graphische Ausgabe mit standard drawing Das Modul stddraw erlaubt das Malen von Punkten, Strichen, Kreisen, Rechtecken etc. # Es wird ein Strich gemalt. import stddraw # die x- und y-Kooordinate des ersten Punktes x1 = 0.2 y1 = 0.3 # die x- und y-Kooordinate des zweiten Punktes x2 = 0.9 y2 = 0.9 px2, y 2q px1, y 1q # male eine Linie vom ersten zum zweiten Punkt stddraw.line(x1, y1, x2, y2) # zeige das gemalte Bild stddraw.show() 1.4.20 stddraw.line(x0, y0, x1, y0) male eine Linie von px0, y 0q zu px1, y 1q stddraw.point(x, y) male einen Punkt an px, y q stddraw.show() zeige das Bild im standard-drawing-Fenster (und warte, bis es vom Nutzer geschlossen wird) stddraw.setPenRadius(r) setze die Stiftdicke auf r (Default-Wert von r ist 0.005) stddraw.Xscale(x0, x1) setze den Bereich der x-Werte auf x0 ď x ď x1 (Default ist x0 “ 0 und x1 “ 1) stddraw.Yscale(x0, x1) setze den Bereich der y -Werte auf y 0 ď y ď y 1 (Default ist y 0 “ 0 und y 1 “ 1) stddraw.setCanvasSize(w, h) setze die Bildgröße auf w -mal-h Pixel (Default für w und h ist 512) # Male das Haus vom Nikolaus. Es hat fünf Ecken: ul (unten links), ur, # ol, or, gi (Giebel), die jeweils mit x- und y-Koordinate # angegeben werden. import stddraw ul_x = 0 ul_y = 0 ur_x = 1 ur_y = 0 ol_x = 0 ol_y = 1 or_x = 1 or_y = 1 gi_x = float(ol_x + or_x)/2 gi_y = ol_y + 0.5 stddraw.setXscale(-0.5,1.5) stddraw.setYscale(-0.5,2) stddraw.line(ul_x,ul_y,ur_x,ur_y) stddraw.line(ul_x,ul_y,ol_x,ol_y) stddraw.line(or_x,or_y,ol_x,ol_y) stddraw.line(or_x,or_y,ur_x,ur_y) stddraw.line(or_x,or_y,gi_x,gi_y) stddraw.line(ol_x,ol_y,gi_x,gi_y) stddraw.show() 1.4.22 #----------------------------------------------------------------------# plotfilter.py (aus dem Buch) #----------------------------------------------------------------------import stdio import stddraw # Read x and y scales from standard input, and configure standard # draw accordingly. Then read points from standard input until # end-of-file, and plot them on standard draw. x0 y0 x1 y1 = = = = stdio.readFloat() stdio.readFloat() stdio.readFloat() stdio.readFloat() stddraw.setXscale(x0, x1) stddraw.setYscale(y0, y1) # Read and plot the points. stddraw.setPenRadius(0.002) while not stdio.isEmpty(): x = stdio.readFloat() y = stdio.readFloat() stddraw.point(x, y) stddraw.show() #---------------------------------------# python plotfilter.py < usa.txt Datei usa.txt: 669905.0 247205.0 1244962.0 700000.0 1097038.8890 245552.7780 1103961.1110 247133.3330 1104677.7780 247205.5560 1108586.1110 249238.8890 1109713.8890 250111.1110 ... 1.4.23 Wir wollen plotfilter.py benutzen, um eine Karte mit den Flughäfen zu malen. Deren Koordinaten haben wir ja bereits. Dazu müssen wir coords-aus-csv.py so modifizieren, dass es zu plotfiler.py passt: § In der ersten Zeile der Ausgabe müssen die Werte x0, y 0, x1, y 1 für die Bereiche der x-Werte (x0 ď x ď x1) und die Bereiche der y -Werte (y 0 ď y ď y 1) stehen. § In den darauffolgenden Zeilen folgen x-Koordinate (Längengrad) und y -Koordinate (Breitengrad) jedes Flughafens. #----------------------------------------------------------------------# airports-to-plotfilter.py #----------------------------------------------------------------------# Es werden die Zeilen einer csv-Datei eingelesen. # Die Ausgabe passt als Eingabe für plotfilter.py . # Zuerst werden die Bereiche für die x- und y-Koordinaten ausgegeben. # Die x-Koordinaten sind die Längengrade (-180 ... 180) und # die y-Koordinaten sind die Breitengrade (-90 .. 90). # Der Eintrag an Stelle 6 der csv-Datei ist der Breitengrad, # und der Eintrag an Stelle 7 ist der Längengrad. import stdio, string # Ausgabe der Bereiche für die x- und y-Koordinaten stdio.writeln('-180.0 -90.0 180.0 90.0') while stdio.hasNextLine(): zeile = stdio.readLine() xkoordinate = string.split(zeile,',')[7] ykoordinate = string.split(zeile,',')[6] stdio.writef('%8s %8s\n', xkoordinate, ykoordinate) #-------------------------------------------------------------------------# ( python airports-to-plotfilter.py < airports.csv ) | python plotfilter.py # 1.4.25 Zeichnen des Graphs einer Funktion Zum Zeichnen des Graphs einer Funktion f pxq im Bereich l ď x ď r § muss man festlegen, wieviele Funktionswerte man im Bereich l ď x ď r berechnen will (das Intervall l . . . r wird in gleichmäßige Abschnitte unterteilt), § die Argument-Wert-Paare px, f pxqq berechnen und § die Funktionswerte benachbarter Argumente durch eine Linie verbinden. Man beachte, dass stets nur eine Näherung berechnet wird, deren Qualität von der Anzahl der berechneten Funktionswerte abhängt. 1.4.26 # # # # # # # # functiongraph.py (aus dem Buch) Der Graph der Funktion f(x) = sin(4x)+sin(20x) wird im Intervall 0..pi gezeichnet. Zuerst wird von der Kommandozeile die Sampling-Dichte (die Anzahl der zu zeichnenden Punkte) eingelesen. Anschließend werden die Punkte berechnet - die x-Koordinate des i-ten Punkts wird in x[i] gespeichert und die y-Koordinate in y[i]. Anschließend werden Linien zwischen benachbarten Punkten gezeichnet. import math, sys, stdarray, stdio, stddraw n = int(sys.argv[1]) x = stdarray.create1D(n+1,0.0) y = stdarray.create1D(n+1,0.0) python functiongraph.py 20 for i in range(n+1): x[i] = math.pi * i/n y[i] = math.sin(4.0*x[i]) + math.sin(20.0*x[i]) stddraw.setXscale(0, math.pi) stddraw.setYscale(-2.0, +2.0) for i in range(n): stddraw.line(x[i], y[i], x[i+1], y[i+1]) python functiongraph.py 200 stddraw.show() Darstellung der Anzahl der warmen Tage in Jena Wir haben bereits das Programm tage-ueber-30.py, mit dem wir die Anzahl der warmen Tage pro Jahr in Jena von 1990 bis 2015 ausgeben. Wir wollen nun das Ergebnis graphisch darstellen: als Kurve und als Säulendiagramm. 1.4.28 # temperatur-kurve.py # Als Eingabe wird für jedes Jahr von 1990 bis 2015 eine Zahl gelesen. # Die Zahlen werden als Funktionsgraph dargestellt. import stdio, stddraw # lies die Werte für jedes Jahr werte = stdio.readAllInts() stddraw.setXscale(-2, 26) stddraw.setYscale(-2, max(werte)+2) stddraw.setPenRadius(0.002) # male ein Koordinatensystem stddraw.setPenColor(stddraw.BLUE) stddraw.line(0,0,26,0) stddraw.line(0,0,0,max(werte)+0.5) # beschrifte die Achsen des Koordinatensystems for i in range(max(werte)/10 + 1): stddraw.text(-0.5, i*10, str(i*10)) for i in range(6): stddraw.text(i*5, -0.7, str(1990 + 5*i)) # male die Kurve stddraw.setPenColor(stddraw.BLACK) for i in range(len(werte)-1): stddraw.line(i, werte[i], i+1, werte[i+1]) stddraw.show() #------------------------------------------------------------# ( python temperaturen-aus-csv.py < Jena_Temperaturen.csv ) | # python tage-ueber-30.py | python temperatur-kurve.py # temperatur-kurve.py # Als Eingabe wird für jedes Jahr von 1990 bis 2015 eine Zahl gelesen. # Die Zahlen werden als Funktionsgraph dargestellt. import stdio, stddraw # lies die Werte für jedes Jahr werte = stdio.readAllInts() stddraw.setXscale(-2, 26) stddraw.setYscale(-2, max(werte)+2) stddraw.setPenRadius(0.002) # male ein Koordinatensystem stddraw.setPenColor(stddraw.BLUE) stddraw.line(0,0,26,0) stddraw.line(0,0,0,max(werte)+0.5) # beschrifte die Achsen des Koordinatensystems for i in range(max(werte)/10 + 1): stddraw.text(-0.5, i*10, str(i*10)) for i in range(6): stddraw.text(i*5, -0.7, str(1990 + 5*i)) # male die Kurve stddraw.setPenColor(stddraw.BLACK) for i in range(len(werte)-1): stddraw.line(i, werte[i], i+1, werte[i+1]) stddraw.show() #------------------------------------------------------------# ( python temperaturen-aus-csv.py < Jena_Temperaturen.csv ) | # python tage-ueber-30.py | python temperatur-kurve.py # temperatur-kurve.py # Als Eingabe wird für jedes Jahr von 1990 bis 2015 eine Zahl gelesen. # Die Zahlen werden als Funktionsgraph dargestellt. import stdio, stddraw # lies die Werte für jedes Jahr werte = stdio.readAllInts() stddraw.setXscale(-2, 26) stddraw.setYscale(-2, max(werte)+2) stddraw.setPenRadius(0.002) # male ein Koordinatensystem stddraw.setPenColor(stddraw.BLUE) stddraw.line(0,0,26,0) stddraw.line(0,0,0,max(werte)+0.5) # beschrifte die Achsen des Koordinatensystems for i in range(max(werte)/10 + 1): stddraw.text(-0.5, i*10, str(i*10)) for i in range(6): stddraw.text(i*5, -0.7, str(1990 + 5*i)) # male die Kurve stddraw.setPenColor(stddraw.BLACK) for i in range(len(werte)-1): stddraw.line(i, werte[i], i+1, werte[i+1]) stddraw.show() #------------------------------------------------------------# ( python temperaturen-aus-csv.py < Jena_Temperaturen.csv ) | # python tage-ueber-30.py | python temperatur-kurve.py Weitere Funktionen zum Malen von Kreisen etc. stddraw.circle(x, y, r) male einen Kreis mit Mittelpunkt px, y q und Radius r import stddraw x = 2 y = 3 r = 4 stddraw.setXscale(0, 6) stddraw.setYscale(0, 6) stddraw.circle(x, y, r) stddraw.show() 1.4.30 Weitere Funktionen zum Malen von Kreisen etc. stddraw.circle(x, y, r) male einen Kreis mit Mittelpunkt px, y q und Radius r import stddraw x = 2 y = 3 r = 4 stddraw.setXscale(0, 6) stddraw.setYscale(0, 6) stddraw.circle(x, y, r) stddraw.show() 4 p2, 3q 1.4.30 stddraw.square(x, y, r) male ein Quadrat mit Mittelpunkt px, y q und Radius r import stddraw x = 2 y = 3 r = 4 stddraw.setXscale(0, 6) stddraw.setYscale(0, 6) stddraw.square(x, y, r) stddraw.show() 1.4.31 stddraw.square(x, y, r) male ein Quadrat mit Mittelpunkt px, y q und Radius r import stddraw x = 2 y = 3 r = 4 stddraw.setXscale(0, 6) stddraw.setYscale(0, 6) stddraw.square(x, y, r) stddraw.show() 4 4 p2, 3q 1.4.31 stddraw.polygon(x, y) male ein Vieleck mit den Ecken pxr0s, y r0sq, pxr1s, y r1sq, . . . (x und y sind Arrays) import stddraw x = [1,0,3,4,5] y = [2,3,4,6,1] stddraw.setXscale(0, 6) stddraw.setYscale(0, 6) stddraw.polygon(x,y) stddraw.show() stddraw.polygon(x, y) male ein Vieleck mit den Ecken pxr0s, y r0sq, pxr1s, y r1sq, . . . (x und y sind Arrays) p4, 6q import stddraw x = [1,0,3,4,5] y = [2,3,4,6,1] stddraw.setXscale(0, 6) stddraw.setYscale(0, 6) stddraw.polygon(x,y) stddraw.show() p3, 4q p0, 3q p5, 1q p1, 2q Alle Formen können auch gefüllt gemalt werden mittels stddraw.filledCircle(x,y,r), stddraw.filledSquare(x,y,r), stddraw.filledPolygon(x,y). stddraw.filledPolygon(x, y) male ein ausgefülltes Vieleck mit den Ecken pxr0s, y r0sq, pxr1s, y r1sq, . . . (x und y sind Arrays) import stddraw x = [1,0,3,4,5] y = [2,3,4,6,1] stddraw.setXscale(0, 6) stddraw.setYscale(0, 6) stddraw.setPenColor(stddraw.BLACK) stddraw.filledPolygon(x,y) stddraw.show() 1.4.33 Alle Formen können auch gefüllt gemalt werden mittels stddraw.filledCircle(x,y,r), stddraw.filledSquare(x,y,r), stddraw.filledPolygon(x,y). stddraw.filledPolygon(x, y) male ein ausgefülltes Vieleck mit den Ecken pxr0s, y r0sq, pxr1s, y r1sq, . . . (x und y sind Arrays) p4, 6q import stddraw p3, 4q x = [1,0,3,4,5] y = [2,3,4,6,1] stddraw.setXscale(0, 6) stddraw.setYscale(0, 6) stddraw.setPenColor(stddraw.BLACK) stddraw.filledPolygon(x,y) stddraw.show() p0, 3q p5, 1q p1, 2q 1.4.33 aus: Sedgewick, Wayne, Dondero: Introduction to Programming in Python – An Interdisciplinary Approach (Addison Wesley 2015) 1.4.34 Zusammenfassung standard draw Wir haben gesehen, wie Bilder aus einfachen Elementen (Punkte, Striche, Kreise, Rechtecke, Schrift) gestaltet werden können. Mit diesen Mitteln lassen sich auch Funktionen und Messwerte graphisch ansprechend darstellen. 1.4.35 1.5 PageRank Bei Anfragen an Suchmaschinen im Internet werden häufig Millionen von Webseiten zu einem Suchwort gefunden. Wie kommt es zu der Reihenfolge, in der die Seiten als Suchergebnis angezeigt werden? Für jede Seite wird ein PageRank bestimmt – das ist die Wahrscheinlichkeit, mit der ein Websurfer, der nach bestimmten Regeln (s.u.) zufällig von einer Webseite zur nächsten surft, die Seite erreicht. Bei der Anzeige eines Suchergebnisses werden die Seiten mit absteigendem PageRank angezeigt (neben möglichen anderen Kriterien). Das hat sich als nützlich erwiesen. Wir wollen an kleinen Beispielen den PageRank ausrechnen. 1.5.1 aaa.de bbb.com aaa.de ddd.org ccc.edu aaa.de bbb.com eee.eu ddd.org ccc.edu bbb.com ddd.org ccc.edu aaa.de eee.eu 1.5.2 1 0 2 3 4 1.5.2 1 0.2082 0.2083 0 0.2116 2 0.1612 3 0.2106 4 Ziel: berechne die Wahrscheinlichkeit, dass der Random Surfer einen Knoten besucht: er startet auf Seite 0 er läuft durch das Netz wie folgt: zu 90% wählt er einen Link zu 10% wählt er irgendeine Seite 1.5.2 1 0.2082 0.2083 0 0.2116 2 0.1612 3 0.2106 4 Ziel: berechne die Wahrscheinlichkeit, dass der Random Surfer einen Knoten besucht: er startet auf Seite 0 er läuft durch das Netz wie folgt: zu 90% wählt er einen Link zu 10% wählt er irgendeine Seite 5 0 graph.txt: 1 2 4 1 0 4 1 0 1 3 4 3 0 1 3 1 2 2 0 1.5.2 Simulation des Random Surfers: § lies den Graph ein § starte auf Seite 0 § mit Wahrsch. 0.9: wähle zufällig einen Link und folge ihm zu einer Nachbarseite mit Wahrsch. 0.1: wähle zufällig irgendeine Seite § wiederhole den letzten Schritt n mal § zähle dabei, wie häufig welche Seite besucht wurde Da es keine festgelegte Form der Darstellung von Graphen gibt, schreiben wir zuerst ein Programm, das einen Graph aus einer Form in die von uns benutzte Form bringt. Teil 1: Einlesen des Graphen Zuerst wird der Graph als 2-dimensionales Array graph[][] gespeichert, so dass graph[i][j] die Anzahl der Kanten von i nach j ist. Dieses Array wird dann ausgegeben. 5 0 1 2 4 1 0 4 1 0 1 3 4 3 0 1 3 1 2 2 0 wird zu [ [ [ [ [ [ 0, 2, 0, 0, 1, 1, 0, 0, 0, 1, 0, 1, 0, 1, 0, 1, 1, 0, 0, 0, graph[][] 0 0 1 0 0 ], ], ], ], ] ] #----------------------------------------------------------------------# graph-zu-am.py # # Liest eine Graph-Datei ein # und gibt die Knotenzahl gefolgt von der Adjazenzmatrix des Graphen aus. #-----------------------------------------------------------------------import stdio, stdarray # Die Graph-Datei beginnt mit der Anzahl Seiten (Knoten). n = stdio.readInt() stdio.writeln(str(n)) graph = stdarray.create2D(n, n, 0) # Der Rest der Graph-Datei besteht aus Zahlepaaren, die für Links (Kanten) stehen. # Zähle die Kanten in der Adjazenzmatrix in graph[][]. while not stdio.isEmpty(): i = stdio.readInt() j = stdio.readInt() # Wir vertrauen nicht darauf, dass der eingelesene Graph # nur aus zulässigen Kanten besteht und fragen das sicherheitshalber ab. if i<n and j<n: graph[i][j] += 1 # Gib die Adjazenzmatrix aus. for zeile in graph : for eintrag in zeile : stdio.writef('%d ', eintrag) stdio.writef('\n') 1.5.5 Teil 2: Simulation des Random Surfers Der Random Surfer geht wiederholt von einer Seite zur nächsten. Er wählt die nächste Seite nach der 90-10-Regel aus: zu 90% wird einem Link auf der aktuellen Seite gefolgt zu 10% wird irgendeine Seite ausgewählt Zufällige Auswahl eines Links auf der aktuellen Seite: Seite 1 hat folgende Links: graph[1]: [ 2, 0, 1, 1, 0 ] Das sind 4 Links – jeder muss mit gleicher Wahrscheinlichkeit gewählt werden. # Die Variable seite enthält die aktuelle Seite. anzahl_links = sum(graph[seite]) r = random.randrange(1, anzahl_links+1) Zufallszahl s = 0 Link zu Seite for j in range(n): s += graph[seite][j] if s >= r : seite = j break 1 0 2 0 3 2 4 3 # random_surfer.py # Lies runden von der Kommandozeile ein, n und graph[][] von standard input. ... # In seitenzaehler wird gespeichert, wie oft jede Seite besucht wurde. seitenzaehler = stdarray.create1D(n,0) # Starte auf Seite 0 und "surfe" runden mal zu einer (neuen) Seite. seite = 0 for i in range(runden): anzahl_links = sum(graph[seite]) if random.randrange(10)==0 or anzahl_links==0 : # mit Wahrscheinlichkeit 0.1: seite = random.randrange(n) # gehe zu einer zufällig gewählten Seite else : # mit Wahrscheinlichkeit 0.9: r = random.randrange(1, anzahl_links+1) # wähle zufällig einen der Links s = 0 # von der Seite und benutze ihn for j in range(n) : s += graph[seite][j] if s >= r : seite = j break seitenzaehler[seite] += 1 # Gib die Page Ranks aus. for v in seitenzaehler: stdio.writef('%f ', float(v)/runden) stdio.writeln() 1.5.7 Benutzung der Programme # python graph-zu-am.py < GraphVL05.txt | python random surfer.py 100000 0.2072 0.2106 0.2114 0.1607 0.2101 # python graph-zu-am.py < Graph500.txt | python random surfer.py 100000 0.0005 0.0054 0.0022 0.0006 0.0020 0.0027 0.0012 0.0007 0.0003 0.0002 0.0007 0.0011 ... Graphische Darstellung der berechneten Page Ranks zu verschiedenen Zeitpunkten. 1.5.8 Verbesserungsmöglichkeiten anzahl links wird immer wieder neu berechnet, um die Wahrscheinlichkeit des Wechsels von einer zu einer anderen Seite zu bestimmen. Stattdessen: schreibe diese Wahrscheinlichkeiten direkt in den Graph. Aus graph[][] wird ein Array prob[][] bestimmt, so dass p[i][j] die Wahrscheinlichkeit dafür ist, dass der Random Surfer in einem Schritt von i zu j geht: prob[i][j] = 0.9 * graph[i][j]/sum(graph[i]) + 0.1 * 1 / n [ [ [ [ Aus [ [ 0, 2, 0, 0, 1, 1, 0, 0, 0, 1, 0, 1, 0, 1, 0, 1, 1, 0, 0, 0, graph[][] 0 0 1 0 0 ], [ [ 0.02, 0.47, 0.02, ], [ 0.47, 0.02, 0.24, ], [ 0.02, 0.02, 0.02, ], wird [ 0.02, 0.02, 0.92, ] ] [ 0.47, 0.47, 0.02, prob[][] 0.47, 0.24, 0.02, 0.02, 0.02, 0.02 0.02 0.92 0.02 0.02 ], ], ], ], ] ] #---------------------------------------------------------------------------------# graph-zu-prob.py # # Liest eine Graph-Datei ein und gibt die Matrix aus, # die die Übergangswahrscheinlichkeiten des Random Surfers bei einem Schritt enthält. #----------------------------------------------------------------------------------import stdio, stdarray # Die Graph-Datei beginnt mit der Anzahl Seiten (Knoten). n = stdio.readInt() stdio.writeln(str(n)) graph = stdarray.create2D(n, n, 0) # Der Rest der Graph-Datei besteht aus Zahlepaaren, die für Links (Kanten) stehen. # Zähle die Kanten in der Adjazenzmatrix in graph[][]. while not stdio.isEmpty(): i = stdio.readInt() j = stdio.readInt() if i<n and j<n: graph[i][j] += 1 # Gib die Matrix mit den Übergangswahrscheinlichkeiten aus. for zeile in range(n) : zeilensumme = sum(graph[zeile]) for eintrag in graph[zeile] : if zeilensumme>0 : stdio.writef('%f ', 0.9 * float(eintrag)/zeilensumme + else : stdio.writef('%f ', 0.1/n ) stdio.writef('\n') 0.1/n ) 1.5.10 #----------------------------------------------------------------------# quicker_surfer.py #----------------------------------------------------------------------import stdio, sys, stdarray, random, stddraw # Liest runden von der Kommandozeile, n und prob[][] von standard input. # Simuliert den Random Surfer in dem Graph # entsprechend den Übergangswahrscheinlichkeiten in prob[][]. ... # Starte auf Seite 0. seite = 0 seitenzaehler = stdarray.create1D(n,0) for i in range(runden): # Lasse den Random Surfer zur nächsten Seite wechseln. r = random.random() s = 0.0 for j in range(n): s += prob[seite][j] if s >= r : seite = j break seitenzaehler[seite] += 1 for v in seitenzaehler: stdio.writef('%4.4f ', float(v)/runden) stdio.writeln() 1.5.11 Benutzung der Programme # python graph-zu-prob.py < Graph500.txt | python quicker surfer.py 100000 0.0007 0.0058 0.0025 0.0007 0.0021 0.0030 0.0011 0.0006 0.0004 ... Graphische Darstellung der berechneten Page Ranks zu verschiedenen Zeitpunkten. 1.5.12 Die Übergangswahrscheinlichkeiten von Knoten 1 sind prob[1]: [ 0.48, 0.02, 0.24, 0.24, 0.02 ] d.h. die die die die die Wahrscheinlichkeit, Wahrscheinlichkeit, Wahrscheinlichkeit, Wahrscheinlichkeit, Wahrscheinlichkeit, zu zu zu zu zu Knoten Knoten Knoten Knoten Knoten 0 1 2 3 4 zu zu zu zu zu gehen, gehen, gehen, gehen, gehen, ist ist ist ist ist 0.48 0.02 0.24 0.24 0.02 1.00 0.00 0.48 0.50 0.74 0.98 1.00 Wähle r zufällig mit 0 ď r ă 1. r = random.random() s = 0.0 for j in range(n): s += prob[seite][j] if s >= r : seite = j break Damit hat der Random Surfer den Schritt zu seite gemacht. Die Übergangswahrscheinlichkeiten von Knoten 1 sind prob[1]: [ 0.48, 0.02, 0.24, 0.24, 0.02 ] d.h. die die die die die Wahrscheinlichkeit, Wahrscheinlichkeit, Wahrscheinlichkeit, Wahrscheinlichkeit, Wahrscheinlichkeit, zu zu zu zu zu Knoten Knoten Knoten Knoten Knoten 0 1 2 3 4 zu zu zu zu zu gehen, gehen, gehen, gehen, gehen, ist ist ist ist ist 0.48 0.02 0.24 0.24 0.02 1.00 0.00 0.48 0.50 0.74 0.98 1.00 Wähle r zufällig mit 0 ď r ă 1. r = random.random() s = 0.0 for j in range(n): s += prob[seite][j] if s >= r : seite = j break Damit hat der Random Surfer den Schritt zu seite gemacht. + + + + prob[1][0] prob[1][1] prob[1][2] prob[1][3] prob[1][4] Direkte Berechnung statt Simulation Anstatt mit dem Random Surfer die Wahrscheinlichkeit des Besuchs einer Seite zu simulieren, können wir die Wahrscheinlichkeit auch direkt ausrechnen. Grundlage ist die Matrix mit den Übergangswahrscheinlichkeiten. In Zeile i stehen die Wahrscheinlichkeiten dafür, in einem Schritt von Seite i zu den anderen Seiten zu kommen. » — — — – [ 1.0 0.0 0.0 0.0 0.0 ] Am Anfang ist RS mit Wahrscheinlichkeit 1 auf Seite 0. 0.02 0.48 0.02 0.02 0.47 prob[][] 0.47 0.02 0.47 0.02 0.24 0.24 0.02 0.02 0.02 0.02 0.92 0.02 0.47 0.02 0.02 fi 0.02 0.02 ffi ffi 0.92 ffi fl 0.02 0.02 [ 0.02 0.47 0.02 0.47 0.02 ] Nach einem Schritt ist RS mit diesen W.keiten auf einer der Seiten. 1.5.14 Multiplikation von Matrizen Eine Matrix A aus z Zeilen und s Spalten hat m ¨ n Einträge aij für 1 ď i ď z und 1 ď j ď s. Bsp.: Matrizen mit 5 Zeilen und 4 Spalten: ¨ ˛ ¨ a11 a12 a13 a14 4 12 ˚a ‹ ˚ ˚ 21 a22 a23 a24 ‹ ˚3 1 ˚ ‹ ˚ ˚a31 a32 a33 a34 ‹ ˚2 5 ˚ ‹ ˚ ˝a41 a42 a43 a44 ‚ ˝1 6 a51 a52 a53 a54 7 11 ˛ 3 4 5 5‹ ‹ ‹ 4 3‹ ‹ 2 9‚ 0 17 Eine Zeilenvektor entspricht einer Zeile einer Matrix. ` v1 v2 v3 v4 v5 ˘ ` ˘ 3 2 11 8 1 Man kann einen Zeilenvektor mit einer Matrix multiplizieren, wenn die Anzahl der Spalten des Zeilenvektors gleich der Anzahl der Zeilen der Matrix sind. Das Produkt eines Zeilenvektors v mit n Spalten und einer Matrix A mit n Zeilen und m Spalten ist ein Zeilenvektor w “ pw1 w2 ¨ ¨ ¨ wm q mit m Spalten und wk n ÿ “ vi ¨ aki . i“1 Bsp: Produkt eines Zeilenvektors mit 5 Spalten und einer Matrix mit 5 Zeilen und 4 Spalten ergibt einen Zeilenvektor mit 4 Spalten. ˛ ¨ 4 12 3 4 ˚3 1 5 5 ‹ ˚ ‹ ˚2 0 4 3 ‹ ˚ ‹ ˝1 6 2 0 ‚ ` ˘ ` 7 11 0 17˘ 3 2 11 8 1 55 97 79 72 1.5.16 » — — — – [ 0.02 0.47 0.02 0.47 0.02 ] Nach einem Schritt ist RS mit diesen W.keiten auf einer der Seiten. [ 0.24 0.04 0.55 0.13 0.04 ] 0.02 0.48 0.02 0.02 0.47 prob[][] 0.47 0.02 0.47 0.02 0.24 0.24 0.02 0.02 0.02 0.02 0.92 0.02 0.47 0.02 0.02 fi 0.02 0.02 ffi ffi 0.92 ffi fl 0.02 0.02 [ 0.24 0.04 0.55 0.13 0.04 ] Nach zwei Schritten ist RS mit diesen W.keiten auf einer der Seiten. [ 0.05 0.15 0.15 0.14 0.51 ] Nach zwei Schritten ist RS Nach drei Schritten ist RS mit diesen W.keiten auf einer der Seiten. mit diesen W.keiten auf einer der Seiten. [ 0.21 0.21 0.21 0.16 0.21 ] Nach 14 Schritten ist RS mit diesen W.keiten auf einer der Seiten. [ 0.21 0.21 0.21 0.16 0.21 ] Nach 15 Schritten ist RS mit diesen W.keiten auf einer der Seiten. 1.5.17 #----------------------------------------------------------------------# markov_surfer.py #----------------------------------------------------------------------import ... # Lies runden von der Kommandozeile ein, n und prob[][] von standard input. ... # ranks speichert die berechneten PageRanks der Seiten, newRanks ist ein Zwischenspeicher. ranks = stdarray.create1D(n,0.0) ranks[0] = 1.0 # Der Random Surfer startet auf Seite 0. newRanks = stdarray.create1D(n,0.0) for xxx in range(runden): # multipliziere ranks[] mit prob[][] for s in range(n): newRanks[s] = 0.0 for i in range(n): newRanks[s] += ranks[i] * prob[i][s] # kopiere das Ergebnis von newRanks[] nach ranks[] for s in range(n): ranks[s] = newRanks[s] # Ausgabe der Page Ranks for pagerank in ranks: stdio.writef('%f ', pagerank) stdio.writef('\n') 1.5.18 Benutzung der Programme # python graph-zu-prob.py < Graph500.txt | python markov surfer.py 100000 0.000651 0.005746 0.002346 0.000618 0.001825 0.002567 ... Ergebnis von quicker surfer.py nach einer Weile Ergebnis von markov surfer.py nach sehr kurzer Zeit markov surfer.py erreicht viel schneller ein stabiles Ergebnis als quicker surfer.py ! 1.5.19 Zusammenfassung § Wir haben gesehen, dass man mit den bisher erlernten Mitteln bereits Programme für interessante Probleme schreiben kann. § Das Programm ist ein Beispiel für ein datengetriebenes Programm. Solche Programme werden häufig verwendet. Wir können verschiedene Arten, Graphen zu speichern, in die von unseren Programmen benutzte Art überführen. § Es ist nicht immer leicht, ein fehlerfreies Programm zu schreiben. Die Überprüfung, dass ein Programm fehlerfrei ist, erfordert gründliche Arbeit. § Es ist nicht immer leicht, ein schnelles Programm zu schreiben. 2 Funktionen und Module Funktionen erlauben (mehr als Verzweigungen und Schleifen) den Programmfluss zwischen verschiedenen Stellen des Programmcodes hin und her springen zu lassen. Sie machen es auch möglich, den gleichen Programmcode an verschiedenen Stellen des Programms wiederzubenutzen. Schließlich kann man Funktionen in Module auslagern, so dass der gleiche Programmcode in verschiedenen Programmen benutzt werden kann. 2. Funktionen und Module 2.1 Definition von Funktionen 2.2 Module und Klienten 2.3 Module und Rekursion 2.4 Versickerung (ein Anwendungsbeispiel) 2.1 Wie man Funktionen definiert Wir haben bereits verschiedene Funktionen benutzt: math.sqrt( ) stdio.writeln( ) str( ) Aufgaben eines Programms, die klar abgegrenzt sind, sollte man auch abgrenzen und z.B. als Funktion aufschreiben. Dadurch bekommt der Programmcode eine bessere Struktur. Fehlersuche, Wartung und Wiederbenutzung werden vereinfacht. 2.1.1 Beispiel: Berechnung von harmonischen Zahlen Die n-te harmonische Zahl ist 1 ` 21 ` 13 ` 41 ` . . . ` n1 . Mathematisch als Funktion aufgeschrieben: n ÿ 1 hpnq “ i i“1 Wir wollen ein Programm harmonisch.py schreiben, das Argumente von der Kommandozeile einliest und deren harmonische Zahlen ausgibt: python harmonisch.py 1 2 4 6 liefert die Ausgaben 1.0 1.5 2.08333333333 2.45 2.1.2 #--------------------------------------------# harmonische_zahlen.py #--------------------------------------------import sys, stdio def harmonische_zahl(n): summe = 0.0 for i in range(1,n+1): summe += 1.0/i return summe for i in range (1,len(sys.argv)): argument = int(sys.argv[i]) wert = harmonische_zahl(argument) stdio.writeln(wert) Was bei der Ausführung von python harmonische zahlen 1 2 4 (informell) passiert: i = 1 argument = 1 harmonische_zahl(1) n = 1 summe = 0.0 i = 1 summe = 1.0 return 1.0 wert = 1.0 i = 2 argument = 2 harmonische_zahl(2) n = 2 summe = 0.0 i = 1 summe = 1.0 i = 2 summe = 1.5 return 1.5 wert = 1.5 i = 3 argument = 4 harmonische_zahl(4) n = 4 summe = 0.0 i = 1 summe = 1.0 i = 2 summe = 1.5 i = 3 summe = 1.83333333 i = 4 summe = 2.08333333 return 2.08333333 wert = 2.08333333 Definition von Funktionen Signatur Funktionsname Parameter-Variable def harmonische_zahl( n ): lokale Variable summe = 0.0 for i in range(1,n+1): Funktions-Rumpf summe += 1.0/i return-Anweisung return summe Rückgabewert 2.1.4 Aufruf von Funktionen for i in range(1, len(sys.argv)) argument = int(sys.argv[i]) Funktions-Aufruf wert = harmonische_zahl( argument ) stdio.writeln(wert) Argument 2.1.5 Gültigkeitsbereich (scope) von Variablen argument und wert sind hier unbekannt def harmonische_zahl(n): summe = 0.0 for i in range(1,n+1): summe += 1.0/i return summe Gültigkeitsbereich von n, summe und i zwei verschiedene Variablen for i in range (1,len(sys.argv)): argument = int(sys.argv[i]) wert = harmonische_zahl(argument) stdio.writeln(wert) Gültigkeitsbereich von i, argument und wert n und summe sind hier unbekannt 2.1.6 Funktionen mit mehreren Argumenten Berechnung des punktweisen Produkts zweier Vektoren: px0 , x1 , x2 , . . . , xn q ˆ py0 , y1 , y2 , . . . , yn q “ x0 ¨ y0 ` x1 ¨ y1 ` x2 ¨ y2 ` . . . ` xn ¨ yn ` n ÿ “ xi ¨ yi ˘ i“0 #----------------------------------# vektor_produkt.py #----------------------------------import stdio def vektor_mal_vektor(x, y): summe = 0 for i in range(len(x)): summe += x[i]*y[i] return summe a = [1, 2, 3] b = [4, 5, 6] e = vektor_mal_vektor(a,b) stdio.writeln(e) #------------------------------------# python vektor_produkt.py # 32 Informeller Programmablauf (nur grobe Vorstellung): a = [1,2,3] b = [4,5,6] vektor_mal_vektor([1,2,3],[4,5,6]) x = [1,2,3] y = [4,5,6] summe = 0 i = 0 summe = 4 i = 1 summe = 14 i = 2 summe = 32 e = 32 Benutzung mehrerer Funktionen Informeller Programmablauf (nur grobe Vorstellung): a = [1,2,3] m = [[4,5,6],[7,8,9]] vektor_mal_matrix([1,2,3],[[4,5,6],[7,8,9]]) ve = [1,2,3] ma = [[4,5,6],[7,8,9]] ergebnis = [0,0] def vektor_mal_vektor(x, y): i = 0 summe = 0 vektor_mal_vektor([1,2,3],[4,5,6]) for i in range(len(x)): x = [1,2,3] summe += x[i]*y[i] y = [4,5,6] return summe summe = 0 i = 0 summe = 4 def vektor_mal_matrix(ve,ma): i = 1 summe = 14 ergebnis = stdarray.create1D(len(ma),0) i = 2 summe = 32 for i in range(len(ma)): ergebnis = [32,0] ergebnis[i] = vektor_mal_vektor(ve, ma[i]) i = 2 return ergebnis vektor_mal_vektor([1,2,3],[7,8,9]) x = [1,2,3] a = [1, 2, 3] y = [7,8,9] m = [[4, 5, 6],[7, 8, 9]] summe = 0 i = 0 summe = 7 e = vektor_mal_matrix(a,m) i = 1 summe = 23 stdio.writeln(e) i = 2 summe = 50 #---------------------------------------ergebnis = [32,50] # python vektor_matrix_produkt.py e = [32,50] # [32, 50] #-----------------------------------# vektor_matrix_produkt.py #-----------------------------------import stdio, stdarray Funktionen mit mehreren return-Anweisungen def isPrime(n): if n < 2: return False i = 2 while i*i <= n: if n%i == 0: return False i += 1 return True 2.1.9 Funktionen ohne return-Anweisung (mit Seiteneffekt) #--------------------------------------------------# balkendiagramm.py #--------------------------------------------------import stdio,stddraw def balkendiagramm(vektor) : m = max(vektor) balkenbreite = 1.0/len(vektor) for k in range(len(vektor)): stddraw.filledRectangle(k*balkenbreite, 0, balkenbreite-0.01, float(vektor[k])/m) balkendiagramm([1,2,3,4,5,6,7,8,9,10]) # balkendiagramm([1,2,3,4,5,6,5,4,3,4,5,6,9,7,4,1]) stddraw.show() 2.1.10 Funktionen ohne return-Anweisung (mit Seiteneffekt) #--------------------------------------------------# balkendiagramm.py #--------------------------------------------------import stdio,stddraw def balkendiagramm(vektor) : m = max(vektor) balkenbreite = 1.0/len(vektor) for k in range(len(vektor)): stddraw.filledRectangle(k*balkenbreite, 0, balkenbreite-0.01, float(vektor[k])/m) balkendiagramm([1,2,3,4,5,6,7,8,9,10]) # balkendiagramm([1,2,3,4,5,6,5,4,3,4,5,6,9,7,4,1]) stddraw.show() 2.1.10 Funktionen ohne return-Anweisung (mit Seiteneffekt) #--------------------------------------------------# balkendiagramm.py #--------------------------------------------------import stdio,stddraw def balkendiagramm(vektor) : m = max(vektor) balkenbreite = 1.0/len(vektor) for k in range(len(vektor)): stddraw.filledRectangle(k*balkenbreite, 0, balkenbreite-0.01, float(vektor[k])/m) balkendiagramm([1,2,3,4,5,6,7,8,9,10]) # balkendiagramm([1,2,3,4,5,6,5,4,3,4,5,6,9,7,4,1]) stddraw.show() 2.1.10 Übergabe von Argumenten beim Funktionsaufruf Erstmal: Allgemeines über Objekte in Python Die “grobe Vorstellung” bei den informellen Programmabläufen über die Speicherung von Daten stimmt natürlich nicht – wir verfeinern die Vorstellung jetzt. Die Daten werden durch Objekte repräsentiert. Jedes Objekt hat eine Identität, einen Typ und einen Wert. Die Identität bestimmt das Objekt eindeutig. (Vorstellung: Adresse des Objektes im Speicher des Computers.) § Der Typ beschreibt das Verhalten des Objektes (d.h. die Werte, die es annehmen kann, und die Operationen, die auf ihm erlaubt sind). § Der Wert ist der Datentyp-Wert, den es repräsentiert/speichert. Bsp.: ein Objekt vom Typ int kann den Wert 99 speichern. § Eine Objekt-Referenz ist eine konkrete Darstellung der Identität des Objektes. Python-Programme benutzen Objekt-Referenzen um § auf den Wert des Objektes zuzugreifen oder ihn zu ändern, § auf die Objekt-Referenz zuzugreifen oder sie zu ändern. 2.1.11 § Ein Literal weist Python an, ein Objekt mit einem bestimmten Wert zu erzeugen. Bsp.: das Literal 99 weist Python an, ein int-Objekt mit Wert 99 zu erzeugen. § Eine Variable ist ein Name für eine Objekt-Referenz. § Ein Ausdruck weist Python an, ein Objekt mit dem Wert des Ausdrucks zu erzeugen. Bsp.: wenn das an die Variable a gebundene Objekt den Wert 3 hat, dann weist der Ausdruck 4*a+2 Python an, ein int-Objekt mit Wert 14 zu erzeugen. § Eine Zuweisung ăVariableą=ăAusdrucką weist Python an, ein Objekt mit dem Wert des Ausdrucks zu erzeugen und die Variable daran zu binden. Falls der Ausdruck nur eine Variable ist, dann wird kein neues Objekt erzeugt, sondern das an die Variable gebundene Objekt genommen. Bsp.: wenn das an die Variable a gebundene Objekt den Wert 3 hat, dann wird für die Zuweisung b = 4*a+2 ein Objekt mit dem Wert 14 erzeugt und dieses Objekt wird an die Variable b gebunden. a = 3 a 3 b = 4*a + 2 b 14 Variable Objekt-Referenz Objekt 2.1.12 Programmablauf auf dem Objekt-Level Ein 3-zeiliges Python-Programm und dessen Wirkung auf dem Objekt-Level: a = 1234 a 1234 b = 47 a 1234 b 47 a 1234 b 47 c 1281 c = a + b 2.1.13 Vertauschung der Werte zweier Variablen. t = a a 1234 b 47 a 1234 b 47 t a = b a 1234 b 47 t b = t a 1234 b 47 t 2.1.14 Auswirkung von Anweisungen. s = ’Abcd’ s ’Abcd’ s = s + ’ ef’ s ’Abcd’ ’ ef’ ’Abcd ef’ Objekte der Standard-Typen int, float, string und boolean sind nicht veränderbar. Wenn einer Variablen dieses Typs ein Wert eines Ausdrucks zugewiesen wird, dann wird ein neues Objekt mit diesem Wert erzeugt. i = 15 i 15 i = i + 1 i 15 1 16 2.1.15 Arrays 0 1 2 a a = [5, 9, 11] 3 5 9 11 0 1 2 b = a a 3 b 5 9 11 0 1 2 b[1] = 17 a 3 b 5 9 11 17 Objekte vom Typ array sind veränderbar (mutable) (anders als Objekte der Standard-Typen int, float, string und boolean)! Kopieren eines Arrays 0 1 2 a a = [5, 9, 11] 3 5 9 11 0 1 2 b = [] a 3 for v in a: 5 b += [v] 9 11 0 1 2 b 3 2.1.17 Kopieren eines Arrays 0 1 2 a a = [5, 9, 11] 3 5 9 11 0 1 2 b = [] a 3 for v in a: 5 b += [v] 9 b[1] = 17 11 0 1 2 b 3 17 2.1.17 Übergabe von Argumenten beim Funktionsaufruf Argumente werden stets als Objekt-Referenzen übergeben. Beispielprogramm: def inc(j): j += 1 Programmablauf auf dem Objekt-Level: i = 99 i 99 Aufruf inc(i) i j 99 j += 1 i j 99 i = 99 inc(i) Informeller Programmablauf: i = 99 j = 99 j = 100 1 100 Nach der Ausführung i 99 1 100 2.1.18 Übergabe des Ergebnisses nach Funktionsausführung Beispielprogramm: def inc(j): j += 1 return j i = 99 i = inc(i) Ergebnisse werden stets als Objekt-Referenzen übergeben. Programmablauf auf dem Objekt-Level: i = 99 i 99 Aufruf inc(i) i j 99 j += 1 i j 99 Informeller Programmablauf: i = j j i = 99 = 99 = 100 100 1 100 Nach der Ausführung i 99 1 100 Seiteneffekte mit Arrays a = [17, 4, 21] def tausche(b, i, j): temp = b[i] b[i] = b[j] b[j] = temp a = [ 17, 4, 21 ] tausche(a, 0, 1) 0 1 2 a 3 17 4 21 Seiteneffekte mit Arrays a = [17, 4, 21] 0 1 2 a 3 def tausche(b, i, j): temp = b[i] b[i] = b[j] b[j] = temp a = [ 17, 4, 21 ] tausche(a, 0, 1) 17 Aufruf von tausche(a,0,1) 4 21 0 1 2 a 3 b i 17 4 21 0 j 1 Seiteneffekte mit Arrays 0 1 2 a a = [17, 4, 21] 3 def tausche(b, i, j): temp = b[i] b[i] = b[j] b[j] = temp a = [ 17, 4, 21 ] tausche(a, 0, 1) 17 4 21 0 1 2 a Aufruf von tausche(a,0,1) 3 b i temp = b[i] 17 4 21 0 j 1 0 1 2 a 3 b temp 17 4 21 i 0 j 1 2.1.20 Seiteneffekte mit Arrays 0 1 2 a Aufruf von tausche(a,0,1) def tausche(b, i, j): temp = b[i] b[i] = b[j] b[j] = temp 3 b i 17 4 21 0 j 1 a = [ 17, 4, 21 ] tausche(a, 0, 1) temp = b[i] 0 1 2 a 3 b b[i] = b[j] temp 17 4 21 i 0 j 1 0 1 2 a 3 b temp 17 4 21 i 0 j 1 Seiteneffekte mit Arrays temp = b[i] def tausche(b, i, j): temp = b[i] b[i] = b[j] b[j] = temp 0 1 2 a 3 b temp 17 4 21 i 0 j 1 a = [ 17, 4, 21 ] tausche(a, 0, 1) b[i] = b[j] 0 1 2 a 3 b b[j] = temp temp 17 4 21 i 0 j 1 0 1 2 a 3 b temp 17 4 21 i 0 j 1 Seiteneffekte mit Arrays b[i] = b[j] def tausche(b, i, j): temp = b[i] b[i] = b[j] b[j] = temp 0 1 2 a 3 b temp 17 4 21 i 0 j 1 a = [ 17, 4, 21 ] tausche(a, 0, 1) b[j] = temp 0 1 2 a 3 b Nach der Ausführung temp 17 4 21 i 0 j 1 0 1 2 a 3 17 4 21 2.1.20 #-------------------------------------------# sort.py # Sortieren mittels Minimum-Suche. #-------------------------------------------import stdio, sys def tausche(b, i, j): temp = b[i] b[i] = b[j] b[j] = temp def minsuche(a, anfang): min = anfang for i in range(anfang+1,len(a)-1): if a[min] > a[i] : min = i return min Funktionen helfen beim Strukturieren von Programmen. #-------------------------------------------m = sys.argv[1:] for i in range(len(m)-1): min = minsuche(m,i) tausche(m, min, i) stdio.writeln(m) Zusammenfassung § Mittels Funktionen lässt sich die Programmiersprache erweitern. § Man muss sich den Ablauf eines Programms nicht mehr Schritt für Schritt in elementaren Anweisungen vorstellen, sondern kann abstrakter über Funktionsaufrufe und Rückgabewerte argumentieren. § Wir können nun Programmcode schreiben, der einfacher zu verstehen, zu verbessern und zu warten ist. 2.2 Module und 2.3 Rekursion Module erlauben es, Funktionen für Programme in verschiedenen Dateien zu definieren. Sie unterstützen die Idee des strukturierten Programmierens. Rekursive Funktionen rufen sich selbst auf. Die Möglichkeit zur Rekursion liefert eine mächtige allgemeine Programmiertechnik, die wir an verschiedenen Beispielen betrachten werden. Bsp. 1: Die Fakultätsfunktion n! (gesprochen n Fakultät“ oder Fakultät von n“) ” ” ist das Produkt der Zahlen von 1 bis n, also: 0! 1! 2! 3! 4! 5! .. . “ “ “ “ “ “ 1 1¨2 1¨2¨3 1¨2¨3¨4 1¨2¨3¨4¨5 .. . “ “ “ “ “ 1 1 2 6 24 120 Allgemein: 1. n! “ 1looooooooooomooooooooooon ¨ 2 ¨ 3 ¨ . . . ¨ pn ´ 1q ¨n (Anm.: 0! “ 1), oder pn´1q! 2. 0! “ 1 und n! “ pn ´ 1q! ¨ n für alle n ě 1. 2.3.1 Implementierung der direkten Variante n! “ 1 ¨ 2 ¨ 3 ¨ . . . ¨ pn ´ 1q ¨ n (Anm.: 0! “ 1) #----------------------------------------------# fakultaet_iterativ.py #----------------------------------------------import stdio, sys def fakultaet(n): ergebnis = 1 for i in range(1,n+1): ergebnis *= i return ergebnis m = int(sys.argv[1]) for i in range(m): stdio.writef("%2d! = %d \n", i, fakultaet(i)) #-------------------------------# python fakultaet_iterativ.py 12 # 0! = 1 # 1! = 1 # 2! = 2 # 3! = 6 # 4! = 24 # 5! = 120 # 6! = 720 # 7! = 5040 # 8! = 40320 # 9! = 362880 # 10! = 3628800 # 11! = 39916800 2.3.2 Implementierung der rekursiven Variante 0! “ 1 und n! “ pn ´ 1q! ¨ n für alle n ě 1. #------------------------------------------------# fakultaet_rekursiv.py #------------------------------------------------import stdio, sys def fakultaet(n): if n == 0: return 1 else: return fakultaet(n-1) * n m = int(sys.argv[1]) for i in range(m): stdio.writef("%2d! = %d \n", i, fakultaet(i)) #-------------------------------# python fakultaet_rekursiv.py 12 # 0! = 1 # 1! = 1 # 2! = 2 # 3! = 6 # 4! = 24 # 5! = 120 # 6! = 720 # 7! = 5040 # 8! = 40320 # 9! = 362880 # 10! = 3628800 # 11! = 39916800 2.3.3 fakultaet(5) n=5 120 fakultaet(5): return fakultaet(4)*5 n=4 24 fakultaet(4): return fakultaet(3)*4 n=3 6 def fakultaet(n): if n == 0: return 1 else: return fakultaet(n-1) * n fakultaet(3): return fakultaet(2)*3 n=2 2 fakultaet(2): return fakultaet(1)*2 n=1 1 fakultaet(1): return fakultaet(0)*1 n=0 def fakultaet(n): if n == 0: return 1 else: m = fakultaet(n-1) * n return m 1 fakultaet(0): return 1 2.3.4 Die Spur der Funktionsaufrufe Die Aufrufe und Rückgabewerte bei fakultaet(5): fakultaet(5) | fakultaet(4) | | fakultaet(3) | | | fakultaet(2) | | | | fakultaet(1) | | | | | fakultaet(0) | | | | | |_return 1 | | | | |_return 1 | | | |_return 2 | | |_return 6 | |_return 24 |_return 120 (Ergebnis (Ergebnis (Ergebnis (Ergebnis (Ergebnis (Ergebnis von von von von von von fakultaet(0)) fakultaet(1)) fakultaet(2)) fakultaet(3)) fakultaet(4)) fakultaet(5)) def fakultaet(n): if n == 0: return 1 else: return fakultaet(n-1) * n 2.3.5 Mathematische Schreibweise für die Fakultätsfunktion n! “ # 1, pn ´ 1q! ¨ n, falls n “ 0 sonst 2.3.6 Bsp. 2: Binärzahlen 2er-Potenzen sind 20 , 21 , 22 , 23 , 24 , . . ., also 1, 2, 4, 8, 16, . . .. Jede natürliche Zahl lässt sich als Summe von 2er-Potenzen darstellen. Bsp.: 46 “ 32 ` 8 ` 4 ` 2 “ 25 ` 23 ` 22 ` 21 5 4 “ 1 ¨ 2 ` 0 ¨ 2 ` 1 ¨ 23 ` 1 ¨ 22 ` 1 ¨ 21 ` 0 ¨ 20 “ ˆ 101110 139 “ 128 ` 8 ` 2 ` 1 “ 27 ` 23 ` 21 ` 20 7 6 5 “1¨2 `0¨2 `0¨2 `0¨24 `1¨23 `0¨22 `1¨21 `1¨20 “ ˆ 10001011 Die Darstellung einer Zahl n als Binärzahl ist eine Folge bm bm´1 . . . b1 b0 m ř (mit bi P t0, 1u) mit der Eigenschaft n “ 2i ¨ bi . i“0 Eigenschaften von Binärzahlen Bsp.: 1 hat 2 hat 4 hat 9 hat 18 hat 37 hat 74 hat 75 hat die die die die die die die die Binärdarstellung Binärdarstellung Binärdarstellung Binärdarstellung Binärdarstellung Binärdarstellung Binärdarstellung Binärdarstellung binp37q “ 1, binp37q “ 10, binp37q “ 100, binp37q “ 1001, binp37q “ 10010, binp37q “ 100101, binp74q “ 1001010 und binp75q “ 1001011. $ ’ 0, falls n “ 0 ’ ’ ’ &1, falls n “ 1 binpnq “ ’ binpt n2 uq0, falls n ě 2 und gerade ’ ’ ’ % binpt n2 uq1, falls n ě 2 und ungerade 2.3.8 Etwas kompaktere Darstellung der Funktion $ ’ 0, falls n “ 0 ’ ’ ’ &1, falls n “ 1 binpnq “ ’ binpt n2 uq0, falls n ě 2 und gerade ’ ’ ’ % binpt n2 uq1, falls n ě 2 und ungerade “ # n, falls n “ 0 oder n “ 1 binpt n2 uqn mod 2, falls n ě 2 2.3.9 Rekursive Implementierung #-----------------------------------------------# binaer.py #-----------------------------------------------import stdio, sys def bin(n): if n==0 or n==1: stdio.write(str(n)) else: bin(n/2) stdio.write(str(n%2)) n = int(sys.argv[1]) for i in range(n): stdio.writef('# %d hat Binaerdarstellung bin(i) stdio.writef('.\n') ',i) #---------------------------------# python binaer.py 21 # 0 hat Binaerdarstellung 0. # 1 hat Binaerdarstellung 1. # 2 hat Binaerdarstellung 10. # 3 hat Binaerdarstellung 11. # 4 hat Binaerdarstellung 100. # 5 hat Binaerdarstellung 101. # 6 hat Binaerdarstellung 110. # 7 hat Binaerdarstellung 111. # 8 hat Binaerdarstellung 1000. # 9 hat Binaerdarstellung 1001. # 10 hat Binaerdarstellung 1010. # 11 hat Binaerdarstellung 1011. # 12 hat Binaerdarstellung 1100. # 13 hat Binaerdarstellung 1101. # 14 hat Binaerdarstellung 1110. # 15 hat Binaerdarstellung 1111. # 16 hat Binaerdarstellung 10000. # 17 hat Binaerdarstellung 10001. # 18 hat Binaerdarstellung 10010. # 19 hat Binaerdarstellung 10011. # 20 hat Binaerdarstellung 10100. Die Spur der Funktionsaufrufe bin(37) | bin(18) | | bin(9) | | | bin(4) | | | | bin(2) | | | | | bin(1) | | | | | |_stdio.write(1) | | | | |_stdio.write(2%2) | | | |_stdio.write(4%2) | | |_stdio.write(9%2) | |_stdio.write(18%2) |_stdio.write(37%2) die bisher ausgegebene Binärzahl ist 1 = 1 10 = 2*1 + 0 = 2 100 = 2*2 + 0 = 4 1001 = 2*4 + 1 = 9 10010 = 2*9 + 0 = 18 100101 = 2*18 + 1 = 37 Bsp. 3: Schnelles Potenzieren 313 “ p36 q2 ¨ 3 Bsp.: “ pp33 q2 q2 ¨ 3 “ ppp31 q2 ¨ 3q2 q2 ¨ 3 “ ppp30 ¨ 3q2 ¨ 3q2 q2 ¨ 3 Allgemein gilt: n a “ $ ’ ’ &1, falls n “ 0 t n2 u 2 pa q , ’ ’ %pat n2 u q2 ¨ a, falls n ě 1 und gerade falls n ě 1 und ungerade Diese Methode nennt man auch Potenzieren durch wiederholtes Quadrieren“. ” 2.3.12 Rekursive Implementierung des schnellen Potenzierens #-----------------------------------------# pot_schnell.py #-----------------------------------------import stdio, sys def potenziere(a,n): if n==0: return 1 else: z = potenziere(a,n/2) z_quadrat = z*z if n%2==0: return z_quadrat else: return z_quadrat*a a = int(sys.argv[1]) n = int(sys.argv[2]) for i in range(n): stdio.writeln( potenziere(a,i) ) #------------------------------# python pot_schnell.py 123 10 # 1 # 123 # 15129 # 1860867 # 228886641 # 28153056843 # 3462825991689 # 425927596977747 # 52389094428262881 # 6443858614676334363 Die Spur der Funktionsaufrufe potenziere(4,19) | potenziere(4,9) | | potenziere(4,4) | | | potenziere(4,2) | | | | potenziere(4,1) | | | | | potenziere(4,0) | | | | | |_return 1 | | | | |_return 4 | | | |_return 16 | | |_return 256 | |_return 262144 |_return 274877906944 4**0 1*1 * 4 4*4 16*16 256*256 * 4 262144*262144 * 4 = = = = = = Anzahl Multiplikationen: 1 0 4 2 16 1 256 1 262144 2 274877906944 2 --8 Allgemein gilt: die Anzahl der rekursiven Aufrufe von potenziere() beim Ausführen von potenziere(a,n) ist ď 1 ` log2 n. Bei jedem Aufruf werden höchstens 2 Multiplikationen ausgeführt. Also werden z.B. beim Ausführen von potenziere(2,1000000) höchstens 40 Multiplikationen ausgeführt. 2.3.14 Vergleich der Rechenzeiten 6 210 7 210 8 210 einfache Multiplikation 21 s 2253 s ?? wiederholtes Quadrieren 0.0047 s 0.045 s 0.55 s direkt in Python 0.0046 s 0.044 s 0.54 s Man braucht große Potenzen“ z.B. beim Ver- und Entschlüsseln von Daten, ” die über das Internet übertragen werden (https). 2.3.15 Bsp. 4: Die Fibonacci-Zahlen $ ’ ’ &0, fibpnq “ n fibpnq 1, ’ ’ %fibpn ´ 1q ` fibpn ´ 2q, falls n “ 0 falls n “ 1 falls n ě 2 0 1 2 3 4 5 6 7 8 9 10 . . . 0 1 1 2 3 5 8 13 21 34 55 . . . 2.3.16 Die direkte Implementierung (ist nicht so gut) #----------------------------------------------# fibonacci_rekursiv.py #----------------------------------------------import stdio, sys def fib(n): if n==0 or n==1: return n else: return fib(n-1) + fib(n-2) n = int(sys.argv[1]) for i in range(n): stdio.writef('# fib(%d) = %d \n',i,fib(i)) #--------------------------------# python fibonacci_rekursiv.py 99 # ... # fib(22) = 17711 # fib(23) = 28657 # fib(24) = 46368 # fib(25) = 75025 # fib(26) = 121393 # fib(27) = 196418 # fib(28) = 317811 # fib(29) = 514229 # fib(30) = 832040 # fib(31) = 1346269 # fib(32) = 2178309 # ... Ab fib(32) fängt es (auf meinem Rechner) an, dass die Rechenzeit länger dauert. Ab fib(38) wartet man schon über eine Minute auf das Ergebnis . . . 2.3.17 Die Spur der Funktionsaufrufe fib(5) | fib(4) | | fib(3) | | | fib(2) | | | | fib(1) | | | | |_return 1 | | | | fib(0) | | | | |_return 0 | | | |_return 1 | | | fib(1) | | | |_return 1 | | | |_return 2 | | fib(2) | | | fib(1) | | | |_return 1 | | | fib(0) | | | |_return 0 | | |_return 1 | |_return 3 | fib(3) | | fib(2) | | | fib(1) | | | |_return 1 Das ist der Anfang der Spur der Funktionsaufrufe bei der Berechnung von fib(5). Man sieht, dass sehr viele Funktionsaufrufe die gleichen Argumente haben – es werden also ständig Funktionswerte berechnet, die schon mal berechnet wurden. Das ist eigentlich unnötig. In diesem Fall ist es also nicht hilfreich, die rekursive Definition der Funktion zu implementieren. Wieviele Funktionsaufrufe werden ausgeführt? # anz aufrufepnq “ 1, falls n “ 0 oder n “ 1 1 ` anz aufrufepn ´ 1q ` anz aufrufepn ´ 2q, falls n ě 2 n 0 1 2 3 4 5 6 7 8 9 10 ... fibpnq 0 1 1 2 3 5 8 13 21 34 55 ... anz aufrufepnq 1 1 3 5 9 15 25 41 67 109 177 . . . def fib(n): if n==0 or n==1: return n else: return fib(n-1) + fib(n-2) 2.3.19 Eine schnelle aber ungenaue Berechnung der Fibonacci-Zahlen 1 fibpnq “ ? ¨ 5 ˜ˆ ? ˙n ˆ ? ˙n ¸ 1` 5 1´ 5 ´ 2 2 #--------------------------------------------------------------------------------# fibo_gold.py #--------------------------------------------------------------------------------import stdio, sys, math def fib_gold(n): return (1/math.sqrt(5)) * ( ((1+math.sqrt(5))/2)**n - ((1-math.sqrt(5))/2)**n) n = int(sys.argv[1]) for i in range(n): stdio.writef('fib(%d) = %f\n',i,fib_gold(i)) 2.3.20 Wo sieht man Fehler im Ergebnis? #----------------------------------------# python fibo_gold.py 100 # fib(0) = 0.000000000 # fib(1) = 1.000000000 # fib(2) = 1.000000000 # fib(3) = 2.000000000 # fib(4) = 3.000000000 # fib(5) = 5.000000000 # fib(6) = 8.000000000 # fib(7) = 13.000000000 ... # fib(29) = 514229.000000000 # fib(30) = 832040.000000001 # fib(31) = 1346269.000000001 # fib(32) = 2178309.000000002 ... # fib(68) = 72723460248141.171875000 # fib(69) = 117669030460994.265625000 # fib(70) = 190392490709135.437500000 # fib(71) = 308061521170129.750000000 # fib(72) = 498454011879265.187500000 # fib(73) = 806515533049395.000000000 Beim Rechnen mit float-Werten treten Rundungsfehler auf, die schließlich ein falsches Ergebnis liefern. Wie entwickelt sich die Abweichung von den korrekten Werten? 2.3.21 Fibonacci-Zahlen mit Matrix-Potenzierung berechnen In Aufgabe 18 hatten wir (für n ě 1): ˆ 0 1 1 1 ˙n ˆ “ fibpn ´ 1q fibpnq fibpnq fibpn ` 1q ˙ Das Potenzieren von Matrizen kann genauso wie das Potenzieren von Zahlen beschleunigt“ werden. ” Da wir die Matrizen-Multiplikation bereits in Aufgabe 17 hatten, wollen wir sie wiederverwenden. Definition von Modulen zur Wiederverwendung von Funktionen #------------------------------------------# Aufgabe17.py: Matrizen ... # multiplizieren, eingeben und ausgeben #------------------------------------------import stdio, sys, stdarray # multMM(A,B) gibt das Produkt der Matrizen # A und B als Ergebnis zurück. def multMM(A,B): C = stdarray.create2D(len(A),len(B),0) for z in range(len(A)): for s in range(len(B)): for i in range(len(A)): C[z][s] += A[z][i]*B[i][s] return C # matrix2D_einlesen liest eine # 2-dimensionale Matrix von standard input. def matrix2D_einlesen(): ... return C # matrix2D_ausgeben(A) gibt Matrix A # im o.g. Format aus. def matrix2D_ausgeben(A): ... #----------------------------------# Lösung von Aufgabe 17: # zwei Matrizen einlesen # und ihr Produkt ausgeben. def main(): A = matrix2D_einlesen() B = matrix2D_einlesen() matrix2D_ausgeben(multMM(A,B)) #-----------------------------------if __name__ == '__main__': main() 2.3.23 Implementierung der Fibonacci-Funktion mit schneller Matrix-Potenzierung Die schnelle Matrix-Potenzierung geht genau wie die schnelle Zahlen-Potenzierung. A muss eine quadratische Matrix sein (also jeweils m Zeilen und Spalten). $ ’ falls n “ 0 ’ m, &E ` t n u ˘2 n A “ A2 , falls n ě 1 und gerade ’ ’` t n u ˘2 % A 2 ¨ A, falls n ě 1 und ungerade Em ist die Einheitsmatrix mit m Zeilen und Spalten. Ihre Einträge Ei,i “ 1 auf der Diagonalen sind 1, alle anderen Einträge sind 0. Es gilt Em ¨ A “ A und A ¨ Em “ A. Die Matrix-Multiplikation nehmen wir aus Aufgabe17.py . 2.3.24 #-----------------------------------------------------------------------# fibo_pot.py # Berechnung der Fibonacci-Funktion mit schneller Matrix-Potenzierung. #-----------------------------------------------------------------------import stdio, sys, stdarray, Aufgabe17 def einheitsM(A): E = stdarray.create2D(len(A),len(A),0) for i in range(len(E)): E[i][i]=1 return E # schnelles Potenzieren von Zahlen def potM(A,n): def potenziere(a,n): if n==0: return einheitsM(A) if n==0: return 1 else: else: Z = potM(A, n/2) z = potenziere(a,n/2) Zquadrat = Aufgabe17.multMM(Z,Z) z_quadrat = z*z if n%2==0: return Zquadrat if n%2==0: return z_quadrat else: return Aufgabe17.multMM( Zquadrat, A ) else: return z_quadrat*a def fib_pot(i): return potM([ [0, 1], [1, 1] ], i )[0][1] def main(): n = int(sys.argv[1]) for i in range(n): stdio.writef('fib(%d) = %d\n', i, int(fib_pot(i))) if __name__ == '__main__': main() Vergleich der Ergebnisse von fib gold und fib pot Das folgende Programm vergleicht die Ergebnisse der Funktionen fib gold() und fib pot(), und gibt die Unterschiede aus. #-------------------------------------------------------# fibo_diff.py #-------------------------------------------------------import stdio, sys, math import fibo_gold, fibo_pot n = int(sys.argv[1]) for i in range(1,n): diff = int(fibo_gold.fib_gold(i)) - fibo_pot.fib_pot(i) if diff != 0: stdio.writef('%d: %d\n', i, diff) #----------------------# python fibo_diff.py 100 # 72: 1 # 73: 2 # 74: 3 # 75: 5 # 76: 8 # 77: 14 # 78: 24 # 79: 39 # 80: 59 # 81: 102 # 82: 169 # 83: 279 ... # 94: 59713 # 95: 98879 # 96: 166784 # 97: 265663 # 98: 440639 # 99: 722686 2.3.26 Die Berechnung der Fibonacci-Zahlen mit Speicherung der benötigten Zwischenergebnisse #-------------------------------------------------------# fibo_direkt.py #-------------------------------------------------------import stdio, sys def fib_direkt(n): f = [ 0 , 1 ] for i in range(2,n+1): f[i%2] = f[0] + f[1] return f[n%2] def main(): n = int(sys.argv[1]) for i in range(n): stdio.writef('%d!= %d\n', i, fib_direkt(i)) if __name__ == '__main__': main() 2.3.27 Welche Implementierung läuft am schnellsten? #-----------------------------------------------------------------------------------------# fibo_vergleich.py #-----------------------------------------------------------------------------------------import stdio, sys, time, fibo_pot, fibo_direkt n = int(sys.argv[1]) # Berechne fib(n) mit fib_direkt() anfangszeit = time.time() fibo_direkt.fib_direkt(n) endzeit = time.time() stdio.writef('Rechenzeit fuer fib(%d) von fib_direkt(): %2f\n', n, endzeit-anfangszeit) # Berechne fib(n) mit fib_pot() anfangszeit = time.time() fibo_pot.fib_pot(n) endzeit = time.time() stdio.writef('Rechenzeit fuer fib(%d) von fib_pot(): %2f\n', n, endzeit-anfangszeit) #---------------------------------------------------------------------------------------- n Rechenzeit fib direkt(n): Rechenzeit fib pot(n): 1234 0.0002 s 0.0001 s 123456 0.227 s 0.007 s 1234567 18.14 s 0.28 s 4321234 223 s 2s 2.3.28 Zufällige Texte erzeugen Das Beispiel auf den folgenden Seiten führt zum Erzeugen eines zufälligen Satzes. Dazu wird § eine Textdatei eingelesen (z.B. Goethes Faust oder Joyces Ulysses) § alle Paare von aufeinanderfolgenden Wörtern gefunden § ein Satz erzeugt, in dem jedes Paar aufeinanderfolgender Wörter auch in der gelesenen Textdatei vorkommt. Direktes Einlesen von Dateien Man kann Dateien zeilenweise als Strings einlesen. def zeilenweise(dateiname): # Lies Datei dateiname zeilenweise in ein Array text_zeilenweise = open(dateiname).readlines() return text_zeilenweise def test(): tzw = zeilenweise("SchillerBriefe3.txt") for i in range(len(tzw)): print i, tzw[i] if __name__ == '__main__': test() 2.3.30 Die Funktion einstring(d) macht einen String aus der ganzen Datei. # einlesen.py import string def einstring(dateiname): # Lies Datei dateiname als einen (langen) String datei = open(dateiname,"r") text_als_ein_string = '' for zeile in datei: text_als_ein_string += string.rstrip(zeile) datei.close() return text_als_ein_string def test(): taes = einstring("SchillerBriefe3.txt") print taes if __name__ == '__main__': test() Die Funktion string.rstrip(z) entfernt Leerzeichen und Zeilenumbrüche am Ende des Strings z. 2.3.31 Zerlegen eines Textes in Wörter Die folgende Funktion zerlege.in woerter § erhält als Eingabe einen String, der z.B. durch Einlesen eines Textes aus einer Datei entstanden ist, § ersetzt alle Satzendezeichen durch . “ ” (dadurch wird der Punkt durch Leerzeichen vom Rest getrennt), § entfernt Zeichen aus dem String, die wir nicht brauchen, und § trennt den String an allen (Folgen von) Leerzeichen auf und erhält dadurch ein Array aus den Teilen des Strings (d.h. den Wörtern), die zwischen zwei Leerzeichen stehen. In dem Array stehen die Wörter in der gleichen Reihenfolge wie im Text. Als Ergebnis wird das Array der Wörter zurückgegeben. 2.3.32 #-------------------------------------------------------# zerlege.py #-------------------------------------------------------import string, einlesen def in_woerter(text): # Ersetze alle Satzendezeichen durch " ." for zeichen in "?!.": text = string.replace(text,zeichen," .") # Ersetze störende Zeichen durch " ". for zeichen in '''"',;:-)([]<>*#_\n\t\r''': text = string.replace(text,zeichen," ") # Zerlege den Text in Wörter. woerter = string.split(text,' ') # Entferne "leere Wörter". woerterliste = [] for i in range(len(woerter)): if len(woerter[i])>=1: woerterliste.append(woerter[i]) return woerterliste def test(): text = einlesen.einstring("SchillerBriefe.txt") woerter = in_woerter(text) print woerter if __name__ == '__main__': test() 2.3.33 Dictionaries Ein Dictionary ist eine Liste aus Paaren ’key’:’value’. Der value kann über den key angesprochen werden. # woerterbuch.py # Erzeuge ein leeres Dictionary. woerterbuch = {} # Trage Wortpaare ein. woerterbuch['Aal'] = 'eal' woerterbuch['Adler'] = 'eagle' woerterbuch['Buch'] = 'book' print woerterbuch for i in woerterbuch: print i, woerterbuch[i] #----------------------------------------------------------------------------------# python woerterbuch.py # {'Aal': 'eal', 'Buch': 'book', 'Adler': 'eagle'} # Aal eal # Buch book # Adler eagle 2.3.34 Erzeugen eines Dictionaries mit aufeinanderfolgenden Wörtern eines Textes Die Aufbereitung des Textes wird abgeschlossen mit der Erstellung eines Dictionaries, in dem zu jedem Wort x des Textes ein Array aller Wörter steht, die im Text auf x folgen. Der Dictionary-Eintrag ’heute’ : [’ist’, ’wird’, ’ist’, ’ein’, ’mal’, ’.’] steht dafür, dass im Text auf das Wort heute zweimal das Wort ist und jeweils einmal das Wort wird, ein bzw. mal und einmal das Satzendezeichen . folgt. #-------------------------------------------------------# wortpaare.py #-------------------------------------------------------import sys, string, zerlege, einlesen def konkordanz(woerterliste): wortfolgen = {} for i in range(len(woerterliste)-1): if not woerterliste[i] in wortfolgen: wortfolgen[woerterliste[i]] = [] wortfolgen[woerterliste[i]].append(woerterliste[i+1]) return wortfolgen def test(): text = einlesen.einstring(sys.argv[1]) woerter = zerlege.in_woerter(text) liste = konkordanz(woerter) print liste if __name__ == '__main__': test() Aus einem Text wird ein Dictionary erzeugt, das zu jedem Wort im Text einen String mit allen seinen direkten Folgewörtern enthält. 2.3.36 Erzeugen eines Satzes wie im Text“ ” Schließlich können wir einen Satz erzeugen. Wir beginnen mit einem Wort, das hinter einem .“ steht. ” Anschließend wählen wir aus dem Dictionary wiederholt ein Folgewort aus, bis wir wieder bei einem .“ ankommen. ” 2.3.37 # schreibe.py import sys, string, zerlege, einlesen, wortpaare, random def schreibe_einen_satz(k): satzende = False satz = '' wort = "." while not satzende: wuerfelwurf = random.randrange(len(k[wort])) wort = k[wort][wuerfelwurf] if wort==".": satzende = True else: satz = satz + ' ' + wort return satz def test(): text = einlesen.einstring(sys.argv[1]) woerter = zerlege.in_woerter(text) liste = wortpaare.konkordanz(woerter) print schreibe_einen_satz(liste) + "." if __name__ == '__main__': test() 2.3.38 Zusammenfassung § Rekursion ist ein nützliches Hilfsmittel zum Schreiben schneller Programme. Wir haben Beispiele gesehen, bei denen man mittels Rekursion recht einfach sehr viel schnellere Lösungen programmieren kann als ohne Rekursion. Wir haben aber auch ein Beispiel gesehen, bei dem Rekursion nicht direkt geholfen hat. § Es ist sehr hilfreich, Programme gut zu strukturieren, damit man ihre Teile als Module nutzen kann. 2.3.39 2.4 Anwendungsbeispiel: Durchlässigkeit Es soll ein Modell für Durchlässigkeit z.B. bei Versickerung von Wasser im Boden oder Leitfähigkeit von gemischten Materialien für Strom oder Wärme aufgestellt und untersucht werden. 2.4.1 Das Modell und die Aufgabe Das Material besteht aus durchlässigen und undurchlässigen Teilen. Wir stellen uns vor, dass Wasser von oben nach unten durchgelassen werden soll. p“ 38 64 , durchlässig Das Material stellen wir uns aus n ˆ n Zellen bestehend vor. Die Dichte des Materials ist der Anteil an durchlässigen Zellen. Wir wollen feststellen, wie die Dichte die Durchlässigkeit beeinflusst, d.h. wie wahrscheinlich die Durchlässigkeit abhängig von der Dichte ist. 2.4.2 Das Modell und die Aufgabe Das Material besteht aus durchlässigen und undurchlässigen Teilen. Wir stellen uns vor, dass Wasser von oben nach unten durchgelassen werden soll. p“ 38 64 , durchlässig Das Material stellen wir uns aus n ˆ n Zellen bestehend vor. Die Dichte des Materials ist der Anteil an durchlässigen Zellen. Wir wollen feststellen, wie die Dichte die Durchlässigkeit beeinflusst, d.h. wie wahrscheinlich die Durchlässigkeit abhängig von der Dichte ist. 2.4.2 Das Modell und die Aufgabe Das Material besteht aus durchlässigen und undurchlässigen Teilen. Wir stellen uns vor, dass Wasser von oben nach unten durchgelassen werden soll. p“ 38 64 , durchlässig Das Material stellen wir uns aus n ˆ n Zellen bestehend vor. Die Dichte des Materials ist der Anteil an durchlässigen Zellen. Wir wollen feststellen, wie die Dichte die Durchlässigkeit beeinflusst, d.h. wie wahrscheinlich die Durchlässigkeit abhängig von der Dichte ist. 2.4.2 Das Modell und die Aufgabe Das Material besteht aus durchlässigen und undurchlässigen Teilen. Wir stellen uns vor, dass Wasser von oben nach unten durchgelassen werden soll. p“ 38 64 , durchlässig Das Material stellen wir uns aus n ˆ n Zellen bestehend vor. Die Dichte des Materials ist der Anteil an durchlässigen Zellen. Wir wollen feststellen, wie die Dichte die Durchlässigkeit beeinflusst, d.h. wie wahrscheinlich die Durchlässigkeit abhängig von der Dichte ist. 2.4.2 Das Modell und die Aufgabe Das Material besteht aus durchlässigen und undurchlässigen Teilen. Wir stellen uns vor, dass Wasser von oben nach unten durchgelassen werden soll. p“ 38 64 , durchlässig Das Material stellen wir uns aus n ˆ n Zellen bestehend vor. Die Dichte des Materials ist der Anteil an durchlässigen Zellen. Wir wollen feststellen, wie die Dichte die Durchlässigkeit beeinflusst, d.h. wie wahrscheinlich die Durchlässigkeit abhängig von der Dichte ist. 2.4.2 Das Modell und die Aufgabe Das Material besteht aus durchlässigen und undurchlässigen Teilen. Wir stellen uns vor, dass Wasser von oben nach unten durchgelassen werden soll. p“ 38 64 , durchlässig Das Material stellen wir uns aus n ˆ n Zellen bestehend vor. Die Dichte des Materials ist der Anteil an durchlässigen Zellen. Wir wollen feststellen, wie die Dichte die Durchlässigkeit beeinflusst, d.h. wie wahrscheinlich die Durchlässigkeit abhängig von der Dichte ist. 2.4.2 Das Modell und die Aufgabe Das Material besteht aus durchlässigen und undurchlässigen Teilen. Wir stellen uns vor, dass Wasser von oben nach unten durchgelassen werden soll. p“ 38 64 , durchlässig Das Material stellen wir uns aus n ˆ n Zellen bestehend vor. Die Dichte des Materials ist der Anteil an durchlässigen Zellen. Wir wollen feststellen, wie die Dichte die Durchlässigkeit beeinflusst, d.h. wie wahrscheinlich die Durchlässigkeit abhängig von der Dichte ist. 2.4.2 Das Modell und die Aufgabe Das Material besteht aus durchlässigen und undurchlässigen Teilen. Wir stellen uns vor, dass Wasser von oben nach unten durchgelassen werden soll. p“ 38 64 , durchlässig Das Material stellen wir uns aus n ˆ n Zellen bestehend vor. Die Dichte des Materials ist der Anteil an durchlässigen Zellen. Wir wollen feststellen, wie die Dichte die Durchlässigkeit beeinflusst, d.h. wie wahrscheinlich die Durchlässigkeit abhängig von der Dichte ist. 2.4.2 Das Modell und die Aufgabe Das Material besteht aus durchlässigen und undurchlässigen Teilen. Wir stellen uns vor, dass Wasser von oben nach unten durchgelassen werden soll. p“ 38 64 , durchlässig Das Material stellen wir uns aus n ˆ n Zellen bestehend vor. Die Dichte des Materials ist der Anteil an durchlässigen Zellen. Wir wollen feststellen, wie die Dichte die Durchlässigkeit beeinflusst, d.h. wie wahrscheinlich die Durchlässigkeit abhängig von der Dichte ist. 2.4.2 Das Modell und die Aufgabe Das Material besteht aus durchlässigen und undurchlässigen Teilen. Wir stellen uns vor, dass Wasser von oben nach unten durchgelassen werden soll. p“ 38 64 , durchlässig p“ 38 64 , undurchlässig Das Material stellen wir uns aus n ˆ n Zellen bestehend vor. Die Dichte des Materials ist der Anteil an durchlässigen Zellen. Wir wollen feststellen, wie die Dichte die Durchlässigkeit beeinflusst, d.h. wie wahrscheinlich die Durchlässigkeit abhängig von der Dichte ist. 2.4.2 Das Modell und die Aufgabe Das Material besteht aus durchlässigen und undurchlässigen Teilen. Wir stellen uns vor, dass Wasser von oben nach unten durchgelassen werden soll. p“ 38 64 , durchlässig p“ 38 64 , undurchlässig Das Material stellen wir uns aus n ˆ n Zellen bestehend vor. Die Dichte des Materials ist der Anteil an durchlässigen Zellen. Wir wollen feststellen, wie die Dichte die Durchlässigkeit beeinflusst, d.h. wie wahrscheinlich die Durchlässigkeit abhängig von der Dichte ist. 2.4.2 Das Modell und die Aufgabe Das Material besteht aus durchlässigen und undurchlässigen Teilen. Wir stellen uns vor, dass Wasser von oben nach unten durchgelassen werden soll. p“ 38 64 , durchlässig p“ 38 64 , undurchlässig Das Material stellen wir uns aus n ˆ n Zellen bestehend vor. Die Dichte des Materials ist der Anteil an durchlässigen Zellen. Wir wollen feststellen, wie die Dichte die Durchlässigkeit beeinflusst, d.h. wie wahrscheinlich die Durchlässigkeit abhängig von der Dichte ist. 2.4.2 Das Modell und die Aufgabe Das Material besteht aus durchlässigen und undurchlässigen Teilen. Wir stellen uns vor, dass Wasser von oben nach unten durchgelassen werden soll. p“ 38 64 , durchlässig p“ 38 64 , undurchlässig Das Material stellen wir uns aus n ˆ n Zellen bestehend vor. Die Dichte des Materials ist der Anteil an durchlässigen Zellen. Wir wollen feststellen, wie die Dichte die Durchlässigkeit beeinflusst, d.h. wie wahrscheinlich die Durchlässigkeit abhängig von der Dichte ist. 2.4.2 Das Modell und die Aufgabe Das Material besteht aus durchlässigen und undurchlässigen Teilen. Wir stellen uns vor, dass Wasser von oben nach unten durchgelassen werden soll. p“ 38 64 , durchlässig p“ 38 64 , undurchlässig Das Material stellen wir uns aus n ˆ n Zellen bestehend vor. Die Dichte des Materials ist der Anteil an durchlässigen Zellen. Wir wollen feststellen, wie die Dichte die Durchlässigkeit beeinflusst, d.h. wie wahrscheinlich die Durchlässigkeit abhängig von der Dichte ist. 2.4.2 Das Modell und die Aufgabe Das Material besteht aus durchlässigen und undurchlässigen Teilen. Wir stellen uns vor, dass Wasser von oben nach unten durchgelassen werden soll. p“ 38 64 , durchlässig p“ 38 64 , undurchlässig Das Material stellen wir uns aus n ˆ n Zellen bestehend vor. Die Dichte des Materials ist der Anteil an durchlässigen Zellen. Wir wollen feststellen, wie die Dichte die Durchlässigkeit beeinflusst, d.h. wie wahrscheinlich die Durchlässigkeit abhängig von der Dichte ist. 2.4.2 Die Aufteilung der Aufgabe – grober Versuch § Prüfe, ob eine Materialprobe durchlässig ist. § Produziere Materialproben abhängig von der Dichte. § Stelle für eine gegebene Dichte fest, wie groß die Wahrscheinlichkeit der Durchlässigkeit seiner Materialproben sind. § Untersuche das allgemein für jede Dichte. Materialproben werden in jedem Teil verwendet. Wir benutzen 2-dimensionale Arrays (gleichviele Zeilen und Spalten) mit Einträgen vom Typ bool für die Materialproben. Eintrag False steht für eine undurchlässige Zelle, und Eintrag True steht für eine durchlässige Zelle. (1) Prüfe, ob eine Materialprobe durchlässig ist Zum Prüfen, ob eine Materialprobe durchlässig ist, § setzt man sie unter Wasser (d.h. man lässt Wasser von oben nach unten durchlaufen) und § schaut nach, ob ganz unten Wasser angekommen ist. Das unter-Wasser-setzen liefert als Ergebnis ein Array der gleichen Größe wir die Materialprobe, bei dem in jeder Zelle steht, ob sie vom Wasser erreicht wurde (False/True). Damit können wir ein Gerüst für das Modul entwerfen. Das Gerüst für das Modul pruefe.py #--------------------------------------------------# pruefe0.py # Das Gerüst für ein Modul zum Prüfen der # Durchlässigkeit einer Materialprobe. #--------------------------------------------------import stdio, sys, stdarray def waessere(durchlaessige_Zellen): n = len(durchlaessige_Zellen) gewaesserte_Zellen = stdarray.create2D(n,n,False) # # hier fehlt noch das Programm # return gewaesserte_Zellen def ist_durchlaessig(material_probe): n = len(material_probe) gewaesserte_Zellen = waessere(material_probe) for s in range(n): if gewaesserte_Zellen[n-1][s]: return True return False def modultest(): probe = stdarray.readBool2D() stdarray.write2D(waessere(probe)) stdio.writeln(ist_durchlaessig(probe)) # # # # # # # # # # # # # # # # # # # # # # more test8a.txt 8 8 0 0 1 1 1 0 1 0 1 0 0 1 1 1 1 1 1 1 1 0 1 1 0 0 0 0 1 1 1 1 1 1 1 0 0 0 1 1 1 0 0 1 1 1 1 0 0 1 0 1 0 1 0 1 1 0 0 1 0 1 0 1 1 0 python pruefe0.py < test8a.txt 8 8 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 False 2.4.5 Entwicklung der Funktion waessere() Beim Wässern einer Materialprobe setzt man zuerst alle durchlässigen Zellen der obersten Schicht unter Wasser. #------------------------------------------------------------- # # pruefe1.py # # Arbeit im Gerüst des Moduls zum Prüfen der Durchlässigkeit # # einer Materialprobe an der Funktion waessere(). # #------------------------------------------------------------ # import stdio, sys, stdarray # # def waessere(durchlaessige_Zellen): # n = len(durchlaessige_Zellen) # gewaesserte_Zellen = stdarray.create2D(n,n,False) # # # in Zeile 0 laufen alle durchlaessigen Zellen voll # # for i in range(n): # gewaesserte_Zellen[0][i] = durchlaessige_Zellen[0][i] # # # # # hier fehlt noch der Rest des Programms # # # return gewaesserte_Zellen # # ... # ... more test8a.txt 8 8 0 0 1 1 1 0 1 0 1 0 0 1 1 1 1 1 1 1 1 0 1 1 0 0 0 0 1 1 1 1 1 1 1 0 0 0 1 1 1 0 0 1 1 1 1 0 0 1 0 1 0 1 0 1 1 0 0 1 0 1 0 1 1 0 python pruefe1.py < test8a.txt 8 8 0 0 1 1 1 0 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 False Dann lassen wir von oben nach unten in allen Schichten jetzt erstmal nur das Wasser in die direkt darunterliegenden Zellen versickern. # more test8a.txt # 8 8 # 0 0 1 1 1 0 1 0 # 1 0 0 1 1 1 1 1 #------------------------------------------------------------# 1 1 1 0 1 1 0 0 # pruefe2.py # 0 0 1 1 1 1 1 1 # Arbeit im Gerüst des Moduls zum Prüfen der Durchlässigkeit # 1 0 0 0 1 1 1 0 # einer Materialprobe an der Funktion waessere(). # 0 1 1 1 1 0 0 1 #-----------------------------------------------------------# 0 1 0 1 0 1 1 0 import stdio, sys, stdarray # 0 1 0 1 0 1 1 0 # def waessere(durchlaessige_Zellen): # python pruefe2.py < test8a.txt n = len(durchlaessige_Zellen) # 8 8 gewaesserte_Zellen = stdarray.create2D(n,n,False) # 0 0 1 1 1 0 1 0 # 0 0 0 1 1 0 1 0 # in Zeile 0 laufen alle durchlaessigen Zellen voll # 0 0 0 0 1 0 0 0 for i in range(n): # 0 0 0 0 1 0 0 0 gewaesserte_Zellen[0][i] = durchlaessige_Zellen[0][i] # 0 0 0 0 1 0 0 0 # 0 0 0 0 1 0 0 0 for z in range(1,n): # 0 0 0 0 0 0 0 0 # in Schichten 1..n-1 laufen zuerst alle offenen Zellen voll, # 0 0 0 0 0 0 0 0 # bei denen es eine darüberliegende gewässerte Zelle gibt # False for s in range(n): gewaesserte_Zellen[z][s] = durchlaessige_Zellen[z][s] and gewaesserte_Zellen[z-1][s] # # hier fehlt noch der Rest des Programms # return gewaesserte_Zellen 2.4.7 Nachdem Wasser von oben in eine Schicht gesickert ist, verteilt es sich von jeder Zelle in der Schicht nach rechts (und danach nach links). #----------------------------------------------------------------------------------# pruefe3.py # Arbeit im Gerüst des Moduls zum Prüfen der Durchlässigkeit einer Materialprobe. #----------------------------------------------------------------------------------import stdio, sys, stdarray def waessere(durchlaessige_Zellen): n = len(durchlaessige_Zellen) gewaesserte_Zellen = stdarray.create2D(n,n,False) # In Schicht 0 laufen alle durchlässigen Zellen voll. for z in range(n): gewaesserte_Zellen[0][z] = durchlaessige_Zellen[0][z] for s in range(1,n): # In Schichten 1..n-1 laufen zuerst alle offenen Zellen voll, # bei denen es eine darüberliegende gewässerte Zelle gibt. for z in range(n): gewaesserte_Zellen[s][z] = durchlaessige_Zellen[s][z] and gewaesserte_Zellen[s-1][z] # Danach laufen in der Schicht alle Zellen voll, # die durchlässig sind und einen gewässerten linken Nachbarn haben. # Dazu geht man die Schicht von links nach rechts durch. for z in range(1,n): gewaesserte_Zellen[s][z] = gewaesserte_Zellen[s][z] or (durchlaessige_Zellen[s][z] and gewaesserte_Zellen[s][z-1]) # der Rest fehlt noch 2.4.8 Nachdem Wasser von oben in eine Schicht gesickert ist, verteilt es sich von jeder Zelle in der Schicht abschließend danach nach links. Damit ist das Modul pruefe.py fertig. #----------------------------------------------------------------------------------# pruefe.py # Modul zum Testen der Durchlässigkeit einer Materialprobe. #----------------------------------------------------------------------------------import stdio, sys, stdarray def waessere(durchlaessige_Zellen): n = len(durchlaessige_Zellen) gewaesserte_Zellen = stdarray.create2D(n,n,False) # In Schicht 0 laufen alle durchlässigen Zellen voll. ... for s in range(1,n): # In Schicht s laufen die Zellen zuerst von oben und dann von links nach rechts voll. ... # Abschließend laufen in der Schicht alle Zellen voll, # die durchlässig sind und einen gewässerten rechten Nachbarn haben. # Dazu geht man die Schicht von rechts nach links durch. for z in range(n-2,-1,-1): gewaesserte_Zellen[s][z] = gewaesserte_Zellen[s][z] or (durchlaessige_Zellen[s][z] and gewaesserte_Zellen[s][z+1]) return gewaesserte_Zellen ... 2.4.9 Das API für das Modul pruefe.py API . . . application programming interface Es wird angegeben, welche Funktionen des Moduls benutzt werden können, mit welchen Parametern sie aufgerufen werden und was sie als Ergebnis liefern. API für pruefe.py: waessere(m) ist durchlaessig(m) setzt Materialprobe m unter Wasser und gibt ein Array bool[][] zurück, in dem alle Zellen, in denen Wasser steht, True sind liefert bool bestimmt, ob Materialprobe m durchlässig ist m ist ein n ˆ n Array bool[][] 2.4.10 (1a) Was beim Programmieren von pruefe.py passiert sein kann . . . Beim Programmieren sollte man stets mit verschiedenen Daten (hier: Materialproben) testen. Dabei ist die Darstellung von Arrays mit stdio nicht gut lesbar und es ist optisch nicht so leicht zu erkennen, ob das Programm alles richtig macht. Da schafft die schöne graphische Darstellung von gewässerten Materialproben Abhilfe. Das machen wir in einem weiteren Modul. #-------------------------------------------------------# zeige.py #-------------------------------------------------------import stdarray, stddraw, pruefe def wasserstand(Probe,gewaesserte_Zellen,zeit=0): n = len(Probe) stddraw.setXscale(-1,n) python zeige.py < test8a.txt stddraw.setYscale(-1,n) # Jede Zelle wird gemalt: schwarz (undurchlässig), # blau (gewässert) oder weiß (durchlässig und ungewässert). for schicht in range(n): for z in range(n): if not Probe[schicht][z]: stddraw.setPenColor(stddraw.BLACK) else: if gewaesserte_Zellen[schicht][z]: stddraw.setPenColor(stddraw.BLUE) else: stddraw.setPenColor(stddraw.WHITE) stddraw.filledSquare(z,n-schicht-1,0.5) if zeit>0: stddraw.show(zeit) else: stddraw.show() def test(): Probe = stdarray.readBool2D() n = len(Probe) wasserstand(Probe,pruefe.waessere(Probe)) if __name__ == '__main__': test() Das API für das Modul zeige.py wasserstand(m,g,z=0) stellt Materialprobe m mit gewässerten Zellen g für z Millisekunden im standard drawing window dar; falls z=0, wird die Darstellung nicht abgebrochen 2.4.13 (2) Erzeuge Materialproben Die Dichte p (0 ď p ď 1) gibt den Anteil der durchlässigen Zellen an. Eine Materialprobe mit Seitenlänge n hat n2 Zellen. Eine Materialprobe mit Seitenlänge n und Dichte p hat also p ¨ n2 Zellen mit Wert True und alle anderen Zellen haben Wert False. Zum Erzeugen einer Materialprobe mit Seitenlänge n und Dichte p gehen wir wie folgt vor: 1. Erzeuge ein banales“ 1-dimensionales Array mit Dichte p: ” die Einträge mit Indizes 0 bis p ¨ n2 sind True und die übrigen Einträge sind False. 2. Mische die Einträge mit stdrandom.shuffle(). 3. Speichere die gemischten Einträge der Reihe nach in eine 2-dimensionales Array um, das eine Materialprobe darstellt. #-------------------------------------------------------# materialprobe.py #-------------------------------------------------------import sys, stdrandom, stdarray, zeige python materialprobe.py 20 0.4 # Erzeuge eine zufällige Materialprobe # mit Seitenlänge n und Dichte p. def erzeugen(n, p): # VorProbe[] ist ein Array der Länge n*n mit Dichte p: # VorProbe[0:p*n*n] hat stets Wert True und # VorProbe[p*n*n:n*n] hat stets Wert False. VorProbe = stdarray.create1D(n*n,False) for s in range(int(round(p*n*n))): VorProbe[s] = True stdrandom.shuffle(VorProbe) python materialprobe.py 20 0.66 # Mache aus 1d-Array VorProbe nun das 2d-Array Probe. Probe = stdarray.create2D(n,n,False) for s in range(n): for z in range(n): if VorProbe[s*n+z]: Probe[s][z] = True return Probe def test(): n = int(sys.argv[1]) p = float(sys.argv[2]) zeige.wasserstand(erzeugen(n,p),stdarray.create2D(n,n,False)) if __name__ == '__main__': test() Das API für das Modul materialprobe.py erzeugen(n,p) liefert eine zufällige Materialprobe mit Seitenlänge n und Dichte p 2.4.16 Jetzt können wir mal eine Materialprobe wässern #-------------------------------------------------------------------# spiel1.py #-------------------------------------------------------------------import sys, materialprobe, pruefe, zeige # Erzeuge eine Materialprobe mit Seitenlänge n # und Dichte p, wässere sie und stelle sie dar. n = int(sys.argv[1]) p = float(sys.argv[2]) Probe = materialprobe.erzeugen(n,p) gewaesserte_Probe = pruefe.waessere(Probe) zeige.wasserstand(Probe,gewaesserte_Probe) #---------------------------------------------------------------------# python spiel1.py 200 0.65 2.4.17 (3) Stelle für eine Dichte fest, wie groß die Wahrscheinlichkeit der Durchlässigkeit seiner Materialproben sind. Erzeuge wiederholt Materialproben der gleichen Größe und mit der gleichen Dichte und zähle, wieviele davon durchlässig sind. 2.4.18 #------------------------------------------------------------------------------------------------# experiment.py #------------------------------------------------------------------------------------------------import sys, stdio, pruefe, materialprobe def w(n, p, wiederholungen): zaehler = 0 for i in range(wiederholungen): Probe = materialprobe.erzeugen(n, p) if pruefe.ist_durchlaessig(Probe): zaehler += 1 return zaehler/float(wiederholungen) def n p r test(): = int(sys.argv[1]) = float(sys.argv[2]) = int(sys.argv[3]) erg = w(n,p,r) stdio.writef('Versickerungswahrscheinlichkeitsabschaetzung bei n=%d und p=%f ist %f.\n', n, p, e if __name__ == '__main__': test() #------------------------------------------------------------------------------------------------# python experiment.py 50 0.3 10000 # Versickerungswahrscheinlichkeitsabschaetzung bei n=50 und p=0.300000 ist 0.000000. # # python experiment.py 50 0.4 10000 # Versickerungswahrscheinlichkeitsabschaetzung bei n=50 und p=0.400000 ist 0.000000. # # python experiment.py 50 0.7 10000 2.4.19 Ein erster Blick auf die Resultate Wahrscheinlichkeit w der Durchlässigkeit für 100000 Proben mit Seitenlänge n “ 50 in Abhängigkeit von der Dichte p. p w ppq 0.3 0.0 0.4 0.0 0.5 0.0002 0.6 0.47 0.7 0.99 0.8 1.0 Die Funktion w p q ist offensichtlich monoton wachsend (d.h. wenn Argument p größer wird, dann wird auch w ppq größer). Wie sieht der Graph der Funktion aus? Für welches p ist w ppq “ 0.5? 2.4.20 (4) Untersuche die Funktion w p q (für Materialproben mit Seitenlänge n “ 50) Zuerst wollen wir (möglichst genau) die Dichte p mit w ppq “ 0.5 finden. Wir wissen, dass w p q monoton wachsend ist, und dass das gesuchte p zwischen u “ 0 und o “ 1 liegt. Wir können nun wie folgt vorgehen: def p_suche(u,o,fehler): m = (u+o)/2 wenn w(m) höchstens Abstand fehler von 0.5 hat: dann return m sonst wenn w(m)>0.5: dann return p_suche(u,m,fehler) (d.h. suche in der unteren Hälfte) sonst return p_suche(m,o,fehler) (d.h. suche in der oberen Hälfte) def main(): p_suche(0,1,0.0001) 2.4.21 #------------------------------------------------------------------------------------------------# mitte.py #------------------------------------------------------------------------------------------------import sys, stdio, pruefe, experiment def p_suche(unten, oben, n, fehler, wiederholungen): # Finde die Dichte p mit w(p)=0.5 m = (unten+oben)/2 w_m = experiment.w(n,m,wiederholungen) if abs(w_m-0.5) < fehler: return m elif w_m > 0.5: return p_suche(unten, m, n, fehler, wiederholungen) else: return p_suche(m,oben, n, fehler, wiederholungen) def n f w test(): = int(sys.argv[1]) = float(sys.argv[2]) = int(sys.argv[3]) erg = p_suche(0.0,1.0,n,f,w) stdio.writef('Bei n=%d ist w(p)=0.5 (mit Fehler %f) bei p=%f.\n', n, f, erg) if __name__ == '__main__': test() #------------------------------------------------------------------------------------------------# python mitte.py 20 0.001 10000 # Bei n=20 ist w(p)=0.5 (mit Fehler 0.001000) bei p=0.593445. # # python mitte.py 50 0.001 10000 # Bei n=50 ist w(p)=0.5 (mit Fehler 0.001000) bei p=0.602295. # 2.4.22 Abschließend wollen wir den Graph der Funktion w p q zeichnen. Die einfachste Methode: für alle x “ 0.00 0.01 0.02 . . . 0.98 0.99 1.00: berechne w pxq zeichne den Strich von w px ´ 0.01q zu w pxq in die Grafik ein Nachteil: wenn die Berechnung eines Funktionswertes w pxq nur 1 Minute dauert, dann dauert diese Berechnung der Grafik 1000 Minuten (über 16 Stunden). Stattdessen: wir berechnen nur die für uns wichtigen Funktionswerte . . . Zuerst berechnen wir alle x-Werte, für die der Funktionswert w pxq gezeichnet werden soll, rekursiv. Dazu halbieren wir das Intervall, das gezeichnet werden soll, solange, bis der Abstand zwischen den x-Werten klein genug ist. 2.4.23 #--------------------------------------------------------------------------# graph1.py n r # Vorbereitung zum Zeichnen des Graphen. # Wir malen rekursiv von links nach rechts, bis die x-Werte # einen Minimalabstand erreichen. #--------------------------------------------------------------------------import experiment, sys, stddraw, stdio def kurve_zeichnen(x_li, y_li, x_re, y_re, n, wiedh=100, x_abstand): if ( x_re-x_li < x_abstand ): stddraw.line(x_li,y_li, x_re,y_re) stdio.writef('%f\n',y_re) stddraw.show(1) else: x_mi = (x_li+x_re) / 2.0 f_x_mi = experiment.w(n, x_mi, wiedh) kurve_zeichnen(x_li, y_li, x_mi, f_x_mi, n, wiedh, x_abstand) kurve_zeichnen(x_mi, f_x_mi, x_re, y_re, n, wiedh, x_abstand) def main(): n = int(sys.argv[1]) x_abstand = float(sys.argv[2]) python graph1.py 20 0.1 stddraw.setXscale(-0.05,1.05) stddraw.setYscale(-0.05,1.05) stddraw.setPenRadius(0.002) kurve_zeichnen(0.0, 0.0, 1.0, 1.0, n, 100, x_abstand) stddraw.show() 2.4.24 #--------------------------------------------------------------------------# graph1.py n r # Vorbereitung zum Zeichnen des Graphen. # Wir malen rekursiv von links nach rechts, bis die x-Werte # einen Minimalabstand erreichen. #--------------------------------------------------------------------------import experiment, sys, stddraw, stdio def kurve_zeichnen(x_li, y_li, x_re, y_re, n, wiedh=100, x_abstand): if ( x_re-x_li < x_abstand ): stddraw.line(x_li,y_li, x_re,y_re) stdio.writef('%f\n',y_re) stddraw.show(1) else: x_mi = (x_li+x_re) / 2.0 f_x_mi = experiment.w(n, x_mi, wiedh) kurve_zeichnen(x_li, y_li, x_mi, f_x_mi, n, wiedh, x_abstand) kurve_zeichnen(x_mi, f_x_mi, x_re, y_re, n, wiedh, x_abstand) def main(): n = int(sys.argv[1]) x_abstand = float(sys.argv[2]) python graph1.py 20 0.01 stddraw.setXscale(-0.05,1.05) stddraw.setYscale(-0.05,1.05) stddraw.setPenRadius(0.002) kurve_zeichnen(0.0, 0.0, 1.0, 1.0, n, 100, x_abstand) stddraw.show() 2.4.24 Nun bauen wir ein weiteres Kriterium für den Abbruch der Halbierung eines Intervalls ein: wenn der Funktionswert f x mi für den Mittelpunkt x mi des Intervalls nicht zu stark vom geschätzten Funktionswert y re ´ y li 2 abweicht, dann vermuten wir, dass die übrigen Fuktionswerte im Intervall auch mittels dieser Abschätzung gut genug bestimmt werden können. Die geduldete Abweichung steht in Variable fehler. 2.4.25 #--------------------------------------------------------------------------------# graph.py #--------------------------------------------------------------------------------import experiment, sys, stddraw def kurve_zeichnen(x_li, y_li, x_re, y_re, n, wiedh=100, x_abstand=0.01, fehler=0.0025): x_mi = (x_li+x_re) / 2.0 y_mi = (y_li+y_re) / 2.0 f_x_mi = experiment.w(n, x_mi, wiedh) if ( x_re-x_li < x_abstand ) or ( abs(y_mi-f_x_mi) < fehler ): stddraw.line(x_li,y_li, x_re,y_re) stddraw.show(1) else: kurve_zeichnen(x_li, y_li, x_mi, f_x_mi, n, wiedh, x_abstand, fehler) kurve_zeichnen(x_mi, f_x_mi, x_re, y_re, n, wiedh, x_abstand, fehler) def main(): n = int(sys.argv[1]) w = int(sys.argv[2]) stddraw.setXscale(-0.05,1.05) stddraw.setYscale(-0.05,1.05) stddraw.setPenRadius(0.002) kurve_zeichnen(0.0, 0.0, 1.0, 1.0, n, w) stddraw.show() if __name__ == '__main__': main() 2.4.26 Zusammenfassung § Modulares Programmieren (Systeme aus kleinen Programmen zusammensetzen) ist praktisch. § Fehlersuche in kleinen Modulen ist einfacher als in großen Programmen. § Entwickele das Programm inkrementell. § Rekursion kann sehr nützlich sein. § Programmiere ggf. Werkzeuge, die beim Programmieren helfen. Modulares Programmieren ist (nur) der Anfang. Moderne Programmiersprachen bieten noch viele weitere Möglichkeiten. Das kommt dann in der Vorlesung im nächsten Semester . . . 4 Algorithmen Wir haben bisher Programme zur Lösung verschiedener Probleme geschrieben. Jedes Programm basiert auf einer Idee (Algorithmus), die man in jeder Programmierprache aufschreiben kann. Beim Entwickeln von Algorithmen geht es u.a. darum, möglichst schnelle Algorithmen mit wenig Speicherplatzbedarf zu finden. In diesem Kapitel schauen wir uns zuerst an, wie man feststellt, wie schnell“ ” ein Algorithmus ist. Anschließend schauen wir uns Algorithmen zum Sortieren an, die (viel) schneller als das Sortieren durch wiederholte Suche des Minimums ist. Abschließend betrachten wir noch Algorithmen zur Wegesuche in Graphen. 4. Algorithmen 4.1 Rechenzeit 4.2 Schnelles Sortieren 4.3 Suche nach Wegen in Graphen 4.1 Rechenzeit von Programmen und Zeit-Komplexität von Problemen Wir haben z.B. bei der Berechnung von Fibonacci-Zahlen gesehen, dass verschiedene Programme sehr unterschiedlich lang brauchen. Wir wollen diese Zeit messen. #--------------------------------------------# miss-fib.py # Misst die Rechenzeit fuers rekursive Berechnen # der Fibonaccizahlen. # time.time() gibt die aktuelle Zeit an. #-------------------------------------------import stdio, sys, time, fibo_rekursiv oberschranke = int(sys.argv[1]) for n in range(1,50): anfangszeit = time.time() fibo_rekursiv.fib(n) endzeit = time.time() rechenzeit = endzeit - anfangszeit stdio.writef('%d %f\n', n, rechenzeit) if rechenzeit > oberschranke: break # python miss-fib.py 10 # 1 0.000002 # 2 0.000002 # 3 0.000002 ... # 15 0.000405 # 16 0.000654 # 17 0.001052 ... # 32 1.441119 # 33 2.333346 # 34 3.771449 # 35 6.112324 # 36 9.876957 # 37 16.031817 Beobachtungen Die Rechenzeit von Programmen wächst mit der Größe der Eingabe. Beispiel 1: Berechnung der Fibonacci-Zahlen def fib(n): if n==0 or n==1: return n else: return fib(n-1) + fib(n-2) Es wird die Rechenzeit in Sekunden abhängig vom Eingabewert n dargestellt. Hängt die Kurve vom Rechner ab? 4.1.2 Beobachtungen Die Rechenzeit von Programmen wächst mit der Größe der Eingabe. Beispiel 1: Berechnung der Fibonacci-Zahlen def fib(n): if n==0 or n==1: return n else: return fib(n-1) + fib(n-2) Es wird die Rechenzeit in Sekunden abhängig vom Eingabewert n dargestellt. Hängt die Kurve vom Rechner ab? Der grobe Verlauf“ der Kurven ist gleich. ” 4.1.2 Beobachtungen Die Rechenzeit von Programmen wächst mit der Größe der Eingabe. Beispiel 1: Berechnung der Fibonacci-Zahlen def fib(n): if n==0 or n==1: return n else: return fib(n-1) + fib(n-2) Es wird die Rechenzeit in Sekunden abhängig vom Eingabewert n dargestellt. Hängt die Kurve vom Rechner ab? Der grobe Verlauf“ der Kurven ist gleich. ” 4.1.2 Beobachtungen Die Rechenzeit von Programmen wächst mit der Größe der Eingabe. Beispiel 1: Berechnung der Fibonacci-Zahlen def fib(n): if n==0 or n==1: return n else: return fib(n-1) + fib(n-2) Es wird die Rechenzeit in Sekunden abhängig vom Eingabewert n dargestellt. Hängt die Kurve vom Rechner ab? Der grobe Verlauf“ der Kurven ist gleich. ” Er ist ähnlich der Kurve von f pnq “ 2n . 4.1.2 Beobachtungen Die Rechenzeit von Programmen wächst mit der Größe der Eingabe. Beispiel 1: Berechnung der Fibonacci-Zahlen def fib(n): if n==0 or n==1: return n else: return fib(n-1) + fib(n-2) Es wird die Rechenzeit in Sekunden abhängig vom Eingabewert n dargestellt. Hängt die Kurve vom Rechner ab? Der grobe Verlauf“ der Kurven ist gleich. ” Er ist ähnlich der Kurve von f pnq “ 2n . Und ziemlich genau der der Kurve von f pnq “ 0.00000155 ¨ 1.61n 4.1.2 Beobachtungen Die Rechenzeit von Programmen wächst mit der Größe der Eingabe. Beispiel 1: Berechnung der Fibonacci-Zahlen def fib(n): if n==0 or n==1: return n else: return fib(n-1) + fib(n-2) Es wird die Rechenzeit in Sekunden abhängig vom Eingabewert n dargestellt. Hängt die Kurve vom Rechner ab? Der grobe Verlauf“ der Kurven ist gleich. ” Er ist ähnlich der Kurve von f pnq “ 2n . Und ziemlich genau der der Kurve von f pnq “ 0.00000155 ¨ 1.61n bzw. f pnq “ 0.0000004 ¨ 1.61n . 4.1.2 Beobachtungen Die Rechenzeit von Programmen wächst mit der Größe der Eingabe. Beispiel 1: Berechnung der Fibonacci-Zahlen def fib(n): if n==0 or n==1: return n else: return fib(n-1) + fib(n-2) Es wird die Rechenzeit in Sekunden abhängig vom Eingabewert n dargestellt. Hängt die Kurve vom Rechner ab? Der grobe Verlauf“ der Kurven ist gleich. ” Er ist ähnlich der Kurve von f pnq “ 2n . Und ziemlich genau der der Kurve von f pnq “ 0.00000155 ¨ 1.61n bzw. f pnq “ 0.0000004 ¨ 1.61n . Alle Kurven sind anscheinend welche von Funktionen f pnq “ c ¨ 1.61n für verschiedene c. n Deshalb sagen wir: die Rechenzeit von fibpnq ist „ 1.61 . 4.1.2 Beispiel 2: Quadrieren einer Matrix def quadratM(A): C = stdarray.create2D(len(A),len(A),0) for z in range(len(A)): for s in range(len(A)): for i in range(len(A)): C[z][s] += A[z][i]*A[i][s] return C Zuerst messen wir die Zeit für das Quadrieren einer Matrix mit 200 Zeilen und Spalten, wobei wir größer werdende Werte in die Matrix eintragen. Die Rechenzeiten bleiben ziemlich gleich: sie hängen kaum“ von der Größe ” der zu multiplizierenden Zahlen ab. 4.1.3 Beispiel 2: Quadrieren einer Matrix def quadratM(A): C = stdarray.create2D(len(A),len(A),0) for z in range(len(A)): for s in range(len(A)): for i in range(len(A)): C[z][s] += A[z][i]*A[i][s] return C Zuerst messen wir die Zeit für das Quadrieren einer Matrix mit 200 Zeilen und Spalten, wobei wir größer werdende Werte in die Matrix eintragen. Die Rechenzeiten bleiben ziemlich gleich: sie hängen kaum“ von der Größe ” der zu multiplizierenden Zahlen ab. Messen wir dagegen die Zeit abhängig von der Seitenlänge der Matrix, steigt die Rechenzeit stark an. 4.1.3 Beispiel 2: Quadrieren einer Matrix def quadratM(A): C = stdarray.create2D(len(A),len(A),0) for z in range(len(A)): for s in range(len(A)): for i in range(len(A)): C[z][s] += A[z][i]*A[i][s] return C Zuerst messen wir die Zeit für das Quadrieren einer Matrix mit 200 Zeilen und Spalten, wobei wir größer werdende Werte in die Matrix eintragen. Die Rechenzeiten bleiben ziemlich gleich: sie hängen kaum“ von der Größe ” der zu multiplizierenden Zahlen ab. Messen wir dagegen die Zeit abhängig von der Seitenlänge der Matrix, steigt die Rechenzeit stark an. Wir vermuten, dass die Kurve der von f pnq “ n3 ähnelt. Die Rechenzeit von quadratMpAq ist „ n3 . Die Eingabegröße n ist dabei die Seitenlänge der Matrix A. 4.1.3 Beispiel 2: Quadrieren einer Matrix def quadratM(A): C = stdarray.create2D(len(A),len(A),0) for z in range(len(A)): for s in range(len(A)): for i in range(len(A)): C[z][s] += A[z][i]*A[i][s] return C Zuerst messen wir die Zeit für das Quadrieren einer Matrix mit 200 Zeilen und Spalten, wobei wir größer werdende Werte in die Matrix eintragen. Die Rechenzeiten bleiben ziemlich gleich: sie hängen kaum“ von der Größe ” der zu multiplizierenden Zahlen ab. Messen wir dagegen die Zeit abhängig von der Seitenlänge der Matrix, steigt die Rechenzeit stark an. Wir vermuten, dass die Kurve der von f pnq “ n3 ähnelt. Die Rechenzeit von quadratMpAq ist „ n3 . Die Eingabegröße n ist dabei die Seitenlänge der Matrix A. 4.1.3 Beispiel 3: Sortieren eines Arrays mit MinSort def minsort(liste): for i in range(len(liste)-1): min = i for j in range(i+1,len(liste)): if liste[min] > liste[j]: min = j t = liste[min] liste[min] = liste[i] liste[i] = t return liste Wir nehmen als Eingabegröße n gleich die Länge des Arrays. 4.1.4 Beispiel 3: Sortieren eines Arrays mit MinSort def minsort(liste): for i in range(len(liste)-1): min = i for j in range(i+1,len(liste)): if liste[min] > liste[j]: min = j t = liste[min] liste[min] = liste[i] liste[i] = t return liste Wir nehmen als Eingabegröße n gleich die Länge des Arrays. Der Vergleich mit f pnq “ n3 scheint nicht zu gehen. 4.1.4 Beispiel 3: Sortieren eines Arrays mit MinSort def minsort(liste): for i in range(len(liste)-1): min = i for j in range(i+1,len(liste)): if liste[min] > liste[j]: min = j t = liste[min] liste[min] = liste[i] liste[i] = t return liste Wir nehmen als Eingabegröße n gleich die Länge des Arrays. Der Vergleich mit f pnq “ n3 scheint nicht zu gehen. Der Vergleich mit f pnq “ n2 sieht vielversprechend aus. 4.1.4 Beispiel 3: Sortieren eines Arrays mit MinSort def minsort(liste): for i in range(len(liste)-1): min = i for j in range(i+1,len(liste)): if liste[min] > liste[j]: min = j t = liste[min] liste[min] = liste[i] liste[i] = t return liste Wir nehmen als Eingabegröße n gleich die Länge des Arrays. Der Vergleich mit f pnq “ n3 scheint nicht zu gehen. Der Vergleich mit f pnq “ n2 sieht vielversprechend aus. Die Rechenzeit von minsortpAq ist „ n2 . Die Eingabegröße n ist dabei die Länge des Arrays A. 4.1.4 Beispiel 4: Suche des Maximums def maxsuche(liste): max = liste[0] for i in range(1,len(liste)): if liste[i] > max: max = liste[i] return max Wir nehmen als Eingabegröße n die Anzahl der Einträge im Array. 4.1.5 Beispiel 4: Suche des Maximums def maxsuche(liste): max = liste[0] for i in range(1,len(liste)): if liste[i] > max: max = liste[i] return max Wir nehmen als Eingabegröße n die Anzahl der Einträge im Array. Der Vergleich mit f pnq “ n scheint nicht zu gehen. 4.1.5 Beispiel 4: Suche des Maximums def maxsuche(liste): max = liste[0] for i in range(1,len(liste)): if liste[i] > max: max = liste[i] return max Wir nehmen als Eingabegröße n die Anzahl der Einträge im Array. Der Vergleich mit f pnq “ n scheint nicht zu gehen. Die Rechenzeit von maxsuche(liste) ist „ n. Die Eingabegröße n ist dabei die Länge von liste. 4.1.5 Zusammenfassung der Beobachtungen Wir haben die Rechenzeiten verschiedener Programme durch einfache Funktionen charakterisieren können. Funktion fib(m) quadratM(A) minsort(A) maxsuche(A) Eingabegröße n Wert von m Seitenlänge von A Länge von A Länge von A Rechenzeit „ n 1, 61 Bezeichnung der Rechenzeit exponentiell n 3 kubisch n 2 quadratisch n ¨ log n quasi-linear n linear Diese Art der Analyse der Rechenzeit beruht auf Experimenten (Ermittlung der Rechenzeit für einige Eingaben) und Herumprobieren, bis ein hinreichend ähnlicher Funktionsgraph gefunden wurde. Wie kann man ohne Experimente die Rechenzeit abschätzen? 4.1.6 Mathematische Analyse Die Rechenzeit einer einfachen Wertzuweisung an eine Variable dauert Zeit c, ist also „ 1. Beispiel: a = b + 2*c if liste[min] > liste[j]: min = j Eine Schleife (while, for) besteht aus einer Bedingung und einem Anweisungsblock. a = 0 while n > 1: n = n/2 a = a+1 for i in min = for j if range(len(liste)-1): i in range(i+1,len(liste)): liste[min] > liste[j]: min = j t = liste[min] liste[min] = liste[i] liste[i] = t Die Rechenzeit einer Schleife ist die Summe der Rechenzeiten jeder Wiederholung des Anweisungsblocks . 4.1.7 Programm Rechenzeit „ n = int(sys.argv[1]) 1 a = 0 1 Wiederholungen der Schleife log n while n > 1: n = n/2 1 a = a+1 1 Die Rechenzeit des Programms ist „ 1 ` 1 ` 2 ¨ log n. Faustregeln für Abschätzungen mit „: § Formeln so weit wie möglich zusammenfassen. § Bei Summen streicht man alles bis auf den größten Summanden. Eine Summe 1 ` 1 ` 1 ` 1 ist „ 1. § Bei Produkten streicht man alle Konstanten Faktoren. Damit bleibt übrig: die Rechenzeit des Programms ist „ log n. Das ist im Prinzip“ die Anzahl der Wiederholungen der Schleife. ” 4.1.8 Im folgenden Programm ist die Eingabegröße n die Länge von liste. Programm max = liste[0] for i in range(1,len(liste)): if liste[i] > max: max = liste[i] Rechenzeit „ 1 Wiedh. der Schleife n´1 1 Die Rechenzeit des Programms ist „ 1 ` pn ´ 1q „ n. Das ist im Prinzip“ wieder die Anzahl der Wiederholungen der Schleife. ” 4.1.9 Im folgenden Programm ist die Eingabegröße n die Länge von liste. Programm for i in range(len(liste)-1): min = i for j in range(i+1,len(liste)): if liste[min] > liste[j]: min = j t = liste[min] liste[min] = liste[i] liste[i] = t Rechenzeit „ Wiedh. n´2 1 n´i ´1 1 1 1 1 Die Rechenzeit des Programms ist „ pn ´ 2q ¨ 4 ` pn ´ 2q ` pn ´ 3q ` pn ´ 4q ` . . . ` 1 “ pn ´ 2q ¨ 4 ` n´2 ÿ i “ pn ´ 2q ¨ 4 ` i“1 1 “ 4n ´ 8 ` ¨ pn2 ´ 3n ` 2q 2 „ 1 ¨ pn ´ 1q ¨ pn ´ 2q 2 n ` n2 ´ n ` 1 „ n2 Damit bleibt übrig: die Rechenzeit des Programms ist „ n2 . Im Prinzip“: ” zwei ineinandergeschachtelte Schleifen mit „ n Wiederholungen haben Rechenzeit „ n2 . 4.1.10 Im folgenden Programm ist die Eingabegröße n die Seitenlänge von A. # quadratM(A) gibt das Quadrat A**2 der quadratischen Matrix A # als Ergebnis zurück. def quadratM(A): C = stdarray.create2D(len(A),len(A),0) for z in range(len(A)): for s in range(len(A)): for i in range(len(A)): C[z][s] += A[z][i]*A[i][s] return C Die Rechenzeit für einen Funktionsaufruf von quadratM(A) ist „ n ¨ n ¨ n “ n3 . Faustregel“: ” drei ineinandergeschachtelte Schleifen mit „ n Wiederholungen haben Rechenzeit „ n3 . Die Analyse der Rechenzeit rekursiver Funktionen kann komplizierter sein. def fib(n): if n==0 or n==1: return n else: return fib(n-1) + fib(n-2) Wir definieren eine Funktion T , so dass T pnq die Rechenzeit von fibpnq ist. § Für T p0q “ 1 und T p1q “ 1. § Für n ě 2 ist T pnq “ T pn ´ 1q ` T pn ´ 2q. Man sieht: T pnq “ fibpn ` 1q. Wir erinnern uns: ˜ˆ ? ˙n ˆ ? ˙n ¸ 1` 5 1´ 5 1 ´ fibpnq “ ? ¨ 2 2 5 Dann ist 1 T pnq „ ? ¨ 5 ˜ˆ ? ˙n`1 ˆ ? ˙n`1 ¸ 1` 5 1´ 5 ´ 2 2 „ 1, 61n`1 ´ 0, 6n`1 „ 1, 61n 4.1.12 Wir haben bei der mathematischen Analyse die gleichen Ergebnisse gefunden wie bei der Analyse mittels Herumprobieren. Ein weiterer Vorteil der mathematischen Analyse ist, dass man die Rechenzeit eines Algorithmus analysieren kann, ohne dass man ihn in einer Programmiersprache implementiert haben muss. Eine mathematische Bemerkung: Wir schreiben „ an Stelle der mathematischen O-Notation ( Oh-Notation“). ” Die O-Notation dient dem Abschätzen des asymptotischen Wachstums von Funktionen. Wir schreiben f „ g statt f P Opg q. Deshalb bedeutet f „ g : f wächst asymptotisch nicht stärker als g . Die Rechenzeit ist „ n3“ bedeutet dann: ” die Rechenzeit wächst nicht stärker als n3 . Die formale Definition von f P Opg q ist Dc Dn0 @n ě n0 : f pnq ď c ¨ g pnq ` c. 4.1.13 Zusammenfassung Wir haben gesehen, dass es zur Abschätzung von Rechenzeiten von Programmen ein realistisches und gut benutzbares mathematisches Modell gibt. Die folgende Tabelle enthält typische Rechenzeiten und den Zuwachs an Rechenzeit eines Programm, das bei Eingabegröße m wenige Sekunden braucht, bei einer Eingabe der Größe 100 ¨ m. Rechenzeit Verlängerung der Rechenzeit (s.o.) linear wenige Minuten quasi-linear wenige Minuten quadratisch einige Stunden kubisch ein paar Wochen exponentiell ewig 4.1.14 4.2 Schnelle Algorithmen zum Sortieren Zum Sortieren der Elemente eines Arrays hatten wir MinSort gesehen, das Rechenzeit „ n2 zum Sortieren von n Elementen benötigt. Die Rechenzeit der Größenordnung n2 stammt daher, dass MinSort jedes der n Elemente mit jedem anderen vergleicht. Zum Sortieren von 32000 Elementen benötigt MinSort 28 Sekunden. Wir werden ein anderes Verfahren vorstellen (HeapSort), das wesentlich schneller ist und 32000 Elemente in 0.2 Sekunden sortiert. Die Rechenzeit ist „ n ¨ log n. Der Grund dieser dramatischen“ Verbesserung liegt darin, ” dass man zum Sortieren jedes Element der n Elemente durchschnittlich nur mit log n Elementen vergleichen muss. Man kann zeigen, dass es nicht besser geht. 4.2.1 Warum überhaupt sortieren? Damit man schneller etwas finden kann. Binäre Suche findet einen gesuchten Eintrag in einem sortierten Array aus n Elementen mit Rechenzeit „ log n, während vollständiges Durchsuchen Rechenzeit „ n hat. 4.2.2 #-----------------------------------------------------------------------------# suche.py #-----------------------------------------------------------------------------import stdio, sys, stdarray, math, random def binaer(liste,x,von,bis): while von < bis: mitte = (von+bis)/2 if liste[mitte]==x: return mitte elif liste[mitte] < x : # suche in der oberen Haelfte weiter von = mitte + 1 else: bis = mitte - 1 # suche in der unteren Haelfte weiter if von==bis and liste[von]==x: return von else: return False def linear(liste,x,von,bis): gefunden = False i = von while i<=bis and liste[i]!=x: i += 1 if i>bis: return False else: return i def main(): liste = [ 1, 2, 3, 4, 5, 7, 8, 9, 10, 11 ] for i in range (0,13): print linear(liste,i,0,9) if __name__ == '__main__': main() 4.2.3 Experimenteller Vergleich der Rechenzeit zwischen linearer und binärer Suche Die Eingabegröße ist die Anzahl der Einträge im Array. 1 sec angegeben. Die Rechenzeiten sind in 1000 Eingabegröße 103 104 105 106 107 linear 0.1 0.8 9.2 90.0 968.4 binär 0.005 0.007 0.009 0.012 0.015 Die Eingabegröße verzehnfacht sich in jedem Schritt. Die Rechenzeit der linearen Suche verzehnfacht sich ebenso (das entspricht Rechenzeit „ n). Die Rechenzeit der binären Suche wächst etwa um Faktor 1 14 (das entspricht Rechenzeit „ log2 n). 4.2.4 K.O.-Sortieren 17 2 5 23 22 11 4 13 4.2.5 K.O.-Sortieren max? 17 2 5 23 22 11 4 13 4.2.5 K.O.-Sortieren 17 2 5 23 22 11 4 13 4.2.5 K.O.-Sortieren 17 max? 2 5 23 22 11 4 13 4.2.5 K.O.-Sortieren 17 23 2 5 22 11 4 13 4.2.5 K.O.-Sortieren 17 23 2 5 max? 22 11 4 13 4.2.5 K.O.-Sortieren 17 23 2 5 22 11 4 13 4.2.5 K.O.-Sortieren 17 23 2 5 22 max? 11 4 13 4.2.5 K.O.-Sortieren 17 23 2 5 22 13 11 4 4.2.5 K.O.-Sortieren max? 17 23 2 5 22 13 11 4 4.2.5 K.O.-Sortieren 23 17 22 2 5 13 11 4 4.2.5 K.O.-Sortieren 23 17 5 2 22 13 11 4 4.2.5 K.O.-Sortieren 23 17 max? 5 2 22 13 11 4 4.2.5 K.O.-Sortieren 23 17 22 5 2 13 11 4 4.2.5 K.O.-Sortieren 23 17 22 5 2 11 13 4 4.2.5 K.O.-Sortieren max? 23 17 22 5 2 11 13 4 4.2.5 K.O.-Sortieren 23 22 17 5 2 11 13 4 4.2.5 K.O.-Sortieren 23 22 max? 17 5 2 11 13 4 4.2.5 K.O.-Sortieren 23 17 22 5 2 11 13 4 4.2.5 K.O.-Sortieren 23 17 2 22 5 11 13 4 4.2.5 K.O.-Sortieren 23 17 2 22 5 11 13 4 4.2.5 K.O.-Sortieren 23 max? 17 2 22 5 11 13 4 4.2.5 K.O.-Sortieren 23 22 17 2 5 11 13 4 4.2.5 K.O.-Sortieren 23 22 17 2 max? 5 11 13 4 4.2.5 K.O.-Sortieren 23 22 17 2 13 5 11 4 4.2.5 K.O.-Sortieren 23 22 17 2 13 5 11 4 4.2.5 K.O.-Sortieren 22 17 2 23 13 5 11 4 4.2.5 K.O.-Sortieren 22 23 max? 17 2 13 5 11 4 4.2.5 K.O.-Sortieren 22 23 17 13 2 5 11 4 4.2.5 K.O.-Sortieren 22 23 17 13 max? 2 5 11 4 4.2.5 K.O.-Sortieren 22 23 17 5 2 13 11 4 4.2.5 K.O.-Sortieren 17 5 2 22 23 13 11 4 4.2.5 K.O.-Sortieren 17 22 23 max? 5 2 13 11 4 4.2.5 K.O.-Sortieren 17 22 23 13 5 2 11 4 4.2.5 K.O.-Sortieren 17 22 23 13 5 2 max? 11 4 4.2.5 K.O.-Sortieren 17 22 23 13 5 2 11 4 4.2.5 K.O.-Sortieren 13 5 2 17 22 23 11 4 4.2.5 K.O.-Sortieren 13 17 22 23 max? 5 2 11 4 4.2.5 K.O.-Sortieren 13 17 22 23 11 5 2 4 4.2.5 K.O.-Sortieren 13 17 22 23 11 5 4 2 4.2.5 K.O.-Sortieren 11 5 13 17 22 23 4 2 4.2.5 K.O.-Sortieren 11 13 17 22 23 max? 5 4 2 4.2.5 K.O.-Sortieren 11 13 17 22 23 5 4 2 4.2.5 K.O.-Sortieren 11 13 17 22 23 5 2 4 4.2.5 K.O.-Sortieren 5 2 11 13 17 22 23 4 4.2.5 K.O.-Sortieren 5 11 13 17 22 23 max? 2 4 4.2.5 K.O.-Sortieren 5 11 13 17 22 23 4 2 4.2.5 K.O.-Sortieren 4 5 11 13 17 22 23 2 4.2.5 K.O.-Sortieren 4 5 11 13 17 22 23 2 4.2.5 K.O.-Sortieren 2 4 5 11 13 17 22 23 4.2.5 K.O.-Sortieren 2 4 5 11 13 17 22 23 Wieviele Vergleiche werden gemacht? 4.2.5 Wieviele Vergleiche werden beim K.O.-Sortieren gemacht? § § Bei jedem Vergleich gibt es einen Sieger. Jede Zahl landet irgendwann mal ganz oben. Beim K.O.-Sortieren von n Zahlen macht man also höchstens n multipliziert mit der Anzahl benötigter Siege, um von ganz unten nach ganz oben zu kommen viele Vergleiche. Wieviele Siege braucht man, um von ganz unten nach ganz oben zu kommen? Man braucht höchstens soviele Siege, wie der Baum Schichten hat. In der untersten Schicht sind n Plätze, in der Schicht darüber n2 usw. Von Schicht zu Schicht halbiert sich die Anzahl der Plätze. Also ist die Anzahl der Schichten die Zahl, wie oft man n durch 2 teilen kann, bis 1 herauskommt, und das ist log2 n. Also werden beim K.O.-Sortieren von n Zahlen höchstens n ¨ log2 n Vergleiche gemacht. 4.2.6 K.O.-Sortieren ist nicht so einfach zu implementieren, da das Auffüllen der Lücken im Baum etwas wirr“ ist. ” Wenn der Baum keine Lücken hat, dann steht das Maximum aller Zahlen im Baum ganz oben, und jeder Teilbaum hat die gleiche Eigenschaft. 23 17 22 2 5 1 11 3 13 4 Problem: die unterste Schicht ist nicht richtig schön . . . 4.2.7 Ein schöner Baum (MaxHeap) habe folgende Eigenschaften: 1. Das Maximum steht ganz oben. 2. Die unterste Schicht ist von links nach rechts gefüllt, und alle anderen Schichten sind komplett gefüllt. 3. Jeder Teilbaum ist ebenfalls schön. 23 17 22 4 2 5 1 11 13 3 4.2.8 Das Entfernen des Maximums und die darauffolgende Reorganisation des MaxHeaps ist einfach. 23 17 22 4 2 5 1 11 13 3 Es ist klar, wo ein Platz im MaxHeap entfernt werden muss. Das Element von dort lässt man von oben an die richtige Stelle absinken. Die Anzahl der Vergleiche dazu in einem MaxHeap mit n Elementen ist höchstens 2mal die Anzahl der Schichten des MaxHeaps, also „ log2 n. 4.2.9 Das Entfernen des Maximums und die darauffolgende Reorganisation des MaxHeaps ist einfach. 17 22 4 2 5 1 11 13 3 Es ist klar, wo ein Platz im MaxHeap entfernt werden muss. Das Element von dort lässt man von oben an die richtige Stelle absinken. Die Anzahl der Vergleiche dazu in einem MaxHeap mit n Elementen ist höchstens 2mal die Anzahl der Schichten des MaxHeaps, also „ log2 n. 4.2.9 Das Entfernen des Maximums und die darauffolgende Reorganisation des MaxHeaps ist einfach. 3 17 4 2 22 5 11 13 1 Es ist klar, wo ein Platz im MaxHeap entfernt werden muss. Das Element von dort lässt man von oben an die richtige Stelle absinken. Die Anzahl der Vergleiche dazu in einem MaxHeap mit n Elementen ist höchstens 2mal die Anzahl der Schichten des MaxHeaps, also „ log2 n. 4.2.9 Das Entfernen des Maximums und die darauffolgende Reorganisation des MaxHeaps ist einfach. 3 17 4 2 22 max? 5 11 13 1 Es ist klar, wo ein Platz im MaxHeap entfernt werden muss. Das Element von dort lässt man von oben an die richtige Stelle absinken. Die Anzahl der Vergleiche dazu in einem MaxHeap mit n Elementen ist höchstens 2mal die Anzahl der Schichten des MaxHeaps, also „ log2 n. 4.2.9 Das Entfernen des Maximums und die darauffolgende Reorganisation des MaxHeaps ist einfach. 22 17 4 2 3 5 11 13 1 Es ist klar, wo ein Platz im MaxHeap entfernt werden muss. Das Element von dort lässt man von oben an die richtige Stelle absinken. Die Anzahl der Vergleiche dazu in einem MaxHeap mit n Elementen ist höchstens 2mal die Anzahl der Schichten des MaxHeaps, also „ log2 n. 4.2.9 Das Entfernen des Maximums und die darauffolgende Reorganisation des MaxHeaps ist einfach. 22 17 4 2 3 5 11 max? 13 1 Es ist klar, wo ein Platz im MaxHeap entfernt werden muss. Das Element von dort lässt man von oben an die richtige Stelle absinken. Die Anzahl der Vergleiche dazu in einem MaxHeap mit n Elementen ist höchstens 2mal die Anzahl der Schichten des MaxHeaps, also „ log2 n. 4.2.9 Das Entfernen des Maximums und die darauffolgende Reorganisation des MaxHeaps ist einfach. 22 17 4 2 13 5 11 3 1 Es ist klar, wo ein Platz im MaxHeap entfernt werden muss. Das Element von dort lässt man von oben an die richtige Stelle absinken. Die Anzahl der Vergleiche dazu in einem MaxHeap mit n Elementen ist höchstens 2mal die Anzahl der Schichten des MaxHeaps, also „ log2 n. 4.2.9 Das Entfernen des Maximums und die darauffolgende Reorganisation des MaxHeaps ist einfach. 22 17 4 2 13 5 11 3 1 Es ist klar, wo ein Platz im MaxHeap entfernt werden muss. Das Element von dort lässt man von oben an die richtige Stelle absinken. Die Anzahl der Vergleiche dazu in einem MaxHeap mit n Elementen ist höchstens 2mal die Anzahl der Schichten des MaxHeaps, also „ log2 n. 4.2.9 Mit Arrays kann man MaxHeaps zum Sortieren implementieren. 23 17 22 4 2 5 1 11 13 3 Der MaxHeap als Array: Index: 0 10 1 23 2 17 3 22 4 4 5 5 6 11 7 13 8 2 9 1 10 3 Länge Die beiden Kinder vom Knoten mit Index i haben Indizes 2i und 2i ` 1. 4.2.10 Beim Sortieren beginnt man mit einem beliebigen Array. Es hat bereits die richtige äußere Form eines MaxHeaps Alle Schichten sind komplett gefüllt, und die unterste Schicht ist von links nach rechts gefüllt. 0 10 Index: 1 1 2 2 3 3 4 4 5 5 6 6 7 7 8 8 9 9 10 10 Länge Man muss noch die innere Form herstellen. § Das Maximum steht ganz oben. § Jeder Teil des MaxHeaps ist ebenfalls ein MaxHeap. Gehe den MaxHeap rückwärts durch. 1 2 3 4 8 5 9 10 6 7 Wird ein Knoten gefunden, der kleiner als eines seiner Kinder ist, dann lasse ihn absinken. Beim Sortieren beginnt man mit einem beliebigen Array. Es hat bereits die richtige äußere Form eines MaxHeaps Alle Schichten sind komplett gefüllt, und die unterste Schicht ist von links nach rechts gefüllt. 0 10 Index: 1 1 2 2 3 3 4 4 5 5 6 6 7 7 8 8 9 9 10 10 Länge Man muss noch die innere Form herstellen. § Das Maximum steht ganz oben. § Jeder Teil des MaxHeaps ist ebenfalls ein MaxHeap. Gehe den MaxHeap rückwärts durch. 1 2 3 4 8 5 9 10 6 7 Wird ein Knoten gefunden, der kleiner als eines seiner Kinder ist, dann lasse ihn absinken. Beim Sortieren beginnt man mit einem beliebigen Array. Es hat bereits die richtige äußere Form eines MaxHeaps Alle Schichten sind komplett gefüllt, und die unterste Schicht ist von links nach rechts gefüllt. 0 10 Index: 1 1 2 2 3 3 4 4 5 10 6 6 7 7 8 8 9 9 10 5 Länge Man muss noch die innere Form herstellen. § Das Maximum steht ganz oben. § Jeder Teil des MaxHeaps ist ebenfalls ein MaxHeap. Gehe den MaxHeap rückwärts durch. 1 2 3 4 8 10 9 5 6 7 Wird ein Knoten gefunden, der kleiner als eines seiner Kinder ist, dann lasse ihn absinken. Beim Sortieren beginnt man mit einem beliebigen Array. Es hat bereits die richtige äußere Form eines MaxHeaps Alle Schichten sind komplett gefüllt, und die unterste Schicht ist von links nach rechts gefüllt. 0 10 Index: 1 1 2 2 3 3 4 4 5 10 6 6 7 7 8 8 9 9 10 5 Länge Man muss noch die innere Form herstellen. § Das Maximum steht ganz oben. § Jeder Teil des MaxHeaps ist ebenfalls ein MaxHeap. Gehe den MaxHeap rückwärts durch. 1 2 3 4 8 10 9 5 6 7 Wird ein Knoten gefunden, der kleiner als eines seiner Kinder ist, dann lasse ihn absinken. Beim Sortieren beginnt man mit einem beliebigen Array. Es hat bereits die richtige äußere Form eines MaxHeaps Alle Schichten sind komplett gefüllt, und die unterste Schicht ist von links nach rechts gefüllt. 0 10 Index: 1 1 2 2 3 3 4 9 5 10 6 6 7 7 8 8 9 4 10 5 Länge Man muss noch die innere Form herstellen. § Das Maximum steht ganz oben. § Jeder Teil des MaxHeaps ist ebenfalls ein MaxHeap. Gehe den MaxHeap rückwärts durch. 1 2 3 9 8 10 4 5 6 7 Wird ein Knoten gefunden, der kleiner als eines seiner Kinder ist, dann lasse ihn absinken. Beim Sortieren beginnt man mit einem beliebigen Array. Es hat bereits die richtige äußere Form eines MaxHeaps Alle Schichten sind komplett gefüllt, und die unterste Schicht ist von links nach rechts gefüllt. 0 10 Index: 1 1 2 2 3 3 4 9 5 10 6 6 7 7 8 8 9 4 10 5 Länge Man muss noch die innere Form herstellen. § Das Maximum steht ganz oben. § Jeder Teil des MaxHeaps ist ebenfalls ein MaxHeap. Gehe den MaxHeap rückwärts durch. 1 2 3 9 8 10 4 5 6 7 Wird ein Knoten gefunden, der kleiner als eines seiner Kinder ist, dann lasse ihn absinken. Beim Sortieren beginnt man mit einem beliebigen Array. Es hat bereits die richtige äußere Form eines MaxHeaps Alle Schichten sind komplett gefüllt, und die unterste Schicht ist von links nach rechts gefüllt. 0 10 Index: 1 1 2 2 3 7 4 9 5 10 6 6 7 3 8 8 9 4 10 5 Länge Man muss noch die innere Form herstellen. § Das Maximum steht ganz oben. § Jeder Teil des MaxHeaps ist ebenfalls ein MaxHeap. Gehe den MaxHeap rückwärts durch. 1 2 7 9 8 10 4 5 6 3 Wird ein Knoten gefunden, der kleiner als eines seiner Kinder ist, dann lasse ihn absinken. Beim Sortieren beginnt man mit einem beliebigen Array. Es hat bereits die richtige äußere Form eines MaxHeaps Alle Schichten sind komplett gefüllt, und die unterste Schicht ist von links nach rechts gefüllt. 0 10 Index: 1 1 2 2 3 7 4 9 5 10 6 6 7 3 8 8 9 4 10 5 Länge Man muss noch die innere Form herstellen. § Das Maximum steht ganz oben. § Jeder Teil des MaxHeaps ist ebenfalls ein MaxHeap. Gehe den MaxHeap rückwärts durch. 1 2 7 9 8 10 4 5 6 3 Wird ein Knoten gefunden, der kleiner als eines seiner Kinder ist, dann lasse ihn absinken. Beim Sortieren beginnt man mit einem beliebigen Array. Es hat bereits die richtige äußere Form eines MaxHeaps Alle Schichten sind komplett gefüllt, und die unterste Schicht ist von links nach rechts gefüllt. 0 10 Index: 1 1 2 10 3 7 4 9 5 5 6 6 7 3 8 8 9 4 10 2 Länge Man muss noch die innere Form herstellen. § Das Maximum steht ganz oben. § Jeder Teil des MaxHeaps ist ebenfalls ein MaxHeap. Gehe den MaxHeap rückwärts durch. 1 10 7 9 8 5 4 2 6 3 Wird ein Knoten gefunden, der kleiner als eines seiner Kinder ist, dann lasse ihn absinken. Beim Sortieren beginnt man mit einem beliebigen Array. Es hat bereits die richtige äußere Form eines MaxHeaps Alle Schichten sind komplett gefüllt, und die unterste Schicht ist von links nach rechts gefüllt. 0 10 Index: 1 1 2 10 3 7 4 9 5 5 6 6 7 3 8 8 9 4 10 2 Länge Man muss noch die innere Form herstellen. § Das Maximum steht ganz oben. § Jeder Teil des MaxHeaps ist ebenfalls ein MaxHeap. Gehe den MaxHeap rückwärts durch. 1 10 7 9 8 5 4 2 6 3 Wird ein Knoten gefunden, der kleiner als eines seiner Kinder ist, dann lasse ihn absinken. Beim Sortieren beginnt man mit einem beliebigen Array. Es hat bereits die richtige äußere Form eines MaxHeaps Alle Schichten sind komplett gefüllt, und die unterste Schicht ist von links nach rechts gefüllt. 0 10 Index: 1 10 2 9 3 7 4 8 5 5 6 6 7 3 8 1 9 4 10 2 Länge Man muss noch die innere Form herstellen. § Das Maximum steht ganz oben. § Jeder Teil des MaxHeaps ist ebenfalls ein MaxHeap. Gehe den MaxHeap rückwärts durch. 10 9 7 8 1 5 4 2 6 3 Wird ein Knoten gefunden, der kleiner als eines seiner Kinder ist, dann lasse ihn absinken. Beim Sortieren beginnt man mit einem beliebigen Array. Es hat bereits die richtige äußere Form eines MaxHeaps Alle Schichten sind komplett gefüllt, und die unterste Schicht ist von links nach rechts gefüllt. 0 10 Index: 1 10 2 9 3 7 4 8 5 5 6 6 7 3 8 1 9 4 10 2 Länge Man muss noch die innere Form herstellen. § Das Maximum steht ganz oben. § Jeder Teil des MaxHeaps ist ebenfalls ein MaxHeap. Gehe den MaxHeap rückwärts durch. 10 9 7 8 1 5 4 2 6 3 Wird ein Knoten gefunden, der kleiner als eines seiner Kinder ist, dann lasse ihn absinken. Der Ablauf von HeapSort Gegeben: ein Array mache aus dem Array einen MaxHeap solange der MaxHeap noch ein Element enthält: entferne das größte Element aus dem Heap und reorganisiere ihn mit einem Element weniger Zusätzlicher Trick: man kann das Array, das man als MaxHeap benutzt, gleichzeitig als Array zm Speichern der sortierten Elemente benutzen. Index: unsortiertes Array: 0 8 1 13 2 7 3 22 4 11 5 4 6 3 7 17 8 24 Länge MaxHeap: 8 24 13 22 11 4 3 17 7 nach 1.Entfernen: 7 22 13 17 11 4 3 7 24 nach 2.Entfernen: 6 17 13 7 11 4 3 22 24 nach 3.Entfernen: 5 13 11 7 3 4 17 22 24 nach 4.Entfernen: 4 11 4 7 3 13 17 22 24 nach 5.Entfernen: 3 7 4 3 11 13 17 22 24 nach 6.Entfernen: 2 4 3 7 11 13 17 22 24 nach 7.Entfernen: 1 3 4 7 11 13 17 22 24 nach 8.Entfernen: 0 3 4 7 11 13 17 22 24 Die Rechenzeit von HeapSort ist „ n ¨ log n Die Eingabegröße ist die Anzahl der zu sortierenden Elemente n. Um die Rechenzeit abzuschätzen, reicht es zu zählen, wieviele Vergleiche gemacht werden. Der MaxHeap hat „ log n Schichten. Ein Element im MaxHeap von der Wurzel aus absinken zu lassen, benötigt „ log n Vergleiche. Die Konstruktion des MaxHeaps aus dem unsortierten Array benötigt dann „ n ¨ log n Vergleiche. Jedes Entfernen eines Elements aus der Wurzel und die Reorganisation des MaxHeaps benötig „ log n Vergleiche. Da n Elemente entfern werden, benötigt man „ n ¨ log n Vergleiche. Insgesamt werden also „ n ¨ log n ` n ¨ log n „ n ¨ log n Vergleiche benötigt. 4.2.14 Jedes Sortierverfahren, das auf Vergleichen der zu sortierenden Element basiert, benötigt mindestens n ¨ log n Vergleiche. Also ist HeapSort ein optimales Sortierverfahren (dieser Art). 4.2.15 #-----------------------------------------------------------------------------# maxheap.py #-----------------------------------------------------------------------------def maxnachfolger(mheap,i): # Gibt den Index des größten Nachfolgers des Elements mit Index i # im Heap mheap zurück (das kann auch i sein). if mheap[0]>=2*i+1: # der Eintrag mit Index i hat zwei Nachfolger if mheap[2*i+1]>=mheap[2*i]: m1 = 2*i+1 else: m1 = 2*i if mheap[i]>=mheap[m1]: m1 = i elif mheap[0]==2*i: # der Eintrag mit Index i hat einen Nachfolger if mheap[2*i]>mheap[i]: m1 = 2*i else: m1 = i else: m1 = i return m1 def tausche(liste,a,b): # Tausche im Array liste die Elemente mit Indizes a und b t = liste[a] liste[a] = liste[b] liste[b] = t def absinken(mheap,i): # Lasse im MaxHeap mheap das Element mit Index i absinken. mm = maxnachfolger(mheap,i) while mm > i: tausche(mheap,i,mm) i = mm mm = maxnachfolger(mheap,i) 4.2.16 #------------------------------------------------------------------------------------# heapsort.py #------------------------------------------------------------------------------------import maxheap, stdarray, random def heapsort(liste): # Sortiere die Einträge mit den Indizes 1..len(liste)-1 im Array liste. liste[0] = len(liste)-1 # Trage die Anzahl der zu sortierenden Elemente in liste[0] ein. for i in range(liste[0]/2, 0, -1): maxheap.absinken(liste,i) # Mache aus liste einen MaxHeap. for i in range(1, liste[0]+1): maxheap.tausche(liste,1,liste[0]) liste[0] -= 1 maxheap.absinken(liste,1) # Entferne das größte Element aus dem MaxHeap # und schreibe es ans Ende von liste, bis alle # Elemente entfernt wurden - dann ist liste sortiert. def main(): liste = stdarray.create1D(100,0) for i in range(1,len(liste)): liste[i] = random.randrange(100) liste[0] = 99 heapsort(liste) print liste if __name__ == '__main__': main() 4.2.17 Experimenteller Vergleich der Rechenzeit von MinSort und HeapSort Die Eingabegröße ist die Anzahl der Elemente im Array. Die Rechenzeiten sind in Sekunden angegeben. Eingabegröße 103 104 105 106 107 MinSort 0.04 s 4.86 s 491.98 s HeapSort 0.01 s 0.12 s 1.52 s 18.22 s 213.08 s Die Eingabegröße verzehnfacht sich in jedem Schritt. Bei MinSort verhundertfacht sich dabei die Rechenzeit, da MinSort Rechenzeit „ n2 hat. Bei HeapSort verzwölffacht sich die Rechenzeit etwa. 4.2.18 Zusammenfassung Manche Probleme kann man mit viel schnelleren Programmen lösen, als man denkt. Sortieren ist ein Beispiel dafür. Wir haben HeapSort als einen verblüffend schnellen Algorithmus gesehen. Andere schnelle Algorithmen zum Sortieren sind z.B. QuickSort, MergeSort oder BucketSort. Wir haben gesehen, dass die Verwendung einer guten Datenstruktur (MaxHeap) ein wesentlicher Grund für die Schnelligkeit des Programms ist. 4.3 Wege finden in Graphen Das Konzept des Graphen hatten wir bereits in VL05 benutzt. Dort hatten wir uns Webseiten und Links dazwischen als die Knoten und die Kanten eines Graphen vorgestellt. Genauso kann man sich § Kreuzungen und Straßen dazwischen in einem Straßennetz Menschen und likes“ dazwischen ” als Graphen vorstellen. § Wege zwischen zwei Knoten in einem Graphen sind dann z.B. § eine Anleitung, wie man von einer Webseite zu einer anderen surft § eine Beschreibung, wie man von einer Stadt zur anderen fährt § ein Hinweis darauf, ob die beiden Menschen Ähnliches mögen. 4.3.1 Unterschiedliche Arten von Graphen Ein gerichteter Graph ohne Gewichte: e c d a Der Graph besteht aus 5 Knoten und 7 Kanten. Er enthält 3 Wege von Knoten a zu Knoten d: sie haben Längen 2, 3 und 4. Er enthält keinen Weg von Knoten c zu Knoten a. b Ein gerichteter Graph mit Gewichten: 1 e 4 c 2 3 5 10 a 1 d Der Graph besteht aus 5 Knoten und 7 Kanten. Er enthält 3 Wege von Knoten a zu Knoten d: sie haben Längen 11, 9 und 7. Er enthält keinen Weg von Knoten c zu Knoten a. b Ungewichtete Graphen entsprechen gewichteten Graphen, bei denen alle Kantengewichte 1 sind.4.3.2 Man interessiert sich für folgende Fragen: § Gibt es einen Weg von Knoten x zu Knoten y ? § Gibt es einen Weg von Knoten x zu jedem anderen Knoten? § Welcher Weg von x zu y ist der kürzeste? § Finde alle kürzesten Wege von Knoten x zu allen anderen Knoten! Wir werden uns Algorithmen für diese Fragen für Graphen mit und ohne Gewichte anschauen: § Breitensuche (für ungewichtete Graphen) § Algorithmus von Dijkstra (für gewichtete Graphen) Hat dieser Graph einen Weg vom roten zum blauen Knoten? Welcher Weg ist der kürzeste? 4.3.4 Breitensuche – die Idee starte mit dem roten Knoten färbe alle Nachbarn von roten Knoten oder grauen Knoten ebenfalls grau, bis der blaue Knoten erreicht ist und merke Dir dabei den Verursacher“ jeder Graufärbung ” 4.3.5 Breitensuche – die Idee starte mit dem roten Knoten färbe alle Nachbarn von roten Knoten oder grauen Knoten ebenfalls grau, bis der blaue Knoten erreicht ist und merke Dir dabei den Verursacher“ jeder Graufärbung ” 1 4.3.5 Breitensuche – die Idee starte mit dem roten Knoten färbe alle Nachbarn von roten Knoten oder grauen Knoten ebenfalls grau, bis der blaue Knoten erreicht ist und merke Dir dabei den Verursacher“ jeder Graufärbung ” 2 1 4.3.5 Breitensuche – die Idee starte mit dem roten Knoten färbe alle Nachbarn von roten Knoten oder grauen Knoten ebenfalls grau, bis der blaue Knoten erreicht ist und merke Dir dabei den Verursacher“ jeder Graufärbung ” 3 3 2 1 4.3.5 Breitensuche – die Idee starte mit dem roten Knoten färbe alle Nachbarn von roten Knoten oder grauen Knoten ebenfalls grau, bis der blaue Knoten erreicht ist und merke Dir dabei den Verursacher“ jeder Graufärbung ” 4 4 3 3 2 4 1 4.3.5 Breitensuche – die Idee starte mit dem roten Knoten färbe alle Nachbarn von roten Knoten oder grauen Knoten ebenfalls grau, bis der blaue Knoten erreicht ist und merke Dir dabei den Verursacher“ jeder Graufärbung ” 5 4 5 4 3 4 3 2 5 1 4.3.5 Breitensuche – die Idee starte mit dem roten Knoten färbe alle Nachbarn von roten Knoten oder grauen Knoten ebenfalls grau, bis der blaue Knoten erreicht ist und merke Dir dabei den Verursacher“ jeder Graufärbung ” 6 5 4 5 4 3 4 3 2 5 1 6 4.3.5 Breitensuche – die Idee starte mit dem roten Knoten färbe alle Nachbarn von roten Knoten oder grauen Knoten ebenfalls grau, bis der blaue Knoten erreicht ist und merke Dir dabei den Verursacher“ jeder Graufärbung ” 7 6 5 4 5 4 3 4 3 2 5 1 6 7 4.3.5 Breitensuche – die Idee starte mit dem roten Knoten färbe alle Nachbarn von roten Knoten oder grauen Knoten ebenfalls grau, bis der blaue Knoten erreicht ist und merke Dir dabei den Verursacher“ jeder Graufärbung ” 8 7 8 6 5 4 5 4 3 4 3 2 5 1 6 7 4.3.5 Breitensuche – die Idee starte mit dem roten Knoten färbe alle Nachbarn von roten Knoten oder grauen Knoten ebenfalls grau, bis der blaue Knoten erreicht ist und merke Dir dabei den Verursacher“ jeder Graufärbung ” 9 8 9 7 8 6 9 5 4 5 4 3 4 3 2 5 1 6 7 4.3.5 Breitensuche – die Idee starte mit dem roten Knoten färbe alle Nachbarn von roten Knoten oder grauen Knoten ebenfalls grau, bis der blaue Knoten erreicht ist und merke Dir dabei den Verursacher“ jeder Graufärbung ” 9 10 8 9 7 8 10 6 9 10 5 4 5 4 3 4 3 2 5 1 6 7 4.3.5 Breitensuche – die Idee starte mit dem roten Knoten färbe alle Nachbarn von roten Knoten oder grauen Knoten ebenfalls grau, bis der blaue Knoten erreicht ist und merke Dir dabei den Verursacher“ jeder Graufärbung ” 11 9 10 11 8 9 11 7 8 10 6 9 10 5 4 5 4 3 4 3 2 5 1 6 7 4.3.5 Breitensuche – die Idee starte mit dem roten Knoten färbe alle Nachbarn von roten Knoten oder grauen Knoten ebenfalls grau, bis der blaue Knoten erreicht ist und merke Dir dabei den Verursacher“ jeder Graufärbung ” 12 12 11 12 9 10 11 8 9 11 7 8 10 6 9 10 5 4 5 4 3 4 3 2 5 1 6 7 4.3.5 Breitensuche – die Idee starte mit dem roten Knoten färbe alle Nachbarn von roten Knoten oder grauen Knoten ebenfalls grau, bis der blaue Knoten erreicht ist und merke Dir dabei den Verursacher“ jeder Graufärbung ” 13 13 12 12 11 12 9 10 11 8 9 11 7 8 10 6 9 10 5 4 5 4 3 4 3 2 5 1 6 13 7 4.3.5 Breitensuche – die Idee starte mit dem roten Knoten färbe alle Nachbarn von roten Knoten oder grauen Knoten ebenfalls grau, bis der blaue Knoten erreicht ist und merke Dir dabei den Verursacher“ jeder Graufärbung ” 13 14 13 12 12 11 12 9 10 11 8 9 11 7 8 10 6 9 10 5 4 5 4 3 4 3 2 5 1 6 13 14 7 4.3.5 Breitensuche – die Idee starte mit dem roten Knoten färbe alle Nachbarn von roten Knoten oder grauen Knoten ebenfalls grau, bis der blaue Knoten erreicht ist und merke Dir dabei den Verursacher“ jeder Graufärbung ” 13 14 15 13 12 12 11 12 9 10 11 8 9 11 7 8 10 6 9 10 5 4 5 4 3 4 3 2 5 1 6 13 14 15 7 4.3.5 Breitensuche – die Idee starte mit dem roten Knoten färbe alle Nachbarn von roten Knoten oder grauen Knoten ebenfalls grau, bis der blaue Knoten erreicht ist und merke Dir dabei den Verursacher“ jeder Graufärbung ” 13 14 15 13 12 12 11 12 13 14 9 10 11 15 8 9 11 16 7 8 10 6 9 10 5 4 5 4 3 4 3 2 5 1 6 16 7 4.3.5 Breitensuche – die Idee starte mit dem roten Knoten färbe alle Nachbarn von roten Knoten oder grauen Knoten ebenfalls grau, bis der blaue Knoten erreicht ist und merke Dir dabei den Verursacher“ jeder Graufärbung ” 13 14 15 13 12 12 11 12 9 10 11 8 9 11 7 8 10 6 9 10 5 4 5 4 3 4 3 2 5 1 6 13 17 14 17 15 16 16 17 17 7 4.3.5 Breitensuche – die Idee starte mit dem roten Knoten färbe alle Nachbarn von roten Knoten oder grauen Knoten ebenfalls grau, bis der blaue Knoten erreicht ist und merke Dir dabei den Verursacher“ jeder Graufärbung ” 13 14 15 13 12 12 11 12 13 14 17 9 10 11 18 15 16 8 9 11 17 16 17 7 8 10 18 17 18 6 9 10 5 4 5 4 3 4 3 2 5 1 6 18 7 4.3.5 Breitensuche – die Idee starte mit dem roten Knoten färbe alle Nachbarn von roten Knoten oder grauen Knoten ebenfalls grau, bis der blaue Knoten erreicht ist und merke Dir dabei den Verursacher“ jeder Graufärbung ” 13 14 15 13 12 12 11 12 13 14 17 9 10 11 18 15 16 19 8 9 11 17 16 17 18 7 8 10 18 17 18 19 6 9 10 19 5 4 5 4 3 4 3 2 5 1 6 7 4.3.5 Breitensuche – die Idee starte mit dem roten Knoten färbe alle Nachbarn von roten Knoten oder grauen Knoten ebenfalls grau, bis der blaue Knoten erreicht ist und merke Dir dabei den Verursacher“ jeder Graufärbung ” 13 14 15 13 12 12 11 12 13 14 17 20 9 10 11 18 15 16 19 8 9 11 17 16 17 18 7 8 10 18 17 18 19 6 9 10 19 20 5 4 5 4 3 4 3 2 5 1 6 20 20 7 4.3.5 Breitensuche – die Idee starte mit dem roten Knoten färbe alle Nachbarn von roten Knoten oder grauen Knoten ebenfalls grau, bis der blaue Knoten erreicht ist und merke Dir dabei den Verursacher“ jeder Graufärbung ” 13 14 15 13 12 21 12 11 12 13 14 17 20 21 9 10 11 18 15 16 19 20 8 9 11 17 16 17 18 21 7 8 10 18 17 18 19 6 9 10 19 20 21 20 5 4 5 4 3 4 3 2 5 1 6 21 7 4.3.5 Breitensuche – die Idee starte mit dem roten Knoten färbe alle Nachbarn von roten Knoten oder grauen Knoten ebenfalls grau, bis der blaue Knoten erreicht ist und merke Dir dabei den Verursacher“ jeder Graufärbung ” 13 14 15 22 21 22 13 12 12 11 12 13 14 17 20 21 9 10 11 18 15 16 19 20 8 9 11 17 16 17 18 21 7 8 10 18 17 18 19 22 6 9 10 19 20 21 20 21 5 4 5 4 3 4 3 2 5 1 6 22 22 22 7 4.3.5 Breitensuche – die Idee starte mit dem roten Knoten färbe alle Nachbarn von roten Knoten oder grauen Knoten ebenfalls grau, bis der blaue Knoten erreicht ist und merke Dir dabei den Verursacher“ jeder Graufärbung ” 13 14 15 22 21 22 23 13 12 12 11 12 13 14 17 20 21 9 10 11 18 15 16 19 20 23 8 9 11 17 16 17 18 21 22 7 8 10 18 17 18 19 22 23 6 9 10 19 20 21 20 21 22 5 4 5 4 3 4 3 2 5 1 6 22 23 23 23 7 4.3.5 Breitensuche – die Idee starte mit dem roten Knoten färbe alle Nachbarn von roten Knoten oder grauen Knoten ebenfalls grau, bis der blaue Knoten erreicht ist und merke Dir dabei den Verursacher“ jeder Graufärbung ” 13 14 15 22 21 22 23 24 13 12 12 11 12 13 14 17 20 21 24 9 10 11 18 15 16 19 20 23 24 8 9 11 17 16 17 18 21 22 23 7 8 10 18 17 18 19 22 23 24 6 9 10 19 20 21 20 21 22 23 5 4 5 23 24 4 3 4 3 2 5 1 6 22 24 7 4.3.5 Breitensuche – die Idee starte mit dem roten Knoten färbe alle Nachbarn von roten Knoten oder grauen Knoten ebenfalls grau, bis der blaue Knoten erreicht ist und merke Dir dabei den Verursacher“ jeder Graufärbung ” 13 14 15 22 21 22 23 13 12 12 11 12 13 14 17 20 21 24 9 10 11 18 15 16 19 20 23 24 8 9 11 17 16 17 18 21 22 23 7 8 10 18 17 18 19 22 23 24 6 9 10 19 20 21 20 21 22 23 5 4 5 23 24 4 3 4 3 2 5 1 6 22 24 7 4.3.5 Breitensuche – die Idee starte mit dem roten Knoten färbe alle Nachbarn von roten Knoten oder grauen Knoten ebenfalls grau, bis der blaue Knoten erreicht ist und merke Dir dabei den Verursacher“ jeder Graufärbung ” 13 14 15 22 21 22 23 13 12 12 11 12 13 14 17 20 21 24 9 10 11 18 15 16 19 20 23 24 8 9 11 17 16 17 18 21 22 23 7 8 10 18 17 18 19 22 23 24 6 9 10 19 20 21 20 21 22 23 5 4 5 23 24 4 3 4 3 2 5 1 6 22 24 7 4.3.5 Breitensuche – die Idee starte mit dem roten Knoten färbe alle Nachbarn von roten Knoten oder grauen Knoten ebenfalls grau, bis der blaue Knoten erreicht ist und merke Dir dabei den Verursacher“ jeder Graufärbung ” 13 14 15 22 21 22 23 13 12 12 11 12 13 14 17 20 21 24 9 10 11 18 15 16 19 20 23 24 8 9 11 17 16 17 18 21 22 23 7 8 10 18 17 18 19 22 23 24 6 9 10 19 20 21 20 21 22 23 5 4 5 23 24 4 3 4 3 2 5 1 6 22 24 7 4.3.5 Breitensuche – die Idee starte mit dem roten Knoten färbe alle Nachbarn von roten Knoten oder grauen Knoten ebenfalls grau, bis der blaue Knoten erreicht ist und merke Dir dabei den Verursacher“ jeder Graufärbung ” 13 14 15 22 21 22 23 13 12 12 11 12 13 14 17 20 21 24 9 10 11 18 15 16 19 20 23 24 8 9 11 17 16 17 18 21 22 23 7 8 10 18 17 18 19 22 23 24 6 9 10 19 20 21 20 21 22 23 5 4 5 23 24 4 3 4 3 2 5 1 6 22 24 7 4.3.5 Breitensuche – die Idee starte mit dem roten Knoten färbe alle Nachbarn von roten Knoten oder grauen Knoten ebenfalls grau, bis der blaue Knoten erreicht ist und merke Dir dabei den Verursacher“ jeder Graufärbung ” 13 14 15 22 21 22 23 13 12 12 11 12 13 14 17 20 21 24 9 10 11 18 15 16 19 20 23 24 8 9 11 17 16 17 18 21 22 23 7 8 10 18 17 18 19 22 23 24 6 9 10 19 20 21 20 21 22 23 5 4 5 23 24 4 3 4 3 2 5 1 6 22 24 7 4.3.5 Breitensuche – die Idee starte mit dem roten Knoten färbe alle Nachbarn von roten Knoten oder grauen Knoten ebenfalls grau, bis der blaue Knoten erreicht ist und merke Dir dabei den Verursacher“ jeder Graufärbung ” 13 14 15 22 21 22 23 13 12 12 11 12 13 14 17 20 21 24 9 10 11 18 15 16 19 20 23 24 8 9 11 17 16 17 18 21 22 23 7 8 10 18 17 18 19 22 23 24 6 9 10 19 20 21 20 21 22 23 5 4 5 23 24 4 3 4 3 2 5 1 6 22 24 7 4.3.5 Breitensuche – die Idee starte mit dem roten Knoten färbe alle Nachbarn von roten Knoten oder grauen Knoten ebenfalls grau, bis der blaue Knoten erreicht ist und merke Dir dabei den Verursacher“ jeder Graufärbung ” 13 14 15 22 21 22 23 13 12 12 11 12 13 14 17 20 21 24 9 10 11 18 15 16 19 20 23 24 8 9 11 17 16 17 18 21 22 23 7 8 10 18 17 18 19 22 23 24 6 9 10 19 20 21 20 21 22 23 5 4 5 23 24 4 3 4 3 2 5 1 6 22 24 7 4.3.5 Breitensuche – die Idee starte mit dem roten Knoten färbe alle Nachbarn von roten Knoten oder grauen Knoten ebenfalls grau, bis der blaue Knoten erreicht ist und merke Dir dabei den Verursacher“ jeder Graufärbung ” 13 14 15 22 21 22 23 13 12 12 11 12 13 14 17 20 21 24 9 10 11 18 15 16 19 20 23 24 8 9 11 17 16 17 18 21 22 23 7 8 10 18 17 18 19 22 23 24 6 9 10 19 20 21 20 21 22 23 5 4 5 23 24 4 3 4 3 2 5 1 6 22 24 7 4.3.5 Breitensuche – die Idee starte mit dem roten Knoten färbe alle Nachbarn von roten Knoten oder grauen Knoten ebenfalls grau, bis der blaue Knoten erreicht ist und merke Dir dabei den Verursacher“ jeder Graufärbung ” 13 14 15 22 21 22 23 13 12 12 11 12 13 14 17 20 21 24 9 10 11 18 15 16 19 20 23 24 8 9 11 17 16 17 18 21 22 23 7 8 10 18 17 18 19 22 23 24 6 9 10 19 20 21 20 21 22 23 5 4 5 23 24 4 3 4 3 2 5 1 6 22 24 7 4.3.5 Breitensuche – die Idee starte mit dem roten Knoten färbe alle Nachbarn von roten Knoten oder grauen Knoten ebenfalls grau, bis der blaue Knoten erreicht ist und merke Dir dabei den Verursacher“ jeder Graufärbung ” 13 14 15 22 21 22 23 13 12 12 11 12 13 14 17 20 21 24 9 10 11 18 15 16 19 20 23 24 8 9 11 17 16 17 18 21 22 23 7 8 10 18 17 18 19 22 23 24 6 9 10 19 20 21 20 21 22 23 5 4 5 23 24 4 3 4 3 2 5 1 6 22 24 7 4.3.5 Breitensuche – die Idee starte mit dem roten Knoten färbe alle Nachbarn von roten Knoten oder grauen Knoten ebenfalls grau, bis der blaue Knoten erreicht ist und merke Dir dabei den Verursacher“ jeder Graufärbung ” 13 14 15 22 21 22 23 13 12 12 11 12 13 14 17 20 21 24 9 10 11 18 15 16 19 20 23 24 8 9 11 17 16 17 18 21 22 23 7 8 10 18 17 18 19 22 23 24 6 9 10 19 20 21 20 21 22 23 5 4 5 23 24 4 3 4 3 2 5 1 6 22 24 7 4.3.5 Breitensuche – die Idee starte mit dem roten Knoten färbe alle Nachbarn von roten Knoten oder grauen Knoten ebenfalls grau, bis der blaue Knoten erreicht ist und merke Dir dabei den Verursacher“ jeder Graufärbung ” 13 14 15 22 21 22 23 13 12 12 11 12 13 14 17 20 21 24 9 10 11 18 15 16 19 20 23 24 8 9 11 17 16 17 18 21 22 23 7 8 10 18 17 18 19 22 23 24 6 9 10 19 20 21 20 21 22 23 5 4 5 23 24 4 3 4 3 2 5 1 6 22 24 7 4.3.5 Breitensuche – die Idee starte mit dem roten Knoten färbe alle Nachbarn von roten Knoten oder grauen Knoten ebenfalls grau, bis der blaue Knoten erreicht ist und merke Dir dabei den Verursacher“ jeder Graufärbung ” 13 14 15 22 21 22 23 13 12 12 11 12 13 14 17 20 21 24 9 10 11 18 15 16 19 20 23 24 8 9 11 17 16 17 18 21 22 23 7 8 10 18 17 18 19 22 23 24 6 9 10 19 20 21 20 21 22 23 5 4 5 23 24 4 3 4 3 2 5 1 6 22 24 7 4.3.5 Breitensuche – die Idee starte mit dem roten Knoten färbe alle Nachbarn von roten Knoten oder grauen Knoten ebenfalls grau, bis der blaue Knoten erreicht ist und merke Dir dabei den Verursacher“ jeder Graufärbung ” 13 14 15 22 21 22 23 13 12 12 11 12 13 14 17 20 21 24 9 10 11 18 15 16 19 20 23 24 8 9 11 17 16 17 18 21 22 23 7 8 10 18 17 18 19 22 23 24 6 9 10 19 20 21 20 21 22 23 5 4 5 23 24 4 3 4 3 2 5 1 6 22 24 7 4.3.5 Breitensuche – die Idee starte mit dem roten Knoten färbe alle Nachbarn von roten Knoten oder grauen Knoten ebenfalls grau, bis der blaue Knoten erreicht ist und merke Dir dabei den Verursacher“ jeder Graufärbung ” 13 14 15 22 21 22 23 13 12 12 11 12 13 14 17 20 21 24 9 10 11 18 15 16 19 20 23 24 8 9 11 17 16 17 18 21 22 23 7 8 10 18 17 18 19 22 23 24 6 9 10 19 20 21 20 21 22 23 5 4 5 23 24 4 3 4 3 2 5 1 6 22 24 7 4.3.5 Breitensuche – die Idee starte mit dem roten Knoten färbe alle Nachbarn von roten Knoten oder grauen Knoten ebenfalls grau, bis der blaue Knoten erreicht ist und merke Dir dabei den Verursacher“ jeder Graufärbung ” 13 14 15 22 21 22 23 13 12 12 11 12 13 14 17 20 21 24 9 10 11 18 15 16 19 20 23 24 8 9 11 17 16 17 18 21 22 23 7 8 10 18 17 18 19 22 23 24 6 9 10 19 20 21 20 21 22 23 5 4 5 23 24 4 3 4 3 2 5 1 6 22 24 7 4.3.5 Breitensuche – die Idee starte mit dem roten Knoten färbe alle Nachbarn von roten Knoten oder grauen Knoten ebenfalls grau, bis der blaue Knoten erreicht ist und merke Dir dabei den Verursacher“ jeder Graufärbung ” 13 14 15 22 21 22 23 13 12 12 11 12 13 14 17 20 21 24 9 10 11 18 15 16 19 20 23 24 8 9 11 17 16 17 18 21 22 23 7 8 10 18 17 18 19 22 23 24 6 9 10 19 20 21 20 21 22 23 5 4 5 23 24 4 3 4 3 2 5 1 6 22 24 7 4.3.5 Breitensuche – die Idee starte mit dem roten Knoten färbe alle Nachbarn von roten Knoten oder grauen Knoten ebenfalls grau, bis der blaue Knoten erreicht ist und merke Dir dabei den Verursacher“ jeder Graufärbung ” 13 14 15 22 21 22 23 13 12 12 11 12 13 14 17 20 21 24 9 10 11 18 15 16 19 20 23 24 8 9 11 17 16 17 18 21 22 23 7 8 10 18 17 18 19 22 23 24 6 9 10 19 20 21 20 21 22 23 5 4 5 23 24 4 3 4 3 2 5 1 6 22 24 7 4.3.5 Breitensuche – die Idee starte mit dem roten Knoten färbe alle Nachbarn von roten Knoten oder grauen Knoten ebenfalls grau, bis der blaue Knoten erreicht ist und merke Dir dabei den Verursacher“ jeder Graufärbung ” 13 14 15 22 21 22 23 13 12 12 11 12 13 14 17 20 21 24 9 10 11 18 15 16 19 20 23 24 8 9 11 17 16 17 18 21 22 23 7 8 10 18 17 18 19 22 23 24 6 9 10 19 20 21 20 21 22 23 5 4 5 23 24 4 3 4 3 2 5 1 6 22 24 7 4.3.5 Breitensuche – die Idee starte mit dem roten Knoten färbe alle Nachbarn von roten Knoten oder grauen Knoten ebenfalls grau, bis der blaue Knoten erreicht ist und merke Dir dabei den Verursacher“ jeder Graufärbung ” 13 14 15 22 21 22 23 13 12 12 11 12 13 14 17 20 21 24 9 10 11 18 15 16 19 20 23 24 8 9 11 17 16 17 18 21 22 23 7 8 10 18 17 18 19 22 23 24 6 9 10 19 20 21 20 21 22 23 5 4 5 23 24 4 3 4 3 2 5 1 6 22 24 7 4.3.5 Breitensuche – die Idee starte mit dem roten Knoten färbe alle Nachbarn von roten Knoten oder grauen Knoten ebenfalls grau, bis der blaue Knoten erreicht ist und merke Dir dabei den Verursacher“ jeder Graufärbung ” 13 14 15 22 21 22 23 13 12 12 11 12 13 14 17 20 21 24 9 10 11 18 15 16 19 20 23 24 8 9 11 17 16 17 18 21 22 23 7 8 10 18 17 18 19 22 23 24 6 9 10 19 20 21 20 21 22 23 5 4 5 23 24 4 3 4 3 2 5 1 6 22 24 7 4.3.5 Breitensuche – die Idee starte mit dem roten Knoten färbe alle Nachbarn von roten Knoten oder grauen Knoten ebenfalls grau, bis der blaue Knoten erreicht ist und merke Dir dabei den Verursacher“ jeder Graufärbung ” 13 14 15 22 21 22 23 13 12 12 11 12 13 14 17 20 21 24 9 10 11 18 15 16 19 20 23 24 8 9 11 17 16 17 18 21 22 23 7 8 10 18 17 18 19 22 23 24 6 9 10 19 20 21 20 21 22 23 5 4 5 23 24 4 3 4 3 2 5 1 6 22 24 7 4.3.5 Breitensuche – die Idee starte mit dem roten Knoten färbe alle Nachbarn von roten Knoten oder grauen Knoten ebenfalls grau, bis der blaue Knoten erreicht ist und merke Dir dabei den Verursacher“ jeder Graufärbung ” 13 14 15 22 21 22 23 13 12 12 11 12 13 14 17 20 21 24 9 10 11 18 15 16 19 20 23 24 8 9 11 17 16 17 18 21 22 23 7 8 10 18 17 18 19 22 23 24 6 9 10 19 20 21 20 21 22 23 5 4 5 23 24 4 3 4 3 2 5 1 6 22 24 7 4.3.5 Programm zur Breitensuche in Graphen Der Graph liegt als Adjazenzmatrix AM[][] vor: AM[i][j] ist True, falls es eine Kante von Knoten i zu Knoten j gibt. Anderenfalls ist AM[i][j] False. Wir hatten bereits ein Programm geschrieben, das die Folge der Kanten eine Graphen einliest und daraus eine Adjazenzmatrix macht. 4.3.6 #---------------------------------------------------------------------------# einlesen.py # # Liest eine Graph-Datei ein # und gibt die Adjazenzmatrix des Graphen als Rückgabewert zurück. #---------------------------------------------------------------------------import sys, stdio, stdarray, string def graph(dateiname): datei = open(dateiname,"r") # Die Graph-Datei beginnt mit der Anzahl Knoten. n = int(string.strip(datei.readline())) # AM ist die Adjazenzmatrix, in die die Graph-Datei umgewandelt werden soll. AM = stdarray.create2D(n, n, False) # Der Rest der Graph-Datei besteht aus Zeilen mit Zahlenpaaren, # die für Kanten stehen. # In der Adjazenzmatrix AM[][] an die entsprechende Stelle True geschrieben. for zeile in datei: Z = string.split(zeile) if len(Z)==2 and int(Z[0])<n and int(Z[1])<n: AM[int(Z[0])][int(Z[1])] = True return AM #-----------------------------------def test(): print graph(sys.argv[1]) if __name__ == '__main__': test() #----------------------------------# python einlesen.py GraphVL13.txt # [[False, True, False, True, False], # [True, False, True, True, False], # [False, False, False, False, True], # [False, False, True, False, False], # [True, True, False, False, False]] #-----------------------------------GraphVL13.txt ist folgende Datei: 5 0 1 0 3 1 0 1 0 1 3 1 2 2 4 3 2 4 1 4 0 4.3.8 Wir stellen uns den eben durchgespielten Algorithmus rundenweise“ vor: ” § in der ersten Runde werden alle Nachbarn des roten Knoten grau gefärbt § in der zweiten Runde werden alle ungefärbten Nachbarn der Knoten, die in der ersten Runde grau gefärbt wurden, grau gefärbt § ... § in der pn ` 1qsten Runde werden alle ungefärbten Nachbarn der Knoten, die in der n ten Runde grau gefärbt wurden, grau gefärbt Man muss Kontrolle darüber haben, von welchen Knoten die Nachbarn grau gefärbt werden müssen. Es ist wichtig, die Knoten rundenweise abzuarbeiten, damit der kürzeste Weg (mit den wenigsten Kanten) gefunden wird. Dazu benutzen wir die Idee der Datenstruktur Warteschlange. Wenn ein Knoten grau gefärbt wird, wird er hinten an die Warteschlange drangestellt. Der Knoten, der vorne in der Warteschlange steht, wird bedient“, ” d.h. seine Nachbarn werden grau gefärbt. 4.3.10 #----------------------------------------------------------------------# warteschlange.py # Stellt die Funktionen einfuegen(W,int), kopf(W), entfernen(W) und ist_leer(W) # für Warteschlangen zur Verfügung. # Es ist unschön, dass bei einfuegen(W,x) mit Seiteneffekten gearbeitet wird. # Aber ohne Objektorientierung geht das nicht anders. #-----------------------------------------------------------------------def einfuegen(W,x): # Stelle x hinten an die Warteschlange W an. W += [x] def kopf(W): # Gib den Eintrag am Anfang der Warteschlange W zurück. return W[0] def entfernen(W): # Entferne das erste Element aus der Warteschlange W # und gib die veränderte Warteschlange als Ergebnis zurück. return W[1:] def ist_leer(W): # Gib zurück, ob die Warteschlange W leer ist. return len(W)==0 #------------------------------------------------------------------4.3.11 Nun muss die Breitensuche mit Benutzung der Warteschlange formuliert werden. am Anfang besteht die Warteschlange nur aus dem roten Knoten solange die Warteschlange nicht leer ist und der blaue Knoten nicht erreicht wurde: nimm den ersten Knoten aktueller knoten aus der Warteschlange färbe alle seine ungefärbten Nachbarn grau, hänge sie hinten an die Warteschlange an und merke dir, dass sie wg. aktueller knoten gefunden wurden 4.3.12 #------------------------------------------------------------------------------# breitensuche.py # # Liest einen Dateinamen von der Kommandozeile, liest den Graph aus der Datei ein # und führt Breitensuche auf dem Graphen durch. # Rückgabewert ist ein Array mit den Vorgängerknoten aller gefundener Knoten. #--------------------------------------------------------------------------------import sys, stdio, stdarray, einlesen, warteschlange def breitensuche(AM, von, nach): # AM: die Adjazenzmatrix des Graphen, # von: der Startknoten, nach: der Zielknoten # n: die Anzahl der Knoten n = len(AM) # grau: enthält für jeden Knoten die Markierung, # ob er bei der Breitensuche bereits gefunden wurde. grau = stdarray.create1D(n, False) # vorgaenger enthält für jeden Knoten, der gefunden wurde, # den Knoten (seinen Vorgänger), von dem aus er gefunden wurde. vorgaenger = stdarray.create1D(n,-1) 4.3.13 # Die Breitensuche wird vorbereitet. grau[von] = True WS = [von] # Die Breitensuche wird gestartet. while not warteschlange.ist_leer(WS) and not besucht[nach]: aktueller_knoten = warteschlange.kopf(WS) # Durchsuche alle Nachbarn des aktuellen Knotens. for nachbar in range(n): if AM[aktueller_knoten][nachbar] and not grau[nachbar]: warteschlange.einfuegen(WS,nachbar) grau[nachbar] = True vorgaenger[nachbar] = aktueller_knoten WS = warteschlange.entfernen(WS) return vorgaenger def ausgabe_weg(V,von,nach): # V ist das Array, das von breitensuche(AM,von,nach) zurückgegeben wird. # Da dort der gefundene Weg "rückwärts" drinsteht, # benutzen wir Rekursion, um ihn vorwärts auszugeben. if not nach==von: ausgabe_weg(V,von,V[nach]) print V[nach], nach 4.3.14 #----------------------------------------------------------------------------def test(): dateiname = sys.argv[1] von = int(sys.argv[2]) nach = int(sys.argv[3]) AM = einlesen.graph(dateiname) V = breitensuche(AM, von, nach) if not V[nach]==-1: ausgabe_weg(V, von, nach) else: stdio.writef('Es gibt keinen Weg von %d nach %d.\n', von, nach) if __name__ == '__main__': test() #------------------------------------------------------------------------------- 4.3.15 Suche nach kürzesten Wegen in gewichteten Graphen Der Algorithmus von Dijkstra 1 Suche nach kürzestem Weg von a nach d: e c 3 f 4 a 2 1 4 5 10 3 d b 4.3.16 Suche nach kürzesten Wegen in gewichteten Graphen Der Algorithmus von Dijkstra Suche nach kürzestem Weg von a nach d: zu Beginn hat a Entfernung 0 und alle anderen Knoten haben Entfernung 8 8 e 4 a 0 1 3 2 1 8 f 5 4 8 c 10 3 8 d b 8 4.3.16 Suche nach kürzesten Wegen in gewichteten Graphen Der Algorithmus von Dijkstra Suche nach kürzestem Weg von a nach d: zu Beginn hat a Entfernung 0 und alle anderen Knoten haben Entfernung 8 8 e 1 3 8 f 5 8 c 3 8 wiederhole, bis alle . . . Knoten abgearbeitet sind: d 4 10 4 2 suche den nicht-abgearbeiteten Knoten x mit 1 kleinster Entfernung a b markiere alle Nachbarn von x wie folgt: 8 0 wenn ein Nachbar mit 8 markiert ist: markiere ihn mit Markierung von x + Gewicht der Kante von x zu ihm sonst: wenn der Nachbar nicht abgearbeitet ist: markiere ihn mit dem Minimum seiner bisherigen Markierung und Markierung von x + Gewicht der Kante jetzt ist der Knoten abgearbeitet 4.3.16 Suche nach kürzesten Wegen in gewichteten Graphen Der Algorithmus von Dijkstra Suche nach kürzestem Weg von a nach d: zu Beginn hat a Entfernung 0 und alle anderen Knoten haben Entfernung 8 8 e 1 3 8 f 5 8 c 3 8 wiederhole, bis alle . . . Knoten abgearbeitet sind: d 4 10 4 2 suche den nicht-abgearbeiteten Knoten x mit 1 kleinster Entfernung a b markiere alle Nachbarn von x wie folgt: 8 0 wenn ein Nachbar mit 8 markiert ist: markiere ihn mit Markierung von x + Gewicht der Kante von x zu ihm sonst: wenn der Nachbar nicht abgearbeitet ist: markiere ihn mit dem Minimum seiner bisherigen Markierung und Markierung von x + Gewicht der Kante jetzt ist der Knoten abgearbeitet 4.3.16 Suche nach kürzesten Wegen in gewichteten Graphen Der Algorithmus von Dijkstra Suche nach kürzestem Weg von a nach d: zu Beginn hat a Entfernung 0 und alle anderen Knoten haben Entfernung 8 8 e 1 3 8 f 5 8 c 3 8 wiederhole, bis alle . . . Knoten abgearbeitet sind: d 4 10 4 2 suche den nicht-abgearbeiteten Knoten x mit 1 kleinster Entfernung a b markiere alle Nachbarn von x wie folgt: 0 1 wenn ein Nachbar mit 8 markiert ist: markiere ihn mit Markierung von x + Gewicht der Kante von x zu ihm sonst: wenn der Nachbar nicht abgearbeitet ist: markiere ihn mit dem Minimum seiner bisherigen Markierung und Markierung von x + Gewicht der Kante jetzt ist der Knoten abgearbeitet 4.3.16 Suche nach kürzesten Wegen in gewichteten Graphen Der Algorithmus von Dijkstra Suche nach kürzestem Weg von a nach d: zu Beginn hat a Entfernung 0 und alle anderen Knoten haben Entfernung 8 8 e 1 3 8 f 5 8 c 3 8 wiederhole, bis alle . . . Knoten abgearbeitet sind: d 4 10 4 2 suche den nicht-abgearbeiteten Knoten x mit 1 kleinster Entfernung a b markiere alle Nachbarn von x wie folgt: 0 1 wenn ein Nachbar mit 8 markiert ist: markiere ihn mit Markierung von x + Gewicht der Kante von x zu ihm sonst: wenn der Nachbar nicht abgearbeitet ist: markiere ihn mit dem Minimum seiner bisherigen Markierung und Markierung von x + Gewicht der Kante jetzt ist der Knoten abgearbeitet 4.3.16 Suche nach kürzesten Wegen in gewichteten Graphen Der Algorithmus von Dijkstra Suche nach kürzestem Weg von a nach d: zu Beginn hat a Entfernung 0 und alle anderen Knoten haben Entfernung 8 8 e 1 3 8 f 5 8 c 3 8 wiederhole, bis alle . . . Knoten abgearbeitet sind: d 4 10 4 2 suche den nicht-abgearbeiteten Knoten x mit 1 kleinster Entfernung a b markiere alle Nachbarn von x wie folgt: 0 1 wenn ein Nachbar mit 8 markiert ist: markiere ihn mit Markierung von x + Gewicht der Kante von x zu ihm sonst: wenn der Nachbar nicht abgearbeitet ist: markiere ihn mit dem Minimum seiner bisherigen Markierung und Markierung von x + Gewicht der Kante jetzt ist der Knoten abgearbeitet 4.3.16 Suche nach kürzesten Wegen in gewichteten Graphen Der Algorithmus von Dijkstra Suche nach kürzestem Weg von a nach d: zu Beginn hat a Entfernung 0 und alle anderen Knoten haben Entfernung 8 3 e 1 3 5 f 6 c 5 3 11 wiederhole, bis alle . . . Knoten abgearbeitet sind: d 4 10 4 2 suche den nicht-abgearbeiteten Knoten x mit 1 kleinster Entfernung a b markiere alle Nachbarn von x wie folgt: 0 1 wenn ein Nachbar mit 8 markiert ist: markiere ihn mit Markierung von x + Gewicht der Kante von x zu ihm sonst: wenn der Nachbar nicht abgearbeitet ist: markiere ihn mit dem Minimum seiner bisherigen Markierung und Markierung von x + Gewicht der Kante jetzt ist der Knoten abgearbeitet 4.3.16 Suche nach kürzesten Wegen in gewichteten Graphen Der Algorithmus von Dijkstra Suche nach kürzestem Weg von a nach d: zu Beginn hat a Entfernung 0 und alle anderen Knoten haben Entfernung 8 3 e 1 3 5 f 6 c 5 3 11 wiederhole, bis alle . . . Knoten abgearbeitet sind: d 4 10 4 2 suche den nicht-abgearbeiteten Knoten x mit 1 kleinster Entfernung a b markiere alle Nachbarn von x wie folgt: 0 1 wenn ein Nachbar mit 8 markiert ist: markiere ihn mit Markierung von x + Gewicht der Kante von x zu ihm sonst: wenn der Nachbar nicht abgearbeitet ist: markiere ihn mit dem Minimum seiner bisherigen Markierung und Markierung von x + Gewicht der Kante jetzt ist der Knoten abgearbeitet 4.3.16 Suche nach kürzesten Wegen in gewichteten Graphen Der Algorithmus von Dijkstra Suche nach kürzestem Weg von a nach d: zu Beginn hat a Entfernung 0 und alle anderen Knoten haben Entfernung 8 3 e 1 3 5 f 6 c 5 3 11 wiederhole, bis alle . . . Knoten abgearbeitet sind: d 4 10 4 2 suche den nicht-abgearbeiteten Knoten x mit 1 kleinster Entfernung a b markiere alle Nachbarn von x wie folgt: 0 1 wenn ein Nachbar mit 8 markiert ist: markiere ihn mit Markierung von x + Gewicht der Kante von x zu ihm sonst: wenn der Nachbar nicht abgearbeitet ist: markiere ihn mit dem Minimum seiner bisherigen Markierung und Markierung von x + Gewicht der Kante jetzt ist der Knoten abgearbeitet 4.3.16 Suche nach kürzesten Wegen in gewichteten Graphen Der Algorithmus von Dijkstra Suche nach kürzestem Weg von a nach d: zu Beginn hat a Entfernung 0 und alle anderen Knoten haben Entfernung 8 3 e 1 3 5 f 4 c 5 3 11 wiederhole, bis alle . . . Knoten abgearbeitet sind: d 4 10 4 2 suche den nicht-abgearbeiteten Knoten x mit 1 kleinster Entfernung a b markiere alle Nachbarn von x wie folgt: 0 1 wenn ein Nachbar mit 8 markiert ist: markiere ihn mit Markierung von x + Gewicht der Kante von x zu ihm sonst: wenn der Nachbar nicht abgearbeitet ist: markiere ihn mit dem Minimum seiner bisherigen Markierung und Markierung von x + Gewicht der Kante jetzt ist der Knoten abgearbeitet 4.3.16 Suche nach kürzesten Wegen in gewichteten Graphen Der Algorithmus von Dijkstra Suche nach kürzestem Weg von a nach d: zu Beginn hat a Entfernung 0 und alle anderen Knoten haben Entfernung 8 3 e 1 3 5 f 4 c 5 3 11 wiederhole, bis alle . . . Knoten abgearbeitet sind: d 4 10 4 2 suche den nicht-abgearbeiteten Knoten x mit 1 kleinster Entfernung a b markiere alle Nachbarn von x wie folgt: 0 1 wenn ein Nachbar mit 8 markiert ist: markiere ihn mit Markierung von x + Gewicht der Kante von x zu ihm sonst: wenn der Nachbar nicht abgearbeitet ist: markiere ihn mit dem Minimum seiner bisherigen Markierung und Markierung von x + Gewicht der Kante jetzt ist der Knoten abgearbeitet 4.3.16 Suche nach kürzesten Wegen in gewichteten Graphen Der Algorithmus von Dijkstra Suche nach kürzestem Weg von a nach d: zu Beginn hat a Entfernung 0 und alle anderen Knoten haben Entfernung 8 3 e 1 3 5 f 4 c 5 3 11 wiederhole, bis alle . . . Knoten abgearbeitet sind: d 4 10 4 2 suche den nicht-abgearbeiteten Knoten x mit 1 kleinster Entfernung a b markiere alle Nachbarn von x wie folgt: 0 1 wenn ein Nachbar mit 8 markiert ist: markiere ihn mit Markierung von x + Gewicht der Kante von x zu ihm sonst: wenn der Nachbar nicht abgearbeitet ist: markiere ihn mit dem Minimum seiner bisherigen Markierung und Markierung von x + Gewicht der Kante jetzt ist der Knoten abgearbeitet 4.3.16 Suche nach kürzesten Wegen in gewichteten Graphen Der Algorithmus von Dijkstra Suche nach kürzestem Weg von a nach d: zu Beginn hat a Entfernung 0 und alle anderen Knoten haben Entfernung 8 3 e 1 3 5 f 4 c 5 3 7 wiederhole, bis alle . . . Knoten abgearbeitet sind: d 4 10 4 2 suche den nicht-abgearbeiteten Knoten x mit 1 kleinster Entfernung a b markiere alle Nachbarn von x wie folgt: 0 1 wenn ein Nachbar mit 8 markiert ist: markiere ihn mit Markierung von x + Gewicht der Kante von x zu ihm sonst: wenn der Nachbar nicht abgearbeitet ist: markiere ihn mit dem Minimum seiner bisherigen Markierung und Markierung von x + Gewicht der Kante jetzt ist der Knoten abgearbeitet 4.3.16 Suche nach kürzesten Wegen in gewichteten Graphen Der Algorithmus von Dijkstra Suche nach kürzestem Weg von a nach d: zu Beginn hat a Entfernung 0 und alle anderen Knoten haben Entfernung 8 3 e 1 3 5 f 4 c 5 3 7 wiederhole, bis alle . . . Knoten abgearbeitet sind: d 4 10 4 2 suche den nicht-abgearbeiteten Knoten x mit 1 kleinster Entfernung a b markiere alle Nachbarn von x wie folgt: 0 1 wenn ein Nachbar mit 8 markiert ist: markiere ihn mit Markierung von x + Gewicht der Kante von x zu ihm sonst: wenn der Nachbar nicht abgearbeitet ist: markiere ihn mit dem Minimum seiner bisherigen Markierung und Markierung von x + Gewicht der Kante jetzt ist der Knoten abgearbeitet 4.3.16 Suche nach kürzesten Wegen in gewichteten Graphen Der Algorithmus von Dijkstra Suche nach kürzestem Weg von a nach d: zu Beginn hat a Entfernung 0 und alle anderen Knoten haben Entfernung 8 3 e 1 3 5 f 4 c 5 3 7 wiederhole, bis alle . . . Knoten abgearbeitet sind: d 4 10 4 2 suche den nicht-abgearbeiteten Knoten x mit 1 kleinster Entfernung a b markiere alle Nachbarn von x wie folgt: 0 1 wenn ein Nachbar mit 8 markiert ist: markiere ihn mit Markierung von x + Gewicht der Kante von x zu ihm sonst: wenn der Nachbar nicht abgearbeitet ist: markiere ihn mit dem Minimum seiner bisherigen Markierung und Markierung von x + Gewicht der Kante jetzt ist der Knoten abgearbeitet 4.3.16 Suche nach kürzesten Wegen in gewichteten Graphen Der Algorithmus von Dijkstra Suche nach kürzestem Weg von a nach d: zu Beginn hat a Entfernung 0 und alle anderen Knoten haben Entfernung 8 3 e 1 3 5 f 4 c 5 3 7 wiederhole, bis alle . . . Knoten abgearbeitet sind: d 4 10 4 2 suche den nicht-abgearbeiteten Knoten x mit 1 kleinster Entfernung a b markiere alle Nachbarn von x wie folgt: 0 1 wenn ein Nachbar mit 8 markiert ist: markiere ihn mit Markierung von x + Gewicht der Kante von x zu ihm sonst: wenn der Nachbar nicht abgearbeitet ist: markiere ihn mit dem Minimum seiner bisherigen Markierung und Markierung von x + Gewicht der Kante jetzt ist der Knoten abgearbeitet 4.3.16 Suche nach kürzesten Wegen in gewichteten Graphen Der Algorithmus von Dijkstra Suche nach kürzestem Weg von a nach d: zu Beginn hat a Entfernung 0 und alle anderen Knoten haben Entfernung 8 3 e 1 3 5 f 4 c 5 3 7 wiederhole, bis alle . . . Knoten abgearbeitet sind: d 4 10 4 2 suche den nicht-abgearbeiteten Knoten x mit 1 kleinster Entfernung a b markiere alle Nachbarn von x wie folgt: 0 1 wenn ein Nachbar mit 8 markiert ist: markiere ihn mit Markierung von x + Gewicht der Kante von x zu ihm sonst: wenn der Nachbar nicht abgearbeitet ist: markiere ihn mit dem Minimum seiner bisherigen Markierung und Markierung von x + Gewicht der Kante jetzt ist der Knoten abgearbeitet 4.3.16 Suche nach kürzesten Wegen in gewichteten Graphen Der Algorithmus von Dijkstra Suche nach kürzestem Weg von a nach d: zu Beginn hat a Entfernung 0 und alle anderen Knoten haben Entfernung 8 3 e 1 3 5 f 4 c 5 3 7 wiederhole, bis alle . . . Knoten abgearbeitet sind: d 4 10 4 2 suche den nicht-abgearbeiteten Knoten x mit 1 kleinster Entfernung a b markiere alle Nachbarn von x wie folgt: 0 1 wenn ein Nachbar mit 8 markiert ist: markiere ihn mit Markierung von x + Gewicht der Kante von x zu ihm sonst: wenn der Nachbar nicht abgearbeitet ist: markiere ihn mit dem Minimum seiner bisherigen Markierung und Markierung von x + Gewicht der Kante jetzt ist der Knoten abgearbeitet 4.3.16 Um den Algorithmus zu programmieren, modifizieren wir das Programm für die Breitensuche. Zuerst ergänzen wir einlesen.py um eine Funktion graph gew zu Einlesen gewichteter Graphen. Die Funktion hat als Rückgabewert eine Adjazenzmatrix AM[][], so dass AM[i][j] das Gewicht der Kante von Knoten i zu Knoten j ist, falls der Graph diese Kante enthält, und anderenfalls ist AM[i][j] undendlich, d.h. float(’inf’). 4.3.17 #-----------------------------------------------------------------# einlesen.py # # Liest eine Graph-Datei ein # und gibt die Adjazenzmatrix des Graphen als Rückgabewert zurück. #-----------------------------------------------------------------import sys, stdio, stdarray, string def graph_gew(dateiname): # Der Graph ist gerichtet und gewichtet (mit positiven int Gewichten). # In der Eingabedatei steht in der ersten Zeile die Anzahl der Knoten, # in jeder darauffolgenden Zeile steht eine Kante und ihr Gewicht in Form von drei Zahlen. # Die zurückgegebene Adjazenzmatrix hat float Einträge. # Jeder Eintrag ist entweder das Gewicht oder float('inf) (falls keine Kante zwischen den Knoten datei = open(dateiname,"r") # Die Graph-Datei beginnt mit der Anzahl Seiten (Knoten). n = int(string.strip(datei.readline())) # AM ist die Adjazenzmatrix der einzulesenden Datei. AM = stdarray.create2D(n, n, float('inf')) # Der Rest der Graph-Datei besteht aus Zahlentripeln, die für Kanten und ihre Gewichte stehen. # In der Adjazenzmatrix AM[][] wird das Gewicht an die entsprechende Stelle gesetzt. for zeile in datei: Z = string.split(zeile) if len(Z)==3 and int(Z[0])<n and int(Z[1])<n and float(Z[2])>0: AM[int(Z[0])][int(Z[1])] = float(Z[2]) return AM 4.3.18 Bei der Breitensuche hatten wir graue“ Knoten. ” Das sind genau die Knoten, deren korrekte Entfernung zu Startknoten bereits bestimmt wurde. Im Programm für den Algorithmus von Dijkstra verwenden wir dafür den Begriff abgearbeitete Knoten“. ” Wir müssen uns stets merken, welche Entfernungs-Markierung für jeden Knoten bestimmt wurde. Ansonsten benutzen wir die gleichen Daten wie in der Breitensuche. 4.3.19 #----------------------------------------------------------------------# dijkstra-einfach.py # # Liest einen Dateinamen von der Kommandozeile, # liest aus der Datei einen gerichteten gewichteten Graphen ein # und führt den Algorithmus von Dijkstra auf dem Graphen durch. # Rückgabewert ist ein Array mit den Vorgängerknoten # auf dem gefundenen Weg. #-----------------------------------------------------------------------import sys, stdio, stdarray, einlesen def suche(AM, von, nach): # n ist die Anzahl der Knoten n = len(AM) # abgearbeitet enthält für jeden Knoten eine Markierung, # ob seine exakte Entfernung zum Startknoten von bereits berechnet ist. abgearbeitet = stdarray.create1D(n, False) # vorgaenger enthält für jeden Knoten, der besucht wurde, # den Knoten (seinen Vorgänger), von dem aus er gefunden wurde. vorgaenger = stdarray.create1D(n,-1) # weglaenge enthält für jeden besuchten Knoten die Länge # des bisher zu ihm gefundenen Weges. weglaenge = stdarray.create1D(n, float('inf')) 4.3.20 # Die Suche wird initialisiert. weglaenge[von] = 0.0 # Die Suche wird gestartet. while not min(abgearbeitet): # Suche den nicht-abgearbeiteten Knoten mit minimaler Entfernung vom Startknoten. for i in range(n): if not abgearbeitet[i]: kandidat = i break for z in range(i+1,n): if not abgearbeitet[z] and weglaenge[z]<weglaenge[kandidat]: kandidat=z aktueller_knoten = kandidat if weglaenge[aktueller_knoten] == float('inf'): break abgearbeitet[aktueller_knoten] = True # Durchsuche alle Nachbarn des aktuellen Knotens. for nachbar in range(n): if not abgearbeitet[nachbar]: if weglaenge[nachbar] > weglaenge[aktueller_knoten] + AM[aktueller_knoten][nachbar]: weglaenge[nachbar] = weglaenge[aktueller_knoten] + AM[aktueller_knoten][nachbar] vorgaenger[nachbar] = aktueller_knoten return vorgaenger 4.3.21 def ausgabe_weg(V,von,nach): if not nach==von: ausgabe_weg(V,von,V[nach]) print V[nach], nach #-----------------------------------------------------------------------def test(): dateiname = sys.argv[1] von = int(sys.argv[2]) nach = int(sys.argv[3]) AM = einlesen.graph_gew(dateiname) V = suche(AM, von, nach) if V[nach] < float('inf'): ausgabe_weg(V, von, nach) else: stdio.writef('Es gibt keinen Weg von %d nach %d.\n', von, nach) #----------------------------------------------------------------------if __name__ == '__main__': test() Datei GraphVL13gew.txt: #-------------------------------------------6 0 1 1.0 # python dijkstra-einfach.py GraphVL13gew.txt 0 3 1 2 5.0 # 0 1 1 3 10.0 # 1 4 1 4 2.0 # 4 2 1 5 4.0 # 2 3 4 5 3.0 4 2 1.0 2 3 3.0 5 6 3.0 Abschätzung der Rechenzeit Breitensuche Die Größe eines Graphen wird durch die Anzahl n seiner Knoten und die Anzahl m seiner Kanten beschrieben. Bei der Breitensuche wird jeder Knoten (höchstens) einmal in die Warteschlange eingefügt. Also wird die große while-Schleife n mal durchlaufen. In der while-Schleife wird für jeden Knoten überprüft, ob er Nachbar des gerade aktuellen Knotens ist. Also hat jeder Durchlauf der while-Schleife Rechenzeit „ n. Mit der Anzahl der Durchläufe der while-Schleife erhält man Rechenzeit „ n2 . Speichert man den Graph so, dass man direkt Zugriff auf die von den Knoten ausgehenden Kanten hat (und nicht alle Knoten auf potentielle Nachbarschaft untersuchen muss), dann erhält man Rechenzeit „ m. Da m ď n2 , kann das besser sein. 4.3.23 Abschätzung der Rechenzeit Algorithmus von Dijkstra Jeder Knoten wird (höchstens) einmal aktueller Knoten. Also wird die große while-Schleife n mal durchlaufen. In der while-Schleife wird der aktuelle Knoten bestimmt – das hat Rechenzeit „ n. Dann werden wieder alle Nachbarn des aktuellen Knotens betrachtet – das hat Rechenzeit „ n. Beides summiert sich zu 2 ¨ n – also hat jeder Durchlauf der while-Schleife Rechenzeit „ n. Mit der Anzahl der Durchläufe der while-Schleife erhält man Rechenzeit „ n2 . Verbesserungspotental: (1) Graph so speichern, dass man direkt Zugriff auf die von den Knoten ausgehenden Kanten hat. (2) Suche nach dem aktuellen Knoten mit Hilfe eines Heaps (Rechenzeit „ log n statt „ n). Damit erreicht man Rechenzeit „ m ` n ¨ log n. 4.3.24 Zusammenfassung Graphen eignen sich zum Modellieren vieler verschiedener Zusammenhänge. Breitensuche ist ein klassisches“ algorithmisches Verfahren. ” Die Datenstruktur Warteschlange (queue) ist eng damit verbunden. Der Algorithmus von Dijkstra (1959) ist ebenfalls ein Klassiker. Der Algorithmus löst ein Optimierungsproblem mit einer einfachen Idee (die trotzdem nicht so leicht zu finden ist). Mit geschickter Auswahl von Datenstrukturen hat der Algorithmus sehr kurze Rechenzeit. Alles in allem ist das typisch Informatik“. ” Anhang: Abschlussprojekte § Pycman § Zielschießen § n-body simulation § Gebirgspfade § Racko § Derivate § Sudoku § Autorschaft-Erkennung § Das Spiel PIG Ausführliche Beschreibungen finden Sie auf der Webseite mit den Übungsblättern. Pycman Eine Pacman-Variante in Python: Sie laufen durch ein Labyrinth, müssen Punkte sammeln und werden dabei von Monstern verfolgt . . . 5.0.2 Zielschießen Die Flugkurve einer Kanonenkugel lässt sich abhängig von ein paar Parametern mittels einer mathematischen Funktion berechnen. Schreiben Sie ein Programm mit schöner grafischer Ausgabe, mit dem man ein Ziel trotz Hindernissen treffen kann. 5.0.3 n-body simulation Sir Isaac Newton formulierte 1687 in seiner Principia die Gesetze, die die Bewegung zweier Körper unter dem gegenseitigen Einfluss ihrer Gravitation beschreiben. Newton war jedoch nicht in der Lage, das Problem für mehr als zwei Körper zu lösen. In der Tat ist dies für drei oder mehr Körper nur numerisch lösbar. Oft werden aber derartige Simulationen gebraucht, um komplexe physikalische Systeme zu studieren. Schreiben Sie ein Programm dafür. 5.0.4 Gebirgspfade Finden Sie Wege durch Gebirge mit möglichst geringen Höhenunterschieden. 5.0.5 Racko Racko ist ein Kartespiel für zwei (oder mehr) Spieler. Jeder Spieler spielt mit 10 Karten auf der Hand. Er darf die Reihenfolge seiner Handkarten nicht ändern. Das Ziel ist es, eine sortierte Folge von Karten auf der Hand zu haben. In jedem Zug kann er eine Karte aufnehmen (nach bestimmten Regeln) und gegen eine Handkarte tauschen . . . Schreiben Sie ein Programm, das bei Racko gewinnt. 5.0.6 Derivate 5.0.7 Sudoku Schreiben Sie ein Programm, das Sudokus löst und lösbare Sudokus produziert. Versuchen Sie, ein möglichst schnelles Programm zu schreiben. 5.0.8 Autorschafts-Test Jeder Text/Autor besitzt eine Signatur aus § durchschnittlicher Wortlänge, § die Anzahl verschiedener Wörter im Verhältnis zur Anzahl der Wörter des Textes, § die Anzahl aller Wörter, die nur einmal im Text vorkommen, im Verhältnis zur Anzahl aller Wörter des Textes, § die durchschnittliche Anzahl von Wörtern pro Satz, und § die durchschnittliche Anzahl von Satzteilen pro Satz. Signaturen lassen sich vergleichen. Aufgabe: bestimme Signaturen von ein paar Autoren und die Ähnlichkeit der Signatur eines Textes zu denen der (bekannten) Autoren. Das Spiel PIG PIG ist ein Würfelspiel für 2 Spieler mit einem Würfel. Die Spieler machen abwechselnd einen Zug. Ein Zug besteht aus einer Folge von Würfen des Würfels. Der ziehende Spieler bestimmt, wann er aufhört zu würfeln. Wenn er im Zug eine 1 würfelt, dann endet der Zug und ergibt 0 Punkte. Wenn der Spieler aufhört zu würfeln, bevor er eine 1 gewürfelt hat, dann erhält er die Summe aller Würfe im Zug als Punkte. Es gewinnt der Spieler, der zuerst aus der Summe seiner Züge mindestens 100 Punkte erreicht. Aufgabe: programmiere Wettkampf zwischen zwei Spielern. Finde eine gute Gewinnstrategie. Die Modulprüfung Die Modulprüfung ist eine mündliche Prüfung und dauert ca. 30 Minuten. Das Prüfungsthema ist entweder der Inhalt von Vorlesung und Übung oder die Vorführung und Diskussion Ihres Abschlussprojekts. Das Abschlussprojekt kann aus der Liste der Projekte gewählt werden oder ein eigenes Projekt sein (nach Rücksprache mit mir). Bei Vorführung und Diskussion des Abschlussprojekts müssen alle notwendigen Dateien (Module und Daten) mindestens 30 Stunden vor Beginn der Prüfung bei [email protected] eingetroffen sein. 5.0.11