第四章串、数组、广义表(4.3.3)
目录
4.3.3串的模式匹配算法
子串的定位运算通常称为串的模式匹配或串匹配。
串的模式匹配设有两个字符串S和T,设S为主串,也称为正文串;设T为子串,也称为模式。
在主串S中查找与模式T相匹配的子串,如果匹配成功,确定相匹配的子串中的第一个字符在主串S中出现的位置。
1.BF算法
模式匹配不一定是从主串的第一个位置开始,可以指定主串中查找的起始位置POS。如果采用字符串顺序存储结构,可以写出不依赖于其他串操作的匹配算法。
1.1算法步骤
BF算法设计思想
- 将主串S的第i个(初始i=l)字符和模式T的第1个字符进行比较,若相等,继续逐个比较后续字符;若不等,从主串S的下一字符(i++)起,重新与T的第一个字符进 行比较。
- 直到主串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运行结果
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运行结果
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];
}
}