键树
又称为数字查找树,它是一棵度 >= 2的树,同以往所学习的树不同的是,键树的结点中存储的不是某个关键字,而是只含有组成关键字的单个符号。
如果关键字本身是字符串,则键树中的一个结点只包含有一个字符;如果关键字本身是数字,则键树中的一个结点只包含一个数位。每个关键字都是从键树的根结点到叶子结点中经过的所有结点中存储的组合。
例如,当使用键树表示查找表{CAI,CAO,CHEN,LI,LAN,ZHAO}
时,为了查找方便,首先对该查找表中关键字按照首字符进行分类(相同的在一起):
{{CAI,CAO,CHEN},{LI,LAN},{ZHAO}}
然后继续分割,按照第二个字符、第三个字符、...,最终得到的查找表为:
{{CAI,CAO},{CHEN},{LI,LAN},{ZHAO}}
然后使用键树结构表示该查找表,如下图 所示:
注意:键树中叶子结点的特殊符号 $ 为结束符,表示字符串的结束。使用键树表示查找表时,为了方便后期的查找和插入操作,约定键树是有序树(兄弟结点之间自左至右有序),同时约定结束符 ‘$’ 小于任何字符。
键树的存储结构
键树的存储结构有两种:一种是通过使用树的孩子兄弟表示法来表示键树,即双链树;另一种是以树的多重链表表示键树,即Trie 树,又称字典树。
Trie树(字典树)
若以树的多重链表表示键树,则树中如同双链树一样,会含有两种结点:
- 叶子结点:叶子结点中含有关键字域和指向该关键字的指针域;
- 除叶子结点之外的结点(分支结点):含有 d 个指针域和一个整数域(记录该结点中指针域的个数);
d 表示每个结点中存储的关键字的所有可能情况,如果存储的关键字为数字,则 d= 11(0—9,以及 $),同理,如果存储的关键字为字母,则 d=27(26个字母加上结束符 $)。
上图中的键树,采用字典树表示如下图所示:
注意:在 Trie 树中,如果从某个结点一直到叶子结点都只有一个孩子,这些结点可以用一个叶子结点来代替,例如 ZHAO 就可以直接作为叶子结点。
字典树查找功能的具体实现
使用 Trie 树进行查找时,从根结点出发,沿和对应关键字中的值相对应的指针逐层向下走,一直到叶子结点,如果全部对应相等,则查找成功;反之,则查找失败。
代码
![](https://i-blog.csdnimg.cn/blog_migrate/4f4a74b3c9c2b3914a09b0d4d7566427.png)
typedef int Status; /* Status是函数的类型,其值是函数结果状态代码,如OK等 */
typedef int Boolean; /* Boolean是布尔类型,其值是TRUE或FALSE */
#include<malloc.h> /* malloc()等 */
#include<stdio.h> /* EOF(=^Z或F6),NULL */
#include<process.h> /* exit() */
/* 函数结果状态代码 */
#define TRUE 1
#define FALSE 0
#define OK 1
#define ERROR 0
#define INFEASIBLE -1
#define OVERFLOW -2
#define N 16 /* 数据元素个数 */
#define LENGTH 27 /* 结点的最大度+1(大写英文字母) */
typedef struct
{
int ord;
}Others; /* 记录的其它部分 */
#define Nil ' ' /* 定义结束符为空格(与教科书不同) */
/* -------------------------------- Trie键树类型 ----------------------------------*/
#define MAXKEYLEN 16 /* 关键字的最大长度 */
typedef struct
{
char ch[MAXKEYLEN]; /* 关键字 */
int num; /* 关键字长度 */
}KeysType; /* 关键字类型 */
typedef struct
{
KeysType key; /* 关键字 */
Others others; /* 其它部分(由主程定义) */
}Record; /* 记录类型 */
typedef enum { LEAF, BRANCH }NodeKind; /* 结点种类:{叶子,分支} */
typedef struct TrieNode /* Trie键树类型 */
{
NodeKind kind;
union
{
struct /* 叶子结点 */
{
KeysType K;
Record *infoptr;
}lf;
struct /* 分支结点 */
{
struct TrieNode *ptr[LENGTH]; /* LENGTH为结点的最大度+1,在主程定义 */
/* int num; 改 */
}bh;
}a;
}TrieNode, *TrieTree;
/* 对两个字符串型关键字的比较约定为如下的宏定义 */
#define EQ(a,b) (!strcmp((a),(b)))
#define LT(a,b) (strcmp((a),(b))<0)
#define LQ(a,b) (strcmp((a),(b))<=0)
/* ---------------------------------------------------------------------------------------------*/
/* -------------------------------- 动态查找表(Trie键树)的基本操作 -------------------------------*/
Status InitDSTable(TrieTree *T)
{ /* 操作结果: 构造一个空的Trie键树T */
*T = NULL;
return OK;
}
void DestroyDSTable(TrieTree *T)
{ /* 初始条件: Trie树T存在。操作结果: 销毁Trie树T */
int i;
if (*T) /* 非空树 */
{
for (i = 0; i < LENGTH; i++)
if ((*T)->kind == BRANCH && (*T)->a.bh.ptr[i]) /* 第i个结点不空 */
if ((*T)->a.bh.ptr[i]->kind == BRANCH) /* 是子树 */
DestroyDSTable(&(*T)->a.bh.ptr[i]);
else /* 是叶子 */
{
free((*T)->a.bh.ptr[i]);
(*T)->a.bh.ptr[i] = NULL;
}
free(*T); /* 释放根结点 */
*T = NULL; /* 空指针赋0 */
}
}
int ord(char c)
{
c = toupper(c);
if (c >= 'A'&&c <= 'Z')
return c - 'A' + 1; /* 英文字母返回其在字母表中的序号 */
else
return 0; /* 其余字符返回0 */
}
Record *SearchTrie(TrieTree T, KeysType K)
{ /* 在键树T中查找关键字等于K的记录。算法9.16 */
TrieTree p;
int i;
for (p = T, i = 0; p&&p->kind == BRANCH && i < K.num; p = p->a.bh.ptr[ord(K.ch[i])], ++i);
/* 对K的每个字符逐个查找,*p为分支结点,ord()求字符在字母表中序号 */
if (p&&p->kind == LEAF && p->a.lf.K.num == K.num&&EQ(p->a.lf.K.ch, K.ch)) /* 查找成功 */
return p->a.lf.infoptr;
else /* 查找不成功 */
return NULL;
}
void InsertTrie(TrieTree *T, Record *r)
{ /* 初始条件: Trie键树T存在,r为待插入的数据元素的指针 */
/* 操作结果: 若T中不存在其关键字等于(*r).key.ch的数据元素, */
/* 则按关键字顺序插r到T中 */
TrieTree p, q = NULL , ap;
int i = 0, j;
KeysType K1, K = r->key;
if (!*T) /* 空树 */
{
*T = (TrieTree)malloc(sizeof(TrieNode));
(*T)->kind = BRANCH;
for (i = 0; i < LENGTH; i++) /* 指针量赋初值NULL */
(*T)->a.bh.ptr[i] = NULL;
p = (*T)->a.bh.ptr[ord(K.ch[0])] = (TrieTree)malloc(sizeof(TrieNode));
p->kind = LEAF;
p->a.lf.K = K;
p->a.lf.infoptr = r;
}
else /* 非空树 */
{
for (p = *T, i = 0; p&&p->kind == BRANCH && i < K.num; ++i)
{
q = p;
p = p->a.bh.ptr[ord(K.ch[i])];
}
i--;
if (p&&p->kind == LEAF && p->a.lf.K.num == K.num&&EQ(p->a.lf.K.ch, K.ch)) /* T中存在该关键字 */
return;
else /* T中不存在该关键字,插入之 */
{
if (!p) /* 分支空 */
{
p = q->a.bh.ptr[ord(K.ch[i])] = (TrieTree)malloc(sizeof(TrieNode));
p->kind = LEAF;
p->a.lf.K = K;
p->a.lf.infoptr = r;
}
else if (p->kind == LEAF) /* 有不完全相同的叶子 */
{
K1 = p->a.lf.K;
do
{
ap = q->a.bh.ptr[ord(K.ch[i])] = (TrieTree)malloc(sizeof(TrieNode));
ap->kind = BRANCH;
for (j = 0; j < LENGTH; j++) /* 指针量赋初值NULL */
ap->a.bh.ptr[j] = NULL;
q = ap;
i++;
} while (ord(K.ch[i]) == ord(K1.ch[i]));
q->a.bh.ptr[ord(K1.ch[i])] = p;
p = q->a.bh.ptr[ord(K.ch[i])] = (TrieTree)malloc(sizeof(TrieNode));
p->kind = LEAF;
p->a.lf.K = K;
p->a.lf.infoptr = r;
}
}
}
}
void TraverseDSTable(TrieTree T, Status(*Vi)(Record*))
{ /* 初始条件: Trie键树T存在,Vi是对记录指针操作的应用函数 */
/* 操作结果: 按关键字的顺序输出关键字及其对应的记录 */
TrieTree p;
int i;
if (T)
{
for (i = 0; i < LENGTH; i++)
{
p = T->a.bh.ptr[i];
if (p&&p->kind == LEAF)
Vi(p->a.lf.infoptr);
else if (p&&p->kind == BRANCH)
TraverseDSTable(p, Vi);
}
}
}
/* --------------------------------------------------------------------------------------------------*/
Status pr(Record *r)
{
printf("(%s,%d)", r->key.ch, r->others.ord);
return OK;
}
void main()
{
TrieTree t;
int i;
char s[MAXKEYLEN + 1];
KeysType k;
Record *p;
Record r[N] = { {{"CAI"},1},{{"CAO"},2},{{"LI"},3},{{"LAN"},4},
{{"CHA"},5},{{"CHANG"},6},{{"WEN"},7},{{"CHAO"},8},
{{"YUN"},9},{{"YANG"},10},{{"LONG"},11},{{"WANG"},12},
{{"ZHAO"},13},{{"LIU"},14},{{"WU"},15},{{"CHEN"},16} };
/* 数据元素(以教科书式9-24为例) */
InitDSTable(&t);
for (i = 0; i < N; i++)
{
r[i].key.num = strlen(r[i].key.ch) + 1;
r[i].key.ch[r[i].key.num] = Nil; /* 在关键字符串最后加结束符 */
p = SearchTrie(t, r[i].key);
if (!p)
InsertTrie(&t, &r[i]);
}
printf("按关键字符串的顺序遍历Trie树(键树):\n");
TraverseDSTable(t, pr);
printf("\n请输入待查找记录的关键字符串: ");
scanf("%s", s);
k.num = strlen(s) + 1;
strcpy(k.ch, s);
k.ch[k.num] = Nil; /* 在关键字符串最后加结束符 */
p = SearchTrie(t, k);
if (p)
pr(p);
else
printf("没找到");
printf("\n");
DestroyDSTable(&t);
}
运行结果: