马拉车算法 manacher算法

开篇序言:理解的越透彻,讲的越简单。
废话多,挑着看。

文章目录

  • 1.算法作用
  • 2.算法过程
    • 2.1预处理
  • 2.2 求最长字符串
  • 代码实现

1.算法作用

马拉车算法(Manacher)能够算出字符串中以每个位置为中心的最长子回文串。如下例:

以acbccbc为例:

  • 以第0个字符为中心的最长子回文串为“a”
  • 以第2个字符为中心的最长子回文串为“cac”
  • 以第3个字符为中心的最长子回文串为“cbccbc”,这是一个偶数回文,以“cc”为中心。所以cc都是中心。

求解最长子回文串是她的妙用之一。下面讲算法内容,由于篇幅有限,写这个也挺耗时间,所以难免不清楚,请对照代码一起看。

2.算法过程

2.1预处理

回文串按照串长度的奇偶性,可以分为奇数回文和偶数回文。如“aba”、“abcba”和“aaa”,都是奇数回文,而“abba”、“abccba”和“aaaa”属于偶数回文。除了串长度的奇偶性外,两种回文还有另一个更加明显区别:对称中心是一个字符还是两个字符。很明显奇数回文,对称中心为一个字符,偶数回文中心为两个字符。如“aba”对称中心为“b”,“abba”对称中心为“bb”。

由于奇偶回文的存在,检查某个字符串是否是回文,我们需要检查它是奇数回文,还是偶数回文,如果两者都不是,那么就不是回文。也就是说我们需要分情况讨论。这种做法,在一般的情况下都没有,但是我们应该知道,每多一种情况,事情就会更复杂一些,代码更多,出bug的概率更大。所以,最好消除掉奇偶性。如果有兴趣的同学也可以尝试实现,分奇偶性的马拉车算法,毕竟不经历,不知天高地厚。

如何消除分情况讨论?在两两字符之间插入flag字符。flag字符必须是,原字符串中没有出现过的字符,比如“#”可以做“aba”和“abba”的flag字符,插入后分别变为“a#b#a”和“a#b#b#a”,他们都变成了奇数回文。方法非常巧妙,值得借鉴。

除了消除分情况讨论外,还需要在首尾加入begin字符end字符。这是为了预防边界异常,具体请看代码,不一一解释。不过也是一个很妙的编程技巧,值得借鉴。

2.2 求最长字符串

经过预处理原字符串S等到字符串T,接下来充分利用回文的对称性,求字符串T中,以每个位置为中心的最长子回文串。这里,子回文串使用中心idx和半径r表示,其左边界idx-r,右边界为idx+r。如下图:
马拉车算法 manacher算法_第1张图片
设r[i]为以i为中心的子回文串的半径。

程序从左向右依次检查每个中心的回文长度。设i为当前要检查的中心。

  • 1.如果之前已经找到的某些子回文中,已经包括了当前i,设其中右边界最右那个回文串为P,其中心为idx(i>idx),右边界为mx(mx>i)。那么可以利用对称性推算此子回文长度。先找到i关于idx对称的j=idx-(i-idx)。由于j左右对称,且idx左右对称,故可知i的左右对称范围也是如此,如下图。
    马拉车算法 manacher算法_第2张图片

但是当j的回文串刚好到达idx的边界或者超出时,只能确定idx与j回文串重合部分范围内,对于i来说是回文(也就是下图i的实线部分),超出部分(也就是i的虚线框部分)需要使用中心扩展检查确认。马拉车算法 manacher算法_第3张图片

  • 2.如果不存在这样的回文串P,也就是i>=mx,那么使用中心扩展搜索。如下图:
    马拉车算法 manacher算法_第4张图片

代码实现

上面的难以讲清楚,所以最好结合代码来看。

def manacher(s):
    center = 0
    mx = 0
    r = [1]
    
    t = "^"+s[0]
    for c in s[1:]:
        t += "#"+c
    t += "$"
    
    for i in xrange(1, len(t)-1):
        if i < mx: r.append(min(mx-i, r[2 * idx - i]))
        else: r.append(1)
        # 中心扩展确认
        while t[i-r[i]] == t[i+r[i]]: r[i] += 1
        # 右侧边界比较
        if mx < i + r[i]: mx, idx = i + r[i], i
    return r

你可能感兴趣的:(算法,manacher,马拉车算法,回文串)