一、概述

  1、基本概念

  字典树,又称为单词查找树,Tire数,是一种树形结构,它是一种哈希树的变种。

  

  2、基本性质

  • 根节点不包含字符,除根节点外的每一个子节点都包含一个字符
  • 从根节点到某一节点。路径上经过的字符连接起来,就是该节点对应的字符串
  • 每个节点的所有子节点包含的字符都不相同

  3、应用场景

  典型应用是用于统计,排序和保存大量的字符串(不仅限于字符串),经常被搜索引擎系统用于文本词频统计。

  4、优点

  利用字符串的公共前缀来减少查询时间,最大限度的减少无谓的字符串比较,查询效率比哈希树高。

二、构建过程

  1、字典树节点定义

class TrieNode // 字典树节点
{
private int num;// 有多少单词通过这个节点,即由根至该节点组成的字符串模式出现的次数
private TrieNode[] son;// 所有的儿子节点
private boolean isEnd;// 是不是最后一个节点
private char val;// 节点的值 TrieNode()
{
num = 1;
son = new TrieNode[SIZE];
isEnd = false;
}
}

  2、字典树构造函数

    Trie() // 初始化字典树
{
root = new TrieNode();
}

  3、建立字典树

// 建立字典树
public void insert(String str) // 在字典树中插入一个单词
{
if (str == null || str.length() == 0)
{
return;
}
TrieNode node = root;
char[] letters = str.toCharArray();//将目标单词转换为字符数组
for (int i = 0, len = str.length(); i < len; i++)
{
int pos = letters[i] - 'a';
if (node.son[pos] == null) //如果当前节点的儿子节点中没有该字符,则构建一个TrieNode并复值该字符
{
node.son[pos] = new TrieNode();
node.son[pos].val = letters[i];
}
else //如果已经存在,则将由根至该儿子节点组成的字符串模式出现的次数+1
{
node.son[pos].num++;
}
node = node.son[pos];
}
node.isEnd = true;
}

  4、在字典树中查找是否完全匹配一个指定的字符串

    // 在字典树中查找一个完全匹配的单词.
public boolean has(String str)
{
if(str==null||str.length()==0)
{
return false;
}
TrieNode node=root;
char[]letters=str.toCharArray();
for(int i=0,len=str.length(); i<len; i++)
{
int pos=letters[i]-'a';
if(node.son[pos]!=null)
{
node=node.son[pos];
}
else
{
return false;
}
}
//走到这一步,表明可能完全匹配,也可能部分匹配,如果最后一个字符节点为末端节点,则是完全匹配,否则是部分匹配
return node.isEnd;
}

  5、前序遍历字典树

  // 前序遍历字典树.
public void preTraverse(TrieNode node)
{
if(node!=null)
{
System.out.print(node.val+"-");
for(TrieNode child:node.son)
{
preTraverse(child);
}
}
}

  6、计算单词前缀的数量

  // 计算单词前缀的数量
public int countPrefix(String prefix)
{
if(prefix==null||prefix.length()==0)
{
return-1;
}
TrieNode node=root;
char[]letters=prefix.toCharArray();
for(int i=0,len=prefix.length(); i<len; i++)
{
int pos=letters[i]-'a';
if(node.son[pos]==null)
{
return 0;
}
else
{
node=node.son[pos];
}
}
return node.num;
}

  完整代码:

package com.xj.test;

public class Trie
{
private int SIZE = 26;
private TrieNode root;// 字典树的根 class TrieNode // 字典树节点
{
private int num;// 有多少单词通过这个节点,即由根至该节点组成的字符串模式出现的次数
private TrieNode[] son;// 所有的儿子节点
private boolean isEnd;// 是不是最后一个节点
private char val;// 节点的值 TrieNode()
{
num = 1;
son = new TrieNode[SIZE];
isEnd = false;
}
}
Trie() // 初始化字典树
{
root = new TrieNode();
} // 建立字典树
public void insert(String str) // 在字典树中插入一个单词
{
if (str == null || str.length() == 0)
{
return;
}
TrieNode node = root;
char[] letters = str.toCharArray();//将目标单词转换为字符数组
for (int i = 0, len = str.length(); i < len; i++)
{
int pos = letters[i] - 'a';
if (node.son[pos] == null) //如果当前节点的儿子节点中没有该字符,则构建一个TrieNode并复值该字符
{
node.son[pos] = new TrieNode();
node.son[pos].val = letters[i];
}
else //如果已经存在,则将由根至该儿子节点组成的字符串模式出现的次数+1
{
node.son[pos].num++;
}
node = node.son[pos];
}
node.isEnd = true;
} // 计算单词前缀的数量
public int countPrefix(String prefix)
{
if(prefix==null||prefix.length()==0)
{
return-1;
}
TrieNode node=root;
char[]letters=prefix.toCharArray();
for(int i=0,len=prefix.length(); i<len; i++)
{
int pos=letters[i]-'a';
if(node.son[pos]==null)
{
return 0;
}
else
{
node=node.son[pos];
}
}
return node.num;
} // 打印指定前缀的单词
public String hasPrefix(String prefix)
{
if (prefix == null || prefix.length() == 0)
{
return null;
}
TrieNode node = root;
char[] letters = prefix.toCharArray();
for (int i = 0, len = prefix.length(); i < len; i++)
{
int pos = letters[i] - 'a';
if (node.son[pos] == null)
{
return null;
}
else
{
node = node.son[pos];
}
}
preTraverse(node, prefix);
return null;
} // 遍历经过此节点的单词.
public void preTraverse(TrieNode node, String prefix)
{
if (!node.isEnd)
{
for (TrieNode child : node.son)
{
if (child != null)
{
preTraverse(child, prefix + child.val);
}
}
return;
}
System.out.println(prefix);
} // 在字典树中查找一个完全匹配的单词.
public boolean has(String str)
{
if(str==null||str.length()==0)
{
return false;
}
TrieNode node=root;
char[]letters=str.toCharArray();
for(int i=0,len=str.length(); i<len; i++)
{
int pos=letters[i]-'a';
if(node.son[pos]!=null)
{
node=node.son[pos];
}
else
{
return false;
}
}
//走到这一步,表明可能完全匹配,可能部分匹配,如果最后一个字符节点为末端节点,则是完全匹配,否则是部分匹配
return node.isEnd;
} // 前序遍历字典树.
public void preTraverse(TrieNode node)
{
if(node!=null)
{
System.out.print(node.val+"-");
for(TrieNode child:node.son)
{
preTraverse(child);
}
}
} public TrieNode getRoot()
{
return this.root;
} public static void main(String[]args)
{
Trie tree=new Trie();
String[]strs= {"banana","band","bee","absolute","acm",};
String[]prefix= {"ba","b","band","abc",};
for(String str:strs)
{
tree.insert(str);
}
System.out.println(tree.has("abc"));
tree.preTraverse(tree.getRoot());
System.out.println();
//tree.printAllWords();
for(String pre:prefix)
{
int num=tree.countPrefix(pre);
System.out.println(pre+"数量:"+num);
}
}
}

  执行结果截图:

  

三、简单应用

  下面讲一个简单的应用,问题是这样的:

  现在有一个英文字典(每个单词都是由小写的a-z组成),单词量很大,而且还有很多重复的单词。

  此外,我们还有一些Document,每个Document包含一些英语单词。下面是问题:

  (问题1)请你选择合适的数据结构,将所有的英文单词生成一个字典Dictionary?

  (问题2)给定一个单词,判断这个单词是否在字典Dictionary中,如果在单词库中,输出这个单词出现总共出现的次数,否则输出NO?

package com.xj.test;

