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 Tobias Pröger Thomas Tschager Datenstrukturen & Algorithmen Lösung 9.1 29. April 2015 Lösungen zu Blatt 9 FS 15 Tiefensuche, Breitensuche und Topologische Sortierung. a) A, B, C, I, F, D, E, H, G b) A, B, C, D, E, H, I, F, G c) Sowohl Breiten- als auch Tiefensuche müssen für jeden Knoten einmal alle Nachbarn besuchen. Ist der Graph durch Adjazenzlisten gegeben, so muss die Liste der Nachbarn für jeden Knoten abgearbeitet werden. Die Laufzeit ist in diesem Fall O(n + m), also für zusammenhängende Graphen O(m) (wie üblich bezeichnen wir die Anzahl Knoten mit n und die Anzahl Kanten mit m). Wenn allerdings nur eine Adjazenzmatrix gespeichert ist, kann man die Nachbarn eines Knotens nur finden, indem man alle Einträge der entsprechenden Zeile durchgeht. Da dies für jeden Knoten getan werden muss, ist die Laufzeit Θ(n2 ). Bei einem sehr dichten Graphen mit m ∈ Θ(n2 ) ist die asymptotische Laufzeit gleich. Bei dünnen Graphen, beispielsweise mit m ∈ O(n), führt die Verwendung einer Adjazenzmatrix allerdings zu einer erheblich schlechteren asymptotischen Laufzeit. d) Eine kleinste Möglichkeit ist die Entfernung der Kanten (E, A) und (F, B). Eine mögliche topologische Sortierung ist A, B, C, I, H, D, F, G, E (die topologische Sortierung ist nicht eindeutig). Die andere kleinste Möglichkeit besteht in der Entfernung der Kanten (A, B) und (F, B). Eine mögliche topologische Sortierung wäre dann B, H, D, G, E, A, C, I, F (auch hier ist die topologische Sortierung nicht eindeutig). Es existieren zahlreiche weitere Möglichkeiten, mehr als zwei Kanten zu entfernen. Insbesondere könnte sogar E 0 = E gewählt werden. In diesem Fall ist der entstehende Graph leer, und jede Reihenfolge der Knoten bildet eine topologische Sortierung. Lösung 9.2 Union-Find Strukturen. Wenn jeweils der Baum mit weniger Knoten an denjenigen mit mehr Knoten angehängt wird, dann gilt für die Union-Find Struktur für jeden Baum mit Höhe h und n Knoten die Invariante n ≥ 2h (Lemma 6.3, Kapitel 6.2.2). Die Invariante besagt, dass ein Baum der Höhe h mindestens 2h Knoten enthalten muss. Wir konstruieren einen Baum der Höhe h und genau 2h Knoten wie folgt: Ein Baum der Höhe h = 0 besteht aus n = 1 = 20 Knoten. Um einen Baum der Höhe h > 0 zu konstruieren, verschmelzen wir zwei Bäume der Höhe h−1 mit je genau 2h−1 Knoten. Da der eine in den anderen eingehängt wird, wächst die Höhe um eins. Somit hat der neue Baum genau Höhe h und 2·2h−1 = 2h Knoten. Die Anzahl benötigter Union-Operationen ist durch folgende rekursive Gleichung gegeben: u(0) = 0, u(h) = 2 · u(h − 1) + 1. (1) Damit ist die Anzahl benötigter Union-Operationen genau u(h) = Ph i=1 2 i−1 = 2h − 1. Mit weniger Operationen kann man unmöglich einen Baum mit 2h Knoten erzeugen, da für jeden zusätzlichen Knoten im Baum mindestens eine Union-Operation nötig ist. Da ein Baum der Höhe h auch mindestens 2h Knoten enthalten muss, ist es unmöglich eine Höhe von h mit weniger als 2h − 1 Operationen zu erreichen. Lösung 9.3 Minimale Spannbäume. a) Das Verfahren von Kruskal berechnet den folgenden minimalen Spannbaum. A 4 6 3 1 B D 7 2 C F 4 3 5 E 5 1 G b) Wir beweisen die Aussage durch vollständige Induktion über die Knotenanzahl |V |. Induktionsverankerung (|V | = 1): Ein Graph mit genau einem Knoten hat keine Kante, also muss |E| = 0 ≤ 1 − 1 = |V | − 1 gelten. Induktionsannahme: Angenommen, jeder ungerichtete kreisfreie Graph mit genau |V | − 1 Knoten hat höchstens |V | − 2 Kanten. Induktionsschluss (|V | − 1 → |V |): Betrachte nun einen ungerichteten kreisfreien Graphen G = (V, E). Aufgrund der Kreisfreiheit gibt es mindestens einen Knoten w mit Grad 0 oder 1. Seien nun V 0 := V \{w} und E 0 := {{u, v} ∈ E | u, v ∈ V 0 }. Offenbar ist |V 0 | = |V | − 1 und, da von w höchstens eine einzige Kante ausgeht, |E 0 | ≥ |E| − 1 (d.h., |E| ≤ |E 0 | + 1). Nach Induktionsvoraussetzung ist |E 0 | ≤ |V 0 | − 1, also erhalten wir |E| ≤ |E 0 | + 1 ≤ |V 0 | − 1 + 1 = |V 0 | = |V | − 1. (2) c) Sei G = (V, E, w) ein ungerichteter Graph, bei dem keine zwei Kanten e, e0 ∈ E das gleiche Gewicht haben. Angenommen, G hätte zwei minimale Spannbäume T1 = (V, E1 ) und T2 = (V, E2 ). Die Kanten aus E1 ∩E2 kommen sowohl in T1 als auch in T2 vor. Seien E10 = E1 \E2 und E20 = E2 \E1 die Kanten, die ausschliesslich in T1 bzw. in T2 enthalten sind. Da die Gewichte aller Kanten paarweise verschieden ist, enthält die Menge E10 ∪· E20 eine eindeutig bestimmte Kante e⊥ minimalen Gewichts. Sei diese o.B.d.A. in T1 enthalten. Würde sie T2 hinzugefügt, dann enthielte T2 nach Aufgabenteil b) einen Kreis. Dieser enthält mindestens 2 eine Kante e0 ∈ E20 (ansonsten hätte er neben e⊥ ausschliesslich Kanten aus E1 ∩ E2 und wäre auch in T1 ein Kreis). Wird e0 aus T2 entfernt und stattdessen e⊥ eingefügt, dann erhalten wir eine Teilmenge von Kanten, die noch immer kreisfrei und zusammenhängend (also ein Spannbaum) ist, wegen w(e⊥ ) < w(e0 ) aber ein geringeres Gesamtgewicht hat. Also war T2 kein minimaler Spannbaum, was der Annahme widerspricht. Lösung 9.4 Schwarze Löcher. Dieses Problem kann analog zum Problem “Star in einer Menge finden” gelöst werden, welches in der ersten Vorlesung vorgestellt wurde. Wir beobachten zunächst, dass es maximal ein schwarzes Loch geben kann: Sei v ein schwarzes Loch. Nach Definition ist der Ausgangsgrad von v genau 0. Also ist (v, v) ∈ / E. Da der Eingangsgrad genau |V | − 1 beträgt, gibt es für alle v 0 ∈ V \{v} eine Kante (v 0 , v) ∈ E und der Ausgangsgrad jedes Knotens v 0 ∈ V \{v} beträgt mindestens 1. Damit kann v 0 kein weiteres schwarzes Loch sein. Der Algorithmus besteht aus zwei Phasen. Zunächst finden wir einen Kandidaten v für ein schwarzes Loch mit |V |−1 Vergleichen. Danach prüfen wir mit 2|V |−1 Vergleichen, ob v wirklich ein schwarzes Loch ist. Sei G = (V, E) mit V = {v1 , ..., vn } und der zugehörigen Adjazenzmatrix AG = (aij ) mit aij ∈ {0, 1}, aij = 1 ⇔ (vi , vj ) ∈ E, gegeben. Zur Bestimmung eines Kandidaten benutzen wir zwei Variablen i und j, die mit i ← 1 und j ← n initialisiert werden. Danach wird der Eintrag aij geprüft. Ist aij = 0, dann ist (vi , vj ) ∈ / E und vj sicher kein schwarzes Loch (denn der Eingangsgrad von vj ist kleiner als n − 1, falls (vj , vj ) ∈ / E). Also setzen wir j ← j − 1. Ist dagegen aij = 1, dann ist (vi , vj ) ∈ E und vi kein schwarzes Loch (denn der Ausgangsgrad von vi ist mindestens 1). Also setzen wir i ← i + 1. Nach n − 1 solchen Vergleichen ist i = j und vi wird unser Kandidat für ein schwarzes Loch. Nun prüfen wir noch, ob vi wirklich ein schwarzes Loch ist, indem wir den Eingangsgrad und den Ausgangsgrad von vi berechnen. Dazu müssen wir lediglich prüfen, ob aij = 0 für j = 1, ..., n und aji = 1 für j = 1, ..., n, j 6= i, sind. Ist dies der Fall, dann ist vi das gesuchte schwarze Loch. Ansonsten besitzt G kein schwarzes Loch. Die Korrektheit des Verfahrens folgt aus der Beobachtung, dass die Zeiger i bzw. j nur dann aktualisiert werden, wenn vi bzw. vj kein schwarzes Loch sind. Ist umgekehrt i so gesetzt, dass vi ein schwarzes Loch ist, dann ist aij = 0 für alle j = 1, ..., n, also wird i niemals vergrössert (sondern nur noch j verkleinert). Analog wird j niemals verkleinert, wenn vj ein schwarzes Loch ist, da in diesem Fall aij = 1 ist für alle i = 1, ..., n, i 6= j. 3