• Keine Ergebnisse gefunden

Beuth Hochschule WS17/18, S. 1 Projekte MB2-ALG Für die Lehrveranstaltung Algorithmen und Datenstrukturen, Zug 1, Beuth Hochschule, WS17/18, von Ulrich Grude. Inhaltsverzeichnis

N/A
N/A
Protected

Academic year: 2021

Aktie "Beuth Hochschule WS17/18, S. 1 Projekte MB2-ALG Für die Lehrveranstaltung Algorithmen und Datenstrukturen, Zug 1, Beuth Hochschule, WS17/18, von Ulrich Grude. Inhaltsverzeichnis"

Copied!
59
0
0

Wird geladen.... (Jetzt Volltext ansehen)

Volltext

(1)

Beuth Hochschule WS17/18, S. 1 Projekte MB2-ALG

Für die Lehrveranstaltung Algorithmen und Datenstrukturen, Zug 1, Beuth Hochschule, WS17/18, von Ulrich Grude.

Inhaltsverzeichnis

Projekt 1: Sammeln in einer unsortierten Reihung...2

1. Grundbegriffe...2

2. Ziel...3

3. Vorgabe: Die Klasse LongSpeicher10...3

4. Fragen...5

5. Die Klasse LongSpeicher10 vervollständigen ...6

6. Der Endtest...8

7. Aufgaben...8

Projekt 2: Sammeln in einer sortierten Reihung...10

1. Ziel...10

2. Vorgaben: Zwei Skelette der Methode index...10

3. Aufgaben...11

Projekt 3: Sammeln in einer unsortierten verketteten Liste...13

1. Ziel...13

2. Vorgabe: Die Klasse LongSpeicher30...13

3. Aufgaben...16

Projekt 4: Sammeln in einer sortierten verketteten Liste...17

1. Ziel...17

2. Vorgabe: Die Methode vorgaenger...17

3. Aufgaben...17

4. Terminologie...17

Projekt 5: Sammeln in einem binären Baum...18

1. Grundbegriffe...18

2. Aufgaben...19

3. Parameterübergabe in Java...21

4. Ziel und Entwicklungsstrategie...21

5. Vorgabe: Die Klasse LongSpeicher51...22

6. Doppelgänger erlauben...25

7. Vorgabe: Die Klasse LongSpeicher50...27

Projekt 6: Sammeln in einer Hash-Tabelle...31

1. Grundbegriffe...31

2. Ziel...31

3. Vorgabe: Die Klasse LongSpeicher60...31

4. Aufgaben...33

Projekt 7: Maximale Teilsumme...34

1. Ein konkretes Beispiel...34

2. Grundbegriffe...34

3. Aufgaben...34

4. Zwischenergebnisse speichern...35

5. "Teile und herrsche" mit Rekursion...36

6. Geht es noch besser?...38

Projekt 8: Ohne und mit "teile und herrsche"...39

1. Sortieren ohne "teile und herrsche"...39

2. Grundbegriffe...40

3. Aufgaben...40

4. Geniale Idee 1...41

5. Geniale Idee 2...41

6. Das Prinzip "teilen und herrschen"...42

Anhang 1: LongSpeicher_EndTest...43

Anhang 2: MaxTeilsumme_EndTest...56

(2)

S. 2, WS17/18 Projekt 1: Sammeln in einer unsortierten Reihung Beuth-Hochschule Projekt 1: Sammeln in einer unsortierten Reihung

(Collecting in an unsorted array) 1. Grundbegriffe

Eine LongSpeicher-Klasse ist eine Erweiterung (oder: Unterklasse) der folgenden abstrakten Klasse:

1 /* --- 2 * Datei AbstractLongSpeicher.java

3 *

4 * Von dieser abstrakten Klasse sollen mehrere konkrete Unterklassen 5 * (LongSpeicher10, LongSpeicher20, ...) entwickelt werden.

6 * Jedes Objekt einer sollchen konkreten Unterklasse ist ein long-Speicher, 7 * d.h. ein Speicher

8 * in den man long-Werte einfuegen kann (mit fuegeEin), 9 * aus dem man long-Werte entfernen kann (mit loesche)

10 * und in dem man die Anwesenheit eines bestimmten long-Wertes pruefen 11 * kann (mit istDrin).

12 *

13 * Alle long-Speicher sollen es zulassen, dass man einen bestimmten 14 * long-Wert mehrmals einfuegt (d.h. Doppelgaenger sollen erlaubt sein).

15 --- */

16 public abstract class AbstractLongSpeicher { 17

18 abstract public String toString();

19 // Liefert eine String-Darstellung dieses Speichers. Beispiele:

20 //

21 // Zahl(en) im Ergebnis von:

22 // Speicher toString 23 // -- "[]"

24 // 10 "[10]"

25 // 20, 30, 10 "[20, 30, 10]"

26

27 abstract public boolean fuegeEin(long n);

28 // Fuegt n in diesen Speicher ein und liefert true.

29 // Liefert false, wenn n nicht eingefuegt werden konnte.

30

31 abstract public boolean loesche(long n);

32 // Loescht ein Vorkommen von n in diesem Speicher, und liefert true.

33 // Liefert false falls n nicht in diesem Speicher vorkommt.

34

35 abstract public boolean istDrin(long n);

36 // Liefert true wenn n in diesem Speicher vorkommt, und sonst false.

37

38 // Methoden mit kurzen Namen:

39 // @formatter:off Hier ist nur eine Zeile pro Methode vorteilhaft 40 static void printf(String s, Object... ob) {System.out.printf (s, ob);}

41 static void pln(Object ob) {System.out.println( ob);}

42 static void p (Object ob) {System.out.print ( ob);}

43 static void pln() {System.out.println( );}

44

45 // long-Werte, die in einen long-Speicher eingefuegt werden, sollen 46 // nicht mit den Operatoren <, <=, == etc, verglichen werden, sondern 47 // mit den folgenden Funktionen lt, le, eq etc.

48 // Dadurch wird es leichter, den Typ long (spaeter einmal) durch einen 49 // anderen Typ (z.B. int oder String oder ...) zu ersetzen.

50 static boolean lt(long a, long b) {return a < b;} // less then 51 static boolean le(long a, long b) {return a <= b;} // less or equal 52 static boolean eq(long a, long b) {return a == b;} // equal

53 static boolean ge(long a, long b) {return a >= b;} // greater or equal 54 static boolean gt(long a, long b) {return a > b;} // greater

55 static boolean ne(long a, long b) {return a != b;} // not equal 56 } // AbstractLongSpeicher

(3)

Beuth Hochschule Projekt 1: Sammeln in einer unsortierten Reihung WS17/18, S. 3 Ein LongSpeicher-Objekt (kurz: ein long-Speicher) ist ein Objekt einer LongSpeicher-Klasse.

Was Sie tun sollen: Erzeugen Sie in Eclipse ein Projekt namens LongSpeicher. Nehmen Sie sich vor, alle Klassen, die Sie in dieses Projekt einfügen werden, dem namenlosen Paket zuzuordnen (indem Sie beim Erzeugen einer Klasse nach Package: nichts eintragen). In Eclipse hat das namenlose Paket den Namen default).

Erzeugen Sie dann in diesem Projekt eine Java-Klasse namens AbstractLongSpeicher und ersetzen Sie ihren Inhalt (das sind nur ein paar Zeilen) durch den hier vorgegebenen Text dieser Klasse.

Entfernen Sie die Zeilen-Nrn am Rand des Textes (entweder im Blockmodus, siehe Alg-

TippszuEclipse.odt Abschnitt 3.) oder mit Hilfe von regulären Ausdrücken und Fangmustern (siehe das selbe Papier, Abschnitt 16.). Entfernen Sie die Zeilen-Nrn auf keinen Fall "einzeln von Hand", das ist einer Informatikerin / eines Informatikers nicht würdig!

2. Ziel

