字典树(trie树)
(图f)
字典树是一种以树形结构保存大量字符串。以便于字符串的统计和查找,经常被搜索引擎系统用于文本词频统计。它的优点是:利用字符串的公共前缀来节约存储空间,最大限度地减少无谓的字符串比较,查询效率比哈希表高。具有以下特点(图f):
(1)根节点为空;
(2)除根节点外,每个节点包含一个字符;
(3)从根节点到某一节点,路径上经过的字符连接起来,为该节点对应的字符串。
(4)每个字符串在建立字典树的过程中都要加上一个区分的结束符,避免某个短字符串正好是某个长字符串的前缀而淹没。
Trie树的实现,可以用数组(静态分配空间),也可以用指针动态分配(动态分配空间)。
Trie 的强大之处就在于它的时间复杂度。它的插入和查询时间复杂度都为 O(k) ,其中 k 为 key 的长度,与 Trie 中保存了多少个元素无关。Trie 的缺点是空间消耗很高。它的优点是:最大限度地减少无谓的字符串比较,查询效率比哈希表高。
Trie的核心思想是空间换时间。利用字符串的公共前缀来降低查询时间的开销以达到提高效率的目的。
Trie树有一些特性:
1)根节点不包含字符,除根节点外每一个节点都只包含一个字符。
2)从根节点到某一节点,路径上经过的字符连接起来,为该节点对应的字符串。
3)每个节点的所有子节点包含的字符都不相同。
4)如果字符的种数为n,则每个结点的出度为n,这也是空间换时间的体现,浪费了很多的空间。
5)插入查找的复杂度为O(n),n为字符串长度。
下面主要介绍trie树在C++版和java版中的初始化,插入,和查询工作
定义trie树的结构体:C++版
typedef struct Trie_node
{ int count; // 统计单词前缀出现的次数
struct Trie_node* next[26]; // 指向各个子树的指针
bool exist; // 标记该结点处是否构成单词
}TrieNode , *Trie;
- using namespace std;
- typedef struct Trie_node
- {
- int count; // 统计单词前缀出现的次数
- struct Trie_node* next[26]; // 指向各个子树的指针
- bool exist; // 标记该结点处是否构成单词
- }TrieNode , *Trie;
- TrieNode* createTrieNode()
- {
- TrieNode* node = (TrieNode *)malloc(sizeof(TrieNode));
- node->count = 0;
- node->exist = false;
- memset(node->next , 0 , sizeof(node->next)); // 初始化为空指针
- return node;
- }
- void Trie_insert(Trie root, char* word)
- {
- Trie node = root;
- char *p = word;
- int id;
- while( *p )
- {
- id = *p - ‘a‘;
- if(node->next[id] == NULL)
- {
- node->next[id] = createTrieNode();
- }
- node = node->next[id]; // 每插入一步,相当于有一个新串经过,指针向下移动
- ++p;
- node->count += 1; // 这行代码用于统计每个单词前缀出现的次数(也包括统计每个单词出现的次数)
- }
- node->exist = true; // 单词结束的地方标记此处可以构成一个单词
- }
- int Trie_search(Trie root, char* word)
- {
- Trie node = root;
- char *p = word;
- int id;
- while( *p )
- {
- id = *p - ‘a‘;
- node = node->next[id];
- ++p;
- if(node == NULL)
- return 0;
- }
- return node->count;
- }
- int main(void)
- {
- Trie root = createTrieNode(); // 初始化字典树的根节点
- char str[12] ;
- bool flag = false;
- while(gets(str))
- {
- if(flag)
- printf("%d\n",Trie_search(root , str));
- else
- {
- if(strlen(str) != 0)
- {
- Trie_insert(root , str);
- }
- else
- flag = true;
- }
- }
- return 0;
- }
java版trie树初始化,插入,和查询给出的字符串为前缀的单词的个数。
import java.util.Scanner;
public class Mein {
/**
* @param args
*/
private int num;//以该节点为根的子树的个数,即以该节点所指字符串作为前缀的单词的个数
private Mein[] next;//孩子结点,若存储的是英文26个字母,则每个节点有26个孩子结点
private boolean exist;//若exist为true,则该节点所表示的字符串为一个单词,否则不是单词,如box,若节点存储o,则表示的是“bo”,不是一个单词,此时exist=false;若节点存储x,则表示的是“box”,是一个单词,此时exist=true
public static Mein init()//初始化
{
Mein node=new Mein();
node.num=0;
node.next=new Mein[26];
node.exist=false;
return node;
}
public static void insert(Mein root,String a)//插入
{
Mein nodeRoot=root;
int id;
for(int i=0;i<a.length();i++)
{
id=a.charAt(i)-‘a‘;
if(nodeRoot.next[id]==null)
{
nodeRoot.next[id]=init();
}
nodeRoot=nodeRoot.next[id];
nodeRoot.num++;
}
nodeRoot.exist=true;
}
public static int inquery(Mein root,String pre)//查询给出的字符串为前缀的单词的个数
{
Mein NodeTemp=root;
int k;
for(int i=0;i<pre.length();i++)
{
k=pre.charAt(i)-‘a‘;
if(NodeTemp.next[k]==null)
{
return 0;
}
NodeTemp=NodeTemp.next[k];
}
return NodeTemp.num;
}
public static void main(String[] args) {
// TODO Auto-generated method stub
Mein Node=new Mein();
Node=init();
Scanner s=new Scanner(System.in);
int n=s.nextInt();//输入n个字符串建立trie树
String[] dir=new String[n];
for(int i=0;i<n;i++)
{
dir[i]=s.next();
}
int m=s.nextInt();//输入m个字符串作为给定的字符串,用来查询以该字符串为前缀的单词的个数
String[] prefix=new String[m];
for(int i=0;i<m;i++)
{
prefix[i]=s.next();
}
for(int i=0;i<n;i++)
{
insert(Node,dir[i]);//向trie树插入输入的n个字符串
}
int num;
for(int i=0;i<m;i++)
{
num=inquery(Node,prefix[i]);//查询以该字符串为前缀的单词的个数
System.out.println(num);
}
}
}