• 从暴力匹配到KMP算法


    前言

    现在有两个字符串:(s1)(s2),现在要你输出(s2)(s1)当中每一次出现的位置,你会怎么做?

    暴力匹配算法

    基本思路

    用两个指针分别指向当前匹配到的位置,并对当前状态进行分类讨论:若相同则继续往下匹配,否则回溯

    大致思路

    (i)来存储(s1)当前匹配到的位置,用(j)来存储(s2)当前匹配到的位置,则可得初始状态下(i=j=0)

    对于当前状态,有两种可能性:

    ①:(s1[i]==s2[j])。则(i++,j++)

    ②:(s1[i]!=s2[j])。则(i-=(j-1),j=0)

    评价

    时间复杂度:(O(nm))。显然,这个方法效率并不高,每一次回溯要耗去大量时间,能不能进行优化呢?

    (KMP)算法

    简介

    (KMP)算法是对暴力匹配算法的改进,由(D.E.Knuth)(J.H.Morris)(V.R.Pratt)同时发现,因此人们称它为(Knuth-Morris-Pratt)算法(简称(KMP)算法)。

    基本思路

    (KMP)算法的关键是利用匹配失败后的信息,尽量减少模式串与主串的匹配次数以达到快速匹配的目的。具体实现就是实现一个(Next)函数,函数本身包含了模式串的局部匹配信息。

    大致思路

    还是用(i)来存储(s1)当前匹配到的位置,用j来存储(s2)当前匹配到的位置,则可得初始状态下(i=j=0)
    对于当前状态,有两种可能性:
    ①:(s1[i]==s2[j])。则(i++,j++)
    ②:(s1[i]!=s2[j])。则(j=Next[j])(i不变)
    其中(Next)数组存储的是当前这一位的部分匹配值(这在后面会详细介绍),所以只要让(j)变成(Next[j]),就可以继续对当前字符串进行匹配了,省去了i回溯所耗去的大量时间

    (Next)数组

    在匹配过程中,你可以发现一个基本事实是:当(s1[i])(s2[j])不匹配时,你其实知道前面(j-1)字符是什么。

    (KMP)算法的想法是,设法利用这个已知信息,不要把"搜索位置"移回已经比较过的位置,继续把它向后移,这样就提高了效率。

    所以,我们就可以把当前所得到的部分匹配值给求出来。又由于对于同一个字符串,部分匹配值是固定不变的,所以可以把它存在(Next)数组里。

    那么(Next)数组怎么求呢?

    记得某大佬说过这样一句话:

    (Excerpt)

    (Next)数组的过程就是一个(KMP)的过程。

    首先,令(i=0)(j=-1)(Next[0]=-1),且当前要求的是(Next[i+1])。则对于当前状态,有两种可能性:
    (j==-1)(s2[i]==s2[j])。则(i++,j++,Next[i]=j)
    (j!=-1)(s2[i]!=s2[j])。则(j=Next[j])//把(j)赋值为(j)的部分匹配值
    这样就可以轻松求出(Next)数组了。

    代码

    #include<bits/stdc++.h>
    #define N 1000000
    #define pc(ch) (pp_<100000?pp[pp_++]=ch:(fwrite(pp,1,100000,stdout),pp[(pp_=0)++]=ch))
    int pp_=0;char pp[100000];
    using namespace std;
    int len1,len2,Next[N+5];//len1存储s1的长度,len2存储s2的长度,这样不用调用strlen(),strlen()会超时;Next[]存储部分匹配值 
    char s1[N+5],s2[N+5];
    inline void write(int x)
    {
        if(x>9) write(x/10);
        pc(x%10+'0');
    }
    inline void GetNext()//求出Next[]数组 
    {
    	register int i=0,j=Next[0]=-1;//初始化 
    	while(i<=len2)//类似于一个KMP的过程 
    	{
    		if(j==-1||s2[i]==s2[j]) i++,j++,Next[i]=j;
    		else j=Next[j];
    	}
    }
    int main()
    {
    	register int i=0,j=0;
    	scanf("%s%s",s1,s2),len1=strlen(s1),len2=strlen(s2),GetNext(); 
    	while(i<=len1)//KMP的过程 
    	{
    		if(j==-1||s1[i]==s2[j]) {++i;if(++j==len2) write(i-len2+1),pc('
    '),j=Next[j];/*如果找到答案就输出*/} 
    		else j=Next[j];//如果匹配失败,就更新j为其部分匹配值 
    	}    
    	for(i=1;i<=len2;++i) write(Next[i]),pc(' ');//依照题意输出Next[]数组 
    	return fwrite(pp,1,pp_,stdout),0;
    }
    
  • 相关阅读:
    WCF相关
    MiniUI级联
    大家一起来学 NHibernate+NUnit (VS2012+SQL Server2008)
    C# 复杂算法
    sql自定义日期函数,返回范围内日期和星期数表。
    RDLC开发笔记
    解决IE7和IE6不支持javaScript中的indexOf函数的问题
    Sql获取周、月、年的首尾时间。
    Sql Server中实现Mysql中的group_concat函数效果
    RDLC隔行变色的实现
  • 原文地址:https://www.cnblogs.com/chenxiaoran666/p/KMP.html
Copyright © 2020-2023  润新知