Zurück zur StartseiteInformatik · Q1 Leistungskurs · Fach wechseln
Kostenlos starten
Q1 Leistungskurs · Informatik
Datenbanken, Datenstrukturen und Automaten in der Qualifikationsphase · Objektorientierung, Datenstrukturen und Algorithmen

Datenstrukturen, Rekursion und Effizienz

Ein Ticketsystem in Köln bearbeitet Anfragen in der Reihenfolge ihres Eingangs, der Zurück-Button im Browser springt dagegen zur zuletzt besuchten Seite zurück. Beide Male werden Daten gespeichert, aber nach völlig verschiedenen Regeln. Welche Struktur passt zu welchem Problem, und wie lange braucht ein Algorithmus, wenn die Datenmenge auf eine Million Einträge wächst? Genau darum geht es auf dieser Seite.

Stack und Queue

Ein Stack (Stapel) arbeitet nach dem Prinzip LIFO (Last In, First Out): Das zuletzt abgelegte Element wird als erstes wieder entnommen, wie bei einem Tellerstapel. Eine Queue (Schlange) arbeitet nach dem Prinzip FIFO (First In, First Out): Wer zuerst kommt, wird zuerst bedient, wie an der Mensa-Kasse.

Links ein Stack mit den Elementen 3 und 7, bei dem push und pop oben stattfinden; rechts eine Queue, bei der hinten eingefügt und vorn entnommen wird. Stack (LIFO) Queue (FIFO) 37 push / pop Top = 7 492 dequeue enqueue Kopf Ende
Struktur Operationen Prinzip Beispiel
Stack push(x), pop(), top(), isEmpty() LIFO Rückgängig-Funktion, Browser-Verlauf
Queue enqueue(x), dequeue(), head(), isEmpty() FIFO Druckerwarteschlange, Ticketanfragen

Beide sind dynamische Datenstrukturen: Ihre Größe ändert sich zur Laufzeit, man greift nur über wenige festgelegte Operationen zu. Das macht sie sicher und einfach zu verwenden. Im Abitur NRW stehen dafür die Java-Klassen Stack<ContentType>, Queue<ContentType> und BinarySearchTree<ContentType> zur Verfügung, die genau diese Operationen anbieten (intern z. B. als verkettete Liste aus Knotenobjekten umgesetzt: Jeder Knoten kennt seinen Nachfolger, push, pop, enqueue und dequeue ändern nur wenige Verweise).

Üben★☆☆Nicht begonnen
Auf einem anfangs leeren Stack werden nacheinander push(3), push(7), pop(), push(5), pop() ausgeführt. a) Geben Sie nach jeder Operation den Inhalt des Stacks an. b)…

Auf einem anfangs leeren Stack werden nacheinander push(3), push(7), pop(), push(5), pop() ausgeführt.

  1. a) Geben Sie nach jeder Operation den Inhalt des Stacks an.
  1. b) Welches Element liefert das nächste pop()?
Lösen
Üben★☆☆Nicht begonnen
Eine Queue enthält (Kopf zuerst) 4, 9, 2. a) Geben Sie den Zustand nach dequeue(), enqueue(6) an. b) Vergleichen Sie kurz das Prinzip einer Queue (FIFO) mit dem eines…

Eine Queue enthält (Kopf zuerst) 4, 9, 2.

  1. a) Geben Sie den Zustand nach dequeue(), enqueue(6) an.
  1. b) Vergleichen Sie kurz das Prinzip einer Queue (FIFO) mit dem eines Stacks (LIFO) und nennen Sie je ein Anwendungsbeispiel.
Lösen

Rekursion

Eine Methode ist rekursiv, wenn sie sich selbst aufruft, und zwar für ein kleineres Teilproblem. Zwei Bestandteile sind zwingend:

int fak(int n){

  if (n <= 1) return 1;      // Abbruchbedingung

  return n * fak(n - 1);     // rekursiver Aufruf

}

Jeder Aufruf legt einen Eintrag (Parameter, Rücksprungadresse) auf den Aufrufstack des Systems. Erst wenn der Anker erreicht ist, werden die Aufrufe von oben nach unten abgearbeitet.

