KMP算法C代码实现
2016-04-14 10:51
477 查看
一、初识KMP
理解KMP算法需要关注2个问题:(请注意:字符串下标从0开始。)
当i指针与j指针失配时:
1、当母串和模式串不匹配时,i指针为什么不需要回溯?
2、当母串和模式串不匹配时,i指针不回溯,那么j指针应该移动到哪?
通过解释第1个问题,我来引出第2个问题。(其实第1个问题,你不明白,不影响你研究第2个问题,你也可以理解了第2个问题后来分析第1个问题。)
对于母串S和模式串T,如果母串的指针i处与模式串的j处失配,假设母串存在回溯i到back(i) (匹配前的i < back(i) < i),有与模式串的从0开始的模式串有一段一样长的“段落”,这个母串的段落只有是S[back(i)] 至S[i-1]才有意义,如果从S[back(i)] 至S[i-1]的过程中就已经不匹配了,那回溯至这个back(i)就更没有必要了。 即S[back(i) ...i-1]=T[0...x] ,x = i-1-back(i)。又因为前面失配时存在同样长度的段落,即S[back[i]...i-1]
= T[ j -1-x... j-1],所以T[0...x] = T[j-1-x ...j-1]。
这其实就转化成了i不变,j指针移动到x+1(后面讨论用next[j] 表示 x +1 )的问题,即问题2。
你可以用back(i) = i - 1 - x = i - next[j]来验证我的分析,注意我的字符串的下标是从0开始的。
我们来讨论问题2:
当 i与j 不匹配的时候,图1所示,因为有k < j 并且 Si-j... Si-1 = T0...Tj-1 ,所以Si-k...Si-1 = Tj-k...Tj-1。
由于S的i不回溯,图2所示,且需要与T的k指针比较,则Si-k...Si-1 = T0...Tk-1。所以T0...Tk-1 = Tj-k...Tj-1。
令next[j] = k 表示当模式串的j与主串相应字符i不匹配时,在模式中需重新与主串中该字符i比较的字符的位置。则next[j]表示为:
那我们如何求取next[j]呢? 用递推的方式,即通过已知的next[0] = -1, 来推出后面如next[1]、next[2]、next[3]... 的值。最关键的是找出next[j + 1] 与next[j]的关系。
所以我们来研究next[j + 1] 与 next[j]的关系。(当然我们的前提是next[j] = k,不要忘记哦)
因为 next[j] = k,这意味着T0...Tk-1= Tj-k ...Tj-1 (0 < k < j,并且k是满足这个等式的max(k)。)
那此时next[j + 1] 等于多少呢?
分两种情况:
1、如 Tk = Tj,则T0...Tk-1Tk = Tj-k...Tj-1Tj 。这表示next[j + 1] = k + 1,又由于k = next[j] ,所以next[j + 1] = next[j] + 1 ,故根据next[j]可求得next[j+1]。
2、较为难理解的是Tk ≠ Tj 。此时显然T0...Tk-1Tk ≠ Tj-k...Tj-1Tj 。如果我们把###Tj-k...Tj-1Tj###
表示母串,以T0...Tk-1Tk###表示模式串。显然当Tj与Tk失配的时候,Tj应该与模式串的k'=next[k] 来匹配。此时应该有T0...Tk'-1 = Tk-k'...Tk-1=Tj-k'...Tj-1。(1 < k' < k < j)。 注意,此时我们需要关注Tk‘与Tj的关系,关注Tk'与Tk的关系对于解决next[j+1]没有用处!
此时也应该分两种情况,即
1)、如果Tk‘= Tj ,所以T0...Tk'-1Tk' = Tj-k'...Tj-1Tj 。所以next[j + 1] = k' + 1,由于k' = next[k],所以next[j + 1] = next[k] + 1,故根据next[k]可求得next[j+1]。
2)、如果Tk' ≠ Tj,我们就要寻找更小的k''来匹配Tj。这当然也是有两种情况,即Tk'' = Tj 或Tk"≠ Tj,对于前者,next[j + 1] = k" +
1 = next[k'] + 1,寻找结束;对于后者,继续寻找更小的k'''、k'''',当然寻找不能无限制下去,结束的条件就是next[0] = -1 或你提前找到了你的k ?(如果你找到了你的k?,显然next[j + 1]
= k?+ 1,寻找结束)。
现在来分析说明get_next函数(见代码实现部分)的正确性。
我们需要证明在next[j]==k的基础上得到next[j+1]=(k/next[k]/.../-1之一) + 1 ,考察字符串abaabcac,下标从0开始,规定next[0]=-1
当j=0, k=-1,满足next[j]=k,因为k=-1,我们可以得到next[j+1]=k+1, 即next[1]=0;
当j=1, k=0, 满足next[j]=k,因为T[j] ≠T[k],但next[k]=-1,我们可以得到next[j+1]=next[k]+1, 即next[2]=0;
当j=2, k=0, 满足next[j]=k,因为T[j]=T[k], 我们可以得到next[j+1]=k+1, 即next[3]=1;
当j=3, k=1, 满足next[j]=k,因为T[j]≠T[k],但T[j]=T[next[k]], 我们可以得到next[j+1] = next[k]+1, 即next[2]=1;
由前面next[j] = k ,我们根据get_next函数可以证明一般性结论next[j+1] = (k/next[k]/.../-1之一) + 1。
二 、代码实现
[cpp] view
plain copy
#include <stdio.h>
#include <string.h>
int next[32] = {-999};
/* 返回模式串T在母串S中第pos个字符的位置 */
/* 调试小技巧 print x = value 或 set var x = value 可以改变gdb运行时变量的值 */
int index_BM(char *S, char *T, int pos)
{
int i;
int j;
i = pos;
j = 0;
while ( (i < strlen(S)) && (j < strlen(T)) )
{
if (S[i] == T[j])
{
i++;
j++;
}
else
{
i = i - j + 1;
j = 0;
}
}
/* 注意strlen(T)意味着j的取值范围为0 ~ (strlen(T) - 1) */
if (strlen(T) == j)
{
return i - strlen(T);
}
else
{
return -1;
}
}
void get_next(char *T, int *next)
{
int k = -1;
int j = 0;
next[j] = k;
while (j < strlen(T))
{
if ( (k == -1) || (T[j] == T[k]) ) //注意等号是==,而不是=
{
++k; // 注意是先加后使用
++j;
next[j] = k;
}
else
{
k = next[k];
}
}
}
int index_KMP(char *S, char *T, int pos)
{
int i;
int j;
i = pos;
j = 0;
while ( (i < strlen(S)) && (j < strlen(T)) )
{
/* j = -1 表示next[0], 说明失配处在模式串T的第0个字符。所以这里特殊处理,然后令i+1和j+1。*/
if ( (j == -1) || S[i] == T[j])
{
i++;
j++;
}
else
{
j = next[j];
}
}
if (strlen(T) == j)
{
return i - strlen(T);
}
else
{
return -1;
}
}
void print_next(int next[], int n)
{
int i;
for (i = 0; i < n; i++)
{
printf("next[%d] = %d\n", i, next[i]);
}
}
int main(void)
{
char *s = "ababcabcacbab";
char *t = "abcac";
int pos = 0;
int index;
printf("================ BM ==============\n");
index = index_BM(s, t, pos);
printf("index = %d\n", index);
printf("================ KMP ==============\n");
get_next(t, next);
print_next(next, strlen(t));
index = index_KMP(s, t, pos);
printf("index = %d\n", index);
}
理解KMP算法需要关注2个问题:(请注意:字符串下标从0开始。)
当i指针与j指针失配时:
1、当母串和模式串不匹配时,i指针为什么不需要回溯?
2、当母串和模式串不匹配时,i指针不回溯,那么j指针应该移动到哪?
通过解释第1个问题,我来引出第2个问题。(其实第1个问题,你不明白,不影响你研究第2个问题,你也可以理解了第2个问题后来分析第1个问题。)
对于母串S和模式串T,如果母串的指针i处与模式串的j处失配,假设母串存在回溯i到back(i) (匹配前的i < back(i) < i),有与模式串的从0开始的模式串有一段一样长的“段落”,这个母串的段落只有是S[back(i)] 至S[i-1]才有意义,如果从S[back(i)] 至S[i-1]的过程中就已经不匹配了,那回溯至这个back(i)就更没有必要了。 即S[back(i) ...i-1]=T[0...x] ,x = i-1-back(i)。又因为前面失配时存在同样长度的段落,即S[back[i]...i-1]
= T[ j -1-x... j-1],所以T[0...x] = T[j-1-x ...j-1]。
这其实就转化成了i不变,j指针移动到x+1(后面讨论用next[j] 表示 x +1 )的问题,即问题2。
你可以用back(i) = i - 1 - x = i - next[j]来验证我的分析,注意我的字符串的下标是从0开始的。
我们来讨论问题2:
当 i与j 不匹配的时候,图1所示,因为有k < j 并且 Si-j... Si-1 = T0...Tj-1 ,所以Si-k...Si-1 = Tj-k...Tj-1。
由于S的i不回溯,图2所示,且需要与T的k指针比较,则Si-k...Si-1 = T0...Tk-1。所以T0...Tk-1 = Tj-k...Tj-1。
令next[j] = k 表示当模式串的j与主串相应字符i不匹配时,在模式中需重新与主串中该字符i比较的字符的位置。则next[j]表示为:
那我们如何求取next[j]呢? 用递推的方式,即通过已知的next[0] = -1, 来推出后面如next[1]、next[2]、next[3]... 的值。最关键的是找出next[j + 1] 与next[j]的关系。
所以我们来研究next[j + 1] 与 next[j]的关系。(当然我们的前提是next[j] = k,不要忘记哦)
因为 next[j] = k,这意味着T0...Tk-1= Tj-k ...Tj-1 (0 < k < j,并且k是满足这个等式的max(k)。)
那此时next[j + 1] 等于多少呢?
分两种情况:
1、如 Tk = Tj,则T0...Tk-1Tk = Tj-k...Tj-1Tj 。这表示next[j + 1] = k + 1,又由于k = next[j] ,所以next[j + 1] = next[j] + 1 ,故根据next[j]可求得next[j+1]。
2、较为难理解的是Tk ≠ Tj 。此时显然T0...Tk-1Tk ≠ Tj-k...Tj-1Tj 。如果我们把###Tj-k...Tj-1Tj###
表示母串,以T0...Tk-1Tk###表示模式串。显然当Tj与Tk失配的时候,Tj应该与模式串的k'=next[k] 来匹配。此时应该有T0...Tk'-1 = Tk-k'...Tk-1=Tj-k'...Tj-1。(1 < k' < k < j)。 注意,此时我们需要关注Tk‘与Tj的关系,关注Tk'与Tk的关系对于解决next[j+1]没有用处!
此时也应该分两种情况,即
1)、如果Tk‘= Tj ,所以T0...Tk'-1Tk' = Tj-k'...Tj-1Tj 。所以next[j + 1] = k' + 1,由于k' = next[k],所以next[j + 1] = next[k] + 1,故根据next[k]可求得next[j+1]。
2)、如果Tk' ≠ Tj,我们就要寻找更小的k''来匹配Tj。这当然也是有两种情况,即Tk'' = Tj 或Tk"≠ Tj,对于前者,next[j + 1] = k" +
1 = next[k'] + 1,寻找结束;对于后者,继续寻找更小的k'''、k'''',当然寻找不能无限制下去,结束的条件就是next[0] = -1 或你提前找到了你的k ?(如果你找到了你的k?,显然next[j + 1]
= k?+ 1,寻找结束)。
现在来分析说明get_next函数(见代码实现部分)的正确性。
我们需要证明在next[j]==k的基础上得到next[j+1]=(k/next[k]/.../-1之一) + 1 ,考察字符串abaabcac,下标从0开始,规定next[0]=-1
当j=0, k=-1,满足next[j]=k,因为k=-1,我们可以得到next[j+1]=k+1, 即next[1]=0;
当j=1, k=0, 满足next[j]=k,因为T[j] ≠T[k],但next[k]=-1,我们可以得到next[j+1]=next[k]+1, 即next[2]=0;
当j=2, k=0, 满足next[j]=k,因为T[j]=T[k], 我们可以得到next[j+1]=k+1, 即next[3]=1;
当j=3, k=1, 满足next[j]=k,因为T[j]≠T[k],但T[j]=T[next[k]], 我们可以得到next[j+1] = next[k]+1, 即next[2]=1;
由前面next[j] = k ,我们根据get_next函数可以证明一般性结论next[j+1] = (k/next[k]/.../-1之一) + 1。
二 、代码实现
[cpp] view
plain copy
#include <stdio.h>
#include <string.h>
int next[32] = {-999};
/* 返回模式串T在母串S中第pos个字符的位置 */
/* 调试小技巧 print x = value 或 set var x = value 可以改变gdb运行时变量的值 */
int index_BM(char *S, char *T, int pos)
{
int i;
int j;
i = pos;
j = 0;
while ( (i < strlen(S)) && (j < strlen(T)) )
{
if (S[i] == T[j])
{
i++;
j++;
}
else
{
i = i - j + 1;
j = 0;
}
}
/* 注意strlen(T)意味着j的取值范围为0 ~ (strlen(T) - 1) */
if (strlen(T) == j)
{
return i - strlen(T);
}
else
{
return -1;
}
}
void get_next(char *T, int *next)
{
int k = -1;
int j = 0;
next[j] = k;
while (j < strlen(T))
{
if ( (k == -1) || (T[j] == T[k]) ) //注意等号是==,而不是=
{
++k; // 注意是先加后使用
++j;
next[j] = k;
}
else
{
k = next[k];
}
}
}
int index_KMP(char *S, char *T, int pos)
{
int i;
int j;
i = pos;
j = 0;
while ( (i < strlen(S)) && (j < strlen(T)) )
{
/* j = -1 表示next[0], 说明失配处在模式串T的第0个字符。所以这里特殊处理,然后令i+1和j+1。*/
if ( (j == -1) || S[i] == T[j])
{
i++;
j++;
}
else
{
j = next[j];
}
}
if (strlen(T) == j)
{
return i - strlen(T);
}
else
{
return -1;
}
}
void print_next(int next[], int n)
{
int i;
for (i = 0; i < n; i++)
{
printf("next[%d] = %d\n", i, next[i]);
}
}
int main(void)
{
char *s = "ababcabcacbab";
char *t = "abcac";
int pos = 0;
int index;
printf("================ BM ==============\n");
index = index_BM(s, t, pos);
printf("index = %d\n", index);
printf("================ KMP ==============\n");
get_next(t, next);
print_next(next, strlen(t));
index = index_KMP(s, t, pos);
printf("index = %d\n", index);
}
相关文章推荐
- 一、Android Studio入门——Eclipse快捷键配置
- MyBatis-Spring-SqlSessionFactoryBean
- 解决java内存溢出最佳配置
- 【SSM-SpringMVC框架】非注解的处理器适配器和映射器
- 仿百度搜索代码
- php 本地配置根目录
- c++ 计时
- JAVA抽象类与接口
- Spring单例与线程安全小结
- 转spring aop实现业务层mysql 读写分离
- Atitit. 注册表操作查询 修改 api与工具总结 java c# php js python 病毒木马的原理
- Atitit. 注册表操作查询 修改 api与工具总结 java c# php js python 病毒木马的原理
- Atitit. 注册表操作查询 修改 api与工具总结 java c# php js python 病毒木马的原理
- 01背包代码
- Github+Jekyll —— 创建个人免费博客(四)jekyll第一个页面
- numpy学习笔记一:numpy的基本用法
- QTableWidgetItem 按数字排序
- Spring AOP
- Eclipse 调试时,出现错误闪退,但是控制台没有打印错误信息
- java多线程之Condition实现更高效线程通信