Задача о наибольшей подпоследовательности-палиндроме — различия между версиями

Материал из Викиконспекты
Перейти к: навигация, поиск
(Оценка асимптотики)
м (rollbackEdits.php mass rollback)
 
(не показано 68 промежуточных версий 6 участников)
Строка 1: Строка 1:
{{В разработке}}
+
{{Определение|definition='''Палиндромом''' (англ. <i>Palindrome</i>) называется строка, которая одинаково читается как слева направо, так и справа налево.}}
Задача о '''наибольшей подпоследовательности-палиндрома''' — это задача поиска длины наибольшей подпоследовательности-палиндрома, которую можно получить вычеркиванием некоторых букв из данной последовательности.
+
{{Определение|definition='''Подпоследовательностью-палиндромом данной строки''' (англ. <i>Largest Palindromic Subsequence</i>) называется последовательность символов из данной строки, не обязательно идущих подряд, являющаяся палиндромом. }}
== Определения ==
+
Например, '''''HELOLEH''''' является подпоследовательностью-палиндромом строки '''''HTEOLFEOLEH'''''.
{{Определение|definition='''Палиндромом''' называется строка, которая одинаково читается как слева направо, так и справа налево.}}
 
  
{{Определение|definition='''Подпоследовательностью-палиндромом данной строки''' называется последовательность символов из данной строки, не обязательно идущих подряд, являющаяся палиндромом. }}
+
{{Задача
 +
|definition =  
 +
Задача о '''наибольшей подпоследовательности-палиндроме''' {{---}} это задача о поиске наибольшей подпоследовательности, которую можно получить вычеркиванием некоторых букв из данной последовательности таким образом, что оставшаяся подпоследовательность будет палиндромом.
 +
}}
  
'''''Например''''', '''''HELOLEH''''' является подпоследовательностью-палиндромом строки '''''HTEOLFEOLEH'''''.
 
 
== Решение ==
 
== Решение ==
=== Алгоритм Манакера ===
+
Обозначим данную последовательность через <tex>S</tex>, а ее элементы {{---}} через <tex>S[i], 0 \leqslant  i \leqslant  n - 1</tex>, где <tex>n</tex> {{---}} длина строки <tex>S</tex>. Будем рассматривать возможные подпоследовательности данной последовательности с <tex>i - </tex>го по <tex>j - </tex>ый символ включительно, обозначив её как <tex>S(i, j)</tex>. Длины максимальных подпалиндромов для данной последовательности будем записывать в двумерный массив <tex>L</tex>: <tex>L[i][j]</tex> {{---}} длина максимальной подпоследовательности-палиндрома, который можно получить из последовательности <tex>S(i, j)</tex>.
Сначала поймем как находить все подпоследовательности-палиндромы нечётной длины, т.е. вычислять массив <tex>d_1[]</tex>; решение для палиндромов чётной длины (т.е. нахождение массива <tex>d_2[]</tex>) получится небольшой модификацией этого.
 
  
Для быстрого вычисления будем поддерживать '''границы''' <tex>(l, r)</tex> самого правого из обнаруженных палиндромов (т.е. палиндрома с наибольшим значением <tex>r</tex>). Изначально можно считать <tex>l=0, r=-1</tex>.
+
Начнем решать задачу с простых подпоследовательностей. Для последовательности из одного элемента (то есть подпоследовательности вида <tex>S(i, i)</tex>) ответ очевиден {{---}} ничего вычеркивать не надо, такая строка будет искомой подпоследовательностью-палиндромом. Для последовательности из двух элементов <tex>S(i, i + 1)</tex> возможны два варианта: если элементы равны, то мы имеем подпоследовательность-палиндром, ничего вычеркивать не надо. Если же элементы не равны, то вычеркиваем любой.
  
Итак, пусть вычисляем значение <tex>d_1[i]</tex> для очередного <tex>i</tex>, при этом все предыдущие значения <tex>d_1[]</tex> уже подсчитаны.
+
Пусть теперь нам дана подпоследовательность <tex>S(i, j)</tex>. Если <tex>S[i]</tex> и <tex>S[j]</tex> элементы подпоследовательности не совпадают, то один из них нужно вычеркнуть. Тогда у нас останется подпоследовательность <tex>S(i, j - 1)</tex> или <tex>S(i + 1, j)</tex> {{---}} то есть мы сведем задачу к подзадаче: <tex>L[i][j] = \max(L[i][j - 1], L[i + 1][j])</tex>. Если же первый и последний элементы равны, то мы можем оставить оба, но необходимо знать решение задачи <tex>S(i + 1, j - 1):L[i][j] = L[i + 1][j - 1] + 2</tex>. Таким образом получаем следующее рекуррентное соотношение:
  
* Если <tex>i</tex> не находится в пределах текущего палиндрома, т.е. <tex>i>r</tex>, то просто выполним тривиальный алгоритм. Т.е. будем последовательно увеличивать значение <tex>d_1[i]</tex>, и проверять каждый раз — правда ли текущая подпоследовательность <tex>[i-d_1[i];i+d_1[i]]</tex> является палиндромом. Когда найдем первое расхождение, либо когда дойдем до границ строки <tex>S</tex> — останавливаемся: окончательно посчитали значение <tex>d_1[i]</tex>. После этого мы должны не забыть обновить значения <tex>(l,r)</tex>.
+
<tex>
 +
L[i][j] =
 +
\begin{cases}
 +
1, & i = j\\
 +
0, & i > j\\
 +
L[i + 1][j - 1] + 2, & s[i] = s[j] \\
 +
\max(L[i][j - 1], L[i + 1][j]), & s[i] \neq s[j]
 +
\end{cases}
 +
</tex>
  
* Рассмотрим случай, когда <tex>i \le r</tex>. Попробуем извлечь часть информации из уже подсчитанных значений <tex>d_1[]</tex>. А именно, отразим позицию <tex>i</tex> внутри палиндрома <tex>(l,r)</tex>, т.е. получим позицию <tex>j=l+(r-i)</tex>, и рассмотрим значение <tex>d_1[j]</tex>. Поскольку <tex>j</tex> — позиция, симметричная позиции <tex>i</tex>, то почти всегда можно просто присвоить <tex>d_1[i]=d_1[j]</tex>.
 
  
[[Файл:Palindrome1.png|300px|thumb|right|палиндром вокруг  фактически "копируется" в палиндром вокруг <tex>i</tex>]]
+
=== Асимптотика ===
 +
Каждый элемент массива мы вычисляем <tex>1</tex> раз за <tex>O(1)</tex> обращаясь к уже вычисленным элементам. Так как размер массива <tex>n \times n</tex>, то алгоритм работает за <tex>O(n^2)</tex>
  
Однако здесь есть тонкость, которую надо обработать правильно: когда "внутренний палиндром" достигает границы внешнего или вылазит за неё, т.е. <tex>j-d_1[j]+1 \le l</tex> (или, что то же самое, <tex>i+d_1[j]-1 \ge r</tex>). Поскольку за границами внешнего палиндрома никакой симметрии не гарантируется, то просто присвоить <tex>d_1[i]=d_1[j]</tex> будет уже некорректно: у нас недостаточно сведений, чтобы утверждать, что в позиции <tex>i</tex> палиндром имеет такую же длину.
+
== Пример ==
 +
Рассмотрим решение на примере последовательности '''''ABACCBA'''''. Первым делом заполняем диагональ массива единицами, они будут соответствовать подпоследовательностями <tex>S(i, i)</tex> из одного элемента. Затем начинаем рассматривать подпоследовательности длины два. Во всех подпоследовательностях, кроме <tex>S(3, 4)</tex>, элементы различны, поэтому в соответствующие ячейки запишем <tex>1</tex>, а в <tex>L[3][4]</tex> {{---}}  <tex>2</tex>.
  
На самом деле, чтобы правильно обрабатывать такие ситуации, надо "обрезать" длину палиндрома, т.е. присвоить <tex>d_1[i]=r-i</tex>. После этого следует пустить тривиальный алгоритм, который будет пытаться увеличить значение <tex>d_1[i]</tex>, пока это возможно.
+
Получается, что мы будем заполнять массив по диагоналям, начиная с главной диагонали. Для подпоследовательностей длины <tex>3</tex> получаются следующие значения: в подпоследовательности '''''ABA''''' первый и последний элемент равны, поэтому <tex>L[0][2] = L[1][1] + 2</tex>. В остальных подпоследовательностях первый и последний элементы различны.
  
:
 
  
[[Файл:Palindrome2.png|300px|thumb|right|палиндром с центром в <tex>j</tex> изображен уже "обрезанным" до такой длины, что он впритык помещается во внешний палиндром]]
+
'''''BAC''''': <tex>L[1][3] = \max(L[1][2], L[2][3]) = 1</tex>
  
