Beispiellösung zu den ¨Ubungen Datenstrukturen und Algorithmen

Werbung
Robert Elsässer
u.v.a.
Paderborn, den 19. Juni 2008
Beispiellösung zu den Übungen
Datenstrukturen und Algorithmen
SS 2008
Blatt 11
AUFGABE 1 (6 Punkte):
In der Vorlesung haben Sie die Union-Find-Datenstruktur zur Verwaltung disjunkter Mengen kennengelernt. Die vorgestellte Implementierung basierte jedoch auf verketteten Listen.
Eine effizientere Implementierung basiert auf Bäumen. Gegeben seien nun folgende Funktionen:
MakeSet(x):
1 p[x] ← x
2 rank[x] ← 0
FindSet(x):
1 if x 6= p[x]
2 then p[x] ← FindSet(p[x])
3 return p[x]
Union(x, y):
1 Link(FindSet(x), FindSet(y))
Link(x, y):
1 if rank[x] > rank[y]
2 then p[y] ← x
3 else p[x] ← y
4
if rank[x] = rank[y]
5
then rank[y] ← rank[y] + 1
Des weiteren wollen wir mit size(x) die Anzahl der Knoten im Teilbaum von x (inklusive x)
bezeichen.
a) Zeigen Sie: Für jede Wurzel x eines Baumes im Union-Find-Wald gilt size(x) ≥ 2rank[x] .
— Lösungsvorschlag —
Wir beweisen die Aussage per Induktion über rank [x]:
rank [x] = 0:
Fall 1 (p[x] = x): Dann wurde die Menge für x neu via MakeSet(x) erstellt.
Es ist somit nichts zu zeigen.
Fall 2 (p[x] 6= x): x ist somit nicht die Wurzel des Baumes, in dem sich x befindet. Dieser Fall ist damit auch nicht von Interesse.
rank[x] → rank[x] + 1:
Fall 1 (p[x] = x): Für jeden Aufruf von Union wurde der jeweils andere Teilbaum dem Knoten x als Kind angehangen. O.E. betrachten wir folgende Sequenz von Aurufen: Union(y0 , x), . . . , Union(yrank[x] , x) mit rank [yi] = i und
size(yi) minimal. Auf diese Weise erhalten wir eine minimale Anzahl von Knoten im Teilbaum mit Wurzel x. In allen anderen Fällen nimmt lediglich size(x)
zu.
Durch diese Sequenz von Operationen erhöht sich rank[x] somit um 1. Im Folgenden bezeichne nun also rank[x] den um 1 erhöhten Rang von x.
⇒ x hat jeweils einen Teilbaum mit rank[y] = 0, 1, . . . , rank[x] − 1.
rank[x]−1
rank[x]−1
IV
P
P
2i = 1 + 2rank[x]−1+1 − 1 = 2rank[x]
size(yi ) ≥ 1 +
⇒ size(x) = 1 +
i=0
i=0
Der Clou: Die IV gilt für die Bäume mit Wurzeln yi , 0 ≤ i < rank[x]. Da
wir diese Bäume unverändert als Kinder von x einhängen gilt die IV auch
weiterhin, womit der Induktionsschritt durchführbar ist.
Fall 2 (p[x] 6= x): Dieser Fall ist wiederum nicht von Interesse, da x nicht Wurzel
des Baumes ist, in dem sich x befindet.
Die Behauptung ist damit gezeigt.
b) Es sei n ∈ N die Anzahl der Knoten in einem Union-Find-Wald. Zeigen Sie: Für jedes
r ≥ 1 gibt es höchstens n/2r Knoten x mit rank [x] = r.
Bemerkung: Daraus folgt unmittelbar rank [x] ≤ log2 (n).
— Lösungsvorschlag —
Wie in (a) stellt Union(y0 , x), . . . , Union(yk−1 , x) mit rank [yi] = i eine die Anzahl der
Knoten im Baum minimierende Sequenz von Aufrufen dar, nach der rank[x] = k ∈ N
gilt. Sei nun n ∈ N und l ∈ N mit ⌊n/2r ⌋ = l. Wir generieren nun mittels obiger Sequenz
einen Wald mit l vielen disjunkte Bäumen. Die Wurzeln dieser Bäume seien x1 , . . . , xl .
Wie in (a) gesehen ist deren Rang gegeben als rank[xi ] = r, i = 1, . . . , l. Ebenfalls mit
dem Resultat aus (a) erhalten wir:
n=
l
X
i=1
(a)
size(xi ) ≥ ⌊n/2r ⌋ · 2r = n/2r · 2r − (n/2r − ⌊n/2r ⌋) ·2r
{z
}
|
0≤·<1
Wie ist dieses Resultat nun zu lesen? Bei n/2r ∈ N Knoten mit rank[x] = r erhalten
wir durch obige Abschätzung, dass ein entsprechender Union-Find-Wald mindestens
in Summe n Knoten aufweisen muss. Das zeigt die Behauptung.
c) Zeigen Sie: Für jede Wurzel x ist die Höhe h(x) unter x beschränkt durch rank [x], d.h.
es gilt h(x) ≤ rank[x].
— Lösungsvorschlag —
Wir zeigen die Aussage per Induktion über rank[x].
rank[x] = 0: x wurde lediglich als Kindknoten in einen bestehenden Baum eingehangen. Die Behauptung ist damit offensichtlich klar.
rank[x] → rank[x] + 1:
Fall 1: Der Baum mit Wurzel x wird in einen bestehenden Baum eingehangen.
Die Höhe der des Teilbaumes mit Wurzel x ändert sich damit nicht, womit
die Behauptung per Induktionsvoraussetzung folgt.
Fall 2: Ein Baum mit Wurzel y und rank[y] < rank[x] werde in den Baum mit
Wurzel x eingehangen (siehe Zeile 2 oder 3 der Funktion Link). Nach IV wissen wir: h(y) ≤ rank[y]. y wird direkt als Kind unterhalb von x eingehangen,
deshalb folgt:
h(x) = max {h(y) + 1, h(x)}
≤ max {rank[y] + 1, rank[x]}
= rank[x]
Hier ändert sich also ebenfalls nichts, weshalb die Behauptung trivialerweise
per Induktion stimmt.
Fall 3: Ein Baum mit Wurzel y und rank[y] = rank[x] werde in den Baum mit
Wurzel x eingehangen (siehe Zeilen 3-5 der Funktion Link). Wieder wissen
wir nach IV um die Korrektheit der Ungleichung für rank[x] und rank[y]. Es
bezeichne rank[x] den Rang von x nach einhängen von y, sprich den um 1
erhöhten Rang von x. Wir erhalten wiederum die Abschätzung:
h(x)neu = max {h(y) + 1, h(x)alt }
≤ max {rank[y] + 1, rank[x] − 1}
| {z }
=rank[x]
= rank[x]
AUFGABE 2 (6 Punkte):
Gegeben seien ein gerichteter Graph G = (VG , EG ), drei paarweise verschiedene Knoten
v0 , w0 , x0 ∈ VG und eine Gewichtsfunktion ω : EG → N. Formulieren Sie einen Algorithmus, der den zweitkürzesten Weg von v0 nach w0 bestimmt, welcher über x0 führt (sofern
ein solcher existiert). Begründen Sie die Korrektheit Ihres Algorithmus und leiten Sie dessen
Laufzeit her.
Die Angabe von Pseudocode ist hier nicht gefordert, jedoch sollte die Grundidee Ihres Algorithmus aus der Beschreibung deutlich werden.
— Lösungsvorschlag —
Notation:
a) p = (u0 , u1, . . . , un−1 ) sei ein Pfad von u0 nach un−1 der Länge n − 1.
b) Seien p1 = (u1,0 , u1,1 , . . . , u1,n1 −1 ), p2 = (u2,0 , u2,1, . . . , u2,n2−1 ) zwei Pfade. Zudem gelte
u1,n1 −1 = u2,0 . Dann bezeichne p2 ◦ p1 die Hintereinanderschaltung“ beider Pfade,
”
sprich p2 ◦ p1 = (u1,0 , . . . , u1,n1 −1 , u2,1 , . . . , u2,n2−1 ).
Die Idee ist sehr einfach: O.E. existiere ein kürzester und zweitkürzester Weg von v0 nach
w0 über x0 und es seien weiterhin p1 = (v0 =: u1,0 , u1,1, . . . , u1,n1 −1 := x0 ) und p2 = (x0 =:
u2,0 , u2,1, . . . , u1,n2 −1 := w0 ) jeweils kürzeste Wege. Dann ist p2 ◦ p1 ein kürzester Weg von v0
nach w0 über x0 .
Ein zweitkürzester Weg von v0 nach w0 muss nun ebenfalls über x0 führen. Klar ist: Dieser
muss sich in mindestens einer Kante vom Ursprungspfad unterscheiden. Wir entfernen nun
für jeden Teilpfad p1 , p2 jede Kante auf dem gegangenen Weg einmal aus G und berechnen
einen neuen kürzesten Weg im veränderten Graphen. Wir erhalten nun für jeden vormals
kürzesten Teilpfad eine Menge von zweitkürzesten Teilpfaden. Formal:


