算法——字符串匹配之KMP——看不懂算我输

来源:互联网 发布:php cli模式 编辑:程序博客网 时间:2024/05/16 14:51

主字符串用S代替,长度为N;模式字符串用P代替,长度为M

本文记录的是博主学习KMP的过程,其中参考资料均已在文中给出链接,博主花了两天的时间才弄懂KMP。如果大家按照给出的学习过程,相信一定会很快弄懂KMP。

KMP

1.KMP算法思想:整体算法步骤和朴素匹配(暴力搜索)类似,相同点是逐个字符进行比较,全部比较成功则返回主字符串S的搜索位置,不同点是在于匹配不成功时,KMP可以找到向后移动到最佳位置继续进行比较,保证了S可以继续比较而不用回溯,减少了无效比较(暴力搜索在比较不成功时仅仅移动到下一个位置,存在大量无效比较)(此处有疑问请继续向下看

2.算法过程描述:参见字符串匹配的KMP算法–阮一峰的网络日志,看完其中的图示并且能够理解模式串在不匹配时向后移动到什么位置即可,但是其中的部分匹配表和下文介绍的next表稍有区别,请以下文为主。

3.算法分析:预处理时间(计算模式串P的next表):O(M),线性。匹配过程:O(N)。所以整个算法的运行时间为O(M+N),最坏运行时间也是O(M+N)。

4.模式串的next表(也叫做失配函数):P[j]前面的字符串P[0…j-1]中,真前缀和真后缀的集合交集中最长的串的长度,计算出来的值是模式串本身的性质。通过计算next表,在匹配不成功时(假设在S[i]和P[j]处不匹配),直接就可以找到下一个与S[i]进行比较的位置。(S中的指针没有回溯,所以时间复杂度为O(N))(参考阮一峰老师KMP算法图示的7-13条)


先给出一个模式串P的next表(此处与阮一峰老师不同,请以此处为主)

位置i 0 1 2 3 4 5 6 7 P[i] A B C D A B D ‘\0’ next[i] -1 0 0 0 0 1 2 0

那么,next表应该如何计算呢?next[i]等于P[0]…P[i - 1]子串中最长的相同真前缀和真后缀的长度。
(1):i = 0,对于模式串的首字符,我们统一规定next[0] = -1;
(2):i = 1,前面的字符串为A,其最长相同真前后缀长度为 0,即next[1] = 0;
(3):i = 2,前面的字符串为AB,其最长相同真前后缀长度为 0,即next[2] = 0;
(4):i = 3,前面的字符串为ABC,其最长相同真前后缀长度为 0,即next[3] = 0;
(5):i = 4,前面的字符串为ABCD,其最长相同真前后缀长度为 0,即next[4] = 0;
(6):i = 5,前面的字符串为ABCDA,其最长相同真前后缀为A,即next[5] = 1;
(7):i = 6,前面的字符串为ABCDAB,其最长相同真前后缀为AB,即next[6] = 2;
(8):i = 7,前面的字符串为ABCDABD,其最长相同真前后缀长度为 0,即next[7] = 0。

下面给出一个例子ABABAB,看看你能不能计算出来吧!
P: ABABABD ——>next[0…7]={-1,0,0,1,2,3,4,0}

为什么可以快速找到下一个比较的位置呢?下面举例说明:

i 0 1 2 3 4 5 6 7 8 主字符串S A B C D A B A B B 模式串P A B C D A B D A A

S[6]与P[6]发生不匹配,说明S[1…5]和P[1…5]相同。通过计算我们知道模式串P[7]前面的P[0…6]子串的真前缀和真后缀中相同的部分的最大长度为2,说明模式串中最多只有前缀P[0…1]=后缀P[4…5],而且当前已经确定P[4…5]=S[4…5],所以我们知道下一轮比较已经存在P[0…1]=S[4…5],可以直接比较P[2]和S[6]即可(next[6]=2,所以直接拿模式串P[2]与上一位不匹配的字符S[6]比较)

那么这样计算的下一个位置会不会过于靠后呢?答:不会!因为我们计算的是模式串中P[i]前面的最长的公共前后缀,它的意思是找到了尽最大可能长的后缀,同时存在一个前缀与之相等。下次比较时,该前缀移动到与该后缀对应的主字符串的位置,因为该后缀尽最大可能的长,所以移动的位置最短,不可能过于靠后。

参考资料:KMP 算法(1):如何理解 KMP 三:KMP 字符串匹配算法 3.2 next 数组是如何求出的


5.next数组如何用代码求解?

// javapublic static int[] getNext(String P) {    int p_len = P.length();    int[] next = new int[p_len+1];    int i = 0; // P的下标    int j = -1;    next[0] = -1;    while (i < p_len) {        if (j == -1 || P.charAt(i) == P.charAt(j)) {            i++;            j++;            next[i] = j;        } else {            j = next[j];        }    }    return next;}

博主还有待理解。这里已经讲得非常清楚了。KMP 算法(1):如何理解 KMP 三:KMP 字符串匹配算法 3.2 next 数组是如何求出的

6.KMP搜索的代码实现

// java public class KMPSearch {    // 求next表    public static int[] getNext(String P) {        int p_len = P.length();        int[] next = new int[p_len+1];        int i = 0; // P的下标        int j = -1;        next[0] = -1;        while (i < p_len) {            if (j == -1 || P.charAt(i) == P.charAt(j)) {                i++;                j++;                next[i] = j;            } else {                j = next[j];            }        }        return next;    }    // 主算法    public static int search(String S, String P, int next[]) {        int i = 0; // S的下标        int j = 0; // P的下标        int s_len = S.length();        int p_len = P.length();        while (i < s_len && j < p_len) {            if (j == -1 || S.charAt(i) == P.charAt(j)) {// P的第一个字符不匹配或S[i]==P[j]                i++;                j++;            } else {                j = next[j]; // 当前字符匹配失败,按照next表进行跳转            }        }        if (j == p_len) {// 匹配成功            return i - j;        }        return -1;    }    public static void main(String[] args) {        String S = "abcdabcdabd";        String P = "abcdabd";        for (int next: getNext(P)) {            System.out.print(next + " ");         }        System.out.println();        System.out.println(KMPSearch.search(S, P, getNext(P)));    }}   

参考资料:
[1]. 字符串匹配算法比较
[2]. 字符串匹配的KMP算法——阮一峰的网络日志
[3]. KMP 算法(1):如何理解 KMP
[4]. KMP 算法(2):其细微之处
[5]. 如果你看不懂KMP算法,那就看一看这篇文章( 绝对原创,绝对通俗易懂)

原创粉丝点击