Rekursion ist elegant, kann aber ineffizient sein. Beim Aufrufbaum der Fibonacci-Methode fib(n) = fib(n-1) + fib(n-2) verzweigt sich jeder Aufruf in zwei neue. Dieselben Teilprobleme werden immer wieder berechnet, die Zahl der Aufrufe wächst exponentiell. Eine iterative Lösung merkt sich nur die letzten beiden Werte und braucht $n$ Schritte und konstanten Speicher. (Alternative: bereits berechnete Werte speichern.)

Vollständiger Aufrufbaum von fib(4): fib(2) wird zweimal berechnet, jeweils mit den Kindern fib(1) und fib(0). fib(4) fib(3) fib(2) fib(2) fib(1) fib(1) fib(0) fib(1) fib(0) fib(2) tritt doppelt auf: Mehrfacharbeit
Üben★☆☆Nicht begonnen
Gegeben ist die rekursive Methode a) Berechnen Sie f(4). b) Nennen Sie Abbruchbedingung und rekursiven Aufruf.

Gegeben ist die rekursive Methode

int f(int n){ if (n == 0) return 0; return n + f(n - 1); }
  1. a) Berechnen Sie f(4).
  1. b) Nennen Sie Abbruchbedingung und rekursiven Aufruf.
Lösen
Üben★★★Nicht begonnen
Gegeben ist a) Zeichnen Sie den Aufrufbaum für g(5) und bestimmen Sie die Anzahl der Aufrufe. b) Erklären Sie, warum die Laufzeit exponentiell wächst, und schlagen Sie…

Gegeben ist

int g(int n){ if (n <= 1) return 1; return g(n - 1) + g(n - 2); }
  1. a) Zeichnen Sie den Aufrufbaum für g(5) und bestimmen Sie die Anzahl der Aufrufe.
  1. b) Erklären Sie, warum die Laufzeit exponentiell wächst, und schlagen Sie eine effizientere Lösung vor.
  1. c) Vergleichen Sie den Speicherbedarf von rekursiver und iterativer Lösung.
Lösen

Der binäre Suchbaum

Ein Baum ist eine nichtlineare Datenstruktur aus Knoten mit Kindern. Im binären Baum hat jeder Knoten höchstens ein linkes und ein rechtes Kind. Ein binärer Suchbaum erfüllt zusätzlich: Alle Werte im linken Teilbaum sind kleiner, alle im rechten größer als der Wert des Knotens.

Einfügen: Beginne bei der Wurzel, gehe bei kleineren Werten nach links, bei größeren nach rechts, bis ein freier Platz da ist. Dieselbe Suche funktioniert beim Finden eines Wertes.

Binärer Suchbaum mit Wurzel 50, links 30 mit Kindern 20 und 40, rechts 70 mit linkem Kind 60. 503070 204060

Drei Traversierungen besuchen jeden Knoten genau einmal (W = Wurzel, L = linker, R = rechter Teilbaum):

Reihenfolge Ablauf Ergebnis für obigen Baum
Pre-order W, L, R 50, 30, 20, 40, 70, 60
In-order L, W, R 20, 30, 40, 50, 60, 70
Post-order L, R, W 20, 40, 30, 60, 70, 50

Die In-order-Ausgabe eines Suchbaums ist immer aufsteigend sortiert. Die Suche nach 60 braucht Vergleiche mit 50, 70 und 60, also 3 Vergleiche; sie folgt höchstens einem Pfad von der Wurzel zu einem Blatt, also der Höhe des Baums.

Üben★★☆Nicht begonnen
Die Zahlenfolge 8, 3, 10, 1, 6, 14 wird in dieser Reihenfolge in einen anfangs leeren binären Suchbaum eingefügt. a) Zeichnen Sie den Baum (Beschreibung der Knoten mit…

Die Zahlenfolge 8, 3, 10, 1, 6, 14 wird in dieser Reihenfolge in einen anfangs leeren binären Suchbaum eingefügt.

  1. a) Zeichnen Sie den Baum (Beschreibung der Knoten mit linkem und rechtem Kind).
  1. b) Geben Sie die In-order-, Pre-order- und Post-order-Ausgabe an.
  1. c) Wie viele Vergleiche braucht die Suche nach 6?
Lösen

Suchen und Sortieren

