<?xml version="1.0"?>
<feed xmlns="http://www.w3.org/2005/Atom" xml:lang="ru">
		<id>http://neerc.ifmo.ru/wiki/api.php?action=feedcontributions&amp;feedformat=atom&amp;user=37.112.34.53&amp;*</id>
		<title>Викиконспекты - Вклад участника [ru]</title>
		<link rel="self" type="application/atom+xml" href="http://neerc.ifmo.ru/wiki/api.php?action=feedcontributions&amp;feedformat=atom&amp;user=37.112.34.53&amp;*"/>
		<link rel="alternate" type="text/html" href="http://neerc.ifmo.ru/wiki/index.php?title=%D0%A1%D0%BB%D1%83%D0%B6%D0%B5%D0%B1%D0%BD%D0%B0%D1%8F:%D0%92%D0%BA%D0%BB%D0%B0%D0%B4/37.112.34.53"/>
		<updated>2026-04-27T06:01:28Z</updated>
		<subtitle>Вклад участника</subtitle>
		<generator>MediaWiki 1.30.0</generator>

	<entry>
		<id>http://neerc.ifmo.ru/wiki/index.php?title=%D0%9F%D1%80%D0%B5%D1%84%D0%B8%D0%BA%D1%81-%D1%84%D1%83%D0%BD%D0%BA%D1%86%D0%B8%D1%8F&amp;diff=44790</id>
		<title>Префикс-функция</title>
		<link rel="alternate" type="text/html" href="http://neerc.ifmo.ru/wiki/index.php?title=%D0%9F%D1%80%D0%B5%D1%84%D0%B8%D0%BA%D1%81-%D1%84%D1%83%D0%BD%D0%BA%D1%86%D0%B8%D1%8F&amp;diff=44790"/>
				<updated>2015-01-31T08:26:03Z</updated>
		
		<summary type="html">&lt;p&gt;37.112.34.53: /* Эффективный алгоритм */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;{{Определение&lt;br /&gt;
|definition = '''Префикс-функция''' ''(англ. prefix-function)'' от строки {{---}} массив длин наибольших [[Период_и_бордер,_их_связь#Определения|бордеров]] для каждой позиции этой строки}}&lt;br /&gt;
Здесь и далее считаем, что символы в строках нумеруются с &amp;lt;tex&amp;gt;1&amp;lt;/tex&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
Определим префикс-функцию от строки &amp;lt;tex&amp;gt;s&amp;lt;/tex&amp;gt; в позиции &amp;lt;tex&amp;gt;i&amp;lt;/tex&amp;gt; следующим образом: &amp;lt;tex&amp;gt;\pi(s, i) = \max\limits_{k = 1..i - 1} \{k : &amp;lt;/tex&amp;gt; &amp;lt;tex&amp;gt;s[1..k] = s[i - k + 1..i] \}&amp;lt;/tex&amp;gt;. Если мы не нашли такого &amp;lt;tex&amp;gt;k&amp;lt;/tex&amp;gt;, то &amp;lt;tex&amp;gt;\pi(s, i)=0&amp;lt;/tex&amp;gt;. &lt;br /&gt;
&lt;br /&gt;
==Наивный алгоритм==&lt;br /&gt;
Наивный алгоритм вычисляет префикс-функцию непосредственно по определению, сравнивая префиксы и суффиксы строк. Обозначим длину строки за &amp;lt;tex&amp;gt;n&amp;lt;/tex&amp;gt;. Будем считать, что префикс-функция хранится в массиве &amp;lt;tex&amp;gt; p &amp;lt;/tex&amp;gt;. &lt;br /&gt;
&lt;br /&gt;
===Псевдокод===&lt;br /&gt;
 '''int'''[] prefixFunction('''string''' s):&lt;br /&gt;
      '''int'''[] p = '''int'''[s.length]&lt;br /&gt;
      fill(p, 0)&lt;br /&gt;
      '''for''' i = 1 '''to''' n&lt;br /&gt;
          '''for''' k = 1 '''to''' i&lt;br /&gt;
              '''if''' s[1..k] == s[i - k + 1..i]&lt;br /&gt;
                  p[i] = k&lt;br /&gt;
      '''return''' p&lt;br /&gt;
&lt;br /&gt;
===Пример===&lt;br /&gt;
Рассмотрим строку &amp;lt;tex&amp;gt;abcabcd&amp;lt;/tex&amp;gt;, для которой значение префикс-функции равно &amp;lt;tex&amp;gt;[0,0,0,1,2,3,0]&amp;lt;/tex&amp;gt;.&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
! Шаг || Строка || Значение функции&lt;br /&gt;
|-&lt;br /&gt;
| &amp;lt;tex&amp;gt;1&amp;lt;/tex&amp;gt; || a || 0&lt;br /&gt;
|-&lt;br /&gt;
| &amp;lt;tex&amp;gt;2&amp;lt;/tex&amp;gt; || ab || 0&lt;br /&gt;
|-&lt;br /&gt;
| &amp;lt;tex&amp;gt;3&amp;lt;/tex&amp;gt; || abc || 0&lt;br /&gt;
|-&lt;br /&gt;
| &amp;lt;tex&amp;gt;4&amp;lt;/tex&amp;gt; || abca || 1&lt;br /&gt;
|-&lt;br /&gt;
| &amp;lt;tex&amp;gt;5&amp;lt;/tex&amp;gt; || abcab || 2&lt;br /&gt;
|-&lt;br /&gt;
| &amp;lt;tex&amp;gt;6&amp;lt;/tex&amp;gt; || abcabc || 3&lt;br /&gt;
|-&lt;br /&gt;
| &amp;lt;tex&amp;gt;7&amp;lt;/tex&amp;gt; || abcabcd || 0&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
===Время работы===&lt;br /&gt;
Всего &amp;lt;tex&amp;gt;O(n^2)&amp;lt;/tex&amp;gt; итераций цикла, на каждой из который происходит сравнение строк за &amp;lt;tex&amp;gt;O(n)&amp;lt;/tex&amp;gt;, что дает в итоге &amp;lt;tex&amp;gt;O(n^3)&amp;lt;/tex&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
==Эффективный алгоритм==&lt;br /&gt;
Вносятся несколько важных замечаний:&lt;br /&gt;
* Заметим, что &amp;lt;tex&amp;gt;p[i + 1] \leqslant p[i] + 1&amp;lt;/tex&amp;gt;. Чтобы показать это, рассмотрим суффикс,оканчивающийся на позиции &amp;lt;tex&amp;gt;i + 1&amp;lt;/tex&amp;gt; и имеющий длину &amp;lt;tex&amp;gt;p[i + 1]&amp;lt;/tex&amp;gt;, удалив из него последний символ, мы получим суффикс, оканчивающийся на позиции &amp;lt;tex&amp;gt;i&amp;lt;/tex&amp;gt; и имеющий длину &amp;lt;tex&amp;gt;p[i + 1] - 1&amp;lt;/tex&amp;gt;, следовательно неравенство &amp;lt;tex&amp;gt;p[i + 1] &amp;gt; p[i] + 1&amp;lt;/tex&amp;gt; неверно.&lt;br /&gt;
* Избавимся от явных сравнений строк. Пусть мы вычислили &amp;lt;tex&amp;gt;p[i]&amp;lt;/tex&amp;gt;, тогда, если &amp;lt;tex&amp;gt;s[i + 1] = s[p[i] + 1]&amp;lt;/tex&amp;gt;, то &amp;lt;tex&amp;gt;p[i + 1] = p[i] + 1&amp;lt;/tex&amp;gt;. Если окажется, что &amp;lt;tex&amp;gt;s[i + 1] \ne s[p[i] + 1]&amp;lt;/tex&amp;gt;, то нужно попытаться попробовать подстроку меньшей длины. Хотелось бы сразу перейти к такому [[Период_и_бордер,_их_связь#Определения|бордеру]] наибольшей длины, для этого подберем такое &amp;lt;tex&amp;gt;k&amp;lt;/tex&amp;gt;, что &amp;lt;tex&amp;gt;k = p[i] - 1&amp;lt;/tex&amp;gt;. Делаем это следующим образом. За исходное &amp;lt;tex&amp;gt;k&amp;lt;/tex&amp;gt; необходимо взять &amp;lt;tex&amp;gt;p[i - 1]&amp;lt;/tex&amp;gt;, что следует из первого пункта. В случае, когда символы &amp;lt;tex&amp;gt;s[k+1]&amp;lt;/tex&amp;gt; и &amp;lt;tex&amp;gt;s[i]&amp;lt;/tex&amp;gt; не совпадают, &amp;lt;tex&amp;gt;p[k]&amp;lt;/tex&amp;gt; {{---}} следующее потенциальное наибольшее значение &amp;lt;tex&amp;gt;k&amp;lt;/tex&amp;gt;, что видно из рисунка. Последнее утверждение верно, пока &amp;lt;tex&amp;gt;k&amp;gt;0&amp;lt;/tex&amp;gt;, что позволит всегда найти его следующее значение. Если &amp;lt;tex&amp;gt;k=0&amp;lt;/tex&amp;gt;, то &amp;lt;tex&amp;gt;p[i]=1&amp;lt;/tex&amp;gt; при &amp;lt;tex&amp;gt;s[i] = s[1]&amp;lt;/tex&amp;gt; , иначе &amp;lt;tex&amp;gt;p[i]=0&amp;lt;/tex&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
[[Файл:mprfx.jpg|800px]]&lt;br /&gt;
&lt;br /&gt;
===Псевдокод===&lt;br /&gt;
 '''int'''[] prefixFunction('''string''' s):&lt;br /&gt;
   p[1] = 0&lt;br /&gt;
   '''for''' i = 2 '''to''' n&lt;br /&gt;
       k = p[i - 1]&lt;br /&gt;
       '''while''' k &amp;gt; 0 '''and''' s[i] != s[k + 1]&lt;br /&gt;
           k = p[k]&lt;br /&gt;
       '''if''' s[i] == s[k + 1]&lt;br /&gt;
           k++&lt;br /&gt;
       p[i] = k&lt;br /&gt;
   '''return''' p&lt;br /&gt;
&lt;br /&gt;
===Время работы===&lt;br /&gt;
Время работы алгоритма составит &amp;lt;tex&amp;gt;O(n)&amp;lt;/tex&amp;gt;. Для доказательства этого нужно заметить, что итоговое количество итераций цикла &amp;lt;tex&amp;gt;\mathrm{while}&amp;lt;/tex&amp;gt; определяет асимптотику алгоритма. Теперь стоит отметить, что &amp;lt;tex&amp;gt;k&amp;lt;/tex&amp;gt; увеличивается на каждом шаге не более чем на единицу, значит максимально возможное значение &amp;lt;tex&amp;gt;k = n - 1&amp;lt;/tex&amp;gt;. Поскольку внутри цикла &amp;lt;tex&amp;gt;\mathrm{while}&amp;lt;/tex&amp;gt; значение &amp;lt;tex&amp;gt;k&amp;lt;/tex&amp;gt; лишь уменьшается, получается, что &amp;lt;tex&amp;gt;k&amp;lt;/tex&amp;gt; не может суммарно уменьшиться больше, чем &amp;lt;tex&amp;gt;n-1&amp;lt;/tex&amp;gt; раз. Значит цикл &amp;lt;tex&amp;gt;\mathrm{while}&amp;lt;/tex&amp;gt; в итоге выполнится не более &amp;lt;tex&amp;gt;n&amp;lt;/tex&amp;gt; раз, что дает итоговую оценку времени алгоритма &amp;lt;tex&amp;gt;O(n)&amp;lt;/tex&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
== Построение префикс-функции по Z-функции==&lt;br /&gt;
=== Постановка задачи ===&lt;br /&gt;
Дан массив с корректной [[Z-функция | Z-функцией]] для строки &amp;lt;tex&amp;gt;s&amp;lt;/tex&amp;gt;, получить за &amp;lt;tex&amp;gt;O(n)&amp;lt;/tex&amp;gt; массив с префикс-функцией для строки &amp;lt;tex&amp;gt;s&amp;lt;/tex&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
=== Описание алгоритма ===&lt;br /&gt;
Пусть Z-функция хранится в массиве &amp;lt;tex&amp;gt;z[1..n]&amp;lt;/tex&amp;gt;. Префикс-функцию будем записывать в массив &amp;lt;tex&amp;gt;p[1..n]&amp;lt;/tex&amp;gt;.&lt;br /&gt;
Заметим, что если &amp;lt;tex&amp;gt;z[i] &amp;gt; 0, &amp;lt;/tex&amp;gt; то для всех элементов с индексом &amp;lt;tex&amp;gt;i + j&amp;lt;/tex&amp;gt;, где &amp;lt;tex&amp;gt;0 \leqslant j &amp;lt; z[i] &amp;lt;/tex&amp;gt;,  значение &amp;lt;tex&amp;gt;p[i + j] &amp;lt;/tex&amp;gt; будет не меньше, чем длина подстроки с &amp;lt;tex&amp;gt; i &amp;lt;/tex&amp;gt; по &amp;lt;tex&amp;gt; i + j&amp;lt;/tex&amp;gt;, что равно &amp;lt;tex&amp;gt;j + 1&amp;lt;/tex&amp;gt; (как изображено на рисунке). &lt;br /&gt;
&lt;br /&gt;
Также заметим, что если мы уже установили в какую-то позицию значение &amp;lt;tex&amp;gt; j &amp;lt;/tex&amp;gt; с позиции &amp;lt;tex&amp;gt; i &amp;lt;/tex&amp;gt;, а потом пытаемся установить значение &amp;lt;tex&amp;gt; j' &amp;lt;/tex&amp;gt; c позиции &amp;lt;tex&amp;gt; i' &amp;lt;/tex&amp;gt;, причём &amp;lt;tex&amp;gt; i &amp;lt; i' &amp;lt;/tex&amp;gt; и &amp;lt;tex&amp;gt; i + j = i' + j' &amp;lt;/tex&amp;gt;, то изменение с позиции &amp;lt;tex&amp;gt; i' &amp;lt;/tex&amp;gt; только уменьшит значение &amp;lt;tex&amp;gt; p[i + j]&amp;lt;/tex&amp;gt;. Действительно, значение после первого присвоения &amp;lt;tex&amp;gt;p[i + j] = j &amp;gt; j' = p[i' + j']&amp;lt;/tex&amp;gt;. В итоге получаем алгоритм: идем слева направо по массиву &amp;lt;tex&amp;gt;z&amp;lt;/tex&amp;gt; и, находясь на позиции &amp;lt;tex&amp;gt;i&amp;lt;/tex&amp;gt;, пытаемся записать в &amp;lt;tex&amp;gt;p&amp;lt;/tex&amp;gt; от позиции &amp;lt;tex&amp;gt;i + z[i] - 1 &amp;lt;/tex&amp;gt; до  &amp;lt;tex&amp;gt;i&amp;lt;/tex&amp;gt; значение &amp;lt;tex&amp;gt; j + 1,&amp;lt;/tex&amp;gt; где &amp;lt;tex&amp;gt;j&amp;lt;/tex&amp;gt; пробегает все значения &amp;lt;tex&amp;gt; 0 \dots z[i] - 1&amp;lt;/tex&amp;gt;, пока не наткнемся на уже инициализированный элемент. Слева от него все значения тоже нет смысла обновлять, поэтому прерываем эту итерацию. &lt;br /&gt;
&lt;br /&gt;
Убедимся, что алгоритм работает за линейное время (см. псевдокод). Каждый элемент устанавливается ровно один раз. Дальше на нем может случиться только &amp;lt;tex&amp;gt;\mathrm{break}&amp;lt;/tex&amp;gt;. Поэтому в итоге внутренний цикл суммарно отработает за количество установленных значений и количество &amp;lt;tex&amp;gt;\mathrm{break}&amp;lt;/tex&amp;gt;. Количество установленных значений {{---}} &amp;lt;tex&amp;gt; n&amp;lt;/tex&amp;gt;. А число &amp;lt;tex&amp;gt;\mathrm{break}&amp;lt;/tex&amp;gt; тоже будет не больше &amp;lt;tex&amp;gt;n&amp;lt;/tex&amp;gt;, так как каждый &amp;lt;tex&amp;gt;\mathrm{break}&amp;lt;/tex&amp;gt; переводит внешний цикл на следующую итерацию, откуда получаем итоговую асимптотику &amp;lt;tex&amp;gt;O(n)&amp;lt;/tex&amp;gt;. &lt;br /&gt;
&lt;br /&gt;
[[Файл:ZP4.jpg|800px]]&lt;br /&gt;
&lt;br /&gt;
=== Псевдокод ===&lt;br /&gt;
 '''int'''[] buildPrefixFunctionFromZFunction('''int'''[] z):&lt;br /&gt;
   '''int'''[] p = '''int'''[z.length]&lt;br /&gt;
   fill(p, 0)&lt;br /&gt;
   '''for''' i = 2 '''to''' z.length - 1&lt;br /&gt;
     '''for''' j = z[i] - 1 '''downto''' 0&lt;br /&gt;
       '''if''' p[i + j] &amp;gt; 0 &lt;br /&gt;
         '''break'''&lt;br /&gt;
       '''else'''&lt;br /&gt;
         p[i + j] = j + 1&lt;br /&gt;
   '''return''' p&lt;br /&gt;
&lt;br /&gt;
==Построение строки по префикс-функции==&lt;br /&gt;
===Постановка задачи=== &lt;br /&gt;
Восстановить строку по префикс-функции за &amp;lt;tex&amp;gt;O(n)&amp;lt;/tex&amp;gt;, считая алфавит неограниченным.&lt;br /&gt;
&lt;br /&gt;
===Описание алгоритма===&lt;br /&gt;
Здесь для удобства будем считать, что строки и массивы нумеруются с нуля.&lt;br /&gt;
&lt;br /&gt;
Пусть в массиве &amp;lt;tex&amp;gt;p&amp;lt;/tex&amp;gt; хранятся значения префикс-функции, в &amp;lt;tex&amp;gt;s&amp;lt;/tex&amp;gt; будет записан ответ. Пойдем по массиву &amp;lt;tex&amp;gt;p&amp;lt;/tex&amp;gt; слева направо.&lt;br /&gt;
&lt;br /&gt;
Пусть мы хотим узнать значение &amp;lt;tex&amp;gt;s[i]&amp;lt;/tex&amp;gt;. Для этого посмотрим на значение &amp;lt;tex&amp;gt;p[i]&amp;lt;/tex&amp;gt;: если &amp;lt;tex&amp;gt;p[i] =0&amp;lt;/tex&amp;gt;, тогда в &amp;lt;tex&amp;gt;s[i]&amp;lt;/tex&amp;gt; запишем новый символ, иначе &amp;lt;tex&amp;gt;s[i] = s[p[i]]&amp;lt;/tex&amp;gt;. Обратим внимание, что  &amp;lt;tex&amp;gt;s[p[i]]&amp;lt;/tex&amp;gt; нам уже известно, так как &amp;lt;tex&amp;gt;p[i] &amp;lt; i&amp;lt;/tex&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
=== Реализация ===&lt;br /&gt;
 '''string''' buildFromPrefix('''int'''[] p):&lt;br /&gt;
   s = &amp;quot;&amp;quot; &lt;br /&gt;
   '''for''' i = 0 '''to''' p.length - 1&lt;br /&gt;
       '''if''' p[i] == 0     &lt;br /&gt;
           s += new character&lt;br /&gt;
       '''else'''&lt;br /&gt;
           s += s[p[i] - 1]&lt;br /&gt;
   '''return''' s&lt;br /&gt;
&lt;br /&gt;
===Доказательство корректности алгоритма===&lt;br /&gt;
Докажем, что если нам дали корректную префикс-функцию, то наш алгоритм построит строку с такой же префикс-функцией. Также заметим, что строк с такой префикс-функцией может быть много, и алгоритм строит только одну из них.&lt;br /&gt;
&lt;br /&gt;
Пусть &amp;lt;tex&amp;gt;p&amp;lt;/tex&amp;gt; {{---}} данная префикс-функция, строку &amp;lt;tex&amp;gt;s&amp;lt;/tex&amp;gt; построил наш алгоритм, &amp;lt;tex&amp;gt; q &amp;lt;/tex&amp;gt; {{---}} массив значений префикс-функции для &amp;lt;tex&amp;gt;s&amp;lt;/tex&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
Докажем корректность индукцией по длине массива префикс-функции полученной строки. Для начала заметим, что на предыдущие значения массива &amp;lt;tex&amp;gt; q &amp;lt;/tex&amp;gt; прибавление нового символа не влияет, так как при подсчёте префикс-функции на &amp;lt;tex&amp;gt; i &amp;lt;/tex&amp;gt;-ой позиции рассматриваются символы на позициях не больше &amp;lt;tex&amp;gt; i &amp;lt;/tex&amp;gt;. Поэтому достаточно показать, что очередное значение префикс-функции будет вычислено правильно.&lt;br /&gt;
* База очевидна для строки длины &amp;lt;tex&amp;gt;1&amp;lt;/tex&amp;gt;.&lt;br /&gt;
* Переход: пусть до &amp;lt;tex&amp;gt;n&amp;lt;/tex&amp;gt;-ой позиции мы построили строку, что &amp;lt;tex&amp;gt;p[0..n - 1] = q[0..n - 1]&amp;lt;/tex&amp;gt;. Возможны два случая:&lt;br /&gt;
**  &amp;lt;tex&amp;gt;p[n] = 0&amp;lt;/tex&amp;gt;. Тогда мы добавляем новый символ, поэтому &amp;lt;tex&amp;gt;q[n]&amp;lt;/tex&amp;gt; тоже будет равно &amp;lt;tex&amp;gt;0&amp;lt;/tex&amp;gt;. &lt;br /&gt;
** &amp;lt;tex&amp;gt;p[n] &amp;gt; 0&amp;lt;/tex&amp;gt;. Бордер строки &amp;lt;tex&amp;gt; s[0..n - 1] &amp;lt;/tex&amp;gt; имеет длину &amp;lt;tex&amp;gt; p[n-1] = q[n-1] &amp;lt;/tex&amp;gt;. Поэтому если дописать к строке &amp;lt;tex&amp;gt; s &amp;lt;/tex&amp;gt; символ &amp;lt;tex&amp;gt; s[q[n]] &amp;lt;/tex&amp;gt;, то бордер нашей новой строки &amp;lt;tex&amp;gt; s[0..n] &amp;lt;/tex&amp;gt; станет равен &amp;lt;tex&amp;gt; p[n] &amp;lt;/tex&amp;gt;, как можно увидеть на [[Префикс-функция#Эффективный алгоритм | рисунке]].&lt;br /&gt;
&lt;br /&gt;
== См. также ==&lt;br /&gt;
*[[Z-функция|Z-функция]]&lt;br /&gt;
*[[Алгоритм Кнута-Морриса-Пратта|Алгоритм Кнута-Морриса-Пратта]]&lt;br /&gt;
== Источники информации ==&lt;br /&gt;
*[[wikipedia:ru:Префикс-функция | Википедия {{---}} Префикс-функция]]&lt;br /&gt;
*[http://e-maxx.ru/algo/prefix_function MAXimal :: algo :: Префикс-функция]&lt;br /&gt;
* Кормен Т., Лейзерсон Ч., Ривест Р. Алгоритмы: построение и анализ. {{---}} 2-е изд. {{---}} М.: Издательский дом «Вильямс», 2007. {{---}} С. 1296 ISBN 978-5-8459-0857-5&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
[[Категория: Алгоритмы и структуры данных]]&lt;br /&gt;
[[Категория: Поиск подстроки в строке]]&lt;/div&gt;</summary>
		<author><name>37.112.34.53</name></author>	</entry>

	</feed>