Изменения

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

Z-функция

4163 байта добавлено, 19:12, 4 сентября 2022
м
rollbackEdits.php mass rollback
{{Определение
|definition = '''Z-функция''' (англ. ''Z-function'') от строки <tex>S</tex> и позиции <tex>x</tex> — это длина максимального префикса подстроки, начинающейся с позиции <tex>x</tex> в строке <tex>S</tex>, который одновременно является и префиксом всей строки <tex>S</tex>. Более формально, <tex>Z[i](s) = \max k \mid s[i\, \mathinner{\ldotp\ldotp}ldots \, i + k] = s[0 \mathinner{\ldotp\ldotp} ldots k]</tex>. <!-- проинлайнил \twodots из clrscode -->
Значение Z-функции от первой позиции не определено, поэтому его обычно приравнивают к нулю или к длине строки.
'''while''' i + zf[i] < n '''and''' s[zf[i]] == s[i + zf[i]]
zf[i]++
'''if''' i + zf[i] >= right
left = i
right = i + zf[i]
==Построение строки по Z-функции==
{{Задача
|definition= Восстановить Необходимо восстановить строку по Z-функции за <tex>O(n)</tex>, считая алфавит ограниченным.
}}
 
===Описание алгоритма===
 
Пусть в массиве <tex>z</tex> хранятся значения Z-функции, в <tex>s</tex> будет записан ответ. Пойдем по массиву <tex>z</tex> слева направо.
Нужно узнать значение <tex>s[i]</tex>. Для этого посмотрим на значение <tex>z[i]</tex>: если <tex>z[i] = 0</tex>, тогда в <tex>s[i]</tex> запишем ещё не использованный символ или последний использованный символ алфавита, если мы уже использовали все символы. Если <tex>z[i] \neq 0</tex>, то нам нужно записать префикс длины <tex>z[i]</tex> строки <tex>s</tex>. Но если при посимвольном записывании этого префикса в конец строки <tex>s</tex> мы нашли такой <tex>j</tex> (индекс последнего символа строки), что <tex>z[j]</tex> больше, чем длина оставшейся незаписанной части префикса, то мы перестаём писать этот префикс и пишем префикс длиной <tex>z[j]</tex> строки <tex>s</tex>.
Для правильной работы алгоритма, будем считать значение <tex>z[0]</tex> равным нулю. Заметим, что не всегда удастся восстановить строку с ограниченным алфавитом неподходящего размера. Например, для строки <tex>abacaba</tex> массив Z-функций будет <tex>[0, 0, 1, 0, 3, 0, 1]</tex>. Используя двоичный алфавит, мы получим строку <tex>abababa</tex>, но её массив Z-функций отличается от исходного. Ошибка восстановления строки возникла, когда закончились новые символы алфавита.
Алгоритм всегда сможет построить Если строить строку по корректному некорректному массиву значений Z-функции, если в алфавите больше одного символато мы получим какую-то строку, но массив значений Z-функций от неё будет отличаться от исходного.
=== Время работы ===
Этот алгоритм работает за O(|S|), так как мы один раз проходим по массиву Z-функций.
=== Реализация ===
'''string''' buildFromZ(z : '''int'''[], alphabet : '''char'''[]):
Докажем, что если нам дали корректную Z-функцию, то наш алгоритм построит строку с такой же Z-функцией.
Пусть <tex>z</tex> — данная Z-функция, строку <tex>s</tex> построил наш алгоритм, <tex>q</tex> — массив значений Z-функции для <tex>s</tex>. Покажем, что массивы <tex>q</tex> и <tex>z</tex> будут совпадать.
Если <tex>z[i] = 0</tex>, то и <tex>q[i] = 0</tex>Файл: Запись_префикса.png|330px|thumb|right|Записали префикс, так как <tex>s[i] \ne s[0]</tex> (начинающийся в результате алгоритма мы получаем, что <tex>s[i] \neq a</tex>. После пишем префикс, а начинающийся в <tex>s[0] = aj</tex>).Этот префикс не изменит символы первого префикса.]]
Рассмотрим значения похожий алгоритм, но с более худшей асимптотикой. Отличие будет в том, что при <tex>z[i] \ne > 0</tex> мы будем писать префикс полностью и возвращаться в позицию <tex>i + 1</tex>. В этом случае Рассмотрим каждый шаг этого алгоритма. Если <tex>sz[i]= 0</tex> является началом префикса исходной строки. Назовём подстроку, совпадающую с некоторым префиксом то мы пишем символ, отличный от первого символа строки, блоком. Возможны три положения блокапоэтому <tex>q[i] = 0</tex>, относительно других блоков. #Он не пересекает другие блоки (но может полностью включать в себя какой-то блок). Тогда а значит <tex>q[i] = z[i]</tex>, потому что наш алгоритм полностью скопирует префикс всей строки.#Рассмотрим случай, когда мы записали часть префикса и прервались для написания нового. Новый блок корректно закончит предыдущий блок (иначе бы массив Если <tex>z[i] > 0</tex> был бы некорректен, и предыдущий блок будет совпадать с префиксом, тогда то при записи <tex>s[i]</tex> мы будем получать <tex>q[i] = z[i]</tex>, потому что мы переписали префикс строки.#Если блок лежит внутри другого блокаНо далее мы можем переписать этот префикс другим префиксом. Заметим, что новый префикс будет содержаться и в префиксе самой строки, поэтому пересечение двух префиксов будет состоять из одинаковых символов. Значит, префикс не будет изменяться, то его как и значение <tex>q[i]</tex>. Тогда массив <tex>q</tex> совпадает с массивом для блока, содержащегося в префиксе строки. Проверяем корректность массива <tex>qz</tex> для этого блока.
Таким образомПокажем, что этот алгоритм эквивалентен нашему алгоритму. Когда мы пишем разные префиксы, то возможны три варианта: они не пересекаются (начало и конец одного префикса не принадлежат другому), один лежит внутри другого (начало и конец префикса принадлежит другому), они пересекаются (начало одного префикса пренадлежит другому, но конец не принадлежит).* Если префиксы не пересекаются, то в алгоритме они не влияют друг на друга.[[Файл: Префиксы1.png|400px]]* Если префикс лежит внутри другого префикса, то записав большой префикс мы запишем и малый, поэтому не нужно возвращаться к началу малого префикса.[[Файл: Префиксы2.png|400px]]* Если префиксы пересекаются, то нам нужно переписать часть префикса, который начинается раньше, и начать писать другой префикс (начало этого префикса запишет конец префикса, начинающегося раньше). Если полностью переписать префикс, начинающийся раньше, то мы рассмотрели все случаине сможем восстановить префикс, при которых <tex>zкоторый начинался раньше конца первого префикса.[[iФайл: Префиксы3.png|400px]] \ne 0</tex> Таким образом, алгоритмы эквивалентны и показали корректность восстановления блоканаш алгоритм тоже корректен. ==Построение Z-функции по префикс-функции== {{Задача|definition=== Постановка задачи === Дан массив с корректной [[Префикс-функция | префикс-функцией]] для строки <tex>s</tex>, . Требуется получить за <tex>O(n)</tex> массив с Z-функцией для строки <tex>s</tex>.}}[[Файл:Case one.png|300px|thumb|right|'''Случай первый''']][[Файл:Case two.png|300px|thumb|right|'''Случай второй''']][[Файл:Case three.png|300px|thumb|right|'''Случай третий''']] <br>
===Описание алгоритма===
<br>
Пусть префикс функция хранится в массиве <tex>P[0 \ldots n - 1]</tex>. Z-функцию будем записывать в массив <tex>Z[0 \ldots n-1]</tex>. Заметим, что если <tex>P[i]>0</tex>, то мы можем заявить, что <tex>Z[i-P[i]+1]</tex> будет не меньше, чем <tex>P[i]</tex>.
<br>
<br>
Так же заметим, что после такого прохода в <tex>Z[1]</tex> будет максимальное возможное значение. Далее будем поддерживать инвариант: в <tex>Z[i]</tex> будет максимальное возможное значение.
<br>
<br>
Пусть в <tex>Z[i] = z > 0</tex>, рассмотрю <tex>j<z</tex>, <tex>Z[j]=k</tex> и <tex>Z[i+j]=k_1</tex>. Пусть <tex>b_1=s[0 \ldots k-1]</tex>, <tex>b_2=s[j \ldots j+k-1]</tex>, <tex>b_3=s[0 \ldots z-1]</tex>. Тогда заметим, что <tex>b_3 = s[i \ldots i+z-1]</tex> и тогда возможны три случая:
 
