KMP算法

来源:互联网 发布:井柏然手写字体软件 编辑:程序博客网 时间:2024/06/05 07:10

    • KMP算法
      • KMP算法简介
      • KMP算法与确定性有限状态自动机DFA
        • 1 DFA与KMP算法
        • 2 DFA子串查找
      • KMP算法的主流实现
        • 1 next数组
        • 2 具体实现

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;    }
原创粉丝点击