使用python解决最长公共子序列(LCS)问题

[轻松掌握动态规划]5.最长公共子序列 LCS_哔哩哔哩_bilibili

一、概述

本代码旨在解决最长公共子序列(LCS)问题,并将动态规划求解过程中的状态转移表格进行可视化展示。通过 matplotlib 库绘制表格,同时使用箭头表示状态转移方向,以及高亮显示匹配字符的单元格,帮助用户更直观地理解 LCS 问题的求解过程。

二、依赖库

  • matplotlib.pyplot:用于绘制图形和可视化数据。
  • numpy:用于处理数值计算和数组操作,主要用于设置坐标轴刻度。
  • matplotlib.patches.FancyArrow:用于绘制带箭头的线条,以表示动态规划中的状态转移。

三、代码结构与函数说明

1. longestCommonSubsequence 函数

收起

python

def longestCommonSubsequence(text1, text2):
    m, n = len(text1), len(text2)
    dp = [[0] * (n + 1) for _ in range(m + 1)]

    for i in range(1, m + 1):
        for j in range(1, n + 1):
            if text1[i-1] == text2[j-1]:
                dp[i][j] = dp[i-1][j-1] + 1
            else:
                dp[i][j] = max(dp[i-1][j], dp[i][j-1])
    return dp, dp[m][n]

  • 功能:使用动态规划方法计算两个字符串 text1 和 text2 的最长公共子序列的长度,并返回动态规划表 dp 和最长公共子序列的长度。
  • 参数
    • text1:第一个输入字符串。
    • text2:第二个输入字符串。
  • 返回值
    • dp:二维列表,存储动态规划过程中的中间结果。
    • dp[m][n]:整数,最长公共子序列的长度。
  • 实现思路
    • 初始化一个 (m + 1) x (n + 1) 的二维数组 dp,其中 m 和 n 分别是 text1 和 text2 的长度。
    • 通过两层嵌套循环遍历 text1 和 text2 的每个字符:
      • 如果当前字符匹配,则 dp[i][j] 等于左上角单元格的值加 1。
      • 如果不匹配,则 dp[i][j] 取上方和左方单元格中的最大值。

2. plot_lcs_dp_table 函数

收起

python

def plot_lcs_dp_table(text1, text2, dp):
    ...

  • 功能:将最长公共子序列的动态规划表进行可视化展示,包括绘制单元格数值、状态转移箭头、网格线,并高亮显示匹配字符的单元格。
  • 参数
    • text1:第一个输入字符串。
    • text2:第二个输入字符串。
    • dp:动态规划表,存储 LCS 计算过程中的中间结果。
  • 返回值:无,直接显示绘制好的图形。
  • 实现步骤
    1. 设置图形和坐标轴
     
    • 创建一个图形和坐标轴对象,设置图形大小。
    • 设置坐标轴的刻度和标签,将 text2 的字符作为 x 轴标签,text1 的字符作为 y 轴标签。
    • 设置图形标题和坐标轴标签的样式。
     
    1. 绘制单元格数值和转移箭头
     
    • 遍历动态规划表的每个单元格,在单元格中心显示对应的数值。
    • 根据状态转移情况绘制箭头:
      • 当字符匹配时,绘制对角箭头。
      • 当字符不匹配时,根据 dp 值的来源绘制上方或左侧箭头。
     
    1. 绘制网格线
     
    • 在图形上绘制水平和垂直的网格线,增强表格的可视化效果。
     
    1. 高亮匹配字符的单元格
     
    • 遍历 text1 和 text2 的每个字符,当字符匹配时,在对应的单元格上绘制半透明的绿色矩形进行高亮显示。
     
    1. 设置图形布局并显示
     
    • 设置 x 轴和 y 轴的标签和样式。
    • 使用 tight_layout 方法自动调整图形布局,确保所有元素都能完整显示。
    • 显示绘制好的图形。

3. 示例执行部分

收起

python

