Kapitel 2 Ein kurzer Ausflug in die Komplexitätstheorie Dieses Kapitel soll in die Grundlagen der Komplexitätstheorie einführen. Die Ausführungen sind relativ informell und decken keinesfalls die Inhalte der Vorlesung „Komplexitätstheorie“ ab. Um die Komplexitäten von Algorithmen und algorithmischen Problemstellungen zu untersuchen, benötigen wir in erster Linie ein Maß um diese anzugeben. Wir greifen hierzu auf die sogenannten Landau-Symbole zurück. Seit ihrer ersten Verwendung durch Bachmann im Jahr 1892 hat sich die O−Notation (gesprochen: „groß Oh“) durchgesetzt, um die Wachstumsgeschwindigkeit oder Größenordnung von Funktionen f : N → R+ und damit von Rechenzeiten zu messen. Ziel ist es, die Beziehungen „≤“, „≥“, „=“, „<“ und „>“ auch auf Funktionen anzuwenden, wobei die strikte Definition f ≤ g, falls f (n) ≤ g(n) ∀n ∈ N ist, durch eine abgeschwächte Bedingung ersetzt wird. Definition 2.1. • g ∈ O(f ) hat die Interpretation, dass g asymptotisch nicht schneller als f wächst, und ist definiert durch die Bedingung, dass g(n)/f (n) durch eine Konstante c nach oben beschränkt ist. D.h.: O(f ) = {g | g : N → R+ , ∃c > 0 ∃n0 ∈ N ∀n ≥ n0 : g(n) ≤ c · f (n)} • g ∈ Ω(f ) hat die Interpretation, dass g asymptotisch nicht langsamer als (oder mindestens so schnell wie) f wächst, und ist definiert durch f ∈ O(g). D.h.: Ω(f ) = {g | g : N → R+ , ∃c > 0 ∃n0 ∈ N ∀n ≥ n0 : g(n) ≥ c · f (n)} • g ∈ Θ(f ) hat die Interpretation, dass f und g asymptotisch gleich schnell wachsen, und ist definiert durch g ∈ (f ) und g ∈ Ω(f ). D.h. Θ(f ) = O(f ) ∩ Ω(f ) bzw.: Θ(f ) = {g | g : N → R+ , ∃c > 0 ∃n0 ∈ N ∀n ≥ n0 : 1 · f (n) ≤ g(n) ≤ c · f (n)} c • g ∈ o(f ) hat die Interpretation, dass g asymptotisch langsamer als f wächst, und ist definiert durch die Bedingung, dass g(n)/f (n) eine Nullfolge ist. D.h.: o(f ) = {g | g : N → R+ , ∀c > 0 ∃n0 ∈ N ∀n ≥ n0 : g(n) < c · f (n)} 1 KAPITEL 2. EIN KURZER AUSFLUG IN DIE KOMPLEXITÄTSTHEORIE 2 • g ∈ ω(f ) hat die Interpretation, dass g asymptotisch schneller als f wächst, und ist definiert durch f ∈ o(g). D.h.: ω(f ) = {g | g : N → R+ , ∀c > 0 ∃n0 ∈ N ∀n ≥ n0 : g(n) > c · f (n)} Beobachtung 2.2. Falls G ein zusammenhängender Graph ist, so gilt m(G) ∈ O(n(G)2 ) und m(G) ∈ Ω(n(G)). Bei der Untersuchung von algorithmischen Problemstellungen unterscheiden wir grundsätzlich zwischen Optimierungsproblemen und Entscheidungsproblemen. Ersteres sind vielfältige Fragestellungen nach (optimalen) Lösungen während letztere nur mit ja oder nein beantwortet werden können. Optimale Lösungen von Optimierungsproblemen und mögliche Lösungen, die zur positiven Beantwortungen eines Entscheidungsproblems führen, nennen wir positive Lösungen. Beispielsweise ist die Frage nach der kleinsten Zahl in einer gegebenen endlichen Teilmenge der ganzen Zahlen ein Optimierungsproblem. Hingegen ist die Frage, ob eine gegebene endliche Teilmenge der ganzen Zahlen eine Zahl kleiner 0 besitzt, ein Entscheidungsproblem. Falls M = {−3, −1, 0, 2, 5} die Teilmenge der ganzen Zahlen ist, so ist −3 die einzige positive Lösung des Optimierungsproblems. Hingegen sind −3 und −1 positive Lösungen des Entscheidungsproblems. Jedes Optimierungsproblem kann in ein Entscheidungsproblem umgeformt werden. Zum Beispiel erhalten wir aus der Frage nach der kleinsten Zahl in einer endlichen Teilmenge der ganzen Zahlen das folgende Entscheidungsproblem: Gegeben seien eine ganze Zahl C und eine endliche Teilmenge M der ganzen Zahlen. Ist C die kleinste Zahl aus M ? Mit Hilfe der obigen Definitionen können die Worst-Case Komplexitäten von Algorithmen analysiert werden. Dabei unterscheidet man zwischen der (Lauf-) Zeitkomplexität, was die Anzahl der im schlechtesten Fall nötigen Rechenschritte ist, und der Platzkomplexität, was den im schlechtesten Fall nötigen Bedarf an Speicherplatz darstellt. Sei zum Beispiel der folgende Algorithmus gegeben. Algorithmus 1 Input: Ein Graph G mit Knoten {v1 , v2 , . . . , vn(G) } und Knotengewichten w : V (G) → R. 1: for i1 = 1 to n(G) do 2: for i2 = 1 to n(G) do .. 3: . 4: for in(G) = 1 to n(G) do Pn(G) Bestimme w({vi1 , vi2 , . . . , vin(G) }) = j=1 w(vij ). 6: end for .. 7: . 8: end for 9: end for 10: Bestimme S ? ∈ Argmax{w({vi1 , vi2 , . . . , vin(G) }) : i1 , i2 , . . . , in(G) ∈ [n(G)]}. Output: Eine Menge S ? ⊆ V (G). 5: Der Algorithmus berechnet n(G)n(G) mal den Wert w({vi1 , vi2 , . . . , vin(G) }) und speichert diesen ab. Das heißt, er benötigt n(G) Speicherorte zum Verwahren der Gewichte w(vi ) und n(G)n(G) Speicherorte zum Verwahren der Werte w({vi1 , vi2 , . . . , vin(G) }). Die Platzkomplexität ist also O(n(G)n(G) ). Bei der Bestimmung von w({vi1 , vi2 , . . . , vin(G) }) für gegebene {i1 , i2 , . . . , in(G) } werden n(G) Werte in O(n) aufsummiert. Ein Maximum von {w({vi1 , vi2 , . . . , vin(G) }) : i1 , i2 , . . . , in(G) ∈ [n(G)]} KAPITEL 2. EIN KURZER AUSFLUG IN DIE KOMPLEXITÄTSTHEORIE 3 kann in O(n(G)n(G) ) Rechenschritten gefunden werden. Also ist die Zeitkomplexität des Algorithmus O(n(G) · n(G)n(G) ). Im Allgemeinen beschränken wir uns in dieser Vorlesung auf Untersuchungen zur Zeitkomplexität von Algorithmen und algorithmischen Problemstellungen. Definition 2.3. Ein Algorithmus heißt effizient, wenn es ein Polynom p : N → R gibt, sodass die Zeitkomplexität des Algorithmus O(p(n)) ist, wobei n das Maß für die Länge der Eingabe ist. Ein algorithmisches Problem, gleich ob Optimierungs- oder Entscheidungsproblem, gehört zur Komplexitätsklasse P der polynomiell lösbaren Probleme, wenn es durch einen effizienten Algorithmus gelöst werden kann. Wir sagen dann, das Problem ist effizient lösbar. Ein algorithmisches Problem gehört zur Komplexitätsklasse NP der nichtdeterministisch polynomiell lösbaren Probleme, wenn eine positive Lösung, egal woher diese stammt, in polynomieller Zeit überprüft werden kann. Auch wenn man für einen Großteil der Problemstellungen aus NP in polynomieller Zeit eine nicht positive Lösung verifizieren kann, ist unklar ob dies für alle Problemstellungen aus NP gilt. Es is weiterhin klar, dass jedes Problem in P auch zu NP gehört. Ob auch jedes Problem aus NP zu P gehört, führt zum Milleniumproblem = P NP. 6= Beispielweise gehören die beiden oben eingeführten Probleme zu kleinsten Zahlen in einer endlichen Teilmenge M natürlicher Zahlen zur Klasse NP. Für eine beliebige natürliche Zahl C kann in O(1) überprüft werden, ob diese negativ ist und in O(|M |) festgestellt werden, ob sie ein Element aus M ist und ob sie gegebenenfalls die kleinste Zahl aus M ist. Allgemein kann man die kleinste Zahl einer gegebenen endlichen Menge M in O(|M |) bestimmen. Damit gehören beide Probleme auch zu P. So wie mit Hilfe der Landau-Symbole Funktionen miteinander vergleichen werden, so können wir auch algorithmische Problemstellungen untereinander in Beziehung setzen. Definition 2.4. Seien A und B zwei algorithmische Probleme. Das Problem A ist algorithmisch nicht schwieriger als das Problem B, in Zeichen A ∝ B, wenn es einen Algorithmus zur Lösung von A mit Maß n für die Länge der Eingabe gibt, der auf einen Algorithmus von B zurückgreifen darf und folgende Eigenschaften hat: • Die Rechenzeit ohne die Aufrufe zur Lösung von B ist durch ein Polynom p(n) beschränkt. • Die Anzahl der Aufrufe des Algorithmus zur Lösung von B ist durch ein Polynom q(n) beschränkt. • Die Eingabelänge für jeden Aufruf des Algorithmus zur Lösung von B ist durch ein Polynom r(n) beschränkt. A und B sind algorithmisch gleich schwer falls A ∝ B und B ∝ A. Mit Hilfe von Definition 2.4 können weitere Komplexitätsklassen definiert werden. Definition 2.5. Ein algorithmisches Problem A gehört zur Komplexitätsklasse NP-schwer der NP-schweren Probleme, falls für alle B ∈ NP die Beziehung B ∝ A gilt. A gehört weiterhin zur Komplexitätsklasse NP-vollständig der NP-vollständig Probleme, falls A ∈ NP und A ∈ NP-schwer. Jedes Problem der Klasse NP-vollständig gehört auch zur Klasse NP-schwer. Andersherum ist dies jedoch nicht immer der Fall.