Algorithmen und Programmierung II

Übungsblatt 8 - Musterlösung


Aufgabe 1

a) - c)
class Buch {

String autor, titel;
int erscheinungsjahr;
Benutzer leser; // der es gerade ausgeliehen hat
Buch naechstes; // in einer verketteten Liste von Buechern

Buch(String a, String t, int j) {
autor = a;
titel = t;
erscheinungsjahr = j;
}

void ausleihen(Benutzer b) throws Exception {
// verbucht die Ausleihe an b,
// sofern b!=null und das Buch nicht ausgeliehen ist
if (b == null) throw new Exception("kein Benutzer");
if (leser != null) throw new Exception("schon ausgeliehen");
leser = b;
naechstes = b.ausgeliehen;
b.ausgeliehen = this;
}

void zurueckgeben() throws Exception {
// verbucht die Rueckgabe, sofern das Buch ausgeliehen ist
if (leser == null) throw new Exception("war gar nicht ausgeliehen");
Buch b = leser.ausgeliehen;
if (b == this)
leser.ausgeliehen = naechstes; // erstes in der Liste
else {
while (b.naechstes != this) // Vorgänger suchen
b = b.naechstes;
b.naechstes = naechstes;
}
leser = null;
naechstes = null;
}

public String toString() {
// liefert eine String-Repräsentation von diesem und allen
// "naechstes" erreichbaren Büchern
return "\t" + autor + ": " + titel + ". " + erscheinungsjahr +
(naechstes == null ? "": "\n" + naechstes);
}
}

class Benutzer {

int nummer;
String name, adresse;
Buch ausgeliehen; // erstes Buch in einer verketteten Liste
// von ausgeliehenen Buechern - oder null

Benutzer(int nu, String na, String a) {
nummer = nu;
name = na;
adresse = a;
}

public String toString() {
return "Name: " + name + "\nAdresse: " + adresse +
"\nMitgliedsnr: " + nummer + "\nausgeliehene Bücher:\n" +
(ausgeliehen == null ? "\t(keine)\n" : ausgeliehen + "\n");
/* Die Verkettung von Strings auf diese Weise sollte normalerweise
*
vermieden werden. Aus Effizienzgründen wäre hier und oben
*
eigentlich die Verwendung der Klasse StringBuffer vorzuziehen. */
}
}
d) Testmethode (Ausgabe)
    public static void main(String[] args) {
// Testmethode
Buch b1 = new Buch("H. Mössenböck", "Sprechen Sie Java?", 2001);
Buch b2 = new Buch("James Gosling et al",
"The Java Language Specification", 2000);
Buch b3 = new Buch("David Flanagan", "Java in a Nutshell", 2002);

Benutzer n1 = new Benutzer(2309, "Max Mustermann", "Takustr. 9");
Benutzer n2 = new Benutzer(2310, "Torsten Test", "Arnimallee 2-6");

System.out.println("Benutzer ohne Bücher: \n\n" + n1);
try {
b1.ausleihen(n1);
b2.ausleihen(n1);
b3.ausleihen(n1);
System.out.println("Benutzer mit Büchern:\n\n" + n1);
b1.ausleihen(n2);
} catch (Exception e) {
System.out.println("doppeltes Ausleihen: " + e + "\n");
}
try {
b3.zurueckgeben();
System.out.println("vorderstes Buch zurückgegeben:\n\n" + n1);
b1.zurueckgeben();
System.out.println("hinterstes Buch zurückgegeben:\n\n" + n1);
b3.ausleihen(n2);
System.out.println("Buch wieder verleihen:\n\n" + n2);
b1.zurueckgeben();
} catch (Exception e) {
System.out.println("doppeltes Zurückgeben: " + e);
}
}

Aufgabe 2

a)  "herkömmlich"
    static int zeroLineA(float[][] a) throws Exception {
int k;
outer: for (int i = 1; i < a.length; i++) {
for (k = 1; k < a[i].length; k++)
if (a[i][k] != 0) continue outer;
return i;
}
throw new Exception("keine Zeile gefunden");
}
b)  Sentinel-Technik
    static int zeroLineB(float[][] a) throws Exception {
int i, k;
a[0] = new float[1]; // "Sentinel-Zeile"
for (i = a.length-1; ; i--) {
a[i][0] = 1; // Sentinel für aktuelle Zeile
for (k = a[i].length-1; a[i][k] == 0; k--) ;
if (k == 0) break; // Zeile gefunden
}
if (i > 0) return i; // bei i == 0 "Sentinel-Zeile" gefunden
throw new Exception("keine Zeile gefunden");
}
c)  mit Exceptions
    static int zeroLineC(float[][] a) throws Exception {
int i, k;
for (i = 1; ; i++) {
k = 1;
try {
while (a[i][k++] == 0) ;
} catch (IndexOutOfBoundsException e) {
if (i == a.length)
throw new Exception("keine Zeile gefunden");
else
 
return i; // in 0-Zeile über Ende hinaus gelaufen
}
}
}
d)  Einfachheit und gute Verständlichkeit sprechen für Variante a). Hier wird am schnellsten ersichtlich, wie die Methode funktioniert, was bei späterem Lesen und Verifizieren des Codes vorteilhaft ist.

Die Verwendung der Sentinel-Technik kann hier als Optimierung betrachtet werden. Man spart pro Schleifendurchgang den Test, ob sich der Index noch innerhalb der Feldgrenzen befinden. Dies wird sich jedoch erst bei sehr großen Matrizen oder sehr vielen Aufrufen der Methode bemerkbar machen. Optimierungen sollten normalerweise erst dann durchgeführt werden, wenn tatsächlich begründet werden kann, dass eine Optimierung an dieser Stelle einen hinreichend großen Effekt auf die Performanz der gesamten Anwendung hat. Andernfalls ist besser lesbarer Code vorzuziehen.

Das Erkennen von Feldgrenzenüberschreitungen anhand von Ausnahmen ist normalerweise nicht zu empfehlen. Ausnahmen sind nicht dazu gedacht, den Kontrollfluss auf diese Weise zu steuern. Der Code ist recht schlecht lesbar. Die Effizienz ist stark von der Größe der Matrix und der Aufrufhäufigkeit abhängig. Bei einer sehr großen Matrix ist die Variante c) schneller als die beiden anderen Varianten: Das Durchsuchen der Matrix ist sehr effizient, erst am Ende wird das Auftreten einer Ausnahme in Kauf genommen. Das Auftreten und Behandeln einer Ausnahme ist allerdings recht teuer. Werden nur kleine Matrizen verarbeitet und wird die Methode sehr häufig aufgerufen, so überwiegen die Kosten für die Ausnahmebehandlung, und Variante c) ist wesentlich schlechter als die ersten beiden.