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;
    }

posted @ 2017-11-20 12:00  Spground  阅读(153)  评论(0编辑  收藏  举报