80
Analyse og implementering af suffikstræer baseret p˚ a udvidede suffiksarrays 0-[0..11] 1-[0..3] 1-[5..6] 1-[7..10] 2-[7..8] 3-[9..10] 4-[1..2] 0 1 2 3 4 5 6 7 8 9 10 11 7 4 1 10 0 9 8 6 3 5 2 11 ippi$ issippi$ ississippi$ i$ mississippi$ pi$ ppi$ sippi$ sissippi$ ssippi$ ssissippi$ $ Speciale af Sune Sloth Simonsen 22. januar 2007 Vejleder: Christian N. Storm Pedersen Datalogisk Institut - Aarhus Universitet

Analyse og implementering af suffikstræer baseret p˚a ... · Kapitel 1 Introduktion Suffikstræet1 er en interessant datastruktur, der har en lang række anvendelsesmuligheder inden

  • Upload
    lamnga

  • View
    220

  • Download
    0

Embed Size (px)

Citation preview

Page 1: Analyse og implementering af suffikstræer baseret p˚a ... · Kapitel 1 Introduktion Suffikstræet1 er en interessant datastruktur, der har en lang række anvendelsesmuligheder inden

Analyse og implementering af suffikstræerbaseret pa udvidede suffiksarrays

0-[0..11]

1-[0..3] 1-[5..6] 1-[7..10]

2-[7..8]

3-[9..10]

4-[1..2]

0 1 2 3 4 5 6 7 8 9 10 11

7 4 1 10 0 9 8 6 3 5 2 11

ippi$

issippi$

ississippi$

i$

mississippi$

pi$

ppi$

sippi$

sissippi$

ssippi$

ssissippi$

$

Speciale af Sune Sloth Simonsen 22. januar 2007Vejleder: Christian N. Storm PedersenDatalogisk Institut - Aarhus Universitet

Page 2: Analyse og implementering af suffikstræer baseret p˚a ... · Kapitel 1 Introduktion Suffikstræet1 er en interessant datastruktur, der har en lang række anvendelsesmuligheder inden

Resume

Suffikstræet er en af de vigtigste datastrukturer til strenganalyse af biologiske data sasomDNA og proteiner. Yderligere har suffikstræet anvendelsesomrader indenfor datakompressionog beregning af en lang række basale datalogiske strengproblemer. I praksis bruges suffiks-træet primært til at analysere store statiske data. I dette tilfælde bliver suffikstræets relativtstore pladsforbrug til en flaskehals for, hvor store mængder data man kan analysere. I 2004præsenterede Mohamed Ibrahim Abouelhoda, Stefan Kurtz og Enno Ohlebusch en løsning padette problem i artiklen Replacing suffix trees with enhanced suffix arrays. Artiklen beskriver,hvordan enhver algoritme baseret pa et suffikstræ kan udskiftes med en mere pladsbesparendealgoritme, der anvender et udvidet suffiksarray i stedet. Samtidigt vil den nye algoritme væreasymptotisk ligesa hurtig som den oprindelige algoritme.

Dette speciale tager udgangspunkt i teknikkerne introduceret i artiklen Replacing suffixtrees with enhanced suffix arrays til at implementere et suffikstræ, der bruger et udvidetsuffiksarray som underliggende datastruktur. Som del af denne analyse formidles teorien iartiklen mere grundigt, og der tilføjes eksempler og illustrationer.

I specialet vises, at det er muligt at implementere et suffikstræ baseret pa et udvidetsuffiksarray uden at øge den asymptotiske tidskompleksitet. Det opnaede resultatet kombine-rer de bedste egenskaber fra de to datastrukturer, nemlig suffikstræets intuitive struktur ogsuffiksarrayets lave pladsforbrug.

Page 3: Analyse og implementering af suffikstræer baseret p˚a ... · Kapitel 1 Introduktion Suffikstræet1 er en interessant datastruktur, der har en lang række anvendelsesmuligheder inden

Abstract

The suffix tree is one of the most important data structures when analyzing biological datasuch as DNA and proteins. Furthermore the suffix tree has applications in data compressionand computation of basic string problems. In practice the suffix tree is primarily used foranalyzing huge statical data. In this situation the space consumption of the suffix tree isbecoming the limiting factor to how large data can be analyzed. In the year 2004, MohamedIbrahim Abouelhoda, Stefan Kurtz and Enno Ohlebusch presented a solution to this problemin the article Replacing suffix trees with enhanced suffix arrays. The article describes how tosystematically substitute every algorithm based on a suffix tree with a more space efficientalgorithm that uses an enhanced suffix array instead and still solves the same problem withoutincreasing the time complexity.

This master thesis uses the knowledge developed in the article Replacing suffix trees withenhanced suffix arrays as a basis to implement a suffix tree that uses an enhanced suffix arrayas the underlying data structure. As part of this analysis the theory in the original article iscommunicated in a more thorough manner including additional examples and illustrations.

The thesis shows, that it is possible to implement a suffix tree based on an enhancedsuffix array without increasing the time complexity of the operations on the tree. The resultcombines the best of the two data structures, the intuitive structure of the suffix tree and thesmall memory print of the suffix array.

Page 4: Analyse og implementering af suffikstræer baseret p˚a ... · Kapitel 1 Introduktion Suffikstræet1 er en interessant datastruktur, der har en lang række anvendelsesmuligheder inden

Indhold

1 Introduktion 31.1 Problemformulering . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 41.2 Krav . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 5

2 Notation og begreber 72.1 Strenge . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 7

2.1.1 Præfiks . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 72.1.2 Suffiks . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 7

2.2 Suffikstræer . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 72.3 Suffiksarrays . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 82.4 Længste fælles præfiks . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 9

3 Relation mellem suffikstræer og udvidede suffiksarrays 113.1 Lcp-intervaller . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 113.2 Lcp-interval-træer . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 133.3 Børne-tabel for lcp-interval-træet . . . . . . . . . . . . . . . . . . . . . . . . . . 13

3.3.1 Bestemmelse af børne-intervaller . . . . . . . . . . . . . . . . . . . . . . 153.4 Navigation i lcp-interval-træet ved brug af børne-tabellen . . . . . . . . . . . . 20

3.4.1 Algoritme til at finde børne-intervaller i konstant tid . . . . . . . . . . . 203.4.2 Algoritme til at finde børne-intervallet for et givet tegn . . . . . . . . . 20

3.5 Suffikslinks-tabel for et lcp-interval-træ . . . . . . . . . . . . . . . . . . . . . . . 213.6 Suffikslinks i lcp-interval-træet . . . . . . . . . . . . . . . . . . . . . . . . . . . 233.7 Bottom-up-gennemløb af lcp-interval-træet . . . . . . . . . . . . . . . . . . . . 24

4 Konstruktion af tabeller 284.1 Konstruktion af suffiksarrays . . . . . . . . . . . . . . . . . . . . . . . . . . . . 284.2 Konstruktion af tabel for længste fælles præfiks . . . . . . . . . . . . . . . . . . 304.3 Konstruktion af børne-tabellen . . . . . . . . . . . . . . . . . . . . . . . . . . . 324.4 Konstruktion af suffikslinks-tabellen . . . . . . . . . . . . . . . . . . . . . . . . 36

5 Optimeringer 385.1 Pladsoptimering af tabel for længste fælles præfiks . . . . . . . . . . . . . . . . 385.2 Pladsoptimering af børne-tabellen . . . . . . . . . . . . . . . . . . . . . . . . . . 395.3 Pladsoptimering af suffikslinks-tabellen . . . . . . . . . . . . . . . . . . . . . . . 45

6 Implementering 476.1 Modul: libcommon . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 476.2 Modul: libcollection . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 486.3 Modul: libsabst . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 516.4 Modul: libsuffixtree . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 53

1

Page 5: Analyse og implementering af suffikstræer baseret p˚a ... · Kapitel 1 Introduktion Suffikstræet1 er en interessant datastruktur, der har en lang række anvendelsesmuligheder inden

6.5 Test . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 55

7 Eksperimentel evaluering 577.1 Benchmark: Construction . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 577.2 Benchmark: Size . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 597.3 Benchmark: Exact String Match . . . . . . . . . . . . . . . . . . . . . . . . . . 617.4 Benchmark: Find All Matches . . . . . . . . . . . . . . . . . . . . . . . . . . . . 637.5 Benchmark: Longest repeated substring . . . . . . . . . . . . . . . . . . . . . . 657.6 Benchmark: Tandem Repeats . . . . . . . . . . . . . . . . . . . . . . . . . . . . 65

8 Konklusion 70

Litteratur 72

A Ekstra benchmarks 74

2

Page 6: Analyse og implementering af suffikstræer baseret p˚a ... · Kapitel 1 Introduktion Suffikstræet1 er en interessant datastruktur, der har en lang række anvendelsesmuligheder inden

Kapitel 1

Introduktion

Suffikstræet1 er en interessant datastruktur, der har en lang række anvendelsesmulighederinden for bioinformatik og datakompression. Yderligere er det muligt at løse mange datalogiskestrengproblemer meget effektivt ved brug af et suffikstræ. Jeg vil herunder præsentere nogleaf de mest kendte anvendelser af suffikstræet for at give en ide om, hvor vigtig en datastruktursuffikstræet er.

• Det er muligt at afgøre om P er en delstreng af X i O(|P |) tid. Yderligere kan allepositionerne hvor P optræder i X findes i O(|P | + z) tid, hvor z er antallet af steder,hvor P optræder i X.

• Den længste delstreng, der mindst optræder to gange i inputstrengen X, kan findes iO(|X|) tid.

• Det er muligt at finde længste fælles delstreng for k strenge X1, X2, ..., Xk iO(|X1|+ |X2|+ ... + |Xk|+ k) tid, hvilket er lineær tid.

• Det længste palindrom i stregen X kan findes i O(|X|) tid.

• Et tandem repeat i en streng X er en triple (i,D, 2) som opfylder: 2

X[i..i + D − 1] = X[i + D..i + 2D − 1]

Det er muligt at finde alle tandem repeats i en streng X i O(|X| · log |X|+ z) tid, hvorz er antal fundne tandem repeats. For mere information om tandem repeats se sektion7.6.

• Et maksimal-par i en streng X er en triple (i, j, l) sadan, at følgende er opfyldt:

X[i..i + l − 1] = X[j..j + l − 1] ∧ X[i− 1] 6= X[j − 1] ∧ X[i + l] 6= X[j + l]

Hvis (i, j, l) er et maksimal-par, er X[i..i + l − 1] en maksimal-gentagelse. En super-maksimal-gentagelse er en maksimal-gentagelse, der ikke er delstreng af nogen andenmaksimal-gentagelse.

Alle maksimal-par, maksimal-gentagelser og super-maksimal-gentagelser for strengen Xkan findes O(|X|+ z) tid, hvor z er antallet af fundne maksimale-par.

1For en beskrivelse af denne datastruktur se sektion 2.22Hvis du har problemer med at forsta notationen, henviser jeg til kapitel 2.

3

Page 7: Analyse og implementering af suffikstræer baseret p˚a ... · Kapitel 1 Introduktion Suffikstræet1 er en interessant datastruktur, der har en lang række anvendelsesmuligheder inden

1. Introduktion 4

• Lempel-Ziv dekomposition af en streng X kan udføres i O(|X|) tid. Lempel-Ziv dekom-position bruges i datakompression. For yderligere detaljer henviser jeg til [AKO04, s.65].

• Matching statistics bliver brugt til approximate string matching og kan udføres i lineærtid ved brug af et suffikstræ[AKO04, s.76].

Som man kan se, har suffikstræet mange anvendelsesmuligheder, og det er endda kun etlille udpluk. Dog er suffikstræet ikke uden problemer. Suffikstræet bruger i værste fald lidtover 24 bytes pr. tegn i inputstrengen3. Og da det netop er for meget store statisk data,at de fleste anvendelser af suffikstræet findes, bliver dette et alvorligt problem. Som løsningintroducerede Udi Manber og Gene Myers [MM90] i 1989 suffiksarrayet. Suffiksarrayet erligesom suffikstræet en datastruktur, der understøtter, at man kan søge i alle suffikserne af enstreng. Den store fordel ved suffiksarrayet fremfor suffikstræet er, at det har et pladsforbrug pakun 4 bytes pr. tegn i inputstregen. Det er en meget stor pladsbesparelse, som desværre ogsamedfører, at udførelsestiden for de fleste algoritmer er asymptotisk større for suffiksarrayetend for suffikstræet.

En vigtig egenskab ved suffikstræet er, at det kan konstrueres i lineær tid til inputstren-gens længde. Pa daværende tidspunkt eksisterede ingen algoritme til lineær konstruktion afsuffiksarrayet, der ikke gjorde brug af et suffikstræ. Derfor var det først i 2003, at suffiksar-rayet blev virkeligt interessant, da Juha Karkkainen og Peter Sanders løste dette problem iartiklen [KS03].

Som situationen sa ud, havde suffiksarrayet stadig problemer i kraft af sin manglende struk-tur. Det gjorde suffiksarrayet mindre intuitivt at arbejde med end suffikstræet. Samtidigt varsuffikstræet stadig asymptotisk hurtigere end suffiksarrayet for mange kendte strengproble-mer. Disse to problemer gav Mohamed Ibrahim Abouelhoda, Stefan Kurtz og Enno Ohlebuschi 2004 en løsning pa i artiklen [AKO04, Replacing suffix trees with enhanced suffix arrays].Artiklen fastslar, at en hvilken som helst algoritme baseret pa et suffikstræ, kan udskiftes meden mere pladsbesparende algoritme, der arbejder pa et udvidet suffiksarray i stedet. Den nyealgoritme vil samtidigt være asymptotisk ligesa hurtig som den gamle algoritme.

1.1 Problemformulering

For mig at se er der stadig et problem med den nuværende situation. Problemet er, at detikke ligefrem er trivielt at udskifte en suffikstræ-algoritme med en ny algoritme, der bruger etudvidet suffiksarray i stedet. Det kræver, at man sætter sig ind i teorien i artiklen [AKO04], ogslavisk udskifter alle dele af algoritmen til at anvende et lcp-interval-træ4 i stedet. Det primæreproblem er, at artiklen fokuserer pa, hvordan suffikstræ-problemer løses ved brug af et udvidetsuffiksarray i stedet for at fokusere pa, hvordan det udvidede suffiksarray kan opfattes somden underliggende datastruktur for et suffikstræ. Det er netop denne fokusændring, som erdette speciales omdrejningspunkt.

Formalet med dette speciale er, gennem en grundig analyse af artiklen [AKO04, Replacingsuffix trees with enhanced suffix arrays], at anvende teknikkerne fra artiklen til implementeringaf et suffikstræ, der bruger et udvidet suffiksarray som underliggende datastruktur. Malet er,at resultatet skal kombinere det bedste fra suffikstræet, nemlig den intuitive struktur, samtidigmed at suffiksarrayets lave pladsforbrug udnyttes.

Specialets hovedbidrag vil være en undersøgelse af, i hvilken grad det er muligt, at kon-struere et suffikstræ der bruger et udvidet suffiksarray som underliggende datastruktur uden

3For et pladsbesparende suffikstræ baseret pa teorien i artiklen [Kur99].4 Lcp-interval-træ datastrukturen er beskrevet i sektion 3.2

Page 8: Analyse og implementering af suffikstræer baseret p˚a ... · Kapitel 1 Introduktion Suffikstræet1 er en interessant datastruktur, der har en lang række anvendelsesmuligheder inden

1. Introduktion 5

at øge den asymptotiske tidskompleksitet i forhold til et normalt suffikstræ. Yderligere vil jeggennem analysen formidle teorien i artiklen pa en mere tilgængelig made.

Undersøgelsen vil resultere i en implementering af bade et normalt suffikstræ og et suffik-stræ baseret pa et udvidet suffiksarray. Jeg vil teste de to implementeringer op mod hinandenfor at efterprøve, om den suffiksarray baserede implementering rent faktisk er asymptotiskligesa hurtig som et normalt suffikstræ. Derudover vil jeg sammenligne de to implementerin-gers pladsforbrug for at give et billede af, hvor meget der er at vinde ved at bruge et suffikstræbaseret pa et udvidet suffiksarray.

1.2 Krav

For at grænsefladen til det suffiksarray baserede suffikstræ er brugbar, skal den ligne græn-sefladen for et normalt suffikstræ sa meget som muligt. Jeg opstiller derfor følgende krav tilgrænsefladen.Følgende skal være muligt:

• at fa bygget et nyt suffikstræ ud fra en streng X i O(|X| · |Σ|) tid, hvor |Σ| er størrelsenaf alfabetet for inputstrengen.

• ud fra et suffikstræ at fa fat i rod-knuden i O(1) tid.

• at kunne løbe igennem alle nedadgaende kanter fra en knude i O(|Σ|) tid.

• at afgøre om en given kunde er rod i suffikstræet i O(1) tid.

• ud fra en knude, at fa af vide om den har en kant til en børne-knude, som starter medet givet tegn i O(|Σ|) tid.

• ud fra en knude, at fa fat i børne-knuden for enden af den kant der starter med et givettegn i O(|Σ|) tid.

• at fa fat i kanten mellem en forældre-knude og en børne-knude i O(1) tid.

• ud fra en knude, at fa fat i den knude suffikslinket5 peger pa i O(1) tid.

• at afgøre om en given kunde er et blad i O(1) tid.

• at bestemme en knudes dybde i træet i O(1) tid.

• ud fra en knude, at finde delstrengen svarende til vejen fra roden ned til knuden i O(1)tid.

• at fa et blads suffiksnummer6 i O(1) tid.

For en præcis API specifikation af suffikstræet se [Sima]. Yderligere skal implementeringenhave samme tidskompleksitet, som hvis operationerne var udført pa et rigtigt suffikstræ.

Pladsforbruget af det udvidede suffiksarray for en streng af længde n forventes at være:

• 4n bytes for suffiksarrayet (SA)

• n bytes for tabellen for længste fælles præfiks (LCP )

• n bytes for børne-tabellen (CHILD)

5 Suffikslinks i suffikstræet er beskrevet i sektion 3.56Hvert blad i et suffikstræ svarer til et et suffiks i inputsekvensen, suffiksnummeret for bladet er startposi-

tionen af suffikset i inputsekvensen.

Page 9: Analyse og implementering af suffikstræer baseret p˚a ... · Kapitel 1 Introduktion Suffikstræet1 er en interessant datastruktur, der har en lang række anvendelsesmuligheder inden

1. Introduktion 6

• 2n bytes for suffikslink tabellen (SLINK)

• n bytes for bucket tabellen (BCK)

I alt forventes det udvidede suffiksarray altsa at bruge 9n bytes. Bemærk at dette kun er detforventede pladsforbrug, og at det udvidede suffiksarray i nogle situationer vil fylde mere.

For at sandsynliggøre, at implementeringen overholder tids- og pladsforbruget beskreveti artiklerne, og tilmed er fleksibel nok, vil jeg teste implementationen med følgende kendtesuffikstræ-algoritmer:

• Is P a substring of X?

• Exact string match

• Longest repeated substring

• Finding tandem repeats

Hver af disse algoritmer er beskrevet i kapitlet 7.Jeg vil sammenligne resultaterne fra overstaende tests med de samme tests kørt pa et

normalt suffikstræ. Resultaterne skulle gerne stemme asymptotisk overens.

Page 10: Analyse og implementering af suffikstræer baseret p˚a ... · Kapitel 1 Introduktion Suffikstræet1 er en interessant datastruktur, der har en lang række anvendelsesmuligheder inden

Kapitel 2

Notation og begreber

I dette kapitel vil jeg indfører de begreber og den notation, der er nødvendig for at kunne læsespecialet. Dette kapitel kan kun anvendes til en kort genopfriskning af disse begreber, hvis enmere detaljeret gennemgang af begreberne ønskes, henviser jeg til bogen [Smy03, ComputingPatterns in Strings]. Den notation jeg bruger til beskrivelsen af begreberne vil blive genbrugtgennem hele specialet, med mindre andet er nævnt.

2.1 Strenge

En streng X er et array over et alfabetet Σ med længde n = |X|. Et tegn α ∈ Σ pa positioni skrives som X[i], altsa kan hele strenge skrives som en sammensætning af alle tegneneX[0]X[1]...X[n − 1]. Læg mærke til at jeg bruger 0-indekserede strenge, altsa er X[0] detførste tegn i strengen X.

En delstreng ω = X[i]X[i + 1]..X[j] af X fra position i til j skrives X[i..j], hvor i ∈ [0, n]og j ∈ [−1, n− 1]. Hvis j < i er X[i..j] den tomme streng ε.

Her under nævnes to vigtige klasser af delstrenge, suffiks og præfiks. Disse spiller en centralrolle i specialet, sa det er vigtigt at notere sig hvordan de er defineret.

2.1.1 Præfiks

Definition 2.1.1. For i ∈ [−1..n−1] kaldes delstrengen X[0..i] et præfiks af X, hvis i < n−1kaldes det et rigtigt præfiks af X.

2.1.2 Suffiks

Definition 2.1.2. For i ∈ [0..n] kaldes delstrengen X[i..n − 1] et suffiks af X, dette skrivesogsa som Xi. Hvis 0 < i kaldes suffikset et rigtigt suffiks af X.

Læg mærke til at det er muligt at lave et præfiks X[0.. − 1] og et suffiks X[n..n − 1]svarende til den tomme streng.

2.2 Suffikstræer

Da dette speciale handler om, hvordan man kan lave en grænseflade svarende til et suffikstræ,men hvor den underliggende datastruktur er et udvidet suffiksarray, er det selvfølgeligt vigtigtat forsta hvordan et suffikstræ er opbygget og hvilke egenskaber det har.

Et suffikstræ kan defineres som følger:

7

Page 11: Analyse og implementering af suffikstræer baseret p˚a ... · Kapitel 1 Introduktion Suffikstræet1 er en interessant datastruktur, der har en lang række anvendelsesmuligheder inden

2. Notation og begreber 8

7

ppi$

4

ppi$

1

ssippi$

ssi

10

$

i

0

mississippi$

9

i$

8

pi$p

6

ppi$

3

ssippi$

i

5

ppi$

2

ssippi$

si

s11

$

Figur 2.1: Suffikstræ for strengen “mississippi”.

Definition 2.2.1. Et suffikstræ for en streng X er en kompakt trie1, der indeholder de n+1suffikser af strengen af X, hvor hvert suffiks har faet tilføjet $ som det sidste tegn. $ er ettegn, der ikke er indeholdt i alfabetet Σ og er større end alle tegn i Σ. Ved at tilføje $ til endenaf alle suffikserne sikres det, at der findes en unik ordning blandt suffikserne. Tegnet $ kaldesfor sentinel.

Hver knude i suffikstræet kan repræsenteres ved den unikke streng, der dannes hvis mansammensætter alle labels fra roden og ned til knuden. Hver indre knude repræsenterer endelstreng der mindst optræder to gange i X. Hvert blad har associeret et suffiksnummer,der angiver suffikset bladet repræsenterer. Hvis et blad har suffiksnummeret i, vil dette bladrepræsenterer strengen Xi.

Pa figur 2.1 ses suffikstræet for strengen “mississippi”.

2.3 Suffiksarrays

Den vigtigste datastruktur for dette speciale er suffiksarrayet. Suffiksarrayet er kernen i detudvidede suffiksarray, der muliggør at simulere et suffikstræ med meget mindre pladsforbrug.Suffiksarrayet kan defineres pa følgende made:

Definition 2.3.1. En streng X indeholder n+1 suffikser angivet ved deres startposition, detj’te suffiks er altsa Xj = X[j..n− 1] hvor j ∈ [0..n]. Det vil sige, at suffiks 0 er hele strengen,og suffiks n er den tomme streng.

Suffiksarrayet SA for strengen X, er et array af heltal mellem 0 og n, der angiver denleksikografiske ordning af de n + 1 suffikser strengen X indeholder, hvor hvert suffiks har faettilføjet sentinel tegnet $ til enden.

Givet et suffiks array SA for strengen X, kan den stigende leksikografiske ordning afsuffikserne findes som XSA[0], XSA[1], ..., XSA[n].

Pa figur 2.2 ses en tabel hvor første kolonne indeholder alle suffikserne for strengen “mis-sissippi” tilføjet $ og sorteret efter deres suffiksnummer. I anden kolonne er suffikserne sorteretefter stigende leksikografisk orden. I tredje kolonne ses det resulterende suffiksarray for stren-gen “mississippi”.

1Trie datastrukturen er beskrevet i [GT02, s. 429-434]

Page 12: Analyse og implementering af suffikstræer baseret p˚a ... · Kapitel 1 Introduktion Suffikstræet1 er en interessant datastruktur, der har en lang række anvendelsesmuligheder inden

2. Notation og begreber 9

Usorteret Sorteret Suffiksarray0: mississippi$ 7: ippi$ 0: 71: ississippi$ 4: issippi$ 1: 42: ssissippi$ 1: ississippi$ 2: 13: sissippi$ 10: i$ 3: 104: issippi$ 0: mississippi$ 4: 05: ssippi$ 9: pi$ 5: 96: sippi$ 8: ppi$ 6: 87: ippi$ 6: sippi$ 7: 68: ppi$ 3: sissippi$ 8: 39: pi$ 5: ssippi$ 9: 5

10: i$ 2: ssissippi$ 10: 211: $ 11: $ 11: 11

Figur 2.2: Suffikserne for strengen “mississippi” sorteret efter suffiksnummer i første kolonneog efter stigende leksikografisk orden i anden kolonne. Tredje kolonne viser det resulterendesuffiksarray for strengen “mississippi”.

2.4 Længste fælles præfiks

Længste fælles præfiks spiller en afgørende rolle, for at suffiksarrayet overhovedet kan opfattessom en træstruktur, nemlig et lcp-interval-træ. Lcp-interval-træet vil blive introduceret isektionen 3.2.

Det længste fælles præfiks for to strenge X og Y er den længste streng P , der er et præfiksaf bade X og Y . Det vil sige, at P = X[0..i], hvor i er givet ved i = max{j |X[0..j] = Y [0..j]og j ∈ [−1..n− 1]}. Vi betegner længste fælles præfiks med lcp (longest common prefix). Lcpaf k strenge S1, S2, ..., Sk kan findes pa følgende made:

lcp(S1, S2, ..., Sk) = lcp(S1, lcp(S2, ...lcp(Sk−1, Sk)))

Det er ofte nyttigt, at kunne finde længden af lcp for to suffikser, der ligger ved siden afhinanden i den leksikografiskeordning. Det svarer til suffikserne for to tilstødende indgange isuffiksarrayet. Til det formal oprettes en ny tabel LCP , der er defineret pa følgende made:

Definition 2.4.1. LCP er et array af heltal fra 0 til n, LCP [0] er defineret til at være 0,og LCP [i] er længden af det længste fælles præfiks af suffikserne XSA[i−1] og XSA[i], hvori ∈ [1..n].

Eksempel. Pa figur 2.3 ses tabellerne SA og LCP for strengen “mississippi”. Yderligereviser tabellen suffikserne af strengen “mississippi” for suffiksnumrene i SA. Pa hvert suffikser lcp af dette suffiks og det forrige suffiks i tabellen markeret med rødt. F.eks. kan det ses iindgang 1 og 2 i tabellen, at lcp af suffikserne 1 og 4 er strengen “issi”, som er markeret medrødt i indgang 2. Dette svarer til følgende udregning:

lcp(XSA[1], XSA[2]) = lcp(X4, X1) = lcp(issippi$, ississippi$) = issi

Page 13: Analyse og implementering af suffikstræer baseret p˚a ... · Kapitel 1 Introduktion Suffikstræet1 er en interessant datastruktur, der har en lang række anvendelsesmuligheder inden

2. Notation og begreber 10

i SA[i] LCP [i] XSA[i]

0 7 0 ippi$1 4 1 issippi$2 1 4 ississippi$3 10 1 i$4 0 0 mississippi$5 9 0 pi$6 8 1 ppi$7 6 0 sippi$8 3 2 sissippi$9 5 1 ssippi$

10 2 3 ssissippi$11 11 0 $

Figur 2.3: Denne figur viser tabellerne SA og LCP for strengen “mississippi”. Yderligereviser kolonnen XSA[i] suffikset svarende til det pagældende suffiksnummer i SA. Pa suffikseter lcp af dette suffiks og det forrige i SA markeret med rødt.

Page 14: Analyse og implementering af suffikstræer baseret p˚a ... · Kapitel 1 Introduktion Suffikstræet1 er en interessant datastruktur, der har en lang række anvendelsesmuligheder inden

Kapitel 3

Relation mellem suffikstræer ogudvidede suffiksarrays

I dette kapitel vil jeg beskrive relationen mellem suffikstræer og udvidede suffiksarrays. Førstvil jeg indføre lcp-intervaller, der angiver længste fælles præfiks for et interval af tilstødendesuffikser i suffiksarrayet. Informationen om disse intervaller gemmes i tabellen CHILD. Udfra lcp-intervallerne, kan suffiksarrayet opfattes som et træ, nemlig et lcp-interval-træ. Dettetræ svarer til et suffikstræ uden blade, det vil sige, at der er en-til-en korrespondance mellemknuderne i lcp-interval-træet og de indre knuder i suffikstræet. Det viser sig, at man kannavigere rundt i suffiksarrayet, som om det var et suffikstræ. En vigtig pointe er, at lcp-interval-træets struktur kan opretholdes i en tabel, CHILD, der kan optimeres til kun at fylden bytes. Denne tabel indeholder dog kun information nok til at navigere ned gennem træet.For at gøre det muligt, at finde suffikslink-knuden for indre knude i suffikstræet indførestabellen SLINK. Til sidst beskrives, hvordan man kan lave et bottom-up-gennemløb af lcp-interval-træet kun ved brug af LCP tabellen. Dette bruges blandt andet til at bygge CHILDtabellen.

Kapitlet beskæftiger sig kun med definitionerne af de tidligere nævnte tabeller, og hvordanman kan navigere rundt i lcp-interval-træet ved hjælp af disse. Algoritmerne til konstruktion aftabellerne er beskrevet i kapitel 4. Kapitlet omhandler heller ikke en dybdegaende diskussionaf pladsforbrug og optimeringer. Dette vil blive præsenteret i kapitel 5.

Teorien i kapitlet er baseret pa artiklen [AKO04, Replacing suffix trees with enhanced suffixarrays]. Jeg har forsøgt at give en mere tilgængelig gennemgang af teorien. Især er beviserneen del mere udførlige end dem artiklen præsenterer. Yderligere har jeg tilføjet eksempler ogfigurer for at øge forstaeligheden af teorien.

3.1 Lcp-intervaller

Et af de essentielle begreber artiklen [AKO04] indfører er lcp-intervaller. Et lcp-interval [i..j]med værdi ` angiver, at lcp af suffikserne fra i til j i suffiksarrayet har længden `. Et sadaninterval kaldes ogsa et `-interval eller et ω-interval, hvor ω er lcp-strengen for intervallet. Ofteskrives `-intervallet fra i til j som `-[i..j].

Givet et lcp-interval `-[i..j], kan vi finde lcp for suffikserne XSA[i] til XSA[j] pa følgendemade, lcp(XSA[i], ..., XSA[j]) = X[SA[i]..SA[i] + ` − 1]. Forklaringen pa dette er, at lcp forXSA[i], ..., XSA[j] starter pa position SA[i] og er ` langt.

11

Page 15: Analyse og implementering af suffikstræer baseret p˚a ... · Kapitel 1 Introduktion Suffikstræet1 er en interessant datastruktur, der har en lang række anvendelsesmuligheder inden

3. Relation mellem suffikstræer og udvidede suffiksarrays 12

i SA[i] LCP [i] XSA[i] lcp-intervaller0123456789

1011

741

100986352

11

014100102130

ippi$issippi$ississippi$i$mississippi$pi$ppi$sippi$sissiippi$ssippi$ssissippi$$

4

2

3

1

1

1

Figur 3.1: Ligesom figur 2.3 viser denne figur forholdet mellem tabellen LCP og suf-fikserne i suffiksarrayet SA for strengen “mississippi”. Pa figuren er der i kolonnen ”lcp-intervaller”tilføjet lcp-intervallerne for tabellen LCP og deres `-værdi.

Definition 3.1.1. Et interval [i..j] hvor 0 ≤ i < j ≤ n, er et lcp-interval med værdien ` hvisog kun hvis

1. LCP [i] < `

2. ∀k ∈ [i + 1, j] : ` ≤ LCP [k]

3. ∃k ∈ [i + 1, j] : ` = LCP [k]

4. LCP [j + 1] < `

De indekser i lcp-intervallet, hvor punkt 3 er opfyldt, kaldes et `-indeks. Disse indekserhar en vigtig betydning i forholdet mellem forældre- og børne-knuder i lcp-interval-træer, somvil blive nærmere forklaret i afsnit 3.2.

Eksempel. Pa figur 3.1 ses tabellerne SA og LCP for strengen “mississippi”. I kolonnenXSA[i] vises suffikset svarende til suffiksnummeret i SA[i]. Pa suffikset er lcp af dette suffiksog det forrige, svarende til suffikserne SA[i − 1] og SA[i], markeret med rødt. I den sidstekolonne ”lcp-intervaller”vises lcp-intervallerne for tabellen LCP og deres `-værdi.

F.eks. kan det ses, at suffikserne “sippi$”, “sissippi$”, “ssippi$” og “ssissippi$” udgør etlcp-interval med lcp-værdi 1, nemlig lcp-intervallet 1-[7..10]. Lad os se om intervallet overhol-der kravene for et lcp-interval specificeret i definition 3.1.1. Første krav er, at LCP [7] < 1,dette er opfyldt, da LCP [7] = 0. Derudover skal værdien i LCP for alle indgange i intervallet[8, 10] være større eller lig med lcp-værdien. Og samtidigt skal mindst en af værdierne værelig med lcp-værdien. Dette svarer til at den mindste værdi af LCP i intervallet skal være ligemed lcp-værdien. At dette krav er opfyldt kan ses ud fra følgende udregning:

1 ≤ min{LCP [8], LCP [9], LCP [10]} = min{2, 1, 3} = 1

Det beviser, at intervallet [7..10], som antaget, er et lcp-interval med lcp-værdi 1. Dettegiver ogsa intuitiv mening, da det kan ses at de 4 suffikser har strengen “s” som længste fællespræfiks.

Page 16: Analyse og implementering af suffikstræer baseret p˚a ... · Kapitel 1 Introduktion Suffikstræet1 er en interessant datastruktur, der har en lang række anvendelsesmuligheder inden

3. Relation mellem suffikstræer og udvidede suffiksarrays 13

Lcp-intervaller kan være indlejret i hinanden. Et eksempel pa dette kan ses pa figur 3.1,hvor intervallet 3-[9..10] er indlejret i intervallet 1-[7..10]. Det betyder at suffikserne i inter-vallet [9..10] har et længste fælles præfiks af længden 3, hvor imod lcp for intervallet [7..10]kun har længden 1. Men da intervallet [9..10] er indeholdt i intervallet [7..10], er lcp af [7..10]ogsa et præfiks af lcp for intervallet [9..10].

Dette kan ses ved, at lcp for [7..10] er strengen “s”, og lcp for [9..10] er strengen “ssi”.Denne hierarkiske orden mellem indlejrede lcp-intervaller og deres forældre-intervaller brugesi afsnit 3.2 til at opfatte lcp-intervallerne som en træstruktur.

3.2 Lcp-interval-træer

En vigtig egenskab ved lcp-intervallerne for et suffiksarray er, at de kan opfattes som ettræ, hvis man tilføjer et specielt interval 0-[0..n] som rod. 0-[0..n] svarer til, at lcp for allesuffikserne af X, tilføjet $, har længden 0. Relationen mellem forældre- og børne-intervallerkan nu defineres pa følgende made:

Definition 3.2.1. Et m-interval [l..r] siges at være indlejret i `-intervallet [i..j], hvisi ≤ l < r ≤ j ∧ ` < m. `-intervallet kaldes sa det omsluttende interval1.

Hvis [i..j] omslutter intervallet [l..r], og der ikke er andre indlejrede intervaller i [i..j], deromslutter [l..r], sa er [l..r] et børne-interval til [i..j], og [i..j] er forældre interval til [l..r].

Jeg vil herunder vise et eksempel pa relationen mellem forældre- og børne-intervallet.

Eksempel. Pa figur 3.2 a) ses suffikserne for strengen “mississippi”, den tilhørende LCPtabel og lcp-intervallerne. Hvis man sammenholder denne figur med figur 3.2 b), der viserlcp-interval-træet for strengen “mississippi”, kan man se at lcp-intervallerne præcist dannerlcp-interval-træets struktur. Den eneste forskel er, at der er tilføjet et specielt interval 0-[0..11]som rod til lcp-interval-træet. Dette interval svarende til at lcp for alle suffikserne er dentomme streng ε.

Det første børne-interval til roden, er 1-[0..3]. Det betyder, at lcp for suffikserne 0 til 3har længden 1. Her er der tale om strengene “ippi$”, “issippi$”, “ississippi$” og “i$”, hvor“i” er det længste fælles prefiks.

1-[0..3] har igen et børne-interval 4-[1..2], der viser, at lcp af suffiks 1 og 2 har længden4. Det længste fælles præfiks af de to strenge “issippi$” og “ississippi$” er “issi”.

Det viser sig, at et lcp-interval-træ kan opfattes som et suffikstræ uden blade. Faktisk er deren en-til-en overensstemmelse mellem interval-knuderne og de indre knuder i det tilsvarendesuffikstræ. For at klargøre sammenhængen mellem lcp-interval-træet og suffikstræet, har jeg pafigur 3.3 lavet et lcp-interval-træ for strengen “mississippi”, og tilføjet bladene fra suffikstræetmed grat.

Hvert blad i suffikstræet, svarende til et suffiks XSA[l], kan repræsenteres som et single-ton interval [l..l] i lcp-interval-træet. Forældre-intervallet til et singleton interval [l..l] er detmindste lcp-interval [i..j], hvor om der gælder, at l ∈ [i..j].

3.3 Børne-tabel for lcp-interval-træet

For at udnytte sammenhængen mellem lcp-interval-træet og suffikstræet, beskrevet i sektion3.2, indføres en ny tabel CHILD . CHILD holder styr pa relationen mellem et lcp-interval ogdets børne-intervaller. I sammenhæng med LCP giver det muligheden for at bestemme allebørne-intervaller for et givet lcp-interval i O(|Σ|) tid, hvilket er konstant tid, da vi antager

1 Da ` < m kan det ikke forekomme at i = l og l = j samtidigt. Da dette ville medføre at ` = m.

Page 17: Analyse og implementering af suffikstræer baseret p˚a ... · Kapitel 1 Introduktion Suffikstræet1 er en interessant datastruktur, der har en lang række anvendelsesmuligheder inden

3. Relation mellem suffikstræer og udvidede suffiksarrays 14

a)

i SA[i] LCP [i] XSA[i] lcp-intervaller0123456789

1011

741

100986352

11

014100102130

ippi$issippi$ississippi$i$mississippi$pi$ppi$sippi$sissiippi$ssippi$ssissippi$$

4

2

3

1

1

1

b)0-[0..11]

1-[0..3]

4-[1..2]

ssi

i

1-[5..6]

p

1-[7..10]

2-[7..8]

i

3-[9..10]

si

s

Figur 3.2: a) viser tabellen fra figur 3.1 for at klargøre sammenhængen med b) lcp-interval-træfor strengen “mississippi”.

0-[0..11]

1-[0..3]

7

ppi$

4-[1..2]

4

ppi$

1

ssippi$

ssi

10

$

i

0

mississippi$1-[5..6]

9

i$

8

pi$

p

1-[7..10]

2-[7..8]

6

ppi$

3

ssippi$

i

3-[9..10]

5

ppi$

2

ssippi$

si

s11

$

Figur 3.3: Figuren viser sammenhængen mellem lcp-interval-træet og suffikstræet for strengen“mississippi”. Lcp-interval-træet er tegnet med sort, derudover er blad-knuderne fra suffiks-træet tilføjet med grat.

Page 18: Analyse og implementering af suffikstræer baseret p˚a ... · Kapitel 1 Introduktion Suffikstræet1 er en interessant datastruktur, der har en lang række anvendelsesmuligheder inden

3. Relation mellem suffikstræer og udvidede suffiksarrays 15

a) b) c)

LCP [q]LCP [q +1]

...LCP [i− 1]LCP [i]

≥ LCP [q]

< LCP [q]

LCP [i]LCP [i + 1]

...LCP [q−1]LCP [q]

> LCP [q]

< LCP [q] LCP [i]LCP [i + 1]

...LCP [q−1]LCP [q]

> LCP [q]

= LCP [q]

Figur 3.4: Figuren viser hvordan LCP værdierne forholder sig for a) CHILD[i].up, b)CHILD[i].down og c) CHILD[i].next`Index, dette skal ses i sammenhæng med definitionenfor CHILD.

at |Σ| er konstant. Ligeledes er det muligt i konstant tid, givet et lcp-interval `-[i..j] og ettegn α ∈ Σ, at finde børne-intervallet [l..r] til [i..j], hvis suffikser har tegnet α pa position `(bemærk at [l..r] kan være et singleton interval).

Jeg starter med at definere tabellen CHILD, der indeholder tre felter up, down og next`Index.Felterne up og down bruges til at finde det først `-indeks, og next`Index bruges til at finderesten af `-indekserne, ud fra det første `-indeks. Lemma 3.3.1 beskriver, hvordan man kanfinde et lcp-intervals børne-intervaller ud fra dets `-indekser. Lemma 3.3.2 fastslar, at for etgivet lcp-interval [i..j], indeholder CHILD[i].down eller CHILD[j +1].up det først `-indeks.Det betyder, at for et hvilket som helst lcp-interval kan vi finde det første `-indeks og derefterfinde de resterende `-indekser. Nar vi har `-indekserne for intervallet, er det let at finde allebørne-intervallerne. Til sidst vil jeg gennemga to eksempler. Et hvor jeg viser hvordan manfinder børne-intervallerne for et givet lcp-interval. Og et hvor jeg forklarer hvordan man finderdet børne-interval, hvis label pa kanten fra forældre-intervallet begynder med et givet tegn.

Definition 3.3.1. CHILD er et array af heltal fra 0 til n, hvor n er længden af LCP . Hverindgang i CHILD indeholder tre felter, up, down og next`Index defineret som følger:

CHILD[i].up = min{q ∈ [0..i− 1] |LCP [i] < LCP [q] ∧∀k ∈ [q + 1..i− 1] : LCP [q] ≤ LCP [k]}

CHILD[i].down = max{q ∈ [i + 1..n] |LCP [i] < LCP [q] ∧∀k ∈ [i + 1..q − 1] : LCP [q] < LCP [k]}

CHILD[i].next`Index = min{q ∈ [i + 1..n] |LCP [i] = LCP [q] ∧∀k ∈ [i + 1..q − 1] : LCP [i] < LCP [k]}

Figur 3.4 giver et overblik over hvordan LCP værdierne for a) CHILD[i].up, b) CHILD[i].downog c) CHILD[i].next`Index forholder sig i henhold til overstaende definition.

3.3.1 Bestemmelse af børne-intervaller

Feltnavnene up og down kan give anledning til en smule forvirring, da felterne ikke angiverpointers i op- og nedadgaende retning i lcp-interval-træet. Men derimod bruges til at findedet første `-indeks for et givet `-interval [i..j]. Det viser sig at `-indekserne, for et `-interval,kan bruges til at finde start- og slutposition for børne-intervallerne.

Page 19: Analyse og implementering af suffikstræer baseret p˚a ... · Kapitel 1 Introduktion Suffikstræet1 er en interessant datastruktur, der har en lang række anvendelsesmuligheder inden

3. Relation mellem suffikstræer og udvidede suffiksarrays 16

For et `-interval [i..j] hvis `-indekser er i1, i2, ..., ik, kan det første `-indeks i1 findes som

i1 =

CHILD[0].next`Index hvis [i..j] = [0..n− 1]CHILD[i].down hvis CHILD[i].down ∈]i, j]CHILD[j + 1].up hvis CHILD[j + 1].up ∈]i, j]

(3.1)

Ovenstaende er beskrevet og bevist i lemma 3.3.2.Resten af `-indekserne i2, i3, ..., ik kan findes pa følgende made

∀m ∈ [1..k − 1] : CHILD[im].next`Index = im+1

Hvilket bare betyder, at vi for et givet `-indeks, kan finde det næste `-indeks vha. next`Indexfeltet.

Nar vi har fundet `-indekserne for `-intervallet [i..j], er det muligt at bestemme alle børne-intervallerne for `-[i..j], ud fra følgende lemma:

Lemma 3.3.1. Lad i1, i2, ..., ik være `-[i..j]’s `-indekserne i stigende rækkefølge, sa er børne-intervallerne for `-[i..j] givet ved [i..i1 − 1], [i1..i2 − 1], ..., [ik..j]. Bemærk at nogle af børne-intervallerne kan være singleton intervaller.

Bevis. For at bevise at [l..r] er et børne-interval til [i..j], skal vi vise at [l..r] er indlejret i[i..j], og at der ikke er noget andet lcp-interval indlejret i [i..j] der omslutter [l..r].

Lad [l..r] være et af følgende `-intervaller [i..i1 − 1], [i1..i2 − 1], ..., [ik..j].

• Hvis [l..r] er et singleton-interval, svarer det til et blad pa knuden `-[i..j] i lcp-interval-træet, og er derfor et børne-interval til `-[i..j].

• Hvis [l..r] er et m-interval, gælder der ud fra definitionen for lcp-intervaller 3.1.1 atLCP [l] < m og LCP [r + 1] < m. Hvis [l..r] = [i..i1 − 1] gælder der, at LCP [r + 1] =LCP [i1] = ` < m, og hvis [l..r] 6= [i..i1−1] ved vi, at LCP [l] = ` < m, derfor ma ` < mfor alle intervallerne. Og da i ≤ l < r ≤ j tydeligvis er opfyldt, er [l..r] indlejret i [i..j].Jeg vil bevise at der ikke kan findes et børne-interval i `-[i..j], der omslutter [l..r], ved etmodbevis. Antag at c-[a..b] er et indlejret lcp-interval i `-[i..j] og omslutter m-[l..r], sa er` < c < m og i ≤ a ≤ l < r ≤ b ≤ j. Men da vi ved, at LCP [l] = ` ∨ LCP [r + 1] = ` oga = l ∧ r = b ikke kan være sandt pa samme tid, sa ma [a + 1..b] indeholde et `-indeks.Hvis dette er tilfældet, er [a..b] ikke et c-interval men et `-interval, derfor eksistererdette interval ikke. Hermed er det bevist at [l..r] ma være et børne-interval til [i..j].

Følgende lemma fastslar, at enten indeholder CHILD[j+1].up det første lcp-indeks for lcp-intervallet [i..j], eller ogsa gør CHILD[i].down. Yderligere beskriver lemmaet ogsa, hvordanvi kan vide, om det er CHILD[j + 1].up eller CHILD[i].down, der indeholder lcp-indekseteller dem begge. Dette lemma kan altsa bruges som grundlag for at finde det første lcp-indeksfor et lcp-interval.

Lemma 3.3.2. For et hvert `-interval[i..j], hvor [i..j] 6= [0..n], er følgende sandt:

1. CHILD[j + 1].up ∈]i, j] ∨ CHILD[i].down ∈]i, j]

2. CHILD[i].down indeholder det første `-indeks [i..j],hvis CHILD[i].down ∈]i, j]

3. CHILD[j + 1].up indeholder det første `-indeks [i..j],hvis CHILD[j + 1].up ∈]i, j]

Bevis. Beviset bestar af tre dele svarende til overstaende punkter.

Page 20: Analyse og implementering af suffikstræer baseret p˚a ... · Kapitel 1 Introduktion Suffikstræet1 er en interessant datastruktur, der har en lang række anvendelsesmuligheder inden

3. Relation mellem suffikstræer og udvidede suffiksarrays 17

1. Antag at `-[i..j] er børne-interval til m-[l..r], det kan vi antage da der altid eksisterer etforældre-interval til `-[i..j] fordi [i..j] 6= [0..n], og [0..n] er roden i lcp-interval-træet. Udfra lemma 3.3.1 ved vi, at enten er i, j + 1 eller dem begge et m-indeks.Hvis i er et m-indeks vil jeg bevise at CHILD[i].down altid er defineret og i <CHILD[i] ≤ j, og hvis j + 1 er et m-indeks, vil jeg bevise, at CHILD[j + 1] altider defineret og i < CHILD[j + 1].up ≤ j.

• Bevis for at CHILD[i].down altid er defineret, hvis i er et m-indeks.Definitionen for lcp-intervaller 3.1.1 giver os, at LCP [i] < ` ≤ LCP [i + 1]. Hvisvi sammenholder det med definitionen for CHILD.down 3.3.1, ser vi, at i + 1 eren gyldig kandidat til CHILD[i].down. Det vil sige, at down altid er defineret idenne situation.

• Bevis for at i < CHILD[i].down ≤ j, hvis i er et m-indeks.Hvis j = n er CHILD[i].down ∈ [i + 1..n] ⇔ i < CHILD[i].down ≤ j ud fradefinitionen af CHILD.down. For j < n vil jeg modbevise at CHILD[i].down ≤i ∨ j < CHILD[i].down. CHILD[i].down ≤ i kan ikke være sandt ud fra defini-tionen af CHILD.down. Jeg vil derfor antage, at j < CHILD[i].down = q. Dvs.at følgende er opfyldt

LCP [i] < LCP [q] ∧ ∀k ∈ [i + 1..q − 1] : LCP [q] < LCP [k]⇒∀k ∈ [i + 1..q] : LCP [i] < LCP [k] (3.2)

Jeg vil vise, at LCP [j +1] ≤ LCP [i] = m hvilket ikke opfylder overstaende formel.Hvis j = r, ma LCP [j+1] < m ifølge definitionen for lcp-intervaller. Og hvis j < r,fastslar lemmaet 3.3.1, at j+1 skal være et m-indeks og dermed er LCP [j+1] = m.Det beviser at i < CHILD[i].down ≤ j.

• Bevis for at CHILD[j + 1].up altid er defineret, hvis j + 1 er et m-indeks.Definitionen for lcp-intervaller giver os, at LCP [j +1] < ` ≤ LCP [j]. Hvis vi sam-menholder det med definitionen for CHILD.up, ser vi, at j er en gyldig kandidattil CHILD[j + 1].up.

• Bevis for at i < CHILD[j + 1].up ≤ j, hvis j + 1 er et m-indeks.Hvis i = 0, er CHILD[j + 1].up ∈ [0..j], og da LCP [0] er defineret til at være0, sa ma LCP [0] ≤ LCP [i], hvilket betyder at CHILD[j + 1].up 6= 0. Vi kanderfor konkludere at i < CHILD[j + 1].up ≤ j. For 0 < i vil jeg modbevise atCHILD[j + 1].up ≤ i ∨ j < CHILD[j + 1].up. Udtrykket j < CHILD[j + 1].upkan ikke være sandt ud fra definitionen af CHILD.up, jeg vil derfor antage, atq = CHILD[j + 1].up ≤ i. Det vil sige, at følgende skal være opfyldt:

LCP [j + 1] < LCP [q] ∧ ∀k ∈ [q + 1..j] : LCP [q] ≤ LCP [k]⇒∀k ∈ [q..j] : LCP [j + 1] < LCP [k] (3.3)

Jeg vil vise, at LCP [i] ≤ LCP [j+1] = m, hvilket ikke opfylder overstaende formel.Hvis i = l, ma LCP [i] < m ifølge definitionen for lcp-intervaller. Og hvis l < i,sa ved vi ud fra lemma 3.3.1, at LCP [i] = m, da i er et m-indeks. Det beviser,ati < CHILD[j + 1] ≤ j.

2. Hvis i < CHILD[j + 1].up ≤ j, sa gælder der:

CHILD[j + 1].up = min{q ∈ [i + 1..j] |LCP [j + 1] < LCP [q]∧∀k ∈ [q + 1..j] : LCP [q] ≤ LCP [k]}

Page 21: Analyse og implementering af suffikstræer baseret p˚a ... · Kapitel 1 Introduktion Suffikstræet1 er en interessant datastruktur, der har en lang række anvendelsesmuligheder inden

3. Relation mellem suffikstræer og udvidede suffiksarrays 18

Men da vi ved, at LCP [j + 1] < LCP [q] altid er opfyldt, nar q ∈ [i + 1..j] ud fradefinitionen for lcp-intervaller, kan vi skrive udtrykket som:

CHILD[j + 1].up = min{q ∈ [i + 1..j] | ∀k ∈ [q + 1..j] : LCP [q] ≤ LCP [k]}= min `Indices(i, j)

Hvor `Indices(i, j) er mængden af alle `-indekser mellem i og j.

3. Hvis i < CHILD[i].down ≤ j, sa gælder der:

CHILD[i].down = max{q ∈ [i + 1..j] |LCP [i] < LCP [q]∧∀k ∈ [i + 1..q − 1] : LCP [q] < LCP [k]}

Men da vi ved, at LCP [i] < LCP [q] altid er opfyldt nar, q ∈ [i+1..j] ud fra definitionenfor lcp-intervaller, kan vi skrive udtrykket som:

CHILD[i].down = max{q ∈ [i + 1..j] | ∀k ∈ [i + 1..q − 1] : LCP [q] < LCP [k]}= min `Indices(i, j)

Dette ses da vi ved, at den mindste lcp-værdi i `-[i..j] er `. Det vil sige, at der ikke kanvære indekser efter det første `-indeks der opfylder overstaende udtryk.

Eksempel. I dette eksempel vil jeg vise, hvordan man finder alle børne-intervallerne til lcp-intervallerne 0-[0..11] og 1-[7..10] i lcp-interval-træet for strengen “mississippi”. Figur 3.5bestar af to dele: a) viser tabellerne SA, LCP og CHILD for strengen “mississippi”, og b)viser lcp-interval-træet for strengen. Denne figur kan sammen med eksemplet bruges til at sesammenhængen mellem CHILD tabellen og lcp-interval-træet for strengen “mississippi”.

For at finde alle børneintervallerne for lcp-intervallet 0-[0..11], er det nødvendigt at findealle lcp-indekserne for intervallet. Dette gøres ud fra formel 3.1, pa følgende made:

01 = CHILD[0].next`Index = 402 = CHILD[4].next`Index = 503 = CHILD[5].next`Index = 704 = CHILD[7].next`Index = 11

Da next`Index ikke er defineret for CHILD[11], er der ikke flere lcp-indekser. Bemærk atmaden man finder det første lcp-indeks for rod-intervallet 0-[0..n], er anderledes end for deandre intervaller. Det næste eksempel viser blandt andet, hvordan man finder det første lcp-indeks for andre lcp-intervaller end rodintervallet.

Da vi har lcp-indekserne, kan vi nu, som beskrevet i 3.3.1, finde børne-intervallerne for0-[0..11] til [0..3], [4..4], [5..6], [7..10] og [11..11]. Til sidst mangler vi bare at finde børne-intervallernes lcp-værdier. Vi kan finde lcp-værdien for et børne-interval som LCP [i1], hvori1 er det første lcp-indeks for børne-intervallet.

Det første lcp-indeks for børne-interval [0..3] er jævnfør formlen 3.1:

01 = LCP [CHILD[0].down] = 1

Pa samme made kan vi finde lcp-værdierne for de andre børne-intervaller. Det giver os børne-intervallerne 1-[0..3], 1-[5..6] og 1-[7..10] for forældre-intervallet 0-[0..11].

Som man kan se pa figur 3.5 b), har intervallet 1-[7..10] to børne-intervaller. Jeg vil hervise, hvordan vi finder disse to intervaller. Igen skal vi starte med at finde alle lcp-indeksernefor intervallet 1-[7..10]. Det første lcp-indeks for 1-[7..10] findes, ud fra formlen 3.1, som:

71 = CHILD[7].down = 9

Page 22: Analyse og implementering af suffikstræer baseret p˚a ... · Kapitel 1 Introduktion Suffikstræet1 er en interessant datastruktur, der har en lang række anvendelsesmuligheder inden

3. Relation mellem suffikstræer og udvidede suffiksarrays 19

a)CHILD[i]

i SA[i] LCP [i] up down next XSA[i]

0 7 0 1 4 ippi$1 4 1 2 3 issippi$2 1 4 ississippi$3 10 1 2 i$4 0 0 1 5 mississippi$5 9 0 6 7 pi$6 8 1 ppi$7 6 0 6 9 11 sippi$8 3 2 sissippi$9 5 1 8 10 ssippi$

10 2 3 ssissippi$11 11 0 9 $

b)0-[0..11]

1-[0..3]

4-[1..2]

ssi

i

1-[5..6]

p

1-[7..10]

2-[7..8]

i

3-[9..10]

si

s

Figur 3.5: Denne figur bestar af to dele: a) viser tabellerne SA, LCP og CHILD for strengen“mississippi”, og b) viser lcp-interval-træet for strengen.

Page 23: Analyse og implementering af suffikstræer baseret p˚a ... · Kapitel 1 Introduktion Suffikstræet1 er en interessant datastruktur, der har en lang række anvendelsesmuligheder inden

3. Relation mellem suffikstræer og udvidede suffiksarrays 20

Der er ikke flere lcp-indekser for 1-[7..10], da next`Index ikke er defineret for CHILD[9]. Detgiver os børne-intervallerne [7..8] og [9..10]. Pa samme made som før finder vi lcp-værdierne71 og 91 for de to børne-intervaller:

71 = LCP [CHILD[8 + 1].up] = 291 = LCP [CHILD[9].down] = 3

det giver os intervallerne 2-[7..8] og 3-[9..10].

3.4 Navigation i lcp-interval-træet ved brug af børne-tabellen

Jeg vil i dette afsnit gennemga algoritmerne til nedadgaende navigation i lcp-træet. Først viljeg beskrive algoritmen til at finde alle børne-intervallerne for et givet lcp-interval. Dereftervil jeg forklare algoritmen til at finde børne-intervallet for et givet lcp-interval, hvor labe-len mellem børne-intervallet og forældre-intervallet starter med et givet tegn. I algoritmerneer tabellerne SA, LCP og CHILD givet som implicit input, og |X| = |SA| = |LCP | =|CHILD| = n.

3.4.1 Algoritme til at finde børne-intervaller i konstant tid

input : Et lcp-interval [i..j]output: En liste med alle børne intervallerne for lcp-intervallet [i..j]

intervalList← nil;1

ia ← FindFirstlIndex([i..j]);2

add(intervalList, [i, ia − 1]);3

while (ib ← CHILD[ia].next`Index) 6=⊥ do4

add(intervalList, [ia, ib − 1]);5

ia ← ib;6

add(intervalList, [ia, j]);7

Algorithm 1: GetChildIntervals: Konstruerer en liste med alle børne-intervallerne for etgivet lcp-interval ud fra CHILD tabellen.

Algoritme 1 finder alle børne-intervallerne for et givet lcp-interval ud fra CHILD tabellen,og returnerer dem i en liste.

Algoritmen starter men en tom liste intervalList. Først findes det første lcp-indeks i1 ud fraprincippet beskrevet i formel 3.1. Vi ved fra lemma 3.3.1, at et lcp-interval med `-indeksernei1, i2, ..., ik har børne-intervallerne [i..i1 − 1], [i1..i2 − 1], ..., [ik..j]. Derfor tilføjes det førstelcp-interval [i..i1 − 1] til interval listen. Derefter gennemløbes resten af lcp-indekserne ved atbruge feltet next`Index i tabellen CHILD. For hvert lcp-indeks ia, a ∈ [1, k − 1], hvor ib erdet næste lcp-indeks efter ia, tilføjes børne-intervallet [ia..ib − 1] til interval listen. Til sidsttilføjes det sidste børne-interval [ak..j] til interval listen.

3.4.2 Algoritme til at finde børne-intervallet for et givet tegn

Algoritme 2 tager et lcp-interval [i..j] og et tegn fra alfabetet som input. Algoritme 1 udnyttestil at finde alle børne-intervallerne for lcp-intervallet [i..j]. Derefter gennemløber algoritmenalle børne-intervallerne. Hvis der findes et børne-interval, hvor labelen fra lcp-intervallet [i..j]ned til børne-intervallet starter med tegnet c returneres det.

Page 24: Analyse og implementering af suffikstræer baseret p˚a ... · Kapitel 1 Introduktion Suffikstræet1 er en interessant datastruktur, der har en lang række anvendelsesmuligheder inden

3. Relation mellem suffikstræer og udvidede suffiksarrays 21

input : Et lcp-interval [i..j] og et tegn c ∈ Σoutput: Børne-intervallet hvis label, fra forældre-intervallet, starter med tegnet

c

childIntervals←GetChildIntervals([i..j]);1

foreach [l..r] in childIntervals do2

if l = n− 1 then3

/* Special tilfælde for 0-[n− 1..n− 1] */c′ ← $;4

else5

parentLcp← max(LCP [l], LCP [r + 1]);6

c′ ← X[SA[l] + parentLcp];7

if c = c′ then return [l..r];8

return ⊥;9

Algorithm 2: Returnerer børne-intervallet hvis label, fra forældre-intervallet, starter medet givet tegn.

Det første tegn i labelen fra børne-intervallet [l..r] til forældre-intervallet [i..j] findes pafølgende made: Først findes lcp-værdien for forældre-intervallet ved at tage maksimum af lcp-værdien for indekserne l og r + 1. Nar vi har lcp-værdien ` for forældre-intervallet, ved vi2,at det første tegn i labelen er pa position SA[l] + `.

Grunden til at lcp-værdien for forældre-intervallet kan findes pa denne made er, at mindsten af indekserne l og r + 1 er lcp-indeks for forældre-intervallet i følge lemmaet 3.3.1. Udfra definitionen af lcp-intervaller 3.2.1 ved vi, at hvis l og r + 1 ikke begge er lcp-indekserfor forældre-intervallet, sa er indekset, der har den største lcp-værdi, lcp-indeks for forældre-intervallet.

Specialtilfældet hvor l = n− 1, er børne-intervallet [n− 1..n− 1]. Det skyldes, atX[SA[n − 1]] = $, og derfor ikke kan have noget fælles præfiks med et andet suffiks. Det vilsige, at labelen til forældre-intervallet, i dette tilfælde, er $.

3.5 Suffikslinks-tabel for et lcp-interval-træ

Som beskrevet i 2.2 kan enhver kunde i et suffikstræ ogsa opfattes som den unikke streng, derdannes hvis man sammensætter alle labels fra roden og ned til knuden. Hvis u er en indreknude i lcp-interval-træet, sa findes der en anden indre knude v i lcp-interval-træet, som erdet længste rigtige suffiks af u. Det vil sige, at v = αu, hvor α ∈ Σ. Suffikslinket dannerrelationen fra u til v.

I denne sektion vil jeg indfører suffikslinks i suffikstræet, og vise hvordan disse kan overførestil lcp-interval-træet. Først definerer jeg suffiksfunktionen, der beskriver associationen suffiks-linket danner mellem to knuder. Derefter beviser jeg, at suffikslinket er defineret for enhverindre knude i suffikstræet. Til sidst defineres suffikslink-intervallet i lcp-interval-træet, ogtabellen SLINK til at gemme denne information.

Følgende definition af suffiksfunktionen er baseret pa [Smy03, s. 116].

Definition 3.5.1.

s(u) ={

ε v = εv u = αv, α ∈ Σ, v ∈ Σ?

Suffiksfunktionen definerer en pointer fra en knude u til en knude v = s(u) i suffikstræet, somer det længste rigtige suffiks af knuden. Denne pointer kaldes et suffikslink.

2Dette er forklaret i sektion 3.1

Page 25: Analyse og implementering af suffikstræer baseret p˚a ... · Kapitel 1 Introduktion Suffikstræet1 er en interessant datastruktur, der har en lang række anvendelsesmuligheder inden

3. Relation mellem suffikstræer og udvidede suffiksarrays 22

Xu

αXi

vXi+1

uXj α

vXj+1

Figur 3.6: Viser u og v i forhold til strengen X og suffikserne Xi, Xi+1, Xj og Xj+1.

Det er altsa muligt at finde det længste rigtige suffiks af en knude u ved hjælp af suffiks-linket. Dette udnyttes f.eks. af algoritmen Computing Matching Statistics [AKO04, s. 76-77].

En vigtig egenskab ved suffikslinks er, at de er defineret for alle indre knude. Dette fastslasaf følgende lemma:

Lemma 3.5.1. Suffiksfunktionen er defineret for alle indre knuder i et suffikstræ.

Bevis. Lad X være en streng, T være suffikstræet for X og u være en indre knude i T .Hvis u er roden i T og dermed u = ε, sa er v = s(u) = ε.Hvis u 66= ε ved vi, at u er et rigtigt præfiks af mindst to suffikser Xi og Xj , fordi u er en indreknude i T . Vi kan skrive u som u = αv, hvor α ∈ Σ og v ∈ Σ?. Det giver os, at v i hvert fald erpræfiks af Xi+1 og Xj+1. Dermed findes den indre knude v, og suffiks funktionen er defineretfor alle interne knuder i T . Figur 3.6 viser et eksempel hvor u og v er tegnet i forhold til Xog suffikserne Xi, Xi+1, Xj og Xj+1.

For at kunne definere suffikslink-intervallet for et lcp-interval, indføres hjælpetabbellenSA−1 og funktionen link.

Definition 3.5.2. SA−1 er den inverse tabel til SA, dvs.

∀ 0 ≤ q ≤ n : SA−1[SA[q]] = q

Definition 3.5.3. Lad XSA[i] = αω, sa er link funktionen der opfylder, at XSA[link(i)] = ω.

Følgende lemma viser hvordan link kan beregnes.

Lemma 3.5.2. Hvis SA[i] < n, sa er link(i) = SA−1[SA[i] + 1].

Bevis. Lad XSA[i] = αω, sa ma XSA[i]+1 = ω. Ud fra definition 3.5.3 af link, ved vi at linkma opfylde

SA[link(i)] = SA[i] + 1⇒link(i) = SA−1[SA[i] + 1]

Nu har vi alt hvad vi skal bruge for at definere suffikslink-intervallet for et lcp-interval.

Definition 3.5.4. Givet et `-interval [i..j], sa kaldes det mindste lcp-interval [l..r], der op-fylder at l ≤ link(i) < link(j) ≤ r, for suffikslink-intervallet til `-[i..j].

Hvis `-[i..j] svarer til knuden αω i suffikstræet for strengen X, og der er et suffikslink fraαω til ω, sa hævder følgende lemma at suffikslink-intervallet for `-[i..j] svarer til knuden ω.

Page 26: Analyse og implementering af suffikstræer baseret p˚a ... · Kapitel 1 Introduktion Suffikstræet1 er en interessant datastruktur, der har en lang række anvendelsesmuligheder inden

3. Relation mellem suffikstræer og udvidede suffiksarrays 23

0-[0..11]

1-[0..3]

4-[1..2]

ssi

i

1-[5..6]

p

1-[7..10]

2-[7..8]

i

3-[9..10]

si

s

Figur 3.7: Lcp-interval-træ for strengen “mississippi”, tilføjet suffikslinks markeret med rød.

Lemma 3.5.3. Givet et αω-interval `-[i..j], sa er suffikslink-intervallet for `-[i..j] det ω-interval der har lcp-værdien `− 1.

Bevis. Lad [l..r] være suffikslink-intervallet for αω-intervallet `-[i..j]. Det vil sige, at αω er lcpfor XSA[i], ..., XSA[j]. Ud fra definitionen 3.5.3 ma ω være lcp for XSA[link(i)], ..., XSA[link(j)].Da [l..r] er det mindste interval, der opfylder l ≤ link[i] < link[j] ≤ r, ma ω ogsa være lcpfor XSA[l], ..., XSA[r]. Dermed er det bevist at [l..r] er ω-intervallet med lcp-værdien `−1.

Vi kan nu definere tabellen SLINK :

Definition 3.5.5. Lad [l..r] være suffikslink-intervallet for `-intervallet [i..j] og i1 være detførste `-indeks for `-[i..j]. Sa indeholder SLINK tabellen:

SLINK[i1].l = l

SLINK[i1].r = r

Resten af indgangene i SLINK er tomme. Hvis der ikke er defineret noget suffikslink-intervalfor `-[i..j], vil indgangen i SLINK være tom.

Det er ikke nødvendigt at gemme lcp-værdien for intervallerne i SLINK, da vi alleredeved den er `− 1.

Det er vigtigt at bemærke, at et hvert lcp-interval mindst har et lcp-indeks, og at detteindeks ikke kan være lcp-indeks for noget andet lcp-interval. Det betyder, at værdierne dergemmes i SLINK aldrig overlapper.

3.6 Suffikslinks i lcp-interval-træet

Eksempel. Pa figur 3.7 ses lcp-interval-træet for strengen “mississippi”. Pa lcp-interval-træet er der tilføjet suffikslinks og labels pa kanterne, sa det er lettere at se hvilke suffikser,der tilhører et givet interval. Jeg vil nu vise et eksempel pa, hvordan man kan finde suffikslink-intervallet for lcp-intervallet 2-[7..8] og gemme det i SLINK tabellen. Lcp-intervallet 2-[7..8]svarer til knuden “si” i suffikstræet. For at finde suffikslink-intervallet for 2-[7..8] skal vi ledeefter det mindste 1-interval [l..r], der opfylder, at l ≤ link(7) < link(8) ≤ r.

link(7) = SA−1[SA[7] + 1] = SA−1[6 + 1] = SA−1[7] = 0

link(8) = SA−1[SA[8] + 1] = SA−1[3 + 1] = SA−1[4] = 1

Page 27: Analyse og implementering af suffikstræer baseret p˚a ... · Kapitel 1 Introduktion Suffikstræet1 er en interessant datastruktur, der har en lang række anvendelsesmuligheder inden

3. Relation mellem suffikstræer og udvidede suffiksarrays 24

Der er tre 1-intervaller i lcp-interval-træet for strengen “mississippi”, [0..3], [5..6] og [7..10].Intervallet [l..r] = [0..3] opfylder kravene. Dette interval skal sa gemmes pa det første lcp-indeks i1’s plads i SLINK tabellen.

i1 = CHILD[8 + 1].up = 8

SLINK[8].r = 0SLINK[8].l = 3

3.7 Bottom-up-gennemløb af lcp-interval-træet

En lang række suffikstræ-algoritmer kan beskrives ved at bottom-up-gennemløb af suffikstræ-et, f.eks. kan man finde tandem-repeats og super-maximale gentagelser. Interessant nok viserdet sig, at det er muligt at optimere bottom-up-gennemløbet af lcp-interval-træet bade tids-og pladsmæssigt.

I denne sektion vil jeg vise, hvordan man kan lave et bottom-up-gennemløb af lcp-interval-træet kun ved brug af LCP -tabellen. I forhold til at lave et rekursivt gennemløb af lcp-interval-træet ved at bruge CHILD-tabellen er dette langt mere pladsbesparende og samtidigthurtigere. Derefter vil jeg bevise korrektheden af algoritmen, og til sidst vil jeg vise et eksempelpa et bottom-up-gennemløb af lcp-interval-træet for strengen “mississippi”.

Et bottom-up-gennemløb af et træ opfylder, at hver knude besøges en gang, og nar knudenbesøges, er alle knudens børn allerede besøgt.

Artiklen [AKO04] præsenterer først en modificeret udgave af algoritmen TraverseWit-hArray fra en artikel af Kasai et al. Algoritmen til bottom-up-gennemløb af lcp-interval-træer, udvides derefter med en børne-liste, sa hver gang et lcp-interval besøges, er alle børne-intervallerne kendte. Den forbedrede algoritme kan ses i Algoritme 3. Elementerne pa stakkener quadrupler 〈lcp, lb, rb, childlist〉, hvor lcp er intervallets lcp-værdi, lb er intervallets ven-stre grænse, rb er intervallets højre grænse, og childlist er en liste der indeholder intervalletsbørne-intervaller. Funktionerne push og pop er de gængse stakoperationer der henholdsvisskubber et element øverst pa stakken, og fjerner det øverste element fra stakken. Variablentop er en pointer til stakkens øverste element. Funktionen add([e1, ..., ek], e) tilføjer elementete til listen [e1, ..., ek], og returnerer den nye liste. Yderligere repræsenterer ⊥ en udefineretværdi.

Det er vigtigt at bemærke at lcp-træet aldrig rigtigt bliver bygget, hver gang et lcp-interval bliver behandlet, er det kun nødvendigt at kende børne-intervallerne. Intervallernebliver udelukkende beregnet ud fra LCP tabellen. Det vil sige, at der ikke opretholdes pointeremellem forældre-intervaller og deres børne-intervaller.

Algoritmen skanner LCP tabellen lineært, hver gang et nyt lcp-interval starter, skubbesdet pa stakken. Nar et intervallet slutter behandles det med funktionen process, hvorefter detbehandlede interval tilføjes til forældre-intervallets børne-liste.

Følgende sætning viser at algoritmen overholder reglerne for et bottom-up-gennemløb.

Sætning 3.7.1. Betragt for-løkken i algoritme 3 linje 3, for et indeks i. Lad top være detøverste interval pa stakken og top−1 være det næstøverste. Bemærk at top−1.lcp < top.lcp, dader kun bliver tilføjet elementer til stakken med større lcp-værdi end det øverste element (seif-sætningen linje 13). Hvis LCP [i] < top.lcp gælder, vil følgende være opfyldt, lige før topbliver poppet af stakken i while-løkken:

1) Hvis LCP [i] ≤ top−1.lcp, sa er top børne-interval til top−1.

2) Hvis top−1.lcp < LCP [i] < top.lcp, sa er top børne-interval til LCP [i]-intervallet derindeholder indekset i.

Page 28: Analyse og implementering af suffikstræer baseret p˚a ... · Kapitel 1 Introduktion Suffikstræet1 er en interessant datastruktur, der har en lang række anvendelsesmuligheder inden

3. Relation mellem suffikstræer og udvidede suffiksarrays 25

input : LCP hvor |LCP | = n, funktionen process

lastInterval←⊥;1

push(〈0, 0,⊥, []〉);2

for i← 1 to n do3

lb← i− 1;4

while LCP [i] < top.lcp do5

top.rb← i-1;6

lastInterval← pop();7

process(lastInterval);8

lb← lastInterval.lb;9

if LCP [i] ≤ top.lcp then10

top.childlist← add(top.childlist, lastInterval);11

lastInterval←⊥;12

if LCP [i] > top.lcp then13

if lastInterval 6=⊥ then14

push (〈LCP [i], lb,⊥, [lastInterval]〉);15

lastInterval←⊥;16

else push (〈LCP [i], lb,⊥, []〉)17

Algorithm 3: bottom-up-gennemløb af lcp-interval-træet

Bevis. Beviset bestar af to dele svarende til overstaende punkter.

1) Først er det nødvendigt at bevise, at top er indlejret i top−1.

Følgende invariant er opretholdt af for -løkken i algoritme 3:Hvis 〈`1, lb1, rb1〉 , ..., 〈`k, lbk, rbk〉 er elementerne pa stakken, hvor 〈`k, lbk, rbk〉 er detøverste element pa stakken, sa gælder der:

∀ 1 ≤ i < j ≤ k : lbi ≤ lbj ∧ `i < `j

Fordi 〈`j , lbj , rbj〉 bliver poppet af stakken før 〈`i, lbi, rbi〉, følger det at rbj ≤ rbi. Dettekan ses ved at top.rb bliver sat til i − 1 lige inden elementet bliver poppet af stakkeni linje 6. Det medfører at `j-intervallet [lbj ..rbj ] er indlejret i `i-intervallet [lbi..rbi].Specielt er top indlejret i top−1.

Nu mangler jeg bare at bevise, at top er børne-interval til top−1. Det vil jeg gøre medet modbevis, hvor jeg antager, at top ikke er et børne-interval til top−1.

Hvis top ikke er et børne-interval til top−1, sa ma der findes et lcp-interval 〈`′, lb′, rb′〉sadan, at top er indlejret i 〈`′, lb′, rb′〉, og 〈`′, lb′, rb′〉 er indlejret i top−1. Dette kan dogkun ske hvis 〈`′, lb′, rb′〉 er et interval pa stakken, der ligger længere nede end top−1,da top−1 og top er de to øverste elementer pa stakken. Men dette er en modsigelse,da det ville betyde, at top−1 er indlejret i 〈`′, lb′, rb′〉, og det modsatte derfor ikke kanvære sandt. Det beviser, at et sadant interval ikke kan findes, og top derfor ma værebørne-interval til top−1.

2) Ligesom i punkt 1) vil jeg bevise at top er indlejret i LCP [i]-intervallet I, der indeholderindekset i. Og at der ikke findes noget lcp-interval, som er indlejret i I og samtidigtomslutter top.

I situationen hvor top−1.lcp < LCP [i] < top.lcp er opfyldt, vil betingelsen i while-løkken være sand. I linje 7 og 8 vil top blive fjernet fra stakken og behandlet. Derefter

Page 29: Analyse og implementering af suffikstræer baseret p˚a ... · Kapitel 1 Introduktion Suffikstræet1 er en interessant datastruktur, der har en lang række anvendelsesmuligheder inden

3. Relation mellem suffikstræer og udvidede suffiksarrays 26

i LCP stak → process

1 1 〈0, 0,⊥, []〉2 4 〈0, 0,⊥, []〉 , 〈1, 0,⊥, []〉3 1 〈0, 0,⊥, []〉 , 〈1, 0,⊥, []〉 , 〈4, 1,⊥, []〉 〈4, 1, 2, []〉4 0 〈0, 0,⊥, []〉 , 〈1, 0,⊥, [〈4, 1, 2〉]〉 〈1, 0, 3, [〈4, 1, 2〉]〉5 0 〈0, 0,⊥, [〈1, 0, 3〉]〉6 1 〈0, 0,⊥, [〈1, 0, 3〉]〉7 0 〈0, 0,⊥, [〈1, 0, 3〉]〉 , 〈1, 5,⊥, []〉 〈1, 5, 6, []〉8 2 〈0, 0,⊥, [〈1, 0, 3〉 , 〈1, 5, 6〉]〉9 1 〈0, 0,⊥, [〈1, 0, 3〉 , 〈1, 5, 6〉]〉 , 〈2, 7,⊥, []〉 〈2, 7, 8, []〉10 3 ul, [〈1, 0, 3〉 , 〈1, 5, 6〉]〉 , 〈1, 7,⊥, [〈2, 7, 8〉]〉11 0 , 5, 6〉]〉 , 〈1, 7,⊥, [〈2, 7, 8〉]〉 , 〈3, 9,⊥, []〉 〈3, 9, 10, []〉 , 〈1, 7, 10, [〈2, 7, 8〉 , 〈3, 9, 10〉]〉 , ...

Figur 3.8: Denne figur viser stakken i starten af for -løkken i algoritme 3, kørt pa LCP forstrengen “mississippi”. Yderligere vises hvilke lcp-intervaller der bliver behandlet (process) iløbet af for -løkken for et givent indeks i.

