動態規劃2 最長公共子串行

2021-10-01 21:42:59 字數 4341 閱讀 8990

問題是給定字串x和y,求出兩個當中最長的公共子串行。比如x=abcdef y=acefg,那麼他們的最長公共子串行就是acef。就是求x的所有可能的子字串與y所有的子字串匹配,如果相同,那麼就是乙個公共子串行,然後求最長的乙個。

建議**上面的公開課,講的非常好。本文思路是根據上面的公開課總結實踐的。

我們先看看求乙個字串的所有子串行,有什麼規律。比如給定乙個字串abc,那麼有多少子串行呢?a b c ab ac bc abc還有乙個空,就是2^3。按照上面公開課講的非常通俗易懂,就是字串一共有n個字元,那麼每一位的字元都有兩個狀態----0-不是子串行的一員 1-是子串行的一員。那麼總共的種類就是2^n種。這是最壞的情況,字串中沒有相同的字元,如果有,結果會比這個少。但是計算就是這麼計算的。

了解了上面,那麼我們進一步判斷,如果給定乙個x的子串行x1,那麼怎麼判斷y中是否含有呢?簡單的方法就是從x1的起始位置和y的起始位置開始判斷,如果相同,每個索引加一,直到x1/y的字串判斷結束。如果不匹配,那麼y索引加一,再從x1的開頭開始計算。

這種是窮舉法,肯定能得到結果,但是時間和空間都是最差的。我們先實現看看(窮舉法排列組合可以參考排列組合)

struct

mnode

};mnode* msubstr(string& srcstr, int index, int

sublen)

else

ftpn->substr =srcstr[i];}}

else

if (sublen > 1

)

nsub = msubstr(srcstr, index - 1

, sublen);

prep->pnext =nsub;

while (nsub !=nullptr)}}

return

tmpp;

}bool comps(string& sstr, string&dstr)

for (int j = dindex; j < dstr.size(); j++)

}}

if (sindex == sstr.size() && dindex <=dstr.size())

return

false;}

intmain()

mnode* tmpp =psubstr;

psubstr = tmpp->pnext;

delete

tmpp;}}

char

inchar;

cin >>inchar;

}

根據排列組合的方式獲取所有的子串,然後匹配,可以列出所有的公共子串,按照從長倒短列印。

那麼我們有沒有更好的解法呢?按照上面的公開課,我們可以這樣計算。lcs(x,y)表示計算x和y的最長公共子串。那麼c(i,j)表示當前x的子串從0-i和y的子串從0-j的最長公共子串。那麼推導公式就是,如果x[i]=y[j],那麼c(i,j) = c(i-1, j-1)+1;如果x[i]!=y[j],那麼c(i,j)=max(c(i-1,j),c(i,j-1))。

描述下來就是求x中0-i組成的字串與y中0-j組成的字串的最長公共子串,是有兩部分推導來的

如果x當前索引的字串等於y當前索引的字串,那麼最長公共子串就是i-1與j-1的最長公共子串加上當前的字元。這個很好理解,就是已知前面的最長公共子串,如果當前相等,那麼加上就好了。

如果不相等呢?我們需要分兩步判斷,就是先把i-1加一,算一下當前的最長公共子串,再把j-1加一算一下當前的最長公共子串,然後判斷哪乙個長,就更新哪乙個結果。

上面就是推導公式。

公開課中對其進行了證明

第一條證明,假設z(1,k)表示c(i,j)的最長公共子串,有k個元素,從1-k,當前x(i)==y(j),那麼z(k)==x(i)==y(j)。去掉當前的k,那麼lcs(i-1,j-1)就等於z(1,k-1)。對於這個,我們有乙個問題,就是必須保證z(1,k-1)是lcs(i-1,j-1),不然我們後面的推論就是錯誤的,因為如果不是最長公共子串,那麼後續的加上1也不是。公開課中用了乙個反證法,比如存在w是乙個比z(1,k-1)更長的子串,那麼|w|(用|w|表示字串w的長度)一定大於k-1,那麼把w拼接上x(i)或y(j),那麼|w+x(i)|肯定大於k,這與我們一開始設定的z(1,k)是最長公共子串衝突。所以z(1,k-1)肯定是取出x(i)和y(j)之後剩下的字串的最長公共子串。同樣的方法可以證明第二條定理。

那麼動態規劃的特徵是什麼,對於什麼樣的問題可以使用動態規劃呢?

第一條就是存在最優子結構。也就是乙個問題的最優解,包含了子問題的最優解。如果子問題不是最優解,那麼也不可能是總問題的最優解。

上面的公式最壞的情況是什麼?按照公開課說的,就是一直是第二條規則。因為第一條規則,不需要計算,直接加一,索引各加一;第二條規則是只有乙個索引加一,還需要分別計算兩次。

怎麼優化呢?按照公開課講的,我們可以看一下這個公式有沒有什麼規律,如果是第一條,那麼沒什麼可以省略的,因為已經很簡單了,直接更新資料就好了。如果是第二條呢?舉個例子,我們看一下第二部計算的分解

