1. 程式人生 > >馬拉車演算法(Manacher Algorithm)--用於計算最長迴文子串

馬拉車演算法(Manacher Algorithm)--用於計算最長迴文子串

馬拉車演算法的目標是找到一串字串中的最長迴文子串,優點是時間複雜度為O(n)
現以尋找 “cgbaabgk” 中的最長子迴文串( “gbaabg”)為例進行說明

演算法過程(總共3步):

1.改造字串結構:

字元座標 0 1 2 3 4 5 6 7 8 9 10 11 12 13
char $ # g # b # a # a # b # k #

將原字串"c g b a a b g k"改造成 "$ # c # g # b # a # a # b # g # k # "
改造的原則是,先在每個字母之間插入’#‘號,再在最前面加入’$'符號
(先不用管為什麼這麼改造,多做幾次就能領悟了)


2.計算新字串中,以每個字元為中心的子迴文串半徑:
在這裡插入圖片描述

例如:
‘$’ 為中心的子迴文串就是 " $ “它自己一個字元,那麼迴文串半徑是1;
‘g’ 為中心的子迴文串是”#g#",長度是3,所以以字元’g’為中心的迴文串半徑為2
座標為7的’#'號

為中心的迴文串為"#b#a#a#b#",長度為9,迴文串的半徑是"#a#b#",長度是5;
這裡的迴文串半徑不是單純的迴文串長度除以2,而是從迴文串中心到迴文串最右邊的字元的長度。


3.通過步驟2的表格獲得我們想要的資料:
如果我想知道原來的整個字串"gbaabk"中,最長的迴文串的長度是多少,那就是表格中radius最大數減一:
在這裡插入圖片描述
即5-1=4,這個字串裡面的最長迴文串長度為4

如果我想求出這個字串裡面的最長迴文子串,可以得到這個子串的起點和終點座標:
在這裡插入圖片描述
最長子迴文串為"baab":
起點字元’b’在原字串中的座標:(7-radius[7])/2=(7-5)/2=1
終點字元’b’在原字串中的座標:(7+radius[7]-1)/2-1

=(7+5-2)/2-1=4
通過這兩個座標可以得到原字串中的最長迴文子串"baab"


馬拉車演算法就這三步,那麼如何高效地獲得這個radius一欄的值呢?
在這裡插入圖片描述

如何獲得改造後的字串的迴文半徑:

以上面的字串“$#g#b#a#a#b#k#”為例,馬拉車演算法是從左到右,依次計算各字元的迴文半徑。但是這裡計算不是蠻力法,而是有技巧的。


這裡有三種情況需要討論:
1.當我們已經從左到右計算到7號’#'號的迴文半徑為5時:

在這裡插入圖片描述
由對稱性可知,方框1和方框2中的內容是關於7號’#'字元對稱的,所以8號’a’是關於6號’a’對稱的6號’a’的迴文半徑是2,故8號’a’的迴文半徑也是2。所以我們可以直接認為8號’a’的迴文半徑就是2了,而不用繼續蠻力求它的迴文半徑長度。

2.當我們想求’b’的迴文半徑時,是否也能直接認為它的迴文半徑是4號’b’的迴文半徑2呢?不能。因為可以看到,座標4的’b’的勢力範圍已經接觸到7號’#'的最大勢力範圍了,我們沒有辦法保證10號’b’是否會有更大的迴文半徑
比如這裡如果12號如果不是’k’而是’a’的話,那’b’的迴文半徑就不是2,是4(迴文字串"#a#b#a#“的迴文半徑是4)。
在這裡插入圖片描述
所以我們先假設10號’b’的迴文半徑是2,,然後繼續用蠻力法探究一下10號’b’的迴文半徑到底能擴散到哪裡。
可以看到以10號座標’b’為中心的的迴文串為”#b#",故迴文半徑為2.
在這裡插入圖片描述


然後開始計算座標11’#‘的迴文半徑:
此時11號’#'已經在7號‘#’的勢力範圍之外了,所以不能再借助7號’#‘的迴文半徑來估算11號’#‘的迴文半徑了,只能老老實實的蠻力算。
在這裡插入圖片描述
可以算出以11號’#‘為中心的迴文串為"#"(即就它一個),所以11號’#'的迴文半徑是1.
這樣一個一個算下去就可以得到以每個字元為中心的迴文半徑:
在這裡插入圖片描述
得到一行迴文半徑後,根據第三步的內容就可以得到我們想要的結果(最長半徑是多少、最長迴文子串是什麼)