vil betingelsen i if -sætningen i linje 10 og while-løkken i linje 5 ikke være opfyldt, datop−1.lcp < LCP [i]. Til sidst vil intervallet I blive skubbet pa stakken, og top vil blivetilføjet til intervallets børne-liste (linje 15).

Det vil sige, at I’s venstre grænse er lig med top.lb, og LCP [i] < top.lcp. I’s højre grænsevil først blive sat i en senere iteration, og hvilket medfører at top.rb = i − 1 < I.rb.Dermed er det bevist at top er indlejret i I.

Antag at der findes et lcp-interval 〈`′, lb′, rb′〉, der omslutter top og er indlejret i I. DaI.lb = top.lb, skal top.lb = lb′ ∧ top.rb < rb′ være opfyldt. Det betyder, at intervalletallerede ma ligge pa stakken, og at intervallet ligger længere nede i stakken end I. Mendette kan ikke ske, da det vil betyde, at I er indlejret i dette interval, og derfor kan detomvendte ikke være tilfældet. Det beviser, at der ikke kan findes sadan et interval, ogderfor ma top være børne-interval til I.

Konsekvensen af sætning 3.7.1 er korrektheden af algoritme 3. Hvis LCP [i] ≤ top−1.lcp eropfyldt, lige før top bliver poppet af stakken, svarer det til, at top er børne-interval til top−1.I denne situation vil top blive poppet af stakken, behandlet og derefter tilføjet til top−1’sbørne-liste. Dermed er kravene for et bottom-up-gennemløb opfyldt i denne situation.

Hvis derimod top−1.lcp < LCP [i] < top.lcp er opfyldt, svarer det til, at top er børne-interval til LCP [i]-intervallet, der indeholder indekset i. I dette tilfælde vil algoritmen hoppeud af while-løkken, nar top er blevet behandlet. Det skyldes, at top−1 nu er det øversteelement pa stakken, og betingelsen i if -sætningen (linje 10) og while-løkken (linje 5) derforikke længere er opfyldt. Derefter vil algoritmen skubbe LCP [i]-intervallet pa stakken, og tilføjelastInterval til LCP [i]-intervallets børne-liste (if -sætningen linie 14). Dette overholder ogsakravene for et bottom-up-gennemløb.

I tilfældet hvor top < LCP [i] vil LCP [i]-intervallet bare blive tilføjet til stakken, derforer det ikke interessant at se pa dette tilfælde.

Da der ikke er andre steder algoritmen tilføjer elementer til børne-listerne, ma algoritmenoverholde kravene for et bottom-up-gennemløb af lcp-interval-træet.

Jeg vil kort argumentere for at algoritmens tidskompleksitet er O(n). For -løkken i linje 3,bliver udført n−1 gange. Da der for hvert gennemløb kun kan skubbes et element pa stakken,og while-løkken fjerner et element for hver gennemløb, vil while-løkken højst blive udført ngange. Dermed er udførelsestiden O(n).

Page 30: Analyse og implementering af suffikstræer baseret p˚a ... · Kapitel 1 Introduktion Suffikstræet1 er en interessant datastruktur, der har en lang række anvendelsesmuligheder inden

3. Relation mellem suffikstræer og udvidede suffiksarrays 27

Eksempel. Bottom-up algoritme 3 kan godt forekomme en anelse kompliceret, derfor viljeg i dette eksempel forklare hvordan algoritmen opfører sig, pa LCP -tabellen for strengen“mississippi”. Jeg vil ikke gennemga algoritmen fuldstændigt, men kigge pa følgende indekser1,3 og 9.

Pa figur 3.8 kan man se LCP tabellen, stakken og hvilke lcp-intervaller der bliver behandletaf funktionen process for hele kørselen af algoritmen. Stakken gar fra venstre mod højre, inogle tilfælde har jeg undladt bunden af stakken for at spare plads pa figuren. Øjebliksbilledet afstakken er taget i starten af for-løkken, hvorimod process er de intervaller der bliver behandleti løbet af for-løkken for indeks i.

i = 1LCP [1] = 1 og top.lcp = 0. Det svarer til, at der er et 1-interval med venstre grænse 0,der starter her. Intervallet bliver skubbet pa stakken i linje 15.

i = 3LCP [3] = 1 og top.lcp = 4. Det betyder, at der er et eller flere intervaller pa stakken, derslutter her, og derfor skal behandles. Det øverste interval pa stakken 〈4, 1,⊥, []〉 poppesaf stakken, behandles og tilføjes derefter som børne-interval til 〈1, 0,⊥, [〈4, 1,⊥〉]〉.

i = 9LCP [9] = 1 og top.lcp = 2. I dette tilfælde viser det sig, at i er lcp-indeks for 1-[7..10].Det første børne-interval 〈2, 7, 8, []〉 til 1-[7..10] bliver behandlet, hvorefter betingelsen iif-sætningen i linie 10 ikke er opfyldt. Dermed er betingelsen i while-løkken hellere ikkelængere opfyldt. Det vil sige, at lastInterval 6=⊥ og LCP [9] > top.lcp, hvilket medfører,at forældre-intervallet 1-[7..10] til 2-[7..8] bliver skubbet pa stakken (linje 15).

Page 31: Analyse og implementering af suffikstræer baseret p˚a ... · Kapitel 1 Introduktion Suffikstræet1 er en interessant datastruktur, der har en lang række anvendelsesmuligheder inden

Kapitel 4

Konstruktion af tabeller

I dette kapitel vil jeg gennemga, hvordan tabellerne i det udvidede suffiksarray kan konstrueresi lineær tid til inputstregen. Specielt vil jeg i detalje gennemga, hvordan CHILD tabellenkonstrueres, da denne tabel spiller en central rolle for, at man kan opfatte suffiksarrayet somen træstruktur.

4.1 Konstruktion af suffiksarrays

Teorien for lineær konstruktion af suffiksarrayet er baseret pa artiklen [KS03]. Artiklen præ-senterer en forholdsvis enkel algoritme skew til lineær konstruktion af suffiksarrays for hel-talsalfabeter1 baseret pa sortering. Grundlæggende bestar algoritmen af følgende trin:

1. Sorter suffikserne der begynder pa position i mod 3 6= 0 rekursivt.

2. Sorter de resterende suffikser baseret pa den opnaede information fra skridt et.

3. Flet de to sorterede lister fra skridt et og to.

Artiklen definerer sentinel tegnet $ til at være mindre end alle tegn i alfabetet. Dette erproblematisk, da alt den præsenteret teori i specialet antager, at dette tegn er større end alletegn i alfabetet. Det er ikke noget stort problem, da skew -algoritmen sagtens kan ændres tilat tage højde for dette.

Første skridt i skew -algoritmen sorterer suffikserne Xi hvor i mod 3 6= 0 i leksikografiskorden. Algoritmen starter med leksikografisk at navngive triplerne X[i..i + 2] med navne fraintervallet X ′

i ∈ [0, 2n3 [, hvor i mod 3 6= 0. For de leksikografiske navne gælder der, at X ′

i ≤ X ′j ,

hvis og kun hvis X[i..i + 2] ≤ X[j..j + 2]. Dette gøres i lineær tid ved at sortere triplernemed radix -sortering og derefter navngive triplerne pa følgende made. Den første triple fardet leksikografiske navn 0. Resten af triplerne navngives efter følgende princip. Lad s′ og t′

være leksikografiske navne for to tripler s og t der ligger lige efter hinanden i den sorterederækkefølge. Sa er t′ defineret ud fra s′ pa følgende made:

t′ ={

s′ + 1 hvis s < ts′ hvis s = t

Hvis alle triplerne far forskellige navne ved denne navngivning, er vi færdige med første delaf algoritmen. Et eksempel pa dette kan ses pa figur 4.1. Figuren viser suffikserne pa positioni mod 3 6= 0 og deres leksikografiske navne for strengen “mississippi” sorteret efter overstaendeprincip.

1Alfabeter der bestar af tegnene 0, 1, .... Bemærk dog at alle alfabeter der kan indekseres kan oversættestil et sadant alfabet.

28

Page 32: Analyse og implementering af suffikstræer baseret p˚a ... · Kapitel 1 Introduktion Suffikstræet1 er en interessant datastruktur, der har en lang række anvendelsesmuligheder inden

4. Konstruktion af tabeller 29

i Xi Sorterede tripler X ′

1 ississippi$ 7: ipp 02 ssissippi$ 1: iss 14 issippi$ 4: iss 15 ssippi$ 10: i$ 27 ippi$ 8: ppi 38 ppi$ 5: ssi 4

10 i$ 2: ssi 4

Figur 4.1: Denne figur viser suffikserne pa position i mod 3 6= 0 sorteret i første skridt afskew -algoritmen for stregen “mississippi”. Kolonnen X ′ viser de leksikografiske navne forsuffikserne.

i X12i Sorterede tripler SA12[i]

0 1102443 : ississippi$$ssissippi 02 : ippi$$ 21 102443 : issippi$$ssissippi 102 : issippi$$ 12 02443 : ippi$$ssissippi 110 : ississipp 03 2443 : i$$ssissippi 2 : i$$ 34 443 : ssissippi 3 : ppi 65 43 : ssippi 43 : ssippi 56 3 : ppi 443 : ssissippi 4

Figur 4.2: Denne figur viser, hvordan SA12 konstrueres ud fra X12 for strengen “mississippi”.Derudover vises sammenhængen mellem strengene X12 og X. Triplerne der sorteres efter faesved X12[ i−1

3 ..bn3 c] for i mod 3 = 1 og X12[n+j−2

3 ..b 2n3 c] hvor j mod 3 = 2, hvilket svarer til

at strengen kortes af efter sentinel -tegnet. Jeg har markeret dette med grat pa figuren.

Hvis alle triplerne derimod ikke har faet et unikt navn, konstrueres suffiksarrayet SA12

for strengen X12 rekursivt. Strengen X12 er defineret som:

X12 = [X ′i : i mod 3 = 1][X ′

j : j mod 3 = 2]

Altsa indeholder X12 de leksikografiske navne for suffikserne, hvis position i opfylder i mod3 = 1, sammensat med de leksikografiske navne for suffikserne hvis position j opfylder j mod3 = 2. Bemærk de leksikografiske navne tages i samme rækkefølge som suffikserne i strengen.F.eks. er X12 for strengen “mississippi”:

X12 = 1 1 0 2 4 4 3iss iss ipp i$$ ssi ssi ppi

Det viser sig, at suffiksarrayet SA12 repræsenterer den ønskede ordning for suffikserne Xi.Dette ses pa figur 4.2 hvor sammenhængen mellem suffikserne af X12 og X kan ses.

Nar vi har den leksikografiske orden af suffikserne pa position i mod 3 6= 0, mangler vibare at finde den leksikografiske orden af resten af suffikserne og flette de to lister.

For at finde den leksikografiske orden af de resterende suffikser sorteres parrene (X[i], Xi+1)for at fa den ønskede ordning. Dette kan gøres med en enkelt radix -sortering, da ordning afXi+1 allerede er kendt i kraft af SA12. Resultatet er suffiksarray SA0 for suffikserne pa posi-tion i mod 3 = 0. Denne tabel kan ses for strengen “mississippi” pa figur 4.3.

Til sidste mangler vi bare at flette de to suffiksarrays. For at finde den leksikografiskeordning mellem et suffiks Xj fra SA0 og et suffiks Xi fra SA12, opdeles problemet i totilfælde.

• Hvis i mod 3 = 1, kan vi skrive Si som X[i]Si+1 og Xj som X[j]Sj+1. Da (j+1) mod 3 =1 og (i + 1) mod 3 = 2, kan den leksikografiske orden af Xi+1 og Xj+1 findes som

Page 33: Analyse og implementering af suffikstræer baseret p˚a ... · Kapitel 1 Introduktion Suffikstræet1 er en interessant datastruktur, der har en lang række anvendelsesmuligheder inden

4. Konstruktion af tabeller 30

i Xi j (X[i], SA12[j]) SA0[i] XSA0[i]

0 mississippi$ 0 (m, 2) 0 mississippi$3 sissippi$ 1 (s, 1) 9 pi$6 sippi$ 2 (s, 0) 3 sissippi$9 pi$ 3 (p, 3) 6 sippi$

Figur 4.3: Denne figur viser suffiksarrayet SA0 for suffikserner Xi hvor i mod 3 = 0 forstrengen “mississippi”.

ordningen mellem de tilsvarende leksikografiske navne i SA12. Problemet er bare atfinde de rigtige indgange i SA12 for suffikserne Xi+1 og Xj+1. Det leksikografiske navnfor suffikset Xi+1 findes som:

SA12

[(i + 1)− 2

3+

⌊n + 2

3

⌋]Hvor SA

12er det inverse suffiksarray til SA12. Ligeledes kan det leksikografiske navn

for Xj+1 findes som:

SA12

[(j + 1)− 1

3

]• Hvis i mod 3 = 2, kan Xi skrives som X[i..i + 1]Xi+2 og Xj som X[j..j + 1]Xj+2. Da

(j + 2) mod 3 = 2 og (i + 2) mod 3 = 1, kan den leksikografiske ordning mellem Xi+2

og Xj+2 findes som ordningen mellem de tilsvarende leksikografiske navne i SA12. Detleksikografiske navn for Xi+2 findes som:

SA12

[(i + 2)− 1

3

]Ligeledes kan Xj+2 findes som:

SA12

[(j + 2)− 2

3+

⌊n + 2

3

⌋]Jeg vil her gennemga hvordan SA0 og SA12 flettes for strengen “mississippi”, dette kan

ogsa ses pa figur 4.4. Algoritmen starter med at sammenligne suffikserne X0 og X7, det svarertil at sammenligne m2 og i4. Det vil sige, at det første suffiksnummer i SA skal være 7. Pasammen made fortsættes frem til suffiks X8. I dette tilfælde skal vi sammenligne triplernemi6 og pp3, da 8 mod 3 = 2. Nar alle tallene i et af de to suffiksarrays er placeret, kan restenaf tallene fra den anden tabel tilføjes enden af suffiksarrayet SA.

Et bevis for at algoritmen er lineær kan ses i artiklen [KS03].

4.2 Konstruktion af tabel for længste fælles præfiks

Det er muligt at producere LCP -tabellen som et biprodukt under konstruktionen af suffiks-arrayet. Det kan dog være en fordel at adskille disse to algoritmer. Pa den made øger manfleksibiliteten, og samtidigt vil algoritmerne ogsa have et mindre pladsforbrug. Artiklen [Man]præsenterer en forbedret udgave af Kasai et al.’s algoritme til lineær konstruktion af LCP .Kasai et al.’s originale algoritme bruger 13n bytes, hvorimod den nye algoritme kun bruger 9bytes.

Algoritme 4 viser Kasai et al.’s originale algoritme. Jeg har tilpasset algoritmen sa suf-fiksarrayet er nul indekseret i stedet for et indekseret. Algoritmen starter med at beregne

Page 34: Analyse og implementering af suffikstræer baseret p˚a ... · Kapitel 1 Introduktion Suffikstræet1 er en interessant datastruktur, der har en lang række anvendelsesmuligheder inden

4. Konstruktion af tabeller 31

0 3 6 9mi6 si4 si4 pi7m2 s1 s0 p3

2 1 0 3 6 5 4 SA12

7 4 1 10 8 5 2 Suffiksnummerpp3 ss0 ss1

i4 i5 i6 i7

7 4 1 10 0 8 3 6 9 5 2 SA

SA0 og SA12 flettes

Figur 4.4: Denne figur viser, hvordan de to suffiksarrays SA0 og SA12 flettes for strengen“mississippi”. Øverst pa figuren er to tabeller, øverste række tal i den venstre tabel er suf-fiksnumrene, der starter pa position j mod 3 = 0 for strengen “mississippi”. Under dissesuffiksnumre ses triplerne X[j..j + 1]X ′

j+2 og under dem tuplerne X[j]X ′j+1. Den højre tabel

indeholder suffiksnumrene, der starter pa position i mod 3 6= 0. Denne tabel er opbygget pasammen made som den venstre, men der er yderligere tilføjet tabellen SA

12som den øverste

række tal i tabellen. Nederst pa figuren ses den resulterende suffiksarray, nar SA0 og SA12

flettes.

input : Strengen X og det tilsvarende suffiksarray SAoutput: LCP -tabellen for strengen X

for i← 0 to n do SA[SA[i]] = i;1

h← 0;2

LCP [0]← 0;3

for i← 0 to n do4

k = SA[i];5

if k 6= 0 then6

j ← SA[k − 1];7

while i + h < n ∧ j + h < n ∧ X[i + h] = X[j + h] do8

h← h + 1;9

LCP [k]← h;10

if 0 < h then h← h− 1;11

Algorithm 4: Kasai et al. LCP konstruktion.

Page 35: Analyse og implementering af suffikstræer baseret p˚a ... · Kapitel 1 Introduktion Suffikstræet1 er en interessant datastruktur, der har en lang række anvendelsesmuligheder inden

4. Konstruktion af tabeller 32

det inverse suffiksarray SA. I linje 5 sættes k til suffiksnummeret, der har den i’de position isuffiksarrayet.

Hvis k 6= 0, skal vi finde det suffiks j, der ligger lige før k i suffiksarrayet (linje 7). Artiklenhenviser til et bevis for at LCP [SA[i − 1]] − 1 ≤ LCP [SA[i]] i Kasai et al.’s artikel. Det erdesværre ikke lykkes mig at fa fat i denne artikel. Heldigvis er det ikke særligt svært at bevisedenne egenskab:

Sætning 4.2.1. Lad `i−1 = LCP [SA[i − 1]] og `i = LCP [SA[i]], hvor SA er det inversesuffiksarray, og bade `i−1 og `i er defineret. Sa gælder der, at `i−1 − 1 ≤ `i.

Bevis. Na vi har `i−1 ved vi at:

Xi−1[0..`i−1 − 1] =XSA[SA[i−1]−1][0..`i−1 − 1]⇒

Xi[0..`i−1 − 2] =XSA[SA[i−1]−1]+1[0..`i−1 − 2]

Dermed er det bevist at `i−1 − 1 ≤ `i.

Denne egenskab udnyttes ved at opretholde invarianten h = LCP [SA[i− 1]]− 1 i startenaf for -løkken. Det vil sige, at det kun er nødvendigt at sammenligne tegnene for suffikserneXk og Xj fra position h og fremefter. Det er ogsa denne egenskab, der medfører at algoritmener lineær. Da h < n, og h kun kan aftage med en for hver iteration, vil linje 9 højst bliveudført O(n) gange.

Nar while-løkken stopper er h = LCP [k]. Derved bliver LCP udfyldt korrekt.Artiklen [Man] observerer at algoritmen behandler suffikserne i følgende rækkefølge

SA[0], ..., SA[n]. Nar SA[i] er tildelt k, bruges denne plads ikke længere. Via Burrows-Wheelertransformation er det muligt at finde SA[i+1] ud fra SA[i]. Det vil sige, at sa snart SA[i+1]er fundet, kan LCP [SA[i]] gemmes i SA[i]. Det sikre, at vi kun bruger 9 bytes til konstruktionaf LCP , hvilket er optimalt. Set artiklen [Man] for yderligere detaljer.

4.3 Konstruktion af børne-tabellen

Børne-tabellen CHILD kan konstrueres i lineær tid ved et dybbe-først-gennemløb af lcp-interval-træ for strengen som beskrevet i afsnittet 3.7. Dette kan gøres ved at definere funk-tionen process i algoritmen 3. Nar process bliver kaldt, er alle de nødvendige informationer tilat udfylde CHILD til radighed.

Overstaende metode er dog ret langsom og ikke særligt pladsbesparende. Artiklen [AKO04]beskriver to specialiserede algoritmer, en til at udfylde felterne up og down i CHILD og entil at udfylde next`Index. Jeg har sammensat disse to algoritmer, da dette ogsa vil væreønskværdigt i en rigtig implementering. Den specialiserede algoritme er bade hurtigere ogbruger væsentligt mindre plads end algoritmen 3. Det skyldes, at stakken i algoritmen kunindeholder heltals indekser. I modsætning til algoritmen 3 hvor elementerne pa stakken erquadrupler, hvoraf en af indgangene er en liste af børne-intervaller. Da denne liste kan være|Σ| lang, er dette en ret stor pladsbesparelse. Algoritmen er kun en konstant faktor hurtigereend algoritme 3, men i praksis har det ogsa sin ret.

Jeg vil herunder gennemga den specialiserede algoritme 5. Ligesom algoritme 3 scanneralgoritme 5 LCP tabellen lineært igennem. For hvert indeks i ∈ [0..n] bliver lcp-værdiensammenlignet med lcp-værdien for indekset, der ligger øverste pa stakken. Hvis betingelsenLCP [i] < LCP [top] i while-løkken er sand, betyder det, at et eller flere lcp-intervaller slutteri indekset i − 1. For hvert lcp-interval der slutter i indekset i − 1, skal feltet down i CHILDsættes til den første lcp-indeks for det pagældende interval. Lige efter lastIndex er blevetsat til det indeks, der la øverst pa stakken, skal det afgøres, om lastIndex er et lcp-indeksfor intervallet, der starter i top. Hvis LCP [top] 6= LCP [lastIndex] er sandt, er lastIndex

Page 36: Analyse og implementering af suffikstræer baseret p˚a ... · Kapitel 1 Introduktion Suffikstræet1 er en interessant datastruktur, der har en lang række anvendelsesmuligheder inden

4. Konstruktion af tabeller 33

det først lcp-indeks. Hvis derimod LCP [top] = LCP [lastIndex], betyder det, at vi skal søgened gennem stakken, indtil vi kommer til en situation hvor LCP [top] 6= LCP [lastIndex],hvorefter vi kan sætte CHILD.down.

Efter at CHILD.down er sat for alle intervallerne, der slutter i i − 1, sættes CHILD.upfor intervallet. Da lastIndex var lcp-indekset for det største af intervallerne, der sluttede ii− 1, sættes CHILD[i].up til lastIndex.

Til sidst er det nødvendigt at tjekke, om top er det næste lcp-indeks efter i. Dette gøresganske simpelt ved at sammenligne lcp-værdierne for top og i. Hvis værdierne er ens, betyderdet, at CHILD.next`Index skal sættes til top.

input : LCP hvor |LCP | = noutput: CHILD

lastIndex← −1;1

push(0);2

for i← 1 to n do3

while LCP [i] < LCP [top] do4

lastIndex← pop();5

if LCP [i] ≤ LCP [top] and LCP [top] 6= LCP [lastIndex] then6

CHILD[top].down← lastIndex;7

/* nu gælder der at LCP [top] ≤ LCP [[i] */if lastIndex 6= −1 then8

CHILD[i].up← lastIndex;9

lastIndex← −1;10

if LCP [i] = LCP [top] then11

CHILD[top].next`Index← i;12

push(i);13

Algorithm 5: Udfylder felterne up, down og next`Index i CHILD

Jeg vil nu bevise, at algoritmen udfylder felterne i CHILD korrekt. Da artiklen [AKO04]bruger en separat algoritme til at udfylde next`Index feltet i CHILD, er denne algoritmemeget simpel. Derfor har forfatterne valgt, at undlade at bevise korrektheden for udfyldningenaf dette felt. Men da min algoritme udfylder alle felterne i CHILD-tabellen, i en samletalgoritme, er det ogsa nødvendigt at bevise, at next`Index feltet bliver korrekt udfyldt.

For at bevise at algoritmen er korrekt, far vi brug for følgende lemma:

Lemma 4.3.1. Følgende invarianter er vedligehold i for-løkken i algoritme 5. Lad e1, ..., ep

være indekserne pa stakken, hvor ep er det øverste element. Sa gælder der:

1. e1 < ... < ep og LCP [e1] ≤ ... ≤ LCP [ep].

2. LCP [ej ] < LCP [ej+1]⇒ ∀ej < k < ej+1 : LCP [k] > LCP [ej+1].

Bevis. Jeg vil bevise de to punkter hver for sig, hvor begge punkter bevises ved induktion.

1. I det første gennemløb af for -løkken indeholder stakken kun indekset 0, derfor er lem-maet opfyldt i denne situation.

Antag at lemmaet er opfyldt, efter for -løkken er gennemløbet i gange, hvor i < n.Lad e1, ..., ep være indekserne stakken indeholder for gennemløb i + 1, sa er der firemuligheder:

Page 37: Analyse og implementering af suffikstræer baseret p˚a ... · Kapitel 1 Introduktion Suffikstræet1 er en interessant datastruktur, der har en lang række anvendelsesmuligheder inden

4. Konstruktion af tabeller 34

