Добавил:
Upload Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:
Алгоритм Бойера — Мура.docx
Скачиваний:
31
Добавлен:
26.11.2019
Размер:
50.87 Кб
Скачать

Алгоритм Бойера — Мура

Данный алгоритм был разработан двумя учеными — Робертом Бойером (англ. Robert Stephen Boyer) и Джеем Муром (англ. J Strother Moore) в 1977 году. Он считается наиболее быстрым среди алгоритмов общего назначения, предназначенных для поиска подстроки в строке.

Примечание:

Вопрос: что значит алгоритмы «общего назначения»?

Ответ: это значит, что эти алгоритмы универсальны, т.е. предназначены для решения широкого класса задач.

Ключевые понятия:

Алфавит — конечное множество символов.

Подстрока — это последовательность подряд идущих символов в строке.

Строка — последовательность символов текста.

Суффикс — это подстрока, заканчивающаяся на последний символ строки.

Примечание:

В определении строки речь не обязательно должна идти именно о тексте.

В общем случае строка — это любая последовательность байтов.

Поиск подстроки в строке осуществляется по заданному образцу, т. е. некоторой последовательности байтов, длина которой не превышает длину строки.

Наша задача заключается в том, чтобы определить, содержит ли строка заданный образец.

Описание:

Алгоритма Бойера — Мура можно представить в виде двух простых шагов.

1 шаг

Для искомого образца строим две таблицы — таблицу стоп-символов и таблицу суффиксов.

Примечание:

Процесс построения таблиц будет описан ниже.

2 шаг

Совмещаем начало строки и образца и начинаем проверку с последнего символа образца.

Если символы совпадают, производится сравнение предпоследнего символа образца и т. д. Если все символы образца совпали с наложенными символами строки, значит, подстрока найдена и поиск окончен.

Если же какой-то символ образца не совпадает с соответствующим символом строки, образец сдвигается на несколько символов вправо, и проверка снова начинается с последнего символа.

Назовем эти «несколько символов», упомянутые в предыдущем абзаце, величиной сдвига.

В качестве величины сдвига берется большее из двух значений:

1) Значение, полученное с помощью таблицы стоп-символов по простому правилу:

Если несовпадение произошло на позиции , а стоп-символ « », то значение величины сдвига будет равно .

Примечание:

- позиция символа в образце (нумерация с 1);

— значение, записанное в таблице стоп-символов, для символа «c».

2) Значение, полученное из таблицы суффиксов.

Подробное описание работы алгоритма

Псевдокод: алгоритм Бойера — Мура

Boyer-Moore-Matcher( )

  1. m

  2. Compute-Last-Occurrence-Function( )

  3. Compute-Good-Suffix-Function( )

  4. while

  5. do

  6. while and

  7. do

  8. if

  9. then print «Образец входит со сдвигом» s

  10. else

Алгоритм основан на трёх идеях.

1. Сканирование слева направо, сравнение справа налево.

Совмещается начало текста (строки) и шаблона, проверка начинается с последнего символа шаблона.

Если символы совпадают, производится сравнение предпоследнего символа шаблона и т. д.

Если все символы шаблона совпали с наложенными символами строки, значит, подстрока найдена, и поиск окончен.

Если же какой-то символ шаблона не совпадает с соответствующим символом строки, шаблон сдвигается на несколько символов вправо, и проверка снова начинается с последнего символа.

Примечание:

Эти «несколько», упомянутые в предыдущем абзаце, вычисляются по двум эвристикам.

Вопрос: что такое эвристика?

Ответ: эвристика — это не полностью математически обоснованный (или даже «не совсем корректный»), но при этом практически полезный алгоритм.

2. Эвристика стоп-символа (англ. bad-character heuristic).

Стоп-символ — это первый справа символ в строке, отличный от соответствующего символа в образце.

Эвристика стоп-символа предлагает попробовать новое значение сдвига, исходя из того, где в образце встречается стоп-символ (если вообще встречается).

В наиболее удачном случае стоп-символ выявляется при первом же сравнении и не встречается нигде в образце.

В этом случае сдвиг можно сразу увеличить на : любой меньший сдвиг заведомо не подойдет, так как стоп- символ в тексте окажется напротив какого-то символа из образца.

Если этот наиболее удачный случай повторяется постоянно, то при поиске подстроки мы просмотрим всего лишь часть текста (вот как полезно сравнивать справа налево!).

