Schleifen - Abteilung für Angewandte Mathematik

Grundlagen der Programmiersprache C für
Studierende der Naturwissenschaften
Teil 3: Schleifen- und Sprunganweisungen
Patrick Schreier
Abteilung für Angewandte Mathematik
Vorlesung vom 03. Mai 2015
Gliederung
Bedingte Anweisungen II
Motivation Schleifenanweisungen
for-Schleife
while, do-while-Schleifen
Sprunganweisungen
Lösung gewöhnlicher Differentialgleichungen
Gliederung
Bedingte Anweisungen II
Motivation Schleifenanweisungen
for-Schleife
while, do-while-Schleifen
Sprunganweisungen
Lösung gewöhnlicher Differentialgleichungen
Programmbeispiel
Quelltext (Wurzelberechnung mit Fehlerbehandlung)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
#include <math.h>
#include <stdio.h>
int main(void)
{
/* declare floating point variable */
double x;
/* read from user input */
printf ("x = ");
scanf("%lf", &x);
if( x < 0 )
{
printf("value x is less than zero\n");
}
else
{
printf ("x = %f\n", x);
printf ("sqrt(x) = %f\n", sqrt(x));
}
return 0;
}
if-else-Anweisung
Syntax:
if(expr)
statement
else
statement
Ob die Anweisungen des if- oder des else-Zweigs bearbeitet
werden, hängt vom Kontrollausdruck ab:
I
Ist der Kontrollausdruck wahr, so wird der if-Zweig bearbeitet,
I
sonst werden die Anweisungen des else-Zweigs bearbeitet.
Sowohl im if- als auch im else-Zweig können mehrere
Anweisungen zu jeweils einem Block zusammengefasst werden.
Dangling else
Vorsicht bei verschachtelten Fallunterscheidungen: Der else-Zweig
gehört immer zum vorangehenden if-Zweig!
Beispiel:
int a;
...
if(a >= 0)
if(a == 0)
printf("a equals 0\n");
else
printf("a is negative number\n");
Der Code liefert eine falsche Ausgabe für a > 0. Man spricht vom
dangling else.
Dangling else vermeiden
Lösungsmöglichkeit 1 (leere Anweisung):
int a;
...
if(a >= 0)
if(a == 0)
printf("a equals 0\n");
else ;
else
printf("a is negative number\n");
Dangling else vermeiden
Lösungsmöglichkeit 1 (leere Anweisung):
int a;
...
if(a >= 0)
if(a == 0)
printf("a equals 0\n");
else ;
else
printf("a is negative number\n");
Lösungsmöglichkeit 2 (Blöcke):
int a;
...
if(a >= 0)
{
if(a == 0)
printf( "a equals 0\n" );
}
else
printf("a is negative number\n");
if-else if – Schreibweise
Für komplexe, verschachtelte Fallunterscheidungen ist die folgende
Schreibweise üblich:
if(expr)
statement
else if(expr)
statement
else if(expr)
statement
...
else
statementN
Der else-Zweig und die Zusammenfassung von Anweisungen zu
Blöcken ist wieder optional.
Vorsicht, Falle I
Gleitpunktoperationen sind immer mit einem kleinen Fehler behaftet.
Gleitpunktzahlen sollten daher nie auf exakte Gleichheit überprüft
werden:
double x = (0.1 + 0.2) + 0.3;
double y = 0.1 + (0.2 + 0.3);
if(x == y)
...
/* will be false */
Stattdessen kann man prüfen, ob zwei Gleitpunktzahlen hinreichend
„nah“ beieinander liegen:
if(fabs(x-y) < 1e-12)
...
Vorsicht, Falle II
Ein häufiger Fehler bei Vergleichen auf Gleichheit ist der folgende:
if(x = 1)
...
/* always true */
Der Kontrollausdruck besteht aus einer Zuweisung. Ihr Wert
entspricht dem der linken Seite nach der Zusweisung (hier 1). Dies
entspricht einer „wahren“ Aussage.
Analog ist der folgende Kontrollausdruck immer „unwahr“:
if(x = 0)
...
/* always false */
switch-Anweisung
Die switch-Anweisung prüft einen ganzzahligen Ausdruck auf
mehrere konstante Alternativen hin ab.
Syntax:
switch(expr)
{
case const_expr1 : statement
case const_expr2 : statement
...
default : statementN
}
I
Alle case-Konstanten müssen unterschiedliche Werte haben.
I
Stimmt der switch-Ausdruck expr mit einer der case-Marken
überein, wird der Programmfluss hinter der Marke fortgesetzt.
I
Die default-Marke ist optional; stimmt der switch-Ausdruck
mit keiner der angegebenen Konstanten überein, wird die
default-Marke angesprungen.
break in switch-Anweisungen
Stimmt der switch-Ausdruck mit einer der case-Marken überein,
werden alle Anweisungen danach ausgeführt – auch solche, die
hinter späteren Marken stehen.
Sollen nur die Anweisungen ausgeführt werden, die zu einer
case-Marke gehören, muss dies explizit durch eine
break-Anweisung erzwungen werden:
int n = 1;
switch(n)
{
case 1: printf("n = 1\n");
break;
case 3: printf("n = 3\n");
break;
case 2: printf("n = 2\n");
break;
default: printf("n >= 4\n");
}
Konditionaloperator
Der einzige ternäre Operator ist der Konditionaloperator ?:
expr1 ? expr2 : expr3
Der Wert des Typ und Wert des Ausdrucks stimmt entweder mit dem
von expr2 oder expr3 überein und zwar abhängig von expr1:
I
Zuerst wird der Ausdruck expr1 ausgewertet.
I
Ist expr1 ungleich Null, so heißt der Ausdruck wahr.
I
Ist expr1 wahr, wird der Ausdruck expr2 ausgewertet, sonst
expr3.
Nur genau einer der Ausdrücke expr2, expr3 wird ausgewertet!
Beispiel:
c = (a > b) ? a : b
/* c = min{a, b} */
Inkrement- und Dekrement-Operatoren
Um eine Variable um den Wert 1 zu erhöhen oder um den Wert 1 zu
erniedrigen gibt es in C die unären Operatoren ++
(Inkrementoperator) und -- (Dekrementoperator).
Die Operatoren können auf alle arithmetischen Typen angewandt
werden, und stehen entweder vor (Präfix-Notation) oder hinter
(Postfix-Notation) der Variablen:
++x
x++
−−x
x−−
x
x
x
x
wird um den Wert 1 erhöht, bevor x im Ausdruck weiterverwendet wird
wird um den Wert 1 erhöht, nachdem x im Ausdruck verwendet wurde
wird um den Wert 1 erniedrigt, bevor x im Ausdruck weiterverwendet wird
wird um den Wert 1 erniedrigt, nachdem x im Ausdruck verwendet wurde
Beispiel:
int a = 0, b = 2;
++a; /* a = 1 */
b--; /* b = 1 */
Seiteneffekte
Jeder Zuweisungsausdruck darf selbst wieder in einem Ausdruck
auftauchen:
int a = 1, b;
b = (a += 1) + 1;
/* a = 2, b = 3 */
Solche verschachtelten Ausdrücke verursachen sog. Seiten- oder
Nebeneffekte, d. h. neben der Auswertung des Ausdrucks wird durch
die Zuweisung auch der Wert einer Variablen geändert.
Seiteneffekte machen den Code schwer lesbar und sind damit
potentielle Fehlerquellen.
Beispiel (Inkrement-Operatoren):
int
i =
j =
k =
i, j, k;
1;
i++; /* j = 1, side effect: i = 2 */
++i; /* k = 3, side effect: i = 3 */
Komma-Operator
Das Komma kennen wir bislang als Trennzeichen in
Variablendefinitionen:
int a1, a2, a3, a4;
Es gibt auch den Komma-Operator:
expr1, expr2
Seine Semantik ist etwas gewöhnungsbedürftig:
I
Zuerst wird der Ausdruck expr1 ausgewertet, dann expr2.
I
Der Typ und Wert des Gesamtausdrucks ist der Typ und der
Wert des zweiten Operanden expr2.
Beispiel:
int a;
double x;
a = 1, x = 2.;
/* value of expression is 2. */
Gliederung
Bedingte Anweisungen II
Motivation Schleifenanweisungen
for-Schleife
while, do-while-Schleifen
Sprunganweisungen
Lösung gewöhnlicher Differentialgleichungen
Schleifenanweisungen
Schleifenanweisungen . . .
I
sind Kontrollanweisungen, die die sonst sequentielle
Verarbeitung der Programmanweisungen ändern.
I
führen eine Anweisung (oder mehrere Anweisungen in einem
Block) wiederholt aus.
I
werden erst beendet, wenn ein vorher angegebenes
Abbruchkriterium erreicht ist (oder die Schleife mit einer
Sprunganweisung verlassen wird).
In C gibt es mehrere Schleifenanweisungen zur Verfügung (for-,
while- und do-while-Schleife).
Auswertung monomialer Ausdrücke
Wir wollen ein Programm schreiben, dass die n-Potenz x n einer
reellen Zahl x berechnet.
Der Definition eines Ausdrucks lässt sich nicht immer auch gleich ein
geeigneter Algorithmus zu seiner Auswertung entnehmen:
xn =
n
Y
i=1
x,
n
Y
i=1
x =x
. . · x},
| · .{z
n Mal
0
Y
x = 1.
i=1
Für unsere Zwecke besser geeignet ist die Darstellung in rekursiver
Form:
x n+1 = x · x n , x 0 = 1.
Algorithmus in Pseudo-Code
Wir halten den der rekursiven Formulierung oben entsprechenden
Algorithmus in Form von Pseudo-Code fest.
Pseudo-Code (n-te Potenz):
y ← 1
for i = 0, 1,..., n-1
y ← x*y
return y
% initialization
% number of iterations is fixed
% loop body, y is updated in each step
Im obigen Verfahren wird der Wert von y in jedem Iterationsschritt
überschrieben. Damit kommt dem Startwert
y ← 1
besondere Bedeutung zu.
Gliederung
Bedingte Anweisungen II
Motivation Schleifenanweisungen
for-Schleife
while, do-while-Schleifen
Sprunganweisungen
Lösung gewöhnlicher Differentialgleichungen
for-Schleife
Die for-Schleife hat von allen Schleifenanweisungen die
aufwändigste Syntax.
Syntax:
for(expr1; expr2; expr3)
statement
→ Der erste Ausdruck expr1 heißt Initialisierungsausdruck. Er wird
vor dem Kontrollausdruck und nur ein einziges Mal ausgewertet.
for-Schleife
Die for-Schleife hat von allen Schleifenanweisungen die
aufwändigste Syntax.
Syntax:
for(expr1; expr2; expr3)
statement
→ Der erste Ausdruck expr1 heißt Initialisierungsausdruck. Er wird
vor dem Kontrollausdruck und nur ein einziges Mal ausgewertet.
→ An zweiter Stelle steht der Kontrollausdruck. Er wird vor dem
ersten und dann nach jedem weiteren Schleifendurchlauf
bewertet. Die Schleife wird beendet, sobald der Kontrollausdruck
unwahr ist.
for-Schleife
Die for-Schleife hat von allen Schleifenanweisungen die
aufwändigste Syntax.
Syntax:
for(expr1; expr2; expr3)
statement
→ Der erste Ausdruck expr1 heißt Initialisierungsausdruck. Er wird
vor dem Kontrollausdruck und nur ein einziges Mal ausgewertet.
→ An zweiter Stelle steht der Kontrollausdruck. Er wird vor dem
ersten und dann nach jedem weiteren Schleifendurchlauf
bewertet. Die Schleife wird beendet, sobald der Kontrollausdruck
unwahr ist.
→ Der dritte Ausdruck wird nach jedem Schleifendurchlauf
ausgewertet. Normalerweise werden hier Schleifenvariablen
inkrementiert oder dekrementiert.
Programmbeispiel (n-te Potenz)
In einer for-Schleife lässt sich die n-te Potenz wie folgt berechnen:
int i, n;
double x, y;
...
/* initialize x, n */
y = 1.;
for(i = 0; i < n; ++i)
y *= x;
Zur Wahl der einzelnen Ausdrücke in der Schleife:
for( i = 0 ; i < n; ++i)
→ An erster Stelle steht der Initialisierungsausdruck. In C beginnen
Laufindizes in der Regel bei 0.
Programmbeispiel (n-te Potenz)
In einer for-Schleife lässt sich die n-te Potenz wie folgt berechnen:
int i, n;
double x, y;
...
/* initialize x, n */
y = 1.;
for(i = 0; i < n; ++i)
y *= x;
Zur Wahl der einzelnen Ausdrücke in der Schleife:
for(i = 0; i < n; ++i )
→ Nach jedem Schleifendurchlauf muss der Laufindex i
inkrementiert werden.
Programmbeispiel (n-te Potenz)
In einer for-Schleife lässt sich die n-te Potenz wie folgt berechnen:
int i, n;
double x, y;
...
/* initialize x, n */
y = 1.;
for(i = 0; i < n; ++i)
y *= x;
Zur Wahl der einzelnen Ausdrücke in der Schleife:
for(i = 0; i < n ; ++i)
→ Der Kontrollausdruck sorgt dann dafür, dass die Schleife soll
nach n Iterationen beendet wird.
Auswertung der Fakultät
Ein weiteres einfaches Beispiel zur Verwendung der for-Schleife ist
die Auswertung der n-ten Fakultat:
n! =
n
Y
i = 1 · 2 · . . . · n.
i=1
Dieser Ausdruck lässt sich leicht in Pseudo-Code übertragen:
Pseudo-Code (Fakultät):
y ← 1
for i = 1, 2,..., n
y ← i*y
return y
% initialization
% range based iteration
% index i is used in loop statement
Programmbeispiel (Auswertung der Fakultät)
Wie das folgende Programmbeispiel illustriert, ist es ohne weiteres
möglich, den Laufindex i im Schleifenkörper zu verwenden:
int factorial, i, n;
...
/* initialize n */
for(factorial = 1, i = 1; i <= n; ++i)
factorial *= i;
Es kann mehr als eine Variable im ersten Ausruck der Schleife
initialisiert werden (Komma-Operator).
Analog können auch die übrigen Ausdrücke komplexer gestaltet
werden:
for(factorial = 1, i = 1; i <= n; factorial *= i++) ;
Gliederung
Bedingte Anweisungen II
Motivation Schleifenanweisungen
for-Schleife
while, do-while-Schleifen
Sprunganweisungen
Lösung gewöhnlicher Differentialgleichungen
Beispiel: Der Euklidische Algorithmus
Der Euklidische Algorithmus ein klassisches Verfahren zur
Berechnung des größten gemeinsamen Teilers zweier natürlicher
Zahlen a, b.
In Pseudo-Code lautet der Algorithmus:
Pseudo-Code (Euklidischer Algorithmus):
if a = 0
return b
while b 6= 0
if a > b
a ← a - b
else
b ← b - a
return a
Die Anzahl der Iterationsschritte (Anweisungen innerhalb von while
und return) hängt von den Eingabegrößen a und b ab und ist nicht
a-priori bekannt.
while-Schleife
Die while-Schleife hat von allen drei C-Schleifen die einfachste
Syntax:
while(expr)
statement
Der Ausdruck expr heißt Kontrollausdruck. Er muss von einem
arithmetischen Typ (oder ein Zeiger) sein.
→ Der Kontrollausdruck wird mit allen Seiteneffekten ausgewertet.
Ist er wahr (d. h. ungleich 0), wird die folgende
Schleifenanweisung statement wiederholt ausgeführt.
while-Schleife
Die while-Schleife hat von allen drei C-Schleifen die einfachste
Syntax:
while(expr)
statement
Der Ausdruck expr heißt Kontrollausdruck. Er muss von einem
arithmetischen Typ (oder ein Zeiger) sein.
→ Der Kontrollausdruck wird mit allen Seiteneffekten ausgewertet.
Ist er wahr (d. h. ungleich 0), wird die folgende
Schleifenanweisung statement wiederholt ausgeführt.
→ Nach jedem Schleifendurchlauf wird der Kontrollausdruck erneut
ausgewertet.
while-Schleife
Die while-Schleife hat von allen drei C-Schleifen die einfachste
Syntax:
while(expr)
statement
Der Ausdruck expr heißt Kontrollausdruck. Er muss von einem
arithmetischen Typ (oder ein Zeiger) sein.
→ Der Kontrollausdruck wird mit allen Seiteneffekten ausgewertet.
Ist er wahr (d. h. ungleich 0), wird die folgende
Schleifenanweisung statement wiederholt ausgeführt.
→ Nach jedem Schleifendurchlauf wird der Kontrollausdruck erneut
ausgewertet.
→ Ist der Kontrollausdruck unwahr, wird die Schleife beendet.
Abweisende Schleife
Ob und wie häufig der Schleifenkörper (auch Schleifenrumpf
genannt) durchlaufen wird, hängt vom Kontrollausdruck ab.
In einer while-Schleife wird der Kontrollausdruck
→ vor dem Eintritt in die Schleife
→ und bei jedem erneuten Schleifendurchlauf
ausgewertet.
Die while-Schleife heißt auch abweisende Schleife: Ist der
Kontrollausdruck bereits vor Eintritt in die Schleife unwahr, wird der
Schleifenkörper nie durchlaufen (d. h. die nach while folgende
Anweisung wird nicht ausgeführt).
Programmbeispiel (Euklidischer Algorithmus)
Quelltext
#include <stdio.h>
int main(void)
{
int a, b;
printf("Enter a: ");
scanf("%d", &a);
printf("Enter b: ");
scanf("%d", &b);
if(a == 0)
{
printf("greatest common divisor(a,b) = %d\n", b);
return 0;
}
while(b != 0)
{
if(a > b)
a -= b;
else
b -= a;
}
printf("greatest common divisor(a,b) = %d\n", a);
return 0;
}
Endlosschleifen
Eine Schleife wird erst verlassen, wenn der Kontrollausdruck unwahr
ist.
Wird das Abbruchkriterium nie erreicht (d. h. bleibt der
Kontrollausdruck immer wahr), kommt es zu einer Endlosschleife:
while(1) ;
/* results in infinite loop */
→ Hängt ein Programm in einer Endlosschleife fest, muss es vom
User oder vom System abgebrochen werden.
→ Der Compiler kann eine Endlosschleifen nicht entdecken: das
Programm ist syntaktisch korrekt!
Gleitpunktzahlen in Kontrollausdrücken
Auch das folgende Beispiel liefert eine Endlosschleife:
float x = 1./9.;
while(x != 1.)
x += 1./9.;
Aufgrund von unvermeidbaren Approximationsfehlern liefert der
Kontrollausdruck
x != 1.
stets den Wert 0 („Unwahr“).
Eine Lösung dieses Problems könnte so aussehen:
float x = 0.1;
while(x <= 1.0)
x += 0.1;
do-while-Schleife
Die do while-Schleife funktionert ähnlich wie eine while-Schleife
mit dem Unterschied, dass der Schleifenkörper mindestens einmal
durchlaufen wird.
Syntax:
do
statement
while(expr);
→ Die Anweisung statement (auch ein Block ist erlaubt) wird
ausgeführt, danach wird der Kontrollausdruck expr
ausgewertet.
do-while-Schleife
Die do while-Schleife funktionert ähnlich wie eine while-Schleife
mit dem Unterschied, dass der Schleifenkörper mindestens einmal
durchlaufen wird.
Syntax:
do
statement
while(expr);
→ Die Anweisung statement (auch ein Block ist erlaubt) wird
ausgeführt, danach wird der Kontrollausdruck expr
ausgewertet.
→ Dies wird solange wiederholt ausgeführt, wie der
Kontrollausdruck wahr ist.
do-while-Schleife
Die do while-Schleife funktionert ähnlich wie eine while-Schleife
mit dem Unterschied, dass der Schleifenkörper mindestens einmal
durchlaufen wird.
Syntax:
do
statement
while(expr);
→ Die Anweisung statement (auch ein Block ist erlaubt) wird
ausgeführt, danach wird der Kontrollausdruck expr
ausgewertet.
→ Dies wird solange wiederholt ausgeführt, wie der
Kontrollausdruck wahr ist.
→ Ist der Kontrollausdruck unwahr, wird die Schleife verlassen.
Äquivalenz von for- und while-Schleife
Alle Schleifenanweisungen sind äquivalent, d. h. man kann jede der
drei Schleifen durch jede der jeweils anderen beiden – in Verbindung
mit bedingten Anweisungen – ersetzen.
Eine for-Schleife beispielsweise
for(expr1; expr2; expr3)
statement
lässt sich stets äquivalent als while-Schleife formulieren:
expr1
while(expr2)
{
statement
expr3
}
Gliederung
Bedingte Anweisungen II
Motivation Schleifenanweisungen
for-Schleife
while, do-while-Schleifen
Sprunganweisungen
Lösung gewöhnlicher Differentialgleichungen
Sprunganweisungen
Sprunganweisungen ändern den sequentiellen Programmablauf,
indem ohne weitere Bedingungen an eine andere Stelle im
Programm (im Quellcode) gesprungen wird.
In C gibt es die folgenden Sprunganweisungen:
I
break
I
continue
I
goto
I
return
Sprunganweisungen werden gebraucht . . .
I
um Schleifen oder eine switch-Anweisung (s. u.) zu verlassen
oder
I
um ein Programm oder eine Funktion zu beenden.
break-Anweisung
Die break-Anweisung bewirkt, dass die innerste umgebende
Schleife einer for-, while- oder do while-Anweisung oder eine
switch-Anweisung sofort verlassen wird.
Syntax:
break;
Beispiel:
int i = 0;
while(1)
/* no infinite loop due to break */
{
i++;
if(i > 99)
break;
}
continue-Anweisung
Die continue-Anweisung darf nur innerhalb einer for-, whileoder do while-Schleife verwendet werden.
Sie bewirkt, dass der aktuelle Schleifendurchlauf beendet wird und
die Schleife mit der nächsten Iteration fortgesetzt wird.
Syntax:
continue;
Beispiel:
int i;
for(i = 0; i < 10; ++i)
{
if(i%2)
continue;
printf("%d is an even number\n", i);
}
return-Anweisung
Die return-Anweisung beendet eine Funktion. Handelt es sich bei
dieser Funktion um main, so wird das Programm beendet.
Syntax:
return expr;
Manche Funktionen (z. B. main) geben einen Wert zurück. Dies
geschieht im Quellcode durch den Aufruf von return. Rückgabetyp
und -wert stimmen dann mit expr überein.
Auch return gehört zu den Sprunganweisungen: der
Programmfluss wird an der Stelle fortgesetzt, an der die Funktion
aufgerufen wurde.
DIE Sprunganweisung – goto
Mit der Sprunganweisung goto kann an eine beliebige andere, mit
einer Marke ausgezeichnete Stelle (innerhalb derselben Funktion)
gesprungen werden:
11:
12:
printf( "First I'm here." );
goto label;
126: label:
127:
printf( "Now I'm here.");
Marken und Sprunganweisung
Eine Marke (engl. label) zeichnet eine mögliche Einsprungstelle im
Quellcode für die goto-Anweisung aus. Marken können an jeder
beliebigen Stelle im Quellcode stehen.
Eine Marke besteht aus einem Bezeichner und einem Doppelpunkt:
label:
statement
Dem Standard nach muss auf eine Marke mindestens eine
Anweisung (auch eine leere Anweisung) folgen.
Mit der Sprunganweisung
goto label;
wird eine Marke innerhalb derselben Funktion angesprungen.
Kritik an goto
Die goto-Anweisungen wird häufig kritisch kommentiert. Das Urteil
ist nicht immer einheitlich.
Pro:
+ Tief verschachtelte Schleifen können z. B. im Fehlerfall gezielt
verlassen werden.
Contra:
- Die Sprunganweisung kann immer mit anderen Sprachmitteln
vermieden werden.
- Extensiver Gebrauch macht Programme schwer lesbar und
damit fehleranfällig.
Manche Programmiersprachen unterbinden die Verwendung von
goto (z. B. Java). Im Handbuch der Skriptsprache Lua hingegen
werden die Vorteile unter bestimmten Bedingungen betont.
Akademisches Beispiel: Schleifen mit goto
Mit bedingten Sprunganweisungen (d. h. Kombination von if +
goto) lassen sich die oben vorgestellten Schleifenanweisungen
simulieren.
Beispielsweise ist der folgende Code
int i;
i = 0;
loop:
if(i < 10)
{
printf("i = %d\n", i);
++i;
goto loop;
}
äquivalent zu der for-Schleife:
int i;
for(i = 0; i < 10; ++i)
printf("i = %d\n", i);
Gliederung
Bedingte Anweisungen II
Motivation Schleifenanweisungen
for-Schleife
while, do-while-Schleifen
Sprunganweisungen
Lösung gewöhnlicher Differentialgleichungen
Bakterienwachstum
Obwohl wir erst wenige Sprachmittel zur Verfügung haben, können
wir bereits einige interessante Fragestellungen praktisch
untersuchen.
Wir betrachten das Wachstum eines Bakterienkultur über einen
Zeitraum [0, T ]. Zu Beginn der Beobachtung t = 0 werden p0
Bakterien geschätzt.
Wir möchten ein Programm näherungsweise bestimmen lassen, wie
groß die Bakterienpopulation p in der Probe zum Zeitpunkt t ∈ [0, T ]
ist.
Mathematische Modellierung I
Angenommen, die Zahl der Bakterien p(t) zum Zeitpunkt t ist
bekannt. Wir beobachten, dass sich kurze Zeit später, zum Zeitpunkt
t + ∆t, die Population wie folgt verändert hat:
p(t + ∆t) = p(t) + ∆tλp(t).
Wir formulieren die obige Gleichung um zu
p(t + ∆t) − p(t)
= λp(t).
∆t
Lassen wir nun ∆t → 0 gehen, so erhalten wir die gewöhnliche
Differentialgleichung:
p0 (t) = λp(t)
für t ∈ (0, T ).
Mathematische Modellierung II
Die Größe der Population ist zum Zeitpunkt t = 0 bekannt. Die
Lösung p erfüllt also das Anfangswertproblem
p0 (t) = λp(t)
für t ∈ (0, T ),
p(0) = p0 .
Das obige Problem hat eine eindeutige Lösung, die wir sogar
angeben können:
p(t) = p0 eλt
Im allgemeinen sind in Anwendungen keine Lösungen in
geschlossener analytischer Form gegeben. Dann ist man auf
numerische Verfahren angewiesen.
Numerisches Verfahren
Sei ∆t eine gewählte Zeitschrittweite, dann setzen wir tk = k ∆t.
Gesucht sind Näherungen
pk ≈ p(tk ),
wobei der Anfangswert p0 bekannt ist.
Aus der Näherung
p(t + ∆t) ≈ p(t) + ∆tλp(t)
erhalten wir die folgende Vorschrift:
pk +1 = pk + ∆tλpk .
Dieses sog. explizite Verfahren lässt sich sehr einfach
implementieren.
Komplettes Programmbeispiel
Quelltext (Unbeschränktes Wachstum)
#include <stdio.h>
int main(void)
{
double initial = .5;
double rate = 1.;
double time, endTime = 4.;
double deltaT = 0.05;
double population;
for(time = 0., population = initial; time < endTime; time += deltaT)
{
printf("%f %f\n", time, population);
population += deltaT*rate*population;
}
printf("%f %f\n", time, population);
return 0;
}
Visualisierung von Daten I
Das obige Programm liefert je nach gewählter Zeitschrittweite ∆t
eine große Menge an Daten.
Daten werden in der Regel zur späteren Verarbeitung
(Postprocessing) in Dateien gespeichert. Für den Moment nutzen wir
dazu die Unix-Shell.
Ausgabe in Datei population.out umleiten:
$ ./population > population.out
Aufruf von gnuplot und Plotten der Daten:
$ gnuplot
gnuplot> plot "population.out"
Visualisierung von Daten II
30
numerical solution
exact solution
25
Population p(t)
20
15
10
5
0
0
0.5
1
1.5
2
2.5
3
3.5
Time t
Abbildung : Ergebnis für λ = 1, p0 = 0.5, ∆t = 0.05
4
Logistisches Wachstum I
Beim sog. logistischen Wachstum wird die Populationsgröße durch
eine Kapazitätsgrenze C beschränkt.
Das modifizierte mathematische Modell lautet:
p0 (t) = λ(C − p(t))p(t) für t ∈ (0, T ),
p(0) = p0
mit p0 ≤ C.
Aus der Herleitung oben erhalten wir die folgende Vorschrift:
pk +1 = pk + ∆tλ(C − pk )pk .
Logistisches Wachstum II
1.4
numerical solution
exact solution
1.2
Population p(t)
1
0.8
0.6
0.4
0.2
0
0
0.5
1
1.5
2
2.5
3
3.5
4
Time t
Abbildung : Ergebnis für λ = 1, C = 1, p0 = 0.5, ∆t = 0.05