java KMP 字符串匹配算法

KMP算法的关键是利用匹配失败后的信息,尽量减少模式串与主串的匹配次数以达到快速匹配的目的。具体实现就是实现一个next()函数,函数本身包含了模式串的局部匹配信息。

下面讲一下我的个人思路:

首先是next()函数的实现,然后求出next【】数组。假如我们现在需要求的是: 字符串B: “ABCDABD字符串A:“ABCDABBBC-ABCDAB-ABCDABCDABDE   中出现的次数,把B分解为7个字符串:

A

AB

ABC

ABCD

ABCDA

ABCDAB

ABCDABD

在求的每个字符串的前缀以后缀共有元素的长度

B的前后缀的度:

  “A”的前缀和后缀都为空集,共有元素的长度为0

  “ABC”的前缀为[A],[AB],后缀为[BC],[C],共有元素的度为0

  “ABCD”的前缀为[A],[AB],[ABC],后缀为[BCD],[CD][D],共有元素的度为0;

  “ABCDA”的前缀为[A],[AB],[ABC],[ABCD],后缀为[BCDA],[CDA][DA],[A],此时注意:前缀和后缀有了相同的元素[A],那么此时的度为1

  “ABCDAB”的前缀为[A],[AB],[ABC],[ABCD],[ABCDA],后缀为[BCDAB],[CDAB]  [DAB],[AB],[B],共有元素为[AB],度为2

  “ABCDABD”的前缀为[A],[AB],[ABC],[ABCD],[ABCDA],[ABCDAB],后缀为[BCDABD],[CDABD][DABD],[ABD],[BD],[D],共有元素的度为

 那么现在已经明白了,我们要求的next数组就是串B的前后缀的度的集合,上面可得度的集合为:{0,0,0,0,1,2,0}

 那么我们要求的next数组即为int[] next={0,0,0,0,1,2,0};

 对应位与度值得关系

  字符串B  C   D  A     D

     |       |     |    |        |

      度:  0   0     1   2   0

   当在A中查找B的时候,如果第一个字符相同,继续进行字符串A的第二个字符与字符串B的第二个字符进行比较,当查找到第7个时,也就是此时查找A的下标和B的下标为6时,字符串A的第七个字符为‘—’而B的第七个字符为‘D’此时比较结果不相同,那么看字符串B的第七个字符(为‘D’)的前一个字符在next数组中的匹配值,此时B的第七个字符的前一个字符为‘B’,‘B’在next数组中的对应的值为2,说明字符串A的第七个字符(为‘—’)前面的两个字符与字符串A中的前两个字符对应,此时应该把字符串B向右移动4位。继续比较字符串A的第七个字符(为‘—’)与字符串B的第三个字符

移动位数=已匹配的字符数—对应部分的匹配值

此时已匹配字符数为:6

对应部分的匹配值(字符串A的第七个字符(为‘—’)的前一个字符的匹配值)为:2

得出移动位数:4

    移动位数为4,就是让字符串B的第三个字符与字符串A的第七个字符(为‘—’)进行比较,那么就可以得出此时查找字符串B的下标为

查找字符串B的下标=已匹配的字符数—移动位数

即此时查找字符串B的下标为:2

   也就是此刻应该让字符串A的第七个字符(为‘—’)与字符串B的第三个字符(为‘C’)进行比较,然后依次进行比较至结束。文字有点绕口,看下面图形分析再结合文字会很好:

1.kmp算法的原理:

  本部分内容转自:点击打开链接

字符串匹配是计算机的基本任务之一。

举例来说,有一个字符串“BBC ABCDAB ABCDABCDABDE”,我想知道,里面是否包含另一个字符串“ABCDABD”

1.

《java KMP 字符串匹配算法》

首先,字符串“BBC ABCDAB ABCDABCDABDE”的第一个字符与搜索词“ABCDABD”的第一个字符,进行比较。因为BA不匹配,所以搜索词后移一位。

2.

《java KMP 字符串匹配算法》

因为BA不匹配,搜索词再往后移。

3.

《java KMP 字符串匹配算法》

就这样,直到字符串有一个字符,与搜索词的第一个字符相同为止。

4.

《java KMP 字符串匹配算法》

接着比较字符串和搜索词的下一个字符,还是相同。

5.

《java KMP 字符串匹配算法》

直到字符串有一个字符,与搜索词对应的字符不相同为止。

6.

《java KMP 字符串匹配算法》

这时,最自然的反应是,将搜索词整个后移一位,再从头逐个比较。这样做虽然可行,但是效率很差,因为你要把搜索位置移到已经比较过的位置,重比一遍。

7.

《java KMP 字符串匹配算法》

一个基本事实是,当空格与D不匹配时,你其实知道前面六个字符是“ABCDAB”KMP算法的想法是,设法利用这个已知信息,不要把搜索位置移回已经比较过的位置,继续把它向后移,这样就提高了效率。

8.

《java KMP 字符串匹配算法》

怎么做到这一点呢?可以针对搜索词,算出一张《部分匹配表》(Partial Match Table)。这张表是如何产生的,后面再介绍,这里只要会用就可以了。

9.

《java KMP 字符串匹配算法》

