题名为:最长公共子序列。
题目要求:
1.用动态规划算法思想设计最长公共子序列问题求解算法,并且对给定的数据(分两类:统一和自选)进行验证。
2.要求分析算法的时间复杂性。
3.与穷举算法,直接递归法,备忘录法进行对比并且形成分析报告。
1.算法原理:
字符串X,长度为m,从1开始数;
字符串Y,长度为n ,从1开始数;
Xi=﹤x1,⋯,xi﹥即X序列的前i个字符(1≤i≤m)
Yj=﹤y1,⋯,yj﹥即Y序列的前j个字符 (1≤j≤n)
LCS(X , Y) 为字符串X和Y的最长公共子序列,即为Z=﹤z1,⋯,zk﹥
若xm=yn(最后一个字符相同),则:
Xm与Yn的最长公共子序列Zk的最后一个字符必定为xm
zk=xm=yn
LCS(Xm,Yn) = LCS(Xm-1,Yn-1)+xm
若xm≠yn,则:
LCS(Xm,Yn)= max{LCS(Xm-1,Yn),LCS(Xm,Yn-1)}
2.算法实现步骤:
1)先找LCS的长度:
a=
b=
创建一个二维数组c[i][j],用来存放LCS的长度
c[0][j],c[i][0]初始化为0,因为任何字符串和一个空字符串的lcs 都是为0。
从a[0]开始,依次和b[i]对比,
(1)如果a[j]和b[j]相等,那么a[i][j]==a[i-1]j-1
(2)如果a[j]和b[i]不相等,那么a[i][j]就和a[i-1][j],a[i][j-1]比较,那个大就填那个值。
依此类推,最后填出的表右下角的值为LCS的长度
[0, 0, 0, 0, 0, 0, 0, 0]
[0, 0, 1, 1, 1, 1, 1, 1]
[0, 0, 1, 1, 1, 2, 2, 2]
[0, 0, 1, 2, 2, 2, 2, 2]
[0, 1, 1, 2, 2, 2, 3, 3]
[0, 1, 2, 2, 3, 3, 3, 4]
[0, 1, 2, 2, 3, 3, 4, 4]
2)再找LCS本身:
为了查找方便,在比较a[i]b[j]时,另创一个数组记录每个位置的动作。如果相等,此位置为“ok”,如果不相等,上面的大为“up”,左面的大为“left”。
最后从c[i][j]开始看,如果flag[i,j]”ok”,那么lcs的最后一个就是a[i-],此时a[i-]b[j-],如果flag[i,j][up],j-1 重新看flag[i,j]的值,如果flag[i,j]”left”,则i-1,在看flag。
a序列 b序列 LCS 时间
18 25 6 0.00024454
15 20 5 0.0001738
50 50 11 0.0012632
100 100 30 0.0046937
500 500 161 0.13901399999
1000 1000 320 0.650319700
3.算法调试
1.在计算时间复杂性时,遇到在500以上数据量时会报错
(maximum recursion depth exceeded in comparison)因为python默认的递归深度是很有限的(默认是1000),因此当递归深度超过999的样子,就会引发这样的一个异常。此时在代码首部加入更改递归深度的代码即可解决。
import sys
sys.setrecursionlimit(100000)
1.穷举法:将a[],b[]两个字符串的所有字符串求出,并将这些字符串相互比较知道找到最长公共子序列为止,当字符串长度很大时,所耗时间非常大。
2.直接递归法:当a[]b[]两个字符串相对应的位置的字符串相同时,则直接求下一个位置,当不同时取两种情况中的最大值。时间复杂都线性增长。
3.备忘录算法是从顶向下计算最优解的思想,备忘录方法的控制结构与直接递归方法的控制结构相同,但备忘录方法用一个表格来保存已解决的子问题的答案,避免了相同问题的重复求解。
import time
import random
import string
import sys
sys.setrecursionlimit(100000)
#找到有几个lcs
q= []
def lcs(a, b):
lena = len(a)
lenb = len(b)
c = [[0 for i in range(lenb + 1)] for j in range(lena + 1)]
flag = [[0 for i in range(lenb + 1)] for j in range(lena + 1)]
for i in range(lena):
for j in range(lenb):
if a[i] == b[j]:
c[i + 1][j + 1] = c[i][j] + 1
flag[i + 1][j + 1] = 'ok'
elif c[i + 1][j] > c[i][j + 1]:
c[i + 1][j + 1] = c[i + 1][j]
flag[i + 1][j + 1] = 'left'
else:
c[i + 1][j + 1] = c[i][j + 1]
flag[i + 1][j + 1] = 'up'
return c, flag
#输出lcs
def printLcs(flag, a, i, j):
if i == 0 or j == 0:
return
if flag[i][j] == 'ok':
printLcs(flag, a, i - 1, j - 1)
q.append(a[i - 1])
print(a[i - 1], end='')
print(len(q))
elif flag[i][j] == 'left':
printLcs(flag, a, i, j - 1)
else:
printLcs(flag, a, i - 1, j)
if __name__ == '__main__':
# a = 'BDCABA'
# b = 'ABCBDAB'
s = string.ascii_uppercase # 所有大写字母(A-Z)
a=[random.choice(s) for i in range(1000)]
b=[random.choice(s) for i in range(1000)]
print(a)
print(b)
t1 = time.perf_counter()
c, flag = lcs(a, b)
printLcs(flag, a, len(a), len(b))
t2 =time.perf_counter()
print()
print(t2-t1)
在计算LCS长度的时候,最好是自己在纸上每一行每一列的算一遍,不然很容易就会懵。通过这次实验也让我解决了以前联系爬虫时遇到的问题,每次爬取的页面多了之后就会报错,当时不知道是递归限制的问题。