动态规划法(十)最长公共子序列(LCS)问题

问题介绍

??给定一个序列\(X=<x_1,x_2,....,x_m>\),另一个序列\(Z=<z_1,z_2,....,z_k>\)满足如下条件时称为X的子序列:存在一个严格递增的X的下标序列\(<i_1,i_2,...,i_k>\),对所有的\(j=1,2,...,k\)满足\(x_{i_j}=z_j.\)

??给定两个序列\(X\)和\(Y\),如果\(Z\)同时是\(X\)和\(Y\)的子序列,则称\(Z\)是\(X\)和\(Y\)的公共子序列最长公共子序列(LCS)问题指的是:求解两个序列\(X\)和\(Y\)的长度最长的公共子序列。例如,序列\(X=<A,B,C,B,D,A,B>\)和\(Y=<B,D,C,A,B,A>\)的最长公共子序列为\(<B,C,B,A>\),长度为4。

??本文将具体阐释如何用动态规划法(Dynamic Programming)来求解最长公共子序列(LCS)问题。

算法分析

1. LCS的子结构

??给定一个序列\(X=<x_1,x_2,....,x_m>\),对\(i=0,1,...,m\),定义\(X\)的第i前缀为\(X_i=<x_1,x_2,....,x_i>\),其中\(X_0\)为空序列。

??(LCS的子结构)令\(X=<x_1,x_2,....,x_m>\)和\(Y=<y_1,y_2,....,y_n>\)为两个序列,\(Z=<z_1,z_2,....,z_k>\)为\(X\)和\(Y\)的任意LCS,则:

  1. 如果\(x_m=y_n,\)则\(z_k=x_m=y_n\)且\(Z_{k-1}\)是\(X_{m-1}\)和\(Y_{n-1}\)的一个LCS。
  2. 如果\(x_m\neq y_n,\)则\(z_k \neq x_m\)意味着\(Z_{k-1}\)是\(X_{m-1}\)和\(Y\)的一个LCS。
  3. 如果\(x_m\neq y_n,\)则\(z_k\neq y_n\)且\(Z_{k-1}\)是\(X\)和\(Y_{n-1}\)的一个LCS。

2. 构造递归解

??在求\(X=<x_1,x_2,....,x_m>\)和\(Y=<y_1,y_2,....,y_n>\)的一个LCS时,需要求解一个或两个子问题:如果\(x_m=y_n\),应求解\(X_{m-1}\)和\(Y_{n-1}\)的一个LCS,再将\(x_m=y_n\)追加到这个LCS的末尾,就得到\(X\)和\(Y\)的一个LCS;如果\(x_m\neq y_n\),需求解\(X_{m-1}\)和\(Y\)的一个LCS与\(X\)和\(Y_{n-1}\)的一个LCS,两个LCS较长者即为\(X\)和\(Y\)的一个LCS。当然,可以看出,LCS问题容易出现重叠子问题,这时候,就需要用动态规划法来解决。

??定义\(c[i,j]\)表示\(X_i\)和\(Y_j\)的LCS的长度。如果\(i=0\)或\(j=0\),则\(c[i,j]=0.\)利用LCS的子结构,可以得到如下公式:

