前缀树

前缀树,又称作字典树,用一个树状的数据结构储存字典中的所有单词。

列,一个包含can、cat、come、do、i、in、inn的前缀树如下图所示:

  • 前缀树是一个多叉树,一个节点可能存在多个节点。
  • 除根节点外,每个节点表示一个字符串的一个字符,整个字符串由前缀树的路径进行表示。
  • 字符串在前缀树的路径并不一定全部都是终止于叶节点,比如in对应的则是字符串inn的一部分。

前缀树的实现

接下来设计一颗前缀树PrefixTree,其包含如下的操作。

  1. insert,在前缀树中添加一个字符串。
  2. search,查找,如果该前缀树中存在该字符串,则返回true
  3. startWith,查找字符串前缀,如果前缀树中存在包含该字符串的前缀,则返回true,否则返回false
  4. matchingStartWith,查找所有包含该前缀的字符串,返回前缀树中所有包含该前缀的字符串,如果不存在则返回null

首先定义前缀树的数据结构,注意这里我们只考虑实现小写英文字母(具体可以根据业务逻辑调整),因为小写英文字母只有26个,所以我们可以将其放到一个容量为26的数组之中去。数组中第一个元素则是对应a的节点,第二个元素则是对应b的节点,依次往下。需要说明的是,不需要专门一个字段表示当前节点的字符,因为可以根据其在父节点对应的位置,从而得知对应的字符信息。

另外我们需要一个boolean类型的字段,去判断该节点的路径对应的字符串是否是一个完整的字符。

综上,数据结构可以定义为如下所示:

kotlin

class PrefixTree {private val root = Node()class Node {val childNode = Array<Node?>(26) { null }var isWord = false}
}

java

    class PrefixTree {private Node root =new Node();class Node {Node[] childNode = new Node[26];boolean isWord = false;}}

insert函数实现

我们添加boybosshiboyhi四个单词,具体分析一下前缀树添加的过程:

  1. 添加boy,逐个添加字母boy对应的节点,并将最后一个字母y对应的节点的isWord标记为true
  2. 添加boss,此时前两个字母bo对应的节点之前已经创建出来,因此只需要创建两个对应s的节点,并将第2个s对应节点的字段isWord标记为true
  3. 添加hiboy,虽然之前有添加过boy三个单词,但是其并不是hiboy的前缀,所以需要为hiboy每个单词创建节点,并将最后的节点置为true
  4. 添加hi,由于hi为前缀,所以不需要创建节点添加,只需要将i对应的节点的isWord置为ture即可

所以,插入的代码可以做如下的实现

kotlin

    fun insert(word: String) {var localRoot = rootword.toCharArray().forEach { char ->val index = char - 'a'if (localRoot.childNode[index] == null) {localRoot.childNode[index] = Node()}localRoot = localRoot.childNode[index]!!}localRoot.isWord = true}

java

        public void insert(String world){Node localRoot = root;for (char c :world.toCharArray()){int index = c - 'a';if (localRoot.childNode[index]==null){localRoot.childNode[index] = new Node();}localRoot = localRoot.childNode[index]}localRoot.isWord = true;}

search函数实现

这个思考起来比较简单一点,直接从根节点开始查找,如果要查找的字符串对应的字符在根节点中不存在,则直接返回false,如果存在则前往该节点,在该节点查找是否存在与字符串对应的第二个字符的节点,没有返回false,如果存在则继续往下查找,直到查找完最后一个字符,最终判断如果isWordtrue则返回true,否则则返回false

所以代码如下所示:

kotlin

    fun search(word: String): Boolean {var localRoot = rootword.toCharArray().forEach { char: Char ->val index = char - 'a'if (localRoot.childNode[index] != null) {localRoot = localRoot.childNode[index]!!} else {return false}}return localRoot.isWord}

java

        public boolean search(String world){Node localRoot = root;for (char c :world.toCharArray()){int index = c - 'a';if (localRoot.childNode[index]!=null){localRoot = localRoot.childNode[index];}else {return false;}}return localRoot.isWord;}

startWith函数实现

函数startWithsearch不同,只要前缀树中存在以输入的前缀开头的单词就应该返回true。因此,函数startWith的返回值和函数search不同之外,其他代码是一样的。
kotlin

    fun startWith(word: String): Boolean {var localRoot = rootword.toCharArray().forEach { char: Char ->val index = char - 'a'if (localRoot.childNode[index] != null) {localRoot = localRoot.childNode[index]!!} else {return false}}return true}

java

        public boolean startWith(String world){Node localRoot = root;for (char c :world.toCharArray()){int index = c - 'a';if (localRoot.childNode[index]!=null){localRoot = localRoot.childNode[index];}else {return false;}}return true;}

