Manacher算法總結

碼頭工人 10年前發布 | 81K 次閱讀 Manacher 算法

Manacher算法

算法總結第三彈 manacher算法,前面講了兩個字符串相算法——kmp和拓展kmp,這次來還是來總結一個字符串算法,manacher算法,我習慣叫他 “馬拉車”算法。

相對于前面介紹的兩個算法,Manacher算法的應用范圍要狹窄得多,但是它的思想和拓展kmp算法有很多共通支出,所以在這里介紹一下。Manacher算法是查找一個字符串的最長回文子串的線性算法。

在介紹算法之前,首先介紹一下什么是回文串,所謂回文串,簡單來說就是正著讀和反著讀都是一樣的字符串,比如abba,noon等等,一個字符串的最長回文子串即為這個字符串的子串中,是回文串的最長的那個。

計 算字符串的最長回文字串最簡單的算法就是枚舉該字符串的每一個子串,并且判斷這個子串是否為回文串,這個算法的時間復雜度為O(n^3)的,顯然無法令人 滿意,稍微優化的一個算法是枚舉回文串的中點,這里要分為兩種情況,一種是回文串長度是奇數的情況,另一種是回文串長度是偶數的情況,枚舉中點再判斷是否 是回文串,這樣能把算法的時間復雜度降為O(n^2),但是當n比較大的時候仍然無法令人滿意,Manacher算法可以在線性時間復雜度內求出一個字符 串的最長回文字串,達到了理論上的下界。

1.Manacher算法原理與實現

下面介紹Manacher算法的原理與步驟。

首先,Manacher算法提供了一種巧妙地辦法,將長度為奇數的回文串和長度為偶數的回文串一起考慮,具體做法是,在原字符串的每個相鄰兩個字符中間插入一個分隔符,同時在首尾也要添加一個分隔符,分隔符的要求是不在原串中出現,一般情況下可以用#號。下面舉一個例子:


(1)Len數組簡介與性質

Manacher算法用一個輔助數組Len[i]表示以字符T[i]為中心的最長回文字串的最右字符到T[i]的長度,比如以T[i]為中心的最長回文字串是T[l,r],那么Len[i]=r-i+1。

對于上面的例子,可以得出Len[i]數組為:



Len 數組有一個性質,那就是Len[i]-1就是該回文子串在原字符串S中的長度,至于證明,首先在轉換得到的字符串T中,所有的回文字串的長度都為奇數,那 么對于以T[i]為中心的最長回文字串,其長度就為2*Len[i]-1,經過觀察可知,T中所有的回文子串,其中分隔符的數量一定比其他字符的數量多 1,也就是有Len[i]個分隔符,剩下Len[i]-1個字符來自原字符串,所以該回文串在原字符串中的長度就為Len[i]-1。

有了這個性質,那么原問題就轉化為求所有的Len[i]。下面介紹如何在線性時間復雜度內求出所有的Len。

(2)Len數組的計算

首先從左往右依次計算Len[i],當計算Len[i]時,Len[j](0<=j<i)已經計算完畢。設P為之前計算中最長回文子串的右端點的最大值,并且設取得這個最大值的位置為po,分兩種情況:

第一種情況:i<=P

那么找到i相對于po的對稱位置,設為j,那么如果Len[j]<P-i,如下圖:



那 么說明以j為中心的回文串一定在以po為中心的回文串的內部,且j和i關于位置po對稱,由回文串的定義可知,一個回文串反過來還是一個回文串,所以以i 為中心的回文串的長度至少和以j為中心的回文串一樣,即Len[i]>=Len[j]。因為Len[j]<P-i,所以說i+Len[j]& lt;P。由對稱性可知Len[i]=Len[j]。

如果Len[j]>=P-i,由對稱性,說明以i為中心的回文串可能會延伸到P之外,而大于P的部分我們還沒有進行匹配,所以要從P+1位置開始一個一個進行匹配,直到發生失配,從而更新P和對應的po以及Len[i]。



第二種情況: i>P

如果i比P還要大,說明對于中點為i的回文串還一點都沒有匹配,這個時候,就只能老老實實地一個一個匹配了,匹配完成后要更新P的位置和對應的po以及Len[i]。



2.時間復雜度分析

Manacher 算法的時間復雜度分析和Z算法類似,因為算法只有遇到還沒有匹配的位置時才進行匹配,已經匹配過的位置不再進行匹配,所以對于T字符串中的每一個位置,只 進行一次匹配,所以Manacher算法的總體時間復雜度為O(n),其中n為T字符串的長度,由于T的長度事實上是S的兩倍,所以時間復雜度依然是線性 的。

下面是算法的實現,注意,為了避免更新P的時候導致越界,我們在字符串T的前增加一個特殊字符,比如說‘$’,所以算法中字符串是從1開始的。

    const int maxn=1000010;  
    char str[maxn];//原字符串  
    char tmp[maxn<<1];//轉換后的字符串  
    int Len[maxn<<1];  
    //轉換原始串  
    int INIT(char *st)  
    {  
        int i,len=strlen(st);  
        tmp[0]='@';//字符串開頭增加一個特殊字符,防止越界  
        for(i=1;i<=2*len;i+=2)  
        {  
            tmp[i]='#';  
            tmp[i+1]=st[i/2];  
        }  
        tmp[2*len+1]='#';  
        tmp[2*len+2]='$';//字符串結尾加一個字符,防止越界  
        tmp[2*len+3]=0;  
        return 2*len+1;//返回轉換字符串的長度  
    }  
    //Manacher算法計算過程  
    int MANACHER(char *st,int len)  
    {  
         int mx=0,ans=0,po=0;//mx即為當前計算回文串最右邊字符的最大值  
         for(int i=1;i<=len;i++)  
         {  
             if(mx>i)  
             Len[i]=min(mx-i,Len[2*po-i]);//在Len[j]和mx-i中取個小  
             else  
             Len[i]=1;//如果i>=mx,要從頭開始匹配  
             while(st[i-Len[i]]==st[i+Len[i]])  
             Len[i]++;  
             if(Len[i]+i>mx)//若新計算的回文串右端點位置大于mx,要更新po和mx的值  
             {  
                 mx=Len[i]+i;  
                 po=i;  
             }  
             ans=max(ans,Len[i]);  
         }  
         return ans-1;//返回Len[i]中的最大值-1即為原串的最長回文子串額長度   
      }  
來自:http://blog.csdn.net/dyx404514/article/details/42061017

 本文由用戶 碼頭工人 自行上傳分享,僅供網友學習交流。所有權歸原作者,若您的權利被侵害,請聯系管理員。
 轉載本站原創文章,請注明出處,并保留原始鏈接、圖片水印。
 本站是一個以用戶分享為主的開源技術平臺,歡迎各類分享!