Trie,又称字典树、单词查找树,是一种树形结构,用于保存大量的字符串。其核心思想就是空间换时间。
优点是:利用字符串的公共前缀来节约存储空间。
有3个基本性质:
1. 根节点不包含字符,除根节点外每一个节点都只包含一个字符。
2. 从根节点到某一节点,路径上经过的字符连接起来,为该节点对应的字符串。
3. 每个节点的所有子节点包含的字符都不相同。
举个简单的例子。
给你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 <cstdio>
#include <iostream>
#include <cstring>
using namespace std;
const int num_chars = 26;
class Trie {
public:
Trie():root(NULL){};
Trie(Trie& tr);
int search(const char* word, char* entry ) const;
int insert(const char* word, const char* entry);
int remove(const char* word, char* entry);
private:
struct Trie_node
{
char* data;
Trie_node* branch[num_chars];
Trie_node();
}* root;
};
Trie::Trie_node::Trie_node()
{
data = NULL;
for (int i=0; i<num_chars; ++i)
branch[i] = NULL;
}
int Trie::search(const char* word, char* entry ) const
{
int position = 0;
char char_code;
Trie_node *location = root;
while( location!=NULL && *word!=0 )
{
if (*word>='A' && *word<='Z')
char_code = *word-'A';
else if (*word>='a' && *word<='z')
char_code = *word-'a';
else return 0;
location = location->branch[char_code];
position++;
word++;
}
if ( location != NULL && location->data != NULL )
{
strcpy(entry,location->data);
return 1;
}
else return 0;
}
int Trie::insert(const char* word, const char* entry)
{
int result = 1, position = 0;
if ( root == NULL ) root = new Trie_node;
char char_code;
Trie_node *location = root;
while( location!=NULL && *word!=0 )
{
if (*word>='A' && *word<='Z')
char_code = *word-'A';
else if (*word>='a' && *word<='z')
char_code = *word-'a';
else return 0;
if( location->branch[char_code] == NULL )
location->branch[char_code] = new Trie_node;
location = location->branch[char_code];
position++;
word++;
}
if (location->data != NULL)
result = 0;
else {
location->data = new char[strlen(entry)+1];
strcpy(location->data, entry);
}
return result;
}
int main()
{
Trie t;
char entry[100];
t.insert("aa", "DET");
t.insert("abacus","NOUN");
t.insert("abalone","NOUN");
t.insert("abandon","VERB");
t.insert("abandoned","ADJ");
t.insert("abashed","ADJ");
t.insert("abate","VERB");
t.insert("this", "PRON");
if (t.search("this", entry))
cout<<"'this' was found. pos: "<<entry<<endl;
if (t.search("abate", entry))
cout<<"'abate' is found. pos: "<<entry<<endl;
if (t.search("baby", entry))
cout<<"'baby' is found. pos: "<<entry<<endl;
else
cout<<"'baby' does not exist at all!"<<endl;
if (t.search("aa", entry))
cout<<"'aa was found. pos: "<<entry<<endl;
}
----------------------------------------------------------------------------------------------------------------------------------------------------------------------
//结点类型
typedef struct node
...{
bool isStr; //记录此处是否有串
int count; //记录子结点的个数
node* next[26]; //孩子的指针数组,数组最多为26,实际上为了减小空间,可以将这里定义为node **,他的大小结合count动态扩张
} node()
: count(0), isStr(false)
...{
memset(next, NULL, sizeof(next)); //初始化为空
}
} *nodeptr;
//Trie类
//insert:插入一个字符串,重复插入无效
//remove:删除指定的字符串,如果不存在,则不进行操作
//find:判断是否有指定的字符串
class Trie
...{
private:
nodeptr root;
//删除t的孩子的孩子
//这样做的原因是
//delete一个指针,这个指针就不确定了,不能继续向上操作
//所以只能删除t的孩子的孩子
//有待改进
void remove(nodeptr t, const char* key, int i)
...{
//孩子的指针,key[i]为孩子
nodeptr pnext = t -> next[key[i] - 'a'];
if (key[i+1] && pnext) //不是最后一个结点
...{
pnext -> count--;
remove(pnext, key, i+1); //递归删除
}
if (!pnext -> count) //子结点是空的,直接删除
delete pnext;
else //否则删除子结点的孩子的指针
pnext -> next[key[i+1] - 'a'] = NULL;
}
public:
Trie()
...{
root = new node;
}
//插入操作
void insert(const char* key)
...{
nodeptr location = root;
do
...{
if (location -> next[*key - 'a'] == NULL) //不存在则新建
...{
nodeptr tmp = new node;
location -> next[*key - 'a'] = tmp;
}
if (*key) //不是0
...{
location -> count++;
location = location -> next[*key - 'a'];
}
} while (*key++);
location -> isStr = true; //到达尾部
}
//删除操作
void remove(const char* key)
...{
if (!find(key)) //找不到则不操作
return;
//预处理根
root -> count--;
//删除根的子结点
remove(root, key, 0);
//尾处理
if (!root -> next[*key - 'a'] -> count)
delete root -> next[*key - 'a'];
else
root -> next[*key - 'a'] = NULL;
}
//查找
bool find(const char* key)
...{
nodeptr location = root;
while (*key && location)
location = location -> next[*key++ - 'a'];
return (location != NULL && location -> isStr);
}
};
文章出处:http://www.diybl.com/course/6_system/linux/Linuxjs/20071027/80149.html
----------------------------------------------------------------------------------------------------------------------------------------------------------------------
字典树的第一题
#include<stdio.h>
#include<string.h>
#include<iostream.h>
#define MAX_NUM 26
#define MAX_DATA 100005
class Tire
{
public:
Tire();
int TireSearch(const char *word,char *entry);
void TireInsert(const char *word,const char *entry);
protected:
struct TireNode
{
char*data;
TireNode* branch[MAX_NUM];
TireNode();
};
TireNode *root;
};
Tire::TireNode::TireNode()
{
data=NULL;
int i;
for(i=0;i<MAX_NUM;i++)
branch[i]=NULL;
}
{
root=NULL;
}
{
int position=0;
if(root==NULL)
root=new TireNode;
char char_code;
TireNode *location=root;
while(location!=NULL&&*word!=0)
{
if(*word>='a'&&*word<='z')
char_code=*word-'a';
if(location->branch[char_code]==NULL)
location->branch[char_code]=new TireNode;
location=location->branch[char_code];
position++;word++;
location->data=new char[strlen(entry)+1];
strcpy(location->data,entry);
}
{
int position=0;
char char_code;
TireNode *location=root;
while(location!=NULL&&*word!=0)
{
if(*word>='a'&&*word<='z')
char_code=*word-'a';
location=location->branch[char_code];
position++;
word++;
}
if(location!=NULL&&location->data!=NULL)
{
strcpy(entry,location->data);
return 1;
}
else
return 0;
}
Tire T;
int main()
{
char str1[10],str2[10],str3[10];
char str[100];
char entry[10];
char ch;
int i,j;
while(1)
{
j=0;
gets(str);
if(strlen(str)==0)
{
// printf("fjkdsajkfdjk/n");
break;
}
for(i=0;str[i]!=' ';i++)
str1[i]=str[i];
str1[i]='/0';
for(i++;i<strlen(str);i++)
str2[j++]=str[i];
str2[j]='/0';
T.TireInsert(str2,str1);
}
while(scanf("%s",str3)!=EOF)
{
//gets(str3);
//getchar();
if(str3[0]=='/0')
break;
if(T.TireSearch(str3,entry))
printf("%s/n",entry);
else
printf("eh/n");
}
}
----------------------------------------------------------------------------------------------------------------------------------------------------------------------
算法描述为:由字母a~z所组成的字符串的一个集合中,各个字符的长度之和为n。设计一个O(n)时间的算法,将这个集合中所有字符串依字典进行排序。注意,这里可能存在非常长的字符串。
#include <stdio.h>
#include <malloc.h>
typedef struct tire
{
struct tire *next[26];
char date;
int cnt;
}*_tire;
void init_tire(_tire root, char *string)
{
_tire s;
s=root;
while(*string!=’//0’)
{
if(s->next[*string - ’a’]==NULL)
{
s->next[*string - ’a’] = (_tire)malloc(sizeof(struct tire));
(s->next[*string - ’a’])->date = *string;
s = s->next[*string - ’a’];
for(int i=0;i<26;i++)
{
s->next[i] = NULL;
}
}
else
{
s = s->next[*string - ’a’];
}
string++;
}
s->cnt=1;
}
void print(_tire root, char *s, int i)
{
int j;
s[i] = root->date;
if(root->cnt==1)
{
s[i+1] = 0;
puts(s);
}
for(j=0;j<26;j++)
{
if(root->next[j]!=NULL)
{
print(root->next[j],s,i+1);
}
} [Page]
}
int main()
{
_tire root;
int m,i;
char s[265];
root = (_tire)malloc(sizeof(struct tire));
puts(/"输入字符串个数:/");
for(i=0;i<26;i++)
{
root->next[i]=NULL;
}
scanf(/"%d/",&m);
getchar();
while(m--)
{
gets(s);
init_tire(root,s);
}
puts(/"//n依字典排序后:/");
for(i=0;i<26;i++)
{
if(root->next[i] != NULL)
{
print(root->next[i],s,0);
}
}
return 0;
}