Я застрял в течение некоторого времени на том, какой алгоритм поиска строк самый быстрый, услышал много мнений, но в итоге я не уверен.
Я слышал, как некоторые люди говорят, что самый быстрый алгоритм - это Бойер-Мур, а некоторые говорят, что Кнут-Моррис-Пратт на самом деле быстрее.
Я искал сложность на них обоих, но они в основном выглядят одинаково O(n+m)
. Я обнаружил, что в худшем случае Бойер-Мур имеет O(nm)
сложность по сравнению с Кнутом-Моррисом-Праттом, который имеет O (m + 2 * n). Где n = длина текста и m = длина шаблона.
Насколько я знаю, у Бойера-Мура время линейного наихудшего случая, если бы я использовал правило Галиля.
Мой вопрос: над всем, что на самом деле является самым быстрым алгоритмом поиска в строке (этот вопрос включает в себя все возможные алгоритмы поиска, не только Бойера-Мура и Кнут-Морриса-Пратта)
Изменить: из-за этого ответа
То, что я точно ищу, это:
Учитывая текст T
и образец, P
я должен найти все появления P
в T
.
Также длина P и T от, [1,2 000 000]
и программа должна работать менее 0,15 сек.
Я знаю, что KMP и Рабин-Карп достаточно, чтобы получить 100% баллов по этой проблеме, но я, например, хотел попробовать реализовать Бойера-Мура. Что было бы лучше для этого типа поиска по шаблону?
источник
Ответы:
Это зависит от вида поиска, который вы хотите выполнить. Каждый из алгоритмов работает особенно хорошо для определенных типов поиска, но вы не указали контекст своих поисков.
Вот некоторые типичные мысли о типах поиска:
Бойер-Мур: работает, предварительно анализируя паттерн и сравнивая справа налево. Если происходит несоответствие, первоначальный анализ используется для определения того, насколько далеко шаблон может быть смещен относительно искомого текста. Это особенно хорошо работает для длинных шаблонов поиска. В частности, он может быть сублинейным, так как вам не нужно читать каждый символ вашего текста.
Кнут-Моррис-Пратт: также предварительно анализирует паттерн, но пытается повторно использовать то, что уже было найдено в начальной части паттерна, чтобы избежать необходимости повторного сопоставления. Это может работать довольно хорошо, если ваш алфавит мал (например, базы ДНК), поскольку у вас больше шансов, что ваши шаблоны поиска содержат многократно используемые подшаблоны.
Ахо-Корасик: Требуется много предварительной обработки, но для ряда паттернов. Если вы знаете, что будете искать одни и те же шаблоны поиска снова и снова, тогда это намного лучше, чем другие, потому что вам нужно анализировать шаблоны только один раз, а не один раз за поиск.
Следовательно, как обычно в CS, нет однозначного ответа на все лучшее . Это скорее вопрос выбора правильного инструмента для работы под рукой.
Еще одно замечание о вашем наихудшем рассуждении: рассмотрите виды поиска, необходимые для создания этого худшего случая, и тщательно продумайте, действительно ли они актуальны в вашем случае. Например,
O(mn)
сложность алгоритма Бойера-Мура в худшем случае проистекает из шаблона поиска и текста, каждый из которых использует только один символ (например, при поискеaaa
вaaaaaaaaaaaaaaaaaaaaa
) - вам действительно нужно быть быстрым для таких поисков?источник
Хотя я немного опаздываю, чтобы ответить на этот вопрос, но я думаю, что
Z-Algorithm
это намного быстрее, чем любые его аналоги. Его сложность в худшем случае составляет O (m + n), и он не требует предварительной обработки шаблона / текста. Это также очень легко кодировать по сравнению с другими алгоритмами.Это работает следующим образом.
Например, есть строка
S ='abaaba'
. Мы должны найтиz(i)
значения дляi=0 to len(S)-1
. Прежде чем углубляться в объяснение, позвольте мне сначала изложить некоторые определения.z(i)
= нет символов префиксаS
, совпадающего с префиксомs(i)
.s(i)
=ith
СуффиксS
.Ниже приведены
s(i)
значения дляs = 'abaaba'
.Значения z соответственно
Для детального понимания алгоритма, обратитесь к следующим ссылкам.
http://codeforces.com/blog/entry/3107
https://www.youtube.com/watch?v=MFK0WYeVEag
Теперь требуется O (N), чтобы найти все
z
значения без каких-либо затрат на предварительную обработку. Теперь было бы интересно узнать, как вы можете использовать эту логику для сопоставления с шаблоном в данной строке?Давайте посмотрим на примере. Шаблон (Р):
aba
, Текст (Т):aacbabcabaad
.Поместите это в форму P $ T. (
$
- любой символ, который не появляется ни в шаблоне, ни в тексте. Я скоро пойду к важности$
.)P$T
знак равноaba$aacbabcabaad
Мы знаем
len(P)
= 3.Все значения z
P$T
являютсяТеперь, который
z(i)
=len(P)
.Ans = 11.
Таким образом, наша модель присутствует вAns-len(P)-1
=7
.-1
для$
персонажа.Теперь, почему
$
или любой такой особый характер важен. РассмотримP = 'aaa'
иT = 'aaaaaaa'
. Без специального символа всеz(i)
будут иметь инкрементные значения. Еще можно найти положение шаблона в тексте с помощью следующих формул:Условие:
z(i)
> =len(P)
и положение:Ans-len(P)
. Но состояние в этом случае становится немного сложным и запутанным. Я лично предпочитаю использовать специальную технику персонажа.источник
z
- это предварительная обработка. Хотя это хорошее объяснение.O(n)
Благодаря этому ответу я изобрел способ преобразования предварительной обработки KMP в предварительную обработку Z. ЗдесьИспользование адресной памяти контента , реализованной в программном обеспечении в виде виртуальной адресации (указание букв к буквам).
Это своего рода излишний алгоритм сопоставления средней строки.
CAM может одновременно соответствовать огромному количеству шаблонов, вплоть до 128-буквенных шаблонов (если они являются ASCII; если они только в Unicode 64). И это один вызов на длину буквы в строке, которой вы хотите соответствовать, и одно случайное чтение из памяти на длину максимальной длины шаблона. Таким образом, если вы анализируете строку длиной 100 000 букв с одновременным использованием до 90 000 000 шаблонов (для хранения такого большого количества шаблонов потребуется около 128 ГиБ), это займет 12 800 000 случайных операций чтения из ОЗУ, поэтому это произойдет за 1 мс.
Вот как работает виртуальная адресация.
Если я начну с 256 начальных адресов, которые представляют первую букву, эти буквы указывают на 256 следующих букв. Если шаблон не существует, вы не сохраните его.
Поэтому, если я продолжаю связывать буквы с буквами, это похоже на наличие 128 фрагментов виртуальной адресации, указывающих на виртуальную адресацию.
Это сработает - но чтобы одновременно получить 900 000 000 шаблонов, есть еще один трюк, который нужно добавить к этому - и он использует тот факт, что вы начинаете с большого повторного использования этих буферов писем, но позже это рассеивается. Если вы перечислите содержимое вместо того, чтобы выделять все 256 символов, то оно очень мало замедлится, и вы получите увеличение емкости в 100 раз, потому что в основном вы в конечном итоге получаете только 1 букву, используемую в каждом буфере указателя букв (который я назвал ' побег').
Если вы хотите получить совпадение строки ближайшего соседа, то у вас есть много таких, которые работают параллельно, и вы собираете их в иерархии, поэтому вы распределяете свою ошибку беспристрастно. если вы пытаетесь найти ближайшего соседа только с одним, то вы смещены к началу дерева.
источник