Qualifikationsphase Q1 · Themenfeld Q1.4

Q1.4 – Höhere Datenstrukturen

Welche Struktur passt zu welchen Operationen?

In E3 wurden Arrays als erste strukturierte Datenform verwendet: Mehrere Werte liegen geordnet vor, werden über Indizes angesprochen und mit Schleifen oder Methoden verarbeitet. In Q1.1 wurden Objekte, Attribute, Methoden und Referenzen modelliert.

Q1.4 verbindet beides: Höhere Datenstrukturen organisieren mehrere Objekte so, dass bestimmte Operationen – Durchlaufen, Einfügen, Löschen, Aktualisieren, Suchen oder Abarbeiten – gezielt möglich werden. Entscheidend ist nicht nur, welche Daten vorhanden sind, sondern welche Operationen auf ihnen sinnvoll, sicher und effizient möglich sein sollen.

Die Renderer dieser Seite sind Werkzeuge zur Veranschaulichung. Ausgangspunkt ist aber die fachliche Frage: Welche Datenorganisation, welche Operationen und welche Invarianten passen zum Problem?

Kerncurriculum
Kerncurriculum kompakt
Modellierung, Operationen und Niveauunterschiede

Das Kerncurriculum verlangt hier keine Sammlung auswendig gelernter Strukturen. Im Mittelpunkt steht, Strukturen objektorientiert zu modellieren, ihre Operationen zu erklären und die Strukturwahl fachlich zu begründen.

Grundlegendes Niveau (Grundkurs und Leistungskurs)
Erhöhtes Niveau (Leistungskurs)
Arbeitsfokus: Beschreibe zu jeder Struktur zuerst das Modell, dann die erlaubten Operationen und erst danach eine mögliche Implementierung.
Brücke
Von Arrays und Objekten zu Datenstrukturen
E3 und Q1.1 als fachliche Grundlage

E3 hat gezeigt: Ein Array speichert mehrere Werte geordnet und erlaubt direkten Zugriff über einen Index. Diese Struktur ist stark, solange Größe und Positionen gut planbar sind. Sie wird aber unhandlich, wenn Daten dynamisch wachsen, häufig in der Mitte eingefügt oder gelöscht werden oder nicht linear, sondern hierarchisch organisiert sind.

Q1.1 liefert die zweite Grundlage: Objekte können Attribute, Methoden und Referenzen besitzen. Aus einem Objekt wird ein Knoten, wenn es neben seinem Inhalt auch Verweise auf andere Knoten trägt. Aus einzelnen Knoten entsteht eine Datenstruktur, wenn Referenzen, Operationen und Regeln zusammenpassen.

Aus Q1.1 ist bekannt: Objekte werden nicht wie primitive Werte einfach kopiert. Variablen enthalten bei Objekten in der Regel Referenzen. Genau diese Idee wird in Q1.4 zur Strukturidee: Datenstrukturen entstehen, indem Objekte über Referenzen miteinander verbunden werden.

Vergleich zwischen gemeinsamer Referenz und getrennten Objekten: Variablen a und b verweisen entweder auf dasselbe Objekt oder auf zwei getrennte Objekte.
Referenzen verbinden Variablen mit Objekten; mehrere Variablen können dasselbe Objekt erreichen.

Für verkettete Listen bedeutet das: Die Liste besteht nicht aus nebeneinanderliegenden Speicherzellen wie ein Array. Stattdessen verweisen Knotenobjekte auf Nachfolger. Wer Referenzen versteht, kann nachvollziehen, warum Einfügen und Löschen bei verketteten Listen als Umlenken von Verweisen modelliert wird.

Strukturidee Array / Datenfeld Verkettete Liste
Position durch Index durch Pfad ab head
Ordnung durch Speicherposition durch next-Referenzen
Zugriff direkt auf a[i] schrittweise durch Traversieren
Strukturänderung oft mit Verschieben verbunden oft durch Umlenken lokaler Referenzen

Eine Datenstruktur besteht damit aus drei zusammengehörigen Ebenen: Organisationsform, Operationen und Invarianten. Ein abstrakter Datentyp trennt zusätzlich die Frage Was darf man tun? von der Frage Wie ist es implementiert?

