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

简介: 问题介绍  给定一个序列X=X=X=,另一个序列Z=Z=Z=满足如下条件时称为X的子序列:存在一个严格递增的X的下标序列,对所有的j=1,2,...,kj=1,2,...,kj=1,2,...,k满足xij=zj.xij=zj.x_{i_j}=z_j.  给定两个序列XXX和YYY,如果ZZZ同时是XXX和YYY的子序列,则称ZZZ是XXX和YYY的公共子序列。

问题介绍

  给定一个序列X=<x1,x2,....,xm>X=<x1,x2,....,xm>,另一个序列Z=<z1,z2,....,zk>Z=<z1,z2,....,zk>满足如下条件时称为X的子序列:存在一个严格递增的X的下标序列<i1,i2,...,ik><i1,i2,...,ik>,对所有的j=1,2,...,kj=1,2,...,k满足xij=zj.xij=zj.
  给定两个序列XXYY,如果ZZ同时是XXYY的子序列,则称ZZXXYY公共子序列最长公共子序列(LCS)问题指的是:求解两个序列XXYY的长度最长的公共子序列。例如,序列X=<A,B,C,B,D,A,B>X=<A,B,C,B,D,A,B>Y=<B,D,C,A,B,A>Y=<B,D,C,A,B,A>的最长公共子序列为<B,C,B,A><B,C,B,A>,长度为4。
  本文将具体阐释如何用动态规划法(Dynamic Programming)来求解最长公共子序列(LCS)问题。

算法分析

1. LCS的子结构

  给定一个序列X=<x1,x2,....,xm>X=<x1,x2,....,xm>,对i=0,1,...,mi=0,1,...,m,定义XX的第i前缀为Xi=<x1,x2,....,xi>Xi=<x1,x2,....,xi>,其中X0X0为空序列。
  (LCS的子结构)令X=<x1,x2,....,xm>X=<x1,x2,....,xm>Y=<y1,y2,....,yn>Y=<y1,y2,....,yn>为两个序列,Z=<z1,z2,....,zk>Z=<z1,z2,....,zk>XXYY的任意LCS,则:

  1. 如果xm=yn,xm=yn,zk=xm=ynzk=xm=ynZk1Zk−1Xm1Xm−1Yn1Yn−1的一个LCS。
  2. 如果xmyn,xm≠yn,zkxmzk≠xm意味着Zk1Zk−1Xm1Xm−1YY的一个LCS。
  3. 如果xmyn,xm≠yn,zkynzk≠ynZk1Zk−1XXYn1Yn−1的一个LCS。

2. 构造递归解

  在求X=<x1,x2,....,xm>X=<x1,x2,....,xm>Y=<y1,y2,....,yn>Y=<y1,y2,....,yn>的一个LCS时,需要求解一个或两个子问题:如果xm=ynxm=yn,应求解Xm1Xm−1Yn1Yn−1的一个LCS,再将xm=ynxm=yn追加到这个LCS的末尾,就得到XXYY的一个LCS;如果xmynxm≠yn,需求解Xm1Xm−1YY的一个LCS与XXYn1Yn−1的一个LCS,两个LCS较长者即为XXYY的一个LCS。当然,可以看出,LCS问题容易出现重叠子问题,这时候,就需要用动态规划法来解决。
  定义c[i,j]c[i,j]表示XiXiYjYj的LCS的长度。如果i=0i=0j=0j=0,则c[i,j]=0.c[i,j]=0.利用LCS的子结构,可以得到如下公式:

c[i,j]=0,i=0j=0c[i1,j1]+1,i,j>0xi=yjmax(c[i,j1],c[i1,j]),i,j>0xiyjc[i,j]={0,若i=0或j=0c[i−1,j−1]+1,若i,j>0且xi=yjmax(c[i,j−1],c[i−1,j]),若i,j>0且xi≠yj

3. 计算LCS的长度

  计算LCS长度的伪代码为LCS-LENGTH. 过程LCS-LENGTH接受两个子序列X=<x1,x2,....,xm>X=<x1,x2,....,xm>Y=<y1,y2,....,yn>Y=<y1,y2,....,yn>为输入。它将c[i,j]c[i,j]的值保存在表cc中,同时,维护一个表bb,帮助构造最优解。过程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

  为了寻找XXYY的一个LCS, 我们需要用到LCS-LENGTH过程中的表bb,只需要简单地从b[m,n]b[m,n]开始,并按箭头方向追踪下去即可。当在表项b[i,j]b[i,j]中遇到一个’diag’时,意味着xi=yjxi=yj是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), 欢迎大家关注哦~~

