2.1 Schrittweise Verfeinerung

Werbung
2 Programmiermethodik
2.1
1
Schlagwörter:
 strukturiertes Programmieren
 systematisches Programmieren
 Techniken und Tricks
 Programmierstil
 Benutzung von Bibliotheken
etc. etc.
2.1
2
2.1 Schrittweise Verfeinerung
(stepwise refinement, top-down programming [N. Wirth 1971])
= allgemeines Algorithmus-Muster zum Lösen eines Problems:
löse Problem:
falls Problem einfach, löse es direkt;
sonst zerlege es in Teilprobleme P1,...Pn ,
löse die Pi für i=1,...,n
und füge die Teillösungen zusammen.
2.1
3
2.1 Schrittweise Verfeinerung
(stepwise refinement, top-down programming [N. Wirth 1971])
= allgemeines Algorithmus-Muster zum Lösen eines Problems:
löse Problem:
falls Problem einfach, löse es direkt;
sonst zerlege es in Teilprobleme P1,...Pn ,
löse die Pi für i=1,...,n
und füge die Teillösungen zusammen.
Beispiele:
P
A
2.1
D
P
B
E
C
F
A
P
(Rekursion)
4
2.1.1 Verfeinerung von Anweisungen
durch Einsatz zusammengesetzter Anweisungen wie im Beispiel:
 Programm:
. . . . . . . ordne a1 und a2 . . . . . . .
 ordne a1 und a2:
if(a1>a2) vertausche a1 und a2
1. Verfeinerungsschritt:
Alternative
 vertausche a1 und a2:
{int x = a1; a1 = a2; a2 = x; }
2. Verfeinerungsschritt:
Block
 Programm:
. . . . . . . if(a1>a2) {int x = a1; a1 = a2; a2 = x; }. . . . . . .
2.1
5
3 Alternativen für die Formulierung des endgültigen Programmcodes:
 Verfeinerungen an Ort und Stelle einfügen (s.o.),
 desgl., dabei die ursprünglichen Formulierungen als Marken belassen,
 für jede Verfeinerung entsprechende Prozedur verwenden - Vorteile:
- eventuell mehrfach verwendbar,
- parametrisierbar,
- Entwicklungsreihenfolge = textuelle Reihenfolge,
- keine Kollisionsgefahr beim Einführen lokaler Variablen.
(Nachteil: zu schwergewichtig bei „kleinen“ Verfeinerungen)
2.1
6
Die Java-Syntax ist dafür eher hinderlich:
 Einsatz der Zeichen {} und ;
 () ist obligatorisch auch für parameterlose Methoden
 Kollisionsgefahr bei lokalen Variablen in Block (statt Methode)
 keine Variablenparameter
u.a.
2.1
7
2.1.2 Verfeinerung von Ausdrücken
(gilt für imperative genauso wie für applikative Programmierung)
 x in Feld a suchen:
int i = 0;
while(!fertig) i++;
// a[i]==x genau dann, wenn x in a vorkommt
 fertig:
(a[i]==x || i==a.length-1)// Klammerung beachten!
Mitunter Funktionsprozedur erforderlich:
if(Position von x in a != 0) .......
 int position(x,a){... (Iteration oder Rekursion) ...}
2.1
8
2.1.3 Beispiel: Suchen durch Einschachteln
(binary search)
Vor.:
 In einem Feld int a[] seien die Zahlen zwischen den
Indizes unten/oben aufsteigend sortiert (unten<oben).
 Unter diesen Zahlen befinde sich die Zahl x, d.h. es gebe
einen Index position (evtl. mehrere) mit
unten<position & a[position]==x & position<oben
Gesucht:
position
Beispiel:
1
unten
2.1
3
4
4
6
7
position
9
x==6
oben
9
Lösungsidee: wiederholtes Halbieren des Intervalls

Position suchen:
for(;;){
setze zeiger auf Intervallmitte;
if(a[zeiger]==x } return zeiger;
else betrachte linkes oder rechtes Teilintervall }

setze zeiger auf Intervallmitte:
int zeiger = (unten+oben)/2;

betrachte linkes oder rechtes Teilintervall:
if
(a[zeiger]< x) unten = zeiger;
else /* a[zeiger]> x */ oben = zeiger; }
2.1
10
... und als Funktionsprozedur verpackt:
static int position(int x, int[]a, int unten, int oben){
for(;;) {
int zeiger = (unten+oben)/2;
if
(a[zeiger]==x) return zeiger;
else if(a[zeiger]< x) unten = zeiger;
else /* a[zeiger]> x */ oben = zeiger;
if(oben-unten == 1) return 0;
}
}
... und erweitert für den Fall des Nichtvorkommens von x
2.1
11
Beachte den erheblich geringeren Aufwand als bei linearer Suche:
Beh.:
Suchen in n=2k-1 Zahlen erfordert höchstens k Schritte
(= Schleifendurchläufe)
Bew.:
(vollständige Induktion:)
I. k=1: n = 21-1 = 1 erfordert 1 Schritt 
II. Die Behauptung gelte für ein bestimmtes k und n = 2k-1:
n=2k-1 Stück
höchstens k Schritte
n=2k-1 Stück
höchstens k Schritte
höchstens k+1 Schritte
2.1
2k+1-1 Stück