(a) ∃ q ∈ [1, p[: LCP [e1] ≤ ... ≤ LCP [eq] ≤ LCP [i + 1] < LCP [eq+1] ≤ ... ≤ LCP [ep]I while-løkken linje 4, bliver elementerne eq+1, ..., ep poppet af stakken, hvorefteri+1 bliver skubbet pa stakken (linie 11). Efter gennemløb i+1 af for -løkken liggerindekserne e1, ..., eq, i + 1 pa stakken, hvor i + 1 er det øverste element. e1 < ... <eq < i + 1 er abenlyst opfyldt, da e1 < ... < ep er opfyldt pr. induktionshypotesen.Og eq < i + 1, da eq er skubbet pa stakken i en tidligere iteration. Da LCP [e1] ≤... ≤ LCP [eq] ≤ LCP [i + 1] er givet, er kravene for lemmaet opfyldt.

(b) ∃ q ∈ [1, p[: LCP [e1] ≤ ... ≤ LCP [eq] ≤ LCP [i + 1] = LCP [eq+1] ≤ ... ≤ LCP [ep]I while-løkken linje 4, bliver elementerne eq+2, ..., ep poppet af stakken, hvorefteri + 1 bliver skubbet pa stakken (linje 11). Efter gennemløb i + 1 af for -løkkenligger indekserne e1, ..., eq, eq+1, i+1 pa stakken, hvor i+1 er det øverste element.e1 < ... < eq < eq+1 < i + 1 er opfyldt ud fra induktionshypotesen, og det faktumat eq+1 < i + 1, fordi eq+1 er skubbet pa stakken i en tidligere iteration endi + 1. LCP [e1] ≤ ... ≤ LCP [eq] ≤ LCP [eq+1] ≤ LCP [i + 1] er ogsa opfyldt daLCP [eq+1] = LCP [i + 1].

(c) LCP [i + 1] ≤ LCP [e1]Nar algoritmen starter bliver indekset 0 skubbet pa stakken, dette element bliveraldrig poppet af stakken, pga. betingelsen i while-løkken i linje 4. Det vil sige, ate1 = 0 og LCP [e1] = 0 ud fra definitionen for lcp-intervaller 3.1.1. I gennemløbi + 1 vil indekserne e2, ..., ep blive poppet af stakken, og derefter vil LCP [i + 1]blive skubbet pa stakken. Da LCP [e1] = 0, er LCP [e1] ≤ LCP [i + 1] opfyldt.

(d) LCP [ep] ≤ LCP [i + 1]Dette svarer direkte til (a) hvor eq = ep Vi ved ud fra induktionshypotesen ate1 < ... < ep, og LCP [e1] ≤ ... ≤ LCP [ep] er opfyldt. e1 < ... < ep < i+1 er opfyldtud fra samme argumentation som i (a), og LCP [e1] ≤ ... ≤ LCP [ep] ≤ LCP [i + 1]er abenlyst opfyldt.

2. Nu mangler jeg bare at vise, at følgende er opfyldt for et indeks ej .

LCP [ej ] < LCP [ej+1]⇒ ∀k ∈]ej , ej+1[: LCP [ej+1] < LCP [k]

Ud fra induktionshypotesen ved vi, at dette gælder for alle elementerne pa stakken efteriteration i af for -løkken. Lad e1, ..., eq, i + 1 være indekserne pa stakken efter iterationi+1. Sa ma situationen inden iteration i+1 se ud som følger, hvis LCP [eq] < LCP [i+1]skal være opfyldt:

∃ q ∈ [1, p[: LCP [e1] ≤ ... ≤ LCP [eq] < LCP [i + 1] < LCP [eq+1] ≤ ... ≤ LCP [ep]

Vi ved ud fra punkt 1 (a), at eq < eq+1 < ... < ep < i + 1, og at stakken indehol-der indekserne e1, ..., eq, i + 1 efter iteration i + 1. Hvis der eksisterer et indeks k′ iintervallet (eq, ep), som ikke ligger pa stakken, ved vi ud fra induktionshypotesen atqq+1 < LCP [k′]. Det betyder, at LCP [i+1] < qq+1 < LCP [k′]. Det vil sige, at følgendeer opfyldt:

∀k ∈]eq, i + 1[: LCP [i + 1] < LCP [k]

Det specielle tilfælde hvor LCP [ep] < LCP [i+1], stemmer helt overens med overstaendehvor eq = ep.

Heraf følger lemmaet.

Lemmaet fastslar, at lcp-intervallerne pa stakken har stigende lcp-værdier jo længere mankommer ned i stakken. Dette skal vi bruge til at bevise følgende sætning, der hævder atalgoritme 5 udfylder felterne i CHILD korrekt.

Page 38: Analyse og implementering af suffikstræer baseret p˚a ... · Kapitel 1 Introduktion Suffikstræet1 er en interessant datastruktur, der har en lang række anvendelsesmuligheder inden

4. Konstruktion af tabeller 35

Sætning 4.3.2. Algoritme 5 udfylder felterne up, down og next`Index i CHILD korrekt.

Bevis. Beviset er delt op efter de tre felter i CHILD.

1. upDet eneste sted i algoritmen hvor up bliver sat er i linje 9. Det er igen nødvendigt atvise, at denne tildeling opfylder definitionen for CHILD 3.3.1. Nar program sætnin-gen CHILD[i].up ← lastIndex bliver udført, gælder der, at LCP [top] ≤ LCP [i] <LCP [lastIndex] ifølge betingelsen i while-løkken. Vi ved ogsa at top < lastIndex < isom en direkte følge af lemma 4.3.1.

Definitionen af CHILD.up for indeks i ser ud som følger:

CHILD[i].up = min{q ∈ [0..i− 1] |LCP [i] < LCP [q] ∧∀k ∈ [q + 1..i− 1] : LCP [q] ≤ LCP [k]}

Jeg vil her argumentere for, at lastIndex er en gyldig kandidat for q. LastIndex ∈[0..i−1] og LCP [i] < LCP [lastIndex] er klart opfyldt ud fra overstaende ræsonnement.Yderligere ved vi, at ∀k ∈ [lastIndex+1..i−1] : LCP [lastIndex] ≤ LCP [k], ellers villelastIndex være poppet af stakken i en tidligere iteration.

Nu mangler jeg bare at bevise, at lastIndex er den mindste kandidat til q. Dette viljeg gøre med et modbevis, hvor jeg antager der findes en kandidat q′ til q, som ligger iintervallet [0..lastIndex − 1]. Ud fra definitionen af CHILD 3.3.1 ved vi, at LCP [i] <LCP [q′] ≤ LCP [lastIndex]. Og fra overstaende overvejelser ved vi, at LCP [top] ≤LCP [i]. Dermed ma LCP [top] ≤ LCP [i] < LCP [q′] ≤ LCP [lastIndex]. Det betyderat q′ ma have ligget pa stakken mellem top og lastIndex. Men da lastIndex indeholderdet element, der la lige over top pa stakken, modsiger det, at et sadant q′ kan eksistere.Dermed følger det at lastIndex er det korrekte indeks CHILD.up.

2. downDet eneste sted i algoritmen hvor down bliver sat er i linje 7, sa jeg skal bare vi-se at denne tildeling opfylder definitionen for CHILD 3.3.1. Nar program sætningenCHILD[top].down ← lastIndex bliver udført, gælder der, at LCP [i] ≤ LCP [top] <LCP [lastIndex]. Det skyldes, at i ved fra lemma 4.3.1, at hvis e1, ..., ep er elemen-terne pa stakken, sa er LCP [e1] ≤ ... ≤ LCP [ep]. Hvilket medfører, at LCP [top] ≤LCP [lastIndex] ma gælde i linje 7. Yderligere er LCP [i] ≤ LCP [top] 6= LCP [lastIndex]opfyldt ifølge if -sætningen i linje 6. Derfor ma LCP [i] ≤ LCP [top] < LCP [lastIndex]være opfyldt i linje 7. Vi ved ogsa, at top < lastIndex < i, som direkte følger af lemma4.3.1.

Hvis vi kigger pa definitionen af CHILD.down, hvor vi bruger top som indeks, ser detud som følger:

CHILD[top].down = max{q ∈ [top + 1..n] |LCP [top] < LCP [q] ∧∀k ∈ [top + 1..q − 1] : LCP [q] < LCP [k]}

Her ses det, at lastIndex er en gyldig kandidat for q. Dette ses ud fra, at følgendeer sandt: lastIndex ∈ [top + 1..n] og LCP [top] < LCP [lastIndex] er klart opfyldt udfra overstaende ræsonnement. Yderligere gælder der, at ∀k ∈ [top + 1..lastIndex− 1] :LCP [lastIndex] < LCP [k] er opfyldt ud fra lemma 4.3.1, hvor top svarer til ej , oglastIndex svarer til ej+1.

Nu mangler jeg bare at bevise, at lastIndex er den største kandidat til q. Dette viljeg gøre med et modbevis. Jeg antager, at der findes en kandidat q′ til q, der er større

Page 39: Analyse og implementering af suffikstræer baseret p˚a ... · Kapitel 1 Introduktion Suffikstræet1 er en interessant datastruktur, der har en lang række anvendelsesmuligheder inden

4. Konstruktion af tabeller 36

end lastIndex og mindre en i. Ud fra definitionen for CHILD 3.3.1 skal følgende væreopfyldt LCP [q′] < LCP [lastIndex], ∀k ∈ [top + 1..q′ − 1] : LCP [q′] < LCP [k] oglastIndex ∈ [top+1..q′−1]. Men det medfører, at lastIndex ville være blevet poppet afstakken, da indekset q′ blev behandlet. Det viser, at sadan et indeks ikke kan eksistere,og dermed ma lastIndex være det korrekte indeks for CHILD.down.

3. next`IndexDet eneste sted i algoritmen hvor next`Index bliver sat er i linje 12. Igen er detnødvendigt at vise, at denne tildeling opfylder definitionen for CHILD 3.3.1. Nar pro-gram sætningen CHILD[top].next`Index ← i bliver udført, gælder der, pa grund afbetingelsen i if -sætningen i linje 11, at LCP [top] = LCP [i].

Hvis vi kigger pa definitionen af CHILD.next`Index, hvor vi bruger top som indeks,ser det ud som følger:

CHILD[top].next`Index = min{q ∈ [top + 1..n] |LCP [top] = LCP [q] ∧∀k ∈ [top + 1..q − 1] : LCP [q] < LCP [k]}

Her ses det, at i er en gyldig kandidat for q, da i ligger i intervallet [top + 1..n], ogLCP [top] = LCP [q] er klart opfyldt ud fra overstaende ræsonnement. Yderligere gælderder, at ∀k ∈ [top + 1..i− 1] : LCP [i] < LCP [k] er opfyldt, ud fra lemma 4.3.1, hvor topsvarer til ej , og i svarer til ej+1.

Nu mangler jeg bare at vise, at i er den mindste kandidat til q. Dette vil jeg, ligesomi beviset for down, vise med et modbevis. Jeg antager, at der findes en kandidat q′

til q, som er mindre end i og større end top. Men vi ved ud fra lemma 4.3.1, at ∀k ∈[top + 1..i − 1] : LCP [i] < LCP [k]. Det betyder at LCP [top] = LCP [i] < LCP [q′],derfor kan q′ ikke være en gyldig kandidat til q. Det beviser, at i ma være den korrekteværdi for CHILD[top].next`Index.

Dermed er det bevist, at algoritme 5 fylder felter up, down og next`Index i CHILD korrekt.

4.4 Konstruktion af suffikslinks-tabellen

Ligesom for de andre tabeller i det udvidede suffiksarray er det muligt at konstruere suffikslink-tabellen i lineær tid. Algoritmen laver et bredde-først-gennemløb af lcp-interval-træet star-tende i roden. Dette sikre, at nar algoritmen besøger et `-interval, vil alle intervaller medlcp-værdi mindre end ` allerede være besøgt.

Na algoritmen besøger lcp-intervallet [i..j], gemmes intervallets grænser pa alle lcp-indekser-nes plads i en midlertidig tabel. Hvis vi kan finde et lcp-indeks for suffikslink-intervallet [l..r],kan vi ogsa finde dets grænser. Nar vi har fundet grænserne for suffikslink-intervallet, gemmesdisse i SLINK-tabellen pa det første lcp-indeks plads for intervallet [i..j].

Problemet ligger altsa i at finde et lcp-indeks for suffikslink-intervallet. Vi ved ud fradefinitionen af suffikslink-intervallet 3.5.4, at l ≤ link(i) + 1 < link(j) ≤ r. Yderligere viserfølgende lemma, at der findes et lcp-indeks for [l..r] i intervallet [link(i) + 1..link(j)].

Lemma 4.4.1. Lad `-[i..j] være et lcp-interval med [l..r] som tilhørende suffiksinterval. Safindes der et (`− 1)-indeks k for [l..r], som opfylder link(i) + 1 ≤ k ≤ link(j).

Bevis. Da følgende er abenlyst sandt:

lcp(XSA[link(i)], ..., XSA[link(j)]) = min{LCP [q]|q ∈]link(i), link(j)]}

ma der findes et indeks k, sa lcp(XSA[link(i)], ..., XSA[link(j)]) = LCP [k] og link(i) + 1 ≤ k ≤link(j).

Page 40: Analyse og implementering af suffikstræer baseret p˚a ... · Kapitel 1 Introduktion Suffikstræet1 er en interessant datastruktur, der har en lang række anvendelsesmuligheder inden

4. Konstruktion af tabeller 37

Det viser sig at dette lcp-indeks kan findes i konstant tid ved brug af range minimum query,hvis LCP -tabellen er blevet behandlet i forvejen. Denne forbehandling tager lineær tid, ogøger derfor ikke tidskompleksiteten. Artiklen [BFC00] forklarer dette i nærmere detalje.

Da der kun bruges O(|Σ|) tid for hver lcp-interval, og forbehandlingen tager O(n), er dentotale tidskompleksitet O(n).

Page 41: Analyse og implementering af suffikstræer baseret p˚a ... · Kapitel 1 Introduktion Suffikstræet1 er en interessant datastruktur, der har en lang række anvendelsesmuligheder inden

Kapitel 5

Optimeringer

I dette kapitel vil jeg præsentere en række pladsoptimeringer af det udvidede suffiksarray.Jeg har i de tidligere kapitler valgt at fokusere pa teorien, der danner grundlaget for det suf-fiksarray baserede suffikstræ, uden at komme ind pa dets pladsforbrug. Men da det udvidedesuffiksarrays minimale pladsforbrug er hele motivationen for overhovedet at bruge det i stedetfor et normalt suffiktræ, er det selvfølgelig helt afgørende for specialet.

Jeg vil starte med, at vise hvordan man kan reducere pladsforbruget for LCP -tabellen,fra 4n bytes til n bytes for en streng med n tegn. Ligeledes kan CHILD-tabellen optimeres,fra at bruge 12n bytes, til kun at bruge n bytes. SLINK tabellen der i en naiv løsning villebruge 8n bytes kan reduceres til kun at bruge 2n bytes.

Artiklen [AKO04] hævder, at disse pladsoptimeringer kan gennemføres uden praktisk for-ringelse af udførelsestiden. Generelt gælder der, at der er et trade-off mellem pladsforbrug ogudførelsestid. For de fleste algoritmer er det muligt at optimere dem til et mindre pladsfor-brug, hvor dette til gengæld vil betyde en længere udførelsestid og omvendt. Og jeg mener ikkedette tilfælde er nogen undtagelse. Jeg vil komme nærmere ind pa dette i løbet af kapitlet.

I implementeringen af et udvidet suffiksarray er det muligt, kun at oprette de tabeller manhar brug for til en given algoritme. F.eks. er det muligt at lave et bottom-up-gennemløb aftræet kun ved brug af tabellerne SA og LCP . Og i de fleste tilfælde er det ikke nødvendigtat have SLINK tabellen til radighed. Dette kan medføre store pladsbesparelser i forhold tilet normalt suffikstræ. Den eneste del af suffiktræet der i nogle tilfælde kan undværes, ersuffikslinkserne. Men da suffikslinksene bruges under konstruktionen af suffikstræet, kan deførst fjernes bagefter.

5.1 Pladsoptimering af tabel for længste fælles præfiks

Det kan observeres at værdierne i LCP sjældent overstiger 255 for almindelig tekst, derfor erdet muligt at gemme LCP pa n bytes. For de værdier der er større end 255, gemmes indeksetog værdien som et par i en liste LCP ′ sorteret efter indeks. Det vil sige, at hvis LCP [i] = 255,findes værdien ved en binær søgning efter indekset i i tabellen LCP ′.

Hvis LCP gennemløbes i sekventiel rækkefølge, vil indekserne i LCP ′ optræde i sammerækkefølge som indekserne i LCP med værdi 255. Det betyder, at LCP stadig kan gen-nemløbes sekventielt i O(n). Dette udnyttes f.eks. i konstruktionen af CHILD-tabellen, hvorLCP -tabellen skannes sekventielt.

Man kunne frygte, at denne pladsoptimering af LCP ville betyde, at det var nødvendigtat sortere elementerne i LCP ′, hver gang et element tilføjes til LCP ′ under konstruktionenaf LCP . Men dette kan undgas ved at konstruere LCP i et array der bruger 4n bytes, ogderefter fylde værdierne over i den pladsoptimerede tabel i den rigtige rækkefølge. Det vil

38

Page 42: Analyse og implementering af suffikstræer baseret p˚a ... · Kapitel 1 Introduktion Suffikstræet1 er en interessant datastruktur, der har en lang række anvendelsesmuligheder inden

5. Optimeringer 39

sige, at elementerne i LCP ′ bliver tilføjet i den rigtige rækkefølge, og derfor ikke behøver atblive sorteret.

Artiklen [AKO04] pastar, at denne pladsoptimering ikke skulle medføre et mærkbart over-head. Det skyldes, at LCP sjældent vil indeholde værdier der er større end 255. Men f.eks.er ca. 20% af værdierne i LCP større end 255 for de første 1000000 tegn af det menneskeligeX kromosom. Sa hvis man tilgar LCP i en vilkarlig rækkefølge, vil det være nødvendigt atlave en binær søgning i en liste med 207694 elementer, hver gang man tilgar en værdi i LCP ,der er større end 255. Dette bliver især et synligt problem, nar man bruger CHILD-tabellentil at navigere ned gennem lcp-interval-træet. Det skyldes, at indgangene i LCP i dette til-fælde ikke bliver tilgaet sekventielt. Det er muligt at forbedre ideen fra artiklen [AKO04], sadet i mange tilfælde ikke er nødvendigt at søge hele LCP ′ igennem. Det kan observeres, atnar CHILD-tabellen bruges til at navigere ned gennem lcp-interval-træet, vil der kun væreopslag i LCP inden for det aktuelle lcp-interval. Hvis det aktuelle lcp-interval [i..j] har en`-værdi, der er større end 255, vil 255 ≤ LCP [i] ∀i ∈]i, j]. Det vil betyde, at alle opslagenei LCP for det aktuelle interval vil resultere i en søgning i hele LCP ′. For at mindske detteproblem, husker jeg positionen p, af det sidste indeks k jeg søgte efter i LCP ′. Nar jeg sa skalfinde et nyt indeks l i LCP ′, er det kun nødvendigt at lave en binær søgning i intervallet I:

I ={

[max(0, p− (k − i)), p] hvis i ≤ k]p, min(p + (k − i), n)] hvis k < i

Dette er en stor forbedring hvis i ligger tæt pa k, hvilket ofte er tilfældet nar CHILD-tabellenbruges til at navigere ned gennem lcp-træet. Dog er dette ikke en worst-case forbedring menkun en praktisk forbedring.

5.2 Pladsoptimering af børne-tabellen

CHILD-tabellen indeholder tre felter up, down og next`Index. Artiklen [AKO04] viser, hvor-dan disse tre felter kan samles i et felt. Som nævnt i 3.3 indeholder CHILD[i].down el-ler CHILD[j + 1].up det første lcp-indeks for lcp-intervallet [i..j]. Men i mange tilfælde vilCHILD[i].down og CHILD[j +1].up indeholde den samme information. For at fjerne denneoverflødige information gemmes down feltet kun, hvis det ikke indeholder den samme infor-mation som up feltet. Heldigvis er det kun nødvendigt at have et down felt for et lcp-interval,dette kan ses ud fra følgende ræsonnement. Et lcp-interval med k lcp-indekser har højstk + 1 børne-intervaller [l1..r1], ..., [lk+1..rk+1]. For at sikre, at vi ikke gemmer information iCHILD, der overlapper med værdier for underliggende børne-intervaller, kan vi kun gemmeinformation om børne-intervallerne pa de k lcp-indekser. Lad [lq..rq] være et af de første kbørne-intervaller, sa kan vi gemme det første `-indeks i CHILD[rq +1].up. For det sidste lcp-interval [lk+1..rk+1] gemmes det første lcp-indeks i CHILD[lk+1].down. Pa den made er detkun nødvendigt med et down felt for et lcp-interval, og samtidigt er vi sikre pa at værdiernefor de forskellige lcp-intervaller ikke overlapper i CHILD.

Hvis værdien for feltet CHILD[i].up gemme i CHILD[i−1].up i stedet, kan det observe-res, at kun et af de tre felter up, down og next`Index er defineret for hvert indeks i CHILD.Da up og down felterne tydeligvis ikke overlapper, er det kun nødvendigt at argumentere forat next`Index ikke er defineret nar up og down er defineret. Feltet down er kun defineret forCHILD[lk+1], men da lk+1 er det sidste lcp-indeks ik for [i..j], kan CHILD[lk+1].next`Indexikke være defineret.

Ud fra definitionen 3.3.1 af CHILD.up ved vi, at CHILD[i + 1].up er defineret hvis ogkun hvis LCP [i] > LCP [i + 1]. Men ifølge definitionen af CHILD.next`Index 3.3.1 kanCHILD[i].next`Index kun være defineret, hvis LCP [i] ≤ LCP [i + 1] gælder. Det vil sige, athvis vi gemmer værdien for CHILD[i + 1].up i CHILD[i].up, vil felterne up og next`Indexaldrig være defineret pa samme tid. Det betyder, at vi kan gemme de tre felter i et enkelt felt.

Page 43: Analyse og implementering af suffikstræer baseret p˚a ... · Kapitel 1 Introduktion Suffikstræet1 er en interessant datastruktur, der har en lang række anvendelsesmuligheder inden

5. Optimeringer 40

i SA[i] LCP [i] up down next XSA[i]

CHILD[i]

0123456789

1011

741

100986352

11

014100102130

21

6

8

9

12

6

9

10

43

57

11

ippi$issippi$ississippi$i$mississippi$pi$ppi$sippi$sissiippi$ssippi$ssissippi$$

Figur 5.1: Figuren viser, hvordan de tre felter i CHILD, up, down og next`Index, kan samlesi et felt for strengen “mississippi”.

Det er muligt at skelne mellem de tre felter pa følgende made:

CHILD[i] =

CHILD[i + 1].up hvis LCP [i] > LCP [i + 1]CHILD[i].down hvis LCP [i] < LCP [i + 1] ∧

LCP [i] < LCP [CHILD[i]]CHILD[i].next`Index hvis LCP [i] ≤ LCP [i + 1] ∧

LCP [i] = LCP [CHILD[i]]

(5.1)

Figur 5.1 viser hvordan de tre felter i CHILD, up, down og next`Index, kan samles i etfelt for strengen “mississippi”. Pilene viser hvor værdierne for up og down kan gemmes. Degra tal i down viser de værdierne, der er redundante, fordi den samme information alleredeer gemt i det tilsvarende up felt.

Eksempel. Jeg vil i dette eksempel vise, hvordan man kan finde ud af om indgangen 3 iCHILD er et up, down eller next`Index felt. For at finde ud af om CHILD[3] er et up,down eller next`Index felt, bruges formel 5.1:

CHILD[3] =

CHILD[3 + 1].up hvis LCP [3] > LCP [3 + 1]CHILD[3].down hvis LCP [3] < LCP [3 + 1] ∧

LCP [3] < LCP [CHILD[3]]CHILD[3].next`Index hvis LCP [3] ≤ LCP [3 + 1] ∧

LCP [3] = LCP [CHILD[3]]

= CHILD[3 + 1].up

= CHILD[4].up

Det viser sig altsa, at CHILD[3] indeholder værdien af CHILD[4].up.Pa samme made kan det ses, at CHILD[7] indeholder værdien af CHILD[7].next`Index,

fordi LCP [7] ≤ LCP [8] og LCP [7] = LCP [CHILD[7]] = LCP [11] = 0.Til sidst ses det, at CHILD[9] indeholder værdien af CHILD[9].down, da LCP [9] <

LCP [10] og LCP [9] = 1 < LCP [CHILD[9]] = LCP [10] = 3.

Page 44: Analyse og implementering af suffikstræer baseret p˚a ... · Kapitel 1 Introduktion Suffikstræet1 er en interessant datastruktur, der har en lang række anvendelsesmuligheder inden

5. Optimeringer 41

Det er yderligere muligt at reducere CHILD-tabellens pladsforbrug til n bytes. For at opnadette, gemmes værdierne i CHILD relativt til deres indeks. Det gøres pa følgende made, hvisj = CHILD[i] sa gemmes værdien abs(j−i) i stedet. Det resulterer i, at værdierne i CHILD-tabellen sjældent overstiger 255. Ved at undlade at gemme de relative værdier der er størreend 255, er det muligt at gemme CHILD pa n bytes.

For et lcp-interval [i..j] hvor det første lcp-indeks ikke længere kan findes ved hjælp afCHILD-tabellen, bruges en binær søgning, som beskrevet i [MM90], til at finde det indekseti stedet. Dette gøres pa følgende made. Lad Σ = {0, 1, ..., |Σ| − 1} være alfabetet for strengenX og α = X[SA[i] + |lcp(XSA[i], XSA[j])|]. Sa findes det første lcp-indeks i1 for [i..j] ved atlave en binær søgning over suffikserne i intervallet efter præfikserne

X[SA[i]..SA[i] + |lcp(XSA[i], XSA[j])| − 1]δ hvor δ ∈ [α + 1, |Σ| − 1] ∪ {$}

Det første lcp-indeks i1 er resultatet af den første binære-søgning, der finder et af præfikserne.Bemærk at α er det første tegn pa kanten ned til det første børne-interval for [i..j]. Hvis

β er det første tegn pa kanten ned til den andet børne-interval for [i..j], kan vi finde posi-tionen hvor det anden børne-interval starter, ved at søge efter præfikset lcp(XSA[i], XSA[j])β.Denne position svarer til det første lcp-indeks. Læg mærke til at lcp(XSA[i], XSA[j]) svarertil strengen, der faes ved at sammensætte alle kanterne ned til intervallet [i..j] i lcp-interval-træet. Det vil sige, at hvis vi allerede har navigeret ned til [i..j] i lcp-interval-træet, kender vilcp(XSA[i], XSA[j]) pa forhand.

Hvis vi allerede har et lcp-indeks ii for forældre-intervallet, og ønsker at finde det næstelcp-indeks, men dette ikke er muligt ved brug af CHILD-tabellen, sa kan det næste lcp-indeks findes ved hjælp af en binær søgning. Den binære søgning finder det næste lcp-indeksi intervallet ]ii, j] pa samme made som beskrevet ovenfor. I dette tilfælde kan vi udnytte,at |lcp(XSA[i], XSA[j])| = LCP [ii]. Hvis de binære-søgninger ikke giver noget resultat, er detfordi der ikke findes flere lcp-indekser for intervallet.

Hvis man blot ønsker at finde et børne-interval, der starter med et givet præfiks, kan detgøres direkte med en binær søgning over suffikserne i forældre-intervallet.

Eksempel. I dette eksempel vil jeg vise, hvordan man kan finde det første lcp-indeks for lcp-intervallet 1-[0..3] over strengen “mississippi”, uden at bruge CHILD-tabellen. Derefter viljeg vise, hvordan man kan finde resten af lcp-indekserne for intervallet. Nar vi har alle lcp-indekserne, ved vi fra lemma 3.3.1, hvordan vi kan findes alle intervallets børne-intervaller.

Brug eventuelt figur 5.1 til at fa et overblik over de værdier jeg bruger i eksemplet.Først findes α:

α = X[SA[0] + |lcp(XSA[0], XSA[3])|] = X[7 + |lcp(ippi$, i$)|] = X[7 + 1] = p

Derefter søger vi binært efter præfikserne, hvor δ ∈ {s, $}:

X[SA[i]..SA[i] + |lcp(XSA[i], XSA[j])| − 1]δ =X[SA[0]..SA[0] + |lcp(XSA[0], XSA[3])| − 1]δ =X[7..7 + |lcp(ippi$, i$)| − 1]δ =X[7..7 + 1− 1]δ =X[7..7]δ = iδ

Det giver præfikserne “is” og “i$”. Den binære søgning efter præfikset “is” giver positionen1. Det vil sige at i1 = 1.

For at finde det næste lcp-indeks efter i1, skal vi først finde α:

α = X[SA[i1] + LCP [i1]] = X[SA[1] + LCP [1]] = X[4 + 1] = s

Page 45: Analyse og implementering af suffikstræer baseret p˚a ... · Kapitel 1 Introduktion Suffikstræet1 er en interessant datastruktur, der har en lang række anvendelsesmuligheder inden

5. Optimeringer 42

Derefter søger vi binært efter præfikset δ ∈ {$}:

X[SA[i1]..SA[i1] + LCP [i1]− 1]δ =X[SA[1]..SA[1] + LCP [1]− 1]δ =X[4..4 + 1− 1]δ =X[4..4]$

Det svarer til præfikset “i$”. Hvis vi søger efter “i$” i intervallet ]i1, j], far vi positionen3, som er det sidste lcp-indeks for intervallet [i..j]. Altsa kan alle lcp-indekserne for et givetlcp-interval findes pa denne made.

