2018 Summer Manacher(马拉车)算法 C和C++版

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/u011469138/article/details/82431327

最长回文子串

如果给你一个字符串abcbcbd,这个字符串里所包含的最长的回文子串是什么呢?毫无疑问,是bcbcb,它的长为6,
我们给出一个暴力简洁的算法:遍历整个字符串,对于每一个字符,都向左和右边找相等的字符,每次更新最大值,直到遍历完成。这样的算法看似简单,但是会遇到一个问题:对于奇数长度的回文串好处理,但对于偶数长度的回文串就比较麻烦。同时,这种算法的最坏复杂度为n^2,我们需要更加高效的算法。于是,“马拉车算法”,也就是Manacher算法就诞生了。

Manacher算法

在计算机科学中,最长回文子串或最长对称因子问题是在一个字符串中查找一个最长的连续的回文的子串,例如“banana”最长回文子串是“anana”。最长回文子串并不一定是唯一的,比如“abracadabra”,没有超过3的回文子串,但是有两个回文字串长度都是3:“ada”和“aca”。在一些应用中,我们求出全部的极大回文子串(不被其他回文串包含的回文子串)。

Manacher发现了一种线性时间算法,可以在列出给定字符串中从任意位置开始的所有回文子串。并且,Apostolico, Breslauer & Galil发现,同样的算法也可以在任意位置查找全部极大回文子串,并且时间复杂度是线性的。因此,他们提供了一种时间复杂度为线性的最长回文子串解法。另外,Jeuring (1994)[3], Gusfield (1997)发现了基于后缀树的算法。也存在已知的高效并行算法。
Manacher算法的算法原理,是在每两个字符之间,都插入#号,这样每个字符串都会变成奇数长度的字符串,同时,要在首位插入无关字符$,防止#和开头的#配对

abcbcbd
$#a#b#c#b#c#b#d#

对于每一个字符i,我们先判断它是否已经被包括在前面已经计算能包括住i的整个最长回文串里面,如果在里面,那么以它关于前一个最长回文串的中点id对称的点j为中心的最长回文长度应该已经计算出来了,如果以那个j为中心的的最长回文串已经包括在在计算好的最长回文串里面,那么我们有p[id-(i-id)]=p[i] 也就是p[j]=p[i]这段话看起来很拗口,但这也是manacher算法的重点
这里写图片描述
还有一种情况,也就是以j为中心的的最长回文串已经超出在以id为中心的的最长回文串,我们就只能选取j的回文串左右两边短的不分,因为超出的部分是我们不知道的,剩下的部分就只能放到后面继续计算
这里写图片描述
我讲解的不好,可以参见下面大佬的博客
http://www.cnblogs.com/grandyang/p/4475985.html
我的主要是给出c和c++的模板,推荐大家使用c版本的,在一些题目中,c++版的容易超时

C

#include<cstring>
#include<cstdio>
#include<algorithm>
#include<iostream>
#include<cstdlib>
using namespace std;
char p[210000];
char t[210000];
int l[210000];

void manacher()
{   

    int len=strlen(t);
    p[0]='$',p[1]='#';
    int pos=2;
    for(int i=0;i<len;i++)
    {
        p[pos]=t[i];
        p[pos+1]='#';
        pos+=2;
    }
    p[pos]='\0';                          //这行十分重要,不能丢了

    int id=0,resLen=0,rescen=0,mx=0;

    for(int i=1;i<pos;i++)
    {

        l[i]=mx>i?min(l[2*id-i],mx-i):1;  //整个算法的精髓所在
        while(p[l[i]+i]==p[i-l[i]]) ++l[i];
        if(mx-i<l[i])
        {
            mx=i+l[i];
            id=i;
        }
        if(l[i]>resLen)
        {
            resLen=l[i];
            rescen=i;
        }

    }

    /*for(int i=(rescen-resLen)/2;i<resLen-1;i++)  //输出整个最长回文串
            printf("%c",t[i]);
    printf("\n");*/

    printf("%d\n",resLen-1);     //输出最长回文串长度


}
int main()
{

    while(~scanf("%s",t))
    {

        manacher();

    }

    return 0;
}

C++

#include<cstdio>
#include<cstring>
#include<algorithm>
#include<iostream>
#include<vector>
#include<string>
using namespace std;
void Manacher(string s)
{
    string t="$#";
    for(int i=0;i<s.size();i++)
    {

        t+=s[i];
        t+="#";
    }


vector<int > p(t.size(),0);

int mx=0,id=0,resLen=0,resCenter=0;

for(int i=1;i<t.size();i++)
{

        p[i]=mx>i?min(p[2*id-1],mx-1):1;
        while(t[i+p[i]]==t[i-p[i]])++p[i];
        if(mx<i+p[i])
        {
            mx=i+p[i];
            id=i;
        }

        if(resLen<p[i])
        {

            resLen=p[i];
            resCenter=i;

        }

}

    cout<< resLen-1<<endl;

    //return s.substr((resCenter-resLen)/2,resLen-1);
}


int main()
{

    string p;
    while(cin>>p)
    {

        Manacher(p);

    }   

    return 0;
}

最后给一个裸题,可以试试上面两个算法,看看哪个能ac

最长回文

https://vjudge.net/problem/HDU-3068
马拉车的 p[ ] 数组还有几个特性!

p[i]-1 为以 i 为中心的回文长度

p[i]/2 表示回文半径

i%2==0 表示这个位置为字符,i/2-1 表示原字符串的位置

i%2==1 表示为字符中间,这两边的字符在原字符串的位置分别为 i/2-1 和 i/2

猜你喜欢

转载自blog.csdn.net/u011469138/article/details/82431327