字符串匹配问题:在源串中找模板串的下标。
RabinKarp:
哈希法。
一般法:PabinKarp
1,对模板字符串x按d进制求值,可以再mod h作为其的hash,
2,在对源串s,依次求出s.length-x.length个hash,
3,匹配两者的hash
时间复杂度:O(mn)
static List<Integer> ans=new ArrayList<>();
public static void main(String[] args) {
// TODO Auto-generated method stub
String a="abababababa";
String b="aba";
long hashb=hash(b);
match(a,b,hashb);
for(int i:ans)System.out.print(i+" ");
}
public static void match(String a,String b,long hashb)
{
for(int i=0;i+b.length()<=a.length();i++)
{
long hasha=hash(a.substring(i,i+b.length()));
if(hasha==hashb)
{
ans.add(i);
}
}
}
public static long hash(String b)
{
long ans=0;
for(int i=0;i<b.length();i++)
{
ans=ans*31+b.charAt(i);
}
return ans;
}
优化,RabinKarp(滚动哈希)
在原理的基础上,优化求哈希值。
时间复杂度为O(m+n)
public class 哈希求字符匹配滚动法 {
static long mod=Long.MAX_VALUE;
static long seed=31;//进制
public static void main(String[] args) {
// TODO Auto-generated method stub
String a="abababababa";
String b="aba";
int lenb=b.length();
long hashb=hash(b);
long res[]=new long[a.length()-lenb+1];//0-4 0-1 0-3 5-2+1=4
for(int i=0;i<lenb;i++)
{
res[i]=hash(a.substring(i,i+lenb));//存放每一位对应的hash
}
for(int i=lenb;i<res.length;i++)
{
char newchar=a.charAt(i);
char oldchar=a.charAt(i-lenb);
long nexthash=(long) (res[i-lenb]*seed-(a.charAt(i-lenb)*Math.pow(seed, lenb))+a.charAt(i))%mod;
res[i]=nexthash;
}
List<Long> ans=new ArrayList<>();
for(int i=0;i<res.length;i++)
{
if(res[i]==hashb)
ans.add((long) i);
}
for(long i:ans)
System.out.print(i+" ");
}
public static long hash(String b)
{
long ans=0;
for(int i=0;i<b.length();i++)
{
ans=(ans*seed+b.charAt(i))%mod;
}
return ans;
}
}
KMP
后缀数组
定义:
所有后缀子串按字典排序后,在数组中记录排名和在子串中的下标
处理回文
哈希 O(n)
#include <bits/stdc++.h>
using namespace std;
#define ull unsigned long long
#define fir(i,a,b) for(int i=a;i<=b;i++)
#define fic(i,a,b) for(int i=a;i>=b;i--)
#define Mod 131 //P进制
const int N=1000007;
char s[N];
ull f1[N],f2[N],p[N];
int ans,t,l,r,mid;
ull Hash1(int i,int j)//正字符串的哈希值
{
return (f1[j]-f1[i-1]*p[j-i+1]);
}
ull Hash2(int i,int j)//反字符串的哈希值
{
return (f2[i]-f2[j+1]*p[j-i+1]);
}
void init()
{
p[0]=1;//p^0为1
fir(i,1,N-1)
p[i]=p[i-1]*131;//P进制的位值
}
int main()
{
init();
while (++t)
{
ans=0;
scanf("%s",s+1);
int len=strlen(s+1);
if (strcmp(s+1,"END")==0) //结束读入
return 0;
f2[len+1]=0;//初始化要注意,不然的话容易GG
fir(i,1,len)
f1[i]=f1[i-1]*Mod+(s[i]-'a'+1);//前缀和
fic(i,len,1)
f2[i]=f2[i+1]*Mod+(s[i]-'a'+1);//后缀和
fir(i,1,len)
{
l=0,r=min(i-1,len-i);//二分枚举长度为奇数的字符串 记住这里l一定要为0,不然的话,你会发现最后一个数据会卡死你.
while(l<r)
{
mid=l+r+1>>1;
if (Hash1(i-mid,i-1)==Hash2(i+1,i+mid))//如果这是一个回文串的话
l=mid;
else
r=mid-1;
}
ans=max(l<<1 | 1,ans);//算出最大长度
l=0,r=min(i-1,len-i+1);//偶数字符串
while (l<r)
{
mid=l+r+1>>1;
if (Hash1(i-mid,i-1)==Hash2(i,i+mid-1))//check判断
l=mid;
else
r=mid-1;
}
ans=max(l<<1,ans);//偶数字符串只需要*2
}
printf("Case %d: %d\n",t,ans);
}
return 0;
}
Malacher O(n)
char a[N];
char b[N];
int p[N];
void init() // a[]为原串,b[]为插入'#'后的新串
{
int k = 0;
b[k ++ ] = '$', b[k ++ ] = '#';
for (int i = 0; i < n; i ++ ) b[k ++ ] = a[i], b[k ++ ] = '#';
b[k ++ ] = '^';
n = k;
}
void manacher() // 马拉车算法,b[]为插入'#'后的新串
{
int rt = 0, mid;
int ans=0;
for (int i = 1; i < n; i ++ )
{
if (i < rt) p[i] = min(p[mid * 2 - i], rt - i);
else p[i] = 1;
while (b[i - p[i]] == b[i + p[i]]) p[i] ++ ;
if (i + p[i] > rt)
{
rt = i + p[i];
mid = i;
}
ans = max(ans,p[i]-1);
}
cout<<ans;
}