字符串的模式匹配(BF,KMP)

BF

//利用字符数组,也可直接用字符串相关函数
static int indexOf(String text,String pattern) {
	 if(text==null||pattern==null) return -1;
	 char[] textchars=text.toCharArray();
	 char[] patternchars=pattern.toCharArray();
	 int tlen=textchars.length;
	 int plen=patternchars.length;
	 if(tlen==0 || plen==0) return -1;
	 if(tlen<plen) return -1;
	 
	 int ti=0,pi=0;
	 while(ti<tlen && pi<plen) {
		 if(textchars[ti]==patternchars[pi]) {
				 pi++;
				 ti++;
		 }else {
			 ti-=pi-1;//找到主串重新开始的下一个位置
			 pi=0;
		 }
	 }
	 return pi==plen?ti-pi:-1;//返回子串在主串的位置
	 
}

优化1:

//在主串中剩余长度小于子串长度的时候可以提前退出,减少比较次数
	static int indexOf2(String text,String pattern) {
		 if(text==null||pattern==null) return -1;
		 char[] textchars=text.toCharArray();
		 char[] patternchars=pattern.toCharArray();
		 int tlen=textchars.length;
		 int plen=patternchars.length;
		 if(tlen==0 || plen==0) return -1;
		 if(tlen<plen) return -1;
		 
		 int ti=0,pi=0;
		 int Distance=tlen-plen;
		 while(pi<plen && ti-pi<=Distance) {
			 if(textchars[ti]==patternchars[pi]) {
					 pi++;
					 ti++;
			 }else {
				 ti-=pi-1;
				 pi=0;
			 }
		 }
		 return pi==plen?ti-pi:-1;
		 
	}

字符串的模式匹配(BF,KMP)_第1张图片
如果上面的图不匹配,那下面就不用匹配了,直接退出循环
字符串的模式匹配(BF,KMP)_第2张图片

找到临界值10-4=6的位置,ti-pi是文本串正在匹配的子串的开始索引,当ti=7,pi=0时,没有比较的必要,立即退出
ti – pi 是指每一轮比较中 text 首个比较字符的位置,如8-2=6,符合要求,可以比较

优化2

static int indexOf3(String text,String pattern) {
	 if(text==null||pattern==null) return -1;
	 int tlen=text.length();
	 int plen=pattern.length();
	 if(tlen==0 || plen==0 || tlen<plen) return -1;
	 
	int tiMax=tlen-plen;
	for(int ti=0;ti<=tiMax;ti++) {
		int pi=0;
		for(;pi<plen;pi++) {
			if(text.charAt(ti+pi)!=pattern.charAt(pi))break;
		}
		//退出只有两个可能:1.pi==plen匹配完了  2.text与pattern不等
		if(pi==plen) return ti;
	}
	return -1;
}

ti的含义:每一轮比较中文本串(主串)首个比较字符的位置
字符串的模式匹配(BF,KMP)_第3张图片

性能分析

字符串的模式匹配(BF,KMP)_第4张图片
最好情况
只需一轮比较就完全匹配成功,比较 m 次( m 是模式串的长度)
时间复杂度为 O(m)

最坏情况(字符集越大,出现概率越低)比如汉字比较
执行了 n – m + 1 轮比较( n 是文本串的长度)
每轮都比较至模式串的末字符后失败( m – 1 次成功,1 次失败)
时间复杂度为 O(m ∗ (n − m + 1)),由于一般 m 远小于 n,所以为 O(mn)

KMP

字符串的模式匹配(BF,KMP)_第5张图片
KMP的精妙之处:充分利用了此前比较过的内容,可以很聪明地跳过一些不必要的比较位置
字符串的模式匹配(BF,KMP)_第6张图片

next数组的使用

模式串的移动距离是以前比较的pi-next[pi]
字符串的模式匹配(BF,KMP)_第7张图片

字符串的模式匹配(BF,KMP)_第8张图片

核心原理

字符串的模式匹配(BF,KMP)_第9张图片
当 d、e 失配时,如果希望 pattern 能够一次性向右移动一大段距离,然后直接比较 d、c 字符
前提条件是 A 必须等于 B
所以 KMP 必须在失配字符 e 左边的子串中找出符合条件的 A、B,从而得知向右移动的距离

向右移动的距离:e左边子串的长度 – A的长度,等价于:e的索引 – c的索引
且 c的索引 == next[e的索引],所以向右移动的距离:e的索引 – next[e的索引]

总结
如果在 pi 位置失配,向右移动的距离是 pi – next[pi],所以 next[pi] 越小,移动距离越大,这样会忽略,所以要取最大公共子串长度
next[pi] 是 pi 左边子串的真前缀后缀的最大公共子串长度

前缀后缀的最大公共子串长度

字符串的模式匹配(BF,KMP)_第10张图片
字符串的模式匹配(BF,KMP)_第11张图片
为什么要将next数组的第一个赋值为-1呢?
next数组的目的是那个字符失配就去查那个字符在next表中的值。
0号位置失配,查表得-1,pi++后=0,模式串又从0开始比较

next表的代码实现

static int[] next(String pattern) {
	int len=pattern.length();
	int[] next=new int[len];
	int i=0;
	int n=next[i]=-1;
	int imax=len-1;
	while(i<imax) {
		if(n<0||pattern.charAt(i)==pattern.charAt(n)) {
			next[++i]=++n;
		}else {
			n=next[n];
		}
	}
	return next;
}

反过来推,next[i]=n,说明i前面两个蓝色的A(包括A)的字符串最长公共子串是n。
如果i字符与n字符不相等,就要去找next[n]的最长公共子串,就是A(可以看到就是A的长度=k ),后面一个k是索引
如果i字符等于k字符,那next[i+1]=k+1
字符串的模式匹配(BF,KMP)_第12张图片

KMP主算法代码

static int KMP(String text,String pattern) {
	 if(text==null||pattern==null) return -1;
	 char[] textchars=text.toCharArray();
	 char[] patternchars=pattern.toCharArray();
	 int tlen=textchars.length;
	 int plen=patternchars.length;
	 if(tlen==0 || plen==0 ||tlen<plen) return -1;
	 
	 int[] next=next(pattern);
	 int ti=0,pi=0,tmax=tlen-plen;
	 while( ti-pi<=tmax && pi<plen) {
		if(pi<0 || textchars[ti]==patternchars[pi]) {
			 pi++;
			 ti++;
		}else {
		 pi=next[pi];
	 	}
	 }
	 return pi==plen?ti-pi:-1;
	 
}

你可能感兴趣的:(java实现数据结构,字符串,算法)