字符串模式匹配算法小结


参考文章:July 的从头到尾彻底理解KMP


字符串模式匹配:在一个文本(长字符串)中找出一个或多个指定的字符串(Pattern),并返回其位置。


下面介绍几种字符串模式匹配算法:Brute-Force算法、KMP算法、BM算法、Sunday算法

文本字符串S,索引 i ,模式字符串P,索引 j

字符串匹配算法之BF算法:

    基本思路:假设文本串S比较到i位置,模式串P比较到j位置。

                     如果当前字符匹配成功(S[i] == P[j]),那么继续向下比较,i ++,j++。

                     如果当前字符匹配失败(S[i] != P[j]),那么将文本串的索引 i ,回溯到 i - j +1位置(回到模式串开头对应的文本串的位置的下一个位置,就是将模式串相对于文本串右移1位),然后 模式串的索引 归0,即:i = i - j +1;j=0。

  如果文本串的长度为n,模式串的长度为m,那么BF的时间复杂度为O(m *n)。

代码:

/*
	 * BF算法:暴力匹配
	 * 
	 */
	
	public static int BFSearch(String s,String p){
		char[] ss = new char[s.length()];
		char[] pp = new char[p.length()];
		s.getChars(0, s.length(), ss, 0);
		p.getChars(0, p.length(), pp, 0);
		return BFSearch(ss,pp);
		
	}
	
	/**
	 * BF算法的具体实现:
	 * 
	 * @param s 文本串

 * @param p 模式串
	 * @return 如果搜索成功,返回p在s中的索引,否则返回 -1;
	 */

	private static int BFSearch(char[] s, char[] p) {
		int i = 0;
		int j =0;
		while(i<s.length && j <p.length){
			if(s[i] == p[j]){
                            //如果当前字符匹配成功,则继续匹配下一个
                               i++;
				j++;
			}else{
                          // 如果当前字符匹配失败,则将文本串索引回掉置i-j+1位置,j = 0;
                          //相当于模式串向右移动一位,从模式串开始字符再进行比较。
                               i = i-j+1;
				j =0;
			}
		}
		if(j<p.length){
			return -1;
		}else{
			return i-j;
		}
	
	}

字符串匹配算法之KMP算法:

   算法的基本流程:

          假设现在文本串S匹配到 i 位置,模式串匹配到 j 位置。

          如果 j = -1,或者当前字符匹配成功(即 S[ i ] == P[ j ]),则继续向下匹配,i ++, j ++.

          如果  j != -1, 并且当前字符匹配失败(即S[ i ]  != P[ j ] ),则保持文本串的索引 i 的位置不变, j =next [ j ]。

      相当于模式串相对与文本串右移 j - next [ j ] 位。

即:当匹配失败时,模式串向右移动的位数 = 失配字符所在位置 - 失配字符对应的next值,即 移动的位数 = j - next[ j ]。


关于next 数组的求解后面叙述。

代码:

	public static int KMPSearch(String s,String p){
		
		char[] ss = new char[s.length()];
		char[] pp = new char[p.length()];
		s.getChars(0, s.length(), ss, 0);
		p.getChars(0, p.length(), pp, 0);
		return KMPSearch(ss,pp);
		
		
	}
/**
 * KMP算法具体实现
 * @param s 文本串
 * @param p 模式串
 * @return
 */
	private static int KMPSearch(char[] s, char[] p) {
		int slen = s.length;
		int plen = p.length;
		int i = -1;//文本串索引
		int j = -1;//模式串索引
		int[] next = getNext(p);//next 数组
		while(i <slen && j<plen){
			if(j == -1 || s[i] == p[j]){
				//如果 j = -1 或者 s[i] == p[j](当前字符匹配成功),则继续向下匹配
				i++;
				j++;
			}else{
				//如果 j != -1 并且s[i] != p[j](当前字符匹配失败),则保持文本串的索引位置不变,令 j = next[j],
				//相当于模式串右移 j-next[j]个位置。
				
				j = next[j];
			}			
		}
		if(j < plen){
			return -1;
		}else{
			return i - plen;
		}
		
	}

下面叙述next数组的求解方法:

第一种: 先寻找前缀后缀最长公共元素的长度,然后将上一步求的数值整体向右移动一位,就是next 数组从1 ~ plen-1所对应的值,next[0] = -1.

第二种:代码递推求的。已知 next[0] = -1,并且next[ j ] = k,然后递推求解 next[ j +1] = ?

代码:

/*
 * 计算模式串P的next数组,并且规定:
 * next[0] = -1;
 * 注意:next[j] = k :表示的是pj之前的字符串的前后缀具有长度为k 的公共子序列
 */
	private static int[] getNext(char[] p) {
		
		int plen = p.length;
		int[] next = new int[plen];
		int k = -1;//前缀索引
		int j = 0;//后缀索引
		next[0] = -1;
		while(j < plen-1){ 
			if(k == -1 || p[k] == p[j]){
				k++;
				j++;
				/*
				 * 原始的next 数组求法只有下面一行: next[j] = k;不存在if else 
				 */
				//next[j] =k;
			/*
			 * 对next数组进行了优化,
			 * p[j]失配时,若p[j] == p[next[j]],则	p[next[j]]必然也会失配。所以为了避免这种情况,
			
			 */
				//p[k]表示前缀,p[j]表示后缀
				if(p[k] != p[j]){
					next[j] = k;
				}else{
				
					next[j] = next[k];//若p[k] == p[j],(p[j]在p[k]后面,也就是next[j]是要靠next[k]递推而来的),
					//那么当p[j]与s[i]发生失配时,根据KMP算法,会将p[next[j]] 与s[i]进行相等比较,由于已知若p[j] == p[k](next[j] = k),
					//那么p[k]必定与s[i]失配,所以为了避免此操作,在获得next[j]时,不应该将k赋值给它,而应该将next[k]赋给next[j],若p[next[k]]还等于p[j],
					//那么就继续重复上述操作,直到k == -1 || p[k] != p[j]为止。
				}
				
			}else{
				k = next[k];
			}
		}
		
		
		return next;
	}
	

关于字符串的前缀后缀最长公共序列的长度与next 数组的关系:


假设模式字符串为: P0,P1,P2,。。。,Pj-1,Pj

(1)字符串的前缀后缀最长公共序列的长度:

       对于上述模式字符串,若存在   P0,P1,P2 ...Pk-1,Pk = Pj-k,Pj-k+1,....,Pj-1,Pj ,那么在包含Pj 的模式字符串P有最大长度为k+1的相同前缀后缀。

(2)求next 数组

      next[ j] = k,表示的当前模式串P中P[j]之前的字符串(即P0 ....Pj-1)有最大长度为k 的前缀后缀公共序列。

所以:将(1)求的数值整体后移一位,就是对应的next数组的值,并规定next[0] = -1.

      如果文本串的长度为n,模式串的长度为m,那么匹配过程的时间复杂度为O(n),算上计算next的O(m)时间,KMP的整体时间复杂度为O(m + n)。

字符串匹配算法之BM算法:

      KMP算法的匹配是从模式串的开头就开始匹配的,BM算法是从模式串的尾部开始匹配的,最坏时间复杂度为O(n),

在实践中,比KMP算法的效率高。

BM算法中有两个规则:

    坏字符规则:当文本串中的某个字符与模式串中 的某个字符不匹配时,文本中的该字符就称为坏字符。此时模式串需要向右移动,移动的位数 = 坏字符在模式串中的位置 - 坏字符在模式串中最右出现的位置。此外,如果"坏字符"不包含在模式串之中,则最右出现位置为-1。

   好后缀规则:当字符失配时,后移位数 = 好后缀在模式串中的位置 - 好后缀在模式串上一次出现的位置,且如果好后缀在模式串中没有再次出现,则为-1。


字符串匹配算法之Sunday算法

Sunday 算法何BM算法很相似,BM算法是从后往前匹配,Sunday算法是从前往后匹配,并且失配的时候关注的是文本串中参加匹配的最末尾的字符的下一位字符。

            如果该字符没有在模式串中出现,则直接跳过,即移动的位数 = 匹配串的长度+1;

          否则,移动的位数 = 模式串中最右端的该字符到末尾的距离 +1.   

举例:

        文本串:  substring searching algorithm

          模式串: search

(1)开始时,把模式串与文本串对其,从文本串的开头进行匹配,匹配第一个字符S,S匹配成功,继续下一个

                      substring searching algorithm

                       search

    (2)第二个字符 u,与e 不匹配,失配时关注的是文本串中参与匹配的最末尾字符的下一位字符,即i  ,查找i 在模式串中最靠右的位置,模式串中没有i,那么模式串要移动的位数 =  模式串的长度 +1 = 6 +1 = 7;所以模式串右移7位,移动到 与 i 的下一位 n 对其的位置。

                     substring searching algorithm

                     search

                     substring searching algorithm

                                      search

(3)然后从n 开始重新匹配,此时第一个字符 n就不匹配,所以关注文本串中参与匹配的最末尾的字符的下一个字符 : r,r 在模式串中的最靠右的位置到末端字符的距离是2 ,所以模式串要右移的位数 = 模式串中最右端的该字符到末尾的距离 +12 +1 = 3.是两个r对其

                 substring searching algorithm

                                               search


(4)匹配成功。












de

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值