Suffix träd

Suffixträd för texten BANAN . Varje delsträng avslutas med specialtecknet $ . De sex banorna från roten till bladen (visas som rutor) motsvarar de sex suffixen A$ , NA$ , ANA$ , NANA$ , ANANA$ och BANANA$ . Siffrorna i bladen anger startpositionen för motsvarande suffix. Suffixlänkar, ritade streckade, används under konstruktionen.

Inom datavetenskap är ett suffixträd (även kallat PAT-träd eller, i tidigare form, positionsträd ) ett komprimerat försök som innehåller alla suffixen i den givna texten som deras nycklar och positioner i texten som deras värden. Suffixträd tillåter särskilt snabba implementeringar av många viktiga strängoperationer.

Konstruktionen av ett sådant träd för strängen tar tid och utrymme linjärt i längden av . När de har konstruerats kan flera operationer utföras snabbt, till exempel att lokalisera en delsträng i , lokalisera en delsträng om ett visst antal misstag är tillåtna, hitta matchningar för ett reguljärt uttrycksmönster etc. Suffixträd gav också ett av de första linjära tidslösningarna för det längsta vanliga delsträngsproblemet . Dessa speedups har en kostnad: att lagra en strängs suffixträd kräver vanligtvis betydligt mer utrymme än att lagra själva strängen.

Historia

Konceptet introducerades först av Weiner (1973) . Snarare än suffixet , Weiner lagrade i sin försök prefixidentifieraren för varje position, det vill säga den kortaste strängen som börjar vid och förekommer endast en gång i . Hans Algoritm D tar ett okomprimerat försök för och utökar det till ett försök för . På detta sätt, med utgångspunkt från det triviala försöket för , ett försök för kan byggas av i följd anrop till Algoritm D; den totala körtiden är dock . Weiners algoritm B upprätthåller flera extra datastrukturer för att uppnå en överallt linjär körtid i storleken på den konstruerade trie. De senare kan fortfarande vara noder, t.ex. för Weiners algoritm C använder slutligen komprimerade försök för att uppnå linjär total lagringsstorlek och körtid. Donald Knuth karakteriserade senare den senare som "Årets algoritm 1973". [ citat behövs ] Läroboken Aho, Hopcroft & Ullman (1974 , avsnitt 9.5) återgav Weiners resultat i en förenklad och mer elegant form, och introducerade termen positionsträd .

McCreight (1976) var den första som byggde ett (komprimerat) försök av alla suffix av . Även om suffixet som börjar på vanligtvis är längre än prefixidentifieraren, skiljer sig deras sökvägsrepresentationer i ett komprimerat försök inte i storlek. Å andra sidan kunde McCreight avstå från de flesta av Weiners extra datastrukturer; bara suffixlänkar kvar.

Ukkonen (1995) förenklade konstruktionen ytterligare. Han tillhandahöll den första onlinekonstruktionen av suffixträd, nu känd som Ukkonens algoritm , med körtid som matchade de då snabbaste algoritmerna. Dessa algoritmer är alla linjära tider för ett alfabet av konstant storlek, och har i värsta fall körtid i allmänhet.

Farach (1997) gav den första suffixträdkonstruktionsalgoritmen som är optimal för alla alfabet. I synnerhet är detta den första linjära tidsalgoritmen för strängar som dras från ett alfabet av heltal i ett polynomområde. Farachs algoritm har blivit grunden för nya algoritmer för att konstruera både suffixträd och suffixarrayer , till exempel i externt minne, komprimerat, kortfattat, etc.

Definition

Suffixträdet för strängen med längden definieras som ett träd så att:

  • Trädet har exakt n blad numrerade från till .
  • Förutom roten har varje intern nod minst två barn.
  • Varje kant är märkt med en icke-tom delsträng av .
  • Inga två kanter som börjar från en nod kan ha strängetiketter som börjar med samma tecken.
  • Strängen som erhålls genom att sammanfoga alla strängetiketter som finns på vägen från roten till bladet stavar suffixet , för från till .

Eftersom ett sådant träd inte finns för alla strängar, är vadderad med en terminalsymbol som inte syns i strängen (vanligtvis betecknad $ ). Detta säkerställer att inget suffix är ett prefix till ett annat, och att det kommer att finnas lövnoder, en för vart och ett av de suffixen av . Eftersom alla interna icke-rotnoder är förgrenade, kan det finnas högst n − 1 sådana noder, och n + ( n − 1) + 1 = 2 n noder totalt ( n blad, n − 1 interna icke-rotnoder, 1 rot).

