最長公共子串行LCS

2021-08-18 12:37:14 字數 4261 閱讀 8264

一些基本概念以及lcs演算法核心思想摘錄自別人的部落格,在此申明。(沒必要做不必要的重複勞動,這篇部落格和我看的一本書中的內容很相似,後面的**改編自原書中的實現,書中)

1)子串行: 乙個序列a = a1,a2,……an,中任意刪除若干項,剩餘的序列叫做a的乙個子串行。也可以認為是從序列a按原順序保留任意若干項得到的序列。

例如:對序列 1,3,5,4,2,6,8,7來說,序列3,4,8,7 是它的乙個子串行。

對於乙個長度為n的序列,它一共有2^n 個子序列,有(2^n – 1)個非空子序列。

(2)公共子串行: 顧名思義,如果序列c既是序列a的子串行,同時也是序列b的子串行,則稱它為序列a和序列b的公共子串行。

例如:對序列 1,3,5,4,2,6,8,7和序列 1,4,8,6,7,5 來說

序列1,8,7是它們的乙個公共子串行。

請注意:空序列是任何兩個序列的公共子串行

例如: 序列1,2,3和序列4,5,6的公共子串行只有空序列。

(3)最長公共子串行

a和b的公共子串行中長度最長的(包含元素最多的)叫做a和b的公共子串行。

仍然用序列1,3,5,4,2,6,8,7和序列1,4,8,6,7,5

它們的最長公共子串行是:

1,4,8,7

1,4,6,7

最長公共子串行的長度是4 。

請注意:最長公共子串行不唯一

請大家用集合的觀點來理解這些概念,子串行、公共子串行以及最長公共子串行都不唯一,所以我們通常說乙個最長公共子串行,但顯然最長公共子串行的長度是一定的。

最長公共子串行問題就是求序列a= a1,a2,……an, 和b = b1,b2,……bm,的乙個最長公共子串行。

因為最長公共子串行不唯一,讓我們把問題簡化,如何求出兩個序列的最長公共子串行長度呢?

你首先能想到的恐怕是暴力列舉?那我們先來看看:序列a有 2^n 個子序列,序列b有 2^m 個子序列,如果任意兩個子串行一一比較,比較的子串行高達 2^(n+m) 對,這還沒有算具體比較的複雜度。

或許你說,只有長度相同的子串行才會真正進行比較。那麼忽略空序列,我們來看看:對於a長度為1的子串行有c(n,1)個,長度為2的子串行有c(n,2)個,……長度為n的子串行有c(n,n)個。對於b也可以做類似分析,即使只對序列a和序列b長度相同的子串行做比較,那麼總的比較次數高達:

c(n,1)c(m,1)*1 + c(n,2) c(m,2) * 2+ …+c(n,p) * c(m,p)*p

其中p = min(m, n)。

嚇著了吧?怎麼辦?試試使用動態規劃演算法

我們用ax表示序列a的連續前x項構成的子串行,即ax= a1,a2,……ax, by= b1,b2,……by, 我們用lcs(x, y)表示它們的最長公共子串行長度,那原問題等價於求lcs(m,n)。為了方便我們用l(x, y)表示ax和by的乙個最長公共子串行。

讓我們來看看如何求lcs(x, y)。我們令x表示子串行考慮最後一項

(1) ax = by

那麼它們l(ax, by)的最後一項一定是這個元素!

為什麼呢?為了方便,我們令t = ax = by, 我們用反證法:假設l(x,y)最後一項不是t,

則要麼l(x,y)為空序列(別忘了這個),要麼l(x,y)的最後一項是aa=bb ≠ t, 且顯然有a < x, b < y。無論是哪種情況我們都可以把t接到這個l(x,y)後面,從而得到乙個更長的公共子串行。矛盾!

如果我們從序列ax中刪掉最後一項ax得到ax-1,從序列by中也刪掉最後一項by得到by-1,(多說一句角標為0時,認為子串行是空序列),則我們從l(x,y)也刪掉最後一項t得到的序列是l(x – 1, y - 1)。為什麼呢?和上面的道理相同,如果得到的序列不是l(x - 1, y - 1),則它一定比l(x - 1, y - 1)短(注意l(,)是個集合!),那麼它後面接上元素t得到的子串行l(x,y)也比l(x - 1, y - 1)接上元素t得到的子串行短,這與l(x, y)是最長公共子串行矛盾。

因此l(x, y) = l(x - 1, y - 1) 最後接上元素t

lcs(ax, by) = lcs(x - 1, y - 1) + 1

