一些概念:
(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,4,5也是它們的乙個公共子串行。
請注意: 空序列是任何兩個序列的公共子串行。
例如: 序列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(,)。
大概的偽**如下:
輸入序列a, b長度分別為n,m,計算二維表 lcs(int,int):
1注意: 我們這裡使用了迴圈計算**裡的元素值,而不是遞迴,如果使用遞迴需要已經記錄計算過的元素,防止子問題被重複計算。for x = 0
to n do
2for y = 0
to m do
3if (x == 0 || y == 0) then
4 lcs(x, y) = 0
5else
if (ax == by) then
6 lcs(x, y) = lcs(x - 1,y - 1) + 1
7else
8 lcs(x, y) = ) max(lcs(x – 1, y) , lcs(x, y – 1))9
endif
10endfor
11 endfor
現在問題來了,我們如何得到乙個最長公共子串行而僅僅不是簡單的長度呢?其實我們離真正的答案只有一步之遙!
(1) lcs(x – 1, y – 1) + 1如果ax = by
這對應l(x,y) = l(x,- 1 y- 1)末尾接上ax
(2.1) lcs(x – 1, y) 如果ax ≠ by且lcs(x – 1, y) ≥lcs(x, y – 1)
這對應l(x,y)= l(x – 1, y)
(2.2) lcs(x, y – 1) 如果ax ≠ by且lcs(x – 1, y)
(3) 0 如果 x =0或者y = 0
這對應l(x,y)=空序列
注意(2.1)和(2.2) ,當lcs(x – 1, y) = lcs(x, y – 1)時,其實走哪個分支都一樣,雖然長度時一樣的,但是可能對應不同的子串行,所以最長公共子串行並不唯一。
神奇吧?又乙個類似的遞推公式。可見我們在計算長度lcs(x,y)的時候只要多記錄一些資訊,就可以利用這些資訊恢復出乙個最長公共子串行來。就好比我們在迷宮裡走路,走到每個位置的時候記錄下我們時從哪個方向來的,就可以從終點回到起點一樣。
另外,說一下複雜度?
時間複雜度時o(n * m),空間也是o(n * m)
今天對lcs的講解就到這裡,聰明的你是不是已經蠢蠢欲動要ac問題啦? 心動不如行動,趕快吧。
最後,我們來提供輸入輸出資料,由你來寫一段程式,實現這個演算法,只有寫出了正確的程式,才能繼續後面的課程。
輸入
第1行:字串a輸出第2行:字串b
(a,b的長度 <= 1000)
輸出最長的子串行,如果有多個,隨意輸出1個。輸入示例
abcicba輸出示例abdkscab
abca請選取你熟悉的語言,並在下面的**框中完成你的程式,注意資料範圍,最終結果會造成int32溢位,這樣會輸出錯誤的答案。
不同語言如何處理輸入輸出,請檢視下面的語言說明。
動態規劃 最長公共子串行問題
最長公共子串行問題 longest common subsequence problem 簡稱lcs問題。題目為給定兩個序列x y求它們的lcs 最長公共子串行 這裡的子串行z的定義為 z中的元素既在x中也在y中,並且他們在x y中滿足嚴格的下標為乙個增序列 假設下標從左到右依次增大 另外,不要求z...
最長公共子串行問題 動態規劃
給定兩個字串s1s2.sn和t1t2.tn。求出這兩個字串最長的公共子串行 輸入 abcicba abdkscab 輸出 abca 定義dp i j 為s1 si和t1 tj對應的lcs的長度 s1 si 1和t1 tj 1對應的公共子列有三種情況 當si 1 tj 1時,在s1 si和t1 tj的...
最長公共子串行問題(動態規劃)
time limit 1000ms memory limit 65536kb problem description 給定兩個序列x input 輸入資料有多組,每組有兩行 每行為乙個長度不超過500的字串 輸入全是大寫英文本母 a,z 表示序列x和y。output 每組輸出一行,表示所求得的最長公...