码迷,mamicode.com
首页 > 其他好文 > 详细

动态规划-最长公共子序列

时间:2020-05-24 12:11:48      阅读:59      评论:0      收藏:0      [点我收藏+]

标签:很多   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

(0)
(0)
   
举报
评论 一句话评论(0
登录后才能评论!
© 2014 mamicode.com 版权所有  联系我们:gaon5@hotmail.com
迷上了代码!