Префикс-функция — различия между версиями
Shersh (обсуждение | вклад) (→Доказательство корректности алгоритма) |
|||
| Строка 1: | Строка 1: | ||
| − | Префикс-функция строки <tex>s</tex> {{---}} функция <tex>\pi(i) = \max\limits_{k = 1..i - 1} \{ 0, k : </tex> <tex>s[1..k] = s[i - k + 1..i] \}</tex>. | + | {{Определение |
| + | |definition = '''Префикс-функция''' ''(prefix-function)'' от строки(обозначается <tex>\pi(s,i)</tex>) - длина наибольшего префикса строки <tex>S[1..i]</tex>, который не совпадает с этой строкой и одновременно является ее суффиксом}} | ||
| + | |||
| + | Префикс-функция строки <tex>s</tex> {{---}} функция <tex>\pi(s, i) = \max\limits_{k = 1..i - 1} \{ 0, k : </tex> <tex>s[1..k] = s[i - k + 1..i] \}</tex>. | ||
Здесь и далее считаем, что символы в строках нумеруются с <tex>1</tex>. | Здесь и далее считаем, что символы в строках нумеруются с <tex>1</tex>. | ||
| − | == | + | ==Наивный алгоритм== |
Наивный алгоритм вычисляет префикс функцию непосредственно по определению, сравнивая префиксы и суффиксы строк. | Наивный алгоритм вычисляет префикс функцию непосредственно по определению, сравнивая префиксы и суффиксы строк. | ||
| Строка 37: | Строка 40: | ||
Всего <tex>O(n^2)</tex> итераций цикла, на каждой из который происходит сравнение строк за <tex>O(n)</tex>, что дает в итоге <tex>O(n^3)</tex>. | Всего <tex>O(n^2)</tex> итераций цикла, на каждой из который происходит сравнение строк за <tex>O(n)</tex>, что дает в итоге <tex>O(n^3)</tex>. | ||
| − | == | + | ==Эффективный алгоритм== |
Вносятся несколько важных замечаний: | Вносятся несколько важных замечаний: | ||
*Следует заметить, что <tex>\pi(i) \le \pi(i-1) + 1</tex>. По определению префикс функции верно, что <tex>s[1..\pi(i)] = s[i - \pi(i) + 1..i]</tex>. В частности, получается, что <tex>s[1..\pi(i) - 1] = s[i - \pi(i) + 1..i - 1]</tex>. Поскольку <tex>\pi</tex> это наибольший префикс равный суффиксу, то <tex>\pi(i - 1) \ge \pi(i) - 1</tex>. | *Следует заметить, что <tex>\pi(i) \le \pi(i-1) + 1</tex>. По определению префикс функции верно, что <tex>s[1..\pi(i)] = s[i - \pi(i) + 1..i]</tex>. В частности, получается, что <tex>s[1..\pi(i) - 1] = s[i - \pi(i) + 1..i - 1]</tex>. Поскольку <tex>\pi</tex> это наибольший префикс равный суффиксу, то <tex>\pi(i - 1) \ge \pi(i) - 1</tex>. | ||
| Строка 89: | Строка 92: | ||
== Литература == | == Литература == | ||
| − | Кормен Т., Лейзерсон Ч., Ривест Р. Алгоритмы: построение и анализ. {{---}} 2-е изд. {{---}} М.: Издательский дом «Вильямс», 2007. {{---}} С. 1296 | + | * Кормен Т., Лейзерсон Ч., Ривест Р. Алгоритмы: построение и анализ. {{---}} 2-е изд. {{---}} М.: Издательский дом «Вильямс», 2007. {{---}} С. 1296 ISBN 978-5-8459-0857-5 |
| − | [[Категория:Алгоритмы и структуры данных]] | + | [[Категория: Алгоритмы и структуры данных]] |
| − | [[Категория:Поиск подстроки в строке]] | + | [[Категория: Поиск подстроки в строке]] |
Версия 10:43, 12 мая 2014
| Определение: |
| Префикс-функция (prefix-function) от строки(обозначается ) - длина наибольшего префикса строки , который не совпадает с этой строкой и одновременно является ее суффиксом |
Префикс-функция строки — функция .
Здесь и далее считаем, что символы в строках нумеруются с .
Содержание
Наивный алгоритм
Наивный алгоритм вычисляет префикс функцию непосредственно по определению, сравнивая префиксы и суффиксы строк.
Псевдокод
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 character
else:
s += s[p[i]]
return s
Доказательство корректности алгоритма
Докажем, что если нам дали корректную префикс-функцию, то наш алгоритм построит строку с такой же префикс-функцией. Также заметим, что строк с такой префикс-функцией может быть много, и алгоритм строит только одну из них.
Пусть данная префикс-функция, правильная строка, строку построил наш алгоритм, массив значений префикс-функции для .
Докажем корректность индукцией по длине массива префикс-функции полученной строки. Для начала заметим, что на предыдущие значения массива прибавление нового символа не влияет, так как при подсчёте префикс-функции на -ой позиции рассматриваются символы на позициях не больше . Поэтому достаточно показать, что очередное значение префикс-функции будет вычислено правильно.
- База очевидна для строки длины .
- Переход: пусть до -ой позиции мы построили строку, что . Возможны два случая:
- . Тогда мы добавляем новый символ, поэтому тоже будет равно .
- . По свойствам префикс-функции — суффикс и префикс строки длины продолжаются одним символом, значит, надо на текущую позицию строки поставить символ . Если значение префикс-функции увеличивается, значит, текущим символом продолжается префикс длины , а из свойств следует, что . По предположению индукцию значение будет вычислено верно. А если значение префикс-функции не увеличивается, значит, символ должен продолжить префикс меньшей длины, а в текущее значение префикс-функции запишется как раз длина нового бордера. Для этого будут использованы значения префикс-функции с меньшими индексами, которые посчитаны верно, опять же по препдположению индукции.
Литература
- Кормен Т., Лейзерсон Ч., Ривест Р. Алгоритмы: построение и анализ. — 2-е изд. — М.: Издательский дом «Вильямс», 2007. — С. 1296 ISBN 978-5-8459-0857-5
