Wie entwerfen Sie eine Programmschleife? Eine systematische Methode hierfür durchläuft die folgenden drei Schritte:
Die Programmschleifen sind hier zunächst in der Programmiersprache Java angegeben; zusätzlich ist auch jeweils noch eine Version in der Programmiersprache Python angegeben.
Ein wichtiges Anwendungsgebiet für Programmschleifen ist die iterative Berechnung von Funktionswerten.
Beispiel: Die Berechnung von an, von n!, von ex, von π, von Binomialkoeffizienten usw. lässt sich durch Iterationsverfahren durchführen.
So berechnen Sie etwa an, indem Sie den Anfangswert 1 n-mal mit a multiplizieren. Den Funktionswert von ex erhalten Sie als Grenzwert einer Reihe. In ähnlicher Weise erhalten Sie π als Grenzwert einer Folge oder Reihe.
Ein Iterationsschema ist eine Tabelle, die für jede Iteration die Werte der beteiligten Variablen enthält. Für jede Variable, die während der Iteration ihren Wert ändert, ist eine Tabellenzeile vorgesehen.
Beispiel: Ein mögliches Iterationsschema für die Berechnung von an ist das folgende:
i | 0 | 1 | 2 | 3 | 4 | ... | n |
---|---|---|---|---|---|---|---|
p | 1 | a | a·a | a·a·a | a·a·a·a | ... | an |
Die Variable i dient zum Zählen der Iterationen, die Variable p enthält das jeweilige Zwischenergebnis nach jeder Iteration. Für die Variable a ist keine Zeile im Iterationsschema vorgesehen, da sie ihren Wert im Verlauf der Berechnung nicht ändert. Das Ergebnis der Berechnung ist der Wert von p in der Spalte, in der i = n ist.
Aus dem Iterationsschema leiten Sie Iterationsgleichungen ab. Für jede Variable des Iterationsschemas stellen Sie eine Iterationsgleichung auf. Darin geben Sie an, wie der Wert dieser Variablen ab der zweiten Spalte zu berechnen ist. Den Wert in der ersten Spalte legen Sie durch Initialisierung fest.
Beispiel: Die Iterationsgleichungen für das obige Iterationsschema lauten
Hierbei bezeichnet v' den Wert der Variablen v in der vorherigen Spalte.
So erhalten Sie den Wert von i in jeder Spalte als Wert von i in der vorherigen Spalte plus 1. Den Wert von p erhalten Sie als Wert von p in der vorherigen Spalte mal a.
Die Iterationsgleichungen gelten in diesem Beispiel für alle Spalten des Iterationsschemas außer der ersten Spalte. Die Werte der ersten Spalte legen Sie durch entsprechende Initialisierungen fest:
Die Iterationsgleichungen können Sie unmittelbar in ein Programm umsetzen, wenn Sie die folgenden Regeln einhalten.
Anschaulich gesehen dürfen Sie für die Berechnung eines Wertes im Iterationsschema nur die darüberliegenden Werte oder die direkt links daneben und darunter liegenden Werte des Iterationsschemas benutzen. So dürfen Sie für die Berechnung des Wertes an Position X nur Werte aus den mit x gekennzeichneten Feldern verwenden (und natürlich Werte von sonstigen Variablen und Konstanten).
x | ||||
x | ||||
x | X | |||
x | ||||
x |
Bild 1: Zulässige Variablen auf der rechten Seite einer Iterationsgleichung
Beispiel: In den obigen Iterationsgleichungen
i = i' + 1
p = p' · a
kommt auf der rechten Seite der Gleichung für i nur die Variable i' vor, die im Iterationsschema nicht oberhalb von i steht, sowie die Konstante 1. Auf der rechten Seite der Gleichung für p kommt nur die Variable p' vor, die im Iterationsschema nicht oberhalb von p steht, sowie die sonstige Variable a.
Nachdem Sie die Iterationsgleichungen nach den obigen Regeln aufgestellt haben, setzen Sie diese direkt in eine While-Schleife um. Vor die While-Schleife fügen Sie noch die Initialisierung der beteiligten Variablen ein. Als Schleifenbedingung wählen Sie eine Bedingung, die im Iterationsschema für alle Spalten vor der Spalte gilt, in der Sie das Ergebnis der Berechnung erwarten.
Beispiel: Das Beispiel zur Berechnung von an ergibt, unter Benutzung der entsprechenden Iterationsgleichungen und Initialisierungen, folgendes Java-Programmstück. Vorausgesetzt ist, dass die Variablen a und n bereits deklariert sind und Werte enthalten. Das Ergebnis der Berechnung ist der Wert von p nach Beendigung der Schleife. Daneben finden Sie die entsprechende Version in Python.
Java
int i=0, p=1;
while (i<n) { i=i+1; p=p*a; } | Python
i=0
p=1 while i<n: i=i+1 p=p*a |
Wo sind die Striche geblieben? Aus der Iterationsgleichung i = i' +1 ist die Wertzuweisung i=i+1 geworden. In der Wertzuweisung wird auf der rechten Seite auf den alten Wert von i zugegriffen. Dieser Wert plus 1 wird dann der Variablen i als neuer Wert zugewiesen. In der Iterationsgleichung dagegen müssen alter und neuer Wert unterschiedlich bezeichnet sein.
Für die formale Programmverifikation benötigen Sie eine Schleifeninvariante; dies ist eine Bedingung, die beim Eintritt in den Schleifenkörper gilt und zum Schluss des Schleifenkörpers wiederum gilt.
Aus dem oben angegebenen Iterationsschema lesen Sie eine solche Schleifeninvariante P als Bedingung ab, die für alle Spalten vor und einschließlich der Spalte gilt, in der Sie das Ergebnis erwarten. Die Bedingung lautet:
P : p = ai ∧ i ≤ n
Die alternierende harmonische Reihe konvergiert (allerdings sehr langsam) gegen den Grenzwert ln(2).
ln(2) = 1 – 1/2 + 1/3 – 1/4 + 1/5 – 1/6 + ...
Für die Berechnung der Reihe verwenden Sie folgendes Iterationsschema:
n | 0 | 1 | 2 | 3 | 4 | 5 | ... |
s | 1/1 | 1/2 | 1/3 | 1/4 | 1/5 | ... | |
v | -1 | 1 | -1 | 1 | -1 | 1 | ... |
p | 0 | 1 | 1-1/2 | 1-1/2+1/3 | ... |
Die Variable n zählt die Iterationen, s enthält jeweils das nächste Reihenglied, v das zugehörige Vorzeichen und p die jeweils aufgelaufene Partialsumme.
Aus dem Iterationsschema gewinnen Sie folgende Iterationsgleichungen:
Auf den rechten Seiten der Iterationsgleichungen benutzen Sie nur Variablen, die nach dem Schema von Bild 1 zulässig sind.
Die Iterationsgleichungen gelten ab der zweiten Spalte. Für die erste Spalte initialisieren Sie die Variablen wie folgt:
Die Iterationsgleichungen und Initialisierungen setzen Sie in folgendes While-Programm um. Die Schleife wird 1000-mal durchlaufen. Das Ergebnis ist der Wert von p nach Ende der Iteration.
Java
int n=0, v=-1;
double s, p=0; while (n<1000) { n=n+1; s=1.0/n; v=-v; p=p+v*s; } | Python
n=0
v=-1 p=0 while n<1000: n=n+1 s=1/n v=-v p=p+v*s |
Das Iterationsschema in der Form, wie Sie es bisher kennengelernt haben, ist in sequenzielles Iterationsschema. Denn es ist gedacht für die sequenzielle Ausführung von Programmschritten. So stehen etwa Variablen "mit Strich", deren Werte bereits durch neue Werte überschrieben worden sind, nicht mehr zur Verfügung. Im Gegensatz dazu stehen in einem parallelen Iterationsschema alle Variablen "mit Strich", also die Werte aus der vorherigen Spalte, uneingeschränkt zur Verfügung. Es ist gedacht für Programmiersprachen, in denen parallele Wertzuweisungen möglich sind. In Python haben Sie diese Möglichkeit durch Tupel-Wertzuweisungen.
Eine entsprechende Veranschaulichung zeigt das folgende Bild 2.
x | ||||
x | ||||
x | X | |||
x | ||||
x |
Bild 2: Paralleles Iterationsschema: zulässige Variablen auf der rechten Seite einer Iterationsgleichung
Anschaulich gesehen können Sie für die Berechnung eines Wertes an Position X im Iterationsschema alle in der linken Spalte daneben liegenden Werte x benutzen.
Das oben angegebene Iterationsschema für die Berechnung von an lässt sich mit den zugehörigen Iterationsgleichungen auch als paralleles Iterationsschema auffassen. Umgesetzt in ein Python-Programmstück mit Tupel-Wertzuweisung erhalten Sie
Vielfach ist es aber einfacher, sequenziell zu programmieren. In dem obigen sequenziellen Iterationsschema für die Berechnung von ln(2) beispielsweise ermitteln Sie zunächst in aller Ruhe den Wert 1/n und das neue Vorzeichen v, um diese dann in der letzten Zeile des Programms zu verwenden.
Bei der Umsetzung eines rein parallelen Iterationsschemas erhalten Sie ein Python-Programm, bei dem der Schleifenkörper der While-Schleife nur aus einer einzigen Tupel-Wertzuweisung besteht.
Für die oben angegebene Berechnung von ln(2) verwenden Sie das folgende parallele Iterationsschema:
n | 1 | 2 | 3 | 4 | 5 | ... |
v | 1 | -1 | 1 | -1 | 1 | ... |
p | 0 | 1 | 1-1/2 | 1-1/2+1/3 | ... |
Aus dem Iterationsschema gewinnen Sie folgende Iterationsgleichungen:
Beachten Sie, dass auf der rechten Seite der Iterationsgleichungen nur Variablen "mit Strich" vorkommen. Die Werte in der ersten Spalte des Iterationsschemas legen Sie wie gewohnt durch Initialisierung fest.
Die Iterationsgleichungen und Initialisierungen setzen Sie anschließend in ein Python-Programm mit einer einzigen Tupel-Wertzuweisung im Schleifenkörper der While-Schleife um:
Python
In funktionalen Programmiersprachen wie etwa Haskell treten endrekursive Funktionen an die Stelle von Iterationen. Auch hier lassen sich mithilfe eines parallelen Iterationsschemas in systematischer Weise Iterationsgleichungen gewinnen, die unmittelbar in entsprechende endrekursive Funktionsdefinitionen umgesetzt werden können.
Das Iterationsschema für die Berechnung von an lässt sich beispielsweise unmittelbar in folgendes Haskell-Programm überführen:
Die Funktion iterate bildet genau die Iterationsgleichungen nach. Der Aufruf von iterate zu Beginn enthält die Anfangswerte für i und p. Die Iteration endet, wenn der Spaltenindex i = n erreicht ist; dann wird als Ergebnis der Wert von p in dieser Spalte zurückgeliefert. Ansonsten ruft die Funktion iterate sich rekursiv selber auf, mit Parametern, die den rechten Seiten der Iterationsgleichungen entsprechen. Somit werden in den jeweiligen Aufrufen der Funktion iterate die Werte des Iterationsschemas von links nach rechts berechnet: iterate 0 1, iterate 1 a, iterate 2 a·a usw.
In diesem Fall lässt sich zwar an in direkter Weise mit einer endrekursiven Funktion berechnen, aber mithilfe des oben angegebenen Schemas überführen Sie beliebige Iterationsgleichungen in eine endrekursive Funktion.
Aufgabe 1: (Berechnung der eulerschen Zahl e)
Die folgende Reihe konvergiert gegen die eulersche Zahl e = 2,71828...:
1 |
n! |
1 |
0! |
1 |
1! |
1 |
2! |
1 |
3! |
1 |
4! |
Stellen Sie ein Iterationsschema für die Berechnung von e auf. Leiten Sie daraus die zugehörigen Initialisierungen und Iterationsgleichungen ab und setzen Sie diese in eine While-Schleife um. Brechen Sie die While-Schleife ab, wenn der neu hinzugekommene Summand kleiner als 10-14 ist.
Aufgabe 2: (Berechnung von π)
Die Leibniz-Reihe konvergiert (allerdings sehr langsam) gegen den Grenzwert π/4:
π/4 = 1 – 1/3 + 1/5 – 1/7 + 1/9 – 1/11 + 1/13 – ...
Stellen Sie ein Iterationsschema für die Berechnung von π/4 auf. Leiten Sie daraus die zugehörigen Initialisierungen und Iterationsgleichungen ab und setzen Sie diese in eine While-Schleife um. Brechen Sie die While-Schleife ab, wenn der neu hinzugekommene Summand betragsmäßig kleiner als 10-4 ist.
Aufgabe 3: (Berechnung von Binomialkoeffizienten)
Der Binomialkoeffizient ist definiert als
n |
k |
n! |
k! · (n-k)! |
Da der Bruch durch (n-k)! gekürzt werden kann, gilt
n |
k |
(n-k+1) · ... · n |
1 · ... · k |
Als Beispiel ist etwa
49 |
6 |
44 · 45 · 46 · 47 · 48 · 49 |
1· 2 · 3 · 4 · 5 · 6 |
Stellen Sie ein Iterationsschema für die Berechnung von auf. Leiten Sie daraus die zugehörigen Initialisierungen und Iterationsgleichungen ab und setzen Sie diese in eine While-Schleife um.