0
点赞
收藏
分享

微信扫一扫

从实例出发透彻理解KMP算法

玩物励志老乐 2022-04-13 阅读 25
java算法

从实例出发透彻理解KMP算法

前言

  1. 最近在leetcode刷算法题时,发现了一道在主字符串中找到子字符串位置的题目。
  2. 例如在“aacbfdes”中寻求“acbfdes” 凑近一看似乎非常简单,这不是有手就行吗,
  3. 但是如果主字符串换成论文级别的长度。一眼不能看出来,你又会怎么做呢

解法一:暴力解法

  • 何谓暴力解法:

    暴力解法就是说:你不是要找到和你完全一样的字符串吗,那我从主字符串一个个往后遍历,第一个开始找没找到,那就从第二个开始找,以此类推。
    请添加图片描述


    int BF(char S[],char T[])
	{

	int i=0,j=0;
	while(S[i]!='\0'&&T[j]!='\0')
	{
		if(S[i]==T[j])
		{
			i++;
			j++;
		}
		else
		{
			i=i-j+1;
			j=0;
		}
	}
	if(T[j]=='\0') return (i-j);     //主串中存在该模式返回下标号 
	else return -1;     //主串中不存在该模式 
	}

从图可以看出,一个个循环的话循环次数太多,耗时太多,不可取,于是某些大神便想出了kmp算法来化简。

解法二:KMP算法

  • 算法精髓:当出现不同字符的时候,算法要求的是:保证主字符串的位置不动,去选择子字符串的哪个字符与主字符串比较!

    举个例子:abca 和abea的比较
    当出现不同的时候,也就是c和e出现了不同,我们保证还是用主串的c接下去比较,但是要和abea的哪个字符串比较就是我们所要找到的点!

  • 首先我们先来看一个例子

假设有"abcabedf"主字符串和"abcabcde"来进行比较,
那么我们发现从第一个开始比较后,在e和c的时候出现了不相等,此时你会怎么比较?

  • 一:按照暴力解法一个个循环
  • 二:找出前面有哪个相等,直接将子串挪到那个位置,开始比较
    请添加图片描述

我们从图可以看到,他们在abcab的阶段都相等,仔细观察一下,我们第二次开始比较的时候,为什么不直接从e前面的ab开始比较呢,

在举个例子:**“abcaedf""abcafed”**的比较
请添加图片描述
发现了什么规律没有?

无论是abcab还是abca都有相同的开头和结尾,正是因为有了这个条件,我们做的正是,将相等部分的头部,放在了相等部分的尾部!!!(理解这个非常重要

举个例子:

"abcab相等的头部和尾部是不是ab"那我们做的第二次比较不正是将c前面的ab与c后面的ab放在一起吗

请添加图片描述

  • 来个小练习题:

    "ababa"的公共头和尾是什么呢?

    很好!正是aba 这个应该很容易理解,不做解释

    "a"的公共头和尾是什么呢?

    答案是没有公共头和尾,只有一个字符的时候,公共头和尾的长度是0(这里后面会再叙述一遍)

这个时候引出了一个问题,加入前面相等的部分没有相同的头和尾怎么办呢?

举个例子:“主串abcdef,子串abefd

第一次比较相同的部分是ab,但是发现ab并没有相同的头和尾,你会怎么移动?

都没有相同的部分让你挪了,还挪个锤子,直接拿让c和子串的头比较呗

请添加图片描述

如果到这里你全部都理解了,那么算法的精髓你掌握的差不多了!

接下来引出一个重要数组

####重要参数:next[ ]数组的定义
其实我们前面已经有涉及了,这边只是做一个解释

next[i]指的是下标为i这个元素前有公共的头和尾的长度
其中i的取值是当主串和子串不相同时,那个时候子串的下标

举个例子:主串:abcabef,子串:abcabcd

第一次比较不同的地方出现在e和c,那么此时i的值等于多少呢,等于5(数组的下标的计算都是从0开始的哦)
而next[5]代表的是,子串下标为5的元素之前的字符串的公共头和尾的长度,这里c之前的字符串是abcab,公共的头和尾是ab,ab的长度是2
**所以!next[5]的值为2!**而我们第二次比较是将主串的e与子串的c进行比较,而此时c的下标不正是next[5]的值2吗,这里我们可以大胆推测:

当出现不同字符时,主串的那个元素,下一次与子串进行比较的字符正是下标为next[i]的值,i的值正是当出现不同时,不同值位于子串的下表,next[i]是,出现不同时,不同元素之前的公共头和尾字符串的长度。

*这里规定:next[0]=-1,这个怎么理解呢?

首先i是0说明第一个就出现了不同,类似于abc与edc的比较,而子串的不同元素是e,他之前还有元素吗?没有了,所以为-1。

举个例子:abca和aec的比较,不同元素的下标为1,next[1]的值是多少呢,0,因为如果只有一个字符的时候,是没有公共头和尾的。那也就是说,第二次比较的时候,主串的b是和子串下标为next[1]也就是下标为0,即和a开始比较!

理解了上面说的全部!已经差不多了,接下来就是通过代码进行进一步掌握了!

求next[i]的代码这里不做解释,比较简单

void GetNext(char T[])
{
int j=0,k=-1;
next[j]=k;
while(T[j]!='\0')
{
	if(k==-1||T[j]==T[k])
	{
		j++;
		k++;
		next[j]=k;
	}
	else k=next[k];
}
}
  • kmp算法代码

	int KMPIndex(SqString s,SqString t)  //KMP算法
	{

	int next[MaxSize],i=0,j=0;
	GetNext(t,next);
	while (i<s.length && j<t.length) 
	{
		if (j==-1 || s.data[i]==t.data[j]) 
		{
			i++;j++;  			//i,j各增1
		}
		else j=next[j]; 		//i不变,j后退,现在知道为什么这样让子串回退了吧
    }
    if (j>=t.length)
		return(i-t.length);  	//返回匹配模式串的首字符下标
    else  
		return(-1);        		//返回不匹配标志
	}

这里解释为什么j=-1也要执行i++和j++这一部分

我们可以假设主串为"abc",子串为"def"

他们一开始从头就不相等,那我们是不是应该让b和e进行比较了呢

而按照代码来看,因为他们两个不相等,所以先进行

j=next[j];

这一步,为的是确定不相同之后,下一次主串应该比较子串的哪个字符,而此时next[0]=-1,这个时候便进入了逻辑

if (j==-1 || s.data[i]==t.data[j]) 
	{
		i++;j++;  			//i,j各增1
	}

i指向的变成了b,而j++就变成了0,也就是说再进行比较的时候是让b和子串下标为0的元素也就是a进行比较,这不正是实现了我们所希望了的a和d不同就让b和d开始比较吗?

最后的话

由于本人是第一次开始写博客,所以难免会有些不当的地方,如果有问题,请在评论区提出,本人一定会第一时间进行解答

举报

相关推荐

0 条评论