SuffixbaumEin Suffixbaum ist in der Informatik eine vielseitige Datenstruktur, die effiziente Lösungen zahlreicher Probleme im Bereich der Stringverarbeitung ermöglicht. Ein Suffixbaum speichert alle Suffixe (Endungen) einer Zeichenkette. Er kann in linearer Zeit mit linearem Speicherverbrauch aufgebaut werden und ermöglicht, einmal aufgebaut, das schnelle Durchführen vieler Operationen wie die Suche nach Wörtern oder Sätzen in längeren Texten. DefinitionEin Suffixbaum für einen String mit Symbolen ist ein gerichteter Baum, der die folgenden Bedingungen erfüllt:
Ein solcher Baum kann nicht für Strings erzeugt werden, die ein Suffix enthalten, das auch ein Präfix von einem anderen Suffix ist. Beispielsweise ist das Suffix Die String-Tiefe eines Knotens gibt an, wie viele Zeichen die kombinierten Kantenbeschriftungen auf dem Weg von der Wurzel zum Knoten haben. Für ein Blatt entspricht das der Länge des Suffixes, das es repräsentiert. KonstruktionDer Baum besteht zu Anfang aus einem Wurzelknoten und einer Liste von (Zeigern auf) allen fortsetzbaren Stellen im Baum, das heißt zu Anfang aus lediglich einem Zeiger auf den Wurzelknoten. Der Baum wird für ein schrittweise zu verlängerndes Wort aufgebaut. Für alle Knoten aus der Liste der fortsetzbaren Stellen wird eine Kante zu einem neuen Knoten gezogen. Diese Kante wird mit dem zuletzt an das Wort angefügten Buchstaben beschriftet. Dieser neue Knoten wird dann in eine Liste der fortsetzbaren Stellen für die nächste Iteration aufgenommen. Zuletzt wird immer auch der Startknoten mit in die neue Liste aufgenommen (da das leere Wort immer ein gültiges Suffix ist). Existiert zu einer fortsetzbaren Stelle bereits eine Kante, deren Beschriftung dem zuletzt angefügten Buchstaben entspricht, so wird kein Knoten hinzugefügt, und stattdessen der bereits existierende Zielknoten in die Liste der fortsetzbaren Stellen aufgenommen. Durch das Hinzufügen des Startknotens in jedem Schritt ist die Liste der fortsetzbaren Stellen nach Iterationen auch Elemente lang, was in quadratischer Laufzeit resultiert. Mithilfe des Suffixarrays und des LCP-Arrays ist es möglich, den Baum auch in linearer Zeit aufzubauen. Dafür werden die Suffixe in der Reihenfolge des Suffixarrays eingefügt. Sei der partielle Suffixbaum, der die ersten Suffixe enthält und das LCP-Array. Der Algorithmus beginnt mit , also nur der Wurzel des Baums. Im Schritt wandern wir den rechtesten Pfad (also den Pfad von dem Blatt mit dem Label bis zur Wurzel) von so lange herauf, bis wir den tiefsten Knoten mit der String-Tiefe finden. Wenn , wird einfach ein neues Blatt als Kind von eingefügt. Dieses Blatt hat das Label . Damit erhalten wir und gehen zum nächsten Schritt. Wenn hingegen gilt, also das neue Suffix innerhalb einer Kante von zum rechtesten Kind abzweigt, muss die bestehende Kante aufgespalten werden. Dazu wird erst die Kante gelöscht. Dann wird ein neuer Knoten eingefügt, mit einer Kante , die den Teil der Kantenbeschriftung bekommt, der direkt vor der Stelle liegt, an der aufgeteilt werden muss (formal: ). Dann wird die Kante eingefügt, die den restlichen Teil der ursprünglichen Kantenbeschriftung erhält (formal: ). Als Letztes wird ein neues Blatt (mit Label ) und eine Kante eingefügt. Die Kante bekommt den Teil des neuen Suffixes als Label, der bisher nicht von abgedeckt wird (formal: ). Obwohl das Heraufwandern auf dem rechten Pfad in einem einzigen Schritt Zeit in Anspruch nehmen könnte, ist die Laufzeit dieses Algorithmus durch insgesamt begrenzt, denn: Jeder im Schritt durchquerte Knoten (außer dem letzten) wird vom rechten Pfad entfernt und wird für alle nachfolgenden Schritte nicht mehr betrachtet. Es werden also insgesamt höchstens Knoten durchlaufen.[2] AnwendungenSuffixbäume ermöglichen eine effiziente Lösung zahlreicher Probleme im Bereich der Stringverarbeitung. Eine verbreitete Anwendung besteht in der Implementierung von String-Matching-Algorithmen in Suchmaschinen aufgrund der geringen Laufzeitkomplexität. Für die Fuzzy-String-Suche ermöglicht ein Suffixbaum effiziente Verfahren, wie beim Vergleich mit wildcards oder k-mismatch[3] oder in Form einer Beschleunigung der Ermittlung der Levenshtein-Distanz über hybrides dynamic programming[4]. Weitere Anwendungsgebiete sind in der Bioinformatik, u. a. in der DNA-Sequenzanalyse, und in der Datenkompression, zur Identifizierung von sich wiederholenden Daten, zu finden. Suchen in SuffixbäumenEin Suchtext kann im Suffixbaum gefunden werden, indem der Baum von der Wurzel an Buchstabe für Buchstabe heruntergewandert wird. Dabei werden maximal Zeichen verglichen. Wenn an jedem Knoten die ausgehenden Kanten lexikografisch sortiert sind, kann die richtige Kante mit binärer Suche in Zeit gefunden werden, wobei die Anzahl der ausgehenden Kanten ist. Dadurch ergibt sich eine Laufzeit von . Durch gewichtete binäre Suche kann diese Lautzeit auf verbessert werden, wobei das Gewicht der Kanten durch die Anzahl der Blätter, die durch diese Kante erreicht werden können, bestimmt wird. Um herauszufinden, wie oft der Suchtext im String vorkommt, wird so lange traversiert, bis komplett eingelesen wurde. Dies bringt uns zu einem Knoten oder einer eingehenden Kante zu Knoten . Die Anzahl der Blätter im Subbaum , mit als Wurzel, gibt an, wie oft in vorkommt. Wenn diese Information in jedem Knoten mitgespeichert wird, kann dies in konstanter Zeit geschehen. Um die Stellen zu finden, an denen der Suchtext vorkommt, wird wieder Knoten gesucht und dann für alle Blätter von die Stelle im Text ausgegeben, die sie repräsentieren. GeschichteDas Konzept wurde erstmals von Weiner (1973)[5] eingeführt. Anstelle des Suffixes speicherte Weiner in seinem Trie den Präfixbezeichner für jede Position, d. h. die kürzeste Zeichenkette, die bei beginnt und nur einmal in vorkommt. Sein Algorithmus D nimmt einen unkomprimierten Trie für und erweitert ihn zu einem Trie für . Auf diese Weise kann, ausgehend vom trivialen Trie für , ein Trie für durch aufeinanderfolgende Aufrufe von Algorithmus D aufgebaut werden; die Gesamtlaufzeit beträgt jedoch . Weiners Algorithmus B benutzt mehrere Hilfsdatenstrukturen, um eine Gesamtlaufzeit zu erreichen, die linear zur Größe des konstruierten Trie ist. Letztere kann immer noch aus Knoten bestehen, z. B. für . Weiners Algorithmus C verwendet schließlich komprimierte Tries, um eine lineare Gesamtspeichergröße und Laufzeit zu erreichen. Das Lehrbuch Aho, Hopcroft & Ullman (1974, Kap. 9.5)[6] gab Weiners Ergebnisse in vereinfachter und eleganterer Form wieder und führte den Begriff Positionsbaum ein. McCreight (1976)[7] war der Erste, der einen (komprimierten) Trie aller Suffixe von S erstellte. Obwohl das bei beginnende Suffix in der Regel länger ist als der Präfixbezeichner, unterscheiden sich ihre Pfaddarstellungen in einem komprimierten Trie nicht in der Größe. Andererseits konnte McCreight auf die meisten der Weiner'schen Hilfsdatenstrukturen verzichten; es blieben nur die Suffix-Links übrig. Ukkonen (1995)[8] vereinfachte die Konstruktion weiter[9] und lieferte die erste Online-Konstruktion von Suffixbäumen, die heute als Ukkonen-Algorithmus bekannt ist, mit einer Laufzeit, die den damals schnellsten Algorithmen entspricht. Diese Algorithmen laufen alle in linearer Zeit für ein Alphabet konstanter Größe und haben sonst im schlimmsten Fall eine Laufzeit von . Farach (1997)[10] gab den ersten Algorithmus zur Konstruktion von Suffixbäumen an, der für alle Alphabete optimal ist. Insbesondere ist dies der erste Algorithmus mit linearer Zeit für Zeichenketten, die aus einem Alphabet von ganzen Zahlen in einem polynomialen Bereich gezogen werden. Farachs Algorithmus wurde zur Grundlage für neue Algorithmen zur Konstruktion von Suffixbäumen und Suffixarrays. Siehe auchLiteratur
WeblinksCommons: Suffix tree – Sammlung von Bildern, Videos und Audiodateien
Einzelnachweise
|