Алгоритм Бойера-Мура — различия между версиями
Kabanov (обсуждение | вклад) м (→Псевдо-код) |
Kabanov (обсуждение | вклад) |
||
| Строка 46: | Строка 46: | ||
* <tex>Co(i, s)</tex>: если <tex>s < i</tex>, то выполняется <tex>x[i-s] \neq x[i]</tex> | * <tex>Co(i, s)</tex>: если <tex>s < i</tex>, то выполняется <tex>x[i-s] \neq x[i]</tex> | ||
| − | Тогда для всех <tex>i</tex> таких, что <tex>0 \leqslant i < m</tex> выполняется <tex>bmGs[i+1]=\min\{s > 0 : Cs(i, s) and Co(i, s)\}</tex>. А значение <tex>bmGs[0]</tex> определим, как длину периода шаблона <tex>x</tex>. | + | Тогда для всех <tex>i</tex> таких, что <tex>0 \leqslant i < m</tex> выполняется <tex>bmGs[i+1]=\min\{s > 0 : Cs(i, s)\ and\ Co(i, s)\}</tex>. А значение <tex>bmGs[0]</tex> определим, как длину периода шаблона <tex>x</tex>. |
Для вычисления bmGs будем использовать таблицу <tex>suff</tex>, определенную так: | Для вычисления bmGs будем использовать таблицу <tex>suff</tex>, определенную так: | ||
для всех <tex>i</tex> таких, что <tex>1 \leqslant i < m</tex> выполняется <tex>suff[i]=\max\{k : x[i-k+1 .. i]=x[m-k .. m-1]\}</tex> | для всех <tex>i</tex> таких, что <tex>1 \leqslant i < m</tex> выполняется <tex>suff[i]=\max\{k : x[i-k+1 .. i]=x[m-k .. m-1]\}</tex> | ||
| + | |||
| + | Сдвиги плохих символов будем хранить в массиве <tex>bmBc</tex> размером <tex>\sigma</tex>. | ||
| + | Для каждого символа <tex>c</tex> из <tex>\Sigma</tex>: <tex>bmBc[c] = \begin{cases} | ||
| + | \min\{i : 1 \leqslant i < m-1\ and\ x[m-1-i]=c\}, & \mbox{if } c \in x\\ | ||
| + | m, & \mbox{otherwise} | ||
| + | \end{cases}</tex> | ||
==Псевдо-код== | ==Псевдо-код== | ||
Версия 00:36, 10 мая 2014
Алгоритм Бойера-Мура, разработанный двумя учеными – Бойером (Robert S. Boyer) и Муром (J. Strother Moore), считается наиболее быстрым среди алгоритмов общего назначения, предназначенных для поиска подстроки в строке. Важной особенностью алгоритма является то, что он выполняет сравнения справа налево в отличии от многих других алгоритмов.
Алгоритм Бойера-Мура считается наиболее эффективным алгоритмом поиска шаблонов в стандартных приложениях и командах, таких как Ctrl+F в браузерах и текстовых редакторах.
Содержание
Асимптотики
- Фаза предварительных вычислений требует времени и памяти
- В худшем случае поиск требует сравнений.
- В лучшем случае требует сравнений.
В 1991 году Р.Коул доказал следующую теорему:
| Теорема (Richard Cole): |
В худшем случае требуется сравнений в случае шаблона с периодом равным длине самого шаблона. |
| Доказательство: |
| Доказательство [1] |
Алгоритм
Алгоритм сравнивает символы шаблона () справа налево, начиная с самого правого, один за другим с символами исходной строки (). В случае несовпадения какого-либо символа (или полного совпадения всего шаблона) он использует две предварительно вычисляемых функций, чтобы сдвинуть позицию для начала сравнения вправо.
Пусть и .
Предположим, что в процессе сравнения возникает несовпадение между символом шаблона и символом исходного текста при проверке в позиции . Тогда и , т.е. символов паттерна уже совпало.
Операция сдвига хорошего суффикса состоит в выравнивании подстроки с её самым правым вхождением в , идущим справа от символа, отличного от .
Если не существует такого сегмента, то смещение состоит в выравнивании самого длинного суффикса подстроки с соответствующим префиксом .
Операция сдвига плохого символа состоит в выравнивании символа исходного текста с его самым правым появлением в .
Если не встречается в шаблоне x, то ни одно вхождение x в y не может включать в себя , и левый конец окна сравнения совмещен с символом непосредственно идущим после , т.е. .
Обратите внимание, что сдвиг плохого символ может быть отрицательным, таким образом для сдвига окна сравнения алгоритм Бойера-Мура использует значение, равное максимуму между сдвигом хорошего суффикса и сдвига плохого символа. Более формально две функции сдвигов определяются следующим образом:
Пусть значения функции сдвига хорошего суффикса хранятся в таблице размером .
Определим два условия:
- : для каждого такого, что выполняется или
- : если , то выполняется
Тогда для всех таких, что выполняется . А значение определим, как длину периода шаблона .
Для вычисления bmGs будем использовать таблицу , определенную так: для всех таких, что выполняется
Сдвиги плохих символов будем хранить в массиве размером . Для каждого символа из :
Псевдо-код
Функция для вычисления таблицы сдвигов плохих символов
void preBmBc(string x, int m, int bmBc[]):
for i = 0 .. ASIZE-1
bmBc[i] = m
for i = 0 .. m - 2
bmBc[x[i]] = m - i - 1
Функция для вычисления таблицы суффиксов
void suffixes(string x, int m, int *suff):
int f, g, i;
suff[m - 1] = m;
g = m - 1;
for i = m - 2 .. 0
if (i > g and suff[i + m - 1 - f] < i - g)
suff[i] = suff[i + m - 1 - f];
else
if (i < g)
g = i;
f = i;
while (g >= 0 and x[g] == x[g + m - 1 - f])
--g;
suff[i] = f - g;
Функция для вычисления сдвигов хороших суффиксов
void preBmGs(string x, int m, int bmGs[]):
int i, j, suff[XSIZE];
suffixes(x, m, suff);
for (i = 0; i < m; ++i)
bmGs[i] = m;
j = 0;
for i = m - 1 .. 0
if (suff[i] == i + 1)
while (j < m - 1 - i)
if (bmGs[j] == m)
bmGs[j] = m - 1 - i;
++j
for i = 0 .. m - 2
bmGs[m - 1 - suff[i]] = m - 1 - i;
Основная функция алгоритма Бойера-Мура
void BM(char *x, int m, char *y, int n):
int i, j, bmGs[XSIZE], bmBc[ASIZE];
//Предварительные вычисления
preBmGs(x, m, bmGs);
preBmBc(x, m, bmBc);
//Поиск подстроки
j = 0;
while (j <= n - m)
i = m - 1
while (i >= 0 and x[i] == y[i + j])
--i
if (i < 0)
OUTPUT(j);
j += bmGs[0];
else
j += MAX(bmGs[i], bmBc[y[i + j]] - m + 1 + i);
Сравнение с другими алгоритмами
Достоинства
- Алгоритм Бойера-Мура на хороших данных очень быстр, а вероятность появления плохих данных крайне мала. Поэтому он оптимален в большинстве случаев, когда нет возможности провести предварительную обработку текста, в котором проводится поиск.
- Алгоритм проще большинства алгоритмов поиска (при некоторых реализациях объем кода сравним с наивным поиском)
- Позволяет добавить множество модификаций, таких как поиск подстроки, включающей любой символ (?) (но для реализации множества символов (*) не походит, т.к. длина шаблона должна быть известна заранее).
Недостатки
- Алгоритмы семейства Бойера-Мура не расширяются до приблизительного поиска, поиска любой строки из нескольких.
- Сравнение не является "чёрным ящиком", поэтому при реализации наиболее быстрого поиска приходится либо рассчитывать на удачную работу оптимизатора, либо вручную оптимизировать поиск.
- На больших алфавитах (например, Юникод) может занимать много памяти. В таких случаях либо обходятся хэш-таблицами, либо дробят алфавит, рассматривая, например, 4-байтовый символ как пару двухбайтовых.
- На искусственно подобранных неудачных текстах (например, needle=«колоколоколоколоколокол») скорость алгоритма Бойера-Мура серьёзно снижается.