1. 程式人生 > >動態規劃-備忘錄和自底向上法解決LCS-python實現

動態規劃-備忘錄和自底向上法解決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:

NONEy1y2y3y4y5y6
x1
x2
x3
x4LUP↖

假如末尾兩個字元相同,那麼這時候算X(m-1)和Y(n-1),所以可以同時將XY減少一個,即左上方的小框框

假如末尾不同則考慮兩種不同情況,比較分別各去除最後一個字元的子問題的解。假如X去了一個的情況下比較大所以選擇X去了最後一個的情況,那麼向上一格;同理,Y去一個比較大那麼向左。

(小補充:)

在構造LCS的函式中,根據上述情況進行遞迴,直到i==0 or j==0;即子串長度是0的時候遞迴截止。並且僅在字元相同的時候列印,遞迴由上到下進行,雖然先遇到字串較長時候的相同字元,但是呼叫遞迴表示式,然後在輸出,導致輸出的結果就是正著方向的了。