Karp-Rabin
时间复杂度
O(n+m)
凡物皆数
正如普通字符串一样,在匹配时,我们需要逐字比较文本串字符和模式串字符是否相等。散列、将一般类型的对象(词条)与自然数(散列地址)之间建立起联系。
在此,我们将任一有限长度的整数向量视作自然数,进而在字符串与自然数之间建立联系。
比如,由大写字母组成的字母表,若将这些字符依次映射为[1,26]内的自然数,则每个这样的字符串都对应于一个26+1=27进制的整数。
“CANTOR”= < 3 , 1 , 14 , 20 , 15 , 18 >
“DATA”= < 4 , 1 , 20 , 1 >
字符串经如此转换所得到的散列码,称作其指纹。
“判断模式串P是否与文本串匹配”的问题可以转化为“判断T中是否有某个子串与模式串P拥有相同的指纹 ”。只要逐一取出T中长度为m的子串,并将其所对应的指纹与P所对应的指纹进行比较,即可确定是否存在匹配位置。若存在匹配位置,还需要进一步进行逐字比对,以排除由于散列压缩产生散列冲突的情况。
散列压缩
首先我们需要明确他的必要性。对于二进制串,基数取2;对于十进制串,基数取10;对于ASCII字符串,基数取128或256。当对ASCII字符串进行指纹计算时,如果模式串稍长,指纹长度便会突破计算机支持的32~64位字长。随着字符串长度m的增大,指纹的计算和比对所用时间都将增大。无法在O(1)时间完成,甚至回退至O(n*m)蛮力算法。
因此需要进行散列压缩。将指纹的数值压缩至一个可以接受的范围。Hash(key)=key % M。
若取散列表长度M=97;十进制串“82818”,其对应指纹为77(82818 % 97 = 77)。
散列冲突
由于压缩,必然会引起冲突。也就是不同的字符串可能会对应同样的指纹。
如:
hash(71828)=48
hash(18284)=48
此时需要进行逐字比较。
快速指纹更新
由于计算文本串m长度的指纹时需要每次后移一位,因此两次相邻比对所对应的字串之间存在极强的联系。
可根据前一子串的指纹,在常数时间内得到后一子串的指纹。
#include<iostream>
using namespace std;
#define M 97
#define R 256
#define DIGIT(S,i) ((S)[i]-'0')
typedef __int64 HashCode;
int match(char* p, char* t);
int* buildbc(char* p);
bool check1by1(char* p, char* t, int i);
void updatehash(HashCode& hashT, char* t, int m, int k, HashCode Dm);
HashCode prepareDm(int m);
int main()
{
char* title = "541464jty341";
char* name = "jty";
cout << "目标字符串首字母地址 : " << match(name, title) << endl;
system("pause");
return 0;
}
int match(char* p,char* t)
{
int m = strlen(p);
int n = strlen(t);
HashCode Dm = prepareDm(m), hashP = 0, hashT = 0;
for (int i = 0; i < m; i++)
{
hashP = (hashP*R + DIGIT(p, i)) % M;
hashT = (hashT*R + DIGIT(t, i)) % M;
}
for (int k = 0;;)
{
if (hashT == hashP)
if (check1by1(p, t, k))
return k;
if (++k > n - m)
return k;
else updatehash(hashT, t, m, k, Dm);
}
}
bool check1by1(char* p, char* t, int i)
{
for (int m = strlen(p), j = 0; j < m; j++, i++)
{
if (p[j] != t[i])
return false;
}
return true;
}
void updatehash(HashCode& hashT, char* t, int m, int k, HashCode Dm)
{
hashT = (hashT - DIGIT(t, k - 1)*Dm) % M;
hashT = (hashT*R + DIGIT(t, k + m - 1)) % M;
if (hashT < 0)
{
hashT += M;
}
}
HashCode prepareDm(int m)
{
HashCode Dm = 1;
for (int i = 1; i < m; i++)
{
Dm = (Dm*R) % M;
}
return Dm;
}