日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當前位置: 首頁 > 编程资源 > 编程问答 >内容正文

编程问答

字符串相关处理kmp,前缀数,后缀树,后缀数组,最长回文串,最长重复字串,最长非重复字串

發布時間:2024/9/30 编程问答 26 豆豆
生活随笔 收集整理的這篇文章主要介紹了 字符串相关处理kmp,前缀数,后缀树,后缀数组,最长回文串,最长重复字串,最长非重复字串 小編覺得挺不錯的,現在分享給大家,幫大家做個參考.

1. 最長回文串

一般用后綴數組或者后綴樹可以解決,

用此方法:http://blog.csdn.net/v_july_v/article/details/6897097

  • 預處理后綴樹,使得查詢LCA的復雜度為O(1)。這步的開銷是O(N),N是單詞S的長度 ;
  • 對單詞的每一位置i(也就是從0到N-1),獲取LCA(S(i), S‘(N-i-1)) 以及LCA(S(i), S’(n-i))。查找兩次的原因是我們需要考慮奇數回文和偶數回文的情況。這步要考察每坨i,所以復雜度是O(N) ;(s'是s的翻轉串
  • 找到最大的LCA,我們也就得到了回文的中心i以及回文的半徑長度,自然也就得到了最長回文。總的復雜度O(n)。?(在前文中提到求樹種兩個節點的最近公共父節點也是用的lca,此題也是相類似,找到s和s'的最近父節點之后可以,則父節點到root之間的便是回文
  • 上面講得太復雜, 對于串S,取其反轉S' 將S和S‘插入后綴樹中 for i [1 to n - 1]
    ? ?找出S[0 - i].reverse() 與 S[i, n]的LCA
    ? ?找出S[0 - i].reverse() 與 S[i + 1, n]的LCA
    從些個LCAs里面選出最大的1

    1.1 是不是想到一種求s和s'最長公共子串(注意不是最長公共子序列)?

    ? ? ?這個方法在有時候不work,

    S = “abacdfgdcaba”, S’ = “abacdgfdcaba”.
    The longest common substring between S and S’ is “abacd”.明顯錯誤。

    1.2

    依次遍歷字符串S中可能的中心點,注意,中心點可能在字符,也可能在兩個字符之間

    //從中心向兩端擴展string expandAroundCenter(string s,int c1,int c2){int l=c1;int r=c2;int n=s.length();while (l>=0 && r<=n-1 && s[l]==s[r]){l--;r++;}return s.substr(l+1,r-l-1);}//方法3:從中心向兩端擴展string IsPalindrome3(string str){int n=str.length();if (n==0){return "";}string longest=str.substr(0,1);for (int i=0;i<n-1;i++){string p1=expandAroundCenter(str,i,i);if (p1.length()>longest.length()){longest=p1;}string p2=expandAroundCenter(str,i,i+1);if (p2.length()>longest.length()){longest=p2;}}return longest;}
    1.3 動態規劃(時間復雜度也是n^2)

    Define P[ i, j ] ← true?iff?the substring Si?… Sj?is a palindrome, otherwise false.

    P[ i, j ] ← ( P[ i+1, j-1 ]?and?Si?= Sj?)

    This yields a straight forward DP solution, which we first initialize the one and two letters palindromes, and work our way up finding all three letters palindromes, and so on…?

    //方法2:動態規劃string IsPalindrome2(string str){if (str==""){return "";}int n=str.length();int maxIndex=0;int maxLength=1;bool IsPal[1000][1000]={false};for (int i=0;i<n;i++){IsPal[i][i]=true;}for (int i=0;i<n-1;i++){if (str[i]==str[i+1]){IsPal[i][i+1]=true;maxIndex=i;maxLength=2;}}for (int len=3;len<=n;len++){for (int i=0;i<=n-len;i++){int j=i+len-1;if (IsPal[i+1][j-1] && str[i]==str[j]){IsPal[i][j]=true;maxIndex=i;maxLength=len;}}}return str.substr(maxIndex,maxLength);}
    1.4


    但是有一種巧妙的方法,不構建后綴樹在o(n)完成

    轉自:http://www.felix021.com/blog/read.php?2040

    首先用一個非常巧妙的方式,將所有可能的奇數/偶數長度的回文子串都轉換成了奇數長度:在每個字符的兩邊都插入一個特殊的符號。比如 abba 變成 #a#b#b#a#, aba變成 #a#b#a#。 為了進一步減少編碼的復雜度,可以在字符串的開始加入另一個特殊字符,這樣就不用特殊處理越界問題,比如$#a#b#a#。

    下面以字符串12212321為例,經過上一步,變成了 S[] = "$#1#2#2#1#2#3#2#1#";

    然后用一個數組 P[i] 來記錄以字符S[i]為中心的最長回文子串向左/右擴張的長度(包括S[i]),比如S和P的對應關系:

    S? #? 1? #? 2? #? 2? #? 1? #? 2? #? 3? #? 2? #? 1? #
    P? 1? 2? 1? 2? 5? 2? 1? 4? 1? 2? 1? 6? 1? 2? 1? 2? 1
    (p.s. 可以看出,P[i]-1正好是原字符串中回文串的總長度)
    那么怎么計算P[i]呢?該算法增加兩個輔助變量(其實一個就夠了,兩個更清晰)id和mx,其中id表示最大回文子串中心的位置,mx則為id+P[id],也就是最大回文子串的邊界。

    然后可以得到一個非常神奇的結論,這個算法的關鍵點就在這里了:如果mx > i,那么P[i] >= MIN(P[2 * id - i], mx - i)。就是這個串卡了我非常久。實際上如果把它寫得復雜一點,理解起來會簡單很多: //記j = 2 * id - i,也就是說 j 是 i 關于 id 的對稱點。
    if (mx - i > P[j])
    ? ? P[i] = P[j];
    else /* P[j] >= mx - i */
    ? ? P[i] = mx - i; // P[i] >= mx - i,取最小值,之后再匹配更新。
    當然光看代碼還是不夠清晰,還是借助圖來理解比較容易。

    當 mx - i > P[j] 的時候,以S[j]為中心的回文子串包含在以S[id]為中心的回文子串中,由于 i 和 j 對稱,以S[i]為中心的回文子串必然包含在以S[id]為中心的回文子串中,所以必有 P[i] = P[j],見下圖。


    當 P[j] > mx - i 的時候,以S[j]為中心的回文子串不完全包含于以S[id]為中心的回文子串中,但是基于對稱性可知,下圖中兩個綠框所包圍的部分是相同的,也就是說以S[i]為中心的回文子串,其向右至少會擴張到mx的位置,也就是說 P[i] >= mx - i。至于mx之后的部分是否對稱,就只能老老實實去匹配了。


    對于 mx <= i 的情況,無法對 P[i]做更多的假設,只能P[i] = 1,然后再去匹配了。

    于是代碼如下: //輸入,并處理得到字符串s
    int p[1000], mx = 0, id = 0;
    memset(p, 0, sizeof(p));
    for (i = 1; s[i] != '\0'; i++) {
    ? ? p[i] = mx > i ? min(p[2*id-i], mx-i) : 1;
    ? ? while (s[i + p[i]] == s[i - p[i]]) p[i]++;
    ? ? if (i + p[i] > mx) {
    ? ? ? ? mx = i + p[i];
    ? ? ? ? id = i;
    ? ? }
    }
    //找出p[i]中最大的 //主要原理就是先通過對稱性獲得以i為中心點的半徑;然后再向左向右擴展,試圖增大半徑
    int Palindrome(char *str) {int len = strlen(str);char *Str = new char[len+len+3];memset(Str,0,len+len+3);//Str[0] = '$';int Len = len+len+2;int *scale = new int[Len];memset(scale,0,sizeof(int)*Len);int j=0;for (int i=1;i<Len;++i){if(i%2==0)Str[i] = str[j++];elseStr[i] = '#';}Str[Len] = '\0';int id = 0;//在i之前的回文段,這個回文段的中心點,這個回文段的右邊的邊界最大int mx = 0;//在i之前的回文段,這個回文段的右邊的邊界最大for (int i=1;i<Len;++i){if (mx >i)//如果之前的回文邊界超過i,要找到i關于回文中心點id的對稱點j{int j = 2*id - i;int rightSpan = mx - i;if(scale[j]>rightSpan)scale[i] = rightSpan;elsescale[i] = scale[j];}else scale[i] = 1;int right = i + scale[i] +1;int left = i - scale[i] -1;while ( (right<Len) && (Str[right]==Str[left]) ){++scale[i];++right;--left;}if (i+scale[i]>mx){mx = scale[i];id = i;}}int maxValue = 0;//cout<<"Len "<<Len<<endl;for (int i=0;i<Len;++i){//cout<<i<<endl;if(scale[i]>maxValue)maxValue = scale[i];}//cout<<"out"<<endl;delete []Str;delete []scale;return maxValue ;}



    2. july:http://blog.csdn.net/v_july_v/article/details/6897097提到后綴樹還可以解決一下幾個問題:
  • 查找字符串o是否在字符串S中。?
    ??方案:用S構造后綴樹,按在trie中搜索字串的方法搜索o即可。?
    ??原理:若o在S中,則o必然是S的某個后綴的前綴。?
    例如S: leconte,查找o: con是否在S中,則o(con)必然是S(leconte)的后綴之一conte的前綴.有了這個前提,采用trie搜索的方法就不難理解了。。?
  • 指定字符串T在字符串S中的重復次數。?
    ??方案:用S+’$'構造后綴樹,搜索T節點下的葉節點數目即為重復次數?
    ??原理:如果T在S中重復了兩次,則S應有兩個后綴以T為前綴,重復次數就自然統計出來了。。?
  • 字符串S中的最長重復子串?
    ??方案:原理同2,具體做法就是找到最深的非葉節點。?
    ??這個深是指從root所經歷過的字符個數,最深非葉節點所經歷的字符串起來就是最長重復子串。?
    為什么要非葉節點呢?因為既然是要重復,當然葉節點個數要>=2。 (此方法應該只能找到可重疊的最長重復字串,譬如abcbcbd,可重疊最長重復字串是bcb,可以看出兩個字串bcb在b上重疊了;其不重疊最長重復字串是bc和cb,用后綴樹暫時想不出什么方法
  • 兩個字符串S1,S2的最長公共部分?
    ??方案:將S1#S2$作為字符串壓入后綴樹,找到最深的非葉節點,且該節點的葉節點(這里是指該節點的所有葉子節點)既有#也有$。 (
    譬如s1=abcd, s2 = ebce,連接組合成abcd#ebce$, ? ??
  • ? 也可以將abcd#,ebce$分布放入后綴樹中,找到深度最大,并且屬于所有字符串的前綴,如:http://nanapple.happy.blog.163.com/blog/static/77501222200861583550778/ }


    第四題可以看做是longest common substring,可以就是說要求最長公共字串必須是連續的,用后綴樹或者后綴數組的時間復雜度是o(n+m)

    當然還可以用動態規劃實現,o(n*m):(為了避免邊界檢查,f[][]都是從1開始計算的,但是f[i][j],表示的是a[i-1]b[j-1])

    char a[]="acbedf";char b[]="ecbedfacabe";int f[50][50];memset(f,0,sizeof(f));int maxL=0,ai=0;for (int i=1;i<=sizeof(a);++i){for(int j=1;j<=sizeof(b);++j){if(a[i-1]==b[j-1]){f[i][j] =f[i-1][j-1] +1;if (f[i][j]>maxL){maxL = f[i][j];ai = i-1;}}}}char c[50];memset(c,0,sizeof(c));strncpy(c,a+ai-maxL+1,maxL);cout<<maxL<<endl<<c<<endl;

    以上代碼主要參考:http://sagittarix.blogspot.com/2008/11/blog-post_06.html


    \

    e

    d

    c

    e

    d

    n

    n

    0

    0

    0

    0

    0

    1

    c

    0

    0

    1

    0

    0

    0

    e

    1

    0

    0

    1

    0

    0

    d

    0

    1

    0

    0

    1

    0

    t

    0

    0

    0

    0

    0

    0

    我們把兩個字符串看作矩陣的x-y軸:首先遍歷字符串時,把兩個字符相同的位置標記成1,不相同的地方標記成0。很容易證明,如果能形成公共子序列,則最長子序列一定是從某個位置開始的對角線的長度,所以我們需要做的就是統計對角線的長度。

    例如str1=”ncedt”與str2=”edcedn”生成的矩陣如左矩陣,遍歷找到最長的對角線即可。


    e

    d

    c

    e

    d

    n

    n

    0

    0

    0

    0

    0

    1

    c

    0

    0

    1

    0

    0

    0

    e

    1

    0

    0

    2

    0

    0

    d

    0

    2

    0

    0

    3

    0

    t

    0

    0

    0

    0

    0

    0

    其實這里還可以優化,即在生成表格的時候,加上對角線上比它前面的元素,這樣一遍遍歷就可以生成左圖所示矩陣,中間記下最大值便可,省下最后一趟比較,降低時間復雜度;空間復雜度也可降低,其實只要一維的數組便可以了,因為每次更新只用到上面一行(或左邊一列,看你程序怎么寫了,都可以)。






    這個直觀上很好理解,但拆分到子問題的思想表達起來比較繞,就轉一下別人的文字:

    ---------------------------------------------------------------------------------------------------------------------

    定義f(m, n)為串Xm和Yn之間最長的子字符串的長度并且該子字符串結束于Xm?和?Yn。因為要求是連續的,所以定義f的時候多了一個要求,即字符串結束于Xm?和?Yn。

    于是有f(m, 0) = f(0, m) = 0
    如果xm != yn,?則f(m, n) = 0
    如果xm = yn,?則f(m, n) = f(m-1, n-1) + 1
    因為最長字符串不一定結束于Xm?和?Yn末尾,所以這里必須求得所有可能的f(p, q) | 0 <>最大的f(p, q)就是解。依照公式用Bottom-up DP可解。

    代碼就不寫了,簡單。但是它與其它的DP填表又有所不同,不是直接得出最大的填到表格最后一格中,而是找出格子中的最大值,其中差別,細細體會




    還有一個問題是 longest common subsquence ,要求最長公共序列不一定是連續的,但是有序的,一般只能用動態規劃實現,o(n*m)

    算法導論講的很透徹,這個博客也不錯:http://blog.csdn.net/orbit/article/details/6717125

    ?現在來分析一下本問題的最優子結構。首先定義問題,假設有字符串str1長度為m,字符串str2長度為n,可以把子問題描述為:求字符串str1<1..m>中從第1個到第i(i <= m)個字符組成的子串str1<1…i>和字符串str2<1..n>中從第1個到第j(j <= n)個字符組成的子串str2<1…j>的最長公共序列,子問題的最長公共序列可以描述為d[i,j] = { Z1,Z2, … Zk },其中Z1-Zk為當前子問題已經匹配到的最長公共子序列的字符。子問題定義以后,還要找出子問題的最優序列d[i,j]的遞推關系。分析d[i,j]的遞推關系要從str1[i]和str2[j]的關系入手,如果str1[i]和str2[j]相同,則d[i,j]就是d[i-1,j-1]的最長公共序列+1,Zk=str1[i]=str2[j];如果str1[i]和str2[j]不相同,則d[i,j]就是d[i-1,j]的最長公共序列和d[i,j-1]的最長公共序列中較大的那一個。

    ??????? 最后是確定d[i,j]的邊界值,當字符串str1為空或字符串str2為空時,其最長公共子串應該是0,也就是說當i=0或j=0時,d[i,j]就是0。d[i,j]的完整遞推關系如下:


    d[i,j]表示,對于序列str1[0,i]和str2[0,j]的lcs



    構建一顆后綴樹的編碼難度較大,一般解題應該用后綴數組:http://dongxicheng.org/structure/suffix-array/

    有時間再研究下后綴數組吧。。。


    總結

    以上是生活随笔為你收集整理的字符串相关处理kmp,前缀数,后缀树,后缀数组,最长回文串,最长重复字串,最长非重复字串的全部內容,希望文章能夠幫你解決所遇到的問題。

    如果覺得生活随笔網站內容還不錯,歡迎將生活随笔推薦給好友。