Áttérés egy vizuális/grafikus program- fejlesztői környezetre (minimális tudnivalók)
Fejlesztői dokumentáció · Web viewAz adott algoritmushoz tartozó script egy osztály...
Transcript of Fejlesztői dokumentáció · Web viewAz adott algoritmushoz tartozó script egy osztály...
Tartalomjegyzék
Tartalomjegyzék................................................................................................................3
Bevezetés...........................................................................................................................5
Felhasználói dokumentáció...............................................................................................6
Mintaillesztő algoritmusok..........................................................................................6
Brute Force (naiv) algoritmus............................................................................8
Knuth-Morris-Pratt algoritmus.........................................................................10
Quick-Search algoritmus..................................................................................14
Rabin-Karp algoritmus.....................................................................................17
Shift-or algoritmus............................................................................................20
Boyer-Moore algoritmus..................................................................................23
Horspool (Boyer-Moore-Horspool) algoritmus...............................................26
Raita algoritmus................................................................................................27
Reverse Colussi algoritmus..............................................................................28
A felhasználói felület.................................................................................................32
Fejlesztői dokumentáció..................................................................................................37
A webes felület szerkezete........................................................................................37
Az algoritmusok bemutató-felületei.................................................................37
Az algoritmusok összehasonlítása lap..............................................................49
A C++ program szerkezete........................................................................................52
Osztályok..........................................................................................................53
A program tesztelése.................................................................................................60
Modultesztelés..................................................................................................60
Rendszertesztelés..............................................................................................65
Összefoglalás...................................................................................................................66
Irodalomjegyzék..............................................................................................................67
BevezetésDolgozatom tárgya a mintaillesztő (sztringkereső) algoritmusok, egészen pontosan az
egzakt mintaillesztés, vagyis az a feladat, hogy egy adott abc feletti véges sorozatban
keressük meg egy rövidebb szövegminta összes egybefüggő előfordulását. Azt
tapasztaltam, hogy a témakör magyar nyelvű irodalma a bemutatott módszerek
számában, és az interaktivitás irányába egyaránt bővíthető.
Célom az, hogy az Algoritmusok és adatszerkezetek II. tárgy keretében tanult, és még
néhány széles körben ismert egzakt mintaillesztési módszert a lehető legtömörebben és
legérthetőbben mutassak be. Ezért választottam a html felületet és a javascript nyelvet,
hiszen így hagyományos böngészővel lépésről-lépésre nyomon követhető az egyes
algoritmusok működése, tetszőleges helyen megállítható a futás, ezzel segítve a
módszer megértését. Továbbá az algoritmusok kényelmesen összehasonlíthatók, ezzel
szemléletessé válnak az egyes módszerek erősségei és gyengéi.
Reményeim szerint sikerült hatékony tanulási segédanyagot előállítanom a következő
évfolyamok hallgatóinak.
Felhasználói dokumentációMintaillesztő algoritmusokBevezető
Ebben a fejezetben a honlapon szereplő algoritmusok részletes leírása szerepel. Néhány,
a szövegben szereplő kifejezést szeretnék egyértelműen meghatározni, ezzel javítva a
leírások érthetőségét. A továbbiakban a szöveget és a mintát rendre n és m hosszúnak
feltételezem. Felteszem, hogy n>m>0, x[i] alatt az x szöveg (i+1). karakterét értem,
vagyis a sztringek karaktereinek számozása 0-tól (hossz-1)-ig terjed. Próbán vagy
illesztési próbán azon karakter-összehasonlítások összességét értem, amik arra
hivatottak, hogy megmondják, hogy a minta a szöveg egy adott pozíciójára illeszkedik-
e, vagyis hogy az adott k-hoz n[k+i] = m[i] (0≤i<hossz). Egy illesztési próba eredmény
kétféle lehet: vagy i db karakteregyezés után karaktereltérést találunk, vagy a próba
teljes egyezésig jut. Két algoritmusnál eltérünk ettől a mintától, a Rabin-Karpnál az
illesztési próba eredménye eltérés, hibás egyezés, és valódi egyezés lehet, a shift-or
algoritmusnál ilyen értelemben nem végzünk illesztési próbát.
Az előfeldolgozás az algoritmus azon része, amelyben valamilyen értéket, tömböt,
táblázatot határozunk meg csupán a minta ismeretében. Ezt a későbbiekben arra
használjuk, hogy egyes pozíciókról próba nélkül meg tudjuk mondani az illeszkedés
lehetetlenségét, illetve, hogy egyes, a szöveg korábbi illesztés során már vizsgált
karaktereiről meg tudjuk mondani, hogy a következő illesztésnél egyeznek-e a minta
megfelelő karaktereivel.
A keresés az algoritmus azon része, amelyben az illesztési próbákat végezzük.
Az ábrákon a próbákat vízszintes vonal választja el egymástól, a vonal mellett
olvasható, hogy mennyi pozícióval lép az algoritmus jobbra. Ha ez nagyobb, mint 1, az
azt jelenti, hogy néhány helyre meg tudjuk mondani az illeszkedés lehetetlenségét. A
zöld háttérszín a karakteregyezést, a piros a karaktereltérést, jelzi, a szürke háttér azt
jelenti, hogy a karakter nem került vizsgálatra a próba során.
Az algoritmusok működésének leírásánál az Algoritmusok és adatszerkezetek II. tárgy
anyagát[1], Cormen, Leiserson, Rivest: Algoritmusok[2] című könyvét, és a téma
Wikipedia[3] szócikkét használtam.
Brute Force (naiv) algoritmus
Rövid leírás
A Brute Force, vagy naiv algoritmus a legköltségesebb, egyben legkönnyebben
implementálható mintaillesztő algoritmus. Előfeldolgozást nem végez. Működése abból
áll, hogy megpróbálja a mintát a szövegre illeszteni minden lehetséges helyen.
Az algoritmus a szövegre balról jobbra, mindig csak egyet ugorva próbálja illeszteni a
mintát.
Előfeldolgozás
Az algoritmus előfeldolgozást nem végez.
1. ábra
Keresés
Egy illesztési próba abból áll, hogy a minta karaktereit balról jobbra véve egyesével
összehasonlítja a szöveg megfelelő karaktereivel egészen addig, amíg karaktereltérésig,
vagy teljes egyezésig nem jut. Az 1. ábra az algoritmus futásának elejét mutatja be. Ha
az illesztés aktuális pozíciója nagyobb (n-m)-nél, akkor már a mintát nem lehet a
szövegre illeszteni, mert túllóg azon, ilyenkor az algoritmus befejezi működését (2.
ábra).
2. ábra
Lépésszám elemzés
Az előfeldolgozás lépésszáma értelemszerűen 0.
A keresés során a 0. pozíciótól az (n-m).-ig összesen (n-m+1) illesztési próbát végzünk
el. Egy illesztési próba legalább 1, és legfeljebb m karakter-összehasonlításból áll, és
létezik olyan eset, amiben minden próba 1, illetve olyan is, amiben minden próba m
lépésből áll. Előbbire példa a (BBBBBBBB; ABC) szöveg-minta pár, hiszen itt minden
illesztésnél elsőre karaktereltéréshez jutunk, utóbbira példa a (BBBBBBBB; BBB) pár.
Tehát az algoritmus lépésszáma Ω(n-m+1), Ο((n-m+1)∙m).
Knuth-Morris-Pratt algoritmus
Rövid leírás
A Knuth-Morris-Pratt (KMP) algoritmus az előfeldolgozás során kiszámolja az ún. next
tömböt, ahol next[i] = az m[0..(i-1)] részsztringhez tartozó leghosszabb olyan valódi
prefix hossza, ami megegyezik m[0..(i-1)] azonos hosszú szuffixével. Ezt a keresés
során úgy használja fel, hogy ha egy próba során a (k+1). karakternél karaktereltéréshez
jutunk, akkor a mintát k - next[k] karakterrel jobbra toljuk. Ekkor tudhatjuk, hogy az
ugrással nem hagytunk ki lehetséges illeszkedést, és a már megvizsgált next[k] karakter
egyezni fog a minta megfelelő karaktereivel.
Előfeldolgozás
A KMP-nél az előfeldolgozás a next tömb feltöltését jelenti. A next (hossz+1) hosszú
tömb i. eleme az m[0..(i-1)] részsztringhez tartozó leghosszabb olyan valódi prefix
hosszát veszi értékül, ami megegyezik m[0..(i-1)] azonos hosszú szuffixével. (A KMP
algoritmusok és adatszerkezetek II. tárgybeli tárgyalásában a next[0] és next[hossz]
értékek nincsenek definiálva. A next[0]-ra a próbák során nem lesz szükségünk, csupán
a tömbszámozási konvenció miatt használjuk. A next[hossz] érték azonban fontos,
hiszen jelen dolgozatban nem csak az első egyezésig keresünk, így ha teljes illeszkedést
találunk, akkor a továbblépésnél szükségünk lesz a next[hossz] értékre.)
Tudjuk, hogy next[0] = next[1] = 0 minden esetben, hiszen 0 illetve 1 hosszú
részsztringnek nem létezik valódi prefixe. Könnyen látható, hogy next[i+1] = (k+1)
csak akkor lehetséges, ha next[i] ≥ k (3. ábra).
3. ábra
Így a next[i] kiszámításakor elég a next[i-1]+1 hosszú szuffixtől indulni, hiszen
hosszabb szuffix nem lehet jó..
4. ábra
A next tömb feltöltését tovább gyorsítja az a megfigyelés, hogy ha next[i] = k, akkor
annak vizsgálatánál, hogy next[i+1] megegyezik-e (k+1)-gyel, elég a prefix (k+1).
karakterét összehasonlítani a prefix utolsó karakterével. A 4. ábrán látható az
’ABABAC’ mintához tartozó next tömb kiszámításának menete
Keresés
A KMP algoritmus minden egyes illesztési próbánál a minta karaktereit balról jobbra
véve egyesével összehasonlítja a szöveg megfelelő karakterével. Ha i egyezés után
karaktereltéréshez, vagy teljes egyezéshez jut, akkor ha next[i] = 0, akkor 1-gyel tovább
lép, és ott kezd új próbát. Ha next[i] ≠ 0, akkor i - next[i]-vel lép tovább, és az új
illesztésben az első next[i] karaktert már ellenőrzöttnek és egyezőnek tekinti. Az 5.
ábrán látható az algoritmus működése.
5. ábra
Lépésszám elemzés
A next tömb feltöltése során elemenként legalább 1 karakter-összehasonlítást kell
végezni, de összesen legfeljebb 2∙m db-ot, tehát az előfeldolgozás lépésszáma Θ(m).
Az algoritmus futása során a szöveg aktuálisan vizsgált karakterének pozíciója csak
előre haladhat, nincs visszalépés a szövegben, mint a Brute Force algoritmusban. Az
előrehaladásnak két esete lehetséges. Első esetben, amikor a minta egyezik a szöveggel,
eggyel előre lépünk a vizsgálati pozícióval. Második eset az, amikor egy adott
pozíciónál eltérés adódik. Ilyenkor a vizsgálat pozíciója nem változik, viszont a mintát
húzzuk odébb, és az adott pozíción újra összehasonlítunk. Mivel ilyenkor az illesztés
pozíciója változik, ilyen eset összesen legfeljebb n-szer (pontosabban n-m+1-szer)
történhet a futás során, (hiszen ennyi pozícióváltással mindenképen a végére jutunk a
szövegnek). Így összesen legfeljebb 2∙n karakter-összehasonlítást végzünk, tehát a
keresés lépésszáma Θ(n).
Megjegyzések
A Morris-Pratt algoritmust 1970-ben, a Knuth-Morris-Pratt algoritmust 1977-ben
publikálták.
További leírások találhatók Fekete István honlapján[1] és az algoritmus Wikipedia
szócikkében (http://en.wikipedia.org/wiki/Knuth-Morris-Pratt_algorithm).
Quick-Search algoritmus
Rövid leírás
Az algoritmus az abc minden elemeihez 1 és m közötti számokat rendelő shift tömböt
használja a felesleges illesztések megspórolására. Balról jobbra haladva, ha
karaktereltérésig vagy teljes egyezésig jut, akkor úgy dönti el, hogy mekkorát ugorhat,
hogy megvizsgálja a szövegnek az illesztés utáni első karakterét.
Előfeldolgozás
A QS algoritmus a mintából előállított shift tömböt használja. A tömb funkciója, hogy a
szövegből olvasott betű alapján meg tudja mondani, hogy mekkora ugrás engedhető
meg úgy, hogy ne maradhasson ki potenciális egyezés. Például, ha olyan karaktert
olvas, ami egyáltalán nem szerepel a mintában, akkor minden olyan pozícióra illesztés,
amibe beletartozik ez a karakter felesleges, így ezeket át kell ugrani (6. ábra).
6. ábra
A shift tömb feltöltése során először az abc minden ’x’ elemére shift[’x’]-et (m+1)-re
állítjuk. Ezután a minta minden ’k’ karakterére shift[’k’]-nak azt az értéket adjuk,
amilyen távolságra van a minta legutolsó ’k’ karaktere a minta végétől (7.ábra).
7. ábra
Fontos, hogy a minta legutolsó ’k’ karakterének távolságát vegyük, különben lehetséges
illeszkedéseket ugorhatnánk át.
Keresés
A keresés során egy próbán belül a karaktereket balról jobbra vizsgáljuk, amíg
karaktereltéréshez, vagy teljes egyezéshez nem jutunk. Ekkor pedig az illesztés utáni
karakterhez tartozó shift értékkel jobbra lépünk. A shift tömb garantálja, hogy az
olvasott karakter alapján ez az első lehetséges illeszkedési pozíció. A 8. ábrán látható az
algoritmus működése.
8. ábra
Lépésszám elemzés
Ha a shift tömb elemeinek inicializálását konstansnak tekintjük, akkor a feltöltés során a
minta karaktereit kell csak vizsgálnunk, vagyis az előfeldolgozás költsége Θ(m).
Az algoritmus előnye, hogy átlagos esetben (ha viszonylag rövid a minta, és a mintában
nem túl sok féle betű fordul elő) igen gyorsan tud haladni, viszont hátránya az, hogy a
vizsgált pozícióhoz képest előre és vissza is kell mozogni a szövegben. A keresés során,
ha mindig a minta utolsó karakterét olvassuk a szövegből, és az alapján változtatjuk a
pozíciót, akkor a Brute Force algoritmus működését kapjuk vissza. Azonban ideális
esetben, ha mindig olyan karakter alapján ugrunk, ami nem szerepel mintában, akkor
elég minden (m+1). pozíciót vizsgálni. Tehát a keresés lépésszáma: Ο(m∙n), Ω(n/m).
Megjegyzések
A Quick Search algoritmust a Boyer-Moore algoritmus egyszerűsítéseként publikálták
1990-ben.
További leírások találhatók Fekete István honlapján[1].
Rabin-Karp algoritmus
Rövid leírás
A Rabin-Karp algoritmus a szöveg és a minta karaktereit számjegyekként értelmezi, a
mintát, és a szöveg részeit számokként. Így ha egy m hosszú szövegrész ugyanazt a
számot jelenti, mint amit a minta jelent, akkor egyezést talált.
Előfeldolgozás
Az előfeldolgozás során kiszámoljuk a mintához tartozó számot. σ elemszámú abc-n az
m hosszú minta, egy σ számrendszerbeli m-jegyű számot jelent. A túlcsordulás
elkerülése végett bevezetünk egy d modulust, és a számokat d modulusban számoljuk.
Ennek az a következménye, hogy két különböző karaktersorozathoz is
hozzárendelhetjük ugyanazt a számot. Ezért ha a próba pozícióján a szöveg
részsztringjének száma megegyezik a minta számával, akkor le kell ellenőrizni
karakterenként az egyezést.
Keresés
A keresés során, ha a vizsgált pozíción a szöveg aktuális részsztringjének száma
megegyezik a minta számával, akkor karakterenként ellenőrizzük az egyezést. Ha nem
egyezik, akkor egy pozícióval tovább lépünk, és kiszámoljuk az új pozícióhoz tartozó
számot. Ahhoz, hogy ezt meggyorsítsuk, használjuk ki, hogy a szám speciálisan
változik. A legnagyobb számjegye eltűnik, a kisebb helyiértékek eggyel nagyobbak
lesznek, és a legkisebb helyiértékre új számjegy kerül. Így ahelyett, hogy az egész
számot újraszámolnánk, elég, ha a legnagyobb számjegyet (a megfelelő helyiértékre
emelve, d modulusban) kivonjuk a számból, az így kapott értéket megszorozzuk σ-val
(a számrendszer alapjával), majd a kapott értékhez hozzáadjuk az új számjegyet.
A példa (9. ábra) 4-es számrendszerben (A = 0; B = 1; C = 2; D = 3) dolgozva, 12-es
modulussal számol.
Minden ugrásnál kivonja az előző számból a legnagyobb helyiértéken álló számjegy
számjegy értékét, a kapott számot megszorozza 4-gyel, és hozzáadja az új, legkisebb
helyiértéken lévő számjegyet.
9. ábra
Lépésszám elemzés
Az előfeldolgozás során karakter-összehasonlítást nem végzünk.
A Rabin-Karp algoritmus a lehetséges n-m+1 pozícióban hasonlítja össze a generált
számokat. Lehetséges, hogy minden pozíción egyeznek az értékek, ezért egyesével
össze kell hasonlítani a karaktereket. Így a Brute Force algoritmus működését kapjuk
vissza. Azonban megfelelő modulus választásával (téves egyezések nélkül) az
algoritmus lineárisan tud futni. Tehát a műveletigény: Ο(m∙n).
Megjegyzések
A Rabin-Karp algoritmust 1987-ben publikálták.
További leírások találhatók Fekete István honlapján[1] és a Wikipedián:
http://en.wikipedia.org/wiki/Rabin%E2%80%93Karp_string_search_ algorithm.
Shift-or algoritmus
Rövid leírás
A shift-or algoritmus az abc minden betűjére elkészíti a mintához tartozó bittérképet,
vagyis egy olyan tömböt, ami 0 és 1 értékeket vesz fel, 0-t ott, ahol szerepel az adott
karakter a mintában, 1-et mindenhol máshol. Ha a minta elég kicsi (word méretű) akkor
a bittérképek felhasználásával egy nagyon effektív keresést lehet végrehajtani.
Előfeldolgozás
Az előfeldolgozás során az abc minden ’x’ karakteréhez tartozó m hosszú B[’x’]
bittömböket hozza létre. Kezdetben minden bittömb minden értékét 1-re inicializálja,
majd, ha a minta i. értékén az ’x’ karakter szerepel, akkor B[’x’][i]-t 0-ra állítja. Így
elkészül minden karakterhez a minta bittérképe (10. ábra).
10. ábra
A keresés során az m hosszú V bitvektorban fogjuk tárolni a vizsgált pozícióban az
illeszkedést, V minden értékét kezdetben 1-re állítjuk.
Keresés
A shift-or algoritmusban nem az eddigi értelemben vett illesztési próbákat végzünk,
hanem a szöveg karakterein egyesével jobbra lépve, minden pozícióhoz kiszámoljuk,
hogy ha a vizsgált pozíció egy illesztés i. karaktere, akkor a szöveg illeszkedik-e a
mintára a 0.-tól az i. karakterig. Ezt V[i]-ben fogjuk tárolni, V[i] = 0 jelenti az i.
karakterig való illeszkedést.
A keresés minden lépésében egy bitenkénti eltolást (shift) és egy bitenkénti vagyolást
(or) fogunk végezni a V bitvektoron. Egy bittel eltoljuk, ez azt jelenti, hogy amely
illeszkedés a szöveg előző karakterénél i hosszú volt, most már (i+1) hosszú. Így
lényegében feltételezzük, hogy a szövegből olvasott új karakterrel nem romlik el az
illeszkedés. Hogy valójában elromlik-e, azt úgy ellenőrizzük, hogy ha a szövegből ’x’-
et olvastuk, akkor az immár egy bittel eltolt V vektort bitenként vagyoljuk a B[’x’]
tömbbel. Így V[i+1] csak akkor lehet 0, ha az előző karakternél olvasásakor V[i] értéke
0 volt, és az ’x’ karakter a minta (i+1). karaktere. A 11. ábra[4] az algoritmus futását
mutatja be.
11. ábra
Ha V[m-1] = 0, az azt jelenti, hogy a vizsgált pozícióval bezárólag teljes illeszkedést
találtunk.
Lépésszám elemzés
Az előfeldolgozás során a minta m karakterét kell csak vizsgálni, így a lépésszám Θ(m).
A keresés során a szöveg minden karakterénél egy shift és egy or műveletet végzünk,
tehát a lépésszám Θ(n).
Megjegyzések
A shift-or algoritmust, bitap és Baeza-Yates-Gonnet néven is ismerik. A története 1964-
ben kezdődött, amikor első változatát a mai napig aktív, 1935-ben született, és az
ELTÉn diplomázott Dömölki Bálint publikálta. Rövid életrajza megtalálható a
http://www.novofer.hu/alapitvany/dijazott/21 címen.
Később, főleg a 90-es években az algoritmusnak több fejlesztése és általánosítása is
készült.
További leírások találhatók a Gaspard-Monge Elektronikai és Számítástechnikai Intézet
honlapján[4] (http://www-igm.univ-mlv.fr/~lecroq/string/node6.html#SECTION0060) és
a Wikipedián: (http://en.wikipedia.org/wiki/Shift_Or_Algorithm).
Boyer-Moore algoritmus
Rövid leírás
A korábbi algoritmusokkal szemben egy próba során a minta utolsó karakterétől kezdve
az első felé, visszafele haladva vizsgálja az illeszkedést. Egy próba végén az ún. „hibás-
karakter” és a „jó-szuffix” tömbök alapján ugrik.
Előfeldolgozás
A Boyer-Moore algoritmus az előfeldolgozás során előállítja a „hibás-karakter” és a
„jó-szuffix” tömböket. Az, hogy a Gs (m+1) hosszú „jó-szuffix” tömb i. eleme Gs[i] = k
azt jelenti, hogy ha egy próba úgy ér véget, hogy (hátulról haladva visszafelé) i db
karakteregyezést találtunk, akkor ahhoz, hogy a szövegnek a próba során megvizsgált
részsztringje illeszkedhessen egy későbbi teljes egyezésre, legalább k karakterrel tovább
kell ugrani (12. ábra).
A Bc „hibás-karakter” tömb a Quick Search algoritmus shift tömbjéhez hasonlóan az
abc minden eleméhez hozzárendeli, hogy ha a szövegből az adott karaktert olvassuk,
akkor mekkora az a legkisebb ugrás, amivel biztosan nem hagytunk ki lehetséges teljes
illeszkedést (6-7. ábrák).
12. ábra
Keresés
Az illesztést a szöveg elejétől kezdve, az illesztésen belül a karaktereket jobbról balra
egyesével összehasonlítva keressük a teljes egyezéseket. Egy próba végén, ha utoljára
az ’x’ karaktert olvastuk a szövegből, és összesen i karakteregyezésig jutottunk, akkor
Bc[’x’]-i és Gs[i] közül a nagyobb értékkel ugrunk. A Gs tömb alapján, és a Bc tömb
alapján történő ugrásra látható egy-egy példa a 13. ábrán.
13. ábra
Lépésszám elemzés
Az előfeldolgozás során a Gs tömb felépítése Ο(m) lépéssel elvégezhető, a Bc tömb
felépítése a Quick Search shift tömbjéhez hasonlóan szintén Ο(m) lépésbe kerül, tehát
az előfeldolgozás lépésszáma Ο(m).
A keresés lépésszáma Ο(n∙m), hiszen szerencsétlen esetben lehetséges, hogy minden
próba m lépésbe telik, és pl. Gs és Bc is legfeljebb 2 minden ugrásnál. Ideális esetben
azonban lépésenként m hosszan ugorhatunk, így a keresés lépésszáma Ω(n/m).
Ismétlődés nélküli minta esetén legfeljebb 3∙n lépés alatt lefut.
Megjegyzések
A Boyer-Moore algoritmust 1977-ben publikálták.
További leírásai megtalálhatók az Algoritmusok című könyben[2], és a Wikipedián
(http://en.wikipedia.org/wiki/Boyer-Moore_string_search_algorithm).
Horspool (Boyer-Moore-Horspool) algoritmus
Rövid leírás
A Boyer-Moore algoritmus egyszerűsítése. Csak a „hibás-karakter” tömböt használjuk.
Előfeldolgozás
A Bc „hibás-karakter” tömb a Quick Search algoritmus shift tömbjéhez hasonlóan az
abc minden eleméhez hozzárendeli, hogy ha a szövegből az adott karaktert olvassuk,
akkor mekkora az a legkisebb ugrás, amivel biztosan nem hagytunk ki lehetséges teljes
illeszkedést (6-7. ábrák).
Keresés
A Boyer-Moore-tól eltérően egy illesztési próbán belül a karakter vizsgálata a
következő sorrendben történik: először a minta utolsó karakterét vizsgáljuk, majd a
minta többi karakterét, az elsőtől indulva egyesével. A másik eltérés, hogy nem a
legutóbb olvasott, hanem mindig az illesztés utolsó karaktere szerint ugrik.
Lépésszám elemzés
Az előfeldolgozás költsége Θ(m).
A keresés lépésszáma: Ο(m∙n), Ω(n/m) (lásd Quick Search algoritmus).
Megjegyzések
A Boyer-Moore-Horspool algoritmust a Boyer-Moore egyszerűsítéseként 1980-ban
publikálták.
További leírások találhatók a Gaspard-Monge Elektronikai és Számítástechnikai Intézet
honlapján[4] (http://www-igm.univ-mlv.fr/~lecroq/string/node18.html#SECTION0180),
és a Wikipedia Boyer-Moore-Horspool szócikkében.
Raita algoritmus
Rövid leírás
A Boyer-Moore algoritmus egyszerűsítése. Működése a Horspool algoritmustól csupán
az egy próbán belül vizsgált karakterek sorrendjében különbözik.
Előfeldolgozás
A Bc „hibás-karakter” tömb a Quick Search algoritmus shift tömbjéhez hasonlóan az
abc minden eleméhez hozzárendeli, hogy ha a szövegből az adott karaktert olvassuk,
akkor mekkora az a legkisebb ugrás, amivel biztosan nem hagytunk ki lehetséges teljes
illeszkedést (6-7. ábrák).
Keresés
A Boyer-Moore-tól eltérően egy illesztési próbán belül a karakter vizsgálata a
következő sorrendben történik: először a minta utolsó karakterét vizsgáljuk, majd a
minta első karakterét, aztán a középső karaktert, végül a többi karaktert a másodiktól
indulva egyesével (a középsőt így akár kétszer is). A másik eltérés, hogy nem a
legutóbb olvasott, hanem mindig az illesztés utolsó karaktere szerint ugrik.
Lépésszám elemzés
Az előfeldolgozás költsége Θ(m).
A keresés lépésszáma: Ο(m∙n), Ω(n/m) (lásd Quick Search algoritmus).
Megjegyzések
A Raita algoritmust a Boyer-Moore egyszerűsítéseként 1992-ben publikálták.
További leírások találhatók a Gaspard-Monge Elektronikai és Számítástechnikai Intézet
honlapján[4] (http://www-igm.univ-mlv.fr/~lecroq/string/node22.html#SECTION0220).
Reverse Colussi algoritmus
Rövid leírás
A Reverse Colussi algoritmus a Boyer-Moore algoritmus továbbfejlesztése. Egy próbán
belüli karakter-összehasonlítások a minta utolsó karakterével kezdődnek, de speciális
sorrendben folytatódnak. A Bc tömb pedig nem csak a legutóbb olvasott karakternek,
hanem az előző ugrás hosszának is a függvényében (tehát két dimenzióban) adja meg a
legnagyobb biztosan megugorható lépéshosszt.
Előfeldolgozás
A Bc tömb az abc egy eleméből és az előző ugrás hosszából álló rendezett párokhoz
rendeli hozzá a következő biztosan megugorható lépéshosszt. Ezt a következő módon
kapjuk meg: ha az előző ugrás s hosszú volt, akkor a minta (m-s-1). karakterének
illeszkednie kell a szövegre, és azt szeretnénk, hogy a mostani illesztésben a minta
utolsó karaktere is illeszkedjen. Ezért megkeressük a mintában azt a két pozíciót,
amelyek s távolságra vannak egymástól, és közülük az első megegyezik a minta (m-s-
1). karakterével, a második a minta (m-1). karakterével (14. ábra[5]).
14. ábra
A 15. ábrán[5] látható az {A;C;G;T} abc-n értelmezett GCAGAGAG mintához tartozó
Bc tömb.
15. ábra
A Gs „jó-szuffix” tömb meghatározásához szükséges néhány segédtáblázat kiszámítása.
Először meghatározzuk a minta ún. speciális karakterpozícióit. Tekintsük a minta
szuffixeit. Ha suf a minta k hosszú szuffixe, akkor ha suf máshol is szerepel a mintában,
keressük meg a minta végéhez legközelebbi olyan előfordulását, ahol az előfordulás
előtti karakter nem egyezik meg a suf előtti karakterrel (16. ábra[5]). Az ilyen szuffixeket
maximálisnak nevezzük. Minden ilyen k-ra a minta (m-1-k). pozíciója speciális.
16. ábra
A példában a k = 1;2;4 hosszú szuffixek fordulnak elő a szövegben úgy, hogy az
előfordulás előtti karakter nem egyezik a szuffix előtti karakterrel. Tehát a speciális
karakterek az (m-1-k) = 6;5;3. karakterek, vagyis a kiemeltek: GCAGAGAG .
A hmin tömbben eltároljuk, hogy a k = 1;2;4 hosszú szuffixek és a mintában talált
előfordulásuk között hány karakter távolság van. Ezt a következő módon tesszük: a k =
1-hez tartozó AG szuffix és előfordulása között 7 karakter távolság van (16. ábra[5]),
ezért hmin[7] értékét 6-ra, a k = 1-hez tartozó speciális pozícióra állítjuk. Hasonlóan a k
= 2 alapján beállított érték: hmin[4] = 5; k = 4-hez hmin[2] = 3.
Ezek után kiszámítjuk az (m-1).-nél kisebb, nem speciális pozíciókra kmin tömb
értékeit. Minden i nem speciális pozícióra kmin[i] = m, ha nincs olyan (m-1)≥j>i, hogy
m[j..(m-1)] szuffix prefixe a mintának, és kmin[i] = k, ha m[j..m-1] a legnagyobb olyan
szuffix, ami prefixe a mintának. Két példa látható a 17-18. ábrákon[5] .
17. ábra
18. ábra
Gs-t úgy töltjük fel, hogy Gs[0]-t 0-ra állítjuk. A többi elemére: először azokat a
pozíciókat írjuk bele, amelyekben hmin nem üres, azután az rmin-beli nem üres
értékeket írjuk bele sorra. Gs[m] értéke m vagy (m-1) attól függően, hogy a minta első
és utolsó karaktere megegyezik-e. A ’GCAGAGAG’ mintához tartozó táblázatok
láthatóak a 19.ábrán[5] .
19. ábra
Keresés
A próbán belüli karaktervizsgálati sorrend a következő: először az utolsó karaktert,
aztán a speciális karaktereket balról jobbra, végül a nem speciális karaktereket
vizsgáljuk meg, szintén balról jobbra haladva. Ha rögtön az első karakter-
összehasonlításnál eltérést találunk, akkor a Bc tömb alapján ugrunk az olvasott karakter
és az előző ugrás hossza alapján (az előző ugrás hosszát kezdetben tekintsük m-nek). Ha
már találtunk i>0 karakteregyezést, akkor Gs[i] értékkel ugorjunk tovább.
Lépésszám elemzés
Az előfeldolgozás Ο(m2) lépésbe kerül, a keresés Ο(n)-be. Legrosszabb esetben is csak
2∙n összehasonlításra van szükség.
Megjegyzések
A Reverse Colussi algoritmust a Boyer-Moore finomításaként 1994-ben publikálták.
További leírások találhatók a Gaspard-Monge Elektronikai és Számítástechnikai Intézet
honlapján[4] (http://www-igm.univ-mlv.fr/~lecroq/string/node17.html#SECTION0170).
A felhasználói felületRendszerkövetelmények
A bemutatóprogramok futtatásához egy a javascript futtatását támogató böngésző
szükséges. Mivel a tesztelés ezeken a felületeken történt, a következő böngészőket
ajánlom: Mozilla Firefox 3.6, Internet Explorer 6, Google Chrome 11, Konqueror,
Opera 11.
Honlaptérkép
A honlap tartalmát öt csoportra osztottam, ezek között navigálhatunk a fenti
menüsorban. A kezdőlap egy rövid szöveges eligazítást tartalmaz a honlap tárgyáról, és
a tartalmak elrendezéséről (20. ábra).
20. ábra
Az algoritmusok lap tartalmazza a javascriptes bemutató-felületeket.
A letöltés menüben találhatók a C++ forrásfile-ok, illetve a program futtatható
állományként is letölthető. Továbbá néhány tesztfile is letölthető, hogy a programot
rögtön ki lehessen próbálni.
A dokumentáció menüből letölthető a jelen dokumentummal megegyező tartalmú
részletes dokumentáció, illetve külön szerepelnek az algoritmusok működésének
részletes leírásai.
A kapcsolat lapba került a szerző köszönetnyilvánítása tanárainak, illetve a szerző
elérhetőségei.
Bemutató-felületek
Az egyes algoritmusok bemutató-felületei a következő sémára épülnek fel: olvasható
egy rövid leírás az algoritmusok működéséről (a részletes leírás a dokumentáció lapon
olvasható), alatta olvasható egy aszimptotikus lépésszám-becslés, alatta a három
ablakból álló felület (erről részletesen a következő bekezdésben), az alatt az algoritmus
C++ implementációja (21. ábra).
21. ábra
Kezdetben a három ablak mellett csak a ’mehet’ feliratú gomb szerepel. A szöveg és a
keresett sztring ablakokban egy-egy példa szöveg található, ezek tetszőlegesen
átírhatók. A mehet gombra kattintáskor a program ellenőrzi, hogy a szöveg és a keresett
sztring értékei megfelelőek-e. A keresés indításához a következő szabályoknak kell
teljesülniük:
egyik sem lehet üres
a szöveg ne legyen rövidebb, mint a sztring
a szöveg ne legyen hosszabb 80 karakternél
a sztring ne legyen hosszabb 22 karakternél
22. ábra
A felületen a ’đ’ kivételével (ezt az algoritmusok speciális karakterként használják)
bármilyen UTF-8 karakter használható, azonban a Rabin-Karp algoritmust szigorúan
szemlélve (ha szeretnénk, hogy a 95-ös számrendszerben valóban csak 0 és 94 közötti
számjegyek szerepeljenek) csak a 22. ábrán feltüntetett karakterek használhatók.
Ha valamely feltételt nem teljesítik a megadott inputok, akkor egy felugró alert
ablakban a következő hibaüzenetek egyike jelenik meg:
Hiba: hiányos adatok.
Hiba: nem megengedett 'đ' karakter.
Hiba: hosszabb minta, mint szöveg.
Hiba: túl hosszú szöveg.
Ha a megadott bemenet teljesíti az elvárásokat, akkor a mehet gombra kattintva a jobb
oldalon a következőhöz hasonló képet kell kapnunk:
23. ábra
A kijelzőn egymás alatt megjelenik a szöveg, és a keresett sztring, és az üzenet, hogy a
0. lépésnél járunk, és természetesen még nem találtuk meg a keresett szót a szövegben.
Ha az -> (előre) gombra kattintunk, akkor az algoritmus tesz egy lépést előre, és
megjeleníti az aktuális állást a kijelzőn. Az első lépés után megjelennek az
előfeldolgozáshoz kapcsolódó információk, és ha legalább az 1. lépésnél járunk, akkor
megjelenik a <- (vissza) gomb, amivel szintén egyesével tudunk lépni az algoritmussal,
csak visszafelé (24. ábra).
24. ábra
Ha a játszd gombra kattintunk, akkor a program elkezdi 0,4 mp-ként léptetni az
algoritmust. Egyúttal a kijelző alatt a lassít, állj, és gyorsít gombok jelennek meg.
Lassítani, gyorsítani 3,2 és 0,1 mp-kénti léptetésig lehet. Az állj gombbal az algoritmus
aktuális állásától folytathatjuk a futtatást.
Ha egy futtatás során a mehet gombra kattintunk, akkor az a szöveg és sztring
ablakokban lévő aktuális értékekkel újra kezdi a futtatást.
Az algoritmusok összehasonlítása lapon léptetésre nincs lehetőség, ott csak az
eredményt tudjuk megszemlélni (25. ábra).
25. ábra
Fejlesztői dokumentációA webes felület szerkezeteAz algoritmusok bemutató-felületei
Osztályszerkezet
Minden bemutató-felület html headerjében először a general.js, majd az adott
algoritmushoz tartozó javascript file szerepel scriptként, végül az aktAlg változó értéke
inicializálása történik (26. ábra).
26. ábra
A general.js scriptben a gombok láthatóságának beállításai, az inputok ellenőrzése, és
azok a változók és függvények vannak, amiket minden algoritmus azonos módon
használ.
Az adott algoritmushoz tartozó script egy osztály leírását és annak példányosítását
tartalmazza. Az osztályszerkezetbe való rendezés azért szükséges, mert a különböző
algoritmusoknak vannak azonos nevű függvényei (init, elore), és az algoritmusok
összehasonlítása lapon szükséges ezen függvények egymás melletti használata. Az
algoritmus osztályok a C++ programban származtatva vannak egy ősosztályból, míg a
javascriptes programban a general.js-beli függvények globálisak, a konkrét
algoritmusok init, és elore függvényei az adott osztályhoz tartoznak (27. ábra).
27. ábra
Minden algoritmus-osztály tartalmaz egy init( ) és egy elore( ) függvényt. Ezeknek a
függvényeknek (megfelelő absztrakcióval) ugyanaz a funkciójuk minden algoritmusra.
Az egyes algoritmusokhoz tartozó osztályok leírásánál az itt leírt működést nevezem
alapértelmezettnek, ennek csak a kiegészítését, vagy ettől való eltéréseket nevezem
meg.
Az init( ) függvény az osztály változóit inicializálja.
Az elore( ) függvény megnöveli a lepes változó értékét, megfelelő esetben az osszlepes
változóét is, majd egy karakter-összehasonlításnyit lép: megvizsgálja, hogy a megfelelő
karakterek egyeznek-e, és ez alapján vagy az akt vagy a jo változó értékét növeli az
adott algoritmusnak megfelelő módon. Ha a jo változó értéke eléri a minta hosszát,
akkor teljes egyezést találtunk, ekkor a db változót, 1-gyel növeli. A függvény a lépés
után eltárolja, és kiírja az aktuális állapotot, és ellenőrzi, hogy a keresés végére értünk-e.
Ha igen, meghívja a general.js leírásában már ismertetett vegen( ) függvényt. A
függvény kezeli, ha a lejátszási módban vagyunk, vagy az összehasonlító felületről
hívjuk a scriptet, ilyenkor újra meghívja az elore( ) függvényt.
general.js
fontosabb változók:
aktAlg az aktuális algoritmus objektuma
txt a szöveg amiben keresünk
txtHTML a szövegnek, amiben keresünk a html kompatibilis verziója
str a minta amit keresünk
strHTML a mintának, amit keresünk a html kompatibilis verziója
lepes a kijelzőn lévő állapot hányadik karakter-összehasonlításnál jár
osszlepes az eddig végrehajtott összes karakter-összehasonlítások száma
akt azon karakter sorszáma, ahova a mintát illeszteni próbáljuk
jo az adott illesztésen egyező karakterek száma
db az eddig talált teljes illeszkedések száma
allapot az eddigi lépések tárolására szolgál
fontosabb függvények:
kezd( ) a mehet gomb kezelése
elore( ) az előre gomb kezelése
vissza( ) a vissza gomb kezelése
gyorsit( ) a gyorsít gomb kezelése
lassit( ) a lassít gomb kezelése
inditallit( ) a játszd/állj gomb kezelése
itoa(i) int -> char konverzió
atoi(a) char -> int konverzió
textToHtml szöveg -> HTML felületen megjeleníthető szöveg konverziót
vegen() a felület gombjainak láthatóságát kezeli, ha a keresés végére értünk
feladat:
A general.js-ben történik a felhasználói dokumentációban ismertetett gombok és
ablakok kezelése, az input adatok ellenőrzése, itt vannak megírva a globális
segédfüggvények, itt történik a globális változók inicializációja. A globális változók, és
a program futásában betöltött szerepük a general.js elején található.
Az aktAlg változó egy algoritmus-osztály objektuma, a feladata, hogy a general.js-ben
meghívott aktAlg.init( ) aktAlg.elore( ) függvényeknél az adott algoritmushoz tartozó
init és elore függvényeket hívja meg.
A 28. és 29. ábrákon láthatók a felület ablakaihoz, gombjaihoz (id alapján) rendelt
változónevek.
28. ábra
29. ábra
A gombra kattintással meghívható függvények a kezd( ), elore( ), vissza( ), gyorsit( ),
lassit( ), inditallit( ). A függvények mindig kezelik a felület gombjainak láthatóságát, a
felirataikat.
A mehet gomb a general.js kezd( ) függvényét hívja meg. A kezd( ) kezeli a gombok
láthatóságát (láthatóvá teszi a kezdetben rejtett eloreGomb és jatszdGomb gombokat),
beolvassa a txt és str értékeket, hibás értékek esetén egy alert ablakban hibaüzenetet
dob. Végül, ha az input adatok megfelelőek, meghívja az aktAlg.init( ) függvényt,
vagyis a futtatandó algoritmushoz tartozó objektum init( ) függvényét.
Az eloreGomb segítségével az aktAlg.elore( ) függvényt lehet hívni, a visszaGomb-bal
pedig a lepes változót csökkentjük eggyel, és kiírjuk a megfelelő lépés állapotát.
A gyorsitGomb, lassitGomb a lejátszás sebességét állítja a coolDown változón
keresztül. A jatszdGomb-on keresztül pedig azt állíthatjuk, hogy automatikusan
(coolDown ms időközönként) lépdeljen-e az algoritmus, vagy álljon, és várjon, amíg az
előre vagy vissza gombokra nem kattintunk.
naive.js
függvények:
init( )
elore( )
feladat:
Az init( ) (mivel a Brute Force algoritmus előfeldolgozást nem végez) csak az allapot[0]
értékét állítja be.
Az elore( ) függvény a megfelelő karakterek egyezése alapján vagy az akt vagy a jo
változó értékét növeli 1-gyel. Ha az akt változó értékét (azaz az illesztés pozícióját)
növeli, akkor egyúttal a jo (azaz a pozíción egyező karakterek számának) értékét 0-ra
állítja. Ha a jo változó értéke eléri a minta hosszát, akkor teljes egyezést találtunk, ekkor
a pozíciót az előbb leírt módon 1-gyel növeli.
kmp.js
változók:
next az előfeldolgozás next tömbje
nextVolt a next tömb feltöltése megtörtént-e
nextDB a next tömb feltöltése hány lépésbe telt
ugras a kijelzőre írandó információt tartalmazza a következő illesztés helyéről
függvények:
azonos(s,elso,masodik,h)
init( )
elore( )
feladat:
Az KMP algoritmus-osztály segédfüggvénye az azonos(s,elso,masodik,h), ami
megvizsgálja, hogy azonosak-e az s szting elso, illetve masodik karakterétől kezdődő h
hosszú részsztringjei, illetve (mivel a a függvényt csak a next tömb feltöltés során
használjuk) a nextDB értékét összehasonlításonként növeli.
Az elore( ) függvény a nextVolt értékétől függően feltölti a next tömböt, vagy egy
karakter-összehasonlítással tovább lép az algoritmus futásában. Az algoritmus
működésének megfelelően, ha az akt értékét növeli, akkor azt t = (jo – next[jo])-val
teszi, majd a jo értékét t-vel növeli.
qs.js
változók:
shift az előfeldolgozás shift tömbje
shiftVolt fel lett-e töltve a shift tömb
shiftDB hány lépésből állt a shift tömb feltöltése
shiftDisplay a shift tömb megjelenítendő részét tartalmazza
ugras a kijelzőre írandó információt tartalmazza a következő illesztés helyéről
függvények:
init( )
elore( )
feladat:
A Quick Search algoritmus elore( ) függvénye a shiftVolt értékétől függően feltölti a
shift tömböt, vagy egy karakter-összehasonlítással tovább lép az algoritmus futásában.
Az algoritmus működésének megfelelően, ha az akt értékét növeli, akkor azt t =
shift[atoi(txt.charAt(akt+str.length))]-val teszi, majd a jo értékét 0-ra állítja.
rk.js
változók
strSzam a szöveg aktuális m karakteréből képzett szám
aktSzam a mintából képzett szám
modulus milyen modulussal dolgozunk
d milyen számrendszerben dolgozunk
ellenorzes találatot ellenőrzünk-e
msg2 a kijelzőre írja, ha találatot ellenőrzünk
függvények:
value(c)
init( )
elore( )
feladat:
A Rabin-Karp algoritmus-osztály segédfüggvénye a value(c) függvény, ami egy adott
karaktert d számrendszerbeli számjeggyé konvertál.
Az init( ) függvény kiszámolja az strSzam értékét, illetve a szöveg első (str.length-1)
karakteréből képzett számot. Azért csak az első (str.length-1) karaktert dolgozza fel,
hogy az elore( ) függvénynek ne kelljen elágazni a szerint, hogy a 0. karaktertől
kezdődő pozícióra illeszti-e a mintát, vagy egy későbbire. Végül beállítja az allapot[0]
értékét.
Az elore( ) függvény (az algoritmus elve miatt) a jo változót nem használja, ellenben az
ellenorzes változó igaz/hamis volta alapján elágazik.
so.js
változók:
s az abc karaktereihez tartozó bitvektorokat tartalmazó mátrix
bits az aktuális bitvektor
lastZero a bits vektor utolsó nullás pozíciója
tablaDB hány lépésből állt az inicializálás
függvények:
init( )
elore( )
feladat:
A shift-or algoritmus init( ) függvényében történik az s mátrix feltöltése, és allapot[0]
értékének beállítása.
Az elore( ) függvény (az algoritmus elve miatt) a jo változót nem használja, és itt az akt
változó az illesztési pozíció utolsó karakterét jelenti.
bm.js
változók:
tabla1 a jó-szuffix tömb
tabla2 a hibás-karakter tömb
tablaVolt fel lettek-e töltve a tabla tömbök
tablaDB hány lépésből állt a tabla tömbök feltöltése
checked a tabla1-beli (goodSuffix) értékkel lépve milyen hosszú karaktersorról
tudjuk, hogy egyezik a mintával
checkedFrom a már ismert mintaegyezés kezdeti karaktere
checkedTo a már ismert mintaegyezés utolsó karakterének pozíciója + 1
ugras kijelzőre írandó információt tartalmazza a következő illesztés helyéről
függvények:
azonos(s,elso,masodik,h)
hova(j)
init( )
elore( )
feladat:
A Boyer-Moore algoritmushoz tartozó osztály változói a checked, checkedFrom,
checkedTo változók, amik arra szolgálnak, hogy a tabla1-beli (jó-szuffix) értékkel lépve
milyen hosszú karaktersorról tudjuk, hogy egyezik a mintával, és ez az egyezés a minta
(checkedFrom). karakterétől a (checkedTo-1). karakteréig tart.
Az osztály segédfüggvénye az azonos(s,elso,masodik,h). Ez a kmp.js-beli azonos
függvénytől annyiban tér el, hogy a speciális ’đ’ karaktert minden karakterrel
egyezőnek tekinti. Erre a tabla1 (jó-szuffix) tömb feltöltésénél van szükség, ugyanis az
(felhasználói dokumentációban részletesen leírt) algoritmus keresi a minta olyan
szuffixeit, amik akár csak részben is illeszkednek a mintára (lásd 12. ábra, a * helyén ’đ’
karakterrel). Másik segédfüggvénye a hova(j), ami az eddig vizsgált karakterek
számából megadja, hogy hányadik karaktert kell vizsgálni következőnek.
Az elore( ) függvény a tablaVolt értékétől függően feltölti a tabla tömböket, vagy egy
karakter-összehasonlítással tovább lép az algoritmus futásában. Az algoritmus
működésének megfelelően, ha az akt értékét növeli, akkor (tabla1[jo]) és
(tabla2[atoi(txt.charAt(akt+str.length-jo-1))] – jo) értékeke közül a nagyobbal növeli
azt. Ha tabla1 alapján ugrik, akkor beállítja a checked, checkedForm, checkedTo
értékeket. Ha a jo változó értékét növeli, akkor meg kell vizsgálni, hogy eljutott-e már
vizsgált (checked) karakterekig, mert ebben az esetben azokat nem kell újra vizsgálni,
hanem azokat átugorva a jo értékét a checked-ben tárolt értékkel kell növelni.
bmh.js
változók:
tabla2 a hibás-karakter tömb
tablaVolt fel lettek-e töltve a tabla tömbök
tablaDB hány lépésből állt a tabla tömbök feltöltése
ugras kijelzőre írandó információt tartalmazza a következő illesztés helyéről
függvények:
init( )
elore( )
feladat:
A Horspool (Boyer-Moore-Horspool) algoritmushoz tartozó elore( ) függvény a
tablaVolt értékétől függően feltölti a tabla2 tömböt, vagy egy karakter-
összehasonlítással tovább lép az algoritmus futásában. Az algoritmus működésének
megfelelően, ha az akt értékét növeli, azt tabla2[atoi(txt.charAt(akt+str.length-1))]-gyel
teszi.
raita.js
változók:
tabla2 a hibás-karakter tömb
tablaVolt fel lett-e töltve a tabla tömb
tablaDB hány lépésből állt a tabla tömb feltöltése
zold a kijelzőn mely karakterek tűnjenek fel zöldben
b jegyzi, hogy a középső karaktert vizsgáltuk-e már kétszer
ugras kijelzőre írandó információt tartalmazza a következő illesztés helyéről
függvények:
hova(j)
init( )
elore( )
feladat:
A Raita algoritmushoz tartozó elore( ) függvény a tablaVolt értékétől függően feltölti a
tabla2 tömböt, vagy egy karakter-összehasonlítással tovább lép az algoritmus futásában.
Az algoritmus működésének megfelelően, ha az akt értékét növeli,
tabla2[atoi(txt.charAt(akt+str.length-1))]-gyel teszi. Segédfüggvénye a hova(j), ami az
eddig vizsgált karakterek számából megadja, hogy hányadik karaktert kell vizsgálni
következőnek.
rc.js
változók:
tablaVolt volt-e már előfeldolgozás
tablaDB hány lépésből állt az előfeldolgozás
ugras kijelzőre írandó információt tartalmazza a következő illesztés helyéről
h a mintaillesztés karaktereinek bejárási sorrendjét tartalmazó tömb
zold a kijelzőn mely karakterek tűnjenek fel zöldben
last előzőleg mekkorát ugrottunk
hmin rmin meghatározásához szükséges tömb
rmin Gs meghatározásához szükséges tömb
Gs az algoritmus jó-szuffix tömbje
Bc az algoritmus hibás-karakter mátrixa
tmpL segédváltozó
függvények:
azonos(s,elso,masodik,h)
hova(j)
init( )
elore( )
feladat:
A Reverse Colussi algoritmushoz tartozó osztály segédfüggvénye az
azonos(s,elso,masodik,h). Ez a kmp.js-beli azonos függvénytől annyiban tér el, hogy a
speciális ’đ’ karaktert minden karakterrel egyezőnek tekinti. Erre a tabla1 (jó-szuffix)
tömb feltöltésénél van szükség, ugyanis az (felhasználói dokumentációban részletesen
leírt) algoritmus keresi a minta olyan szuffixeit, amik akár csak részben is illeszkednek
a mintára (lásd 12. ábra, a * helyén ’đ’ karakterrel). Másik segédfüggvénye a hova(j),
ami az eddig vizsgált karakterek számából megadja, hogy hányadik karaktert kell
vizsgálni következőnek.
Az elore( ) függvény a tablaVolt értékétől függően feltölti a h, Gs, és Bc tömböket (és
az ehhez szükséges rmin és hmin segédtömböket), vagy egy karakter-összehasonlítással
tovább lép az algoritmus futásában. Ha akt értékét növeli és jo értéke 0, akkor
Bc[atoi(txt.charAt(akt+this.hova(jo)))][last]-tal növeli azt, ha jo ≠ 0, akkor Gs[jo]-val.
Minden ugrásnál last értékét az ugrás hosszára állítja.
Az algoritmusok összehasonlítása lap
A summary.html, az algoritmusok összehasonlítása lap a többitől eltérően a general2.js-
t használja a general.js helyett, és az összes algoritmushoz tartozó javascript file-t és a
summary.js file-t is használja (30. ábra).
30. ábra
general2.js
fontosabb változók:
aktAlg az aktuális algoritmus objektuma
txt a szöveg amiben keresünk
txtHTML a szövegnek, amiben keresünk a html kompatibilis verziója
str a minta amit keresünk
strHTML a mintának, amit keresünk a html kompatibilis verziója
lepes a kijelzőn lévő állapot hányadik karakter-összehasonlításnál jár
osszlepes az eddig végrehajtott összes karakter-összehasonlítások száma
akt azon karakter sorszáma, ahova a mintát illeszteni próbáljuk
jo az adott illesztésen egyező karakterek száma
db az eddig talált teljes illeszkedések száma
allapot az eddigi lépések tárolására szolgál
fontosabb függvények:
kezd( ) a mehet gomb kezelése
itoa(i) int -> char konverzió
atoi(a) char -> int konverzió
textToHtml szöveg -> HTML felületen megjeleníthető szöveg konverziót
feladat:
A general2.js tartalmában alig tér el a general.js-től. Kezeli a gombokat (amelyekből
ezen az oldalon kevesebb van), tartalmazza az algoritmusok futásához szükséges
globális változókat és segédfüggvényeket. Az egyetlen lényegi eltérés, hogy kezeli, ha
az osszesit változó igazra van állítva, ekkor a folytonos változót igazra állítja.
summary.js
változók:
osszesit globális változó, melyet az egyes algoritmusok scriptjei használnak, hogy
az összesítéshez megfelelően viselkedjenek
mainout a kijelző
tmpout segédsztring
függvények:
kiir(nev, a, b, c)
mindentMost( )
feladat:
A script tartalmazza a mehet gomb által meghívott mindentMost( ) függvényt, ami
egyesével minden egyes algoritmust lejátszik 0 ms-os lépésközzel egy rejtett kijelzőn,
és a keresések eredményeit (előfeldolgozás lépésszáma, keresés lépésszáma, találatok
száma) kiírja a megjelenített kijelzőn, a kiir(nev,a,b,c) segédfüggvény használatával.
A C++ program szerkezeteA program a 29. ábrán látható módon az algoritmusok egy ősosztályából, az ebből
származtatott osztályokból, és a főprogramból áll.
31. ábra
Az algoritmusok osztályban az elofeldolgoz( ) és futtat( ) függvények tisztán
virtuálisak, ezek minden alosztályban külön definiálva vannak. A kiir függvény az
eredmények konzolra való kiírását végzi el.
A protected adattagok az elodb, futdb – ezek az előfeldolgozás és a futtatás során
végzett karakter-összehasonlítások számát tárolják, a talalt adattag a teljes illeszkedések
számát tárolja, a txt, str, és nev sztringek pedig a szöveget, a mintát, és az algoritmus
nevét tárolják (utóbbira csak a kiíratáshoz van szükség). A konstruktor a nev-en kívül az
összes adattagot értelemszerűen inicializálja.
Az egyes alosztályok az adott algoritmus előfeldolgozási és keresési algoritmusainak
implementációját tartalmazzák, illetve az ezekhez szükséges változókat,
segédfüggvényeket. Konstruktoruk meghívja az ősosztály konstruktorát, és inicializálja
az algoritmusspecifikus adattagokat, illetve a nev sztringet.
A főprogram megvizsgálja, hogy kapott-e paraméterként egy input és egy output file-t,
ha nem, akkor konzolon bekéri ezeket, majd az inputfile-ból #-ig olvassa a szöveget, a
következő #-ig a mintát. Ezek után mind a 9 algoritmusosztályt példányosítja, meghívja
az elofeldolgoz, a futtat, végül a kiir függvényeit.
Osztályok
Az egyes osztályok metódusai, adattagjai, és működésük rövid leírása szerepel itt. A
konstruktorokat nem szerepeltetem, mindegyiknek t és s a szöveg és a keresett minta a
paraméterei, és az adattagok inicializálást tartalmazza. Az elofeldolgoz( ) és a futtat( )
metódusok értelemszerűen az algoritmus megfelelő részeit futtatják, és ez alapján
számítják ki a segéd vectorok, változók értékeit, illetve végzik el az algoritmus lépéseit.
Amennyiben működésük nem tér el a felhasználói dokumentációban ismertetettektől,
nem térek ki rájuk.
algoritmus
metódusok:
virtual void elofeldolgoz( ) = 0 ;
virtual void futtat( ) = 0 ;
void kiir(ostream& out)
adattagok:
string txt;
string str;
int elodb;
int futdb;
int talalat;
string nev;
feladat:
Az ősosztály. Azokat a metódusokat és adattagokat tartalmazza, amelyek minden
algoritmus működéséhez szükségesek.
naive
metódusok:
void elofeldolgoz( )
void futtat( )
feladat:
A Brute Force algoritmus felhasználói dokumentációban ismertetett működésének
megfelelően az előfeldolgoz( ) nem csinál semmit, a futtat elvégzi a keresést.
kmp
metódusok:
bool azonos(string s, int elso, int masodik, int h)
void elofeldolgoz( )
void futtat( )
adattagok:
vector<int> next
feladat:
A KMP algoritmus osztályának segédfüggvénye az azonos(s,elso,masodik,h), ami
megvizsgálja, hogy azonosak-e az s szting elso, illetve masodik karakterétől kezdődő h
hosszú részsztringjei. A next vector str.length( )+1 méretű.
qs
metódusok:
void elofeldolgoz( )
void futtat( )
adattagok:
vector<int> shift
feladat:
A Quick Search algoritmus osztályának segédtömbje a shift vector 256 méretű.
rk
metódusok:
void elofeldolgoz( )
void futtat( )
adattagok:
int strNum
int txtNum
feladat:
A Rabin-Karp algoritmus osztályának adattagjai az strNum és txtNum a mintához,
illetve a szöveg vizsgált részletéhez tartozó számokat tartalmazza a futás során.
so
metódusok:
void elofeldolgoz( )
void futtat( )
adattagok:
vector<int> bittomb;
int bitvektor;
feladat:
A shift-or algoritmusban a bittomb a karakterekhez tartozó bittérképeket tartalmazza
(tehát egy vector mátrixról van szó), a bitvektor a futás során aktuális karakterhez
tartozó bitvektort tartalmazza (lásd felhasználói dokumentáció).
bm
metódusok:
void elofeldolgoz( )
void futtat( )
bool azonos(vector<int> s, int elso, int masodik, int h);
int hova(int j);
adattagok:
vector<int> gs;
vector<int> bc;
int checked;
int checkedFrom;
int checkedTo;
int jo;
feladat:
A Boyer-Moore algoritmushoz tartozó osztály változói a checked, checkedFrom,
checkedTo változók, amik arra szolgálnak, hogy gs-beli (jó-szuffix) értékkel lépve
milyen hosszú karaktersorról tudjuk, hogy egyezik a mintával, és ez az egyezés a minta
(checkedFrom). karakterétől a (checkedTo-1). karakteréig tart. A jo az illesztésen már
megtalált karakteregyezések számát tárolja. A bc a hibás-karakter tömböt tárolja.
Az osztály segédfüggvénye az azonos(s,elso,masodik,h). Ez a kmp osztálybeli azonos
függvénytől annyiban tér el, hogy a speciális 0 kódú karaktert minden karakterrel
egyezőnek tekinti. Erre gs vector feltöltésénél van szükség, ugyanis az (felhasználói
dokumentációban részletesen leírt) algoritmus keresi a minta olyan szuffixeit, amik akár
csak részben is illeszkednek a mintára (lásd 12. ábra). Másik segédfüggvénye a hova(j),
ami az eddig vizsgált karakterek számából megadja, hogy hányadik karaktert kell
vizsgálni következőnek.
bmh
metódusok:
void elofeldolgoz( )
void futtat( )
adattagok:
vector<int> bc;
feladat:
A Horspool algoritmus adattagja a bc (hibás-karakter) tömb értékét az elofeldolgoz( )
számolja ki.
raita
metódusok:
void elofeldolgoz( )
void futtat( )
int hova(int j)
adattagok:
vector<int> bc;
int b;
vector<bool> checked;
feladat:
A Raita algoritmus adattagjai a bc (hibás-karakter) tömb, a b változó azt tárolja, hogy a
középső elemet vizsgáltuk-e már kétszer, az str.length() méretű checked vector a minta
karaktereire tárolja, hogy illeszkednek-e az illesztésen.
rc
metódusok:
void elofeldolgoz( )
void futtat( )
int hova(int j);
bool azonos(vector<int> s, int elso, int masodik, int h);
bool azonos(string s, int elso, int masodik, int h);
adattagok:
vector<int> h;
vector<bool> checked;
int last;
vector<int> hmin;
vector<int> rmin;
vector<int> gs;
vector< vector<int> > bc;
int jo;
feladat:
A Reverse Colussi algoritmus adattagjai a felhasználói dokumentációban ismertetett h,
hmin, rmin, gs (jó-szuffix), bc (hibás-karakter) tömböknek felelnek meg, a jo, last
checked segédváltozók az rc.js scriptbeliekkel azonosan működnek. Az algoritmushoz
tartozó segédfüggvény az azonos(s,elso,masodik,h). Ez a kmp osztály azonos
függvénytől annyiban tér el, hogy a speciális 0 kódú karaktert minden karakterrel
egyezőnek tekinti. Erre gs feltöltésénél van szükség, ugyanis az (felhasználói
dokumentációban részletesen leírt) algoritmus keresi a minta olyan szuffixeit, amik akár
csak részben is illeszkednek a mintára (lásd 12. ábra). Másik segédfüggvénye a hova(j),
ami az eddig vizsgált karakterek számából megadja, hogy hányadik karaktert kell
vizsgálni következőnek.
A program teszteléseModultesztelés
Feketedoboz-tesztelés
A feketedoboz-tesztelés során a következő általános teszteseteket vizsgáltam:
hibaüzenet kiváltása (túl hosszú szöveg, illegális karakter használata, rövidebb
szöveg, mint minta, üres mező)
szöveg: A
minta: AA
szöveg:
minta: AA
szöveg: AđAAđAA
minta: AđAA
szöveg:ABABABBBABACABAADABACACABBADABBABABABADADCAABB
ABABBABBABAAABACAABABADABBABAACABBABADABBACABABBA
minta: ABABB
annak az ellenőrzése, hogy tévesen átugrunk-e teljes egyezéseket (ismétlődéses
minta)
szöveg: ABBABBABBABB
minta: ABBABB
szöveg: AAAAAAA
minta: AAA
szöveg: ABBBABBBABBBA
minta: ABBBA
szövegben nem szereplő minta
szöveg: ABACABABABAA
minta: ABABAC
szöveg: ABADABA
minta: ABAB
a szöveg legvégén/legelején lévő teljes egyezés
szöveg: ABABABABABABAC
minta: ABABAC
szöveg: AAABAABABAAB
minta: AAA
majdnem teljes egyezés
szöveg: ABABABABABABAB
minta: ABABAC
szöveg: AABAABBBAA
minta: AAA
Fehérdoboz-tesztelés
A fehérdoboz-tesztelés során az algoritmusokhoz egyesével olyan tesztadatokat
készítettem, hogy minden programrészen legalább egyszer végigfusson. Az
algoritmusoknál megnevezem a futás lehetséges ágait, és az ágakhoz megmutatom,
hogy hol futnak a példában. Az algoritmusok triviális ágaihoz (karaktereltérés,
karakteregyezés, teljes illeszkedés) nem jelölöm külön a futás helyét. A tesztadatok:
Brute Force
szöveg: CABABABCA
minta: ABC
Az algoritmus ágai a karaktereltérés, a karakteregyezés, és a teljes illeszkedés.
KMP
szöveg: ABABABACADA
minta: ABABAC
Az algoritmus ágai az előfeldolgozás során az (1) egyesével növekedő next tömb, és a
(2) csökkenő. A keresés során az ágak a karaktereltérés, a karakteregyezés, és a teljes
illeszkedés.
1: next[3] = 1; next[4] = 2
2: next[5] = 3; next[6] = 0
Quick Search
szöveg: AAAAXAAXAA
minta: AAA
Az algoritmus ágai a (1)mintában nem szereplő karakter alapján történő ugrás, és a
(2)mintában szereplő karakter alapján történő ugrás, illetve a karaktereltérés, a
karakteregyezés, és a teljes illeszkedés.
1: AAAAX
AAA
2: AAAAXAAXAA
AAA
Rabin-Karp
szöveg: ie?8ajd@_?b/c=cdd
minta: ie?8
Az algoritmus egyetlen ága, ami nem fut le minden futtatásnál, a (1) számok egyezése
utáni ellenőrzés (32. ábra).
32. ábra
shift-or
szöveg: ABABABACABAB
minta: ABABAC
Az algoritmus egyetlen ága, ami nem fut le minden futtatásnál, a (1) teljes illeszkedés.
Boyer-Moore
szöveg: BAAABAABAABACCCAABA
minta: ABAAB
Az algoritmus ágai: (1) teljes illeszkedés utáni ugrás Gs tömb alapján, (2) karakter-
eltérés utáni ugrás Bc tömb alapján, (3) karaktereltérés utáni ugrás Gs tömb alapján,
illetve a karaktereltérés, a karakteregyezés, és a teljes illeszkedés.
1: BAAABAABAABACCCAABA
ABAAB
2: BAAABAABAABACCCAABA
ABAAB
3: BAAABAABAABACCCAABA
ABAAB
Horspool
szöveg: AAAABBBABAB
minta: ABAB
Az algoritmus ágai az első karakter-összehasonlítás utáni, és a többedik összehasonlítás
utáni karaktereltérés, a karakteregyezés, és a teljes illeszkedés. (Első összehasonlítás
utáni teljes egyezéshez 1 karakterből álló mintával kellett kipróbálni.)
Raita
szöveg: AAAABBBABAB
minta: ABAB
Az algoritmus ágai a karaktereltérés, a karakteregyezés, és a teljes illeszkedés.
Reverse Colussi
szöveg: BAAABAABAABACCCAABA
minta: ABAAB
Az algoritmus ágai az első karakter-összehasonlítás utáni, és a többedik összehasonlítás
utáni karaktereltérés, illetve a karakteregyezés, és a teljes illeszkedés.
Rendszertesztelés
A rendszertesztelés során a felületet kipróbáltam különböző felbontású képernyőn
(1280x800; 1024x768; 800x600), különböző operációs rendszerek alatt (Ubuntu,
Windows Xp, Windows7), és különböző böngészők alatt (Mozilla Firefox, Internet
Explorer, Chrome, Konqueror, Opera). A program több fázisban is átesett
rendszertesztelésen, így a fejlesztés korai szakaszában felmerülő hibák javításához nem
kellett minden egyes javascript vagy html file-t újraírni.
A felmerült hibák, és javításaik: javascriptben szöveges változó i. elemére való
hivatkozásnál az s[i] jelölés Explorer alatt nem működött, ezért az s.charAt(i) jelölésre
tértem át.
Ha a javascriptes felületen egy keresést megállítottam, az input mezők tartalmát hibásra
átírtam, majd a mehet gombra kattintottam, akkor a hibaüzenet után még az előző
példán lehetett előre, és visszafele lépkedni, azonban néhány változó inicializálása miatt
az eredmény elromlott. Ezt a general.js scriptbeli utasítások sorrendjének átrendezésével
javítottam ki.
A Boyer-Moore algoritmus felületénél túl hosszú minta esetén a jó-szuffix tömb kiírása
egy helyett két sort vesz igénybe, ezért a kijelző magasságát nagyobbra kellett állítani,
hogy minden információ megfelelően megjelenjen.
ÖsszefoglalásAz elkészült programok lehetőséget adnak az algoritmusok gyors megismeréséhez és
megértéséhez, lefedik az Algoritmusok és adatszerkezetek II. tárgy idevonatkozó
anyagát, azonban több irányba is bővíthető, javítható.
Természetesen az algoritmusok száma tekintetében bővíthetők a programok, ilyen
jellegű fejlesztést a programok struktúrája könnyen elérhetővé is tesz.
Az egyik legtermészetesebb bővítési lehetőség az előfeldolgozás szemléltetése. Ez
sajnos algoritmusonként egyedi felületet igényelne, ezért ez túlmutat jelen dolgozat
keretein.
Az érthetőség további javítása szempontjából a stuktogramok megjelenítése, esetleg
azon való léptetés, a stuktogram megfelelő celláinak kiemelése segíthetne. Hasonlóan
szemléletes lehet a C++ kód az aktuális lépéshez vonatkozó sorainak kiemelése.
Mindezek mellett reményeim szerint egy alapos, átfogó, és könnyen értelmezhető
felület született.
.
Irodalomjegyzék
[1] Fekete István: Mintaillesztés (egyetemi jegyzet)
http://people.inf.elte.hu/fekete/docs_2/mintaill/Mintaillesztes.pdf
(2011-05-12)
[2] Thomas H. Cormen, Charles E. Leiserson, Ronald L. Rivest: Algoritmusok,
Műszaki Könyvkiadó, 1997, [884], ISBN-963-16-3029-3
[3] http://en.wikipedia.org/wiki/String_matching (2011-05-12)
[4] Christian Charras, Thierry Lecroq: Handbook of Exact String Matching Algorithms,
http://www-igm.univ-mlv.fr/~lecroq/string/
(2011-05-12)
[5] Y. K. Shie, R. C. T. Lee: Reverse Colussi.ppt,
http://alg.csie.ncnu.edu.tw/course/StringMatching/Reverse%20Colussi.ppt
(2011-05-12)