目录
相关文章
|
5月前
|
JSON 数据挖掘 API
1688API最新指南:商品详情接口接入与应用
本指南介绍1688商品详情接口的接入与应用,该接口可获取商品标题、价格、规格、库存等详细信息,适用于电商平台开发、数据分析等场景。接口通过商品唯一标识查询,支持HTTP GET/POST请求,返回JSON格式数据,助力开发者高效利用1688海量商品资源。
|
机器学习/深度学习 人工智能 弹性计算
普通玩家入局AIGC的正确姿势是什么?
随着人工智能技术的不断发展,AIGC作为其中一种重要的应用,正在越来越受到大众的关注和重视。AI从理解语言,理解文字,理解图片和视频,走向了生成内容,这被称之AIGC,是一种“人机共创”的新模式。有人觉得AIGC距离我们的生活还很遥远,需要有超高的数学基础,超强的代码能力才能接触到这个领域。其实不是这样的,人人都可以玩转AIGC。智能对话机器人,AIGC绘画、用文本生成视频等八大热门AIGC的应用场景,可以通过阿里云推出的书籍《玩转AIGC》提供最详细的操作指南和实践方案。
478 2
|
关系型数据库 MySQL 数据库
MySQL忘记密码的处理方法(MySQL重置密码)
本文主要讲解MySQL如何重置密码(MySQL密码重置方法)
91527 2
MySQL忘记密码的处理方法(MySQL重置密码)
|
算法 C语言 索引
09【C语言 & 趣味算法】再识:折半查找(二分查找):基本思想、程序流程图及完整代码、附:顺序查找
09【C语言 & 趣味算法】再识:折半查找(二分查找):基本思想、程序流程图及完整代码、附:顺序查找
09【C语言 & 趣味算法】再识:折半查找(二分查找):基本思想、程序流程图及完整代码、附:顺序查找
|
消息中间件 存储 网络协议
RabbitMQ 26问,基本涵盖了面试官必问的面试题
RabbitMQ 26问,基本涵盖了面试官必问的面试题
3557 1
|
存储 消息中间件 缓存
史上最全最详细的Java架构师成长路径图,程序员必备
从新手码农到高级架构师,要经过几步?要多努力,才能成为为人倚重的技术专家?本文将为你带来一张程序员发展路径图,但你需要知道的是,天下没有普适的道理,具体问题还需具体分析,实践才能出真知。
6917 0
|
SQL NoSQL 关系型数据库
【并发】高并发下库存超卖问题如何解决?
【并发】高并发下库存超卖问题如何解决?
5751 0
|
存储 运维 Kubernetes
Kubernetes集群方方面面实战教程学习线路指南
Kubernetes集群方方面面实战教程学习线路指南
606 1
|
计算机视觉
CVPR2021 | 华为诺亚实验室提出Transformer in Transformer
transformer用于图像方面的应用逐渐多了起来,其主要做法是将图像进行分块,形成块序列,简单地将块直接丢进transformer中。然而这样的做法忽略了块之间的内在结构信息,为此,这篇论文提出了一种同时利用了块内部序列和块之间序列信息的transformer模型,称之为Transformer-iN-Transformer,简称TNT。
CVPR2021 | 华为诺亚实验室提出Transformer in Transformer
|
JSON 前端开发 数据格式
django中ajax post数据时request.POST获取数组问题
1、前言 最近在使用django开发web页面时,使用ajax的post参数中带有数组,然后在 request.POST 里获取的数组时,数组变成了一个元组!!!官方给出的通过 request.
3720 0

热门文章

最新文章