zoukankan      html  css  js  c++  java
  • 动态规划-最长公共子序列

    问题描述

        最长公共子序列,英文缩写为LCS(Longest Common Subsequence)。其定义是,一个序列S,如果分别是两个或多个已知序列的子序列,且是所有符合此条件序列中最长的,则 S 称为已知序列的最长公共子序列。而最长公共子串(要求连续)和最长公共子序列是不同的。

    题目:如果字符串一的所有字符按其在字符串中的顺序出现在另外一个字符串二中,则字符串一称之为字符串二的子序列。注意,并不要求子串(字符串一)的字符必须连续出现在字符串二中。请编写一个函数,输入两个字符串,求它们的最长公共子序列,并打印出最长公共子序列。

    例如:输入两个字符串BDCABAABCBDAB,字符串BCBA和BDAB都是是它们的最长公共子串,则输出它们的长度4,并打印任意一个子串。

     

    最长公共子序列应用

        最长公共子序列是一个十分实用的问题,它可以描述两段文字之间的“相似度”,即它们的雷同程度,从而能够用来辨别抄袭。对一段文字进行修改之后,计算改动前后文字的最长公共子序列,将除此子序列外的部分提取出来,这种方法判断修改的部分,往往十分准确。简而言之,百度知道、百度百科都用得上。如判断S1和S2相似的办法是找出他们的公共子序列S3,S3以相同的顺序在S1和S2中出现,但是不必要连续。S3越长,S1和S3就越相似。

    用动态规划解决

    1 描述一个最长公共子序列(描述最优解的结构)

    先介绍LCS问题的性质:记Xm={x0, x1,…xm}Yn={y0,y1,…,yn}为两个字符串,而Zk={z0,z1,…zk}是它们的LCS,则:

    1) 如果xm=yn,那么zk=xm=yn,并且Zk-1Xm-1Yn-1的一个LCS
    2)如果xm≠yn,那么当zk≠xmZXm-1Y的一个LCS
    3)如果xm≠yn,那么当zk≠ynZYn-1X的一个LCS

    证明略见《算法导论》

    2 递归定义最优解的值

        有了上面的性质,我们可以得出如下的思路:求两字符串Xm={x0, x1,…xm}Yn={y0,y1,…,yn}LCS,如果xm=yn,那么只需求得Xm-1Yn-1LCS,并在其后添加xmyn)即可;如果xm-1≠yn-1,我们分别求得Xm-1YLCSYn-1XLCS,并且这两个LCS中较长的一个为XYLCS

    记字符串XiYj的一个LCS的长度为c[i,j]。如果i=0或j=0,其中一个的长度为0,因此LCS的长度为0。由LCS问题的最优子结构可得递归式

              /      0                               if i<0 or j<0
    c[i,j]=          c[i-1,j-1]+1                    if i,j>=0 and xi=x
    j
                    max(c[i,j-1],c[i-1,j]           if i,j>=0 and xi≠xj


    3 计算LCS的长度(按自底向上的方式计算最优解的值)

        上面的公式用递归函数不难求得。但直接递归(自顶向下)会有很多重复计算,我们用从底向上循环求解的思路效率更高。

    为了能够采用循环求解的思路,我们用一个矩阵c[0..m,0..n]保存下来当前已经计算好了的c[i,j],当后面的计算需要这些数据时就可以直接从矩阵读取。另外,求取c[i,j]可以从c[i-1,j-1] c[i,j-1]或者c[i-1,j]三个方向计算得到,相当于在矩阵c[0...m,0...n]中是从c[i-1,j-1]c[i,j-1]或者c[i-1,j]的某一个各自移动到c[i,j],因此在矩阵中有三种不同的移动方向:向左、向上和向左上方,其中只有向左上方移动时才表明找到LCS中的一个字符(因为c[i-1,j-1]+1 ,if i,j>=0 and xi=xj)。于是我们需要用另外一个矩阵b[1..m,1..n]保存移动的方向。

    下面是计算字符串Xm={x0, x1,…xm}Yn={y0,y1,…,yn}的LCS的长度伪代码:

     

    LCS-LENGTH(X,Y)
      m <- length[X]
      n <- length[Y]
      
      //i=0 或 j=0
      for i <- 0 to m
          do c[i,0]= 0
      for j <- 0 to n
          do c[0,j]= 0
      
      for i=1 to m
          do for j=1 to n
    	     do if xi=yj       //c[i,j]=c[i-1,j-1]+1 ,if i,j>=0 and xi=xj
    		       then c[i,j]=c[i-1,j-1]+1
    			        b[i,j]="左上方箭头"
    			else if c[i,j-1]>=c[i-1,j]   //max(c[i,j-1],c[i-1,j] ,if i,j>=0 and xi≠xj
                        then c[i,j]=c[i,j-1]
    					     b[i,j]="左方箭头"
    				 else  c[i,j]=c[i-1,j]
    				       b[i,j]="上方箭头"
    return c and b

    算法时间复杂度O(mn)

    具体例子图见《算法导论》P211

    4 由计算出的结果构造一个最优解

    由返回的表b可以用来快速构造Xm={x0, x1,…xm}Yn={y0,y1,…,yn}的一个LCS。首先从b[m,n]处开始,沿着当前箭头指向在表格中找到下一个位置,由此跟踪下去。每当在b[i,j]中遇到一个“左上方箭头”时,即意味着xi=yj是LCS的一个元素。这种方法是按照反序来找LCS的每一个元素的,下面的递归输出X和Y的一个LCS.初始调用为Print-LCS(b,X,length[X],length[Y])
    Print-LCS(b,X,i,j)
      if i=0 或 j=0
         then return
      if b[i,j]="左上方箭头"
         then Print-LCS(b,X,i-1,j-1)
      else if b[i,j]="上方箭头"
             then Print-LCS(b,X,i-1,j)
    	   else  Print-LCS(b,X,i,j-1)
    该运算时间为O(m+n)









  • 相关阅读:
    MySQL实现了四种通信协议
    深入了解Windows句柄到底是什么
    Linux虚拟地址空间布局以及进程栈和线程栈总结
    malloc 函数详解
    数组指针和指针数组的区别
    Linux中sudo配置
    ctrl+c,ctrl+d,ctrl+z在linux程序中意义和区别
    linux select函数详解
    linux grep命令详解
    Linux find 用法示例
  • 原文地址:https://www.cnblogs.com/jiangu66/p/3167773.html
Copyright © 2011-2022 走看看