В общем случае эвристика стоп-символа работает так. Предположим, что при сравнении права налево мы наткнулись на первое несовпадение: , где . Пусть — номер самого правого вхождения символа в образец (если этот символ вообще не появляется в образце, считаем равным ). Мы утверждаем, что можно увеличить на , не упустив ни одного допустимого сдвига.

В самом деле, если , то стоп-символ вообще не встречается в образце , так что можно сразу сдвинуть образец на позиций вправо.

Если , то образец можно сдвинуть на позиций вправо, т.к. при меньших сдвигах стоп-символ в тексте не совпадет с соответствующим символом образца.

Наконец, если , то эвристика предлагает сдвигать образец не вправо, а влево; алгоритм Бойера — Мура эту рекомендацию игнорирует, поскольку эвристика безопасного суффикса всегда предлагает ненулевой сдвиг вправо.

Чтобы применять эвристику стоп-символа полезно для каждого возможного стоп-символа вычислить значение . Это делается простой процедурой Compute-Last-Occurrence-Function («найти последнее вхождение»), которая для каждого вычисляет — номер крайнего правого вхождения в , или нуль, если в не входит. В этих обозначениях приращение сдвига, диктуемое эвристикой стоп-символа, есть , как и написано в строке 13 алгоритма Boyer-Moore-Matcher.

Псевдокод: найти последнее вхождение стоп-символа в подстроку

Compute-Last-Occurrence-Function( )

  1. for each character

  2. do

  3. for to

  4. do

  5. return

Примечание:

Время работы процедуры Compute-Last-Occurrence-Function есть

Пример использования эвристики стоп-символа:

Предположим, что мы производим поиск слова «колокол».

Первая же буква не совпала — «к» (назовём эту букву стоп-символом).

Тогда можно сдвинуть шаблон вправо до последней буквы «к».

!

Строка: * * * * * * к * * * * * *

Шаблон: к о л о к о л

Следующий шаг: к о л о к о л

Если стоп-символа в шаблоне вообще нет, шаблон смещается за этот стоп-символ.

!

Строка: * * * * * а л * * * * * * * *

Шаблон: к о л о к о л

Следующий шаг: к о л о к о л

В данном случае стоп-символ — «а», и шаблон сдвигается так, чтобы он оказался прямо за этой буквой. В алгоритме Бойера — Мура эвристика стоп-символа вообще не смотрит на совпавший суффикс (см. ниже), так что первая буква шаблона («к») окажется под «л», и будет проведена одна заведомо холостая проверка.

Если стоп-символ «к» оказался за другой буквой «к», эвристика стоп-символа не работает.

!

Строка: * * * * к к о л * * * * *

Шаблон: к о л о к о л

Следующий шаг: к о л о к о л ?????

В таких ситуациях выручает третья идея алгоритма — эвристика совпавшего суффикса.

3. Эвристика безопасного (совпавшего) суффикса (англ. good-suffix heuristic).

Если и — строки, будем говорить, что они сравнимы (обозначение: ), если одна из них является суффиксом другой. Если выровнять две сравнимые строки по правому краю, то символы, расположенные один под другим, будут совпадать. Отношение симметрично: если , то и .

Эвристика безопасного суффикса состоит в следующем: если , где (и число — наибольшее с таким свойством), то мы можем безбоязненно увеличить сдвиг на

Иными словами, — наименьшее расстояние, на которое мы можем сдвинуть образец без того, чтобы какой- то из символов, входящих в «безопасный суффикс» оказался напротив несовпадающего с ним символа из образца. Поскольку строка заведомо сравнима с пустой строкой , число корректно определено для всех . Стоит также заметить, что для всех , так что на каждом шаге алгоритма Бойера — Мура образец будет сдвигаться вправо хотя бы на одну позицию. Мы будем называть функцией безопасного суффикса (англ. good-suffix function), ассоциированной со строкой .

Псевдокод: вычисление функции безопасных суффиксов

Compute-Good-Suffix-Function( )

  1. m = length(P)

  2. pi[] = префикс-функция(P)

  3. pi1[] = префикс-функция(обращение(P))

  4. for j=0..m

  5. suffshift[j] = m - pi[m]

  6. for i=1..m

  7. j = m - pi1[i]

  8. suffshift[j] = min(suffshift[j], i - pi1[i])

Примечание:

Время работы процедуры Compute-Good-Suffix-Function есть .

