博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
KMP代码实现
阅读量:6333 次
发布时间:2019-06-22

本文共 4182 字,大约阅读时间需要 13 分钟。

1. 简述

    KMP是经典的笔试题目之一。昨天复习了一下,主要有一下几点需要注意。

    首先,原理上,利用最长的相等前端距离和后端距离(详见C语言名题精选百则6.4节)。
    其次,有两类代码,一类实现fail函数,另一类实现next函数。
    · fail数组的含义是,当前元素匹配失败了,那么[开始元素,当前元素]范围内,最长前端的下标。假设最长相等前端后端分别为pat[0]-pat[s],pat[i-s]-pat[i], fail[i]=s。很多情况下,没有最长前端,所以可能有很多-1出现(没有的时候,用-1代替)。在kmp主函数中,如果text[t]!=pat[p],那么p=fail[p-1]+1。
    · next数组的含义是,当前元素匹配失败了,那么[开始元素,当前元素的前一元素]的范围内,最长前端的元素后面那个元素的下标。假设最长相等前端后端分别为pat[0]-pat[s],pat[i-1-s]-pat[i-1], next[i]=s+1。对于next只有next[0]=-1其余,最小的也就是0。在kmp主函数中,如果text[t]!=pat[p],那么p=next[p]
    · 区别:fail[i]是[0, i]的最长前后端的前端的最后一个元素。 next[i]是[0, i-1]的最长前后端的前端的最后一个元素后面的那个元素,实际上next[i]就是说当前pat[i]匹配失败了,那么下一个可以匹配的pat下标是多少。
next[i]"指的是pat[0]到pat[i-1]的范围内,“最长相等前端后端”的前端的最后一个元素的再往后面的一个元素。假设最长相等前端后端分别为pat[0]-pat[s], pat[i-1-s]-pat[i-1],next[i]=pat[s+1]。
     然后,对于next函数,是可以优化的,对于pat[p] == pat[next[p]]的情况,可以想象,如果text[t]!=pat[p],那么下一个要取p=next[p],那么由于前者相等,所以必然还是不等还给往前找next[next[p]],所以可以要求对于next[0]后面的元素,如果pat[p]=pat[next[p]], 令next[p]=next[next[p]]。这一步优化可以在前面求next[p]的过程中,处理出来,也可以在next求出来后,再来一遍进行优化。
     KMP可以说的有很多,理解也有很多,就先到这里了。尤其是实现算法的细节也很多,这个比较晕,当前next函数的实现较多,一遍都是将next的,毕竟能够优化嘛,建议找算法的话,找一些权威一点的,维基百科链接的代码,等等。

2. 代码

    当然上代码还是上自己写的。不一定对啊,不过通过fail和next都通过一些例子验证了,估计不会错吧。其实要记得话,记next应该就够了,网上fail的实现真的很少,看来我看C名题百则上的代码,走了弯路,不过其中关于最长相等前端后端的说明还是不错的,虽然还能说得更好点吧。。呵呵。实际上,最长相等的前端后端是有一种嵌套的前端后端的含义在,书里面没挑明这一点,所以理解起来不是那么直接。 

#include 
<
iostream
>
using
 
namespace
 std;
//
 fail 函数:经典KMP
//
 next 函数:经典KMP另一种定义 
//
 next-val 函数:改进KMP,要保证pat[i] != pat[next[i]],
//
 可以首先求得next数组,然后,求next-val,
//
 如果pat[i] != pat[next[i]],那么next-val[i]=next[i];
//
 反之next-val[i]=next-val[next[i]]; 由于这种相等的   
