码迷,mamicode.com
首页 > 编程语言 > 详细

Manacher 算法讲解 O(N)复杂度的 最长回文子串求解

时间:2015-08-29 15:30:18      阅读:210      评论:0      收藏:0      [点我收藏+]

标签:算法   回文串   manacher   

求解最长回文子串的方法很多,有几种常见的O(N^2)的最长回文子串求解方法,比如说枚举中心位置向两边扩展,动态规划等,大部分朋友应该都比较熟悉。

Manacher算法相比于上面两种方法,时间复杂度是O(N),空间复杂度也是O(N),可以说是快速求解决回文子串的利器。下面介绍这一算法的思想,以及在文末给与它的实现。

我们以字符串 "ACBCABBB"为例(只考虑长度为奇数的回文串,待会会说明为何这么做),这里引入回文半径的概念,回文半径是指以该字符为中心的回文串的半径,比如ABA,B字符的回文半径为2,Manacher算法的关键点是利用了之前的计算结果,(KMP算法同样也是巧妙的运用了之前的计算结果,才有了高效的匹配速度), 比如说我计算到了第三个字符,也就是B为中心的最长回文串是ACBCA,其回文半径为3,那么我计算以下一个字符C为中心的最长回文串时,因为C处在B的回文半径之内, 我们已经知道C关于B字符的对称位置的C‘的回文半径了,C‘的回文半径为1,被包裹在了B的回文半径内部,因此以C为中心的回文串就不用计算了,其回文半径必定也是为1。

那么当我计算下一个字符A的中心的回文串时,同样的做法,先看A是否被包含在B的回文半径之内,然而并没有....两者相交了,因此A字符必须一步步计算其回文半径。

还有一种情况,比如说字符串DBCBDBCBHAHA,在计算第七个字符C为中心的回文半径时,C被包裹在D的回文半径内,因此我们知道C关于D字符C‘的回文半径了,但是C‘的回文半径为3,B的回文半径并没有把C‘的回文串全部包含在内,因此C的回文半径还是得重新计算,但是这里依然存在着计算优化,我们知道C的回文半径至少为2,之后的计算可以从2开始计算(根据对称位置C‘的回文半径得来)


算法基本思想就是这样,实际计算过程中我需要创建3个变量,分别是rArr[ lenOfString ],index,iRadius,这三个变量的含义如下:

rArr数组是存储每一位字符的回文半径

index,存储的是当前具有最右位置的回文串的字符的下标(最右位置看下边的解释)

iRadius,存储的是index下标的字符的回文半径

关于最右位置的解释,index和iRadius是配套使用的,所谓的最右位置是指index+iRadus-1的值最大(这个值恰好是index下标的字符为中心的回文串的最右边的字符的下标),

最右位置是需要不断更新的,因为这就是Manacher算法就是需要它来判断当前字符是否需要被重新计算。


回到上面的问题来,回文串有两种情况,一种是奇数,一种是偶数,显然Manacher对于是奇数的情况很好处理,那么如果最长回文串是偶数该如何处理呢?答案就是让它变为奇数,是否有种数学的思想在里面,将一个问题转化为一个已经解决了的问题。

因此,我们需要对字符串进行预处理,预处理的方法就是给它填充某种特定字符,比如字符串ABBA,先预处理成Manacher字符串,我给它填充#号,就变成了#A#B#B#A#,这样就可以保证,不管原先的字符串的最长回文子串是奇数还是偶数,填充了字符后,都将变成一个奇数的回文子串了。(假设原先的回文子串长度为N,对于长度为N的字符串将会给它填充N+1个字符,则它的长度变成了2N+1,即变成了奇数)。

至此,Manacher算法介绍完了。下面是它的实现代码,仅供参考:


//Manacher
string getManacherString(string& str){    /*预处理字符串*/
	string res="#";
	int i,len=str.length();
	for(i=0;i<len;i++)
	{
		res+=str[i];
		res+="#";
	}
	return res;
}
int MaxBackSubString(string src){
	int i,len=src.length();
	if(len<2)
		return len;
	string  str=getManacherString(src);
	len=str.length();
	int max=0;
	int* pArr=new int[len];
	int index=0,pRadius=0;
	for(int i=0;i<len;i++){
		if(i>=index+pRadius-1){     /*如果当前字符未被包裹在最右位置之内(如相交时),则需要一步步计算其回文半径*/
			int temp=1;
			while(i+temp<len&&i-temp>=0){
				if(str[i+temp]==str[i-temp])
					temp++;
				else
					break;
			}
			index=i;
			pRadius=temp;
			pArr[i]=pRadius;
			if(pRadius>max)
				max=pRadius;
		}
		else
		{
			int otherPos=index-(i-index);
			if(otherPos-pArr[otherPos]+1>index-pRadius+1)   /*如果其对称位置被包裹在index字符的回文半径之内,则无需计算了*/
			{
				pArr[i]=pArr[otherPos];
				continue;
			}
			else
			{
				int temp=otherPos-(index-pRadius+1)+1;      /*获取当前字符已经匹配的回文半径,从该回文半径开始计算它的最终回文半径*/
				while(i-temp>=0&&i+temp<len){
					if(str[i-temp]==str[i+temp])
						temp++;
					else
						break;
				}
				pRadius=temp;
				index=i;
				pArr[i]=pRadius;
				if(pRadius>max)
					max=pRadius;
			}
		}
	}
	delete[] pArr;
	return (2*max-1)/2;   /*因为填充了特殊字符的原因*/
}




int main(int arg1,char** arg2){
	/*
	char str1[]="today is sunny and all is right";
	char str2[]=" al";
	char* res=strstr(str1,str2);
	if(res!=NULL)
		cout<<res<<endl;
	else
		cout<<"res is null"<<endl;
		*/

	string str="caccccacd";
	int len=MaxBackSubString(str);
	cout<<len<<endl;
	getchar();
}


若有疑问或是发现错误,欢迎留言。


版权声明:本文为博主原创文章,未经博主允许不得转载。

Manacher 算法讲解 O(N)复杂度的 最长回文子串求解

标签:算法   回文串   manacher   

原文地址:http://blog.csdn.net/u011408355/article/details/48068549

(0)
(0)
   
举报
评论 一句话评论(0
登录后才能评论!
© 2014 mamicode.com 版权所有  联系我们:gaon5@hotmail.com
迷上了代码!