【动态规划】最长公共子序列Python实现

2023-12-14 20:12:08

问题描述

  • 给定两个序列 X = { ? x 1 , x 2 , ? ? , x m ? } X = \set{x_{1} , x_{2} , \cdots , x_{m}} X={x1?,x2?,?,xm?} Y = { ? y 1 , y 2 , ? ? , y n ? } Y = \set{y_{1} , y_{2} , \cdots , y_{n}} Y={y1?,y2?,?,yn?},找出 X X X Y Y Y的最长公共子序列

最长公共子序列的结构

  • 设序列 X = { ? x 1 , x 2 , ? ? , x m ? } X = \set{x_{1} , x_{2} , \cdots , x_{m}} X={x1?,x2?,?,xm?} Y = { ? y 1 , y 2 , ? ? , y n ? } Y = \set{y_{1} , y_{2} , \cdots , y_{n}} Y={y1?,y2?,?,yn?}的最长公共子序列为 Z = { ? z 1 , z 2 , ? ? , z k ? } Z = \set{z_{1} , z_{2} , \cdots , z_{k}} Z={z1?,z2?,?,zk?}
    • x m = y n x_{m} = y_{n} xm?=yn?,则 z k = x m = y n z_{k} = x_{m} = y_{n} zk?=xm?=yn?,且 Z k ? 1 Z_{k - 1} Zk?1? X m ? 1 X_{m - 1} Xm?1? Y n ? 1 Y_{n - 1} Yn?1?的最长公共子序列
    • x m ≠ y n x_{m} \neq y_{n} xm?=yn? z k ≠ x m z_{k} \neq x_{m} zk?=xm?,则 Z Z Z X m ? 1 X_{m - 1} Xm?1? Y Y Y的最长公共子序列
    • x m ≠ y n x_{m} \neq y_{n} xm?=yn? z k ≠ y n z_{k} \neq y_{n} zk?=yn?,则 Z Z Z X X X Y n ? 1 Y_{n - 1} Yn?1?的最长公共子序列

子问题的递归结构

  • x m = y n x_{m} = y_{n} xm?=yn?时,找出 X m ? 1 X_{m - 1} Xm?1? Y n ? 1 Y_{n - 1} Yn?1?的最长公共子序列,然后在其尾部加上 x m x_{m} xm?
  • x m ≠ y n x_{m} \neq y_{n} xm?=yn?时,找出 X m ? 1 X_{m - 1} Xm?1? Y Y Y的一个最长公共子序列及 X X X Y n ? 1 Y_{n - 1} Yn?1?的一个最长公共子序列,这两个公共子序列中较长者即为 X X X Y Y Y的最长公共子序列
c [ i ] [ j ] c[i][j] c[i][j]递归方程

c [ i ] [ j ] = { 0 , i = 0 , j = 0 c [ i ? 1 ] [ j ? 1 ] + 1 , i , j > 0 ; x i = y j max ? { ? c [ i ] [ j ? 1 ] , c [ i ? 1 ] [ j ] ? } , i , j > 0 ; x i ≠ y j c[i][j] = \begin{cases} 0 , & i = 0 , j = 0 \\ c[i - 1][j - 1] + 1 , & i , j > 0 ; x_{i} = y_{j} \\ \max\set{c[i][j - 1] , c[i - 1][j]} , & i , j > 0 ; x_{i} \neq y_{j} \end{cases} c[i][j]=? ? ??0,c[i?1][j?1]+1,max{c[i][j?1],c[i?1][j]},?i=0,j=0i,j>0;xi?=yj?i,j>0;xi?=yj??


时间复杂性

  • 由于每个数组单元的计算耗费 O ( 1 ) O(1) O(1)时间,因此算法耗时 O ( m n ) O(mn) O(mn)

构造最长公共子序列

  • 在算法 L C S LCS LCS中,每次递归调用使 i i i j j j 1 1 1,因此算法的计算时间为 O ( m + n ) O(m + n) O(m+n)

Python实现

def longest_common_subsequence(str_1, str_2):
    m = len(str_1)
    n = len(str_2)

    # 创建一个二维数组来存储子问题的解
    dp = [[0] * (n + 1) for _ in range(m + 1)]

    # 填充二维数组
    for i in range(1, m + 1):
        for j in range(1, n + 1):
            if str_1[i - 1] == str_2[j - 1]:
                dp[i][j] = dp[i - 1][j - 1] + 1
            else:
                dp[i][j] = max(dp[i - 1][j], dp[i][j - 1])

    # 构造最长公共子序列
    lcs = ''

    i, j = m, n
    while i > 0 and j > 0:
        if str_1[i - 1] == str_2[j - 1]:
            lcs = str_1[i - 1] + lcs

            i -= 1
            j -= 1
        elif dp[i - 1][j] > dp[i][j - 1]:
            i -= 1
        else:
            j -= 1

    return lcs


str_1 = 'ABCDGH'
str_2 = 'AEDFHR'

lcs = longest_common_subsequence(str_1, str_2)

print(f'最长公共子序列: {lcs}')

算法的改进

  • 如果只需要计算最长公共子序列的长度,则只用到数组 c c c的第 i i i行和第 i ? 1 i - 1 i?1
  • 因此,用两行的数组空间就可以计算出最长公共子序列的长度,可将空间需求减至 O ( min ? { ? m , n ? } ) O(\min\set{m , n}) O(min{m,n})

文章来源:https://blog.csdn.net/from__2023_11_28/article/details/134927075
本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。