Algorithmen

KAPITEL 04 · PROGRAMMIERUNG

Algorithmen

Die Grundlage effizienter Programmierung – von Sortier- und Suchalgorithmen über Graph-Algorithmen bis zur Komplexitätsanalyse (Big O). Lernen Sie, wie man Probleme systematisch löst.

Sortieralgorithmen Suchalgorithmen Big O Notation Graph-Algorithmen

Inhaltsverzeichnis

Schnellübersicht

Auf dieser Seite lernen Sie alles über Algorithmen:

  • Definition: Was ist ein Algorithmus?
  • Eigenschaften: Determinismus, Finitheit, Eindeutigkeit
  • Komplexität: Big O Notation (O(1), O(log n), O(n), O(n²))
  • Sortieralgorithmen: Bubble Sort, Quick Sort, Merge Sort
  • Suchalgorithmen: Lineare Suche, Binäre Suche
  • Graph-Algorithmen: Dijkstra, BFS, DFS
  • Best Practices: Effizienz, Lesbarkeit, Testing
  • FAQ: Häufige Fragen zu Algorithmen

1. Was ist ein Algorithmus?

Definition

Ein Algorithmus ist eine endliche Folge von eindeutigen Anweisungen zur Lösung eines Problems oder zur Erreichung eines Ziels. Er nimmt eine Eingabe entgegen, verarbeitet sie durch eine definierte Abfolge von Schritten und liefert eine Ausgabe.

Algorithmen sind das Herzstück der Informatik – sie steuern alles von Suchmaschinen über Navigationssysteme bis hin zu KI-Modellen. Ein guter Algorithmus ist effizient (schnell, speicherschonend), korrekt (liefert das richtige Ergebnis) und wartbar (leicht zu verstehen und anzupassen).

Beispiele aus dem Alltag: Ein Kochrezept ist ein Algorithmus (Eingabe: Zutaten, Ausgabe: Gericht). Eine Wegbeschreibung ist ein Algorithmus (Eingabe: Start- und Zielpunkt, Ausgabe: Route). Ein Sortieralgorithmus ordnet eine Liste nach bestimmten Kriterien (z.B. alphabetisch oder numerisch).

Die 4 wichtigsten Eigenschaften

Jeder Algorithmus sollte diese grundlegenden Eigenschaften erfüllen:

Determinismus

Bei gleicher Eingabe liefert der Algorithmus immer das gleiche Ergebnis. Keine Zufälligkeit oder Seiteneffekte.

Finitheit

Der Algorithmus terminiert nach endlich vielen Schritten. Er darf nicht in einer Endlosschleife laufen.

Eindeutigkeit

Jeder Schritt ist klar definiert und lässt nur eine Interpretation zu. Keine mehrdeutigen Anweisungen.

Effektivität

Jeder Schritt ist in endlicher Zeit ausführbar. Der Algorithmus sollte praktikabel und effizient sein.

2. Komplexität – Big O Notation

Die Big O Notation beschreibt, wie die Laufzeit oder der Speicherbedarf eines Algorithmus mit wachsender Eingabegröße (n) skaliert. Sie ist das wichtigste Werkzeug zur Analyse von Algorithmen.

O(1)

Konstant

Laufzeit ist unabhängig von der Eingabegröße. Immer gleich schnell.

Beispiel: Zugriff auf Array-Element, Hash-Table Lookup
O(log n)

Logarithmisch

Sehr effizient. Bei Verdopplung der Eingabe nur ein Schritt mehr.

Beispiel: Binäre Suche, Balanced Binary Tree
O(n)

Linear

Laufzeit wächst proportional zur Eingabegröße. Verdopplung = doppelte Zeit.

Beispiel: Lineare Suche, Schleife über Array
O(n log n)

Linearithmisch

Gut für Sortieralgorithmen. Etwas schlechter als linear, aber viel besser als quadratisch.

Beispiel: Merge Sort, Quick Sort (average), Heap Sort
O(n²)

Quadratisch

Schlecht für große Eingaben. Verdopplung = 4-fache Zeit. Bei n=1000: 1 Million Operationen.

Beispiel: Bubble Sort, Insertion Sort, verschachtelte Schleifen
O(2ⁿ)

Exponentiell

Extrem schlecht. Schon bei n=30: 1 Milliarde Operationen. Nur für sehr kleine Eingaben praktikabel.