Anforderungen
Anforderungen an eine Datenstruktur
Von der Situation zum Operationsprofil

Stell dir eine Lerngruppe vor, die Quellenkarten für ein Referat sammelt. Neue Bücher und Webseiten kommen dazu, einzelne Karten werden umsortiert, fehlerhafte Angaben werden aktualisiert, manche Karten werden entfernt und am Ende sollen die Quellen in einer begründeten Reihenfolge abgearbeitet werden. Eine andere Situation wäre ein Bücherstapel: Das zuletzt aufgelegte Buch liegt oben. Eine Warteschlange arbeitet dagegen die zuerst Angekommenen zuerst ab.

Aus solchen Alltagssituationen entsteht die fachliche Frage nicht „Welche Struktur kenne ich?“, sondern: Welche Operationen treten häufig auf, und welche Regeln dürfen dabei nicht verletzt werden?

Datenmenge

Ist die Größe fest planbar oder wächst die Sammlung dynamisch?

Zugriff

Muss häufig eine Position direkt gelesen werden oder reicht schrittweises Traversieren?

Änderung

Wird vorne, hinten oder in der Mitte eingefügt, gelöscht und aktualisiert?

Ordnung

Muss Reihenfolge erhalten bleiben, gilt LIFO/FIFO oder gibt es eine Hierarchie?

Operationsprofil

Ein Operationsprofil beschreibt, welche Operationen besonders wichtig sind: get, set, insert, remove, traverse, push, pop, enqueue, dequeue, search oder Traversierungen im Baum.

Folgerung

Erst das Operationsprofil begründet die Datenstrukturwahl. Häufiger Indexzugriff spricht für ein Array; häufige lokale Referenzänderung für eine verkettete Liste; geregeltes Abarbeiten für Stack oder Queue; Hierarchie und Suchräume für Bäume.

Arbeitsauftrag: Notiere für „Quellenkarten sammeln“ drei häufige Operationen und entscheide, ob ein Array, eine verkettete Liste, ein Stack oder eine Queue naheliegt.
ADT
ADT: Operationen vor Implementierung
Der Vertrag einer Datenstruktur

Ein abstrakter Datentyp ist ein Vertrag: Er beschreibt, welche Operationen erlaubt sind und welches Verhalten sie haben. Die konkrete Speicherung bleibt zunächst offen. Eine lineare Liste erlaubt zum Beispiel Zugriff, Aktualisierung, Einfügen, Löschen und Durchlaufen einer geordneten Folge.

Was-Ebene

get(i) liefert ein Element, set(i, wert) aktualisiert einen Inhalt, insert(i, wert) fügt ein Element ein, remove(i) entfernt eines, traverse() besucht die Elemente in Reihenfolge.

Wie-Ebene

Dieselbe Liste kann intern durch ein Array oder durch Knoten mit Referenzen implementiert werden. Diese zweite Entscheidung beeinflusst Laufzeit, Speicherbedarf und Änderungsaufwand.

interface Liste<T> {
  int length();
  boolean isEmpty();
  T get(int index);
  void set(int index, T value);
  void insert(int index, T value);
  T remove(int index);
}

Der Bezug zu Q1.1 ist direkt: Ein ADT ist eine fachliche Schnittstelle. Konkrete Klassen implementieren diese Schnittstelle und kapseln ihre interne Darstellung nach dem Geheimnisprinzip.

Die folgende Rendererfläche zeigt eine lineare Folge als ADT-Idee: sichtbar ist die Reihenfolge, nicht die interne Speicherung.

Die Deutung ist absichtlich abstrakt: Die Operationen sprechen über eine geordnete Folge. Ob die Folge später in einem Datenfeld oder in Knoten liegt, ist noch nicht entschieden.

Arbeitsauftrag: Ergänze zu drei Operationen aus dem Interface je eine Vorbedingung oder Nachbedingung, zum Beispiel „Index muss gültig sein“.
Array
Array-basierte Liste
Direktzugriff, Aktualisieren und Verschiebungskosten

Die array-basierte Liste greift die E3-Erfahrung mit Datenfeldern wieder auf. Ein Array speichert Werte an nummerierten Positionen. Deshalb ist get(i) stark: Die Position ist über den Index unmittelbar bestimmbar.

