Computerorientierte Mathematik I

Werbung
10
Kapitel 2
Probleme, Algorithmen, Programme:
Einige Beispiele
2.1
2.1.1
Temperatur Umrechnung
Das Problem:
Temperaturangaben in Fahrenheit sollen in Celsius umgerechnet werden.
2.1.2
Der Algorithmus:
Dafür nutzt man den Zusammenhang zwischen beiden Temperaturskalen. Beide Skalen haben eine
äquidistante Unterteilung mit folgenden Entsprechungen 1 :
0 Grad Fahrenheit ∼
= −17 79 Grad Celsius,
100 Grad Fahrenheit ∼
37 7 Grad Celsius.
=
9
Hieraus lässt sich die Temperatur C in Celsius als affin-lineare Funktion der Temperatur F in Fahrenheit berechnen, vgl. Abbildung 2.1:
5
C = (F − 32) .
9
Dies resultiert in den folgenden Algorithmus.
1 Der anekdotischen überlieferung zufolge kamen diese auf folgende Weise zustande. Fahrenheit wollte eines Tages eine
“normierte” Temperaturskala entwickeln. Es war gerade Winter und ziemlich kalt (nämlich −17 79 Grad Celsius). Da er sich
keine kältere Temperatur vorstellen konnte, normierte er diese zu 0. Anschließend wollte er die normale Körpertemperatur
des Menschen zu 100 normieren. Da er aber an diesem Tage leichtes Fieber hatte, wurden daraus 37 79 Grad Celsius.
Version vom 21. Oktober 2004
11
12
KAPITEL 2. PROBLEME, ALGORITHMEN, PROGRAMME
- C
−17, 78
0
37, 78 50
100
-
0
32
100
F
212
Abbildung 2.1: Die Celsius- und Fahrenheitskala.
Algorithmus 2.1 (Temperatur Umrechnung)
1. Einlesen von F
2. Umrechnung in C
3. Ausgabe von C
2.1.3
Das Programm
Wir sehen uns jetzt ein zugehöriges Java Programm an. Dieses erzeugt ein Applet, das in Abbildung 2.2 im Applet Launcher auf meinem Macintosh Rechner dargestellt ist.
Abbildung 2.2: Das Temperatur Applet.
Zunächst der Programmtext:
Programm 2.1 Temperatur.java
//Temperatur.java
//
// Transforms Fahrenheit to Celsius
import java.awt.*;
import java.applet.Applet;
import java.awt.event.ActionListener;
import java.awt.event.ActionEvent;
(1)
(1)
(1)
(2)
(3)
(3)
(3)
public class Temperatur extends Applet
(4)
13
2.1. TEMPERATUR UMRECHNUNG
{
(5)
// variables for the problem
double fahrenheit, celsius;
(6)
// objects for the graphical user interface
Label inputPrompt, outputPrompt1, outputPrompt2;
TextField input, output;
(7)
(7)
// setup the graphical user interface components
// and initialize labels and text fields
public void init() {
// define labels and textfields
inputPrompt = new Label("Geben Sie eine Temperatur "
+ "in Fahrenheit an und drücken Sie Return.");
outputPrompt1 = new Label("Die Temperatur in "
+ "Celsius beträgt");
outputPrompt2 = new Label(" Grad.");
(8)
(9)
(9)
(9)
(9)
(9)
input = new TextField(10);
input.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent e) {
calculateTemperature();
}
}); // action will be on input field
(10)
(11)
(11)
(11)
(11)
(11)
output = new TextField( 10 );
output.setEditable( false );
// disable editing in output field
(12)
(13)
(13)
// add labels and textfields to the applet
add(inputPrompt);
add(input);
add(outputPrompt1);
add(output);
add(outputPrompt2);
(14)
(14)
(14)
(14)
(14)
}
// process user’s action on the input text field
public void calculateTemperature() {
// get input number
fahrenheit = Double.parseDouble(input.getText());
// calculate celsius and round it to 1/100 degrees
celsius = 5.0 / 9 * (fahrenheit - 32);
(15)
(16)
(17)
14
KAPITEL 2. PROBLEME, ALGORITHMEN, PROGRAMME
// use Math class for round
celsius = Math.round(celsius * 100);
celsius = celsius / 100.0;
// show result in textfield output
output.setText(Double.toString(celsius));
}
}
(18)
(19)
(20)
(21)
(22)
Wir erläutern jetzt die Bedeutung der einzelnen Anweisungen gemäß der Nummerierung am rechten
Rand.
(1) Alles nach // bis zum Ende einer Zeile ist Kommentar. Die Kommentare enthalten den Namen
des Programms (Temperatur.java) und Informationen darüber, was das Programm tut.
Kommentare sollten auf Englisch sein, man weiß nie, wer das Programm einmal verwenden
muss.
(2) – (3) Das Programm benutzt Teile der von SUN entwickelten Bibliothek für Java. Die benötigten
Teile müssen dem Compiler per import-Anweisung mitgeteilt werden. Durch die Zeile import
java.awt.* in (2) werden alle Bibliotheksdateien geladen, die zum awt, dem abstract window
toolkit gehören. Die Import-Anweisungen (3) laden die Applet-Klasse, und zwei Klassen zur
Verarbeitung von Aktionen (ActionListener und ActionEvent).
Jede Klasse besteht grob gesprochen aus einer Kollektion von gleich gearteten Daten und Methoden, die auf diesen Daten arbeiten. Inzwischen gibt es Tausende von verfügbaren Klassen in
Java, und die Klassenbibliothek wird laufend erweitert. Eine Beschreibung aller jeweils in Java
verfügbaren Klassen findet man unter http://java.sun.com/j2se/1.4.2/docs/index.html.
(4) Deklariert das Hauptprogramm Temperatur. Jedes Hauptprogramm ist in Java selber eine
Klasse, was durch das Schlüsselwort class zum Ausdruck gebracht wird. Temperatur erweitert (Schlüsselwort extends) die vordefinierte Klasse Applet. Dies bedeutet, dass das
lauffähige Programm Temperatur als Applet zur Verfügung steht, also über das WWW geladen und mit Appletviewern oder HTML-Browsern ausgeführt werden kann, vgl. Abbildung 2.2
und Abbildung 2.3.2
Das Schlüsselwort public bringt zum Ausdruck, dass die Klasse Temperatur auch von anderen
Klassen benutzt werden darf. Alle durch import geladenen Klassen sind ebenfalls public,
d. h. die von ihnen bereit gestellten Methoden können von Temperatur benutzt werden.
(5) – (22) Die geschweiften Klammern {. . .} enthalten den Block der Klasse Temperatur. In ihm
wird festgelegt, was die Klasse leisten soll.
(6) Definiert die Variablen celsius, fahrenheit vom Datentyp double. Dieser bezeichnet Gleitkommazahlen doppelter Präzision (daher die Bezeichnung double), also im Rechner darstellbare reelle Zahlen mit großer Genauigkeit.
2 Man kann natürlich auch in Java eigenständige Programme schreiben; hierauf wird in den Übungen näher eingegangen.
2.1. TEMPERATUR UMRECHNUNG
15
celsius, fahrenheit können also reelle Werte annehmen, aber keine anderen wie z. B. Buchstaben oder Strings.
Die Bezeichner celsius und fahrenheit sind mnemonisch gewählt, d. h. aus den Namen lässt
sich leicht auf die Bedeutung schließen. Man sollte stets mnemonische Bezeichner verwenden.
(7) Deklariert die Objekte für das graphische User Interface, drei Label und zwei Textfelder. Label
sind Strings von Zeichen, und Textfelder sind rechteckige einzeilige Felder, die Text enthalten
können.
(8) Leitet die Definition der public Methode init() an. Diese wird automatisch beim Start des
Applets durch den Browser oder Appletviewer aufgerufen. Die Klammern () bedeuten, dass
die Methode keinen Input erwartet. Das Schlüsselwort void gibt an, dass die Methode keinen Outputwert erzeugt. Der Block der Methode wird wieder durch geschweifte Klammern
begrenzt.
(9) Definiert die Anfangseigenschaften der Labels. Für jedes in (7) deklarierte Label wird durch
new Label (...)
ein neues Objekt vom Typ Label erzeugt und mit dem in den Klammern angegebenen String initialisiert. Die Quotes ". . ." dienen als Begrenzer eines Strings, das + konkateniert zwei Strings
zu einem.
(10) Hierdurch wird ein neues Textfeld der Länge 10 (d. h. für 10 sichtbare Zeichen) eingerichtet und
der Variablen input zugewiesen. Dieses Textfeld ist jetzt unter dem Namen input ansprechbar.
(11) Diese (später näher erklärten 3 ) Anweisungen bewirken, dass das Textfeld input Aktionen
auslösen kann. Dies geschieht durch das Drücken der <Return> Taste. Der dem Textfeld hinzugefügte ActionListener startet beim Drücken der <Return> Taste die Methode mit dem
vorgeschriebenen Namen actionPerformed, die wiederum die von uns weiter unten in dem
Zeilen (15) – (20) definierte Methode calculateTemperature() startet.
(12) Richtet analog zu (10) das Textfeld output ein.
(13) Nutzt die Methode setEditable() der Klasse TextField, um output nicht-editierbar zu
machen.
(14) Hier werden durch die Methode add() der Klasse Applet alle Labels und Textfelder dem Applet hinzugefügt. Da keine weiteren Layoutanweisungen erfolgen, werden sie auf der verfügbaren
Appletfläche zeilenweise zentriert angeordnet. Dabei sind mehrere Komponenten pro Zeile
möglich.
(15) Leitet die Definition der public Methode calculateTemperature() ein, die die eigentliche
Rechnung und die Ein- und Ausgabe erledigt.
3
Hier wird eine anonyme Klasse definiert, die das Interface ActionListener präzisiert, vgl. Kapitel 7.3.8.
16
KAPITEL 2. PROBLEME, ALGORITHMEN, PROGRAMME
(16) Zunächst wird die in input angegebene Zahl der Variablen fahrenheit zugewiesen. Dies
erfolgt durch zwei ineinander geschachtelte Methoden. Die in input eingegebene Zahl ist
zunächst nur ein String. Dieser wird durch
input.getText()
eingelesen, anschließend durch die Methode parseDouble() der Klasse Double, also durch
Double.parseDouble() zu einer double Variablen konvertiert. Dieser Wert wird der double
Variablen fahrenheit zugewiesen.
Man könnte die Zeile (16) äquivalent auch in die folgenden Anweisungen zerlegen:
String str = input.getText(); // erzeugt String str
double temp = Double.parseDouble(str);
// erzeugt double temp aus str
fahrenheit = temp;
// weist den Wert von temp
// der Variablen fahrenheit zu
(17) Ist eine Zuweisung. Der Wert des Ausdruck (5.0 / 9) * (fahrenheit - 32) wird berechnet und der Variablen celsius zugewiesen. Da / sowohl die ganzzahlige Division mit Rest, als
auch die reellwertige Division bezeichnet, muss man (z. B. durch Angabe von 5.0 statt 5) dem
Compiler klar machen, dass hier die reellwertige Division gemeint ist. Beachte: 5 / 9 ergibt
den Wert 0, aber 5.0 / 9 den Wert 0.5555555555555556.
Hier findet die eigentliche Umrechnung statt. Alle anderen Anweisungen im Programm beziehen sich auf das User-Interface und die Ein- und Ausgabe.
(18) – (19) Um viele Nachkommastellen bei der Ausgabe zu vermeiden, wird das Ergebnis auf 2
Nachkommastellen gerundet. Dies geschieht durch Verwendung der Methode round() der
Klasse Math, die viele mathematische Standardfunktionen bereitstellt. round() rundet eine
double Zahl auf die nächstliegende ganze Zahl.
(20) Zeigt das Resultat im Ausgabefeld output. Die Methode toString() der Klasse Double
wandelt den double Wert celsius in einen String um, der dann mit der Methode setText()
der Klasse TextField für das Textfeld output benutzt wird.
Wir haben im Programm Temperatur.java einige wichtige Begriffe kennen gelernt: Variable, Zuweisung, Klasse, Methode.
Eine Variable ist (in erster Näherung) ein Name (Platzhalter) für Objekte (Daten) eines Typs, z. B. des
Typs “reelle Zahl”.
Sie belegt im Speicher einen bestimmten (dem Benutzer unbekannten) Speicherplatz, dessen Größe
vom vereinbarten Typ des Objekts abhängt. Dieser Speicherplatz ist unter dem Namen der Variablen
ansprechbar.
Eine Zuweisung aktualisiert den Wert (Inhalt) des Speicherplatzes.
17
2.1. TEMPERATUR UMRECHNUNG
Beispiel 2.1 Die Deklaration
double fahrenheit, celsius;
erzeugt im Speicher die (automatisch mit 0 initialisierten) Objekte
celsius
fahrenheit
0
0
.
Die Eingabe von 95 bewirkt folgendes. Durch
fahrenheit = Double.parseDouble(input.getText());
wird der Variablen fahrenheit der eingelesene Wert zugewiesen.
celsius
fahrenheit
0
95.0
Die Zuweisung
celsius = (5.0 / 9) * (fahrenheit - 32)
bewirkt die Auswertung des Ausdrucks (5.0 / 9) * (fahrenheit - 32) zu
(5.0/9) ∗ (95 − 32) =
5
· 63 = 35
9
und die Zuweisung an die Variable celsius.
celsius
fahrenheit
35.0
95.0
Man beachte: Ein Ausdruck hat einen Wert. Eine Zuweisung hat einen Effekt.
Eine Klasse besteht aus Variablen (auch Komponenten oder Datenfelder genannt) und Methoden (auch
Funktionen genannt). In Klassen sollen gleichartige Daten zusammengefasst werden und alle Methoden, sie zu verarbeiten, bereitgestellt werden. Die Komponenten und Methoden können durch
das Schlüsselwort public auch anderen Klassen zur Verfügung gestellt werden, bzw. durch das
Schlüsselwort private außerhalb der Klasse verboten werden. Der Zugriff auf eine public Methode
oder Komponente einer anderen Klasse erfolgt durch den “.”, wie in
Double.parseDouble() oder Math.round()
Klassen sind in Java auch Datentypen, man kann also Variable dieses Typs definieren, wie z. B.
TextField input;
Eine solche Variable wird auch als Instanz der Klasse oder Objekt bezeichnet. Instanzen (Objekte)
können die Methoden und Komponenten der Klasse benutzen. Der Zugriff geschieht wieder mit dem
“.” wie in
18
KAPITEL 2. PROBLEME, ALGORITHMEN, PROGRAMME
input.getText()
Methoden einer Klasse sind in anderen Programmiersprachen als Funktionen bekannt. Sie können ein
oder mehrere Argumente eines bestimmten Typs übergeben bekommen wie in
Math.round(celsius * 100)
und liefern Werte eines Rückgabetyps, bei Math.round vom Typ long, der für (lange) ganze Zahlen
steht. Argumenttypen und Rückgabetyp müssen in der Deklaration vereinbart werden, für Math.round
ist diese in der Klasse Math gegeben durch
long round(double a)
Dabei steht a für den zu übergebenden Wert vom Typ double.
Diese Bemerkungen sind nur eine sehr oberflächliche Einführung in Methoden und Klassen. Sie werden wesentlich detaillierter in Kapitel 7 behandelt.
Das Programm Temperatur.java wird4 durch den Befehl
javac Temperatur.java
kompiliert. Dies erzeugt die Datei Temperatur.class, die das Java Programm als Byte-Code enthält,
sowie eine Hilfsklasse Temperatur$1.class 5 .
In dieser Form kann es über das Internet durch Browser aus html-Seiten geladen und ausgeführt
werden, siehe Abbildung 2.3.
Abbildung 2.3: Darstellung des Applet Temperatur“ im Internet Explorer.
”
Dafür benötigt man eine html-Datei, die das Laden auslöst. Sie kann wie folgt aussehen.
Datei startApplet.html
<title>Temperatur</title>
4 in
5
meiner Unix shell
Diese zweite .class Datei rührt von der anonymen Klasse her, die den ActionListener präzisiert.
19
2.2. EINKOMMENSTEUERBERECHNUNG
<hr>
<applet codebase=. code="Temperatur.class" width=550 height=70>
</applet>
<hr>
<a href="Temperatur.java">The source.</a>
codebase gibt das directory an, in dem der code Temperatur.class zu finden ist. codebase=.
bedeutet also, dass sich dieser in der gleichen directory wie Temperatur.html befindet. Durch
width=550 und height=70 werden die Weite und Höhe des Applets (gemessen in Pixel) festgelegt.
Diese Werte können im Applet selbst6 bzw. im Appletviewer durch Veränderung der Fenstergröße, in
dem das Applet angezeigt wird, verändert werden. Abbildung 2.4 zeigt eine verkleinerte Darstellung
durch Änderung der Fenstergröße im Appletviewer. Sie zeigt deutlich, das die Objekte zeilenweise
zentriert auf das Applet gelegt werden.
Abbildung 2.4: Darstellung des Applet Temperatur“ bei Veränderung der Fenstergröße im Applet”
viewer.
Durch Laden der html-Datei startApplet.html in einen html-Browser (z. B. Internet Explorer)
oder einen Appletviewer steht dann das Applet ausführbar zur Verfügung.
2.2
2.2.1
Einkommensteuerberechnung
Das Problem
In einem (hypothetischen) Steuermodell sind Einkommensteuern S in Abhängigkeit des zu versteuernden Einkommens E zu zahlen. Das Steuermodell verwendet den sogenannten Stufen- oder Scheibentarif, in dem das Einkommen E in Scheiben unterteilt wird, die mit unterschiedlichen Sätzen besteuert
werden. Hier sind es 3 Scheiben:
E ≤ 20.000 e
20.000 < E ≤ 60.000
E > 60.000 e
6 durch
0 e Steuer
20% von E − 20.000
8.000 + 40% von E − 60.000
die Anweisung setSize( x, y ) in der Methode init()
20
2.2.2
KAPITEL 2. PROBLEME, ALGORITHMEN, PROGRAMME
Der Algorithmus
Aus dem Scheibentarif ergibt sich die Einkommensteuer S als stückweise lineare, monoton steigende
Funktion von E, vgl. Abbildung 2.5.
6 Steuer S in Te
24
!
!!
!
!
!!
!
!!
!
!!
8
Einkommen E in Te
-
0
20
40
60
80
Abbildung 2.5: Die Einkommensteuer Kurve.
Dies resultiert in den folgenden Algorithmus.
Algorithmus 2.2 (Steuer Berechnung)
1. Einlesen von E
2. Berechnung von S
3. Ausgabe von S
2.2.3
Das Programm
Ein entsprechendes Java Programm ist:
Programm 2.2 Steuer.java
// Steuer.java
//
// calculates taxes depending on the income in a
// piecewise linear fashion
import java.awt.*;
import java.applet.Applet;
import java.awt.event.ActionListener;
import java.awt.event.ActionEvent;
public class Steuer extends Applet {
// constants for tax calculation
final double noTaxBound = 20000,
lowRate = .2,
100
21
2.2. EINKOMMENSTEUERBERECHNUNG
lowTaxBound = 60000,
highRate = .4;
double income, tax;
// setup the graphical user interface components
// and initialize labels and text fields
Label inputPrompt, outputString;
TextField input;
Panel p1, p2; // use subdivision of applet into 2 panels
// for better layout
public void init() {
//set layout for applet so that eveything is left alligned
setLayout(new FlowLayout(FlowLayout.LEFT));
p1 = new Panel();
p2 = new Panel();
// define layout and color for panel p1
p1.setLayout(new FlowLayout(FlowLayout.LEFT));
p1.setBackground(Color.pink);
inputPrompt = new Label("Geben Sie das zu versteuernde "
+ "Einkommen an und drücken Sie Return. EURO");
input = new TextField(10);
input.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent e) {
calculateTax();
}
});
// action will be on input field
p1.add(inputPrompt);
p1.add(input);
// put prompt on panel
// put input on panel
// same for panel p2
p2.setLayout(new FlowLayout(FlowLayout.LEFT));
p2.setBackground(Color.yellow);
// this time we write the output into a label
outputString = new Label("Es sind " + tax
+ " EURO Steuern zu zahlen. Ihr Finanzamt.
p2.add(outputString);
// add panels to the applet
");
22
KAPITEL 2. PROBLEME, ALGORITHMEN, PROGRAMME
add(p1);
add(p2);
}
// process user’s action on the input text field
public void calculateTax() {
income = Double.parseDouble(input.getText());
if (income <= noTaxBound) {
// no tax
tax = 0;
} else if (income <= lowTaxBound) { // low rate applies
tax = lowRate * (income - noTaxBound);
} else {
tax = lowRate * (lowTaxBound - noTaxBound)
// low rate applies
+ highRate * (income - lowTaxBound);
// high rate applies
}
// round to Pfennig
tax = Math.round(tax * 100) / 100.0;
outputString.setText("Es sind " + tax
+ " EURO Steuern zu zahlen. Ihr Finanzamt.");
outputString.invalidate();
// marks outputString for updating when it changes
validate();
// tells applet to check for invalidations
// and update layout if necessary
}
}
Das entspechende Applet ist in Abbildung 2.6 dargestellt.
Hier sehen wir zusätzlich:
• Die Definition von Konstanten.
• Die if-Anweisung.
• Panels
• Layout-Anweisungen
2.2. EINKOMMENSTEUERBERECHNUNG
23
Abbildung 2.6: Das Steuer Applet.
Konstanten sollte man immer über Bezeichner ansprechen, da man so ihre Werte bei einer möglichen
Änderung nur an einer Stelle ändern muss. 7 Konstanten werden in Java über das Schlüsselwort final
gekennzeichnet.8 Sie müssen bei der Definition initialisiert werden.
Als Bezeichner (Identifikator) für Klassen, Methoden, Variablen, Kanstanten usw. kommen in Java
bestimmte Strings in Frage. Allgemein bestehen Strings aus 16-bit Unicode Zeichen. Diese Zeichen
sind bei den ersten 128 Zeichen mit dem ASCII-Zeichensatz und mit den ersten 256 Zeichen mit dem
ISO8859-1 (Latin 1) Zeichensatz verträglich. Bei 16 Bit sind insgesamt ca. 34000 Zeichen darstellbar,
so dass auch Zeichensätze exotischer Sprachen (äthiopisch, Bengali, Tamil . . .) darstellbar sind.
Als Bezeichner kommen solche Strings in Frage, die mit einem Unicode-Buchstaben beginnen und
nur aus Unicode-Buchstaben und Unicode-Ziffern bestehen. Zulässig sind also
input_1, Möhring,
nicht jedoch
3-Felder, no Tax Bound.
Die if-Anweisung (if-statement) hat in Java die Form
if (Bedingung) Anweisung1 else Anweisung2
wobei der else-Teil fehlen kann. Dabei ist Bedingung ein logischer Ausdruck. Logische Ausdrücke
sind in Java vom Typ boolean und können die Werte true und false für die beiden Wahrheitswerte
wahr und falsch annehmen.
Ist im if-statement der Wert von Bedingung gleich true, so wird Anweisung1 ausgeführt, ansonsten
(Bedingung ist “false”) Anweisung2 (bzw. nichts, falls der else-Teil fehlt).
Anweisung1 bzw. Anweisung2 können wieder if-statements sein, wodurch eine Verschachtelung wie
im obigen Programm auftritt.
Die Klasse Panel (aus java.awt) stellt eine Möglichkeit dar, die Oberfläche eines Applets zu untergliedern. In unserem Fall werden zwei Panels p1 und p2 definiert. Für beide Panels wird mit
setLayout() ein Layout festgelegt, nämlich ein linksbündiges Flowlayout. Ohne Layout Spezifi7 Ein Negativbeispiel war die Umstellung der Postleitzahlen; die Anzahl der Zeichen hierfür war meist nie als Konstante
deklariert worden, was einen immensen Umstellungsaufwand erforderte und ganze Programmsysteme lahmlegte.
8 Genau genommen sind Konstanten in Java Variable, deren Wert nicht mehr geändert werden darf.
24
KAPITEL 2. PROBLEME, ALGORITHMEN, PROGRAMME
zierung wird das zentrierte Flowlayout genommen (wie in Temperatur.java). Darüber hinaus
werden mit der Methode setBackground() Farben für diese Panels definiert. Die Argumente dieser
Methode sind Konstanten der Klasse Color, werden also (Zugriff auf Komponenten!) mit Color.pink
angesprochen.
Auch das gesamte Applet verwendet ein linksbündiges Flowlayout. Die Layoutanweisungen sind
nicht absolut, sondern ändern sich mit der Größe des Applets. Das linksbündige Flowlayout schreibt
nur vor, dass die Komponenten in der Reihenfolge der add() Befehle Zeile für Zeile eingefügt werden.
Die Ausgabe erfolgt diesmal über ein Label outputString, das in init() eingerichtet und initialisiert wird, und in calculateTax() mit der Methode setText() aktualisiert wird. Um die
Länge dem veränderten Text anzupassen, wird das Objekt outputString zunächst durch die Anweisung outputString.invalidate() als “verändert” markiert, und mit dem Aufruf der Methode
validate() für das Applet wird dieses angewiesen, outputString zu aktualisieren.
2.3
Primzahl
2.3.1
Das Problem
Zu einer gegebenen natürlichen Zahl n ∈ N, n > 0 soll die kleinste Primzahl p bestimmt werden, die
größer als n ist.9
2.3.2
Der Algorithmus
Der Algorithmus zur Lösung dieses Problems basiert auf der folgenden Idee.
Prüfe die Zahlen n + 1, n + 2, n + 3, . . ., ob sie durch eine kleinere natürliche Zahl k > 1 ohne Rest
teilbar ist. Die erste Zahl z, die das nicht ist, ist die gesuchte Primzahl.
Algorithmus 2.3 (Primzahl)
Lese die Zahl n ein
Setze z := n
Wiederhole
Erhöhe z um 1
überprüfe ob z Primzahl ist
bis z Primzahl ist
Gebe z aus
Der Test, ob z eine Primzahl ist, geschieht nach folgender Idee:
9
N bezeichnet die Menge der natürlichen Zahlen 0, 1, 2, . . .
25
2.3. PRIMZAHL
Teile z durch alle Zahlen k = 2, 3, . . . (bis k · k > z) und prüfe ob der Rest 0 ist. Ist dies für ein k der
Fall, so ist z keine Primzahl, andernfalls (d. h. keines der k teilt z ohne Rest) ist z eine Primzahl.
Diese Idee führt zu dem folgenden Algorithmus. Hierin ist teilerGefunden eine sogenannte Boolesche
Variable, also eine Variable, die nur die Werte wahr oder falsch annimmt. Die geschweiften Klammern
{. . .} enthalten Kommentare.
Algorithmus 2.4 (Primzahltest)
Setze k := 2
Setze teilerGefunden := falsch {noch kein Teiler gefunden}
Solange (teilerGefunden = falsch) und (k · k ≤ z) führe aus
Teile z ganzzahlig durch k. Sei rest der entstehende Rest
Falls rest = 0 so setze teilerGefunden := wahr
Setze k := k + 1
{z ist Primzahl falls teilergefunden = falsch beim Austritt aus der Schleife}
Algorithmus 2.3 und 2.4 werden jetzt in ein Java Programm umgesetzt.
Programm 2.3 Primzahl.java
// Primzahl.java
//
// input: natural number n
// output: smallest prime number p with p > n
//
// method: apply prime test to n+1, n+2, ... until prime is found,
//
prime testing of z is done by testing numbers k = 2 ...
//
k * k <= z if they are factors of z
//
import
import
import
import
with
java.awt.*;
java.applet.Applet;
java.awt.event.ActionListener;
java.awt.event.ActionEvent;
public class Primzahl extends Applet {
Label inputPrompt;
// declare Label
TextField input, output; // declare textfields for input and output
int n,
z,
k;
boolean
// the input integer read from terminal
// candidate for the prime, set to n+1, n+2 etc.
// possible divisor of z
divisorFound;
// Boolean, indicates that a divisor
// k of z has been found
26
KAPITEL 2. PROBLEME, ALGORITHMEN, PROGRAMME
// setup the graphical user interface components
// and initialize labels and text fields
public void init() {
// set layout
setLayout(new FlowLayout(FlowLayout.LEFT));
inputPrompt = new Label("Geben Sie die Zahl n ein "
+ "und drücken Sie Return.");
input = new TextField(10);
input.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent e) {
// call method for finding next prime
findPrime();
}
});
// action will be on input field
// output will be text in a field
output = new TextField(60);
output.setEditable(false);
output.setBackground(Color.yellow);
add(inputPrompt);
add(input);
add(output);
// put prompt on applet
// put input on applet
// put output on applet
}
// function for finding next prime number
public void findPrime() {
// get input number
n = Integer.parseInt(input.getText());
z = n;
do {
z++;
k = 2;
divisorFound = false;
// so far no divisor of z has been found
while (!divisorFound && k*k <= z) {
// check if k is a divisor of z
if (z % k == 0) {
divisorFound = true;
}
k++;
} //end while
27
2.3. PRIMZAHL
} while (divisorFound);
// define the output string
String str = "Die nächstgrößere Primzahl nach "
+ n + " ist " + z + ’.’;
// put the sting into the output field
output.setText( str );
}
}
Das entsprechende Applet ist in Abbildung 2.7 dargestellt.
Abbildung 2.7: Das Primzahl Applet.
Neu sind hier folgende Konstrukte für Schleifen:
while (Fortsetzungsbedingung) Anweisung
bzw.
do Anweisung while (Fortsetzungsbedingung)
Im ersten Fall wird Anweisung so oft ausgeführt, wie Fortsetzungsbedingung gilt. Diese Bedingung
wird vor jedem Eintritt in die Schleife überprüft. Evtl. wird die Schleife also auch kein mal durchlaufen.
Im zweiten Fall erfolgt die Überprüfung der Fortsetzungsbedingung nach jeder Abarbeitung von Anweisung. Anweisung wird also mindestens einmal ausgeführt.
Neu sind ferner folgende Operatoren:
!
logische Negation
&&
logisches und
%
Rest bei ganzzahliger Division, 8 % 3 ergibt 2
Bei der Ausgabe wird zunächst der String str-durch Konkatenation von Zeichenketten und Werten
von Variablen erzeugt. Dieser wird dann als String im Textfeld output ausgegeben.
Betrachten wir das Programm für das Zahlenbeispiel n = 50. Tabelle 2.1 dokumentiert die Veränderung
der Werte der Variablen n, z, k, divisorFound während des Programmablaufs (von oben nach unten).
28
KAPITEL 2. PROBLEME, ALGORITHMEN, PROGRAMME
Tabelle 2.1: Werte der Variablen in Programm 2.3
n
0
50
50
50
50
50
50
z
0
0
50
51
51
51
51
k
0
0
0
0
2
2
2
divisorFound
false
false
false
false
false
false
false
50
51
3
false
50
50
51
51
3
4
true
true
50
50
50
50
52
52
52
52
4
2
2
2
true
true
false
false
50
50
52
52
2
3
true
true
50
50
50
50
53
53
53
53
3
2
2
2
true
true
false
false
50
50
53
53
3
3
false
false
50
50
53
53
4
4
false
false
50
50
53
53
5
5
false
false
Aktion im Programm
Default Initialisierung bei Definition
nach Einlesen in n
nach z = n
nach z++
nach k = 2
nach divisorFound = false
Eintritt in while-Schleife
z % k = 51 % 2 6= 0
nach k++
neuer Eintritt in while-Schleife
z % k = 51 % 3 = 0
“then” statement ausgeführt
nach k++
kein neuer Eintritt in while-Schleife
neuer Eintritt in do-Schleife
nach z++
nach k = 2
nach divisorFound = false
Eintritt in while-Schleife
z % k = 52 % 2 = 0
“then” statement ausgeführt
nach k++
kein neuer Eintritt in while-Schleife
neuer Eintritt in do-Schleife
nach z++
nach k = 2
nach divisorFound = false
Eintritt in while-Schleife
z % k = 53 % 2 6= 0
nach k++
neuer Eintritt in while-Schleife
z % k = 53 % 3 6= 0
nach k++
neuer Eintritt in while-Schleife
z % k = 53 % 4 6= 0
nach k++
neuer Eintritt in while-Schleife
z % k = 53 % 5 6= 0
29
2.3. PRIMZAHL
n
50
50
z
53
53
k
6
6
divisorFound
false
false
50
50
53
53
7
7
false
false
50
50
53
53
8
8
false
false
Aktion im Programm
nach k++
neuer Eintritt in while-Schleife
z % k = 53 % 6 6= 0
nach k++
neuer Eintritt in while-Schleife
z % k = 53 % 7 6= 0
nach k++
k*k = 8 ∗ 8 > 53
⇒ kein neuer Eintritt in while-Schleife
divisorFound hat Wert false
⇒ kein neuer Eintritt in do-Schleife
⇒ z = 53 ausgegeben
Die Korrektheit des Programms folgt aus den Vorüberlegungen10 :
- Die while-Schleife testet alle in Frage kommenden natürlichen Zahlen k ≤
von z sind.
√
z ob sie Teiler
- Die do-Schleife terminiert mit einem z, für das kein Teiler gefunden wurde.
- Da es zu jeder Zahl n eine Primzahl p mit p > n gibt, terminiert auch die do-Schleife nach
endlich vielen Schritten (keine Endlosschleife).
Der Algorithmus kann auf verschiedene Weisen verbessert und schneller gemacht werden.
1. Die if-Anweisung
if (z % k == 0) {divisorFound = true;}
kann ersetzt werden durch die (allerdings schwer lesbare) Zuweisung
divisorFound = (z % k == 0);
Dabei ist z % k == 0 ein Boolescher Ausdruck, dessen Wert der Variablen divisorFound
zugewiesen wird.
2. Es kommen nur ungerade Zahlen als Primzahlen in Frage. Daher kann man als ersten Wert von
z die erste ungerade Zahl > n nehmen und dann stets z um 2 erhöhen.
3. Da z ungerade gewählt wird, kommen nur ungerade Zahlen k als Teiler von z in Frage.
Der Einbau dieser Änderungen (Anfang und Ende wie in Programm 2.3):
10
Dabei wird vorausgesetzt, dass nur natürliche Zahlen eingegeben werden. Für andere typzulässige Eingaben wie zum
Beispiel negative ganze Zahlen wird nichts ausgesagt. Solche Eingaben könnte man natürlich durch entsprechende Fallunterscheidungen “abfangen”.
30
KAPITEL 2. PROBLEME, ALGORITHMEN, PROGRAMME
. . .
if (n % 2 == 0) z = n - 1; else z = n;
// this makes z + 2 smallest odd number > n
do {
z += 2;
k = 3;
divisorFound = false;
while (!divisorFound && k*k <= z) {
divisorFound = (z % k == 0);
k += 2;
}\\endwhile
} while (divisorFound);
. . .
Dabei ist k += 2 äquivalent zu k = k + 2.
4. Eine andere Verbesserung besteht darin, in der while-Schleife eine unnötige Erhöhung der
Variablen k zu vermeiden. Dies kann erreicht werden durch:
if (z % k == 0) {
divisorFound = true;
} else {
k += 2;
}
2.4
Literaturhinweise
Die Beispiele sind [OSW83] entnommen. Dort finden sich insgesamt 100 Beispielaufgaben für einfache Programme und zugehörige Lösungsalgorithmen (allerdings in Pascal).
Weitere Beispiele für einfache Java Programme findet man in [DD01, CW98, Krü02].
Herunterladen