标签:很多 style get 必须 二维 多重 ret %s 公式
最长公共子序列(Longest-Common-Subsequences,LCS)是一个在一个序列集合中
(通常为两个序列)用来查找所有序列中最长子序列的问题。
最长公共子串(Longest-Common-Substring,LCS)问题是寻找两个或多个已知字符
串最长的子串。此问题与最长公共子序列问题的区别在于子序列不必是连续的,而子串却
必须是连续的。
思路:假设求最长公共子序列的函数为:MaxLen(i,j),i,j分别是所求2个字符串
S1,S2的长度,利用动态规划的思想,既是把问题规模缩小为一个更小的问题来解决,
从而实现递归的办法。比如考虑MaxLen(i,j)与MaxLen(i-1,j-1)的关系,从而
不断的把问题进而缩小规模。
(1)S1的最后一个元素与S2的最后一个元素相同,这说明该元素一定位于公共子序列中。
因此,现在只需要找:MaxLen(i-1,j-1);假如S1的最后一个元素与S2的最后一个元素相等
MaxLen(i,j) = MaxLen(i-1,j-1) + 1;
(2)假如最后的元素不相等,那说明最后一个元素不可能是最长公共子序列中的元素。
因此会产生两个子问题:MaxLen(i-1,j) 和 MaxLen(i,j-1),最长公共子序列MaxLen(i,j)
的值就从上面2个子问题中取个最大值,
MaxLen(i-1,j)表示:最长公共序列可以在(x1,x2,....x(i-1)) 和 (y1,y2,...yj)中找。
MaxLen(i,j-1)表示:最长公共序列可以在(x1,x2,....xi) 和 (y1,y2,...y(j-1))中找。
求解上面两个子问题,得到的公共子序列谁最长,那谁就是 MaxLen(i,j)。用数学表示就是:
即:MaxLen(i,j) = Max(MaxLen(i,j-1),MaxLen(i-1,j) );
(3)边界条件:
MaxLen(n,0) = 0 ( n= 0…len1)
MaxLen(0,n) = 0 ( n=0…len2)
(4)递归中重复计算的问题会影响程序的执行效率,因此通过二维数组来计算,避免重复计算的问题。
就是说原问题 转化 成子问题后,子问题中有相同的问题。
原问题是:LCS(X,Y)。子问题有 ?LCS(Xn-1,Ym-1) ?LCS(Xn-1,Ym) ?LCS(Xn,Ym-1)
初一看,这三个子问题是不重叠的。可本质上它们是重叠的,因为它们只重叠了一大部分。举例:
第二个子问题:LCS(Xn-1,Ym) 就包含了:问题?LCS(Xn-1,Ym-1),为什么?
因为,当Xn-1 和 Ym 的最后一个元素不相同时,我们又需要将LCS(Xn-1,Ym)进行分解:
分解成:LCS(Xn-1,Ym-1) 和 LCS(Xn-2,Ym)也就是说:在子问题的继续分解中,有些问题是重叠的。
由于像LCS这样的问题,它具有重叠子问题的性质,因此:用递归来求解就太不划算了。
因为采用递归,它重复地求解了子问题啊。而且注意哦,所有子问题加起来的个数可是指数级的。
关键是采用动态规划时,并不需要去一一计算那些重叠了的子问题。
或者说:用了动态规划之后,有些子问题 是通过 “查表“ 直接得到的,而不是重新又计算一遍得到的。
例如求fib(5),分解成了两个子问题:fib(4) 和 fib(3),求解fib(4) 和 fib(3)时,又分解了
一系列的小问题....
从中可以看出:根的左右子树:fib(4) 和 fib(3)下,是有很多重叠的!!!比如,对于 fib(2),
它就一共出现了三次。如果用递归来求解,fib(2)就会被计算三次,而用DP(Dynamic Programming)
动态规划,则fib(2)只会计算一次,其他两次则是通过”查表“直接求得。而且,更关键的是:查找
求得该问题的解之后,就不需要再继续去分解该问题了。而对于递归,是不断地将问题分解,直到
分解为基准问题(fib(1) 或者 fib(0))
比如:abcfbc abfcab
0 a b c f b c
0 1 2 3 4 5 6
0 0 0 0 0 0 0 0 0
a 1 0 1 1 1 1 1 1
b 2 0
f 3 0
c 4 0
a 5 0
b 6 0
i=1,j=1时,a=a,那么maxlen(1,1)的值就是maxlen(0,0)+1=1,先完成第1行的二维数组值,
i=1,j=2时,a<>b,取maxLen(0,2),maxLen(1,1)两者的最大值,为1。
的计算,然后再完成第2行,直到计算到最后一行,二维数组中的最大值既是LCS。
(5)当填完表后,通过该二维表从后往前递推,即可求出最长公共子序列。
通过递推公式,可以看出,取maxLen[i][j]取maxLen[i-1][j-1]
或者是maxLen[i-1][j]和maxLen[i][j-1]的较大值(可能相等)。
我们将从最后一个元素maxLen[i][j]倒推出S1和S2的LCS。
maxLen[6][6] = 4,且S1[6] != S2[6],所以倒推回去,maxLen[6][6]的值
来源于maxLen[6][5]或者maxLen[5][6],他们都是4,决定一个方向,后续遇到值相同,都按此方向进行。
maxLen[6][5] = 4, 且S1[5] = S2[6], 所以倒推回去,maxLen[6][5]的值来源于maxLen[5][4]。
以此类推,
如果遇到S1[i] != S2[j] ,且res[i-1][j] = res[i][j-1] 这种存在分支的情况,
这里都选择一个方向(之后遇到这样的情况,也选择相同的方向,要么都往左,要么都往上)。
Python算法实现:
1 import numpy as np 2 3 def LCS(string1,string2): 4 len1 = len(string1) 5 len2 = len(string2) 6 # i是列-第1个字符串,j是行-第2个字符串 7 res = [[0 for i in range(len1+1)] for j in range(len2+1)] 8 for i in range(1,len2+1): 9 for j in range(1,len1+1): 10 if string2[i-1] == string1[j-1]: 11 res[i][j] = res[i-1][j-1]+1 12 else: 13 res[i][j] = max(res[i-1][j],res[i][j-1]) 14 # -1,-1是获取数组的最后一个元素的值 15 return res,res[-1][-1] 16 17 def getLCS(s1,s2,arr,n): 18 # 直接用len求二位数组的维度,返回的是这个数组有多少行nrow,第二个字符串,相当于j 19 j = len(arr)-1 20 # 如果想要求列的话,可以求数组中某一个行向量的列维度ncol,第一个字符串,相当于i 21 i = len(arr[0])-1 22 comlcs = "" 23 while n > 0: 24 if s2[j-1] == s1[i-1]: 25 comlcs += s2[j-1] 26 i -= 1 27 j -= 1 28 n -= 1 29 elif arr[j][i-1] >= arr[j-1][i]: 30 i -= 1 31 elif arr[j-1][i] > arr[j][i-1]: 32 j -= 1 33 return comlcs 34 35 36 37 def main(): 38 # 假设这里输入的串都是有公共子序列 39 s1,s2 = input("请分别输入两个字符串,逗号分隔:").split(",") 40 arr,maxLong = LCS(s1,s2) 41 print(np.array(arr)) 42 print("最大公共子序列长度:%d" % maxLong) 43 lcs = getLCS(s1,s2,arr,maxLong) 44 # [::-1]倒序输出字符串 45 print("最大公共子序列长度:%s" % lcs[::-1] ) 46 47 48 49 if __name__ == "__main__": 50 main()
标签:很多 style get 必须 二维 多重 ret %s 公式
原文地址:https://www.cnblogs.com/an-wl/p/12949060.html