基本介绍
字典树又称Trie树,前缀树(事实上前缀树这个名字就很好的解释了Trie的储存方式)。
这是一种树形结构,典型用于统计、排序、和保存大量字符串。所以经常被搜索引擎系统用于文本词频统计。它的优点是:利用字符串的公共前缀来减少查询时间,最大限度地减少无谓的字符串比较,查询效率比哈希树高。
一个字典树的应用场景:在搜索框输入部分单词下面会有一些神关联的搜索内容,你有时候都很神奇是怎么做到的,这其实就是字典树的一个思想。
来一张图理解一下Trie的储存方式:
字典树有以下几个性质:
- 根节点不包含字符,除了根节点每个节点都只包含一个字符。可以把root节点理解成一个哨兵节点。
- 从根节点到某一个叶子节点,路过字符串起来就是该节点对应的字符串。
- 每个节点的子节点字符不同,也就是找到对应单词、字符是唯一的。
常见操作
我们假定字典树存的只有26个小写英文字母组成的字符串。首先定义Trie树节点结构体:
typedef struct ss{int son[27];} node;node trie[NMAX]; //存储所有字典树的节点int total_node = 0; //节点总数(不算根节点)
Trie树两个常见操作:插入和查找(删除实在少见,就不讲了)。
插入
代码如下:
void insert(const char[] str){int pos = 0, len = strlen(str);for(int i = 0; i < len; i++){if(!trie[pos].son[str[i] - 'a'])trie[pos].son[str[i] - 'a'] = ++total_node;pos = trie[pos].son[str[i] - 'a'];}}
从根节点开始,我们遍历待插入的字符串。对于当前被遍历到的字符
,我们定位到当前节点
的子节点在
节点数组的下标
,如果下标有效(不为0),那么说明字符串当前字符已经存在,可继续往下遍历,更新
;否则,则说明字符串当前字符不存在需要插入,给当前树节点
设置新的子节点位置
并更新
。
查找
代码如下:
void insert(const char[] str){int pos = 0, len = strlen(str);for(int i = 0; i < len; i++){if(!trie[pos].son[str[i] - 'a'])return false;pos = trie[pos].son[str[i] - 'a'];}return true;}
和插入代码唯一的不同:对于某个当前被遍历到的字符,如果当前节点
对应子节点下标
为0,说明子节点不存在,即待查找的字符串不存在,直接返回false。
例题
原题链接
AC代码:
#include <iostream>#include <cstring>using namespace std;typedef struct ss{int son[27];int num; //字符串最后一个字符对应节点的num表示被搜索的次数} node;node trie[500000];int total_node = 0;void insert(const char str[]){int pos = 0, len = strlen(str);for(int i = 0; i < len; i++){if(!trie[pos].son[str[i] - 'a'])trie[pos].son[str[i] - 'a'] = ++total_node;pos = trie[pos].son[str[i] - 'a'];}}int search(const char str[]){int pos = 0, len = strlen(str);for(int i = 0; i < len; i++){if(!trie[pos].son[str[i] - 'a']) return 0;pos = trie[pos].son[str[i] - 'a'];}return ++trie[pos].num;}int main(void){char str[51];int n, m;scanf("%d", &n);for(int i = 0; i < n; i++){scanf("%s", str);insert(str);}scanf("%d", &m);for(int i = 0; i < m; i++){scanf("%s", str);int ans = search(str);if(ans == 0) printf("WRONG\n");if(ans == 1) printf("OK\n");if(ans >= 2) printf("REPEAT\n");}return 0;}
