zoukankan      html  css  js  c++  java
  • LCS(最长公共子序列)

      这个问题很有意思,在生物应用中,经常需要比较两个(或多个)不同生物体的DNA片段。例如,某种生物的DNA可能为S1 = ACCGGTCGAGTGCGCGGAAGCCGGCCGAA,S2 = GTCGTTCGGAATGCCGTTGCTCTGTAAA。比较两个DNA串是想要确定它们之间的“相识度”,作为度量两种生物相近程度的指标。LCS就是寻找第三个串S3,S3满足定义:给定一个序列X = <x1,x2,...,xn>,另一个序列Y = <y1,y2,...,ym>即存在一个严格递增的X的下标序列<i1,i2,...,im>,对所有j = 1,2,...,m,满足xij = zj。例如,Z = BCDB是ABCBDAB的子序列,对应的下标为[2,3,5,7]。

      如果用暴力求解则很容易就达到指数阶的运行时间,所以显然需要优化。通过分析发现LCS问题具有最优子结构性质,于是,考虑用动态规划是一个非常不错的方案。

      显然早就有许多人对此类问题做过很多工作,不管是理论上还是实际测试,都证明动态规划可以解决这一类型的问题,有时候甚至是最优方案。如何熟练使用动态规划的一大难点在于分析问题,要理解如何使用动态规划来解决问题,首先分析问题是否具有最优子结构是重要的,然后就是通过分析问题得到递归公式,只要得到了递归公式,我们就可以使用动态规划了。

      LCS问题也不例外,经过分析和查资料,我们知道了LCS得最优子结构定理:

      令X = <x1,x2,...,xn>和Y = <y1,y2,...ym>为两个序列,Z = <z1,z2,...,zk>为X和Y的任意LCS。

    • 如果xn = ym,则zk = xn = ym且Zk - 1是Xn - 1和Ym - 1的一个LCS;
    • 如果xn ≠ ym且zk ≠ xn,说明Z是Xn - 1和Y的一个LCS;
    • 如果xn ≠ ym且zk ≠ ym,说明Z是X和Ym的一个LCS;

      然后,我们开始建立最优解的递归式。定义dp[i][j]为Xi和Yj的LCS长度。显然的,当i = 0或j = 0时,LCS = 0。再结合最优子结构定理,可得到递归式:

                    (    0;                              当 i = 0 或 j = 0;
    dp[i][j] =      {    dp[i - 1][j - 1] + 1            当 i,j > 0 且 xi = yj;
                    (    max(dp[i][j - 1], dp[i - 1][j]) 当 i,j > 0 且 xi != yj.

      得到递归式后,就可以根据公式写出递归算法或动态规划算法:

    #include <iostream>
    #include <string>
    #include <vector>
    #include <minmax.h>
    
    class Solution {
    public:
    	int LongestCommonSubsequence(std::string s1, std::string s2) {
    		if (s1.empty() || s2.empty())
    			return 0;
    		std::vector<std::vector<int> > dp(s1.size(), std::vector<int>(s2.size(), 0));
    		int length = 0;
    		for (int i = 1; i < dp.size(); i++) {
    			for (int j = 1; j < dp[0].size(); j++) {
    				if (s1[i] == s2[j])
    					dp[i][j] = dp[i - 1][j - 1] + 1;
    				else
    					dp[i][j] = max(dp[i][j - 1], dp[i - 1][j]);
    				length = max(length, dp[i][j]);
    			}
    		}
    		return length + 1;
    	}
    };
    
    int main()
    {
    	Solution solve;
    	std::string s1{"ABCBDAB"};
    	std::string s2{"ABDCABA"};
    
    	std::cout << solve.LongestCommonSubsequence(s1, s2) << std::endl;
    
    	return 0;
    }

      最后,类似的问题还有LIS(最长递增子序列)、LPS(最长回文子串)等等。

      参考资料:

        《算法导论》

  • 相关阅读:
    Git学习1:Git起步
    [转载]AMOLED结构详解,BOE专家给你分析驱动补偿
    [C#] Microsoft .Net框架SerialPort类的用法与示例
    API 的历史
    AMOLED原理介紹
    [C#] SHA1校验函数用法
    示波器基本原理之三:存储深度
    示波器基本原理之二:采样率
    示波器基本原理之一:带宽
    数字转中文
  • 原文地址:https://www.cnblogs.com/darkchii/p/8447257.html
Copyright © 2011-2022 走看看