串的模式匹配

时间:2022-09-09 08:41:13

基本概念

字串的定位操作通常称作模式匹配(其中子串成为模式串),是各种串处理系统中的重要操作之一.
在博文c语言实现字符串中给出了定位函数int string_index(h_string S, h_string T, int pos)的一种算法.该算法的基本思路:
从主串S的第pos个字符起和模式串T的第一个字符比较,若相等,则继续逐个比较后续字符;否则从主串S的下一个字符起再重新和模式串T的字符比较.依次类推,直至模式T中的每一个字符依次和主串S中的一个连续字符序列相等,则称匹配成功,函数返回值为和模式T中第一个相等的字符在主串S中的序号,否则称匹配不成功,函数返回值为-1
例如,设 S 为目标串, T 为模式串,且不妨设:
S=“ s0 s1 s2 sn1 ” , T=“ t0 t1 t2 tm1 ” 串的匹配实际上是对合法的位置 0 ≦ i ≦ n-m 依次将目标串中的子串 s[i…i+m-1] 和模式串 t[0…m-1] 进行比较:
◆ 若 s[i…i+m-1]=t[0…m-1] :则称从位置 i = pos - 1开始的匹配成功,亦称模式 t 在目标 s 中出现;
◆否则匹配失败i=i+1,继续比较,直至i>n-m

在某些情况下,此算法的效率为O(n+m),其中n为主串S的长度,m为模式串T的长度.
例如,检查串”China”是否存在于下列主串时:
“More thoughts from users on the ground in China about ExpressVPN as seen on Twitter and Facebook! “

但是在某些情况下,此算法的效率为O(n*m).
例如,检查”00000001”是否存在于下列字串时:
“00000000000000000000000000000000000000000000000000000000000000001”

算法的C语言实现

int string_index(h_string S, h_string T, int pos)
{
/* 若T为空串 */
if(!(T.length))
return -1;
/* 比较 */
/* 串S的起始位置 */
int i = pos -1;
/* 串T的起始位置 */
int j = 0;
while(i < S.length && j < T.length)
{
if(S.str[i] == T.str[j])
{
i += 1;
j += 1;
}
else
{
i = i -j + 1;
j = 0;
}
}
if (j >= T.length)
return (i - T.length);
return -1;
}

算法的改进

KMP算法

此算法可以在O(n+m)的时间复杂度下完成串的模式匹配操作.相比于前面的算法,其改进在于:
每当匹配过程中出现字符比较不相等时,不需要回溯i指针,而是利用已经得到的”部分匹配”的结果将模式串向右”滑动”尽可能远的一段距离之后,继续进行比较.
实现KMP算法的难点在于:当匹配过程中产生”失配”(即 si tj )时,主串中第i个字符(i指针不回溯(与上诉算法的主要区别))应与模式中哪个字符再做比较

