zoukankan      html  css  js  c++  java
  • 字符串匹配算法-KMP

      举例来说,有一个字符串"BBC ABCDAB ABCDABCDABDE",我想知道,里面是否包含另一个字符串"ABCDABD"?

      在上面这个例子中,字符串"BBC ABCDAB ABCDABCDABDE"称为主串,字符串"ABCDABD"称为模式串

      许多算法可以完成这个任务,Knuth-Morris-Pratt算法(简称KMP)是最常用的之一。下面,我用自己的语言,解释KMP算法。

    1、首先,主串"BBC ABCDAB ABCDABCDABDE"的第一个字符与模式串"ABCDABD"的第一个字符,进行比较。因为B与A不匹配,所以模式串后移一位

        image

    2、因为B与A不匹配,模式串再往后移

        image

    3、就这样,直到主串有一个字符,与模式串的第一个字符相同为止

        image

    4、接着比较主串和模式串的下一个字符,还是相同

        image

    5、直到主串有一个字符,与模式串对应的字符不相同为止

        image

    6、这时,最自然的反应是,将模式串整个后移一位,再从头逐个比较。这样做虽然可行,但是效率很差,因为你要把"搜索位置"移到已经比较过的位置,重比一遍

        image

    7、一个基本事实是,当空格与D不匹配时,你其实知道前面六个字符是"ABCDAB"。KMP算法的想法是,设法利用这个已知信息,不要把"搜索位置"移回已经比较过的位置,继续把它向后移,这样就提高了效率

    怎么做到这一点呢?可以针对模式串,算出一张《部分匹配表》(Partial Match Table)。这张表是如何产生的,后面再介绍,这里只要会用就可以了

        image

    8、已知空格与D不匹配时,前面六个字符"ABCDAB"是匹配的。查表可知,最后一个匹配字符B对应的"部分匹配值"为2,因此按照下面的公式算出向后移动的位数:

      移动位数 = 已匹配的字符数 - 对应的部分匹配值(最后一个匹配字符)

      因为 6 - 2 等于4,所以将模式串向后移动4位。

        image

    9、因为空格与C不匹配,模式串还要继续往后移。这时,已匹配的字符数为2("AB"),对应的"部分匹配值"为0。所以,移动位数 = 2 - 0,结果为 2,于是将模式串向后移2位。

        image

    10、因为空格与A不匹配,继续后移一位。

        image

    11、发现主串中的A与模式串中的A匹配,接着比较主串和模式串的下一个字符,还是相同,直到发现C与D不匹配。

        image

    12、因为C与D不匹配,所以移动位数= 6 - 2,继续将模式串向后移动4位

        image

    13、然后再逐位比较,直到模式串的最后一位,发现完全匹配,于是搜索完成。如果还要继续搜索(即找出全部匹配),移动位数 = 7 - 0,再将模式串向后移动7位,这里就不再重复了

        image

    14、下面介绍《部分匹配表》是如何产生的。

      首先,要了解两个概念:"前缀"和"后缀"。 "前缀"指除了最后一个字符以外,一个字符串的全部头部组合;"后缀"指除了第一个字符以外,一个字符串的全部尾部组合

        image

    15、"部分匹配值"就是"前缀"和"后缀"的最长的共有元素的长度。以"ABCDABD"为例

      -"A"的前缀和后缀都为空集,共有元素的长度为0;

      -"AB"的前缀为[A],后缀为[B],共有元素的长度为0;

      -"ABC"的前缀为[A, AB],后缀为[BC, C],共有元素的长度0;

      -"ABCD"的前缀为[A, AB, ABC],后缀为[BCD, CD, D],共有元素的长度为0;

      -"ABCDA"的前缀为[A, AB, ABC, ABCD],后缀为[BCDA, CDA, DA, A],共有元素为"A",长度为1;

      -"ABCDAB"的前缀为[A, AB, ABC, ABCD, ABCDA],后缀为[BCDAB, CDAB, DAB, AB, B],共有元素为"AB",长度为2;

      -"ABCDABD"的前缀为[A, AB, ABC, ABCD, ABCDA, ABCDAB],后缀为[BCDABD, CDABD, DABD, ABD, BD, D],共有元素的长度为0。

    Java实现代码

    package com.buaa;
    
    import java.util.Arrays;
    import java.util.Random;
    
    /** 
    * @ProjectName PatternMatchAlgorithm
    * @PackageName com.buaa
    * @ClassName KMP
    * @Description TODO
    * @Author 刘吉超
    * @Date 2016-05-08 09:41:01
    */
    public class KMP {
        /**
         * KMP匹配字符串 
         * 
         * @param originString 主串
         * @param moduleString 模式串
         * @param partialmatchArray 部分匹配表
         * @return 若匹配成功,返回下标,否则返回-1 
         */
        public static int match(String originString,String moduleString,int[] partialmatchArray) {
        	// 主串
        	if (originString == null || originString.length() <= 0) {
        		return -1;
        	}
        	// 模式串
            if (moduleString == null || moduleString.length() <= 0) {
                return -1;
            }
            // 如果模式串的长度大于主串的长度,那么一定不匹配
            if (originString.length() < moduleString.length()) {
                return -1;
            }
            
            int origin_index = 0;
            int module_index = 0;
            boolean startMatch = false;
            
            for(;origin_index < originString.length() 
            		&& module_index < moduleString.length();
            		origin_index++){
            	// 主串中字符
            	char char_origin = originString.charAt(origin_index);
            	// 模式串中的字符
            	char char_module = moduleString.charAt(module_index);
            	
            	if(char_origin == char_module){
            		startMatch = true;
            		module_index++;
            	}else if(startMatch){
            		// 向前移动"已匹配的字符数 - 最后一个匹配字符对应的部分匹配值",换句话说,module_index的值就是最后一个匹配字符对应的部分匹配值
            		module_index = partialmatchArray[module_index];
            		startMatch = false;
        			origin_index--;
            	}
            }
            
            if(module_index == moduleString.length()){
            	return origin_index - module_index;
            }
            
            return -1;
        }
    	
    	/**
         * 计算每个元素对应的"部分匹配值"
         */
        public static int[] matchStr(String moduleString) {
            if (moduleString == null || moduleString.length() == 0) {
                return new int[0];
            }
    
            int[] matchArray = new int[moduleString.length()];
            
            /*
             * 对matchArray数组进行初始化
             * 虽然int数组中每个元素的默认值就是0,但在开发时,建议在使用变量之前,先对变量进行初始化
             */
            Arrays.fill(matchArray, 0);
    
            for (int i = 0; i < moduleString.length(); i++) {
            	for (int j = 0; j < i; j++) {
            		String prefixStr = moduleString.substring(0, j + 1);
            		String suffixStr = moduleString.substring(i - j, i + 1);
            		if (prefixStr.equals(suffixStr)) {
            			matchArray[i] = prefixStr.length();
            			break;
            		} 
            	}
            }
    
            return matchArray;
        }
        
    	/**
    	 * 随机生成字符串
    	 * 
    	 * @param length 表示生成字符串的长度  
    	 * @return String
    	 */
    	public static String generateString(int length) {
    	    String baseString = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789";     
    	    
    	    StringBuilder result = new StringBuilder();
    	    
    	    Random random = new Random();     
    	    for (int i = 0; i < length; i++) {     
    	        result.append(baseString.charAt(random.nextInt(baseString.length())));     
    	    }
    	    
    	    return result.toString();     
    	 }
    	
        public static void main(String[] args) {
        	// 主串
    //    	String originString = generateString(10);
        	String originString = "BBCABCDABBBCDABCDABDE";
        	// 模式串
    //        String moduleString = generateString(4);
        	String moduleString = "ABCDABD";
            // 部分匹配表
            int[] partialmatchArray = matchStr(moduleString);
            
            System.out.println("主串:" + originString);
            System.out.println("模式串:" + moduleString);
            System.out.println("部分匹配表:"+ Arrays.toString(partialmatchArray));
            
            int index = match(originString,moduleString,partialmatchArray);
            System.out.println("匹配的下标:" + index);
        }
    }

    如果,您认为阅读这篇博客让您有些收获,不妨点击一下右下角的【推荐】。
    如果,您希望更容易地发现我的新博客,不妨点击一下左下角的【关注我】。
    如果,您对我的博客所讲述的内容有兴趣,请继续关注我的后续博客,我是【刘超★ljc】。

    本文版权归作者和博客园共有,欢迎转载,但未经作者同意必须保留此段声明,且在文章页面明显位置给出原文连接,否则保留追究法律责任的权利。

  • 相关阅读:
    [LeetCode 1029] Two City Scheduling
    POJ 2342 Anniversary party (树形DP入门)
    Nowcoder 106 C.Professional Manager(统计并查集的个数)
    2018 GDCPC 省赛总结
    CF 977 F. Consecutive Subsequence
    Uva 12325 Zombie's Treasure Chest (贪心,分类讨论)
    Poj 2337 Catenyms(有向图DFS求欧拉通路)
    POJ 1236 Network of Schools (强连通分量缩点求度数)
    POJ 1144 Network (求割点)
    POJ 3310 Caterpillar(图的度的判定)
  • 原文地址:https://www.cnblogs.com/codeOfLife/p/5536289.html
Copyright © 2011-2022 走看看