`
ritlv97d
  • 浏览: 16761 次
社区版块
存档分类
最新评论

如何高效地进行远程大规模字符串比较问题

 
阅读更多

  关键字 (keywords):大规模 字符串 匹配 远程 比较 快速     
  随着互联网的快速发展,信息量成爆炸趋势,大规模的文本处理已经成为一个挑战,今天这里我想解决一个海量数据中会经常遇到的一个问题,就是如何在两台主机之间进行高效地大规模字符串比较问题,如果给定100MB字符串A和1GB字符串B分别在远程在两台主机上,那我想比较A是否是B的字串?
  
  怎么办呢?很明显,我们用一般的算法是无法解决这个问题的。因为如果是一般的算法,肯定是先传送这两个字符串到同一台机子上,然后再用KMP等算法进行字符串比较,我想大家都知道其实这样是非常耗时的,我下面给出了我的解法,使用的算法是随机算法,解决的大致如下:
  先将A字符串转为01串,转换后,设01串的长度为lenA,然后计算该字符串的指纹,指纹是一个整数,在这里的指纹即能唯一表示A的字符串,如同每一个人的都有自己的手指指纹一样,是唯一的。
  同样将B串转为10串,转换后,假设01串的长度为lenB,那么它的指纹个数为(lenB-lenA+1)
  如下图所示:
  
  从上图中,可以看到,B串共有n个指纹。
  那么怎么计算这个指纹呢?
  一般计算指纹如下:令I(x)是x的编码,去Ip(x)=I(x)(mod p)作为x的指纹。
  Ip(x)是x的指纹
  I(x)是x的编码,就是我们说的原来的字符串
  p是一个小于M的素数,M可根据具体需要调整,这里取M=2*n^2
  Ip(x)等于I(x)%p
  其实就是把所有01串转为一个大数然后mod p 后的结果就是指纹
  我的算法如下:用java实现  /** * @param chs : 01串 * @param p :大素数 * @param m :A串的长度 * @return :返回指纹 * 时间复杂度:O(m) */ public static long getFirstFingerprint(char[] chs, long p, int m) { //把01串转为一个大数然后mod p 得出来的就是指纹 BigInteger fingerprint = BigInteger.valueOf(0); BigInteger pow = BigInteger.valueOf(1); BigInteger bp = BigInteger.valueOf(p); //这部分的计算就是转为一个大数 for (int i = m - 1; i >= 0; i--) { if (chs[i] - '0' == 1) { fingerprint = fingerprint.add(pow); } //System.out.println(pow); pow = pow.multiply(BigInteger.valueOf(2)); } //mod p fingerprint = fingerprint.mod(bp); return fingerprint.longValue(); } 首次计算时用上面的算法,再计算第二个时候用下面另外一种方法,主要基于第一种方法的结果计算得到
  
  我的算法实现如下: /** * @param firstFingerprint : 前一个指纹 * @param xj : 前一个01串的第一位 * @param xjm: 前一个01串的后边一个,如1000010, * 前一个01串是100001,那么后边一个就是0了,因此,xjm就是0 * @param p: 大素数 * @param m: A串的长度 * @return 下一串的指纹 * 时间复杂度:O(1) */ public static long getNextFingerprint(long firstFingerprint, int xj, int xjm, long p, int m) { BigInteger bi = BigInteger.valueOf(2); long wp = 0; if (xj != 0) { BigInteger exp = BigInteger.valueOf(m); BigInteger mod = BigInteger.valueOf(p); bi = bi.modPow(exp, mod); wp = bi.longValue(); } long ret = (2 * firstFingerprint - wp + xjm) % p; if (ret < 0) ret += p; return ret; } 另外一个问题是快速求得大素数p,p是小于M,但与M(M=2n^2)相近的素数,这里我用了Miller-Rabin的一个改进算法
  时间复杂度为O(k*(log(n))^3),n是M的值,k与素数的误判率有关,误判率为1/2^k,如果k取100,基本上就已经不可能出现错误了
  我将抽取时间写另外一篇blog是关于大素数高效判断的方法,也是随机算法。
  至此,问题也就解决了,其他的只需要把主程序写一下就行了,我的主程序如下:
  这里假设两个串都在一台机器上,传输被忽略了,如要模拟,其实只须传输一个指纹到另一台主机就行了  //获得M相邻的素数p //这里的算法我会在下一次blog写下 public static long getNeighborPrimeFast(long M) { long p = --M; boolean isPrime = false; while(!isPrime) { isPrime = Prime.isPrime((int)p); if (isPrime == false) p = --M; } return p; } //随机产生01串 public static char[] generateCode(int num) { char tmp[] = {'0', '1'}; char[] chs = new char[num]; for (int i = 0; i < num; i++) { chs[i] = tmp[r.nextInt(2)]; } return chs; } public static void main(String[] args) { //A串(01串) 长度为15 随机产生01串(由于是随机产生,太大的话很难与B串匹配) char[] ys = StringMatcher.generateCode(15); //B串(01串) 长度为500000 随机产生01串 char[] xs = StringMatcher.generateCode(500000); int n = xs.length; int m = ys.length; long M = 2 * n * n; long p = StringMatcher.getNeighborPrimeFast(M); long[] fingerPrints = new long[n - m + 1]; long fingerYs; fingerPrints[0] = StringMatcher.getFirstFingerprint(xs, p, m); fingerYs = StringMatcher.getFirstFingerprint(ys, p, m); for (int i = 1; i < fingerPrints.length; i++) { fingerPrints[i] = StringMatcher.getNextFingerprint(fingerPrints[i - 1], xs[i - 1] - '0', xs[i + m - 1] - '0', p, m); } int cnt = 1; for (int i = 0; i < fingerPrints.length; i++) { if (fingerPrints[i] == fingerYs) { System.out.println("count:" + cnt++ + " " + i); } } } 测出结果:与A相同的指纹,准确率(1-1/2^100)
  count:1 29747
  count:2 50706
  count:3 83590
  count:4 96803
  count:5 103301
  count:6 229482
  count:7 236235
  count:8 246710
  count:9 334959
  count:10 353036
  count:11 363681
  count:12 384086
  count:13 388068
  count:14 417645
  count:15 482496
  count:16 483673
  count:17 487611
  count:18 494533
分享到:
评论

相关推荐

Global site tag (gtag.js) - Google Analytics