字典树
介绍
Trie树,又叫字典树、前缀树(Prefix Tree)、单词查找树 或 键树,是一种多叉树结构。如下图:
性质
- 根节点不包含字符,除根节点外的每一个子节点都包含一个字符。
- 从根节点到某一个节点,路径上经过的字符连接起来,为该节点对应的字符串。
- 每个节点的所有子节点包含的字符互不相同。
步骤
当构建字典树时,可以按照以下步骤进行:
-
创建根节点:开始构建字典树时,首先创建一个根节点。根节点不代表任何字符,可以看作是一个空字符串或特殊字符。
-
插入字符串:对于要插入的每个字符串,从根节点开始,按字符顺序逐步插入。遍历字符串的字符,对于每个字符,检查当前节点是否有对应的子节点。
- 如果存在对应的子节点,则将当前节点更新为子节点,并继续处理下一个字符。
- 如果不存在对应的子节点,则创建一个新的子节点,并将其与当前节点连接。然后将当前节点更新为新创建的子节点,并继续处理下一个字符。
重复以上步骤,直到遍历完整个字符串。最后,将最后一个字符的节点标记为终止节点,表示该字符串在字典树中存在。
-
重复插入:根据需要,可以重复执行插入操作,将多个字符串插入到字典树中。
构建字典树的过程是根据字符串的字符逐步创建节点并建立连接,直到将所有字符串都插入完成。最终形成的字典树可以高效地支持字符串的搜索和前缀匹配操作。
下面是一个示例来构建一个字典树,包含字符串 “apple”、“apply”、“app”、“banana” 和 “bat”:
(root)
/ \
a b
/ \ |
p b a
/ \ \ \
p l t n
/ \ \ |
l e y a
\
y
具体实现
//字典树--每各节点的只有一个父亲节点,每个节点的子节点的个数不超过字母的个数,这就保证了每各单词路径的唯一性
#include<iostream>
#include<vector>
using namespace std;
struct node {
int next[26];//26个字母
int cnt;//以该节点结尾的字符串被访问次数--标记该节点是否为单词的结尾节点
}tree[1000000];//树
int cut = 1;//节点计数器
//插入字符串
void insert(string s) {
int len = s.length();
int now = 0;//当前节点
for (int i = 0; i < len; i++) {//遍历字符串
int index = s[i] - 'a';
if (tree[now].next[index] == 0) {//如果没有这个节点
tree[now].next[index] = cut++;//新建节点
}
now = tree[now].next[index];//转移到下一个节点
}
tree[now].cnt++;//当前节点被访问次数+1--以该节点结尾的字符串被访问了一次
}
//查找字符串 --返回该字符串
string search(string s) {
int len = s.length();
int now = 0;//当前节点
for (int i = 0; i < len; i++) {//遍历字符串
int index = s[i] - 'a';
if (tree[now].next[index] == 0) {//如果没有这个节点
return "no";//返回
}
now = tree[now].next[index];//转移到下一个节点
}
if (tree[now].cnt > 0) {//如果当前节点被访问次数大于0
return s;//返回该字符串
}
else {
return "no";//返回
}
}
//删除字符串--因为每个节点的父亲节点只有一个,所以只需要将以该节点结尾的字符串被访问次数-1即可
void del(string s) {
int len = s.length();
int now = 0;//当前节点
for (int i = 0; i < len; i++) {//遍历字符串
int index = s[i] - 'a';
if (tree[now].next[index] == 0) {//如果没有这个节点
return;//返回
}
now = tree[now].next[index];//转移到下一个节点
}
tree[now].cnt--;//以该节点结尾的字符串被访问次数-1
}
//判断字符串是否存在
bool isExist(string s) {
int len = s.length();
int now = 0;//当前节点
for (int i = 0; i < len; i++) {//遍历字符串
int index = s[i] - 'a';
if (tree[now].next[index] == 0) {//如果没有这个节点
return false;//返回
}
now = tree[now].next[index];//转移到下一个节点
}
if (tree[now].cnt > 0) {//如果以该节点结尾的字符串被访问次数0
return true;//返回true
}
else {
return false;//返回false
}
}
//判断字符串是否是另一个字符串的前缀
int isPrefix(string s) {
int flag = 0;
int len = s.length();
int now = 0;//当前节点
for (int i = 0; i < len; i++) {//遍历字符串
int index = s[i] - 'a';
if (tree[now].next[index] == 0) {//如果没有这个节点
flag = 1;
break;//返回
}
//if (i == len - 1 && tree[now].next[index] != 0) {//如果是最后一个节点
// flag = 0;
// break;
//}
now = tree[now].next[index];//转移到下一个节点
}
if (flag == 0) {
return now;//返回当前节点
}
else {
return -1;//返回-1
}
}
//以该点为根节点进行深度优先搜索
void dfsForPrefix(int now, string s) {
if (tree[now].cnt > 0) {//如果以该节点结尾的字符串被访问次数大于0
cout << s << endl;//输出该字符串
}
for (int i = 0; i < 26; i++) {//遍历26个字母
if (tree[now].next[i] != 0) {//如果有这个节点
dfsForPrefix(tree[now].next[i], s + char(i + 'a'));//深度优先搜索
}
}
}
//根据前缀查找字符串--返回以该前缀开头的字符串
string searchByPrefix(string s) {
int len = s.length();
int now = 0;//当前节点
int index = isPrefix(s);//判断是否是前缀
if (index == -1) {//如果不是前缀
return "no";//返回
}
else {
now = index;//转移到前缀的最后一个节点
}
//查找以该前缀开头的字符串
dfsForPrefix(index, s);//以该节点为根节点进行深度优先搜索
return s;//返回该前缀
}
//判断字符串是否是另一个字符串的子串
bool isSubString(string s) {
int len = s.length();
int now = 0;//当前节点
for (int i = 0; i < len; i++) {//遍历字符串
int index = s[i] - 'a';
if (tree[now].next[index] == 0) {//如果没有这个节点
return false;//返回
}
now = tree[now].next[index];//转移到下一个节点
}
if (tree[now].cnt > 0) {//如果以该节点结尾的字符串被访问次数大于0
return true;//返回true
}
else {
return false;//返回false
}
}
int main() {
insert("hello");
insert("world");
insert("copilot");
insert("ok");
insert("result");
insert("define");
insert("definite");
insert("definitely");
cout<<search("definitely")<<endl;
del("definitely");
cout<<search("definitely")<<endl;
//-------------------------------------
/* cout << search("hello") << endl;
cout << search("world") << endl;
cout << search("copilot") << endl;
cout << search("ok") << endl;
cout << search("result") << endl;
cout << search("define") << endl;
cout << search("definite") << endl;
cout << search("definitely") << endl;
cout << search("definitely1") << endl;*/
cout << searchByPrefix("def") << endl;
return 0;
}