字符串的多模匹配,KMP,字典树,AC自动机,现在学习字典树;
概念:
字典树又称为单词查找树,用于统计,排序和保存大量的字符串(但不仅限于字符串),所以经常被搜索引擎系统用于文本词频的统计。优点是利用字符串的公共前缀来减少查询时间,最大限度减少无畏字符串比较,查询效率比哈希树高。
其实字典树就是DFA!!,每层是一个状态,只不过我觉得这棵树的空间利用率不高罢了(HDU1251,G++MLE怨念ing)。
性质:
1.根节点不包含字符,根节点外每个节点都只包含一个字符
2.根节点到某一节点上,路径上经过的字符拼接起来,为该节点对应的字符串;
3.每个节点的所有子节点包含的字符都不相同。
基本操作:查找,插入,删除(少见)
实现方法:
1.从根节点开始一次搜索。
2.取得要查找关键字的第一个字母,并根据该字母选择对应的子树并转到该子树进行检索
3.相应子树上,取得要查找关键词的第二个字母,并进行进一步选择对应的子树进行检索。
4.迭代
5. 某个节点处,关键词的所有字母已被取出,则读取附在该节点上的信息,即完成查找。
应用:
1.串的快速检索:给出N个单词组成的熟词表,以及一篇全用小写英文写的文章,按照最早出现的顺序写出所有不在熟词表中的生词。
2. 串的排序:给定N个互不相同的仅有一个单词构成的英文名,让你将他们安字典序大小,从小到大输出。用字典树进行排序,采用数组的方式创建创建字典树,这棵树的每个节点的所有子节点很显然按照其字母大小排序,对这棵树进行先序遍历即可。
3.最长公共前缀:对所有串建立字典树,对于两个串的最长公共前缀的长度,即他们所在节点的公共祖先个数,于是,问题转外化为当时公共祖先问题。
4.学习自动机的热身
代码实现:
首先是一个板子:
//#pragma comment(linker, "/STACK:1024000000,1024000000")
#include<stdio.h>
#include<string.h>
#include<math.h>
#include<map>
#include<set>
#include<deque>
#include<queue>
#include<stack>
#include<bitset>
#include<string>
#include<fstream>
#include<iostream>
#include<algorithm>
using namespace std;
#define ll long long
#define Pair pair<int,int>
//#define max(a,b) (a)>(b)?(a):(b)
//#define min(a,b) (a)<(b)?(a):(b)
#define clean(a,b) memset(a,b,sizeof(a))// 水印
//std::ios::sync_with_stdio(false);
// register
const int MAXN=1e3+10;
const int INF32=0x3f3f3f3f;
const ll INF64=0x3f3f3f3f3f3f3f3f;
const ll mod=1e9+7;
const double PI=acos(-1.0);
//---------------------------
const int maxn=26;//假设只有小写字母
const int maxm=1e5+10;
struct treenode{
int count;//表示该节点所表示字符串在所有字符串中以前缀形式出现的总次数。
treenode *next[maxm];
}head;
int charmapping[256];//字符映射数组,charmapping[i]=x表示字符i对应于treenode中的next[x]
void init_charmapping(){
for(int i='a';i<='z';++i){
//该字典树只允许输入小写字符组成的字符串 ,
//如果想要增加新字符,添加映射并增大maxn 即可。
charmapping[i]=i-'a';
}
}
void init_trie(){
head.count=1;//初始化为1,包括空串&&避免树头被删除
for(int i=0;i<maxn;++i){
head.next[i]=NULL;
}
}
treenode* createnew(){//申请一个新节点并初始化
treenode* newnode;
newnode=(treenode*)malloc(sizeof(treenode));
newnode->count=0;
for(int i=0;i<maxn;++i){
newnode->next[i]=NULL;
}
return newnode;
}
void updata(char *s,int num){//向字典树中添加num个字符串s
int k=0,temp;
treenode* t=&head;
while(s[k]){//该字符存在
t->count+=num;//此时的节点作为前驱被访问的次数++
temp=charmapping[s[k]];//temp对应该节点的map映射的值
if(t->next[temp]==NULL)//该映射为空
t->next[temp]=createnew();//申请,加入
t=t->next[temp];//向后指
k++;//遍历下一个字符
}
t->count+=num;//以该字符串为前驱的再加num个。
}
int search(char *s,int num){//查找字符串中是否存在num和s
int k=0,temp;
treenode* t=&head;
while(s[k]){
temp=charmapping[s[k]];
if(t->next[temp]==NULL||t->next[temp]->count<num)
return 0;//小于num
t=t->next[temp];
k++;
}
//这里只是单纯的查找字符串s,后面不能多,前面不能少。
int snum=t->count;//最后存在的 个数
for(int i=0;i<maxn;++i){//遍历maxn个字符
if(t->next[i]){//如果这个元素后面有该字符
snum-=t->next[i]->count;//减去后面存在的字符数。
}
}
if(snum>=num){//仍然符合要求
return 1;
}
return 0;
}
void erase(char *s,int num){//删除字典树中nun个s字符串,并将无用的节点释放
int k=0,temp;
treenode* t=&head;
treenode* t1;
head.count-=num;//根节点减少num个
while(s[k]){
temp=charmapping[s[k]];
t->next[temp]->count-=num;//该节点的 子树删掉num个
if(t->next[temp]->count==0){//删干净了。
t1=t->next[temp];
t->next[temp]=NULL;
k++;
break;
}
t=t->next[temp];
k++;
}
while(s[k]){
temp=charmapping[s[k]];
t=t1->next[temp];
free(t1);
t1=t;
k++;
}
free(t1);
}
char temp[1000];
void printall(treenode* tnode,int pos){//递归打印字典树,打出来就是字典序升序排列
int count=tnode->count;
for(int i=0;i<maxn;++i){
if(tnode->next[i])
count-=tnode->next[i]->count;
}
for(int i=0;i<count;++i){
cout<<temp<<endl;
}
for(int i='a';i<='z';++i){
if(tnode->next[charmapping[i]]){
temp[pos]=i;
temp[++pos]='\0';
printall(tnode->next[charmapping[i]],pos);
temp[--pos]='\0';
}
}
}
int main(){
init_charmapping();
init_trie();
char x[1000];
int num,oper;
while(cin>>oper){
if(oper==1){
clean(x,'\0');
cin>>x>>num;
updata(x,num);
}
else if(oper==2){
clean(x,'\0');
cin>>x>>num;
if(search(x,num)==0)
cout<<"No Find"<<endl;
else
cout<<"Yes Find"<<endl;
}
else if(oper==3){
clean(x,'\0');
cin>>x>>num;
erase(x,num);
}
else{
printall(&head,0);
}
}
}
/*
1
apple 1
2
app 1
2
apple 1
1
add 1
4
3
apple 1
4
*/
然后在HDU上找了个板子题:HDU-1251-统计难题:http://acm.hdu.edu.cn/showproblem.php?pid=1251
直接AC:
const int MAXN=28;
const int INF32=0x3f3f3f3f;
const ll INF64=0x3f3f3f3f3f3f3f3f;
const ll mod=1e9+7;
const double PI=acos(-1.0);
//---------------------------
struct node{
int count;
node *nxt[MAXN];
}tree;
void intt(){
tree.count=1;
for(int i=0;i<MAXN;++i){
tree.nxt[i]=NULL;
}
}
void insert(char *s){
node *t=&tree;
int k=0;
while(s[k]){
t->count++;
if(t->nxt[s[k]-'a']==NULL){
node *p=(node*)malloc(sizeof(node));
p->count=0;
for(int i='a';i<='z';++i){
p->nxt[i-'a']=NULL;
}
t->nxt[s[k]-'a']=p;
}
t=t->nxt[s[k]-'a'];
++k;
}
t->count++;
}
int Query(char *s){
node *t=&tree;
int k=0;
while(s[k]){
if(t->nxt[s[k]-'a']==NULL) return 0;
t=t->nxt[s[k]-'a'];
++k;
}
return t->count;
}
int main(){
intt();
char s[15];
while(gets(s)){
if(s[0]=='\0') break;
//cout<<s<<endl;
insert(s);
clean(s,'\0');
}
while(gets(s)){
if(s[0]=='\0') break;
cout<<Query(s)<<endl;
clean(s,'\0');
}
}
然后还有一道板子:HDU-1075-What Are You Talking About:http://acm.hdu.edu.cn/showproblem.php?pid=1075
大概意思就是首先给你一个一一对应的字典,然后再给你篇文章,你需要按照这个字典里将文章翻译出来,(字典里没有的不需要翻译),字典树+标记就行了,注意一下格式问题,要不然格式错误(我中间加了一个getchar()吃换行。。)
AC:
struct node{
int vis,cnt;
string word;
node *nxt[28];
node():vis(0),cnt(1){
for(int i=0;i<26;++i){
nxt[i]=NULL;
}
}
}root;
void insert(string s1,string s2){//s1对应s2
int l=s1.size();
node *t=&root;
for(int i=0;i<l;++i){
t->cnt++;
if(t->nxt[s1[i]-'a']==NULL){
t->nxt[s1[i]-'a']=new node();
}
t=t->nxt[s1[i]-'a'];
}
t->vis=1;t->word=s2;
}
string Find(string s){
node *t=&root;
int l=s.size();
for(int i=0;i<l;++i){
if(t->nxt[s[i]-'a']==NULL)
return "";
t=t->nxt[s[i]-'a'];
}
string res="";
if(t->vis==1){
res=t->word;
}
return res;
}
int main(){
string s1,s2;
cin>>s1;
while(cin>>s1>>s2){
if(s1=="END") break;
insert(s2,s1);
}
char ch=getchar();//我在这个地方加一个换行
char s[3100]={'\0'};
while(gets(s)){
if(s[0]=='E') break;
string e="";
int k=0;
while(s[k]!='\0'){
if(s[k]>'z'||s[k]<'a'){
string ans=Find(e);
if(ans.size()==0) cout<<e;
else cout<<ans;
e="";cout<<s[k++];continue;
}
e+=s[k++];
}
cout<<endl;
}
}