Алгоритм Манакера — различия между версиями
(→Псевдокод) |
|||
| Строка 12: | Строка 12: | ||
===Псевдокод=== | ===Псевдокод=== | ||
| − | + | <font color=green>// <tex>s</tex> {{---}} исходная строка</font> | |
| − | + | <font color=green>// <tex>d1, d2</tex> {{---}} массивы для записи ответа</font> | |
| − | '''for''' i = 1 '''to''' n | + | '''(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) | ||
| + | |||
==Алгоритм Манакера== | ==Алгоритм Манакера== | ||
===Идея=== | ===Идея=== | ||
Версия 14:25, 4 апреля 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 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
Вычисление значений массива :
// — исходная строка
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
Оценка сложности
Внешний цикл в приведенном алгоритме выполняется ровно раз, где — длина строки. Попытаемся понять, сколько раз будет выполнен внутренний цикл, ответственный за наивный подсчет значений. Заметим, что каждая итерация вложенного цикла приводит к увеличению на . Действительно, возможны следующие случаи:
- , т.е. сразу будет запущен наивный алгоритм и каждая его итерация будет увеличивать значение хотя бы на .
- . Здесь опять два случая:
- , но тогда, очевидно, ни одной итерации вложенного цикла выполнено не будет.
- , тогда каждая итерация вложенного цикла приведет к увеличению хотя бы на .
Т.к. значение не может увеличиваться более раз, то описанный выше алгоритм работает за линейное время.