12
Also:
mit n=2k-1 ist k = log2(n+1), d.h.
Einschachteln erfordert maximal
log2(n+1) Schritte
im Gegensatz zu:
lineares Suchen erfordert maximal
n Schritte
Beachte: Obwohl die Schritte beim Einschachteln aufwendiger sind,
amortisiert sich dieser Aufwand schnell bei wachsendem n:
n:
log2 n:
2.1
32
1024
32.768 1.048.576
5
10
15
20
13
2.1.4 Beispiel: Sortieren durch Einfügen
(insertion sort)
Aufgabe: sortiere a[1] bis a[n] „in situ“
2.1

sortiere:
for(int i=2; i<=n; i++)
ordne a[i] vorn ein

ordne a[i] vorn ein:
a[0] = a[i]; // sentinel
schaffe Lücke an der richtigen Stelle k;
a[k] = a[0];

schaffe Lücke an der richtigen Stelle k;
int k = i;
while(a[0]<a[k-1]) a[k] = a[--k];
14
... und als Prozedur verpackt, z.B. für Gleitkommazahlen:
static void insertionSort(float[]a, int n) {
for(int i=2; i<=n; i++) {
a[0] = a[i]; // sentinel
int k = i;
while(a[0]<a[k-1]) a[k] = a[--k];
a[k] = a[0]; }
}
2.1
15
2.1.5 Beispiel: Sieb des Eratosthenes
zur Ermittlung aller Primzahlen unterhalb von n
Idee (Eratosthenes, griech. Mathematiker, 3. Jhdt. v. Chr.):
sieve(p:list) = p : sieve [x | x<-list, x `mod` p /= 0 ]
2.1
und damit
sieve[2..n]
oder auch
sieve[2..n/2]
(oder auch
sieve[2..] ! )
16
Imperativ:
Feld mit Zahlen 2,3,...,n-1 füllen und wiederholt
Primzahl-Vielfache „durchs Sieb fallen lassen“
2.1

Eratosthenes:
fülle Sieb;
for(int i=1; weitersuchen; ) {
bestimme nächste Primzahl;
entferne deren Vielfache
}
stelle Primzahlen zusammen.

fülle Sieb:
boolean[] durchgefallen = new boolean[n];
// alle false, d.h. noch im Sieb
durchgefallen[0] = durchgefallen[1] = true;
17
 bestimme nächste Primzahl:
while(durchgefallen[++i]) ;
// i ist nächste Primzahl
 entferne deren Vielfache:
for(int k=i*i; k<n; k+=i)
durchgefallen[k] = true;

stelle Primzahlen zusammen: . . . . .
 weitersuchen:
i<n-1
2.1
besser:
i<n/2
noch besser:
i<√n , d.h. i*i<n !
18
... und als Prozedur verpackt:
static int[] eratosthenes(int n) {
boolean[] durchgefallen = new boolean[n];
durchgefallen[0] = durchgefallen[1] = true;
for(int i=1; i*i<n; ) {
while(durchgefallen[++i]) ;
for(int k=i*i; k<n; k+=i)
durchgefallen[k] = true; }
int[] result = new int[n];
int k = 0;
for(int i=2; i<n; i++)
if(!durchgefallen[i]) result[k++] = i;
return result;
}
2.1
19
2.1.6 Verfeinerung mit Revision
! Nicht immer ist die Verfeinerung ohne nachträgliche Eingriffe möglich !
Beispiel:
Wort b in Text a durch anderes Wort c ersetzen
(vgl. Aufgabe 7.2)
genauer:
b kommt tatsächlich in a vor, und c ist nicht länger als b;
nur das erste Auftreten von b soll berücksichtigt werden;
a ist Inhalt eines Textfelds, das gegebenenfalls hinten
mit Leerzeichen aufgefüllt werden soll.
Sein•oder•nicht•einein,•das•ist•hier•die•Frage.••
Sein•oder•nicht•sein,•das•ist•hier•die•Frage.••••
2.1
20

Ersetzen:
int i;
for(i=0; !gefunden; i++);
ersetze b durch c;
schließe Lücke;
fülle mit Leerzeichen auf.

gefunden:
for(int k=0; k<b.length; k++)
if(a[i+k]!=b[k]) return false;
return true;
‘ for(i=0; !gefunden; i++); :
seek: for(i=0; ; i++) {
for(int k=0; k<b.length; k++)
if(a[i+k]!=b[k]) continue seek;
break; }
2.1
21
 ersetze b durch c:
for(int k=0; k<c.length; k++)
a[i+k] = c[k];
 schließe Lücke:
for(int k=0; k<a.length-i-b.length; k++)
a[i+c.length+k] = a[i+b.length+k];
 fülle mit Leerzeichen auf:
for(int k=0; k<b.length-c.length; k++)
a[a.length-b.length+c.length+k] = ' ';
2.1
22
... und zusammengefasst:
static void replace(char[]a, char[]b, char[]c) {
int i;
seek: for(i=0; ; i++) {
for(int k=0; k<b.length; k++)
if(a[i+k]!=b[k]) continue seek;
break; }
for(int k=0; k<c.length; k++)
a[i+k] = c[k];
for(int k=0; k<a.length-i-b.length; k++)
a[i+c.length+k] = a[i+b.length+k];
for(int k=0; k<b.length-c.length; k++)
a[a.length-b.length+c.length+k] = ' ';
}
2.1
23
Herunterladen