馬拉車算法 manacher算法

開篇序言:理解的越透徹,講的越簡單。
廢話多,挑着看。

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。如下圖:
在這裏插入圖片描述
設r[i]爲以i爲中心的子迴文串的半徑。

程序從左向右依次檢查每個中心的迴文長度。設i爲當前要檢查的中心。

  • 1.如果之前已經找到的某些子迴文中,已經包括了當前i,設其中右邊界最右那個迴文串爲P,其中心爲idx(i>idx),右邊界爲mx(mx>i)。那麼可以利用對稱性推算此子迴文長度。先找到i關於idx對稱的j=idx-(i-idx)。由於j左右對稱,且idx左右對稱,故可知i的左右對稱範圍也是如此,如下圖。
    在這裏插入圖片描述

但是當j的迴文串剛好到達idx的邊界或者超出時,只能確定idx與j迴文串重合部分範圍內,對於i來說是迴文(也就是下圖i的實線部分),超出部分(也就是i的虛線框部分)需要使用中心擴展檢查確認。在這裏插入圖片描述

  • 2.如果不存在這樣的迴文串P,也就是i>=mx,那麼使用中心擴展搜索。如下圖:
    在這裏插入圖片描述

代碼實現

上面的難以講清楚,所以最好結合代碼來看。

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
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章