第四章串、数组、广义表(4.3.3)

4.3.3串的模式匹配算法

子串的定位运算通常称为串的模式匹配或串匹配。

串的模式匹配设有两个字符串S和T,设S为主串,也称为正文串;设T为子串,也称为模式。

在主串S中查找与模式T相匹配的子串,如果匹配成功,确定相匹配的子串中的第一个字符在主串S中出现的位置。

1.BF算法

​ 模式匹配不一定是从主串的第一个位置开始,可以指定主串中查找的起始位置POS。如果采用字符串顺序存储结构,可以写出不依赖于其他串操作的匹配算法。

1.1算法步骤

BF算法设计思想

  1. 将主串S的第i个(初始i=l)字符和模式T的第1个字符进行比较,若相等,继续逐个比较后续字符;若不等,从主串S的下一字符(i++)起,重新与T的第一个字符进 行比较。
  2. 直到主串S的一个连续子串字符序列与模式T相等。返回值为S中与T 匹配的子序列第一个字符的序号,即匹配成功。否则,匹配失败, 返回值0.

1.2算法描述

int Index(SString S, SString T, int pos)
{
	//返回模式串T在主串S中第pos个字符之后第S一次出现的位置。若不存在,则返回值为0
	//其中,T非空,1≤pos≤StrLength(S)
	int i = pos;
	int j = 1;//初始化
	while(i<=S.length && j<=T.length)//两个串均未比较到末尾
	{
		if(S[i].ch==T[j].ch)
		{
			++i;
			++j;
		} //继续比较后继字符
		else
		{
			i=i-j+2;
			j=1;
		} //指针后退重新开始匹配
	}
	if (j>T.length)
		return i-T.length;//匹配成功
	else
		return 0;//匹配失败
}

在很多场景中,主串远比模式串长得多,即n>>m。
匹配成功的最好时间复杂度: O(m) ;
匹配失败的最好时间复杂度: O(n-m+1) = O(n-m)=O(n);
匹配失败的最坏时间复杂度:O(n*m)。

1.3代码

#include <iostream>
using namespace std;
int Index_BF(string A, string B, int pos) {
	int i = pos, j = 0;
	while (i < A.length() && j < B.length()) {
		//两个字符串均为比较到串尾(只有有一个到串尾就跳出循环) 
		if (A[i] == B[j]) {
			i++;
			j++;
		}
		else {
			//匹配失败指针回溯
			i = i - j + 1;
			j = 0;
		}
	}
	if (j >= B.length()) return i - B.length();
	else return -1;
}
int main() {
	string a = "abckjsef";
	string b = "kjs";
	int flag = Index_BF(a, b, 4);
	if (flag == -1) cout << "子串在主串之中不存在!" << endl;
	else cout << "子串在主串中起始位置为:" << flag + 1 << endl;
	return 0;
}

1.4运行结果

image-20220121175054606

2.KMP算法

上面的BF算法中频繁的重复比较相当于模式串在不断地进行自我比较,导致其效率低下。我们不得不思考有没有更高校的方法。

我们不想要主串回溯操作,利用部分匹配直接跳转到还没进行匹配的字符那里。

next数组: 当模式串的第j个字符匹配失败时,令模式串跳到next[j]再继续匹配。

当子串和模式串不匹配时,主串指针i不回溯,模式串指针j=next[j]。

KMP算法,最坏时间复杂度 O(m+n);其中,求 next 数组时间复杂度 O(m),模式匹配过程最坏时间复杂度 O(n)。

2.1算法描述

int Index_KMP(SString S, SString T, int pos)
{ 	// 利用模式串T的next函数求T在主串S中第pos个字符之后的位置的KMP算法
	//其中,T非空,1≤pos≤StrLength(S)
	int i = pos, j = 1;
	while (i <= S.length && j <=S.length )//两个串均未比较到串尾
		if (j == 0 || S[i] == T[j]) // 继续比较后继字
		{
			++i;
			++j;
		}
		else //利⽤next数组进⾏匹配(主串指针不回溯)
			j = next[j]; // 模式串向右移动
	if (j > T[0]) // 匹配成功
		return i - T[0];
	else
		return 0;//匹配失败
}

2.2代码

#include<iostream>
#include<cstring>
using namespace std;

//当T[k] == T[j],代表匹配成功,进行下一次的匹配。
void setNext(string T, int next[])
{
	int tlen = T.length();
	next[0] = -1;
	int j = 0, k = -1;
	while (j < tlen)
	{
		if (k == -1 || T[k] == T[j])  
        //当k = -1,代表前面匹配失败,重新开始匹配。
        //当T[k] == T[j],代表匹配成功,进行下一次的匹配。
		{
			k++;
			j++;
			next[j] = k;
		}
		else
		{
			k = next[k];    //如果两个条件都不满足,让k = next[k],去next的位置,重新开始。
		}
	}
}
int getLocate(string S, string T, int next[])
{
	setNext(T, next);
	int slen = S.length();
	int tlen = T.length();
	int i = 0, j = 0;
	
	//next=前后缀最长公共部分+1
	while (i < slen && j < tlen)
	{
		if (j == -1 || S[i] == T[j])
		{
			i++;
			j++;
		}
		else
		{
			j = next[j];
		}
	}
	if (j == tlen)
	{
		return i - tlen + 1;
	}
	return -1;
}
int main()
{
	int next[100];
	string s = "BBCSABCDABSABCDABCDABDE";
	string t = "ABCDABDABC";
	cout << getLocate(s, t, next);
	return 0;
}

2.3运行结果

image-20220121180821927

3.

3.1 算法4.3 计算next的函数值

void get_next(SString T,int next[)) 
{//求模式串 T的 next 函数值并存入数组 next
i=l;next[l)=O;j=O; 
while (i <T[O]) 
{ 
if(j==O II T[i)==T[j)) {++i;++j;next[i)=j; I 
else j=next[j);
}
}

3.2 算法4.4 计算next 函数的修正值

void get_nextval(SString T, int nextval[]) 
{//求模式串 T 的 next 函数修正值并存入数组 nextval
i=l;nextval [ 1] =0; j=O; 
while(i<T[O]) 
if (j==O II T [i) ==T [j l)
{
    ++i;++j;
     if(T[i) !=T[j)) nextval[i)=j; 
    else nextval [ i) =next val [ j) ;
}
else j=nextval[j];
}
}
posted @ 2022-02-10 16:47    阅读(19)  评论(0)    收藏  举报