轉自:http://blog.csdn.net/dyx404514/article/details/42061017
http://blog.csdn.net/xingyeyongheng/article/details/9310555
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]<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即爲原串的最長迴文子串額長度
}
manacher算法:
定義數組p[i]表示以i爲中心的(包含i這個字符)迴文串半徑長
將字符串s從前掃到後for(int i=0;i<strlen(s);++i)來計算p[i],則最大的p[i]就是最長迴文串長度,則問題是如何去求p[i]?
由於s是從前掃到後的,所以需要計算p[i]時一定已經計算好了p[1]....p[i-1]
假設現在掃描到了i+k這個位置,現在需要計算p[i+k]
定義maxlen是i+k位置前所有迴文串中能延伸到的最右端的位置,即maxlen=p[i]+i;//p[i]+i表示最大的
分兩種情況:
1.i+k這個位置不在前面的任何迴文串中,即i+k>maxlen,則初始化p[i+k]=1;//本身是迴文串
然後p[i+k]左右延伸,即while(s[i+k+p[i+k]] == s[i+k-p[i+k]])++p[i+k]
2.i+k這個位置被前面以位置i爲中心的迴文串包含,即maxlen>i+k
這樣的話p[i+k]就不是從1開始
由於迴文串的性質,可知i+k這個位置關於i與i-k對稱,
所以p[i+k]分爲以下3種情況得出
//黑色是i的迴文串範圍,藍色是i-k的迴文串範圍,