串匹配
在主串中查找子串的位置
主串S
子串P
BF算法
定义两个控制变量i和j,i初始化在主串S串的0号位置,j初始化在子串P串的0号位置。
- 若比较得出该位置主串和子串的元素相等,那么i++,j++;
- 若比较得出该位置主串和子串的元素不相等,i回退到本次比较开始的下一个位置,j回退到0位置,再开始比较;(这里隐含了一种穷举的思想:相当于S串以每一个字符作为开始,和P串进行比较,直到找到为止)
效率分析
时间复杂度O(m*n)
空间复杂度O(1)
int BF(const char *str,const char *sub,int pos)
{
int len1 = strlen(str);
int len2 = strlen(sub);
int i = pos;
int j =0;
if(str == NULL || sub == NULL || pos < 0 || pos >= len1)
{
return -1;
}
while(i < len1 && j < len2)//主串未遍历完
{
if(str[i] == sub[j])
{
i++;
j++;
}
else
{
//i回退到本次开始(i-j)的下一个位置,j退到0
i= i-j+1;
j = 0;
}
}
if(j == len2)//子串已遍历完
return i - j;
else
return -1;
}
KMP算法
前情摘要:
BF算法:当比较出现失配时,
对于主串S,i回退到上一次比较的下一个位置,即i = i - j + 1
对于子串P,j回到0位置,即j = 0
重点要学会理解算法的思想,其实代码实现并不难。
进入正题:
KMP的基本思想:
该算法相对于 Brute-Force(暴力)算法有比较大的改进,主要是消除了主串指针的回溯,从而使算法效率有了某种程度的提高。
对于每个子串 P 的每个元素 Pj,都存在一个实数 k ,使得子串 P 开头的 k 个字符(P 0 P 1…Pk-1)
依次与 P j 前面的 k(P j-k P j-k+1…P j-1,这里第一个字符 P j-k 最多从 P1 开始,所以 k < j
)个字符相同。如果这样的 k 有多个,则取最大的一个。子串 P 中每个位置 j 的字符都有这种信息,采用 next 数组表示,即 next[ j ]=MAX{ k }。
简单理解
在子串P中找k位置(失配时j应该回退的位置)的方法:
在P串中,每个位置之前,找两个完全相等的最长真子串,其中前一个子串以0位置开始,后一个子串以当前位置-1结束,则当前位置的k值就是子串的长度
,就是j失配的回退值
。
存储k值:next数组
大小应该和P串的长度相等
> next[0] = -1;
> next[1] = 0;
其实,在网上有很多教程的next数组的前两位设定和我们所写的不太一样
> next[0] = 0;
> next[1] = 1;
我们来讨论一下:
例如,对于下面的例子:
a b c a b a b c a b c
k值 —— -1 0 0 0 1 2 1 2 3 4 5
c a b c......
在第一个位置就相当于失配了,那么在指针回溯时,子串P的next[j] = 0,还是当前位置,会进入一个死循环,因此,此时唯一地解决方法就是让主串指针i++;,而这与KMP的思想相悖,因为我们并不想操作和移动主串的指针。
当我们处理这种情况时,可以考虑同步步进,子串p回溯到-1后再++;还是跑到0位置,而主串的指针i也++,这样就可以避免死循环了,将j == -1的情况写到str[i] == sub[j]的情况中去,也可以简化我们代码的书写。
实现如下:
int KMP(const char *str,const char *sub,int pos)
{
int len1 = strlen(str);
int len2 = strlen(sub);
int i = pos;
int j =0;
if(str == NULL || sub == NULL || pos < 0 || pos >= len1)
{
return -1;
}
//p串的next数组
int *next = (int*)malloc(len2 * sizeof(int));
assert(next != NULL);
GetNext(sub,next);
while(i < len1 && j < len2)
{
//j == -1必须放在最前面,若把这个条件写到else内,则程序跑到这里会崩溃
if( j == -1 || str[i] == sub[j])
{
i++;
j++;
}
else
{
j = next[j];
}
}
if(j >= len2)
return i - j;
else
return -1;
}
下面的重点就是,如何通过算法实现GetNext(sub,next)
基本思想:
求i-+1位置的next[i+1]就是它和next[i]的关系,可以简单理解为寻求一个函数关系:
next[i+1] = F(next[i])
假设已知:next[i] = k
则:P0…Pk-1 == Pi-k…Pi-1
那么对于Pk和Pi来说,它们有什么关系呢?
分以下两种情况讨论:
- Pk == Pi
- Pk != Pi
第①种情况:
可以推出:
P0…Pk == Pi-k…Pi
串的长度是k+1,即next[i+1] = k+1
———————————————————
第②种情况:
k = next[k];
void GetNext(const char *sub,int *next)
{
int len =strlen(sub);
next[0] = -1;
next[1] = 0;
int i = 1;
int k = 0;
while(i + 1 < len)
{
if(k == -1 || sub[k] == sub[i])
{
next[++i] = ++k;
//next[i+1] = k+1;
//k++;
//i++;
}
else
{
//KMP的思想
k = next[k];
}
}
}
今天就这么多!