Алгоритм Манакера — различия между версиями
(→См. также) |
(→Идея) |
||
Строка 30: | Строка 30: | ||
# <tex>i > r</tex>, т.е. текущая позиция не попадает в границы самого правого из найденных палиндромов. Тогда просто запустим наивный алгоритм для позиции <tex>i</tex>. | # <tex>i > r</tex>, т.е. текущая позиция не попадает в границы самого правого из найденных палиндромов. Тогда просто запустим наивный алгоритм для позиции <tex>i</tex>. | ||
− | # <tex>i \leqslant r</tex>. Тогда попробуем воспользоваться значениями, посчитанным ранее. Отразим нашу текущую позицию внутри палиндрома <tex>[l;r] : j = (r - i) + l</tex>. Поскольку <tex>i</tex> и <tex>j</tex> — симметричные позиции, то если <tex>d1[j] = k</tex>, мы можем утверждать, что и <tex>d1[i] = k</tex>. Это объясняется тем, что палиндром симметричен относительно своей центральной позиции. Т.е. если имеем некоторый палиндром длины <tex>k</tex> с центром в позиции <tex>l \leqslant i \leqslant r</tex>, то в позиции <tex>j</tex>, симметричной <tex>i</tex> относительно отрезка <tex>[l; r]</tex> тоже может находиться палиндром длины <tex>k</tex>. Это можно лучше понять, посмотрев на рисунок. Снизу фигурными скобками обозначены равные подстроки. Однако стоит не забыть про один граничный случай: что если <tex>i + d1[j] - 1</tex> выходит за границы самого правого палиндрома? Так как информации о том, что происходит за границами это палинлрома у нас нет (а значит мы не можем утверждать, что симметрия сохраняется), то необходимо ограничить значение <tex>d1[i]</tex> следующим образом: <tex>d1[i] = min(r - i, d1[j])</tex>. После этого запустим наивный алгоритм, который будет увеличивать значение <tex>d1[i]</tex>, пока это возможно. | + | # <tex>i \leqslant r</tex>. Тогда попробуем воспользоваться значениями, посчитанным ранее. Отразим нашу текущую позицию внутри палиндрома <tex>[l;r] : j = (r - i) + l</tex>. Поскольку <tex>i</tex> и <tex>j</tex> — симметричные позиции, то если <tex>d1[j] = k</tex>, мы можем утверждать, что и <tex>d1[i] = k</tex>. Это объясняется тем, что палиндром симметричен относительно своей центральной позиции. Т.е. если имеем некоторый палиндром длины <tex>k</tex> с центром в позиции <tex>l \leqslant i \leqslant r</tex>, то в позиции <tex>j</tex>, симметричной <tex>i</tex> относительно отрезка <tex>[l; r]</tex> тоже может находиться палиндром длины <tex>k</tex>. Это можно лучше понять, посмотрев на рисунок. Снизу фигурными скобками обозначены равные подстроки. Однако стоит не забыть про один граничный случай: что если <tex>i + d1[j] - 1</tex> выходит за границы самого правого палиндрома? Так как информации о том, что происходит за границами это палинлрома у нас нет (а значит мы не можем утверждать, что симметрия сохраняется), то необходимо ограничить значение <tex>d1[i]</tex> следующим образом: <tex>d1[i] = \min(r - i, d1[j])</tex>. После этого запустим наивный алгоритм, который будет увеличивать значение <tex>d1[i]</tex>, пока это возможно. |
После каждого шага важно не забывать обновлять значения <tex>[l;r]</tex>. | После каждого шага важно не забывать обновлять значения <tex>[l;r]</tex>. |
Версия 21:44, 16 апреля 2016
Задача: |
Пусть дана строка палиндром. | . Требуется найти количество подстрок , являющиеся палиндромами. Более формально, все такие пары , что —
Содержание
Уточнение постановки
Легко увидеть, что таких подстрок в худшем случае будет
. Значит, нужно найти компактный способ хранения информации о них. Пусть — количество палиндромов нечетной длины с центром в позиции , а — аналогичная величина для палиндромов четной длины. Далее научимся вычислять значения этих массивов.Наивный алгоритм
Идея
Опишем сначала наивный алгоритм решения задачи. Чтобы посчитать ответ для позиции
, будем на каждом шаге увеличивать длину палиндрома с центром в и убеждаться, что рассматриваемая строка не перестала быть палиндромом, либо не произошел выход за границы массива. Очевидно, что такой алгоритм будет работать за .Псевдокод
//— исходная строка // — массивы для записи ответа (int[], int[]) calculate(string s): for i = 1 to n d1[i] = 1 while i - d1[i] > 0 and i + d1[i] <= n and s[i - d1[i]] == s[i + d1[i]] d1[i]++ d2[i] = 0 while i - d2[i] - 1 > 0 and i + d2[i] <= n and s[i - d2[i] - 1] == s[i + d2[i]] d2[i]++ return (d1, d2)
Алгоритм Манакера
Идея
Алгоритм, который будет описан далее, отличается от наивного тем, что использует значения, посчитанные ранее. Будем поддерживать границы самого правого из найденных палиндромов —
. Итак, пусть мы хотим вычислить — т.е. длину наибольшего палиндрома с центром в позиции . При этом все предыдущие значения в массиве уже посчитаны. Возможны два случая:- , т.е. текущая позиция не попадает в границы самого правого из найденных палиндромов. Тогда просто запустим наивный алгоритм для позиции .
- . Тогда попробуем воспользоваться значениями, посчитанным ранее. Отразим нашу текущую позицию внутри палиндрома . Поскольку и — симметричные позиции, то если , мы можем утверждать, что и . Это объясняется тем, что палиндром симметричен относительно своей центральной позиции. Т.е. если имеем некоторый палиндром длины с центром в позиции , то в позиции , симметричной относительно отрезка тоже может находиться палиндром длины . Это можно лучше понять, посмотрев на рисунок. Снизу фигурными скобками обозначены равные подстроки. Однако стоит не забыть про один граничный случай: что если выходит за границы самого правого палиндрома? Так как информации о том, что происходит за границами это палинлрома у нас нет (а значит мы не можем утверждать, что симметрия сохраняется), то необходимо ограничить значение следующим образом: . После этого запустим наивный алгоритм, который будет увеличивать значение , пока это возможно.
После каждого шага важно не забывать обновлять значения
.Заметим, что массив
считается аналогичным образом, нужно лишь немного изменить индексы.Псевдокод
Приведем код, который вычисляет значения массива
://
— исходная строка
int[] calculate1(string s):
int l = 0
int r = -1
for i = 1 to n
int k = 0
if i <= r
k = min(r - i, d[r - i + l])
while i + k + 1 <= n and i - k - 1 > 0 and s[i + k + 1] == s[i - k - 1]
k++
d1[i] = k
if i + k > r
l = i - k
r = i + k
return d1
Вычисление значений массива
://
— исходная строка
int[] calculate2(string s):
int l = 0
int r = -1
for i = 1 to n
int k = 0
if i <= r
k = min(r - i + 1, d[r - i + l + 1])
while i + k <= n and i - k - 1 > 0 and s[i + k] == s[i - k - 1]
k++
d2[i] = k
if i + k - 1 > r
l = i - k
r = i + k - 1
return d2
Оценка сложности
Внешний цикл в приведенном алгоритме выполняется ровно
раз, где — длина строки. Попытаемся понять, сколько раз будет выполнен внутренний цикл, ответственный за наивный подсчет значений. Заметим, что каждая итерация вложенного цикла приводит к увеличению на . Действительно, возможны следующие случаи:- , т.е. сразу будет запущен наивный алгоритм и каждая его итерация будет увеличивать значение хотя бы на .
-
- , но тогда, очевидно, ни одной итерации вложенного цикла выполнено не будет.
- , тогда каждая итерация вложенного цикла приведет к увеличению хотя бы на .
. Здесь опять два случая:
Т.к. значение
не может увеличиваться более раз, то описанный выше алгоритм работает за время .