算法学习之Trie树

算法学习之Trie树
by MPS
【定义】
  trie树又名字母树,是针对字符串的匹配,查找的一种高效手段,是哈希表的一种变种,但青出于蓝胜于蓝。我这个蒟蒻也是表示啃了两天才啃出来这些皮毛,当做学习笔记,关于trie树的外貌,大概如下:
                                                            
显然,这棵树有三个特点:
(1)有一个完全没有用的根(在生物上好像叫假根来着....)
(2)每个节点都是由一个字符构成
(3)路径连起来就是一个字符串
另外,图中有一些红节点,这是为了以后查询是否存在而做的铺垫
【trie树的深入】
  trie的好处在于无需构造,为什么这样说呢?因为我们发现,事实上,构造就是插入,插入就是“沿着前人的光荣传统,没有就自己开垦。”然后查找就更不用说了,具体统计出现频率只需要增加数据域即可
【trie树的模板】
(以@NOIRP同学出的一道题目为例子,方便引入模板)
                                                                           《英语时态》
     #题目描述#
        对于给定的一个单词,可能会有n种时态,对于不同的时态,小J同学就有点晕了,于是决定把它们分类,也就是对于每一个单词,它应该属于第几个柜子?对于能放在一个柜子的条件是当且仅当这几个单词一样。举个例子:
        3
        become
        became
        become
        那么很显然,become放在第一个柜子,became放在第二个柜子,这时,出现了重复的单词become,所以也放在第一个柜子,那么输出应该是
        1 2 1
     (注意,类似于:
          become
          become
          became
         则became应装进第三个柜子
       )
   #输入描述#
        第一行n
        第二行至第n+1行,每行一个单词(只包含小写字母和数字0-9)
  #输出描述#
         如题
  #数据范围#
       对于20%的数据,n≤1000,单词长度≤8
       对于100%的数据,n≤30001,单词长度≤20
【分析】
  题目的意思很明显,对于一个单词,查找之前是否出现过,如果出现过则输出第一次出现的序号,否则输出当前的序号,这时,思路涌现:
  (1)O(N^2)暴力,不过很明显TLE
  (2)哈希表优化,由于单词是只包含小写字母和数字0-9,所以弄一个哈希表记录下来,时间复杂度O(N),不过我们仔细观察:假设我们将一个字母都转为数字以后累加(这样才能不冲突),那么当单词长度为20,且每一个字母都是a,那么空间需求要60^20,太恐怖了,承受不起,所以MLE,可以用拉链法优化
(3)我们想到了学习主题——trie树,只要打一个id域即可!
       (补充:trie树查询插入删除的复杂度都是O(单词长度))
        那么时间复杂度就应该是O(N*L^2),正好卡时间AC!
【代码】
#include <iostream>
#include <cstdio>
#include <algorithm>
#include <cctype>
#include <cstring>
#include <string>
#include <cstdlib>
#include <vector>
#include <queue>
#include <deque>
using namespace std;

const int MaxL=20;
const int MAXS=100;
const int MaxN=30001;

typedef struct Trie_tree{
	int id;
	struct Trie_tree *next[MAXS];
}TrieNode,*Trie;

TrieNode* creat(){
	TrieNode* root=(TrieNode *)malloc(sizeof(TrieNode));
	root->id=0;
	memset(root->next,0,sizeof(root->next));
	return root;
}
int i;
Trie t;

int change(char x){
	if(x>='a' && x<='z')return x-'a';
	else return x-'0';
}

int Search(Trie root,char* word){
	TrieNode* node=root;
	char* p=word;
	while(*p && node!=NULL){
		node=node->next[(change(*p))];
		p++;
	}
	if(node!=NULL && node->id!=0)
	  return node->id;
	else
	  return i;
}

void Insert(Trie root,char* word){
	TrieNode* node=root;
	char* p=word;
	while(*p){
		if(node->next[change(*p)]==NULL)
			node->next[change(*p)]=creat();
		node=node->next[change(*p)];
		p++;
	}
	node->id=Search(t,word);
}

int Count(Trie root,char* word){
	TrieNode* node=root;
	char* p=word;
	while(*p && node!=NULL){
		node=node->next[change(*p)];
		p++;
	}
	return node->id;
}

int n;
char word[MaxN][MaxL];
int main(){
  freopen("letter.in","r",stdin);
  freopen("letter.out","w",stdout);
  scanf("%d",&n);
  t=creat();
  for(i=1;i<=n;i++)
  {
  	scanf("%s",word[i]);
    Insert(t,word[i]);
  }
  for(i=1;i<=n;i++)
    printf("%d ",Count(t,word[i]));
  return 0;
}

例题2:《Contact 联系》(来源:USACO)

Contact

联系
IOI'98

by Henry HuGang HuGangMail

奶牛们开始对用电波望远镜扫描牧场外的宇宙感兴趣。最近,他们注意到了一种非常奇怪的脉冲调制微波被从星系的中央发射出来。他们希望知道电波是否是被某些地外生命发射出来的,还是仅仅是普通的的星星的心跳。

帮助奶牛们用一个能够分析他们在文件中记下的记录的工具来找到真相。他们在寻找长度在AB之间(含)在每天的数据文件中重复得最多的比特序列 (1 <= A <= B <= 12)。他们在找那些重复得最多的比特序列。一个输入限制告诉你应输出多少频率最多的序列。