matchingStartWith函数实现

该函数要求返回所有匹配到前缀的单词。

所以分为以下两个步骤:

  1. 匹配前缀,如果匹配不到直接返回null
  2. 匹配到之后,在该前缀的节点进行深度优先搜索,将遍历到的所有单词全部加入集合进行返回

所以代码如下所示:
kotlin

    fun matchingStartWith(word: String): List<String>? {val wordResultPre = StringBuilder()var localRoot = rootword.toCharArray().forEach { char: Char ->val index = char - 'a'if (localRoot.childNode[index] != null) {wordResultPre.append(char)localRoot = localRoot.childNode[index]!!} else {return null}}//深度优先搜索 所有的剩余单词val result = ArrayList<String>()dfs(localRoot, StringBuilder(),wordResultPre,result)return result}private fun dfs(node: Node, str: StringBuilder, wordResultPre: StringBuilder, result: ArrayList<String>) {val childNodes = node.childNodefor (index in childNodes.indices) {val childNode = childNodes[index]if (childNode != null) {str.append(('a' + index).toChar())if (childNode.isWord) {result.add(wordResultPre.toString() + str.toString())}dfs(childNode,str, wordResultPre, result)}}}

java

        public List<String> matchingStartWith(String world){StringBuilder wordResultPre = new StringBuilder();Node localRoot = root;for (char c :world.toCharArray()){int index = c - 'a';if (localRoot.childNode[index]!=null){wordResultPre.append(c);localRoot = localRoot.childNode[index];}else {return null;}}//深度优先搜索 所有的剩余单词List<String> result =new ArrayList<>();dfs(localRoot,new StringBuilder(),wordResultPre,result);return result;}private void dfs(Node node, StringBuilder stringBuilder, StringBuilder wordResultPre, List<String> result) {Node[] childNodes = node.childNode;for (int i =0 ;i<childNodes.length;i++){Node childNode = childNodes[i];if (childNode!=null){stringBuilder.append('a'+i);if (childNode.isWord){result.add(wordResultPre.toString()+stringBuilder.toString());}dfs(childNode,stringBuilder,wordResultPre,result);}}}

前缀树应用

题目

输入一个包含n个单词的数组,可以把它们编码成一个字符串和n个下标。例如,单词数组["time","me","bell"]可以编码成一个字符串"time#bell#",然后这些单词就可以通过下标[0,2,5]得到。对于每个下标,都可以从编码得到的字符串中相应的位置开始扫描,直到遇到’#'字符前所经过的子字符串为单词数组中的一个单词。所以如果输入的是字符串数组["time","me","bell"],那么编码之后最短的字符串是"time#bell#",长度是10

分析

题目的目标是得到最短的编码,因此,如果一个单词是另一个单词的后缀,那么单词在编码字符串中就不需要单独出现,这是因为单词可以通过在单词中偏移下标得到。

所以我们很容易想到前缀树,但是题目是关于字符串的后缀。所以,我们可以反转字符串,之后使用前缀树。

根据题目信息,我们不需要关心每个单词,只需要遍历每个叶子节点对应的字符串的长度,然后加在一起即可。但是注意,字符串和字符串之间存在#,所以需要对长度进行额外+1。

总体来说使用深度优先搜索,代码参考如下:

    public void minLength(String [] words){PrefixTree.Node node = build(words);int [] total  = {0};dfs(node,1,total);}private void dfs(PrefixTree.Node node, int length, int[] total) {boolean isLeaf = true;for (PrefixTree.Node childNode : node.getChildNode()) {if (childNode != null) {isLeaf = false;dfs(childNode, length + 1, total);}}if (isLeaf) {total[0] += length;}}

前缀树介绍,定义,图文详解分析——Java/Kotlin双版本代码相关推荐

  1. 二叉树广度优先搜索、深度优先搜索(前序、中序、后序)遍历,动图详解-Java/Kotlin双版本代码

    自古逢秋悲寂寥,我言秋日胜春朝 二叉树结构说明 本博客使用树节点结构,如下所示: Kotlin 版本 class TreeNode(var value: String, var leftNode: T ...

  2. java方法怎么写_java方法定义格式详解,java方法怎么写?

    对于java方法你了解多少呢?你知道java方法应该如何写吗?下面要给大家介绍的就是和java方法相关的内容,一起来了解一下这个概念吧. 在学习运算符的时候,都为每个运算符单独的创建一个新的类和mai ...

  3. 三星三防s8计算机功能在哪里,三星Galaxy S8防水性能怎么样 三星S8防水介绍【图文详解】...

    现在很多手机都配备了防水功能,那么最新发布的 三星S8防水吗? 三星Galaxy S8防水性能怎么样? 相信很多人都不了解,这里给大家介绍下. 三星S8防水吗? 三星Galaxy S8是一款具备IP6 ...

  4. 领域驱动设计(DDD)架构演进和DDD的几种典型架构介绍(图文详解)

    我们生活中都听说了DDD,也了解了DDD,那么怎么将一个新项目从头开始按照DDD的过程进行划分与架构设计呢? 一.专业术语 各种服务 IAAS:基础设施服务,Infrastructure-as-a-s ...

  5. bios设置 联想m8000t_Ideapad 700-17笔记本使用bios设置u盘启动方法介绍【图文详解】...

    联想Ideapad 700-17笔记本是一款2016年上市的游戏型笔记比电脑,这款电脑搭载了intel酷睿第六代压标处理器以及gtx950m 4g高性能独立显卡,能够让游戏用户们顺畅运行大型游戏,那么 ...

  6. 三星台式计算机报价,三星台式机价格与测评介绍【图文详解】

    说到韩国的三星品牌相信消费者一定十分熟悉,从智能手机到 家电 应用,三星的产品涵盖了许多方面.今天要介绍的一款三星的产品则是很少有人知道的三星台式电脑,说是台式电脑其实还是不够严谨的,具体的说应该是一 ...

  7. 台式计算机机箱插线图示,联想机箱接线介绍【图文详解】

    [导读]计算机已经是我们再熟悉不过的工具了,几乎我们每天都在用.现在更有一些朋友想要diy计算机.对于diy计算机我们会遇到很多的难题,这些也需要我们掌握一定的知识.因为如果我们不按照规定去连接线的话 ...

  8. 如何使用光盘启动计算机,如何从光驱启动?从光驱启动方法介绍【图文详解】...

    如何从光驱启动电脑?什么是光驱?光驱是一种用于读取光碟的电脑设备,一般的电脑都需要安置光驱的,没有光驱的电脑是不能正常的读取光碟和磁盘的.那么我们要如何才能从光驱来启动电脑呢?光驱是可以设置的,设置之 ...

  9. 台式电脑怎么添加计算机硬盘,台式机怎么加硬盘 台式机加硬盘教程介绍【图文详解】...

    如今常规的电脑分为台式电脑和笔记本电脑,台式电脑相对来说比较笨重.不易携带一些,而笔记本电脑就比较轻盈.方便携带一些.台式电脑比较适合在家庭中使用,笔记本电脑一般来说都是商务人士或者学生党用的比较多. ...

最新文章

  1. 十年AI谁“最能打”?AI 2000榜单:何恺明最佳论文“刷”状元,谷歌机构排名第一...
  2. MultiBaC包消除不同组学数据之间的批次效应
  3. Fedora 从 15.0 开始将修改以太网卡命名规则
  4. oracle中常见ck和fk是什么,Oracle常用知识总结
  5. c语言实现bf算法的定位函数,数据结构c语言版严蔚敏清华大学出版社第四章串.ppt...
  6. 并非最边界的情况“OK“就真的“OK“(记洛谷P1720WA的经历,Java语言描述)
  7. 零基础搭建Hadoop大数据处理环境
  8. Maven的配置和使用(三)
  9. python实验室公众号_区块链研究实验室 | 使用Python编写Tendermint应用程序
  10. html——点击a标签打开新的标签页
  11. 用TensorFlow Lite 写个手写体识别 APP
  12. SAM-BA连接不上
  13. 2018年全国邀请赛(江苏) 比赛总结
  14. 串口,使用交叉还是直连串口线
  15. ROS运行时出现Couldn‘t find executable错误
  16. Github Actions Self-Hosted 本地运行Actions
  17. 涂鸦Zigbee SDK开发系列教程——2.环境搭建
  18. 【机器学习笔记14】softmax多分类模型【下篇】从零开始自己实现softmax多分类器(含具体代码与示例数据集)
  19. 求根号x(LeetCode--分治篇)
  20. 谷歌突然宣布:上帝的密码防线逐渐崩溃!人工智能有可能是人类文明史的终结!...

热门文章

  1. 艾宾浩斯背词法(网络收集)
  2. Pygame学习笔记9:计时、声音和Oil Spill游戏
  3. 新科高清GPS地图4.04版测评
  4. spark 内存管理
  5. 全国各省市县 人口密度 数据 下载 空间数据 高精度 空间分布数据 多年 人口热力图 地理信息 GIS...
  6. 想转行做IC,却找不到适合自己的岗位?
  7. java二进制字符串转整数
  8. xshell连接远程电脑上的虚拟机上的linux
  9. 中学计算机培训心得,多媒体教学培训心得体会
  10. 2019年下半年电子商务设计师上午真题(手抄版)