Beispiel: Rekursive Fibonacci, Traveling Salesman (brute force)

Warum ist Big O wichtig?

Big O hilft Ihnen, die Skalierbarkeit eines Algorithmus zu verstehen. Ein O(n²)-Algorithmus mag für 100 Elemente schnell sein, aber bei 10.000 Elementen wird er extrem langsam. Ein O(n log n)-Algorithmus bleibt auch bei großen Eingaben effizient. Bei der Wahl zwischen Algorithmen sollten Sie immer die erwartete Eingabegröße berücksichtigen.

3. Sortieralgorithmen

Sortieralgorithmen ordnen Elemente einer Liste in einer bestimmten Reihenfolge (z.B. aufsteigend oder absteigend). Sie gehören zu den häufigsten Operationen in der Programmierung.

01

Bubble Sort

Einfach, aber ineffizient

Vergleicht benachbarte Elemente und tauscht sie, wenn sie in falscher Reihenfolge sind. Wiederholt dies, bis keine Vertauschungen mehr nötig sind.

  • Einfach zu implementieren
  • In-Place (kein zusätzlicher Speicher)
  • Stabil (Reihenfolge gleicher Elemente bleibt)
  • Sehr langsam für große Listen
Komplexität: Best: O(n) · Average: O(n²) · Worst: O(n²) · Speicher: O(1)
02

Quick Sort

Schnell und effizient

Wählt ein Pivot-Element, partitioniert die Liste in Elemente kleiner/größer als Pivot und sortiert rekursiv. Divide-and-Conquer-Strategie.

  • Sehr schnell in der Praxis
  • In-Place (geringer Speicherbedarf)
  • Cache-freundlich
  • Nicht stabil
  • Worst-Case bei schlechter Pivot-Wahl
Komplexität: Best: O(n log n) · Average: O(n log n) · Worst: O(n²) · Speicher: O(log n)
03

Merge Sort

Stabil und vorhersehbar

Teilt die Liste rekursiv in zwei Hälften, sortiert jede Hälfte und merged sie wieder zusammen. Garantiert O(n log n) in allen Fällen.

  • Garantiert O(n log n)
  • Stabil
  • Gut für große Datenmengen
  • Benötigt zusätzlichen Speicher O(n)
  • Langsamer als Quick Sort in der Praxis
Komplexität: Best: O(n log n) · Average: O(n log n) · Worst: O(n log n) · Speicher: O(n)
Algorithmus Best Case Average Worst Case Speicher Stabil
Bubble Sort O(n) O(n²) O(n²) O(1)
Insertion Sort O(n) O(n²) O(n²) O(1)
Selection Sort O(n²) O(n²) O(n²) O(1)
Quick Sort O(n log n) O(n log n) O(n²) O(log n)
Merge Sort O(n log n) O(n log n) O(n log n) O(n)
Heap Sort O(n log n) O(n log n) O(n log n) O(1)

Welchen Sortieralgorithmus wann verwenden?

  • Kleine Listen (< 50 Elemente): Insertion Sort (einfach, schnell für kleine Daten)
  • Allgemeine Sortierung: Quick Sort (schnell, speichereffizient)
  • Stabile Sortierung nötig: Merge Sort (garantiert Stabilität)
  • Speicherbeschränkung: Heap Sort (O(1) Speicher, garantiert O(n log n))
  • Bereits fast sortiert: Insertion Sort (O(n) im Best Case)

4. Suchalgorithmen

Suchalgorithmen finden ein bestimmtes Element in einer Datenstruktur. Die Wahl des Algorithmus hängt davon ab, ob die Daten sortiert sind oder nicht.

01

Lineare Suche

Einfach, aber langsam

Geht die Liste Element für Element durch, bis das gesuchte Element gefunden wird oder das Ende erreicht ist. Funktioniert auf unsortierten Daten.

  • Funktioniert auf unsortierten Daten
  • Einfach zu implementieren
  • Kein zusätzlicher Speicher
  • Langsam für große Listen O(n)
Komplexität: Best: O(1) · Average: O(n) · Worst: O(n) · Speicher: O(1)
02

Binäre Suche

Schnell, aber sortierte Daten nötig

