字符串查找问题,求解字符串的周期(KMP算法)
2016-10-25 22:27
239 查看
本总结是是个人为防止遗忘而作,不得转载和商用。
记:文本串长度为N,模式串长度为M
1,i = 0, j = 0
2,判断test[i +j] 是否等于 pattern[j]
相等则j++后继续回到第二步
不相等则j = 0,i++,直到遍历完test
因此,该算法的时间复杂度O(M*N),空间复杂度O(1)
改进在哪里呢?
BF算法在匹配失败时,要重新从pattern的第一个字母开始匹配,而KMP对这点做了改进。
为了解释这点,我们假设现在的情况是:当前字符匹配成功,即text[i]==pattern[j],令j++,继续匹配下一个字符时发现匹配失败,即:text[i] ≠pattern[j],如下图:
接下来,我们做这样的事情:文本串保持不动,模式串向后滑动一段位置。这样或许能够更快的找到模式串在文本串中的位置,如下图:
那么滑动多少呢?为了方便说明,这里把上图的第二行和第三行画成下面的样子:
上图第1行的A~B就是上上图第二行的土黄色部分,d就是P[j],而这么滑动的条件就是A=B。
可以这么做的原因是:因为A= B,所以滑动之后我只需要从pattern的c开始和test[i]进行对比就好(假设),不用从头对比了。
总之:为了能向后滑动模式串,我们需要在模式串的P[j] 前找到一个最长的前缀A和最长的后缀B,且A = B。然后我只需要对比下c和test[i]是否相等就可以,A那一块就不用再对比了。
总之:查找满足条件的最大的k,使得:p0p1 ...pk-2pk-1= pj-kpj-k+1 ...pj-2pj-1
假设在c处模式串不等于文本串,于是就找出c前面的最长且相等的前后缀,方法很简单,如图所示对比所有的前后缀,找出相等且最长的。
PS:abaab和自身是一定相等的,没意义,pass。
因为找到的最长相等前后缀的长度为2,所以next数组的c的位置就赋值为2。
PS:笔试面试中有可能只需要求next数组。
p0p1 ...pk-2pk-1= pj-kpj-k+1 ...pj-2pj-1
则,对于模式串的位臵j+1,考察pj:
若p[k]==p[j]:则next[j+1]=next[j]+1
若p[k]≠p[j]:则看下图
上图的黄色不等于灰色表示p[k]≠p[j]。
这时怎么办呢?
你看,虽然B那一块等于A那一块,但p[k]≠p[j],所以B那一块加上灰色就不等于A那一块加上黄色,那我向前考察。
对于k来说,其next[k] 已经求出来了,假设next[k]= h,这意味着k前面那一段(B那一块)有个长度为k的前缀和后缀内容一样,即1那一块 = 3那一块。
又因为B = A,所以1那一块 = 2那一块。
因此对j来说,它前面那一大段有个长度为h的前缀和后缀是相等的。
于是,既然“B+k”不等于“A+j”,他俩没戏了,那我看看“1+蓝色”是否等于“2+黄色”?
如果相等,则next[j+1]= h+1
如果不相等,那就重复上面的过程,看看next[h]。
voidGetNext(char *p, int next[])
{
intnLen = (int)strlen(p);
next[0]= -1;
intk = -1;
intj = 0;
while(j < nLen - 1 ){
//此刻,k即next[j-1],且p[k]表示前缀,p[j]表示后缀
//注:k == -1表示未找到k前缀与k后缀相等,首次分析可先忽略
if( k == -1 || p[j] == p[k] ) {
++j;
++k;
next[j]= k;
}else { // p[j] 与 p[k] 失配,继续递归计算前缀p[next[k]]
k= next[k];
}
}
}
若next[j]=k,说明模式串应该从j滑动到k位置;
但若此时满足pattern[j]==pattern[k],因为text[i]≠pattern[j],所以text[i]≠pattern[k],即i和k没有匹配,应该继续滑动到next[k]。
换句话说:在原始的next数组中,若next[j]=k并且pattern[j]==pattern[k],next[j]可以直接等于next[k],即:由滑动到k位置改为直接滑动到next[k]位置。
以最后的a举例,之前得出的值使原始next中所示的值P[8]=2,但是在改进之后,因为T[P[8]]= T[8],所以P[8] = P[2],而P[2] 在求得时候同理它是等于p[0] 的,所以新next里p[8] = p[2] = p[0] = -1。
PS:如果模式串以1开始数,则原始next和新next中的所有值都要加1。因此在面试时需要先问主考官:你问我的KMP是不是改进版本的KMP,模式串是从0开始数的还是从1开始数的。
如T[12]的next[12] = 9,而T[12]的12 - 9 = 3,3是可以被12整除的,所以3就是T的最小周期长度。
PS:这里的next串是原始next。
题目
给定文本串text和模式串pattern,从text中找出pattern第一次出现的位置。记:文本串长度为N,模式串长度为M
暴力求解(BF)
这个简单。1,i = 0, j = 0
2,判断test[i +j] 是否等于 pattern[j]
相等则j++后继续回到第二步
不相等则j = 0,i++,直到遍历完test
因此,该算法的时间复杂度O(M*N),空间复杂度O(1)
KMP算法
KMP算法是一种线性时间复杂度的字符串匹配算法,它是对BF算法改进。Tip: KMP算法的时间复杂度O(M+N),空间复杂度O(M) |
BF算法在匹配失败时,要重新从pattern的第一个字母开始匹配,而KMP对这点做了改进。
为了解释这点,我们假设现在的情况是:当前字符匹配成功,即text[i]==pattern[j],令j++,继续匹配下一个字符时发现匹配失败,即:text[i] ≠pattern[j],如下图:
上图的第一行:文本串 上图的第二行:模式串 我们的目的是查找模式串在文本串中第一次出现的位置,而当前的状态是:文本串和模式串中土黄色的部分已经完全一样,但文本串中黄色部分的字符T[i]不等于模式串中绿色部分的字符P[j],即:T[i+j] != P[j]。 |
那么滑动多少呢?为了方便说明,这里把上图的第二行和第三行画成下面的样子:
上图第1行的A~B就是上上图第二行的土黄色部分,d就是P[j],而这么滑动的条件就是A=B。
可以这么做的原因是:因为A= B,所以滑动之后我只需要从pattern的c开始和test[i]进行对比就好(假设),不用从头对比了。
总之:为了能向后滑动模式串,我们需要在模式串的P[j] 前找到一个最长的前缀A和最长的后缀B,且A = B。然后我只需要对比下c和test[i]是否相等就可以,A那一块就不用再对比了。
总之:查找满足条件的最大的k,使得:p0p1 ...pk-2pk-1= pj-kpj-k+1 ...pj-2pj-1
Next数组
假设在c处模式串不等于文本串,于是就找出c前面的最长且相等的前后缀,方法很简单,如图所示对比所有的前后缀,找出相等且最长的。
PS:abaab和自身是一定相等的,没意义,pass。
因为找到的最长相等前后缀的长度为2,所以next数组的c的位置就赋值为2。
PS:笔试面试中有可能只需要求next数组。
如何求Next数组
于是,对于模式串的位置j,有next[j]=k,即:p0p1 ...pk-2pk-1= pj-kpj-k+1 ...pj-2pj-1
则,对于模式串的位臵j+1,考察pj:
若p[k]==p[j]:则next[j+1]=next[j]+1
若p[k]≠p[j]:则看下图
上图的黄色不等于灰色表示p[k]≠p[j]。
这时怎么办呢?
你看,虽然B那一块等于A那一块,但p[k]≠p[j],所以B那一块加上灰色就不等于A那一块加上黄色,那我向前考察。
对于k来说,其next[k] 已经求出来了,假设next[k]= h,这意味着k前面那一段(B那一块)有个长度为k的前缀和后缀内容一样,即1那一块 = 3那一块。
又因为B = A,所以1那一块 = 2那一块。
因此对j来说,它前面那一大段有个长度为h的前缀和后缀是相等的。
于是,既然“B+k”不等于“A+j”,他俩没戏了,那我看看“1+蓝色”是否等于“2+黄色”?
如果相等,则next[j+1]= h+1
如果不相等,那就重复上面的过程,看看next[h]。
代码
综上,计算Next数组的代码如下:voidGetNext(char *p, int next[])
{
intnLen = (int)strlen(p);
next[0]= -1;
intk = -1;
intj = 0;
while(j < nLen - 1 ){
//此刻,k即next[j-1],且p[k]表示前缀,p[j]表示后缀
//注:k == -1表示未找到k前缀与k后缀相等,首次分析可先忽略
if( k == -1 || p[j] == p[k] ) {
++j;
++k;
next[j]= k;
}else { // p[j] 与 p[k] 失配,继续递归计算前缀p[next[k]]
k= next[k];
}
}
}
进一步分析Next数组
文本串匹配到i,模式串匹配到j,此刻,若text[i]≠pattern[j],即失配的情况:若next[j]=k,说明模式串应该从j滑动到k位置;
但若此时满足pattern[j]==pattern[k],因为text[i]≠pattern[j],所以text[i]≠pattern[k],即i和k没有匹配,应该继续滑动到next[k]。
换句话说:在原始的next数组中,若next[j]=k并且pattern[j]==pattern[k],next[j]可以直接等于next[k],即:由滑动到k位置改为直接滑动到next[k]位置。
变种的next数组
于是,我们可以在做另一种next数组,如下:以最后的a举例,之前得出的值使原始next中所示的值P[8]=2,但是在改进之后,因为T[P[8]]= T[8],所以P[8] = P[2],而P[2] 在求得时候同理它是等于p[0] 的,所以新next里p[8] = p[2] = p[0] = -1。
PS:如果模式串以1开始数,则原始next和新next中的所有值都要加1。因此在面试时需要先问主考官:你问我的KMP是不是改进版本的KMP,模式串是从0开始数的还是从1开始数的。
KMP可以求解字符串的周期
如T[12]的next[12] = 9,而T[12]的12 - 9 = 3,3是可以被12整除的,所以3就是T的最小周期长度。
PS:这里的next串是原始next。
相关文章推荐
- 从给定的文本中,查找其中最长的重复子字符串的问题
- 经典考题——无重复字符问题(查找字符串中第一个无重复字符)
- 关于动态存储分配函数的调用,在已经过排序的数组中查找及删除内容的操作,余数的分析,删除字符数组中的空格,对链表的逆置,在源字符串中查找子字符串的个数,函数指针以及函数的调用,循环赋值带来的问题以及插入
- 程序员编程艺术:第二章、字符串是否包含及匹配/查找/转换/拷贝问题
- 查找字符串问题
- 字符串查找匹配问题
- 有关字符串查找的问题
- SQL Server 中查找字符串在另一字符串中的索引位置问题
- KMP算法应用------求解一个字符串的最长重复子串
- HDU1358 字符串求周期 KMP算法
- 字符串查找算法之(一)KMP算法
- 程序员编程艺术:第二章、字符串是否包含及匹配/查找/转换/拷贝问题
- 编程之美3.1——字符串移位包含的问题(KMP算法)
- linux下查找字符串&mysql-为magento性能测试修改innodb的innodb_buffer_pool_size而引发的问题
- 动态规划法求解距离字符串问题
- 程序员编程艺术:第二章、字符串是否包含及匹配/查找/转换/拷贝问题
- 字符串周期 KMP求解
- 字符串包含问题的求解(最简单的方法——只有两行代码)
- 程序员编程艺术:第二章、字符串是否包含及匹配/查找/转换/拷贝问题
- (转)从给定的文本中,查找其中最长的重复子字符串的问题