动态规划-最长公共子序列算法实现可视化
本算法课程设计要求设计一个程序,能够实现动态规划寻找最长公共子序列的可视化。
具体要求:给定两个字符串s1和s2,
(1)求出最长公有子序列的长度
(2)求出最长公有子序列。
提示:文章写完后,目录可以自动生成,如何生成可参考右边的帮助文档
设计要求
提示:这里可以添加本文要记录的大概内容:
本算法设计要求设计一个程序,能够实现动态规划寻找最长公共子序列的可视化。
具体要求:给定两个字符串s1和s2,
(1)求出最长公有子序列的长度
(2)求出最长公有子序列。
(3)实现可视化。
# 一、动态规划法
本设计所使用的算法—动态规划法
动态规划法往往用于求解最优化问题,采用分布决策,计算每一步的最优解。与分治法相同都是是将一个大问题划分成若干个小规模的同类子问题,而且动态规划法的每一步决策都依赖于子问题的解,自底向上,求出整个问题的解。但是分治法对于一些子问题会出现重复计算的现象,而动态规划法能避免重叠子问题现象。
利用动态规划法求解问题的基本步骤:
(1)划分阶段:依照问题的时间或空间特征。把问题分为若干个阶段。在划分阶段时。注意划分后的阶段一定要是有序的或者是可排序的。否则问题就无法求解。
(2)确定状态和状态变量:将问题发展到各个阶段时所处于的各种客观情况用不同的状态表示出来,状态的选择要满足无后效性。
(3)确定决策并写出状态转移方程:由于决策和状态转移有着天然的联系,状态转移就是依据上一阶段的状态和决策来导出本阶段的状态。所以假设确定了决策。状态转移方程也就可写出。但其实经常是反过来做。依据相邻两个阶段的状态之间的关系来确定决策方法和状态转移方程。
(4)寻找边界条件:给出的状态转移方程是一个递推式。须要一个递推的终止条件或边界条件。
二、功能模块详细设计
1.动态规划算法解决LCS问题
详细设计思想
通过分析最优子结构得到:当s1(i)=s2(j),则从c(i)= s1(i)=s2(j),而且c(k-1)是s1(i-1)和s2(j-1)的最长公共子序列;当s1(i)!=s2(j)且c(k)!=s1(i),则c是s1(i-1)和s2的最长公共子序列;当s1(i)!=s2(j)且c(k)!=s2(j),则c是s1和s2(j-1)的最长公共子序列;由于最长公子序列问题的最优化子结构性质建立在子问题最优值的递归关系。用c[i][j]记录序列s1和s2的最长公共子序列的长度。根据最优子结构性质可以得出递归关系:
c[i + 1][j + 1] = c[i][j] + 1 s1[i]=s2[j]
c[i][j]=
c[i + 1][j + 1] = c[i + 1][j] s1[i]!=s2[j] 且c[i + 1][j] > c[i][j+1]
c[i + 1][j + 1] = c[i][j + 1] s1[i]!=s2[j] 且c[i + 1][j] < c[i][j + 1]
用数组d[][]来记录转移方向,便于后续回溯输出序列字母
d[i+1][j+1]=ok s1[i]=s2[j]
d[i][j]= d[i+1][j+1]=left c[i+1][j]<c[i][j+1]
d[i+1][j+1]=up c[i ][j+1] > c[i+1][j ]
用s[]存储公共子序列。当c[i][j]不为空时,当d[i][j]=ok时,就在s[]中插入该字符,并向左上角寻找下一个,当d[i][j]=left时,向左找下一个,当d[i][j]=up时,向上找下一个,循环找出所有序列字母,并返回列表中的元素。
代码如下(示例):
def find_lcseque(s1, s2):
# 生成字符串长度加1的0矩阵,m用来保存对应位置匹配的结果
c = [[0 for x in range(len(s2) + 1)] for y in range(len(s1) + 1)]
# d用来记录转移方向
d = [[None for x in range(len(s2) + 1)] for y in range(len(s1) + 1)]
for i in range(len(s1)):
for j in range(len(s2)):
if s1[i] == s2[j]: # 字符匹配成功,则该位置的值为左上方的值加1
c[i + 1][j + 1] = c[i][j] + 1
d[i + 1][j + 1] = 'ok'
elif c[i + 1][j] > c[i][j + 1]: # 左值大于上值,则该位置的值为左值,并标记回溯时的方向
c[i + 1][j + 1] = c[i + 1][j]
d[i + 1][j + 1] = 'left'
else: # 上值大于左值,则该位置的值为上值,并标记方向up
c[i + 1][j + 1] = c[i][j + 1]
d[i + 1][j + 1] = 'up'
(i, j) = (len(s1), len(s2))
# print(numpy.array(d))
s = []
while c[i][j]: # 不为None时
k = d[i][j]
if k == 'ok': # 匹配成功,插入该字符,并向左上角找下一个
s.append(s1[i - 1])
i -= 1
j -= 1
if k == 'left': # 根据标记,向左找下一个
j -= 1
if k == 'up': # 根据标记,向上找下一个
i -= 1
s.reverse() # 返回列表中的元素
return ''.join(s), d, c # 将序列中的元素以指定的字符连接生成一个新的字符串
2.用turtle库实现动态化显示
详细设计思想
首先绘制列表s1与s2,用循环,每次写一个字母,就将画笔移动到新的坐标位置,s1列表每次将x向右移动50个单位长度,循环画出列表s1,s2列表第一个字母应当与s1[1]在x,y坐标上相差一个方格的长度,每写一个字母就将y坐标向下移动50个单位长度。第二步就是一行一行填充表格,根据方向列表d[i][j],设置双重循环,当d[i][j]=ok时并且i j不等于0时,画笔落下,准备填充图形,设置填充颜色为粉色,开始绘制正方形,向当前画笔方向移动50个像素长度,逆时针移动90度循环4次,然后再向前移动20个像素,在方格内部中心填写文本c[i][j]填充完成,当d[i][j]不等于ok时,填充颜色设置为蓝色,重复上述步鄹,到循环结束。
代码如下(示例):
def print_strings():
s1 = e1.get()
s2 = e2.get()
f, d, c = find_lcseque(s1, s2)
def in_print_strings(s1, s2, d, c):
xx = x
yy = y + n
j, k = 0.5 * n, 0.75 * n
for i in s1:
j += n
turtle.penup()
turtle.goto(xx + j, yy) # 将画笔 移动到坐标为x,y的位置
turtle.write(i, align='center', font='18')
for i in s2:
k += n
turtle.penup()
turtle.goto(xx - 10, yy - k)
turtle.write(i, align='center', font='18')
ii = 0
jj = 0
turtle.speed(20)
turtle.pensize(2)
turtle.penup()
for i in range(len(s1) + 1):
for j in range(len(s2) + 1):
turtle.goto(x + j * n, y - i * n)
if d[i][j] == 'ok' and i > ii and j > jj:
# draw(n, "pink", str(c[i][j]))
turtle.pendown()
turtle.begin_fill() # 准备开始填充图形
turtle.fillcolor("pink") # 绘制图形的填充颜色
for index in range(4):
turtle.forward(n) # 向当前画笔方向移动distance像素长度
turtle.left(90) # 逆时针移动degree°
turtle.fd(20) # 向前移动20个像素
turtle.write(c[i][j], align='center',
font='13') # 写文本写文本,s为文本内容,font是字体的参数,分别为字体名称,大小和类型;font为可选项,font参数也是可选项
turtle.end_fill() # 填充完成
turtle.penup()
ii == i
jj = j
else:
turtle.pendown()
turtle.begin_fill() # 准备开始填充图形
turtle.fillcolor("blue") # 绘制图形的填充颜色
for index in range(4):
turtle.forward(n) # 向当前画笔方向移动distance像素长度
turtle.left(90) # 逆时针移动degree°
turtle.fd(20) # 向前移动20个像素
turtle.write(c[i][j], align='center',
font='13') # 写文本写文本,s为文本内容,font是字体的参数,分别为字体名称,大小和类型;font为可选项,font参数也是可选项
turtle.end_fill() # 填充完成
turtle.penup()
turtle.done()
GUI编程实现交互窗口
详细设计思想
首先需要自定义一个函数将LCS函数中的参数get()到,然后用tkinter库中的Buton、Canvas、Entry、Lable等组件来生成一个操作窗口。
def getValue():
s1 = e1.get()
s2 = e2.get()
f, d, c = find_lcseque(s1, s2)
textPad.insert(tk.END, "两个字符的最长公共子序列为:"+f + "\n")
# print_strings(s1, s2, d, c)
window = tk.Tk()
window.title('my window')
window.geometry('450x500')
canvas1 = tk.Canvas(window, height=200, width=500)
image_file = tk.PhotoImage(file="1589343264328.GIF")
image = canvas1.create_image(0,0, anchor='nw', image=image_file)
canvas1.pack()
l1 = tk.Label(window, bg='pink', width=50, text='请输入待匹配字符串')
l1.pack()
e1 = tk.Entry(window, bd=5)
e1.pack()
l2 = tk.Label(window, bg='pink', width=50, text='请输入匹配字符串')
l2.pack()
e2 = tk.Entry(window, bd=5)
e2.pack()
b = tk.Button(window, text="开始匹配", command=getValue, width=25)
b.pack()
b2 = tk.Button(window, text="退出", command=quit, width=25)
b2.pack()
b3 = tk.Button(window, text="动态演示", command=print_strings, width=25)
b3.pack()
b3.quit()
textPad = ScrolledText(window, width=50, height=5)
textPad.pack()
window.mainloop()
总结
**运行结果展示**