KMP匹配

20240708 add:

 B站视频动画:最浅显易懂的 KMP 算法讲解_哔哩哔哩_bilibili

next数组的作用

        记录了模式串和主串(文本串)不匹配时,模式串应该从哪里开始匹配(应该跳过的个数)。

        如下图,当在C不匹配,使用最后一个匹配的字符所对应的next值作为下一轮模式串开始比对的位置。

next数组是什么

        记录下标i之前(包括i)的字符串中,有多大长度的相同前缀后缀

为是什么next数组有这个功能?

        当发生不匹配时,最后一个匹配的字符所对应的next值就是前缀、后缀中最长的相同部分,这部分已经相同,不用再比较了

 leetcode 28.找出字符串中第一个匹配项的下标        

整体流程:

1.计算next数组

f

以下为原文:

____________________________________________________________________________

KMP匹配:
定义:KMP是一种改进的字符串匹配算法,其关键是利用匹配失败后的信息,尽量减少模式串和主串的匹配次数以达到快速匹配的目的。

1.KMP做到主串的位置指针i不回退,不像朴素匹配那样在发生不匹配时需要回退主串的位置指针,仅移动模式串的位置指针即可完成匹配。
相关概念:
前缀:不包含最后一个字符的所有以第一个字符开头的连续子串(从左向右扫描),比如字符串abaca,其前缀有:a,ab,aba,abac
后缀:不包含第一个字符的的所有以最后一个字符结尾的连续子串(从左到右扫描),比如字符串abaca,其后缀有:baca,aca,ca,a
最长公共前后缀:就是前缀和后缀中最长的公共部分。上述中就是a。

2.KMP匹配流程
2-1: 开始比较,当模式串的第x个位置的字符和主串第x个位置的字符不相等时,寻找模式串x位置(不包含x)左边的最长公共前后缀,将模式串从前缀位置移动到后缀位置。
2-2: 继续移动主串位置指针,重复2-1。

3.next数组相关

next数组的作用是当发生不匹配时,知道下一步该怎么做。即下一步数组。
next数组大小和模式串大小相等,当模式串某个位置发生不匹配时,就取当前位置对应的next数组的对应值,在对应值的位置继续比较主串和模式串。
next数组表示的是模式串发生不匹配时,不匹配位置左边串(不包含当前位置)的最大公共前后缀
比如模式串为:ABCABCDE 对应的next数组值为:-1,0,0,0,1,2,3,0

如何求next数组:当知道next[k]的值,如何求next[k+1]的值?(前提条件:模式串的位置指针为p)

  1. next[0]=-1,next[1]=0,next[2]开始递归求解
  2. 若p[k+1] == p[next[k]+1],则next[k+1] = next[k]+1
  3. 若p[k+1] != p[next[k]+1],则继续判断若p[k+1] == p[next[next[k]]+1]

学习过程中的一些tips:

1.next数组一定是递增的吗?
不一定,比如模式串是ABCABED,那么字符‘E’处的next值为2(最长公共前后缀为AB),但是字符‘D’处的next值为0。

2.KMP匹配的难点是next数组的求法及为什么当主串与模式串不匹配时,可以用next数组的方式移动模式窜的位置指针(不回退主串的位置指针)。
 

参考博文:https://blog.csdn.net/v_july_v/article/details/7041827 图文并茂,清晰易懂,非常感谢。

C语言代码如下:

#include<stdio.h>
#include<stdlib.h>
#include<unistd.h>
#include<signal.h>  //signal()
#include<string.h>
#include<sys/stat.h>
#include<time.h>
#include<stdarg.h>

#if 1
#define INFO_DEBUG    "%d lines in "__FILE__", complie time is "__TIME__" "__DATE__" \n",__LINE__
#define ADI_RUN_ERROR      1
#define ADI_NULL_POINTER  -1
#define ADI_OK             0a
#define ADI_PRINT         printf("[%s][%d]:",__FUNCTION__,__LINE__);printf
#define ADI_ASSERT(c,prt,ret)       if(c) {printf(prt);return ret;}
#endif