符合的序列可能会重叠,并且至少重复一次的序列会被计数。

PROGRAM NAME: contact

INPUT FORMAT

第一行:

三个用空格分隔的整数: A, B, N; (1 <= N < 50)

第二行及以后:

一个最多2000字符的序列,全是01; 每行有80个字符,除了可能的最后一行。

SAMPLE INPUT (file contact.in)

2 4 10 01010010010001000111101100001010011001111000010010011110010000000

在样例里,序列100出现了12次,而序列1000出现了5次。次数最多的序列是00,出现了23次。

OUTPUT FORMAT

输出N个频率最高的序列(按照频率由高到低的次序)。由短到长排列频率相同的这些序列,如果长短相同,按二进制大小排列。如果出现的序列个数小于N,输出存在的序列。

对于每个存在的频率,先输出单独包含该频率的一行,再输出以空格分隔的这些频率。每行六个(除非少于六个剩下)。

SAMPLE OUTPUT (file contact.out)

23001501 10121001111 000 001100108010070010 10016111 00005011 110 100040001 0011 1100

【分析】
  不难发现,这题的主要思想还是查询是否出现,当然这题也可以暴力,只不过会TLE的很惨。。。
  万变不离其宗——trie树
  打个cnt域即可
【代码】
#include <iostream>
#include <cstdio>
#include <algorithm>
#include <cctype>
#include <cstring>
#include <string>
#include <cstdlib>
#include <vector>
#include <queue>
#include <deque>
using namespace std;

const int MaxN=101;
const int MaxSize=2;
const int MaxM=10001;

typedef struct Trie_tree{
	int cnt;
	struct Trie_tree* next[MaxSize];//对于每一个数组只有0或者1两个孩子 
}TrieNode,*Trie;

TrieNode* creat(){
	TrieNode* root=(TrieNode*)malloc(sizeof(TrieNode));
	root->cnt=0;
	memset(root->next,0,sizeof(root->next));
	return root;
}

int comp(char A[],char B[]){
	int l1=strlen(A),l2=strlen(B);
	if(l1<l2)return 1;
	if(l1>l2)return -1;
	for(int i=0;i<l1;i++)
	  if(A[i]<B[i])return 1;
	  else if(B[i]<A[i])return -1;
	return 0;
}

struct result{
  char s[MaxN];
  int c;
  friend bool operator< (result a,result b){
    if(a.c!=b.c)
	  return a.c>b.c;//次数从大到小排序 
	else
	  return comp(a.s,b.s); 
  }
}ans[MaxM],res[51];

char str[MaxN];
int a,b,n,len;
int total;//答案个数 

void readfile(){
  scanf("%d%d%d",&a,&b,&n);
  char c;
  scanf("\n");
  while(~scanf("%c",&c))
  	if(c!='\n')str[len++]=c;
}

void Insert(Trie root,char* word){
	TrieNode* node=root;
	char *p=word;
	while(*p){
		if(node->next[*p-'0']==NULL)
			node->next[*p-'0']=creat();
		node=node->next[*p-'0'];
		p++;
	}
	node->cnt++;
}

int Count(Trie root,char* word){
	TrieNode* node=root;
	char* p=word;
	while(*p && node!=NULL){
		node=node->next[*p-'0'];
		p++;
	}
	return node->cnt;
}

char word[MaxN];
void trie_tree(){
  int i,j,k;
  Trie t=creat();
  for(i=0;i<=len-a;i++){
  	memset(word,'\0',sizeof(word));
    for(j=a;j<=b;j++)
    {
      if(i+j-1>=len)continue;
	  for(k=i;k<=i+j-1;k++)
	    word[k-i]=str[k];
      Insert(t,word);
    }
  }
  for(i=0;i<=len-a;i++){
  	memset(word,'\0',sizeof(word));
    for(j=a;j<=b;j++)
    {
      if(i+j-1>=len)continue;
	  for(k=i;k<=i+j-1;k++){
	    word[k-i]=str[k];
	    ans[total].s[k-i]=str[k];
	  }
      ans[total].c=Count(t,word);
      total++;
	}
  }
}

void out(){//最后输出的部分有些凌乱,读者可以自行修改
	int i,j,last=0,tmp=1,calc=0,ac=0;
	sort(ans,ans+total);
	for(i=0;i<total;i++){
		if(ans[i].c!=last){
		  calc++;
		  last=ans[i].c;
		  if(calc>n)break;
	    }
		if(i==0 || strcmp(ans[i].s,ans[i-1].s)!=0)res[ac++]=ans[i];
	}
	last=-1;
	calc=0;
	sort(res,res+ac);
	for(i=0;i<ac;i++){
		if(tmp%6==0)printf("\n");
		if(res[i].c!=last){
		  if(i!=0)printf("\n");
		  calc++;
		  if(calc>n)break;
		  printf("%d\n",res[i].c);last=res[i].c;
		  tmp=0;
		}
		printf("%s ",res[i].s);tmp++;
	}
}

int main(){
  freopen("contact.in","r",stdin);
  freopen("contact.out","w",stdout);
  readfile();
  trie_tree();
  out();
  return 0;
}


  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值