数据结构与算法专题——第十二题 Trie树

今天来聊一聊Trie树,Trie树的名字有很多,比如字典树,前缀树等等。

一:概念

下面有and,as,at,cn,com这几个关键词,构建成 trie 树如下。

从上面图中,应该可以或多或少的发现一些好玩的特性。

  • 根节点不包含字符,除根节点外的每一个子节点都包含一个字符。

  • 从根节点到某一节点,路径上经过的字符连接起来,就是该节点对应的字符串。

  • 每个单词的公共前缀作为一个字符节点保存。

二:使用范围

既然学Trie树,肯定要知道这玩意是用来干嘛的?

1. 词频统计。

可能有人要说了,词频统计简单啊,一个hash或者一个堆就可以打完收工,但问题来了,如果内存有限呢?还能这么玩吗?这种限制级条件下就可以用trie树来压缩下空间,因为公共前缀都是用一个节点保存的。

2. 前缀匹配

就拿上面的图来说吧,如果我想获取所有以 "a" 开头的字符串,从图中可以很明显的看到是:and,as,at,如果不用trie树,你该怎么做呢?很显然朴素的做法时间复杂度为O(N2) ,用Trie树就不一样了,它可以做到h,h为你检索单词的长度,可以说这是秒杀的效果。

举个例子:现有一个编号为1的字符串”and“,怎样插入到trie树中呢?采用动态规划的思想,将编号”1“计入到每个途径的节点中,那么以后我们要找”a“,”an“,”and"为前缀的字符串的编号将会轻而易举。

三:实际操作

到现在为止,我想大家已经对trie树有了大概的掌握,下面看看如何来实现。

1:定义trie树节点

为了方便,我也采用纯英文字母,大家都知道字母有26个,所以构建的trie树就是一个26叉树,每个节点包含26个子节点,实现代码如下:

/// <summary>/// Trie树节点/// </summary>public class TrieNode{/// <summary>/// 26个字符,也就是26叉树/// </summary>public TrieNode[] childNodes;/// <summary>/// 词频统计/// </summary>public int freq;/// <summary>/// 记录该节点的字符/// </summary>public char nodeChar;/// <summary>/// 插入记录时的编码id/// </summary>public HashSet<int> hashSet = new HashSet<int>();/// <summary>/// 初始化/// </summary>public TrieNode(){childNodes = new TrieNode[26];freq = 0;}}

2: 添加操作

既然是26叉树,那么当前节点的后续子节点是放在当前节点的哪一叉中,也就是放在childNodes中哪一个位置,这里采用 int k = word[0] - 'a' 来计算位置。

/// <summary>/// 插入操作/// </summary>/// <param name="root"></param>/// <param name="s"></param>public void AddTrieNode(ref TrieNode root, string word, int id){if (word.Length == 0)return;//求字符地址,方便将该字符放入到26叉树中的哪一叉中int k = word[0] - 'a';//如果该叉树为空,则初始化if (root.childNodes[k] == null){root.childNodes[k] = new TrieNode();//记录下字符root.childNodes[k].nodeChar = word[0];}//该id途径的节点root.childNodes[k].hashSet.Add(id);var nextWord = word.Substring(1);//说明是最后一个字符,统计该词出现的次数if (nextWord.Length == 0)root.childNodes[k].freq++;AddTrieNode(ref root.childNodes[k], nextWord, id);}

3:删除操作

删除操作中,不仅要删除该节点的字符串编号,还要对词频减一操作。

/// <summary>/// 删除操作/// </summary>/// <param name="root"></param>/// <param name="newWord"></param>/// <param name="oldWord"></param>/// <param name="id"></param>public void DeleteTrieNode(ref TrieNode root, string word, int id){if (word.Length == 0)return;//求字符地址,方便将该字符放入到26叉树种的哪一颗树中int k = word[0] - 'a';//如果该叉树为空,则说明没有找到要删除的点if (root.childNodes[k] == null)return;var nextWord = word.Substring(1);//如果是最后一个单词,则减去词频if (word.Length == 0 && root.childNodes[k].freq > 0)root.childNodes[k].freq--;//删除途经节点root.childNodes[k].hashSet.Remove(id);DeleteTrieNode(ref root.childNodes[k], nextWord, id);}

4:测试

这里我从网上下载了一套的词汇表,共2279条词汇,现在要做的就是检索 “go” 开头的词汇,并统计go出现的频率。

