Modul 3 von 6

Algorithmen und Datenstrukturen

Ein Programm, das mit zehn Datensätzen funktioniert, kann bei zehn Millionen stillstehen. In diesem Modul lernst du, das vorher zu erkennen - und die richtige Struktur für die jeweilige Aufgabe zu wählen. Das ist die Vorlesung, die im Studium am meisten aussiebt, und zugleich die, die am meisten hängen bleibt.

Fortschritt in diesem Modul 0 von 7 Lektionen

1. Was ist ein Algorithmus?

Ein Algorithmus ist eine eindeutige Handlungsvorschrift, die ein Problem in endlich vielen Schritten löst. Er ist unabhängig von der Programmiersprache - man kann ihn auf einer Serviette aufschreiben.

Die fünf Eigenschaften (Prüfungswissen)
  • Eindeutigkeit: Jeder Schritt ist unmissverständlich beschrieben.
  • Ausführbarkeit: Jeder Schritt ist tatsächlich durchführbar.
  • Endlichkeit: Die Beschreibung ist endlich lang.
  • Terminierung: Das Verfahren kommt nach endlich vielen Schritten zum Ende.
  • Determiniertheit: Gleiche Eingabe führt zu gleichem Ergebnis.
Warum ein Rezept kein Algorithmus ist

"Salz nach Geschmack" verletzt die Eindeutigkeit. "Rühren, bis der Teig gut aussieht" verletzt die Terminierung - es gibt kein prüfbares Abbruchkriterium. Genau an diesen Stellen scheitern übrigens auch Anforderungen aus Fachabteilungen: "Zeig mir die wichtigsten Kunden" ist kein umsetzbarer Auftrag, "sortiere nach Jahresumsatz und zeig die obersten zehn" schon.

Pseudocode

Zum Aufschreiben von Algorithmen nutzt man Pseudocode: strukturiert wie ein Programm, aber ohne Rücksicht auf Syntax. In Klausuren wird oft genau das verlangt.

ALGORITHMUS GroesstesElement(liste)
  WENN liste ist leer DANN
      GIB ZURÜCK "kein Element"

  groesstes ← liste[0]

  FÜR i VON 1 BIS länge(liste) - 1
      WENN liste[i] > groesstes DANN
          groesstes ← liste[i]

  GIB ZURÜCK groesstes

2. Laufzeit und die O-Notation

Die entscheidende Frage bei jedem Algorithmus lautet nicht "wie viele Sekunden braucht er?", sondern "wie stark wächst der Aufwand, wenn die Datenmenge wächst?" Sekunden hängen vom Rechner ab, das Wachstumsverhalten nicht.

Das Telefonbuch

Einen Namen im Telefonbuch findest du in Sekunden, obwohl Millionen Einträge drinstehen - weil du aufschlägst, vergleichst und eine Hälfte wegwirfst. Würdest du stattdessen Zeile für Zeile von vorn lesen, säßest du Tage daran. Beide Male dieselben Daten, derselbe Mensch, aber ein völlig anderes Wachstumsverhalten.

Die O-Notation beschreibt genau dieses Wachstum. Konstante Faktoren werden weggelassen: Ob ein Schritt 2 oder 20 Mikrosekunden dauert, ist bei genügend großen Datenmengen egal - die Wachstumsklasse nicht.

KlasseNameBei 1 000 ElementenBei 1 000 000Beispiel
O(1)konstant1 Schritt1 Schritt Zugriff auf ein Array-Element
O(log n)logarithmischca. 10ca. 20 binäre Suche
O(n)linear1 0001 000 000 eine Liste einmal durchgehen
O(n log n)linear-logarithmischca. 10 000ca. 20 Mio. gute Sortierverfahren
O(n²)quadratisch1 Million1 Billion zwei verschachtelte Schleifen
O(2ⁿ)exponentiellpraktisch unberechenbar alle Teilmengen durchprobieren
Was diese Zahlen bedeuten

Nimm an, ein Schritt dauert eine Mikrosekunde. Bei einer Million Datensätze braucht ein O(n)-Verfahren eine Sekunde. Ein O(n²)-Verfahren braucht für dieselben Daten rund elfeinhalb Tage. Das ist kein Unterschied, den man mit einem schnelleren Server behebt.

