串的模式匹配算法

本文介绍了两种经典的字符串匹配算法——BF(暴力匹配)算法和KMP算法。首先阐述了BF算法的基本思想及其优缺点,随后详细解释了KMP算法的工作原理,包括next数组的计算方法及其实现代码。此外,还探讨了KMP算法的一种改进形式。

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

一、BF(brute force)算法

#include<stdio.h>
void main()
{
	char s[]="abcdabcdaaabbbccc";
	char t[]="aaabbb";//设定s为主串,t为模式(子串)
	int i=0,j=0;
	while(s[i]&&t[j])//当s,t中任一一个字符串空时结束循环
		if(s[i]==t[j])//当前项s[i],t[j]匹配成功时,自动匹配下一个
		{
		 	i++;
			j++;
		}
		else
		{
			i=i-j+1;
			j=0;//匹配失败时,i进行回溯,i回到前一次匹配开头的下一个位置,j回到模式开头
		}
	if(!t[j]) printf("%d\n",i-j);
	else printf("匹配失败!\n");
}

暴力算法,对模式和主串进行一个个的匹配。该算法的优点在于:算法简单易懂,能够通过代码就明白算法的意义;但同时也存在缺点,缺点是对已知信息的利用不够充分且时间复杂度不稳定(最高可达O(m*n),最低为O(n),其中m为主串长度,n为模式长度)。对信息的利用不够充分体现在,当我通过扫描模式后,对模式中的字符是已知的,但是当我匹配失败的时候,我每次还是从模式的开头重新进行扫描。这样,就浪费了我对于模式的已知信息。

例如:

我们有主串:abcdabcdbccbbbccc;有模式:bcdbca。假设我们的匹配已经进行到了如下的步骤:
在这里插入图片描述
此时s[10]!=t[5],按照上面的BF算法,我们就应该让i回溯(i=i-j+1=6),让j=0重新开始匹配。但是我们已经读取了模式t中的字符,即我们知道蓝色的“bc”和黄色的“bc”是同时存在于模式t中的,故我们只需要保持i=10不动,让模式t中的t[2]与主串中的s[10]开始比较,这样我们就节省了两次移动,五次比较,使得程序的效率大大提升。

那么现在的问题就是,我如何知道当s[i]!=t[j]时,我的模式串的哪一位同s[i]进行下一轮比较,这就引出了另一个串的模式匹配算法:KMP算法

二、KMP算法

next[]数组

前文提到,我们需要知道当s[i]!=t[j]时,我的模式串的哪一位同s[i]进行下一轮比较。我们设模式串的第j位和主串的第i位匹配失败时,模式串重新从第next[j]位开始和主串的第i位匹配,于是现在问题转化为我们如何求next[j]?

分析next[]数组的意义可以知道(令k=next[j]),next[j]表示模式t中第j位匹配失败时,模式t中第j位之前有k个字符和模式t开头的k个字符是匹配的,并且我们要使得k要最大,如若不然主串在小于i-k号位置之前就会有匹配成功的可能,于是我们可以得到k的值:k=max{k|1<=k<j且t[0…k-1]=t[j-k…j-1]}。但存在k不存在的情况和j=0的情况:当k不存在时,即是说模式t中第j位之前没有k个字符和模式t开头的k个字符是匹配的,故我们需要令j=0(即next[j]=0),让其从开头重新匹配;当j=0时,子串与主串失配,此时需要i++,特别的我们令next[0]=-1。这样,我们可以得到next[]的值。

通过递归的方法来求next数组的值

1)设next[0]=-1;
2)设next[0…j-1]都已经求得;
我们现在要求next[j]:
令k_0=next[j-1];k_p=next[k_(p-1)],则当t[k_p]=t[j-1]时,next[j]=k_p+1;(可以用数学归纳法证明)

void get_next(char *t,int next[])
{
	next[0]=-1;//j=0
	for(int j=1;t[j];j++)
	{//循环终止条件为子串t中所有位置的next[]值已经求出来了
		int k=next[j-1];//将k初始化为k0,即此时k为序列{kp}中最大的
		while(k!=-1&&t[j-1]!=t[k])k=next[k];/*当t[j-1]!=t[k]时,利用k_p=next[k_(p-1)]
											求出k_1,k_2,k_3…*/
		next[j]=k+1;//当满足t[j-1]==t[k]时,knext[j]=k_p+1
	}
}

于是,通过数组next[]指导我们失配时,j的位置,这样我们就能够更快速的模式匹配

#include<stdio.h>
void get_next(char *t,int next[])
{
	next[0]=-1;//j=0
	for(int j=1;t[j];j++)
	{//循环终止条件为子串t中所有位置的next[]值已经求出来了
		int k=next[j-1];//将k初始化为k0,即此时k为序列{kp}中最大的
		while(k!=-1&&t[j-1]!=t[k])k=next[k];/*当t[j-1]!=t[k]时,利用k_p=next[k_(p-1)]
											求出k_1,k_2,k_3…*/
		next[j]=k+1;//当满足t[j-1]==t[k]时,knext[j]=k_p+1
	}
}

void main()
{
	char s[]="abcdabcdbccbbbccc";
	char t[]="bcdbca";
	int next[30];
	get_next(t,next);//获得next数组的全部值
	int i=0,j=0;
	while(s[i]&&(j==-1||t[j]))
		if(j==-1||s[i]==t[j])
		{
			i++;
			j++;
		}
		else j=next[j];//当s[i]!=t[j]时,j从next[j]的位置开始匹配
	if(!t[j]) printf("%d\n",i-j);
	else printf("匹配失败!\n");
}

改进的KMP算法

void get_nextval(char t[],int nextval[])
{//获得数组nextval的递归算法
	int j,k;
	nextval[0]=-1;
	for(j=1;t[j];j++)
	{
		k=t[j-1];
		while(k!=-1&&t[k]!=t[j-1])
			k=nextval[k];//前面的和获得next数组值的算法一样
		if(t[j]==t[k+1])
			nextval[j]=nextval[k];/*当t[j]和j将要滑向的位置的t[k+1]的值相等时,
								  j继续向下滑,即nextval[j]的值和nextval[k]的值
								  相等,然后再进行比较
								  */
		else 
			nextval[j]=k+1;
	}
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值