Karakterlánc-kereső algoritmusok nagy szövegekhez Java-val

1. Bemutatkozás

Ebben a cikkben számos algoritmust mutatunk be egy minta keresésére egy nagy szövegben. Leírjuk az egyes algoritmusokat a megadott kóddal és egyszerű matematikai háttérrel.

Vegye figyelembe, hogy a megadott algoritmusok nem a legjobb módja a teljes szöveges keresésnek a bonyolultabb alkalmazásokban. A teljes szöveges keresés megfelelő elvégzéséhez használhatjuk a Solr vagy az ElasticSearch alkalmazást.

2. Algoritmusok

Kezdjük egy naiv szöveges keresési algoritmussal, amely a leg intuitívabb, és segít felfedezni az adott feladattal kapcsolatos egyéb speciális problémákat.

2.1. Segítő módszerek

Mielőtt elkezdenénk, definiáljunk egyszerű módszereket a Rabin Karp algoritmusban használt prímszámok kiszámításához:

public static long getBiggerPrime (int m) {BigInteger prime = BigInteger.probablePrime (getNumberOfBits (m) + 1, új Random ()); return prime.longValue (); } privát statikus int getNumberOfBits (int szám) {return Integer.SIZE - Integer.numberOfLeadingZeros (szám); } 

2.2. Egyszerű szöveges keresés

Az algoritmus neve jobban leírja, mint bármely más magyarázat. Ez a legtermészetesebb megoldás:

public static int simpleTextSearch (char [] minta, char [] text) {int patternSize = minta.hossz; int textSize = szöveg.hossz; int i = 0; míg (((i + patternSize) = patternSize) visszatér i; } i + = 1; } return -1; }

Ennek az algoritmusnak az ötlete egyszerű: ismételje meg a szöveget, és ha egyezik a minta első betűje, ellenőrizze, hogy a minta összes betűje megegyezik-e a szöveggel.

Ha m a mintában szereplő betűk száma, és n a betűk száma a szövegben, az algoritmusok időbeli összetettsége O (m (n-m + 1)).

Legrosszabb eset az a esetén fordul elő Húr sok részleges előfordulás:

Szöveg: baeldunbaeldunbaeldunbaeldun Minta: baeldung

2.3. Rabin Karp algoritmus

Mint fentebb említettük, az Egyszerű szöveges keresés algoritmus nagyon nem hatékony, ha a minták hosszúak, és ha a minta sok ismétlődő elemet tartalmaz.

A Rabin Karp algoritmus ötlete az, hogy kivonatolással keressen mintát a szövegben. Az algoritmus elején ki kell számolnunk a minta hash-ját, amelyet később az algoritmusban használnak. Ezt a folyamatot ujjlenyomat-számításnak hívják, itt részletes magyarázatot találhatunk.

Az előfeldolgozási lépésben az a fontos, hogy időbeli összetettsége igen O (m) és a szöveges iteráció eltart Tovább) amely az egész algoritmus időbeli összetettségét adja O (m + n).

Az algoritmus kódja:

public static int RabinKarpMethod (char [] minta, char [] text) {int patternSize = minta.hossz; int textSize = szöveg.hossz; hosszú prím = getBiggerPrime (patternSize); hosszú r = 1; mert (int i = 0; i <patternSize - 1; i ++) {r * = 2; r = r% prim; } long [] t = új long [textSize]; t [0] = 0; hosszú pfinger = 0; for (int j = 0; j <patternSize; j ++) {t [0] = (2 * t [0] + szöveg [j])% prím; pfinger = (2 * pfinger + minta [j])% prim; } int i = 0; logikai átadott = hamis; int diff = textSize - patternSize; for (i = 0; i <= diff; i ++) {if (t [i] == pfinger) {megfelelt = igaz; for (int k = 0; k <patternSize; k ++) {if (szöveg [i + k]! = minta [k]) {átadott = hamis; szünet; }} if (megfelelt) {return i; }} if (i <diff) {hosszú érték = 2 * (t [i] - r * text [i]) + text [i + patternSize]; t [i + 1] = ((%% prime) + prime)% prime; }} return -1; }

A legrosszabb esetben az algoritmus időbeli összetettsége igen O (m (n-m + 1)). Ennek az algoritmusnak azonban átlagosan van O (n + m) az idő bonyolultsága.