Wie man die Klasse abliest

Eine brauchbare Faustregel für Klausuren:

  • Keine Schleife, nur ein paar Rechnungen → O(1)
  • Eine Schleife über alle n Elemente → O(n)
  • Eine Schleife in einer Schleife, beide über n → O(n²)
  • Der Suchbereich halbiert sich in jedem Schritt → O(log n)
  • Halbieren und jede Ebene einmal durchgehen → O(n log n)

3. Suchen: linear und binär

Lineare Suche

Von vorn anfangen und jedes Element prüfen. Funktioniert immer, auch bei unsortierten Daten. Aufwand: O(n).

Binäre Suche

Setzt sortierte Daten voraus. Man schaut in die Mitte, vergleicht und verwirft die Hälfte, in der der gesuchte Wert nicht liegen kann. Aufwand: O(log n).

Gesucht: 23 in [4, 8, 15, 16, 23, 42, 50, 68]

Schritt 1:  Mitte ist 16.   23 > 16  →  rechte Hälfte behalten
            [23, 42, 50, 68]
Schritt 2:  Mitte ist 50.   23 < 50  →  linke Hälfte behalten
            [23, 42]
Schritt 3:  Mitte ist 23.   gefunden!

3 Schritte statt 5 bei linearer Suche.
Warum log n so mächtig ist

Bei einer Million sortierter Einträge braucht die binäre Suche höchstens 20 Schritte. Bei einer Milliarde sind es 30. Jede Verdopplung der Datenmenge kostet genau einen zusätzlichen Schritt. Das ist der Grund, warum Datenbanken Indizes anlegen - ein Index ist nichts anderes als eine sortierte Struktur, in der sich binär suchen lässt.

Der Preis des Sortierens

Binäre Suche ist nur schneller, wenn die Daten bereits sortiert sind. Einmal sortieren kostet O(n log n) - mehr als eine einzelne lineare Suche. Die Faustregel: Wird die Liste oft durchsucht und selten geändert, lohnt sich das Sortieren. Bei einer einmaligen Suche lohnt es sich nicht.

4. Sortierverfahren

Sortieren ist das Standardbeispiel der Algorithmik, weil sich daran alles zeigen lässt: verschiedene Ansätze für dasselbe Problem, mit messbar unterschiedlichem Aufwand. Wähl unten ein Verfahren und sieh ihm zu.

Worauf du beim Zusehen achten solltest
  • Bei Bubblesort wandert der größte Wert in jedem Durchlauf ganz nach rechts - der grüne Bereich wächst von hinten.
  • Bei Insertionsort ist der linke Teil immer schon sortiert; jedes neue Element wird hineingeschoben.
  • Bei Quicksort siehst du, wie sich um den Trennwert herum zwei Bereiche bilden, die dann getrennt weiterbearbeitet werden.
  • Vergleich die Zähler: Stell 50 Balken ein und schau, wie viele Vergleiche Bubblesort gegenüber Quicksort braucht.
VerfahrenMittelSchlechtester FallStabil?Wann sinnvoll
BubblesortO(n²)O(n²)janur zum Lernen
SelectionsortO(n²)O(n²)neinwenn Vertauschen teuer ist
InsertionsortO(n²)O(n²)jakleine oder fast sortierte Mengen
MergesortO(n log n)O(n log n)jawenn garantierte Laufzeit zählt
QuicksortO(n log n)O(n²)neinStandardfall in der Praxis
Was „stabil“ bedeutet - und warum es zählt

Ein Sortierverfahren heißt stabil, wenn Elemente mit gleichem Schlüssel ihre ursprüngliche Reihenfolge behalten. Beispiel: Du sortierst eine Kundenliste erst nach Name, dann nach Ort. Bei einem stabilen Verfahren stehen die Kunden innerhalb jedes Orts weiterhin alphabetisch. Bei einem instabilen ist diese Ordnung dahin. Genau deshalb ist Stabilität in Berichten und Auswertungen wichtig.

5. Array und verkettete Liste

Eine Datenstruktur legt fest, wie Daten im Speicher angeordnet sind. Diese Anordnung entscheidet, welche Operationen schnell sind und welche nicht.

Array - die Reihenhaussiedlung

Alle Elemente liegen direkt hintereinander im Speicher. Aus der Startadresse und dem Index lässt sich jede Adresse sofort ausrechnen.

Zugriff per Index: O(1)
Einfügen in der Mitte: O(n) - alles dahinter muss verschoben werden

Verkettete Liste - die Schnitzeljagd

Die Elemente liegen verstreut. Jedes enthält neben seinem Wert einen Verweis auf das nächste. Man muss der Kette folgen.

Zugriff per Index: O(n)
Einfügen an bekannter Stelle: O(1) - nur zwei Verweise umhängen

Die Grundeinsicht dieses Moduls

Es gibt keine beste Datenstruktur, nur passende. Ein Array ist schnell im Lesen und langsam im Einfügen, eine verkettete Liste genau umgekehrt. Welche du wählst, hängt davon ab, was dein Programm überwiegend tut - und diese Frage stellst du dir am besten, bevor du anfängst zu programmieren.

6. Stack und Queue

Zwei Strukturen, die sich nur darin unterscheiden, an welchem Ende Elemente herauskommen - und die dadurch völlig verschiedene Aufgaben lösen.

Stack (Stapel, LIFO)

Wie ein Stapel Teller: Der zuletzt abgelegte kommt zuerst wieder herunter.

Wo er steckt: die Rückgängig-Funktion in jedem Programm, der Zurück-Knopf im Browser, die Verwaltung von Funktionsaufrufen im Computer (deshalb heißt es "Stack Overflow", wenn sich eine Funktion endlos selbst aufruft).

Queue (Warteschlange, FIFO)

Wie die Schlange an der Kasse: Wer zuerst kam, wird zuerst bedient.

Wo sie steckt: Druckaufträge, eingehende Bestellungen, Nachrichten zwischen Systemen. In SAP-Landschaften laufen fast alle Schnittstellen über Queues, damit bei einem Systemausfall nichts verloren geht.

7. Bäume und Hashtabellen

Bäume

Ein Baum ordnet Daten hierarchisch: eine Wurzel, darunter Knoten, ganz unten Blätter. Im binären Suchbaum gilt für jeden Knoten: links stehen kleinere Werte, rechts größere.

            50
           /  \
         30     70
        /  \   /  \
      20   40 60   80

Suche nach 40:  50 → zu klein, nach links
                30 → zu groß, nach rechts
                40 → gefunden. Drei Vergleiche.

Solange der Baum ausgeglichen ist, kostet Suchen, Einfügen und Löschen jeweils O(log n). Datenbankindizes verwenden eine Weiterentwicklung davon, den B-Baum - bei einer Tabelle mit Millionen Zeilen führt er in einer Handvoll Schritten zum Ziel.

Wenn ein Baum entartet

Fügt man in einen einfachen Suchbaum bereits sortierte Daten ein (1, 2, 3, 4, …), hängt jeder Knoten rechts am vorigen - der Baum wird zur Liste und die Suche fällt auf O(n) zurück. Deshalb gibt es selbstbalancierende Bäume, die sich beim Einfügen automatisch neu ordnen.

Hashtabellen

Die schnellste Struktur zum Nachschlagen. Eine Hashfunktion rechnet aus dem Schlüssel direkt die Speicherstelle aus - ohne jedes Suchen. Aufwand: O(1) im Mittel.

Kollisionen

Zwei verschiedene Schlüssel können auf dasselbe Fach zeigen. Das ist unvermeidlich und kein Fehler - man hängt die Werte dann im Fach aneinander. Erst wenn zu viele Kollisionen auftreten, sinkt die Geschwindigkeit von O(1) Richtung O(n). Eine gute Hashfunktion verteilt deshalb möglichst gleichmäßig.

StrukturSuchenEinfügenSortiert?
Array (unsortiert)O(n)O(1) am Endenein
Array (sortiert)O(log n)O(n)ja
Verkettete ListeO(n)O(1)nein
Ausgeglichener BaumO(log n)O(log n)ja
HashtabelleO(1)O(1)nein
Die Entscheidungsfrage

Brauchst du die Daten in Reihenfolge? Dann Baum. Willst du nur möglichst schnell nachschlagen? Dann Hashtabelle. Das ist der eine Unterschied, auf den es in der Praxis meistens hinausläuft.

Übungen

Aufgabe 1 · leicht

Welche Komplexitätsklasse hat dieser Code?

for (let i = 0; i < n; i++) {
  for (let j = 0; j < n; j++) {
    summe += i * j;
  }
}
Lösung anzeigen

O(n²). Die äußere Schleife läuft n mal, die innere für jeden Durchlauf noch einmal n mal - zusammen n · n Durchläufe.

Aufgabe 2 · leicht

Und diese hier?

let i = n;
while (i > 1) {
  i = i / 2;
}
Lösung anzeigen

O(log n). In jedem Durchlauf halbiert sich i. Von 1 000 000 kommt man in etwa 20 Schritten unter 1 - genau das beschreibt der Logarithmus zur Basis 2.

Aufgabe 3 · mittel

Ein Verfahren braucht für 1000 Datensätze 2 Sekunden. Wie lange braucht es für 4000 Datensätze, wenn es O(n) ist - und wie lange, wenn es O(n²) ist?

Lösung anzeigen

O(n): Die Datenmenge vervierfacht sich, also vervierfacht sich die Zeit: 8 Sekunden.

O(n²): Die Zeit wächst mit dem Quadrat. Vierfache Datenmenge bedeutet 4² = 16-fache Zeit: 32 Sekunden. Bei 40 000 Datensätzen wären es schon über eine halbe Stunde.

Aufgabe 4 · mittel

Implementiere die binäre Suche. Gib den Index des gesuchten Werts zurück, oder -1, wenn er nicht vorkommt.

Lösung anzeigen
function binaereSuche(liste, ziel) {
  let links = 0;
  let rechts = liste.length - 1;

  while (links <= rechts) {
    const mitte = Math.floor((links + rechts) / 2);

    if (liste[mitte] === ziel) return mitte;
    if (liste[mitte] < ziel) links = mitte + 1;
    else rechts = mitte - 1;
  }
  return -1;
}

Die drei klassischen Fallen: <= statt < in der Schleifenbedingung (sonst wird das letzte Element nie geprüft); mitte + 1 und mitte - 1 statt mitte (sonst Endlosschleife); und das Abrunden mit Math.floor.

Aufgabe 5 · schwer

Eine Anwendung verwaltet 5 Millionen Artikel. Sie muss sehr häufig einen Artikel anhand seiner Artikelnummer finden und außerdem regelmäßig alle Artikel nach Preis sortiert ausgeben. Welche Datenstrukturen wählst du?

Lösung anzeigen

Die beiden Anforderungen widersprechen sich - also nimmt man beides:

  • Eine Hashtabelle mit der Artikelnummer als Schlüssel für das Nachschlagen in O(1).
  • Zusätzlich einen ausgeglichenen Suchbaum (oder ein sortiertes Array), geordnet nach Preis, für die sortierte Ausgabe.

Beide verweisen auf dieselben Artikelobjekte, die Daten liegen also nur einmal im Speicher. Der Preis dafür: Bei jeder Änderung müssen beide Strukturen gepflegt werden. Genau das macht eine Datenbank, wenn du einen zweiten Index anlegst - Lesen wird schneller, Schreiben etwas langsamer. Diese Abwägung begegnet dir in Modul 4 wieder.

Selbsttest

Karteikarten zu diesem Modul

Wenn du tiefer einsteigen willst

  • visualgo.net - Animationen zu praktisch jedem Verfahren, das im Studium vorkommt. Nach dieser Seite die beste Anlaufstelle zum Nachschauen.
  • "Algorithmen und Datenstrukturen" von Thomas Ottmann und Peter Widmayer - das deutschsprachige Standardwerk.
  • "Grokking Algorithms" von Aditya Bhargava - erklärt mit Zeichnungen statt Formeln. Gut geeignet, wenn dir die Vorlesung zu formal wird.