求取最长回文字符串,o(n)的最优算法manacher

算法的第一步就是在每个字符的左右都加上一个#,这样有什么效果呢。

比如aba初始化之后为#a#b#a#,字符串长度为7是奇数。

比如1221初始化之后为#1#2#2#1#,字符串长度为9是奇数。

为什么我们要将其转换成奇数呢,因为算法求取回文串长度的时候,需要有一个中心节点,之后分别向左右搜索,所以需要将回文串豆转换为奇数长度。

之后我们需要将str[0]赋值为一个字符,可以赋值为$,不这样做也可以,但是这样做我们就可以从1开始处理字符串,大家知道C++的数组是从0开始的。

算法的第二步就进入了manacher算法的内部。这个算法之中其实就是利用回文串的对称性,做了一个优化,从而使得我们不用取一个一个的取计算。比如说s是一个回文字符串,我们计算其右半部分的一个节点的时候我们发现它复合回文串的特征,那么我们就知道s的左半部分一定也存在一个相同的节点,在s的范围内这个节点左右的元素都是相同的,但是有可能的是这个节点所代表的回文串会超出s字符串的长度,这个是一定会出现的。但是能省一部分工作就省一部分。


算法中有一个int型的数组,比如我们定义为 int p[200]; 那么p[i]的意思就是代表i节点左右回文字符串的长度。

算法中还有两个辅助变量centerID和maxEage,分别代表的是当前最长回文字符串的中心节点和其p[i]的值,也就是回文串的长度。

我感觉算法核心就在p[i] = Min(p[2*centerID-i],p[centerID]+centerID-i);,它之前会判断i是否在回文串中,如果在则继续判断以i为节点的回文串对称点j的p值和maxEage比较取最小的一个,因为只有在回文串中才能保证优化的可行性。如果i不在回文串中,那么将p赋值为1,左右一起搜索求出p值。


下面是源代码,里面还有一些注释,大家可以看一下。

<span style="font-size:14px;">#include<stdio.h>
#include<iostream>
#include<string.h>
#include<algorithm>
using namespace std;

#define Min(x, y) ((x)<(y)?(x):(y))

char inputString[100];//存放输入的原字符串
char testString[200];//存放初始化之后的字符串
int p[200];//存放testString中以每个元素为中心的会问字符串长度
int len;//字符串长度

void manacher()
{
    int i;
    int centerID,maxEage;
    //centerID代表最大回文字符串的中心
    //maxEage代表最大回文字符串的边界长度,也就是说2maxEage - 1就是字符串长度。
    maxEage = 0;
    centerID = 0;
    for (i = 1; i < len; i++)
    {
    		if (maxEage > i)//如果i在最长回文字符串的范围内
    		{
    			p[i] = Min(p[2*centerID-i],p[centerID]+centerID-i);
    			//j = 2*centerID-i为i的对称点
    			//其实比较的就是以i为中心的回文字符串是否在最大回文字符串内部,比较的就是p[j]和i到maxEage的距离。
    		}
    		else
    		{
    			p[i] = 1;//本身也算在内,所以最小长度为1
    		}

    		for(; testString[i+p[i]]==testString[i-p[i]]; p[i]++);

    		if (p[i] + i > maxEage)//如果找到更长的回文字符串,及时替换。
    		{
    			maxEage = p[i] + i;
    			centerID = i;
    		}
    }
    //因为字符串最中间的节点也maxEage内,所以提取左右边界时应该-1
}

void start()
{
	int i;
	len = strlen(inputString);
	testString[0] = '$';
	testString[1] = '#';
	for(i = 0; i < len; i++)
	{
		testString[i*2+2] = inputString[i];
		testString[i*2+3] = '#';
	}
	len = len*2 + 2;
	//cout<<testString<<endl;//可以验证初始化的字符串是否正确
	//cout<<len<<endl;//可以输出初始化字符串的长度
}

int main()
{
	int answer = 0,i,jiedian = 0;
	memset(p,0,sizeof(p));
	memset(testString,0,sizeof(testString));
	cin.getline(inputString,80);
	start();
	manacher();

	for(i=0;i<len;i++)
	{
	    if(p[i] > answer)
         {
            answer = p[i];
            jiedian = i;
         }
	}

    //cout<<jiedian<<endl;//可以定位最长回文字符串的中心点
    //cout<<p[jiedian]<<endl;

    cout<<"最长回文字符串:";
    for(i = jiedian - p[jiedian] + 1; i <= jiedian + p[jiedian] - 1; i++)
    {
        if(testString[i] == '#')
            continue;
        cout<<testString[i];
    }

	cout<<endl<<"字符串长度:"<<answer-1<<endl;
	return 0;
}
</span>




郑重声明:本站内容如果来自互联网及其他传播媒体,其版权均属原媒体及文章作者所有。转载目的在于传递更多信息及用于网络分享,并不代表本站赞同其观点和对其真实性负责,也不构成任何其他建议。