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

Материал из Викиконспекты
Перейти к: навигация, поиск
Строка 24: Строка 24:
 
a[i][j] =
 
a[i][j] =
 
\begin{cases}
 
\begin{cases}
 +
0, & i = 0\text{ or }j = 0 \\
 
  a[i - 1][j - 1] + 1, & x[i] = y[j] \\
 
  a[i - 1][j - 1] + 1, & x[i] = y[j] \\
 
  max(a[i][j - 1], a[i - 1][j]), & x[i] \neq y[j]
 
  max(a[i][j - 1], a[i - 1][j]), & x[i] \neq y[j]
Строка 29: Строка 30:
 
</tex>
 
</tex>
  
Очевидно, что сложность алгоритма составит <tex> O(n^2) </tex>.
+
Очевидно, что сложность алгоритма составит <tex> O(mn) </tex>, где <tex> m </tex> и <tex> n </tex> — длины последовательностей.
  
 
=== Доказательство оптимальности ===
 
=== Доказательство оптимальности ===
Предположим, что некоторое значение <tex> a[i][j] </tex> посчитано неверно. Однако, в случае различия соответствующих символов, они не могут одновременно участвовать в НОП, а значит ответ действительно равен формуле для случая с различными символами. В случае же равенства, ответ не может быть больше, чем <tex> a[i - 1][j - 1] + 1 </tex>, так как тогда неверно посчитано значение <tex> a[i - 1][j - 1] + 1 </tex>.
+
База: при <tex> i = 0 </tex> или <tex> j = 0 </tex> длина одной из последовательностей равна нулю, поэтому и их НОП тоже нулевой длины.
 +
 
 +
Переходы: предположим, что некоторое значение <tex> a[i][j] </tex> посчитано неверно. Однако, в случае различия соответствующих символов, они не могут одновременно участвовать в НОП, а значит ответ действительно равен формуле для случая с различными символами. В случае же равенства, ответ не может быть больше, чем <tex> a[i - 1][j - 1] + 1 </tex>, так как тогда неверно посчитано значение <tex> a[i - 1][j - 1] + 1 </tex>.
  
 
=== Построение подпоследовательности ===
 
=== Построение подпоследовательности ===
 
Для каждой пары элементов будем хранить не только длину НОП соответствующих префиксов, но и номера последних элементов, участвующих в этой НОП.Таким образом, посчитав ответ, мы сможем восстановить всю наибольшую общую подпоследовательность.
 
Для каждой пары элементов будем хранить не только длину НОП соответствующих префиксов, но и номера последних элементов, участвующих в этой НОП.Таким образом, посчитав ответ, мы сможем восстановить всю наибольшую общую подпоследовательность.
  
=== Пример реализации на Java ===
+
=== Псевдокод ===
  public int lcs(int[] s1, int[] s2) {
+
X, Y — данные последовательности; a[i][j] — НОП для префикса длины i последовательности X и префикса длины j последовательности Y; b[i][j] — пара индексов элемента таблицы, соответствующего оптимальному решению вспомогательной задачи, выбранной при вычислении a[i][j].
      int[][] a = new int[s1.length][s2.length];
+
 
      //Подсчет значений
+
  // подсчёт таблиц
      for (int i = 0; i < s1.length; i++)
+
  LCS(X, Y)
          for (int j = 0; j < s2.length; j++) {
+
    m = length(X)
              if (s1[i] == s2[j])
+
    n = length(Y)
                  if ((i == 0) || (j == 0)) {
+
    for i = 1 to m
                      a[i][j] = 1;
+
      a[i][0] = 0
                  } else {
+
    for j = 0 to n
                      a[i][j] = a[i - 1][j - 1] + 1;
+
      a[0][j] = 0
                  }
+
    for i = 1 to m
              else {
+
      for j = 1 to n
                  if ((i > 0) && (a[i - 1][j] > a[i][j])) {
+
        if x[i] = y[i]
                      a[i][j] = a[i - 1][j];
+
          a[i][j] = a[i - 1][j - 1] + 1
                  }
+
          b[i][j] = pair(i - 1, j - 1)
                  if ((j > 0) && (a[i][j - 1] > a[i][j])) {
+
        else
                      a[i][j] = a[i][j - 1];
+
          if a[i - 1][j] >= a[i][j - 1]
                  }
+
            a[i][j] = a[i - 1][j]
              }
+
            b[i][j] = pair(i - 1, j)
          }
+
          else
       return a[s1.length - 1][s2.length - 1];
+
            a[i][j] = a[i][j - 1]
    }
+
            b[i][j] = pair(i, j - 1)
 +
 
 +
  // вывод НОП
 +
  PrintLCS(b, X, i, j)
 +
    if i = 0 or j = 0 // пришли к началу НОП
 +
      return
 +
    if b[i][j] = pair(i - 1, j - 1) // если пришли в a[i][j] из a[i - 1][j - 1], то X[i] = Y[j], надо вывести этот элемент
 +
      PrintLCS(b, X, i - 1, j - 1)
 +
      print X[i]
 +
    else
 +
       if b[i][j] = pair(i - 1, j)
 +
        PrintLCS(b, X, i - 1, j)
 +
      else
 +
        PrintLCS(b, X, i, j - 1)