text1 = "algorithm"
text2 = "alignment"
dp, length = longestCommonSubsequence(text1, text2)
print(f"LCS Length: {length}")
plot_lcs_dp_table(text1, text2, dp)

  • 功能:提供一个示例,演示如何调用 longestCommonSubsequence 函数计算最长公共子序列的长度,并调用 plot_lcs_dp_table 函数将动态规划表进行可视化展示。
  • 步骤
    • 定义两个示例字符串 text1 和 text2
    • 调用 longestCommonSubsequence 函数计算 LCS 长度,并将结果存储在 dp 和 length 中。
    • 打印最长公共子序列的长度。
    • 调用 plot_lcs_dp_table 函数绘制动态规划表的可视化图形。

四、注意事项

  • 代码中的字符串 text1 和 text2 可以根据需要进行修改,以计算不同字符串的最长公共子序列。
  • 图形的大小和字体大小等样式参数可以根据实际情况进行调整,以获得更好的可视化效果。

五、总结

本代码通过动态规划方法解决了最长公共子序列问题,并使用 matplotlib 库将动态规划表进行可视化展示。通过箭头和高亮单元格,用户可以更清晰地理解 LCS 问题的求解过程和状态转移关系。

完整代码

import matplotlib.pyplot as plt
import numpy as np
from matplotlib.patches import FancyArrow

def longestCommonSubsequence(text1, text2):
    m, n = len(text1), len(text2)
    dp = [[0] * (n + 1) for _ in range(m + 1)]

    for i in range(1, m + 1):
        for j in range(1, n + 1):
            if text1[i-1] == text2[j-1]:
                dp[i][j] = dp[i-1][j-1] + 1
            else:
                dp[i][j] = max(dp[i-1][j], dp[i][j-1])
    return dp, dp[m][n]

def plot_lcs_dp_table(text1, text2, dp):
    m, n = len(text1), len(text2)
    fig, ax = plt.subplots(figsize=(n+3, m+3))
    
    # 设置坐标轴标签和标题
    ax.set_xticks(np.arange(n+1))
    ax.set_yticks(np.arange(m+1))
    ax.set_xticklabels([''] + list(text2), fontsize=12, color='blue')
    ax.set_yticklabels([''] + list(text1), fontsize=12, color='red')
    ax.tick_params(axis='both', which='both', length=0)
    plt.title("LCS Dynamic Programming Table\n(Arrows Show State Transitions)", 
              fontsize=14, pad=20)

    # 绘制单元格数值和转移箭头
    arrow_kwargs = {
        'width': 0.3, 
        'head_width': 0.2, 
        'head_length': 0.2,
        'fc': 'darkorange',
        'ec': 'none'
    }

    for i in range(m+1):
        for j in range(n+1):
            # 显示数值
            ax.text(j, i, str(dp[i][j]), 
                     ha='center', va='center', fontsize=14)
            
            # 绘制转移箭头
            if i > 0 and j > 0:
                if text1[i-1] == text2[j-1]:
                    # 对角箭头(字符匹配时)
                    ax.add_patch(FancyArrow(
                        j-0.4, i-0.4, -0.3, -0.3, **arrow_kwargs
                    ))
                else:
                    # 上方箭头
                    if dp[i][j] == dp[i-1][j]:
                        ax.add_patch(FancyArrow(
                            j, i-0.4, 0, -0.3, **arrow_kwargs
                        ))
                    # 左侧箭头
                    if dp[i][j] == dp[i][j-1]:
                        ax.add_patch(FancyArrow(
                            j-0.4, i, -0.3, 0, **arrow_kwargs
                        ))

    # 绘制网格线
    for i in range(m+1):
        ax.axhline(i-0.5, color='gray', lw=1, alpha=0.5)
    for j in range(n+1):
        ax.axvline(j-0.5, color='gray', lw=1, alpha=0.5)

    # 高亮匹配字符的单元格
    for i in range(1, m+1):
        for j in range(1, n+1):
            if text1[i-1] == text2[j-1]:
                ax.add_patch(plt.Rectangle(
                    (j-0.5, i-0.5), 1, 1, 
                    fill=True, alpha=0.2, color='limegreen'
                ))

    plt.xlabel("Text2 Characters", fontsize=12, color='blue', labelpad=10)
    plt.ylabel("Text1 Characters", fontsize=12, color='red', labelpad=10)
    plt.tight_layout()
    plt.show()

# 示例执行
text1 = "algorithm"
text2 = "alignment"
dp, length = longestCommonSubsequence(text1, text2)
print(f"LCS Length: {length}")
plot_lcs_dp_table(text1, text2, dp)

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值