• [学习笔记] kmp


    不知道开头写什么话

    康康下面这个问题:

    给定一个模式串和一个文本串,求模式串在文本串中出现的位置,次数.

    一个显然的做法是对于文本串中的每个位置,都把模式串从头开始匹配.

    但是这样的时间复杂度太高了,

    所以我们有了接下来要讲的:kmp算法.

    step 0

    先定义一些东西.

    (a):模式串.

    (b):文本串.

    (la):(a)的长度.

    (lb):(b)的长度.

    (next[i]):模式串中满足(a[1sim j]=a[i-j+1sim i])的最大的(j).

    (f[i]):满足(a[1sim j]=b[i-j+1sim i])的最大的(j).

    step 1

    首先暴力的算法因为时间复杂度太高((O(la*lb)))而被抛弃...

    所以我们要想想怎么排除掉一些多余的情况.

    先举个例子:

    (a):bababb.

    (b):bababababababababb.

    我们在第一次匹配的时候会是这样:

    babab|b

    babab|ababababababb

    在第六位的时候失配了..

    但是我们可以发现,前五位的(f[i])都已经求出来了.

    如果我们采取暴力的做法,将(a)直接往后移一位时,就是这样:

    |bababb

    b|ababababababababb

    可以发现在第一位就失配了,

    所以对第六位没有任何帮助.

    于是我们可以想想,

    要怎样跳才能一步跳到有用的位置.

    这时我们就需要这个next数组.

    step 2

    (首先next数组的定义我已经放上面了...)

    假如我们已经求出了next数组(先别管怎么求的).

    当我们匹配完第(i)位,且第(i)位在模式串中对应的是(j),

    (就像上面的例子中,(i)=5,(j)=5)

    而在第(i)+1位失配了.

    于是我们就要改变(j),使(i+1)能匹配上.

    而改变的方式就是把(j)改为(next[j]).

    想一想这是为什么.

    假设有一个(k>next[j]),使(a[1sim k+1]=b[i+1-(k+1)+1sim i+1]).

    (因为我们这里是在求(i)+1,而(j)是与(i)匹配,所以(k)加了1)

    (k)对于(next[j])能使答案更优,

    因为(i)是匹配上了的,所以(a[1sim j]=b[i-j+1sim i]).

    因为(i+1)(k+1)匹配上了,所以(i)(k)也能匹配上,

    (a[1sim k]=a[i-k+1sim i]).

    因此我们可以发现(a[1sim k]=a[j-k+1sim j]).

    画个图理解一下(前面的不理解的可以自己画下图):

    红色区域代表匹配了的部分.

    图片过于丑陋还请原谅.

    回到正题,根据(next[j])的定义,这样的(k)是不存在的(否则(next[j])就等于(k))

    因此直接跳到(next[j])肯定是更优的.

    但是,(next[j])怎么求呢?

    step 3

    仔细康康(next)(f)的定义,是不是非常像?

    没错,我们就可以像求(f)一样,直接求(next).

    就相当于自己与自己匹配一次.

    code:

    inline void getnext(){
    	Next[1]=0;
    	for(int i=2,j=0;i<=la;i++){
    		while(j>0&&a[i]!=a[j+1]) j=Next[j];
    		if(a[i]==a[j+1]) j++;
    		Next[i]=j;
    	}
    }
    

    所以,这个问题就解决了!

    总的代码:

    #include <iostream>
    #include <cstdio>
    #include <cstring>
    #define fre(x) freopen(x".in","r",stdin),freopen(x".out","w",stdout)
    using namespace std;
    
    inline int read(){
    	int sum=0,f=1;char ch=getchar();
    	while(ch>'9'||ch<'0'){if(ch=='-')f=-1;ch=getchar();}
    	while(ch>='0'&&ch<='9'){sum=sum*10+ch-'0';ch=getchar();}
    	return f*sum;
    }
    
    const int N=1000005;
    int T,la,lb,f[N],Next[N],ans;
    char a[N],b[N];
    
    inline void getnext(){
    	Next[1]=0;
    	for(int i=2,j=0;i<=la;i++){
    		while(j>0&&a[i]!=a[j+1]) j=Next[j];
    		if(a[i]==a[j+1]) j++;
    		Next[i]=j;
    	}
    }
    
    inline void kmp(){
    	for(int i=1,j=0;i<=lb;i++){
    		while(j>0&&(j==la||b[i]!=a[j+1])) j=Next[j];
    		if(b[i]==a[j+1]) j++;
    		f[i]=j;if(f[i]==la) ans++;
    	}
    }
    
    signed main(){
    	T=read();
    	while(T--){
    		scanf("%s%s",a,b);
    		la=strlen(a),lb=strlen(b);
    		for(int i=la;i>=1;i--) a[i]=a[i-1];
    		for(int i=lb;i>=1;i--) b[i]=b[i-1];
    		getnext();kmp();		
    		printf("%d
    ",ans);ans=0;
    	}
    	return 0;
    }
    
  • 相关阅读:
    IO操作之BIO、NIO、AIO
    IO之Socket网络编程
    this.getClass()和super.getClass()得到的是同一个类
    经济增长的背后
    SVN分支创建与合并
    .net类库里ListView的一个BUG
    VS2010调试技巧
    用C#代码编写的SN快速输入工具
    请教如何改善C#中socket通信机客户端程序的健壮性
    利用WebClient实现对Http协议的Post和Get对网站进行模拟登陆和浏览
  • 原文地址:https://www.cnblogs.com/zsq259/p/11745658.html
Copyright © 2020-2023  润新知