Inhaltsverzeichnis 1. Einleitung 2. Bedeutung 3. Implizite Suffixbäume 4. Vorgehensweise 5. Regeln 6. Lineare Laufzeit a. Indizes und Endmarker b. Erweiterungen c. Suffix-Links d. Skip und Count e. Überblick 7. Suffixbaum erzeugen 8. Zusammenfassung Der Algorithmus von Ukkonen Einleitung: Esko Ukkonen entwickelte einen Algorithmus, der es ermöglicht Suffixbäume in linearer Zeit aus einem beliebigen Text der Länge n zu konstruieren. Dabei stellt sich insbesondere die Frage, wie das in dieser Zeit möglich ist. Der Schlüssel dazu ist die Vorgehensweise des Algorithmus von Ukkonen. Dieser konstruiert, um den gewünschten Suffixbaum zu einem gegebenen String S zu erhalten, ständig neue implizite Suffixbäume. Dies bedeutet, dass man sich einen impliziten Suffixbaum konstruiert, aus dem wiederum ein impliziter Suffixbaum konstruiert wird, und dies wird solange fortgesetzt bis man sich im letzten Schritt nur noch aus dem nun gegebenen impliziten Suffixbaum den gewünschten Suffixbaum konstruieren muss. So wird keine lineare Laufzeit erreicht. Wenn man jedoch geschickt vorgeht, dann kann dennoch lineare Laufzeit erreicht werden. Dazu dienen einige Tricks, die in dieser Ausarbeitung ebenfalls noch erklärt werden. Deren Bedeutung für den Ukkonens Algorithmus darf man niemals vergessen! Bedeutung: Linearer Zeitbedarf ist natürlich erwünscht, und da dieser Algorithmus dazu noch auf günstige Art und Weise eingesetzt werden kann, ist Ukkonens Algorithmus auch von praktischer Bedeutung bei der Konstruktion von Suffixbäumen. Beispielsweise benötigt der Algorithmus weniger Speicherplatz als Weiners Algorithmus, d.h., es werden somit auch weniger Schreibaktionen auf dem Speicher ausgeführt, was jedoch wiederum Zeit kostet. Außerdem ist Ukkonens Algorithmus ein „On-line“-Algorithmus, was soviel bedeutet, dass man den Text, zu dem man sich einen Suffix-Baum bauen will, einfach von vorne nach hinten einlesen kann. Dies bedeutet aber wiederum, dass der Verwaltungsaufwand sich auch in Grenzen hält und in diesem Sinne der Algorithmus relativ „einfach“ zu implementieren ist. Implizite Suffixbäume: Definition: Einen impliziten Suffixbaum für einen String S erhält man, indem erstens alle Terminalsymbole $ von den jeweiligen Kantenmarkierungen eines Suffixbaumes für einen String S$ gelöscht werden, zweitens jede Kante, die nun keine Markierung mehr hat, und drittens jeder Knoten, der folglich weniger als zwei Kinder hat, entfernt wird. Beispiel: Wie man in diesem Beispiel auch erkennen kann, wird nach Entfernen eines inneren Knotens, der weniger als zwei Kinder hat, die Markierung der angrenzenden Kanten zu einer einzigen Markierung „zusammengezogen“, welche nun nur noch einer Kante angehört Vorgehensweise: Ukkonens Algorithmus besteht aus insgesamt n Phasen, wobei jede Phase i aus i Erweiterungen besteht. In jeder Phase muss ein neues Zeichen S(i+1) aus dem Text eingelesen werden. Dabei muss in jeder Erweiterung j der Phase i+1 sichergestellt werden, dass es einen Pfad βS(i+1) gibt, wobei der Pfad β aus der Zeichenfolge besteht, die mit dem j-ten Zeichen des Textes beginnt und mit dem i-ten Zeichen endet. Beim Einlesen muss man sich dazu an gewisse Regeln halten: Regeln: Hinweis: Der gegebene Pfad β beginnt immer bei der Wurzel! Es gibt drei grundlegende Regeln, die bei der Ausführung des Algorithmus zu beachten sind: 1. Falls das Ende eines Pfades β ein Blatt ist, dann wird das Zeichen S(i+1) an die Markierung der letzten Kante diesen Pfades angehängt 2a. Falls es keinen Pfad β gibt, der mit dem Zeichen S(i+1) fortsetzt, dann entsteht eine neue Kante mit der Kantenmarkierung S(i+1). Diese Regel ist also von Interesse, falls des Ende von β ein innerer Knoten ist. 2b. Falls 2a zutrifft, jedoch der Pfad β innerhalb einer Kante endet, dann wird am Ende diesen Pfades ein neuer innerer Knoten v eingefügt und es entsteht eine neue Kante, die zu diesem Knoten v inzident ist. Diese Kante wird nun mit dem an den Pfad β anzuhängenden Zeichen S(i+1) markiert. 3. Es gibt bereits einen Pfad βS(i+1), der Teil des impliziten Suffixbaumes ist – in diesem Fall muss nichts getan werden. Lineare Laufzeit: Indizes und Endmarker: Jeder Kante kann also ein Teilstring des gegebenen Textes zugeordnet werden. Ukkonens Algorithmus setzt in den entsprechenden Phasen und Erweiterungen Indizes für die Kante, zu der das Zeichen S(i+1) hinzugefügt wird. Diese besteht aus einem Anfangsindex und einem Endindex. Der Anfangsindex einer Kante entspricht genau der Stelle des Zeichens aus dem Text, das einer neu entstandenen Kante als Markierung hinzugefügt wurde. Nachdem der Anfangsindex einer Kante einmal gesetzt wurde, wird dieser im weiteren Ablauf des Algorithmus nicht mehr verändert. Der Endindex der Kante ist die Stelle des zuletzt zu der Kantenmarkierung hinzugefügten Zeichens. Falls eine neue Kante entsteht, ist also Anfangsund Endindex dieser Kante gleich. Entsteht aufgrund Regel 2b ein innerer Knoten, der eine Kante in zwei neue Kanten aufteilt, so muss auch ein weiterer Anfangs- und ein weiterer Endindex entstehen. Diese kann man berechnen, indem man die Anzahl der Zeichen zu dem inneren Knoten abzählt und den Wert des Anfangsindexes der Kante kennt, als es den inneren Knoten noch nicht gab. Da diese Berechnung nur einmal nötig ist, geschieht dies in konstanter Zeit. Falls eine Kante bei einem Blatt endet, so wird der Endindex als „aktuelles Ende“ bezeichnet. Indizes sind deshalb so wichtig, da sie insbesondere beim Weglassen von Erweiterungen und bei Skip/Count benötigt werden Erweiterungen weglassen: Immer wenn Regel 1 anzuwenden ist, so braucht in dieser Erweiterung nichts getan zu werden, da das „aktuelle Ende“ nach jeder Phase automatisch inkrementiert wird. Falls man in einer Phase noch nicht Regel 3 angewendet hat, gilt sogar, dass man in allen folgenden Phasen in denselben Erweiterungen sofort weiß, dass man Regel 1 anwenden muss. Der Grund dafür ist, dass man immer den entsprechenden Pfad bis zu einem Blatt durchlaufen muss, um das Zeichen S(i+1) einzufügen. Durch den Trick mit dem „aktuellen Ende“ kann man Erweiterungen, bei denen Regel 1 anzuwenden ist, weglassen und sich somit sehr viel Zeit sparen. Außerdem werden Indizes für Skip und Count benötigt. Suffix-Links: Ein weiteres Feature des Ukkonens Algorithmus sind Suffix-Links. Diese können folgendermaßen definiert werden: Sei x ein einzelnes Zeichen und α ein Substring, der unter Umständen leer sein kann. Wenn es einen Pfad xα gibt, der zu einem inneren Knoten v führt, und wenn es einen Pfad α gibt, der zu einem Knoten s(v) führt, dann wird der Zeiger von v nach s(v) Suffix-Link genannt. Aus der Definition folgt: Falls α leer ist, so liegt ein Suffix-Link, der zur Wurzel führt, vor. Die Definition soll durch die Abbildung nochmals veranschaulicht werden. Da - nach Definition – nur ein innerer Knoten von Interesse ist, muss nur bei Anwendung von Regel 2b der Knoten v gemerkt werden, welchen man in der nächsten Erweiterung derselben Phase den Suffix-Link einfügt. Außerdem muss in dieser nächsten Erweiterung ohnehin der Pfad α durchlaufen werden. In den folgenden Phasen braucht dann zusammen mit Skip/Count nicht mehr der ganze Pfad durchlaufen werden, und somit sind auch nicht mehr Zeichenvergleiche erforderlich. Der Schlüssel dabei ist das Motto: „Falls Du gerade ein Zeichen S(i+1) eingefügt hast, dann springe von dem Blatt, dass zu dieser Kante gehört, zu dem benachbarten inneren Knoten. Wenn dieser die Wurzel ist, dann gibt es keinen Suffix-Link in diesem Pfad; ansonsten folge dem Suffix-Link und schon hast Du Zeit gewonnen!“ Skip und Count: Der Teilabschnitt „Indizes und Endmarker“ spielt eine wichtige Rolle für Skip und Count. Der Grund hierfür ist, dass bei Skip/Count die Anzahl der Zeichen, die zu einer Kante gehören gezählt werden. Beispielsweise könnte man für jede Kante und für jede neu entstandene Kante die Anzahl in einem Array abspeichern, das man ständig updatet. Somit muss man die Anzahl der Zeichen nicht immer wieder von neuem berechnen. Es muss dabei einiges beachtet werden, was jedoch intuitiv klar sein sollte. Für die Berechnung der Anzahl von Zeichen einer Kantenmarkierung bildet man die Differenz aus dem Endindex und dem Anfangsindex der entsprechenden Kante. Beim Skip/Count-Trick wird also solange von einem inneren Knoten zum nächsten inneren Knoten gesprungen bis man bei dem gesuchten Ende des Pfades β angekommen ist. Hierbei muss nur noch das erste Zeichen verglichen werden, da nur noch sichergestellt werden muss, dass man die richtige Kante auswählt. Nach dem Vergleich überspringt man also die Anzahl der Zeichen, die zur Kantenmarkierung gehören. Für Suffix-Links ist wichtig, wie viele Zeichen sich zwischen dem Blatt und dem benachbarten inneren Knoten befinden (Count), damit man weiß, bei welchem Index die Kante beginnt, die aus s(v) herausführt. Dies sollte aber auch mit dem Endindex der Eingangskante zum Knoten s(v) bestimmt werden können. Da durch Skip und Count nicht mehr jedes Zeichen verglichen werden muss, ergibt sich daraus ein immenser Zeitgewinn. Überblick: Hier soll genauer erklärt werden, warum Ukkonens Algorithmus nur linearen Zeitbedarf hat. Da der implizite Suffixbaum, aus dem der gewünschte Suffixbaum hervorgeht, O(n) viele Knoten hat, und da es bei der Konstruktion dieses impliziten Suffixbaumes Erweiterungen gibt, bei denen ohnehin schon offensichtlich ist, dass sie weggelassen werden können, erreicht der Algorithmus lineare Laufzeit. Von Bedeutung ist hier vor allem Skip/Count, wodurch man herausfinden kann, an welcher Stelle im Text man sich gerade befindet. Um das erste Zeichen zu bestimmen benötigt man O(|Σ|) viel Zeit, da der maximale Verzweigungsgrad O(|Σ|) beträgt. Außerdem benötigt man für die Berechnung der Anzahl an Zeichen einer Kantenmarkierung O(1) viel Zeit, da Anfangs- und Endindex schon während der Konstruktion des impliziten Suffixbaumes geeignet bestimmt werden. Suffix-Links bringen weiteren Speed-Up. Suffixbaum erzeugen: Nach Ausführung der letzten Phase und der letzten Erweiterung muss nun nur noch aus dem so erhaltenen impliziten Suffixbaum der gewünschte Suffixbaum konstruiert werden. Man führt dafür ein Terminalsymbol $ ein („Sentinel“). Dafür muss zuerst festgestellt werden, wo man das Terminalsymbol einfügen muss. Um den impliziten Suffixbaum zu durchlaufen, kann man Skip/Count und Suffix-Links geeignet verwenden Zusammenfassung: Ukkonens Algorithmus ist ein sehr effizienter Algorithmus, der bevorzugt zur Konstruktion von Suffixbäumen eingesetzt wird. Hierbei werden implizite Suffixbäume solange konstruiert bis im letzten Schritt der gesuchte Suffixbaum daraus konstruiert werden kann. Dadurch dass gewisse Erweiterungen in den entsprechenden Phasen weggelassen, Suffix-Links erzeugt und genutzt werden können und da mithilfe von Skip und Count nicht jedes einzelne Zeichen vergleicht werden braucht, erreicht der Algorithmus LINEARE LAUFZEIT.