Teilt die sortierte Liste wiederholt in zwei Hälften und vergleicht das mittlere Element mit dem gesuchten Wert. Eliminiert jeweils eine Hälfte.

  • Extrem schnell O(log n)
  • Effizient für große Datenmengen
  • Minimaler Speicherbedarf
  • Daten müssen sortiert sein
  • Sortieren kostet O(n log n)
Komplexität: Best: O(1) · Average: O(log n) · Worst: O(log n) · Speicher: O(1)

Lineare vs. Binäre Suche – Wann was?

Lineare Suche: Verwenden Sie, wenn die Daten unsortiert sind, nur einmal gesucht wird oder die Liste sehr klein ist (< 50 Elemente).

Binäre Suche: Verwenden Sie, wenn die Daten bereits sortiert sind oder Sie häufig suchen müssen. Das Sortieren lohnt sich, wenn Sie mehr als log(n) Suchoperationen durchführen.

Beispiel: Bei 1 Million Elementen braucht lineare Suche bis zu 1 Million Vergleiche, binäre Suche nur 20 Vergleiche (log₂(1.000.000) ≈ 20).

5. Graph-Algorithmen

Graph-Algorithmen arbeiten auf Graphen (Knoten und Kanten). Sie werden für Routenplanung, soziale Netzwerke, Empfehlungssysteme und viele andere Anwendungen verwendet.

01

Dijkstra

Kürzester Pfad

Findet den kürzesten Pfad von einem Startknoten zu allen anderen Knoten in einem gewichteten Graphen. Verwendet einen Greedy-Ansatz.

  • Findet kürzeste Pfade
  • Funktioniert mit positiven Gewichten
  • Effizient mit Priority Queue
  • Funktioniert nicht mit negativen Gewichten
Komplexität: O((V + E) log V) mit Priority Queue · Speicher: O(V)
02

BFS (Breadth-First Search)

Breitensuche

Erkundet den Graphen Ebene für Ebene. Beginnt beim Startknoten und besucht zuerst alle Nachbarn, dann deren Nachbarn, usw. Verwendet eine Queue.

  • Findet kürzesten Pfad (ungewichtet)
  • Gut für Level-Order Traversal
  • Vollständig (findet immer Lösung)
  • Hoher Speicherbedarf O(V)
Komplexität: O(V + E) · Speicher: O(V)
03

DFS (Depth-First Search)

Tiefensuche

Erkundet den Graphen so tief wie möglich entlang eines Pfades, bevor es backtrackt. Verwendet einen Stack (oder Rekursion).

  • Geringer Speicherbedarf O(V)
  • Gut für Topological Sort
  • Gut für Zyklus-Erkennung
  • Findet nicht unbedingt kürzesten Pfad
Komplexität: O(V + E) · Speicher: O(V)

BFS vs. DFS – Wann was?

  • BFS: Wenn Sie den kürzesten Pfad (ungewichtet) finden wollen, oder wenn der Zielknoten nah am Startknoten ist
  • DFS: Wenn Sie alle Pfade erkunden wollen, Zyklus-Erkennung durchführen oder Topological Sort benötigen
  • Dijkstra: Wenn Sie kürzeste Pfade in gewichteten Graphen mit positiven Gewichten finden wollen
  • Bellman-Ford: Wenn der Graph negative Gewichte hat (langsamer als Dijkstra)

6. Best Practices für Algorithmen

Effizienz

  • Wählen Sie den Algorithmus basierend auf der erwarteten Eingabegröße
  • Vermeiden Sie unnötige Verschachtelung von Schleifen
  • Nutzen Sie effiziente Datenstrukturen (HashMap, TreeSet)
  • Vermeiden Sie redundante Berechnungen (Memoization, Caching)
  • Profilen Sie Ihren Code, um Engpässe zu identifizieren

Lesbarkeit

  • Verwenden Sie aussagekräftige Variablennamen
  • Kommentieren Sie komplexe Logik, aber nicht offensichtliche Schritte
  • Halten Sie Funktionen klein und fokussiert (Single Responsibility)
  • Verwenden Sie etablierte Algorithmen statt eigener Implementierungen
  • Dokumentieren Sie die Komplexität (Big O) in Kommentaren

Testing

  • Testen Sie mit Edge Cases (leere Liste, ein Element, sehr große Eingabe)
  • Testen Sie Best Case, Average Case und Worst Case
  • Verwenden Sie Unit Tests für jeden Algorithmus
  • Vergleichen Sie Ergebnisse mit bekannten Referenzimplementierungen
  • Testen Sie auf Korrektheit UND Performance

