前缀树又称为Trie树。常用来保存字符串集合。
在树中,每个单词的结束位置对应一个单词结点,反过来,从根结点到每个单词结点的路径上所有字母连接而成的字符串就是该结点对应的字符串。
在程序上,将根结点编号为0,然后把其余结点编号从1开始的正整数,然后用一个数组来保存每个结点的所有子结点,用下标直接存取。
用ch[i][j]结点i的那个编号为j 的子结点,把所有小写字母按照字典序为0,1,2,则ch[i][0]表示结点i的子结点a 如果这个子结点不存在,则
ch[i][0]=0 sigma_size表示字符集的大小。比如,当字符集为全体小写字母时,sigma_size=26
使用Trie时,往往需要在单词上附加信息。val[i]表示结点i对应的附加信息。例如。如果每个字符串有一个权值,就可以把这个权值保持在val[i]中。简单起见,下面的代码中假定权值大于0,因此val[i]>0 当且仅当结点i是单词结点。
输入:字符串的数目n,各个字符串以及其权值。查找的次数q,查找的字符串。
输出:对于每个查找的字符串,输出其权值。
const int maxnode = 100 + 5;
const int sigma_size = 26;
字母表为全体小写字母的Trie:
//字母表为全体小写字母的Trie
struct Trie
{
int ch[maxnode][sigma_size];
int sz; //结点总数
int val[maxnode];
Trie()
{
//初始时只有一个根结点
sz = 1;
memset(ch[0], 0, sizeof(ch[0]));
}
int idx(char c)
{
//字符c的编号
return c - 'a';
}
//插入字符串s,附加信息为v 注意v必须非0 因为0代表“本结点不是单词结点”
void insert(const char *s, int v)
{
int u, n, i, c;
u = 0;
n = strlen(s);
for(i = 0; i < n; i++)
{
c = idx(s[i]);
if(!ch[u][c]) //结点不存在
{
memset(ch[sz], 0, sizeof(ch[sz]));
val[sz] = 0; //中间结点的附加信息为0
ch[u][c] = sz++; //新建结点
}
u = ch[u][c];
}
val[u] = v; //字符串的最后一个字符附加信息为v
}
//寻找字符串s 如果找到返回权值val[i] 否则返回0
int search(const char *s)
{
int u, n, i, c;
u = 0;
n = strlen(s);
for(i = 0; i < n; i++)
{
c = idx(s[i]);
if(!ch[u][c]) //找不到
return 0;
u = ch[u][c];
}
return val[u];
}
};
下面简单介绍一下左孩子-右兄弟法表示法的Trie树。
如果字符集太大,我们就需要用到左儿子-右兄弟表示法。左孩子表示该节点的下一个结点,右兄弟表示该节点的左右兄弟。
struct Trie
{
int head[maxnode]; //第i个结点的左孩子编号
int next[maxnode]; //第i个结点的右兄弟编号
int sz; //结点总数
char ch[maxnode]; //第i个结点的是字符
Trie()
{
sz = 1; //初始时只有一个结点
head[0] = 0;
next[0] = 0;
}
void insert(const char *s)
{
int u, i, v, n;
u = 0;
n = strlen(s);
for(i = 0; i < n; i++)
{
// 找字符s[i]
for(v = head[u]; v != 0; v = next[v])
if(ch[v] == s[i])
break;
if(!v) //没找到
{
v = sz++; //新建结点
ch[v] = s[i];
head[v] = 0;
next[v] = head[u]; //插入链表顶部
head[u] = v;
}
u = v;
}
}
};