zur Implementierung von Kompressionsalgorithmen Suffix-Bäume als Implementierung des Wörterbuchs Effiziente Algorithmen und Komplexitätstheorie Suffix-Baum = kompaktierter Suffix-Trie Suffix-Trie = Trie aller Suffixe Trie = Baum, Kanten mit Buchstaben beschriftet ∀ Knoten ∀ ausgehende Kanten: Beschriftungen verschieden, mit Knoten w Konkatenation der Kantenbeschriftungen w assoziiert Vorlesung Thomas Jansen 03.07.2006 Suffix-Tries im Worst Case quadratische Größe, darum Suffix-Bäume 1 Suffix-Bäume 2 Begriffe für den Algorithmus von Ukkonen I Kanten mit Zeichenketten ∈ Σ+ beschriftet I I (Trie: Beschriftungen verschieden) Anfangsbuchstaben verschieden) X = x1 x2 · · · xn ∈ Σn = Zeichenkette, für die Suffix-Baum konstruiert wird I I für Speicherplatz: statt Zeichenkette xi · · · xj nur Indizes (i, j) an Kanten schreiben I Größe ≤ 2n Ti = Suffix-Trie für x1 x2 · · · xi Tei = Suffix-Baum für x1 x2 · · · xi Phase i = Konstruktion von Tei aus Tg i−1 I Ziel Konstruktion in linearer Zeit auf linearem Platz I (Baum: I I I Endknoten = sl , so dass ∀sj mit j < l xi -Kante angehängt werden muss dafür (schon bei Suffix-Tries) Suffix-Links I aktiver Knoten = sk , so dass ∀sj mit j < k sj Blatt ist I Suffix-Link(aW ) = Zeiger auf W I I für Algorithmus künstliche Wurzel ⊥ mit Suffix-Link(ε)=⊥ Änderungen nur zwischen Endknoten und aktivem Knoten erforderlich Knoten in Ti explizit, wenn auch in Tei vorhanden, sonst I effiziente Konstruktion von Suffix-Bäumen Ukkonen (1995): On-line construction of suffix trees implizit 3 4 Referenzen Vorbereitung des Algorithmus von Ukkonnen Lemma 217 I (xj xj+1 · · · xk−1 , (k, p)) Referenz von xj xj+1 · · · xp , wenn xj xj+1 · · · xk−1 ∈ Tei , p ≤ i I Referenzen nicht eindeutig I Referenz heißt kanonisch, wenn p − k minimal e Sei X = x1 x2 · · · xn ∈ Σn , Tg i−1 Suffix-Baum für x1 · · · xi−1 , Ti Suffix-Baum für x1 · · · xi für i ∈ {1, . . . , n}. Ist (s, (k, i − 1)) eine Referenz in Tg i−1 auf den Endknoten, dann e ist (s, (k, i)) eine Referenz in Ti auf den aktiven Knoten. I kanonische Referenzen nicht eindeutig hilft aktiven Knoten für nächste Phase zu finden I (xj xj+1 · · · xk−1 , (k, ∞)) offene Referenz von xj xj+1 · · · xn , wenn xj xj+1 · · · xk−1 ∈ Tei Lemma 218 I Sei X = x1 x2 · · · xn ∈ Σn , sei aW ∈ X mit a ∈ Σ, sei aW innerer Knoten im Suffix-Baum T für X . W ist innerer Knoten in T . Kanten zu Blättern mit offenen Referenzen beschriftet brauchen keine Änderungen garantiert Existenz der Suffix-Links 6 5 Der Algorithmus von Ukkonen Zentrale Prozedur Update Eingabe Referenz (s, (k, p)) des aktiven Knotens, Phase i Ausgabe Endknoten in Tg i−1 1. w := ε 2. (s, k) := Kanonisiere(s, (k, p)) 3. (fertig, r ) := TesteUndTeile(s, (k, p), xi ) 4. So lange nicht fertig 5. Erzeuge neuen Knoten m, Kante (r , m) mit Beschriftung (i, ∞) 6. Falls w 6= ε 7. suflink(w ) := r 8. w := r 9. (s, k) := Kanonisiere(suflink(s), (k, p)) 10. (fertig, r ) := TesteUndTeile(s, (k, p), xi ) 11. Falls w 6= ε 12. suflink(w ) := s 13. Ausgabe (s, k) Σn ) KonstruiereSuffixBaum(X = x1 · · · xn ∈ S 1. T := {⊥, ε, v }, {(ε, v )} ∪ {(⊥, ε)} x∈Σ 2. 3. 4. 5. 6. sufLink(ε) := ⊥ s := ε k := 2 Für i ∈ {2, 3, . . . , n} (s, k) := Update(s, (k, i − 1), i) Beobachtungen f1 konstruiert I initial T I (s, (k, i − 1)) ist immer aktiver Knoten in Tg i−1 I Korrektheit und Laufzeit hängt ganz von Update ab 7 8 Beispiel Algrithmus von Ukkonen BBAA BA A A B B BAA ⊥ ε A A, B A B A AA {⊥, ε, v}, {(ε, v)} ∪ KonstruiereSuffixBaum(X = ABABBAA, n = 7) := 10 S {(⊥, ε)} Laufzeit durch Kanonisiere x∈Σ Laufzeit des Algorithmus von Ukkonen suflink(ε) := ⊥ s := ε k := 2 Für i ∈ {2, 3, . . . , n} –(s, k) := Update(s, (k, i − 1), i ) 9 BAA ABBAA1. T 2. 3. 4. 5. 6. s 0. BBAA AB ABBAA ABABBAA ···xp |+1 ···w|w | A w|x k =6 i =7 11. ···xp | n=7 s = w1 w2 ···w|x Σ = {A, B}, X = ABABBAA fertig Eingabe Referenz (s, (k, p)) des aktiven Knotens, neues Zeichen x Ausgabe falsch“ und Knoten, an den xi angehängt werden muss ” oder wahr“ ” 1. Falls |xk xk+1 · · · xp | = 0 2. Falls x-Kante aus s existiert 3. Dann Ausgabe (wahr, s). STOP 4. Sonst Ausgabe (falsch, s). STOP 5. Sonst w 6. Sei e Kante s → s 0 mit w1 = xk . 7. Falls x = w|xk xk+1 ···xp |+1 8. Dann Ausgabe (wahr, s). STOP 9. Sonst w 10. Teile e = s → s 0 wie folgt aus: s −→k m k −→ Ausgabe (falsch, m) BABBAA Hilfsprozedur TesteUndTeile ! Beobachtung jeder Aufruf von Kanonisiere von TesteUndTeile-Aufruf gefolgt Wir wollen zeigen lineare Laufzeit bei geeigneter Implementierung also Kanonisiere-Aufrufe müssen nicht gezählt werden klar Σ ist konstant Beobachtungen I Laufzeit proportional zur Anzahl betrachteter Knoten I es genügt, Knoten in Kanonisiere und TesteUndTeile zu zählen I k wird nirgendwo gesenkt Beobachtung in Kanonisiere für jeden weiteren Knoten wird k erhöht Erinnerung k sinkt nie und steigt nicht über n + 1 also Rechenzeit in Kanonisiere = O(Rechenzeit in TesteUndTeile + n) 11 12 Laufzeit durch TesteUndTeile Zur Implementierung von Suffix-Bäumen unterscheide Aufrufe mit Ergebnis wahr und falsch unsere Überlegungen setzen voraus wahr-Aufrufe lassen Update enden beim Verlassen von Update endet Phase i also Aufwand durch wahr-Aufrufe von TesteUndTeile = O(n) I Platz je Knoten O(1) I Zeit je Zugriff auf xi -Kind O(1) Wie realisiert man das? Implementierung mit Arrays Beobachtung in falsch-Aufruf von TesteUndTeile wächst Suffix-Baum I Platz je Knoten O(|Σ|) = O(1) I Zugriff je xi -Kind O(1) Erinnerung Suffix-Baum hat Größe ≤ 2n + schnell also Aufwand durch falsch-Aufrufe von TesteUndTeile = O(n) − O(|Σ|) je Knoten sehr groß also insgesamt Rechnzeit und Speicherplatz O(n) 14 13 Nicht-triviale Implementierung von Suffix-Bäumen Nicht-triviale Implementierung von Suffix-Bäumen (2) Idee Verwalte Kinder aller Knoten in einem Array, Größe O(n) Implementierung mit linearen Listen I Platz je Knoten O(|Kinder|) = O(1) I Zugriff je xi -Kind O(|Kinder|) = O(1) Zugriff auf xi -Kind des Knotens v durch Hashfunktion h : V × Σ → N0 klar |V | · |Σ| = Θ(n · |Σ|) Hashtabelle also Hashing-typische Probleme mit Kollisionen + wesentlich platzeffizienter − langsam Anmerkung mit vernünftiger Hashing-Implementierung Zeit O(1) bei Platz O(n) mit W’keit dicht bei 1 machbar Implementierung mit balancierten Bäumen (z. B. AVL-Bäume) I Platz je Knoten O(|Kinder|) = O(1) I Zugriff je xi -Kind O(log |Kinder|) = O(1) Anmerkung in Suffix-Bäumen meist große Verzweigungsgrade nur nah bei der Wurzel + wesentlich platzeffizienter − etwas langsamer als mit Arrays naheliegend hybride Implementierung mit Arrays wurzelnah, sonst nicht-triviale Implementierung 15 16