1 定义
键树又称数字查找树,它是一棵度大于2的树,树中的每个节点值含有组成关键字的符号。例如,若关键字是数值,则节点中只包含一个数位。若关键字是单词,则节点中只包含一个字母字符。这种树会给某种类型的关键字的表的查找带来方便。
通常键树有两种存储结构:双链树和Trie树
2 双链树
双链树是以孩子兄弟链表来表示的键树,每个分支节点包含3个域:symbol域(用于存储关键字的一个字符),first域(存储指向子树根的指针),next域(存储指向右兄弟的指针)。同时叶子节点的infoptr域存储指向关键字记录的指针。
双链树的查找代码如下:
Record * SearchDLTree(DLTree T, KeysType K){
//在非空双链树T中查找关键字等于K的记录
p = T->first;i=0; //初始化,从第一个带有字符的节点开始
while(p && i<K.num){ //依次遍历关键字的字符
while(p && p->symbol < K.ch[i]) p = p->next; //向右兄弟节点搜索
if (p && p->symbol == K.ch[i]){ //匹配成功
p = p->first; //向子节点搜素
++i; //匹配关键字的下一个字符
}else{ //匹配失败
p = NULL; //查找不成功,强制退出循环
}
}
if (p && p->kind == LEAF) //查找成功则最后到达叶子节点,否则为NULL
return p->infoptr;
else return NULL;
}
3 Trie树
若以树的多重链表表示键树,则树的每个节点中应含有d个指针域,此时的键树又称为Trie树。若从键树中某个节点到叶子节点的路径上每个节点都只有一个孩子,则可将该路径上所有节点压缩成一个“叶子节点”,且在该叶子节点中存储关键字及指向记录的指针等信息。
在Trie树中有两种节点:
- 分支节点:含有d个指针域和一个指示该节点中非空指针域个数的整数域。在分支节点中不设数据域,每个分支节点所表示的字符均有其父节点中指向该节点的指针所在位置决定。
- 叶子节点:含有关键字域和指向记录的指针域
Trie树的查找过程为从根结点出发,沿和给定值相应的指针逐层向下直到叶子节点,若分支节点中和给定值相应的指针为空,或叶子节点中的关键字与给定值不符,则查找不成功。
typedef struct TrieNode{
NodeKind kind;
union{
struct {KeysType K; Record * infoptr;}lf; //叶子节点
struct {TrieNode *ptr[27]; int num;}bh //分支节点
};
}TrieNode,*TrieTree;
查找代码
Record *SearchTrie(TrieTree T, KeysType K){
//在Trie树中查找关键字为K的记录
p=T; i=0;
while(p && p->kind == BRANCH && i<K.num){ //在分支节点中向下查找匹配
p = p->bh.ptr[ord(K[i])]; //查找关键字K对应字符在分支节点中的位置
i++;
}
if(p && p->kind == LEAF && p->lf.K == K) return p->lf.infoptr; //查找成功
else return NULL; //查找失败
}
双链树和Trie树是键树的两种不同的表示方法,从其不同的存储结构来看,若键树中节点的度比较大,则采用Trie树节点较双链树更为合适。他们的查找过程都是从根节点出发,走了一条从根到叶子的路径,查找时间依赖于树的深度。因此,适当地调整对关键字的分割方案,例如先按首字符分割,再按尾字符分割,再按第二个字符…交替分割,缩减树的深度。