KMP算法
首先要说的一个问题是关于单个模板串在文本串中的匹配问题。给定一个模板串S,和一个文本串P,我们怎么得到S在P中出现的位置呢?
我们关键要解决的问题是,当模板串与文本串中的某一子串不匹配时,模板串下个要比较的子串应该在哪个位置?这里便有了next数组的出现。
我们用next[i]表示子串s[0…i]有多大长度的相同前缀后缀。下面是next函数得到的代码。具体流程如下:
1.在一个循环中以i=1到i=n-1的顺序计算next函数next[i]的值。
2.为了计算当前的next函数值next[i],我们令变量j表示右端点位于i-1的最好的后缀的长度。初始时j=next[i-1]。
3.通过比较s[j]和s[i]来检查长度为j+1的后缀是否同时也是一个前缀。如果两者相等,那么我们置next[i]=j+1,否则我们减少j至next[j-1]并且重复该过程。
4.如果j=0并且仍没有任何一次匹配,则置next[i]=0并且至下一个下标i+1.
void Get_Next(char* s){
int i, j;
n = strlen(s);
next[0] = 0;
for(i = 1;i < n;i++){
j = next[ i - 1];
while(j > 0 && s[i] != s[j]) j = next[j - 1];
if(s[i] == s[j]) j++;
next[i] = j;
}
return ;
}
我们会到问题本身,我们要注意一下几点:
1.我们的失配数组应当建立在模式串意义下,而不是文本串意义下。因为显然模式串要更加灵活,在失配后换位时,更灵活简便地处理。
2.我们的操作只是针对模式串的前缀−−–−−毕竟是失配函数,失配之后只有可能是某个部分前缀需要“快速移动”。
具体代码如下:
lens = strlen(s+1), lenp = strlen(p+1);//s是文本串,p是模板串
for(i = 2;i <= lenp;i++){
j = next[i-1];
while(j && p[i] != p[j+1]) j = next[j];
if(p[j+1] == p[i]) j++;
next[i] = j;
}//得到模板p的next数组
j = 0;
for(i = 1;i <= lens;i++){
while(j > 0 && p[j+1] != s[i]) j = next[j];
if(p[j+1] == s[i]) j++;
if(j == lenp) printf("%d\n",i - lenp + 1), j = next[j];
}
for(i = 1;i <= lenp;i++) printf("%d ",next[i]);
哈希
字符串哈希的用处在于给定一堆长度不一的字符串,区别出其中互异的元素。
具体做法见下:
int idx(char x){
return x - 'a' + 1;
}//这里主要是为了得到每个字符所代表的值。
ll hash(char* s){
ll Hash = 0;
int i, len = strlen(s);
for(i = 0;i < len;i++){
Hash = (Hash * p + idx(s[i]))%mod;
} //其中p和mod取得尽量大可以保证冲突概率小
return Hash;
}//求解字符串的哈希值
//子串哈希值:hash(l,r)=((hash[r]-hash[l-1]*pow(p,r-l+1))%mod+mod)%mod
求解出的哈希值一般存在一个set集合中,使用set中的count函数即可判断是否有重复了。
AC自动机
由于KMP算法只能实现单个模板串在文本串中的匹配,无法高效的完成多个模板串在文本串中的匹配,因此AC自动机应运而生了。
AC自动机主要分为三部分:
1.用模式串构建字典树
int trie[maxn][26];//字典树
int cntword[maxn];//单词出现的个数
int fail[maxn];
void insertWords(char* s){
int i, root = 0, l = strlen(s);
for(i = 0;i < l;i++){
int next = s[i] - 'a';
if(!trie[root][next]) trie[root][next] = ++cnt;
root = trie[root][next];
}
cntword[root]++;
}
2.找fail指针
当发现失配的字符失配的时候,跳转到fail指针指向的位置,然后再次进行匹配操作,AC自动机之所以能实现多模式匹配,就归功于Fail指针的建立。当前节点t有fail指针,其fail指针所指向的节点和t所代表的字符是相同的。因为t匹配成功后,我们需要去匹配t->child,发现失配,那么就从t->fail这个节点开始再次去进行匹配。
void getFail(){
queue q;
int i;
for(i = 0;i < 26;i++){
if(trie[0][i]){
fail[trie[0][i]] = 0;
q.push(trie[0][i]);
}
}
while(!q.empty()){
int now = q.front();
q.pop();
for(i = 0;i < 26;i++){
if(trie[now][i]){
fail[trie[now][i]] = trie[fail[now]][i];
q.push(trie[now][i]);
}
else trie[now][i] = trie[fail[now]][i];
}
}
}
3.文本串的匹配
int query(char* s){
int now = 0, ans = 0, i, j, l = strlen(s);
for(i = 0;i < l;i++){
now = trie[now][s[i] - 'a'];
for(j = now;j && cntword[j] != -1;j = fail[j]){
ans += cntword[j];
cntword[j] = -1;
}
}
return ans;
}//这里实现的是,有多少模板串在文本串中出现,当然AC自动机还可以实现模板串出现次数的统计,暴力跳fail的代码和这个差不多。
关于AC自动机用拓扑排序优化的坑后面再补吧。