11. ¨Ubungsblatt zu Algorithmen I im SS 2010

Werbung
Karlsruher Institut für Technologie
Institut für Theoretische Informatik
Prof. Dr. Peter Sanders
G.V. Batz, C. Schulz, J. Speck
11. Übungsblatt zu Algorithmen I im SS 2010
http://algo2.iti.kit.edu/AlgorithmenI.php
{sanders,batz,christian.schulz,speck}@kit.edu
Musterlösungen
Aufgabe 1
(Negative Kreise in gerichteten Graphen, 2 + 2 + 2 Punkte)
Wir betrachten hier die Ausführung des Algorithmus von Bellman-Ford auf einem gewichteten und
gerichteten Graphen G. Der Graph habe n Knoten und der Startknoten für Bellman-Ford sei mit s
bezeichent. Alle Knoten im Graphen seien von s erreichbar. Mit d[v]k wird hier der Wert von d[v] nach
der k-ten Runde bezeichnet.
a) Zeigen Sie: Ein negativer Kreis existiert genau dann, wenn in der n-ten Runde von Bellman-Ford
d[v] für einen Knoten verkleinert wird.
Hinweis: Siehe Übung.
b) Es seien nun n Runden von Bellman-Ford auf G angewendet und alle Datenstrukturen entsprechend aufgebaut (insbesondere der Array der zu jedem Knoten seinen Vorgänger speichert).
Zudem ist ein Knoten u, mit d[u]n < d[u]n−1 gegeben. Es gibt also nach a) einen Kreis mit
negativem Gewicht. Geben Sie einen Algorithmus mit Laufzeit in O(n) an, der einen negativen
Kreis in G findet und begründen Sie die Laufzeit.
Hinweis: Was passiert wenn man von u aus immer wieder zum Vorgänger geht?
c) Zeigen Sie, dass das was Ihr Algorithmus findet tatsächlich ein negativer Kreis ist.
Hinweis: Sie dürfen davon ausgehen, dass es keinen Kreis mit Gewicht 0 gibt.
Musterlösung:
a) ∃v : d[v]n < d[v]n−1 ⇒ es existiert ein negativer Kreis:
In der Übung wurde gezeigt: Wenn kein negativer Kreis existiert, findet Bellman-Ford zu jedem
Knoten v einen kürzesten Weg von s nach v. Da dieser Weg keine Kreise enthält, gibt es maximal
n − 2 Knoten und n − 1 Kanten auf dem kürzesten Weg zwischen s und v. Insbesondere hat
Bellman-Ford den kürzesten Weg nach maximal n − 1 Runden gefunden. Damit ist in der nten Runde keine weitere Verkleinerung von d[v] mehr möglich. Deshalb muss im Falle einer
Verkleinerung in der n-ten Runde ein negativer Kreis existieren.
Es existiert ein negativer Kreis ⇒ ∃v : d[v]n < d[v]n−1 :
Es sei C ein Kreis der Länge k in G mit einem Gesamtgewicht der Kanten von −c < 0. Nach
t ≤ n − 1 Runden gelte für alle Knoten v ∈ C d[v] < ∞. Da ein negativer Kreis nie statisch wird,
existiert in jeder Runde ` ab der t + 1-ten mindestens ein Knoten v ∈ C mit d[v]` < d[v]`−1 .
b) Algorithmus:
1. Starte mit Knoten u.
2. Markiere den aktuellen Knoten und gehe zum Elterknoten.
3. Falls der Elterknoten nicht markiert ist, gehe zu 2..
4. Wenn der Elterknoten markiert ist, hat man einen Kreis gefunden.
5. Man merkt sich den gerade erreichten Knoten.
1
6. Gib den aktuellen Knoten aus und gehe zum Elterknoten.
7. Wenn der Elterknoten nicht der gemerkte Knoten ist, gehe zu 6..
Der Algorithmus terminiert nach maximal O(n) Schritten:
Nach n Runden von Bellman-Ford haben alle Knoten v außer s einen Elterknoten u 6= v. Wenn
man beim Rückwärtslaufen“ in s ankommt und s sich selbst als Elterknoten hätte, dann wäre
”
man einen kürzesten Pfad von s nach u zurückgelaufen. Dieser hätte jedoch in der n-ten Runde
von Bellman-Ford nicht kürzer werden können, daher kann dieser Fall nicht auftreten. Deshalb
muss man irgendwann immer auf einen schon markierten Knoten stoßen. Da es aber maximal
n Knoten gibt, die markiert werden können, passiert dies spätestens nach n Schritten. Die
Ausgabe des Kreises benötigt auch maximal n Schritte. Damit werden insgesamt nur O(n)
Schritte benötigt.
c) Es sei v1 , . . . , vk mit Kanten e1 = (v1 , v2 ), . . . , ek = (vk , v1 ) der in b) gefundene Kreis C. Wobei
vi jeweils der Elter von vi+1 ist, der Elter von v1 ist vk . Wegen der n-ten Runde (oder der
vorherigen) von Bellman-Ford gilt d[vi+1 ]n = c(ei ) + d[vi ]n−1 . Summiert man die obige Formel
über alle i so erhält man:
k
k
X
X
d[vi ]n =
(c(ei ) + d[vi ]n−1 )
i=1
i=1
Für jedes i gilt zudem d[vi ]n−1 ≥ d[vi ]n , und daher:
k
X
k
X
d[vi ]n ≥
i=1
i=1
k
X
0 ≥
(c(ei ) + d[vi ]n )
c(ei )
i=1
Damit ist C entweder Kreis vom Gewicht 0 (wurde durch den Hinweis ausgeschlossen) oder ein
Kreis von negativem Gewicht.
Aufgabe 2
(Jarnı́k-Prim und Kruskals Algorithmus, 3 + 3 Punkte)
Berechnen Sie einen MST des angegebenen Graphen mit dem Algorithmus von Jarnı́k-Prim und dem
Algorithmus von Kruskal. Geben Sie jeweils die Kanten des MST in der Reihenfolge an, in der sie
der Algorithmus auswählt. Um Eindeutigkeit herzustellen, verwenden Sie Knoten a als Startknoten
von Jarnı́k-Prim und sortieren Sie die Kanten bei beiden Algorithmen bei Unentschieden nach den
Nummern der Endknoten.
b
d
4
8
c
a
1
10
5
7
5
e
3
f
5
g
4
3
13
11
1
i
3
j
2
9
h
7
6
Musterlösung: Jarnı́k-Prim: {a, b}, {a, h}, {h, i}, {i, f }, {f, e}, {i, j}, {b, c}, {f, g}, {g, d}
Kruskal: {a, b}, {h, i}, {a, h}, {e, f }, {i, f }, {i, j}, {b, c}, {f, g}, {g, d}
2
Aufgabe 3
(Minimale Spannbäume, 3 + 3 Punkte)
a) Sei G = (V, E) ein zusammenhängender ungerichteter gewichteter Graph und T ein MST in G.
Für V 0 ⊆ V sei G0 der von V 0 induzierte Teilgraph von G und T 0 der von V 0 induzierte Teilgraph
von T . Zeigen Sie: Wenn T 0 zusammenhängend ist, dann ist T 0 ein MST in G0 .
b) Sei G = (V, E) ein zusammenhängender ungerichteter gewichteter Graph mit Gewichtsfunktion
c : E → R>0 . Sei T ein MST in G bzgl. der Gewichtsfunktion c.
Sei e0 eine Kante in T und q0 ∈ R mit c(e0 ) ≥ q0 ≥ 0. Wir definieren nun eine weitere Gewichtsfunktion c0 : E → R>0 mit c0 (e) := c(e) für e 6= e0 und mit c0 (e0 ) := c(e0 ) − q0 .
Zeigen Sie: T ist auch bzgl. der Gewichtsfunktion c0 ein MST in G.
Musterlösung:
a) T 0 ist Baum, da es nach Vorraussetzung zusammenhängend ist und keine Kreise enthält. Anngenommen T 0 enthielte einen Kreis: Da T 0 Teilgraph von T ist, würde dann aber auch T einen
Kreis enthalten, was ein Widerspruch zur Baumeigenschaft von T ist.
Außerdem ist T 0 spannender Baum, da es als von V 0 induzierter Teilgraph in G alle Knoten
von V 0 berührt.
Weiter ist T 0 minimaler Spannbaum. Andernfalls gäbe es einen anderen Spannbaum T 00 in G0
mit Gewicht c(T 00 ) < c(T 0 ) und wir könnten einen Graph T̃ bilden, indem wir alle Kanten von
T 0 aus T entfernen und stattdessen alle Kanten von T 00 einfügen. Da alle Bäume in G0 gleich
viele Kanten haben (siehe Übung), haben T 0 und T 00 auch gleichviele Kanten. Weiter ist T 0 ein
Teilgraph von T . Folglich werden beim bilden von T̃ gleich viele Kanten weggenommen wie hinzugefügt. Also haben T und T̃ gleich viele Kanten. Außerdem sind T und T 00 zusammenhängend
und somit auch T̃ . Also ist T̃ iebenfalls spannender Baum in G. Es ist aber
c(T̃ ) = c(T ) + c(T 00 ) − c(T 0 ) < c(T )
|
{z
}
<0
im Widerspruch zur Minimalität von T .
b) Wäre T bezüglich der Gewichtsfunktion c0 kein MST in G, dann gäbe es einen MST T 0 in G
mit c0 (T 0 ) < c0 (T ). Dabei gehört e0 entweder zu T 0 dazu oder nicht.
Fall 1: e0 gehört zu T 0 dazu. Da sich c und c0 nur in der Bewertung von e0 unterscheiden und e0
sowohl in T als auch T 0 enthalten ist gilt sowohl c0 (T ) = c(T ) − q0 als auch c0 (T 0 ) = c(T 0 ) − q0 .
Wir haben also
c0 (T 0 ) < c0 (T ) ⇔ c(T 0 ) − q0 < c(T ) − q0 ⇔ c(T 0 ) < c(T )
im Widerspruch zur Minimalität von T bzgl. c.
Fall 2: e0 gehört nicht zu T 0 dazu. In diesem Fall gilt
c(T 0 ) = c0 (T 0 ) < c0 (T ) = c(T ) − q0 < c(T ) ,
was erneut ein Widerspruch zur Minimalität von T bzgl. c ist.
3
Aufgabe 4
(Anwendungsproblem, 1 + 5 Punkte)
Man hat ihnen ein Bild der folgenden Art
gegeben: Zunächst wurden die Hintergrundpixel
völlig zufällig gewählt, d.h. jedes Pixel hat
eine zufällige Farbe zugewiesen bekommen.
Anschließend wurden kleine Objekte jeweils
komplett mit der gleichen Farbe auf das Bild
gezeichnet.
Weiter bekommen Sie folgende Graphmodellierung an die Hand gegeben: G = (V, E) mit
V die Pixel des Bildes und e = {p1 , p2 } ∈ E
genau dann wenn p1 und p2 sind benachbart
und haben die gleiche Farbe.
a) Argumentieren Sie, dass das Graphmodell höchstens O(n) Kanten hat.
b) Geben Sie einen Algorithmus an, der nur mit Hilfe des Graphmodells die Regionen/Objekte
identifiziert die mehr als R Pixel haben und ausgibt. Der Algorithmus soll die Laufzeit von
O(nαT (m, m)) nicht überschreiten.
Musterlösung:
a) Ein Pixel hat entweder 4 oder 8 Nachbarn (je nach Definition). Damit können im Graphen
höchstens 4n Kanten existieren.
b) Aufgrund der Definition des Graphen reduziert sich das Problem auf das Finden von Zusammenhangskomponenten in G. Zusammenhangskomponenten kann man in Zeit O(mαT (m, m))
unter Verwendung einer Union-Find Datenstruktur finden (Union-by-rank + Pfadkompression).
Der Algorithmus sieht folgendermaßen aus:
1: procedure connectedComponents(G = (V, E))
2:
for v ∈ V do initSet(v)
3:
for e = {u, v} ∈ E do
4:
if find(u) 6= find(v) then
5:
union(u, v)
6: return
Allerdings müssen wir uns noch über die Größe der Regionen Gedanken machen. Dazu bekommt
jede Menge ein Attribut size und wir modifizieren die union operation, so dass der Repräsentant (der Knoten der als Parent sich selbst hat) der neuen vereinigten Menge die Summe der
beiden vereinigten Mengen als Wert erhält. Initialisiert werden die Size Attribute mit 1. Weiter führen wir eine neue Operation size(i : 1..n) ein, welche uns die Größe der Menge liefert,
in der sich der Knoten i befindet. Diese Operation kann man folgendermaßen implementieren:
1: function size(i : 1..n)
2:
return size[find(i)];
Zum Ausgeben der Objekte formulieren wir Breitensuchen, die in den jeweiligen Zusammenhangskomponenten nur einmal starten können:
1: procedure findeObjekte(G, R)
2:
connectedComponents(G)
3:
unmark all nodes
4:
forall v ∈ V do
4
if size(v) ≥ R and v not marked then
6:
output ”found new object larger than R”
7:
perform a bfs starting at v and mark all visited nodes; print them
8: return
5:
Wir haben höchstens O(m) Find und Union Operationen, die ausgeführt werden müssen, um
Zusammenhangskomponenten zu finden. Da m ∈ O(n) ergibt sich die geforderte Laufzeit für
das Finden der Zusammenhangskomponenten. Die modifizierte Breitensuche zum Ausgeben der
Komponenten, hat ebenfalls die geforderte Laufzeit. Man beachte das in der size Funktion eine
find Operation steckt.
Zusatzaufgabe 1
(Bottleneck Shortest Path, 2 + 2 + 1 + 1 Punkte)
Sei G = (V, E) ein zusammenhängender ungerichteter gewichteter Graph und s, t ∈ V . Ein kreisfreier
Pfad P zwischen s und t heiße ein Bottleneck Shortest Path für s und t, wenn das größte in P
auftretende Kantengewicht minimal ist für alle Pfade zwischen s und t.
a) Zeigen Sie: Ist T ein MST in G, dann ist der in T eindeutige Pfad P zwischen zwei Knoten
s, t ∈ V ein Bottleneck Shortest Path in G für s und t.
b) Geben Sie einen Algorithmus an, der für gegebenes G = (V, E), gegebene s, t ∈ V und einen
gegebenen MST T in G einen Bottleneck Shortest Path P zwischen s und t ausgibt. Die Laufzeit
soll dabei höchstens in O(|P |) liegen (mit |P | die Anzahl Kanten von P ). Nehmen Sie an T wäre
vom Jarnı́k-Prim Algorithmus geliefert worden und liege in Form des Arrays parent vor.
Hinweis: Es genügt nicht nacheinander die Vorgänger von s und t abzulaufen, da die Laufzeit
sonst schlimmstenfalls in Ω(|V |) liegt.
c) Argumentieren Sie kurz warum Ihr Algorithmus korrekt ist.
d) Argumentieren Sie kurz warum Ihr Algorithmus das geforderte Laufzeitverhalten aufweist.
Musterlösung:
a) Angenommen, P ist kein Bottleneck Shortest Path in G, dann gibt es einen anderen Pfad P 0
zwischen s und t in G, mit
max
e Kante auf P 0
c(e) <
max
e Kante auf P
c(e) .
Sei e0 = {u, v} eine Kanten auf P mit
c(e0 ) =
max
e Kante auf P
c(e) .
Offenbar gilt c(e) < c(e0 ) für alle Kanten e auf P 0 , somit liegt e0 nicht auf P 0 . Bzgl. e0 zerfällt
T in die Teilbäume Tu und Tv , wobei s und t jeweils in einem von beiden liegen (sonst gäbe es
in T mehr als einen einfachen Pfad zwischen ihnen). Da P 0 aber s und t verbindet und e0 nicht
auf P 0 liegt, muss es ein Kante e00 = {u0 , v 0 } =
6 e0 geben mit u0 in Tu und v 0 in Tv . Nun können
0
wir einen neuen spannenden Baum T in G bilden, indem wir e0 in T durch e00 ersetzen. Wegen
c(e00 ) < c(e0 ) gilt aber
c(T 0 ) = c(T ) + c(e00 ) − c(e0 ) < c(T ) .
|
{z
}
<0
Wir haben also einen spannenden Baum T 0 in G mit geringeren Gesamtgewicht als T . Dies
widerspricht aber der Tatsache, dass T MST ist in G.
5
b) Ausgehend von den Knoten s und t springe man jeweils zum Vorgängerknoten des aktuellen
Knoten, was mit Hilfe von parent möglich ist. Dabei werde jeder besuchte Knoten als gesehen“
”
markiert. Irgendwann treffen sich diese beiden Prozesse, was man daran erkennt, dass ein bereits
markierter Knoten besucht wird.
Beide Prozesse merken sich bei ihrer Arbeit jeweils das umgekehrte“ parent, der eine in einem
”
durch eine Liste implementierten Stack, der andere in einer durch eine Liste implementierten
Queue. Sobald sich beide Prozesse getroffen haben, kann man zuerst die Queue und dann den
Stack leer räumen und so die Kanten des Pfades ausgeben (der Stack oder die Queue enthält
möglicherweise ein paar Knoten zuviel, welche man beim entnehmen verwerfen muss).
Wichtig ist, dass man die beiden Prozesse nicht nacheinander ablaufen lässt sondern alternierend. D.h., man lässt die beiden Prozesse jeweils abwechselnd zum nächsten Vorgängerknoten
springen (sonst kann Laufzeit O(Pfadlänge) nicht garantiert werden).
c) Da es in T zwischen zwei Knoten genau einen Pfad gibt, und der Jarnı́ck-Prim den Baum
ausgehend von einem Startknoten r wachsen lässt, kommt man durch das Ablaufen des parent
irgnedwann zu diesem r. Beide Knoten Prozesse müssen sich daher irgendwann treffen.Dabei
sind zwei Fälle möglich:
Fall 1: s und t liegen nicht auf dem selben gerichteten Pfad nach r. Dann treffen sich beide
gerichtete Pfade in einem Knoten v ∗ , spätestens aber in r. D.h. einer der beiden Prozesse trifft
zuerst auf v ∗ und markiert v ∗ , der zweite erkennt dies sobald er ebenfalls auf v ∗ trifft und der
Algorithmus terminiert.
Fall 2: s und t liegen auf dem selben gerichteten Pfad nach r. Liege o.B.d.A. t zwischen s und
r. Dann erreicht die von s gestartete Prozess irgendwann t und wir sind fertig. In diesem Fall
sei v ∗ := t.
Da in T alle einfachen Pfade eindeutig sind, ist der einfache Pfad zwischen s und t über v ∗ die
richtige Lösung.
d) Da die beiden Prozesse alternierend ablaufen, stoppen sie jeweils spätestens nach Pfadlänge + 1
Schritten. Das Aufbauen und Abräumen der Stacks besteht in höchstens O(Pfadlänge) Stackoperationen. Sowohl alle Zugriffe auf parent als auch alle Stack- und Queueoperationen (Listenimplementierung!) benötigen nur konstante Zeit. Insgesamt haben wir also O(Pfadlänge) Laufzeit.
6
Herunterladen