【C++】KMP浅析及其代码

来源:互联网 发布:曼彻斯特大学 知乎 编辑:程序博客网 时间:2024/05/01 07:25

先抛出要求解的问题:已知一个文本串
“BBC ABCDAB ABCDABCDABDE”和模式串“ABCDABD”,现在求解模式串出现在文本串中的位置。

最直观的就是使用暴力的方式求解。假设现在文本串S匹配到 i 位置,模式串P匹配到 j 位置,则有:

如果当前字符匹配成功(即S[i] == P[j]),则i++,j++,继续匹配下一个字符;如果失配(即S[i]! = P[j]),令i = i - (j - 1),j = 0。相当于每次匹配失败时,i 回溯,j 被置为0。
这里写图片描述

不匹配时S串,自动往右走。

这里写图片描述

当遇到匹配的时候,S串和P串都往右走,i++,j++。

这里写图片描述

S串和P串一直往右走,直到遇到不匹配的:

这里写图片描述

那么S串往回退,P串直接退到0处,i = i - (j - 1),j = 0:

这里写图片描述

暴力查找的代码如下:

int MatchSub(char *s,char *p){    int sLen=strlen(s);    int pLen=strlen(p);    int i=0,j=0;    while(i<sLen && j<pLen){        if(s[i]==p[j]){            ++i;            ++j;        }        else{            i=i-j+1;            j=0;        }    }    if(j==pLen){        return i-j;    }    else        return -1;}

这中暴力匹配的方法因为这其中存在着重复计算的原因所以其中存在着可以优化的部分。那么当不匹配(即S[i]! = P[j])时,能否可以使i和j不回退那么多呢?有,这就是大名鼎鼎的KMP算法解决的问题了。

KMP算法的主旨是利用之前已经部分匹配这个有效信息,保持i 不回溯,通过修改j 的位置,让模式串尽量地移动到有效的位置。

KMP算法对暴力匹配方法做了如下更改:
假设现在文本串S匹配到 i 位置,模式串P匹配到 j 位置 如果j = -1,或者当前字符匹配成功(即S[i] == P[j]),都令i++,j++,继续匹配下一个字符; 如果j != -1,且当前字符匹配失败(即S[i] != P[j]),则令 i 不变,j = next[j]。此举意味着失配时,模式串P相对于文本串S向右移动了j - next [j] 位。 换言之,当匹配失败时,模式串向右移动的位数为:失配字符所在位置 - 失配字符对应的next 值。
而next数组中代表当前字符之前的字符串中,有多大长度的相同前缀后缀。例如如果next [j] = k,代表j 之前的字符串中有最大长度为k的相同前缀后缀。
匹配如下所示:

这里写图片描述

与暴力匹配不同,此时i不变,j移动到next[j],如下所示:

这里写图片描述

从上面可以看出其实问题的关键是next数组中,那么问题来了:next数组是怎么来的呢?
next数组保存的是模式串当前字符前(不包含当前字符)值相等的最长前缀和后缀。若模式串当前下标为j,对于P = p0 p1 …pj-1 pj,寻找模式串P中长度最大且相等的前缀和后缀。如果存在p0 p1 …pk-1 = pj- k pj-k+1…pj-1,那么在包含pj的模式串中有最大长度为k的相同前缀后缀。

由于前缀和后缀是相同的因此当不匹配(即S[i] != P[j])的时候就可以将j回退到当前值的相同前缀的下一个值,即j = next[j](注意:如果j移动之后P[j]还是和当前的S[i]不匹配那么,j需要继续往前回归)。

上文中的模式串“ABCDABD”的next数组为:
这里写图片描述

下面给出完整的KMP代码。

首先是计算next数组:void getNext(char *p, int next[]){    int pLen = strlen(p);    next[0]=-1;    int k=-1,j=0;    while(j<pLen){        if(k==-1 || p[k]==p[j]){            ++k;            ++j;            next[j]=k;        }        else            k=next[k];    }   }然后是KMP算法:void KMP(char *s,char *p, int next[]){    int sLen = strlen(s);    int pLen = strlen(p);    int i=0,j=0;    while(i<sLen && j<pLen){        if(j==-1 || s[i]==p[j]){            ++i;            ++j;                   }        else{            j=next[j];        }    }    if(j==pLen)        return i-j;    else        return -1;}

后记:有一个很奇怪的现象,当KMP()代码中写成如下形式的时候:

int strStrKMP(string haystack, string needle, int next[]){    int i=0,j=0;    //int hLen = haystack.length(),nLen=needle.length();    while(i<haystack.length() && j<needle.length()){        if(j==-1 || haystack[i]==needle[j]){            ++i;            ++j;        }        else            j=next[j];        cout<<needle.length()<<endl;    }

的时候只能输出-1,不能输出其他的值,但是当把while的条件改成这样时:

int hLen = haystack.length(),nLen=needle.length();    while(i<hLen  && j<nLen){    ......    }

代码又能很好的运行了。是不是中遍历数组haystack的时候,再对它求长度的时候就会出错?!

原创粉丝点击
热门问题 老师的惩罚 人脸识别 我在镇武司摸鱼那些年 重生之率土为王 我在大康的咸鱼生活 盘龙之生命进化 天生仙种 凡人之先天五行 春回大明朝 姑娘不必设防,我是瞎子 手机一直扣费怎么办 手机卡自动扣费怎么办 淘宝退货运费险不赔怎么办 类目不能开直通车怎么办 淘宝虚拟恶意退款怎么办 淘宝虚拟单退款怎么办 虚拟物品被骗了怎么办 网络选修课挂了怎么办 美团商家退出怎么办 卖家版运费险太贵了怎么办 美瞳没有客源怎么办 购物车满了怎么办 手机程序无响应怎么办 三星手机无响应怎么办 游戏无响应了怎么办 手机百度无响应怎么办 新手机响应慢怎么办 vivo手机无响应怎么办 vivo软件无响应怎么办 退货商家不处理怎么办 淘宝页面变小了怎么办 淘宝卖家让微信交易被骗怎么办 苹果下载特别慢怎么办 淘宝没有支付宝怎么办 淘宝买东西限购怎么办 淘宝被别人登录怎么办 淘宝被厂家投诉怎么办 买家退货说是假货怎么办 同行给差评怎么办 被买家举报了怎么办 淘宝商品被屏蔽怎么办 电脑处于离线状态怎么办 计算机处于离线状态怎么办 交易猫安全提醒怎么办 网吧进游戏代码怎么办 车票冲突买不了怎么办 苹果8淘宝打不开怎么办 我的淘宝打不开怎么办 福袋不支持退货怎么办 不支持跨区下单怎么办 支付宝被占用怎么办