zoukankan      html  css  js  c++  java
  • kmp字符串匹配

      首先我们来看洛谷的一道题:给出两个字符串s1和s2,其中s2为s1的子串,求出s2在s1中所有出现的位置。字符串匹配的题,最简单暴力的方法就是比较嘛,两层循环,外面一层就for:1—>lens1,里面一层是for:1->lens2,然后逐个比较,看s1[i+j]和s[j]是否相等,如果相等比较下一位,直到比到lens2,如果都相等,那就找到了一个位置,然后i++往后继续这么比较,如果中途出现不一样的,那就直接结束第二层循环,i++,继续比较。这样的复杂度是O(nm)的,n代表lens1,m代表lens2,而我们这道题的n,m<=100W,显然是不可能过的,那么我们可以怎样优化这个算法那?

      我们注意到,每次比较的时候,一旦出现不匹配,两个字符串都要从头再开始,辛辛苦苦几十年,一朝回到解放前。

      对于两个字符串ababababc(S1)

           ababc(s2)

    我们一直比较到了c才发现不合适,这个时候其实有一部分已经不需要再比较了,这一部分就是s1的第二个ab和s2的第一个ab,为什么不需要再比了?因为s1的第二个ab和s2的第二个ab匹配,而s2的第二个ab和第一个ab是一样的,所以也肯定能匹配,十分的神奇,看起来很棒,但是应该怎么实现那?从这个例子中我们也能注意到我们需要对s2做一点文章,是他失配(匹配不成功)的时候能够在失配位置原地找到另一半 现在应该从哪一位开始匹配当前的s1串的这失配的一位(也就是前面有若干位已经不需要再比较了)假设我们现在匹配到了s1[i]和s2[j],失配了,那么也就是已经匹配上了j-1位了,这时候j前面有k个字符串和从第一位开始的k个字符串一样,由于j前面这k位已经匹配过了,所以最前面的k位就不需要匹配了,直接挪过来就行,说来有那么一点点的抽象,建议读者自己拿笔划一划(虽然可能没有读者)。以abababb和ababb为例,当i指向S1的第5位,j指向s2的第5位是我们发现失配了,这时候普通的做法是i从第2位开始,j从新从第一位开始逐个比较,但kmp的做法是i不动了,j跳到第三位(S2串的第五位的前两位和开头的前两位一样,不需要再比较了)这样的话,我的S1字符串只需要扫一遍,时间复杂度就降到了O(n+m)。

      附一张图帮助理解

      那么现在我们就只剩一个问题了,匹配时随时可能失配,我们得怎么去求每一位的字符失配之后要跳到哪一位?我们用数组nex【i】来存储第i位字符失配后要跳到哪一位去,首先如果第一位就失配了,那就跳到第0位去(第0位什么也没有),也就是可以用s1的下一位来和s2的第一位进行匹配了,然后从第二位开始,我首先找到前一位的nex,如果前一位的nex+1就等于这一位,那前一位的nex+1就是这一位的nex,为啥那?因为我前一位的nex是k的话,代表从前一位开始往前数k个(这k个包括前一位)和最开始的k个是一样的,这时当前位和k+1又一样,就代表当前位开始往前数k+1位和最开始的k+1位都是一样的,皆大欢喜,那要是当前位和k+1位不一样怎么办呐?继续往前张k的nex:kk,同样的道理:从前一位开始往前数kk个(这kk个包括前一位)和最开始的kk个是一样的,这时比较kk+1和当前位,直到找到了0也没有匹配上,那很抱歉,当前位的nex就是0了。

      模板: 

     1 #include<iostream>
     2 #include<cstdio>
    3
    #include<cstring> 4 #include<string> 5 using namespace std; 6 char s1[1000005],s2[1000005]; 7 int len1,len2; 8 int nex[1000005]; 9 void getnext()//求nex数组 10 { 11 int j=0; 12 for(int i=2;i<=len2;++i) 13 { 14 j=nex[i-1]; 15 while(j&&s2[i]!=s2[j+1])j=nex[j]; 16 if(s2[i]==s2[j+1])nex[i]=j+1; 17 else nex[i]=0;//这个if-else是针对j=0的情况的,如果j=0,可能当前位刚好等于第一位,那么他的nex就是1,但是也可能并不是,那么他的nex就是0了 18 } 19 } 20 void kmp() 21 { 22 int j=0; 23 for(int i=1;i<=len1;++i) 24 { 25 while(j&&s2[j+1]!=s1[i])j=nex[j];//如果当前位失配,那就往前跳;还有一种情况就是j就是len2,这时已经是下一次循环了,i+1了,就相当于失配了 26 if(s2[j+1]==s1[i])j++;//因为跳到0会结束,所以还是判断一下是不是相等,相等才能相配长度+1 27 if(j==len2)printf("%d ",i-len2+1); 28 } 29 } 30 int main() 31 { 32 scanf("%s",s1+1);scanf("%s",s2+1);//从第一位开始存储 33 len1=strlen(s1+1),len2=strlen(s2+1);//从第一位开始获取长度。忽略第0位 34 getnext(); 35 kmp(); 36 for(int i=1;i<=len2;++i) 37 printf("%d ",nex[i]); 38 return 0; 39 }
  • 相关阅读:
    分布式系统学习笔记(2)-大型网站架构
    分布式系统学习笔记(1)-基础知识
    sql left join and right join
    java并发基础-Synchronized
    java内存模型
    JavaEE杂项--spring的异常体系
    javaEE杂项 --下载文件乱码的不同解决方案
    JavaEE杂项- spring AOP的几种实现方式(表面)
    字符编码:unicode,ascii,utf-8.
    Android SDK Manager国内无法更新的解决方案
  • 原文地址:https://www.cnblogs.com/yuelian/p/11599555.html
Copyright © 2011-2022 走看看