Пример использования эвристики безопасного (совпавшего) суффикса:

Если при сравнении строки и шаблона совпало один или больше символов, шаблон сдвигается в зависимости от того, какой суффикс совпал.

Строка: * * т о к о л * * * * *

Шаблон: к о л о к о л

Следующий шаг: к о л о к о л

В данном случае совпал суффикс «окол», и шаблон сдвигается вправо до ближайшего «окол». Если подстроки «окол» в шаблоне больше нет, но он начинается на «кол», сдвигается до «кол», и т. д.

Обе эвристики требуют предварительных вычислений — в зависимости от шаблона поиска заполняются две таблицы. Таблица стоп-символов по размеру соответствует алфавиту (например, если алфавит состоит из 256 символов, то её длина 256); таблица суффиксов — искомому шаблону. Именно из-за этого алгоритм Бойера — Мура не учитывает совпавший суффикс и несовпавший символ одновременно — это потребовало бы слишком много предварительных вычислений.

Опишем подробнее обе таблицы.

Таблица стоп-символов

Считается, что символы строк нумеруются с 1 (как в Паскале).

В таблице стоп-символов указывается последняя позиция в образце (исключая последнюю букву) каждого из символов алфавита. Для всех символов, не вошедших в образец , пишем 0 (для нумерации с 0 — соответственно, −1).

Например, если , таблица стоп-символов будет выглядеть так.

Символ a b c d [все остальные]

Последняя позиция 5 2 7 6 0

Обратите внимание, для стоп-символа «d» последняя позиция будет 6, а не 8 — последняя буква не учитывается. Это известная ошибка, приводящая к неоптимальности. Для АБМ она не фатальна («вытягивает» эвристика суффикса), но фатальна для упрощённой версии АБМ — алгоритма Хорспула.

Если несовпадение произошло на позиции , а стоп-символ , то сдвиг будет .

Таблица суффиксов

Для каждого возможного суффикса шаблона указываем наименьшую величину, на которую нужно сдвинуть вправо шаблон, чтобы он снова совпал с . Если такой сдвиг невозможен, ставится (в обеих системах нумерации). Например, для того же будет:

Суффикс [пустой] d cd dcd ... abcdadcd

Сдвиг 1 2 4 8 ... 8

Иллюстрация

было ? ?d ?cd ?dcd ... abcdadcd

стало abcdadcd abcdadcd abcdadcd abcdadcd ... abcdadcd

Если шаблон начинается и заканчивается одной и той же комбинацией букв, вообще не появится в таблице.

Например, для для всех суффиксов (кроме, естественно, пустого) сдвиг будет равен 4.

Суффикс [пустой] л ол ... олокол колокол

Сдвиг 1 4 4 ... 4 4

Иллюстрация

было ? ?л ?ол ... ?олокол колокол

стало колокол колокол колокол ... колокол колокол

Пример:

Пусть у нас есть набор символов (алфавит) из пяти символов: и мы хотим найти вхождение образца “ ” в строке “ ”. Следующие схемы иллюстрируют все этапы выполнения алгоритма:

1 шаг

Таблица стоп-символов

a

b

c

d

e

4

3

0

0

0

Если несовпадение произошло на позиции , а стоп-символ , то сдвиг будет .

Таблица суффиксов для образца “ ”.

Суффикс [пустой] d ad bad bbad abbad

Сдвиг 1 5 5 5 5 5

Иллюстрация

было ? ?d ?ad ?bad ?bbad abbad

стало abbad abbad abbad abbad abbad abbad

2 шаг

a b e c c a a b a d b a b b a d

a b b a d

Накладываем образец на строку. Совпадения суффикса нет — таблица суффиксов даёт сдвиг на одну позицию. Для несовпавшего символа исходной строки «с» (5-я позиция) в таблице стоп-символов записан 0. Сдвигаем образец вправо на 5-0=5 позиций:

a b e c c a a b a d b a b b a d

a b b a d

Символы 3—5 совпали, а второй — нет. Эвристика стоп-символа для «а» не работает (2-4=-2). Но поскольку часть символов совпала, в дело включается эвристика совпавшего суффикса, сдвигающая образец сразу на пять позиций!

a b e c c a a b a d b a b b a d

a b b a d

И снова совпадения суффикса нет. В соответствии с таблицей стоп-символов сдвигаем образец на 1 позицию и получаем искомое вхождение образца:

a b e c c a a b a d b a b b a d

a b b a d