В информатике алгоритм поиска строк Бойера–Мура — эффективный алгоритм поиска строк , который является стандартным эталоном для практической литературы по поиску строк. [1] Он был разработан Робертом С. Бойером и Дж. Строзером Муром в 1977 году. [2] Оригинальная статья содержала статические таблицы для вычисления сдвигов шаблонов без объяснения того, как их создавать. Алгоритм создания таблиц был опубликован в последующей статье; эта статья содержала ошибки, которые позже были исправлены Войцехом Риттером в 1980 году. [3] [4]
Алгоритм предварительно обрабатывает искомую строку ( шаблон), но не искомую строку (текст). Таким образом, он хорошо подходит для приложений, в которых шаблон намного короче текста или где он сохраняется в нескольких поисках. Алгоритм Бойера–Мура использует информацию, собранную на этапе предварительной обработки, для пропуска разделов текста, что приводит к более низкому постоянному множителю, чем у многих других алгоритмов поиска строк. В целом алгоритм работает быстрее по мере увеличения длины шаблона. Ключевыми особенностями алгоритма являются сопоставление с хвостом шаблона, а не с головой, и пропуск текста скачками по нескольким символам, а не поиск каждого отдельного символа в тексте.
Алгоритм Бойера–Мура ищет вхождения P в T , выполняя явные сравнения символов при различных выравниваниях. Вместо поиска методом грубой силы всех выравниваний (которых существует ), Бойер–Мур использует информацию, полученную путем предварительной обработки P , чтобы пропустить как можно больше выравниваний.
До введения этого алгоритма обычным способом поиска в тексте было изучение каждого символа текста на предмет первого символа шаблона. Как только он был найден, последующие символы текста сравнивались с символами шаблона. Если совпадений не было, текст снова проверялся символ за символом в попытке найти совпадение. Таким образом, необходимо изучить почти каждый символ в тексте.
Ключевое понимание этого алгоритма заключается в том, что если конец шаблона сравнивается с текстом, то можно делать переходы по тексту, а не проверять каждый символ текста. Причина, по которой это работает, заключается в том, что при выравнивании шаблона по тексту последний символ шаблона сравнивается с символом в тексте. Если символы не совпадают, нет необходимости продолжать поиск в обратном направлении по тексту. Если символ в тексте не совпадает ни с одним из символов шаблона, то следующий символ в тексте для проверки располагается на m символов дальше по тексту, где m — длина шаблона. Если символ в тексте находится в шаблоне, то выполняется частичный сдвиг шаблона по тексту для выравнивания по совпадающему символу, и процесс повторяется. Переходы по тексту для выполнения сравнений вместо проверки каждого символа в тексте уменьшают количество сравнений, которые необходимо выполнить, что является ключом к эффективности алгоритма.
Более формально, алгоритм начинается с выравнивания , поэтому начало P выравнивается с началом T . Затем символы в P и T сравниваются, начиная с индекса m в P и k в T , двигаясь назад. Строки сопоставляются от конца P до начала P . Сравнения продолжаются до тех пор, пока не будет достигнуто начало P (что означает, что есть совпадение), или пока не произойдет несовпадение, при котором выравнивание смещается вперед (вправо) в соответствии с максимальным значением, разрешенным рядом правил. Сравнения выполняются снова при новом выравнивании, и процесс повторяется до тех пор, пока выравнивание не сместится за конец T , что означает, что больше совпадений найдено не будет.
Правила сдвига реализованы как поиск в таблице за постоянное время с использованием таблиц, созданных во время предварительной обработки P.
Сдвиг рассчитывается с применением двух правил: правила плохого символа и правила хорошего суффикса. Фактическое смещение сдвига — это максимальное из смещений, рассчитанных по этим правилам.
Правило плохого символа рассматривает символ в T, на котором процесс сравнения не удался (предполагая, что такой сбой произошел). Находится следующее вхождение этого символа слева в P , и предлагается сдвиг, который приводит это вхождение в соответствие с несовпадающим вхождением в T. Если несовпадающий символ не встречается слева в P , предлагается сдвиг, который перемещает весь P за точку несовпадения.
Методы различаются в зависимости от точной формы, которую должна принять таблица для правила плохого символа, но простое решение поиска с постоянным временем выглядит следующим образом: создайте двумерную таблицу, которая индексируется сначала индексом символа c в алфавите, а затем индексом i в шаблоне. Этот поиск вернет вхождение c в P со следующим по величине индексом или -1, если такого вхождения нет. Тогда предлагаемый сдвиг будет , с временем поиска и пространством, предполагая, что алфавит имеет конечную длину k .
Реализации C и Java ниже имеют сложность пространства (make_delta1, makeCharTable). Это то же самое, что и исходная delta1 и таблица плохих символов BMH . Эта таблица сопоставляет символ в позиции со сдвигом на , причем последний экземпляр — наименьшая величина сдвига — имеет приоритет. Все неиспользуемые символы устанавливаются как как контрольное значение.
Правило хорошего суффикса заметно сложнее как по концепции, так и по реализации, чем правило плохого символа. Как и правило плохого символа, оно также использует особенность алгоритма сравнения, начинающегося с конца шаблона и продолжающегося к началу шаблона. Его можно описать следующим образом: [5]
Предположим, что для заданного выравнивания P и T подстрока t из T соответствует суффиксу P , и предположим, что t является наибольшей такой подстрокой для заданного выравнивания.
- Затем найдите, если она существует, самую правую копию t ′ строки t в P такую, что t ′ не является суффиксом P и символ слева от t ′ в P отличается от символа слева от t в P. Сдвиньте P вправо так, чтобы подстрока t ′ в P выровнялась с подстрокой t в T.
- Если t ′ не существует, то сдвиньте левый конец P вправо на наименьшую величину (за левый конец t в T ) так, чтобы префикс сдвинутого шаблона совпадал с суффиксом t в T . Это включает в себя случаи, когда t является точным совпадением P .
- Если такой сдвиг невозможен, то сдвигаем P на m (длина P) позиций вправо.
Правило хорошего суффикса требует двух таблиц: одну для использования в общем случае (где найдена копия t ′ ), а другую для использования, когда общий случай не возвращает осмысленного результата. Эти таблицы будут обозначены L и H соответственно. Их определения следующие: [5]
Для каждого i , — это наибольшая позиция, меньшая m , такая, что строка соответствует суффиксу и такая, что символ, предшествующий этому суффиксу, не равен . определяется как нуль, если нет позиции, удовлетворяющей условию.
Пусть обозначает длину наибольшего суффикса , который также является префиксом P , если таковой существует. Если такового не существует, пусть будет равен нулю.
Обе эти таблицы можно построить за время и использовать пространство. Сдвиг выравнивания для индекса i в P задается как или . H следует использовать только если равно нулю или было найдено совпадение.
Индекс | Несоответствие | Сдвиг 0 | Н| 1 1 | АН| 8 2 | МУЖЧИНА| 3 3 | НМАН| 6 4 | АНМАН| 6 5 | ПАНМАН| 6 6 | НПАНМАН| 6 7 | АМПАНМАН| 6
Объяснение:
Индекс 0, нет совпавших символов, прочитанный символ не был N. Длина хорошего суффикса равна нулю. Поскольку в шаблоне много букв, которые также не являются N, у нас здесь минимальная информация — сдвиг на 1 является наименее интересным результатом.
Индекс 1, мы сопоставили N, и ему предшествовало что-то, отличное от A. Теперь посмотрим на шаблон, начиная с конца, где у нас N, которому предшествует что-то, отличное от A? Есть еще два N, но обоим предшествует A. Это значит, что никакая часть хорошего суффикса не может быть нам полезна — сдвиг на полную длину шаблона 8.
Индекс 2: Мы сопоставили AN, и ему предшествовало не M. В середине шаблона есть AN, которому предшествует P, поэтому он становится кандидатом на сдвиг. Сдвиг этого AN вправо для выравнивания с нашим соответствием — это сдвиг на 3.
Индекс 3 и выше: сопоставленные суффиксы не соответствуют ничему другому в шаблоне, но конечный суффикс AN соответствует началу шаблона, поэтому все сдвиги здесь равны 6. [6]
Простая, но важная оптимизация Бойера-Мура была предложена Цви Галилом в 1979 году . [7] В отличие от сдвига, правило Галила имеет дело с ускорением фактических сравнений, выполняемых при каждом выравнивании, путем пропуска разделов, которые известны как совпадающие. Предположим, что при выравнивании k 1 , P сравнивается с T до символа c из T . Затем, если P сдвигается на k 2 таким образом, что его левый конец оказывается между c и k 1 , на следующей фазе сравнения префикс P должен соответствовать подстроке T [( k 2 - n ).. k 1 ] . Таким образом, если сравнения дойдут до позиции k 1 из T , вхождение P может быть записано без явного сравнения после k 1 . Помимо повышения эффективности Бойера-Мура, правило Галила требуется для доказательства линейного времени выполнения в худшем случае.
Правило Галила в его первоначальной версии эффективно только для версий, которые выводят несколько совпадений. Оно обновляет диапазон подстрок только при c = 0 , т. е. полном совпадении. Обобщенная версия для работы с подсовпадениями была представлена в 1985 году как алгоритм Апостолико–Джанкарло . [8]
Алгоритм Бойера–Мура, представленный в оригинальной статье, имеет худшее время выполнения только если шаблон не появляется в тексте. Это было впервые доказано Кнутом , Моррисом и Праттом в 1977 году [3] , а затем Гибасом и Одлыжко в 1980 году [9] с верхней границей 5 n сравнений в худшем случае. Ричард Коул дал доказательство с верхней границей 3 n сравнений в худшем случае в 1991 году [10].
Когда шаблон встречается в тексте, время выполнения исходного алгоритма составляет в худшем случае. Это легко увидеть, когда и шаблон, и текст состоят исключительно из одного и того же повторяющегося символа. Однако включение правила Галила приводит к линейному времени выполнения во всех случаях. [7] [10]
Различные реализации существуют в разных языках программирования. В C++ он является частью стандартной библиотеки с C++17, а Boost предоставляет универсальную реализацию поиска Бойера–Мура в библиотеке алгоритмов . В Go (язык программирования) есть реализация в search.go. D (язык программирования) использует BoyerMooreFinder для сопоставления на основе предикатов в диапазонах как часть библиотеки времени выполнения Phobos.
Алгоритм Бойера–Мура также используется в grep GNU . [ 11]
Алгоритм Бойера –Мура–Хорспула представляет собой упрощение алгоритма Бойера–Мура, использующее только правило плохих символов.
Алгоритм Апостолико–Джанкарло ускоряет процесс проверки того, произошло ли совпадение при заданном выравнивании, пропуская явные сравнения символов. При этом используется информация, полученная во время предварительной обработки шаблона в сочетании с длинами совпадений суффиксов, записанными при каждой попытке сопоставления. Для хранения длин совпадений суффиксов требуется дополнительная таблица, равная по размеру искомому тексту.
Алгоритм Райта улучшает производительность алгоритма Бойера–Мура–Хорспула. Шаблон поиска конкретной подстроки в заданной строке отличается от алгоритма Бойера–Мура–Хорспула.