Der einfache Fall ist Aktualisieren: set(i, wert) verändert den Inhalt einer vorhandenen Zelle. Die Struktur bleibt gleich. Einfügen und Löschen sind dagegen Strukturänderungen: Nachfolgende Elemente müssen häufig verschoben werden, damit die Reihenfolge und die lückenlose Indexstruktur erhalten bleiben.

class ArrayListe {
  private int[] daten;
  private int anzahl;

  int get(int index) {
    return daten[index];
  }

  void set(int index, int wert) {
    daten[index] = wert;       // Inhalt ändert sich, Struktur bleibt gleich
  }
}

Beispiel: Einfügen an Position 1

Vor dem Renderer steht die Vorfrage: Welche Elemente müssen ihren Platz verlassen, damit an Position 1 ein neuer Wert eingefügt werden kann?

Deutung der Darstellung
  • Direktzugriff: get(i) ist schnell, weil die Position berechnet werden kann.
  • Einfügen: Elemente rechts von der Einfügestelle müssen verschoben werden.
  • Löschen: Elemente rechts vom gelöschten Element rücken nach links.
  • Aktualisieren: Nur value ändert sich; die Positionen bleiben erhalten.
Methodenbezug

In einer Listenklasse werden get, set, insert und remove als Methoden modelliert. Die Klasse entscheidet intern, wann verschoben oder Kapazität erweitert werden muss.

Arbeitsauftrag: Begründe, warum set(2, 99) methodisch anders zu bewerten ist als insert(2, 99).
Verkettete Liste
Verkettete Liste und Knotenmodell
Objektstruktur, Referenzen und lokale Operationen

Die verkettete Liste ist der zentrale Q1.1-Anschluss in Q1.4. Ein Knoten ist ein Objekt mit Inhalt und Referenz: value speichert den Inhalt, next verweist auf den nächsten Knoten. Die Liste besitzt mit head einen Einstiegspunkt; je nach Implementierung kommen tail und current als Arbeitsreferenzen hinzu.

Bevor einzelne Operationen betrachtet werden, muss das Grundmodell der verketteten Liste klar sein: Ein Knoten speichert nicht nur einen Wert, sondern auch eine Referenz auf den nächsten Knoten. Die Liste besitzt mit head einen Einstiegspunkt. Von dort aus wird die Struktur Schritt für Schritt über next-Referenzen erschlossen.

Einfach verkettete Liste mit head-Zeiger, vier Knoten mit value und next sowie null am Ende.
In einer verketteten Liste entsteht Reihenfolge über next-Verweise.

Diese Darstellung macht zugleich die Grenze gegenüber dem Array sichtbar. Beim Array ist die Position über den Index unmittelbar erreichbar. In der verketteten Liste entsteht Position erst durch den Weg: Man beginnt bei head und folgt den next-Verweisen, bis der gesuchte Knoten erreicht ist.

class Node {
  int value;
  Node next;

  Node(int value) {
    this.value = value;
  }
}

Strukturmodell und Renderer

Die Abbildung sichert die Grundidee. Der Renderer zeigt anschließend dieselbe Struktur im Seitenkontext und macht sichtbar, welche Knoten beim Traversieren fokussiert werden.

Traversieren statt Direktzugriff

Traversieren bedeutet: current wandert entlang der next-Referenzen. Jede Bewegung folgt genau einer Verbindung. Deshalb ist der Zugriff auf eine spätere Position pfadbasiert und nicht direkt.

Node current = head;
while (current != null) {
  verarbeite(current.value);
  current = current.next;
}

Die fachliche Folgerung: Positionen sind in der verketteten Liste keine festen Adressen, sondern Ergebnisse einer Such- oder Laufbewegung.

Lokales Einfügen hinter current

Beim Einfügen ist nicht nur entscheidend, dass ein neuer Knoten entsteht. Entscheidend ist die Reihenfolge der Referenzänderungen. Wird zuerst current.next auf den neuen Knoten gesetzt, ohne den bisherigen Nachfolger zu sichern, kann der Rest der Liste unerreichbar werden.

Einfügen eines neuen Knotens hinter current in drei Schritten: vorher, new.next übernimmt current.next, danach zeigt current.next auf new.
Beim Einfügen muss zuerst der bisherige Nachfolger gesichert werden.