我們沒有繼續往下分,這裡就可以看出,有重複,兩個[6,5],計算了兩遍,並且還是靠近樹的根的位置,就導致重複計算的內容更多,那麼如何優化呢?這裡大家估計都想到了,就是把乙個[6,5]記錄下來,到下乙個計算的時候,判斷一下,如果有,就不再重複計算了,這樣就用空間替換了時間,並且是非常可觀的,因為記錄結果花不了多少空間,但是確實能節省很多時間。

這裡就引出了動態規劃的第二個特徵,重疊子問題。就是我們在計算的過程中,因為分解問題導致會出現重複的子問題,我們可以使用備忘法,就是上面說的,記錄一下這個問題的答案,下次碰到直接獲取就好了。

但是這樣並沒有減少太多的運算,因為什麼呢?主要還是因為第二個規則,如果x(i)!=y(j),因為我們僅僅能利用i和j相同時的重複計算,但是並不能嚴格使用i-1和j-1的結果。比如x=abc123kmnp,y=kmnpabc456,那麼最長公共子串是kmnp,當我們計算到x和y的c的下一位時,所得的子串並不能通過前面的abc來判斷,因為隨著不斷的遍歷,最長公共子串是kmnp而不是abc,並且求kmnp與abc沒有任何關係。

這個方法就不實現了,每一小步都是上面的乙個窮舉法,只不過如果碰到一樣的可以減少一次計算。

公開課最後給出了這個問題的最終方法。這個方法我在看一些演算法題題解的時候有人用到,當時大家都驚為天人,原來一切都還是出自於國外,可惜沒有乙個作者給其他人指出這種思路出自**,如何使用,都自己默默的接受了別人的讚許。

上面是給定的乙個例子求abcbdab與bdcaba的最長公共子串行。有多個解,比如bcba和bdab。看上面的圖,我們把兩個字串用乙個二維陣列表示,每個元素是x(i)與y(j)的對應關係。陣列中灰色的是預設填充0,表示的是,x和y字串對應空字串的時候,那麼肯定都是沒有相同的元素。然後我們一行一行的比較(每行顏色我用了淡橙色和淡藍色區分),第一行對於x(0)和y(0),我們知道a與b不相等,所以他們的相同的子串的個數是0,也就是最長公共字序列是0,也就是空字串。x(1)與y(0)比較發現b與b是相等的,所以就在上一次計算得出的公共子串行基礎上加1(這樣我們前面的每次計算都是有用的,沒有額外的開銷),到了x(2)和y(0),因為c與b不相等,所以當前就等於前面已經求得的公共子串行,也就是1.這裡也用到了上面的推導公式,如果相同就加1,不相同就等於lcs(x(i-1),y(j-1))分別加上i和j,求得的兩個公共子串行的最大值。那好,到了x(3)和y(0),這時有乙個b和b相等,為什麼還是1呢?根據公式推導如果x(i)==y(j),那麼當前的最長公共子串等於lcs(x(i-1),y(j-1))+1,因為要退兩個,也就是x(2)和y(-1),是0,所以lcs(x(3),y(0))還是1.從圖示中可以看出,如果相等,那麼就是左上角頂格的數字加一,如果不等,就是前一格或是上一格的最大值。前一格就是lcs(x(i-1), y(j)),上一格就是lcs(x(i), y(j-1));同樣相等的話就是左上角的加一,左上角就是lcs(x(i-1),y(j-1))。真是完美的解決方案,非常形象,也很好理解,也很好記。把數學和幾何完美的結合在一起。那麼最長公共子串怎麼求呢?很簡單,一行一行的掃瞄或是一排一排的掃瞄,在上一次結果所在的i,j位置往後,第一次數字增加的地方就是公共子串的位置,為了求最長的公共子串,我們可以得到最大的數值,然後逆向操作,第一次數字減少的地方就是公共子串的元素。

void

lcs()

else

}else

else

if (i > 0

)

else

if (j > 0

)

}if (*(lcsmap + a.size() * i + j) >maxnum)}}

string

slcs;

for (int j = maxj; j >= 0; j--)}}

cout

<}

動態規劃2 最長公共子串行

最長公共子串行1 所有的回溯找路徑 記錄之前來的路徑 找兩個字串的最長公共子串行 char s1 maxlen char s2 maxlen 答案為4 遞推公式 回溯路徑 最長公共子串行2 回溯法求路徑 3所有的回溯找路徑 記錄之前來的路徑 41 遞推公式 5 分最後乙個相同和最後乙個不同來分析 6...

動態規劃 最長公共子串行

問題描述 我們稱序列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 longest common length 的 每個字元可以不連續,如x y 那麼它們的最長公共子串行為。這是乙個經典的動態規劃問題,著手點還是找到 最精髓的 狀態轉移方程 假設x,y兩個序列的前i,j個位置的最大子串行已經找到為r i j 自底往上 那麼x i 與y...