date-created: 2024-06-27 10:40:07 date-modified: 2024-06-27 02:18:22
Komplexität
anchored to 112.00_anchor_overview proceeds from 112.21_rekursive_funktionen aber auch 112.19_unentscheidbare_probleme
Overview
Bis dato haben wir uns nur Bereiche angeschaut, wo wir evaluieren wollten, ob man es überhaupt berechnen konnte. Sofern man einfach beliebig viel Zeit und auch Speicher hat.
Dabei hatten wir dann die drei Strukturen definiert:
- 112.03_reguläre_sprachen
- 112.15_abzählbarkeit
- 112.16_nicht_erkennbar_entscheidbar und dafür hatten wir auch diverse Beispielfragen definiert, die als Beispiele dafür dienen.
Motivation
Wir möchten folgend auch auf Zeit und Speicher achten und darin dann Komplexität betrachten / definieren und evaluieren.
[!Tip] Motivation
Wir wollen innerhalb der berechenbaren Problemen dann evaluieren, wie effizient man eine Lösung für ein Problem bestimmmen kann!
Was wir hierbei auch sehen werden:
- Es gibt Probleme, die man effizient lösen kann
- aber auch Probleme, die man nicht effizient prüfen kann –> Dabei werden wir erfahren, dass diese Menge nicht zwingend getrennt voneinander ist, wie es etwa bei ist!
Beispiel | Komplexität betrachten:
[!Req] Beispielsprache
Wir betrachten folgende Sprache, bei welcher wir folgend den Speicher und die Zeit ( bzw. erstmal die Schritte, die sie brauch) evaluieren wollen!
Wir wissen, dass sie entscheidbar / berechenbar ist!
Wie viele Schritte benötigen wir hier mindestens um Eingaben zu verarbeiten? Was benötigen wir, um es passend anzusetzen? #card
- wir sehen hier, dass die Zahl der Schritte davon abhängt, wie groß die Eingabe ist!
- Ferner müssen wir hier mehr oder weniger eine konkrete Turingmaschine betrachten, um das genau bestimmen zu können –> damit wir ein Gefühl bekommen ,wie eine Eingabe verarbeitet wird.
betrachten wir also eine Turingmaschine : Auf Eingabe arbeitet sie folgend:
- Fahre das Band ab, wenn irgendwo eine 0 nach einer 1 kommt ( also invalide Struktur)
- 0er und 1er auf dem band sind, machen wir folgendes:
- Fahre das band ab, streiche genau eine 0 / 1 ab
- fahre dann bis zum Beginn der 1 / 0 und streiche auch genau da eine 0 / 1 weg
- fahre wieder zurück und wiederhole den Prozess, bis keine Zahl mehr übrig ist.
- accept –> wenn nichts übrig ist am Ende
- reject –> wenn am Ende noch 1 oder 0 übrig sind ( also sie sind ungleich!)
Damit haben wir also folgende Quantitäten an Schritten (approximiert!):
Wir sehen hier, dass wir die landau-Notation benötigen! Siehe hier
111.04_laufzeitanalyse_onotation
[!Attention] Unterschiede von det / non-det TMs bei Komplexität
Wir werden hier sehen, dass Turingmaschinen zwar äquivalent in der Entscheidung von Problemen sind ( also genau das gleiche können), aber in der Zeitkomplexität und der Speicherkomplexität divergieren / nicht gleich sind!
Wir wollen aus dieser Betrachtung dann zwei folgende Komplexitäten definieren:
Zeitkomplexität:
[!Definition]
Sei eine deterministische Einband-Turingmaschine über einem Eingabealpabet , die immer anhält (berechnet also etwas!)
Wir zählen die Schritte dann mit folgender Notation: für die Anzahl der Schritte, die die Tm auf dem Input durchläuft, bis sie anhält!
Wie können wir dann die Zeitkomplexität besteimmen? #card
Wir bezeichnen dann die Zeitkomplexität bzw auch die Laufzeit von mit : –> es ist also die maximale Anzahl von Schritten, die auf allen Inputs der Länge durchläuft
Speicherkomplexität:
[!Definition]
Sei wieder eine deterministische Einband-Turingmaschine über einem Eingabealphabet , die immer anhält.
Ferner beschreiben wir jetzt mit die Menge der Zellen auf dem Band, auf denen auf dem Input operiert - die sie benutzt - bis sie anhält.
Wie können wir damit jetzt die Speicherkomplexität beschreiben? #card
Für eine TM beschreiben wir die Speicherkomplexität als Funktion:
–> also wir suchen die maximale Zahl von Zeilen, die eine TM bei dieser Berechnung verwenden könnte.
“ läuft dann also in ”
Achtung: Es müssen hier auch leere Zellen, solche, die durchlaufen, aber nicht beschrieben werden, mitgezählt werden, weil man sonst leere Zellen zum kodieren nehmen könnte, um Werte zu repräsentieren –> Dadurch würde man quasii “keinen Speicher” verwenden!
Obere / untere Schranken
Da wir hier jetzt quantifizieren können, wie schnell (oder wie viel Speicher) eine Turingmaschine für ein berechenbares Problem brauch, können wir ferner Schranken bestimmen, die durch diverse Tms, die das gleiche Problem betrachten / lösen.
[!Definition] Obere Schranken
Sei eine Sprache. Wir sagen, dass die Funktion eine obere Schranke für die Zeitkomplexität von ist, falls folgendes gilt:
Was muss gelten? Wie können wir das dann formal beschreiben? #card
Es muss gelten, dass es eine deterministische TM gibt, welche entschiedet, sodass ist.
Also formal:
- , und ferner dann:
- und somit dann schlussendlich:
Somit folgt also: hat Zeitkomplexität höchstens , wenn es eine DTM gibt, deren Rechenzeit auf jeder Eingabe der Länge nicht länger als ist! (Wir haben also einen maximalen Wert gefunden, welcher nicht weiter überschritten wird / werden kann)
[!Definition] Untere Schranken
Sei eine Sprache. Wir sagen, dass die Funktion eine untere Schranke für die Zeitkomplexität von ist, falls folgendes gilt:
Was muss gelten? Wie können wir das dann formal beschreiben? #card
Es gilt, falls für jede det Tm die entscheidet,, gilt, dass ist!
Also formal:
- und somit
- und somit also
Es folgt also:
hat die Zeitkomplexität , wenn jede auf dem schwierigsten Wort der Länge mindestens braucht
Folgerungen
Wir wollen jetzt aus den obigen Definitionen einige Folgerungen / Lemma / betrachten, die für die weitere Betrachtung praktisch sind / sein können:
Wir wollen zuerst herausfinden, dass es trivial ist, wie das Arbeitsalphabet konstruiert / gegeben ist.
Arbeitsalphabet macht keinen Unterschied
[!Lemma] Behauptung
Sei eine Funktion, die von einer TM mit einem Arbeitsalphabe in der Zeit berechnet werden kann.
Wieso ist es jetzt trivial, welches Alphabet wir verwenden? Wie können wir zu einem anderen konvertieren? #card
Wir können jetzt etwa das vorhandene Alphabet in ein anderes konvertieren ( wir kodieren jeden Buchstaben um in eine andere Grundlage)
Wir können durch diese Umrechnung dann die neue Zeit bestimmen:
Beweisen könnte man das mit folgendem Ansatz:
- Wir kodieren alle Buchstaben mit binären bits, und verwenden dann die Buchstaben dann, um sie zu repräsentieren
Es folgt dadurch dann:
Wenn auf in berechenbar ist dann ist es auch
Lemma | Arbeitsalphabet umkodieren
[!Satz]
Es gilt: Für jede -Band TM existiert eine -Band Tm , sodass und ferner folgt noch ein Bezug zur Zeit und Platz-Komplexität!
Was folgt aus dieser Aussge für und ? #card
Aus obiger Betrachtung folgt ferner noch:
und für die Zeitkomplexität folgt:
Woher kommt ? –> Zu Beginn der TM müssen wir die Eingabe zu unserem neuen Alphabet umkodieren! und somit durchlaufen wir es komplett und schreiben die Eingabe um.
Beweis Idee:
Sei jetzt etwa das Arbeitsalphabet von . Dann wählen wir jetzt eine neue Struktur . Falls der Inhalt des -ten Bandes von ist und der Kopf auf zeigt, enthält das -te Band von dann folgendes Wort:
(Falls etwa ungerade ist , fügen wir einfach ein Leerzeichen am Ende hinzu (padding quasi)) -> Der Kopf zeigt dann immer auf das Tupel, dass enthält.
Verwenden wir dann diese Zustand, um zu kodieren, ob dieser Buchstabe das erste oder zweite Symbol im Typel ist.
–> Die Speicherkomplexität ist damit höchstens
–> Das gleiche gilt dann auch für die Rechenzeit, allerdings muss man am Anfang einmal das Band abfahren und es umkodieren –> einmal hin und wieder zurück
[!Attention] Es macht wenig Sinn über multiplikative Konstanten zu streiten, da man sie weg-canceln kann
[!Tip]
Wir stellen fest, dass es scheinbar keinen großen Unterschied macht, wenn man das Arbeitsalphabet in ein anderes konvertiert und dann die TM verwendet –>
Daher verwenden wir die O-Notation, damit wir solche Modifikationen einfach inkludieren und nicht extra Betrachten müssen!
Notwendigkeit der polynomiellen Laufzeit
[!Bsp]
Wir sehen, dass die Implementierung der TM, für eine beliebige entscheidbare Sprache meist nicht soo relevant ist, weil wir gleich sehen werden, dass man sie mit einer Universellen TM umsetzen und damit eine maximale Grenze nicht überschritten wird.
Ferner, durch die obigen Sätze, können wir aussagen, dass die meisten TMs - in verschiedenen Strukturen - darauf reduziert werden können.
- Beidseitig offfene Bänder
- write-only Bänder
- random-access Bänder –> sofern das Anfahren einer Speicheradresse ist –> das band durch die Laufzeit beschränkt.
Komplexität der Universellen Turingmaschine
112.14_universal_turing_machine
[!Beweis] Satz | Beschreibung einer Effizienten Universellen Turingmaschine
Sei jetzt eine Funktion, die von einer TM in gegebener Zeit berechnet werden kann –> das heißt hält auf nach spätestens Schritten
Dann sagen wir jetzt: Es existiert eine UTM , welche die gleiche Funktion in der Zeit - also nach dieser Menge von Schritten, also in berechnet.
Dabei ist von der Größe des Arbeitsalphabetes , der Menge von Bändern und der Größe des Zustandsraumes abhängig ist –> also es wird eine Konstante sein, die wir betrachten / anwenden können.
Wie können wir jetzt so anpassen, dass wir auf reduzieren können? Wie konstruieren wir es entsprechend? #card
Wir wollen jetzt die einfachere Variante beweisen, dass erhalten wird, statt dem (den weiteren Schritt argumentieren wir einfach)
- Wir nehmen an, dass nur ein Band hat - falls nicht, können wir es ja in transformieren ( Wir wenden ferner als Alphabet an ( weil wir ja alles damit kodieren können))
- Wir erinnern uns an die universelle Turingmaschine, welche 3 Bänder hatte ( 1. Simulation von , 2. Gödelnummer (also Programm) von speichern; 3. speichert den aktuellen Zustand von (Simuliert))
–> Jeder Schritt von kostet nur overhead afu den anderen beiden Bändern –> wobei ja von den Parametern: abhängt ( wenn die Übergänge von betrachtet, bildet es hier eine Matrix!)
Damit haben wir also maximal erzeugt!
Man kann diese Struktur jetzt noch von auf verkürzen:
–>die Bänder kann man effizienter Kodieren
Wir speichern alle Bänder in einem Zustand . ProblemL Die Köpfe auf den Bändern können sich ja unabhängig verschieben. –> Wenn das passiert brauchen wir irgendwie einen Shift der Bänder relativ zueinander –> Das kann man mit einer geschickten Codierung realisieren! ( Kodiere die Zahl der Shifts selbst, in )
- Das geht durch amortisierte Laufzeitanalyse
Folgerung
Durch diese Konversion / Betrachtung in verschiedenen Varianten - wir aber immer auf die gleiche zurückkommen - können wir jetzt Aussagen über Probleme treffen:
- Im folgenden werden wir also den Fokus darauf setzen, dass ein Problem in polynomieller Laufzeit lösbar ist.
- Es ist somit irrelevant, wie die TM implementiert wird!
[!Attention] Was nicht egal ist:
Es ist nicht egal, ob die Turingmaschine deterministisch ist / oder nicht
–> Obwohl sie in der Berechenbarkeit gleich sind, ist ihre Zeit / Platz-Komplexität nicht gleich –> bei einer Det ist sie meist sogar exponentiell größer!
Laufzeit- und Speicherkomplexität | Klassen
Wir können jetzt ferner Klassen für die Zeit- und Speicherkomplexität definieren:
Komplexitätsklasse TIME
[!Definition]
Sei eine Zeit-Komplexitäts-angabe
Wir wollen daraus jetzt die Komplexitätsklasse bestimmen:
Wie wird sie definiert, was enthält sie? #card
Die Komplexitätsklasse ist die Menge aller Sprachen , die in worst-case Laufzeit von einer deterministischen Turingmaschine entschieden werden können.
Wir beschreiben die Menge dann folgend:
Komplexitätsklasse SPACE
[!Definition]
Sei eine Speicherplatz-Komplexitäts-angabe
Wir wollen daraus jetzt die Komplexitätsklasse bestimmen:
Wie wird sie definiert, was enthält sie? #card
Die Komplexitätsklasse ist die Menge aller Sprachen , die in worst-case Laufzeit von einer deterministischen Turingmaschine entschieden werden können.
Wir beschreiben die Menge dann folgend:
–> Also alle TMs, die genau die gleiche Speicherkomplexität aufweisen!
Daraus kann man jetzt die Komplexitätsklasse bestimmen!
Komplexitätsklasse P
[!Definition]
Wir wollen die Menge von Sprachen, die in polynomieller Zeit det. entschieden werden können.
Dafür definieren wir
was beschreibt sie? #card
Diese neue Menge enthält also alle Sprachen, die in polynomieller Laufzeit von einer deterministischen Turingmaschine entschieden werden können
(Bedenke, dass Polynom sich in der Betrachtung der Laufzeit immer auf den größten Grad reduziert)
Komplexitätsklasse PSPACE
[!Req] Definition PSPACE
Wir wollen noch die Menge von Sprachen, die einen polynomiellen Speicherbedarf haben, beschreiben / als Menge zusammenfassen.
Wie definieren wir diese Menge PSPACE? #card
Die Menge wird folgend beschrieben:
–> Heißt also, dass sie alle Sprachen inkludiert, die mit einem polynomiellen Speicherbedarf von einer det. Turingmaschine entschieden werden können.
TIME SPACE
[!Req]
Für jede Funktion gilt folgend:
(Bedenke, dass , die Komplexitätsklassen beschreibt, die alle TMs aufweist, die die Größe hat)
Das kommt daher, dass jede (det!), die in der Zeit arbeitet, nicht mehr als Felder beschreien kann.
Es gilt also für jede DTM (Bedenke, dass , die Komplexität für eine spezifische Tm beschreibt (also für eine beliebige TM!))
Hierbei ist die Frage, warum die Ungleichheit bei einzelnne BSp genau anders war.
In schritten können wir höchstens Schritte beschreiben.
Wir beschränken uns hierbei nach unten, weil wir eine maximale Grenze setzen, ferner aber auch weniger angewandt / verwenedet werden kann! –> Damit haben wir also eine Grenze nach oben hin, aber können natürlich auch kleinere Werte erhalten, die die Komplexität verursachen!
Bemerkungen
- Konkret haben wir hier ein Entscheidungsproblem beschrieben, denn man kann hier enweder angeben, ob:
- es einen kürzesten Pfad von gibt oder nicht (ja / nein!)
- Was ist der Wert der Funktion am Punkt
- Welcher Input minimiert den Wert der Funktion ?
Solche Probleme kann man meist auf ein Entscheidungproblem reduzieren oder es mit diesem approximieren, weil endliche Mengne durch Binärzahelen in logarithmischer Komplexität -> Länge der Binärzahl / Anzahl der entscheidungsprobleme <- adresssierbar sind!
[!Example]
Wir stellen die Frage: “Was ist die Länge des kürzesten Weges von ”?
Diese Frage können wir umstellen, sodass man hier auch ein entscheidungsproblem betrachtet -> was sich aber Schritt für Schritt an die Lösung hinarbeitet ( also versucht nach und nach dem kürzesten Pfad naher zu kommen!).
Wir stellen die Frage folgend um:
- Gibt es einen Weg kürzer ?
- Gibt es einen Weg kürzer als
- oder
–> Also immer die gleiche Frage, aber wir werden in der Länge immer kleiner!
All diese Fragen werden nach logarithmisch vielen Schritten konvergieren und somit wird die polynomielle Laufzeit nicht wirklich veränder –> Man kann es also gleich formulieren / das gleiche Meinen!
Fokus lag auf theoretische Turingmaschinen
Wir haben diese obige Definition primär so gesetzt, dass wir uns auf Turingmaschinen bezogen haben.
Dass wir diese Struktur nun auch auf reale Maschinen anwenden kann, ist logisch, da sie ähnlich funktionieren, auch wenn sie eine spezifischerre Konstruktion / Architektur aufweisen!.
[!Tip] Theoretische Übersetzung von TM -> praktische Implementation
Betrachten wir ein Beispiel:
Sei ein Graph, und die Frage, ob er zusammenhängend ist –> Entscheidungsproblem!
Wie gehen wir jetzt vor, um für eine TM die Komplexität zu beschreiben, wie können wir das aber unter praktischer Betrachtung ebenfalls lösen? #card
Formal müssten wir hier eine TM mit Input konstruieren, die bits des inputs zählt und die Zahl der Rechenschritte von zählen muss / wird.
Praktisch: ( bezieht sich auch viel auf 111.00_anchor) wir haben eine bessere / praktischere Lösung beschrieben:
- setzen wir
- definiere Algorithmus, welcher die Zahl der arithmetischen Operationen im Algorithmus zählt ( also er kann abhängig von sagen, wie viele Schritte wir brauchen werden!)
- Anschließend zeigen wir dann noch
Diese Struktur ist formal nicht ganz gleich.
–> Es wird hier eine von Neumann Architektur (RAM).
Wir wissen, dass RAMs turing äquivalent und somit kann man diese RAM-Konstruktion dann auch mit einer TM in linearer Zeit darstellen kann.
Further Resources
Wir werden uns dann weiter mit den NP-Problemen beschäftigen!