Изменения

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

Z-функция

15 366 байт добавлено, 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>S</tex>. Необходимо построить массив <tex>Z</tex>Более формально, такой что <tex>Z[i]</tex> является префикс функцией данной строки с позиции <tex>(s) = \max k \mid s[i\, \ldots \, i</tex>*'''Описание алгоритма'''Для работы алгоритма заведём две переменные: <tex>left</tex> и <tex>right</tex> - начало и конец наибольшего префикса строки <tex>S</tex> с максимальным значением <tex>right</tex>. Изначально <tex>left+ k] =s[0\ldots k]</tex> и . <tex>right=0</tex!-- проинлайнил \twodots из clrscode -->.
Это динамический алгоритм. Пусть нам известны значения Значение Z-функции от <tex>0</tex> до <tex>i-1</tex>. Найдём <tex>Z[i]</tex>первой позиции не определено, поэтому его обычно приравнивают к нулю или к длине строки. Есть два случая}}'''Примечание: <tex>i > right</tex> и <tex>i \leq right</tex>''' далее в конспекте символы строки нумеруются с нуля.
Пусть <tex>i > right</tex>. Тогда просто пробегаемся по строке <tex>S</tex> и сравниваем символы из начала с символами после позиции <tex>i</tex>. (<tex>S[i+j] == S[j]</tex>)Пусть <tex>j</tex> первая позиция в строке <tex>S</tex> для которой не выполняется равенство <tex>S[i+j] == S[j]</tex>, тогда <tex>j</tex> это Файл:Zfunc-examp.png|мини|500px|Строка и её Z-функция для позиции <tex>i</tex>. Тогда <tex>left = i, right = i + j - 1</tex>. ]]
Если <tex>i \leq right</tex>, сравним <tex>Z[i - left] + i</tex> и <tex>right</tex>. Если <tex>right</tex> меньше, то надо просто пробежаться по строке начиная с позиции <tex>right</tex> и вычислить значение <tex>Z[i]</tex>. Иначе мы уже знаем значение <tex>Z[i]</tex>, так как оно равно значению <tex>Z[i - left]</tex>== Тривиальный алгоритм ==
*Простая реализация за <tex>O(n^2)</tex>, где <tex>n</tex> — длина строки. Для каждой позиции <tex>i</tex> перебираем для неё ответ, начиная с нуля, пока не обнаружим несовпадение или не дойдем до конца строки.  === Псевдокод === '''Код алгоритмаint''' int[] zzFunction(String ps : '''string''') {: '''int'''[] ans zf = new '''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-блоком назовем подстроку с началом в позиции <tex>i</tex> и длиной <tex>Z[pi]</tex>.length<br>Для работы алгоритма заведём две переменные: <tex>left</tex> и <tex>right</tex> — начало и конец Z-блока строки <tex>S</tex> с максимальной позицией конца <tex>right</tex> (среди всех таких Z-блоков, если их несколько, выбирается наибольший). Изначально <tex>left=0</tex> и <tex>right=0</tex>.Пусть нам известны значения Z-функции от <tex>0</tex> до <tex>i-1</tex>. Найдём <tex>Z[i]</tex>. Рассмотрим два случая. # <tex>i > right</tex>:<br><!---->Просто пробегаемся по строке <tex>S</tex> и сравниваем символы на позициях <tex>S[i+j];</tex> и <tex>S[j]</tex>.<!-- ans-->Пусть <tex>j</tex> первая позиция в строке <tex>S</tex> для которой не выполняется равенство <tex>S[i+j] = S[0j] </tex>, тогда <tex>j</tex> это и Z-функция для позиции <tex>i</tex>. Тогда <tex>left = i, right = 0;i + j - 1</tex>. В данном случае будет определено корректное значение <tex>Z[i]</tex> в силу того, что оно определяется наивно, путем сравнения с начальными символами строки.# <tex>i \leqslant right</tex>:<br><!---->Сравним <tex>Z[i - left] + i</tex> и <tex>right</tex>. Если <tex>right</tex> меньше, то надо просто наивно пробежаться по строке начиная с позиции <tex>right</tex> и вычислить значение <tex>Z[i]</tex>. Корректность в таком случае также гарантирована.<!---->Иначе мы уже знаем верное значение <tex>Z[i]</tex>, так как оно равно значению <tex>Z[i - left]</tex>.[[Файл:z-func.png]]  int n = p== Время работы ===Этот алгоритм работает за <tex>O(|S|)</tex>, так как каждая позиция пробегается не более двух раз: при попадании в диапазон от <tex>left</tex> до <tex>right</tex> и при высчитывании Z-функции простым циклом.length === Псевдокод === '''int'''[] zFunction(s : '''string''');: '''int left '''[] zf = 0;'''int'''[n] '''int ''' left = 0, right = 0; '''for (int ''' 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) { int j left = 0;i while (right = i + j zf[i] '''return''' zf == Поиск подстроки в строке с помощью Z-функции ==< tex>n && p</tex> — длина текста.charAt<tex>m</tex> — длина образца. <br> Образуем строку <tt>s = pattern + # + text</tt>, где <tt>#</tt> — символ, не встречающийся ни в <tt>text</tt>, ни в <tt>pattern</tt>. Вычисляем Z-функцию от этой строки.В полученном массиве, в позициях в которых значение Z-функции равно <tex>|\texttt{pattern}|</tex>, по определению начинается подстрока, совпадающая с <tt>pattern</tt>.  === Псевдокод === '''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-функции=={{Задача|definition= Необходимо восстановить строку по Z-функции, считая алфавит ограниченным.}}===Описание алгоритма===Пусть в массиве <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-функций от неё будет отличаться от исходного. === Время работы === pЭтот алгоритм работает за O(|S|), так как мы один раз проходим по массиву Z-функций.charAt=== Реализация === '''string''' buildFromZ(jz : '''int'''[], alphabet : '''char'''[])) {: '''string''' s = "" '''int''' prefixLength = 0 <font color=green>// длина префикса, который мы записываем</font> '''int''' j<font color=green>// позиция символа в строке, который будем записывать</font> '''int''' newCharacter = 0 <font color=green>// индекс нового символа</font> '''for''' i = 0 '''to''' z.length - 1 <font color=green>// мы не пишем какой-то префикс и не будем писать новый</font> '''if''' z[i] = 0 '''and''' prefixLength = 0 if newCharacter < alphabet.length s += alphabet[newCharacter] newCharacter++ else s +;= alphabet[newCharacter - 1] } <font color=green>// нам нужно запомнить, что мы пишем префикс </font> '''if''' z[i] > prefixLength ans prefixLength = z[i] j = 0 <font color=green>// пишем префикс</font> '''if''' prefixLength > 0 s += s[j;] left j++ prefixLength-- '''return''' s ===Доказательство корректности алгоритма===  Докажем, что если нам дали корректную Z-функцию, то наш алгоритм построит строку с такой же Z-функцией. Пусть <tex>z</tex> — данная Z-функция, строку <tex>s</tex> построил наш алгоритм, <tex>q</tex> — массив значений Z-функции для <tex>s</tex>. Покажем, что массивы <tex>q</tex> и <tex>z</tex> будут совпадать. [[Файл: Запись_префикса.png|330px|thumb|right|Записали префикс, начинающийся в <tex>i;</tex>. После пишем префикс, начинающийся в <tex>j</tex>. Этот префикс не изменит символы первого префикса.]]  right Рассмотрим похожий алгоритм, но с более худшей асимптотикой. Отличие будет в том, что при <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 + j ]</tex>, потому что мы переписали префикс строки. Но далее мы можем переписать этот префикс другим префиксом. Заметим, что новый префикс будет содержаться и в префиксе самой строки, поэтому пересечение двух префиксов будет состоять из одинаковых символов. Значит, префикс не будет изменяться, как и значение <tex>q[i]</tex>. Тогда массив <tex>q</tex> совпадает с <tex>z</tex>. Покажем, что этот алгоритм эквивалентен нашему алгоритму. Когда мы пишем разные префиксы, то возможны три варианта: они не пересекаются (начало и конец одного префикса не принадлежат другому), один лежит внутри другого (начало и конец префикса принадлежит другому), они пересекаются (начало одного префикса пренадлежит другому, но конец не принадлежит).* Если префиксы не пересекаются, то в алгоритме они не влияют друг на друга.[[Файл: Префиксы1.png|400px]]* Если префикс лежит внутри другого префикса, то записав большой префикс мы запишем и малый, поэтому не нужно возвращаться к началу малого префикса.[[Файл: Префиксы2.png|400px]]* Если префиксы пересекаются, то нам нужно переписать часть префикса, который начинается раньше, и начать писать другой префикс (начало этого префикса запишет конец префикса, начинающегося раньше). Если полностью переписать префикс, начинающийся раньше, то мы не сможем восстановить префикс, который начинался раньше конца первого префикса.[[Файл: Префиксы3.png|400px]] Таким образом, алгоритмы эквивалентны и наш алгоритм тоже корректен. ==Построение Z-функции по префикс- 1;функции==   } else {{Задача if (ans|definition= Дан массив с корректной [[i Префикс-функция | префикс- leftфункцией]] для строки <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> ansПусть в <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 = anss[i \ldots i+z- left1];</tex> и тогда возможны три случая:  } else {# <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>. int # <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>. while #: Тогда <tex>b_1 = b_2 </tex>, но <tex>b_2</tex> не является подстрокой строки <tex>b_3</tex> (так как<tex>j + right k-1 > z< n && p/tex>).charAt(Так как известно, что <tex>s[z] \ne s[i+z]</tex>, то <tex>s[0 \ldots z-j] = s[i+j\ldots i+rightz-1]</tex> и тогда понятно, что <tex>Z[i+j]=z-j</tex>.  <br><br><br><br><br><br> ===Псевдокод=== '''int[]''' buildZFunctionFromPrefixFunction(P : '''int'''[n]) '''int'''[] Z ='''int'''[n] '''for''' i = p.charAt(right 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''' ans Z[i+ j] = right + min(Z[j ], Z[i] - i;j) left t = i;+ j right i = right t + j - 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- и префикс- функциями] } }[[Категория: Дискретная математика и алгоритмы]] return ans;[[Категория: Поиск подстроки в строке]] }[[Категория:Точный поиск]]
1632
правки

Навигация