字符串匹配之PabinKarp(模式匹配)

一、什么是字符串匹配呢?
给定一个母串,再给定一个子串,要在母串中找是否有与子串对应的字符串
例如:母串S:ABABAC
子串P:BAB
在这里插入图片描述
方法一:一般做法(时间复杂度很高)
利用指针Si和指针Pj作比较
如果它们相同就i+1,j+1;如果到某一个地方不同,称为失配。
当出现失配,把i退回之前标记的位置向后移动一个i+1的位置,j变为0回到起始点。一直重复上面操作。
直到i找到对的位置记为k,k位置和P0作比较,后面都符合匹配,两个指针一直向后移动,做++操作,当连续累计匹配个数等于P的长度时候,称为找到合适位置,返回m.
原因:假设S的长度为m,需要将母串S的字符都要作为起点,需要做m次匹配
假设P的长度为n
则它们的时间复杂度为0(n×m)
方法二:哈希法RabinKarp
hash-----滚动hash
思路:在子串P把字符串求出一个整数值,例如3个字符求出一个值;任意多个字符按一定顺序排列(字符序列,字符串)都可以求出一个值。这个值称为哈希值。
对S采用同样的算法,从起点开始,对每3字符序列的哈希值求出来。
算出来之后比对哈希值。
在母串S中求值只需要m次
在子串P中求值需要n次
先对P求哈希,o(n)
对S求哈希,o(m)
此时它们的时间复杂度仍为o(m×n),并未降低
求哈希hash
把若干个字符按进制的方法做一个转换,假设一个种子值为31进制,
在P子串中
C ^0 × 31^2+ C^1 ×31^1+C2
这样代码不好写,改进
((0+C0)×31+C1)×31+C2
什么是滚动hash,我求出第一个长度为n的子串的hash值,根据hash计算的特点,算第二个子串的hash值得时候不用再算,用第一个hash值×C(种子的值)+新增的字符元素值-第一个元素的值
在这里插入图片描述
在算每n个(在这道题里面是3个)子串hash值的时候,将它们组成一个数组。
先求P子串的hash值,再求S母串的hash,m次
有hash数组之后,把母串S数组扫描一遍,匹配与子串P相同hash值时m次
o(2m+n)
再这个题目中每三个元素的hash值,一开始需要三次,后面只需要1次
滚动hash:时间复杂度0(n+m)

  • 代码:
package rabinkarp;
import org.Lanqiao.rabinkarp.elementary.NExponent;
/*滚动hash法
 * 对目标字符串按d进制求值,mod h取余作为其hash
 * 对原串,依次求出m个字符的hash,保存在数组中(滚动计算)
 * 匹配时,只需比对目标串的hash值和预存的原串的hash值表*/
/*S:母串    P:子串*/
public class Rabinkarp {
	public static void main(String[] args) {
		String s="ABABABA";
		String p="ABA";
		match(p,s);
	}
	private static void match(String p,String s) {
		long hash_p =hash(p); //p的hash值
		int p_len=p.length();
		for(int i=0;i+p_len<=s.length();i++) {
			long hash_i=hash(s.substring(i,i+p_len));//i为起点,长度为plen的子串的hash值
			if(hash_i==hash_p) {
				System.out.println("match:"+i);
			}
		}
		long[] hashOfS=hash(s,p.length());
		match(hash_p,hashOfS);
	}
private static void match(long hash_p,long[] hash_s) {
	for(int i=0;i<hash_s.length;i++) {
		if(hash_s[i]==hash_p) {
			System.out.println("match:"+i);
		}
	}
}
final static long seed=31;
//n是子串的长度
//用滚动hash法求出s中长度为n的每个子串的hash,组成一个hash数组
static long[] hash(final String s,final int n) {
	long[] res=new long[s.length()-n+1];
	//前面m个字符的hash值
	res[0]=hash(substring(0,n));
	for(int i=n;i<s.length();i++) {
		char newChar=s.charAt(i);
		char oldchar=s.charAt(i-n);
		//前n个字符的hash值*seed-前n字符的第一个字符元素*seed的n次方
		long v=(res[i-n]*seed+newChar-NExponent.ex2(seed,n)*oldchar)%Long.MAX_VALUE;
		res[i-n+1]=v;
	}
	return res;
}
private static String substring(int i, int n) {
	// TODO Auto-generated method stub
	return null;
}
/*使用100000个不同字符串产生的冲突数,大概在0-3波动,使用100百万不同的字符串,冲突数大概在100+范围波动*/
static long hash(String str) {
	long h=0;
	for(int i=0;i!=str.length();i++) {
		h=seed*h+str.charAt(i);
	}
	return h%Long.MAX_VALUE;
	
}
}

发布了137 篇原创文章 · 获赞 7 · 访问量 2万+

猜你喜欢

转载自blog.csdn.net/gl620321/article/details/105477049
今日推荐