Trie树就是字典树,其核心思想就是空间换时间。
举个简单的例子。
给你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级别的。所以为了节省空间。我们用动态链表,或者用数组来模拟动态。空间的花费,不会超过单词数×单词长度。
给出一个用类封装的字典树代码
//HDU 1247
#include <iostream>
#include <algorithm>
#include <string>
using namespace std;
typedef struct dictor DIC;
DIC *root = NULL;
struct dictor {
dictor (){ exist = false; memset ( child , 0 , sizeof ( child ) ); }
void insert ( char *ins );
bool find ( const char *ins );
private:
DIC *child[26];
bool exist;
};
void dictor::insert ( char *ins )
{
DIC *cur = root,*now;
int len = strlen ( ins );
for ( int i = 0; i < len; ++ i )
{
if ( cur->child[ ins[i] - 'a' ] != NULL )
{
cur = cur->child[ ins[i] - 'a' ];
}
else
{
now = new DIC;
cur->child[ ins[i] - 'a' ] = now;
cur = now;
}
}
cur->exist = true;
}
bool dictor::find ( const char *ins )
{
DIC *cur = root;
int len = strlen ( ins );
for ( int i = 0; i < len; ++ i )
{
if ( cur->child[ ins[i] - 'a' ] != NULL )
cur = cur->child[ ins[i] - 'a' ];
else
return false;
}
return cur->exist;
}
char words[50050][100];
char s1[100],s2[100];
DIC dict;
int main ()
{
root = &dict;
int n = 0;
while ( scanf ( "%s",words[n] ) != EOF )
{
dict.insert ( words[n++] );
}
for ( int i = 0; i < n; ++ i )
{
int len = strlen ( words[i] );
for ( int j = 1; j < len; ++ j )
{
memset ( s1, 0, sizeof ( s1 ) );
memset ( s2, 0, sizeof ( s2 ) );
strncpy ( s1, words[i], j );
strcpy ( s2, words[i]+j );
if ( dict.find ( s1 ) && dict.find ( s2 ) )
{
printf ( "%s\n", words[i] );
break;
}
}
}
//system ( "pause" );
return 0;
}
#include <iostream>
#include <algorithm>
#include <string>
using namespace std;
typedef struct dictor DIC;
DIC *root = NULL;
struct dictor {
dictor (){ exist = false; memset ( child , 0 , sizeof ( child ) ); }
void insert ( char *ins );
bool find ( const char *ins );
private:
DIC *child[26];
bool exist;
};
void dictor::insert ( char *ins )
{
DIC *cur = root,*now;
int len = strlen ( ins );
for ( int i = 0; i < len; ++ i )
{
if ( cur->child[ ins[i] - 'a' ] != NULL )
{
cur = cur->child[ ins[i] - 'a' ];
}
else
{
now = new DIC;
cur->child[ ins[i] - 'a' ] = now;
cur = now;
}
}
cur->exist = true;
}
bool dictor::find ( const char *ins )
{
DIC *cur = root;
int len = strlen ( ins );
for ( int i = 0; i < len; ++ i )
{
if ( cur->child[ ins[i] - 'a' ] != NULL )
cur = cur->child[ ins[i] - 'a' ];
else
return false;
}
return cur->exist;
}
char words[50050][100];
char s1[100],s2[100];
DIC dict;
int main ()
{
root = &dict;
int n = 0;
while ( scanf ( "%s",words[n] ) != EOF )
{
dict.insert ( words[n++] );
}
for ( int i = 0; i < n; ++ i )
{
int len = strlen ( words[i] );
for ( int j = 1; j < len; ++ j )
{
memset ( s1, 0, sizeof ( s1 ) );
memset ( s2, 0, sizeof ( s2 ) );
strncpy ( s1, words[i], j );
strcpy ( s2, words[i]+j );
if ( dict.find ( s1 ) && dict.find ( s2 ) )
{
printf ( "%s\n", words[i] );
break;
}
}
}
//system ( "pause" );
return 0;
}