动态规划 最长公共子序列过程分析
动态规划 最长公共子序列过程
明确什么是公共子序列
首先需要科普一下,最长公共子序列(longest common sequence)和最长公共子串(longest common substring)不是一回事儿。什么是子序列呢?即一个给定的序列的子序列,就是将给定序列中零个或多个元素去掉之后得到的结果。什么是子串呢?给定串中任意个连续的字符组成的子序列称为该串的子串。
- 子序列是没有序的
- 子串是有序的
一个简单的例子说明:
$s_1$ = “abcd”;这个字符串的子序列诸如 “a”,”ab”,”bc”,”ac”等 且拥有子串的数量是$2^n$个 其中 n
是字符串字符的个数。然而”ab”,”bc”,”abc”这些则称为子串,是按照一定顺序的
动态规划
求解LCS问题,不能使用暴力搜索方法。一个长度为n的序列拥有 2的n次方个子序列,它的时间复杂度是指数阶,太恐怖了。解决LCS问题,需要借助动态规划的思想。
动态规划算法通常用于求解具有某种最优性质的问题。在这类问题中,可能会有许多可行解。每一个解都对应于一个值,我们希望找到具有最优值的解。动态规划算法与分治法类似,其基本思想也是将待求解问题分解成若干个子问题,先求解子问题,然后从这些子问题的解得到原问题的解。与分治法不同的是,适合于用动态规划求解的问题,经分解得到子问题往往不是互相独立的。若用分治法来解这类问题,则分解得到的子问题数目太多,有些子问题被重复计算了很多次。如果我们能够保存已解决的子问题的答案,而在需要时再找出已求得的答案,这样就可以避免大量的重复计算,节省时间。我们可以用一个表来记录所有已解的子问题的答案。不管该子问题以后是否被用到,只要它被计算过,就将其结果填入表中。这就是动态规划法的基本思路。
特征分析
解决LCS问题,需要把原问题分解成若干个子问题,所以需要刻画LCS的特征。
设A=“$a_0$,$a_1$,…,$a_m$”,B=“$b_0$,$b_1$,…,$b_n$”,且Z=“$z_0$,$z_1$,…,$z_k$”为它们的最长公共子序列。不难证明有以下性质:
-
如果 $a_m$=$b_n$,则$z_k$=$a_m$=$b_n$,且“$z_0$,$z_1$,…,$z_(k-1)$”是”$a_0$,$a_1$,…,$a_(m-1)$”和“$b_0$,$b_1$,…,$b_(n-1)$”的一个最长公共子序列;
-
如果 $a_m$ != $b_n$,则若$z_k$!=$a_m$,蕴涵“$z_0$,$z_1$,…,$z_k$”是“$a_0$,$a_1$,…,$a_(m-1)$”和“$b_0$,$b_1$,…,$b_n$”的一个最长公共子序列;
-
如果$a_m$!=$b_n$,则若$z_k$!=$b_n$,蕴涵“$z_0$,$z_1$,…,$z_k$”是“$a_0$,$a_1$,…,$a_m$”和“$b_0$,$b_1$,…,$b_(n-1)$”的一个最长公共子序列。
用递推公式
例题分析
最长公共子串问题
给定两个字符串 $s_1$$s_2$…..$s_n$和$t_1$$t_2$……$t_n$。求出这两个字符串最长的公共子序列的长度。
限制条件: 1 <= n; m <= 1000
输入
n = 4
m = 4
s = “abcd”
t = “becd”
输出
3 (“becd”)
代码
#include <iostream>
#include <cstdio>
#include <algorithm>
#include <cstring>
#include <string>
using namespace std;
string a,b;
int dp[1001][1001];
int solve(int al,int bl)
{
for(int i=0;i<al;i++)
{
for(int j=0;j<bl;j++)
{
if(a[i]==b[j])
{
dp[i+1][j+1]=dp[i][j]+1;
}
else
{
dp[i+1][j+1]=max(dp[i][j+1],dp[i+1][j]);
}
}
}
return dp[al][bl];
}
int main()
{
while(cin>>a>>b)
{
int al=a.length();
int bl=b.length();
printf("%d\n",solve(al,bl));
}
return 0;
}