void
 get_fail(
const
 
char
 pat[], 
int
 fail[]) {
  
int
 len 
=
 strlen(pat);
  
int
 i,j;
  fail[
0
=
 
-
1
;
  
for
(i
=
1
; i
<
len; i
++
) {
    
for
(j
=
fail[i
-
1
]; j
>=
0
 
&&
 pat[j
+
1
]
!=
pat[i]; j
=
fail[j])
      ;
    fail[i] 
=
 (j
<
0
 
&&
 pat[j
+
1
]
!=
pat[i]) 
?
 
-
1
 : j
+
1
  }
  cout 
<<
 
"
fail array: 
"
 
<<
 endl;
  
for
(
int
 i
=
0
; i
<
len; i
++
) {
    cout 
<<
 i 
<<
 
"
"
 
<<
 fail[i] 
<<
 endl;
  }
}
int
 kmp_fail(
const
 
char
 text[], 
const
 
char
 pat[]) {
  
int
 t_length 
=
 strlen(text);
  
int
 p_length 
=
 strlen(pat);
  
int
 t,p;
  
int
*
 fail 
=
 
new
 
int
[t_length];
  get_fail(pat, fail);
  
for
(t
=
0
,p
=
0
; t
<
t_length
&&
p
<
p_length; ) {
    
if
(text[t] 
==
 pat[p]) 
//
 相等都往后走一步 
      t
++
,p
++
;
    
else
      
if
(p 
>
 
0
//
 前面还有可能的元素没比较过 
         p 
=
 fail[p
-
1
]
+
1
;
      
else
 
//
 可能的元素都比较过了 
         t
++
;
  }
  delete []fail;
  
return
 t
<
t_length
?
(t
-
p_length):
-
1
;
}
void
 get_next(
const
 
char
 pat[], 
int
 next[]) {
  
int
 len 
=
 strlen(pat);
  
int
 i,j; next[
0
=
 
-
1
;
  
for
(i
=
1
; i
<
len; i
++
) {
    
for
(j
=
next[i
-
1
]; j
>=
0
 
&&
 pat[i
-
1
]
!=
pat[j]; j
=
next[j])
      ;
    
if
(j
<
0
 
||
 pat[i
-
1
]
!=
pat[j])
      next[i] 
=
 
0
;
    
else
 
      next[i] 
=
 j
+
1
//
 if (pat[i]==pat[next[i]]) next[i]=next[next[i]];
  }
  cout 
<<
 
"
next array: 
"
 
<<
 endl;
  
for
(
int
 i
=
0
; i
<
len; i
++
) {
    cout 
<<
 i 
<<
 
"
"
 
<<
 next[i] 
<<
 endl;
  }
  
for
(
int
 i
=
0
; i
<
len; i
++
) {
    
if
(pat[i] 
==
 pat[next[i]])
      next[i] 
=
 next[next[i]];
  }
  cout 
<<
 
"
new next array: 
"
 
<<
 endl;
  
for
(
int
 i
=
0
; i
<
len; i
++
) {
    cout 
<<
 i 
<<
 
"
"
 
<<
 next[i] 
<<
 endl;
  }
}
int
 kmp_next(
const
 
char
 text[], 
const
 
char
 pat[]) {
  
int
 t_length 
=
 strlen(text);
  
int
 p_length 
=
 strlen(pat);
  
int
 t,p; 
int
*
 next 
=
 
new
 
int
[p_length];
  get_next(pat, next);
  
for
(t
=
0
,p
=
0
; t
<
t_length,p
<
p_length; ) {
    
if
(text[t] 
==
 pat[p]) 
      t
++
,p
++
;
    
else
      
if
(next[p] 
==
 
-
1
//
 说明此时p=0,而且pat[0]都匹配不了 
        t
++
;
      
else
 
        p 
=
 next[p];  
  }
  delete []next;
  
return
 t
<
t_length 
?
 (t
-
p_length):
-
1
;  
}
int
 main() {
  
const
 
char
 
*
text 
=
 
"
aaaddddddddddddddddd
"
;
//
  const char *pat = "aaaadd";
//
  const char *pat = "xyxyyxyxyxx";
  
const
 
char
*
 pat 
=
 
"
abcabcaa
"
;
  cout 
<<
 
"
index: 
"
 
<<
 kmp_fail(text, pat) 
<<
 endl;
  cout 
<<
 
"
index: 
"
 
<<
 kmp_next(text, pat) 
<<
 endl;
  system(
"
PAUSE
"
);
  
return
 
0
;
}

    主要说一下测试样例:对于pat="abcabcaa",答案来自网络,只有next和next-val的正确答案:-1,0,0,0,1,2,3,4和-1,0,0,-1,0,0,-1,4。对于pat="xyxyyxyxyxx" 答案来自书籍,只有fail的正确答案:-1,-1,0,1,-1,0,1,2,3,2,0。

3. 参考
    C语言名题精选百则,6.4节。 其中讲了fail函数的实现。其中有fail的测试数据。
    数据结构习题与解析(第三版,A级,Page114)。
    手算KMP匹配的Next值和Nextval值。   其中有next和next-val的测试数据。
    Knuth–Morris–Pratt algorithm 维基百科   没仔细看,不过链接了很多代码实现和讲解的网页,由于代码实现细节被一些中文书先入为主了,这些代码有些看不进去。
4. 备注
    大致参考就这些了,国内的一些博客上的讲解我就不连接了,不能保证其肯定是对的,反正很多我在不会kmp的时候,是看不太懂,好像有一套理解方式是从自动机那边走,算法导论好像就是先讲了自动机,就接着讲了字符串匹配的kmp,我现在的理解方式就是从最长前端最长后端理解。

转载地址:http://oanoa.baihongyu.com/

你可能感兴趣的文章
ES6 结构和扩展运算符
查看>>
王利阳:电商大促 决战6.18
查看>>
kafka消息传输的事务定义
查看>>
JAVA 后台数据校验
查看>>
实现LNMMP
查看>>
mysql的pid文件出现问题
查看>>
计算rem单位
查看>>
第七章 大网高级 ASA
查看>>
rsync+inotify触发式远程同步
查看>>
优秀设计师应当知道的几大UI设计原则(一)
查看>>
mongodb高级查询
查看>>
struts2.1 struts.devMode BUG解决方案
查看>>
日本法院裁定三星诉苹果专利侵权案败诉
查看>>
Windows Server 2012R2 桌面体验问题直通车
查看>>
桌面支持--复印证件技巧
查看>>
Silverlight实用窍门系列:50.InkPresenter涂鸦板的基本使用,以及将效果保存为Png图片【附带源码实例】...
查看>>
MySQL数据库经典书籍share
查看>>
给出三个数,要求输出 最大的一个
查看>>
Linux系统中获取帮助的方法及Linux系统的哲学思想
查看>>
在windows环境创建,安装windows服务
查看>>