KMP算法的详细讲解

KMP算法

KMP算法是一种改进的字符串匹配算法。

讲述KMP算法之前,我们要先建立一个概念,没有这个概念,KMP很难理解。
那,我们先忘掉原问题,来进行一个概念建设。这个概念就是:在一个字符串中,一个字符之前的字符串最长前缀和最长后缀的匹配长度,有点绕,我们举个例子吧:

在这里插入图片描述
对于字符d,我们想求一个信息,就是d之前的字符串最长前缀和最长后缀的匹配,同时我们要加一个限定:前缀不能包含最后一个字符,后缀也不能包含第一个字符,也就是说:当前缀长度取1时,只有一个字符a,后缀长度也取1时,只有字符c,a和c字符不相等,所以我们说当前缀和后缀长度都取1时不相等;同理,当前缀和后缀长度都取2时,即ab和bc也不相等;当前缀和后缀长度都取3时相等,abc和abc,我们找到一种相等的情况,但它是最长的吗?还得再试,当前缀和后缀长度取4时,为abca和cabc,不相等;当取5时,为abcab和bcabc不相等;当取6时相等,但此时这种情况我们不能要,因为我们已经限定了:前缀不能包含最后一个字符,后缀也不能包含第一个字符。那所有相等的可能性中,长度为3时最长。所以我们说d位置上的信息,我想记录的话,为3。这个3的含义就是d前面的字符串最长前缀和最长后缀的匹配长度。
现在,最长前缀和最长后缀的概念就建立起来了



回到我们算法本身,现在要求的是:str1包不包含str2。 我们根据str2求最长前缀与最长后缀这个信息,比如说 str2为“ababac”,我们每个位置的信息都求,也就是说我们求解的东西是个数组,叫做 next数组

这里我们把求next数组的信息写成一个函数,对于0位置,在求的过程中,因为它前面没有字符串,所以默认-1,即 next[0]=-1;1位置上是b,它前边的字符串只有a,而我们的概念里又说了,前缀不能包含最后一个字符,后缀也不能包含第一个字符,而它前边的字符串只有一个字符,人为规定0, next[1]=0;对于2位置的信息,因为它前边只有两个字符,所以它俩相等就为1,不等就为0,由于a和b不相等,此时next[2]=0;对于后面的每一个位置都可以按照某种方法非常快的求出该字符之前的字符串最长前缀和最长后缀的匹配长度,求解得到完整的next数组应为 next=[-1,0,0,1,2,3], 这个方法我们后边详细讲

然后我们先把KMP的整个过程,看看用这个信息是怎么加速的,完成之后再去剖析这个next数组到底怎么又快又对的把它解出来。

