zoukankan      html  css  js  c++  java
  • 动态规划-最长公共子序列

    最长公共子序列(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()
     
  • 相关阅读:
    超级有用的15个mysqlbinlog命令
    表迁移工具的选型-复制ibd的方法-传输表空间
    误删mysql表物理文件的解决方法(不涉及恢复数据)
    美团SQL优化工具SQLAdvisor
    Linux网络状态工具ss命令使用详解
    MySQL应用架构优化-实时数据处理
    运维利器万能的 strace
    ngrep命令用法
    [Linux|DBA]运维三十六计
    sysbench使用
  • 原文地址:https://www.cnblogs.com/an-wl/p/12949060.html
Copyright © 2011-2022 走看看