KMP算法

注:本文主要是按照李春葆版本《数据结构》来总结的。

1. BF算法(简单匹配算法) 时间复杂度O(n*m)

public int index(char [] s,char [] t){
		if(s==null || t==null ||s.length<t.length){
			return -1;	
		}
		for(int i=0;i<=s.length-t.length;i++){
			int j=i,k=0;
			for(;k<t.length && s[j]==t[k] ;j++,k++);
			if(k==t.length){
				return i;				
			}
		}
		return -1;
	}

或者

public int index2(char[] s,char[] t){
		if(s==null || t==null ||s.length<t.length){
			return -1;	
		}
		int i=0,j=0,k=-1; //k用来返回最后的下标
		while(i<s.length && j<t.length){
			if(s[i]==t[j]){
				i++;
				j++;
			}else{
				i=i-j+1;
				j=0;
			}
		}
		if(j>=t.length){
			return k=i-t.length;
		}
		return k;
	}

2. KMP 算法 时间复杂度O(n+m)

KMP算法消除了主串指针的回溯,利用模式串当前子串的前缀和后缀匹配的子串的最长长度来计算。

前缀:长度为n的字符串中按排列顺序的1或n-1个字符组成的子串,如abcab的前缀是a, ab, abc, abca

后缀:和前缀类似,由字符串后面的字符组成,abcab的后缀是b, ab, cab, bcab

若模式串 t 和主串 s 中符合


即"t0t1…tj-1"="si-jsi-j+1…si-1"  如果在模式t中,"t0t1…tj-1"≠"t1t2…tj",则"t0t1…tj-1"≠"si-j+1si-j+2…si",对于“失配”现象,我们需要找到一个前缀,使得"t0t1…tk-2"≠"tj-k+1tj-k+2…tj-1" 且 "t0t1…tk-1"="tj-ktj-k+1…tj-1“才有"tj-ktj-k+1…tj-1"="si-ksi-k+1…si-1"="t0t1…tk-1"


这样当si与tj 比较失配时,可以直接将si与tk比较,也就是模式串t右滑j-k位,k即是next[j].由于t0t1...tj中可能存在多个前缀与后缀相同的情况,我们应该选择最长的前缀进行滑动,保证回溯最少。

public int [] GetNext(char[] t){
		if(t==null || t.length==0){
			return null;
		}
		//k表示最长相同前后缀的长度,next[0]=-1是规定的,k=0表示相同的前后缀长度为0
		int j=0,k=-1;
		//k<j,且-1<=k<=j-1
		//next[]用来存放已经计算好的最长前后缀长度
		int [] next=new int[t.length];
		next[0]=-1;
		while(j<t.length-1){ //前缀长度至少比模式串长度少1
			if(k==-1 || t[j]==t[k]){ //k=-1表示没有相同的前缀和后缀,t[j]=t[k]表示当前的字符也相同,
				k++;  //前后缀长度加1
				j++; //如果当前字符相同,说明包含当前字符为前缀的子串的next增加1,所以j需要加1
				next[j]=k;
			}else{
				k=next[k];  //当前字符不相同,那么最长的前后缀长度就是前面得到的最大长度
			}
			
		}
		return next;
	}
public int KMP(char[] s,char[] t){
		if(s==null ||t==null ||s.length<t.length){
			return -1;
		}
		int [] next=GetNext(t); //得到模式串t的next数组
		int i=0,j=0,k=-1; //k用来返回最后的下标
		//i表示主串中各个字符的下标,所以初始值为0,j初始值为0,表示模式串与主串先顺序比较,如果发现不匹配,则模式串进行滑动
		while(i<s.length && j<t.length){
			if(j==-1 || s[i]==t[j]){ //注意:j=next[j],所以j可能等于-1
				i++;
				j++;
			}else{ //此时,主串s不用回溯,只用移动模式串t即可
				j=next[j];
			}
		}
		if(j>=t.length){
			k= i-t.length;
		}
		return k;
	}

3. KMP改进算法
例如主串s为 aaabaaab, 模式串t为 aaaab,模式串的next数组是 -1 0 1 2 3,匹配如下:

由于t 的前4个字符相同,所以当i=3, j=3 失配时,也就是a 不等于b ,应该不用比较 j=0,1,2, 应该直接比较 i=4, j=0 。
next[j]=k, 如果在模式串中 tj=tk, 而此时已经失配( si 不等于 tj), 则 si 一定不会等于 tk,所以此时将模式串向后移至 next[j] 也一定会失配。此时,应该将 si 直接和 t next[k] 比较,也就是说,next[j] 应该和 next[k] 相等(tj=tk 时)

为此将next[j] 修正为 nextval[j]:
比较t.data[j] 和 t.data[k],若不等,则 nextval[j]=next[j]=k; 若相等nextval[j]=nextval[k];

	public int[] GetNextval(char[] t){
		if(t==null ||t.length==0){
			return null;
		}
		int [] nextval=new int[t.length];
		nextval[0]=-1;
		int j=0,k=-1;
		while(j<t.length-1){
			if(k==-1 || t[j]==t[k]){
				k++;
				j++;
				if(t[j]!=t[k]){ //如果不相等,则nextval[j]就是next[j],也就是k
					nextval[j]=k;
				}else{ 
					nextval[j]=nextval[k];
				}
			}else{
				k=nextval[k];
			}
		}
		return nextval;
	}
	public int improvedKMP(char[]s ,char[] t){
		if(s==null ||t==null ||s.length<t.length){
			return -1;
		}
		int [] nextval=GetNextval(t); //得到模式串t的nextval数组
		int i=0,j=0,k=-1; //k用来返回最后的下标
		//i表示主串中各个字符的下标,所以初始值为0,j初始值为0,表示模式串与主串先顺序比较,如果发现不匹配,则模式串进行滑动
		while(i<s.length && j<t.length){
			if(j==-1 || s[i]==t[j]){ //注意:j=nextval[j],所以j可能等于-1
				i++;
				j++;
			}else{ //此时,主串s不用回溯,只用移动模式串t即可,且按照nextval来移动
				j=nextval[j];
			}
		}
		if(j>=t.length){
			k= i-t.length;
		}
		return k;

	}
improvedKMP() 和KMP() 的不同就在于匹配失效后,模式串t 的移动由 next[j] 变成了nextval[j]; 重点主要是next[] 和 nextval[] 的求法,而nextval[j] 相对于next[] ,主要是看 tj 是否等于tnext[j](也就是t[k])。


  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值