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

    KMP算法

    1.KMP算法简介

    KMP算法是一种改进的字符串匹配算法,由D.E.Knuth,J.H.Morris和V.R.Pratt同时发现,因此人们称它为克努特——莫里斯——普拉特操作(简称KMP算法)。KMP算>> 法的关键是利用匹配失败后的信息,尽量减少模式串与主串的匹配次数以达到快速匹配的目的。具体实现就是实现一个next()函数,函数本身包含了模式串的局部匹配>> 信息。时间复杂度O(m+n)。

    2.KMP算法与确定性有限状态自动机DFA

    2.1 DFA与KMP算法

    子串查找问题通常会存在两个字符串,一个是原串s, 另一个是模式串p,设m = len(s), n = len(p)并且通常m >> n

    对于子串查找问题,很朴素、直接的一个解法就是暴力求解法,即从s中的第0个字符开始,将原串s中的每一个字符和模式串p的每一个字符进行比较,若全部匹配成功,则返回;否则,则从s中的第1个字符开始,重复之前的比较操作直到到达s的最后一个字符;

    从上述的描述中易得出 暴力解法 的时间复杂度是O(nm),暴力解法的缺点:
    - 时间复杂度高;
    - 不适用于字符流的情况;当原串s是字符流(例如网络字符)的时候,该解法存在回溯,若不加额外的缓存,是不能用于此类问题的;

    但是,大多数情况下,原串和模式串都比较小,也都不是字符流的情况,而采用高级算法通常都用一些预处理的过程,对于小规模问题这都是不划算的,因此暴力解法还是很常用的,比如jdkindexOf()就是采用暴力解法实现的。

    从暴力解法的描述可以看出,当从s的字符i开始,与模式串p逐字符比较时,若在i+k处发生失配时,指向s的指针是需要回溯到i+1继续逐字符比较,而没有利用好已经匹配好的k个字符。

    KMP算法解决的问题是:当发生字符失配的时候,不回溯指针i。这样就能克服 暴力解法 的两个缺点。

    KMP算法核心的思想是:当发生字符失配的时候,充分利用已经匹配成功的k个字符的信息,避免指针i的回溯

    KMP算法可以用确定性有限状态机DFA来直观的阐述。
    DFA
    - 包含有限的状态(包括开始和停止)
    - 每一个字符只发生一次状态的转移
    - 如果一系列的状态转移到了停止状态,则匹配成功

    关键在于如何根据模式串p构建DFA,略。

    2.2 DFA子串查找

    
    public class DFASubStringSearchDemo {
    
        public static void main(String[] args) {
            // TODO Auto-generated method stub
            char[] radixChar = new char[] {'A', 'B', 'C', 'D',
                    'E', 'F', 'G', 'H',
                    'I', 'J', 'K', 'L',
                    'M', 'N', 'O', 'P',
                    'Q', 'R', 'S', 'T',
                    'U', 'V', 'W', 'X',
                    'Y', 'Z'};
            String txt = "ABCBDBCBABCBCBABBWEHJHHOISCBIIOSAOPOPIOHUCUIBSYGTWBNIOAUSABCBDBCBABCBCBABBCBABCCCAASASADSWFEFSDBCBABCCCAASASADSWFEFSDB";
            System.out.println(txt.length());
            String pattern = "ABC";
            DFASubStringSearchDemo demo  = new DFASubStringSearchDemo();
            int[][] dfa = demo.buildDfa(pattern, radixChar);
            int startIndex = demo.search(txt, pattern, dfa);
            System.out.println(startIndex);
            String target = startIndex+pattern.length() <= txt.length() ? txt.substring(startIndex, startIndex+pattern.length()) : "NO MATCH";
            System.out.println(target);
        }
    
        public int[][] buildDfa(String pattern, char[] radixChar) {
            int[][] dfa = new int[radixChar.length][pattern.length()];
            dfa[pattern.charAt(0)-'A'][0] = 1;
            for (int X = 0, j = 1; j < pattern.length(); j++) {
                for (int c = 0; c < radixChar.length; c++)
                    dfa[c][j] = dfa[c][X];
                dfa[pattern.charAt(j)-'A'][j] = j+1;
                X = dfa[pattern.charAt(j)-'A'][X];
            }
    
            return dfa;
        }
    
        public int search(String txt, String pattern, int[][] dfa) {
            int n = txt.length(), m = pattern.length(), i = 0, j = 0;
            for (; i < n && j < m; i++)
                j = dfa[txt.charAt(i)-'A'][j];//状态转移
            if (j == m)
                return i - m;
            else
                return n;
        }
    }
    

    3. KMP算法的主流实现

    3.1 next数组

    next[j]数组表示模式串p的位置j发生失配时,应该从next[j]处继续匹配,而不用回溯原串s的i指针。
    同时,next[j]的值也是表示模式p[0~j-1]的最长公共前后缀的长度。

    3.2 具体实现

    kmp算法

    
        public int kmp(String txt, String pattern, int[] next) {
            int i = 0, j = 0;
            for (; i < txt.length() && j < pattern.length();) {
                if (j == -1 || txt.charAt(i) == pattern.charAt(j)) {
                    i++;
                    j++;
                } else {//mismatch 利用next数组得到j回退的位置
                    j = next[j];
                }
            }
            if (j == pattern.length())
                return i - j;
            else
                return -1;//not found
        }
    
    

    关键在于构建next数组

    
    public int[] getNext(String pattern) {
            int[] next = new int[pattern.length()];
            next[0] = -1;
            int k = -1;
            int j = 0;
            while (j < pattern.length() - 1) {
                if (k == -1 || pattern.charAt(j) == pattern.charAt(k)) {
                    j++;k++;
                    if (pattern.charAt(j) == pattern.charAt(k))
                        next[j] = next[k];//j和k字符相同,因此j发生失配时,若跳转到k,则k也会发生失配,继而跳转到next[k],所以不如直接一步到位,将k的next值而不是k赋值给next[j];
                    else
                        next[j] = k;
                } else {
                    k = next[k];//k backup until pattern[k] == pattern[j]
                }
            }
            return next;
        }
    

  • 相关阅读:
    [SCOI 2003] 字符串折叠
    [POJ 3252] Round Numbers
    [ZJOI 2010] 数字计数
    [POJ 2282] The Counting Problem
    [POJ 1191] 棋盘分割
    [POJ 3345] Bribing FIPA
    [POJ 2785] 4 Values whose Sum is 0
    [NOIP 2017] 列队
    [NOIP 2017] 宝藏
    基于Qt Gui的Led控制显示程序
  • 原文地址:https://www.cnblogs.com/Spground/p/8536141.html
Copyright © 2011-2022 走看看