您的位置:首页 > 其它

Manacher算法求最长回文子串

2016-07-31 20:54 295 查看
利用Manacher算法求最长回文子串可以使算法时间复杂度变为O(n),下面介绍Manacher算法主要思想:Manacher算法首先对字符串做一个预处理:1.在最左最右分别插入一个特殊字符,这样可以让我们不用考虑越界问题;2.在每个字符的左边加入一个‘#’,这样可以让我们不用考虑字符串长度的奇偶性。如字符串“abbaabccba”经过预处理后变成“$#a#b#b#a#a#b#c#c#b#a#^”。算法中用mx记录当前最大回文子串的右边界,id记录最大回文子串的中心,P[i]表示已i为中心的回文子串向左或向右的长度,该长度包含了位置i所在的字符。上述字符串对应的P[i]值如下:



不难知道处理后的字符串的回文子串左右必然以#结束:此时存在两种情况:1.中心为#,P[i]对应的回文子串的左边部分必然有#数目等于非#数目,即在原字符串中的回文子串长度应该为(P[i]-1)/2*2=P[i]-1;2.中心为非#字符,则P[i]对应的回文子串的左边部分#字符必然比非#字符多1,即在原字符串中的回文子串长度应该为(P[I]-1-1)/2*2+1=P[i]-1。所以当程序中得到了所有的P[i]后,找出其中最大的P[i]记为max_len,此时的中心i记为center,则最长回文子串的左边界为center-max_len+1,而由上面分析可知最左边界必然为#,所以原字符串中最长回文子串的第一个字符在处理后的字符串中的位置是center-max_len+1+1,又观察可知处理后字符的位置indexLast和该字符在原字符串的位置indexInit的关系为indexInit=indexInit/2-1,所以原字符串中最长回文子串的第一个字符位置为left=(center-max_len+1+1)/2-1;,又已知该回文子串长度为max_len-1,记原字符串为str,则最长回文子串为str.substr(left,max_len-1)。

由上述分析可知,程序的关键是求所有的P[i],代码如下:

int mirror_i=2*id-i;  //i关于id的对称位置
if (i<mx)
{
if (P[mirror_i]<mx-i)
{
P[i]=P[mirror_i];
}
else
P[i]=mx-i;
}
else
P[i]=1;
while(tmpStr[i-P[i]]==tmpStr[i+P[i]])
++P[i];
if (i+P[i]-1>mx)
{
mx=P[i]+i-1;
id=i;
}

manacher算法主要利用了回文串关于中心对称的性质,详见下图:

当 mx - i > P[j] 的时候,以S[j]为中心的回文子串包含在以S[id]为中心的回文子串中,由于 i 和 j 对称,以S[i]为中心的回文子串必然包含在以S[id]为中心的回文子串中,所以必有 P[i] = P[j],见下图。



当 P[j] > mx - i 的时候,以S[j]为中心的回文子串不完全包含于以S[id]为中心的回文子串中,但是基于对称性可知,下图中两个绿框所包围的部分是相同的,也就是说以S[i]为中心的回文子串,其向右至少会扩张到mx的位置,也就是说 P[i] >= mx - i。至于mx之后的部分是否对称,就只能一个一个匹配了。



对于 mx <= i 的情况,无法对 P[i]做更多的假设,只能P[i] = 1,然后再去匹配了。

完整程序如下:

//Manacher算法
#include "stdafx.h"
#include <iostream>
#include <string>
using namespace std;
class Solution {
public:
string longestPalindrome(string s) {
string tmpStr="$";
for (int i=0;i<s.size();++i)
{
tmpStr+='#'+s.substr(i,1);
}
tmpStr+="#^";
int mx=0;//最大回文子串右边界
int id=0;//最大回文子串的中心
int *P=new int[tmpStr.size()-1];//P[i]代表中心为i时,回文子串向左或向右扩张的长度,包括tmpStr[i]。
fill_n(P,tmpStr.size()-1,0);
for (int i=1;i<tmpStr.size()-1;++i)
{
int mirror_i=2*id-i;  //i关于id的对称位置
if (i<mx)
{
if (P[mirror_i]<mx-i)
{
P[i]=P[mirror_i];
}
else
P[i]=mx-i;
}
else
P[i]=1;
while(tmpStr[i-P[i]]==tmpStr[i+P[i]])
++P[i];
if (i+P[i]-1>mx)
{
mx=P[i]+i-1;
id=i;
}
}
int max_len=0;
int center=0;
for (int i=1;i<tmpStr.size()-1;++i)
{
if (P[i]>max_len)
{
max_len=P[i];
center=i;
}
}

int left=(center-max_len+1+1)/2-1;
return s.substr(left,max_len-1);
}
};

int _tmain(int argc, _TCHAR* argv[])
{
string input;
Solution sln;
while(cin>>input)
{
cout<<"最长回文子串为:"<<sln.longestPalindrome(input)<<endl;
}
return 0;
}
输入字符串abbaabccba,运行结果如下:

内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息