java\02_Algorithmen\ (Start mit java Aufgabe_ALG_NN_k.java)
| Lücke | Inhalt |
|---|---|
| (1) | 0 |
| (2) | raeder.size() - 1 |
| (3) | r.getAkkuStand() < 25 und nicht r.isDefekt() |
| (4) | anzahl := anzahl + 1 |
| (5) | anzahl |
funktion zaehleLadebedarf(raeder : List<Fahrrad>) : integer
anzahl : integer := 0
zähle i von 0 bis raeder.size() - 1
r : Fahrrad := raeder.get(i)
wenn r.getAkkuStand() < 25 und nicht r.isDefekt() dann
anzahl := anzahl + 1
Ende wenn
Ende zähle
rückgabe anzahl
Ende funktion
Die Funktion liefert 3 (Räder 102 mit 15 %, 104 mit 22 % und 107 mit 8 %). Rad 110 hat 28 % (nicht unter 25), Rad 103 ist defekt, wird aber schon wegen 60 % nicht gezählt.
Java · Aufgabe_ALG_01_1.java (Auszug)
// Lösung ALG-01.1: zählt einsatzfähige (nicht defekte) Räder mit Akkustand unter 25 %
static int zaehleLadebedarf(List<Fahrrad> raeder) {
int anzahl = 0; // Lücke 1
for (int i = 0; i <= raeder.size() - 1; i++) { // Lücke 2
Fahrrad r = raeder.get(i);
if (r.getAkkuStand() < 25 && !r.isDefekt()) { // Lücke 3
anzahl = anzahl + 1; // Lücke 4
}
}
return anzahl; // Lücke 5
}
Ausgabe
zaehleLadebedarf(Testdaten) = 3 Betroffene Räder: 102 104 107 Leere Liste -> 0
r.isDefekt() = false, Java-Schreibweise anzahl++, i < raeder.size() bei Java-Schleifen. b) 1 P für Wert und Radnummern.Erklärung: Klassisches Zählmuster. „unter 25 %“ heißt <, nicht <=. Die Schleife läuft bis size() - 1, weil der letzte gültige Index eins kleiner als die Anzahl ist.
funktion einsatzbereit(raeder : List<Fahrrad>, typ : string,
minAkku : integer) : List<Fahrrad>
ergebnis : List<Fahrrad> := new List<Fahrrad>()
zähle i von 0 bis raeder.size() - 1
r : Fahrrad := raeder.get(i)
wenn r.getTyp() = typ und nicht r.isDefekt() und r.getAkkuStand() >= minAkku dann
ergebnis.add(r)
Ende wenn
Ende zähle
rückgabe ergebnis
Ende funktion
funktion durchschnittsStrecke(ausleihen : List<Ausleihe>, stationNr : integer) : double
summe : double := 0.0
anzahl : integer := 0
zähle i von 0 bis ausleihen.size() - 1
a : Ausleihe := ausleihen.get(i)
wenn a.getStartStation() = stationNr dann
summe := summe + a.getStrecke()
anzahl := anzahl + 1
Ende wenn
Ende zähle
wenn anzahl = 0 dann
rückgabe 0.0
Ende wenn
rückgabe summe / anzahl
Ende funktion
Java · Aufgabe_ALG_01_2.java (Auszug)
// a) alle einsatzbereiten Räder eines Typs mit Mindest-Akkustand in eine neue Liste
static List<Fahrrad> einsatzbereit(List<Fahrrad> raeder, String typ, int minAkku) {
List<Fahrrad> ergebnis = new ArrayList<>();
for (int i = 0; i < raeder.size(); i++) {
Fahrrad r = raeder.get(i);
if (r.getTyp().equals(typ) && !r.isDefekt() && r.getAkkuStand() >= minAkku) {
ergebnis.add(r);
}
}
return ergebnis;
}
// b) durchschnittliche Strecke aller Ausleihen, die an einer Station beginnen
static double durchschnittsStrecke(List<Ausleihe> ausleihen, int stationNr) {
double summe = 0.0;
int anzahl = 0;
for (int i = 0; i < ausleihen.size(); i++) {
Ausleihe a = ausleihen.get(i);
if (a.getStartStation() == stationNr) {
summe = summe + a.getStrecke();
anzahl = anzahl + 1;
}
}
if (anzahl == 0) {
return 0.0; // keine Division durch 0
}
return summe / anzahl;
}
Ausgabe
a) einsatzbereit(raeder, "E-Bike", 30): Rad 101 (E-Bike, Akku 85 %) Rad 105 (E-Bike, Akku 95 %) Rad 109 (E-Bike, Akku 55 %) Rad 111 (E-Bike, Akku 90 %) Rad 112 (E-Bike, Akku 33 %) einsatzbereit(raeder, "Lastenrad", 30): [Rad 106 (Lastenrad, Akku 40 %)] b) durchschnittsStrecke je Startstation: Station 1: 4,6000 km Station 2: 2,1000 km Station 3: 6,9667 km Station 4: 5,0000 km Station 5: 10,7500 km Station 6: 7,9000 km Station 7: 0,0000 km
>= minAkku) 2 P, add und Rückgabe 1 P. b) Summe und Zähler initialisiert 1 P, Schleife 1 P, Bedingung mit Aufsummieren und Zählen 1 P, Behandlung „keine Ausleihe“ 1 P, Rückgabe des Durchschnitts 1 P. In Java ist der Stringvergleich mit equals korrekt; im Pseudocode wird = akzeptiert (Anlage 5).Erklärung: a) ist das Filtermuster – die Originalliste bleibt unverändert, weil nur gelesen und in eine neue Liste geschrieben wird. b) kombiniert Summe und Zähler; ohne die Abfrage anzahl = 0 würde für eine Station ohne Ausleihen durch 0 geteilt (in Java mit double ergibt das NaN, also ein sinnloses Ergebnis ohne Fehlermeldung).
funktion langeFahrtenJeStation(ausleihen : List<Ausleihe>,
anzahlStationen : integer) : integer[]
ergebnis : integer[] := new integer[anzahlStationen] // alle Elemente sind 0
zähle i von 0 bis ausleihen.size() - 1
a : Ausleihe := ausleihen.get(i)
wenn a.getDauer() > 60 dann
index : integer := a.getStartStation() - 1 // Station 1 -> Index 0
ergebnis[index] := ergebnis[index] + 1
Ende wenn
Ende zähle
rückgabe ergebnis
Ende funktion
anzahlStationen Elementen angelegt (mit 0 belegt) 1 P, Schleife über alle Ausleihen 1 P, Bedingung > 60 1 P, Index startStation - 1 2 P, Rückgabe 1 P. Wer das Array mit anzahlStationen + 1 Elementen anlegt und Index 0 ungenutzt lässt, erfüllt die Vorgabe „Index i für Station i + 1“ nicht (−2 P).funktion erzeugeStatistik(ausleihen : List<Ausleihe>) : List<KundenStatistik>
statistik : List<KundenStatistik> := new List<KundenStatistik>()
zähle i von 0 bis ausleihen.size() - 1
kundenNr : integer := ausleihen.get(i).getKundenNr()
vorhanden : boolean := false
zähle j von 0 bis statistik.size() - 1
wenn statistik.get(j).getKundenNr() = kundenNr dann
vorhanden := true
Ende wenn
Ende zähle
wenn nicht vorhanden dann
statistik.add(new KundenStatistik(kundenNr))
Ende wenn
Ende zähle
rückgabe statistik
Ende funktion
Gleichwertige Alternative mit Hilfsliste: nummern : List<integer> mitführen und mit wenn nicht nummern.contains(kundenNr) dann nummern.add(kundenNr); statistik.add(new KundenStatistik(kundenNr)) arbeiten.
contains) 4 P, Objekt mit Konstruktor erzeugen und hinzufügen 2 P, Rückgabe 1 P. Nicht korrekt: statistik.contains(new KundenStatistik(kundenNr)) – ein neu erzeugtes Objekt ist nie in der Liste enthalten (Anlage 2: Objekte sind nur gleich, wenn es dasselbe Objekt ist), dadurch entstehen Duplikate (−3 P).funktion berechneStatistik(ausleihen : List<Ausleihe>, statistik : List<KundenStatistik>,
von : Datum, bis : Datum) : void
zähle i von 0 bis statistik.size() - 1
ks : KundenStatistik := statistik.get(i)
anzahl : integer := 0 // für jeden Kunden neu
summe : integer := 0
zähle j von 0 bis ausleihen.size() - 1
a : Ausleihe := ausleihen.get(j)
wenn a.getKundenNr() = ks.getKundenNr()
und a.getDatum().liegtZwischen(von, bis) dann
anzahl := anzahl + 1
summe := summe + a.getDauer()
Ende wenn
Ende zähle
ks.setAnzahl(anzahl)
ks.setGesamtDauer(summe)
wenn anzahl > 0 dann
ks.setDurchschnitt(summe / anzahl) // Kommazahl! Java: (double) summe / anzahl
sonst
ks.setDurchschnitt(0.0)
Ende wenn
Ende zähle
Ende funktion
statistik 2 P, Zähler und Summe je Kunde (innerhalb der äußeren Schleife) auf 0 gesetzt 2 P, innere Schleife über ausleihen 2 P, Bedingung Kundennummer 1 P, Zeitraum mit liegtZwischen 2 P, Zählen und Aufsummieren 2 P, Werte per Setter gesetzt 1 P, Durchschnitt mit Behandlung „0 Ausleihen“ 2 P. Gleichwertig: ein Durchlauf über die Ausleihen, bei dem jeweils das passende Statistikobjekt gesucht und hochgezählt wird, danach eine Schleife für die Durchschnitte.Java · Aufgabe_ALG_01_3.java (Auszug)
// a) Anzahl der Fahrten über 60 Minuten je Startstation; Index = stationNr - 1
static int[] langeFahrtenJeStation(List<Ausleihe> ausleihen, int anzahlStationen) {
int[] ergebnis = new int[anzahlStationen]; // alle Elemente sind 0
for (int i = 0; i < ausleihen.size(); i++) {
Ausleihe a = ausleihen.get(i);
if (a.getDauer() > 60) {
int index = a.getStartStation() - 1;
ergebnis[index] = ergebnis[index] + 1;
}
}
return ergebnis;
}
// b) für jede Kundennummer genau ein KundenStatistik-Objekt (Werte noch 0)
static List<KundenStatistik> erzeugeStatistik(List<Ausleihe> ausleihen) {
List<KundenStatistik> statistik = new ArrayList<>();
for (int i = 0; i < ausleihen.size(); i++) {
int kundenNr = ausleihen.get(i).getKundenNr();
boolean vorhanden = false;
int j = 0;
while (j < statistik.size() && !vorhanden) {
if (statistik.get(j).getKundenNr() == kundenNr) {
vorhanden = true;
}
j = j + 1;
}
if (!vorhanden) {
statistik.add(new KundenStatistik(kundenNr));
}
}
return statistik;
}
// c) Werte je Kunde für den Zeitraum von..bis berechnen und setzen
static void berechneStatistik(List<Ausleihe> ausleihen, List<KundenStatistik> statistik,
Datum von, Datum bis) {
for (int i = 0; i < statistik.size(); i++) {
KundenStatistik ks = statistik.get(i);
int anzahl = 0; // für jeden Kunden neu!
int summe = 0;
for (int j = 0; j < ausleihen.size(); j++) {
Ausleihe a = ausleihen.get(j);
if (a.getKundenNr() == ks.getKundenNr()
&& a.getDatum().liegtZwischen(von, bis)) {
anzahl = anzahl + 1;
summe = summe + a.getDauer();
}
}
ks.setAnzahl(anzahl);
ks.setGesamtDauer(summe);
if (anzahl > 0) {
ks.setDurchschnitt((double) summe / anzahl); // keine Ganzzahldivision
} else {
ks.setDurchschnitt(0.0);
}
}
}
Ausgabe
a) langeFahrtenJeStation(ausleihen, 6) = [0, 0, 1, 0, 1, 1] b) erzeugeStatistik: 5 Objekte Kunde 5001: anzahl=0, gesamtDauer=0, durchschnitt=0,00 Kunde 5002: anzahl=0, gesamtDauer=0, durchschnitt=0,00 Kunde 5003: anzahl=0, gesamtDauer=0, durchschnitt=0,00 Kunde 5004: anzahl=0, gesamtDauer=0, durchschnitt=0,00 Kunde 5005: anzahl=0, gesamtDauer=0, durchschnitt=0,00 c) berechneStatistik(..., 2026-09-16, 2026-09-17): Kunde 5001: anzahl=1, gesamtDauer=26, durchschnitt=26,00 Kunde 5002: anzahl=0, gesamtDauer=0, durchschnitt=0,00 Kunde 5003: anzahl=2, gesamtDauer=98, durchschnitt=49,00 Kunde 5004: anzahl=1, gesamtDauer=33, durchschnitt=33,00 Kunde 5005: anzahl=1, gesamtDauer=9, durchschnitt=9,00 Kontrolle Gesamtzeitraum 2026-09-14 bis 2026-09-17: Kunde 5001: anzahl=3, gesamtDauer=66, durchschnitt=22,00 Kunde 5002: anzahl=2, gesamtDauer=76, durchschnitt=38,00 Kunde 5003: anzahl=3, gesamtDauer=173, durchschnitt=57,67 Kunde 5004: anzahl=2, gesamtDauer=128, durchschnitt=64,00 Kunde 5005: anzahl=2, gesamtDauer=40, durchschnitt=20,00
Erklärung: Die Aufgabe kombiniert drei Grundmuster: a) Ergebnis-Array „je Station“ (wie W24 „je Strecke“), b) eindeutige Liste (wie S26 a), c) verschachtelte Schleifen mit Summe/Durchschnitt und Zeitraumfilter (wie S26 b). Die häufigsten Punktverluste: Zähler und Summe nur einmal vor der äußeren Schleife initialisiert (dann summieren sich die Werte über alle Kunden auf), Ganzzahldivision (98 / 2 ginge gut, 173 / 3 würde in Java 57 statt 57,67 ergeben) und Division durch 0 bei Kunde 5002, der im Zeitraum nichts ausgeliehen hat.
a) (je 1 P, zwei davon) Ein Array hat eine feste Größe, die beim Anlegen festgelegt wird; eine Liste wächst und schrumpft dynamisch. · Bei Arrays greift man mit feld[i] zu und fragt die Länge mit length ab, bei Listen über Methoden (get(i), size()). · Listen bieten Methoden zum Einfügen und Entfernen (add, remove), beim Array muss man dafür umkopieren. · In Java kann ein Array primitive Typen (int) speichern, eine Liste nur Objekte (Integer).
b) kleinster Index 0, größter Index 23. (1 P)
c) Index 24 existiert nicht. Java löst zur Laufzeit eine ArrayIndexOutOfBoundsException aus; wird sie nicht behandelt, bricht das Programm ab. Das Array wird nicht automatisch vergrößert. (1 P)
d) 7 · 24 = 168 Elemente; Zugriff: zaehler[4][17]. (1 P)
Java · Aufgabe_ALG_02_1.java (Auszug)
public static void main(String[] args) {
int[] belegung = new int[24];
System.out.println("belegung.length = " + belegung.length
+ ", kleinster Index = 0, größter Index = " + (belegung.length - 1));
try {
belegung[24] = 5;
} catch (ArrayIndexOutOfBoundsException e) {
System.out.println("belegung[24] = 5 -> " + e.getClass().getSimpleName());
System.out.println(" Meldung: " + e.getMessage());
}
int[][] zaehler = new int[7][24];
System.out.println("zaehler: " + zaehler.length + " Zeilen x " + zaehler[0].length
+ " Spalten = " + (zaehler.length * zaehler[0].length) + " Elemente");
zaehler[4][17] = 12; // Freitag (Index 4), 17-Uhr-Stunde
System.out.println("zaehler[4][17] = " + zaehler[4][17]);
List<Integer> liste = new ArrayList<>();
liste.add(7);
liste.add(3);
liste.add(9);
System.out.println("Liste wächst dynamisch: size() = " + liste.size()
+ " " + liste);
}
Ausgabe
belegung.length = 24, kleinster Index = 0, größter Index = 23 belegung[24] = 5 -> ArrayIndexOutOfBoundsException Meldung: Index 24 out of bounds for length 24 zaehler: 7 Zeilen x 24 Spalten = 168 Elemente zaehler[4][17] = 12 Liste wächst dynamisch: size() = 3 [7, 3, 9]
funktion summeJeTag(daten : integer[][]) : integer[]
summen : integer[] := new integer[daten.length]
zähle tag von 0 bis daten.length - 1
summe : integer := 0
zähle f von 0 bis daten[tag].length - 1
summe := summe + daten[tag][f]
Ende zähle
summen[tag] := summe
Ende zähle
rückgabe summen
Ende funktion
funktion summeJeZeitfenster(daten : integer[][]) : integer[]
anzahlFenster : integer := daten[0].length
summen : integer[] := new integer[anzahlFenster]
zähle f von 0 bis anzahlFenster - 1
summe : integer := 0
zähle tag von 0 bis daten.length - 1
summe := summe + daten[tag][f]
Ende zähle
summen[f] := summe
Ende zähle
rückgabe summen
Ende funktion
Alternative für b): Ergebnis-Array anlegen und in beliebiger Schleifenreihenfolge summen[f] := summen[f] + daten[tag][f] rechnen.
summeJeTag = [57, 58, 61, 63, 68, 68, 55] · summeJeZeitfenster = [17, 73, 74, 95, 126, 45] (Kontrolle: beide ergeben zusammen 430 Ausleihen).
Java · Aufgabe_ALG_02_2.java (Auszug)
// a) Summe je Zeile (= je Wochentag)
static int[] summeJeTag(int[][] daten) {
int[] summen = new int[daten.length];
for (int tag = 0; tag < daten.length; tag++) {
int summe = 0;
for (int f = 0; f < daten[tag].length; f++) {
summe = summe + daten[tag][f];
}
summen[tag] = summe;
}
return summen;
}
// b) Summe je Spalte (= je Zeitfenster)
static int[] summeJeZeitfenster(int[][] daten) {
int anzahlFenster = daten[0].length;
int[] summen = new int[anzahlFenster];
for (int f = 0; f < anzahlFenster; f++) {
int summe = 0;
for (int tag = 0; tag < daten.length; tag++) {
summe = summe + daten[tag][f];
}
summen[f] = summe;
}
return summen;
}
Ausgabe
a) summeJeTag = [57, 58, 61, 63, 68, 68, 55] b) summeJeZeitfenster = [17, 73, 74, 95, 126, 45] Kontrolle: Gesamtsumme über Zeilen = 430, über Spalten = 430
Erklärung: Für Spaltensummen läuft die äußere Schleife über die Spalten (daten[0].length), die innere über die Zeilen (daten.length). Der häufigste Fehler ist, beide Grenzen mit daten.length zu bilden – bei 7 × 6 führt das zu einem Indexfehler.
funktion genutzteRaeder(ausleihen : List<Ausleihe>) : List<integer>
ergebnis : List<integer> := new List<integer>()
zähle i von 0 bis ausleihen.size() - 1
radNr : integer := ausleihen.get(i).getRadNr()
wenn nicht ergebnis.contains(radNr) dann
ergebnis.add(radNr)
Ende wenn
Ende zähle
rückgabe ergebnis
Ende funktion
[101, 105, 106, 104, 110, 109, 111, 107, 112, 102] – 10 Radnummern; 101 und 106 kommen in den Ausleihen je zweimal vor, stehen aber nur einmal in der Liste.
funktion entferneDefekte(raeder : List<Fahrrad>) : integer
entfernt : integer := 0
i : integer := raeder.size() - 1 // rückwärts durchlaufen
solange i >= 0
wenn raeder.get(i).isDefekt() dann
raeder.remove(i)
entfernt := entfernt + 1
Ende wenn
i := i - 1
Ende solange
rückgabe entfernt
Ende funktion
Gleichwertig vorwärts, wenn i nur erhöht wird, falls nichts entfernt wurde:
i : integer := 0
solange i < raeder.size()
wenn raeder.get(i).isDefekt() dann
raeder.remove(i) // i bleibt: Nachfolger steht jetzt auf Position i
entfernt := entfernt + 1
sonst
i := i + 1
Ende wenn
Ende solange
Für die Testdaten liefert die Funktion 2 (Räder 103 und 108 werden entfernt).
Nach remove(1) (Rad 108) rücken alle folgenden Räder eine Position nach vorn: 103 steht nun auf Index 1, 109 auf Index 2. Die Schleife erhöht i aber auf 2 und prüft als Nächstes 109. Rad 103 wird übersprungen und bleibt in der Liste; das Ergebnis ist [107, 103, 109] mit Rückgabe 1 statt 2. Das passiert immer, wenn zwei zu entfernende Elemente direkt hintereinander stehen.
Java · Aufgabe_ALG_02_3.java (Auszug)
// a) jede Radnummer genau einmal, in der Reihenfolge des ersten Auftretens
static List<Integer> genutzteRaeder(List<Ausleihe> ausleihen) {
List<Integer> ergebnis = new ArrayList<>();
for (int i = 0; i < ausleihen.size(); i++) {
int radNr = ausleihen.get(i).getRadNr();
if (!ergebnis.contains(radNr)) {
ergebnis.add(radNr);
}
}
return ergebnis;
}
// b) defekte Räder entfernen - rückwärts, damit kein Element übersprungen wird
static int entferneDefekte(List<Fahrrad> raeder) {
int entfernt = 0;
for (int i = raeder.size() - 1; i >= 0; i--) {
if (raeder.get(i).isDefekt()) {
raeder.remove(i); // remove(int index)
entfernt = entfernt + 1;
}
}
return entfernt;
}
Ausgabe (inklusive der fehlerhaften Variante aus d)
a) genutzteRaeder = [101, 105, 106, 104, 110, 109, 111, 107, 112, 102] b) entferneDefekte(Testdaten) = 2, Rest: [101, 102, 104, 105, 106, 107, 109, 110, 111, 112] c) richtig (rückwärts) auf [107, 108d, 103d, 109]: entfernt 2 -> [107, 109] falsch (vorwärts, i++) auf [107, 108d, 103d, 109]: entfernt 1 -> [107, 103, 109]
contains 2 P, add und Rückgabe 1 P. b) 1 P (Reihenfolge muss stimmen). c) Zähler 1 P, Durchlauf ohne Überspringen (rückwärts oder bedingtes Erhöhen) 3 P, Prüfung isDefekt() mit remove(i) 2 P, Rückgabe 1 P. Eine Lösung, die eine neue Liste ohne defekte Räder zurückgibt, erfüllt die Aufgabe nicht (übergebene Liste soll verändert werden, Rückgabe ist die Anzahl) – höchstens 3 P. d) Index-Verschiebung erkannt 2 P, am Beispiel konkret erläutert 1 P.a) Die Funktion liefert 0 (richtig wäre 15, Rad 102). (1 P)
b) min wird mit 0 initialisiert. Da kein Akkustand kleiner als 0 ist, ist die Bedingung raeder[i].getAkkuStand() < min nie erfüllt; die Funktion gibt immer den Startwert 0 zurück. Außerdem wird raeder[0] nie betrachtet, weil die Schleife bei 1 beginnt – das ist nur richtig, wenn das erste Element als Startwert dient. (2 P)
c) min : integer := raeder[0].getAkkuStand() (1 P)
d) Das Array muss mindestens ein Element enthalten (darf nicht leer bzw. null sein), sonst führt der Zugriff auf raeder[0] zu einem Laufzeitfehler. (1 P)
Java · Aufgabe_ALG_03_1.java (Auszug)
// gegebene, FEHLERHAFTE Funktion
static int minAkkuFalsch(Fahrrad[] raeder) {
int min = 0;
for (int i = 1; i <= raeder.length - 1; i++) {
if (raeder[i].getAkkuStand() < min) {
min = raeder[i].getAkkuStand();
}
}
return min;
}
// korrigierte Funktion: Initialisierung mit dem ersten Element
static int minAkku(Fahrrad[] raeder) {
int min = raeder[0].getAkkuStand();
for (int i = 1; i <= raeder.length - 1; i++) {
if (raeder[i].getAkkuStand() < min) {
min = raeder[i].getAkkuStand();
}
}
return min;
}
Ausgabe
fehlerhaft: minAkkuFalsch(101..105) = 0 korrigiert: minAkku(101..105) = 15 leeres Array -> ArrayIndexOutOfBoundsException (Vorbedingung: mindestens 1 Element)
funktion findeRad(raeder : List<Fahrrad>, radNr : integer) : Fahrrad
gefunden : Fahrrad := null
i : integer := 0
solange i < raeder.size() und gefunden = null
wenn raeder.get(i).getRadNr() = radNr dann
gefunden := raeder.get(i)
Ende wenn
i := i + 1
Ende solange
rückgabe gefunden // null, wenn nicht vorhanden
Ende funktion
Gleichwertig: Zählschleife mit rückgabe raeder.get(i) direkt beim Treffer und rückgabe null nach der Schleife.
funktion vollstesRad(raeder : List<Fahrrad>, typ : string) : Fahrrad
bestes : Fahrrad := null
zähle i von 0 bis raeder.size() - 1
r : Fahrrad := raeder.get(i)
wenn r.getTyp() = typ und nicht r.isDefekt() dann
wenn bestes = null oder r.getAkkuStand() > bestes.getAkkuStand() dann
bestes := r
Ende wenn
Ende wenn
Ende zähle
rückgabe bestes
Ende funktion
Java · Aufgabe_ALG_03_2.java (Auszug)
// a) lineare Suche mit Abbruch; null, wenn nicht gefunden
static Fahrrad findeRad(List<Fahrrad> raeder, int radNr) {
Fahrrad gefunden = null;
int i = 0;
while (i < raeder.size() && gefunden == null) {
if (raeder.get(i).getRadNr() == radNr) {
gefunden = raeder.get(i);
}
i = i + 1;
}
return gefunden;
}
// b) nicht defektes Rad eines Typs mit dem höchsten Akkustand; null, wenn keines passt
static Fahrrad vollstesRad(List<Fahrrad> raeder, String typ) {
Fahrrad bestes = null;
for (int i = 0; i < raeder.size(); i++) {
Fahrrad r = raeder.get(i);
if (r.getTyp().equals(typ) && !r.isDefekt()) {
if (bestes == null || r.getAkkuStand() > bestes.getAkkuStand()) {
bestes = r;
}
}
}
return bestes;
}
Ausgabe
a) findeRad(raeder, 109) = Rad 109 (E-Bike, Akku 55 %) findeRad(raeder, 200) = null b) vollstesRad(raeder, "Lastenrad") = Rad 106 (Lastenrad, Akku 40 %) vollstesRad(raeder, "E-Bike") = Rad 105 (E-Bike, Akku 95 %) vollstesRad(raeder, "Tandem") = null
null bei Nichtfinden 1 P. b) Startwert null (bzw. erstes passendes Rad) 1 P, Bedingung Typ und nicht defekt 1 P, Maximumvergleich 2 P, Rückgabe des Objekts 1 P.Erklärung: Bei b) ist die Falle der Startwert: Mit raeder.get(0) zu starten wäre falsch, weil Rad 101 ein E-Bike ist und bei der Suche nach Lastenrädern nicht in Frage kommt. Das defekte Lastenrad 103 hat zwar den höchsten Akkustand (60 %), richtig ist aber 106 (40 %).
d = √((6,0 − 4,0)² + (4,0 − 3,0)²) = √(4 + 1) = √5 ≈ 2,24 km
funktion naechsteStation(stationen : List<Station>, kx : double, ky : double,
benoetigt : integer) : Station
beste : Station := null
minEntfernung : double := 0.0 // erst gültig, wenn beste <> null
zähle i von 0 bis stationen.size() - 1
s : Station := stationen.get(i)
verfuegbar : integer := 0 // je Station neu zählen
zähle j von 0 bis s.getRaeder().size() - 1
r : Fahrrad := s.getRaeder().get(j)
wenn nicht r.isDefekt() und r.getAkkuStand() >= 30 dann
verfuegbar := verfuegbar + 1
Ende wenn
Ende zähle
wenn verfuegbar >= benoetigt dann
e : double := entfernung(kx, ky, s.getX(), s.getY())
wenn beste = null oder e < minEntfernung dann
beste := s
minEntfernung := e
Ende wenn
Ende wenn
Ende zähle
rückgabe beste // null, wenn keine Station passt
Ende funktion
getRaeder() 2 P, Bedingung „einsatzbereit“ (nicht defekt und ≥ 30 %) 2 P, Vergleich >= benoetigt 1 P, Entfernung mit entfernung(…) 1 P, Minimumlogik mit korrektem Startwert 2 P, Station merken und zurückgeben (null, wenn keine passt) 2 P.| Station | Entfernung | einsatzbereite Räder | kommt in Frage? |
|---|---|---|---|
| 1 Hauptbahnhof | 2,83 km | 1 (101) | nein |
| 2 Marktplatz | 0,71 km | 1 (105; 104 hat nur 22 %) | nein |
| 3 Uni-Campus | 2,24 km | 1 (106; 107 zu leer, 108 defekt) | nein |
| 4 Hafen | 3,91 km | 1 (109) | nein |
| 5 Klinikum | 4,12 km | 0 (110 hat nur 28 %) | nein |
| 6 Seepark | 4,74 km | 2 (111, 112) | ja |
Die Funktion liefert Station 6 Seepark (4,74 km). Die nächstgelegene Station 2 Marktplatz (0,71 km) hat nur ein einsatzbereites Rad und erfüllt die Bedingung „mindestens 2“ nicht; nur am Seepark stehen zwei einsatzbereite Räder.
Entfernungen sind nie negativ. Mit minEntfernung := 0 wäre e < minEntfernung nie erfüllt (bei einer Bedingung ohne beste = null) – es würde nie eine Station gemerkt und die Funktion gäbe immer null zurück. Geeignete Alternativen: einen Merker verwenden (beste = null oder e < minEntfernung, wie oben), mit einem sehr großen Wert starten (z. B. Double.MAX_VALUE bzw. größer als jede mögliche Entfernung) oder die erste passende Station als Startwert nehmen.
Java · Aufgabe_ALG_03_3.java (Auszug)
/** Hilfsfunktion aus Anlage 5: Luftlinie in km. */
static double entfernung(double x1, double y1, double x2, double y2) {
return Math.sqrt((x2 - x1) * (x2 - x1) + (y2 - y1) * (y2 - y1));
}
// b) nächstgelegene Station mit mindestens "benoetigt" einsatzbereiten Rädern
static Station naechsteStation(List<Station> stationen, double kx, double ky,
int benoetigt) {
Station beste = null;
double minEntfernung = 0.0; // wird erst gültig, wenn beste != null
for (int i = 0; i < stationen.size(); i++) {
Station s = stationen.get(i);
int verfuegbar = 0;
for (int j = 0; j < s.getRaeder().size(); j++) {
Fahrrad r = s.getRaeder().get(j);
if (!r.isDefekt() && r.getAkkuStand() >= 30) {
verfuegbar = verfuegbar + 1;
}
}
if (verfuegbar >= benoetigt) {
double e = entfernung(kx, ky, s.getX(), s.getY());
if (beste == null || e < minEntfernung) {
beste = s;
minEntfernung = e;
}
}
}
return beste;
}
Ausgabe
a) Entfernung Kunde (4,0|3,0) - Station 3 (6,0|4,0) = 2,24 km Übersicht je Station: Station 1 Hauptbahnhof Entfernung 2,83 km, einsatzbereit: 1 Station 2 Marktplatz Entfernung 0,71 km, einsatzbereit: 1 Station 3 Uni-Campus Entfernung 2,24 km, einsatzbereit: 1 Station 4 Hafen Entfernung 3,91 km, einsatzbereit: 1 Station 5 Klinikum Entfernung 4,12 km, einsatzbereit: 0 Station 6 Seepark Entfernung 4,74 km, einsatzbereit: 2 b/c) naechsteStation(stationen, 4.0, 3.0, 1) = Station 2 Marktplatz b/c) naechsteStation(stationen, 4.0, 3.0, 2) = Station 6 Seepark b/c) naechsteStation(stationen, 4.0, 3.0, 3) = null
Erklärung: Typischer „nächster Nachbar mit Bedingung“ wie in W23 – zusätzlich muss je Station in einer inneren Schleife gezählt werden. Weil die erste Station die Bedingung nicht erfüllen muss, ist der Merker beste = null der sicherste Startwert.
| Einheit | Anzahl Byte |
|---|---|
| 1 KiB | 2¹⁰ = 1 024 |
| 1 kB | 10³ = 1 000 |
| 1 MiB | 2²⁰ = 1 048 576 |
| 1 GB | 10⁹ = 1 000 000 000 |
202 : 2 = 101 Rest 0 101 : 2 = 50 Rest 1 50 : 2 = 25 Rest 0 25 : 2 = 12 Rest 1 12 : 2 = 6 Rest 0 6 : 2 = 3 Rest 0 3 : 2 = 1 Rest 1 1 : 2 = 0 Rest 1 Reste von unten nach oben lesen 202 (dezimal) = 1100 1010 (binär) Hex: 1100 = C, 1010 = A -> 202 = CA (hex) Probe: 12 · 16 + 10 = 202
7E (hex) = 7 · 16 + 14 = 126; 1011 0110 (binär) = 128 + 32 + 16 + 4 + 2 = 182.
2¹² = 4 096 verschiedene Werte (vorzeichenlos 0 bis 4 095).
Kontrollrechnung mit Java · Aufgabe_ALG_04_1.java · Ausgabe
a) 1 KiB = 1024 Byte, 1 kB = 1000 Byte, 1 MiB = 1048576 Byte, 1 GB = 1000000000 Byte b) 202 dezimal = 11001010 binär = CA hex Divisionsrestverfahren 202: 202 : 2 = 101 Rest 0 101 : 2 = 50 Rest 1 50 : 2 = 25 Rest 0 25 : 2 = 12 Rest 1 12 : 2 = 6 Rest 0 6 : 2 = 3 Rest 0 3 : 2 = 1 Rest 1 1 : 2 = 0 Rest 1 c) 7E hex = 126 dezimal, 10110110 binär = 182 dezimal d) 12 Bit -> 2^12 = 4096 verschiedene Werte (0 bis 4095)
| Feld | Wertebereich | Datentyp | Byte | Begründung |
|---|---|---|---|---|
| ausleihNr | 1 bis 3 000 000 | int | 4 | größer als 32 767 (short) |
| kundenNr | 1 bis 200 000 | int | 4 | größer als 32 767 |
| radNr | 1 bis 5 000 | short | 2 | größer als 127, kleiner als 32 767 |
| startStation | 1 bis 60 | byte | 1 | passt in −128 … 127 |
| startZeit | ca. 1,8 · 10¹² | long | 8 | größer als 2,1 · 10⁹ (int) |
| dauer | 0 bis 1 440 | short | 2 | größer als 127 |
| strecke | Kommazahl, einfache Genauigkeit | float | 4 | einfache Genauigkeit reicht |
| preisCent | 0 bis 50 000 | int | 4 | größer als 32 767 |
Datensatz = 4 + 4 + 2 + 1 + 8 + 2 + 4 + 4 = 29 Byte
pro Jahr = 29 Byte · 2 400 Datensätze/Tag · 365 Tage
= 25 404 000 Byte
in MiB = 25 404 000 : 1 024 : 1 024 ≈ 24,23 MiB
Kontrollrechnung mit Java · Aufgabe_ALG_04_2.java · Ausgabe
ausleihNr int 4 Byte kundenNr int 4 Byte radNr short 2 Byte startStation byte 1 Byte startZeit long 8 Byte dauer short 2 Byte strecke float 4 Byte preisCent int 4 Byte a/b) Datensatz = 29 Byte Wertebereiche: byte -128..127, short -32768..32767 pro Jahr = 29 * 2400 * 365 = 25404000 Byte = 24,2271 MiB (zum Vergleich 25,4040 MB)
strecke double (8 Byte) wählt, verliert in a) 0,5 P; b) wird dann mit 33 Byte und 27,57 MiB als Folgerechnung anerkannt.Erklärung: short reicht bis 32 767, byte bis 127 (vorzeichenbehaftet). Ein Zeitstempel in Millisekunden passt längst nicht mehr in int – das ist der Grund, warum Java System.currentTimeMillis() als long liefert.
Pixel je Foto = 4 000 · 3 000 = 12 000 000 Pixel Bit je Foto = 12 000 000 Pixel · 3 Kanäle · 8 Bit = 288 000 000 Bit Byte je Foto = 288 000 000 Bit : 8 = 36 000 000 Byte MiB je Foto = 36 000 000 : 1 024 : 1 024 ≈ 34,33 MiB Fotos pro Jahr = 2 Fotos · 2 400 Rückgaben/Tag · 365 Tage = 1 752 000 Fotos Byte pro Jahr = 36 000 000 Byte · 1 752 000 = 63 072 000 000 000 Byte TiB pro Jahr = 63 072 000 000 000 : 1 024⁴ ≈ 57,36 TiB
Auch der Weg über den gerundeten Wert ist richtig: 34,33 MiB · 1 752 000 = 60 146 160 MiB; : 1 024 : 1 024 ≈ 57,36 TiB.
komprimiert, 2 Jahre = 63 072 000 000 000 Byte · 2 : 15 = 8 409 600 000 000 Byte
= 8,41 TB (SI, wie die Herstellerangabe) ≈ 7,65 TiB
1 Festplatte "8 TB" = 8 000 000 000 000 Byte ≈ 7,28 TiB
Anzahl = 8,41 TB : 8 TB = 1,05 -> aufrunden = 2 Festplatten
Falle: Wer 7,65 TiB mit „8 TB“ vergleicht, kommt fälschlich auf eine Platte – es muss in derselben Einheit verglichen werden (8,41 TB > 8 TB bzw. 7,65 TiB > 7,28 TiB).
Der Hersteller rechnet mit SI-Präfixen: 8 TB = 8 · 10¹² Byte. Das Betriebssystem rechnet binär (1 TiB = 2⁴⁰ = 1 099 511 627 776 Byte), beschriftet den Wert aber mit „TB“: 8 · 10¹² : 2⁴⁰ ≈ 7,28 TiB (angezeigt abgeschnitten bzw. abzüglich Dateisystem-Verwaltungsdaten ≈ 7,27). Es fehlt also kein Speicher, nur die Einheit ist eine andere.
Kontrollrechnung mit Java · Aufgabe_ALG_04_3.java · Ausgabe
a) Pixel pro Bild = 12000000 Bit pro Bild = 288000000 Byte pro Bild = 36000000 MiB pro Bild = 34,3323 Bilder pro Jahr = 1752000 Byte pro Jahr = 63072000000000 TiB pro Jahr = 57,3636 b) JPEG 1:15, 2 Jahre = 8409600000000 Byte = 8,4096 TB = 7,6485 TiB 8 TB = 7,2760 TiB benötigte Platten = 2 (Falsch gerechnet mit TiB-Wert gegen 8: 7,65 / 8 -> 1 Platte)
Erklärung: Gleiches Schema wie W23 (GiB) und S26 (TiB): erst ein Bild in Bit, dann Byte, dann Hochrechnung, zuletzt Umrechnung. Die zwei Kameras je Rückgabe werden am häufigsten vergessen.
| Durchlauf | Inhalt nach dem Durchlauf | Vertauschungen |
|---|---|---|
| 1 | [17, 8, 33, 25, 42] | 4 (42↔17, 42↔8, 42↔33, 42↔25) |
| 2 | [8, 17, 25, 33, 42] | 2 (17↔8, 33↔25) |
| 3 | [8, 17, 25, 33, 42] | 0 → Abbruch |
| 4 | – | – |
Bubblesort hat im schlechtesten Fall die Zeitkomplexität O(n²). Besser: Mergesort (O(n log n)), Heapsort (O(n log n)) oder Quicksort (im Mittel O(n log n)).
Kontrolle mit Java · Aufgabe_ALG_05_1.java · Ausgabe
Start: [42, 17, 8, 33, 25] nach Durchlauf 1: [17, 8, 33, 25, 42] Vertauschungen: 4 nach Durchlauf 2: [8, 17, 25, 33, 42] Vertauschungen: 2 nach Durchlauf 3: [8, 17, 25, 33, 42] Vertauschungen: 0 Abbruch, weil im letzten Durchlauf nicht getauscht wurde.
funktion sortiereNachAkku(raeder : Fahrrad[]) : void
n : integer := raeder.length
zähle i von 0 bis n - 2
zähle j von 0 bis n - 2 - i
wenn raeder[j].getAkkuStand() > raeder[j + 1].getAkkuStand() dann
hilf : Fahrrad := raeder[j] // ganzes Objekt tauschen
raeder[j] := raeder[j + 1]
raeder[j + 1] := hilf
Ende wenn
Ende zähle
Ende zähle
Ende funktion
funktion sortiereNachAkku(raeder : Fahrrad[]) : void
n : integer := raeder.length
durchlauf : integer := 0
getauscht : boolean := true // neu
solange getauscht // geändert (statt zähle i …)
getauscht := false // neu
zähle j von 0 bis n - 2 - durchlauf
wenn raeder[j].getAkkuStand() > raeder[j + 1].getAkkuStand() dann
hilf : Fahrrad := raeder[j]
raeder[j] := raeder[j + 1]
raeder[j + 1] := hilf
getauscht := true // neu
Ende wenn
Ende zähle
durchlauf := durchlauf + 1
Ende solange
Ende funktion
Ein Sortierverfahren ist stabil, wenn Elemente mit gleichem Sortierschlüssel nach dem Sortieren in derselben Reihenfolge stehen wie vorher. Das Verfahren oben ist stabil, weil nur bei > getauscht wird: Räder mit gleichem Akkustand werden nie gegeneinander vertauscht. (Mit >= wäre es instabil.)
Java · Aufgabe_ALG_05_2.java (Auszug, Variante b)
// a/b) Bubblesort aufsteigend nach Akkustand, mit vorzeitigem Abbruch
static void sortiereNachAkku(Fahrrad[] raeder) {
int n = raeder.length;
boolean getauscht = true;
int durchlauf = 0;
while (getauscht) {
getauscht = false;
for (int j = 0; j <= n - 2 - durchlauf; j++) {
if (raeder[j].getAkkuStand() > raeder[j + 1].getAkkuStand()) {
Fahrrad hilf = raeder[j];
raeder[j] = raeder[j + 1];
raeder[j + 1] = hilf;
getauscht = true;
}
}
durchlauf = durchlauf + 1;
}
}
Ausgabe
Ladereihenfolge (aufsteigend nach Akkustand): Rad 107 (E-Bike, Akku 8 %) Rad 102 (E-Bike, Akku 15 %) Rad 104 (E-Bike, Akku 22 %) Rad 110 (Lastenrad, Akku 28 %) Rad 112 (E-Bike, Akku 33 %) Rad 106 (Lastenrad, Akku 40 %) Rad 109 (E-Bike, Akku 55 %) Rad 103 (Lastenrad, Akku 60 %, defekt) Rad 108 (E-Bike, Akku 71 %, defekt) Rad 101 (E-Bike, Akku 85 %) Rad 111 (E-Bike, Akku 90 %) Rad 105 (E-Bike, Akku 95 %)
funktion sortiere(ausleihen : Ausleihe[], vergleiche : Function) : void
zähle i von 0 bis ausleihen.length - 2
zähle j von 0 bis ausleihen.length - 2 - i
wenn vergleiche(ausleihen[j], ausleihen[j + 1]) > 0 dann
hilf : Ausleihe := ausleihen[j]
ausleihen[j] := ausleihen[j + 1]
ausleihen[j + 1] := hilf
Ende wenn
Ende zähle
Ende zähle
Ende funktion
funktion nachDauerAbsteigend(a : Ausleihe, b : Ausleihe) : integer
wenn a.getDauer() > b.getDauer() dann
rückgabe -1 // längere Ausleihe gehört nach vorn
Ende wenn
wenn a.getDauer() < b.getDauer() dann
rückgabe 1
Ende wenn
rückgabe 0
Ende funktion
Kurzform (ebenfalls richtig): rückgabe b.getDauer() - a.getDauer().
funktion nachStationDannPreis(a : Ausleihe, b : Ausleihe) : integer
wenn a.getStartStation() <> b.getStartStation() dann
rückgabe a.getStartStation() - b.getStartStation() // aufsteigend
Ende wenn
wenn a.getPreis() > b.getPreis() dann // gleiche Station: Preis absteigend
rückgabe -1
Ende wenn
wenn a.getPreis() < b.getPreis() dann
rückgabe 1
Ende wenn
rückgabe 0
Ende funktion
Reihenfolge der Ausleihnummern: 11, 1, 9, 2, 3, 8, 4, 6, 5, 12, 7, 10.
Die Ausleihen 1 und 9 beginnen beide an Station 1 und kosten beide 0,00 € – die Vergleichsfunktion liefert 0. Ein stabiles Verfahren lässt sie in der ursprünglichen Reihenfolge (1 vor 9). Bei einem instabilen Verfahren könnte auch 9 vor 1 stehen.
Java · Aufgabe_ALG_05_3.java (Auszug)
// b) generischer Bubblesort: die Reihenfolge bestimmt allein die Vergleichsfunktion
static void sortiere(Ausleihe[] ausleihen, Comparator<Ausleihe> vergleiche) {
for (int i = 0; i < ausleihen.length - 1; i++) {
for (int j = 0; j < ausleihen.length - 1 - i; j++) {
if (vergleiche.compare(ausleihen[j], ausleihen[j + 1]) > 0) {
Ausleihe hilf = ausleihen[j];
ausleihen[j] = ausleihen[j + 1];
ausleihen[j + 1] = hilf;
}
}
}
}
// c1) nach Dauer absteigend
static int nachDauerAbsteigend(Ausleihe a, Ausleihe b) {
if (a.getDauer() > b.getDauer()) {
return -1;
} else if (a.getDauer() < b.getDauer()) {
return 1;
}
return 0;
}
// c2) nach Startstation aufsteigend, bei gleicher Station nach Preis absteigend
static int nachStationDannPreis(Ausleihe a, Ausleihe b) {
if (a.getStartStation() != b.getStartStation()) {
return a.getStartStation() - b.getStartStation();
}
if (a.getPreis() > b.getPreis()) {
return -1;
} else if (a.getPreis() < b.getPreis()) {
return 1;
}
return 0;
}
Ausgabe
sortiere(feld, nachDauerAbsteigend): Ausleihe 5: Kunde 5004, Rad 110, Station 5 -> 4, 2026-09-15, 95 min, 12,40 km, 6,50 EUR Ausleihe 3: Kunde 5003, Rad 106, Station 3 -> 5, 2026-09-14, 75 min, 9,80 km, 7,50 EUR Ausleihe 7: Kunde 5002, Rad 111, Station 6 -> 3, 2026-09-15, 64 min, 8,70 km, 13,80 EUR Ausleihe 12: Kunde 5003, Rad 106, Station 5 -> 3, 2026-09-17, 58 min, 9,10 km, 5,80 EUR Ausleihe 8: Kunde 5003, Rad 107, Station 3 -> 3, 2026-09-16, 40 min, 6,20 km, 4,00 EUR Ausleihe 10: Kunde 5004, Rad 112, Station 6 -> 1, 2026-09-16, 33 min, 7,10 km, 0,30 EUR Ausleihe 6: Kunde 5005, Rad 109, Station 4 -> 2, 2026-09-15, 31 min, 5,00 km, 7,20 EUR Ausleihe 9: Kunde 5001, Rad 101, Station 1 -> 6, 2026-09-16, 26 min, 7,30 km, 0,00 EUR Ausleihe 4: Kunde 5001, Rad 104, Station 3 -> 1, 2026-09-14, 22 min, 4,90 km, 0,00 EUR Ausleihe 1: Kunde 5001, Rad 101, Station 1 -> 3, 2026-09-14, 18 min, 4,60 km, 0,00 EUR Ausleihe 2: Kunde 5002, Rad 105, Station 2 -> 1, 2026-09-14, 12 min, 2,10 km, 3,40 EUR Ausleihe 11: Kunde 5005, Rad 102, Station 1 -> 2, 2026-09-17, 9 min, 1,90 km, 2,80 EUR sortiere(feld, nachStationDannPreis): Ausleihe 11: Kunde 5005, Rad 102, Station 1 -> 2, 2026-09-17, 9 min, 1,90 km, 2,80 EUR Ausleihe 1: Kunde 5001, Rad 101, Station 1 -> 3, 2026-09-14, 18 min, 4,60 km, 0,00 EUR Ausleihe 9: Kunde 5001, Rad 101, Station 1 -> 6, 2026-09-16, 26 min, 7,30 km, 0,00 EUR Ausleihe 2: Kunde 5002, Rad 105, Station 2 -> 1, 2026-09-14, 12 min, 2,10 km, 3,40 EUR Ausleihe 3: Kunde 5003, Rad 106, Station 3 -> 5, 2026-09-14, 75 min, 9,80 km, 7,50 EUR Ausleihe 8: Kunde 5003, Rad 107, Station 3 -> 3, 2026-09-16, 40 min, 6,20 km, 4,00 EUR Ausleihe 4: Kunde 5001, Rad 104, Station 3 -> 1, 2026-09-14, 22 min, 4,90 km, 0,00 EUR Ausleihe 6: Kunde 5005, Rad 109, Station 4 -> 2, 2026-09-15, 31 min, 5,00 km, 7,20 EUR Ausleihe 5: Kunde 5004, Rad 110, Station 5 -> 4, 2026-09-15, 95 min, 12,40 km, 6,50 EUR Ausleihe 12: Kunde 5003, Rad 106, Station 5 -> 3, 2026-09-17, 58 min, 9,10 km, 5,80 EUR Ausleihe 7: Kunde 5002, Rad 111, Station 6 -> 3, 2026-09-15, 64 min, 8,70 km, 13,80 EUR Ausleihe 10: Kunde 5004, Rad 112, Station 6 -> 1, 2026-09-16, 33 min, 7,10 km, 0,30 EUR Kontrolle mit Java-Comparator, Reihenfolge der Ausleihnummern: 11 1 9 2 3 8 4 6 5 12 7 10
> 0 2 P, Tausch 2 P, kein fest verdrahtetes Attribut 1 P (andere Verfahren wie Selection- oder Insertionsort ebenfalls volle Punkte). b) je Fall (größer, kleiner, gleich) 1 P. c) Stationsvergleich 2 P, Preisvergleich absteigend 2 P, Gleichheit 1 P. d) Reihenfolge 2 P, Stabilität erläutert 1 P.Erklärung: Der Sortieralgorithmus kennt kein Attribut mehr – er fragt nur „steht a hinter b?“ (> 0). Dadurch genügt ein getesteter Algorithmus für beliebig viele Sortierkriterien. In Java entspricht die Vergleichsfunktion einem Comparator<Ausleihe>, der z. B. als Methodenreferenz Aufgabe_ALG_05_3::nachDauerAbsteigend oder als Lambda übergeben wird.
| Ausdruck | Ergebnis |
|---|---|
| code.length() | 9 |
| code.charAt(3) | '0' (Zeichen) |
| code.substring(3, 7) | "0107" |
| code.indexOf('-') | 2 |
Strings sind Objekte. == vergleicht bei Objekten die Referenzen, also ob beide Variablen auf dasselbe Objekt im Speicher zeigen. Zwei inhaltsgleiche Strings (z. B. einer aus einer Benutzereingabe, einer im Programm) sind meist verschiedene Objekte – == liefert dann false. equals() vergleicht dagegen den Inhalt Zeichen für Zeichen. (Dass == bei zwei gleichen Literalen manchmal zufällig true liefert, liegt am String-Pool und ist nicht verlässlich.)
Kontrolle mit Java · Aufgabe_ALG_06_1.java · Ausgabe
code.length() = 9
code.charAt(3) = '0'
code.substring(3, 7) = "0107"
code.indexOf('-') = 2
a == b -> false (vergleicht Referenzen)
a.equals(b) -> true (vergleicht Inhalte)
funktion pruefePasswort(pw : string) : boolean
wenn pw.length() < 10 dann
rückgabe false
Ende wenn
sonderzeichen : string := "!?#+-_*"
gross : boolean := false
klein : boolean := false
ziffer : boolean := false
sonder : boolean := false
zähle i von 0 bis pw.length() - 1
c : char := pw.charAt(i)
wenn c = ' ' dann
rückgabe false // Leerzeichen verboten
Ende wenn
wenn istGrossbuchstabe(c) dann
gross := true
Ende wenn
wenn istKleinbuchstabe(c) dann
klein := true
Ende wenn
wenn istZiffer(c) dann
ziffer := true
Ende wenn
wenn sonderzeichen.indexOf(c) >= 0 dann
sonder := true
Ende wenn
Ende zähle
rückgabe gross und klein und ziffer und sonder // erst NACH der Schleife auswerten
Ende funktion
| Passwort | Ergebnis | Grund |
|---|---|---|
| Rad2026! | false | nur 8 Zeichen |
| auenrad#2026 | false | kein Großbuchstabe |
| Auenrad#2026 | true | alle Regeln erfüllt |
| Auen Rad#2026 | false | enthält ein Leerzeichen |
Java · Aufgabe_ALG_06_2.java (Auszug)
// a) Passwortrichtlinie der Auenrad-App
static boolean pruefePasswort(String pw) {
if (pw.length() < 10) {
return false;
}
String sonderzeichen = "!?#+-_*";
boolean gross = false;
boolean klein = false;
boolean ziffer = false;
boolean sonder = false;
for (int i = 0; i <= pw.length() - 1; i++) {
char c = pw.charAt(i);
if (c == ' ') {
return false;
} else if (istGrossbuchstabe(c)) {
gross = true;
} else if (istKleinbuchstabe(c)) {
klein = true;
} else if (istZiffer(c)) {
ziffer = true;
} else if (sonderzeichen.indexOf(c) >= 0) {
sonder = true;
}
}
return gross && klein && ziffer && sonder;
}
Ausgabe
pruefePasswort("Rad2026!") = false
pruefePasswort("auenrad#2026") = false
pruefePasswort("Auenrad#2026") = true
pruefePasswort("Auen Rad#2026") = false
pruefePasswort("AUENRAD#2026x") = true
pruefePasswort("Auenrad2026") = false
< 10 → false, also „mindestens 10“) 1 P, Schleife über alle Zeichen (0 bis length() - 1) 1 P, je Zeichenklasse Merker korrekt initialisiert und gesetzt 4 × 1 P, Leerzeichenprüfung 1 P, Gesamtergebnis nach der Schleife 1 P. b) je Passwort 0,5 P.Erklärung: „Mindestens ein …“ verlangt Merker, die erst nach der Schleife ausgewertet werden. Genau diese Fehler steckten im Struktogramm von W21: Länge mit > statt >= geprüft, Schleife bis ≤ length und ein Merker, der im sonst-Zweig wieder auf false gesetzt wurde.
funktion zerlegeName(vollname : string) : string[]
ergebnis : string[] := new string[2]
pos : integer := vollname.indexOf(',')
wenn pos = -1 dann // kein Komma
ergebnis[0] := ""
ergebnis[1] := vollname.trim()
sonst
ergebnis[1] := vollname.substring(0, pos).trim() // Nachname
ergebnis[0] := vollname.substring(pos + 1, vollname.length()).trim() // Vorname
Ende wenn
rückgabe ergebnis
Ende funktion
funktion pruefeRadCode(code : string) : boolean
wenn code.length() <> 9 dann
rückgabe false
Ende wenn
wenn code.substring(0, 3) <> "AR-" oder code.charAt(7) <> '-' dann
rückgabe false
Ende wenn
summe : integer := 0
zähle i von 3 bis 6
c : char := code.charAt(i)
wenn nicht istZiffer(c) dann
rückgabe false
Ende wenn
summe := summe + zahlwert(c)
Ende zähle
p : char := code.charAt(8)
wenn nicht istZiffer(p) dann
rückgabe false
Ende wenn
rückgabe zahlwert(p) = summe mod 10
Ende funktion
Java · Aufgabe_ALG_06_3.java (Auszug)
// a) "Nachname, Vorname" -> [Vorname, Nachname]
static String[] zerlegeName(String vollname) {
String[] ergebnis = new String[2];
int pos = vollname.indexOf(',');
if (pos == -1) {
ergebnis[0] = "";
ergebnis[1] = vollname.trim();
} else {
ergebnis[1] = vollname.substring(0, pos).trim();
ergebnis[0] = vollname.substring(pos + 1, vollname.length()).trim();
}
return ergebnis;
}
// b) Format "AR-" + 4 Ziffern + "-" + Prüfziffer (= Summe der 4 Ziffern mod 10)
static boolean pruefeRadCode(String code) {
if (code.length() != 9) {
return false;
}
if (!code.substring(0, 3).equals("AR-") || code.charAt(7) != '-') {
return false;
}
int summe = 0;
for (int i = 3; i <= 6; i++) {
char c = code.charAt(i);
if (!istZiffer(c)) {
return false;
}
summe = summe + zahlwert(c);
}
char p = code.charAt(8);
if (!istZiffer(p)) {
return false;
}
return zahlwert(p) == summe % 10;
}
Ausgabe
zerlegeName("Schulze-Ebert, Anna Lena") -> Vorname "Anna Lena", Nachname "Schulze-Ebert"
zerlegeName("Haas,Tom") -> Vorname "Tom", Nachname "Haas"
zerlegeName(" Nowak , Piotr ") -> Vorname "Piotr", Nachname "Nowak"
zerlegeName("Müller") -> Vorname "", Nachname "Müller"
pruefeRadCode("AR-0107-8") = true
pruefeRadCode("AR-0107-7") = false
pruefeRadCode("AR-01O7-8") = false
pruefeRadCode("AR-107-8") = false
pruefeRadCode("AR-0959-3") = true
pruefeRadCode("XR-0107-8") = false
substring(0, pos) 1 P, Vorname ab pos + 1 1 P, trim 1 P, Array angelegt und zurückgegeben 1 P. b) Länge 1 P, „AR-“ und Bindestrich an Position 7 1 P, Schleife über die vier Ziffern mit Ziffernprüfung 2 P, Summe und Vergleich der Prüfziffer mit mod 10 2 P. c) je sinnvollem Problem 1 P.Erklärung: Bei substring(von, bis) ist bis ausgeschlossen – substring(0, pos) endet also genau vor dem Komma. In Java muss der Präfixvergleich mit equals erfolgen (!code.substring(0, 3).equals("AR-")).
a) Checked Exceptions erben von Exception (aber nicht von RuntimeException). Der Compiler erzwingt, dass sie gefangen (try/catch) oder mit throws in der Methodensignatur weitergereicht werden. Sie beschreiben äußere Umstände, die auch korrekter Code nicht verhindern kann, z. B. IOException, SQLException. Unchecked Exceptions erben von RuntimeException; der Compiler verlangt keine Behandlung. Sie entstehen meist durch Programmierfehler, z. B. NullPointerException, ArithmeticException, IllegalArgumentException.
b) Der finally-Block wird immer ausgeführt, nachdem der try-Block (und ggf. ein catch-Block) beendet ist – egal, ob eine Ausnahme aufgetreten ist, ob sie gefangen wurde und auch bei einem return im try-Block. Typischer Einsatz: Ressourcen freigeben, z. B. Datei oder Datenbankverbindung schließen, Sperre freigeben.
| Aufruf | Ausgabe | Rückgabewert / Ablauf |
|---|---|---|
| centProMinute("750", 30) | A B E F | 25 (750 / 30, kein Fehler) |
| centProMinute("7,50", 30) | A C E F | −1: parseInt wirft NumberFormatException, ergebnis behält den Startwert |
| centProMinute("750", 0) | A D E F | 0: Ganzzahldivision durch 0 wirft ArithmeticException, der catch-Block setzt 0 |
| centProMinute("750", -5) | A E | kein Rückgabewert: die IllegalArgumentException wird von keinem catch-Block gefangen; nach dem finally-Block (E) wird sie an den Aufrufer weitergereicht, „F“ und return werden nicht mehr ausgeführt |
Kontrolle mit Java · Aufgabe_ALG_07_2.java · Ausgabe (der Aufrufer fängt die Ausnahme des 4. Aufrufs ab, um sie anzuzeigen)
1) centProMinute("750", 30): Ausgabe A B E F | Rückgabe 25
2) centProMinute("7,50", 30): Ausgabe A C E F | Rückgabe -1
3) centProMinute("750", 0): Ausgabe A D E F | Rückgabe 0
4) centProMinute("750", -5): Ausgabe A E | keine Rückgabe
-> Ausnahme beim Aufrufer: IllegalArgumentException: Minuten negativ
Der Vorschlag ist schlecht. Ein leerer Block catch (Exception e) { } verschluckt jeden Fehler: Es gibt weder Meldung noch Protokolleintrag, und der Aufrufer rechnet mit −1 weiter, als wäre nichts passiert. Außerdem fängt er auch Ausnahmen ab, die gar nicht hier behandelt werden sollen (die IllegalArgumentException bei negativen Minuten, Programmierfehler wie NullPointerException), sodass Fehler verdeckt werden. Eine unterschiedliche Reaktion (Division durch 0 → 0, Formatfehler → −1) ist nicht mehr möglich. Besser: gezielt die erwarteten Ausnahmen fangen, sinnvoll reagieren und protokollieren.
ausleihen ist null (Aufrufer hat keine Daten geladen) → Zugriff führt zu NullPointerException.funktion durchschnittsDauer(ausleihen : List<Ausleihe>, stationNr : integer) : double
wenn ausleihen = null dann
throw new IllegalArgumentException("Liste der Ausleihen fehlt")
Ende wenn
wenn stationNr < 1 dann
throw new IllegalArgumentException("Ungültige Stationsnummer: " + stationNr)
Ende wenn
summe : integer := 0
anzahl : integer := 0
zähle i von 0 bis ausleihen.size() - 1
a : Ausleihe := ausleihen.get(i)
wenn a.getStartStation() = stationNr dann
summe := summe + a.getDauer()
anzahl := anzahl + 1
Ende wenn
Ende zähle
wenn anzahl = 0 dann
throw new KeineDatenException("Keine Ausleihen für Station " + stationNr)
Ende wenn
rückgabe summe / anzahl // Kommazahl! Java: (double) summe / anzahl
Ende funktion
funktion zeigeStationsbericht(ausleihen : List<Ausleihe>, stationNr : integer) : void
try
d : double := durchschnittsDauer(ausleihen, stationNr)
ausgabe("Station " + stationNr + ": durchschnittliche Dauer " + d + " min")
catch (KeineDatenException e)
ausgabe("Für diese Station liegen keine Ausleihen vor.")
catch (IllegalArgumentException e)
protokolliere(e.getMessage())
Ende try
Ende funktion
Java · Aufgabe_ALG_07_3.java (Auszug) – in Java muss die geprüfte Ausnahme mit throws deklariert werden
/** eigene, geprüfte (checked) Ausnahme */
static class KeineDatenException extends Exception {
private static final long serialVersionUID = 1L;
KeineDatenException(String meldung) {
super(meldung);
}
}
// b) durchschnittliche Dauer der Ausleihen, die an stationNr beginnen
static double durchschnittsDauer(List<Ausleihe> ausleihen, int stationNr)
throws KeineDatenException {
if (ausleihen == null) {
throw new IllegalArgumentException("Liste der Ausleihen fehlt (null)");
}
if (stationNr < 1) {
throw new IllegalArgumentException("Ungültige Stationsnummer: " + stationNr);
}
int summe = 0;
int anzahl = 0;
for (int i = 0; i < ausleihen.size(); i++) {
Ausleihe a = ausleihen.get(i);
if (a.getStartStation() == stationNr) {
summe = summe + a.getDauer();
anzahl = anzahl + 1;
}
}
if (anzahl == 0) {
throw new KeineDatenException("Keine Ausleihen für Station " + stationNr);
}
return (double) summe / anzahl;
}
// c) Aufrufer: fängt die Ausnahmen und reagiert benutzerfreundlich
static void zeigeStationsbericht(List<Ausleihe> ausleihen, int stationNr) {
try {
double d = durchschnittsDauer(ausleihen, stationNr);
System.out.printf(Locale.GERMANY, "Station %d: durchschnittliche Dauer %.1f min%n",
stationNr, d);
} catch (KeineDatenException e) {
System.out.println("Station " + stationNr
+ ": Für diese Station liegen keine Ausleihen vor.");
} catch (IllegalArgumentException e) {
System.out.println("FEHLER-LOG: " + e.getMessage());
}
}
Ausgabe
Station 3: durchschnittliche Dauer 45,7 min Station 4: durchschnittliche Dauer 31,0 min Station 7: Für diese Station liegen keine Ausleihen vor. FEHLER-LOG: Ungültige Stationsnummer: 0 FEHLER-LOG: Liste der Ausleihen fehlt (null)
null und < 1 mit throw 2 P, Schleife mit Summe und Anzahl 2 P, KeineDatenException bei Anzahl 0 (vor der Division) 2 P, Rückgabe als Kommazahl 1 P. c) Aufruf und Ausgabe im try-Block 1 P, catch für KeineDatenException mit Meldung 1 P, catch für IllegalArgumentException mit protokolliere(e.getMessage()) 1 P, korrekte Struktur (Ausgabe nur im Erfolgsfall) 1 P.Erklärung: Die Prüfungen am Anfang („Wächter“) brechen bei Vertragsverletzungen sofort ab (fail fast). Die fachliche Situation „keine Daten“ wird als eigene, geprüfte Ausnahme gemeldet; der Aufrufer muss sich darum kümmern und kann eine verständliche Meldung anzeigen, statt eine falsche Zahl (0 oder NaN) auszugeben.
a) Der Basisfall ist die Abbruchbedingung: Für ihn liefert die Funktion ihr Ergebnis direkt, ohne sich selbst erneut aufzurufen (z. B. 0! = 1). Im Rekursionsfall ruft sich die Funktion mit einem kleineren bzw. einfacheren Teilproblem selbst auf und setzt aus dessen Ergebnis ihr eigenes zusammen (z. B. n! = n · (n − 1)!). Der Rekursionsfall muss sich dem Basisfall nähern.
b) Die Funktion ruft sich endlos selbst auf. Jeder Aufruf legt einen neuen Eintrag (Parameter, lokale Variablen, Rücksprungadresse) auf dem Aufrufstapel ab, bis der Stack-Speicher erschöpft ist. Java bricht dann mit einem StackOverflowError ab.
| Aufruf | n | n div 2 | n mod 2 | Rückgabewert |
|---|---|---|---|---|
| 1 | 13 | 6 | 1 | binaer(6) + "1" = "110" + "1" = "1101" |
| 2 | 6 | 3 | 0 | binaer(3) + "0" = "11" + "0" = "110" |
| 3 | 3 | 1 | 1 | binaer(1) + "1" = "1" + "1" = "11" |
| 4 | 1 | 0 | 1 | binaer(0) + "1" = "" + "1" = "1" |
| 5 | 0 | – | – | "" (Basisfall) |
| 6 | – (nicht benötigt) | |||
Für n = 0 liefert die Funktion den leeren Text "" statt "0". Korrektur: Der Basisfall gilt für n < 2 und gibt die Ziffer selbst zurück.
funktion binaer(n : integer) : string
wenn n < 2 dann
rückgabe "" + n // "0" bzw. "1"
Ende wenn
rückgabe binaer(n div 2) + (n mod 2)
Ende funktion
Achtung: Nur den Basisfall auf wenn n = 0 dann rückgabe "0" zu ändern, wäre falsch – dann entstünde für jede Zahl eine führende Null (binaer(13) = "01101").
Java · Aufgabe_ALG_08_2.java (Auszug, korrigierte Funktion)
// korrigierte Funktion: Basisfall n < 2 liefert die Ziffer selbst
static String binaerKorrigiert(int n) {
if (n < 2) {
return "" + n;
}
return binaerKorrigiert(n / 2) + (n % 2);
}
Ausgabe (mit Aufrufprotokoll der ursprünglichen Funktion)
a) Schreibtischtest binaer(13):
binaer(13) aufgerufen
binaer(6) aufgerufen
binaer(3) aufgerufen
binaer(1) aufgerufen
binaer(0) aufgerufen
binaer(0) liefert ""
binaer(1) liefert "1" (n div 2 = 0, n mod 2 = 1)
binaer(3) liefert "11" (n div 2 = 1, n mod 2 = 1)
binaer(6) liefert "110" (n div 2 = 3, n mod 2 = 0)
binaer(13) liefert "1101" (n div 2 = 6, n mod 2 = 1)
Ergebnis: "1101"
binaer(0) aufgerufen
binaer(0) liefert ""
b) binaer(0) = "" <- falsch, erwartet "0"
binaerKorrigiert(0) = "0" Kontrolle: 0
binaerKorrigiert(1) = "1" Kontrolle: 1
binaerKorrigiert(2) = "10" Kontrolle: 10
binaerKorrigiert(13) = "1101" Kontrolle: 1101
binaerKorrigiert(45) = "101101" Kontrolle: 101101
binaerKorrigiert(255) = "11111111" Kontrolle: 11111111
funktion suche(nummern : integer[], gesucht : integer,
links : integer, rechts : integer) : integer
wenn links > rechts dann
rückgabe -1 // Basisfall 1: Suchbereich leer
Ende wenn
mitte : integer := (links + rechts) div 2
wenn nummern[mitte] = gesucht dann
rückgabe mitte // Basisfall 2: gefunden
Ende wenn
wenn nummern[mitte] < gesucht dann
rückgabe suche(nummern, gesucht, mitte + 1, rechts) // rechte Hälfte
sonst
rückgabe suche(nummern, gesucht, links, mitte - 1) // linke Hälfte
Ende wenn
Ende funktion
| Aufruf | links | rechts | mitte | nummern[mitte] | weiter |
|---|---|---|---|---|---|
| 1 | 0 | 11 | 5 | 106 | rechts suchen |
| 2 | 6 | 11 | 8 | 109 | rechts suchen |
| 3 | 9 | 11 | 10 | 111 | rechts suchen |
| 4 | 11 | 11 | 11 | 112 | gefunden → Index 11 |
StackOverflowError, und die Fehlersuche ist schwieriger. (Bei der binären Suche ist die Tiefe mit etwa log₂ n – bei 1 000 Elementen höchstens 10 Ebenen – allerdings unkritisch.)Java · Aufgabe_ALG_08_3.java (Auszug, rekursiv und iterativ)
// a) rekursive binäre Suche in einem aufsteigend sortierten Array; -1 = nicht gefunden
static int suche(int[] nummern, int gesucht, int links, int rechts) {
String info = links <= rechts ? " mitte = " + ((links + rechts) / 2) : " -> links > rechts";
System.out.println(" suche(nummern, " + gesucht + ", " + links + ", " + rechts + ")" + info);
if (links > rechts) {
return -1; // Basisfall 1: Bereich leer
}
int mitte = (links + rechts) / 2;
if (nummern[mitte] == gesucht) {
return mitte; // Basisfall 2: gefunden
} else if (nummern[mitte] < gesucht) {
return suche(nummern, gesucht, mitte + 1, rechts); // rechte Hälfte
} else {
return suche(nummern, gesucht, links, mitte - 1); // linke Hälfte
}
}
// zum Vergleich: iterative Variante
static int sucheIterativ(int[] nummern, int gesucht) {
int links = 0;
int rechts = nummern.length - 1;
while (links <= rechts) {
int mitte = (links + rechts) / 2;
if (nummern[mitte] == gesucht) {
return mitte;
} else if (nummern[mitte] < gesucht) {
links = mitte + 1;
} else {
rechts = mitte - 1;
}
}
return -1;
}
Ausgabe
b) Aufrufe für gesucht = 112: suche(nummern, 112, 0, 11) mitte = 5 suche(nummern, 112, 6, 11) mitte = 8 suche(nummern, 112, 9, 11) mitte = 10 suche(nummern, 112, 11, 11) mitte = 11 Ergebnis: Index 11 Aufrufe für gesucht = 100: suche(nummern, 100, 0, 11) mitte = 5 suche(nummern, 100, 0, 4) mitte = 2 suche(nummern, 100, 0, 1) mitte = 0 suche(nummern, 100, 0, -1) -> links > rechts Ergebnis: Index -1 iterativ: 112 -> 11, 104 -> 3, 100 -> -1 max. Anzahl Halbierungen bei 1000 Elementen: 10
mitte + 1 / mitte - 1) 2 P, Ergebnis des rekursiven Aufrufs zurückgegeben 1 P. b) 2 P für alle vier Aufrufe. c) je Vor- und Nachteil 1 P Nennung + 1 P Begründung.a) Eine Race Condition liegt vor, wenn mehrere nebenläufige Abläufe (Threads, Prozesse, Server) auf dieselbe Ressource zugreifen, mindestens einer davon schreibend, und das Ergebnis von der zufälligen zeitlichen Reihenfolge der Zugriffe abhängt. Folge sind inkonsistente Daten, z. B. verlorene Änderungen oder Doppelbuchungen.
b) (zwei davon) Zwei Kunden reservieren bzw. leihen gleichzeitig dasselbe (letzte) Rad aus. · Zwei Kunden geben gleichzeitig an einer fast vollen Station zurück (Zähler freier Stellplätze). · Station meldet den Akkustand, während die Werkstatt den Datensatz desselben Rades ändert. · Das Kundenkonto wird gleichzeitig in App und Kundenportal geändert (z. B. Guthaben/Abo).
Es entsteht eine Doppelbuchung (Lost Update): Beide Threads lesen bei t1 und t2 den Wert 1, bevor einer von ihnen schreibt. Beide prüfen auf Basis dieses veralteten Werts (t3, t4), beide schreiben 0 und speichern eine Reservierung (t5, t6). Für ein einziges Lastenrad gibt es nun zwei Reservierungen; die Änderung von Thread A wird von B überschrieben, der Bestand müsste eigentlich „−1“ sein. Ursache: Die Folge Lesen – Prüfen – Schreiben ist ein kritischer Abschnitt, der nicht atomar bzw. nicht gegen gleichzeitigen Zugriff geschützt ist.
Beim pessimistischen Sperren sperrt ein Thread den Datensatz vor dem Lesen (z. B. in einer Transaktion mit SELECT … FOR UPDATE, in Java mit synchronized/Lock). Andere Threads, die denselben Datensatz sperren wollen, müssen warten, bis die Sperre mit dem Ende der Transaktion (Commit) freigegeben wird. Dadurch kann immer nur ein Thread den kritischen Abschnitt durchlaufen.
| Zeit | Thread A (Jana) | Thread B (Tom) | DB |
|---|---|---|---|
| t1 | sperrt Datensatz, liest 1 | 1 (gesperrt von A) | |
| t2 | will sperren → wartet | 1 | |
| t3 | prüft 1 > 0 → ja | wartet | 1 |
| t4 | schreibt 0, speichert Reservierung, Commit → Sperre frei | wartet | 0 |
| t5 | erhält Sperre, liest 0 | 0 (gesperrt von B) | |
| t6 | prüft 0 > 0 → nein → „kein Lastenrad frei“, Sperre frei | 0 |
Java · Aufgabe_ALG_09_2.java (Auszug: geschützte Variante)
/** geschützt: kritischer Abschnitt unter einer Sperre (pessimistisch) */
static void reserviereGesperrt(String kunde) throws InterruptedException {
synchronized (SPERRE) {
int frei = freieLastenraeder;
Thread.sleep(50);
if (frei > 0) {
freieLastenraeder = frei - 1;
reservierungen++;
System.out.println(" " + kunde + ": Reservierung bestätigt");
} else {
System.out.println(" " + kunde + ": leider kein Rad frei");
}
}
}
Ausgabe (Reihenfolge der Threadzeilen kann je Lauf variieren)
Ohne Sperre (1 freies Lastenrad, 2 gleichzeitige Anfragen): Thread B (Kunde Tom) : Reservierung bestätigt Thread A (Kundin Jana): Reservierung bestätigt => Reservierungen: 2, freieLastenraeder: 0 Mit Sperre (synchronized): Thread A (Kundin Jana): Reservierung bestätigt Thread B (Kunde Tom) : leider kein Rad frei => Reservierungen: 1, freieLastenraeder: 0
| Variante | Vorteil | Nachteil |
|---|---|---|
| A (pessimistisch, Sperre bei Detailansicht) | Wer die Detailansicht geöffnet hat, kann das Rad sicher reservieren; ein Konflikt beim Speichern ist ausgeschlossen, die Logik ist einfach. | Das Rad ist bis zu 10 Minuten blockiert, auch wenn der Kunde nur schaut oder die App schließt; andere sehen es als „nicht verfügbar“. Viele offene Sperren belasten die Datenbank; Sperren müssen bei Verbindungsabbruch zuverlässig per Timeout aufgehoben werden. |
| B (optimistisch, Versionsnummer) | Keine Blockade: Beliebig viele Kunden können das Rad gleichzeitig ansehen; Sperren dauern nur Millisekunden, das System skaliert gut, keine Deadlocks. | Ein Konflikt fällt erst beim Tippen auf „Reservieren“ auf; der langsamere Kunde erhält eine Fehlermeldung und muss neu wählen. Zusätzliche Versionsspalte und Konfliktbehandlung nötig. |
Empfohlen wird Variante B. Viele Kunden sehen sich Räder an, aber nur selten wollen zwei Kunden in denselben Sekunden dasselbe Lastenrad reservieren – Konflikte sind also selten, und dann ist eine Meldung „leider gerade vergeben“ zumutbar. Variante A hält eine Sperre über eine Benutzerinteraktion von bis zu 10 Minuten; das ist viel zu lang, weil das Rad in dieser Zeit für alle anderen blockiert ist. Sperren sollten nur so lange dauern wie die eigentliche Änderung (Millisekunden) – das leistet die unteilbare Operation in Variante B.
funktion reserviere(db : RadDB, radNr : integer, kundenNr : integer) : boolean
info : RadInfo := db.leseRad(radNr)
wenn info.getStatus() <> "frei" dann
rückgabe false // schon reserviert oder ausgeliehen
Ende wenn
wenn db.setzeStatusWennVersion(radNr, "reserviert", info.getVersion()) dann
db.legeReservierungAn(radNr, kundenNr)
rückgabe true
sonst
rückgabe false // Version geändert: jemand war schneller
Ende wenn
Ende funktion
Java · Aufgabe_ALG_09_3.java (Auszug, mit simulierter Datenbank und zwei gleichzeitigen Threads)
// c) Reservierung mit optimistischem Sperren
static boolean reserviere(RadDB db, int radNr, int kundenNr) {
RadInfo info = db.leseRad(radNr);
if (!info.getStatus().equals("frei")) {
return false; // schon vergeben
}
// ... Kunde bestätigt in der App, Zeit vergeht ...
try { Thread.sleep(30); } catch (InterruptedException e) { }
if (db.setzeStatusWennVersion(radNr, "reserviert", info.getVersion())) {
db.legeReservierungAn(radNr, kundenNr);
return true;
}
return false; // Konflikt: jemand war schneller
}
Ausgabe
Gleichzeitige Reservierungsversuche für Rad 106: 2 davon erfolgreich: 1, abgewiesen (Konflikt): 1 Status danach: reserviert, Version: 8, gespeicherte Reservierungen: 1 Dritter Versuch später: reserviere(...) = false
leseRad 1 P, Statusprüfung 1 P, setzeStatusWennVersion mit der zuvor gelesenen Version 2 P, Reservierung nur bei Erfolg und korrekte Rückgabewerte 1 P.Erklärung: Entscheidend ist, dass Prüfen und Ändern in einer unteilbaren Operation passieren. Wer stattdessen erst den Status liest und dann einfach „reserviert“ schreibt, hat genau die Race Condition aus ALG-09.2. In einer echten Anwendung würden Statusänderung und Anlegen der Reservierung zusätzlich in einer Transaktion zusammengefasst, damit nicht nur eines von beiden gespeichert wird.