我们有这样的信息后,怎么做的? 假设现在在str1中开始的位置是i位置,它和str2的0位置开始配,直到str1的X位置和str2的Y位置没配上,那 之前我们一般想的解决办法是:如果i位置往下配,如果有一个位置没配上,我们又从i+1位置开始再重新和str2的0位置匹配…那现在我们看看有了这个信息之后,此时我们不把i位置回退到i+1位置再重新匹配, 而现在我们关于str2的Y位置求了一个信息(Y之前最长前缀和最长后缀的匹配长度),所以在str2中就有两个相同子串(可重叠),我们将最长后缀的第一个字符对应到str1中,假设是j位置,我们就拿str1的j位置和str2的0位置开始往下配,同时因为后缀和前缀是相等的,所以等量的推过来,前缀给你匹配的字符也是最长后缀的长度,即我们此时还是j位置和0位置往下配,但是因为str1的j位置到X位置之间的字符和str2的0位置和Z位置之间的字符是相等的,所以我们不用再费周折进行比较,可以直接跳到str1的X位置和str2的Z位置进行比较往下配,以此类推,就是这么个流程。
在这里插入图片描述
我们来个例子吧:
在这里插入图片描述
我们可以看到str1的p位置 'a’字符 和 str2的q位置 ‘f’ 字符不等,其余都相等;

  • 按照以往的方法,又重新比较str1的1位置和str2的0位置;
  • 而现在由于str2每个位置上的信息(next数组),str2下标q位置上 ‘f’ 字符的最长前缀与最长后缀长度为3,即划线1部分和2部分相等,又由于字符串str1中字符 'a’之前所有的字符都相等,所以对应到str1的3部分和str2的2部分相等,所以,str1的3部分=str2的2部分=str2的1部分,所以我们从str1的j位置与str2的0位置比较,由于str1的3部分=str2的2部分,我们不用再重新进行比较,可以从str1的p位置与str2的k位置进行匹配,由于a和a相等,我们继续往下比,直到str1的r位置与str2的q位置字符不相等,此时由于str2字符串中q位置的最长前后缀信息是3,所以我们同上,将str2再向后移动3个长度为止与str1进行比较,由于最长前后缀的存在,我们可以不用比较字符也知道str1的p->r位置上的字符和str2的0->k位置上的字符相等,因此我们只需比较str1的r+1->t位置上的字符和str2的k+1->q位置上的字符,由于str1已经到了字符串末尾,所以匹配成功。
    重点:每次str2对不上,str2都往右推,而且有一段是我不需要配的,由 可能能对上的位置开始继续往下配

以上过程较为简单,主要是在于如何求next数组。

next数组中0到2位置上的值我们已经讲过, 接下来的位置用数学归纳法,在i位置上的时候,假设前边已经正确求解了所有next数组的值,那我想求i位置上next数组的值,0到i位置上已经正确算过了,如果我能利用之前的结论求出i位置上最长前缀与最长后缀的匹配长度,我自然可以顺利的推导i+1位置上的。(:至于为什么i位置上的信息只需要比较i-1位置上的字符与其最长前缀的下一个字符比较,且为什么是最长的,可以拿数学推导哦)

现在假设next数组中 i-1位置上的信息是4,那求i位置上的信息就变得很简单,只需比较i-1位置上的字符与j位置上的字符是否相等,

  • 相等,则i位置上的信息就是i-1上的信息+1,为4+1=5;
    在这里插入图片描述
    举例:求最后一个位置的next数组信息
    在这里插入图片描述
  • 不相等,再向前比较,将j位置上跳出来的两块,看其最长前缀的下一个字符(p位置)是否与b相等,若相等,则i位置上的信息就是p上的信息+1,为2+1=3;若不相等,再向前比较,将p位置上跳出来的两块,看其最长前缀的下一个字符是否与b相等,以此类推~

在这里插入图片描述
举例:求最后一个位置的next数组信息
在这里插入图片描述
:上述说的j位置跳出来的最长前后缀长度为2,但实际算出来的不一定都是2哦

代码:

public static int getIndexOf(String s, String m) {
		if (s == null || m == null || m.length() < 1 || s.length() < m.length()) 
			return -1;
		char[] ss = s.toCharArray();
		char[] ms = m.toCharArray();
		int si = 0;
		int mi = 0;
		int[] next = getNextArray(ms);
		while (si < ss.length && mi < ms.length) {
			if (ss[si] == ms[mi]) {
				si++;
				mi++;
			} else if (next[mi] == -1) {
				si++;
			} else {
				mi = next[mi];
			}
		}
		return mi == ms.length ? si - mi : -1;
	}

	public static int[] getNext(char[] ms) {
		if (ms.length == 1) 
			return new int[] { -1 };
		int[] next = new int[ms.length];
		next[0] = -1;
		next[1] = 0;
		int pos = 2;
		int cn = 0;
		while (pos < next.length) {
			if (ms[pos - 1] == ms[cn]) {
				next[pos++] = ++cn;
			} else if (cn > 0) {
				cn = next[cn];
			} else {
				next[pos++] = 0;
			}
		}
		return next;
	}

kmp算法就是这样子的哦~~

评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值