字典树百度百科:又称单词查找树,Trie树,是一种树形结构,是一种哈希树的变种。典型应用是用于统计,排序和保存大量的字符串(但不仅限于字符串),所以经常被搜索引擎系统用于文本词频统计。它的优点是:利用字符串的公共前缀来减少查询时间,最大限度地减少无谓的字符串比较,查询效率比哈希表高。
学习了字典树之后,觉得它很明显的就是用空间来换时间,空间复杂度特别大,比如字典数单单存26个小写字母,那么每个节点的孩子节点都有26个孩子节点,字典树中的每一层都保留着不同单词的相同字母。为了好说明,假设,所有的单词只包括a,b,c,d四个字母,那么树是这样建立的。
如果是查找一个字符串的话,那么在已经建好的树中就是来回得“拐弯”,每一层只选择唯一的一个点。
问题1.给定一些字符串建立字典树,然后再给出一个字符串,问该字符串是前面多少个字符串的前缀。
#include <iostream>
#include <stdio.h>
#include <string.h>
using namespace std;
struct Trie
{
int cnt;//有多少单词经过该节点
Trie*next[27];
Trie()
{
cnt=0;
for(int i=0;i<27;i++)
next[i]=NULL;
}
}root;
void create(char *s)//将字符串s建立在trie树中
{
Trie *p=&root;
int len=strlen(s);
for(int i=0;i<len;i++)
{
int id=s[i]-'a';//唯一标识
if(p->next[id]==NULL)
{
p->next[id]=new Trie;
p->next[id]->cnt++;
}
else
p->next[id]->cnt++;
p=p->next[id];
}
}
int find(char *s)//查找字符串s是多少单词的前缀。
{
Trie *p=&root;
int len=strlen(s);
for(int i=0;i<len;i++)
{
int id=s[i]-'a';
if(p->next[id]==NULL)
return 0;
p=p->next[id];
}
return p->cnt;
}
int main()
{
char s[11];
while(gets(s)&&s[0]!='\0')
create(s);//根据给定的字符串建立字典树
while(cin>>s)
cout<<find(s)<<endl;
return 0;
}
题目2:判断输入的字符串中是否存在某一个字符串是其他一个字符串的前缀。
判断输入的串中是否存在某个串是另外串的前缀。
建立字典树,关键是设立某个串结尾的标志,即在哪个字母结尾。要判断是否存在前缀要考虑两种情况,一是当前输入的串是否是以前输入的串的前缀,而是以前输入的串是否是当前输入的串的前缀。前一种情况在查找该串时,会从第一个字母查找到最后一个字母,中间不返回任何值,说明当前的串是以前的前缀,后一种情况,当在查找当前串的时候遇到以前串结束的标志,则说明以前串是当前串的前缀。代码中结束的标志为保存串中最后一个字母的那个节点的flag值为-1.
如图:
本题姿势不对就会爆栈和超内存。建树的时候要采用申请内存空间的形式,而不要在构造函数里面写。
#include <iostream>
#include <stdio.h>
#include <malloc.h>
#include <string.h>
using namespace std;
const int maxn=10;
char str[12];
int t,n;
bool ok;
struct Trie
{
int flag;//当前串结束的标志,当前节点有单词经过,标志位1,在,哪个节点结束,其节点的flag=-1
Trie*next[10];
Trie()
{
flag=0;
}
}root;
/*错误的写法
void create(char *s)
{
int len=strlen(s);
Trie *p=&root;
for(int i=0;i<len;i++)
{
int id=s[i]-'0';
if(p->next[id]==NULL)
{
p->next[id]=(Trie *)malloc(sizeof(root));
p->next[id]->flag=1;
}
else
p->next[id]->flag=1;
p=p->next[id];
}
p->flag=-1;//结束的标志
}*/
void create(char *s)
{
int len=strlen(s);
Trie *p=&root,*q;
for(int i=0;i<len;i++)
{
int id=s[i]-'0';
if(p->next[id]==NULL)
{
q=(Trie *)malloc(sizeof(root));
q->flag=1;
for(int j=0;j<10;j++)
q->next[j]=NULL;
p->next[id]=q;
p=p->next[id];
}
else
{
p->next[id]->flag=1;
p=p->next[id];
}
}
p->flag=-1;
}
int find(char *s)
{
int len=strlen(s);
Trie *p=&root;
for(int i=0;i<len;i++)
{
int id=s[i]-'0';
if(p->next[id]==NULL)
return 0;//以前没有单词经过
if(p->next[id]->flag==-1)
return -1;//以前串是当前串的前缀
p=p->next[id];
}
return -1;//当前串是以前串的前缀
}
void release(Trie *root)//释放空间
{
if(root==NULL)
return ;
for(int i=0;i<10;i++)
{
if(root->next[i]!=NULL)
release(root->next[i]);
}
free(root);
}
int main()
{
scanf("%d",&t);
while(t--)
{
ok=1;
for(int i=0;i<10;i++)
root.next[i]=NULL;
scanf("%d",&n);
while(n--)
{
scanf("%s",str);
if(find(str)==-1)
ok=0;
if(!ok)//有前缀,后面的就不用建树了
continue;
create(str);
}
if(ok)
printf("YES\n");
else
printf("NO\n");//存在前缀输出NO
release(&root);
}
return 0;
}
题目3:将输入的字符串利用trie数进行编号 ,从1开始.
int cnt=1;
struct Trie
{
bool ok;
int id;
Trie *next[27];
Trie()
{
ok=0;
for(int i=0;i<=27;i++)
next[i]=NULL;
}
}root;
int hash(char *s) //返回字符串s的编号,创建和查找于一身
{
int len=strlen(s);
Trie *p=&root;
for(int i=0;i<len;i++)
{
int id=s[i]-'a';
if(!p->next[id])
p->next[id]=new Trie;
p=p->next[id];
}
if(p->ok)
return p->id;
else
{
p->ok=1;
p->id=cnt++;
}
return p->id;
}