動態規劃-備忘錄和自底向上法解決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是存放的是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
二,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的時候遞迴截止。並且僅在字元相同的時候列印,遞迴由上到下進行,雖然先遇到字串較長時候的相同字元,但是呼叫遞迴表示式,然後在輸出,導致輸出的結果就是正著方向的了。