Manacher详解
Posted
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Manacher详解相关的知识,希望对你有一定的参考价值。
之前的字符串题解中对Manacher的思想进行了简略的介绍,在这篇文章中,我将会详细的将这个算法的初衷和具体实现理论进行解释。声明一点,这是我个人的理解,可能有不全面之处,望多包涵。在之前的几篇文章中,我也发现有个别的编辑错误,希望大家在看的时候多加思考,不要被我的思维禁锢。
可能有的人没有看过之前的文章,那么我再赘述几句。
回文串:以最中间一个字符或者中轴线为对称轴左右两侧镜像相等的字符串。
例如 :
123321 这个字符串前三个字符和后三个字符镜像相等
1234321 这个字符串 前四个字符和后四个字符镜像相等。
从上面的例子可以看出,回文串有两种可能,一种是偶对称,即对称轴的位置没有字符,并且整个字符串的字符个数是偶数;另一种是奇对称,即对称轴的位置是一个字符,并且整个字符串的字符个数是奇数。普通的处理方法是将两种情况分开讨论,逐个枚举回文中心,由中心向两边扩散,确定回文串的长度。
Manacher算法就是在这个基础上进行改进的,他的改进主要有两个方面:
1??上述方法需要将情况分为奇对称和偶对称两种情况,比较麻烦,Manacher将原字符串进行了些微的改进,就可以用统一的方法对得到的新字符串进行处理。方法如下:
奇数和偶数之和必定为奇数,那么,在一个字符串的首尾和每两个字符之间加入一个标记符,就可以将所有的偶对称子串处理为奇对称串,这样仅用奇对称判断就可以处理偶对称的问题。
举例如下:
原串 123321
新串 *1*2*3*3*2*1*
将原来的6字符变成了13字符
原串 1234321
新串 *1*2*3*4*3*2*1*
将原来的7字符变成了15字符
如上所示,所有字符巧妙的变成了奇对称。如果原来回文串的字符个数是奇数,那么改进后的回文串的对称中心一定是字母,且该字母一定也是原回文串的对称中心。如果原来回文串的字符个数是偶数,那么改进后的回文串对称中心一定是插入的特殊符号(本文中为*),且该符号的位置在原串中是对称轴的位置。
2??之前的办法是将每个字符作为回文中心来确定以该字符为中心的回文串长度,也就意味着需要无数次的枚举和比较。Manacher 的第二个改进方向就是,利用之前确定的回文串的特征,减少本回文中心所在回文串确定长度时的判断次数。
现在就有两个问题,第一,既然要借助之前求得的回文串特征,那么在这个过程中必然应该将之前求得的所有回文串的信息记录下来,至少应该记录其回文中心和回文串长度吧?Manacher 选择记录的是回文串中心和回文半径(回文串的一半长度),这个过程是用一个叫做 p[]数组的以及它的下标完成的,如p[ i ]就表示以 i 为回文中心的字符串的回文半径为 p[ i ]。第二,之前已经判断过很多个回文串了,到底借助哪个回文串效果最好呢。
如下:
如果现在要判断A位置的回文长度,idx,i,i‘ 这三个中心的回文串长度已经得知,那么应该借助以idx为回文中心的这个回文串还是以 i 为回文中心的回文串,或者是 以 i’ 为中心的回文串?答案是 选择 idx 为中心的回文串,这个时候就有人会说:idx 那个回文串长度最长,覆盖面最广,直觉上来讲,就应该选它。
如果你也这么想,那请看下面这个
这个时候,求 A 点处的回文半径,应该借助哪个回文串呢?如果这时候你的答案还是 idx ,那我可以明确地告诉你:宝贝,这次你真的猜错了。因为这个时候需要借助的是 B 为中心的回文串了。为什么会这样?因为要求 A 所在的回文串长度,必然得借助和它关系最大的一个回文串,这样才能尽可能保证借助到的长度最多,正如上图所展示,B 比 idx 的右边界更远一些,也就意味着,B 对“前方的道路”更了解一些,尽管idx确实在长度上占优势,在A的左侧 idx所在回文串长度远远长于B所在回文串,但在A点的右侧,B的长度更多一些。回文中心左右两侧元素个数是相等的,即模版回文串分布在A点左右两侧可以用得到的字符个数是相等的(以最少的一方计算),从这个角度来看,选B更合理一些。由此可以得出一个结论:求当前中心所在回文串长度,要借助的模版必定是已经求得的回文串中右边界最远的回文串。
知道了需要借助哪个模版串之后,接下来就需要讨论如何借助模版串的问题。想知道如何借助模版串,首先应该清楚的是模版串和当前要求的回文中心的位置关系,这样才好下手。
模版串和当前回文中心的位置关系共有四种可能:
-
在第一种情况中,选择的模版串中心是 idx,其右边界为 idx右 ,i‘ 是 i 关于 idx 对称的点,由于idx是回文串,因此,在 idx串 内部 i 和 i’ 的对称情况是一样的,可以跳过这部分比较提高效率,但是在 idx 串之外i‘ 的情况 和 i 的情况是否一样则无法确定,因此需要在 idx右边界之外 对 i 的对称情况进行确定。这种情况中 i 串 最短的可能长度是 idx右-i ,在此基础上对 idx右的右侧和 i -(idx右 - i )的左侧进行处理即可。
-
在第二种情况中,选择的模版串 idx ,其 右边界 记为 idx右,i’ 是 i 关于 idx 的对称点,同上,在 idx 内部,i‘ 和 i 的对称情况一样,因此在上图这种情况下, i 串 的回文半径至少等于 i’ 的回文半径,但在idx右的右侧,是否还有字符使 i 回文不可知,因此 在i‘回文长度的基础上,对 idx右 的右侧和 i - ( idx右 - i )的左侧进行处理即可。
3.
在第三种情况中,选择的模版串 idx ,其右边界记为 idx右, i‘ 是 i 关于 idx 的对称点,同上,在 idx 内部,i’ 和 i 的对称情况是一样的,又因为 i 和 i‘ 串完全被 idx 串包含,因此对称情况完全一样,即 i 串 回文长度 最终等于 i‘ 串的长度
4.
在第四种情况中,i 串 完全脱离之前已经求得的所有回文串,因此只能以 i 为中心在其左右两侧逐个判断。
以上是理论知识的介绍,下面我将具体实现代码给大家进行解读,要说一点,代码的实现和理论知识略有不同,代码的实现将上述情况进行了综合,把其中相同的操作进行合并。
主要声明:
const int maxn; // 存放字符串长度可能的最大值
char str[ 3 * maxn ] , //存放处理后的新字符串
s[ maxn ] ; //存放原串
int p[ 3 * maxn ];存放新串每个字符对应的回文半径长度
int len1, //存放原串的长度;
len2 ; //存放处理后新串的长度-1;
Manacher的代码实现分为两个部分。
1??第一部分 生成新串
2??第二部分 生成p[]数组
代码很精简,但逻辑性特别强,方法也很独特。今天的分享到此结束,感谢您的阅读。
以上是关于Manacher详解的主要内容,如果未能解决你的问题,请参考以下文章