【数据结构】串的模式匹配算法

参考资料:《数据结构(C语言版)严蔚敏著》
版权说明:未经作者允许,禁止转载。如引用本文内容,需标明作者及出处。如本文侵犯了您的权益,请联系我删除并致歉。
文章说明:如文章中出现错误,请联系我更改。如您对文章的内容有任何疑问,也欢迎来与我讨论。
本文正在施工中...请稍等...


基本概念

模式匹配:在串S中定位某个子串T的操作称为模式匹配,其中串S称为目标串,串T称为模式串。
匹配成功:在模式匹配过程中,若出现模式串T中的每个字符依次和目标串S中的一个连续的字符序列相等,则称匹配成功,否则称匹配不成功。


常见算法

Brute Force算法

从目标串S的第pos个字符起和模式串的第一个字符进行比较,若相等,则继续逐个比较后续字符;否则从目标串的下一个字符起再重新和模式串的字符进行比较。依次类推,直至模式串T中的每个字符依次和目标串S中的一个连续的字符序列相等。

int Index(SString S, SString T, int pos){
    //返回子串T在主串S中第pos个字符之后的位置。若不存在,则返回0.
    //其中,T非空,1≤pos≤StrLength(S)
    i=pos; j=1;
    while(i<=S[0] && j<=T[0]){
        if(S[i]==T[j]) { ++i; ++j; } //继续比较后续字符
        else { i=i-j+2; j=1; }       //指针后退,重新开始匹配
    }
    if(j>T[0]) return i-T[0];
    else return 0;
}

KMP算法

KMP算法是在BF算法基础上的一种改进算法,其改进在于:每当一趟匹配过程中出现字符比较不相等时,不需要回退目标串指针,而是利用已经得到的“部分匹配”的结果,计算出模式串指针最少可以回退到模式串中的哪个位置重新开始匹配。

理论推导(计算回退的位置)

假设目标串S\large s_{1}s_{2}...s_{n},指针为i(1 \leqslant i \leqslant n),模式串Tp_{1}p_{2}...p_{m},指针为j(1 \leqslant j \leqslant m),此时匹配进行到比较\large s_{i}p_{j},发现二者不相等(“失配”),如下图1所示。

(图1)

下一时刻,根据KMP算法,假设j回退到模式串中的k(k < j)位置,i不回退,匹配从\large s_{i}p_{k}比较重新开始。那么,应该要满足模式串的前k-1个字符与目标串中i之前的k-1个字符依次相等,即:

①  \large s_{i-k+1}s_{i-k+2}...s_{i-1}=p_{1}p_{2}...p_{k-1},如下图所示。

(图2)

而在回退之前,已经得到的“部分匹配”的结果是,模式串的前j-1个字符与目标串中i之前的j-1个字符依次相等,即:

②  \large s_{i-j+1}s_{i-j+2}...s_{i-1}=p_{1}p_{2}...p_{j-1},参见图1。

根据上式,取出模式串中j之前的k-1个字符,也有下式成立:

③  \large s_{i-k+1}s_{i-k+2}...s_{i-1}=p_{j-k+1}p_{j-k+2}...p_{j-1}

根据式①和式③,得到以下等式:

④  \large p_{1}p_{2}...p_{k-1}=p_{j-k+1}p_{j-k+2}...p_{j-1}

可以看出,k值仅依赖于模式串,由此,我们便得到了k的计算公式。

 令next[j]=k,它表示当模式串中第j个字符与目标串中相应字符“失配”时,在模式串中需要重新和目标串中该字符进行比较的字符的位置k,也即j指针回退到位置k。定义如下:

next[j]=\left\{\begin{matrix} 0,\ when\ j=1\\ max\{​{k|1<k<j\ and\ p_{1}...p_{k-1}=p_{j-k+1}...p_{j-1}\}},\ when\ k\ is\ not\ null\\ 1\ ,\ others \end{matrix}\right.

通过上述定义,可以得到k的值。通俗地解释一下:

首先,初始化next[1]=0。其次对于j>1next[j]的值等于,模式串子串p_{1}p_{2}...p_{j-1}最长相等真前缀与真后缀的字符个数加1。举个例子看一下:

在求得模式串的next函数之后,匹配可按如下进行:假设以指针ij分别指示主串和模式中正待比较的字符,令i的初值为pos,j的初值为1。若在匹配过程中,s_{i}=p_{j},则ij分别增1,否则,i不变,而j退到next[j]的位置重新开始比较,若相等,则指针各自增1,否则,j再退到下一个next值的位置,依次类推,直至下列两种可能:一种是j退到某个next值时字符比较相等,另一种是j退到值为0。在这两种情况下,两个指针都要各自增1。下面给出KMP匹配算法。

int Index_KMP(SString S, SString T, int pos){
    //利用模式串T的next函数求T在目标串S中第pos个字符之后的位置的KMP算法。
    //其中,T非空,1≤pos≤StrLength(S)
    i=pos; j=1;
    while(i<=S[0] && j<=T[0]){
        if(j==0 || S[i]==T[j]) { ++i; ++j; }   //继续比较后继字符
        else j=next[j];                        //模式串指针后退
    }
    if(j>T[0]) return i-T[0];                  //匹配成功
    else return 0;
}

上面介绍了求next函数的简单方法,但计算机执行起来效率较低,下面给出更为高效的算法。

对于next[j]=k,存在等式④\large p_{1}p_{2}...p_{k-1}=p_{j-k+1}p_{j-k+2}...p_{j-1}成立,且不存在更大的k值使其成立。我们考虑next[j+1],如果有p_{k}=p_{j},那么自然有next[j+1]=k+1。如果p_{k}\neq p_{j},我们将\large p_{1}p_{2}...p_{k-1}p_{k}看成是模式串\large p_{j-k+1}p_{j-k+2}...p_{j-1}p_{j}看成是目标串,由于有④式成立,且当前p_{k}\neq p_{j},那么需要回退模式串指针到next[k],匹配从p_{next[k]}p_{j}比较重新开始,如果二者相等,那么说明在该模式串中有p_{1}...p_{next[k]}=p_{j-next[k]+1}...p_{j},且不存在大于next[k]的值满足该式,所以根据next函数的定义有next[j+1]=next[k]+1;如果二者不相等,那么依次类推,直至下列两种情况,一是p_{j}和模式串中某个字符匹配成功,此时next[j+1]=next[next[...next[k]...]]+1,二是模式串中不存在这样的字符,此时next[j+1]=1

算法描述如下:

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

上述求next的算法仍有改进的空间,在比较\large s_{i}p_{j}出现不等时,即s_{i}\neq p_{j},需要回退模式串指针到next[j]=k,而如果p_{j}=p_{k},那么自然有s_{i}\neq p_{k},无需再次比较。所以不如在计算next值时,就避免这样的情况出现,由j直接回退到p_{j}\neq p_{k'}的位置k',那么改进后的get\_next算法如下:

void get_next(SString T, int next[]){
    //求模式串T的next函数修正值并存入数组next
    i=1; next[1]=0; j=0;
    while(i<T[0]){
        if(j==0 || T[i]==T[j]){
            ++i; ++j;
            if(T[i]!=T[j]) next[i]=j;
            else next[i]=next[j];
        }
        else j=next[j];
    }
}

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

wingrez

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值