Dokumentation

  • Dokumentieren Sie Eingabe- und Ausgabeformate
  • Geben Sie die Zeit- und Raumkomplexität an
  • Beschreiben Sie Einschränkungen (z.B. nur positive Gewichte)
  • Fügen Sie Beispiele für typische Anwendungsfälle hinzu
  • Verlinken Sie auf relevante Literatur oder Referenzen

Die goldene Regel

"Premature optimization is the root of all evil" – Donald Knuth. Optimieren Sie erst, wenn Sie messbare Performance-Probleme haben. Schreiben Sie zuerst korrekten, lesbaren Code. Dann profilieren Sie und optimieren Sie gezielt die Engpässe. Ein O(n²)-Algorithmus mag für kleine Eingaben akzeptabel sein – verschwenden Sie keine Zeit mit vorzeitiger Optimierung.

7. FAQ – Häufige Fragen & Antworten

Häufige Fragen zu Algorithmen

Was ist der Unterschied zwischen einem Algorithmus und einem Programm?

Ein Algorithmus ist die abstrakte Idee oder Methode zur Lösung eines Problems (z.B. "Sortiere durch wiederholtes Vergleichen benachbarter Elemente"). Ein Programm ist die konkrete Implementierung eines Algorithmus in einer Programmiersprache (z.B. Bubble Sort in Python).

Ein Algorithmus kann in vielen verschiedenen Programmen implementiert werden, und ein Programm kann mehrere Algorithmen enthalten.

Warum ist Quick Sort schneller als Merge Sort, obwohl beide O(n log n) sind?

Beide haben die gleiche asymptotische Komplexität O(n log n), aber Quick Sort ist in der Praxis oft schneller wegen:

  • Bessere Cache-Lokalität: Quick Sort arbeitet in-place und nutzt den Cache effizienter
  • Kleinere Konstanten: Die versteckten Faktoren in der Big-O-Notation sind bei Quick Sort kleiner
  • Weniger Speicher: Quick Sort braucht O(log n) Speicher, Merge Sort O(n)

Merge Sort hat dafür den Vorteil, dass es stabil ist und garantierte O(n log n) Laufzeit bietet (Quick Sort kann im Worst-Case O(n²) sein).

Wann sollte ich welche Datenstruktur verwenden?

Die Wahl der Datenstruktur hängt von den Operationen ab, die Sie häufig durchführen:

  • Array: Schneller Zugriff per Index O(1), langsames Einfügen/Löschen O(n)
  • Linked List: Schnelles Einfügen/Löschen O(1), langsamer Zugriff O(n)
  • HashMap: Schnelles Suchen/Einfügen/Löschen O(1) average
  • Binary Search Tree: Sortierte Daten, Suchen O(log n)
  • Heap/Priority Queue: Schnelles Finden des Minimums/Maximums O(1)
  • Graph: Beziehungen zwischen Elementen (soziale Netzwerke, Routen)
Was bedeutet "stabil" bei Sortieralgorithmen?

Ein Sortieralgorithmus ist stabil, wenn er die relative Reihenfolge von Elementen mit gleichem Schlüssel beibehält.

Beispiel: Sie sortieren eine Liste von Personen nach Alter. Zwei Personen haben das gleiche Alter (z.B. 25). Ein stabiler Algorithmus behält ihre ursprüngliche Reihenfolge bei (z.B. die Person, die zuerst in der Liste stand, bleibt zuerst).

Stabile Algorithmen: Merge Sort, Insertion Sort, Bubble Sort
Instabile Algorithmen: Quick Sort, Heap Sort, Selection Sort

Wie analysiere ich die Komplexität eines Algorithmus?

Schritte zur Komplexitätsanalyse:

  1. Identifizieren Sie die Grundoperation: Was wird am häufigsten ausgeführt? (Vergleich, Zuweisung, etc.)
  2. Zählen Sie die Ausführungen: Wie oft wird die Grundoperation in Abhängigkeit von n ausgeführt?
  3. Bestimmen Sie die dominante Term: Behalten Sie nur den Term mit der höchsten Wachstumsrate
  4. Ignorieren Sie Konstanten: O(2n) wird zu O(n), O(n² + n) wird zu O(n²)