(i)
p = (u(i) , u(i) , . . . , u(i)


) ist zweitkürzester Pfad
(i)
j
j,0
j,1
j,nj −1
(i)
Ij := pj , 1≤j≤2
(i)
(i)
′

von uj,0 nach uj,n(i)−1 in G = (VG , EG \ {(uj,i, uj,i+1)}), 0 ≤ i < nj − 1 
j
Der zweitkürzeste Pfad von v0 nach w0 ergibt sich mittels
 

n(i)
−2



j

 X


(i)
(i)
,
ω(uj,k , uj,k+1)
(α, β) := min (i, j) min i 1≤j≤2 

pj(i) ∈Ij 

 k=0


(α)
indem nun pβ durch pβ ausgetaucht und die neue Komposition der Teilpfade gebildet wird.
Bemerkung: Schleifen, die von einem Knoten des Pfades zu einem bereits auf dem Pfad liegenden Knoten führen sind hier kein Problem, da dieser Pfad durch den Dijkstra-Algorithmus
nie ausgegeben wird, sprich auf dessen Korrektheit beruht. Ebenso ist es kein Problem herauszufinden, ob kürzeste respektive zweitkürzeste Pfade existieren. Dies liefert uns die folgende
Modifikation des Dijkstra-Algorithmus:
ModifiedDijkstra(G, source, target, ω):
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
for v ∈ V do
d[v] ← ∞
π[v] ← Nil
d[source] ← 0
Q ← new MinHeap(V)
while Q 6= ∅ do
u ← ExtractMin(Q)
if u = target
then L ← new LinkedList()
head[L] ← Nil
while π[u] 6= Nil do
key[u] ← u
next[u] ← head[L]
head[L] ← u
u ← π[u]
return L
for u ∈ Adj[v] do
if d[u] + ω(u, v) < d[v]
then d[v] ← d[u] + ω(u, v)
π[v] ← u
return Nil
Laufzeit: Zwei kürzeste Teilpfade werden via Dijkstra bestimmt, sprich wir erhalten O(|V |2 ).
Der längst mögliche Pfad von v0 nach w0 führt über |V |−1 Kanten, sprich im worst-case wird
Dijkstra nochmals |V | − 1 mal aufgerufen, um zweitkürzeste Pfade zu ermitteln. Die Bestimmung des minimalen Pfade in der Menge der zweitkürzesten Pfade fällt hier asymptotisch
nicht ins Gewicht. Wir erhalten also eine Laufzeit von O(|V |3 ).
AUFGABE 3 (6 Punkte):
Betrachten Sie den folgenden Algorithmus in Pseudocode.
Update-MST(G = (V, E), T = (W, F ), e, ω, ω ′):
1 if e 6∈ F ∧ ω ′ (e) > ω(e)
2 then return NoTreeEdgeIncWeight(?)
3 if e 6∈ F ∧ ω ′ (e) < ω(e)
4 then return NoTreeEdgeDecWeight(?)
5 if e ∈ F ∧ ω ′ (e) > ω(e)
6 then return TreeEdgeIncWeight(?)
7 if e ∈ F ∧ ω ′ (e) < ω(e)
8 then return TreeEdgeDecWeight(?)
Als Eingabe erhält obiger Algorithmus einen ungerichteten, zusammenhängenden, gewichteten Graph G = (V, E) mit zugehöriger Gewichtsfunktion ω : E → N, einen minimalen
Spannbaum T = (W, F ) von G, eine neue Gewichtsfunktion ω ′ : E → N und eine Kante e,
deren Gewicht geändert wird.
Der Algorithmus soll nun den minimalen Spannbaum T von G aktualisieren und zurückgeben, so dass dieser auch mit dem neuen Gewicht von e (sprich ω ′ (e) statt wie vorher ω(e))
immer noch ein minimaler Spannbaum ist. Dabei soll kein von Grund auf neuer MST für G
berechnet werden.
a) Beschreiben Sie in Worten, wie die Algorithmen NoTreeEdgeIncWeight und TreeEdgeDecWeight funktionieren müssen, damit Update-MST korrekt arbeitet. Wie
könnte Update-MST an den entsprechenden Stellen (Zeile 2 und Zeile 8) abgeändert
werden? Begründen Sie Ihren Antworten.
— Lösung —
In beiden Fällen bleibt T offensichtlich weiterhin ein MST von G, da entweder das Gewicht einer Kante, welche nicht Teil von T, also zu schwer war, zugenommen hat (Zeile
2) oder aber das Gewicht einer Baumkante veringert wurde, womit sie immer noch
Teil von T ist (Zeile 8). An diesen Stellen kann man sich die Aufrufe von NoTreeEdgeIncWeight und TreeEdgeDecWeight sparen und einfach T zurück geben.
Beweis:
– e 6∈ F ∧ ω ′ (e) > ω(e)
Durch das Hinzufügen von e zu T entsteht in dem Teilgraph ein Kreis. Um diesen
aufzulösen, muss wiederum eine Kante aus T entfernt werden. Sei C die Kantenmenge dieses Kreises ohne e.
∀c ∈ C ω(c) ≤ ω(e) (sonst wäre e statt c Teil von T)
⇒ ∀c ∈ C ω ′ (c) < ω ′(e)
⇒ Analog zu b) wird e als schwerste Kante von C wieder aus T entfernt.
– e ∈ F ∧ ω ′ (e) < ω(e)
Bzgl. ω war T ein MST . Durch das Senken des Gewichtes einer einzelnen Kante
um eine Differenz d sinkt genauso das Gewicht jedes MST maximal um d, so in
diesem Fall. Damit ist T weiterhin minimal.
b) Beschreiben Sie in Worten, wie der Algorithmus NoTreeEdgeDecWeight funktionieren muss, damit Update-MST korrekt arbeitet. Begründen Sie ihre Antwort.
Formulieren Sie ihre Idee dann als Pseudocode und gehen Sie kurz darauf ein, warum
ihr Algorithmus korrekt ist.
Hinweis: Ein möglicher Algorithmus lässt sich mit einer Laufzeit von O(|W | + |F |)
realisieren.
— Lösungsvorschlag —
Es gilt zu prüfen, ob e, welche bis jetzt nicht Teil von T war, durch das nun verringerte
Gewicht eine Baumkante in einem MST von G sein kann. Dazu wird e zur Kantenmenge
von T hinzugefügt. Da T ein MST ist, entsteht dadurch in T ein Kreis C. Diesen Kreis
gilt es zu finden (s. Pseudocode) um dann die Kante mit dem nun größten Gewicht
wieder aus ihm zu entfernen. Das Ergebnis ist wieder ein MST.
NoTreeEdgeDecWeight(T, e = {u, v}, ω ′):
1
2
3
4
5
6
7
8
9
10
11
12
BFS(T, u)
C ← {e}
w←v
while π[w] 6= nil do
// gemeint ist hier das π aus der Breitensuche
C ← C ∪ {{π[w], w}}
w ← π[w]
emax ← e
for each e′ ∈ C do
if ω ′ (e′ ) > ω ′ (emax )
then emax ← e′
T ′ ← (T ∪ {e})\{emax }
return T ′
c) Verfahren Sie nun analog zu b) für den Algorithmus TreeEdgeIncWeight.
Hinweis: Das heisst nicht, dass die oben genannte Laufzeit auch hier möglich sein muss.
— Lösungsvorschlag —
Hier überprüft man nun, welche Knoten nach wie vor in der gleichen Zusammenhangskomponenten wie einer der Knoten der Kante e liegen, wenn man sie entfernt (Breitensuche). Diese Knoten werden markiert (Implizit durch Breitensuche und d[v] gegeben).
Nun durchläuft der Algorithmus alle Kanten von G und überprüft, ob sie einen markierten Knoten mit einem nichtmarkierten Knoten verbinden (also aus T wieder eine
einzige Zusammenhangskomponente machen würden). Von diesen Kanten wählt man
diejenige mit dem kleinsten Gewicht (welche auch wieder e sein kann) aus und fügt sie
T \{e} hinzu. (Im weiteren Verlauf der Lösung gestatte ich mir diese etwas unsaubere
Schreibweise.)
TreeEdgeIncWeight(G = (V, E), T, e = {u, v}, ω ′):
1
2
3
4
5
6
7
8
BFS(T \{e}, u)
emin ← e
for each e′ = {w, x} ∈ E do
if (d[w] = ∞ ∧ d[x] < ∞) ∨ (d[x] = ∞ ∧ d[w] < ∞)
then if ω ′(e′ ) < ω ′ (emin )
then emin ← e′
T ′ ← (T \{e}) ∪ {emin }
return T ′
AUFGABE 4 (6 Punkte):
Sei der folgende gewichtete, ungerichtete, zusammenhängende Graph G = (V, E) gegeben:
Gesucht ist ein minimaler Spannbaum. Bestimmen Sie diesen mithilfe des Algorithmus von
Kruskal und geben Sie nach jedem Schritt den Inhalt der unterliegenden Datenstruktur an.
— Lösungsvorschlag —
Schritt
1
2
3
4
5
6
7
8
9
Graph:
Zusammenhangskomponenten
{A}, {B}, {C}, {D}, {E}, {F }, {G}, {H}, {I}
{A, H}, {B}, {C}, {D}, {E}, {F }, {G}, {I}
{A, H}, {B, I}, {C}, {D}, {E}, {F }, {G}
{A, B, H, I}, {C}, {D}, {E}, {F }, {G}
{A, B, F, H, I}, {C}, {D}, {E}, {G}
{A, B, F, H, I}, {C}, {D, E}, {G}
{A, B, F, G, H, I}, {C}, {D, E}
{A, B, C, F, G, H, I}, {D, E}
{A, B, C, D, E, F, G, H, I}
Kante
{A, H}
{B, I}
{A, B}
{F, I}
{D, E}
{G, H}
{C, I}
{B, E}
Herunterladen