import java.io.BufferedReader;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStreamReader;
import java.util.HashMap;
import java.util.Map; public class Trie
{
private int SIZE = 26;
private TrieNode root;// 字典树的根 class TrieNode // 字典树节点
{
private int num;// 有多少单词通过这个节点,即由根至该节点组成的字符串模式出现的次数
private TrieNode[] son;// 所有的儿子节点
private boolean isEnd;// 是不是最后一个节点
private char val;// 节点的值 TrieNode()
{
num = 1;
son = new TrieNode[SIZE];
isEnd = false;
}
}
Trie() // 初始化字典树
{
root = new TrieNode();
} // 建立字典树
public void insert(String str) // 在字典树中插入一个单词
{
if (str == null || str.length() == 0)
{
return;
}
TrieNode node = root;
char[] letters = str.toCharArray();//将目标单词转换为字符数组
for (int i = 0, len = str.length(); i < len; i++)
{
int pos = letters[i] - 'a';
if (node.son[pos] == null) //如果当前节点的儿子节点中没有该字符,则构建一个TrieNode并复值该字符
{
node.son[pos] = new TrieNode();
node.son[pos].val = letters[i];
}
else //如果已经存在,则将由根至该儿子节点组成的字符串模式出现的次数+1
{
node.son[pos].num++;
}
node = node.son[pos];
}
node.isEnd = true;
} // 计算单词前缀的数量
public int countPrefix(String prefix)
{
if(prefix==null||prefix.length()==0)
{
return-1;
}
TrieNode node=root;
char[]letters=prefix.toCharArray();
for(int i=0,len=prefix.length(); i<len; i++)
{
int pos=letters[i]-'a';
if(node.son[pos]==null)
{
return 0;
}
else
{
node=node.son[pos];
}
}
return node.num;
} // 打印指定前缀的单词
public String hasPrefix(String prefix)
{
if (prefix == null || prefix.length() == 0)
{
return null;
}
TrieNode node = root;
char[] letters = prefix.toCharArray();
for (int i = 0, len = prefix.length(); i < len; i++)
{
int pos = letters[i] - 'a';
if (node.son[pos] == null)
{
return null;
}
else
{
node = node.son[pos];
}
}
preTraverse(node, prefix);
return null;
} // 遍历经过此节点的单词.
public void preTraverse(TrieNode node, String prefix)
{
if (!node.isEnd)
{
for (TrieNode child : node.son)
{
if (child != null)
{
preTraverse(child, prefix + child.val);
}
}
return;
}
System.out.println(prefix);
} // 在字典树中查找一个完全匹配的单词.
public boolean has(String str)
{
if(str==null||str.length()==0)
{
return false;
}
TrieNode node=root;
char[]letters=str.toCharArray();
for(int i=0,len=str.length(); i<len; i++)
{
int pos=letters[i]-'a';
if(node.son[pos]!=null)
{
node=node.son[pos];
}
else
{
return false;
}
}
//走到这一步,表明可能完全匹配,可能部分匹配,如果最后一个字符节点为末端节点,则是完全匹配,否则是部分匹配
return node.isEnd;
} // 前序遍历字典树.
public void preTraverse(TrieNode node)
{
if(node!=null)
{
System.out.print(node.val+"-");
for(TrieNode child:node.son)
{
preTraverse(child);
}
}
}
public TrieNode getRoot()
{
return this.root;
}
public static void main(String[]args) throws IOException
{
Trie tree=new Trie();
String[] dictionaryData= {"hello","student","computer","sorry","acm","people","experienced","who","reminds","everyday","almost"};
//构建字典
for(String str:dictionaryData)
{
tree.insert(str);
}
String filePath="C:\\Users\\Administrator\\Desktop\\sourceFile.txt";
File file=new File(filePath);
if(file.isFile() && file.exists())
{
InputStreamReader read = new InputStreamReader(new FileInputStream(file));
BufferedReader bufferedReader = new BufferedReader(read);
String lineTxt = null;
Map<String,Integer> countMap=new HashMap<String,Integer>();
while((lineTxt = bufferedReader.readLine())!= null)
{
if(tree.has(lineTxt))
{
if(countMap.containsKey(lineTxt))
{
countMap.put(lineTxt, countMap.get(lineTxt)+1);
}
else
{
countMap.put(lineTxt, 1);
}
}
else
{
System.out.println(lineTxt+"不在字典中!");
}
}
for(String s:countMap.keySet())
{
System.out.println(s+"出现的次数"+countMap.get(s));
}
read.close();
}
} }

  其中text文件内容为:

  

  程序执行结果为:

  

四、参考资料

  1、http://baike.baidu.com/link?url=X0XQ-obbacAS3GsVN1ktZtaVEPp0u7J1aClFdwdq-DiFjS-kSE-Ce1-q9_dLXb58PDyOkQxK0kB2l1PFUpB36_

  

  

