6.0 KiB
6.0 KiB
原创
: KMP算法详解
KMP算法详解
要求:对一个串中某个子串进行定位操作,返回匹配到的串的起始位置
假设所有串的起始字符索引为1
数据结构定义
typedef struct
{
char *ch;
int length;
}Str;
一、简单模式匹配
//指向原串的索引i需要回溯,从原串中的每一个字符重新进行匹配,直到匹配成功
int index(Str str,Str substr)
{
//i,j分别为原串和模式串的索引
//k记录匹配时上一次的起始位置
int i=1,j=1,k=i;
while(i<=str.length&&j<=str.length)
{
if(str.ch[i]==substr.ch[j])++i,++j;
//匹配失败则回溯到上一次开始匹配的位置加1
else j=1,i=++k; //i的回溯
}
if(j>substr.length) return k;//返回匹配成功索引
else return 0;//没有匹配到
}
二、KMP算法
1.思路
- 主体思路:
1)i 不用进行回溯,当原串和模式串不发生匹配时,先找出模式串中的不匹配字符pj,取其模式串的子串F=p1p2p3…pj-1,找出F的前部分FL和后部分FR最先发生相重合的位置,将模式串后移到该位置,即j重新指向的位置是F串中前后重合的子串长度加1;
2) 我们可以定义一个next[j]数组表示模式串中第j个字符不发生匹配时,应该从next[j]处的字符重新与原串比较。- 特殊情况:
1)模式串的第一个字符与原串就不匹配,则从原串的下一个位置同模式串进行匹配;
2)当串F中不存在前后重合的部分,则从原串中不发生匹配的字符同模式串的第一个字符开始比较;- 求next[j]数组例子
模式串 | A | B | A | B | A | B | B | ||||||||
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
j | 1 | 2 | 3 | 4 | 5 | 6 | 7 | next[j] | 0 | 1 | 1 | 2 | 3 | 4 | 5 |
将上述描述转换成简洁的代码描述:
//t=next[j]
//这一点是KMP的核心,仔细琢磨
if(pj=pt)next[j+1]=t+1;
else t=next[t];
2.next[ j ]数组代码实现
void getnext(Str substr, int next[])
{
int i=1,j=0;
//对next[1]进行初始化,即next[i]=j,这一点也很重要
next[1]=0;
while(i<substr.length)
{
//模式串匹配
//如果pi=pj,则,next[i+1]=j+1
//如果不匹配,则j=next[j];
if(j==0||substr.ch[i]==substr[j])next[++i]=++j;
else j=next[j];
}
}
3.著名的KMP算法
int KMP(Str str, Str substr, int next[])
{
int i=1,j=1;
while(i<=str.length&&j<=str.length)
{
if(j==0||str.ch[i]==substr.ch[j])++i,++j;
else j=next[j];
//没有了i的回溯,这是KMP算法的精髓
//充分利用了模式串的重复性
//即使不存在重复字段,在比较时,实现最大的移动量
}
if(j>substr.length)return i-substr.length;//返回匹配成功索引
else return 0;//没有匹配到
}
三、上述KMP算法的改进
上述KMP算法在一种特殊情况下有些匹配显得有些多余
例如下面这个next数组:
模式串 | A | A | A | A | A | B | |||||||
---|---|---|---|---|---|---|---|---|---|---|---|---|---|
j | 1 | 2 | 3 | 4 | 5 | 6 | next[j] | 0 | 1 | 2 | 3 | 4 | 5 |
代码实现步骤:
//k=next[j]
//1.当j=1时,和next数组一样,将nextval[1]=0;
//2.当pj=pk时,nextval[j]=nextval[k];
//3.pj!=pk时,nextval[j]=k;
//第二步是算法改进的关键
//这是next数组,放在一起比较
//t=next[j]
//这一点是KMP的核心,仔细琢磨
if(pj=pt)next[j+1]=t+1;
else t=next[t];
//于是可以改进为
//匹配成功
++i;++j;
if(substr.ch[i]!=substr.ch[j])nextval[i]=j;
else nextval[i]=nextval[j];
//匹配失败,直接跳转到nextval[j]
j=nextval[j];
nextval数组函数:
void getenextval(Str substr, int nextval[])
{
int i=1,j=0;
nextval[1]=0;
while(i<substr.length)
{
if(j==0||substr.ch[i]=substr.ch[j])
{
++i;++j;
if(substr.ch[i]!=substr.ch[j]) nextval[i]=j;
else nextval[i]=nextval[j];
}
else j=nextval[j];
}
}
四、最完整的KMP算法实现
typedef struct
{
char *ch;
int length;
}Str;
void getenextval(Str substr, int nextval[])
{
int i=1,j=0;
nextval[1]=0;
while(i<substr.length)
{
if(j==0||substr.ch[i]=substr.ch[j])
{
++i;++j;
if(substr.ch[i]!=substr.ch[j]) nextval[i]=j;
else nextval[i]=nextval[j];
}
else j=nextval[j];
}
}
int KMP(Str str, Str substr, int nextval[])
{
int i=1,j=1;
while(i<=str.length&&j<=str.length)
{
if(j==0||str.ch[i]==substr.ch[j])++i,++j;
else j=nextval[j];
}
if(j>substr.length)return i-substr.length;
else return 0;
}