C# TrieTree介绍及实现方法
发布时间:2022-02-10 22:04:53
标签:TrieTree
在自然语言处理(NLP)研究中,NGram是最基本但也是最有用的一种比对方式,这里的N是需要比对的字符串的长度,而今天我介绍的TrieTree,正是和NGram密切相关的一种数据结构,有人称之为字典树。TrieTree简单的说是一种多叉树,每个节点保存一个字符,这么做的好处是当我们要做NGram比对时,只需要直接从树的根节点开始沿着某个树叉遍历下去,就能完成比对;如果没找到,停止本次遍历。这话讲得有些抽象,我们来看一个实际的例子。
假设我们现在词库里面有以下一些词:
上海市
上海滩
上海人
上海公司
北京
北斗星
杨柳
杨浦区
如图所示:挂在根节点上的字有上、北、杨,
如果我们现在对“上海市杨浦区”这个词做3gram就有上海市、海市杨、市杨浦、杨浦区,现在我们要知道哪些词是能够被这个字典识别的,通常我们可以用NGram来做分词。有了这颗树,我们只需要依次取每个字符,从根开始进行比对,比如上海市,我们能够匹配 上->海->市,这个路径,所以匹配;比如海市杨,由于没有“海”字挂在根节点上,所以停止;市杨浦也无法匹配;最终匹配杨浦区,得到 杨->浦->区 这个路径,匹配。
最终我们可以把“上海市杨浦区”切分为 上海市|杨浦区。
尽管TrieTree要比普通字符串数组节省很多时间,但这并不是没有代价的,因为你要先根据字典构建这棵树,这个代价并不低,当然对于某个应用来说一旦TrieTree构建完成就可以重复使用,所以针对大规模比对来说,性能提升还是很客观的。
下面是TrieTree的C#实现。
public class TrieTree
{
TrieNode _root = null;
private TrieTree()
{
_root = new TrieNode(char.MaxValue,0);
charCount = 0;
}
static TrieTree _instance = null;
public static TrieTree GetInstance()
{
if (_instance == null)
{
_instance = new TrieTree();
}
return _instance;
}
public TrieNode Root
{
get { return _root;
}
}
public void AddWord(char ch)
{
TrieNode newnode=_root.AddChild(ch);
newnode.IncreaseFrequency();
newnode.WordEnded = true;
} int charCount;
public void AddWord(string word)
{
if (word.Length == 1)
{
AddWord(word[0]);
charCount++;
}
else
{
char[] chars=word.ToCharArray();
TrieNode node = _root;
charCount += chars.Length;
for (int i = 0; i < chars.Length; i++)
{
TrieNode newnode=node.AddChild(chars[i]);
newnode.IncreaseFrequency();
node = newnode;
}
node.WordEnded = true;
}
}
public int GetFrequency(char ch)
{
TrieNode matchedNode = _root.Children.FirstOrDefault(n => n.Character == ch);
if (matchedNode == null)
{
return 0;
}
return matchedNode.Frequency;
}
public int GetFrequency(string word)
{
if (word.Length == 1)
{
return GetFrequency(word[0]);
}
else
{
char[] chars = word.ToCharArray();
TrieNode node = _root;
for (int i = 0; i < chars.Length; i++)
{
if (node.Children == null)
return 0;
TrieNode matchednode = node.Children.FirstOrDefault(n => n.Character == chars[i]);
if (matchednode == null)
{
return 0;
}
node = matchednode;
}
if (node.WordEnded == true)
return node.Frequency;
else
return -1;
}
}
}
这里我们使用了单例模式,因为TrieTree类似缓存,不需要重复创建。下面是TreeNode的实现:
public class TrieNode
{
public TrieNode(char ch,int depth)
{
this.Character=ch;
this._depth=depth;
}
public char Character;
int _depth;
public int Depth
{
get{return _depth;
}
}
TrieNode _parent=null;
public TrieNode Parent
{
get {
return _parent;
}
set { _parent = value;
}
}
public bool WordEnded = false;
HashSet<TrieNode> _children=null;
public HashSet<TrieNode> Children
{
get {
return _children;
}
}
public TrieNode GetChildNode(char ch)
{
if (_children != null)
return _children.FirstOrDefault(n => n.Character == ch);
else
return null;
}
public TrieNode AddChild(char ch)
{
TrieNode matchedNode=null;
if (_children != null)
{
matchedNode = _children.FirstOrDefault(n => n.Character == ch);
}
if (matchedNode != null)
//found the char in the list
{
//matchedNode.IncreaseFrequency();
return matchedNode;
}
else
{
//not found
TrieNode node = new TrieNode(ch, this.Depth + 1);
node.Parent = this;
//node.IncreaseFrequency();
if (_children == null)
_children = new HashSet<TrieNode>();
_children.Add(node);
return node;
}
}
int _frequency = 0;
public int Frequency
{
get { return _frequency;
}
}
public void IncreaseFrequency()
{
_frequency++;
}
public string GetWord()
{
TrieNode tmp=this;
string result = string.Empty;
while(tmp.Parent!=null) //until root node
{
result = tmp.Character + result;
tmp = tmp.Parent;
}
return result;
}
public override string ToString()
{
return Convert.ToString(this.Character);
}
}


