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

Материал из Викиконспекты
Перейти к: навигация, поиск
(Время работы)
(Оптимизация)
Строка 36: Строка 36:
  
 
==Оптимизация==
 
==Оптимизация==
Внесем несколько важных замечаний:
+
В начале приводится псевдокод алгоритма, а затем его обоснование.
*<tex>\pi(i)</tex> превосходит <tex>\pi(i-1)</tex> не больше, чем на <tex>1</tex>. Действительно, если <tex>\pi(i) > \pi(i-1) + 1</tex>, тогда <tex>\pi(i) - 1 > \pi(i-1)</tex>, значит в <tex>\pi(i-1)</tex> не максимально возможное значение, получили противоречие.
 
*Избавимся от явных сравнений строк. Пусть мы вычислили <tex>\pi(i-1)</tex> и <tex>s[\pi(i-1) + 1] = s[i]</tex>, тогда очевидно <tex>\pi(i) = \pi(i-1) + 1</tex>. Если же условие <tex>s[\pi(i) + 1] = s[i + 1]</tex> ложно, то хотелось бы найти наибольшую длину <tex> k</tex>, для которой верно <tex>\pi(i) = k + 1</tex>. Когда мы найдем такое <tex>k</tex> нам достаточно будет сравнить <tex>s[k + 1]</tex> и <tex>s[i]</tex>, при их равенстве <tex>\pi(i) = k + 1</tex> будет верно. Будем искать наше <tex>k</tex> пока оно больше нуля, при равенстве нулю <tex>\pi(i) = 1</tex>, если <tex>s[i] = s[1]</tex>, иначе нулю. Общая схема алгоритма у нас есть, теперь нужно только научиться искать <tex>k</tex>.
 
*Для поиска <tex>k</tex> нам стоит использовать равенство <tex>k = \pi(k)</tex>, когда <tex>s[k+1] = s[i]</tex> ложно, взяв за исходное <tex> k = \pi(i -1)</tex>, это позволит выбирать <tex>k</tex> по убыванию вплоть до нуля, так как очевидно, что <tex>\pi(x) \geq \pi(\pi(x))</tex> для любых <tex>x</tex>.
 
Последние два пункта наглядно проиллюстрированы на рисунке:
 
 
 
[[Файл:Prefix2.jpg‎]]
 
 
===Псевдокод===
 
===Псевдокод===
 
  '''Prefix_function''' (<tex>s</tex>)
 
  '''Prefix_function''' (<tex>s</tex>)
Строка 54: Строка 48:
 
           <tex>\pi</tex>[i] = k
 
           <tex>\pi</tex>[i] = k
 
       '''return''' <tex>\pi</tex>
 
       '''return''' <tex>\pi</tex>
 +
 +
===Корректность работы===
 +
