Изменения

Перейти к: навигация, поиск

Префикс-функция

2320 байт добавлено, 19:33, 4 сентября 2022
м
rollbackEdits.php mass rollback
{{Определение
|definition = '''Префикс-функция''' ''(англ. prefix-function)'' от строки {{---}} массив длин наибольших [[Период_и_бордер,_их_связь#Определения|бордеров]] для каждой позиции этой строки}}
Здесь и далее считаем, что символы в строках нумеруются с <tex>10</tex>.
Определим префикс-функцию от строки <tex>s</tex> в позиции <tex>i</tex> следующим образом: <tex>\pi(s, i) = \max\limits_{k = 1..\ldots i - 1} \{k : </tex> <tex>s[0 \ldots k - 1..k] = s[i - k + 1..\ldots i] \}</tex>. Если мы не нашли такого <tex>k</tex>, то <tex>\pi(s, i)=0</tex>.
==Наивный алгоритм==
Наивный алгоритм вычисляет префикс -функцию непосредственно по определению, сравнивая префиксы и суффиксы строк. Обозначим длину строки за <tex>n</tex>. Будем считать, что префикс-функция хранится в массиве <tex> p </tex>.
===Псевдокод===
'''int'''[] prefixFunction('''string''' s): '''int'''[] p = '''int'''[s.length] fill(<tex>\pi</tex>p, 0) '''for''' i = 1 0 '''to''' ns.length - 1 '''for''' k = 1 0 '''to''' i- 1 '''if''' s[10..k] == s[i - k + 1..i] <tex>\pi</tex>p[i] = k '''return''' <tex>\pi</tex>p
===Пример===
==Эффективный алгоритм==
Вносятся несколько важных замечаний:
*Заметим, что <tex>\pip[i + 1] \leqslant \pip[i] + 1</tex>. Чтобы показать это, рассмотрим суффикс,оканчивающийся на позиции <tex>i + 1</tex> и имеющий длину <tex>\pip[i + 1]</tex>, удалив из него последний символ, мы получим суффикс, оканчивающийся на позиции <tex>i</tex> и имеющий длину <tex>\pip[i + 1] - 1</tex>, следовательно неравенство <tex>\pip[i + 1] > \pip[i] + 1</tex> неверно.*Избавимся от явных сравнений строк. Пусть мы вычислили <tex>\pip[i]</tex>, тогда, если <tex>s[i + 1] = s[\pip[i]]</tex>, то <tex>\pip[i + 1] = \pip[i] + 1</tex>. Если окажется, что <tex>s[i + 1] \ne s[\pip[i]]</tex>, то нужно попытаться попробовать подстроку меньшей длины. Хотелось бы сразу перейти к такому [[Период_и_бордер,_их_связь#Определения|бордеру]] наибольшей длины, для этого подберем такое <tex>k</tex>, что <tex>k = \pi(p[i) ] - 1</tex>. Делаем это следующим образом. За исходное <tex>k</tex> необходимо взять <tex>\pi(p[i - 1)]</tex>, что следует из первого пункта. В случае, когда символы <tex>s[k+1]</tex> и <tex>s[i]</tex> не совпадают, <tex>\pi(p[k)- 1]</tex> {{---}} следующее потенциальное наибольшее значение <tex>k</tex>, что видно из рисунка. Последнее утверждение верно, пока <tex>k>0</tex>, что позволит всегда найти его следующее значение. Если <tex>k=0</tex>, то <tex>\pi(p[i)]=1</tex> при <tex>s[i] = s[1]</tex> , иначе <tex>\pi(p[i)]=0</tex>.
[[Файл:mprfx.jpg|800px]]
 
===Псевдокод===
'''int'''[] prefixFunction('''string''' s): <tex>\pi</tex> p[10] = 0 '''for''' i = 2 1 '''to''' ns.length - 1 k = <tex>\pi</tex>p[i-1] '''while''' k > 0 '''and''' s[i] != s[k + 1] k = <tex>\pi</tex>p[k- 1] '''if''' s[i] == s[k + 1] k++ <tex>\pi</tex> p[i] = k '''return''' <tex>\pi</tex>p
===Время работы===
== Построение префикс-функции по Z-функции==
=== Постановка задачи ===
Дан массив с корректной <tex> z[[Z-функция | Z-</tex> функцией ]] для строки <tex>s</tex>, получить за <tex>O(n)</tex> массив с префикс{{---}}функцией для строки <tex>s</tex>.
=== Описание алгоритма ===
Пусть <tex>Z-</tex> функция хранится в массиве <tex>z[0 \ldots n-1..n],</tex> префикс. Префикс-функцию будем записывать в массив <tex>p[0 \ldots n-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]j </tex> то менять уже не нужно, так как это только уменьшит значение с позиции <tex>p[k]i </tex>, так как если мы установили в а потом пытаемся установить значение <tex>j-' </tex>ую позицию с c позиции <tex>ki' </tex>, где причём <tex>k i < i' </tex> и <tex> i + j = i' + j' </tex>, то такое изменение с позиции <tex> i' </tex> только уменьшит значение <tex> p[i + j]</tex>. Действительно, значение после первого присвоения <tex>p[i + j] = j > j' = p[i' + 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.. \dots z[i] - 1</tex>, пока не наткнемся на уже инициализированный элемент. В итоге каждый Слева от него все значения тоже нет смысла обновлять, поэтому прерываем эту итерацию.  Убедимся, что алгоритм работает за линейное время (см. псевдокод). Каждый элемент устанавливается ровно один раз. Дальше на нем может случиться только <tex>\mathrm{break}</tex>. Поэтому в итоге внутренний цикл суммарно отработает за количество установленных значений и количество <tex>p\mathrm{break}</tex>. Количество установленных значений {{---}} <tex> n</tex> будет просмотрен не более двух раз, то есть . А число операции <tex>\mathrm{break}</tex> тоже будет не больше <tex> 2 n</tex>, так как каждый <tex>\cdot nmathrm{break}</tex> переводит внешний цикл на следующую итерацию, откуда получаем итоговую асимптотику <tex>O(n).</tex>.
[[Файл:ZP4.jpg|800px]]
=== Псевдокод ===
'''int'''[] buildPrefixFunctionFromZFunction('''int'''[] z): '''int'''[] p = '''new int'''[z.length] fill(p, 0) '''for''' i = 2 1 '''to''' z.length - 1
'''for''' j = z[i] - 1 '''downto''' 0
'''if''' p[i + j] > 0
p[i + j] = j + 1
'''return''' p
 
=== Доказательство корректности ===
Пусть наш алгоритм неправильно посчитал какие-то значение <tex>p[i]</tex>. Пусть <tex> ans[1..n]</tex> это массив, содержащий корректную префикс-функцию. Рассмотрим позицию в которой наш алгоритм посчитал некорректно, пусть это позиция <tex>i</tex>. Заметим, что <tex>ans[i]\geqslant p[i]</tex> так как алгоритм записывает длину подстроки, оканчивающуюся на <tex>i</tex>{{---}}ом символе, соответственно правильный ответ, не может быть меньше нашего. Предположим, что <tex>ans[i] > p[i], </tex> посмотрим на значение <tex>z[i - ans[i] + 1]</tex> оно больше либо равно <tex>ans[i]</tex>. Но тогда наш алгоритм бы записал в <tex> p[i]</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]- 1]</tex>. Обратим внимание, что <tex>s[p[i]- 1]</tex> нам уже известно, так как <tex>p[i] - 1 < i</tex>.
=== Реализация ===
s += new character
'''else'''
s += s[p[i]- 1]
'''return''' s
Докажем, что если нам дали корректную префикс-функцию, то наш алгоритм построит строку с такой же префикс-функцией. Также заметим, что строк с такой префикс-функцией может быть много, и алгоритм строит только одну из них.
Пусть <tex>p</tex> {{---}} данная префикс-функция, <tex>s'</tex> правильная строка, строку <tex>s</tex> построил наш алгоритм, <tex> q </tex> {{---}} массив значений префикс-функции для <tex>s</tex>.
Докажем корректность индукцией по длине массива префикс-функции полученной строки. Для начала заметим, что на предыдущие значения массива <tex> q </tex> прибавление нового символа не влияет, так как при подсчёте префикс-функции на <tex> i </tex>-ой позиции рассматриваются символы на позициях не больше <tex> i </tex>. Поэтому достаточно показать, что очередное значение префикс-функции будет вычислено правильно.
* База очевидна для строки длины <tex>1</tex>.
* Переход: пусть до <tex>n</tex>-ой позиции мы построили строку, что <tex>p[1..0 \ldots n - 1] = q[1..0 \ldots n - 1]</tex>. Возможны два случая:
** <tex>p[n] = 0</tex>. Тогда мы добавляем новый символ, поэтому <tex>q[n]</tex> тоже будет равно <tex>0</tex>.
** <tex>p[n] > 0</tex>. По свойствам префикс-функции Бордер строки <tex> s'[0 \ldots n - 1] </tex> имеет длину <tex> p[n]-1] = s'q[n-1] </tex> {{---}} суффикс и префикс строки . Поэтому если дописать к строке <tex> s' </tex> длины символ <tex> ps[q[n] - 1] </tex> продолжаются одним символом, значит, надо на текущую позицию то бордер нашей новой строки <tex> s [0 \ldots n] </tex> поставить символ станет равен <tex> s[p[n]] </tex>, как можно увидеть на [[Префикс-функция#Эффективный алгоритм | рисунке]]. Если значение  == Критерий корректности значений префикс-функции увеличивается, значит, текущим символом продолжается =={{Задача|definition = Дан массив значений префикс длины -функции некоторой строки <tex> p[n - 1] s</tex>, а из свойств следуетнеобходимо проверить, что корректен ли он за <tex> p[n - 1] \geqslant p[n] - 1 O(|s|)</tex>. По предположению индукцию значение Так же узнать размер минимального алфавита, при котором он корректен.}} === Решение ===Если выполняется неравенство <tex> q0 \leqslant p[n - i + 1] \leqslant p[i] + 1</tex> будет вычислено верно. А если значение , то мы можем построить строку из алгоритма выше, значит префикс-функции не увеличиваетсяфункция корректна. Найдем минимальный алфавит, значит, символ <tex> s[n] </tex> должен продолжить при котором префикс меньшей длины, а в текущее -функция корректна. Если значение префикс-функции запишется как раз длина нового [[Период_и_бордерв текущей ячейке больше нуля, буква известна и алфавит не нуждается в добавлении новой буквы. Иначе, необходимо исключить все ранее известные буквы,_их_связь#Определения|бордера]]возвращаясь и проверяя для меньших префиксов. Для этого будут Если все уже известные буквы использованы значения префикс-функции с меньшими индексами, которые посчитаны вернопонятно что, опять же по предположению индукциинеобходимо добавить новую букву.
=== Доказательство корректности ===
Докажем, что найденнный выше алфавит минимален от противного. Допустим, существует строка, использующая алфавит меньшей мощности. Рассмотрим первое вхождение буквы, которая есть в нашем алфавите, а в их отсутствует. Понятно, что для этого символа префикс-функция равна 0, т.к. мы добавили новую букву. Пройдемся циклом <tex>\mathrm{while}</tex> по подпрефиксам. Т.к. в меньшем решении буква не новая, то она увеличит подпрефикс и префикс-функция в новой строке будет отличаться от нуля в этом символе, а должна равняться нулю. Противоречие, следовательно не существует алфаивта меньшей мощности, чем найденный алгоритмом выше.
 
=== Псевдокод ===
'''bool''' is_correct('''int'''[] p):
'''for''' i = 0 '''to''' p.length - 1
'''if''' i > 0 && p[i] > p[i - 1] + 1 || p[i] < 0
'''return''' '''false'''
'''return''' '''true'''
'''int''' minimal_alphabet('''int'''[] p):
c = 1
s[0] = 0
'''for''' i = 1 '''to''' p.length - 1
'''if''' p[i] == 0
'''fill'''(used, false)
k = p[i - 1]
'''while''' k > 0
used[s[k]] = '''true'''
k = p[k - 1]
s[i] = -1
'''for''' j = 1 '''to''' c
'''if''' !used[j]
s[i] = j;
'''break'''
'''if''' s[i] == -1
s[i] = c++
'''else'''
s[i] = s[p[i] - 1]
'''return''' c
== См. также ==
== Источники информации ==
*[[wikipedia:ru:Префикс-функция | Википедия {{---}} Префикс-функция]]
*[http://e-maxx.ru/algo/prefix_function MAXimal :: algo {{---}} :: Префикс-функция]
* Кормен Т., Лейзерсон Ч., Ривест Р. Алгоритмы: построение и анализ. {{---}} 2-е изд. {{---}} М.: Издательский дом «Вильямс», 2007. {{---}} С. 1296 ISBN 978-5-8459-0857-5
[[Категория: Алгоритмы и структуры данных]]
[[Категория: Поиск подстроки в строке]]
[[Категория:Точный поиск]]
1632
правки

Навигация