zoukankan      html  css  js  c++  java
  • 『笔记』KMP算法 字符串匹配(看猫片)

    前言

    此篇笔记根据自己的理解和练习心得来解释算法,只代表个人观点,如有不足请指出(我刚学QWQ)

    浅谈字符串匹配

    设想一个场景,假设你是一个净化网络语言环境的管理员,每天需要翻阅大量的文章和帖子来查找敏感字,那么这个时候很简单的可以想到可以运用字符串匹配来做到,但是朴素的单模式字符串匹配耗用时间多,那么在这里我们就可以运用高效率的(KMP)算法来解决。

    算法对比

    • 朴素单模式字符串匹配算法

    朴素的单模式串匹配大概就是枚举每一个文本串元素,然后从这一位开始不断向后比较,每次比较失败之后都要从头开始重新比对
    给定一个文本串(要查找符合条件的字符串),以及一个模式串(需要匹配的字符串)

    模式串:abcab
    文本串:abcacababcab
    

    一般的思想就是,当我们在第五位失配时,我们会从当前模式串的第一位所处在文本串的位置的后一位开始与模式串的第一位进行匹配,直到匹配成功位置,就会出现以下的情况

    模式串:   abcab
    文本串:abcacababcab
    

    这一种算法的正确性是100%的,但是复杂度显然不是那么喜人,一般来说,期望时间复杂度可以为(O(n+m))的,但是一些有心的出题人甚至可以卡到(O(nm)),所以这个算法仅仅用于暴力打法即可。

    • (KMP)字符匹配算法

    对于失配以后的字符串,不需要去从头开始枚举浪费时间,而是根据预先处理好的值来进行枚举即可,也就是寻找最优历史处理,
    根据处理的过程,时间复杂度为(O(n+m))

    依旧是给定一个样例来说明

    模式串:abcabc
    文本串:abcabdababcabc
    

    在这个样例中我们可以看到,在第六位的时候失配了,这个时候有一个(KMP)算法的专门小(trick),我们观察字符串可以发现的是,在模式串中,失配的前一位第五位在这整个模式串中可以找到一个和他相匹配的字符,也就是第二位的(b),那么我们现在就可以把模式串的匹配长度跳转到(2)继续查找,那么为什么可以这样做呢,因为我们可以发现,在匹配的时候,第五位的(b)之所以可以转移到第二位,靠的就是以第五位结尾的一个子串,可以与从(1-2)这个范围内的子串一一对应起来,也就是完全的按位匹配。

    那么转移过来就是这样的

    模式串:   abcabc
    文本串:abcabdababcabc
    

    通过这个方法我们可以节省一大堆无用的时间。

    思路分析

    首先设置(kmp)数组为失配数组,也就是存储当匹配失败后跳转到接下来模式串匹配的最优的位置,因为相比较于文本串,模式串更加的灵活多变,处理起来也很方便,那么处理失配数组的时候将模式串当做处理串。

    那么核心就在于如何来处理失配数组的值

    我们要明确的是,在上面条件的基础上,我们要考虑的是当模式串的第(i)为失配以后,如何来调到最优的位置继续进行匹配,因为在文本串当中(i)以前的位置都已经失效了,那么我们对于每一个(kmp_i)要记录的是:

    在模式串(str)中,我们可以找到一个最优的位置(j),满足(igeq j)并且满足(str_i=str_j),并且在(j!=1)的时候,有从(str_1-str_{j-1})分别与(str_{i-j+1}-str_{j-1})按位匹配。

    简单来说就是在模式串中,存在一个长度为(len)的以(1)开头以(j)结尾的子串与以(i-j+1)开头以(i)结尾的子串完全相同

    代码实现

    #include<iostream>
    #include<cstring>
    #include<stack>
    #include<algorithm>
    #include<cmath>
    #include<cstdio>
    #include<queue>
    #include<map> 
    using namespace std;
    const int N=1e6+9; 
    int kmp[N],j;
    int lena,lenb;
    char a[N],b[N]; 
    int main()
    {
    	cin>>a+1;
    	cin>>b+1;
    	lena=strlen(a+1);
    	lenb=strlen(b+1);
    	//预处理kmp数组 
    	for(int i=2;i<=lenb;i++)//第一个字符一定是匹配的所以不需要i=1 
    	{
    		while(b[i]!=b[j+1]&&j)//如果不匹配就往回跳 
    		j=kmp[j];
    		if(b[i]==b[j+1])j++;//遇见相同的就向右移动 
    		kmp[i]=j; 
    	} 
    	j=0;
    	for(int i=1;i<=lena;i++)
    	{
    		while(j&&b[j+1]!=a[i])
    		j=kmp[j];//通过自己匹配自己来求得每一个点的kmp值 
    		if(b[j+1]==a[i])
    		j++;
    		if(j==lenb)
    		{
    			printf("%d
    ",i-lenb+1);//这个是开始的值 
    			j=kmp[j]; 
    		}
    	}
    	for(int i=1;i<=lenb;i++)
    	printf("%d ",kmp[i]);
    	return 0; 
    }
    
  • 相关阅读:
    (转)基于C#的socket编程的TCP异步实现
    socket
    (转)抽象类、抽象属性、抽象方法
    (转)c# 互斥锁
    (转)C#连接OleDBConnection数据库的操作
    c# DLL封装并调用
    网络cmd命令
    (转)UCOSII在任务切换与出入中断时堆栈指针的使用
    app和bootloader跳转 MSP与PSP
    (转)stm32启动文件详解
  • 原文地址:https://www.cnblogs.com/1123LXY/p/14116814.html
Copyright © 2011-2022 走看看