Алгоритм Манакера — различия между версиями
(→Псевдокод) |
(→Псевдокод) |
||
| Строка 42: | Строка 42: | ||
<font color=green>// <tex>s</tex> {{---}} исходная строка</font> | <font color=green>// <tex>s</tex> {{---}} исходная строка</font> | ||
| − | '''int''' l = 0 | + | '''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 | ||
Вычисление значений массива <tex>d2</tex>: | Вычисление значений массива <tex>d2</tex>: | ||
<font color=green>// <tex>s</tex> {{---}} исходная строка</font> | <font color=green>// <tex>s</tex> {{---}} исходная строка</font> | ||
| − | '''int''' l = 0 | + | '''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 | ||
===Оценка сложности=== | ===Оценка сложности=== | ||
Версия 14:29, 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[] 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
Оценка сложности
Внешний цикл в приведенном алгоритме выполняется ровно раз, где — длина строки. Попытаемся понять, сколько раз будет выполнен внутренний цикл, ответственный за наивный подсчет значений. Заметим, что каждая итерация вложенного цикла приводит к увеличению на . Действительно, возможны следующие случаи:
- , т.е. сразу будет запущен наивный алгоритм и каждая его итерация будет увеличивать значение хотя бы на .
- . Здесь опять два случая:
- , но тогда, очевидно, ни одной итерации вложенного цикла выполнено не будет.
- , тогда каждая итерация вложенного цикла приведет к увеличению хотя бы на .
Т.к. значение не может увеличиваться более раз, то описанный выше алгоритм работает за линейное время.
