前缀树-Trie树

news2024/11/26 13:41:22

前缀树—Trie树,也叫作“单词查找树”、“字典树”

它属于多叉树结构,典型应用是用于统计,排序和保存大量的字符串(但不仅限于字符串),所以经常被搜索引擎系统用于文本词频统计。它的优点是:利用字符串的公共前缀来减少查询时间,最大限度地减少无谓的字符串比较,查询效率比哈希树高

前缀树是一个由“路径”和“节点”组成多叉树结构。由根节点出发,按照存储字符串的每个字符,创建对应字符路径

存储结果如下
在这里插入图片描述

3个基本性质:
1.根节点不包含字符,除根节点外每一个节点都只包含一个字符(词组);
2.从根节点到某一节点,路径上经过的字符(词组)连接起来,为该节点对应的字符串;
3. 每个节点的所有子节点包含的字符都不相同。

基本操作有:查找、插入和删除, 删除操作不会删除节点

实现逻辑如下
前缀树节点定义

    /// <summary>
    /// 前缀树节点
    /// </summary>
    public class TrieNode
    {
        // 节点存的值
        public string value = string.Empty;

        // 经过该节点的次数
        public int passCount = 0;

        // 以此节点为终点的数量
        public int endCount = 0;

        // 子节点
        public Dictionary<string, TrieNode> childMap = new Dictionary<string, TrieNode>();
    }

前缀树实现

   public class TrieTree
    {
        private TrieNode rootNode = null;

        // 下面代码中处理的字符串是以下划线分割的字符串,如 A_B_C_D

        public TrieTree()
        {
            rootNode = new TrieNode();
        }

        /// <summary>
        /// 添加数据
        /// </summary>
        /// <param name="msg"></param>
        public void Insert(string msg)
        {
            string[] arr = msg.Split('_');
            int index = 0;
            TrieNode node = rootNode;
            while (index < arr.Length)
            {
                string key = arr[index];
                TrieNode childNode = null;

                // 子节点中不包含 key 则创建一个节点添加
                if ( !node.childMap.TryGetValue(key, out childNode))
                {
                    childNode = new TrieNode();
                    childNode.value = key;
                    childNode.passCount = 0;
                    childNode.endCount = 0;
                    node.childMap[key] = childNode;
                }

                // 经过该节点的次数 +1
                childNode.passCount++;
                if (index >= arr.Length - 1)
                {
                    // 如果是结尾,则结尾数+1
                    childNode.endCount++;
                }
                
                // 令 node 等于 子节点
                node = childNode;
                ++index;
            }
        }

        /// <summary>
        /// 搜索
        /// </summary>
        /// <param name="msg"></param>
        /// <returns></returns>
        public TrieNode Search(string msg)
        {
            if (string.IsNullOrEmpty(msg))
            {
                return rootNode;
            }

            string[] arr = msg.Split('_');
            int index = 0;
            TrieNode node = rootNode;
            // 深度优先遍历
            while (index < arr.Length)
            {
                string key = arr[index];
                TrieNode childNode = null;
                // 子节点中以 key 查找
                if (!node.childMap.TryGetValue(key, out childNode))
                {
                    break;
                }

                // 令 node 等于子节点
                node = childNode;
                ++index;
            }

            return (index == arr.Length) ? node : null;
        }

        /// <summary>
        /// 删除 msg
        /// 前缀树不会删除节点,只是修改节点记录的 passCount、endCount
        /// </summary>
        /// <param name="msg"></param>
        public void Remove(string msg)
        {
            string[] arr = msg.Split('_');
            int index = 0;
            TrieNode node = rootNode;
            while (index < arr.Length)
            {
                string key = arr[index];
                // 子节点中以 key 查找
                if (!node.childMap.TryGetValue(key, out node))
                {
                    break;
                }

                // 经过该节点的次数 -1
                node.passCount--;
                if (index == arr.Length - 1)
                {
                    // 如果是结尾,则结尾数 -1
                    node.endCount--;
                }
                ++index;
            }
        }

        /// <summary>
        /// 计算以 msg 为前缀的数量
        /// </summary>
        /// <param name="msg"></param>
        /// <returns></returns>
        public int PrefixCount(string msg)
        {
            TrieNode node = Search(msg);
            if (null == node)
            {
                return 0;
            }
            return node.passCount;
        }

        /// <summary>
        /// 计算存储的 msg 个数
        /// </summary>
        /// <param name="msg"></param>
        /// <returns></returns>
        public int EndCount(string msg)
        {
            TrieNode node = Search(msg);
            if (null == node)
            {
                return 0;
            }
            return node.endCount;
        }

        /// <summary>
        /// 打印所有前缀为 msg 的信息
        /// </summary>
        /// <param name="msg"></param>
        public void PrefixTraverse(string msg)
        {
            // 先查找以 msg 为前缀的节点
            TrieNode node = Search(msg);
            if (null == node)
            {
                return;
            }

            List<string> list = new List<string>();
            list.Add(msg);
            // 遍历 所有子节点
            foreach(var childNode in node.childMap.Values)
            {
                BackTracing(childNode, list);
            }
        }

        /// <summary>
        /// 回溯的查找所有子节点
        /// </summary>
        /// <param name="node"></param>
        /// <param name="list"></param>
        private void BackTracing(TrieNode node, List<string> list)
        {
            // 将节点的值添加到 list
            list.Add(node.value);

            // 如果节点是结尾则,将整个字符串打印出来
            if (node.endCount > 0)
            {
                string msg = string.Empty;
                foreach(var value in list)
                {
                    msg += value;
                }
                Console.WriteLine(msg);
            }

            // 遍历所有子节点
            foreach(var childNode in node.childMap.Values)
            {
                // 递归调用回溯算法
                BackTracing(childNode, list);
            }
            // 将节点的值从 list 中删除 (此为回溯)
            list.RemoveAt(list.Count - 1);
        }
    }