Die Abbildung zeigt deshalb die sichere Reihenfolge: Zuerst übernimmt der neue Knoten mit new.next den bisherigen Nachfolger. Erst danach wird current.next auf den neuen Knoten umgelenkt. So bleibt die Restliste während der gesamten Operation erreichbar.

void insertAfterCurrent(Node current, Node neu) {
  neu.next = current.next;
  current.next = neu;
}

Der Renderer zeigt die Operation zusätzlich als schrittweise Strukturveränderung innerhalb der Seitenlogik.

Lokales Löschen hinter current

Beim Löschen hinter current wird kein ganzer Speicherbereich verschoben. Stattdessen wird eine Referenz so umgelenkt, dass der Nachfolger von current übersprungen wird.

Löschen eines Knotens hinter current durch Umlenken der next-Referenz; der entfernte Knoten ist danach nicht mehr verknüpft.
Beim Löschen wird die Liste durch Umlenken einer Referenz geschlossen.

Der entfernte Knoten ist danach nicht mehr Teil der logischen Listenstruktur. Entscheidend ist: Die Liste bleibt von head aus erreichbar, weil current nun direkt auf den nächsten relevanten Knoten verweist.

void removeAfterCurrent(Node current) {
  if (current == null || current.next == null) return;
  current.next = current.next.next;
}

Der Renderer ergänzt diese Referenzlogik als dynamische Darstellung. Er ersetzt die Bilddeutung nicht, sondern zeigt, wie die Operation im Ablauf gelesen werden kann.

Aktualisieren

current.value = neuerWert; ändert nur den Inhalt eines Knotens. Die Referenzen bleiben unverändert. Das ist eine Inhaltsänderung, keine Strukturänderung.

Kapselung

Eine Listenklasse sollte nicht erlauben, dass fremder Code beliebig next-Referenzen verändert. Methoden kapseln die Operationen und schützen die Listeninvariante.

Arbeitsauftrag: Zeichne die Liste 12 → 24 → 36 nach insertAfterCurrent, wenn current auf 24 zeigt.
Stack
Stack als LIFO-ADT
Zugriffsbeschränkung am oberen Ende

Ein Stack ist nicht einfach „eine Liste, die senkrecht gezeichnet wird“. Als ADT legt er fest, dass nur ein Zugriffspunkt frei ist: top. Neue Elemente werden mit push oben abgelegt, pop entfernt genau das oberste Element, top oder peek liest es nur.

Operationen
  • push(value): oben einfügen
  • pop(): oberstes Element entfernen und liefern
  • top() / peek(): oberstes Element lesen
  • isEmpty(): Leere prüfen
LIFO

LIFO bedeutet last in, first out. Das zuletzt eingefügte Element wird zuerst entfernt. Diese Regel entsteht aus der Zugriffsbeschränkung, nicht aus der Darstellung.

Vor dem Renderer steht die Vorfrage: Welches Element darf als nächstes entfernt werden, wenn nur top zugänglich ist?

Stack-Anwendungen sind Browser-Zurück, Undo-Funktionen, Klammerprüfung und der Call-Stack aus Q1.3: Rücksprunginformationen werden zuletzt abgelegt und zuerst wieder benötigt.

class Stack {
  private Node top;

  void push(int value) {
    Node neu = new Node(value);
    neu.next = top;
    top = neu;
  }
}

Objektorientiert nutzt ein Stack intern Knoten oder eine Liste, gibt aber nur Stack-Operationen frei. Das ist das Geheimnisprinzip aus Q1.1: Die interne Struktur bleibt gekapselt.

Arbeitsauftrag: Begründe, warum pop beim Stack nicht auf ein beliebiges Element zugreifen darf.
Queue
Queue als FIFO-ADT
Einfügen hinten, Entnehmen vorne

Eine Queue besitzt zwei definierte Enden: vorne front oder head, hinten tail. Mit enqueue wird hinten eingefügt, mit dequeue vorne entnommen. Dadurch entsteht FIFO: first in, first out.

Operationen
  • enqueue(value): hinten anfügen
  • dequeue(): vorne entfernen und liefern
  • front() / peek(): vorderes Element lesen
  • isEmpty(): Leere prüfen
Stack vs. Queue