Версия 01:54, 23 ноября 2011

Задача нахождения наибольшей общей подпоследовательности (longest common subsequence, LCS) — это задача поиска последовательности, которая является самой длинной подпоследовательностью нескольких последовательностей (обычно двух).

Определения

Определение:
Последовательность [math] Z = \left \langle z_1, z_2, ..., z_k \right \rangle [/math] является подпоследовательностью (subsequence) последовательности [math] X = \left \langle x_1, x_2, ..., x_m \right \rangle [/math], если существует строго возрастающая последовательность [math] \left \langle i_1, i_2, ..., i_k \right \rangle [/math] индексов [math] X [/math] таких, что для всех [math] j = 1, 2, ..., k [/math] выполняется соотношение [math] x_{i_j} = z_j [/math].

Другими словами, подпоследовательность данной последовательности — это последовательность, из которой удалили ноль или больше элементов. Например, [math] Z = \left \langle B, C, D, B \right \rangle [/math] является подпоследовательностью последовательности [math] X = \left \langle A, B, C, B, D, A, B \right \rangle [/math], а соответствующая последовательность индексов имеет вид [math] \left \langle 2, 3, 5, 7 \right \rangle [/math].

Определение:
Последовательность [math] Z [/math] является общей подпоследовательностью (common subsequence) последовательностей [math] X [/math] и [math] Y [/math], если [math] Z [/math] является подпоследовательностью как [math] X [/math], так и [math] Y [/math].

Постановка задачи

Даны две последовательности: [math] X = \left \langle x_1, x_2, ..., x_m \right \rangle [/math] и [math] Y = \left \langle y_1, y_2, ..., y_n \right \rangle [/math]. Требуется найти общую подпоследовательность [math] X [/math] и [math] Y [/math] максимальной длины. Заметим, что таких подпоследовательностей может быть несколько.

Наивная идея решения

Переберем все различные подпоследовательности обеих строк и сравним их. Мы гарантированно найдем искомую НОП, однако время работы алгоритма будет экспоненциально зависеть от длины исходных последовательностей.

Динамическое программирование

Решение

Обозначим как [math] a[i][j] [/math] НОП префиксов данных последовательностей, заканчивающихся в элементах с номерами [math] i [/math] и [math] j [/math] соответственно. Получаем следующее рекуррентное соотношение:

[math] a[i][j] = \begin{cases} 0, & i = 0\text{ or }j = 0 \\ a[i - 1][j - 1] + 1, & x[i] = y[j] \\ max(a[i][j - 1], a[i - 1][j]), & x[i] \neq y[j] \end{cases} [/math]

Очевидно, что сложность алгоритма составит [math] O(mn) [/math], где [math] m [/math] и [math] n [/math] — длины последовательностей.

Доказательство оптимальности

База: при [math] i = 0 [/math] или [math] j = 0 [/math] длина одной из последовательностей равна нулю, поэтому и их НОП тоже нулевой длины.

Переходы: предположим, что некоторое значение [math] a[i][j] [/math] посчитано неверно. Однако, в случае различия соответствующих символов, они не могут одновременно участвовать в НОП, а значит ответ действительно равен формуле для случая с различными символами. В случае же равенства, ответ не может быть больше, чем [math] a[i - 1][j - 1] + 1 [/math], так как тогда неверно посчитано значение [math] a[i - 1][j - 1] + 1 [/math].

Построение подпоследовательности

Для каждой пары элементов будем хранить не только длину НОП соответствующих префиксов, но и номера последних элементов, участвующих в этой НОП.Таким образом, посчитав ответ, мы сможем восстановить всю наибольшую общую подпоследовательность.

Псевдокод

X, Y — данные последовательности; a[i][j] — НОП для префикса длины i последовательности X и префикса длины j последовательности Y; b[i][j] — пара индексов элемента таблицы, соответствующего оптимальному решению вспомогательной задачи, выбранной при вычислении a[i][j].

 // подсчёт таблиц
 LCS(X, Y)
   m = length(X)
   n = length(Y)
   for i = 1 to m
     a[i][0] = 0
   for j = 0 to n
     a[0][j] = 0
   for i = 1 to m
     for j = 1 to n
       if x[i] = y[i]
         a[i][j] = a[i - 1][j - 1] + 1
         b[i][j] = pair(i - 1, j - 1)
       else
         if a[i - 1][j] >= a[i][j - 1]
           a[i][j] = a[i - 1][j]
           b[i][j] = pair(i - 1, j)
         else
           a[i][j] = a[i][j - 1]
           b[i][j] = pair(i, j - 1)
 
 // вывод НОП
 PrintLCS(b, X, i, j)
   if i = 0 or j = 0 // пришли к началу НОП
     return
   if b[i][j] = pair(i - 1, j - 1) // если пришли в a[i][j] из a[i - 1][j - 1], то X[i] = Y[j], надо вывести этот элемент
     PrintLCS(b, X, i - 1, j - 1)
     print X[i]
   else
     if b[i][j] = pair(i - 1, j)
       PrintLCS(b, X, i - 1, j)
     else
       PrintLCS(b, X, i, j - 1)