串匹配: 在一个串(字符串)中找存不存在另一个串(字符串),并返回找到的位置。
C库中的方法: char * strstr(const char * S, const char *P);
BF算法 朴素算法
该直接穷举算法从字符串S的每一个字符开始查找,看字符串T是否会出现。
给S和T都有一个指针,开始时S[0]与T[0]比较,若相同则继续向后比较,若出现不同,则T[0]继续与S[i]比较(i为上次出现不同时S指针的位置),直到主串结束,或者匹配成功。
例:S=“ababcabcacbab” T=“abcac”
代码实现
// O(m*n)
int BF(char *s, char *p, int pos)
{
if(s == NULL || p == NULL || pos < 0) return -1;
int lens = strlen(s);
int lenp = strlen(p);
if(lens - pos < lenp) return -1;//判断主串
int i = pos;
int j = 0;
while(i < lens && j < lenp)
{
if(s[i] == p[j])
{
i++;
j++;
}
else
{
i = i - j + 1;//回到原始位置+1
j = 0;//j回到起始位置
}
}//两个串 某一个到了结尾
if(j == lenp)//如果是j到达了结尾,那么返回的i-j就是主串中匹配合适的起始位置
return i - j;
return -1;//匹配失败
}
KMP算法
相比较起来,BF算法是一种暴力的求解方法。
如果出现这种类型的两个字符,就会出现大量的比较,使得算法效率很低
而KMP算法就解决的这类问题
首先我们需要一个叫前缀表(piefix table)的东西,将按下图所示的方法找到对应的最大公共前后缀,舍弃最后一个,且第一位下标记-1
然后继续进行比较,且是从挪动下标位置开始,即上图下标1处开始
若如果用BF算法对刚才我们举例的极端例子进行查找,会继续非常多次的比较,而用KMP算法
节省了很多次的比较,仅用七次就可以匹配完成
代码实现
在GetNext函数中,我们假如已经知道前缀表中下标为5的地方为1,而要求下标为6的地方为多少时,只需要比较对应的数字,前缀中A后面是B,所以最后缀在,在最后一个A后面加上一个B,那么6下标出的数字就是前一个2,通过最长前后缀的性质可以这样进行比较得到下一个的下标。
如果后缀A后面不是B,则根据当前len的下标,指向前一个的下标对应的前缀表,继而len指向的是下标1
然后继续进行比较,重复一开始的操作。注意上图的示例最终需要向右全部移动一格,且next[0] == -1
void GetNext(char *p, int *next)//实现前缀表
{
next[0] = -1;//0 下标的最大公共前后缀为 -1
int lenp = strlen(p);
if(lenp < 2) return;
next[1] = 0;//1 下标的最大公共前后缀为 0
int i = 1, len = 0;
//开始比较就是从两个数字的时候开始计算它的最大公共前后缀,因为一个数字必然为0,p[0]与p[1]比较
while(i + 1 < lenp)//到倒数第二个下标时结束
{
if(len == -1 || p[i] == p[len])
{
next[++i] = ++len;
}
else
{
len = next[len];
}
}
}
int KMP(char *s, char *p, int pos)
{
if(s == NULL || p == NULL || pos < 0) return -1;
int lens = strlen(s);//s 是主串
int lenp = strlen(p);
if(lens - pos < lenp || lenp == 0) return -1;
int i = pos;
int j = 0;
//next 就是前缀表(piefix table)
int *next = (int *)malloc(sizeof(int) * lenp);//申请 p 串空间大小的指针
GetNext(p, next);
while(i < lens && j < lenp)
{
if(j == -1 || s[i] == p[j])
{
i++;
j++;
}
else
{
j = next[j];
}
}
free(next);
if(j == lenp)
return i - j;
return -1;
}
推荐大家去b站看一个up叫“正月点灯笼”关于KMP算法的讲解