zoukankan      html  css  js  c++  java
  • 子字符串查找之Rabin-Karp算法

    1.背景

    1.1 算法简介:M.O.Rabin和R.A.Karp发明了一种基于散列的字符串查找算法。我们只需要计算模式串的散列函数,然后利用相同的散列函数计算文本中所有可能的M个字符的子字符串散列值并寻找匹配。如果找到了一个散列值和模式字符串相同的子字符串,再继续验证是否相同。这是一个有趣的算法,重点不在于其只用线性时间解决问题,而在于其对散列技术的使用,这是一个具有启发性的算法!

    1.2 结论:Rabin-Karp算法优点是运行速度为线性级别,缺点是内循环很长。(若干次算术运算,而其他算法都只需比较字符)

    2.思想

    2.1 基本思想:长度为M的模式串对应着一个R进制的M位数使用除留余数法构造一个能够将 R 进制的 M 位数转化为一个0到Q-1之间的 int 值的散列函数。我们在不溢出的情况下选择一个尽可能大的随机素数Q(因为我们并不真正需要一张散列表,故Q越大越好,冲突越少)。接下来对文本所有长度为M的子串计算散列值并寻找匹配。

    2.2 计算散列函数:思想很简单,只需将所有子串散列值求出,一个一个匹配即可,但是如果离线计算每个子串的散列值,时间复杂度与 M*N 成正比,故问题变成了如何在线性时间求出散列值。

    2.3 关键思想:既然子串长度确定为M(即模式串的长度M),那我们每次只需向后移动一位,将第一个元素去掉,最后一个元素加上即可更新散列值,再与模式串散列值比较即可。在这种情况下时间复杂度与N成正比。

    3.实现

    3.1 具体步骤:

    1. 得到模式串长度M
    2. 选择一个恰当的R,尽可能大的随机素数Q,构造散列函数
    3. 计算出模式串的散列值patHash
    4. 从文本第一个子串开始,依次向右移动,判断其散列值是否与patHash相等

    3.2 利用蒙特卡洛法验证正确性:在之前我们讲过,当散列值相同时我们再逐个比较字符是否匹配,以此来确保我们得到的是一个匹配而非仅仅散列值相同的子串,但是我们可以不这么做。假设我们取一个1e20数量级的素数Q,那么一个随机键的散列值与模式串冲突的概率就会小于1e-20。这足以确保答案的正确性,如果仍不放心,我们可以再运行一次,这样就下降到了1e-40,同理你可以将概率降到你满意的数值(牺牲时间)。

    3.3 代码示例:

    /*
    Rabin-Karp算法
    -ValenShi 
    只能匹配数字,选R为10,若想匹配其他字符,需要调整。 
    */ 
    #include<cstdio>
    #include<cstring>
    typedef long long ll;
    const int maxn = 1e5;
    const int R = 10;
    const int Q = 1e9+7;
    char pat[maxn],txt[maxn];
    int RM;
    ll qpow(ll a,ll b,ll M){
    	ll res = 1;
    	while(b){
    		if(b&1)	res = res*a%M;
    		a = a*a%M;
    		b >>= 1;
    	}
    	return res%M;
    }
    int charValue(char a[],int i){
    	return a[i]-'0';		//仅适用于数字 
    }
    ll hash(char a[],int m){
    	ll res = 0;
    	for(int i = 0;i < m;i++)
    		res = res*R + charValue(a,i);
    	return res;
    }
    bool check(int x){
    	return true;	//可在此对每个字符进行匹配 
    }
    int solve(){
    	scanf("%s",txt);
    	scanf("%s",pat);
    	int m = strlen(pat);
    	int n = strlen(txt);
    	RM = qpow(R,m-1,Q);
    	ll patHash = hash(pat,m);
    	ll txtHash = hash(txt,m);
    	if(txtHash == patHash)	return 0;
    	for(int i = 0;i < n-m;i++){
    		txtHash = (txtHash - RM*charValue(txt,i)%Q + Q)%Q;
    		txtHash = (txtHash*R + charValue(txt,i+m))%Q;
    		if(patHash == txtHash)
    		if(check(i-m+1))	return i+1;
    	} 
    	return n;				//未找到 
    }
    int main(){
    	printf("%d",solve());
    	return 0;
    }

    3.4 使用注意:与KMP比起来,这个算法理解起来简单一些(如果熟悉散列表的话),也有趣一些,但是该算法需要注意R的选取。如果仅仅匹配数字字符串,那么R大于10皆可,若包括各种字符,我们则需要为每个字符设定一个R进制的值(可以使用ASCII码),这时要求R要大于字符总数(否则有冲突)。相比之下KMP算法等其他算法则没这方面的缺陷。

    参考资料:

    《算法导论》原书第三版 P580

    《算法》第四版 [Robert Sedgewick] P505

  • 相关阅读:
    关于Slowloris HTTP DoS
    [转]更新SDK失败后,出现无法找到SDK location的解决方法
    [吐槽文]一篇课设小结
    无聊时候打打字之百度地图 【更新中】
    git初学 多多指教
    无聊的时候打打字之寒假项目小记录
    机会来了,创业你准备好了吗?
    技术高速发展,IT人员路在何方?
    开发Web应用程序常用优化性能的技巧
    DataTable中存在空值 输出到EXCEL时格式变乱的解决方法【附代码】
  • 原文地址:https://www.cnblogs.com/long98/p/10352152.html
Copyright © 2011-2022 走看看