Suffix Trees: Simple Algorithm and Applications

Werbung
Suffix Trees: Simple Algorithm and Applications
Valerio Lupperger
Suffix Trees:
Simple Algorithm and Applications
Proseminar
Bioinformatik
Wintersemester 2010/11
Valerio Lupperger
1
Suffix Trees: Simple Algorithm and Applications
Valerio Lupperger
Inhaltsverzeichnis
1. Einleitung
2. Geschichte
3. Definitionen und Eigenschaften
4. Naive Konstruktion
5. Generalisierter Suffix Tree
6. Implementationsfragen
6.1 Array
6.2 Linked List
7. Anwendungsbeispiele für Suffix Trees
7.1 Exaktes String Matching
7.2 Datenbank von Pattern
2
Suffix Trees: Simple Algorithm and Applications
Valerio Lupperger
1. Einleitung
Um mit DNA/RNA bioinformatisch zu arbeiten, reichen meist die
Sequenzen der Nukleobasen, die als String dargestellt werden.
Dasselbe gilt auch für Proteine, mit denen mithilfe von AminosäurenStrings ebenfalls abstrakt gearbeitet werden kann. Diese, zum
Arbeiten benötigte, Algorithmen wiederum benötigt spezielle „Werkzeuge“, um effizient durchgeführt werden zu können.
Ein Beispiel hierfür stellen die Suffix Trees dar, die einen gegebenen
String so vorbereiten, dass im Anschluss mit linearem Zeitaufwand
festgestellt werden kann, ob ein beliebiges Pattern in diesem String
auftritt, und wenn dies der Fall sein sollte, auch angibt, an welcher
Stelle dieses Pattern im String zu finden ist.
2. Geschichte
Zum ersten Mal wurde 1973 ein Algorithmus zur Erstellung eines sog.
Positionsbaums von Weiner veröffentlicht, der als Vorgänger des
heutigen Suffix Trees gesehen werden kann.
Ein paar Jahre später entwickelte McCreight einen anderen
Algorithmus, der ebenfalls eine lineare Laufzeit aufwies, aber weniger
Speicherplatz benötigte.
Es dauerte noch 20 Jahre bis ein weiterer, bis heute angewandter
Algorithmus von Ukkonen entwickelt wurde. Dieser kann als eine
Abwandlung des McCreight Algorithmus gesehen werden, die
allerdings leichter verständlich ist.
Da die beiden erst genannten Algorithmen allerdings den Ruf
haben schwer verständlich zu sein, finden die Suffix Trees bis heute
eher selten Anwendung.
3
Suffix Trees: Simple Algorithm and Applications
Valerio Lupperger
3. Definitionen und Eigenschaften
Vor der Konstruktion eines Suffix Trees müssen ein paar grundlegende
Definitionen festgehalten werden.
Der zu verarbeitende String sei der String S [1,...,m] mit der Länge
|String| = m über dem abgeschlossenen Alphabet A. Ein Suffix Tree
ST wird festgelegt durch folgende Definitionen:
1. Der ST ist ein gerichteter Wurzelbaum.
2. Die Blätter des ST sind durchnummeriert von 1 bis m.
3. Jeder innere Knoten (außer der Wurzel) besitzt mindestens 2
Kinder.
4. Jede Kante ist mit einem nicht-leeren Substring von S
beschriftet.
5. Kanten die am selben Knoten beginnen, dürfen nicht mit
demselben Buchstaben starten.
6. Die Verknüpfung aller Kanten, die in der Wurzel beginnen und
zu einem mit Nummer i Blatt führen, ergeben genau einen
Substring S[i,...,m].
Es kann passieren, dass ein Substring nicht in einem Blatt endet, wenn
das Suffix zugleich ein Präfix darstellt. Um sicherzustellen, dass jedes
Suffix in einem Blatt endet, wird am Ende jedes Strings und somit
auch jedes Substrings ein Zeichen angehängt, das nicht in A existiert.
Im Folgenden wird das Symbol $ angehängt, welches nicht in einem
Alphabet vorkommt, das aus Buchstaben besteht.
Somit besitzt der ST für den String S$ folgende Eigenschaften:
1. Durch das Endsymbol besitzt der ST nun m+1 Blätter, die je
ein Suffix von S$ darstellen.
2. Es gibt maximal m innere Knoten.
3. Folglich gibt es höchstens 2m+1 Knoten.
4. Jeder Knoten kann bis zu |Alphabet| Kinder haben.
5. Die max. |Kanten| = |Knoten| -1 = 2m (da außer zur Wurzel,
jede Kante zu einem Knoten führt)
4
Suffix Trees: Simple Algorithm and Applications
Valerio Lupperger
4. Naive Konstruktion
Die eigentlich in linearer Zeit durchführbare Konstruktion wird hier
nun zum Verständnis des Aufbaus eines ST in naiver bzw. intuitiver
Form durchgeführt.
Zunächst wird der komplette String S[1,...m]$ als erste Kante an die
Wurzel angefügt. Diese wird mit S$ beschriftet und an das
Kantenende, das Blatt, wird eine „1“ geschrieben.
Anschließend werden noch die Substrings S[i,...,m]$ (in aufsteigender
Reihenfolge von 2 - m) betrachtet, deren erste Symbole mit den ersten
Symbolen der bereits bestehenden Kanten verglichen werden. Sollten
diese Symbole nicht identisch sein, wird von der Wurzel aus eine
komplett neue Kante gebildet, die logischerweise die Beschriftung
S[i,...,m]$ erhält. Am Blatt dieses Pfads wird folglich das „i“
angetragen. Bei Übereinstimmung der ersten Symbole werden die
folgenden Symbole verglichen. Dann wird die bereits vorhandene
Kante solange verfolgt, bis sich die Symbole unterscheiden. Dort wird
ein Knoten gesetzt, aus dem eine neue Kante entspringt. Diese wird
mit den restlichen Symbolen (inklusive des sich unterscheidenden
Symbols) des i-ten Strings beschriftet. Das Blatt wird, wie im ersten
Fall, mit einem „i“ versehen.
Um den kompletten ST zu erzeugen, wird dieses Verfahren nun
solange wiederholt, bis das Suffix nur noch das Terminalsymbol $
enthält, welches dann zum Schluss noch eine eigene Kante von der
Wurzel aus erhält. Zur besseren Vorstellung siehe Bild 4.1.
k
i
$
2
i
w
i
w
i
$
4
$
5
w
$
i
$
1
3
Bild 4.1 Ein ST zum String „kiwi$“
5
Suffix Trees: Simple Algorithm and Applications
Valerio Lupperger
5. Generalisierter Suffix Tree
Ein generalisierter ST ist ein ST, der sich nicht nur aus einem String
zusammensetzt, sondern einer ganzen Reihe von Strings (S1,...,Sn) . Die
einfachste Methode einen solchen ST zu erzeugen, besteht darin, an
jeden String ein anderes Terminalsymbol anzuhängen, welches
natürlich nicht in A vorkommt, die modifizierten Strings
hintereinander zu hängen und einen ST der neu entstandenen
Stringkette zu bilden.
Die Blätter müssten dann als Informationen sowohl den dort endenden
String als auch die passende Startposition enthalten.
Dasselbe Ziel lässt sich auch erreichen, indem in einen bereits
erstellten ST für den String S1 ein weiterer String S2 eingefügt wird.
Dies passiert nach demselben Prinzip wie beim Erstellen eines ST für
einen String. Es wird nach Übereinstimmungen mit bereits
angetragenen Symbolen an Kantenanfängen an der Wurzel gesucht
und bei Übereinstimmung die Kante verfolgt bis ein Mismatch auftritt
etc. Dabei ist darauf zu achten, dass bei selbem Terminalsymbol in
den Blättern evtl. mehr Informationen gespeichert werden müssen, da
unterschiedliche Strings die gleichen Suffixe enthalten können und
dann alle im selben Blatt enden.
6. Implementationsfragen
Bei typischen Anwendungen, z. B. im bioinformatischen Bereich, geht
die Stringgröße bis in die Millionen oder sogar Milliarden und
deswegen ist es wichtig, eine praktische und effiziente Implementation
zu finden. Die wesentlichen Fragen, um die es bei der Implementation
eines ST geht, sind die Wahl der Datenstruktur für die Knoten und die
dann von dort abgehenden Kanten.
Hierbei ist die wichtigste Entscheidung, die Entscheidung zwischen
schnellem Zugriff auf die Kanten und benötigtem bzw. vorhandenem
Speicherplatz für den ST.
6
Suffix Trees: Simple Algorithm and Applications
Valerio Lupperger
6.1 Array
Die wohl simpelste Art einen Knoten darzustellen gelingt mithilfe
eines Arrays. Jeder innere Knoten bekommt ein Array der Länge |A|,
wobei die einzeln Zellen des Arrays jeweils den möglichen
Anfangssymbolen der folgenden Kanten entsprechen. Die Zellen
zeigen dann jeweils auf die folgenden Kinder des Knotens. Dieses
Array erlaubt einen Zugriff auf Knoten und Kanten in konstanter Zeit.
Allerdings wird der benötigte Speicherplatz für wachsende |String|
bzw. |Alphabet| unpraktisch groß.
6.2 Linked List
Eine weitere Möglichkeit den Knoten zu implementieren bietet die
Linked List. Für jeden neuen Knoten wird eine Liste von Symbolen
angelegt, die dann jeweils um die hinzukommenden Startsymbole der
aus dem Knoten hervorgehenden Kanten erweitert wird. Beim Zugriff
auf den Knoten wird dann jedes Mal die Liste nach dem
entsprechenden Symbol durchsucht.
Durch das Einhalten einer z. B. alphabetischen Reihenfolge
kann sowohl die Konstruktions- als auch die Suchzeit verbessert
werden. Im Falle einer erfolglosen Suche würde der Algorithmus im
Durchschnitt früher abbrechen.
7. Anwendungsbeispiele für Suffix Trees
Ist ein ST erst einmal in linearer Zeit erstellt, so können damit viele
„Matching-Probleme“ effizienter gelöst werden als mit vielen anderen
Methoden. Folgende Beispiele lassen sich in linearem Zeitaufwand
lösen.
7.1 Exaktes String Matching
Das exakte String Matching ist ein weit verbreitetes Problem, das
nicht nur in der Bioinformatik anzutreffen ist. Dabei geht es darum,
7
Suffix Trees: Simple Algorithm and Applications
Valerio Lupperger
ein gegebenes Pattern P der Länge n in einem Text der Länge m zu
finden. Dies ist in O(n+m) Zeit möglich, da der ST in O(m) Zeit
aufgebaut werden kann und die anschließende Suche einen
Zeitaufwand von O(n) benötigt.
Die Suche nach einem Pattern in einem ST erfolgt durch das
Verfolgen der passenden Kante des ST, d. h. an der Wurzel wird die
Kante mit dem passenden ersten Buchstaben ausgewählt und dann
solange verfolgt bis kein übereinstimmendes Zeichen mehr gefunden
wird. Dann ist das Pattern nicht im Text. Andernfalls wird das Pattern
komplett gefunden. In diesem Fall werden die restlichen Unterbäume
nach dem gefundenen Pattern bis zu den Blättern verfolgt, um
herauszufinden, wie oft und wo das Pattern im Text auftritt. Dies kann
durch z. B. den depth-first Algorithmus in linearer Laufzeit in
Abhängigkeit von der Anzahl der Vorkommen von P geschafft
werden. Diese Gesamtlaufzeit wäre dann O(n+m+k) bei k-fachem
Vorkommen.
7.2 Datenbank von Pattern
In einer großen Datenbank nach einem Pattern der Länge n zu suchen
wirft mehrere Probleme auf. Die Vorbereitung als auch die Suche
müssen schnell gehen, aber auch der Speicherbedarf sollte möglichst
gering gehalten werden.
Dies alles bietet die Datenstruktur des ST. Hierfür wird der
generalisierte ST benutzt, welcher die Datenbank der Länge m in
linearer Zeit O(m) vorbereiten kann und dafür auch nur O(m) Platz
benötigt. Ebenso kann die Suche nach dem P in linearer Zeit O(n)
durchgeführt werden.
8. Fazit
Zusammenfassend kann gesagt werden, dass sich mit den STAlgorithmen viele Suchalgorithmen auf Texten enorm beschleunigen
lassen, da die Probleme sich in linearer Abhängigkeit zum Suchstrings
vorbereiten lassen und dann in linearer Abhängigkeit zum gesuchten
Pattern lösen lassen. Somit sind sie vielen anderen Suchalgorithmen
überlegen.
8
Suffix Trees: Simple Algorithm and Applications
Valerio Lupperger
Literatur
[1] D. Gusfield: Algorithms on Strings, Trees, and Sequences –
Computer Science and Computional Biology,
CambridgeUniversity Press, 1997; Kapitel 5, Abschnitt 6.4,
Abschnitte 7.1 und 7.3 – 7.6.
[2] http://de.wikipedia.org/wiki/Suffixbaum (Internetenzyklopädie)
vom 02.06.2010; zuletzt besucht am 07.11.2010
[3] F. Schüle: Suffix Trees, 2006.
Ausarbeitung in Form eines Seminars in der Bioinformatik
an der Universität Ulm
[4] S. Telejnikov, Suffixbäume, 2006.
Ausarbeitung in Form eines Seminars für Algorithmen an der
Universität Stuttgart
9
Herunterladen