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

Материал из Викиконспекты
Перейти к: навигация, поиск
(Оптимизация)
Строка 1: Строка 1:
Префикс-функция строки <tex>s</tex> {{---}} функция <tex>\pi(i) = max \{ j | j < i,</tex> <tex>s[1..j] = s[i - j + 1..i] \}</tex>.
+
Префикс-функция строки <tex>s</tex> {{---}} функция <tex>\pi(i) = max \{ k | k < i,</tex> <tex>s[1..k] = s[i - k + 1..i] \}</tex>.
  
 
==Алгоритм==
 
==Алгоритм==
Строка 27: Строка 27:
 
       <tex>\pi</tex> = 0
 
       <tex>\pi</tex> = 0
 
       '''for''' i = 1 '''to''' n
 
       '''for''' i = 1 '''to''' n
           '''for''' j = 1 '''to''' i - 1
+
           '''for''' k = 1 '''to''' i - 1
               '''if''' s[1..j] == s[i - j + 1..i]
+
               '''if''' s[1..k] == s[i - k + 1..i]
                     <tex>\pi</tex>[i] = j
+
                     <tex>\pi</tex>[i] = k
 
       '''return''' <tex>\pi</tex>
 
       '''return''' <tex>\pi</tex>
  
Строка 38: Строка 38:
 
Внесем несколько важных замечаний:
 
Внесем несколько важных замечаний:
 
*<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)</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> j</tex>, для которой верно <tex>\pi(i) = j + 1</tex>. Когда мы найдем такое <tex>j</tex> нам достаточно будет сравнить <tex>s[j + 1]</tex> и <tex>s[i]</tex>, при их равенстве <tex>\pi(i) = j + 1</tex> будет верно. Будем искать наше <tex>j</tex> пока оно больше нуля, при равенстве нулю <tex>\pi(i) = 1</tex>, если <tex>s[i] = s[1]</tex>, иначе нулю. Общая схема алгоритма у нас есть, теперь нужно только научиться искать <tex>j</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>j</tex> нам стоит использовать равенство <tex>j = \pi(j)</tex>, когда <tex>s[j+1] = s[i]</tex> ложно, взяв за исходное <tex> j = \pi(i -1)</tex>, это позволит выбирать <tex>j</tex> по убыванию вплоть до нуля, так как очевидно, что <tex>\pi(x) \geq \pi(\pi(x))</tex> для любых <tex>x</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>.
 
===Псевдокод===
 
===Псевдокод===
 
  '''Prefix_function''' (<tex>s</tex>)
 
  '''Prefix_function''' (<tex>s</tex>)
 
       <tex>\pi</tex> = 0
 
       <tex>\pi</tex> = 0
 
       '''for''' i = 2 '''to''' n
 
       '''for''' i = 2 '''to''' n
           j = <tex>\pi</tex>[i - 1]  
+
           k = <tex>\pi</tex>[i - 1]  
           '''while''' j > 0 && s[i] != s[j + 1]
+
           '''while''' k > 0 && s[i] != s[k + 1]
               j = <tex>\pi</tex>[j]
+
               k = <tex>\pi</tex>[k]
           '''if''' s[i] == s[j + 1]
+
           '''if''' s[i] == s[k + 1]
               j++
+
               k++
           <tex>\pi</tex>[i] = j
+
           <tex>\pi</tex>[i] = k
 
       '''return''' <tex>\pi</tex>
 
       '''return''' <tex>\pi</tex>
  

Версия 19:09, 15 апреля 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] = 0
     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].

Оптимизация

Внесем несколько важных замечаний:

  • [math]\pi(i)[/math] превосходит [math]\pi(i-1)[/math] не больше чем на [math]1[/math]. Действительно, если [math]\pi(i) \gt \pi(i-1) + 1[/math], тогда [math]\pi(i) - 1 \gt \pi(i-1)[/math], значит в [math]\pi(i-1)[/math] не максимально возможное значение, получили противоречие.
  • Избавимся от явных сравнений строк. Пусть мы вычислили [math]\pi(i-1)[/math] и [math]s[\pi(i-1) + 1] = s[i][/math], тогда очевидно [math]\pi(i) = \pi(i-1) + 1[/math]. Если же условие [math]s[\pi(i) + 1] = s[i + 1][/math] ложно, то хотелось бы найти наибольшую длину [math] k[/math], для которой верно [math]\pi(i) = k + 1[/math]. Когда мы найдем такое [math]k[/math] нам достаточно будет сравнить [math]s[k + 1][/math] и [math]s[i][/math], при их равенстве [math]\pi(i) = k + 1[/math] будет верно. Будем искать наше [math]k[/math] пока оно больше нуля, при равенстве нулю [math]\pi(i) = 1[/math], если [math]s[i] = s[1][/math], иначе нулю. Общая схема алгоритма у нас есть, теперь нужно только научиться искать [math]k[/math].
  • Для поиска [math]k[/math] нам стоит использовать равенство [math]k = \pi(k)[/math], когда [math]s[k+1] = s[i][/math] ложно, взяв за исходное [math] k = \pi(i -1)[/math], это позволит выбирать [math]k[/math] по убыванию вплоть до нуля, так как очевидно, что [math]\pi(x) \geq \pi(\pi(x))[/math] для любых [math]x[/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]O(n)[/math] итераций за [math]O(1)[/math], что дает нам итоговое [math]O(n)[/math].

Литература

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