猜你喜欢
- 为什么要使用路由在之前我们的代码中,页面跳转使用的代码如下所示:Navigator.of(context).push( Mate
- 昨晚,一同事问到我,怎么利用java反射解析内部类静态成员变量的值,于是顺手写下了。废话不多说,直接上代码!待解析类结构如下:/** * @
- 1.概述数据库开发一直是JAVA开发的核心之一,作为现在JAVA EE的基石框架,Spring Boot自身携带了一个JDBCTemplat
- Singleton是众多设计模式中最容易理解的一种,也是众多设计模式中较为重要的一种设计模式。接下来我们看看具体介绍。Singleton模式
- 先创建一个title.xml<LinearLayout xmlns:android="http:/
- 问题注意:本人使用的Spring Boot 2.0.2, 对1.5.x系列未必有用。官方文档在这里直接解决办法0, 移除spring-boo
- 百度是个好东西,这篇调用了百度的接口(当然大牛也可以自己写),人脸检测技术,所以使用的前提是有网的情况下。当然大家也可以去参考百度的文档。话
- 本文实例讲述了Java程序中实现调用Python脚本的方法。分享给大家供大家参考,具体如下:在程序开发中,有时候需要Java程序中调用相关P
- 通过ExifInterface可以将拍照时的一些属性信息写入图片文件里,其中包括经纬度信息。本文介绍一种将经纬度坐标写入JPEG图片文件的方
- 前言Spring动态配置多数据源,即在大型应用中对数据进行切分,并且采用多个数据库实例进行管理,这样可以有效提高系统的水平伸缩性。而这样的方
- 1.Android 连接MySQL数据库public class DBOpenHelper {private static String d
- 占位符Placeholder的使用xml中的配置:<?xml version="1.0" encoding=&qu
- 这个东西我已经用了有段时间了,从开始写文章就在用这个,主要原因还是因为我比较懒。懒得去寻找图片,同时又怕万一惹来版权争议。。。跟我所有的文章
- 多线程内容大致分两部分,其一是异步操作,可通过专用,线程池,Task,Parallel,PLINQ等,而这里又涉及工作线程与IO线程;其二是
- 一、简介BeanPostProcessor是Spring IOC容器给我们提供的一个扩展接口。实例化Bean做前置处理、后置处理二、接口定义
- 将网络资源url转化为File文件将互联网上的http开头的url资源,保存到本地。 private File getNetUrlHttp(
- 网易Java程序员两轮面试题,请作答。part 1:网易JAVA程序员一面1.volatile有什么用?2.Minor GC和Full GC
- 一、使用JDK生成WSDL的对象类1、cmd进入JDK的bin文件中执行命令 wsimport -keep -p com.demo.clie
- 运行效果C#实现using Android.App;using Android.OS;using Android.Widget;namesp
- TCP与UDP都属于TCP/IP协议TCP(Transmission Control Protocol,传输控制协议)是面向连接的协议,也就