简介
初学KMP,在网络上找到的字符串基本都是从数组下标为1的位置开始的,因此来补充一个下标从0开始的字符串的KMP算法的C语言实现。
头文件见上篇博客,即C语言字符串基本操作
代码展示
#include<stdio.h>
#include<stdlib.h>
#include"StringForC.h"
//KMP算法
void SolveNext(String Son , int next[]);
int KMP(String Father , String Son , int next[] , int StartPosition);
int main()
{
String Father , Son;
if(CreateString(&Father) && CreateString(&Son))
{
int *next = (int *)malloc(sizeof(int) * Son.Length);
SolveNext(Son , next);
int Position;
Position = KMP(Father , Son , next , 0);
printf("%d" , Position);
}
return 0;
}
// 求解next数组
void SolveNext(String Son , int next[])
{
int j = 0;
int k = -1;
next[0] = -1; // 说明主串从下一个字符开始和模式串重新匹配
while(j < Son.Length)
{
if(k == -1 || Son.Value[j] == Son.Value[k]) //如果是重新匹配或字符匹配成功
{
next[++j] = ++k;
}
else // 如果匹配失败,则把求值问题看作一个模式匹配问题
{
k = next[k];
}
}
}
int KMP(String Father , String Son , int next[] , int StartPosition)
{
int ReturnPosition;
int i = StartPosition;
int j = -1;
int Flag = 1; // 作为开始匹配的标记
while(i < Father.Length && j < Son.Length)
{
if(Flag)
{
j++;
Flag = 0;
}
else if(j == -1 || Father.Value[i] == Son.Value[j])
{
i++;
j++;
}
else // 如果匹配失败,模式串回退
{
j = next[j];
}
}
if(j >= Son.Length) // 如果模式串全部匹配完毕
ReturnPosition = i - Son.Length;
else
ReturnPosition = -1; // 如果未能搜索到
return ReturnPosition;
}
算法阐述
对于字符串匹配问题,回溯匹配算法的时间复杂度为O(nm),而KMP算法的时间复杂度为O(n+m)。
直白的说,KMP算法的核心问题就是求解next函数的值,next函数的抽象化表示我就不做赘述了,描述的肯定没有各位大佬清晰,只是说说
我个人的理解:
由于KMP算法主串不回溯,只有模式串回溯,并且回溯长度视情况而定,而这个情况,分为三种,第一种,如果主串和模式串第一个字符就不匹配的情况下,此时next的函数值设置为一个标记值,表示模式串不回退,第二种,如果模式串不是在第一个字符就失配,那么就要进行讨论,模式串需要回溯多少:
我们来看一个例子:
主串: ababaf
模式串:abaf
进行字符串匹配,首先aba三个字符均匹配成功,第四个字符b与f不匹配,此时再进行观察,发现失配字符前的字符串有一个特征:a字符重复了,此时如果仍然将模式串指针回退至首位,会造成信息 (即a)浪费,因此,我们希望能保留这个a,即将模式串指针回退到b,以模式串第二位(b)去对比主串的失配字符(b)。
那么问题来了:如何判断需要模式串需要回退多少?
这就是KMP算法的核心,即next函数,通过寻找模式串中相同的前后 缀的字符数目的最大值,来确定next函数的值,仍然继续看这两个字符串:
主串: ababaf
模式串:abaf
模式串中,aba具有这一特征,以b为中心,前缀和后缀都是a,我们需要的是回退到b,即回退到第2个字符,而前后缀重复的字符数是1,因此,next的值为重复字符数+1,即2,而next值记录的是失配是回退的位置,因此当 j (失配位置)为4时,next的值为2,以此类推:
j = 1 : next = 0(标记值)
j = 2 : next = 0+1 = 1
j = 3 : next = 0+1 = 1
j = 4 : next = 1+1 = 2
这就确定了next的值。
代码解析
- 在C语言中求解next的值:
next作为整型数组出现,长度与模式串长度相同,这里需要做一下改变,由于数组和字符串第一位都是0,因此标记值应为-1,模式串前后缀无重复数值时的值应为-1,加一后为0,退到首位。
// 求解next数组
void SolveNext(String Son , int next[])
{
int j = 0;
int k = -1;
next[0] = -1; // 说明主串从下一个字符开始和模式串重新匹配
while(j < Son.Length)
{
if(k == -1 || Son.Value[j] == Son.Value[k]) //如果是重新匹配或字符匹配成功
{
next[++j] = ++k;
}
else // 如果匹配失败,则把求值问题看作一个模式匹配问题
{
k = next[k];
}
}
}
- 进行主串与模式串匹配:
匹配时遇到的问题就是如果从头开始就失配,则会时主串指针移动到第二位,导致匹配出错,因此设计一个标记变量,作为开头匹配和第一个字符即失配的区别
int KMP(String Father , String Son , int next[] , int StartPosition)
{
int ReturnPosition;
int i = StartPosition;
int j = -1;
int Flag = 1; // 作为开始匹配的标记
while(i < Father.Length && j < Son.Length)
{
if(Flag)
{
j++;
Flag = 0;
}
else if(j == -1 || Father.Value[i] == Son.Value[j])
{
i++;
j++;
}
else // 如果匹配失败,模式串回退
{
j = next[j];
}
}
if(j >= Son.Length) // 如果模式串全部匹配完毕
ReturnPosition = i - Son.Length;
else
ReturnPosition = -1; // 如果未能搜索到
return ReturnPosition;
}
初学者,若有错误,欢迎指正