模式串匹配实现--BF算法+KMP算法+c++代码

0、前言

串就是字符串,由数字、字母、下划线组成的一串字符。在C语言中式用双引号,像:“abcd” 就是一个字符串,存储方式类似字符数组,要注意的还是下标与位置相差1,即a[0]= ‘a’ ,a[2]=‘c’ ;还有就是 ‘\0’ 是结尾符。

串模式匹配有两个字符串, S S S:主串; T T T:子串,也称为模式串。
串匹配就是在主串中查找与模式串T相匹配的子串,若是匹配成功,则返回匹配子串出现的第一个位置。
BF(Brute-Force)算法是最简单直观的模式(串)匹配算法
KMP算法是在BF算法的改进,不用回溯,使用next函数,

1、BF算法

1.1、算法描述

BF算法就是你想的那样“暴力”算法,一个一个相匹配,直至成功或失败,若懂的话下面文字就不用看了,再往下有代码。

主串开始计数,遍历比较字符,主串字符与模式串字符是否相等,若相等,则接着比较(主串位置与模式串位置都往后移动一位比较);若不相等,则回溯,即主串计数返回到开始匹配的下一个位置。直至在主串中找到与模式串相同的串,返回值是匹配子串出现的第一个位置。若匹配不成功的结果是主串计数到末尾。

这就是BF算法是思想吧。下面是详细步骤,想看就看。

  • (1)分别利用计数指针 i i i j j j指示主串 S S S和模式串 T T T中当前正待比较的字符位置, i i i的初始值为pos(指定主串中查找的起始位置), j j j初始值为1。
  • (2) 如果两个串均未比较到串尾,即 i i i j j j小于等于 S S S T T T的长度是,则循环执行以下操作:
    • S.ch[i] 和 T.ch[j]比较,若相等,则 i 和 j 均分别指示串中下一个位置,继续比较后继字符;
    • 若不等,计数指针后退重新开始匹配,从主串的下一个字符 (i=i-j+2) 起再重新和模式串的第一个字符(j=1)比较
  • (3)如果 j>T.length ,说明匹配成功,返回和模式 T T T中第一个字符相等的字符在主串 S S S的序号(i-T.length);否则匹配不成功,返回0.

1.2、BF代码

#include<iostream>
#include<string.h>
using namespace std;

#define maxlen 123		//串的最大长度 

typedef struct{
	char ch[maxlen+1];		//存储串的一维数组
	int length;				//串的长度 
}sstring;

int Index_BF(sstring S,sstring T,int pos){
	//BF算法匹配 
	int i,j;
	i=pos;j=1;
	while(i<=S.length && j<=T.length){
		if(S.ch[i]==T.ch[j]){
			i++;j++;
		}
		else{
			i=i-j+2;j=1;
		}
	}
	if(j>T.length) return i-T.length;
	else return 0;
}

int main()
{
	sstring S,T;
	int loc;		//保存匹配位置 
	int pos=1;			//查找起始位置,这里默认为1 
	cout<<"输入主串(第一个位置字符不计,一般为0(下同)):"<<endl;
	cin>>S.ch;
	S.length=strlen(S.ch)-1;			//存储串长度 
	cout<<"输入模式串:"<<endl;
	cin>>T.ch;
	T.length=strlen(T.ch)-1;
	loc=Index_BF(S,T,pos);
	if(loc)
		cout<<"匹配成功!"<<endl<<"匹配开始位置:"<<loc<<endl; 
	else
		cout<<"主串中无此模式串";
	return 0;
 } 

代码运行结果:
在这里插入图片描述

2、KMP算法

2.1、描述

KMP算法是在BF算法基础上的改进,由Knuth、Morris和Pratt同时设计实现。
其改进在于每当一趟匹配过程中比较不等时,不需要回溯 i i i指针,而是例用已经得到的”部分匹配“的结果向右”滑动“,接着继续进行比较。
要解决的问题就是当主串中第 i i i个字符与模式中第 j j j个字符“失配”时,主串中第 i i i个字符再与模式串中哪个字符再比较?
解决办法是用到next函数,其 next[j]=k ,k就是再进行比较的字符的位置。
模式串的next函数定义:
n e x t [ j ] = { 0 j = 1 ( t 1 与 s 1 比 较 不 等 时 , 下 一 步 进 行 t 1 与 s i + 1 ) 的 比 较 ) M a x { k ∣ 1 < k < j 且 有 “ t 1 t 2 ⋯ t k − 1 ” = “ t j − k + 1 t j − k + 2 ⋯ t j − 1 ” } 1 k = 1 ( 不 存 在 相 同 子 串 , 下 一 步 进 行 t 1 与 s i 的 比 较 next[j] = \begin{cases} 0 &j=1(t_1与s_1比较不等时,下一步进行t_1与s_{i+1})的比较) \\ Max &\lbrace k|1<k<j且有“t_1t_2\cdots t_{k-1}”=“t_{j-k+1}t_{j-k+2} \cdots t_{j-1} ”\rbrace\\ 1 &k=1(不存在相同子串,下一步进行t_1与s_i的比较\\ \end{cases} next[j]=0Max1j=1(t1s1t1si+1)){k1<k<jt1t2tk1=tjk+1tjk+2tj1}k=1t1si
手推计算 n e x t [ j ] next[j] next[j]值方法是:模式串第 j j j位置前缀(从第1位置,最大到 j-2 )与后缀(从第 j-1 往前到第2位置)最大相同数+1

2.2、KMP代码

#include<iostream>
#include<string.h>
using namespace std;

#define maxlen 123		//串的最大长度 

typedef struct{
	char ch[maxlen+1];		//存储串的一维数组
	int length;				//串的长度 
}sstring;

void get_next(sstring T,int next[]){
	//求模式串T的next函数值并存入到数组next中 
	int i=1,j=0;
	next[0]=0;			//下标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];
	}
}

int Index_KMP(sstring S,sstring T,int pos,int next[]){
	int i,j;
	i=pos;j=1;
	while(i<=S.length && j<=T.length){
		if(j==0 || S.ch[i]==T.ch[j]){
			i++;j++;
		}
		else{
			j=next[j]; 
		}
	}
	if(j>T.length) return i-T.length;
	else return 0;
}

int main()
{
	sstring S,T;
	int next[20],i;
	int loc;			//保存匹配位置 
	int pos=1;			//查找起始位置,这里默认为1 
	cout<<"输入主串(第一个位置字符不计,一般为0(下同)):"<<endl;
	cin>>S.ch;
	S.length=strlen(S.ch)-1;			//存储串长度 
	cout<<"输入模式串:"<<endl;
	cin>>T.ch;
	T.length=strlen(T.ch)-1;
	get_next(T,next);
	cout<<"next:"<<endl;			//打印next值
	for(i=1;i<T.length+1;i++)
		cout<<next[i]<<" ";
	cout<<endl;
	loc=Index_KMP(S,T,pos,next);
	if(loc)
		cout<<"匹配成功!"<<endl<<"匹配开始位置:"<<loc<<endl; 
	else
		cout<<"主串中无此模式串";
	return 0;
 } 

代码运行结果:
在这里插入图片描述

2.3、next修正

前面定义的next函数在某些情况下有缺陷,就像模式串是“aaaabb”,主串“aaabaaaabba”时,当 i = 4 i=4 i=4 j = 4 j=4 j=4时 S.[i] ≠ \neq =T.[j] ,由 next[i]的指示还需进行 ( i = 4 , j = 3 ) 、 ( i = 4 , j = 2 ) 、 ( i = 4 , j = 1 ) (i=4,j=3)、(i=4,j=2)、(i=4,j=1) (i=4,j=3)(i=4,j=2)(i=4,j=1)直至 n e x t [ j ] = 0 next[j]=0 next[j]=0 i i i才往后移动一位。实际上,由于模式中1~3个字符和第4个字符都相等,因此不需要再和主串中第4个字符相比较,接着进行 i = 5 , j = 1 i=5,j=1 i=5,j=1时的比较。计算next函数修正就是要干这件事的。
算法代码

void get_nextval(sstring T,int nextval[]){
	int i=1,j=0;
	nextval[0]=0;
	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]=nextval[j];
			else nextval[i]=j;
		}
		else j=nextval[j];
	}
}

总结

BF和KMP算是模式串匹配里最经典的算法了的。
BF算法算是比较蛮力的方法,一个一个遍历,假如主串长度为n,模式串长度为m;
最好情况下平均时间复杂度为O(n+m);
最坏情况下平均时间复杂度是O(n×m);
虽然BF算法的时间复杂度是O(n×m),但一般情况下,其实际的执行时间近似于O(n×m),因此还是很实用。KMP算法仅当模式串与主串之间存在许多“部分匹配”的情况下,才显得比BF快得多。
KMP算法最大特点是不需要回溯,仅对主串从头到尾扫描一遍(这对处理从外设输入的庞大文件很有效,可以边读边匹配,而无需回头重读)
看图:
在这里插入图片描述
上面这个图分四个运行结果,从运行时间上来看,KMP是比BF算法快点,当主串字符“部分重复”的不多时,这俩也没什么太大区别;对next的修正来说,只有遇到特殊情况的时候,就“重复”多的时候,作用就比较明显。

  • 3
    点赞
  • 27
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
BF算法KMP算法都是模式匹配算法,但是它们的时间复杂度不同。BF算法的时间复杂度为O(m*n),其中m和n分别为主模式的长度。而KMP算法的时间复杂度为O(m+n)。因此,当模式较长时,KMP算法的效率更高。 下面是BF算法KMP算法的介绍和演示: 1. BF算法(暴力匹配算法) BF算法是一种朴素的模式匹配算法,它的思想是从主的第一个字符开始,依次和模式的每个字符进行比较,如果匹配成功,则继续比较下一个字符,否则从主的下一个字符开始重新匹配BF算法的时间复杂度为O(m*n)。 下面是BF算法的Python代码演示: ```python def BF(main_str, pattern_str): m = len(main_str) n = len(pattern_str) for i in range(m-n+1): j = 0 while j < n and main_str[i+j] == pattern_str[j]: j += 1 if j == n: return i return -1 # 测试 main_str = 'ababcabcacbab' pattern_str = 'abcac' print(BF(main_str, pattern_str)) # 输出:6 ``` 2. KMP算法(Knuth-Morris-Pratt算法) KMP算法是一种改进的模式匹配算法,它的核心思想是利用已经匹配过的信息,尽量减少模式与主匹配次数。具体来说,KMP算法通过预处理模式,得到一个next数组,用于指导匹配过程中的跳转。KMP算法的时间复杂度为O(m+n)。 下面是KMP算法的Python代码演示: ```python def KMP(main_str, pattern_str): m = len(main_str) n = len(pattern_str) next = getNext(pattern_str) i = 0 j = 0 while i < m and j < n: if j == -1 or main_str[i] == pattern_str[j]: i += 1 j += 1 else: j = next[j] if j == n: return i - j else: return -1 def getNext(pattern_str): n = len(pattern_str) next = [-1] * n i = 0 j = -1 while i < n-1: if j == -1 or pattern_str[i] == pattern_str[j]: i += 1 j += 1 next[i] = j else: j = next[j] return next # 测试 main_str = 'ababcabcacbab' pattern_str = 'abcac' print(KMP(main_str, pattern_str)) # 输出:6 ```

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值