转自http://hi.baidu.com/chioyang/blog/item/e8b58f384f51f2c3d56225fb.html
我们从一个普通的串的模式匹配算法开始讲起,这样你才能更深入的了解KMP算法及其优点。
咱们先来看看普通的串的模式匹配算法是怎么进行比较的
主串 (S) a b a b c a b c a c b a b
子串 (T)a b c a c (子串又被称为模式串)
红色表示当前这趟比较指针所在位置,兰色表示当前这趟比较中匹配的部分
第一趟(详细过程)
a b a b c a b c a c b a b
a b c a c
a b a b c a b c a c b a b
a b c a c
a b a b c a b c a c b a b
a b c a c
遇到不匹配的地方时指针回朔,子串向前移动一位(下同),变成如下形式
a b a b c a b c a c b a b
a b c a c
第二趟(省略了中间阶段指针移动比较过程,下同)
a b a b c a b c a c b a b
a b c a c
第三趟
a b a b c a b c a c b a b
a b c a c
第四趟
a b a b c a b c a c b a b
a b c a c
第五趟
a b a b c a b c a c b a b
a b c a c
第六趟
a b a b c a b c a c b a b
a b c a c _
完成匹配,跳出
这就是普通算法的详细匹配过程,看明白了算法就简单了
详细算法我现在就不给了,等以后有时间再编辑。不过假如串的长度为m,子串的长度为n的话,那么这个算法在最坏的情况下的时间复杂度为O(m*n) ,有没有办法降低它的时间复杂度呢?(废话,当然有拉,不然回这个帖子干什么)
-----------------------------
拜D.E.Knuth 和 J.H.Morris 和 V.R.Pratt 所赐,我们有了一种时间复杂度为O(m+n)的算法,为了纪念这3位强人为计算机科学所做的贡献,分别取这3位先生的名字的首写字母K,M,P来命名这个算法,即著名的KMP算法。
我们先不管这个KMP算法是什么,我们先来看看我们能够想到怎样的方法来改进上面的普通算法。
通过观察,我们发现一个问题,如果一个子串,假设为a b c d e f , 兰色的部分与主串匹配, 红色的f 与主串不匹配,那么这个子串最多能往右边移动几位呢?因为子串中的第一个字符 a!=b !=c !=d !=e !=f ,那么主串中与b c d e 匹配的部分肯定和a不匹配。而与f不匹配的那部分无法判断与a是否匹配。因此子串最多向右移动5位, 即a移动到f所在的位置上,再进行判断。
在解决这个问题的同时,我们又发现了一个问题,当这个子串为a b c d a f 时又会如何呢?因为无法判断主串中与a匹配对应的位置开始往后的部分是否与整个子串相匹配,所以子串最多向右移动4位,即a移动到a的位置上,然后再进行判断。
按照这个方法,我们再来看看前面我们举的例子。
第一趟
a b a b c a b c a c b a b
a b c a c
第二趟
a b a b c a b c a c b a b
a b c a c
第三趟
a b a b c a b c a c b a b
a b c a c _
完成匹配,跳出
是不是简单多了呢?这个方法不困难吧,也很容易理解吧。
事实上这就是很多人大叫困难的KMP算法的最基本也是最核心的方法
你现在是不是在想 如果我早生50年,KMP算法就要改名了呢?
当然,强人们的思维比我们严密多了,他们考虑的更完全,比如象 a b c d e a b c ,这种字符串相同的情况,当然这种情况并不比单个字符相同的情况复杂多少。在思想上KMP算法与上面我们讲的方法完全一致,都是要让子串向右移动尽可能远的一段距离来达到降低时间复杂度的目的,但在具体操作上KMP算法与上面的方法又有所不同。他们为子串引入了一个参数next[ j ] ,我们先来讲下next[j] 怎么求:
假设子串为 'p1 p2 p3 p4.......pm '
对于第j个字符,有next[ j ] =
(1) 0 (j=1)
(2) Max{ k | 1<k<j && ' p1...p k-1 ' = ' p j-k+1...p j-1 ' }
(3) 1 其他情况
PS:我要补充的是,为什么要引入next数组,在朴素的字符串匹配中,比如匹配如下字符串:
S=a b c a b b a b d a b b a
T=a b c a b d
当比较到S[4]和T[4]时,发现S[5]!=T[5],S和T都要回溯,S要回溯的下标1(下标从0开始),T要回溯到下标0接着比较。
在S和T匹配过程中,遇到不匹配的字符之前所有字符都是匹配的,即这里的S[5]!=T[5],但是S[0~4]=T[0~4]。
引入next数组以后,由于已知next[5]=2,只需要将T回溯到next[5]=2开始,而S不需要回溯,因为这时一定有S[3]=T[0],S[4]=T[1]。
下标 0 1 2 3 4 5 6 7 8 9 10 11 12
S= a b c a b b a b d a b b a
T= a b c a b d