码迷,mamicode.com
首页 > 其他好文 > 详细

常用字符串算法

时间:2014-08-13 12:52:06      阅读:291      评论:0      收藏:0      [点我收藏+]

标签:style   blog   http   color   os   io   for   ar   

简介

字符串的处理几乎无处不在,常用的字符串算法有KMP、扩展KMP、Trie树、AC自动机、Manacher、哈希、SA、SAM等。

 

 

Knuth-Morris-Pratt 算法

给你两个字符串AB,询问B串是否是A串的子串(A串是否包含B串)。

可以枚举从A串的什么位置起开始与B匹配,然后验证是否匹配。假如A串长度为n,B串长度为m,那么这种方法的复杂度是O (mn)的。

而KMP算法能够在线性复杂度内求出一个串在另一个串的所有匹配位置。

KMP的核心思想在于构建一个前缀数组(失配数组),对于模式串P,假设字符串下标从1开始,数组F满足F[i]=max{j|j<i且P[1..j]=P[i-j+1..i},即P的长度为j的前缀等于P的长度为i的前缀的长度为j的后缀。这说明F[i]所求的是串P[1..i]的一个最长的前缀P[1..j],这个前缀也是它的后缀。当这个前缀找不到的时候,F[i]设为0。

例如P=ababc,则F[4]=2,因为ab(P[1..2])是abab(P[1..4])的最长的前缀,又是它的后缀ab(P[3..4])。

有了F数组,就可以匹配两个字符串,主串T与模式串P。

先求出P的F数组,然后维护两个下标i和j,表示当前模式串P的前j个字符与主串T在位置i的前j个字符匹配,即P[1..j]=T[i-j+1..i]。

当算法在尝试对T[i+1]和P[j+1]匹配时,发现它们字符不同,那么就用F数组进行跳跃,使j=F[j]。

KMP算法高效的原因在于充分利用了匹配中的已有信息。关键的一步在于T[i+1]!=P[j+1]时j=F[j]。

因为我已知P[1..j]=T[i-j+1..i]而P[1..F[j]]=P[j-F[j]+1..j],因此P[1..F[j]]=T[i-F[j]+1..i],所以我们可以直接从F[j]位置开始继续匹配。

在实际实现中,字符串的下标从0开始,而我们仍然定义F数组的下标从1开始,对代码做一些简单的调整即可。

bubuko.com,布布扣
 1 const int maxn=1111111;
 2 char P[maxn];
 3 char T[maxn];
 4 int f[maxn];
 5 
 6 void getFail(char P[],int f[]){
 7     int i=0,j=-1;
 8     int len=strlen(P);
 9     f[0]=-1;
10     while (i<len){
11         if (j==-1||P[i]==P[j]){
12             i++,j++;
13             f[i]=j;
14         }
15         else{
16             j=f[j];
17         }
18     }
19 }
20 
21 void KMP(char T[],char P[],int f[]){
22     int i=0,j=0;
23     int n=strlen(T);
24     int m=strlen(P);
25     getFail(P,f);
26     while(i<n){
27         if(j==-1||T[i]==P[j]){
28             i++,j++;
29         }
30         else{
31             j=f[j];
32         }
33         if(j==m){
34             // TO DO:
35             //ans++;
36             j=f[j];
37         }
38     }
39 }
KMP

一些练习题

POJ 3461 Oulipo
计算单词W在整篇文章T中出现的次数。
KMP最基本的应用,统计出现次数,套模板即可。

bubuko.com,布布扣
1         if(j==m){
2             // TO DO:
3             ans++;
4             j=f[j];
5         }
View Code

 

POJ 2752 Seek the Name, Seek the Fame
找到一个S的子串作为前缀-后缀字符串。所谓前缀-后缀字符串即S的子串不仅是S的前缀又是S的后缀。
子串s[ 1 -> f[n] ] 是最长的子串,既是是s的前缀又是s的后缀,同理1 -> f[ f[n] ] 是次短的...依次递归。

bubuko.com,布布扣
1         while (f[n]>0){
2             stk.push(f[n]);
3             n=f[n];
4         }
View Code

 

POJ 2406 Power Strings
输出最大的n使得s由a重复n次而成。
当 n%(n-f[n])==0时,n-f[n] 是s最短的循环节。

bubuko.com,布布扣
1         if (n%(n-f[n])==0){
2             printf("%d\n",n/(n-f[n]));
3         }
4         else{
5             printf("1\n");
6         }
View Code

 

POJ 1961 Period
对每个前缀i,若能由某些字符重复k次形成,输出最大的k。
与上题类似,枚举i,若i%(i-f[i])==0 则最短循环节为i-f[i],k为i/(i-f[i])

bubuko.com,布布扣
1         for (int i=2;i<=n;i++){
2             if (f[i]>0&&i%(i-f[i])==0){
3                 printf("%d %d\n",i,i/(i-f[i]));
4             }
5         }
View Code

 

HDU 3336 Count the string
求出s有多少个子串是它本身的前缀。
DP公式如下。

bubuko.com,布布扣
1         for (int i=1;i<=n;i++){
2             dp[i]=dp[f[i]]+1;
3             ans=(ans+dp[i])%10007;
4         }
View Code

 

HDU 3746 Cyclic Nacklace
至少要在字符串s后面补几个字符才能凑成一个循环。
若本身已经有循环节,则答案为0。

bubuko.com,布布扣
1         if (f[n]>0&&n%(n-f[n])==0) printf("0\n");
2         else printf("%d\n",n-f[n]-n%(n-f[n]));
View Code

 

HDU 2087 剪花布条
给定T和P,为T中能分出几块P。
只匹配一次的KMP。当匹配成功时将j置为0即可。

bubuko.com,布布扣
1         if(j==m){
2             // TO DO:
3             ans++;
4             j=0;
5         }
View Code

 

HDU 2594 Simpsons’ Hidden Talents
求a的最长前缀是b的后缀。
将两串拼接成s,a在前b在后,则问题转化为求一个串的前缀是后缀。
注意s的前缀不一定是a的前缀也不一定是b的后缀,所以当f[n]>na或f[n]>nb时我们要忽略子串s[ 1->f[n] ]。

bubuko.com,布布扣
 1         while (f[m]>n1||f[m]>n2){
 2             m=f[m];
 3         }
 4         if (f[m]>0){
 5             for (int i=0;i<f[m];i++){
 6                 printf("%c",s1[i]);
 7             }
 8             printf(" %d\n",f[m]);
 9         }
10         else{
11             printf("0\n");
12         }
View Code

 

hdu 4763 Theme Section
求出满足EAEBE格式的最长子串E的长度。
由最长前缀后缀推广而来。
首先由大到小枚举前缀后缀,对于每个前缀后缀f[x],在字符串中间寻找f[i]=f[x],若找到则输出答案,否则继续枚举。

bubuko.com,布布扣
 1         int x=n;
 2         bool flag=false;
 3         while (f[x]>(n/3)) x=f[x];
 4         while (f[x]>0){
 5             flag=false;
 6             for (int i=f[x]*2;i<=n-f[x];i++){
 7                 if (f[i]==f[x]){
 8                     flag=true;
 9                     break;
10                 }
11             }
12             if (flag) break;
13         }
14         if (!flag) printf("0\n");
15         else printf("%d\n",f[x]);
View Code

 

扩展 KMP 算法

给定主串S和模板串T,扩展KMP问题要求解的就是extend[1..|S|],其中extend[i]表示S[i..|S|]与T的最长公共前缀,即S中的每个后缀与T的最长公共前缀的长度。

对比KMP算法,发现当extend[i]=|T|时,T恰好在S中出现。因此该算法是KMP算法的进一步扩展,称为扩展KMP算法。

 

bubuko.com,布布扣
 1 #include<iostream>
 2 #include<cstdio>
 3 #include<cstring>
 4 using namespace std;
 5 const int MM=100005;
 6 int next[MM],extand[MM];
 7 char S[MM],T[MM];
 8 void GetNext(const char *T){
 9      int len=strlen(T),a=0;
10      next[0]=len;
11      while(a<len-1 && T[a]==T[a+1]) a++;
12      next[1]=a;
13      a=1;
14      for(int k=2;k<len;k++){
15          int p=a+next[a]-1,L=next[k-a];
16          if( (k-1)+L >= p){
17              int j = (p-k+1)>0 ? (p-k+1) : 0;
18              while(k+j<len && T[k+j]==T[j]) j++;
19              next[k]=j;
20              a=k;
21          }
22          else
23              next[k]=L;
24      }
25 }
26 void GetExtand(const char *S,const char *T){
27      GetNext(T);
28      int slen=strlen(S),tlen=strlen(T),a=0;
29      int MinLen = slen < tlen ? slen : tlen;
30      while(a<MinLen && S[a]==T[a]) a++;
31      extand[0]=a;
32      a=0;
33      for(int k=1;k<slen;k++){
34          int p=a+extand[a]-1, L=next[k-a];
35          if( (k-1)+L >= p){
36              int j= (p-k+1) > 0 ? (p-k+1) : 0;
37              while(k+j<slen && j<tlen && S[k+j]==T[j]) j++;
38              extand[k]=j;
39              a=k;
40          }
41          else
42              extand[k]=L;
43      }
44 }
45 int main(){
46     while(scanf("%s%s",S,T)==2){
47          GetExtand(S,T);
48          for(int i=0;i<strlen(T);i++)
49              printf("%d ",next[i]);
50          puts("");
51          for(int i=0;i<strlen(S);i++)
52              printf("%d ",extand[i]);
53          puts("");
54     }
55     return 0;
56 }
扩展KMP

一些练习题

HDU 4333 Revolving Digits
读入数字串P,T由两个P拼接而成。
则T从0到n的每个长度为n的后缀即为一种数字排列。
对于T的后缀i,设其与原数字P的最长公共前缀长度为L。
若L>=n,说明此后缀表示的数与原数字相等。
若L<n,则令 T[i+extand[i]] 与 P[extand[i]] 比较大小即可得出两数的大小。
对于类似123123形式的重复串,排列三次以后又回到了123123的形式,所以答案必须除以循环节。
用KMP的找到最小循环节个数n/(n-f[n])

bubuko.com,布布扣
 1         scanf("%s",P);
 2         strcpy(T,P);
 3         strcat(T,P);
 4         GetExtand(T,P);
 5         int n=strlen(P);
 6         int cnt1=0,cnt2=0,cnt3=0;
 7         for (int i=0;i<n;i++){
 8             if (extand[i]>=n) cnt2++;
 9             else if (T[i+extand[i]]<P[extand[i]]) cnt1++;
10             else cnt3++;
11         }
12         getFail(P,f);
13         int tol=1;
14         if (n%(n-f[n])==0) tol=n/(n-f[n]);
15         printf("Case %d: %d %d %d\n",++Cas,cnt1/tol,cnt2/tol,cnt3/tol);
View Code

 

HDU 4300 Clairewd’s message
给一个密文到明文的映射表
给一个串,前面为密文,后面为明文,密文一定是完整的,但明文不完整或没有
将这个串补全。
令原串为T,将原串全部翻译为P。
可以发现原串T的后缀i是P的前缀。
从(n+1)/2开始枚举T的后缀,对于每个后缀i,若i+extand[i]>=n则从T:0~i-1为密文,P:i~n-1为明文。

bubuko.com,布布扣
1         GetExtand(T,P);  
2         int ret=len;  
3         for (int i=(len+1)/2;i<len;i++){  
4             if (extand[i]+i>=len){  
5                 ret=i;  
6                 break;  
7             }  
8         }  
View Code

 

 

 

Trie 树

 

Aho-Corasick 自动机

 

Manacher 算法

 

字符串哈希

 

后缀数组

 

后缀自动机

 

常用字符串算法,布布扣,bubuko.com

常用字符串算法

标签:style   blog   http   color   os   io   for   ar   

原文地址:http://www.cnblogs.com/zinthos/p/3909555.html

(0)
(0)
   
举报
评论 一句话评论(0
登录后才能评论!
© 2014 mamicode.com 版权所有  联系我们:gaon5@hotmail.com
迷上了代码!