Lineare Suche: Das Feld wird von vorn Element für Element durchlaufen, im schlechtesten Fall mit $n$ Vergleichen.

Binäre Suche (nur in einem sortierten Feld): Man vergleicht mit dem mittleren Element. Ist der gesuchte Wert kleiner, sucht man links weiter, ist er größer, rechts. Jeder Vergleich halbiert den Suchbereich.

Warum logarithmisch? Nach $k$ Halbierungen bleiben etwa $n/2^k$ Elemente. Der Bereich ist auf ein Element geschrumpft, wenn $2^k \geq n$ gilt, also nach etwa $\log_2 n$ Schritten. Für $n = 1.000$ sind das nur etwa 10 Vergleiche.

Selectionsort: Suche im unsortierten Rest das kleinste Element und tausche es an die erste freie Stelle. Wiederhole dies für den verbleibenden Rest.

Allgemein sind es $(n-1) + (n-2) + \dots + 1 = \frac{n(n-1)}{2}$ Vergleiche, unabhängig von der Anordnung der Eingabe. Die Laufzeit wächst quadratisch: Verdoppelt man $n$, vervierfacht sich der Aufwand.

Üben★★☆Nicht begonnen
Das Feld {2, 5, 8, 12, 16, 23, 38, 56} ist sortiert. a) Führen Sie die binäre Suche nach 23 durch und geben Sie die verglichenen Elemente an. b) Wie viele Vergleiche…

Das Feld {2, 5, 8, 12, 16, 23, 38, 56} ist sortiert.

  1. a) Führen Sie die binäre Suche nach 23 durch und geben Sie die verglichenen Elemente an.
  1. b) Wie viele Vergleiche braucht die lineare Suche nach 56? Wie viele die binäre?
  1. c) Begründen Sie, warum die binäre Suche bei $n = 1.024$ höchstens etwa 11 Vergleiche benötigt.
Lösen
Üben★★☆Nicht begonnen
Das Feld {7, 3, 9, 1, 5} wird mit Selectionsort aufsteigend sortiert. a) Geben Sie das Feld nach jedem Durchlauf der äußeren Schleife an. b) Bestimmen Sie die Anzahl der…

Das Feld {7, 3, 9, 1, 5} wird mit Selectionsort aufsteigend sortiert.

  1. a) Geben Sie das Feld nach jedem Durchlauf der äußeren Schleife an.
  1. b) Bestimmen Sie die Anzahl der Vergleiche für $n = 5$ und allgemein für $n$.
Lösen

Mergesort und Effizienz

Selectionsort ist für große Datenmengen zu langsam. Mergesort nutzt das Prinzip „Teile und herrsche“:

  1. Teilen: Das Feld in zwei Hälften zerlegen.

  2. Herrschen: Beide Hälften rekursiv sortieren (Anker: Länge 1 ist schon sortiert).

  3. Mischen: Die beiden sortierten Hälften zu einem sortierten Feld zusammenführen, indem man immer die vorderen Elemente vergleicht und das kleinere übernimmt.

Aufwand: Bei $n = 2^k$ wird $k$-mal halbiert, es gibt also $k = \log_2 n$ Ebenen. Auf jeder Ebene werden insgesamt höchstens $n$ Elemente gemischt, also höchstens $n$ Vergleiche. Gesamt: etwa $n \cdot \log_2 n$ Vergleiche.

$n$ Selectionsort $\approx n^2/2$ Mergesort $\approx n \cdot \log_2 n$
1.000 500.000 rund 10.000
1.000.000 $5 \cdot 10^{11}$ rund $2 \cdot 10^{7}$

Mergesort benötigt aber zusätzlichen Speicher für die gemischten Teilfelder. Es gibt also einen Zielkonflikt zwischen Laufzeit und Speicherplatz, den man bei der Wahl eines Verfahrens begründen muss.

Üben★★★Nicht begonnen
Mergesort teilt ein Feld der Länge $n$ in zwei Hälften, sortiert sie rekursiv und mischt sie. a) Führen Sie Mergesort für {6, 2, 9, 4, 1, 7, 3, 8} bis zum Ende aus und…

