Префикс-функция — различия между версиями
Sergej (обсуждение | вклад) (→Доказательство корректности алгоритма) |
Shersh (обсуждение | вклад) (→Построение строки по префикс-функции: некоторые мелкие правки) |
||
| Строка 58: | Строка 58: | ||
==Построение строки по префикс-функции== | ==Построение строки по префикс-функции== | ||
===Постановка задачи=== | ===Постановка задачи=== | ||
| − | Восстановить строку по префикс-функции за <tex>O(N)</tex> | + | Восстановить строку по префикс-функции за <tex>O(N)</tex>, считая алфавит неограниченным. |
===Описание алгоритма=== | ===Описание алгоритма=== | ||
| − | |||
Пусть в массиве <tex>p</tex> хранятся значения префикс-функции, в <tex>s</tex> будет записан ответ. Пойдем по массиву <tex>p</tex> слева направо. | Пусть в массиве <tex>p</tex> хранятся значения префикс-функции, в <tex>s</tex> будет записан ответ. Пойдем по массиву <tex>p</tex> слева направо. | ||
| − | |||
| − | == | + | Пусть мы хотим узнать значение <tex>s[i]</tex>. Для этого посмотрим на значение <tex>p[i]</tex>: если <tex>p[i] =0</tex> тогда в <tex>s[i]</tex> запишем новый символ, иначе <tex>s[i] = s[p[i]]</tex>. Обратим внимание, что <tex>s[p[i]]</tex> нам уже известно, так как <tex>p[i] < i</tex>. |
| − | '''string''' buildFromPrefix('''int'''[] p) | + | |
| − | + | '''string''' buildFromPrefix('''int'''[] p): | |
| − | + | s = "" | |
| + | '''for''' i = 0 '''to''' p.length - 1: | ||
| + | '''if''' p[i] == 0: | ||
s += new char | s += new char | ||
| − | + | '''else:''' | |
s += s[p[i]] | s += s[p[i]] | ||
| − | + | '''return''' s | |
===Доказательство корректности алгоритма=== | ===Доказательство корректности алгоритма=== | ||
Докажем, что если нам дали корректную префикс-функцию, то наш алгоритм построит строку с такой же префикс-функцией. Также заметим, что строк с такой префикс-функцией может быть много, и алгоритм строит только одну из них. | Докажем, что если нам дали корректную префикс-функцию, то наш алгоритм построит строку с такой же префикс-функцией. Также заметим, что строк с такой префикс-функцией может быть много, и алгоритм строит только одну из них. | ||
| − | Пусть <tex>p</tex> данная префикс-функция, <tex>s'</tex> правильная строка, <tex>s</tex> | + | Пусть <tex>p</tex> данная префикс-функция, <tex>s'</tex> правильная строка, строку <tex>s</tex> построил наш алгоритм, <tex> q </tex> массив значений префикс-функции для <tex>s</tex>. |
Докажем корректность индукцией по длине массива префикс-функции полученной строки. | Докажем корректность индукцией по длине массива префикс-функции полученной строки. | ||
| − | База очевидна для строки длиной <tex>1</tex>. | + | * База очевидна для строки длиной <tex>1</tex>. |
| − | Переход: | + | * Переход: пусть до <tex>n</tex>-ой позиции мы построили строку, что <tex>p[1..n - 1] = q[1..n - 1]</tex>. Возможны два случая: |
<tex>1)</tex> <tex>p[n] = 0</tex>. Тогда мы добавляем новый символ, поэтому <tex>q[n]</tex> тоже будет равно <tex>0</tex>. Также, предыдущие значения <tex>q</tex> не поменяются и останутся верными. | <tex>1)</tex> <tex>p[n] = 0</tex>. Тогда мы добавляем новый символ, поэтому <tex>q[n]</tex> тоже будет равно <tex>0</tex>. Также, предыдущие значения <tex>q</tex> не поменяются и останутся верными. | ||
Версия 14:43, 2 мая 2014
Префикс-функция строки — функция .
Содержание
Алгоритм
Наивный алгоритм вычисляет префикс функцию непосредственно по определению, сравнивая префиксы и суффиксы строк.
Псевдокод
Prefix_function () = [0,..,0] for i = 1 to n for k = 1 to i - 1 if s[1..k] == s[i - k + 1..i] [i] = k return
Пример
Рассмотрим строку abcabcd, для которой значение префикс-функции равно .
| Шаг | Строка | Значение функции |
|---|---|---|
| a | 0 | |
| ab | 0 | |
| abc | 0 | |
| abca | 1 | |
| abcab | 2 | |
| abcabc | 3 | |
| abcabcd | 0 |
Время работы
Всего итераций цикла, на каждой из который происходит сравнение строк за , что дает в итоге .
Оптимизация
Вносятся несколько важных замечаний:
- Следует заметить, что . По определению префикс функции верно, что . В частности, получается, что . Поскольку это наибольший префикс равный суффиксу, то .
- Избавимся от явных сравнений строк. Для этого подберем такое , что . Делаем это следующим образом. За исходное необходимо взять , что следует из первого пункта. В случае, когда символы и не совпадают, — следующее потенциальное наибольшее значение , что видно из рисунка. Последнее утверждение верно, пока , что позволит всегда найти его следующее значение. Если , то при , иначе .
Псевдокод
Prefix_function () [1] = 0 k = 0 for i = 2 to n while k > 0 && s[i] != s[k + 1] k = [k] if s[i] == s[k + 1] k++ [i] = k return
Время работы
Время работы алгоритма составит . Для доказательства этого нужно заметить, что итоговое количество итераций цикла определяет асимптотику алгоритма. Теперь стоит отметить, что увеличивается на каждом шаге не более чем на единицу, значит максимально возможное значение . Поскольку внутри цикла значение лишь уменьшается, получается, что не может суммарно уменьшиться больше, чем раз. Значит цикл в итоге выполнится не более раз, что дает итоговую оценку времени алгоритма .
Построение строки по префикс-функции
Постановка задачи
Восстановить строку по префикс-функции за , считая алфавит неограниченным.
Описание алгоритма
Пусть в массиве хранятся значения префикс-функции, в будет записан ответ. Пойдем по массиву слева направо.
Пусть мы хотим узнать значение . Для этого посмотрим на значение : если тогда в запишем новый символ, иначе . Обратим внимание, что нам уже известно, так как .
string buildFromPrefix(int[] p):
s = ""
for i = 0 to p.length - 1:
if p[i] == 0:
s += new char
else:
s += s[p[i]]
return s
Доказательство корректности алгоритма
Докажем, что если нам дали корректную префикс-функцию, то наш алгоритм построит строку с такой же префикс-функцией. Также заметим, что строк с такой префикс-функцией может быть много, и алгоритм строит только одну из них.
Пусть данная префикс-функция, правильная строка, строку построил наш алгоритм, массив значений префикс-функции для .
Докажем корректность индукцией по длине массива префикс-функции полученной строки.
- База очевидна для строки длиной .
- Переход: пусть до -ой позиции мы построили строку, что . Возможны два случая:
. Тогда мы добавляем новый символ, поэтому тоже будет равно . Также, предыдущие значения не поменяются и останутся верными.
. Предположим, что . Заметим, что подстрока с по оканчивается на -ом символе. По предположению индукции наш алгоритм построил правильную строку до символа, следовательно, . Представим, что , тогда получается, что префикс с в строке , является подстрокой заканчивающийся на -ом символе, но тогда возникает противоречие с тем, что массив корректный. Также, предыдущие значения не поменяются и останутся верными.
Простой пример некорректного , тогда по алгоритму получится строка . Очевидно, что префикс-функции не будут совпадать.
Литература
Кормен Т., Лейзерсон Ч., Ривест Р. Алгоритмы: построение и анализ. — 2-е изд. — М.: Издательский дом «Вильямс», 2007. — С. 1296.
