計算字符串的最長回文子串 :Manacher算法介紹


轉自: http://www.open-open.com/lib/view/open1419150233417.html 

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,如下圖:

可以得到T[j-(p-i),j+(p-i)]=T[i-(p-i),p],如果Len[j]<p-i ,那么以j為回文中心的右界點一定在T[j-(p-i),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開始的。

public static int subPalindrome(String s)
    {
        int sLen=s.length();

        StringBuffer sb=new StringBuffer();

        for(int i=0;i<sLen;i++)
        {
            sb.append("#");
            sb.append(s.charAt(i));
        }
        sb.append("#");

        char[] chs=sb.toString().toCharArray();
        int[] len=new int[chs.length];
        len[0]=1;//初始時每個字符的回文子串長度為0(元素自身)
        int p=0;//(遍歷時到達的最右邊界的位置)
        int p0=0;//p所對應的回文串的中間位置
        int ans=0; //結果

        for(int i=1;i<chs.length;i++) {
            len[i]=1;
            int j = p0 - (i - p0); //i以p0為中心,對稱的j點


            if (i < p) { //i在最右邊界的左邊.可以利用之前的計算
                if (len[j] < p - i) {  //i的對稱點j,所對應的回文長度小於p-i,說明以i為中心的回文右邊界一定在j和po之間,所以len[i]=len[j]
                    len[i] = len[j];
                } else { //len[j]相對應的回文已經超出了po為中心的左邊界,可能len[i]的情況和len[j]不同,可能超出p的右邊界,所以需要計算
                    while (i-len[i]>=0 && i+len[i]<chs.length && (chs[i - len[i]] == chs[i + len[i]])) {
                        len[i]=len[i]+1;
                    }
                    if (len[i] + i > p) {
                        p = len[i] + i;
                        p0 = i;
                    }
                }
            } else {//i直接在p的右邊, 需要計算
                while (i-len[i]>=0 && i+len[i]<chs.length && chs[i - len[i]] == chs[i + len[i]] ) {
                    len[i]=len[i]+1;
                }
                if (len[i] + i > p) {
                    p = len[i] + i;
                    p0 = i;
                }
            }

            if (ans < len[i])
                ans = len[i]-1;
        }
        return ans;
    }

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM