KMP 算法是 D.E.Knuth、J,H,Morris 和 V.R.Pratt 三位神人共同提出的,称之为 Knuth-Morria-Pratt 算法,简称 KMP 算法。该算法相对于 Brute-Force(暴力)算法有比较大的改进,主要是消除了主串指针的回溯,从而使算法效率有了某种程度的提高。
算法具体实现以及相关说明建议直接B站搜索-严蔚敏数据结构。讲解详细,通俗易懂。
获得Next[]数组对应值的函数:
需注意的是,Next数组的第一个元素用来存长度,第二个元素往后才用来存数据,这样是更方便理解,因为此时的数据是从1开始。
1. 当模式串Q第j个字符和主串S第i个字符不等时存在等式:
Q1.....Qj-1 = Si-j+1 .. Si-1;
2.假设存在k,将模式串进行移位,移到第k位后。主串不动。
主串与移位后的模式串得出的等式:
(1)Q1 ... Qk-1 = Si-k+1 ..Si-1;
主串和未移位的模式串得出的等式:
(2)Qj-k+1 .... Qj-1 =Si-k+1 ..Si-1;(该等式就相当于1等式的子等式)
(1)和(2)等式就可以得出:
Qj-k+1 ... Qj-1 = Q1....Qk-1;那么只需要Qj-k+1...Qj-1 = Q1....Qk-1等式成立即可。
那么相关Next数组的数值我们就可以看出来了
eg:
1 2 3 4 5 6 7 8
模式串: a b a a b c a c
Next[] : 0 1 1 2 2 3 1 2
但是当数据长度越长,越往后对其Next数组的数值的判断越复杂。
此时需要注意:当Next[i] = k,此时对Q[i]和Q[k]进行判断如果相等:Next[i+1]=k+1;
(Next[i] = k 说明 Qi-k+1 ... Qi-1 = Q1 .... Qk-1,那么当Qi=Qk所以就会存在
Qi-k+ ... Qi-1 Qi=Q1... Qk-1 Qk,那么Next[i+1] = k+1。)
如果不相等k=Next[k];
(当前模式串的Q[i] 不等于Q[k],我们将Qi的串看成主串,Qk的串看成模式串就很好理解了,.就拿上面举的例子来说 第4个字符的Next[4] = 2 ,且Q[4] 不等于Q[2]。我们将原有的模式串看成主串,将Q1 .. Q2 看成模式串T,因为T[2]和Q4不相等,那么我们就需要移位,移位多少就根据此时T[2]的Next[2]是多少。此时T[2]的Next[2]是1,那么就是Q[4]和T[1]继续判断,此时相等,那么k=2.如果不等,此时Next[1]=0,那么就从1重新开始比。)
总的来说就是如果Q[i]和Q[k]不相等,就将原有的看成主串,k长度的串看成字串,因为此时出现了不匹配的状况,所以需要移位,拿当前不匹配的当前位的Next值又是多少,就将k长度看成主串,其Next[k]长度看成字串,直达出现相等的时候或者Next = 0的时候。
此处用的就是递归的思想。
具体的例子可以去看严蔚敏老师的数据结构,里面有很详细的例子。
KMP算法改进:
1 2 3 4 5
当主串 a a a b a
模式串 a a a a c
此时模式串的Next值 分别是:
模式串:a a a a c
Next[] : 0 1 2 3 4
当S[4] != T[4]时,i指针不动,j指针移到第3个字符。在进行S[4]与T[3]的比较,又不等,i指针不动,j指针移到第二个字符。此时可以看出多出来许多不必要的比较。
对Next数组的值求解进行改进:
新增的判断其主要部分在于如果T[j] =T[k]了就将nextval[j] = nextval[k];因为T[j] = T[k],
当S[i] != T[j]时,j指针移到到nextval[j]位,但此时T[j] 与T[nextval[j]]是相等的,
那么S[i]与T[nextval[j]]必然是不相等的,那么还需要在进行一次移位。所以就进行了不必要的重复。新增的判断让其在相等的时候,就回溯到nextval[k]的位置,避免了重复的步骤。
因为我们这是从第一位字符开始的,那么回溯一次即可。