测试代码如下

    public class TrieTreeTest
    {
        private static TrieTree tree = new TrieTree();

        private static List<string> list = new List<string>() {
            "A_B",
            "A_B_C_D",
            "A_B_C_D",
            "A_B_C_D",
            "A_B_C_F",
            "A_B_E",
            "A_B_E_D",
            "B_C",
            "B_C_D",
            "B_C_E"
        };

        public static void Test()
        {
            foreach (var msg in list)
            {
                tree.Insert(msg);
            }

            TrieNode node = tree.Search("A");

            foreach (var msg in list)
            {
                int preCount = tree.PrefixCount(msg);
                int endCount = tree.EndCount(msg);

                Console.WriteLine(msg + "  pre:" + preCount + "   end:" + endCount);
            }

            Console.WriteLine("=======================\n");
            tree.PrefixTraverse("");
            Console.WriteLine("=======================\n");

            tree.Remove("A_B_C_D");
            tree.Remove("A_B_C_D");
            tree.Remove("B_C_D");

            foreach (var msg in list)
            {
                int preCount = tree.PrefixCount(msg);
                int endCount = tree.EndCount(msg);
                Console.WriteLine(msg + "  pre:" + preCount + "   end:" + endCount);
            }

            Console.WriteLine("=======================\n");
            tree.PrefixTraverse("");
            Console.WriteLine("=======================\n");

        }

前缀树是一种非常有用的字符串存储结构,它解决了像 HashMap 这种存储结构无法实现的问题——前缀统计,并且由于是复用节点,也很好的节约了存储空间

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

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

相关文章

【前段基础入门之】=>玩转【CSS】开篇章!

目录 CSS 的简介&#xff1a;CSS的编写位置行内样式内部样式外部样式 样式表的优先级CSS语法规范&#xff1a; 总结&#xff1a; CSS 的简介&#xff1a; 层叠样式表&#xff08;Cascading Style Sheets&#xff0c;缩写为 CSS&#xff09;是一种样式表语言&#xff0c;用来描述…

前端项目练习(练习-007-typescript-02)

学习前&#xff0c;首先&#xff0c;创建一个web-007项目&#xff0c;内容和web-006一样。&#xff08;注意将package.json中的name改为web-007&#xff09; 前面的例子&#xff0c;我们使用了nodejswebpack&#xff0c;成功创建了包含html&#xff0c;ts&#xff0c;css三个文…

【.net core】使用nssm发布WEB项目

nssm下载地址&#xff1a;NSSM - the Non-Sucking Service Manager 配置方式 修改服务在nssm工具下输入命令:nssm edit jntyjr 其中 jntyjr为添加服务时设置的Service name nssm可以设置任何以参数启动的应用程序以服务形式启动,通过设置参数内容启动服务 以上配置等同于执行…

ReferenceError: primordials is not defined错误解决

问题场景&#xff1a; 从github上拉了一个项目&#xff0c;想要学习一下&#xff0c;在起服务的时候出现了这个问题。 造成的原因&#xff1a; gulp 与 node 版本起冲突。 1&#xff09;首先&#xff0c;安装 gulp&#xff0c;查看版本&#xff1b; npm install gulp -g g…

如何设计科研问卷?

问卷研究法的最大特点在于能在较短时间内调查很多研究对象取得大量的资料&#xff0c;并能对资料进行数量化处理&#xff0c;经济省时&#xff0c;因此是教育研究中使用频率较高、用途较广泛的一种研究方法。问卷研究法的关键在于设计一份信度、效度较高&#xff0c;内容合理的…

二维码怎么分解成链接?线上快速解码教学

怎么分解二维码呢&#xff1f;有些时候我们需要将二维码图片分解成链接使用&#xff0c;所以想要使用解码功能一般都需要通过二维码生成器工具来完成。那么如何在线将二维码分解成链接呢&#xff0c;可能有些小伙伴还不知道怎么操作&#xff0c;下面就给大家分享一下免费二维码…

较真儿学源码系列-PowerJob时间轮源码分析

PowerJob版本&#xff1a;4.3.2-main。 之前分析过PowerJob的启动流程源码&#xff0c;感兴趣的可以查看《较真儿学源码系列-PowerJob启动流程源码分析》 1 简介 试想一下&#xff0c;如果此时有一个需要延迟3s执行的任务&#xff0c;你会怎么实现呢&#xff1f;一种常规的思路…

洗地机哪个牌子好用又实惠?口碑最好的洗地机推荐

智能技术飞速发展的时代&#xff0c;扫地机器人这类智能家电其实也在顺应潮流和用户需求&#xff0c;不断更新迭代。暂且不说市面上现有多少个洗地机品牌&#xff0c;单单一个洗地机品牌旗下&#xff0c;其实每年都会有多个系列的新品亮相&#xff0c;我们面对的选择多了&#…

Python交叉验证实现

目录 <font colorblue size4 face"楷体">HoldOut 交叉验证<font colorred size4 face"楷体">K 折交叉验证<font colorblue size4 face"楷体">分层 K 折交叉验证<font colorblue size4 face"楷体">Leave P Out…

融云 CallPlus + X,通话场景一站式解决方案

融云近期上线的 CallPlus SDK&#xff0c;针对音视频呼叫场景单独设计后端服务 Call Server&#xff0c;信令延时低至 150ms&#xff0c;确保各端计时准确、一致&#xff1b;上线了音视频通话互转、灵活的多人通话、通话记录管理能力等功能。关注【融云全球互联网通信云】了解更…

掌动智能兼容性测试有哪些优势

兼容性测试为企业带来市场竞争优势&#xff0c;并提高用户满意度。在软件开发过程中&#xff0c;将兼容性测试作为一个重要的环节&#xff0c;将为企业的成功和用户满意度打下坚实的基础。那么&#xff0c;掌动智能兼容性测试的具体优势是什么?下面&#xff0c;就来看看具体介…

【面试题】说说你对 async和await 理解

前端面试题库 &#xff08;面试必备&#xff09; 推荐&#xff1a;★★★★★ 地址&#xff1a;前端面试题库 表妹一键制作自己的五星红旗国庆头像&#xff0c;超好看 async await详解 原理&#xff1a; async声明该函数是异步的&#xff0c;且该函数会返回一个…

比例导引详解(Proportional navigation guidance,PNG)-及Python程序

模型算法推导 比例导引是一种制导算法&#xff0c;其经典程度相当于控制器中的PID&#xff0c;在本文中&#xff0c;只对其二维平面的情况做分析&#xff0c;考虑一个拦截弹拦截机动目标&#xff08;固定目标相当于目标速度为0&#xff09;&#xff0c;其运动如下图所示&#…

变配电智能化系统:提高效率与安全性

随着科技的发展&#xff0c;电力系统正在逐步向智能化、数字化方向转型。变配电系统作为电力系统的重要组成部分&#xff0c;其智能化水平直接影响着电力系统的运行效率和稳定性。 一、系统概述 力安科技变配电智能化系统是一种采用先进技术&#xff0c;实现对变配电设…

DD5 进制转换

目录 一、题目 二、分析 三、代码 一、题目 进制转换_牛客题霸_牛客网 二、分析 三、代码 #include <iostream> #include <vector> #include <string> using namespace std; string Greater_than_Ten(int digit)//余数大于等于10的时候转换成对应的字母…

低照度增强算法(图像增强+目标检测+代码)

本文介绍 在增强低光图像时&#xff0c;许多深度学习算法基于Retinex理论。然而&#xff0c;Retinex模型并没有考虑到暗部隐藏的损坏或者由光照过程引入的影响。此外&#xff0c;这些方法通常需要繁琐的多阶段训练流程&#xff0c;并依赖于卷积神经网络&#xff0c;在捕捉长距…

从零搭建开发脚手架 顺应潮流开启升级 - SpringBoot 从2.x 升级到3.x

文章目录 涉及升级项导入包修改SpringBoot3.x中spring.factories功能被移除 涉及升级项 升级JDK 8 -> JDK17 Spring Boot 2.3.7 -> Spring Boot 3.1.3 Mysql5.7.x -> Mysql8.x Mybatis-Puls 3.4.2 -> 3.5.3 knife4j 2.x -> 4.3.x sa-token 1.24.x -> 1.…

Apache Derby的使用

Apache Derby是关系型数据库&#xff0c;可以嵌入式方式运行&#xff0c;也可以独立运行&#xff0c;当使用嵌入式方式运行时常用于单元测试&#xff0c;本篇我们就使用单元测试来探索Apache Derby的使用 一、使用IDEA创建Maven项目 打开IDEA创建Maven项目&#xff0c;这里我…

C++: 模板(进阶)

学习目标 1.了解非类型模板参数 2.了解类模板的特化 3.知道模板分离编译会出现的问题 1.非类型模板参数(整型常量) 模板参数: 1.类型形参:在模板参数列表中,class/typename后的参数名称 2.非类型形参:整型常量 示例: template<class T ,size_t N>class arr{public://....…

Docker和Docker compose的安装使用指南

一&#xff0c;环境准备 Docker运行需要依赖jdk&#xff0c;所以需要先安装一下jdk yum install -y java-1.8.0-openjdk.x86_64 二&#xff0c;Docker安装和验证 1&#xff0c;安装依赖工具 yum install -y yum-utils 2&#xff0c;设置远程仓库 yum-config-manager --add-r…