更高效的字符串匹配算法——shift-and
在接触这个算法之前,一直觉得kmp巧夺天工,利用next数组的递推,实现对于模式串任一子串最大相同前后缀的找寻,继而在匹配目标串的过程中,一旦遇到失配情况,可以令 匹配起始下标 进行合理范围内最大的跳跃,从而将匹配整体复杂度从O(nm)降为O(m+n)。
a b c a b c ........
a b c a b k 可从目标串第二个 a 处开始匹配
shift-and算法与其思路基本相同,同样是找寻最大匹配长度,只不过它的实现基于更为方便快捷的位运算,算法的实现需要用到<bitset>这个头文件,点击查看其功能用法。bitset类实现了通过类似数组下标的方式访问对象的每一个bit位,极大方便了对于该对象储存信息的提取。
算法大致思路:
1.根据模式串每位的字符建立字符表,如 a 对应的(bitset<5>)"00101",表示在模式串的1,3位上可以是字符a。
2.初始化储存结果的bitset对象 D 为"00000",表示没有一个字符成功匹配,(即没有一个字符既是完整模式串的前缀,又是当前目标串的后缀,通俗来讲就是,失配后起始位要老老实实挪到下一位,跳不成)。之后则不断从目标串中读入字符,并套用公式
D=(D<<1|1)&B[s[i]];
表示将原结果左移一位,将[1]位置为1,表示对于新的一位匹配的期望,如果B[s[i]]第一位刚好也为1,则D串中即可更新。
该&操作的意义是:(D<<1|1)试图将所有匹配位均向左增加一位,如果新加入的字符在模式串中在该位出现过,则匹配串升级成功。
3.时刻检查,如果D中n位为1,则证明出现一组匹配结果。
下面展示样题:
2016 大连ICPC B
数字串匹配,模式串(长度小于1000)中的每个位上,均有若干个可选项,给定一个较长的目标串(长度小于5*1e6),输出所有匹配结果。
#include<iostream>
#include<bitset>
#include<cstdio>
#include<cstring>
using namespace std;
bitset<1001> b[10],ans;
char s[5000005];
int n,len;
int main()
{
cin>>n;
char ch;
for(int i=1; i<=n; i++)
{
int m;
scanf("%d",&m);
for(int j=0; j<m; j++)
{
int x;
scanf("%d",&x);
b[x][i]=1;
}
}
scanf("%s",s+1);
len=strlen(s+1);
for(int i=1; i<=len; i++)
{
ans[1]=1;
ans&=b[s[i]-'0'];
if(ans[n]==1)
{
ch=s[i+1];
s[i+1]=0;
puts(s+i-n+1);
s[i+1]=ch;
}
ans<<=1;
}
return 0;
}
压着超时的线通过,通过这道题发现直接用puts比一个一个putchar快不少。