In diesem Projekt 1 sollen Sie eine LongSpeicher-Klasse namens LongSpeicher10 schreiben, die den Speicher als eine unsortierte Reihung (an unsorted array) implementiert.

3. Vorgabe: Die Klasse LongSpeicher10

Um das Schreiben der Klasse LongSpeicher10 zu erleichtern und zu beschleunigen, ist die folgende (kompilierbare und ausführbare, aber noch) unvollständige Version der Klasse bereits vorgegeben:

1 // Datei LongSpeicher10.java

2 /* --- 3 Jedes Objekt der Klasse LongSpeicher10 ist ein Speicher, in dem man

4 long-Werte sammeln (einfuegen, entfernen, suchen) kann.

5 Doppelgaenger sind erlaubt.

6 --- 7 Implementierung: Als unsortierte Reihung.

8 --- */

9 import static java.lang.String.format;

10

11 class LongSpeicher10 extends AbstractLongSpeicher {

12 // --- 13 // Zum Ein-/Ausschalten von Testbefehlen:

14 static final boolean TST1 = true;

15 // --- 16 private long[] speicher;

17 private int lbi = -1; // letzter belegter Index 18

19 public LongSpeicher10(int length) { 20 speicher = new long[length];

21 }

22 // --- 23 private int index(long n) {

24 // Liefert -1, wenn n nicht in diesem Speicher vorkommt.

25 // Liefert sonst einen Index i, an dem ein n im Speicher steht 26 // (d.h. fuer diesen Index i gilt: speicher[i] == n).

27

28 return -999; // MUSS ERSETZT WERDEN 29 }

30 // --- 31 @Override

32 public String toString() {

33 // Liefert eine String-Darstellung dieses Speichers. Beispiele:

34 //

35 // Zahl(en) im Ergebnis von:

36 // Speicher toString

(4)

S. 4, WS17/18 3. Vorgabe: Die Klasse LongSpeicher10 Beuth-Hochschule

37 // -- "[]"

38 // 10 "[10]"

39 // 20, 30, 10 "[20, 30, 10]"

40

41 return "Noch nicht implementiert!"; // MUSS ERSETZT WERDEN 42 }

43 // --- 44 @Override

45 public boolean fuegeEin(long n) {

46 // Liefert false, falls dieser Speicher bereits voll ist.

47 // Fuegt sonst n in diesen Speicher ein und liefert true.

48

49 return false; // MUSS ERSETZT WERDEN 50 }

51 // --- 52 @Override

53 public boolean loesche(long n) {

54 // Entfernt ein n aus diesem Speicher, und liefert true.

55 // Liefert false falls n in diesem Speicher nicht vorkommt.

56

57 return false; // MUSS ERSETZT WERDEN 58 }

59 // --- 60 @Override

61 public boolean istDrin(long n) {

62 // Liefert true wenn n in diesem Speicher vorkommt, und sonst false.

63

64 return false; // MUSS ERSETZT WERDEN 65 }

66 // --- 67 // Zum Testen:

68 private void print(String name) {

69 // Gibt name, speicher.length, lbi und alle long-Werte dieser 70 // Sammlung (in 2 Zeilen) zur Standardausgabe aus:

71 printf("%s.length: %d, lbi: %2d:%n", name, speicher.length, lbi);

72 printf("%s.toString(): %s%n", name, this.toString());

73 }

74 // --- 75 static public void main(String[] sonja) {

76 printf("LongSpeicher10: Jetzt geht es los!%n");

77 printf("---%n");

78 printf("Test toString mit leerer Sammlung:%n%n");

79 LongSpeicher10 lsa = new LongSpeicher10(4);

80 printf("lsa.toString(): %s%n", lsa.toString());

81 printf("---%n");

82 printf("Positive Tests mit fuegeEin und toString():%n%n");

83 printf("lsa.fuegeEin(25): %b%n", lsa.fuegeEin(25));

84 printf("lsa.toString(): %s%n", lsa.toString());

85 printf("lsa.fuegeEin(15): %b%n", lsa.fuegeEin(15));

86 printf("lsa.toString(): %s%n", lsa.toString());

87 printf("lsa.fuegeEin(45): %b%n", lsa.fuegeEin(45));

88 printf("lsa.toString(): %s%n", lsa.toString());

89 printf("lsa.fuegeEin(35): %b%n", lsa.fuegeEin(35));

90 printf("lsa.toString(): %s%n", lsa.toString());

91 printf("---%n");

92 printf("Negative Tests mit fuegeEin und toString:%n");

93 // HIER MUESSEN BEFEHLE EINGEFUEGT WERDEN

94 printf("---%n");

95 printf("Negativer Test mit index und leerer Sammlung:%n%n");

96 // HIER MUESSEN BEFEHLE EINGEFUEGT WERDEN

97 printf("---%n");

98 printf("Positive Tests mit index und halb-voller Sammlung:%n%n");

(5)

Beuth Hochschule Projekt 1: Sammeln in einer unsortierten Reihung WS17/18, S. 5

99 // HIER MUESSEN BEFEHLE EINGEFUEGT WERDEN

100 printf("---%n");

101 printf("Positive Tests mit index und ganz-voller Sammlung:%n%n");

102 // HIER MUESSEN BEFEHLE EINGEFUEGT WERDEN

103 printf("---%n");

104 printf("Negative Tests mit index und halb-voller Sammlung:%n%n");

105 // HIER MUESSEN BEFEHLE EINGEFUEGT WERDEN

106 printf("---%n");

107 printf("Negative Tests mit index und ganz-voller Sammlung:%n%n");

108 // HIER MUESSEN BEFEHLE EINGEFUEGT WERDEN

109 printf("---%n");

110 printf("Positive Tests mit istDrin und halb-voller Sammlung:%n%n");

111 // HIER MUESSEN BEFEHLE EINGEFUEGT WERDEN

112 printf("---%n");

113 printf("Positive Tests mit istDrin und ganz-voller Sammlung:%n%n");

114 // HIER MUESSEN BEFEHLE EINGEFUEGT WERDEN

115 printf("---%n");

116 printf("Negative Tests mit istDrin und leerer Sammlung: %n%n");

117 // HIER MUESSEN BEFEHLE EINGEFUEGT WERDEN

118 printf("---%n");

119 printf("Negative Tests mit istDrin und halb-voller Sammlung:%n%n");

120 // HIER MUESSEN BEFEHLE EINGEFUEGT WERDEN

121 printf("---%n");

122 printf("Negative Tests mit istDrin und ganz-voller Sammlung:%n%n");

123 // HIER MUESSEN BEFEHLE EINGEFUEGT WERDEN

124 printf("---%n");

125 printf("Negative Tests mit loesche:%n%n");

126 // HIER MUESSEN BEFEHLE EINGEFUEGT WERDEN

127 printf("---%n");

128 printf("Positive Tests mit loesche:%n%n");

129 // HIER MUESSEN BEFEHLE EINGEFUEGT WERDEN

130 printf("---%n");

131 printf("Sind Doppelgaenger erlaubt?%n%n");

132 // HIER MUESSEN BEFEHLE EINGEFUEGT WERDEN

133 printf("---%n");

134 printf("LongSpeicher10: Das war's erstmal!%n%n");

135 } // main

136 // --- 137 } // class LongSpeicher10

Was Sie tun sollen: Erzeugen Sie im Eclipse-Projekt LongSpeicher eine Klasse namens LongSpeicher10 und ersetzen Sie ihren Inhalt (das sind nur ein paar Zeilen) durch den hier vorgegebenen Text dieser Klasse. Entfernen Sie die Zeilen-Nrn am Rand des Textes (wie weiter oben bei der Klasse AbstractLongSpeicher beschrieben).

Das Programm LongSpeicher10 ist dann (hoffentlich) sofort ausführbar, aber seine Ausgaben werden noch auf Fehler in der Klasse LongSpeicher10 hinweisen. Das ist kein Wunder, denn die vorgegebene Klasse ist ja noch unvollständig.

4. Fragen

Schauen Sie sich den vorgegebenen Text der Klasse LongSpeicher10 genau an und versuchen Sie dann, die folgenden Fragen zu beantworten (noch bevor Sie den Text verändern):

Frage-01: Wie viele Variablen werden in jedes LongSpeicher10-Objekt eingebaut und wie heißen diese Variablen? (Tipp: Die Anzahl dieser Variablen ist nicht sehr groß. In ein Objekt werden nur die Variablen eingebaut, die direkt in der Klasse und ohne das Schlüsselwort static vereinbart wurden).

Frage-02: Wie heißt die Reihung (the array), in der später long-Werte gespeichert werden?

(6)

S. 6, WS17/18 4. Fragen Beuth-Hochschule Frage-03: Wo (in welcher Zeile der Klasse) und wann wird die Länge dieser Reihung festgelegt und die Reihung (mit einem new-Befehl) erzeugt? (Immer Sonntags um 10? Oder immer wenn die Methode toString aufgerufen wird? Oder wann?)

Frage-04: Was bedeutet der Variablen-Name lbi?

Wann muss der Wert dieser Variablen um 1 erhöht werden? Immer wenn ... ? Wann muss der Wert dieser Variablen um 1 erniedrigt werden? Immer wenn ... ?

Frage-05: In welcher Zeile wird ein LongSpeicher10-Objekt namens lsa erzeugt?

(Tipp: Die Nr. dieser Zeile ist größer als 70).

Frage-06: Wie viele Indizes hat die long-Reihung in dem Objekt namens lsa?

Frage-07: Wie viele dieser Indizes sind anfangs belegt und wie viele sind anfangs noch frei (unmittelbar nachdem das Objekt lsa erzeugt wurde)?

5. Die Klasse LongSpeicher10 vervollständigen

Sie sollen (in der vorgegebenen, noch unvollständigen Version der Klasse LongSpeicher10) die mit // MUSS ERSETZT WERDEN oder mit // HIER MUESSEN BEFEHLE EINGEFUEGT WERDEN gekennzeichneten Zeilen durch geeignete Java-Befehle ersetzen. Mit anderen Worten: Sie sollen die Methoden index, toString, fuegeEin, loesche und istDrin implementieren und testen (mit Befehlen, die Sie in die main-Methode eingefügt haben. Ein paar Test-Befehle stehen als Beispiele schon dort).

Die folgende Reihenfolge von Implementierungs- und Test-Schritten wird (stark) empfohlen:

Schritt-01: Implementieren Sie die Methode fuegeEin. Diese Methode muss zwei Fälle unterscheiden und behandeln. Behandeln Sie zuerst den einfacheren Fall. Nutzen Sie außerdem aus, dass in Java ein Befehl wie der Folgende erlaubt ist:

speicher[++lbi] = n;

Zuerst wird lbi um 1 erhöht (denn das ++ steht hier vor lbi). Dann erfolgt die Zuweisung.

Schritt-02: Implementieren Sie die Methode toString wie folgt:

1. Behandeln Sie zuerst den einfachsten Fall (wenn der speicher leer ist).

2. Vereinbaren Sie dann ein StringBuilder-Objekt namens sb, um darin den (möglicherweise ziemlich langen) Ergebnis-String der Methode "zusammenzubauen", und fügen Sie den ersten long- Wert (speicher[0]) in sb ein. Dieser Wert ist ein Sonderfall, weil davor kein Trenner ", " stehen darf/muss.

3. Fügen Sie dann (mit einer for-Schleife) alle weiteren long-Werte in sb ein, jeden mit einem Trenner ", " davor.

Regel: Wenn es möglich ist, soll man if-Befehle in Schleifen vermeiden! Hier ist es möglich.

Unterschätzen Sie diese toString-Methode nicht. Sie bietet viele Gelegenheiten, Fehler zu machen.

Schritt-03: Testen Sie, ob die toString-Methode den korrekten String liefert, wenn die Sammlung noch leer ist.

Schritt-04: Testen Sie die Methoden fuegeEin und toString. Fügen Sie nacheinander mehrere long- Werte in eine Sammlung ein und geben Sie nach jedem Einfügen das Ergebnis von toString aus.

Vergewissern Sie sich, dass eine Sammlung der Länge 1 ohne Trennzeichen ausgegeben wird und längere Sammlungen mit der richtigen Anzahl von Trennzeichen ", ". Und testen Sie auch den Fall, dass die Sammlung voll ist und die Methode fuegeEin das Ergebnis false liefern muss.

Schritt-05: Implementieren Sie die Methode index. Diese Methode muss die Reihung speicher von Anfang an (ab dem Index 0) durchsuchen. Überlegen Sie sorgfältig:

Bis wohin (bis zu welchem Index) muss die Suche gehen?

(7)

Beuth Hochschule Projekt 1: Sammeln in einer unsortierten Reihung WS17/18, S. 7 Und denken Sie daran, long-Werte nicht mit den Operatoren <, <=, == etc. zu verglichen, sondern mit den Methoden lt, le, eq, etc., die in der Klasse AbstractLongSpeicher vereinbart sind.

Hinweis: Ein naheliegender Fehler besteht darin, die Klasse index sorgfältig zu schreiben und

auszutesten, dann zu vergessen "was sie leistet" und in anderen Methoden "die gleiche Leistung" noch mal zu programmieren. Das würde die Wartungskosten unnötig erhöhen. Behalten Sie genau im Kopf, welche Leistung Sie in index eingebaut haben, und wenn Sie in einer anderen Methode diese Leistung brauchen können, dann rufen Sie index auf!

Schritt-06: Testen Sie die Methode index. Testen Sie sie mit einer leeren Sammlung und

mit einer etwa halb-vollen Sammlung und mit einer ganz-vollen Sammlung.

Vergewissern Sie sich, dass die Methode index für jeden vorhandenen long-Wert den richtigen Index liefert und dass die Methode für ein paar nicht-vorhandene Werte das Ergebnis -1 liefert.

Schritt-07: Implementieren Sie die Methode istDrin. Der Rumpf dieser Methode darf nur einen Befehl enthalten, nämlich einen return-Befehl. Auf keinen Fall dürfen Sie in dieser Methode einen if- Befehl verwenden oder die Literale true oder false benutzen!

Schritt-08: Testen Sie die Methode istDrin. Testen Sie sie mit einer leeren Sammlung, mit einer etwa halb-vollen Sammlung und mit einer ganz-vollen Sammlung. Vergewissern Sie sich, dass die Methode für jeden vorhandenen long-Wert true liefert und für ein paar nicht-vorhandene long-Werte false.

Schritt-09: Implementieren Sie die Methode loesche. Dies ist vermutlich die schwierigste der 5 Methoden. Beachten Sie die folgenden 3 Warnungen:

Wie-man-es-nicht-machen-darf-1: speicher[i] = 0;

Damit würde zwar der alte Wert von speicher[i] zerstört, aber gleichzeitig würde die Zahl 0 in die Sammlung eingefügt. Das wäre falsch.

Wie-man-es-nicht-machen-darf-2: Um die Reihungs-Komponente speicher[i] zu löschen alle weiter rechts liegenden Komponenten um eine Position nach links verschieben.

Diese Idee kann zu einer korrekten Lösung führen, ist in diesem Projekt aber unnötig ineffizient, denn im schlimmsten Fall müssten (fast) alle Komponenten der Reihung speicher verschoben werden. Es gibt aber auch eine Lösung mit folgender Eigenschaft: Wenn man den Index i der zu löschenden Komponenten erst mal hat, müssen nur noch 2 einfache Befehle ausgeführt werden, auch wenn der speicher z.B. 10 Tausend oder 10 Millionen Komponenten enthält.

Wie-man-es-nicht-machen-darf-3: In die zu löschende Komponente den Wert einer Nachbar- Komponente kopieren. Das wäre falsch, weil sich dann zwei Kopien eines long-Wertes in der Sammlung befinden würden, obwohl der Benutzer nur eine Kopie eingefügt hat. Wenn der Benutzer später eine Kopie löscht, rechnet er nicht damit, dass sich noch eine weitere Kopie in der Sammlung befindet. Außerdem gibt es nicht immer eine Nachbarkomponente (wenn sich nur ein long-Wert im speicher befindet).

Wie-man-es-machen-sollte? Dazu hier ein heißer Tipp:

Es gibt eine Komponente der Reihung speicher, die man besonders leicht löschen kann, und zwar ohne die Reihung selbst im geringsten zu verändern. Welche Komponente ist das und wie kann man sie löschen? Wenn Sie die richtige Antworten auf diese Fragen gefunden haben, brauchen Sie nur noch einen relativ kleinen Zusatz-Geistesblitz und schon haben Sie eine gute loesche-Methode!

Die Methode loesche muss zwei Fälle behandeln. Sie haben doch sicherlich den einfacheren Fall zuerst behandelt, damit der kompliziertere Fall dadurch ein bisschen einfacher wird? (Entschuldigen Sie, dass ich gefragt habe :-)).

Schritt-10: Testen Sie die Methode loesche. Testen Sie sie mit einer leeren Sammlung, mit einer etwa halb-vollen Sammlung und mit einer ganz-vollen Sammlung. Klappt das Löschen der ersten

(8)

S. 8, WS17/18 5. Die Klasse LongSpeicher10 vervollständigen Beuth-Hochschule Komponenten speicher[0]? Und das Löschen der letzten Komponenten speicher[lbi]? Und das Löschen einer Komponenten, die "mitten drin" liegt? Liefert loesche in den richtigen Fällen false?

Testen Sie am besten jetzt, ob man Doppelgänger (mehrere gleiche long-Werte) einfügen und wieder löschen kann.

Letzter Schritt: Jetzt hat Ihre Arbeitsgruppe eine Belohnung verdient. Gehen Sie an einen geeigneten Ort (z.B. in die Mensa) und nehmen Sie dort gemeinsam Ihr(e) Lieblingsgetränk(e) zu sich.

(9)

Beuth Hochschule Projekt 1: Sammeln in einer unsortierten Reihung WS17/18, S. 9 6. Der Endtest

Wenn Sie alle Methode implementiert und so gründlich getestet haben, dass Sie bereit wären, 10 Euro auf Ihre Korrektheit zu wetten (aber auf keinen Fall früher!)

sollen Sie mit dem vorgegebenen JUnit-Testprogramm LongSpeicher_EndTest einen Endtest (oder: Abnahmetest) durchführen. Ein solcher Test hat im Wesentlichen nur ein Ergebnis: Entweder

"einen roten Balken" oder "einen grünen Balken".

Achtung: Missbrauchen Sie das Testprogramm LongSpeicher_EndTest nicht als

"Entwicklungswerkzeug" (d.h. während Sie die Methoden fuegeEin, toString, ...

implementieren). Es ist nicht als Testwerkzeug geeignet (seine Fehlermeldungen sind schwer zu verstehen). Sie sollen üben, eigene Testbefehle (in die main-Methode der betreffenden Klasse) zu schreiben, deren Ausgaben leicht zu verstehen sind.

Vergleich: Das vorgegebene Testprogramm als Entwicklungswerkzeug zu benutzen(statt eigene Testbefehle in die main-Methode zu schreiben) ist etwa so, als ob Sie als Rennfahrer für die Tour-de- France trainieren, dabei aber nicht Ihre Rennrad benutzen, sondern ein zufällig herumstehendes Moped.

Selbst wenn Sie damit schneller als Ihre Team-Kollegen sind, wird Ihr Trainer Sie nicht dafür loben.

Das Testprogramm LongSpeicher_EndTest (und eine Anleitung, wie man es in ein Eclipse- Projekt integrieren kann) finden Sie im Anhang 1 am Ende dieser Datei.

7. Aufgaben

Aufgabe-01: Was für ein Ding ist AbstractLongSpeicher?

Aufgabe-02: Was für ein Ding ist LongSpeicher10?

Aufgabe-03: Was ist eine LongSpeicher-Klasse?

Aufgabe-04: Was ist ein LongSpeicher-Objekt?

Aufgabe-05: Geben Sie die Namen aller abstrakten Methoden an, die in der Klasse AbstractLongSpeicher vereinbart werden.

Aufgabe-06: Die Klasse LongSpeicher10 enthält eine private Methode int index(long n). Diese Methode wird nicht von der Schnittstelle LongSpeicher "vorgeschrieben". Wozu ist sie da?

Was bringt sie für Vorteile?

Aufgabe-07: Ungefähr wie lang kann eine Reihung (engl. an array) in Java höchstens sein (d.h. wie viele Komponenten kann eine Java-Reihung höchstens enthalten)? Geben Sie als Antwort eine ungefähre Zahl an (sie kann ruhig 10 % von der genauen, richtigen Antwort abweichen). Geben Sie keine Formel oder andere Rechenaufgabe an.

Aufgabe-08: Was ist die wichtigste positive Eigenschaft von Reihungen? Was ist die einzige negative Eigenschaft von Reihungen?

Aufgabe-09: Angenommen, wir benutzen eine bestimmte Plattform (d.h. eine bestimmte Kombination aus Hardware und Software), um das Programm LongSpeicher10 auszuführen. Wovon hängt es dann noch ab, wie lange eine Ausführung der Methode istDrin dauert?

Aufgabe-10: Wovon hängt es (auf derselben Plattform) ab, wie lange eine Ausführung der Methode fuegeEin dauert? Rechnen Sie auch bei dieser Aufgabe mit hinterhältigen und irreführend

formulierten Fragen! :-)

Aufgabe-11: Angenommen, Sie führen mit einem ausreichend großen LongSpeicher10-Objekt folgendes Experiment durch:

Zuerst fügen Sie n long-Werte ein. Dann rufen Sie die Methode istDrin genau n Mal mit solchen long-Werten auf, die Sie nicht eingefügt haben (so dass istDrin immer false liefern muss). Sie messen die Zeit, die Ihr Rechner für die Ausführung dieser istDrin-Aufrufe benötigt.

Dieses Experiment führen Sie einmal mit n gleich 100 Millionen durch und

(10)

S. 10, WS17/18 7. Aufgaben Beuth-Hochschule finden heraus, dass Ihr Rechner 150 sec dafür benötigt.

Wie lange wird Ihr Rechner dann vermutlich brauchen, wenn Sie das Experiment mit n gleich 200 Millionen wiederholen?

Aufgabe-12: (Fortsetzung der vorigen Aufgabe) Wie lange wird Ihr Rechner benötigen, wenn Sie das erste Experiment (mit n gleich 100 Millionen) wiederholen, istDrin aber mit jedem in den Speicher eingefügten long-Wert einmal aufrufen (so dass istDrin immer true liefern muss)?

(11)

Beuth Hochschule Projekt 2: Sammeln in einer sortierten Reihung WS17/18, S. 11 Projekt 2: Sammeln in einer sortierten Reihung

(Collecting in a sorted array) 1. Ziel

In diesem Projekt sollen Sie eine LongSpeicher-Klasse namens LongSpeicher20 schreiben, die den Speicher als eine sortierte Reihung (a sorted array) implementiert.

Beginnen Sie mit einer Kopie der Datei LongSpeicher10.java, und ändern Sie diese Kopie in geeigneter Weise.

Besonders wichtig ist die Änderung der Methode index. Dazu finden Sie im nächsten Abschnitt zwei verschiedene Vorgaben (von denen Sie eine wählen können und dann noch vervollständigen müssen).

Aber auch die anderen Methoden müssen geändert werden. Nur die Methode toString kann hier unverändert von LongSpeicher10 übernommen werden.

2. Vorgaben: Zwei Skelette der Methode index

Das Verfahren binäres Suchen (in einer sortierten Reihung) ist im Prinzip relativ leicht zu verstehen, aber bei der konkreten Implementierung gibt es reichlich Gelegenheiten, kleine Fehler einzubauen, die zu völlig falschen Ergebnissen oder Ausnahmen führen.

Im Folgenden werden zwei "Skelette der Methode index" (unvollständige Versionen der Methode index) vorgegeben, eine iterative und eine rekursive. Sie sollen eines der Skelette auswählen, es vervollständigen und in Ihre Klasse LongSpeicher20 einbauen.

Hinweis: Wer beide Skelette vervollständigt und ausprobiert bekommt dafür keine Minuspunkte! :-) Zur Erinnerung: long-Werte werden hier mit den Methoden lt (less than) und gt (greater than) verglichen, die in der Klasse AbstractLongSpeicher vereinbart sind.

Skelett 1: Eine iterative Version der (binär suchenden) Methode index Sie sollen die drei Auslassungen ... durch geeignete Java-Befehle ersetzen.

1 private int index(long n) {

2 // Liefert (wenn moeglich) einen Index i, an dem ein n im speicher 3 // steht (d.h. fuer den gilt: speicher[i] == n).

4 // Falls n nicht im speicher steht, wird der Index geliefert, an dem 5 // n eingefuegt werden sollte (falls man es einfuegen moechte).

6 // Achtung:

7 // Das Ergebnis von index ist moeglicherweise gleich speicher.length 8 // (und somit kein Index von speicher), und zwar wenn

9 // 1. der speicher voll ist (d.h. lbi == speicher.length-1 gilt) und 10 // 2. n groesser ist als alle long-Werte in diesem Speicher.

11

12 // Binaer gesucht wird in der Teilreihung speicher[von..bis]:

13 int von = 0;

14 int bis = lbi;

15

16 while (von <= bis) {

17 int mitte = von + (bis - von) / 2;

18

19 if (gt(n, speicher[mitte])) {

20 ... // rechts weitersuchen 21 } else if (lt(n, speicher[mitte])) {

22 ... // links weitersuchen 23 } else {

24 ... // hier gilt: eq(n, speicher[mitte]) 25 }

26 }

27 return von; // n steht nicht im speicher

(12)

S. 12, WS17/18 2. Vorgaben: Zwei Skelette der Methode index Beuth-Hochschule

28 }

Skelett 2: Eine rekursive Version der (binär suchenden) Methode index

Genauer geht es hier um eine ganz einfache (und gar nicht rekursive) Methode index, die nur die Aufgabe hat, eine rekursive Hilfsmethode (die hier indexR heißt) aufzurufen und ihr ein paar

zusätzliche Parameter zu übergeben. Die rekursive Methode macht dann die eigentliche Arbeit. Dieses Muster (eine nicht-rekursive Methode "für die Kunden" und eine rekursive Hilfsmethode, die die Arbeit macht) kommt bei rekursiven Methoden häufiger vor.

1 private int index(long n) {

2 // Liefert (wenn moeglich) einen Index i, an dem ein n im speicher 3 // steht (d.h. fuer den gilt: speicher[i] == n).

4 // Falls n nicht im speicher steht, wird der Index geliefert, an dem 5 // n eingefuegt werden sollte (falls man es einfuegen moechte).

6 // Achtung:

7 // Das Ergebnis von index ist moeglicherweise gleich speicher.length 8 // (und somit kein Index von speicher), und zwar wenn

9 // 1. der speicher voll ist (d.h. lbi == speicher.length-1 gilt) und 10 // 2. n groesser ist als alle long-Werte in diesem Speicher.

11

12 return indexR(n, 0, lbi);

13 }

Jetzt kommt das Skelett der rekursiven Hilfsmethode indexR.

Auch hier sollen Sie die drei Auslassungen ... durch geeignete Befehle ersetzen:

14 private int indexR(long n, int von, int bis) { 15 // Rekursive Hilfsmethode fuer index.

16 // Sollte nur von index aufgerufen werden.

17

18 // Ein einfacher Fall:

19 if (von > bis) return von; // n steht nicht im speicher 20

21 int mitte = ... ; 22

23 // Zwei rekursive Faelle:

24 if (gt(n, speicher[mitte])) return ... ; 25 if (lt(n, speicher[mitte])) return ... ; 26

27 // Noch ein einfacher Fall:

28 return mitte; // hier gilt: eq(n, speicher[mitte]) 29 }

3. Aufgaben

Aufgabe-01: Angenommen, die Variable lbi (letzter belegter Index) hat den Wert 4 und die Reihung (the array) speicher enthält 5 long-Werte wie folgt:

Die Reihung speicher:

Indizes 0 1 2 3 4

long-Werte 20 30 40 50 60

Berechnen Sie mit Papier und Bleistift die Ergebnisse der Funktion index für verschiedene Parameter und tragen Sie Ihre Ergebnisse in die folgende Tabelle ein:

Ergebnisse der Funktion index:

n 10 20 25 35 55 65

index(n)

(13)

Beuth Hochschule Projekt 2: Sammeln in einer sortierten Reihung WS17/18, S. 13

Aufgabe-02: Stellen Sie sich vor:

1. In der iterativen Version der Funktion index würde die Variable mitte (in Zeile 17) nicht mit dem Ausdruck

von + (bis - von ) / 2

sondern mit dem mathematisch äquivalenten, aber einfacheren Ausdruck (von + bis) / 2

initialisiert

2. Die Reihung speicher enthält 2 Milliarden Komponenten.

3. Der long-Wert g ist größer ist als alle long-Werte im speicher. Was läuft dann schief, wenn der Funktionsaufruf

... index(g) ...

ausgeführt wird?

Aufgabe-03: Füllen Sie die noch freien Felder der folgenden Tabelle aus:

Zweierpotenz ungefähr entsprech-

ende Zehnerpotenz deutsches

Zahlwort amerikanisches Zahlwort

210 103 1 Tausend 1 thousend

220 106 1 Million 1 million

230 240 250 260

Aufgabe-04: Genau wie oft muss der Rumpf der while-Schleife in der iterativen Funktion index höchstens ausgeführt werden, wenn die Reihung speicher ungefähr a viele long-Werte enthält?

Füllen Sie die folgende Tabelle entsprechend aus:

Anzahl a der

long-Werte 103 106 109 1012 1015 1018

Wie oft while- Rumpf ausführen?

(14)

S. 14, WS17/18 Projekt 3: Sammeln in einer unsortierten verketteten Liste Beuth-Hochschule Projekt 3: Sammeln in einer unsortierten verketteten Liste

Neben wichtigen positiven Eigenschaften haben Reihungen (engl. arrays) auch eine negative Eigenschaft: "Sie sind aus Beton", d.h. nachdem eine Reihung erzeugt wurde, kann man ihre Länge nicht mehr verändern. Dagegen sind verkettete Listen "aus Gummi", d.h. man kann immer noch einen weiteren Knoten anhängen oder einfügen (bis der Ausführer keinen freien Speicher mehr hat).

1. Ziel

In diesem Projekt sollen Sie eine LongSpeicher-Klasse namens LongSpeicher30 schreiben, die den Speicher als eine unsortierte einfach verkettete Liste implementiert.

2. Vorgabe: Die Klasse LongSpeicher30

Die folgende (kompilierbare und ausführbare, aber noch) unvollständige

Klasse LongSpeicher30 ist vorgegeben (Erläuterungen dazu findet man nach der Klasse):

1 // Datei LongSpeicher30.java

2 /* --- 3 Jedes Objekt der Klasse LongSpeicher30 ist ein Speicher, in dem man

4 long-Werte sammeln (einfuegen, entfernen, suchen) kann.

5 Doppelgaenger sind erlaubt.

6 --- 7 Implementierung: Als unsortierte einfach verkettete Liste

8 --- */

9 class LongSpeicher30 extends AbstractLongSpeicher {

10 // --- 11 // Zum Ein-/Ausschalten von Testbefehlen:

12 static final boolean TST1 = false;

13 // --- 14 // Eine (statische) geschachtelte Klasse (nested static class).

15 // Jedes Objekt dieser Klasse kann als Knoten einer einfach 16 // verketteten Liste verwendet werden:

17 static private class Knoten { 18 Knoten next;

19 long data;

20

21 Knoten(Knoten next, long data) {// Konstruktor 22 this.next = next;

23 this.data = data;

24 } 25 }

26 // --- 27 // Eine leere Liste besteht aus 2 Dummy-Knoten:

28 // einem End-Dummy-Knoten EDK und einem Anfangs-Dummy-Knoten ADK. Die 29 // "richtigen Knoten" werden spaeter zwischen die 2 Dummies gehaengt.

30 final Knoten EDK = new Knoten(null, 0);// End-Dummy-Knoten 31 final Knoten ADK = new Knoten(EDK, 0); // Anfangs-Dummy-Knoten

32 // --- 33 private Knoten vorgaenger(long n) {

34 // Liefert den ersten Knoten k in dieser unsortierten Liste fuer den 35 // gilt: k.next.data == n

36 // (d.h. k ist der Vorgaenger eines Knotens, 37 // dessen data-Komponente gleich n ist).

38 // Falls n in dieser Sammlung nicht vokommt, ist k der 39 // Vorgaenger des EDK.

40

41 // Die gesuchte Zahl n in den End-Dummy-Knoten eintragen 42 // (spaetestens dort wird sie dann gefunden)

43 EDK.data = n;

44

(15)

Beuth Hochschule Projekt 3: Sammeln in einer unsortierten verketteten Liste WS17/18, S. 15

45 return ADK; // MUSS ERSETZT WERDEN 46 }

47 // --- 48 @Override

49 public String toString() {

50 // Liefert eine String-Darstellung dieses Speichers. Beispiele:

51 //

52 // Zahl(en) im Ergebnis von:

53 // Speicher toString 54 // -- "[]"

55 // 10 "[10]"

56 // 20, 30, 10 "[20, 30, 10]"

57

58 return "Noch nicht implementiert!"; // MUSS ERSETZT WERDEN 59 }

60 // --- 61 @Override

62 public boolean fuegeEin(long n) {

63 // Fuegt n in diesen Speicher ein und liefert true.

64

65 return false; // MUSS ERSETZT WERDEN 66 }

67 // --- 68 @Override

69 public boolean loesche(long n) {

70 // Loescht ein Vorkommen von n in diesem Speicher, und liefert true.

71 // Liefert false falls n nicht in diesem Speicher vorkommt.

72

73 return false; // MUSS ERSETZT WERDEN 74 }

75 // --- 76 @Override

77 public boolean istDrin(long n) {

78 // Liefert true wenn n in diesem Speicher vorkommt, und sonst false.

79

80 return false;// MUSS ERSETZT WERDEN 81 }

82 // --- 83 // Zum Testen:

84 private void print(String name) {

85 // Gibt den name und diese Sammlung in lesbarer Form 86 // zur Standardausgabe aus:

87 printf("%s.toString(): %s%n", name, this.toString());

88 } 89

90 private String nach_vor(long n, String soll) { 91 // Nachfolger des Vorgaengers:

92 // Zum Testen der Methode vorgaenger.

93 // Liefert eine String-Repraesentation des Nachfolgers 94 // des Knotens vorgaenger(n). Diese String-Repraesentation 95 // stellt n dar oder ist gleich "EDK".

96

97 Knoten nv = vorgaenger(n).next;

98

99 // Ergebnis, falls n in dieser Sammlung vorkommt:

100 String erg = "" + nv.data;

101

102 // Ergebnis, falls n in dieser Sammlung nicht vorkommt:

103 if (nv == ADK) erg = "ADK"; // Sollte nie benoetigt werden 104 if (nv == EDK) erg = "EDK";

105

106 // Berechnung einer Erfolgs-/Fehler-Meldung:

(16)

S. 16, WS17/18 2. Vorgabe: Die Klasse LongSpeicher30 Beuth-Hochschule

107 String mld = erg.equals(soll) ? "OK" : "Fehler";

108

109 // Ergebnis und Meldung zurueckgeben:

110 return erg + " <-- " + mld;

111 }

112 // --- 113 static public void main(String[] sonja) {

114 printf("LongSpeicher30: Jetzt geht es los!%n");

115 printf("---%n");

116 printf("Test vorgaenger (ohne fuegeEin!):%n%n");

117 LongSpeicher30 lsa = new LongSpeicher30();

118 lsa.print("lsa");

119 // Einfuegen ohne fuegeEin:

120 Knoten adk = lsa.ADK;

121 adk.next = new Knoten(adk.next, 20);

122 adk.next = new Knoten(adk.next, 30);

123 adk.next = new Knoten(adk.next, 10);

124 printf("Eingefuegt wurden: 20, 30, 10%n");

125 lsa.print("lsa");

126 printf("%n");

127 printf("lsa.nach_vor(10): %s%n", lsa.nach_vor(10, "10"));

128 printf("lsa.nach_vor(30): %s%n", lsa.nach_vor(30, "30"));

129 printf("lsa.nach_vor(20): %s%n", lsa.nach_vor(20, "20"));

130 printf("lsa.nach_vor(99): %s%n", lsa.nach_vor(99, "EDK"));

131 printf("---%n");

132 printf("Tests fuegeEin und toString:%n%n");

133 LongSpeicher30 lsb = new LongSpeicher30();

134 lsb.print("lsb");

135 printf("lsb.fuegeEin(20): %b%n", lsb.fuegeEin(20));

136

137 // Hier sollen Sie weitere aehnliche Testbefehle eifuegen 138

139 printf("---%n");

140 printf("LongSpeicher30: Das war's erstmal!%n%n");

141 } // main

142 // --- 143 } // class LongSpeicher30

Eine Klasse kann (nicht nur Methoden und Attribute, sondern auch) Klassen als Elemente enthalten, die man als geschachtelte Klassen (nested classes) bezeichnet. Die Klasse LongSpeicher30 enthält eine geschachtelte Klasse namens Knoten.

Wir unterscheiden richtige Knoten und Dummy-Knoten ("nicht-richtige" Knoten oder "Hilfsknoten").

Die beiden Dummy-Knoten haben folgenden Sinn:

Der End-Dummy-Knoten EDK: Vor jedem Suchen in der Liste werden wir den gesuchten long-Wert n in den Knoten EDK eintragen, etwa so:

EDK.data = n;

Damit stellen wir sicher, dass beim nachfolgenden Suchen der Wert n auf jeden Fall gefunden wird, entweder in einem richtigen Knoten, oder aber im End-Dummy-Knoten. Durch diesen kleinen Trick wird das Suchen ein bisschen vereinfacht und beschleunigt.

Der Anfangs-Dummy-Knoten ADK: Um einen Knoten K zu löschen, muss man das next-Attribut seines Vorgängerknotens VK verändern, etwa so:

VK.next = VK.next.next;

Nach diesem Befehl zeigt VK.next nicht mehr auf K, sondern auf den Nachfolgerknoten von K, und K ist aus der Liste "ausgeklinkt", d.h. gelöscht.

(17)

Beuth Hochschule Projekt 3: Sammeln in einer unsortierten verketteten Liste WS17/18, S. 17 Mit dem Anfangs-Dummy-Knoten ADK sorgen wir dafür, dass auch der erste richtige Knoten einen Vorgänger hat und genau so gelöscht werden kann, wie alle anderen Knoten. Ohne den ADK wäre das Löschen des ersten richtigen Knotens ein unschöner Sonderfall.

Weil man beim Löschen eines Knotens K auf seinen Vorgänger VK zugreifen muss, liefert die Methode vorgaenger den Vorgänger eines Knotens, der den long-Wert n enthält, und nicht einen Knoten, der n enthält. Trotzdem kann man die Methode vorgaenger nicht nur in der Methode loesche benutzen, sondern auch in istDrin.

Was Sie tun sollen: Die vorgegebene (noch unvollständige) Klasse LongSpeicher30 in Ihr Eclipse- Projekt LongSpeicher integrieren (ähnlich wie Sie das mit LongSpeicher10 schon gemacht haben). Dann die mit // MUSS ERSETZT WERDEN gekennzeichneten Zeilen durch geeignete Java- Befehle ersetzen (d.h. die Methoden vorgaenger, toString, fuegeEin, istDrin und

loesche implementieren). Jedes mal, wenn Sie eine Methode implementiert haben, sollen Sie geeignete Testbefehle in die main-Methode einfügen und die Methode gründlich testen (diese Testbefehle sind ein wichtiger Teil Ihrer Lösung!). Es folgen hier ein paar Hinweise und Tipps:

Reihenfolge: Es gibt 2 gute Möglichkeiten, mit der Vervollständigung der vorgegebenen Klasse LongSpeicher30 zu beginnen:

Möglichkeit 1: Methode vorgaenger

Möglichkeit 2: Methoden fuegeEin und toString (beide zusammen)

Methode vorgaenger: Schon in der veröffentlichten unvollständige Version von LongSpeicher30 enthält die main-Methode ein paar Befehle, mit denen die Methode vorgaenger getestet wird. Diese Befehle geben sogar "OK" bzw. "Fehler" aus, je nachdem ob der betreffende Test "gut oder

schlecht" ausgegangen ist. Versuchen Sie zu verstehen, wie diese Befehle (und die vorgegebene

Methode mit dem merkwürdigen Namen vor_nach) funktionieren, auch wenn das keineswegs einfach ist.

Methode toString: Versuchen Sie, alles was Sie in den vorangegangenen Projekten 1 und 2 über diese Methode gelernt haben auch in diesem Projekt anzuwenden. Mit anderen Worten: Versuchen Sie, "die abstrakte Struktur" dieser Methode aus den vorigen Projekten zu übernehmen, auch wenn die konkreten Befehle dieser Methode in diesem Projekt ein bisschen anders aussehen müssen (im aktuellen Projekt gibt es keine Reihung namens speicher, sondern nur die Dummy-Knoten ADK und EDK).

Methode fuegeEin: Der Rumpf dieser Methode sollte nur 2 Befehle enthalten, und der zweite sollte so aussehen:

return true;

Sie brauchen sich also nur noch über den ersten Befehl Gedanken zu machen.

Methode loesche: Diese Methode sollten Sie erst angehen, nachdem Sie die Methode vorgaenger programmiert und getestet haben. Können Sie raten, warum?

Methode istDrin: Diese Methode sollten Sie erst angehen, nachdem Sie die Methode vorgaenger programmiert und getestet haben. Können Sie raten, warum?

Der Rumpf von istDrin darf nur eine einfache Anweisung (engl. simple statement) enthalten. Er darf keine zusammengesetzte Anweisung (engl. compound statement) oder andere Befehle enthalten (ganz ähnlich wie in LongSpeicher10).

Wenn Sie (anhand der Ausgaben Ihrer Testbefehle in der main-Methode) festgestellt haben, dass alle 5 Methoden (vorgaenger, toString, fuegeEin, loesche, istDrin) richtig funktionieren, sollen Sie einen Endtest mit dem JUnit-Testprogramm LongSpeicher_EndTest durchführen.

3. Aufgaben

(18)

S. 18, WS17/18 3. Aufgaben Beuth-Hochschule Aufgabe-01: Was ist der wichtigste Vorteil einer verketteten Liste (a linked list) im Vergleich zu einer Reihung (compared with an array)?

Aufgabe-02: Angenommen, ein LongSpeicher30-Objekt ls enthält 10 Tausend long-Werte. Die Methode boolean vorgaenger(long n) enthält eine Schleife.

Wie oft wird der Rumpf dieser Schleife ausgeführt, wenn der Parameter n in ls nicht vorkommt?

Wie oft wird der Rumpf dieser Schleife im Durchschnitt ausgeführt, wenn man vorgaenger mit jedem in ls gespeicherten long-Wert einmal aufruft?

(19)

Beuth Hochschule Projekt 4: Sammeln in einer sortierten verketteten Liste WS17/18, S. 19 Projekt 4: Sammeln in einer sortierten verketteten Liste

1. Ziel

In diesem Projekt sollen Sie eine LongSpeicher-Klasse namens LongSpeicher40 schreiben, die den Speicher als eine sortierte einfach verkettete Liste implementiert.

Beginnen Sie mit einer Kopie der Datei LongSpeicher30.java. und ändern Sie in der Kopie zuerst die Methode vorgaenger entsprechend der Vorgabe (siehe nächsten Abschnitt). Ändern Sie dann die Befehle der Methoden fuegeEin, loesche und istDrin.

2. Vorgabe: Die Methode vorgaenger

Nur der Anfangskommentar ist vorgegeben (aber der ist, wie alle Anfangskommentare, sehr wichtig).

Die Befehle, die ihn verwirklichen, müssen Sie ergänzen:

1 private Knoten vorgaenger(long n) {

2 // Liefert den ersten Knoten k in dieser sortierten Liste fuer den 3 // gilt: ge(k.next.data, n)

4 // (d.h. k ist der Vorgaenger des ersten Knotens, 5 // dessen data-Komponente groesser oder gleich n ist).

6 // Der Knoten k kann ein "richtiger Knoten" oder der EDK sein.

7

8 return null; // MUSS ERSETZT WERDEN 9 }

3. Aufgaben

Aufgabe-01: Welche Vorteile hat eine sortierte verkettete Liste (LongSpeicher40) im Vergleich zu einer unsortierten verketteten Liste (LongSpeicher30)?

Aufgabe-02: Welche Vorteile hat eine unsortierte verkettete Liste (LongSpeicher30) im Vergleich zu einer sortierten verketteten (LongSpeicher40)?

Aufgabe-03: Warum kann man in einer sortierten verketteten Liste nicht binär suchen (wie in einer sortierten Reihung (LongSpeicher20)?

Aufgabe-04: Welche Vorteile hat eine sortierte Reihung (a sorted array, LongSpeicher20) im Vergleich mit einer sortierten verketteten Liste (LongSpeicher40)?

Aufgabe-05: Welche Vorteile hat eine sortierte verkettete Liste (LongSpeicher40) im Vergleich mit einer sortierten Reihung (a sorted array, LongSpeicher20) ? 4. Terminologie

Das Wort Liste (engl. list) bezeichnet in vielen Zusammenhängen eine aus Knoten bestehende Datenstruktur, bei der jeder Knoten (außer dem letzten) einen Zeiger auf einen Nachfolger enthält.

In der Java-Standardbibliothek wird das englische Wort List leider in einer ganz anderen Bedeutung verwendet. Dort ist List (vor allem) eine Schnittstelle. Ein List-Objekt ist eine Sammlung, bei der man mit Hilfe von Indizes 0, 1, 2, ... auf die einzelnen Komponenten zugreifen kann. ArrayList ist ein Beispiel für eine List-Klasse. Objekte der Java-Standardklasse LinkedList sind dagegen doppelt verkettete Listen (fast jeder Knoten enthält einen Zeiger auf seinen Vorgänger und einen Zeiger auf seinen Nachfolger).

(20)

S. 20, WS17/18 Projekt 5: Sammeln in einem binären Baum Beuth-Hochschule Projekt 5: Sammeln in einem binären Baum

Verkettete Listen sind flexibel ("aus Gummi"), aber das Suchen in einer Liste ist langsam.

Sortierte Reihungen erlauben ein sehr schnelles (binäres) Suchen, sind aber unflexibel ("aus Beton").

Binäre Bäume kombinieren die Vorteile von Listen und die von sortierten Reihungen:

Sie sind so flexibel wie Listen und beim Suchen so schnell wie sortierte Reihungen.

1. Grundbegriffe

Def.: Ein binärer Baum ist entweder ein leerer Baum

oder er besteht aus einem Knoten, an dem zwei binäre Bäume hängen.

Die Reihenfolge der beiden Bäume, die an einem Knoten hängen, ist wichtig. Man bezeichnet sie deshalb auch als den linken Unterbaum bzw. als den rechten Unterbaum des Knotens. Mit anderen Worten: "Binäre Bäume sind keine Mobiles" (die sich im Wind drehen können) und keine Graphen (bei denen es auch Knoten gibt, an denen andere Knoten hängen können, bei denen aber die Reihenfolge der

"dranhängenden Knoten" keine Rolle spielt)!.

Bei Sammlungen, die als Reihung oder Liste implementiert sind, macht es keinen großen Unterschied, ob man Doppelgänger erlaubt oder nicht. Bei binären Bäumen ist es schwieriger, Doppelgänger zu erlauben. Das merkt man unter anderem daran, dass der sehr abstrakte ("Implementierungs-ferne") Begriff "sortiert" anders definiert werden muss, wenn man Doppelgänger erlauben will.

Def.: Ein binärer Baum (ohne Doppelgänger) ist sortiert wenn jeder Knoten einen Schlüssel enthält und für jeden Knoten k gilt:

- jeder Schlüssel im linken Unterbaum von k ist kleiner als der Schlüssel von k und - jeder Schlüssel im rechten Unterbaum von k ist größer als der Schlüssel von k.

Def.: Ein binärer Baum (mit Doppelgängern) ist sortiert wenn jeder Knoten einen Schlüssel enthält und für jeden Knoten k gilt:

- jeder Schlüssel im linken Unterbaum von k ist kleiner-oder-gleich dem Schlüssel von k und - jeder Schlüssel im rechten Unterbaum von k ist größer als der Schlüssel von k.

Gleiche Schlüssel im linken und im rechten Unterbaum zu erlauben wäre keine besonders gute Idee und würde das Suchen unnötig erschweren. Dagegen macht es keinen wichtigen Unterschied, ob man

gleiche Schlüssel nur im linken Unterbaum erlaubt oder nur im rechten. In diesem Projekt soll die obige Definition gelten ("gleiche Schlüssel nur im linken Unterbaum").

Die Knoten eines binären Baumes kann man sich auf sogenannten Ebenen angeordnet vorstellen, die man mit 0 beginnend nummeriert, wie in der nebenstehenden Abbildung:

Def.: Die Tiefe eines Baums ist gleich der Anzahl der Ebenen, auf denen sich Knoten befinden.

Ein leerer Baum hat die Tiefe 0. Ein Baum der Tiefe 1 hat genau 1 Knoten.

Ein Baum der Tiefe 2 hat 2 oder 3 Knoten. Ein Baum der Tiefe 3 hat zwischen 3 und 7 Knoten etc.

Ebene 0

Ebene 1

Ebene 2

... ... ...

(21)

Beuth Hochschule Projekt 5: Sammeln in einem binären Baum WS17/18, S. 21 2. Aufgaben

Aufgabe-01: Fügen Sie die folgenden Schlüssel (in der angegebenen Reihenfolge) in einen anfangs leeren, sortierten binären Baum ein:

50, 30, 70, 10, 60, 80, 40

Aufgabe-02: Fügen Sie die gleichen Schlüssel, aber in der folgenden Reihenfolge in einen anfangs leeren, sortierten binären Baum ein:

30, 70, 60, 40, 10, 50, 80

Anmerkung 1: Zwei sortierte Reihungen, die die gleichen Zahlen (Schlüssel) enthalten, sehen "genau gleich" aus. Zwei sortierte Bäume, die die gleichen Zahlen (Schlüssel) enthalten, können sehr

unterschiedlich aussehen.

Anmerkung 2: Achten Sie bei den folgenden Aufgaben besonders auf den Unterschied zwischen

"Genau" und "Ungefähr" und den Unterschied zwischen "höchstens" und "mindestens"!

Aufgabe-03: Genau wie viele Knoten können auf den einzelnen Ebenen höchstens stehen?

Füllen Sie die folgende Tabelle aus:

Ebene 0 1 2 3 4 5 6 n

Max. Anzahl

Knoten 1 2

Aufgabe-04: Genau wie viele Knoten kann ein binärer Baum der Tiefe t höchstens enthalten?

Füllen Sie die folgenden Tabelle aus:

Tiefe 1 2 3 4 5 6 7 n

Max. Anzahl

Knoten 1 3

Aufgabe-05: Ungefähr wie viele Knoten können auf bestimmten Ebenen höchstens stehen?

Geben Sie jeweils die Ziffer 1 und ein deutsches Zahlwort an.

Ebene 10 20 30 40 50 60

Max. Anzahl

Knoten 1 Tausend 1 Million

Aufgabe-06: Ungefähr wie viele Knoten können auf bestimmten Ebene höchstens stehen?

Geben Sie jeweils die Ziffer 1 und ein amerikanisches Zahlwort an.

Ebene 10 20 30 40 50 60

Max. Anzahl

Knoten 1 thousend 1 million

Aufgabe-07: Ungefähr wie viele Knoten kann ein binärer Baum der Tiefe t höchstens enthalten?

Geben Sie jeweils die Ziffer 1 und ein deutsches Zahlwort an.

Tiefe 10 20 30 40 50 60

Max. Anzahl Knoten

Aufgabe-08: Genau welche Tiefe hat ein binärer Baum mit a Knoten mindestens?

(22)

S. 22, WS17/18 2. Aufgaben Beuth-Hochschule Füllen Sie die folgende Tabelle aus:

Anzahl a

der Knoten 1 Tausend 1 Million 1 Milliarde 1 Billion 1 Billiarde 1 Trillion Mindest-

Tiefe

Aufgabe-09: Genau welche Tiefe hat ein binärer Baum mit a Knoten mindestens?

Füllen Sie die folgende Tabelle aus:

Anzahl a

der Knoten 8 Tausend 2 Million 16 Milliarde 4 Billion 64 Billiarde 32 Trillion Mindest-

Tiefe

Aufgabe-10: Welche Tiefe hat ein Baum mit 1 Tausend Knoten höchstens?

Referenzen

ÄHNLICHE DOKUMENTE

In diesem Abschnitt soll gezeigt werden, dass es manchmal noch einen einfacheren Weg gibt, um eine weitere Ordnung für eine Klasse K zu definieren: Wenn man schon ein oder

Wenn dieser (Referenz-) Wert auf ein Objekt zeigt (d.h. wenn er ungleich null ist), kann man die Übergabe auch so beschreiben: Das Objekt wird per Referenz übergeben (d.h. die

The array object ar (the target-value of the variable ar) contains 3 elements and additionally an int- variable named length with value 3. In all arrays this length -variable

Die Variable lob zeigt auf ein LongSpeicher30-Objekt (das lange Rechteck &#34;von ganz links nach ganz rechts&#34;).. Dieses Objekt ist (zur Zeit) eine leere Liste von

Hinweise: Solange man keine Doppelgänger einfügt, sehen Sammlungen des Typs LongSpeicher50 und LongSpeicher51 genau gleich aus.. Nr Bezeichnung

Welche Knoten haben einen leeren linken Unterbaum (und gehören somit zum Fall 2).. Welche Knoten haben einen leeren rechten Unterbaum (und gehören somit zum

This class offers constant time performance for the basic operations (add, remove, contains and size), assuming the hash function disperses the elements properly among the

Für die 14 Beispiel-Schlüssel ist hash03 nicht besonders gut, denn sie lässt 5 der 10 Listen leer und bewirkt, dass 8 der 14 Schlüssel in dieselbe Liste (ht[5]) eingefügt werden..