非常不详细的优化KMP算法——优化next

  KMP算法是一种改进的匹配算法,主要为了匹配字符串(个人愚见)。他的用处无处不在。

eg:

 

一般情况下,这种问题可以用简单的检索完成,代码如下:

import java.util.Scanner;
public class Class{
	public static void main(String [] arg) {
		Scanner scanner=new Scanner(System.in);
		System.out.println("输入两个字符串:");		
		String str1=scanner.next();//此处字符串不包含空格
		String str2=scanner.next();
	    if(KMP.kmp(str1,str2)){//KMP识别是否为子串
	    	//KMP.kmp<----->isSon
	    	System.out.println(str2+"是"+str1+"的子串");
	    }
	    else {
	    	System.out.println(str2+"不是"+str1+"的子串");
	    }
	    	
	}
	public static boolean isSon(String str1,String str2) {
		for(int j=0;j<str1.length();j++) {
			for(int i=0;i<str2.length();i++) {
				if(str1.charAt(j)!=str2.charAt(i)) {
					j=j-i+1;
					break;
				}
				else if(i==str2.length()-1) {
					return true;
				}
				j++;
			}
		}
		return false;
	}

}
  • 值得注意的是:在匹配的过程中,一定要注意主码位置的回溯。如果在某一位置,str1.charAt(i)!=str2.charAt(j)时,会有子串j=0,容易忽略掉主串中i的回溯。这样有可能会导致匹配出错。比如str1="abababc",str2="ababc",前四匹配时,都满足str1.charAt(i)=str2.charAt(j).但是,第五次时,由于只令j=0,回溯子串,忽略掉对主串计数的回溯。会使后面的会有匹配,耽误结果,会导致结果:str2无法与str1匹配,str1不是str2不是他的字串。

       那么,来解决这一问题,加上对主码的回溯,传统主码的回溯是,直接回为:j=j-i-1。也就是说,如果从某个字符开始的一串都匹配过了,突然有一个与字串匹配不对,则主串要返回到这个字符的下一个,字串要重新开始匹配。那么,如果(极端情况)这个主串很特殊,字串也很特殊,每次匹配时,都会使字串匹配到最后一个字符,然后失败,返回抽头再来。那么,一个m,n长的主串和子串救护匹配(m-n)*n+n次。这是极端情况,也是最大匹配情况。那么有没有一种算法可以使它不用回溯这么多?可以让它每个字符减少回溯的次数?

他来了!!!

   D.E.Knuth,J.H.Morris和V.R.Pratt给我们带来了福音,他们联手提出了KMP算法,他的思想是,先对子串进行检索,找出其中的规律,以减少匹配过程中的重复匹配。

举一个简单的例子,我们检索任意一串字符中是否包括aa。传统的算法(如上),这里记第一个a为a1,第二个a为a2。每次先匹配a1,如果与a1不匹配直接下一个,直到找到与a1相匹配的那个字符。然后,再匹配这个字符后的那个,是否与a2匹配。如果匹配,任务完成,我们不讨论。如果不匹配,根据代码的运行,会再用这个与a2不匹配的字符与a1匹配,这里肯定是不匹配的,但是计算机不知道,他需要再走一遍。嗯,这就痛点,而D.E.Knuth,J.H.Morris和V.R.Pratt这三个人解决了这一痛点。他们的算法在匹配时,先对子串进行检索,得到了一个高效的回溯的数组next[],用这个数组回溯主串则大大减少重复发的检索,next()的求法,就不写了,下面有代码。还有优化的部分。

注释的代码为调试的代码:

public class KMP {
	//next()待优化
	public static void main(String[] args) {
		String str1="abbc22abc";
		String str2="aaaa";
//		int next[]=new int[str2.length()];
//	    Next(str2,next);//完成next
		System.out.println(kmp(str1,str2));
	}
	public static boolean kmp(String str1,String str2) {
		int next[]=new int[str2.length()];
		Next(str2,next);//完成next
		int i=0,j=0;//i计数str1 j计数str2
		while(i<str1.length()&&j<str2.length()) {
//			System.out.println("str1:"+str1.charAt(i));
//			System.out.println("str2:"+str2.charAt(j));//观察
			if(str1.charAt(i)==str2.charAt(j)) {
				j++;i++;//符合则都进一
			}
			else {
				if(next[j]==-1) {
					i++;j=0;//不符合且next为-1  则主码下一个 适配串第一个
				}
				else j=next[j];//不为-1时  跳入能判断的地方
				}
		}
		//判断
		if(j==str2.length()) {
			System.out.println("匹配开始位置:第"+(i-str2.length()+1)+"个字符");
			return true;
		}
			
		else return false;
	}
	public static void Next(String str2,int next[]) {
		//next数组的值是代表着字符串的前缀与后缀相同的最大长度,(不能包括自身)。
//		if(无错误) i++,j++
//		当前字符出错 应跳转到什么位置
//		else if(s[当前出错位置]==-1) i++ j=0 j表示检索子串的位置  i表示长串的位置
//		else 出错且s[当前出错位置]!=-1 j不动,
		
		
		next[0]=-1;int n=1;//n记录判断的位置,也是用作结束的条件
		while(n<str2.length()) {
			if(next[n-1]==-1) {
				//当该字符的前一位无前缀时,只需判断第一位1(下标为0)是否一致
				if(str2.charAt(0)==str2.charAt(n))
					//如果一致则说明不可以匹配
					next[n]=0;
				else next[n]=-1;//不一致则说明有可能匹配需要匹配
					
			}
			else {
				//当该字符的前一位有前缀时,则判断当前的字符是否与str2.charAt(s[n-1])一致
				if(str2.charAt(next[n-1]+1)==str2.charAt(n))
					//如果一致则说明不可以匹配
					next[n]=next[n-1]+1;
				else if(str2.charAt(n)==str2.charAt(0)) {
					next[n]=0;
				}
				else next[n]=-1;//不一致则说明有可能匹配需要匹配
			}
			n++;
		}
//		System.out.println("第一次:");
//		for(int i=0;i<str2.length();i++)
//			System.out.print(next[i]+" ");
		int m=1;//从1开始优化
//		当匹配时,用到next[]时   如:用到j=next[j]
//      如果charAt(j)==charAt(next[j])    
//		则再次匹配charAt(j)与harAt(i)一定再次无效需要再j=next[j]
//		所以next[]需要再次回溯从小大回 一次回到位
		while(m<str2.length()) {//优化next
			if(next[m]!=-1) //只针对next[m]不为-1的情况
				if(str2.charAt(m)==str2.charAt(next[m])) {//如果相等则将值改为下一次KMP下一次运行所要寻找的值(这里为了将无用的检索省略)
					next[m]=next[next[m]];
				}
			m++;
		}
//		System.out.println("\n第二次:");
//		for(int i=0;i<str2.length();i++)
//			System.out.print(next[i]+" ");
	}
}

 

 

 

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值