ETH Zürich
Institut für Theoretische Informatik
Prof. Dr. Angelika Steger, Prof. Dr. Thomas Holenstein
Ralph Keusch
HS 2015
Algorithmen und Komplexität
Lösungsvorschlag zu Übungsblatt 8
Lösungsvorschlag zu Aufgabe 1
Wir führen dieses Problem auf das Problem der L ÄNGSTEN G EMEINSAMEN T EILSEQUENZ zurück. Die Sequenz x speichert die n gegebenen Zahlen in der ursprünglichen Reihenfolge und
die Sequenz y speichert die selben Zahlen in aufsteigender Reihenfolge. Wir bestimmen eine
längste gemeinsame Teilsequenz z von x und y; beispielsweise mit dem Verfahren von Aufgabe 2
aus Serie 7. Offensichtlich ist diese Sequenz eine längste monoton steigende Teilsequenz von x.
Wir benötigen Zeit O(n log n), um die n Zahlen zu sortieren, und Laufzeit O n2 , um z zu bestimmen (mit
dem Algorithmus aus Aufgabe 2 der Serie 7). Insgesamt ergibt sich eine Laufzeit
von O n2 .
Lösungsvorschlag zu Aufgabe 2
Wir entwerfen einen Algorithmus nach dem Prinzip der Dynamischen Programmierung. Wir
speichern die Werte unserer sechs Geldscheine 1, 6, 8, 15, 23, 25 aufsteigend in einem Array A.
Geldschein Nummer 1 hat also den Wert A[1] = 1 und Geldschein Nummer 6 hat den Wert
A[6] = 25. Sei nun für 1 ≤ i ≤ n und 1 ≤ k ≤ 6 f (i, k ) die minimale Anzahl Geldscheinen in
welcher der Betrag i dargestellt werden kann, falls nur Geldscheine der ersten k Typen verwendet
werden dürfen. Es folgt daraus, dass
f (i, 1) = i
für alle 1 ≤ i ≤ n
(1)
weil genau i Geldscheine mit Wert 1 verwendet werden müssen um den Betrag i darstellen zu
können. Weiter gilt
f (0, k) = 0 für alle 1 ≤ k ≤ 6
(2)
weil kein Geldschein benötilgt wird um 0 darzustellen.
Für 1 < k ≤ 6 und A[k] ≥ i ≥ n gilt
f (i, k) = min { f (i − A[k], k) + 1, f (i, k − 1)}
(3)
weil der kte Geldschein entweder mindestens einmal oder nie benutzt werden kann und für
1 < k ≤ 6 und 0 < i < A[k ] gilt
f (i, k ) = f (i, k − 1)
(4)
weil der kte Geldschein in diesem Fall nicht verwendet werden kann. Nach dem Prinzip der
Dynamischen Programmierung bauen wir uns eine Tabelle f (i, k) bottom up auf und finden Die
Antwort zu unserem Problem in der Zelle f (n, 6).
Beweis der Korrektheit: Es reicht zu zeigen, dass die Rekursion (3), (4) und die Verankerungen (1),
(2) korrekt sind und nie auf eine noch leere Zelle der Tabelle zugegriffen wird. Falls i = 0, dann
werden offensichtlich 0 Geldschein benötigt um den Betrag i darzustellen. Falls i > 0 und k = 1
dann werden genau i Geldscheine mit Wert 1 benötigt. Falls i > A[k] und k > 1 so kann man
entweder mindestens einen Geldschein vom Typen k verwenden und muss folglich noch den
Betrag i − A[k] konstruieren, benötigt also f (i − A[k], k) + 1 Geldscheine oder man verwendet
den kten Geldschein nicht und muss den Betrag i mit den verbleibenden k − 1 Typen zusammenstellen. Dazu benötigt man f (i, k − 1) Geldscheine. Falls i < A[k] kann der kte Geldschein nicht
1
Algorithm 1 MinGeldscheine(n)
for k=1. . . 6 do
f (0, k ) := 0
end for
for i = 1 . . . n do
f (i, 1) := i
for k=2. . . 6 do
if i ≥ A[k ] then
f (i, k) := min { f (i − A[k ], k) + 1, f (i, k − 1)}
else
f (i, k) := f (i, k − 1)
end if
end for
end for
return f (n, 6)
verwendet werden. Folglich benötigt man genau f (i, k − 1) Geldscheine. Damit haben wir (1)-(4)
bewiesen. Es ist einfach zu sehen, dass in allen drei rekursiven Aufrufen wenn f (i, k) gesetzt
wird nur auf Zellen f (i0 , k0 ) mit 0 ≤ i0 ≤ i und 1 ≤ k0 ≤ k zugegriffen wird. Diese Zellen sind per
Definition des Algorithmus bereits ausgefüllt.
Beweis der Laufzeit: Die erste Schleife wird nur 6 mal ausgeführt. Die zweite wird n mal ausgeführt wobei in jeder Iteration die Innere Schleife 5 mal ausgeführt wird. Daraus gibt sich eine
Gesamtlaufzeit von 6 + n · 5 · O(1) = O(n).
Sobald die Tabelle f (i, k) erstellt wurde kann mittels Backtracking eine optimale Lösung gefunden werden. Wir starten dafür mit f (n, 6). Immer wenn wir in einer Zelle f (i, k ) ankommen,
gehen wir wie folgt vor. Falls 1 < k ≤ 6 und A[k] ≤ i ≤ n dann prüfen wir ob f (i, k ) =
f (i − A[k], k) + 1. Falls dies der Falle ist geben wir einen Geldschein vom Typ k aus und fahren
in der Zelle f (i − A[k], k) weiter. Falls dies nicht der Fall ist fahren wir ohne etwas auszugeben in
der Zelle f (i, k − 1) weiter. Falls 1 < k ≤ 6 und i < A[k] fahren wir direkt ohne etwas auszugeben
in Zelle f (i, k − 1) weiter. Wenn k = 1 stoppen wir und geben i mal den Geldschein 1 aus und
falls i = 0 ist stoppen wir direkt.
Lösungsvorschlag zu Aufgabe 3
Wir bestimmen zuerst die Höhen h T und h T 0 der beiden ( a, b)-Bäume. Da die Bäume höhenbalanciert sind, müssen wir jeweils nur von der Wurzel bis zu einem beliebigen Blatt laufen, und
finden die Höhen der Bäume in O(log m + log n).
Wir nehmen erst an, dass h T 0 ≤ h T . Wir laufen im Baum T solange von der Wurzel zum rechtesten Kind bis wir einen inneren Knoten mit Tiefe h T − h T 0 erreicht haben. Sei v dieser Knoten.
Nun hängen wir T 0 an dieser Stelle ein. Das funktioniert, da alle Elemente in T 0 grösser sind als
das grösste Element in T. Wir verschmelzen also v mit der Wurzel von T 0 . Hierbei müssen wir die
Schlüsselmenge S von v durch S ∪ { g} ∪ K 0 ersetzen, wobei g das maximale Element in T bezeichnet (wir finden dieses in Laufzeit O(log m), indem wir einfach dem rechtesten Ast in T folgen)
und K 0 die Schlüsselmenge in der Wurzel von T 0 ist. Nun kann es natürlich passieren, dass hierdurch die ( a, b)-Bedingung verletzt wird. Wir müssen dann eventuell noch Rebalancierungen
vornehmen, was wir mit dem gleichen Verfahren wie bei Insert( x, T ) tun können, siehe Skript.
Die Höhe des Baumes nimmt dabei maximal um 1 zu. Der Aufwand für die Rebalancierung ist
O(log m) (siehe Skript).
Für h T 0 > h T geht man analog vor. Man geht in T 0 nach links, bis man Tiefe h T 0 − h T erreicht hat,
2
und hängt dort T ein. Wieder passt man die Schlüsselmengen an und muss eventuell Rebalancierungen vornehmen, was maximal Zeit O(log n) benötigt.
Für den gesamten Algorithmus ergibt sich also eine Laufzeit von O(log m + log n).
3