数据结构与算法
2015-11-04 15:13
615 查看
KMP详解
在讲解KMP算法之前,还是有必要回顾一下BF(简单粗暴但效率相对较低)算法吧
其中S是主串,T是模式串,pos是从什么位置开始匹配int Inedex(String S,String T,int pos){ int i=pos; //需注意一点无论是主串还是模式串,我们S[0]和T[0]都保存的是字符串长度,而不是字符串本身的字符 int j=1; while(i<=S[0]&&j<=T[0]){ /*若i小于S的长度且j小于T的长度时循环*/ if(S[i]==T[j]){ i++; j++; }else{ i=i-j+2; //这里之所以是i-j+2是因为T[0]不是字符,保存的是长度,你用两个这样的数组比较一下就可以了 } } if(j>T[0]) return i-T[0];//找到了,就返回匹配成功的字串的第一个位置。 else return 0; }
相信大家看了代码,就知道它的工作原理了吧,即在主串中一个一个与模式串匹配,如果失配,主串的位置就+1,模式串就回到1,这样又重复执行前面的比较,这样的匹配简单易懂,但缺乏技巧。
什么是KMP算法呢?
KMP算法是三位前辈(D.E.Knuth、J.H.Morris、V.R.Pratt)发表的,他们真实太厉害了,向前辈致敬!!!!!具体表现
第一种情况
S | 12 | I | l | o | v | e | d | a | f | a | n | z | i |
---|---|---|---|---|---|---|---|---|---|---|---|---|---|
i | 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 |
T | 5 | I | l | 0 | v | x |
---|---|---|---|---|---|---|
j | 0 | 1 | 2 | 3 | 4 | 5 |
第二种情况
S | 11 | w | w | w | . | d | a | f | a | n | z | i |
---|---|---|---|---|---|---|---|---|---|---|---|---|
i | 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 |
T | 3 | w | w | . |
---|---|---|---|---|
j | 0 | 1 | 2 | 3 |
第三种情况
S | 12 | b | b | s | b | b | s | b | b | c | d | f | z |
---|---|---|---|---|---|---|---|---|---|---|---|---|---|
i | 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 |
T | 6 | b | b | s | b | b | c |
---|---|---|---|---|---|---|---|
j | 0 | 1 | 2 | 3 | 4 | 5 | 6 |
第四种情况
S | 11 | s | s | s | s | s | s | s | d | f | z | i |
---|---|---|---|---|---|---|---|---|---|---|---|---|
i | 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 |
T | 6 | s | s | s | s | b | c |
---|---|---|---|---|---|---|---|
j | 0 | 1 | 2 | 3 | 4 | 5 | 6 |
可以看出S中的第8个位置和T中的第5个位置失配了,然而我们仔细观察,发现下一次比较其实可以让S中的第8个位置和T中的第4个位置开始匹配了
观察了这么多,其实可以说出KMP的算法的用途了,就是一次匹配失败后让主串失配的那个字符串和模式串中第几个字符和它比较。而不用让主串回溯了,大大提高了效率。而且,其实我们可以总结一些通俗的规律呢?,你比如说第一种情况中,模式串中第5个字符没有被匹配到,而它前面的没有相等的前缀和后缀,所以主串中的失配字符下一次就和模式串中的第一个元素比较。而第二种情况,因为模式串失配的那个字符前有一个前缀和后缀相等,所以呢,主串的失配字符下一次就和模式串的第二个元素比较。第三种情况,前缀和后缀有两个字符,所以主串失配的字符下一次就和模式串的第三个元素开始比较。第四种情况,前缀后后缀有3个元素相等,所以主串失配的字符下次就和模式串的第4个比较。
也许有人问了,第四个怎么就是前缀和后缀相等有4个元素呢?记住:这里的前缀和后缀相等元素的个数是以字符相等情况下,尽可能多的个数,自要前缀和后缀不完全相同就可以了
规律一般化
在这里我们引入next数组,这在KMP算法里是核心,我们就用第三种情况做分析吧。S | 12 | b | b | s | b | b | s | b | b | c | d | f | z |
---|---|---|---|---|---|---|---|---|---|---|---|---|---|
i | 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 |
T | 6 | b | b | s | b | b | c |
---|---|---|---|---|---|---|---|
j | 0 | 1 | 2 | 3 | 4 | 5 | 6 |
next | 0 | 1 | 2 | 1 | 2 | 3 |
next数组代码原理
void get_n 4000 ext(String T,int *next) { int i,j; i=0; j=1; next[1]=0 while(j<T[0]) { if(i==0||[T[i]==T[j]) { ++i; ++j; next[j]=i; }else i=next[i]; } }
z这一组代码就实现了NEXT数组了
KMP算法改进
先给出一个例子吧:S | 11 | a | a | a | a | d | a | f | a | n | z | i |
---|---|---|---|---|---|---|---|---|---|---|---|---|
i | 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 |
T | 3 | a | a | a | a | a | x |
---|---|---|---|---|---|---|---|
j | 0 | 1 | 2 | 3 | 4 | 5 | 6 |
j | 0 | 1 | 2 | 3 | 4 | 5 |
void get_nextval(String T,int *nextval) { int i,j; int i=0; int j=1 next[1]=0; while(j<T[0]) { if(i==0 || T[i]==T[j]) { i++; j++; if(T[i]==T[j]) nextval[j]=nextval[i]; else nextval[j]=i }else{ i=T[i]; } } }
强烈建议按照上面说的数组,画一次图,推算一次。
然后整体的匹配方法 ,
int Index_KMP1(String S, String T, int pos) { int i = pos; int j = 1; int next[255]; get_nextval(T, next); while (i <= S[0] && j <= T[0]) if (j==0 || S[i] == T[j]) ++i; ++j; } else j = next[j]; if (j > T[0]) return i-T[0]; else return 0; }
因为调用get_nextval(T, next)函数,一个循环为m(m就是模式串的长度),然后while循环执行了n次(n为主串的长度),所以时间复杂度就是m+n吧
转载请注明源地址:http://blog.csdn.net/xiaofanzidafanzi/
相关文章推荐
- Android之获取手机上的图片和视频缩略图thumbnails
- android string.xml文件中的整型和string型代替
- Android java 与 javascript互访(相互调用)的方法例子
- 数据库链接字符串查询网站
- android上改变listView的选中颜色
- String.intern
- 动易2006序列号破解算法公布
- Prototype源码浅析 String部分(二)
- Flex字符串比较 还有Flex字符串操作
- Ruby中的String对象学习笔记
- Ruby实现的矩阵连乘算法
- Ruby中创建字符串的一些技巧小结
- ASP下经常用的字符串等函数参考资料
- 将字符串小写转大写并延时输出的批处理代码
- 将字符串转换成System.Drawing.Color类型的方法
- C#插入法排序算法实例分析
- Lua源码中字符串类型的实现
- Lua性能优化技巧(四):关于字符串
- Lua教程(七):数据结构详解
- 字符串聚合函数(去除重复值)