以下介绍串的模式匹配,一是经典的模式匹配,就是主串移动,子串不断从头进行匹配;二是优化经典串对比的方式,即KMP算法,通过减少对比次数的方式使得时间复杂度更加少。
经典串模式匹配:子串不断从头开始,只要匹配到错误的,子串回到第一个位置,主串回到匹配前的后一个位置,重新开始模式串匹配。
如下图,S串中 i 到第三个位置时,与T串中的 j 的第三个位置不符合,则就会回归到第二个位置,即 b 的位置,再跟T串进行比对。
经典模式的思路很简单,就是通过不断从头开始比对,来对到符合子串,如果移动到符合时,就返回正确的标志。以下是以数组来定义串的。
i 回溯到原来位置+1,因为i = j,所以 i = i - j +1(i,j均从0开始)
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
int BT_algorithm(char* S, int s_len, char* P, int p_len)
{
int i = 0, j = 0;
while (i < s_len && j < p_len)
{
if (S[i] == P[j])
{
i++; j++;
}
else {
i = i - j + 2;
j = 1;
}
}
if (j >= p_len) return 1;
else return 0;
}
int main()
{
char S[] = "ababcabcacbab";
char P[] = "abcac";
int s_len = strlen(S);
int p_len = strlen(P);
if (BT_algorithm(S, s_len, P, p_len))
{
printf("匹配成功!!!\n");
}
else {
printf("匹配失败!!!\n");
}
return 0;
}
此法就是最简单的方法,也叫做暴力破解法。
接下来是对KMP算法的解析:
KMP算法是通过一个next[]来确定 i 回到的位置,经典的方法是每次都从下一个移动开始,但我们可以发现,一些重复的符号,明显串是不同的,因此没必要进行重新回溯。
KMP算法就是,S 中的 i 一直在移动,而子串 T 的 j 根据next[]函数来确定T对比的位置。
next[]函数,表明当模式中第j个字符与主串中相应字符“失配”时,在模式中需要重新和主串中该字符进行比较的字符的位置。
以下通过一个例子来进行解释:这是其所有的回溯的位置
当匹配的 j 为1时,即第一个位置匹配不成功,那么还是从第一个位置进行匹配;
当匹配的 j 为2时,即第二个位置匹配不成功,那么ab匹配错误,那么还是从第一个位置进行匹配。
当匹配的 j 为3时,即第三个位置匹配不成功,那么abc匹配错误,因为三个元素的前缀后缀不同,因此还是从第一个位置进行匹配。
当匹配的 j 为5时,即第五个位置匹配不成功,那么abcaa匹配错误,因为有前后缀a是一样的元素,因此下一次匹配从第二个位置进行匹配。
当匹配的 j 为7时,即第七个位置匹配不成功,那么abcaabb匹配错误,因为前后缀都有ab一样的两个元素,因此下一次匹配从第三个位置开始。
以此类推,就不一一进行解释了,KMP算法的手动推算方法可以看“王道” 老师的讲解,是通过两张纸进行拉就可以进行模拟了。大概的流程就如下图所示:
实现代码:
next函数:
void get_next(char* P,int p_len,int* next) {
int i = 1,j = 0;
next[1] = 0;
while (i < p_len)
{
if (j == 0 || P[i] == P[j])
{
++i; ++j;
next[i] = j;
}
else {
j = next[j];
}
}
}
主函数:
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#define MAXLEN 500
int KMP_algorithm(char* S, int s_len, char* P, int p_len,int* next)
{
int i = 0,j = 0;
while (i < s_len && j < p_len)
{
if (j == 0||S[i] == P[j])
{
i++; j++;
}
else
j = next[j];
}
if (j >= p_len)return TRUE;
else return FALSE;
}
int main()
{
char S[] = "ababcabcacbab";
char P[] = "abcac";
int s_len = strlen(S);
int p_len = strlen(P);
int next[MAXLEN] = { 0 };
get_next(P, p_len, next);
if (KMP_algorithm(S, s_len, P, p_len,nextval))
{
printf("匹配成功!!!\n");
}
else {
printf("匹配失败!!!\n");
}
return 0;
}
KMP算法还有一个优化版,就是争对如果连续出现多个相同元素时,减少没必要的对比,下面定义为naxtval[]函数,这个就不做多解释了,看代码。
void get_nextval(char* P, int p_len, int* nextval) {
int i = 1, j = 0;
nextval[1] = 0;
while (i < p_len)
{
if (j == 0 || P[i] == P[j])
{
++i; ++j;
if (P[i] != P[j]) nextval[i] = j;
else nextval[i] = nextval[j];
}
else {
j = nextval[j];
}
}
}
总结:KMP算法相对在理解时,是有很大的困难的,我理解得也不是很透彻。