数据结构第28节 字典树

字典树(Trie,也称前缀树)是一种用于存储字符串的树形数据结构。它将字符串中的字符作为树的边,每个节点代表一个可能的前缀。字典树非常适合处理大量字符串的搜索、插入和删除操作,尤其是在查找具有相同前缀的字符串时非常高效。

基本概念:

  • 根节点:通常不包含任何数据,它的子节点包含字符串的第一个字符。
  • 内部节点:代表字符串的中间字符。
  • 叶子节点:代表字符串的结束字符,通常会附加一个标志表示该路径是一个完整的单词。

案例分析与实现:

假设我们要实现一个简单的字典树来存储英文单词,比如 “cat”, “cattle”, “dog”, “dodge” 和 “do”。

步骤1: 定义节点类
class TrieNode {private final int ALPHABET_SIZE = 26;TrieNode[] children = new TrieNode[ALPHABET_SIZE];boolean isEndOfWord;public TrieNode() {isEndOfWord = false;for (int i = 0; i < ALPHABET_SIZE; i++) {children[i] = null;}}
}
步骤2: 实现字典树类
public class Trie {private TrieNode root;public Trie() {root = new TrieNode();}// 插入单词到字典树中public void insert(String word) {TrieNode current = root;for (int i = 0; i < word.length(); i++) {int index = word.charAt(i) - 'a';if (current.children[index] == null) {current.children[index] = new TrieNode();}current = current.children[index];}current.isEndOfWord = true;}// 搜索单词是否存在于字典树中public boolean search(String word) {TrieNode current = root;for (int i = 0; i < word.length(); i++) {int index = word.charAt(i) - 'a';if (current.children[index] == null) {return false;}current = current.children[index];}return current != null && current.isEndOfWord;}
}
步骤3: 使用字典树
public class Main {public static void main(String[] args) {Trie trie = new Trie();trie.insert("cat");trie.insert("cattle");trie.insert("dog");trie.insert("dodge");trie.insert("do");System.out.println("Search cat: " + trie.search("cat")); // 应输出 trueSystem.out.println("Search dog: " + trie.search("dog")); // 应输出 trueSystem.out.println("Search do: " + trie.search("do"));   // 应输出 trueSystem.out.println("Search dodge: " + trie.search("dodge")); // 应输出 trueSystem.out.println("Search dodger: " + trie.search("dodger")); // 应输出 false}
}

这个简单的字典树实现了插入和搜索功能。在实际应用中,你可能还需要实现删除、获取所有前缀单词等功能。字典树在拼写检查、自动完成、IP路由选择等领域有着广泛的应用。

当然,我们可以继续扩展字典树的功能,添加删除操作以及遍历字典树以列出所有单词或特定前缀下的单词。以下是完善后的代码:

步骤4: 添加删除功能

为了正确地删除一个单词,我们需要从字典树中移除该单词的路径,但要注意不要移除其他单词的公共前缀。这需要我们递归地检查并删除空闲的节点。

// 删除单词
public boolean delete(String key) {return delete(root, key, 0);
}private boolean delete(TrieNode node, String key, int depth) {if (node == null) return false;if (depth == key.length()) {if (!node.isEndOfWord) return false;node.isEndOfWord = false;// 如果当前节点没有孩子,则可以删除if (isEmpty(node)) {node = null;return true;}return false;}int index = key.charAt(depth) - 'a';boolean shouldDeleteCurrentNode = delete(node.children[index], key, depth + 1);if (shouldDeleteCurrentNode) {node.children[index] = null;// 如果当前节点没有任何孩子并且不是单词的结尾,那么删除它if (isEmpty(node) && !node.isEndOfWord) {return true;}}return false;
}private boolean isEmpty(TrieNode node) {for (int i = 0; i < node.children.length; i++) {if (node.children[i] != null) return false;}return true;
}

步骤5: 遍历字典树

遍历字典树可以让我们打印出所有的单词,或者找到具有特定前缀的所有单词。

// 打印具有给定前缀的所有单词
public void printWordsWithPrefix(String prefix) {TrieNode node = get(prefix);if (node == null) {System.out.println("No words found with the given prefix.");return;}printWords(node, prefix);
}private void printWords(TrieNode node, String currentString) {if (node.isEndOfWord) {System.out.println(currentString);}for (int i = 0; i < node.children.length; i++) {if (node.children[i] != null) {printWords(node.children[i], currentString + (char)(i + 'a'));}}
}// 查找具有给定前缀的节点
private TrieNode get(String key) {TrieNode current = root;for (int i = 0; i < key.length(); i++) {int index = key.charAt(i) - 'a';if (current.children[index] == null) {return null;}current = current.children[index];}return current;
}

现在,我们可以在主函数中使用这些方法:

public static void main(String[] args) {Trie trie = new Trie();trie.insert("cat");trie.insert("cattle");trie.insert("dog");trie.insert("dodge");trie.insert("do");System.out.println("Search cat: " + trie.search("cat"));System.out.println("Search dog: " + trie.search("dog"));System.out.println("Search do: " + trie.search("do"));System.out.println("Search dodge: " + trie.search("dodge"));System.out.println("Search dodger: " + trie.search("dodger"));trie.printWordsWithPrefix("d");trie.delete("cat");System.out.println("After deleting 'cat': Search cat: " + trie.search("cat"));
}

这样,我们就有了一个更完整的字典树实现,包括插入、搜索、删除和遍历功能。

在学生成绩管理系统中使用字典树(Trie)来实现增删改查(CRUD)操作是一种创新的方法,尤其是当需要对大量以文本形式存储的数据(如学生姓名)进行快速前缀搜索时。但是,传统的字典树并不直接支持数据的删除或修改操作,因为这些操作可能会影响树的结构。为了简化说明,我们将使用字典树来存储学生姓名,并使用一个额外的数据结构(如HashMap)来关联姓名与成绩,以便于更新和删除操作。

下面是一个使用Java实现的简化版学生成绩管理系统,其中使用字典树来存储学生姓名,并使用HashMap来存储姓名和成绩之间的对应关系。

首先,我们定义字典树的节点类TrieNode

class TrieNode {private final int ALPHABET_SIZE = 26;TrieNode[] children = new TrieNode[ALPHABET_SIZE];boolean isEndOfWord;public TrieNode() {isEndOfWord = false;for (int i = 0; i < ALPHABET_SIZE; i++) {children[i] = null;}}
}

然后,定义字典树类Trie

public class Trie {private TrieNode root;public Trie() {root = new TrieNode();}public void insert(String key) {TrieNode current = root;for (int i = 0; i < key.length(); i++) {int index = key.charAt(i) - 'a';if (current.children[index] == null) {current.children[index] = new TrieNode();}current = current.children[index];}current.isEndOfWord = true;}public boolean search(String key) {TrieNode current = root;for (int i = 0; i < key.length(); i++) {int index = key.charAt(i) - 'a';if (current.children[index] == null) {return false;}current = current.children[index];}return current != null && current.isEndOfWord;}
}

接下来,创建StudentScoreManager类,用于管理学生姓名和成绩:

import java.util.HashMap;public class StudentScoreManager {private Trie studentNames;private HashMap<String, Integer> scores;public StudentScoreManager() {studentNames = new Trie();scores = new HashMap<>();}public void addScore(String name, int score) {studentNames.insert(name);scores.put(name, score);}public void updateScore(String name, int score) {if (scores.containsKey(name)) {scores.put(name, score);} else {throw new IllegalArgumentException("Student not found.");}}public void deleteScore(String name) {if (scores.containsKey(name)) {scores.remove(name);// 删除字典树中的条目(这里简化处理,不实际删除)} else {throw new IllegalArgumentException("Student not found.");}}public int getScore(String name) {if (scores.containsKey(name)) {return scores.get(name);} else {throw new IllegalArgumentException("Student not found.");}}public boolean checkName(String name) {return studentNames.search(name);}
}

最后,在main方法中使用StudentScoreManager

public static void main(String[] args) {StudentScoreManager manager = new StudentScoreManager();manager.addScore("Alice", 90);manager.addScore("Bob", 85);manager.addScore("Charlie", 92);System.out.println(manager.checkName("Alice")); // 输出 trueSystem.out.println(manager.getScore("Bob")); // 输出 85manager.updateScore("Charlie", 95);System.out.println(manager.getScore("Charlie")); // 输出 95manager.deleteScore("Bob");System.out.println(manager.checkName("Bob")); // 输出 false
}

在这个示例中,addScore方法同时在字典树和HashMap中添加学生姓名和成绩;updateScoredeleteScore仅更新或删除HashMap中的成绩,而字典树保持不变(为了简单起见,没有实现在字典树中删除节点的功能)。getScorecheckName分别用于获取成绩和检查姓名是否存在。

需要注意的是,这种方法在删除操作上可能会导致字典树中存在“孤儿”节点,如果需要维护一个完全一致的字典树,那么删除操作需要更复杂的逻辑来重构树的结构。

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

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

相关文章

[GXYCTF2019]BabySQli

原题目描述&#xff1a;刚学完sqli&#xff0c;我才知道万能口令这么危险&#xff0c;还好我进行了防护&#xff0c;还用md5哈希了密码&#xff01; 我看到是个黑盒先想着搞一份源码 我dirsearch明明扫到了.git&#xff0c;算了直接注入试试看 随便输入了两个东西&#xff0c…

Type-C PD芯片:引领充电技术的新纪元

随着科技的飞速发展&#xff0c;人们对电子设备的依赖日益加深&#xff0c;对充电速度、效率和安全性的要求也越来越高。在这样的背景下&#xff0c;Type-C PD&#xff08;Power Delivery&#xff09;芯片应运而生&#xff0c;以其高效、安全、智能的特点&#xff0c;成为了充电…

vscode编译环境配置-golang

1. 支持跳转 如果单测函数上方不显示run test | debug test&#xff0c;需要安装Code Debugger&#xff08;因为以前的go Test Explorer不再被维护了&#xff09; 2. 单测 指定单个用例测试 go test -v run TestXXXdlv 调试 需要安装匹配的go版本和delve版本&#xff08;如…

Qt易错总结

一、编译相关 建议用qmake !!!,cmake坑点太多&#xff01;&#xff01;&#xff01; 1.自定义控件识别不了 cmakelist加上 include_directories(${PROJECT_SOURCE_DIR}/你自定义控件的相对路径) 2.添加模块&#xff08;以QCharts为例&#xff09; find_package(QT NAMES Q…

Linux多线程编程-生产者与消费者模型详解与实现(C语言)

1.什么是生成者与消费者模型 生产者-消费者模型是并发编程中的经典问题&#xff0c;描述了多个线程&#xff08;或进程&#xff09;如何安全、有效地共享有限的缓冲区资源。在这个模型中&#xff0c;有两种角色&#xff1a; 生产者&#xff08;Producer&#xff09;&#xff1…

<数据集>光伏板缺陷检测数据集<目标检测>

数据集格式&#xff1a;VOCYOLO格式 图片数量&#xff1a;2400张 标注数量(xml文件个数)&#xff1a;2400 标注数量(txt文件个数)&#xff1a;2400 标注类别数&#xff1a;4 标注类别名称&#xff1a;[Crack,Grid,Spot] 序号类别名称图片数框数1Crack8688922Grid8248843S…

研究生发表期刊/会议必看,一文看懂A/B/C类和顶刊

主要看&#xff1a;中国计算机学会&#xff08;CCF&#xff09;推荐国际学术期刊 里面的划分等级 等级为&#xff1a;A类&#xff08;最难&#xff09;>B类&#xff08;中等难度&#xff09;>C类&#xff08;难度一般&#xff09; 本人这边计划&#xff1a;最低发C刊&a…

css-grid布局(栅格布局)

css新世界-auto-fit grid 一个比flex更强大的布局,适合做整体布局 grid-template-columns: repeat(auto-fill, minmax(100px, 1fr)); auto-fit的话有strech效果gap 不仅可以用于grid 也可用flex. 在grid-template-areas表示这个位置空着grid area 的 [a b]命名可重复命名 表示的…

大模型分布式训练并行技术

随着深度学习的发展&#xff0c;模型规模逐渐增大&#xff0c;数据量和计算需求也呈爆炸式增长。在单个计算设备上完成大模型的训练变得不切实际&#xff0c;因此&#xff0c;分布式训练成为了解决这一问题的关键。在分布式训练中&#xff0c;数据并行是一种非常有效的策略&…

【JavaScript脚本宇宙】JavaScript图表库大比拼:从实时数据到时间表,一网打尽

数据可视化利器&#xff1a;探索六款流行JavaScript图表库 前言 在Web开发中&#xff0c;数据可视化是一个非常重要的领域。随着JavaScript图表库的不断涌现&#xff0c;开发人员可以更轻松地创建各种交互式和实时的图表。本文将介绍几种流行的JavaScript图表库&#xff0c;包…

双向收发的信号应该在哪进行串联端接?分享几个实用设计方法!

高速先生成员--黄刚 经过上次高速先生的描述&#xff0c;相信大家已经掌握了串联端接的秘诀了&#xff0c;简单来说&#xff0c;那就是第一步&#xff1a;先看看芯片的驱动内阻&#xff0c;第二步&#xff1a;再用加起来50欧姆匹配的方法来选择适合的串阻值&#xff0c;第三步&…

【C语言报错已解决】格式化字符串漏洞(Format String Vulnerability)

&#x1f3ac; 鸽芷咕&#xff1a;个人主页 &#x1f525; 个人专栏: 《C干货基地》《粉丝福利》 ⛺️生活的理想&#xff0c;就是为了理想的生活! 文章目录 引言&#xff1a;一、问题描述&#xff1a;1.1 报错示例&#xff1a;1.2 报错分析&#xff1a;1.3 解决思路&#xff…

HEROIC FANTASY WERE CREATURES PACK VOL 2 (幻想生物)

这个包收集了5种英雄幻想生物:狼人,狼人,狼人山羊,狼人鲨鱼和狼人蜘蛛。 狼人:27.5 Ktris Max//101个骨骼//4种材质//最多4096*4096个纹理//40个动画(11个是根运动变体) 狼人:15.83 Ktris//66个骨骼//3种材质//最多4096*4096个纹理//35个动画(9个是根运动变体) wereg…

Centos 使用nfs配置共享目录使docker集群所有容器日志统一主机访问

Centos 使用nfs配置共享目录&#xff0c;使docker集群所有容器日志统一存放在主机一个共享目录下&#xff0c;供开发人员访问查看 准备两台或以上Centos服务器 192.168.0.1 nfs服务器 192.168.0.2 nfs客户端 以root用户登录192.168.0.1服务器&#xff0c;执行以下操作 注意先…

excel根据数据批量创建并重命名工作表

需求 根据一列数据&#xff0c;批量创建并重命名工作表 做法 1. 右键该sheet&#xff0c;选择查看代码 2. 输入VBA代码 正向创建 Sub create_sheets_by_col()Dim num% 定义为integer*num Application.WorksheetFunction.CountA(Sheet1.Range("A:A")) num是非空…

木舟0基础学习Java的第十八天(IO流,字节流,字符流,缓冲)

IO流正常使用流程&#xff1a;1.抛异常 2.资源读写 3.关闭资源(从后往前关) 字节流&#xff1a;(拷贝推荐使用) 开发中一般不会抛出异常 用try{}catch(){} 也不推荐字节流读中文 FileInputStream:读 FileInputStream fsnew FileInputStream("e:/b.txt");//11111…

设计模式使用场景实现示例及优缺点(行为型模式——策略模式)

策略模式&#xff08;Strategy Pattern&#xff09; 策略模式&#xff08;Strategy Pattern&#xff09;是一种行为设计模式&#xff0c;它能够在运行时选择最适合的算法或行为&#xff0c;同时能够将算法族封装成独立的类&#xff0c;并使它们之间可以相互替换。这种模式是通…

类形断言和和类型推导的区别是什么?

类型断言&#xff08;Type Assertion&#xff09;和类型推导&#xff08;Type Inference&#xff09;在TypeScript中的区别 如下&#xff1a; 定义&#xff1a; 类型断言&#xff1a;是程序员明确指定一个值的类型&#xff0c;即允许变量从一种类型更改为另一种类型。它不会进行…

接着探索Linux的世界 -- 基本指令(文件查看、时间相关、打包压缩等等)

话不多说&#xff0c;直接进入主题 一、cat指令 -- 查看目标文件的内容 语法&#xff1a;cat [选项][文件] 功能&#xff1a; 查看目标文件的内容 -b 对非空输出行编号 -n 对输出的所有行编号 -s 不输出多行空行 1、查看目标文件的内容 2、 -b 对非空输出行编号 3、-n 对…

24/07/11数据结构(6.1215)双链表实现-栈实现

像写单链表的一些功能接口一样,先来写一些双链表的接口熟悉一下它的主体框架: #include<stdio.h> #include<stdlib.h> typedef int LDataType; //双向带头循环链表的节点 typedef struct ListNode{ LDataType _data; //指向下一个节点的起始位置 str…