KMP算法学习笔记(java实现)

KMP算法学习笔记

首先是一个字符串匹配问题

两个字符串,S P,求P是否为S的子串。S的长度为m,P的长度为n。也就是求一个L,S [ L , L+n ) = P。

示例:

T = [ a b a a c a b a b c a c]

P = [ a b a b c ]

  • 暴力匹配

    设两个指针,i指向S的开始,j指向P的开始。

    如果 S [ i ] 和 P [ j ] 相同,则 i 和 j 同时向后移位(i++、j++)。判断是否相同,如果全部相同,匹配成功,输出结果。

    如果不同,则 j 恢复到 0,i 加一,重复以上操作。

    public static int indexOf(String S, String P){
      int i = 0;//指向S 匹配起点 不要动
      int j = 0;//指向P
      int sc = i;//临时扫描工具人
      while(sc < s.length()){
        if(s.charAt(sc) == p.charAt(j)){//字符相同
          sc++;
          j++;
          if(j == p.length()){//P全部匹配成功
            return i;
          }else{//匹配失败
            i++;
            sc = i;//扫描指针以i为起点
            j = 0;//j恢复至起点
          }
        }
      }
      return -1;
    }
    
      缺点:速度慢
    
  • KMP算法

    前缀表 (prefix table)

    写出P数组的前缀表 P = [ a b a b c ]
    a
    a b
    a b a
    a b a b
    (不包括最后一个c)
    把每一个前缀当作一个独立的字符串,然后依次求出最长公共前后缀列表。
    求最长公共前后缀列表方法:
    以 a b a b 为例,当前缀后缀长度为3时,前缀为a b a,后缀为 b a b。
    两者不同,当长度为2时,前缀为 a b,后缀为 a b。两者相同,于是最长公共前后缀为2。
    同理,依次求出,分别为 [ 0, 0, 1, 2 ]


    next数组

    最后在最前方加 -1, [ -1, 0, 0, 1, 2 ]为前缀表。长度与P长度相同,并一一对应。

    Pababc
    next-10012

    next数组失配位置回退 j 的位置,是最长公共前后缀的长度。即最长公共前后缀列表的值。

    f(L) = k 指当 L 失配,则 S 回溯到位置 K。K为P [ 0 ~ L-1] 的最长匹配数。

    当字符串匹配时,i++,j++,如果不匹配,i不变,j 回溯,直到next[ j ] == -1,证明第一个也无法匹配,则i++。

    这个方法的好处是,i永不回溯,j根据next数组回溯。

    回溯之后,从回溯的位置开始比较,前面不比较。

    private static int[] getNext(String p) {
      int len = p.length();
      int[] next = new int[len];
      next[0] = -1;
      int i = 0, k = -1;
    	while (i < len - 1) {
        if (k == -1 || p.charAt(i) == p.charAt(k)) 
          ++k;
          ++i;
          next[i] = k;
    	  } else {
          k = next[k];
        }
      }
      return next;
    }
    
  • 例题及完整代码

    给出一个母串和一个子串,然后输出子串第一次出现在母串中的位置下标。

    输入:

    abaacababcac

    ababc

    输出:5

    public class KMP {
    
    	public static void main(String[] args) {
    		// TODO Auto-generated method stub
    		String T = "abaacababcac";
    		String P = "ababc";
    		int[] next = getNext(P);
    		/*//测试next数组
    		 * for(int i:next) { System.out.println(i); }
    		 */
    		System.out.println(kmp(T,P,next));
    		
    	}
    	
    	private static int kmp(String T,String P,int[] next) {
    		int i = 0,j = 0;
    		while(i < T.length() && j < P.length()) {
    			if(T.charAt(i) == P.charAt(j)) {
    				i++;
    				j++;
    			}else {
    				if(next[j] == -1) {
    					i++;
    				}else {
    					j = next[j];
    				}
    			}
    		}
    		if(j == P.length()) {
    			return i - j; //题目要求输出位置下标,不用+1
    		}
    		return -1;
    	}
    	
    	//求next数组
    	private static int[] getNext(String p) {
    	    int len = p.length();
    	    int[] next = new int[len];
    	    next[0] = -1;
    	    int i = 0, k = -1;
    	    while (i < len - 1) {
    	        
    	        if (k == -1 || p.charAt(i) == p.charAt(k)) {
    	            ++k;
    	            ++i;
    	            next[i] = k;
    	        } else {
    	            k = next[k];
    	        }
    	    }
    	    return next;
    	}
    	
    	
    }
    
    
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值