mit dynamischer Programmierung. Es geht um ein Konzept,
mit dem wir Optimierungsprobleme lösen können, die eine spezielle Struktur haben,
nämlich die sich aus optimalen Teilproblemen zusammensetzen.
Und das Konzept, was man anwendet, besteht immer aus
diesen vier Schritten hier. Wir müssen erst einmal schauen,
ob unser Problem, diese entsprechende Struktur hat,
die notwendig ist für die Lösung mit dynamischer Programmierung.
Das heißt, ob wir es durch Lösung von Teilprobleme,
die unabhängig voneinander sind, bearbeiten können,
dann definieren wir den Wert der optimalen Lösung rekusiv.
Wir sagen einfach, wie wir aus optimalen Teillösungen
die richtige Gesamtlösung zusammensetzen würden
Dann drehen wir die Berechnungsreihenfolge um und rechnen nicht mehr top down,
so wie die Rekussion definiert ist, sondern bottom up,
indem wir erst Teillösung berechnen,
für kleine Teilprobleme und die Informationen speichern und wiederverwerten.
Und am Ende überlegen wir uns den letzten Schritt,
wie wir aus dem optimalen Zielwert,
den wir aus dem dritten Schritt schon berechnet haben,
auch die optimale Lösung erzeugen können.
So, das haben wir konzeptionell uns angeschaut und haben uns
dann ein Beispiel angeguckt.
Heute ist irgendwie
Haha. Aha. Jetzt dürfen Sie die Aufnahme stoppen.
Das ist jetzt so peinlich. Aber wir können noch
Microsoft fragen, was die Lösung für dieses Problem ist
Oha. Okay. Ich hoffe, das passiert jetzt nicht immer
an der Stelle. Okay. So. Okay, gut. So,
dann haben wir uns ein Beispiel angeschaut, wo wir diese Technik anwenden konnten.
Das war ein sehr einfaches Beispiel. Wir hatten hier
so zwei Montagebänder und es ging um eine Zeitoptimierung hier in diesem Grafen.
Und wir sind einfach hier an diesem Beispiel diese Schritte durchgegangen,
haben gesehen, dass wir die optimale Verarbeitungszeit,
die wir berechnen wollten, berechnen können,
indem wir die optimale Zeit bis zu einem gewissen Punkt,
zu einer gewissen Station berechnen können und daraus den nächsten Wert für die nächste Station berechnen konnten und haben das dann in erst rekrusiv gelöst,
was zu einer langsamen Lösung führt und dann durch die Umkehr der Berechnungsreihenfolge ein dynamisches Programmierverfahren entwickelt,
was effizient die Berechnung ermöglichte.
So, heute wollen wir uns, oh nee.
Ah, okay. Irgendwie streikt nur mein Pointer hier
Nein, das steckt hier schon dran. Aber ist egal
. Ich kann das auch mit dem Keyboard hier machen.
So, heute schauen wir uns ein zweites Beispiel an.
Eins, was ein bisschen komplexer ist als das,
was wir gerade gesehen haben, was ein klassisches Beispiel dafür ist,
wie dynamische Programmierung eingesetzt werden kann.
Es geht um Matrix-Kettenmultiplikation. Das Problem habe ich
am Ende der letzten Vorlesung auch schon eingeführt.
Wir wollen Matrizen miteinander multiplizieren, warum wir das tun wollen.
Es spielt im Weiteren keine Rolle. Die haben unterschiedliche Dimensionen,
also Anzahl Zeilen und Spalten und zur Berechnung dieses Produkts,
zweier Matrizen müssen wir halt für jedes Element der Ausgabematrix,
die Teile mal dem Spalte quasi als Skalarprodukt ausrechnen
Ja, die Laufzeit dafür ist Anzahl der Zeilen der ersten Matrix,
mal Anzahl der Spalten der ersten Matrix-Malanzahl der Spalten der
zweiten Matrix und die Anzahl der Spalten der ersten Matrix,
das M hier muss identisch sein mit der Anzahl der Zeilen der zweiten Matrix,
sonst können wir das Produkt nicht berechnen.
So, Matrix-Multiplikation ist assoziativ, das heißt,
wenn wir drei Matrizen miteinander multiplizieren wollen,
A, mal B, mal C,
dann können wir entweder B,
mal C miteinander multiplizieren und dann mit A oder A mal B und dann mit C. Ja,
und da kommt das gleiche Ergebnis raus.
Allerdings die Rechenzeit, die wir dafür benötigen,
ist ja unterschiedlich
Aber wenn wir jetzt mal ein kleines Beispiel nehmen mit drei unterschiedlich großen Matrizen,
dann sehen wir,
dass die Ergebnismatrizen nach einer ersten Multiplikation sehr unterschiedlich groß sein können,
also eins mal A2,
würde zu einer zehnmal fünf Matrix führen und A2 mal A3 zu einer 100 mal 50 Matrix,
zu einer sehr großen Matrix.
Und dementsprechend ist die Anzahl der Multiplikationen,
die wir benötigen, wenn wir erst A1 und mit A2
multiplizieren und dann mit A3 um einen Faktor 10 geringer,
als wenn wir erst A2 mit A3 multiplizieren und dann nachher
das Ergebnis mit A1. Ja,
das ist
so 75.000 Multiplikationen führt
Okay, so,
wenn wir jetzt also eine ganze Kette von Multiplikationen hätten,
müssen wir überlegen, in welcher Reihenfolge wir das Ganze tun.
Diese Arten von Multiplikationen treten auf bei verschiedenen Simulationsverfahren.
Da hat man dann teilweise mit diesen sehr großen Matrizen zu tun.
Ja, okay, so. Dann haben wir uns auch schon angeschaut,
wie viele Möglichkeiten es gibt, dieses Produkt zu bilden.
Das ist natürlich das Erste, was man schauen sollte,
weil wenn wir gar nicht so viele verschiedene Alternativen haben,
dann können wir vielleicht durch ein einfaches Aufzählungsverfahren das Problem auch lösen.
Hier ist das natürlich nicht der Fall,
hier gibt es sehr, sehr viele verschiedene Arten,
diesen Ausdruck zu klammern
Und für N gleich 4 stehen einfach mal die fünf Optionen
hier aufgelistet, wie wir das tun können.
Wir können das Ganze rekursiv beschreiben. Hier schon die Anzahl und
wir sehen auch gleich, dass dieses rekursive Schema genau das ist,
was wir auch verwenden werden.
Nämlich, wir können uns überlegen, wann findet denn die
letzte Multiplikation statt? Also in diesem letzten Ausdruck,
hier wäre die letzte Multiplikation zwischen der Matrix, Armer WMC und D.
Also da wäre die letzte Multiplikation. Oder hier oben
können wir uns das auch überlegen. Wenn wir wissen,
wo die letzte Multiplikation stattfindet, an welcher Position K,
dann haben wir zwei Faktoren.
Alle Matrizen,
die davor stehen und alle,
die dahinter stehen
Müssen die ja erst mal miteinander ausmultiplizieren. Und dann können wir rekrusiv beschreiben,
wie viele Möglichkeiten es da gibt. Also das heißt,
wenn wir an der Position K hier die Teilung vornehmen,
dann haben wir für den ersten Faktor,
das sind die Matrizen A1 bis AK,
P von K viele Möglichkeiten, für den zweiten Teil,
das sind die Matrizen K plus 1 bis N,
P von N-K viele Möglichkeiten, die können wir alle miteinander kombinieren,
also bilden wir das Produkt von beiden.
Und dann können wir das K noch an einer beliebigen Position
in dieser Liste setzen,
was uns dann zu dieser Summenformel führt für die Anzahl der Möglichkeiten,
wie wir die Klammern in einen solchen Ausdruck setzen können
Das waren dieser Ausdruck P von N, der hier rekursiv definiert ist.
Den kann man näherungsweise bestimmen. Nicht exakte Ernährungsweise,
das sind die Katalanzahlen und wir sehen, dass es hier exponentiell wächst.
Gut, so, jetzt geht es darum,
die Lösungen zu entwickeln für unseren Ansatz.
Oh nee. Ich fasse es nicht.
Ich klaue dir mal den
Hm. Tja
Mal ein bisschen zu weit. Ich hoffe,
das müssen wir jetzt nicht alle fünf Minuten machen.
Das wäre bitter. Okay, so, wie wollen wir das,
wie machen wir das? Okay, wir können als erstes
überlegen, wie berechnen wir ein Zeilprodukt aus dieser Liste?
Wir haben also jetzt diese Liste der Matritzen,
A1 bis A1 und wir können jetzt jedes beliebige Teilprodukt von
einer Position I bis seiner Position J berechnen.
Das ist also dann das Produkt AI, mal AI plus 1
und so weiter bis AJ und das beschreiben wir so mit so einem AI.j.
Das ist also dieses Teilprodukt. Jetzt können wir so beschreiben und wir wissen auch schon,
wie groß das ist, also so eine Matrix AI,
die hat die Größe PI minus 1 mal PI,
das ist also die Teil der Eingabe, wie groß diese Matritzen sind
Und wir wissen ja immer, die Anzahl der Spalten,
der ersten Matrix, entspricht der Anzahl der Zeilen der zweiten Matrix.
Deswegen können wir hier genau jetzt ausrechnen, wie groß das Resultat ist.
Und wenn wir nämlich jetzt das Produkt AI bis AJ berechnen,
dann ist die resultierende Matrix,
ergibt sich entsprechend aus dem Produkt der ersten Karmatritzen und dann
der zweiten Carplus 1 bis J. Und um das auszurechnen,
brauchen wir dann eine Multiplikation, die Multiplikation
Für die letzte Matrix-Multiplikation hier. Und das sind PI minus eins,
mal PK, mal PJ, viele Skalare-Multiplikationen für diese letzte Multiplikation.
Weil die Matrix, die hier rauskommt,
hat eben die Anzahl der, jetzt würde ich mal aufpassen,
ob ich nicht durcheinanderkomme,
die Anzahl der Zeilen von der Matrix AI und die Anzahl der Spalten von der Matrix sagt AK.
Das steht hier mit dem PI minus eins und dem PK
. Okay, also die drei P-Werte multipliziert ist die Anzahl
der Skalaren-Multiplikationen für das Produkt.
Und dann brauchen wir natürlich noch die Multiplikation für den ersten
Teil und für den zweiten Teil.
Aber die können wir eben
rekursiv beschreiben
So, die Position K. kennen wir jetzt noch nicht.
Also wenn wir das ausrechnen wollen, da können wir die
Position hier beliebig festlegen in dem String und dann entscheiden,
wo wir genau das letzte Produkt festlegen wollen.
Und die Anzahl der Multiplikationen, die wir brauchen,
für den ersten Faktor und für den zweiten Faktor hier,
die können wir entsprechend rekrusiv beschreiben.
Und insbesondere ist die Anzahl der Multiplikationen, die wir hier brauchen,
ja, das ist unabhängig davon, was auf der anderen Seite hier passiert.
Ja, insofern haben wir unabhängige Teilprobleme, die können wir lösen,
wir können das Optimum bestimmen und die dann kombinieren.
So, Wie sieht das Ganze jetzt aus?
Wir überlegen uns, wie viele Multiplikationen benötigen wir,
um all das Produkt der Madrizen von Matrix I bis Matrix J zu berechnen.
Das beschreiben wir in diesem MIJ hier. Und da wissen wir,
okay, MIJ, das ist die Anzahl der Multiplikationen,
also wir gehen jetzt davon aus,
wir machen die letzte Multiplikation an Position K,
dann wissen wir, wir brauchen erst mal MIK,
viele Multiplikationen für den ersten Faktor.
Dann brauchen wir MK plus 1 bis J,
viele Multiplikationen für den zweiten Faktor, ist also rekusiv hier beschrieben.
Und die letzte Multiplikation an der Position kam,
müssen wir durchführen, das sind PI minus 1 mal PK,
mal PJ, viele Multiple Applikationen dafür.
Und das ist dann die Summe, die wir brauchen. Wir wissen nicht,
wo wir das K hinlegen sollen, und das ist unser Freiheitsgrad,
wir können das jetzt optimieren, wir können eine Position suchen,
die hoffentlich die minimale Anzahl von Multiplikationen dann liefert.
Das heißt, das MIJ ist eine Minimierung über diesen Wert K.
Also entweder ist es null, wenn I gleich dort ist,
okay, also wenn I gleich dort ist,
müssen wir gar nichts multiplizieren, das haben wir nur eine Matrix.
Ansonsten nehmen wir einfach die Position K,
die uns zu den minimalen Anzahl von Multiplikationen führt.
Wir haben das Minimum von I,
kleiner gleich kleiner J,
das sind die möglichen Positionen für
Für die letzte Multiplikation. Und dann haben wir diese Formel hier.
M-I-K plus M, K plus 1j plus P-I minus 1
mal PK mal PJ. Okay, damit können wir
das ausrechnen. Und was wir eigentlich nur wissen wollen,
ist, dass M-1-N, wenn wir also N-Fiel Matrizen haben,
das müssten wir dann entsprechend ausrechnen.
Gut, so, dann machen wir das erst mal rekursiv.
Das war die erste Möglichkeit, das zu tun.
Sagen jetzt, okay, wir haben Recursive Matrix Chain,
das ist unser Algorithmus. Die Matrizen übergeben wir jetzt
gar nicht. Wir sagen der Routine gar nicht,
welche Matrizen wir verwenden, weil die interessieren uns nicht.
Wir wollen ja
nur die Reihenfolge wissen
Bestimmung der Reihenfolge, müssen wir die Anzahl der Multiplikation minimieren.
Und dazu müssen wir nur wissen, wie groß die Matratzen sind.
Also wie viele Zahlen und wie viele Spalten sie haben.
Und das speichern wir einfach nur in diesem VectorP.
Da steht es drin. Das heißt,
wir brauchen den VectorP und wir kriegen hier den Bereich,
für den wir die Berechnung durchführen wollen, also I bis J.
Und wenn wir diese Funktion jetzt das erste Mal aufrufen,
um für unsere ganze Liste von Matrizen ein Ergebnis zu berechnen,
dann rufen wir das auf mit I gleich 1 und J gleich N.
Also für die gesamte Kette. So,
wenn I gleich J ist,
dann müssen wir überhaupt nicht multiplizieren,
dann ist das Ergebnis also null,
also null Multiplikationen,
benötigen wir zur Berechnung des Ergebnisses
Andernfalls müssen wir minimieren in dieser Vorschleife hier.
Also das entspricht diesem Minimum. Wir setzen das M hier
zunächst auf unendlich. Und in der Vorschleife gehen wir
alle möglichen Positionen für die letzte Multiplikation durch. Ja,
das K, das läuft also jetzt von I bis J minus eins.
Und wir nehmen jeweils das Minimum aus dem bisher gefundenen Minimum M.
und diese Formel hier. Das sind die rekusiven Aufrufe,
Rerecursive Matrix Chain und dann PIK, also von I bis K,
plus, und dann kommt die Funktion mit den Parametern K plus 1 J,
das ist also hier der zweite, rekusive Aufruf,
zweiten Teil der Formel.
Und dann kommt hier hinten dieses Produkt,
also P von I minus 1 mal P von K mal P von J,
um die Anzahl der Multiplikation für den letzten Rechenschritt zu beschreiben.
So, und dann können wir das M zurückgeben und sind fertig.
Das ist übrigens falsch eingerückt. Das haben sie hoffentlich sofort
gesehen hier. Okay, so. Damit hätten wir also
eine Lösung zu Berechnen des Problems und wieder stellt sich natürlich
die Frage, ist das effizient oder nicht effizient?
Schon fast eine rhetorische Frage. Es ist jetzt nicht sehr effizient,
weil wir hier die rekursiven Aufrufe immer wieder durchführen werden,
für auch immer wieder die gleichen Teilabschnitte.
Ja, und das würde sehr aufwendig werden, das zu tun.
So, Mist. Das gibt es echt nicht.
Tja, ja. Was halten Sie denn da vor,
die VfB-Präsentation als PDF zur Xbox-Here sagen?
Das wäre auch noch eine Option. Die andere Option wäre
den Rechner mal komplett neu zu starten
Wer ist für Rechner Neustarten? Okay, sie sind fürs PDF
. Alle haben sie jetzt nicht mit gerechnet, ne?
Ja, das drücke ich schon die ganze Zeit.
Aha. So, okay
Gut. So, hier waren wir. Okay, so,
die Laufzeit von dem Ganzen. Hier ist eine ganz grobe
Abschätzung der Laufzeit. Ist wie folgt. Wir wissen,
dass wir eine konstante Laufzeit haben, wenn unsere Kettenlänge nur eins ist,
also wenn wir nur eine Matrix haben,
dann drücke ich mal hier den Knopf.
Wenn I gleich J ist, Haben wir konstante Zeit?
Ansonsten müssen wir hier die Rekursiven Aufrufe starten.
Und das können wir direkt umsetzen in eine Rekurrenzgleichung.
Wir haben also hier die Summe von K gleich 1
bis N minus 1. Das ist die Schleife.
Und in jedem Schleifenaufruf haben wir zwei rekursive Aufrufe.
T von K für die Zeit der Berechnung der optimalen Multiplikationsreihenfolge
der Kette von 1 bis K und T von N-K für den zweiten Teil.
Und diese Rekurrenzgleichung aufzulösen, ist es jetzt nicht so einfach.
Da muss man ein bisschen rumrechnen und man kann es auch
nicht so exakt, aber man kann relativ schnell zeigen,
dass die Laufzeit auf alle Fälle größer gleich 2 hoch N
minus 1 ist.
Also O von 2 hoch
N minus
Okay, für die Gesamtlaufzeit. So, wie machen wir
das Ganze jetzt mit einem dynamischen Programmieransatz? Wir überlegen uns erstmal,
gibt es überlappende Teilprobleme, die gibt es offensichtlich,
weil so viele verschiedene rekrusive Aufrufe gibt es ja überhaupt nicht.
Ja, wir haben unsere Liste von Matrizen, die geht von 1 bis N.
Ja, und wir nehmen immer Teilstücke daraus und sagen,
wie lange brauchen wir, um das zu multiplizieren.
Aber wie viele verschiedene Teilstücke gibt es denn davon?
Also wir haben N-Möglichkeiten zur Wahl des Anfangspunktes dann nochmal,
ja, maximal N, eigentlich natürlich sogar weniger,
für die Wahl bis N-Punktes, ja.
Wir können also genau sagen, alle möglichen Aufrufe dieses
rekusiven Verfahrens werden mit Parametern I und J gestartet,
wobei die zwischen 1 und N liegen.
Und es gibt nur N mal N plus 1 halbe,
viele Teilprobleme, die überhaupt auftreten können.
Andere Kombinationen von I und J gibt es überhaupt nicht,
mit denen wir das Programm aufrufen. So, das sind
nur Endquadrat, viele im abgeschätzt. Endquadrat viele. Und das heißt,
was wir natürlich tun können, ist,
wir können diese Endquadrat vielen rekusieren, aufrufe, einfach ausführen,
das Ergebnis merken und dann daraus das Ergebnis rekonstruieren, aufbauen,
ohne dass wir jedes Mal rekursiv neu die Berechnung anstoßen.
So, also wir speichern das Resultat für eine Eingabe PJ in einer N-N-Matrix.
Ja, wir werden das jetzt eine große Matrix anlegen.
Das sind ein bisschen Speicher, investieren. Ja,
und auf der einen Dimension liegt das I und hier liegt das J. Ja,
und dann können wir an irgendeiner Position,
hier können wir dann das M-I-J speichern
Okay, wenn wir jetzt aber das MEJ hier berechnen wollen,
dann brauchen wir ja andere Werte. Ja,
jetzt zurückgehen und gucken, wenn wir jetzt ein MEJ bestimmen wollen,
dann brauchen wir ein M-IK und ein MK plus 1J.
Also wir müssen irgendwie hier auf andere Zellen in dieser Matrix zugreifen.
Das heißt, wir können jetzt nicht irgendwie das Ganze berechnen,
sondern wir müssen uns eine Strategie überlegen,
in welcher Reihenfolge wir diese Matrix füllen wollen mit Werten.
So, aber wir sehen schon, wenn I gleich J ist,
dann brauchen wir keinen rekursiven Aufruf, dann wissen wir,
da kommt immer null raus.
Also I gleich J, das sind die Felder hier auf
der Diagonalen
Hier kommt immer null raus. Das heißt, die können
wir zuerst berechnen. Und was man dann tun kann,
ist, dass man als nächstes die Zellen nimmt,
die eine möglichst kurze Kette haben.
Also wenn wir zum Beispiel sagen,
wir nehmen das Produkt von 3 von Matrix 3 bis 4.
Da haben wir auch nur, da haben wir
gar keine Alternativen, welcher Reihenfolge wir das multiplizieren.
Das sind die Felder hier neben der Diagonale. Hier und hier und hier
. Und als nächstes würden wir dann die Felder berechnen,
die 2 von der Diagonale weg sind. Weil das,
was wir hier sehen, ist, dass die Länge dieses dieser,
der Kette, die wir multiplizieren, wenn wir diesen M-Wert dafür berechnen wollen,
dann brauchen wir immer nur WM-Werte von kürzeren Ketten.
Wenn wir einmal sagen, wir haben die Projekt,
die Kette, wenn das hier die komplette ist,
von 1 bis N und wenn wir den Abschnitt von Ibis J berechnen wollen,
dann legen wir irgendwo in der Mitte das K und die Stücke,
die wir haben, die sind garantiert immer kürzer als die Gesamtkette,
die wir haben.
Das heißt, was wir machen müssen, ist,
wir brauchen eine Berechnungsreihenfolge,
wo die entsprechende Länge hier dieser Teilketten langsam wächst.
Ja, wir fangen an mit ganz kurzen Ketten und
dann nehmen wir immer längere Ketten
Und das ist genau die Reihenfolge, in der wir das Ganze tun.
Wir machen eine Bottom-Up-Berechnung. Also wir fangen mit kurzen Ketten an,
machen dann längere. Und zwar mit wachsender Kettenlänge. Das heißt,
die Berechnung geht von MIJ-Werten zu, wo der Wert der Kettenlänge,
das ist ja j minus i plus 1,
also entwert minus Anfangswert plus 1, wo der langsam steigt.
Weil wir wissen,
dass zur Berechnung von MIJ brauchen wir nur Einträge M-UV,
wobei die Kettenlänge von,
also V-U plus 1 kleiner ist,
als die Kettenlänge zwischen I und
So, also wir müssen das jetzt mit steigender Länge berechnen.
So, und das ist genau die Art und Weise,
wie wir das tun.
Hier ist der Algorithmus, der das Ganze macht.
Wir beginnen, also N ist die gesamte Kettenlänge,
die wir jetzt betrachten wollen. Wir wollen hier,
also P ist der Vektor, wo die Dimension der Madridsen drinstehen.
Und wir initialisieren als erstes mal alle MEI mit Null.
Das sind die auf der Diagonalen. Die haben wir jetzt alle,
da können wir überall Null reinschreiben, da wissen wir,
dafür brauchen wir keine Multiplikation.
Dann verwenden wir diese Vorschleife, für die Variable L.
Die läuft von zwei bis N. El beschreibt jetzt die Kettenlänge.
Also in jeder Iteration erhöhen wir die Kettenlänge.
Wir sagen jetzt, wir berechnen alle Zweierketten.
Also es wäre die Multiplikation von A1 mal A2,
von A2 mal A3, A3 mal A4 und so weiter.
Das wäre die erste Iteration für L gleich zwei.
Und danach wird die Kettenlänge auf drei vergrößert.
Das wäre also A1 mal A2 mal A3 oder A2
mal A3 mal A4 und so weiter.
Und es geht so lang, bis wir bei N angekommen sind.
Wenn wir bei En angekommen sind, haben wir die komplette Kette betrachtet.
Jetzt wissen wir alle verschiedenen Produkte, die auftreten können mit dieser Kettenlänge bestimmen.
Das machen wir, indem das I hier von eins
kann man sehr schlecht unterscheiden. Das ist eine Eins,
also von eins bis n minus L plus eins läuft.
Das sind alle möglichen Positionen. Ja,
das sieht dann so aus, dass wir,
wenn das also wieder unser gesamtes RAR ist und wir haben jetzt eine Kettenlänge von,
sagen wir, drei und drei Feldern hier.
Das L wäre jetzt gleich drei. Da müssen wir
dieses Fenster von drei Feldern hier drüber schieben. Ja,
dann werden es die drei, dann die drei und dann die drei berechnet und so weiter. Schieben das also immer so weiter. Das macht diese Vorschleife hier für das I. Ja,
im J wird dann die Endposition bestimmt,
also Fluss L minus 1. Ja,
und dann läuft hier die Minimierung, die Sie schon kennen,
also das MIJ wird auch unendlich gesetzt,
dann kommt hier die Position K, die wir ja nicht kennen,
die wird variiert zwischen I und J minus 1,
also irgendwo in dieser Liste und dann wissen wir,
wie viele Multiplikationen wir brauchen, wenn wir an diese Position,
die letzte Multiplikation legen, das rechnen wir in dem Kuh aus,
das ist MIK plus MK plus 1J plus per E-Mius 1 mal PK mal PJ,
die Formel sollten Sie jetzt schon kennen und wenn das kleiner ist,
als das bisherige Minimum,
dann merken wir uns das als neues Minimum
Okay, das ist schon alles. Und auf
diese Art und Weise würden wir diese Matrix systematisch füllen.
Bis zum letzten Feld, was hier oben irgendwo in der Ecke ist.
Bevor wir uns das nochmal an ein Beispiel anschauen,
hier die Laufzeit des Ganzen. Das sieht man in dem Algorithmus
schon wieder sofort. Wir haben also drei geschachtelte Vorschleifen.
Die hier geht von zwei bis Enden.
Also es sind zehnmal N oder O von N viele Durchläufe.
Die geht von eins bis n minus L. Die wird also,
wenn die Kettenlänge länger wird, wird die hier kleiner.
Aber das sind natürlich im ungünstigsten Fall immer noch en
viele Durchläufe
Und das K, ja, grob abgeschätzt,
kann das im schlimmsten Fall auch wieder in Durchläufe haben,
sodass wir insgesamt auf N hoch 3 Laufzeit kommen.
Für diesen Algorithmus. Was wesentlich besser ist,
als das exponentielle Wachstum, was wir vorher hatten.
Bei dynamischer Programmierung muss man auch immer darüber nachdenken,
wie viel Speicher man braucht. Das ist bei unseren bisherigen Verfahren
eigentlich immer nicht so relevant gewesen. Hier ist es das aber,
weil wir müssen ja in einer großen Matrix alle Zwischenergebnisse speichern.
Und das hängt ja von N ab.
Also je größer die Eingaben, umso größer wir diese Matrix.
Und wir brauchen hier quadratisch viel Speicherbedarf dieser Realisierung hier,
ja, und das kann für große Enden ein Problem werden,
ja, also wenn, also enden,
wenn sie jetzt wirklich was hätten, wo sehr große Zahlen vorkommen,
dann bräuchten sie ja eine sehr große Matrix und dann könnte das der Engpass sein.
Gut, so, wir schauen uns nochmal ein Beispiel an,
wie das aussieht und sie sehen meine Matrix, also diese hier,
die ich hierhin gemalt habe, in einer etwas unkonventionellen Art und Weise aufgeschrieben.
Leider ist es jetzt nicht mehr animiert. So,
was, ja, so, also,
was jetzt passiert ist,
wir nehmen diese Matrix und damit das irgendwie so aussieht wie bei so einem Rekursionsbaum,
wenn ich das vorstellen kann,
drehen wir diese um 45 Grad herum
Ich kompensiere die Animation hier durch Gestik, ja.
Also 45 Grad herumgedreht, das heißt, sie steht jetzt
auf der Spitze, hier ist die Diagonale, ja.
Und sie sehen, auf dieser Achse ist das I,
das läuft von 1, 2, 3, 4, 5 bis 6.
Und auf dieser Achse hier ist das J,
das läuft von 1 bis 6 von hier unten nach hier oben, ja.
Und dieser Eintrag wäre zum Beispiel der Eintrag 1, 1,
das ist 2-2, 3, 3, 4,
4, 5, 5, 6, 6, ja.
Hier sehen Sie unser Beispiel, wir haben sechs Matrizen,
das sind die Dimensionen.
Also die erste Matrix ist eine 30 mal 35 Matrix
Die zweite, eine 35 mal 15 Matrix, die dritte,
eine 15 mal 5, das ist eine 5 mal 10,
eine 10 mal 20, eine 20 mal 25 Matrix.
Jetzt sehen Sie auch nochmal schön,
dass die zweite Zahl immer mit der ersten übereinstimmen muss,
sonst können wir das Matrix-Produkt gar nicht bilden.
Und dieser PI-Vektor würde dann auch so aussehen, da steht dann 30,
35, 15, 5, 10 und 20 dran.
So, das ist der Dimensionsvektor, der das komplett beschreibt.
So, die Matrix wird gefüllt von unten nach oben,
also das nächste, was passiert,
wäre die Kettenlänge 2,
das ist diese Ebene hier
Und da würde man schauen, wie viele Multiplikationen braucht man
beispielsweise bei diesem orangenen Feld hier. Zur Berechnung von A2,
also hier ist das IA2 mal A3. Kann man überlegen,
A2 mal A3, wer hat 35 mal 15 mal 5,
ja, sind 2625 Multiplikationen, um das auszurechnen.
Ja, wenn wir zum Beispiel A4 mal A5 ausrechnen würden,
kommen wir genau auf 1000 Multiplikationen, ist das Feld.
So, wenn wir diese Ebene bestimmt haben, können wir die nächste Ebene berechnen,
das ist die hier, das ist die Kettenlänge 3.
Hier hat man jetzt schon die Wahl für das K,
wenn ich nur Zweimal Tritzen multipliziere, habe ich ja gar keine Alternative,
aber wenn ich drei Madrids multipliziere, multipliziere,
zum Beispiel die zwischen zwei und vier,
dann ist das grüne Feld hier, das wäre also A2-maler,
drei-mal A4,
da kann ich ja entweder erst A2-maler A3 rechnen oder erst A3-mal A4.
Es gibt also zwei Möglichkeiten. Hier sehen wir für die beiden Möglichkeiten
die entsprechenden Formeln, M2-2 plus M34 plus dieses Produkt.
Oder die zweite Variante M2-3 plus M4-4
plus das letzte Produkt. Und jetzt sehen Sie,
dass wir an dieser Stelle auf Matrix-Einträge zugreifen,
die weiter unten stehen
Um die Berechnung da durchzuführen. Okay, weil die Kettenlängen immer kürzer werden. So,
dann hätten wir das berechnet,
denn hier ist es ein etwas komplizierteres schon,
dass es also jetzt M2, 5, ja,
und da sehen wir, da haben wir schon eine ganze Reihe von Möglichkeiten,
drei Möglichkeiten, wo wir das K hinlegen,
entweder zwischen zwei und drei,
zwischen drei und vier oder zwischen vier und fünf und hier sehen sie die drei Formeln,
das ist alles nur ausgerechnet und dann sehen sie das Minimum, davon sind
diese 7125. So, und wenn sie das bis zum Ende machen,
dann kommen sie hier oben an, dann haben sie das M16 berechnet,
das hat 15.125 Multiplikationen und das ist eben dann entsprechend die minimale Anzahl von Multiplikationen,
die sie mindestens brauchen, um dieses Produkt mit der konventionellen Matrix-Multiplikation zu berechnen.
Ja, das ist das Berechnungsergebnis dann hier oben in der Spitze. Okay,
so, was man hier, ja,
was man hier sieht, ist, zum einen,
wie natürlich das Ergebnis aufgebaut wird, aus den anderen Matrizen,
die man vorher berechnet hat, man sieht auch,
man hat im Prinzip ein quadratischen Aufwand zum Füllen dieser Matrix,
das ist ja schon quadratisch viele Felder,
ja,
und zur Berechnung eines jeden Feldes hat man nochmal die Minimierung über die verschiedenen Möglichkeiten was bis zu N-Fielen sind.
Und dadurch kommt der N-Hoch-3-Aufwand zustande. Was wir noch nicht
sehen, ist, wie jetzt diese Klammerung eigentlich aussieht.
Wir sind jetzt in der dynamischen Programmierung an Schritt 3 angekommen.
Also wir haben uns erstens überlegt, wie die optimale,
wie die Lösung zusammengesetzt wird, aus optimalen Teillösungen.
Wir haben eine rekusive Definition durchgeführt und wir haben die Berechnungsreihenfolge umgedreht in einem What-tom-up-Verfahren.
Das steht hier. Und dadurch haben wir die Laufzeit gespart
oder reduziert und haben jetzt ein Verfahren mit Laufzeit N-Hoch-3 erhalten.
Aber wir haben noch nicht das Ergebnis in dem Sinne,
dass wir wissen, in welcher Reihenfolge müssen wir denn jetzt überhaupt Matrizen multiplicieren.
Hier steht nur die Anzahl der Multiplikation. Das war bei
den Montagebändern genauso. Bei den Montagebändern war es auch so,
da wussten wir am Ende die Zeit, aber noch nicht,
wo der Wagen lang fahren musste, um diese Zeit zu erreichen.
Gut, das wäre das Nächste, was jetzt zu tun ist.
Wir müssen irgendwie Informationen speichern, um zu rekonstruieren,
in welcher Reihenfolge wir die Multiplikation durchführen.
So, und da haben wir nochmal kurz zurück. Das machen wir,
indem wir, wie auch bei den den Montagebändern,
uns immer die Schlüsselinformation an der Stelle, wo wir minimiert haben,
wo mir eine Entscheidung gefällt haben, merken.
Hier machen wir, fällen wir die Entscheidung hier. Hier entscheiden wir,
wenn wir das Produkt zwischen I und J ausrechnen,
an welcher Stelle führen wir die letzte Multiplikation durch?
Das ist die Position K. Über die wird minimiert und immer
wenn dieses Produkt kleiner ist, als das bisher gefunden,
dann verwenden wir dieses Produkt, als das bisher gefundene.
Und was wir jetzt einfach tun, ist,
dass wir uns an dieser Stelle das K merken.
Wir merken uns einfach, was ist die letzte Multiplikation,
um zu dem Produkt zu kommen?
Und das ist die einzige Information, die wir brauchen,
um nachher die komplette Klammerungsstruktur zu rekonstruieren
So, so sieht der Algorithmus dann aus.
Wir haben ein zusätzliches Ray, das heißt S,
S-E-J, was für jede Position IJs einfach speichert,
was ist die Position der letzten Multiplikation,
um den entsprechenden Wert, der in MEJ steht,
zu erreichen.
Okay, so, das ist die eine Stelle,
die man ändern muss in dem Algorithmus,
der Rest ist noch alles das Gleiche.
Und daraus können wir jetzt
die optimale Lösung entsprechend rekonstruieren
Wie machen wir das? Ja, wir schauen uns mal an, was rekursiv passiert.
Wir hätten hier oben das Produkt von A1 bis AN.
Das wollen wir ja eigentlich berechnen. Und wenn wir an
dieser Stelle, können wir jetzt in der Matrix nachschauen,
der Matrix M nachschauen, da stände dann die Anzahl der Multiplikationen.
Da stellen wir jetzt diese 15.000 so und so viel Multiplikation,
in dem RAS, in der Matrix S, muss ich sagen,
steht die Position drin, wenn wir bei S1N gucken,
die Position, wo die letzte Multiplikation stattfindet.
Wir wissen also, um die optimale Anzahl von Multiplikationen zu erreichen,
in der Kette von 1 bis N müssen wir zum Schluss an der Position K. multiplizieren.
Das heißt, wir müssen erst mal das Produkt von A1
bis AK berechnen und von K plus 1 bis N und die beiden dann multiplizieren.
So, das heißt, wir brauchen das Produkt von A1
bis AK und das wäre dann S1N. Ja,
und hier hätten wir dann S1N plus 1 bis N.
Das ist der hintere Teil. Und jetzt können wir in
der Matrix S wieder nachgucken, wo die letzte Multiplikation sein müsste,
um hier das entsprechende Optimum zu erhalten.
Ja, und das machen wir dann immer so weiter und
kriegen dann die komplette Klammerungsstruktur. Um die zu erhalten,
können wir jetzt einfach hier in diesem Baum einen Durchlauf machen,
Und dass sie dann so,
also ich zeige immer erst das Bild,
dann gucken wir uns im Pseudo-Code nochmal an.
Wir beginnen hier bei A16. Da steht der Carvert, K gleich 3. Ja,
dann springen wir zu A13 und gucken uns wieder den Carvert an.
Da würde eins stehen. Dann kommen wir zu der Matrix
A1. Ja, und das andere Produkt wäre A23.
Da steht das K gleich 2 drin. Wir wissen also
mit dem Carvert immer, wo wir die Liste trennen müssen.
Dann kommen wir zu dem A2 und zu dem A3. Hier
auf der rechten Seite hatten wir ja noch das Produkt von A4 bis A6.
Da standen K gleich 5, kann man jetzt gar nicht so gut lesen.
Da hätten wir also 4 bis A5 und das würden wir bei K gleich 4 trennen.
Da hätten wir A4 und A5 und hier hätten wir dann das A6. So,
wenn man sich den Baum anguckt, dann sieht man,
dass man durch einen Umlauf des Baums die Klammerungstruktur direkt ausgeben kann.
Immer wenn wir nach links laufen,
müssen wir eine öffnende Klammer schreiben und wenn wir dann bei einer Matrix ankommen,
schreiben wir die Matrix hin und immer dann,
wenn wir wieder nach oben laufen,
wenn wir auf der rechten Seite wieder nach oben laufen,
machen wir eine schließende Klammer.
Also hier ist eine Klammerung um A16,
so können Sie sich das vorstellen.
Okay, so, das bedeutet, wir gehen hier oben
in den Baum rein
Die laufen hier lang, also wir laufen hier außen drumherum,
A16, wir wissen Karl gleich 3, wir laufen zu A13,
schreiben eine öffnende Klammer, wir gehen zu A1,
schreiben eine öffnende Klammer, haben wir das Klammer auf,
Klammer auf, schreiben das A1, gehen zurück,
gehen hier auf der rechten Seite runter, schreiben wir nichts,
dann öffnende Klammer A2 mal A3, schließende Klammer, schließende Klammer,
in den rechten Ast rein und dann kommt eine öffnende Klammer,
öffnende Klammer A4, dann kommt das A5,
schließende Klammer, ihr nichts, A6,
schließende Klammer,
schließende Klammer und dann haben sie
den kompletten Klammerungsausdruck
Hier sehen Sie die S-Matrix, auch wieder so auf die Seite gekippt.
Also zur Berechnung von A12. Hier fehlt die unterste Ebene. Wissen Sie,
dass Sie an der Stelle eins das K legen müssen.
Und wenn Sie zum Beispiel das Produkt von,
nehmen wir das hier, von eins bis sechs berechnen wollen,
müssen Sie das ja in der Position 3,
die letzte Multiplikation durchführen müssen und so weiter.
Und Sie laufen nur durch diese Information durch, um den Klammerausdruck zu bekommen.
Und der Pseudocode dazu sieht eben so aus,
wir beginnen, wenn ILAJ ist,
dann schreiben wir nur die Matrix auf,
sondern das AI,
hier ist noch so ein Index dran,
schreiben wir dann dahin
Und andererseits, andernfalls, schreiben wir eine öffnende Klammer,
rufen rekursiv unsere Funktion auf für den linken Teil,
also von S, das S von I bis zu dem SEJ.
Und dann rekursiv den Teil, den rechten Teil,
das ist von SEJ plus 1 bis J.
Und dann schreiben wir die schließende Klammer.
Und das produziert entsprechend die Ausgabe. Das ist ein rekursiver
Algorithmus, aber rekursive Verfahren sind natürlich nicht grundsätzlich schlecht.
Der hier ist relativ effizient, weil er quasi nur einmal
durch den Baum läuft und alle Symbole einmal ausgibt.
Und keinerlei Berechnungen mehr
Durchführen muss. Ja, okay, das ist der
ganze Trick zur Berechnung der optimalen Reihung für diese Matrix-Multiplikation.
Es ist einfach so ein Standardbeispiel, was man verwendet,
um dynamische Programmierung zu zeigen. Es gibt viele Anwendungen,
auch in der Praxis, wo man so gar nicht vermutet,
wer von Ihnen benutzt Tech?
Müssen Sie wohl in technisch dynamische Programmierung vorkommt?
Haben Sie schon mal Blocksatz geschrieben
Wenn sie in Latech oder in Tech einen Fließtext als Blocksatz setzen,
da muss Tech für sie entscheiden, wo die Zeilenumbrüche gemacht werden.
Das ist eine schwierige Frage,
weil sie können ja nur an speziellen Stellen Umbrüche machen,
also die dürfen vielleicht Wörtern auch trennen,
aber selbst dann dürfen sie nur an speziellen Stellen trennen und in nächste Zeilen gehen.
Und je nachdem, wo sie das machen,
haben sie unterschiedlich viel White Space, sagt man,
weiße Lücken zu verteilen in der Zeile.
Und wenn das das schlecht läuft,
dann haben sie in so einer Zeile so riesige Lücken drin,
das wollen sie da nicht sehen.
Das macht man mit dynamischer Programmierung. Das ist ein klassisches
Beispiel für dynamische Programmierung. Dieses Problem der Aufteilung dieser White Spaces
in so einem, bei so einem Blogs als Text,
kann man als Problem beschreiben mit Teilproblem, mit optimaler Teilstruktur.
Können Sie schon darüber nachdenken, was das sein könnte?
Wahrscheinlich irgendwie so bis zur Zeile I. Wie muss ich
das da verteilen und welche Optionen habe ich da?
Und dann können Sie das entsprechend so berechnen.
Das ist ein schönes Beispiel. Anderes habe ich eigentlich schon
erwähnt. Jegliche Art von Textvergleichen funktionieren häufig eben
auch so
Wenn sie also zum Beispiel ähnliche Wörter erkennen wollen. Also jemand tippt irgendwas,
das ist nicht das, was die Suchmaschine kennt oder so,
die macht dann tolle Vorschläge und solche Textkorrektursachen,
wenn sie den Abstand ausrechnen wollen, zwischen zwei Zeichenketten.
Also wie viele Zeichen müssen sie ändern, um eine Textstelle,
einen Text der Form A in eine Texte Form B zu überführen,
ist etwas, was man mit dynamischer Programmierung macht.
Ja, okay. So,
also steht das die ganze Zeit schon da oben hier?
Gott, das kann ich übersehen
Zumachen. Naja, die Folie ist,
der Foliensatz ist sowieso durch, ist egal.
So, okay, also abschließende Bemerkung zu dynamischer Programmierung.
Das sind die, das ist das Anwendungsszenario, wo wir
es benutzen können. Wir brauchen zwei Eigenschaften in Optimierungsprobleme.
Wir brauchen die optimale Teilstruktur. Das heißt,
eine optimale Lösung kann aus optimalen Teillösungen konstruiert werden.
Und die optimale Teillösung können dann unabhängig voneinander bestimmt werden.
Und dann haben wir überlappende Teilprobleme. Das heißt,
es gibt eine polynomieelle Anzahl, das ist wichtig,
weil wenn das exponentiell viele wären, dann lohnt es sich nicht.
Eine polynormelle Anzahl von Problem, deren Lösung immer wieder zur Lösung größerer Teilprobleme herangezogen werden.
Wenn wir bei dem Matrix-Problem sehr schön gesehen, wie das ist.
Und immer, wenn diese Eigenschaften da sind,
können wir mit dynamischer Programmierung das Optimierungsproblem lösen.
Und dann haben wir diese vier Schritte, die wir machen.
Wir zeigen erst mal, dass die Struktur des Problems
so ist. Wir, wir definieren eine rekrusive Lösung.
Das können Sie natürlich auch nur im Kopf machen.
Das benutzen Sie nachher ja nicht. Sie drehen die Berechnungsreihenfolge
um durch Memoisation, durch Speicherung der Teilergebnisse.
Das spielt eigentlich immer eine Rolle. Und dann müssen Sie
noch einen Mechanismus haben, um die optimale Lösung zu rekonzieren
Manchmal ist der vierte Schritt echt das Schwierigste bei dem Ganzen,
sich gut zu überlegen, wie man eigentlich dann die Informationen speichern muss,
um das Optimum Maria zu erhalten.
Okay, so, das war es zur dynamischen Programmierung.
Ich muss jetzt einen anderen Foliensatz nehmen und ich gönne Ihnen
jetzt zwei Minuten Pause und ich riskiere einen Neustart
Dem aufmerksamen Benutzer ist wahrscheinlich aufgefallen,
dass sie wieder Windows 7 sehen, wo ich fortschrittlich,
wie ich bin, die letzten paar Wolllesungen mit Windows 8 beschritten habe.
Ich gebe Ihnen jetzt keine Empfehlung. Das ist cool.
Ich gebe Ihnen jetzt keine Empfehlung für die Verwendung von Windows
7 und Windows 8. Wenn das Herunterfahren jetzt ungewöhnlich lange dauert,
dann passt es zum Bild,
. .
Das ist der, der präsentiert. So, mal gucken, wie lange es hält.
Okay, also, wir schreiten voran, ein neues Kapitel.
Wir müssen Für ein Kapitel mal, die sind relativ konstruktiven Ansatz,
den wir bisher hatten. Nämlich ein Problem angucken und versuchen,
einen möglichst guten Algorithmus zu entwickeln, hinter uns lassen.
Und eine etwas andere Sichtweise einnehmen. Das Problem,
was wir nämlich jetzt haben, ist,
dass wenn das die Technik der Wahl ist,
die sie so anwenden, sie in der Praxis zu Situationen kommen,
wo ihnen vielleicht kein guter Algorithmus einfällt.
Dann sind sie da und überlegen und dann sagen,
könnt ihr es euch so machen oder so machen,
aber es funktioniert irgendwie nicht oder es dauert alles zu lange oder es ist nicht exakt genug.
Und dann sind sie in einer sehr ungünstigen Situation,
weil ihr Chef hat ihnen gesagt,
lösen sie mir dieses Problem und dann stehen sie da und sagen,
ich habe das jetzt drei Wochen versucht, und mir ist nichts Gutes eingefallen.
Ja, es ist nicht so gut. Das heißt,
wir brauchen Techniken in der Informatik, um Probleme zu bewerten,
um zu bewerten, auf ein Problem schwer ist oder leicht ist.
Damit müssen wir uns auseinandersetzen und für einige von Ihnen ist
es quasi das zweite Mal, dass Sie das sehen.
Also wir gucken uns Komplexitätsklassen und NW Vollständigkeit an.
Das haben Sie in der formalen Grundlagen gelernt.
Das ist für Sie so ein bisschen Wiederholung.
Es hat sich bisher noch nie jemand beschwert,
dass Sie es zweimal gemacht haben
Für die anderen ist es auch ein bisschen neu,
weil ich habe natürlich eine etwas andere Sicht jetzt darauf habe,
sondern dass ich wirklich ganz gezielt aus der Praxis heraus betrachte.
Also, Sie gucken sich das Problem an, Sie analysieren es,
Sie entwickeln einen effizienten Algorithmus, wenn Sie Glück haben,
wenn Sie Pech haben, fällt Ihnen keiner ein.
Dann ist die Frage, was tun Sie als nächstes? Ja,
als nächstes ist, vermuten Sie, es gibt einen effizienten Algorithmus,
aber Sie waren bis zum jetzigen Zeitpunkt noch zu dumm,
ihn zu finden.
Dann werden Sie weiter suchen oder bestenfalls das Internet oder Ihre Kollegen
bemühen und sich irgendwelche Tipps holen und werden weiter versuchen,
einen effizienten Algorithmus zu finden
Wenn irgendwann der Punkt erreicht ist,
wo sie vielleicht nicht mehr dran glauben,
dass es so ein effizientes Verfahren gibt,
brauchen wir eine Möglichkeit, um dem Ganzen Sicherheit zu geben.
Dann könnten sie sich hinsetzen und sagen, okay,
ich weise eine untere Schranke für mein Problem nach.
Ich zeige, dass egal welcher Algorithmus benutzt,
wird er mindestens Omega von so und so viel Ennen hoch, irgendwas Zeit benötigt.
Haben wir schon gesehen beim Sortieren. Da haben wir so
eine untere Schranke mal berechnet. Untere Schranken zu berechnen ist
extrem schwierig. Es ist ja im Allgemeinen immer so,
dass wenn man eine Aussage machen muss, wo ein Für alle,
ein allquanter Vorkommen, wird es immer schwierig.
Sie müssen in dem Augenblick sagen, für alle Algorithmen,
die es irgendwie geben kann,
die werden alle mindestens so und so lange benötigen.
Das ist sehr schwer, so einen Beweis zu führen.
Und das schafft man nicht. Das schaffen auch die meisten Informatiker für die meisten Probleme,
die wir so haben in der Praxis, erstmal nicht.
Da gibt es keine unteren Schrank. Keine, die so gut sind,
dass sie uns wirklich helfen, die Komplexität zu beschreiben.
Was wir hingegen schaffen, ist, das Problem zu klassifizieren,
indem wir es in eine Klasse stecken, wo auch ganz viele andere schwere Probleme drin sind.
Wenn wir das hinbekommen, Dann haben wir eine Chance,
zumindest zu sagen, das gehört zu einer Gruppe von Problemen, die alle schwierig sind.
Selbst wenn wir für diese gesamte Klasse noch keine genaue Aussage machen können, wie schwer die ist. Das heißt,
die Antwortmöglichkeit, die Sie dann haben,
Sie können dann zu Ihrem Chef gehen und sagen,
ich habe keinen effizienten Algorithmus gefunden,
Sie können dann nicht sagen, es existiert keiner,
das können Sie nicht sagen,
aber Sie können zumindest sagen, dass wenn dieses Problem,
wenn Ihr Problem gelöst wäre in einer effizienten Art und Weise,
dann wären auch ganz viele andere Probleme auf effiziente Art und Weise gelöst und die Wahrscheinlichkeit,
dass das geht,
ist erstmal gering
Wenn, dann wären sie zumindest nicht dümmer wie all die anderen,
die versucht hätten, die anderen Probleme zu lösen.
Ja, das bringt sie auf alle Fälle in eine bessere Situation.
Das heißt, es ist gut, wenn sie in der Lage sind, die Probleme zu klassifizieren.
Und wir brauchen einen Formalismus, mit dem man das tun kann. So, wie geht das? Ach so,
bevor wir zeigen, bevor ich ihnen zeige, wie das geht,
möchte ich ihnen auch noch mal an ein paar Beispielen zeigen,
dass es notwendig ist, einen Prozess dafür zu entwickeln,
eine Methode dafür zu entwickeln, weil hier sind so Fälle von Problemen,
die einfach und die schwer sind, die sehr ähnlich klingen.
Ja, also zum Beispiel hier oben,
das ist ein Problem, was wir schon behandelt haben,
finde den kürzesten einfachen Weg in einem Grafen, zwischen zwei Knoten.
Wüssen sie schon? Ja, positive Kantengewichte, Deixtra,
sonst nehmen sie einen Wellmann fort, wissen genau,
wie das geht, wissen, das geht,
also in N-Hoch-3-N-N-N-N-N-Hoch-3, im ungünstigsten Fall, ja,
oder in M-Log-in, wenn sie den Diikstra verwenden können.
So, aber was ist, wenn jetzt jemand sagt,
ich suche jetzt aber nicht den kürzesten einfachen Part,
sondern den längsten einfachen Pfad, ja, dann sagen sie,
ja, klar, kürzesten kann ich, kann ich den längsten auch,
dann fangen sie vielleicht an und drehen die Kantengewichte um oder fangen an,
irgendwas umzugewichten oder so und denken, das geht ganz einfach,
das klingt ja schon genauso, ja.
Dieses Problem ist schwer, ja, wir kennen kein Algorithmus mit
polinomärer Laufzeit zur Berechnung der längsten einfachen Wege in Grafen.
Hier sind zwei sehr verwandte Probleme. Der Hamilton-Kreis und die Euler-Tour,
der Hamilton-Kreis ist ein einfacher Kreis, der alle Knoten enthält.
Also sie besuchen in einem Grafen alle Knoten,
aber sie laufen über die Kanten,
aber sie dürfen jeden Knoten auch nur einmal besuchen.
Deswegen steht da einfach. Das ist einfach.
Jeder Knoten nur einmal. Die Euler-Tour ist ein Kreis,
der alle Kanten einmal enthält. Also jede Kante wird einmal
durchlaufen. Die Knoten können sie mehrfalls besuchen,
aber jede Kante nur einmal
Hamilton-Kreis ist schwer. Der Hamilton-Kreis zu berechnen, dafür kennen wir keinen polynomiellen Algorithmus.
Der Euler-Kreis, die Euler-Tour zu berechnen, ist einfach,
das können wir in Linearzeit lösen, das Problem.
Und die klingen sehr ähnlich. Also ich wäre,
wenn ich die Probleme zum ersten Mal hören würde,
wäre ich nicht in der Lage zu sagen, ja,
das ist das Schwere und das ist das Leichte.
Ja, warum? Manche Probleme klingen relativ einfach auf den ersten Blick,
zum Beispiel in einem Grafen, einen Teilgraf zu finden,
wo alle keine Knoten, also einen Teilgraf mit Karkon,
wo jeder mit jedem verbunden ist.
Da würden sagen, das sehe ich doch sofort. Ja,
hier zehn Knoten, jeder hat mit jedem eine Kante.
In Allgemeinen ist das schwer. Wir haben keinen Algorithmus,
der das kann. Independence Set ist es so,
das Umgekehrte. Also wir haben einen Teilgraf mit Karknoten wo
keine Kante zwischen diesen Knoten existiert. Das ist genauso schwierig.
Fährbarkeit ist auch so ein schönes Beispiel. Fährbarkeit ist,
sie geben jeden Knoten eine Farbe und zwei Knoten,
die mit einer Kante verbunden sind, dürfen nicht die gleiche Farbe haben.
Wenn Sie fragen, ob das mit zwei Farben geht,
dann ist das einfach. Können Sie ganz leicht machen.
Brauchen Sie nur eine Tiefensuche, können Sie es sofort machen.
Wenn Sie fragen, ob das mit drei Farben geht,
ist es schwer. Wir haben keinen Algorithmus,
der das kann. In polynomieller Zeit entscheiden,
ob wir einen Grafen mit drei Farben einfärben können
Ja, und dann gibt es das schöne Partition-Problem,
das schreibe ich immer so auf.
Ich finde das so toll. Erstens ist es ein Beispiel,
was nicht so kombinatorisch ist, sondern eher numerisch.
Zweitens ist es eins aus der Praxis, das braucht man schon als kleines Kind.
Also sie haben eine Menge ganzer Zahlen. Sie haben nach Halloween
eine große Tüte mit Süßigkeiten und die haben einen unterschiedlichen Wert
und sie müssen die in zwei Teilmengen aufteilen, sodass sich die gleiche Summe ergibt.
Praktisch hochrelevant das Problem und auch dieses Problem,
allein schon die Tatsache, ob es geht, Ist endlich schwer,
also den Brief kennen Sie jetzt noch nicht, aber ist so,
dass wir heute keinen polynomiellen Algorithmus kennen, der das tun kann.
Also, das ist schwer, das ist einfach,
das ist schwer, das ist einfach, das ist schwer,
das ist auch schwer, das ist einfach,
das ist schwer und Partition ist auch schwer.
Okay, diese Liste dient nur dazu, um klarzumachen,
dass sich die einfachen und die schwierigen Probleme manchmal
sehr ähnlich anhören und es nicht offensichtlich ist zu erkennen,
was ein einfach zu lösendes Problem und ein schwer
zu lösen das Problem ist
So, um jetzt eine Aussage zu machen über die Schwierigkeit
von Probleme, müssen wir das alles sehr stark formalisieren.
Da kommen wir gar nicht drumherum.
Wir müssen ja eine Aussage machen für alle Algorithmen,
die wir ex entwickeln können für ein entsprechendes Problem.
Wir machen das wie folgt. Zunächst einmal wissen wir,
es gibt Optimierungs- und Entscheidungsprobleme. Entscheidungsprobleme sind so,
dass wir einfach fragen, ob irgendetwas existiert in einer Datenmenge und Optimierungsprobleme.
Da suchen wir nach der besten Möglichkeit.
Beim Faden existiert ein Pfad von A nach Bs,
das Entscheidungsproblem und das Optimierungsproblem ist,
was ist denn der kürzeste Pfad von A
nach B
So, das sind erstmal zwei grundsätzliche Problemklassen.
Und wir werden uns im Folgenden nur noch mit Entscheidungsproblemen auseinandersetzen,
also nur noch nach Existenz einer Lösung fragen.
Und dass das legitim ist, obwohl es so viele Optimierungsprobleme gibt,
das sieht man an der folgenden Betrachtung.
Wir können für jedes Optimierungsproblem ein entsprechendes Entscheidungsproblem formulieren,
indem wir einfach sagen, gibt es eine Lösung für das Problem P mit einem Wert kleiner gleich K.
Also P war jetzt das Optimierungsproblem und wir fragen,
gibt es eine Lösung kleiner gleich K?
Und dann haben wir
ein Entscheidungsproblem
Und wenn Sie sich jetzt vorstellen, zum Beispiel,
wir können ja mal was einsetzen, gibt es einen
kürzesten Pfad in dem Grafen mit weniger als fünf Kilometer.
Dann haben Sie aus dem kürzesten Pfadeproblem ein Optimierungsproblem, ein Entscheidungsproblem gemacht.
Und dieses Problem hat einen Namen, das ist das Car-Threshsh-Should-Problem,
also Car-Grenzwert-Problem. Also wir fragen einfach nach einem Grenzwert,
ob der unterschritten wird. Und wenn wir dieses Entscheidungsproblem lösen können,
dann können wir auch das Optimierungsproblem lösen,
indem wir ganz einfach so vorgehen, dass wir sagen,
okay, wir machen einfach wiederholt die Anwendung dieses Entscheidungsproblems zur Lösung des Optimierungsproblems.
Und dann können Sie ja sowas machen wie eine binäre Suche
Sagen, gibt es einen kürzesten Pfad der Länge 64?
Nein, fragen Sie noch einen der Länge 32. Nein,
dann fragen Sie rein der Länge 16 und so weiter.
Und dann können Sie mit einer binären Suche sich dem Optimum relativ gut annähern.
Und das geht auch relativ effizient. Das heißt,
bei Problemen gehen wir davon aus, dass Optimierungsprobleme dann schwer sind,
wenn die zugehörigen Entscheidungsprobleme auch schwer sind.
Ja, weil wenn die schwerste Entscheidung fällen zu können,
dann wird man auch nicht gut optimieren können.
Okay, so, das heißt, im Folgenden reduzieren wir
unsere Betrachtungsweise auf Entscheidungsprobleme. Das hat einen großen Vorteil,
unsere Struktur der Probleme ändert sich gravierend,
weil die Ausgabe eines Entscheidungsproblems ist ja nur 0 oder
1,
wahr oder falsch
Ist erst mal ein großer Vorteil. So, okay,
wir schauen uns nur Entscheidungsprobleme an. Jetzt müssen wir sie abbilden.
Wir wollen eigentlich weg von Formulierungen. Wir wollen nicht mehr
sowas haben wie, ja, kürzester Weg in ein Graf.
Wie soll man darüber etwas zeigen, etwas beweisen?
Sie brauchen irgendetwas Griffigeres, wo alle Sachen ganz präzise definiert sind.
Wir wollen es beschränken auf Probleme,
die wir beschreiben als eine Funktion über Zeichenketten von Nullten und Einsen,
also 0-1 Stern, also eine beliebige Sequenz aus Nullen und Einsen.
Und das Ergebnis unserer Funktion ist Null oder Eins,
ist ein Entscheidungsproblem. Sagt entweder Null oder Eins.
Das nennen wir unser konkretes Problem
Vorher haben wir ein abstraktes Problem. Unsere Probleme sehen natürlich nicht so aus.
Wir ragen nach dem kürzesten Weg und nicht nach irgendeiner 0
-1-Kette, wo 0 oder 1 rauskommen soll.
Aber zwischen dem abstrakten Problem und dem konkreten Problem
liegt nur ein Schritt, nämlich die Kodierung.
Wir müssen unsere abstrakte Formulierung von dem,
was wir eigentlich beschreiben wollen, überführen in eine solche Funktion.
Weil dann haben wir eine sehr formalen Rahmen,
in dem unsere Probleme liegen können.
Unsere Probleme sind dann alle beschrieben durch solche Funktionen
hier,
die alle gleich aussehen
Okay, was macht jetzt die Kodierung? Also die Kodierung
bildet jetzt das abstrakte Problem auf das konkrete Problem ab.
Dabei könnte es natürlich sein, dass irgendwelche Codes,
die wir verwenden, gar nicht zu einem Problem passen.
Das heißt, es gibt also sowas wie ungültige Syntax oder so.
Und wenn wir sowas haben, dann gehen wir immer davon
aus, dass unser Algorithmus einfach nein sagen soll.
Also wenn irgendwas keinen Sinn ergibt, sagt er immer, nein.
Und wenn das ein sinnvolle Problembeschreibung ist und es wäre ja,
dann sagt er ja,
dann kommt er eins raus
Okay, wenn wir das tun,
dann können wir auch einfach sagen, okay,
welche dieser Zeichenketten von Nullen und Eisen sind denn solche,
die eine Eins liefern, die ja,
ein Ja oder ein Wahrantworten.
Und das ist eine Menge, ja.
Das ist der Übergang zu sogenannten formalen Sprachen in der Informatik.
Wir haben also das konkrete Entscheidungsproblem und das betrachten wir als
Menge der Eingaben, für die der Algorithmus Ja sagen wird.
Ja, es ist ja nur ein kurzer Hinweis,
das werden wir gar nicht so viel weiter verwenden.
Wir werden einfach nur die korrekte Antwort immer als Menge
betrachten können
Okay, so, ja, wenn wir das jetzt kodieren, dann könnte die Codierung natürlich sehr lang werden.
Wir haben eine Eingabe und dann brauchen wir unheimlich viele Nullen und Eisen, um das Problem komplett zu beschreiben. Dann hätten wir ein Problem,
weil wir vielleicht zeigen könnten, auf dem Algorithmus hier,
der auf den Nullen und Einsen arbeitet,
das ist vielleicht super effizient, O von N,
ja, aber unsere Codierung,
die das abstrakte Problem mit dieses konkrete Problem überführt,
die braucht dann zwei hochennen viele Bits, ja,
und dann würde der Algorithmus zwar Of und En Laufzeit,
aber wenn die Eingabe zwei auch eng groß ist oder eben zwei auch En Laufzeit,
das heißt, das wäre nicht gut.
Die Kodierung darf nicht zu groß sein. Wir betrachten ja
immer die Unterscheidung jetzt zwischen Polynomiel und nicht Polynomiel,
also exponentiell. Und wir sagen einfach,
solange wir in der Lage sind,
das abstrakte Problem mit einer Eingabegröße N in ein konkretes Problem zu überführen,
wobei die Eingabelänge polynomiell ist in dem N,
dann ist alles gut
Also es darf um ein Polynom wachsen, auch mit einem größeren K,
wenn sie so wollen, aber es muss polynomiell sein.
Wir können gerne N-Hoch 3 oder N-Hoch 5 viele Wits benötigen zur Beschreibung des Problems,
aber es muss immer ein Polynom sein. Gut,
ein Beispiel dazu, mal angenommen, wir haben so ein Graf-Problem,
kürzeste Wege oder so, dann könnten wir uns das erste sagen,
okay, jetzt müssen wir jetzt irgendwie formulieren, nur damit sie sich das mal vorstellen können.
Dann könnten sie sagen, wir müssen irgendwie den Graf mit Endknoten und M kannten irgendwie durch so eine 0-1-Kette beschreiben. Und eine Möglichkeit,
das zu tun, Eine von vielen, ja,
es ist nicht eine besonders intelligente, es ist wahrscheinlich nicht die kürzeste,
es ist sicherlich nicht die kürzeste Stardiss One,
aber nur irgendeine, um es zu machen,
wäre zu sagen, ich benutze immer ein 0-0-Symbol,
um meine binäre Null zu beschreiben,
ein 0-1 für eine binäre 1, ja,
und eine 1-0, um zu sagen,
hier fängt ein neues Listenelement an und 1-1,
hier um zu beschreiben, hier fängt eine neue Liste an.
Damit kann ich beliebige, mit diesem Code kann ich beliebige Mengen von Listen beschreiben.
Da kommt also meine binärzahl, dann kommt ein 1-0,
dann kommt wieder eine binärzahl, dann kommt wieder eine 1-0
Und irgendwann kommt dann eine 1-1 und dann fängt eine neue Liste an.
Und mehr brauche ich nicht, um Graf zu beschreiben.
Ich brauche ja nur die Menge der Adjazenzlisten kodieren. Also eine Adjazenzliste sieht dann so aus,
fängt an mit 1-1, neue Liste,
dann kommt die Binärzahl bis Knotens I,
also der Knoten, zu dem die Liste gehört,
dann würde ich eine 1-0 schreiben für ein neues Listen-Element,
dann kommt die Binärzahl des ersten Nachbars und dann 1-0 für ein neues Listen-Element,
bin ja jetzt alles zweiten,
Nachbars und so weiter.
Und irgendwann kommt dann wieder 1-1,
dann kommt der nächste Knoten. Das wäre eine Möglichkeit,
einen Graf zu beschreiben und sie hätten nur Einsen und Nullen
Okay, die Länge dieser Beschreibung, müssen wir uns überlegen,
ob das jetzt auch noch den Ansprüchen genügt.
Wenn wir also einen Grafa mit M vielen Kanten und N vielen Knoten,
dann hätten wir, müssten wir diese Nummern beschreiben und die
Nummern hätten Logorithmus zu Basis 2 von N viele Wits.
Ja, genau genommen zweimal so viel,
weil für jedes Bit brauchen wir ja hier zwei Zeichen,
0-0 oder 0-1. Ja,
und für die Kanten brauchen wir entsprechend dann auch so viele,
für jede Kante,
müssen wir dann
eine so eine Knotennummer speichern
N mal zweimal Lock 2N für die Knotennummern, diesen vorderen Teil hier. Ja,
und dann haben wir noch zweimal M für die ganzen Kanten
und zwei plus zweimal Lock 2N für die ganzen Nummern,
die da vorkommen können. Vielleicht fehlt hier noch eine Konstante,
vielleicht müsst ihr nochmal plus eins und so.
Ist aber alles nicht wichtig, wenn sie das zusammenfassen,
kommt da O von M-Log-In raus. Das heißt,
wir können im Rokalkül betrachtet ein Grafen mit N vielen Knoten und M
vielen Kanten in mit Platz O von M-Log-In vielen Witz quasi beschreiben.
Und das ist definitiv polynomiell in der Eingabelänge
Okay, ist ja weniger als M-Quadrat, wenn wir so wollen.
Okay, so, das ist Codierung. Mit Codierung
werden wir uns jetzt im Folgenden überhaupt nicht mehr beschäftigen.
Das Wichtige ist nur, dass man sieht,
dass man jedes abstrakte Problem so auch beschreiben kann und somit
überführen kann in so ein konkretes Problem.
Gibt es natürlich Einschränkungen, wenn Sie dann reelle Zahlen haben und so,
dann geht das nur mit einer gewissen Genauigkeit,
aber im Wesentlichen ist das so.
Okay, so, jetzt können wir diese Funktion,
das sind unsere Algorithmen in Klasseneinteilen
Und jetzt kommen wir zur Berechenbarkeit. Wir betrachten Funktionen von
0-1 Sternen nach 0-1 Sternen.
Das ist jetzt kein Tippfehler hier. Also eben haben wir gesagt,
wir überführen jedes Problem in ein Entscheidungsproblem und dann stand hier hinten nur null oder eins.
Also hier steht jetzt wieder eine Zeichenkette. Das ist,
weil wir das brauchen später. Aber da sind natürlich die Funktionen,
die nur eine 0-1 haben, dann eingeschlossen.
Also solche Funktionen, die eine Zeichenkette von Nullen und Einsen
in eine andere Zeichenkette von Nullen und Einsen überführen,
die bezeichnen wir als Polynomzeit berechenbar.
Genau dann, wenn es einen Algorithmus A gibt, der diese Funktion berechnet.
Und der muss die Laufzeit haben, TA von N,
also bei Eingabellänge N, N, Phile Bits,
ja, muss der NO von N hoch K sein.
Und K ist irgendeine Konstante. Hauptsache konstant, keine
große Konstante sein, aber konstant. Polynom, Zeit,
also hier die Laufzeit ist beschrieben oder begrenzt durch ein Polynom.
Deswegen Polynom Zeit, berechenbar, weil der Algorithmus A in
der Lage ist, in dieser Zeit diese Funktion auszurechnen.
Ja, okay. So, damit kommen wir zu einer
Komplexitätsklasse. Wir können sagen, wir betrachten alle Probleme,
alle Entscheidungsprobleme, Die bilden jetzt eine Eingabe 01 Stern nach 01.
Ob hier ist kein Stern. Jetzt sind es die Entscheidungsprobleme. Solche Entscheidungsprobleme nehmen wir,
wenn die Polynomenzeit berechenbar sind, dann gehören sie zur Klasse P. Gut,
alle Probleme, die wir bisher, für die wir bisher Algorithmen gesehen haben,
in der ganzen Vorlesung, ob es Sortieren war,
ob es Suchen war, ob es Graf-Algorithmen waren,
all diese Probleme lagen in P.
Wir haben Algorithmen angegeben, die waren ein Quadrat,
N-O-3, N-Log-In, irgendwie sowas.
Das ist alles polynomiell
Okay, so, also die Komplexitätsklasse P,
das ist die formale Definition, was mir mal wichtig ist, ist,
dass sie auch wissen, was es für die Praxis bedeutet.
Ja, das hört sich, wenn man,
also Komplexitätstheorie kann sich so abgefahren anhören,
als hätte es nichts mit einem tatsächlichen Leben zu tun.
Das ist aber nicht so. Jedes Entscheidungsproblem, was sie haben,
können sie kodieren, ja, mit polynomierender Zeit.
Und dann können sie entscheiden, ob es in polynomieller Zeit lösbar ist oder nicht.
In der Regel können sie nur zeigen, ob es lösbar ist.
Bei nicht wissen sie dann immer nicht so genau, ja.
Und dann können sie es dieser Klasse zuordnen
Bei Optimierungsproblemen ändern sie sich hoffentlich,
da betrachten wir das Car-Threshold-Problem, also das Grenzwertproblem,
das ist das ein zugehörige Entscheidungsproblem mit einem Grenzwert K,
dass sie da, ja, Entscheidungsproblem,
wenn das ein P ist,
ist das Optimierungsproblem für uns auch ein P. Streng genommen ist das nicht ganz exakt,
also normalerweise betrachtet man nur Entscheidungsprobleme,
aber da wir in der Praxis so viele Optimierungsprobleme haben,
benutzen wir diese Begriffe da eben auch,
indem wir dann eigentlich implizit immer dieses Car-Threshold-Problem meinen.
So, jetzt kommt der Clou an der ganzen Geschichte und das ist der Grund,
warum wir uns so auf die Polynome versteifen.
Polynome haben den Vorteil, sie sind gegen Verkettung abgeschlossen,
wenn sie ein Polynom nehmen und ein Polynom wieder einsetzen,
kommt wieder ein Polynom raus.
Das hat dann höhere Exponenten, aber es ist wieder ein Polynom.
Das heißt, die Polynome als eine Klasse von Funktionen ist abgeschlossen in sich.
Sie können die ineinander einsetzen, kriegen wieder Polynome. Das bedeutet,
wenn ein Problem in Polynomzeit lösbar ist und die Kodierung polynomiert lang ist,
ist das Problem nach der Kodierung immer noch im Polynomzeit lösbar.
Sie können also über die Kodierung hinweg dieses Resultat übertragen.
Das liegt einfach daran, weil Polynome können sie ineinander einsetzen
, kommt wieder ein Polynom aus.
Wenn also das konkrete Problem Polynomzeit lösbar ist,
ist auch das abstrakte Problem in Polynomzeit lösbar oder umgekehrt.
Deswegen können sie auch für ein abstraktes Problem sagen, das gehört in P.
Sie können sagen, kürzeste Wegeberechnung ist ein Problem, das ist in P.
Weil es ein, weil sie es im Polynom hier in der Zeit berechnen können. Sie müssen nur im Hinterkopf haben,
sie meinen damit nicht, da gibt es so ein,
also ich meine damit nicht nur,
da gibt es so ein O von M-Log-in-Algorithmus,
sondern sie meinen eigentlich damit, es gibt eine Kodierung nach 0-1,
es gibt ein zugehöriges Entscheidungsproblem und das ist eigentlich das eigentliche Problem,
was in dieser richtigen Klasse ist
Okay. So, damit haben wir die einfachen Probleme.
Mit denen ist natürlich das Problem nicht gelöst.
Wir wollten uns ja um die schwierigen Probleme kümmern.
Für die wollten wir eine entsprechende Formulierung finden. So,
jetzt könnte man sagen, klar,
wenn es P gibt für polynomiell, dann gibt es natürlich NP,
das ist dann eben nicht polynomiell.
Das ist es nicht. Ja, das ist einer der
größten Denkfehler, den man machen kann.
NP steht nicht für Nicht-Polynomiell. NP steht für etwas anderes,
was wir gleich sehen werden.
Wir werden jetzt versuchen, die schwierigen Probleme zu separieren
Wir könnten natürlich einfach sagen, wir nehmen die anderen.
Aber wir wissen schon, dass wir es ja nicht,
dass wir für die anderen, dass wir für die Probleme
nicht so leicht nachweisen können, dass sie schwer sind.
Ja, wir müssen also irgendeinen anderen Weg finden,
die schwierigen Probleme zu kapseln, zu klassifizieren.
Deswegen zunächst mal ein anderer Begriff. Wir nehmen wieder Funktionen.
Hier fehlt kein Stern, der ist absichtlich nicht mehr da.
Also die Funktion von 0-1 Sternen nach 0
-1 bezeichnen wir als Polynomzeit verifizierbar. Ja,
genau dann, wenn, zu jeder Eingabe X,
also zu so einer 0-1-Kette, existiert ein Zertifikat Y,
wieder so eine 0-1-Kette, der Eigenschaft,
dass die Länge der Kette polynomiell in der Länge der Eingabe ist.
Das ist das Erste, was gelten muss.
Das klingt jetzt sehr kompliziert. Ich werde Ihnen gleich sagen,
was das bedeutet. Es ist eigentlich ganz, ganz einfach.
So, es ist das Erste. Und dann gibt es einen Algorithmus A,
der kriegt die Eingabe und das Zertifikat und er ist genau dann eins,
wenn die Funktion eins ergeben würde.
Und dieser Algorithmus hat wieder polynomia Gelaufzeit.
Okay, das klingt total verwirrend. Vor allen Dingen
diese Zertifikatgeschichte hört sich komisch an
Der Begriff war übrigens bis vor ein paar Jahren relativ unbelegt.
Da kommen wir mal so aus dem, aus der alten Historie.
Neuerdings kennt den wieder jeder, weil man irgendwelche Zertifikate braucht,
um auf irgendwelche Server zu kommen und so.
Also, was hier mit dem Zertifikat gemeint ist,
ist streng genommen nichts anderes als die Lösung.
Ja? Was sie machen ist, sie haben ein Problem,
ja, das ist die Eingabe für das Problem.
Und was hier steht, ist, mal angenommen,
ich sage ihnen die richtige Antwort.
Dann gibt es einen Algorithmus, das ist das Zertifikat,
das ist nicht richtig eine. Dann gibt es einen Algorithmus,
der ist in der Lage, in pullinumierler Zeit zu prüfen,
ob diese Lösung wirklich stimmt
Also um zum Beispiel für kürzeste Wege zu zeigen,
dass es Polynom-Zeit verifizierbar, müsste ich sagen, das ist der Graf.
Das ist ein kürzester Pfad. Ja,
und sie müssten den Algorithmus definieren, der überprüft,
ob dieser Pfad die Länge K hat.
Sie müssten nicht prüfen, ob es der kürzeste ist.
Nur ob der von der Länge kleiner ist als unser Grenzwert K. Okay,
also das Zertifikat müssen Sie im Kopf immer übersetzen in die Lösung des eigentlichen Problems.
Also umgangssprachlich, das Zertifikat stellt die Lösung dar und
der Algorithmus A überprüft in polynomierter Zeit,
ob die Lösung korrekt ist
Das mag ihn im Moment relativ trivial erscheinen,
aber ich kann dir sagen,
es gibt Probleme, da ist es nicht so leicht,
in polynumieller Zeit zu überprüfen, ob die Antwort korrekt ist.
Das ist manchmal schwierig. Also für viele Probleme,
die wir gesehen haben, ist es immer einfach,
aber manchmal kann das sehr schwer sein. So,
und für all die Probleme, die wir ganz am Anfang gesehen haben,
können wir uns die Zertifikate überlegen. Gehen wir mal auf diese Seite hier,
da sind die ganzen Probleme, da können wir sofort Zertifikate angeben.
Finde den längsten einfachen Weg in einem Grafen. Ja,
das ist ein Optimierungsproblem, Cards-Rashford-Problem,
gibt es einen längsten einfachen Weg größer gleich, sich Einheiten.
Zertifikat wäre eine Liste von Knoten in dem Grafen.
Ihr Algorithmus würde den Graf nehmen, diese Liste von Knoten,
würde die Pfadlänge ausrechnen und gucken,
ob es größer gleich K ist und dann eins oder null ausgeben.
Kürzeste Weg ist dann sehr ähnlich. Hamilton-Kreis können sich auch
vorstellen. Das ist einfach nur die Reihenfolge der Knoten.
Ihr Algorithmus müsste überprüfen, dass jeder Knoten nur einmal vorkommt
und dass zwischen zwei benachbarten Knoten in meine Kante existiert.
Sonst wäre es ja kein Kreis. Ja,
Krieg ist, glaube ich, auch klar.
Fährtbarkeit. Sie würden die Funktion angeben. Sie würden sagen,
das ist meine Färbungsfunktion. Der Knoten wird rot,
der wird grün, der wird blau und so weiter mit
den drei Farben
Und ihr Algorithmus müsste überprüfen, dass keine zwei
meine Kante benachbarten Knoten mit der gleichen Farbe gefärbt sind.
Sie sehen, Zertifikate beschreiben Lösungen und die Algorithmen,
über die wir reden, die verifizieren, ob die Lösungen korrekt ist.
Okay, so, das ist Polynon-Zeit verifizierbar.
Und jetzt kommt die Komplizitätsklasse NP. Das sind einfach
alle konkreten Entscheidungsprobleme, die wir in Polynon-Zeit verifizieren können
Wir wissen nicht, ob das mehr ist. Wir wissen nicht,
ob das mehr ist als P. Vielleicht ist es so,
dass jedes Problem, was wir im Polynomen-Zeit verifizieren können,
wir auch in Polynomzeit berechnen können.
Könnte sein, das wissen wir nicht. Es ist erstmal
nur eine Menge von Problemen, die wir beschreiben können.
Wir haben aber gesehen, dass unsere schwierigen Probleme von eben,
von denen wir gar nicht wissen, wo sie dazu gehören,
die gehören auf alle Fälle schon mal zur NP.
Für die konnten wir ein Zertifikat eingeben. Okay, so,
wir wissen nicht, ob P eine Teilmenge von NP ist,
könnte auch sein, dass diese Mengen gleich sind.
Die wissenschaftliche Welt weiß es nicht, sie vermutet,
dass es diese Mengen ungleich sind.
Das ist die zentralste Fragestellung in der Informatik,
die hier steht
Diese Frage zu lösen, ist von ganz immenser Bedeutung,
wie wir noch sehen werden, weil,
wenn wir das wüssten,
dann könnten wir nämlich auf einen Schlag Probleme klassifizieren in schwer und einfach.
Noch wissen wir es nicht, aber wir vermuten mal,
dass diese Mengen nicht gleich sind.
Okay, wir haben also zwei Szenarien. Wir haben dieses
Szenario oder dieses Szenario. Wir haben hier die große Klasse
NP und entweder P ist gleich NP oder sie sind unterschiedlich,
haben wir hier P und hier ist NP.
Wer sie unterschiedlich ist, dann muss es irgendetwas geben,
was ja in NP ist und nicht in P.
Und die Frage ist, können wir das beschreiben?
Können wir irgendwie
das andere beschreiben
Ja. Achso, vielleicht sollte ich noch sagen,
es steht da nicht auf der Folie, aber nur damit sie sich das merken.
Und die Frage ist natürlich, warum heißt das jetzt NP,
wenn das nicht einfach nicht polynomiell heißt? Der Begriff kommt,
ja, das ist die Abkürzung für nicht deterministisch polynomiell.
Das heißt, sie können mit einem Algorithmus,
der in der Lage ist, die Lösung zu raten,
das ist das Zertifikat, was der nicht deterministische Anteil,
in polynomieller Zeit die Lösung berechnen.
Also NP ist die Abkürzung für nicht deterministisch, polynomiell
So, die Frage ist, wie heißen jetzt,
können wir irgendwie die schwierigen Probleme daraus trennen, herausnehmen.
Okay, was sind die schwierigsten Probleme? Wir versuchen mal herauszufinden,
was sind die Probleme, die schwer sind. So,
wir bezeichnen ein Entscheidungsproblem A aus dieser Menge als NP vollständig.
Genau dann, wenn Folgendes gilt, mal angenommen,
dieses Problem A würde auch in P liegen.
Dann würde daraus sofort folgen, dass P gleich NP ist.
Das müssen die schwierigsten Probleme sein. Ja, also wir sagen,
wir nehmen irgendein Problem hier raus und wenn das NP liegt,
Da gibt es kein anderes Problem mehr,
was nicht auch in Pele liegt.
Da liegen die alle in P. Ja, das ist die Aussage,
die hier steht. Diese Probleme bezeichnen wir als NPV ständig.
Das sind offensichtlich die schweren Probleme. Und die Klasse dieser Probleme,
das ist NPC, NP Complete, nicht deterministisch,
polynomiell, berechenbar und dann ist es vollständig, ist complete.
So, das ist also die Menge der NPV vollständigen Entscheidungsprobleme.
Bei Optimierungsproblemen machen wir jetzt wieder das, was wir vorher auch gemacht haben.
Wir sagen, dafür gibt es den Begriff nicht wirklich,
aber wir sagen, naja, wir nehmen einfach das Car-Three-Show-Problem.
Und wenn wir das nehmen, dann haben wir ein Entscheidungsproblem
und dann können wir wieder gucken, ob es NPV ständig ist.
Und wenn das auch NPV ständig ist,
dann nennen wir das Optimierungsproblem nicht NPV ständig,
sondern NP schwer
Ja, ist ein Sprachgebrauch. Also Optimierungsprobleme heißen NP schwer,
wenn die zugehörigen Carsware-Short-Probleme NP vollständig sind. So,
deswegen benutzen wir eigentlich auch typischerweise eher den Begriff NP schwer,
weil wir typischerweise auch Optimierungsprobleme haben.
Okay, also wir sind also an dem Punkt angekommen,
dass wir zwei Szenarien haben. Wir haben dieses Szenario,
alles ist gleich, die Menge P ist gleich der Menge
NP ist gleich der Menge NPC.
Oder wir haben dieses Szenario, dann ist P eine Teilmenge
von NP, aber eine echte Teilmenge.
Und dann gibt es noch die anderen. Und da,
in den anderen sind auf alle Fälle
die Probleme NPC drin
Nicht jedes Problem muss dann da drin sein. Vielleicht gibt es
auch noch andere, aber auf alle Fälle wissen wir,
dass dies keine Schnittmenge gibt zwischen NPC und P.
Weil wenn es ein Problem aus NPC gibt, was auch NP gibt,
dann gehen die alle, hören die alle zusammen.
So ist dies, einfach definiert. Also die haben keinen Schnitt,
das ist das Entscheidende. Wenn wir jetzt davon ausgehen,
dass P-Ungleich-NP ist, diese beiden Mengen nichts miteinander zu tun haben,
dann wissen wir, dass diese Probleme alle nicht im Polynomie derzeit lösbar sein können.
Und das ist genau das Resultat, was wir wollen.
Wir wollen nachweisen, dass sie schwer lösbar sind
Okay, so, das war jetzt eine Menge Theorie.
Es ist natürlich die Frage, wie können wir denn jetzt
zeigen, dass so ein Problem da reingehört.
Ja, also wie können wir diese Beziehung hier zeigen?
Ja, das sieht ja nicht einfacher aus wie irgendwas,
was wir vorher hatten. Jetzt müssen wir auch wieder zeigen,
wenn A, Element P ist, müssen wir zeigen,
oha, alle Probleme, die in P sind,
die in NP sind, sind auch in P.
Also wir haben wieder so einen für alle Beweis,
von dem wir keine Ahnung haben, wie wir den führen sollen.
Wir brauchen eine Strategie, um irgendwie sowas nachweisen zu können
Okay, die Strategie gibt es und der entscheidende Weg dabei ist eine sogenannte Reduktion.
Ja, eine Reduktion funktioniert wie voll. Wir bilden einfach
ein Problem auf ein anderes ab. Und wir sagen dann,
wenn wir einen Algorithmus fürs eine Problem haben,
dann können wir mit dieser Abbildung auch das andere Problem lösen,
mit diesem Algorithmus. Wir haben also jetzt zwei Probleme,
A und B und wir suchen eine Funktion,
das ist eine Funktion eher, die überführt einfach eine Eingabe,
also so eine 0-1-Zeichenkette in einer anderen 0-1-Zeichenkette mit der Eigenschaft.
Das X ist Element B, genau dann,
wenn er von X Element A ist.
Also sie transportieren die Eingabe für das eine Problem in
die Eingabe für ein anderes Problem
Mit einer Funktion eher. Und zwar nicht irgendwie,
sondern so, dass wenn für die Eingabe RX,
für das Problem A, die Antwort ja ist oder war,
dann muss auch für die Eingabe X die Antwort für das Problem B,
Ja oder Wahl lauten und umgekehrt.
Das ist eine Äquivalenzzeichen hier. Es muss Äquivalenz sein.
Müssen es so überführen, dass immer dann, wenn A
ja sagt, muss B auch Ja sagen und umgekehrt.
So, und er muss noch eine Eigenschaft haben,
das müssen wir nämlich im Pulli noch mehr der Zeit
berechnen können
So, wenn das so ist, dann machen wir jetzt Folgendes.
Wir entwickeln jetzt einen Algorithmus für B,
in dem wir einfach sagen,
wir nehmen die Eingabe für B, wir verwenden die Reduktionsfunktion,
wir erzeugen eine neue 0,1 Zeichenkette R von X,
die stecken wir in den Algorithmus für Problem A.
Und A sagt dann 1 oder 0 und wir wissen,
weil das Äquivalent ist, dass auch das Ergebnis für B
1 oder 0 sein muss.
Wir können also für zwei Probleme, die Algorithmen ineinander überführen.
Wir können aus der Lösung für Algorithmus A
eine Lösung für Algorithmus B machen.
So, jetzt gibt es wieder einen Begriff.
Wenn es so eine Reduktionsfunktion eher gibt, dann heißt B,
Polynomzeit reduzierbar auf A. Also wir können in Polynomzeit,
diese Funktion eher hat polynomiellen Zeitbedarf, können wir eine Funktion machen,
die die Eingabe von B auf A abbildet.
Und damit können wir das Problem weh lösen. So,
und jetzt können wir, dafür haben wir auch noch eine Nominikatur,
wir schreiben das so, B, kleiner gleich PA.
Und das Tolle daran ist, ist,
wir können eine Aussage machen über die Schwierigkeit dieser Probleme.
Denn ich Egal wie der Algorithmus für A aussieht.
Dieser Algorithmus ist auch anwendbar für B. Durch diese Reduktionsfunktion.
Wenn Sie sich was super Intelligentes einfallen lassen für A,
haben Sie auch eine gute Lösung für B. Das heißt,
Sie können hier eine Aussage machen über die relative Schwierigkeit der Fragestellung.
Nicht mehr über die Komplexität der Algorithmen, sondern über die Fragestellung.
Okay, so, das machen wir mit einer Reduktion.
Und was daraus folgt, ist, wenn A-Element P ist,
mal angenommen, wir können das Problem A in Polynomzeit lösen,
können wir auch das Problem weh in Polynomzeit lösen.
Wie? Naja, wir nehmen den Algorithmus für A,
der das im Polynomzeit macht, wir nehmen die Eingabe X,
wir überführen die nach R-Phone X.
Er braucht nur polynomelle Zeit,
das erste
Dann kommt er von X da raus. Da stecken wir
in den Algorithmus A rein, Algorithmus aber auch polynomiale Zeit,
sagt 1 oder 0. Wir wissen, dass das identisch ist.
Wir sagen nochmal 1 oder 0. Okay. Der Schritt braucht
polynomiale Zeit. Der Schritt braucht Opolynomiale Zeit, also polynomiale
Zeit. Aber, aber, dass er von X,
das Airphone X könnte ja groß sein.
Das Airphone X könnte riesengroß sein. Und dann wäre diese
polynomiale Zeit vielleicht ja wieder exponentiell.
Aber das Airphone X kann überhaupt nicht
groß sein
Warum kann das nicht groß sein? Weil er hat ja
nur polynomiell viel Zeit, seine Ausgabe zu produzieren.
Und in polynomieller Zeit kann ein Algorithmus auf einer RAM,
auf einem Computer auch nur polynomiell viel Speicher vollschreiben.
Das heißt, er von X ist in seiner Länge polynomiell in Bezug auf X.
Und Polynome sind gegeneinander abgeschlossen. Das heißt,
wenn wir eine Polynomiell große Eingabe in den Algorithmus
hier reinstecken und die hat polynomielle Laufzeit,
dann ist die Aufzeit nachher vielleicht viel höher,
aber sie ist immer noch polynomiell
Okay. Gut, so viel zur Reduktion. Mit diesem
Mechanismus können wir nun zeigen, dass Probleme schwierig sind.
Okay, was wir zeigen können, ist,
dass wir die Definition, die wir hatten für NB Vollständigkeit,
nun anders ausdrücken können, indem wir Reduktionen verwenden.
Ich zeige Ihnen erst nochmal die Definition, wir gehen nochmal zurück. Definition war,
das Problem ist NPV ständig und das ist die Klasse,
genau dann, wenn Os A-Element P folgt P gleich NP.
Und da haben wir gesagt, okay, das können wir nicht nachweisen hier.
Das ist schwierig. Aber mit dem Reduktionstechnik können wir jetzt sagen,
können wir zeigen, ein Problem ist NPV vollständig gehört,
also in diese Klasse, wenn zwei Dinge gelten.
Erstens, AS-Element NP, Also es muss ja erstmal überhaupt
in der übergeordneten NP-Klasse drin sein.
Und dann muss gelten für alle Probleme in NP, gilt B,
kleiner Gleich-P, also B ist Polynomzeit reduzierbar auf A.
Das müssen wir zeigen. Und wenn wir das zeigen,
dann haben wir gezeigt, das Problem A, ein NP vollständiges Problem ist.
Denn wir können dann sofort überlegen, was passiert.
Mal angenommen, A, können wir in Polynomzeit lösen.
Und es gibt für jedes Problem aus NP diese Reduktionsfunktion,
dann können wir B nehmen, wir verwenden die Reduktionsfunktion,
wir wandeln den Algorithmus,
den wir für A haben,
in polynomialer Zeit und haben ein Polynomzeitalgorithmus für
Ja, wir können jedes Problem in polynomineller Zeit lösen, wenn wir eine Lösung für A haben.
Gut, da steht hier angenommen, A, Element P, dann können wir Probleme B mit folgendem Algorithmus lösen,
das ist der Algorithmus für B, wir berechnen die Reduktionsfunktion,
wir verwenden den Algorithmus für A, der Algorithmus hat dann polynomielle Laufzeit,
weil der Algorithmus hat polynomielle Laufzeit,
die Reduktionsfunktion berechnen wir im Polynomieler Laufzeit und die ist nur polynomieell lang,
ja, und demnach folgt das für jedes Problem in NP,
gilt B, ist auch Element P,
weil wir einen Algorithmus gezeigt haben,
der das in polynomieeller Zeit machen kann
Okay, so, jetzt sind wir aber immer noch nicht weiter.
Hier steht immer noch ein Für alle. Wir müssen immer noch einen Beweis führen,
wo wir für alle Probleme nachweisen können, dass etwas gilt.
Das wollen wir nicht. Wir wollen irgendwie einen Fall uns angucken.
So, wie können wir jetzt nachweisen, dass ein Problem
tatsächlich NPV-ständig ist? Also zu dieser schweren Klasse gehört.
Wir machen den Schritt eins. Wir zeigen A-Element NP.
Das ist fast immer einfach. Für die Probleme,
die wir hatten, haben wir das eben schon gemacht.
Sie müssen nur das Zertifikat angeben und aufschreiben,
dass sie in polynomialer Zeit nachweisen können,
dass diese Lösung korrekt ist
Das machen sie. Das kann man aufschreiben.
Das ist ganz einfach. Das kann man argumentieren.
Es gibt Probleme, da ist das schwer. Ja,
sage ich Ihnen auch. Es ist nicht immer einfach,
aber es ist fast immer einfach. So,
jetzt nehmen wir ein Problem aus der Klasse NPC,
also ein NP vollständiges Problem, das nennen wir Kuh.
Für Kuh zeigen wir jetzt, dass es eine Polynomzeitreduktion gibt,
die Kuh-Polynomzeit reduziert auf A.
Dann sind wir fertig, indem wir nur ein Problem genommen
haben.
Weil dieses Problem schon in der Klasse NPC ist.
Also weil das Problem schon NPV ständig ist.
Denn für das Problem gilt, es ist in NPV ständig.
Das heißt, für alle Probleme B aus NP gilt B,
kleinergleich PQ, so haben wir es ja definiert.
Und mit Kuh, kleinergleich PR,
also mit Kuh-Polinumzeit reduzierbar auf A, wissen wir,
dass wir auch das Problem B auf A reduzieren können.
Weil natürlich können wir das System hier schachteln.
Wir können natürlich für ein drittes Problem eine Lösung finden,
indem wir die eine Reduktionsfunktion nehmen.
Das bilden wir auf das nächste Problem ab. Dann nehmen
wir die Reduktionsfunktion und wollen das auf den Algorithmus ab,
berechnen das Ergebnis, überführen das, überführen das und haben dann das Gesamtergebnis.
Wir schachteln das einfach ineinander
Okay, so, also, mit dem Beweis wären wir durch die Tür.
Und hier kommt keins für alle mehr vor.
Wir können uns ein beliebiges Problem raussuchen aus der Klasse NPC
und müssen diese Reduktion durchführen. Und wenn die Reduktion funktioniert,
wenn wir diese Funktion angeben können und dann entsprechend das Resultat angeben können,
dann und zeigen können, dass das Resultat identisch ist,
dann sind wir fertig.
Allerdings haben wir noch ein Problem. Ein Problem bleibt noch
, wir kennen kein Problem in NPC.
Wir kennen kein Problem, Kuh, was wir verwenden können.
Und das müssen wir noch lösen. Wenn wir eins kennen
, dann können wir das System anwenden.
Und das eine, das zeige ich Ihnen nächste Woche.