zoukankan      html  css  js  c++  java
  • KMP算法简介

    KMP算法,是由Knuth,Morris,Pratt共同提出的模式匹配算法,其对于任何模式和目标序列,都可以在线性时间内完成匹配查找,而不会发生退化,是一个非常优秀的模式匹配算法。本文就对该算法进行基本的介绍,由于水平有限,解释不恰当的地方,欢迎指出谢谢。

    在KMP算法中,对于每一个模式串我们会事先计算出模式串的内部匹配信息,在匹配失败时最大的移动模式串,以减少匹配次数。

    比如,在简单的一次匹配失败后,我们会想将模式串尽量的右移和主串进行匹配。右移的距离在KMP算法中是如此计算的:在已经匹配的模式串子串中,找出最长的相同的前缀和后缀,然后移动使它们重叠。

    KMP算法对于朴素匹配算法的改进是引入了一个跳转表next[]数组。以模式字符串ABABABB为例,其跳转表为:

    index 0 1 2 3 4 5 6
    substr A B A B A B B
    next -1 0 0 1 2 3 4

    求解过程如下:候选串即为最长的相同的前缀和后缀串
    (1)当 j 等于 0 的时候发生不匹配,即模式串第一个字符与主串i位置不匹配,应将i跳过当前位置,从下一个位置和模式串第一个字符继续比较,此时将 next[0] 设置为-1来表示特殊情况;
    (2)当 j 等于 1 时发生不匹配,此时匹配的子串 S 为“A”,候选串只能是空串,下次匹配还是从模式串的下标0开始比较,即 next[1] 设为0;
    (3)当 j 等于 2 时发生不匹配,此时匹配的子串 S 为“AB”,候选串只能是空串,下次匹配还是从模式串的下标0开始比较,即 next[2] 设为0;
    (4)当 j 等于 3 时发生不匹配,此时匹配的子串 S 为“ABA”,候选串为是“A”,因此 next[3] 设为1;
    (5)当 j 等于 4 时发生不匹配,此时匹配的子串 S 为“ABAB”,候选串为是“AB”,因此 next[4] 设为2;
    (6)当 j 等于 5 时发生不匹配,此时匹配的子串 S 为“ABABA”,候选串为是“ABA”和“A”,选择长度大的子串“ABA”,因此 next[5] 设为3;
    (7)当j等于6时发生不匹配,此时匹配的子串 S 为“ABABAB”,候选串为是“ABAB”和“AB”,选择长度大的子串“ABAB”,因此 next[6] 设为4;

    为了更进一步的说明next的求解:
    再以模式串ababaaababaa为例:
    next例子

    以主串为ABABCABCABABABBC, 模式串为ABABABB为例的匹配过程

    index 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
    substr A B A B C A B C A B A B A B B C
    one A B A B A(2) B B
    two A B A(0) B A B B
    three A B A(0) B A B B
    four A B A B A B B

    通过模式串的4次移动,完成了对目标串的模式匹配。这里以匹配的第一步为例,当模式串(从0计数)在第4个位置处不匹配,next[4] = 2, 所以从模式串的第2个位置从新与不匹配处比较,得到第二步。当第二步中的不匹配位置的next值0时,表示第一个字符匹配不成功,则i向后移动一个,模式串从头开始匹配,得到第三步。
    在整个匹配过程中,无论模式串如何向后滑动,目标串的输入字符都在不会回溯,直到找到模式串,或者遍历整个目标串都没有发现匹配模式为止。

    关于求解 next 数组的方法:
    如何利用 next[n] 求解 next[n+1],减少重复计算呢?
    求解 next[n+1] 的时候,由上边的分析可知,此时存在两个子串 a 和 b 是匹配的,即模式串中 0 到 k-1 的子串(对应 n 时的前缀)和子串 n-k 到 n-1 (对应 n 时的后缀)是相互匹配的,下面分两种情况求解 next[n+1]:
    第一种情况:下标 k 处的字符与 n 处的字符匹配,则 0 到 k 位置的子串与 n-k 到 n 位置的子串匹配(例如 n+1 的情况(k=2)为:ABABA),显然有 next[n+1] = k + 1 = next[n] + 1
    第二种情况:下标 k 处的字符与 n 处的字符不匹配,这样我们为了消除将0 到 k的子串与n处的不匹配,需要向前移动到 next[n] 处。假设 next[k] = k’,相当于找到了S串中的这么一对子串0~k’-1 和 n- (k’ - 1) 到n是匹配的,如果 k’ 处字符和 n 处字符匹配,这利用情况 1)中的办法求得 next[n+1] , 否则用同样的办法借助之前求得的 next 数组值来继续处理。(这一部分我也不是很懂?)

    Java代码如下:

    import java.util.Scanner;
    public class KMPOfJava {
    
        public static void main(String[] args) {
            KMPOfJava kmp = new KMPOfJava();
            Scanner in = new Scanner(System.in);
            String str = "";
            String substr = "";
            int next[];
            while (in.hasNext()){
                str = in.nextLine();
                substr = in.nextLine();
                next = kmp.getNext(substr);
                System.out.println(kmp.KMP(str, substr, next));
                kmp.printNext(next);
            }
        }
        /**
         * 测试用例:
         * 主串:    ABABCABCACBAB
         * 模式串:ABCAC
         * 模式串:ABABAB
         * 模式串:ABABABB
         * 主串:    abbababaaababaa
         * 模式串:ababaaababaa 
         * */
        /** KMP算法 */
        public int KMP(String str, String substr, int next[]){
            int i = 0, j = 0;
            while (i < str.length() && j < substr.length()){
                if (str.charAt(i) == substr.charAt(j)){//匹配
                    ++i;
                    ++j;
                }else{//不匹配
                    j = next[j];//取出next数组对应j处的值
                    if (j == -1){//如果为第一个位置
                        j = 0;//从模式串的头部开始比较
                        ++i;//主串后移一个位置
                    }
                }
            }
            if (j == substr.length())
                return i - substr.length();
            else return -1;
    
        }
    
        /** 得到模式串的next数组 */
        public int[] getNext(String substr){
            int i = 0, j = -1;
            //定义next数组,长度为模式串的长度
            int next[] = new int[substr.length()];
            //设置next初始位置为-1
            next[0] = -1;
            //求解next其他值(当前字符串的最长的相同的前缀和后缀)
            while (i < substr.length() - 1){
                //第一种情况:k处字符与n-k处字符匹配
                if (j == -1 || substr.charAt(i) == substr.charAt(j)){
                    ++i;
                    ++j;
                    next[i] = j;
                }else{//第二种情况:k处字符与n-k处字符不匹配
                    j = next[j];
                }
            }
            return next;
        }
    
        /**打印next函数 */
        public void printNext(int next[]){
            for (int n : next){
                System.out.print(n + " ");
            }
            System.out.println();
        }
    }
    
    不积跬步,无以至千里;不积小流,无以成江海。
  • 相关阅读:
    小白使用分布式追踪系统
    小白使用Hystrix
    Java泛型的协变与逆变
    try-finally的时候try里面带return
    URLEncoder.encode编码空格变+号
    匿名内部类和Lambda表达式是一样的吗?
    Spring Cloud Config配置git私钥出错
    Git本地已有项目关联远程仓库
    第一次使用HSDB
    Springboot应用使用Docker部署
  • 原文地址:https://www.cnblogs.com/xiaocai-ios/p/7779801.html
Copyright © 2011-2022 走看看