English· Español· Deutsch· Nederlands· Français· 日本語· ქართული· 繁體中文· 简体中文· Português· Русский· العربية· हिन्दी· Italiano· 한국어· Polski· Svenska· Türkçe· Українська· Tiếng Việt· Bahasa Indonesia

un

Gast
1 / ?

Was der Kurs abdeckte und was er ausließ

Hammings Kurs behandelte: Analog-Digital-Wandlung, Fehlerkorrektur, Simulation, Statistik, numerische Analyse und n-dimensionale Geometrie. Er dachte rechnerisch – Signalverarbeitung, Codierungstheorie und digitale Filterung erfordern alle rechnerisches Denken.

Er lehrte algorithmische Komplexität nie systematisch.

Algorithmische Komplexitätswachstumskurven: O(1) flach, O(log N) sanft, O(N) diagonal, O(N²) Parabel

Warum die Lücke existierte

Hammings mentales Modell entstand in einer Zeit, in der Hardware-Engpässe dominierten. Die Frage lautete: Wie viele Transistoren pro Chip? Der Algorithmus lief auf der verfügbaren Hardware. Bei N=100 kostet ein O(N²)-Algorithmus 10.000 Operationen. Bei N=1.000 kostet er 1.000.000. Bei N=10.000 (heute Routine in Abhängigkeitsgraphen, sozialen Netzwerken & Paketmanagern): er kostet 100.000.000. Der Unterschied zwischen O(N) & O(N²) war bei den Skalen, mit denen Hamming arbeitete, unsichtbar & bei den Skalen, die seine Studenten bewohnen würden, katastrophal.

Donald Knuth veröffentlichte The Art of Computer Programming ab 1968 — im selben Jahr, in dem Hamming auf dem Höhepunkt seiner Forschungstätigkeit war. Big-O-Notation wurde in den 1970er & 1980er Jahren zum Standardvokabular der Algorithmen. Hammings Kurs stammt aus dem Jahr 1995, aber sein mentales Modell der Berechnung entstand früher. Er hat dieses Kapitel nie aktualisiert.

Ein Fundamental nach Hammings eigenem Test

Hammings Test für ein Fundamental: (1) es hat lange Bestand gehabt, (2) der Rest des Fachgebiets lässt sich daraus mit Standardmethoden ableiten.

Big O erfüllt beide Tests. Die Wachstumsraten-Analyse besteht seit Knuth. Daraus leiten sich Algorithmenauswahl, Datenstrukturwahl & Leistungsvorhersage ab — der größte Teil der praktischen Informatik ergibt sich aus der Frage „Wie wächst der Aufwand, wenn N wächst?“ Hamming hat das dauerhafteste Fundamental seines eigenen Fachgebiets übersehen.

Big O als Fundamental

Hamming lehrte, dass Fundamentale spezifische Technologien überdauern. Er nannte als Beispiel die Infinitesimalrechnung: einmal erfunden, über Jahrhunderte anwendbar in Physik, Ingenieurwesen, Wirtschaft & Biologie. Periphere Werkzeuge kommen & gehen; Fundamentale wirken kumulativ.

Hamming lehrte, dass Fundamentale spezifische Technologien überdauern. Zählt algorithmische Komplexität nach seinem eigenen Test als Fundamental? Wende beide seiner Kriterien an: (1) Langlebigkeit & (2) Ableitbarkeit — lässt sich der Rest des Fachgebiets daraus ableiten? Begründe deine Position konkret.

Wie Kosten wachsen

Big O misst, wie Kosten wachsen, wenn die Eingabe wächst. Nicht der konstante Faktor — die Rate. Zwei Algorithmen, die bei N=100 beide „ein paar Millisekunden“ brauchen, können sich bei N=10.000 um den Faktor 10.000 unterscheiden, wenn einer in O(N)-Zeit und der andere in O(N²)-Zeit läuft.

Die gängigen Komplexitätsklassen

O(1) — Konstant. Gleiche Kosten unabhängig von N. Beispiele: Hash-Tabellen-Zugriff über Schlüssel, Array-Zugriff über Index, Stack-Push/Pop. Verdoppelt man N, ändert sich nichts.

O(1)-Wachstumskurve: flache horizontale Linie