Начнем с рассмотрения леммы, в которой показано, что путем итерации префиксной функции <tex>\pi</tex> можно перечислить все префиксы длины <tex> k</tex> <tex>s_k</tex>, которые являются истинными суффиксами заданного префикса длины <tex>i</tex> <tex>s_i</tex>. Введем обозначение <tex> \pi ' [i] = (\pi[i], \pi^{2}[i], ..., \pi^{t}[i])</tex>, где величина <tex>\pi^{j}[i]</tex> обозначает <tex>j</tex>-ю итерацию префиксной функции, т.е. <tex>\pi^{0}[i] = i</tex> и при <tex> i \ge 1</tex> <tex>\pi^{j}[i] = \pi[\pi^{j-1}[i]]</tex>. Кроме того, понятно, что последовательность <tex>\pi'[i]</tex> обрывается, когда в ней будет достигнуто значение <tex>\pi^t[i] = 0</tex>.
 +
{{Лемма
 +
|about=
 +
Лемма об итерации префиксной функции
 +
|statement=
 +
Пусть <tex>s</tex> - строка длиной <tex>n</tex> с префиксной функцией <tex>\pi</tex>. Тогда для всех <tex> i = 0,1,..,n</tex> имеем <tex>\pi'[i] = (k : k < q</tex> и <tex>s_k s_i</tex>.
  
 
===Время работы===
 
===Время работы===

Версия 21:09, 8 июня 2012

Префикс-функция строки [math]s[/math] — функция [math]\pi(i) = max \{ k | k \lt i,[/math] [math]s[1..k] = s[i - k + 1..i] \}[/math].

Алгоритм

Наивный алгоритм вычисляет префикс функцию непосредственно по определению, сравнивая префиксы и суффиксы строк.

Пример

Рассмотрим строку abcabcd, для которой значение префикс-функции равно [math][0,0,0,1,2,3,0][/math].

Шаг Строка Значение функции
[math]1[/math] a 0
[math]2[/math] ab 0
[math]3[/math] abc 0
[math]4[/math] abca 1
[math]5[/math] abcab 2
[math]6[/math] abcabc 3
[math]7[/math] abcabcd 0

Псевдокод

Prefix_function ([math]s[/math])
     [math]\pi[/math] = []
     for i = 1 to n
         for k = 1 to i - 1
             if s[1..k] == s[i - k + 1..i]
                 [math]\pi[/math][i] = k
     return [math]\pi[/math]

Время работы

Всего [math]O(n^2)[/math] итераций цикла, на каждой из который происходит сравнение строк за [math]O(n)[/math], что дает в итоге [math]O(n^3)[/math].

Оптимизация

В начале приводится псевдокод алгоритма, а затем его обоснование.

Псевдокод

Prefix_function ([math]s[/math])
     [math]\pi[/math] = 0
     for i = 2 to n
         k = [math]\pi[/math][i - 1] 
         while k > 0 && s[i] != s[k + 1]
             k = [math]\pi[/math][k]
         if s[i] == s[k + 1]
             k++
         [math]\pi[/math][i] = k
     return [math]\pi[/math]

Корректность работы

Начнем с рассмотрения леммы, в которой показано, что путем итерации префиксной функции [math]\pi[/math] можно перечислить все префиксы длины [math] k[/math] [math]s_k[/math], которые являются истинными суффиксами заданного префикса длины [math]i[/math] [math]s_i[/math]. Введем обозначение [math] \pi ' [i] = (\pi[i], \pi^{2}[i], ..., \pi^{t}[i])[/math], где величина [math]\pi^{j}[i][/math] обозначает [math]j[/math]-ю итерацию префиксной функции, т.е. [math]\pi^{0}[i] = i[/math] и при [math] i \ge 1[/math] [math]\pi^{j}[i] = \pi[\pi^{j-1}[i]][/math]. Кроме того, понятно, что последовательность [math]\pi'[i][/math] обрывается, когда в ней будет достигнуто значение [math]\pi^t[i] = 0[/math]. {{Лемма |about= Лемма об итерации префиксной функции |statement= Пусть [math]s[/math] - строка длиной [math]n[/math] с префиксной функцией [math]\pi[/math]. Тогда для всех [math] i = 0,1,..,n[/math] имеем [math]\pi'[i] = (k : k \lt q[/math] и [math]s_k s_i[/math].

Время работы

С помощью метода потенциалов можно показать, что время работы [math]O(n)[/math]. Потенциал величины [math]k[/math] связывается с текущим ее значением в алгоритме. Начальное значение этого потенциала равно нулю. На каждой итерации цикла [math]while[/math] значение [math]k[/math] уменьшается, поскольку [math]\pi(k) \lt k[/math]. Однако в силу [math]\pi(k) \ge 0[/math] значение этой переменной не бывает отрицательным. Также значение [math]k[/math] изменяется не более чем на 1 внутри тела цикла [math]for[/math]. Поскольку перед входом в цикл выполняется [math] k \lt i[/math] и поскольку значение переменной [math]i[/math] увеличивается в каждой итерации цикла [math]for[/math], справедливость неравенства [math]k \lt i[/math] сохраняется (подтверждая тот факт, что соблюдается также неравенство [math]\pi(i) \lt i [/math]. Каждое выполнение тела цикла [math]while[/math] можно оплатить соответствующим уменьшение потенциальной функции, поскольку [math]\pi(k) \lt k [/math]. Кроме этого значение потенциальной функции возрастает не более чем на 1, из-за этого амортизированная стоимость тела цикла [math]for[/math][math]O(1)[/math]. Так как количество итераций [math]O(n)[/math], и поскольку конечное значение потенциальной функции по величине не меньше, чем ее начальное значение, полное время работы в наихудшем случае равно [math]O(n)[/math].

Литература

Кормен Т., Лейзерсон Ч., Ривест Р. Алгоритмы: построение и анализ. — 2-е изд. — М.: Издательский дом «Вильямс», 2007. — С. 1296.