#define SUCCESS 0
#define NULL_POINTER 1

int strLength(char *str)
{
    int length = 0;
	while('\0' != str[length])
	{
	    length++;
	}
	return length;
}

/*串str_main和str_sub都存在,str_sub是非空串。若主串str_main中存在和str_sub相同的子串,则返回它在主串str_main中第pos个字符之后第一次出现的位置,否则返回0
pos从0开始。*/
int Index(char *str_main,char *str_sub,int pos)
{
    ADI_ASSERT((NULL == str_main)||(NULL == str_sub),"NULL pointer\n",NULL_POINTER);
    int size_main = strLength(str_main);
    int size_sub = strLength(str_sub);

    int start_compare = pos;
    while(start_compare+size_sub<=size_main)
    {
    		if(0 == memcmp(&str_main[start_compare],&str_sub[0],size_sub))
		{
			return start_compare-pos;
		}
		start_compare++;
    }

    return 0;
	
}

/*实现上述Index不使用memcmp做判断,即朴素的模式匹配*/
int Index_ps(char *str_main,char *str_sub,int pos)
{
    ADI_ASSERT((NULL == str_main)||(NULL == str_sub),"NULL pointer\n",NULL_POINTER);
    int size_main = strLength(str_main);
    int size_sub = strLength(str_sub);
    int i=0;

    int start_compare = pos;
    while(start_compare+size_sub<=size_main)
    {
		while(str_main[start_compare+i] == str_sub[i])
		{
			i++;
		}
		if(i == size_sub)
		{
			return start_compare-pos;
		}
		else 
		{
			i=0;
		}
		start_compare++;
    }

    return -1;
	
}

/*求next数组*/
void get_next(char* str, int next[])
{
	int size_str = strLength(str);
	
	int pos_str = 0;  //模式串的位置,从0开始
	/*k=-1是next[0]的初值,
	整个循环过程中k为模式串str当前字符str[pos_str]的前一个字符str[pos_str-1]的最长公共前后缀中前缀的后一个位置pos
	在递归过程中,若str[k] == str[pos_str],则当前字符str[pos_str]的最长公共前后缀可由上一个字符的最长公共前后缀+1计算
	*/
	int k = -1;       
	next[0] = k;

	while(pos_str<size_str)
	{
	    /*str[k]是前缀的最后一个字符,str[pos_str]是后缀的最后一个字符,若相等,则最长公共前后缀加1*/
		if(-1 == k||str[k] == str[pos_str])
		{
		    pos_str++;
			k++;
			next[pos_str] = k;
		}
		else
		{
			k = next[k];
		}
	}

	for(int i=0;i<size_str;i++)
	{
		printf("%d ",next[i]);
	}
	printf("\n");
}

/*返回值为主串和模式串匹配成功的在主串中的起始位置
若返回-1,说明匹配失败*/
int Index_kmp(char *str_main,char *str_sub)
{
	ADI_ASSERT((NULL == str_main)||(NULL == str_sub),"NULL pointer\n",NULL_POINTER);
    int size_main = strLength(str_main);
    int size_sub = strLength(str_sub);
    int pos_main = 0,pos_sub = 0;
    int next[size_sub];
    get_next(str_sub,next);

    while(pos_main<size_main && pos_sub<size_sub)
    {
       //匹配成功或-1 == pos_sub(意味着模式串的当前位置没有公共前后缀)
		if(str_main[pos_main] == str_sub[pos_sub] || -1 == pos_sub)
		{
			pos_main++;
			pos_sub++;
		}
		else 
		{
			pos_sub=next[pos_sub];
		}
    }

    if(pos_sub == size_sub)
    {
		return pos_main-size_sub;
    }
    else
    {
		return -1;
    }
	
}


int main()
{
    int pos = Index_kmp("ABABCABCABCDE","ABCABCD");
    if(-1 != pos)
    {
		ADI_PRINT("KMP success! pos = %d\n",pos);
    }
    else
    {
		ADI_PRINT("can not find !\n");
    }

    
    return SUCCESS;
}



  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值