LCS(longest-common-subsequence problem)就是两个序列中最长的公共子序列
例如:X1=[1,2,3,4,5,6,76,66] X2=[2,453,3,545,4,4324] 在这两段序列中LCS 为2,3,4
定理:LCS最优子结构:
令X=[x1,x2,x3,x4,.......,xm] Y=[y1,y2,y3,y4,.........,ym] Z=[z1,z2,z3,z4......,zk]是X,Y的任意LCS
1.如果xm==yn,就是说最后两个相同时,对于两个序列来说,可以暂时把分别位于最后的这两个xm and yn拿出来,由原来的找X[x1~xm]和Y[y1~yn]的LCS变为找X[x1~x(m-1)]和Y[y1~y(n-1)]的LCS。由于xm=yn,所以可以原问题比子问题的LCS多1
2.如果xm!=yn,并且如果zk!=xm则说明Z是X(m-1)和Y的一个LCS,因为LCS的最后一个不是X的最后一个,那把X中的最后一个去了也无妨喽;同理zk!=yn则说明Z是X和Y(n-1)的一个LCS。所以找到这两种情况下最长的就行了。
#备注代码中c是存放的是Xi到Yj的LCS长度
i和j是分别在X和Y序列的下标,从右到左.在代码中,ij不是字符串的下标,而是下标加1,也就是说ij是子串长度
b存放的东西是构造LCS需要用的
# ------------------------------------备忘录实现-----------------------------------
def LCS(X, Y):
m, n = len(X), len(Y)
c = []
b = []
for i in range(m + 1):
c.append([float('inf') for i in range(n + 1)])
b.append([float('inf') for i in range(n + 1)])
LCS_help(X, Y, c, b)
for i in range(len(X) + 1):
print(c[i])
print("----------------------------------")
for i in range(len(X) + 1):
print(b[i])
Print_LCS(b, X, len(X), len(Y))
# print(c)
def LCS_help(X, Y, c, b):
i = len(X)
j = len(Y)
if c[i][j] < float('inf'):
return c[i][j]
if i == 0 or j == 0:
c[i][j] = 0
elif X[i - 1] == Y[j - 1]:
X1 = X[:i - 1]
Y1 = Y[:j - 1]
b[i][j]="LUP"
c[i][j] = LCS_help(X1, Y1, c, b) + 1
else:
X1 = X[:i - 1]
Y1 = Y[:j - 1]
if(LCS_help(X1, Y, c, b)>=LCS_help(X, Y1, c, b)):
b[i][j]="UP"
c[i][j]=LCS_help(X1, Y, c, b)
else:
b[i][j] = "L"
c[i][j] = LCS_help(X, Y1, c, b)
#c[i][j] = max(LCS_help(X, Y1, c, b), LCS_help(X1, Y, c, b))
return c[i][j]
# ------------------------------------自底向上-----------------------------------
def LCS_DowntoUp(X, Y):
m = len(X)
n = len(Y)
c, b = [], []
for i in range(m + 1):
c.append([0 for i in range(n + 1)])
b.append([0 for i in range(n + 1)])
for i in range(0, m + 1):
c[i][0] = 0
for j in range(0, n + 1):
c[0][j] = 0
for i in range(1, m + 1):
for j in range(1, n + 1):
if (X[i - 1] == Y[j - 1]):
c[i][j] = c[i - 1][j - 1] + 1
b[i][j] = "LUP"
elif (c[i-1][j] >= c[i][j - 1]):
c[i][j] = c[i - 1][j]
b[i][j] = "UP"
else:
c[i][j] = c[i][j - 1]
b[i][j] = "L"
for i in range(len(X) + 1):
print(c[i])
print("----------------------------------")
for i in range(len(X) + 1):
print(b[i])
Print_LCS(b,X,len(X),len(Y))
return c, b
def Print_LCS(b,X,i,j):
if i==0 or j==0:
return
if b[i][j]=="LUP":
Print_LCS(b,X,i-1,j-1)
print(X[i-1])
elif b[i][j]=="UP":
Print_LCS(b,X,i-1,j)
else:
Print_LCS(b,X,i,j-1)
我们可以这样理解,从LCS的最优解定理出发来思考:
#在构造的b中储存了字符串“LUP”"UP"“L”,分别表示左上,正上,左。b的横坐标看成Y的length,纵坐标是X的length
ex:
NONE | y1 | y2 | y3 | y4 | y5 | y6 |
x1 | ||||||
x2 | ||||||
x3 | ||||||
x4 | LUP↖ |
假如末尾两个字符相同,那么这时候算X(m-1)和Y(n-1),所以可以同时将XY减少一个,即左上方的小框框
假如末尾不同则考虑两种不同情况,比较分别各去除最后一个字符的子问题的解。假如X去了一个的情况下比较大所以选择X去了最后一个的情况,那么向上一格;同理,Y去一个比较大那么向左。
(小补充:)
在构造LCS的函数中,根据上述情况进行递归,直到i==0 or j==0;即子串长度是0的时候递归截止。并且仅在字符相同的时候打印,递归由上到下进行,虽然先遇到字符串较长时候的相同字符,但是调用递归表达式,然后在输出,导致输出的结果就是正着方向的了。