动态规划-备忘录和自底向上法解决LCS-python实现

一、计算LCS的长度

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是存放的是XiYjLCS长度

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

二,LCS的构造

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的时候递归截止。并且仅在字符相同的时候打印,递归由上到下进行,虽然先遇到字符串较长时候的相同字符,但是调用递归表达式,然后在输出,导致输出的结果就是正着方向的了。

你可能感兴趣的:(算法导论的学习)