O(log N) — Logarithmisch. Jeder Schritt halbiert die verbleibende Arbeit. Beispiele: binäre Suche in einem sortierten Array, BVH-Raumabfrage in einer Game-Engine, Operationen auf einem balancierten Binärbaum. Bei N=1.000.000: log₂(1.000.000) ≈ 20 Schritte.

O(log N)-Wachstumskurve: schneller Anstieg, dann Abflachung

O(N) — Linear. Der Aufwand wächst mit der Eingabe. Beispiele: linearer Scan einer Liste, Lesen einer Datei, Zählen von Elementen in einer Sammlung. Bei N=10.000: 10.000 Operationen.

O(N)-Wachstumskurve: gerade diagonale Linie

O(N log N) — Linearithmisch. Fast linear, etwas schlechter. Beispiele: Merge-Sort, effiziente Algorithmen für kürzeste Wege (Dijkstra mit Heap). Bei N=10.000: ca. 133.000 Operationen.

O(N log N)-Wachstumskurve: etwas steiler als linear

O(N²) — Quadratisch. Der Aufwand explodiert. Beispiele: list.contains() innerhalb einer Schleife über dieselbe Liste, Bubble-Sort, naive paarweise Vergleiche. Bei N=1.000: 1.000.000 Operationen. Bei N=10.000: 100.000.000 Operationen.

O(N²)-Wachstumskurve: parabolische Explosion

O(2^N) — Exponentiell. Bei jeder sinnvollen Skala unbrauchbar. Beispiele: Brute-Force-Kombinatorik, alle Teilmengen finden. Bei N=30: über 1.000.000.000 Operationen.

O(2^N)-Wachstumskurve: exponentielle Explosion

Die Skala, die es sichtbar macht

N=10 Vergleiche:
O(N):   10
O(N²):  100
Verhältnis:  10x

N=1.000 Vergleiche:
O(N):   1.000
O(N²):  1.000.000
Verhältnis:  1.000x

N=10.000 Vergleiche:
O(N):   10.000
O(N²):  100.000.000
Verhältnis:  10.000x

Bei N=10 ist der Unterschied kaum spürbar. Bei N=10.000 läuft der O(N²)-Algorithmus 10.000-mal langsamer. Deshalb blieb MOAD-0001 jahrzehntelang unsichtbar: Die Graphen, auf denen er 1993 lief, hatten 50 Knoten. Bis 2020 lief derselbe Code auf Abhängigkeitsgraphen mit 50.000 Knoten.

Drei Operationen klassifizieren

Wende die Komplexitätsklassen auf konkrete Operationen an. Die zentrale Frage bei jeder: Wie viele Operationen sind nötig, wenn N wächst?

Gib für jede der folgenden Operationen die Big-O-Komplexität an und erkläre in einem Satz, warum: (a) Ein Wort in einem Wörterbuch über die Seitenzahl nachschlagen (b) Ein Wort durchsucht jede Seite eines Wörterbuchs (c) Ein gemischtes Kartenspiel sortieren, indem jede mögliche Reihenfolge ausprobiert wird Schreibe für jede Operation einen Erklärungssatz.

Korrekter Code, falsche Wachstumskurve

Korrekter Code, der in O(N²)-Zeit läuft, liefert identische Ergebnisse wie O(N)-Code. Die Tests bestehen. Die Ausgabe stimmt überein. Es wird keine Ausnahme ausgelöst. Der Fehler verbirgt sich in der Wachstumskurve.

Diese Eigenschaft macht O(N²)-Fehler sedimentär: sie versteinern in funktionierendem Code und werden erst sichtbar, wenn N eine Schwelle überschreitet. Der Code hat sich nicht geändert. Die Welt um ihn herum hat sich geändert.

MOAD-0001: Der sedimentäre Fehler

Das am weitesten verbreitete Beispiel: visited = [] innerhalb einer Schleife zur Graph-Traversierung.

visited = []
for node in graph:
if node not in visited:  # O(N)-Scan bei jedem Aufruf
visited.append(node)
process(node)

Jeder not in visited-Aufruf scannt 0 bis len(visited)-1 Elemente. N Aufrufe × N/2 durchschnittlich gescannte Elemente = O(N²) insgesamt. Die Lösung: eine Zeile.

visited = set()  # O(1) Mitgliedschaftstest
for node in graph:
if node not in visited:  # O(1) Hash-Lookup
visited.add(node)
process(node)

