不说废话。
关于kmp算法的用途假设大家都知道了它是用来干什么的。
但具体如何使用、以及为什么这么使用是行之有效的,这正是本篇博客期望要解释清楚的。
=============================
声明,本篇文章最适合那些尝试通过读懂代码来了解kmp原理的人阅读------尤其是当你最迷糊的点是如下代码时:
j=next[j-1] // 求解next[]数组的方法中的一句代码
=============================
先放上一个连接。
三哥讲kmp
不要以为上述的链接播放量少就代表讲的不好或者翻译的字幕不好。其实恰恰相反。
对于模式串(子串):aabaabaaa
得到其next[]数组的各元素的如下:
关于如何得到这个next[]数组,先表述一下代码的操作流程(原理下面讲)
定义变量 i=1,j=0; 全程i在j的右侧(部分小伙伴可能从高中就习惯区间左i右j了,但是此处是反着的,读代码的时候要时刻清楚这一点,不烦一不小心就迷糊了)
初始next[0]=0;
接下来开始遍历。
对于每个i,j对,比较子串s在对应下标位置的字符值是否相同。相同与否将决定i,j如何变化
(1).若s[i]==s[j],则 i++,next[i]=j+1,j++;(注意代码执行顺序,不可颠倒)
(2).若s[i]!=s[j],i保持不变,进入看戏模式,观看j的回跳表演。令j=next[j-1],再次比较s[i]与s[j]的值是否相等,若相等,则执行(1),否则,再次另j=next[j-1],再次比较,直至s[i]==s[j] 或 j=0 为止。注意,此处s[i]==s[j]时的j也有可能为0,。换句话说,最差劲的情况就是j=0了。若j=0时,s[i]还是不等于s[j],则让next[i]=0,i++;
(3)重复(1),(2),直至得到next[s.length-1]的值为止(即直至得到全部部分匹配表next的值为止)
看到这儿,有的同学不禁蛋疼的想问:
1.不是说好的拿子串去匹配主串吗,那你他娘的这儿也没见到主串露过头啊?咋只研究子串自己了呢?
2.i,j为啥这么移动啊?他们各自有啥具体的含义没?
好,那么在下就给上面蛋疼的小伙子开点狗皮膏药治治。
参考如下主串,子串:
主串T=abxabcabcaby
子串s=abcaby
现在设对比到如下位置:
01 2 3 4 5 6 7 8 9
a b x a b c a b c a by
a b c a b y
0 1 2 3 4 5
即主串对比到idx1=8,子串对比到idx2=5的位置
当发现主[8]!=子[5]时,我们的想法是按住躁动的主串idx1的猪头,让它不要动,然后通过移动idx2,来尝试匹配。
那么此处观察子串,前部分abc中的ab和后部分的aby中的ab长得一样(这不废话),而aby又和主[6]主[7]长得一样
那,是不是可以有个大胆的想法,i不是傲娇吗,那就让原来的子串idx2=5挪到idx2=2去。这样,新的idx2的前面俩元素不就和主串idx1前面的俩元素一样了吗。那么就可以不用去比较着俩元素了。直接从新idx2开始和idx1比。
那么,对于任意的情况,何不干脆直接研究一下子串自身的这种“前后重复”的情况?
试想,在研究好之后,利用上面的说明,不就正好代表了:当子串idx2与子串idx1不匹配时(idx1往前,和idx2往前的idx个元素必然完全一样,因为此时正是已经主串匹配到idx1,子串匹配到idx2了),如果知道idx2往回跳多少个单位,可以使得直接idx1与新idx2比,那么不就正好省了好多力气了吗。
所以,上面第一个问题得到解答。
那么关于第二个问题,i,j又分别代表什么含义呢?
想把第二个问题理解了,就还得理解问题一中说到的“三部分相等”的这一块内容。
当然,对于第二个问题,其实只需要理解子串自身的“前后重复”问题就行了。
先放答案:
j代表从下标0到下标j(包含j)这j+1个连续串等于从i往前数j个再加上i自己一共j+1个字符相等的个数-1.
i代表在子串中,从下标0到i自身这部分局部串满足前j+1个字符数等于这个局部串后j+1个字符数的原子串的子串。
而上述不停出现的 j+1 这个数字,就是 i 下标对应的 next[i]的值。(具体是因为,java是从0开始计数的。下标到下标j一共有j+1个元素)
所以:next[idx-1]的含义就代表了:当子串匹配到下标idx不匹配时,下标0到下标idx-1这个长度为idx的串前面和后面相同字符数目。而且,让j=next[j-1]时,j正好跳到前面第一个不匹配的元素下标那儿(还是归功于从0开始计数的好处)