目录
前言
一、BF算法
1.BF算法是什么
2.BF算法的实现
二、KMP算法
1.KMP算法是什么
2.next数组
3.代码实现
总结
例如:随着我们对字符串的不断学习和深入了解,我们会面对一座绕不开的大山——BF算法和BMP算法,本文就介绍了BF BMP算法的基础内容。
BF算法是用来查找某一字符串是不是另一字符串的子串与C语言库函数strstr()函数的功能类似
BF算法,即暴力 (Brute Force)算法,是普通的模式匹配算法,BF算法的思想就是将目标串S的第一个字符与模式串T的第一个字符进行匹配,若相等,则继续比较S的第二个字符和 T的第二个字符;若不相等,则比较S的第二个字符和T的第一个字符,依次比较下去,直到得出最后的匹配结果。——摘自百度百科
我们举一个例子
上面的字母分别是母串str和子串sub,下面的数字是两个字符串的下标。
i用来控制母串,j用来控制子串,我们需要比较str[i]与sub[j]是否相同来查找sub是不是str的母串,如果是i++,j++;然而当str[i]不等于sub[j]的时候j下标需要回到子串的起始位置,i需要回到母串的上一次查找的后一位置即i=i-j+1; j=0;这样一直重复循环直到i走到str字符串的最后一个位置,即sub不是str的子串,或者j走到了sub字符串的末尾,即sub是str的子串。
如果sub是str的子串BF会返回子串在母串的起始位置的下标,如果不是子串就会返回-1、
这种算法简单粗暴,但运算速度慢,效率低。最坏情况: 要进行M*(N-M+1)次比较
下面是我的代码
//BF算法的模拟实现
//des是主串,src是子串
//如果找到返回下标值
//没有找到返回-1
#include
#include
#include
int BF(const char* des, const char* src)
{
assert(des && src);
int lenDes = strlen(des);
int lenSrc = strlen(src);
int i = 0;
int j = 0;
while (i < lenDes && j < lenSrc)
{
if (des[i] == src[j])
{
i++;
j++;
}
else
{
i = i - j + 1;
j = 0;
}
}
if (j == lenSrc)
{
return i - j;
}
return -1;
}
int main()
{
char arr1[] = "awqefuhfnfjudjudjdj";
char arr2[] = "qef";
int ret = BF(arr1, arr2);
if (ret)
{
printf("找到了下标是 %d\n", ret);
}
else
{
printf("没有找到\n");
}
return 0;
}
KMP算法是一种改进的字符串匹配算法,由D.E.Knuth,J.H.Morris和V.R.Pratt提出的,因此人们称它为克努特—莫里斯—普拉特操作(简称KMP算法)。KMP算法的核心是利用匹配失败后的信息,尽量减少模式串与主串的匹配次数以达到快速匹配的目的。具体实现就是通过一个next()函数实现,函数本身包含了模式串的局部匹配信息。KMP算法的时间复杂度O(m+n) 。
区别KMP和BF唯一不一样的地方在于KMP算法在主串的i不会回退,字串的j也不会回退到0号位置。我们需要在i的前面找到与子串匹配的一部分。
KMP算法的精髓就是next数组,也就是用next[j]=k,来表示,不同的j来对应一个k值,这个k就是你将来移动的j要移动的位置。
而k的值是这样要求的:
1.规则:找到匹配成功部分的两个相等的真子串(不包含本身),一个以下标0开始,另一个以下标j-1结束。
2.不管什么数据next[0]=-1,next[1]=0,在这里,我们以下标开始,而说到的第几个第几个是从1开始。
我们举个例子你就会明白了。
0 1 2 3 4 5
a b c a b c 上面是下标。下面是字符串,求它的next数组,
我们j=0时next[0]=-1,j=1时next[1]=0,j=2时,我们要找出以下标是0的元素,到下标是j-1的元素组成的字符串中两个相同的子串的长度就是next数组的值。
经过以上说明,我们可以试着求一下例子中的next数组
举个例子j=4的时候,要查找0到j-1的子串,有abca, a , a三个子串,而abca不是真子串所以只有a,a这两个真子串,所以next[4]=1
0 1 2 3 4 5
-1 0 0 0 1 2
说明:next[0]可以是任意数值,但是将-1赋值给next[0]在代码的实现比较方便
next数组储存的是相等的真子串的长度
这时我们需要通过数学推导总结出公式,才能用代码来描述
p[0]....p[k-1] == p[x]....p[j-1]
p为要求出的next数组的字符串,以上公式通过next数组定义,有两个相同的真子串来表示的,x为符合条件的任意j下标,我们又通过定义可知两个真子串的长度应该是相同的所以
k-1-0==j-1-x
x==j-k;
这时我们引出一个前提next[j]=k;
所以p[0]...p[k-1] p[k]==p[x]...p[i-1] p[i]
所以nex[i]=k;
next[i+1]=k+1;
而当我们的前提不成立的时候,即next[j]!=k;时我们就需要将我们的k回退到下标k所对应的next数组的值即k=next[k];
这样我们就可以避免出现错误,细心的同学会发现当k回退到主串的起始位置时还不成立,k会回退到-1的位置,数组就越界了,我们就在写代码时要注意这个问题。
以上就是KMP算法的理论环节
#include
#include
#include
#include
void GetNext(char* sub, int* next, int lenSub)
{
next[0] = -1;
next[1] = 0;
int k = 0;//用来记录上一个k
int i = 2;
while (i < lenSub)
{
if (k==-1||sub[i-1] == sub[k])
{
next[i] = k + 1;
i++;
k++;
}
else
{
k = next[k];
}
}
}
int KMP(char* str, char* sub,int pos)
{
assert(str && sub);
int lenStr = strlen(str);
int lenSub = strlen(sub);
if (str == NULL || sub == NULL)
return -1;
if (lenStr == 0 || lenSub == 0)
return -1;
if (lenStr < lenSub)
return -1;
if (pos<0 || pos>lenStr)
return -1;
int i = pos;
int j = 0;
int* next = (int*)malloc(sizeof(int) * lenSub);
assert(next!=NULL);
GetNext(sub, next,lenSub);
while (i < lenStr && j < lenSub)
{
if (j==-1||str[i] == sub[j])
{
i++;
j++;
}
else
{
j = next[j];
}
}
if (j >= lenSub)
{
return i - j;
}
return -1;
}
int main()
{
char arr1[] = "qwedcssfghhuiughj";
char arr2[] = "huiughj";
printf("%d\n", KMP(arr1, arr2, 0));//10
return 0;
}
我们将KMP算法封装在一个函数内部,进入函数时,我们就断言,防止用户传入空指针,并且防止用户传入没有指向字符串的指针,然后按照我们上述的思路实现。
我们会注意到KMP函数内有一个j==-1的情况就是回退到第一个元素后还与字串的元素不同的·情况,i++,j++就可以把数组越界的情况解决,同时我的malloc函数没有free,这是一个无关紧要的小问题。