KMP算法介绍总结
KMP算法来源
KMP算法英文是(Knuth-Morris-Pratt),它是以三个发明者命名的,起头的K代表的是著名科学家Donald Knuth.其最常用于字符串匹配,查询。
算法说明
一般我们匹配字符串的时候,我们从目标字符串dstr(假设长度为n)的第一个下标选取和sptr长度(长度为m)一样的子字符串进行比较,如果一样,就返回开始处的下标值,不一样,选取str下一个下标,同样选取长度为n的字符串进行比较,直到str的末尾(实际比较时,下标移动到n-m).这样的时间复杂度是O(m*n)。而使用KMP算法的复杂度是O(m+n)。
那KMP是如何简化时间复杂度的了?利用目标字符串dstr的性质(比如里面部分字符串的重复性,即使不存在重复字段,在比较时,实现最大的移动量)。举例说明:有一个字符串“BBC ABCDAB ABCDABCDABDE”,想知道,里面是否包含字符串""ABCDABD"?- 看上图所示:字符串“BBC ABCDAB ABCDABCDABDE”的第一个字符与搜索词"ABCDABD"的第一个字符,进行比较,因为B与A不匹配,所以搜索词后移一位。
- 往后移动一位以后,还是不一致,继续移动。知道第一位相同 ,此时情况如下所示:
- 第一个字符的数字相同后,继续比较第2个字符,也完全相同 直到有一个字符不完全相同: D对应的是空格。此时常规的做法就是将搜索词整个后移一位,然后再依次进行比较。但是这样做,因为你需要从第2位开始,依次进行比较,那些曾经比过的位。但此时的实际情况却是:当D与空格不相匹配的时候,你实际上是知道签名六个字符是“ABCDAB”的。KMP算法就是利用了这个信息,不把搜索的起始位置移动到已经比较过的位置,继续把他向后移动,避免了来回移动,从而提高效率,但这样,假如刚好有一个子串从第2位开始能匹配上不是就遗漏了吗?
- KMP 当然不是随意的移动,是有一定规则的。这个规则就是《部分匹配表》或者也称之为Next表。这是当前例子的Next表:
- 我们先暂时不管,这种表是如何产生的和为什么,先按照这张表进行位移。当我们发现空格与D不匹配,前面6个字符“ABCDAB”是匹配的时候。查表可值,最后一个匹配字符B对应的“部分匹配值”是2,按照下面的公式计算向后移动的位数:
移动位数 = 已匹配的字符数-对应的部分匹配值
从上面的公式我们可以得出:6-2=4,所以将搜索词向后移动4位,到达下面的位置。
- 移动了4位以后,再次依次比较,发现空格与C不匹配,此时,已匹配的字符数为2(“AB”),对应的“部分匹配值”为0,所以移动位数为2,于是往后再移动2位。
- 空格与A不匹配。直接后移动一位。,此时C与D不匹配。匹配的字符数为6,部分匹配值为2,6-2=4,移动4位。
- 此时发现完全匹配了。此时如果还要继续找出所有的匹配值,则7-0=7,移动7位继续往下即可。
- 从上面可以看出KMP算法非常重要的点就是得出Next表。 这里先介绍“前缀”和“后缀”。“前缀”指除了最后一个字符以外,一个字符串的全部头部组合;“后缀”指除了第一个字符以外,一个字符串的全部尾部组合。 “部分匹配值就是”前缀“和”后缀“的最长的共有元素的长度。
- "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 以上就是Next表的计算方法。其实,"部分匹配"的实质是,有时候,字符串头部和尾部会有重复。比如,"ABCDAB"之中有两个"AB",那么它的"部分匹配值"就是2("AB"的长度)。搜索词移动的时候,第一个"AB"向后移动4位(字符串长度-部分匹配值),就可以来到第二个"AB"的位置。
参考文档: