Zeitkomplexität
Laufzeit
Die Laufzeit eines Algorithmus gibt an, wie viel Zeit benötigt wird, um eine Eingabegröße zu verarbeiten, oft in Abhängigkeit von der Größe der Eingabe dargestellt. Sie dient dazu, die Effizienz und Skalierbarkeit von Algorithmen zu bewerten und zu vergleichen.
- Problem bei Laufzeitanalyse ist, dass die tatsächliche Laufzeit von vielen Faktoren abhängt
- Hardware
- Software
- Rechnerarchitektur
- Übersetzer
- Implementierung
-> Laufzeit soll unabhängig von Hard- und Software gelten. D.h. wird in der Regel auf der Basis der von Pseudocode gemacht
- Maschinenmodell - uniform
- Eine Pseudocode-Instruktion braucht einen Zeitschritt
- Wird eine Instruktion r-mal aufgerufen, werden r Zeitschritte benötigt
- Die Zahlengröße spielt keine Rolle
- für Schleifen gibt es Regeln:
- Sequenz: max der Teile.
- Schleife: (max. Iterationszahl) × (Body‑Komplexität).
- Halbierung / Verdopplung: O(log N)
- Geschachtelt: Komplexitäten multiplizieren.
- Danach Konstanten streichen.
- Um die Laufzeit zu analysieren, schauen wir uns die Größenordnung der Laufzeit an. Also das asymptotische Verhalten der Laufzeit als Funktion der Eingabegröße n
- Details werde ignoriert
- Dadurch erhält man Laufzeit- / Wachstumsklassen (logarithmisch, linear, quadratisch, exponentiell, etc.), in die man Algorithmen einordnet.
- Laufzeit eines Algorithmus hängt ab von:
- Größe der Eingabe (Parameter n)
- Art der Eingabe
- Beobachtungen
- insertion sort ist bei aufsteigend sortierten Eingaben schneller
- insertion sort ist bei absteigend sortierten Eingaben langsam
- Die Laufzeit lässt sich als Funktion T(n) darstellen. n ist die Eingabegröße. Ziel ist es die oberen Schranken (Garantien) zu finden
- Obere Schranken:
- Untere Schranken:
- Obere Schranken:
- Da je nach Rechenmodell, also welche Hard- und Software verwendet wird, die tatsächliche Laufzeit stark variieren kann, rechnet man in Schritten
Laufzeitanalyse Beispiel
In diesem Beispiel ist der Wert von sum Äquivalent zu der Anzahl an Schritten, die der Algorithmus durchläuft
Nach der Ausführung hat sum den Wert
Nach der Ausführung hat sum den Wert
Nach der Ausführung hat sum den Wert
Größenordnungen:
| Wachstum | W-Ordnungen | N = 1000 | 10N | 100N | 1000N |
|---|---|---|---|---|---|
| konstant | 1 | 1 Minute | 1 Minute | 1 Minute | 1 Minute |
| logarithmisch | log N | 1 Minute | 1,3 Minuten | 1,6 Minuten | 2 Minuten |
| linear | N | 1 Minute | 10 Minuten | 2 Stunden | 1 Tag |
| leicht überlinear | N log N | 1 Minute | 13 Minuten | 3 Stunden | 1½ Tage |
| quadratisch | N² | 1 Minute | 2 Stunden | 5 Tage | 23 Wochen |
| kubisch | N³ | 1 Minute | 1 Tag | 2 Jahre | 2000 Jahre |
| exponentiell | 2^N | 1 Minute | ∞ | ∞ | ∞ |
Fallunterscheidung
- wie oben steht kann die Laufzeit bei gleichem n stark variieren. Deshalb führt man Fallunterscheidungen durch
Worst-Case Analyse
- Für jedes n definiere Laufzeit
- T(n) = Maximum über alle Eingaben der Größe n
- Garantie für jede Eingabe / „schlechtester Fall“
- Üblich für Laufzeitanalyse
- Beobachtung:
- Betrachte nun Algorithmus A mit Laufzeit 100n und Algorithmus B mit Laufzeit 5n²
- Ist n klein, so ist Algorithmus B schneller
- Ist n groß, so wird das Verhältnis Laufzeit B / Laufzeit A beliebig groß
- Algorithmus B braucht also einen beliebigen Faktor mehr Laufzeit als A (wenn die Eingabe groß genug ist)
- Betrachte nun Algorithmus A mit Laufzeit 100n und Algorithmus B mit Laufzeit 5n²
Average-Case Analyse
- Für jedes n definiere Laufzeit
- T(n) = Durchschnitt über alle Eingaben der Größe n
- Hängt von Definition des Durchschnitts ab (wie sind die Eingaben verteilt)
Best-Case Analyse
- Für jedes n definiere Laufzeit
- T(n) = Minimum über alle Eingaben der Größe n
- „Nicht“ garantiert für jede Eingabe / „bester Fall“
Beispiel: Laufzeitanalyse von insertion sort
Worst-Case
für absteigend sortierte Eingabe
-> konstante Faktoren wenig Aussagekräftig, da hier im Beispiel gezeigt wird, dass bei steigendem n alles von
Asymptotische Laufzeitanalyse
- ignorieren von konstanten Faktoren
- Betrachte das Verhältnis von Laufzeiten für
- Klassifiziere Laufzeiten durch Angabe von „einfachen Vergleichsfunktionen“
O-Notation – Obere Schranke
!250
ist die tatsächliche Laufzeitfunktion, die alle Operationen und Konstanten beinhaltet. ist die Funktion, die wir zur Abschätzung des Wachstumsverhaltens verwenden. bedeutet, dass für große nicht schneller wächst als , abgesehen von Konstanten.
-Notation – Untere Schranke
!250
bedeutet, dass für mindestens so stark wächst wie - Beim Wachstum ignorieren wir konstanten
-Notation (Theta-Notation)
- Die Theta-Notation beschreibt, dass eine Funktion
sowohl asymptotisch nach oben als auch nach unten durch eine Funktion beschränkt ist , - wobei:
und sind positive Konstanten. ist eine Grenze, ab der diese Ungleichung gilt.
- wobei:
Echte obere und untere Schranke
- o-Notation: echte obere Schranke
-Notation: echte untere Schranke
Interpretation
| "asymptotische Version" | von |
|---|---|
| O | |
| o | |
| Ausdruck | Wachstum von |
Vergleich | Wachstum von |
|---|---|---|---|
| Wachstum von |
Wachstum von |
||
| Wachstum von |
Wachstum von |
||
| Wachstum von |
Wachstum von |
||
| Wachstum von |
Wachstum von |
||
| Wachstum von |
Wachstum von |
Mehrere Parameter & Datenstrukturen:
- Graph‑Durchlauf: äußere Schleife über V Knoten, innere über ausgehende Kanten ⇒
, weil jede Kante einmal. - PriorityQueue.add():
in Queue‑Größe ⇒ Algorithmus oft .
- PriorityQueue.add():
- Amortisierte vs. Worst‑Case Kosten beachten.
Laufzeitklassen
| faktoriell | |
| exponentiell | |
| kubisch | |
| quadratisch (polynomiell) | |
| super-linear | |
| linear | |
| Wurzelfunktion | |
| logarithmisch | |
| beschränkt/konstant |
Hierarchie
!Laufzeitklassen Vergleich.png
Laufzeitanalyse von Sortieralgorithmen
- insertion sort
- Worst-Case:
- Best-Case:
- Worst-Case:
- selection sort
- Worst Case:
- Worst Case:
- bubble sort
- Komplexität:
- Komplexität:
- count sort
- Komplexität:
- n = Länge Array
- m = Wertebereich
- Komplexität:
Laufzeit elementarer Java-Strukturen (worst-case)
| Struktur | Operation | Laufzeit |
|---|---|---|
| Stack (Resizing‑Array) | push, pop | O(1) |
| Queue (Ring‑Array) | enqueue, dequeue | O(1) |
| LinkedList | addFirst, pollFirst | O(1) |
| get(i), set(i), contains | O(N) | |
| PriorityQueue (Heap) | add, removeMin | O(log N) |
| ArrayList / ArrayDeque | addLast | amort. O(1) |
(ArrayDeque meist schnellste Deque; ArrayList Einfügen mittendrin O(N)).
Rekursionsbaum
| Muster | Baumhöhe | Verzweigungen k | Gesamtlaufzeit |
|---|---|---|---|
| T(N)=T(N−1)+c | O(N) | 1 | Θ(N) |
| T(N)=T(N/2)+c | O(log N) | 1 | Θ(log N) |
| T(N)=T(N−1)+T(N−2)+T(N−3)+c | O(N) | 3 | O(3ᴺ) |
| T(N)=2·T(N/2)+c | O(log N) | 2 | Θ(N) |
Komplexitätsklassen
Komplexitätsklassen sind Kategorien, die Berechnungsprobleme danach gruppieren, wie viel Rechenzeit ein Algorithmus im schlimmsten Fall benötigt, um sie zu lösen, abhängig von der Größe der Eingabe.
| Klasse | Definition | Bedeutung |
|---|---|---|
| P | Probleme lösbar in polynomieller Zeit | „praktisch“ effizient |
| NP | Lösung prüfbar in polynomieller Zeit | enthält P |
| NP‑schwer | Jedes NP‑Problem polynomiell reduzierbar | mind. so schwer wie NP |
| NP‑vollständig | in NP und NP‑schwer | härteste NP‑Probleme |
- P =? NP offen (Millennium‑Problem, 1 Mio $).
- Polynomzeit für ein einziges NP‑vollständiges Problem ⇒ P = NP.
Typische NP‑vollständige Probleme
- Traveling‑Salesman (TSP): Finde in einem vollständigen, gewichteten Graphen einen Zyklus mit minimalem Gewicht, der jeden Knoten genau einmal enthält
- Hamilton‑Pfad/‑Zyklus: Finde einen Pfad, der jeden Knoten eines gegebenen Graphen genau einmal besucht.
- Max‑Cut: Bestimme in einem gewichteten Graphen einen Schnitt mit maximalem Gewicht.
- 0/1‑Rucksack, auch bei Beschränkung auf ganzzahlige Gewichte