Teilsummenproblem

Dem Algorithmus zur Lösung des Teilsummenproblems werden die folgenden Daten übergeben:
und . Alle Einträge von sowie auch sind dabei aus .

Gesucht wird die Teilmenge aus , sodass die Summe der Elemente aus ist (sofern es eine solche Summe gibt).

Beispiel

Gegeben ist das folgende Array:

Für gibt es eine Teilsumme und zwar:
Für gibt es keine Teilsumme
Für gibt es keine Lösungen, da die Summe aller Elemente von nur beträgt.

Naiver Algorithmus

Alle möglichen Teilsummen in ausprobieren. Da aus Elementen besteht und jedes Element entweder in der Teilsumme enthalten sein kann oder nicht gibt es mögliche Teilsummen.
Der naive Algorithmus ist also viel zu teuer.

Ansatz mit dynamischer Programmierung

Teilproblem

Es wird nun versucht das Teilproblem von zu lösen.
Dazu wird definiert. stellt sich genau die Frage: Ist Teilsumme von und gibt dementsprechend oder zurück.

Rekursion

Bei der Rekursion gibt es nun zwei Möglichkeiten. Die Teilsumme benutzt das Element oder die Teilsumme benutzt das Element nicht. Es entsteht also die folgende Rekursionsformel:

Der erste Teil () entspricht der Aussage, dass die Teilsumme das Elemente nicht verwendet.
Der zweite Teil () entspricht der Aussage, dass die Teilsumme das Element verwendet. Für den Fall, dass bei der Subraktion eine negative Zahl herauskommt kann zurückgegeben werden. Wenn das Element an der Stelle grösser als die gesuchte Teilsumme ist, so wird es von keiner Teilsumme verwendet werden.

Zudem müssen noch zwei Basisfälle definiert werden:

(für )

DP-Tabelle

Für den Fall und dem Array lässt sich die folgende DP-Tabelle aufstellen:

Die Lösung wird unten rechts in rot geschrieben. Eine bedeutet, dass es eine Teilsumme gibt. Um jetzt herauszufinden, was die einzelnen Summanden dafür sind kann man das Backtracking anwenden. Dazu gibt jeder Sprung nach links einen Summanden an.

Die Summanden sind also und

Laufzeitanalyse

Laufzeit:
Speicher:

Hier ist spannend zu beobachten, dass die Laufzeit von der Grösse der gesuchten Zahl abhängt. Dieses Verhalten nennt sich pseudo polynomielle Laufzeit.

Hier betrachtet man nun die Inputlänge. Dazu wird betrachtet, was die Anzahl der benötigten Bits für ist. Also der Logarithmus zur Basis von b: . Einfachheitshalber wird angenommen, dass jeder Eintrag des Arrays je eine Zelle des RAMs benötigen.

Das ergibt dann eine Inputlänge von .

Für ergibt das eine Inputlänge von aber eine Laufzeit von , was ein exponentielles Wachstum ist.

Für ergibt das eine Inputlänge von aber eine Laufzeit von , was ein polynomielles Wachstum ist.

Es stellt sich nun also die Frage, ob es eine Möglichkeit gibt das ganze zu verbessern und für alle eine polynomielle Laufzeit zu erreichen.

Die Antwort darauf ist: Man weiss es nicht aber es gibt eine starke Vermutung, dass es nicht besser geht (P und NP).

Link to original

Rucksackproblem

Link to original

P und NP

Link to original

Approximations Algorithmus anhand des Rucksackproblems

Es stellt sich raus, dass das Rucksackproblem ein NP vollständiges Problem ist. Somit ist eine Lösung des Problems wahrscheinlich nicht in polynomieller Laufzeit möglich. Es gibt aber Algorithmen, welche das Problem approximieren und so in besserer Laufzeit zu einem Ergebnis kommen, welches der eigentlichen Lösung des Problems sehr nahe ist.

Der Approximative Algorithmus nimmt folgende Input Werte:
Des Weiteren wird die folgende Annahme getroffen: Alle
Diese Annahme ist aber harmlos, wenn ist, kann es ohnehin nicht verwendet werden.

Ausserdem gilt: wobei der Profit der optimalen Lösung ist und das maximale

Das Ziel des Algorithmus ist es nun eine Lösung zu finden, welche fast optimal ist und in polynomineller Zeit gefunden werden kann.

Die Idee um das zu erreichen ist es zu runden.

