内容简介:Rabin-Karp算法(也可以叫Karp-Rabin算法),由Richard M. Karp和Michael O. Rabin在1987年发表,它也是用来解决多模式串匹配问题的。它的实现方式有点与众不同,首先是计算两个字符串的哈希值,然后通过比较这两个哈希值的大小来判断是否出现匹配。选择一个合适的哈希函数很重要。假设文本串为
一:背景
Rabin-Karp算法(也可以叫Karp-Rabin算法),由Richard M. Karp和Michael O. Rabin在1987年发表,它也是用来解决多模式串匹配问题的。
它的实现方式有点与众不同,首先是计算两个字符串的哈希值,然后通过比较这两个哈希值的大小来判断是否出现匹配。
二:算法分析与实现
选择一个合适的哈希函数很重要。假设文本串为 t[0, n)
,模式串为 p[0, m)
,其中$0<m<n$,$Hash(t[i,j])$代表字符串 t[i, j]
的哈希值。
当$Hash(t[0, m-1])!=Hash(p[0,m-1])$时,我们很自然的会把$Hash(t[1, m])$拿过来继续比较。在这个过程中,若我们重新计算字符串 t[1, m]
的哈希值,还需要$O(n)$的时间复杂度,不划算。观察到字符串 t[0, m-1]
与 t[1, m]
中有$m-1$个字符是重合的,因此我们可以选用滚动哈希函数,那么重新计算的时间复杂度就降为$O(1)$。
Rabin-Karp算法选用的滚动哈希函数主要是利用 Rabin fingerprint
的思想,举个例子,计算字符串 t[0, m - 1]
的哈希值的公式如下,
$$
Hash(t[0, m-1])=t[0]\ast\,b^{m-1}+t[1]\ast\,b^{m-2}+...+t[m-1]\ast\,b^0\tag{t[0]代表该字符的ASCII码}
$$
其中的$b$是一个常数,在Rabin-Karp算法中,我们一般取值为$256$,因为一个字符的最大值不超过$255$。上面的公式还有一个问题,哈希值如果过大可能会溢出,因此我们还需要对其取模,这个值应该尽可能大,且是质数,这样可以减小哈希碰撞的概率,在这里我们就取$101$。
则计算字符串 t[1, m]
的哈希值公式如下,
$$
Hash(t[1,m])=(Hash(t[0,m-1])-t[0]\ast\,b^{m-1})\ast\,b+t[m]\ast\,b^0\tag{请仔细对比上式}
$$
完整代码如下,
#include <iostream>
#include <string.h>
using namespace std;
#define BASE 256
#define MODULUS 101
void RabinKarp(char t[], char p[])
{
int t_len = strlen(t);
int p_len = strlen(p);
// 哈希滚动之用
int h = 1;
for (int i = 0; i < p_len - 1; i++)
h = (h * BASE) % MODULUS;
int t_hash = 0;
int p_hash = 0;
for (int i = 0; i < p_len; i++)
{
t_hash = (BASE * t_hash + t[i]) % MODULUS;
p_hash = (BASE * p_hash + p[i]) % MODULUS;
}
int i = 0;
while (i <= t_len - p_len)
{
// 考虑到哈希碰撞的可能性,还需要用 memcmp 再比对一下
if (t_hash == p_hash && memcmp(p, t + i, p_len) == 0)
cout << p << " is found at index " << i << endl;
// 哈希滚动
t_hash = (BASE * (t_hash - t[i] * h) + t[i + p_len]) % MODULUS;
// 防止出现负数
if (t_hash < 0)
t_hash = t_hash + MODULUS;
i++;
}
}
int main()
{
char t[100] = "It is a test, but not just a test";
char p[10] = "test";
RabinKarp(t, p);
return 0;
}
输出如下,
test is found at index 8 test is found at index 29
三:复杂度分析
首先看空间复杂度,很容易判断,$S(n)=O(1)$。
再来看时间复杂度,取文本串长度为$n$,模式串长度为$m$,预处理需要$O(m)$,在匹配过程中,最佳情况下,未出现哈希碰撞,$T_{best}(n)=O(n-m)$,最坏情况下,每次都出现碰撞,$T_{worst}(n)=O((n-m)*m)$,因为在现实生活中,$n$往往远大于$m$,因此最后的复杂度表格为,
| $S(n)$ | $O(1)$ |
|---|---|
| $T_{best}(n)$ | $O(n)$ |
| $T_{worst}(n)$ | $O(nm)$ |
以上就是本文的全部内容,希望本文的内容对大家的学习或者工作能带来一定的帮助,也希望大家多多支持 码农网
猜你喜欢:- 通俗易懂--决策树算法、随机森林算法讲解(算法+案例)
- 限流算法之漏桶算法、令牌桶算法
- 什么是Paxos算法?Paxos算法是区块链核心算法之一
- 一文读懂对称加密算法、非对称加密算法和Hash算法
- 算法(六):图解贪婪算法
- 算法篇03:排序算法
本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们。
Node.js开发实战
[美] Jim R. Wilson / 梅晴光、杜万智、陈琳、纪清华、段鹏飞 / 华中科技大学出版社 / 2018-11-10 / 99.90元
2018年美国亚马逊书店排名第一的Node.js开发教程。 . Node.js是基于Chrome V8引擎的JavaScript运行环境,它采用事件驱动、非阻塞式I/O模型,具有轻量、高效的特点。Node.j s 工作在前端代码与 数据存储层之间,能够提高web应用的工作效率和 响应速度。本书以最新版Node.js 8为基础,从实际案例出发 讲解Node.js的核心工作原理和实用开发技......一起来看看 《Node.js开发实战》 这本书的介绍吧!