public static void Main(){Trie trie = new Trie();var file = File.ReadAllLines(Environment.CurrentDirectory + "//1.txt");foreach (var item in file){var sp = item.Split(new char[] { ' ' }, StringSplitOptions.RemoveEmptyEntries);trie.AddTrieNode(sp.LastOrDefault().ToLower(), Convert.ToInt32(sp[0]));}Stopwatch watch = Stopwatch.StartNew();//检索go开头的字符串var hashSet = trie.SearchTrie("go");foreach (var item in hashSet){Console.WriteLine("当前字符串的编号ID为:{0}", item);}watch.Stop();Console.WriteLine("耗费时间:{0}", watch.ElapsedMilliseconds);Console.WriteLine("\n\ngo 出现的次数为:{0}\n\n", trie.WordCount("go"));}

下面我们拿着ID到txt中去找一找,嘿嘿,是不是很有意思。

测试文件:http://files.cnblogs.com/huangxincheng/1.zip

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/307329.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

leetcode46. 全排列

一:题目 二:上码 class Solution {/**1.全排列问题,我们的横向都是从0开始不再是st了 因为我们用到前面已经使用过的元素单是在纵向递归当中我们不能使用重复的元素&#xff0c;这里我们用used来去重。*/private List<List<Integer>> ans new ArrayList<>(…

leetcode47. 全排列 II

一:题目 二:上码 class Solution {private List<List<Integer>> ans new ArrayList<>();private List<Integer> path new ArrayList<>();private boolean[] used;public void getAns(int[] nums,boolean[] used) {if (path.size() nums.leng…

C# 很少人知道的科技

本文来告诉大家在C#很少有人会发现的科技。即使是工作了好多年的老司机也不一定会知道这些科技&#xff0c;如果觉得我是在骗你&#xff0c;那么请看看本文的内容。原本最初 C# 的设计是简单和高效开发的&#xff0c;在经过了这么多年众多公司和开发者的努力下&#xff0c;整个…

Typescript前端接口联调自动化的探究与实践

源宝导读&#xff1a;Web应用程序一般都是前后端分离的基本架构&#xff0c;而前后端很可能分别是两拨人分别开发&#xff0c;前后端的接口连调成为高频沟通的对象&#xff0c;开发内耗最大的也在这个环节。本文将分享如何基于OpenAPI将前后端接口协议标准化和自动化&#xff0…

leetcode51. N 皇后(java详解)

一:题目 二:上码 class Solution {/**思路:1.先说我们选用的数据结构;我们是选取的是List<List<string>> ans 来存每次的结果;我们在创建这个二维矩阵的时候用的是char的二维数组,那么的话等到我们得到一种可行解的时候 将char的二维数组每一行转换成 String 并存…

程序员如何跨越35岁危机?这篇给点干货建议!

职场&认知洞察 丨 作者 / findyi这是findyi公众号的第83篇原创文章这两天在我的读者群里做了一个职业小调研&#xff0c;发现关注我公众号的70%以上都是程序员。毕竟程序员吸引程序员&#xff0c;这也算猿粪吧&#xff0c;哈哈。这个小调研也引发大家对程序员行业的激烈探讨…

leetcode455. 分发饼干

一:题目 二:上码 class Solution {public int findContentChildren(int[] g, int[] s) {int ans 0;int gIndex 0;int sIndex 0;Arrays.sort(g);Arrays.sort(s);while (gIndex < g.length && sIndex < s.length) {if (s[sIndex] > g[gIndex]) gIndex; //只…

写了多年代码,你会 StackOverflow 吗

写了多年代码&#xff0c;你会 StackOverflow 吗Intro准备写一个傻逼代码的系列文章&#xff0c;怎么写 StackOverflow 的代码&#xff0c;怎么写死锁代码&#xff0c;怎么写一个把 CPU 跑满&#xff0c;怎么写一个 OutOfMemory 的代码。今天主要来看 StackOverflow&#xff0c…

C#实现迭代器

迭代器模式&#xff08;Iterator&#xff09;&#xff0c;提供一种方法顺序访问一个聚合对象中的各种元素&#xff0c;而又不暴露该对象的内部表示。C#中使用IEnumerator接口实现&#xff0c;Java中使用Iterator接口实现&#xff0c;其中原理都差不多&#xff0c;下面我就用C#代…

从CLR GC到CoreCLR GC看.NET Core为云而生

内存分配概要前段时间在园子里看到有人提到了GC学习的重要性&#xff0c;很赞同他的观点。充分了解GC可以帮助我们更好的认识.NET的设计以及为何在云原生开发中.NET Core会占有更大的优势&#xff0c;这也是一个程序员成长到更高层次所需要经历的过程。在认识GC的过程中&#x…

springboot邮件发送(牛客论坛项目之QQ邮箱发送)

一:邮箱发送原理 1:狂神图解 张三通过smtp协议连接到Smtp服务器&#xff0c;然后发送一封邮件给网易的邮件服务器网易分析发现需要去QQ的邮件服务器&#xff0c;通过Smtp协议将邮件转投给QQ的Smtp服务器QQ将接收到的邮件存储在456789qq.com这个邮件账号的空间中李四通过Pop3协…

如果淘宝双十一架构用. Net Core,如何“擒住”高并发、高可用、低延迟?

电商的秒杀和抢购&#xff0c;对我们来说&#xff0c;都不是一个陌生的东西。然而&#xff0c;从技术的角度来说&#xff0c;这对于Web系统是一个巨大的考验。当一个Web系统&#xff0c;在一秒钟内收到数以万计甚至更多请求时&#xff0c;系统的优化和稳定至关重要。缓存技术是…

.NET5在开发平台上远优于Java,如何发挥优势?

上周.NET5 RC2已发布&#xff0c;.NET5已经肉眼可见的即将到来&#xff0c;令人期待&#xff01;从.NET Framework到.NET Core再到.NET5&#xff0c;能看到诸多开发者和公司都在积极拥抱新技术。对比Java&#xff0c;国内主流开发都还停留在Java8&#xff0c;在云原生的互联网时…

leetcode122. 买卖股票的最佳时机 II

一:题目 二&#xff1a;上码 class Solution {/**思路:1.局部最优:我们买入当前股票等哪天遇见最大值的时候买出 赚最大利润2.全局最优:局部最优推出全局最优3.这个利润是可以被分解的 7 1 5 10利润: -6 4 5那么最大利润是459其实就是1买入10卖出,但是我们可以在5这天…

多重继承和菱形问题

翻译自 John Demetriou 2018年4月8日 的文章 《Multiple Inheritance And The Diamond Problem》[1]开篇之前&#xff0c;我假设每个人都知道在面向对象编程中继承是什么&#xff0c;以及它能提供什么好处。我不会深入探究对象继承的基础知识。这篇文章更关注于多重继承和它所面…

Jekins持续集成在ERP研发中的应用实践

源宝导读&#xff1a;“持续集成”是敏捷最佳实践中&#xff0c;保证高质量交付的关键环节之一。本文将介绍明源云ERP系统在研发过程中&#xff0c;应用Jekins平台完成持续集成自动构建的实践。一、认识持续集成持续集成是一种软件开发实践&#xff0c;即团队开发成员经常集成他…

leetcode45. 跳跃游戏 II(java详解)

一:题目 二:上码 class Solution {public int jump(int[] nums) {int ans 0;int curIndex 0;//当前统计出来的可以移动的最远距离的下标int nextIndex 0;//在到达 当前最远距离下标的这段距离内 我们统计出的可以达到的最远距离//如果在统计的过程中 其覆盖范围已经大于数组…

Ids4 认证保护 API 方案更新

壹时刻保持学习的喜悦可能你咋一看这个标题不知道什么意思&#xff0c;其实我也没想好怎么表达&#xff0c;因为是一个特别简单的小知识点。先说下为什么突然说到了Ids4&#xff1f;这几天大家都知道&#xff0c;我在视频《微服务之eShop讲解》&#xff0c;目前讲到了购物车微服…

dotNet Core 3.1 使用 Aspose (部署 Docker)

在之前的文章《dotNET Core中使用Aspose&#xff08;部署Docker&#xff09;》中介绍了在 dotNet Core2.1 中使用 Aspose &#xff0c;并部署到 Docker 中&#xff0c;现在 dotNET Core 升级到了 3.1 &#xff0c;Docker 镜像发生了变化&#xff0c;一些依赖的安装也有些变化。…

MySQL之一条Update的执行流程

文章目录1:执行的语句2:在更新操作中流程中特有的部分(1):redo log&#xff08;重做日志&#xff09;(2):binlog&#xff08;归档日志&#xff09;(3):Redo日志跟binlog日志的区别2:执行流程1:执行的语句 update T set c c 1 where ID 2;2:在更新操作中流程中特有的部分 (…