Dazu wird durch ersetzt, wobei folgendermassen definert ist:

und bleiben unverändert die selben Teilmengen wie davor passen in den Rucksack.

Beispiel

, Profite :
Die gerundeten Profite :

Aus der Anwendung des Rundens folgen nun die folgenden Beobachtungen:

Daraus folgen nun die folgenden beiden Varianten:

  • , Profit , Laufzeit: (zu langsam)
  • , Profit ???, Laufzeit:

Die Ungleichung: gilt, da .

Es stellt sich nun allerdings die Frage, wie gut sich für das ursprüngliche Rucksackproblem eignet.

wird daher untersucht um festzustellen, wie gut es sich eignet.

Es ist bekannt, das das Optimum für ist.
Für jede Teilmenge , welche in den Rucksack passt gilt:

Das gilt auch für :

Daraus folgt dann:

Die Idee ist nun, dass ein (kleines) gewählt wird und gesetzt wird.
(Eigentlich müsste stehen, da ist. Einfachheitshalber wird die Rundung aber weggelassen.)

Daraus folgt nun:

Da klein ist, ist die Lösung fast so gut wie .

Die Laufzeit ist nun:

Also ist die Laufzeit polynominell in

Link to original

Längste Aufsteigende Teilfolge

Das Ziel des Algorithmus ist es, die längste Teilfolge in einem Array zu finden, welche kontinuierlich aufsteigt.

Im Array ist die längste aufsteigende Teilfolge (LAT) z.B.
und hat die Länge .

Gegeben ist dem Algorithmus ein Array von Integern und es wird angenommen, das kein Wert doppelt im Array ist.

Gesucht ist die längste aufsteigende Teilfolge wie am Beispiel oben illustriert.

Ansatz mit dynamischer Programmierung

Versuch 1

Beim Versuch 1 wird für immer die längste AT von verwendet.

Beispiel


Das Array hat die folgenden LATs:





Das Problem tritt hier zwischen und auf. Dieser Schritt ist mit Rekursion nicht bzw. nur schwer zu implementieren.

Versuch 2

Versuch 2 geht ähnlich vor, wie dies auch schon in Versuch 1 der Fall war. Allerdings merkt man sich beim zweiten Versuch alle Endungen einer LAT in .

Beispiel



(eindeutig)
(eindeutig)

Auch hier ist dies nur schwer mit Rekursion zu implementieren.

Versuch 3

Die Idee bei Versuch 3 ist, dass man sich auch kürzere ATs in merkt.

ist also definiert, als “Es gibt eine AT der Länge , die in Position endet”. gibt entweder zurück wenn die AT in endet oder wenn sie es nicht tut.

Die Rekursion sieht dann für folgendermassen aus:

Der Basisfall lautet hier folgendermassen:
für alle

Laufzeitanalyse

Wenn man sich die LATs noch einmal anschaut, so sieht man, dass es immer sinnvoller ist, die Folge zu verwenden und nicht . Denn jedes Element, mit welchem man die Folge fortsetzen kann, setzt auch die Folge fort. Hingegen können Elemente, welche aber sind noch an die Folge angefügt werden, jedoch nicht an .

Versuch 4

Der vierte Versuch macht sich genau das oben beobachtete zu Nutzen und soll so die Laufzeit verbessern.

ist die kleinstmögliche Endung einer AT der Länge in . Falls keine solche AT existiert wird dafür geschrieben.

Die Rekursion sieht dann für folgendermassen aus:

Die beiden Basisfälle lauten:

Hier ist nun eine Laufzeit von möglich.

Beispiel

Auch hier kann man wieder einige Eigenschaften entdecken, welche die Implementierung noch einmal verbessern:

  • Die Zeilen sind immer sortiert.
  • Pro Zeile wird immer maximal ein Wert geändert.
  • Der Wert, welcher geändert werden muss kann mit der Binären Suche in durchgeführt werden.
  • Wenn für jede Zeile dasselbe Array verwendet wird und die Einträge nur überschrieben werden, dauert das Update einer Zeile nur
  • Bei Zeilen kommt man somit auf eine Gesamtlaufzeit von

Wenn man nun die Elemente der LAT haben möchte, so muss man für jedes Element des Arrays den Vorgänger speichern. Wichtig ist, dass es der Vorgänger zum Zeitpunkt des Einfügen dieses Elementes ist.

Link to original