一.KMP算法介绍
KMP算法是一种改进的字符串匹配算法,由D.E.Knuth,J.H.Morris和V.R.Pratt提出的,因此人们称它为克努特—莫里斯—普拉特操作(简称KMP算法)。KMP算法的核心是利用匹配失败后的信息,尽量减少模式串与主串的匹配次数以达到快速匹配的目的。具体实现就是通过一个next()函数实现,函数本身包含了模式串的局部匹配信息。KMP算法的时间复杂度O(m+n)。—— 百度百科
二.为什么存在 next 数组
普通的 BF 算法,存在大量的多余匹配,为了减少模式串与主串的匹配次数以达到快速匹配的目的。来优化 BF 算法里面匹配失败后,将子串从头开始与模式匹配的问题。在实际的匹配当中,当模式串与子串匹配失败的时候,有很多的情况子串是不需要从头开始跟模式串匹配的。只需要退回到一个特定的位置。next数组就是一个长度跟子串的长度一样,数组中存储子串中每个字符在匹配失败后,需要退回的位置的数组。
当有了 next 数组,当子串与模式串匹配失败后,子串无需从最开始的位置与模式串匹配,只需从 next 数组中存储的位置开始再次与模式串匹配。
三.子串退回位置的求解
其中我们将在子串第一个字符匹配失败退回位置记作 -1,在第二个字符匹配失败退回记作 0 。
这样根据上述方法就能求解出,子串每个位置匹配失败后的退回位置,将每个字符匹配失败后的退回位置放在一个长度与子串长度相等数组中,这个数组就是 next 数组。
四.next数组的代码求解:
第一种情况:
在求 next 数组的时候,当前位置 i 匹配失败后,k 代表退回的位置,如果 k 所在的位置的字符,与 i 所在位置的字符相等时,则 i+1 位置匹配失败后,退回的位置就是,k+1;
第二种情况:
当前位置 i 匹配失败后,k 代表退回的位置,如果 k 所在的位置的字符,与 i 所在位置的字符不相等时,此时,就需要 k 继续按照当前位置的继续回退,直到回退到,k 位置的字符与 i 位置的字符相等,或者 k==-1.这个时候就回到了,第一种情况。
五.代码实现及讲解:
代码:
#define _CRT_SECURE_NO_WARNINGS 1 #include<stdio.h> #include<stdlib.h> #include<assert.h> #include<string.h> void GetNext(char* sub,int *next) { int sublen = strlen(sub); int i = 2; // i 从数组子串第三个元素开始 int k = 0; // 代表回退的位置,k的位置始终位于,i - 1 匹配失败的回退位置的对应的 k, // 即可由 i - 1 位置求出 的 k,求出 i 位置的 k。 next[0] = -1; next[1] = 0; while (i<sublen) { //当 k 回退到头的时候 即 k==-1,就需要回退到 0 号下标位置, //或者 如果 退回位置字符和匹配失败的字符相同 if (k == -1 || sub[i - 1] == sub[k]) { next[i] = k + 1; //已经求出 i 的退回位置,i 继续往后走 i++; // k 要变成新的 i-1 位置的退回位置。 k++; } //如果 退回位置字符和匹配失败的字符不相同 else { // k 在当前位置继续回退 k = next[k]; } } } int KMP(char* str, char* sub, int pos) { assert(str && sub); int StrLen = strlen(str); int SubLen = strlen(sub); assert(pos >= 0&&pos<StrLen); //创建 next 数组 int * next = (int*)malloc(sizeof(int)*SubLen); int i = pos; //主串 int j = 0; //子串 //构建next数组 GetNext(sub,next); //主串和子串匹配 while (i < StrLen && j < SubLen) { //如果匹配的字符相同就继续往后匹配 if (j==-1 || str[i] == sub[j]) { i++; j++; } //回退 else { j = next[j]; } } //当 j 遍历到最后时,也就是 子串全部匹配成功 if (j >= SubLen) { return i - j; } //模式串与子串匹配失败 return -1; }
测试:
六.最后
不经一番寒彻骨,怎得梅花扑鼻香。