FAKULTÄT FÜR INFORMATIK DER TECHNISCHE UNIVERSITÄT MÜNCHEN Lehrstuhl für Effiziente Algorithmen WS 2010 / 2011 Systementwicklungsprojekt Konstruktion von Suffix-Bäumen mit dem DiGeST-Algorithmus İlker Hatipoğlu Betreuer Dipl.-Inform. Johannes Krugel Prof. Dr. Ernst W. Mayr Inhalt I. Einleitung 1 II. Suffix-Bäume und Suffix-Arrays 1 1. Motivation . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 1 2. Suffix . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 1 3. Definition Suffix-Baum. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 2 4. Komprimierte Suffix-Bäume (Compressed Suffix Tree). . . . . . . . . . . . . 3 5. Der naive Konstruktionsalgorithmus. . . . . . . . . . . . . . . . . . . . . . . . . . . . 4 6. Suffix-Arrays . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 4 III.DiGeST (Disk-Based Genomic Suffix Tree) 5 1. Vorbearbeitungsphase . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 5 2. Sortieren der Suffixe . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 5 3. Zusammenfügen der Suffix-Arrays (Merge-Phase). . . . . . . . . . . . . . . . . 7 4. Ein Beispiel . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 7 IV. Implementierung 11 1. Die verwendeten Datenstrukturen . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 11 2. Vorbearbeitungsphase . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 12 3. Sortieren der Suffixe . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 12 4. Zusammenfügen der Suffixe . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 12 Literaturverzeichnis. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 13 I. Einleitung Seitdem verschiedene Genomen sequenziert werden können und die gesammelten Genom-Daten täglich wachsen, interessieren sich Genomforscher für die Analyse und den Vergleich dieser Genom-Strings. Allerdings sind solche Strings sind in der Regel recht groß, so dass dessen Umgang einen enormen technischen Aufwand erfordert. Allein ein einfacher Vergleich zweier Strings dieser Länge könnte z.B. mit einem naiven Verfahren Jahre lang dauern. In diesem Engpass helfen die effektiven Methoden der Informatik und Mathematik, die zum StringVergleich entwickelt sind. Diese Methoden basieren meist auf Suffix-Bäume (Suffix-Tree) oder Suffix-Arrays. [Kleffe2001] Allerdings auch bei diesen Methoden, hat man ein Problem mit der Größe des zu bearbeitenden Strings: Selbst die Suffix-Bäume von kleineren Genom-Strings können sehr groß werden, so dass sie leicht den verfügbaren Arbeitsspeicher überschreiten. z.B. für einen Eingabe-String der Größe 6 GB würde man mindestens 60 GB Arbeitsspeicher brauchen, um seinen Suffix-Baum zu behalten. Das legt die Verwendung eines Festplatte-Basierten-Algorithmus zur Konstruktion eines SuffixBaums nahe. [Barsky2008] Diese Arbeit beschäftigt sich mit einer solchen Methoden, nämlich der „DiGeST“ (Disk-Based Genomic Suffix Tree), die von Marina Barsky, Ulrike Stege, Alex Thomo und Chris Upton entwickelt wurde. II. Suffix-Bäume und Suffix-Arrays 1. Motivation Problem: Gegeben ist ein String S der Länge n. Gesucht ist das Vorkommen eines beliebigen Strings s der Länge m in S. (m <= n) → Die klassische Suche mit naivem Zeichenvergleich ist für die extrem großen Strings viel zu aufwändig. Lösungsidee: Das Ziel ist eine andere Repräsentation von S zu finden, die – in linearer Zeit hergestellt werden kann, – lineare Speicherkomplexität hat, – eine Suche mit der Zeitkomplexität O(|m|) ermöglicht. Ein Suffix-Baum ist eine solche Indizierung des Strings und ermöglicht es, Suchaufgaben in sehr kurzer Zeit abzuarbeiten. [Penkova2005] 2. Suffix Ein Suffix eines Strings ist ein Teilstring, der an einer beliebigen Buchstabe anfängt und bis zum Ende des ganzen Strings läuft. Ein String hat genau so viele Suffixe wie ihre einzelne Zeichen. 1 String: 'CGCATA' Suffixe: CGCATA GCATA CATA ATA TA A Ein Suffix kann über seine Anfangsposition im String eindeutig bestimmt werden, da alle Suffixe bis zum Ende des Strings laufen: CGCATA → 0 GCATA → 1 CATA → 2 ATA → 3 TA → 4 A→5 Man sieht gleich, dass der Suffix '0' der String selbst ist. 3. Definition Suffix-Baum Einen Suffix-Baum erhält man, indem man alle Suffixe eines Strings in eine Baumstruktur einträgt. z.B. für den String 'CGCATA': A C TA GCATA TA 3 ATA 2 GCATA 0 1 4 2 In dieser Baumansicht entspricht jedes Blatt ein Suffix von 'CGCATA'. So sind alle Suffixe mit den Blättern des Baums repräsentiert, bis auf den letzten Suffix 'A'. Hier tritt der Fall auf, dass der Suffix 'A' gleichzeitig auch ein Präfix von einem anderen Suffix 'ATA' ist. Um diesen Fall umzugehen, fügt man am Ende des Strings ein Sonderzeichen ein, das sonst in dem String nicht auftaucht. Dadurch können wir in der Baumansicht zeigen, dass auch der Knoten von 'A' zum Ende des Strings führen kann und somit 'A' auch ein Suffix von 'CGCATA' ist. Nach dieser Änderung sieht der Suffix-Baum für den String 'CGCATAɛ' folgendermaßen aus: A C TAɛ GCATAɛ TAɛ ɛ 3 5 ATAɛ 2 GCATAɛ 0 1 4 Ein solcher Baum T für einen String S der Länge n hat dann folgende Eigenschaften [Penkova2005]: – T hat genau n Blätter. – Jeder innere Knoten hat mindestens zwei Kinder. – Die Kanten haben nicht-leere Beschriftungen (Repräsentiert Substrings). – Ein Knoten darf nicht mehr als ein Kind mit derselben Beschriftung haben. – Jeder Pfad vom Wurzel bis zu einem Blatt entspricht einen Suffix des Strings. 4. Komprimierte Suffix-Bäume (Compressed Suffix Tree) Wie oben dargestellt, entspricht jede Kante des Baums einen Teilstring. Aber das Speichern dieser Teilstrings selbst an den Kanten ist sehr platzaufwendig. Besonders bei sehr großen Strings wie Genom-Strings ist das nicht tolerierbar. Allerdings, da diese Teilstrings durch ihre Anfangs- und Endpositionen bestimmt werden können, kann man statt den Teilstrings ihre Anfangs- und Endpositioen an den Kanten speichern, wofür man viel weniger Platz braucht. So ein Baum wird als „komprimierter Suffix Baum“ benannt. 3 Der komprimierte Suffix-Baum für 'CGCATAɛ': 3-3 0-0 4-6 1-6 4–6 3-6 6 -6 3 2 5 1-6 0 1 4 5. Der naive Konstruktionsalgorithmus Wir nehmen an, dass uns ein String S der Länge n vorliegt. Um davon einen Suffix-Baum zu konstruieren, fügen wir im Grunde alle Suffixe von S hintereinander in den Baum ein: – Wir fangen mit dem Suffix mit dem Index '0', also S selbst. – Für den nächsten Suffix, prüfen wir, ob der neu einzufügende Suffix mit einem der schon eingefügten Suffixen einen gemeinsamen Präfix hat, also ob einige Anfangszeichen übereinstimmen. – Falls nein: Der neue Suffix wird einfach an den Wurzel angehängt. – Falls ja: Der Pfad von dem Suffix im Baum mit dem gemeinsamen Präfix wird verfolgt, bis ein erreicht wird, die beide Suffixe nicht gemeinsam haben. An dieser Stelle wird ein neuer innerer Knoten an dem Pfad eingefügt und von diesem inneren Knoten noch ein Blatt, das den neuen Suffix repräsentiert. – Das Verfahren läuft weiter, bis alle Suffixe in den Baum eingefügt worden sind. Dieser naive Algorithmus braucht O(n2) Zeit, daher ist in der Praxis nicht einsetzbar. Im folgenden schauen wir uns das DiGeST Verfahren an, das den Suffix-Baum in linearer Zeit bildet. Dafür erklären wir zuerst den Begriff Suffix-Array. 6. Suffix-Arrays Ein Suffix-Array von einem String S der Länge n ist ein Array, der die Suffixe von S in lexikographischer Reihenfolge behält. Dabei werden im Array nicht die Suffixe selbst sondern ihre Anfangspositionen gespeichert. z.B. das Suffix-Array für den String 'CGCATAɛ': suffixArray[0] = 5 → Aɛ suffixArray[1] = 3 → ATAɛ suffixArray[2] = 2 → CATAɛ suffixArray[3] = 0 → CGCATAɛ suffixArray[4] = 1 → GCATAɛ suffixArray[5] = 4 → TAɛ 4 Suffix-Arrays haben eine Speicherkomplexität von O(n) und können mit der Zeitkomplexität O(n) erstellt werden. III. DiGeST (Disk-Based Genomic Suffix Tree) Der DiGeST-Algorithmus ist von Barsky, Stege, Thomo und Upton entwickelt worden. Er ist ein Festplattenbasierter Suffix-Baum Konstruktionsalgorithmus für Eingabe-Strings, die größer als der verfügbare Arbeitsspeicher sind [Barsky2008]. Der Algorithmus läuft in drei Phasen: – Vorbearbeitung: Partitionieren des Eingabe-Strings in kleinere Teilstrings und Enkodieren der Teilstrings mit einem Binäralphabet, um den Speicherplatz besser auszunutzen. – Sortieren der Suffixe Erstellen von Suffix-Arrays für jeden Teilstring. (Larsson-Algorithmus fürs Sortieren) [Barsky2008] – Zusammenfügen der Suffix-Arrays Zusammenfügen der sortierten Suffix-Arrays in einen Suffix-Baum. Bei der Entwicklung des Algorithmus wurde besonders Wert darauf gelegt, dass die Festplattenzugriffe auf den Eingabe-String optimiert werden. Also so dass während der obigen drei Schritten möglichst wenige Festplattenzugriffe passieren. 1. Vorbearbeitungsphase Der Eingabestring wird in Partitionen aufgeteilt, deren Größe von dem verfügbaren Arbeitsspeicher und von dem Algorithmus benötigten Speicherplatz abhängt. z.B. der Larsson-Algorithmus braucht 8 x N (N = Die ausgewählte Partitiongröße) Bytes für den Aufbau eines Suffix-Arrays für einen Teilstring der Größe N. In [Barsky2008] wird es erwähnt, dass der Algorithmus, nach den Resultaten der Experimente, die Partitionen der Größe 100MB schnell bearbeiten kann. Die Teilstrings werden auch noch mit einem geeigneten Binäralphabet enkodiert, wodurch der benötigte Speicher zum Speichern der Strings reduziert wird. In diesem Fall können die Zeichen des DNA-Alphabets {a, c, g, t} mit einem 2-Bit Binärstring repräsentiert werden. 2. Sortieren der Suffixe Für jeden Teilstring wird ein eigenes Suffix Array durch den 'Larsson-Algorithmus' (Ein SuffixArray Sortierungsalgorithmus, siehe [Barsky2008]) erstellt. Dabei werden beim Suffix-Vergleich ein Präfix der nächsten Teilstring, das sogenannte 'Tail', mitberücksichtigt, damit die Reihenfolge in einzelnen Teiltring-Suffix-Arrays mit der Suffix-Reihenfolge von dem ursprünglichen ganzen String übereinstimmt. Mit einem Beispiel erklären wir den Gebrauch von Tails deutlicher: S = 'ACAGCACCG' sei der Eingabe-String, S1 = 'ACA' der erste Teilstring, S2 = 'GCA' der zweite Teilstring und S3 = 'CCG' der dritte Teilstring nach der Partitionierung. 5 Dann wären die erstellten Suffix-Arrays für die Teilstrings: array1 [0] = 2 → A array2 [0] = 2 → A array2 [0] = 2 → A array1 [1] = 0 → ACA array2 [1] = 1 → CA array2 [1] = 1 → CA array1 [2] = 1 → CA array2 [2] = 0 → GCA array2 [2] = 0 → GCA Diese Partitionierung findet aber nur deswegen statt, weil der Eingabe-String selbst normalerweise zu groß für den Arbeitsspeicher ist. Das eigentliche Ziel ist im nächsten Merge-Schritt des DiGeST-Algorithmus, aus diesen TeilstringSuffix-Arrays einen Suffix-Baum für den gesamten Eingabe-String aufzubauen. Also diese Teilstring-Suffix-Arrays und die Suffixe, die darin sortiert sind, nur im Bezug auf dem ganzen String von Bedeutung. Genauer gesagt, wenn wir für S2 die Teilstring-Suffixe 'A' und 'CA' mit den „lokalen“ Positionen '2' und '1' sortieren wollen, wollen wir eigentlich die Suffixe von S mit den „globalen“ Positionen '5' und '4' sortieren, nämlich 'ACCG' und 'CACCG'. In dieser Hinsicht, wollen wir beim Sortieren von array1 eigentlich die globalen Suffixen 'ACAGCACCG' (Position 0) , 'CAGCACCG' (Position 1) und 'AGCACCG' (Position 2) sortieren, was in die folgende lexikographische Reihenfolge resultieren sollte: 'ACAGCACCG' → 0 'AGCACCG' → 2 'CAGCACCG' → 1 Was aber im array1 nach einer „lokalen“ Sortierung ohne Berücksichtigung des ganzen EingabeStrings anders aussieht: 'A(GCACCG)' → 2 'ACA(GCACCG)' → 0 'CA(GCACCG)' → 1 Daher erweitern wir die Suffixe eines Teilstrings beim Vergleichen um einen Präfix des nächsten Teilstrings mit einer vordefinierten Länge (Das 'Tail') und somit sichern, dass die „lokale“ SuffixReihenfolge in den Suffix-Arrays mit der „globalen“ Reihenfolge übereinstimmt. Üblicherweise werden in den Suffix-Arrays die Anfangspositionen der Suffixe gespeichert, statt der Suffixe selbst. Das würde aber später beim Zusammenfügen der Suffixe mehrfache Festplattenzugriffe verursachen, da die Suffixe miteinander verglichen werden müssen. Um das zu verhindern, hängt man beim DiGeST an den Anfangspositionen auch ein Präfix des Suffixes an. Dieser Präfix wird später beim Suffix-Vergleich verwendet und erst wenn diese angehängten Präfixen der zwei Suffixe gleich sind, findet ein Festplattenzugriff auf den ursprünglichen EingabeString statt, um die Reste der Suffixe zu vergleichen. 6 3. Zusammenfügen der Suffix-Arrays (Merge-Phase) In der Merge-Phase werden die ersten (lexikographisch kleinsten) Elemente dieser Suffix-Arrays miteinander verglichen (Wieder unter Berücksichtigung der 'Tails'!). Der kleinste Suffix wird seinem Suffix-Array entnommen und wird in den Suffix-Baum geschrieben. Der Vergleich geht mit den verbliebenen Suffixen weiter. Dieser Schritt wiederholt sich so lange, bis alle Suffix-Arrays leer sind, also alle Suffixe in den Baum eingetragen worden sind. Das einfügen eines Suffixes in den Baum basiert auf folgenden Prinzipien: – Da die Suffixe im Voraus über Suffix-Arrays lexikographisch sortiert sind, ist es sicher gestellt, dass der neu einzufügende Suffix lexikographisch größer als der zuletzt eingefügte ist. – Der lexikographisch größte Suffix im Baum, also anders gesagt der zuletzt eingefügte, wird mit der äußersten Kante des Baums repräsentiert. (Äußerst rechte Kante, falls die rechten Kinder eines Knotens die lexikographisch größeren sind.) Nach diesen zwei Prinzipien reicht es, um im Baum die richtige Stelle fürs Einfügen zu finden, wenn man für einen neuen Suffix den „längsten gemeinsamen Präfix“ (LCP- Longest common prefix) mit dem zuletzt eingefügten Suffix berechnet, und die äußerste Kante des Baums vom Wurzel um die Länge des LCP verfolgen. An der Stelle, wo LCP aufhört, treten dann zwei Fälle auf: – Der LCP hört an einem Knoten auf. Dann fügt man einfach einen neues Blatt, das den neuen Suffix repräsentiert. – Der LCP hört auf der Kante zwischen zwei Knoten auf. Da wird die Kante aufgeteilt, indem ein neuer innerer Knoten an der Stelle eingefügt wird. Dann wird ein Blatt an diesen neuen inneren Knoten angehängt, das den neuen Suffix repräsentiert. Somit vermeidet man den Aufwand, den man beim naiven Suffix-Baum Konstruktionsalgorithmus hätte, wo man die Kanten traversieren und dabei die einzelne Zeichen vergleichen würde. Wenn der verfügbare Speicherplatz während der Suffix-Baum-Aufbau voll wird, wird der aufgebaute Baum in die Festplatte geschrieben. Das Zusammenfügen der Suffixe wird mit einem neuen Baum fortgesetzt. So entsteht zum Schluss ein „Suffix-Wald“ auf der Platte. Für jeden auf die Festplatte geschriebenen Baum, wird auch der zuletzt eingetragene Suffix (Also der lexikographisch größte in diesem Baum) in einer seperaten Datenstruktur für Suffix-BaumDividers geschrieben. In dieser Datenstruktur wird mit jedem solchen Divider-Suffix auch ein Referenz auf den dazugehörigen Suffix-Baum gespeichert. Bei einer String-Suche über den gebauten Index wird dann der Suchstring zuerst mit den DividerSuffixen verglichen, um zu finden, in welchem Baum des Waldes nach diesem String gesucht werden soll. 4. Ein Beispiel Mit einem Beispiel veranschaulichen wir das DiGeST Verfahren: – S = 'CGCATAɛ' sei der Eingabe-String – S1 = 'CGC' und S2 = 'ATCɛ' seien die Teilstrings nach der Partitionierung. – Der 'Tail', also ein Präfix der S2, der beim Suffix-Vergleich an S1-Suffixe angehängt wird, sei zwei Zeichen lang. Also 'AT'. – Für S2 braucht man kein 'Tail' weil er der letzte Teilstring ist. 7 Dann würden die Teilstring-Suffix-Arrays so aussehen: array1 [0] = 2 → C array2 [0] = 0 → ATCɛ array1 [1] = 0 → CGC array2 [1] = 2 → Cɛ array1 [2] = 1 → GC array2 [2] = 1 → TCɛ Wir nehmen die obersten (Lexikographisch kleinsten) Elemente von jedem Array zum Vergleich: array1 [0] = 2 → C array2 [0] = 0 → ATCɛ Daraus wird wiederum der kleinste Suffix ausgewählt um in den Suffix Baum geschrieben zu werden (Hinweis: Auch hier werden beim Vergleichen die 'Tails' in Betracht gezogen!!): array1 [0] = 2 → C(AT) array2 [1] = 0 → ATCɛ Der Suffix 'ATCɛ' mit der globalen Position '3' wird in den leeren Suffix-Baum eingetragen. Die dadurch entstandene äußerste Kante (Im Moment die einzige Kante im Baum) markieren wir mit Rot: ATCɛ 3 Jetzt werden die Vergleichsfelder „nachgefüllt“, also der nächste Suffix vom array2 wird eingelesen: array1 [0] = 2 → C array2 [1] = 2 → Cɛ array2 [1] = 2 → Cɛ Der kleinste Suffix wird ausgewählt: array1 [0] = 2 → C(AT) Der Suffix 'Cɛ' mit der globalen Position '5' muss in den Baum eingetragen werden. Jetzt muss der längste gemeinsame Präfix (LCP) von der äußersten Kante (Markiert mit rot) und dem neuen Suffix untersucht werden. Da 'ATCɛ' und 'Cɛ' keinen gemeinsamen Präfix haben, hört die Suche nach dem LCP schon da auf, wo sie angefangen hat, nämlich am Wurzelknoten. So wird der neue Suffix einfach als ein neues Blatt hinzugefügt (Neue äußerste Kante mit Rot markiert): ATCɛ 3 Cɛ 5 8 Also nochmal wird der nächste Suffix vom array2 nachgeladen, und der kleinste Suffix wird ausgewählt: array1 [0] = 2 → array1 [0] = 2 → C array2 [2] = 1 → TCɛ C(AT) array2 [2] = 1 → TCɛ Jetzt muss wieder der LCP mit der äußersten Kante untersucht werden. Der Suffix 'Cɛ', den die äußerste Kante repräsentiert, und der neue globale Suffix 'CATCɛ' (Die „globalen“ Suffixen werden in den Baum eingetragen. Hier steht der Teilstring-Suffix 'C' für den globalen String 'CATCɛ' mit der globalen Position '2' !!) haben einen gemeinsamen Präfix → 'C'. Also die LCP-Suche endet auf der Kante, auf der Stelle zwischen den Zeichen 'C' und 'ɛ'. Die Kante wird durch einen neuen inneren Knoten aufgeteilt und ein neues Blatt für den neuen Suffix wird eingefügt: C ATCɛ ATCɛ ɛ 3 2 5 Das Verfahren wird weitergeführt, bis alle Suffixe in den Suffix-Arrays in den Baum eingetragen worden sind: array1 [1] = 0 → array1 [1] = 0 → CGC array2 [2] = 1 → TCɛ CGC(AT) array2 [2] = 1 → TCɛ LCP von 'CGC' (Global 'CGCATCɛ', Position '0') und 'CATCɛ' → 'C'. LCP endet an einem Knoten → Neues Blatt einfügen: C ATCɛ ɛ 3 5 ATCɛ 2 GCATCɛ 0 9 array1 [2] = 1 → array1 [2] = 1 → GC array2 [2] = 1 → TCɛ GC(AT) array2 [2] = 1 → TCɛ LCP von 'GC' (Global 'GCATCɛ', Position '1') mit 'CGCATCɛ' ist → leer Suffix wird an den Wurzel angehängt. C GCATCɛ ATCɛ ɛ 3 GCATCɛ ATCɛ 1 0 2 5 - array2 [2] = 1 → TCɛ - array2 [2] = 1 → TCɛ LCP von 'TCɛ' mit 'GCATCɛ' → leer Suffix wird an den Wurzel angehängt: C GCATCɛ ATCɛ ɛ 3 5 TCɛ GCATCɛ ATCɛ 2 0 1 4 10 IV. Implementierung In dieser Arbeit wurde es versucht eine DiGeST-Implementierung für die C++ Sequenz-AnalyseBibliothek „SeqAn“ [Gogol-Doering2009] zu erstellen. Unten sind die in der Implementierung verwendeten SeqAn-Datenstrukturen gelistet. Für eine detaillierte Erklärung dieser Datenstrukturen und Klassen siehe [Gogol-Doering2009]. 1. Die verwendeten Datenstrukturen Für die Realisierung des Algorithmus ist eine Spezialisierung der SeqAn Klasse 'Index' implementiert. Nämlich „Index_DIGEST“: template<typename TText, typename TSpec> class Index<TText, Index_DIGEST<TSpec> > Die Member-Variablen der Klasse: Ein 'StringSet' zum Speichern des Eingabe-Strings (In Partitionen): typedef StringSet<Dna5String, Owner<ConcatDirect<> > > TDigestStringSet; Ein 'SeqAn-Triple' für einen Suffix (Teilstring Nummer, Suffix Nummer im Teilstring, Präfix): typedef Triple<unsigned, unsigned, Dna5String> TDigestSuffix; Ein 'SeqAn-String' als ein Suffix-Array: typedef String<TDigestSuffix> TDigestSAString; Noch ein 'SeqAn-String' zum Speichern aller Suffix-Arrays: String<TDigestSAString, External<> > suffixArrays; Ein 'StringSet' für Tails: StringSet<Dna5String> suffixArrayTails; Ein 'struct' für einen Suffix-Baum-Knoten: typedef struct DigestSTNode { int startPos; int endPos; unsigned nodeDepth; unsigned edgeLength; bool isLeaf; Dna5 firstChar; unsigned children[5]; } TdigestSTNode; Ein 'SeqAn-String' aus Suffix-Baum-Knoten als Suffix-Baum: String<TDigestSTNode> TDigestSuffixTree Ein 'SeqAn-String' zum Speichern aller Suffix-Bäume in einem „Suffix-Wald“: 11 typedef String<TDigestSuffixTree, External<> > TDigestSuffixForest; Ein 'SeqAn-Triple' als ein Divider-Suffix (Teilstring Nummer, Suffix Nummer im Teilstring, Präfix): typedef Triple<unsigned, unsigned, Dna5String> TDigestSTDivider; Ein 'SeqAn-String' zum Speichern von Dividers: String<TDigestSTDivider> STDividers; Die drei Phasen des DiGeST-Algorithmus sind in der Spezialisierung wie folgt realisiert worden. 2. Vorbearbeitungsphase Findet in den Funktionen readFileInStringSet(...) oder readMultiSeqFileInStringSet(...) statt, wobei die erste Funktion einen rohen Eingabestring in Partitionen mit einer vorgegebenen Größe aufteilt, die dem Index-Objekt im Konstruktor übergeben wird. Die zweite Funktion dagegen liest nur einen bereits partitionierten String aus einer Multi-Sequenz-Datei (z.B. in 'fasta' Format) in ein StringSet<> Objekt. Das binäre Enkodieren des Eingabestrings wurde der Einfachheit halber ausgelassen. 3. Sortieren der Suffixe Findet in der Funktion createSuffixArrays(...) statt. In der Implementierung wird ein standard C-Sortieralgorithmus statt des Larsson-Algorithmus verwendet, um beim Erstellen der Arrays die 'Tails' berücksichtigen zu können. 4. Zusammenfügen der Suffixe Findet in der Funktion buildSuffixTree(...) statt. 12 Literaturverzeichnis Article [Kleffe2001] Kleffe J. Sequenzvergleiche mit Suffixbäumen Article [Penkova2005] Penkova S.; Tchorbadjiiski A. Suffix Trees und Suffix Arrays Article [Barsky2008] Barsky, M.; Stege U.; Thomo A.; Upton C. A New Method for Indexing Genomes Using On-Disk Suffix Trees Article [Doering2008] Döring, A.; Weese, D.; Rausch, T. & Reinert, K. SeqAn An efficient, generic C++ library for sequence analysis BMC bioinformatics, BioMed Central, 2008, 9, 1471-2105, Website: http://www.seqan.de Book [Gogol-Doering2009] Gogol-Döring, A. & Reinert, K. Biological Sequence Analysis Using the SeqAn C++ Library Chapman & Hall / CRC Press, 2009 13