Beispiel: Eine verschachtelte Schleife, die n×n mal läuft, hat O(n²) Komplexität.

Was ist der Unterschied zwischen Zeit- und Raumkomplexität?

Zeitkomplexität: Wie viel Zeit benötigt der Algorithmus in Abhängigkeit von der Eingabegröße? Gemessen in Big O (z.B. O(n), O(n²)).

Raumkomplexität: Wie viel zusätzlichen Speicher benötigt der Algorithmus? Auch in Big O (z.B. O(1) für in-place, O(n) für zusätzlichen Speicher).

Oft gibt es einen Trade-off: Ein Algorithmus kann schneller sein, wenn er mehr Speicher verwendet (z.B. Caching, Lookup-Tables).

Warum ist O(n log n) die untere Grenze für vergleichsbasierte Sortierung?

Dies ist ein mathematisch bewiesenes Ergebnis. Für n Elemente gibt es n! (n Fakultät) mögliche Permutationen. Ein vergleichsbasierter Algorithmus muss im Worst-Case mindestens log₂(n!) Vergleiche durchführen, um alle Permutationen zu unterscheiden.

Nach der Stirling-Formel ist log₂(n!) ≈ n log n. Daher ist O(n log n) die theoretische untere Grenze für vergleichsbasierte Sortieralgorithmen.

Ausnahme: Nicht-vergleichsbasierte Algorithmen wie Counting Sort, Radix Sort oder Bucket Sort können O(n) erreichen, haben aber Einschränkungen (z.B. nur für Integer, begrenzter Wertebereich).

Wie wähle ich den richtigen Algorithmus für mein Problem?

Schritte zur Algorithmus-Auswahl:

  1. Problem analysieren: Welche Eingabe erwarten Sie? Wie groß ist n? Welche Operationen sind am häufigsten?
  2. Anforderungen definieren: Brauchen Sie schnellste Laufzeit, geringsten Speicher, stabile Sortierung?
  3. Algorithmus-Katalog prüfen: Gibt es einen bekannten Algorithmus für dieses Problem?
  4. Trade-offs abwägen: Zeit vs. Speicher, Einfachheit vs. Performance
  5. Implementieren und testen: Testen Sie mit realistischen Daten
  6. Profilen und optimieren: Messen Sie die Performance und optimieren Sie Engpässe

Tipp: Verwenden Sie etablierte Bibliotheken und Frameworks statt eigener Implementierungen, wenn möglich.

Zusammenfassung

Die wichtigsten Punkte

  • Algorithmus: Endliche Folge eindeutiger Anweisungen zur Problemlösung
  • Eigenschaften: Determinismus, Finitheit, Eindeutigkeit, Effektivität
  • Big O Notation: Beschreibt Skalierbarkeit (O(1), O(log n), O(n), O(n log n), O(n²), O(2ⁿ))
  • Sortieralgorithmen: Bubble Sort (einfach), Quick Sort (schnell), Merge Sort (stabil)
  • Suchalgorithmen: Lineare Suche (unsortiert), Binäre Suche (sortiert, O(log n))
  • Graph-Algorithmen: Dijkstra (kürzester Pfad), BFS (Breitensuche), DFS (Tiefensuche)
  • Best Practices: Effizienz, Lesbarkeit, Testing, Dokumentation
  • Goldene Regel: "Premature optimization is the root of all evil" – erst korrekten Code, dann optimieren

Nächste Schritte

Algorithmen sind das Fundament effizienter Programmierung. Üben Sie die Implementierung der wichtigsten Algorithmen (Quick Sort, Binäre Suche, BFS/DFS) und analysieren Sie deren Komplexität. Nutzen Sie Plattformen wie LeetCode, HackerRank oder Codewars, um Ihre Algorithmus-Skills zu verbessern.

Weiterführende Themen

Datenstrukturen

Arrays, Linked Lists, Trees, Graphen – die Bausteine effizienter Algorithmen.

Zu Datenstrukturen
Programmiersprachen

Python, Java, C++, JavaScript – die Sprachen zur Implementierung von Algorithmen.

Zu Programmiersprachen
Software-Engineering

Softwareentwicklung im großen Maßstab – Architektur, Patterns, Testing.

Zu Software-Engineering
Testing

Unit Tests, Integration Tests, TDD – Qualitätssicherung in der Softwareentwicklung.

Zu Testing