`

【串和序列处理 5】KMP子串匹配算法

阅读更多

模式匹配: 在字符串S中,子串P的定位操作通常称做串的模式匹配。说白了,就是在一个字符串中寻找子串。在Suffix Trie和PAT tree中我们已经讨论过匹配子串的方法了。这里我们讨论一种线性匹配算法来寻找子串。

 

例: 我们要在S="ababcabcacbab"中查找子串P="abcac"。下图左侧是一种很普通的模式匹配算法

这种普通的模式匹配算法很简单,但时间复杂度是O(n*m)。其中n=S.length,m=T.length.  代价很高。难道真的要像第三趟到第四趟那样:好不容易匹配到S中的第7个位置,但由于不相同,则有回溯到第4个位置重新开始。

 

其实,每一次匹配过后,主串S中被匹配过的位置其实不用再匹配了。也就是上一趟匹配结果对下一趟有指导作用。 我们用一个证明来说明这一点(假如主串S=s1 s2 .... sn ,模式串P=p1 p2 ... pm  )。

 

证明:当一趟匹配完成之后,我们发现si !=pj 。那么至少说明了模式串p1... p(j-1)是匹配成功的。也就是得到了:  p1  p2  ...  p(j-1) = s(i-j+1)   s(i-j+2) .....  s(i-1) 。 比如第三趟 s7!=p5 => s2...s6=p1...p4 = "abca".

         如果像上图左侧算法那样,从第三趟i=7回溯到第四趟i=4是有必要的话,那么也说第四趟可能完全匹配到了模式串P。好,我们现在就假设si != sj之后,i 回溯主串(i-j+1)的下一个位置上可以匹配成功模式串P,那么我们可以得到 p1 p2 ... p(j-1) =s(i-j+2)   s(i-j+3) ... s(i)。

         合并下标蓝色的两个式子,我们可以得到:

                   s(i-j+1)   s(i-j+2) .....  s(i-1)= s(i-j+2)   s(i-j+3) ...  s(i)

         也就是:  s(i-j+1)= s(i-j+2)= s(i-j+3)= .... =s(i-1)=s(i)

         我靠,主串S中全部的字符都一样,这种情况下才必须每一次都回到主串的下一个位置上重新开始。而只要有字符不同,就完全没有这个必要了。好了,基于这个证明成果,我们开始介绍KMP算法。

 

 

KMP 模式匹配 —— D.E.Knuth   V.R.Pratt和J.H.Morris同时发现的。因此人们称它为克努特-莫里斯-莫拉特操作(简称KMP算法)。KMP的优势在于当每一趟匹配结果中出现了不等情况时,主串并不需要回溯位置i (上面已经证明),而只要 回溯模式串P即可 。也就是说,只需要在主串S的位置i 处重新与模式串的位置k进行比较(如上图右侧过程)。 那么这个 重新 需要定位的模式串k位置有什么要求呢,或者说我们怎么确定这个k呢。我们再用一个小小的证明来揭示( 还是假如主串S=s1 s2 .... sn ,模式串P=p1 p2 ... pm  ):

 

证明:当一趟匹配完成之后,我们发现si !=pj 。此时首先可以肯定的是k< j,因为模式串j 必须回溯。

        如果 s1 与 pk 有重新比较的必要,那么模式串P前k-1个字符必须满足下列关系式:

                            p1  p2  ...  p(k-1)=s(i-k+1)  s(i-k+2) ...  s(i-1)

        此外,由于经过额一趟的匹配之后,已经可以得到“部分匹配”结果,主串S中i 位置的前k个字符一定等于模式串P中j 位置上的前k个字符:

                           p(j-k+1)  p(j-k+2)  ...  p(j-1) = s(i-k+1)   s(i-k+2) .....  s(i-1) 。

        合并两个蓝色的式子:

                           p1  p2  ...  p(k-1) = p(j-k+1)   p(j-k+2)   ...   p(j-1)

        我们发现了,位置k的值取决于模式串P自己必须满足上面这个红色的式子。

 

 

失效函数: 当在模式串P的第j 个位置上发生匹配不成功时,需要将模式串回溯到位置 k处的这样一个f(j)=k 的函数,就叫做失效函数。其中j 和k 的值必须满足p1  p2  ...  p(k-1) = p(j-k+1)   p(j-k+2)   ...   p(j-1)。 也就是说 pj 的前k-1个字符必须等于pk 的前k-1 个字符。因此,失效函数f(j)的定义如下:

 

                                                 0       当j=1时

                                   f(j) =      Max{k|1<k<j 且 p1...p(k-1)=p(j-k+1)...p(j-1) }

                                                 1       不满足上面的情况

 

比如模式串P=“abcac”的每一个位置的失效函数如下:

                                            j         1   2   3   4   5

                                           P         a   b   c   a    c

                                         k=f(j)     0   1   1   1   2


失效函数的算法

     假如f(j)=k,则表明 p1...p(k-1) = p(j-k+1)...p(j-1)。这说明 pj 的前k-1个字符必须等于pk 的前k-1 个字符。也就是说p1...pk一定是p1...pj的一个后缀子串。因此我们可以把模式串P与自身做KMP算法的匹配来求解这个K值。算法如下:

     (1) 若 pk=pj, 则p1...p(k-1)pk= p(j-k+1)...p(j-1) pj 。 表明f(j+1)=k+1

     (2) 若 pk!=pj , 则可以把求f(j+1)看成以P为主串和匹配串的模式匹配问题。即 pk!=pj 则比较pj与p(f(k)),如果pj==p(f(k)),则f(j+1)=f(k)+1。否则继续比较下去直到f(k)=0为止。

 

     失效函数算法的运行时间是o(m).

 

 

KMP算法Java源代码

package net.hr.algorithm.string;

public class KMP {

	private int[] next=null;
	private char[] mainAry=null;
	private char[] patternAry=null;
		
	public KMP(String main,String pattern){		

		this.mainAry=new char[main.length()+1];
		this.patternAry=new char[pattern.length()+1];
		System.arraycopy(main.toCharArray(),0,mainAry,1,main.length());
		System.arraycopy(pattern.toCharArray(),0,patternAry,1,pattern.length());
		this.next=new int[pattern.length()+1];
	}
	/**
	 * KMP匹配
	 * @param pos 从主串起始位置开始
	 */
	public int match(int pos){

		if(pos>mainAry.length)
			throw new IndexOutOfBoundsException();
		failFunction();
		
		int i=pos,j=1;
		while(i<mainAry.length&&j<patternAry.length){
			if(j==0||mainAry[i]==patternAry[j]){
				++i;
				++j;
			}else{
				j=next[j];
			}
		}
		if(j>=patternAry.length) return i-patternAry.length+1;
		else return 0;	
	}
	/**
	 * 匹配串失效函数
	 */
	private void failFunction(){
		
		int i=1,j=0;
		next[1]=0;
		while(i<patternAry.length-1){
			if(j==0||patternAry[i]==patternAry[j]){
				next[++i]=++j;
			}else{
				j=next[j];
			}
		}
	}
	
	/**
	 * 测试
	 * @param args
	 */
	public static void main(String[] args) {
		
		KMP kmp=new KMP("acabaabaabcacaabc","abaabcac");
		System.out.println(kmp.match(1));
	}

}
 

KMP算法效率

 

      对于长度为n的文本串和长度为m的模式串进行模式匹配的运行时间为O(n+m) . 很显然,因为文本串在KMP算法中并不需要回溯,因此与模式串的比较次数为O(n)。但模式串要建立失效函数,所付出的代价是O(m)。因此总体的时间复杂度是O(m+n)。实际中,m要远小于n,因此近似可以认为KMP效率为O(n)。

      但是KMP算法有种最坏的情况,当模式串P="aaaaa"时,即每一个字符都一样的时候。则失效函数为:

                                   j     1 2 3 4 5

                                  P     a a a a a

                                 f(j)    0 1 2 3 4

      此时如果主串中的s[i]!=p[j]的时候,根据模式串P回溯j=f(j)的原则。s[i]需要从模式串P的最后一个字符一步一步回溯到第一个字符,每次都要比较一遍。这时的时间复杂度为O(m)。那么对于n个字符的S串而言,最差的时间复杂度就是O(n*m)了,退化成了蛮力匹配。

 

     KMP和后缀树都可以用来匹配子串。因此我们这里与后缀树做一个比较,虽然后缀树在查找的过程中只需要大概O(m)的时间复杂度。对长度n的文本串建立后缀树最好的算法需要O(n)时间复杂度,因此后缀树大致也需要O(n+m)

 

 

 

 

 

 

6
0
分享到:
评论

相关推荐

    kmp算法匹配子串vc++6.0

    基于c语言实现。作为一个IBM的研究人员,请写一个程序找出给定的DNA片段之间的相同之处,使得对个体的调查相关联。 一个DNA碱基序列是指把在...给出一个DNA碱基序列的集合,确定在所有序列中都出现的最长的碱基序列

    KMP算法,全称Knuth-Morris-Pratt字符串搜索算法,是一种线性时间复杂度的字符串匹配算法

    KMP算法,全称Knuth-Morris-Pratt字符串搜索算法,是一种线性时间复杂度的字符串匹配算法。它的主要思想是在发生不匹配时,能知道部分已经匹配的字符序列的后缀和模式串的前缀存在重复,因此可以利用这些信息避免...

    以下是KMP算法的一个简化版本的伪代码实现.pdf

    KMP算法,即Knuth-Morris-Pratt算法,是一个线性时间复杂度的字符串匹配算法。它能在O(n+m)的时间复杂度内完成一个长度为n的文本串S和一个长度为m的模式串T的匹配工作,其中n和m分别代表文本串和模式串的长度。相比...

    ACM算法模板和pku代码

    本科参加ACM竞赛的过程中积累下来的一部分算法模板,和自己在PKU上面做的一部分题目。 模板目录结构: 目录: 动态规划 O(n^2)的最长上升子序列 nlogn最长上升子序列 高精度 计算几何 Graham扫描法 两线段交点 凸...

    高效算法:竞赛、应试与提高必修128例.[法] Christoph Dürr Jill-Jênn Vie(带书签文字版).pdf

    2 8 字符串的最长回文子串:Manacher 算法 42 第 3 章 序列 44 3 1 网格中的最短路径 44 3 2 编辑距离(列文斯登距离45 3 3 最长公共子序列 47 3 4 升序最长子序列 49 3 5 两位玩家游戏中的必胜策略 52 第 4 章 数组...

    ACM 算法经典代码 数据结构经典代码

    11. 最大子串匹配 121 12. 最大子段和 122 13. 最大子阵和 123 常用源代码 包括很多经典算法 数学问题: 1.精度计算——大数阶乘 2.精度计算——乘法(大数乘小数) 3.精度计算——乘法(大数乘大数) 4.精度...

    浙大算法包,几何 结构\数论\数值计算\图论_NP搜索\图论_连通性\图论_匹配\组合\

    模式匹配(kmp) 逆序对数 字符串最小表示 最长公共单调子序列 最长子序列 最大子串匹配 最大子段和 最大子阵和 组合\ 排列组合生成 生成gray码 置换(polya) 字典序全排列 字典序组合 组合公式

    Algorithm:这个存储库将包含我学校的抄袭检查项目。 它将比较不同算法的时间复杂度

    抄袭检测 - 不同算法的比较分析用于抄袭检测的​​算法类型:子串匹配子序列匹配使用的算法:LCSS Rabin Karp KMP Boyer Moore Naive ========== LCSS 运行时间 O(mn) 子序列匹配使用的数据结构:2D Array HashMap =...

    Introduction_to_Algorithms:一些我自己的代码

    KMP字符串匹配: KMP字符串匹配,Linux源码中strstr函数的实现 二分查找: 二分查找,包括循环实现和非循环实现 矩阵链乘法: 计算矩阵相乘的最小次数以及输出如何相乘 最优二叉查找树: 求最优二叉查找树的期望搜索...

    ACM经典算法及例子

    一.数论 4 1.阶乘最后非零位 4 2. 模线性方程(组) 4 3. 素数表 6 4. 素数随机判定(miller_rabin) 6 5. 质因数分解 7 6. 最大公约数欧拉函数 8 ...11. 最大子串匹配 121 12. 最大子段和 122 13. 最大子阵和 123

    ACM常用算法代码 pdf

    目录 一.数论 4 1.阶乘最后非零位 4 2. 模线性方程(组) 4 3. 素数表 6 4. 素数随机判定(miller_rabin) 6 5. 质因数分解 7 6. 最大公约数欧拉函数 8 ...11. 最大子串匹配 121 12. 最大子段和 122 13. 最大子阵和 123

    ACM算法模板选doc

    算法,模板,ACM 十进制转任意进制 ...字符串KMP 凸包graham算法 输出最短路径 子集 拓扑排序 欧拉函数&素数筛选 Pick定理 高精度浮点数比较 日历 15数码是否有解 最大M子段和 MILLER_RABIN素性测试

    JavaScript中数据结构与算法(四):串(BF)

    串是由零个或多个字符组成的有限序列,又叫做字符串 串的逻辑结构和线性表很相似的,...这里主要讨论下字符串模式匹配的几种经典的算法:BF、BM、KMP BF(Brute Force)算法 Brute-Force算法的基本思想: 从目标串s 的

    ACM算法模板集锦(几何,结构,其他,数论,数值计算,图论)

    模式匹配(kmp) 逆序对数 字符串最小表示 最长公共单调子序列 最长子序列 最大子串匹配 最大子段和 最大子阵和 组合\ 排列组合生成 生成gray码 置换(polya) 字典序全排列 字典序组合 组合公式

    浙江大学ACM模板 计算几何,图论,数据结构,经典题的模板

    10.11 最大子串匹配 204 10.12 最大子段和 205 10.13 最大子阵和 206 11.其它 207 11.1 大数(只能处理正数) 207 11.2 分数 212 11.3 矩阵 214 11.4 线性方程组 216 11. 5 线性相关 218 11.6 日期 219 11.7 读入 220 ...

    ACM经典、常用代码

    这是我整理过的关于ACM题目常用到的算法代码,word文档,条理清晰,...6. 模式匹配(kmp) 7. 逆序对数 8. 字符串最小表示 9. 最长公共单调子序列 10. 最长子序列 11. 最大子串匹配 12. 最大子段和 13. 最大子阵和

    数据结构的钻石版 acm 模版

    13.6 模式匹配(kmp) 122 13.7 逆序对数 123 13.8 字符串最小表示 123 13.9 最长公共单调子序列 124 13.10 最长子序列 125 13.11 最大子串匹配 126 13.12 最大子段和 127 13.13 最大子阵和 127 14、 其它 128 14.1 ...

    leetcode走方格起点到终点-oj-program:oj-程序

    leetcode走方格起点到终点 LeetCode题目 动态规划,南大开放日2017年上机题。 中序遍历。 BST判断,中序遍历 交换节点,满足二叉搜索树。中序遍历的两种实现。...字符串匹配,kmp算法,得到匹配子串的个数 贪心算法

    浙江大学ACM模板(经典代码)

    13.6 模式匹配(kmp) 122 13.7 逆序对数 123 13.8 字符串最小表示 123 13.9 最长公共单调子序列 124 13.10 最长子序列 125 13.11 最大子串匹配 126 13.12 最大子段和 127 13.13 最大子阵和 127 14、 其它 128 14.1 ...

Global site tag (gtag.js) - Google Analytics