\[
c[i,j]=\left\{
\begin{array}{lr}
0,\qquad 若i=0或j=0\ c[i-1, j-1]+1,\qquad 若i,j>0且x_i=y_j\ \max(c[i, j-1], c[i-1, j]),\qquad 若i,j>0且x_i\neq y_j
\end{array}
\right.
\]

3. 计算LCS的长度

??计算LCS长度的伪代码为LCS-LENGTH. 过程LCS-LENGTH接受两个子序列\(X=<x_1,x_2,....,x_m>\)和\(Y=<y_1,y_2,....,y_n>\)为输入。它将\(c[i, j]\)的值保存在表\(c\)中,同时,维护一个表\(b\),帮助构造最优解。过程LCS-LENGTH的伪代码如下:

LCS-LENGTH(X, Y):
m = X.length
n = Y.length
let b[1...m, 1...n] and c[0...m, 0...n] be new table

for i = 1 to m
    c[i, 0] = 0
for j = 1 to n
    c[0, j] = 0

for i = 1 to m
    for j = 1 to n
        if x[i] == y[j]
           c[i,j] = c[i-1, j-1]+1
           b[i,j] = ‘diag‘

        elseif c[i-1, j] >= c[i, j-1]
            c[i,j] = c[i-1, j]
            b[i,j] = ‘up‘

        else
            c[i,j] = c[i, j-1]
            b[i,j] = ‘left‘

return c and b

4. 寻找LCS

??为了寻找\(X\)和\(Y\)的一个LCS, 我们需要用到LCS-LENGTH过程中的表\(b\),只需要简单地从\(b[m, n]\)开始,并按箭头方向追踪下去即可。当在表项\(b[i,j]\)中遇到一个‘diag‘时,意味着\(x_i=y_j\)是LCS的一个元素。按照这种方法,我们可以按逆序依次构造出LCS的所有元素。伪代码PRINT-LCS如下:

PRINT-LCS(b, X, i, j):
    if i == 0 or j == 0
        return
    if b[i,j] == ‘diag‘
        PRINT-LCS(b, X, i-1, j-1)
        print x[i]
    elseif b[i,j] == ‘up‘:
        PRINT-LCS(b, X, i-1, j)
    else
        PRINT-LCS(b, X, i, j-1)

程序实现

??有了以上对LCS问题的算法分析,我们不难写出具体的程序来实现它。下面将会给出Python代码和Java代码,供读者参考。

??完整的Python代码如下:

import numpy as np

# using dynamic programming to solve LCS problem
# parameters: X,Y -> list
def LCS_LENGTH(X, Y):
    m = len(X) # length of X
    n = len(Y) # length of Y

    # create two tables, b for directions, c for solution of sub-problem
    b = np.array([[None]*(n+1)]*(m+1))
    c = np.array([[0]*(n+1)]*(m+1))

    # use DP to sole LCS problem
    for i in range(1, m+1):
        for j in range(1, n+1):
            if X[i-1] == Y[j-1]:
                c[i,j] = c[i-1,j-1]+1
                b[i,j] = ‘diag‘
            elif c[i-1,j] >= c[i, j-1]:
                c[i,j] = c[i-1,j]
                b[i,j] = ‘up‘
            else:
                c[i,j] = c[i,j-1]
                b[i,j] = ‘left‘
    #print(b)
    #print(c)
    return b,c

# print longest common subsequence of X and Y
def print_LCS(b, X, i, j):

    if i == 0 or j == 0:
        return None
    if b[i,j] == ‘diag‘:
        print_LCS(b, X, i-1, j-1)
        print(X[i-1], end=‘ ‘)
    elif b[i,j] == ‘up‘:
        print_LCS(b, X, i-1, j)
    else:
        print_LCS(b, X, i, j-1)

X = ‘conservatives‘
Y = ‘breather‘

b,c = LCS_LENGTH(X,Y)
print_LCS(b, X, len(X), len(Y))

输出结果如下:

e a t e 

??完整的Java代码如下:

package DP_example;

import java.util.Arrays;
import java.util.List;

public class LCS {
    // 主函数
    public static void main(String[] args) {
        // 两个序列X和Y
        List<String> X = Arrays.asList("A","B","C","B","D","A","B");
        List<String> Y = Arrays.asList("B","D","C","A","B","A");

        int m = X.size(); //X的长度
        int n = Y.size(); // Y的长度
        String[][] b = LCS_length(X, Y); //获取维护表b的值

        print_LCS(b, X, m, n); // 输出LCS
    }

    /*
    函数LCS_length:获取维护表b的值
    传入参数: 两个序列X和Y
    返回值: 维护表b
     */
    public static String[][] LCS_length(List X, List Y){
        int m = X.size(); //X的长度
        int n = Y.size(); // Y的长度
        int[][] c = new int[m+1][n+1];
        String[][] b = new String[m+1][n+1];

        // 对表b和表c进行初始化
        for(int i=1; i<m+1; i++){
            for(int j=1; j<n+1; j++){
                c[i][j] = 0;
                b[i][j] = "";
            }
        }

        // 利用自底向上的动态规划法获取b和c的值
        for(int i=1; i<m+1; i++){
            for(int j=1; j<n+1; j++){
                if(X.get(i-1) == Y.get(j-1)){
                    c[i][j] = c[i-1][j-1]+1;
                    b[i][j] = "diag";
                }
                else if(c[i-1][j] >= c[i][j-1]){
                    c[i][j] = c[i-1][j];
                    b[i][j] = "up";
                }
                else{
                    c[i][j] = c[i][j-1];
                    b[i][j] = "left";
                }
            }
        }

        return b;
    }

    // 输出最长公共子序列
    public static int print_LCS(String[][] b, List X, int i, int j){

        if(i == 0 || j == 0)
            return 0;

        if(b[i][j].equals("diag")){
            print_LCS(b, X, i-1, j-1);
            System.out.print(X.get(i-1)+" ");
        }
        else if(b[i][j].equals("up"))
            print_LCS(b, X, i-1, j);
        else
            print_LCS(b, X, i, j-1);

        return 1;
    }
}

输出结果如下:

B C B A 

参考文献

  1. 算法导论(第三版) 机械工业出版社
  2. https://www.geeksforgeeks.org/longest-common-subsequence/

注意:本人现已开通两个微信公众号: 因为Python(微信号为:python_math)以及轻松学会Python爬虫(微信号为:easy_web_scrape), 欢迎大家关注哦~~

原文地址:https://www.cnblogs.com/jclian91/p/9173076.html

时间: 2024-08-03 04:03:04

动态规划法(十)最长公共子序列(LCS)问题的相关文章

动态规划算法解最长公共子序列LCS问题

第一部分.什么是动态规划算法 ok,咱们先来了解下什么是动态规划算法. 动态规划一般也只能应用于有最优子结构的问题.最优子结构的意思是局部最优解能决定全局最优解(对有些问题这个要求并不能完全满足,故有时需要引入一定的近似).简单地说,问题能够分解成子问题来解决. 动态规划算法分以下4个步骤: 描述最优解的结构 递归定义最优解的值 按自底向上的方式计算最优解的值   //此3步构成动态规划解的基础. 由计算出的结果构造一个最优解.   //此步如果只要求计算最优解的值时,可省略. 好,接下来,咱们

[2016-05-09][51nod][1006 最长公共子序列Lcs]

时间:2016-05-09 21:12:54 星期一 题目编号:[2016-05-09][51nod][1006 最长公共子序列Lcs] 题目大意:[2016-05-09][51nod][1006 最长公共子序列Lcs].md 分析:动态规划 dp[i][j] 表示字符串A以第i个位置 ,字符串B以第j个位置的最长公共子序列的长度 dp[i][j] = dp[i - 1][j - 1] + 1 if a[i] == a[j] else dp[i][j] == max(dp[i - 1][j] ,

1006 最长公共子序列Lcs

1006 最长公共子序列Lcs 基准时间限制:1 秒 空间限制:131072 KB 给出两个字符串A B,求A与B的最长公共子序列(子序列不要求是连续的). 比如两个串为: abcicba abdkscab ab是两个串的子序列,abc也是,abca也是,其中abca是这两个字符串最长的子序列. Input 第1行:字符串A 第2行:字符串B (A,B的长度 <= 1000) Output 输出最长的子序列,如果有多个,随意输出1个. Input示例 abcicba abdkscab Outpu

POJ 1458 Common Subsequence(最长公共子序列LCS)

POJ1458 Common Subsequence(最长公共子序列LCS) http://poj.org/problem?id=1458 题意: 给你两个字符串, 要你求出两个字符串的最长公共子序列长度. 分析: 本题不用输出子序列,非常easy,直接处理就可以. 首先令dp[i][j]==x表示A串的前i个字符和B串的前j个字符的最长公共子序列长度为x. 初始化: dp全为0. 状态转移: IfA[i]==B[j] then dp[i][j]= dp[i-1][j-1]+1 else dp[

序列最的问题之最长公共子序列LCS

在程序设计竞赛中,我们时常会遇到序列求最值的问题.在讲今天的问题之前,先小小的说明一下,子序列与子串的问题. 子序列:在原序列中不一定连续: 子串:在原序列中必须连续. 接下来,就开始今天要讲的最长公共子序列LCS(Longest Common Subsequence).对于LCS这一类的问题,一般是相对于两个序列而言,str[]与ch[].先假设str的长度为n,ch的长度为m.假设str[]="ASBDAH",ch[]="SDAAH";其中"SDA&q

51nod 1006 最长公共子序列Lcs(dp+string,无标记数组实现)

1006 最长公共子序列Lcs 基准时间限制:1 秒 空间限制:131072 KB 分值: 0 难度:基础题 收藏 关注 取消关注 给出两个字符串A B,求A与B的最长公共子序列(子序列不要求是连续的). 比如两个串为: abcicba abdkscab ab是两个串的子序列,abc也是,abca也是,其中abca是这两个字符串最长的子序列. Input 第1行:字符串A 第2行:字符串B (A,B的长度 <= 1000) Output 输出最长的子序列,如果有多个,随意输出1个. Input示

求三个字符串的最长公共子序列LCS(A,B,C)

LCS(A,B,C)!=LCS(A,LCS(B,C)) 反例: abcd abcde abced LCS(B,C)求出来可能是abce或者abcd dp[i][j][k]表示A[0...i],B[0...j],C[0...k]的LCS 转移方程: if (a[i]==b[j]&&b[j]==c[k]) dp[i][j][k]=dp[i-1][j-1][k-1]+1; else dp[i][j][k]=max(max(dp[i][j][k], dp[i-1][j][k]), max(dp[i

编程算法 - 最长公共子序列(LCS) 代码(C)

最长公共子序列(LCS) 代码(C) 本文地址: http://blog.csdn.net/caroline_wendy 题目: 给定两个字符串s,t, 求出这两个字符串最长的公共子序列的长度. 字符串的子序列并一定要连续, 能够包含间隔. 即最长公共子序列问题(LCS, Longest Common Subsequence) 使用动态规划, 假设字符相等, 两个字符串就依次递增一位, 一直到字符串的结尾. 代码: /* * main.cpp * * Created on: 2014.7.17

动态规划-最长公共子序列LCS

0 问题 给定两个字符串,求最长公共子序列LCS. 也就是说两个字符串中都有的部分,或者理解为,两个字符串同时都删除字符串中的某些字符,使得最终的两个字符串,相等,且是最长的. 1 分析 假设两个str1,str2字符串,已经知道了最长公共子序列长度为L 那么,当在str1和str2,两个的尾部,同时添加一个相同的字符,比如a,那么新的str1,和str2的最长公共子序列长度就是L+1 当str1后面添加一个字符,str2不添加,那么最长公共子序列长度为L 反之,str1不添加,str2添加,那

1006 最长公共子序列Lcs(经典动态规划)

传送门 Description 给出两个字符串A B,求A与B的最长公共子序列(子序列不要求是连续的). 比如两个串为: abcicba abdkscab ab是两个串的子序列,abc也是,abca也是,其中abca是这两个字符串最长的子序列. Input 第1行:字符串A 第2行:字符串B (A,B的长度 <= 1000) Output 输出最长的子序列,如果有多个,随意输出1个. Sample Input abcicba abdkscab Sample Output abca 思路 记:Xi