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

Материал из Викиконспекты
Перейти к: навигация, поиск
(Оценка асимптотики)
Строка 1: Строка 1:
Задача о '''наибольшей подпоследовательности-палиндрома''' — это задача поиска наибольшей подпоследовательности-палиндрома, которую можно получить вычеркиванием некоторых букв из данной последовательности.
+
{{В разработке}}
 +
Задача о '''наибольшей подпоследовательности-палиндрома''' — это задача поиска длины наибольшей подпоследовательности-палиндрома, которую можно получить вычеркиванием некоторых букв из данной последовательности.
 +
 
 
== Определения ==
 
== Определения ==
 +
 
{{Определение|definition='''Палиндромом''' называется строка, которая одинаково читается как слева направо, так и справа налево.}}
 
{{Определение|definition='''Палиндромом''' называется строка, которая одинаково читается как слева направо, так и справа налево.}}
  
Строка 7: Строка 10:
 
'''''Например''''', '''''HELOLEH''''' является подпоследовательностью-палиндромом строки '''''HTEOLFEOLEH'''''.  
 
'''''Например''''', '''''HELOLEH''''' является подпоследовательностью-палиндромом строки '''''HTEOLFEOLEH'''''.  
 
== Решение ==
 
== Решение ==
Для решения этой задачи будет необходимо перебрать все подпоследовательности-палиндромы данной последовательности. Наиболее оптимально это можно сделать с помощью алгоритма Манакера.
+
[[Файл:Palindrome11.jpg|200px|thumb|right|Массив длин подпоследовательностей-палиндромов]]
 +
[[Файл:Palindrome12.jpg|200px|thumb|right|Наглядный массив переходов]]
 +
Обозначим данную последовательность через <tex>S</tex>, а ее элементы — через <tex>S[i], 1 \le i \le n</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>S(i, i)</tex>) ответ очевиден — ничего вычеркивать не надо, такая строка будет искомой подпоследовательностью-палиндромом. Для последовательности из двух элементов <tex>S(i, i + 1)</tex> возможны два варианта: если элементы равны, то мы имеем подпоследовательность-палиндром, ничего вычеркивать не надо. Если же элементы не равны, то вычеркиваем любой.
Это алгоритм, который для поиска ответа в позиции  раз за разом пробует увеличить ответ на единицу, каждый раз сравнивая пару соответствующих символов.
 
  
Такой алгоритм слишком медленен, весь ответ он может посчитать лишь за время <tex>O(n^2)</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>.
Реализация тривиального алгоритма:
 
  
  List d1(n), d2(n)
+
== Пример ==
  for i = 0 to n
+
Рассмотрим решение на примере последовательности '''''ABACCBA'''''. Первым делом заполняем диагональ массива единицами, они будут соответствовать подпоследовательностями <tex>S(i, i)</tex> из одного элемента. Затем начинаем рассматривать подпоследовательности длины два. Во всех подпоследовательностях, кроме <tex>S(4, 5)</tex>, элементы различны, поэтому в соответствующие ячейки запишем <tex>1</tex>, а в <tex>L[4][5]</tex> — <tex>2</tex>.
    d1[i] = 1
 
    while i - d1[i] >= 0 && i + d1[i] < n && s[i-d1[i]] == s[i+d1[i]]
 
      ++d1[i]
 
    d2[i] = 0;
 
    while i - d2[i] - 1 >= 0 && i + d2[i] < n && s[i - d2[i] - 1] == s[i + d2[i]]
 
      ++d2[i]
 
  
