您的位置:首页 > 编程语言

KMP算法代学习之(二)代码深入学习

2012-11-08 14:29 288 查看
前段时间学习了KMP算法,感觉略懂略懂!但算法这个东西理解一定要深入,不是略懂略懂就能敷衍过去的!真真做题的时候,才发现仅仅套模板是根本没用的,必须自己写得来代码,这样的话才能从细节,从根本上了解KMP算法的实现过程!

既然这样的话,就只好再把KMP算法弄出来好好学习一翻了!

很简单的一个例子:有I am xiao shuai.这一个句子,问“xiao shuai”在不在这说话中,如果在,起始位置在哪里,如果不在,返回-1。很显然xiao shuai是在这个句子中的,而且起始位置是5。

这个问题怎么用算法来解决呢,只是单纯地解决问题,不一定用KMP算法?

int compare(char *t,char *p){
int t_len=strlen(t),p_len=strlen(p);
int i,j,idx;
if(t_len<p_len)return -1;
for(j=idx=i=0;i<t_len;i++){
while(idx<t_len&&j<p_len&&t[idx]==p[j])idx++,j++;
if(j==p_len)//表示已经匹配到了
return i;
else if(idx==t_len)//已经到末尾没有匹配到
return -1;
else//还没有匹配完,重新开始匹配
j=0;idx=i+1;
}
return -1;
}

这是最朴素的字符串匹配算法。但是呢,这个实现不怎么优雅,我们把代码修改一下:

int compare(char *t,char *p){
int t_len=strlen(t),p_len=strlen(p);
int i,j;
if(t_len<p_len)return -1;
i=j=0;
while(i<t_len&&j<p_len){
if(t[i]==p[j])
i++,j++;
else
i=i-j+1,j=0;
}
if(j==p_len) return i-j;
return -1;
}

这样看要简洁多了! 其实这种匹配思想是很明白的,在匹配的过程中,如果出现不匹配的情况:那么i=i-j+1,j=0,即i回溯到本轮不匹配时起始位置的下一个位置,j回溯到自己开头!

这样解决问题是可行的,但其执行的效率呢?如果字符串的长度达到100w级别,依靠这个算法能快速解决问题吗?

我们再回到刚才的例子,我们真的有必要在不匹配的时候,把j老老实实地退回去,让i只是从本轮匹配开始的地方往前一步吗? answer is No!

这时候我们才真真转入KMP算法的学习。KMP算法就是研究字符串在匹配的过程中中途怎么跳的问题!即怎么能跳得快一点,而不是傻傻地把j老老实实地退回去,让i只是从本轮匹配开始的地方往前一步。

KMP算法把重心专注在了匹配串,我猜想因为一般而言,匹配串的长度比原串要短很多,吃柿子的时候要挑软柿子捏嘛!认准了对象,接下来怎么做呢?

我们知道,孔子曾经说过:吾有知乎哉,无知也,有鄙夫问于我,空空如也,我叩其两端而竭焉!字符串问题的处理也遵循这种思想,叩其两端,哪两端呢? 一端是前缀,一端是后缀!比如:Trie树,后缀树!KMP算法也没有跳出这个圈圈,因为它是从前缀入手的!

比如字符串:ababacb,它的前缀有a,ab,aba,abab,ababa,ababac,ababacb。KMP算法构造了一个next数组来存储这些前缀的一种性质:不为自身的最大首尾重复子串长度。

通俗地说,就是自我覆盖程度!那么对于前缀a,其next值规定为-1,即没有自我覆盖。这一点刚开始很难想通,a本身不是自我覆盖吗?那么请看红色的文字!

一般学习KMP算法对一个长度不太大的字符串,应该有能力手工计算出其next值!

这里我们用程序来计算就可以了!

void cal_next(char *p){ //这里计算next,主要考虑的是index的变化
int len=strlen(p);
int i,index;
next[0]=-1;//KMP的起点从-1开始
for(i=1;i<len;i++){
index=next[i-1];//这里有点回溯的感觉
while(index>-1&&p[index+1]!=p[i]){
index=next[index];
}
if(p[index+1]==p[i]){
next[i]=index+1;
}else{
next[i]=-1;
}
}
}

至于其计算的规则,我在第一篇KMP的博文中已经写了,也可以参考July的博客(百度:July,算法之道)!

那么,next数组计算出来后,怎么用呢?

int compare(char *t,char *p){
int t_len=strlen(t),p_len=strlen(p);
int i,j;
if(t_len<p_len)return -1;

i=j=0;
cal_next(p);//与普通匹配相比,多了计算next数组的一行代码
while(i<t_len&&j<p_len){
if(t[i]==p[j])
i++,j++;
//这里j=0的时候是个特殊情况,所以需要单独处理
//比如,对于字符串 t="abbbbaccababca",p="bbba"
//如果没有j==0的判定,就会出现死循环
else if(j==0)
i++;
else
j=next[j-1]+1;//这里 i不再回退了,而随着i不再回退,j也不归零了!
}
if(j==p_len) return i-j;
return -1;
}

如果比较上面的普通匹配的代码,就会发现改的东西不多!
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
相关文章推荐