Manacher演算法__演算法

來源:互聯網
上載者:User
一:背景

  給定一個字串,求出其最長迴文子串。例如:
  (1)s=”abcd”,最長迴文長度為 1;
  (2)s=”ababa”,最長迴文長度為 5;
  (3)s=”abccb”,最長迴文長度為 4,即 bccb。
  以上問題的傳統思路大概是,遍曆每一個字元,以該字元為中點向兩邊尋找。其時間複雜度為 O(n2) O(n^2),很不高效。而在1975年,一個叫Manacher的人發明了一個演算法,Manacher演算法,也稱馬拉車演算法,該演算法可以把時間複雜度提升到 O(n) O(n)。下面來看看馬拉車演算法是如何工作的。 二:演算法過程分析

  由於迴文分為偶迴文(比如 bccb)和奇迴文(比如 bcacb),而在處理奇偶問題上會比較繁瑣,所以這裡我們使用一個技巧,在字元間插入一個字元(前提這個字元未出現在串裡)。舉個例子:s="abbahopxpo",轉換為s_new="$#a#b#b#a#h#o#p#x#p#o#"(這裡的字元 $ 只是為了防止越界,下面代碼會有說明),如此,s 裡起初有一個偶迴文abba和一個奇迴文opxpo,被轉換為#a#b#b#a#和#o#p#x#p#o#,長度都轉換成了奇數。
  定義一個輔助數組int p[],p[i]表示以s_new[i]為中心的最長迴文的半徑,例如:

i 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21
s_new[i] $ # a # b # b # a # h # o # p # x # p # o #
p[i] 1 2 1 4 5 2 1 2 1 2 1 2 1 2 1 6 1 2 1 2 1

可以看出,p[i]-1正好是原字串中最長迴文串的長度。
  Manacher演算法之所以快,就快在對 p 數組的求法上有個捷徑。在我們解決了奇偶迴文的繁瑣時,剩下的痛點就是求 p 數組,按照普通思維,我們是這樣求解的:求解p[i],先初始化p[i]=1,再以s_new[i]為中心判斷兩邊是否相等,相等就p[i]++。這就是普通的思維,但是我們想想,能否讓p[i]的初始化不是 1,讓它更大點,看下圖:

  設定兩個變數,mx 和 id 。
  mx 代表以s_new[id]為中心的最長迴文最右邊界,也就是mx=id+p[id]。
  假設我們現在求p[i],也就是以s_new[i]為中心的最長迴文半徑,如果i<mx,如上圖,那麼:

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

2 * id -i其實就是等於 j ,p[j]表示以s_new[j]為中心的最長迴文半徑,見上圖,因為 i 和 j 關於 id 對稱,我們利用p[j]來加快尋找。 三:代碼

/** *  * author 劉毅(Limer) * date   2017-02-25 * mode   C++  */#include<iostream>  #include<string.h>#include<algorithm>  using namespace std;char s[1000];char s_new[2000];int p[2000];int Init(){    int len = strlen(s);    s_new[0] = '$';    s_new[1] = '#';    int j = 2;    for (int i = 0; i < len; i++)    {        s_new[j++] = s[i];        s_new[j++] = '#';    }    s_new[j] = '\0';  //別忘了哦      return j;  //返回s_new的長度  }int Manacher(){    int len = Init();  //取得新字串長度並完成向s_new的轉換      int maxLen = -1;   //最長迴文長度      int id;    int mx = 0;    for (int i = 1; i < len; i++)    {        if (i < mx)            p[i] = min(p[2 * id - i], mx - i);  //需搞清楚上面那張圖含義, mx和2*id-i的含義        else            p[i] = 1;        while (s_new[i - p[i]] == s_new[i + p[i]])  //不需邊界判斷,因為左有'$',右有'\0'              p[i]++;        if (mx < i + p[i])  //我們每走一步i,都要和mx比較,我們希望mx儘可能的遠,這樣才能更有機會執行if (i < mx)這句代碼,從而提高效率          {            id = i;            mx = i + p[i];        }        maxLen = max(maxLen, p[i] - 1);    }    return maxLen;}int main(){    while (printf("請輸入字串:\n"))    {        scanf("%s", s);        printf("最長迴文長度為 %d\n\n", Manacher());    }    return 0;}
四:演算法複雜度分析

  文章開頭已經提及,Manacher演算法為線性演算法,即使最差情況下其時間複雜度亦為 O(n) O(n),在進行證明之前,我們還需要更加深入地理解上述演算法過程。
  定義 mx 為以s_new[id]為中心的最長迴文最右邊界,也就是mx=id+p[id]。j 與 i 關於 id 對稱,根據迴文的性質,p[i]的值基於以下三種情況得出:
  (1)j 的迴文串有一部分在 id 的之外,如下圖:

上圖中,黑線為 id 的迴文,i 與 j 關於 id 對稱,紅線為 j 的迴文。那麼根據代碼此時p[i]=mx-i,即紫線。那麼p[i]還可以更大麼。答案是不可能。見下圖:

假設右邊新增的紫色部分是p[i]可以增加的部分,那麼根據迴文的性質,a 等於 d ,也就是說 id 的迴文不僅僅是黑線,而是黑線+兩條紫線,矛盾,所以假設不成立,故p[i]=mx-i,不可以再增加一分。
  (2)j 迴文串全部在 id 的內部,如下圖:

此時p[i]=p[j],那麼p[i]還可以更大麼。答案亦是不可能。見下圖:

假設右邊新增的紅色部分是p[i]可以增加的部分,那麼根據迴文的性質,a 等於 b ,,也就是說 j 的迴文應該再加上 a 和 b ,矛盾,所以假設不成立,故p[i]=p[j],也不可以再增加一分。
  (3)j 迴文串左端正好與 id 的迴文串左端重合,見下圖:

此時p[i]=p[j]或p[i]=mx-i,並且p[i]還可以繼續增加,所以需要

while (s_new[i - p[i]] == s_new[i + p[i]])     p[i]++;

  根據(1)(2)(3),很容易推出Manacher演算法的最壞情況,即為字串內全是相同字元的時候。在這裡我們重點研究Manacher()中的for語句,推算髮現for語句內平均訪問每個字元5次,即時間複雜度為: Tworst(n)=O(n) T_{worst}(n)=O(n)。
  同理,我們也很容易知道最佳情況下的時間複雜度(最佳情況即字串內字元各不相同)。推算得平均訪問每個字元4次,即時間複雜度為: Tbest(n)=O(n) T_{best}(n)=O(n)。
  綜上,Manacher演算法的時間複雜度為 O(n) O(n)

參考文獻:
[1] Stephen__. hdu3068之manacher演算法+詳解

文章轉自我的個人部落格:http://www.61mon.com/index.php/archives/181/

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.