Gleiches Verhalten. Gleiche Ausgabe. Gleiche Tests bestanden. Die Wachstumsrate ändert sich von O(N²) zu O(N). Bei N=1.000: 1.000× schneller. Bei N=10.000: 10.000× schneller.

Warum Hammings Ära dies verursacht hat

In frühem Java & C war ArrayList (dynamisches Array) der Standard für sequenzielle Container. Sets existierten, waren aber nicht idiomatisch – Entwickler griffen auf das Vertraute zurück. Eine Graph-Traversierung von 1993 mit N=50 lief in Mikrosekunden mit visited = []. Dieser Code landete in der Versionskontrolle, wurde kopiert, in Bibliotheken eingebaut und über Paketmanager ausgeliefert. Bis 2020 lief dasselbe Muster auf Abhängigkeitsgraphen mit 50.000 Knoten.

Der Code war 1993 korrekt. Er wurde teuer, als sich die Welt um ihn herum veränderte. Hammings Ära hat diese Art von Defekt verursacht, indem sie die Frage nach der Wachstumsrate nie benannt hat.

Diagnose & Fix

Wende die Diagnose an: finde das O(N²)-Muster und identifiziere die Lösung.

Du findest diesen Code in einer Produktionscodebasis: `if item not in visited_list: visited_list.append(item)` innerhalb einer Schleife über 50.000 Elemente. Wie viele Operationen führt der Membership-Test im Durchschnitt über die gesamte Schleife aus, und womit würdest du `visited_list` ersetzen, um das Problem zu beheben?

Welche Namensänderungen

Bevor Big O einen Namen hatte, bemerkten Programmierer: „Das läuft langsam.“ Sie haben profitiert. Sie haben umgeschrieben. Aber sie konnten das Muster nicht lehren — sie konnten nur auf Instanzen zeigen. Ein Muster ohne Namen kann sich nicht verbreiten.

Nachdem Big O einen Namen hatte, konnte ein Senior Engineer sagen: „Das ist O(N²), ersetzen Sie es durch ein Set“ und ein Junior Engineer konnte das Muster verstehen, anwenden und seinerseits weitergeben. Der Name machte das Muster zu einer übertragbaren Wissenseinheit.

Hamming kannte diese Dynamik in anderen Bereichen. Er beschrieb, wie die Benennung von „Spaghetti-Code“ in den 1960er Jahren der Community ermöglichte, eine Klasse von Defekten zu erkennen, zu diskutieren und schließlich auszumerzen, die jeder erlebt, aber niemand benannt hatte. Derselbe Mechanismus gilt für Komplexitätsklassen.

Unhamming ergänzt den Wortschatz, den Hamming ausgelassen hat: O(1), O(log N), O(N), O(N log N), O(N²), O(2^N). Diese Namen ermöglichen es Ihnen, die Wachstumskurve im gelesenen Code zu erkennen, die Leistung bei Skalierung vorherzusagen und Korrekturen präzise zu kommunizieren. Sie erfüllen Hammings eigenen Test für ein Fundament: dauerhaft und generativ für den Großteil des restlichen Feldes. [TITLE who_coined_big_o/]

Von der Zahlentheorie zur Informatik

Die Big-O-Notation stammt nicht aus der Informatik. Sie entstand in der reinen Mathematik – genauer gesagt in der Zahlentheorie – 74 Jahre bevor Donald Knuth sie für Algorithmen übernahm.

Paul Bachmann, 1894

Paul Bachmann, ein deutscher Mathematiker, führte die O-Notation in seinem Buch Die Analytische Zahlentheorie von 1894 ein. Er benötigte eine kompakte Möglichkeit, auszudrücken, dass eine Größe nicht schneller als eine andere wächst, bis auf einen konstanten Faktor. Die Schreibweise „f(n) = O(g(n))“ bedeutete: f wächst höchstens so schnell wie g. Dies ermöglichte es Zahlentheoretikern, über Fehlerterme in Approximationen zu argumentieren, ohne exakte Konstanten verfolgen zu müssen.

Bachmann dachte nicht an Schleifen, Datenstrukturen oder Laufzeiten. Er beschäftigte sich mit der Verteilung von Primzahlen – insbesondere mit den Fehlertermen im Primzahlsatz.

Edmund Landau, 1909