设S为目标串,T为模式串,且不妨设:S=“ s0 s1 s2 sn1 ”,T=“ t0 t1 t2 tm1 ”。

  1. 假设此时应与模式中第k(k < j)个字符开始继续比较,则模式中前k-1个字符一定满足下面的关系(且不可能存在k`>k):

    sik+1sik+2...si1=t0t1t2...tj1(3)

  2. 由前面得到的”部分匹配”,有下面等式:

    sik+1sik+2...si1=tjk+1tjk+2...tj1(2)

  3. 根据上述的两个等式的左边相等可得:

    t0t1t2...tj1=tjk+1tjk+2...tj1 (3)

由上式可知,当模式串满足式(3)的两个子串时,在匹配过程中,主串中第i个字符与模式串中第j个字符不相等时,仅将模式串向右滑动至模式串中第k个字符和主串第i个字符对齐继续比较即可

若令next[j]=k,则next[j]表明当前模式中第j个字符与主串相应字符失配时在模式串中需要重新和主串中该字符进行比较的字符的位置next函数的定义如下:


f(n)=1,MAX,0, j=0k {k | 1 < k < jt0...tj1=tjk+1...tj1}


:

上述的讲述难以理解,配图说明
假设在我们的匹配过程中出现了这一种情况:

串的模式匹配
根据 KMP 算法,在该失配位会调用该位的 next 数组的值!在这里有必要来说一下 next 数组的作用!

首先,我们取之前已经匹配的部分(即蓝色的那部分!)

串的模式匹配

前面的next函数,体现到图中就是这个样子!

串的模式匹配

next 数组的作用如下图所示:

串的模式匹配

下一次匹配:

串的模式匹配

求模式串abaabcac的next函数值

  • 当模式串第一个字符没有匹配

串的模式匹配

此时主串i+1;next[0]=-1

  • 当模式串第二个字符没有匹配

串的模式匹配

此时,next[1]=0,即返回第一个字符

  • 当模式串第三个字符没有匹配
    串的模式匹配

此时,next[2]=0,即返回第一个字符

  • 当模式串第四个字符没有匹配
    串的模式匹配
    此时,next[3]=1,即返回第二个字符

  • 当模式串第五个字符没有匹配
    串的模式匹配
    此时,next[4]=1,即返回第二个字符

  • 当模式串第六个字符没有匹配
    串的模式匹配
    此时,next[5]=2,即返回第三个字符

  • 当模式串第七个字符没有匹配
    串的模式匹配
    此时,next[6]=0,即返回第一个字符

  • 当模式串第八个字符没有匹配
    串的模式匹配
    此时,next[7]=1,即返回第二个字符


next

可以把next函数求值问题看成模式匹配问题,整个模式串既是主串又是模式串。而在当前的匹配过程中,已有 tjk+1=t0 , tjk+2=t1,...,tj1=tk1 ,则当 tjtk 时应将模式串向右滑动至以模式串中的第next[k]个字符和主串中第j个字符相比较,若 next[k]=k ,且 tj=tk ,则说明在子串中第j+1个字符之前存在一个长度为k’(即next[k])的最长子串,和模式串从第一个字符起长度为k’的子串相等.即


t1t2...tk=tjk+1tjk+2...tj(1<k<k<j)


这就是说, next[j+1]=k+1


next[j+1]=next[k]+1


同理,若 tjtk ,则将模式继续向右滑至将模式串中第 next[k] 个字符和 tj 对齐,……,以此类推,直至 tj 和模式串中某个字符匹配或者不存在任何 k ( 0<k<j )满足下面等式


t1t2...tk=tjk+1tjk+2...tj(1<k<k<j)


则有:

next[j+1]=0

求模式串abaabcac的next函数值
串的模式匹配
1.根据定义可以知道 next[0]=1,next[1]=0


2.求 next[2] ,由于 next[1]=0,t0=a,t1=b,t0t1 ,所以 next[2]=0


3.求 next[3] ,由于 next[2]=0,t0=a,t2=a,t0=t2 ,所以 next[3]=next[2]+1=1


4.求 next[4] 由于 next[3]=1,t1=b,t3=a,t1t3 ,又因为 next[next[3]]=0,t0=a,t3=a 所以 next[4]=next[next[3]]+1=1


5.求 next[5] ,由于 next[4]=1,t4=b,t1=b,t1=t4 ,所以 next[5]=next[4]+1=2


6.求 next[6] ,由于 next[5]=2,t2=a,t5=c,t2t5 ,又因为 next[next[5]]=0,t0=a,t5=c,t0t5 ,所以 next[6]=0


7.求 next[7] ,由于 next[6]=0,t0=a,t6=a,t0=t6 ,所以 next[7]=next[6]+1=1


8.综上,next函数值如下:
串的模式匹配

C语言代码实现

next函数求值代码实现

int get_next(h_string S, int next[])
{
/* 根据next函数的定义 */
next[0] = -1;
next[1] = 0;
int i = 2;
int j = 0;
while(i < S.length)
{
if(j == -1 || S.str[i-1] == S.str[j])
{
next[i] = j + 1;
j = next[i];
i += 1;
}
else
j = next[j];
}
}

测试文件

int main()
{
char *ch = "abaabcac";
int next[8];
get_next(ch, next);
int i = 0;
for(i = 0; i < 8;i++)
printf("%d ", next[i]);
printf("\n");
return 0;
}

运行结果

串的模式匹配

模式匹配函数的实现

int string_index(h_string S, h_string T, int pos)
{
/* 如果模式串为空 */
if(!T.length)
return -1;
/* 主串的下标 */
int i = pos -1;
/* 模式串的下标 */
int j = 0;
while(i < S.length && j < T.length)
{
if(S.str[i] == T.str[j])
{
i++;
j++
}
else
{
j = next[j];
. }
}
if(j >= T.length)
return (i - T.length);
return -1;
}