子串自身存在重复部分时
朴素匹配算法会进行很多不必要的匹配步骤
KMP算法
引入next数组(回溯函数)
表示子串各个位置j值的变化
next[j]就是第j位匹配不通过时,回溯next[j]位,j+1位再进行匹配
next数组计算时可以从第三位开始计算
因为第一位和第二位值是确定的
第一位前面没有其他字符,回溯函数的值定义为-1
第二位前面只有一个字符,回溯函数的值定义为0
第三位开始计算,如果第一位字符和第二位字符相等,next[j]=1,否则next[j]=0
第四位继续计算,
第一位=第二位 情况下(next[j]=1)
如果第二位和第三位字符也相等,next[j+1]=next[j]+1
否则,j回溯next[j]位(j=next[j])
以此类推
程序如下:
class test
{
/*
KMP算法
检索某字符串s在另一字符串str中,第pos位之后是否存在,存在则返回第一次出现的位置
*/
static void Main(string[] args)
{
String str = “ababaaaba”; //构建主字符串
String s = “aa”; //构建子字符串
int pos = 4; //检索起始的位数,最低位定义为1,表示从主字符串第一位str[0]开始检索,也就是检索整个主字符串
int result=searchIndex(str, s, pos); //执行匹配方法,获得结果
switch (result){
case -1:
Console.Write(“在字符串{0}中,从第{1}位起开始检索,未找到{2}”,str,pos,s);
break;
default:
Console.Write(“在字符串{0}中,从第{1}位起开始检索,{2}最先出现在第{3}位”, str, pos , s , result);
break;
}
Console.Read();
}
static int searchIndex(String str, String s, int pos)
{
int m = str.Length;
int n = s.Length;
if (m < n)
{
Console.Write(“要检索的目标字符串长度大于被检索字符串” + ‘\n’);
}
else if (pos > m || pos <= 0)
{
Console.Write(“检索起始位置超出被检索字符串长度” + ‘\n’);
}
else
{
int[] next = getNext(str);
int i = pos-1; //主字符串指针
int j = 0; //子字符串指针
while (i < m && j < n)
{
if (j == -1||str[i].Equals(s[j])) //匹配成功,匹配位数+1继续循环,进行下一位的匹配
{
i++;
j++;
}
else
{
j = next[j]; //匹配不成功,j进行回溯,回溯的值为这一位的回溯函数值
}
}
if (j >= n) //j>=n说明子字符串全部匹配成功,也就是说在主字符串中已经找到了子字符串
{
return i – n + 1; //返回子字符串的在主字符串中首次出现的开始位置,这个位置定义和pos一样,从1开始
}
}
return -1; //程序运行到这里返回-1,说明未找到子字符串
}
static int[] getNext(String T) //获取字符串的next数组 记录每次匹配时从哪里开始比较
{
int[] next = new int[T.Length];
next[0] = -1;
next[1] = 0; //第二个元素的回溯函数值必然是0,可以证明:
int i = 2; //要计算next值的字符的索引
int j = 0; //计算next值所需要的中间变量,每一轮迭代初始时j总为next[i-1]
while (i < T.Length)
{
if (T[i – 1] == T[j]) //迭代计算next值是从第三个元素开始的
{ //第i位字符它的前缀首位和末尾字符相等,回溯值+1
next[i] = j+1;
i++;
j++;
}
else
{ //如果不相等j就回溯,回溯的位数为第j位的回溯函数值
j = next[j];
if (j == -1) //如果j == -1表示回到首位,则第i位的回溯值就是0了
{
next[i] = j + 1;
i++;
j++;
}
}
}
return next;
}
}