一个全新的视角来看KMP算法(简单!形象!)
2016-07-23 09:53
393 查看
前面《字符串匹配》中我们介绍了KMP算法,《KMP算法Java实现》中给出了KMP算法的实现。
KMP算法很多人都说难,我第一次从《算法导论》中看到的时候也觉得难,后来重看算导时自己推导了一遍,觉得不难了,但是还是感觉印象不深,推导过后一段时间又会很模糊,下次遇到又得重新推,如此往复,浪费了大量时间。KMP算法原理上并不难,但是之所以给人难的感觉是因为它不够直观。今天突发奇想想到了一种将KMP算法图形化的方法,我们对图像的认识远比字符串深刻,下面就予以介绍。
我们还是取之前文章中的例子,T和P分别如下:
b a c b a b a b a
a b c b a T
a b a b a c a P
如何将其图形化呢?
因为要将其变为二维图像,我们需要两个量作为每个元素的坐标,第一个量就取元素在字符串中的位置(从0开始),那么第二个量呢?也很简单,取字母在字符表中的位置(从0开始),这样就得到一个坐标,然后我们将其标记到坐标轴中,如下分别是T和P的图形:
图形已经在上面了,那么字符串匹配问题现在就可以重新描述为:给定一个图形T和一个图形P,如果P在T中以偏移s出现(吻合),那么称s是有效偏移;否则,称它是无效偏移。现在的问题就是找到所有的有效偏移。
有两条很明显(但是很重要)的性质:
性质1. 如果P出现在T中,那么其形状是吻合的,而且对应的每一个部分也是吻合的
性质2. P中可能会有多个形状相同的区域,比如下图中的0-2和2-4,[u]而pi[i]正是记录这些信息的,比如这里的pi[4] = 3,也就是图中的坐标2.[/u]
现在我们根据伪代码来重新看一下匹配过程
KMP-MATCHER(T,P)
1. n = T.length
2. m = P.length
3. pi = COMPUTE-PREFIX-FUNCTION(P)
4. q = 0 //number of characters matched
5. for i = 1 to n
6. while q>0 and P[q+1] != T[i]
7. q = pi[q]
8. if P[q+1] == T[i]
9. q = q+1
10. if q == m
11. print "Pattern occurs with shift" i-m
12. q = pi[q] //look for next match
从第5行开始,第5行开始的for循环依次比对T中的每个位置,如果不匹配,按照我们上面给出的两条性质,要想找到与当前位置i匹配的T,那么它的前面部分也必须匹配(性质1),那么我们就应该找到前面部分匹配的位置来进行比对,根据性质2,T中可能不止有一个部分形状相同,因此我们应该遍历前面所有可能使得当前匹配的part,直到找到一个或者遍历完(一个都没发现)。而这正是6-7行的while循环所做的工作。如果当前吻合了,继续比对下一部分(8-9行),当整个P都吻合了(第10行),输出位置(第11行),继续下一次的比对(第12行)。
就写到这里,望仔细体会!
KMP算法很多人都说难,我第一次从《算法导论》中看到的时候也觉得难,后来重看算导时自己推导了一遍,觉得不难了,但是还是感觉印象不深,推导过后一段时间又会很模糊,下次遇到又得重新推,如此往复,浪费了大量时间。KMP算法原理上并不难,但是之所以给人难的感觉是因为它不够直观。今天突发奇想想到了一种将KMP算法图形化的方法,我们对图像的认识远比字符串深刻,下面就予以介绍。
我们还是取之前文章中的例子,T和P分别如下:
b a c b a b a b a
a b c b a T
a b a b a c a P
如何将其图形化呢?
因为要将其变为二维图像,我们需要两个量作为每个元素的坐标,第一个量就取元素在字符串中的位置(从0开始),那么第二个量呢?也很简单,取字母在字符表中的位置(从0开始),这样就得到一个坐标,然后我们将其标记到坐标轴中,如下分别是T和P的图形:
图形已经在上面了,那么字符串匹配问题现在就可以重新描述为:给定一个图形T和一个图形P,如果P在T中以偏移s出现(吻合),那么称s是有效偏移;否则,称它是无效偏移。现在的问题就是找到所有的有效偏移。
有两条很明显(但是很重要)的性质:
性质1. 如果P出现在T中,那么其形状是吻合的,而且对应的每一个部分也是吻合的
性质2. P中可能会有多个形状相同的区域,比如下图中的0-2和2-4,[u]而pi[i]正是记录这些信息的,比如这里的pi[4] = 3,也就是图中的坐标2.[/u]
现在我们根据伪代码来重新看一下匹配过程
KMP-MATCHER(T,P)
1. n = T.length
2. m = P.length
3. pi = COMPUTE-PREFIX-FUNCTION(P)
4. q = 0 //number of characters matched
5. for i = 1 to n
6. while q>0 and P[q+1] != T[i]
7. q = pi[q]
8. if P[q+1] == T[i]
9. q = q+1
10. if q == m
11. print "Pattern occurs with shift" i-m
12. q = pi[q] //look for next match
从第5行开始,第5行开始的for循环依次比对T中的每个位置,如果不匹配,按照我们上面给出的两条性质,要想找到与当前位置i匹配的T,那么它的前面部分也必须匹配(性质1),那么我们就应该找到前面部分匹配的位置来进行比对,根据性质2,T中可能不止有一个部分形状相同,因此我们应该遍历前面所有可能使得当前匹配的part,直到找到一个或者遍历完(一个都没发现)。而这正是6-7行的while循环所做的工作。如果当前吻合了,继续比对下一部分(8-9行),当整个P都吻合了(第10行),输出位置(第11行),继续下一次的比对(第12行)。
就写到这里,望仔细体会!
相关文章推荐
- Java中的Iterator(迭代器)的一般用法
- hdu 4315 Climbing the Hill (阶梯博弈)
- 安卓 自定义布局 每次都有AttributeSet ,是什么意思?
- 【脑补】未来网购大家更想看到的。。。
- hdu 5744 Keep On Movin (字符串)
- HTML&CSS基础学习笔记1.16-单元格间距和表格主体
- kbengine基础教程--01
- index_ffs, leading,merge,no_merge,no_unnest,use_hash
- 解决refreshing gradle project 和Building gradle project info 一直卡住\速度慢
- 互联网职业概述
- springmvc中解决post乱码
- HDOJ 1789 Doing Homework again
- (转)OpenCV提取视频每一帧及将连续图片合成视频
- tomcat源码解析(一)——Bootstrap和Catalina启动部分
- 石子合并(一)
- sql关键语句详解
- 小希的迷宫
- RecyclerView 实现下拉刷新和自动加载
- Android学习笔记之AndroidManifest.xml文件解析
- URL传递中文字符,特殊危险字符的解决方案(仅供参考)urldecode、base64_encode