Boyer-Moore algoritme | |
---|---|
Opkaldt efter | Robert S. Boyer [d] og J Strother Moore [d] |
Forfatter | Robert S. Boyer [d] og J Strother Moore [d] |
formål | Effektiv søgning efter en understreng i en streng |
Datastruktur | Strenge |
værste tid | |
Hukommelsesomkostninger | eller |
Mediefiler på Wikimedia Commons |
Boyer-Moore-strengsøgningsalgoritmen er en generel algoritme designet til at søge efter en understreng i en streng . Designet af Robert Boyerog Jay Moorei 1977 [1] . Fordelen ved denne algoritme er, at på bekostning af en vis mængde foreløbige beregninger på skabelonen (men ikke på den streng, hvori søgningen foretages), sammenlignes skabelonen ikke med kildeteksten i alle positioner - nogle af kontrollerne springes over, da det åbenbart ikke giver resultat.
Et generelt estimat af den beregningsmæssige kompleksitet af den moderne version af Boyer-Moore-algoritmen er , hvis stopkaraktertabellen ikke bruges (se nedenfor), og hvis stopkaraktertabellen bruges, hvor er længden af den streng, der søges i. , er længden af søgemønsteret, er alfabetet , som sammenligningen er foretaget på [2] .
Algoritmen er baseret på tre ideer.
1. Scan fra venstre mod højre, sammenlign fra højre mod venstre. Begyndelsen af teksten (linjen) og mønsteret kombineres, kontrollen starter fra det sidste tegn i mønsteret. Hvis tegnene matcher, sammenlignes mønsterets næstsidste karakter osv. Hvis alle tegnene i mønsteret matcher strengens overlappende tegn, så er understrengen fundet, og den næste forekomst af understrengen søges.
Hvis et eller andet tegn i mønsteret ikke matcher strengens tilsvarende karakter, flyttes mønsteret et par tegn til højre, og testen starter igen fra det sidste tegn.
Disse "få" nævnt i det foregående afsnit er beregnet af to heuristika.
2. Stop symbol heuristik. ( Bemærk: stopsymbol-heuristikken er til stede i de fleste beskrivelser af Boyer-Moore-algoritmen, inklusive Boyer og Moores originale artikel [1] , men er ikke nødvendig for at opnå køretidsestimatet [2] ; desuden kræver denne heuristik yderligere hukommelse til arbejde og ekstra tid under udarbejdelsen af skabelonen.)
Antag, at vi søger efter ordet "klokke". Det første bogstav matchede ikke - "k" (lad os kalde dette bogstav et stopsymbol ). Så kan du flytte skabelonen til højre til dens sidste bogstav "k".
Streng: * * * * * * til * * * * * * Skabelon: k o l o k o l Næste trin: k o l o k o lHvis der ikke er noget stoptegn i mønsteret, flyttes mønsteret forbi det stopkarakter.
Streng: * * * * * a l * * * * * * * * Skabelon: k o l o k o l Næste trin: k o l o k o lI dette tilfælde er stoptegnet "a", og mønsteret forskydes, så det er lige bag det bogstav. I Boyer-Moore-algoritmen ser stop-karakterheuristikken slet ikke på det matchede suffiks (se nedenfor), så det første bogstav i mønsteret ("k") vil være under "l", og en kendt dummy check vil blive foretaget.
Hvis stopsymbolet "k" er bag et andet bogstav "k", virker stopsymbolets heuristik ikke.
Streng: * * * * til c o l * * * * * Skabelon: k o l o k o l Næste trin: k o l o k o lI sådanne situationer kommer den tredje idé om Boyer-Moore-algoritmen til undsætning - den matchende suffiksheuristik.
3. Heuristik af det matchede suffiks. Uformelt, hvis suffikset S matches, mens mønsteret læses fra højre mod venstre, men tegnet b foran S i mønsteret (det vil sige mønsteret er PbS) ikke stemmer overens, så flytter den matchede suffiksheuristik mønsteret det mindste tal af steder til højre, så strengen S matcher med mønsteret, og tegnet forud for det givne match S i mønsteret ville være forskelligt fra b (hvis der overhovedet er et sådant tegn). For denne skabelon betragtes formelt et heltal array suffshift[0..m], hvor suffshift[i] er lig med minimumsantallet, således at (hvis og ) og for alle for hvilke og holder (se eksempler nedenfor for forklaring ). Så, hvis tegnene matchede under læsning af mønsteret fra højre mod venstre , men tegnet ikke matchede, så flyttes mønsteret suffshift[mk] tegn til højre. For eksempel:
Linje: * * * * * * p til en * * * * * Skabelon: s ka l k a l k a Næste trin: c a l c a l c aI dette tilfælde matchede "ka"-suffikset, og mønsteret flyttes til højre til den nærmeste "ka", der ikke har et "l" foran sig.
String: * * to c o l * * * * * Skabelon: k o l o k o l Næste trin: k o l o k o lI dette tilfælde matchede "nær"-suffikset, og skabelonen flyttes til højre til den nærmeste "nær", der ikke har et "l" foran sig. Hvis delstrengen "om" ikke længere er i mønsteret, men den starter med "tæl", skifter til "tæller" osv.
Advarsel : Et bogstavmismatch før den nærmeste forekomst af et matchende suffiks er en forudsætning. Hvis vi begrænser os til at skifte til den nærmeste forekomst af et matchet suffiks, så kan algoritmen arbejde uacceptabelt langsomt. Når du f.eks. søger efter et længdemønster i en streng, der indeholder tegnene "a" efterfulgt af en streng med længde , udfører en algoritme, der bruger skift uden at tage hensyn til uoverensstemmelser mellem tegn , operationer, selv når du bruger stoptegnheuristikken. På den anden side er det blevet bevist [2] , at køretiden for BM-algoritmen, under hensyntagen til uoverensstemmelsen mellem tegn (det vil sige ved at bruge suffshift-arrayet defineret ovenfor), er ens , selv uden brug af stopkarakterheuristiken ( givet i bogen af M. Kroshmore og W. Ritter [2] er beviset for dette faktum en modifikation af Coles bevis fra 1994 [3] , som kun betragtede tilfældet med ikke-periodiske mønstre).
Begge heuristika kræver forudberegninger - afhængigt af søgemønsteret udfyldes to tabeller. Tabellen med stopsymboler svarer i størrelse til alfabetet - (hvis for eksempel alfabetet består af 256 tegn, så er dets længde 256); suffiksetabel - til det ønskede mønster, det vil sige .
Lad os beskrive begge tabeller mere detaljeret.
Stoptegntabellen viser den sidste position i mønsteret ( eksklusive det sidste bogstav ) af hvert tegn i alfabetet. For alle tegn, der ikke er inkluderet i , skriver vi 0, hvis tegnnummereringen starter fra 1 (eller −1, hvis nummereringen starter fra 0). For eksempel, hvis , vil tabellen med stoptegn StopTable se sådan ud (tabellen er givet for tilfælde af en streng nummereret fra nul; når du nummererer tegn fra en, skal du tilføje en til alle tal):
Symbol abcd [alle andre] Sidste position 4 1 6 5 -1Bemærk, at for stoptegnet "d" vil den sidste position være 5, ikke 7 - det sidste bogstav tages ikke i betragtning. Dette er en kendt fejl, der fører til suboptimalitet. For BM-algoritmen er den ikke fatal (suffiksheuristikken redder situationen), men den er fatal for en forenklet version af BM -algoritmen - Horspool-algoritmen .
Hvis, når man sammenligner mønsteret med strengen fra højre mod venstre , opstod mismatchet ved position , og stoptegnet er , så skal mønsteret forskydes med tegn.
For hvert muligt suffiks af det givne mønster angiver vi den mindste mængde, hvormed mønsteret skal flyttes til højre, så det matcher igen, og samtidig ville tegnet forud for denne forekomst ikke matche tegnet forud for suffikset . Hvis en sådan forskydning ikke er mulig, skal du sætte (i begge nummersystemer). For vil for eksempel være:
Suffiks [blank] c cc bcc ... aaccbccbcc Offset 2 1 6 10 ... 10 Illustration Det var ? ?c ?cc ?bcc ... aaccbccbcc blev aaccbccbcc aaccbccbcc aaccbccbcc aaccbccbcc ... aaccbccbccFor "klokke":
Suffiks [blank] l ol kol ... okol bell Skift 1 7 7 4 ... 4 4 Illustration Det var ? ?l ?ol ?kol ... ?ring på klokken blev en klokke klokke klokke klokke ... klokke klokkeDer er en algoritme til at beregne suffikstabellen suffshift[0..m] med køretid . [2] Denne algoritme er baseret på de samme ideer som algoritmerne til beregning af præfiksfunktionen og strengen Z-funktionen [4] [5] . Implementeringen af denne algoritme i C++ er som følger:
std :: vektor < int > suffshift ( m + 1 , m ); std :: vektor < int > z ( m , 0 ); for ( int j = 1 , maxZidx = 0 , maxZ = 0 ; j < m ; ++ j ) { if ( j <= maxZ ) z [ j ] = std :: min ( maxZ - j + 1 , z [ j - maxZidx ]); mens ( j + z [ j ] < m && s [ m - 1 - z [ j ]] == s [ m - 1 - ( j + z [ j ])]) z [ j ] ++ ; if ( j + z [ j ] - 1 > maxZ ) { maxZidx = j ; maxZ = j + z [ j ] -1 ; _ } } for ( int j = m - 1 ; j > 0 ; j -- ) suffshift [ m - z [ j ]] = j ; //løkke #1 for ( int j = 1 , r = 0 ; j <= m - 1 ; j ++ ) //løkke #2 if ( j + z [ j ] == m ) for (; r <= j ; r ++ ) hvis ( suffshift [ r ] == m ) suffshift [ r ] = j ;I den første løkke i koden gengives koden til beregning af den såkaldte Z-funktion , men for den inverterede streng . [5] Nemlig for enhver sådan at , elementet indeholder længden af det længste suffiks af strengen , som også er suffikset af hele strengen . Ved at bruge arrayet beregnes den ønskede array suffshift[0..m] (se beskrivelse nedenfor). Ved hver iteration af den sidste løkke falder den med 1, og ved hver iteration af løkken, der er indlejret i den, falder den med 1. Derfor, da , , og algoritmen til at beregne Z-funktionen virker for (se f.eks. , den tilsvarende artikel , samt artikel [5] ), den samlede køretid for denne algoritme er .
For at bevise rigtigheden af den præsenterede kode er det praktisk at forestille sig, at strengen er parset , hvilket er lig med den omvendte streng . Ved definitionen af suffshift har vi suffshift[ ] , hvor er det mindste positive tal, således at enten 1) strengen er et præfiks for strengen , eller 2) strengen er lig med og tegnene og er forskellige. I tilfælde 2) er per definition nødvendigvis opfyldt . Løfte #1 finder således alle suffshift-værdier opnået af regel 2 , når du løber fra til 1). For at beregne suffshift-værdierne opnået af regel 1), bemærker vi, for det første, hvis er et præfiks , så er det nødvendigvis opfyldt , og for det andet, hvis suffshift[ ] = for nogle , så er det nødvendigvis . Baseret på disse to observationer beregner loop #2 de resterende uinitialiserede suffshift-værdier (dvs. sådan, at suffshift[k] = m).
Lad rækken af skift suffshift[0..m] for den givne skabelon tælles. Så er C++-implementeringen af Boyer-Moore-algoritmen til at finde den første forekomst i en tekst i tide uden brug af stop-tegnheuristik som følger [2] :
for ( int i = 0 , j = 0 ; i <= n - m && j >= 0 ; i += suffshift [ j + 1 ]) { for ( j = m - 1 ; j >= 0 && s [ j ] == tekst [ i + j ]; j- ) ; if ( j < 0 ) report_occurrence ( i ); }En sådan algoritme er ikke egnet til at finde alle forekomster af et mønster i en tekst i tide. Hvis vi fjerner betingelsen "j >= 0" fra den ydre sløjfe, vil algoritmen finde alle forekomster, men i værste fald kan den udføre operationer, som let kan ses ved at betragte en streng, der kun består af bogstaverne " en". For at søge efter alle forekomster bruges følgende modifikation, som virker tid på grund af den såkaldte Galil-regel [6] :
int j , bundet = 0 ; //altid enten bundet = 0 eller bundet = m - suffshift[0] for ( int i = 0 ; i <= n - m ; i += suffshift [ j + 1 ]) { for ( j = m - 1 ; j >= bundet && s [ j ] == tekst [ i + j ]; j- ) ; if ( j < bundet ) { report_occurrence ( i ); bundet = m - suffshift [ 0 ]; j = -1 _ //sæt j som om vi læser hele mønsteret s, ikke kun op til bundet } else { bundet = 0 ; } }Galils regel er baseret på følgende simple observation. Hvis en forekomst findes ved position , så vil den næste søgning forsøge at finde en forekomst af mønsteret ved position suffshift[0], og ved definitionen af suffshift er tegnene allerede kendt for at matche tegnene for suffshift[0] . Så når du udfører en højre-til-venstre-søgning for at afgøre, om der er en forekomst af mønsteret ved position , er det ingen mening i at kontrollere tegnene suffshift[0] . Det er hvad den "bundne" variabel er til. Det er bevist, at en sådan heuristik hjælper med at få tid til at søge efter alle forekomster af mønsteret i strengen [6] .
For at aktivere stopsymbol-heuristikken er det nok i += suffshift[j+1]at erstatte linjen " " med følgende udtryk i slutningen af hovedløkken:
if ( j < bundet ) i += suffshift [ j + 1 ]; else i += max ( suffshift [ j + 1 ], j - StopTabel [ tekst [ i + j ]]);Det ønskede mønster er " abbad". Stopsymboltabellen vil se sådan ud (i dette eksempel vil det være mere praktisk at bruge nummerering fra én)
Symbol abd [andre] Position 4 3 5 0Suffikstabellen for alle mulige suffikser (undtagen tomme) giver et maksimalt skift på 5.
abeccaabadbabbad abbadVi pålægger en prøve på linjen. Der er ingen suffiksmatch - suffikstabellen giver et skift med én position. For det uoverensstemmende tegn i kildestrengen " с" (5. position) skrives 0 i stoptegntabellen. Skift mønsteret til højre efter 5-0=5positioner.
abeccaabadbabbad abbadSymbolerne 3-5 matchede, men det andet gjorde det ikke. Heuristikken for " а" virker ikke ( 2-4=-2). Men da nogle af karaktererne matchede, kommer heuristikken i det matchede suffiks i spil, og flytter mønsteret med fem positioner på én gang!
abeccaabadbabbad abbadIgen er der ikke noget matchende suffiks. I overensstemmelse med tabellen med stoptegn flytter vi prøven med en position og får den ønskede forekomst af prøven:
abeccaabadbabbad abbadFor at bevise rigtigheden af algoritmen er det tilstrækkeligt at vise, at hvis en eller anden heuristik foreslår et skift med mere end én position til højre, vil mønsteret ikke blive fundet i de manglende positioner.
Så lad suffikset S matche , skabelonstrengen er lig med PbS , stoptegnet er a (herefter er små bogstaver symboler, store bogstaver er strenge).
Streng: * * * * * * * a [-- S --] * * * * Mønster: [--- P ---] b [-- S --]Stop symbolheuristik. Fungerer, når der ikke er et tegn i strengen V. Hvis P=WaV , og der ikke er et tegn i strengen V , så foreslår stopkarakterens heuristik et skift med | V |+1 position.
Streng: * * * * * * * * * * * * en [-- S --] * * * * * * * * Mønster: [- W -] a [--- V ---] b [-- S --] Spring over: [- W -] a [--- V ---] b [-- S --] Nyt trin: [- W -] a [--- V ---] b [-- S --]Faktisk, hvis strengen V ikke indeholder bogstavet a , er der intet at prøve for den manglende | v | stillinger.
Hvis der ikke er et tegn i mønsteret , så foreslår stoptegnheuristikken et skift med | P |+1 position - og det giver heller ingen mening at prøve den manglende | P |.
Streng: * * * * * * * * en [-- S --] * * * * * * * * Mønster: [--- P ---] b [-- S --] Spring over: [--- P ---] b [-- S --] Nyt trin: [--- P ---] b [-- S --]Matchende suffiks heuristik. Selve den uformelle sætning - "den mindste mængde, som mønsteret skal flyttes til højre for at matche S igen, men tegnet før det givne match med S (hvis et sådant tegn eksisterer) ville være forskelligt fra b" - siger, at mindre skift er ubrugelige.
Boyer-Moore-Horspool (ABMH)-algoritmen fungerer bedre end Boyer-Moore-algoritmen (ABM) på tilfældige tekster - det gennemsnitlige estimat er bedre for det.
ABMX bruger kun stopsymbolet heuristik; stoptegnet tages som tegnet af inputstrengen, der matcher det sidste tegn i mønsteret, uanset hvor uoverensstemmelsen opstod .
Da rigtige søgeprøver sjældent har en ensartet fordeling , kan ABMS give både en gevinst og et tab sammenlignet med ABM.
På korte alfabeter (for eksempel, når man sammenligner DNA- segmenter, består alfabetet kun af fire tegn: A , T , G , C ) svigter stop-karakter-heuristikken allerede ved korte suffikser. Den enkleste måde at forbedre ydeevnen af ABM under sådanne forhold er at bygge en tabel for et par tegn i stedet for et stopkarakter: det, der ikke matchede, og det, der kommer før det [7] . En sådan algoritme blev kaldt Zhu-Takaoka-algoritmen.
Forbehandling tager tid.
Turbo-Boyer-Moore-algoritmen er udviklet af en gruppe forskere ledet af M. Kroshmore , tilbyder en anden tilgang til korte alfabeter og løser samtidig det andet problem - kvadratisk kompleksitet i værste fald.
Ud over stopkarakterheuristikken og den matchede suffiksheuristik anvendes en tredje heuristik, turboshiftheuristikken [8] .
Lad suffikset UV matche første gang (og suffikset heuristik virkede, hvilket giver et fuldstændigt overlap af dette suffiks), anden gang - et kortere V (muligvis V = ∅).
! ! inputstreng: * * * * * * * * * * # [-U-] [V] * * * * * * * * # [V] * * * * * * 1. Matchet UV: * [-U-] [V] * * * * [-U-] [V] 2. Så matchede V: * [-U-] [V] * * * * * * [-U-] [V] Suffiks heuristisk skift: * [-U-] [V] * * * * * * [-U-] [V] Turboshift: * [-U-] [V] * * * * * * [-U-] [V]Figuren viser, at den mindst mulige forskydning er | U |. Ellers er de to tegn, der er angivet med udråbstegn, forskellige i inputstrengen, men ens i mønsteret. Dette er turboshift-heuristikken.
Algoritmen gør sit arbejde for sammenligninger med den første kamp i værste fald.
Boyer-Moore algoritmen er meget hurtig på "gode" data.[ afklar ] , og sandsynligheden for, at der opstår "dårlige" data er ekstremt lille. Derfor er det optimalt i de fleste tilfælde, når det ikke er muligt at forbehandle den tekst, hvori søgningen udføres [9] . Medmindre på korte tekster vil gevinsten ikke retfærdiggøre foreløbige beregninger.
Algoritmerne for Boyer-Moore-familien udvides ikke til en omtrentlig søgning, søgningen efter en hvilken som helst streng fra flere.
Sammenligningen er ikke en " sort boks " (kun hvis stop-karakter-heuristikken bruges), så når du implementerer den hurtigste søgning, skal du enten stole på, at optimeringsværktøjet fungerer med succes , eller manuelt optimere søgningen i assemblersprog.
Hvis teksten sjældent ændres, og søgningen udføres ofte (f.eks. af en søgemaskine ), kan du indeksere teksten. Indekssøgningsalgoritme er hurtigere[ klargør ] Boyer-Moore-algoritmen.
På store alfabeter (som Unicode ) kan stopkaraktertabellen optage meget hukommelse. I sådanne tilfælde undlades enten hashtabeller , eller alfabetet opdeles, f.eks. i betragtning af et 4-byte tegn som et par to-byte, eller (hvilket er det enkleste) de bruger en variant af Boyer'en -Moore-algoritme uden heuristik af stopkarakterer.
Der er en række modifikationer af Boyer-Moore-algoritmen, der sigter mod endnu større acceleration - for eksempel turboalgoritmen, den omvendte Colussi-algoritme [10] og andre.
Strenge | |
---|---|
String lighedsmål | |
Understrengssøgning | |
palindromer | |
Sekvensjustering | |
Suffiksstrukturer | |
Andet |