昨日刷leetcode突然刷到一个最长回文字符串的题,在题解看到了马拉车算法,看完之后ohohohohoohohohoh!!!这个算法的优点就是记录已经获得的有用信息,减少无用的遍历。看完这个算法决定写个博客,所以我写了这篇Kmp算法(同样通过增加空间复杂度减少时间复杂度)
Kmp算法是用来寻找一个字符串是否包含另一个字符串,这个另一个字符串就是模式串
Kmp算法优秀在哪????
朴素算法BF | Kmp算法 |
在对主串的匹配的时候 主串:s1s2s3s4...sn 模式串:p1p2p3p4 转图一
| kmp算法则 //当i=1时进行匹配,当匹配到i=4失败了就要从i=4开始
|
图一:::::
//主串为s
//模式串为p
for(int i=0;i<s.size();i++)
{
for(int j=0;j<p.size();j++)
{
if(j==p.size())
return true;
if(s[i+j]!=p[j])
break; //当i=1时进行匹配,当匹配到i=4失败了就要从i=2开始
//这就意味者i=2,i=3进行的匹配就白用了
}
}
下面我用一个例子来表示为什么出现kmp算法。
比如
1 2 3 4 5 6 7 8 9 10 11 12 13
主串: a b a b c d a b a b a b e
模式串:a b a b e
当我们进行匹配的时候1,2,3,4都时对的,当进行5的时候发现不相等,按照正常来说我们要会到主串的2进行重新匹配,但是我们肯定还会匹配到5这个位置,那我们为什么不将5这个位置直接对齐模式串的一个位置,使接下来的匹配直接从5开始从而不需要再对主串2、3、4进行判断。
这里说一下为什么能从5直接开始,因为首先模式串匹配到5这个位置就说明答案(就是最后要找到的字串)肯定包含5这个位置的字符串或者答案字符串在5这个位置后面,所以我们肯定能在模式串中找到一个位置,保证选择这个位置对主串的5的位置进行匹配的时候:
1.如果答案包含5,那么肯定能进行匹配成功
2.如果答案不包含5,那么也不会对后面产生影响
所以我们在找这个位置的时候一定是假设答案串包含5这个位置的,
因为我们找位置的时候把这个位置当成答案,所以进行匹配选择的这个位置之前的字符要匹配
就意思模式串中和c对标的这个字符之前的一定要和主串中的字符相同
回到我们的例子就是这样
主串: a b a b c d a b a b a b e
模式串: a b a b e
但是如果有多种可能进行匹配呢,比如
1 2 3 4 5 6 7 8
主串: a b a b a b a c
模式串:a b a b a c
这时候第6个位置不同,我们要从第6个位置进行匹配,
可以选择
1.
主串: a b a b a b a c
模式串: a b a b a c
2.
主串: a b a b a b a c
模式串: a b a b a c
我们可以看到这两种情况,对于之前我们说的匹配的位置之前的字符都相同
1 中相同的aba
2 中相同的a
这时候我们如何进行选择呢
答案是选择第一种进行匹配,我们选择策略就是选最长的进行匹配,因为这样保证了如果匹配它一定是最优的匹配(意思就是1号能匹配成功但是2号不一定能匹配成功,但是如果2号能匹配成功,那么1号匹配不成功的话,那么我们会继续对匹配不成功的位置进行找模式串的位置,此时找到的最长的匹配就肯定是2号匹配,,这样选择1号可以是所有可能成为答案的子串都覆盖,而一开始选择2号则会丢失一些情况)
所以问题到了现在我们找的就是如何找到模式串中的相应位置g,使模式串中的第g个字符去对标主串中我们刚才没有匹配成功的那个字符。使其对标后我们要满足两个条件:1、模式串g前面的字符和主串中对标位置上的字符相同。2、这个g的位置一定是尽可能大,意思就是g之前的字符组成的串是最长的
然后我们从例子始解决这个问题
主串: a b a b a b a c
模式串:a b a b a c
我们从头开始进行匹配,当我们进行到标注的这个字符的时候我们知道匹配不成功了,我们记主串中这个标记位置为r,这时候我们根据上面的分析,我们要找到模式串中的一个特殊位置g,来让这个位置g对标到主串中的标记字符(也就是上一次匹配没有成功的位置字符)。上面我们说到了我们找到的这个位置g达成在模式串中0~(g-1)位置上的字符都要和主串对应。
这时候我们要知道我们已经获得的信息是主串位置r之前的字符已经匹配了,放到这个例子中就是我们已经知道这标红的已经相等,所以这时候模式串中的标红字段可以替换主串中的标红字段了,所以我们要找目标就可以替换成去模式串中去找(为什么要这么替换呢,因为主串中标红字段前面可能有别的字符,这样会导致我们记录的信息过多,而因为模式串每次标红都是从第一个字符开始,所以更加方便规划)
我们为了找规律所以我们先从位置g最小开始找起
g=0
主串: a b a b a b a c
模式串: a b a b a c
这时候我们满足g之前的和主串中都相等,如果g不能更大,那么g=0
g=1
主串: a b a b a b a c
模式串: a b a b a c
这时候我们满足g之前的和主串中都相等,如果g不能更大,那么g=1,这时候g=0已经舍弃
g=2
主串: a b a b a b a c
模式串: a b a b a c
这时候我们满足g之前的和主串中不相等,如果g不能更大,那么g=1
g=3
主串: a b a b a b a c
模式串: a b a b a c
这时候我们满足g之前的和主串中都相等,如果g不能更大,那么g=3,这时候g=1已经舍弃
g=4
主串: a b a b a b a c
模式串: a b a b a c
这时候我们满足g之前的和主串中不相等,如果g不能更大,那么g=3
g=5
主串: a b a b a b a c
模式串 a b a b a c
这时候我们满足g之前的和主串中不相等,g不能更大,所以g=3
所以只有在g=1和g=3时候符合相等条件,我们前面又说过可以用模式串中的去替换,所以当g=1和g=3是就可以转换为
g=1
模式串 : a b a b a c
g=3
模式串 : a b a b a c
⬆这个位置是又红又橙
所以我们要找的就转化为在第一次匹配失败时
主串: a b a b a b a c
模式串:a b a b a c
模式串中字符c(就是和主串匹配失败的字符)之前的字符组成字符串的前缀和后缀的最长公共部分。
前缀:包括首字符的除了最后一个字符的所有子串。
后缀:包括最后一个字符的除了首的所有子串。
而这种找最长的匹配位置的方法,就是找前缀和后缀的最长公共部分。
(未完待续。。。)