Der Stack arbeitet an einem Ende. Die Queue arbeitet an zwei Enden: hinten wächst die Struktur, vorne wird abgearbeitet. Das macht sie für faire Reihenfolgen geeignet.

Vor dem Renderer steht die Vorfrage: Welches Element muss als nächstes entnommen werden, damit die Ankunftsreihenfolge erhalten bleibt?

Typische Anwendungen sind Warteschlangen, Druckerwarteschlangen, Ticketsysteme, Ereignisverarbeitung und als Ausblick Breitensuche (BFS). Auch hier gilt Kapselung: Die interne Liste wird nicht direkt manipuliert, sondern über enqueue, dequeue und front genutzt.

class Queue {
  private Node head;
  private Node tail;

  void enqueue(int value) {
    Node neu = new Node(value);
    if (tail != null) tail.next = neu;
    tail = neu;
    if (head == null) head = neu;
  }
}
Arbeitsauftrag: Vergleiche Stack und Queue am Beispiel einer Aufgabenliste: Wann ist „zuletzt zuerst“ sinnvoll, wann „zuerst zuerst“?
Bäume
Bäume und binäre Suchbäume
Hierarchie, Rekursion, Traversierung und Ordnungsregel

Warum lineare Strukturen nicht reichen

Listen, Stacks und Queues organisieren Daten linear. Viele Probleme sind aber verzweigt: Ordner enthalten Unterordner, Entscheidungen führen in unterschiedliche Suchräume, eine Organisation hat Über- und Unterordnungen. Dafür braucht man Strukturen, in denen ein Knoten mehrere Nachfolger besitzen kann.

Vor dem Renderer steht die Vorfrage: Welche Beziehungen kann eine lineare Liste nur umständlich ausdrücken?

Grundbegriffe sauber lesen

Begriff Bedeutung Algorithmische Frage
WurzelKnoten ohne ElternknotenWo beginnt der Zugriff?
KnotenObjekt mit Inhalt und KindreferenzenWelcher Wert wird verarbeitet?
KanteVerbindung zwischen Eltern- und KindknotenWelcher Pfad ist möglich?
BlattKnoten ohne KinderWo endet ein Such- oder Traversierungspfad?
Innerer KnotenKnoten mit mindestens einem KindWo verzweigt die Struktur?
TeilbaumKnoten mit allen darunterliegenden KnotenWelche kleinere Struktur ist wieder ein Baum?
Tiefe / HöheAbstand zur Wurzel bzw. längster Weg nach untenWie lang können Pfade werden?

Teilbäume als Brücke zu Q1.3

Jeder Teilbaum ist wieder ein Baum. Genau diese Selbstähnlichkeit greift Q1.3 auf: Basisfall, Teilproblem und Rekursionsschritt lassen sich bei Baumoperationen besonders natürlich formulieren. Q1.4 fragt zusätzlich, welche Datenstruktur diese Operation überhaupt trägt.

void inorder(Node node) {
  if (node == null) return;     // Basisfall
  inorder(node.left);           // linker Teilbaum
  verarbeite(node.value);       // aktueller Knoten
  inorder(node.right);          // rechter Teilbaum
}

Binärer Baum und Traversierungen

Ein binärer Baum hat pro Knoten höchstens zwei Kinder: linken und rechten Teilbaum. Dadurch werden Traversierungen eindeutig beschreibbar: Preorder besucht zuerst den Knoten, Inorder zwischen linkem und rechtem Teilbaum, Postorder nach den Teilbäumen.

Inorder liefert im binären Suchbaum eine sortierte Reihenfolge, weil die Ordnungsregel für jeden Knoten gilt: links kleiner, rechts größer.

Binärer Suchbaum: Invariante und Operationen

Ein binärer Suchbaum ist ein binärer Baum mit Ordnungsregel. Diese Regel ist eine Invariante: Sie muss nach Suchen, Einfügen, Löschen und Aktualisieren gültig bleiben. Eine Aktualisierung des Schlüssels ist deshalb nicht beliebig harmlos; sie kann die Ordnungsregel verletzen und muss geprüft oder als Entfernen plus Einfügen modelliert werden.

Suchen

Jeder Vergleich entscheidet, ob links oder rechts weitergesucht wird. Der andere Teilbaum wird verworfen.