Mergesort teilt ein Feld der Länge $n$ in zwei Hälften, sortiert sie rekursiv und mischt sie.

  1. a) Führen Sie Mergesort für {6, 2, 9, 4, 1, 7, 3, 8} bis zum Ende aus und geben Sie die Teilungs- und Mischschritte an.
  1. b) Begründen Sie, dass bei $n = 2^k$ etwa $k$ Ebenen mit je höchstens $n$ Vergleichen entstehen.
  1. c) Vergleichen Sie für $n = 1.000.000$ die Größenordnung der Vergleiche mit Selectionsort ($\approx n^2/2$).
Lösen

Stack-Anwendung und systematisches Testen

Ein Stack eignet sich, wenn das zuletzt Geöffnete zuerst geschlossen werden muss. Das ist bei Klammern der Fall. Algorithmus zur Prüfung auf korrekte Klammerung:

  • Lies den Ausdruck von links nach rechts.

  • Öffnende Klammer: push.

  • Schließende Klammer: Ist der Stack leer oder passt das oberste Element nicht zum Typ, ist der Ausdruck falsch. Sonst pop.

  • Am Ende ist der Ausdruck korrekt, wenn der Stack leer ist.

Systematisches Testen: Eine Implementierung wird mit Testfällen geprüft, die Normalfälle, Grenzfälle und Fehlerfälle abdecken. Zu jedem Testfall legt man die erwartete Ausgabe vorher fest. Gute Testfälle für die Klammerprüfung:

Art Eingabe Erwartung
Grenzfall leerer Ausdruck korrekt
Fehler nur ) falsch (Stack leer beim Schließen)
Fehler (( falsch (Stack am Ende nicht leer)
Normalfall [()] korrekt
Üben★★★Nicht begonnen
Ein Ausdruck mit Klammern (, ), [, ] soll auf korrekte Klammerung geprüft werden. a) Beschreiben Sie einen Algorithmus mit einem Stack. b) Führen Sie ihn für ([)] und…

Ein Ausdruck mit Klammern (, ), [, ] soll auf korrekte Klammerung geprüft werden.

  1. a) Beschreiben Sie einen Algorithmus mit einem Stack.
  1. b) Führen Sie ihn für ([)] und ([()]) durch und begründen Sie die Ergebnisse.
  1. c) Beschreiben Sie vier Testfälle zur systematischen Prüfung Ihrer Implementierung (leerer Ausdruck, nur schließende Klammer usw.).
Lösen

Zurück zum Anfang: Das Ticketsystem passt zur Queue (FIFO), der Zurück-Button zum Stack (LIFO). Bei einer Million Einträge und $10^8$ Vergleichen pro Sekunde macht der Unterschied zwischen Selectionsort ($\approx n^2/2$) und Mergesort ($\approx n \cdot \log_2 n$) aus knapp 1,4 Stunden Rechenzeit nur den Bruchteil einer Sekunde.

Auf einen Blick

  • Stack (LIFO: push, pop) und Queue (FIFO: enqueue, dequeue) sind dynamische Datenstrukturen mit festen Zugriffsoperationen.

  • Rekursion braucht eine Abbruchbedingung und einen rekursiven Aufruf mit kleinerem Problem. Mehrfachberechnung (Fibonacci) macht sie exponentiell langsam, und jeder offene Aufruf belegt Speicher auf dem Aufrufstack.

  • Im binären Suchbaum liegen kleinere Werte links, größere rechts. Pre-, In- und Post-order sind die Traversierungen; In-order liefert sortierte Werte.

  • Binäre Suche (sortiertes Feld) braucht etwa $\log_2 n$ Vergleiche, lineare Suche bis zu $n$.

  • Selectionsort braucht $\frac{n(n-1)}{2}$ Vergleiche, Mergesort („Teile und herrsche“) etwa $n \cdot \log_2 n$, benötigt dafür aber zusätzlichen Speicher.

  • Systematisches Testen deckt Normal-, Grenz- und Fehlerfälle mit vorher festgelegten Erwartungen ab.

  • Operatoren: nennen Sie Operationen und Prinzipien, führen Sie aus Algorithmen Schritt für Schritt, begründen Sie Aufwandsabschätzungen und vergleichen Sie Verfahren.

Alle Aufgaben zu diesem Abschnitt anzeigen