淺談Manacher算法

轉自:點擊打開鏈接


manacher算法是我在網上無意中找到的,主要是用來求某個字符串的最長迴文子串.

不過網上的版本還不太成熟,我就修改了下.
不要被manacher這個名字嚇倒了,其實manacher算法很簡單,也很容易理解,程序短,時間複雜度爲O(n).
求最長迴文子串這個問題,我聽說有個分治+拓展kmp的算法,不過我估計後綴數組也可以.
但殺雞豈能用牛刀?

現在進入正題:
首先,在字符串s中,用rad[i]表示第i個字符的迴文半徑,即rad[i]儘可能大,且滿足:
s[i-rad[i],i-1]=s[i+1,i+rad[i]]
很明顯,求出了所有的rad,就求出了所有的長度爲奇數的迴文子串.
至於偶數的怎麼求,最後再講.
假設現在求出了rad[1..i-1],現在要求後面的rad值,並且通過前面的操作,得知了當前字符i的rad值至少爲j.現在通過試圖擴大j來掃描,求出了rad[i].再假設現在有個指針k,從1循環到rad[i],試圖通過某些手段來求出[i+1,i+rad[i]]的rad值.
根據定義,黑色的部分是一個迴文子串,兩段紅色的區間全等.
因爲之前已經求出了rad[k],所以直接用它.有3種情況:
淺談manacher算法
①rad[i]-k<rad[i-k]
如圖,rad[i-k]的範圍爲青色.因爲黑色的部分是迴文的,且青色的部分超過了黑色的部分,所以rad[i+k]肯定至少爲rad[i]-k,即橙色的部分.那橙色以外的部分就不是了嗎?這是肯定的.因爲如果橙色以外的部分也是迴文的,那麼根據青色和紅色部分的關係,可以證明黑色部分再往外延伸一點也是一個迴文子串,這肯定不可能,因此rad[i+k]=rad[i]-k.爲了方便下文,這裏的rad[i+k]=rad[i]-k=min(rad[i]-k,rad[i-k]).
淺談manacher算法
②rad[i]-k>rad[i-k]
如圖,rad[i-k]的範圍爲青色.因爲黑色的部分是迴文的,且青色的部分在黑色的部分裏面,根據定義,很容易得出:rad[i+k]=rad[i-k].爲了方便下文,這裏的rad[i+k]=rad[i-k]=min(rad[i]-k,rad[i-k]).

根據上面兩種情況,可以得出結論:當rad[i]-k!=rad[i-k]的時候,rad[i+k]=min(rad[i]-k,rad[i-k]).
注意:當rad[i]-k==rad[i-k]的時候,就不同了,這是第三種情況:
淺談manacher算法
如圖,通過和第一種情況對比之後會發現,因爲青色的部分沒有超出黑色的部分,所以即使橙色的部分全等,也無法像第一種情況一樣引出矛盾,因此橙色的部分是有可能全等的,但是,根據已知的信息,我們不知道橙色的部分是多長,因此就把i指針移到i+k的位置,j=rad[i-k](因爲它的rad值至少爲rad[i-k]),等下次循環的時候再做了.
整個算法就這樣.
至於時間複雜度爲什麼是O(n),我已經證明了,但很難說清楚.所以自己體會吧.
上文還留有一個問題,就是這樣只能算出奇數長度的迴文子串,偶數的就不行.怎麼辦呢?有一種直接但比較笨的方法,就是做兩遍(因爲兩個程序是差不多的,只是rad值的意義和一些下標變了而已).但是寫兩個差不多的程序是很痛苦的,而且容易錯.所以一種比較好的方法就是在原來的串中每兩個字符之間加入一個特殊字符,再做.如:aabbaca,把它變成a#a#b#b#a#c#a,這樣的話,無論原來的迴文子串長度是偶數還是奇數,現在都變成奇數了.
程序:
fin>>ST;
for (int i=0,End=ST.size(); i!=End; i++)
{
 
  s[(i<<1)+1]=ST[i];
   s[(i<<1)+2]='#';
}
s[0]='?';
s[ST.size()<<1]='*';//注意頭,尾和中間插入的字符不同
//前面是初始化
for (int i=1,j=0,k,End=ST.size()<<1;i<End; )
{
    while(s[i-j-1]==s[i+j+1]) j++;  //掃描得出rad值
   rad[i]=j;
    for (k=1;k<=j &&rad[i-k]!=rad[i]-k; k++)rad[i+k]=min(rad[i-k],rad[i]-k);  //k指針掃描
   i+=k;  //i跳到下一個需要計算rad值的位置
   j=max(j-k,0);  //更新下一個rad值的初始值
}
這算法很強吧!去做ural1297把(求最長迴文子串),本人已

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章