字符串哈希及KMP

字符串很神奇,因为它在计算机中应用很广泛,就每一个程序都需要用到字符串,所以学好字符串是非常重要的。

接下来就介绍两个字符串的基本操作

1:字符串hash  一种可以查找几个字符串有几个不同的字符串。

其精髓就是把一堆字符串都转换成几个数字的和的形式。

要领就是把字符串每一位字母的阿斯克码不会拼啊都乘上一个比较神奇的数,再%上一个神奇的数,比如你的生日啊,qq号什么的,尽量是质数,太大了也不行,会爆炸的。

van成之后,再把这个值都存进一个数组中,最后查的时候直接用就是了。

为什么这样就可以实现查找不同的字符串呢。

因为这相当于给每个字符串一个编号,且这个编号就是每个字符串的每个位数乘上一个上文提到的很神奇的数,来保证不会出现编号重了的现象。

如果这样你还是觉得不保险,你还可以双hash,这样成功率会大大增加,但还是不排除出现错误,这种情况又叫哈希冲突。

好了,解释完毕上代码

#include<iostream>
#include<string>
#include<cstdio>
#include<map>
using namespace std;
const int mod=;
int n,hash[],total;
map<int,int>m;
string s;
int find_hash(string x)
{
int ans=;
for(int i=;i<x.size();i++)
{
ans=(ans*%mod+((int)x[i]))%mod;
}
return ans;
}
int main()
{
scanf("%d",&n);
for(int i=;i<=n;i++)
{
cin>>s;
hash[i]=find_hash(s);
}
for(int i=;i<=n;i++)
{
if(m[hash[i]])continue;
m[hash[i]]=;
total++;
}
printf("%d",total);
}

2:KMP字符串匹配算法

既然知道了如何查找不同的算法,那么接下来就要实现KMP字符串匹配了,

KMP字符串匹配就是指 给出两个字符串s1和s2,其中s2为s1的子串,求出s2在s1中所有出现的位置。

当然就需要引进一个数组叫next数组也叫子串的前缀数组。

next数组指什么呢,指这个串的最长的前缀与后缀相等的长度。

比如说

下标:01234

值:   ABABA

的next数组是

下标:01234

值:   00123

因为字符串的第一位的下标默认为0.

所以我们也把下标弄成和字符串一样的格式.

有了这个数组有什么用呢?

我们想如果这个字符串失配了,那这个位置的子串字符一定不与父串字符相等,但是之前的所有字符都相等。

所以我们就匹配到之前的相等的字符处

比如:

父串:ABABCABABA

子串:   ABABA

当匹配到s1[4] 和 s2[4]时(以字符串的下标)s1[4]!=s2[4]

我们就要回到之前的位置,那之前的位置在哪呢,你可能会想应该在next[4]这里吧,但是你看如果在next[4]这里,那么s2[现在失配的位置]=s2[next[4]]那我们这样做有什么用呢照样失配,

所以我们应该回到next[i-1]的位置,或者让next的下标集体+1,这就是有些KMP的代码中有现在失配的位置=next[现在失配的位置]。

还有一点,第0位的next数组是算不出来的,所以我们找next数组的函数中的循环要从1开始。

但是如果父串的这个位置与任何的前缀都不行,那就只能放弃这个位置了,让父串的位置+1.

代码:

#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;
char a[1000010],b[1000010];
int n,m,j,next[1000010];
void pre()
{
next[1]=0;
j=0;
for(int i=1;i<m;i++)
{
while(j>0&&b[i]!=b[j])
j=next[j-1];
if(b[i]==b[j])
j++;
next[i]=j;//这一步在上一步的原因是,nex[i]是i失配后要跳到的不等于s[i-1]和s[i]的一个字符的位置,所以j要加1之后b[nex[i]]就不等于b[i]了
}
}
void kmp()
{
for(int i=0;i<n;i++)
{
while(j>0&&a[i]!=b[j])
j=next[j-1];
if(a[i]==b[j])
j++;
if(j==m)
{
cout<<i+1-(m-1)<<endl;//j要返回到上次的位置,这样可以使i重复利用,因此不能使j=0;
}
}
}
int main()
{
cin>>a;
cin>>b;
n=strlen(a);
m=strlen(b);
pre();
j=0;
kmp();
for(int i=0;i<m;i++)
cout<<next[i]<<" ";
}

  

上一篇:【数据库】mysql深入理解乐观锁与悲观锁


下一篇:c#委托中的匿名方法和lambda表达式