模式匹配算法详解:KMP算法

本文深入探讨了模式匹配算法的基础概念,并详细解释了KMP算法的工作原理及其实现方式。通过对比基本模式匹配算法与KMP算法,阐述了KMP算法如何通过避免模式串的重复比较,显著提高匹配效率。文中还提供了KMP算法的Java实现代码,展示了改进后的next数组计算方法,进一步减少了主串的比较次数。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >


基本的模式匹配算法

假设现在有主串S=s1,...,sn,,模式串P=p1,...,pm,基本的模式匹配算法是将P中的字符p1与S中的字符s1比较,如果相等,则依次递增比较pi+1和sj+1。如果不相等,则将p1与S中的字符s2比较,依次类推。如果存i=m,则存在匹配模式,否则匹配失败。

KMP算法

KMP算法是由由D.E.Knuth与V.R.Pratt和J.H.Morris同时发现,所以人们称它为克努特—莫里斯—普拉特算法,该算法的时间复杂度为O(n+m),n为主串的长度,m为模式串的长度。

KMP算法的特点是不需要回潮对主串的匹配,核心思想是如果模式串当前字符a与主串当前的字符b不匹配,分两种情况,如果对于模式串中字符c之前的一个子串s,存在一个以模式串首位开始与子串s相同的子串s',则对于之前子串s其实已经通过其之后子串s'完成匹配了,所以不再需要比较,则从模式串的中间位置开始比较。如果不存在这样的子串s',则说明字符b之前开始的子串都不与模式串匹配。

算法思路:该算法的关键是当不匹配时,下一次比较将模式串的第几位开始。根据前面的分析,该位置与模式串相关,由于和主串的比较还是依次从头至尾 的,模式串重新比较的位置与主串无关,这就意味着模式串不变时,模式串中下一个匹配的位置是确定的,而不受主串影响。

对于模式串P=p1,...,pi-1,pi,...,pm如果模式串的子串p1,...,pi-1,已经和主串S=s1,...,sn中子串sj,...,sj+i-1匹配成功,而pi与sj+i不相等,即不匹配,则我们要使得j=j+1,即将模式串与主串中当前位置的下一个位置的字符串起开始比较。正如前文所说的,我们不是重新从模式串的首位开始比较,而跳跃地从一个中间位置开始比较。我们用一个长度为m的数组next[m]保存模式串中当每一个位置的字符匹配失败后,下一次模式串重新开始比较的位置。

next的值事实上是针对当模式串P中第j位的字符P[j]与主串匹配失败时计算的,分为三种情况:

第一种,next[i] = -1,当i=0时;

第二种next[i] = max{k|1<k<i且p[0],...,p[k-1]=[i-k],...p[i-1]};

其他情况,next[i] = 0

这该算法的JAVA源码如下:

 

/**
 * 
 * @author newstar
 * @since 2011-09-27
 *
 */
public class KMP1 {

	
	public int match(String p,String t){
		String pattern = p;
		String text = t;
		int i = 0, j = 0;
		int[] next = calculate_next(pattern);
		while(i < pattern.length()&& j <text.length()){
			if(i==-1|| pattern.charAt(i)==text.charAt(j)){
				++i;
				++j;
			}
			else{
				i = next[i];
			}
		}
		if(j > pattern.length()) 
			return j-pattern.length();
		else return 0;
	}
	
	public int[] calculate_next(String pattern){
		int m = pattern.length();
		System.out.println(m);
		int[] next = new int[m];
		int i = 0,j = -1;
		next[0] = -1;
		while(i<m-1){
			if(j==-1||pattern.charAt(i)==pattern.charAt(j)){
				++i;
				++j;
				next[i] = j;
			}
			else{
				j = next[j];
			}
		}
		return next;
	}

	/**
	 * @param args
	 */
	public static void main(String[] args) {
		// TODO Auto-generated method stub
		String pattern = "abcfdabceg";
		String text = "egaradfaababcfdabcegeabcbcae";
		KMP1 run = new KMP1();
		int position = run.match(pattern,text);
		System.out.println(position);

	}
}


 

细心的读者也许会发现calculate_next函数和match函数十分类似,其实对于next的计算其实已经将模式串即当作主串又当作模式串来计算的。因为KMP算法本质其实是考虑到模式串中有相同子串,前面的子串和后面的子串相同,如果后面的子串已经是匹配成功的时,前面的子串必定和相应的主串是相匹配的。

仔细分析以下发现其实next值的计算还有改时,可以减少对主串的比较,改进后的next值的计算,分为三种情况:

第一种,如果这p[i]=p[0],则next[i]=-1。表明模式串的当前位置与模式串的首位相同,当模式串当前位置与主串的当前位匹配失败时,则模式串的首位与主串的当前位也会匹配失败,则模式串的首位应和主串当前位的下一位比较,即主串当前位向右移一位。模式串当前位为首位。

第二种,如果第一种情况不满足时,当p[0],...,p[k-]=p[i-k],...p[i],k>=0,则next[i] = 0。表明如果模式串当前位置为结束位的一个子串与模式串的首位开始的一个子串相等,如果模式串当前位置匹配失败,则表明模式串首位开始的那个子串也会匹配失败,则应从模式串的首位重新开始匹配。

第三种,如果前两情况不满足时,当p[0],...,p[k-1]=[i-k],...p[i-1]且p[k]!=p[i],k>=1,则next[i] =k-1。表明如果模式串当前位置之前的一个子串与模式串的首位开始的一个子串相等,如果模式串当前位置匹配失败,但是模式串匹配失败位置之前子串是匹配成功的,且与模式串首位开始的子串是匹配的,所以应相应从模式串的首位开始的子串的结束位开始匹配。

以下改进后的KMP算法的Java源码:

/**
 * 
 * @author newstar
 * @since 2011-09-27
 *
 */
public class KMP2 {

	
	public int match(String p,String t){
		String pattern = p;
		String text = t;
		int i = 0, j = 0;
		int[] next = calculate_next(pattern);
		while(i < pattern.length()&& j <text.length()){
			if(i==-1|| pattern.charAt(i)==text.charAt(j)){
				++i;
				++j;
			}
			else{
				i = next[i];
			}
		}
		if(j > pattern.length()) 
			return j-pattern.length();
		else return 0;
	}
	
	public int[] calculate_next(String pattern){
		int m = pattern.length();
		int[] next = new int[m];
		next[0] = -1;//next[i]=-1时表示主串当前位的下一位与模式串的第0位(首位)进行比较,
		//即表示主串的当前位置应加1,模式串的当前位置应为首位(第0位)
		System.out.println("i="+0+" "+"-1");
		int i = 1,j = 0;
		while(i<m){
			if(pattern.charAt(i)==pattern.charAt(0)){//j=0,第二种情况
				next[i] = -1;
				System.out.println("i="+i+" "+next[i]);
				i++;
				j++;
			}
			else if(pattern.charAt(i)!=pattern.charAt(j)){
				next[i] = j;
				System.out.println("i="+i+" "+next[i]);
				i++;
				j = 0;
			}
			else {
				if(pattern.charAt(i)==pattern.charAt(j)){
					i++;
					j++;
				}
				next[i] = 0;
				System.out.println("i="+i+" "+next[i]);
			}
		}
		return next;
	}

	/**
	 * @param args
	 */
	public static void main(String[] args) {
		// TODO Auto-generated method stub
		String pattern = "abcfdabceg";
		String text = "egaababcfdabcegeabcbcae";
		KMP2 run = new KMP2();
		int position = run.match(pattern,text);
		System.out.println(position);
	}
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值