串:KMP算法

前言

串的模式匹配

子串的定位操作通常称为模式匹配,它求的是子串(常称模式串)在主串中的位置。

前缀、后缀、部分匹配值

前缀:包含第一个字符且不包含最后一个字符的子串。

后缀:包含最后一个字符且不包含第一个字符的子串。

部分匹配值:字符串的最长相等的前后缀长度。

以"ababa"为例:

  • "a"的前后缀均为空,最长相等的前后缀长度为0。
  • "ab"的前缀为{"a"},后缀为{"b"},最长相等前后缀长度为0。
  • "aba"的前缀为{"a","ab"},后缀为{"a","ba"},最长相等前后缀长度为1。
  • "abab"的前缀为{"a","ab","aba"},后缀为{"b","ab","bab"},……2。
  • "ababa"的前缀为{"a","ab","aba","abab"},后缀为{"a","ba","aba","baba"},……3。

故字符串"ababa"的部分匹配值为00123。

KMP算法

以主串为"ababcabcacbab",子串(模式串)为"abcac"为例。

求出子串的部分匹配值为00010,得到部分匹配值表(PM表)

a

b

c

a

c

0

0

0

1

0

在匹配过程中,第一趟:

主串:a b a b c a b c a c b a b

子串:a b c

a与c不匹配,则查PM表得最后一个匹配字符b的匹配值为0,则子串向后移动的位数:

移动的位数=已匹配的字符数-对应部分的匹配值

则子串向后移动2-0=2位,继续匹配,如此往复直到主串或子串走完。


上述每遇到不匹配的字符,都去找前一个字符的部分匹配值,所以干脆优化一下PM表,将表中元素统一向右移一位,得next数组:

a

b

c

a

c

-1

0

0

0

1

  1. 第一个元素右移后的空缺用-1来填补,因为若是第一个字符不匹配,则子串直接右移一位,用上述公式计算右移的位数也是0-(-1)=1,正确。
  2. 由于最后一个元素的部分匹配值是给下一个元素用的,而没有下一个元素,所以可以舍去。

设编号为j(从1开始),则后移位数Move=(j-1)-next[j],由于在实际匹配过程中子串在内存里是不会移动的,只是子串的指针在移动。子串在主串上向后移动Move位,相当于子串的指针向前移动Move位。所以相当于将子串的指针j回退到 j=j-Move=next[j]+1。

所以可以将next数组优化一下,所有元素+1:

编号j

1

2

3

4

5

子串S

a

b

c

a

c

next

0

1

1

1

2

综上所述,next数组是在PM表基础上元素整体右移后+1得到的。

next[j]的含义是:在子串的第j个字符与主串发生失配时,则跳到子串的next[j]位置重新与主串当前位置进行比较。

KMP算法的时间复杂度为O(m+n)(O(m)求出next数组,O(n)遍历主串),其优点是主串不回溯。

改进的KMP算法

next数组还可以进一步优化,以主串"aaabaaaaab",子串"aaaab"为例:

主串:a a a b a a a a a b

子串:a a a a b

第一趟匹配a与b不匹配,如果按照next数组,子串指针j又得移到子串第一个字符开始与b匹配,实际大可不必,前面都是a肯定与b不匹配。

当子串第j个元素与主串失配,j需要跳回next[j]处,如果next[j]处的元素与j处的元素相等,那么next[j]处的元素必然失配,故需要接着往前找next[next[j]],如果next[next[j]]处的值不等于j处的值,则将next[j]修正为next[next[j]],如果二者相等,则继续向前寻找next,直至两者不相等为止,更新后的数组命名为nextval。

如下图所示:

模式串S

a

a

a

a

b

j

1

2

3

4

5

next[j]

0

1

2

3

4

nextval[j]

0

0

0

0

4

总结

求next数组

  1. 依次求得各子串的部分匹配值,生成PM表。
  2. PM表元素统一右移一位,首位用-1填充。
  3. 所有元素+1。

求nextval数组

