Trie树分析
时间:2022-05-06
本文章向大家介绍Trie树分析,主要内容包括Trie树、Trie树的节点类、Trie树的初始化、Trie树的插入、Trie树的构建、Trie树的查找、Trie树中是否包某个前缀、Trie树前序遍历、获取指定前缀出现次数、获取含有指定前缀的所有单词、基本概念、基础应用、原理机制和需要注意的事项等,并结合实例形式分析了其使用技巧,希望通过本文能帮助到大家理解应用这部分内容。
Trie树
Trie树介绍
Trie,又称单词查找树或键树,是一种树形结构。典型应用是用于统计和排序大量的字符串(但不仅限于字符串),所以经常被搜索引擎系统用于文本词频统计。它的优点是:最大限度地减少无谓的字符串比较,查询效率比哈希表高。
它有3个基本性质:
1.根节点不包含字符,除根节点外每一个节点都只包含一个字符。
2.从根节点到某一节点,路径上经过的字符连接起来,为该节点对应的字符串。
3.每个节点的所有子节点包含的字符都不相同。
Trie中每个节点有一个特殊标记作为结束符号,通过该标记可以判断当前节点是否是一个字符串的终结节点。
下图是一个Trie树的例子,记录了to,tea,ted,ten,a,i,in,inn这些words(以蓝色结尾)。
个人总结了如下方法:
Trie树的节点类
class Node{
//节点字符值
private char val;
//节点的子节点,即以根节点到该节点的值组成的字符串为前缀的字符串构建的节点
private Map<Character,Node> childrens=new HashMap<Character,Node>();
//是否为结束节点,即一个字符串是否到达末尾节点 当end>0时表示结束节点
private int end=0;
//从根节点到该结束节点组成的字符串的重复数量 即单词列表中每个单词的词频
private int dumpliNum=0;
//以到达该节点组成的子串为前缀的字符串数量
private int prexNum=0;
/**
* @return the dumpliNum
*/
public int getDumpliNum() {
return dumpliNum;
}
/**
* @param dumpliNum the dumpliNum to set
*/
public void setDumpliNum(int dumpliNum) {
this.dumpliNum = dumpliNum;
}
/**
* @return the prexNum
*/
public int getPrexNum() {
return prexNum;
}
/**
* @param prexNum the prexNum to set
*/
public void setPrexNum(int prexNum) {
this.prexNum = prexNum;
}
/**
* @return the val
*/
public char getVal() {
return val;
}
/**
* @param val the val to set
*/
public void setVal(char val) {
this.val = val;
}
/**
* @return the childrens
*/
public Map<Character, Node> getChildrens() {
return childrens;
}
/**
* @param childrens the childrens to set
*/
public void setChildrens(Map<Character, Node> childrens) {
this.childrens = childrens;
}
/**
* @return the end
*/
public int getEnd() {
return end;
}
/**
* @param end the end to set
*/
public void setEnd(int end) {
this.end = end;
}
}
Trie树的初始化
public class Trie {
//待构建的字符串列表
private String words[];
private Node root;
public Trie(String[] words){
this.root=new Node();
this.words=words;
}
}
Trie树的插入
//插入方法
public void insert(String word){
if(word==""||word==null){
return;
}
Node cur=root;
char [] chars=word.toCharArray();
for(int i=0;i<chars.length;i++){
//如果不存在该值,则创建新节点
char val=chars[i];
if(!cur.getChildrens().containsKey(val)){
Node newNode=new Node();
newNode.setVal(val);
cur.getChildrens().put(val,newNode);
}
//如果已经存在则pass
cur=cur.getChildrens().get(val);
cur.setPrexNum(cur.getPrexNum()+1);
}
cur.setEnd(1);
cur.setDumpliNum(cur.getDumpliNum()+1);
}
Trie树的构建
public Node builtTrie(){
for (int i=0;i<words.length;i++){
insert(words[i]);
}
return root;
}
Trie树的查找
public boolean search(String word){
boolean flag=true;
Node cur=root;
char[] chars=word.toCharArray();
for(int i=0;i<chars.length;i++){
if(!cur.getChildrens().containsKey(chars[i])){
returnfalse;
}
cur=cur.getChildrens().get(chars[i]);//更新游标
}
return flag&&cur.getEnd()>0;//并且字符串最后一个字符必须是结束节点
}
Trie树中是否包某个前缀
//以某个字符串开头 比如字符串列表中有[abb,abbb],则ab返回true
public boolean startWith(String word){
boolean flag=true;
Node cur=root;
char[] chars=word.toCharArray();
for(int i=0;i<chars.length;i++){
if(!cur.getChildrens().containsKey(chars[i])){
return false;
}
cur=cur.getChildrens().get(chars[i]);//更新游标
}
//return flag&&cur.getEnd()>0;//并且该节点是结束节点
return flag;//和查找相比去掉了字符串最后一个字符要是结束节点
}
Trie树前序遍历
// 前序遍历,获取从指定节点开始,到所有结束节点组成的字符串
Map<String, Node> preTraversal(Nodenode,String word){
Map<String, Node> m=new HashMap<String,Node>();
if(node!=null){
if(node!=root&&node.getEnd()>0){
m.put(word, node);
}
for(Map.Entry<Character, Node>entry:node.getChildrens().entrySet()){
Node cur=entry.getValue();
String temp=word+String.valueOf(cur.getVal());
m.putAll(preTraversal(cur,temp));
}
}
return m;
}
获取指定前缀出现次数
//获取指定前缀出现次数
int countPrefix(String prefix){
Node cur =root;
char[] chars=prefix.toCharArray();
while(cur.getChildrens().size()>0){
for(int i=0;i<chars.length;i++){
if(cur.getChildrens().containsKey(chars[i])){
cur=cur.getChildrens().get(chars[i]);
}
}
return cur.getPrexNum();
}
return 0;
}
获取含有指定前缀的所有单词
//获取含有指定前缀的所有单词
Map<String,Node>containPrefixOfwords(String prefix){
Map<String,Node> m=newHashMap<String,Node>();
//找到前缀的最后一个单词的所在节点
Node cur=root;
char[] chars=prefix.toCharArray();
while(cur.getChildrens().size()>0){
for(int i=0;i<chars.length;i++){
if(cur.getChildrens().containsKey(chars[i])){
cur=cur.getChildrens().get(chars[i]);
}else{
return null;
}
}
break;
}
m.putAll(preTraversal(cur,prefix));
return m;
}
全部代码和测试类
package trie;
import java.util.HashMap;
import java.util.Map;
import java.util.Stack;
/**
*
*Trie
* Trie树
*@author: 汤高
*@date: :2018-1-8 下午4:33:44
*@version 1.0
*/
public class Trie {
//待构建的字符串列表
private String words[];
private Node root;
public Trie(String[] words){
this.root=new Node();
this.words=words;
}
public Node builtTrie(){
int index=0;
for (int i=0;i<words.length;i++){
insert(words[i],++index);
}
return root;
}
//插入方法
public void insert(String word){
if(word==""||word==null){
return;
}
Node cur=root;
char [] chars=word.toCharArray();
for(int i=0;i<chars.length;i++){
//如果不存在该值,则创建新节点
char val=chars[i];
if(!cur.getChildrens().containsKey(val)){
Node newNode=new Node();
newNode.setVal(val);
cur.getChildrens().put(val, newNode);
}
//如果已经存在则pass
cur=cur.getChildrens().get(val);
cur.setPrexNum(cur.getPrexNum()+1);
}
cur.setEnd(1);
cur.setDumpliNum(cur.getDumpliNum()+1);
}
//插入方法
public void insert(String word,int index){
if(word==""||word==null){
return;
}
Node cur=root;
char [] chars=word.toCharArray();
for(int i=0;i<chars.length;i++){
//如果不存在该值,则创建新节点
char val=chars[i];
if(!cur.getChildrens().containsKey(val)){
Node newNode=new Node();
newNode.setVal(val);
cur.getChildrens().put(val, newNode);
}
//如果已经存在则pass
cur=cur.getChildrens().get(val);
cur.setPrexNum(cur.getPrexNum()+1);
}
cur.setEnd(index);
cur.setDumpliNum(cur.getDumpliNum()+1);
}
//查找方法
public boolean search(String word){
boolean flag=true;
Node cur=root;
char[] chars=word.toCharArray();
for(int i=0;i<chars.length;i++){
if(!cur.getChildrens().containsKey(chars[i])){
return false;
}
cur=cur.getChildrens().get(chars[i]);//更新游标
}
return flag&&cur.getEnd()>0;//并且字符串最后一个字符必须是结束节点
}
//以某个字符串开头 比如 字符串列表中有[abb,abbb],则ab返回true
public boolean startWith(String word){
boolean flag=true;
Node cur=root;
char[] chars=word.toCharArray();
for(int i=0;i<chars.length;i++){
if(!cur.getChildrens().containsKey(chars[i])){
return false;
}
cur=cur.getChildrens().get(chars[i]);//更新游标
}
//return flag&&cur.getEnd()>0;//并且该节点是结束节点
return flag;//和查找相比去掉了 字符串最后一个字符要是结束节点
}
//前序遍历
Map<String, Node> preTraversal(Node node,String word){
Map<String, Node> m=new HashMap<String, Node>();
if(node!=null){
if(node!=root&&node.getEnd()>0){
m.put(word, node);
}
for(Map.Entry<Character, Node> entry:node.getChildrens().entrySet()){
Node cur=entry.getValue();
String temp=word+String.valueOf(cur.getVal());
m.putAll(preTraversal(cur,temp));
}
}
return m;
}
//获取指定前缀出现次数
int countPrefix(String prefix){
Node cur =root;
char[] chars=prefix.toCharArray();
while(cur.getChildrens().size()>0){
for(int i=0;i<chars.length;i++){
if(cur.getChildrens().containsKey(chars[i])){
cur=cur.getChildrens().get(chars[i]);
}
}
return cur.getPrexNum();
}
return 0;
}
//获取含有指定前缀的所有单词
Map<String,Node> containPrefixOfwords(String prefix){
Map<String,Node> m=new HashMap<String,Node>();
//找到前缀的最后一个单词的所在节点
Node cur=root;
char[] chars=prefix.toCharArray();
while(cur.getChildrens().size()>0){
for(int i=0;i<chars.length;i++){
if(cur.getChildrens().containsKey(chars[i])){
cur=cur.getChildrens().get(chars[i]);
}else{
return null;
}
}
break;
}
m.putAll(preTraversal(cur,prefix));
return m;
}
String getMax(){
String result="";
Stack<Node> stack=new Stack<Node>();
stack.push(root);
while(!stack.isEmpty()){
Node node=stack.pop();
if(node.getEnd()>0||node==root){
if(node!=root){
String word= this.words[node.getEnd()-1];
if(word.length()>result.length()||
word.length()==result.length()&&result.compareTo(word)>0){//最长的且具有最小字典序的word
result=word;
}
}
for(Node n:node.getChildrens().values()){//拿到子节点
stack.push(n);
}
}
}
return result;
}
class Node{
//节点字符值
private char val;
//节点的子节点,即以根节点到该节点的值组成的字符串为前缀的字符串构建的节点
private Map<Character,Node> childrens=new HashMap<Character,Node>();
//是否为结束节点,即一个字符串是否到达末尾节点 当end>0时表示结束节点
private int end=0;
//从根节点到该结束节点组成的字符串的重复数量 即单词列表中每个单词的词频
private int dumpliNum=0;
//以到达该节点组成的子串为前缀的字符串数量
private int prexNum=0;
/**
* @return the dumpliNum
*/
public int getDumpliNum() {
return dumpliNum;
}
/**
* @param dumpliNum the dumpliNum to set
*/
public void setDumpliNum(int dumpliNum) {
this.dumpliNum = dumpliNum;
}
/**
* @return the prexNum
*/
public int getPrexNum() {
return prexNum;
}
/**
* @param prexNum the prexNum to set
*/
public void setPrexNum(int prexNum) {
this.prexNum = prexNum;
}
/**
* @return the val
*/
public char getVal() {
return val;
}
/**
* @param val the val to set
*/
public void setVal(char val) {
this.val = val;
}
/**
* @return the childrens
*/
public Map<Character, Node> getChildrens() {
return childrens;
}
/**
* @param childrens the childrens to set
*/
public void setChildrens(Map<Character, Node> childrens) {
this.childrens = childrens;
}
/**
* @return the end
*/
public int getEnd() {
return end;
}
/**
* @param end the end to set
*/
public void setEnd(int end) {
this.end = end;
}
}
public static void main(String[] args) {
String[] words=new String[]{"ab","abb","ab","abbc","b","ba","bdd"};
Trie t=new Trie(words);
Node root=t.builtTrie();//构建trie树
System.out.println("trie树包含bdd吗? "+t.search("bdd"));
System.out.println("trie树包含bd吗? "+t.search("bd"));
System.out.println("trie树包含bd前缀吗?"+t.startWith("bd"));
System.out.println("trie树包含bdX前缀吗?"+t.startWith("bdX"));
System.out.println("根节点前序遍历,获取所有单词和它出现的次数");
for(Map.Entry<String, Node> en:t.preTraversal(root, "").entrySet()){
System.out.println(en.getKey()+"出现几次:"+en.getValue().dumpliNum);
}
System.out.println("以ab开头的前缀出现几次:"+t.countPrefix("ab"));
System.out.println("包含ab为前缀的所有单词");
for(Map.Entry<String, Node> en:t.containPrefixOfwords("ab").entrySet()){
System.out.println(en.getKey()+"单词出现几次:"+en.getValue().dumpliNum);
}
}
}
测试结果
trie树包含bdd吗? true
trie树包含bd吗? false
trie树包含bd前缀吗?true
trie树包含bdX前缀吗?false
根节点前序遍历,获取所有单词和它出现的次数
abb出现几次:1
b出现几次:1
ba出现几次:1
bdd出现几次:1
abbc出现几次:1
ab出现几次:2
以ab开头的前缀出现几次:4
包含ab为前缀的所有单词
abb单词出现几次:1
ab单词出现几次:2
abbc单词出现几次:1
- WebFont 三宗罪之一:WebFont 与 FOUT
- 探究基于声明的身份标识
- 深化“互联网+先进制造业”发展工业互联网的系列解读二:打造平台体系
- WebFont 三宗罪之二:吹毛求疵的WebFont 渲染差异
- IDC发布IT转型报告,现代化、自动化、转型三要素必不可少
- 存储过程和触发器的应用
- 两部委印发车联网产业标准体系建设指南 提到了自动驾驶
- 状态开关按钮ToggleButton
- 微软开放 .NET 框架源代码
- Angularjs基础(十二)
- 妙趣横生的HTML5 Page Visibility API
- 禁止/移除 WordPress 4.2 中前台自动加载的 emjo 脚本
- 项目管理方面的几个.NET开源项目
- 如何向十岁以下的朋友解释编程?这个说法碉堡了!
- JavaScript 教程
- JavaScript 编辑工具
- JavaScript 与HTML
- JavaScript 与Java
- JavaScript 数据结构
- JavaScript 基本数据类型
- JavaScript 特殊数据类型
- JavaScript 运算符
- JavaScript typeof 运算符
- JavaScript 表达式
- JavaScript 类型转换
- JavaScript 基本语法
- JavaScript 注释
- Javascript 基本处理流程
- Javascript 选择结构
- Javascript if 语句
- Javascript if 语句的嵌套
- Javascript switch 语句
- Javascript 循环结构
- Javascript 循环结构实例
- Javascript 跳转语句
- Javascript 控制语句总结
- Javascript 函数介绍
- Javascript 函数的定义
- Javascript 函数调用
- Javascript 几种特殊的函数
- JavaScript 内置函数简介
- Javascript eval() 函数
- Javascript isFinite() 函数
- Javascript isNaN() 函数
- parseInt() 与 parseFloat()
- escape() 与 unescape()
- Javascript 字符串介绍
- Javascript length属性
- javascript 字符串函数
- Javascript 日期对象简介
- Javascript 日期对象用途
- Date 对象属性和方法
- Javascript 数组是什么
- Javascript 创建数组
- Javascript 数组赋值与取值
- Javascript 数组属性和方法