For at mindske sandsynligheden for at tilga en indgang i CHILD-tabellen hvis værdi erstørre end 255, oprettes tabellen BCKd for en given parameter d. Denne tabel indeholder forhver streng ω:

ω ∈ αd, hvor α ∈ Σ ∪ {$}

af længde d, det mindste indeks i sa ω er et præfiks af XSA[i]. Pa denne made er det muligtat søge d tegn ned gennem lcp-interval-træet uden brug af CHILD-tabellen. Jo længere vinavigerer ned gennem lcp-interval-træet, jo mindre bliver de lcp-intervaller vi møder. Og daværdierne i CHILD afhænger relativt af størrelsen af intervallerne, betyder det, at værdiernei CHILD vil blive mindre, jo længere ned i træet vi kommer.

For at kunne sla op i BCKd med en streng af længde d, bruges funktionen ϕ til at givestrengen en unik heltalskode. En vigtig egenskab ved ϕ er, at hvis t ligger lige efter s iden leksikografiske orden, sa gælder der at ϕ(s) = ϕ(t) + 1. Det betyder, at man kan findeindkodningen af den næste streng i den leksikografiske orden ud fra den forrige.

Funktionen ϕ er defineret pa følgende made:

Definition 5.2.1. Lad w = c0c1...cd−1 være en streng over alfabetet Σ hvor ci er positioneni alfabetet for det pagældende tegn. Sa er funktionen ϕ defineret pa følgende made:

ϕ(c0c1...cd−1) = c0|Σ|d−1 + c1|Σ|d−2 + ... + cd−1|Σ|0

BCKd kan konstrueres i O(n) tid ved et lineært gennemløb af LCP -tabellen pa følgendemade. Hver gang LCP [i] < d ∧ d < |XSA[i]| er opfyldt, gemmes BCKd[ϕ(X[SA[i]..SA[i] +d])] = i. Hvis LCP [i] < d ∧ d ≥ |XSA[i]| gemmes BCKd[ϕ(XSA[i]$d−|XSA[i]|)] = i. Somudgangspunkt skal alle værdierne i BCKd være sat til 0. Man kan se, at tabellen kan kon-strueres i lineær tid ud fra følgende ræsonnement. Det tager O(n) tid at løbe alle indgangenei LCP igennem og afgøre hvilke indekser der skal gemmes. Derudover tager det O(d) tid laveen indkodning af en streng af længde d. Dette skal højst gøres d gange, dvs. O(d2). Det giveros tidskompleksiteten O(n + d2).

Parameteren d vælges sadan at BCKd aldrig fylder mere end n bytes. Der er (|Σ| + 1)d

forskellige strenge af længden d med tegn fra mængden Σ ∪ {$}. Da hver af indgangene iBCKd er et indeks til SA, fylder disse hver 4 bytes. Det vil sige, at vi kan beregne d ud frastørrelsen af alfabetet og n:

4 · (|Σ|+ 1)d = n ⇒log2(4 · (|Σ|+ 1)d) = log2(n) ⇒

log2(4) + log2((|Σ|+ 1)d) = log2(n) ⇒2 + d · log2(|Σ|+ 1) = log2(n) ⇒

d =log2(n)− 2

log2(|Σ|+ 1)

Page 46: Analyse og implementering af suffikstræer baseret p˚a ... · Kapitel 1 Introduktion Suffikstræet1 er en interessant datastruktur, der har en lang række anvendelsesmuligheder inden

5. Optimeringer 43

i ϕ BCK[ϕ] LCP [i] SA[i] XSA[i]

0 ϕ(aa) = 0 BCK[ϕ(aa)] 0 3 aabaabbbabaa$1 3 6 aabbbabaa$2 2 13 aa$3 ϕ(ab) = 1 BCK[ϕ(ab)] 1 4 abaabbbabaa$4 4 11 abaa$5 2 0 abbaabaabbbabaa$6 3 7 abbbabaa$7 ϕ(a$) = 2 BCK[ϕ(a$)] 1 14 a$8 ϕ(ba) = 3 BCK[ϕ(ba)] 0 2 baabaabbbabaa$9 4 5 baabbbabaa$

10 3 12 baa$11 2 10 babaa$12 ϕ(bb) = 4 BCK[ϕ(bb)] 1 1 bbaabaabbbabaa$13 3 9 bbabaa$14 2 8 bbbabaa$15 ϕ($$) = 8 BCK[ϕ($$)] 0 15 $

Figur 5.2: Denne figur viser bucket-tabellen BCK og suffikserne for strengen “abbaabaabbba-baa”. Kolonnen BCK[ϕ] viser hvordan værdierne i BCK fordeler sig i forhold til suffikserneXSA[i]. Jeg har ikke medtaget de indgang i BCK der er lig med 0. I kolonnen XSA[i] harjeg markeret præfikset af længde d med skiftevis rød og blat, svarende til intervallerne BCKindeholder start positionerne for. Jeg har valgt d til 2, hvilket betyder at BCK bliver lidtstørre end n bytes, for at gøre eksemplet lidt mere interessant.

Da d skal være et heltal, runder vi ned til nærmest heltal. Dette valg af d betyder, atkonstruktionen af BCKd tager O(n + d2) = O(n) tid da d2 < n.

Eksempel. I dette eksempel vil jeg vise, hvordan man kan bruge BCKd tabellen til at findedet lcp-interval, der svarer til, at man søger d tegn ned gennem lcp-interval-træet fra roden.Dette lcp-interval kan sa bruges som udgangspunkt for videre søgning ned gennem lcp-interval-træet. Jeg vil ogsa kigge pa situationen hvor der søges efter en streng, som er kortere end d.I dette tilfælde vil jeg vise, hvordan man finder det tilsvarende singleton-interval.

Pa figur 5.2 ses bucket-tabellen BCK og suffikserne for strengen “abbaabaabbbabaa”.Kolonnen BCK[ϕ] viser, hvordan værdierne i BCK fordeler sig i forhold til suffikserneXSA[i]. I kolonnen XSA[i] har jeg markeret præfikset af længde d med skiftevis rød og blat,svarende til intervallerne BCK indeholder start positionerne for. F.eks. kan det ses at lcp-intervallet med præfiks “ab” starter pa position 8, altsa er værdien af BCK[ϕ(ba)] = BCK[2] =8. Jeg har valgt d til 2, hvilket betyder at BCK bliver lidt større end n bytes, for at gøre ek-semplet lidt mere interessant.

Hvis man ønsker at finde lcp-intervallet svarende til en søgning i lcp-interval-træet efterstrengen “abb”, kan det gøres pa følgende made. Først finder vi start-positionen for lcp-intervallet, der har præfikset “ab”, ved brug af BCK-tabellen. Slut-positionen for dette in-terval kan findes ved at finde start-positionen for lcp-intervallet med præfiks “a$”. Det giveros intervallet:

[BCK[ϕ(ab)]]..BCK[ϕ(ab) + 1]− 1] =[BCK[1]]..BCK[2]− 1]=[3..7− 1]=[3..6]

Nu ved vi, at alle suffikser i intervallet [3..6] har præfikset “ab”. For at finde positionen afstrengen “abb” i lcp-interval-træet skal vi bare søge efter “b”, ved brug af CHILD-tabellen,

Page 47: Analyse og implementering af suffikstræer baseret p˚a ... · Kapitel 1 Introduktion Suffikstræet1 er en interessant datastruktur, der har en lang række anvendelsesmuligheder inden

5. Optimeringer 44

fra intervallet [3..6].I nogle tilfælde er det ikke muligt at finde start-positionen for det næste interval ved at

sla op pa det næste indeks i BCK. I et sadant tilfælde vil følgende være sandt BCK[ϕ(ω)] >BCK[ϕ(ω) + 1]. I denne situation skal vi fortsætte med at gennemløbe BCK, indtil vi finderen indgang, hvis værdi er større end start-positionen for det forrige interval. Dette svarer tilat vi leder efter indekset k i BCK:

