字符串模式匹配(一)——单模匹配(KMP)

字符串模式匹配(一)——单模匹配(KMP)

单模匹配的常用算法为KMP,多模匹配常用算法为AC自动机。

暴力匹配法(BF, O ( ∣ S ∣ × ∣ T ∣ ) O(|S|\times |T|) O(S×T))

设源串 S S S的匹配指针为 i i i,模式串 T T T的匹配指针为 j j j。暴力法通过逐个比较 S S S T T T中的每个字符进行模式匹配。

算法流程:循环执行下列步骤,直到 i i i越界,匹配结束:

  • S [ i ] = T [ j ] S[i]=T[j] S[i]=T[j],该点成功匹配,继续比较。若 j j j越界则为成功匹配,j=0进行下次匹配。
  • S [ i ] ≠ T [ j ] S[i]\ne T[j] S[i]=T[j],则为二者的失配点,回溯二者的匹配指针。二者在失配前均为成功匹配,匹配指针一直同步变化, j j j为相对于匹配开始点的增量,因此本轮匹配起点即为 i − j i-j ij i i i回溯到起点的下一个位置i=i-j+1j=0
string s,t;
vector<int>ans;//记录所有成功匹配开始点下标
void bf(){
    int i=0,j=0;
    while(i<s.size()){
        if(t[j]==s[i]){
            i++,j++;
            if(j>=t.size()) ans.push_back(i-t.size()),j=0;//j越界则产生答案
        }else i=i-j+1,j=0;//匹配失败,回溯
    }
}

KMP( O ( ∣ S ∣ + ∣ T ∣ ) O(|S|+|T|) O(S+T))

KMP是在BF上进行改进的单模匹配算法,其主要改进是最长公共前后缀数组,以避免大量的非必要回溯。

大量回溯的不必要性

设源串 S S S的匹配指针为 i i i,模式串 T T T的匹配指针为 j j j。设当前轮匹配的失配点前, S S S中成功匹配部分为其子串 S ′ S' S T T T中成功匹配部分为其前缀 T ′ T' T S ′ S' S T ′ T' T完全相同。

  • T ′ T' T中每个字符都不同:失配时暴力法的回溯为i=i-j+1j=0,但 i i i的回溯是完全不必要的。由于 T ′ T' T中每个字符都不同,因此S[i-j+1]也不可能与T[0]相同。 i i i不动,j=0即可。
  • T ′ T' T中有部分字符相同,且相同部分为 T ′ T' T的长度为 L L L的前缀和后缀: S ′ S' S也具有等长的相同前后缀,因此 T ′ T' T前缀与 S ′ S' S后缀已经匹配,这部分无需重复匹配。失配时 i i i不动, j j j移动到 T ′ T' T前缀的下个位置L继续匹配即可。
  • T ′ T' T中部分字符相同,且相同部分并非 T ′ T' T的前缀和后缀:此种情况等价于 T ′ T' T中每个字符都不同。 i i i不动,j=0即可。
    在这里插入图片描述
    由此可得, i i i完全没有必要回溯,只对 j j j回溯即可

最长公共前后缀数组

定义数组 N e x t [ j ] Next[j] Next[j],设模式串 T T T的前 j − 1 j-1 j1个字符构成其前缀 T ′ T' T,则 N e x t [ j ] Next[j] Next[j]存储 T ′ T' T的前缀与后缀的最长交集长度(注意此处均指不包含 T ′ T' T自身的真前后缀),则 T ′ T' T的后缀中最后下标为 j − 1 j-1 j1 T ′ T' T前缀中最后下标为 N e x t [ j ] − 1 Next[j]-1 Next[j]1(公共长度-1,注意字符串下标从 0 0 0开始),代表当失配时, j j j所回溯的位置

  • T [ i ] = T [ j ] T[i]=T[j] T[i]=T[j],则 [ 0 , i ] [0,i] [0,i]的长度转移 [ 0 , i − 1 ] [0,i-1] [0,i1]的长度并 + 1 +1 +1 N e x t [ i + 1 ] = N e x t [ i ] + 1 Next[i+1]=Next[i]+1 Next[i+1]=Next[i]+1
  • T [ i ] ≠ T [ j ] T[i]\ne T[j] T[i]=T[j],则前缀与后缀失配,属于回溯分析中的情况3,后缀失去后缀特性,无法在KMP中用于直接跳跃, j j j回溯。 N e x t [ i + 1 ] = 0 , j = N e x t [ j ] Next[i+1]=0,j=Next[j] Next[i+1]=0,j=Next[j]

获取最长公共前后缀数组的本质:模式串本身在进行自我模式匹配

string s,t;//下标从0开始
vector<int>Next,ans;//注意next是cpp的关键字
void getNext(){//本质:模式串在进行自我模式匹配
    Next.resize(t.size()+1);
    Next[0]=0,Next[1]=0;//Next[0]不用;由于在找真前后缀的最长交集长度,因此Next[1]为0
    for(int i=1;i<t.size();i++){
        int j=Next[i];
        while(j&&t[i]!=t[j]) j=Next[j];//j在失配点跳跃到Next[j]
        if(t[i]==t[j]) Next[i+1]=j+1;//[0,i]的答案转移自[0,i-1]+1
        else Next[i+1]=0;//后缀失去后缀特性
    }
}
void kmp(){
    getNext();
    int j=0;
    for(int i=0;i<s.size();i++){
        while(j&&s[i]!=t[j]) j=Next[j];//j在失配点跳跃到Next[j]
        if(s[i]==t[j]) j++;
        if(j==t.size()) ans.push_back(i+1-t.size());//j越界,则匹配成功
    }
}
posted @   椰萝Yerosius  阅读(2)  评论(0编辑  收藏  举报  
相关博文:
阅读排行:
· winform 绘制太阳,地球,月球 运作规律
· TypeScript + Deepseek 打造卜卦网站:技术与玄学的结合
· AI 智能体引爆开源社区「GitHub 热点速览」
· Manus的开源复刻OpenManus初探
· 写一个简单的SQL生成工具
点击右上角即可分享
微信分享提示