一、概念
Trie树,又称字典树,单词查找树或者前缀树,是一种用于快速检索的多叉树结构,如英文字母的字典树是一个26叉树,数字的字典树是一个10叉树。
与二叉查找树不同,Trie树的键不是直接保存在节点中,而是由节点在树中的位置决定。一个节点的所有子孙都有相同的前缀,也就是这个节点对应的字符串,而根节点对应空字符串。一般情况下,不是所有的节点都有对应的值,只有叶子节点和部分内部节点所对应的键才有相关的值。时间复杂度为O(m)。
如果在hole、honor、ahead、allow、all、always这几个单词中,查找always这个单词,需要首先把字母a从头到位和每个单词比较,比较成功再比较字母l,尔后比较字母w......
如果有n个单词,查找的单词长度为m,则这种查询方式的时间复杂度为O(nm)
上图以单词allow、all、always、ahead、hole、honor为例,建立的字典树,根节点为空,在每个结尾的字母的节点上做上标记。
二、结构体
在节点中,val存放字母,sign做标记,son是一个二级指针,指向一个node指针类型的数组,数组中的指针指向node节点(子节点)。
三、代码
/*
用结构体实现字典树
*/
#include <iostream>
#include <string>
using namespace std;
const int N = 26; //
struct trieNode{ //字典树节点的结构体
char val; //节点值
trieNode** son; //二级指针
int cnt; //用于标记
trieNode(char c) { //初始化
val = c;
son = new trieNode*[N];
for (int i = 0; i < N; i++) son[i] = 0;
cnt = 0;
}
};
trieNode * root; //创建根节点
void insert(string s) //插入方法,参数为输入的字符串
{
trieNode* p = root; //创建结构体指针p,指向root
for (int i = 0; i < s.length(); i++) {
int c = s[i] - 'a';
if (!p->son[c]) //假如节点的son指向的trieNode类型的表中的第c个元素为空
p->son[c]= new trieNode(c); //在节点的son指向的trieNode类型的表中的第c个元素创建一个新节点,c作为新节点的初始化参数
p = p->son[c]; //指针p移动指向新节点
}
p->cnt++; //改标记值,表示有单词
}
int query(string s) //查询方法
{
trieNode* p = root; //trieNode类型的指针p
for (int i = 0; i < s.length(); i++) {
int c = s[i] - 'a'; //c作为查找的线索
if (!p->son[c])return 0; //如果指向的为空则返回0;
p = p->son[c]; //如果存在,则指向下一个节点
}
return p->cnt; //返回标记值;
}
/*bool delete_trie(string s)
{
trieNode* p = root;
int i = 0;
if(delete_node(root,i,s)) return 1; //删除完毕返回真
return 0; //否则返回假
}*/
bool delete_node(trieNode* p,int i,string s) //删除单词,p指针指向节点,s是要删除的字符串,i是字符串中的字符序号
{
if (i == s.length()) //边界条件
{
if (p->cnt > 0) //如果标识大于0,则令标识为0,从标识符上来看表示已经删除该节点
p->cnt=0;
return 1; //到达边界 返回真
}
int j = s[i] - 'a'; //j是子节点数组中的下标
i++; //i向后移
if (!delete_node(p->son[j], i, s)) //递归,如果返回值为假就执行
return 0;
int sum = 0; //sum用来计数
for (int a = 0; a < N; a++) //查看p指针指向的节点是否有多个子节点
{
if (p->son[a]) sum++;
}
if (sum >1||p->son[j]->son) //如果有多个子节点,则不能删除,或者子节点后面还有子节点,也不能删除
return 0;
delete p->son[j]; //释放子节点内存
p->son[j] = nullptr; //指针指向空
return 1; //删除成功
}
int main()
{
root = new trieNode(' '); //root节点初始化;
int in = 10;
string s;
while (in) {
cout << "请输入数字进行操作:1.插入;2.查询;3.删除;4.退出" << endl;
cin >> in;
switch (in)
{
case 1: {
cout << "请输入:" << endl;
cin >> s;
insert(s);
cout << s << "插入成功" << endl;
break; }
case 2: {
cout << "请输入:" << endl;
cin >> s;
if (query(s))
cout << s << "在字典树中查询到" << endl;
else
cout << s << "未查找到" << endl;
break;
}
case 3: {
cout << "请输入:" << endl;
cin >> s;
delete_node(root, 0, s);
cout << s << "删除成功" << endl;
break;
}
case 4:return 0;
default: {
cout << "输入错误!请重新输入" << endl;
}
break;
}
}
return 0;
}
/*
用数组实现字典树
*/
#include <iostream>
#include<string>
using namespace std;
const int N = 26; //对应26个英文字母
const int M = 105; //输入单词最长长度
int trie[M][N] = { 0 }; //节点数组
int cnt[M] = { 0 }; //统计该节点有多少个单词
int idx = 0; //标识
void insert(string s) //插入函数
{
int p = 0; //p=0代表进入了根节点
for (int i = 0; i < s.length(); i++) {
int c = s[i] - 'a';
if (!trie[p][c]) trie[p][c] = ++idx; //如果节点为空,加入数字
p = trie[p][c]; //p移到数组的下一行
}
cnt[p]++; //以该字母为结束的单词数加一
}
int query(string s) //查询函数
{
int p = 0; //从根节点进入
for (int i = 0; i < s.length(); i++) {
int c = s[i] - 'a';
if (!trie[p][c])return 0; //如果该节点为空,返回0
p = trie[p][c]; //如果该节点不为空,则向下移
}
return cnt[p]; //返回标识
}
int main()
{
int in = 10;
string s;
while (in) {
cout << "请输入数字进行操作:1.插入;2.查询;3.退出" << endl;
cin >> in;
switch (in)
{
case 1: {
cout << "请输入:" << endl;
cin >> s;
insert(s);
cout << s << "插入成功" << endl;
break; }
case 2: {
cout << "请输入:" << endl;
cin >> s;
if (query(s))
cout << s << "在字典树中查询到" << endl;
else
cout << s << "未查找到" << endl;
break;
}
case 3: {
return 0;
}
default: {
cout << "输入错误!请重新输入" << endl;
}
break;
}
}
return 0;
}
运行结果: