算法篇——串的模式匹配算法

一、引入


子串的定位运算通常称为串的模式匹配或者串匹配。此运算相当广泛,比如在搜索引擎、拼写检查、语言翻译、数字压缩等应用中都需要用到串匹配。

常用的串匹配算法有简单的模式匹配算法(BF)和KMP算法。

二、简单的模式匹配算法(BF)


简单的模式匹配算法可以理解为比对算法,是指有一个串S和串T,如果我们需要判断串T是不是串S的子串我们就需要用串T和串S一一比对。具体实现流程如下图表示:

由上图我们可以发现,我们分别用两个指针i、j分别指示S、T中需要比较多的字符位置i的初值为pos,j的初值为1。然后我们发现,如果两个串均未比较到串尾,即i、j均分别小于等于S和T的长度时,则循环执行下面操作:

  • S.ch[i]和T.ch[j]比较,若相等,则i和j分别指示串中的下一个位置,继续比较后续字符;
  • 如果不等,指针后退重新开始匹配,从主串的下一个字符(i=i-j+2)起在再重新和模式的第一个字符开始比较。

如此重复,如果j>T.length,说明模式T中的每个字符依次和主串S中的一个连续字符字符序列相等,那么就匹配成功了,返回和T中的第一个字符相等的字符在S中的序号(i-T.length);反之返回0;

代码描述:

cpp 复制代码
int Index_BF(SString S,SString T,int pos){    //返回T在S中第pos个字符开始第一次出现的位置,若不存在,返回0;
//其中T非空,1<=pos<=S.length
    int 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
        }
}

BF算法的思路直观简单,但是当匹配失败时指针总是回溯。这就导致其时间复杂度较高。下面介绍另一种改进算法------KMP算法。

三、KMP算法


先在此处简单介绍下KMP算法。KMP算法与BF的算法不同,在BF算法中当遇到不匹配的值时就会指针回溯。所以当出现最后一个字符不匹配时,首字符就将回溯很多次。这样是很没用必要的。这时候就需要使用KMP算法了。使用它时指针是不回溯的。而他具体是怎么运行的呢?请看图示

这时候我们就将用到以下几个概念了:前缀后缀部分匹配值(PM)。

**前缀:**除去最后一个字符以外的所有头部子串组合,例如"abab"的前缀为{a,ab,aba}

**后缀:**除去的第一个字符以外的所有尾部字符组合,例如"abab"的后缀为{b,ab,bab}

**部分匹配值:**字符串的前后缀元素的交集,例如"abab"的部分匹配值为{ab}。PM=1

这样,我们就得到了一个关于字符串的PM表,如下:

回顾上面的KPM算法的模拟图我们发现子串需要向后移动的位数满足下面这样一个公式:

移动位数=已匹配的字符数−对应最大公共前后缀长度

通过仔细阅读发现使用部分匹配值时,每当匹配失败,就去找它前一个元素的部分匹配值,但是用代码表述起来就显得不太方便。所以,当我们将整个PM表的PM整体右移再+1就能得到一个新的PM数组------next[j]。

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

由此定义可以推断出模式串"abaabcac"的next函数值如下图所示:

综上,我们需要先求取next的函数值才能灵活运用KMP算法来进行串的模式匹配。故在使用KMP算法的第一步就是需要先计算next的函数值。

代码描述:

cpp 复制代码
void get_next(SString T,int next[]){
    //求模式串T中的next函数值并存入数组netx
    int i=1,j=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[i]}
        }
}

当我们完成了next的计算之后,紧接着我们就可以来使用KMP算法了。

代码描述:

cpp 复制代码
int Index_KMP(SString S,SStringT,int pos){
    //利用T的next函数求T在主串S中第pos个字符之后的位置
    //
    int 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    //匹配失败
        }
}

至此。关于串的模式匹配算法的基本学习我们就已经完成的差不多了。 如果我的内容对你有帮助,在下就厚着脸皮讨个点赞关注。如果你有更好的想法,还望留在评论区让我来参考学习。我将不胜感激并努力创作出更好的内容。

相关推荐
闪电麦坤958 分钟前
数据结构:递归的种类(Types of Recursion)
数据结构·算法
Gyoku Mint1 小时前
机器学习×第二卷:概念下篇——她不再只是模仿,而是开始决定怎么靠近你
人工智能·python·算法·机器学习·pandas·ai编程·matplotlib
纪元A梦1 小时前
分布式拜占庭容错算法——PBFT算法深度解析
java·分布式·算法
px不是xp1 小时前
山东大学算法设计与分析复习笔记
笔记·算法·贪心算法·动态规划·图搜索算法
枫景Maple2 小时前
LeetCode 2297. 跳跃游戏 VIII(中等)
算法·leetcode
鑫鑫向栄2 小时前
[蓝桥杯]修改数组
数据结构·c++·算法·蓝桥杯·动态规划
鑫鑫向栄2 小时前
[蓝桥杯]带分数
数据结构·c++·算法·职场和发展·蓝桥杯
小wanga3 小时前
【递归、搜索与回溯】专题三 穷举vs暴搜vs回溯vs剪枝
c++·算法·机器学习·剪枝
天宫风子3 小时前
线性代数小述(一)
线性代数·算法·矩阵·抽象代数