Edmund Landau, ebenfalls ein deutscher Mathematiker, popularisierte die Notation in seinem Handbuch der Lehre von der Verteilung der Primzahlen von 1909. Landau führte auch die verwandte Notation o (klein-o) ein und verwendete die Familie der asymptotischen Symbole so häufig, dass sie als Bachmann-Landau-Notation oder einfach Landau-Notation bekannt wurde.

Sechs Jahrzehnte lang existierte die O-Notation ausschließlich in der Mathematik. Kein Programmierer dachte daran.

Donald Knuth, 1968 & 1976

Donald Knuth übertrug die Notation in die Informatik. In The Art of Computer Programming (Bd. 1, 1968) verwendete Knuth die O-Notation zur Beschreibung der Laufzeit von Algorithmen — eine direkte Übertragung von Bachmanns Werkzeug in ein neues Gebiet. Er war der Erste, der sie systematisch in der Algorithmusanalyse einsetzte.

1976 veröffentlichte Knuth einen kurzen Artikel in SIGACT News mit dem Titel „Big Omicron and Big Omega and Big Theta“. Er führte Omega (Ω) für untere Schranken und Theta (Θ) für enge Schranken ein und vervollständigte damit den heute in der Informatik verwendeten Dreier-Symbolwortschatz. Er plädierte außerdem für eine Standardisierung dieser Symbole in der Fachwelt — ein bewusster Standardisierungsakt, der die Verbreitung beschleunigte.

In den frühen 1980er Jahren erschien Big O in jedem Algorithmen-Lehrbuch. In den 1990er Jahren wurde es zum Standardvokabular in Vorstellungsgesprächen.

Eine 74-jährige Reise

1894 — Bachmann führt O in der Zahlentheorie ein
1909 — Landau popularisiert O, o und die gesamte Notationsfamilie
1953 — Hamming beginnt seine Forschung bei Bell Labs (lernt Big O nie als CS-Werkzeug kennen)
1968 — Knuth wendet O auf die Algorithmusanalyse in TAOCP Vol. 1 an
1976 — Knuth führt Omega und Theta ein; plädiert für Standardisierung
1980er — Big O gelangt in alle CS-Lehrpläne
1993 — MOAD-0001-Sedimentschicht bildet sich im Produktionscode
1995 — Hamming unterrichtet die letzte Version seines Kurses; behandelt Big O nie
2026 — MOAD-0001 in über 1.000 Open-Source-Projekten gefunden

Bachmanns Werk verbrachte 74 Jahre in der reinen Mathematik – sichtbar für jeden Mathematiker, unsichtbar für jeden Programmierer. Knuth erkannte das Potenzial der Übertragung. Hamming – in derselben Zeit tätig und mit derselben Computing-Community zusammenarbeitend – nahm sie nie in seinen Kurs auf.

Warum Knuths Standardisierung wichtig war

Knuths Paper von 1976 tat mehr, als nur Omega und Theta einzuführen. Es argumentierte, dass das Fachgebiet eine einheitliche Notation brauche und dass inkonsistente Notation aktiv schädlich sei. Verschiedene Lehrbücher verwendeten O für unterschiedliche Bedeutungen – manchmal als obere Schranke, manchmal als Approximation, manchmal ohne Angabe, ob Konstanten eine Rolle spielen. Knuth räumte das auf.

Dies ist Hammings Musterbenennungs-Dynamik, angewandt auf die Notation selbst. Vor Knuths Standardisierung konnten Ingenieure Komplexitätsaussagen nicht präzise über Lehrbücher, Papers oder Teams hinweg kommunizieren. Nach der Standardisierung trug „this runs in O(N log N)“ unabhängig vom Sprecher genau eine Bedeutung.

Knuth lieferte auch die informelle Übersetzung: „O(f(n)) bedeutet, dass die Laufzeit höchstens ein konstantes Vielfaches von f(n) beträgt, für alle hinreichend großen n.“ Diese Interpretation – obere Schranke, bis auf einen konstanten Faktor, für große Eingaben – wurde zur Standardintuition, die jeder Programmierer lernt.

Bachmann erfand die O-Notation 1894 für die Zahlentheorie. Knuth übernahm sie 1968 für die Algorithmusanalyse. Was musste sich – in der Informatik, im Maßstab oder in der Arbeitsweise von Programmierern – ändern, damit ein Werkzeug der reinen Mathematik zum unverzichtbaren Vokabular für Software-Ingenieure wurde? Nenne mindestens zwei Faktoren.