Suffix Trees: Simple Algorithm and Applications

Werbung
Suffix Trees:
Simple Algorithm and Applications
Melanie Jakob
10. Mai 2010
1
Einleitung
Ein Suffix Tree ist eine Datenstruktur, die die interne Struktur eines Strings verdeutlicht
und alle Teilstrings eines Strings S enthält. Ihr Vorteil liegt darin, Lösungen für viele
String Probleme, wie zum Beispiel das Substring-Problem, in linearer Zeit zu finden. Dabei schlagen Suffix Trees Brücken zwischen exaktem und unexaktem Matching und finden
unter anderem in der Genomanalyse Verwendung.
Bei einem gegebenen Text T der Länge m kann der Suffix Tree in einer Vorbereitungszeit
von O(m) erstellt werden und ein beliebiger String der Länge n kann in O(n) in T gefunden werden, oder dessen Auftreten wird ausgeschlossen. Die lineare Zeitgrenze der Suche
ist essentiell für typische Anwendungen der Suffix Trees, bei denen eine große Menge zu suchender Strings eingegeben wird. Das Erstellen eines Suffix Trees in O(m) ist durch den
Algorithmus von Ukkonen möglich, auf den im Folgenden noch näher eingegangen wird.
2
Geschichte
1973 entwickelte Peter Weiner einen Algorithmus zur Konstruktion von Positionsbäumen.
Diese Positionsbäume sind die Vorgänger der heutigen Suffix Trees, benötigten jedoch sehr
viel Speicherplatz. Edward M. McCreight erfand 1976 einen platzsparenderen Algorithmus
zur Konstruktion von Suffix Trees. Weitere 20 Jahre später wurde ein anders konzipierter
Algorithmus von Esko Ukkonen erfunden. Dieser Algorithmus enthält alle ursprünglichen
Ideen von McCreights, lässt sich aber wesentlich einfacher erklären. Da die Vorgehensweise
und Implementierungstricks sehr ähnlich sind, kann man Ukkonens Algorithmus als eine
Suffix Trees: Simple Algorithm and Applications
Melanie Jakob
Variante von McCreights Algorithmus betrachten. Ein grundlegende Idee des Ukkonen-Algorithmus ist, dass zuerst von einem naiven Implementationsansatz ausgegangen wird, dessen worst-case-Laufzeit durch leicht zu verstehende Implementationstricks verbessert wird.
Dadurch kommt der Algorithmus auf eine Laufzeit von O(m) in Abhängigkeit zur Länge
des Strings.
Obwohl mehr als 30 Jahre seit Weiners Original vergangen sind, werden Suffix Trees nur
sehr selten in der Informatik unterrichtet. Sie erhalten generell weniger Aufmerksamkeit
und Anwendung als erwartet, vermutlich aufgrund der schlechten Verständlichkeit der beiden originalen Paper aus den 70er Jahren. Trotzdem sind die Algorithmen, obwohl sie
nicht simpel sind, nicht komplizierter als andere viel gelehrte Methoden. Wenn sie gut implementiert sind, sind die Algorithmen praktisch anwendbar und ermöglichen effiziente Lösungen für viele komplexe String Probleme.
3
Grundlegende Definitionen
Ein Suffix Tree T für einen String S der Länge m ist ein gerichteter Wurzelbaum mit genau
m Blättern nummeriert von 1 bis m. Jeder innere Knoten, außer der Wurzel, hat mindestens zwei Kinder und jede Kante ist mit einem nicht-leeren Substring von S beschriftet. Die
Beschriftung zweier ausgehender Kanten eines Knotens darf nicht mit demselben Buchstaben beginnen. S[i..m] entspricht der Verknüpfung der Kanten-Beschriftungen auf dem Weg
von der Wurzel bis zum Blatt i und stellt exakt den Suffix von S beginnend bei der Position i dar.
In bestimmten Fällen können Probleme bei der Konstruktion eines Suffix Trees auftauchen.
Nämlich genau dann, wenn ein Suffix von S mit einem Präfix eines anderen Suffixes von S
übereinstimmt. Dann kann kein Suffix Tree nach den obigen Bestimmungen erstellt werden, weil der Pfad des ersten Suffixes nicht in einem Blatt enden würde. Die Lösung dieses
Problems ist denkbar einfach. Um dafür zu sorgen, dass kein Suffix ein Präfix eines anderen Suffixes ist, hängt man am Ende jedes Strings ein Zeichen an, welches nicht aus demselben Alphabet stammt wie S.
Wenn man die Terminierung durch ein solches Zeichen, z.B. $, nachdrücklich betonen
möchte, kann man den String als S$ bezeichnen. Meistens ist diese Hilfestellung jedoch
nicht nötig und ohne es explizit zu erwähnen, ist jeder String durch das Symbol $ abgeschlossen, auch wenn das Symbol selbst nicht zu sehen ist.
Seite 2 von 8
Suffix Trees: Simple Algorithm and Applications
4
Melanie Jakob
Naiver Algorithmus zur Aufstellung eines Suffix Trees
In diesem Abschnitt wird ein einfaches Verfahren vorgestellt, für einen String S einen Suffix Tree zu konstruieren.
Zuerst wird für das Suffix S[1..m]$, das den gesamten Eingabestring darstellt, eine Kante
von der Wurzel aus geschaffen. Diese Kante wird mit S$ beschriftet und das neu hinzugefügte Blatt am Ende der Kante wird mit „1“ beschriftet.
Im zweiten Schritt wird jedes Suffix S[i..m] betrachtet, wobei i von 2 bis m inkrementiert
wird. Für dieses Suffix muss beginnend bei der Wurzel der längste Pfad gefunden werden,
bei welchem die Beschriftung mit einem Präfix von S[i..m] übereinstimmt. Dieser Pfad ist
einzigartig, weil die Beschriftungen ausgehender Kanten eines Knotens nie mit denselben
Buchstaben beginnen. An einem gewissen Punkt sind keine weiteren Übereinstimmungen
mehr möglich, weil kein Suffix von S$ ein Präfix von irgendeinem anderen Suffix von S$
sein kann. Wenn der Algorithmus so in der Mitte einer Kante angekommen ist und kein
neues Zeichen, welches zu einer Verlängerung des gefundenen Präfixes von S[i..m] passt,
mehr vorhanden ist, wird an dieser Stelle die Kante aufgebrochen und ein neuer Knoten
mit einer neuen Kante angefügt. Die neue Kante wird mit den verbleibenden Buchstaben
von S[i..m] beschriftet, die nicht mehr mit dem Pfad übereinstimmen. Das Blatt bekommt
den Index i. Sollte kein passender Pfad im vorhandenen Suffix Tree existieren, wird eine
neue Kante von der Wurzel aus angelegt, beschriftet mit S[i..m]. Das dazugehörige Blatt
wäre natürlich i.
Der Verlauf der Konstruktion lässt sich mit Hilfe der Abbildung leicht nachvollziehen. [7]
CO
1
O$
OC
2
$
CO
$
1
O$
OC
CO
C
O$
CO
C
O$
1
usw.
3
2
Da für einen String der Länge n gilt, dass die Anzahl der Zeichen aller Suffixe des Strings
m1 
gleich 
ist, beträgt die Laufzeit des naivem Algorithmus im worst-case O(m2).
2
Seite 3 von 8
Suffix Trees: Simple Algorithm and Applications
5
Melanie Jakob
Generalisierter Suffix Tree für eine Menge von Strings
Ein Generalisierter Suffix Tree ist eine Baum, der für eine Menge von Strings (S 1 … Sk)
steht. Der einfachste Weg diesen Generalisierten Suffix Tree zu bilden, ist am Ende jedes
Strings ein unterschiedliches Kennzeichen zu setzen und anschließend alle Strings aneinander zu reihen. Die Kennzeichen dürften dabei selbstverständlich nicht aus demselben Alphabet stammen, wie die Strings. Aus dieser Reihe kann nun ein Suffix Tree in linearer
Zeit in Abhängigkeit zur Summe der einzelnen Stringlängen konstruiert werden. Die Nummerierung der Blätter bestünde dabei jeweils aus der Nummer für den String S i und der
Startnummer in Si.
Ein Generalisierter Suffix Tree lässt sich auch ohne eine vorherige Verknüpfung der Strings
aufstellen. Dabei könnten die zusammengefassten Beschriftungen auf unterschiedlichen
Kanten möglicherweise auf unterschiedliche Strings zurückzuführen sein. Folglich würde die
Anzahl der Indizes pro Kante von zwei auf drei erhöht werden. Außerdem könnten die Suffixe zweier Strings identisch sein können, in diesem Fall muss ein Blatt alle Strings und
Startpositionen der beinhalteten Strings indizieren.
6
Praktische Implementationsfragen
Die wesentliche Herausforderung bei der Implementierung von Bäumen ist die Darstellung
der Zweige der Knoten. Die beste Lösung sollte dabei das Gleichgewicht zwischen dem
Speicherbedarf und der Geschwindigkeit halten, sowohl bei der Konstruktion des Suffix
Trees, als auch bei der Anwendung.
6.1 Array
Die einfachste Art die Zweige darzustellen, ist ein Array für jeden Knoten v, der kein Blatt
ist. Das Array hat verschiedene Zellen, die jeweils mit dem Anfangsbuchstaben der Kantenbeschriftung eines Kindes indiziert sind. Diese Zellen enthalten dann einen Zeiger vom
Knoten zum Kind. Dieses Array ermöglicht direkten Zugriff und Updates in konstanter
Zeit und ist einfach zu programmieren. Doch der Speicherbedarf macht diese Implementierung unbrauchbar, wenn die Anzahl der Elemente im Alphabet und die Länge m des
Strings S größer werden.
Seite 4 von 8
Suffix Trees: Simple Algorithm and Applications
Melanie Jakob
6.2 Linked List
Alternativ zum Array kann man ebenso eine Linked List dazu verwenden, Knoten und deren Zweige zu implementieren. Die Linked List enthält dabei alle Anfangsbuchstaben der
Kantenbeschriftungen von Zweigen eines Knotens. Wird eine neue Kante aus demselben
Knoten zum Baum hinzugefügt wird, wird auch ein neuer Buchstabe zur Liste hinzugefügt.
Die Traversierung des Suffix Trees ist durch die sequentielle Suche nach dem entsprechenden Buchstaben umgesetzt. Die durchschnittliche Laufzeit einer Suche kann reduziert werden, indem die Liste sortiert gespeichert wird. Der springende Punkt dabei ist, dass ein
schnellerer Abbruch der Suche ermöglicht wird, falls ein Zeichen nicht in der Liste vorkommt.
7
Erste Anwendung der Suffix Trees
Warum sind Suffix Trees so wichtig? Man kann sie dazu benutzen, das exakte Matching
Problem in linearer Zeit zu lösen, und zwar in Abhängigkeit zur Länge des Suchmusters.
Außerdem ermöglichen Suffix Trees auch die Lösung von weiteren komplexeren Stringverarbeitungsproblemen. Diese Probleme tauchen überall dort auf, wo man suchen muss, nämlich in den Gebieten von Information Retrieval - Internet, datenbankbasierten Anwendungen, Texteditoren, Bibliothekssystemen, www-Verzeichnissen, usw. Des Weiteren ist die
Bioinformatik ein wesentliches Einsatzgebiet für Suffix Trees, da Gensequenzen als String
darstellbar sind.
7.1 Exact String Matching
Gegeben sei der Pattern P der Länge n und ein Text T der Länge m. Mithilfe eines Suffix
Tree's kann man jedes Auftreten von P in T in O(n+m) Zeit finden.
Anleitung:
„Bilde einen Suffix Tree T für den Text T in O(m) Zeit. Dann finde eine Übereinstimmung
der Buchstaben des Patterns P mit einem eindeutigen Pfad in T bis P entweder aufgebraucht ist oder keine Übereinstimmungen mehr möglich sind. Im letzten Fall kommt P
nicht in T vor. Im ersten Fall jedoch ist jedes Blatt im Unterbaum unter der letzten Zusammensetzung mit der Startposition von P in T nummeriert und jede Startposition von P
in T nummeriert so ein Blatt.“ [1]
Seite 5 von 8
Suffix Trees: Simple Algorithm and Applications
Melanie Jakob
Nachdem der Suffix Tree für einen feststehenden Text fertiggestellt worden ist, kann eine
lange Reihe von Patterns eingegeben werden und die Suche nach den Vorkommen jedes
dieser Pattern kann dann möglichst schnell durchgeführt werden. Die Länge der Pattern sei
n und die Anzahl der Vorkommen sei k. Benutzt man einen Suffix Tree können alle Vorkommen in O(n+k) Zeit gefunden werden, absolut unabhängig von der Länge des Textes.
Wenn nur ein einziges Aufkommen eines Patterns P gefordert ist und die Vorbereitung ein
bisschen erweitert wird, dann kann die Suchzeit von O(n+k) auf O(n) reduziert werden.
Die Idee dabei ist jeden Knoten mit der kleinsten Zahl eines Blattes in seinem Unterbaum
zu beschriften. Bei der Suche kann man also auf diese Beschriftung der Knoten zurückgreifen und hat dadurch sofort eine Startposition für ein Vorkommen der Pattern im Text.
7.2 Exact Set Matching
Das Problem des Exakten Set Matchings besteht darin, dass alle Vorkommen von einer
Reihe von Strings P in einem Text T gefunden werden sollen, wobei das komplette Set
von , Strings auf einmal eingegeben wird. Tatsächlich können alle Vorkommen eines speziellen P's der Länge n in O(n+k p) Zeit gefunden werden, wobei k die Anzahl der Vorkommen von P ist, wenn T erst bekannt ist und festgehalten wird und die Pattern variieren.
Also ist das Exakte Set Matching Problem eigentlich ein einfacherer Fall, weil das Set P
eingegeben wird, zur gleichen Zeit in der T bekannt ist. Um es zu lösen bilden wir einen
Suffix Tree T für den Text T in Zeit O(m) und nutzen dann diesen Baum zur sukzessiven
Suche nach jedem Auftreten eines Patterns in P. Die totale benötige Zeit bei dieser Vorgehensweise ist O(n+m+k).
7.3 Substring Problem einer Datenbank von Patterns
Eine Reihe von Strings oder eine Datenbank ist gegeben und steht fest. Später wird eine
Reihe von Strings gebildet und für jeden dieser dargestellten Strings müssen alle Strings
der Datenbank herausgesucht werden, die den String als Substring enthalten. Das ist die
Umkehrung des Exakten Set Matching Problems, in dem gefragt wird, welcher der fixen
Patterns in einem Substring des eingegebenen Strings enthalten ist.
Im Zusammenhang mit Datenbanken für Genom DNA Daten ist das Problem Substrings
zu finden, real und kann nicht mit dem Exakten Set Matching gelöst werden. Die DNA
Datenbank beinhaltet eine Sammlung früher sequenzierter DNA Strings. Wenn ein ein neuer DNA String sequenziert wird, könnte er in einem bereits sequenzierten String enthalten
sein, deshalb ist eine effiziente Methode dies herauszufinden nötig.
Seite 6 von 8
Suffix Trees: Simple Algorithm and Applications
Melanie Jakob
Die totale Länge m aller Strings in der Datenbank ist vermutlich sehr hoch. Was macht
also eine gute Datenstruktur und Suchalgorithmus für das Substring-Problem aus? Die
zwei Hemmnisse sind, dass die Datenbank wenig Speicherplatz benötigen soll und dass jede
Suche sowie die Vorbereitung der Datenbank zur Suche schnell durchgeführt werden soll.
Suffix Trees bieten eine attraktive Lösung zu diesem Datenbank Problem. Ein generalisierter Suffix Tree T für die Strings der Datenbank wird in O(m) Zeit gebildet und, noch wichtiger, dieser braucht nur O(m) Platz. Ein einzelner String S der Länge n wird in O(n) Zeit
gefunden, oder das Vorkommen wird ausgeschlossen. Wie gewöhnlich wird das durch Zusammenführen des Strings mit dem Pfad im Baum beginnend bei der Wurzel bewerkstelligt.
7.4 Longest Common Substring
Ein klassisches Problem der String-Analyse ist den längsten gemeinsamen Substring zweier
gegebener Strings S1 und S2 zu finden. Das nennt man das Longest Common Substring
Problem.
Ein effizienter und konzeptionell einfacher Weg den längsten gemeinsamen Substring zu
finden, ist einen Generalisierten Suffix Tree für S 1 und S2 zu bilden. Jedes Blatt in diesem
Baum stellt entweder ein Suffix einer der beiden Strings dar, oder ein Suffix, das in beiden
Strings enthalten ist. Markiert man nun jeden inneren Knoten mit einer 1 bzw. 2, falls ein
Blatt im Unterbaum ist, dass S1 bzw. S2 abbildet, dann ist jeder Pfad im Suffix Tree, dessen innere Knoten mit 1 und 2 beschriftet sind, ein gemeinsamer Substring. Der längste
dieser Pfade ist der Longest Common Substring. Also muss der Algorithmus nur den Knoten mit der maximalen String-Tiefe (größte Anzahl Buchstaben auf dem Pfad zum Knoten), welcher mit 1 und 2 markiert ist.
Der Suffix Tree kann in linearer Zeit in Abhängigkeit zur Länge von S 1 und S2 aufgestellt
werden. Die Knoten zu markieren und die Berechnung der String-Tiefe kann mithilfe von
Standard Baum-Traversierungen ebenfalls in linearer Zeit durchgeführt werden. Der längste gemeinsame Substring zweier String kann also durch die Verwendung eines Generalisierten Suffix Trees in linearer Zeit gefunden werden.
Seite 7 von 8
Suffix Trees: Simple Algorithm and Applications
8
Melanie Jakob
Fazit
In dieser Arbeit wurden Suffixbäume und die naive Konstruktion solcher Datenstrukturen
dargestellt und wie man in ihnen suchen kann. Außerdem wurde ein kurzer Überblick über
die Anwendungsmöglichkeiten von Suffix Trees gegeben, die in Wirklichkeit vielzählig sind.
Den größten Nutzen erzielen Suffix Trees wohl bei der Suche nach unterschiedlichen Strings
oder Patterns in einem feststehenden Text oder einer Datenbank.
Literatur
[1] D. Gusfield: Algorithms on Strings, Trees, and Sequences – Computer Science and
Computional Biology, Cambridge University Press, 1997; Kapitel 5, Abschnitt 6.4, Abschnitte 7.1 und 7.3 – 7.6.
[2] Wikipedia: Suffixbaum, Wikipedia, Die freie Enzyklopädie, Stand 30.12.2009,
aufgerufen am 10.05.2010.
[3] Wikipedia: Regulärer Ausdruck, Wikipedia, Die freie Enzeklopädie, Stand 23.04.2010,
aufgerufen am 10.05.2010.
[4] J. Harzer: Der Suffix-Tree, Stand 11.06.2001, aufgerufen am 10.05.2010.
[5] S. Penkova, A. Tchorbadjiiski: Suffix Trees und Suffix Arrays, Ausarbeitung
Proseminar Algorithmen und Datenstrukturen (Sommersemester 2005), RheinischWestfälische Technische Hochschule, Fakultät für Informatik, 2005, zugegriffen am
10.05.2010.
[6] F. Schüle: Suffix Trees, Vortragsfolien und Ausarbeitung Hauptseminar Bioinformatik
(Sommersemester 2006), Universität Ulm, Fakultät für Informatik, 2006, zugegriffen am
10.05.2010.
[7] S. Telejnikov: Suffixbäume, Ausarbeitung Hauptseminar (Sommersemester 2006),
Universität Stuttgart, Institut für formale Methoden der Informatik, 2006, zugegriffen
am 10.05.2010.
Seite 8 von 8
Herunterladen