1、KMP算法的介绍
前提:
BF的算法效率是比较低下的,KMP算法是字符串查找遍历的另一种小乱比较高的算法。KMP算法的核心就是避免不必要的回溯,问题有模式串决定,不是有目标决定。
以下是几个思路启发,对KMP算法进行独自的思考:
思路启发一:
对于这个例子,当出现失配的情况之前,前面的内容子串和母串都是一一匹配的,而且子串的内容各不相同,所以当出现失配的情况时,不需要回溯到 L 浪费效率,而是应该从 e 重新开始匹配。
思路启发二:
这种情况下,由于出现了相同的字符,也就子串失配的位置前有前缀和后缀是一样的,所以不适用于思路启发一。而是应该在第二个w中进行匹配。也就是子串的第一个字符j[1]应该与母串的i[2]进行匹配。如下所示:
思路启发三:
思路启发三是根据思路启发二进行修改,应该这种子串的重复形式与思路启发二不同。此处的前缀和后缀分别有两个字符串表示。也就是这种情况下的j[1]和j[2]与i[4]i[5]是匹配的,而且与j[4]j[5]也是相同的,所以j[1]应该放在i[4]处开始匹配,不能放过任何一种的匹配可能,如下所示:
思路启发四
这种情况是思路启发二的极端情况,应该按如下的匹配方式
以上是四条规律总结出来就是KMP算法。
2、接下来对next数组进行探讨:
next数组,指导着模式匹配串下一步改用第几号元素进行去匹配。
(0号元素分别存储着子串和母串的元素多少)
再次补充:
KMP的next数组的作用是指导T这个模式匹配串下一次匹配应该匹配的位置。
以思路启发一为例:
由于思路启发一的子串个元素个不相同,所以如果当任意位置失配的时候,都是以j[1]来进行重新的配对。但是如果是第一个就失配的话,已经不可能拿第一个再去和母串进行匹配,而是应该拿0号元素。而这里的0号元素存放着整个字符串的长度。所以,这时候可以用一个while来进行判断,如果当j == 0时,i++,j++,这时候就同时移位再次判断。next数组和移位后如下所示:
以思路启发二为例:
此时子串i[2]的前缀和后缀是相同的,所以j[3]失配的时候,此位应该是由j[2]来进行重新匹配。思路启发二的next数组如下所示:
以思路启发三为例:
当j[6]出现失配的时候,由于j[6]的前缀bb和后缀bb相同,所以有两个元素相同,对应的next数组应该为3,也就是此位应该由j[3]进行重新的匹配。
而当j[5]出现失配的时候,由于前缀和后缀只有一个元素相同,所以对应的next数组应该为2,也就是此位应该由j[2]来进行重新匹配。
其他的类似
所以,思考启发三的next数组如下所示;
以思路启发四为例:
由上诉可知,当j[5]发生失配的时候,前缀和后缀分别有3个元素对应,所以对应的next数组值应该是4。其他的类似计算,思考启发四的next数组如下所示:
补充:前缀和后缀的判断
前缀和后缀是以失配的位置来进行判断的,前缀和后缀是是相对的,而子串的第一个位置一定是前缀。而后缀是位置的不同相对判断的。
下面一子串 ababaaaba为例,首元素是j[1]= a,对于j[6]元素来说,其前缀和后缀如下所示:
前缀和后缀如下所示:
可见,最高其前缀和后缀的最高相同元素是3个,所以其对应的next数组值是4。
3、next数组的实现
当模式匹配串T失配的时候,NEXT数组对应的元素知道应该用T串的那个元素进行下一轮的匹配。
next数组获取代码如下:
void GetNext(char *son, int *next) { int front = 0; //前缀 int back = 1; //后缀 next[1] = 0; //next数组的第一个元素肯定为0 while (back < strlen(son)) //当后缀未到达最后一个字符时 { if (0 == front || son[back] == son[front]) //当匹配时,应该都同时再加1进行下一个数的匹配 { back++; front++; next[back] = front; } else //当失配时回溯寻找前缀front,因为前缀是固定,而后缀是相对的 front = next[front]; } }
测试代码:
int main() { char Big[256] = " ababaaaba"; int next[256] = { 0 }; GetNext(Big, next); for (int i = 1; i < strlen(Big); i++) printf("%d ", next[i]); return 0; }
结果如下:
4、KMP算法实现
代码如下:
int KMPFindString(char *mom, char *son, int pos) { int next[256] = {0}; int mompos = pos, sonpos = 1; GetNext(son, next); //以下和bf算法相似 while (mompos <= sizeof(mom) && sonpos <= strlen(son) ) { if ( (sonpos == 0) || (mom[mompos] == son[sonpos]) ) { mompos++; sonpos++; } else sonpos = next[sonpos]; } if (sonpos > strlen(son)) //整个成功的匹配了 return mompos - strlen(son); else return 0; }