Префикс-функция — различия между версиями
Sergej (обсуждение | вклад) (→Описание алгоритма) |
Sergej (обсуждение | вклад) (→Описание алгоритма) |
||
| Строка 67: | Строка 67: | ||
=== Описание алгоритма === | === Описание алгоритма === | ||
Пусть <tex>Z-</tex> функция хранится в массиве <tex>z[1..n],</tex> префикс-функцию будем записывать в массив <tex>p[1..n]</tex>. | Пусть <tex>Z-</tex> функция хранится в массиве <tex>z[1..n],</tex> префикс-функцию будем записывать в массив <tex>p[1..n]</tex>. | ||
| − | Пойдем по массиву <tex>z</tex> слева направо, заметим, что если <tex>z[i] > 0, </tex> то для всех элементов с индексом <tex>i + j</tex>, где <tex>0 \leqslant j < z[i] </tex> значение <tex>p[i + j] </tex> будет не меньше, чем длина подстроки с <tex> i </tex> по <tex> i + j</tex>, что равно <tex>j + 1, </tex> в силу определения префикс и <tex>Z</tex> функции. Также заметим, что если мы уже установили значение в какое-то <tex>p[k]</tex> то менять уже не нужно, так как это только уменьшит значение <tex>p[k]</tex>, так как если мы установили в <tex>j-</tex>ую позицию с позиции <tex>k</tex>, где <tex>k < i</tex>, | + | Пойдем по массиву <tex>z</tex> слева направо, заметим, что если <tex>z[i] > 0, </tex> то для всех элементов с индексом <tex>i + j</tex>, где <tex>0 \leqslant j < z[i] </tex> значение <tex>p[i + j] </tex> будет не меньше, чем длина подстроки с <tex> i </tex> по <tex> i + j</tex>, что равно <tex>j + 1, </tex> в силу определения префикс и <tex>Z</tex> функции. Также заметим, что если мы уже установили значение в какое-то <tex>p[k]</tex> то менять уже не нужно, так как это только уменьшит значение <tex>p[k]</tex>, так как если мы установили в <tex>j-</tex>ую позицию с позиции <tex>k</tex>, где <tex>k < i</tex>, то такое изменение только уменьшит значение <tex> p[j]</tex>. В итоге получаем алгоритм: идем слева направо по массиву <tex>z</tex> и пусть мы сейчас находимся на позиции <tex>i</tex>, тогда пытаемся записать в <tex>p</tex> от позиции <tex>i + z[i] - 1 </tex> до <tex>i</tex> значение <tex> j - i + 1,</tex> где <tex>j</tex> пробегает все значения <tex> 0.. z[i] - 1</tex>. В итоге каждый элемент в <tex>p</tex> будет просмотрен не более двух раз, то есть число операции будет не больше <tex> 2 \cdot n</tex> откуда получаем асимптотику <tex>O(n).</tex> |
[[Файл:ZP4.jpg|800px]] | [[Файл:ZP4.jpg|800px]] | ||
Версия 00:21, 22 мая 2014
| Определение: |
| Префикс-функция (англ. prefix-function) от строки — массив длин наибольших бордеров для каждой позиции этой строки |
Здесь и далее считаем, что символы в строках нумеруются с .
Определим префикс-функцию от строки в позиции следующим образом: . Если мы не нашли такого , то .
Содержание
Наивный алгоритм
Наивный алгоритм вычисляет префикс функцию непосредственно по определению, сравнивая префиксы и суффиксы строк. Обозначим длину строки за .
Псевдокод
int[] prefixFunction(string s)
fill(, 0)
for i = 1 to n
for k = 1 to i
if s[1..k] == s[i - k + 1..i]
[i] = k
return
Пример
Рассмотрим строку , для которой значение префикс-функции равно .
| Шаг | Строка | Значение функции |
|---|---|---|
| a | 0 | |
| ab | 0 | |
| abc | 0 | |
| abca | 1 | |
| abcab | 2 | |
| abcabc | 3 | |
| abcabcd | 0 |
Время работы
Всего итераций цикла, на каждой из который происходит сравнение строк за , что дает в итоге .
Эффективный алгоритм
Вносятся несколько важных замечаний:
- Заметим, что . Чтобы показать это, рассмотрим суффикс,оканчивающийся на позиции и имеющий длину , удалив из него последний символ, мы получим суффикс, оканчивающийся на позиции и имеющий длину , следовательно неравенство неверно.
- Избавимся от явных сравнений строк. Пусть мы вычислили , тогда, если , то . Если окажется, что , то нужно попытаться попробовать подстроку меньшей длины. Хотелось бы сразу перейти к такому бордеру наибольшей длины, для этого подберем такое , что . Делаем это следующим образом. За исходное необходимо взять , что следует из первого пункта. В случае, когда символы и не совпадают, — следующее потенциальное наибольшее значение , что видно из рисунка. Последнее утверждение верно, пока , что позволит всегда найти его следующее значение. Если , то при , иначе .
Псевдокод
int[] prefixFunction(string s)
[1] = 0
for i = 2 to n
k = [i-1]
while k > 0 and s[i] != s[k + 1]
k = [k]
if s[i] == s[k + 1]
k++
[i] = k
return
Время работы
Время работы алгоритма составит . Для доказательства этого нужно заметить, что итоговое количество итераций цикла определяет асимптотику алгоритма. Теперь стоит отметить, что увеличивается на каждом шаге не более чем на единицу, значит максимально возможное значение . Поскольку внутри цикла значение лишь уменьшается, получается, что не может суммарно уменьшиться больше, чем раз. Значит цикл в итоге выполнится не более раз, что дает итоговую оценку времени алгоритма .
Построение префикс-функции по Z-функции
Постановка задачи
Дан массив с корректной функцией для строки , получить за массив с префикс—функцией для строки .
Описание алгоритма
Пусть функция хранится в массиве префикс-функцию будем записывать в массив . Пойдем по массиву слева направо, заметим, что если то для всех элементов с индексом , где значение будет не меньше, чем длина подстроки с по , что равно в силу определения префикс и функции. Также заметим, что если мы уже установили значение в какое-то то менять уже не нужно, так как это только уменьшит значение , так как если мы установили в ую позицию с позиции , где , то такое изменение только уменьшит значение . В итоге получаем алгоритм: идем слева направо по массиву и пусть мы сейчас находимся на позиции , тогда пытаемся записать в от позиции до значение где пробегает все значения . В итоге каждый элемент в будет просмотрен не более двух раз, то есть число операции будет не больше откуда получаем асимптотику
Псевдокод
int[] buildPrefixFunctionFromZFunction(int[] z)
int[] p = new int[z.length]
for i = 2 to z.length - 1
for j = z[i] - 1 downto 0
if p[i + j] > 0
break
else
p[i + j] = j + 1
return p
Доказательство корректности
Пусть наш алгоритм неправильно посчитал какие-то значение . Пусть это массив, содержащий корректную префикс-функцию. Рассмотрим позицию в которой наш алгоритм посчитал некорректно, пусть это позиция . Заметим, что так как алгоритм записывает длину подстроки, оканчивающуюся на —ом символе, соответственно правильный ответ, не может быть меньше нашего. Предположим, что посмотрим на значение оно больше либо равно . Но тогда наш алгоритм бы записал в корректное значение.
Построение строки по префикс-функции
Постановка задачи
Восстановить строку по префикс-функции за , считая алфавит неограниченным.
Описание алгоритма
Пусть в массиве хранятся значения префикс-функции, в будет записан ответ. Пойдем по массиву слева направо.
Пусть мы хотим узнать значение . Для этого посмотрим на значение : если тогда в запишем новый символ, иначе . Обратим внимание, что нам уже известно, так как .
Реализация
string buildFromPrefix(int[] p):
s = ""
for i = 0 to p.length - 1
if p[i] == 0
s += new character
else
s += s[p[i]]
return s
Доказательство корректности алгоритма
Докажем, что если нам дали корректную префикс-функцию, то наш алгоритм построит строку с такой же префикс-функцией. Также заметим, что строк с такой префикс-функцией может быть много, и алгоритм строит только одну из них.
Пусть данная префикс-функция, правильная строка, строку построил наш алгоритм, массив значений префикс-функции для .
Докажем корректность индукцией по длине массива префикс-функции полученной строки. Для начала заметим, что на предыдущие значения массива прибавление нового символа не влияет, так как при подсчёте префикс-функции на -ой позиции рассматриваются символы на позициях не больше . Поэтому достаточно показать, что очередное значение префикс-функции будет вычислено правильно.
- База очевидна для строки длины .
- Переход: пусть до -ой позиции мы построили строку, что . Возможны два случая:
- . Тогда мы добавляем новый символ, поэтому тоже будет равно .
- . По свойствам префикс-функции — суффикс и префикс строки длины продолжаются одним символом, значит, надо на текущую позицию строки поставить символ . Если значение префикс-функции увеличивается, значит, текущим символом продолжается префикс длины , а из свойств следует, что . По предположению индукцию значение будет вычислено верно. А если значение префикс-функции не увеличивается, значит, символ должен продолжить префикс меньшей длины, а в текущее значение префикс-функции запишется как раз длина нового бордера. Для этого будут использованы значения префикс-функции с меньшими индексами, которые посчитаны верно, опять же по предположению индукции.
См. также
Источники информации
- Википедия — Префикс-функция
- MAXimal :: algo — Префикс-функция
- Кормен Т., Лейзерсон Ч., Ривест Р. Алгоритмы: построение и анализ. — 2-е изд. — М.: Издательский дом «Вильямс», 2007. — С. 1296 ISBN 978-5-8459-0857-5
