KMP

本文深入讲解了KMP算法的工作原理及实现过程,包括next数组的构造方法及其在字符串匹配中的应用,通过实例帮助读者理解算法背后的逻辑。

KMP算法:消除了BF算法的主串指针在相当多个字符比较相等后,只要有一个字符比较不相等便需要回退的缺点。

KMP算法的主要思想:设s为主串,t为模式串,j为s串当前比较字符的下标,k为t串当前比较的字符的下标,令i和j 的初值为0。

          1,当 s(j)==t(k)时,j和k分别增1之后再比较。

          2,当不相等时,j不变,k改变为next[k]之后再比较。

next数组:存储的是子串匹配时的一个回溯的位置。

                  0    1    2     3    4    5    6    7   8    9  10   11   12  13

例如:        a    b    a    b    c    a    b    c    d    a    b    c    d    e         j

next数组    -1   0   0    1    2    0    1    2    0   0     1    2    0    0

next数组值的确定规则:在子串中存在两个真子串相等,并且一个以0位置开始,一个以(j-1)位置结束

      next[0]=-1        next[1]=0  这两个是固定的,不变的

      next[2]:看串“ab”,根据next数组值确定规则,可以确定不存在两个相等的子串,所以next[2]=0

      next[3]:看串“aba”,根据next数组值确定规则,可以确定有相同的串“a”,所以next[3]=1

     next[4]:看串“abab”,根据next数组值确定规则,可以确定有相同的串“ab”,next[4]=2

     next[5]:看串“ababc“,根据next数组值确定规则,可以确定没有相同的串,next[5]=0

    。。。。。。。。

    按照这个规则,可以写出next数组

得到next数组的代码:

void GetNext(char *sub,int next[])
{
	//求子串的next数组
	int  j=0,k=-1;
	int lenth=strlen(sub);
	next[0]=-1;
	next[1]=0;
	while(j<lenth)
	{
		if(sub[j]==sub[k] || k==-1)
		{
			next[++j]=++k;
		}
		else
		{
			k=next[k];
		}
	}
}

一脸懵逼,是不是。。。上述代码就是用来求解模式串中每个位置的next[]值。

下面具体分析,我把代码分为两部分来讲:

(1):i和j的作用是什么?

i和j就像是两个”指针“,一前一后,通过移动它们来找到最长的相同真前后缀

(2):if...else...语句里做了什么?


假设i和j的位置如上图,由next[j] = k得,也就是对于位置i来说,区段[0, j - 1]的最长相同真前后缀分别是[0, k - 1]和[j - k, j - 1],即这两区段内容相同

按照算法流程,if (P[j] == P[k]),则next[++j] = ++k;;若不等,则k = next[k],见下图:


next[k]代表[0, k - 1]区段中最长相同真前后缀的长度。如图,用左侧两个椭圆来表示这个最长相同真前后缀,即这两个椭圆代表的区段内容相同;同理,右侧也有相同的两个椭圆。所以else语句就是利用第一个椭圆和第四个椭圆内容相同来加快得到[0, j- 1]区段的相同真前后缀的长度。

细心的朋友会问if语句中k== -1存在的意义是何?第一,程序刚运行时,k是被初始为-1,直接进行P[j] == P[k]判断无疑会边界溢出;第二,else语句中k= next[k],k是不断后退的,若k在后退中被赋值为-1(也就是k = next[0]),在P[j] == P[k]判断也会边界溢出。综上两点,其意义就是为了特殊边界判断。

  完整代码:

#include<stdio.h>
#include<string.h>
void GetNext(char *sub,int next[])
{
	//求子串的next数组
	int  j=0,k=-1;
	int lenth=strlen(sub);
	next[0]=-1;
	next[1]=0;
	while(j<lenth)
	{
		if(sub[j]==sub[k] || k==-1)
		{
			next[++j]=++k;
		}
		else
		{
			k=next[k];//如果
		}
	}
}
int KMP(char *str,char *sub,int next[])
{
	int i=0;
	int j=0;
	int stlen=strlen(str);
	int sublen=strlen(sub);
	GetNext(sub,next);
	while(i<stlen && j<sublen)
	{
		if(str[i]==sub[j] || j==-1)
		{
			i++;
			j++;
		}
		else
		{
			j=next[j];
		}
	}
	if(j==sublen)
	{
		return i-j;
	}
	return -1;
}
void main()
{
	char str[]="abcababcabc";
	char sub[]="abcabc";
	int next[10]={0};
	int index=KMP(str,sub,next);
	for (int i = 0; i < strlen(sub); ++i)
    {
        printf("%d  ",next[i]);
    }
	 printf("\n");
	 printf("%d\n",index);
    
}


         



我们这里说的KMP不是拿来放电影的(虽然我很喜欢这个软件),而是一种算法。KMP算法是拿来处理字符串匹配的。换句话说,给你两个字符串,你需要回答,B串是否是A串的子串(A串是否包含B串)。比如,字符串A="I'm matrix67",字符串B="matrix",我们就说B是A的子串。你可以委婉地问你的MM:“假如你要向你喜欢的人表白的话,我的名字是你的告白语中的子串吗?” 解决这类问题,通常我们的方法是枚举从A串的什么位置起开始与B匹配,然后验证是否匹配。假如A串长度为n,B串长度为m,那么这种方法的复杂度是O (mn)的。虽然很多时候复杂度达不到mn(验证时只看头一两个字母就发现不匹配了),但我们有许多“最坏情况”,比如,A= "aaaaaaaaaaaaaaaaaaaaaaaaaab",B="aaaaaaaab"。我们将介绍的是一种最坏情况下O(n)的算法(这里假设 m<=n),即传说中的KMP算法。 之所以叫做KMP,是因为这个算法是由Knuth、Morris、Pratt三个提出来的,取了这三个人的名字的头一个字母。这时,或许你突然明白了AVL 树为什么叫AVL,或者Bellman-Ford为什么中间是一杠不是一个点。有时一个东西有七八个人研究过,那怎么命名呢?通常这个东西干脆就不用人名字命名了,免得发生争议,比如“3x+1问题”。扯远了。 个人认为KMP是最没有必要讲的东西,因为这个东西网上能找到很多资料。但网上的讲法基本上都涉及到“移动(shift)”、“Next函数”等概念,这非常容易产生误解(至少一年半前我看这些资料学习KMP时就没搞清楚)。在这里,我换一种方法来解释KMP算法。
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符  | 博主筛选后可见
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值