Z-функция — различия между версиями

Материал из Викиконспекты
Перейти к: навигация, поиск
м (Псевдокод)
м (rollbackEdits.php mass rollback)
 
(не показаны 33 промежуточные версии 7 участников)
Строка 1: Строка 1:
 
{{Определение
 
{{Определение
|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}\, i + k] = s[0 \mathinner{\ldotp\ldotp} k]</tex>. <!-- проинлайнил \twodots из clrscode -->
+
|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\, \ldots \, i + k] = s[0 \ldots k]</tex>. <!-- проинлайнил \twodots из clrscode -->
  
 
Значение Z-функции от первой позиции не определено, поэтому его обычно приравнивают к нулю или к длине строки.
 
Значение Z-функции от первой позиции не определено, поэтому его обычно приравнивают к нулю или к длине строки.
Строка 46: Строка 46:
 
       '''while''' i + zf[i] < n '''and''' s[zf[i]] == s[i + zf[i]]
 
       '''while''' i + zf[i] < n '''and''' s[zf[i]] == s[i + zf[i]]
 
         zf[i]++
 
         zf[i]++
       '''if''' i + zf[i] >= right
+
       '''if''' i + zf[i] > right
 
         left = i
 
         left = i
 
         right = i + zf[i]
 
         right = i + zf[i]
Строка 66: Строка 66:
 
==Построение строки по Z-функции==
 
==Построение строки по Z-функции==
 
{{Задача
 
{{Задача
|definition= Восстановить строку по Z-функции за <tex>O(n)</tex>, считая алфавит ограниченным.
+
|definition= Необходимо восстановить строку по Z-функции, считая алфавит ограниченным.
 
}}
 
}}
 
 
===Описание алгоритма===
 
===Описание алгоритма===
 
 
Пусть в массиве <tex>z</tex> хранятся значения Z-функции, в <tex>s</tex> будет записан ответ. Пойдем по массиву <tex>z</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>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>z[0]</tex> равным нулю.
 +
 
 +
Заметим, что не всегда удастся восстановить строку с ограниченным алфавитом неподходящего размера. Например, для строки <tex>abacaba</tex> массив Z-функций будет <tex>[0, 0, 1, 0, 3, 0, 1]</tex>. Используя двоичный алфавит, мы получим строку <tex>abababa</tex>, но её массив Z-функций отличается от исходного. Ошибка восстановления строки возникла, когда закончились новые символы алфавита.
  
Алгоритм всегда сможет построить строку по корректному массиву значений Z-функции, если в алфавите больше одного символа.
+
Если строить строку по некорректному массиву значений Z-функции, то мы получим какую-то строку, но массив значений Z-функций от неё будет отличаться от исходного.
  
 +
=== Время работы ===
 +
Этот алгоритм работает за O(|S|), так как мы один раз проходим по массиву Z-функций.
 
=== Реализация ===
 
=== Реализация ===
 
  '''string''' buildFromZ(z : '''int'''[], alphabet : '''char'''[]):
 
  '''string''' buildFromZ(z : '''int'''[], alphabet : '''char'''[]):
Строка 108: Строка 110:
 
Докажем, что если нам дали корректную Z-функцию, то наш алгоритм построит строку с такой же Z-функцией.
 
Докажем, что если нам дали корректную Z-функцию, то наш алгоритм построит строку с такой же Z-функцией.
  
Пусть <tex>z</tex> — данная Z-функция, строку <tex>s</tex> построил наш алгоритм, <tex>q</tex> — массив значений Z-функции для <tex>s</tex>.
+
Пусть <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>, так как <tex>s[i] \ne s[0]</tex> (в результате алгоритма мы получаем, что <tex>s[i] \neq a</tex>, а <tex>s[0] = a</tex>).
+
[[Файл: Запись_префикса.png|330px|thumb|right|Записали префикс, начинающийся в <tex>i</tex>. После пишем префикс, начинающийся в <tex>j</tex>. Этот префикс не изменит символы первого префикса.]]
  
Рассмотрим значения <tex>z[i] \ne 0</tex>. В этом случае <tex>s[i]</tex> является началом префикса исходной строки. Назовём подстроку, совпадающую с некоторым префиксом строки, блоком. Возможны три положения блока, относительно других блоков.
+
Рассмотрим похожий алгоритм, но с более худшей асимптотикой. Отличие будет в том, что при <tex>z[i] > 0</tex> мы будем писать префикс полностью и возвращаться в позицию <tex>i + 1</tex>. Рассмотрим каждый шаг этого алгоритма. Если <tex>z[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>z</tex>.
#Он не пересекает другие блоки (но может полностью включать в себя какой-то блок). Тогда <tex>q[i] = z[i]</tex>, потому что наш алгоритм полностью скопирует префикс всей строки.
 
#Рассмотрим случай, когда мы записали часть префикса и прервались для написания нового. Новый блок корректно закончит предыдущий блок (иначе бы массив <tex>z</tex> был бы некорректен, и предыдущий блок будет совпадать с префиксом, тогда <tex>q[i] = z[i]</tex>.
 
#Если блок лежит внутри другого блока, то его массив <tex>q</tex> совпадает с массивом для блока, содержащегося в префиксе строки. Проверяем корректность массива <tex>q</tex> для этого блока.
 
  
Таким образом, мы рассмотрели все случаи, при которых <tex>z[i] \ne 0</tex>, и показали корректность восстановления блока.
+
Покажем, что этот алгоритм эквивалентен нашему алгоритму. Когда мы пишем разные префиксы, то возможны три варианта: они не пересекаются (начало и конец одного префикса не принадлежат другому), один лежит внутри другого (начало и конец префикса принадлежит другому), они пересекаются (начало одного префикса пренадлежит другому, но конец не принадлежит).
==Построение Z-функции по префикс-функции==
+
* Если префиксы не пересекаются, то в алгоритме они не влияют друг на друга.
=== Постановка задачи ===
+
[[Файл: Префиксы1.png|400px]]
Дан массив с корректной [[Префикс-функция | префикс-функцией]] для строки <tex>s</tex>, получить за <tex>O(n)</tex> массив с Z-функцией для строки <tex>s</tex>.
+
* Если префикс лежит внутри другого префикса, то записав большой префикс мы запишем и малый, поэтому не нужно возвращаться к началу малого префикса.
 +
[[Файл: Префиксы2.png|400px]]
 +
* Если префиксы пересекаются, то нам нужно переписать часть префикса, который начинается раньше, и начать писать другой префикс (начало этого префикса запишет конец префикса, начинающегося раньше). Если полностью переписать префикс, начинающийся раньше, то мы не сможем восстановить префикс, который начинался раньше конца первого префикса.
 +
[[Файл: Префиксы3.png|400px]]
 +
 
 +
Таким образом, алгоритмы эквивалентны и наш алгоритм тоже корректен.
 +
 
 +
==Построение Z-функции по префикс-функции==  
 +
 
 +
{{Задача
 +
|definition= Дан массив с корректной [[Префикс-функция | префикс-функцией]] для строки <tex>s</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>
 
<br>
 
<br>
Так же заметим, что после такого прохода в <tex>Z[1]</tex> будет максимальное возможное значение. Далее будем поддерживать инвариант: в <tex>Z[i]</tex> будет максимальное возможное значение.
 
 
<br>
 
<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> и тогда возможны три случая:
+
<br>
# <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>. <!--(см картинку)-->
 
  
 
===Псевдокод===
 
===Псевдокод===
  '''int[]''' buildZFunctionFromPrefixFunction('''int'''[] P)
+
  '''int[]''' buildZFunctionFromPrefixFunction(P : '''int'''[n])
  '''int''' n = P.length;
+
   '''int'''[] Z = '''int'''[n]
   '''int'''[] Z=new '''int'''[n]
+
   '''for''' i = 1 '''to''' n - 1
   '''for'''('''int''' i = 1; i < n; i++)
+
       '''if''' P[i] > 0
       '''if'''(P[i])
 
 
         Z[i - P[i] + 1] = P[i]
 
         Z[i - P[i] + 1] = P[i]
   Z[0] = n;
+
   Z[0] = n
   '''int''' t
+
   '''int''' i = 1
   '''for'''('''int''' i = 1; i < n - 1; i++)
+
   '''while''' i < n
      t = i;
+
      '''int''' t = i
       '''if'''(Z[i])
+
       '''if''' Z[i] > 0
         '''for'''('''int''' j = 1; j < Z[i] && Z[i + j] <= Z[j]; j++)
+
         '''for''' j = 1 '''to''' Z[i] - 1
 +
            '''if''' Z[i + j] > Z[j]
 +
              '''break'''
 
             Z[i + j] = min(Z[j], Z[i] - j)
 
             Z[i + j] = min(Z[j], Z[i] - j)
 
             t = i + j
 
             t = i + j
       i = t
+
       i = t + 1
 
   '''return''' Z
 
   '''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>.
  
 
== См. также ==
 
== См. также ==
Строка 159: Строка 189:
 
* [http://habrahabr.ru/post/113266/ Поиск подстроки и смежные вопросы — Хабр]<br>
 
* [http://habrahabr.ru/post/113266/ Поиск подстроки и смежные вопросы — Хабр]<br>
 
* [[wikipedia:ru:Z-функция | Википедия — Z-функция]]<br>
 
* [[wikipedia:ru:Z-функция | Википедия — Z-функция]]<br>
* [http://codeforces.ru/blog/entry/9612/ Переход между Z- и префикс- функциями]
+
* [http://codeforces.ru/blog/entry/9612/ Codeforces — Переход между Z- и префикс- функциями]
  
 
[[Категория: Дискретная математика и алгоритмы]]
 
[[Категория: Дискретная математика и алгоритмы]]
 
[[Категория: Поиск подстроки в строке]]
 
[[Категория: Поиск подстроки в строке]]
 
[[Категория:Точный поиск]]
 
[[Категория:Точный поиск]]

Текущая версия на 19:12, 4 сентября 2022

Определение:
Z-функция (англ. Z-function) от строки [math]S[/math] и позиции [math]x[/math] — это длина максимального префикса подстроки, начинающейся с позиции [math]x[/math] в строке [math]S[/math], который одновременно является и префиксом всей строки [math]S[/math]. Более формально, [math]Z[i](s) = \max k \mid s[i\, \ldots \, i + k] = s[0 \ldots k][/math]. Значение Z-функции от первой позиции не определено, поэтому его обычно приравнивают к нулю или к длине строки.

Примечание: далее в конспекте символы строки нумеруются с нуля.

Строка и её Z-функция

Тривиальный алгоритм

Простая реализация за [math]O(n^2)[/math], где [math]n[/math] — длина строки. Для каждой позиции [math]i[/math] перебираем для неё ответ, начиная с нуля, пока не обнаружим несовпадение или не дойдем до конца строки.

Псевдокод

 int[] zFunction(s : string):
   int[] zf = int[n]
   for i = 1 to n − 1
     while i + zf[i] < n and s[zf[i]] == s[i + zf[i]]
       zf[i]++
   return zf

Эффективный алгоритм поиска

Z-блоком назовем подстроку с началом в позиции [math]i[/math] и длиной [math]Z[i][/math].
Для работы алгоритма заведём две переменные: [math]left[/math] и [math]right[/math] — начало и конец Z-блока строки [math]S[/math] с максимальной позицией конца [math]right[/math] (среди всех таких Z-блоков, если их несколько, выбирается наибольший). Изначально [math]left=0[/math] и [math]right=0[/math]. Пусть нам известны значения Z-функции от [math]0[/math] до [math]i-1[/math]. Найдём [math]Z[i][/math]. Рассмотрим два случая.

  1. [math]i \gt right[/math]:
    Просто пробегаемся по строке [math]S[/math] и сравниваем символы на позициях [math]S[i+j][/math] и [math]S[j][/math].Пусть [math]j[/math] первая позиция в строке [math]S[/math] для которой не выполняется равенство [math]S[i+j] = S[j][/math], тогда [math]j[/math] это и Z-функция для позиции [math]i[/math]. Тогда [math]left = i, right = i + j - 1[/math]. В данном случае будет определено корректное значение [math]Z[i][/math] в силу того, что оно определяется наивно, путем сравнения с начальными символами строки.
  2. [math]i \leqslant right[/math]:
    Сравним [math]Z[i - left] + i[/math] и [math]right[/math]. Если [math]right[/math] меньше, то надо просто наивно пробежаться по строке начиная с позиции [math]right[/math] и вычислить значение [math]Z[i][/math]. Корректность в таком случае также гарантирована.Иначе мы уже знаем верное значение [math]Z[i][/math], так как оно равно значению [math]Z[i - left][/math].

Z-func.png

Время работы

Этот алгоритм работает за [math]O(|S|)[/math], так как каждая позиция пробегается не более двух раз: при попадании в диапазон от [math]left[/math] до [math]right[/math] и при высчитывании Z-функции простым циклом.

Псевдокод

 int[] zFunction(s : string):
   int[] zf = int[n]
   int left = 0, right = 0
   for i = 1 to n − 1
     zf[i] = max(0, min(right − i, zf[i − left]))
     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]
   return zf

Поиск подстроки в строке с помощью Z-функции

[math]n[/math] — длина текста. [math]m[/math] — длина образца.
Образуем строку s = pattern + # + text, где # — символ, не встречающийся ни в text, ни в pattern. Вычисляем Z-функцию от этой строки. В полученном массиве, в позициях в которых значение Z-функции равно [math]|\texttt{pattern}|[/math], по определению начинается подстрока, совпадающая с pattern.

Псевдокод

 int substringSearch(text : string, pattern : string):
   int[] zf = zFunction(pattern + '#' + text)
   for i = m + 1 to n + 1
     if zf[i] == m 
       return i


Построение строки по Z-функции

Задача:
Необходимо восстановить строку по Z-функции, считая алфавит ограниченным.

Описание алгоритма

Пусть в массиве [math]z[/math] хранятся значения Z-функции, в [math]s[/math] будет записан ответ. Пойдем по массиву [math]z[/math] слева направо.

Нужно узнать значение [math]s[i][/math]. Для этого посмотрим на значение [math]z[i][/math]: если [math]z[i] = 0[/math], тогда в [math]s[i][/math] запишем ещё не использованный символ или последний использованный символ алфавита, если мы уже использовали все символы. Если [math]z[i] \neq 0[/math], то нам нужно записать префикс длины [math]z[i][/math] строки [math]s[/math]. Но если при посимвольном записывании этого префикса в конец строки [math]s[/math] мы нашли такой [math]j[/math] (индекс последнего символа строки), что [math]z[j][/math] больше, чем длина оставшейся незаписанной части префикса, то мы перестаём писать этот префикс и пишем префикс длиной [math]z[j][/math] строки [math]s[/math].

Для правильной работы алгоритма будем считать значение [math]z[0][/math] равным нулю.

Заметим, что не всегда удастся восстановить строку с ограниченным алфавитом неподходящего размера. Например, для строки [math]abacaba[/math] массив Z-функций будет [math][0, 0, 1, 0, 3, 0, 1][/math]. Используя двоичный алфавит, мы получим строку [math]abababa[/math], но её массив Z-функций отличается от исходного. Ошибка восстановления строки возникла, когда закончились новые символы алфавита.

Если строить строку по некорректному массиву значений Z-функции, то мы получим какую-то строку, но массив значений Z-функций от неё будет отличаться от исходного.

Время работы

Этот алгоритм работает за O(|S|), так как мы один раз проходим по массиву Z-функций.

Реализация

string buildFromZ(z : int[], alphabet : char[]):
  string s = ""
  int prefixLength = 0 // длина префикса, который мы записываем
  int j // позиция символа в строке, который будем записывать
  int newCharacter = 0 // индекс нового символа
  for i = 0 to z.length - 1
      // мы не пишем какой-то префикс и не будем писать новый
      if z[i] = 0 and prefixLength = 0
          if newCharacter < alphabet.length
              s += alphabet[newCharacter]
              newCharacter++
          else
              s += alphabet[newCharacter - 1]
      // нам нужно запомнить, что мы пишем префикс 
      if z[i] > prefixLength
          prefixLength = z[i]
          j = 0
      // пишем префикс
      if prefixLength > 0
          s += s[j]
          j++
          prefixLength--       
  return s

Доказательство корректности алгоритма

Докажем, что если нам дали корректную Z-функцию, то наш алгоритм построит строку с такой же Z-функцией.

Пусть [math]z[/math] — данная Z-функция, строку [math]s[/math] построил наш алгоритм, [math]q[/math] — массив значений Z-функции для [math]s[/math]. Покажем, что массивы [math]q[/math] и [math]z[/math] будут совпадать.

Записали префикс, начинающийся в [math]i[/math]. После пишем префикс, начинающийся в [math]j[/math]. Этот префикс не изменит символы первого префикса.

Рассмотрим похожий алгоритм, но с более худшей асимптотикой. Отличие будет в том, что при [math]z[i] \gt 0[/math] мы будем писать префикс полностью и возвращаться в позицию [math]i + 1[/math]. Рассмотрим каждый шаг этого алгоритма. Если [math]z[i] = 0[/math], то мы пишем символ, отличный от первого символа строки, поэтому [math]q[i] = 0[/math], а значит [math]q[i] = z[i][/math]. Если [math]z[i] \gt 0[/math], то при записи [math]s[i][/math] мы будем получать [math]q[i] = z[i][/math], потому что мы переписали префикс строки. Но далее мы можем переписать этот префикс другим префиксом. Заметим, что новый префикс будет содержаться и в префиксе самой строки, поэтому пересечение двух префиксов будет состоять из одинаковых символов. Значит, префикс не будет изменяться, как и значение [math]q[i][/math]. Тогда массив [math]q[/math] совпадает с [math]z[/math].

Покажем, что этот алгоритм эквивалентен нашему алгоритму. Когда мы пишем разные префиксы, то возможны три варианта: они не пересекаются (начало и конец одного префикса не принадлежат другому), один лежит внутри другого (начало и конец префикса принадлежит другому), они пересекаются (начало одного префикса пренадлежит другому, но конец не принадлежит).

  • Если префиксы не пересекаются, то в алгоритме они не влияют друг на друга.

Префиксы1.png

  • Если префикс лежит внутри другого префикса, то записав большой префикс мы запишем и малый, поэтому не нужно возвращаться к началу малого префикса.

Префиксы2.png

  • Если префиксы пересекаются, то нам нужно переписать часть префикса, который начинается раньше, и начать писать другой префикс (начало этого префикса запишет конец префикса, начинающегося раньше). Если полностью переписать префикс, начинающийся раньше, то мы не сможем восстановить префикс, который начинался раньше конца первого префикса.

Префиксы3.png

Таким образом, алгоритмы эквивалентны и наш алгоритм тоже корректен.

Построение Z-функции по префикс-функции

Задача:
Дан массив с корректной префикс-функцией для строки [math]s[/math]. Требуется получить массив с Z-функцией для строки [math]s[/math].
Случай первый
Случай второй
Случай третий


Описание алгоритма


Пусть префикс функция хранится в массиве [math]P[0 \ldots n - 1][/math]. Z-функцию будем записывать в массив [math]Z[0 \ldots n-1][/math]. Заметим, что если [math]P[i]\gt 0[/math], то мы можем заявить, что [math]Z[i-P[i]+1][/math] будет не меньше, чем [math]P[i][/math].

Так же заметим, что после такого прохода в [math]Z[1][/math] будет максимальное возможное значение. Далее будем поддерживать инвариант: в [math]Z[i][/math] будет максимальное возможное значение.

Пусть в [math]Z[i] = z \gt 0[/math], рассмотрю [math]j\lt z[/math], [math]Z[j]=k[/math] и [math]Z[i+j]=k_1[/math]. Пусть [math]b_1=s[0 \ldots k-1][/math], [math]b_2=s[j \ldots j+k-1][/math], [math]b_3=s[0 \ldots z-1][/math]. Тогда заметим, что [math]b_3 = s[i \ldots i+z-1][/math] и тогда возможны три случая:

  1. [math]k\lt k_1[/math].
    Тогда [math]b_1 \subset s[0 \ldots k_1-1]=s[i+j \ldots i+j+k_1-1][/math] и тогда очевидно, что мы не можем увеличить значение [math]Z[i+j][/math] и надо рассматривать уже [math]i=i+j[/math].
  2. [math]k\lt z-j[/math] и [math]k\gt k_1[/math].
    Тогда [math]b_1 = b_2 \subset b_3 = s[i \ldots i+z-1] \Rightarrow b_1 = s[i+j \ldots i+j+k-1][/math] и тогда очевидно, что [math]Z[i+j][/math] можно увеличить до [math]k[/math].
  3. [math]k\gt z-j[/math] и [math]k\gt k_1[/math].
    Тогда [math]b_1 = b_2 [/math], но [math]b_2[/math] не является подстрокой строки [math]b_3[/math] (так как[math]j+k-1 \gt z[/math]). Так как известно, что [math]s[z] \ne s[i+z][/math], то [math]s[0 \ldots z-j] = s[i+j \ldots i+z-1][/math] и тогда понятно, что [math]Z[i+j]=z-j[/math].







Псевдокод

int[] buildZFunctionFromPrefixFunction(P : int[n])
  int[] Z = int[n]
  for i = 1 to n - 1
     if P[i] > 0
        Z[i - P[i] + 1] = P[i]
  Z[0] = n
  int i = 1
  while i < n 
     int t = i
     if Z[i] > 0
        for j = 1 to Z[i] - 1
           if Z[i + j] > Z[j]
              break
           Z[i + j] = min(Z[j], Z[i] - j)
           t = i + j
     i = t + 1
  return Z

Время работы

Внешний цикл [math]\mathrm{while}[/math] отработает за [math]O(n)[/math] итераций, так как внутри него [math]i[/math] увеличивается не менее чем на [math]1[/math]. А внутренний цикл выполнит суммарно не более [math]O(n)[/math] итераций, так как после него [math]i[/math] увеличится на количество итераций внутреннего цикла, но [math]i[/math] не может увеличиться более чем на [math]n[/math], так как каждое значение [math]Z[i][/math] не может превзойти [math]n[/math].

См. также

Источники информации