Effiziente Algorithmen

Werbung
Exakte Suche in Strings
(Exact String Matching Problem)
Geg.: Muster P der Länge n, String S der Länge m
(n<m), endliches Alphabet Σ
Ges.: alle Vorkommen von P in S
Bsp.: P = ana, T = bananas
Effiziente Algorithmen
Suchen in Zeichenfolgen: Teil 1
Martin Gruber
Gabriele Koller
Vielzahl von Anwendungen: Textverarbeitung,
Information Retrieval, Datenbanksuche in der
Bioinformatik, ...
1
2
Ansätze zum Pattern Matching
Substring Problem
Naiver Algorithmus - O(m·n):
praktisch proportional zu m+n
Geg.: (langer) String S der Länge m,
endliches Alphabet Σ
Ziel: Vorverarbeitung des Strings in O(m) Zeit;
danach soll für beliebige Patterns P der Länge n
(n<m) in O(n) Zeit bestimmt werden, ob P in T
Suchaufwand unabhängig von der Textlänge!
Lineare Algorithmen - O(m+n):
• 1976 Knuth, Morris und Pratt
• 1976 Boyer, Moore
• 1980 Karp, Rabin
Bsp.: T = bananas
P1 = na, P2 = ana, P3 = ast, ...
Suche nach vielen Patterns im selben String?
3
Suffixe eines Strings
Suffix-Trie
Suffixe für S = bananas:
Sortiert:
S1 =
S2 =
S3 =
S4 =
S5 =
S6 =
S7 =
S2 =
S4 =
S6 =
S1 =
S3 =
S5 =
S7 =
bananas
ananas
nanas
anas
nas
as
s
Sortieren
4
• Suchbaum für m Suffixe
• Einzelne Buchstaben als
Kanteninformation
• Gleiche Anfänge (bzw.
Fortsetzungen) nur
einmal gespeichert
• (Lange) Kantenketten
ohne Verzweigung
• 1 Schritt pro Zeichen
ananas
anas
as
bananas
nanas
nas
s
• Suchaufwand O(n)?
• Erstellungsaufwand O(m)?
P1 = na, P2 = ana, P3 = ast, ...
5
a
n
n
s
2
n
s
a
6
n
n
s
s
a
a
5
4
n
s
a
a
b
s
a
s
1
a
7
3
bananas
6
1
Suffix-Tree
Suffix-Tree - Definition
• Suffix-Tree T für String S
der Länge m enthält alle
Suffixe S1, ..., Sm
• Ableitung aus Trie:
Kantenfolgen ohne
Verzweigung zu einer
Kante zusammenfassen
• Kanteninformation:
(längere) Folge von Zeichen
• Im Blatt für Suffix Si wird
Index i gespeichert
• Suffix-Tree T für einen String S der Länge m ist ein
Baum mit m Blättern (von 1 bis m nummeriert)
• Jeder innere Knoten (außer der Wurzel) hat mindestens
2 Nachfolger
• Jede Kante ist mit einem nicht-leeren Substring von S
beschriftet
• Keine zwei Kanten eines Knotens haben Kantenbeschriftungen mit gleichem Anfangszeichen
• Konkatenation der Kantenbeschriftungen von der Wurzel
bis zu einem Blatt i entspricht Suffix Si =S[i..m],
für alle i = 1, ..., m
a
s
s
n
a
6
s
n
a
s
4
n
a
b
a
n
a
n
a
s
7
s
n
a
s
5
3
2
bananas
1
7
8
Suffix als Präfix eines Suffixes
Suffix-Tree mit Schlusszeichen
• Suffix-Tree für S = banana
• Manche Suffixe enden nicht
in einem Blatt, sondern
mitten auf einer Kante
• Hat weniger als m Blätter
• Problem: Suffix ist Präfix
eines anderen Suffixes
(letztes Zeichen von S
kommt mehrfach in S vor)
• Bsp.: „na“ von „nana“
• Lösung des Präfix-Problems:
Schlusszeichen, das nicht im
String S vorkommt, z.B. $
a
←6
n
a
←4
n
a
n
a
←5
n
a
b
a
n
a
n
a
1
$
$
• Impliziter Suffix-Tree:
ohne Schlusszeichen
(für manche Zwecke
ausreichend)
3
2
a
n
a
6
$
n
a
$
4
b
a
n
a
n
a
$
2
banana
1
n
a
7
$
n
a
$
5
3
banana$
9
10
Suffix-Tree - Speicherbedarf
Suffix-Tree - Eigenschaft E1
•
•
•
•
Substring:
• Für ein Pattern P gibt es in T
einen Weg von der Wurzel
aus ⇔ P ist Substring von S
• P ist Präfix eines Suffixes
m Blätter
< m innere Knoten
< 2m Kanten
< 2m Kanteninformationen
(Anfangs- und Endindex
des Substrings in S, damit
O(1) Speicherplatz pro
Kante)
⇒ Speicherbedarf für T für
m Suffixe: O(m)
a2:2
s7:7
n
3:4
a
66
s7:7
n
a
5:7
s
44
s7:7
b
a
n
a
n
1:7
a
s
22
11
n
a3:4
77
s7:7
n
5:7
a
s
a
n
a
6
s
55
Beispiele:
P = „ana“ ist Präfix von
S2=„ananas“ und S4=„anas“,
P = „ast“ ist kein Präfix
eines Suffixes
33
bananas
bananas
1234567
11
s
s
n
a
s
4
b
a
n
a
n
a
s
2
1
n
a
7
s
n
a
s
5
3
bananas
12
2
Suffix-Tree - Eigenschaft E2
Suchen im Suffix-Tree
Anzahl und Lage von Substrings:
• Wenn P in S mehrmals (k-mal)
vorkommt, dann hat der durch
das Ende des Pfades für P
n
festgelegte Unterbaum k
a
Blätter
• Indizes dieser Blätter geben n
Vorkommen von P in S an as
• gilt nicht im impliziten T
2
Bsp.: P = „ana“ in S2=„ananas“
und S4=„anas“
Ist Pattern P vorhanden? = Ist P Substring von S?
a
s
s
6
s
4
b
a
n
a
n
a
s
1
n
a
7
s
n
a
s
5
3
bananas
13
Beginne in Wurzel von T;
i ← 1;
Wiederhole:
Mit i-tem Zeichen von P verzweigen;
i ← i +1;
Kante verfolgen und Kanteninfo prüfen, dabei
bei jedem überprüften Zeichen: i ← i +1;
Bis Ende von P erreicht oder keine passende Fortsetzung
von P in T mehr möglich
Aufwand: O(n) - proportional zur Patternlänge
Schnelles Suchen im Suffix-Tree (1)
Schnelles Suchen im Suffix-Tree (2)
Voraussetzung: Pattern ist garantiert vorhanden
Suche für das in S vorhandene Pattern P alle
Vorkommen
Wieviele gibt es? Wo beginnen sie?
Algorithmus: schnell, alle Kanteninfos ignorieren!
• P bis an sein Ende verfolgen
• von erreichtem Knoten aus zu allen k Blättern
des Unterbaums (max. 2k Kanten)
• In je einem Schritt von Knoten zu Knoten
• Kanteninfo kann übersprungen werden
• Zeichentiefe d des erreichten Knotens ist
entscheidend für Weitergehen oder Ende
Aufwand: O(n*)
n* = Anzahl der Knoten auf dem Suchweg
14
Aufwand: O(n* + k)
k = Anzahl der Vorkommen von P
15
16
Naiver Algorithmus zum Aufbau von T
Suffix Trees in O(m) Zeit erstellen
Idee: Suffixe nacheinander zu T hinzufügen
Weiner (1973): Linear Pattern Matching
Algorithms, Proc. of the 14th IEEE Symp. on
Switching and Automata Theory
Erzeuge Kante für S1 inkl. $ und Blatt mit Beschriftung 1
Für alle Suffixe Si mit i = 2 bis m:
Finde längsten übereinstimmenden Pfad für Si
Am Ende dieses Pfades: Füge falls nötig Knoten in die
Kante ein, erzeuge neue Kante und neues Blatt mit
Beschriftung i
„The algorithm of 1973“ (Knuth)
McCreight (1976): A Space-Economical Suffix
Tree Construction Algorithm, J. ACM 23
Lange Zeit wenig bekannt, weil „extremely difficult to
understand“ (Gusfield)
Ukkonen (1995): On-Line Construction of SuffixTrees, Algorithmica 14
⇒ Aufwand linear in O(m)?
Viel einfacher (Gusfield), wird hier erklärt
17
18
3
Einfachster Aufbau-Algorithmus
Pfaderweiterungen (1)
Ti: impliziter Suffix-Tree für die i ersten Zeichen
von S, enthält also alle Suffixe des Strings S[1..i]
T enthalte Pfad für E = S[j..i-1], füge S[i] an
Fall 1: E endet in einem Blatt
⇒ letzte Kanteninfo um S[i] erweitern
Konstruiere Baum T1
Phase i
Für i ← 2 bis m:
// für jedes Zeichen
Für j ← 1 bis i:
// für alle bisherigen Teil-Suffixe
Verlängere den Pfad für S[j..i-1] um S[i] // Ti -1 →Ti
(naiv: von Wurzel aus schnell hingehen)
...
E1
E2 +S[i]
...
Pfaderweiterung i,j
j
Was passiert an diesem Pfad in Phase i+1?
Aufwand: Anzahl der Knoten auf dem Weg +
O(1) für Verlängern
// Tm →T
Füge $ an alle Pfade für S1 ,..., Sm an
⇒ einfach, übersichtlich, ineffizient - O(m³)
19
20
Pfaderweiterungen (2)
Pfaderweiterungen (3)
Fall 2: E = S[j..i-1] endet im Inneren des Baumes
und es gibt keine Fortsetzung mit S[i]
⇒ neue Kante und Blatt einfügen (a+b),
eventuell sogar neuen inneren Knoten (b)
Fall 3: E +S[i] ist schon im Baum enthalten
⇒ nichts tun
a)
...
E1
...
E1
...
E2
b)
...
...
E1
...
E2
E1
...
E2+F
...
F
G
j
E1
...
E2
S[i]
F G
...
E2+S[i]+F
S[i]
F
j
Aufwand: AnzKnoten für Weg + O(0) für nichts tun
Aufwand: AnzKno auf dem Weg + O(1) f. Veränd.
21
22
Aufwand für Pfaderweiterung
Bemerkungen zur Pfaderweiterung
Beim einfachsten Algorithmus pro Phase:
O(m²) Pfaderweiterungen =
O(m²) Pfade + O(m²) Veränderungen
Fall 1: automatische Erweiterung bei Verwendung
von [a:i] für die letzte Kanten-information von E
Fall 2: Es entsteht mindestens ein neuer Knoten
(Blatt), das geschieht höchstens m-mal
⇒ Mit zwei effizienzsteigernden Maßnahmen kann man
Anzahl der Pfaderweiterungen, bei denen tatsächlich
etwas zu tun ist, auf O(m) reduzieren
Fall 3: nichts tun
23
24
4
1. Effizienzsteigerung:
Einmal Blatt, immer Blatt
2. Effizienzsteigerung:
j-Schleife bei Fall 3 abbrechen
Wenn nach der Pfaderweiterung für j in Phase i
der Pfad für S[j..i] in einem Blatt endet → für
dieses j Fall 1 in allen späteren Phasen
(automatische Erweiterung)
Bedingung ist erfüllt bei:
Die (innere) j-Schleife von Phase i kann bei
Auftreten von Fall 3 abgebrochen werden
Begründung: Im Fall 3 in Phase i bei j muss Pfad
für S[j..i] schon in Ti-1 gewesen sein
(Pfaderweiterungen mit kleinerem j haben
Veränderungen in größerer Zeichentiefe als
i - j + 1 verursacht, sie können also S[i] nicht in
dieser Tiefe erzeugt haben).
Da S[j..i] schon in Ti-1, sind auch S[k..i] für k > j
schon dort (Substrings) → Fall 3 für diese k
Fall 1 (Pfad endet schon in Blatt)
Fall 2 (Blatt wird als Pfadende erzeugt)
Beweis: In Phase k = i+1 (und k > i+1) tritt immer
wieder Fall 1 auf.
Keine Regel erweitert über ein Blatt hinaus
25
26
Verbesserte Pfaderweiterung (1)
Verbesserte Pfaderweiterung (2)
Pfaderweiterung unter Verwendung der
beiden effizienzsteigernden Maßnahmen
• Für alle j mit Pfaderweiterung nach Fall 1
oder 2 in Phase i oder früher: in späteren
Phasen nichts mehr tun
• j-Schleife der Phase i+1 beginnt also mit
dem Index, bei dem Fall 3 auftrat, und
läuft von dort aus bis wieder Fall 3 auftritt
Phase:
i:
i+1:
i+2:
→j
p ... q
q q+1 .... r
r r+1 ...
Es ist also bei insgesamt höchstens 2m
Pfaderweiterungen etwas zu tun
27
28
Verbesserter Aufbau-Algorithmus
Verbesserter Algorithmus: Aufwand
Algorithmus unter Verwendung der beiden
Effizienzsteigerungen
• Anzahl der Pfaderweiterungen: nur mehr O(m)
• Jede Änderung im Baum: O(1)
Konstruiere Baum T1;
j ← 1;
Für i ← 2 bis m+1:
// S[m+1] = $
Wiederhole:
Pfaderweiterung (i, j):
Wenn Fall 1 oder Fall 2: j ← j+1; // nächstes j
// Wenn Fall 3: j-Schleife beenden
Bis Fall 3 oder j > i
// j > i: E leer
Aber: die jeweiligen Pfade von der Wurzel zum Ort
des Geschehens können viel Zeit erfordern
Lösung: Verwendung von Suffix-Links
(dann muss man i.A. nicht bei der Wurzel beginnen)
29
30
5
Suffix-Tree - Eigenschaft E3
Suffix-Links - Beispiel
Suffix-Link:
a
• Für jeden inneren Knoten v auf dem Pfad für P = yE gibt
es einen entsprechenden inneren Knoten s(v) für P' = E
(bei leerem E gilt s(v) = Wurzel)
Beweis: Es muss Pfade für yEF und
yEG mit F≠G geben, damit v existiert.
Daher gibt es auch Pfade für EF
und EG und daher existiert auch s(v);
für alle Vorgängerknoten gilt dasselbe.
v
F
F
G
n
a
6
s
E
yE
s(v)
G
j+1
s
s
n
a
s
4
b
a
n
a
n
a
s
2
1
n
a
7
s
n
a
s
5
3
bananas
j
31
3. Effizienzsteigerung: Suffix-Link
32
Pseudocode zu Ukkonens Algorithmus
Wenn beim Baum-Aufbau ein neuer Knoten v am
Ende von S[j..i-1] entsteht: v merken
(siehe: Pfaderweiterung (i, j), Fall 2b)
Bei der anschließenden Pfaderweiterung (i, j+1)
am Ende von S[j+1..i-1] findet man den Knoten
s(v) oder erzeugt ihn neu:
Suffix-Link v → s(v) einfügen.
Übergang von j zu j+1 immer in einem Schritt über
den Suffix-Link, unmittelbar vor dem Blatt des
Pfades j, nur direkt nach Entstehen des Knotens
muss man einen Schritt zurückgehen.
Konstruiere Baum T1;
j ← 1; i ← 2; Z ← Zeiger auf Wurzel;
Wiederhole:
Gehe mit Z an das Ende des Pfades j;
Pfaderweiterung (Z, i, j);
Wenn NK: Suffix-Link Z0 → Z eintragen; NK zurücksetzen;
Wenn Fall 1 oder Fall 2: j ← j+1;
Wenn Fall 2b: Z0 ← Z; NK ← neuer Knoten;
Z ← Suffix-Link(Z) oder mit Z um 1 zurückgehen;
Wenn Fall 3: i ← i+1;
// nächste Phase
Bis j > i;
// j > i: E leer
33
34
Suffix-Tree - Zusammenfassung
Verwaltung der Nachfolger
• Ein Suffix-Tree für einen String der Länge m
lässt sich mit Hilfe von Ukkonens Algorithmus
in O(m) Zeit und O(m) Platz konstruieren
(bei konstantem Alphabet Σ)
• Im Suffix-Tree können beliebige Patterns der
Länge n in O(n) Zeit gefunden werden
zum Teil abhängig von der Größe des Alphabets Σ
• Array: mit |Σ| Einträgen pro Knoten
Platz: O(m·|Σ|), Zeit: O(1)
• Lineare Liste:
Platz: O(m), Zeit: O(|Σ|)
• Balancierter Baum:
Platz: O(m), Zeit: O(log |Σ|)
⇒Suffix-Trees: effiziente Lösung für eine Vielzahl
komplexer Stringprobleme
• Hashtabelle:
Platz: O(m), Zeit: O(1), aber Hashfunktion
35
36
6
Aufwand mit Berücksichtigung von Σ
Suffix-Tree - Verallgemeinerung (1)
Speicherplatz O(m·|Σ|)
Konstruktion O(m)
Suche
O(n)
Suffix-Tree für eine Menge von Strings
Idee:
• jeder String bekommt eigenes Schlusszeichen
• alle werden aneinandergehängt
• Blätter erhalten String- und Suffix-Index
oder:
Speicherplatz O(m)
Konstruktion Min(O(m·log m), O(m·log |Σ|))
Suche
Min(O(n·log m), O(n·log |Σ|))
T enthält „uninteressante“ Suffixe → kürzen
37
Suffix-Tree - Verallgemeinerung (2)
38
Suffix-Tree für 2 Strings
Idee:
S1: banana$
S2: band$
• jeder String bekommt gleiches Schlusszeichen
• alle werden nacheinander eingefügt
a
Ann.: Strings S1 und S2 haben gemeinsamen
Anfang: S[1..i], z.B. S1 = banana, S2 = band
• S1 mit Ukkonen‘s Alg. einfügen
• Phasen 1 bis i wurden für S2 schon
durchgeführt: bei Phase i+1 weitermachen
• für alle Strings wiederholen
a
d
$
1:6
n
a
$
1:4
1:2
a
n
a
$
d
$
2:1
$
1:7, 2:5
a
2:4
$
2:2
n
d
$
b
a
n
$
n
n
a
$
$
d
$
1:5 2:3
Blattinfo:
String:Position
1:3
1:1
39
40
Wiederholung Suffix-Tree
a
Effiziente Algorithmen
s
s
n
a
Suchen in Zeichenfolgen: Teil 2
6
s
n
a
s
Martin Gruber
Gabriele Koller
4
b
a
n
a
n
a
s
2
1
41
n
a
s
n
a
s
Substring Problem:
7
5
3
bananas
Geg.: String S der Länge m,
Pattern P der Länge n,
endliches Alphabet Σ
Suffix-Tree-Aufbau: O(m)
Suche nach P: O(n)
42
7
Demo-Programm strmat
Suffix-Tree für großes Alphabet
Webseite von Dan Gusfield:
http://www.cs.ucdavis.edu/~gusfield/strmat
• Natürliche Sprachen
Sammlung von C-Programmen für String
Matching Algorithmen
Aufwand für Aufbau des Suffix-Trees:
• Weiner, McCreight, Ukkonen:
O(m) für konstantes Alphabet
• Farach (1997):
O(m) für Integer-Alphabet im Intervall [1,m]
(z.B. chinesische Schriftzeichen)
• Integer-Alphabet
43
44
Farachs Ansatz
Farachs Ansatz - 1. Schritt (1)
Divide-and-Conquer-Prinzip:
• Erstelle kompaktierten Trie To für alle
„ungeraden Suffixe“ (mit m/2 Blättern)
• Berechne aus To den Suffix-Tree Te für
die „geraden Suffixe“
• Füge To und Te zu T zusammen - O(m)
Suffix-Tree To für „ungerade Suffixe“ erstellen
• Paare bilden: 5 4 3 5 5 1 4 2 5 5 6
• Paare mit Radix-Sort sortieren → String S'
i
1
2
3
4
5
6
S' = 4 1 3 2 5 6
Rangzahl von Paar i
• Suffix-Tree TS' für S' rekursiv aufbauen
• To aus TS' ableiten
Bsp.: S = 5 4 3 5 5 1 4 2 5 5 6 (TRITTBRETT$)
45
Farachs Ansatz - 1. Schritt (2)
Suffix-Tree TS'
5
1
(6) 11:11
7
(5) 10:11
3
(4) 2:11
(4) 7:11
6
(5
1: )
1
(1) 6:11
5
To
(3) 3:11
1
Farachs Ansatz - 2. Schritt (1)
S=54355142556
(6) 6:6
3
(5) 5:6
(4) 1:6
4
(3) 3:6
(1) 2:6
2
(2) 4:6
S' = 4 1 3 2 5 6
46
Suffix-Tree Te für gerade Suffixe aus To ableiten
• „Ungerade Suffixe“ mit vorangehendem Zeichen
der „geraden Suffixes“ und mittels stabilem
Radix-Sort sortieren:
S=54355142556
lex. Reihenfolge in To: 3 7 5 1 9 11
11
9
(4,3) (1,7) (5,5) (2,9) (5,11) → (1,7) (2,9) (4,3) (5,5) (5,11)
Blattindex: i → 2i-1
Bei gleichem Anfangszeichen Zwischenknoten einfügen
47
→Lex. Reihenfolge für gerade Suffixe: 6 8 2 4 10
→Longest Common Prefix (lcp) berechnen
(-1)
48
8
Farachs Ansatz - 2. Schritt (2)
Farachs Ansatz - 3. Schritt
• Lex. Reihenfolge für gerade Suffixe: 6 8 2 4 10
• lcp für benachbarte Suffixe (über To):
Suffix-Trees To und Te zusammenfügen
• Kanten aus To und Te mittels gekoppelter
Tiefensuche der Reihe nach zu T hinzufügen
• Merge: Kanten mit gleichem Anfangszeichen
verschmelzen
→ es könnte zuviel verschmolzen worden sein
• Unmerge: jeden inneren Knoten u prüfen, ob
Verschmelzung bis an diese Stelle passt
Aufwand für Merge und Unmerge: jeweils O(m)
lcp(l2i, l2j) = lcp(l2i+1, l2j+1) + 1, wenn S[2i] = S[2j]
lcp(l2i, l2j) = 0, sonst
Te
(4) 2:11
6
8
2
4:
4
(5) 5:11
(2) 8:11
S=54355142556
(1) 6:11
(5
)
(6) 11:11
lcp(l6, l8) = 0
lcp(l8, l2) = 0
lcp(l2, l4) = 0
lcp(l4, l10) = 1
4
10
49
50
Farachs Ansatz - 3. Schritt: Merge
S=54355142556
5
1
9
11
x: Zeichentiefe(x) = 5
lcp(S2,S7) = 1
(6) 11:11
3
y
(5)
5:6
z
(4) 2:11
8
2
4
10
6
7
(1) 6:11
(6) 11:11
10
(5) 1
:1
x
(4) 7:11
1
9
S=54355142556
(6) 11:1
1
2:6
(4) 7:11
(5) 5:11
4
5
5:6
(2) 8:11
2
(4) 2:11
(2) 8:11
(1) 6:11
3
(4)
(3) 3:11
2
8
(5)
(1) 6:11
(4) 2:11
8
6
4:
4
7
T
11
(4) 7:11
(1) 6:11
(2) 8:11
6
9
(6) 11:1
1
(5) 1
:1
(4
)2
:6
(4) 7:11
(5
)
Te
(6) 11:11
(5) 10:11
1
(3) 3:11
5
11
Baum passt bis zu Knoten u ⇔ Zeichentiefe(u) = lcp(Si,Sj)
(i aus Te, j aus To; u Vorfahr von Si und Sj)
T
(6) 11:11
7
(4) 2:11
(4) 7:11
3
(1) 6:11
(3) 3:11
(5
1: )
1
(1) 6:11
To
Farachs Ansatz - 3. Schritt: Unmerge
y: Zeichentiefe(y) = 1
lcp(S1,S4) = 1
10
z: Zeichentiefe(z) = 3
lcp(S4,S9) = 2
4
51
Resultierender Suffix-Tree
Zusammenfassung Suffix-Trees
T
S=54355142556
5
4
(6) 11:11
(4) 2:11
1
5: 5
(6) 11:11
2
11
(5)
(1) 6:11
7
(1) 6:11
3
(5) 1
:1
(3) 3:11
(2) 8:11
8
Suffix-Trees eignen sich hervorragend für
verschiedene Suchaufgaben in Strings
String S der Länge m, Pattern P der Länge n
• Aufbau des Suffix-Trees für S in O(m)
(6) 11:1
1
2 :2
(2) 8:11
6
(3) 3:11
(1) 6:11
(4)
52
- Konstruktionsalgorithmus für konstante Alphabete
von Ukkonen (1995)
- Konstruktionsalgorithmus für große Alphabete von
Farach (1997)
10
• Suche nach P im Suffix-Tree in O(n)
• Platzbedarf O(m)
9
53
54
9
Suffix-Array
Suffix-Array: Beispiel
Manber, Myers (1991)
Speichersparende Datenstruktur für Suffixe
String S der Länge m, Pattern P der Länge n
• Suffix Si: si, ..., sm (für i = 1 bis m)
Grundidee:
• m Suffixe lexikographisch ordnen, nur SuffixIndex speichern
• mit binärer Suche ein Suffix mit Präfix P suchen
• maximal log2 m Vergleiche P ⇔ Si
1
S
2
3
4
5
6
↓
a
n
a
n
a
s
↓
b
a
n
a
n
a
s
↓
a
n
a
s
↓
n
a
n
a
s
1
7
b a n a n a s
SA
↓
a
n
a
n
a
s
↓ ↓ ↓
n a s
a s
s
2
3
4
5
6
7
2 4 6 1 3 5 7
↓ ↓ ↓ ↓ ↓ ↓
a a b n n s
n s a a a
a
n n s
s
a a
n s
a
s
55
Einfachste Datenstruktur
1
2
3
4
5
6
Suffix-Array durch einfaches Sortieren
Suffix-Array für String S der Länge m
• Mit Standard-Sortierverfahren sortieren
7
b a n a n a s
S
Pos
56
2 4 6 1 3 5 7
1 bananas
2 ananas
3 nanas
4 anas
5 nas
6 as
7s
S3 = nanas
Pos: Array zur Festlegung der lexikographischen
Ordnung
Pos[k] = i, wenn Si an k-ter Stelle
Nur S und Pos speichern
Sortieren
2 ananas
4 anas
6 as
1 bananas
3 nanas
5 nas
7s
⇒ Einfach, aber ineffizient ⇒ Aufwand?
57
58
Suffix-Tree und Suffix-Array
Suffix-Array aus Suffix-Tree ableiten
Suffix-Tree
• Konstruktion des Suffix-Trees: O(m)
• Traversieren in lexikal. Reihenfolge und Blattindizes der Reihe nach im Array eintragen: O(m)
T
Suffix-Array
a
s
s
n
a
6
s
n
a
s
4
b
a
n
a
n
a
s
n
a
SA
2
4
6
1
3
5
7
S
b
a
n
a
n
a
s
7
s
n
a
s
5
7
6
4
2
3
5
Traversieren
SA
2
4
6
1
3
5
7
3
1
2
⇒ Suffix-Array in linearer Zeit konstruierbar
1
59
60
10
Einfachste Suche (1)
Einfachste Suche (2)
Einfachste Suche für „P in S?“ (ohne Sonderfälle)
Sonderfälle:
• P < SPos[1] oder P > SPos[m]: P kein Substring
• P = SPos[1] oder P = SPos[m]: erster/letzter Suffix ist Lösung
1 2 3 4 5 6 7
L ← 1; R ← m;
Pos 2 4 6 1 3 5 7
Wiederhole M ← (L + R) / 2;
Wenn P < SPos[M]: R ← M;
L
M
R
Wenn P > SPos[M]: L ← M;
Bis (P ist Präfix von SPos[M]) oder (L + 1 = R);
Nachbehandlung nach der binären Suche:
a) Frage „Ist P Suffix?“: Längen überprüfen
b) Alle Vorkommen von P in S: von M aus nach links und
rechts gehen, solange es Lösungen gibt
c) Anzahl der Vorkommen (wenn sehr oft): Je eine binäre
Suche für linke und rechte Grenze
Ergebnis: P in S vom Pos[M]-ten Zeichen an
61
62
Einfachste Suche (3)
Ineffizienz beim Vergleichen
Vergleichen von Substrings: Zeichen für
Zeichen vergleichen (ab erstem Zeichen)
Oft stimmen die vorderen Zeichen der beiden Substrings
schon überein → mit lcp ermitteln
Funktion Plcp(i) = lcp(P, SPos[i]): schwach monoton steigend
bis zur Lösung (wenn es eine gibt), dort Höhe n, dann
schwach monoton fallend
Aufwand: log2 m String-Vergleiche zu je
max. n Zeichen-Vergleichen: O(n·log2 m)
Plcp
n
...
0
...
i
1
m
63
64
Schnelleres Vergleichen mittels lcp (1)
Schnelleres Vergleichen mittels lcp (2)
Es sei bekannt:
l = lcp(P, SPos[L])
r = lcp(P, SPos[R])
(für l ≥ r, sonst symmetrisch mit Rlcp):
l
a) l > Llcp[M]: c = Llcp[M],
Llcp[M] = c
Lösung links
P L M
c?
lcp
l
?
?
r
L
M
R
i
Ges.: c = lcp(P, SPos[M])
Lösung gefunden, links oder rechts von M?
Weiters seien bekannt:
Llcp[M] = lcp(SPos[L], SPos[M])
Rlcp[M] = lcp(SPos[M], SPos[R])
b) l = Llcp[M]: falls P[l+1..k] =
SPos[M][l+1..k] → c = l+k,
P[c+1] >/< SPos[M][c+1]
⇒ Lösung links/rechts
c) l < Llcp[M] ⇒ c = l,
Lösung rechts
65
l
k
c = l+k
Llcp[M]
P L M
Llcp[M]
c=l
P L M
66
11
Effizienzsteigerung
Zusätzliche Arrays Llcp und Rlcp
Zusammenfassung:
Für festes m kommt man bei der binären Suche zu
einem M immer auf demselben Weg
(beginnend mit L = 1 und R = m), man hat dort
immer die selben Werte von L und R
a) und c)
Keine Zeichenvergleiche erforderlich, c kann einfach
aus Werten am Rand des Intervalls hergeleitet werden
b) Jeder der insgesamt k (≤ n) Zeichenvergleiche erhöht
Max(l,r) um 1
Wenn Llcp[M] = lcp(SPos[L], SPos[M]) und
Rlcp[M] = lcp(SPos[M], SPos[R]) bekannt:
bei binärer Suche max. 2n Zeichenvergleiche nötig
Gesamtaufwand für Suche: O(n + log2 m)
Können wir alle Llcp[M] und Rlcp[M] vorab berechnen?
⇒ Es gibt nur m-2 Tripel (L,M,R) für M = 2, ..., m-1
⇒ Für jeden String S mit m Zeichen kann man die
Arrays Llcp[M] und Rlcp[M], M = 2, ..., m-1 vorausberechnen
67
Binärbaum mit lcp-Werten
68
Berechnung der lcp-Arrays
lcp(1,1)
Für benachbarte Blätter im Suffix-Tree i,i+1:
lcp(i,i+1) = Zeichentiefe von v, wobei v der
innere Knoten auf dem Weg vom Blatt i zum
Blatt i+1 mit niedrigster Zeichentiefe ist
Für (i, j) mit j > i+1:
lcp(i, j) = Min(lcp(k, k+1)) für k = i, ..., j-1
lcp(1,2)
lcp(1,2)
lcp(1,4)
lcp(2,3)
lcp(2,4)
lcp(3,4)
lcp(1,8)
lcp(4,5)
lcp(4,6)
lcp(5,6)
lcp(4,8)
lcp(6,7)
⇒ Aufwand für Suche im Suffix-Array (Pos, Llcp,
Rlcp): O(n + log2 m)
Llcp[M]
Rlcp[M]
lcp(6,8)
lcp(7,8)
69
Suffix-Tree für lcp-Berechnung
70
Direkte Erstellung des Suffix-Arrays
T
Manber und Myers (1991):
Direkte Konstruktion des Suffix-Arrays in
O(m log m)
0
a
1
n
a
3
n
a
s
s
6
s (3)
4
(2)
2
(1)
s
2
b
a
n
a
n
a
s
n
a
s
n
a
s
5
(6)
3
(5)
7
(7)
Bsp.: lcp(i,i+1)
lcp(1,2) = 3
lcp(2,3) = 1
lcp(3,4) = 0
Kärkkäinen, Sanders (2003):
Direkte Konstruktion des Suffix-Arrays in O(m)
= Skew-Algorithmus
Bsp.: lcp(i,j):
lcp(2,4) = 0
1
(4)
71
72
12
Struktur des Skew-Algorithmus
Skew-Algorithmus - Datenstrukturen
Divide-and-Conquer-Prinzip:
1. Erstelle das Suffix-Array SA12 für die
Suffixe an Positionen i mod 3 ≠ 0
2. Berechne das Suffix-Array SA0 für die
übrigen Suffixe mit Hilfe von SA12
3. Füge beide Suffix-Arrays zusammen
Lexikographische Ordnung der Suffixe
• SA: Suffix Array für S
Ord.
1 2 3 4
SA[i] = j: an i-ter Stelle
der lex. Ordnung steht Sj SA 2 4 6 1
• SO: Ordnungsfeld für S
1 2 3 4
Suffix
enthält S*
SO[j] = i: Sj steht in der
SO 4 1 5 2
lex. Ordnung an Stelle i
(welches Suffix ist lexikographisch kleiner?)
5
6
7
3 5 7
5
6
7
6 3 7
SO aus SA bestimmen: SO[SA[i]] = i
Aufwand: O(m)
Eingabe: String S über Integer-Alphabet Σ
73
74
Skew Algorithmus - 1. Schritt
Skew Algorithmus - 2. Schritt
1. Wähle Tripel an Positionen i mod 3 ≠ 0
Suffix-Array SA0 für übrige Suffixe Si
erstellen (jene mit i mod 3 = 0)
2. Sortiere diese Tripel mit Radix-Sort
Si = (S[i], Si+1)
3. Berechne SO12 und SA12 für diese Tripel
(ev. rekursiv)
Reihenfolge der Si+1 ist aus SA12/SO12
bekannt, nur stabil nach S[i] sortieren
75
76
Skew Algorithmus - 3. Schritt
Suffix-Tree versus Suffix-Array
Vergleich von Si aus SA0 mit Sj aus SA12:
S: String der Länge m, P: Pattern der Länge n
Fall 1: j mod 3 = 1:
Si = (S[i], Si+1), Sj = (S[j], Sj+1)
wenn S[i]=S[j]: Reihung nach Si+1 und Sj+1
Erstpublikation
Datenstruktur
Speicherbedarf
Fall 2: j mod 3 = 2:
Si = (S[i], S[i+1], Si+2), Sj = (S[j], S[j+1], Sj+2)
wenn S[i]=S[j]: Reihung nach S[i+1] und S[j+1]
wenn S[i+1]=S[j+1]: Reihung nach Si+2 und Sj+2
Suchaufwand
Suffix-Tree
1973
1 Baum
O(2m)
Knoten
O(n)
Suffix-Array
1991
3 Arrays
Θ(3m)
Integers
Θ(n + log m)
zusätzlich von Größe des Alphabets abhängig
77
78
13
Anwendungen für Suffix-Trees
Exaktes String Matching
Exaktes String Matching
Longest Common Substring
Linearisierung zirkulärer Strings
Substrings für Pattern-Datenbank
DNA-Kontaminierung
Common Substring von > 2 Strings
Geg.: String S, Pattern P
Ges.: Ist P in S enthalten?
Varianten:
• Geg.: String S fix, viele Patterns P
Ges.: alle Vorkommen von P in S
• Geg.: Pattern P fix, viele Strings S
Ges.: alle Vorkommen von P in S
79
80
Longest Common Substring (LCS)
Longest Common Substring - Lösung
Geg.: zwei Strings Sa, Sb (Länge: ma, mb)
• Allg. Suffix-Tree für Sa und Sb erstellen
- Blatt entspricht Suffix aus Sa und/oder Sb
- jeden internen Knoten mit 1 und/oder 2 markieren,
wenn Unterbaum Blatt aus Sa und/oder Sb enthält
- mit 1+2 markierte Knoten: Substrings aus Sa und Sb
Ges.: längster Substring, der in Sa und Sb
enthalten ist
Bsp.: TRITTBRETT, RAUBRITTER
• Liefere Knoten mit größter Zeichentiefe, der mit
1+2 markiert ist
„vermutlich nicht linear lösbar“ (Knuth, 1970)
Linearer Aufwand: O(ma+mb)
(Baum-Konstruktion und Traversieren)
81
82
Linearisierung zirkulärer Strings
Linearisierung zirkulärer Strings - Lsg.
Geg.: zirkulärer String S mit Nummerierung
1 bis m (ab beliebiger Position)
• S an beliebiger Position durchschneiden
→ linearer String L;
• Suffix-Tree für LL$ aufbauen
• Suffix-Tree bis Zeichentiefe m traversieren:
immer lexikalisch kleinste Kante wählen
• Beliebiges Blatt aus Unterbaum gibt
Schnitt-Position an
Ges.: Schnitt-Position, sodass resultierender
linearer String der lexikalisch kleinste aller
m möglichen Strings ist
Anwendung: zirkuläre Moleküle (Chemie)
Aufwand: O(m)
83
84
14
Literatur (1)
Literatur (2)
Dan Gusfield:
Algorithms on Strings, Trees, and Sequences
Cambridge University Press, 1997
Udi Manber, Gene Myers:
Suffix Arrays: A New Method for On-line String
Searches
SIAM Journal on Computing 23, S. 262-272, 1993
Martin Farach:
Optimal Suffix Tree Construction with Large
Alphabets
Proc. of the 38th Annual Symposium on
Foundations of Computer Science, S. 137-143,
IEEE, 1997
Juha Kärkkäinen, Peter Sanders:
Simple Linear Work Suffix Array Construction
Int. Colloquium on Automata, Languages and
Programming (ICALP 2003), Springer LNCS 2719,
S. 943-955, 2003
85
86
Substrings für Pattern-Datenbank
Verallgemeinerung des Substring Problems
Effiziente Algorithmen
Geg.: fixe Menge von Strings (Datenbank)
mit großer Gesamtlänge m, Folge von
Strings P
Suchen in Zeichenfolgen: Teil 3
Anhang: Weitere Anwendungen
Ges.: alle Strings aus der Datenbank, die p
als Substring enthalten
Bsp.: DNA-DB für US-Militärpersonal
87
88
Substrings für Pattern-DB - Lösung
DNA-Kontaminierung
• Allg. Suffix-Tree für Strings in DB
aufbauen
• Suche nach Substring wie bisher (auch für
alle Vorkommen)
• Gesamter String ist in der DB, wenn Pfad
für P an einem Blatt endet
Geg.: String Sa und String Sb
Aufwand: O(m) für Aufbau des Suffix-Trees
+ O(n) bzw. O(n+k) für jede Suche
Sa: neue DNA
Sb: mögliche Verunreinigungen
Ges.: alle Substrings von Sb in Sa, die eine
Länge l (Schwellwert) überschreiten
d.h. Kandidaten für Verunreinigung
89
90
15
DNA-Kontaminierung - Lösung
Allg. DNA-Kontaminierung
• Allg. Suffix-Tree für Sa und Sb aufbauen
• Alle internen Knoten, die Blätter aus Sa
und Sb im Unterbaum haben, markieren
• Markierte Knoten mit Zeichentiefe > l
liefern
Verallgemeinerung: Menge von Strings, die
mögliche Verunreinigungen darstellen
Geg.: String S und Menge von Strings P
Ges.: alle Substrings von Strings aus P in S,
die eine Länge l überschreiten
Aufwand: O(|Sa|+|Sb|)
91
92
Allg. DNA-Kontaminierung - Lösung
Common Substrings von >2 Strings
• Allg. Suffix-Tree für S und alle Strings
aus P aufbauen
• Alle internen Knoten, die Blätter aus S und
einem String aus P im Unterbaum haben,
markieren
• Markierte Knoten mit Zeichentiefe > l
liefern
Aufwand: O(Summe der Stringlängen)
Geg.: Menge von K Strings mit
Gesamtlänge von n
Ges.: Finde Substrings in dieser Menge, die
in vielen der Strings vorkommen
93
94
Common Substrings - Lösung (1)
Common Substrings - Lösung (2)
für k = 2, ..., K: l(k) = Länge des längsten
Substrings, der in mindestens k Strings
enthalten ist
Tabelle mit K-1 Einträgen für l(k) und einen
der jeweiligen Substrings erzeugen
Aufwand: O(nK)
(O(n) mittels lowest common ancestor)
• Allg. Suffix-Tree für K Strings aufbauen
- Blätter mit jeweiligem String-Index (1 bis K)
markieren
- jeder String erhält eigenes Schlusszeichen
- C(v) = Anzahl der unterschiedlichen StringIndizes im Unterbaum von internem Knoten v
• Wenn C(v) und Stringtiefe für jeden
Knoten bekannt: l(k) durch Traversierung
in O(n) finden
95
96
16
Common Substrings - Lösung (3)
Berechnung von C(v) in O(n·K)
Bit-Vektor der Länge K an jedem v:
- Bit i = 1, wenn Blatt mit i im Unterbaum von v
- C(v) = Anzahl der 1-Bits
- Bit-Vektoren der Nachfolger oder-verknüpfen
in O(K)
Vektor V(k): Stringtiefe des tiefsten Knotens
mit C(v)=k
97
17
Herunterladen