KMP字符串匹配

今天终于弄明白了如何计算KMP匹配算法中的next数组。

KMP匹配中的next数组的第i个元素j=next[i],表示模式串pattern中的前i个字符pattern[0..i-1]中,真前缀pattern[0..j-1]和真后缀pattern[i-j-2...i-1]相等,这样再进行模式匹配时,如果主串中当前字符和pattern中第i个字符匹配时unmatch,此时,不用回溯主串的指针,而是使用pattern的next[i]字符与主串的当前字符匹配。(通过画图和清晰描述)

在计算模式串的next值时,可以根据前一个元素的next计算结果得到。根据next[i-1]计算next[i]:

记j=next[i-1],则pattern[0..j-1]与pattern[i-j-3..i-2]相等,然后判断pattern[j]和pattern[i-1]是否相等,

如果pattern[j]==pattern[i-1],那么next[i]=j+1=next[i-1]+1;

如果pattern[j]!=pattern[i-1],此时可以将S‘=pattern+(i-j-3)看作主串,与模式串pattern匹配时,在pattern[j]出发生了不匹配,那么根据KMP匹配算法,应该使用pattern的next[j]与S’的当前元素(pattern的第i-1元素)进行匹配,直至找到j‘时pattern[i-1]==pattern[j’]相等,此时next[i]=j‘+1。

通过以上分析得到如下计算next数组的方法:

int* KMPGetNext(const char* pattern){
	int n = strlen(pattern);
	if (n==0)	return NULL;
	int* next = new int[n];
	next[0] = -1;	//next[0]总是为0,表示当模式串的第一个字符不匹配时,pattern[0]应该和主串的下一个字符进行匹配
	int preMatchCount = -1;
	for (int i=1; i<n; i++){
		//查找与pattern[i-1]匹配的字符
		while (preMatchCount>=0 && pattern[i-1]!=pattern[preMatchCount])
			preMatchCount = next[preMatchCount];
		preMatchCount++;
		next[i] = preMatchCount;
	}
	return next;
}

该方法对于模式串abcabcad计算得到的next数组是:

01234567
abcabcad
-10001234

但是在上述next数组中,如pattern[3]匹配失败时,将使用pattern[0]进行再次匹配,而pattern[0]==pattern[3],显然也是匹配失败的,同样pattern[4]匹配失败时,将使用pattern[1]进行再次匹配,显然也是匹配失败的,所以可以对上述GetNext方法进行修改,减少不必要的匹配,如下:

int* KMPGetNext(const char* pattern){
	int n = strlen(pattern);
	if (n==0)	return NULL;
	int* next = new int[n];
	next[0] = -1;	//next[0]总是为0,表示当模式串的第一个字符不匹配时,pattern[0]应该和主串的下一个字符进行匹配
	int preMatchCount = -1;
	for (int i=1; i<n; i++){
		//查找与pattern[i-1]匹配的字符
		while (preMatchCount>=0 && pattern[i-1]!=pattern[preMatchCount])
			preMatchCount = next[preMatchCount];
		preMatchCount++;
		if (pattern[i]==pattern[preMatchCount])
			next[i] = next[preMatchCount];
		else
			next[i] = preMatchCount;
	}
	return next;
}

KMP模式匹配方法:

在主串与模式串进行匹配时,如果在patter[i]和S[j]出不匹配,那么下次使用pattern[next[i]]与S[j]进行匹配。主串的索引不需要回溯,总共时间复杂度O(n)(加上next数组的计算时间,总共时间复杂度O(m+n),m和n分别是模式串和主串的长度)

实现如下:

int KMPStringMatch(const char*S, const char* pattern){
	int* next = KMPGetNext(pattern);	//计算pattern的next数组
	int indexOfPattern = 0;	//模式串索引
	int indexOfS = 0;		//主串索引
	while (S[indexOfS]!='\0' && pattern[indexOfPattern]!='\0'){
		if (S[indexOfS] == pattern[indexOfPattern]){//主串字符和模式串字符相等时,继续匹配下一个字符
			indexOfS++;	indexOfPattern++;
		} else {
			indexOfPattern = next[indexOfPattern];
			if (indexOfPattern<0){//遇到next值为-1的情况,需要特殊处理
				indexOfPattern = 0;
				indexOfS++;
			}
		}
	}
	delete[] next;
	if (pattern[indexOfPattern] == '\0'){//模式串已经匹配到最后时,说明匹配成功
		return indexOfS-strlen(pattern);
	} else {
		return -1;
	}
}

AB使A: aba B: ababa, 2.

此题可使用KMP匹配的思路进行求解,模式串pattern末尾添加一个不与任何字符匹配的字符,然后计算next数组,并且在与主串匹配时,当匹配到模式串末尾时,表示匹配成功一个子串,然后从末尾字符的next开始继续进行匹配。

实现如下:

int* KMPbilityGetNext(const char* pattern){//在pattern末尾假设增加一个和任何字符都不匹配的虚字符
	int n = strlen(pattern);
	if (n==0)	return NULL;
	int* next = new int[n+1];
	next[0] = -1;	//next[0]总是为0,表示当模式串的第一个字符不匹配时,pattern[0]应该和主串的下一个字符进行匹配
	int preMatchCount = -1;
	for (int i=1; i<n; i++){
		//查找与pattern[i-1]匹配的字符
		while (preMatchCount>=0 && pattern[i-1]!=pattern[preMatchCount])
			preMatchCount = next[preMatchCount];
		preMatchCount++;
		if (pattern[i]==pattern[preMatchCount])
			next[i] = next[preMatchCount];
		else
			next[i] = preMatchCount;
	}
	next[n] = preMatchCount;	//pattern末尾假设有一个和任何字符都不匹配的虚字符
	return next;
}
int MatchCount(const char* S, const char* pattern){
	int* next = KMPbilityGetNext(pattern);
	int matchCount = 0;	//匹配成功次数
	int indexOfPattern = 0;
	int indexOfS = 0;
	while (S[indexOfS]!='\0'){
		if (S[indexOfS] == pattern[indexOfPattern]){//主串字符和模式串字符相等时,继续匹配下一个字符
			indexOfPattern++;
			if (pattern[indexOfPattern]=='\0'){//匹配成功
				matchCount++;
				indexOfPattern = next[indexOfPattern];
			} else {
				indexOfS++;
			}
		} else {//字符匹配失败
			indexOfPattern = next[indexOfPattern];
		}
		if (indexOfPattern<0) {//遇到next值为-1的情况,需要特殊处理
			indexOfPattern = 0;
			indexOfS++;	
		}
	}
	delete[] next;
	return matchCount;
}

 

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值