比较每个元素与其next处的元素,若相等,则接着往前找next的next,直到找到与当前元素不同的元素的编号,将该编号赋给先前遍历过的next值。

目的就是为了省去相同的不匹配的字符重复比较的次数。

C语言实现

//KMP算法(包括改进KMP算法)
#define MAXLEN 25
#include<stdio.h>
#include<string.h>
typedef struct{
	char ch[MAXLEN];//ch[0]不用,从ch[1]开始存,位序与下标相等,能存24个数据元素 
	int length;
}SString;

void get_next(SString T,int next[]){//求next数组 
	int i=1,j=0;//next[i]=j
	next[1]=0;//特别地,next[1]=0
	while(i<T.length){
		if(j==0||T.ch[i]==T.ch[j]){
			i++;
			j++;
			next[i]=j;
		}else{
			j=next[j];
		}
	}
}

void get_nextval(SString T,int nextval){
	int i=1;j=0;//nextval[i]=j
	nextval[1]=0;
	while(i<T.length){
		if(j==0||T.ch[i]==T.ch[j]){
			i++;
			j++;
			if(T.ch[i]!=T.ch[j]) nextval[i]=j;
			else nextval[i]=nextval[j];//消除递归,追根溯源 
		}
		else{
			j=nextval[j];
		}
	}	
}

int index_KMP(SString S,SString T,int next[]){//KMP
	int i=1,j=1;//主串和模式串的指针
	while(i<=S.length&&j<=T.length){
		if(j==0||S.ch[i]==T.ch[j]){//当第一个字符就不匹配时,ij都右移一位 
			i++;
			j++;
		}else{
			j=next[j];
		}
	}
	if(j>T.length)return i-T.length;//匹配成功 
	return 0;//匹配失败 
}

int main(){
	char* ss="googlegoogle";
	char* tt="abaabcaba";
	
	SString S,T;
	//init
	for(int i=1;i<13;i++){
		S.ch[i]=ss[i-1];
	}
	S.length=12;
	for(int i=1;i<10;i++){
		T.ch[i]=tt[i-1];
	}
	T.length=9;
	
	int next[9];
	get_next(T,next);
	
	for(int i=1;i<=9;i++)
		printf("%d ",next[i]);
		
	return 0;
}

 

  • 2
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
KMP算法是一种字符匹配算法,用于在一个文本S内查找一个模式P的出现位置。其核心思想是利用已经匹配的部分来避免重复匹配。 具体实现如下: 1. 预处理模式P,得到一个next数组。next[i]表示当P[i]与S[j]不匹配时,P[i]应该与S的哪个位置进行匹配,即P[0]~P[next[i]-1]与S[j-next[i]+1]~S[j-1]已经匹配成功。 2. 从文本S的第一个位置开始匹配,同时用一个指针i记录当前匹配到的模式位置,用另一个指针j记录当前匹配到的文本位置。 3. 如果P[i]与S[j]匹配成功,则继续匹配下一个位置。 4. 如果P[i]与S[j]不匹配,则根据next[i]的值将模式向右移动i-next[i]个位置,同时将i设置为next[i],继续匹配。 5. 如果匹配成功,则返回匹配位置;如果匹配失败,则返回-1。 代码实现如下: ```java public static int kmp(String s, String p) { int[] next = getNext(p); int i = 0, j = 0; while (i < p.length() && j < s.length()) { if (i == -1 || p.charAt(i) == s.charAt(j)) { i++; j++; } else { i = next[i]; } } if (i == p.length()) { return j - i; } else { return -1; } } private static int[] getNext(String p) { int[] next = new int[p.length()]; next[0] = -1; int i = 0, j = -1; while (i < p.length() - 1) { if (j == -1 || p.charAt(i) == p.charAt(j)) { i++; j++; next[i] = j; } else { j = next[j]; } } return next; } ``` 以上代码实现了KMP算法,其中getNext()方法用于计算next数组kmp()方法用于匹配字符

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值