其基本性质可以归纳为:
1. 根节点不包含字符,除根节点外每一个节点都只包含一个字符。
2. 从根节点到某一节点,路径上经过的字符连接起来,为该节点对应的字符串。
3. 每个节点的所有子节点包含的字符都不相同。
其基本操作有:查找插入和删除,当然删除操作比较少见.我在这里只是实现了对整个树的删除操作,至于单个word的删除操作也很简单.
搜索字典项目的方法为:
(1) 从根结点开始一次搜索;
(2) 取得要查找关键词的第一个字母,并根据该字母选择对应的子树并转到该子树继续进行检索;
(3) 在相应的子树上,取得要查找关键词的第二个字母,并进一步选择对应的子树进行检索。(4) 迭代过程……(5)在某个结点处,关键词的所有字母已被取出,则读取附在该结点上的信息,即完成查找。其他操作类似处理.
举个简单的例子。
给你100000个长度不超过10的单词。对于每一个单词,我们要判断他出没出现过,如果出现了,第一次出现第几个位置。
这题当然可以用hash来,但是我要介绍的是trie树。在某些方面它的用途更大。比如说对于某一个单词,我要询问它的前缀是否出现过。这样hash就不好搞了,而用trie还是很简单。
现在回到例子中,如果我们用最傻的方法,对于每一个单词,我们都要去查找它前面的单词中是否有它。那么这个算法的复杂度就是O(n^2)。显然对于100000的范围难以接受。现在我们换个思路想。假设我要查询的单词是abcd,那么在他前面的单词中,以b,c,d,f之类开头的我显然不必考虑。而只要找以a开头的中是否存在abcd就可以了。同样的,在以a开头中的单词中,我们只要考虑以b作为第二个字母的……这样一个树的模型就渐渐清晰了……
假设有b,abc,abd,bcd,abcd,efg,hii这6个单词,我们构建的树就是这样的
对于每一个节点,从根遍历到他的过程就是一个单词,如果这个节点被标记为红色,就表示这个单词存在,否则不存在。
那么,对于一个单词,我只要顺着他从跟走到对应的节点,再看这个节点是否被标记为红色就可以知道它是否出现过了。把这个节点标记为红色,就相当于插入了这个单词。
这样一来我们询问和插入可以一起完成,所用时间仅仅为单词长度,在这一个样例,便是10。
我们可以看到,trie树每一层的节点数是26^i级别的。所以为了节省空间。我们用动态链表,或者用数组来模拟动态。空间的花费,不会超过单词数×单词长度。
#include<iostream>
#include<cstring>
using namespace std;
class trie
{
public:
trie* next[26];
int num; //记录前缀的个数
bool value;//标记这里是不是一个单词
trie()
{
for(int i=0;i<26;i++)
next[i]=0;
value=0;
num=0;
}
}root;
void insert(char* s)
{
trie *p=&root;
int k=0;
while(s[k]!='\0')
{
if(!p->next[s[k]-'a'])
p->next[s[k]-'a']=new trie;
p=p->next[s[k]-'a'];
p->num++; //每访问节点就++1次,多好啊!
k++;
}
p->value=1; //在这里可根据具体题意来加东西
}
int find(char *s)
{
trie* p=&root;
int k=0;
while(s[k]!='\0'&&p->next[s[k]-'a'])
{
p=p->next[s[k]-'a'];
k++;
}
if(s[k]=='\0')
return p->num;
return 0;
}
int main()
{
char english[12],martian[12],word[12];
while(gets(english)&&english[0]!='\0')
{
insert(english);
}
while(scanf("%s",martian)!=EOF)
{
cout<<find(martian)<<endl;
}
//system("pause");
return 0;
}
说明:这题是1组的数据的,如果是多组数据,要在主函数里加上:
---------------------------------------------
trie *p=&root;
char ask1[20000];
while()
{For()
{ insert(p,ask1);
find(p,ask1);
}
P=new trie();//
注意这里多组数据要建多个树
}
---------------------------------------
同时还要修改
insert(trie *p,char *s)
Find(trie *p,char *s)
----------------------------------------
自己做题体会吧!
练习题目:所有字符串的题目,可以考虑用trie!
HDU:1004,1251,1075,1800,2072都是。
hdu1274代码:
#include<iostream>
#include<cstring>
#include<cstdio>
using namespace std;
class trie
{
public:
trie* next[26];
bool value;
trie()
{
for(int i=0; i<26; i++)
next[i]=NULL;
value=false;
}
} root;
char a[50005][55];
void insert(char *str)
{
trie* p=&root;
int k=0;
while(str[k]!='\0')
{
if(!p->next[str[k]-'a'])
{
trie *q=new trie;
p->next[str[k]-'a']=q;
}
p=p->next[str[k]-'a'];
k++;
}
p->value=true;
}
int find(char* str)
{
trie* p=&root;
int k=0;
while(str[k]!='\0'&&p->next[str[k]-'a'])
{
p=p->next[str[k]-'a'];
k++;
}
if(str[k]=='\0'&&p->value)
return 1;
return 0;
}
int main()
{
int n=0;
char x[55],y[55];
while(scanf("%s",a[n])!=EOF)
{
getchar();
insert(a[n]);
n++;
}
for(int i=0; i<n; i++)
{
int len=strlen(a[i]);
for(int j=0; j<len; j++)
{
memset(x,0,sizeof(x));
memset(y,0,sizeof(y));
strncpy(x,a[i],j+1);
strcpy(y,a[i]+j+1);
if(find(x)&&find(y))
{
cout<<a[i]<<endl;
break;
}
}
}
return 0;
}
以上两段代码可以用作模板!
字典树的典型应用:
1.统计一组字符串中某前缀出现的次数(直接用上面的代码就行)。
2.判断一组字符串中是否有一个字符串是另一个字符串的前缀。
3. 串排序:给定N个互不相同的仅由一个单词构成的英文名,让你将他们按字典序从小到大输出
用字典树进行排序,采用数组的方式创建字典树,这棵树的每个结点的所有儿子很显然地按照其字母大小排序。对这棵树进行先序遍历即可。