已知空格与D不匹配时,前面六个字符“ABCDAB”是匹配的。查表可知,最后一个匹配字符B对应的部分匹配值2,因此按照下面的公式算出向后移动的位数:

  移动位数 已匹配的字符数 – 对应的部分匹配值

因为 6 – 2 等于4,所以将搜索词向后移动4位。

10.

《java KMP 字符串匹配算法》

因为空格与C不匹配,搜索词还要继续往后移。这时,已匹配的字符数为2“AB”),对应的部分匹配值0。所以,移动位数 = 2 – 0,结果为 2,于是将搜索词向后移2位。

11.

《java KMP 字符串匹配算法》

因为空格与A不匹配,继续后移一位。

12.

《java KMP 字符串匹配算法》

逐位比较,直到发现CD不匹配。于是,移动位数 = 6 – 2,继续将搜索词向后移动4位。

13.

《java KMP 字符串匹配算法》

逐位比较,直到搜索词的最后一位,发现完全匹配,于是搜索完成。如果还要继续搜索(即找出全部匹配),移动位数 = 7 – 0,再将搜索词向后移动7位,这里就不再重复了。

14.

《java KMP 字符串匹配算法》

下面介绍《部分匹配表》是如何产生的。

首先,要了解两个概念:前缀后缀。 前缀指除了最后一个字符以外,一个字符串的全部头部组合;后缀指除了第一个字符以外,一个字符串的全部尾部组合。

15.

《java KMP 字符串匹配算法》

部分匹配值就是前缀后缀的最长的共有元素的长度。以“ABCDABD”为例,

  - “A”的前缀和后缀都为空集,共有元素的长度为0

  - “AB”的前缀为[A],后缀为[B],共有元素的长度为0

  - “ABC”的前缀为[A, AB],后缀为[BC, C],共有元素的长度0

  - “ABCD”的前缀为[A, AB, ABC],后缀为[BCD, CD, D],共有元素的长度为0

  - “ABCDA”的前缀为[A, AB, ABC, ABCD],后缀为[BCDA, CDA, DA, A],共有元素为“A”,长度为1

  - “ABCDAB”的前缀为[A, AB, ABC, ABCD, ABCDA],后缀为[BCDAB, CDAB, DAB, AB, B],共有元素为“AB”,长度为2

  - “ABCDABD”的前缀为[A, AB, ABC, ABCD, ABCDA, ABCDAB],后缀为[BCDABD, CDABD, DABD, ABD, BD, D],共有元素的长度为0

16.

《java KMP 字符串匹配算法》

//求next数组

public static void getNext(char[] P, int[] next){//P为需要查找的数组
		int c,k;	//c为模板字符串的下标,k为最大度的长度
		int l=P.length;	//模板字符串的长度
		next[0]=0;	//模板字符串的第一个字符的前后缀的度 为0
		for(c=1,k=0;c<l;c++){	//从模板字符串的第二个长度开始,依次计算每个字符对应的next值
			while(k>0&&P[c] !=	P[k] ){
				k=next[k-1];
			}
			if(P[c] ==	P[k]){
				k++;
			}
			next[c]=k;
			
		}
	}

查找T中存在P的个数

public static int kmp(char[] T,char[] P,int[] next){
		int m,n;	//m,需要搜索的字符串的长度,n需要进行在T中查找的T的字符串的长度
		int i,q;	//q,需要搜索的字符串的下标,i是T的下标
		int count=0;	//查找到的次数
		n=T.length;
		m=P.length;
		getNext(P,next);
		for(i=0,q=0;i<n;i++){
			while(q>0&&P[q]!=T[i]){
				/*
				 * 据此例:当第一次i==6时,q==6,此时T[i]为c,P[q]为D,并不相同,
				 * next【q-1】为当前匹配到字符的上一个字符的度
				 * 根据P[q]=='D'的上一个字符'B'的度next【q-1】==2可知P[q]为D的前面两个字符都相同,
				 * P移动的位数= 已匹配的字符数 - 对应的部分匹配值】
				 * 此时已匹配q==6个
				 * 计算得出字符串P应该移动的位数==q-next【q-1】=4,此时应该向后移动4为,移动4位之后,当前q的位置应该为2,
				 * 即 q = q-(q-next【p-1】),简化得:q=next[q-1]
				 * 只需要把当前P[q]
				 */
				
				q=next[q-1];	
			}
			if(P[q]==T[i]){
				q++;
			}
			if(q==m){
				count++;
				q=0;
			}
		}
		return count;
	}

测试:

public class KMP {
	public static void main(String args[]){
	String str="ABCDABc-BBC-ABCDAB-ABCDABCDABDE-ABCDABD";
	String st="ABCDABD";
	char[] cstr=str.toCharArray();
	char[] cst=st.toCharArray();
	int[] next=new int[st.length()];
	//getNext(cst,next);
	int count=kmp(cstr,cst,next);
	System.out.println(count);
	}

终于弄清原理了,还是要多画啊,若有不对请指出,大家一起交流哈
《java KMP 字符串匹配算法》~

    原文作者:KMP算法
    原文地址: https://blog.csdn.net/sun_ru/article/details/51822484
    本文转自网络文章,转载此文章仅为分享知识,如有侵权,请联系博主进行删除。
点赞