В завершение описания алгоритма сталось только напомнить, что надо не забывать обновлять значения <tex>(l,r)</tex> после вычисления очередного значения <tex>d_1[i]</tex>.
+
'''''ACC''''': <tex>L[2][4] = \max(L[2][3], L[3][4]) = 2</tex>
  
Также повторимся, что выше описано рассуждение для вычисления массива нечётных палиндромов <tex>d_1[]</tex>; для массива чётных палиндромов <tex>d_2[]</tex> все рассуждения аналогичны.
+
'''''CCB''''': <tex>L[3][5] = \max(L[3][4], L[4][5]) = 2</tex>
==== Оценка асимптотики ====
 
На первый взгляд не очевидно, что данный алгоритм имеет линейную асимптотику: при вычислении ответа для определённой позиции в нем нередко запускается тривиальный алгоритм поиска палиндромов.
 
  
Однако более внимательный анализ показывает, что алгоритм всё же линеен. (Стоит сослаться на [[алгоритм построения Z-функции строки]], который внутренне сильно напоминает данный алгоритм, и работает также за линейное время.)
+
'''''CBA''''': <tex>L[4][6] = \max(L[4][5], L[5][6]) = 1</tex>
  
В самом деле, легко проследить по алгоритму, что каждая итерация, производимая тривиальным поиском, приводит к увеличению на один границы <tex>r</tex>. При этом уменьшений <tex>r</tex> по ходу алгоритма происходить не может. Следовательно, тривиальный алгоритм в сумме совершит лишь <tex>O(n)</tex> действий.
+
Продолжая далее аналогичные рассуждения, заполним все ячейки над диагональю и в ячейке <tex>L[0][6]</tex> получим ответ {{---}} <tex>6</tex>.
  
Учитывая, что, кроме тривиальных поисков, все остальные части алгоритма Манакера очевидно работают за линейное время, мы и получаем итоговую асимптотику: <tex>O(n)</tex>.
+
Если же в задаче необходимо вывести не длину, а саму подпоследовательность-палиндром, то дополнительно к массиву длин мы должны построить массив переходов {{---}} для каждой ячейки запомнить, какой из случаев был реализован.
  
==== Псевдокод ====
+
Последовательность заполнения массива и массив переходов см. на изображениях ниже.
  
=== Тривиальный алгоритм ===
+
[[Файл:Palindrome11.png|200px|Заполнение массива длин (1)]]
 +
[[Файл:Palindrome12.png|200px|Заполнение массива длин (2)]]
 +
[[Файл:Palindrome13.png|200px|Заполнение массива длин (3)]]
 +
[[Файл:Palindrome14.png|200px|Заполнение массива длин (4)]]
 +
[[Файл:Palindrome15.png|200px|Массив переходов]]
  
==== Псевдокод ====
+
== Псевдокод ==
 +
Перед вызовом процедуры заполняем <tex>L[][]</tex> начальными значениями: <tex>L[i][j] = 1</tex> если <tex>i=j</tex>, <tex>L[i][j] = 0</tex>, если <tex>i>j</tex>, в остальных случаях <tex>L[i][j]=-1</tex>.
 +
При первом вызове функции в качестве аргументов передаем индексы первого и последнего элементов исходной строки. Например для строки длиной <tex> N </tex> вызов функции будет иметь следующий вид: <tex> \mathrm{palSubSeq(0, N - 1)}</tex>. Искомая же длина будет записана в ячейке <tex>L[0][N-1]</tex>.
 +
 
 +
'''Функция для вычисления длины палиндрома''':
 +
*Границы исходной последовательности: <tex> \mathtt{left, right}</tex>
 +
<code style = "display: inline-block;">
 +
  '''int''' palSubSeq(left: '''int''', right: '''int'''):
 +
    '''if''' L[left][right] == -1
 +
      '''if''' s[left] == s[right]
 +
        L[left][right] = palSubSeq(left + 1, right - 1) + 2
 +
      '''else'''
 +
        L[left][right] = max(palSubSeq(left + 1, right), palSubSeq(left, right - 1)) 
 +
    '''return''' L[left][right]
 +
</code>
 +
'''Процедура для построения искомого палиндрома''':
 +
*Границы исходной последовательности: <tex> \mathtt{left, right}</tex>
 +
*Границы искомой подпоследовательности-палиндрома, где правой границей будет длина найденного палиндрома: <tex> \mathtt{palLeft, palRight}</tex>
 +