字典树(Trie树)实现与应用的更多相关文章

  1. 字典树(Trie树)的实现及应用

    >>字典树的概念 Trie树,又称字典树,单词查找树或者前缀树,是一种用于快速检索的多叉树结构,如英文字母的字典树是一个26叉树,数字的字典树是一个10叉树.与二叉查找树不同,Trie树的 ...

  2. [POJ] #1002# 487-3279 : 桶排序/字典树(Trie树)/快速排序

    一. 题目 487-3279 Time Limit: 2000MS   Memory Limit: 65536K Total Submissions: 274040   Accepted: 48891 ...

  3. Atitit 常见的树形结构 红黑树  二叉树   B树 B+树  Trie树 attilax理解与总结

    Atitit 常见的树形结构 红黑树  二叉树   B树 B+树  Trie树 attilax理解与总结 1.1. 树形结构-- 一对多的关系1 1.2. 树的相关术语: 1 1.3. 常见的树形结构 ...

  4. 洛谷$P4585\ [FJOI2015]$火星商店问题 线段树+$trie$树

    正解:线段树+$trie$树 解题报告: 传送门$QwQ$ $umm$题目有点儿长我先写下题目大意趴$QwQ$,就说有$n$个初始均为空的集合和$m$次操作,每次操作为向某个集合内加入一个数$x$,或 ...

  5. luoguP6623 [省选联考 2020 A 卷] 树(trie树)

    luoguP6623 [省选联考 2020 A 卷] 树(trie树) Luogu 题外话: ...想不出来啥好说的了. 我认识的人基本都切这道题了. 就我只会10分暴力. 我是傻逼. 题解时间 先不 ...

  6. [转载]字典树(trie树)、后缀树

    (1)字典树(Trie树) Trie是个简单但实用的数据结构,通常用于实现字典查询.我们做即时响应用户输入的AJAX搜索框时,就是Trie开始.本质上,Trie是一颗存储多个字符串的树.相邻节点间的边 ...

  7. Luogu P2922 [USACO08DEC]秘密消息Secret Message 字典树 Trie树

    本来想找\(01Trie\)的结果找到了一堆字典树水题...算了算了当水个提交量好了. 直接插入模式串,维护一个\(Trie\)树的子树\(sum\)大小,求解每一个文本串匹配时走过的链上匹配数和终点 ...

  8. 字典树 trie树 学习

    一字典树 字典树,又称单词查找树,Trie树,是一种树形结构,哈希表的一个变种   二.性质 根节点不包含字符,除根节点以外的每一个节点都只包含一个字符: 从根节点到某一节点,路径上经过的字符串连接起 ...

  9. 【字符串算法】字典树(Trie树)

    什么是字典树 基本概念 字典树,又称为单词查找树或Tire树,是一种树形结构,它是一种哈希树的变种,用于存储字符串及其相关信息. 基本性质 1.根节点不包含字符,除根节点外的每一个子节点都包含一个字符 ...

  10. 字典树 Trie树

    什么是Trie树? 形如 其中从根节点到红色节点的路径上的字母所连成的字符串即为一个Trie树上所存的字符串. 比如,这个trie树上有ab,abc,bd,dda这些字符串. 至于怎么构建和查找或添加 ...

随机推荐

  1. HTTP - GET和POST的区别

    网上有很多文章介绍这两种HTTP请求的区别,我也不懂,主要还是看了一些文章,在这里写下一些笔记. 语义不同 在HTTP协议中,最初规定GET是用来查询或者获取资料,只读,POST用于修改数据,可写.因 ...

  2. C# Activex开发、打包、签名、发布 C# Activex开发、打包、签名、发布 [转]

    C# Activex开发.打包.签名.发布 2013-06-22 12:01:20 浏览:3823 一.前言 最近有这样一个需求,需要在网页上面启动客户端的软件,软件之间的通信.调用,单单依靠HTML ...

  3. ubuntu 12.04 安装Docker 实战

    2016-3-8 从网络服务商那里申请到一台Ubuntu测试服务器,用来测试安装Docker环境. 注:本人初学Docker,对Linux命令也仅是稍稍了解,如有错误,烦请告知. 查看系统相关信息 可 ...

  4. Activity intent经常使用的 FLAG

    Intent.FLAG_ACTIVITY_NEW_TASK 默认的跳转类型,会重新创建一个新的Activity,不过与这种情况,比方说Task1中有A,B,C三个Activity,此时在C中启动D的话 ...

  5. android 左右翻页

    布局: <?xml version="1.0" encoding="utf-8"?><RelativeLayout xmlns:android ...

  6. 将树苺派升级到Raspbian 8 (Jessie)

    我的树苺派2B跑的是Raspbian 7 (Wheezy),有不少软件都让我觉察出老旧来.想着Debian官方已经发布Debian 8 (Jessie)大半年了(8.0发布于2015/04/25),树 ...

  7. eclipse 代码提示时闪退问题

    解决办法:在eclipse.ini里面最下面加上这句话 -Dorg.eclipse.swt.browser.DefaultType=mozilla

  8. No Assistant Results

    由于修改一些文件名字等会导致这个不工作. "Organizer" / "Projects" / 选择你的项目.  "Delete" .

  9. 使用BAT批处理执行sql语句的代码

    使用BAT批处理执行sql语句的代码 有时候需要执行一些Sql语句时,不想开企业管理器,或者是发给客户执行但那边又不懂代码,这时就可以用下面方法 1.把待执行Sql保存在一个文件,这里为2011022 ...

  10. saiku 升级&备份&恢复

    最近使用saiku,重新编译升级之后维护困难,下面讲解一下简单的办法: 1.备份saiku-server下面的data文件夹 2.备份saiku-server下面的repository文件夹 3.升级 ...