字符串匹配算法(KMP, BM, Sunday)

最近学习了一下字符串匹配的一些算法,在这里做一个总结。最简单的暴力匹配就不说了。

我们检查在目标串中是否存在模式串。

1.KMP算法

class KMP{
	private int next[];
	private String pattern;
	private int size;
	
	public KMP(String p)
	{
		pattern = p;
		size = p.length();
		next = new int[size+1];
		getNext();
	}
	
	private void getNext()//求next数组
	{
		int i = 0;
		int j = -1;
		next[0] = -1;
		while(i < size)
		{
			while(j >= 0 && pattern.charAt(i)!= pattern.charAt(j) )j=next[j];
			++i;
			++j;
			next[i] = j;
		}
	}
	public int KMPMatch(String t)
	{
		int lenp = size;
		int lent = t.length();
		int i = 0, j = 0;
		while(i < lent && j < lenp)
		{
			if(j == -1 || t.charAt(i) == pattern.charAt(j))
			{
				i++;j++;
			}
			else j = next[j];
		}
		if(j == lenp)
			return i - j; //匹配成功
		else return -1; //匹配失败
	}
	
}

KMP在最坏情况下也有着O(n+m)的复杂度,也就是在线性时间内能够完成匹配,但是有人说该算法和strstr函数的速度差不多。

本人水平有限,就不具体说算法的原理了,有一篇写的还不错的学习KMP的文章,推荐一下,KMP算法

2.BM算法

BM算法相对于KMP比较好理解,同时,它的最优时间复杂度可以达到O(n/m),其中n为目标串的长度,m为模式串的长度。最坏时间复杂度为O(nm),但是总体来说BM算法的表现要比KMP算法好,我们通常用的ctrl + F 就是用BM算法实现的。

该算法将模式串从后往前与目标串匹配,匹配过程遵循以下两个规则(可以只用其中一个规则,或者两个规则中选择移动距离最长的规则):

(1)坏字符规则:

在当前字符失配的情况下(假设目标串中失配字符为 X,X在目标串的位置为 j ),则检查模式串中是否存在 字符, 

i: 如果不存在,那么显然X之前的字符串都不可能和模式串匹配。所以我们直接移动m – j + 1个字符来跳过X字符。举个例子:

                    0 12 3 4 5 6 7 8 9 10 11

                 …a c b d b c a b c a  b   c

 pattern[ ]   a b c a b c

  匹配到 d->a 时,出现失配,这个时候为了避开d字符,我们向后移动 6 – 3 + 1个字符,进行下一轮匹配。

ii: 如果模式串中存在X字符,那么我们就找到模式串中最右端的X,假设其位置为pos,将其与目标串中的X与模式串中第pos个字符对齐。

    然后从后往前继续进行匹配。例如:

                   0 12 3 4 5 6 7 8 9 10 11

                 …a c b b b c a b c a  b   c

 pattern[ ]   a b c a b c

移动后:          a
b c a b c

规则的实现:进行预处理,记录每个字符出现的最右端的位置。(后面有代码)

(2)好后缀规则:

有两种情况:

 i: 举个例子来说明

                   0 1 2 3 4 5 6 7 8 9 10 11

                   d d d d b c a b c

pattern[]     d b c a b c

我们从后往前匹配,b c匹配成功,这个时候我们称b c好后缀

这个时候我们检查模式串中是否存在好后缀,如果存在,我们需要的是最右边的好后缀的起始位置。在例子中,该位置为 1。

这个时候我们将找到的位置 1 与 当前失配位置的后一个位置(即 4) 对齐。实质是将模式串中bc与目标串中的bc对齐。

可以得到:

                   0 12 3 4 5 6 7 8 9 10 11

                   d d d d b c a b c

pattern[]     d b c a b c

     d
b c a b c

 ii: 有了第一种情况,第二种情况就好解释了。假设我们已经得到了好后缀,并且在整个模式串中都没有好后缀,但是,模式串的开始有一部分是好后缀的子串(我们称之为 start),此时我们将 start 和 好后缀中相应的部分对齐。例子:

                   0 1 2 3 4 5 6 7 8 9 10 11

                   d d d d c a b …

pattern[]     a b c  c c a b

                                    
a b c c c a b

该例中 a b 即为start。   

有了这两个规则我们就可以进行匹配了。代码如下:

class BM{
	
	private int[] right;//用来记录每个字符在最右端的位置.
	private int[] goodSuffix;//goodSuffix[i]记录若i-1处字符失配时,应该移动的位数。
	private int[] t;
	private String pattern;
	private int size;
	
	public BM(String p) {
		pattern = new String(p);
		size = pattern.length();
		right = new int[256];
		t = new int[size+1];
		goodSuffix = new int[size+1];
		countRight();
		countGoodSuffix1();
		countGoodSuffix2();
	}
	
	private void countRight() // 坏字符规则的预处理
	{
		for(int i = 0; i < 256; i++)
			right[i] = -1;
		for(int i = 0; i < size; i++)
			right[pattern.charAt(i)] = i;
	}
	
	private void countGoodSuffix1()
	{
		int i = size, j = size + 1;
		t[i] = j;
		while(i >= 0)
		{
			while(j <= size && pattern.charAt(i-1) != pattern.charAt(j-1)) 
			{
				if(t[j] == 0) goodSuffix[j] = j-i;
				j = t[j];
			}
			i--;j--;
			t[i] = j;
			
		}
	}
	
	private void countGoodSuffix2() {
		int i;
		int j = t[0];
		for(i = 0; i < size; i++)
		{
			if(goodSuffix[i] == 0) goodSuffix[i] = j;
			if(i == j) j = t[j];
		}
	}
	
	public int Match(String a)
	{
		int lena = a.length();
		int skip;
		for(int i = 0; i <= lena - size; i += skip)
		{
			skip = 0;
			for(int j = size - 1; j >= 0; j--)
			{
				if(a.charAt(i) != pattern.charAt(j))
				{
					skip = right[a.charAt(i)] > goodSuffix[j]?
							right[a.charAt(i)]:goodSuffix[j];
					break;
				}	
			}
			if(skip == 0) return i;
		}
		return -1;
	}
}

3.Sunday算法(待续)

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