zoukankan      html  css  js  c++  java
  • KMP算法详解

    问题描述

      KMP是解决子串的定位操作的一种算法,即在一个字符串中找到另一个字符串出现的位置,如果找不到就返回-1.我们使用的例子如下:主串为ababcabcacbab, 子串为abcac。

    符号标记

    符号 | 描述 -- | -- S | 主串 T | 子串 i | 主串的下标 j | 子串的下标

    传统做法

      传统做法同时遍历子串T和主串S,记i,j分被为主串和子串的下标,如果S[i]==S[j],则同时执行i++和j++;如果S[i]!=S[j]则将原先主串的开始遍历处的下标+1,重新开始比较。如下图:

    当S[2]!=T[2]时,我们将T向右移动一个单位并重复上述操作。

    这样不断重复直到子串全部匹配完毕返回子串位置,或者遍历超过了主串的长度,返回-1。

    int indexOf_1(string S,string T){
    int i=0;
    int j=0;
    while (i<S.size()&&j<T.size()){
    if(S[i]==T[j]) {i++;j++;}
    else {i=i-j+1;j=0;}
    }
    if(j>=T.size()) return i-j;
    else -1;
    }
    
    int main(){
    string S = "ababcabcacbab";
    string T = "abcac";
    cout<<indexOf_1(S,T)<<endl;
    system("pause");
    }
    

    传统方法的弊端

      拿我们的例子来说,假如传统方法遍历时,出现了以下一幕:

    我们比较了子串的a b c a 前五个字符但是但比较第5个字符时出现了不相等的情况,于是我们将子串向右移动一位再进行比较。这样不断重复直到:

    现在我们可以思考一个问题,当我们遇到第一幅图的情况时,其实我们是已经知道移动子串两次的开始字符分别为b和c,这个信息就存储子串当中,因为我们经过第一幅图的比较就已经确定了子串的bc与主串的bc相匹配。那么我们怎么样利用上这个信息,使得子串的移动不是1个单位1个单位的移,而是根据上一次匹配得到信息,直接跳过某些字符的匹配。下面我们就来探讨下两个问题。

    问题一:子串滑动多大距离

      当我们比较主串和子串的某个字符时,如果两个字符不相等我们应该将子串向右滑动几个单位。即若S[i]!=T[j],我们滑动子串使得S[0:i]=T[0:k],我们现在来确定k的大小。通过上一次不成功的比较,我们得到了如下结论S[i]!=T[j],因为子串已经比较到j了所以j前面的已经全都匹配上了。即:`S[i-j:i-1]==T[1:j-1]`。但我们滑动子串使得i与k相比较时,我们必须保证`S[i-k:i-1]==T[1:k-1]`.我们用下图来表示这三者之间的关系。

    在j和k左边上下都是相互匹配的,通过图我们可以很清楚的看到,移动后的子串与原先的子串在绿色部分也是相互匹配的。即:T[1:k-1]=T[j-k+1:j-1]
      这样我们就可以通过一个next数组保存子串的匹配信息。next[j]=k表示当子串的T[j]与主串S[i]不匹配时,向右移动多个单位使T[k]与S[i]进行匹配。如果T[k]!=S[i],则继续使得T[next[k]]与S[i]相比较。
    next的计算公式如下:

    注:这里公式中的-1指的是当子串与主串在第一个字符处就不相等时,子串向右移动一个单位,即相当于原先子串-1处的字符与主串i处字符比较。实际上-1处是无效的。这里只是做一个标记,当遇到next[j]=-1时,子串滑动一个单位。

    问题二:如何求子串的next值

      通过问题一中的分析我们可以知道子串中的next的值只与子串本身有关即`T[1:k-1]=T[j-k+1:j-1]`中只包含T的字符。现在我们可以确定的是next(0)=-1. 我们可以据此来完成递推。即已知next[j]=k,求next[j+1]。 分两种情况讨论。 * 当T[k]=T[j],这时next[j+1]=k+1=next[j]+1,画图表示如下。

    其中蓝色部分表示T[1:k-1]=T[j-k+1:j-1]这是通过next[j]=k得出的。(这里需要注意的是需要理解next[j]=k只表示j(或者k)之前的一段字符相等,并不能得出k和j两处的关系,具体的公式得出可以看看问题一中的推导)。
    这时若T[k]=T[j],图中的蓝色部分向右扩展一个单位得出T[1:k]=T[j-k+1:j]:

    于是有next[j+1]=next[j]+1

    • 当T[k]!=T[j],next[j+1]=next[k]+1,k=next[k](重复使得T[next[k]]T[j])。这里也很好理解既然T[k]!=T[j],那我们就可以再从前面找到一个值k',使得T[k']T[j],k'的前面某一段必须与j前面相同的一段相同,同样也与k前面某一段相同,根据k,k'前拥有相同的一段就可以根据next[k]求出k'。

    上面的两种情况我们也可以将其看作是子串和主串的匹配问题。只不过子串和主串都是T的某一段。

    代码部分

    //求next数组
    void getNext(string T,int next[],int len){
    next[0]=-1;
    int k=-1;
    int j=0;
    while(j<len){
    if(k==-1||T[j]==T[k]){ j++;k++;next[j]=k; }
    else{
      k = next[k];
    }
    }
    }
    //匹配子串
    int kmp(string S,string T){
    int len = T.size();
    int* next = (int*)malloc(sizeof(int)*len);
    getNext(T,next,len);
    int i=0;
    int j=0;
    while (i<S.size()&&j<T.size()){
    if(j==-1||S[i]==T[j]) {i++;j++;}
    else {j=next[j];}
    }
    int size = T.size();
    if(j>=size) return i-j;
    else return -1;
    }
    
    int main(){
    string S = "ababaababcacbcacbab";
    string T = "ababcac";
    int index2 = kmp(S,T);
    cout<<index2<<endl;
    system("pause");
    }
    

    小结

      传统的算法的最坏情况下时间复杂度是O(m*n),当一般这种情况不多见。KMP算法的时间复杂度是O(m+n)。
  • 相关阅读:
    在windows下如何批量转换pvr,ccz为png或jpg
    cocos2d-x 中的 CC_SYNTHESIZE 自动生成 get 和 set 方法
    制作《Stick Hero》游戏相关代码
    触摸事件的setSwallowTouches()方法
    随机生成数(C++,rand()函数)
    随机生成数
    cocos2d-x 设置屏幕方向 横屏 || 竖屏
    Joystick 摇杆控件
    兔斯基 经典语录
    Cocos2d-x 3.2 EventDispatcher事件分发机制
  • 原文地址:https://www.cnblogs.com/Mrfanl/p/10793226.html
Copyright © 2011-2022 走看看