字典树,顾名思义,就是把字典放到树上
不不不,是用字典来建树
把树建成字典的样子?
差不多。
字典树指的是某个字符串集合对应的有根树,树的每条边上恰好对应一个字符,每个顶点代表从根到该节点的路径所对应的字符串(将所有经过的边上的字符按照顺序连接起来)。有时我们也称trie上的边为转移,节点为状态
是不是读起来很恶心?
其实可以这么理解
字典树就是一棵利用了字符串的公共前缀(比如gcakioi和gcakimo的前缀就是gcaki)来减少查询时间的树。当你查询单词的时候,只要沿着转移边查找就可以了,并且对于每个单词的结尾,在字典树上都会有标记来表示这是一整个单词。
举个例子
现在有两个单词
A:GCAKIOI
B:GCAKIMO
那么这个字典树大概就是这个样子
非常直观对吧
就是在相同的时候合到一起,不同的时候分开,在单词结尾进行标记就好了
操作实现:
1.初始化
一棵空的trie只包含一个根节点,该点的字符指针都指向空
int ch[N][Z]//Z为字符集大小,ch[i][j]表示节点i的j字符指向的节点 bool bo[N]//对给以i为终止字符的节点打标记
2.插入
当需要插入一个字符串S的时候,我们令一个指针P指向根节点,然后依次扫描S中的每个字符c
- 如果指针P的c字符指针指向一个存在的节点Q,则令P=Q
- 如果指针P的c字符指针指向空,则新建一个节点Q,令P的c字符指针指向Q,然后令P=Q
- 当S中的字符扫描完毕时,在当前节点指针P上标记它是一个字符串的末尾
inline void build(string s,int id) { int now=1,len=s.size(); rep(i,0,len-1) { int c=s[i]-'a'; if(!ch[now][c]) { ch[now][c]=++cnt; }//如果还没有这个节点,就新创建一个节点 now=ch[now][c];//当前节点转移过去 } bo[now]=id;//给以节点now为终止节点的打上标记 }
3.查询
当我们要查询一个字符串S在trie中是否出现时,我们令一个指针P起初指向根节点,然后依次扫描S中的每一个字符c
- 如果指针P的c字符指针指向空,则说明S没有被插入过trie
- 如果指针P的c字符指针指向一个已经存在的节点Q,则令P=Q
- 当S中的字符扫描完毕时,若当前节点P被标记为一个字符串的末尾,则说明S在trie中已经存在,否则说明S没有被插入过trie
inline bool find(char s[]) { int now=1,len=strlen(s),c,k; rep(i,0,len-1)//在字典树上查找该单词 { c=s[i]-'a'; if(!ch[now][c]) return false; now=ch[now][c]; } return true; }
当字符均为小写字母或者大写字母时,trie可以看成一个26叉树
它的空间复杂度为O(N*C),N是节点个数,C是字符集的大小