1. 程式人生 > >求迴文串o(n)演算法

求迴文串o(n)演算法

Manacher演算法,O(n)迴文子串演算法

    這裡,我介紹一下O(n)迴文串處理的一種方法。Manacher演算法.

原文地址:

http://zhuhongcheng.wordpress.com/2009/08/02/a-simple-linear-time-algorithm-for-finding-longest-palindrome-sub-string/

    其實原文說得是比較清楚的,只是英文的,我這裡寫一份中文的吧。

    首先:大家都知道什麼叫回文串吧,這個演算法要解決的就是一個字串中最長的迴文子串有多長。這個演算法可以在O(n)的時間複雜度內既線性時間複雜度的情況下,求出以每個字元為中心的最長迴文有多長,

    這個演算法有一個很巧妙的地方,它把奇數的迴文串和偶數的迴文串統一起來考慮了。這一點一直是在做迴文串問題中時比較煩的地方。這個演算法還有一個很好的地方就是充分利用了字元匹配的特殊性,避免了大量不必要的重複匹配。

    演算法大致過程是這樣。先在每兩個相鄰字元中間插入一個分隔符,當然這個分隔符要在原串中沒有出現過。一般可以用‘#’分隔。這樣就非常巧妙的將奇數長度迴文串與偶數長度迴文串統一起來考慮了(見下面的一個例子,迴文串長度全為奇數了),然後用一個輔助陣列P記錄以每個字元為中心的最長迴文串的資訊。P[id]記錄的是以字元str[id]為中心的最長迴文串,當以str[id]為第一個字元,這個最長迴文串向右延伸了P[id]個字元。

    原串:    w a a b w s w f d

    新串:   # w # a # a # b # w # s # w # f # d #

輔助陣列P:1 2 1 2 3 2 1 2 1 2 1 4 1 2 1 2 1 2 1

    這裡有一個很好的性質,P[id]-1就是該回文子串在原串中的長度(包括‘#’)。如果這裡不是特別清楚,可以自己拿出紙來畫一畫,自己體會體會。當然這裡可能每個人寫法不盡相同,不過我想大致思路應該是一樣的吧。

    好,我們繼續。現在的關鍵問題就在於怎麼在O(n)時間複雜度內求出P陣列了。只要把這個P陣列求出來,最長迴文子串就可以直接掃一遍得出來了。

    由於這個演算法是線性從前往後掃的。那麼當我們準備求P[i]的時候,i以前的P[j]我們是已經得到了的。我們用mx記在i之前的迴文串中,延伸至最右端的位置+1處。同時用id這個變數記下取得這個最優mx時的id值。(注:為了防止字元比較的時候越界,我在這個加了‘#’的字串之前還加了另一個特殊字元‘$’,故我的新串下標是從1開始的)

好,到這裡,我們可以先貼一份程式碼了。

 

 
  1. const int MAXN=100000+1000;

  2. char S[MAXN*2];//改造後的串

  3. char T[MAXN];//原始串

  4. int p[MAXN*2];

  5. void init()//改造原始串

  6. {

  7. int i,j;

  8. S[0]='@',S[1]='#';

  9. for(int i=0,j=2;T[i];i++,j+=2)

  10. {

  11. S[j]=T[i];

  12. S[j+1]='#';

  13. }

  14. S[j]=0;

  15. }

  16. void manacher()

  17. {

  18. int id,i,mx=0;//mx指的是那個id能觸及的最大位置 +1 的地方

  19. for(int i=1;S[i];i++)

  20. {

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

  22. else p[i]=1;

  23. while(S[i-p[i]] == S[i+p[i]])

  24. p[i]++;

  25. if(i+p[i]>mx)

  26. {

  27. mx=i+p[i];

  28. id=i;

  29. }

  30. }

  31. }

程式碼是不是很短啊,而且相當好寫。很方便吧,還記得我上面說的這個演算法避免了很多不必要的重複匹配吧。這是什麼意思呢,其實這就是一句程式碼。

 

if( mx > i)

    p[i]=MIN(p[2*id-i], mx-i);

就是當前面比較的最遠長度mx>i的時候,P[i]有一個最小值。這個演算法的核心思想就在這裡,為什麼P陣列滿足這樣一個性質呢?

   (下面的部分為圖片形式)

    看完這個演算法,你有可能會覺得這種演算法在哪會用到呢?其實迴文串字尾陣列也可以做。只是複雜度是O(n log n)的,而且一般情況下也不會刻意去卡一個log n的演算法。可正好hdu就有這麼一題,你用字尾陣列寫怎麼都得T(當然應該是我寫得太爛了)。不信的話大家也可以去試試這題。

下面給出HDU3068最長迴文串的程式碼:

 

 
  1. #include <iostream>

  2. #include<cstdio>

  3. #include<algorithm>

  4. #include<cstring>

  5. #include<cmath>

  6. using namespace std;

  7. const int MAXN=110000+1000;

  8. char S[MAXN*2];//構造的新串

  9. char T[MAXN];//原始串

  10. int n;

  11. int ans;

  12. int p[MAXN*2];

  13. void init()

  14. {

  15. int i,j;

  16. S[0]='@',S[1]='#';

  17. for(i=0,j=2;T[i];i++,j+=2)

  18. {

  19. S[j]=T[i];

  20. S[j+1]='#';

  21. }

  22. S[j]=0;

  23. }

  24. int manacher()

  25. {

  26. int ans=0;

  27. int id,i,mx=0;

  28. for(int i=1;S[i];i++)

  29. {

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

  31. else p[i]=1;

  32. while(S[i-p[i]] == S[i+p[i]])

  33. p[i]++;

  34. if(i+p[i]>mx)

  35. {

  36. mx=i+p[i];

  37. id=i;

  38. }

  39. ans = max(ans,p[i]-1);

  40. }

  41. return ans;

  42. }

  43. int main()

  44. {

  45. while(scanf("%s",T)==1)

  46. {

  47. init();

  48. printf("%d\n",manacher());

  49. }

  50. return 0;

  51. }