字符串匹配(Karp-Rabin算法)

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;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值