字符串匹配基础(下):如何借助BM算法轻松理解KMP算法?

字符串匹配基础(下):如何借助BM算法轻松理解KMP算法?

KMP算法基本原理

假设主串是a,模式串是b,把不能匹配的那个字符叫做坏字符,把已经匹配的那段字符串叫做好前缀

当遇到坏字符的时候,把模式串往后滑动,在滑动过程中,只要模式串和好前缀有上下重合,前面几个字符的比较就相当于拿好前缀的后缀子串跟模式串的前缀子串在比较

KMP算法就是在模式串和主串匹配过程中,当遇到坏字符后,对于已经比对过的好前缀是否找到一种规律,将模式串一次性滑动很多位。

拿好前缀本身,在它的后缀子串中,查找最长的那个可以跟好前缀的前缀子串匹配的,假设最长的可匹配的那部分前缀子串是{v},长度是k,把模式串一次性往后滑动 j-k位,相当于每次遇到坏字符的时候,把j更新为k,i不变,然后继续比较

将好前缀的所有后缀子串中最长的可匹配前缀子串的那个后缀子串叫做最长可匹配后缀子串,对应的前缀子串叫做最长可匹配前缀子串

KMP算法提前构建一个数组,用来存储模式串中每个前缀的最长可匹配前缀子串的结尾字符下标,把这个数组定义为next数组,也叫作失效函数,数组的下标是每个前缀结尾字符下标,数组的值是前缀的最长可匹配前缀子串的结尾字符下标

​ 模式串: a b a b a c d

模式串前缀(好前缀候选) 前缀结尾字符下标 最长可匹配前缀子串结尾字符下标 next值

a 0 -1(不存在) next[0] = -1

a b 1 -1 next[1] = -1

a b a 2 0 next[2] = 0

a b a b 3 1 next[3] = 1

a b a b a 4 2 next[4] = 2

a b a b a c 5 -1 next[5] = -1

比如前缀候选: a b a

让a b a找出最长可匹配前缀子串结尾字符下标

a b a a b a

​ a a

​ b a a b //结束了,b a != a b,只有第一行的a = a ,a下标为0,所以next[2] =0

kmp算法框架:

//a,b分别是主串和模式串;n ,m 分别是主串和模式串的长度
public static int kmp(char[] a ,int n,char[] b ,int m ){
	int[] next = getNexts(b,m);
	int j = 0 ;
	for(int i = 0 ; i < n ; ++i){
		while(j  > 0 && a[i] != b[j]){       //一直找到a[i]和b[j]
			j = next[j - 1] + 1;
		}
		if(a[i] == b[j]){
			++j;
		}
		if(j == m){     //找到匹配模式串的了
			return i - m +1;
		}
	}
	return -1;
}

失效函数计算方法

next数组如何算出来的?

按照下标从小到大依次计算next数组的值,当我们要计算next[i]的时候,前面的next[0]、next[1]、……、next[i-1]应该都算出来了,利用已经算出来的next值,是否可以快速推导出next[i]的值呢?

如果next[i-1] = k -1,即子串b[0,k-1]是b[0,i-1]的最长可匹配前缀子串,如果b[0,k-1]的下一个字符b[k],与b[0,i-1]的下一个字符b[i]匹配,那子串b[0,k]是b[0,i]的最长可匹配子串,所以next[i] = k

// b 表示模式串,m表示模式串的长度
private static int[] getNexts(char[] b ,int m){
	int[] next = new int[m];
	next[0] = -1;
	int k = -1;
	for(int i = 1 ; i < m ; ++i){
		while(k != -1 && b[k+1] != b[i]){
			k = next[k];
		}
		if(b[k + 1] == b[i]){
			++k;
		}
		next[i] = k ;
	}
	return next;
}

推荐:

http://www.zhihu.com/question/21923021 逍遥行的回答

发布了75 篇原创文章 · 获赞 9 · 访问量 9186

猜你喜欢

转载自blog.csdn.net/ywangjiyl/article/details/104502694