zoukankan      html  css  js  c++  java
  • Manacher算法——求最长回文子串

    首先,得先了解什么是回文串。回文串就是正反读起来就是一样的,如“abcdcba”。我们要是直接采用暴力方法来查找最长回文子串,时间复杂度为O(n^3),好一点的方法是枚举每一个字符,比较较它左右距离相邻的点是否相等,我们姑且称之为中心检测法,时间复杂度为O(n^2)。

    当我们遇到字符串为“aaaaaaaaa”,之前的算法就会发生各个回文相互重叠的情况,会产生重复计算,然后就产生了一个问题,能否改进?答案是能,1975年,一个叫Manacher发明了Manacher Algorithm算法,俗称马拉车算法,其时间复杂为O(n)。该算法是利用回文串的特性来避免重复计算的。

    在中心检测法中,我们在遍历的过程要考虑到回文串长度的奇偶性,比如说“abba”的长度为偶数,“abcba”的长度为奇数,这样在寻找最长回文子串的过程要分别考奇偶的情况,是否可以统一处理了?

    Manacher算法:

    一)第一步是改造字符串S,变为T,其改造的方法如下:

    在字符串S的字符之间和S的首尾都插入一个“#”,如:S=“abba”变为T="#a#b#b#a#" 。我们会发现S的长度是4,而T的长度为9,长度变为奇数了!!那S的长度为奇数的情况时,变化后的长度还是奇数吗?我们举个例子,S=“abcba”,变化为T=“#a#b#c#b#a#”,T的长度为11,所以我们发现其改造的目的是将字符串的长度变为奇数,这样就可以统一的处理奇偶的情况了

    二)第二步,为了改进回文相互重叠的情况,我们将改造完后的T[ i ] 处的回文半径存储到数组P[ ]中,P[ i ]为新字符串T的T[ i ]处的回文半径,表示以字符T[i]为中心的最长回文字串的最端右字符到T[i]的长度,如以T[ i ]为中心的最长回文子串的为T[ l, r ],那么P[ i ]=r-i+1。这样最后遍历数组P[ ],取其中最大值即可。若P[ i ]=1表示该回文串就是T[ i  ]本身。举一个简单的例子感受一下:

    数组P有一性质,P[ i ]-1就是该回文子串在原字符串S中的长度 ,那就是P[i]-1就是该回文子串在原字符串S中的长度,至于证明,首先在转换得到的字符串T中,所有的回文字串的长度都为奇数,那么对于以T[i]为中心的最长回文字串,其长度就为2*P[i]-1,经过观察可知,T中所有的回文子串,其中分隔符的数量一定比其他字符的数量多1,也就是有P[i]个分隔符,剩下P[i]-1个字符来自原字符串,所以该回文串在原字符串中的长度就为P[i]-1。

    另外,由于第一个和最后一个字符都是#号,且也需要搜索回文,为了防止越界,我们还需要在首尾再加上非#号字符,实际操作时我们只需给开头加上个非#号字符,我就直接使用美元吧$,结尾不用加的原因是字符串的结尾标识为'',等于默认加过了。这样原问题就转化成如何求数组P[ ]的问题了。

    三)如何求数组P [ ]

      从左往右计算数组P[ ], Mi为之前取得最大回文串的中心位置,而R是最大回文串能到达的最右端的值。

      1)当 i <=R时,如何计算 P[ i ]的值了?毫无疑问的是数组P中点 i 之前点对应的值都已经计算出来了。利用回文串的特性,我们找到点 i 关于 Mi 的对称点 j ,其值为 j= 2*Mi-i 。因,点 j 、i 在以Mi 为中心的最大回文串的范围内([L ,R]),

           a)那么如果P[j] <R-i (同样是L和j 之间的距离),说明,以点 j 为中心的回文串没有超出范围[L ,R],由回文串的特性可知,从左右两端向Mi遍历,两端对应的字符都是相等的。所以P[ j ]=P[ i ](这里得先从点j转到点i 的情况),如下图:

         b)如果P[ j ]>=R-i (即 j 为中心的回文串的最左端超过 L),如下图所示。即,以点 j为中心的最大回文串的范围已经超出了范围[L ,R] ,这种情况,等式P[ j ]=P[ i ]还成立吗?显然不总是成立的!因,以点 j 为中心的回文串的最左端超过L,那么在[ L, j ]之间的字符肯定能在( j, Mi ]找到相等的,由回文串的特性可知,P[ i ] 至少等于R- i,至于是否大于R-i(图中红色的部分),我们还要从R+1开始一一的匹配,直达失配为止,从而更新R和对应的Mi以及P[ i ]。

      2)当 i > R时,如下图。这种情况,没法利用到回文串的特性,只能老老实实的一步步去匹配。

    总体看来,Manacher可以看出是中心检测法的一种优化,我们会发现如果把这一行代码删除

            if(i<mx)
            {
                p[i]=min(p[2*id-i],mx-i);
            }
            else
            {
                p[i]=1;
            }

    程序也是可以运行的,但这样是不是又变成了中心检测法了?我认为Manacher算法的最大优点在于利用回文串对称的性质,在处理p数组的时候,利用指针i,j的同步性,避免了匹配失败后的下标回退,因而将时间复杂度优化为了O(n)。

    相应的代码如下:

     1 char x[MAX];
     2 char s_new[MAX*2];
     3 int p[MAX*2];
     4 int Init()
     5 {
     6     int i,j,len;
     7     len=strlen(x);
     8     s_new[0]='$';
     9     s_new[1]='#';
    10     j=2;
    11     for(i=0; i<len; i++)
    12     {
    13         s_new[j++]=x[i];
    14         s_new[j++]='#';
    15     }
    16     s_new[j]='';
    17     return j;
    18 }
    19 int Manacher()
    20 {
    21     int i;
    22     int len=Init();
    23     int max_len=-1;
    24     int id;
    25     int mx=0;
    26     for (i=1; i<len; i++)
    27     {
    28         if(i<mx)
    29         {
    30             p[i]=min(p[2*id-i],mx-i);
    31         }
    32         else
    33         {
    34             p[i]=1;
    35         }
    36         while (s_new[i-p[i]]==s_new[i+p[i]])
    37         {
    38             p[i]++;
    39         }
    40         if(mx<i+p[i])
    41         {
    42             id=i;
    43             mx=i+p[i];
    44         }
    45     }
    46     for(i=1; i<len; i++)
    47     {
    48         if(max_len<p[i]-1)
    49         {
    50             max_len=p[i]-1;
    51         }
    52     }
    53     return max_len;
    54 }
  • 相关阅读:
    【转载】Linux 内存管理机制
    【学习笔记】cache/buffer
    【错误记录】PowerShell 超级无语的语法错误(令人怀疑人生)
    【Ansible 文档】【译文】模版(Jinja2)
    【Ansible 文档】【译文】Playbooks 变量
    【Ansible 文档】提示、推荐、注意事项
    【Ansible 文档】【译文】网络支持
    银行卡算法规则
    网站优化:浏览器缓存控制简介及配置策略
    学习一份百度的项目目录结构规范
  • 原文地址:https://www.cnblogs.com/wkfvawl/p/9669195.html
Copyright © 2011-2022 走看看