(2) ax ≠ by

仍然設t = l(ax, by), 或者l(ax, by)是空序列(這時t是未定義值不等於任何值)。

則t ≠ ax和t ≠ by至少有乙個成立,因為t不能同時等於兩個不同的值嘛!

(2.1) 如果t ≠ ax,則有l(x, y)= l(x - 1, y),因為根本沒ax的事嘛。

lcs(x,y) = lcs(x – 1, y)

(2.2) 如果t ≠ by,l類似l(x, y)= l(x , y - 1)

lcs(x,y) = lcs(x, y – 1)

可是,我們事先並不知道t,由定義,我們取最大的乙個,因此這種情況下,有lcs(x,y) = max(lcs(x – 1, y) , lcs(x, y – 1))。

看看目前我們已經得到了什麼結論:

lcs(x,y) =

(1) lcs(x - 1,y - 1) + 1 如果ax = by

(2) max(lcs(x – 1, y) , lcs(x, y – 1)) 如果ax ≠ by**

這時乙個顯然的遞推式,光有遞推可不行,初值是什麼呢?

顯然,乙個空序列和任何序列的最長公共子串行都是空序列!所以我們有:

lcs(x,y) =

(1) lcs(x - 1,y - 1) + 1 如果ax = by

(2) max(lcs(x – 1, y) , lcs(x, y – 1)) 如果ax ≠ by

(3) 0 如果x = 0或者y = 0

到此我們求出了計算最長公共子串行長度的遞推公式。我們實際上計算了乙個(n + 1)行(m + 1)列的**(行是0..n,列是0..m),也就這個二維度陣列lcs(,)。

現在問題來了,我們如何得到乙個最長公共子串行而僅僅不是簡單的長度呢?其實我們離真正的答案只有一步之遙!

仍然考慮那個遞推式,我們lcs(x,y)的值**的三種情況:

注意(2.1)和(2.2) ,當lcs(x – 1, y) = lcs(x, y – 1)時,其實走哪個分支都一樣,雖然長度時一樣的,但是可能對應不同的子串行,所以最長公共子串行並不唯一。

神奇吧?又乙個類似的遞推公式。可見我們在計算長度lcs(x,y)的時候只要多記錄一些資訊,就可以利用這些資訊恢復出乙個最長公共子串行來。就好比我們在迷宮裡走路,走到每個位置的時候記錄下我們時從哪個方向來的,就可以從終點回到起點一樣。

另外,說一下複雜度?

時間複雜度時o(n * m),空間也是o(n * m)

package com.csk.algs;

/** * created by c49s09k_01 on 2018/4/10.

* * lcs:最長公共子串行

* * s(i,j) = 1 若 ai == bj

* s(i,j) = 2 若 ai != bj,且 l(i - 1, j) >= l(i, j - 1)

* s(i,j) = 3 若 ai != bj,且 l(i - 1, j) < l(i, j - 1)

*/public

class

lcs

else

if(l[i - 1][j] >= l[i][j - 1])

else}}

i = alen;

j = blen;

k = l[alen][blen];

char res = new

char[k];

while((i != 0) && (j != 0))

case

2 :

case

3 : }}

return

new string(res);

}public

static

void

main(string args)

}

缺點:如最長公共子串行不止乙個的話,只能輸出其中的乙個。

有時間去改-_-

注:參考文章:

LCS 最長公共子串行

問題描述 我們稱序列z z1,z2,zk 是序列x x1,x2,xm 的子串行當且僅當存在嚴格上 公升的序列 i1,i2,ik 使得對 j 1,2,k,有 xij zj。比如z a,b,f,c 是 x a,b,c,f,b,c 的子串行。現在給出兩個序列 x和 y,你的任務是找到 x和 y的最大公共子...

LCS最長公共子串行

求兩個字串的最大公共子串行問題 子串行的定義 若給定序列x 則另一串行z 是x的子串行是指存在乙個嚴格遞增下標序列使得對於所有j 1,2,k有 zj xij。例如,序列z 是序列x 的子序列,相應的遞增下標序列為。分析 用動態規劃做 1.最長公共子串行的結構 事實上,最長公共子串行問題具有最優子結構...

LCS最長公共子串行

lcs是longest common subsequence的縮寫,即最長公共子串行。乙個序列,如果是兩個或多個已知序列的子串行,且是所有子串行中最長的,則為最長公共子串行。複雜度對於一般的lcs問題,都屬於np問題。當數列的量為一定的時,都可以採用動態規劃去解決。解法動態規劃的乙個計算最長公共子串...