字符串子串匹配KMP算法

别来无恙 提交于 2020-03-07 20:04:38

子串匹配问题:返回一个子串在字符串中第一次出现的下标,若无则返回-1

KMP:

一个朴素的子串匹配如下:(以下使用TypeScript语言编写)

private subStringIndex(source:string,target:string):number{
      if(target.length>source.length||!source||!target){
          return -1;
      }
      let res=-1;
      for(let i=0;i<=source.length-target.length;i++){
          if(source[i]==target[0]){
             let temp=true;
             for(let j=0;j<target.length;j++){   //可以看到使用了两层的for循环,时间复杂度是0(n^2)
                 if(source[i+j]!=target[j]){
                     temp=false;
                     break;
                 }
             }
             if(temp){
                 res=i;
                 return res
             }
         }
      }
      return res;
}

在子串与母串不匹配时,母串的i会回退到子串第一个的位置,减少这个回退的数量就是KMP的目标

KMP:通过getNext获得当source[i+j]!=target[j]时i向前移动多少位置

next=当匹配到这个字符不一致时,子串上从开始到这个字符的串中前缀与后缀的最大公共部分+1,比如 母串是:abcdabae, 子串是aba ,子串会在index=2处不一致,前后缀最长公共部分是a,(前缀a、ab,后缀a、ba)next=2  ,母串的匹配再从跳到(a+2)c开始,而c!=a,在index=0处不一致子串的前后缀最长公共部分是空 next=1,母串只跳一步

比如:

模式串     a   b   a   a   b   c   a   c

next值     0   1   1   2  2   3  1   2

如何通过代码获得next数组: 

private getNext(s:string):number[]{
         let next=[];
         next[0]=-1;  //哨兵
         let i=0;
         let j=-1;
         while(i<s.length){
             if(j==-1||s[i]==s[j]){
                 i++;
                 j++;
                 next[i]=j;//如果一直相同比如aaaaa/abababa会生成[-1,0,1,2,3,...]

             }else{
                 j=next[j];//j在i之前,这里会将j回退直到j==-1或者匹配时
             }
         }
         return next;
     }

如果难理解也可以看以下另一种方法得到的next数组

public getNext(s:string):number[]){

        let next=[];

        next[0]=0;

        for(let i=1,j=0;i<s.length;i++{

                while(j>0&&s[j]!=s[i]){

                        j=next[j-1];

                        if(s[j]==s[i]){

                                j++;

                        }

                        next[i]=j;

                }

        }

}

private subStringIndexKMP(source:string,target:string):number{

         if(target.length>source.length||!source||!target){
             return -1;
         }
         let res=-1;
         let i=0;
         let next:number[]=this.getNext(source);
         while(i<=source.length-target.length){
             if(source[i]==target[0]){
                let temp=true;
                for(let j=0;j<target.length;j++){
                    if(source[i+j]!=target[j]){
                        temp=false;
                        i+=next[j+1];    //i向前移动j+1跳过哨兵
                        break;
                    }
                }
                if(temp){
                    res=i;
                    return res
                }
            }else{
                i++;
            }
         }
         return res;
     }

let s="abababca"
let yi="abc";
let y="bab";
console.log(this.subStringIndexKMP(s,yi));//输出4
console.log(+this.subStringIndexKMP(s,y));//输出1

 

 

 

 

 

标签
易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!