Ukkonens Algorithmus

Werbung
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.
Herunterladen