前言
KMP算法是很经典且高效率的字符串匹配算法,时间复杂度O(m+n)。而纯暴力的BF算法,时间复杂度为O(mn)。网上解析KMP算法的博客也很多,在后面推荐几个不错的博客理解其原理,在此只贴出代码。
关键
1.首先需要理解前缀、后缀的概念。
比如:字符串"abaab"
前缀 | “a” | “ab” | “aba” | “abaa” |
---|---|---|---|---|
后缀 | “b” | “ab” | “aab” | “baab” |
2.然后理解next数组的概念
next数组逐个存储前缀等于后缀的个数,即前缀等于后缀的公共字段的个数。
如:
“abaab”:
字符 | a | b | a | a | b | NULL |
---|---|---|---|---|---|---|
位置j | 0 | 1 | 2 | 3 | 4 | 5 |
next[j] | -1 | 0 | 0 | 1 | 1 | 2 |
- 位置j为4时,字符串为:“abaa”,前缀和后缀中有相同的子串"a",所以next[j]=1。
- 位置j为5时,字符串为:“abaab”,前缀和后缀中有相同的"ab",所以next[j]为2。
注意:因为设置第一个值为-1,所以实际的next[j]就根据当前位置计算next值。和有些文章计算i之前的字符串next[j]的值不一样。同时,如果字符串的索引从1开始的话,那么next[j]的值需要+1。
3.那么next[j]的值到底是什么意思呢?意思就是模式串(短的)从next[j]的值开始的位置和主串进行匹配。因为主串的指针i是一直往后移动的,所以主串和模式串的位置next[j]匹配,就相当于模式串往后移动了。
4.最后编写代码即可。编码时注意理解概念,从理论到实际还是有所不同的。kmp算法,主串的位置指针i是一直往下走的,避免了回溯而浪费时间。
代码
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <string>
#include <iostream>
using namespace std;
void FindNext(string str, int next[])//找到next数组
{
int i = 0;
int j = -1;
int n = str.size();//str的长度
next[0] = j;//next[0]存储-1,在j=next[j]时,如果j=0,则next[j]=-1,下次判断就可以进入第一个if
while (i < n)
{
if (j == -1 || str[i] == str[j])//j=-1表示上一次不匹配;两者相等则j++赋值给next
{
i++;
j++;
next[i] = j;
}
else
{
j = next[j];//不满足,j跳转
}
}
}
//优化后的next数组
void FindNextVal(string str, int next[])
{
int i = 0;
int j = -1;
int n = str.size();//str的长度
next[0] = j;//next[0]存储-1,在j=next[j]时,如果j=0,则next[j]=-1,下次判断就可以进入第一个if
while (i < n)
{
if (j == -1 || str[i] == str[j])//j=-1表示上一次不匹配;两者相等则j++赋值给next
{
i++;
j++;
if (str[i] == str[j])//如果当前位置的str值等于当前位置存储的next数组的str值,可不必该次计算
next[i] = next[j]; //因为已经知道当前不匹配值为str[i],那么str[j]与str[i]相同的话也不匹配
else
next[i] = j;
}
else
{
j = next[j];//不满足,j跳转
}
}
}
int KMP(string s, string p, int next[])//s模板串(长的), p匹配串(短的),next数组,返回匹配字符的头所在位置
{
FindNextVal(p, next);//给匹配串计算next数组
int i = 0;//s串的位置,一直增加
int j = 0;//p串位置,根据next数组改变
int n = s.size();
int m = p.size();
while (i < n && j < m)
{
if (j == -1 || s[i] == p[j])
{
i++;
j++;
}
else
{
j = next[j];//j的位置决定了p字符位置
}
}
if (j == m)//说明在s中找到了子串p
{
return i - j;
}
else
{
return -1;
}
}
int main()
{
int next[100] = { 0 };
cout << KMP("abcababbabaabc", "abaab", next) << endl;
}