KMP算法的剖析与实现
来源:互联网 发布:实惠猪软件下载 编辑:程序博客网 时间:2024/05/17 08:13
KMP算法的实现
字符串的匹配主要是查找子串在主串中出现的位置,主要的算法有两种Brute-Force及其改进的KMP算法,本文将详解之。
1.Brute-Force的主要思想
若有主串S=“abaababaddecab”,子串T=“abad”要在S中找到T的正确匹配位置。
第一次匹配
a b a a b a b a d d e c a b
a b a d (此时S.i(3)!=T.j(3))
第二次匹配则回退i 指向b
a b a a b a b a d d e c a b
a b a d(i=1,j=0此时依然S!=T)
第三次匹配 从 i=2开始
a b a a b a b a d d e c a b
a b a d(i=3时不相等)
第四次匹配从i=3开始
a b a a b a b a d d e c a b
a b a d(i=6时不匹配)
第五次匹配回退i=4
a b a a b a b a d d e c a b
a b a d
第六次匹配i=5
a b a a b a b a d d e c a b
a b a d(S=T匹配成功)
这种思路是从主串S的第一个字符开始不断地匹配,如果匹配失败则就回退到i=i-j+1的位置继续匹配,直至成功。
具体的测试代码如下:
#include<iostream>
#include<string>
using namespace std;
int findtins(string S,string T)
{
int i=0,j=0;
while(i<S.length()&& j<T.length())
{
if(S[i]==T[j])
{
i++;
j++;
}
else
{
i=i-j+1;
j=0;
}
}
if(j>=T.length())
returni-j+1;
else
return-1;
}
int main()
{
stringS="abaababaddecab";
stringT="abad";
intresult=findtins(S,T);
cout<<"子串在主串中的位置是:\n"<<result<<endl;
return 0;
}
运行结果:
这就是Brute-Force算法匹配过程实现。
2.KMP算法
KMP算法是在Brute-Force上的改进,其主要思想是:每当一次匹配过程中出现字符不相等时,不需要回退主串的指针,而是利用前面已经得到的“部分匹配”的结果,将子串向右移动若干个字符后,继续与主串的当前字符比较。
例如假设有主串S=“abaababaddecab“,子串T=”abad“,则KMP的匹配过程:
a b a a b a b a d d e c a b(S.i)
a b a d(T.j)(i=3,j=3时不匹配)
这时候如果是Brute-Force算法则会将i回退到i=1,j=0的位置比较S.1与T.0,但其实这是没有必要的因为T.0!=T.1,而T.1==S.1
所以T.0一定不等于S.1,又因为S.2==T.2,T.0==T.2,所以T.0一定等于S.2,所以直接比较S.3和 T.1。
所以下一次比较为:
a b a a b a b a d d e c a b(S.i)
a b a d (T.j)(此时比较S.3!=T.1)
当前的j=1和主串不匹配,next[1]=0(后面介绍),则从j=0,i=3开始匹配S.3和T.0
a b a a b a b a d d e c a b(S.i)
a b a d (T.j)(此时i匹配到i=6,j=3失配)
此时的next[3]=1,所以下一次比较S.6和T.1即
a b a a b a b a d d e c a b(S.i)
a b a d (T.j)(i=8,j=3,完全匹配)
这就是KMP的匹配过程,可以看到这种匹配方式只需要四次就可以找到子串的位置。
KMP算法的关键在于如何确定next[j]=k的值,这个表达式的意思是在子串的地j个元素和主串当前字符不匹配时,直接把当前的S串中的字符和子串的第k个字符开始比较(也可以说是把子串向右移动到k的位置,然后把k和当前的S中的字符比较)。问题的关键在于k值如何确定?这就是next函数的意义了。
若主串S=”S0S1S2S3S4…….Sn”,T=”T0T1T2T3T4………Tn”
若比较过程中有以下关系式1:
Si-jSi-j+1…..Si-kSi-k+1….Si-1Si
T0T1…..Tk-2Tk-1Tk…..Tj-k-1Tj-kTj-k+1….Tj-iTj
若此时有Si!=Tj
而上图中红色的部分分别都相等则下一次比较从Tk开始,此时的next[j]=k。
从以上的分析可以知道next函数的值和主串没有关系,只和子串有关,下面具体分析一下如何在一个子串中求出next函数的值。
假设有子串T=“abaabacaba“,求其next[]的过程如下:
j 0 1 2 3 4 5 6 7 8 9
子串 a b a a b a c a b a
Next[j] -1 0 0 1 1 2 3 0 1 2
下面解释一下这九个next值是如何得到的
Next[0]=-1,规定。next[1]=0,若T.j=b失配则之前的a与之前的没有相等的,所以必须从j=0,即netx[1]=0开始匹配。Next[2]=0,若此时T.2失配,他之前的元素为b,前面没有与b相等的元素,所以也必须从头开始匹配。Netx[3]=1,因为3之前有字母a,与第一个a相等,所以此时应该从1位置匹配(结合前面的那个关系式1)。
Next[4]=1,4之前的元素为a,与第一个a相等,所以下一个比较的字符是1.现在来求next[5],其前面的T.4=b,next[4]=1,又因为T.4=T.1=b,所以next[5]=next[4]+1=2;现在求next[6],T.6之前的数为T.5=a,因为netx[5]=2;T.5==T.2,所以next[6]=next[5]+1=3;接下来求next[7],因为next[6]=3,但是T.6!=T.3,再继续比较T.6和T.(next[3]=1),显然T.6!=T.1
在继续比较T.6和T.(next[1]=0),也不相等,下一次比较时next[0]=-1了则next[7]=next[0]+1=0(此时相当于从头开始匹配),next[8],next[9]以此类推。
这就是next函数的求解过程。
Getnext()函数的代码:
void getnext(string T,int next[])//求T中的next值放入next数组中
{
int j,k;
j=0;
k=-1;
next[0]=-1;
while(j<T.length())
{
if(k==-1|| T[j]==T[k])
{
j++;
k++;
next[j]=k;
}
else
{
k=next[k];//向右移动子串
}
}
}
KMP的实现代码:
#include<iostream>
#include<string>
using namespace std;
void getnext(string T,int next[])
{
int j,k;
j=0;
k=-1;
next[0]=-1;
while(j<T.length())
{
if(k==-1|| T[j]==T[k])
{
j++;
k++;
next[j]=k;
}
else
{
k=next[k];
}
}
}
int kmpfind(string S,string T,int next[])
{
int i=0,j=0;
while(i<S.length()&&j<T.length() )
{
if(S[i]==T[j])
{
i++;
j++;
}
else
{
j=next[j];
}
}
if(j>=T.length())
{
returni-T.length()+1;
}
else
{
return-1;
}
}
int main()
{
stringS="abaababaddecab";
stringT="abad";
intnext[100];
getnext(T,next);
intresult=kmpfind(S,T,next);
cout<<"kmp查找子串在主串的位置为:\n"<<result<<endl;
return 0;
}
运行结果:
下面是next函数的改进版本:
void getnextval(string T,int nextval[])
{
int j,k;
j=0;
k=-1;
next[0]=-1;
while(j<T.length())
{
if(k==-1|| T[j]==T[k])
{
j++;
k++;
if(T[j]!= T[k])
{
nextval[j]=k;
}
else
{
nextval[j]=nextval[k];
}
}
else
{
k=next[k];
}
}
}
关于nextval的说明:
j 0 1 2 3 4 5 6 7 8 9 10
子串 a b c d a b c d a b d
Next[j] -1 0 0 0 0 1 2 3 4 5 6
Nextval[]-1 0 0 0 -1 0 0 0 -1 0 6
其中nextval[]存放的是改进后的next函数的值,如果主串的对应的字符S.i和T.8失配,则应该取T.next[8]与主串S.i比较,因为T.4=T.8=a,所以一定与S.i失配,则取T.next[4]与S.i比较,即T.0和S.i比较,因为T.0等于a,所以也失配,则取next[0]=-1,这时模式串停止向右滑动。其中T.4,T.0和S.i的比较是没有意义的,所以需要修改next[8]和next[4]的值为-1,同理用这种方法修正其他的next函数值。- KMP算法的剖析与实现
- kmp算法的理解与实现
- kmp算法的理解与实现
- KMP算法的理解与实现
- kmp算法的理解与实现
- 红黑树算法的实现与剖析
- 红黑树----红黑树算法的实现与剖析
- 红黑树算法的实现与剖析
- 红黑树算法的实现与剖析
- 红黑树算法的实现与剖析
- KMP算法的实现
- KMP算法的实现
- KMP算法的实现
- KMP算法的实现
- KMP算法的实现
- kmp算法的实现
- kmp算法的实现
- KMP算法的实现
- 算法题:最长公共子序列
- 下拉刷新,UIRefreshControl
- 無人機相關資源
- linux 线程操作问题undefined reference to 'pthread_create'的解决办法(cmake)
- CVPR 2015 paper 下载
- KMP算法的剖析与实现
- unittest理解
- 算法题:删除 K 位数字
- Double比较大小
- LeetCode Reverse Integer (处理溢出)
- 从Eclipse导入工程到Android Studio
- 淘宝上的骗子,有新骗术了,赶紧过来看哦!
- 从打工到创业的艰辛路程
- hdu 2561 第二小整数