Einfügen

Einfügen ist eine Suche nach einem freien Kind entlang der Ordnungsregel. Der neue Knoten wird Blatt.

Löschen

Blatt entfernen, ein Kind hochziehen oder bei zwei Kindern mit Inorder-Nachfolger/-Vorgänger ersetzen.

Traversieren

Preorder, Inorder und Postorder beschreiben verschiedene Besuchsreihenfolgen derselben Struktur.

Die folgenden Rendererflächen zeigen die Operationen nicht als Dekoration, sondern als Lesespuren im Suchbaum.

Arbeitsauftrag: Führe die Inorder-Traversierung auf dem dargestellten BST durch und begründe, warum das Ergebnis sortiert ist.
Vergleich
Vergleich und begründete Strukturwahl
Vom Operationsprofil zur Entscheidung

Strukturwahl ist keine Geschmackssache. Sie hängt davon ab, welche Operationen häufig sind, welche Invarianten gelten und ob die Daten linear, zugriffsbeschränkt oder hierarchisch organisiert werden müssen.

Kriterium Array-Liste Verkettete Liste Stack Queue Baum / BST
Struktur linear, positionsbasiert linear, referenzbasiert linear, ein Zugriffspunkt linear, zwei Zugriffspunkte hierarchisch, verzweigt
Starker Fall häufig get(i) oder set(i) lokales Einfügen/Löschen nach gefundener Position letzte Eingabe zuerst bearbeiten erste Eingabe zuerst bearbeiten Hierarchie oder geordneter Suchraum
Kritischer Fall Einfügen/Löschen in der Mitte Direktzugriff auf entfernte Position Zugriff auf beliebiges Element Zugriff auf beliebiges Element unbalancierte oder ungültige Ordnung
Leitfrage Brauche ich Positionsadressierung? Kann ich über Pfade arbeiten? Soll nur top sichtbar sein? Soll Reihenfolge fair erhalten bleiben? Gibt es Verzweigung oder Suchpfade?

Welche Struktur passt, wenn ...?

... häufig get(i) gebraucht wird?

Array-Liste, weil der Index direkten Zugriff ermöglicht.

... vorne häufig eingefügt und gelöscht wird?

Verkettete Liste, Stack oder Queue, abhängig von der erlaubten Zugriffsdisziplin.

... die letzte Aktion zuerst rückgängig gemacht wird?

Stack, weil LIFO genau dieses Muster erzwingt.

... Anfragen fair abgearbeitet werden?

Queue, weil FIFO die Ankunftsreihenfolge erhält.

... Daten hierarchisch organisiert sind?

Baum, weil Verzweigungen und Teilbäume modelliert werden können.

... dynamisch sortiert gesucht wird?

Binärer Suchbaum, wenn die Ordnungsregel erhalten bleibt.

Arbeitsauftrag: Entscheide für drei eigene Anwendungssituationen die passende Datenstruktur und begründe jeweils mit Operationen statt mit Namen.
Sicherung
Tool- und Sicherungsperspektive
Darstellungen lesen, Operationen erklären, Auswahl begründen

Die PNG-Abbildungen sichern zentrale Referenzideen: Objektvariablen verweisen auf Objekte, Listen entstehen durch next-Verweise, Einfügen sichert zuerst den alten Nachfolger und Löschen schließt die Liste durch Referenzumlenkung. Die Renderer bleiben ergänzend wichtig, weil sie Operationen und Varianten im Seitenkontext schrittweise darstellen.

Modell lesen

Markiere head, current, tail, value, next oder Baumbegriffe.

Operation beschreiben

Formuliere vor dem Code, welche Referenz, welcher Inhalt oder welcher Teilbaum verändert wird.

Pseudocode ergänzen

Ergänze fehlende Zuweisungen und prüfe, ob dabei Daten erreichbar bleiben.

Aufwand begründen

Beziehe die Laufzeit auf Indexzugriff, Traversieren, Endenzugriff oder Suchpfadlänge.

Der Rückbezug auf Q1.3 bleibt besonders im Baumteil wichtig: Viele Baumoperationen werden rekursiv formuliert. Q1.4 erweitert diesen Blick um die Strukturfrage: Welche Organisation der Daten macht die Operation überhaupt passend?