Effiziente Konstruktion eines Suffix-Baumes (Ukkonen 1992) Definition Einen impliziten Suffix-Baum für ein Wort w erhält man, indem man den Suffix-Baum für w$ erzeugt und dann der Reihe nach 1. das Symbol $ aus allen Kantenmarkierungen entfernt 2. jede Kante entfernt, die eine -Markierung hat und 3. unter Zusammenfassen der Markierungen jeden Knoten ungleich der Wurzel mit Ausgangsgrad 1 entfernt. Einen impliziten Suffix-Baum eines Präfixes w[1 .. i] von w erhält man durch Anwendung der obigen Regeln auf den Suffix-Baum für w[1 .. i]$. Bemerkung: • Im impliziten Suffix-Baum gibt es weniger als |w$| Blätter. • Jeder Suffix von w ist Präfix einer Zeichenkette, die durch einen Knoten im Baum repräsentiert wird. Im folgenden bezeichne Ti den impliziten Suffix-Baum für w[1 .. i]. Die Idee des Algorithmus: • T1 besteht aus einer Wurzel und einem Blatt, das mit 1 markiert ist. Die Kantenmarkierung ist w[1]. • Ukkonens Algorithmus arbeitet in Phasen. In Phase i, 1 < i ≤ n, wird aus dem impliziten Suffix-Baum Ti−1 der Baum Ti für das Präfix w[1 .. i] konstruiert. • Die Phase i ist in i Erweiterungsschritte unterteilt, die in der Reihenfolge von 1 bis i durchgeführt werden. • Im Erweiterungsschritt j der Phase i wird das Suffix w[j .. i] von w[1 .. i] in den Baum Ti−1 eingefügt. Man sucht also das Ende eines Pfads von der Wurzel, der w[j .. i − 1] repräsentiert (dieses Wort wurde in Phase i − 1 eingesetzt!) und erweitert dann um das Symbol w[i]. • Aus Tn erzeugt man dann den Suffix-Baum für w$. Das Prinzip des Ukkonens Algorithmus: Konstruiere T1 . for i = 2 to n do /* n = |w| */ begin /* Phase i */ for j = 1 to i do begin /* Erweiterungsschritt j in der Phase i */ /* Finde Weg von der Wurzel, der w[j..i-1] repräsentiert. */ /* Dann erweitere man, falls notwendig, den Baum so, */ /* dass auch w[j..i] repräsentiert wird. */ end end Regeln zur Suffix-Erweiterung Nachdem man den w[j .. i − 1] repräsentierenden Weg von der Wurzel aus gefunden hat, muss der Baum so erweitert werden, dass er auch w[j .. i] repräsentiert. (Erweiterung j in der Phase i.) Regel 1 Der Weg endet in einem Blatt. Dann erweitere man die Markierung der letzten Kante des Weges um w[i]. Regel 2 Der Weg endet so, dass es keine Fortsetzung mit w[i] im Baum gibt, d.h. (i) er endet in einem internen Knoten und keine Kantenmarkierung zu einem Nachfolgerknoten beginnt mit w[i] oder (ii) er endet innerhalb“ einer Kantenmarkierung und der nachfolgende Buchstabe ist ” ungleich w[i]. Man erzeugt in beiden Fällen ein neues Blatt mit Markierung j und fügt es (i) mit einer durch w[i] markierten Kante als direkten Nachfolger ein oder (ii) über einen neu erzeugten internen Knoten mit dem Blatt als direkten Nachfolger in“ die Kante ein. ” Regel 3 Der Weg endet so, dass es eine Fortsetzung mit w[i] im Baum gibt. Dann muss nichts getan werden. Satz Die naive Implementation des Ukkonen-Verfahrens benötigt O(|w|3 ) Schritte. Definition von Suffix-Links Sei aβ eine Zeichenkette mit a ∈ Σ, β ∈ Σ∗ . Ist u ein interner Knoten ungleich der Wurzel im impliziten Suffix-Baum mit ρ(u) = aβ, und existiert ein weiterer Knoten v mit ρ(v) = β, dann wird ein Zeiger von u auf v als Suffix-Link von u bezeichnet. Man schreibt auch v = s(u). Lemma Wird ein neuer interner Knoten in der Erweiterung j einer Phase i in den Baum eingesetzt und repräsentiert dieser Knoten aβ, a ∈ Σ, β ∈ Σ∗ , dann gibt es im Baum einen Knoten v mit ρ(v) = β oder in der Erweiterung j + 1 derselben Phase wird ein neuer interner Knoten v 0 mit ρ(v 0 ) = β eingesetzt. Korollar Jeder in Ukkonens Algorithmus neu erzeugte interne Knoten besitzt spätestens nach der folgenden Erweiterung einen Suffix-Link. Korollar Für jeden impliziten Suffix-Baum Ti gilt: Ist v ein interner Knoten ungleich der Wurzel mit ρ(v) = aβ, a ∈ Σ, β ∈ Σ∗ , dann existiert ein Knoten s(v) im Baum mit ρ(s(v)) = β. Ablauf der Phase i unter Verwendung der Suffix-Links: Der erste Erweiterungsschritt setzt w[1 .. i] in den Baum Ti−1 ein. Da dies die momentan längste repräsentierte Zeichenkette im Baum ist, endet die Suche im Blatt und die Erweiterung muss mit Regel 1 vorgenommen werden. Hat man einen globalen Zeiger auf das Blatt mit Markierung 1, das ja in Ti−1 das Wort w[1 .. i − 1] repräsentiert, kann der erste Erweiterungsschritt in jeder Phase in konstanter Zeit duchgeführt werden. Dann lassen sich die Erweiterungsschritte j mit 2 ≤ j ≤ i wie folgt durchführen: 1. Finde den ersten Knoten v am Ende oder oberhalb von w[j − 1 .. i − 1], der entweder die Wurzel oder aber ein interner Knoten ist, der einen Suffix-Link besitzt. Sei w[j − 1 .. i − 1] = ρ(v)γ, γ ∈ Σ∗ . 2. Ist v die Wurzel, suche das Ende von w[j .. i − 1] im Baum. Ist v keine Wurzel, folge dem Suffix-Link zum Knoten s(v) und suche von dort aus das Ende von γ. 3. Wende die der Situation entsprechende Erweiterungsregel an, so dass w[j .. i] im Baum repräsentiert wird. 4. Wurde im vorangehenden Erweiterungsschritt j − 1 durch Regel 2 ein neuer interner Knoten ũ erzeugt, so folgt, dass w[j .. i − 1] am Knoten s(ũ) endet. Erzeuge Suffix-Link ũ → s(ũ). Trick 1 - der skip/count - Trick Ist man einem Suffix-Link gefolgt, so muss man von dort aus das Ende einer Zeichenkette γ suchen. Dies benötigt bei direkter Implementation O(|γ|) Schritte. Da das Suffix w[j .. i − 1] im Baum Ti−1 repräsentiert ist, kann man aber ausgehend vom Knoten s(v) eine Kante mit Markierung γ 0 suchen, die mit dem gleichen Buchstaben wie γ beginnt. Ist |γ 0 | < |γ|, dann müssen die restlichen Zeichenvon γ 0 nicht verglichen werden! Dieser Schritt wird iteriert, bis man das Ende vom Suffix w[j .. i − 1] erreicht hat. Lemma Sei v → s(v) ein Suffix-Link, dem im Ukkonen Algorithmus gefolgt wird. Zu diesem Zeitpunkt gilt Stufe(v) ≤ Stufe(s(v)) + 1 Satz Mit dem skip/count-Trick benötigt jede Phase des Ukkonen-Algorithmus O(n) Zeiteinheiten, wobei n = |w| ist. Trick 2 - Abbruch bei Regel 3 Wenn immer man in einer Phase beim Erweiterungsschritt j Regel 3 anwendet, kann diese Phase beendet werden. Trick 3 - Implizites Ausführen von Regel 1 Ein einmal erzeugtes Blatt mit Markierung j wird nie verändert! Also muss in jeder SuffixErweiterung j einer Phase i die Regel 1 angewendet werden. Um die Anwendung nicht jedesmal durchführen zu müssen, ersetzt man die Kantenmarkierung w[r .. i], kodiert durch (r, i), durch (r, e), wobei e eine globale Größe ist, die das momentane Ende der Zeichenkette angibt und bei jeder Phase um 1 erhöht wird. Damit muss Regel 1 nie mehr ausgeführt werden. Jede Phase besteht also aus einer Anzahl von Erweiterungen nach Regel 1, gefolgt von Erweiterungen nach Regel 2, bei denen jeweils ein neues Blatt erzeugt wird, und dann Erweiterungen nach Regel 3. Trick 4 -Einschränken der Suffix-Erweiterungen Da bei jeder Anwendung von Regel 2 ein Blatt erzeugt wird, merkt man sich also, bis zu welchem Index ji−1 in der vorangegangenen Phase i − 1 Regeln 1 oder 2 angewendet wurden. In der Phase i beginnt man dann mit den Suffix-Erweiterungen ab Schritt ji−1 + 1, bis man bei j 0 erstmalig Regel 3 anwendet oder aber die Phase beendet ist. Phase i endet mit der Kenntnis, wo w[j 0 .. i] in Ti repräsentiert wird. In der Phase i + 1 muss also als erstes das Wort w[j 0 .. i + 1] eingesetzt werden, d.h. man befindet“ sich zu Beginn der ” Phase i + 1 am richtigen“ Knoten, um eine Erweiterung durchzuführen. Damit kann die erste ” Erweiterung in der Phase i + 1 in konstanter Zeit durchgeführt werden. In zwei aufeinander folgenden Phasen gibt es also höchstens 2 explizite Erweiterungen, die den gleichen Index haben. Satz Unter Verwendung von Suffix-Links und den Tricks 1 – 4 konstruiert Ukkonens Algorithmus die impliziten Suffix-Bäume T1 bis Tn in O(n) Schritten. Aus dem impliziten Suffix-Baum Tn für w lässt sich ein Suffix-Baum für w$ leicht erzeugen, indem man mit der Zeichenkette w$ eine weitere Phase des Ukkonen-Algorithmus durchführt. Satz Ukkonens Algorithmus erzeugt einen Suffix-Baum für ein Wort w in O(|w|) Schritten.