一、KMP算法的算法思想
位置 | 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 |
主串 | a | b | b | a | a | b | b | a | a | b | a |
模式串 | a | b | b | a | a | b | a |
我们想要知道模式串在主串的哪个位置出现?
常规做法:
从主串的开头(即第零个位置)与模式串的开头(即第零个位置)开始,一一进行比较,若二者元素相同,则主串和模式串同时向后比较下一个元素,当出现不匹配时,则主串回退到第一个位置,模式串回退到开头;当再次发生不匹配时,主串回退到第二个位置,模式串回退到开头……每次不匹配,主串都回退到下一个元素,模式串都回退到开头,再重新进行比较,直到找到主串中与模式串匹配的字符串的开始位置或者不存在这样的位置为止。
显然根据描述可知,这种暴力方法在最坏情况下的时间复杂度为:O(mn),其中 m、n 分别是主串和模式串的长度。
KMP算法的目标:
主串:发生不匹配时,自己不回退
模式串:发生不匹配时,尽可能少地回退,并且其回退位置前的字符串是与主串匹配的,这样才能保证主串不需要回退
如何实现?
1- 发生不匹配时,模式串中不匹配元素前的字符串(记为 match),对于主串和模式串来说是相同的,我们用 match 的头部去匹配 match 的尾部,最长的公共部分就是模式串需要回退的位置。【即最长公共前/后缀】
举个栗子:
上文中给出的主串和模式串,显然在模式串第六个元素的位置发生不匹配,match 为 abbaab
abbaab的头部有:a,ab,abb,abba,abbaa 【不包含最后一个元素,称为前缀】
abbaab的尾部有:b,ab,aab,baab,bbaab 【不包含第一个元素,称为后缀】
显然,最长匹配为 ab,最长匹配长度为 2,因此模式串回退到第二个元素的位置。【即b】
【为何可行?因为 match 的头部和尾部是相同的,match 的尾部在不匹配发生时已经与主串匹配,所以 match 的头部与主串也是匹配的,因此我们进行这样的回退不需要再与主串比较模式串回退位置前的元素,保证了主串不需要回退】
2- 因此,在进行模式串和主串的匹配之前,对模式串的每个子字符串 [0..i],算出其相匹配的前缀与后缀中,最长的字符串长度。
二、求解模式串pattern的最大匹配数
举个栗子:
位置 | 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 |
模式串pattern | a | b | a | b | a | b | z | a | b | a | b | a | b | a |
最大匹配数 | 0 | 0 | 1 | 2 | 3 | 4 | 0 | 1 | 2 | 3 | 4 | 5 | 6 | 5 |