KMP算法

以前在一本数据结构书中看到了KMP算法,大概懂了其意思,但一直没有看懂其具体的实施过程。最近又看了一下,大概明白了一些,故写出来,与大家分享一下我的想法,希望读者不吝赐教。

KMP算法简介

KMP算法是一种改进的字符串匹配算法,由D.E.Knuth,J.H.Morris和V.R.Pratt同时发现,因此人们称它为克努特——莫里斯——普拉特操作(简称KMP算法)。KMP算法的关键是利用匹配失败后的信息,尽量减少模式串与主串的匹配次数以达到快速匹配的目的。具体实现就是实现一个next()函数,函数本身包含了模式串的局部匹配信息。(摘自百度百科)

算法原理

1.朴素匹配算法 
这个算法是大家最容易想到,也是最容易理解的。它是将主串s与模式串t进行逐个比对,当主串的某位与模式串的不相等时,主串的指针位置不变,将模式串的指针拉到起始位置。再继续向前进行比较直到成功或者失败。显然这符合大家思维,但是它的问题就出现了,当字符串很长时,它的效率是极低的,期间进行了多次重复比较,产生大量的冗余。而KMP算法就是减少多次重复的比较,加快效率。
2.KMP算法 
将主串与模式串进行逐个对比,当主串的第i个和模式串的第j个元素不相等的时候,则说明模式串的前(j-1)个元素匹配成功,即可得方程等式:
(1)
则对于主串必然存在一个这样的等式,其中的k可能存在,也可能不存在,但是该等式仍然成立:
(2)

当k<j时,由(1)式可得以下等式: 

(3)

所以由(2)和(3)式可得:

 (4)

而且该式成立的条件是k<j。

由(4)式可以说明:模式串从1-->j序列中必然存在一个k值,使得此序列的前k-1个元素与后k-1个元素一一对应且相等。

所以,当主串的第i个和模式串的第j个元素不相等的时候,主串的i不变,模式串向右滑动至其k位置,然后i位置和k位置继续向右进行比较,后续的比较同理。


next[]数组的构造

next[]数组是KMP算法的精髓所在,也是最难理解的部分。在网上看了一下该算法和别人解释,对于next[]数组的构造有不同的构造法,所以其值可能不同,但原理都是一样的。都是为了找到重复的那部分,找到那个k的位置。下面是我对其构造的一种理解和算法:其中next[]数组的值表示k的位置,即模式串与主串不相等时,next[]的值即为模式串应该向右滑动到的位置k。
其计算公式如下图:

假设有一个如下图的模式串t:

则按照上图next[]的构造规则,其值如下:

对上图next[] 的分析过程:
  • 当j=0时,由上述计算公式的next[j]=0;
  • 当j=1时,从0至j-1为:a,满足(4)式的最大k值为0;
  • 当j=2时,从0至j-1为:ab,满足(4)式的最大k值为0;
  • 当j=3时,从0至j-1为:abc,满足(4)式的最大k值为0;
  • 当j=4时,从0至j-1为:abca,则a=a,满足(4)式的最大k值为1;
  • 当j=5时,从0至j-1为:abcab,则ab=ab,满足(4)式的最大k值为2;
  • 当j=6时,从0至j-1为:abcabc,则abc=abc,满足(4)式的最大k值为3;
  • 当j=7时,从0至j-1为:abcabcd,满足(4)式的最大k值为0;
  • 当j=8时,从0至j-1为:abcabcde,满足(4)式的最大k值为0;
  • 当j=9时,从0至j-1为:abcabcdea,则a=a,满足(4)式的最大k值为1;
以上就是找k值得大概过程。都是根据公式在找,接下来就是通过Java代码来找值:
public static int[] getNext(char[] t){
		int[] next=new int[t.length];
		
		/*将m=0,n=1,当n位置(t[n])和m位置(t[m])相等时,m和n同时前进,m即为该n处以前的最大k值;
		 * 当两者不相等时,只有n前进,则使m=0,返回0处。
		 * */
		int m=0,n=1;
		next[m]=m;          //使next[0]=0
		while(n<t.length){
			next[n]=m;      //m即为最大的k值
			if(t[n]==t[m]){
				m++;        //当t[n]==t[m]时,表示(0->m-1)段和(n-m -> n-1)段相等
			}else{
				m=0;        //不相等时,m=0
			}
			n++;
		}
		return next;
	}

KMP算法代码

下面是根据我自己的理解写的一个KMP算法,主要包括匹配字符和获取next[]数组。
public class KMP {

	public static void main(String[] args){
		String S="asdhhsadfgkdfgasdfgghajcfkk";  //主串
		String T="dfgkdfgasd";                   //模式串
		
		char[] SArray=S.toCharArray();
		char[] TArray=T.toCharArray();
		
		int i=0,j=0;
		int[] next=null;
		next=getNext(TArray);
		for(;i<S.length();i++){
			if(j<T.length()){
				if(SArray[i]==TArray[j]){
					j++;           //匹配成功j++,继续向右进行
				}else{
					j=next[j];     //不成功,模式串向右滑动到k位置
				}
			}else{
				
				break;
			}
		}
		if(i==S.length()){
			System.out.println("没找到!");
		}else{
			System.out.println(i);   //找到,输出模式串在主串中匹配成功的尾部位置
		}
		
	}
	
	public static int[] getNext(char[] t){
		int[] next=new int[t.length];
		int m=0,n=1;
		next[m]=m;
		while(n<t.length){
			next[n]=m;
			if(t[n]==t[m]){
				m++;
			}else{
				m=0;
			}
			n++;
		}
		//输出next[]数组结果
		/*for(int k=0;k<t.length;k++){
			System.out.print(t[k]+"  ");
		}
		System.out.println();
		for(int k=0;k<next.length;k++){
			System.out.print(next[k]+"  ");
		}*/
		return next;
	}
}

本人菜鸟一枚,代码就将就着看吧!

总结

KMP算法确实在比较过程中省去了很多重复的比较动作,但其理解起来很难,自己也是第一次没看懂后来又多看了几遍,看了很多人的解释才懂了一些,而且当模式串比较短或者没有重复的字符时,和朴素匹配算法没有区别,意义不大。当模式串比较长且有较多重复段,则很适用,能节省很多时间。

紧是自己的学习与理解,有什么错误希望读者能评论出来,加以指正,以便更多的人探讨学习!


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值