# <tex>k<k_1</tex>.
#: Тогда <tex>b_1 \subset s[0 \ldots k_1-1]=s[i+j \ldots i+j+k_1-1]</tex> и тогда очевидно, что мы не можем увеличить значение <tex>Z[i+j]</tex> и надо рассматривать уже <tex>i=i+j</tex>.
# <tex>k<z-j</tex> и <tex>k>k_1</tex>.
#: Тогда <tex>b_1 = b_2 \subset b_3 = s[i \ldots i+z-1] \Rightarrow b_1 = s[i+j \ldots i+j+k-1]</tex> и тогда очевидно, что <tex>Z[i+j]</tex> можно увеличить до <tex>k</tex>.
# <tex>k>z-j</tex> и <tex>k>k_1</tex>.
#: Тогда <tex>b_1 = b_2 </tex>, но <tex>b_2</tex> не является подстрокой строки <tex>b_3</tex> (так как<tex>j+k-1 > z</tex>). Так как известно, что <tex>s[z] \ne s[i+z]</tex>, то <tex>s[0 \ldots z-j] = s[i+j \ldots i+z-1]</tex> и тогда понятно, что <tex>Z[i+j]=z-j</tex>.
Пусть префикс функция хранится в массиве <tex>P[0 ... n - 1]</tex>. Z-функцию будем записывать в массив <tex>Z[0 ... n-1]</tex>. Заметим, что если <tex>P[i]>0</tex>, то мы можем заявить, что <tex>Z[i-P[i]+1]</tex> будет не меньше, чем <tex>P[i]</tex>.
<br>
<br>
Так же заметим, что после такого прохода в <tex>Z[1]</tex> будет максимальное возможное значение. Далее будем поддерживать инвариант: в <tex>Z[i]</tex> будет максимальное возможное значение.
<br>
Пусть в <tex>Z[i] = z > 0</tex>, рассмотрю <tex>j<k</tex>, <tex>Z[j]=k</tex> и <tex>Z[i+j]=k_1</tex>. Заметим, что <tex>s[0 ... z-1]</tex> совпадает с <tex>s[i...z-1]</tex> и тогда возможны три случая:# <tex>k<k_1</tex>. Тогда мы не можем увеличить значение <tex>Z[i+j]</tex> и надо рассматривать уже <tex>i=i+j</tex>. <!--(см картинку)--br># <tex>k<z-j</tex> и <tex>k>k_1</tex>. Тогда очевидно, что <tex>Z[i+j]</tex> можно увеличить до <tex>k</tex>. <!--(см картинку)--br># <tex>k>z-j</tex> и <tex>k>k_1</tex>. Тогда понятно, что <tex>Z[i+j]=z-j</tex>. <!--(см картинку)--br>
===Псевдокод===
'''int[]''' buildZFunctionFromPrefixFunction(P : '''int'''[n] P) '''int''' n = P.length; '''int'''[] Z=new '''int'''[n] '''for'''(i = 1 '''intto''' i = n - 1; i < n; i++) '''if'''(P[i])> 0
Z[i - P[i] + 1] = P[i]
Z[0] = n; '''int''' ti = 1 '''forwhile'''(i < n '''int''' i = 1; i < n - 1; i++) t = i; '''if'''(Z[i])> 0 '''for'''(j = 1 '''intto''' j = 1; j < Z[i] && - 1 '''if''' Z[i + j] <= > Z[j]; j++) '''break'''
Z[i + j] = min(Z[j], Z[i] - j)
t = i + j
i = t+ 1
'''return''' Z
 
===Время работы===
Внешний цикл <tex>\mathrm{while}</tex> отработает за <tex>O(n)</tex> итераций, так как внутри него <tex>i</tex> увеличивается не менее чем на <tex>1</tex>. А внутренний цикл выполнит суммарно не более <tex>O(n)</tex> итераций, так как после него <tex>i</tex> увеличится на количество итераций внутреннего цикла, но <tex>i</tex> не может увеличиться более чем на <tex>n</tex>, так как каждое значение <tex>Z[i]</tex> не может превзойти <tex>n</tex>.
== См. также ==
* [http://habrahabr.ru/post/113266/ Поиск подстроки и смежные вопросы — Хабр]<br>
* [[wikipedia:ru:Z-функция | Википедия — Z-функция]]<br>
* [http://codeforces.ru/blog/entry/9612/ Codeforces — Переход между Z- и префикс- функциями]
[[Категория: Дискретная математика и алгоритмы]]
[[Категория: Поиск подстроки в строке]]
[[Категория:Точный поиск]]
1632
правки

Навигация