数据结构.字符串.模式匹配算法

  • 也就是查找子串的算法
  • 比如在akchsduiyfuwasjjvafnameyihujrlbkjee查找name,明显就是应该交给计算机干的活

BF算法

  • Brute Force 暴力穷举。计算机的专属工作,也可以交给闪电侠
  • 也就是从目标字符串S的第一个字符开始与模式字符串T进行一一比较,若出现不想等的字符则从S的第二个字符开始再与T进行一一比较,直到找到匹配的子串or找不到匹配的子串
int BF(char *s,char *t)
{//BF算法,返回第一个子串的索引,未找到则返回-1
    int sl=0,tl=0;
    for(;s[sl];++sl);//目标字符串长度sl
    for(;t[tl];++tl);//模式字符串长度tl
                     //感觉其实长度还是由程序员传入会更保险

    int index = 0;
    for( ; index <= sl-tl ; ++index)
    {//brute force
        int si = index;//目标穿匹配起点
        int ti = 0;    //模式串匹配起点
        for(;ti<tl && s[si]==t[ti];++si,++ti);
        if(ti == tl) return index;//模式串全部匹配成功
    }
    return -1;//匹配失败
}
  • 时间复杂度O(n*m),n目标串长度,m模式串长度

KMP算法

  • 穷举显得很笨拙
  • 就这一点就值得开发更好的算法了

算法思想

  • 发现问题:当从匹配起点(index)匹配到某处(si)发现不相等时,BF是将起点移动到了(index + 1)继续从头开始匹配。这有点不对劲。
  • KMP的想法是,我的目标串的扫描已经从index到了si,并不想回到index+1。那么Knuth、Morris和Pratt同时在研究这个问题并同时发现了KMP算法。于是叫KMP算法了。。。
  • 请允许我介绍我自己常用的一个表示方法:字符串str,以str[a,b]表示从str[a]开始到str[b]结束的子串。
  • 研究以index为起点匹配到s[si]!=t[ti]时,如果继续以(index,si)之间的某点为起点来从头开始匹配,其实也就是企图找到这样一个点temp,满足index < temp < si且串s[temp,si-1]==t[0,si-temp-1]。
  • 而由于从index为起点一直匹配直到si才不相等,也就意味着s[index,si-1]==t[0,ti-1],也就有s[temp,si-1]==t[temp-index,ti-1]。再加上上文的条件,也就有t[0,si-temp-1]==t[temp-index,ti-1](条件1)。
  • 也就是说模式串应当满足(条件1)才值得去寻找这样一个点temp。如果满足条件1,那么应当继续从si=si和ti=si-temp处继续匹配即可。此时并不需要从目标串重新规定起点,而是仍然从不匹配的si处继续开始匹配,不需要回滚,意味着目标串只需要扫描一边。匹配成功后的返回可以减去模式串的长度得到起点。
  • (条件1)可以化简成比较友好的形式
    • t[0,si-temp-1]==t[temp-index,ti-1] (1)由条件1得到
    • 设next==si-temp-1 (2)设
    • si-index==(si-temp)+(temp-index)==ti (3)由原意得到
    • (si-temp)==next+1 (4)由(2)得到
    • (temp-index)==ti-(si-temp) (5)有(3)得到
    • (temp-index)==ti-next-1 (6)将(4)代入(5)中得到
    • t[0,next]==t[ti-next-1,ti-1] (7)将(2)(6)代入(1)中得到
  • 如果满足了(条件1),可见这是个只依赖于模式串的存在,对于通常是短短的模式串来说,完全值得这个开销去得到这个“跳跃值”。之所以为跳跃,是因为当s[si]!=t[ti]的时候,只需要把ti移动到(0,ti)之间的某个值next,使得t[0,next]==t[ti-next-1,ti-1]即可。
  • 于是设立一个next数组,用来指示ti的跳跃点

算法代码

int KMP(char *s,char *t)
{
    //同上获得sl和tl,不在写出

    int *next = new int[tl];//跳跃数组
    int i=0,j=-1;
    next[0]=-1;//意味着s[si]!=t[0]时ti设为-1
    while(i<tl)
    {
        if(j==-1||t[i]==t[j])
        {//t[i]==t[j]意味着i的下一位i+1应该跳到j+1处
            ++j;
            ++i;
            next[i]=j;
        }
        else
        {
            j=next[j];
        }
    }//获取next数组

    int si=0,ti=0;
    while(si<sl && ti<tl)
    {
        if(ti==-1 || s[si]==t[ti])
        {//若s[si]==t[ti]则si和ti都应该向下移动一位
         //若ti==-1,代表s[si]!=t[0],此时由于ti被设为了-1
         //仍然可以++si和++ti(ti变成了0)
            ++ti;
            ++si;
        }
        else
        {//跳跃
            ti=next[ti];
        }
    }
    if(ti==tl) return si-tl;
    else return -1;
}

啊啊啊等我再画图

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值