ETH Zürich Institut für Theoretische Informatik Prof. Dr. Angelika Steger Florian Meier, Ralph Keusch HS 2017 Algorithmen und Komplexität Lösungsvorschlag zu Übungsblatt 3 Lösungsvorschlag zu Aufgabe 1 Ein Graph ist genau dann bipartit, wenn seine Knoten so mit zwei Farben (rot und blau) färbbar sind, dass keine zwei adjazenten Knoten die gleiche Farbe erhalten. Eine solche 2-Färbung nennen wir zulässig; die Menge der roten Knoten und die Menge der blauen Knoten ist dann gerade die disjunkte Zerlegung V1 ] V2 der Knotenmenge, die in der Definition von „bipartit“ verlangt wird. Beachte, dass so eine Zerlegung in einem zusammenhängenden Graphen eindeutig ist (bis aufs Vertauschen der beiden Mengen). Ist ein Knoten gefärbt müssen alle seine Nachbaren die andere Farbe besitzen und deren Nachbaren wieder die andere Farbe, usw. Wir nehmen zunächst an, dass der Graph zusammenhängend ist. Wir führen ausgehend von einem beliebigen Startknoten eine Breitensuche durch, und färben den Graphen während der Breitensuche ein. Den Startknoten färben wir o.B.d.A. mit der Farbe rot ein. Von nun an haben wir keine Wahl mehr für die Farbe: Wenn wir von einem roten Knoten aus einen Knoten besuchen, müssen wir ihn blau färben, und umgekehrt. Stossen wir also im Laufe des Algorithmus auf einen “Widerspruch”, d.h. wir besuchen z.B. einen bereits rot eingefärbten Knoten von einem ebenfalls rot gefärbten Knoten aus, dann gibt es keine zulässige 2-Färbung, da wir nie die Wahl gehabt hatten irgendeinen Knoten anders zu färben. Wir können die Breitensuche abbrechen mit der Meldung, dass der Graph nicht bipartit ist. Können wir dagegen die Breitensuche komplett durchführen, ohne dass es ein solches Problem gibt, haben wir eine zulässige 2-Färbung gefunden, da wir im Verlauf der Breitensuche für jede Kante überprüft haben, ob sie zwei Knoten verschiedener Farbe verbindet. Der Graph ist also bipartit. Ein Graph ist genau dann bipartit, wenn alle seine Zusammenhangskomponenten bipartit sind. Ist G nicht zusammenhängend, können wir deshalb für jede der Zusammenhangskomponenten einzeln testen, ob sie bipartit ist. Die Laufzeit unseres Algorithmus ist in jedem Fall O(|V | + | E|) (Breitensuche!). Lösungsvorschlag zu Aufgabe 2 Wir beobachten zunächst, dass ein Digraph D = (V, A) keine topologische Ordnung besitzen kann, wenn er Zyklen (d.h. gerichtete Kreise) enthält. Ausserdem beobachten wir, dass jeder zyklusfreie Digraph mindestens einen Knoten ohne ausgehende Kanten enthält. Wir nennen solche Knoten Senken und stellen folgende Behauptung auf. Lemma 1. Sei D = (V, A) ein Digraph und v ∈ V eine Senke in D. Eine topologische Ordnung von D \ {v}, f : V \ {v} → {1, ..., |V | − 1} bildet zusammen mit f (v) := |V | eine topologische Ordnung von D. Beweis. Wir nehmen an, dass es eine Kante (u, w) ∈ A gibt, so dass f (u) > f (w) gilt. Weil f auf V \ {v} eine topologische Ordnung bildet, muss w = v sein. Dies führt aber zum Widerspruch, weil per Definition für alle u ∈ V \ {v} gilt f (u) < f (v) = |V |. 1 Falls D keinen Zyklus enthält können wir also, indem wir sukzessive Senken entfernen und ihnen den grössten verbleibenden Wert zuordnen, eine topologische Ordnung finden. Wir wollen nun diese Idee verwenden um mittels modifizierter Tiefensuche eine topologische Ordnung zu finden. Wir stellen den Status jedes Knoten durch eine Farbe dar: Schwarze Knoten v wurde bereits abgearbeitet, d.h. ihnen wurde bereits ein Wert f [v] zugewiesen. Graue Knoten sind gerade auf dem Stack der Tiefensuche, und weisse Knoten wurden noch nicht betrachtet. Einige Bemerkungen zum Pseudocode: • Mit Γ(v) sind jeweils nur die durch von v ausgehende Kanten erreichbaren Knoten gemeint. • Sind wir an einer Senke v angekommen, so ist Γ(v) leer. Dann kann v keinen durch ausgehende Kante erreichbaren Nachbarn haben, der nicht schwarz wäre, und der Knoten v wird innerhalb der dritten if-Schleife schwarz gefärbt. • Die äussere for-Schleife garantiert, dass die Tiefensuche den ganzen Graphen abarbeitet und der Algorithmus nicht terminiert, bevor allen Knoten ein Wert f [v] zugewiesen wurde. Zur Korrektheit: Es ist klar, dass wir im Lauf einer Tiefensuche früher oder später entweder auf eine Senke des weiss-grauen Restgraphen oder einen bereits besuchten (also grauen) Knoten stossen. Im ersten Fall können wir diesem Knoten wie beschrieben einen Wert zuweisen, im zweiten Fall haben wir einen Zyklus gefunden, d.h. einen Beweis, dass es keine topologische Sortierung von G gibt. Wenn der zweite Fall nie eintritt, sind die Knoten zum Schluss topologisch geordnet. Es ist einfach zu sehen, dass für alle Kanten (u, v) gelten muss, dass f (u) ≤ f (v). Falls f (u) > f (v) dann würde das bedeuten, dass u vor v einen Wert zugewiesen bekommen hätte. Dies ist aber nicht möglich, weil dann v als Nachbar von u zu diesem Zeitpunkt schwarz gefärbt wäre, was zu einem Widerspruch führt. Zur Laufzeit: Die äussere for-Schlaufe geht genau einmal durch alle Knoten. Die Laufzeit von T OP S ORT ergibt sich daher aus derjenigen der Tiefensuche, ist also O(|V | + | E|). Lösungsvorschlag zu Aufgabe 3 Sei L die Menge der Blätter des Baums T = (V, E). Es bezeichne T [V \ L] den Graphen, der entsteht, wenn man von T die Blätter entfernt. Lemma 2. Die Zentren von T und T [V \ L] sind für |V | ≥ 3 identisch. Beweis. Für einen Knoten v ∈ V \ L gilt: Jeder Knoten u mit d(v, u) = ecc(v) ist ein Blatt, da es sonst einen Knoten u0 mit d(v, u0 ) > d(v, u) gäbe. Daraus folgt, dass sich die Exzentrizität von v um mindestens 1 verringert, wenn alle Blätter von T entfernt werden. Andererseits ist klar, dass der Nachbar eines Blatts u mit d(v, u) = ecc(v) immer noch in T [V \ L] ist und sich deshalb die Exzentrizität von v um höchstens 1 verringert. Es gilt also für alle Knoten v ∈ V \ L ecc T [V \ L] (v) = ecc T (v) − 1. Es ist klar, dass für |V | ≥ 3 die Zentren von T keine Blätter sind. Da die Zentren von T bzw. T [V \ L] als Knoten minimaler Exzentrizität bezüglich T bzw. T [V \ L] definiert ist und sich diese Grössen um genau 1 unterscheiden, sind die Zentren von T [V \ L] genau die Zentren von T. Aus obigem Lemma finden wir folgendes iterative Vorgehen, um das Zentrum von T zu bestimmen: Wir entfernen in jeder Phase jeweils alle Blätter von T, d.h. sei Ti der Baum zu Beginn der i-ten Iteration, dann wählen wir Ti+1 = Ti [V ( Ti ) \ L( Ti )]. Wir brechen die Iteration ab, sobald |V ( Tk )| ≤ 2 erreicht ist. V ( Tk ) enthält dann genau die Zentren von T. Die eleganteste Art, diese Idee in einen Algorithmus F IND C ENTER (siehe Pseudocode) umzusetzen, ist, die Blätter in einer Queue zu führen. Wir nehmen an, dass T als Adjazenzlisten gegeben ist und bezeichnen mit T 0 den schrittweise zurechtgestutzten Baum. Während des Algorithmus 2 Algorithm 1 T OP S ORT (Graph G = (V, E)) for all v ∈ V do C OLOR [v] := W HITE end for S := new Stack T IME := |V | for all w ∈ V do if C OLOR [w] = W HITE then v := w repeat if ∃u ∈ Γ(v) mit C OLOR [u] = G REY then return “topological sort doesn’t exist” end if if ∃u ∈ Γ(v) mit C OLOR [u] = W HITE then C OLOR [v] := G REY S.Push(v) v := u end if if ∀u ∈ Γ(v) : C OLOR [u] = B LACK then C OLOR [v] := B LACK f [v] := T IME T IME := T IME − 1 v := S.Pop() end if until v = nil end if end for Gib (v, f [v]) für alle v ∈ V aus lassen wir die Adjazenzlisten von T unverändert (!), zählen aber für jeden Knoten v ∈ V seinen Grad in T 0 in der Variable D EGREE [v] mit. In jedem Durchlauf der while-Schleife wird der vorderste Knoten v der Queue gedanklich aus T 0 entfernt und die Grade seiner Nachbarn entsprechend aktualisiert. (Natürlich hat v als Blatt höchstens einen Nachbarn in T 0 , aber da wir nur die Adjazenzlisten von T haben, müssen wir alle Nachbarn in T durchlaufen, obwohl uns nur einer davon wirklich interessiert!) Sobald ein Nachbar u von v ein Blatt in T 0 wird, wird er in die Queue eingefügt. Nach jeder Iteration sind also die inneren Knoten von T 0 genau die Knoten mit D EGREE [v] ≥ 2 und die Blätter von T 0 genau die Knoten in der Queue. Man beachte, dass die Variable D EGREE [v] im Allgemeinen ins Negative heruntergezählt wird - dies tut nichts zur Sache, da wir ja jeweils nur testen, ob sie genau 1 ist oder nicht, um v im richtigen Moment in die Queue einzufügen. Zur Korrektheit: Man überlegt sich leicht, dass der Algorithmus zuerst alle Blätter von T0 = T entfernt, dann diejenigen von T1 = T [V \ L] usw., so dass der letzte Knoten in der Queue genau ein Zentrum ist. Zur Laufzeit: Die while-Schleife wird |V |-mal ausgeführt. Die darin enthaltene for all-Anweisung wird total höchstens ∑v∈V deg(v) = 2| E|-mal ausgeführt. 2| E| ist auch der Zeitbedarf für die Initialisierung des Arrays D EGREE zu Beginn. Es ergibt sich eine Laufzeit von O(|V | + | E|). (Das ist im Wesentlichen das Argument, mit dem in der Vorlesung und im Skript die Laufzeit der Breitensuche begründet wird.) Da für Bäume zudem | E| = |V | − 1 gilt, ist die Laufzeit von F IND C ENTER O(|V |). 3 Algorithm 2 F IND C ENTER (Tree T = (V, E)) Q := new Queue for all v ∈ V do D EGREE [v] := deg v if D EGREE [v] = 1 then Q.Insert(v) end if end for while Q.NotEmpty do v := Q.Dequeue() for all u ∈ Γ(v) do D EGREE [u] := D EGREE [u] − 1 if D EGREE [u] = 1 then Q.Insert(u) end if end for end while return v 4