<code style = "display: inline-block;">
 +
  <font color=green>// palindrome {{---}} массив символов, где в palindrome[i] содержится символ искомой последовательности-палиндрома</font>
 +
  palChars(left: '''int''', right: '''int''', palLeft: '''int''', palRight: '''int'''):
 +
    '''while''' left <tex>\leqslant </tex> right
 +
      '''if''' left == right '''and''' L[left][right] == 1
 +
        palindrome[palLeft++] = S[left++]
 +
      '''else'''
 +
        '''if''' S[left] == S[right]
 +
          palindrome[palLeft++] = S[left++]
 +
          palindrome[palRight--] = S[right--]
 +
        '''else'''
 +
          '''if''' L[left + 1][right] <tex> > </tex> L[left][right - 1]
 +
            left++
 +
          '''else'''
 +
            right--
 +
</code>
  
 
== См. также ==
 
== См. также ==
*[[Задача о наибольшей общей подпоследовательности]]
+
* [[Задача о наибольшей общей подпоследовательности]]
*[[Задача о наибольшей возрастающей подпоследовательности]]
+
* [[Задача о наибольшей возрастающей подпоследовательности]]
 +
* [[Задача о наибольшей общей палиндромной подпоследовательности]]
  
== Литература ==
+
==Источники информации==
*[[wikipedia:Palindrome|Wikipedia — Palindrome]]
+
* [[wikipedia:ru:Палиндром|Википедия {{---}} Палиндром]]
*[[wikipedia:ru:Палиндром|Википедия — Палиндром]]
+
* [[wikipedia:Palindrome|Wikipedia {{---}} Palindrome]]
*[http://e-maxx.ru/algo/palindromes_count E-Maxx - Нахождение всех подпалиндромов]
+
* [http://wcipeg.com/wiki/Longest_palindromic_subsequence Wikipedia {{---}} Longest palindromic subsequence]
  
 
[[Категория:Дискретная математика и алгоритмы]]
 
[[Категория:Дискретная математика и алгоритмы]]
 
[[Категория:Динамическое программирование]]
 
[[Категория:Динамическое программирование]]

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

Определение:
Палиндромом (англ. Palindrome) называется строка, которая одинаково читается как слева направо, так и справа налево.


Определение:
Подпоследовательностью-палиндромом данной строки (англ. Largest Palindromic Subsequence) называется последовательность символов из данной строки, не обязательно идущих подряд, являющаяся палиндромом.

Например, HELOLEH является подпоследовательностью-палиндромом строки HTEOLFEOLEH.


Задача:
Задача о наибольшей подпоследовательности-палиндроме — это задача о поиске наибольшей подпоследовательности, которую можно получить вычеркиванием некоторых букв из данной последовательности таким образом, что оставшаяся подпоследовательность будет палиндромом.


Решение

Обозначим данную последовательность через [math]S[/math], а ее элементы — через [math]S[i], 0 \leqslant i \leqslant n - 1[/math], где [math]n[/math] — длина строки [math]S[/math]. Будем рассматривать возможные подпоследовательности данной последовательности с [math]i - [/math]го по [math]j - [/math]ый символ включительно, обозначив её как [math]S(i, j)[/math]. Длины максимальных подпалиндромов для данной последовательности будем записывать в двумерный массив [math]L[/math]: [math]L[i][j][/math] — длина максимальной подпоследовательности-палиндрома, который можно получить из последовательности [math]S(i, j)[/math].

Начнем решать задачу с простых подпоследовательностей. Для последовательности из одного элемента (то есть подпоследовательности вида [math]S(i, i)[/math]) ответ очевиден — ничего вычеркивать не надо, такая строка будет искомой подпоследовательностью-палиндромом. Для последовательности из двух элементов [math]S(i, i + 1)[/math] возможны два варианта: если элементы равны, то мы имеем подпоследовательность-палиндром, ничего вычеркивать не надо. Если же элементы не равны, то вычеркиваем любой.

Пусть теперь нам дана подпоследовательность [math]S(i, j)[/math]. Если [math]S[i][/math] и [math]S[j][/math] элементы подпоследовательности не совпадают, то один из них нужно вычеркнуть. Тогда у нас останется подпоследовательность [math]S(i, j - 1)[/math] или [math]S(i + 1, j)[/math] — то есть мы сведем задачу к подзадаче: [math]L[i][j] = \max(L[i][j - 1], L[i + 1][j])[/math]. Если же первый и последний элементы равны, то мы можем оставить оба, но необходимо знать решение задачи [math]S(i + 1, j - 1):L[i][j] = L[i + 1][j - 1] + 2[/math]. Таким образом получаем следующее рекуррентное соотношение:

[math] L[i][j] = \begin{cases} 1, & i = j\\ 0, & i \gt j\\ L[i + 1][j - 1] + 2, & s[i] = s[j] \\ \max(L[i][j - 1], L[i + 1][j]), & s[i] \neq s[j] \end{cases} [/math]


Асимптотика

Каждый элемент массива мы вычисляем [math]1[/math] раз за [math]O(1)[/math] обращаясь к уже вычисленным элементам. Так как размер массива [math]n \times n[/math], то алгоритм работает за [math]O(n^2)[/math]

Пример

Рассмотрим решение на примере последовательности ABACCBA. Первым делом заполняем диагональ массива единицами, они будут соответствовать подпоследовательностями [math]S(i, i)[/math] из одного элемента. Затем начинаем рассматривать подпоследовательности длины два. Во всех подпоследовательностях, кроме [math]S(3, 4)[/math], элементы различны, поэтому в соответствующие ячейки запишем [math]1[/math], а в [math]L[3][4][/math][math]2[/math].

Получается, что мы будем заполнять массив по диагоналям, начиная с главной диагонали. Для подпоследовательностей длины [math]3[/math] получаются следующие значения: в подпоследовательности ABA первый и последний элемент равны, поэтому [math]L[0][2] = L[1][1] + 2[/math]. В остальных подпоследовательностях первый и последний элементы различны.


BAC: [math]L[1][3] = \max(L[1][2], L[2][3]) = 1[/math]

ACC: [math]L[2][4] = \max(L[2][3], L[3][4]) = 2[/math]

CCB: [math]L[3][5] = \max(L[3][4], L[4][5]) = 2[/math]

CBA: [math]L[4][6] = \max(L[4][5], L[5][6]) = 1[/math]

Продолжая далее аналогичные рассуждения, заполним все ячейки над диагональю и в ячейке [math]L[0][6][/math] получим ответ — [math]6[/math].

Если же в задаче необходимо вывести не длину, а саму подпоследовательность-палиндром, то дополнительно к массиву длин мы должны построить массив переходов — для каждой ячейки запомнить, какой из случаев был реализован.

Последовательность заполнения массива и массив переходов см. на изображениях ниже.

Заполнение массива длин (1) Заполнение массива длин (2) Заполнение массива длин (3) Заполнение массива длин (4) Массив переходов

Псевдокод

Перед вызовом процедуры заполняем [math]L[][][/math] начальными значениями: [math]L[i][j] = 1[/math] если [math]i=j[/math], [math]L[i][j] = 0[/math], если [math]i\gt j[/math], в остальных случаях [math]L[i][j]=-1[/math]. При первом вызове функции в качестве аргументов передаем индексы первого и последнего элементов исходной строки. Например для строки длиной [math] N [/math] вызов функции будет иметь следующий вид: [math] \mathrm{palSubSeq(0, N - 1)}[/math]. Искомая же длина будет записана в ячейке [math]L[0][N-1][/math].

Функция для вычисления длины палиндрома:

  • Границы исходной последовательности: [math] \mathtt{left, right}[/math]

 int palSubSeq(left: int, right: int):
   if L[left][right] == -1 
     if s[left] == s[right] 
       L[left][right] = palSubSeq(left + 1, right - 1) + 2
     else 
       L[left][right] = max(palSubSeq(left + 1, right), palSubSeq(left, right - 1))  
   return L[left][right]

Процедура для построения искомого палиндрома:

  • Границы исходной последовательности: [math] \mathtt{left, right}[/math]
  • Границы искомой подпоследовательности-палиндрома, где правой границей будет длина найденного палиндрома: [math] \mathtt{palLeft, palRight}[/math]

 // palindrome — массив символов, где в palindrome[i] содержится символ искомой последовательности-палиндрома
 palChars(left: int, right: int, palLeft: int, palRight: int):
   while left [math]\leqslant [/math] right
     if left == right and L[left][right] == 1
       palindrome[palLeft++] = S[left++]
     else
       if S[left] == S[right]
         palindrome[palLeft++] = S[left++]
         palindrome[palRight--] = S[right--]
       else
         if L[left + 1][right] [math] \gt  [/math] L[left][right - 1]
           left++
         else
           right--

См. также

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