Suffixlänkar är en nyckelfunktion för äldre linjärtidskonstruktionsalgoritmer, även om de flesta nyare algoritmer, som är baserade på Farachs algoritm, avstår från suffixlänkar. I ett komplett suffixträd har alla interna icke-rotnoder en suffixlänk till en annan intern nod. Om sökvägen från roten till en nod stavar strängen , där är ett enda tecken och är en sträng (eventuellt tom) , den har en suffixlänk till den interna noden som representerar . Se till exempel suffixlänken från noden för ANA till noden för NA i figuren ovan. Suffixlänkar används också i vissa algoritmer som körs på trädet.

Ett generaliserat suffixträd är ett suffixträd gjort för en uppsättning strängar istället för en enda sträng. Den representerar alla suffix från denna uppsättning strängar. Varje sträng måste avslutas med en annan avslutningssymbol.

Funktionalitet

Ett suffixträd för en sträng med längden kan byggas i tid, om bokstäverna kommer från ett alfabet med heltal i en polynomområde (i synnerhet gäller detta för alfabet med konstant storlek). För större alfabet domineras speltiden genom att först sortera bokstäverna för att få dem till ett område med storleken ; i allmänhet tar detta tid. Kostnaderna nedan anges under antagandet att alfabetet är konstant.

Antag att ett suffixträd har byggts för strängen med längden eller att ett generaliserat suffixträd har byggts för uppsättningen strängar av total längd . Du kan:

  • Sök efter strängar:
    • Kontrollera om en sträng med längden är en delsträng i tid.
    • Hitta den första förekomsten av mönstren av total längd som delsträngar i tid.
    • Hitta alla förekomster av mönstren av total längd som delsträngar i tid.
    • Sök efter ett reguljärt uttryck P i förväntad tid sublinjär i .
    • Hitta för varje suffix av ett mönster , längden på den längsta matchningen mellan ett prefix av och en delsträng i i tid. Detta kallas matchningsstatistik för .
  • Hitta egenskaper för strängarna:
    • Hitta de längsta vanliga delsträngarna av strängen och i tid.
    • Hitta alla maximala par , maximala upprepningar eller supermaximala upprepningar i tid.
    • Hitta Lempel–Ziv- nedbrytningen i tid.
    • Hitta de längsta upprepade delsträngarna i tid.
    • Hitta de vanligast förekommande delsträngarna med en minsta längd i tid.
    • Hitta de kortaste strängarna från som inte förekommer i , i tid, om det finns sådana strängar.
    • Hitta de kortaste delsträngarna som bara förekommer en gång på tid.
    • Hitta, för varje , de kortaste delsträngarna av som inte förekommer någon annanstans i i tid .

Suffixträdet kan förberedas för konstant tid lägsta gemensamma förfaderhämtning mellan noder i tid. Man kan då också:

  • Hitta det längsta vanliga prefixet mellan suffixen och i .
  • Sök efter ett mönster P med längden m med högst k felmatchningar i tid, där z är antalet träffar.
  • Hitta alla maximala palindromer i , eller tid om gap med längd är tillåtna, eller om missmatchningar är tillåtna.
  • Hitta alla tandemupprepningar i , och k -mismatch tandemupprepningar i .
  • Hitta de längsta vanliga delsträngarna till minst strängar i för i tid.
  • Hitta den längsta palindromiska delsträngen av en given sträng (med hjälp av strängens generaliserade suffixträd och dess baksida) i linjär tid.

Ansökningar

Suffixträd kan användas för att lösa ett stort antal strängproblem som förekommer inom textredigering, fritextsökning, beräkningsbiologi och andra tillämpningsområden. Primära applikationer inkluderar:

  • Strängsökning , i O(m) komplexitet, där m är längden på delsträngen (men med initial O(n) tid som krävs för att bygga suffixträdet för strängen)
  • Hitta den längsta upprepade delsträngen
  • Hitta den längsta gemensamma delsträngen
  • Hitta den längsta palindromen i ett snöre

Suffixträd används ofta i bioinformatikapplikationer , för att söka efter mönster i DNA- eller proteinsekvenser (som kan ses som långa teckensträngar). Förmågan att söka effektivt med felmatchningar kan anses vara deras största styrka. Suffixträd används också vid datakomprimering ; de kan användas för att hitta upprepade data och kan användas för sorteringssteget av Burrows–Wheeler-transformen . Varianter av LZW- komprimeringsscheman använder suffixträd ( LZSS ). Ett suffixträd används också i klustring av suffixträd , en dataklustringsalgoritm som används i vissa sökmotorer.

