Префикс-функция — различия между версиями
(→Псевдокод) |
|||
Строка 1: | Строка 1: | ||
{{Определение | {{Определение | ||
− | |definition = '''Префикс-функция''' ''(prefix-function)'' от строки | + | |definition = '''Префикс-функция''' ''(англ. prefix-function)'' от строки {{---}} длина наибольшего [[Период_и_бордер,_их_связь#Определения|бордера]] этой строки}} |
+ | Здесь и далее считаем, что символы в строках нумеруются с <tex>1</tex>. | ||
− | + | Определим префикс-функцию от строки <tex>s</tex> в позиции <tex>i</tex> следующим образом: <tex>\pi(s, i) = \max\limits_{k = 1..i - 1} \{k : </tex> <tex>s[1..k] = s[i - k + 1..i] \}</tex>. Если мы не нашли такого <tex>k</tex>, то <tex>\pi(s, i)=0</tex>. | |
− | |||
==Наивный алгоритм== | ==Наивный алгоритм== | ||
− | Наивный алгоритм вычисляет префикс функцию непосредственно по определению, сравнивая префиксы и суффиксы строк. | + | Наивный алгоритм вычисляет префикс функцию непосредственно по определению, сравнивая префиксы и суффиксы строк. Обозначим длину строки за <tex>n</tex> |
===Псевдокод=== | ===Псевдокод=== | ||
'''Prefix_function''' (<tex>s</tex>) | '''Prefix_function''' (<tex>s</tex>) | ||
fill(<tex>\pi</tex>, 0) | fill(<tex>\pi</tex>, 0) | ||
− | '''for''' i = 1 '''to''' | + | '''for''' i = 1 '''to''' n |
'''for''' k = 1 '''to''' i | '''for''' k = 1 '''to''' i | ||
'''if''' s[1..k] == s[i - k + 1..i]) | '''if''' s[1..k] == s[i - k + 1..i]) | ||
Строка 42: | Строка 42: | ||
==Эффективный алгоритм== | ==Эффективный алгоритм== | ||
Вносятся несколько важных замечаний: | Вносятся несколько важных замечаний: | ||
− | *Заметим, что <tex>\pi[i + 1] \ | + | *Заметим, что <tex>\pi[i + 1] \leqslant \pi[i] + 1</tex>. Чтобы показать это, рассмотрим суффикс,оканчивающийся на позиции <tex>i + 1</tex> и имеющий длину <tex>\pi[i + 1]</tex>, удалив из него последний символ, мы получим суффикс, оканчивающийся на позиции <tex>i</tex> и имеющий длину <tex>\pi[i + 1] - 1</tex>, следовательно неравенство <tex>\pi[i + 1] > \pi[i] + 1</tex> неверно. |
− | *Избавимся от явных сравнений строк. Пусть мы вычислили <tex>\pi[i]</tex>, тогда, если <tex>s[i + 1] = s[\pi[i]]</tex>, то <tex>\pi[i + 1] = \pi[i] + 1</tex>. Если окажется, что <tex>s[i + 1] \ne s[\pi[i]]</tex>, то нужно попытаться попробовать подстроку меньшей длины. | + | *Избавимся от явных сравнений строк. |
+ | **Пусть мы вычислили <tex>\pi[i]</tex>, тогда, если <tex>s[i + 1] = s[\pi[i]]</tex>, то <tex>\pi[i + 1] = \pi[i] + 1</tex>. | ||
+ | **Если окажется, что <tex>s[i + 1] \ne s[\pi[i]]</tex>, то нужно попытаться попробовать подстроку меньшей длины. Хотелось бы сразу перейти к такому [[Период_и_бордер,_их_связь#Определения|бордеру]] наибольшей длины, для этого подберем такое <tex>k</tex>, что <tex>k = \pi(i) - 1</tex>. Делаем это следующим образом. За исходное <tex>k</tex> необходимо взять <tex>\pi(i - 1)</tex>, что следует из первого пункта. В случае, когда символы <tex>s[k+1]</tex> и <tex>s[i]</tex> не совпадают, <tex>\pi(k)</tex> {{---}} следующее потенциальное наибольшее значение <tex>k</tex>, что видно из рисунка. Последнее утверждение верно, пока <tex>k>0</tex>, что позволит всегда найти его следующее значение. Если <tex>k=0</tex>, то <tex>\pi(i)=1</tex> при <tex>s[i] = s[1]</tex> , иначе <tex>\pi(i)=0</tex>. | ||
[[Файл:Prfx.jpg]] | [[Файл:Prfx.jpg]] | ||
Строка 49: | Строка 51: | ||
'''Prefix_function''' (<tex>s</tex>) | '''Prefix_function''' (<tex>s</tex>) | ||
<tex>\pi</tex>[1] = 0 | <tex>\pi</tex>[1] = 0 | ||
− | + | '''for''' i = 2 '''to''' n | |
− | '''for''' i = 2 '''to''' | ||
k = <tex>\pi</tex>[i-1] | k = <tex>\pi</tex>[i-1] | ||
'''while''' k > 0 && s[i] != s[k + 1] | '''while''' k > 0 && s[i] != s[k + 1] | ||
Строка 91: | Строка 92: | ||
* Переход: пусть до <tex>n</tex>-ой позиции мы построили строку, что <tex>p[1..n - 1] = q[1..n - 1]</tex>. Возможны два случая: | * Переход: пусть до <tex>n</tex>-ой позиции мы построили строку, что <tex>p[1..n - 1] = q[1..n - 1]</tex>. Возможны два случая: | ||
** <tex>p[n] = 0</tex>. Тогда мы добавляем новый символ, поэтому <tex>q[n]</tex> тоже будет равно <tex>0</tex>. | ** <tex>p[n] = 0</tex>. Тогда мы добавляем новый символ, поэтому <tex>q[n]</tex> тоже будет равно <tex>0</tex>. | ||
− | ** <tex>p[n] > 0</tex>. По свойствам префикс-функции <tex> s'[p[n]] = s'[n] </tex> {{---}} суффикс и префикс строки <tex> s' </tex> длины <tex> p[n] </tex> продолжаются одним символом, значит, надо на текущую позицию строки <tex> s </tex> поставить символ <tex> s[p[n]] </tex>. Если значение префикс-функции увеличивается, значит, текущим символом продолжается префикс длины <tex> p[n - 1] </tex>, а из свойств следует, что <tex> p[n - 1] \geqslant p[n] - 1 </tex>. По предположению индукцию значение <tex> q[n - 1] </tex> будет вычислено верно. А если значение префикс-функции не увеличивается, значит, символ <tex> s[n] </tex> должен продолжить префикс меньшей длины, а в текущее значение префикс-функции запишется как раз длина нового бордера. Для этого будут использованы значения префикс-функции с меньшими индексами, которые посчитаны верно, опять же по | + | ** <tex>p[n] > 0</tex>. По свойствам префикс-функции <tex> s'[p[n]] = s'[n] </tex> {{---}} суффикс и префикс строки <tex> s' </tex> длины <tex> p[n] </tex> продолжаются одним символом, значит, надо на текущую позицию строки <tex> s </tex> поставить символ <tex> s[p[n]] </tex>. Если значение префикс-функции увеличивается, значит, текущим символом продолжается префикс длины <tex> p[n - 1] </tex>, а из свойств следует, что <tex> p[n - 1] \geqslant p[n] - 1 </tex>. По предположению индукцию значение <tex> q[n - 1] </tex> будет вычислено верно. А если значение префикс-функции не увеличивается, значит, символ <tex> s[n] </tex> должен продолжить префикс меньшей длины, а в текущее значение префикс-функции запишется как раз длина нового [[Период_и_бордер,_их_связь#Определения|бордера]]. Для этого будут использованы значения префикс-функции с меньшими индексами, которые посчитаны верно, опять же по предположению индукции. |
− | == | + | == Источники информации == |
* Кормен Т., Лейзерсон Ч., Ривест Р. Алгоритмы: построение и анализ. {{---}} 2-е изд. {{---}} М.: Издательский дом «Вильямс», 2007. {{---}} С. 1296 ISBN 978-5-8459-0857-5 | * Кормен Т., Лейзерсон Ч., Ривест Р. Алгоритмы: построение и анализ. {{---}} 2-е изд. {{---}} М.: Издательский дом «Вильямс», 2007. {{---}} С. 1296 ISBN 978-5-8459-0857-5 | ||
+ | * | ||
+ | |||
+ | == См. также == | ||
+ | |||
[[Категория: Алгоритмы и структуры данных]] | [[Категория: Алгоритмы и структуры данных]] | ||
[[Категория: Поиск подстроки в строке]] | [[Категория: Поиск подстроки в строке]] |
Версия 17:36, 14 мая 2014
Определение: |
Префикс-функция (англ. prefix-function) от строки — длина наибольшего бордера этой строки |
Здесь и далее считаем, что символы в строках нумеруются с
.Определим префикс-функцию от строки
в позиции следующим образом: . Если мы не нашли такого , то .Содержание
Наивный алгоритм
Наивный алгоритм вычисляет префикс функцию непосредственно по определению, сравнивая префиксы и суффиксы строк. Обозначим длину строки за
Псевдокод
Prefix_function () fill( , 0) for i = 1 to n for k = 1 to i 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 for i = 2 to n k = [i-1] 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