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

一、引入


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

常用的串匹配算法有简单的模式匹配算法(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    //匹配失败
        }
}

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

相关推荐
电子_咸鱼2 分钟前
LeetCode——Hot 100【电话号码的字母组合】
数据结构·算法·leetcode·链表·职场和发展·贪心算法·深度优先
仰泳的熊猫3 分钟前
LeetCode:785. 判断二分图
数据结构·c++·算法·leetcode
rit84324998 分钟前
基于MATLAB实现基于距离的离群点检测算法
人工智能·算法·matlab
my rainy days2 小时前
C++:友元
开发语言·c++·算法
haoly19892 小时前
数据结构和算法篇-归并排序的两个视角-迭代和递归
数据结构·算法·归并排序
微笑尅乐2 小时前
中点为根——力扣108.讲有序数组转换为二叉搜索树
算法·leetcode·职场和发展
im_AMBER3 小时前
算法笔记 05
笔记·算法·哈希算法
夏鹏今天学习了吗3 小时前
【LeetCode热题100(46/100)】从前序与中序遍历序列构造二叉树
算法·leetcode·职场和发展
吃着火锅x唱着歌3 小时前
LeetCode 2389.和有限的最长子序列
算法·leetcode·职场和发展
嶔某3 小时前
二叉树的前中后序遍历(迭代)
算法