Union-Find (Algorithmus 1.5)
Dynamische Zusammenhangskomponenten aus Sedgewick/Wayne, Algorithmus 1.5 (Listing 1.54) – Quick-Find, Quick-Union und gewichtetes Quick-Union zum Umschalten, find und union Schritt für Schritt mit Bäumen, id[] und Arrayzugriffen, dazu der Testclient mit tinyUF.txt.
So geht’s: Oben die Variante wählen: Quick-Find, Quick-Union oder gewichtetes Quick-Union. Beim Öffnen und bei jedem Wechsel ist tinyUF.txt geladen und läuft mit „Abspielen“ oder „Schritt“ durch den Testclient. Für jedes Paar fragt er erst connected, nur wenn nötig folgt union, und unten wächst die Ausgabe wie im Buch bis „2 components“. Die Bäume zeigen, wer an wem hängt, darunter steht dasselbe als id[] (bei der gewichteten Variante auch sz[]). Beim Schritt für Schritt sieht man find von Knoten zu Knoten zur Wurzel laufen. Der Zähler nennt die Arrayzugriffe, das Kostenmaß des Buchs. Am deutlichsten wird der Unterschied beim „Schlimmsten Fall“: Mit Quick-Union entsteht daraus eine lange Kette, gewichtet bleibt der Baum flach. Eigene Paare gehen mit union(p, q) und connected(p, q), „↺ Neu starten“ setzt die gewählte Variante zurück. Die Fortsetzung mit Abbildung 1.61 und Satz H steht unter Gewichtetes Quick-Union.
Erklärung
Union-Find (Algorithmus 1.5) löst das Problem der dynamischen Zusammenhangskomponenten: Es kommen Paare p q rein, die „verbunden“ werden. Die Frage ist jederzeit: Sind zwei Knoten über irgendeine Kette miteinander verbunden? Beispiele: Rechner im Netz, Pixel in einem Bild, Personen in einem sozialen Netz.
Das Gerüst:
id[]: pro Knoten eine Komponenten-ID. Anfangsid[i] = i, jeder Knoten ist seine eigene Komponente.count: Anzahl der Komponenten, startet bei N und sinkt bei jeder echten Vereinigung um 1.connected(p, q): einfachfind(p) == find(q).- Invariante:
find()liefert für alle Knoten einer Komponente denselben Wert.union()muss das aufrechterhalten.
Der Testclient: Liest N, dann Paare. Sind sie schon verbunden, wird das Paar ignoriert. Sonst union und Ausgabe. Bei tinyUF.txt (10 Knoten, 11 Paare) werden 8 Verbindungen ausgegeben, 3 Paare waren schon über Umwege verbunden. Ergebnis: 2 Komponenten.
Die drei Varianten für find/union:
Quick-Find (S. 245):
id[p]ist direkt die Komponente.findist ein Arrayzugriff, also konstant.unionmuss aber das ganze Array durchlaufen und alle IDs der einen Komponente umschreiben, also N pro Vereinigung. Bei N Vereinigungen quadratisch, für große Eingaben unbrauchbar.Quick-Union (S. 247):
id[p]zeigt auf einen Elternknoten, jede Komponente ist ein Baum.findläuft hoch bis zur Wurzel (id[p] == p).unionhängt einfach eine Wurzel unter die andere, eine einzige Zuweisung. Problem: Die Bäume können zu langen Ketten entarten, dann kostetfindwieder N.Gewichtetes Quick-Union (S. 252): Zusätzliches Array
sz[]mit der Baumgröße. Beiunionwird immer der kleinere Baum unter den größeren gehängt. Dadurch bleibt die Höhe höchstens lg N, alsofindundunionlogarithmisch. Damit gehen auch Millionen Knoten problemlos.
Kostenüberblick:
| Variante | find | union |
|---|---|---|
| Quick-Find | 1 | N |
| Quick-Union | Baumhöhe (bis N) | Baumhöhe |
| Gewichtet | lg N | lg N |
Noch besser wird es mit Pfadkomprimierung (jeder Knoten auf dem Weg zur Wurzel wird direkt an die Wurzel gehängt), dann ist es praktisch konstant. Das steht im Buch als Übung.