zoukankan      html  css  js  c++  java
  • KMP

    KMP算法

    特别感谢 orz sofu6 让我悟了

    (KMP) 算法指的是字符串模式匹配算法,要解决的问题就是在字符串(也叫主串)中的模式(pattern)定位问题

    说简单点就是我们平时常说的关键字搜索。模式串就是关键字(接下来称它为P),如果它在一个主串(接下来称为T)中出现,就返回它的具体位置,否则返回-1(常用手段)。

    暴力算法

    从左到右一个个匹配,如果这个过程中有某个字符不匹配,就跳回去,将模式串向右移动一位

    我们可以这样初始化:

    之后我们只需要比较 (i) 指针指向的字符和 (j) 指针指向的字符是否一致。如果一致就都向后移动,如果不一致

    (A)(E)不相等,那就把 (i) 指针移回第 (1) 位(假设下标从 (0) 开始),j移动到模式串的第(0)位,然后又重新开始这个步骤:

    显然肯定会T掉,怎么优化??

    KMP算法

    利用已经部分匹配这个有效信息,保持 (i) 指针不回溯,通过修改 (j) 指针,让模式串尽量地移动到有效的位置

    那么当发现一个字符与主串不匹配时,(j) 指针应该移向哪儿??

    探究 (j) 移动的规律

    C 和 D 不匹配了把 (j) 移动到哪儿??显然是第一位

    下面情况相同

    可以把 (j) 移到第2位,因为前面有两个字母已经匹配

    得出KMP精髓

    (j) 要移动的下一个位置 (k) 要保证最前面 (k) 个字符 (j) 之前的最后 (k) 个字符是一样的

    (P[0 => k-1] == P[j-k => j-1])

    另一种理解

    若子串的前缀集和后缀集中,重复的最长子串的长度为(k),则下次匹配子串的j可以移动到第 (k) 位(下标为0为第0位)

    在“aba”中,前缀集就是除掉最后一个字符'a'后的子串集合{a,ab},同理后缀集为除掉最前一个字符a后的子串集合{a,ba},那么两者最长的重复子串就是a,k=1;

    在“ababa”中,前缀集是{a,ab,aba,abab},后缀集是{a,ba,aba,baba},二者最长重复子串是aba,k=3;

    图解

    发现 (C)(D) 不匹配 (j) 位前面的子串是 (ABA) ,该子串的前缀集是({A,AB}),后缀集是({A,BA}),最大的重复子串是(A),只有(1)个字符,所以j移到 (k) 即第1位

    同理

    (j) 位的时候,(j)前面的子串是(ABCAB),前缀集是({A,AB,ABC,ABCA},)后缀集是({B,AB,CAB,BCAB}),最大重复子串是 (AB),个数是(2)个字符,因此(j) 移到 (k) 即第2位。

    匹配时的代码

    void pre(){
    	for(int i = 2,j = 0;i <= m; i++){
    		while(j > 0 && b[i] != b[j + 1])j = P[j];//匹配失败,退步
    		if(b[i] == b[j + 1]) j++;//匹配成功,继续匹配
    		P[i] = j;
    	}
    } 
    

    important

    怎么求这些 (k) 呢??

    因为在 (P) 的每一个位置都可能发生不匹配,所以我们要计算每一个位置 (j) 对应的 (k) ,用一个(P)数组保存(B[j] = k)当 A(主串)[i] != B[ j ]时,(j) 指针的下一个位置,因为下标从0开始的,(k) 值实际是 (j) 位前的子串的最大重复子串的长度

    (P[j]) 的值(也就是(k))表示,当(B[j] != A[i])时,(j) 指针的下一步移动位置

    我们用递推的思想

    (B = “ababacb”)预处理,我们假设已经求出了(P[1],P[2],P[3],P[4],)(P[5],P[6])

        1 2 3 4 5 6 7
    B = a b a b a c b
    P = 0 0 1 2 ? ? ?  
    

    很显然$ P[5] = P[4] + 1(,因为)P[4](可以知道)B[1……2](已经和)B[3……4]$相等了,现在又有 (B[3] = B[5]),所以(P[5])可以用(P[4])加一个字符得到所以如果在匹配过程中在(P[5])位置不同的时候,直接把(j)移到4(P[5] + 1)的位置进行比较

    而接着看(P[6]) ,它显然不是(P[5] + 1),因为(P[P[5] + 1] != B[6])那么就要考虑“退一步”,将(j)退到(P[P[3] + 1])与A[i]比较,还不匹配,再退发现为(P[1] = 0),stop

    注意

    看清查找子串时,在主串中能不能交叉,举个例子:A = 'aaaaaa',B = 'aa',如果不能交叉,匹配完成就直接返回 (j = 0) ,如果允许在主串中重复时,如果返回 (j = 0) ,就会漏掉连续的(2,3|4,5)位置的子串aa;所以应该返回 (j = P[j]) ;

    int ans = 0,j = 0;
    for(int i = 1;i <= n; i++){
         while(j > 0 && a[i] != b[j + 1])j = P[j];
    	if(a[i] == b[j + 1])j++;
    	     if(j == m){
    		ans++,j = 0;
    	  }
        }
    return ans;
    
    

    模板体

    /*
      work by:Ariel
    */
    #include <iostream>
    #include <cstdio>
    #include <cstring>
    using namespace std;
    const int A = 1e3 + 2;
    int P[A], n, m;
    char a[A], b[A];
    void pre(){
    	P[1] = 0;
    	int j = 0;
    	for(int i = 1;i < m; i++){
    		while(j > 0 && b[i + 1] != b[j + 1])j = P[j];
    		if(b[i + 1] == b[j + 1]) j++;
    		P[i + 1] = j;
    	}
    } 
    int kmp(){
    	int ans = 0,j = 0;
    	for(int i = 0;i < n; i++){
    		while(j > 0 && a[i + 1] != b[j + 1])j = P[j];
    		if(a[i + 1] == b[j + 1])j++;
    		if(j == m){
    			ans++; j = 0;
    		}
    	}
    	return ans;
    }
    int main(){
      while(cin >> a + 1){
      	if (a[1] == '#')break;
      	scanf("%s",b + 1);
      	m = strlen(b + 1);
      	n = strlen(a + 1);
      	pre();
      	printf("%d
    ",kmp());
      }
      
    }
    
    

    Power Strings

    hash可以水??

    solution:

    (KMP)一个简单的应用,要求主串中最多子串的个数,联想 (KMP) 自我匹配函数

    不难发现如果(n % (n - p[n]) == 0)那就证明有子串,子串长度为(n - p[n]),所以最多子串个数为(n / (n - p[n]))

    然后就套模板了

    /*
      work by:Ariel
    */
    #include <iostream>
    #include <cstdio>
    #include <cstring>
    #include <string>
    using namespace std;
    const int A = 1e6 + 5;
    const int inf = 0x3f3f3f3f;
    int p[A],n;
    char a[A];
    void pre(){
    	p[1] = 0;
    	for(int i = 1,j = 0;i < n; i++){
    		while(j > 0 && a[i + 1] != a[j + 1])j = p[j];
    		if(a[i + 1] == a[j + 1])j++;
    		p[i + 1] = j;
    	}
    }
    int main(){
       while(1){
       	   scanf("%s",a + 1);
       	   if(a[1] == '.')break;
       	   n = strlen(a + 1);
       	   pre();
       	   if(n % (n - p[n]) == 0) printf("%d
    ",n / (n - p[n]));
       	   else printf("1
    ");
      }
    }
    
    

    Radio Transmission

    solution:

    结论题
    (ans = n - p[n])

    /*
      work by:Ariel
    */
    #include <iostream>
    #include <cstdio>
    #include <cstring>
    using namespace std;
    const int D = 1e6 + 5;
    char a[D];
    int p[D], n;
    void pre(){
    	p[1] = 0;
    	for(int i = 1,j = 0;i < n; i++){
    	    while(j > 0 && a[i + 1] != a[j + 1])j = p[j];
    	    if(a[i + 1] == a[j + 1]) j++;
    	    p[i + 1] = j;
    	}
    }
    int main(){
       scanf("%d%s", &n,a + 1);
       pre();
       printf("%d",n - p[n]); 
    }
    
    
  • 相关阅读:
    vue.js环境配置步骤及npm run dev报错解决方案
    消息处理之performSelector
    iOS开发网络篇—发送GET和POST请求(使用NSURLSession)
    iOS手机号正则表达式并实现344格式 (正则的另一种实现方式)
    IOS开发——正则表达式验证手机号、密码
    iOS网络请求之---GET和POST
    CLLocation
    ios本地文件内容读取,.json .plist 文件读写
    UITableViewCell的4种样式
    iOS开发通过代码方式使用AutoLayout (NSLayoutConstraint + Masonry)
  • 原文地址:https://www.cnblogs.com/Arielzz/p/14259629.html
Copyright © 2011-2022 走看看