Exaktes Set-Matching mit Sux- oder Keyword-Trees Thomas Laner (0125355) Bernhard Pugfelder (0027467) Christopher Thurnher (0125913) 10. Januar 2006 In dieser Ausarbeitung werden zwei grundlegende Verfahren zum Finden aller identischen Muster (Patterns) bzw. einer Menge von Mustern (exaktes Set-Matching Problem) innerhalb eines Textes vorgestellt. Das Set-Matching Problem kommt in vielen heutigen Anwendungen zum Tragen, wobei sicherlich die nächstliegende die Suche nach Wörtern oder Wortteilen in Bereich der Textverarbeitung ist. Aber auch die Datenkomprimierung (insb. Lempel-Ziv-Welch), DNS-Sequenzanalyse, Datenbankmanagement oder allgemeine Keyword-Suchprobleme sind beispielhafte Anwendungsgebiete von Set-Matching Algorithmen, wobei die Ezienz eines solchen Matching meist sehr entscheidend ist. Bei den beiden vorzustellenden Verfahren handelt es sich einerseits um die Anwendung von Sux-Trees oder andererseits von Keyword-Trees, welche jedoch aufgrund ihrer unterschiedlichen Konzeptionen in unterschiedlichen Anwendungskontexten zu betrachten sind. Insbesondere die Aufwandsanalysen des Pre-Processing (Konstruktion der Datenstruktur, welche für die Suchoperationen obligatorisch sind), der Suchoperation nach entsprechenden Mustern und der Speicheraufwand der beiden Verfahren werden in dieser Ausarbeitung vorgestellt. Hierzu ist die Ausarbeitung in zwei Abschnitte unterteilt und ist wie folgt organisiert. Der erste Abschnitt stellt die Lösung das Set-Matching Probelm mittels Sux-Trees vor und beinhaltet weiters die Ezienzanalysen der benötigten Tree-Konstruktion, der Suchoperation und des Speicheraufwandes. Der zweite Abschnitt behandelt äquivalent zum ersten Abschnitt die Lösung mittels Keyword-Trees. Aufbau des Keyword-Trees und die Suche von Mustern bzw. zugehörige Ezienzanalysen sind wiederum Teil dieses Abschnittes. 1 Set-Matching mit Sux-Trees 1.1 Skizzierung der Idee Die grundlegende Idee hinter dem Set-Matching Problem ist das Aunden einer Menge von Patterns P = {P1 , . . . , Pk } in einem gewählten String S , welcher aus einzelnen Strings mit Anzahl k besteht. Hierzu soll auch die Häugkeit des Vorkommens von P in S ermittelt werden. Ein Pattern selbst kann ebenfalls aus einem bestimmten Zeichen oder einer beliebigen, zusammengesetzten Zeichenfolge bestehen und zwei Pattern P1 und P2 sind genau dann identisch, wenn beide gleich lang sind und weiters gilt: P1 [i] = P2 [i], ∀i ∈ [1, length[P1 , P2 ]. Sowohl T als auch alle möglichen Pattern P werden über ein Alphabet Σ (Zeichensatz) deniert, wobei |Σ| im Weiteren noch ein entscheidende Eigenschaft für das Laufzeitverhalten eines solchen Set-Matching mittels Sux-Trees ist. 1 Um die Realisierung von Set-Matching ezient zu gestalten, kann das ähnliche Prinzip wie bei sonstigen, bekannten Suchproblemen angewendet werden, nämlich der Aufbau einer gesonderten Datenstruktur als Baum [Wei73, Kos89]. Diese Baumstruktur wird grundsätzlich Sux-Trie genannt und repräsentiert alle vorkommenden Suxe (Wortendungen) von S , so dass alle gültigen Patterns Pi genau in einem bestimmten Sux Sk exakt oder als Substring enthalten ist. Die Anzahl an Suxen für bestimmten String S entspricht genau length(S), d. h. S1 entspricht genau S , für alle weiteren Suxe Si wird entsprechend immer das erste Zeichen entfernt. Slength(S) enthält also genau das letzte Zeichen von S . Das folgende Beispiel mit S = bananas zeigt die Bildung von Suxen: S1 S2 S3 S4 S5 S6 S7 = bananas = ananas = nanas = anas Sortiert =⇒ = nas = as =s S2 S4 S6 S1 S3 S5 S7 = ananas = anas = as = bananas = nanas = nas =s Die erhaltenen Suxe werden nun derart in einen Sux-Trie eingetragen, so dass ein bestimmter Pfad einen Sux repräsentiert und jede Kante ein entsprechendes Zeichen darstellt. Die Sortierung der Suxe ist für einen korrekten Aufbau des Sux-Tries notwendig und bedingt die Reihenfolge der Kinderknoten. Die Abbildung 1 illustriert den Sux-Trie zum obigen Beispiel mit S = bananas. Durch den Aufbau einer Baumstruktur erhält man das optimale Laufzeitverhalten für das Suchen eines beliebigen P mit O(length(P )) (siehe auch 1.3), jedoch muss nun noch der Aufwand für das Pre-Processing (also der Aufwand für die Konstruktion eines solchen Sux-Tries) beachtet werden, welche mit O(count(suf f ixes)2 ) begrenzt ist. Erhöht sich jedoch die Länge von T, und somit auch die Anzahl an Suxen, wird ein linearer Aufwand für das Pre-Processing unbedingt notwendig, so dass anstatt eines Sux-Trie ein modizierter Sux-Tree verwendet wird. Dieser wird durch das Zusammenfassen von Kantenfolgen ohne Verzweigen des Sux-Tries gebildet und kann in O(count(suf f ixes)) erstellt werden. Das Pre-Processing von Sux-Trees ist jedoch stark abhängig von |Σ|, so dass nur unter der Berücksichtigung gewisser Eigenschaften des Sux-Tree der Aufwand mit O(count(suf f ixes)) begrenzt ist (insbesondere bei nicht konstanten Σ). Näheres zu Pre-Processing ist in Unterabschnitt 1.2 zu nden. Abbildung 1: Sux-Trie zu String S = bananas 2 Die Abbildung 2 skizziert den aus Abbildung 1 resultierenden, expliziten Sux-Tree für S = bananas (mit Ende-Zeichen s). Ein impliziter Sux-Tree beinhaltet kein zusätzliches Ende-Zeichen für die einzelnen Suxe. Dies kann zu dem so genannten Präx-Problem führen, d. h. ein bestimmtes Si endet nicht in einem Knoten, sondern ist Teil eines anderen Suxes Sj ⇒ Si ist Präx von Sj . Mit dem Hinzufügen eines spezischen Ende-Zeichen am Ende jedes Si (expliziter Sux-Tree) kann dieses Präx-Problem vermieden werden. Abbildung 2: Expliziter Sux-Tree zu String S = bananas Um die Speicherung der einzelnen Suxe minimal zu halten, kann weiters anstatt der expliziten Zeichenfolge, welche Si repräsentiert, eine Indexierung mit Anfangs- und Endindex des ersten und letzten Zeichens von Si innerhalb von T für jede Kante gespeichert werden. Somit sollte der benötigte Speicherplatz mit O(length(suf f ixes) ∗ |Σ|) begrenzt werden können. Näheres dazu in Unterabschnitt 1.4. Die Abbildung 3 illustriert einen solchen expliziten Sux-Tree anhand des Beispiels S = bananas. Abbildung 3: Expliziter, komprimierter Sux-Tree zu String S = bananas Folgende wichtige Eigenschaften sind wesentlich für die Verwendung von Sux-Trees bei SetMatching Problemen: • Wenn P in S vorkommt ( ⇐⇒ P ist Substring von S ) =⇒ P ist Präx von ein (oder mehreren) Suxen und es existiert genau ein Pfad von der Wurzel aus, welche P enthält. • Wenn P in S k -mal vorkommt, so hat der Konten am Endes des P enthaltenen Pfades genau k Unterbäume. Diese Eigenschaft ist jedoch nur in expliziten Sux-Trees immer gültig. • Jeder innere Knoten v auf dem Pfad P = yE , wobei y als ein beliebiges Zeichen und E als eine 3 beliebige (auch leere) Zeichenfolge deniert sind, existiert immer ein weiterer innerer Knoten v 0 mit dem Pfad P 0 = E . Die Beziehung von v und v 0 wird als Sux-Link bezeichnet. 1.2 Konstruktion des Sux-Trees Das wesentliche Kriterium der Verwendung von Sux-Trees ist ein möglichst ezienter Aufbau des zu S gehörenden Trees, welcher alle denierten Suxe Si , i ∈ [1, length(T )] enthält. Zusätzlich soll jeder aufgebaute Sux-Tree T auch explizit sein, so dass tatsächlich alle Eigenschaften E1-3 gültig sind. Dabei kann ein einfacher, naiver Algorithmus schnell gefunden werden, nämlich das sukzessive Einfügen aller Suxe in den Sux-Tree T . Hierbei wird T mit einer Kante für den Sux S1 (also gesamten String S ) und einem angefügten Ende-Zeichen initialisiert. Danach wird nacheinander die Suxe S2 ...Sm derart eingefügt, dass der längst bisher in T zutreende Pfad gesucht und eine entsprechend notwendige Pfaderweiterung durchgeführt wird. Hierzu kann es notwendig werden, eine bestehende Kante durch einen Knoten aufzuspalten oder einen neuen Knoten an einen bestehenden Elternknoten hinzuzufügen. Dieser naive Algorithmus zeigt aber bei wachsender Anzahl an Suxen in S aufgrund der notwendigen i-Vergleiche für Sux Si ein Laufzeitverhalten von O(m2 ). Um das Laufzeitverhalten auf O(m) zu drücken, müssen die in Unterabschnitt 1.1 denierten Eigenschaften E1-3 genützt werden. Weiters muss noch die Gröÿe das zugrunde liegende Alphabet |Σ| beachtet werden, insbesondere bei einem nicht konstanten |Σ| kann ein Aufbau von O(m) nur unter spezischen Annahmen garantiert werden. Im Falle eines konstanten Alphabets |Σ| = σ ist ein linearer Aufwand unabhängig der Art von Σ garantiert, wobei insbesondere die Algorithmen [Wei73, McC76, Ukk83, Ukk95] eine Lösung präsentieren. Weiters wird in [GK97] die bekanntesten Algorithmen zum Aufbau von Sux-Trees bei Σ = const gegenübergestellt und evaluiert. Ist |Σ| nicht konstant, so kann beispielsweise für ein Σ = N mittels des Algorithmus [Far97] ein Aufbau mit linearer Aufwand realisiert werden. Der Algorithmus aus [Ukk95] basiert auf dem folgenden einfachen Schema, welches die Zeichen 2 . . . m aus S sukzessive in T einfügt, so dass nach dem i-ten Zeichen die Suxe S1...i in Ti enthalten wird. Zusätzlich wird in einem nachfolgenden Schritt noch für alle Suxe S2...m ein Ende-Zeichen eingefügt. Initialisiert wird T1 mit dem Sux Sm mit angefügten Ende-Zeichen Das Hauptkriterium für die Ezienz dieses Aufbau-Algorithmus ist die notwendige Pfaderweiterung für jedes Zeichen i bei der Betrachtung aller bereits eingefügten Teil-Suxe. Hierzu muss sowohl die eigentliche Erweiterung als auch die Suche in bestehenden Pfaden betrachtet werden. • Im Kontext der Pfaderweiterung für jeden Schritt i müssen folgende drei Erweiterungsfälle unterschieden werden: 1. Wenn der Pfad E = S[j . . . i − 1] in einem Blatt endet =⇒ Füge Zeichen S[i] am Ende der letzten Kanteninfo ein. 2. Wenn der Pfad E = S[j . . . i − 1] im Inneren von Ti−1 endet und keine Fortsetzung mit S[i] existiert =⇒ neue Kante und Blatt bzw. falls notwendig auch neuen inneren Knoten einfügen. 3. Wenn E = S[j . . . i − 1] + S[i] bereits in Ti−1 enthalten ist =⇒ keine Erweiterung notwendig. • Für jede Pfaderweiterung muss der Teilpfad E = S[j . . . i − 1] nicht immer von der Wurzel aus gesucht werden, sondern mit Hilfe der Eigenschaft E3 (Sux-Links) kann die Suche bereits nahe bei den Blättern begonnen werden. D. h. der Aufwand für die Suche von Teilpfaden wird dadurch wesentlich verringert. Der Übergang von Ti−1 → Ti kann immer konstant durchgeführt werden, die Anzahl an Pfaderweiterungen kann durch zusätzlichen Bedingungen (Einmal Blatt immer Blatt, Beendigung einer 4 Iteration bei Erweiterungsfall 3) bei der Pfaderweiterung mit O(m) beschränkt werden. Durch die Verwendung von Sux-Links kann ebenfalls die Suche des Teilpfades E auch mit O(m) realisiert werden, so dass insgesamt der Aufbau von T bezüglich eines Strings S mit linearen Aufwand implementiert werden kann. Bei dem nicht konstanten Alphabet Σ = N kann die Methode von [Far97] zum Aufbau des zugehörigen Sux-Tree T mit einem Aufwand von O(m) angewendet werden. Hierbei wird mittels Divide-and-Conquer (also rekursiv) in den ersten beiden Schritten zwei Sux-Tries T0 und T1 erstellt, welche jeweils alle ungeraden bzw. geraden Suxe beinhalten. In einem dritten und letzten Schritt werden T0 und T1 zu dem Sux-Tree T verschmolzen. Dieser Schritt ist entscheidend für die Ezienz, es kann aber gezeigt werden, dass durch die Ausnützung von strukturellen Eigenschaften ein O(m) erreicht werden kann. 1.3 Suchen von Mustern in Sux-Trees Bei der Suche werden mögliche Vorkommen eines Patterns P im Sux-Tree T überprüft. Dabei wird einfach von der Wurzel ausgehend jeweils jene Kante verfolgt dessen Kanteninfo eine passende Fortsetzung von P in T repräsentiert. Dies wird solange wiederholt bis das Ende von P erreicht wird oder aber keine passende Kantenfortsetzung mehr für P in T existiert. Für den Fall dass das Pattern enthalten ist müssen also genau n Zeichenvergleiche getätigt werden, ist P nicht enthalten wird die Schleife schon früher abgebrochen. Daraus ergibt sich ein Aufwand von O(n), wobei n die Länge von P ist. Noch schneller wird die Suche, wenn man davon ausgehen kann dass P garantiert im Suchstring enthalten ist. In diesem Fall wird die Kanteninfo einfach ignoriert und nur noch die Zeichentiefe d ist entscheidend für ein Weitergehen oder einen Abbruch, dabei gelten folgende Regeln: d=n d>n d<n Ende von P in diesem Knoten Ende von P liegt um d-n Zeichen davor auf der Kante Weiterverzweigen Dadurch reduziert sich der Aufwand auf O(n0 ), wobei n0 die Anzahl der Knoten auf dem Suchweg repräsentiert. Bei der Suche nach allen Vorkommen muss man zusätzlich vom erreichten Ende von P zu allen, von dort aus erreichbaren Blättern weitergehen. Die Anzahl der Vorkommen k entspricht der Anzahl der erreichten Blätter, dazu gibt es maximal 2k Kanten und man benötigt auch nur ebenso viele Verzweigungen. Also folgt man der Reihe nach allen Verzweigungen, daher ist der Aufwand O(n0 +k). 1.4 Speichern von Sux-Trees Aufgrund der vielen Kanteninformationen wäre der Speicherbedarf sehr groÿ. Aus diesem Grund bedient man sich eines einfachen Tricks: Jede Kanteninformation ist ein Substring des zu durchsuchenden Strings S. Wenn man statt des Substrings lediglich die Indizes p (von) und q (bis) speichert, so ist der Speicherbedarf für den gesamten Sux-Tree für m Suxe O(m), wobei m die Länge des Strings ist. Zur Verwaltung der Nachfolger eines Knotens gibt es folgende Varianten: Arrays Das Array hat die Gröÿe |Σ|, daher ergibt sich der Gesamtplatzbedarf für einen Sux-Tree von O(m · |Σ|), dafür kann der Zugri aber in konstanter Zeit erfolgen (O(1)). Lineare Liste (sortiert oder unsortiert) Der Platzbedarf ist proportional zur Anzahl der Nachfol- ger also der Anzahl der Knoten, da jeder Knoten mit Ausnahme der Wurzel Nachfolger eines Knotens ist. Durch die Proportionalität zu m, ist der Platzbedarf in O(m). Die Zugriszeit 5 allerdings ist proportional zu |Σ| bzw. zur Gröÿe des Alphabets also in O(|Σ|), was vor allem bei groÿen Alphabeten sehr langsam ist. Balancierte Bäume Platzbedarf gleich wie bei linearen Listen, aber die Zugriszeit etwas schneller, nämlich in O(log |Σ|). Hashtabellen Hashtabelle der Gröÿe O(m). Zugri in konstanter Zeit (O(1)), aber nur unter der Annahme das die Berechnung der Hashfunktion in konstanter Zeit berechnen lässt. Knoten nahe der Wurzel haben meist sehr viele Nachfolger (folglich wären Arrays besonders gut geeignet), während weiter entfernte Knoten dagegen meist weniger Nachfolger haben (aus Speicherplatzgründen eine der anderen Varianten besser geeignet). Aus diesem Grund wäre daher eine Mischung der genannten Datenstrukturen ideal. 1.5 Zusammenfassung Die folgende Tabelle faÿt nochmals den Aufwand für die Operationen Konstruktion und Suchen im Falle von String-Matching und in Abhängigkeit zu Σ dar. S ...zu Grunde liegender String T ...zugehöriger Sux-Tree P ...Menge von Mustern Pi ...einzelnes Muster aus P ki ...Vorkommnisse von Pi Tabelle 1: Operation Aufwand Aufbau von T O(length(S)) Suchen von Pi O(length(Pi )) Aufwand bei Verwendung von Arrays zur Darstellung von T . Im Kontext von Set-Matching wird der Aufwand entsprechend der Menge P angepaÿt: Gesamtaufwand bei Set-Matching O(length(S) + P length(Pi ) + P ki ) 2 Set-Matching mit Keyword-Trees 2.1 Skizzierung der Idee Der Aho-Corasick-Algorithmus [AC75] wurde 1975 von Alfred V. Aho und Margaret J. Corasick entwickelt. Anders als beim Sux-Tree baut diese Methode ausgehend von Keywords einen endlichen Automaten auf und vergleicht diesen dann mit dem Eingabetext. Dieses Verfahren bietet sich besonders an, wenn die Keywords nach denen gesucht wird bekannt sind und sich nicht oft andern, da dann der Aufbau des Automaten oine geschehen und er zur späteren Verwendung abgespeichert werden kann. 6 2.2 Konstruktion des Keyword-Trees Bevor die Suche in einem beliebigen Text beginnen kann, muss der Keyword-Tree anhand von bestimmten vorher bekannten Keywords aufgebaut werden. Das Verhalten des Keyword-Tree Automaten wird durch 3 Funktionen bestimmt, der goto, output und failure Funktionen. Die Zustände bzw. Knoten des Automaten werden durchnummeriert, die Zustandsübergange werden anhand der goto Funktion bestimmt. Sie bestimmt, mit welchem Zeichen man von einem Zustand in einen anderen wechseln kann. Von einem Knoten aus kann es nie 2 Kanten mit den gleichen Zeichen geben. Am Anfang hat man nur einen Knoten mit Nummer 0. Ausgehend vom diesem Knoten werden nacheinander die Buchstaben der Keywords eingefügt, die goto Funktion für jeden Zustand wird dabei erstellt. Endet an einem Knoten ein Keyword, so wird die output Funktion für diesen Knoten mit dem Keyword versehen. Am Ende dieser Phase schaut der Automat dann wie in Abbildung 4 aus (Keyword-Tree mit Keywords he, she, his, hers ). Abbildung 4: Keyword-Tree mit goto (Zustandsübergänge) und output Funktionen in geschwungenen Klam- mern Danach muss noch für jeden Knoten die failure Funktion berechnet werden. Diese Funktion gibt an, bei welchem Zustand im Automaten fortgesetzt werden soll, wenn beim Suchen im Text ein Zeichen kommt, für dass es keinen geeigneten Übergang im aktuellen Zustand gibt. Diese Funktion wird für alle Knoten mit ansteigender Tiefe berechnet. Zunächst wird für alle Knoten mit Tiefe 1 die failure Funktion auf 0 gesetzt (0 ist die Nummer des Knotens, also der Startknoten). Für alle anderen Knoten wird dann folgendes Verfahren verwendet: Man merkt sich das Zeichen a mit dem man von Vorgängerknoten r in den aktuellen Knoten s gekommen ist und setzt die Variable state auf den Zustand f ailure(r). Falls im Knoten state nun ein Zustandsübergang existiert, mit dem man mit den Zeichen a in einen anderen Zustand übergehen kann (goto(state, a) existiert), so setzt man f ailure(s) auf goto(state, a). Danach muss man noch die output Funktion von s mit der output Funktion von f ailure(s) verschmelzen. Falls kein solchen Übergang existiert, so setzt man state auf f ailure(state) und versucht es erneut. Falls nie ein passender Übergang gefunden wird, man also wieder im Startknoten angekommen ist, so setzt man f ailure(s) auf 0. In Abbildung 5 sind zum oberen Beispiel nun auch die failure Funktionen eingezeichnet. Beispielsweise merkt man sich für den Knoten 4 merkt das Zeichen h, geht entlang der failure Funktion von Zustand 3 (Vorgängerknoten) auf Zustand 0, dort kommt man mit goto(0, h) auf Zustand 1, also ist die failure Funktion von Zustand 4 Zustand 1. Analog dazu ist die failure Funktion von Zustand 5 Zustand 2. Da in Zustand 2 jedoch der Wert der output Funktion he ist, muss man noch die output Funktion von Zustand 5 (she ) mit der von Zustand 2 verschmelzen. Die Konstruktion gliedert sich also in 2 Teile. der erste Teil ist der Aufbau des Automaten anhand der Keywords. Der Aufwand dafür ist O(n) (n ist die Anzahl der Kanten). Für die Fehlerlinks gilt: Es kann nie mehr Rückschritte geben als die aktuelle Tiefe des Knotens. Da die failure Funktionen 7 Abbildung 5: Keyword-Tree mit failue Funktion (gestrichelte Pfeile) der vorangegangenen Knoten schon existieren, man also nicht alle vorhergehenden Knoten noch mal abarbeiten muss, kann es höchstens so viele Rückwärtsschritte wie Knoten, also maximal n geben. Daher ist die Laufzeit für die Konstruktion der failue Funktion O(n). 2.3 Suchen von Mustern in Keyword-Trees Hat man einmal den Automaten erstellt, kann man ganz einfach die verarbeiteten Keywords in einem beliebigen Text in linearem Zeitaufwand nden. Der Text wird als Eingabe String übergeben. Für jedes Zeichen im Text geht man im Automaten anhand der goto Funktion in den nächsten Zustand über. Falls die output Funktion dieses Knotens einen Wert hat, bzw. ein Keyword darin endet, so wird es ausgegeben. Falls im aktuellen Zustand kein kein Übergang mit dem aktuellen Zeichen existiert, dann liefert die goto Funktion fail. In diesem Fall geht man die failure Funktion des aktuellen Zustands entlang, solange bis die goto Funktion des neuen Zustands mit dem aktuellen Zeichen nicht fail liefert, oder man wieder im Startknoten ist. Der Suchaufwand beim Keyword-Tree liegt im O(m) (m ist die Länge des Textes). Jedes Zeichen wird einzeln abgearbeitet, für jedes Zeichen führen wir einen goto und möglicherweise mehrere failure Schritte aus. Nach einem goto Schritt bleibt man entweder im Startknoten oder man geht in einen anderen Knoten dessen Tiefe um 1 gröÿer ist als der vorherige; die maximale Tiefe ist m. Da jeder failure Schritt in einen Knoten mit geringerer Tiefe endet, kann man pro Zeichen maximal m failure Schritte benötigen. Da man aber erst einmal erfolgreiche (goto) Schritte braucht um in eine höhere Tiefe zu gelangen, kann man höchstens so viele failure Schritte benötigen wie goto Schritte. Daher ist der Suchaufwand mit 2m begrenzt, was einen Aufwand von O(m) entspricht. 8 2.4 Zusammenfassung Hier nur Auistung der Ezienzdaten zu den oben erwähnten Operationen. n ...Anzahl der Knoten m...Anzahl der Zeichen im Text Tabelle 2: Keyword-Tree Aufwand Konstruktion O(n) Suche O(m) Aufwand der Verwendung des Keyword-Trees bei Set-Matching Problemen. Literatur [AC75] [Far97] A. V. Aho and M. J. Corasick. Ecient string matching: An aid to bibliographic search. Communications of the ACM, 18(6):333340, 1975. M. Farach. Optimal sux tree construction with large alphabets. In Proceedings of , pages 137143, Miami Beach, Florida, 1997. the 38th IEEE Annual Symposium on Foundations of Computer Science [GK97] Robert Giegerich and Stefan Kurtz. From Ukkonen to McCreight and Weiner: A unifying view of linear-time sux tree construction. Algorithmica, 19(3):331353, 1997. [Heu05] V. Heun. Kapitel 2: Algorithmische Bioinformatik. http://wwwmayr.informatik. tu-muenchen.de/lehre/2002SS/cb/lecturenotes/chapter2.pdf, Mai 2005. Slides of the Lecture [Kil05] Algorithmische Bioinformatik in PDF-Format. Pekka Kilpeläinen. Lecture 4: Set matching and Aho-Corasick Algorithm. http://www. cs.uku.fi/~kilpelai/BSA05/lectures/slides04.pdf, Spring 2005. Slides of the Lecture Biosequence Algorithms in PDF-Format. [Kos89] S. Rao Kosaraju. Ecient tree pattern matching. In Proceedings of the posium on the Foundations of Computer Science, pages 178183, 1989. 30th IEEE Sym- [McC76] Edward M. McCreight. A space-economical sux tree construction algorithm. 23(2):262272, 1976. J. ACM , [Ukk83] Esko Ukkonen. On approximate string matching. In Proceedings of the 1983 International FCT-Conference on Fundamentals of Computation Theory, pages 487495, London, UK, 1983. Springer-Verlag. [Ukk95] Esko Ukkonen. On-line construction of sux trees. , 14(3):249260, 1995. Algorithmica [Wei73] P. Weiner. Linear pattern matching algorithms. In Proceedings of the Symposium on Switching and Automata Theory, pages 111, 1973. 9 14th IEEE Annual