KMP字符串匹配算法
这两天仔细研究了一下kmp匹配算法,一开始觉得有点绕,后来一步步把序列写到纸上比较,终于算是搞懂了这里面的逻辑,写出来和大家分享一下。
一、kmp算法描述
kmp算法旨在高效地从字符串S中找到子串T的位置。对于这样一个问题,最容易想到的做法便是:遍历S,对于S中的每一个字符,遍历T,并逐个比较S和T中的字符,直到找到完全匹配的位置。这种做法完全没有利用前一次错误匹配中的信息。而kmp算法通过分析子串T的结构,使得在匹配错误之后,不用在S中回溯,也不用再次从T的第一个字符开始比较,从而实现了算法效率的提升。
kmp算法使用一个与T等长度的next数组来表征T的结构信息。对于字符串T:
字符pj的next[j]值定义为
next[j]的意义就是字符pj之前字符串p0p1…pj-1中,前缀子串恰与后缀子串相等的最大长度。例如:字符串”abcdabcefgh”的next数组即为[-1 0 0 0 0 1 2 3 0 0 0]。
有了子串T相应的next数组之后,kmp算法首先依然是从首个字符串开始匹配S和T,假设S和T的前j个字符均可匹配,但第j+1个字符不匹配,即
,如下图
此时按照kmp算法,只需要将T移动到上图所示的位置,并且从
和
开始向后匹配即可,其中k=next[j]。后面以此类推,这样便不需要在S串中回溯,也不需要从T串的第一个字符开始匹配,大大提高了算法效率。
这里有两个问题:首先,为何不用比较T中的
与S中的
?这点比较简单,由T串的next[j]=k的定义即可得出这二者必定相等。第二,为何p0可以直接移动到pj-k的位置,而之前的位置都不用比较?这里采用反证法,假设将T中的p0移动到S中pj-k-1的位置,如下图
可以判定S中
与T中的
必定不相等,因为如果相等的话,就可以得出T串中next[j]=k+1,矛盾,所以此位置不需要比较就可以判定不相等。以此类推,pj-k之前的位置都不需要比较。
C语言的kmp算法代码如下:(其中get_next函数后面描述)
int kmp_index(char* S, char* T) { int T_len = strlen(T); int* next = (int *)malloc(T_len * sizeof(int)); get_next(T, next); int i = 0, j = 0; while (S[i] != '\0') { if (S[i] == T[j] || j == -1) { if (j == T_len - 1) { return i-j; } ++i; ++j; } else { j = next[j]; } } return -1; }
二、计算next数组
上面我们知道,kmp算法的关键在于计算T串的next数组的值,这里我们采用递推的方法,假设已知next[j]=k,如何求next[j+1]呢?
由next[j]=k可知:T串中有p0p1…pk-1 = pj-k…pj-1。接下来看pk与pj:
(1)若pk=pj,那么就有p0p1…pk-1pk= pj-k…pj-1pj 也就是next[j+1] = next[j]+1=k+1
(2)如果pk不等于pj,采用回溯的方法:
此时我们需要找一个k1,使得
,这样即可说明next[j+1]=k1。但是如何找到这样一个k1呢?这就是回溯的关键。
而由next[j] = k >= k1可知:
取红色方框的部分,并与上面大括号的方程联立,可以得到:
上面的式子表明:k1=next[k],这就是回溯表达式,检查k1对应的字符与pj是否相等,若相等则next[j+1]=k1, 否则接着按照回溯表达式回溯,直到next[k]=-1为止,也就是回溯到T串的第一个字符。
按照上面的逻辑,可用C语言描述get_next函数如下:
void get_next(char* T, int* next) { next[0] = -1; int j = 0; int k; while(T[j] != '\0') { k = next[j]; while (T[j] != T[k] && k != -1) { // 若不相等,回溯找k1,直到相等或到达第一个为止 k = next[k]; } next[j+1] = k+1; j++; } }







浙公网安备 33010602011771号