Übungsaufgaben zu Rekursion und Iteration
https://bildung.social/@oerinformatik/
https://oer-informatik.de/rekursion-uebungsaufgaben
Rekursion, Iteration, Schreibtischtest, Aufrufstapel, Stapelüberlauf, binäre Suche, Endrekursion, Pseudocode, Übungsaufgaben, Prüfungsvorbereitung
tl/dr; (Bearbeitungszeit insgesamt 84 min, mehrere Aufgaben): Zwei umfangreiche Übungsaufgaben zu Rekursion und Iteration: Schreibtischtest und Fehlersuche bei rekursiven Algorithmen, Abstieg und Aufstieg, Aufrufstapel und Stapelüberlauf, Umwandlung in beide Richtungen, rekursive Funktionen über einen Kategoriebaum, binäre Suche, Aufruftiefe berechnen, Endrekursion und Testabdeckung. Alle Lösungen sind ausgeblendet und mit Punkteverteilung versehen [AI-Assisted]. (Zuletzt geändert am 27.10.2026)
Diese Übungsaufgaben gehören zum Artikel Rekursion und Iteration. Die Grundmuster für Arrays erklärt der Artikel Prozedurale Algorithmen mit Arrays.
Hinweise zur Bearbeitung: Von FIA wird erwartet, dass ihnen Aufgaben auf Profilkompetenz-Ebene (Aufgabe 3.1 und Aufgabe 3.2) keine Probleme bereiten. Wenn nichts anderes verlangt ist, darfst Du Deine Algorithmen in Pseudocode, als Struktogramm oder als Programmablaufplan angeben. Arrays beginnen mit dem Index 0; Variablen musst Du nicht deklarieren. Die Beispiellösungen verwenden die Schreibweise aus dem Artikel (:= für die Zuweisung, = und <> für Vergleiche, div für die ganzzahlige Division, mod für den Rest, Ausgabe … ohne Zeilenumbruch). Andere korrekte Lösungen sind ebenfalls richtig. Schreib Deine Lösung zuerst vollständig auf, bevor Du die Beispiellösung aufklappst.
Die Codebeispiele kannst Du in vier Notationen anzeigen. Die Notationen sind im Artikel Algorithmen mit Pseudocode dokumentieren beschrieben.
anzahl: integer := 0
Aufgabe 3.1: Kategoriepfade und Artikelzählung im Fahrrad-Onlineshop (Profilkompetenz)
(ca. 43 min Bearbeitungszeit)
Du bist Auszubildende*r im IT-Team der CSB-Rad GmbH in Bielefeld. Für den Relaunch des Onlineshops im Frühjahr 2027 wünscht sich Sophie Bertram, Geschäftsführerin Vertrieb und E-Commerce, zwei Dinge: Über jeder Artikelseite soll der Kategoriepfad stehen (z. B. Fahrräder > Lastenräder > E-Lastenräder > Long John), und im Menü soll hinter jeder Kategorie die Zahl der aktiven Artikel einschließlich aller Unterkategorien erscheinen. Du arbeitest mit Jonas, Deinem Mit-Azubi, an der neuen Navigation.
Im Testsystem ist der Kategoriebaum bereits um einige Ebenen erweitert. Die Navigation lädt ihn beim Start in zwei Arrays, deren Index die Kategorie-ID ist (Index 0 bleibt frei):
bezeichnung[id]: Bezeichnung der Kategorie,oberId[id]: ID der Oberkategorie,0bei einer Hauptkategorie.
id |
bezeichnung[id] |
oberId[id] |
aktive Artikel direkt in der Kategorie |
|---|---|---|---|
| 1 | Fahrräder | 0 | 0 |
| 2 | E-Bikes | 1 | 3 |
| 3 | Lastenräder | 1 | 0 |
| 4 | Stadträder | 1 | 2 |
| 5 | Komponenten | 0 | 0 |
| 6 | Rahmen | 5 | 1 |
| 7 | Schlösser | 5 | 0 |
| 8 | Beleuchtung | 5 | 1 |
| 9 | Bremsen | 5 | 2 |
| 10 | Antrieb | 5 | 2 |
| 11 | E-Lastenräder | 3 | 2 |
| 12 | Long John | 11 | 1 |
| 13 | Bügelschlösser | 7 | 2 |
| 14 | Rahmenschlösser | 7 | 1 |
a) Jonas hat für den Kategoriepfad folgende rekursive Prozedur geschrieben:
Prozedur zeigePfad(id: integer)
Ausgabe bezeichnung[id]
wenn oberId[id] <> 0 dann
Ausgabe " > "
zeigePfad(oberId[id])
Ende wenn
Ende zeigePfad
aa) Führe einen Schreibtischtest durch und trage das Ergebnis in die rechte Spalte ein. (4 Punkte)
| Testfall | id |
erwartetes Ergebnis | Ergebnis Schreibtischtest |
|---|---|---|---|
| 1 | 12 | Fahrräder > Lastenräder > E-Lastenräder > Long John |
|
| 2 | 8 | Komponenten > Beleuchtung |
|
| 3 | 5 | Komponenten |
| Testfall | id |
erwartetes Ergebnis | Ergebnis Schreibtischtest |
|---|---|---|---|
| 1 | 12 | Fahrräder > Lastenräder > E-Lastenräder > Long John |
Long John > E-Lastenräder > Lastenräder > Fahrräder |
| 2 | 8 | Komponenten > Beleuchtung |
Beleuchtung > Komponenten |
| 3 | 5 | Komponenten |
Komponenten |
Bewertung: Testfall 1 2 Punkte, Testfälle 2 und 3 je 1 Punkt.
Nachvollzug für Testfall 1 (Aufrufebenen):
| Ebene | id |
Ausgabe beim Abstieg | oberId[id] <> 0? |
Aufruf |
|---|---|---|---|---|
| 1 | 12 | Long John |
ja → > |
zeigePfad(11) |
| 2 | 11 | E-Lastenräder |
ja → > |
zeigePfad(3) |
| 3 | 3 | Lastenräder |
ja → > |
zeigePfad(1) |
| 4 | 1 | Fahrräder |
nein | – |
ab) Beschreibe den Fehler und eine Möglichkeit, ihn zu korrigieren. (4 Punkte)
Fehler (2 Punkte): Die Bezeichnung wird vor dem rekursiven Aufruf ausgegeben, also beim Abstieg. Die Prozedur beginnt bei der Kategorie des Artikels und läuft zur Hauptkategorie hinauf; deshalb erscheint die unterste Kategorie zuerst und der Pfad steht in umgekehrter Reihenfolge. Hauptkategorien (Testfall 3) sind nicht betroffen.
Korrektur (2 Punkte): Erst die Oberkategorien ausgeben lassen (rekursiver Aufruf), danach – beim Aufstieg – das Trennzeichen und die eigene Bezeichnung:
Prozedur zeigePfad(id: integer)
wenn oberId[id] <> 0 dann
zeigePfad(oberId[id])
Ausgabe " > "
Ende wenn
Ausgabe bezeichnung[id]
Ende zeigePfad
Andere korrekte Korrekturen sind ebenfalls richtig, z. B. eine rekursive Funktion, die den Pfad als Zeichenkette zurückgibt: Rückgabe pfad(oberId[id]) & " > " & bezeichnung[id].
ac) Erkläre am Beispiel des Aufrufs zeigePfad(12) der korrigierten Prozedur, was auf dem Aufrufstapel geschieht, und gib die größte Aufruftiefe an. (4 Punkte)
Jeder Aufruf erhält auf dem Aufrufstapel einen eigenen Stapelrahmen mit seinem Parameter
idund der Rücksprungadresse. Die Ebenen haben daher unterschiedliche Werte vonid(12, 11, 3, 1), ohne sich gegenseitig zu überschreiben. (1 Punkt)Beim Abstieg ruft
zeigePfad(12)die Prozedur mit 11 auf, diese mit 3, diese mit 1. Die aufrufenden Ebenen warten, ihre Rahmen bleiben liegen. (1 Punkt)zeigePfad(1)erreicht die Abbruchbedingung (oberId[1] = 0), gibtFahrräderaus und wird als erstes beendet; ihr Rahmen wird vom Stapel entfernt. Beim Aufstieg setzt jede Ebene hinter ihrem Aufruf fort, gibt>und ihre Bezeichnung aus und wird beendet (Last In, First Out). (1 Punkt)Größte Aufruftiefe: 4 gleichzeitig offene Aufrufe (= Anzahl der Ebenen im Pfad). (1 Punkt)
ad) Beim Import eines Datenbestands des Schlossherstellers ist für die Kategorie 14 versehentlich oberId[14] = 14 gespeichert worden. Erläutere, was beim Aufruf zeigePfad(14) passiert, und nenne zwei Maßnahmen, die das verhindern. (4 Punkte)
Ablauf (2 Punkte): Die Kategorie verweist auf sich selbst. oberId[14] <> 0 ist immer wahr, daher ruft sich zeigePfad(14) immer wieder mit derselben ID auf; das Problem wird nicht kleiner, die Abbruchbedingung wird nie erreicht (Endlosrekursion). Jeder Aufruf legt einen neuen Stapelrahmen an, bis der Stapelspeicher voll ist: Stapelüberlauf (in Java StackOverflowError), die Seite wird nicht angezeigt bzw. der Dienst bricht ab. Es wird nichts ausgegeben, weil die Ausgaben erst beim Aufstieg erfolgen.
Maßnahmen (je 1 Punkt, zwei verlangt):
Daten beim Import und in der Datenbank prüfen, z. B.
CHECK (ober_kategorie_id <> kategorie_id); längere Zyklen (13 → 7 → 13) erkennt erst eine Prüfung beim Import, die den Pfad bis zur Hauptkategorie verfolgt.Eine maximale Tiefe als zusätzlichen Parameter mitgeben (
zeigePfad(id, restTiefe), Abbruch mit Fehlermeldung beirestTiefe = 0).Bereits besuchte IDs merken und bei einer Wiederholung abbrechen.
Iterative Fassung mit Schleifenzähler verwenden (verhindert den Stapelüberlauf, aber nicht die Endlosschleife – nur zusammen mit einer Obergrenze sinnvoll).
b) Beschreibe je einen Vorteil und einen Nachteil der rekursiven Lösung im Vergleich zu einer iterativen Lösung. (4 Punkte)
Je 2 Punkte für eine Beschreibung mit Begründung, z. B.:
Vorteil: Die rekursive Lösung ist kurz und leicht verständlich, weil sie die Struktur des Problems direkt abbildet („erst der Pfad der Oberkategorie, dann die eigene Bezeichnung“). Die Umkehr der Reihenfolge erledigt der Aufrufstapel; eine Hilfsdatenstruktur ist nicht nötig.
Nachteil: Für jeden Aufruf wird ein Stapelrahmen angelegt. Die rekursive Lösung braucht daher mehr Arbeitsspeicher und durch die Aufrufe etwas mehr Rechenzeit; bei großer Tiefe oder fehlerhaften Daten droht ein Stapelüberlauf.
Ebenfalls richtig: schwerer zu debuggen bzw. nachzuvollziehen (Vorgänge auf mehreren Ebenen gleichzeitig), Korrektheit lässt sich wie ein Induktionsbeweis begründen (Vorteil).
c) Damit die Navigation auch bei fehlerhaften Daten nicht abstürzt, soll die Prozedur iterativ umgesetzt werden. Dazu steht die Klasse Stapel zur Verfügung:
Stapel
+ Stapel() // erzeugt einen leeren Stapel
+ ablegen(wert: Text) // legt wert oben auf den Stapel
+ entnehmen(): Text // entfernt das oberste Element und gibt es zurück
+ istLeer(): boolean // true, wenn der Stapel leer ist
Erstelle einen iterativen Algorithmus, der den Kategoriepfad in der richtigen Reihenfolge ausgibt. (10 Punkte)
Prozedur zeigePfad(id: integer)
Prozedur zeigePfad(id: integer)
s: Stapel := new Stapel()
aktuell: integer := id
solange aktuell <> 0 // bis über die Hauptkategorie hinaus
s.ablegen(bezeichnung[aktuell])
aktuell := oberId[aktuell]
Ende solange
Ausgabe s.entnehmen() // Hauptkategorie ohne Trennzeichen
solange nicht s.istLeer()
Ausgabe " > " & s.entnehmen()
Ende solange
Ende zeigePfad
Als Struktogramm:

Punkteverteilung:
Stapel erzeugen, Laufvariable mit
idinitialisieren: 1 PunktSchleife über die Oberkategorien mit korrekter Bedingung (Hauptkategorie wird noch abgelegt, Schleife endet bei 0): 3 Punkte
Bezeichnung ablegen: 1 Punkt
zur Oberkategorie weitergehen: 1 Punkt
erste Bezeichnung (Hauptkategorie) ohne Trennzeichen ausgeben: 2 Punkte
Stapel bis zum Ende leeren und mit
>ausgeben: 2 Punkte
Alternativen mit voller Punktzahl, wenn korrekt:
ohne Stapel, den Pfad als Zeichenkette von rechts nach links aufbauen:
pfad := bezeichnung[aktuell], dann in der Schleifepfad := bezeichnung[aktuell] & " > " & pfad, am EndeAusgabe pfad;Trennzeichen über einen Merker („erstes Element“) statt der getrennten ersten Ausgabe.
Zum Schutz vor Zyklen (Teilaufgabe ad) kann die erste Schleife zusätzlich einen Zähler mit Obergrenze erhalten; das ist hier nicht verlangt.
d) Die korrigierte rekursive Prozedur aus ab) soll in einem Whitebox-Test geprüft werden. Bestimme die Mindestanzahl an Testfällen für eine vollständige Anweisungsüberdeckung, begründe Deine Antwort und gib einen geeigneten Testfall an. (3 Punkte)
Ein Testfall genügt. (1 Punkt)
Begründung: Anweisungsüberdeckung verlangt, dass jede Anweisung mindestens einmal ausgeführt wird. Ein Aufruf mit einer Unterkategorie führt in der äußeren Ebene den Rumpf der Verzweigung aus (rekursiver Aufruf, Trennzeichen) und erreicht im innersten Aufruf die Hauptkategorie, bei der die Verzweigung übersprungen wird. Die Ausgabe der Bezeichnung läuft in jeder Ebene. Da hier beide Ausgänge der Verzweigung durchlaufen werden, ist mit demselben Testfall sogar die Zweigüberdeckung erreicht. (1 Punkt)
Testfall: z. B.
id = 8, erwartete AusgabeKomponenten > Beleuchtung(jede andere Unterkategorie ist ebenso richtig). Eine Hauptkategorie allein (z. B.id = 5) reicht nicht, weil der Rumpf der Verzweigung nicht ausgeführt wird. (1 Punkt)
e) Für die Zahlen hinter den Menüeinträgen sollst Du eine rekursive Funktion artikelGesamt(id: integer): integer erstellen. Sie liefert die Zahl der aktiven Artikel in der Kategorie id einschließlich aller Unterkategorien, Unterunterkategorien usw. Dir stehen zwei fertige Funktionen zur Verfügung:
unterkategorien(id: integer): integer[]liefert ein Array mit den IDs der direkten Unterkategorien (leeres Array, wenn es keine gibt), z. B.unterkategorien(5)→[6, 7, 8, 9, 10].anzahlArtikel(id: integer): integerliefert die Zahl der aktiven Artikel direkt in der Kategorie (rechte Spalte der Tabelle).
ea) Erstelle die Funktion artikelGesamt. (8 Punkte)
Funktion artikelGesamt(id: integer): integer
summe: integer := anzahlArtikel(id) // Artikel direkt in der Kategorie
kinder: integer[] := unterkategorien(id)
zähle i von 0 bis laenge(kinder) - 1
summe := summe + artikelGesamt(kinder[i]) // rekursiv je Unterkategorie
Ende zähle
Rückgabe summe
Ende artikelGesamt
Punkteverteilung:
Summe mit den direkten Artikeln beginnen: 2 Punkte
Unterkategorien holen und vollständig durchlaufen (Indexgrenzen): 2 Punkte
rekursiver Aufruf je Unterkategorie, Ergebnis aufaddieren: 3 Punkte
Rückgabe der Summe: 1 Punkt
Die Abbruchbedingung steckt in der Schleife: Bei einer Kategorie ohne Unterkategorien läuft die Schleife nicht, es folgt kein weiterer Aufruf. Eine ausdrückliche Abfrage wenn laenge(kinder) = 0 dann Rückgabe anzahlArtikel(id) ist ebenso richtig. Fehler mit Punktabzug: Rückgabewert des rekursiven Aufrufs nicht addiert; Summe als globale Variable, die nicht zurückgesetzt wird; nur die direkten Unterkategorien gezählt.
eb) Gib an, welchen Wert artikelGesamt(1) liefert und wie oft die Funktion dabei insgesamt aufgerufen wird. (3 Punkte)
Rückgabewert: 8 (Fahrräder 0 + E-Bikes 3 + Lastenräder 0 + E-Lastenräder 2 + Long John 1 + Stadträder 2). (1,5 Punkte)
Aufrufe: 6 – je einer für die Kategorien 1, 2, 3, 11, 12 und 4 (in dieser Reihenfolge). (1,5 Punkte)
ec) Jonas meint: „artikelGesamt können wir genauso leicht in eine Schleife umwandeln wie zeigePfad.“ Erläutere, worin sich die beiden Rekursionen unterscheiden und was eine iterative Fassung von artikelGesamt zusätzlich braucht. (3 Punkte)
zeigePfadruft sich je Ebene höchstens einmal auf (lineare Rekursion): Der Weg führt in einer Linie zur Hauptkategorie; eine Schleife mit einer Laufvariablen genügt. (1 Punkt)artikelGesamtruft sich je Ebene für jede Unterkategorie auf (Baumrekursion). Nach der Rückkehr aus einem Zweig muss das Programm wissen, welche Geschwister-Kategorien noch offen sind – das merkt sich bei der Rekursion der Aufrufstapel. (1 Punkt)Eine iterative Fassung braucht deshalb eine eigene Hilfsdatenstruktur für die noch zu besuchenden Kategorien, z. B. einen Stapel (Tiefensuche) oder eine Warteschlange (Breitensuche): Start-ID ablegen, solange nicht leer eine ID entnehmen, ihre Artikel addieren und ihre Unterkategorien ablegen. (1 Punkt)
Ebenfalls richtig: Alle Kategorien des Arrays durchlaufen und für jede über oberId nach oben prüfen, ob id auf ihrem Pfad liegt – ohne Hilfsdatenstruktur, aber mit deutlich mehr Rechenaufwand.
Aufgabe 3.2: Offline-Check-in und Türschilder beim Barcamp (Profilkompetenz)
(ca. 41 min Bearbeitungszeit)
Am Ratz und Rübe Berufskolleg (RRB) in Bielefeld findet am Samstag, 13.03.2027, das Barcamp „Lernen.Digital.Leben 2027“ statt. Du bist Auszubildende*r im Orga-Team und unterstützt Emre Aksoy, der das Technikteam der Schüler*innen leitet. Svenja Krämer von der Schul-IT OWL besteht darauf, dass die beiden Check-in-Terminals auch dann weiterarbeiten, wenn WLAN oder Info-Server ausfallen. Für diesen Offline-Modus speichert jedes Terminal am Vorabend ein Array ids mit den Nummern aller Anmeldungen (anmeldung_id), aufsteigend sortiert und ohne doppelte Werte. Bis zu 400 Anmeldungen werden erwartet.
Mia Schröder hat die Suche nach einer Anmeldenummer so umgesetzt:
Funktion sucheIterativ(ids: integer[], wert: integer): integer
links := 0
rechts := laenge(ids) - 1
solange links <= rechts
mitte := (links + rechts) div 2
wenn ids[mitte] = wert dann
Rückgabe mitte
Ende wenn
wenn ids[mitte] < wert dann
links := mitte + 1
sonst
rechts := mitte - 1
Ende wenn
Ende solange
Rückgabe -1
Ende sucheIterativ
a) Zum Testen verwendet Mia ein kleines Array:
| Index | 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 |
|---|---|---|---|---|---|---|---|---|---|---|
ids[Index] |
104 | 117 | 123 | 138 | 151 | 166 | 172 | 189 | 203 | 215 |
aa) Beschreibe, nach welchem Verfahren die Funktion sucht und welche Voraussetzung die Daten dafür erfüllen müssen. (3 Punkte)
Die Funktion arbeitet nach dem Verfahren der binären Suche (Intervallhalbierung, Teile und herrsche). (1 Punkt)
Sie vergleicht den gesuchten Wert mit dem Element in der Mitte des Suchbereichs
links … rechts. Bei Gleichheit gibt sie den Index zurück. Ist das mittlere Element kleiner, sucht sie in der rechten Hälfte weiter, sonst in der linken. Ist der Bereich leer (links > rechts), ist der Wert nicht enthalten, Rückgabe −1. (1 Punkt)Voraussetzung: Das Array muss aufsteigend sortiert sein. (1 Punkt)
ab) Führe für die Suche nach 189 und nach 120 einen Schreibtischtest durch. Trage für jeden Schleifendurchlauf die Werte ein und gib den Rückgabewert an. (5 Punkte)
| Suche nach | Durchlauf | links |
rechts |
mitte |
ids[mitte] |
|---|---|---|---|---|---|
| 189 | 1 | ||||
| 189 | … | ||||
| 120 | 1 | ||||
| 120 | … |
| Suche nach | Durchlauf | links |
rechts |
mitte |
ids[mitte] |
|---|---|---|---|---|---|
| 189 | 1 | 0 | 9 | 4 | 151 |
| 189 | 2 | 5 | 9 | 7 | 189 |
| 120 | 1 | 0 | 9 | 4 | 151 |
| 120 | 2 | 0 | 3 | 1 | 117 |
| 120 | 3 | 2 | 3 | 2 | 123 |
| 120 | (Ende) | 2 | 1 | – | – |
Rückgabewerte: Suche nach 189 → 7; Suche nach 120 → −1 (Schleife endet, weil links > rechts).
Bewertung: Suche nach 189 2 Punkte (je Durchlauf 0,5, Rückgabe 1), Suche nach 120 3 Punkte (je Durchlauf 0,5, Endzustand 0,5, Rückgabe 1).
b) Für eine Lerneinheit über Rekursion soll die Suche zusätzlich rekursiv umgesetzt werden. Erstelle eine rekursive Funktion mit der folgenden Signatur und gib an, wie sie für die Suche nach wert erstmals aufgerufen wird. (8 Punkte)
Funktion sucheRekursiv(ids: integer[], wert: integer, links: integer, rechts: integer): integer
Funktion sucheRekursiv(ids: integer[], wert: integer, links: integer, rechts: integer): integer
wenn links > rechts dann // Suchbereich leer
Rückgabe -1
Ende wenn
mitte: integer := (links + rechts) div 2
wenn ids[mitte] = wert dann
Rückgabe mitte
Ende wenn
wenn ids[mitte] < wert dann
Rückgabe sucheRekursiv(ids, wert, mitte + 1, rechts)
sonst
Rückgabe sucheRekursiv(ids, wert, links, mitte - 1)
Ende wenn
Ende sucheRekursiv
// Erstaufruf
index: integer := sucheRekursiv(ids, wert, 0, laenge(ids) - 1)
Punkteverteilung:
Abbruchbedingung „Bereich leer“ vor dem Zugriff auf
ids[mitte], Rückgabe −1: 2 PunkteMitte berechnen, Treffer zurückgeben: 1 Punkt
rekursiver Aufruf für die rechte Hälfte mit
mitte + 1: 1,5 Punkterekursiver Aufruf für die linke Hälfte mit
mitte - 1: 1,5 PunkteErgebnis der rekursiven Aufrufe jeweils zurückgeben: 1 Punkt
Erstaufruf mit
0undlaenge(ids) - 1: 1 Punkt
Die Laufvariablen links und rechts der Schleife sind zu Parametern geworden; die negierte Schleifenbedingung (links > rechts) ist die Abbruchbedingung.
c) Hannah Klein, Schülerin der FIA-Klasse, hat eine eigene rekursive Fassung geschrieben:
1 Funktion sucheR(ids: integer[], wert: integer, links: integer, rechts: integer): integer
2 mitte := (links + rechts) div 2
3 wenn ids[mitte] = wert dann
4 Rückgabe mitte
5 Ende wenn
6 wenn links > rechts dann
7 Rückgabe -1
8 Ende wenn
9 wenn ids[mitte] < wert dann
10 sucheR(ids, wert, mitte, rechts)
11 sonst
12 Rückgabe sucheR(ids, wert, links, mitte - 1)
13 Ende wenn
14 Ende sucheR
ca) Zeile 10 enthält zwei Fehler. Beschreibe beide Fehler, ihre Auswirkung (gern an einem Wert aus den Testdaten) und je eine Korrektur. (6 Punkte)
Fehler 1 – Rückgabewert geht verloren (3 Punkte): Der rekursive Aufruf in Zeile 10 liefert ein Ergebnis, das nicht zurückgegeben wird. Die Funktion endet in diesem Zweig ohne Rückgabewert. Beispiel 189: sucheR(0, 9) → Mitte 4 (151 < 189) → Zeile 10 ruft sucheR(4, 9) auf → … → ein innerer Aufruf findet 189 an Index 7, aber der Wert wird von den äußeren Ebenen verworfen; der Erstaufruf liefert keinen (bzw. einen undefinierten) Wert. In Java meldet der Compiler missing return statement. Korrektur: Rückgabe sucheR(ids, wert, mitte + 1, rechts).
Fehler 2 – Bereich wird nicht kleiner (3 Punkte): Für die rechte Hälfte wird mitte statt mitte + 1 als neue linke Grenze übergeben. Ist links = mitte (das passiert, sobald der Bereich nur noch ein oder zwei Elemente umfasst), wird die Funktion immer wieder mit denselben Grenzen aufgerufen: Endlosrekursion und Stapelüberlauf. Beispiel 120: sucheR(0, 9) → sucheR(0, 3) → Mitte 1 (117 < 120) → sucheR(1, 3) → Mitte 2 (123 > 120) → sucheR(1, 1) → Mitte 1 (117 < 120) → sucheR(1, 1) → … Beispiel 215: ab sucheR(8, 9) mit Mitte 8 endlos. Korrektur: mitte + 1 übergeben (siehe oben).
cb) Prüfe, ob die Reihenfolge der Anweisungen in den Zeilen 2 bis 8 ein Problem verursachen kann, und begründe Deine Antwort. (3 Punkte)
Ja: Die Abbruchprüfung
links > rechtssteht nach dem Zugriff aufids[mitte]. (1 Punkt)Bei einem leeren Array (z. B. wenn die Anmeldedaten am Vorabend nicht übertragen wurden) ist
links = 0undrechts = -1;mitteist dann 0 oder −1 (je nach Rundung der ganzzahligen Division). Der Zugriff liegt außerhalb des Arrays, es kommt zum Laufzeitfehler (ArrayIndexOutOfBoundsException). (1 Punkt)Bei einem nicht leeren Array liefert die Reihenfolge kein falsches Ergebnis, wenn die ganzzahlige Division wie in Java zur Null hin rundet: Ist der Bereich leer geworden (
links = rechts + 1), zeigtmitteauf ein Element unmittelbar neben dem Suchbereich, das wegen der Sortierung nicht gleichwertsein kann. Rundetdivab (z. B.//in Python), entsteht beim Bereich0 … -1der Index −1. Sauber ist nur, die Abbruchbedingung vor dem ersten Zugriff zu prüfen. (1 Punkt)
d) Der Hersteller der Terminals gibt an, dass ein Aufruf der Suchfunktion 96 Byte auf dem Aufrufstapel belegt; für die Check-in-App stehen 256 KiB Stapelspeicher zur Verfügung.
da) Berechne, wie viele Vergleiche mit ids[mitte] die binäre Suche bei 400 Anmeldungen höchstens braucht, wie groß die Aufruftiefe von sucheRekursiv dann höchstens ist und wie viel Stapelspeicher dafür benötigt wird. (3 Punkte)
Geg.: n = 400 Anmeldungen; s = 96\ \mathrm{B} je Aufruf; S = 256\ \mathrm{KiB} = 262.144\ \mathrm{B}
Ges.: höchste Zahl der Vergleiche k, Aufruftiefe t, Stapelbedarf M
Mit k Vergleichen lassen sich höchstens 2^k - 1 Elemente durchsuchen (jeder Vergleich halbiert den Rest). Gesucht ist das kleinste k mit
2^k - 1 \geq n \quad | + 1
2^k \geq n + 1 \quad | \log_2
k \geq \log_2(n + 1) = \log_2(401) \approx 8{,}647
\underline{\underline{k = 9}} \text{ Vergleiche (ganzzahlig aufgerundet)}
Die rekursive Fassung braucht im ungünstigsten Fall (Wert nicht enthalten) neben den 9 Aufrufen mit Vergleich einen letzten Aufruf, der links > rechts feststellt:
t = k + 1 = 9 + 1 = \underline{\underline{10}} \text{ Aufrufe}
M = t \cdot s = 10 \cdot 96\ \mathrm{B} = \underline{\underline{960\ \mathrm{B}}}
Das sind weniger als 0,4 % des Stapelspeichers. Bewertung: je Ergebnis 1 Punkt; t = 9 und M = 864\ \mathrm{B} mit nachvollziehbarer Begründung ebenfalls richtig.
db) Hannah schlägt alternativ eine rekursive lineare Suche vor, die sich für jedes Element einmal aufruft (im ungünstigsten Fall n + 1 Aufrufe). Berechne, ab wie vielen Anmeldungen diese Suche den Stapelspeicher überlaufen lässt. Andere Daten auf dem Stapel bleiben unberücksichtigt. (3 Punkte)
Ges.: kleinste Anzahl n, bei der (n + 1) \cdot s > S
(n + 1) \cdot s > S \quad | : s
n + 1 > \frac{S}{s} \quad | - 1
n > \frac{S}{s} - 1 = \frac{262.144\ \mathrm{B}}{96\ \mathrm{B}} - 1 = \frac{8.189}{3} \approx 2.729{,}667
\underline{\underline{n = 2.730}} \text{ Anmeldungen}
Probe: 2.731 \cdot 96\ \mathrm{B} = 262.176\ \mathrm{B} > 262.144\ \mathrm{B}, bei 2.729 Anmeldungen sind es 262.080\ \mathrm{B}. Für das Barcamp mit 400 Anmeldungen (38.496 B) reicht der Speicher noch, für größere Veranstaltungen nicht – und andere Daten auf dem Stapel senken die Grenze weiter.
Bewertung: Ansatz 1 Punkt, Umformung 1 Punkt, Ergebnis 1 Punkt.
e) Erkläre, ob sucheRekursiv aus b) endrekursiv ist, warum artikelGesamt aus Aufgabe 3.1 es nicht ist und ob die Endrekursion in einer Java-Umsetzung Stapelspeicher spart. (4 Punkte)
Eine Funktion ist endrekursiv, wenn der rekursive Aufruf die letzte Aktion ist und sein Ergebnis unverändert zurückgegeben wird. (1 Punkt)
sucheRekursivist endrekursiv: In beiden Zweigen stehtRückgabe sucheRekursiv(…), danach ist nichts mehr zu tun. (1 Punkt)artikelGesamtist nicht endrekursiv: Nach jedem rekursiven Aufruf wird das Ergebnis noch zur Summe addiert und die Schleife fortgesetzt; der Stapelrahmen wird weiter gebraucht. (1 Punkt)Compiler mit Endrekursions-Optimierung ersetzen den Aufruf durch einen Sprung, sodass kein neuer Stapelrahmen entsteht (konstanter Speicher). Java bzw. die JVM führen diese Optimierung nicht durch; in Java spart die endrekursive Form also keinen Stapelspeicher. (1 Punkt)
f) Svenja Krämer meint: „Auf den Terminals kommt nur die iterative Fassung infrage.“ Nimm zu dieser Aussage Stellung. (3 Punkte)
Beispiel für eine begründete Stellungnahme (je ein Punkt für ein zutreffendes Argument, ein Punkt für ein begründetes Fazit):
Für die binäre Suche ist das Risiko eines Stapelüberlaufs praktisch ausgeschlossen: Die Aufruftiefe wächst nur logarithmisch (10 Aufrufe bei 400 Anmeldungen, 21 bei einer Million). Ein „nur“ ist daher übertrieben.
Die iterative Fassung ist trotzdem etwas schneller (keine Aufrufe), braucht konstanten Speicher und ist ebenso kurz und gut lesbar; sie bietet beim Check-in also keinen Nachteil.
Fazit: Ich stimme Svenja im Ergebnis zu – auf dem Terminal sollte die iterative Fassung laufen, weil sie robust und nicht schwerer verständlich ist. Die rekursive Fassung wäre aber ebenfalls vertretbar; ausgeschlossen werden muss nur eine Rekursion, deren Tiefe mit der Datenmenge linear wächst (siehe db).
Eine andere Position mit stimmiger Begründung ist ebenfalls richtig.
g) Die zwölf E-Ink-Türschilder speichern die Belegung der fünf Session-Slots eines Raums als Zahl muster von 0 bis 31: Der Wert 16 steht für Slot 1, 8 für Slot 2, 4 für Slot 3, 2 für Slot 4 und 1 für Slot 5; ein gesetztes Bit bedeutet „belegt“. Für das Wartungsmenü soll das Muster als Folge von genau fünf Ziffern 0 und 1 angezeigt werden, Slot 1 links. Beispiel: muster = 6 ergibt 00110.
ga) Erstelle eine rekursive Prozedur zeigeBelegung(muster: integer, stellen: integer), die das Muster mit der angegebenen Zahl an Stellen (einschließlich führender Nullen) ausgibt. Die Abbruchbedingung soll über stellen gesteuert werden; der Aufruf für das Türschild lautet zeigeBelegung(muster, 5). (5 Punkte)
Prozedur zeigeBelegung(muster: integer, stellen: integer)
wenn stellen > 0 dann
zeigeBelegung(muster div 2, stellen - 1) // erst die Slots weiter links
Ausgabe muster mod 2 // dann das eigene Bit
Ende wenn
Ende zeigeBelegung
Punkteverteilung:
Abbruchbedingung über
stellen(bei 0 nichts mehr ausgeben): 1 Punktrekursiver Aufruf mit
muster div 2undstellen - 1(Problem wird kleiner): 2 PunkteAusgabe von
muster mod 2nach dem rekursiven Aufruf, damit Slot 1 links steht: 2 Punkte
Weil die Abbruchbedingung an stellen und nicht an muster > 0 hängt, entstehen die führenden Nullen von selbst. Steht die Ausgabe vor dem Aufruf, erscheint das Muster spiegelverkehrt (Slot 5 links): höchstens 3 Punkte.
gb) Gib die Ausgabe von zeigeBelegung(13, 5) an und nenne die belegten Slots. (2 Punkte)
| Ebene | muster |
stellen |
Aufruf | Ausgabe beim Aufstieg |
|---|---|---|---|---|
| 1 | 13 | 5 | zeigeBelegung(6, 4) |
1 (als letzte) |
| 2 | 6 | 4 | zeigeBelegung(3, 3) |
0 |
| 3 | 3 | 3 | zeigeBelegung(1, 2) |
1 |
| 4 | 1 | 2 | zeigeBelegung(0, 1) |
1 |
| 5 | 0 | 1 | zeigeBelegung(0, 0) |
0 (als erste) |
| 6 | 0 | 0 | – (Abbruch) | – |
Ausgabe: 01101 (1 Punkt). Belegt sind die Slots 2, 3 und 5 (8 + 4 + 1 = 13) (1 Punkt).
Links und weitere Informationen
Hinweis zur Nachnutzung als Open Educational Resource (OER)
Dieser Artikel und seine Texte, Bilder, Grafiken, Code und sonstiger Inhalt sind - sofern nicht anders angegeben - lizenziert unter CC BY 4.0. Nennung gemäß TULLU-Regel bitte wie folgt: “Übungsaufgaben zu Rekursion und Iteration” von oer-informatik.de (H. Stein), Lizenz: CC BY 4.0. Der Artikel wurde unter https://oer-informatik.de/rekursion-uebungsaufgaben veröffentlicht, die Quelltexte sind in weiterverarbeitbarer Form verfügbar im Repository unter https://gitlab.com/oer-informatik/algorithmik/algorithmen. Erstellt mit KI-Assistenz; von mir umfassend redaktionell überarbeitet, geprüft und freigegeben. Stand: 27.10.2026.
[Kommentare zum Artikel lesen, schreiben] / [Artikel teilen] / [gitlab-Issue zum Artikel schreiben]