Ezen kívül létezik ennek az algoritmusnak a Monte Carlo verziója, amely gyorsabb, de rossz egyezéseket (hamis pozitívumokat) eredményezhet.

2.4 Knuth-Morris-Pratt algoritmus

Az Egyszerű szöveges keresés algoritmusban láttuk, hogyan lehet az algoritmus lassú, ha a szövegnek sok olyan része van, amely megfelel a mintának.

A Knuth-Morris-Pratt algoritmus ötlete a shift tábla kiszámítása, amely információt szolgáltat számunkra, ahol a jelöltjeinket kell keresnünk.

A KMP algoritmus Java megvalósítása:

public static int KnuthMorrisPrattSearch (char [] minta, char [] szöveg) {int patternSize = minta.hossz; int textSize = szöveg.hossz; int i = 0, j = 0; int [] shift = KnuthMorrisPrattShift (minta); míg (((i + patternSize) = patternSize) visszatér i; } if (j> 0) {i + = eltolás [j - 1]; j = Math.max (j - eltolás [j - 1], 0); } else {i ++; j = 0; }} return -1; }

És így számoljuk ki a váltástáblát:

public static int [] KnuthMorrisPrattShift (char [] minta) {int patternSize = minta.hossz; int [] shift = új int [patternSize]; eltolás [0] = 1; int i = 1, j = 0; míg ((i + j)  0) {i = i + shift [j - 1]; j = Math.max (j - eltolás [j - 1], 0); } else {i = i + 1; j = 0; }}} visszatérő műszak; }

Ezen algoritmus időbeli összetettsége is O (m + n).

2.5. Egyszerű Boyer-Moore algoritmus

Két tudós, Boyer és Moore újabb ötlettel állt elő. Miért ne hasonlíthatná össze a mintát a jobbról balra írt szöveggel balról jobbra, miközben a váltásirány ugyanaz marad:

public static int BoyerMooreHorspoolSimpleSearch (char [] minta, char [] text) {int patternSize = minta.hossz; int textSize = szöveg.hossz; int i = 0, j = 0; míg ((i + patternSize) <= textSize) {j = patternSize - 1; while (szöveg [i + j] == minta [j]) {j--; if (j <0) visszatér i; } i ++; } return -1; }

A várakozásoknak megfelelően ez be fog futni O (m * n) idő. De ez az algoritmus az előfordulás és a meccsheurisztika megvalósításához vezetett, ami jelentősen felgyorsítja az algoritmust. Itt többet találhatunk.

2.6. Boyer-Moore-Horspool algoritmus

A Boyer-Moore algoritmus heurisztikus megvalósításának számos változata létezik, és a legegyszerűbb a Horspool variáció.

Az algoritmus ezen változatát Boyer-Moore-Horspoolnak hívják, és ez a variáció megoldotta a negatív eltolódások problémáját (a negatív eltolás problémájáról a Boyer-Moore algoritmus leírásában olvashatunk).

A Boyer-Moore algoritmushoz hasonlóan a legrosszabb esetben is az idő összetettsége O (m * n) míg az átlagos komplexitás O (n). A helyhasználat nem a minta méretétől függ, hanem csak az ábécé méretétől, amely 256, mivel ez az ASCII karakter maximális értéke angol ábécében:

public static int BoyerMooreHorspoolSearch (char [] minta, char [] text) {int shift [] = új int [256]; mert (int k = 0; k <256; k ++) {shift [k] = minta.hossz; } for (int k = 0; k <minta.hossz - 1; k ++) {shift [minta [k]] = minta.hossz - 1 - k; } int i = 0, j = 0; míg ((i + minta.hossz) <= szöveg.hossz) {j = minta.hossz - 1; míg (szöveg [i + j] == minta [j]) {j - = 1; if (j <0) visszatér i; } i = i + shift [szöveg [i + minta.hossz - 1]]; } return -1; }

4. Következtetés

Ebben a cikkben számos algoritmust mutattunk be a szöveges kereséshez. Mivel számos algoritmus erősebb matematikai hátteret igényel, megpróbáltuk az egyes algoritmusok alatti fő gondolatot ábrázolni és egyszerű módon megadni.

És mint mindig, a forráskód megtalálható a GitHubon.