图解Tire树+代码实现
简介
Trie又称为前缀树或字典树,是一种有序树,它是一种专门用来处理串匹配的数据结构,用来解决一组字符中快速查找某个字符串的问题。Google搜索的关键字提示功能相信大家都不陌生,我们在输入框中进行搜索的时候,会下拉出一系列候选关键词。
上面这个关键词提示功能,底层最基本的原理就是我们今天说的数据结构:Trie树
我们先看看Tire树长什么样子,以单纯的单词匹配为例,首先它是一棵多叉树结构,根节点是一个空字符,树中节点分为普通节点和结尾节点(如图中红色节点)。结尾节点表示加上前面前缀,可以称为一个单词,如图中hi,him。
工作过程
Tire树与之前串匹配最大的不同点是,之前我们都是单模式串,查看主串中是否有与模式串匹配的子串,操作过程也是用模式串去与主串进行比较。而Tire树是多模式串,我们先将模式串提前构建成Tire树,然后查看主串是否匹配模式串,且更适用于类似如上关键词提示的前缀匹配。接下来我们自己通过实现一个简易的关键词提示功能来讲解Tire树。
数据结构
一个value存储当前节点值,用一个26大小的数组存储当前节点的孩子节点,这是一个简单但是可能产生浪费的方法,可以采用有序存入采用二分法查找,或者采用hash表,跳表进行优化。一个标志当前节点是否可作为尾节点。
/**
* Trie树节点
* 假设我们只做26个小写字母下的匹配
*/
public static class Node{
//当前节点值
private char value;
//当前节点的孩子节点
private Node[] childNode;
//标志当前节点是否是某单词结尾
private boolean isTail;
public Node(char value) {
this.value = value;
}
}
初始化
初始化一个仅有root节点的Tire树,root节点值为'/0'。
Node root;
public void init() {
root = new Node('\0');
root.childNode = new Node[26];
}
构建字典树
将需要加入的模式串加入Tire树,遍历当前字符串字符,从Tire树根节点开始查找当前字符,如果字符已经存在不需要处理,并且从这个字符节点出发,查看下一个字符是否存在,如果当前节点不存Tire树,才需要插入当前字符,当插入最后一个字符时需要标志当前字符节点为尾节点。
/**
* 将当前串插入字典树
* @param chars
*/
public void insertStr(char[] chars) {
//首先判断首字符是否已经在字典树中,然后判断第二字符,依次往下进行判断,找到第一个不存在的字符进行插入孩节点
Node p = root;
//表明当前处理到了第几个字符
int chIndex = 0;
while (chIndex < chars.length) {
while (chIndex < chars.length && null != p) {
Node[] children = p.childNode;
boolean find = false;
for (Node child : children) {
if (null == child) {continue;}
if (child.value == chars[chIndex]) {
//当前字符已经存在,不需要再进行存储
//从当前节点出发,存储下一个字符
p = child;
++ chIndex;
find = true;
break;
}
}
if (Boolean.TRUE.equals(find)) {
//在孩子中找到了 不用再次存储
break;
}
//如果把孩子节点都找遍了,还没有找到这个字符,直接将这个字符加入当前节点的孩子节点
Node node = new Node(chars[chIndex]);
node.childNode = new Node[26];
children[chars[chIndex] - 'a'] = node;
p = node;
++ chIndex;
}
}
//字符串中字符全部进入tire树中后,将最后一个字符所在节点标志为结尾节点
p.isTail = true;
}
应用
匹配有效单词
遍历字符串,从根节点出发,查看字符是否存在,只要存在不存在的情况,直接返回false,如果每个字符都存在,判断最后一个字符是否为结尾节点,如果不是,到这里还不是一个有效单词,返回false,否则,返回true。
/**
* 查看当前字符串是否可以在trie中找到
* @param str 主串
* @return true/false
*/
public boolean isMatch(String str) {
//从root开始进行匹配,只要有一个找不到即为匹配失败
char[] chars = str.toCharArray();
int chIndex = 0;
Node p = root;
while (null != p) {
Node[] children = p.childNode;
boolean flag = false;
for (Node child : children) {
if (null == child) {continue;}
if (child.value == chars[chIndex]) {
flag = true;
p = child;
++ chIndex;
//当比较最后一个字符的时候,这个字符需要是结尾字符才能完全匹配
if (chIndex == chars.length && p.isTail) {
return true;
}
break;
}
}
if (Boolean.FALSE.equals(flag)) {
return false;
}
}
return false;
}
测试样例:
public static void main(String[] args) {
//he, him, lot, a
//初始化Tire树
Trie trie = new Trie();
trie.init();
//构建Tire树,只有以下单词才是有效单词
trie.insertStr("he".toCharArray());
trie.insertStr("him".toCharArray());
trie.insertStr("lot".toCharArray());
trie.insertStr("a".toCharArray());
//匹配字符串是否为有效单词
System.out.println(trie.isMatch("lot"));
System.out.println(trie.isMatch("lit"));
}
运行结果:
关键词提示
根据输入的关键词前缀,匹配所有可能出现的关键词。首先遍历字符串,从节点出发,只要有一个找不到,直接返回null,直至找到最后一个字符对应的节点,从该节点出发找到所有尾节点。
/**
* 找到所有以str为前缀的字符串
* @param str 前缀串
* @return 所有以str为前缀的单词
*/
public List<String> findStrPrefix(String str) {
//根据str首先找到str最后一个字符,然后从这个字符出发,找到所有字符串
List<String> result = new ArrayList<>();
char[] chars = str.toCharArray();
//分成两步走
//1。找到str最后一个自字符在字典树中的node
//2。从该node出发,找到所有的结尾node,即为以str为前缀的字符串
int chIndex = 0;
Node p = root;
while (null != p && chIndex < chars.length) {
Node[] children = p.childNode;
boolean flag = false;
for (Node child : children) {
if (null == child) {continue;}
if (child.value == chars[chIndex]) {
//已经找到
p = child;
flag = true;
++ chIndex;
break;
}
}
//如果没有找到,直接返回空
if (Boolean.FALSE.equals(flag)) {
return null;
}
}
//找到了最后一个节点
//深度优先遍历,查找所有尾节点
this.dfs(p, new StringBuilder(str), result);
return result;
}
public void dfs(Node p, StringBuilder str, List<String> result) {
Node[] children = p.childNode;
for (Node child : children) {
if (null == child) {
continue;
}
str.append(child.value);
if (child.isTail) {
result.add(str.toString());
}
//再递归查当前节点的孩子节点
dfs(child, str, result);
//需要将刚刚set进去的节点删除,否则影响当前节点的下一个孩子节点
//举个例子,h的孩子节点有e,i,当e放进去之后不拿出来,在遍历到i的时候,就会形成hei
str.setLength(str.length() - 1);
}
}
测试样例
public static void main(String[] args) {
//he, him, lot, a
//初始化Tire树
Trie trie = new Trie();
trie.init();
//构建Tire树,只有以下单词才是有效单词
trie.insertStr("he".toCharArray());
trie.insertStr("him".toCharArray());
trie.insertStr("lot".toCharArray());
trie.insertStr("a".toCharArray());
//匹配字符串是否为有效单词
List<String> strings = trie.findStrPrefix("h");
}
运行结果
总结
到这里Trie树就讲完了,主要就是聚合前缀,通过树的特性,按照链路进行访问,同时标志尾节点,标志到当前节点是一个完整的字符串。
图解Tire树+代码实现的更多相关文章
- AC自动机:Tire树+KMP
简介 AC自动机是一个多模式匹配算法,在模式匹配领域被广泛应用,举一个经典的例子,违禁词查找并替换为***.AC自动机其实是Trie树和KMP 算法的结合,首先将多模式串建立一个Tire树,然后结合K ...
- [数据结构]字典树(Tire树)
概述: Trie是个简单但实用的数据结构,是一种树形结构,是一种哈希树的变种,相邻节点间的边代表一个字符,这样树的每条分支代表一则子串,而树的叶节点则代表完整的字符串.和普通树不同的地方是,相同的字符 ...
- UVa 11732 (Tire树) "strcmp()" Anyone?
这道题也是卡了挺久的. 给出一个字符串比较的算法,有n个字符串两两比较一次,问一共会有多少次比较. 因为节点会很多,所以Tire树采用了左儿子右兄弟的表示法来节省空间. 假设两个不相等的字符串的最长公 ...
- UVa 1401 (Tire树) Remember the Word
d(i)表示从i开始的后缀即S[i, L-1]的分解方法数,字符串为S[0, L-1] 则有d(i) = sum{ d(i+len(x)) | 单词x是S[i, L-1]的前缀 } 递推边界为d(L) ...
- 基于Tire树和最大概率法的中文分词功能的Java实现
对于分词系统的实现来说,主要应集中在两方面的考虑上:一是对语料库的组织,二是分词策略的制订. 1. Tire树 Tire树,即字典树,是通过字串的公共前缀来对字串进行统计.排序及存储的一种树形结构 ...
- 海量数据处理之Tire树(字典树)
参考博文:http://blog.csdn.net/v_july_v/article/details/6897097 第一部分.Trie树 1.1.什么是Trie树 Trie树,即字典树,又称单词查找 ...
- 「十二省联考 2019」异或粽子——tire树+堆
题目 [题目描述] 小粽是一个喜欢吃粽子的好孩子.今天她在家里自己做起了粽子. 小粽面前有 $n$ 种互不相同的粽子馅儿,小粽将它们摆放为了一排,并从左至右编号为 $1$ 到 $n$.第 $i$ 种馅 ...
- Trie树(代码),后缀树(代码)
Trie树系列 Trie字典树 压缩的Trie 后缀树Suffix tree 后缀树--ukkonen算法 Trie是通过对字符串进行预先处理,达到加快搜索速度的算法.即把文本中的字符串转换为树结构, ...
- Tire树模板-于是他错误的点名开始了
题目背景 XS中学化学竞赛组教练是一个酷爱炉石的人. 他会一边搓炉石一边点名以至于有一天他连续点到了某个同学两次,然后正好被路过的校长发现了然后就是一顿欧拉欧拉欧拉(详情请见已结束比赛CON900). ...
随机推荐
- 使用Servlet编写增删改查
第一步创建一个表 1 create database liyongzhendb default character set utf8 collate utf8_bin; 2 3 CREATE TABL ...
- 学习Keepalived(三)
1.1Keepalived高可用软件 Keepalived起初是专为LVS设计的,专门用来监控LVS集群系统中各个服务节点的状态,后来又加入了VRRP的功能,因此除了配合LVS服务外,也可以作为其他服 ...
- 2. 使用Github
2. 使用Github 2.1 目的 借助github托管项目代码 2.2 基本概念 仓库(Repository) 仓库用来存放项目代码,每个项目对应一个仓库,多个开源项目则有多个仓库 收藏(Star ...
- HTML5摇一摇(上)—如何判断设备摇动
刚刚过去的一年里基于微信的H5营销可谓是十分火爆,通过转发朋友圈带来的病毒式传播效果相信大家都不太陌生吧,刚好最近农历新年将至,我就拿一个"摇签"的小例子来谈一谈HTML5中如何调 ...
- vue2.0开发聊天程序(八) 初步完成
项目地址 服务器源码地址:https://github.com/ermu592275254/chat-socket 网页源码地址:https://github.com/ermu592275254/ch ...
- 如何保证同事的代码不会腐烂?一文带你了解 阿里巴巴 COLA 架构
一起养成写作习惯!这是我参与「掘金日新计划 · 4 月更文挑战」的第1天,点击查看活动详情. 本文开始前,问大家一个问题,你觉得一份业务代码,尤其是互联网业务代码,都有哪些特点? 我能想到的有这几点: ...
- SourceMonitor的安装
SourceMonitor 本词条缺少名片图,补充相关内容使词条更完整,还能快速升级,赶紧来编辑吧! 中文名 SourceMonitor 软件大小 1743KB 软件语言 英文 软件类别 国外软件 ...
- Mybatis 多表实现多对一查询、添加操作
Mybatis 多表实现多对一查询.添加操作 学习内容: 1. 多对一之添加操作 1.1.需求 1.2.数据库表(多对一或一对多,数据库外键都是设置在多的一方) 1.3.类设计 1.4.Mapper ...
- jboss7学习2-jboss7入门(端口和访问的ip问题)
1.下载地址: http://www.jboss.org/jbossas/downloads ,下载Certified Java EE 6 Full Profile版本. 2.解压 jboss-as- ...
- 微信小程序支付框样式以及功能
1.页面代码 <view catchtap='showInputLayer' class="btn_pay">立即支付</view> <!-- 密码输 ...