前言
字典树,又称 Trie 树,是一种多叉树,专门用于存放字符串,通过压缩具有相同前缀的字符串达到存储多字符串时节省空间的目的。同时借助相同前缀压缩存储的特性,可以实现一些搜索的功能
以 [she,he,say,shr,her] 为例,标颜色的表示为一个字符串结尾:
代码实现
结构与接口定义
//字典树结构存储字符串,只支持小写字母
//每棵字典树的根节点都不表示字符,只为了引出后面的字符
typedef struct TrieNode{
int isEnd;
//next数组存的是字典树的地址
struct TrieNode **next;
//当前字典树中串的个数
int num;
}TrieNode,*Trie;
//初始化字典树
Status trieInit(Trie &trie);
//插入新的字符串
Status trieInsert(Trie &trie,HStr hStr);
//查找有无指定字符串
Status trieSearch(Trie trie,HStr hStr);
//查询目前字典树中所有以targetLStr为前缀的串,并返回查找到的串的个数
int trieFindAll(Trie trie,LStr * &lStrList,LStr targetLStr);
接口实现中使用的字符串不是 C 语言自带的字符串类型,而是自己实现的一种基于 malloc() 函数分配堆存储的字符串结构和一种基于链式存储结构的字符串结构,与字典树的实现没有多大关系,读者只需要了解相关函数的作用以及字典树接口实现的思想即可
typedef struct HString{
char *data;
//长度不包括串中的'\0',整个串结构对外屏蔽了'\0'字符
int length;
}HString,*HStr;
typedef struct LinkedStringNode{
char data;
struct LinkedStringNode *next;
}LinkedStringNode,*LinkedStringNodePtr;
typedef struct LinkedString{
LinkedStringNodePtr head;
LinkedStringNodePtr tail;
int length;
}LinkedString,*LStr;
几个有用到的字符串操作接口实现:
//复制链串
LStr copyLStr(LStr lStr){
//lStr为空
if(lStr == NULL) return NULL;
LStr newLStr = (LStr)malloc(sizeof(LinkedString));
//lStr为空串
if(lStr->head == lStr->tail && lStr->head == NULL){
newLStr->head = newLStr->tail = NULL;
newLStr->length = 0;
return newLStr;
}
newLStr->head = newLStr->tail = NULL;
LinkedStringNodePtr lStrPtr = lStr->head;
newLStr->head = (LinkedStringNodePtr)malloc(sizeof(LinkedStringNode));
LinkedStringNodePtr newLStrPtr = newLStr->head;
while(lStrPtr != lStr->tail){
newLStrPtr->data = lStrPtr->data;
//拷贝不只是内容上的拷贝,物理地址不能重复引用
newLStrPtr->next = (LinkedStringNodePtr)malloc(sizeof(LinkedStringNode));
newLStrPtr = newLStrPtr->next;
newLStrPtr->next = NULL;
lStrPtr = lStrPtr->next;
}
newLStrPtr->data = lStrPtr->data;
newLStrPtr->next = NULL;
newLStr->tail = newLStrPtr;
newLStr->length = lStr->length;
return newLStr;
}
//将字符拼接到链式字符串尾部
Status appendChar(LStr &lStr,char c){
if(lStr == NULL){
lStr = (LStr)malloc(sizeof(LinkedString));
if(lStr == NULL) return OVERFLOW;
lStr->length = 0;
lStr->head = lStr->tail = (LinkedStringNodePtr)malloc(sizeof(LinkedStringNode));
if(lStr->head == NULL) return OVERFLOW;
lStr->head->data = c;
lStr->head->next = NULL;
}else if(lStr->head == lStr->tail && lStr->head == NULL){
//空串
lStr->head = lStr->tail = (LinkedStringNodePtr)malloc(sizeof(LinkedStringNode));
lStr->head->data = c;
lStr->head->next = NULL;
}else{
lStr->tail->next = (LinkedStringNodePtr)malloc(sizeof(LinkedStringNode));
if(lStr->tail->next == NULL) return OVERFLOW;
lStr->tail = lStr->tail->next;
lStr->tail->data = c;
lStr->tail->next = NULL;
}
lStr->length += 1;
return TRUE;
}
接口实现
#include "Trie.h"
//初始化字典树
Status trieInit(Trie &trie){
trie = (Trie)malloc(sizeof(TrieNode));
if(trie == NULL) return OVERFLOW;
trie->isEnd = 0;
trie->num = 0;
//26个字母的标识域,有的话存在以这个字母为值的子节点
trie->next = (Trie*)malloc(26 * sizeof(TrieNode));
if(trie->next == NULL){
//如果数据域创建失败也当作字典树创建失败,字典树的创建要回退
free(trie);
return OVERFLOW;
}
//因为是初始化,所以26个字母的标识域都要置为空
for(int i = 0;i < 26;i++){
trie->next[i] = NULL;
}
return TRUE;
}
Status trieInsert(Trie &trie,HStr hStr){
//如果字典树为空,就先创建字典树
if(trie == NULL){
trieInit(trie);
}else if(trie->next == NULL){
//如果字典树中用来存放后续字符的数据域为空也要创建
trie->next = (Trie *)malloc(26 * sizeof(TrieNode));
}
//临时字典树指针在被插入的参数字典树上移动
Trie temp = trie;
for(int i = 0;i < hStr->length;i++){
int index = hStr->data[i] - 'a';
if(temp->next[index] == NULL){
Trie newTrie;
trieInit(newTrie);
temp->next[index] = newTrie;
temp = newTrie;
}else{
temp = temp->next[index];
}
}
//如果isEnd被标记为1,说明要插入的串已经存在了
if(temp->isEnd != 1){
temp->isEnd = 1;
trie->num += 1;
return TRUE;
}
return FALSE;
}
Status trieSearch(Trie trie,HStr hStr){
if(trie == NULL || trie->next == NULL) return FALSE;
Trie temp = trie;
for(int i = 0;i < hStr->length;i++){
//将字母映射到下标
int index = hStr->data[i] - 'a';
if(temp->next[index] == NULL) return FALSE;
temp = temp->next[index];
}
return temp->isEnd;
}
void trieFind(Trie trie,LStr * &lStrList,LStr &temLStr,int *i){
if(trie == NULL) return;
for(int j = 0;j < 26;j++){
if(trie->next[j] != NULL){
LStr newLStr = copyLStr(temLStr);
appendChar(newLStr,(char)(j + 'a'));
if(trie->next[j]->isEnd == 1){
lStrList[++(*i)] = newLStr;
}
trieFind(trie->next[j],lStrList,newLStr,i);
}
}
}
//查询目前字典树中所有以targetLStr为前缀的串存放在lStrList中,并返回查找到的串的个数
int trieFindAll(Trie trie,LStr * &lStrList,LStr targetLStr){
Trie tempTrie = trie;
LStr * temList = (LStr *)malloc(100 * sizeof(LStr));
int *i = (int *)malloc(sizeof(int));
*i = -1;
if(targetLStr != NULL){
LinkedStringNodePtr temp = targetLStr->head;
while(temp != NULL){
int index = (temp->data) - 'a';
if(tempTrie->next[index] == NULL){
lStrList = NULL;
return 0;
}
tempTrie = tempTrie->next[index];
temp = temp->next;
}
if(tempTrie->isEnd == 1){
temList[++(*i)] = copyLStr(targetLStr);
}
}
trieFind(tempTrie,temList,targetLStr,i);
lStrList = (LStr *)malloc((++(*i)) * sizeof(LStr));
for(int j = 0;j < (*i);j++){
lStrList[j] = copyLStr(temList[j]);
}
return (*i);
}