=== Алгоритм Манакера ===
+
Получается, что мы будем заполнять массив по диагоналям, начиная с главной диагонали, ведущей из левого верхнего угла в правый нижний. Для подпоследовательностей длины <tex>3</tex> получаются следующие значения: в подпоследовательности '''''ABA''''' первый и последний элемент равны, поэтому <tex>L[1][3] = L[2][2] + 2</tex>. В остальных подпоследовательностях первый и последний элементы различны.
Сначала поймем как находить все подпоследовательности-палиндромы нечётной длины, т.е. вычислять массив <tex>d_1[]</tex>; решение для палиндромов чётной длины (т.е. нахождение массива <tex>d_2[]</tex>) получится небольшой модификацией этого.
 
  
Для быстрого вычисления будем поддерживать '''границы''' <tex>(l, r)</tex> самого правого из обнаруженных палиндромов (т.е. палиндрома с наибольшим значением <tex>r</tex>). Изначально можно считать <tex>l=0, r=-1</tex>.
+
'''''BAC''''': <tex>L[2][4] = max(L[2][3], L[3][4]) = 1</tex>
  
Итак, пусть вычисляем значение <tex>d_1[i]</tex> для очередного <tex>i</tex>, при этом все предыдущие значения <tex>d_1[]</tex> уже подсчитаны.
+
'''''ACC''''': <tex>L[3][5] = max(L[3][4], L[4][5]) = 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>.
+
'''''CCB''''': <tex>L[4][6] = max(L[4][5], L[5][6]) = 2</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>.
+
'''''CBA''''': <tex>L[5][7] = max(L[5][6], L[6][7]) = 1</tex>
  
[[Файл:Palindrome1.png|300px|thumb|right|палиндром вокруг  фактически "копируется" в палиндром вокруг <tex>i</tex>]]
+
Продолжая далее аналогичные рассуждения, заполним все ячейки под диагональю и в ячейке <tex>L[1][7]</tex> получим ответ <tex>(6)</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> палиндром имеет такую же длину.
+
Если же в задаче необходимо вывести не длину, а саму подпоследовательность-палиндром, то дополнительно к массиву длин мы должны построить массив переходов — для каждой ячейки запомнить, какой из случаев был реализован.
  
На самом деле, чтобы правильно обрабатывать такие ситуации, надо "обрезать" длину палиндрома, т.е. присвоить <tex>d_1[i]=r-i</tex>. После этого следует пустить тривиальный алгоритм, который будет пытаться увеличить значение <tex>d_1[i]</tex>, пока это возможно.
+
== Псевдокод ==
  
:
 
  
[[Файл:Palindrome2.png|300px|thumb|right|палиндром с центром в <tex>j</tex> изображен уже "обрезанным" до такой длины, что он впритык помещается во внешний палиндром]]
+
== Литература ==
 
+
[http://en.wikipedia.org/wiki/Palindrome Wikipedia — Palindrome]
В завершение описания алгоритма сталось только напомнить, что надо не забывать обновлять значения <tex>(l,r)</tex> после вычисления очередного значения <tex>d_1[i]</tex>.
 
 
 
Также повторимся, что выше описано рассуждение для вычисления массива нечётных палиндромов <tex>d_1[]</tex>; для массива чётных палиндромов <tex>d_2[]</tex> все рассуждения аналогичны.
 
==== Оценка асимптотики ====
 
На первый взгляд не очевидно, что данный алгоритм имеет линейную асимптотику: при вычислении ответа для определённой позиции в нем нередко запускается тривиальный алгоритм поиска палиндромов.
 
 
 
Однако более внимательный анализ показывает, что алгоритм всё же линеен. (Стоит сослаться на алгоритм построения [[Z-функция|Z-функции]] строки, который внутренне сильно напоминает данный алгоритм, и работает также за линейное время.)
 
 
 
В самом деле, легко проследить по алгоритму, что каждая итерация, производимая тривиальным поиском, приводит к увеличению на один границы <tex>r</tex>. При этом уменьшений <tex>r</tex> по ходу алгоритма происходить не может. Следовательно, тривиальный алгоритм в сумме совершит лишь <tex>O(n)</tex> действий.
 
 
 
Учитывая, что, кроме тривиальных поисков, все остальные части алгоритма Манакера очевидно работают за линейное время, мы и получаем итоговую асимптотику: <tex>O(n)</tex>.
 
 
 
==== Псевдокод ====
 
Для случая подпоследовательностей нечётной длины, т.е. для вычисления массива <tex>d_1[]</tex>, получаем такой код:
 
 
 
  List d1(n)
 
  l = 0, r = -1
 
  for i = 0 to n
 
    k = (i > r ? 0 : min(d1[l + r - i], r - i)) + 1
 
    while i + k < n && i - k >= 0 && s[i+k] == s[i-k]
 
      ++k
 
    d1[i] = k--
 
    if i + k > r
 
      l = i - k,  r = i + k
 
  
Для подпоследовательностей чётной длины, т.е. для вычисления массива <tex>d_2[]</tex>, лишь немного меняются арифметические выражения:
+
[http://ru.wikipedia.org/wiki/%D0%9F%D0%B0%D0%BB%D0%B8%D0%BD%D0%B4%D1%80%D0%BE%D0%BC Википедия — Палиндром]
 
 
  List d2(n)
 
  l = 0, r = -1
 
  for i = 0 to n
 
    k = (i > r ? 0 : min(d2[l + r - i + 1], r - i + 1)) + 1
 
    while (i + k - 1 < n && i - k >= 0 && s[i + k - 1] == s[i - k])
 
      ++k
 
    d2[i] = --k
 
    if i + k - 1 > r
 
      l = i - k,  r = i + k - 1
 
  
 
== См. также ==
 
== См. также ==
*[[Задача о наибольшей общей подпоследовательности]]
+
[http://neerc.ifmo.ru/wiki/index.php?title=%D0%97%D0%B0%D0%B4%D0%B0%D1%87%D0%B0_%D0%BE_%D0%BD%D0%B0%D0%B8%D0%B1%D0%BE%D0%BB%D1%8C%D1%88%D0%B5%D0%B9_%D0%BE%D0%B1%D1%89%D0%B5%D0%B9_%D0%BF%D0%BE%D0%B4%D0%BF%D0%BE%D1%81%D0%BB%D0%B5%D0%B4%D0%BE%D0%B2%D0%B0%D1%82%D0%B5%D0%BB%D1%8C%D0%BD%D0%BE%D1%81%D1%82%D0%B8 Задача о наибольшей общей подпоследовательности]
*[[Задача о наибольшей возрастающей подпоследовательности]]
 
  
== Литература ==
+
[http://neerc.ifmo.ru/wiki/index.php?title=%D0%97%D0%B0%D0%B4%D0%B0%D1%87%D0%B0_%D0%BE_%D0%BD%D0%B0%D0%B8%D0%B1%D0%BE%D0%BB%D1%8C%D1%88%D0%B5%D0%B9_%D0%B2%D0%BE%D0%B7%D1%80%D0%B0%D1%81%D1%82%D0%B0%D1%8E%D1%89%D0%B5%D0%B9_%D0%BF%D0%BE%D0%B4%D0%BF%D0%BE%D1%81%D0%BB%D0%B5%D0%B4%D0%BE%D0%B2%D0%B0%D1%82%D0%B5%D0%BB%D1%8C%D0%BD%D0%BE%D1%81%D1%82%D0%B8 Задача о наибольшей возрастающей подпоследовательности]
*[[wikipedia:Palindrome|Wikipedia — Palindrome]]
 
*[[wikipedia:ru:Палиндром|Википедия — Палиндром]]
 
*[http://e-maxx.ru/algo/palindromes_count E-Maxx - Нахождение всех подпалиндромов]
 
  
 
[[Категория:Дискретная математика и алгоритмы]]
 
[[Категория:Дискретная математика и алгоритмы]]
 
[[Категория:Динамическое программирование]]
 
[[Категория:Динамическое программирование]]

Версия 21:01, 13 декабря 2012

Эта статья находится в разработке!

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

Определения

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


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


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

Решение

Массив длин подпоследовательностей-палиндромов
Наглядный массив переходов

Обозначим данную последовательность через [math]S[/math], а ее элементы — через [math]S[i], 1 \le i \le n[/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].

Пример

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

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

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

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

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

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

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

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

Псевдокод

Литература

Wikipedia — Palindrome

Википедия — Палиндром

См. также

Задача о наибольшей общей подпоследовательности

Задача о наибольшей возрастающей подпоследовательности