用于字符串匹配的KMP算法

在这先介绍字符串中前缀、后缀的概念,首先前缀是必包含第一个元素,不包含最后一个元素的;而后缀是必包含最后一个元素,不包含第一个元素的。一个字符串的前缀后缀是一个集合,可以有很多个。

例如,数组[b,a,b,a,b]中,前缀有{b},{b,a},{b,a,b},{b,a,b,a};后缀有{b},{a,b},{b,a,b},{a,b,a,b}

其中前缀和后缀的最长匹配度,即为它们最长能相同的字符数,即为{b,a,b},所以最长匹配度就是3.

 

如果我们有一个字符串S "babababcbabababb",有一个模板P ”bababb“,问S中是否有P存在,若存在则返回其在S中的位置。

这题我们固然可以用暴力法去破解,然而时间复杂度太高了,所以可以使用KMP算法求解。这里我们只是理解一下KMP算法,具体原理并不会讲。

首先我们要设一个指针i指向S的字符,之后会用i去扫描每个字符;设一个指针j指向P的字符,之后会用j去扫描每个字符。(先说一下,在KMP算法中i不需要回溯,只有j可能回溯)

KMP算法是如何来检测字符是否匹配的呢?

下面我们来绘图解释一下:

 

这个图是KMP匹配的过程,(标有红色的是S和P失配时的字符。注:此处P有多行只是将P每次失配过程按行展开而已)

  1. 首先i和j都从开头开始匹配,直到i==5,j==5时,我们看到P中第一行的那个红色b的那里,S[i]!=P[j],所以失配;(第一行时)
  2. 然后,i不变(记住不会像暴力法那样返回0),j是变成失配前的子数组{b,a,b,a,b}的前缀后缀最长匹配长度的值,即为3;(第二行时)
  3. 随后发现S[i]=c和P[j]=a,失配,i不变,j变成{b,a,b}的前缀后缀最长匹配长度的值,即为1;(第三行)
  4. 此时S[i] = c, P[j] = a,失配,i不变,j变成{b}的最长匹配长度的值,即为0;(第四行)
  5. 这时,S[i] = c, P[j] = b,还是失配,此时因为j已经为0了(最长匹配长度可以认为是-1了),即j<0,所以此时i++,j++。(第五行)
  6. 这时,一开始S[i] = b, P[j] = b,匹配,则i和j都加1,直到再次失配时,即为第六行红色的b那里P[j] = b时,再次失配,i继续不变,j继续回溯到失配前的子数组{b,a,b,a,b}的最长匹配长度的值那里,即j=3;(第六行)
  7. 最后当j等于P.length时,即为完全匹配成功,此时只需返回i-j的值,即为P在S中的位置

由上述过程我们可以看出,最关键的是把j回溯成前缀后缀最长匹配长度的值,所以构建的next数组即是存储这个值

说了这么多,我们还是用代码来好好看看next是如何构建的呢?

public class KMP {

	public static void main(String[] args) {
		String s = "babababcbabababb";
		String p = "bababb";
		System.out.println(indexOf(s, p));
	}
	
	public static int indexOf(String s, String p) {
		if(s.length() == 0 || p.length() == 0)	return -1;
		if(p.length() > s.length())	return -1;
		
		int[] next = next(p);
		int i = 0;	//s位置
		int j = 0;	//p位置
		int sLen = s.length();
		int pLen = p.length();
		
		while(i < sLen) {
			//1.如果j = -1, 或者当前字符匹配成功(即S[i] == P[j]),都令i++,j++
			//j = -1,因为next[0]=-1,说明p的第一位和i这个位置无法匹配,这时i,j都增加1
			//i移位,j从0开始
			if(j == -1 || s.charAt(i) == p.charAt(j)) {
				i++;
				j++;
			} else {//失配
				//2.如果j!=-1,且当前字符匹配失败(即S[i]!=P[j]),即令i不变,j=next[j]
				//next[j]都为j所对应的next值
				j = next[j];	//回溯
				//上述 j = next[j]的意思是:j失配,j回溯到next[j],记住next[j]是一个值一个数!
			}
			if(j == pLen) {	//匹配成功
				/*	可用来统计匹配的次数
				count++;
				i--;
				j = next[j-1];
				*/
				return (i-j);
			}
		}
		return -1;
	}
	
	public static int[] next(String ps) {
		int pLength = ps.length();
		int[] next = new int[pLength];	//next数组
		char[] p = ps.toCharArray();	//模式字符串
		next[0] = -1;    //显然可以直接把next[0]赋值成-1
		if(ps.length() == 1)
			return next;
		next[1] = 0;    //显然可以把next[1]赋值成0
		int j = 1;
		int k = next[j];	//看看位置j的最长匹配前缀在哪里
		while(j < pLength - 1) {
			//现在要推出next[j+1],检查j和k位置上的关系即可
			if(k < 0 || p[j] == p[k]) {
				next[++j] = ++k;
			} else {    //k>=0 && p[j]!=p[k]
				k = next[k];
			}
		}
		return next;
	}
}

em...其实这里我也不太知道next值具体是怎样计算的...原理是啥什么的也一脸懵...以后知道了再来修改博客...

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值