史上最好理解的KMP算法

来源:互联网 发布:金十数据官网 编辑:程序博客网 时间:2024/05/16 18:21

KMP算法的解释很多,但是大多都晦涩难懂。
我看了好几篇,决定把其中讲得比较好的几篇归纳一下。
但是版权属于它们:
The Knuth-Morris-Pratt Algorithm in my own words
字符串匹配的KMP算法
如果你看不懂KMP算法,那就看一看这篇文章

暴力匹配算法

首先,我假设你已经是一个已经懂暴力匹配算法的人了。
但是暴力匹配算法有一个问题:当你知道你的目标串和搜索串不匹配的时候,它已经附带了一些信息。
比如在如图匹配,实际上目标串已经匹配到了第七个字符D
这里写图片描述
如果接下来你还是把A往后移动一格,实际上你浪费了这次匹配。你应该把A往后移动四格才对。
而利用到了这个信息的就是KMP算法:

已知部分匹配表的KMP算法

首先我们先给出一个部分匹配表:
这里写图片描述
这个表你先用着,具体怎么生成接下来会说。

Step1:

这里写图片描述
已知空格和D不匹配但是前面六格是匹配的。查表可知,对后一个匹配字符B对应的部分匹配值为2,根据如下公式

移动位数=已匹配的字符数-对应的部分匹配值

因为6-2=4,所以搜索词移动4位。

再来试一次:

这里写图片描述
因为空格与c不匹配,搜索词还要继续往后移动。这时,已匹配的字符数为2(“AB”),对应的部分匹配值为0.所以,移动位数=2-0,结果为2,于是将搜索词后移两位。
依次类推。
而匹配串是怎么产生的呢?

部分匹配表的产生

首先来了解前缀和后缀的概念
这里写图片描述
“前缀”指除了最后一个字符以外,一个字符串的全部头部组合;”后缀”指除了第一个字符以外,一个字符串的全部尾部组合。
这里写图片描述
产生依据:

  - “A”的前缀和后缀都为空集,共有元素的长度为0;
  - “AB”的前缀为[A],后缀为[B],共有元素的长度为0;
  - “ABC”的前缀为[A, AB],后缀为[BC, C],共有元素的长度0;
  - “ABCD”的前缀为[A, AB, ABC],后缀为[BCD, CD, D],共有元素的长度为0;
  - “ABCDA”的前缀为[A, AB, ABC, ABCD],后缀为[BCDA, CDA, DA, A],共有元素为”A”,长度为1;
  - “ABCDAB”的前缀为[A, AB, ABC, ABCD, ABCDA],后缀为[BCDAB, CDAB, DAB, AB, B],共有元素为”AB”,长度为2;
  - “ABCDABD”的前缀为[A, AB, ABC, ABCD, ABCDA, ABCDAB],后缀为[BCDABD, CDABD, DABD, ABD, BD, D],共有元素的长度为0。

直观来看是两张图:
这里写图片描述
这里写图片描述
绿色是相同的字符串

java代码

但是看到求部分匹配表的java代码,我们又一次懵逼了。

public int[] getNext(String b){    int len=b.length();    int j=0;    int next[]=new int[len+1];//next表示长度为i的字符串前缀和后缀的最长公共部分,从1开始    next[0]=next[1]=0;    for(int i=1;i<len;i++)//i表示字符串的下标,从0开始    {//j在每次循环开始都表示next[i]的值,同时也表示需要比较的下一个位置        while(j>0&&b.charAt(i)!=b.charAt(j))j=next[j];        if(b.charAt(i)==b.charAt(j))j++;        next[i+1]=j;    }    return next;}

为什么计算部分匹配表的时候不按套路出牌啊?为啥代码这么简单啊。
但是我们要知道,部分匹配表也是有信息可以利用的。
如图
这里写图片描述
当我们知道第j(此处为5)个字符的部分匹配值为i(此处为1)的时候,求第j+1(6)个字符的部分匹配值,我们只需要判断第i+1(2)个字符是不是和第j+1(6)是不是相等就可以了。如果相等,则第j+1(6)的部分匹配值必为i+1(2)。
所以代码有一行是:

        if(b.charAt(i)==b.charAt(j))j++;        next[i+1]=j;

但是如果这两个字符不相等呢?

从前面来找子前后缀

1 、如果要存在对称性,那么对称程度肯定比前面这个的对称程度小,所以要找个更小的对称,这个不用解释了吧,如果大那么就继承前面的对称性了。

2 、如果还是不相等,要找更小的对称,必然在对称内部还存在子对称,而且这个必须紧接着在子对称之后。

while(j>0&&b.charAt(i)!=b.charAt(j))j=next[j];
0 0
原创粉丝点击