ML - belfrage.net

Werbung
Eidgenössische
Technische Hochschule
Zürich
Ecole polytechnique fédérale de Zurich
Politecnico federale di Zurigo
Federal Institute of Technology at Zurich
Institut für Theoretische Informatik
Peter Widmayer
Michael Gatto
Datenstrukturen & Algorithmen
Lösungen 8
SS 07
Aufgabe 8.1:
a) Eine Multipop-Operation auf einem Stack mit n Elemente hat natürlich eine worst-case Laufzeit von
O(n): falls wir das Element nicht finden, oder wir das unterste Element im Stack suchen, so entfernen
wir alle n Elemente aus dem Stack, was eine Laufzeit von O(n) in Anspruch nimmt.
b) Die Idee ist, beim Einfügen eines Elements gleich für dessen Entfernen aus dem Stack zu bezahlen.
Also legen wir je 1 Franken bei jeder Einfügeoperation auf die Seite. Bei einer Multipop-Operation
können wir diesen Kredit benutzen, um das Entfernen des Elements zu bezahlen.
c) Ein formaler Beweis sieht wie folgt aus: Wir betrachten eine Folge von n Operationen. Als Kontostand
Bal` nach der `-ten Operation definieren wir die Anzahl Elemente, die auf dem Stack liegen. Die
amortisierten Kosten sind definiert als a` =
− Bal`−1 , die tatsächlichen Kosten
t` plus die
Ptn` + Bal` P
Pn
n
Differenz der Kontostände. Es soll gelten: `=1 a` = `=1 (t` + Bal` − Bal`−1 ) = `=1 t` + Baln −
Bal0 , wobei Baln − Bal0 ≥ 0 da wir die tatsächlichen Kosten durch die amortisierten Kosten decken
müssen. Die amortisierten Kosten für eine Top-Operation sind a` = t` + Bal` − Bal`−1 = 1, da sich die
Anzahl Elemente auf dem Stack nicht ändert und wir das oberste Element in konstanter Zeit anschauen
können. Sei die `-te Operation eine Push-Operation, mit bereits m − 1 Elementen auf dem Stack. Es
gilt für die amortisierten Kosten: a` = t` +Bal` −Bal`−1 = 1+m−(m−1) = 2, die amortisierte Kosten
sind konstant. Sei die `-te Operation eine Pop-Operation auf einem Stack mit n > 0 Elementen. Es
gilt: a` = t` + Bal` − Bal`−1 = 1 + m − 1 − m = 0, die Kosten sind amortisiert konstant (sogar gratis!).
Für eine Pop-Operation auf dem leeren Stack gilt: a` = t` + Bal` − Bal`−1 = 1 + 0 − 0 = 1, die Kosten
sind dennoch amortisiert konstant. Der interessante Fall ist natürlich Multipop. Nehmen wir deshalb
an, die `-te Operation sei eine Multipop-Operation auf einem Stack mit m Elementen, und es würden
k ≤ m Elemente aus dem Stack entfernt (mehr können es ja nicht sein, da Multipop beim leeren Stack
aufhört). Es gilt: a` = t` + Bal` − Bal`−1 = k + m − k − m = 0, die Kosten sind für jedes k amortisiert
konstant. Es bleibt noch zu zeigen, dass Baln − Bal0 ≥ 0. Da wir mit dem leeren Stack beginnen,
gilt Bal0 = 0. Weiter ist der Stack
Fall leer, und es gilt Baln ≥ 0. Damit haben wir
Pnim schlimmsten
Pn
bewiesen, dass die Ungleichung `=1 a` ≥ l=1 t` + Baln − Bal0 gilt mit konstanten amortisierten
Kosten.
Aufgabe 8.2:
Wir zeigen, wie man eine Triangulierung benutzen kann, um eine Menge von n Zahlen zu sortieren. Gegeben
sei eine Menge von n Zahlen X = {x1 , . . . , xn }. Wir bilden damit eine Instanz zu einem Triangulierungsproblem, dessen Lösung die sortierte Reihenfolge der Zahlen ablesen lässt. Die Instanz sieht wie folgt aus, und
ist für die Zahlenmenge {6, 2, 5, 7} beispielhaft in der Figur unten abgebildet: jede Zahl xi ∈ X wird zu einem
Punkt (xi , 0) abgebildet. Dadurch entsteht eine Menge von n Punkten, die auf einer Geraden liegen. Wir addieren noch den Punkt (x1 , 1) zu dieser Punktmenge. Es gibt eine einzige (nichtdegenerierte) Triangulierung
dieser Punktmenge: sie bildet jeweils Dreiecke mit den Punkten (xi , 0), (xi+1 , 0), (x1 , 1), i ∈ {1, . . . , n − 1}.
Diese Triangulierung lässt offensichtlich die sortierte Reihenfolge ablesen: man nimmt den ersten Punkt der
Liste und wählt solange den Punkt mit kleinerer x-Koordinate, bis es keinen solchen Punkt mehr gibt. Somit
hat man in O(n) Zeit die kleinste Zahl gefunden. Nun traversiert man die x-Achse von links nach rechts, indem
man bei jedem Punkt den Nachbarn mit grösserer x-Koordinate wählt. Den Punkt (x1 , 1) ignoriert man stets.
Der Aufbau der Instanz erfolgt in linearer Zeit, ebenso das Ablesen der sortierten Reihenfolge. Die Laufzeit dieses Algorithmus
ist deshalb O(n) plus die Laufzeit zur Berechnung der Triangulierung. Da man eine beliebige Schlüsselmenge nicht schneller als in
Ω(n log n) sortieren kann, gilt diese untere Schranke auch für die
Berechnung einer Triangulierung für eine Menge von n Punkten
in der Ebene.
y
1
(0, 0)
x
Aufgabe 8.3:
a) (zum Verständnis etwas präziser als 2-3 Sätze) Wir verfolgen einen Scanline-Ansatz, der die WasserInstallation von links nach rechts traversiert. Die Scanline enthält die Regenrinnen (in einem Balancierten Suchbaum, mit Schlüssel hoehe der Rinnen) die durch die Scanline geschnitten werden. Die
Haltepunkte sind die Start- und Endpunkte der Rinnen. Bei jedem Startpunkt fügen wir die Rinne in
den Baum ein; falls sie zuoberst liegt (und somit die maximale Höhe hat), so muss die dem Wasser
exponierte Fläche der bisherigen höchsten Rinne adaptiert werden, und man merkt sich die Stelle ab
der die aktuelle Rinne die oberste geworden ist. Beim den Endpunkten der Rinnen wird die jeweilige
Rinne aus dem Baum entfernt. Lag die Rinne zuoberst, so muss zuerst noch die gefangene Wassermenge
adaptiert werden. Nun liegt eventuell eine andere Rinne zuoberst, und wir merken uns wieder diesen
Haltepunkt als die Stelle, bei der die Rinne das Maximum geworden ist. Schlussendlich fliesst das
Wasser der entfernten Rinne auf ihren kleineren unmittelbaren Nachbarn im Baum (falls vorhanden).
Wir inkrementieren die Wassermenge dieser Rinne um die Menge Wasser, welche die zu entfernende
Rinne gefangen hat.
b) from i := rinnen.lower until i> rinnen.upper do
haltepunkte.put(rinnen.item(i).links)
haltepunkte.put(rinnen.item(i).rechts)
end
sort(haltepunkte)
start_segment:= haltepunkte.item(haltepunkte.lower).links
from i := haltepunkte.lower until i > haltepunkte.upper do
rinne := get_rinne(haltepunkte.item(i))
if (rinne.links = haltepunkte.item(i)) then
-- Startpunkt
oldmax := scanline.max
scanline.insert(rinne)
if (scanline.max.equal(rinne) then
--eingefuegte Rinne ist Maximum
oldmax.wassermenge := oldmax.wassermenge + (rinne.links-start_segment)*amount
start_segment := rinne.links
end
else
--Endpunkt
if(scanline.max.equal(rinne)) then
--Rinne ist zuoberst
rinne.wassermenge := rinne.wassermenge + (rinne.rechts-start_segment)*amount
start_segment := rinne.rechts
end
abflussrinne:= scanline.previous(rinne)
scanline.remove(rinne)
abflussrinne.wassermenge:= rinne.wassermenge + abflussrinne.wassermenge
--abfliessendes Wasser auf neue Maximum gezaehlt.
end
i:= i + 1
2
end
Die Laufzeit des Ansatzes ist O(N log N ): es gibt 2N Haltepunkte, die in sortierter Reihenfolge traversiert werden müssen. Pro Haltepunkt ist der Aufwand in O(log n). Die Herstellung der sortierten
Reihenfolge erfolgt mit einem optimalen Sortierverfahren in O(N log N ) Zeit.
c) Wir folgen dem gleichen Ansatz wie in a), mit einigen kleinen Unterschieden. Erstens wird jedes Loch
zu einem zusätzlichen Haltepunkt. Zweitens lassen wir beim Entfernen einer Rinne aus der Scanline
das Wasser nicht auf die unterstehende Rinne abfliessen, sondern wir berechnen zuerst nur, wieviel
Wasser die Rinnen von oben direkt abfangen. Die Wassermenge, die jede Rinne durch die Löcher
anderer Rinnen auffängt wird in einem zweiten Schritt berechnet. Dazu dienen die Haltepunkte bei
den Löchern: bei jedem solchen Haltepunkt merken wir uns, auf welche Rinne das Wasser abfliesst.
Es handelt sich dabei um die Rinne im balancierten Baum, die den nächstkleineren Schlüssel hat.
Da jede Rinne in höchstens eine andere Rinne abfliesst, entsteht dadurch ein Wald von Bäumen mit
folgender Interpretation: die Wassermenge der Kinder fliesst in den Vaterknoten. Am Ende des ScanlineAlgorithmus müssen die Bäume noch per post-order traversiert werden, und die Wassermengen die jede
Rinne auffängt dem Vaterknoten dazuaddiert. Somit wird jedem Knoten die Wassermenge zugewiesen,
die in seinen Unterknoten direkt von oben gesammelt wurde. Das Traversieren erfolgt in O(N ) Zeit, der
Aufbau des Baumes auch in Zeit O(N ). Somit kann diese Aufgabe auch in Zeit O(N log N ) berechnet
werden.
Aufgabe 8.4:
Wir vergessen kurz die Analysis-Vorlesung, und verfolgen einen kombinatorischen Ansatz, um das Integral zu
berechnen. Die Beobachtung, die zum Lösungsansatz führt, ist, dass wir das Gebiet in (zum Teil degenerierte)
Trapeze unterteilen können. Für jedes Trapez kann dessen Fläche berechnet werden, und das Integral entsteht
aus der Summe der Flächen der Trapeze. Ein Trapez entsteht, wenn eine neue Gerade das Minimum wird
und die Funktion F beschreibt, oder wenn die minimale Gerade die x-Achse schneidet (denn da ändert sich
das Vorzeichen, mit dem die Fläche fürs Integral zählt). Dies impliziert, dass man alle Punkte enumerieren
muss, bei denen sich die minimale Funktion ändert. Solche Änderungen entstehen nur, falls sich Geraden
kreuzen. Entsprechend müssen wir alle solche Kreuzungspunkten enumerieren. Wir verfolgen deshalb einen
Scanline-Ansatz. Die Scanline geht entlang der x-Achse von links nach rechts durch das Gebiet, und enthält
alle Geraden in der Reihenfolge, wie sie die Scanline schneiden (in aufsteigender y-Koordinate). Bei jedem
Schnittpunkt zwischen zwei Geraden tauschen wir ihre Reihenfolge in der Scanline, fügen die neu ersichtlichen
Schnittpunkte als Haltepunkte der Scanline zu, und prüfen ob sich die tiefste Gerade geändert hat. Falls
dies der Fall ist, so berechnen wir (mit der aus der Geometrie bekannten Formel) die Fläche des Trapezes,
das gerade beendet wurde, und addieren es zum Integral. Diese Schritte wiederholen wir solange, bis wir das
rechte Ende des Intervalls erreicht haben, und somit den Wert des Integrales berechnet wurde.
Wir beginnen am linkem Intervall-Ende xl , und fügen die Geraden in einen balancierten Suchbaum ein. Als
Schlüssel für die i-te Gerade benutzen wir fi (xl ). Wir sammeln eine Startmenge an Haltepunkte für die
Scanline. Für jede Gerade prüfen wir, ob sie die x-Achse im Intervall [xl , xr ] schneidet. Falls ja, handelt es
sich um einen Haltepunkt. Weiter addieren wir als Haltepunkte die Schnittpunkte von je zwei benachbarten
Geraden in der Scanline, die sich im Intervall [xl , xr ] schneiden. Nun beginnen wir den Scan: wir betrachten
das aktuelle Minimum in der Scanline, und merken uns xstart := xl als den Punkt, bei dem die Gerade das
Minimum wird. Wir gehen zum nächsten Haltepunkt xi : falls es sich um einen Schnittpunkt zwischen zwei
Geraden handelt, so vertauschen wir die zwei Geraden in der Scanline, und prüfen ob die Schnittpunkte der
neu entstandenen Nachbarn im Intervall [xi , xr ] liegen. Falls ja, addieren wir sie als Haltepunkte der Scanline.
Falls in der Vertauschung die Gerade fk beteiligt war, die im Intervall [xstart , xi ] das aktuelle Minimum war,
so berechnen wir die Fläche des Trapez mit Eckpunkten (xstart , 0), (xi , 0), (xi , fk (xi )) und (xstart , fk (xstart ))
als Ii = 21 (xi − xstart ) · (fk (xi ) + fk (xstart )). Man beachte, dass man durch diese Formel automatisch das
richtige Vorzeichen erhält, mit dem die Fläche zum Integral zählt. Danach setzen wir xstart := xi . Falls der
Schnittpunkt einer Gerade fk mit der x-Achse auftritt, so ist dieser nur interessant, falls diese Gerade das
aktuelle Minimum ist. Falls dies zutrifft, so berechnen wir die Fläche des degenerierten Trapez (ein Dreieck)
mit Eckpunkten (xstart , 0), (xi , 0), (xi , fk (xi )) und (xstart , fk (xstart )) (ein Dreieck).
3
Beim Erreichen des rechten Intervallendes xr ist die Berechnung beendet. Zum Abspeichern der Haltepunkte
können wir einen Minimum-Heap benutzen. Das Einfügen der k Haltepunkte hat insgesamt eine Laufzeit von
O(k log k). Pro Haltepunkt haben wir konstanten Aufwand, und somit eine gesamte Laufzeit von O(k log k +
n log n). Im schlimmsten Fall gibt es für n Geraden k = Θ(n2 ) Schnittpunkte, und dies führt zu einer Laufzeit
von O(n2 log n).
Mit einer einfachen Überlegung lässt sich die Anzahl relevanten Schnittpunkte auf O(n) reduzieren. Bei
jedem Kreuzungspunkt ist die Gerade die “unten” war nach dem Schnittpunkt nicht mehr relevant, da sie
nie mehr das Minimum werden kann. Sie kann deshalb aus der Scanline entfernt werden, und für diese Gerade
müssen wir keine weitere Haltepunkte betrachten. Beim Entfernen entsteht ein neues Nachbarnpaar, das sich
schneiden könnte, und wir fügen dieses Paar ein. Da wir am Anfang n Haltepunkte haben, pro entfernte
Gerade höchstens einen neuen Schnittpunkt einfügen, und sonst noch die Schnittpunkte mit der x-Achse
betrachten müssen, gibt es insgesamt 3n Haltepunkte (wobei einige Geraden betreffen, die bereits entfernt
wurden, und deshalb ignorieren kann).
Aufgabe 8.5:
Wir wollen untersuchen, ob der Beweis für die lineare Laufzeit des Median-Algorithmus von Blum auch mit
3er Gruppen funktioniert. Wir gehen exakt wie in Kapitel 3.1 des Buchs vor. Die Wahl des Aufteilungselementes v als Median der Mediane sichert, dass mit Ausnahme der Gruppe, in der v selbst vorkommt und
der möglicherweise vorkommenden einzigen Gruppe mit weniger als drei Elementen, jede Dreiergruppe mit
mittlerem Element kleiner als v wenigstens zwei Elemente enthält, die kleiner als v sind. Das gleiche gilt für
Elemente, die grösser als v sind. Also gibt es in der Ausgangsfolge wenigstens
N
1 N
− 2) ≥
−4
2·(
2 3
3
Elemente, die kleiner als v sind,und ebenso viele Elemente, die grösser als v sind. Daraus folgt, dass das
Verfahren für höchstens 2N
3 + 4 Elemente rekursiv aufgerufen werden muss. Damit ergibt sich analog zum
Buch folgende Rekursionsformel für die Laufzeit:
T (N ) ≤ T
N
3
+T
2
N +4
+a·N
3
(1)
Nun scheitert man beim Versuch, eine Konstante c zu finden mit der Eigenschaft, dass T (N ) ≤ cN gilt für
alle N ≥ N0 . Analog zum Buch haben wir die Bedingung:
N
2
T (N ) ≤ c ·
+c·
N +4 +a·N
3
3
1
2
≤ c · N + c + c · N + 5c + a · N
3
3
= cN + 6c + aN
Für keine Konstante c ist diese Laufzeit kleiner als cN wie gefordert, da der Summand aN nicht wie im Fall
von 5er Gruppen kompensiert werden kann. Die Konstante a ≥ 1 ist fest vorgegeben durch die Laufzeit des
Aufteilungsschritts.
Wir haben gezeigt, dass ein analoger Beweis für Dreiergruppen nicht funktioniert und damit erklärt, weshalb
der Algorithmus von Blum mit 5er Gruppen realisiert wurde.
Die Frage, ob der Algorithmus mit 3er Gruppen immer noch linear ist, ist viel schwieriger zu beantworten
und sprengt eigentlich den Rahmen von Datenstrukturen und Algorithmen.
Die Schwierigkeit liegt darin, dass wir in (1) für den Aufwand nur eine obere Schranke haben. Wir müssten
zeigen,
2N dass es eine worst case Sequenz von Zahlen gibt, die tatsächlich auf jeder Stufe immer mindestens
Zahlen für den rekursiven Aufruf übriglässt.
3
4
Für k = 7 sieht die Situation wie folgt aus. Wieder sichert die Wahl von v als Median der Mediane, dass es
höchstens zwei Gruppen gibt, die ein kleineres mittleres Element als v haben und weniger als 4 Elemente,
die kleiner als v sind. Die einzigen Gruppen, die diese Eigenschaft haben, sind die Gruppe, in der v selbst
vorkommt, und die einzige Gruppe mit weniger als k Elementen.
Jede andere Gruppe, bei der das mittlere Element kleiner als v ist, hat somit 4 Elemente, die kleiner als v
sind. Analog gilt dies für die Gruppen mit
mittlerem Element als v.
grösserem
Es gibt deshalb in der Ausgangsfolge 4 · ( 21 N7 − 2) ≥ 2N
7 − 8 Elemente, die kleiner als v sind, und gleich
viele, die grösser als v sind.
5N
Die Rekursion wird deshalb auf höchstens dN − 2N
7 − 8e = d 7 + 8e Elemente aufgerufen.
Wir können nun wieder die Rekursionsformel für die Laufzeit schreiben:
5
N
+T
N +8
+a·N
T (N ) ≤ T
7
7
Wie im Buch suchen wir eine Konstante c so, dass T (N ) ≤ cN , für alle N ≥ N0 . Daraus erhält man
N
5
N
5
6
T (N ) ≤ c ·
+c·
N +8 +a·N ≤c·
+ c + c · N + 9c + a · N = cN + 10c + aN
7
7
7
7
7
Wir wählen nun c so, dass
erhält man:
6
7 cN
+ 10c + aN ≤ cN,. Dazu stellen wir die Bedingung, dass c > 14a. Daraus
6
6
Nc
13
cN + 10c + aN < cN + 10c +
=
N c + 10c ≤ cN.
7
7
14
14
N
Daraus folgt 10 < 14
, N ≥ 140.
Wir haben somit bewiesen, dass für alle N > 140, und bei der Wahl von c > 14a der Aufwand linear in cN
ist.
Der Median-Algorithmus terminiert also auch in linearer Zeit, wenn man k = 7 wählt.
5
Herunterladen