LCS(longest-common-subsequence problem),又名最长公共子序列问题
给定两个序列X和Y,若是Z既是X的子序列,也是Y的子序列,咱们称它为X和Y的公共子序列
好比X={A,B,C,D,E,F,G} Y={T,A,C,M,G},那么Z={A,C,G},就是其最长公共子序列
复制代码
咱们通常的字符串匹配,就是子串查找;可是实际应用中,每每有子串查找解决不了的问题;
好比『我家有台电视机,你要不』和『我有个电视机,你要不』这二者其实指代同一个事情;
那么在实际应用场景中,特别是工业领域,每每没法存在严格的子串和母串;
更多的是类似性问题,即类似的字符串指代同一个物品/事件。
复制代码
最长公共子序列问题:给定两个序列
X={X1,X2,...,Xm} Y={Y1,Y2,...,Yn},求X和Y长度最长的公共子序列。
复制代码
好了,咱们想想,假设这是个算法题,咱们会怎么作?算法
第一反应多是穷举扫描,可是呢,咱们也知道,若是字符串太长,暴力方法运行的时间是咱们不能忍受的。数组
而后咱们又会想到,将大问题拆分红小问题,用递归的思想解决。post
咱们假设:
1.Xm=Yn,那么X,Y的最长公共子序列,确定会包含最后这个字符,那么就变成求解Xm-1,Ym-1的LCS
2.Xm!=Yn,这个时候,若是最长公共子序列不含Xm,就意味着,该题变成求解Xm-1和Yn的公共子序列
3.Xm!=Yn,这个时候,若是最长公共子序列不含Yn,就意味着,该题变成求解Xm和Yn-1的公共子序列
复制代码
至此为止,咱们找到了求解最长公共子序列的方法。优化
咱们仔细观察一下上述的分析过程,是否是似曾相识? 这正是问题!spa
让咱们来回顾一下什么是动态规划:code
动态规划一般用来求解最优化问题
动态规划是经过组合子问题的解来解决原问题
....
复制代码
以及观察某个问题,是否适用于动态规划算法:cdn
1.是否具备最优子结构性质
若是一个问题的最优解,包含其子问题的最优解
2.具备重叠子问题性质
问题的递归算法会反复求解相同的子问题
复制代码
详见《什么是动态规划》一章blog
好了,接着说回LCS,经过上述的分析,咱们获得以下的公式: 递归
为何公式是如上形式?由于咱们假设X,Y串以下排列,像是一个二维数组 事件
其中X={A,B,C,A,D,A,B} Y={B,A,C,D,B,A}
C[i][j]表明公共子序列的长度
若是Xi=Yj,那么意味着子序列又多匹配上一位,其在Xi-1,Yj-1的最长公共子序列的结果上多增长了一个。
若是Xi!=Yj,那么意味着,Xi,Yj的最长公共子序列的答案确定在Xi-1,Yj或者Xi,Yj-1的最长公共子序列中,那么既然是最长,确定是取二者中更大的值;
至此,咱们已经清晰的定义出LCS的求解公式。
按照这个公式,咱们能够自顶向下的递归或者自底向上的累加;通常动态规划,采用自底向下更简单些;
思路以下:
int nLenX = X.length();
int nLenY = Y.length();
char** C = new char[nLenX + 1][nLenY + 1];
//初始化c二维数组
....
//LCS
for ( int i = 1; i <= nLenX; i++ )
{
for ( int j = 1; j <= nLenY; j++ )
{
if ( X[i-1] == Y[j-1])
{
C[i][j] = C[i-1][j-1] + 1;
}
else if ( C[i-1][j] >= C[i][j-1] )
{
C[i][j] = c[i-1][j];
}
else
{
C[i][j] = C[i][j-1];
}
}
}
//析构数组
复制代码
按照公式实现就好了,很简单
这里有个小细节。C[][]下标是从1开始的,而且长度比X,Y要长1位,这是从实现角度,省下了考虑i-1 < 0的问题,实现的一个小技巧。
以下图所示:
目前为止,咱们确实是实现了LCS,可是如何输出该最长子串呢?
很简单,只要在上述代码中,对其走过的字串进行标记,标记后经过反向递归,获得路径,如上图所示的灰色箭头就是其回溯的路径。
其余相关章节
复制代码
算法相关文章之一:《简单说说二叉搜索树》
算法相关文章之二:《B树,一点都不神秘》
算法相关文章之三:《B树很简单,插入so easy》
算法相关文章之四:《什么是动态规划》
算法相关文章之五:《LCS,给你一个不同的模糊匹配》