Genomförande

Om varje nod och kant kan representeras i utrymme, kan hela trädet representeras i utrymme. Den totala längden av alla strängar på alla kanter i trädet är , men varje kant kan lagras som positionen och längden på en delsträng av S , vilket ger en total utrymmesanvändning av datorord. Det värsta tänkbara utrymmesutnyttjandet av ett suffixträd ses med ett fibonacci-ord , vilket ger hela -noderna.

Ett viktigt val när man gör en implementering av ett suffixträd är förälder-barn-relationerna mellan noder. Det vanligaste är att använda länkade listor som kallas syskonlistor . Varje nod har en pekare till sitt första underordnade, och till nästa nod i underordnade listan är den en del av. Andra implementeringar med effektiva körtidsegenskaper använder hash-kartor , sorterade eller osorterade arrayer (med array-fördubbling ) eller balanserade sökträd . Vi är intresserade av:

  • Kostnaden för att hitta barnet på en given karaktär.
  • Kostnaden för att sätta in ett barn.
  • Kostnaden för att värva alla barn i en nod (dividerat med antalet barn i tabellen nedan).

Låt σ vara storleken på alfabetet. Då har du följande kostnader:

Insättningskostnaden amorteras och att kostnaderna för hash anges för perfekt hashning.

Den stora mängden information i varje kant och nod gör suffixträdet mycket dyrt, och konsumerar cirka 10 till 20 gånger minnesstorleken på källtexten i bra implementeringar. Suffixarrayen reducerar detta krav till en faktor 8 (för array inklusive LCP- värden inbyggda inom 32-bitars adressutrymme och 8-bitars tecken.) Denna faktor beror på egenskaperna och kan nå 2 med användning av 4-byte breda tecken ( behövs för att innehålla vilken symbol som helst i vissa UNIX-liknande system, se wchar_t ) på 32-bitarssystem. Forskare har fortsatt att hitta mindre indexeringsstrukturer.

Parallellkonstruktion

Olika parallella algoritmer för att påskynda konstruktionen av suffixträd har föreslagits. Nyligen har en praktisk parallell algoritm för suffixträdkonstruktion med arbete (sekventiell tid) och span har utvecklats. Algoritmen uppnår god parallell skalbarhet på flerkärniga maskiner med delat minne och kan indexera det mänskliga genomet – cirka 3 GB – på under 3 minuter med en 40-kärnig maskin.

Extern konstruktion

Även om det är linjärt är minnesanvändningen för ett suffixträd betydligt högre än den faktiska storleken på sekvenssamlingen. För en stor text kan konstruktion kräva externa minnesansatser.

Det finns teoretiska resultat för att konstruera suffixträd i externt minne. Algoritmen av Farach-Colton, Ferragina & Muthukrishnan (2000) är teoretiskt optimal, med en I/O-komplexitet som är lika med den för sortering. Men den övergripande intrikata av denna algoritm har hittills förhindrat dess praktiska implementering.

Å andra sidan har det funnits praktiska arbeten för att konstruera diskbaserade suffixträd som skalas till (få) GB/timmar. De senaste metoderna är TDD, TRELLIS, DiGeST och B 2 ST.

TDD och TRELLIS skalar upp till hela det mänskliga genomet vilket resulterar i ett diskbaserat suffixträd av en storlek på tiotals gigabyte. Dessa metoder kan dock inte effektivt hantera samlingar av sekvenser som överstiger 3 GB. DiGeST presterar betydligt bättre och klarar av att hantera samlingar av sekvenser i storleksordningen 6GB på cirka 6 timmar.

Alla dessa metoder kan effektivt bygga suffixträd för fallet när trädet inte passar i huvudminnet, men ingången gör det. Den senaste metoden, B 2 ST, skalas för att hantera ingångar som inte får plats i huvudminnet. ERA är en ny konstruktionsmetod för parallella suffixträd som är betydligt snabbare. ERA kan indexera hela det mänskliga genomet på 19 minuter på en 8-kärnig stationär dator med 16 GB RAM. På ett enkelt Linux-kluster med 16 noder (4 GB RAM per nod) kan ERA indexera hela det mänskliga genomet på mindre än 9 minuter.

Se även

Anteckningar

externa länkar