Manacher算法

本文主要内容转载自:http://blog.youkuaiyun.com/ggggiqnypgjg/article/details/6645824/

1)以下为转载内容:

这里,我介绍一下On)回文串处理的一种方法。Manacher算法.
原文地址:
http://zhuhongcheng.wordpress.com/2009/08/02/a-simple-linear-time-algorithm-for-finding-longest-palindrome-sub-string/
    其实原文说得是比较清楚的,只是英文的,我这里写一份中文的吧。
    首先:大家都知道什么叫回文串吧,这个算法要解决的就是一个字符串中最长的回文子串有多长。这个算法可以在On)的时间复杂度内既线性时间复杂度的情况下,求出以每个字符为中心的最长回文有多长,
    这个算法有一个很巧妙的地方,它把奇数的回文串和偶数的回文串统一起来考虑了。这一点一直是在做回文串问题中时比较烦的地方。这个算法还有一个很好的地方就是充分利用了字符匹配的特殊性,避免了大量不必要的重复匹配。
    算法大致过程是这样。先在每两个相邻字符中间插入一个分隔符,当然这个分隔符要在原串中没有出现过。一般可以用‘#’分隔。这样就非常巧妙的将奇数长度回文串与偶数长度回文串统一起来考虑了(见下面的一个例子,回文串长度全为奇数了),然后用一个辅助数组P记录以每个字符为中心的最长回文串的信息。Pid]记录的是以字符strid]为中心的最长回文串,当以strid]为第一个字符,这个最长回文串向右延伸了Pid]个字符。
    原串:    w aa bwsw f d
    新串:   # w# a # a # b# w # s # w # f # d #
辅助数组P:  1 2 1 2 3 2 1 2 1 2 1 4 1 2 1 2 1 2 1
    这里有一个很好的性质,Pid-1就是该回文子串在原串中的长度(包括‘#’)。如果这里不是特别清楚,可以自己拿出纸来画一画,自己体会体会。当然这里可能每个人写法不尽相同,不过我想大致思路应该是一样的吧。
    好,我们继续。现在的关键问题就在于怎么在On)时间复杂度内求出P数组了。只要把这个P数组求出来,最长回文子串就可以直接扫一遍得出来了。
    由于这个算法是线性从前往后扫的。那么当我们准备求Pi]的时候,i以前的Pj]我们是已经得到了的。我们用mx记在i之前的回文串中,延伸至最右端的位置。同时用id这个变量记下取得这个最优mx时的id值。(注:为了防止字符比较的时候越界,我在这个加了‘#’的字符串之前还加了另一个特殊字符‘$’,故我的新串下标是从1开始的)
好,到这里,我们可以先贴一份代码了。

void pk()
{
    int i;
    int mx = 0;
    int id;
    for(i=1; i<n; i++)
    {
        if( mx > i )
            p[i] = MIN( p[2*id-i], mx-i );        
        else
            p[i] = 1;
        for(; str[i+p[i]] == str[i-p[i]]; p[i]++)
            ;
        if( p[i] + i > mx )
        {
            mx = p[i] + i;
            id = i;
        }
    }
}

   代码是不是很短啊,而且相当好写。很方便吧,还记得我上面说的这个算法避免了很多不必要的重复匹配吧。这是什么意思呢,其实这就是一句代码。

if (  mx  >  i )
    p [ i ] = MIN (  p [ 2 * id - i ],  mx - i );

就是当前面比较的最远长度mx>i的时候,Pi]有一个最小值。这个算法的核心思想就在这里,为什么P数组满足这样一个性质呢?
   (下面的部分为图片形式)




2)这篇博客讲解的很透彻,本文只是在其基础上写了一个完整运行的代码。

#include<iostream>
#include<string>
#include<cstring>
#include<vector>
using namespace std;
int main()
{
	string old_str="waabwswfd";
	int old_len=old_str.length();
	
	//构造新串,首位和每两个相邻字符之间插入一个分隔符,这里用#
	int len=2*old_len+2;
	string str(len-1,'#');
 	for(int i=0;i<old_len;++i){
		str[2*i+1]=old_str[i]; 	
 	}
    str = '$'+str +='\0';//开始位置插入一个$,防止越界 
    
    int max_pos=0;//最长回文串中心位置 
    //求以每个字符为中心的回文串长度
	vector<int> p(len,1);
    int mx=0,pos;//mx为以str[pos]为中心时,回文串最远向右延伸到str[mx] 
	for(int i=1;i<len;++i){
    	if(mx>i){
	    	p[i]=min(p[2*pos-i],mx-i);
	    	//2*pos-i为i关于pos对称的在pos左面的位置
			/*
			这个算式基于以下的原理: 
				(1)若i>j,则当我们计算到p[i]时,p[j]的值我们已经算完。 
				(2)若以str[i]为中心(用以str[2*pos-i]为中心来替代,因为已经计算过),回文串向右最远扩展了k个字符,那么
				(2-1)若str[i]+k<mx,即上一次计算以str[pos]为中心的最长回文串时,已经包含了已str[i]为中心的回文串,则p[i]=p[2*pos-i]
				(2-2)若str[i]+k>mx,那么对于str[i]+k大于mx那一部分,我们不确定它是否仍然匹配,所以只能令p[i]=mx-i,因为这段我们知道是回文串。 
			*/
	    }
    	//逐个匹配 
    	while(str[i+p[i]]==str[i-p[i]]){
	    	p[i]++;
	    }
	    
    	if(i+p[i]>mx){	//更新向右最远匹配位置 
	    	mx=p[i]+i;
	    	pos=i;
	    }
	    max_pos=p[i]>p[max_pos]?i:max_pos;
    }
    //准备求最长回文串 
    int max_len=p[max_pos]-1;
    int start=(max_pos-p[max_pos])/2;
    string ans=old_str.substr(start,max_len);
    
    cout<<ans<<endl;
	return 0;
}




评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值