Алгоритм Бойера-Мура — различия между версиями
Kabanov (обсуждение | вклад) (→Псевдо-код) |
Kabanov (обсуждение | вклад) |
||
Строка 8: | Строка 8: | ||
Пусть <tex>|y|=n</tex> и <tex>|x|=m</tex>. | Пусть <tex>|y|=n</tex> и <tex>|x|=m</tex>. | ||
− | Предположим, что в процессе сравнения возникает несовпадение между символом <tex>x[i]=a</tex> шаблона и символом <tex>y[i+j]=b</tex> исходного текста при проверке в позиции <tex>j</tex>. Тогда <tex>x[i+1 .. m-1]=y[i+j+1 .. j+m-1]=u</tex> и <tex>x[i] \neq y[i+j]</tex>, т.е. <tex>m - i - 1</tex> символов | + | Предположим, что в процессе сравнения возникает несовпадение между символом <tex>x[i]=a</tex> шаблона и символом <tex>y[i+j]=b</tex> исходного текста при проверке в позиции <tex>j</tex>. Тогда <tex>x[i+1 .. m-1]=y[i+j+1 .. j+m-1]=u</tex> и <tex>x[i] \neq y[i+j]</tex>, т.е. <tex>m - i - 1</tex> символов шаблона уже совпало. |
Операция '''сдвига хорошего суффикса''' состоит в выравнивании подстроки <tex>u</tex> с её самым правым вхождением в <tex>x</tex>, идущим справа от символа, отличного от <tex>x[i]</tex>. | Операция '''сдвига хорошего суффикса''' состоит в выравнивании подстроки <tex>u</tex> с её самым правым вхождением в <tex>x</tex>, идущим справа от символа, отличного от <tex>x[i]</tex>. | ||
Строка 22: | Строка 22: | ||
[[Файл:boyer-moore-algorithm-3.gif|450px|thumb|center|'''Сдвиг плохого символа''', символ <tex>a</tex> входит в <tex>x</tex>.]] | [[Файл:boyer-moore-algorithm-3.gif|450px|thumb|center|'''Сдвиг плохого символа''', символ <tex>a</tex> входит в <tex>x</tex>.]] | ||
− | Если <tex>y[i+j]</tex> не встречается в шаблоне x, то ни одно вхождение x в y не может включать в себя <tex>y[i+j]</tex>, и левый конец окна сравнения совмещен с символом непосредственно идущим после <tex>y[i+j]</tex>, т.е. <tex>y[i+j+1]</tex>. | + | Если <tex>y[i+j]</tex> не встречается в шаблоне <tex>x</tex>, то ни одно вхождение <tex>x</tex> в <tex>y</tex> не может включать в себя <tex>y[i+j]</tex>, и левый конец окна сравнения совмещен с символом непосредственно идущим после <tex>y[i+j]</tex>, т.е. <tex>y[i+j+1]</tex>. |
[[Файл:boyer-moore-algorithm-4.gif|450px|thumb|center|'''Сдвиг плохого символа''', символ <tex>b</tex> не входит в <tex>x</tex>.]] | [[Файл:boyer-moore-algorithm-4.gif|450px|thumb|center|'''Сдвиг плохого символа''', символ <tex>b</tex> не входит в <tex>x</tex>.]] | ||
Строка 60: | Строка 60: | ||
'''return''' bmBc | '''return''' bmBc | ||
− | Функция для вычисления таблицы суффиксов. Она находит для каждой позиции в шаблоне <tex>x</tex> максимальную длину суффикса <tex>x</tex>, который повторяется в строке и заканчивается в данной позиции. | + | Функция для вычисления таблицы суффиксов. Она находит для каждой позиции в шаблоне <tex>x</tex> максимальную длину суффикса <tex>x</tex>, который повторяется в строке и заканчивается в данной позиции. |
+ | |||
+ | '''Примеры:''' | ||
+ | {| class="wikitable" | ||
+ | ! Строка || Значение функции | ||
+ | |- | ||
+ | | abcabcabc || 0, 0, 3, 0, 0, 6, 0, 0, 9 | ||
+ | |- | ||
+ | | abcabcc || 0, 0, 1, 0, 0, 1, 7 | ||
+ | |} | ||
+ | |||
+ | Также, очевидно, что значение функции для последнего элемента будет равно длине всей строки. | ||
'''int'''[] '''suffixes'''(string x, int m): | '''int'''[] '''suffixes'''(string x, int m): | ||
'''int''' f = 0 | '''int''' f = 0 | ||
Строка 141: | Строка 152: | ||
* На больших алфавитах (например, Юникод) может занимать много памяти. В таких случаях либо обходятся хэш-таблицами, либо дробят алфавит, рассматривая, например, 4-байтовый символ как пару двухбайтовых. | * На больших алфавитах (например, Юникод) может занимать много памяти. В таких случаях либо обходятся хэш-таблицами, либо дробят алфавит, рассматривая, например, 4-байтовый символ как пару двухбайтовых. | ||
* На искусственно подобранных неудачных текстах (например, шаблон "abcabcabcabcabc") скорость алгоритма Бойера-Мура серьёзно снижается. | * На искусственно подобранных неудачных текстах (например, шаблон "abcabcabcabcabc") скорость алгоритма Бойера-Мура серьёзно снижается. | ||
+ | |||
+ | ==Варианты== | ||
+ | ===Алгоритм Бойера — Мура — Хорспула=== | ||
+ | Этот алгоритм работает лучше Бойера-Мура на случайных текстах — для него оценка в среднем лучше. | ||
+ | Алгоритм использует только сдвиги плохих символов, при этом за такой символ берётся символ из исходного текста, который соответствует последнему символу шаблона, независимо от того, где случилось несовпадение. | ||
+ | Поскольку реальные поисковые образцы редко имеют равномерное распределение, алгоритм Бойера-Мура-Хорспула может дать как выигрыш, так и проигрыш по сравнению с стандартной реализацией. | ||
+ | ===Алгоритм Чжу — Такаоки=== | ||
+ | На коротких алфавитах сдвиги плохих символов не помогают уже на коротких суффиксах. Простейший способ улучшить работу алгоритма в таких условиях — вместо одного плохого символа строить таблицу для пары символов: несовпавшего и идущего перед ним. Такой алгоритм получил собственное имя: алгоритм Чжу — Такаоки. | ||
+ | На предварительную обработку расходуется <tex>O(m+\sigma^2)</tex> времени. | ||
==Ссылки== | ==Ссылки== |
Версия 18:49, 10 мая 2014
Алгоритм Бойера-Мура, разработанный двумя учеными — Бойером (Robert S. Boyer) и Муром (J. Strother Moore), считается наиболее быстрым среди алгоритмов общего назначения, предназначенных для поиска подстроки в строке. Важной особенностью алгоритма является то, что он выполняет сравнения в шаблоне справа налево в отличии от многих других алгоритмов.
Алгоритм Бойера-Мура считается наиболее эффективным алгоритмом поиска шаблонов в стандартных приложениях и командах, таких как Ctrl+F в браузерах и текстовых редакторах.
Содержание
Алгоритм
Алгоритм сравнивает символы шаблона
справа налево, начиная с самого правого, один за другим с символами исходной строки . В случае несовпадения какого-либо символа (или полного совпадения всего шаблона) он использует две предварительно вычисляемых функций, чтобы сдвинуть позицию для начала сравнения вправо.Пусть
и .Предположим, что в процессе сравнения возникает несовпадение между символом
шаблона и символом исходного текста при проверке в позиции . Тогда и , т.е. символов шаблона уже совпало.Операция сдвига хорошего суффикса состоит в выравнивании подстроки
с её самым правым вхождением в , идущим справа от символа, отличного от .Если не существует такого сегмента, то смещение состоит в выравнивании самого длинного суффикса
подстроки с соответствующим префиксом .Операция сдвига плохого символа состоит в выравнивании символа исходного текста
с его самым правым появлением в .Если
не встречается в шаблоне , то ни одно вхождение в не может включать в себя , и левый конец окна сравнения совмещен с символом непосредственно идущим после , т.е. .Обратите внимание, что сдвиг плохого символа может быть отрицательным, таким образом для сдвига окна сравнения алгоритм Бойера-Мура использует значение, равное максимуму между сдвигом хорошего суффикса и сдвига плохого символа. Более формально две функции сдвигов определяются следующим образом:
Пусть значения функции сдвига хорошего суффикса хранятся в массиве
размером .Определим два условия:
- : для каждого такого, что выполняется или
- : если , то выполняется
Тогда для всех
таких, что выполняется . А значение определим, как длину периода шаблона .Для вычисления bmGs будем использовать массив
, определенный так: для всех таких, что выполняетсяСдвиги плохих символов будем хранить в массиве
размером . Для каждого символа из :Массивы
и вычисляются за времени до основной фазы поиска и требуют, очевидно, памяти.Псевдо-код
Константой
обозначим размер нашего алфавита.Функция для вычисления таблицы сдвигов плохих символов. Она будет равна длине шаблона для всех символов, которые не встречаются в шаблоне, и порядковому номеру с конца для остальных (кроме последнего, для него тоже берется длина шаблона). Вычисляется прямо по определению за
int[] preBmBc(string x, int m): int bmBc[ASIZE] // Значение по умолчанию = m for i = 0 .. ASIZE-1 bmBc[i] = m for i = 0 .. m - 2 bmBc[x[i]] = m - i - 1 return bmBc
Функция для вычисления таблицы суффиксов. Она находит для каждой позиции в шаблоне
максимальную длину суффикса , который повторяется в строке и заканчивается в данной позиции.Примеры:
Строка | Значение функции |
---|---|
abcabcabc | 0, 0, 3, 0, 0, 6, 0, 0, 9 |
abcabcc | 0, 0, 1, 0, 0, 1, 7 |
Также, очевидно, что значение функции для последнего элемента будет равно длине всей строки.
int[] suffixes(string x, int m): int f = 0 int suff[m] suff[m - 1] = m int 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 return suff
Функция для вычисления сдвигов хороших суффиксов
void preBmGs(string x, int m): int i, j, suff[XSIZE] int bmGs[] suff = suffixes(x, m) for i = 0 .. m - 1 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(string x, int m, string y, int n): int bmGs[m] int bmBc[ASIZE] //Предварительные вычисления bmGs = preBmGs(x, m) bmBc = preBmBc(x, m) //Поиск подстроки int j = 0 while j <= n - m int i = m - 1 while i >= 0 and x[i] == y[i + j] --i if i < 0 OUTPUT(j) // Найдена подстрока в позиции j j += bmGs[0] //Очевидно, что можем сделать сдвиг на период, т.к. там явно не будет совпадений. else j += MAX(bmGs[i], bmBc[y[i + j]] - m + 1 + i)
Асимптотики
- Фаза предварительных вычислений требует времени и памяти
- В худшем случае поиск требует сравнений.
- В лучшем случае требует сравнений.
где
— длина исходного текста, — длина шаблона, — размер алфавита.В 1991 году Р.Коул доказал следующую теорему:
Теорема (Richard Cole): |
В худшем случае требуется сравнений в случае шаблона с периодом равным длине самого шаблона. |
Доказательство: |
Доказательство [1] |
Сравнение с другими алгоритмами
Достоинства
- Алгоритм Бойера-Мура на хороших данных очень быстр, а вероятность появления плохих данных крайне мала. Поэтому он оптимален в большинстве случаев, когда нет возможности провести предварительную обработку текста, в котором проводится поиск.
- На больших алфавитах (относительно длины шаблона) алгоритм чрезвычайно быстрый и требует намного меньше памяти относительно алгоритма Ахо-Корасик.
- Алгоритм проще большинства алгоритмов поиска (при некоторых реализациях объем кода сравним с наивным поиском)
- Позволяет добавить множество модификаций, таких как поиск подстроки, включающей любой символ (?) (но для реализации множества символов (*) не походит, т.к. длина шаблона должна быть известна заранее).
Недостатки
- Алгоритмы семейства Бойера-Мура не расширяются до приблизительного поиска, поиска любой строки из нескольких.
- На больших алфавитах (например, Юникод) может занимать много памяти. В таких случаях либо обходятся хэш-таблицами, либо дробят алфавит, рассматривая, например, 4-байтовый символ как пару двухбайтовых.
- На искусственно подобранных неудачных текстах (например, шаблон "abcabcabcabcabc") скорость алгоритма Бойера-Мура серьёзно снижается.
Варианты
Алгоритм Бойера — Мура — Хорспула
Этот алгоритм работает лучше Бойера-Мура на случайных текстах — для него оценка в среднем лучше. Алгоритм использует только сдвиги плохих символов, при этом за такой символ берётся символ из исходного текста, который соответствует последнему символу шаблона, независимо от того, где случилось несовпадение. Поскольку реальные поисковые образцы редко имеют равномерное распределение, алгоритм Бойера-Мура-Хорспула может дать как выигрыш, так и проигрыш по сравнению с стандартной реализацией.
Алгоритм Чжу — Такаоки
На коротких алфавитах сдвиги плохих символов не помогают уже на коротких суффиксах. Простейший способ улучшить работу алгоритма в таких условиях — вместо одного плохого символа строить таблицу для пары символов: несовпавшего и идущего перед ним. Такой алгоритм получил собственное имя: алгоритм Чжу — Такаоки. На предварительную обработку расходуется
времени.