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

Материал из Викиконспекты
Перейти к: навигация, поиск

Постановка задачи

Имеются строки [math]S[/math] и [math]T[/math] такие, что элементы этих строк [math]-[/math] символы из конечного алфавита [math] \sum [/math]. Говорят, что строка [math]Z[/math][1 .. m] является подстрокой строки [math]S[/math][1 .. n], если существует такой индекс [math]k[/math] ∈ [0 .. n - m], что для любого [math]i[/math] ∈ [1 .. m] справедливо [math]S[k + i] = Z[i][/math]. Требуется найти такую строку [math]Z[/math], максимальной длины, что [math]Z[/math] является и подстрокой [math]S[/math], и подстрокой [math]T[/math].

Алгоритм

Данный алгоритм основывается на методе половинного деления. Пусть длина наибольшей общей подстроки будет [math]x[/math]. Заметим, что у строк [math]S[/math] и [math]T[/math] обязательно найдется общая подстрока длины [math]y[/math] ∈ [0 .. [math]x[/math]], так как в качестве такой строки можно взять префикс наибольшей общей подстроки. Рассмотрим функцию [math]f[/math] : [0 .. min(len([math]S[/math]), len([math]T[/math]))] → {0, 1}, которая для [math]i[/math] из области определения равна 1, если у строк [math]S[/math] и [math]T[/math] есть общая подстрока длины [math]i[/math], иначе она равна 0. Согласно замечанию, функция [math]f[/math] должна по мере возрастания [math]i[/math] быть равной 1 до некоторого момента, а затем обращаться в 0. Собственно, максимальное значение, при котором функция принимает значение 1, является длиной наибольшей общей подстроки. Таким образом требуется с помощью бинарного поиска найти это значение. В ходе работы придется проверять наличие общей подстроки заданной длины. При этом предполагается использовать хеширование, чтобы улучшить асимптотику алгоритма. Делается это следующим образом: 1) у строки [math]S[/math] хешируем подстроки заданной длины и полученные хеши записываем в Set. 2) у строки [math]T[/math] хешируем подстроки заданной длины и в случае совпадения хеша с элементом Set выполняем посимвольную проверку на совпадение подстрок. Предполагается, что хеширование будет проводится так же, как и в алгоритме Рабина-Карпа.

Псевдокод

int f(int) - функция описанная в алгоритме

int findGCS(int left, int right)
  if (left >= right)
    return left
  val = (left + right) / 2
  if (f(val) == 1)
    return findGCS(val, right)
  else
    return findGCS(left, val - 1)

Тогда для поиска длины наибольшей общей подстроки требуется вызвать findGCS со следующими параметрами:

n = min(len(S), len(T))
result = findGCS(0, n)

Время работы

Проведем оценку асимптотики времени работы предложенного алгоритма. Посмотрим сколько нам потребуется действий на каждом шаге бинарного поиска. Во-первых, хеширование подстрок строки [math]S[/math] и запись их в Set требует O(len([math]S[/math])) шагов. Во-вторых, хеширование подстрок строки [math]T[/math] и проверка их наличия в Set требует O(len([math]T[/math])). В приведенных рассуждениях предполагается, что операции записи в Set и проверка наличия элемента в Set работают за амортизированную O(1). Поскольку хешировали с помощью этого метода, то это занимает линейное время. Значит,на каждый шаг бинарного поиска требуется O(max(len([math]S[/math]), len([math]T[/math]))) действий. На самом деле требуется несколько больше времени, поскольку совпадение хешей не дает гарантии совпадения подстрок, однако чтобы это было справедливо с большой вероятностью, достаточно проверить совпадение лишь нескольких произвольных символов, вместо полной проверки. Тогда на это потребуется некоторое константное число операций, что маскируется с помощью O. Заметим, что всего для завершения бинарного поиска потребуется O(log(min(len([math]S[/math]), len([math]T[/math])))) шагов. Следовательно, суммарное время работы алгоритма будет O(log(min(len([math]S[/math]), len([math]T[/math]))) * max(len([math]S[/math]), len([math]T[/math]))) действий.

Литература

Кормен Т., Лейзерсон Ч., Ривест Р. Алгоритмы: построение и анализ. — 2-е изд. — М.: Издательский дом «Вильямс», 2007. — С. 1296.