字符串的模式匹配算法KMP

博客介绍了三种模式匹配方法。一是利用串的其他操作实现,用到StrLength等基本操作;二是朴素模式匹配算法,用基本数组实现,分析了不同情况的时间复杂度;三是KMP算法,包括其原理、部分匹配表及改进,该算法在部分匹配多时有优势。

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

 

目录

1、利用串的其他操作实现模式匹配

2、朴素的模式匹配算法:不用串的其他操作,只用基本数组实现

3、KMP

3.1 KMP算法

3.2 KMP算法改进


简单介绍三种方法。

1、利用串的其他操作实现模式匹配

//返回子串T在主串S中第pos个字符之后的位置
int Index(String S, String T, int pos)
{
    int n,m,i;
    String sub;
    if(pos>0){
		n = StrLength(S);
		m = StrLength(T);
		i = pos;
		while(i <= n-m+1){
			SubSting(sub,S,i,m); //取主串第i个位置长度与T相等的子串给sub
			if(StrCompare(sub,T) != 0)  //如果两串不相等
			    ++i;
		    else
				return i;  //如果两串相等则返回i值
		}
	}
	return 0;  //若无子串与T相等,返回0
}

代码当中用到了StrLength、SubString、StrCompare等基本操作。

int StrLength(String S) //返回串长
{
	return S[0];
}

//用Sub返回串S的第pos个字符起长度为len的子串
Status SubString(String Sub, String S, int pos, int len)
{
	int i;
	if(pos<1 || pos>S[0] || len<0 || len>S[0]-pos+1)
		return ERROR;
	for(i=1; i<len; i++)
		Sub[i] = S[pos+i-1];
	Sub[0] = len;
	return OK;
}

//串的比较:若S>T,返回值>0;S<T,返回值<0;S=T,返回值=0。
int StrCompare(String S,String T)
{
	int i;
	for(i=1;i<=S[0]&&i<=T[0];i++){
		if(S[i] != T[i])
			return S[i]-T[i];
	}
	return S[0]-T[0];
}

2、朴素的模式匹配算法:不用串的其他操作,只用基本数组实现

简单来说就是对主串的每一个字符作为子串的开头,与要匹配的字符串进行匹配。对主串做大循环,每个字符开头做子串长度的小循环,直到匹配成功或全部遍历结束为止。

//朴素模式匹配
//返回子串T在主串S中第pos个字符之后的位置
int Index(String S, String T, int pos)
{
	int i = pos; //i用于主串S中当前位置下标值
	int j = 1;   //j用于子串T中当前位置下标值
	while(i <= S[0] && j <= T[0])  //将主串长和要匹配的子串长存放在S[0]和T[0]中
	{
		if(S[i] == T[j]){  //两字母相等则继续
			++i;
			++j;
		}
		else{  //指针后退重新开始匹配
			i = i-j+2;  //i后退到上次匹配首位的下一位
			            //i = i-(j-1)+1 (i的新位置 = 旧位置+1)
			j = 1;      //j后退到子串T的首位
		}
	}
	if(j > T[0])
		return i-T[0];
	else
		return 0;
}

时间复杂度:(n为主串长,m为子串长)

最好情况:一开始就匹配成功,O(1)

最坏情况:每次不成功的匹配都发生在子串T的最后一个字符,O((n-m+1)*m)

平均情况:(n+m)/2次查找,O(n+m)

3、KMP

朴素模式匹配:主串S的i值不断回溯来完成。KMP算法就是为了让这没必要的回溯不发生。

KMP算法中:i值不可以变小。那么既然i不能回溯,要考虑的变化就是j值了。子串T的首字符与自身后面的字符之间的比较,如果发现有相等字符,j值的变化就会不同。即:j值的变化与主串S没什么关系,关键在于T串的结构中是否有重复的问题。

3.1 KMP算法

我们把T串各个位置的j值的变化定义为一个数组next:

如果前后缀一个字符相等,k值为2;两个字符相等k为3;那个相等则k为n+1。没有相等字符属于其它情况,next[j]=1。

next[j]是一个部分匹配表:有时候字符串头部和尾部会重复,比如"abcdab"中有两个"ab",那么它的部分匹配值就是2,实际上就是"ab"的长度。搜索词移动时,第一个"ab"向后移动4位(字符串长度 - 部分匹配值)就可以来到第二个"ab"的位置。

失配时,T相对于主串S向右移动了j-next[j]位。当某个字符失配时,该字符对应的next值会告诉你下一步匹配中,T应该跳到那个位置。如果next[j]=0/1,则跳到T的开头字符;如果next[j]=k且k>1,代表下次匹配跳到j之前的某个字符,且具体跳过了k个字符。

//KMP算法
//通过计算返回子串T的next数组
void get_next(String T, int *next)
{
	int i=1,j=0;
	next[1]=0;
	while(i<T[0])
	{
		if(j==0 || T[i]==T[j]) //若字符相同
		{   //T[i]表示后缀的单个字符,T[j]表示前缀的单个字符
			++i;
			++j;
			next[i] = j;
		}
		else
			j = next[j];  //若字符不相同,则j值回溯    
	}
}
//返回子串T在主串S中第pos个字符之后的位置
int Index_KMP(String S, string T, int pos)
{
	int i = pos;  //i用于主串S中当前位置下标值
	int j = 1;    //j用于子串T中当前位置下标值
	int next[255];     //定义next数组
	get_next(T, next); //对子串T作分析,得到next数组
	while(i<=S[0] && j<=T[0])
	{
		if(j==0 || S[i]==T[j]){  //两字母相等则继续
			++i;
			++j;
		}
		else  //指针后退开始重新匹配
			j = next[j]; //j退回合适的位置,i值不变
                         //移动位数 = 已匹配的字符数 - 对应的部分匹配值
	}
	if(j>T[0])
		return i-T[0];
	else
		return 0;
}

算法时间复杂度为O(n+m)。

KMP算法仅在子串与主串之间存在许多“部分匹配”的情况下才能体现出优势,否则和朴素的模式匹配没有很大差异。

3.2 KMP算法改进

//KMP算法改进
//求模式串T的next函数修正值并存入数组nextval
void get_nextval(String T, int *nextval)
{
	int i=1,j=0;
	nextval[1]=0;
	while(i<T[0])
	{
		if(j==0 || T[i]==T[j]){
			++i;
			++j;
			if(T[i] != T[j])
				nextval[i] = j;
			else
				nextval[i] = nextval[j];
		}
	}
}

在计算出next值的同事,如果a位字符与它next值指向的b位字符相等,则该a位字符的nextval就指向b位的nextval值;若不相等,则该a位的nextval值就是它自己a位的next值。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值