KMP算法字符串匹配

关于KMP的资料不算少,但是都不是很好理解(个人看法),所以自己整理一下,加深记忆。


KMP算法是解决字符串匹配问题的较高效算法,字符串匹配问题是常见的算法问题。给出两个字符串,分别为母串和子

串,字符串匹配就是为了在母串中找到该子串。

正常的思路是逐个去匹配,也就是下面几个步骤

1.首先用子串的第一个字符去匹配母串的第一个字符

2.若匹配成功,即两个字符相等,则继续匹配下一个,若不等,匹配子串的第一个和母串的第二个

3.假设母串的前五个和子串都相同,当匹配第六个时,不同,那么此时正常的思路是用子串的第一个去匹配母串的第二

个,步骤重复与上一致。当字符串足够长的时候,这个办法效率就会很低。因为这个办法没有用到一个重要的信息,母

串的前五个和子串都是相同的,KMP算法呢就是利用这个信息,推算出一个值X,只需要比较母串的第六个和子串的第

X个即可,不需要反复的比较字符。


这个值叫做部分匹配值,那么每一个子串字符都会对应一个部分匹配值,这些值组成一个数组,也就是常说的Next数组

部分匹配值是如何得到的,也就是怎样求出Next数组,就成为了KMP算法的关键。


求出部分匹配值


先了解部分匹配值是什么,然后再想如何用代码求出来。用一个字符串来解释,“abaabcac” ,这个字符串有8位,每

一位都对应一个匹配值,匹配值就是该位之前的字符串的前缀后缀中共同元素的最大长度。 "前缀"指除了最后一个字

符以外,一个字符串的全部头部组合;"后缀"指除了第一个字符以外,一个字符串的全部尾部组合。

当N=1,默认匹配值为0;

当N=2,字符串为a,匹配值为1

当N=3,字符串为ab,前缀为a,后缀为b,无公共元素,匹配值为1

当N=4,字符串为aba,前缀为a,ab,后缀为a,ba,公共元素长度为1,匹配加1为2

...

以此类推,此时可能大家会有两个疑惑,为什么匹配值要加1,为什么匹配值计算的是不包括当前这个字符的值

第一,匹配值加1是因为,回到上面那个例子,字母串前五个相同,假设此时Next[6]=3,当子串第六个去和母串的

第六个匹配时,发现并不相同,可以想象是将子串向右滑行最大的距离,假设此时前五个字符为“abcab”,第一二个字

符和第四五个字符相同,那么完全可以将子串向右滑行3个距离,直接用c去和母串的第六个比较,所以匹配值加1。

如果这样还不好理解的话,字符串都是需要遍历比较的(此时先不考虑字符串是从0开始遍历的,后面会提到),用i

遍历母串,j遍历子串,当i=j=6的时候,i=6,j=next[j]=3,这样就不需要再用子串从头遍历了。


那么卫视不包括当前字符是因为,当前的字符是和母串不想同的字符,那么右滑的时候不需要把这个字符考虑进去,

这个字符还是等待进一步比较的,只需要右滑掉首尾最大程度一致的字符即可。


思路理解了以后,下面的代码示例(以Java为例)

代码中就需要注意上面提到的字符串从0遍历的问题了。你可能会发现看完了上面说的一箩筐,即使了解了部分匹配

是什么,是怎么根据字符串算出来的,但是依旧不会写代码。求部分匹配值,可以看成是对字符串的一种切割,因

为求得的匹配值实际上就是被“切割”下来的字符串的长度。


//样例子串
String match="abaabcac";
		
//部分匹配值数组,下标从1开始到子串的长
int[] next=new int[match.length()+1];
		
//默认值
next[1]=0;
int i=1,j=0;
while(i<match.length()){//直到子串遍历完
	if(j==0 || match.charAt(i-1)==match.charAt(j-1)){
		++i;++j;next[i]=j;
	}else{
		j=next[j];
	}
}




字符串匹配


字符串的匹配过程其实和求部分匹配值的过程很相似

public class KMP {
	public static void main(String[] args) {
		//样例母串
		String orignal="acabaabaabcacaabc";
		
		//样例子串
		String match="abaabcac";
		
		//部分匹配值数组,下标从1开始到子串的长
		int[] next=new int[match.length()+1];
		
		//默认值
		next[1]=0;
		int i=1,j=0;
		while(i<match.length()){//直到子串遍历完
			if(j==0 || match.charAt(i-1)==match.charAt(j-1)){
				++i;++j;next[i]=j;
			}else{
				j=next[j];
			}
		}
		//开始匹配
		i=1;j=1;
		while(i<=orignal.length()&&j<=match.length()){
			if(j==0|| orignal.charAt(i-1)==match.charAt(j-1)){
				i++;j++;
			}else{
				j=next[j];
			}
			
			if(j>match.length()){
				System.out.println(i-match.length()-1);//从母串第几个开始
				System.out.println(orignal.substring(i-match.length()-1, i-1));
			}
		}
		
	}
}




有一个较难理解的地方是下标问题,遍历字符串的下标从0开始,但是长度计算是从1开始,想通这一点KMP也就更加

好理解了,其实先不需要管下标,下标值总是长度值-1。


参考:(有些细节方面不太一致,但是思路是想通的)

http://blog.csdn.net/yutianzuijin/article/details/11954939/

http://kb.cnblogs.com/page/176818/

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值