[轻松掌握动态规划]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 计算过程中的中间结果。
- 返回值:无,直接显示绘制好的图形。
- 实现步骤:
- 设置图形和坐标轴:
- 创建一个图形和坐标轴对象,设置图形大小。
- 设置坐标轴的刻度和标签,将
text2
的字符作为 x 轴标签,text1
的字符作为 y 轴标签。 - 设置图形标题和坐标轴标签的样式。
- 绘制单元格数值和转移箭头:
- 遍历动态规划表的每个单元格,在单元格中心显示对应的数值。
- 根据状态转移情况绘制箭头:
- 当字符匹配时,绘制对角箭头。
- 当字符不匹配时,根据
dp
值的来源绘制上方或左侧箭头。
- 绘制网格线:
- 在图形上绘制水平和垂直的网格线,增强表格的可视化效果。
- 高亮匹配字符的单元格:
- 遍历
text1
和text2
的每个字符,当字符匹配时,在对应的单元格上绘制半透明的绿色矩形进行高亮显示。
- 设置图形布局并显示:
- 设置 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)