k = min{ϕ(ω) + i |BCK[ϕ(ω)] < BCK[ϕ(ω) + i], i ∈ [ϕ(ω) + 1, d[}

I tilfælde af at strengen ω ikke findes i træet. Vil følgende være opfyldt BCK[ϕ(ω[0..d])] =0. Derved er det let at finde ud, om en streng ikke findes i træet.

Hvis man ønsker at finde en streng p i lcp-interval-træet, der er kortere d, kan man igenbruge tabellen BCK. Der er i dette tilfælde, to muligheder for hvordan p kan se ud. Entenender strengen p med sentinel-tegnet $, eller ogsa gør den ikke.

Hvis p ikke ender pa $ vil resultatet af søgningen være et lcp-interval. Da p er mindre endd, kan vi ikke bruge BCKd direkte. Lad a være det mindste tegn i alfabetet og v = pad−|p| ogw = p$d−|p|. Sa kan intervallet [l..r] findes pa følgende made:

l ={

0 hvis ∀i ∈ [ϕ(v), ϕ(w)] : 0 = BCK[i]min{BCK[i] | 0 < BCK[i], i ∈ [ϕ(v), ϕ(w)]} ellers.

r ={

0 hvis ∀i ∈ [ϕ(w) + 1, (|Σ + 1|)d[: 0 = BCK[i]min{BCK[i]− 1 | 0 < BCK[i], i ∈ [ϕ(w) + 1, (|Σ + 1|)d[} ellers.

Hvis vi f.eks. ønsker at finde intervallet for strengen “b”, gøres det som følger.

l ={

0 hvis ∀i ∈ [ϕ(ba), ϕ(b$)] : 0 = BCK[i]min{BCK[i] | 0 < BCK[i], i ∈ [ϕ(ba), ϕ(b$)]} ellers.

= min{BCK[i] | 0 < BCK[i], i ∈ [3, 5]}= BCK[3] = 8

r ={

0 hvis ∀i ∈ [ϕ(w) + 1, (|Σ + 1|)d[: 0 = BCK[i]min{BCK[i]− 1 | 0 < BCK[i], i ∈ [ϕ(w) + 1, (|Σ + 1|)d[} ellers.

= min{BCK[i]− 1 | 0 < BCK[i], i ∈ [6, (2 + 1)2[}= min{BCK[i]− 1 | 0 < BCK[i], i ∈ [6, 9[}= BCK[8]− 1 = 15− 1 = 14

Det giver os intervallet [8..14], hvis præfiks ganske rigtigt er “b”.Hvis p derimod ender med tegnet $, vil resultatet af søgningen være et singleton-interval,

da p er et af suffikserne i lcp-interval-træet. Strengen p kunne eksempelvis være “a$”, i safald ville singleton-intervallet kunne findes som:

[l..r] = [BCK[ϕ(a$)]..BCK[ϕ(a$)]] = [7..7]

Teorien i denne sektion er baseret pa [AKO04, s. 77-78], men da dette afsnit er meget lidtdetaljeret, har jeg suppleret med information fra artiklen [AKO01, s. 15]. Der var dog stadigen del ting der var uklare, sa jeg har selv udfyldt resten af hullerne i teorien.

Bemærk at BCK-tabellen indeholder en række indgange der altid vil være 0. Det drejersig om indgangene hvor, $ optræder i strengen pa andre pladser end de sidste. F.eks. vilstrengen “$a” aldrig optræde i lcp-interval-træet. Dette kan selvfølgelig optimeres væk, menda det ville gøre teorien unødvendigt kludret, har jeg undladt det.

Page 48: Analyse og implementering af suffikstræer baseret p˚a ... · Kapitel 1 Introduktion Suffikstræet1 er en interessant datastruktur, der har en lang række anvendelsesmuligheder inden

5. Optimeringer 45

SLINK[i]i BCK[ϕ] l r SA[i] LCP [i] XSA[i]

0 BCK[ϕ(ip)] 7 0 ippi$1 BCK[ϕ(is)] 4 1 issippi$2 0 1 1 4 ississippi$3 BCK[ϕ(i$)] 10 1 i$4 BCK[ϕ(mi)] 0 0 mississippi$5 BCK[ϕ(pi)] 9 0 pi$6 BCK[ϕ(pp)] 8 1 ppi$7 BCK[ϕ(si)] 6 0 sippi$8 3 2 sissippi$9 BCK[ϕ(ss)] 5 1 ssippi$

10 0 1 2 3 ssissippi$11 BCK[ϕ($$)] 11 0 $

Figur 5.3: Figuren viser den pladsoptimeret SLINK-tabel for stregen “mississippi”. Derer tilføjet en række andre tabeller for at illustere sammenhængen mellem SLINK og dissetabeller.

5.3 Pladsoptimering af suffikslinks-tabellen

I følge lemma 3.5.1 har et hvilket som helst lcp-interval [i..j] et tilsvarende suffikslink-interval[l..r]. For at kunne finde suffikslink-intervallet, gemmes l og r i SLINK-tabellen pa det førstlcp-indeks plads for intervallet [i..j]. Det resulterer i at SLINK-tabellen fylder 8n bytes. Udfra samme teknik som for de to tidligere pladsoptimeringer ønsker vi at begrænse antallet afværdier over 255, sa hvert felt i SLINK kan gemmes pa 1 byte. Det gøres ved at gemme denvenstre grænse for suffikslink-intervallet relativt til den største værdi i BCKd, der stadig ermindre end værdien vi vil gemme. Det svarer til:

SLINK[i1].l = l −BCKd[ϕ(X[SA[l]..SA[l] + d− 1])]= l −BCKd[ϕ(X[SA[i1] + 1..SA[i1] + 1 + d− 1])]

hvor i1 er det første lcp-indeks for [i..j]. Ligeledes gemmes suffikslink-intervallets højre grænserelativt til den venstre grænse. Dette skulle gerne resultere i at felterne l og r i SLINK hverkan gemmes pa en byte. For at man stadig kan fa fat i de værdier der er større end 255, brugessamme teknik som for LCP -tabellen, hvor en ekstra sorteret liste indeholder par af indekserog deres værdier.

For et interval `-[i..j] hvor ` ≥ d+1 kan vi finde suffikslink-intervallets grænser ved følgendeberegning:

l = SLINK[i1].l + BCKd[ϕ(X[SA[i1] + 1..SA[i1] + 1 + d− 1])]r = SLINK[i1].r + l (5.2)

Hvis ` < d+1 er værdierne i SLINK-tabellen ikke sat, det skyldes at BCKd[ϕ(X[SA[i1]+1..SA[i1] + 1 + `− 1])] ikke er defineret. I dette tilfælde kan vi finde suffikslink-intervallet vedat søge efter stregen X[SA[i] + 1..SA[i] + 1 + `− 1] i lcp-interval-træet. Dette tager O(`) tid.

Eksempel. Jeg vil i dette eksempel vise, hvordan man kan finde suffikslink-intervallet til etgivet lcp-interval ud fra den optimerede SLINK-tabel. Ligesom det i sidste eksempel har jegigen valgt d for BCK til 2, da d i virkeligheden ville være 0 for strengen “mississippi”. Figur5.3 viser den optimerede SLINK-tabel, og andre tabeller der kan være nyttige i forhold tileksemplet. Som man kan se, indeholder SLINK kun to intervaller. Det skyldes, at suffikslink-intervallerne hvor ` < d er blevet fjernet i henhold til ovenstaende teori. Jeg vil bade vise,hvordan man finder et suffikslink-interval hvor ` < d, og et hvor d ≤ `.

Page 49: Analyse og implementering af suffikstræer baseret p˚a ... · Kapitel 1 Introduktion Suffikstræet1 er en interessant datastruktur, der har en lang række anvendelsesmuligheder inden

5. Optimeringer 46

Intervallet 3-[9..10] har et tilhørende suffikslink-interval med lcp-værdi 2, dette intervalfindes pa følgende made. Først skal vi finde det første lcp-indeks ved at bruge CHILD-tabellen.Det første lcp-indeks for 3-[9..10] er i1 = 10. Ved at bruge formel 5.2 kan vi finde suffikslink-intervallet til:

l = SLINK[i1].l + BCKd[ϕ(X[SA[i1] + 1..SA[i1] + 1 + d− 1])]= SLINK[10].l + BCKd[ϕ(X[SA[10] + 1..SA[10] + 1 + 1])]= 0 + BCKd[ϕ(si)] = 7

r = SLINK[i1].r + l = SLINK[10].r + l = 1 + l = 8

Suffikslink-intervallet for 3-[9..10] er altsa 2-[7..8].Hvis man ønsker at finde suffikslink-intervallet for 2-[7..8], vil man opdage, at SLINK

ikke er defineret for intervallet 2-[7..8]. I dette tilfælde skal vi søge efter strengen:

X[SA[i] + 1..SA[i] + 1 + `− 1] = X[SA[7] + 1..SA[7] + 1 + 2− 1] = X[7..8] = i

fra roden i lcp-interval-træet. Resultatet af denne søgning er intervallet 1-[0..3].

Page 50: Analyse og implementering af suffikstræer baseret p˚a ... · Kapitel 1 Introduktion Suffikstræet1 er en interessant datastruktur, der har en lang række anvendelsesmuligheder inden

Kapitel 6

Implementering

Dette kapitel har til formal at give læseren et overblik over den implementerede software.Implementeringen bestar af fire biblioteker, to der henholdsvis implementerer et suffikstræ,baseret pa et udvidet suffiksarray, og et pladoptimeret suffikstræ. Og to biblioteker der im-plementerer en række fælles datastrukturer og funktioner. API specifikationen for grænsefla-derne til de fire biblioteker kan findes pa websiden [Simd] under API specifikation. Softwarener implementeret i sproget C og benytter GNU autotools, og er testet under Linux, OS Xog Microsoft Windows. Udover de nævnte biblioteker benyttes to eksterne biblioteker [Mal,libcheck] og [Tro, libpopt] til henholdvis unit-test og parsing af kommandolinje parametre.

6.1 Modul: libcommon

Biblioteket libcommon stiller basis funktionalitet og typer til radighed, som de fleste program-mer har brug for. Figur 6.1 giver et overblik over hvilke moduler libcommon indeholder. Jegvil herunder overordnet gennemga funktionaliteten de forskellige moduler stiller til radighed.For at benytte et af disse moduler skal man bare inkludere headeren for modulet og linke ensprogram med biblioteket.

Mange af disse moduler indeholder kun den funktionalitet jeg har haft brug for til atudvikle de to implementeringer af suffikstræer. Men det er ideen at modulerne bliver udvidetmed mere funktionalitet i fremtidige projekter.

array Modulet array indeholder funktionalitet til at arbejde pa standart C arrays. F.eks. erdet muligt at finde længden af et statisk allokeret array. Eller lade to elementer i arrayetbytte plads.

check Modulet indeholder funktioner til udskrive fejlbeskeder i sammenhæng med unit-testbiblioteket libcheck. Det kan f.eks. være hvis to heltal skal tjekkes for lighed.

compare Dette modul indeholder en række sammenligningsfunktioner. Disse funktioner bru-ges f.eks. til at sortere elementerne i et array. Samligningsfunktionen gives som pa-

libcommon

types intervals error tuples check math array

Figur 6.1: Figuren viser de forskellige moduler i biblioteket libcommon.

47

Page 51: Analyse og implementering af suffikstræer baseret p˚a ... · Kapitel 1 Introduktion Suffikstræet1 er en interessant datastruktur, der har en lang række anvendelsesmuligheder inden

6. Implementering 48

rameter til sorteringfunktionen, der sa sorterer elementerne pa basis af resultatet frasammenligningsfunktionen.

error Indeholder funktioner til at udskrive fejlbeskeder til kommandolinjen.

intervals Definerer en række interval typer, og funktioner der arbejder pa disse intervaller.F.eks. er det muligt at definere et heltals interval og gennemløbe det.

math Modulet math eksisterer primært fordi, C ’s matematik bibliotek ikke implementerergeneriske min og max funktioner og logaritme-funktionen log2.

tupels Modulet gør det muligt at definere tupler og tripler over forskellige typer. Disse kanf.eks. bruges, hvis en funktion skal returnere flere værdier, eller hvis man ønsker atgemme par af værdier i en liste.

types Modulet stiller en række basistyper til radighed. F.eks. er bool og NULL defineret idette modul.

6.2 Modul: libcollection

Dette modul implementerer forskellige datastrukturer, og en lang række funktioner der arbej-der pa dem. De fleste af datastrukturerne er generiske, det vil sige, at de kan defineres til atindeholde en vilkarlig type. Biblioteket gør brug af funktionaliteten i libcommon, det er derfornødvendigt at have libcommon installeret for at kunne bruge libcollection.

Der findes en allerede mange biblioteker der implementerer de datastrukturer som libcol-lection stiller til radighed. Problemet med disse biblioteker er, at de ikke er generiske. De flesteaf disse biblioteker implementerer datastrukturer, der arbejder pa void pointere. Sa hvis manf.eks. ønsker at lave en liste af char ’s, vil hver char blive gemt som en void pointer. Det vilbetyde, at der bruges 4 bytes pr. tegn i stedet for 1 byte. Dette er ikke acceptabelt nar derfokuseres pa pladsforbrug, og det er den primære grund til, at jeg har valgt at implementeremit eget datastruktur-bibliotek.

Forskellen fra min implementering af datastrukturene til en void pointer baseret implemen-tering er, at jeg bruger makroer til at definere datastrukturene. Makroen tager elementtypensom paramenter, og definerer ud fra denne type, funktionerne der arbejder pa datastrukturen.

Hvis man f.eks. vil lave en array-liste, der arbejder pa typen char, opretter man en he-ader char_array_list.h der kalder makroen DEFINE_ARRAY_LIST_PROTOTYPES(char),og en C -fil hvor makroen DEFINE_ARRAY_LIST(char) kaldes. De to skridt er alt hvadder skal til for at oprette en char_array_liste. For at gøre det endnu letter for brugerenhar jeg oprettet en lang række specialiserede datastrukturer pa forhand. Hvis man f.eks. harbrug for en array-stak indeholdende positive heltal, skal man bare inkludere headeren libcol-lection/specialized_collections/uint_array_stack.h.

Pa figur 6.2 ses sammenhængen mellem de forskellige datastrukturer i biblioteket libcol-lection. F.eks. kan det ses, at string er en generalisering af typen char_array, som igen eren specialisering af datastrukturen array. Det kan ogsa ses, at datastrukturen array_stackindeholder en array_list som underliggende datastruktur.

array Modulet array implementerer et generisk array af en givet størrelse. Størrelsen afarrayet er gemt sammen med arrayet i modsætning til et standart C -array. For at detstadig skulle være muligt at bruge subscript-operatorerne, er array ikke implementeretsom en struct. Derimod er arrayet implementeret som et C -array, hvor de første 4 bytesangiver størrelse af arrayet og resten af array indeholder elementerne. En pointer tilarrayet vil altsa pege pa det første element, i stedet for positionen hvor størrelsen afarrayet er gemt.

Page 52: Analyse og implementering af suffikstræer baseret p˚a ... · Kapitel 1 Introduktion Suffikstræet1 er en interessant datastruktur, der har en lang række anvendelsesmuligheder inden

6. Implementering 49

libcollection

arraytype:

array_listtype:

array_stacktype:

bit_array

char_array_list

string

char_array

string_buffer

extended_byte_array

byte_array

<<bind>>

byte

uint_tuple_array_list

<<bind>>

uint_typle

linked_listtype:

<<bind>>

char

<<bind>>

char

Figur 6.2: Figuren viser sammenhængen mellem de forskellige datastrukturer i biblioteketlibcollection.

Page 53: Analyse og implementering af suffikstræer baseret p˚a ... · Kapitel 1 Introduktion Suffikstræet1 er en interessant datastruktur, der har en lang række anvendelsesmuligheder inden

6. Implementering 50

Nar man bruger makroerne til at definere et array af en givet type, oprettes en rækkefunktioner der er præfikset med typens navn. Det er dermed muligt bruge arrays afforskellige typer pa samme tid.

Eksempel. Følgende kode opretter et int-array over tallene 1, ..., 10, og ganger hverttal med sig selv.

int numbers[] = {1,2,3,4,5,6,7,8,9,10};int_array* arr = int_array_new_from_array(10,numbers);ARRAY_MAP(int,arr,i,e,{e=e*e;

})

Som man kan se, har jeg defineret makroer til at gennemløbe elementerne i et array. Paden made undgar man at lave fejl i grænserne for en løkken, samtidig med at udtrykketkan skrives mere eksplicit.

array_list En array-liste er en liste baseret pa et array, hvor det er muligt at tilga detselementer i konstant tid. Array-listen bygger oven pa modulet array, men til forskelfra arrayet kan en array-liste vokse dynamisk. Hvis man indsætter elementer indtil detunderliggende array er fyldt, allokeres et dobbelt sa stort array. Det betyder, at nyeelementer kan tilføjes til enden af listen i amortiseret konstant tid. Ligeledes skrumperdet underliggende array ogsa hvis elementerne fjernes.

array_stack Modulet er en stak baseret pa modulet array_list.

bit_array Dette modul implementerer et array, der kan indeholde positive heltal med engivet øvre grænse. Hvis man f.eks. ønsker et array med 10 elementer, der hver ligger iintervallet [0, 7], kan det gøres ved at kalde funktionen bit_array_new(10,8). Det smarteved bit-arrayet er, at det i mange tilfælde fylder væsentligt mindre end et normalt array.Det skyldes, at hver værdi kun gemmes pa det nødvendige antal bit. F.eks. vil vi kunnegemme værdier i intervallet [0, 7] pa tre bit. Hvor et normalt array vil bruge otte bit.

extended_byte_array Denne datastruktur implementerer princippet nævnt i 5.1. Det er endatastruktur, der kan indeholde positive heltal, der fylder 4 bytes hver. For de elementerder er mindre end 255, gemmes værdien i et array pa n bytes. For de resterende elementergemmes 255 i arrayet. Yderligere gemmes for disse elementer deres indeks og værdi somet par i en sorteret liste. Dette betyder en betydelig pladsbesparelse hvis der ikke ersærligt mange elementer der er større end 255. For at være mere eksakt skal mere end62.5% af elementerne være mindre end 255 for at opna en pladsbesparelse.

linked_list Dette modul implementerer en generisk lænket liste. Listen har en pointer til denførste og den sidste knude i listen. Og hver knude har en pointer til den næste knude.

string Da en almindeligt streng i C ikke kender sin længde, har jeg implementeret moduletstring, der definerer strenge hvor længden er gemt sammen med strengen. Faktisk er enstreng bare et char -array baseret pa modulet array. Udover funktionerne for char -arrayhar jeg tilføjet gense streng funktion.

string_buffer Modulet string_buffer definerer en streng-buffer baseret pa en char arrayliste. En streng-buffer er i modsætning til en normal streng mutabel. Det betyder, atdet er muligt ændre strengen. F.eks. kan man tilføje nye tegn til stregen, eller ændreeksisterende tegn.

Page 54: Analyse og implementering af suffikstræer baseret p˚a ... · Kapitel 1 Introduktion Suffikstræet1 er en interessant datastruktur, der har en lang række anvendelsesmuligheder inden

6. Implementering 51

libsabst

LCP

CHILD

SA

libcollection

tstring extended_byte_array uint_array

suffixtree

X

SA_construction

LCP_construction

CHILD_construction

produce

produce

produce

BCK_constructionBCKproduce

byte_array

Figur 6.3: Figuren viser suffikstræ-datastrukturen i libsabst. Dette suffikstræ bestar af stren-gen X og fire tabeller SA, LCP , BCK og CHILD. Derudover vises de fire moduler, derkonstruerer tabellerne i lineær tid. Til sidste kan det ses hvilke datastrukturer i libcollectiontabellerne baseres pa.

6.3 Modul: libsabst

Biblioteket libsabst1 bestar af en implementering af det suffiksarray baserede suffikstræ, samtfunktioner til lineær konstruktion af de tabeller der udgør det udvidede suffiksarray. Derudoverindeholder biblioteket en række suffikstræ-algoritmer, der arbejder pa suffikstræet.

Jeg vil herunder beskrive modulerne i biblioteket, disse moduler implementerer direkteteorien præsenteret i specialet. Jeg har dog undladt at implementere SLINK-tabel, og dealgoritmer der arbejder pa den. Resultatet er, at det ikke er muligt at bruge suffikslinks itræet. Jeg mener dog, at det er et fornuftigt sted at skære, da suffikslinks i mange tilfældekan undværes.

Figur 6.3 viser suffikstræ-datastrukturen i libsabst. Dette suffikstræ bestar af strengen Xog fire tabeller SA, LCP , BCK og CHILD. Jeg har undladt at medtage tabellen SLINK,da denne tabel ikke er en del af min implementering. Derudover vises de fire moduler, der kon-struerer tabellerne i lineær tid. Til sidst er det muligt at se sammenhængen mellem tabellerne ilibsabst og datastrukturer i libcollection. F.eks. kan det ses, at tabellen LCP bliver konstrueretaf LCP_construction og er baseret pa datastrukturen extended_byte_array i libcollection.

BCK_construction Modulet indeholder algoritmen til at konstruere tabellen BCK liniærtud fra inputstrengen X, suffiksarrayet SA og tabellen for længste fælles præfiks LCP .En nærmere beskrivelse af denne konstruktion kan findes i sektion 5.2.

CHILD_construction Dette modul implementerer algoritmen beskrevet i 4.3 til lineær kon-struktion af CHILD-tabellen. Pa figur 6.4 ses et eksempel pa brugen af modulet.

1Suffix array based Suffix Tree Library

Page 55: Analyse og implementering af suffikstræer baseret p˚a ... · Kapitel 1 Introduktion Suffikstræet1 er en interessant datastruktur, der har en lang række anvendelsesmuligheder inden

6. Implementering 52

string X = string_new("mississippi");uint_array* SA = SA_construction(X);extended_byte_array* LCP = LCP_construction(X, SA);uint_array* CHILD = CHILD_construction(LCP);bck_table* BCK = BCK_construction(s, SA, LCP);sabst_tree* tree = sabst_new(X, SA, LCP, BCK, CHILD);

Figur 6.4: Denne figur viser hvordan man opretter et suffikstræ baseret pa et udvidet suffiks-array. Først konstrueres det udvidede suffiksarrays tabeller, disse tabeller gives derefter sominput til konstruktionen af suffikstræet.

LCP_construction Modulet implementerer lineær konstruktion af LCP -tabellen ud fra enstreng og det tilhørende suffiksarray. Koden til dette modul er baseret pa funktionen_lcp_vmjk_9125n. Denne funktion findes i filen lcp_aux.c i arkivet ds.tgz pa siden [MF].For en nærmere beskrivelse af teorien bag denne funktion henviser jeg til sektion 4.2.

SA_construction Modulet implementerer lineær konstruktion af suffiksarrayet for en givenstreng. Denne konstruktion er nærmere beskrevet i sektion 4.1. Koden til dette moduler baseret pa koden i bagerst i artiklen [KS03]. Dog skulle koden ændres betragteligtfor at være brugbar sammen med resten af implementeringen. For det første er kodenskrevet i C++, det var dog kun sma ændringer der skulle til for at fa koden ændret tilC. Det største problem var, at sentinel tegnet $ var defineret til at være mindre endalle tegn i alfabetet. Hvorimod resten af min implementering er afhængig af, at dettetegn er større end alle tegn i alfabetet. Den eksisterende kode skulle ikke ændres særligtmeget for at løse dette problem, men det krævede en fuldstændig forstaelse af teorien iartiklen [KS03].

bottom_up Dette modul implementerer bottom-up-gennemløbet af lcp-interval-træet be-skrevet i sektionen 3.7.

exact_string_match Dette modul indeholder to suffikstræ-algoritmer. Den første algoritmeafgør om en givet streng eksisterer i lcp-interval-træet. Den anden finder alle startposi-tioner af en given streng i lcp-interval-træet. Disse algoritmer bruges til at benchmarkelibsabst, og er beskrevet i kapitel 7.

longest_repeated_substring Dette modul implementerer suffikstræ-algoritmen, der finderden længste delstreng, der optræder flere steder i lcp-interval-træet. Denne algoritmebruges ogsa til at benchmarke libsabst, og er beskrevet i sektionen 7

suffixtree Modulet implementerer det suffiksarray baserede suffikstræ, og al funktionalitettil at navigere i det. Grænsefladen til suffikstræet opfylder kravene for grænsefladenbeskrevet i sektion 1.2. Dog med undtagelse af at det ikke er muligt at fa fat i suffikslinketfor en knude. Yderligere bygges de underliggende tabeller, suffikstræet er baseret pa, iet separat modul. Jeg synes dette var en smartere løsning, da det giver mulighed for atudskifte algoritmerne, der bygger de forskellige tabeller.

For at grænsefladen til det suffiksarray baserede suffikstræ skulle ligne grænseflade for etnormalt suffikstræ mest muligt, har jeg har lavet et identisk interface til suffikstræerne ilibsabst og libsuffixtree. Den eneste forskel pa de to grænseflader er præfikset af navnene.For at opna dette har jeg designet grænseflade til et suffikstræ, som jeg viste var mulig atrealisere med et normal pointer-baseret suffikstræ. Ud fra denne grænseflade designedejeg suffikstræerne i libsabst og libsuffixtree. En knude i de to suffikstræer er selvfølgeligimplementeret forskelligt. For suffiksarrayet er en knude et lcp-interval svarende til en

Page 56: Analyse og implementering af suffikstræer baseret p˚a ... · Kapitel 1 Introduktion Suffikstræet1 er en interessant datastruktur, der har en lang række anvendelsesmuligheder inden

6. Implementering 53

knude i lcp-interval-træet. Hvorimod en knude i libsuffixtree er et indeks til et array derindeholder informationen om knuden. For at gemme strukturen af de forskellige typerder bliver brugt af suffikstræet, handteres disse typer kun via funktioner i suffikstræet.Dette har ogsa den fordel, at det er muligt at ændre strukturen af disse typer, uden atskulle ændre alle algoritmerne der arbejder pa datastrukturen. Resultatet er, at det ermuligt udelukkende ved at ændre type- og funktionsnavnene, at flytte en algoritme fraden ene datastruktur til den anden.

Grænsefladen til suffikstræet i libsabst er dokumenteret pa følgende webside [Simb]. Dukan eventuelt sammenligne med grænsefladen for suffikstræet i libsuffixtree, pa siden[Simc].

tandem_repeats Dette modul implementerer den sidste, og største, benchmark-algoritme.Algoritmen finder alle branching og non-branching tandem repeats i en streng. Dennealgoritme er, ligesom de tre andre benchmark algoritmer, beskrevet i sektionen 7.

6.4 Modul: libsuffixtree

Biblioteket libsuffixtree bestar af en implementering af et pladsoptimeret suffikstræ base-ret pa teorien i artiklen [Kur99], en algoritme til lineær konstruktion af suffikstræet og firebenchmark-algoritmer. Den lineære konstruktion af suffikstræet bygger pa McCreights algorit-me beskrevet i artiklen [McC76], og de fire benchmark-algoritmer er identiske med benchmark-algoritmerne nævnt i sektion 6.3.

Det er interessant at se pa pladsforbruget for suffikstræet. Suffikstræet bestar af en array-liste, der indeholder informationen om alle de indre knuder i træet, og et array der indeholderden nødvendige information for bladene i suffikstræet. For hver indre knude er følgende infor-mation gemt:

• Indekset pa det første barn. (unsigned int)

• Om det første barn er et blad. (bool)

• Indekset til knudens bror. En knudes bror er knuden placeret til højre for den pagældendeknude, med samme forældre knude. (unsigned int)

• Om knudes bror er et blad. (bool)

• Startpositionen i strengen for et af suffikserne i undertræet for den pagældende knude.(unsigned int)

• Knudens dybte i suffikstræet. (unsigned int)

• Indekset til den indre knude den pagældende knudes suffikslink peger pa. (unsigned int)

Der bruges altsa 4 ∗ 5 = 20 bytes og 2 bits pr. indre knude. I værste fald kan der være n− 1indre knuder, dette er tilfældet nar suffikstræet er et binært træ. Det giver et pladsforbrugpa ca. 20n bytes for de indre knuder.

Bladede i suffikstræet er placeret sadan, at et blads bror aldrig er en indre knude. Det vilsige, at det kun er nødvendigt at gemme indekset til et blads bror. Dette gøres i et array aflængde n. Altsa skal der bruges 4n bytes til at gemme bladene. I alt giver det et pladsforbrugpa ca. 24n bytes i værste fald for at opretholde suffikstræet i hukommelsen.

Figur 6.5 viser sammenhængen mellem datastrukturene i libsuffixtree, og hvordan dissedatastrukturer afhænger af libcollection. F.eks. kan man se, at et suffikstræ bestar af en strengX, et array leafs og en array-liste branches. Det kan ogsa ses at suffikstræet bliver bygget afmodulet ST_construction.

Page 57: Analyse og implementering af suffikstræer baseret p˚a ... · Kapitel 1 Introduktion Suffikstræet1 er en interessant datastruktur, der har en lang række anvendelsesmuligheder inden

6. Implementering 54

libcollection

libsuffixtree

suffixtree

branchesleafs

branch+first_child: unsigned int

+branch_brother: unsigned int

+depth: unsigned int

+head_position: unsigned int

+suffix_link: unsigned int

branch_array_list

*

<<bind>>

branch

arraytype:

array_listtype:

uint_array<<bind>>

uintstring

X

ST_construction

produce

Figur 6.5: Figuren viser sammenhængen mellem de forskellige datastrukturer i biblioteketlibsuffixtree.

Page 58: Analyse og implementering af suffikstræer baseret p˚a ... · Kapitel 1 Introduktion Suffikstræet1 er en interessant datastruktur, der har en lang række anvendelsesmuligheder inden

6. Implementering 55

exact_string_match Dette modul implementerer de to benchmark-algoritmer nævnt i 6.3.Benchmark-algoritmerne for libsabst og libsuffixtree er identiske, med den undtagelse, atnavnene pa funktioner og typer har forskellige præfiks. Som eksempel pa dette henvisestil følgende to funktioner:

bool ST_match(ST* tree, string P){return !ST_index_is_nil(tree, ST_find(tree, ST_get_root(tree), P));

}

bool sabst_match(sabst_tree* tree, string P){return !sabst_index_is_nil(tree, sabst_find(tree, sabst_get_root(tree), P));

}

Begge funktioner implementerer benchmark-algoritmen, der afgører om en streng op-træder i suffikstræet. Funktionen ST_match er fra libsuffixtree og sabst_match er fralibsabst.

longest_repeated_substring Dette modul implementerer benchmark algoritmen nævnt i6.3.

ST_construction Dette modul implementerer McCreights algoritme til lineær konstruktionaf suffikstræer. Implementeringen af algoritmen benytter kendskab til det pladsoptime-rede suffikstræs struktur, og kan derfor ikke bruges til at opbygge et generelt suffikstræ.Dette kan dog gøres muligt med relativt sma ændringer.

suffixtree Modulet suffixtree implementerer det pladsoptimerede suffikstræ og al funktiona-litet til at navigere rundt i det. Bemærk at suffikslinks er understøttet i denne imple-mentering i modsætning til libsabst.

tandem_repeats Ligesom de andre benchmark-algoritmer stemmer denne algoritme ogsaoverens med tandem-repeat-algoritmen fra 6.3.

6.5 Test

For at øge sandsynligheden for at den implementerede software er korrekt, og for at minimeretidsforbruget pa at finde fejl under udviklingen, har jeg testes softwaren pa forskellige planer.Jeg har primært brugt unit-test biblioteket [Mal, libcheck] til automatisk-test af softwaren.Hver gang jeg har tilføjet ny funktionalitet, har jeg lavet unit-tests, der tester denne funktio-nalitet. Det sikre en stabil udvikling, hvor det er lettere at opdage, hvis den nye funktionalitetmedfører fejl i den eksisterende software.

Ud over unit-test pa funktionsniveau, har jeg ogsa brugt unit-test til black box test afsoftwaren pa integrationsniveau. F.eks. tester jeg de tidligere nævnte benchmark-algoritmerpa realistisk input, hvor jeg kender det korrekte output. Det er dog problematisk at inkludereunit-tests, der tager meget lang tid. Derfor har jeg yderligere testet benchmark algoritmernefra libsabst og libsuffixtree op mod hinanden pa instanser op til 17 megabytes. Da libsabst oglibsuffixtree er meget forskelligt opbyggede, viser disse test, at de underliggende datastrukturerog algoritmer med stor sandsynlighed er korrekte. Det er dog vigtigt at gøre sig klart, at detteikke er ensbetydende med at benchmark-algoritmerne er korrekte.

Page 59: Analyse og implementering af suffikstræer baseret p˚a ... · Kapitel 1 Introduktion Suffikstræet1 er en interessant datastruktur, der har en lang række anvendelsesmuligheder inden

6. Implementering 56

Til hver datastruktur i libcollection findes en tilhørende unit-test fil. Disse filer er navngivetpa følgende made: for modulet array hedder unit-test-filen array_test_cases.c. Hver af dissetest-filer indeholder en test-suite bestaende af adskillelige test-cases. For et bibliotek bliverdisse test-suites bundet sammen i filen unittest.c. Hvis man kompilerer biblioteket med makecheck, oprettes unit-test programmet test. Dette program kører alle test-suites i biblioteket.

For mig handler det om at finde en balance, hvor man ikke bruger alt for meget tid pa atteste, men samtidigt er forholdsvis sikker pa, at den eksisterende software er korrekt underudviklingen. Jeg har forsøgt at opna dette, ved at teste ny funktionalitet indtil jeg er rimeligtoverbevist om at den implementeringen virker som forventet. Nar der alligevel opstar en fejl,tilføjer jeg flere unit-tests, indtil jeg har fundet fejlen. Nar jeg har rettet fejlen, tilføjer jegtests for denne fejl. Ideen er, at tidlige test fanger fejl der er svære at finde senere, men senetest sikre softwaren korrekthed bedre.

Page 60: Analyse og implementering af suffikstræer baseret p˚a ... · Kapitel 1 Introduktion Suffikstræet1 er en interessant datastruktur, der har en lang række anvendelsesmuligheder inden

Kapitel 7

Eksperimentel evaluering

For at finde ud af hvordan det suffiksarray baserede suffikstræ klarer sig i forhold til detpladsoptimerede suffikstræ, har jeg udført en række benchmarks, hvor jeg tester bibliotekternelibsabst og libsuffixtree op mod hinanden.

Jeg har indsamlet følgende data til eksperimenterne:

Det menneskelige X kromosom Det menneskelige X kromosom. Alfabet størrelse 4. Da-tane kan findes pa websiden http://www.gutenberg.org/etext/3523. Jeg har fjernetalt fra filen undtagen selve sekvensen.

Swissprot: Den komplette samling af proteinsekvenser fra Swissprot databasen (release38). Alfabet størrelse 20. ftp://ftp.ebi.ac.uk/pub/databases/swissprot/sw oldreleases/sprot38.tar.gz Jeg har fjernet alt fra filen sprot38.dat undtagen selve se-kvensen.

Random: En tilfældigt genereret sekvens over et alfabet af størrelse 62.

Disse data repræsenterer forskellige typer af strenge med varierende alfabetstørrelse. Forhvert benchmark vil jeg yderligere variere længden af datane ved at tage et præfiks af denpagældende sekvens. Bilag A indeholde yderligere benchmark pa den engelske bibel, hvoralfabetstørrelsen er 92.

For de to biblioteker libsabst og libsuffixtree har jeg udført 6 benchmarks pa alle deoverstaende data. I de følgende sektioner vil jeg beskrive hvert benchmark og de opnaederesultater.

Alle benchmarks er udført pa en computer med Intel Pentium 4 1.7Ghz processor og 896MB ram. Operativsystemet er Ubuntu (brezzy) linux.

7.1 Benchmark: Construction

Dette benchmark viser, hvor lang tid det tager at bygge suffikstræet for de to biblioteker.Pa figur 7.1, 7.2 og 7.3 ses eksperimentet udført pa de forskellige data. Konstruktionen afsuffikstræet forventes at være lineær i antal tegn for begge biblioteker. Dette se ud til atstemme overens med eksperimenterne. Yderligere kan det ses, at det konstante forhold mel-lem udførelsestiderne for de to biblioteker varierer for eksperimenterne. Jeg vil tro, at denvigtigste faktor for denne forskel er alfabetstørrelsen. Libsuffixtree er klart hurtigst pa bench-market for det menneskelige X kromosom hvor alfabetstørrelsen kun er 4. Derimod er libsabstmange gange hurtigere pa benchmarket for tilfældigt generede strege, som har et alfabet medstørrelsen 62. Udførelsestiden for konstruktionen af suffikstræet i libsuffixtree er O(n·|Σ|), altsaer konstruktionen lineært afhængig af alfabetstørrelsen. For libsabst er det mere vanskeligt at

57

Page 61: Analyse og implementering af suffikstræer baseret p˚a ... · Kapitel 1 Introduktion Suffikstræet1 er en interessant datastruktur, der har en lang række anvendelsesmuligheder inden

7. Eksperimentel evaluering 58

0

50

100

150

200

250

252015105

Seku

nder

Milioner tegn

Konstruktion af suffikstræet for det menneskelige X kromosom

libsabstlibsuffixtree

Figur 7.1: Denne figur viser udførelsestiden for at konstruere suffikstræet for det menneskeligeX kromosom. Grafen viser antallet af sekunder det tager for de to biblioteker libsabst oglibsuffixtree at bygge suffikstræet for præfikser af det menneskelige X kromosom af varierendelængde.

0

50

100

150

200

250

300

252015105

Seku

nder

Milioner tegn

Konstruktion af suffikstræet for proteinerne i Swissprot databasen

libsabstlibsuffixtree

Figur 7.2: Figuren viser udførelsestiden for at konstruere suffikstræet for alle proteinernei Swissprot databasen. Grafen viser antallet af sekunder det tager at bygge suffikstræet forinput af varierende længde.

Page 62: Analyse og implementering af suffikstræer baseret p˚a ... · Kapitel 1 Introduktion Suffikstræet1 er en interessant datastruktur, der har en lang række anvendelsesmuligheder inden

7. Eksperimentel evaluering 59

0100200300400500600700800900

1000

252015105

Seku

nder

Milioner tegn

Konstruktion af suffikstræet for tilfældigt generede strenge

libsabstlibsuffixtree

Figur 7.3: Figuren viser udførelsestiden for at konstruere suffikstræet for tilfældig generedestrenge af varierende længde.

afgøre hvorfor udførelsestiden varierer for de forskellige data typer, men alfabetstørrelsen seikke ud til at spille en ligesa stor rolle som for libsuffixtree.

Jeg kan ud fra dette benchmark konkluderer, at alfabetstørrelsen er afgørende for hvilketet af bibliotekerne, der konstruerer suffikstræet hurtigst. Dog er libsabst kun et par gangelangsommere end libsuffixtree for de sma alfabeter, hvorimod libsuffixtree er en del langsom-mere for store alfabeter. Det er vigtigt at bemærke at SLINK tabellen, som tidligere nævntikke bliver bygget som en del af det udvidede suffiksarray, og det derfor ikke vil være muligtat bruge suffikslinks i libsabst. Denne begrænsning gør sig ikke gældende for libsuffixtree.

7.2 Benchmark: Size

En vigtig motivation for at bruge libsabst i stedet for libsuffixtree er, at libsabst bruger væsent-ligt mindre hukommelse. De fleste applikationer for suffikstræer kræver, at der kan arbejdespa meget store strenge. Dette kan blive et problem hvis suffikstræet bruger sa meget plads,at swappen tages i brug. Hvis det sker vil udførelseshastigheden falde drastisk. Derfor er detvæsentligt at sammenligne pladsforbruget af suffikstræet for de to biblioteker.

I benchmarket bygger jeg først suffikstræet for strengen, derefter beregner jeg pladsfor-bruget i bytes ud fra størrelsen af de underliggende datastrukturer.

Igen er det vigtigt at bemærke, at libsabst ikke indeholder tabellen SLINK, hvis dennetabel var medtaget ville pladsforbruget forøges med 2n bytes.

Pa figur 7.4, 7.5 og 7.6 ses pladsforbruget af suffikstræerne for de forskellige sekvenser.Som det kan ses, er pladsforbruget for libsabst næsten ens for alle sekvenserne. I modsætningtil libsabst ser pladsforbruget for libsuffixtree ud til at variere i med alfabetstørrelsen. Smaalfabeter giver et stort pladsforbrug, og store alfabeter mindsker pladsforbruget. Dette skyldes,at indre knuder i suffikstræet i libsuffixtree bruger meget mere plads end bladene. For etlille alfabet øges sandsynligheden for, at der er flere gentagelser i inputsekvensen. Og dahver gentagelse i sekvensen resulterer i en tilsvarende indre knude i suffikstræet, vil dettebetyde et større pladsforbrug for suffikstræet i libsuffixtree. Helt præcist bruger suffikstræet i

Page 63: Analyse og implementering af suffikstræer baseret p˚a ... · Kapitel 1 Introduktion Suffikstræet1 er en interessant datastruktur, der har en lang række anvendelsesmuligheder inden

7. Eksperimentel evaluering 60

500

400

300

200

100

252015105

Meg

abyt

es

Milioner tegn

Størrelsen af suffikstræet for det menneskelige X kromosom

libsabstlibsuffixtree

Figur 7.4: Denne figur viser pladsforbruget af suffikstræerne for præfikser af det menneske-lige X kromosom. Størrelsen vises som megabytes i forhold til antal tegn, for hver af de tobiblioteker libsabst og libsuffixtree.

400

300

200

100

252015105

Meg

abyt

es

Milioner tegn

Størrelsen af suffikstræet for proteinerne i Swissprot databasen

libsabstlibsuffixtree

Figur 7.5: Denne figur viser pladsforbruget af suffikstræerne for præfikser af proteinerne idatabasen Swissprot.

Page 64: Analyse og implementering af suffikstræer baseret p˚a ... · Kapitel 1 Introduktion Suffikstræet1 er en interessant datastruktur, der har en lang række anvendelsesmuligheder inden

7. Eksperimentel evaluering 61

300

200

100

252015105

Meg

abyt

es

Milioner tegn

Størrelsen af suffikstræet for tilfældig generede strenge

libsabstlibsuffixtree

Figur 7.6: Denne figur viser pladsforbruget af suffikstræerne for tilfældigt generede strenge.

libsabst mellem 7.0 og 7.4 bytes pr. tegn for de udførte eksperimenter. Hvorimod libsuffixtreebruger mellem 11.4 og 18.7 bytes pr. tegn i inputsekvensen. Først vil jeg hæfte mig ved, atlibsuffixtree har et meget mindre pladsforbrug for de udvalgte data, end man kan forvente iværstefald. Hvis man ogsa inkluderede tabellen SLINK i libsabst ville pladsforbruget ikkevære ret langt fra hinanden for de tilfældigt generede strege. Yderligere er det muligt atpakke suffikstræet sa man mindst spare 4 bytes for hver indre knude. Altsa er libsuffixtreepladsmæssigt konkurrencedygtigt for inputsekvenser med fa gentagelser. Men man ma alligevelkonkludere, at libsabst har en fordel, hvis man sammenligner de to biblioteker udlukkende papladsforbrug.

7.3 Benchmark: Exact String Match

Et af de mest oplagte strengeproblemer besvarer spørgsmalet: er X en delstreng af Y . Dettekan besvares ved hjælp af suffikstræet for strengen Y , ved at søge, fra roden, ned gennemsuffikstræet efter strengen X. Hvis hele X strengen bliver fundet, er svaret ja, ellers er detnej. Denne algoritme hedder exact string match.

Dette benchmark udfører exact string match algoritmen 300000 gange pa et præfiks aflængden 10000000 for hver af sekvenserne. Til de 300000 søgninger bruges 300000 delstrengeaf inputsekvensen med varierende længde mellem 10 og 50 tegn. For at ca. halvdelen afsøgningerne skal fejle, søges der hver anden gang efter delstrengen bagfra.

Pa figur 7.7, 7.8 og 7.9 ses benchmarket udført pa de forskellige sekvenser. Som man kanse, knækker libsabst virkeligt halsen pa dette benchmark, faktisk er libsuffixtree er op til 54gange hurtigere end libsabst, hvilket ma siges at være rimeligt katastrofalt. Dette benchmarkudfordre virkeligt libsabst svageste punkt. Nar der søges fra roden i et stort suffikstræ, vilde første værdier i CHILD-tabellen næsten altid være over 255. Det betyder, at vi skalbruge BCK-tabellen til at finde lcp-indekserne, i værste fald kan det være nødvendigt atlave en binær søgning efter indekserne. Det resulterer i ret store omkostninger i forhold til atbruge CHILD-tabellen. For at søge p tegn ned gennem suffikstræet i libsuffixtree tager detO(p · |Σ|), og det er derfor meget hurtigt for sma alfabeter. Men samtidigt er det netop pa de

Page 65: Analyse og implementering af suffikstræer baseret p˚a ... · Kapitel 1 Introduktion Suffikstræet1 er en interessant datastruktur, der har en lang række anvendelsesmuligheder inden

7. Eksperimentel evaluering 62

0

50

100

150

200

250

30025020015010050

Seku

nder

Tusinde søgninger

Exact string match i det menneskelige X kromosom

libsabstlibsuffixtree

Figur 7.7: Denne figur viser hvor lang tid det tager at lave 300000 søgninger i et præfiks afmenneskelige X kromosom med algoritmen exact string match.

0

5

10

15

20

25

30

35

40

30025020015010050

Seku

nder

Tusinde søgninger

Exact string match for proteinerne i Swissprot databasen

libsabstlibsuffixtree

Figur 7.8: Denne figur viser hvor lang tid det tager at lave 300000 søgninger i et præfiks afproteinerne i Swissprot databasen med algoritmen exact string match.

Page 66: Analyse og implementering af suffikstræer baseret p˚a ... · Kapitel 1 Introduktion Suffikstræet1 er en interessant datastruktur, der har en lang række anvendelsesmuligheder inden

7. Eksperimentel evaluering 63

0

10

20

30

40

50

60

70

30025020015010050

Seku

nder

Tusinde søgninger

Exact string match i tilfældigt generede strenge

libsabstlibsuffixtree

Figur 7.9: Denne figur viser hvor lang tid det tager at lave 300000 søgninger med algoritmenexact string match i tilfældigt generede strenge.

sma alfabeter libsabst klarer sig darligst. Det skyldes, at sma alfabeter øger sandsynlighedenfor, at værdierne i CHILD-tabellen er over 255. Ligeledes vil et mindre alfabet ogsa giveanledning til flere gentagelser i inputsekvensen, og dermed øges sandsynligheden for, at der erværdier i LCP som er over 255. Disse to faktorer forværre udførelseshastigheden væsentligtfor libsabst. Dog skal det siges til libsabst forsvar, at algoritmen der søger de p tegn ned i træet,kun søger en knude ned i træet af gangen. Hvis algoritmen støder pa en værdi i CHILD erder 255, resulterer det i en binær søgning for at finde børne-knuden. Det ville være hurtigereat nøjes med at lave en binær søgning, der finder intervallet med præfiks X i dette tilfælde.

Man ma konkludere, at libsabst har et stort problem med at lave mange korte søgningerfra roden i træet. Dette er en ret grundliggende mangel for biblioteket. Men alligevel betyderdet ikke, at libsabst er ubrugligt, da de fleste algoritmer vil bruge væsentligt længere tid vedhver knude og dermed udglatte det store forhold mellem libsuffixtree og libsabst. Et eksempelpa dette kan ses i sektion 7.6.

7.4 Benchmark: Find All Matches

Dette benchmark minder meget om Exact String Match. Algoritmen finder alle positionernefor en givet delstreng i suffikstræet. Dette gøres ved at søge, fra roden, ned gennem suffikstræetindtil alle tegnene i delstrengen er fundet. Derefter findes alle bladene i undertræet. Hvert afdisse blades suffiksnummer angiver startpositionen for delstrengen i sekvensen for suffikstræet.Benchmarket udføres præcist som det forrige, hvor der søges after 300000 delstregen i etpræfiks længden 10000000 af de forskellige sekvenser.

Resultatet af dette benchmark er stort set lig resultatet for benchmarket Exact StringMatch, og der kan stort set drages de samme konklusioner ud fra dette benchmark. Jeghavde egentligt forventet, at der ville være større forskel mellem dette benchmark og detforrige. Men algoritmen er abentbart sa hurtigt til at finde bladene, der angiver positionernei inputsekvensen, at det ikke gør den store forskel.

Pa figur 7.10, 7.11 og 7.12 kan resultatet af benchmarket ses. Graferne er konstrueret pa

Page 67: Analyse og implementering af suffikstræer baseret p˚a ... · Kapitel 1 Introduktion Suffikstræet1 er en interessant datastruktur, der har en lang række anvendelsesmuligheder inden

7. Eksperimentel evaluering 64

0

50

100

150

200

250

30025020015010050

Seku

nder

Tusinde søgninger

Find all matches i det menneskelige X kromosom

libsabstlibsuffixtree

Figur 7.10: Denne figur viser, hvor lang tid det tager at lave 300000 søgninger med algoritmenfind all matches i et præfiks af det menneskelige X kromosom.

0

5

10

15

20

25

30

35

40

30025020015010050

Seku

nder

Tusinde søgninger

Find all matches for proteinerne i Swissprot databasen

libsabstlibsuffixtree

Figur 7.11: Denne figur viser, hvor lang tid det tager at lave 300000 søgninger med algoritmenfind all matches i et præfiks af proteinerne i Swissprot databasen.

Page 68: Analyse og implementering af suffikstræer baseret p˚a ... · Kapitel 1 Introduktion Suffikstræet1 er en interessant datastruktur, der har en lang række anvendelsesmuligheder inden

7. Eksperimentel evaluering 65

0

10

20

30

40

50

60

70

30025020015010050

Seku

nder

Tusinde søgninger

Find all matches i tilfældigt generede strenge

libsabstlibsuffixtree

Figur 7.12: Denne figur viser, hvor lang tid det tager at lave 300000 søgninger med algoritmenfind all matches i tilfældigt generede strenge.

samme made som for Exact String Match.

7.5 Benchmark: Longest repeated substring

Dette benchmark finder den længste delstreng, der mindst findes to steder i inputsekvensen.Dette gøres ved at finde den indre knude i suffikstræet der ligger længst fra roden.

Resultatet af benchmarket for de forskellige inputsekvenser kan ses pa figur 7.13, 7.14 og7.15.

Som man kan se, klarer libsabst sig noget bedre pa dette benchmark end for exact stringmatch benchmarket. Det kan skyldes, de store omkostninger der er ved at navigere i toppenaf træet, udglattes af tiden, der bruges i resten af træet. Det medfører at forskellen mellemde to biblioteker ikke bliver sa stor. Men som man kan se, er libsuffixtree stadig hurtigere foralle inputsekvenserne end libsabst.

7.6 Benchmark: Tandem Repeats

Dette er det sidste og største benchmark. Benchmarket har til formal at vise udførelsestiden forde to biblioteker libsabst og libsuffixtree for en realistisk algoritme. Algoritmen jeg har valgt,finder alle branching og non-branching tandem repeats for inputsekvenserne. Algoritmen erbaseret pa teorien fra artiklen [SGry]. Et tandem repeat er en delstreng, der gentages to gangi træk i inputstrengen. Hvis (i,D, 2) repræsenterer et branching tandem repeat, der starter paposition i og har længden D, sa vil følgende være sandt:

X[i..i + D − 1] = X[i + D..i + 2D − 1] ∧ X[i] 6= X[i + 2D]

F.eks. indeholder strengen “banana” et branching tandem repeat nemlig (2, 2, 2).Et non-branching tandem repeat (i,D, 2) opfylder følgende:

X[i..i + D − 1] = X[i + D..i + 2D − 1] ∧ X[i] = X[i + 2D]

Page 69: Analyse og implementering af suffikstræer baseret p˚a ... · Kapitel 1 Introduktion Suffikstræet1 er en interessant datastruktur, der har en lang række anvendelsesmuligheder inden

7. Eksperimentel evaluering 66

05

101520253035404550

252015105

Seku

nder

Milioner tegn

Beregning af længeste gentagende delstreng i det menneskelige X kromosom

libsabstlibsuffixtree

Figur 7.13: Denne figur viser, hvor lang tid det tager at finde en den længste delstreng, dermindst findes to steder i et præfiks af det menneskelige X kromosom.

0

10

20

30

40

50

60

252015105

Seku

nder

Milioner tegn

Beregning af længeste gentagende delstreng for proteinerne i Swissprot databsen

libsabstlibsuffixtree

Figur 7.14: Denne figur viser, hvor lang tid det tager at finde den længste gentagede delstrengi et præfiks af proteinerne i Swissprot databasen.

Page 70: Analyse og implementering af suffikstræer baseret p˚a ... · Kapitel 1 Introduktion Suffikstræet1 er en interessant datastruktur, der har en lang række anvendelsesmuligheder inden

7. Eksperimentel evaluering 67

0

10

20

30

40

50

60

252015105

Seku

nder

Milioner tegn

Beregning af længeste gentagende delstreng i tilfældigt generede strenge

libsabstlibsuffixtree

Figur 7.15: Denne figur viser, hvor lang tid det tager at finde den længste gentagede delstrengi tilfældigt generede strenge.

0

50

100

150

200

250

300

252015105

Seku

nder

Milioner tegn

Beregning af tandem repeats i det menneskelige X kromosom

libsabstlibsuffixtreelibsabst (bottom-up)

Figur 7.16: Denne figur viser, hvor lang tid det tager at finde alle branching og non-branchingtandem repeats i et præfiks af det menneskelige X kromosom.

Page 71: Analyse og implementering af suffikstræer baseret p˚a ... · Kapitel 1 Introduktion Suffikstræet1 er en interessant datastruktur, der har en lang række anvendelsesmuligheder inden

7. Eksperimentel evaluering 68

0

50

100

150

200

250

300

252015105

Seku

nder

Milioner tegn

Beregning af tandem repeats for proteinerne i Swissprot databsen

libsabstlibsuffixtreelibsabst (bottom-up)

Figur 7.17: Denne figur viser, hvor lang tid det tager at finde tandem repeats i et præfiks afproteinerne i Swissprot databasen.

0

50

100

150

200

250

300

252015105

Seku

nder

Milioner tegn

Beregning af tandem repeats i tilfældigt generede strenge

libsabstlibsuffixtreelibsabst (bottom-up)

Figur 7.18: Denne figur viser, hvor lang tid det tager at finde tandem repeats i et præfiks afproteinerne i Swissprot databsen.

Page 72: Analyse og implementering af suffikstræer baseret p˚a ... · Kapitel 1 Introduktion Suffikstræet1 er en interessant datastruktur, der har en lang række anvendelsesmuligheder inden

7. Eksperimentel evaluering 69

F.eks. indeholder strengen “banana” et non-branching tandem repeat (1, 2, 2).Det viser sig, at for hvert branching tandem repeat vil der være nul eller flere tilhørende

non-branching tandem repeats. Dette gør det let at finde non-branching tandem repeats ud fraet branching tandem repeat. Følgende lemma fastslar denne sammenhæng.

Lemma 7.6.1. Et hvert non-branching tandem repeat (i,D, 2) er en venstrerotation af etandet tandem repeat (i + 1, D, 2).

Bevis. Lad (i,D, 2) være et non-branching tandem repeat. Det vil sige, at følgende er sandt:

X[i..i + D − 1] = X[i + D..i + 2D − 1] ∧ X[i] = X[i + 2D]⇒X[i + 1..i + 1 + D − 1] = X[i + 1 + D..i + 1 + 2D − 1]

Altsa er (i + 1, D, 2) enten et branching eller et non-branching tandem repeat.

Bemærk at en serie af non-branching tandem repeats skal afsluttes af et branching tandemrepeat. Jeg vil ikke ga ind i hvordan algoritmen til at finde tandem repeats fungerer, for endetaljeret forklaring af dette, henviser jeg til artiklen [SGry].

Ligesom for de andre benchmarks har jeg implementeret to næsten identiske algoritmer,der finder alle tandem repeats i suffikstræet for henholdsvis libsabst og libsuffixtree. Yderligerehar jeg implementeret en algoritme baseret pa bottom-up-gennemløbet beskrevet i sektionen3.7. Denne algoritme gør brug af kendskabet til den underliggende suffiksarray-struktur tilat optimere hastigheden af algoritmen. Samtidigt benytter algoritmen kun tabellerne SA ogLCP , og opnar derved et pladsforbrug pa kun 5 bytes pr. tegn i inputstregen.

Tandem repeat-algoritmen har udførelsestiden O(n log n + |output|). Dog tæller jeg kunde fundne tandem repeats, derfor er udførelsestiden nærmere O(n log n). Denne udførelsestidser ud til at stemme godt overens med det opnaede resultat, der kan ses pa figurene 7.16,7.17 og 7.18. Som man kan se, klarer libsabst sig udmærket i forhold til libsuffixtree, menlibsuffixtree er stadig op til dobbelt sa hurtig. Derimod er algoritmen, der er baseret pa etbottom-up-gennemløb næsten dobbelt sa hurtig som libsuffixtree. Altsa kan det bedst svaresig, at bruge libsabst hvis man kan formulere sin algoritme som et bottom-up-gennemløb aflcp-interval-træet. Pa den made sparer man bade tid og plads.

Dette benchmark viser igen, at libsabst har et problem nar CHILD-tabellen anvendes.Dog er problemet ikke ligesa stort i dette benchmark, da algoritmen bruger meget tid i hverknude, hvilket betyder at omkostningerne ved at navigere i træet bliver udlignet.

Konklussionen er, at libsabst har en kæmpe fordel for alle algoritmer der kan formuleressom et bottem-up-gennemløb af lcp-interval-træet. Samtidigt viser dette benchmark ogsa atnavigationen i lcp-interval-træet ved brug af CHILD-tabellen, ikke er et ligesa stort problemsom tidligere antaget.

Page 73: Analyse og implementering af suffikstræer baseret p˚a ... · Kapitel 1 Introduktion Suffikstræet1 er en interessant datastruktur, der har en lang række anvendelsesmuligheder inden

Kapitel 8

Konklusion

Jeg har gennem specialet analyseret og formidlet teorien i artiklen [AKO04] med specielt fokuspa at implementere et suffikstræ med et udvidet suffiksarray som underliggende datastruktur.Jeg har gennem forklaringer og illustrationer udfyldt mange huller i teorien, og forbedret dedele der var beskrevet overfladisk. Jeg har tilføjet mange detaljer til beviserne, sa de er lettereat forsta for en person, der ikke er helt inde i datalogisk streng-teori.

Yderligere har jeg implementeret softwarebiblioteket libsabst, der realiserer alle aspektergennemgaet i specialet, undtagen den teori der omhandler suffikslink-tabellen.

Det er nu tid til at reflektere over de opnaede resultater. I indledningen sektion 1.2 opstil-lede jeg en række krav for implementeringen af det suffiksarray baserede suffikstræ (libsabst).Først definerede jeg en række krav for grænsefladen til det udvidede suffiksarray. Det varmalet, at denne grænseflade skulle ligne et normalt suffikstræet sa meget som muligt. Bibli-oteket libsabst implementerer alle de opsatte krav for grænsefladen undtagen et punkt. Detikke er muligt at fa fat i en knudes suffikslink, i kraft af at suffikslink-tabellen SLINK ikkeer implementeret. Dette kan dog forholdsvis let udbedres, da jeg har beskrevet teorien forkonstruktion og brug af SLINK i specialet. Som beskrevet i sektion 6.3 er grænsefladen fordet udvidede suffiksarray og det normale suffikstræ stort set identiske, derved ma det siges,at dette mal er opnaet.

Yderligere har jeg defineret en række krav for pladsforbruget og udførelsestiden af detsuffiksarray baserede suffikstræet. Det var forventet, at det udvidede suffiksarray ville fyldeomkring 7 bytes pr. input karakter1. Denne forventning viste sig i praksis at være opfyldt, forde eksperimenter jeg har udført i sektion 7.2.

Kravene for udførelsestiderne opstilt i sektion 1.2 viste sig ogsa at være opfyldt i kraftaf, at libsabst var asymptotisk ligesa hurtig som libsuffixtree for de udførte eksperimenter isektion 7. Det idikerer, at udførelsestiden for de enkelte operationer pa suffikstræet ogsa eroverholdt.

Resultatet af den eksperimentelle evaluering viser, at libsabst er en del langsommere endlibsuffixtree til at navigere ned gennem suffikstræet. Men at denne svaghed i nogen gradbliver udlignet for algoritmer, der ikke kun bevæger sig i toppen af suffikstræet. Det blevogsa fastslaet, at libsabst med fordel kan bruges for algoritmer, som kan formuleres somet bottom-up-gennemløb af lcp-interval-træet. For den type algoritmer tilbyder libsabst enmeget stor pladsbesparelse samtidigt med at udførelsestiden er hurtigere end for libsuffixtree.Pladsforbruget for libsabst er generelt bedre end for libsuffixtree, for de udvalgte inputdatabruget libsuffixtree op til 2.5 gange sa meget plads som libsabst. Det ma anses for den størstefordel ved at bruge libsabst, da det betyder, at man kan arbejde pa data, der er op til 2.5gang større end for libsuffixtree. Yderligere tilbyder libsabst en fleksibilitet, som libsuffixtree

1Her er ikke medregnet pladsforbruget for selve stregen og suffikslink-tabellen.

70

Page 74: Analyse og implementering af suffikstræer baseret p˚a ... · Kapitel 1 Introduktion Suffikstræet1 er en interessant datastruktur, der har en lang række anvendelsesmuligheder inden

8. Konklusion 71

pa ingen made kan male sig med. Med libsabst er det muligt, at vælge hvilke tabeller manønsker at gøre brug af, og dermed ogsa hvilken funktionalitet suffikstræet stiller til radighed.Libsabst giver ogsa mulighed for, at ens algoritmer bade kan udnytte suffikstræets struktur,samtidigt med informationen i det udvidede suffiksarray er til radighed.

I modsætning til mine benchmarks viser stort set alle resultaterne af eksperimenterne iartiklen [AKO04, s. 78-83], at det udvidede suffiksarray er hurtigere end suffikstræet. Artik-len bruger algoritmer, der er optimeret til at arbejde direkte pa det udvidede suffiksarray,hvorimod libsabst arbejder pa suffiksarrayet gennem et abstraktionslag. Jeg vil tro, at det erdenne abstraktion, der medfører et stort overhead i forhold til artiklens algoritmer. Men medlibsabst har man jo altid muligheden for at ændre sin algoritme til at arbejde direkte pa detudvidede suffiksarray, og derved opna de samme resultater som i artiklen.

Der er stadig mange low-level optimering og forbedringer der kan tilføres libsabst, menjeg mener at resultatet allerede nu viser meget potentiale. Og jeg haber at andre kan brugebiblioteket som udgangspunkt for en mere professionel implementering af et bibliotek, derkombinerer det bedste fra bade suffikstræet og suffiksarrayet.

Pa følgende adresse [Simd, Speciale webside] kan du hente softwarebibliotekerne libcom-mon, libcollection, libsuffixtree og libsabst. Yderligere indeholder siden installationsvejledningog API-specifikation for ovenstaende biblioteker.

Page 75: Analyse og implementering af suffikstræer baseret p˚a ... · Kapitel 1 Introduktion Suffikstræet1 er en interessant datastruktur, der har en lang række anvendelsesmuligheder inden

Litteratur

[AKO01] Mohamed Ibrahim Abouelhoda, Stefan Kurtz, and Enno Ohlebusch. EnhancedSuffix Arrays and Applications. CRC Press, LLC, 2001.

[AKO04] Mohamed Ibrahim Abouelhoda, Stefan Kurtz, and Enno Ohlebusch. Replacingsuffix trees with enhanced suffix arrays. Discrete Algorithms, 1(2):53–86, 2004.

[BFC00] Michael A. Bender and Martin Farach-Colton. The LCA problem revisited. In LatinAmerican Theoretical INformatics, pages 88–94, 2000.

[GT02] Michael Goodrich and Roberto Tamassia. Algorithm Design. John Wiley and Sons,Inc., 2002.

[KS03] J. Karkkainen and P. Sanders. Simple linear work suffix array construction. InProc. 13th International Conference on Automata, Languages and Programming.Springer, 2003.

[Kur99] Stefan Kurtz. Reducing the space requirement of suffix trees. Software Practice andExperience, 29(13):1149–1171, 1999.

[Mal] Arien Malec. libcheck. http://check.sourceforge.net/.

[Man] Giovanni Manzini. Two space saving tricks for linear time lcp computation.

[McC76] Edward M. McCreight. A space-economical suffix tree construction algorithm. J.ACM, 23(2):262–272, 1976.

[MF] Giovanni Manzini and Paolo Ferragina.http://roquefort.di.unipi.it/∼ferrax/SuffixArray/.

[MM90] Udi Manber and Gene Myers. Suffix arrays: a new method for on-line string searches.In SODA ’90: Proceedings of the first annual ACM-SIAM symposium on Discretealgorithms, pages 319–327, Philadelphia, PA, USA, 1990. Society for Industrial andApplied Mathematics.

[SGry] Jens Stoye and Dan Gusfield. Simple and flexible detection of contiguous repeatsusing a suffix tree. Theor. Comput. Sci., 270(1-2):843–850, uary.

[Sima] Sune Simonsen. Api for libcommon, libcollection, libsabst og libsuffixtree.http://www.we-knowhow.dk/thesis/docs/.

[Simb] Sune Simonsen. Api for suffikstræet i libsabst.http://www.we-knowhow.dk/thesis/libsabst suffixtree.html.

[Simc] Sune Simonsen. Api for suffikstræet i libsuffixtree.http://www.we-knowhow.dk/thesis/libsuffixtree suffixtree.html.

72

Page 76: Analyse og implementering af suffikstræer baseret p˚a ... · Kapitel 1 Introduktion Suffikstræet1 er en interessant datastruktur, der har en lang række anvendelsesmuligheder inden

LITTERATUR 73

[Simd] Sune Simonsen. Speciale webside.http://www.we-knowhow.dk/thesis/.

[Smy03] Bill Smyth. Computing Patterns in Strings. Pearson Education Limited, 2003.

[Tro] Erik Troan. libpopt. http://freshmeat.net/projects/popt.

Page 77: Analyse og implementering af suffikstræer baseret p˚a ... · Kapitel 1 Introduktion Suffikstræet1 er en interessant datastruktur, der har en lang række anvendelsesmuligheder inden

Bilag A

Ekstra benchmarks

Følgende benchmarks er udført pa den engelske bibel med alfabet størrelsen 93, hvor jeg harfjernet alt andet end selve teksten (http://www.gutenberg.org/etext/1581). De udførtebenchmarks er de samme som beskrevet i kapitlet 7.

0

5

10

15

20

25

30

35

40

54321

Seku

nder

Milioner tegn

Konstruktion af suffikstræet for biblen

libsabstlibsuffixtree

Figur A.1: Denne figur viser udførelsestiden for at konstruere suffikstræet for den engelskebibel. Grafen viser antallet af sekunder det tager for de to biblioteker libsabst og libsuffixtreeat bygge suffikstræet for præfikser af biblen.

74

Page 78: Analyse og implementering af suffikstræer baseret p˚a ... · Kapitel 1 Introduktion Suffikstræet1 er en interessant datastruktur, der har en lang række anvendelsesmuligheder inden

A. Ekstra benchmarks 75

80

70

60

50

40

30

20

10

54321

Meg

abyt

es

Milioner tegn

Størrelsen af suffikstræet for biblen

libsabstlibsuffixtree

Figur A.2: Denne figur viser pladsforbruget af suffikstræerne for præfikser af biblen. Størrelsenvises som megabytes i forhold til antal tegn for hver af de to biblioteker libsabst og libsuffixtree.

0

20

40

60

80

100

120

140

30025020015010050

Seku

nder

Tusinde søgninger

Exact string match i biblen

libsabstlibsuffixtree

Figur A.3: Denne figur viser hvor lang tid det tager at lave 300000 søgninger i et præfiks afbiblen med algoritmen exact string match.

Page 79: Analyse og implementering af suffikstræer baseret p˚a ... · Kapitel 1 Introduktion Suffikstræet1 er en interessant datastruktur, der har en lang række anvendelsesmuligheder inden

A. Ekstra benchmarks 76

0

20

40

60

80

100

120

140

160

30025020015010050

Seku

nder

Tusinde søgninger

Find all matches i biblen

libsabstlibsuffixtree

Figur A.4: Denne figur viser, hvor lang tid det tager at lave 300000 søgninger i med algoritmenfind all matches i et præfiks af biblen.

0

2

4

6

8

10

12

14

54321

Seku

nder

Milioner tegn

Beregning af længeste gentagende delstreng for biblen

libsabstlibsuffixtree

Figur A.5: Denne figur viser, hvor lang tid det tager at finde en den længste delstreng, dermindst findes to steder i et præfiks af biblen.

Page 80: Analyse og implementering af suffikstræer baseret p˚a ... · Kapitel 1 Introduktion Suffikstræet1 er en interessant datastruktur, der har en lang række anvendelsesmuligheder inden

A. Ekstra benchmarks 77

0

10

20

30

40

50

60

70

54321

Seku

nder

Milioner tegn

Beregning af tandem repeats i biblen

libsabstlibsuffixtreelibsabst (bottom-up)

Figur A.6: Denne figur viser, hvor lang tid det tager at finde alle branching og non-branchingtandem repeats i et præfiks af biblen.