首先讲解一下KMP算法,KMP算法是讲的是单模式字符串匹配算法。由于传统的字符串匹配算法中,当发现匹配不满足要求时,模式串将会回到开头,而待匹配字符串也有回到相对应的匹配位子的下一个,重新进行匹配。而Kmp算法则是利用一个next函数(表示字符串的自我覆盖程度)。当匹配不成功时,待匹配字符串的比较位置将不移动,而是利用next函数来计算模式串应该要计较的位子(由于next函数特性,模式串从起始位子到next()前一个位子都是和主串已经匹配了)。
其简单实现代码如下:
//KMP 算法
#include<iostream>
#include<string>
using namespace std;
//用来求字符串test模式函数,表示其自我覆盖的程度,用递归的方式实现
int next(const string& test)
{
int size = test.size();
if(size==1) //当为单字符时则自动表示为-1.无覆盖
return -1;
if(size==2)
return (test[0]==test[1])-1;
//递归的开始
string middle = test.substr(0,size-1);
int index = next(middle);
if(index>=0&&test[index+1]==test[size-1])
return index + 1;
else
return (test[0]==test[size-1])-1;
}
//实现KMP算法,返回字串在主串中的位子
int KMP(const string& s,const string& l)
{
int target_size = s.size();
int parttern_size = l.size();
int i,j=0;
for(i=0;i<target_size;i++)
{
if(s[i]==l[j])
{
j++;
if(j==parttern_size)
{
cout<<"匹配成功"<<endl;
return i-parttern_size+1;
}
}
//表示匹配失败时,KMP的处理方式
else
{
if(j>0)
{
string temp = l.substr(0,j);
if(j==4)
cout<<temp<<endl;
j=next(temp)+1;
i--;
}
// if(j==0) //如果是模型串第一个都不匹配则按照普通串来基选
}
}
return 0;
}
int main()
{
cout << KMP("aaaabaaa","baa")<< endl;
return 0;
}
接着来看看trie树,trie树在处理大量字符串时有相当的优势,其经常用于排序,存储大量字符串。特别是计算字符串出现的频率(搜索引擎中查找热门词汇)或字符串前最比较
好比假设有b,abc,abd,bcd,abcd,efg,hii 这6个单词,我们构建的树就是如下图这样的:
![]()
其实现代码如下:(在该代码中就是在存储字符串的同时还会检测树中是否存在该字符串)
//trie树的实现 #include<stdio.h> #include<stdlib.h> #include<iostream> #include<string> using namespace std; #define MAXSIZE 26 typedef struct Trie { struct Trie* child[MAXSIZE]; char word; bool flag; }Trie,*PTrie; //插入 bool insert(PTrie root,string ptr); int main() { PTrie root; //初始化该trie根节点(空节点) root=(PTrie)malloc(sizeof(Trie)); root->flag=false; string ss; while(cin>>ss) { cout<<ss<<"是否在树中:"<<insert(root,ss)<<endl; } return 0; } //边查询边插入字符串 bool insert(PTrie root,string ss) { bool sign = true ; //默认单词是在其中的 PTrie temp=root; string::iterator ptr=ss.begin(); while(ptr!=ss.end()) { int position=*ptr-'a'; //cout<<temp->child[position]<<endl; if(!temp->child[position]) //用来共享前缀 { temp->child[position]=(PTrie)malloc(sizeof(Trie)); sign = false; //创建分支证明不在 } temp->child[position]->word=*ptr; ptr++; temp=temp->child[position]; } if(temp->flag!=true) { temp->flag=true; sign=false; //因为flag不是true所以也不在,但绝对是某个单词的前缀 } return sign; }
关于AC自动机就是KMP算法的扩展,其主要是多模式字符串的匹配,而且它是要建立在Trie树上的。
以下内容是网上别人那参考过来的,认为分析的还比较清楚。故拿来参考给自己以后用
首先简要介绍一下AC自动机:Aho-Corasick automation,该算法在1975年产生于贝尔实验室,是著名的多模匹配算法之一。一个常见的例子就是给出n个单词,再给出一段包含m个字符的文章,让你找出有多少个单词在文章里出现过。要搞懂AC自动机,先得有模式树(字典树)Trie和KMP模式匹配算法的基础知识。AC自动机算法分为3步:构造一棵Trie树,构造失败指针和模式匹配过程。
如果你对KMP算法和了解的话,应该知道KMP算法中的next函数(shift函数或者fail函数)是干什么用的。KMP中我们用两个指针i和j分别表示,A[i-j+ 1..i]与B[1..j]完全相等。也就是说,i是不断增加的,随着i的增加j相应地变化,且j满足以A[i]结尾的长度为j的字符串正好匹配B串的前 j个字符,当A[i+1]≠B[j+1],KMP的策略是调整j的位置(减小j值)使得A[i-j+1..i]与B[1..j]保持匹配且新的B[j+1]恰好与A[i+1]匹配,而next函数恰恰记录了这个j应该调整到的位置。同样AC自动机的失败指针具有同样的功能,也就是说当我们的模式串在Tire上进行匹配时,如果与当前节点的关键字不能继续匹配的时候,就应该去当前节点的失败指针所指向的节点继续进行匹配。
看下面这个例子:给定5个单词:say she shr he her,然后给定一个字符串yasherhs。问一共有多少单词在这个字符串中出现过。我们先规定一下AC自动机所需要的一些数据结构,方便接下去的编程。
1 const int kind = 26 ;
2 struct node{
3 node * fail; // 失败指针
4 node * next[kind]; // Tire每个节点的个子节点(最多个字母)
5 int count; // 是否为该单词的最后一个节点
6 node(){ // 构造函数初始化
7 fail = NULL;
8 count = 0 ;
9 memset(next,NULL, sizeof (next));
10 }
11 } * q[ 500001 ]; // 队列,方便用于bfs构造失败指针
12 char keyword[ 51 ]; // 输入的单词
13 char str[ 1000001 ]; // 模式串
14 int head,tail; // 队列的头尾指针有了这些数据结构之后,就可以开始编程了:
首先,将这5个单词构造成一棵Tire,如图-1所示。
![]()
1 void insert( char * str,node * root){
2 node * p = root;
3 int i = 0 ,index;
4 while (str[i]){
5 index = str[i] - '