数据结构——串的模式匹配算法

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/Never_Blue/article/details/71562846

2、串的模式匹配算法

       串的查找操作也称作串的模式匹配操作,模式匹配操作的具体含义是:在主串(也称作目标串)中,从位置start开始查找是否存在子串(也称作模式串),如在主串中查找到一个与模式串相同的子串,则称查找成功;如在主串中为查找到一个与模式串相同的子串,则称查找失败。当模式匹配成功时函数返回模式串的第一个字符在主串中的位置,当模式匹配失败时返回-1。

2、1 Brute-Force算法

       Brute-Force算法实现模式匹配的思想是:设主串为s=" s 0 s 1 …s n-1 ",模式串为t=" t 0 t 1 …t n-1 "。
(1)从主串s的第一个字符开始和模式串t的第一个字符比较,若相等则继续比较后续字符。
(2)若主串s的第一个字符和模式串t的第一个字符比较不相等,则从主串s的第二个字符开始重新与模式串t的第一个字符串比较,若相等则继续比较后续字符。
(3)如此不断继续。若存在模式串t中的每个字符依次和主串s中的一个连续字符序列相等,则模式匹配成功,函数返回模式串t的第一个字符在主串s中的下标;若比较完主串s的所有字符序列,不存在一个和模式串t相等的子串,则模式匹配失败,函数返回-1。

public class BruceForce {
	public static int bruceForce(String str , String subStr) {
		int result = 0;
		int len = str.length();
		int lenSub = subStr.length();
		if (lenSub > len) {
			result = -1;
		}
		int i = 0 , j = 0;
		while (i < len && j < lenSub) {
			if (str.charAt(i) == subStr.charAt(j)) {
				i ++;
				j ++;
			}
			else {
				i = i - j + 1;
				j = 0;
			}
		}
		if (j == lenSub) {
			result = i - lenSub + 1;
		}
		return result;
	}
	
	public static void main(String[] args) {
		String str = "cddcdc";
		String subStr = "cdc";
		int result = bruceForce(str, subStr);
		if (result > 0) {
			System.out.println("pos = " + result);
		}
		else if (result == 0) {
			System.out.println("未找到!");
		}
		else if (result == -1) {
			System.out.println("子串比主串长");
		}
	}
}
输出结果为:
pos = 4
       这个算法简单并易于理解,但是有些情况下时间效率并不高。主要原因是:在主串和子串已有相当多个字符比较相等的情况下,只要有一个字符比较不相等,便需要把主串的比较位置(即函数中变量i的值)回退。设主串的长度为n,子串的长度为m,则Brute-Force算法在最好情况下的时间复杂度为O(m),在最坏的情况下的时间复杂度为O(n*m)。

2、2 KMP算法

1、Bruce-Force算法的缺点以及解决方法分析

        KMP算法是Brute-Force算法基础上的改进算法。KMP算法的特点主要是,消除了Brute-Force算法的主串比较位置在相当多字符比较相等后,只要有一个字符比较不相等,主串位置便需要回退的特点。
       分析Brute-Force算法的匹配过程可以发现,算法中的主串比较位置的回退并非一定比较。这可分为以下两种情况。
(1)第一种情况如上节的图中所示。主串s="cddcdc"、模式串t="cdc"的模式匹配过程为:当s0= t0,s1= t1,s1≠t1时,算法中下一次的比较位置为i=1,j=0,接下来比较s1和t0。但是因为t0≠t1,而s1=t1,所以一定有s1≠t0。所以此时比较s1和t0无意义,实际上随后可直接比较s2和t0
(2)第二种情况,主串s="abacabab"、模式串t="abab"的第一次匹配如下图所示。此时有 s0= t0='a',s1= t1='b',s2= t2='a',s3≠t3。因此有t0≠t1,s1= t1,所以必有s1≠t0。又因有t0= t2,s2= t2,所以必有s2=t0。因此下面可以直接比较s3和t1


       总结以上两种情况可以发现,一旦si和tj比较不相等时,主串s的比较位置不必回退,主串的si可直接和模式串的t k (0≤k<j)比较,k的确定与主串s并无关系,k的确定只与模式串t本身的构成有关,即从模式串本身就可以求出k的值。
       现在讨论一般情况。设s=" s 0 s 1 …s n-1 ",t="t 0 t 1 …t m-1 ",当模式匹配比较到s i ≠t j (0≤i<n,0≤j<m)时,必存在"s i-j s i-j+1 …s i-1 "="t 0 t 1 …t j-1 "。①若此时模式串"t 0 t 1 …t j-1 "中不存在任何"t 0 t 1 …t k-1 "="t j-k t j-k+1 …t j-1 "(0<k<j)形式的真子串,则说明在模式串 " t 0 t 1 …t j-1 "中不存在任何以为t 0 首字符的字符串与主串 " s i-j s i-j+1 …s i-1 "中分别以 s i-j s i-j+1 、...、 s i-1 为首字符的字符串匹配,下一次可直接比较 s i t 0 。②此时若模式中存在如 " t 0 t 1 …t k-1 "=" t j-k t j-k+1 …t j-1 "形式的真子串,则说明模式中的子串已经和主串匹配,下次可以直接比较 s i t k

2、KMP算法的改进

分析式"t0t1…tk-1"="tj-ktj-k+1…tj-1"(0<k<j),模式串中是否存在可相互重叠的真子串,只与模式串自身有关,与主串无关。因此,对于主串s="s0s1…sn-1",子串,t="t0t1…tm-1",可以首先计算出模式串t中每个字符的最大榛子穿的字符个数k。当模式匹配到si≠tj(0≤i<n,0≤j<m)时,随后要比较的主串的下标值不变,模式串的下边值即为k。

3、模式串中最大真子串的求法

       模式串中每个字符的最大真子串构成一个数组,定义为模式串的next[j]函数。模式串的next[j]函数定义如下:

       next[j]函数表示的是模式串t中是否存在最大真子串,以及最大真子串的字符个数k。这里之所以称为最大真子串,是因为:①求出的是所有子串中最大子串;②不允许k等于j。
             next[j]定义中的第一种情况,是在模式串"t0t1…tj-1"中存在这样两个长度均小于j的字符串,其中一个字符串以t0为首字符,另一个字符串以tj-1为末字符,满足 "t0t1…tk-1"="tj-ktj-k+1…tj-1", 且这样的相等子串是所有这种相等子串中长度最大的。
        next[j]定义中的第二种情况,是在模式串 " t 0 t 1 …t j-1 " 中不存在任何满足"t0t1…tk-1"="tj-ktj-k+1…tj-1"条件的真子串。
        next[j]定义中的第三种情况,是当j=0是给出的特殊取值。当j=0是,令netx[j]函数取值为-1。在函数这几种,当netx[j]=-1时,令主串的下标和模式串的下标同时增1,即随后用子串的第一个字符和当前字符的下一个字符进行比较。

4、KMP函数设计

       KMP函数中,当模式串t中的tj与主串s的si(i≥j)比较不相等时,若模式串t中不存在如上所说的真子串,有next[j]=0,则下一次比较si和t0,这是第一种情况;若模式串t存在真子串"t0t1…tk-1"="tj-ktj-k+1…tj-1",且满足0<k<j,则有next[j]=k,则下一次比较主串s的si和子串t的tk,这是第二种情况;当j=0时有next[j]=-1,则令主串的下标和模式串的下边同时增1,即随后用主串s当前字符的下一个字符和子串t的t0比较。
       KMP函数可按如下方法设计:设s为主串,t为模式串,i为主串当前比较字符的下标,j为模式串当前比较字符的下标。令i的初值为start,j的初值为0。当si=tj时,i和j分别增1再继续比较;否则i不变,j改为next[j]值在继续比较。笔记哦啊过程中有两种情况:一是j增加到某个值或j退回到某个j=next[j]值时有si=tj,则此时i和j分别增1再继续比较;二是j退回到j=-1时,令主串和子串的下标各增1,随后比较si+1t0。这样的循环过程知道变量i大于等于主串s的长度或变量j大于等于子串t的长度终止。

5、计算next[j]值的函数设计

next[j]值的计算问题是一个递推计算问题。设有next[j]=k,即模式串t中存在,其中k为满足等式的最大值"t 0t 1…t k-1"="t j-kt j-k+1…t j-1"(0<k<j),则计算next[j+1]的值有以下两种情况。
(1)若t k=t j,则表明在模式串中有 "t 0 t 1 …t k "="t j-k t j-k+1 …t j ",且不可能存在任何一个k'>k满足上式,因此有:next[j+1]=next[j]+1=k+1。
(2)若 t k ≠t j ,则可把计算next[j+1]值的问题看成是把模式串t'向右滑动至k'=next[k](0<k'<k<j)。若此时tk'=tj,则表明在模式串t中有 "t0t1…tk'"="tj-k'tj-k'+1…tj"(0<k'<k<j)有:next[j+1]=k'+1=next[k]+1。若此时tk'≠tj,则将模式串t'右滑到k''=next[k']后继续匹配。以此类推,直到某次比较有tk=tj(此即为上述情况),或某次比较有tk≠tj且k=0,此时有:next[j+1]=0。

6、代码示例

public class KMP {
	public static int indexKMP(String str , String subStr , int start) { //str表示主串,subStr表示子串,start表示开始比较的下标
		int[] next = getNext(subStr);
		int i = start , j = 0 , result;
		while ( i < str.length() && j < subStr.length() ) {
			if ( (j == -1) || (str.charAt(i) == subStr.charAt(j)) ) {
				i++;
				j++;
			}
			else
				j = next[j];
		}
		if(j == subStr.length())
			result = i - subStr.length() + 1;
		else
			result = -1;
		return result;
	}
	
	public static int[] getNext(String subStr) {
		int j = 1 , k = 0;
		int[] next = new int[subStr.length()];
		
		next[0] = -1;
		next[1] = 0;
		while (j < subStr.length() - 1) {
			if (subStr.charAt(j) == subStr.charAt(k)) {
				next[j + 1] = k + 1;
				j++;
				k++;
			}
			else if ( k == 0 ) {
				next[j + 1] = 0;
				j++;
			}
			else 
				k = next[k];
		}
		return next;
	}
	
	public static void main(String[] args) {
		String str = "cddcdc";
		String subStr = "cdc";
		int result = indexKMP(str , subStr , 0);
		if ( result == -1 ) 
			System.out.println("Not find!");
		else 
			System.out.println("pos = " + result);
	}
}
输出结果为:
pos = 4











猜你喜欢

转载自blog.csdn.net/Never_Blue/article/details/71562846
今日推荐